# 更新日志 本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。 > **关于仓库改名**:本项目原名 `dsh-chaoxing`(学习通模式), > v2.0.0 起改名 `dsh-xuexi`(网课模式),工具前缀 `cx_*` → `eye_*` / `hand_*`。 > 原因见下面 2.0.0 那一条。 --- ## [2.2.4] — 2026-10-15 **文档全量重写 + 安装脚本改造。代码行为未变,版本号只为把"文档与代码对得上"这件事钉住。** ### 文档 - **`README.md` 整体重写**(面向开源用户,中文):补上目录、这是什么 / 解决什么问题 / 适用与不适用、环境要求、快速开始、典型流程、配置与环境变量、常见问题、贡献、许可证。 - **修掉三处事实错误**(都会让人照着做错事): - 工具分组:原文「手(6)」表内实为 7 项、`hand_read` 归类与 `index.js` 相反 → 统一为 **眼 5 / 手 6 / 兜底 5**,`index.js` 头部注释的「15 个工具」一并改对。 - 「`hand_play` 参数里故意没有倍速」→ **与代码相反**:`rate` 是允许的 (测试 `C7c` 明确断言),只禁 `seek` / `heartbeat` / `jumpTo` / `percent`。 - 「需要一个开了调试端口的浏览器」→ 已过时:`launchBrowser` 会自己发现并启动浏览器, 手动命令降级为可选兜底。 - 安装章节补上真实约束:仅 Windows、DSH ≥ 0.2.0、Node ≥ 20、预设在 CUSTOM 分组、 需打开「通用 → 代码工作工具」。 - 补 `facts/`、`scripts/`、`install.ps1`、`cordis.patch.yml`、`CHANGELOG.md` 进仓库结构。 ### 提示词 - `prompts/xuexi-mode.md` 排版与结构重做,**内容实质不变**: - 修掉「一点五」这个破坏编号体系的标题 → 全篇重编号为 〇 ~ 十一 + 附。 - 修掉引用块里嵌 `###` 的写法。 - 「派复核员」两处指令矛盾(一处 `send_message`、一处 `spawn_teammate`)→ 统一为 `spawn_teammate` → `send_message` → `wait_agent`(只等一次)。 - 「播完 ≠ 记上了」原文写了三份 → 单点留在「九 · 播」,其余改为引用。 - 新增**术语表**:区分 元素编号 `i` / 清单序号 `n` / 选项编号 `o0` / 票据 / 对账清单 / 登记 —— 把 `i` 填进 `picks` 的键会直接报 `ARG`,是最高频的实操错误。 - 工具表分组与 README 对齐(眼 5 / 手 6 / 兜底 5);14 处代码块补上语言标注; 强调符号收敛为 ★(要点)与 ⚠️(禁止/坑)两级,标题不再带装饰符。 ### 安装脚本 - **备份与回滚改为默认关闭**,安装前交互询问(新增 `Ask-YesNo`): 拒绝备份 → 不产生任何备份文件;备份但不保留还原点 → 安装成功后自动删除。 - 新增 `-NonInteractive`(不询问,默认全否)。 - **硬依赖 Windows**:顶部加平台闸门(PS 7+ 判 `$IsWindows`),注释声明不做跨平台抽象。 ### 测试 - `E4` 版本钉 `2.2.3` → `2.2.4`(上一轮 2.2.0 曾漏改这里)。 --- ## [2.2.3] — 2026-10-15 **修掉两类把事实说错的问题:工具替服务端下结论、匹配落空时沉默。** (2026-10-15 真机测试日志暴露,根因逐条定位到了具体代码 / 提示词位置。) ### 修复 - **`hand_play` 谎报"播完就算完成"(高危)**:`finished` 判的是**播放器本地状态** (`ended` / `currentTime` 到末尾),跟服务端有没有记上完全是两件事。 但工具在 `reason:"FINISHED"` 时**无条件**输出"播完就算完成 —— 不用再确认", 把 `lib` 层如实返回的 `markAfter`(还是「未完成」)盖掉了。 后果:模型播完就走,事后发现没记上又回头重播;目录其实早已清零的那些也被重播一遍 —— 实测同一个视频连播三遍,标记仍是「未完成」。 现在按三种情况如实说:标记「未完成」→ 给固定排除顺序(**先刷新一次** → 仍「未完成」才是真没记上 → 换方式做或交给用户),并写明**别原地重播**; 标记「已完成」→ 继续下一条;读不到 → 明说不猜。 顺带修了判定顺序:原写法先判"已完成"再排除"未完成", 而「任务点未完成」本身含"完成"子串,顺序错了会判反。 - **`eye_list({frame})` 名字对不上时静默返回 0(中危)**:`lib/see.mjs` 里 `onlyFrame` 匹配落空就 `continue`,最终报 `count: 0`,模型读成"这一页没东西可点"。 实测正因为此白白绕掉好几步(模型从 `frameUrl` 抄来的名字比 `label` 多一段路径)。 现在落空时返回 `frameMissed: true` + 实际存在的窗口名列表, 与 `lib/hand.mjs` 的 `scrollPage`(同样场景)报法对齐 —— 本文件开头"坑 1"讲的就是"沉默的错误最危险",这条路径此前没落实。 - **CDP 超时被误判成"世界的问题"(中危)**:截图撞上 60s 超时时报的是 "超过 60000ms **没有响应**",既不含"超时"也不含 "socket",于是被归成 WORLD, hint 变成"退回三件万能事:**eye_shot** 看一眼" —— 让模型对着刚卡死的工具重试它自己。 现在 `wrapError` 的判据补上「没有响应 / 无响应」,归到 TOOL, 给出的是"用 `eye_open` 重新接管"这条真有用的。 - **`hand_scroll` 的"慢翻"能力没写进说明(轻)**:每滚一步其实已有 `sleep(400)`, 能力本来就够;模型去试 `dwellMs` 被拒,是因为描述里没提。补了一句说明, **没有新增参数** —— `additionalProperties:false` 下加字段等于动工具契约。 ### 提示词 - **`prompts/xuexi-mode.md` 里还有同一句错误论断的三个副本**:上一批只改了运行时 返回的 hint,而预设里仍写着"播完就放心往下走,不用反复确认"/"播完就算完成, 不用再确认,直接做下一条" —— 措辞比 hint 更强,而且是模型**实际加载**的内容。 三处统一改成「播完 ≠ 记上了」+ 同一个排除顺序 + "别原地重播"。 `lib/hand.mjs` 的 `finish()` 里那句"正常播完就该算完成"也一并改成给出排除顺序 (它同样会随返回值给模型,不改就和新预设打架)。 ### 文档 - 补上 **2.1.0 / 2.2.0 / 2.2.1 / 2.2.2** 四条漏记(此前 CHANGELOG 停在 2.0.1)。 - `facts/平台事实.md`:补"标记是页面加载时渲染的""目录计数与条目标记刷新不同步" "带过期参数的旧直链会落到提示页",并新增第六节「一节底下可能还有好几页内容」。 --- ## [2.2.2] — 2026-10-15 - **修预设「网课模式加载失败」**:上下文压缩三件套此前平铺在 `plugins` 列表里, 现在包进 `cordis:group` + `isolate`。 - **`install.ps1`**:提示改中英双语,并强制写入 UTF-8 BOM(中文不再乱码)。 - **安装校验**:装完逐个比对 SHA-256,不再只数文件个数。 - **省量与闭环**:截图省量、`__need_shot` 闭环、登录改走全窗口;修 README / install 的漂移。 - **健壮性**:socket 死亡不再把调用挂死;浏览器发现支持跨平台;修两个静默点错的场景。 --- ## [2.2.1] — 2026-10-10 - **上下文压缩三件套此前根本不存在**:`/compact` 和自动压缩只写在别处,没有挂进预设。 现在补挂上去。 --- ## [2.2.0] — 2026-10-09 - **把真机测试暴露的三个根因堵住**(见提交 `b538d52`)。 - 修 E4 断言跟上 2.2.0 —— 上一轮只改了 `package.json`,忘了 `test/run.mjs` 里的版本钉。 --- ## [2.1.0] — 2026-10-08 - **按真机探测重做播放 / 滚动 / 看**,并补上「这类活儿的形状」。 - ⚠️ 当时 `package.json` 没跟着改版本号(仍停留在 2.0.0),本条为事后补记。 --- ## [2.0.1] — 2026-10-09 **修复四个实测缺陷(外部代码审查发现,全部带回归测试)。** ### 修复 - **`eye_check({ignore})` 导致复核-提交链路死锁(高危)**:早先 eye_check 按 `buildLedger(areas, ignore)` 算清单哈希,而 `hand_verdict` / `hand_submit` 按 `buildLedger(areas)` 算 —— 只要 ignore 了任何一处挑的题,三处哈希就对不上: 复核登记永远查不到(`NEED_TWO_REVIEWERS` 死循环)、票据永远 `STALE_TOKEN`、 复核员还会被 `REVIEW_INCOMPLETE` 拒掉。现在**口径统一收进 `gate.reviewContext()`**: 题目集合的身份 = 全量清单哈希(ignore 不参与),ignore 按身份存进 session (eye_check 写、另两处读);`hand_verdict` 的"不在清单"按页面上真实有的判、 "没给结论"按这一轮要复核的判。 - **`hand_click` 的提交软保护点完才触发(高危)**:早先 `clickLocator` 真点完之后 才检查目标文字 —— 返回"拦住了"时答案其实已经交上去,还谎报了"没点成"。 现在**点击之前**先只读地看一眼目标(编号 / 文字 / 坐标三种定位都覆盖); 看漏了点完才发现的,如实警告,不再谎报。另:`hand_pick` / `hand_write` / `hand_submit` 成功后旧票据作废(答案变了票据不该继续有效)。 - **`hand_submit` 每次都误报"弹出了确认框"(中危)**:早先拿按钮编号跨两次扫描 求差集,而编号带每次扫描随机生成的 token,永远对不上 → 每次提交都把 "提交后页面的第一个按钮"当确认框报出去,诱导模型乱点。现在改按稳定身份 (tag+文字+href+onclick)比,且跳转到新页面时不报(`gate.newButtons()`)。 - **`hand_click` 的 `settleMs` 参数永远不可用(中危)**:早先把整个参数对象传给 `normLocator`,settleMs 被当成"不认识的键"拒掉(三种定位写法全灭)。现在 定位键和 settleMs 分开传。附带教训:原 A6 测试用越界值 99999 碰巧被 错误检查拦下而"通过",掩盖了缺陷 —— 已补 A39–A41 回归测试。 - **整页截图静默截断**:整页超过 4 段(7200px)时,现在明确告诉模型 "只截到了多少、下面还有多少没截",避免把"没截到"当成"不存在"。 - **小修**:笔记 pending 去重按存进去的截断形态比(>300 字的去重此前失效); `loadState` 不再和模块级 `EMPTY` 共享数组引用。 ### 测试 - 新增 A39–A41(settleMs)、B28–B33(口径统一 / ignore 全链路缺陷复现 / 新按钮判定),断言数 242 → 252。 --- ## [2.0.0] — 2026-10-08 **大重构:从「某个网站专用」改成「通用网页手眼」。** ### 为什么(一次真实事故) 用户换了一个学校的账号,插件**完全不会变通**。日志还原出来的链条: 1. `cx_chapters` 内部**无条件把用户翻好的页面导航走**(去读课程清单)—— 而提示词里白纸黑字写着"不会导航它" 2. 课程清单读取**不等 iframe 渲染**就抛错,于是报 `COURSE_NOT_FOUND` 3. 没有"换浏览器标签页"这个工具,模型没法自己回到用户那一页 4. 通用眼睛那次没看见内嵌窗口里的东西 5. → **原地打转十分钟**,最后用户手动帮它打开页面 根因:**把"某个网站长什么样"写死在工具里,又把"按什么顺序调工具"写进提示词。** 页面一变,写死的全失效;而提示词只教了流程,没教它"认不出来就退回看和点"。 ### 三条宪法 ``` ① 工具只认识屏幕:对账清单、票据指纹、编号,全部从屏幕上现读 ② 格式错在动手之前拦住,且零副作用(ARG/USAGE 失败 = 页面一下都没被碰过) ③ 大脑永远有退路:截图 → 列能点的 → 点一个看变化 ``` ### 工具:16 → 15 | 旧 | 新 | 处置 | |---|---|---| | `cx_open` | `eye_open` | 改名 | | `cx_page` | `eye_see` | **重构**:只报四类区域(挑/写/按钮/媒体),不带业务概念 | | `cx_dom` | `eye_list` | 改名 | | `cx_shot` | `eye_shot` | + `shotId`(可选)+ `frameRects` | | — | `eye_check` | **新增**:交前复核七条 + 票据 | | `cx_click` | `hand_click` | + 跨窗口路由;软保护:点到"提交"类按钮却没过复核会被拦住 | | — | `hand_pick` | **新增**:从页面给出的项里挑(单选/多选/判断/下拉都是它) | | `cx_type` | `hand_write` | 重构成 area 模型 + 写完回读 | | `cx_scroll` | `hand_scroll` | 改名 | | — | `hand_goto` | **新增**:唯一导航,工具绝不自己跳页 | | `cx_tab` | `hand_tab` | ⚠️ 语义变了:翻小节内页面 → **换浏览器标签页**(原来根本没有这个能力) | | `cx_do(play)` | `hand_play` | 拆出 | | `cx_do(answer)` | `hand_submit` | 拆出,**不收答案**,只读屏幕 + 复核登记 | | `cx_note`+`cx_progress` | `hand_note` | 并一个;记账单位改中性词 | | `cx_verdict` | `hand_verdict` | + 身份判定 + 覆盖语义 | | `cx_courses` / `cx_chapters` / `cx_enter` / `cx_nav` | **删** | 网站地图。`cx_nav` 本来就是点一下"下一节" | ### 复核闸门重做 - **删掉 `afterReview` 布尔后门**:旧代码 `不一致 && !afterReview → 拦`, 传个 `true` 就放行,没有任何机制证明"补过特写、重看过"。 现在不一致时**永远不发票据**;放行只能靠复核员重新登记且新结论一致。 - **登记改成"每人一份当前结论、整体覆盖"**:身份由 `ctx.agents` 判定,不靠自报。 (原打算用 `round` 计数防"重登记被当成第三个人",那是补丁摞补丁;改覆盖后 bug 自然消失。) - **登记绑定清单指纹**:换一套题自动作废,常驻复核员不会串题。 - **票据绑定页面指纹**:内容变(点了/写了/翻页)才作废,滚动和截图不算 —— 防空转。 - **对账清单只给结构和编号,不给文字**:DOM 文字可能被搅过,给复核员乱码只会带偏它。 ### 新增的基础设施 - `lib/args.mjs` —— 参数校验层:**纯函数、零副作用**。还带"键白名单", 模型写 `{题号:1}` 这种业务概念会被当场点出来。 - `lib/gate.mjs` —— 复核七条 + 票据 + 登记簿,全是纯函数,可单独测。 - 四类错误 `category`:`ARG` / `USAGE`(页面没动)/ `WORLD`(屏幕上没这东西)/ `TOOL`(浏览器出问题)—— 让模型一眼看出**该改自己还是该改看法**。 ### 提示词:348 行 → 约 190 行 - 删掉:网站页面长相、`aria-label`、字体混淆、播放器类型、课程→单元→小节结构、全部踩坑史 - 挪到:`facts/平台事实.md` + `hand_note`(经验,不是事实;下次先看一眼再信) - 留下:判断依据、能力边界、铁律、方法 - 新增:每个工具一行「干什么 / 怎么写」,让模型学会调用 ### 其它 - 仓库 / 包名 / `dsh.id`:`dsh-chaoxing` → **`dsh-xuexi`** - 预设:`chaoxing` / 学习通模式 → **`xuexi` / 网课模式** - 过程文件:`<工作区>/.chaoxing/` → `<工作区>/.xuexi/`;机器状态 `~/.dsh/chaoxing` → `~/.dsh/xuexi` - 删掉 `lib/observe.mjs` / `lib/inventory.mjs` / `lib/act.mjs` / `lib/state.mjs` / `lib/check.mjs` (内容拆进 `see.mjs` / `hand.mjs` / `note.mjs` / `gate.mjs`,网站相关部分全删) - 测试重写:参数校验(含零副作用的结构保证)、复核闸门、工具表面、痕迹检查、预设自检 ### 补记的欠账 下面 1.2.3 → 1.5.0 五条是本次补写的(当时只发了版本没写日志)。 --- ## [1.5.0] — 2026-10-08 - **把"三方独立判题"从提示词请求升级成硬关卡**:新增判题员专用工具 `cx_verdict`, 提交时检查"至少两个**不同**判题员登记过",不够就 `NEED_INDEPENDENT_JUDGES`。 - 三份结论不一致 → `JUDGES_DISAGREE` 拦下,要带 `afterReview: true` 才放行。 > ⚠️ **这个 `afterReview` 是个后门**(传 true 就能过),2.0.0 已删除。 - `lib/check.mjs` 新增 `compareJudgeAnswers`。 ## [1.4.0] — 2026-10-08 - 判题改成**三方独立作答**(用户提议):主脑自己也要定一份,不能只转发判题员。 - 起因:日志显示主脑 6 次提交的答案与判题员给的 6 份**一字不差** —— 它在转发,不是判题; 一个把判断题当多选的错就没人兜住 → 20/100。 ## [1.3.2] — 2026-10-08 - 新增提交前的**答案形状检查**(`checkAnswerShape`):题号/题型错位、单选给了多个、 给了一个页面上没有的选项 → `ANSWERS_SHAPE_MISMATCH`,提示里点名是哪道题、什么题型、实际有哪些选项。 - 起因:测验 20/100 —— 一道判断题只有 A/B,模型提交了 `[A,B,C]`; 旧的点击后守卫虽然报了错,但提示太含糊,模型只好猜 `[A]`。 ## [1.3.1] — 2026-10-08 - 查清"判题员一直开不起来"的原因并堵上。 ## [1.3.0] — 2026-10-08 - 新增**漏答闸门** `checkAnswerCoverage`:提交前先数这套题有几道,缺一道就退回不提交, 并告诉你哪道空着(`ANSWERS_INCOMPLETE`)。 - 单测抓到一个致命 bug:`total = 0` 时每个答案键都被判成"多余" → **所有提交都被拦死**; 修成 `n === 0` 时返回 `ok:true, unchecked:true`。 - 起因:测验 40/100 —— 5 道题只答了 4 道就交了。 --- ## [1.2.2] — 2026-10-08 ### 新增 - **`test/run.mjs` —— 假 CDP 测试台**(`npm test`,18 项断言) 这个项目出过好几次「语法过了、能装载、真机一跑就错」的事, 所以关键分支必须有能自动跑的验证。测试台伪造一个 `browser` 对象 (`sock.send` 按命令名返回预设结果),然后直接调**真实的** `ensureWorkTab` 和**真实的** `output.render`,断言结果。 覆盖:选页逻辑 / 空白页接管 / 截图把图交给模型 / 工具表面 / 预设引用的插件是否存在。 > 第 4 组是**跑起来就立刻抓到**问题的:`cx_shot` 的描述还在说 > 「DSH 会自动显示图片」—— 而当时根本没有图。 `test/` 不在 `files` 白名单里,不会进安装副本。 --- ## [1.2.1] — 2026-10-08 ### 修复 - 🔴 **`cx_enter` 三次全坏(`NO_TABS_ON_PAGE`)** 实测日志:3 次 `cx_enter` **全部**失败,模型每次都得再补一次 `cx_page({all:true})` 才能干活,白费一步。 根因:刚导航进小节时 `#prev_tab` 还没渲染出来,而 `readTabs` 在 没有标签条的页面上会立刻返回 `null`(那是为了让首页不干等 25 秒)。 两件事单独都对,凑一起就坏了。 → `cx_enter` 在 `enterSection` 之后先等 `#prev_tab` 出现(15s)再读。 - 🟡 **整页截图可能存不进附件** 附件的图片有像素/体积上限,整页 PNG 动辄几 MB,超了 `saveImage` 直接抛, 图就没了 —— 等于又变回「只有路径」。 → `screenshot` / `screenshotFull` 支持 `quality`; `takeShot` 先试 PNG,被拒就改用 JPEG(78) 重截一次,并标明 `downscaled`。 --- ## [1.2.0] — 2026-10-08 ### 修复 用户实测发现三个问题,查证后还挖出第四个、也是更严重的一个。 - 🔴 **`cx_shot` 从来没把图给过模型**(最严重) 旧实现只返回 `{file: '…png'}`,是纯文本 —— **模型看不到任何图**。 所以它只能去读被字体混淆的 DOM 硬猜。日志里它自己写着 「题干+选项都被替换字混淆,但可用上下文语义还原(圪盾=矛盾、圧性=属性)」—— 那就是猜。猜对是运气,不是能力。 → 查清 DSH 的契约后修正:`ContentBlockMap` 有 `image` 变体, `ImageBlock = { type:'image', attachment: ImageAttachmentRef }`, 用 `ctx.attachments.saveImage(...)` 存附件。 现在 `cx_shot` / `cx_do({action:"read"})` 的返回里**真的带着图**。 - 🔴 **DSH 看不到用户手动翻好的页面**(用户直接指出) 用户自己翻到「马原章节任务点」后叫 DSH 开始,DSH 却认准状态里记的旧标签页 (还停在课程页),把**它**导航到章节页 —— 用户翻的那页从未被看见。 这跟预设的意图是**相反**的:预设是「用户把页面递过来」,不是「模型抢一个自己的页面」。 三个根因叠加: a. `ensureWorkTab` 第 ① 步无条件复用记录的 `targetId` b. `attachToPage` 兜底取 `pages[0]`(浏览器里最早的那个) c. `cx_open` 无条件 `goto(首页)`,把用户翻好的页冲掉 → 新增 `preferForeground`:探测 `document.visibilityState`, 绑定到用户正在看的那个学习通标签页;`cx_open` 只在不在学习通上时才导航。 干活中途不重绑 —— 免得把正在播的视频切走。 - 🟡 **浏览器里多出一个空白标签页**(用户指出) 浏览器启动自带的空白页没人管,`ensureWorkTab` 又开了第二个。 → 第 ③ 步先找闲置空白页接管,没有再新建。 - 🔴 **没有调用子智能体做题**(用户指出) 查证:这个环境**根本没有 `subagent` 工具** —— Agent Teams 层显式禁用了 `tool-subagent` / `tool-subagent-fork`,换成 `spawn_teammate` / `send_message` / `wait_agent`。 更糟的是:预设的 `plugins` 列表就是**作用域白名单**,只写了 `[persona, chaoxing]`, 所以模型拿到的 30 个工具里**没有 `read` / `write` / `read_image`** —— 提示词里写的「判题员用 `read_image` 读图」根本无从执行。 → `cordis.patch.yml` 加上 `@deepseek-ai/dsh-tool-fs`(read / write / edit / **read_image**), 只在本预设作用域生效 → 提示词改写为 `spawn_teammate` + `send_message` + `wait_agent`, 并新增「你看不到图的时候」一节 ### 新增 - `cx_open` 返回 `diagnostics`(`attachmentsAvailable` / `boundTab` / `tabs`)—— 这些都是「只在真机上才知道」的事实,排查时一眼看出哪一层坏了。 --- ## [1.1.1] — 2026-10-08 ### 变更 - `package.json` 的 `files` 白名单去掉 `docs/` —— 那是给人看的参考资料, 不该进安装副本。安装后只含 `index.js` / `cordis.patch.yml` / `lib` / `prompts`。 - 清掉 `cx_dom` 描述里对已删除动作 `action:"unknown"` 的过时引用。 --- ## [1.1.0] — 2026-10-08 ### 变更 **判断权交还给大模型。** 用户指出三个问题,都成立: - **工具还在替 Agent 判断** `inventory` 给每个 item 打 `action:'play'/'answer'/'human'`、`needsHearing:true`、 `boundary:"你没有听觉能力,不要猜…"`、`beyondMe:"…用 human 交给用户"`。 Agent 照着 `action` 派活,根本没想 —— 这跟「大模型是大脑」是矛盾的。 → 全部删掉,只报事实(有什么东西、什么状态、这一页的环境)。 「这一页有音频 + 一道填空题」是事实;「所以这是听力题、我做不到」是 Agent 的判断。 - **完全没有学习的可能** Agent 自己发现的规律(「马原独立作业页连选项都乱码」)无处可写,会话一结束就没了。 → 新增 **`cx_note`** —— Agent 自己的笔记本(`lesson` / `pending` / `skip` / `general`)。 `cx_open` 开机把摘要带回来 —— 这就是学习。 - **提示词是「说明书」不是「培养」** 13540 字全是菜谱(第 1 步/第 2 步、遇到 X 就 Y)。给了步骤,它就只会照抄步骤。 → 重写成「领域认知 + 硬约束 + 工作原则」,**4231 字**(后因补充判题员一节增至 5000 字)。 ### 工具变更(10 → 15) - 新增 `cx_note`(笔记本) - `cx_do` 收敛为 `play` / `read` / `answer` 三个**页面动作**; `human` / `skip` 移进 `cx_note` —— 那是工作流记录,不是页面操作 - `cx_progress` 改为读笔记(`pending` / `skip`)并生成汇报文本 - `cx_open` 返回 `notes` 摘要 铁律(原速 / 不拖拽 / 不伪造心跳 / 不破解验证码 / 人脸拒绝)仍由 `cx_do` 代码强制。 --- ## [1.0.2] — 2026-10-08 ### 修复 第二次真机测试的现象是「Agent 一直翻页什么都不干,用户以为卡死」。 根因是 `cx_chapters` 读不到课程,导致它拿不到章节全貌,只能一节一节翻。 - 🔴 **`cx_chapters` / `cx_enter` 读到空课程列表** 课程列表在跨域 iframe 里,`goto` 之后要等约 3.5 秒才渲染完。 `cx_courses` 等了(`waitForCourseListStable`)所以能列出 25 门课; 这两个工具没等,于是每次拿到空数组,报 `COURSE_NOT_FOUND` 且 `available=[]`(日志里连续 6 次)。 → 抽出统一的 `resolveCourse()`:goto + 等渲染 + 读 + 模糊匹配标题。 - 🔴 **`readTabs` 在没有标签条的页面上硬等 25 秒 + 固定 sleep 5 秒** 它被 `cx_page` 无条件调用,而首页/章节页/作业页根本没有 `#prev_tab` —— 于是每调一次白烧 30 秒。实测「`cx_page` 稳定 25 秒」就是这个。 → 先判断有没有标签条,没有立刻返回 `null`;有则轮询到数量稳定。 → 同步修好 5 个调用点的 `null` 处理(`cx_enter` / `cx_tab` 会直接崩)。 - 🟡 **`cx_enter` 不给 `section` 时报 `SECTION_NOT_FOUND`** 但它其实成功读到了 59 个小节 —— 那不是错误,是「还没说进哪个」。 → 改成 `ok:true` + `needSection:true` 返回清单。 - 🟡 **`cx_page({all:true})` 不在小节页时报干巴巴的 `NO_TABS`** → 现在先报 `pageKind`,并明确说「你不在小节页上」。 - 提示词新增「不要一节一节往下翻」—— 那门课有 59 个小节,逐节翻光发现没活干就要二十分钟,用户看不到进展只会以为死机。 --- ## [1.0.1] — 2026-10-07 ### 修复 **这是 1.0.0 发布后第一次真机使用暴露出来的问题。** 那一次会话几乎完全跑不动, 根因是几个各自独立的缺陷叠在一起,其中第一个是致命的。 - 🔴 **CDP 会话失效后无法自愈(致命)** 学习通在 `i.chaoxing.com` / `mooc1.` / `mooc2-ans.` 之间跨源跳转会让 CDP session 失效。 失效后**每一条命令**都报 `Session with given id not found`,而旧代码没有任何重连 —— 连 `cx_open` 都救不回来,整个会话彻底卡死只能重启。 现在所有页面命令都走一层带自动重连的 `send()`:遇到会话失效就重新 attach 再重试。 - 🔴 **`getWork` 只检查浏览器活着,不检查页面会话** 浏览器级命令照样成功,于是每次都把同一个**死句柄**返回出去。 现在多做一次页面级存活探测。 - 🔴 **`cx_courses` 报 `value is not lossless JSON`** 返回里带了 `remaining: c.remaining`,而 `readCourseList` 根本不返回这个字段 → 值是 `undefined` → DSH 直接拒绝整条调用。这是那次事故的第一块多米诺骨牌。 现在正确计算 `total - done`,**并在工具出口加了一层统一清洗**, 把 `undefined` / `NaN` / `Infinity` / 函数 / 循环引用一次性挡掉。 - 🔴 **点不动子窗口里的元素** `page.click()` 只在**主窗口**里找元素,而学习通大量内容在 iframe 里; 而且 `Input.dispatchMouseEvent` 用的是页面视口坐标,与子窗口内的 `getBoundingClientRect()` 差着整个 iframe 的偏移 —— 点过去会点到旁边。 现在主窗口用真实鼠标点击,子窗口改用元素自己的 `click()`, 必要时把主页面也导航过去(如实标成 `top-nav`)。 - 🔴 **`cx_dom` 的编号不稳定** 编号里的"框架序号"是**排序后的位置**,而学习通页面挂着动态 iframe, 两次调用之间顺序会变 —— 同一个编号一会儿指这个元素、一会儿指那个。 实测 Agent 连点 4 次同一个编号,两次"不存在"、两次"点不动",直接绕死。 现在每次调用生成唯一 token,点击时在**所有窗口里搜这个标记**,与顺序无关。 - 🟡 **认不出的窗口统统被标成「主页面」** 导致日志里 `byFrame: {主页面: 38}` 看起来"都在主页面", 实际是好几个子窗口混在一起报同一个名字。现在只有真正的顶层窗口才叫「主页面」。 ### 性能 - 🟡 **`cx_page` 从 25 秒降到亚秒级** `evalInFrame` 原来默认走**主世界**,而主世界要先等 `mainWorldContext(frameId, 1500)` —— **每个窗口最多等 1.5 秒**。 学习通首页挂十几个窗口,一次 `cx_page` 实测 25 秒几乎全花在这里。 现在默认走**隔离世界**(创建立即返回),只有真正要读 `videojs` / 页内全局变量的 3 处调用显式标注 `{ isolated: false }`。 ### 其它 - `install.ps1` 支持 `-ProfileName`(默认 `desktop`) - `install.ps1` 修掉「删了旧副本后 `pnpm install` 不重建」的坑: 改用 `pnpm add`,并加了装完自检 --- ## [1.0.0] — 2026-10-07 首个可发布版本。 ### 架构 - **确立「大模型是司令,脚本只是手」的分工**: 工具里不出现任何业务判断,要么报告事实,要么执行一个明确指令 - `lib/` 按「读 / 做」拆开: - `observe.mjs` —— 所有「读」,不允许有业务判断 - `act.mjs` —— 所有「做」,不允许有业务分支 - `inventory.mjs` —— 把「读」整理成「这一页有几个任务点」 - `browser.mjs` —— CDP 底座,零依赖 - `state.mjs` —— 工作区文件 + 待办清单 ### 工具(14 个) - **传感器**:`cx_open` `cx_courses` `cx_chapters` `cx_enter` `cx_page` `cx_shot` `cx_progress` `cx_dom` - **执行器**:`cx_tab` `cx_nav` `cx_do` `cx_click` `cx_scroll` `cx_type` ### 核心能力 - **多任务点识别**:一个页面可以同时挂「视频 + 章节测验」,不会漏 - **播完回读验证**:播完读页面自己的记账(`aria-label`)确认任务点真的完成 - **跨页编号 `key`**:`cx_do({ key:"2:1" })` 自动切页,不用先 `cx_tab` - **整节总览**:`cx_page({ all:true })` 一次看清整节所有页面 - **通用手眼**:不预设任务类型,大模型现场适应未知模块 - **主脑闸门**:子智能体调用 `cx_*` 一律拒绝,防并发错乱 ### 工程 - **过程文件进工作区**:截图与进度落在 `<工作区>/.chaoxing/`,不再乱丢家目录 - **浏览器配置独立**:专用 profile,跨会话共用登录态 - **进程级静音**:`--mute-audio`,页面察觉不到 - **断点续播**:每完成一个任务点落盘 - **安装脚本**:自动备份 / 可回滚 / 支持 `-DryRun` ### 提示词 - 14 节的决策手册,含: - 核心循环、能力边界、汇报节奏 - **判题员子智能体协议**(上下文隔离,省 50~100 倍 token) - 文件放哪 ### 文档 - `docs/学习通事实.md` —— 全部实测页面机制(10 大节),含踩过的坑与真相 --- ## 未发布 ### 待验证 - `subagent` 工具在学习通模式下的可用性(判题员工作流依赖它) - macOS / Linux 实机 ### 计划 - 更多播放器形态的适配验证 - 判题员批量并行(仅在「只刷测验」场景有意义)