dsh-xuexi · 网课模式
工具只当手和眼,判断全归大模型。
当前版本 2.2.4 | 许可 MIT | 平台 Windows | 更新日志 CHANGELOG.md
一个 DeepSeek Harness(简称 DSH)插件 + Agent 预设:
接管你正在用的浏览器,按真实学习的方式把网课任务做完。
它不认识任何网站 —— 它认识屏幕。课程页面长什么样、题在第几个框里、
点哪个按钮算「下一节」,这些一律不写进代码,全部由大模型当场看图判断。
目录
这是什么
一句话:16 个工具 = 手和眼,大模型 = 大脑。
大模型 = 大脑:看、判断、决定、应对没见过的情况
16 个工具 = 手和眼:点、写、滚、截图、取事实 —— 不认识任何网站
工具只做两件事之一:报告它看到的事实,或者执行你指定的一个动作。
「这是什么、该做什么、做不到怎么办」—— 全是模型的事,工具一概不替你判断。
配合它的是一个 Agent 预设「网课模式」:装上之后,你在 DSH 里切到这个模式、
把要做的那一页在浏览器里翻好,然后说一句「接管浏览器,把这一页做完」就行。
解决什么问题
大部分「网页自动化脚本」的思路是把页面结构写死进代码:课程列表在第几个
div 里、完成标记是什么类名、下一节按钮叫什么。页面一变就崩,于是不断加
if,最后没人敢改。
这个插件走的是相反的路。它诞生于一次真实事故:
旧版本把「某个网站长什么样」写死在工具里,又把「按什么顺序调工具」写进提示词。
用户换了一个学校的账号 —— 页面结构不一样:
· 专用工具报「课程不存在」(其实是它自己那个读法没读到)
· 想换回用户翻好的那一页,却根本没有「换标签页」这个工具
· 想直接点课程卡片,可通用眼睛那次没看见内嵌窗口里的东西
· 结果:原地打转十分钟,最后要用户手动帮它打开页面
根因不是模型笨,是它的退路被堵死了。
所以 v2 只守三条原则:
| # | 原则 | 落地方式 |
|---|
| ① | 工具只认识屏幕 | 编号、对账清单、票据指纹 —— 全部从屏幕上现读,代码里没有业务概念 |
| ② | 格式错在动手之前拦住 | 每个工具第一步是纯计算校验;ARG / USAGE 失败 = 页面一下都没被碰过 |
| ③ | 大脑永远有退路 | 截图 → 列出能点的 → 点一个看变化。这三件永远不会失效 |
适用与不适用
适用:任何在浏览器里做的网课任务 —— 看视频、翻文档、做测验、进页面再出来。
不挑站点,因为站点相关的东西一概不写进代码。
做不到、会交回给你(它会记下来,做完一次性汇报,不中途打断你):
| 情况 | 归宿 |
|---|
| 答案要靠听才知道 | 交给用户(模型没有听觉) |
| 答案要靠看视频内容才知道 | 交给用户(模型读不到画面语义) |
| 讨论帖 / 发帖 | 归用户 |
| 图形验证码 | 不破解,交给人 |
| 人脸 / 抓拍 | 直接拒绝(要求摄像头前坐着真人,原理上做不到) |
只有四种情况会当场打断你:图形验证码 / 未登录 / 熔断或今日额度 / 人脸抓拍。
其余一律 hand_note({ tag:"pending" }) 记账,做完一次性汇报。
环境要求
| 项 | 要求 | 备注 |
|---|
| 操作系统 | Windows 10/11 | 安装脚本是 PowerShell 脚本,硬依赖 Windows;暂不支持 macOS / Linux |
| DeepSeek Harness | ≥ 0.2.0(桌面版) | 见 package.json → dsh.engines.dsh |
| Node.js | ≥ 20 | 一般用 DSH 自带运行时,不需单独装 |
| 浏览器 | Chrome / Edge / Chromium 任一 | 插件自己发现并启动;也可用 DSH_BROWSER_PATH 指定 |
| 调试端口 | 9222(默认) | 被占用时改 cordis.patch.yml 里的 config.port |
安装脚本还需要 DSH 自带的 node 与 pnpm
(位于 ~/.dsh/dsh-runtimes/dsh-primary-runtime/dependencies/),缺任一项会直接报错退出。
安装
在插件目录里执行(脚本靠 $PSScriptRoot 定位源码):
# 1. 装进 DSH 桌面配置档(默认配置档 desktop)
.\install.ps1
# 可选参数
.\install.ps1 -DryRun # 只看会改什么,什么都不动
.\install.ps1 -ProfileName default # 装到别的配置档
.\install.ps1 -VerifyOnly # 只校验已安装副本,不改任何东西
.\install.ps1 -Rollback "<.bak- 备份路径>" # 回滚到安装前的 package.json
.\install.ps1 -NonInteractive # 不询问:不备份、不启用回滚
# 2. 重启 DeepSeek Harness
# 3. 设置 → Agent 预设 → CUSTOM 分组 → 选「网课模式」
# 4. 若新任务选择器里看不到它:通用设置 → 打开「代码工作工具」
备份与回滚默认关闭。 一次普通安装会在动任何东西之前问你两个问题:
[?] 是否在修改前备份配置档 package.json? [y/N]
[?] 是否保留它作为还原点(之后可用 -Rollback 还原)? [y/N]
两者的组合结果:
| 备份 | 还原点 | 行为 |
|---|
| 否 | 否(默认) | 不产生任何备份文件,装完没有可回滚点 |
| 是 | 否 | 生成临时备份作本次兜底,安装成功后自动删除 |
| 是 | 是 | 保留还原点,装完打印 .\install.ps1 -Rollback "<路径>" |
| 否 | 是 | 会追问「回滚依赖备份,是否同时打开备份」;拒绝则回滚关闭且仍不建备份 |
-DryRun / -VerifyOnly / -Rollback / -NonInteractive 四种模式不询问。
装完脚本会逐文件比对 SHA-256(不是只数文件个数),内容对不上直接失败并给出回滚命令。
浏览器
一般不用手动开 —— 插件按 DSH_BROWSER_PATH → 平台常见路径 → PATH 的顺序自己找浏览器,
自己带 --remote-debugging-port 拉起来(Chrome / Edge / Chromium 任一)。
想自己控制浏览器时手动开一个也行:
chrome.exe --remote-debugging-port=9222 --user-data-dir="$env:USERPROFILE\.dsh\xuexi\browser-profile"
⚠️ 必须带 --user-data-dir:不带的话 Edge 会把命令交给你正在用的那个实例 ——
窗口被抢走,而且永远不会绑上 9222。登录态就存在这个 profile 里,跨会话复用。
快速开始
装好 → 重启 DSH → 切到「网课模式」→ 先把要做的那一页在浏览器里翻好,然后说:
接管浏览器,把这一页的任务做完
一次典型会话(工具调用由模型自己决定,你只看结果):
| 步骤 | 它在做什么 |
|---|
eye_open | 接管浏览器,绑定到你正在看的那一页(不会替你跳页) |
eye_see | 报这一页有几处要处理、每处是「挑」还是「写」、完成标记是什么 |
hand_play / hand_read | 播视频 / 慢翻文档(原速,不拖进度条) |
eye_shot | 看不清就截图,图直接进上下文,你看得到 |
hand_pick / hand_write | 在页面上作答 |
eye_check → hand_submit | 三方独立复核通过才交 |
做完它一次性汇报:做了什么 / 要你处理什么 / 主动跳过了什么。
它不会替你填账号密码,也不会破解验证码 —— 这些会记进待办交给你。
16 个工具
分三组:眼(5) 只报事实、手(6) 只做一个动作、兜底(5) 只能由代码守住。
眼(5)—— 只报事实
| 工具 | 干什么 |
|---|
eye_open | 接管浏览器并绑定到用户正在看的那个标签页;返回开着哪几个标签页、接的是哪个、登录没有、以前的笔记。不会导航用户那一页,幂等 |
eye_see | 这一页有哪几处要处理,每处标明是挑(pick)/ 写(write)/ 按钮 / 媒体,有几个选项、现在选了什么、有没有「已完成」标记 |
eye_list | 通用眼睛:这一页所有能点能填的东西,每个一个编号(含内嵌窗口)。不判断哪个该点 |
eye_shot | 截图,图直接放在返回里 —— 模型确实能看到,不是只给路径 |
eye_check | 交之前复核(只读,跑多少次都不影响页面)。七条全过才发票据 |
eye_shot 的省图三问(截图很贵):
- 这问题
eye_see 的数据(areas / marks / done)能答吗?能就别拍。
- 必须拍的话,整页还是某一处?优先按编号裁那一处 ——
eye_shot({ "i": "<区编号>" }),
自动算矩形,pick 区会取整组选项的并集,比整页小一个数量级。
- 这张图是给复核员看的吗?是就
embed: false 只落盘,把 file 路径发给它(它用 read_image 读)。
页面内容跟上次看时相同、且之后没动过手 → 默认不重烧(返回「未变」);确实要就 force: true。
手(6)—— 只做一个动作
| 工具 | 干什么 |
|---|
hand_click | 点(编号 / 可见文字 / 屏幕坐标,三选一)。返回里会告诉你页面怎么变了 |
hand_pick | 在可挑区里挑选项。单选 / 多选 / 判断 / 下拉对它都是同一件事;只认页面上真实存在的选项,给了没有的会 rejected 并列出实际有哪些 |
hand_write | 在可写区写字,写完回读实际内容(不是「我应该写上了」) |
hand_scroll | 滚。每步之间已自动停顿,所以没有(也别传)dwellMs 这类参数;想慢翻就小步多滚 { "px": 300, "times": 20 } |
hand_goto | 唯一的导航:跳网址 / 后退 / 刷新。工具绝不自作主张跳页 |
hand_tab | 换浏览器标签页(浏览器顶上那一排,不是网页里的页签) |
兜底(5)—— 只有这两类动作需要代码兜住
| 工具 | 为什么必须由代码守 |
|---|
hand_play | 铁律:原速 1x、永不拖进度条、永不伪造心跳、严格串行。参数里故意没有 seek / heartbeat / jumpTo / percent;rate 只在用户明确要求倍速时才传 |
hand_read | 慢翻一个文档到底:回顶 → 小步 300px → 每步停 1 秒 → 到底。文档类任务点对翻阅节奏敏感,大步快跳不计「读过」 |
hand_submit | 做错回不了头:不接受任何答案(只读页面)、没有有效票据就交不出去,交之前自己再走一遍七条 |
hand_note | 笔记本 + 给用户的待办清单(lesson / pending / skip / general),下次 eye_open 带回来 |
hand_verdict | 复核员专用:登记复核结论。主脑调它会返回 LEAD_CANNOT_JUDGE(那是对的) |
复核员(子智能体)只开放 hand_verdict 与 read_image;
它调其它手眼工具一律返回 NOT_THE_LEAD。
典型流程:做完一页
① 先数清楚:eye_see 看这一页有几处、每处是什么、完成标记在哪
② 一处一处做:媒体 hand_play / 文档 hand_read / 题 hand_pick + hand_write
③ 做完一处回头确认:标记变了没有(没变先刷新一次再看 —— 标记是页面加载时渲染的)
④ 有题要交:看图 → 自己定 → 派两个复核员 → eye_check → hand_submit
⑤ 做不来的记 hand_note({ tag:"pending" }),继续下一处,收尾一次性汇报
两条省时间、也省得白干的经验:
- 一页常常挂着好几处,不要做完一个就翻页。
- 「播完了」和「记上了」是两件事:
finished 只说明播放器到了末尾,
任务点记没记上是服务端的事,工具读不到就不许替它下结论。
看到「未完成」的定式动作是先刷新一次;刷新后仍「未完成」才是真没记上 ——
别原地重播,重播治不了「没记上」,只会白等一轮。
做题:三方独立复核
答题是全站唯一做错回不了头的动作(一般不给重做),所以交之前要三方独立:
① 主脑自己看图定一份,用 hand_pick / hand_write 弄在【页面上】
★ 先弄完,再派人。别把自己那份告诉任何人。
② spawn_teammate 起两个复核员 → send_message 派活 → wait_agent 只等一次
给:同一张截图 + 一句「这一页要复核的清单序号 n」
不给:任何答案、任何文字
③ 它们各自 hand_verdict 登记(值写图上看得见的标签:"B" / "对")
④ eye_check 三方比对 → 七条全过才发票据 → hand_submit
为什么主脑的答案必须落在页面上:旧版主脑把复核员的 JSON 原样转发,
6 次提交和复核员给的 6 份一字不差 —— 实质只有一份判断,一个错就没人兜住。
现在工具直接读页面上的 filled,想报一个假答案,它做不到。
没有后门:不一致时永远不发票据,没有票据就交不出去。
放行只有一条路 —— 复核员拿到特写后重新登记、且新结论一致。
登记是「每人一份当前结论、整体覆盖」,身份由 ctx.agents 判定,不靠自报。
eye_check 会拦这七条:
| # | 拦什么 | 错误码 |
|---|
| 1 | 挑的地方还空着 | EMPTY_PICK |
| 2 | 选了页面上没有的项 / 数量越界 | OUT_OF_RANGE |
| 3 | 写的地方是空的 | EMPTY_WRITE |
| 4 | 复核员不够两份 | NEED_TWO_REVIEWERS |
| 5 | 三份不一致 | REVIEWERS_DISSENT |
| 6 | 还有拿不准的 | HAS_UNCERTAIN |
| 7 | 你指定的那个「交」按钮找不到 | NO_SUBMIT_TARGET |
写字的题只查「有没有空白」—— 开放题答案本来就各不相同,不比文字;
但主脑自己要看一眼写得对不对,拿不准就 hand_note({ tag:"pending" }) 交给用户。
票据有时效(15 分钟),且页面一动(点 / 写 / 翻页,滚动和截图不算)就作废。
边界与铁律
铁律六条(代码会拦,参数层面就堵死):
原速 1x | 永不拖进度条 | 永不伪造心跳 | 严格串行 | 不破解验证 | 不代填账号密码
- 倍速:默认 1x 是我们自己的底线;但「别人改成几倍」是事实,工具会如实报告、不纠回去。
只有用户明确要求时才传
rate。
- 人脸 / 抓拍直接拒绝 —— 它要求摄像头前坐着真人,原理上做不到。
四类错误(返回里的 category,决定该改自己还是该改看法):
| category | 意思 | 页面动过吗 |
|---|
ARG | 调用格式写错了 | 没动 |
USAGE | 格式对,组合不对 | 没动 |
WORLD | 屏幕上真没这东西 | 可能动过 |
TOOL | 浏览器掉了 / 上一个任务还在跑 | —— |
配置与环境变量
| 变量 | 干什么 |
|---|
DSH_BROWSER_PATH | 显式指定浏览器可执行文件。设了但文件不存在会直接报错,不悄悄换一个 |
DSH_CDP_TIMEOUT_MS | 单条 CDP 命令的超时(默认 60000)。浏览器假死时最坏也就是超时后一句明白话,不挂起 |
过程文件落在哪:<工作区>/.xuexi/(截图、笔记、记账)—— 跟对话在一起,好清理。
机器状态:~/.dsh/xuexi/(browser-profile 登录态跨会话共用;load-diag.log 是装载诊断日志)。
仓库结构
index.js 16 个工具 + 统一外壳 + 前置校验 + 角色闸门
install.ps1 安装到 DSH 配置档(Windows;备份/回滚默认关闭、交互询问)
cordis.patch.yml 注册 Agent 预设「网课模式」+ 预设作用域白名单
lib/
args.mjs ★ 参数校验(纯函数,零副作用)
gate.mjs ★ 复核七条 + 票据指纹 + 登记簿(纯函数,可单独测)
browser.mjs CDP:标签页 / 截图 / 跨窗口 / 自愈重连;
socket 健康层(死亡广播 + 发送超时)+ 浏览器发现
see.mjs 看:四类区域 + 编号清单
hand.mjs 手:点 / 挑 / 写 / 滚 / 播 / 慢翻
note.mjs 笔记本 + 过程文件
prompts/xuexi-mode.md 模式提示词(注入给模型,改它 = 改行为)
facts/平台事实.md 经验种子(不是规则,用完让位给真实观察)
scripts/patch-profile.mjs 改 DSH 配置档 package.json(由 install.ps1 调用)
test/
run.mjs 静态断言集(六组,333 条,零副作用)
live.mjs fixture 测试台(真 chromium;没有就跳过)
fixture/ 静态 fixture 页(main / frame / frames)
CHANGELOG.md 版本历史
LICENSE MIT
安装副本只包含 package.json 的 files 白名单:index.js、cordis.patch.yml、
prompts/、lib/(外加 npm 总会带上的 package.json / README / LICENSE)。
test/ 与 facts/ 不进安装副本。
测试
npm test # 静态断言集:333 条,不需要浏览器
npm run test:live # fixture 测试台:真 chromium + 真 CDP(没有浏览器就自动跳过)
npm test 分六组:
| 组 | 覆盖什么 |
|---|
| A | 参数校验(含零副作用的结构保证) |
| B | 复核闸门(七条 + 票据 + 登记覆盖) |
| C | 工具表面(16 个、前缀、并发标志、铁律参数里不许有 seek / heartbeat 这类作弊口子) |
| D | 痕迹检查(代码与提示词里不许有平台字样、不许写死网址) |
| E | 预设自检(cordis.patch.yml / package.json / 提示词三者对得上) |
| G | socket 健康层 + 浏览器发现(用最小 WebSocket 服务器做确定性验证) |
test:live 拿静态 fixture 页(单选 / 下拉 / 多选 / 文本域 / 跨域 iframe 登录框)
跑产品自己的代码路径:编号和 DOM 上挂的必须是同一个、按编号点/写真的落到元素上、
按编号裁剪的 PNG 尺寸对拍、跨域 iframe 的页面坐标与命中、登录态必须扫所有窗口、
关掉浏览器后调用快速失败而不是挂起。
常见问题
预设列表里看不到「网课模式」?
它在 CUSTOM 分组下(带 name 的预设都归自定义)。另外确认
通用设置 →「代码工作工具」已打开,否则新任务选择器不显示自定义预设。
装完显示「加载失败」?
看 ~/.dsh/xuexi/load-diag.log:有 module-eval start 没有 module-eval ok
= import 阶段报错;有 apply start 没有 apply done = 注册时报错,堆栈就在下面几行。
连不上浏览器 / 报 ECONNREFUSED?
插件会自己拉浏览器。拉不起来通常是本机没装 Chrome / Edge / Chromium,
或 DSH_BROWSER_PATH 指向了不存在的文件(指错会明确报错,不会悄悄换一个)。
截图老返回「未变」,是不是坏了?
不是。页面内容跟上次看时相同、且之后没有点/写/滚/翻页,就不重复烧图。
确实要一张新的就加 force: true。
它会替我作弊刷课吗?
不会,且代码层面堵死:原速 1x(只有你明确要求时才传 rate)、
不拖进度条、不伪造心跳、不破解验证、不代填账号密码;人脸抓拍直接拒绝。
支持 macOS / Linux 吗?
不支持。安装脚本与工具代码都硬依赖 Windows,项目短期内只面向 Windows 用户。
-ProfileName 能用吗?
暂不建议使用:scripts/patch-profile.mjs 目前把配置档目录固定为 desktop,
用非默认配置档时打补丁与回滚会写到 desktop。等这个参数打通后再用。
贡献
改动前先跑通静态断言(333 条,不需要浏览器):npm test。提交前请确认:
- 新增/改工具 → 同步更新
index.js 的工具描述、prompts/xuexi-mode.md 的调用示例、本文的工具表
- 改提示词 →
npm test 的 E 组会校验提示词与预设/包的一致性,别跳过
- 改工具契约(参数)→ 补一条 A 组断言(
lib/args.mjs 是纯函数层)
- 仓库统一 LF(见
.gitattributes);install.ps1 必须存成 UTF-8 with BOM
问题与建议:https://github.com/LeuJasYoh/dsh-xuexi/issues
许可证
MIT © 2026 LeuJasYoh