dsh-inner-browser-use 0.40.0 → 0.41.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.
package/README.md CHANGED
@@ -12,7 +12,7 @@ live picture of that browser, and your own cursor keeps working on the picture.
12
12
  | --- | --- |
13
13
  | `browser_open` | Starts the browser (or reuses the running one) and opens an address. `headless: false` shows a real window on the desktop instead. |
14
14
  | `browser_view` | Returns the address, the title, every control as `1 … 120`, and the visible text. |
15
- | `browser_read` | Reads the page the way a person does: one Markdown block of headings, paragraphs, lists and links, no controls. Far fewer tokens than a snapshot on long pages, and the links stay usable. Give it a `query` and only the sentences that answer it come back: BM25 over Readability-style blocks, then per-sentence scoring with heading, position and link-density priors, idf-weighted coverage, MMR de-duplication and a character budget — 92% fewer tokens on a long page in the bench. |
15
+ | `browser_read` | Reads the page the way a person does: one Markdown block of headings, paragraphs, lists and links, no controls. Far fewer tokens than a snapshot on long pages, and the links stay usable. Give it a `query` and only the sentences that answer it come back: BM25 over Readability-style blocks, then per-sentence scoring with heading, position and link-density priors, idf-weighted coverage, MMR de-duplication and a character budget — 92% fewer tokens on a long page in the bench. A question the page cannot answer returns the nearest passages and says so instead of pasting the page (79% fewer tokens in the bench). |
16
16
  | `browser_find` | Finds words (or a regex) anywhere on the page: matching controls come back with a clickable number, matching text with its point, each saying whether it is on screen. |
17
17
  | `browser_extract` | Returns the items a CSS selector matches, one compact line each with its link, instead of the whole page. |
18
18
  | `browser_click` | Clicks a numbered control, or an `x`/`y` point. `double: true` double-clicks. A cookie banner or modal over the control is cleared first, then the click happens. |
package/README.zh.md CHANGED
@@ -11,7 +11,7 @@ profile,不是产品自带的那个浏览页签:右栏放的是它的画面
11
11
  | --- | --- |
12
12
  | `browser_open` | 启动浏览器(已在跑就复用)并打开一个地址。传 `headless: false` 会改为在桌面上开真实窗口。 |
13
13
  | `browser_view` | 返回地址、标题、编号成 `1 … 120` 的控件,以及可见文字。 |
14
- | `browser_read` | 按人读文章的方式给 Markdown(标题、段落、列表、链接)。带 `query` 只回能回答的那几句(段落排序后再按句子打分,标题、位置、链接密度做先验,重复句去掉,按字符预算装箱);问不到时自动改为整页,不再空手回来。页面和上次读的没变就只回一行,不重发正文(`force: true` 强制重发)。 |
14
+ | `browser_read` | 按人读文章的方式给 Markdown(标题、段落、列表、链接)。带 `query` 只回能回答的那几句(段落排序后再按句子打分,标题、位置、链接密度做先验,重复句去掉,按字符预算装箱);问不到时只回最近的三段并明说没答上,不再把整页端回来。页面和上次读的没变就只回一行,不重发正文(`force: true` 强制重发)。 |
15
15
  | `browser_extract` | 按 CSS 选择器把列表项读成一行一条、带链接,比整页读省得多。 |
16
16
  | `browser_view` 的 `map` | 大页面先看地标:各类控件各多少个、去重后的控件和编号、正文多少字,但不带回正文。 |
17
17
  | `browser_click` | 点编号控件,或点 `x`/`y` 坐标。`double: true` 双击。控件被横幅或弹层挡着时,先关掉它再点。 |
@@ -186,6 +186,12 @@ sentences are packed to a character budget and restored to document order. Terms
186
186
 
187
187
  `sentences: false` keeps the paragraph path for callers who want the whole section.
188
188
 
189
+ A question the page cannot answer costs the miss, not the page: when no sentence clears the
190
+ cut, `readRelevant` returns the three blocks that ranked highest (each trimmed towards 260
191
+ characters) and no text, and the tool turns that into a line saying nothing directly answers the
192
+ question plus those passages. The whole page is still one `browser_view` away, so the expensive
193
+ fallback — a page pasted into the context for a question it never addressed — is gone.
194
+
189
195
  ## Sign-ins and reclaim
190
196
 
191
197
  Sign-in state lives in the profile, and session cookies — the kind a sign-in usually sets,
package/docs/BENCH.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 问了但页面没答(0.41.0)
2
+
3
+ 同一张故事页,问一个页面里根本没有的词(`quantum chromodynamics`)。左边是旧的兜底:
4
+ 把整页当答案交出去;右边是现在只给最近的三段加一句说明。
5
+
6
+ | 项 | 整页兜底 | 最近几段 |
7
+ | --- | --- | --- |
8
+ | 回来的字符数 | 2089 | 438 |
9
+ | 折合 token | 614 | 129 |
10
+ | 省下 | — | 79% |
11
+ | 最近几段 | — | 3 段(每段 ≤ 260 字) |
12
+ | 是否明说没答上 | 否 | 是 |
13
+ | 读这一趟 | — | 7 ms |
14
+
15
+ 命中路径的 `read-answer` 行不受影响(1172 → 90 字,省 92%)。
16
+
1
17
  ## 只读答案那几句(0.40.0)
2
18
 
3
19
  同一张本地故事页:24 段会议记录里夹着一段账单说明,问题是「退款多久到账」。一条路按段
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-inner-browser-use",
3
- "version": "0.40.0",
3
+ "version": "0.41.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/read.js CHANGED
@@ -1383,13 +1383,23 @@ export function evidence(blocks, query, options = {}) {
1383
1383
  */
1384
1384
  export async function readRelevant(page, query, options = {}) {
1385
1385
  const budget = Math.max(400, Math.min(24_000, Number(options.maxChars ?? 4000)))
1386
+ /* A short, untrimmed paragraph is what a miss shows: enough to say what the
1387
+ page is about, never enough to be the page. */
1388
+ const near = (blocks) => blocks
1389
+ .map((block) => String(block?.t ?? '').replace(/\s+/g, ' ').trim())
1390
+ .filter((body) => body !== '')
1391
+ .slice(0, 3)
1392
+ .map((body) => {
1393
+ const cut = body.length > 260 ? `${body.slice(0, 260).replace(/\s+\S*$/, '')}…` : body
1394
+ return { heading: '', text: cut }
1395
+ })
1386
1396
  try {
1387
1397
  const raw = String((await evaluateIn(page, blocks(budget * 8))) ?? '{}')
1388
1398
  const said = JSON.parse(raw)
1389
1399
  const all = Array.isArray(said?.blocks) ? said.blocks : []
1390
1400
  const ranked = rank(all, query)
1391
1401
  if (ranked.length === 0) {
1392
- return { title: String(said?.title ?? page.title ?? ''), url: String(page.url ?? ''), text: '', chars: 0, matched: 0, total: all.length }
1402
+ return { title: String(said?.title ?? page.title ?? ''), url: String(page.url ?? ''), text: '', chars: 0, matched: 0, total: all.length, nearby: near(all) }
1393
1403
  }
1394
1404
  /* Passages cost whole paragraphs; sentences cost only the ones that say
1395
1405
  something about the query, which on a long page is most of the saving. */
@@ -1413,10 +1423,15 @@ export async function readRelevant(page, query, options = {}) {
1413
1423
  matched += 1
1414
1424
  if (used >= budget) break
1415
1425
  }
1426
+ /* A question the page does not answer should not cost the page: the few
1427
+ blocks that came closest say what the page is about, and the caller can
1428
+ still ask for everything. */
1429
+ const nearby = matched === 0 ? near(ranked.slice(0, 3).map((row) => row.block)) : []
1416
1430
  return {
1417
1431
  title: String(said?.title ?? page.title ?? ''),
1418
1432
  url: String(page.url ?? ''),
1419
1433
  text: lines.join('\n\n'),
1434
+ nearby,
1420
1435
  chars: used,
1421
1436
  matched,
1422
1437
  total: all.length,
package/tools.js CHANGED
@@ -2260,8 +2260,16 @@ export function sidecarTools(sidecar, refs, host = {}) { // ui-copy-ignore
2260
2260
  const found = await readRelevant(page, asked, { maxChars: ceiling })
2261
2261
  if (found.fault !== undefined) return `the page did not answer when read — ${String(found.fault).split('\n')[0].slice(0, 200)}`
2262
2262
  if (found.text === '') {
2263
- /* The question missed every block: the page is still the answer, so
2264
- hand it over whole rather than make the caller guess again. */
2263
+ /* A question the page does not answer costs the nearest passages and
2264
+ a sentence saying so, not the whole page: whole pages are what
2265
+ this tool exists to avoid, and the caller can ask for one. */
2266
+ const closest = Array.isArray(found.nearby) ? found.nearby : []
2267
+ if (closest.length > 0) {
2268
+ const body = closest
2269
+ .map((row) => `${row.heading === '' ? '' : `## ${String(row.heading)}\n`}${String(row.text)}`)
2270
+ .join('\n\n')
2271
+ return keep(`${found.url}\n# ${found.title === '' ? '(untitled)' : found.title}\n\nnothing directly answers ${JSON.stringify(asked)} (${String(found.total)} blocks read). The closest passages:\n\n${body}\n\nask for the whole page with browser_view, or try other words.`)
2272
+ }
2265
2273
  const whole = await readArticle(page, { maxChars: ceiling })
2266
2274
  if (whole.fault === undefined && whole.text !== '') {
2267
2275
  return keep(`${whole.url}\n# ${whole.title === '' ? '(untitled)' : whole.title}\n\nnothing here answers ${JSON.stringify(asked)} (${String(found.total)} blocks read), so the whole page follows:\n\n${whole.text}${whole.truncated ? `\n\n(cut at ${String(whole.chars)} characters; raise max_chars for more)` : ''}`)