把本机的 Claude Code 包成一支随身的数字干活团队 —— 手机就是派单台,VSCode 是干活的车间。出门、躺床、地铁里掏出手机随时下达任务,VSCode 那边一个或多个 Claude agent 并行执行:改代码、跑测试、推部署、监训练。回到电脑前,所有会话历史、所有 chat tab 都完整在那,电脑接着干无缝衔接。

说一句很有信息量的元叙事:这套东西本身也是 Claude 替我搭的。从最初 python -m http.server 看一眼 jsonl 渲染,到 FastAPI + SSE 流式 + 三层登录防护 + Caddy + Let's Encrypt + SSH 反向隧道 + CDP 注入 + 多 agent 并行 + 移动端 PWA + 单元测试 + 一键起停脚本 —— 1 天搞定。我做的更多是产品经理:给截图、说"这个交互不对,要这样"、定优先级、做决策。这篇文章本身的发布也是它通过 admin API 直接 POST 上来的。

地铁里发个 PR

"把刚那个 bug 修一下,跑测试,提 PR" — 一句话发出去,到家时 PR 已经躺在 GitHub 等 review。

外面看训练任务

多卡 GPU 训练跑一晚上,半夜醒了手机扫一眼就知道 SFT 还在不在跑、显存够不够、loss 走势对不对。

训练环境一句话搭起

新拿到一台 GPU 机?让 agent 装 CUDA、配 Python 环境、拉数据集、跑 sanity check —— 你只看报告做决策。

会话与 VSCode 双向贯通

电脑前对话到一半起身离开,手机看到完整上下文继续聊;回电脑后 VSCode 直接接着干,会话同一份。

等火车 / 排队刷活

15 分钟空档不够开电脑,但够让 agent 处理一两个 todo —— 重命名文件、跑个脚本、改个文案。

开会偷偷推代码

会议无聊低头看手机不奇怪。让 agent 顺手把上午说的需求落地,散会就能 demo。

iPhone 截图:手机里看 SFT 训练任务负载,agent 用 Bash 抓 GPU/RAM 数据并渲染成表格回报
实拍:外面用 iPhone 看一台 6 卡训练机的 SFT 进度。问"机器啥负载了",agent 思考 12.7s → 跑 Bash 工具调用 → 拿回 GPU util / 显存 / 功耗 / 温度 / RAM / load avg → 渲染成表格 → 给结论"完全正常,SFT 还在跑(step 2000+, val loss 1.18 / 5000 step 进度 40%),剩约 95 分钟"。整个交互流式,思考过程可折叠,工具卡跟 VSCode 里完全一致。

01起点 — 离开电脑就是断开

本机装了 claude CLI,VSCode 里的 Claude Code 插件用的就是它。VSCode 这套体验很好:流式输出、工具卡片、思考过程、工作区上下文 —— 但它有一个硬限制:只能坐在电脑前用

实际工作里这个限制很难受:

  • 开会、出门、地铁:agent 还在跑 / 还在等你拍板,你只能干瞪眼
  • RDP / 远程桌面 不是为手机设计的:要在手机上做 IDE 级操作 —— 点小按钮、切窗口、看 diff、用右键菜单 —— 操作粒度跟"电脑前坐着"完全不是一回事;加上图像流压缩、整窗口画面流量大,远程桌面本身就重,"能看不能用"
  • Claude 官网 / 移动 App 是另一套登录态,跟本机的会话完全隔离 —— 你的代码、你的 prompt 文件、你的项目上下文都不在那边
  • 每次需要 agent 拍板的时刻你不在线,等你坐回电脑前,可能已经卡了 1 小时

为什么必须本地 + 必须 VSCode

有人会说"为什么不直接用云端 AI 工具?" —— 这个问题值得讲清,因为它不是偏好,是硬约束。这条约束分两层:必须本地(资源在哪)+ 必须 VSCode(人怎么 review)。

第一层 · 资源都在本机,搬不动也不该搬

  • 本地有大量代码仓库 —— 几十个项目分散在不同目录,跨仓库复用、相互依赖、共享 lib。"全部 push 到云让 AI 看"既不现实(数据量、私有协议、未提交修改),也不合规(含密钥 / 客户数据 / 内部接口)。云端 AI 看到的永远是个不完整的子集
  • SSH 私钥 + 公司 VPN + .env 文件都在本机,云端工具看不到;放过去 = 把所有秘密交给第三方
  • 未提交的本地修改是上下文的一部分;云端工具只能看到你 push 上去的版本

第二层 · AI 干的活要人 review,VSCode 是 review 的工具栈

AI 写代码不是"扔出去自己跑"就完事,每一段重要改动都要人审计。审计需要 IDE 级别的工具:Diff 视图Source Control 面板Go to Definition / Find ReferencesGit BlameDebugger整个工作区符号搜索。这套工具网页版 IDE / 云端编辑器都做不到 IDE 这个级别。把 AI 协作搬上云 = 失去人 review 的能力

架构因果链:本地仓库 → 必须本地干活 → 必须 VSCode 做人审计 → web 只能"远程驱动 VSCode",而不能"取代 VSCode"。所以这套系统里 web 是嘴和眼,VSCode 是大脑和手。

02一条消息的旅程 — 从手到回

不讲技术,先把"一条消息从你手发出,到 agent 给你回结果"这件事的体验描出来。

1
你在地铁里掏出手机,浏览器打开 https://dreamyouxi.com:8089,输密码登录。顶栏的下拉里选好项目("claude_web")和会话(昨晚电脑前没聊完的那个),界面跟 VSCode 里的对话布局一致。
2
输入"把刚那个 bug 改一下,跑测试,提 PR",点发送。
3
同一时刻,你家里电脑开着的 VSCode 那个 chat tab,就像有个隐形的人替你打字 + 按发送 —— 这条消息冒进了输入框,发送按钮自己被点了。
4
VSCode 里的 Claude 开始干活:思考读文件改代码跑测试写 commit message调 GitHub CLI 提 PR —— 跟你坐电脑前完全一样。
5
你的手机屏幕上几乎实时显示 agent 的思考过程(可折叠)、每一次工具调用(Read / Edit / Bash 都是独立卡片,可点开看输入参数和返回)、最终的文字回复。每一段都是流式逐字出来的。
6
几分钟后,你看到"已提交 PR #42 [链接]"。等你回到电脑前,VSCode 里那个 chat tab 历史完整在那 —— 同一个对话、同一个会话 ID、同一份 jsonl 文件。你可以接着往下推。

整套体验跟"远程把命令发到本机"或者"上传代码到云端 AI 改"很不一样:

  • 没有"上传代码"这个步骤 —— agent 本来就在你电脑上,能看见所有文件
  • 没有"我得 push 一下让云端拉" —— 改完直接进你本地 git status
  • 没有"切回 VSCode 把云端改的 pull 下来" —— VSCode 那边一直是真相源
  • 手机端就是 VSCode 的远程屏幕和键盘,干完活无缝合并到电脑上

03技术链路全景 + 完整功能清单

把上一章那段叙事画成图就是这样。绿色框是公网,蓝色框是本机:

PUBLIC NETWORK LOCAL MACHINE Browser SSE + Cookie Auth Caddy :8089 TLS 1.3 + LE 真证书 HTTPS SSH Reverse Tunnel 远端 18089 ⇋ 本机 5000 FastAPI :5000 认证 / 审计 SSE / 三层防护 CDP Inject execCommand 写消息 + click 发送 CDP :19222 VSCode 里的 Claude long-running · 写 jsonl

每个框做一件事,下面这张表是模块的"一句话定位",具体怎么实现 + 为什么这么实现放到第 04 章逐个拆。

Browser
手机或电脑浏览器,单页前端 33KB(HTML+JS+CSS 内联)。SSE 接收流式事件,渲染成跟 VSCode 一致的 chat UI。Cookie 持久化登录态。
Caddy :8089
公网 VPS 上的反代,挂 Let's Encrypt 真证书提供 HTTPS。flush_interval -1 禁缓冲保 SSE 流式不被打断。
SSH Reverse Tunnel
本机主动发起的 SSH 隧道,把公网 VPS 的 127.0.0.1:18089 接到本机的 127.0.0.1:5000。出方向打洞,本机不用开任何入站端口。
FastAPI :5000
业务核心。认证(cookie + Basic Auth 双兼容)+ 审计日志 + 三层防爆破 + SSE 流式路由 + 项目/会话管理。约 1500 行代码。
CDP Inject 模块
通过 Chrome DevTools Protocol 连进 VSCode(Electron),找到对应 chat tab 的 webview,把消息打进 React 输入框 + 触发发送。约 600 行 cdp_helper.py
VSCode 里的 Claude
long-running 的 Claude Code 插件实例,真正干活的人。读项目文件、调工具、写 jsonl 会话记录到 ~/.claude/projects/<cwd>/<sid>.jsonl。支持多 workspace 多 chat tab 并行

完整功能清单

实现到现在,这个 web 端支持的功能按场景分类列在下面。基本是"VSCode Claude Code 体验在手机上能复现的部分 + 一些只有手机端有意义的辅助":

对话 / 流式

  • 流式文本输出(边写边显示)
  • thinking 思考过程流式 + 默认折叠
  • 工具卡片(Bash / Read / Edit / Grep / Write / Glob 等)含输入参数与返回
  • tool_result 自动挂到对应 tool_use 卡片下
  • Markdown 渲染(marked + DOMPurify)流式期纯文本防闪烁,停下后转 markdown
  • image 块占位显示(不渲染原图,省流量)

会话 / 工程

  • 多工程切换(projects.json + 顶栏下拉,每工程独立当前 session)
  • 多会话切换 + 历史回放(按 mtime 倒序,分页 10 条 + 上滑加载)
  • 会话标题:custom-title > ai-title > first user text 预览三级 fallback
  • 手动重命名 session(写 custom-title 事件,VSCode 那边也认)
  • 删除 session(同步关 VSCode chat tab + 清绑定 cache)
  • 新建对话(slash /new-conversation 触发新 chat tab)
  • 主动刷新(?after=<uuid> 拉增量)+ 下拉刷新

多 VSCode + 多 agent

  • 多 VSCode workspace 切换(按窗口标题匹配 launcher)
  • 同一 workspace 多 chat tab 并行(每个 tab = 一个独立 agent task)
  • session ↔ webview_id 持久化绑定(session_webview_cache.json
  • 三步绑定:cache verify → silent scan → URI handler 开新 tab
  • 不允许 resume:保护用户 chat tab 的工作上下文
  • post-inject 反向验证:消息发错 session 立即报错

认证 / 安全 / 审计

  • Cookie session(30 天 TTL,session_token 文件签名跨重启不失效)
  • Basic Auth 兼容(curl / 自检 / iOS Safari 双轨)
  • 5 分钟 IP 失败 10 次封 IP(in-memory)
  • 当日累计失败 10 次硬锁定(auth_state.json 跨重启)
  • "宽锁不严锁":正确密码永远放行
  • 10 秒外部写入保护(防止跟 VSCode 撞 jsonl)
  • 完整 audit.log(auth / chat / lock / vscode_inject 事件)

移动端 / PWA

  • 100dvh + safe-area 自适应 iOS 地址栏伸缩 / Home indicator
  • 输入框 16px 防 iOS 自动放大
  • Enter 行为按设备分(桌面发送 / 移动换行)
  • "加到主屏幕"全屏(PWA manifest + SVG icon)
  • localStorage 记忆 lastProject / lastSession,重新进入回到上次
  • 3 秒全局刷新限速(按钮 + 下拉同入口)

部署 / 运维

  • 幂等启停脚本(start.ps1 / stop.ps1,重复运行不双开)
  • 精确进程匹配(命令行特征识别,不误杀其他 python / ssh)
  • 启动自检(FastAPI 200 + 公网隧道 200)
  • 使用 .env 集中管理密钥 / 公网 IP / 端口
  • 账号用量监控(/account/usage,缓存)
  • pytest 单测(mock CDP 不依赖 VSCode,~5s)+ e2e selftest 脚本

04子模块拆解 — 怎么实现 + 为什么这么实现

每个子模块两个角度:先讲怎么实现(落到代码 / DOM selector / 协议字段),再讲为什么这么实现(替代方案为什么不行 / 选这条路省了什么坑)。

04.1 · CDP 注入 + 流式回显(核心创新)

怎么实现

CDP(Chrome DevTools Protocol)是 Chrome / Edge / Electron 用来跟 DevTools 通信的协议。VSCode 是 Electron 应用,本质是 Chromium —— 启动时加 --remote-debugging-port=19222,就能用 CDP 远程操控里面任何 webview。

第一步 · 找到 chat tab:GET http://127.0.0.1:19222/json 拿所有调试目标列表。Claude Code 的 chat 面板是 type=iframe 的目标,URL 形如 vscode-webview://<hash>/index.html?id=<webview-uuid>&extensionId=Anthropic.claude-code。每个 chat 内部嵌套两层 frame —— 外层是 VSCode 的包装(没有 input/button),内层 name="active-frame" 才是真 React UI,CDP 操作要钻到内层。

第二步 · 把消息打进去:通过 CDP WebSocket 连上目标,Page.createIsolatedWorld 在内层 frame 建隔离 world,跑这段 JS:

(() => {
  const input = document.querySelector(
    'div[role="textbox"][aria-label="Message input"]'
  );
  input.focus();
  document.execCommand('selectAll', false, null);
  document.execCommand('delete', false, null);
  document.execCommand('insertText', false, <消息>);
  document.querySelector('button[class*="sendButton"]').click();
})()

第三步 · 把流式响应捞回来:Claude Code 的对话存在 ~/.claude/projects/<encoded-cwd>/<session-uuid>.jsonl(NDJSON,每行一个事件)。注入消息后,后端按节奏 stat 这个文件的 mtime,有新行就读出来 → SSE 推前端。

为什么这么实现

项目最初的 v1 想法很直白:web 后端每条消息 spawn 一个 claude -p --resume <sid> 子进程,stdout 转 SSE。代码 100 行就能跑。这条路撞了三堵墙:

v1 · 自己跑 claude 子进程

每条消息 spawn 一个,跟 VSCode 抢 jsonl

VSCode 那边 long-running 的 Claude 也在写同一份 jsonl。第二个 claude 进程读到一份正在被改的文件,内部 hang 死,SSE 永远不关,前端永远转圈。某次系统里同时挂了 5 个 hang 住的 claude,占 1.3GB 内存。

而且每次 spawn 要 3-5 秒冷启动 + 加载几 MB jsonl,第一个 token 出来前的"卡顿"很明显。

v2 · 远程驱动 VSCode

Web 是远程键盘和屏幕,VSCode 是干活的人

VSCode 已经 long-running 跑着 Claude Code,不要再起一份。直接把消息打进它的输入框就好。

没有第二个 claude,没有撞车,没有冷启动;jsonl 永远只被一个进程写。会话状态完全跟 VSCode 一致 —— 这恰好兼容前面"必须 VSCode 做人 review"那条架构约束。

另一个细节:为什么用 execCommand('insertText') 而不是 input.textContent = msg?因为 contenteditable 直接赋值不会触发 React 的 onInput 事件,sendButton 永远 disabled。execCommand 走浏览器原生编辑路径,React 这边正常收到 input 事件。

04.2 · 多 agent 并行 — 多 workspace + 多 chat tab

怎么实现

这部分是同时开多个独立 agent 干活的能力底座。VSCode 一个进程内可以同时存在多个 workspace 窗口,每个窗口里又可以开多个 chat tab。CDP 的 /json 接口能拿到所有窗口里的所有 chat tab,问题是怎么把"web 端的项目"对应到"哪个 VSCode 窗口里的哪个 tab"。

核心机制(实测出来的启发式)

  • 每个 chat tab 在 CDP /json 输出里是一对:宿主 page target + 紧跟着的 iframe target。iframe 紧跟其宿主 page 的输出顺序就是这个启发式
  • 每个宿主 page 的 title 包含 VSCode 当前窗口的 workspace folder 名(形如 " - claude_web - "
  • web 端切到项目 X → 后端 _find_launcher_in_workspace(X) 按窗口标题匹配 → 拿到该 workspace 下可用 chat tab 列表

多 chat tab 并行的部分由 session 缓存承担:每个 chat tab 绑一个 session_id(持久化在 session_webview_cache.json)。web 端同时切换不同 session = 不同 chat tab 各自独立跑,互不干扰、互不抢 jsonl、互不阻塞

为什么这么实现

"多 agent 同时开工"是这套环境的核心能力。举例:

  • tab 1 跑 大型重构(agent 自动改文件 / 跑测试 / 提 PR),需要 30 分钟;
  • tab 2 跑 调研任务(搜代码 / 读文档 / 写技术备忘),需要 10 分钟;
  • tab 3 干 临时小活(重命名变量 / 改文案),需要 1 分钟。

三个 tab 三个独立 session,三个 long-running Claude 实例并行跑,互不干扰。手机上从一个 session 切到另一个就能看到各自进度。这是 web 端能"指挥多个 agent 同时干活"的物理基础 —— 没有这层,"远程指挥"就只是单线程的"远程聊天"。

有了 Claude Code 这种本地 agent + 这套远程指挥层 + 多 agent 并行,能力上不输任何远程 / 云端 AI 工具。云端 AI 没有的:完整本地代码上下文、本机工具链、IDE 级 review;本地 AI 原本没有的:手机随时指挥、多任务并行监工 —— 这套环境补上了后半段。

04.3 · Session 安全 — 不允许 resume

怎么实现

web 端选定 session A,要在 VSCode 里找到 / 创建对应 chat tab,三步绑定:

步骤做什么条件
Step 1
Cache + 静默验证
本地有 session_id → webview_id 缓存,读那个 tab 的 active-frame URL,看 ?session= 参数是不是 A对得上 → 直接注入;对不上 → 清缓存,下一步
Step 2
静默扫描所有 tab
遍历所有 chat tab,读每个 active-frame URL,找 ?session=A找到 → 绑定缓存,注入;没有 → 下一步
Step 3
URI handler 开新 tab
触发 vscode://anthropic.claude-code/open?session=A,snapshot tabs before/after,等新 webview_id 出现真有新 tab → 绑定,注入;没有(URI 把 session 加进了现有 tab)→ refuse

注入完消息之后,后端再做一次反向验证:看哪个 jsonl 文件被刚才的注入触发更新了,确认它的 sessionId == 我们要发的 sid。对不上 → 立即清缓存 + 报错,绝不 retry,绝不 fallback。

为什么这么实现

VSCode 用户的 chat tab 不是一次性的窗口,它承载着用户的工作上下文:思考状态、未发的草稿、滚动位置、刚切到一半的代码。

不允许 resume:web 不能主动 focus 一个已存在的 chat tab、不能改它的 session、不能在它里面调用 /resume 命令。任何"自动恢复 / 重定位 / 跳到另一个 tab"的隐式行为都禁止。

这条铁律跟前面"VSCode 是 review 底座"是一脉相承的 —— 你回到电脑前要看的就是 VSCode 里这些 tab,web 端的便利绝对不能以打断 user 为代价

"宁可让 user 看到清晰错误自己处理,也不要偷偷改了 user 的 tab。"

04.4 · 公网链路 — Caddy + Let's Encrypt + SSH 反向隧道

怎么实现

浏览器 │ HTTPS (TLS 1.3, LE 真证书) ▼ 公网 VPS Caddy :8089 │ flush_interval -1 (禁缓冲,保 SSE 流式) │ default_sni (直连 IP 不发 SNI 也能握手) ▼ SSH Reverse Tunnel 远端 127.0.0.1:18089 ⇋ 本机 127.0.0.1:5000 │ 由本机主动发起隧道,出方向打洞 ▼ 本机 FastAPI :5000 (单一认证入口)

本机只在 127.0.0.1:5000 监听,不开任何入站端口。SSH 反向隧道由本机主动发起 ssh -R 127.0.0.1:18089:127.0.0.1:5000 vps,出方向打通。Caddy 在 VPS 上接 :8089,反代到 :18089(即本机 :5000)。证书 Caddy 自动从 Let's Encrypt 申请并续期,配置 3 行。

为什么这么实现

从手机到本机要走加密通道,路线 4 选 1:

路线评价
A · WebSocket + 自加密WS 协议本身不带加密,"自己写应用层加密 = 重新实现 TLS"。CVE 史上自己写 crypto 基本全军覆没。否决
B · HTTP + IP 白名单密码 + 聊天明文走 ISP / WiFi / 路由,任何中间节点能嗅。下策
C · 自签 HTTPS + Basic AuthCaddy 一行 tls internal,浏览器首次警告点继续。可用但有"高级 → 继续"的心智成本。
D · Caddy + Let's Encrypt + 域名真 CA 证书,浏览器零警告。代价是要个域名。选这个

反向隧道选 SSH 而不是 frp / ngrok / 其他 tunneling tool 也是同样思路 —— SSH 二十年的工具,工业标准协议,本机已经装着,不引入新依赖也不重造

04.5 · 三层登录防护 — 宽锁不严锁

怎么实现

第 1 层 · 凭证

HTML form 登录 → set httponly cookie(用 session_token 文件签名,重启不失效)。同时兼容 Basic Auth,方便 curl / 自检。

第 2 层 · IP 限速

5 分钟窗口内同 IP 失败 10 次 → 临时封该 IP(in-memory)。挡爆破,过窗口自动恢复。

第 3 层 · 单日硬锁

当日累计失败 10 次 → 写入 auth_state.json 硬锁定,跨重启依然锁着。次日 0 点自动重置。

为什么这么实现

关键设计点:正确密码永远放行,锁定只挡错误尝试。这就是"宽锁不严锁"。否则攻击者反复用错误密码触发锁定 → 把合法用户也连带锁出来,锁本身就成了攻击工具。

解锁也要轻:Remove-Item auth_state.json 立即生效,不用重启 server。

04.6 · 移动端体验 — PWA + iOS 适配

怎么实现

问题处理
iOS 浏览器地址栏伸缩100dvh + env(safe-area-inset-bottom)
输入框聚焦 iOS 自动放大输入框 font-size: 16px
Enter 行为冲突桌面 = 发送 / 移动端 = 换行(避免误发)
下拉刷新顶部按下 + 拖 ≥ 70px 释放触发
历史首屏快默认拉 10 条,滚顶 < 80px 自动加载更早
"加到主屏幕"全屏iOS PWA meta + manifest.json + 黑底白字 SVG icon
流式渲染防闪烁流式期间 textContent 写入;content_block_stopmarked.parse 转 markdown

为什么这么实现

手机端是这工具的主战场。每一条都是真用户在某次操作时摔过的坑 —— iOS Safari 输入框聚焦时把页面放大那种小事,PC 程序员根本想不到,但用户每次发消息都被打断一次,体验立刻崩塌。

05实战案例 — 这套环境替我们干过什么

"有了 Claude Code + 远程指挥 + 多 agent 并行" 不是空话,已经在干真实的活。下面是几个有代表性的实际案例,全部由 Claude 在 VSCode 里执行,多数环节通过 web 端远程下达。

案例 1 · 这个 web 工具本身从 0 到上线 本机 → 公网

FastAPI 主程序 + cdp_helper 模块 + 单页前端 + 启停脚本 + pytest 单测 + 一系列 dev_probe 调研脚本 —— Claude 写。Caddyfile 配置、Let's Encrypt 申请脚本、SSH 反向隧道命令、systemd unit、本机自启 —— Claude 写。我做的事:定方向、看实现、提反对意见、做产品决策("宽锁不严锁"、"不允许 resume"、"web 是 VSCode 远程延伸而非替代")。

✓ 公网可访问、HTTPS 真证书、移动端 PWA 加桌面,1 天搞定,全程 Claude 写代码 + 我审 diff。

案例 2 · 调研 VSCode webview 内部结构 逆向 / 探索

CDP 注入这条路没有官方文档。Claude 自己写了一系列 probe 脚本(dev_probes/probe_cdp.py / find_target.py / find_session_id_path.py / deep_probe.py / inject_test.py)—— 列 CDP target、找 chat 面板、定位 React 输入框 selector、试不同 JS 注入方式、反推 session_id 在哪个 URL 参数里。这是从 0 摸索一个未文档化系统的全过程。

✓ 完整摸出 vscode-webview 双层 frame 结构 + DOM selector + execCommand 这条注入路径,对应代码沉淀进 cdp_helper.py

案例 3 · 推 prod 部署到云服务器 运维 / SSH

本机另一个项目(fastapi_www)需要部署到腾讯云 Ubuntu 服务器。Claude 干的全套:rsync dry-run 给变更清单 → 等用户授权 → 真推 → SSH 上去重启 systemd 服务 → curl 全部关键路由测状态码 + 拉前几 KB HTML 自己读判断"看起来正常吗" → 异常时立即触发回滚。整套部署人不用碰命令行,只看 Claude 的报告做 yes/no 决策。

✓ rsync + systemd + logrotate + LE 续期一整套生产运维流程,全部由 agent 编排执行;用户只做"批准 / 拒绝"。

案例 4 · 这篇文章本身的发布 web → admin API

就是你正在读的这一篇。前几版从 dev 本地预览到 prod 上线全是 Claude 写文章 → reupload 到线上 admin API → 验证 19/20/21 项命中。中间踩过 PowerShell 中文丢失、UTF-8 BOM、token 必须 hash 后传 Authorization 等坑,Claude 自己排错自己修。每一版重新组装的 HTML 模板、CSS 命名空间、内嵌 webp、admin API multipart upload —— 我只负责给反馈"这一段太技术了换个写法"。

✓ 多轮内容迭代(v1 4222 字 → v6 8000+ 字),多次 reupload 全部 200 OK,sanitizer / 自动 word_count 重算 / 命名空间 rescope 都正确。

案例 5 · 训练机环境部署 + SFT 任务全流程 训练 / GPU

一台多卡训练机从裸 Ubuntu 到跑出 SFT 模型,整个生命周期都通过这套流程走 —— 不用一次次坐到机器前。

  • 环境部署:装 CUDA / cuDNN / Python deps、配 conda 环境、拉 pretrained 权重 + dataset、跑 sanity check(每一步 Claude 都给出命令 + 解释 + 异常排查)
  • 训练脚本搭建:DeepSpeed / accelerate 配置、batch / lr / warmup 调参、checkpoint 策略、val loss 监控埋点
  • 方案讨论:选 base model、要不要加 LoRA、val loss 平台期是不是该 early stop —— 这些决策也通过对话推进,agent 给数据 + 利弊,我做拍板
  • 任务执行 + 监工:起训练、配定期 health check、异常 alert;文章开头那张实拍图就是这个流程末端"监工"环节 —— 训练已经稳定在跑,出门后用手机随时问"机器啥负载了"

✓ 训练全周期(环境 → 脚本 → 方案 → 执行 → 监工)都靠这套远程流程走完,从"必须坐机器前"变成"手机也能推进"。Claude 干活、跑命令、读输出、排异常,我做决策,整套训练 setup 出现在 chat 历史里也是天然文档。

案例 6 · 训练任务监工(文章开头那张图)长跑任务

这是案例 5 末端的实拍延伸。训练已经稳定在跑,出门前问"机器啥负载了" → Claude ssh 上训练机,nvidia-smi + free -h + uptime,渲染表格回报。一晚上中间醒了几次都问一下,第二天到家直接看 final loss + 决定要不要 evaluate。不再需要 RDP / VPN 远程登服务器看终端

✓ 长跑任务监工成本从"开 VPN + RDP + 终端 + nvidia-smi 一连串操作"降到"手机一句话"。

06真实踩过的坑 — 6 个值得记下来的

HTTP header 不能塞中文

WWW-Authenticate: Basic realm="Claude 登录" 直接 500。HTTP header 只能 latin-1,中文字面量抛 UnicodeEncodeError。realm 必须用纯 ASCII。

Caddy 必须 default_sni

直连 IP 不发 SNI,没有 default_sni 配置 TLS 握手就失败,浏览器只看到 ERR_SSL_PROTOCOL_ERROR。

SSE 被 Caddy 缓冲

没加 flush_interval -1 时,反代会等响应结束才下发 —— 流式协议变成"等完整回复一次性出现"。一行配置救命。

contenteditable 直接赋值不触发事件

input.textContent = msg React 收不到 input 事件,sendButton 永远 disabled。必须用 document.execCommand('insertText', ...) 走原生编辑路径。

同 sid 多个 claude 进程撞车

v1 时代,web 起的 claude --resume X 跟 VSCode 那边的 claude 抢同一份 jsonl,第二个 hang 死。v2 改成 CDP 注入后这个问题从根上消失了。

iOS PWA manifest 严格

iOS 17+ Safari 解析 manifest 极挑剔:"purpose": "any maskable""sizes": "any" 都会让整份 manifest 作废 fallback 到普通书签 —— 加到主屏幕变成截图。具体到 sizes 数字、避开双 purpose 才 OK。

07方法论 — 几条可复用的判断

  1. 共用文件系统当总线。web 端和 VSCode 通过同一份 jsonl 通信,不需要任何同步协议、不需要数据库、不需要 polling。文件系统就是 message bus。
  2. 不重造 TLS。"自己写应用层加密"是经典反模式。Caddy + Let's Encrypt 一行配置拿到工业级 TLS 1.3 + 真 CA 证书,工作量比"WebSocket + 自加密"少十倍。
  3. 远程驱动而非自己跑。如果一个进程已经在系统里干着这件事,你的工具应该驱动它而不是再起一份。多进程抢同一份状态是分布式系统的经典坑,能避就避。
  4. 不打断用户的工作上下文。"resume 别人的 tab"看似省事,实际是把 user 的思考状态、未发草稿、滚动位置都偷偷改了。宁可清晰地报错,不要静默地越权
  5. 宽锁不严锁。任何"失败次数 → 锁定"的设计,锁定都只能挡错误尝试,正确凭证必须永远放行。
  6. 幂等运维脚本start.ps1 内部先调 stop.ps1 清场再起,重复运行不双开。开发期最频繁的动作是"改代码 → 重启 → 验证",这个回路必须无脑。

08结语 — AI 协作模式给我的更深感受

从实际使用感受来说 —— 出门、躺床、地铁、开会、出差、坐高铁、临时加塞排查 prod,全都用得上。它改变了我什么时候能对 AI 下达任务这件事:以前是"坐到电脑前 = 能干活",现在是"任何有手机信号的地方 = 能干活"。多 chat tab 并行更进一步 —— 一个 agent 在跑长任务的同时,我可以从手机随时让另一个 agent 干别的,同一时刻多条线并发推进,效率不是单纯的"快 N 倍",是"原来一次只能推一件事,现在能同时推三件"。

技术上最有价值的发现是 CDP 注入这条路 —— 它不止能远程控 VSCode,任何 Electron 应用都行。Slack、Discord、Notion 都是 Electron,理论上都能远程驱动。

但更想分享的是这次开发本身的体验。1 天搭出来一个完整的、能跑公网、有审计、有防爆破、能多 agent 并行、移动端能 PWA 加桌面的工具,放在两年前光是写需求文档都不止一天。这次我做的工作主要是:定方向、看实现并提反对意见、给真实场景反馈、做产品决策。角色从"写代码的"变成"做决策的"。这个变化比"AI 让我写代码快 10 倍"更深刻 —— 它让我把心思花在对方向、定原则、看反馈上,而不是花在循环写"绑定 listener、注册路由、写测试用例"那些重复劳动上。

同时也要诚实说一句:AI 写得快,但不等于不需要 review。每次 reupload 之前我都在 VSCode 里看 diff、看 git status、跟旧版本比、跑一下确认渲染对。这恰好就是上面"必须 VSCode"那套逻辑的实战验证 —— 没有 IDE 级 review 工具,AI 写得再快也不敢 ship。

限制和后续可做的事也清楚:

  • SSH 反向隧道断开不会自动重连(autossh 可解)
  • 本机重启不会自启 fastapi(Task Scheduler 可解)
  • VSCode 必须开着 + 必须用 CDP 启动方式 —— 接受这个前提的代价就能拿到所有的好处
  • CDP 端口本机任何进程能在任何 VSCode webview 跑 JS —— 单用户机器接受,多用户/不可信环境别开

整个工程不大:server.py 约 1500 行,cdp_helper.py 约 600 行,单页前端 33KB。技术栈 FastAPI + uvicorn + asyncio + websockets,部署 Caddy + SSH 反向隧道。但里面踩过的每个坑、做过的每个决策,都是真实工程里"不重要的细节决定能不能用"那种东西。希望对正在做类似事情的人有用。