文章目录
- 一、从「许愿池」到「工作台」
- [二、核心洞察:Agent 缺的不是能力,是指称手段](#二、核心洞察:Agent 缺的不是能力,是指称手段)
- 三、整体架构:四层结构
- [四、关键演进:从本地网页服务到原生 widget](#四、关键演进:从本地网页服务到原生 widget)
- 五、数据模型:为什么画布必须存在用户项目里
- 六、四条核心工作流
-
- [6.1 打开画布](#6.1 打开画布)
- [6.2 生成图片:让框的尺寸成为 prompt 的一部分](#6.2 生成图片:让框的尺寸成为 prompt 的一部分)
- [6.3 按标注改图:这是整个项目的灵魂](#6.3 按标注改图:这是整个项目的灵魂)
- [6.4 AI HTML 与 AI Slides:把画布变成产出容器](#6.4 AI HTML 与 AI Slides:把画布变成产出容器)
- [七、Skill 机制:Agent 侧的行为契约](#七、Skill 机制:Agent 侧的行为契约)
- 八、上手实践
- 九、局限与思考
- 十、可迁移的五条设计经验
- 结语

当 Coding Agent 开始处理图像与视觉设计,纯文字对话就成了瓶颈。Cowart 用一块 tldraw 画布,把「指哪打哪」还给了人类。
一、从「许愿池」到「工作台」
用 Codex、Claude Code 这类 Coding Agent 干活,有一种熟悉的别扭感:你把需求写成一段话,扔进对话框,然后祈祷它理解得八九不离十。文字处理代码时问题不大------函数名、文件路径、行号都是精确的坐标。但一旦任务进入视觉领域,坐标就消失了。
试着用纯文字描述这样一个修改:
「把右上角那个价格标签往左移一点,字号调小,颜色换成和下面按钮一致的那种蓝,另外中间那块留白太大了,把插图放大填一填。」
每一个「那个」「一点」「那种」都是一次信息损耗。人和人沟通时靠的是手指------指着屏幕说「这里」。而在对话框里,手指被剥夺了。
Cowart 解决的正是这件事。它是一个面向 Codex 的原生无限画布 widget 插件,基于 tldraw 构建,让 Codex 不只读文字提示词,还能读到你画在图上的箭头、圈选和批注文字。项目 2026 年 6 月开源,MIT 许可,短时间内在 GitHub 收获五千余星,作者是钟二信(ZHONG XIN),一位有 Sketch 插件与设计工具背景的产品经理。
本文面向的读者是:想理解 Agent 与图形界面如何耦合的开发者,正在做 AI 工具前端的工程师,以及想把 Codex 用作日常视觉工作台的重度用户。我们会从「为什么需要画布」讲到代码结构、数据模型、MCP widget 机制,再到实际使用中的取舍与局限。
二、核心洞察:Agent 缺的不是能力,是指称手段
先把问题定义清楚。当前的图像生成模型(GPT Image 系列及同类)在生成质量上已经足够,真正卡住普通用户的是两个环节:
第一,prompt 的冷启动。 看到一张好图,你不知道该怎么描述它才能复现。
第二,迭代时的指称(reference)。 生成之后要改,改的往往是局部------某个位置、某个元素、某种比例关系。语言在描述空间关系时天生笨拙,而这恰恰是设计工作的主战场。
这两点合起来,导致一个荒谬的现象:模型能力足够强,但人类的表达带宽不够,于是产出停留在「差不多能看」,很难推进到「就是我要的」。
Cowart 的思路不是加强 prompt 工程,而是换一种输入模态。画布上的一个箭头,等价于一段冗长的方位描述;一个圈,等价于「我说的是这块区域」;框的尺寸本身,等价于「按这个比例生成」。空间信息用空间方式传递,几乎零损耗。
更进一步,这套交互还带来了「留痕」的副作用:原图、标注、修订图并排放在画布上,一次迭代的完整推理链条可见可回溯。这是对话式界面很难提供的------聊天记录是线性的,而设计过程是并行发散的。
三、整体架构:四层结构
Cowart 的仓库结构相当克制,顶层只有几个目录:
cowart/
├── .codex-plugin/ # Codex 插件声明
├── .mcp.json # MCP server 配置
├── mcp/
│ ├── server.mjs # MCP 服务主入口
│ └── lib/
│ ├── plugin-root.mjs
│ ├── widget-resource.mjs
│ ├── cowart-static-widget.mjs
│ └── canvas-storage.mjs
├── skills/ # 三个 Agent skill 定义
├── src/ # tldraw 画布前端(React)
├── scripts/ # start-mcp / probe-mcp / vite-build-once / start-canvas.sh
├── public/
├── index.html
├── vite.config.js
└── package.json
映射成运行时的四层:
| 层 | 职责 | 关键实现 |
|---|---|---|
| 插件声明层 | 向 Codex 注册 skill 与 MCP server | .codex-plugin/、.mcp.json |
| MCP 服务层 | 暴露工具、托管 widget 资源、读写画布数据 | mcp/server.mjs 及 mcp/lib/* |
| 画布前端层 | 无限画布、AI 框、标注、演示 | src/(tldraw + React 19) |
| 项目数据层 | 画布 JSON 与图片/HTML 资源 | 用户项目下的 canvas/ 目录 |
这个分层里最值得注意的一点是:前三层住在插件仓库,第四层住在用户项目。数据与代码彻底分离,后面会看到这个决定的分量。
依赖清单里的信息量
package.json 是最诚实的架构文档:
json
{
"name": "cowart-canvas",
"version": "0.1.20",
"type": "module",
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.7.4",
"@modelcontextprotocol/sdk": "^1.29.0",
"tldraw": "^5.1.1",
"@tldraw/assets": "^5.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"vite": "^7.0.0",
"html2canvas": "^1.4.1",
"fractional-indexing": "^3.2.0",
"lucide-react": "^1.24.0",
"zod": "^4.4.3"
}
}
逐条读下来,实现路径几乎不用猜:
@modelcontextprotocol/sdk+@modelcontextprotocol/ext-apps:不只是普通 MCP server,而是用了 MCP 的应用扩展能力,把 UI 作为资源交付给宿主渲染。这是「原生 widget」而非「本地网页」的技术前提。tldraw5.x:画布引擎。选它意味着形状系统、选择状态、相机变换、撤销栈这些重活全部复用,Cowart 只需扩展自定义 shape(AI 图片框、AI HTML 框、Slides)。html2canvas:把标注后的画布区域导出为位图。这是「按标注改图」链路的关键一环------模型看到的是一张包含箭头和文字的合成图,而不是一串坐标。fractional-indexing:分数索引排序,常用于维护形状层级(z-order)或 Slides 页序,插入元素时无需重排整个序列。zod:MCP 工具入参校验。工具边界上的类型安全,能显著降低 Agent 传错参数导致的静默失败。vite7 + React 19 :现代构建链,npm run build走的是自写的scripts/vite-build-once.mjs,把产物打成可被 MCP 托管的静态 widget。
仓库还提供了一条聚合质量校验命令:
bash
npm run quality # = npm run check && npm run build && npm run probe:mcp
check 用 node --check 对每个 .mjs 做语法校验,probe:mcp 主动探活 MCP server。对于一个插件项目,这套自检比接一堆测试框架更务实------插件失效的主要形态就是「MCP 起不来」和「widget 资源加载不到」,正好被这两步覆盖。
四、关键演进:从本地网页服务到原生 widget
Cowart 早期版本的形态是:Codex 启动一个本地 Vite 服务(默认端口 43217),用户在浏览器或 in-app browser 里打开 http://127.0.0.1:43217/。这条路走得通,但代价不小------需要切窗口,端口可能冲突,服务生命周期要人管,画布和对话在两个上下文里。
现在的主线换成了 MCP widget:Codex 调用 render_cowart_canvas_widget,画布直接在 Codex 内部渲染 。scripts/start-canvas.sh 只作为本地开发的 fallback 保留。
这次调整背后有一个通用结论值得记下来:Agent 的图形界面应该由宿主渲染,而不是由插件另开一个世界。 原因有三:
- 上下文同源。widget 与对话在同一容器里,选择状态、剪贴板、截图能通过 widget bridge 双向流动,不需要走 HTTP 端口做进程间通信。
- 权限与安全边界清晰。本地起服务意味着开一个监听端口,在企业环境里是需要解释的行为;widget 资源由 MCP 通道交付,边界收敛。
- 心智负担低。用户不需要理解「插件其实是个网页」这件事。
mcp/lib/ 下的两个文件名把这条链路写得很直白:widget-resource.mjs 负责把构建产物声明为 MCP 资源,cowart-static-widget.mjs 负责将其组装成宿主可直接渲染的静态 widget。plugin-root.mjs 解决的是另一个经典麻烦------插件被安装到任意路径后,如何稳定定位自身根目录以读取产物和资源。
五、数据模型:为什么画布必须存在用户项目里
画布数据的落盘路径是这样的:
<你的项目目录>/
└── canvas/
└── pages/
└── <page-id>/
├── cowart-canvas.json # 画布本体
└── assets/ # 图片、生成的 HTML 等资源
默认写入当前用户项目,而不是插件仓库。三个环境变量控制这套行为:
| 变量 | 作用 | 默认值 |
|---|---|---|
COWART_PORT |
本地开发服务端口 | 43217 |
COWART_PROJECT_DIR |
画布数据归属的项目目录 | 当前项目 |
COWART_CANVAS_DIR |
画布数据目录 | $COWART_PROJECT_DIR/canvas |
这个设计看着朴素,实际影响很大:
画布随项目走。 A 项目的画布和 B 项目的画布天然隔离,不会互相污染。你在做官网改版时的所有草图,就躺在官网仓库里。
画布进版本控制。 cowart-canvas.json 是纯文本,能 commit、能 diff、能 review、能跟着分支切换。设计过程第一次和代码变更处在同一条时间线上。这一点对团队协作的意义比听起来更大------设计稿不再是 Figma 里的一个孤立链接,而是仓库的一部分。
资源本地化。 生成的图片和 HTML 落在 assets/,不依赖任何云端存储,离线可用,也不会因为服务下线而失效。
卸载或更新插件不丢数据。 代码和数据分离,插件目录随时可以删了重装。
如果要给「Agent 工具的数据应该放哪」下一条经验:放在用户的工作目录,用纯文本格式,让版本控制接管历史。 不要放在插件自己家里,也不要急着上云。
六、四条核心工作流
6.1 打开画布
在 Codex 里用自然语言说明意图即可:
Open the Cowart canvas for this project.
Codex 通过 render_cowart_canvas_widget 打开原生 widget。首次使用建议在安装后新开一个对话,让新注册的 skill 和 MCP 工具完整加载------这是插件类工具的通用坑,工具清单通常在会话初始化时快照。
6.2 生成图片:让框的尺寸成为 prompt 的一部分
流程是三步:
- 在画布上创建并选中一个
AI 图片框; - 在弹出的生成面板里写 prompt,可选择一张或多张画布上已有的图作为参考图;
- 发送。
关键在于发送出去的载荷不只是文字。Cowart 会把 prompt + 参考图 + 选中框的位置与尺寸信息 一起交给 Codex。Codex 按这个框的比例生成图片,然后把占位框替换成普通图片形状。
这里有两个容易被忽略的巧思:
- 尺寸即约束。你不需要在 prompt 里写「16:9」「竖版海报」,把框拉成什么形状,图就按什么比例出。空间约束用空间方式表达。
- 参考图来自画布本身。画布上任何一张图都能被指定为参考,风格延续变成一次点选,而不是一段「保持和上一张一致的插画风格、同样的配色、同样的线条粗细......」的祈祷文。
6.3 按标注改图:这是整个项目的灵魂
步骤:
- 在画布上对图片做标注------箭头、圈选、文字说明,用 tldraw 原生的绘图工具;
- 选中被标注的图片,点击
按标注修改; - Cowart 导出一张包含原图、箭头和标注文字的合成截图,通过 widget bridge 发给 Codex。
Codex 读取截图里的标注意图,生成一张去掉标注痕迹的新图,放在原图旁边。原图和标注不会被删除或移动。
值得展开的是这条链路的技术选择。要把「改这里」传达给模型,理论上有几种方案:
- 坐标 JSON:把标注序列化成结构化数据(框选区域、箭头起止点、附加文字),随 prompt 一起送出。精确,但需要模型在文字空间里重建视觉布局,且要求模型严格遵循自定义 schema。
- mask + inpainting:生成蒙版做局部重绘。技术上最「正统」,但依赖特定的图像编辑接口,且对「把这个元素往左移」这类结构性修改无能为力。
- 合成截图:把标注烧进像素,让多模态模型直接看图理解。
Cowart 选了第三种,用 html2canvas 完成导出。这个选择的合理性在于:现代多模态模型本来就擅长看图,让它看一张画满批注的图,与让人类设计师看同一张图,理解路径是一致的。不需要定义中间协议,不需要模型学习任何私有格式,能力天花板直接对齐视觉模型本身的水平。
代价是精度上限受模型视觉理解能力约束,且需要「重新生成整图」而非局部修补,细节可能漂移。但对绝大多数「构思---迭代」场景,这个权衡是划算的。
还有一个体贴的细节:修订图放在原图旁边,而不是覆盖原图。设计迭代天然是分叉的,保留分支比追求整洁更重要。同时这也支持一种手工用法------你自己截一张带标注的 Cowart 图发给 Codex,走的是同一条修订流程。
6.4 AI HTML 与 AI Slides:把画布变成产出容器
这是 Cowart 从「改图工具」向「工作台」跨出的一步。
AI HTML :创建并选中一个 AI HTML 框(默认 1024 × 576,16:9),输入 prompt 与参考图,Codex 生成完整可运行的单文件 HTML ,直接嵌入这个框。生成的 HTML 作为画布中的嵌入页面存放在当前 page 的 assets/ 目录。选中后可以下载渲染图、直接编辑文本,也可以继续用画布标注来迭代 HTML,或者反过来根据 HTML 与标注生成图片。
「单文件 HTML」这个约束选得很准:无外部依赖、可直接 <iframe> 嵌入、可单独打开、可 commit 进仓库。它同时是产物、是素材、也是可编辑的中间态。
AI Slides :外框默认 1048 × 600,对应一页 1024 × 576 内容加四周各 12px 留白。用法有两种:
- 组装:把画布上已有的图片或 HTML 拖入 Slides,或复制图片后选中 Slides 粘贴进去,内容自动按顺序横向排列;
- 生成:选中空 Slides,在生成面板里写整套演示的描述、添加参考图,选择 3、5、10 页或自定义页数(默认 5 页),Codex 生成一组视觉与叙事连贯的独立 16:9 HTML 页面依次加入。
注意一个交互规则:Slides 已有内容时不再显示生成面板。这是一条防误伤的设计------避免在已有成果上意外触发批量覆盖。
演示模式支持左侧缩略图预览与切换、全屏播放、方向键/空格/点击翻页,并且保留 HTML 自身的按钮、链接和表单交互,播放控制栏固定在顶部。也就是说,做出来的不是静态幻灯片,而是一串可交互页面。
七、Skill 机制:Agent 侧的行为契约
Cowart 注册了三个 skill:
| Skill | 触发场景 | 行为 |
|---|---|---|
cowart:cowart-open-canvas |
用户要求打开画布 | 渲染原生画布 widget |
cowart:cowart-image-gen |
需要生成、填充、替换画布上的图片 | 接收画布内 prompt 与参考图,生成图片替换选中的 AI 图片 框;无选中框时插入到当前页面 |
cowart:cowart-image-edit |
用户提供 Cowart 标注截图 | 依据标注生成修订图 |
Skill 和 MCP 工具的分工,是理解这类插件的关键:
- MCP 工具是能力------读取选择状态、保存画布、插入图片或 HTML、写入页面资源目录。它们是确定性的、可校验的原子操作(入参由 zod 把关)。
- Skill 是判断力------什么时候该调哪个工具、参数怎么组织、多步操作如何编排、边界情况如何降级(比如「没有选中框时怎么办」)。
把「能力」和「判断」分开写,好处是两侧可以独立演进:加一个新的画布形状,只需扩工具;改变 Agent 的行为倾向,只需改 skill 文本。这也是 Agent 插件相较传统插件的结构性差异------一部分逻辑是用自然语言写的,可以被非工程师维护。
八、上手实践
安装
两条路:让 Codex 自动安装(在对话里直接要求它按仓库说明装好),或者手动安装。手动路径推荐把插件 clone 到 Codex personal marketplace 默认引用的位置,并确认 ~/.agents/plugins/marketplace.json 中存在 Cowart 条目:
json
{
"name": "personal",
"interface": {
"displayName": "Personal"
},
"plugins": [
{
"name": "cowart",
"source": {
"source": "local",
"path": "./plugins/cowart"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}
然后先注册 personal marketplace,再安装插件。安装完成后开一个新对话再使用。
| ⚠️
安装类命令请以仓库 README 的当前版本为准。给 Agent 授予「自动安装插件」权限时,值得先自己读一遍要执行的命令------这是所有 Agent 插件生态的共同风险面,与具体项目无关。
本地开发
开发时仍可直接启动 Vite 画布服务,并通过 COWART_PROJECT_DIR 指定画布数据所属的用户项目目录。改完代码后跑一遍聚合校验:
bash
npm run check # 语法自检所有 .mjs
npm run build # 构建 widget 静态产物
npm run probe:mcp # 探活 MCP server
npm run quality # 以上三步串联
几条实用建议
- 先把框拉对比例再写 prompt。框的尺寸是硬约束,比在文字里描述比例可靠得多。
- 标注要「说人话」。箭头指向位置,文字写清动作(「删掉」「换成蓝色」「放大 1.5 倍」)。把它当成给外包设计师的批注来写,而不是给机器写指令。
- 把画布当版本库用。一轮迭代留一列,横向铺开,比反复覆盖同一张图更容易看出走向。
canvas/目录记得纳入 git ,但注意assets/里的位图会让仓库变胖,长期项目考虑 Git LFS 或定期归档。- Slides 生成前先备好参考图,风格一致性主要靠参考图而非文字描述维持。
九、局限与思考
客观地讲几条限制:
强依赖宿主。 Cowart 是为 Codex 写的插件,skill 命名、widget 渲染、图片生成能力都绑定在这套宿主上。想迁移到别的 Agent,MCP 服务层和前端可以复用,但 skill 层和 widget 交付方式要重做。
生成质量取决于底层模型。 Cowart 是交互层的创新,不改变生成能力本身。标注被理解到什么程度、修订图细节保真度如何,最终由多模态模型决定。
整图重生成而非局部修补。 「按标注改图」得到的是新生成的一张图,未被标注的区域也可能发生细微变化。对精修阶段的工作不够可靠。
项目仍在早期。 版本号停在 0.1.x,贡献者集中在作者本人,接口和数据格式都有变动可能。用于严肃生产前建议锁定版本。
协作是单机的。 画布存在本地文件里,多人实时协作不在当前范围内------虽然通过 git 做异步协作反而顺畅。
这些局限不影响它的价值。Cowart 真正的贡献不在功能清单,而在示范了一种范式:Agent 不必被困在对话框里,它可以拥有为特定任务定制的图形界面,而这个界面的产物直接落在用户的文件系统中。
十、可迁移的五条设计经验
如果你也在做 Agent 侧的工具或界面,Cowart 有几条经验值得直接抄:
1. 为任务选对输入模态。 空间问题用空间输入,时间问题用时间轴输入,别一律塞进文本框。表达带宽的提升,往往比 prompt 调优的收益大一个量级。
2. 让界面成为 prompt 的一部分。 选中框的尺寸、标注的位置、拖拽的顺序,都是零成本的高质量输入信号。用户已经在操作了,顺手把语义收集起来就好。
3. 数据放用户家里,用纯文本,交给 git。 可 diff、可回滚、可分支、可 review,插件生死不影响数据存续。
4. 用宿主原生的 UI 交付方式。 别自己另开端口起一个网页世界。MCP widget 这类机制在上下文共享、权限边界和心智负担上全面占优。
5. 能力与判断分层。 确定性操作做成带 schema 校验的工具,编排与降级策略写进 skill 的自然语言里。两层各自演进,改一处不牵动全局。
结语
Cowart 的代码量不算大,思路也不复杂:拿一个成熟的画布引擎,接一套 MCP 工具,把数据落在用户项目里,再用三个 skill 教 Agent 怎么配合。但它触到了一个真问题------当 AI 的能力越过某个门槛,瓶颈就从模型转移到了人机接口。
过去两年,我们习惯了通过打磨 prompt 来榨取模型能力。Cowart 提供了另一个方向的答案:换一种更符合人类直觉的表达方式,让「指哪打哪」重新成为可能。一个箭头胜过一百个形容词,这在人与人之间早就是常识,现在轮到人与 Agent 了。
对开发者,它是一份关于 MCP widget、tldraw 扩展与 Agent 插件分层的活样本;对使用者,它是让 Codex 从「代码助手」变成「视觉工作台」的一块拼图。无论哪一种身份,都值得 clone 下来跑一遍。
参考
- 项目仓库:zhongerxin/Cowart(MIT,2026 年 6 月开源)
- 画布引擎:tldraw/tldraw
