news 2026/9/28 18:36:06

持久化Web AI编码工作区:让Claude Code/Codex会话不再断档

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
持久化Web AI编码工作区:让Claude Code/Codex会话不再断档

如果你最近开始认真玩 vibecoding,大概率是这么个状态:电脑上装好 Claude Code 或 Codex,开个终端,把需求往对话框里一贴,然后看着 AI 自己读代码、改文件、跑测试。爽是真的爽,但用上几天你就会发现,终端里的会话寿命短得可怕——手一抖按到 Ctrl+C,项目刚到一半的思路就没了;离开工位换台机器,刚才那轮对话的进度根本带不走;半夜跑一个依赖升级,还得惦记着电脑别休眠。

这个项目就是冲着这个问题来的。Easy Web Vibecoding 是一个专为 Claude Code / Codex 打造的持久化 Web AI 编码工作区:CLI 引擎照常跑在你自己的机器上,但会话的启动、续接、上下文和历史记录全部通过浏览器来管理。说白了,就是把“临时起意的终端对话”变成“能存、能查、能接着干的长期项目资产”。如果你在搭 AI 编码工具链,或者已经被终端断档折磨得够呛,这篇文章会把从零搭建这套工作区的架构思路、关键代码、踩坑记录和使用习惯一次讲清楚。

1. 为什么 vibecoding 需要一套“打不死的”工作区

1.1 vibecoding 的本质:不是让 AI 写代码,而是让对话变成开发主线

vibecoding 这个词听起来像玩笑,但它的工作方式和传统写代码完全不同。传统方式是“我脑子里有方案,我负责实现”;vibecoding 是“我描述要的效果和边界,AI 负责实现,我负责判断方向和把关质量”。Claude Code 和 Codex 这类 agent 型 CLI 之所以能撑起这种玩法,是因为它们不是简单的补全工具,而是有完整工具循环的智能体:读文件、改文件、跑命令、看报错、再改,一整圈下来几乎不需要人打断。

这也带来一个很实际的问题:一次正经的 vibecoding 任务,往往包含几十上百轮交互。这些交互本身就是项目最重要的资产——里面记录了需求的演变、AI 为什么这么做、你中途否掉了哪些方案。传统终端把这一切当成一次性聊天,关掉窗口就什么都没有了。我的想法很简单:能不能让这些对话像代码一样被保存、被检索、被跨设备复用?答案就是给 CLI 外面套一层持久化的 Web 工作区。

1.2 终端会话的三个死穴

我当初决定动手写这个项目,是因为被终端会话的常规用法坑了几次,总结下来就三个问题:

窗口关闭等于进程死亡。agent 型 CLI 跑一个复杂重构任务时,经常是“读代码五分钟、改代码两分钟”,你看着像卡住了就手痒去按 Ctrl+C;或者你只是换个目录、清理终端窗口,后台任务直接没了。更尴尬的是半夜挂着跑一个长测试,电脑自动休眠,第二天醒来发现进度全丢。

上下文断档。终端本身不保存任何东西。换台电脑、重启一下 CLI,AI 就完全不记得上一次聊到哪了。你只能靠 Ctrl+Up 翻历史命令,或者把上次的结论复制到剪贴板里手动带过去,这跟在记事本里管代码没什么区别。

长任务没有托管机制。跑构建、跑集成测试、批量迁移代码,这类任务动辄十几分钟,普通终端没法“挂在那里等你回来”。它既不能让你关掉浏览器出门,也没法在任务结束后给你一个可靠的通知和记录。

这三个问题叠加起来,vibecoding 的体验就变成:爽十分钟,难受一整天。Easy Web Vibecoding 的定位就是把这三根刺拔掉。

1.3 为什么选择 Claude Code 和 Codex 当引擎

市面上能跑的 agent CLI 不少,我最终锁定这两个,一是因为它们各自代表了不同的编码风格,二是它们都留了比较友好的可编程调用接口。

维度Claude CodeCodex
擅长场景跨文件深度重构、复杂逻辑推理、老代码梳理快速生成脚手架、批量替换、按明确规范执行
对话接入支持 stream-json 输出流支持 exec 模式、JSON 事件输出
会话续接支持 resume / 指定 session 续聊支持继续上一次会话
项目记忆CLAUDE.md 自动加载AGENTS.md 自动加载

两者互补性很强:我处理遗留系统时更喜欢用 Claude Code 多问几个“为什么”,而新建模块或做格式统一时 Codex 的响应速度更利落。工作区里我按项目维度配置 agent 类型,同一个 Web 面板下想换引擎就换,不需要重新搭环境。需要提醒的是,CLI 的具体命令参数随版本迭代变化很快,下面的命令我都以当前主流版本为准,跑不通时先看一眼--help再继续。

2. 架构设计:浏览器只是壳,真正持久化的是会话本身

2.1 三个核心层:引擎层、会话层、展示层

动手之前我先把架构拆成了三层,这样每个层的职责都很干净,调试起来也容易定位问题。

引擎层是最底层,负责真正拉起 Claude Code 或 Codex 子进程,并且一定要用伪终端(PTY)来接,不能用普通管道。原因很直接:这类 CLI 在交互模式下会渲染各种状态、清屏、控制动画,普通管道拿不到这些控制字符,而且很多命令在非 TTY 环境下会直接拒绝交互或者改变行为。用 PTY 等于骗过 CLI,让它以为自己还在一个正经终端里。

会话层是这套系统的核心,负责把每一轮对话、每一次工具调用、每一条命令的原始输出都落库。它不关心你用的是哪个 agent,只关心“这个 session 当前处于什么状态、历史上发生了什么”。跟你聊天的是 AI,但陪你聊天的是会话层。

展示层相对简单,就是一个浏览器端的终端模拟器加一个会话管理界面。它唯一的特殊职责是处理一件事:如果页面刷新或者网络断了,怎么把这个 1:1 的交互窗口重新接回去。这层不需要知道 AI 怎么思考,只需要忠实呈现流式输出,并把用户的键盘输入原样传给 PTY。

2.2 持久化三件套:快照、消息流、项目上下文

持久化不是简单把 stdout 存成文件,我把需要保存的东西分成三类:

会话快照。每个 session 启动时的完整参数:项目路径、当前工作目录、选用的 agent、环境变量、系统提示词。这相当于 Qt 里的保存存档——任何时候重启工作区,都能按这些参数把会话原样拉起来。

消息流。用户说的每句话、AI 回复的每个 token、agent 执行的每条工具调用,都按顺序记录,并打上递增的序号。这个序号很重要,后面讲断线重连会专门解释。

项目上下文。每次会话结束时,我会让系统自动生成一段摘要,追加到项目根目录的 CONTEXT 文件里。下次任何会话开始时,AI 会先读这份上下文,接着上一次的思路继续干活。这才是真正意义上的“持久化记忆”,比单纯依赖 CLI 自己的 resume 功能可靠得多。

2.3 为什么必须 WebSocket 而不是轮询

早期原型版本我偷懒用过轮询:前端每秒请求一次“把新输出给我”。结果是:agent 输出一快,界面就一卡一卡,因为每次请求都要重复拉取缓冲区;agent 输出一慢,你又得白白浪费请求。更麻烦的是轮询很难处理“服务器主动推送状态变化”这种需求,比如任务结束、进程退出、需要用户确认。

WebSocket 才是为这个场景设计的。它的三个特性恰好对应我的需求:

  • 全双工:前端能发输入和 resize,后端能推输出和状态事件,互不干扰;
  • 低延迟:token 一产出就可以通过 WS 帧推给浏览器,不用等下一次 HTTP 轮询周期;
  • 连接语义清晰:前端断开后我能立刻感知,并决定是杀掉子进程还是让它继续跑。

在实际实现里,我每个 session 维护一个 WebSocket,服务端把 PTY 的数据流原样透传出去,前端收到后往 xterm.js 里写。这样即使用户切到别的标签页,输出也不会丢。

3. 从零搭起工作区:环境、骨架与最小可用版本

3.1 环境准备和 CLI 安装

先说基础设施。整套工作区跑在 Node.js 上,所以第一步是确认 Node 版本,建议 18 以上,我用的是 20 LTS。接着安装两个 agent CLI:

node -v npm install -g @anthropic-ai/claude-code npm install -g @openai/codex claude --version codex --version

装完之后别急着写代码,先把登录搞定。Claude Code 在交互式界面里执行/login走 OAuth 流程;Codex 直接执行codex login。这一步做完,CLI 会把凭证存在系统钥匙串或用户目录下。后面很多奇怪的报错,起点都在这里。

3.2 项目骨架与依赖选择

创建工作目录,装四个核心依赖:

mkdir easy-web-vibecoding && cd easy-web-vibecoding npm init -y npm install express ws node-pty better-sqlite3

逐个解释一下选择:express 负责提供静态页面和 HTTP 接口,ws 负责 WebSocket,node-pty 负责拉起伪终端,better-sqlite3 负责持久化。如果你更习惯 Python 生态,也可以把 express 换成 fastapi,但 node-pty 在 Node 生态里最成熟,所以我整套选型跟着 Node 走。

最小可用版本不需要做界面,浏览器端直接上 xterm.js:

npm install xterm xterm-addon-attach

3.3 会话持久层:把对话当数据存

这是最容易偷懒也最值得做扎实的部分。我用 SQLite 建了两张核心表:

CREATE TABLE sessions ( id TEXT PRIMARY KEY, project_path TEXT NOT NULL, agent_type TEXT NOT NULL, -- 'claude' 或 'codex' cwd TEXT NOT NULL, env TEXT NOT NULL DEFAULT '{}', status TEXT NOT NULL DEFAULT 'idle', -- idle / running / paused / done created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL REFERENCES sessions(id), role TEXT NOT NULL, -- user / assistant / system / tool content TEXT NOT NULL, seq INTEGER NOT NULL, -- 输出游标,断线重连时用 created_at INTEGER NOT NULL );

打开 WAL 模式很重要:

PRAGMA journal_mode = WAL;

原因很现实:浏览器翻历史记录的时候,agent 可能还在往后写新消息。默认的 journal 模式会导致读和写互相锁死,WAL 模式让读写并行,体验完全不同。这套 schema 跑了一个月,几千条消息没有任何性能问题。

3.4 最小 WebSocket 桥接:让浏览器握住 PTY

有了持久层,接下来就是把 PTY 和浏览器接起来。核心代码不长,我用 node-pty 建子进程:

const pty = require('node-pty'); const child = pty.spawn(agentCmd, agentArgs, { name: 'xterm-256color', cols: 120, rows: 40, cwd: session.cwd, env: { ...process.env, ...parsedEnv } }); child.onData(data => { // 把输出存进 messages 表,同时推给 WebSocket storeOutput(session.id, data); ws.send(JSON.stringify({ type: 'output', data })); }); ws.on('message', raw => { const msg = JSON.parse(raw); if (msg.type === 'input') child.write(msg.data); if (msg.type === 'resize') child.resize(msg.cols, msg.rows); });

前端用 xterm.js 四行代码就能连上:

const term = new Terminal({ cols: 120, rows: 40 }); const ws = new WebSocket(`ws://${location.host}/ws?session=${id}`); term.loadAddon(new AttachAddon(ws)); term.open(document.getElementById('terminal'));

到这里,一个能跑的最小版本就出来了:浏览器开一个终端,输入命令,agent 正常干活,输出全部落库。接下来才是让它“持久化”真正好用的关键调节。

4. 把 Claude Code / Codex 调教成“能续上”的 Agent:关键参数与上下文

4.1 两种接入方式的取舍:一次性调用 vs 持久交互进程

CLI 通常给两种调用方式。一种是一次性调用,比如 Claude Code 的-p参数或 Codex 的 exec 模式,适合脚本化触发;另一种是交互式进程,启动后一直等待下一轮输入,适合长对话。

方式典型命令适合场景代价
一次性调用claude -p "..."/codex exec "..."定时任务、单轮问答、批量处理每次都要重新加载上下文,长对话不划算
持久交互进程直接 spawnclaude/codexvibecoding 主线对话、跨轮次联动需要自己管理状态、输出解析、重连恢复

我在工作区里默认走持久交互进程。原因很直接:vibecoding 的本质是“多轮你来我往”,如果每轮都新起进程,AI 的记忆只能靠我们塞回去的文本,Token 开销大且丢失交互细节。保持同一进程,AI 内部状态是连续完整的。

但如果进程被杀掉或服务器重启,就得靠 4.2 的上下文方案兜底。

4.2 用 CLAUDE.md / AGENTS.md 当项目的“永久记忆”

这是整个项目里最值钱的一个设计。Claude Code 会在项目根目录自动读取 CLAUDE.md 作为初始上下文,Codex 对应的是 AGENTS.md。我一开始只当它是文档,后来才意识到这分明是“给 AI 的持久记忆卡”。

我在每个项目的 CLAUDE.md 里维护这几类内容:

# 项目规则 - 所有时间处理统一用 dayjs,不要用原生 Date - 错误信息必须带操作名,比如 readFile failed: xxx - 测试文件放在 tests/unit 下,命名 xx.spec.ts # 当前模块地图 utils/ - 公共工具,改动需同步 axios 封装 legacy/ - 遗留代码,禁止大面积重构,先增加测试再动手

工作区在每次开启会话前,会额外生成一份临时的启动提示,追加到会话第一轮输入里:

请先阅读 CLAUDE.md,并结合 CONTEXT.md 中上一次会话的摘要继续任务。 上次进展:... 待办:...

这样即使进程完全重启,AI 也能在第一条回复里快速回到工作轨道,而不是从“你是谁”开始。我试过对比:有这份上下文时,续接任务的第一轮有效动作通常会提前 5 到 10 分钟。

4.3 身份认证与密钥管理的三个注意点

第一个坑,就是热搜里常出现的codex auth token is unavailable。这类报错多半是后台服务环境下,CLI 默认从系统钥匙串读凭证读不到导致的。我处理方式是:工作区以后台守护进程方式运行时,不依赖 CLI 自己的登录态,而是在启动子进程时用环境变量注入密钥:

export ANTHROPIC_AUTH_TOKEN="your_token_here" export OPENAI_API_KEY="your_token_here"

第二个坑是别把密钥写进项目配置文件。我专门在 .gitignore 里加了:

.env *.token .session-*

第三个坑会在 6.1 展开讲,就是当你改了 API 基础地址去接其他兼容模型服务时,endpoint 路径对不上导致的连环报错。

5. 持久化背后的三个关键决策:选型与取舍

5.1 为什么是 SQLite 而不是纯文件

有人会觉得,消息记录不就是往文件里 append 吗?没那么简单。一次复杂任务可能产生几千条消息,有用户输入、AI 回复、工具调用、状态事件,我在前端要按 session 分页查、按关键词搜、按时间排序。用纯文件做这些操作,要么每次全量读进内存然后手写过滤,要么自己维护索引——都很容易翻车。

SQLite 的价值不是存储,而是查询。一个SELECT * FROM messages WHERE session_id = ? ORDER BY seq就搞定了,还能配合普通索引把几十万条消息的查询压到毫秒级。而且单文件备份非常方便,整个工作区压缩一下就能拷走。

但 SQLite 不适合当一个纯粹的“数据流”。我在落库的同时,还会把每条原始输出 append 到一个 JSONL 文件里,专门用来回放调试。两个系统并行:SQLite 管查询,JSONL 管真相。

5.2 断线重连如何做到不出错

这部分是我重构最多的地方。最初版本是:浏览器断开,WebSocket 一关,我就把子进程杀掉,太蠢了。用户只是关了页面,agent 任务凭什么停?

新的逻辑是:子进程的生命周期由服务端管理,而不是由 WebSocket 管理。浏览器断开只影响展示,不影响执行。重连时前端把session_id和last_seq一起带上:

  1. 服务端把messages表里所有seq > last_seq的消息一次性回放;
  2. 回放完再挂上实时流;
  3. 如果子进程已经不在(比如服务重启),就根据最新快照重新拉起,并用resume/continue等参数尝试续接上一次对话上下文。

这个设计跑起来的效果是:我白天在公司电脑上打开一个会话,晚上回家用笔记本打开同一个 URL,AI 会先把白天省略掉的输出补上,然后继续等我下一句指令。

5.3 多设备访问时的安全边界

持久化 Web 工作区意味着“能被浏览器访问”,这套便利必须配好边界,不然就是给自己挖矿。

我的默认配置是只监听 127.0.0.1,保证只有本机浏览器能连。如果确实需要局域网或远程访问,我不会直接裸开端口,而是用反向代理加一层基础认证,WebSocket 也走同一个代理路径。同时所有密钥只存在服务端环境变量里,浏览器端拿到的永远是渲染后的会话数据,页面里绝不出现令牌或配置明文。

启动时服务端会在终端打印一个带一次性 token 的 URL,类似“浏览器打开这个地址完成授权”。这个机制既是便利,也是门槛——总比任何拿到 IP 的人都能直连要强。

6. 实测中踩过的三个坑和完整排查过程

6.1 codex endpoint 请求失败:从日志挖出来的路径问题

有段时间我为了让 Codex 接一个第三方兼容模型服务,在本地加了一层 API 网关配置。结果每问到一半就报错,错误信息大意是“请求 /responses 这个 endpoint 时失败”。最气人的是它不告诉你为什么失败。

排查思路按顺序来:

  1. 先开 CLI 的 verbose 日志,确认请求到底打到了哪个完整 URL;
  2. 对比日志里的请求路径和网关层实际支持的路由;
  3. 发现网关把请求转发到了/v1/chat/completions,而 Codex 发的是/v1/responses,路径对不上直接 404;
  4. 修正网关路由映射,把/v1/responses正确指向后端,同时调大超时时间,因为流式响应本身就是要长时间连接的。

这个坑的教训是:改任何 API 基础地址之前,先搞清楚目标服务的接口风格是 chat/completions 还是 responses,否则即使认证通过也会在请求层失败。

6.2 “authentication required; reopen the url printed by ...”这类认证提示

这套工作区用了一段时间后,我在一次浏览器缓存清理后碰到了一个很经典的认证提示:页面提示需要认证,要求重新打开终端里打印的 URL。排查过程其实不复杂:

  1. 看服务端日志,确认请求是否带上了正确的授权参数;
  2. 发现是我清理了 Cookie,而工作区的授权态存在 HttpOnly Cookie 里;
  3. 重新打开启动时打印的那个带一次性 code 的 URL,重新完成授权;
  4. 为避免再犯,给授权加了一个较长的有效期,并支持用固定入口重新获取。

这类问题的本质是“会话凭证存在浏览器侧”,如果直接杀掉浏览器进程或者清 Cookie,就得重新走一遍授权流程。我的建议是:把启动时打印的授权 URL 写进项目目录的 README 里,方便以后找回。

6.3 输出乱码和长任务中断:两个表面上像神迹的故障

乱码问题。有个项目里混进了 GBK 编码的日志文件,agent 跑测试时把非 UTF-8 内容打到了终端,整个输出流被一串锟斤拷中断。PTY 是字节流,不负责给你解编码。解决办法是在前端渲染前做一次容忍度高的解码,把非法字节替换成占位符,而不是让整个 stream 卡死。

长任务中断。浏览器断线后,子进程虽然继续跑,但如果整个服务被重启(比如系统自动更新),子进程还是会被带走。我的处理是两层防护:一是给子进程起独立的进程组,服务重启时自动继承会话快照并重新拉起;二是定时把当前输出进度写进快照,恢复时从最后一条完整记录继续。最终效果是依赖升级这种一小时级任务,中途服务重启也不会从头再来。

7. 跑了一个月之后:我的实际用法和建议

7.1 现在的典型工作流

这套系统现在是我每天的主力入口。典型的早上是这样的:打开浏览器进入工作区首页,选择“home 自动化”项目,点进昨天的会话。AI 根据 CONTEXT.md 自动恢复上下文,先回一段“当前进度:依赖打包脚本已完成,还剩两个模块的单测命名没统一”,然后我直接回复“继续处理那两个模块”。

中午出门前,我开一个会话让它跑一次全量回归测试,然后关掉笔记本。下午用手机打开同一个 URL,虽然屏幕小,但能看输出、能下指令,基本等同于随身带了个开发环境。这个过程放在以前是不可想象的——要么抱着电脑,要么牺牲一整个下午的进度。

7.2 适合与不适合 Web 工作区的场景

跑了一个月,我也摸清了这套方案的边界:

适合的场景不适合的场景
长时间重构和大范围代码迁移只改一行字的小操作,终端更快
后台跑测试、构建、依赖升级依赖 TUI 强交互、需要方向键精细操作的工具
跨设备切换、多人轮流看一个会话需要本地 GUI 或桌面通知的工具链联动
需要长期追踪项目决策过程离线环境、无浏览器可用的轻量场景

手机上看会话只建议“看一眼进度、回一句指令”,真做精细 review 还是得回电脑端。这个边界不是工具的缺陷,是交互形态本身决定的。

7.3 接下来想扩展的方向

目前在工作区里跑的还只是单人单会话,我很清楚它离“完整产品”还差很多。接下来最想做的是这三个方向:

一是多用户协作。同一个会话可以生成一个只读分享链接,同事打开就能看到 AI 的实时输出,不用再截图发聊天软件。

二是自动会话摘要。现在每条消息都存,但几千条消息找起来还是累。我计划每隔 50 轮自动压缩一次历史,生成“阶段性摘要”,既保留细节又降低检索成本。

三是跟 Git 状态联动。目前 AI 改完代码,我还得自己去看 diff。理想状态是在 Web 面板上直接展示改动的文件列表和关键 diff,甚至一键回滚某个会话版本。

最后分享一个我自己最受用的小技巧:每次开始新任务之前,先让 AI 把当前git status和 TODO 列表读一遍,再生成一份 session-brief 文件。把这句话写进 CLAUDE.md 之后,我的会话续接成功率明显高了一截。工具做得再花哨,真正让 vibecoding 变成生产力的,往往就是这些不起眼的流程细节。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 18:33:54

React学习03:用create-react-app搭建脚手架并接入TaoToken统一Key

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 18:33:40

LetterShell 初识:嵌入式 shell 的配置骨架与验证路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 18:33:40

实习之T100开发:用TaoToken统一Key打通JVM调优配置链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华