chatgpt-web-bridge · git:20260921.a3206ea · 2026-09-21 · sha256 2cc533e2a8daa062

chatgpt-web-bridge git:20260921.a3206eaB

Immutable. This exact content never changes and is served at /api/v1/blob/2cc533e2a8daa062.

---
name: chatgpt-web-bridge
description: |
  通过 chatgpt-web 桥(MCP / REST)把本地 agent 接入 ChatGPT 网页版:网页端当大脑,本地只做传输与执行。
  触发:「推进/继续网页端会话」「让网页端设计、本地执行」「协作/对等/共同推进」「对等模式/让两边辩一下/协作评审」「共同协作/本地跑任务它维护模型」「用网页端的资料/只读拉取」「把网页版接进 harness」「conversation_id 续接」「网页版比较聪明所以用它」。
  不适用:让网页端直接执行本地 shell/文件操作——桥只承载文本,网页端永远没有本地工具。
---

# ChatGPT Web Bridge

## 快速使用(自然语言即模式)

| 你说 | 模式 | 本地做什么 |
|---|---|---|
| 「推进网页端 XXX 项目/会话」 | A 推进 | 发「继续」式最小提示往前推;纠偏算在内(只给事实不给方案);判断何时真结束,没结束就不停 |
| 「让网页端 GPT 指挥现在这个项目」 | B 指挥循环 | 它出指令 → 本地执行 → 回帖事实到同一 conv → 直到它说完成 |
| 「协作/对等/共同推进 XX」 | C 协作 | **取长补短**:交锋(因模型不同/权限不同有信息差)+ 执行(永远本地,禁止交给网页端) |
| 「用网页端 XXX 的资料/看它怎么说的」 | D 只读 | 只拉取不发送:会话记录、项目文件、记忆——零写操作 |

**会话定位(项目制 + 申请制)**:**所有会话必须落在项目里**。首轮前先 `list_projects` 找最接近的项目 → **向用户确认该项目** → 首轮带 `project_id`(名字即可)——确认前禁止发送。无接近项目 → 报用户处理(桥不能建项目,由用户网页端建或显式批准例外);**独立会话只在用户显式批准时允许**。续接已有会话:指明 conversation_id/标题精确命中,并用 `list_conversations` 的 `gizmo_id` 核对该会话确属项目;目标会话是独立会话 → 报用户决定(迁项目或批例外)。**任何会产生新网页会话的动作(首轮、换 project_id、原会话损坏重建)都必须先向用户说明原因并获批准。**

## 核心原则:谁思考

**网页端是大脑,本地是手和邮差。**

- 网页端持有完整上下文(会话历史 + Project 指令 + Memory)——它的判断依赖这些,不需要在消息里复述给它。
- 本地侧只做三件事:把文字送进正确的 conversation_id、把回文拿回来、(指挥模式下)忠实执行网页端给出的任务。
- **不要替它写执行步骤。** 发「继续」式最小提示,让它自己决定下一步。消息写得越细,网页端思考得越少。

## 适用边界与安全

**触发**:推进/继续已有网页端会话、网页端设计本地执行的回合制循环、「协作/对等/共同」类取长补短(C 协作模式:交锋+本地执行)、conversation_id 专线续接。

**不适用**:

- 让网页端直接执行本地命令或读写文件——桥是文本桥,网页端永远没有本地工具
- 高频自动轮询——这是低频人工节奏
- Chrome 专用 profile 未登录 ChatGPT——先登录再调用,别让请求悬死

**工具边界(没有的恢复原语)**:桥**没有 resume/continue-generation 工具**——生成中断或死生成后不存在「续拉那条回复」的接口,唯一恢复路径是**在同一 conversation_id 里发短催促**(「继续」/「接着上一条写完」),网页端会基于已落盘上下文续写。不要在工具清单里找 resume,找到了也没有(2026-09-14 实证:危机中调 `mcp_list_tools` 找续拉工具是空转)。

**固定成本与限制(结构性,不可优化——定价进决策)**:

| # | 成本 | 实质 |
|---|---|---|
| C1 | 每轮数分钟生成 | 联网+长报告是 ChatGPT 思考本身,回合制本质,省不掉 |
| C2 | 发送 ≥30s 节流、限流冷却 300s | 防账号级限流的必要成本,绕过会连累全部通道 |
| C3 | 长文经 DOM composer 注入 | 不是干净 API——`\n` 截断已修但前端一改就出新坑;长稿拆多条进同一 conv |
| C4 | 网页会话间无真隔离 | 新会话仍共享账号记忆+项目上下文——需盲/隔离的活只能本地子代理 |
| C5 | 桥是 DOM 自动化 | 随 OpenAI 前端演进持续长新坑;事故只能降成本不能清零 |

**安全与验证**:所有写操作(新建会话、删除会话)都是真实网页操作;删除类工具需服务端显式 `W2A_ENABLE_DESTRUCTIVE=1`。本地侧只验证 conversation_id 续接正确、tab 复用正确,不替网页端校验其输出内容的正确性——那是它自己的责任。

## 循环纪律:本地侧是唯一的发动机

网页端永远不会主动发起下一轮——**本地收到回帖不等于回合结束**。每轮拿到回文后,先对照本模式的终止条件判断;不满足就继续发下一轮,直到终止条件命中或用户叫停。「它回完了」不是停止理由,「终止条件命中」才是。

**等回复纪律(2026-09-11 定型,网页端裁决后才收敛的坑)**:

- **长等待是常态**:联网搜索 + 长报告要数分钟到十几分钟。等待本身就是工作的一部分,不是回合结束信号。
- **轮询到有结果为止**:发出消息后对方仍在生成 ≠ 本轮结束。在回合内持续拉取(REST 返回或 backend-api 拉尾部)直到拿到完整回文或明确失败;回合时长、上下文压缩预期都不构成停止理由。拿不准时就拉尾部确认——「还在写」和「写完了」是可观测的两种状态,不许靠猜。
- **阻塞抛给协作者**:本地卡住的点(环境门禁、授权边界、协议歧义、缺工具)先发回同一 conversation_id 求方案,写清「阻塞事实 + 已试过的路径 + 待裁决项」,不要在本地只汇报给用户或写进任务控制就停。网页端看不见本机状态,它要的是可裁决的输入不是进度摘要。
- **本地中断 ≠ 消息未送达**:每个逻辑发送先指定稳定的 `operation_id`(REST 也支持 `Idempotency-Key`);确认和重连沿用原 ID。取消、超时、`not_ready` / `generation_stuck` / `read_throttled` 后先 `get_send_status(operation_id=..., refresh=true)`,按已记录会话与消息 UUID 核实;Chrome 不通时不带 refresh 仍能查本地记录。只有 `not_sent` 可原 ID 重试;`delivery_unknown` / `dispatched` / `reply_received` 均不允许盲目重发,`delivered` / `completed` 转为读取原会话。旧版本或无记录时用 `get_conversation` 核实,`not_found` 不能证明未发送。
- **失败发送会留草稿**:`Composer text verification failed` 时插入内容可能部分留在 composer 里成为该会话的草稿,后续发送连带校验失败——2026-09-15 起桥在发送窗口(type→click→ack)任何失败时自动清 composer 草稿;若仍见校验失败,草稿来自别的原因(人工输入/旧残留),用 `evaluate_script` 清或换新会话(2026-09-14 实证)
- **`Composer text verification failed after retry`**:2026-09-14 前的主因是会话页 composer 遇 `\n` 只插入首段(ChatGPT 前端更新,`Input.insertText` 截断);vendored 源码已改走 `execCommand('insertText')`,若再见此错先怀疑残留草稿或前端又改
- **服务恢复先核对身份**:`start.ps1` 内部调用 `ensure`,检查 REST 健康和真实 MCP 握手,只停止可执行文件、命令行、创建时间及监听归属均验证通过的故障进程。`ready:true + usage_state:unused` 是连接正常但未使用;旧版 `starting` 且三个连接标志为 true 也不表示卡死。venv launcher 与 Python 子进程同名属正常,禁止据此判孤儿、按进程名批量终止或删除仍可能被使用的锁文件。网页“重试”/缺输入框另按页面失败阶段排查;明确 challenge/401/403/429 不循环刷新、不重启健康 daemon。
- **新会话 ≠ 干净隔离**:换 system_prompt 开新会话仍共享同账号、同 Memory、同项目上下文——A/B 生成、盲评这类需要真隔离的活只能用本地子代理,多开网页会话只是表面隔离(2026-09-14 实证:B 侧生成会话混进了盲评消息,评审看到了被评审稿的生成过程)
- **回复未落盘需继续核查**:尾部仍是 user 或 `reply_persisted:false` 只说明当前未读到持久化回复,不能单次判定生成死亡。用 `wait_reply` 观察,并结合网页生成状态、发送记录和完整会话判断;仅在确认已停止生成、该发送已落盘且原任务授权包含继续时,才在原 conversation_id 发新的短催促,使用新 `operation_id`。读取失败、429、空/不完整结果均不构成重发依据;分页先查 total 再定位尾部。
- **桥侧超时 ≠ 网页端死**:`generation_stuck` / 读超时只是桥等不到了,网页端可能仍在正常生成——**尾部有 assistant 消息 ≠ 生成完**(引言节点会先落盘、status=in_progress 持续更新)。`wait_reply` 只在终态(非 in_progress)报 `replied`;若它 timeout 先看 `tail_status`——`in_progress` = 还在写,再 `wait_reply` 即可,**此时催促是错误动作**(2026-09-15 实证:误判死生成发「继续」,网页端实际仍在生成)

- **确认随发送方绑定**:首次向目标发送仍须确认;真正的新 MCP session 或 daemon 重启须重新确认。同一 daemon、同一 session id、同一目标且未被接管/释放时,空闲及 CDP driver 回收不撤销原确认,30 分钟 TTL 只影响向其他发送方提示占用。`send_seq=0` 是监听器实例计数,`WinError 10054` 本身不能证明身份变化;先对照前后 `session_key`。仍需确认时按返回的目标和占用信息处理,禁止自行设 `confirm=true`;生成保护继续有效。

## 四种用法

### A. 推进模式(默认,低频)

把已有网页任务往前推一格:

- 最小提示:`继续` / `按你的结论推进` / `下一步`
- 纠偏只给判断不给方案:`这条线判断错了,事实是 X` —— 让它自己想修法
- 每轮必须带 conversation_id;不知道就先 `list_conversations` 找目标
- 网页端产出长报告时,本地只保存/转发原文,不做二次「翻译」
- **终止条件**(满足其一才停):网页端书面宣布本阶段完成且没有它自己的下一步;它提出需要用户/外部世界才能回答的问题(权限、要买的数据、要你拍板的方向);用户叫停。**它写完一轮报告不是终止。**

### B. 指挥循环(网页端指挥本地)

网页端设计 → 本地执行 → 回帖 → 网页端继续设计:

1. 首轮前先按「会话定位」走完项目确认(`list_projects` → 用户确认 → `chat_completion` 带 `project_id`),存下返回的 `conversation_id` 作为专线
2. 每轮本地回帖只写事实:**结果摘要 + 关键证据 + 阻塞项**,不夹带自己的分析
3. 网页端输出的指令照做,做完把结果发回同一 conversation_id
4. 循环频率低是正常的——这是回合制协作,不是轮询系统
5. **继续义务**:回帖后必须读它的下一条指令并执行——一轮就停等于循环没建起来
6. **终止条件**(满足其一才停):网页端宣布任务完成/无下一轮指令;连续两轮它只复述已有结论不产生新指令;它的指令越权(要本地没有的能力)且澄清后仍无法执行;用户叫停

### C. 协作模式(取长补短:交锋 + 本地执行)

用户说「协作/对等/共同」即进入本模式。两个成分缺一不可:

- **为什么交锋**:两个模型不一样(思考方式不同)、权限不一样(网页端看得见搜索/项目/Memory,本地看得见文件系统/执行/实时状态)——信息差决定了必须先把各自立场摊开再合并,任何一侧单方输出都会丢掉对方的盲区。
- **为什么执行在本地**:网页端永远没有本地工具。需要执行(跑命令/改文件/取真实结果)的动作永远由本地完成,**禁止把执行交给网页端**。

适用前提:**token 不缺、本地智力被认可、两边互有知识盲区**。任何其他场景回到 A/B。

**协议(防坍缩结构,顺序不可省)**:

1. **R0 盲答**:同一问题两侧独立作答——网页端**不许看**本地已写好的答案,本地也别先发自己的立场去锚它。各自交卷后才交换。
2. **对齐盲区**:首轮双方各自声明「我看不见什么」。之后把问题路由给看得见的那侧:网页端要本地数据就问,本地要网页端搜/读项目就问。
3. **逐条交锋**:每个议题独立走状态机 `proposed → accepted / rejected / deferred / escalated`。反驳先 steelman(复述对方论点的最强版本),再给证据反驳;**只有新证据能翻案,自信语气不算证据**。
4. **执行落本地**:议题分出方案后,需要真实执行的步骤由本地完成;网页端只维护判断/模型/裁决。本地上报格式:`[OBS] 任务来源|事前预测(若有)|真实结果(保留原文摘录)|知情度标签`;需要模型修订时发 `[UPDATE-REQUEST]`。
5. **上报节奏**:默认每里程碑一次(一个里程碑 = 一条可判定的局部因果链);例外立即上报(预测失败/关键反证/State 明显错误/足以改变行动/不可逆操作前);无信息增量的中间步骤不上报。每步上报会把维护成本做实,只报失败会造成成功样本偏置——两个极端都不要。
6. **轮次封顶**:默认 5 轮或议题清零。到顶仍分歧 → 产出**分歧报告**(各自立场 + 关键证据 + 僵持点)交给用户裁决。禁止假装共识。
7. **谄媚自检**:连续两轮没有任何实质异议是危险信号不是成功信号——此时主动要求对方「指出我立场里最弱的一点」。

**信封格式**(每轮本地发给网页端的消息):

```text
[PEER R{n}] {议题}
我的立场: …
依据: …
对你上轮的反驳: (先复述你的最强版本)…
我可能盲的地方: …
需要你侧的数据: …
待裁决: …(无则省)
```

**终止**:双方书面确认收敛,或分歧报告交付用户,或用户喊停。**只交换一轮不是对等协作——议题未达终态前本地侧必须继续发下一轮。**

### D. 只读模式(用网页端的资料,不发送)

用途:把网页端已积累的东西当本地输入——会话历史、项目文件、Memory、它的既有结论。零发送、零新会话、零 Memory 写入。

- 通道:`list_conversations` / `get_conversation` / `list_projects` / `get_project_files` / `get_memories`(MCP 走共享 utility 槽,不占会话 tab)
- 单次请求即结束——只读没有循环,拿到资料就地交付
- 拿到长资料本地只做筛选/引用,不改写它的结论
- 触发词:「用网页端 XXX 的资料」「看看它/项目里怎么说的」「把 XXX 会话的结论拉下来」
- **空结果先看 `reason`**:`get_conversation` 返回带 `reason` 字段消歧——`not_found`=后端 404(先核 conversation_id,以 `list_conversations` 的 id 逐字符为准或从 tab 的 `/c/{id}` URL 取)、`empty`=可达但暂无可视内容(常见于生成中)、`fetch_failed`=拉取本身报错、`ok`=正常。历史行为是把 404 `conversation_inaccessible` 静默映射成空——别再把空结果一律当「会话还在」
- **长文自动导出全文**:get_conversation 的长页默认返回 out_file、file_bytes、file_sha256;按行区间或字符切片读该 UTF-8 文件至末尾。单条长消息用 offset 定位、limit=1,可指定 out_file;需要程序直接消费完整 JSON 时才设 max_inline_bytes=0。客户端提示 truncated 或 JSON 单行被截短时,先用现有导出参数,或解析原始 JSON 文件的 messages[].content;只查工具名会漏掉参数。REST 缺失不妨碍正常 SSE 读取;页面 DOM 是虚拟列表,不能用“最后一个/最长一个 turn”认定最新完整计划。返回 partial:true 时文件也只是部分 DOM 内容,不能宣称已读全。
- **真实错误取证**:直连 `127.0.0.1:9222`(browser-level flatten attach,不占用桥的 tab),在 chatgpt.com 页内 fetch `/api/auth/session` 拿 token 后 fetch `/backend-api/conversation/{id}`,看真实 status——区分 404(ID 错) / 429(限流) / mapping 结构变化

## 硬规则

- **一对一专线(申请制)**:一个本地会话只绑定一个网页端 conversation_id,绑定后不得擅自更换。禁止为多路并行/A-B 盲测/分身评审开多个网页会话——需要隔离执行时用本地子代理(各自独立上下文,天然盲),不需要隔离的活本地自己做。**任何新开网页会话都必须先向用户说明原因并获批准**(含首轮——走「会话定位」的项目确认流程;含中途换 project_id、原会话损坏重建)。
- **执行永远本地**:凡是要产出/执行/跑实验的活都在本地完成(含子代理);网页端只承担依赖它自身上下文与判断的角色(推理、评审、用它项目知识的分析),不是并行生成器。「我做不了干净的盲」不成立——本地起个子代理就是干净的盲。
- **转述中立**:把用户任务发给网页端时逐字或中性改写,不自行加预设方向(如「哪里弱/哪里是自我安慰」这类措辞等于替它定了批判结论)。

1. 专线靠 `conversation_id` 续接;首轮创建后可省略(同 session 自动续),跨 session 必须显式带
2. 永不发「记住/以后都/remember」措辞——会污染 ChatGPT Memory
3. 临时上下文塞在 message / system_prompt 里,不落 Memory
4. 同一会话永远落在同一个浏览器 tab(conv-affinity 内置);别手动挪桥用 tab
5. **会话必须落项目**:`list_projects` 取最接近项目 → 用户确认 → 首轮一次带对 `project_id`(gizmo id 或精确项目名,桥会校验,传错直接报错而不是落错项目);未确认不发送,无合适项目报用户,独立会话需用户显式批准
6. 发现发错会话/项目时**不要立刻重发**——先确认前一条是否已落地,否则产生重复会话;确需重发=新开会话,先向用户申请
7. 只读任务在消息里声明「只读测试」;删除类工具需服务端 `W2A_ENABLE_DESTRUCTIVE=1`
8. 生成很慢(联网搜索 + 长报告):客户端超时 ≥15 分钟,中途绝不重发
9. 桥内置账号级跨进程节流(send ≥30s、backend 读 ≥8s、命中限流全体冷却 300s,配置 `request_pace_*`/`W2A_PACE_*`)——别写循环猛发绕过它,限流会连累所有通道

## 通道选择

| 场景 | 通道 | 说明 |
|---|---|---|
| 串行推进会话 | REST `POST /v1/chat/completions` + `conversation_id` | daemon 复用会话 tab |
| agent 工具态 | MCP `chatgpt-web` → stdio `chatgpt-web2api-mcp`(推荐,harness-bound) | `chat_completion` + 读工具 |
| 读会话/列项目 | MCP `get_conversation` / `list_*` | 走共享 utility 槽,不占会话 tab |

## 运维(本机实例)

- 注册:harness MCP 配置用 stdio `chatgpt-web2api-mcp`(随会话生灭,首调自动拉起 Chrome,不用零进程);共享 daemon 才用 SSE `:8090`
- 启动(仅 daemon 模式):`<本仓库>/mcp/chatgpt-web-bridge/start.ps1`(Chrome 独立进程 + REST :8080 + MCP :8090);venv 在仓库外时由 `W2A_VENV` 解析
- 自愈:`chatgpt-web2api ensure`(daemon 挂了/重启后跑一次即可,幂等带锁;stdio 模式不需要)
- Chrome 与 daemon 生命周期解耦:重启 daemon 不动浏览器;Windows daemon 经原生 WMI 进程代理隐藏启动,脱离工具任务的父进程及其 Job。代理创建失败会明确报错,不退回可能随任务退出的后台进程。
- 登录态掉了:在专用 profile 的 Chrome 里登录,daemon 自动恢复
- 排查 tab 绑定:`http://127.0.0.1:9222/json/list`
- 其它设备安装:见 `examples/install-other-devices.md`(桥已 vendored 在 `mcp/chatgpt-web-bridge/`,随本仓库同步)

## 反模式

- 消息里写好全套执行步骤让网页端照做——等于你在思考
- 每轮不带 conversation_id——每轮一个新会话,上下文全断
- 未经用户确认裸发首轮——首开=新开会话,必须先定项目、向用户确认、获批准再发;顺序反了就会产生游离会话(2026-09-14 实证)
- 仅凭尾部仍是 user 或回复尚未落盘,就判定生成死亡并立即催促——应先核对发送记录、完整会话与网页生成状态
- 给网页端发「执行这条 shell 命令」——它没有也不该有本地工具
- 客户端默认 60s 超时——正常生成要数分钟
- 对等模式下让网页端先看你答案再「评审」——锚定的同意不是共识
- 对等模式分歧到顶继续磨——出分歧报告交用户,不伪造收敛
- 收到一轮回帖就当任务结束——终止看条件不看「它这轮说完了」
- 发完消息看到对方还在生成就结束回合交差——等回复是本地义务,轮询到有结果为止
- 本地阻塞只向用户汇报或写进任务控制就停——先把阻塞事实+已试路径+待裁决项发回协作者求方案
- 把「干净 A/B」拆给两个网页会话——盲隔离用本地子代理做,不是多开专线;网页端新开会话仍共享账号记忆与项目上下文,不是真隔离
- 以「我无法盲自己」为由把执行外包——本地子代理的隔离上下文就是盲
- 让网页端充当被测系统的执行角色(如流水线 Writer)——它只承担评审/研究/裁决;被测执行体必须是流水线实际用的那个(本地 subagent),否则实验测的不是目标对象
- 在评审会话里就地完成被评审对象的生成——评审 tab 看得见生成过程,盲评不再盲
- 协作模式下每步都上报——按里程碑粒度,每步上报等于把维护成本做实
- 协作模式下只上报失败——丢失成功预测证据,形成失败样本偏置
- 协作模式下把执行步骤发给网页端让它"做"——执行永远本地,它只做判断/建模/裁决
- 本地侧发送被打断后立刻重发——中断不等于未送达,先核实落盘