从WorkBuddy到WorkDSH:透明AI编程工作台的开源实践
发布时间:2026/10/2 19:51:46来源:尧图网络
做这件事的起因是上个月我在一个有几万行代码的旧项目里做重构。AI 工作台帮我把十几个文件改了一遍自检时看起来“都改完了”结果构建脚本里两个硬编码路径被悄悄覆盖掉部署到测试环境才发现。站在终端前那一刻我就想明白了我缺的不是一个更聪明的对话入口而是一个能看清每一步执行过程的开源工作台。所以就有了 WorkDSH。名字里的 DSH 其实是 developer shell 的缩写核心目标很简单——把 WorkBuddy 这类 AI 编码工作台带给我的体验用一套完全透明、本地优先、可自己改代码的方式重新实现。这篇文章我不打算只晒项目截图。我会把 WorkDSH 从想法到开源的完整过程讲清楚包括架构设计、核心功能落地、Skill 系统和自定义规则怎么处理以及发布时踩到的一堆坑。想自己做一个开源版 AI 编程工作台的人或者正在纠结“要不要重写一套自用工具”的人应该都能从里面拿到点能用的东西。1. 为什么我会动手重写一个 WorkBuddy现有工具的不可控让我很难受先说清楚我并不是觉得 WorkBuddy 这类产品做得不好。相反正是它们让我意识到“AI 编程工作台”应该长什么样不是被动回答问题的聊天框而是一个能读代码、改文件、跑命令、记住你偏好的执行中枢。问题在于商业化产品为了体验连贯往往会把内部决策藏起来。你看到的是结果看不到的是“它为什么这么干”。我自己在真实项目里遇到三个特别具体的摩擦点可能很多用类似工具的人也有同感。第一个摩擦点是决策不可回放。模型说“我把 xx 函数改名了”但它到底改了哪几处、影响哪些调用点、有没有漏掉字符串拼接的场景如果工作台不给足过程信息你只能靠 diff 一点一点翻。小项目还好项目一大这种不透明就会变成焦虑源。第二个摩擦点是环境绑定。换一台机器、换一个账号配置和记忆就得重新来一遍。本地模型、不同厂商的模型接口、私有知识库这些我希望随时能换而不是被工具锁在固定通道里。第三个摩擦点是规则难以沉淀。很多产品或支持自定义指令但“自定义”的范围通常只停留在提示词层面。我希望定义一个规则之后后续所有任务都生效包括工具调用方式、文件写入偏好、命令执行策略。这些如果不在引擎底层做而只是塞进 prompt等于每次都要看模型心情。所以我给自己划了一条清晰的产品边界WorkDSH 不做 IDE只做 AI 执行中枢。它可以作为一个独立命令行工具跑也可以被 VS Code、Neovim 这类外部编辑器反向调用。它的所有配置、记忆、规则、日志都是纯文件能被 Git 跟踪能被 diff能回滚。这是闭源工具很难给我的安全感。1.1 我对“AI 编程工作台”的完整理解在讲架构之前先把概念对齐一下。我们说的不是简单“问一句答一句”而是一个可以连续操作环境的智能体Agent系统。它通常包括几个层面对话层理解用户的目标和上下文。规划层把一个大的编程任务拆成若干小步骤。工具层真正去执行读文件、改文件、跑命令、搜索代码等操作。记忆层在跨对话场景中保留项目偏好和历史决策。规则层全局指令、临时指令、项目规则之间做优先级协调。WorkBuddy 这类产品的价值不在于它“能写代码”而在于它把这些层面串成了一条可执行的流水线。我的 WorkDSH 要复刻的正是这条流水线本身而不是某个 IDE 的界面。1.2 WorkDSH 的目标透明执行而非漂亮的自动化我给自己定了一个验收标准任何一个文件改动都必须能追溯到“是哪一次模型决策、基于哪些上下文产生的”。所以在 WorkDSH 里所有工具调用、模型输入输出、命令执行日志都会落到.wdsh/目录。出了问题不是去猜而是打开日志看那一轮的完整上下文。这个设计带来的直接代价是它不像商业产品那么“自动化”。它要求使用者对过程保持关注。但我的经验是真正要在生产环境里用的工具首先得让人敢用。敢用比用得爽更重要——这也是我把开源作为前提的根本原因代码摆在那里谁都能审计。2. WorkDSH 的核心架构模型路由、代理循环和沙盒三层设计整个系统我拆成了三层模型路由层、代理循环层、沙盒执行层。三层各管各的事互相只通过结构化消息通信。这个分层是我从第一版乱写代码里吃了亏之后才定的。2.1 模型路由层一套接口兼容本地模型和云端 API模型路由层没有做得太复杂核心思路是“一切皆 OpenAI 兼容”。无论你用的是本地模型还是各类提供 OpenAI 兼容接口的服务只要配置一个providerWorkDSH 就能接上。// provider 配置结构 export interface ModelProvider { name: string type: openai-compatible | local-http baseUrl: string model: string maxContextTokens: number maxOutputTokens: number temperature?: number }这里有个细节必须提醒不同模型的maxOutputTokens上限差别很大而且有的模型会忽略你传的参数。我第一版直接写死max_tokens8000结果接一个旧模型时疯狂报错排查了半天才发现是对面只支持 2048。所以后来我把“生成预算”和“模型实际能力”分开模型层会先做一次能力探测再根据预算截断上下文。另外一个经验是本地模型和云端 API 的延迟差异巨大。代理循环里如果对时间不敏感容易在本地模型上等半天。所以我在请求层加了超时控制和重试逻辑而且重试时会把整个上下文重新组装因为有些本地服务会在中途断掉连接重新发送同一份请求也不会从断点恢复。2.2 代理循环计划、工具调用、观察结果每一步都可审计代理循环是整个系统的发动机。我采用的不是“一次生成全部步骤”的方式而是逐步推进每一步由模型决策一个工具调用执行器执行完并把结果喂回去模型再决定下一步。async function runAgent(ctx: AgentContext, maxSteps 30) { for (let step 0; step maxSteps; step) { const decision await ctx.model.plan(ctx.getMessages()) if (decision.type finish) break const result await ctx.executor.run(decision.tool) const observed truncateResult(result, 12000) ctx.messages.push({ role: assistant, content: decision.explanation }) ctx.messages.push({ role: tool, toolName: decision.tool.name, output: observed }) ctx.logGitRage(step, decision, result) // 关键这一步的完整上下文落盘 } }这个伪代码很简单真正的复杂点在两个地方。第一个是“结果截断”。模型生成能力再强也不能无限容纳工具输出。一条命令可能产生几百行日志如果全塞回上下文后面几步的推理质量会迅速下降。我的做法是命令输出只保留头尾各 100 行中间如果过长就用提示词压缩成摘要同时在上下文中标注“此处省略 N 行”。这些省略信息不会丢会完整写进本地日志需要时可以人工查看。第二个是“错误注入”。工具调用失败时不能只说“失败”两个字必须把可理解的错误信息带回来。比如命令退出码非零、文件不存在、权限不足每一种情况在返回给模型时都会附带结构化错误码让模型知道该换路还是该停下。这里我的经验是不要尝试把错误格式化得太友好让模型看到原始 stderr 往往更能做出正确判断。2.3 沙盒与终端执行权限、超时、可回滚工作台要跑命令这是和普通聊天工具最大的区别也是最危险的地方。我自己的策略是“宽松默认关键拦截”。WorkDSH 的沙盒实现分三层路径策略默认允许读取项目目录下的文件写入必须限定在项目目录内或者用户在配置里显式添加的可写路径。命令策略危险命令比如格式化磁盘、删除根目录等会被模式匹配拦截但拦截只是提示用户可以在命令行里显式允许一次或永久允许。超时策略每条命令默认 30 秒超时超时后强制结束进程并把“已超时”作为结果返回给模型避免模型在那里傻等。我遇到过的最经典问题模型为了“确认改动”跑了一个会卡在交互输入里的脚本进程既不退出也不报错。如果没有超时保护整套代理循环就挂死了。所以命令执行器里必须自带子进程管理而且我强烈建议用独立进程组来运行命令防止子进程继续运行。沙盒里还有个容易被忽略的点命令执行的工作目录。模型经常在子目录里执行命令但忘记了上一条命令在别的地方留下了状态。所以 WorkDSH 每次执行命令时都会显式声明工作目录并在日志里记录回放时能看出“模型当时是在哪个目录下做的决策”。3. 把 WorkBuddy 式体验落到具体功能diff 编辑、跨对话记忆和目录策略架构搭好之后真正让工具“好用”的是几个核心功能。我挑三个最关键的展开讲。3.1 文件编辑用 diff 而不是全量覆盖WorkDSH 的文件写入机制不是“模型生成的内容直接覆盖目标文件”而是先生成 diff再应用 diff。为什么因为模型直接输出整个文件时很容易因上下文超出而丢掉尾部内容或者无意识地改动无关部分。diff 模式强迫模型只交付“增量”应用层再负责合并。默认的写入流程是这样的模型先读取文件内容read_file。模型生成一个统一 diff 格式的修改计划。应用器用parse-diff之类的库解析并检查 diff 是否能干净地应用到目标文件。如果冲突返回错误让模型重新生成。这个流程看起来多了好几步但在真实项目里能救命。有一次模型在改配置文件时因为目标文件在我生成 diff 之后又被另一个进程改动过应用器直接拒绝避免了覆盖别人刚写入的内容。这个冲突检测能力任何做 AI 编程工作台的人都值得加上。另外所有应用的 diff 都会存入.wdsh/patches/并且每次改动前自动建立一份当前文件快照。回滚时不需要 Git 介入直接从快照恢复或反向应用 diff。对那种“改了但想撤销又不想把整个提交重来”的场景非常管用。3.2 系统缓存目录能不能改到 D 盘这类配置问题的体验设计搜索热词里有一个让我特别有共鸣的“workbuddy 系统缓存目录能改到 D 盘吗”。这说明很多人在意的是“工具的数据到底放在哪怎么迁移”。WorkDSH 从一开始就把这类路径设计成可配置项而且全部集中在wdsh config命令里。WorkDSH 的目录模型是这样的workdir当前项目的工作目录所有文件操作默认在这里。cache_root模型缓存、临时文件的根目录默认在系统临时目录下可以改成任意位置。log_root运行日志和审计记录的存放位置。memory_path跨对话记忆数据库的位置。在 Windows 上把缓存目录改到 D 盘只需要一条命令wdsh config set cache_root D:/WorkDSH/cache wdsh config set log_root D:/WorkDSH/logs改完之后WorkDSH 会经历一次“数据搬迁”流程旧缓存目录里的有效文件会被复制到新位置然后更新配置最后才清理旧临时文件。为什么这么设计因为直接改配置会导致旧记录失效重新生成又慢又浪费。配置文件本身是纯文本 YAML放在用户目录下既适合 Git 管理也方便排查问题。这个功能背后其实藏着一个通用设计原则工具的数据位置永远应该是显式的而不是藏在某个生态的默认目录里。用户对“自己的工作台”有迁移需求时目录可配置比什么都重要。3.3 跨对话记忆不是把聊天记录存下来而是抽取偏好“跨对话记忆”是我花了最多心思去做的模块。最初我走偏了以为把历史对话全部存进数据库下一次在上下文里再翻出来就是记忆。结果上下文爆掉而且历史里最关键的偏好信息反而被淹没。后来我把记忆设计成“抽取式”的。每当一次任务结束系统会做一轮总结判断任务过程中出现了哪些可复用的新信息再写入记忆库。记忆库用最简单的关系型结构本地 SQLite核心表是这样的CREATE TABLE memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT, content TEXT, source_session TEXT, created_at TEXT, updated_at TEXT, hit_count INTEGER DEFAULT 0 );记忆有两种类型。一种是结构化配置记忆比如“这个项目测试命令用pnpm test不要用npm test”“缓存目录已改到 D:/WorkDSH/cache”。另一种是自然语言经验记忆比如“线上环境签名密钥在构建时自动注入本地调试务必跳过签名步骤”。加载记忆时不是把全部 content 塞进上下文而是做一次轻量检索。默认的检索方式是基于关键词打分如果你愿意也可以接入 embedding 模型做向量检索。每一条记忆只保留 key 和一段不超过 200 字的内容摘要太多就丢。这个“摘要化”策略保证记忆在长时间使用后依然能塞进上下文窗口。我自己的实测效果是跨对话记忆一旦生效最明显的感受不是“它记住了我说过的话”而是“它不再反复问我已经回答过的问题”。那种体验飞跃远大于单纯提升模型能力。4. Skill 系统和自定义指令把工作流沉淀成可复用资产如果说记忆让 WorkDSH 变得“懂你”那 Skill 系统就是让 WorkDSH 变得“能干”。WorkBuddy 的一大批搜索热词都是关于 skill、使用教程、自定义指令的说明大家真正关心的是“怎么把一个常见任务固化成固定套路”。4.1 为什么 Skill 用 JSON Schema 声明而不是硬编码函数我做第一版 Skill 系统时用的是函数注册制每种技能写一个 TS 函数往里传上下文。后来发现这个方案很难维护——技能越加越多执行逻辑和模型上下文耦合严重用户想新增一个技能还得重新编译。所以第二版我改成 JSON Schema 声明式。一个技能就是一份 JSON 文件描述名称、触发条件、执行步骤、需要哪些上下文。执行引擎只负责解释这份 JSON不关心具体逻辑。下面是我实际用的一个 code_review 技能示例{ name: code_review, description: 对指定目录做一轮代码审查输出问题清单和修改建议, triggers: [review, 审查, code review], steps: [ { tool: glob, args: [**/*.{ts,js}] }, { tool: read_file, target: fileList }, { tool: run_command, cmd: git diff --cached, optional: true } ], output: { format: markdown, sections: [问题清单, 风险等级, 修改建议] } }这个 JSON 不是为了给模型看的而是给引擎看的。引擎拿到 skill 后会把用户的真实意图和 JSON 里的 description 一起送给模型让模型决定如何编排 steps。换句话说JSON 定义的是“可能性框架”模型在框架内做具体决策。这样既保证了技能的可预测性又保留了模型的灵活性。技能存放位置同样遵循“一切皆文件”原则~/.wdsh/skills/放全局技能项目根目录下的.wdsh/skills/放项目专属技能。同名技能里项目级优先这养成了一个很自然的“全局通用 vs 项目特化”的层次。4.2 规则引擎全局规则、临时指令、Skill 默认值如何共存搜索热词里有一句话很关键“给 WorkBuddy 定几条规则后续对所有任务都生效”。这句话点出了规则系统和普通对话提示的根本区别。规则不是聊天的临时上下文而是一个独立的、持久化的、有优先级的配置层。WorkDSH 的规则分三级全局规则写在~/.wdsh/rules.md对所有项目生效。项目规则写在project/.wdsh/rules.md只对当前项目生效。会话指令在交互中临时指定的指令只影响当前会话。运行时引擎会把这三层规则按“会话 项目 全局”的顺序合并越具体越靠前。但合并时不是全文拼接而是先做 token 预算分配——每层规则先提取前 N 句关键句总注入量不超过上下文预算的 15%。这个预算控制非常重要否则规则一多模型真正的推理空间就被挤压了。我曾经因为规则写得太多导致模型在每个工具调用前都要“背诵规则”似的绕来绕去回答质量明显下降。后来加上关键句提取规则命中率反而更高了。规则文件里可以定义命令偏好、文件写入策略、避免使用的 API、强制命名规范等。它的实际威力在于配合工具执行层一起生效而不只是影响对话语气。比如项目规则写明“所有 shell 命令必须显式设置--no-interactive”执行器里就会在命令组装阶段强制加上这个参数。这是我没预料到但非常出效果的一个特性。5. 从自用工具到开源项目许可证、文档和发布检查清单代码在自己文件夹里能跑通是一回事开源出来有人愿意看、愿意试是另一回事。项目发布前我专门花了一整周做“开源化”改造内容远不止把仓库设为 public 那么简单。5.1 开源许可证怎么选先看场景别上来就 MIT很多开发者第一个挑的许可证是 MIT觉得最自由、最省事。但我的项目里有二进制分发需求还计划后续开放插件生态未来可能有一些品牌和商标保护方面的考量。这时 MIT 的“无保护”特性反而不适合。我做了一个简单的对比表把自己的需求往里套许可证适用场景我关心的点MIT只希望代码被人随便用不在意衍生项目的约束专利条款弱商标保护不明确Apache-2.0需要明确专利授权、商标声明适合工具类项目需保留修改声明但提供专利保护GPL-3.0希望所有衍生项目必须开源阻止闭源分发传染性较强插件生态可能受限最终选的是 Apache-2.0。它给我的直接好处是代码可以被任意使用和分发但修改后的版本需要保留原始的版权和许可声明同时它有明确的专利授权条款对项目未来商业化探索更友好。如果只是纯个人学习项目、不打算开放插件生态MIT 也完全没问题关键是想清楚“以后这个项目可能往哪走”。另外有一个很多人忽略的点第三方依赖的许可证兼容性。开源项目不能只给自家代码选许可还要检查依赖项。WorkDSH 里用的一些 npm 包是 MIT而某几个工具库是 Apache-2.0 兼容问题不大。但如果你引用了 GPL 的组件整个项目的许可证性质就可能被传染。发布前我专门生成了一份THIRD_PARTY_NOTICES把依赖项许可证情况集中列出这也是大项目审计时很容易被问到的东西。5.2 开源仓库做这三件事项目才不算“死码”第一写一份能让人 5 分钟跑起来的 README。刚开始我把 README 写成了技术架构文档满篇都是“设计理念”。后来一个朋友告诉我开源项目的第一读者是“想要在 10 分钟里判断值不值得装的人”。所以我改成三步式装什么、怎么配、跑一个最小示例。尽量不放没有实操价值的套话。第二提供真实可运行的示例目录。我放了一个带简单 bug 的 demo 项目README 里直接给出一条 WorkDSH 指令“帮我找出 login.ts 里的竞态条件并修复”。新用户照着跑一遍就能直观感受工具的实际效果比看他写程序要强。第三发布前写好 issue 模板和行为准则。看起来不新鲜但它的实际作用是提前过滤无效反馈让用户知道“问题应该带哪些上下文”。我收到的第一条有效 issue 就是用户附上了.wdsh日志目录下的 session 记录我几分钟就能定位问题。如果没这个模板用户大概率只会写一句“它给我改错了”那谁都帮不上忙。5.3 发布后真实踩到的坑日志膨胀、模型参数分歧、文档滞后项目发布之后我遇到了三个非常具体的问题都是自用时根本意识不到的。第一个是日志膨胀。开发时我自己用日志目录会有意识地偶尔清一清。开源之后别人会长时间运行.wdsh/logs/里积累了体量很大的会话和 diff 记录。有用户反馈“跑一次任务磁盘涨了几百兆”。我后来给日志系统增加了滚动策略只保留最近 20 次会话的完整日志更早的只留摘要。这个问题不及时处理开源项目的口碑会很快被消耗掉。第二个是模型参数分歧。社区里有人用更强的模型跑同一个任务效果很好也有人用轻量模型跑完全无法收敛。这说明我在模型层设定的推理参数太依赖单一模型的特性。后续我改成了“按 provider 提供默认参数的配置模板”不同模型各自适配而不是全局一刀切。第三个是文档滞后于功能。我发版之后才发现 README 里写的配置项名称和实际不一致原因是开发过程中字段名改过一版忘了同步文档。后来我写了一个小脚本直接从代码里的配置 schema 生成配置文档杜绝了这类手写文档与代码脱节的问题。6. 现在还不满意的地方和接下来的迭代想法说点实话。WorkDSH 现在离“开源版 WorkBuddy”这个目标还差得远尤其是我自己天天用反而最清楚它不顺手的地方。首先是最难处理的上下文压缩。当任务跨了很多文件、涉及大量 git 历史时模型上下文长度还是很容易吃满。我目前是粗暴地靠“结果截断 记忆摘要”撑过去但真正优雅的方案应该是做分层上下文核心文件保持完整边缘文件做摘要按需再展开。这个方向我会继续迭代。其次是多代理协作。现在的 WorkDSH 还是一个单一代理循环读代码、规划、写代码都由同一个模型上下文完成。有些复杂任务里负责执行改动的模型很容易被负责“读代码”的上下文污染。我下一步想做的是拆成三个角色探查者负责搜代码规划者负责定方案执行者负责落改动。各自有独立的消息上下文只通过任务交接区通信。这个架构改动很大但我觉得值得试。最后是插件化。我自己写了很多针对不同语言项目的技能但每个技能都硬编码在仓库里别人用起来未必合适。理想状态是提供一个 SDK让第三方能注册自定义工具和自定义执行策略。WorkDSH 的工具层结构已经很接近这个目标了缺的只是把接口正式公布出来。如果看到这里你也想自己做一个类似的 AI 编程工作台我的建议是从最让你“不舒服”的那个场景开始。别一开始就想着做全套选一个你每天都会遇到、又觉得现有工具不够透明的小场景把它做扎实。我就是从一个“AI 改配置文件不看 diff”的愤怒下午开始的做到现在WorkDSH 已经成了我日常开发逃不开的一部分。它不完美但每一行代码都在我掌控里这种确定性是商业工具暂时给不了我的。
网站建设高端定制企业官网