DeepSeek Harness 插件:用 CDP 驱动一个受控 Chrome 实例,附带「弹窗收集规则 +…
30.6/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/21 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add joansgwjbsu/dsh-plugin-cdp该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · browser
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 15 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 69 处,展示前 66 条)
- 读取本地文件lib/index.mjs:56
for (const c of candidates) if (existsSync(c)) return c; - 发起网络请求lib/index.mjs:82
const res = await fetch(`${this.base}/json/version`, { signal: AbortSignal.timeout(3000) }); - 写入本地文件lib/index.mjs:90
mkdirSync(this.profile, { recursive: true }); - 执行外部命令来自配置/变量lib/index.mjs:101
const child = spawn(this.chromePath, args, { detached: true, stdio: "ignore" }); - 发起网络请求lib/index.mjs:116
const res = await fetch(`${this.base}/json/list`); - 读取本地文件lib/index.mjs:123
if (existsSync(this.statePath)) { - 读取本地文件lib/index.mjs:125
id = JSON.parse(readFileSync(this.statePath, "utf8")).targetId; - 写入本地文件lib/index.mjs:142
mkdirSync(dirname(this.statePath), { recursive: true }); - 写入本地文件lib/index.mjs:143
writeFileSync(this.statePath, JSON.stringify({ targetId }, null, 2)); - 发起网络请求lib/index.mjs:147
const res = await fetch(`${this.base}/json/new?${encodeURIComponent(url)}`, { method: "PUT" }); - 读取本地文件lib/index.mjs:395
for (const exe of browserCandidates(prefs.browser)) if (existsSync(exe)) return exe; - 写入本地文件lib/index.mjs:568
mkdirSync(dirname(out), { recursive: true }); - 写入本地文件lib/index.mjs:569
writeFileSync(out, buf); - 读取本地文件lib/index.mjs:890
const exeFromPref = browserCandidates(prefs.browser).find((p) => existsSync(p)) || ""; - 读取本地文件lib/index.mjs:901
exeExists: !!exePath && existsSync(exePath), - 读取本地文件lib/prefs.mjs:132
if (!existsSync(path)) return { ...base, _exists: false }; - 读取本地文件lib/prefs.mjs:134
const raw = JSON.parse(readFileSync(path, "utf8")); - 写入本地文件lib/prefs.mjs:170
mkdirSync(dirname(path), { recursive: true }); - 写入本地文件lib/prefs.mjs:172
writeFileSync(tmp, JSON.stringify(clean, null, 2), "utf8"); - 写入本地文件lib/prefs.mjs:173
renameSync(tmp, path); - 读取本地文件lib/supervisor.mjs:53
return JSON.parse(readFileSync(path, "utf8")); - 写入本地文件lib/supervisor.mjs:60
mkdirSync(dirname(path), { recursive: true }); - 写入本地文件lib/supervisor.mjs:63
writeFileSync(path, JSON.stringify(value, null, 2), "utf8"); - 执行外部命令固定命令lib/supervisor.mjs:91
const r = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }); - 读取本地文件lib/supervisor.mjs:196
if (!existsSync(this.stateDir)) return 0; - 读取本地文件lib/supervisor.mjs:197
return readdirSync(this.stateDir) - 写入本地文件lib/supervisor.mjs:212
mkdirSync(this.stateDir, { recursive: true }); - 执行外部命令来自配置/变量lib/supervisor.mjs:215
const child = spawn( - 读取本地文件lib/supervisor.mjs:275
if (!existsSync(this.stateDir)) return null; - 读取本地文件lib/supervisor.mjs:276
const gens = readdirSync(this.stateDir) - 读取本地文件lib/match.mjs:323
const st = statSync(path); - 读取本地文件lib/match.mjs:326
let text = readFileSync(path, "utf8"); - 读取本地文件lib/jobs.mjs:1498
const [text, info] = await Promise.all([readFile(ledgerPath, "utf8"), stat(ledgerPath)]); - 读取本地文件lib/jobs.mjs:1498
const [text, info] = await Promise.all([readFile(ledgerPath, "utf8"), stat(ledgerPath)]); - 读取本地文件lib/jobs.mjs:1521
const names = (await readdir(logDir)).filter((name) => logPattern.test(name)).sort(); - 读取本地文件lib/jobs.mjs:1524
const [text, info] = await Promise.all([readFile(filePath, "utf8"), stat(filePath)]); - 读取本地文件lib/jobs.mjs:1524
const [text, info] = await Promise.all([readFile(filePath, "utf8"), stat(filePath)]); - 读取本地文件lib/jobs.mjs:1546
const names = (await readdir(logDir)).filter((name) => ndjsonPattern.test(name)).sort(); - 读取本地文件lib/jobs.test.mjs:561
const text = readFileSync(join(JW_DIR, "run-zp-B.log"), "utf8"); - 读取本地文件lib/jobs.test.mjs:669
const dirNames = readdirSync(JW_DIR); - 读取本地文件lib/logs.mjs:566
text = readFileSync(item, "utf8"); - 读取本地文件lib/logs.mjs:572
else if (item.path) text = readFileSync(item.path, "utf8"); - 写入本地文件lib/plugin.test.mjs:298
writeFileSync(resumePath, RESUME_FIXTURE, "utf8"); - 删除本地文件递归lib/plugin.test.mjs:316
rmSync(dir, { recursive: true, force: true }); - 删除本地文件递归lib/selftest.mjs:223
rmSync(join(tmpPrefs, ".."), { recursive: true, force: true }); - 读取本地文件lib/selftest.mjs:242
const manifest = JSON.parse(readFileSync(pkgUrl, "utf8")); - 执行外部命令固定命令lib/supervisor.test.mjs:58
spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }); - 读取本地文件lib/supervisor.test.mjs:116
const snapGenOf = () => JSON.parse(readFileSync(join(stateDir, "rules.json"), "utf8")).generation; - 读取本地文件lib/supervisor.test.mjs:169
ok(existsSync(newStatusPath), `新一代状态文件存在 status.gen${swap.generation}.json`); - 读取本地文件lib/supervisor.test.mjs:170
const newStat = JSON.parse(readFileSync(newStatusPath, "utf8")); - 读取本地文件lib/supervisor.test.mjs:174
ok(existsSync(oldStatusPath), `老一代状态文件仍在 status.gen${gen1}.json(未被覆盖)`); - 读取本地文件lib/supervisor.test.mjs:175
const oldStat = JSON.parse(readFileSync(oldStatusPath, "utf8")); - 写入本地文件lib/supervisor.test.mjs:188
appendFileSync(donePath, JSON.stringify({ key: "old-task", generation: gen1 }) + "\n", "utf8"); - 写入本地文件lib/supervisor.test.mjs:189
appendFileSync(donePath, JSON.stringify({ key: "new-task", generation: swap.generation }) + "\n", "utf8"); - 读取本地文件lib/supervisor.test.mjs:190
const events = readFileSync(join(stateDir, "events.ndjson"), "utf8").trim().split("\n"); - 删除本地文件递归lib/supervisor.test.mjs:227
rmSync(dir, { recursive: true, force: true }); - 写入本地文件lib/worker.mjs:58
mkdirSync(dirname(file), { recursive: true }); - 写入本地文件lib/worker.mjs:61
appendFileSync(file, `${text}\n`, "utf8"); - 读取本地文件lib/worker.mjs:80
if (!snapshotPath || !existsSync(snapshotPath)) return null; - 读取本地文件lib/worker.mjs:82
return JSON.parse(readFileSync(snapshotPath, "utf8")); - 读取本地文件lib/worker.mjs:95
if (!existsSync(donePath)) return set; - 读取本地文件lib/worker.mjs:96
for (const line of readFileSync(donePath, "utf8").split(/ ? /)) { - 写入本地文件lib/worker.mjs:193
mkdirSync(stateDir, { recursive: true }); - 写入本地文件lib/worker.mjs:199
writeFileSync(resolve(stateDir, `status.gen${generation}.json`), JSON.stringify(status, null, 2), "utf8"); - 执行外部命令来自配置/变量run-all-tests.mjs:31
const r = spawnSync(process.execPath, ['--test', path], { encoding: 'utf8' }); - 执行外部命令来自配置/变量run-all-tests.mjs:48
const r = spawnSync(process.execPath, [join(libDir, 'selftest.mjs')], { encoding: 'utf8' });
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-app-boot@deepseek-ai/dsh-tools@deepseek-ai/schemastery给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
README
由 DeepSeek 最新模型翻译生成dsh-plugin-cdp
DeepSeek Harness 插件:用 CDP 驱动一个受控 Chrome 实例,附带「弹窗收集规则 + 文本可视化 + 无停机热替换」能力。
它解决什么问题
基于浏览器扩展的自动化通道驱动不了部分网站:
| 问题 | 表现 |
| 受控标签页被清空 | 目标站点会让被控制的标签页变空白 |
| 必须借用用户标签页 | 借来的标签页同样会变空白 |
| 成功弹窗不可信 | 页面报告了成功对话框,但操作并未真正生效 |
本插件因此自带一个受控 Chrome 实例、直接说 CDP 协议,并强制「可验证的操作流程」。
零运行时依赖 —— 只用 Node 内置的 fetch 与 WebSocket(要求 Node ≥ 22)。
展开完整文档(README 剩余 1 节)
dsh-plugin-cdp DeepSeek Harness 插件:用 CDP 驱动一个受控 Chrome 实例,附带「弹窗收集规则 + 文本可视化 + 无停机热替换」能力。 它解决什么问题 基于浏览器扩展的自动化通道驱动不了部分网站: | 问题 | 表现 | | 受控标签页被清空 | 目标站点会让被控制的标签页变空白 | | 必须借用用户标签页 | 借来的标签页同样会变空白 | | 成功弹窗不可信 | 页面报告了成功对话框,但操作并未真正生效 | 本插件因此自带一个受控 Chrome 实例、直接说 CDP 协议,并强制「可验证的操作流程」。 零运行时依赖 —— 只用 Node 内置的 fetch 与 WebSocket(要求 Node ≥ 22)。 兼容性 | 项目 | 要求 | | DeepSeek Harness | >=0.2.0-rc.2 <0.3.0-0(即 engines.dsh,由 DSH 的兼容性判定直接校验);已验证 0.2.1-rc.2 | | @deepseek-ai/dsh-tools | ^0.1.5-rc.3 \|\| ^0.2.0-rc.1 \|\| ^0.2.1-rc.2(peer,由宿主提供) | | @deepseek-ai/schemastery | ^3.18.2(peer,由宿主提供) | | @deepseek-ai/cordis | ^4.0.2(peer,由宿主提供) | | Node | ≥ 22(宿主自带 Node 24,无需另装) | engines.dsh 是 DSH 真正会校验的字段。 DSH 的 evaluatePluginCompatibility() 在插件安装/启动时读取它;一旦声明,它单独决定兼容性(peerDependencies 不再参与判定)。 本插件声明 >=0.2.0-rc.2 <0.3.0-0:覆盖已验证的 0.2.x,并用 <0.3.0-0 挡住 0.3 的预发布版本(<0.3.0 在 includePrerelease 下会误放行 0.3.0-rc.1)。 注意:DSH 升级后,请确认配置档里的 @deepseek-ai 包也已拉齐到同版本。 应用更新不会自动刷新配置档的依赖树。详见 UPDATES.md。 各版本的封装产物、SHA256 与「该用哪个版本」见 RELEASES.md。 自查一条命令(在仓库目录执行;单元测试文件不随 npm 包分发): node run-all-tests.mjs 安装 npm i dsh-plugin-cdp 或在 DSH 的插件配置里加入: - id: cdp name: "dsh-plugin-cdp" config: port: 9222 三个核心能力 1. 弹窗收集规则,插件本身不携带任何规则 首次使用时插件返回一份问卷,由 DSH 内置的 ask_user_question 弹窗询问,一屏收齐: 岗位方向 · 工作地点 · 薪资下限 · 投递平台 · 浏览器 · 排除规则 答案落盘后不再重复询问。 设计红线:所有规则字段默认为空,问卷的规则类问题不生成选项、不标 (Recommended)。一旦预置选项,就等于把某个人的规则当成新用户的默认答案。插件提供的是能力,不是内容。 2. 纯文本可视化,零依赖 不依赖浏览器、不依赖图表库,用 Unicode 字符画图: ── 薪资分布(月薪 · 按 salaryMin 分桶,n=80;日薪 1 单未计入) ─── <7k │████░░░░░░░░░░░░░│ 7 7-9k │██████████░░░░░░░│ 19 9-12k │█████████████████│ 34 12-15k │██████░░░░░░░░░░░│ 12 ≥20k │█░░░░░░░░░░░░░░░░│ 2 薪资未明 │█░░░░░░░░░░░░░░░░│ 2 ── 投递时间线 ────────────────────────── ▂▁▁▁█ (5 天,49 单) 峰值 44 单 含薪资直方图、地点条形图、平台占比、判定构成、投递火花线、等宽明细表。中文按双宽计算,表格列不会错位。 3. 运行器热替换:改规则不用停 采用 先起新的、再退旧的,而不是原地重启: 1. 用户改规则 → 写新快照,规则指纹变化 2. 起新一代进程,与老一代并存 3. 等新一代写出心跳,证明它真的活着 4. 老一代退位 为什么不是原地重启:那必须先杀掉老进程,于是总有一段「没有任何进程在跑」的真空期。 为什么重叠期安全:两代共享同一份 append-only 的完成记录。已完成的事两边都读得到(不会重复做),新增记录只是追加(不会互相覆盖)。重叠本身就是「不断档」的实现手段。 失败回滚:新一代若在就绪窗口内没写出心跳,保留老一代继续运行,绝不在新规则起不来时把任务停掉。 工具一览(15 个) | 工具 | 作用 | | cdp_status | 受控 Chrome 的状态、端口、标签页 | | cdp_navigate | 导航并等待加载 | | cdp_eval | 在标签页里执行 JS 并返回 JSON | | cdp_read | 读取标签页 URL / 标题 / 可见文本 | | cdp_screenshot | 截图到本地文件 | | cdp_new_tab / cdp_use_tab | 新建 / 切换受控标签页 | | cdp_apply_verified | 带校验的操作:等详情面板稳定且薪资与卡片一致才点击,且拒绝重复操作 | | cdp_prefs | 读取 / 写入偏好(含弹窗问卷) | | cdp_browser_info | 如实报告使用的浏览器与可执行文件是否存在 | | cdp_jobs_report | 岗位 / 薪资 / 地点文本可视化报告 | | cdp_jobs_data | 原始 JSON | | cdp_runner_status | 运行器状态,含「规则改了但还没生效」提示 | | cdp_runner_reload | 同步规则并热替换 | | cdp_runner_stop | 停止运行器 | cdp_apply_verified 为什么存在 一次「点错岗位」的操作长这样:点击列表卡片 → 右侧详情面板其实还没切换 → 此时点「申请」→ 申请到了上一个岗位。 这个工具强制三道闸门: 1. 等详情面板标题连续 3 次读数稳定 2. 面板薪资必须与卡片薪资逐字相同 3. 按钮文案若已是「继续沟通 / 已投递」则直接拒绝 任何一条不过就返回 ok: false 并说明原因,绝不「看起来成功」。 ⚠️ 成功对话框不等于操作已记录。请始终到站点自己的记录页复核。 配置 - id: cdp config: chromePath: "" # 留空则自动探测 userDataDir: "你的独立浏览器目录" port: 9222 windowSize: "1500,950" defaultTimeoutMs: 120000 # 单条 CDP 命令超时 loadEventMs: 8000 # 等待页面加载事件的上限 settleMs: 600 # 导航后额外稳定等待 prefsPath: "偏好落盘路径" stateDir: "运行器状态目录" 注意:受控实例使用独立的用户目录,不复用你日常浏览器的配置,因此需要在该实例里单独登录一次目标站点。 开发与测试 node --test lib/jobs.test.mjs # 27 项 node --test lib/report.test.mjs # 28 项 node lib/plugin.test.mjs # 62 项 node lib/supervisor.test.mjs # 44 项 node lib/selftest.mjs # 插件加载自检 自检覆盖三类只有宿主才会拒绝的错误,本地跑 execute() 是测不出来的: 1. output.schema 漏声明返回字段 2. 返回值含 undefined(不是合法 JSON) 3. 输出未通过 validateJsonSchemaValue 已知限制 1. 输出字段必须与 output.schema 逐一对应 —— additionalProperties: false,多一个字段就会在宿主里被拒 2. 运行器默认是心跳模式 —— 未配置命令时不执行任何实际操作,这是有意为之:先证明链路通 3. 热替换依赖 append-only 状态 —— 若把共享状态改成「整份重写」,重叠期就会互相丢更新,那时「先起后杀」不再安全 4. 受控浏览器需要单独登录 —— 独立用户目录意味着不复用日常登录态 License MIT DeepSeek Harness 插件,提供 Chrome DevTools Protocol 自动化能力,自带自管理的 Chrome 实例,另外还有: - 弹窗驱动规则收集 —— 插件不带任何规则;它通过 DSH 内置的 ask_user_question 问用户一次,然后存下答案。 - 零依赖文本仪表盘 —— Unicode 条形图、迷你走势图和等宽表格,用来展示岗位 / 薪资 / 地点数据(能识别 CJK 双宽字符,列永远不会错位)。 - 零停机热替换 —— 规则变化时,新一代 worker 先启动,证明自己活着之后,老一代才退休。两代共享 append-only 状态,所以重叠期是安全的,也不会出现没有进程在跑的真空期。新一代启动失败,就保留老一代。 共 15 个工具。零运行时依赖(Node ≥ 22 内置 fetch + WebSocket)。 ⚠️ 成功对话框不能证明操作已记录。始终到站点自己的记录页复核。
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
joansgwjbsu/dsh-plugin-cdp
DeepSeek Harness 插件:用 CDP 驱动一个受控 Chrome 实例,附带「弹窗收集规则 +…
相关插件 · 浏览器
智能体插件框架deepseek-ai/deepseek-harness
DeepSeek Harnessdsh是由 DeepSeek AI 开发的开源 agent harness智能体框架。
★ 270,422已汉化DSH
71.9
多设备 Agent 看板YaoApp/yao
✨ 所有 Agent 和工作区集中管理,覆盖你的每一台设备。任务看板追踪进度,桌面端、移动端、浏览器、API…
★ 8,147已汉化DSH
71.2
可扩展侧边栏工作台omdsh-dev/DSH-better-sidebar
DSH 的双工作台:原生右侧栏 + 自绘底部面板,并把 ctx.betterSidebar 服务开放给所有插件
★ 4,761已汉化DSH
71.1
浏览器自动化桥Tencent/BrowserSkill
你的 Agent。你的浏览器。连接,就能协作。
★ 9,967已汉化DSH
71
AI 小说创作工作台EthanYoQ/AI-Novel-Writer
AI 小说作家 / AI Novel Writer
★ 1,591已汉化DSH
70.7
智能体浏览器引擎platonai/Browser4
DeepSeek Harness 集成
★ 1,175已汉化DSH
70.4
这是你的仓库?认领条目