最近圈子里 “vibecoding” 这个词几乎成了 AI 编程的代名词。我的日常主力工具就是 Claude Code 和 Codex 这两款终端 AI 编码代理,用习惯之后回不去,但一个痛点越来越明显:终端会话太“脆”了。笔记本合盖、SSH 断开、电脑重启,辛苦聊了半个小时的上下文当场归零。折腾了几天之后,我干脆做了一个名为Easy Web Vibecoding的持久化 Web AI 编码工作区,把 Claude Code / Codex 完整跑在服务端,浏览器随手打开就能继续上次的活。这篇文章把这套工作区的架构思路、核心实现和踩坑过程完整记录下来,给同样被“会话断连”折磨的人一个可直接参考的落地模板。
这个项目解决的问题很具体:让 AI 编码代理拥有“不会断”的持久会话。它适合几类人——本地开发但想随时换设备继续的、把编码代理跑在云开发机上的人、以及想把 agent 工作区开放给团队共用的场景。下面直接进入正题,从为什么要 web 化说起,然后是架构、持久化、CLI 适配层、部署安全,最后是实测记录和坑点总结。
1. 为什么把 AI 编码工作区搬进浏览器:vibecoding 的痛点
1.1 vibecoding 并不只是“让 AI 写代码”
很多人把 vibecoding 理解成“全靠 AI 写代码,人类当监工”,这其实严重低估了它。真正跑过一个完整需求你会发现,vibecoding 的核心是人机之间的多轮对话式协作:你描述意图、审查输出、纠正方向,AI 完成搜索、编辑、运行、调试这一整套劳动。这个过程中最有价值的不是某一段生成代码,而是那一长串对话上下文——它记录了需求演进的来龙去脉、哪些方案被否掉、为什么被否掉。
这也就意味着,会话状态本身就是资产。一旦终端会话断开,资产就丢了。本地跑 Claude Code 的时候,终端窗口不小心关闭,你损失的可能是一小时积累的决策记录。第一次遇到这种事我还能忍,第二次、第三次之后,我开始认真考虑一个方案:把这些 CLI 工具搬到服务端,用 Web 作为前端,让会话变成“可续传”的状态。
1.2 终端会话的“脆断”问题:SSH 断开、休眠、换设备
终端工具默认不提供远程会话驻留。SSH 连接一断,远程那个进程虽然未必立刻退出,但你的终端无法再 attach,所谓会话实际上已经“凉了”。本地跑也一样,笔记本合盖休眠,回来一看 agent 还在跑任务,但输出已经无法回传,只能 Ctrl+C 重来。
Codex 和 Claude Code 这类交互式 TUI 工具尤其依赖 TTY 环境。它们要在终端里绘制状态面板、等待键盘指令、动态展示工具调用过程。一旦失去 TTY,要么降级成非交互模式,要么直接行为异常。所以我确定的第一个设计目标就是:用伪终端(PTY)方式启动 CLI,让它们在服务端永远保有一个“活着的”终端,浏览器只是这个终端的窗口而已。
1.3 这个工作区适合谁:三类典型场景
根据自己的实际使用,我梳理出三类最适合这个方案的人:
- 多设备切换党:公司电脑、家里笔记本、临时借的机器,浏览器打开工作区就是同一个环境,不用任何安装配置。这对我来说是最大痛点,之前换设备意味着换一个“什么都不知道”的空终端。
- 开发机/云服务器用户:把 agent 跑在配置更高的开发机上,人在任何地方通过浏览器操作。这相当于给编码代理配了一个恒温恒湿的房间。
- 团队共享 agent 工作区:几个人同时看同一个 agent 的执行过程,或者在同一个项目目录里接力使用。这一点目前做得还比较轻,但已经有团队在用了。
2. 工作区架构:一个 Web 壳、两条命令通道、一套会话仓库
2.1 选型思路:不用 Electron,直接上 Web
很多同类工具习惯用 Electron 做桌面壳。我的选择是直接用浏览器。理由很实际:工作区本来就要长期驻留、远程访问,套个 Electron 反而把“零安装、跨平台”的优势丢了。浏览器天然支持远程、天然跨平台、天然多窗口,Web 技术栈的实时通信能力又足够用,没有理由自缚手脚。
整体架构可以概括成三块:
| 模块 | 职责 | 关键技术选择 |
|---|---|---|
| 前端 Web 壳 | 代码查看、终端模拟、会话时间线、参数配置 | React + xterm.js + WebSocket |
| 网关服务 | 进程管理、消息转发、认证授权 | Node.js + Express + WebSocket |
| 会话仓库 | 持久化对话/文件/环境状态 | SQLite + 文件系统 |
指令流从浏览器出发,经 WebSocket 到达网关,网关把它写入 PTY 的 stdin;CLI 的所有输出从 PTY stdout 读出来,再经 WebSocket 推回前端。这一来一回虽然简单,但设计上我拆成了两条独立通道:控制通道和数据通道。控制通道负责会话生命周期管理,数据通道只传输终端输出和用户输入,避免后续加功能时互相干扰。
2.2 浏览器端:编辑器、终端模拟器和会话面板
前端最麻烦的不是界面本身,而是渲染效率和还原度。Claude Code 和 Codex 的 TUI 会在终端里渲染大量 ANSI 转义序列,如果前端终端模拟器处理不好,输出会乱得没法看。xterm.js 这块做得最成熟,我直接用了它,并且开启了convertEol和scrollback配置,保证长输出不会把浏览器卡死。
会话面板是整个前端的灵魂。它记录每一轮用户输入和 agent 响应、工具调用事件、权限请求,支持按时间回跳。这样即使刷新页面,也能立刻恢复之前的完整对话时间线,而不只是重新看到一个空白终端。这个设计思路来自一个朴素想法:如果你打开 IDE 发现刚才打开的标签页全丢了,肯定也会烦躁。
2.3 服务端进程管理:守护进程加伪终端
进程管理是整个工作区的地基。我用 Node.js 的node-pty来创建伪终端,这样 Claude Code / Codex 会认为自己在真实终端中运行,交互模式、颜色输出、快捷键全部正常。核心启动逻辑大致如下:
import { spawn, IPty } from 'node-pty'; const pty: IPty = spawn('npx', ['@anthropic-ai/claude-code'], { name: 'xterm-256color', cols: 120, rows: 40, cwd: projectDir, env: { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1' } }); pty.onData((data) => { ws.send(JSON.stringify({ type: 'pty:data', sessionId, data: Buffer.from(data).toString('base64') })); });进程挂了怎么办?我在网关里挂了一个简单的守护循环:当 PTY 进程异常退出时,自动重新拉起,并把退出前的最后一段日志写入会话仓库。会话上下文靠文件系统和 SQLite 双重保存,所以重启后 agent 虽然需要重新加载对话,但至少项目目录、文件改动和之前的关键输出都不会丢。
3. 持久化会话是核心:状态保存、续跑与断线重连
3.1 到底要持久化哪些东西:不只是聊天记录
很多人以为持久化就是把对话文本存下来,这是典型误区。实际跑 vibecoding 工作流时,真正需要保存的至少包括四类状态:
- 对话时间线:用户输入、AI 回复、权限请求、工具调用的事件序列;
- 进程快照:工作目录、环境变量、当前 PTY 的列宽行高;
- 文件变更:agent 新建/修改的文件,Myers diff 或原始文件快照;
- 配置上下文:当前使用的模型、温度参数、系统提示词版本。
SQLite 表结构比较简单,我重点设计了事件表:
CREATE TABLE session_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, seq INTEGER NOT NULL, event_type TEXT NOT NULL, payload TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX idx_session_events ON session_events(session_id, seq);事件按seq严格排序,前端拿到后可以精确重建时间线。文件变更不往数据库里塞,直接写文件系统快照目录,避免数据库膨胀。
3.2 断线重连:心跳、session token 与重挂载
断线重连这个功能听着简单,实现细节很磨人。我用的方案是三段式:
- 心跳保活:前端每 15 秒发一次 ping,网关记录最后活跃时间;超过 60 秒无心跳,临时释放 PTY 的输出缓冲区但仍然保留进程。
- session token:每次创建会话生成一个随机 token,浏览器刷新后带着 token 重新连接,网关校验后在已存在的 PTY 上继续转发数据。
- 重挂载补偿:断线期间产生的终端输出会进入滚动缓冲区,重连后一次性补发给前端,避免中间漏掉关键输出。
实测中这个方案对付笔记本休眠、网络闪断都很有效。唯一的遗憾是如果进程本身被系统杀掉(比如服务器重启),session token 也只能帮你恢复历史记录,没法复活那个进程。
3.3 会话恢复的前后端配合:刷新不丢时间线
会话恢复我特意做了“两层”:终端层恢复和对话层恢复。终端层恢复解决的是 xterm.js 缓冲区回填,重连后立刻能看到断线前的输出;对话层恢复则从 SQLite 拉取事件表,以结构化时间线的形式渲染在侧边栏。好处是你可以只回看某一段工具调用的输入输出,而不必在终端乱码里翻找。
恢复过程中有个很务实的细节:要把原 PTY 的 cols/rows 重新设置一次。如果不做这一步,某些 TUI 组件会沿用旧的布局绘制,重连后会看到半个错位的界面。只需要在重连消息里带上终端尺寸,调用pty.resize(cols, rows)即可,成本极低收益极高。
4. Claude Code / Codex 集成:适配层设计
4.1 两款 CLI 到底差在哪:输出形态与交互约定
如果你只用过其中一个,可能觉得它们差不多。真正在适配层里折腾过后,我感受到的差异非常大:
| 维度 | Claude Code | Codex |
|---|---|---|
| 交互风格 | 行内划线编辑,逐段确认 | Agent 模式多任务并行,输出频密 |
| 权限模型 | 弹窗式许可请求 | 按目录/命令级别规则放行 |
| 输出特征 | 文字流+工具事件穿插 | 结构化工具调用更适合程序解析 |
| 常见部署形态 | npx 直接启动 | 官方 CLI + 可配置模型端点 |
适配层不能把两者当同一套接口处理。我采用插件式适配器:每个 CLI 对应一个 adapter,适配器负责把 PTY 输出解析成统一的内部事件结构。统一结构是{ type: 'user_input' | 'agent_reply' | 'tool_call' | 'permission_request', payload }。前端只需要消费这一套结构,不需要知道背后跑的是哪个 CLI。
4.2 适配层核心逻辑:非入侵式解析输出
解析 TUI 输出是适配层最脏最累的活。我一开始试图用正则从 ANSI 转义序列里抠文本,结果遇到转义变体就崩。后来换了个思路:不解析完整 TUI,而是抓特征行。比如 Claude Code 的权限请求会渲染成明确的关键字文本,Codex 的工具调用有稳定的 JSON 片段。为此适配层维护了一个特征规则表:
const patternRules = [ { cli: 'claude-code', pattern: /Permission needed/i, eventType: 'permission_request' }, { cli: 'codex', pattern: /"tool":"[^"]+"/, eventType: 'tool_call' }, { cli: 'claude-code', pattern: /^> /m, eventType: 'agent_reply' } ];这段代码很粗糙,但它揭示了一个核心原则:与其追求完美解析,不如在关键事件上做到高精度召回。我在文中不给你完整规则表,是因为每个 Claude Code / Codex 版本都会改输出格式,规则表必须按你实际版本调。
4.3 模型配置:DeepSeek、本地 LM Studio 等社区热点场景
集成适配层之外,另一个高频需求是把两款 CLI 接到不同模型服务。社区里特别常见的是 Codex 接入 DeepSeek、Claude Code 调用本地 LM Studio 模型。这两个场景我都实测过,配置思路有差异但底层一样:CLI 认的是 OpenAI 或 Anthropic 协议兼容端点,拿到兼容地址填进去就行。
以 Codex 为例,在配置里把 base URL 指向 DeepSeek 的兼容地址就能直连;Claude Code 则通过环境变量指定 base URL 和模型名连 LM Studio。注意两点:一是不同版本字段名可能有差异,改之前看一眼版本文档;二是我见过不少人把地址写错成http://127.0.0.1:1234/v1/chat/completions,正确写法通常只要http://127.0.0.1:1234/v1,多余路径会让/responses这类端点直接 404。
适配层的模型配置区其实是个很好的延伸:它让同一个 Web 工作区既能连官方服务,也能连本地模型,还不会互相污染配置。我在会话仓库里为不同 CLI 保存独立的环境变量表,切换项目时自动加载,省去了手动 export 的麻烦。
5. 部署与安全:从本机到公网的取舍
5.1 本地启动:先跑通再说
部署这块我建议分两步:先本地,后远端。本地启动非常简单,核心命令只有这几条:
git clone https://github.com/your/repo easy-web-vibecoding cd easy-web-vibecoding npm install npm run build npm run start启动后浏览器访问http://localhost:3150,首次会让你填 CLI 类型、项目目录和模型配置。本地场景我不加任何认证,因为服务默认只绑定 127.0.0.1。这里有一个自己踩过的坑:不要图省事直接监听 0.0.0.0。我试过在局域网里直接局域网访问,结果始终报认证缺失,排查下来是会话 token 机制在跨 IP 场景下没生效。只监听本地,或者监听外网但强制 token 校验,两条路二选一。
5.2 部署到开发服务器:systemd 守护是标配
放到云服务器上跑,最省心的方式是 systemd。我给工作区写了一个 service 单元:
[Unit] Description=Easy Web Vibecoding After=network.target [Service] User=vibecoding WorkingDirectory=/srv/vibecoding ExecStart=/usr/bin/node server.js Restart=always RestartSec=5 Environment=NODE_ENV=production [Install] WantedBy=multi-user.target关键参数是Restart=always。工作区服务本身挂掉的话,systemd 五秒内拉起来,PTY 进程由网关内部的守护循环再拉起,双层保险。日志我用 journalctl 统一收集,方便排查“为什么 agent 突然没响应”这类问题。
5.3 安全设计:权限隔离和 token 校验不能省
在安全这件事上我吃过一次亏才开始重视:第一次部署到公网服务器时只加了个简单密码,结果根本没挡住扫描器,日志里全是暴力破解尝试。现在的工作区强制了几条规则:
- 会话 token 校验:所有 WebSocket 连接必须带 token,token 绑定会话 ID,过期时间默认 24 小时;
- 系统用户隔离:每个项目用独立 Linux 用户运行 PTY 进程,agent 的写权限被限制在项目目录内;
- 命令白名单:在网关层做一层基本的命令过滤,禁止
rm -rf /这类明显危险操作直接透传; - 访问控制:如需公网访问,建议前面再套一层反向代理并启用基础认证。
要注意的是,这些规则解决的是“防乱用”,不解决“防恶意”,如果 agent 本身被提示词注入诱导执行了恶意命令,工作区很难兜住。所以不要把公网工作区当成完全可信环境,敏感项目我只会内网开放。
6. 实测表现与踩坑记录
6.1 真实效果:一次完整的 Spring Boot 任务跑完
使用场景最能说明问题。有一次我需要快速搭一个 Spring Boot 集成 WebSocket 的小 demo,要求实时推送后端日志到前端页面。我在工作区新建会话,指定项目目录为/srv/demos/spring-ws,选择 Claude Code 作为 CLI,把需求描述清楚后让它开始干活。
整个过程持续了大约三十分钟。期间我在浏览器侧边栏目睹了它创建 Maven 项目、写配置类、加 WebSocket 端点、跑构建。中途有个权限请求弹出,我点了允许,它继续往下执行。第三十分钟构建通过,页面打开后能看到日志实时刷新。这中间我的笔记本休眠过一次,唤醒后刷新浏览器,会话时间线完整还在,没有断片。这个“休眠唤醒不丢上下文”的体验,说实话比我以前纯终端用法踏实得多。
6.2 踩坑记录:我从这些错误里学到的
篇幅有限,我挑四个最典型的讲,都是实操中真会遇到的。
坑一:Codex 在自定义端点时报/responses请求失败。社区里类似的报错像cc switch local proxy failed while handling codex endpoint /responses一样让人抓狂。根因多半不是网络,而是端点路径或认证头不匹配。Codex 的某些版本会向 base URL 拼接额外的路径段,你只需要确认配置的地址是根级兼容端点,不要在地址里带/v1之类的多余层级。
坑二:插件加载失败。Claude Code 启动时偶尔报failed to load plugins web boot,典型情况是插件声明了但实际目录不存在,或者 Node 版本不匹配。我的建议是启动工作区前先单独在终端里跑一次claude命令确认能正常运行,再交给工作区托管。很多 web 化失败其实是底层 CLI 本身就起不来。
坑三:中文输入丢字符。前端输入框拿到中文直接写进 PTY stdin,结果动不动丢字符或乱码。原因是 xterm.js 默认对非 ASCII 输入的处理和底层 PTY 编码不完全一致。解决方法是前端先把输入编码成 UTF-8 Buffer 再发送,后端写入时不能再做一次字符串转换。这个细节我排查了两小时,最后发现就是多转了一次编码。
坑四:长任务输出把浏览器拖垮。某些任务输出量巨大,xterm.js 的 scrollback 再加结构化事件双份数据,网页卡到无法操作。后来加了滚动节流和事件聚合:连续同类输出合并成一条摘要事件,明细保留在终端缓冲区里。这样侧边栏不炸,终端仍然能滚回去看完整输出。
6.3 还能往哪里扩展:团队协作与远程文件管理
这套工作区目前足够自用,但我也清楚它的扩展空间很大。一个方向是多人协作——给会话加只读/可写权限位,其他人可以围观 agent 执行过程甚至投票批准权限请求,这很接近“AI 结对编程”的形态。另一个方向是文件管理集成——目前文件改动只能通过终端看,如果能直接把 agent 生成的文件 diff 渲染成类似代码评审的界面,体验会好很多。
最后说点个人体会:把 AI 编码工作区 Web 化,本质上是在给“vibecoding 式开发”做一个真正可靠的房子。终端工具本身很优秀,但它们默认设计哲学是“一个人在一台机器前盯着屏幕”,这不是 vibecoding 工作流的常态。如今我可以早上在公司电脑开一个会话,下午回家用平板继续同一件事,这种体验一旦用惯就回不去了。如果你也在拿 AI 编码代理做高频日常开发,我建议你认真考虑给它们配一个长期驻留的 Web 工作区——别等到会话断掉的那天再后悔。