dsh-browser-plus 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/LICENSE +22 -0
  3. package/NOTICE.md +7 -0
  4. package/README.en.md +100 -0
  5. package/README.md +99 -2
  6. package/assets/dsh-browser-plus-256.png +0 -0
  7. package/assets/dsh-browser-plus-512.png +0 -0
  8. package/assets/dsh-browser-plus-small.svg +9 -0
  9. package/assets/dsh-browser-plus.ico +0 -0
  10. package/assets/dsh-browser-plus.svg +11 -0
  11. package/assets/readme-workspace.png +0 -0
  12. package/cordis.patch.yml +17 -0
  13. package/docs/MIGRATION.md +48 -0
  14. package/docs/README.md +22 -0
  15. package/docs/SOAK-CHECKLIST.md +98 -0
  16. package/docs/architecture.md +88 -0
  17. package/docs/tool-reference.md +124 -0
  18. package/docs/user-guide.md +121 -0
  19. package/docs/why-browser.md +45 -0
  20. package/lib/browser/runtime.d.ts +225 -0
  21. package/lib/browser/runtime.js +302 -0
  22. package/lib/browser/types.d.ts +668 -0
  23. package/lib/browser/types.js +18 -0
  24. package/lib/browser-electron/auth-cookies.d.ts +54 -0
  25. package/lib/browser-electron/auth-cookies.js +83 -0
  26. package/lib/browser-electron/chrome-state.d.ts +187 -0
  27. package/lib/browser-electron/chrome-state.js +12 -0
  28. package/lib/browser-electron/entry.d.ts +66 -0
  29. package/lib/browser-electron/entry.js +62 -0
  30. package/lib/browser-electron/fingerprint.d.ts +29 -0
  31. package/lib/browser-electron/fingerprint.js +42 -0
  32. package/lib/browser-electron/host-main.d.ts +18 -0
  33. package/lib/browser-electron/host-main.js +2494 -0
  34. package/lib/browser-electron/icon.d.ts +11 -0
  35. package/lib/browser-electron/icon.js +23 -0
  36. package/lib/browser-electron/page-chrome.d.ts +21 -0
  37. package/lib/browser-electron/page-chrome.js +2034 -0
  38. package/lib/browser-electron/provider.d.ts +709 -0
  39. package/lib/browser-electron/provider.js +2575 -0
  40. package/lib/browser-electron/remote-host.d.ts +143 -0
  41. package/lib/browser-electron/remote-host.js +952 -0
  42. package/lib/browser-electron/task-summary.d.ts +2 -0
  43. package/lib/browser-electron/task-summary.js +12 -0
  44. package/lib/browser-electron/task-thumbnail.d.ts +11 -0
  45. package/lib/browser-electron/task-thumbnail.js +9 -0
  46. package/lib/browser-electron/write-guard.d.ts +41 -0
  47. package/lib/browser-electron/write-guard.js +123 -0
  48. package/lib/index.d.ts +16 -0
  49. package/lib/index.js +14 -0
  50. package/lib/tool-browser/index.d.ts +31 -0
  51. package/lib/tool-browser/index.js +1931 -0
  52. package/package.json +95 -4
  53. package/screenshots.json +3 -0
  54. package/scripts/build-icons.mjs +80 -0
  55. package/scripts/capture-window.ps1 +79 -0
  56. package/scripts/crop-image.ps1 +20 -0
  57. package/scripts/smoke-browser-tools.mjs +1968 -0
  58. package/scripts/smoke-chrome-world.mjs +63 -0
  59. package/scripts/smoke-electron-host.mjs +50 -0
  60. package/src/browser/runtime.ts +470 -0
  61. package/src/browser/types.ts +649 -0
  62. package/src/browser-electron/auth-cookies.ts +125 -0
  63. package/src/browser-electron/chrome-state.ts +174 -0
  64. package/src/browser-electron/entry.ts +115 -0
  65. package/src/browser-electron/fingerprint.ts +45 -0
  66. package/src/browser-electron/host-main.ts +2330 -0
  67. package/src/browser-electron/icon.ts +26 -0
  68. package/src/browser-electron/page-chrome.ts +2046 -0
  69. package/src/browser-electron/provider.ts +3088 -0
  70. package/src/browser-electron/remote-host.ts +1004 -0
  71. package/src/browser-electron/task-summary.ts +10 -0
  72. package/src/browser-electron/task-thumbnail.ts +17 -0
  73. package/src/browser-electron/write-guard.ts +134 -0
  74. package/src/index.ts +52 -0
  75. package/src/tool-browser/index.ts +1974 -0
  76. package/src/types/electron-shim.d.ts +143 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,166 @@
1
+ # Changelog
2
+
3
+ ## v0.5.0 (2026-10-03)
4
+
5
+ **新增四个工具(37 → 41)**
6
+
7
+ - **`browser_dialog`** —— 查看/引导 `alert`/`confirm`/`prompt`。以前一律自动接受,「确认删除」这类页面**永远被同意**;现在可以先设 `accept`/`dismiss`(`prompt()` 配 `promptText`)再触发它,`inspect` 报告上一次与当前策略。
8
+ - **`browser_console` / `browser_network`** —— 读控制台报错、未捕获异常与网络请求(每个视图各 200 条环形缓冲)。**读取默认不清空**,`clear: true` 才清;支持 `level` / `urlContains` / `failedOnly` / `limit`。
9
+ - **`browser_emulate`** —— 视口(可带移动端行为与 DPR)、自定义 UA、`prefers-color-scheme`;`clear: true` 一次撤销三样。
10
+
11
+ **改进**
12
+
13
+ - **`browser_execute` 现在也收语句体**(自动判别:先试表达式,再试脚本体,语句体用 `return` 返回值)。
14
+ - **`browser_snapshot` 新增 `query` 与 `limit`** —— 按 kind+label 过滤,且**过滤发生在计上限之前**(否则默认上限 60 之外的元素永远搜不到)。
15
+ - **坐标点击落在视口外时明确报错**,并报出那个点上实际是什么元素(坐标点击**不会自动滚动**,以前是静默丢弃)。
16
+
17
+ **修复**
18
+
19
+ - **页面缩放 ≠ 100% 时,二级菜单弹层不贴按钮** —— 实测 160% 下偏 330px(`left` 311 → 502)。根因是 chrome 内部 px 与页面 px 差一个缩放倍数。
20
+ - **`browser_dialog` 的 `inspect` 看不到刚触发的对话框** —— 它没有自己排空宿主里的对话框。
21
+
22
+ > 每条改动的背景、真机证据与踩过的坑,见对应提交信息。
23
+
24
+ ## v0.4.3 (2026-09-30)
25
+
26
+ - **取消抽屉设计 + 页面不再被顶栏遮挡**(用户要求「取消抽屉的设计,同时为了不遮挡,像谷歌浏览器那样处理」)。
27
+ - **取消抽屉**:隐藏悬停热区 `#toolbarRevealZone` 与「收起」按钮 `#toolbarHide` ✓ —— 顶栏**常驻**,不再有滑出/收起 ✓。
28
+ - **不遮挡**:Chrome 的语义是「页面从工具栏下方开始」✓。这里是**注入式** chrome(在页面内部)✗,所以用同一语义把**文档整体下移**顶栏高度(标签行 40 + 工具栏 44 = 84px ✓,`padding-top` + `!important` 以压过页面自身样式 ✓)。
29
+ - **实测**:`getComputedStyle(html).paddingTop === '84px'` ✓,页面首个内容元素 `getBoundingClientRect().top === 84` ✓ —— 正好在顶栏下方 ✓。截图确认 IANA 的 logo 与导航**完整可见** ✓。
30
+ - **已知限制**:页面自身的 `position:fixed` 元素仍会贴到视口顶部(被顶栏盖住)✗ —— 因为注入式 chrome 无法缩小页面视口 ✗。**彻底解决需要把 chrome 做成独立的宿主视图**(Chrome 的真实架构 ✓),那是更大的改动 ✓。
31
+ - **标签栏显示 favicon**(Chrome 的标签几乎由 favicon 主导,这是「像不像」最显眼的一块)。宿主监听 `page-favicon-updated` ✓,把图标 URL 放进 `ChromeTabSummary.favicon` ✓,chrome 用 `createElement('img')` + `.src` 渲染 ✓(**绝不拼 innerHTML** —— 该 URL 由页面控制 ✓)。页面没给图标时回退**内联 SVG 地球** ✓(与 Chrome 一致)。
32
+ - `data:,`(空 data URI)被显式忽略 ✓ —— 有些页面用它代替真图标 ✓,当成图标会渲染出**破图** ✗(实测 example.com 就是这样 ✓)。
33
+ - **实测**:GitHub 标签显示猫图标 ✓、Example Domain 显示地球 ✓。
34
+ - 🔴 **修复:窗口从最小化恢复后,视图不会被重新布局(一直是 0×0)**。宿主只订阅了 `resize` ✗,而**最小化时 `getContentSize()` 返回 0** ✓ → 此时创建的视图被布局成 0×0 ✓,恢复窗口**不会触发 `resize`** ✗ → 该视图**永远**是 0 宽 ✓。**实测**:正常时创建的视图 `innerWidth=1388` ✓;最小化时创建的 `0` ✓;**恢复后同一视图仍是 0** ✗(修复前)。
35
+ - **修法**:除 `resize` 外,补订阅 `restore` / `show` / `maximize` / `unmaximize` 触发 `layoutViews()` ✓。
36
+ - **验证(按效果)**:最小化时创建视图 → `0` ✓ → **恢复窗口后同一视图变为 `1388×831`** ✓✓。
37
+ - **副作用**:这解释了此前一连串怪现象 —— 隐藏/最小化视图里 `100vw` 为 0 → chrome 的标签栏塌缩成 8px ✓、`visibilityState: hidden` ✓、`browser_screenshot` 超时 ✓。
38
+ - 🔴 **修复 `showView` 与 `createView` 的竞态 —— 新标签的视图永远不会被显示**。`RemoteElectronViewHost.showView()` 只等 client ready ✗,**不等视图 materialize** ✗,而 `createView` 的 RPC 是**首次使用时才发**的 ✓ → `showView` 先到宿主 ✓ → 宿主抛 `unknown view` ✓ → 被 `.catch` 吞掉 ✓ → **新标签的视图从未 `setVisible(true)`** ✗。而 `createView` 又无条件 `activeViewByTask.set(...)` ✓ → `switch-tab` 的 `activeViewChanged` 守卫对「点那个新标签」**恒为 false** ✓ → **按构造就是空操作** ✓✓。
39
+ - **修法**:`showView` 先 `await handle.materializeForShow()`(新增的公开方法,内部走既有的 `materializeOnce`)再发 `showView` RPC ✓。
40
+ - **验证**:新增回归测试钉住**顺序**(`materializeForShow` 必须在 `call('showView')` 之前,且必须被 `await`)—— 该测试在修复前必然失败 ✓。**注意**:`remote-host.ts` 跑在 **DSH 进程**里,**此修复需重启 DSH 才生效**。
41
+ - **chrome 改成 Chrome 那样的「窗框」,而不是浮动小部件**。用户指出「一点都不像」——根因是形态:之前是**居中的 1180px 浮动条**(圆角 + 阴影 + 上下两行之间留缝),而 Chrome 的窗框是**贴满窗口宽度、上下齐平**的。现改为全宽 + 齐平:标签行通栏在最顶部、激活标签与工具栏同色连成一片、工具栏无圆角无阴影、地址栏全宽。
42
+ - 顺带消除了一处**视口单位依赖**:原规则 `width:min(1180px,calc(100vw - 20px))` 在**窗口最小化或视图隐藏**时 `100vw` 为 0 → `calc` 变负数 → `width` 非法失效 → 回退 `auto` 收缩成 **8px = 左右 padding**,`left:50%` 也失效。**实测确认**(同一份 CSS,1 个标签时 `[104,0,1180,34]`、2 个标签时 `[-4,0,8,34]`)。新规则不依赖视口单位。
43
+ - **注入式 chrome 新增 Chrome 风格标签栏**(渲染层已完成并真机验证)。位置在工具栏**上方**,每个标签显示页面标题、当前标签高亮;工具栏随之下移 42px 给它让位。数据来自宿主新增的 `tabs` 状态(每个标签 = 一个宿主视图,`active` 标记可见的那个)。
44
+ - 🔒 **标签的 URL 只保留 origin**(走 `taskSummaryUrl()`)。该状态会注入页面、并写进按钮的 `title` 属性,若给完整 URL 等于把查询串/token 交给页面。**这一点是队友在实现时主动指出我契约里的疏漏。**
45
+ - 🐛 **真机验证发现并修掉一处 CSS 缺陷**:`#tabstrip` 没有自己的背景,标签只有 5% 白 → 在白色页面上**完全看不见**。工具栏之所以可见是因为它自带深色底。现给标签加不透明底色 + 边框 + 阴影,并把 `flex-basis` 设为 200px(此前被压到 44px 最小宽度,标题只剩「E…」,多个标签无法区分)。
46
+ - ⚠️ **已知未完成:点击标签不会切换**。真机实测:点击确实落在 chrome 上(`elementFromPoint` 返回 chrome 宿主)、binding 通路正常(「接管」按钮可用、control 正确翻转)、宿主 `switch-tab` 分支与 `syncVisibleTaskVisibility` 逻辑复核无误 —— 但 `active` 标记不变、可见页面不变。**根因尚未定位**,下一轮继续。
47
+ - **注入式 chrome 改成 Chrome 式常驻顶栏**。此前工具栏**默认隐藏**,鼠标移到顶部中央才滑出;现在**默认常驻**,按 Chrome 的排布分组:左侧导航组(← → ⟳ ⏹ 🏠)│ 胶囊地址栏(带锁图标)│ 右侧功能组(☆ ⏱ ▤ 接管 收起)。点「收起」后仍回到原来的悬停模式。
48
+ - 三个功能图标原先排在地址栏**之前**,用挂载后重排 DOM 的方式挪到右侧——比重写那几行超长 SVG 字符串安全得多。
49
+ - 新增 `host.dataset.dshChrome` **版本标记**:宿主元素在 light DOM 里,即使 chrome 用 closed shadow root,外部也能读到它,用来判断页面里跑的是哪一版 chrome。
50
+ - 🔴 **实测发现**:在 `address` 的 `focus` 里调 `select()`(想复刻 Chrome「点一下即全选」)**会让 CDP `Input.insertText` 再也落不进这个 shadow DOM 地址栏** ✗ —— 普通页面输入框不受影响,`browser_type` 本身正常。已在代码里留注释阻止再加回来。**Chrome 式全选待另行实现。**
51
+ - **用 DSH 自己的校验器验证全部 37 个工具的 schema**。DSH 在**注册时**校验每个工具的 schema,**被拒会让整个插件从会话里消失**(而不是只失败一次调用) —— 此前这一点只有重启才能发现 ✗。新增 `test/tool-schema.test.mjs`:导入 `@deepseek-ai/dsh-tools` 的 `assertObjectJsonSchema` / `assertSupportedJsonSchema`(注册时用的就是它们)对 37 个工具全跑一遍,并用 `validateJsonSchemaValue`(DSH **每次调用**时跑的)验证 10 组**真实调用参数**能被接受 ✓。**全部通过** → 插件能被 DSH 接受 ✓。
52
+ - 冒烟里**手写的**输出形状检查也换成同一个 `validateJsonSchemaValue` ✓ —— 不再是我自己近似的规则,而是 DSH 真正执行的那一条。33 项仍全绿。
53
+ - **补上 JS 侧指纹的断言(冒烟共 33 项),并确认现有指纹是自洽的**。此前只验证过**请求头**(UA / sec-ch-ua / Accept-Language),**页面里 JS 读到的指纹从未验证过**。实测:UA `...Chrome/148.0.7778.280...`、brands `[Not/A)Brand 99, Chromium 148]`、`webdriver:false`、`vendor:Google Inc.`、`window.chrome` 存在、`languages` 已加权 —— **这正是真实 Chromium 的样子**(真实 Chromium 的 UA 同样带 `Chrome/`,brands 同样没有 `Google Chrome`),自洽 ✓。新增断言钉住**「UA 的主版本号必须出现在某个 brand 里」**这条不变量,防止以后被改坏。
54
+ - **没有改动指纹**:我一度把「UA 说 Chrome、brands 说 Chromium」当成矛盾并准备用 `Emulation.setUserAgentOverride` 去「修」,测量后确认那是 Chromium 的正常表现,且是 `fingerprint.ts` 模块注释里**有意为之**的设计(不编造引擎背不出来的指纹)。**是测量拦住了我。**
55
+ - 🔴 **修复:浏览器直接导出的 cookie 文件会被整份拒绝**。`browser_auth { action: 'restore', file }` 的条目校验**要求 `url` 字段**,但**浏览器扩展(Cookie-Editor / EditThisCookie)与 Edge 自带导出写的是 `domain` + `path`,根本没有 `url`** → 于是「从浏览器导出 → 导入」这条**声明的主要工作流完全不工作** ✗(报 `no usable entries`),只有本插件自己导出的格式能用 ✗。
56
+ - **修法**:条目校验改为**归一化** —— 有 `url` 用之,否则由 `domain` + `path` + `secure` 推导(`.example.com` 的前导点会去掉,因为 URL host 不能带它)。顺带支持 `sameSite`,并同时接受 Chromium 拼写(`no_restriction`)与 Playwright 拼写(`None`/`Lax`/`Strict`),透传到宿主的 `cookies.set`。
57
+ - **验证**:单测用扩展的真实字段形状(含 `sameSite: 'Lax'` 的大小写差异);冒烟在**真实 Chromium** 上导入一份扩展形状的文件 → `{restored:1, present:1}`(装进去了且事后能读回),32 项 `EXIT=0`。
58
+ - **交互效果断言补全到 10 项(冒烟共 31 项)**:新增 **`click_ref`**(文档里的主要交互方式,此前只验证过「调用返回了」,从未验证过「页面收到了」)、双击、滚动、`fill` 选 select 四条**效果断言**。同时对 `browser_scroll` 与 `browser_fill` 做了**旧代码现场实测**:滚动 `scrollY` 0 → 69 ✓、填充输入框拿到值 ✓ —— **没有第三个静默失效的输入工具**。`browser_scroll` 那条断言第一版写错了(当时页面本身没有可滚动高度,`scrollY` 理应不变),已改为先注入高元素。
59
+ - **键盘输入此前也在空转(由第 9 轮的修复一并治好,本轮补上断言)**:实测运行中的旧代码,`browser_press_key` 派发后页面收到的 `keydown` 列表**为空** ✗ —— Enter/Tab/Escape/方向键/Ctrl+A 全靠它。它与鼠标按压走**同一条 `dispatchInput` 路径**,因此第 9 轮的 `Emulation.setFocusEmulationEnabled` 修复**同时治好了两者**。`browser_type`(`Input.insertText`)不受影响,一直正常。冒烟新增两条**效果断言**(键盘 → 页面收到 `Enter`;输入法 → 聚焦输入框内容变为所输入文本),现共 **27 项**、`EXIT=0`。
60
+ - **新增 `browser_drag`(工具数 36 → 37)**:`from` 按下 → 中间移动 → `to` 释放。两端复用 `browser_click` 的三种寻址(`x`+`y` / `selector` / `text`)并先滚入视野。`steps` 默认 12、上限 60 —— **中间移动是滑块与可排序库真正监听的东西,瞬移会被忽略**,单测把插值坐标逐一钉住(`[32.5, 55, 77.5, 100]`),并断言 `buttons` 在按下后保持为 1、释放时归 0(否则页面会看到卡住的按键)。冒烟在真实页面上注入探针,验证手势**跨 7 次事件**(悬停 + 6 次中间移动)、按在源、释放在目标。**已知限制**:只驱动指针式拖拽,依赖 HTML5 `dragstart`/`drop` 的页面不会响应(合成鼠标移动不产生原生拖放),描述里已写明。
61
+ - 🔴 **修复:点击在真实网站上完全不生效**。`browser_click`/`browser_double_click`/`browser_ref` 会返回 `{clicked:true}` 而**页面收不到任何鼠标事件** —— 实测 example.com / iana.org / httpbin.org 三个站点全部如此。
62
+ - **根因**:Chromium 在**渲染进程不认为自己被聚焦**时会丢弃合成的鼠标**按压**(移动不受此门控,所以 `hover` 看起来一直正常,掩盖了问题)。后台任务的视图天然不聚焦;真实 https 页面上连可见视图在其窗口未激活时也不聚焦。`data:` URL 的渲染器在进程内、不做这个门控,所以本地探针一直「看起来正常」,掩盖了它。
63
+ - **修法**:派发任何合成输入前,对每个视图调用一次 `Emulation.setFocusEmulationEnabled({ enabled: true })`(provider 侧,任何宿主都适用;用 `WeakSet` 保证每视图只调一次)。
64
+ - **关键**:这**保持在可信 CDP 输入路径上** —— 没有退化成 `element.click()`,事件仍是 `isTrusted`,因此**不削弱反检测能力**,不需要在「点击可用」与「更少触发人机验证」之间做取舍。
65
+ - 焦点模拟是**惰性**的:只有 agent 真正派发输入时才启用,纯人工浏览的会话永远不会打开它。
66
+ - **验证**:冒烟里三条断言(左键到达 `mousedown,mouseup,click` / 右键到达页面 `contextmenu` 且 `button:2` / 修饰键 `shiftKey+ctrlKey` 均为真)从**全红变全绿**,24 项 `EXIT=0`。
67
+ - **指针工具支持右键与修饰键**:`browser_click`/`browser_double_click` 新增 `button`(left/right/middle)与 `modifiers`(alt/ctrl/meta/shift),`browser_hover` 新增 `modifiers`。右键用于页面自定义上下文菜单(Electron 不装原生菜单,所以拿到事件的就是页面自己的 handler);修饰键用于 shift 扩选等。CDP 修饰位掩码复用既有的 `modifierMask()`。单测钉住位掩码(ctrl=2/shift=8)与「按下与抬起必须同键」——否则页面会看到卡住的按键。
68
+ - **集成冒烟扩到 21 项,覆盖工具层与并行**:新增 ① **真实工具层跑在真实 provider 上**(`browser_open`/`browser_content`/`browser_click`/`browser_snapshot`/`browser_scrape`/`browser_auth`)——单测用假浏览器、此前的冒烟直接调 provider,这是唯一让两者相遇的地方;② **工具输出形状检查**(逐字段比对声明 schema,因为 DSH 会在运行时校验输出,而直接调 `execute()` 绕过了它);③ **双任务并行**:A 跑 8 URL 抓取时,B 的 snapshot **13ms 返回**且 `aWasStillBusy:true`;④ **100 个 URL @ 并发 8**:100 行、100 个不同 `seq`、0 失败、**8.1 秒**、结束后**标签页数回到 1**(无泄漏)。
69
+ - **修 `text` 寻址在「逐字符 span」页面上匹配失败(真实集成测试抓到的真 bug)**:文字匹配此前枚举候选标签(`a, button, input, span, div, li, td…`),**漏了 `p` 等大量承载正文的标签**。现代站点常把正文/按钮文字拆成**逐字符的 `<span>`**(带淡入动画,example.com 现在就是这样),此时每个叶子只含一个字,**整句只存在于容器上**——容器不在候选表里就永远匹配不到。现改为**一次自底向上遍历**,让每个元素都算出自己的文本(不枚举标签、也不用逐元素 `textContent` 重复走子树)。实测同一页面:`text='This domain is for use'` 命中 `<p>` ✓,`text='Learn more'` 命中 `<a>` 而非包含它的 `<p>`(最短标签优先)✓。
70
+ - **新增真实集成冒烟 `npm run smoke:browser-tools`**:用**真实 Electron 宿主 + 真实 Chromium** 驱动**真实 provider**,覆盖 12 项(含 click 三种寻址、scrape 并发、cookie 导出→文件→导入往返)。这是唯一覆盖「provider → RPC → host-main → CDP → Chromium」整条链路的检查。它**自带 profile**(`DSH_BROWSER_PLUS_USER_DATA`),所以能在 DSH 运行时跑而不抢 profile 锁。**首次运行即抓到上面那个 bug。**
71
+ - **`DSH_BROWSER_PLUS_USER_DATA` 可覆盖宿主 profile**:Chromium 对 profile 加单例锁,此前无法并存两个宿主(验证脚本会与运行中的 DSH 冲突)。
72
+ - **指针工具支持选择器/文字寻址**:`browser_click`/`browser_double_click`/`browser_hover` 此前**只能给坐标**,想点一个按钮必须先 `browser_snapshot` 拿 `ref` 再 `click_ref`——**每次交互两轮**。现在三种寻址任选:`x`+`y`、`selector`、或 `text`(可见文字/aria-label/value,不区分大小写)。后两者在**页内解析**、把元素滚入视野,再派发**真实鼠标事件**(不是 `element.click()`,保留真实输入语义);返回 `target` 说明实际点到了什么。多个匹配时**最内层可见元素胜出**(文字最短优先,同长取更深者),避免点到包住按钮的容器。页内脚本抽成可导出的 `pointerTargetScript()`,因此新增了一条**构建期语法测试**(页内脚本只在浏览器里解析,写错要等运行时才炸)与注入防护断言。
73
+ - **`browser_scrape` 支持并发**:新增 `concurrency`(默认 1,上限 8),每个 worker 占一个自己的标签页,从共享队列取 URL。6 个 URL / 3 并发的测试验证「每个 URL 恰好访问一次、`seq` 覆盖 0..n-1、worker 标签页全部回收」。每行新增 **`seq`**(URL 在输入里的下标):并发时行按完成顺序落盘,按 `seq` 排序即可还原原始顺序。
74
+ - **`browser_scrape` 改用独立标签页**:此前批次调用的是作用于「会话当前激活标签页」的 `navigate`/`execute`,于是 ① 批次运行期间任何工具调用都会和它**抢同一个标签页**,抽取可能拿到错误的页面;② 用户正在读的页面被导航冲掉。现在批次创建**自己的标签页且不激活它**,结束后销毁。为此把 `navigate`/`execute`/`waitForElement` 的核心抽成按标签页的私有方法(公开方法变成一行转发),为后续并发打底。
75
+ - **后台批次跳过 250ms 绘制等待**:`waitForDocumentReady` 在 `document.readyState === complete` 后会**固定再等 250ms**,目的是让截图/快照不抓到空白渲染——但抓取只读 **DOM** 不读像素,这一等纯属浪费(1000 个 URL 白等 250 秒)。现在该延迟可参数化,后台批次传 0。**实测单页开销从 ~267ms 降到 ~6ms**(测试里 2 个 URL 的批次 534ms → 12.7ms)。
76
+ - **新增 `browser_scrape`:批量抓取,结果直接落盘**:后台批量访问 URL,每页把一行 JSON 追加到文件,**结果完全不经过模型**——一千条与一条的 token 成本相同。`action=start` 立即返回(单次工具调用只有 ~60s 预算,大批次要跑几分钟),用 `action=status` 轮询,`action=stop` 停止且**保留已写入的行**(每行产生即落盘)。每行是 `{ url, ok, data }` 或 `{ url, ok, error }`;**单页失败不终止整批**;页面返回不可序列化的值(循环引用/BigInt)也只让该行降级为错误行。`outPath` 受 `writeRoots` 限制并在开始时截断(重跑不会混入上一批)。工具总数 35 → **36**。
77
+ - **`browser_auth` 支持从文件导入登录状态**:新增 `file` 参数(`action=restore`),从 JSON 文件读取 cookie 列表 —— 真实导出动辄几百条,内联经模型传递不现实。接受裸数组或 `{"cookies": [...]}`;不合法条目跳过并在 `failed` 中计数。路径受 `browser-electron.readRoots` 白名单约束(与 `browser_upload_file` 同一道闸),越界抛 `BROWSER_READ_PATH_DENIED`;文件不可读/形状不对抛 `BROWSER_AUTH_FILE_INVALID`。**注意**:Chrome/Edge 127+ 的 App-Bound Encryption 使 cookie 无法从磁盘解密(复制 profile 也无效,实测取到 0 条),所以只能导入用户自己导出的文件,或直接在本插件浏览器里用「接管」登录一次。
78
+ - **对齐请求指纹,减少被判定为机器人**:实测服务端收到的请求里有三处明显破绽 —— ① `User-Agent` 里带着 `Electron/42.9.3`;② **完全没有 `sec-ch-ua` 系列头**(真 Chrome 必发);③ `Accept-Language` 只有光秃秃的 `zh-CN`(真 Chrome 是带 q 权重的列表)。现在:剥离 Electron 令牌、补上与引擎自身 `navigator.userAgentData` 一致的 client hints、把语言列表交给 Chromium 自己加权。**关键是三者互相一致**(请求头与页面内 JS 说法相同),而不是单点伪装。新增配置 `browser-electron.userAgent`(整体替换)与 `maskAutomation`(默认 `true`,设 `false` 则原样发送引擎指纹)。纯逻辑抽到 `fingerprint.ts` 并有真实单测。
79
+ - **新增深色空状态页,并修复空白视图上所有操作超时**:新建的 `WebContentsView` 在首次导航前**没有提交任何文档** —— 既按默认白底渲染(和整套深色 UI 格格不入),又让 CDP **没有 frame 可 evaluate**,于是 `browser_execute`/`browser_snapshot`/`browser_content` 全部 30s 超时、`browser_screenshot` 直接报错。现在视图创建时立即加载一个惰性(无可交互元素)的深色空状态页,窗口也设了深色 `backgroundColor`。**两个症状是同一个根因**。
80
+ - **修复写盘白名单默认值从未生效**:`entry.ts` 的 `writeRoots`/`readRoots` 声明为 `z.array(z.string())` 而没有默认值,而 schemastery 会把**缺省键物化成 `[]`**;`[]` 不是 nullish,于是 provider 的 `config.writeRoots ?? defaultWriteRoots()` **永远走不到默认分支** —— 结果是文档承诺的「默认 = 工作目录 + 系统临时目录」从来不成立,`browser_screenshot(savePath)` / `browser_download` / `browser_upload_file` 一律以「none configured」被拒。现在默认值在 **schema 层**物化,既让缺省等于文档默认值,又保住「显式 `[]` = 全禁」这个真实语义。
81
+ - **可选:把注入 chrome 移进隔离世界**(`browser-electron.chromeWorld: isolated`,**默认仍是 main**)。开启后 chrome 经 `Page.createIsolatedWorld` 注入自己的 JS 世界,`Runtime.addBinding` 也用 `executionContextName` 限定在其中——被访问页面**读不到** `window.__dshTasks` / `window.__dshTrail` / `window.__dshBrowserTaskAction`,连「提前 hook JSON.stringify 偷 binding token」这条残留路径也一并消失。四条注入路径(挂载、bootstrap、patch、active 标记)统一走 `runChromeScript`,导航时丢弃旧 world 以便为新文档重建。**默认未切换**:该改动重写工具栏注入路径,必须在真实窗口按 `docs/SOAK-CHECKLIST.md` 第 8 节逐项验证后再考虑改默认值。
82
+ - **下载改由子进程直接落盘**:此前子进程把整包 base64 塞进一行 JSON 回传,父进程再解码写盘——64MiB 的下载在 host→parent→disk 路径上要复制约 8 份(含 RPC 行缓冲)。现在子进程自己写文件、只回 `{ bytes }`,下载体**完全不再经过 RPC socket**,峰值内存与 `MAX_RPC_BUFFER_BYTES` 的压力同时消失。
83
+ - **测试 seam 移出声明的 API 面**:`tsconfig` 打开 `stripInternal`,`internals` 与 `DeferredRemoteView` 不再出现在 `lib/*.d.ts` 里(运行时导出保留,定点测试照常可用)。
84
+ - **chrome 重装改由宿主执行**:provider 在导航后注入的是**无 token** 的 `PAGE_CHROME_SCRIPT`,而只有宿主持有每视图 token——一旦宿主自身的注入失败,退化的那份会让工具栏按钮静默失效。新增可选的 `reinstallChrome()` seam:自托管宿主经 RPC 重装 token 版,不提供该能力的宿主(桌面外壳)仍走原回退。
85
+ - **每次调用的 `timeoutMs` 被夹在工具预算之下**:`browser_wait_for`/`browser_content` 此前把调用方给的值原样下传,所以 `timeoutMs: 90000` 实际会在 60s 被运行时掐断,模型只会收到一条笼统的 tool timeout。现在夹到「预算 − 5s」,并在参数描述里写明上限,让 provider 先给出干净的 `BROWSER_OPERATION_TIMEOUT`。
86
+ - **历史记录不再全量深拷贝**:`browser_history` 此前对每条 entry 做 `JSON.parse(JSON.stringify(params))`,把完整脚本与输入文本逐条复制一遍;现在只做浅拷贝并对超长字符串截断(附剩余字数),同时保持 lossless JSON。
87
+ - **单条历史有存储上限**:`execute` 的脚本与 `type` 的文本超过 32KiB 时会被截断并打标;`browser_replay` 遇到被截断的条目会明确拒绝(`BROWSER_HISTORY_TRUNCATED`),而不是重放一个被悄悄剪短的脚本。
88
+ - **补上两处零覆盖**:① `entry.ts` 的组合入口(外部 viewHost 优先 / 缺省自托管 / 销毁时只 dispose 自托管 host / 配置透传)此前没有任何测试;② 页面 chrome 的 patch 握手(epoch 与 revision 连续性判定)此前只有「标识符还在」的源码断言——现在把它抽成纯函数 `decideChromeMessage` 并真跑:丢包、乱序、重放、跨文档都要 resync 而不是部分应用。
89
+ - **自愈不再在空白页上重放输入**:主机崩溃后重建的视图是 `about:blank`,而 `Input.*` 在空白文档上会「什么都不做但正常 resolve」,于是 `browser_click`/`browser_type` 会在页面上什么都没发生的情况下报成功。现在这类命令不再自动重放,而是抛 `BROWSER_HOST_RESTARTED` 并提示重开页面(读类命令仍照常自愈)。
90
+ - **快照 `truncated` 语义修正**:此前用 `out.length >= cap` 判断,页面恰好有 cap 个可见候选时会误报截断;现在只有真的因达到上限而提前跳出才算截断。
91
+ - **`browser_scroll` 描述与实现对齐**:实际是「约一屏(视口高度的 80%,最少 480px)」,描述原写「one viewport」。
92
+ - **新增 CI**:`.github/workflows/ci.yml` 在 push/PR 上跑 `npm ci --omit=optional` + `npm test`,并额外用 `git diff --exit-code -- lib` 校验**已提交的 `lib/`** 与 `src/` 一致——这正是 `pretest` 会掩盖的那类漂移(改了 src、本地重建了 lib,却没提交重建结果)。
93
+ - **新增 ESLint 最小集**:只启用类型感知的 `no-floating-promises` / `no-misused-promises` 与两条一致性规则,不引入格式化以免搅动现有风格。首次运行即发现一处真实缺陷:`setWindowOpenHandler` 里的 `loadURL()` 未被 await 也未挂 rejection handler,失败时会产生未处理拒绝。
94
+ - **`CHANGELOG.md` 现在会进 npm tarball**(此前 `files` 未列)。
95
+ - **缩略图捕获加超时**:`capturePage()` 在合成器卡住时可能永不 settle,而 `finally` 不会执行 → 单飞标志永远为 true,此后缩略图静默停更。现在 5s 超时按失败处理。
96
+ - **减少跨进程 IPC**:`syncVisibleTaskVisibility` 此前对**每个** view(含其它任务的隐藏页与后台标签)各发一次 `executeJavaScript`;现在只对 active 状态真正变化的 view 发送。
97
+ - **`installPageChrome` 的 active 判定改用 viewId**:此前只比 taskKey,导致可见任务的**后台标签页**导航时被当成「正在显示」,会启动其页面定时器并顶掉真正可见视图的 chrome epoch。
98
+ - **`restoreAuth` 逐条隔离**:此前任一条 cookie 非法即整批 reject、已写入不可回滚、调用方也拿不到计数;现在逐条 try/catch 并回报 `{restored, failed}`。
99
+ - **会话丢失会自动重开**:工具层按任务缓存 session id;若 provider 被重载(实例换了、不再认识旧 id),此前该任务之后**每次**调用都报 `BROWSER_SESSION_UNKNOWN`,只能靠人想到调 `browser_reset_session`。现在检测到该错误会丢弃缓存并重开一次(仅此一种错误会重试)。
100
+ - **`browser_close_tab` 如实返回**:此前无条件 `{closed:true}`,render 里「Tab not found.」是死代码;未知 tabId 与成功无法区分。现在 provider 返回布尔值,工具层如实回填。
101
+ - **`browser_handoff` 纳入白名单**:它不是只读工具(会改任务控制状态),此前不受 `browser_restrict` 约束。
102
+ - **`browser_screenshot` 仅在写盘时受白名单约束**:不带 `savePath` 的截图仍是只读,保持「只读工具永不拦截」的承诺;带 `savePath` 时按写盘工具守卫。
103
+ - **`browser_reset_session` 纳入每任务 FIFO**:此前直接 close,可能把并发排队操作正在使用的会话/视图拆掉。
104
+ - **`browser_restrict` 校验名字是否存在**:此前只校验 `browser_` 前缀,拼错(如 `browser_snapsho`)会被接受并静默拒绝该任务所有受守卫动作。
105
+ - **输入派发补上超时兜底**:`click`/`clickRef`/`doubleClick`/`hover`/`type`/`pressKey` 的 8 处 `Input.*` 派发此前是裸 `await`,页面主线程被同步 JS 阻塞时会一直挂到工具预算耗尽;现在与其它 CDP 调用一样有 15s 上限并响应调用方 signal。
106
+ - **超时错误带稳定 code**:`withTimeout` 现在抛 `BROWSER_OPERATION_TIMEOUT`(保留 `TimeoutError` 名称),此前只有 `execute`/`waitForElement` 两处归一化,其余超时是裸 Error,无法按 code 分支。
107
+ - **快照重试有总预算且尊重取消**:空清单重试此前最多 5 次、每次可等满求值超时(理论上约 182s,远超 60s 工具预算),且 `.catch(() => undefined)` 会吞掉 abort 继续重试。现在整段重试有 3s 预算,abort 会立即中止并上抛。
108
+ - **`browser_fill` 不再静默假成功**:checkbox/radio 点击后回读 `checked`,未变更(disabled 或被处理器取消)报 `ok:false`;input/textarea 在请求了非空值却得到空值时报错;`submit` 改为锚定**实际填入成功**的最后一个字段所在表单(此前取第一个能解析到的字段),并在 `requestSubmit()` 因 HTML5 约束校验不通过而**不提交**时如实返回 `submitted:false`(此前一律报 true)。
109
+ - **`browser_content format=json` 不再恒返回 `{}`**:DOM 元素没有自有可枚举属性,`JSON.stringify(document.body)` 永远是 `{}`。现在返回有界的结构视图(标签/id/class/子节点/叶子文本),若文本本身就是 JSON 则原样透传。
110
+ - **`browser_press_key` 支持标点**:此前只认字母数字与功能键,`Ctrl+-`、`Ctrl+/`、`,`、`.`、`[`、`]` 等一律抛 `BROWSER_KEY_UNKNOWN`,而工具描述却写「single characters」。现在按 US 布局补全可打印 ASCII。
111
+ - **`browser_wait_for` 非法选择器快速失败**:此前会每 250ms 重试直到超时(默认 15s)再报一个误导性的 `BROWSER_WAIT_TIMEOUT`,现在立即抛 `BROWSER_SELECTOR_INVALID`。
112
+ - **工具栏不再吞掉代理点击**:注入 chrome 的顶部中央 280×56 感应区在捕获阶段 `preventDefault` + `stopImmediatePropagation`,且从不检查 Agent 输入抑制窗口,导致落在该带的 CDP 点击到不了页面、`browser_click` 却报成功;抽屉打开后无自动关闭,死区还会扩大到约 940×42。现在感应与触发都会在 Agent 输入期间让路。
113
+ - **缩略图失败不再 5Hz 重试**:抓取失败(空图/编码失败/抛错)时 dirty 标记未清除,`finally` 每 200ms 重排一次,任务面板打开期间会以 5 次/秒无限抓屏。失败路径现在清除标记,只在有新动作时才重试。
114
+ - **下载上限改为流式判定**:此前先 `arrayBuffer()` 读完整包再比 64MiB,超大响应会先撑爆渲染进程,上限形同虚设;现在先看 `content-length`,再边读边累计并在超限时 `cancel()` 流,同时给页内 fetch 加了超时。
115
+ - **`browser_upload_file` 加读白名单**:此前只有写有白名单,上传可把任意本地文件交给页面(绕过 DSH 文件策略)。现在 `filePath` 必须存在且落在 `browser-electron.readRoots`(默认同 `writeRoots`)之内,校验在触碰 DOM 之前完成,越界抛 `BROWSER_READ_PATH_DENIED`。
116
+ - **任务摘要不再下发缩略图**:`window.__dshTasks` 走页面主世界,此前携带可见任务的 288px JPEG,任意页面可据此读走其他任务的屏幕内容。摘要只保留 `thumbnailVersion`,图片仅经定向的 `task.thumbnail` 补丁下发。写盘拒绝消息也不再回显允许根路径。
117
+ - **`browser_restrict` 改为按任务隔离**:此前白名单是模块级全局状态,一个任务设置后会把**所有**并行任务的浏览器工具一起限制。现在规则按调用任务存储,插件级 `tool-browser.allowedActions` 作为默认值,单个任务可用空列表只为解除自己。
118
+ - **页面可见轨迹脱敏**:注入页面的 `window.__dshChromeBootstrap.trail` 只保留展示所需字段——`type` 折叠为字符数、`execute` 丢弃脚本、URL 折叠为 origin、路径折叠为 basename。此前被访问页面可用一行 JS 读走同任务中早前站点输入的文本(含密码)与执行过的脚本。
119
+ - **页面→宿主控制通道加每视图 token**:`__dshBrowserTaskAction` 的 payload 必须携带 `createView` 生成的随机 token。此前任意页面脚本可伪造 `set-control-owner=human` 冻结该任务的 Agent 自动化。残留风险:页面若在 chrome 注入前 hook `JSON.stringify` 仍可能窃取 token,彻底解法是 isolated world 注入(后续工作)。
120
+ - **读操作纳入每任务 FIFO**:`snapshot`/`content`/`screenshot`/`list_tabs`/`history`/`session`/`space(list)` 现在会等待在飞写操作,修复并发 `browser_open` + `browser_snapshot` 读到导航前页面的竞态;同 readKey 的 in-flight 去重保留,`operationTails` 改为队列空闲即回收。
121
+ - **主机自愈改用稳定错误码**:新增 `BROWSER_HOST_DEAD` 与 `isBrowserHostDead()`;子进程在**调用中途**崩溃(exit / spawn error / socket close / 缓冲溢出 / RPC 超时)现在与"先崩再调"一样自愈一次,不再依赖错误文案匹配。disposed 之后不再重启子进程。
122
+ - **`available()` 实检**:`ElectronBrowserViewHost` 新增可选 `isAvailable?()`;自托管 host 落地实现(带缓存),Electron 缺失时如实上报不可用,seam 的 provider 选择错误码得以真正生效。
123
+ - **markdown 抓取修复**:`browser_content format=markdown` 的 walker 现在递归块级容器,标题/链接/列表在常见 `div` 嵌套下不再退化成纯文本。
124
+ - **下载内存**:子进程下载上限 256MiB → 64MiB,父进程 RPC 缓冲 512MiB → 128MiB(按 base64 推导保留 1.5× 余量)。
125
+ - **`browser_upload_file`**:补 30s 超时与 Agent 输入抑制标记,与其它输入工具一致。
126
+ - **`internals`**:标注 `@internal`;因 `lib/tool-browser/index.d.ts` 仍会导出,未真正移出公共 API 面(后续)。
127
+ - **写入路径白名单**:`browser_screenshot` 与 `browser_download` 只能写入 `browser-electron.writeRoots`(默认工作目录 + 系统临时目录)之内的路径;越界抛出 `BROWSER_WRITE_PATH_DENIED` 且不落盘。路径解析会处理最深已存在祖先的真实路径,防 `..` 与符号链接逃逸。
128
+ - **下载共用导航准入**:`browser_download` 与 `browser_navigate` 走同一套 URL 准入(仅 HTTP(S),拒绝 URL 内嵌凭据),不再绕过 `httpOnly`。
129
+ - **测试可信度**:`npm test` 现在先执行 `tsc`(`pretest`),不再对可能过期的构建产物 `lib/` 做假绿测试;快速迭代可用 `npm run test:only`。
130
+ ## v0.4.2 (2026-09-17)
131
+
132
+ - **按站点清理 Cookie**: `browser_auth action="clear"` 支持按 `domain`(含子域)与/或 `name` 精确删除 Cookie;未限定范围时必须显式 `all: true`,避免误清全部登录态。用于清理 WAF 轮换名称留下的旧代挑战 Cookie。
133
+
134
+ ## v0.4.1 (2026-08-26)
135
+
136
+ - **多标签会话恢复**: keyed browser sessions are recovered when the tool-layer session cache is lost, so the first direct switch or close operation still targets the existing tabs.
137
+
138
+ ## v0.4.0 (2026-08-26)
139
+
140
+ - **显式人机交接**: 任务卡显示运行、等待用户、用户接管、失败和空闲状态;用户可在页面中接管/交还任务,`browser_tasks` 与 `browser_handoff` 暴露同一状态。
141
+ - **语义浏览控制**: 新增后退、前进、刷新、停止、滚动,以及由 `snapshotId` 和元素 ref 驱动的精确点击/滚动到元素工具。
142
+ - **轻量工作区同步**: Host 改为 bootstrap + versioned patch;常规操作只更新一张任务卡和一条轨迹。
143
+ - **资源预算**: 任务缩略图仅在任务面板打开时按需单飞捕获,带 2 秒节流和 32 项缓存;后台页面停止地址栏和用户活动轮询。
144
+ - **低干扰工具栏**: 工具栏默认隐入页面上方,顶部中间悬停出现圆形下箭头;展开后最右侧上箭头可收起工具栏及其关联浮层。
145
+
146
+ ## v0.3.1 (2026-08-23)
147
+
148
+ - **单窗口任务管理器**: 所有 DSH 任务共享一个可见浏览器窗口,同时保留隔离的任务视图、标签和历史;页面任务管理器切换可见任务,后台任务操作不会抢走当前页面。
149
+ - **任务标签**: `browser_space` 命名或列出浏览器任务,不再表示原生窗口;任务标签显示在任务管理器和活动窗口标题。
150
+ - **可视工作区**: 任务与操作轨迹可同时打开,切换任务同步轨迹,并显示可见页面的实时缩略图。
151
+ - **Browser Flow 图标**: 新增 SVG 主源、PNG/ICO 衍生资源及 Electron 窗口图标接入。
152
+
153
+ ## v0.3.0 (2026-08-21)
154
+
155
+ Ego 级功能集:
156
+
157
+ - **JS 对话框**:宿主自动 accept(页面永不卡死),草案以 `drainDialog` 读回并写入 `browser_history`(`dialog` 记录)。
158
+ - **输入工具**:`browser_press_key`(CDP keyDown/keyUp,修饰键位掩码)、`browser_double_click`(clickCount 2)、`browser_hover`(mouseMoved)、`browser_upload_file`(DOM.setFileInputFiles 真实文件选择)。
159
+ - **等待与定位**:`browser_wait_for`(250ms 有界轮询,`BROWSER_WAIT_TIMEOUT`);快照每个元素输出 `loc=`(id/name/aria-label/text 定位链)。
160
+ - **每任务窗口**:每个 DSH 任务一个独立 `BrowserWindow`(createView key);`browser_space` 命名窗口标题并列出全部窗口。
161
+ - **稳定性**:Electron 锁定 42.9.3(43.4.1 组合器故障);capture CDP 回退仅 detach 同窗口视图;截断/挂起防护(per-poll 超时)。
162
+ - **质量**:32/32 测试(FakeHost 行为测试 12 条 + 源码断言/页面 chrome 断言);SDD 全流程评审(每任务 implement->review->fix 循环 + 整支 final review)。
163
+
164
+ ## v0.2.0 (2026-08-21)
165
+
166
+ 首版 `dsh-browser-plus`:共享可见浏览器、ego 风格页面内工具栏、操作轨迹(trail)面板、用户控制检测、稳定单视图合成。
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ParticleLight
4
+ Copyright (c) 2026 dsh-browser contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/NOTICE.md ADDED
@@ -0,0 +1,7 @@
1
+ # Notice
2
+
3
+ This repository contains independently maintained work for **dsh-browser-plus**.
4
+
5
+ Some components were originally derived from the MIT-licensed `dsh-browser` codebase. The upstream MIT copyright notice remains in [LICENSE](LICENSE), as required by that license.
6
+
7
+ All subsequent product design, branding, runtime recovery behavior, documentation, tests, and maintenance in this repository are maintained by ParticleLight.
package/README.en.md ADDED
@@ -0,0 +1,100 @@
1
+ # dsh-browser-plus
2
+
3
+ > A visible browser runtime for DeepSeek Harness. Humans and agents operate the same real page, not a headless replay or screenshot proxy.
4
+
5
+ dsh-browser-plus is developed on top of the MIT-licensed `dsh-browser` codebase and independently maintained by ParticleLight.
6
+
7
+ [![awesome · DSH plugin](https://img.shields.io/badge/awesome-DSH_plugin_%E2%9C%94-red?style=flat)](#)
8
+ [![npm](https://img.shields.io/npm/v/dsh-browser-plus?style=flat&label=npm&color=blue)](https://www.npmjs.com/package/dsh-browser-plus)
9
+ [![downloads](https://img.shields.io/npm/dm/dsh-browser-plus?style=flat&label=downloads&color=blue)](https://www.npmjs.com/package/dsh-browser-plus)
10
+ [![GitHub Stars](https://img.shields.io/github/stars/ParticleLight/dsh-browser-plus?style=flat&label=Stars&logo=github)](https://github.com/ParticleLight/dsh-browser-plus)
11
+ [![license](https://img.shields.io/github/license/ParticleLight/dsh-browser-plus?style=flat&label=license&color=green)](LICENSE)
12
+ [![CI](https://img.shields.io/github/actions/workflow/status/ParticleLight/dsh-browser-plus/ci.yml?branch=main&style=flat&label=CI&logo=github)](https://github.com/ParticleLight/dsh-browser-plus/actions)
13
+ [![DSH](https://img.shields.io/badge/DSH-0.1.0--rc.1%20%7C%7C%200.2.0--rc.1-blue?style=flat)](#)
14
+ [![status](https://img.shields.io/badge/status-stable-green?style=flat)](#)
15
+
16
+ ## Why it exists
17
+
18
+ Browser automation should not disappear into a process the user cannot inspect. dsh-browser-plus keeps the browser window visible while giving agents reliable CDP control.
19
+
20
+ - **Visible by default**: a real Electron `WebContentsView`, not a headless relay.
21
+ - **Task isolation**: all DSH sessions share one visible window while keeping isolated task views, tabs, and history; the page task manager switches the visible view, and `browser_space` names browser tasks.
22
+ - **Human handoff**: page chrome, bookmarks, the task workspace, operation trail, and user activity detection live on the real page; a user can take control of the active task and explicitly return it to the agent.
23
+ - **Task workspace**: task and operation trail are independent opaque panels (Chrome menu surface, `#292a2d`) that can stay open together. Each task exposes running, waiting-user, human-control, failed, or idle state. Thumbnails refresh on demand only while the task panel is open; background tasks retain their last image.
24
+
25
+ ![dsh-browser-plus task workspace](assets/readme-workspace.png)
26
+
27
+ - **Physical input**: keyboard, mouse, hover, double-click, and file selection use CDP instead of synthetic `element.click()` events.
28
+ - **Recovery-aware**: a recycled child re-materializes the same session view; the first recovered capture waits for compositor readiness.
29
+ - **Stable baseline**: Electron 42.9.3 is pinned; the resolver rejects Electron 43.4.1 because of compositor failures.
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ dsh plugin --profile web add github:ParticleLight/dsh-browser-plus
35
+ ```
36
+
37
+ If another browser bundle is already installed, read the [migration guide](docs/MIGRATION.md), then restart DSH Web.
38
+
39
+ ## Main capabilities
40
+
41
+ | Scenario | Tools |
42
+ | --- | --- |
43
+ | Open and inspect | `browser_open`, `browser_snapshot`, `browser_content`, `browser_screenshot` |
44
+ | Native navigation | `browser_back`, `browser_forward`, `browser_reload`, `browser_stop`, `browser_scroll` |
45
+ | Snapshot references | `browser_click_ref`, `browser_scroll_into_view` |
46
+ | Page interaction | `browser_click`, `browser_press_key`, `browser_double_click`, `browser_hover`, `browser_type` |
47
+ | Forms and files | `browser_fill`, `browser_upload_file`, `browser_wait_for` |
48
+ | Tasks and handoff | `browser_tasks`, `browser_handoff`, `browser_list_tabs`, `browser_switch_tab`, `browser_close_tab`, `browser_space` |
49
+ | Auth and recovery | `browser_auth`, `browser_reset_session`, `browser_history` |
50
+ | Dialogs | `browser_dialog` |
51
+ | Diagnostics | `browser_console`, `browser_network` |
52
+ | Device emulation | `browser_emulate` |
53
+ | Dragging | `browser_drag` |
54
+ | Page scripts | `browser_execute` |
55
+ | Bulk scraping | `browser_scrape` |
56
+ | Downloads | `browser_download` |
57
+ | Replay | `browser_replay` |
58
+ | Bot checks | `browser_challenge` |
59
+ | Action allow-list | `browser_restrict` |
60
+ | Session | `browser_session`, `browser_reset` |
61
+
62
+ Snapshots expose a short-lived `snapshotId` plus element references. Prefer reference tools and take a fresh snapshot after the page changes; page-level scripts automatically ignore the browser's own chrome.
63
+
64
+ ## How it works
65
+
66
+ ```text
67
+ browser_* tools
68
+ -> BrowserRuntime (ctx.browser seam)
69
+ -> ElectronBrowserProvider (CDP)
70
+ -> RemoteElectronViewHost (loopback JSON-RPC)
71
+ -> host-main.js (BrowserWindow + WebContentsView)
72
+ ```
73
+
74
+ The chrome and task manager are injected through a closed Shadow DOM rather than a second Electron view. Versioned incremental workspace updates keep task and trail rendering light while background task updates stay isolated and do not steal the user's visible page.
75
+
76
+ `alert`, `confirm`, and `prompt` are accepted immediately **by default** so pages do not block, and the detail is recorded as a `dialog` item in `browser_history`. To drive a page that confirms a destructive action, set how the NEXT dialog is answered with `browser_dialog` (`accept` / `dismiss`, with `promptText` for `prompt()`) and then trigger it; `inspect` reports the last one.
77
+
78
+ ## Reliability rules
79
+
80
+ 1. Never reparent a visible `WebContentsView`.
81
+ 2. The CDP capture fallback only touches siblings in the same window and restores them.
82
+ 3. Native and full-page capture wait once for compositor readiness after child recovery.
83
+ 4. Dialogs, captures, dynamic waits, and child recovery have regression tests and live SOAK coverage.
84
+
85
+ See [SOAK-CHECKLIST](docs/SOAK-CHECKLIST.md) for the complete runtime verification list.
86
+
87
+ ## Development
88
+
89
+ ```sh
90
+ npm install
91
+ npm run build
92
+ npm test
93
+ npm run smoke:electron-host # requires a local DSH Web instance; validates real Electron Host navigation and page handoff
94
+ ```
95
+
96
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution rules, [docs](docs/README.md) for the full documentation set, and [CHANGELOG](CHANGELOG.md) for version history.
97
+
98
+ ## License
99
+
100
+ MIT. See [LICENSE](LICENSE) and [NOTICE](NOTICE.md).
package/README.md CHANGED
@@ -1,3 +1,100 @@
1
- # Temporary Holding Version
1
+ # dsh-browser-plus
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 专为 DeepSeek Harness(DSH)打造的 EGO 风格可视化 Agent 浏览器。基于 `dsh-browser` 构建,支持人机协同操作:用户可以在真实页面中操作,Agent 也能同步执行、接管和恢复任务。浏览器支持多任务流并行管理与切换,并完整记录 Agent 的操作轨迹;任务流和操作轨迹均配备独立的可视化菜单面板,让任务状态、页面缩略图与操作过程一目了然。
4
+
5
+ 基于 MIT 许可的 `dsh-browser` 代码基础持续开发,并由 ParticleLight 独立维护。
6
+
7
+ [![awesome · DSH plugin](https://img.shields.io/badge/awesome-DSH_plugin_%E2%9C%94-red?style=flat)](#)
8
+ [![npm](https://img.shields.io/npm/v/dsh-browser-plus?style=flat&label=npm&color=blue)](https://www.npmjs.com/package/dsh-browser-plus)
9
+ [![downloads](https://img.shields.io/npm/dm/dsh-browser-plus?style=flat&label=downloads&color=blue)](https://www.npmjs.com/package/dsh-browser-plus)
10
+ [![GitHub Stars](https://img.shields.io/github/stars/ParticleLight/dsh-browser-plus?style=flat&label=Stars&logo=github)](https://github.com/ParticleLight/dsh-browser-plus)
11
+ [![license](https://img.shields.io/github/license/ParticleLight/dsh-browser-plus?style=flat&label=license&color=green)](LICENSE)
12
+ [![CI](https://img.shields.io/github/actions/workflow/status/ParticleLight/dsh-browser-plus/ci.yml?branch=main&style=flat&label=CI&logo=github)](https://github.com/ParticleLight/dsh-browser-plus/actions)
13
+ [![DSH](https://img.shields.io/badge/DSH-0.1.0--rc.1%20%7C%7C%200.2.0--rc.1-blue?style=flat)](#)
14
+ [![status](https://img.shields.io/badge/status-stable-green?style=flat)](#)
15
+
16
+ ## 为何使用它
17
+
18
+ 浏览器自动化不应躲在用户看不见的进程里。dsh-browser-plus 把浏览器窗口留在用户眼前,同时让 agent 获得可靠的 CDP 操作能力。
19
+
20
+ - **真实可见**:Electron `WebContentsView`,不是 headless relay。
21
+ - **任务隔离**:所有 DSH session 共享一个可见窗口,但各自保留隔离的任务视图、标签与历史;页面任务管理器切换可见视图,`browser_space` 命名浏览器任务。
22
+ - **人机协作**:工具栏是**常驻顶栏**(画在独立视图里,不随页面缩放);书签、任务工作区与操作轨迹都在真实页面上运行,用户可在工具栏最右侧接管或显式交还给 Agent。
23
+ - **任务工作区**:任务与操作轨迹是彼此独立的不透明面板(Chrome 菜单表面 `#292a2d`),可同时打开;每项任务显示执行中、等待用户、用户接管、失败或空闲状态。缩略图仅在任务面板打开时为当前可见任务按需刷新,后台任务保留最后图像。
24
+
25
+ ![dsh-browser-plus 任务工作区](assets/readme-workspace.png)
26
+
27
+ - **真实输入**:键盘、鼠标、hover、双击和文件选择都走 CDP,而不是 `element.click()` 伪事件。
28
+ - **恢复能力**:child 回收后会重新物化相同会话的视图;恢复后的首张截图等待 compositor 稳定。
29
+ - **稳定基线**:固定 Electron 42.9.3;43.4.1 的 compositor 故障会被 resolver 拒绝。
30
+
31
+ ## 安装
32
+
33
+ ```sh
34
+ dsh plugin --profile web add github:ParticleLight/dsh-browser-plus
35
+ ```
36
+
37
+ 已有浏览器 bundle 时,先阅读[迁移指南](docs/MIGRATION.md),然后重启 DSH Web。
38
+
39
+ ## 主要能力
40
+
41
+ | 场景 | 工具 |
42
+ | --- | --- |
43
+ | 打开与读取 | `browser_open`、`browser_snapshot`、`browser_content`、`browser_screenshot` |
44
+ | 语义导航 | `browser_back`、`browser_forward`、`browser_reload`、`browser_stop`、`browser_scroll` |
45
+ | 快照引用 | `browser_click_ref`、`browser_scroll_into_view` |
46
+ | 页面交互 | `browser_click`、`browser_press_key`、`browser_double_click`、`browser_hover`、`browser_type` |
47
+ | 表单与文件 | `browser_fill`、`browser_upload_file`、`browser_wait_for` |
48
+ | 任务与交接 | `browser_tasks`、`browser_handoff`、`browser_list_tabs`、`browser_switch_tab`、`browser_close_tab`、`browser_space` |
49
+ | 登录与恢复 | `browser_auth`、`browser_reset_session`、`browser_history` |
50
+ | 对话框 | `browser_dialog` |
51
+ | 诊断 | `browser_console`、`browser_network` |
52
+ | 设备模拟 | `browser_emulate` |
53
+ | 拖拽 | `browser_drag` |
54
+ | 页面脚本 | `browser_execute` |
55
+ | 批量抓取 | `browser_scrape` |
56
+ | 下载 | `browser_download` |
57
+ | 回放 | `browser_replay` |
58
+ | 人机验证 | `browser_challenge` |
59
+ | 动作白名单 | `browser_restrict` |
60
+ | 会话 | `browser_session`、`browser_reset` |
61
+
62
+ 快照返回短生命周期的 `snapshotId` 与元素引用;优先用 `browser_click_ref` 或 `browser_scroll_into_view` 操作,页面变化后重新快照。页面级脚本会自动过滤浏览器自身 chrome。
63
+
64
+ ## 工作方式
65
+
66
+ ```text
67
+ browser_* tools
68
+ -> BrowserRuntime (ctx.browser seam)
69
+ -> ElectronBrowserProvider (CDP)
70
+ -> RemoteElectronViewHost (loopback JSON-RPC)
71
+ -> host-main.js (BrowserWindow + WebContentsView)
72
+ ```
73
+
74
+ 页面 chrome 和任务管理器通过 closed Shadow DOM 注入,而不是第二个 Electron view;任务状态与轨迹以版本化增量消息同步,后台任务更新自己的隔离视图,不会抢走用户当前可见页面。
75
+
76
+ `alert`、`confirm`、`prompt` **默认**会被立刻接受,避免页面卡死,详情记到 `browser_history` 的 `dialog` 条目。要驱动「确认删除」这类页面,先用 `browser_dialog` 设好**下一个**对话框怎么答(`accept` / `dismiss`,`prompt()` 可配 `promptText`)再触发它;`inspect` 报告上一次。
77
+
78
+ ## 可靠性规则
79
+
80
+ 1. 不 reparent 可见 `WebContentsView`。
81
+ 2. CDP capture 回退只临时处理同一窗口的 sibling,并保证恢复。
82
+ 3. child 恢复后,native 和 full-page capture 都只等待一次 compositor settle。
83
+ 4. 对话框、截图、动态等待和 child recovery 均有回归测试与真实 SOAK 覆盖。
84
+
85
+ 完整运行时检查见 [SOAK-CHECKLIST](docs/SOAK-CHECKLIST.md)。
86
+
87
+ ## 开发
88
+
89
+ ```sh
90
+ npm install
91
+ npm run build
92
+ npm test
93
+ npm run smoke:electron-host # 需要本地 DSH Web 已启动;验证真实 Electron Host 导航与页面交接
94
+ ```
95
+
96
+ 贡献方式见 [CONTRIBUTING.md](CONTRIBUTING.md)。更完整的使用说明在 [docs](docs/README.md),版本变更见 [CHANGELOG](CHANGELOG.md)。
97
+
98
+ ## License
99
+
100
+ MIT。详见 [LICENSE](LICENSE) 与 [NOTICE](NOTICE.md)。
Binary file
Binary file
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-label="dsh-browser-plus">
2
+ <!-- The 16/32px derivative: the two title-bar dots are sub-pixel at that size, so
3
+ they are dropped and the outline is drawn a little heavier instead. -->
4
+ <rect x="10" y="10" width="236" height="236" rx="54" fill="#202124"/>
5
+ <g transform="translate(20 20) scale(9)" fill="none" stroke="#ffffff" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round">
6
+ <rect x="3.25" y="4.75" width="17.5" height="14.5" rx="3.25"/>
7
+ <path d="M3.25 9.4h17.5"/>
8
+ </g>
9
+ </svg>
Binary file
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-label="dsh-browser-plus">
2
+ <!-- Minimal white line art on the browser's own surface colour. The plate is what
3
+ keeps a white outline legible on a light taskbar; on a dark one it disappears. -->
4
+ <rect x="10" y="10" width="236" height="236" rx="54" fill="#202124"/>
5
+ <g transform="translate(20 20) scale(9)" fill="none" stroke="#ffffff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
6
+ <rect x="3.25" y="4.75" width="17.5" height="14.5" rx="3.25"/>
7
+ <path d="M3.25 9.4h17.5"/>
8
+ <circle cx="6.15" cy="7.05" r=".9" fill="#ffffff" stroke="none"/>
9
+ <circle cx="8.75" cy="7.05" r=".9" fill="#ffffff" stroke="none"/>
10
+ </g>
11
+ </svg>
Binary file
@@ -0,0 +1,17 @@
1
+ # dsh-browser-plus bundle patch: mounts the shared-browser capability into a
2
+ # profile composition. The browser seam (ctx.browser) is always mounted. The
3
+ # Electron provider and browser_* tools are ALWAYS registered: when the host
4
+ # provides `electronViewHost` (the desktop shell) that embedded view is used,
5
+ # and otherwise the plugin self-hosts its own Electron window, so installing
6
+ # the plugin is enough for the browser to work anywhere.
7
+ - insert:
8
+ - id: browser
9
+ name: dsh-browser-plus/browser
10
+
11
+ - id: browser-electron
12
+ name: dsh-browser-plus/browser-electron
13
+ config:
14
+ viewHost: !!js ctx.get('electronViewHost')
15
+
16
+ - id: tool-browser
17
+ name: dsh-browser-plus/tool-browser
@@ -0,0 +1,48 @@
1
+ # 迁移到 dsh-browser-plus
2
+
3
+ > 如果你的 DSH profile 仍装着旧版浏览器插件(package name 为 `dsh-builtin-browser`),按本指南切换到 `dsh-browser-plus` 并迁移登录态。
4
+
5
+ ## 步骤
6
+
7
+ ### 1. 导出当前浏览器登录态(可选但推荐)
8
+
9
+ 在旧包仍生效的 DSH 会话中:
10
+
11
+ ```
12
+ browser_auth action="flush" # 把返回的 cookies JSON 保存到文件
13
+ ```
14
+
15
+ ### 2. 安装新包(正式名 dsh-browser-plus)
16
+
17
+ ```sh
18
+ # 从 GitHub 安装(推荐;或使用 release tarball 或源码目录)
19
+ dsh plugin --profile web add github:ParticleLight/dsh-browser-plus
20
+ ```
21
+
22
+ ### 3. 移除已替换的旧包(可选)
23
+
24
+ ```sh
25
+ dsh plugin --profile web remove dsh-builtin-browser # 如实际安装名如此
26
+ ```
27
+
28
+ ### 4. 恢复登录态
29
+
30
+ 新包会话中:
31
+
32
+ ```
33
+ browser_auth action="restore" cookies=<步骤1保存的JSON>
34
+ ```
35
+
36
+ > 说明:宿主 userData 目录已从 `dsh-builtin-browser-host` 改为 `dsh-browser-plus-host`
37
+ > (host-main.ts `app.setPath('userData', ...)`),因此 cookie 不会自动迁移,必须走一遍 export/import。
38
+
39
+ ## 变更对使用者可见的部分
40
+
41
+ - 当前共 36 个 `browser_*` 工具;除输入、文件和任务工具外,新增语义导航、滚动、快照引用、任务状态与显式人机交接。
42
+ - **一个共享可见浏览器窗口**,每个 DSH 任务保持隔离视图、标签与历史;页面任务管理器切换任务,后台任务操作不会抢走当前页面。`browser_space label="..."` 命名浏览器任务,空参列出任务。
43
+ - JS 对话框**默认**自动 accept,记录见 `browser_history`(action `dialog`);需要驱动「取消」路径时用 `browser_dialog` 先设策略。
44
+ - Electron 锁定 **42.9.3**(43.4.1 组合器故障,勿升)。
45
+
46
+ ## 重启要求
47
+
48
+ 迁移 profile bundle 后请**重启 DSH Web**,确保旧 loader entry 完整卸载、新包以 `dsh-browser-plus` 身份加载(见 `docs/SOAK-CHECKLIST.md`)。
package/docs/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # dsh-browser-plus 文档索引
2
+
3
+ 按"你想做什么"选择入口:
4
+
5
+ | 目标 | 入口 |
6
+ | --- | --- |
7
+ | 了解插件为什么存在、与无头方案的区别 | [为什么做共享真实浏览器](why-browser.md) |
8
+ | 安装、配置、日常使用与常见问题 | [用户指南](user-guide.md) |
9
+ | 全部 41 个工具的参数、输出与示例 | [工具参考](tool-reference.md) |
10
+ | 了解 seam / provider / 工具三层与自托管实现 | [架构说明](architecture.md) |
11
+ | 重启 DSH 后的运行时验证清单 | [SOAK-CHECKLIST](SOAK-CHECKLIST.md) |
12
+ | 切换到 dsh-browser-plus 与登录态搬运 | [迁移指南](MIGRATION.md) |
13
+ | 回到项目首页 | [README](../README.md) |
14
+
15
+ ## 各篇概览
16
+
17
+ - **[为什么做共享真实浏览器](why-browser.md)** — 定位、设计理念、与 Playwright 等无头方案的对比、边界与取舍。
18
+ - **[用户指南](user-guide.md)** — 环境要求、安装、配置项、快速上手、操作纪律、FAQ 与故障排查。
19
+ - **[工具参考](tool-reference.md)** — 每个 `browser_*` 工具的参数、输出、守卫与示例。
20
+ - **[架构说明](architecture.md)** — 三层结构、`ElectronBrowserViewHost` 接缝、自托管 RPC 子进程、截图通道、Electron 版本选择、共享窗口任务管理器与页面 chrome。
21
+ - **[SOAK-CHECKLIST](SOAK-CHECKLIST.md)** — 重启 DSH 后逐项验证对话框/输入工具/等待定位/共享窗口任务管理器/稳定性(一次性通过后可放心发布)。
22
+ - **[迁移指南](MIGRATION.md)** — 切换到 `dsh-browser-plus` 的步骤与 cookie 搬运。