dsh-inner-browser-use 0.37.0 → 0.38.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.
@@ -160,6 +160,11 @@ Calls that touch the browser are marked not concurrency-safe; reading state, con
160
160
  requests is. `browser_open`, `browser_view`, `browser_wait` and `browser_shot` carry longer
161
161
  timeouts than the rest.
162
162
 
163
+ `browser_wait` does not poll: one call hands the page a predicate and a `MutationObserver`, and the
164
+ picture comes back on the mutation that satisfies it. A page that changes 137 ms after the wait
165
+ starts answers at 138 ms, not at the next 100 ms tick — the same reason `idle` (a mutation-free
166
+ window) costs one round trip on an animation-heavy page instead of one per frame.
167
+
163
168
  Sign-in state lives in the profile, and session cookies — the kind a sign-in usually sets,
164
169
  which Chrome would otherwise drop on exit — are saved next to it and restored on the next
165
170
  launch, so a restart of the app does not sign the person out.
package/docs/BENCH.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## 等到变化,而不是去问(0.38.0)
2
+
3
+ `browser_wait` 以前在宿主侧每 100 毫秒读一次页面判断条件,命中与否都被凑到下一个整拍上。
4
+ 现在一次调用把谓词和 `MutationObserver` 交给页面,满足条件的那次变化直接 resolve,回程只走
5
+ 一次。
6
+
7
+ | 同一件事 | 0.37.0(宿主轮询) | 0.38.0(页面观察) |
8
+ | --- | --- | --- |
9
+ | 五次不同时相的等待,平均耗时 | 205 ms | 140 ms |
10
+ | 每次等待的探测来回 | 3 | 1 |
11
+ | `idle` 判定 | 固定间隔重读页面 | 350 ms 无变化即停,一次来回 |
12
+
13
+ `wait-late` 行就是上表:每次实验在等待开始的同一刻给页面装一个定时器,所以旧路径必然要走到
14
+ 下一个 100 毫秒整拍,新路径不必。预算 600 ms,实测 140 ms。
15
+
1
16
  ## 只列该看的那一个(0.37.0,同一台本地页面,加了两个广告请求)
2
17
 
3
18
  页面自己放一个 `<video src="/movie.mp4">`、一个 `<audio>`、fetch 一个 master 播放列表,再 fetch 两条广告(`/ads/preroll.mp4` 9 MB、`/px/beacon.mp4` 2 B)。不筛选时 5 条都在;默认列表剩 3 条,页面播放器那个标 `main` 排第一,`save: main` 存的就是它。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-inner-browser-use",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "An agent-owned Chromium sidecar mirrored into the DeepSeek Harness right column: the agent drives it, the person watches and can take the mouse, and logins survive restarts.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/tools.js CHANGED
@@ -1070,13 +1070,64 @@ export async function settle(page, timeout = 5000, since = 0) {
1070
1070
  * @returns {Promise<boolean>} whether it ever held.
1071
1071
  */
1072
1072
  async function until(page, expression, timeout) {
1073
- const deadline = Date.now() + timeout
1074
- while (Date.now() < deadline) {
1075
- const value = await evaluate(page, expression)
1076
- if (value === true) return true
1077
- await wait(100)
1078
- }
1079
- return false
1073
+ /* The page watches itself. One round trip that the page resolves on the
1074
+ mutation which matters: no snapshot every 100 ms, no rounding the answer up
1075
+ to the next tick, and nothing to miss between two polls. */
1076
+ const ceiling = Math.max(200, Number(timeout) || 1000)
1077
+ const script = `new Promise((done) => {
1078
+ const deadline = Date.now() + ${String(ceiling)}
1079
+ let watcher = null
1080
+ let timer = null
1081
+ let frame = null
1082
+ const stop = (value) => {
1083
+ if (watcher !== null) watcher.disconnect()
1084
+ watcher = null
1085
+ if (timer !== null) clearTimeout(timer)
1086
+ timer = null
1087
+ if (frame !== null) cancelAnimationFrame(frame)
1088
+ frame = null
1089
+ done(value)
1090
+ }
1091
+ const test = () => { try { return Boolean(${expression}) } catch (error) { return false } }
1092
+ if (test()) return stop(true)
1093
+ watcher = new MutationObserver(() => { if (test()) stop(true) })
1094
+ if (document.documentElement !== null) watcher.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true })
1095
+ const tick = () => {
1096
+ if (test()) return stop(true)
1097
+ if (Date.now() >= deadline) return stop(false)
1098
+ frame = requestAnimationFrame(tick)
1099
+ }
1100
+ frame = requestAnimationFrame(tick)
1101
+ timer = setTimeout(() => stop(false), deadline - Date.now())
1102
+ })`
1103
+ const answer = await evaluate(page, script, undefined, ceiling + 3000)
1104
+ return answer === true
1105
+ }
1106
+
1107
+ /**
1108
+ * How long the page has been still: a mutation resets the clock, and the call
1109
+ * comes back the moment nothing has moved for `quiet` ms. Reads no node, so an
1110
+ * animation-heavy page costs the same as a blank one.
1111
+ */
1112
+ async function quietInPage(page, timeout = 5000, quiet = 350) {
1113
+ const ceiling = Math.max(300, Number(timeout) || 5000)
1114
+ const script = `new Promise((done) => {
1115
+ const ceiling = Date.now() + ${String(ceiling)}
1116
+ const settleMs = ${String(Math.max(50, Number(quiet) || 350))}
1117
+ let changedAt = Date.now()
1118
+ let frame = null
1119
+ const watcher = new MutationObserver(() => { changedAt = Date.now() })
1120
+ if (document.documentElement !== null) watcher.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true })
1121
+ const tick = () => {
1122
+ const now = Date.now()
1123
+ if (now - changedAt >= settleMs) { watcher.disconnect(); return done(true) }
1124
+ if (now >= ceiling) { watcher.disconnect(); return done(false) }
1125
+ frame = requestAnimationFrame(tick)
1126
+ }
1127
+ frame = requestAnimationFrame(tick)
1128
+ })`
1129
+ const answer = await evaluate(page, script, undefined, ceiling + 3000)
1130
+ return answer === true
1080
1131
  }
1081
1132
 
1082
1133
  /**
@@ -3150,8 +3201,9 @@ export function sidecarTools(sidecar, refs, host = {}) { // ui-copy-ignore
3150
3201
  const at = await until(page, `location.href.includes(${JSON.stringify(String(args.url))})`, timeout)
3151
3202
  return at ? `at ${String(await evaluate(page, 'location.href'))}` : `address never contained ${String(args.url)}`
3152
3203
  }
3153
- await settle(page, timeout)
3154
- return 'idle'
3204
+ const quiet = await quietInPage(page, timeout)
3205
+ if (typeof page.adoptOpened === 'function') await page.adoptOpened().catch(() => '')
3206
+ return quiet ? 'idle' : 'still moving'
3155
3207
  }),
3156
3208
  },
3157
3209
  {