Open Design源码分析
本文面向对 AI 辅助设计工具内部实现感兴趣的前端工程师与架构师,系统分析 Open Design 的三大核心系统——Agent 适配器层、Prompt Stack 组合引擎、Skill 注册表——的源码架构与关键实现。适合已了解项目基本用法、希望深入理解其技术原理的读者。
目录
-
概述 1.1 项目概况 1.2 技术栈 1.3 核心设计理念 -
核心架构 2.1 整体拓扑 2.2 组件关系 2.3 数据流 -
源码分析 3.1 Agent 适配器系统 3.2 Prompt Stack 组合引擎 3.3 Skill 注册表 3.4 Artifact 流式解析器 -
技术亮点 4.1 多 CLI 统一适配 4.2 分层 Prompt 编排 4.3 文件式可扩展架构 4.4 反 AI-Slop 质量控制体系 4.5 沙盒化安全预览 -
实践指南 5.1 自定义 Agent 接入 5.2 自定义 Skill 开发 5.3 调试技巧 -
总结参考文献
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 技术栈
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;},promptViaStdin: true,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.fallbackModels, available: false };}let version = null;try {const { stdout } = awaitexecFileP(resolved, def.versionArgs, { timeout: 3000 });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 上执行两阶段探测:
-
通过 resolveOnPath(在 PATH 中搜索可执行文件 + Windows 扩展名尝试)确认 CLI 是否可用 -
通过解析 --help输出来判断 CLI 支持哪些可选参数(如--include-partial-messages)
这种能力探测机制使 Open Design 能在不绑定特定 CLI 版本的前提下,动态利用新版本的功能。
3.1.3 流式格式与事件解析
streamFormat 字段定义了四种流式格式:
|
|
|
|
|---|---|---|
claude-stream-json |
|
|
json-event-stream |
|
|
acp-json-rpc |
|
|
plain |
|
|
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 {constparts: string[] = [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_DIRECTIONS: DesignDirection[] = [{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 {constrefs: string[] = [];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 === 0) return'';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, { withFileTypes: true });} 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 = {inside: boolean; // 是否在 <artifact> 标签内buffer: string; // 未解析的缓冲区identifier: string;artifactType: string;title: string;content: string; // 累积的 artifact 内容};
3.4.2 核心解析循环
function* feed(delta: string): 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.identifier, fullContent: state.content };state.inside = false;// ...}}
关键实现细节:
-
findOpenTag支持部分匹配(partial)状态——当缓冲区尾部出现<arti这样的前缀时,解析器暂停并等待更多数据,避免过早将不完整的标签作为文本输出 -
在标签内查找闭合标签时,保留 CLOSE_TAG.length - 1个字符在缓冲区,防止闭合标签跨越两个 SSE chunk -
flush函数在流结束时将所有残留内容输出,确保不丢失任何数据
3.4.3 事件类型
解析器输出四种类型的 ArtifactEvent:
|
|
|
|
|---|---|---|
text |
|
|
artifact:start |
<artifact ...> 开始标签 |
|
artifact:chunk |
|
|
artifact:end |
</artifact> 结束标签 |
|
前端根据事件类型决定如何更新 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 架构中最精巧的部分。六个层次按固定优先级组合:
-
DISCOVERY_AND_PHILOSOPHY(硬规则,最高优先级)——三回合协议、品牌资产协议、五维自评 -
BASE_SYSTEM_PROMPT(身份宪章)——设计师身份 + 通用工作流 -
DESIGN.md(视觉令牌)——权威的配色/字体/间距/组件规则 -
SKILL.md + Pre-flight(工作流)——Skill 特定的结构和工作流步骤 -
Project Metadata(项目上下文)——用户在创建项目时选择的参数 -
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;},promptViaStdin: true,streamFormat: 'json-event-stream',eventParser: 'my-agent', // 需要在 daemon 中添加对应的解析器}
如果该 CLI 输出的 JSON 格式与现有解析器不兼容,需要在 Daemon 中添加解析器,解析 tool_use、text_delta、thinking 等事件类型。
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
夜雨聆风