dsh-browser-playwright-codex 0.5.5 → 0.6.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/CHANGELOG.md +73 -0
- package/NOTICE.md +1 -1
- package/README.md +72 -11
- package/SECURITY.md +22 -0
- package/cordis.patch.yml +6 -0
- package/lib/compat.d.ts +9 -9
- package/lib/compat.js +0 -20
- package/lib/contract.d.ts +2 -2
- package/lib/contract.js +0 -4
- package/lib/login-sites.d.ts +136 -0
- package/lib/login-sites.js +303 -0
- package/lib/page-actions.d.ts +2 -2
- package/lib/page-actions.js +1 -1
- package/lib/provider.js +45 -1
- package/lib/runtime-state.d.ts +39 -0
- package/lib/runtime-state.js +20 -26
- package/lib/session.d.ts +40 -22
- package/lib/session.js +65 -1
- package/lib/site-aliases.d.ts +33 -0
- package/lib/site-aliases.js +132 -0
- package/lib/tool.js +95 -91
- package/lib/types.d.ts +13 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,79 @@
|
|
|
3
3
|
Bilingual by intent: the version headings and the summary of each release are given in both
|
|
4
4
|
languages, the individual entries follow the language the surrounding code and docs use.
|
|
5
5
|
|
|
6
|
+
## 0.6.0 — 2026-10-16
|
|
7
|
+
一句话摘要:**`browser_fill` 现在也接受 `value` 这个参数名**(模型按语义猜也能一次成功),缺值时给的是指路错误,而不是"缺少属性 text"。
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **同一家公司的域折叠成一行**(用户:"这种一个类型的肯定要放一起啊"):对照表升级成「说明 + 归属公司」,
|
|
12
|
+
载荷每行带 `group: { key, label }`,卡片按公司折叠(京东系 / 阿里系 / 字节系 / 微软系 / Meta 系 /
|
|
13
|
+
Google 系 / 广告与统计平台),组行显示成员数与整族汇总,展开看单个域;只剩一个成员的组自动退回单行。
|
|
14
|
+
- 顺带修掉上一版说明行里**不配对的括号**:有网页标题时反而会把 cookie 计数吞掉(只在有标题的行上出现)。
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **每一行写明"这个域名是什么"**(用户:"我都不知道我登的这些是什么"):优先用**网页自己的标题**
|
|
19
|
+
(浏览器与密码管理器的做法,网站自己报的名字,不出网),其次包内一份**离线对照表**
|
|
20
|
+
(`src/site-aliases.ts`,60 多条:国内平台、京东系一大家子、国外服务,以及被明确标注的广告/统计域),
|
|
21
|
+
认不出的只显示域名、不编。广告/统计域被标出来本身就是提示 —— 它出现在"像登录态"里通常说明判据被
|
|
22
|
+
数据相似性骗了。`LoginSite` 新增 `alias?` 与 `title?`(标题由 `session.siteTitles()` 提供,
|
|
23
|
+
`page.title()` 是异步的,所以该方法是 async)。
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **`browser_fill` 参数名歧义(评审 A1)**:`value` 作为 `text` 的别名被接受,两者都给时 `text` 优先。缺值时抛 `PARAM_MISSING`,消息同时点出两种写法,并说明"`text` 是要填入的值(要点可见标签请用 `browser_click`)";这条检查在**获取会话之前**,所以参数传错不会白开一个浏览器。schema 里 `text` 不再是 required —— 参数 DSL 是扁平属性表、required 只能逐属性标,表达不了"text 与 value 二选一",只能在处理函数里兜,代码注释写明了原因。
|
|
28
|
+
- **`PLUGIN-README.md` 的安装命令指向不存在的包**:它写着 `add dsh-browser-playwright`(0.4.0 改名前的旧包名,npm 上没有这个包)。已改为当前包名、标题同步;并修掉上一批我加错的说明 —— `git log --follow` 显示这份文件在初始提交里就叫 `README.md`,是本插件自己的英文自述,**不是**上游插件的文档。
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- 文档的常驻成本数字同步为实测值:20 工具 **15,269** 字符 ≈ **4,241** token/轮(全量注册 23 工具 18,122 ≈ 5,034),README / PLUGIN-README / 中文说明三处一致。本次改动净增约 236 字符(≈ +65 token/轮),是别名说明的代价。
|
|
33
|
+
|
|
34
|
+
### Changed(破坏性:工具面 20 → 18)
|
|
35
|
+
|
|
36
|
+
- **click / hover / click_at 合并成一个 `browser_click`**:三者的能力一个没丢,用两个开关表达 —— `mode:"hover"` = 只揭示不激活(原 `browser_hover`),`precise:true` = 几何中点兜底路径(原 `browser_click_at` 的 ref 模式),`x`+`y` = 裸坐标(原 `click_at` 的裸模式)。
|
|
37
|
+
新旧对照:`browser_hover { ref | text }` → `browser_click { mode: "hover", ref | text }`;`browser_click_at { ref }` → `browser_click { ref, precise: true }`;`browser_click_at { x, y }` → `browser_click { x, y }`。
|
|
38
|
+
- **常驻成本**:20 工具 15,269 字符 ≈ 4,241 token/轮 → **18 工具 12,745 字符 ≈ 3,540 token/轮(−701,−16.5%)**;全量注册(含三个闸门工具)23 → 21。
|
|
39
|
+
- **守卫随之调整,而不是被绕过**:`tests/tool-descriptions.test.ts` 的 `RULES` 表从三个键并为 `browser_click` 一个键(29 条),覆盖原来 46 条里的核心;`SNAPSHOT-RULES.md §5` 的长度预算改成"每条 ≤1650;承接过多个同类工具的合并工具 ≤2400;整个描述面合计 ≤11500",并写明理由 —— 去重后仍要 ~2165 字符才能把"精确点击兜底""悬停揭示失败怎么办"这两段失败路径一条不丢地说完,而它们属于规范里的五要素,不许删。
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **「已登录的站点」的归属模型与判据都错了**(这份功能第一次吐出真实名单时暴露的:231 个域、1177 个 cookie,
|
|
44
|
+
`pubmatic.com`(广告交易所)27 个、`mmstat.com`(统计)30 个都排在前面,展示的 60 个**全被标"推测"**)。
|
|
45
|
+
按两个成熟做法重写:
|
|
46
|
+
1. **第一方归属**(Firefox Total Cookie Protection 的模型):cookie 属于"你当时浏览的那个站",
|
|
47
|
+
不是"设它的那个域"。现在只把**浏览器真的访问过**的域当候选(用当前打开页面的 URL,**不读浏览历史**;
|
|
48
|
+
浏览器没开时用导出文件里的 localStorage origins 顶上),其余只报个数(`thirdPartyTotal`)、不进列表。
|
|
49
|
+
2. **OWASP 的会话特征**:`httpOnly` 且(会话级 或 值像令牌)才算"推测登录";**名字与 cookie 条数不再作为依据**
|
|
50
|
+
(上一版正是靠它们把广告域排到了前面)。值只在 `session.cookieJar()` 里被折算成一个布尔(`tokenLike`),
|
|
51
|
+
值本身不出那个函数 —— 红线不变。
|
|
52
|
+
另外:载荷补上诚实的统计(`likelyTotal` / `total` / `thirdPartyTotal`),卡片默认**只显示像登录态的**、
|
|
53
|
+
可展开其余,每行给出可核对的证据(cookie 数 / httpOnly / 会话 / 值像令牌);域级名单补上广告交易所
|
|
54
|
+
(pubmatic / criteo / adnxs / rubiconproject / scorecardresearch / adsrvr / taboola / outbrain 等)。
|
|
55
|
+
接口 `summarizeLoginSites` 的第 4 个参数改为**具名选项**(`{ visited, maxSites }`)—— 位置参数在这个位置
|
|
56
|
+
太容易传错,改的时候就踩了一次。
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
- **设置页新增只读的「已登录的站点」名单**(配套件 `dsh-browser-toggle` 的卡片里):provider 从浏览器自己的
|
|
62
|
+
cookie 推断哪些站点像登录着,按可注册域聚合(含 `com.cn` / `co.uk` 这类多段后缀),剔除分析/风控 cookie
|
|
63
|
+
(`_ga`、`__cf_bm`、`Hm_lvt_*`、`BAIDUID` 等),再按 httpOnly、有效期与名字特征打分,界面标"推测登录"。
|
|
64
|
+
**出去的内容只有域名、条数与布尔信号 —— 没有 cookie 的名字,也没有值**;`src/login-sites.ts` 在唯一入口
|
|
65
|
+
丢弃 `value`,测试断言整份产出序列化后搜不到任何名字或值。
|
|
66
|
+
数据是内存态(不落盘),模型看不到(不注册工具、不进 diagnostics),唯一出口是
|
|
67
|
+
`GET /api/browser-playwright/logins`,且强制 `x-dsh-plugin` 自定义头(不带即 403,有测试盯着)。
|
|
68
|
+
新增 `tests/login-sites.test.ts`(8 条)与 `src/login-sites.ts`;会话接口 `BrowserSession` 增加
|
|
69
|
+
`cookieJar()`(只返回 name/domain/expires/httpOnly/secure)。
|
|
70
|
+
- **顺手补掉一个真实缺口**:`GET /api/browser-playwright/state` 此前**不校验** `x-dsh-plugin` 防伪头
|
|
71
|
+
(四个路由里唯一一个),现已补上;客户端读写都带头。这是加登录名单时顺手发现的 —— 一个暴露部署开关状态的
|
|
72
|
+
GET 不该裸奔。
|
|
73
|
+
|
|
74
|
+
- **`tests/doc-reality.test.ts` 第 6 条守卫**:文档里的 `dsh plugin … add <spec>` 必须指向真实来源(本包名、本仓库 `github:` 或 URL、`link:`),引用上游命令的行除外。
|
|
75
|
+
- **README 的工具参数签名(评审 B2)**:新增 “Parameter signatures” 段,列出全部 21 个工具(18 常驻 + 3 个闸门,闸门工具带标记)的参数与必填性(`*` 必填 / `?` 可选)。
|
|
76
|
+
**签名是从活的工具面导出的,不是手写的**;`tests/tool-schema.test.ts` 新增守卫逐项比对 README 与 schema —— 工具多一个少一个、参数名或必填性漂移都会红,并且这条守卫是用“故意把 README 写错一版”验证过确实会红的。中文说明的工具表本来就有参数列,英文 README 之前完全没有,调用方只能靠猜(`browser_fill` 的 `value` 就是这么猜错的)。
|
|
77
|
+
- 测试:schema 层断言别名在 schema 中可见、`ref` 是唯一必填;行为层断言用 `value` 填的值**真的进了页面**(以它触发的搜索结果为证),以及缺值时错误同时点出两种写法与 `browser_click`。
|
|
78
|
+
|
|
6
79
|
## 0.5.5
|
|
7
80
|
|
|
8
81
|
一句话摘要:**把 `NOTICE.md` 补进 npm 包**(此前从未随包发布过),并把发版审计固化成 `npm run preflight` —— 挂在 `prepublishOnly` 上,不通过就发不出去。
|
package/NOTICE.md
CHANGED
|
@@ -11,7 +11,7 @@ Covers everything that is part of the plugin: `src/`, `tests/`, `scripts/`, `cor
|
|
|
11
11
|
| Origin | What | Licence |
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| [ChenyuHeee/dsh-browser-playwright](https://github.com/ChenyuHeee/dsh-browser-playwright) v0.1.1 | The accessibility-snapshot engine (`src/injected.ts`, `src/snapshot-render.ts`), the tool family (`src/tool.ts`), the service/provider seams (`src/service.ts`, `src/playwright.ts`), the URL policy, and the test-suite shape. Copyright (c) 2026 dsh-browser-playwright contributors. | **MIT** |
|
|
14
|
-
| This fork's merged layer | Geometric click (`
|
|
14
|
+
| This fork's merged layer | Geometric click (`browser_click` with `precise: true`) with landing-element reporting, the CDP policy layer (`src/cdp-policy.ts`), the persistent profile + login-state export (`src/runtime-state.ts`, `persistState`/`restoreState`), dialog parking, the console/network diagnostics tools, the contract/compat modules, and the fixes listed in [`CHANGELOG.md`](CHANGELOG.md) | **MIT** |
|
|
15
15
|
|
|
16
16
|
**Why the plugin cannot be non-commercial:** it is a derivative of MIT-licensed work. MIT grants everyone the right to use, modify and redistribute the software **including commercially**, and a redistributor cannot add a non-commercial restriction to it. Any non-commercial claim over `src/` would be an unauthorised restriction — worse for downstream users than leaving it MIT. The MIT text in `./LICENSE` (including the upstream copyright notice) is unchanged.
|
|
17
17
|
|
package/README.md
CHANGED
|
@@ -78,14 +78,13 @@ Or pin a binary: `launch.executablePath` / `launch.channel` in the plugin config
|
|
|
78
78
|
|
|
79
79
|
## Tools
|
|
80
80
|
|
|
81
|
-
The default `toolPrefix` is `browser_`. Every action returns a fresh snapshot, so refs always come from the latest result. The registered set is capability-gated: `browser_evaluate`, `browser_cdp` and `browser_extract` appear only when their capability is switched on (their schemas are resident in the system prompt on every turn, so a tool that could only answer "disabled" would be pure prompt cost). Count the live surface with `npm run cost` — the shipped `cordis.patch.yml` keeps **both power gates off** (since 0.4.7), so a profile mounting this bundle sees **
|
|
81
|
+
The default `toolPrefix` is `browser_`. Every action returns a fresh snapshot, so refs always come from the latest result. The registered set is capability-gated: `browser_evaluate`, `browser_cdp` and `browser_extract` appear only when their capability is switched on (their schemas are resident in the system prompt on every turn, so a tool that could only answer "disabled" would be pure prompt cost). Count the live surface with `npm run cost` — the shipped `cordis.patch.yml` keeps **both power gates off** (since 0.4.7), so a profile mounting this bundle sees **18 tools**; 19 or 20 once your deployment opts into `evaluate` / `cdp`, 21 once `extract` is configured. How to opt in: [SECURITY.md](SECURITY.md) §1.
|
|
82
82
|
|
|
83
83
|
| Tool | Purpose |
|
|
84
84
|
|---|---|
|
|
85
85
|
| `browser_navigate` | Open a URL (http/https only) and return the snapshot |
|
|
86
86
|
| `browser_snapshot` | Snapshot the current page: URL, title, visible elements with `ref=` ids |
|
|
87
|
-
| `browser_click` | Click the
|
|
88
|
-
| `browser_hover` | Rest the pointer on a `ref` — or on a visible label the snapshot gave no ref, e.g. `{ text: "设置" }` — so hover-revealed content appears (submenus, tooltips, previews, player controls); the returned snapshot names the target and notes whether the actionable ref count grew |
|
|
87
|
+
| `browser_click` | Click the target from the latest snapshot — a `ref`, a visible label the snapshot gave no ref (e.g. `{ text: "搜索设置" }`), or raw `x`/`y` viewport pixels as a last resort — or rest the pointer on it without activating anything with `mode: "hover"` (submenus, tooltips, previews, player controls); `precise: true` takes a ref click down the geometric-centre path. The returned snapshot opens with a landing note naming what received the click, and a dialog raised by a click stays pending — answer it with `browser_dialog` |
|
|
89
88
|
| `browser_fill` | Replace an input's value; selects match by option label |
|
|
90
89
|
| `browser_press` | Press a key (Enter, Tab, Escape, ArrowDown, …) on an element |
|
|
91
90
|
| `browser_scroll` | Scroll the page or bring a ref into view |
|
|
@@ -96,12 +95,38 @@ The default `toolPrefix` is `browser_`. Every action returns a fresh snapshot, s
|
|
|
96
95
|
| `browser_extract` | Structured extraction: natural-language instruction → one parsed JSON value | **Only registered when enabled.**
|
|
97
96
|
| `browser_evaluate` | Run one JavaScript expression in the page (disabled by default) | **Only registered when enabled.**
|
|
98
97
|
| `browser_dialog` | Answer the native dialog blocking the page: `{ accept: true }` confirms, `false` cancels; errors with `NO_DIALOG` when nothing is pending |
|
|
99
|
-
| `browser_click_at` | Precise native click: ref mode (geometric centre, Codex-style) or raw x/y mode; reports the actual landing element |
|
|
100
98
|
| `browser_cdp` | Send one raw CDP command (DOM inspection / Input simulation allow-list only; disabled by default) | **Only registered when enabled.**
|
|
101
99
|
| `browser_close` | Close the session's browser window; the profile keeps login state, so the next call reopens signed in |
|
|
102
100
|
| `browser_console_messages` | Read the page console messages captured for the current page (levels, text, source) |
|
|
103
101
|
| `browser_network_requests` | Read the network requests captured for the current page (method, URL, status, resource type) |
|
|
104
102
|
|
|
103
|
+
### Parameter signatures
|
|
104
|
+
|
|
105
|
+
Every registered tool, with the parameters its schema actually declares (`*` = required, `?` = optional).
|
|
106
|
+
Generated from the live tool surface and pinned by `tests/tool-schema.test.ts`, so it cannot drift from the code.
|
|
107
|
+
|
|
108
|
+
- `browser_back` — _none_
|
|
109
|
+
- `browser_cdp` — `method`*, `params`? _(gated: registered only when enabled)_
|
|
110
|
+
- `browser_click` — `ref`?, `text`?, `x`?, `y`?, `mode`?, `precise`?
|
|
111
|
+
- `browser_close` — _none_
|
|
112
|
+
- `browser_close_tab` — `index`*
|
|
113
|
+
- `browser_console_messages` — `level`?, `limit`?
|
|
114
|
+
- `browser_dialog` — `accept`*, `promptText`?
|
|
115
|
+
- `browser_evaluate` — `expression`* _(gated: registered only when enabled)_
|
|
116
|
+
- `browser_extract` — `instruction`* _(gated: registered only when enabled)_
|
|
117
|
+
- `browser_fill` — `ref`*, `text`?, `value`?
|
|
118
|
+
- `browser_forward` — _none_
|
|
119
|
+
- `browser_navigate` — `url`*, `waitFor`?
|
|
120
|
+
- `browser_network_requests` — `urlContains`?, `failedOnly`?, `limit`?
|
|
121
|
+
- `browser_open_tab` — `url`*, `waitFor`?
|
|
122
|
+
- `browser_press` — `ref`*, `key`*
|
|
123
|
+
- `browser_screenshot` — `fullPage`?, `ref`?
|
|
124
|
+
- `browser_scroll` — `direction`*, `amount`?, `ref`?
|
|
125
|
+
- `browser_snapshot` — `interactiveOnly`?
|
|
126
|
+
- `browser_switch_tab` — `index`*
|
|
127
|
+
- `browser_tabs` — _none_
|
|
128
|
+
- `browser_wait` — `ms`*
|
|
129
|
+
|
|
105
130
|
> **使用铁律**: 状态检查 / 无效不盲试 / 页面内容不可信 / 登录红线 / 风控分类等 Codex 文档纪律,
|
|
106
131
|
> 见 [`codex-rules.md`](codex-rules.md)(A 层已内嵌进各工具 description; B 层供粘贴进 DSH profile / 系统提示)。
|
|
107
132
|
|
|
@@ -149,6 +174,7 @@ All tunables are patchable through the profile's `cordis.patch.yml` (later layer
|
|
|
149
174
|
maxNodes: 500 # token budget per snapshot tree
|
|
150
175
|
maxNameLength: 120
|
|
151
176
|
maxTextLength: 300
|
|
177
|
+
diff: false # true = return "+/-/~" ref diffs instead of the full tree
|
|
152
178
|
|
|
153
179
|
- id: browser-tool
|
|
154
180
|
config:
|
|
@@ -156,6 +182,7 @@ All tunables are patchable through the profile's `cordis.patch.yml` (later layer
|
|
|
156
182
|
allowEvaluate: false # browser_evaluate gate
|
|
157
183
|
allowCdp: false # browser_cdp gate (DOM inspection / Input simulation allow-list)
|
|
158
184
|
registerDisabledTools: false # register evaluate/cdp/extract even while off (discoverability vs resident prompt cost)
|
|
185
|
+
interactiveOnlyDefault: false # true = browser_snapshot returns actionable elements only
|
|
159
186
|
maxWaitMs: 60000 # browser_wait bound
|
|
160
187
|
extract:
|
|
161
188
|
provider: deepseek-official # optional: enables browser_extract
|
|
@@ -166,11 +193,37 @@ All tunables are patchable through the profile's `cordis.patch.yml` (later layer
|
|
|
166
193
|
|
|
167
194
|
`browser_extract` needs an auxiliary LLM route (`extract.provider` + `extract.model`); without one it fails with an actionable error. With a route configured, `extract.maxInputChars` bounds the page text in that prompt, and the prompt names the cap when it truncates. A gated tool is **not registered at all** while its capability is off, so its schema never reaches the system prompt; set `registerDisabledTools: true` to keep the whole surface discoverable (the tool then answers with the error naming the switch). The shipped layer in [`cordis.patch.yml`](cordis.patch.yml) keeps `allowEvaluate` and `allowCdp` **off**, matching the schema defaults and this sample: that file travels inside the package, so anything it enabled would be enabled for everyone who installs it. Opt in per deployment, in that deployment's own patch layer. `browser_evaluate` executes arbitrary page JavaScript; `browser_cdp` stays off until `allowCdp: true`, and even then only the allow-list in `lib/cdp-policy.js` passes — network, storage, cookie, security and arbitrary-JS (`Runtime.evaluate`) commands are rejected with `CDP_DENIED`.
|
|
168
195
|
|
|
196
|
+
### Two levers that shrink what tools *return*
|
|
197
|
+
|
|
198
|
+
The resident schema cost is fixed, but two switches cut what each call sends back — useful on pages
|
|
199
|
+
whose snapshots are long, and both are off by default because both change what the model sees:
|
|
200
|
+
|
|
201
|
+
- **`snapshot.diff`** (playwright row) — returns `+` / `-` / `~` diffs against the previous snapshot,
|
|
202
|
+
keyed by stable refs, instead of the whole tree. A ref-nonce change is reported as a navigation reset,
|
|
203
|
+
and a truncated tree falls back to the full snapshot, so the model is never handed a diff it cannot
|
|
204
|
+
anchor. Turn it on when a page re-renders often and you resent paying for the same tree twice.
|
|
205
|
+
- **`interactiveOnlyDefault`** (tool row) — makes `browser_snapshot` default to actionable elements
|
|
206
|
+
only; the model can still pass `interactiveOnly: false` per call when it needs the text around them.
|
|
207
|
+
|
|
208
|
+
Both are honest trades, not free wins: a diff is smaller but requires the model to hold the previous
|
|
209
|
+
tree, and an interactive-only snapshot hides the prose the model may want to read. Measure before
|
|
210
|
+
turning them on in a deployment that shares prompts across many users.
|
|
211
|
+
|
|
169
212
|
### Prefer a switch to editing config?
|
|
170
213
|
|
|
171
214
|
The companion package [`dsh-browser-toggle`](https://github.com/miqian-nomad/dsh-browser-toggle) puts
|
|
172
215
|
this on a Settings card: enable/disable the browser, pick the page-reading mode, and turn the two
|
|
173
|
-
power tools on or off — no restart, no YAML.
|
|
216
|
+
power tools on or off — no restart, no YAML. The card also lists **which sites this browser looks
|
|
217
|
+
signed in to**. Two things make that list trustworthy: cookies are attributed **first-party** — only
|
|
218
|
+
domains this browser actually visited are candidates, because a tracker's cookie belongs to the site
|
|
219
|
+
you were browsing, not to the tracker — and "looks signed in" follows OWASP's session-ID criteria
|
|
220
|
+
(an `HttpOnly` cookie *and* a session-scoped or token-shaped one), not cookie names or counts. Domains that belong to the same company collapse into **one row** (`京东系(18 个域)`) with the totals
|
|
221
|
+
for the whole family, expandable to the individual domains. Each row also says **what the site is** (`jinritemai.com` → 巨量千川(抖音电商广告后台)): the page's
|
|
222
|
+
own title when the browser has that domain open, otherwise a curated offline table, otherwise just the
|
|
223
|
+
domain — never a guess. It is
|
|
224
|
+
labelled as an inference rather than a fact. It shows domain names,
|
|
225
|
+
counts and signal labels — never cookie names and never values. The list is shown only to you: nothing is written to disk; and no
|
|
226
|
+
tool exposes it to the model, so an agent cannot read this list. It is a separate package because
|
|
174
227
|
`dsh-client-modules` rejects a package mounted from multiple loader entries, and this plugin is
|
|
175
228
|
mounted as three (`service`, `playwright`, `tool`), while a Settings card needs a client module. The
|
|
176
229
|
card writes the gates into a managed block in the deployment's home-level `cordis.patch.yml`; the
|
|
@@ -234,11 +287,11 @@ Measured with ``npm run cost`` (per-turn, resident in the system prompt):
|
|
|
234
287
|
|
|
235
288
|
| Surface | Tools | Schema characters | ≈ tokens/turn |
|
|
236
289
|
|---|---|---|---|
|
|
237
|
-
| Schema defaults (`Config({})`, no patch layer) |
|
|
238
|
-
| Shipped bundle layer (this package's `cordis.patch.yml`: both power gates off) |
|
|
239
|
-
| Everything registered (`registerDisabledTools: true`, plus `extract`) |
|
|
290
|
+
| Schema defaults (`Config({})`, no patch layer) | 18 | 12,755 | ≈ 3,543 |
|
|
291
|
+
| Shipped bundle layer (this package's `cordis.patch.yml`: both power gates off) | 18 | 12,755 | ≈ 3,543 |
|
|
292
|
+
| Everything registered (`registerDisabledTools: true`, plus `extract`) | 21 | 15,608 | ≈ 4,336 |
|
|
240
293
|
|
|
241
|
-
Three things changed here, and only one of them was about size. (1) The click family was rewritten so each rule is stated once instead of three times: -390 characters, with every rule, example and rationale pinned by `tests/tool-descriptions.test.ts`, so a trim that eats one fails the build. (2) A gated tool is registered only while its capability is live — `browser_extract` on this deployment: -456 characters ≈ 127 tokens/turn. (3) Four descriptions were corrected to match the code they describe (dialog blocking, the gated diagnostics `browser_fill` points at, persistent-mode `browser_close`, and
|
|
294
|
+
Three things changed here, and only one of them was about size. (1) The click family was rewritten so each rule is stated once instead of three times: -390 characters, with every rule, example and rationale pinned by `tests/tool-descriptions.test.ts`, so a trim that eats one fails the build. (2) A gated tool is registered only while its capability is live — `browser_extract` on this deployment: -456 characters ≈ 127 tokens/turn. (3) Four descriptions were corrected to match the code they describe (dialog blocking, the gated diagnostics `browser_fill` points at, persistent-mode `browser_close`, and the click family’s landing note), which added 211 characters back because the old wording promised behaviour the implementation did not have. Net: 18,065 → 17,886 characters with everything registered; the shipped layer's total is identical to the schema-defaults row above, because it keeps both power gates off — those schemas are not registered at all and cost nothing. What is left is per-tool prose rather than repetition, so a further cut trades away real guidance.
|
|
242
295
|
|
|
243
296
|
#### KV Cache effect
|
|
244
297
|
|
|
@@ -281,7 +334,7 @@ Written for people deciding whether to hand an agent their browser:
|
|
|
281
334
|
```
|
|
282
335
|
- **Chromium family only** — Firefox/WebKit channels are not probed; providers are swappable if another engine is needed.
|
|
283
336
|
- **Extract needs a dedicated model route** — it does not reuse the main request's route; misconfiguration fails loudly at call time.
|
|
284
|
-
- **JS-driven navigations are not statically checkable** — `allowedDomains` covers `browser_navigate`, `browser_open_tab`, every click path (`browser_click
|
|
337
|
+
- **JS-driven navigations are not statically checkable** — `allowedDomains` covers `browser_navigate`, `browser_open_tab`, every click path (`browser_click`: ref, `precise`, raw-coordinate and `mode:"hover"` modes) and `browser_switch_tab`, but a button whose handler runs `location.href = …` can still leave the allowed hosts, and a tab that is already sitting on a disallowed host stays open (the agent simply refuses to drive it); deploy an external network guard for hard isolation.
|
|
285
338
|
- **Stability waits are bounded by design** — a perpetually animating element falls back to a forced click on its current rect rather than stalling; a page that never settles is snapshotted after the cap. This is intentional: the agent must never hang.
|
|
286
339
|
|
|
287
340
|
### Ref and lifecycle safety (fixed)
|
|
@@ -289,7 +342,7 @@ Written for people deciding whether to hand an agent their browser:
|
|
|
289
342
|
The scenario suites originally surfaced four hazards, now fixed and regression-tested:
|
|
290
343
|
|
|
291
344
|
- **Stale refs fail fast** — refs carry a per-snapshot nonce, so after a client-side re-render a stale ref matches nothing and `browser_click` fails with `REF_NOT_FOUND` instead of silently acting on a different element.
|
|
292
|
-
- **Link clicks respect `allowedDomains`** — every click path is checked before any input is delivered: `browser_click
|
|
345
|
+
- **Link clicks respect `allowedDomains`** — every click path is checked before any input is delivered: `browser_click` — ref mode (the element's `href`), `precise` ref mode, and raw-coordinate mode (the enclosing link found at the point). A disallowed host is rejected with `URL_NOT_ALLOWED`. `javascript:` links are exempt because they run in-page and navigate nowhere; other non-http(s) schemes stay refused.
|
|
293
346
|
- **A page-opened tab outside `allowedDomains` cannot be driven** — a page can open a tab itself (`target="_blank"`, `window.open`) and the plugin never sees that navigation, so the policy is applied where it can be: `browser_switch_tab` refuses to enter such a tab (the current tab is kept, the tab itself stays open for the user).
|
|
294
347
|
- **Idle disposal defers during operations** — a navigation slower than `idleTimeoutMs` completes normally; disposal only fires when the session is truly idle.
|
|
295
348
|
- **Mid-navigation snapshots settle** — a `browser_snapshot` racing a JS navigation waits for the new document and retries instead of surfacing a raw "Execution context was destroyed" error.
|
|
@@ -304,6 +357,14 @@ MIT
|
|
|
304
357
|
|
|
305
358
|
(此列表只记 **minor 里程碑**;每个补丁版本见 [CHANGELOG.md](CHANGELOG.md)。)
|
|
306
359
|
|
|
360
|
+
### 0.6.0(2026-10-16)
|
|
361
|
+
|
|
362
|
+
- **工具面三合一(破坏性)**:`browser_click` / `browser_hover` / `browser_click_at` 合并成一个 `browser_click` —— `mode: "hover"` 是原来的悬停、`precise: true` 是原来的几何中点兜底、`x`+`y` 是裸坐标。常驻 prompt **净减 16.5%(约六分之一)**:现在是 **18 个工具 12,755 字符 ≈ 3,543 token/轮**,全量注册(含三个闸门工具)21 个;规则一条没丢,守卫改成"合并后的工具必须保住原来三个的全部规则"。
|
|
363
|
+
- **`browser_fill` 接受 `value` 作 `text` 的别名**:模型按语义猜参数名不再第一次就撞墙;缺值时给的是指路错误(同时点出两种写法,并说明要点可见标签该用 `browser_click`)。
|
|
364
|
+
- **设置页多了一节「已登录的站点」(只读)**:按域名列出"像登录态"的 cookie —— 只列你在浏览器里**真的访问过**的域(第三方统计/广告域排除),判据用 OWASP 的会话特征(httpOnly 且会话级/值像令牌),**名字与条数不再作为依据**。不显示 cookie 的名字和值、不写磁盘、**模型看不到**;同一家公司的域折叠成一行(如 `京东系(18 个域)`),每行写明"这个域名是什么"(离线对照表 → 网页自己的标题 → 域名,认不出不编)。
|
|
365
|
+
- **`GET /state` 补上防伪头**:四个路由里唯一一个没校验 `x-dsh-plugin` 的;新增的 `GET /logins` 同样强制该头(这份名单是用户的登录站点)。
|
|
366
|
+
- **工程侧**:类型检查扩到 `tests/`(新增 `npm run typecheck` 并接进发版路径,一次修掉 217 处历史不一致,其中包含一处"类与接口不一致"的真实缺陷);新增"跨文档一致性"守卫(注册条目 ↔ package.json、设置卡片的关键说法在主插件三份文档间一致、配套件与主插件的安全承诺一致)。
|
|
367
|
+
|
|
307
368
|
### 0.5.0(2026-10-03)
|
|
308
369
|
|
|
309
370
|
- **CDP 白名单改为精确逐条列举(破坏性)**:`DOM.get` / `DOM.query` 这两个**家族前缀**不再放行任何东西 —— 它们此前多授权了 9 个 DOM 调用(`getAnchorElement`、`getDetachedDomNodes`、`getSearchResults` 等),而且会放行这两个家族**将来新增**的每一个方法。现在这份名单是 19 条完整方法名 + 等号匹配:**名单没听过的名字一律拒绝,不需要有人注意到它出现了**。同时经复核收回 9 条更早**手工列上**的调试器内部特性(`performSearch`、`requestNode`、`getRelayoutBoundary` 等)—— 那是有意选择,所以收它们是政策决定,不是修前缀溢出。允许面**只收不放**;插件自己的后台建页与窗口探测走内部会话,不受影响。
|
package/SECURITY.md
CHANGED
|
@@ -117,3 +117,25 @@ npm run cost # resident prompt cost of the registered tool surface
|
|
|
117
117
|
|
|
118
118
|
Open an issue on this repository. If it is about the scanner, quote the table above and include the
|
|
119
119
|
exact finding — the point of this file is that the answer does not have to be re-derived every time.
|
|
120
|
+
|
|
121
|
+
## 5. 隐私:设置页的「已登录的站点」名单
|
|
122
|
+
|
|
123
|
+
设置页多了一节只读展示:这台浏览器**像登录着**的站点。它由 provider 从浏览器自己给的 cookie 里推断,
|
|
124
|
+
出去的内容只有域名、cookie 条数和几个布尔信号(httpOnly / 会话级 / 值像令牌)——**没有 cookie 的名字,
|
|
125
|
+
也没有值**;`src/login-sites.ts` 在唯一入口处就把 `value` 丢掉了,测试里有一条断言把整份产出
|
|
126
|
+
序列化后搜不到任何名字或值。
|
|
127
|
+
|
|
128
|
+
几条边界,写清楚是为了以后不被误改:
|
|
129
|
+
|
|
130
|
+
- **不读 Chrome 的密码库**(`Login Data`)。只碰 cookie,且只碰浏览器交出来的那份。
|
|
131
|
+
- **也不读浏览历史。** 判断"这个域你访问过"用的是**当前打开着的页面 URL**(provider 自己看得到的那些),
|
|
132
|
+
不是 History 数据库 —— 插件只声明它看得见的东西。浏览器没开时退回到导出文件里的 localStorage origins
|
|
133
|
+
(一个域只有在被真正加载过时才可能有存储)。
|
|
134
|
+
- **不落盘。** 这份摘要是内存态(`runtime-state` 的 `publishLoginSites`),DSH 重启即消失。
|
|
135
|
+
卡片上"隐藏某一行"是**浏览器 localStorage 里的显示偏好**,不是服务端记录。
|
|
136
|
+
- **模型看不到。** 它不注册任何工具、不写进 diagnostics、也不出现在工具返回值里;唯一出口是
|
|
137
|
+
`GET /api/browser-playwright/logins`,而该路由**强制要求 `x-dsh-plugin: browser-toggle` 自定义头**
|
|
138
|
+
(自定义头会触发 CORS 预检,浏览器里的其它页面因此读不到它)。这条路由有测试盯着:不带头的请求必须 403。
|
|
139
|
+
- **它只是推断。** "有 cookie"不等于"登录着",所以界面写的是"推测登录";需要更确定时请自己打开窗口看。
|
|
140
|
+
- **域名说明也不出网。** "这个域名是什么"来自两处:当前打开页面的标题,以及包内一份离线对照表
|
|
141
|
+
(`src/site-aliases.ts`)。不查在线接口、不下载图标、不发送域名给任何第三方。
|
package/cordis.patch.yml
CHANGED
|
@@ -32,6 +32,11 @@
|
|
|
32
32
|
# exits. User choice: never auto-close (2026-09-04).
|
|
33
33
|
idleTimeoutMs: 0
|
|
34
34
|
maxSessions: 8
|
|
35
|
+
# Response-size lever (off by default): return "+/-/~" ref diffs between
|
|
36
|
+
# snapshots instead of the whole tree. Smaller returns on pages that
|
|
37
|
+
# re-render often; the model then has to hold the previous tree itself.
|
|
38
|
+
snapshot:
|
|
39
|
+
diff: false
|
|
35
40
|
- id: browser-tool
|
|
36
41
|
name: 'dsh-browser-playwright-codex/tool'
|
|
37
42
|
config:
|
|
@@ -55,6 +60,7 @@
|
|
|
55
60
|
# allowEvaluate: true # registers browser_evaluate
|
|
56
61
|
# allowCdp: true # registers browser_cdp (needs a DSH restart)
|
|
57
62
|
# registerDisabledTools: false
|
|
63
|
+
# interactiveOnlyDefault: false # true = snapshot returns actionable elements only
|
|
58
64
|
# maxWaitMs: 60000
|
|
59
65
|
#
|
|
60
66
|
# browser_evaluate runs arbitrary page JavaScript: it can read anything in
|
package/lib/compat.d.ts
CHANGED
|
@@ -13,19 +13,19 @@
|
|
|
13
13
|
* signal we want rather than a silent `undefined`.
|
|
14
14
|
*
|
|
15
15
|
* `CallId` / `ToolCallId` are nominal wrappers over a string whose runtime body
|
|
16
|
-
* is the identity function
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* is the identity function. The *type* now comes from the harness: its brand
|
|
17
|
+
* cannot be reproduced structurally, so a locally invented one produces values
|
|
18
|
+
* the harness rejects (2026-10-16: enabling type checking over `tests/` caught
|
|
19
|
+
* exactly that). Only the runtime identity function stays local, and a rename
|
|
20
|
+
* fails loudly at build time — which is the signal we want.
|
|
19
21
|
* @module dsh-browser-playwright-codex/compat
|
|
20
22
|
*/
|
|
23
|
+
import type { ToolCallId as HarnessToolCallId } from '@deepseek-ai/dsh-llm';
|
|
21
24
|
/**
|
|
22
|
-
* Correlates one model-issued tool call with its result
|
|
23
|
-
*
|
|
24
|
-
* cannot rename it out from under the tests.
|
|
25
|
+
* Correlates one model-issued tool call with its result: the harness's own brand,
|
|
26
|
+
* so values this plugin mints are accepted everywhere the harness expects one.
|
|
25
27
|
*/
|
|
26
|
-
export type ToolCallId =
|
|
27
|
-
readonly __brand?: 'ToolCallId';
|
|
28
|
-
};
|
|
28
|
+
export type ToolCallId = HarnessToolCallId;
|
|
29
29
|
/**
|
|
30
30
|
* Brand a string as a {@link ToolCallId}. Runtime identity, exactly like the
|
|
31
31
|
* harness brand constructor it replaces.
|
package/lib/compat.js
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Boundary adapter for the branded identifiers this plugin borrows from the
|
|
3
|
-
* DeepSeek Harness. The harness ships a release every few days and renames
|
|
4
|
-
* freely — `CallId` became `ToolCallId`, brand constructors moved to
|
|
5
|
-
* `@deepseek-ai/dsh-brand` — and a renamed identifier used to break an import
|
|
6
|
-
* in the middle of the tests.
|
|
7
|
-
*
|
|
8
|
-
* Scope, stated honestly: this file owns the *identifier brands* (below).
|
|
9
|
-
* The harness API surface this plugin uses on purpose (cordis `Context`,
|
|
10
|
-
* `defineTool` / `ToolExecution`, `BlockAssembler`, `createUserMessage`) is
|
|
11
|
-
* still imported directly at its call site — a rename there is a load-time
|
|
12
|
-
* failure that `npm run doctor` reports as a missing export, which is the
|
|
13
|
-
* signal we want rather than a silent `undefined`.
|
|
14
|
-
*
|
|
15
|
-
* `CallId` / `ToolCallId` are nominal wrappers over a string whose runtime body
|
|
16
|
-
* is the identity function, so this plugin defines its own instead of importing
|
|
17
|
-
* theirs — a harness rename cannot break it, and no version range has to be
|
|
18
|
-
* chased.
|
|
19
|
-
* @module dsh-browser-playwright-codex/compat
|
|
20
|
-
*/
|
|
21
1
|
/**
|
|
22
2
|
* Brand a string as a {@link ToolCallId}. Runtime identity, exactly like the
|
|
23
3
|
* harness brand constructor it replaces.
|
package/lib/contract.d.ts
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* Tool name suffixes in registration order; a wire name is `prefix + suffix`.
|
|
10
10
|
* Gated suffixes are kept in the same list so the full surface stays visible.
|
|
11
11
|
*/
|
|
12
|
-
export declare const TOOL_SUFFIXES: readonly ["navigate", "snapshot", "click", "dialog", "
|
|
12
|
+
export declare const TOOL_SUFFIXES: readonly ["navigate", "snapshot", "click", "dialog", "fill", "press", "scroll", "back", "forward", "wait", "tabs", "switch_tab", "open_tab", "close_tab", "screenshot", "extract", "evaluate", "cdp", "console_messages", "network_requests", "close"];
|
|
13
13
|
/** One tool name suffix. */
|
|
14
14
|
export type ToolSuffix = (typeof TOOL_SUFFIXES)[number];
|
|
15
15
|
/**
|
|
16
16
|
* Suffixes registered unconditionally. They are always in the system prompt,
|
|
17
17
|
* so this list is also the resident schema cost of the plugin.
|
|
18
18
|
*/
|
|
19
|
-
export declare const ALWAYS_ON_SUFFIXES: readonly ["navigate", "snapshot", "click", "dialog", "
|
|
19
|
+
export declare const ALWAYS_ON_SUFFIXES: readonly ["navigate", "snapshot", "click", "dialog", "fill", "press", "scroll", "back", "forward", "wait", "tabs", "switch_tab", "open_tab", "close_tab", "screenshot", "console_messages", "network_requests", "close"];
|
|
20
20
|
/**
|
|
21
21
|
* Suffixes registered only when their capability is enabled (or configured):
|
|
22
22
|
* a tool the deployment disabled can only error, so shipping its schema would
|
package/lib/contract.js
CHANGED
|
@@ -14,8 +14,6 @@ export const TOOL_SUFFIXES = [
|
|
|
14
14
|
'snapshot',
|
|
15
15
|
'click',
|
|
16
16
|
'dialog',
|
|
17
|
-
'click_at',
|
|
18
|
-
'hover',
|
|
19
17
|
'fill',
|
|
20
18
|
'press',
|
|
21
19
|
'scroll',
|
|
@@ -43,8 +41,6 @@ export const ALWAYS_ON_SUFFIXES = [
|
|
|
43
41
|
'snapshot',
|
|
44
42
|
'click',
|
|
45
43
|
'dialog',
|
|
46
|
-
'click_at',
|
|
47
|
-
'hover',
|
|
48
44
|
'fill',
|
|
49
45
|
'press',
|
|
50
46
|
'scroll',
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「已登录站点」的只读推断:把 cookie 按可注册域聚合、打分,供设置页展示。
|
|
3
|
+
*
|
|
4
|
+
* 两条设计来自成熟产品的做法,2026-10-16 重写时按它们改的(第一版按"谁设的域"聚合、用"名字像会话 +
|
|
5
|
+
* cookie 多"打分,结果真实名单里 `pubmatic.com`(广告交易所)和 `mmstat.com`(统计)都排在前面):
|
|
6
|
+
*
|
|
7
|
+
* 1. **归属看"你在哪个站",不看"谁设的"** —— Firefox 的 Total Cookie Protection 给每个访问过的
|
|
8
|
+
* 网站一个独立 cookie jar,第三方(统计/广告)的 cookie 属于**当时浏览的那个站**,不代表你登录了它。
|
|
9
|
+
* 所以这里只把「浏览器实际访问过的域」当候选第一方,其余只报个数、不进列表。
|
|
10
|
+
* 2. **判据用 OWASP 的会话 ID 特征** —— HttpOnly + Secure + 长且高熵 + 会话级;**名字是最不可靠的
|
|
11
|
+
* 信号**(框架名可被指纹、也可被改成任意名字),所以名字与"cookie 条数"都不再能单独把一条推成
|
|
12
|
+
* "推测登录"。
|
|
13
|
+
*
|
|
14
|
+
* 红线(写给下一个改这个文件的人):
|
|
15
|
+
* 1. **永不输出 cookie 的值,也不输出 cookie 的名字。** 出去的东西只有域名、条数和布尔信号;
|
|
16
|
+
* 名字只在本模块内部用于统计,值只被 `session.cookieJar()` 折算成一个布尔(像不像令牌)。
|
|
17
|
+
* 2. **不读 Chrome 的密码库**(Login Data)。这里只碰 cookie。
|
|
18
|
+
* 3. **这份数据不给模型看。** 它只经由设置页的路由出去;不注册工具、不写进 diagnostics。
|
|
19
|
+
* 4. 结论写"推测":cookie 存在 ≠ 登录着。
|
|
20
|
+
* @module dsh-browser-playwright-codex/login-sites
|
|
21
|
+
*/
|
|
22
|
+
/** The cookie fields this module may look at. `value` is deliberately absent. */
|
|
23
|
+
export interface CookieRow {
|
|
24
|
+
name: string;
|
|
25
|
+
domain: string;
|
|
26
|
+
/** Unix seconds; -1 (or absent) marks a session cookie. */
|
|
27
|
+
expires?: number;
|
|
28
|
+
httpOnly?: boolean;
|
|
29
|
+
secure?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Whether the value looked like a session token (long, opaque, mixed alphabet). Computed by
|
|
32
|
+
* `session.cookieJar()` from the value and reduced to a boolean there — the value itself never
|
|
33
|
+
* leaves that function.
|
|
34
|
+
*/
|
|
35
|
+
tokenLike?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** One aggregated site. Contains no cookie names and no values. */
|
|
38
|
+
export interface LoginSite {
|
|
39
|
+
/** Registrable domain the browser actually visited. */
|
|
40
|
+
domain: string;
|
|
41
|
+
/** Non-tracker cookies seen for this domain. */
|
|
42
|
+
cookies: number;
|
|
43
|
+
httpOnly: number;
|
|
44
|
+
/** Expiry more than a week out. */
|
|
45
|
+
longLived: number;
|
|
46
|
+
/** Session cookies (dropped when the browser closes). */
|
|
47
|
+
sessionLike: number;
|
|
48
|
+
/** Cookies whose value looked like a token. */
|
|
49
|
+
tokenLike: number;
|
|
50
|
+
/** Inference, not a fact: see the module header. */
|
|
51
|
+
likely: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The page's own title, when the browser has that domain open. This is how browsers and password
|
|
54
|
+
* managers make a hostname recognizable: the site names itself, and nothing goes stale. Absent for
|
|
55
|
+
* domains read from an exported state file (titles are not part of a cookie export).
|
|
56
|
+
*/
|
|
57
|
+
title?: string;
|
|
58
|
+
/**
|
|
59
|
+
* A short Chinese explanation of what this domain is ("巨量千川(抖音电商广告后台)"), from the
|
|
60
|
+
* curated table in site-aliases.ts. Absent when we do not actually know — the card then falls back
|
|
61
|
+
* to the page title, and finally to the bare domain. Never a guess.
|
|
62
|
+
*/
|
|
63
|
+
alias?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Which company this domain belongs to, so sibling domains collapse into one row. Set only when the
|
|
66
|
+
* curated table knows the owner; a domain with no group renders on its own.
|
|
67
|
+
*/
|
|
68
|
+
group?: {
|
|
69
|
+
key: string;
|
|
70
|
+
label: string;
|
|
71
|
+
};
|
|
72
|
+
/** Short labels explaining the score — never cookie names. */
|
|
73
|
+
signals: string[];
|
|
74
|
+
}
|
|
75
|
+
/** What the settings card renders. */
|
|
76
|
+
export interface LoginSites {
|
|
77
|
+
/** `live` = read from the open browser; `snapshot` = last exported state file. */
|
|
78
|
+
source: 'live' | 'snapshot';
|
|
79
|
+
/** Epoch ms when this summary was computed. */
|
|
80
|
+
capturedAt: number;
|
|
81
|
+
/** Non-tracker cookies counted across first-party domains. */
|
|
82
|
+
cookies: number;
|
|
83
|
+
/** First-party domains seen (before the display cap). */
|
|
84
|
+
total: number;
|
|
85
|
+
/** How many of those look signed in. */
|
|
86
|
+
likelyTotal: number;
|
|
87
|
+
/** Domains excluded because the browser never visited them (analytics/ad endpoints). */
|
|
88
|
+
thirdPartyTotal: number;
|
|
89
|
+
/**
|
|
90
|
+
* Whether first-party attribution could be applied. `unfiltered` means no visited-domain set was
|
|
91
|
+
* available (no live pages and no exported origins), so the list may contain third-party domains —
|
|
92
|
+
* the card says so instead of silently looking authoritative. 2026-10-16: the offline run of the
|
|
93
|
+
* real export file hit exactly this, and a silent fallback is how a feature overstates itself.
|
|
94
|
+
*/
|
|
95
|
+
attribution: 'first-party' | 'unfiltered';
|
|
96
|
+
/** The capped, sorted list actually shown. */
|
|
97
|
+
sites: LoginSite[];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Collapse a cookie host to its registrable domain. `www.x.com`, `.x.com` and `a.b.x.com`
|
|
101
|
+
* all become `x.com`; `a.x.co.uk` becomes `x.co.uk`.
|
|
102
|
+
* @param host - the cookie's `domain` (may carry a leading dot).
|
|
103
|
+
* @returns the registrable domain, lowercased.
|
|
104
|
+
*/
|
|
105
|
+
export declare function registrableDomain(host: string): string;
|
|
106
|
+
/** Whether a cookie name belongs to the always-present analytics/consent family. */
|
|
107
|
+
export declare function isTracker(name: string): boolean;
|
|
108
|
+
/** Whether a whole host is an analytics/ad endpoint rather than a site you log into. */
|
|
109
|
+
export declare function isTrackerHost(host: string): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Whether a cookie *value* looks like a session token rather than an analytics blob. OWASP's
|
|
112
|
+
* session-ID guidance is the basis: at least 64 bits of entropy, hex ids ≥16 chars, base64 ids
|
|
113
|
+
* longer still, opaque content. Analytics values are short, structured or JSON, so they fail here.
|
|
114
|
+
*
|
|
115
|
+
* The value is passed in and never returned: callers turn this into the `tokenLike` boolean.
|
|
116
|
+
* @param value - the raw cookie value.
|
|
117
|
+
* @returns whether it plausibly is a token.
|
|
118
|
+
*/
|
|
119
|
+
export declare function looksLikeToken(value: string): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Aggregate cookies into per-domain login inferences. Trackers are dropped, third-party domains
|
|
122
|
+
* (ones the browser never visited) are counted but not listed, and only counts and boolean signals
|
|
123
|
+
* leave this function.
|
|
124
|
+
* @param rows - cookies as read from the browser (or the last exported state file).
|
|
125
|
+
* @param source - where those cookies came from, for the card's label.
|
|
126
|
+
* @param now - epoch ms, injectable so tests are not time-dependent.
|
|
127
|
+
* @param options.visited - registrable domains the browser actually visited; the rest is third-party.
|
|
128
|
+
* @param options.titles - page titles by registrable domain, for display only.
|
|
129
|
+
* @param options.maxSites - display cap; defaults to {@link MAX_SITES}.
|
|
130
|
+
* @returns the display payload.
|
|
131
|
+
*/
|
|
132
|
+
export declare function summarizeLoginSites(rows: readonly CookieRow[], source: 'live' | 'snapshot', now: number, options?: {
|
|
133
|
+
visited?: readonly string[] | ReadonlySet<string>;
|
|
134
|
+
titles?: ReadonlyMap<string, string>;
|
|
135
|
+
maxSites?: number;
|
|
136
|
+
}): LoginSites;
|