AI 产品决策专栏
一个产品人用 AI 从 0 到 1 做产品的全过程记录。这里不是结果展示,而是每次取舍、每个判断、每条决策背后的真实推演。
构建期从 Deskhand 仓库和专栏内容自动抓取
连续阅读
仪表盘下直接进入内容区。点击条目原地展开详情,然后直接读下一条,不再跳转页面。
结论:需要 Claude Code-like 的能力去扩展自己的能力 scope,完成一整套事情的交付。
我自己在践行且观察到身边很多人,都在用 Claude Code 去做很多编程以外的事情——增长运营、PRD 编写、PPT 编写、Excel 编写、内存清理、桌面整理、相册分类等等,用途太多了。
我是从 2024 年 12 月开始用 Cursor,我是产品出身,所以本身就对代码不是很敏感,对于其他用途更感兴趣,所以一直在用 Cursor、后来用 Claude Code 去做各种事情。我非常相信这个能力远远超出编程写软件,它所能实现的事情对于很多非技术者都有帮助。
我也很相信 Cowork 这样的客户端产品远远不够,因为 Cowork 就是一个「普通人的 Claude Code」,能用它大大扩展自己的能力边界和能力深度。未来我相信是超级个体的未来,不再是 specialized,而是一个人完成一个很大 scope 的 judgement to ship。
核心洞察:
- Claude Code 的底层能力让它有非常大的能力扩展空间,几乎可以做任何电脑办公能做的事
- Claude Code 对于非技术的普通人不是最优解
- 未来是超级个体的未来
- 超级个体未来会越来越需要 Claude Code-like 的能力去扩展自己的能力 scope,完成一整套事情的交付
结论:需要的不是一个工具,而是一个助理。普通人不容易接触到、不容易通过命令行去下载安装部署。
1. 安装部署门槛高
普通人不容易接触到、不容易通过命令行去下载安装部署。
2. 命令行界面劝退
CC 的命令行界面让人望而却步,只能用指令和提示词来操作,几乎零引导,让用户难以深入使用它的功能。
3. 功能决策面向技术者
CC 的功能设计主要以技术者为主。只要一个产品面向的用户群体不同,功能决策上必然会有差异:
- 目录即工作区 — 技术者习惯项目目录结构,普通人文件随便丢桌面或下载文件夹
- 输出不可视 — 生成的 HTML/代码/图片以原始格式呈现,非技术者习惯于所见即所得
- 权限模型太技术 — bash 执行、文件写入这些权限概念对普通人没有意义
- 工具命名面向开发者 — Read、Write,Bash、Edit 这些工具名是开发者词汇
- 上下文管理是隐性的 — 技术者理解 token 限制,普通人不知道为什么 AI 就"忘了"之前说的话
- 反馈全是文本流 — 没有进度条,没有状态指示
4. 是工具,不是助理
CC 以「用户主动使用和编辑」为主。但普通人需要的不是一个工具,而是一个助理。助理不是在吩咐的时候才去做,而是主动知道「我能做什么」。
5. 用户表达困难,缺乏引导
CC 不能很好地帮助用户讲清楚需要什么。它会去问,但问了之后的文字回答依然可能讲不清。用户更需要的是引导式的体验。
6. 错误信息不可理解
普通人看到报错直接就慌了,不知道是自己的问题还是 AI 的问题。
7. 成果散落找不到
做完了不知道东西在哪。生成的文件散落在目录里,没有一个「这是你的成果」的入口。
8. 不知道它能干什么
CC 没有能力边界的展示,普通人不知道「原来你还能帮我整理照片」。
9. 多步骤任务容易断
复杂任务中间出了问题,不知道做到哪了、哪步成功了哪步失败了。
结论:围绕「用一句话定义Deskhand到底」,最终方案按文中取舍执行。
一个住在你电脑里的助理,它能操作你的文件,理解你的需求、主动帮你把事情做完。
结论:需要这个产品」排序,按「谁更需要这个产品」排序:。
按「谁更需要这个产品」排序:
- 行政/财务助理 — 日常工作几乎全是重复性文件操作,技术能力最低,被现有 AI 工具服务得最差
- 小公司创始人 — 什么都要自己干,时间极度稀缺,需要跨多种文件类型工作
- 跨境电商卖家 — 大量批量化、模式化的文件操作,现有工具完全无法满足
- 独立教师/培训师 — 高频刚需,每周都要做,涉及素材整理和文档生成
- 自由摄影师 — 明确的批量文件操作需求,工作流清晰
- 产品经理 — 强烈的「所见即所得」需求
- 房产中介/销售 — 个性化文档生成需求明确
- 大学生 — 需求真实且痛点明确,但学习能力强
- 律师/法务 — 需求高价值,但行业合规要求高
- 退休人员/数字化新手 — 最需要「零门槛」的体验
结论:需要懂技术就能用,别的 AI 是在它们自己的地方帮你做东西,做完了你得下载回来。
1. 它就在你电脑里干活
别的 AI 是在它们自己的地方帮你做东西,做完了你得下载回来。Deskhand 直接在你电脑上干活——它能去你的桌面找到那个 Excel,能去你的下载文件夹翻出上周的发票。
2. 同样的能力,但你不需要懂技术就能用
Cowork 和 Claude Code 也能在你电脑上干活,但它们的界面、用词、操作方式都是给懂技术的人设计的。Deskhand 做的事情一样,但你看到的、摸到的、理解的方式完全不同。
3. 它会主动找事做,不是等你嘱咐
现在所有 AI 产品都是你说一句它做一句。但普通人的问题是:我都不知道该让你做什么。Deskhand 会自己看到「你桌面很乱」然后问你要不要整理。
结论:需要:上传任何文件,理解任何技术概念,做任何配置。
用户用一句模糊的话描述需求,Deskhand 直接在用户电脑上完成了一件事,并且把成果展示给用户看。
整个过程里用户不需要:上传任何文件,理解任何技术概念,做任何配置。
具体来说,第一个版本至少要让用户能走通这条路:
- 打开 Deskhand(像打开任何 app 一样,双击就行)
- 用自然语言说一件事(比如「帮我把下载文件夹里这个月的 PDF 整理到一个新文件夹里」)
- Deskhand 去做了,中间用户能看到它在干什么(不是看代码,是看「正在扫描文件…找到 12 个 PDF…正在移动…」)
- 做完了,直接展示结果(「已整理完成,12 个文件移到了这里,点击查看」)
结论:需要的集成是微信和钉钉而不是 Slack 和 Notion。
风险 1:用户的思维和操作习惯转变(最难)
普通人以前做任何事都是自己动手——自己打开 Excel 填数据、自己一张张整理照片。现在要他们接受「我只说我要什么,让 AI 去做」,这是一个从未有过的操作方式。
这意味着产品不能只是「能做到」,还要让用户「知道怎么让它做」「敢让它做」「看得懂它在做什么」「确认结果是对的」。
风险 2:Anthropic 自己做了
Cowork 已经在做类似的事。如果 Anthropic 继续往非技术用户方向迭代,Deskhand 的差异化窗口会越来越小。
但这个世界对产品的需求细分是很多的。Cowork 服务的是英语世界的主流用户,一个中国的小公司老板,他的文件是中文的、他的工作流是本地化的、他需要的集成是微信和钉钉而不是 Slack 和 Notion。这些细分需求 Anthropic 不会优先做。
风险 3:主动助理做不好会变成骚扰
主动发现需求是核心差异点,但做不好就是弹窗广告。
关键控制:少一点去打扰、找准时机再开口、让用户不需要花太多时间去看这些提议。
结论:需要自己握方向盘了。以前是「我去操作工具来完成任务」,现在是「我让 AI 去操作工具来完成任务」。
以前是「我去操作工具来完成任务」,现在是「我让 AI 去操作工具来完成任务」。就像特斯拉 FSD——你还是那个决定去哪的人,但你不再需要自己握方向盘了。
三年后,用户和 Deskhand 的关系是:我的工作,我来指挥,Deskhand 来操作。
场景示例:
- 早上 9 点:Deskhand 告诉你邮箱里有新邮件摘要,桌面文件已按类型整理好
- 上午 10 点:说「帮我用上个月的销售数据做一份月报」,Deskhand 自动完成数据分析和文档生成
- 下午 2 点:把发票照片同步到电脑,说「帮我录入报销表」,Deskhand 识别并填入 Excel
- 下午 5 点:说「帮我把上次的方案更新一下」,Deskhand 找到文件、替换数据、展示更新
核心变化不是「AI 能做什么」,而是「人的工作方式变了」——从亲手操作每一步,变成描述意图,检查结果,做决策。手不再忙了,脑子还是你的。
结论:优先搭建“Electron + React”框架层,再补 UI 骨架与基础设施。
框架层:
- Electron 桌面容器
- React 渲染层
UI 骨架层:
- 三栏布局:SessionSidebar(左)+ ChatArea(中)+ ArtifactPanel(右)
- InputToolbar(底部输入区)
- SettingsPage(设置页)
基础设施层:
- 类型系统(核心类型层)
- 状态管理(Jotai)
- IPC 通信桥(main ↔ renderer)
搭建顺序: 框架层 → UI 骨架层(用 mock 数据跑起来)→ 基础设施层 → 然后才进入功能开发。
先让界面能看到,能点,再往里填真实逻辑。这样每一步都有可验收的产出,而不是写了一堆看不见的基础代码。
结论:背景: 如果凭空搭一套架构,万一跑不通,推倒重来的成本太高。
背景: 如果凭空搭一套架构,万一跑不通,推倒重来的成本太高。所以策略是:先找已经验证过的架构参考。在对比了多个同类开源项目之后,最终选择了 craft-agent 的架构。
为什么这套架构是 work 的:
- Electron + React + Jotai + Tailwind 是成熟的桌面应用技术栈
- Monorepo 结构把 UI、类型、业务逻辑清晰分层
- Claude Agent SDK 作为 Agent 层,直接获得工具调用、权限管理,流式响应等能力
- IPC 三进程模型是 Electron 的安全最佳实践
- 事件驱动的数据流天然适配流式场景
怎么用的: 技术架构照着来,业务逻辑从零写。遇到具体问题时去翻 craft-agent 的实现看它怎么解决的,但不 fork、不照搬代码。
结论:我们最后选A,Electron。
讨论过的方案:
- A. Electron — Chromium + Node.js,VSCode/Slack/Discord 都在用,生态最成熟
- B. Tauri — 系统 WebView + Rust,包体小性能好,但生态年轻
- C. Flutter Desktop — 跨平台 UI 框架,但不是 Web 技术栈
我们最后选 A。
- 参考项目 craft-agent 用的就是 Electron,架构已验证可行
- Claude Agent SDK 是 Node.js 生态的,Electron 的 main 进程天然是 Node.js,集成零摩擦
- Tauri 的 Rust 后端和 Node.js SDK 之间需要额外的桥接层
- AI 对 Electron + React 的熟悉度远高于 Tauri + Rust
结论:从用户习惯出发: 左侧会话列表 + 中间对话区,这是用户已经习惯的 AI 对话布局。
从用户习惯出发: 左侧会话列表 + 中间对话区,这是用户已经习惯的 AI 对话布局。没有理由在这里创新。
从产品差异出发: 但只有两栏是不够的。桌面 agent 操作 scope 非常大,可能跨不同文件、目录操作。如果没有成果面板,用户说「帮我生成一个网页」,AI 生成 HTML 文件,但用户得自己找到文件打开才能看到效果。体验很差。
排除 D(任务看板): 用户和 AI 的交互本质是对话——「帮我做这个」「好的,做完了」。这是对话节奏,不是拖拽卡片的节奏。看板适合项目管理,不适合「我说你做」的助理模式。
结论:需要:文件读写、命令执行、权限确认、流式响应。
从产品核心能力看: Deskhand 的核心是「AI 操作你的电脑」,Agent 层需要:文件读写、命令执行、权限确认、流式响应。Claude Agent SDK 把这些全部内置了。
从事件流架构看: SDK 的 AgentEvent 机制天然适配流式 UI。Agent 执行过程中会发出 text_delta、tool_start、tool_result 等事件,UI 只需要监听这些事件就能实时展示「AI 正在做什么」。
关于方案 B(直接调 Anthropic API): 看起来更「轻量」,实际上工作量巨大。权限拦截、流式处理、上下文管理、错误恢复、子任务编排——这些 SDK 都已经处理好了。
关于方案 C(LangChain / CrewAI): 这类框架的核心价值是「模型无关」,但 Deskhand 不需要多模型——深度绑定 Claude 的 Agent 能力,把体验做到极致,比什么模型都能用但什么都不精更有价值。
结论:仓库按 Electron 应用层、核心类型层、共享业务层三层组织。
- Electron 应用层(主进程 / 预加载层 / 渲染层)
- 核心类型层
- 共享业务逻辑层(agent、sessions、skills)
从 vibe coding 的实际体验看: 分成三个包之后,AI 的「活动范围」变清晰了。相当于给 AI 画了格子,每个格子里的代码职责单一,AI 犯错的概率就低了。
从 Electron 的特殊性看: Electron 有 main 进程(Node.js)和 renderer 进程(浏览器),运行环境不同但需要共享类型定义。把核心类型独立成单独一层后,两个进程都能安全复用。
结论:会话数据采用 JSONL 追加写入,优先保证流式写入简单可靠。
- 追加写入天然适配流式场景——AI 每产生一条消息就 append 一行,不需要读取整个文件再写回
- 调试友好,直接用文本编辑器就能看会话内容
- SQLite 的优势是复杂查询,但会话数据的查询需求很简单,不需要 SQL 的能力
- craft-agent 也用的 JSONL,验证过性能足够
结论:需要找的时候搜索比打标签更自然。这 5 个概念覆盖了完整的使用链路,每一个都对应用户能感知到的东西。
这 5 个概念覆盖了完整的使用链路,每一个都对应用户能感知到的东西。
明确不要的概念:
- 工作区(Workspace) — 普通人不会先想「我现在要进入哪个工作区」。一个应用就是一个工作区。
- 标签(Labels) — 按时间排序的会话列表就够了,需要找的时候搜索比打标签更自然。
- 任务状态(Status) — Todo/In Progress/Done 是项目管理的概念。「帮我整理照片」不需要状态流转,做完了就是做完了。
- 权限配置(Permissions Config) — 普通人不理解 bash 命令和文件写入的区别,统一用确认模式就好。
减法的原则: 如果一个概念需要用户理解技术背景才能使用,不要。如果一个概念是给「管理者」设计的而不是给「使用者」设计的,不要。宁可少几个功能,也不要让用户在打开产品的第一秒就感到困惑。
结论:我们最后选B,按用户感知分。
在设计对话 UI 之前,得先搞清楚 Claude Agent SDK 是怎么工作的。它不是一问一答的聊天机器人,而是一个会自主循环执行任务的 Agent。
一次对话的典型流程:
用户发了一条消息之后,Agent 进入一个循环:
- 思考 — 每一轮开始前,Agent 先内部推理
- 决策 — 根据思考结果,走两条路:简单操作直接调用工具,或派子代理处理复杂子任务
- 执行 — 工具返回结果,或子代理完成任务返回结果
- 输出 — 向用户输出进度信息或阶段性结果
- 回到第 1 步,继续下一轮,直到整个任务完成
整个过程是流式的——不是等全部做完再一次性返回。UI 要渲染的,本质上就是这条事件流里的各种事件。
那这些事件怎么归类?
A. 按 SDK 原始事件类型分(技术视角) — thinking、text_delta、tool_use、tool_result……十几种事件逐一映射到 UI 组件。坏处是太碎了,用户不需要感知这么多状态。
B. 按用户感知分 — 站在用户角度,他们关心的就这几类:
- 等待状态 — AI 在思考,或者在两个工具调用之间的间隙
- 工具执行过程 — AI 读了什么文件、跑了什么命令
- 子代理活动 — AI 派了个子任务去干活
- 最终回复 — 一段 Markdown 文字
再加两个特殊情况:
- 权限请求 — AI 想执行危险操作,需要用户点确认
- 错误/状态消息 — 出错了、被用户停止了
我们最后选 B。
SDK 的事件类型是给开发者用的技术协议,用户不需要也不应该感知这种粒度的区别。每一类对应一个 UI 组件,职责清晰。这样不管 Agent 的执行顺序多复杂,UI 都能正确响应。
结论:我们最后选B,按 Turn 分组。
一次 Agent 执行可能产生几十个事件。如果把这些全平铺在聊天流里,会非常乱。
A. 平铺消息流(像 Slack) — 每个事件都是一条独立消息。问题是用户消息会被淹没,找不到"AI 最终说了什么"。
B. 按 Turn 分组,卡片式展示 — 把用户一次提问到 AI 完整回复之间的所有事件,打包成一个"Turn"。一个 Turn 卡片里分两个区域:
- 上半部分:Activity 区(工具调用列表,可折叠)
- 下半部分:Response 区(最终文字回复)
Activity 区是一棵树——因为子代理里面还有自己的工具调用。
C. 双栏布局 — 聊天在左边只显示文字,工具调用放右边面板。问题是跟 ArtifactPanel 冲突。
我们最后选 B。
用户的心智模型是"我问了一个问题,AI 去做了一些事,然后给了我一个回答"——Turn 卡片正好对应这个心智模型。树形结构解决了子代理的嵌套问题。
结论:我们最后选B,区分状态。
Agent 执行过程中有几种"等待":
- 刚发完消息,Agent 还没有任何输出 — 纯等待
- Agent 在思考 — 上一个工具完成了,下一个还没开始
- 工具正在执行中
- 子代理在干活
A. 统一一个 "Thinking..." 动画 — 简单,但用户分不清 AI 是在想还是在干活。
B. 区分状态,但保持简洁 — 等待中显示随机循环的等待文案 + 读秒计时,工具执行中 Activity 区实时出现新的工具行。
C. 完全透明,显示内部状态 — 对非技术用户来说太吓人。
我们最后选 B。
核心问题是解决用户的等待焦虑,而不是给用户看技术细节。随机切换文案加上读秒计时,让用户感觉到"它一直在动"。
结论:我们最后选C,按危险等级分层。
这是 Deskhand 跟普通聊天产品最大的区别——AI 真的会动你电脑上的东西。
A. 全部放行,不做权限控制 — 风险太大。
B. 全部拦截,每个操作都要确认 — 体验极差。
C. 按危险等级分层 — 把工具分成两类:
- 安全操作(读文件、搜索)→ 自动放行
- 危险操作(写文件、删文件,执行 bash 命令)→ 弹确认
然后提供两种模式:
- Ask 模式:危险操作都要确认(默认)
- Allow-all 模式:只拦截真正的破坏性命令
我们最后选 C。
A 不可能选——用户是非技术人群,不知道 rm -rf / 意味着什么。B 的问题是把安全做成了体验的敌人——一个任务可能有 20+ 次工具调用,用户要点 20 次"允许"。
结论:我们最后选B,默认 Ask。
A. 默认 Allow-all,让用户自己收紧 — 新用户还没建立对 AI 的信任,第一次就看到 AI 在自己电脑上跑命令没有任何确认,会慌。
B. 默认 Ask,让用户自己放宽 — 新用户默认每次危险操作都要确认。前几次会觉得有点烦,但逐渐建立信任。
C. 引导式,首次使用时让用户选 — 新用户根本不知道该选哪个。
我们最后选 B。
安全产品的经典原则是"默认安全,允许放宽"——而不是反过来。Ask 模式下,每次确认弹窗其实是一次"教育"——用户逐渐学会 AI 会做哪些类型的操作。几次之后用户自然会想切到 Allow-all。
结论:围绕「输入栏需要哪些控制项」,最终方案按文中取舍执行。
A. 极简,只有文本框 + 发送按钮 — 用户没法控制 AI 的行为。
B. 全量,所有配置项都放进去 — 输入栏会变成一个密密麻麻的控制面板,非技术用户看到就懵了。
C. 分层,核心控制项进输入栏,其余放设置页 — 输入栏只放跟"这次对话"直接相关的控制项:模型选择、思考级别、工作目录、权限模式、发送/停止按钮。
我们最后选 C。
输入栏是用户每次对话都要面对的地方。标准是:这个配置项会不会在不同对话之间频繁切换?模型选择会,思考级别会,工作目录会,权限模式会——这些都是"每次对话可能不同"的配置。
结论:我们最后选B,通过 IPC 走主进程。
A. 渲染进程直接调用 Agent SDK — API Key 暴露在渲染进程里是绝对不能接受的用户安全问题。
B. 通过 IPC 走主进程 — 渲染进程通过 preload 暴露的 API 发消息,主进程负责调用 Agent SDK,收到事件后通过 IPC 推回渲染进程,渲染进程更新 atoms 触发 UI 更新。
C. 独立后端服务 — 多了一层网络通信,而且 Electron 本身就有主进程可以跑 Node.js。
我们最后选 B。
这就是 Electron 的标准架构——渲染进程负责 UI,主进程负责系统级操作。Agent SDK 是 Node.js 模块,天然跑在主进程里。IPC 是 Electron 内置的进程间通信,性能好,安全。
结论:我们最后选D,展示头几条。
A. 跟普通工具一样,只显示一行 — 用户看不到子代理在干嘛。
B. 可展开的嵌套树,默认折叠 — 执行过程中用户只看到一行在转圈。
C. 默认展开,全部显示 — 列表会很长,把 Response 区推得很远。
D. 展示头几条,剩余折叠 — 子代理默认展示前几条工具调用,剩余的折叠成"还有 N 项..."。
我们最后选 D。
用户需要看到子代理在干嘛(解决焦虑),但又不能让子代理的细节喧宾夺主。展示头几条让用户知道"子代理在做什么类型的事",剩余的折叠不占空间。
结论:我们最后选B,按错误类型分别处理。
A. 统一一个错误提示 — 用户不知道发生了什么。
B. 按错误类型分别处理 — 网络错误、API 错误、工具执行失败、用户主动停止,各自不同的提示和样式。
C. 错误全部静默 — 但网络断了、API Key 无效这种 AI 自己解决不了的问题,不告诉用户就会一直卡着。
我们最后选 B。
关键区分是"AI 能自己处理的错误"和"需要用户介入的错误"。工具执行失败属于 AI 能自己处理的——Agent 看到工具返回了错误,会自己决定重试还是换个方案。但网络断了、API Key 无效需要告诉用户具体是什么问题、该怎么办。
结论:我们最后选C,垂直切片。
A. 按模块拆 — ChatArea 做完了但 Agent 没接,没法验证。
B. 按功能层拆 — UI 和数据分开做,接的时候大概率对不上。
C. 垂直切片,端到端逐步交付 — 每一刀都切一个"从 UI 到 Agent"的完整功能:
- 先跑通最小对话——发消息能收到 AI 纯文本回复
- 加工具调用显示
- 加 Markdown 渲染
- 加停止按钮
- 加配置传递
- 加权限系统
- 加子代理树形展示
- 加错误处理
我们最后选 C。
A 和 B 的共同问题是"做了一堆但看不到效果"。C 的核心思路是每一步做完都能看到新东西。影响范围小,出错率低,修起来也快。
结论:核心问题有四个。用户气泡颜色太重,深绿白字一上来就把注意力抢走了。
核心问题有四个。
- 用户气泡颜色太重,深绿白字一上来就把注意力抢走了。
- 所有消息间距几乎一样,内容一多就显得挤。
- 标题和正文层级差距不够,扫读时抓不住重点。
- assistant 每条都带头像和名字,连续执行任务时会被切成很多段。
这些问题叠在一起,就会出现一种感觉:信息都在,但阅读阻力很高。
结论:我们把用户气泡从高饱和深绿,改成低饱和 teal 灰,文字改回深色。
我们把用户气泡从高饱和深绿,改成低饱和 teal 灰,文字改回深色。
这样处理后,用户消息还是清晰可见,但不会压过 AI 回复。整体配色也更统一,不会像界面里突然塞进一块“外来颜色”。
结论:统一用大间距也不对,会让页面太松。角色切换(user 和 assistant 互换)时,用较大间距。
统一用大间距也不对,会让页面太松。更稳妥的做法是区分两种情况。
- 角色切换(user 和 assistant 互换)时,用较大间距。
- assistant 连续输出同一段工作流时,用较小间距。
这样既有呼吸感,也能保持连续性。用户会自然把连续的 assistant 消息看成同一段执行过程,而不是很多互不相关的回复。
结论:只做了两件事。我们没有改一整套排版系统,只做了两件事。
我们没有改一整套排版系统,只做了两件事。
- 拉开标题和正文的字号差。
- 给标题上方更多留白。
这两个动作看起来简单,但效果很直接:读者滚动页面时,能更快定位段落结构,不用每次都从正文第一句重新理解上下文。
结论:在 Deskhand 这个场景里,用户面对的是一个执行任务的 agent,不是群聊中的多角色对话。
在 Deskhand 这个场景里,用户面对的是一个执行任务的 agent,不是群聊中的多角色对话。角色区分靠左右布局和气泡样式已经够了,重复显示头像和名字只会增加视觉噪音。
去掉后,界面会更像“系统持续工作并汇报进度”,而不是“AI 在不停发新消息”。这更贴合产品心智,也更省用户注意力。
结论:这轮优化不追求花哨,而是把注意力预算还给内容本身。
这轮优化不追求花哨,而是把注意力预算还给内容本身。
我们接受的取舍是:不靠装饰性元素制造“存在感”,而是通过颜色轻量化、间距分层和排版层次,让用户更快读懂 AI 在做什么。
结论:团队头脑风暴了 7 个方向,覆盖按钮、组件、模板与交互策略。
头脑风暴了 7 个方向:
- 选择题按钮 — 对话中内联的可点击选项(Claude/ChatGPT 已有)
- Generative UI 组件渲染 — Agent 输出 JSON,前端渲染预设组件(表单、评分、排序等)
- Artifact 交互工具 — 在面板里生成可交互的完整应用(mind map、决策树、playground)
- 示例驱动选择 — 不问用户要什么,直接给几个成品让用户挑
- 渐进式引导对话 — 检测到模糊输入后,自动进入一步步的引导
- 空间画布 — 2D 拖拽排列想法碎片,AI 解读空间关系
- 模板启动器 — 常见任务的结构化填空模板
结论:这些方向分为“UI 原语、交互策略、完整体验”三层。
它们不在同一层面,分三层:
- 底层原语(UI 积木):选择题按钮、Generative UI 组件
- 交互策略(用积木搭的模式):示例先行、渐进引导、模板启动器
- 完整体验(独立的重功能):Artifact 交互工具、空间画布
选择题按钮本质上是 Generative UI 的最简形态。示例先行、渐进引导、模板都是「策略」,需要 UI 原语来承载。
决策:投资 Generative UI 组件层作为基础设施,上层策略自然可以在上面跑。
结论:核心决策维度是“生成方式 × 交互位置”,并补充“谁发起”。
两个正交的轴:
- 生成方式:JSON 预设组件 vs AI 从零写 HTML
- 交互位置:Chat 内联 vs Artifact 面板
加上第三个轴:
- 谁发起:Agent 主动 vs User 主动
完整矩阵
| 生成方式 | 交互位置 | 谁发起 | 场景举例 | 状态 |
|---|---|---|---|---|
| JSON 组件 | Chat | Agent | 检测到模糊意图,内联弹出选择/表单 | 🎯 本设计重点 |
| JSON 组件 | Chat | User | 用户点「模板」按钮,出现结构化填空 | 🎯 本设计重点 |
| JSON 组件 | Artifact | Agent | Agent 生成结构化问卷到面板 | 🎯 本设计重点 |
| JSON 组件 | Artifact | User | 用户打开配置面板 | 🎯 本设计重点 |
| AI 生成 | Chat | Agent | ❌ 不自然,排除 | — |
| AI 生成 | Chat | User | ❌ 不自然,排除 | — |
| AI 生成 | Artifact | Agent | 检测到探索需求,生成 playground | ✅ 已有设计 |
| AI 生成 | Artifact | User | 用户要求生成 mind map | ✅ 已有设计 |
Playground 已覆盖「AI 生成 + Artifact」。空白在「JSON 组件」四格,这是 Generative UI 的核心地带。
结论:采用“策划配表”思路,让 AI 主要输出 JSON 配置而非完整 HTML。
核心类比:策划配表。
传统模式:AI 是程序员,从零写完整 HTML → 慢、不可控、不一致。
Generative UI 模式:AI 是策划,往预设模板里填配置 JSON → 快、可控、一致。
就像游戏开发:工程师搭好引擎和系统,策划通过配表填充内容。
具体对比:
| 维度 | JSON 组件 | AI 生成 HTML |
|---|---|---|
| 速度 | ~200 tokens 配置 | ~2000+ tokens 完整代码 |
| 一致性 | 永远匹配设计系统 | 每次不一样 |
| 可靠性 | Schema 约束,可校验 | 无数种写坏的方式 |
| 交互回传 | 预设好的处理逻辑 | AI 每次要自己写通信代码 |
| 维护 | 改一个模板全局生效 | 无法追溯修改 |
| 灵活性 | ❌ 只能做预设过的 | ✅ 无限自由度 |
Trade-off:JSON 覆盖 95% 常见场景,AI 生成 HTML(Playground/Artifact)兜底剩余 5% 创意场景。两者互补。
结论:实现路径是预置 UI 模板,并由 AI 通过 JSON 配置驱动渲染。
机制
- 预先编写 UI 组件模板(HTML + 交互逻辑 + 样式)
- 注册为 tool call(定义 JSON schema)
- AI 调用 tool,输出配置 JSON
- 前端收到 JSON,注入模板,渲染到 Chat 内联或 Artifact 面板
- 用户交互后,结果作为 tool result 回传对话
第一个 Vertical Slice:Playground
用 Playground 验证整个 Generative UI 模式:
- 预写 Playground 页面框架(布局、交互、样式)
- 注册
render_playgroundtool,schema 定义可配置项 - AI 调用 tool 输出配置 JSON
- 前端渲染到 Artifact 面板
- 用户交互结果回传对话
验证通过后,同一套机制复用到更多模板(问卷、mind map、表单等)。
结论:Generative UI 与 Clipboard、Playground、Skills 形成互补的表达链路。
- Clipboard Intelligence:收集上下文的入口 → Generative UI 是表达意图的出口,互补
- Playground 探索设计:Generative UI 是 Playground 的升级实现方式——从 AI 写 HTML 变为 AI 填配置
- 技能系统:UI 模板可以作为技能的一部分分发,技能不仅定义 Agent 行为,还定义 Agent 可用的 UI 组件
## MVP 完成后的方向讨论
Q7:Playground Skill 和 Generative UI render_playground 会冲突吗?
会。两者的 tool description 高度重叠,Agent 不知道该调哪个。
决策:保留共存,明确分工。
render_playground(JSON 配表)→ 标准场景,快、一致- Playground Skill(AI 写 HTML)→ 创意/复杂场景(mind map、自定义可视化等),自由度高但慢
解法:更新两者的 description 使其互斥,让 Agent 一看就知道选哪个。
结论:下一步聚焦 Guided Form,优先解决信息收集场景。
从用户故事出发,识别了三个文字对话效率明显低于 UI 交互的场景:
故事 1(信息收集):小王说"帮我写封道歉邮件",AI 要问语气、对象、事由、长度……一个个问太慢。一个表单一次收齐。
故事 2(方案对比):小李让 AI 推荐工具,AI 回三大段文字,来回翻着对比很累。→ 但本质和 playground 重叠(多维度调参选感觉),暂不做。
故事 3(标注反馈):小张让 AI 改文案,说"第二段收一点",AI 猜不准。直接在文本上标注更精确。→ 有价值,排后面。
决策:先做故事 1 — Guided Form(引导式信息收集)。
结论:Guided Form 采用“一次一题”的引导式交互,而非传统平铺表单。
不是传统表单(一页列 10 个字段),而是 一次一题 的引导式体验。
类比:iPhone 首次设置流程,小红书兴趣选择引导。每次只看一个问题,答完平滑过渡到下一题,顶部有进度条。非技术用户不会被一堆字段吓到。
升级点:AI 可以根据对话上下文做条件分支——如果用户之前提过"给客户",就跳过"收件人是谁"这题。
结论:需要收集多维度信息时,主动调用 render_guided_form tool。
- 触发:Agent 自主判断。AI 分析用户输入,发现需要收集多维度信息时,主动调用
render_guided_formtool。和 render_playground 同一套模式。 - 回传:和 playground 一致——表单底部生成结构化 prompt,用户复制粘贴到对话。不需要新的回传机制。
结论:视觉规范统一为浅色主题,并对齐 Deskhand 现有设计系统。
决策:Generative UI 模板统一使用浅色主题,和产品主界面视觉一致(而非当前 playground 的深色主题)。
基于产品现有 design tokens:背景 #f8fbfb、卡片 #ffffff、强调色 #10a37f、字体 SF Pro Display。具体效果做出来看实物验证,不在文档里过度定义。
## 方向重定义 — 从 Agent 触发到用户选择
Q12:Agent 会主动调用 Generative UI 工具吗?
实测结论:不会。 用户说"帮我写封邮件",Agent 直接写或者用文字追问,不会主动弹表单。
根本原因:Agent 无法检测到"用户表达困难"这个信号。表达困难的人不会说"我表达困难"——他们只会给一个模糊指令,或者直接放弃。AI 没有信号可以触发。
Q13:触发方式应该怎么改?
决策:从 Agent 触发改为用户主动选择。
类比:微信发消息可以打字,发语音,发图片,发位置——用户自己知道哪种方式最适合表达当前想说的东西。
Generative UI 工具变成"输入方式菜单"中的选项。用户看到一排交互方式,自己选一个开始。虽然用户不知道怎么用文字表达,但他们能认出"哦,这个工具能帮我 Q14:用户表达方式的全景"。
###框架是什么?
两轴 + 对角线:
- Y 轴:知道自己要什么 ↔ 不知道自己要什么(认知维度)
- X 轴:能说准 ↔ 说不准(表达维度)
- 对角线:一句话说完 ↔ 一句话说不完(信息量维度)
8 个区域:
| 区域 | 位置 | 表达方式 | 现有覆盖 |
|---|---|---|---|
| 自然语言 | 知道+能说准 | 文字/语音 | ✅ 文字输入 |
| 渐进式引导 | 知道+能说准+复杂 | 分步表单 | ⚠️ Guided Form(场景偏弱) |
| 直接展示 | 知道+说不准 | 发图片/截屏 | ✅ 剪贴板附件 |
| 文件级展示 | 知道+说不准+复杂 | 上传文件/项目 | 🔲 未覆盖 |
| 追问引导 | 不知道+能说准 | 苏格拉底式提问 | ✅ AI 自然对话 |
| 结构化梳理 | 不知道+能说准+复杂 | Trade-off 拆解 | 🔲 未覆盖 |
| 生成式探索 | 不知道+说不准 | Playground / Tournament | ✅ Playground / 🔲 Tournament |
| 沉浸式发散 | 不知道+说不准+复杂 | Mood Board / 灵感画廊 | 🔲 未覆盖 |
核心判断:右下角(不知道+说不准)是 Generative UI 价值最大的象限。文字在这里真的无能为力,必须靠 UI 帮用户"不用说话也能表达"。
Q15:Tournament 是什么?为什么重要?
Tournament 是一种"两两对比淘汰"的交互模式。用户不需要描述自己想要什么,只需要在两个具体选项之间做二选一。多轮淘汰后,偏好自然浮现。
关键升级:选项不必是同一维度。比如决定旅游目的地,不是"东京 vs 巴黎",而是:
- 🏖️ 海边 vs 🏔️ 山里(环境偏好)
- 🎒 穷游 vs 🏨 舒适(预算偏好)
- 📸 打卡 vs 🍜 美食(活动偏好)
4 轮下来得到偏好画像,AI 根据画像推荐。从"选一个"变成"发现你是谁"。
Tournament 和 Playground 互补,都在右下角象限:
- Playground → 连续参数的感觉探索(滑块调参)
- Tournament → 离散选项的偏好发现(二选一淘汰)
Q16:Guided Form 怎么处理?
保留,不砍。已经做了,不碍事。但不再投入优化自动触发。它在"知道+能说准+复杂"象限,场景不够强——大多数情况下 Agent 直接做+迭代比先填表更快。
## 方向确认 + Playground 拓展
Q17:Generative UI 还值得做吗?
质疑点:
- 官方入口占 UI 位置,用户真的会点吗?
- JSON 配表只能做调参面板,相比 Claude 官方 Playground Skill(AI 写完整 HTML,支持 6 种模板)用途太窄
澄清:官方 Playground 是需要用户主动安装的 skill,非技术用户大概率不知道它的存在。所以不是竞争问题,而是用户需求问题。
Generative UI 需要做。象限图已经表明,"不知道自己要什么 + 说不准"的场景真实存在且高频,文字对话在这里无能为力。
Q18:为什么坚持 JSON 配表而非让 AI 写 HTML?
三个原因:
- Token 量和时间节约 — JSON 配置 ~200 tokens,AI 写完整 HTML ~2000+ tokens。响应速度差一个量级。
- 编写质量的稳定性 — JSON 有 Schema 约束,不会出现格式错误,内容过长导致 error、iframe sandbox 限制等问题。AI 写 HTML 有无数种写坏的方式。
- 用户选择可靠回传 — Generative UI 的核心价值是用户的选择能回到对话里影响后续生成。JSON 配表模式下,回传逻辑预写在模板里,测过一次永远 work。AI 写 HTML 每次都要重写 postMessage 通信代码,任何环节出错用户的选择就丢了。
灵活性的损失由 Playground Skill(AI 写 HTML)兜底——不需要回传的纯探索/创意场景,让 AI 自由发挥。
Q19:Playground 需要拓展什么能力?
现有 playground 只有 slider/select/color/toggle 四种控件,只能做"调参面板"。但非技术用户大多数时候的需求是:"我不想一个一个参数调,我想看几个完整方案,选一个顺眼的。"
这是两种不同的交互模式:
| Controls 模式(现有) | Options 模式(新增) | |
|---|---|---|
| 核心动作 | 一个一个参数调 | 看几个完整方案,选一个 |
| 用户心智 | "我来微调" | "你给我看,我来挑" |
| 参数空间 | 连续的(滑块) | 离散的(完整选项) |
| 配置 | 是主角 | 是配角(选完方案后可选微调) |
决策:给 playground 加 options 模式,不做独立模板。
- 当 JSON 里传
controls→ 渲染调参面板(现有行为) - 当 JSON 里传
options→ 渲染画廊选择(新增) - options 模式下可附带少量 controls 做二次微调
- 两种模式共享同一个 prompt 输出和 Send to Chat 机制
- AI 通过同一个
render_playgroundtool 使用,不需要学两个 tool
Q20:入口放在哪?
AI 不可能知道用户"不知道自己想要什么"——用户不会主动说"你不懂我",他们聊几轮对不上心意就直接放弃。所以必须有一个可见的入口,让用户在沮丧时能看到"还有别的方式"。
决策:官方按钮入口,同时重新设计 InputToolbar 布局。
InputToolbar 布局重设计:
删除:推理等级按钮(非技术用户不需要)、预览按钮(未实现)
合并:Skills + MCP Tools → 统一的 Tools 按钮(点开按分类展示)
按"给 AI 什么"vs"AI 怎么跑"分为左右两组。
## render_playground 可靠性优化
Q29:render_playground 的错误率为什么高?
分析了失败的 playground 输出文件:
JSON schema 本身没有报错 — 文件的 CONFIG 都通过了 Zod 校验,option cards 也正确渲染。
真正的 bug 集中在两个自由文本字段(previewTemplate 和 promptTemplate):
- 模型自创变量名:写了
{{selected_option_label}},实际变量叫{{selectedLabel}} - 模型用了不支持的语法:
{{#if difficulty}}...{{/if}},引擎只支持{{id}}简单替换
用户看到的效果:左边 option 卡片正常,但右边预览区和底部 prompt 栏显示了未替换的模板语法原文。
诊断:问题不是 schema 太复杂,而是 JSON Schema 能约束结构,但约束不了字符串字段内部的"微语法"(变量名、模板语法)。Q4 论证的"可靠性"优势被自由文本字段侵蚀了。
Q30:业界怎么做"AI 动态生成交互式 UI"?
调研了业界做法,三大方向:
| 方向 | 代表 | 灵活性 | 可靠性 |
|---|---|---|---|
| 严格 JSON Schema,AI 只填字段值 | UI-Schema、OpenAI Structured Outputs | ⬇️ | ⬆️⬆️⬆️ |
| AI 生成完整代码,沙箱执行 | Vercel v0、Anthropic Artifacts | ⬆️⬆️⬆️ | ⬆️⬆️ |
| 混合:结构化配置 + 预定义组件库 | Pydantic AI | ⬆️⬆️ | ⬆️⬆️ |
核心 insight:AI 只填结构化字段值,前端模板完全可控 — 这是可靠性最高的模式。自由文本字段(HTML 模板片段)是 LLM 可靠性的天然弱点。
Q31:怎么修?
四个候选方向:
- 工具描述加 concrete example — 让模型知道确切变量名
- 模板引擎加容错 — 支持
{{#if}},让模型的自然倾向也能 work - 两者都改
- 消除自由文本 — 干掉
previewTemplate和promptTemplate,前端自动生成
决策:方向 4。
方向 1-3 都是在修补"让模型写对模板",但业界验证的最佳实践是根本不让模型写模板。我们的 Playground Skill(AI 写完整 HTML)已经兜底了需要高度自定义的场景。
具体改动:
- 删除
previewTemplate— Options 模式:前端直接展示选中 option 的previewHtml;Controls 模式:前端根据 controls 定义自动生成参数预览 - 删除
promptTemplate— Options 模式:前端自动组装"我选择了 {label}({description})";Controls 模式:前端遍历 controls 生成"参数名: 值"列表 - 简化 tool schema — 模型只输出 title、description、options/controls、presets,全部结构化数据
Trade-off:灵活性降低(模型不能自定义预览和 prompt 措辞),但可接受 — 创意场景由 Playground Skill 兜底。
Q34:我们对"配表"的理解是不是搞错了?
是的。
之前的理解:AI 零代码,只输出纯 JSON 字段值 → 前端从模板渲染一切。
问题:视觉内容(PPT 长什么样、按钮长什么样)本质上需要 HTML/CSS 来表达,纯结构化字段表达不了。强行用 JSON 配表 → option 只剩文字标签 + emoji → 对"选 PPT 风格"这种需要看到实际效果的场景毫无帮助。
修正后的理解:配表 = 模板提供框架,AI 填充内容。AI 不是零代码,而是省代码。
模板负责(固定的):
- 整体布局(左控件 / 右预览 / 下方 prompt 栏)
- 控件渲染逻辑(slider、select、toggle、color picker 的绘制和交互)
- 状态管理(选中了什么、参数值是多少)
- prompt 自动生成 + Send to Chat 机制
- 整体视觉风格和动画
AI 负责(自由发挥的):
- 右侧预览区的实际内容(比如画一个 PPT 幻灯片的 HTML/CSS)
- 具体有哪些选项、哪些控件、什么参数范围
- 预览内容怎么响应用户的选择和参数变化
## render_tournament 设计
Q38:Tournament 的淘汰赛和偏好发现是两种模式吗?
不是。 从模板角度完全一样 — 都是「展示两张卡片 → 用户选一张 → 下一轮 → 收集结果」。
区别只在 AI 怎么填 JSON:
- 淘汰赛:rounds 里放同类选项(8 种食物两两配对)
- 偏好发现:rounds 里放跨维度对比(海边 vs 山里、穷游 vs 舒适)
模板不需要区分。一个模板,AI 自己决定怎么用。
Q39:需要 skip 功能吗?
不需要。 每轮必须选一个,轮数固定。YAGNI — 先做最简版本。
Q40:卡片需要 previewHtml 吗?
不需要。 Tournament 的核心是快速直觉选择 — 凭感觉点一个,不需要看视觉预览。emoji + 标题 + 一句话描述足够承载决策信息。
和 Playground 定位不同:Playground 是「看到效果才能选」,Tournament 是「凭直觉选」。
Q41:回传格式是排序还是流水账?
流水账。 记录每轮的选择事实,不做排序。
原因:
- 淘汰赛算不出真排序 — A 赢 B,C 赢 D,A 赢 C,但 B 和 D 没比过,不知道谁第几
- 模板更简单 — 每轮记一条,不需要排序算法
- AI 解读能力足够 — 给原始记录,AI 自己提炼偏好模式
Q42:结束后的流程?
直接 Send to Chat,和 playground / guided-form 保持一致。不做可编辑文本框。用户想补充说明可以在输入框里加。
Q43:Schema 定义
`json
{
"title": "五一去哪玩",
"description": "几轮快速二选一,帮你理清思路",
"rounds": [
{
"left": { "emoji": "🏖️", "title": "海边", "description": "阳光沙滩,放松躺平" },
"right": { "emoji": "🏔️", "title": "山里", "description": "清新空气,远离喧嚣" }
}
]
}
`
## Tournament 机制重新理解
Q45:第一版实现暴露了什么问题?
用户测试后发现两个问题:
- 每个维度只有1v1 — 用户说"去旅游",AI 只给了2个选项,或者每轮都是独立对比(A vs B, C vs D),没有擂台赛机制
- 赢家不守擂 — A 赢了 B 之后,下一轮突然变成 C vs D,A 消失了
根本原因:机制理解错了。
- 第一版理解:Tournament 是"预定义轮次"模式,AI 给一个 rounds 数组,前端按顺序播放
- 实际应该是:多维度擂台赛 — 每个维度独立跑擂台赛(赢家守擂,输家淘汰),最后收集每个维度的赢家形成偏好画像
Q46:什么是"多维度擂客赛"?
核心机制:
- 多个偏好维度(风格、预算、节奏等),每个维度对应一条独立的擂客赛赛道
- 每个维度内部是擂客赛:
- 3-5 个候选项
- 第一个是擂主(左边),第二个是挑战者(右边)
- 用户选赢家 → 赢家守擂(留在左边),输家淘汰,下一个候选项上场
- 直到池子空了,擂主就是该维度的赢家
- 结果是偏好画像:每个维度的赢家组合("海岛 · 预算敏感 · 慢节奏")
## 开发路线图
已完成:
- ✅ render_playground — 验证 Generative UI 管线
- ✅ 视觉规范落地 + playground 模板翻新(浅色主题)
- ✅ render_guided_form — 引导式信息收集
- ✅ 更新 playground skill / render_playground description 消除冲突
- ✅ "Send to Chat" 附件模式(替代 clipboard copy)
- ✅ InputToolbar 布局重设计 — 左右分组 + 合并 Tools + 删除 Reasoning/Preview
- ✅ Interact 菜单入口 — 交互方式按钮
- ✅ Playground options 模式 — 半配表半写架构
- ✅ render_tournament — 多维度擂客赛
下一步:
- 🔲 Generative UI 体验重构 — 从"功能齐全"到"让人想用,用得爽"
结论:Playground 要解决“用户说不清但看得懂”的探索型表达问题。
关键线索:
- 用户心中有一种想要的配色/布局/风格,但自己都说不出来具体是哪一种
- 如果给几个示例对比,用户一眼就能找到最接近心中印象的那个
- 这类需求在纯文本对话里需要反复来回描述,效率很低
- Playground 提供了一种"通过视觉对比和微调找到答案"的交互模式
结论:采用复用官方 Playground Skill 并做 Deskhand 适配的方案。
讨论过的方案:
- A: 从零设计一套探索式交互系统
- B: 复用官方插件,微调适配
- C: 只做配色探索这一个场景
选择 B。官方插件已有 6 个成熟模板,覆盖设计、数据、概念图、文档审阅、代码审查、架构可视化。模板规范详细(布局、控件、状态管理、prompt 输出),生成质量稳定。Deskhand 已有 Skills 系统,插件结构可直接纳入。改动量极小,只需调整输出目标和触发方式。
结论:触发方式采用“Agent 建议 + 用户确认”,兼顾可控与流畅。
讨论过的方案:
- A: Agent 自动判断并弹出 playground(体验流畅,但实现复杂,可能打扰用户)
- B: 用户主动触发(可控,但用户需要知道有这个功能)
- C: Agent 建议,用户确认后再弹出
选择 C。不会突然冒出来吓人,也不需要用户自己想到"我应该用探索模式"。Agent 的判断依据来自官方 SKILL.md:"when the input space is large, visual, or structural and hard to express as plain text"。建议话术示例:"这个需求比较适合用可视化方式探索,要不要试试?"
结论:Playground 展示在 Artifact Panel 的 iframe 中,复用现有基础设施。
讨论过的方案:
- A: Artifact Panel 的 iframe(不离开 Deskhand,复用已有基础设施)
- B: 独立 Electron 窗口(空间大,但多窗口管理复杂)
- C: 对话流内嵌 iframe(最紧凑,但交互空间受限)
选择 A。Artifact Panel 设计文档已确定:HTML 通过 sandbox="allow-scripts" + srcdoc 注入渲染。Agent 生成 playground HTML → Write 事件被捕获 → 自动出现在 Artifact Panel。零额外 UI 开发,完全复用已有的基础设施。
结论:v1 采用“复制 Prompt 回到对话”,暂不做自动回流。
讨论过的方案:
- A: 全自动回流 — 点确认后 postMessage 直接发送到对话(最流畅,但用户失去审阅机会)
- B: 自动填入输入框 — postMessage 填入聊天输入框,用户可改再发(需要新通信管道)
- C: 复制 Prompt — playground 底部生成自然语言 prompt,用户复制粘贴
选择 C。官方插件已内置 prompt 输出 + 复制按钮,零额外开发。复制粘贴是所有人都会的操作,不构成体验瓶颈。用户能看到、能改、能决定发不发。真正有价值的部分是 playground 本身(视觉探索),不是结果怎么传回去。如果后续用户反馈说复制粘贴太麻烦,再升级到 postMessage 方案也不迟。
结论:改动聚焦输出目标(写入 Artifact)和触发逻辑(支持 Agent 主动建议)。
改动 1 — 输出目标:
- 原版第 4 步:"Open in browser. After writing the HTML file, run
open <filename>.html" - 改为:"写入文件。Artifact Panel 会自动捕获 Write 事件并渲染 playground"
改动 2 — 触发逻辑:
- 原版 "When to use":"When the user asks for an interactive playground"
- 追加:"当 Agent 识别到用户需求涉及视觉、结构或多选项对比,且难以用纯文本表达时,主动建议用户使用 playground 探索"
不改的部分:
- 6 个模板文件(design-playground、data-explorer、concept-map、document-critique、diff-review、code-map)— 它们只是教 Agent 怎么写 HTML 的规范,与展示位置无关
- 核心要求(单 HTML 文件、实时预览、prompt 输出、复制按钮、预设方案、暗色主题)— 全部适用
- 状态管理模式(单一 state 对象 + updateAll)— 全部适用
结论:上线依赖 Artifact Panel 渲染能力与 Skills 系统分发能力。
- Artifact Panel 提供 HTML iframe 渲染能力 → playground 才能在 Deskhand 内展示
- 在 Artifact Panel 完成前,playground skill 仍可以 fallback 到
open命令在浏览器中打开 - Skills 系统已完成,skill 文件可以随时放入 Deskhand 专属 skills 目录
结论:本机制将街头采访中流行的 "This or That"(二选一)游戏改造为一种人机交互的偏好挖掘工具。
本机制将街头采访中流行的 "This or That"(二选一)游戏改造为一种人机交互的偏好挖掘工具。
核心理念:很多人对自己的偏好是"说不清但感觉得到"的。直接问"你喜欢什么风格的旅行",用户可能答不上来;但问"海岛 or 古城",用户能瞬间给出答案。通过一系列快速的直觉选择,把用户自己都不一定能言语化的偏好"逼"出来,再由 AI 分析并转化为具体推荐。
一句话描述:用直觉选择代替理性表达,让 AI 帮用户认识自己。
结论:需要挖掘的偏好方向,如"风格"、"预算"、"节奏"。
- 偏好维度(Dimension):一个需要挖掘的偏好方向,如"风格"、"预算"、"节奏"。每个维度对应一条独立的擂台赛赛道。
- 选项(Option):每个维度下的具体候选项,代表该维度的某种倾向。如"风格"维度下有"海岛"、"古城"、"都市"、"山野"等。
- 擂主(Champion):当前维度中上一轮胜出的选项。
- 挑战者(Challenger):从队列中依次取出的、与擂主对决的选项。
结论:需要问什么、不需要问什么、每个维度问几轮。
在进入擂台赛之前,AI 先分析用户已经给出的信息,决定需要问什么、不需要问什么、每个维度问几轮。
核心原则:
- 已知信息直接跳过:用户说"我想去日本",则"目的地国家"维度已确定,无需再问。AI 应自动下钻到更细的维度(如"城市偏好:东京 or 京都 or 大阪")。
- 高区分度维度优先:优先问那些对最终推荐影响最大的维度。例如旅游场景中"风格"比"是否需要 WiFi"重要得多。
- 信息足够即可终止:不需要把所有维度都问完。如果前 2 个维度的结果已经能把候选范围收敛到少数几个,后续维度可以跳过。
结论:每个选项应代表一种清晰可感知的倾向,避免含糊或重叠。
- 每个选项应代表一种清晰可感知的倾向,避免含糊或重叠。
- 选项之间应有足够的区分度,让用户能凭直觉快速判断。
- 数量适中:每个维度 3-5 个选项为宜,太多会让用户疲劳。
结论:需要向下挖掘决策信息"的场景,例如:旅游目的地推荐、餐厅/菜品推荐、礼物挑选、穿搭风格探索、学习路径规划、居住环境偏。
此机制适用于任何"用户难以直接决策需要向下挖掘决策信息"的场景,例如:旅游目的地推荐、餐厅/菜品推荐、礼物挑选、穿搭风格探索、学习路径规划、居住环境偏好等。
结论:默认宽度固定,左侧列表又占了很大一块,真正能看内容的预览区太窄。
问题不在功能多少,而在空间分配不合理。
默认宽度固定,左侧列表又占了很大一块,真正能看内容的预览区太窄。到了笔记本屏幕上,网页和文档都挤在一条细长区域里,很多场景根本看不清。
结论:默认宽度改成"可用空间的一半",不再用固定像素当起点。
默认宽度改成“可用空间的一半”,不再用固定像素当起点。可用空间就是窗口宽度减去会话侧栏。
同时保留两条底线。
- 对话区不能低于最小可读宽度。
- 面板自身也保持最小宽度,避免收得太窄。
这样做的好处是,大小屏都能得到可用初始状态,用户拖拽后也不会把某一侧压到不可用。
结论:首版场景里,用户一次只会专注看一个 artifact。
首版场景里,用户一次只会专注看一个 artifact。常驻侧栏长期占宽度,性价比不高。
改成 toolbar 内的文件名下拉后,切换能力还在,但横向空间都回到了预览区。右侧再保留几个高频动作按钮(比如打开文件位置、复制、刷新),日常操作不会变慢。
结论:我们把边界情况提前定死了。没有 artifact 时,下拉不可用并显示占位信息。
我们把边界情况提前定死了。
- 没有 artifact 时,下拉不可用并显示占位信息。
- 只有一个 artifact 时,下拉仍可点,但不会引入多余流程。
- 新 artifact 生成后自动选中最新项,延续原有习惯。
- 窗口缩小时自动收敛面板宽度,避免把对话区挤坏。
这些约束能保证行为可预期,不会因为窗口尺寸或文件数量变化而突然失控。
结论:不做。有三件事故意不做。不上 tab 形态的多文件栏。
有三件事故意不做。
- 不上 tab 形态的多文件栏。
- 不做“按比例”持久化,只记住用户上次的像素宽度。
- 不做窗口放大后的自动扩张。
理由很简单:这些都不是当前体验瓶颈。先把“看得清、切得快、行为稳”做扎实,再考虑扩展。
结论:需要 Artifact Panel,把 Agent 产出从“文件路径”转成可直接查看的结果。
关键线索:
- agent 能操作整个电脑的文件,不限于某个目录
- 用户每次去 Finder 找文件太麻烦
- 只展示某个目录的话,被操作的文件不一定在范围内
- MD/HTML/Mermaid 等文件渲染前难以阅读,非技术者没有趁手工具
- 没有这个面板,agent 说"我创建了 index.html"跟 ChatGPT 没区别
结论:Artifact Panel 定位为“展示效果”的独立面板,面向非技术用户。
讨论过的方案:
- A: 展示文件(偏开发者)— 文件树、代码高亮、diff
- B: 展示效果(偏非技术者)— 渲染后的网页、文档、图表
- C: 不需要独立面板 — 在聊天流里内联展示
选择 B。Deskhand 面向非技术者,技术用户会选 Cursor 等 IDE。非技术用户不关心源码,关心"成品"。"帮我做个网页" → 直接看到渲染后的网页,这才是差异化体验。
结论:多文件采用“左侧列表 + 右侧单文件预览”,默认 preview 模式。
讨论过的方案:
- A: 平铺列表 — 按时间排列
- B: 标签页切换 — 每个文件一个 tab
- C: 单文件聚焦 — 侧边列表可切换,同一时间只展示一个文件
选择 C。现有 UI 已经是左 200px + 右预览的布局,直接复用。非技术用户一次只需关注一个产出物。
UI 变化:
- 去掉 4 个 tab(Files/Changes/Terminal/Browser)— Changes 和 Terminal 是开发者概念,Browser 合并到预览
- 左侧 200px:artifact 列表,按操作时间排序,显示文件名 + 类型图标
- 右侧:渲染预览区,默认 preview 模式
- 保留 code/preview 切换,但默认 preview
- 保留复制、刷新按钮
- 每个 artifact 项提供"在 Finder 中打开"
结论:围绕「数据怎么捕获」,最终方案按文中取舍执行。
讨论过的方案:
- A: 监听 tool_start/tool_result 事件,Write/Edit 时提取文件路径
- B: 从 tool_use 消息内容中解析
选择 A。useAgentEvents.ts 已经在处理这些事件,加个分支就行。实时性好,agent 一写文件面板就响应。Bash 间接创建文件的情况先不管,后续再补。
流程:
- agent 调用 Write/Edit → tool_start 事件
- 提取文件路径 → IPC 读取文件内容
- 加入 artifactsAtom → 面板自动打开并选中
- 同一文件多次编辑不重复,预览自动刷新
结论:首版优先支持 HTML、Markdown 和图片渲染,其他类型走代码高亮兜底。
- HTML — iframe 渲染(最有冲击力的场景)
- Markdown — 格式化文档(非技术用户最常遇到)
- 图片(png/jpg/svg)— 直接展示
- 其他 — 代码高亮兜底,后续可扩展 Mermaid、CSV 等
结论:HTML 采用 iframe sandbox + srcdoc 渲染,允许脚本但隔离同源能力。
讨论过的方案:
- A: 完全沙箱 — 不加载外部资源,不执行 JS
- B: 允许外部资源,不执行 JS
- C: 基本放开 — allow-scripts,不加 allow-same-origin,srcdoc 注入
- D: Electron webview — 独立进程
选择 C。agent 生成的 HTML 大概率用 Tailwind CDN 等外部资源,不让加载页面就废了。sandbox="allow-scripts" 不加 allow-same-origin,通过 srcdoc 注入内容。无法访问父窗口、本地文件系统、Electron API。跟 Claude.ai Artifacts 做法一致。webview 在新版 Electron 中不推荐,且复杂度高。
结论:Artifact 列表按会话持久化,重开历史会话可恢复文件列表。
讨论过的方案:
- A: 不持久化 — 关 app 就没了
- B: 跟随会话持久化 — 存在会话元数据里
- C: 不持久化,提供"在 Finder 中打开"按钮
选择 B。打开历史对话,聊天记录写着"我创建了 index.html"但面板是空的,体验割裂。实现简单:会话元数据里存 artifacts: string[](文件路径数组)。打开会话时检查文件是否还在磁盘上,不在的标灰或移除。不缓存文件内容,每次从磁盘实时读取。
## 实现规划
Slice 1:最小端到端链路(纯文本)
目标:agent 写文件 → 面板自动弹出并显示内容
- 监听 Write/Edit 工具事件 → 提取文件路径 → 加入 artifactsAtom
- 去掉 4 个 tab,简化为单一视图
- 左侧显示 artifact 列表(文件名 + 路径)
- 点击 artifact → IPC 读取文件内容 → 右侧显示纯文本
- 面板在首个 artifact 出现时自动打开
- 验收:让 agent 写个文件,面板自动弹出并显示内容
Slice 2:渲染能力
目标:agent 做网页 → 面板里直接看到渲染效果
- HTML → iframe sandbox 渲染(srcdoc 注入,allow-scripts)
- Markdown → 格式化文档渲染
- 图片(png/jpg/svg)→ 直接展示
- code/preview 切换(默认 preview)
- 其他文件 → 代码高亮兜底
- 验收:让 agent 做个网页,面板里直接看到渲染效果
Slice 3:持久化 + 体验打磨
目标:关掉 app 重开,历史对话的 artifact 还在
- artifact 路径存入会话元数据(
artifacts: string[]) - 打开历史会话时恢复 artifact 列表
- 文件不存在时标灰处理
- "在 Finder 中打开"按钮
- artifact 列表项的图标和样式优化
- 验收:关掉 app 重开,历史对话的 artifact 还在
结论:转向剪贴板,因为“截屏即回复”差异化弱且 Computer Use 尚不成熟。
在屏幕感知方向的讨论中发现两个问题:
- "截屏让 AI 回复"跟"手动截图发 ChatGPT"只是省了几步操作,不是真正差异化
- 真正的差异化(AI 操作电脑)技术不够成熟,Computer Use 准确率不足以面向小白用户
转向剪贴板方向后,最初提出的用户故事(翻译、解释、OCR 等)同样存在问题:这些都可以直接发给 ChatGPT 解决,专门做一层"便捷 UI"可能反而带来功能的不通用/不稳定,没有实质价值。
结论:核心洞察是把“剪贴板历史”与 AI 结合成可检索的共享记忆。
灵感来源:类似 Paste(macOS 剪贴板管理器)的产品,记录每一次复制操作。
关键洞察:Paste 这类工具只能存和搜,ChatGPT 只能处理你手动粘贴的内容。两者结合后,剪贴板变成用户和 AI 之间的共享记忆。
核心差异化:ChatGPT 是"你给我什么我处理什么",Deskhand 是"你复制过的一切我都记得,随时可以调用"。
结论:剪贴板智能聚焦 Clip Collect、Clip Recall、Clip Insight 三大能力。
Clip Collect(批量采集)
连续复制多条内容,不用每次切窗口粘贴,最后一次性交给 AI 处理。
核心场景:用户在做信息收集类工作时,需要频繁在不同来源之间切换并记录内容。传统方式是"复制 → 切窗口 → 粘贴到文档 → 切回来",非常繁琐。有了 Clip Collect,用户只需连续复制,最后一次性让 AI 处理。
场景 1:"整理用户反馈"
社区运营在论坛翻阅不同帖子,每看到一条有价值的用户原话就复制一下。最后跟 AI 说"帮我整理一下用户关于 XX 功能的发言" → AI 把所有采集的片段归类整理。
场景 2:"竞品对比"
产品经理浏览 5 个竞品官网,每个页面复制一段核心卖点。最后"帮我把刚才复制的竞品信息做个对比表" → AI 结构化成表格。
场景 3:"收集面试题"
用户刷面试经验帖,看到好的面试题就复制。最后"帮我把这些面试题分类整理" → AI 按类型归类。
场景 4:"做旅行攻略"
用户在小红书/马蜂窝翻游记,复制感兴趣的餐厅、景点、住宿。最后"帮我用这些内容做一个三天的行程安排" → AI 组织成完整攻略。
Clip Recall(复制回溯)
从剪贴板历史中检索过去复制过的内容。用自然语言描述即可,AI 用语义搜索定位。
场景 1:"验证码被覆盖了"
复制了短信验证码,习惯性又复制了别的东西,回到登录页粘贴出来不是验证码。→ "我刚才复制的验证码是什么" → 秒找回。
场景 2:"那个链接我还要用"
半小时前从微信群复制了一个链接,后来剪贴板被覆盖。→ "帮我找一下我之前复制的那个链接" → AI 定位到。
场景 3:"对方的地址我复制过"
从聊天记录复制了朋友的收货地址,用完后被覆盖,另一个平台下单又需要。→ "我之前复制过一个地址" → AI 找到。
场景 4:"那段话原文是什么来着"
写文章时记得之前复制过一段好的引用,但忘了原文。→ "我前两天复制过一段关于 XX 的话" → AI 语义搜索找到最匹配的。
场景 5:"API KEY 只显示了一次"
注册服务时 API KEY 只显示一次,用户复制了但后来被覆盖,找不到了。→ "我之前复制过一个 API KEY" → AI 从历史中找回。(需要隐私设计考量)
Clip Insight(轨迹洞察)
剪贴板历史作为数据源,AI 分析用户的工作/生活轨迹。不是"我这周复制了什么",而是 AI 把剪贴板当作一个信号源,在需要回顾工作轨迹时自动调用。
场景:"辅助工作回顾"
用户想回顾本周工作。AI 结合剪贴板历史(复制过的链接、文本、代码片段)分析出工作主题和轨迹,生成词云图/星图/时间线等可视化报告。
结论:不做独立 UI,剪贴板历史作为 AI 上下文自动可用。
探讨了三种方案:
- A: 对话内的隐式能力 — 不做独立 UI,剪贴板历史作为 AI 上下文自动可用。用户在对话中自然引用"我之前复制的 XX"即可。
- B: 独立的剪贴板面板 — 类似 Paste 的可视化面板,展示历史、支持搜索和拖拽到对话。
- C: 系统级快捷入口 — 全局快捷键呼出轻量面板(类似 Raycast/Alfred),不依赖主窗口。
决定:A(对话为主)+ 轻量感知提示。
- 对话是小白已经会的交互方式,不需要学新界面
- B(独立面板)增加理解成本,小白不知道面板跟对话怎么配合
- C(全局快捷键)小白记不住,实现复杂度高,v1 不值得
- 纯 A 的问题是用户不知道能力存在,所以需要轻量提示(如输入框附近显示"📋 已记录 N 条复制",或新复制时闪一下"已记录"),让用户感知到 AI 在帮忙记着
结论:采用“自然语言 + 📋面板”双通路引用剪贴板内容。
探讨了三种方式:
- 纯自然语言 — 直接说"我之前复制过一个地址",AI 自己判断。最自然但可能误判。
- 约定关键词 — 用"剪贴板"等触发词,AI 看到就查历史。更可靠但需要用户知道约定。
- 输入框快捷操作 —
@clipboard或📋按钮,把剪贴板历史作为附件插入对话。最明确但@语法对小白不直观。
讨论中发现一个关键问题:对小白来说,"描述自己要找什么"(回忆/recall)比"看到就认出来"(识别/recognition)难得多。 纯对话方式要求用户能描述出来,但很多时候他们只是"看到就能认出来"。
修正决定:自然语言为主 + 输入框📋按钮触发轻量剪贴板面板。
两条路径并存:
- 知道自己要什么 → 直接打字"帮我找之前复制的地址"
- 不确定要什么 → 点📋按钮,弹出卡片列表浏览,点选后自动作为对话上下文附加
面板的具体设计待下一步讨论。
结论:面板采用输入框上方弹出的卡片式列表,作为对话辅助而非独立工具。
参考 Paste(macOS 剪贴板管理器)的卡片式浏览体验,但适配 Deskhand 的场景——面板是对话的辅助,不是独立工具。
布局: 从输入框上方弹出的纵向列表,最新的在最上面,往下滚动看更早的。类似 Spotlight 搜索结果的形态。(Paste 的横向滚动适合全屏工具,但从输入框弹出的面板空间有限,纵向更合适。)
每条记录展示:
- 内容预览(文本截断前 N 字 / 图片缩略图 / 链接标题)
- 时间戳("3 分钟前"、"今天 14:30")
- 内容类型图标(文字、图片、链接、代码)
选中交互: 点击一条记录 → 作为"附件"出现在输入框上方(类似发图片的体验)。用户可以继续打字补充指令,然后一起发送。支持多选。
搜索: 面板顶部有搜索框,支持关键词过滤。对 Clip Recall 场景(找特定内容)比翻列表更快。
结论:采用“全部记录 + 本地加密存储”,并在发送给 AI 时按需解密。
剪贴板会经过敏感内容(密码、银行卡号、API KEY 等)。探讨了三种方式:
- 全部记录,本地加密存储 — 什么都记,数据只存本地,用系统 Keychain 加密。发给 AI 时才解密。
- 智能过滤 + 用户可选 — 默认过滤明显敏感内容,提供开关让用户选择"记录所有"。
- 只记录元数据 — 不存原文,只记录类型/长度/来源/时间。最安全但功能大打折扣。
决定:方式 1(全部记录,本地加密存储)。
Deskhand 本身就是能读写文件、执行命令的桌面 agent,用户已经给了很高信任。剪贴板数据的敏感度不会比文件系统更高。本地加密足够。
结论:需要直接比较内容。参考了两个开源剪贴板管理器:Maccy、Pesto。
参考了两个开源剪贴板管理器:Maccy、Pesto。
监控方式:轮询,0.5 秒间隔,几乎零开销。
macOS 没有剪贴板变化的事件通知 API,所有剪贴板管理器都用轮询。Maccy 和 Pesto 都是 0.5 秒一次。原生 Swift 应用利用 NSPasteboard.changeCount(一个整数),每次轮询只比较这个数字,没变化就跳过——99% 的周期开销接近于零。
Electron 的 clipboard 模块没有 changeCount,需要直接比较内容。优化方案:每次轮询对当前剪贴板内容算哈希,跟上次比较,不同才处理。开销依然很小。
值得借鉴的模式:
- 自检测标记(Pesto):Deskhand 自己写入剪贴板时加标记,监控时跳过,避免死循环
- 重复检测:内容哈希去重,同样的内容不重复记录
- 密码管理器过滤:检测 1Password/Bitwarden 等的特殊剪贴板类型,默认跳过(与 Q7 隐私策略配合)
存储方案:JSONL 文件。 与现有会话存储架构一致,图片单独存文件、JSONL 存路径。v1 用简单字符串匹配做搜索,后续有需要再升级。
结论:AI 先通过现有 Read 工具读取剪贴板历史文件,v1 不新增专用 Tool。
探讨了两种思路:
- A: 专用 Tool — 注册
clipboard_history工具,AI 按需调用查询,支持时间/关键词/类型过滤 - B: 直接 Read — 剪贴板历史就是 JSONL 文件,Agent 用现有 Read 工具直接读
决定:B(直接 Read),不造新轮子。
数据已经存在 JSONL 文件里,Agent 已有 Read 工具,AI 完全能自己解析和过滤。专用 Tool 只是把"读文件 + 过滤"封装一层,v1 没必要。
实现要点:在 Agent 的 system prompt 中告知剪贴板历史文件的路径,让 AI 在用户提到"剪贴板"相关内容时知道去哪读。历史过长时通过限制文件大小或分页解决。
结论:需要发给 AI,但全文内联可能撑爆 context。
问题 1:自然语言查询 + 历史很长
用户说"我之前有一封邮件",如果让 AI 直接读整个 JSONL(可能几千条),token 成本和延迟不可接受。
解决方案:轻量索引文件。 主进程在记录剪贴板的同时维护 clipboard-index.jsonl,每条只存元数据(id、时间、类型、前 N 字符预览、字符数)。AI 先读索引定位候选,再读具体条目的完整内容。
问题 2:用户从面板选了多条长内容
选中内容需要发给 AI,但全文内联可能撑爆 context。
解决方案:截断 + 引用。 参考 Claude Code 的实际行为制定内容策略。
调研发现:Claude Code 读取图片时直接读原图,没有"先缩略图再按需看原图"的机制。大量内容会撑爆 context window,触发自动摘要导致早期内容丢失。建议分批处理(每次 5-10 张)。
基于此制定以下策略:
| 类型 | 面板展示 | 发给 AI | 限制 |
|---|---|---|---|
| 短文本(≤500字符) | 全文预览 | 全文内联 | 无 |
| 长文本(>500字符) | 截断预览 | 前500字符 + 引用(AI 需要时自己 Read 完整版) | 无 |
| 图片 | 缩略图(UI 性能) | 原图 | 单次最多 5 张 |
不限制用户选择文本条数(截断机制已兜底),图片超过 5 张时提示分批处理。
存储上限:500 条记录,超出后清理最早的。
结论:v1 先做 Clip Recall(复制回溯)。
v1 先做 Clip Recall(复制回溯)。
- 技术最简单,只需"记录 + 读取"
- 用户价值最即时——"找回被覆盖的内容"一次就能感受到
- Collect 和 Insight 都依赖同样的基础设施,Recall 做完基础就打好了
- Collect 本质上是 Recall + "AI 批量处理选中内容",v2 自然延伸
v1 scope:
- 后台剪贴板监控 + JSONL 存储 + 索引
- 输入框📋按钮 + 剪贴板面板(浏览/搜索/选择)
- 选中内容作为对话上下文发送
- 自然语言引用(system prompt 告知文件路径)
Collect 和 Insight 作为 v2。
结论:默认不做持续提示,只在首次使用时做轻量引导。
现有架构
InputToolbar 已有的布局:
- 📎按钮 - 附件入口(可复用)
- 弹出面板模式已有先例(Workspace/Tools/Skills)
入口
复用📎按钮。 点击后弹出面板,面板内有"文件"和"剪贴板"两个 tab。
- 📎本身就是"附加内容"的语义,剪贴板也是一种附加内容
- 小白不需要区分"文件附件"和"剪贴板"——都是"给 AI 看一个东西"
- 工具栏已有 7 个按钮,不宜再加
面板设计
原设计是纵向列表 + emoji 图标,实际验收发现 4 个问题:
- 📎按钮被剪贴板独占,文件附件功能丢失
- emoji 图标不专业
- 固定 340px 宽度太窄,内容预览被截断
- 纵向列表无法合理展示图片缩略图
面板形态:内容浏览面板
- 从输入框上方弹出,宽度与输入框等宽
- 比现有 popup(Tools/Skills)更大,因为这是内容浏览型面板
- 保持 popup 交互模式(点外面关闭)
Tab 结构:
- Tab 1: Files(文件附件选择)
- Tab 2: Clipboard(剪贴板历史)
卡片网格布局(2 列,纵向滚动)
卡片设计:
- 类型图标:SVG 线条图标(跟工具栏风格统一),不用 emoji
- 文本卡片:多行预览,底部显示时间 + 字符数
- 图片卡片:显示缩略图,底部显示时间 + 文件大小
- 链接卡片:显示 URL 预览
选中交互
- 点击卡片 → 边框高亮 + 勾选标记(再点取消)
- 支持多选
- 选中后面板底部出现确认栏:"已选 N 项 · [确认附加]"
- 点确认 → 面板关闭 → 选中内容作为附件出现在输入框上方(预览卡片 + ❌移除按钮)
- 用户打字补充指令 → 一起发送
感知提示
探讨了三种方式:
- 📎按钮动态 badge — 显示"已记录 N 条",数字变化时闪一下
- 新复制时 toast — 每次新复制在角落闪一个"已记录"提示
- 不做提示 — 用户打开📎面板自然看到历史
决定:方式 3(不做提示)。
后台功能不需要不断刷存在感。就像 Time Machine 不会一直告诉你"已备份 N 个文件"。用户知道它在跑就行,需要时打开就能找到。
唯一需要的是首次引导:用户第一次打开 Deskhand 时告知"你的剪贴板历史会自动记录,随时可以通过📎找回"。之后不再打扰。
结论:剪贴板应作为 AI 的透明环境能力,而不是需要学习的独立功能。
反思
v1 实现完成后回顾发现,我们把剪贴板当成了一个"独立产品功能"来设计——有专门的面板 UI、专门的交互流程、专门的引导教育。但这偏离了真正的产品价值。
类比:Claude Code 的文件访问。 CC 从不告诉用户"我可以读你的 ~/projects 目录"。用户说"帮我看看那个 config 文件",CC 就去读了。用户第一次看到这个行为时自然理解了"哦原来你能读我的文件",之后就把它当成基础能力来用。
剪贴板应该完全一样。它不是一个需要用户认知和学习的"功能",而是 AI 能力的一个透明扩展——就像 AI 能读文件一样,它也能读剪贴板历史。
核心洞察
Deskhand 跟 ChatGPT 的本质区别:ChatGPT 活在浏览器里,只知道用户告诉它的东西。Deskhand 活在用户的电脑上,能感知用户的工作环境。
剪贴板只是"实例。这个模式可以扩展到:
- 最近打开/修改的文件
- 环境感知"的第一个浏览器最近访问的页面
- 日历事件
- 等等
这些都不是"功能",而是 AI 可以按需调用的数据源。用户不需要知道 AI 能访问哪些数据源——只管说要什么,AI 自己判断该去哪找。
对已有实现的重新定位
| 模块 | 原定位 | 新定位 |
|---|---|---|
| 后台监控 + 存储 | 剪贴板功能的基础 | 环境感知基础设施(正确) |
| System prompt 注入 | Slice 4 的一个步骤 | 核心交互路径(最重要) |
| 📎面板 + 卡片网格 | 核心交互入口 | 附件选择器的一个 tab(辅助) |
| 搜索框 | 核心功能 | 低优先级(自然语言路径更重要) |
| 首次引导提示 | 必须做 | 不需要(用户通过使用自然发现) |
理想体验
用户完全不知道有"剪贴板功能"这回事。他只是正常跟 AI 聊天,某天说了一句"我之前复制过一个什么什么",AI 就找到了。那个时刻用户会觉得"这个 AI 真的懂我"——这比任何面板 UI 都有冲击力。
后续方向
- 抽象"透明数据源"模式:让添加新的环境数据源(文件、浏览器历史等)变得简单
- 优化 System prompt:不是告诉 AI "你有剪贴板功能",而是"你可以访问用户的环境数据,在需要时自行调用"
- 面板 UI 保持现状:已做的不删,但不再投入更多精力打磨。定位从"剪贴板管理器"变成"附件选择器的一个 tab"
- Clip Collect / Clip Insight 重新评估:在"透明能力"框架下,这些可能不需要专门的 UI,而是 AI 在合适语境下自然触发的行为
结论:围绕「Deskhand目前最大的短板是」,最终方案按文中取舍执行。
缺少差异化。 跟 ChatGPT Desktop / Claude Desktop 比,没有足够的理由让人切换过来。
结论:围绕「核心用户是谁」,最终方案按文中取舍执行。
纯小白用户。 不懂代码的人,想用 AI 完成日常任务(整理文件,写内容、处理信息)。
提出了三个方向:
- A: 任务模板 / 场景化入口 — 把 AI 能力翻译成可点击的任务卡片,降低"不知道说什么"的门槛
- B: 主动感知 + 建议 — AI 主动发现用户环境中的问题并提供帮助,从被动等指令变成主动服务
- C: 可视化工作流 / 自动化 — 自然语言驱动的自动化,类似 Automator
结论:优先探索“任务模板、主动感知建议、可视化工作流”三条差异化方向。
在"主动感知"大方向下,提出了四个子方向:
- 个人记忆助手 — 记住用户在电脑上做过的一切,帮用户找回信息。类似轻量版 Rewind.ai
- 看屏幕说话的助手 — 用户按快捷键截屏,AI 理解当前画面并提供上下文帮助
- 文件落地即处理 — 监控文件夹,新文件出现时自动触发 AI 处理(摘要、OCR、分类等)
- 每日 Briefing — 每天主动给用户一个电脑状态简报
结论:记住用户在电脑上做过的一切,帮用户找回信息。
结论:核心概念是一键截屏后由 AI 直接理解当前界面并给出帮助。
一键截屏,AI 看懂你在干嘛。
用户在任何场景下卡住了,按一个快捷键(如 Cmd+Shift+D),Deskhand 截取当前屏幕,AI 看到画面后主动理解并提供帮助。
跟现有产品的本质区别:
- ChatGPT / Claude Desktop:用户得自己截图、粘贴、描述问题
- Deskhand:一个快捷键,AI 自己看、自己理解、自己建议
技术可行性:Electron desktopCapturer API 截屏 + Claude Vision 理解图片。
结论:这些用户故事可复用同一条能力链路:截屏、理解、给建议。
故事 1:"这个弹窗什么意思?"
用户电脑弹出英文对话框(如 "Your disk is almost full"),看不懂不敢乱点。截屏 → AI 用大白话解释弹窗含义并建议操作。
故事 2:"这软件怎么操作?"
用户第一次打开 Keynote/Excel 等软件,面对复杂界面不知所措。截屏 → AI 识别软件并引导下一步操作。
故事 3:"这个页面我该填什么?"
用户在银行网站/政府网站填表,不理解字段含义。截屏 → AI 逐个解释每个字段该填什么。
故事 4:"帮我把这个记下来"
用户看到有用信息(菜谱、电话号码、操作步骤),想保存但不知道怎么高效操作。截屏 → AI 自动提取关键信息并整理保存。
故事 5:"这个东西值得买吗?"
用户在电商网站看商品,截屏 → AI 分析商品参数、价格,给出购买建议。
故事 6:"我电脑出问题了"
用户电脑很卡/软件报错/出现异常。截屏 → AI 诊断问题并给出解决方案,甚至可以直接帮用户执行修复。
补充:这些故事不需要分开实现。从技术角度看,所有故事的流程完全一样:截屏 → AI 看图 → 给建议。AI 自己能从截图判断用户处于什么场景。用户只需学会一个动作(按快捷键)。
结论:初步倾向“画中画浮窗”承载截图分析结果,但仍待最终确认。
AI 分析完截图后,结果应该展示在哪里?探讨了 7 种方案:
- 系统通知式 — macOS 通知形式出现在右上角。简短不打断,但空间太小
- Spotlight 式弹窗 — 屏幕中央弹出面板,可追问。轻量且有足够空间
- 画中画浮窗 — 屏幕角落小窗口,可拖动缩放。能同时看原界面和 AI 建议
- 屏幕标注覆盖层 — AI 直接在截图上标注箭头、高亮框。最直观但技术最复杂
- 侧边栏滑出 — 从屏幕右侧滑出窄面板。有空间但遮挡较多
- 回到 Deskhand 主窗口 — 截图作为消息插入对话。最简单但需切换窗口
- 语音回复 — AI 语音说出建议,不显示文字。对老年用户友好但不适合所有场景
初步倾向方案 3(画中画浮窗),但未最终决定。
结论:仅靠“快捷截屏回复”不构成差异化,关键在于 AI 能直接代操作。
讨论中发现一个关键问题:"按快捷键截屏让 AI 回复"跟"手动截图发给 ChatGPT"本质上只是省了几步操作,不是真正的差异化。
真正的区别应该是:ChatGPT 只能告诉你该怎么做,Deskhand 能直接帮你做。
- ChatGPT:"你应该点击右上角的'关闭'按钮"
- Deskhand:"我看到了这个弹窗,要我帮你关掉它吗?" → 用户说好 → AI 直接操作
桌面应用的杀手锏是能操作用户的电脑——通过 macOS Accessibility API 或鼠标键盘模拟,AI 可以帮用户点按钮、填表单、打开应用、移动文件。
结论:Computer Use 当前的准确率不够高。
Computer Use 当前的准确率不够高。点错按钮、填错字段,对小白用户是灾难性体验。技术不稳时强推只会砸口碑。
结论:首次启动自动进入可聊天状态,回访用户可直接看到历史会话。
讨论过的方案:
- A: 启动时自动创建一个空 session(用户打开就能直接聊)
- B: 启动时不创建,显示空状态,用户点「New Chat」才创建
- C: 启动时加载已有 sessions,如果没有才自动创建一个
选择 C。首次用户不需要任何额外操作就能开始用,回访用户也能看到历史会话。两种需求都被满足了,不用纠结。
结论:消息在用户发送和每个 agent 事件到达时即时写入。
讨论过的方案:
- A: 每条消息到达时立即写入(用户消息发送时 + 每个 agent 事件到达时)
- B: Agent 完成一轮回复后批量写入(等
complete事件再一次性写) - C: 像 craft-agent 那样用队列 + 防抖(事件到达时入队,合并频繁写入)
选择 A。这样最省事。JSONL(一种每行一条 JSON 的文本格式)天然支持往文件末尾追加,不用读整个文件就能加新内容。而且即使程序崩了,消息也不会丢。后面要是性能吃不消了,再在外层套个队列就行,接口不变。
结论:采用懒加载策略,仅在切换会话时读取该会话消息。
讨论过的方案:
- A: 启动时全部加载(所有 session 的消息都读进内存)
- B: 懒加载(启动时只加载元数据,切换时才加载消息)
选择 B。会话一多,一次性全加载肯定卡。侧边栏其实只需要知道每个会话的名称、时间、预览信息就够了,不用把全部消息都读进来。
结论:新建会话先建内存态,用户发送首条消息后再落盘。
讨论过的方案:
- A: 点击按钮 → 立即创建 session 并写入磁盘 → 切换过去
- B: 点击按钮 → 先创建内存中的空 session → 用户发第一条消息时才持久化
选择 B。用户可能手误点错了,或者创建了但不想聊。结果产生一堆空会话文件,后期清理也麻烦。改成用户真正发消息了再存,逻辑上更合理——只有开始聊天了才算创建了一个会话。
结论:会话命名先用首条用户消息截断,后续再补 AI 命名。
讨论过的方案:
- A: 用第一条用户消息的前 N 个字作为 preview(简单截断,不调 AI)
- B: Agent 第一轮回复完成后,用 AI 生成简短标题
- C: 先用 A 作为临时名称,后续再加 B 作为优化
选择 C。方案 A 不需要任何成本,发完消息侧边栏就能看到。方案 B 要调用 API、要设计提示词,后面再加也不迟。先让产品能跑起来再说。
结论:操作入口采用 hover 的“···”菜单,统一承载重命名、归档和删除。
讨论过的方案:
- A: Hover 时显示删除按钮
- B: 右键菜单(Rename / Delete)
- C: Hover 显示
···按钮,点击弹出菜单
选择 C。··· 是最常见的设计,用户都熟悉。hover 时才出现,不占地方。菜单扩展性也强,以后想加什么功能直接往里塞就行。
第一版菜单项:
- Rename — 点击后 inline 编辑名称
- Archive — 设置
hidden: true,从列表消失 - Delete — 弹确认框,确认后删除
结论:日期分组首版不做,先保持按时间倒序。
SPEC 要求按日期分组(Today / Yesterday / Dec 19),但讨论后决定不做。ChatGPT 都没分组,用户早习惯了。简单按时间倒序,最近聊的在最上面,找起来也方便。分组反而增加认知负担,不值当。
结论:会话搜索首版不做,待会话量增长后再加。
讨论过的方案:
- A: 第一版就做(输入框过滤)
- B: 先不做,后面再加
选择 B。会话少的时候,搜索根本用不上。先把核心功能跑通,搜索什么时候加都不晚。
结论:未读标记首版采用简单蓝点方案。
讨论过的方案:
- A: 简单的蓝色圆点,点击进入后自动消除
- B: 数字 badge(显示未读消息数)
- C: 先不做
选择 A。蓝色圆点简洁明了,够用了。数字 badge 信息量太大,非技术用户看了反而懵。就一个布尔值的事,实现成本也低。
结论:分类机制首版不做,仅保留 Archive 的显隐管理。
讨论过的方案:
- 目录分类
- 人工 label 分类
- 收藏/Pin
全部不做。非技术用户最怕的就是「整理」。ChatGPT 也没有分类,按时间排就够了。Archive 是唯一保留的"分类"——就两个状态:显示或隐藏。把列表弄干净比啥都强。等用户量起来了再根据反馈加功能。
## 设计总结
Session 生命周期
App 启动 → 加载元数据 → 有会话则选中最近的,没有则创建一个空会话
用户点 New Chat → 创建内存态 session → 设为当前会话
用户发第一条消息 → 持久化到磁盘 → 写入用户消息
Agent 事件到达 → 更新 UI → 写入磁盘 → 更新会话元数据
用户切换会话 → 切换 active session → 未加载则加载消息 → 已加载则使用缓存
SessionItem 显示
名称 = name > preview > "New Chat"
时间 = 相对时间(2m / 1h / 3d)
··· 菜单
- Rename — 点击后 inline 编辑名称
- Archive — 隐藏会话
- Delete — 弹确认框,确认后删除
不做的事(YAGNI)
- 日期分组
- 会话搜索
- AI 自动命名
- 分类/标签/收藏
- 虚拟滚动
- 多窗口
## 实现规划
Slice 1:Session 持久化链路(地基)
- App 启动加载元数据,sidebar 显示真实数据
- 用户发消息 → 写入磁盘
- Agent 事件到达 → 写入磁盘 + 更新元数据
- 端到端验证:发消息 → 关闭 app → 重新打开 → 消息还在
Slice 2:新建会话 + 切换
- 点击 New Chat → 创建内存态空 session → 切换过去
- 点击 sidebar 项 → 切换 active session → 懒加载消息
- 端到端验证:创建多个会话 → 来回切换 → 各自消息独立
Slice 3:会话操作菜单
- Hover 时显示
··· - 点击弹出菜单:Rename / Archive / Delete
- 端到端验证:三个操作各自正常工作
Slice 4:未读标记
- SessionItem 显示蓝色圆点
- 点击进入后自动消除
- 端到端验证:创建未读 session → 蓝色圆点出现 → 点击后消失
结论:我们最后选A,全局选择。
讨论过的方案:
- A: 全局选择 — 勾选对所有会话生效
- B: 按会话选择 — 每个 session 独立技能组合
- C: 全局默认 + 会话可覆盖
我们最后选 A。
- YAGNI — 先跑通最小链路
- 目标用户是非技术人群,全局开关最直观
- 后续需要再加会话级覆盖
结论:我们最后选A,拼接到 prompt。
讨论过的方案:
- A: 拼接到 prompt —
skillContent + "\n\n" + userMessage - B: 作为 system prompt 参数注入
我们最后选 A。
- 当前 SDK 的
query()只暴露prompt参数,没有 system prompt 入口 - craft-agent 也是类似做法
- 最低成本,不需要改 SDK 调用方式
结论:我们最后选A,全部默认选中。
讨论过的方案:
- A: 全部默认选中,不想用的手动关
- B: 全部默认不选,需手动勾选
- C: 根据 SKILL.md 的 enabled 字段决定
我们最后选 A。
- 目标用户是非技术人群,装了技能就应该直接能用
- 减少操作步骤
结论:我们最后选A,存到 config 文件(复用现有 saveConfig/loadConfig)。
讨论过的方案:
- A: 存到 config 文件(复用现有 saveConfig/loadConfig)
- B: 存到 localStorage
- C: 存到 skill 目录的 meta 文件
我们最后选 A。
- 复用现有的 config 持久化机制,零额外基础设施
- 因为默认全选,只需要记录“被关掉的技能”即可
- localStorage 清缓存就没了,不够可靠
结论:我们最后选B,启动时加载 + SkillsPopup 打开时刷新。
讨论过的方案:
- A: 仅 app 启动时加载
- B: 启动时加载 + SkillsPopup 打开时刷新
- C: 文件系统监听(fs.watch)实时检测
我们最后选 B。
- 成本很低(打开弹窗时多一次 IPC 调用)
- 体验明显好于仅启动加载(用户新装技能不用重启)
- 比 fs.watch 简单得多,YAGNI
结论:不做任何 skill 内容加载/注入,启动时加载所有 skill 完整内容到 renderer。
原实现(Q2 的结论):
- 启动时加载所有 skill 完整内容到 renderer
- 每次发消息时,把所有启用 skill 的 content 拼到 prompt 前面
- 问题:每条消息都带全部 skill 内容,浪费 token;且不符合官方设计
官方 Claude Code Skills 的三阶段机制:
- 发现:启动时只加载 name + description 元数据
- 激活:用户请求匹配 skill 描述时,Claude 自己决定调用 Skill tool
- 执行:此时才加载 SKILL.md 完整内容
craft-agent 的做法:
- 把目录作为 plugin 传给 SDK:
plugins: [{ type: 'local', path: workspaceRoot }] - SDK 内置 Skill tool,自动处理发现→激活→执行
- craft-agent 自己不做任何 skill 内容加载/注入
修正方案:
- 在 DeskhandAgent 的 SDK options 中添加 插件目录配置,传入 Claude 本地数据目录 和 Deskhand 本地目录
- 如果用户选了 workspace,也传入 workspace 路径
- 删除 InputToolbar 中的 prompt 拼接逻辑
- 删除 技能开关状态 和相关持久化(SDK 管理激活,不需要手动开关)
- 保留 技能加载 IPC 和 技能列表状态 用于 UI 展示(SkillsPopup 显示可用技能列表)
- 符合官方设计,按需加载节省 token
- 复用 SDK 内置能力,不造轮子
- Skill 调用会作为 tool_start/tool_result 事件出现在消息流中
结论:我们最后选A,在 activity tree 中展示(和 Read/Write/Bash 等工具一样)。
讨论过的方案:
- A: 在 activity tree 中展示(和 Read/Write/Bash 等工具一样)
- B: 单独的 skill 激活通知
- C: 在消息气泡中标注
我们最后选 A。
- SDK 的 Skill tool 调用会产生
tool_start和tool_result事件 - 现有的 ToolActivityRow 已经支持渲染任意工具调用
- 只需添加 Skill 的图标和描述提取逻辑
- 用户体验一致:所有 agent 行为都在同一个 activity tree 中可见
实现:
- ToolActivityRow 的 ToolIcon 添加
Skillcase(扳手图标) - getToolDescription 添加
Skillcase(显示 skill 名称)
结论:Deskhand 会自动扫描 Claude 默认 skills 目录,实现现有 skills 的即插即用兼容。
发现:切换到 SDK plugin 机制(Q6)后,Deskhand 扫描 Claude 默认 skills 目录 目录,这和 Claude Code CLI 使用的是同一套位置。
效果:
- 用户在 Claude Code 中已配置的 skills,打开 Deskhand 后直接可用
- 无需任何迁移、导入或重新配置
- SkillsPopup 中会自动列出这些 skills
- Claude 在对话中会按需激活它们(三阶段机制不变)
为什么值得记录:
- 对从 Claude Code 迁移过来的用户,体验是无缝的
- 降低了产品的上手门槛——"装了就能用"
- 这不是刻意设计的功能,而是选择正确架构(复用 SDK plugin 系统)的自然结果
结论:我们最后选A,对话开场提示(嵌入聊天区顶部)。
状态:brainstorming 进行中,尚未结论。
问题背景
当前用户获取 skill 的方式:手动找到 skill → 下载/clone → 放到正确目录。对非技术用户来说门槛太高。类比:如果 Claude Code 要求用户自己去找依赖、下载、判断要不要用,体验会很差。
候选方案(6 个)
1. 内置精选 Skills(开箱即用)
- 打包精选 SKILL.md 进 app,首次启动复制到 Deskhand 专属 skills 目录
- 优点:零门槛,离线可用,质量可控
- 缺点:种类有限,更新绑定发版
- 工程量:低
2. Agent 自主获取(用户无感) ❌ 已排除
- Agent 自动判断+下载+安装,用户不感知
- 排除原因:安全风险;skill 元数据膨胀会降低 AI 判断质量(选择困难)
3. Agent 搜索+展示+用户确认(find-skills 模式)
- 内置 find-skills skill,agent 搜索 skills.sh 生态,展示结果,用户确认后安装
- 参考:https://github.com/vercel-labs/skills/blob/main/skills/find-skills/
- 优点:复用现有生态,用户有选择权,agent 处理技术细节
- 缺点:依赖 skills.sh 生态质量,需处理 Electron 中的 CLI 调用
- 待讨论:搜索结果质量参差不齐时,agent 推荐质量也会受影响
4. 应用内 Skill 商店 ❌ 已排除
- 类似 VS Code Extension Marketplace
- 排除原因:本质是软件时代应用商店思路,把"去 GitHub 找"换成"去商店找",用户认知负担没变。不是最佳解。
5. Skill Recommend(行为分析推荐)
- 分析用户历史对话 → 匹配 skill 目录 → 在对话外 UI 推荐
- 优点:主动式,个性化
- 缺点:推荐不准会烦人,需要 skill 目录匹配,需要对话外 UI
- 工程量:中-高
6. Skill Auto-Create(自动生成)
- 检测用户反复做的事 → 自动生成 SKILL.md → 下次自动激活
- 优点:真正个性化,创造市场上不存在的 skill,最有差异化
- 缺点:生成质量不确定,需要模式检测,隐私顾虑
- 工程量:高
早期倾向(已修正,保留作为思路记录)
- ~~1(内置精选)作为地基,几乎确定要做~~
- ~~3(find-skills)作为桥梁,但对外部生态质量有顾虑~~
- ~~5 vs 6 需要进一步讨论——推荐别人的 skill vs 自己生成 skill~~
- ~~核心张力:从外部获取 vs 系统自己生长,哪个更适合非技术用户?~~
修正:之前错误地把 1(内置)归类为"服务知道 skill 的用户"。实际上内置 skills 恰恰服务不知道 skill 的用户——默认配置让 AI 遇到匹配场景自然使用,用户无需知情。
战略分层(关键洞察)
深度用户(知道 skill,红海):
- 他们自己会获取 skills,也知道主动让 AI 去 find
- 用运营手段解决(文档、推荐优质源、社区分享),不需要产品投入
浅层用户(不知道 skill,蓝海):
- 他们只会说"帮我做 XX",不知道也不关心 skill 的存在
- 可以享受默认预设的通用 skill(哪怕自己不知情)
- 这批用户是产品要服务的核心对象
结论
1(内置精选)✅ 确定做
- 服务对象:浅层用户
- 实现:增加优质通用 skills 作为默认配置
- 用户无需知道 skill 的存在,AI 遇到匹配场景自然使用
3(find-skills)✅ 确定做
- 服务对象:深度用户
- 实现:将 find-skills 作为默认配置的 skill 之一
- 深度用户主动说"帮我找个做 XX 的 skill"时,agent 自动搜索+安装
5+6(Skill Insight Agent)✅ 设计完成
5(推荐已有 skill)和 6(自动生成 skill)合并为同一个 agent——目标相同(洞察用户行为、提升效率),只是手段不同。agent 自己判断走哪条路径。
#### 为什么合并?
最初把 5 和 6 当作两个独立功能讨论。但深入思考后发现:两者的输入相同(用户行为模式),触发条件相同(发现有价值的模式),呈现方式相同(需要告知用户)。区别只在输出——推荐现成的还是生成新的。这是同一个 agent 的两个分支,不是两个系统。
#### 通知形式:为什么是新 session 而不是 UI 组件?
讨论过的方案:
- A: 对话开场提示(嵌入聊天区顶部)
- B: 侧边栏洞察卡片(独立 UI 区域)
- C: 周期性摘要消息(特殊系统消息)
- D: 新 session + 未读提醒
选择 D,关键思考:A 和 B 都是传统 UI 做法,不够 AI Native。问题在于——如果用户看到推荐后想说"这个方向对但还需要微调",静态卡片或提示做不到。新 session 的本质是把"推荐"变成一次对话,用户可以直接回复、讨论、修改。类似微信/Instagram 的系统消息机制,用户看到未读提醒,打开就是一个可以互动的对话。而且业务逻辑上,对于我们而言只是创建了一个新的session并且给他安排了任务,额外的开发成本小,用户体验好。
#### 触发条件:为什么是固定周期?
讨论过的方案:
- A: 固定周期(每周/每 N 次对话)+ 质量门槛
- B: 阈值触发(某行为出现 X 次以上)
- C: 用户可配置周期
我们最后选 A。B 听起来更智能,但"什么算一个模式"本身很难定义(同样指令 3 次?相似意图 5 次?),实现复杂度高。A 更简单——定期跑分析,加质量门槛:没有有价值的发现就不创建 session。用户感知上像 B("系统有洞察时才找我"),实现上是 A。C 是优化项,不影响核心体验,后续再加。
#### 搜索精准度:宁缺毋滥
agent 搜索现成 skill 时必须高精准度匹配。反例:用户只是写了一份述职报告,就把任意"报告模板" skill 推荐过去——这种宽泛匹配无意义,反而损害信任。agent 需要真正理解用户的行为模式,而不是做关键词匹配。
#### 创建流程:为什么是"先描述再创建"?
讨论过的方案:
- A: 展示 SKILL.md 草稿,等用户确认
- B: 直接创建并激活,事后告知
- C: 用自然语言描述打算创建的 skill,用户确认后再生成
我们最后选 C。A 的问题是 SKILL.md 对非技术用户是天书。B 是先斩后奏,缺乏信任感(和之前排除"静默生效"的理由一致)。C 最自然——agent 用人话说"我打算做什么、它会怎么工作",用户回复"好"或"好但再加个 XX",全程不接触技术细节。
示例对话:
agent:"我注意到你每周都会整理项目进展,格式都差不多——标题、本周亮点、遇到的问题、下周计划。我可以帮你创建一个专门的技能,以后你只需要说'帮我写周报'就行。要我创建吗?"
用户:"好,但是再加一个'需要协助的事项'"
agent:"好的,已创建。下次你说'写周报'我就会用这个模板。"
#### 存储位置:为什么是 Deskhand 专属目录?
讨论过的方案:
- A: Deskhand 专属 skills 目录 — Deskhand 专属
- B: Claude 默认 skills 目录 — 和 Claude Code 共享
- C: 让用户选
我们最后选 A。自动生成的 skill 来自用户在 Deskhand 里的行为模式,放到 Claude 默认 skills 目录 会污染 Claude Code 环境——一个"写周报" skill 在 CLI 里未必有意义。方向是单向兼容的:Claude Code 的 skill → Deskhand 能用(Q8),Deskhand 生成的 skill → 不反向污染 Claude Code。
完整流程
定期后台分析用户对话历史 → 发现有价值的行为模式?→ 否:不出声 → 是:创建新 session + 未读提醒 → agent 展示行为模式分析报告 + 建议 → 搜索是否有匹配的现成 skill(高精准度)→ 有:推荐 + 理由,用户确认后安装 → 没有:用自然语言描述打算创建的 skill → 用户在对话中确认/修改/拒绝 → 确认后生成 skill,存到 Deskhand 专属 skills 目录
结论:可借鉴 /insights 的多阶段 pipeline:facet 提取、缓存、并行分析和汇总。
发现
Claude Code 内置了 /insights 命令,功能与我们的 Skill Insight Agent 高度相关。通过探查其实现,梳理出完整流程:
- 读取 Claude 本地数据目录 下的所有历史会话数据
- 对每个 session 提取元数据(工具调用次数、token 用量、语言分布、git 操作、响应时间等)——纯本地计算
- 用 Claude 模型对每个 session 做 facet extraction——让模型判断用户目标、满意度、摩擦点、session 类型等,输出结构化 JSON
- 汇总所有 session 的统计数据
- 把汇总数据分发给多个并行的 prompt(project_areas、interaction_style、what_works、friction_analysis、suggestions、on_the_horizon、fun_ending 等维度),每个维度用一个独立的 Claude API 调用生成分析
- 最后跑一个 at_a_glance 汇总 prompt,把前面所有维度的结果综合成简短摘要
- 所有结果拼装成 HTML 报告,写到本地可查看的分析报告文件
本质上是一个多阶段 AI pipeline:本地统计 → 逐 session AI 提取(带缓存)→ 汇总 → 多维度并行分析 → 最终摘要。
我们可以借鉴什么?
facet 提取 + 缓存机制:先对每个 session 做结构化提取,缓存结果,再做跨 session 的模式分析。比直接把所有对话丢给 AI 分析高效得多。
多维度并行分析:不同分析维度行跑,最后汇总用独立的 prompt 并。这个架构可以直接复用。
我们比它更进一步的地方
| 维度 | Claude Code /insights | Deskhand Skill Insight Agent |
|---|---|---|
| 触发方式 | 手动(用户输入 /insights) | 定期自动触发 |
| 输出形式 | 静态 HTML 报告 | 可交互的 session 对话 |
| 后续动作 | 建议用户自己改 CLAUDE.md | 直接帮用户创建/安装 skill |
| 目标用户 | 技术用户(需理解 CLAUDE.md) | 非技术用户(自然语言对话) |
关键补充:静态报告作为前置步骤
讨论后决定:Skill Insight Agent 的 session 应该先输出一份工作分析报告,再进行 skill 推荐/创建。
- 报告是"诊断",skill 推荐是"处方"——先让用户看到"我了解你的工作模式",建立信任和上下文
- 用户看到分析后可能自己就有想法:"对,这个事情我确实经常做,能不能帮我优化?"
- 报告本身就有价值,即使用户不需要新 skill
更新后的完整流程
定期后台分析用户对话历史 → 阶段 1:facet 提取(逐 session,带缓存)→ 阶段 2:跨 session 模式分析(多维度并行)→ 发现有价值的行为模式?→ 否:不出声 → 是:创建新 session + 未读提醒 → 先展示工作分析报告(你最近在做什么、怎么做的、哪里有摩擦)→ 基于分析,提出 skill 建议:→ 搜索匹配的现成 skill(高精准度)→ 推荐 → 没有匹配的 → 用自然语言描述打算创建的 skill → 用户在对话中确认/修改/拒绝 → 确认后生成 skill,存到 Deskhand 专属 skills 目录
结论:不做任何配置,对话中 AI 自然使用内置 skill。
已完成
- Q6: SDK plugin 集成 ✅
- Q7: Skill tool 在 activity tree 展示 ✅
- Q8: Claude Code skills 自动兼容 ✅
Slice A:内置精选 Skills
- 来源:Q9 方案 1
- 做什么:打包几个通用 SKILL.md,首次启动复制到 Deskhand 专属 skills 目录
- 端到端验证:用户装完 app,不做任何配置,对话中 AI 自然使用内置 skill
- 依赖:无
- 复杂度:低
Slice B:find-skills 作为默认 skill
- 来源:Q9 方案 3
- 做什么:将 find-skills 的 SKILL.md 作为内置 skill 之一(Slice A 的一部分)
- 端到端验证:用户说"帮我找个做 XX 的 skill"→ agent 搜索 → 展示结果 → 安装
- 依赖:Slice A(复用内置 skill 的分发机制)
- 复杂度:低
Slice C:Session 未读基础设施
- 来源:Q9 5+6 的前置 + 会话管理系统
- 做什么:实现
hasUnread持久化 +sessions:update-metaIPC + 侧边栏未读标记 UI - 端到端验证:后台创建一个 session → 侧边栏出现未读标记 → 点击后标记消失
- 依赖:无(但与会话管理系统有交叉)
- 复杂度:中
Slice D:手动触发 Insight(先手动,后自动)
- 来源:Q9 5+6 + Q10
- 做什么:手动触发版本,验证分析质量。实现 facet 提取 + 缓存 + 多维度并行分析 + 在新 session 里展示工作分析报告
- 端到端验证:用户触发分析 → 新 session 出现(带未读标记)→ 里面有工作分析报告 → 可以对话互动
- 依赖:Slice C
- 为什么先手动:先验证分析质量再自动化,避免自动推送低质量内容损害信任
- 复杂度:高
Slice E:Skill 推荐 + 创建
- 来源:Q9 5+6
- 做什么:在 Slice D 的报告基础上,增加 skill 搜索推荐(高精准度)和自动创建能力(先描述再创建)
- 端到端验证:报告后 agent 建议 skill → 用户确认/修改 → skill 安装/创建到 Deskhand 专属 skills 目录 → 下次对话自动可用
- 依赖:Slice D + Slice B(搜索用 find-skills)
- 复杂度:中
Slice F:定期自动触发
- 来源:Q9 5+6 触发条件
- 做什么:把 Slice D 从手动改为定期自动 + 质量门槛(没有有价值的发现就不出声)
- 端到端验证:用户什么都不做 → 一段时间后收到未读提醒 → 打开是分析报告 + 建议
- 依赖: Slice D + Slice E
- 复杂度:中
依赖关系
Slice A(内置 skills)──→ Slice B(find-skills)──→ Slice E(推荐+创建)
Slice C(未读基础设施)──→ Slice D(手动 insight)──→ Slice E ──→ Slice F(自动触发)
A/B 和 C 可以并行开发,互不依赖。
结论:首批内置 skills 选择 playground、find-skills、skill-creator、frontend-design。
选择过程
分析了 skills.sh 榜单 Top 10(总安装量 39K+),发现榜单用户群体是开发者,Top 10 几乎全是开发向(React、Remotion、Vercel 等)。需要结合 Deskhand 非技术用户的需求筛选。
排除的:
- vercel-react-best-practices、remotion-best-practices、vercel-composition-patterns、vercel-react-native-skills — 框架/平台专用,非技术用户用不上
- agent-browser、browser-use — 需要浏览器自动化基础设施,Deskhand 暂不支持
- web-design-guidelines — 和 frontend-design 重叠度高
最终选择(4 个)
核心原则:一切尽可能 AI Native,不需要 UI 操作,用户直接跟 AI 讲就行。
1. playground(来源:Anthropic 官方插件)
- 解决的问题:用户不知道如何表达需求 → AI 用可交互的可视化工具帮你表达
- 生成自包含 HTML 文件,有控件、实时预览、prompt 输出
- 内置多种模板(设计、数据、概念图、文档评审等)
- 来源:
anthropics/skills官方插件系统
2. find-skills(来源:vercel-labs/skills)
- 解决的问题:用户不需要学习如何寻找 skill → 直接跟 AI 说"帮我找个做 XX 的"
- 搜索 skills.sh 生态,展示结果,用户确认后安装
- 已在 Q9 中确定作为 Slice B
- 来源:https://github.com/vercel-labs/skills/blob/main/skills/find-skills/
3. skill-creator(来源:Anthropic 官方)
- 解决的问题:用户不需要学习如何创建 skill、看晦涩文档 → 直接跟 AI 描述需求
- 提供完整的 skill 创建指南,包括初始化、编辑、打包流程
- 和 Slice E(Skill Insight Agent 的自动创建能力)天然对齐
- 来源:https://github.com/anthropics/skills/blob/main/skills/skill-creator/
4. frontend-design(来源:Anthropic 官方插件)
- 解决的问题:非技术用户做网页的刚需(落地页、简历页、小商家网站等)
- 强调避免"AI 味"设计,生成有设计感的前端代码
- 来源:
anthropics/skills官方插件系统
设计哲学
这 4 个 skill 构成了一个完整的"AI Native 能力层":
用户不会表达 ──→ playground 帮你可视化表达
用户不会找 skill ──→ find-skills 帮你搜索安装
用户不会建 skill ──→ skill-creator 帮你创建
用户要做网页 ──→ frontend-design 帮你设计
用户不需要知道"skill"这个概念的存在,只需要自然地和 AI 对话,内置 skill 会在合适的时机被 SDK 自动激活。
结论:核心问题:如果把所有 session 的消息历史灌给一个 Agent,上下文肯定炸。
核心问题:如果把所有 session 的消息历史灌给一个 Agent,上下文肯定炸。
解决方案:
Stage 1 (Map): 逐 session 独立提取 facet
→ 每个 session 单独一次 AI 调用,输出结构化 JSON(约 200-300 tokens)
→ 结果缓存,已分析过的不重复跑
Stage 2 (Reduce): 只把提取出的 facet 汇总
→ 50 个 facet × ~300 tokens ≈ 1.5 万 tokens,一个上下文窗口轻松装下
→ 找出跨 session 的重复模式,而不是每个 session 单独推荐
关键洞察:50 个 session 不是推荐 50 个 skill。只有重复出现的模式才有价值——"用户过去一个月写了 8 次周报,结构都一样"才值得变成 skill,偶尔做一次的事不值得。
结论:我们最后选A,一次调用。
讨论过的方案:
- A: 一次调用 — 把单个 session 的完整消息喂给 AI,输出结构化 facet JSON(用户目标、任务类型、重复模式、摩擦点)。大部分 session 几十轮对话,token 量可控。极少数超长 session 做智能截断。
- B: 两次调用 — 先用便宜模型压缩摘要,再用主模型从摘要中提取 facet。省 token 但多一轮调用,且压缩过程可能丢失对 skill 发现有价值的细节。
我们最后选 A。
- 大部分 session 一次调用就够
- 信息无损,不会因为压缩丢失 skill 发现线索
- 超长 session 是边缘 case,截断处理即可
注意:facet 提取的目标是"发现用户反复做的事,推荐或创建 skill",不是做使用统计。消息数量、工具调用次数、时长等指标对 skill 发现毫无帮助,需要的是语义理解。
结论:我们最后选A,单一 prompt。
讨论过的方案:
- A: 单一 prompt — 一个 prompt 搞定,让 AI 自己判断哪些模式值得提取
- B: 多维度并行 — 像 Claude Code /insights 那样拆成几个独立 prompt 并行跑
我们最后选 A。
- 我们的目标很聚焦(找 skill 机会),不像 Claude Code 的 /insights 要做全面画像
- 一个精心设计的 prompt 就够了,不需要多维度并行的复杂度
结论:手动触发只是开发阶段验证分析质量用的,不上线。
手动触发只是开发阶段验证分析质量用的,不上线。直接用 IPC 调用或 devtools console 触发即可。Slice F(自动触发)上线后这个入口就不重要了。
结论:我们最后选A,每个 session 目录下存 .facet.json。
讨论过的方案:
- A: 每个 session 目录下存
.facet.json— 和 session 数据放一起,session 删了缓存自然跟着删 - B: 统一存到
~/.deskhand/cache/facets/— 集中管理,和 session 数据解耦
我们最后选 A。
- 缓存跟着 session 走,生命周期一致,零维护成本
结论:我们最后选A,Haiku。
讨论过的方案:
- A: Haiku — 最便宜最快,适合批量处理。facet 提取是结构化任务,Haiku 够用
- B: Sonnet — 贵一点但理解力更强
我们最后选 A。
- facet 提取是相对简单的结构化提取任务,Haiku 性价比最高
- Stage 2 的跨 session 模式分析用 Sonnet(需要更强的推理能力,但只有一次调用)
结论:围绕「InsightSession的报」,最终方案按文中取舍执行。
报告生成:
- A: 预生成 — Stage 2 的输出直接作为新 session 的第一条 assistant 消息存入。用户打开就能看到,不需要再调 AI。
- B: 实时生成 — 创建空 session,让 agent 实时生成报告。多一次 AI 调用且内容本质一样。
我们最后选 A。分析结果已经有了,没必要再让 AI 重新说一遍。
用户回复处理:
- A: 不做特殊处理 — 正常 agent 对话,报告作为历史消息自然提供上下文
- B: 注入额外 system context — 给 agent 额外注入角色提示
我们最后选 A。Slice D 的目标是验证分析质量,不是做完整的互动体验。角色注入是 Slice E/F 的优化项。
结论:我们最后选A,Pipeline 阶段自动搜索。
讨论过的方案:
- A: Pipeline 阶段自动搜索 — Stage 2 之后加 Stage 3,对每个 pattern 跑
skills find命令,搜索结果写进报告 - B: 报告里只给建议描述,用户回复后对话式搜索
- C: 报告里给推荐搜索关键词,不实际搜索
我们最后选 A。
- 既然已经知道用户的模式,直接搜好呈现结果,减少用户操作步骤
- 非技术用户不应该需要自己想搜索词
结论:我们最后选B,报告里给出创建建议(含详细描述)。
讨论过的方案:
- A: 自动创建 skill — 全自动但可能创建出低质量 skill
- B: 报告里给出创建建议(含详细描述),用户确认后 agent 创建
- C: 只报告,不主动创建
我们最后选 B。
- 自动创建风险太高(质量不可控)
- 完全不管又浪费了分析结果
- 给出建议 + 用户确认是最好的平衡
关键 UX 要求:不能只给一个 skill 名称,必须用自然语言描述这个 skill 会做什么、怎么帮到用户。用户需要理解价值才能做决定。
结论:需要说"好"或点按钮,agent 在后台执行。
核心原则:
- 不暴露 "skill"、"npx"、"命令" 等技术术语
- 用"工具""偏好""帮你记住"这种自然语言
- 用户只需要说"好"或点按钮,agent 在后台执行
报告示例:
## 模式 1:周报生成(3次会话)
你经常让我帮你写周报,而且格式都一样:标题、亮点、问题、下周计划。
✅ 有现成的方案
我找到了一个专门做周报的工具,装上之后你只需要告诉我这周做了什么,
我就自动按你习惯的格式生成。要不要我帮你装上?
💡 可以帮你记住偏好(没有现成方案时)
我可以帮你把这些偏好记下来:深色主题 + 响应式布局 + hero + pricing。
以后你只需要说"帮我做个落地页",我就自动按这些偏好来。
结论:我们最后选A,消息内嵌按钮。
讨论过的方案:
- A: 消息内嵌按钮 — 在 assistant 消息末尾渲染按钮,类似权限确认 UI
- B: 独立卡片组件 — 特殊的推荐卡片,视觉上和普通消息区分
- C: 底部快捷回复 — 输入框上方显示预设回复选项
我们最后选 A。
- 复用权限确认的 UI 模式,开发成本最低
- 用户已经熟悉这个交互模式
- 一次只推荐一个,不需要复杂的卡片布局
额外决策:一次只推荐一个(最高频的模式),不要信息轰炸。
结论:我们最后选B,点击按钮等于自动发送预设消息。
讨论过的方案:
- A: 主进程直接执行 系统命令执行能力 — 简单但绕过 agent
- B: 点击按钮等于自动发送预设消息,agent 用 Bash 工具执行安装
我们最后选 B。
- Agent 知道上下文,安装失败时能自动排查、换方案
- 非技术用户不可能自己 debug 安装报错
- 主进程直接跑出错了还得自己写错误处理逻辑,不如让 agent 兜底
结论:我们最后选A,只讲一个 pattern。
不需要 skill-creator(那是和用户协作创建用的),也不需要 init/package 流程。
一个正规 skill 的最小结构:
skill-name/
└── SKILL.md # frontmatter (name + description) + 指令正文
scripts/references/assets 都是可选的。对于"记住用户偏好"这类简单 skill,一个 SKILL.md 就够了。
Agent 从 insight 分析中已有足够信息生成完整的 SKILL.md(frontmatter + 触发描述 + 执行指令)。创建完成后下次对话自动可用(SDK 启动时扫描 Deskhand 专属 skills 目录)。
## Slice E 完整流程
⚠️ 以下为 v2 架构(Q26-Q28 修正后)。v1 的 Stage 2+3+硬编码方案已废弃。
架构修正背景(Q26-Q28)
v1 实现中发现的问题:
- Stage 2(Sonnet 分析)和 Stage 3(npx skills find)是割裂的,Stage 2 写报告时不知道搜索结果
- 推荐文案拼接逻辑 硬编码推荐文案,和 AI 生成的报告语气不一致
- 报告列出多个 pattern 但推荐只针对一个,用户不知道在解决哪个问题
- 推荐的 skill 只有名字没有描述,用户不敢装
Q26: 报告聚焦范围?
只聚焦最高频的一个 pattern。
- A: 只讲一个 pattern,从分析到推荐一条线贯穿 ✅
- B: 展示所有 pattern,推荐部分标注针对哪个
我们最后选 A。信息不分散,用户不会困惑"你到底在解决哪个问题"。
Q27: 分析 + 搜索 + 报告生成的架构?
合并为单个 Agent 调用,用 DeskhandAgent + find-skills skill。
v1 流程(已废弃):
Stage 2: Sonnet 分析 patterns → 生成 report(不知道搜索结果)
Stage 3: 代码调用 npx skills find(AI 不参与)
推荐文案拼接逻辑: 硬编码拼接推荐文案
v2 流程:
Stage 1: Haiku 逐 session 提取 facet(不变,批量预处理 + 缓存)
Stage 2: 创建 insight session → DeskhandAgent 接收 facets + prompt
→ Agent 自己分析模式(聚焦最高频的一个)
→ Agent 自然调用 find-skills 搜索匹配的 skill
→ Agent 评估搜索结果是否真的匹配
→ Agent 写出完整报告 + 推荐
→ 报告末尾输出结构化 JSON 标记推荐动作
→ 我们解析 JSON → 转为 UI action 按钮
- AI 全程有上下文,报告自然连贯
- 不需要 多段硬编码分析与推荐脚本
- find-skills 已是内置 skill,agent 直接可用
- 用户回复时 agent 已有完整分析记忆
Q28: Agent 中间过程是否对用户可见?
可见。Activity Tree 里展示搜索步骤。
- A: 只看到最终报告
- B: 能看到完整过程(Activity Tree 里有 find-skills 搜索步骤)✅
我们最后选 B。用户能看到"AI 搜索了什么、找到了什么",增加透明度和信任感。
## Slice F: 定期自动触发
Q29: 触发的时机用什么方式?
每次对话结束后检查。
讨论过的方案:
- A: 每次对话结束后检查 — agent.chat() 完成后,检查是否满足触发条件。自然嵌入现有流程,不需要额外定时器。
- B: 定时器轮询 — app 启动时 setInterval,每 24 小时检查一次。时间精确但用户不用 app 时白跑。
- C: App 启动时检查 — 每次打开 app 时检查。简单但用户一直不关 app 就永远不触发。
我们最后选 A。
- 不引入新的定时器机制,复用现有对话流程
- 触发频率自然跟用户活跃度挂钩——用得多分析得多,不用就不打扰
- 实现最简单,在 主进程消息处理流程 的 聊天请求结束节点 末尾加一个异步检查即可
Q30: 触发的阈值用什么?
对话次数,每 20 次新对话触发一次。
讨论过的方案:
- A: 对话次数 — 每完成 N 次对话后触发。跟用户实际使用频率挂钩。
- B: 时间间隔 — 距离上次 insight 超过 N 天。节奏固定但可能数据量不够。
- C: 两者结合 — 至少 N 次对话 AND 至少间隔 M 天。
我们最后选 A,并把 N 定为 20。
- 20 个对话里出现 3+ 次的模式比 10 个里出现 2 次的更可信
- 普通用户大概 1-2 周触发一次,重度用户 3-5 天一次,都不算打扰
- 加上质量门槛(没发现有价值的模式就不出声),实际推送频率更低
为什么不是 10:用户提出了 skill 膨胀问题——如果推送太频繁,长期积累下来 skill 越来越多(10 个、20 个),反而成为负担。20 次的间隔在"数据充足"和"不打扰"之间取得更好的平衡。
Q31: 如何避免重复推荐同一个模式?
只分析上次 insight 之后的新 session。
讨论过的方案:
- A: 只分析新对话 — 每次 insight 只看 上次 insight 时间戳 之后的新 session。同一个模式不会被反复发现。
- B: 记录已推荐的模式 — 维护"已推荐列表",下次分析时排除。更精确但多一层状态管理。
- C: 设上限 — 自动创建的 skill 最多 N 个,到上限后停止推送。
我们最后选 A。
- 如果上次发现了重复模式,用户接受了 → 再推荐没意义
- 如果用户不接受 → 再推荐也没意义
- 自然避免重复推荐,不需要额外的状态管理
- 如果老模式在新对话中又出现,说明用户没采纳上次建议,不该再烦他
Q32: "上次 insight"的状态存哪?
存到 config 文件,加 上次 insight 时间戳 字段。
讨论过的方案:
- A: 存到 config 文件 — 复用现有 Deskhand 配置文件,加一个上次 insight 时间戳字段。
- B: 存到独立状态文件,专门记录 insight 相关状态。
我们最后选 A。
- 复用现有持久化机制,零额外基础设施
- 只需要一个时间戳字段,不值得单独开文件
## 最小链路实现(v1 - 已废弃)
以下为 Q2 时的设计,已被 Q6 修正。保留作为决策记录。
端到端流程:
本地 skills 目录 → 主进程加载技能清单 → 前端展示并允许勾选
→ 用户发消息时附带所选技能信息 → agent 按配置执行
实现步骤(vertical slice):
- 加载通道 — 建立技能清单的主进程到前端通信
- 状态层 — 维护技能列表和启停状态,并从配置初始化
- 启动加载 — 应用启动时拉取技能并写入状态
- 弹窗接真数据 — 替换 mock,直接读取真实状态并支持勾选切换
- InputToolbar badge — 显示已启用技能数量(总数 - disabled 数)
- 消息注入 — 发消息时附带所选技能内容
- 持久化 — 技能开关变化时写回配置文件
结论:围绕「需要支持哪些权限模式」,最终方案按文中取舍执行。
讨论过的方案:
- A: ask + allow-all(两档)
- B: ask + allow-all + "Always Allow" 单工具记忆
- C: 三档(safe/ask/allow-all)
- D: 基于风险等级自动分类
最终选择只做 ask 模式。目标用户包含非技术人群,让他们去理解 "Always Allow Bash" 是什么意思有点强人所难。allow-all 风险太大,先不做。后面如果用户觉得弹窗太烦再加。
结论:需要弹窗确认的操作是 Bash、Edit 和 Write。
需要确认:
- Bash — 风险最高,能执行任何命令
- Edit — 修改现有文件,可能覆盖重要内容
- Write — 创建或覆盖文件,有不可逆后果
自动通过:
- Read — 只读,无副作用
- Glob / Grep — 只搜索,无副作用
- WebFetch / WebSearch — 只浏览,无副作用
结论:弹窗展示“原始命令 + 人话说明”,兼顾技术与非技术用户。
讨论过的方案:
- A: 只显示原始命令(如
bash: ls -la) - B: AI 生成人话描述(如「要查看文件列表」)
- C: 原始命令 + 简短描述
选 C,兼顾技术和非技术用户。示例:
bash: ls -la(查看文件列表)edit: 某个目标文件(编辑文件)write: report.docx(创建文件)
结论:不做 "Always Allow"、不做 "Accept All"。
不做 "Always Allow"、不做 "Accept All"。保持最简。模式切换放在其他地方(InputToolbar),弹窗本身只负责即时决策。
结论:用户拒绝后由 agent 接收拒绝结果,并自行降级或解释下一步。
SDK 的 PreToolUse hook 返回拒绝结果后,agent 收到"操作被用户拒绝"的反馈,自己决定下一步:换个方法完成任务,或者跟用户解释为什么需要这个操作。这是 Claude Code 等产品的标准做法。
## 迭代:加回 allow-all 模式
背景:实现完 ask-only 后,重新讨论认为技术用户需要快捷模式。
Q6: 是否需要 allow-all 模式?
需要。加回 allow-all 模式。
原始决策(Q1)是只做 ask 模式,理由是 YAGNI + 非技术用户安全。但实际考虑后,技术用户对速度的需求很强,每次 Edit/Write 都弹窗会严重打断心流。
修正后的模式:
- ask(默认):Bash/Edit/Write 全部弹窗确认
- allow-all:大部分操作自动放行,仅明确的删除命令仍弹窗
Q7: allow-all 模式下哪些操作仍需拦截?
只拦截明确的删除类 bash 命令。
检测方式:提取 bash 命令的第一个词(base command),匹配删除命令黑名单:rm、rmdir、unlink、shred。Edit/Write 在 allow-all 下全部放行,修改文件 ≠ 删除文件,风险可接受。
已知局限:find . -delete、git clean -f、xargs rm 等间接删除无法检测。V1 先覆盖 90% 场景,后续可加更多模式匹配。
Q8: 模式切换入口在哪?
放在 InputToolbar 的 toolbar 上。
类似现有的 model selector,用户可以随时切换。默认 ask 模式。
两种模式行为对比
| 操作 | ask 模式 | allow-all 模式 |
|---|---|---|
| Read/Glob/Grep | 自动通过 | 自动通过 |
| WebFetch/WebSearch | 自动通过 | 自动通过 |
| Edit | 弹窗确认 | 自动通过 |
| Write | 弹窗确认 | 自动通过 |
| Bash(普通命令) | 弹窗确认 | 自动通过 |
| Bash(删除命令) | 弹窗确认 | 弹窗确认 |
## 不做的事
- 单工具记忆(记住某个工具"总是允许")
- 三档以上的权限分级
- 基于风险等级自动分类
结论:用户打开 app 后,先选一个项目目录,然后在这个目录下跟 agent 对话。
用户打开 app 后,先选一个项目目录,然后在这个目录下跟 agent 对话。类似 Cursor 打开一个项目。
结论:用现有的 WorkspacePopup(工具栏的文件夹按钮)。
用现有的 WorkspacePopup(工具栏的文件夹按钮)。点击弹出目录列表 + "Select Directory..." 按钮,调用系统目录选择器。不新增额外 UI。
结论:围绕「未选目录时怎么办」,最终方案按文中取舍执行。
不阻断对话。agent 用 app 数据目录兜底(app.getPath('userData')/workspace/),不污染用户 home 目录。工具栏提示"未选择工作目录"。
结论:围绕「选择后要不要记住」,最终方案按文中取舍执行。
记住最近选择的目录。下次打开 app 自动恢复。存一个 lastWorkingDirectory 到 config。
结论:围绕「对话中能换目录吗」,最终方案按文中取舍执行。
不能。SDK 的 cwd 绑定到 session,设了就不能改(SDK 会按目录维度保存会话记录,改了目录就接不上历史)。换目录 = 开新 session。
结论:未选目录:显示提示文字(如 "No directory")。
- 未选目录:显示提示文字(如 "No directory")
- 已选目录:显示目录名(如 "Deskhand")
## 数据流
用户点击 "Select Directory..." 后,系统目录选择器返回路径。应用把这个路径写入当前配置并持久化,下次启动会自动恢复。
用户发送消息时,会把当前目录一并带给 agent,后续文件读写和命令执行都在这个目录范围内进行。
## 技术约束
- SDK
cwd绑定 session,不可中途更改 - SDK 会按目录维度保存会话记录
- resume 时必须用相同 cwd,否则找不到历史
结论:InputToolbar 重构按“资源层、配置层、交互层”三层重新分组。
按更加清晰的功能层面划分:
- 资源层:文件、剪贴板、技能、MCP — 用户视角:我要添加什么内容?
- 配置层:Workspace、Permission、Model — 用户视角:我要智能体做到什么水平?
- 交互层:playground, this or that — 用户视角:我要怎么和智能体交互?
结论:原布局:所有按钮都在输入框内底部工具栏,左图标右文字一字排开。
原布局:所有按钮都在输入框内底部工具栏,左图标右文字一字排开。
新布局:
- 输入框内:只放直接影响「这条消息」的操作 — 附件/interact + 发送
- 输入框外下方:放 agent 级配置 — Workspace、Permission、Model
结论:“+”按钮采用一级菜单 + 二级子面板结构。
一级菜单:
`
Attach
Upload files (直接打开文件选择器)
Clipboard history > (进入二级面板)
────────-
Skills
Skills > (进入二级面板)
────────-
MCP
MCP Connections > (进入二级面板)
`
点击带 > 的项进入二级子面板,子面板左上角有 < 返回按钮。
结论:改为 + 菜单的二级子面板,风格统一,更紧凑。
相比之前的独立大面板,改为 + 菜单的二级子面板,风格统一,更紧凑。缩略图更小,一行展示标题即可。
结论:显示已安装的 skills 列表,每个可切换开关,以及安装新 skill 的入口。
显示已安装的 skills 列表,每个可切换开关,以及安装新 skill 的入口。
结论:MCP 是外部服务连接(Figma、Linear、GitHub 等),通过 MCP 协议提供工具。
MCP 是外部服务连接(Figma、Linear、GitHub 等),通过 MCP 协议提供工具。与 Skills(内置插件系统)是不同的概念,UI 上分开展示。
结论:Pick a Style,This or That。
- Pick a Style
- This or That
结论:Workspace:工作目录选择,Permission:权限模式(Ask / Auto)。
- Workspace:工作目录选择
- Permission:权限模式(Ask / Auto)
- Model:模型选择
每个都有向上弹出的选择面板,和输入框内的弹窗风格一致。
结论:弹窗按触发按钮分组定位:输入区按钮上弹,配置区按钮上弹并对齐触发点。
- 输入框内按钮(+、/)→ 弹窗从输入框上方弹出,左对齐到按钮位置
- 配置栏按钮(Workspace、Permission)→ 弹窗从配置栏上方弹出,左对齐
- Model → 弹窗从配置栏上方弹出,右对齐
结论:围绕「InputToolbar弹窗组件」,最终方案按文中取舍执行。
组件清单:
- PopupContainer — 外层容器
- PopupHeader — 标题行,统一为:可选返回按钮 + 标题(13px semibold)
- PopupSectionLabel — 分组标签(10px uppercase)
- PopupItem — 列表项(icon 16px + label + 可选 hint/箭头)
- PopupDivider — 分隔线
结论:优先修复字号、标题风格、搜索框冗余与组件间距不一致问题。
- 配置栏文字太小 — 现状 11px,改为 13px
- Workspace 弹窗标题风格不统一 — 改为统一的 13px 标题行
- Interact 弹窗标题风格不统一 — 改为统一的 13px 标题行
- Model 弹窗不需要搜索框 — 去掉搜索框,直接列表
- Permission 没有弹窗 — 改为弹窗选择,每项带简短说明
- 各弹窗间距/图标不统一 — 全部使用共享组件
结论:好的 UI 层次感是“通过空间层叠表达前后关系”,而不是靠边框分割。
放在上面,而不是画在上面
好的 UI 让你觉得屏幕不是一张平面,而是有前后关系的——背景在后面,卡片浮在上面,按钮又浮在卡片上面。差的 UI 让你觉得所有东西都被压扁在同一层玻璃上。
判断标准:闭上眼想象界面,如果你能说出"这个在那个上面",就是好的;如果你只能说"这个在那个旁边",就是扁的。
结论:边框用于二维分割,阴影用于三维层叠,本项目优先用阴影表达层级。
边框是"画"出来的——它在说"这里有一条线把两个区域分开"。阴影是"摆"出来的——它在说"这个东西浮在那个东西上面"。
- 边框暗示的是二维的分割(左右、上下)
- 阴影暗示的是三维的层叠(前后、高低)
当你用边框分隔两个区域时,它们在同一个平面上。当你用阴影时,一个在另一个上面。
实际操作原则:
- 同一个面内部不需要分割线(侧边栏底部栏不需要 border-top)
- 不同层级之间用阴影而非边框(侧边栏和聊天区之间)
- 边框只在极少数场景使用:表单输入框的边界、表格的网格线
结论:侧边栏不和谐的根因是“平面分区信号过强、卡片层级信号不足”。
原始状态:纯白背景(#ffffff)+ 右侧 1px border + 满高贴边 + 直角。
问题不是某一个属性不对,而是这些属性组合在一起传达了一个错误的信号——"我是界面的一个区域",而不是"我是放在这里的一个东西"。
具体来说:
- 纯白 vs 青色背景:整个界面是温暖的浅青色(
#f0f7f7),侧边栏是冷的纯白。色温不统一,像是从另一个设计系统里贴过来的。 - 满高贴边:真实世界里没有东西是严丝合缝贴在墙角的。贴边 = 画上去的;有间距 = 放上去的。
- 直角:直角暗示"我是建筑结构的一部分",圆角暗示"我是一个独立的物体"。
- border-r:一条线在说"左边是侧边栏,右边是聊天区"——这是在描述平面布局,不是在构建空间关系。
结论:需要线来"声明"自己的边界,阴影已经说明了一切。
改动:
`
之前:bg-white + border-r + 满高 + 直角
之后:bg-white + shadow + margin(8px) + rounded-2xl(16px)
`
这些改动组合在一起传达的信号是:侧边栏是一张白色卡片,被放在了青色桌面上。
margin→ 它和窗口边缘之间有空气,说明它是独立的rounded-2xl→ 它有自己的轮廓,是一个物体shadow→ 它浮在背景上方,有高度- 去掉
border-r→ 它不需要线来"声明"自己的边界,阴影已经说明了一切
结论:需要"浮起来"的是侧边栏和输入框,它们才是放在地面上的积木。
原始状态:标题栏用略浅的白 + border-b 和聊天区分开。
但标题栏和聊天区本质上是同一个"地面"——它们都是背景层。真正需要"浮起来"的是侧边栏和输入框,它们才是放在地面上的积木。
改动:标题栏改为和聊天区同色,去掉 border 和 shadow。现在标题栏和聊天区融为一体,形成一个连续的青色画布。
延伸原则:不是所有东西都需要层级。层级是稀缺资源,只给真正需要"突出"的元素。如果所有东西都浮起来,就等于没有东西浮起来。
结论:不要。侧边栏底部有一个操作栏,原来用 border-top 和会话列表分开。
不要。
侧边栏底部有一个操作栏,原来用 border-top 和会话列表分开。但侧边栏是一张卡片,一张卡片就是一个面。在一个面的内部画线,等于在说"这其实是两个东西"——那它就不是一张卡片了。
去掉 border-top 之后,侧边栏从上到下是一个完整的白色表面,底部操作栏自然地"长"在卡片底部,而不是被一条线"隔"在下面。
推广:任何时候你想在一个组件内部加分割线,先问自己——这真的是一个组件,还是两个?如果是一个,就不要画线。
结论:原始状态:border + shadow。
原始状态:border + shadow。
border 和 shadow 同时存在时,它们在传达矛盾的信号:
- border 说:"我的边界在这里"(二维)
- shadow 说:"我浮在上面"(三维)
去掉 border,只留 shadow(并加强),信号变得统一:这是一个浮在聊天内容上方的白色输入卡片。
结论:界面 z 轴分为画布层、卡片层、选中态层和弹窗层。
经过这轮优化,界面的 z 轴从低到高是:
- Layer 0 青色画布(bg-primary)← 标题栏 + 聊天区 + 空白区域
- Layer 1 白色卡片(bg-sidebar)← 侧边栏(圆角 + 阴影 + 间距)
- Layer 1 白色卡片(bg-white)← 输入框(圆角 + 阴影)
- Layer 2 选中态 session item ← 浮在侧边栏卡片上的小块
- Layer 3 弹窗 / 下拉菜单 ← 最高层,带 shadow-popup
每一层都通过阴影暗示自己的高度,而不是通过边框声明自己的边界。
结论:后续设计应先定层级再定样式,优先用留白和阴影而非边框。
几条可复用的原则:
- 新增 UI 元素时先问:它在哪一层? 确定 z 轴位置后,用对应强度的阴影。不要默认加 border。
- 同一层的元素共享背景色,不需要边框分隔。用间距和留白来区分。
- 圆角 + 间距 = 独立物体。如果一个元素需要看起来是"放在那里的",给它圆角和四周的呼吸空间。
- 阴影强度对应层级高度。Layer 1 用
0.06,Layer 2 用0.08,弹窗用0.12。保持一致。 - border 是最后手段,只用在表单控件(input/select)和数据表格上。