git:20260916.b57377e to git:20260925.32ce6c0

83 added, 70 removed. Audit B to B.

---
name: chatgpt-bridge
- description: 用用户本机已登录的 chatgpt.com 会话做两件事:(1) 新建对话,把你不确定/不清楚的技术问题发过去,等回答生成结束,把完整答案取回来;(2) 按关键字找回历史对话,在里面继续追问,拿到确切答案。复用 Chrome 登录凭证(不碰账号密码、不需要 API key),读取走 ChatGPT 官方 backend-api 因此 markdown 完整、不受页面虚拟化影响。触发场景 — 问一下 ChatGPT / 帮我问 GPT / 用我的 ChatGPT 问 / 复查这个结论 / 找回之前和 GPT 聊过的 X / 继续问那个对话 / ask ChatGPT in my browser / 我不确定这个 API 怎么用 去问一下.
+ description: "用用户本机已登录的 chatgpt.com 会话做两件事:(1) 新建对话,把你不确定/不清楚的技术问题发过去,等回答生成结束,把完整答案取回来;(2) 按关键字找回历史对话,在里面继续追问,拿到确切答案。复用 Chrome 登录凭证(不碰账号密码、不需要 API key),读取走 ChatGPT 官方 backend-api 因此 markdown 完整、不受页面虚拟化影响。触发场景 — 问一下 ChatGPT / 帮我问 GPT / 用我的 ChatGPT 问 / 复查这个结论 / 找回之前和 GPT 聊过的 X / 继续问那个对话 / ask ChatGPT in my browser / 我不确定这个 API 怎么用 去问一下。"
agent_created: true
---
# chatgpt-bridge
- 把 ChatGPT 当成一个**可脚本调用的外部专家**:发送你不确定的问题,取回准确答案;
- 或按关键字定位历史对话并在其中继续追问。
+ ## Purpose
- - **发送**走页面 UI(官方接口需要页面计算的 proof-of-work / sentinel token,绕过会被拒)。
+ 把 ChatGPT 当成一个**可脚本调用的外部专家**:发送你不确定的问题,取回准确答案;或按关键字
+ 定位历史对话并在其中继续追问。三条设计约束:
+
+ - **发送**走页面 UI(官方发送接口需要页面计算的 proof-of-work / sentinel token,绕过会被拒)。
- **读取**走 `backend-api`(markdown 完整、含代码块,不受对话 DOM 虚拟化影响)。
- **凭证**复用用户本机 Chrome 的登录态副本,全程不接触密码,也不需要 OpenAI API key。
- ## 何时用 / 不用
+ ## When to Use
- 用:
- - 遇到不确定的技术细节(某个 API 的确切签名、某个标准里的编号、某段代码是否成立),需要**外部权威答案**而不是自己猜。
+ - 遇到不确定的技术细节(某个 API 的确切签名、某个标准里的编号、某段代码是否成立),需要
+ **外部权威答案**而不是自己猜。
- 用户说"帮我问一下 GPT""复查这个结论""找回之前聊过的那个话题再问一句"。
- - 需要在已登录站点上做操作,而 AppleScript 路径不可用(见 `references/transport.md`)。
+ - 需要在已登录站点上做操作,而 AppleScript 路径不可用。
+ - 触发词:问一下 ChatGPT、帮我问 GPT、用我的 ChatGPT 问、复查这个结论、找回之前和 GPT 聊过的 X、
+ 继续问那个对话、ask ChatGPT in my browser、"我不确定这个 API 怎么用 去问一下"。
- 不用:
- - 答案能靠本地代码/文档/搜索确定,没必要消耗用户配额。
- - 只想抓公开网页 → 用 WebFetch。
+ ## When NOT to Use
- ## 快速开始
+ - 答案能靠本地代码 / 文档 / 搜索确定 -> 没必要消耗用户配额。
+ - 只想抓公开网页 -> 用 WebFetch。
+ - 本机 Chrome 中未登录 chatgpt.com -> 无法复用登录态,停下来让用户手动登录后重试。
+ ## Workflow
+
+ **快速开始**:
+
```bash
SK="$HOME/.workbuddy/skills/chatgpt-bridge/scripts"
NODE=<node 绝对路径> # 需要 Node 18+
# 关键:清掉沙箱注入的代理环境变量,否则连不上本地 CDP
AB() { env -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY \
NO_PROXY="127.0.0.1,localhost" no_proxy="127.0.0.1,localhost" "$@"; }
python3 "$SK/bridge.py" up --proxy http://127.0.0.1:7890 # 1. 起桥(无代理需求则 --proxy none)
AB $NODE "$SK/chatgpt.mjs" status # 2. 确认已登录、输入框就绪
... # 3. 干活(见下)
python3 "$SK/bridge.py" down # 4. 必须收尾(会删掉含 cookie 的副本)
```
`bridge.py up` 返回 JSON:`{"status":"up","port":9222,"browser":"Chrome/152..."}`。
- `status` 里 `loggedIn: true` 才继续;为 `false` 说明用户 Chrome 里没登录,停下来告知用户手动登录后重试。
- ## 工作流 1:新建对话提问,取回答案
+ **工作流 1:新建对话提问,取回答案**
```bash
AB $NODE "$SK/chatgpt.mjs" ask "<你的问题>" --new --out /tmp/ans.json --timeout 240000
```
- `--new` 会先跳到 `chatgpt.com/` 开一个新对话再提问。**回答较长时务必用 `--out`**:
- stdout 只打印摘要,全文写进 JSON 文件,再用读文件工具取,避免命令行输出被截断。
-
- 返回(同时写入 `--out`):
-
- ```json
- { "ok": true, "matchedLastUserMessage": true, "conversationId": "...",
- "title": "...", "url": "https://chatgpt.com/c/...", "answerChars": 2353,
- "answer": "<完整 markdown>", "transcriptTurns": ["user","assistant"] }
- ```
-
- **判定标准:`ok: true` 且 `matchedLastUserMessage: true` 才算拿到答案。**
- `matched` 是脚本自动做的强校验 —— 把 API 返回的「最后一条 user 消息」与本次发送的问题做归一化比对,
- 不等则说明读到了别的轮次或发送失败。`ok: false` 时检查 `domFallback` 字段。
+ `--new` 先跳到 `chatgpt.com/` 开新对话再提问。回答较长时**务必用 `--out`**:stdout 只打印摘要,
+ 全文写进 JSON 文件,再用读文件工具取,避免命令行输出被截断。
- ## 工作流 2:按关键字找回历史对话 / 继续提问
+ **工作流 2:按关键字找回历史对话 / 继续提问**
```bash
- # 1) 关键字定位(语义检索,不要求字面命中)
AB $NODE "$SK/chatgpt.mjs" search "网格生成算法 语言选择" --limit 5 --out /tmp/s.json
- # -> items[].conversationId / title / snippet(snippet 是命中的原文片段,可用于人肉确认)
-
- # 2) 方案 A:打开它,读全文
- AB $NODE "$SK/chatgpt.mjs" read <conversationId> --out /tmp/c.json
- # -> 同时生成 /tmp/c.md(markdown 全文,含表格与代码块)
-
- # 3) 方案 B:在里面继续追问(自动跳转到该对话再发问)
+ AB $NODE "$SK/chatgpt.mjs" read <conversationId> --out /tmp/c.json # 另生成 /tmp/c.md
AB $NODE "$SK/chatgpt.mjs" ask "<追问>" --in <conversationId> --out /tmp/followup.json
```
- 检索是**语义**的:搜"读取节点坐标"可能返回标题为「DWG图纸比较方法」的对话。所以
- **不要只看标题就下结论**,用 `snippet` 判断是否真是目标话题;不匹配就换关键字或改用
- `list` 人工挑。`list` 按更新时间列出最近会话。
-
- ## 命令参考
+ ## Decision Rules
| 命令 | 作用 | 关键参数 |
|---|---|---|
| `status` | 当前页 URL / 标题 / 会话 id / 是否登录 / 输入框是否就绪 | - |
| `new` | 跳到新对话 | - |
| `open <id\|url>` | 打开指定对话 | - |
| `ask "<文本>"` | 发送并等待回复,取回答案 | `--new` `--in <id>` `--out` `--timeout` |
| `read [<id>]` | 导出完整会话(API,markdown 保真) | `--out`(另生成同名 `.md`) |
| `list` | 最近会话列表 | `--limit` `--out` |
| `search "<词>"` | 关键字/语义检索历史会话 | `--limit` `--out` |
| `api <path>` | 直接打 authed GET,调试用 | - |
- `--timeout` 是等待回答的上限(默认 300000ms)。实测普通问题 20s 左右返回;
- 超时不代表失败,脚本仍会读一次已持久化的内容并打印 `WARN`。
+ - **拿到答案的判定标准**:`ok: true` 且 `matchedLastUserMessage: true`。`matched` 是脚本做的强校验
+ —— 把 API 返回的「最后一条 user 消息」与本次发送的问题归一化比对,不等则说明读到了别的轮次。
+ `ok: false` 时检查 `domFallback` 字段。
+ - **超时**:`--timeout` 是等待上限(默认 300000ms)。超时**不代表失败**,脚本仍会读一次已持久化
+ 的内容并打印 `WARN`;此时仍以 `matchedLastUserMessage` 为准。
+ - **检索是语义的**:搜"读取节点坐标"可能返回标题为「DWG图纸比较方法」的对话。**不要只看标题就
+ 下结论**,用 `snippet` 判断是否真是目标话题;不匹配就换关键字或改用 `list` 人工挑。
+ - **读优先走 API**:仅在 API 读不到时才用 DOM 兜底。响应形状、选择器与完成判定的实现细节见
+ `references/chatgpt-api.md`。
+ - **失败分流**:超时 / 连不上 / 崩溃循环 / 403 验证码 -> 读 `references/known-issues.md`;
+ 环境相关项(CDP、profile 副本、代理、AppleScript)-> 读 `references/transport.md`。
- ## 必须收尾
+ ## Constraints
- ```bash
- python3 "$SK/bridge.py" down
- ```
+ - **必须收尾**:结束前运行 `python3 "$SK/bridge.py" down`。临时 profile 副本里是**有效的 session
+ cookie**,不收尾等于把登录凭证留在盘上;`down` 会杀掉实例并删除副本。若用户明确要保留调试现场,
+ 用 `status` 查看路径后自行处理,但绝不长期保留。
+ - **发送消息是外部动作**:内容必须是用户确实想问的,不要替用户自由发挥;第一次发送后先汇报,
+ 再继续。
+ - **隐私**:检索/读取用户的历史对话属于隐私数据,只取所需,不要把无关历史内容转述出去。
+ - **只对用户自己的账号、只做用户要求的动作**;遇到验证码 / 二次验证 / 未登录,停下来交回用户,
+ 不要尝试绕过。
+ - 代理、端口、Chrome 路径都是环境相关项,不要硬编码成常量后长期依赖。
- 临时 profile 副本里是**有效的 session cookie**,不收尾等于把登录凭证留在盘上。
- `down` 会杀掉实例并删除副本。若确认用户希望保留调试现场,用 `status` 查看路径后自行处理,
- 但绝不长期保留。
+ ## Stop Conditions
- ## 坑(都已修好,改脚本时别改回去)
+ - `status` 的 `loggedIn: false` -> 停止,告知用户在 Chrome 里手动登录后重试;不要尝试代替登录。
+ - 出现验证码 / Turnstile / 二次验证 -> 停止并交回用户,不尝试绕过。
+ - `--proxy` 指定的代理 curl 实测不通 -> 停止,先让用户修代理,不要反复重试。
- | 现象 | 根因 | 处理 |
- |---|---|---|
- | `ERR_TUNNEL_CONNECTION_FAILED` / 全部请求超时 | 沙箱注入的 `HTTP_PROXY/HTTPS_PROXY` 被 Chrome 采纳 | 启动子进程时剔除代理类环境变量 |
- | 导航随机超时 + 日志 `Network service crashed` | 未加 `--no-sandbox`,Chrome 自身沙箱初始化失败导致网络服务崩溃循环 | 必须 `--no-sandbox --disable-gpu` |
- | CDP `/json/version` 返回空 → 客户端报 `expected value at line 1 column 1` | 本地请求也被代理兜走了 | 客户端加 `NO_PROXY=127.0.0.1,localhost` |
- | 工具调用结束时浏览器一起消失(`exit 137`) | 实例与调用同进程组 | Python 侧 `start_new_session=True` |
- | Cloudflare 403 + Turnstile | `--headless=new` 的 UA 与 profile 里 `cf_clearance` 绑定不符 | 用有头模式(本 skill 默认) |
- | Node 脚本卡满超时才退出 | 用作超时的 `setTimeout` 未 `unref()` | 所有定时器 `unref()` |
- | 按"轮次数增加"判断回答结束永远超时 | ChatGPT 对话 DOM 虚拟化,只挂载最近约 5 轮 | 用「输入框被清空 + stop 按钮出现后消失」,再以 API 校验 |
- | `open`/`new` 后立刻读状态拿不到输入框 | SPA 异步 hydrate,固定 sleep 不够 | `waitForComposer()` 轮询等待 |
- | 在 `eval` 里用 `location.href` 跳转导致该 eval 永不返回 | 导航销毁执行上下文 | 导航一律由 Node 侧 `Page.navigate` 发起 |
+ ## Verification
- ## 边界与安全
+ 交付前逐项确认:
- - 只对用户自己的账号、只做用户要求的动作。发送消息是**外部动作**:内容必须是用户确实想问的,
- 不要替用户自由发挥;第一次发送后先汇报,再继续。
- - 检索/读取用户的历史对话属于隐私数据:只取所需,不要把无关历史内容转述出去。
- - 遇到验证码 / 二次验证 / 未登录,停下来交回用户,不要尝试绕过。
- - 代理、端口、Chrome 路径都是环境相关项:换机器先跑 `bridge.py up` 看返回,失败读
- `~/.workbuddy/tmp/chrome-bridge/chrome.log`。详细排障见 `references/transport.md`。
+ - [ ] `bridge.py up` 返回 `status: up`;`status` 显示 `loggedIn: true`。
+ - [ ] 取回的答案满足 `ok: true` 且 `matchedLastUserMessage: true`(或已说明超时后读到的是 WARN 内容)。
+ - [ ] 长答案经 `--out` 落盘后读取,未依赖被截断的 stdout。
+ - [ ] 已运行 `bridge.py down`;未把含 cookie 的 profile 副本留在磁盘上。
+
+ ## Output
+
+ - **Outcome**:ChatGPT 的完整 markdown 答案(或历史会话全文),落到 `--out` 指定的 JSON;
+ `read` 额外生成同名 `.md`。
+ - **Done when**:`## Verification` 全部勾选,且桥已 `down`。
+ - **Evidence**:`ask` 返回的 `conversationId` / `url` / `answerChars` 与 `matchedLastUserMessage: true`;
+ `read` 生成的 `.md` 文件路径。
+
+ ## References
+
+ 按需加载,不要预先全读:
+
+ - `references/chatgpt-api.md` — 需要接口路径、页面选择器、响应形状或完成判定的实现细节时读。
+ - `references/transport.md` — 需要排障 CDP 连接、profile 副本、Chrome 启动参数或代理时读。
+ - `references/known-issues.md` — 出现超时 / 连不上 / 崩溃循环 / 403 验证码等现象时读。
+
+ ## Scripts
+
+ - `scripts/bridge.py` — 起桥/收桥:把最小 Chrome profile 副本交给一个可抛弃的 Chrome 实例并开
+ CDP 端口。用法 `bridge.py up [--port 9222] [--proxy URL|none] [--window-size WxH]` /
+ `status` / `down [--all]`;`up` 返回 JSON,`down` 删除副本。
+ - `scripts/chatgpt.mjs` — 全部 ChatGPT 操作(`status`/`new`/`open`/`ask`/`read`/`list`/`search`/`api`),
+ 见 `## Decision Rules` 的命令表。
+ - `scripts/cdp.mjs` — 底层 CDP 客户端,被 `chatgpt.mjs` 导入;也可 CLI 直用做临时调试:
+ `node cdp.mjs targets | nav <url> [timeoutMs] | diag <url> [timeoutMs] | eval <js> | eval-file <path>`。