乐于分享
好东西不私藏

Open Design源码分析

Open Design源码分析

本文面向对 AI 辅助设计工具内部实现感兴趣的前端工程师与架构师,系统分析 Open Design 的三大核心系统——Agent 适配器层、Prompt Stack 组合引擎、Skill 注册表——的源码架构与关键实现。适合已了解项目基本用法、希望深入理解其技术原理的读者。

目录

  1. 概述  1.1 项目概况  1.2 技术栈  1.3 核心设计理念
  2. 核心架构  2.1 整体拓扑  2.2 组件关系  2.3 数据流
  3. 源码分析  3.1 Agent 适配器系统  3.2 Prompt Stack 组合引擎  3.3 Skill 注册表  3.4 Artifact 流式解析器
  4. 技术亮点  4.1 多 CLI 统一适配  4.2 分层 Prompt 编排  4.3 文件式可扩展架构  4.4 反 AI-Slop 质量控制体系  4.5 沙盒化安全预览
  5. 实践指南  5.1 自定义 Agent 接入  5.2 自定义 Skill 开发  5.3 调试技巧
  6. 总结参考文献

1. 概述

1.1 项目概况

Open Design 是 Anthropic Claude Design 的开源替代方案,由 nexu-io 团队开发,采用 Apache-2.0 许可证。项目约 18,700 行 TypeScript/JavaScript 代码,核心架构可概括为”Web App + 本地 Daemon + 用户已有的 Agent CLI”。

从源码角度看,Open Design 不包含任何 AI 模型运行时——它的全部价值集中在三个层次:

  • 将不同 Agent CLI 统一适配为标准化事件流
  • 将三层内容(基础系统提示词 + Design System + Skill 工作流)组装为结构化 Prompt Stack
  • 将 Agent 输出解析为可渲染的 Artifact

1.2 技术栈

层次
技术选型
说明
前端
Next.js 16 App Router + React 18 + TypeScript
支持 SSR 和 Serverless 路由
后端 Daemon
Node.js + Express
本地常驻进程,管理 Agent 生命周期
数据库
SQLite(better-sqlite3)
项目/会话/消息/标签页持久化
Agent 通信
child_process.spawn + SSE
stdio 驱动 Agent,SSE 推送事件到前端
流式解析
Claude Stream JSON / ACP JSON-RPC / Pi RPC
三种流式协议的统一解析
导出
Puppeteer(PDF)、pptxgenjs(PPTX)、archiver(ZIP)
多格式导出管线
构建工具
pnpm workspace + Corepack
Monorepo 依赖管理

1.3 核心设计理念

从源码层面可以提炼出六条设计原则,每一条都直接体现在代码结构中:

  • 用已有 CLI 而非内置 Agent:Daemon 的 agents.ts 中定义了 10 个 Agent 适配器,每个只需约 20 行配置,将不同的 CLI 映射为统一的 buildArgs + streamFormat
  • Skill 是文件而非插件:skills.ts 扫描 skills/*/SKILL.md,解析 YAML frontmatter 后直接注册
  • Design System 是 Markdown 而非 JSON 主题:遵循 9 段式 Schema,Agent 直接 grep 需要的 Section
  • Prompt Stack 是可编排的字符串组合:system.ts 中 composeSystemPrompt 将 Discovery 指令 + 身份宪章 + DESIGN.md + SKILL.md + 项目元数据 + Deck 框架按固定顺序拼接
  • 初始化表单是硬编码 RULE 1:discovery.ts 中规定了 Turn 1 必须先返回 <question-form>,这是反 AI-Slop 的第一道门槛
  • Daemon 使 Agent 具备真实文件系统访问能力:Agent 被 spawn 在 .od/projects// 目录下,拥有 Read/Write/Bash/WebFetch 能力

2. 核心架构

2.1 整体拓扑

Open Design 支持三种部署拓扑,共享同一套 Web Bundle,差异仅在于 Transport 层:

  • Topology A(全本地):浏览器 → Next.js Dev Server → 本地 Daemon → Agent CLI
  • Topology B(Vercel + 本地 Daemon):浏览器 → Vercel 部署的 Web App → WebSocket 隧道 → 本地 Daemon → Agent CLI
  • Topology C(Vercel 直连 API):浏览器 → Vercel Serverless → Anthropic Messages API(BYOK)

全本地模式下的架构如下:

浏览器├── React SPA(聊天 · 文件工作区 · iframe 预览)├─ /api/*(Next.js rewrite 到 Daemon)│  └── 本地 Daemon(Express + SQLite)│        ├── /api/agents(Agent 检测与模型列表)│        ├── /api/skills(Skill 扫描与注册)│        ├── /api/design-systems(Design System 加载)│        ├── /api/projects(项目管理)│        └── /api/chat(SSE 流式对话)│  └──child_process.spawn(AgentCLI,[...],{cwd })│  └──AgentCLI 进程│  ├──ReadDESIGN.md+ SKILL.md│  ├──Writeindex.html 到工作目录│  └──stdout→DaemonSSE→ 前端预览└─ Anthropic SDK(BYOK 回退路径,仅 Topology C)

2.2 组件关系

Open Design 的逻辑组件分为三个运行时:

Web App 层(apps/web/src/):

  • App.tsx——编排 Mode/Skill/Design System 选择器 + Send
  • providers/——Daemon SSE 和 BYOK API 两种 Transport
  • prompts/——系统提示词组合引擎
  • artifacts/——流式 <artifact> 标签解析器
  • runtime/——iframe srcdoc 沙盒渲染、Markdown 渲染、导出

Daemon 层(apps/daemon/src/):

  • server.ts——Express 路由,处理 /api/* 请求
  • agents.ts——Agent PATH 扫描器与各 CLI 的 argv 构造器
  • skills.ts——SKILL.md 读取与 frontmatter 解析
  • design-systems.ts——DESIGN.md 加载与色板提取
  • claude-stream.ts——Claude Code 的流式 JSON 解析器

Agent CLI 层(用户本地安装):

  • Claude Code、Codex CLI、Cursor Agent、Gemini CLI 等
  • 由 Daemon 通过 child_process.spawn 驱动

2.3 数据流

一次完整的”生成原型”对话的数据流:

流程执行说明:

  • 步骤 1-2:用户在 Web 界面输入 Prompt,前端调用 composeSystemPrompt() 将 Discovery 指令 + DESIGN.md + SKILL.md 拼接为完整系统提示词
  • 步骤 3-4:Daemon 创建项目工作目录,将 DESIGN.md 和 SKILL.md 写入文件系统(Agent 的 cwd),使其可被 Agent 的工具(Read)直接访问
  • 步骤 5-9:Daemon spawn Agent CLI,Agent 的标准输出被逐行解析为类型化事件(text/thinking/tool_use/tool_result/status),通过 SSE 推送到前端
  • 步骤 10-11:Agent 生成 HTML 文件后退出,Daemon 通知前端文件就绪
  • 步骤 12-14:前端读取生成的 HTML,通过 srcdoc 注入沙盒 iframe,呈现最终预览

3. 源码分析

3.1 Agent 适配器系统

Agent 适配器系统是整个 Open Design 架构中最关键的抽象层,定义在 apps/daemon/src/agents.ts 中。

3.1.1 核心数据结构

AGENT_DEFS 数组定义了每个 Agent 的完整元数据:

exportconstAGENT_DEFS = [  {id'claude',name'Claude Code',bin'claude',versionArgs: ['--version'],helpArgs: ['--help'],capabilityFlags: {'--include-partial-messages''partialMessages','--add-dir''addDir',    },fallbackModels: [DEFAULT_MODEL_OPTION,      { id'sonnet'label'Sonnet (alias)' },      { id'opus'label'Opus (alias)' },// ...    ],buildArgs(_prompt, _imagePaths, extraAllowedDirs = [], options = {}) => {const caps = agentCapabilities.get('claude') || {};const args = ['-p''--output-format''stream-json''--verbose',      ];if (caps.partialMessages) {        args.push('--include-partial-messages');      }if (options.model && options.model !== 'default') {        args.push('--model', options.model);      }      args.push('--add-dir', ...extraAllowedDirs);      args.push('--permission-mode''bypassPermissions');return args;    },promptViaStdintrue,streamFormat'claude-stream-json',  },// ... 其余 9 个 Agent 定义];

关键设计点:

  • buildArgs 是闭包而非纯数据,允许每个 Agent 根据运行时参数(模型选择、额外目录、能力标记)动态构造不同的命令行参数
  • capabilityFlags 机制通过解析 --help 输出来探测已安装 CLI 支持的功能标记,避免向旧版本传递不支持的参数
  • promptViaStdin 字段解决了 Windows 上 CreateProcess 命令行长度限制(~32KB),将 Prompt 通过标准输入管道传送而非作为命令行参数

3.1.2 Agent 检测与能力探测

asyncfunctionprobe(def) {const resolved = resolveOnPath(def.bin);if (!resolved) {return { ...stripFns(def), models: def.fallbackModelsavailablefalse };  }let version = null;try {const { stdout } = awaitexecFileP(resolved, def.versionArgs, { timeout3000 });    version = stdout.trim().split('\n')[0];  } catch {// binary exists but --version failed; still mark available  }// Probe --help to record capability flagsif (def.helpArgs && def.capabilityFlags) {const caps = {};try {const{stdout}=awaitexecFileP(resolved,def.helpArgs,{timeout:5000 });for (const [flag, key] ofObject.entries(def.capabilityFlags)) {        caps[key] = stdout.includes(flag);      }    } catch {// If --help fails, leave caps empty — buildArgs falls back to safe baseline    }    agentCapabilities.set(def.id, caps);  }// ...}

probe 函数在每个 Agent 上执行两阶段探测:

  1. 通过 resolveOnPath(在 PATH 中搜索可执行文件 + Windows 扩展名尝试)确认 CLI 是否可用
  2. 通过解析 --help 输出来判断 CLI 支持哪些可选参数(如 --include-partial-messages

这种能力探测机制使 Open Design 能在不绑定特定 CLI 版本的前提下,动态利用新版本的功能。

3.1.3 流式格式与事件解析

streamFormat 字段定义了四种流式格式:

格式
适用 Agent
解析方式
claude-stream-json
Claude Code
逐行 JSON 解析,提取 content_block_delta 等类型化事件
json-event-stream
Codex, Gemini, OpenCode, Cursor
逐行 JSON 解析,各 CLI 有专用 eventParser
acp-json-rpc
Hermes, Kimi CLI, Kiro CLI
ACP 协议 JSON-RPC over stdio
plain
Qwen Code
原样文本流,逐 chunk 转发

Daemon 的聊天处理函数根据 streamFormat 选择对应的解析器,将原始 stdout 输出统一转换为类型化 UI 事件:

text → 聊天消息内容thinking → 思考过程(可折叠展示)tool_use → 工具调用卡片(Read/Write/Bash/WebFetch)tool_result → 工具调用结果status → 状态更新(如 "完成")

3.2 Prompt Stack 组合引擎

Prompt Stack 组合引擎是 Open Design 区别于普通 AI 工具的核心差异化组件,定义在 apps/web/src/prompts/ 目录下。

3.2.1 组合函数入口

system.ts 的 composeSystemPrompt 函数按固定优先级拼接 Prompt 栈:

exportfunctioncomposeSystemPrompt({  skillBody, skillName, skillMode,  designSystemBody, designSystemTitle,  metadata, template,}: ComposeInput): string {constpartsstring[] = [DISCOVERY_AND_PHILOSOPHY,           // Layer 1: 硬规则(最高优先级)'\n\n---\n\n# Identity and workflow charter\n\n',BASE_SYSTEM_PROMPT// Layer 2: 身份宪章(背景)  ];if (designSystemBody) {    parts.push(/* Layer 3: DESIGN.md 设计令牌 */);  }if (skillBody) {const preflight = derivePreflight(skillBody);    parts.push(/* Layer 4: SKILL.md 工作流 + 预飞行指令 */);  }const metaBlock = renderMetadataBlock(metadata, template);if (metaBlock) parts.push(/* Layer 5: 项目元数据 */);// Layer 6: Deck 框架指令(仅 Deck 模式,无 Skill 种子时)if (isDeckProject && !hasSkillSeed) {    parts.push(`\n\n---\n\n${DECK_FRAMEWORK_DIRECTIVE}`);  }return parts.join('');}

组合逻辑的设计要点:

  • Layer 1 (DISCOVERY_AND_PHILOSOPHY) 放在最前面——它包含 RULE 1/2/3 硬规则,必须优先于后续的软性措辞
  • Layer 2 (BASE_SYSTEM_PROMPT) 提供身份和通用工作流背景,如果 Layer 1 和它有冲突,Layer 1 优先
  • Layer 3 将 DESIGN.md 标注为 “authoritative”(权威),禁止 Agent 自由发明调色板外的颜色
  • Layer 4 包含 derivePreflight 动态生成的预飞行指令——检测 Skill 是否附带 assets/template.html 和 references/checklist.md,如果有则在 SKILL.md 正文前强制注入 Read 指令
  • Layer 6 (DECK_FRAMEWORK_DIRECTIVE) 只在 Deck 项目且无 Skill 种子时注入——这是防止 Agent 编写有 bug 的 slide 导航/缩放/打印逻辑的最后防线

3.2.2 Discovery 指令层

discovery.ts 输出了 DISCOVERY_AND_PHILOSOPHY 常量,这是一个约 300 行的模板字符串,包含了 Open Design 最核心的对话行为规则。

三回合协议(Three-Turn Protocol)是整个系统的行为基石:

Turn 1: 一句 prose + <question-form id="discovery"> + STOPTurn 2: 分支处理品牌答案  - Branch A: "Pick a direction for me" → 方向选择表单 + STOP  - Branch B: "I have a brand spec" → 品牌资产五步提取 → TodoWrite  - Branch C: 其他 → TodoWrite 直接开始Turn 3+: TodoWrite 计划 → live updates → checklist → 五维自评 → <artifact>

表单 JSON Schema 直接硬编码在 Prompt 中:

{"questions":[{"id":"output","label":"What are we making?","type":"radio","required":true,"options":["Slide deck / pitch","Single web prototype / landing", ...]},{"id":"platform","label":"Primary surface","type":"radio","options":["Mobile (iOS/Android)","Desktop web", ...]},{"id":"brand","label":"Brand context","type":"radio","options":["Pick a direction for me","I have a brand spec","Match a reference site"]},// ... 其余问题]}

3.2.3 视觉方向库

directions.ts 定义了 5 种确定性视觉方向,每种方向包含 CSS-ready 的 OKLch 调色板和字体栈:

exportconstDESIGN_DIRECTIONSDesignDirection[] = [  {id'editorial-monocle',label'Editorial — Monocle / FT magazine',displayFont"'Iowan Old Style', 'Charter', Georgia, serif",bodyFont"-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",palette: {bg:'oklch(97% 0.012 80)'// off-white papersurface'oklch(99% 0.005 80)',fg:'oklch(20% 0.02 60)'// inkmuted:   'oklch(48% 0.015 60)',border:  'oklch(89% 0.012 80)',accent:  'oklch(58% 0.16 35)',     // warm rust / clay    },posture: ['serif display, sans body, mono for metadata only','no shadows, no rounded cards — borders + whitespace do the work',    ],  },// ... 其余 4 种方向];

renderDirectionSpecBlock() 函数将 5 种方向渲染为 Markdown 文本嵌入系统提示词,renderDirectionFormBody() 则生成 JSON 表单数据供前端渲染方向选择器卡片。

3.2.4 预飞行指令生成

derivePreflight 函数检测 Skill Body 中引用了哪些侧文件(assets/template.html、references/checklist.md 等),生成一条强制 Read 指令:

functionderivePreflight(skillBody: string): string {constrefsstring[] = [];if (/assets\/template\.html/.test(skillBody)) refs.push('`assets/template.html`');if (/references\/layouts\.md/.test(skillBody)) refs.push('`references/layouts.md`');if (/references\/checklist\.md/.test(skillBody)) refs.push('`references/checklist.md`');// ...if (refs.length === 0return'';return` **Pre-flight (do this before any other tool):** Read ${refs.join(', ')} ...`;}

这是 Open Design 反 AI-Slop 机制的关键环节——即使 Skill Body 自身的 Workflow 已经包含了 Read 步骤,但 Agent 在上下文不足时可能跳过。一条简短的前置指令显著提高了遵循率。

3.3 Skill 注册表

Skill 注册表定义在 apps/daemon/src/skills.ts 中,负责扫描 skills/ 目录、解析 YAML frontmatter、推断缺失字段、生成最终注册项。

3.3.1 扫描与解析

exportasyncfunctionlistSkills(skillsRoot) {const out = [];let entries = [];try {    entries = awaitreaddir(skillsRoot, { withFileTypestrue });  } catch {return out; // 目录不存在时返回空列表  }for (const entry of entries) {if (!entry.isDirectory()) continue;const dir = path.join(skillsRoot, entry.name);const skillPath = path.join(dir, "SKILL.md");try {const stats = awaitstat(skillPath);if (!stats.isFile()) continue;const raw = awaitreadFile(skillPath, "utf8");const { data, body } = parseFrontmatter(raw);const hasAttachments = awaitdirHasAttachments(dir);const mode = data.od?.mode || inferMode(body, data.description);// ...      out.push({id: data.name || entry.name,name: data.name || entry.name,description: data.description || "",        mode,previewType: data.od?.preview?.type || "html",designSystemRequired: data.od?.design_system?.requires ?? true,body: hasAttachments ? withSkillRootPreamble(body, dir) : body,        dir,// ... 其他规范化字段      });    } catch {// Skip unreadable entries — this is discovery, not validation.    }  }return out;}

容错策略:listSkills 采用发现而非验证模式——单个 Skill 解析失败不影响其他 Skill。try/catch 包裹在最内层,不因一个损坏的 Skill 而阻塞整个注册表。

3.3.2 Mode 推断

当 Skill 的 od.mode 字段缺失时,inferMode 函数通过关键词匹配推断 Mode:

functioninferMode(body, description) {const hay = `${description ?? ""}\n${body ?? ""}`.toLowerCase();if (/\bimage|poster|illustration|photography|图片|海报|插画/.test(hay)) return"image";if (/\bvideo|motion|shortform|animation|视频|动效|短片/.test(hay)) return"video";if (/\baudio|music|jingle|tts|sound|音频|音乐|配音|音效/.test(hay)) return"audio";if (/\bppt|deck|slide|presentation|幻灯|投影/.test(hay)) return"deck";if (/\bdesign[- ]system|\bdesign\.md|\bdesign tokens/.test(hay)) return"design-system";if (/\btemplate\b/.test(hay)) return"template";return"prototype";}

这确保了零配置兼容性——任何遵循 Claude Code SKILL.md 规范的 Skill 只需放入 skills/ 目录即可被正确识别。

3.3.3 Skill Root Preamble

当 Skill 附带侧文件时,withSkillRootPreamble 在 Skill Body 前插入一条绝对路径指令:

functionwithSkillRootPreamble(body, dir) {const preamble = [`> **Skill root (absolute):** \`${dir}\``,">","> This skill ships side files alongside `SKILL.md`. When the workflow","> below references relative paths such as `assets/template.html` or","> `references/layouts.md`, resolve them against the skill root above","> and open them via their full absolute path.","","",  ].join("\n");return preamble + body;}

这是解决 Agent CWD 与 Skill 目录不一致问题的关键——Agent 的工作目录是 .od/projects//,而 Skill 文件在 skills//,没有绝对路径时 Agent 无法通过相对路径找到模板文件。

3.4 Artifact 流式解析器

Artifact 解析器定义在 apps/web/src/artifacts/parser.ts 中,是一个生成器函数驱动的流式状态机。

3.4.1 状态机设计

解析器维护一个内部状态对象,通过 feed 和 flush 两个生成器函数驱动状态转换:

typeParserState = {insideboolean;       // 是否在 <artifact> 标签内bufferstring;        // 未解析的缓冲区identifierstring;artifactTypestring;titlestring;contentstring;       // 累积的 artifact 内容};

3.4.2 核心解析循环

functionfeed(deltastring): Generator<ArtifactEvent> {  state.buffer += delta;while (state.buffer.length > 0) {if (!state.inside) {const open = findOpenTag(state.buffer);if (open.kind === 'none') {yield { type'text'delta: state.buffer };        state.buffer = '';return;      }if (open.kind === 'partial') {// 潜在的开始标签前缀在缓冲区尾部,等待更多数据if (open.start > 0) {yield{type:'text',delta:state.buffer.slice(0,open.start) };          state.buffer = state.buffer.slice(open.start);        }return;      }// 找到了完整的开始标签const attrs = parseAttrs(open.attrs);      state.inside = true;      state.identifier = attrs['identifier'] ?? '';yield { type'artifact:start'identifier: state.identifier, ... };continue;    }// 在标签内,查找结束标签const closeIdx = state.buffer.indexOf(CLOSE_TAG);if (closeIdx === -1) {// 保留足够字节以检测跨 chunk 的部分闭合标签const flushUpTo = state.buffer.length - (CLOSE_TAG.length - 1);if (flushUpTo > 0) {const chunk = state.buffer.slice(0, flushUpTo);        state.content += chunk;yield{type:'artifact:chunk',identifier:state.identifier,delta:chunk };      }return;    }// 找到结束标签,完成 artifactyield { type'artifact:end'identifier: state.identifierfullContent: state.content };    state.inside = false;// ...  }}

关键实现细节:

  • findOpenTag 支持部分匹配(partial)状态——当缓冲区尾部出现 <arti 这样的前缀时,解析器暂停并等待更多数据,避免过早将不完整的标签作为文本输出
  • 在标签内查找闭合标签时,保留 CLOSE_TAG.length - 1 个字符在缓冲区,防止闭合标签跨越两个 SSE chunk
  • flush 函数在流结束时将所有残留内容输出,确保不丢失任何数据

3.4.3 事件类型

解析器输出四种类型的 ArtifactEvent:

事件类型
触发时机
携带数据
text
标签外的文本
delta
artifact:start
检测到完整 <artifact ...> 开始标签
identifier, artifactType, title
artifact:chunk
标签内的内容片段
identifier, delta
artifact:end
检测到 </artifact> 结束标签
identifier, fullContent

前端根据事件类型决定如何更新 UI:text 追加到聊天消息,artifact:start 创建预览区域,artifact:chunk 累积内容,artifact:end 触发 iframe 渲染。

4. 技术亮点

4.1 多 CLI 统一适配

Open Design 通过约 200 行的 AGENT_DEFS 数组完成了 10 个 Agent CLI 的适配,每个 Agent 只需 5 个核心字段(bin, versionArgs, buildArgs, streamFormat, promptViaStdin),平均每个 Agent 约 20 行配置。

适配层的设计精髓在于 buildArgs 使用了闭包而非纯数据:

  • Claude Code 通过 --add-dir 传递额外目录(能力探测门控)、通过 --permission-mode bypassPermissions 禁用交互式权限提示
  • Codex 通过 -c sandbox_workspace_write.network_access=true 开启网络访问、通过 OD_CODEX_DISABLE_PLUGINS 环境变量控制插件加载
  • Cursor Agent 通过 --print --stream-partial-output --force --trust 组合实现非交互模式
  • 所有支持 stdin 的 CLI 使用 promptViaStdin: true 将 Prompt 通过管道传送,绕开 Windows 命令行长度限制

模型选择系统同样考虑周全:每个 Agent 有 fallbackModels 静态提示列表,部分 Agent(OpenCode、Cursor Agent、Hermes、Kimi CLI、Pi)支持通过 CLI 子命令动态获取模型列表(listModels / fetchModels),并始终预置一个 'default' 选项表示”让 CLI 自行决定”。

4.2 分层 Prompt 编排

Prompt Stack 的分层设计是 Open Design 架构中最精巧的部分。六个层次按固定优先级组合:

  1. DISCOVERY_AND_PHILOSOPHY(硬规则,最高优先级)——三回合协议、品牌资产协议、五维自评
  2. BASE_SYSTEM_PROMPT(身份宪章)——设计师身份 + 通用工作流
  3. DESIGN.md(视觉令牌)——权威的配色/字体/间距/组件规则
  4. SKILL.md + Pre-flight(工作流)——Skill 特定的结构和工作流步骤
  5. Project Metadata(项目上下文)——用户在创建项目时选择的参数
  6. DECK_FRAMEWORK_DIRECTIVE(Deck 框架,最低优先级但最后写入)——导航/计数器/打印样式表

层次 1 放在最前面确保硬规则优先于后续层的软性措辞;层次 6 放在最后确保它覆盖前面对 slide 处理的任何更弱的指令。”前置硬规则 + 后置覆盖”的双向控制是 Prompt 工程中的一个重要模式。

4.3 文件式可扩展架构

Open Design 的可扩展性建立在三个零注册原则之上:

  • Skill:放入 skills/ 目录的文件夹,重启 Daemon 后自动出现在选择器。listSkills 函数按目录扫描,inferMode 函数从关键词推断类型
  • Design System:放入 design-systems/ 目录的单个 Markdown 文件。9 段式 Schema 是泛化的,Agent 通过 grep 按需读取特定 Section
  • Agent CLI:在 AGENT_DEFS 数组中添加一项,约 20 行 TypeScript 代码

这种文件式架构使得贡献门槛降到最低——增加一个 Skill 无需修改任何注册代码,增加一个 Design System 无需运行任何同步脚本。

4.4 反 AI-Slop 质量控制体系

Open Design 的源码中嵌入了六重质量管理机制,全部以 Prompt 指令形式实现(而非运行时检查):

第一重——强制初始化表单(discovery.ts RULE 1):Turn 1 必须返回 <question-form>,明确禁止直接生成代码。这将设计决策权前置给用户,用 30 秒的表单填写取代了 30 分钟的反复迭代。

第二重——品牌资产五步协议(discovery.ts RULE 2 Branch B):定位 → 下载 → 提取色值 → 编码 brand-spec.md → 复述确认。核心原则是”绝不允许 Agent 从记忆中猜测品牌色值”。

第三重——P0/P1/P2 检查清单(derivePreflight + RULE 3 Step 7):每个 Skill 可附带 references/checklist.md,Agent 必须在生成后逐项自检,P0 项全部通过方可输出。

第四重——五维自评(discovery.ts RULE 3 Step 8):Agent 在输出前对自己的作品在五个维度(哲学/层次/执行/具体性/克制)评分 1-5,任意维度低于 3 分必须修复。

第五重——AI Slop 黑名单(discovery.ts Section C):明确列出被禁止的模式:紫色渐变、通用 Emoji 图标、圆角左边框卡片、Inter 作为 Display 字体、编造的数据指标。

第六重——诚实占位符:当 Agent 没有真实数据时,写  或灰块标注,禁止编造 “10× faster” 这类虚假数据。

4.5 沙盒化安全预览

预览系统使用 <iframe sandbox="allow-scripts"> 实现安全隔离,不允许 allow-same-origin

  • 静态 HTML 通过 srcdoc 属性直接加载,完全隔离于宿主 DOM
  • JSX Artifact 注入 vendored React 18 + Babel standalone 动态编译执行
  • Agent 每次写入文件后触发 100ms 防抖重载,降低渲染闪烁
  • Agent 的 cwd 限定在 .od/projects// 目录下,权限控制继承自 Agent CLI 自身的权限系统

5. 实践指南

5.1 自定义 Agent 接入

在 apps/daemon/src/agents.ts 的 AGENT_DEFS 数组中添加一项:

{id'my-agent',name'My Agent',bin'my-agent-cli',versionArgs: ['--version'],fallbackModels: [DEFAULT_MODEL_OPTION,    { id'my-model'label'My Model' },  ],buildArgs(_prompt, _imagePaths, _extra, options = {}) => {const args = ['--no-interactive''--output-format''json'];if (options.model && options.model !== 'default') {      args.push('--model', options.model);    }return args;  },promptViaStdintrue,streamFormat'json-event-stream',eventParser'my-agent',  // 需要在 daemon 中添加对应的解析器}

如果该 CLI 输出的 JSON 格式与现有解析器不兼容,需要在 Daemon 中添加解析器,解析 tool_usetext_deltathinking 等事件类型。

5.2 自定义 Skill 开发

创建文件结构:

skills/my-skill/├── SKILL.md              # 必需:YAML frontmatter + Markdown 工作流├── assets/│   └── template.html     # 推荐:CSS 类系统 + CSS 变量占位符├── references/│   ├── layouts.md        # 粘贴即用的区域/页面/幻灯片骨架│   └── checklist.md      # P0/P1/P2 自检清单└── example.html          # 推荐:手搓的真实设计交付物

SKILL.md 的 frontmatter 中声明 od.mode 决定 Skill 在 UI 中的分组,od.design_system.sections 声明需要注入的 DESIGN.md Section(减少 Prompt token 消耗)。

5.3 调试技巧

  • Daemon 日志:pnpm tools-dev logs 可查看 Daemon/Web/Desktop 日志,大多数问题在 stderr 末尾几行即可定位
  • Agent 交互调试:检查 Daemon 拼接的完整 Prompt 内容(日志中搜索 “systemPrompt”),验证 DESIGN.md 和 SKILL.md 是否正确注入
  • Artifact 不渲染:确认 Agent 输出中是否包含 <artifact> 标签。如果没有,可能是 Prompt Stack 中的输出格式约束未被模型遵循
  • SSE 断开排查:检查 Nginx 是否启用了 gzip(gzip 会缓冲分块 SSE 响应),需要设置 proxy_buffering off; gzip off;

6. 总结

  • Open Design 的源码架构围绕三个核心抽象展开:Agent 适配器层(约 200 行,10 个 CLI 统一适配)、Prompt Stack 组合引擎(6 层分层组合,硬规则前置)、Skill/Design System 注册表(文件式零注册扩展)
  • Agent 适配器系统通过 buildArgs 闭包 + streamFormat + 能力探测实现多 CLI 统一接口,每个新 Agent 接入平均只需约 20 行代码
  • Prompt Stack 组合引擎的”前置硬规则 + 后置覆盖”模式是 Prompt 工程的重要设计模式:DISCOVERY_AND_PHILOSOPHY 在前确保 RULE 1/2/3 不可绕过,DECK_FRAMEWORK_DIRECTIVE 在后确保 Deck 渲染的可靠性
  • 六重反 AI-Slop 机制全部以 Prompt 指令实现:强制初始化表单、品牌资产五步协议、P0/P1/P2 检查清单、五维自评、AI Slop 黑名单、诚实占位符——无需运行时检查
  • 文件式架构将贡献门槛降到最低:Skill 即文件夹,Design System 即单个 Markdown 文件,Agent 即约 20 行 TypeScript 配置
  • 源码的组织体现了”提示词即产品”的核心理念——prompts/ 目录下的 5 个 TypeScript 文件(discovery.ts、system.ts、directions.ts、official-system.ts、deck-framework.ts)是项目价值的真正载体

参考文献

[1] Open Design GitHub 仓库:https://github.com/nexu-io/open-design

[2] agents.ts(Agent 适配器源码):https://github.com/nexu-io/open-design/blob/main/apps/daemon/src/agents.ts

[3] system.ts(Prompt 组合引擎源码):https://github.com/nexu-io/open-design/blob/main/apps/web/src/prompts/system.ts

[4] discovery.ts(Discovery 指令层源码):https://github.com/nexu-io/open-design/blob/main/apps/web/src/prompts/discovery.ts

[5] directions.ts(视觉方向库源码):https://github.com/nexu-io/open-design/blob/main/apps/web/src/prompts/directions.ts

[6] skills.ts(Skill 注册表源码):https://github.com/nexu-io/open-design/blob/main/apps/daemon/src/skills.ts

[7] parser.ts(Artifact 流式解析器源码):https://github.com/nexu-io/open-design/blob/main/apps/web/src/artifacts/parser.ts

[8] architecture.md(架构文档):https://github.com/nexu-io/open-design/blob/main/docs/architecture.md

[9] skills-protocol.md(Skills 协议文档):https://github.com/nexu-io/open-design/blob/main/docs/skills-protocol.md

[10] huashu-design(设计哲学来源):https://github.com/alchaincyf/huashu-design