| name | chat-plus-adapter-debugger |
| description | 为 Chat Plus 新框架编写、修复、审查站点适配脚本。先读取完整 builtin://chat-plus-adapter-debugger/SKILL.md 获取 skillToken;这次读取 SKILL.md 不需要 skillToken。不要用正则或局部读取只抓 token。后续调用不带或带错 skillToken 会报错并必须重试,所以正式调试前先拿到正确 token。 |
Chat Plus Adapter Debugger
目标
产出或修复一段可直接粘贴到 Chat Plus 的站点适配脚本,并满足新框架硬规则:
- 最外层必须
return { ... }
- 必须包含
meta
- 必须包含四个 hook:
transformRequest
extractResponse
decorateBubbles
continueConversation
decorateBubbles 必须使用 ctx.helpers.ui.decorateProtocolBubbles(...)
continueConversation 必须使用 ctx.helpers.plans.dom(...)
continueConversation 必须使用 ctx.continuationText
extractResponse 必须返回 responseContentPreview
transformRequest 要么使用 ctx.helpers.buildInjectedText(...) 改写请求,要么明确 return null
- 输出抓取必须基于真实协议 / 真实响应,不接受用消息 DOM 反向抄正文代替
extractResponse
- 输入实现允许两条路:
- 优先请求层协议注入
- 协议层因加密、签名、opaque body 或其他原因无法稳定改写时,退回 DOM 发送
- 生成脚本时必须使用平台 helpers:JSON 解析、文本转换、协议注入、气泡装饰、续写 DOM plan 都不要手写替代实现
使用 chrome-cdp
准备页面调试前,先确认工具列表存在 chrome-cdp,并读取 builtin://chrome-cdp/SKILL.md。本 skill 的执行入口可直接代理常用 CDP 调试命令,也可用更贴近 Chat Plus 工作流的别名。
只有用户明确允许启动浏览器调试会话后,才使用浏览器调试命令。MCP Gateway 内置 chrome-cdp 默认通过原生 Chrome DevTools Protocol 启动或复用独立的持久 profile Chrome,不连接用户已经打开的 Chrome 调试端口。
常用别名:
capture start
capture clear
network search <filter>
network get <request-id>
network perf
等价 CDP 命令:
netclear
net <filter>
netget <request-id>
perfnet
html [target] [selector]
snap [target]
evalraw <target> <method> [json]
如果 chrome-cdp 启动或浏览器连接失败:
- 运行
stop
- 再运行
open <url> 或 launch <url>
- 不要要求用户关闭已有 Chrome 或已有 DevTools 调试端口,除非用户明确要求连接既有浏览器
抓样本流程
按这个顺序抓样本。不要把 URL、Performance index 或页面 DOM 文本当作稳定 request id。
- 打开 / 选中目标页面,并确认处于用户真实登录态和真实使用模式
- 执行
capture start 或 netclear,让 CDP Network 从用户操作前开始记录
- 告诉用户现在只需要在页面里手动发送一条唯一探针文本,例如
CHATPLUS_ADAPTER_PROBE_YYYYMMDD_001
- 用户回页面手动发送这条探针文本
- 等用户明确回复:
发好了
- 执行
network search <关键词> 或 net <关键词> 搜索 CDP Network 记录
- 按 URL / method / status / MIME / timing / body 线索找真实发送请求
- 复制
net 输出里的 CDP request id
- 执行
network get <request-id> 查看请求体、响应体、状态码、content-type 和传输类型
- 根据
network get 输出总结协议结构,不复述敏感 headers
- 只有当 body 太长、输出被截断、需要离线解码二进制/长流式内容时,才追加
--request-file / --response-file 保存到临时文件后再读取
- 如果首条消息创建了新会话或跳转,必须在新会话页重新执行
capture start,让用户发第二条 follow-up 样本
硬要求:
- 不要代用户发送首条探针消息
- 不要在用户说
发好了 前假设请求已经产生
- 不要把“让用户自己开 DevTools 自己找请求”当默认流程;如果已获许可,优先由 AI 用
chrome-cdp 直接看
netget 输出可能包含 Authorization、Cookie、设备 ID、会话 ID;最终回答和中间总结都不要复述这些值
Request Id 规则
允许的查询方式:
- 操作前先执行
capture start 或 netclear
- 操作后执行
network search <url-fragment>、network search <api-path>、network search <method>、network search <mime> 或其它从候选里来的关键词
- WebSocket 请求同样先用
network search <关键词> 定位,再用 network get <request-id> 读取 frames
- 如果
network search 没有结果,用 network perf 或 perfnet 辅助确认是否漏开 Network、页面是否跳转、请求是否发生在 worker / service worker
禁止的反推方式:
- 不要把 Performance URL、Performance index 或 DOM 文本当作稳定 request id
- 不要在没执行
capture start / netclear 的情况下期待 CDP Network 有用户操作前的历史记录
- 不要扩大到 console、storage、历史请求或页面气泡里猜 chat 请求
- 不要在未确认 request id 前读取 body 或生成 adapter
证据层
抓取必须按三层证据链推进:
- CDP Network 层
- 用
network search <filter> 搜索真实 request id
- 用
network get <request-id> 补齐浏览器层原始请求体、响应体、状态码和 content-type
- PerformanceResourceTiming 层
- 用
network perf 查看发送后新增资源
- Performance URL 只是候选线索,不是 request id
- DOM 层
- 用
html / snap 确认用户消息、AI 消息、输入框、发送按钮、模式开关和消息气泡选择器
- DOM 文本不能替代
extractResponse 的真实响应协议
URL 已知但 Body / Response 缺失
先分类,不要直接归因为站点没有发请求:
- URL 是错的:常见误拿
OPTIONS、埋点、标题生成、历史同步、配置、心跳、模型列表、附件上传
- URL 是对的,但
netget 输出被截断或 body 太长:才用 request id 和 --request-file / --response-file 保存到临时文件
- URL 是对的,但不是普通文本:按真实 transport 解析;不能稳定改写请求时,
transformRequest 返回 null
- URL 是对的,但首条消息重建页面或会话:进入新页面后抓第二条样本
- URL 是对的,但 CDP Network 没有历史:重新
capture start 后再让用户发送
netget 看到的是网络 body,不是 F12 Preview。网络 body 可能包含应用层 framing / transport envelope,必须按协议层次还原。
网络 body 解码顺序:
- 先看
netget 里的 status、content-type、content-encoding、request/response headers,不要只看文件扩展名
- 如果是
application/json 或纯文本,按 UTF-8 直接读
- 如果是 SSE / NDJSON,按文本流逐行解析
- 如果 UTF-8 打开时在 JSON 前后出现
\0、控制字符、�、、 等字节,先检查是否是 length-prefixed JSON 帧
- 如果 payload 是 protobuf / MessagePack / CBOR / 压缩内容,就按对应协议 decoder 处理;不要盲目删除前几个字节
模式差异处理
有些站点存在会明显改变请求结构、响应结构或消息 DOM 的模式开关,例如:
- 思考 / 深度思考
- 联网搜索 / 智能搜索
- 专家模式 / 快速模式
- 其他会影响消息渲染或发送链的开关
遇到这种情况时,必须先确认用户实际使用的是哪种模式,再决定抓样本和写 adapter。
硬要求:
- 不要默认把“无思考”样本当成“有思考”也可用
- 不要默认把“无搜索”样本当成“有搜索”也可用
- 不要只因为基础聊天能工作,就假设带模式开关时 DOM 结构不变
- 如果存在思考 / 深度思考模式,
extractResponse 必须排除思考文本,只提取最终 assistant 正文和需要保留的协议块
- 如果页面存在思考组件,
decorateBubbles 的目标选择器必须避开思考组件,不把思考组件纳入协议卡片渲染范围
默认策略:
- 如果用户明确会使用思考模式,就优先在思考模式开启时调试
- 如果用户明确会使用搜索模式,就优先在搜索模式开启时调试
- 如果用户会同时使用多种模式,优先按“最复杂、最接近真实使用”的组合抓样本
- 如果用户不使用这些模式,可以按当前关闭状态调试
硬规则
1. 只接受新框架写法
- 不要接受手写
renderProtocolCard、getProtocolCardTheme、detectToolResultTone 这类旧 UI 辅助函数
- 不要接受旧版“自己拼 details 卡片”的
decorateBubbles
- 不要接受
continueConversation 自己 click() / dispatchEvent() / setTimeout()
- 不要接受硬编码
[CHAT_PLUS_...] 协议标记
- 不要接受缺
meta、缺 adapterName、缺 capabilities
- 不要接受
JSON.parse(bodyText) 直接解析请求体,必须走 ctx.helpers.json.parse(bodyText)
- 不要接受自己拼接注入文本,必须走
ctx.helpers.buildInjectedText(...)
- 不要接受自己把
ctx.bodyText / ctx.responseText 转字符串,必须走 ctx.helpers.text.toText(...)
meta.contractVersion 必须是整数,按当前 Chat Plus 项目范式优先用 2
meta.capabilities 必须用对象写明四类能力,不要写成数组、字符串或空对象
2. 四个 hook 的职责不能变
-
transformRequest(ctx)
负责请求层注入。输入注入优先走协议层。请求可稳定改写时,必须用 ctx.helpers.text.toText(ctx.bodyText) 取文本、用 ctx.helpers.json.parse(bodyText) 解析 JSON、用 ctx.helpers.buildInjectedText(...) 生成注入文本。请求不可稳定改写时,必须保留 hook 并 return null。
-
extractResponse(ctx)
负责从真实响应里提取 AI 正文。输出抓取必须走真实协议 / 真实响应。不要从页面现成消息 DOM 倒推正文,当成响应提取。必须保留协议块,不要把 toolCall / toolResult / codeMode 过滤掉。必须返回 responseContentPreview。不要随意 slice(...)。
-
decorateBubbles(ctx)
负责隐藏用户注入块,把可见协议块变成统一卡片,并保留协议块之外的普通文本。必须直接走 ctx.helpers.ui.decorateProtocolBubbles(...)。
-
continueConversation(ctx)
负责返回 DOM 发送方案。必须直接走 ctx.helpers.plans.dom(...)。必须使用 ctx.continuationText。不能自己执行真实 DOM 副作用。第一次 send 默认用 mode: "click",只有确认站点必须 Enter 时才改。
3. 协议块语义
toolCall 默认只展示,不等于自动执行
- 真正可自动执行 / 手动执行的是
codeMode
codeMode 卡片的手动运行按钮、卡片容器、源码节点,统一由平台 helper 负责,不让站点脚本重复造轮子
工作流程
模式 A:生成或修复 Adapter
按下面顺序工作:
- 确认目标站点 URL
- 拿到真实请求样本
- 拿到真实响应样本
- 如果是流式响应,拿到完整流式样本
- 拿到用户消息、AI 消息、输入框、发送按钮的 DOM 信息
- 先判断
transformRequest 能不能稳定改写
- 再判断
extractResponse 怎么提取正文和协议块
- 再给出
decorateBubbles
- 最后给出
continueConversation
- 自检是否符合全部硬规则
如果样本不足,先指出缺什么,不要装作信息足够。
模式 B:审查现有 Adapter
不要只看四个函数在不在。必须逐项检查:
meta 是否完整
transformRequest 是否使用 ctx.helpers.buildInjectedText(...) 或明确 return null
extractResponse 是否返回 responseContentPreview
decorateBubbles 是否直接使用 ctx.helpers.ui.decorateProtocolBubbles(...)
continueConversation 是否直接使用 ctx.helpers.plans.dom(...)
continueConversation 是否使用 ctx.continuationText
- 是否还残留旧卡片函数或协议硬编码
输出顺序固定:
- findings
- 证据不足 / 假设
- 总评
抓样本时必须确认的内容
请求侧
- 用户真实输入在哪个字段
- 如果是消息数组,最后一条 user 消息怎么定位
- 请求体是不是 JSON
- 是否有签名、加密、opaque body,导致请求层不能稳定改写
- 如果请求层不能稳定改写,是否应改为 DOM 发送链兜底
响应侧
- AI 正文在哪个字段
- 是否是 SSE / EventSource / WebSocket / 其他流式格式
- 协议块是混在正文里,还是单独字段
- 是否存在 answer / thinking / summary 多阶段
- 思考 / 搜索模式开启后,响应事件格式是否发生变化
- 思考字段、思考事件、reasoning delta、thinking block 必须和最终正文分开;不要把思考内容拼进
responseContentPreview
codeMode begin/end 是否能完整保留
- 输出是否能直接从真实响应事件 / 响应体提取,而不是依赖页面已渲染 DOM
DOM 侧
- 用户消息容器
- AI 消息容器
- 思考区块 / 搜索区块 / 引用区块是否插入到 assistant turn 内部
- 思考区块选择器必须单独识别,并从
assistantSelectors / 协议卡片装饰范围中排除
- 输入框
- 发送按钮,或 Enter 发送目标
- 输入框赋值后是否需要
input / change
生成脚本时的强制要求
meta
必须包含 contractVersion、adapterName、capabilities。默认能力结构按真实样本填写,常见 JSON 请求体 + SSE 响应 + helper 卡片 + DOM 续写应写成:
meta: {
contractVersion: 2,
adapterName: "...",
capabilities: {
requestInjection: "json-body",
responseExtraction: "sse",
protocolCards: "helper",
autoContinuation: "dom-plan",
},
},
字段语义:
requestInjection: 请求层注入方式,例如 "json-body";如果请求不可稳定改写,可写 "none",但 transformRequest 必须明确 return null
responseExtraction: 真实响应提取方式,例如 "sse"、"ndjson"、"json"、"websocket"、"grpc-web"、"dom-fallback";优先真实协议,不要默认 DOM
protocolCards: 必须是 "helper",对应 ctx.helpers.ui.decorateProtocolBubbles(...)
autoContinuation: 必须是 "dom-plan",对应 ctx.helpers.plans.dom(...)
transformRequest
- 请求可改时:
- 这是输入侧的优先方案
- JSON 请求体必须用
ctx.helpers.json.parse(bodyText) 解析,不要写 JSON.parse(bodyText)
- 请求文本必须用
ctx.helpers.text.toText(ctx.bodyText) 获取,不要自己转字符串
- 用
ctx.helpers.buildInjectedText(...)
- 不要自己拼接注入字符串
- 返回
applied / bodyText / requestMessagePath / requestMessagePreview
- 请求不可改时:
- 常见原因包括签名、加密、protobuf/二进制、opaque body、不可稳定定位消息字段
- 保留 hook
- 直接
return null
- 后续输入改走 DOM 发送链,不要硬改协议
标准请求体读取和注入模板:
const bodyText = ctx.helpers.text.toText(ctx.bodyText);
if (!ctx.injectionText || !bodyText) return null;
const body = ctx.helpers.json.parse(bodyText);
if (!body) return null;
const originalText = slot.text;
const nextText = ctx.helpers.buildInjectedText(ctx.injectionText, originalText, ctx.injectionMode);
if (nextText === originalText) return null;
slot.apply(nextText);
extractResponse
- 默认返回完整提取文本
- 只能基于真实协议 / 真实响应提取
- 返回
responseContentPreview
- 如果是 SSE / 流式响应,先按真实事件格式重组,再提取正文
- 如果响应里有 thinking / reasoning / thought / analysis 等思考字段或事件,必须过滤掉,只把最终 answer/content 文本放入
responseContentPreview
- 不要把协议块截断
- 不要把页面消息气泡的
innerText / textContent 当作主提取方案
标准响应体读取模板:
const responseText = ctx.helpers.text.toText(ctx.responseText);
if (!responseText) return null;
decorateBubbles
只能写成这种方向:
decorateBubbles(ctx) {
return ctx.helpers.ui.decorateProtocolBubbles({
root: ctx.root || document,
protocol: ctx.protocol,
userSelectors: [...],
assistantSelectors: [...],
});
}
允许传:
normalizeUserText
normalizeAssistantText
beforeRenderUserNode
beforeRenderAssistantNode
- 用于排除思考组件的选择器 / 过滤逻辑
但不要自己重写整套卡片系统。
如果站点有思考组件:
assistantSelectors 应只命中最终回答消息区域,不要命中思考容器
- 如果思考组件嵌在 assistant turn 内部,必须用 helper 支持的过滤/normalize/beforeRender 钩子避开它
- 不要把思考文本当作普通 assistant 文本进行协议块扫描
- 不要给思考组件渲染协议卡片、代码卡片或工具卡片
禁止:
- 自己操作 DOM 创建卡片
- 自己隐藏协议块
- 自己写 details / button / code card
- 绕过
ctx.helpers.ui.decorateProtocolBubbles(...)
continueConversation
只能写成这种方向:
continueConversation(ctx) {
return ctx.helpers.plans.dom({
root: ctx.root,
composerText: ctx.continuationText,
input: {
selectors: [...],
kind: "textarea",
dispatchEvents: ["input", "change"],
},
send: {
mode: "click",
selectors: [...],
waitForEnabled: true,
maxWaitMs: 2000,
},
});
}
输入侧策略说明:
- 优先协议注入
- 协议注入不可稳定实现时,才退回这个 DOM 发送方案
- 不要明明能稳定改请求,还默认只做 DOM 发送
input.kind 必须按真实 DOM 确认后写 "textarea" 或 "contenteditable",不要看见输入框就猜
- 第一次生成
send 时优先 mode: "click",不要默认 Enter;只有真实站点要求 Enter 且有证据时才用键盘发送
禁止:
btn.click()
dispatchEvent(...)
setTimeout(...)
return { dispatched: true }
- 不使用
ctx.continuationText
已踩坑写法纪律
这些不是风格偏好,是生成和审查 adapter 时必须拦截的错误:
| 检查点 | 正确做法 | 错误做法 |
|---|
| JSON 解析 | ctx.helpers.json.parse(bodyText) | JSON.parse(bodyText) |
| 文本注入 | ctx.helpers.buildInjectedText(...) | 手写字符串拼接 |
| 文本获取 | ctx.helpers.text.toText(ctx.bodyText) | String(...)、.toString()、手动解码 |
decorateBubbles | ctx.helpers.ui.decorateProtocolBubbles({...}) | 自己操作 DOM 或重造卡片 |
continueConversation | ctx.helpers.plans.dom({...}) | 自己 click() / dispatchEvent() |
continueConversation.send | 首次默认 mode: "click" | 没证据就默认 Enter |
| 输入框 kind | 先确认 "textarea" 或 "contenteditable" | 对号入座、凭经验猜 |
| 最外层 | return { meta: {...}, transformRequest, extractResponse, decorateBubbles, continueConversation } | 裸函数、对象字面量、漏 hook |
| 协议注入 | JSON 可解析且能稳定定位消息时优先协议注入 | 能改请求却直接退 DOM |
自检清单
输出脚本前必须自己检查:
输出纪律
生成 / 修复 Adapter 时
最终先给:
- 完整 JS 脚本
- 极简说明
- 输入走协议注入还是 DOM 发送
- 如果没走协议注入,为什么不能稳定改协议
- 响应是从哪个真实协议字段 / 事件提取
- DOM 选择器
- 风险点
Review 时
最终先给 findings,不要先说“整体不错”。
禁止做法
- 不要输出伪代码
- 不要把旧框架写法说成“也可以”
- 不要只因为四个函数都在,就说脚本合格
- 不要凭站点名猜字段和选择器
- 不要在信息不足时拍脑袋生成高置信度脚本
- 不要把
decorateBubbles 降级成纯样式函数
- 不要把
continueConversation 写成真实 DOM 执行器
- 不要把输出抓取写成“从聊天气泡 DOM 读文本”
- 不要把思考 / reasoning 文本混入最终
responseContentPreview
- 不要让思考组件参与协议卡片装饰
- 不要在协议可稳定改写时,直接跳过协议注入只做 DOM 发送
- 不要在
transformRequest 里使用 JSON.parse(bodyText)、手写注入拼接、手动字符串转换
- 不要没确认
textarea / contenteditable 就写输入框 kind
- 不要没证据就把续写发送写成 Enter
- 不要输出临时页面注入代码
- 不要复述
netget 输出或临时文件里的敏感 header、cookie、token、设备 ID 或会话 ID
默认结论规则
- 如果一个脚本没用
ctx.helpers.ui.decorateProtocolBubbles(...),就判定不符合新框架
- 如果一个脚本没用
ctx.helpers.plans.dom(...),就判定不符合新框架
- 如果一个脚本在
transformRequest 里直接 JSON.parse(bodyText),就判定需要改成 helper
- 如果一个脚本自己拼接注入文本,而不是用
ctx.helpers.buildInjectedText(...),就判定需要修改
- 如果一个脚本没确认输入框 kind 或没证据就默认 Enter 发送,就判定存在续写风险
- 如果一个脚本缺
meta,就判定不符合新框架
- 如果一个脚本还带旧卡片函数,就判定需要重写
- 如果一个脚本的输出正文依赖页面消息 DOM 抓取,而不是协议 / 响应提取,就判定不符合框架预期
- 如果存在思考模式却把思考文本纳入
responseContentPreview,就判定响应提取错误
- 如果存在思考组件却把它纳入
decorateBubbles 的卡片渲染范围,就判定 DOM 选择器错误
- 如果请求体是可解析 JSON 且能稳定定位用户消息,却没有优先尝试协议注入,就判定不符合输入侧策略
- 如果请求不可改写,不是失败;只要
transformRequest 明确 return null 且 continueConversation 可用,就算符合框架
配套工具
本 skill 只负责产出或修复 Chat Plus 适配脚本;脚本本身、临时样本、抓回来的 request/response 文件不要走 shell_command 的 echo > / Set-Content / here-doc / cat 再自己写文件。
- 落盘生成或修改 adapter 脚本一律走
multi_edit_file,让网关记录变更路径、执行 path guard、产出可审查的 diff。
- 查看样本、临时文件、
netget --request-file / --response-file 产出的内容一律走 read_file,输出带行号、受 allowed directories 约束、二进制自动拒绝。
- 浏览器调试继续走
chrome-cdp,不要试图在 shell_command 里自己起 Chrome 或 node 调 CDP。