夜雨聆风学习资料网

ARTICLE · 1143189

做一款 Claudix 式对话插件:从聊天框到能干活的 Agent 宿主

做一款 Claudix 式对话插件:从聊天框到能干活的 Agent 宿主

大家好,我是 James。

上一篇我讲了 Agent SDK 的双面宿主——真正跑 Agent 的是 SDK + CLI,无头服务与 IDE 只是两套适配。

今天把镜头拧到 IDE 这一面:在 VS Code / 云 IDE 里做类似 Claudix 的侧边栏对话插件,护城河到底在哪。

接个 Chat API、画个好看的 Webview,做出来的是「会说话的实习生」。真正能改文件、能停干净、能活过容器重启的,是一整套 三进程工程。用户给出的是「帮我改代码」,不是「随便起一堆进程、随便写盘、随便把会话写进会蒸发的目录」。

先花一分钟对齐几个词

词
说人话是什么意思
三进程
Webview(界面)+ 插件宿主(管行为)+ CLI worker(真正干活的子进程)
Webview
编辑器里的侧边栏界面,负责展示与交互
插件宿主
插件主体:决定写到哪、何时杀进程、怎么向人确认
CLI worker
真正跑工具循环的子进程,是「干活的那双手」
通道 ID
一次对话连接的标识;停掉后必须能重建,不能死抱旧的
消息泵
宿主循环读取消息的地方;被长时间等待堵住就会假死
执行授权
允许读文件、调工具、改代码(通常有弹窗)
宿主授权
允许起多少进程、写到哪、活多久、能不能真停下来
随包 CLI
打包进扩展目录的命令行程序,不用系统 PATH 上那个版本
软链持久化
把配置目录链接到数据卷,避免容器重启后家目录蒸发

(SDK、PID、SIGTERM、ACL、OOM、cgroup 这类通用技术名词就不解释了,直接用。)

补一句视角,它划出了这篇的边界:把 AI 当补全工具,插件做到「侧边栏能聊天」就到头了;把它当可以委派任务的执行单元,要处理的就变成进程归谁管、写到哪个盘、能不能真停下来。宿主做成什么样,决定了同一套内核能发挥多少——这一层业界叫 Harness(把模型、工具、权限和运行环境捆在一起的那层外壳)。


01 | 全局视角:为什么「侧边栏 Chat」不够用

把三进程想成一支乐团:

乐团
三进程里对应什么
舞台
(观众看到的部分)
Webview:侧边栏界面
指挥
插件宿主:决定节奏、谁上谁下、什么时候停
乐手
CLI worker:真正把音符拉出来的那双手
乐谱
配置与会话持久化
指挥停了、乐手还在拉
假停止:界面停了,进程还在写文件
演出结束还抓着旧节目单
停后仍握旧通道 ID
乐谱写在沙滩上
家目录不落持久卷,会话隔夜蒸发

这个类比把本篇四个硬问题一次讲清:

  • 观众只看得见舞台。 界面做得再漂亮,没有乐手(CLI worker)就只是放录音——改不了任何文件。
  • 指挥必须管得住乐手的生死。 指挥收了指挥棒(点了停止),乐手如果没收到信号还在拉,音乐就不会真的停——对应 PID 回收,这是安全问题不是体验问题。
  • 乐谱要放在不会被擦掉的地方。 云 IDE 的「家目录」常常不落在持久卷上,你把会话写在那里,等于把乐谱写在沙滩上,一个浪过来就没了。
  • 指挥不能因为等一个决定就僵住。 有乐手举手问「这一段要不要重复」,指挥不能放下指挥棒一直等,否则整个乐团都停摆——对应权限弹窗不能堵住消息泵。

先把痛点立起来。三类工单,值班第一反应经常是「模型变笨了 / 云 IDE 不稳」。展开后,根因几乎都在宿主。

现场 A:点了停止,再问就永远转圈

用户话术:「停一下再继续问,就一直 loading。」

误判:模型 hang。真相:Host 已关掉旧通道,Webview 还握着旧通道 ID;再发送找不到通道,响应永不回,界面 busy 永久为 true。坏的是会话管子的状态机。

现场 B:昨天还能聊,今天像新号

用户话术:「隔夜重开,会话和 MCP 配置全没了。」

误判:插件丢数据、模型失忆。真相:默认配置写在「家目录」类路径,而云 IDE 容器的家目录常常不落在持久卷上。插件以为在写家,其实在写会蒸发的临时房。

现场 C:人以为停了,进程还在改文件

用户话术:「都点停止了,怎么文件还在变?」

误判:幽灵 bug、别人在改。真相:SDK 内部 spawn CLI,往往不把 worker PID 交给你;你只关了自己的通道抽象,OS 进程还活着。这是安全事故,也是巨大的浪费。

用户的真实期望通常是这四条:

  • 能读工作区、改多文件、跑终端;  
  • 危险操作会停下来问人;  
  • 云 IDE 关掉再开,会话还在;  
  • 点了停止,进程真的停——不会偷偷继续写盘。

这四条 全都落在插件工程层,不落在「模型更聪明」上。

组件
侧边栏 Chat
Claudix 式对话插件
模型
远端 LLM
经本地 CLI runtime
Tool
无,或后端代调
SDK tool loop + 工作区
权限 UI
无
Modal / 权限模式
进程模型
浏览器 + HTTP
Webview + Extension + CLI worker
持久化
服务端会话
卷上配置目录 + 备份
失败形态
HTTP 5xx
busy 卡死、僵尸进程、OOM、会话蒸发

没有右列,你手里只有聊天机器人。有了右列,才是编辑器里的 Agent 宿主。

当插件获得 Agent 执行能力时,它也同时获得了消费机器与改写工作区的能力。宿主工程如何管住这种能力?


02 | 行业调研:IDE 里的 Agent 在卷什么

做选型前,先横评一圈「编辑器里的 Agent 」。表面都是聊天气泡,底层差得很远。

产品 / 形态
进程模型
权限确认
云 IDE / 多用户
你能学什么
Cursor
深度集成
产品级确认
偏本机
UI 与工作流
Continue
扩展 + 可配后端
相对轻
开源骨架
扩展怎么搭
Cline 同类
扩展拉起 Agent
人机确认是卖点
本机为主
权限产品感
Claude Code / CLI 系
终端优先
终端确认流
本机强
tool loop 边界
Claudix 式自研
Webview + Extension + 随包 CLI
Modal + 模式 + ACL
卷上持久化 + 协作隔离
云 IDE 真上线的坑

摘要一句:

开源扩展告诉你「UI 怎么搭」;CLI 系告诉你「 Agent 怎么转」;云 IDE 插件告诉你「宿主怎么活」。

我们偷什么、拒什么?

  • 偷:CLI 系的 tool loop 边界;Cline 类的确认产品感;开源扩展的搭法。  
  • 拒:只抄 Chat UI;假设家目录天然持久;把 PID 回收当成「SDK 会处理好」;用 prompt 劝善代替写权限裁决。

选 Claudix 式,不是因为Chat 更漂亮,而是因为云 IDE 场景里,会话蒸发、僵尸进程、共享盘误写 会在第一周就爆——这些不是开源脚手架默认送的。


03 | 设计结论:先定三进程,再写 UI

为什么不先堆设置页?设置页漂亮、管道烂,用户只会说「插件坏了」。

因为真实干活的是 CLI worker,所以插件必须管得住它的生死;因为权限确认发生在 Webview,所以消息泵不能被 Modal await 堵死;因为云 IDE 的家目录常不落持久卷,所以配置根要主动焊到数据卷。

三个最容易混的词:

术语
角色
插件 Extension
展示、确认、写哪、何时杀进程
Agent SDK
造连接、消息流、权限回调入口
CLI Runtime
真正跑 tool loop 的子进程

口诀:

SDK 帮你造连接,CLI 才是干活的双手,插件才是真正决定产品行为的逻辑。

回到乐团:SDK 是乐器(能发声),CLI worker 是乐手(真正演奏),插件是指挥(决定演什么、什么时候停)。 买再好的乐器,没有乐手和指挥也开不了音乐会。

内核循环:

用户输入(Webview)    → Extension Host(launch / 权限 / 通道)    → CLI worker(Reason → Tool → Observe…)    → 需要确认则 Modal → 继续    → 停止则关通道 + kill worker

上一篇的 Agentic Loop 还在;这里多了两跳——UI 跳和宿主跳。少任何一跳,产品形态就塌。

Agent 在 IDE 里的自主性,其实至少两层授权:

授权
含义
多数产品认真做了吗
执行授权
允许读文件、调工具、改代码
通常有(Modal / 模式)
宿主授权
允许起多少进程、写到哪、活多久、停得下不停得下
常常被当成「实现细节」

本篇把后者当成一等公民。


04 | 必做的壳:因为X,所以选 Y

下面不是功能清单,是工程骨架。

4.1 随包 CLI,而不是 PATH

因为云 IDE / 同学电脑上全局 CLI 版本会漂移,所以SDK spawn 必须指向扩展目录里的随包二进制,不要「方便地」改用 PATH。

代价很实在:安装包变大、发版跟 CLI 节奏、打包路径写错就是 P0。但这是对的。现场一半排障时间,会浪费在「我这边能跑你那边不能」——根因根本不在业务代码。

4.2 权限请求不能堵泵

因为权限弹窗可能挂几分钟,所以消息泵对长挂起请求 不能 await。普通 RPC 可以等;权限类必须派发后立刻返回,让其它响应与流式消息继续进泵。

// 示意 · TypeScript// 保证:权限弹窗挂起时,其它消息仍能进泵voiddispatchPermission(req).catch(logError); // 不要 await 在读泵循环里
# 示意 · Python(无头对照)# 保证:没有 UI 时,问卷类工具 deny 并回喂文本选项asyncdefon_pre_tool_use(name: str, payload: dict) -> dict:if name == "AskUserQuestion":return {"decision": "deny", "reason": "请用文本选择题回复"}return {"decision": "allow"}

问卷类工具在 IDE 走专用 Modal,且 永不进自动放行白名单。某「自动编辑」模式下黑名单外可自动执行——那是产品策略,不是 SDK 默认。做插件却按无头策略全自动放行,是抄错作业。

4.3 停了还能再聊

因为用户点停止会关掉通道,所以再发送不能死抱旧通道 ID。

否则:通道不存在 → Webview 永远收不到响应 → busy 永久 true。用户话术是「点了停止再问就一直转圈」——看起来像模型挂了,其实是宿主状态机挂了。

解法:发送路径幂等 relaunch——有活跃复用,没有就新建;in-flight promise 合并并发;失败路径必须把 busy 拨回 false。

4.4 配置与激活顺序

两级配置是这类产品标配:用户级配置目录 + 工作区级配置目录。共享容器再加两刀:

  • 写权限代码裁决(ACL),不是 prompt 劝善;  
  • 按用户隔离配置子目录,避免协作误写他人配置。

激活顺序写错就全盘乱:

  1. 先 持久化(软链 / 备份)——早于任何读取默认配置路径;  
  2. 再引导与依赖注入;  
  3. 再挂 Transport、ACL、Webview。

生产上还有一层反直觉:软链的首要建设者甚至可能是容器入口脚本——扩展内逻辑经常只是自愈,因为无头运行时可能比 IDE 扩展更早写入配置目录。


05 | 硬问题:线上踩过的才叫护城河

这一章全是「现象 → 根因 → 怎么做」。

5.1 PID 回收:SDK 不给你 PID,你也得管住

现象:停对话后进程表里还有 CLI;卸载扩展后 CPU 继续涨。

根因:SDK 内部 spawn;关闭通道抽象 ≠ kill OS 进程。回到乐团:指挥收起了指挥棒,可乐手压根没看见——手上的弓还在拉,音符还在往外冒。

因为调用方拿不到官方 PID API,所以要在模块加载时登记 worker(例如 hook spawn、只认本插件 CLI),关闭走 SIGTERM → 超时 SIGKILL。

// 示意 · TypeScript// 保证:停对话 / 卸载扩展后进程表干净const worker = awaitwatchNextCliSpawn();awaitrunQuery();awaitterminate(worker, { escalateMs: 2000 });
# 示意 · Python# 保证:取消后子进程不会继续写盘proc = await asyncio.create_subprocess_exec(*cmd)try:await run_agent(proc)finally:    proc.terminate()try:await asyncio.wait_for(proc.wait(), timeout=2)except asyncio.TimeoutError:        proc.kill()

若不这样: worker 越积越多,被当成「机器配太小」;更糟的是用户以为停了,进程还在写盘。

5.2 并发 launch:多会话恢复会 OOM

现象:一开 IDE,多个历史会话同时恢复,容器直接被杀。

根因:N 个 launch 都读到「内存还够」→ 一起 spawn → cgroup 说再见。

因为内存判断与 spawn 之间有竞态,所以要串行拿「内存许可」:FIFO、发许可前强制刷新真实余量、锁持有到 spawn 完成、超时强制释放、拒绝时说人话「内存不足」。

5.3 持久化:有软链 ≠ 有备份

现象:家目录蒸发;或软链在、内容空了。

对策:

  • 配置目录软链到数据卷;  
  • 定时备份;  
  • 空目录自动从备份恢复。

软链管「写哪」,备份管「回滚」。只做一件,另一类事故照样来。

5.4 首屏:别把 CLI 冷启串进关键路径

因为用户先要「能点」,所以先标记已连接,配置后台拉;别把命令行工具的冷启动串进首屏。预热只是提速手段,不是开聊的前提——这一点和连接层预热那篇是同一套判断。

5.5 共享盘 ACL:fail-closed

prompt 劝善不是护栏。写权限代码裁决;未就绪则拒绝写入。共享容器上「先放开再说」等于请人误删。

5.6 别造上帝类

第一天按域拆:薄 Host + 厚 handler(会话、权限、持久化、worker 登记各管一摊)。上帝类三个月后无人敢改,而线上事故专挑无人敢改的地方爆发。


06 | 一次对话全过程

带决策点的端到端:

  1. 激活决策:配置目录是否已焊到数据卷?否 → 软链 / 必要时恢复备份。先于任何读配置。

  2. 侧边栏首屏决策:等配置拉完再可点,还是先可点?——先可点;配置后台加载。

  3. 发送拿 launch 许可(串行)→ spawn 随包 CLI → 登记 PID → SDK query。决策:命中已有活跃通道则复用。

  4. 流式渲染消息块回 Webview。敏感操作 → Modal(不堵泵);共享盘路径再过 ACL。决策:问卷工具?——专用 Modal,永不自动放行。

  5. 停止关通道 + SIGTERM/SIGKILL。决策:再发送?——幂等 relaunch,失败拨回 busy。

  6. 后台备份仍在跑;容器重建靠卷 + 备份,不靠「家目录应该还在」的愿望。

只做「Webview + HTTP Chat」,你会省掉 PID、权限泵、ACL、卷与备份——每一项对应一类线上事故。省的是开发时间,买的是值班时间。


07 | 行业对比:三进程宿主怎么选

维度
侧边栏 Chat 壳
Claudix 式 IDE 插件
无头服务宿主
终端 CLI 优先
权限 UI
无
Modal + 模式
Hook 文本降级
终端确认流
进程回收
基本无 CLI
PID + watchdog
取消作用域
用户 Ctrl+C
云 IDE 持久化
靠服务端
软链 + 备份
任务态 / 产物通道
本机家目录
首屏
HTTP 延迟
先连接后配置
预热只是提速手段
无 Webview
协作 ACL
少见
代码裁决
运行时注入
少见

什么时候选谁:

  • 选 Claudix 式插件:人要在编辑器里点允许、工作区即真相、要扛重启与误写。  
  • 选无头宿主:入口是 IM/HTTP 且无可靠 UI——去做上一篇的双宿主无头面,别硬做插件。  
  • 选 Chat 壳:只演示、不改盘——接受它不是 Agent。  
  • 选终端 CLI:本机重度用户;云 IDE 多租户不是它的主场。

08 | 常见坑

坑 1:权限 await 堵泵

  • 诱因:Transport 里「收到请求就 await 处理」写起来最顺手。  
  • 错解药:把 Modal 超时调短——泵仍堵,只是更快失败。  
  • 正解药:长挂起请求派发后立刻返回;泵继续读;结果用回调/响应消息回去。

坑 2:停后仍握旧通道 ID

  • 诱因:停止只关 Host 侧,Webview 状态未复位。  
  • 错解药:教用户「重载窗口」——那是产品失败。  
  • 正解药:发送路径幂等 relaunch;失败拨回 busy;停止时同步清 Webview 通道引用。回到乐团:这一场已经谢幕了,你还攥着上一场的节目单等下一个音符——它不会再来了。

坑 3:多会话同时 launch

  • 诱因:恢复历史时 Promise.all 很香。  
  • 错解药:加大容器内存——治标,波峰仍会打死邻居。  
  • 正解药:串行 launch 许可 + 刷新真实余量 + 超时释放 + 人话错误。

坑 4:只软链不备份

  • 诱因:软链一次「看起来持久了」。  
  • 错解药:发现空目录再手动拷——不可复制。  
  • 正解药:软链管写哪,备份管回滚;空目录自动恢复。

额外误判:把 busy / 丢会话 / 僵尸写盘当成模型变笨。先怀疑管道:通道、PID、卷、泵。


总结

  1. Claudix 式插件的护城河是三进程工程,不是 Chat API 包装。 Webview + Extension + CLI 少一环都像玩具。  
  2. SDK 造连接,CLI 干活,插件决定产品行为。 宿主授权与执行授权同等重要。  
  3. 权限泵、PID、卷上持久化、并发 launch,是上线第一周刚需。 不是「以后优化」。  
  4. 有 UI 走 Modal,无 UI 走 Hook 降级。 两面策略对齐,不抄错通道。  
  5. 先画三进程图再写 UI;薄 Host + 厚 handler。 设置页漂亮救不了管道烂。

Claudix 式插件的护城河是三进程工程,不是 Chat API 包装——少一环都像玩具。

下一篇我会写 Agent 的消费授权:用户给的是目标,不是空白支票——额度、可见、可停,先于安全专篇。


关注我,James 的成长日记,持续分享干货,帮你在 AI 时代少走弯路。

相关学习资料