dsh-browser-playwright-codex 0.7.0 → 0.7.2
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 +28 -1
- package/README.md +2 -1
- package/lib/client.js +107 -91
- package/lib/gates-patch.d.ts +6 -3
- package/lib/gates-patch.js +18 -8
- package/lib/runtime-state.d.ts +2 -0
- package/lib/runtime-state.js +5 -1
- package/lib/settings-card.js +9 -2
- package/lib/tool.js +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
|
+
## 0.7.2 — 2026-10-19
|
|
2
|
+
|
|
3
|
+
一句话摘要:**设置卡推倒重做** —— 用户要求按 DSH 设置页规范重新设计:首屏只留「主开关 + 三个分区摘要」,所有长说明收进折叠。0.7.1 因卡在 npm staged 状态作废(版本号让给这一版)。
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **设置卡重做**:主开关一行;「页面读取方式」两个紧凑单选项 + 一个「两种模式到底差在哪」折叠;「工具权限」三个开关(任意 JS / 原始 CDP / 诊断工具)+ 一个「这些开关具体放行什么」折叠,标题行直接显示「N / 3 已开启」;「已登录的站点」默认收起,标题行只给「N 个域 · M 个 cookie」,展开后仍按公司分组折叠。名单每行只保留域名 + 说明 + cookie 数,逐个站点的四个 OWASP 信号不再塞在主视图里。
|
|
8
|
+
- 卡片文案统一为"人话",不再出现 evaluate / cdp / 引擎 / legacy / aria 这些词。
|
|
9
|
+
|
|
1
10
|
# Changelog
|
|
2
11
|
|
|
3
12
|
Bilingual by intent: the version headings and the summary of each release are given in both
|
|
4
13
|
languages, the individual entries follow the language the surrounding code and docs use.
|
|
5
14
|
|
|
15
|
+
## 0.7.1 — 2026-10-19
|
|
16
|
+
|
|
17
|
+
一句话摘要:**只修文档与一处口径** —— 外部评分(8.9/10)点名的问题:CHANGELOG 的数字手写写错、对比表把五个闸门写成三个、有头模式的 viewport 仍然不生效却没标注。行为零变化。
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **设置卡新增第三个工具开关:诊断工具**(`browser_console_messages` / `browser_network_requests`)。与 `evaluate` / `cdp` 同一套机制:`POST /api/browser-playwright/gates` 现在接受 `diagnostics`,home 补丁块的整行 config 一并写入,`tool.ts` 把**实际生效值**(而不是猜的)发布给卡片;卡片上显示"当前:N 个开着"也把这一项算进去。
|
|
22
|
+
- **「已登录的站点」默认折叠**:标题行直接给出「N 个域 · M 个 cookie · 点击展开」,点开才渲染说明与名单(分组仍各自折叠)。设置页不再被这份名单占掉大半屏。
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **CHANGELOG 0.7.0 的数字按实测改正**:常驻是 **16 工具 / 11,179 字符**(此前手写成 11,005 字符,token 数也跟着漂),对比基线 18 工具 / 12,755 字符保持不变,字符降幅 **−1,576(−12.4%)**。这是连续第三个版本出现"主文档被守卫钉住、CHANGELOG 叙事手写漂移"——本轮起新增加一条守卫:**最新一节 CHANGELOG 不许出现手写的 `≈ N token/轮` 数字**,要写就写实测命令与结果表。
|
|
27
|
+
- **对比与优势.md 的闸门清单**:"五个闸门"此前只列了 `evaluate` / `cdp` / `extract` 三个名字,补上 `console_messages` / `network_requests`,并把它与全量注册的字符差从旧的 2,853 更正为实测 4,603 字符(token 换算见 README 成本表)。
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **有头模式的 `viewport` 标注**:`browser-lifecycle.ts:44` 用 `viewport: null` 跟随真实窗口,所以 `launch.viewport` 只在 `headless: true` 时生效;README 配置示例与 Known Limitations 现在明写这一点(此前"配了也不生效"没有任何提示,被外部评分连续四次点出)。
|
|
32
|
+
|
|
6
33
|
## 0.7.0 — 2026-10-19
|
|
7
34
|
|
|
8
35
|
一句话摘要:**两个插件合成一个** —— 设置卡片并入本包;加载行从三行收成一行(这是 `dsh-client-modules` 允许本包带 client 模块的前提),独立的 `dsh-browser-toggle` 退役。
|
|
@@ -19,7 +46,7 @@ languages, the individual entries follow the language the surrounding code and d
|
|
|
19
46
|
|
|
20
47
|
### Changed
|
|
21
48
|
|
|
22
|
-
- **两个诊断工具改为门控**:`browser_console_messages` / `browser_network_requests` 不再常驻 —— 新增 `tool.diagnostics: false`(默认关),开启后才注册。常驻成本 **18 工具 12,755 字符 ≈ 3,543 token/轮 → 16 工具 11,
|
|
49
|
+
- **两个诊断工具改为门控**:`browser_console_messages` / `browser_network_requests` 不再常驻 —— 新增 `tool.diagnostics: false`(默认关),开启后才注册。常驻成本 **18 工具 12,755 字符 ≈ 3,543 token/轮 → 16 工具 11,179 字符 ≈ 3,105 token/轮(−438,−12.4%)**;全量注册仍是 21 工具 15,782 字符 ≈ 4,384。
|
|
23
50
|
- **快照瘦身(运行时大头)**:无名无状态的 `generic` 包装层不再逐层输出(真实页面常把一个链接包在 7~8 层 div 里),显示用 `href` 去掉追踪参数(`utm_*` / `gclid` / `rsv_*` / `sa` 等)并截到 160 字符。百度结果页形状的样本:**6,169 → 2,557 字符(−58.6%)**,行数 108 → 24。refs 与点击行为不变 —— 省的是模型读到的东西。
|
|
24
51
|
- 闸门写入块改为**整行 config**(新标记 `# >>> dsh-browser:`),并兼容旧的 `dsh-browser-toggle` 标记就地替换 —— 避免同一个 id 被两段 patch 争抢。
|
|
25
52
|
- `scripts/shipped-gates.mjs` 按新行名读取出厂闸门;`scripts/build-client.mjs` 把 `client/client.js` 复制进 `lib/`。
|
package/README.md
CHANGED
|
@@ -227,7 +227,7 @@ All tunables are patchable through the profile's `cordis.patch.yml` (later layer
|
|
|
227
227
|
headless: false # visible window you can watch and rescue
|
|
228
228
|
persistent: true # profile-backed window, login survives close
|
|
229
229
|
# profileDir: ~/.dsh/browser-profiles/playwright by default
|
|
230
|
-
viewport: { width: 1280, height: 800 }
|
|
230
|
+
viewport: { width: 1280, height: 800 } # headful: IGNORED (the window tracks the desktop; only headless: true pins it)
|
|
231
231
|
navigationTimeoutMs: 30000
|
|
232
232
|
ignoreHTTPSErrors: false
|
|
233
233
|
allowedDomains: [] # e.g. ['github.com'] restricts navigation
|
|
@@ -431,6 +431,7 @@ links below go to GitHub rather than to files inside the package:
|
|
|
431
431
|
registerDisabledTools: false
|
|
432
432
|
maxWaitMs: 60000
|
|
433
433
|
```
|
|
434
|
+
- **`launch.viewport` only applies headless** — the visible window is a real window: it tracks the desktop size (`browser-lifecycle.ts` passes `viewport: null` in headful), so setting 1280×800 there changes nothing. Pin the size by running `headless: true`, or resize the window itself.
|
|
434
435
|
- **Chromium family only** — Firefox/WebKit channels are not probed; providers are swappable if another engine is needed.
|
|
435
436
|
- **Extract needs a dedicated model route** — it does not reuse the main request's route; misconfiguration fails loudly at call time.
|
|
436
437
|
- **JS-driven navigations are not statically checkable** — `allowedDomains` covers `browser_navigate`, `browser_open_tab`, every navigating click (`browser_click`: `ref`, `text`, `precise` and raw-coordinate modes) and `browser_switch_tab`; `mode: "hover"` reveals content without activating anything, so it is not checked against the URL policy. A button whose handler runs `location.href = …` can still leave the allowed hosts, and so can a `<form>` whose `action` points at another host (pressing its submit button is a navigation this check does not statically see), 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.
|
package/lib/client.js
CHANGED
|
@@ -176,6 +176,11 @@ window.__ModuleLoader__.load({
|
|
|
176
176
|
label: '允许在页面里执行任意 JavaScript',
|
|
177
177
|
hint: '打开后 AI 能在任意网页里运行脚本:能读走页面上的任何内容,也能用你的登录状态去点、去填、去提交。真正需要时才打开。',
|
|
178
178
|
},
|
|
179
|
+
{
|
|
180
|
+
key: 'diagnostics',
|
|
181
|
+
label: '诊断工具:控制台消息与网络请求',
|
|
182
|
+
hint: '给排障用:能读出页面控制台的报错和最近的网络请求(URL、状态、失败原因)。平时用不到;遇到"点了没反应"或"背后的请求失败了"再打开。',
|
|
183
|
+
},
|
|
179
184
|
{
|
|
180
185
|
key: 'allowCdp',
|
|
181
186
|
label: '允许原始 CDP 调试协议',
|
|
@@ -224,13 +229,14 @@ window.__ModuleLoader__.load({
|
|
|
224
229
|
const [enabled, setEnabled] = useState(null);
|
|
225
230
|
const [engine, setEngine] = useState(null);
|
|
226
231
|
const [engineUserChosen, setEngineUserChosen] = useState(false);
|
|
227
|
-
const [gates, setGates] = useState({ allowEvaluate: null, allowCdp: null });
|
|
232
|
+
const [gates, setGates] = useState({ allowEvaluate: null, allowCdp: null, diagnostics: null });
|
|
228
233
|
const [busy, setBusy] = useState(false);
|
|
229
234
|
const [msg, setMsg] = useState('');
|
|
230
235
|
const [logins, setLogins] = useState(null);
|
|
231
236
|
const [showAll, setShowAll] = useState(false);
|
|
232
237
|
const [openGroups, setOpenGroups] = useState([]);
|
|
233
238
|
const [hiddenDomains, setHiddenDomains] = useState(readHiddenDomains());
|
|
239
|
+
const [loginsOpen, setLoginsOpen] = useState(false);
|
|
234
240
|
const mountedRef = useRef(true);
|
|
235
241
|
|
|
236
242
|
const refresh = async () => {
|
|
@@ -245,6 +251,7 @@ window.__ModuleLoader__.load({
|
|
|
245
251
|
setGates({
|
|
246
252
|
allowEvaluate: typeof s.allowEvaluate === 'boolean' ? s.allowEvaluate : null,
|
|
247
253
|
allowCdp: typeof s.allowCdp === 'boolean' ? s.allowCdp : null,
|
|
254
|
+
diagnostics: typeof s.diagnostics === 'boolean' ? s.diagnostics : null,
|
|
248
255
|
});
|
|
249
256
|
}
|
|
250
257
|
catch (error) {
|
|
@@ -333,16 +340,21 @@ window.__ModuleLoader__.load({
|
|
|
333
340
|
const payload = {
|
|
334
341
|
allowEvaluate: gates.allowEvaluate === true,
|
|
335
342
|
allowCdp: gates.allowCdp === true,
|
|
343
|
+
diagnostics: gates.diagnostics === true,
|
|
336
344
|
};
|
|
337
345
|
payload[key] = next;
|
|
338
346
|
try {
|
|
339
347
|
const r = await api('/gates', 'POST', payload);
|
|
340
348
|
if (r && r.error)
|
|
341
349
|
throw new Error(r.error.message || r.error.code || 'unknown');
|
|
342
|
-
setGates({
|
|
350
|
+
setGates({
|
|
351
|
+
allowEvaluate: r.allowEvaluate === true,
|
|
352
|
+
allowCdp: r.allowCdp === true,
|
|
353
|
+
diagnostics: r.diagnostics === true,
|
|
354
|
+
});
|
|
343
355
|
setMsg(r.changed
|
|
344
356
|
? (next
|
|
345
|
-
? '已打开:' + (key ===
|
|
357
|
+
? '已打开:' + ((GATE_ROWS.find((row) => row.key === key) || {}).label || key) + '。工具会在热重载后出现(约 1-2 秒);若一直没出现,重启一次 DSH。'
|
|
346
358
|
: '已关闭。工具会在热重载后消失;若还在,重启一次 DSH。')
|
|
347
359
|
: '状态未变化');
|
|
348
360
|
}
|
|
@@ -360,18 +372,26 @@ window.__ModuleLoader__.load({
|
|
|
360
372
|
const statusLabel = enabled === null ? '读取中…' : (enabled ? '已启用' : '已禁用');
|
|
361
373
|
const current = ENGINE_OPTIONS.find((o) => o.id === engine) || null;
|
|
362
374
|
|
|
375
|
+
const GATE_SHORT = {
|
|
376
|
+
allowEvaluate: '页面里的任意 JavaScript',
|
|
377
|
+
allowCdp: '原始 CDP 调试协议',
|
|
378
|
+
diagnostics: '控制台消息 / 网络请求',
|
|
379
|
+
};
|
|
380
|
+
const ENGINE_SHORT = { legacy: '最稳,奇怪网站也不容易出错', aria: '更准,复杂页面优先' };
|
|
381
|
+
|
|
363
382
|
return h('div', { style: S.page }, [
|
|
364
383
|
h('h2', { key: 'title', style: S.title }, '浏览器'),
|
|
365
384
|
h('p', { key: 'intro', style: S.intro },
|
|
366
|
-
'让 AI
|
|
385
|
+
'让 AI 用你的登录状态替你上网办事。这里的改动立刻生效,不需要重启。'),
|
|
386
|
+
|
|
387
|
+
// ---- 主开关 ----
|
|
367
388
|
h('div', { key: 'row', style: S.row }, [
|
|
368
389
|
h(Dot, { key: 'dot', state }),
|
|
369
390
|
h('div', { key: 'text', style: { flex: 1, minWidth: 0 } }, [
|
|
370
391
|
h('div', { key: 'label', style: S.rowTitle }, '允许 AI 操作网页'),
|
|
371
|
-
h('div', { key: 'desc', style: S.rowDesc },
|
|
392
|
+
h('div', { key: 'desc', style: S.rowDesc },
|
|
372
393
|
h(Tag, { key: 'status' }, statusLabel),
|
|
373
|
-
'
|
|
374
|
-
]),
|
|
394
|
+
'关闭后窗口会关掉,登录状态保留。'),
|
|
375
395
|
]),
|
|
376
396
|
h(Toggle, {
|
|
377
397
|
key: 'toggle',
|
|
@@ -381,37 +401,40 @@ window.__ModuleLoader__.load({
|
|
|
381
401
|
label: '允许 AI 操作网页',
|
|
382
402
|
}),
|
|
383
403
|
]),
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
404
|
+
|
|
405
|
+
// ---- 页面读取方式 ----
|
|
406
|
+
h('div', { key: 'engineGroup', style: S.group }, [
|
|
407
|
+
'页面读取方式',
|
|
408
|
+
h('span', { key: 'cur', style: S.groupCount },
|
|
409
|
+
current ? ' 当前:' + current.label : ' 读取中…'),
|
|
410
|
+
]),
|
|
411
|
+
h('div', { key: 'engines', style: { ...S.list, marginTop: 8 } },
|
|
412
|
+
ENGINE_OPTIONS.map((o) => h(RadioRow, {
|
|
390
413
|
key: o.id,
|
|
391
|
-
p: o,
|
|
414
|
+
p: { id: o.id, label: o.label, hint: ENGINE_SHORT[o.id] || '' },
|
|
392
415
|
selected: engine === o.id,
|
|
393
416
|
disabled: engine === null || busy,
|
|
394
417
|
onPick: chooseEngine,
|
|
395
418
|
}))),
|
|
396
|
-
h(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
419
|
+
h(Notes, {
|
|
420
|
+
key: 'engineNotes',
|
|
421
|
+
title: '两种模式到底差在哪',
|
|
422
|
+
children: h('div', null, ENGINE_OPTIONS.map((o) => h('p', { key: o.id, style: S.note },
|
|
423
|
+
o.label + ':' + o.hint))),
|
|
424
|
+
}),
|
|
425
|
+
|
|
426
|
+
// ---- 工具权限 ----
|
|
427
|
+
h('div', { key: 'gateGroup', style: S.group }, [
|
|
428
|
+
'工具权限',
|
|
429
|
+
h('span', { key: 'sum', style: S.groupCount },
|
|
430
|
+
gatesOn === 0 ? ' 全部关闭' : ' ' + gatesOn + ' / ' + GATE_ROWS.length + ' 已开启'),
|
|
402
431
|
]),
|
|
403
|
-
h('div', { key: '
|
|
404
|
-
|
|
405
|
-
h('p', { style: S.note },
|
|
406
|
-
'这两个开关控制的是"能力",不是效果微调:打开"任意 JavaScript"就等于把页面里的东西都交给 AI。'
|
|
407
|
-
+ '插件包里默认关闭 —— 因为包是发给每个安装者的,默认打开等于替所有人做了这个决定;'
|
|
408
|
-
+ '这里打开的是你自己机器上的选择,记在你自己机器的配置层里。')),
|
|
409
|
-
h('div', { key: 'gates', style: { ...S.list, marginTop: 8 } }, GATE_ROWS.map((row) =>
|
|
410
|
-
h('div', { key: row.key, style: S.row }, [
|
|
432
|
+
h('div', { key: 'gates', style: { ...S.list, marginTop: 8 } },
|
|
433
|
+
GATE_ROWS.map((row) => h('div', { key: row.key, style: S.row }, [
|
|
411
434
|
h(Dot, { key: 'dot', state: gateState(row.key) }),
|
|
412
435
|
h('div', { key: 'text', style: { flex: 1, minWidth: 0 } }, [
|
|
413
436
|
h('div', { key: 'label', style: S.rowTitle }, row.label),
|
|
414
|
-
h('div', { key: 'desc', style: S.rowDesc }, row.hint),
|
|
437
|
+
h('div', { key: 'desc', style: S.rowDesc }, GATE_SHORT[row.key] || row.hint),
|
|
415
438
|
]),
|
|
416
439
|
h(Toggle, {
|
|
417
440
|
key: 'toggle',
|
|
@@ -421,34 +444,54 @@ window.__ModuleLoader__.load({
|
|
|
421
444
|
label: row.label,
|
|
422
445
|
}),
|
|
423
446
|
]))),
|
|
424
|
-
h(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
447
|
+
h(Notes, {
|
|
448
|
+
key: 'gateNotes',
|
|
449
|
+
title: '这些开关具体放行什么',
|
|
450
|
+
children: h('div', null, [
|
|
451
|
+
h('p', { key: 'evaluate', style: S.note },
|
|
452
|
+
'页面里执行任意 JavaScript:能读走页面内容,也能用你的登录状态去点、去填、去提交。真正需要时才打开。'),
|
|
453
|
+
h('p', { key: 'cdp', style: S.note },
|
|
454
|
+
'原始 CDP 协议:只放行查看类和模拟输入类命令;读 Cookie/存储、改网络、动安全设置一律被拦。排查疑难页面用。'),
|
|
455
|
+
h('p', { key: 'diagnostics', style: S.note },
|
|
456
|
+
'控制台消息 + 网络请求:能读出页面报错和最近的请求(URL、状态、失败原因)。平时用不到,遇到"点了没反应"再打开。'),
|
|
457
|
+
h('p', { key: 'effect', style: S.note },
|
|
458
|
+
'工具在热重载后出现或消失(约 1-2 秒);若一直没变,重启一次 DSH。'),
|
|
459
|
+
]),
|
|
460
|
+
}),
|
|
461
|
+
|
|
462
|
+
// ---- 已登录站点(默认收起) ----
|
|
463
|
+
h('div', {
|
|
464
|
+
key: 'loginsGroup',
|
|
465
|
+
style: { ...S.group, cursor: 'pointer', userSelect: 'none' },
|
|
466
|
+
onClick: () => setLoginsOpen((open) => !open),
|
|
467
|
+
}, [
|
|
468
|
+
'已登录的站点',
|
|
469
|
+
h('span', { key: 'count', style: S.groupCount },
|
|
470
|
+
logins === null || logins.sites === undefined
|
|
471
|
+
? ' 读取中…'
|
|
472
|
+
: ' ' + logins.sites.length + ' 个域 · '
|
|
473
|
+
+ logins.sites.reduce((acc, site) => acc + site.cookies, 0) + ' 个 cookie'),
|
|
474
|
+
h('span', { key: 'hint', style: { ...S.groupCount, marginLeft: 8 } },
|
|
475
|
+
loginsOpen ? '点击收起' : '点击展开'),
|
|
476
|
+
]),
|
|
477
|
+
loginsOpen ? h(Notes, { key: 'loginsNotes', title: '这份名单怎么来的' },
|
|
430
478
|
h('p', { style: S.note },
|
|
431
|
-
'
|
|
432
|
-
+ '
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
logins === null
|
|
479
|
+
'只列这台浏览器真的访问过的域;第三方统计/广告域已排除。判据是 OWASP 的会话特征,'
|
|
480
|
+
+ '名字不作为依据;不显示 cookie 的名字和值,名单只在本机内存,AI 看不到。')) : null,
|
|
481
|
+
loginsOpen === false
|
|
482
|
+
? null
|
|
483
|
+
: logins === null
|
|
437
484
|
? h('div', { key: 'loginsUnknown', style: S.tip },
|
|
438
485
|
'读取不到:这个 profile 里的插件版本还没上报,或还没打开过浏览器。')
|
|
439
486
|
: (() => {
|
|
440
487
|
const pool = showAll ? logins.sites : logins.sites.filter((s) => s.likely);
|
|
441
488
|
const visible = pool.filter((s) => hiddenDomains.indexOf(s.domain) < 0);
|
|
442
489
|
const hiddenCount = pool.length - visible.length;
|
|
443
|
-
// 同一家公司的域折叠成一行:真实名单里京东系二十来个域各自占一行,小半屏都是它。
|
|
444
490
|
const byGroup = new Map();
|
|
445
491
|
const items = [];
|
|
446
492
|
for (const site of visible) {
|
|
447
493
|
const key = site.group ? site.group.key : null;
|
|
448
|
-
if (key === null) {
|
|
449
|
-
items.push({ kind: 'single', site });
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
494
|
+
if (key === null) { items.push({ kind: 'single', site }); continue; }
|
|
452
495
|
let group = byGroup.get(key);
|
|
453
496
|
if (group === undefined) {
|
|
454
497
|
group = { kind: 'group', key, label: site.group.label, sites: [] };
|
|
@@ -457,7 +500,6 @@ window.__ModuleLoader__.load({
|
|
|
457
500
|
}
|
|
458
501
|
group.sites.push(site);
|
|
459
502
|
}
|
|
460
|
-
// 组里只剩一个域就没有折叠的意义,退回单行。
|
|
461
503
|
for (let i = items.length - 1; i >= 0; i -= 1) {
|
|
462
504
|
if (items[i].kind === 'group' && items[i].sites.length === 1)
|
|
463
505
|
items[i] = { kind: 'single', site: items[i].sites[0] };
|
|
@@ -470,16 +512,12 @@ window.__ModuleLoader__.load({
|
|
|
470
512
|
h('div', { key: 'text', style: { flex: 1, minWidth: 0 } }, [
|
|
471
513
|
h('div', { key: 'label', style: S.rowTitle }, [
|
|
472
514
|
site.likely ? h(Tag, { key: 'tag' }, '推测登录') : null,
|
|
473
|
-
// 说明优先:光看域名认不出来是这个功能最初的痛点。
|
|
474
515
|
site.alias || site.title || site.domain,
|
|
475
516
|
]),
|
|
476
517
|
h('div', { key: 'desc', style: S.rowDesc },
|
|
477
518
|
(site.alias ? site.domain + ' ' : '')
|
|
478
519
|
+ (site.title && site.title !== site.alias ? '「' + site.title + '」 ' : '')
|
|
479
|
-
+ site.cookies + ' 个 cookie
|
|
480
|
-
+ ' / 会话 ' + site.sessionLike
|
|
481
|
-
+ ' / 值像令牌 ' + site.tokenLike + ')'
|
|
482
|
-
+ (site.signals.length > 0 ? ' ' + site.signals.join(' · ') : '')),
|
|
520
|
+
+ site.cookies + ' 个 cookie'),
|
|
483
521
|
]),
|
|
484
522
|
h(Btn, {
|
|
485
523
|
key: 'hide',
|
|
@@ -493,74 +531,52 @@ window.__ModuleLoader__.load({
|
|
|
493
531
|
const renderItem = (item) => {
|
|
494
532
|
if (item.kind === 'single') return rowFor(item.site, false);
|
|
495
533
|
const open = openGroups.indexOf(item.key) >= 0;
|
|
496
|
-
const anyLikely = item.sites.some((s) => s.likely);
|
|
497
534
|
return h('div', { key: 'group-' + item.key }, [
|
|
498
535
|
h('div', { key: 'head', style: S.row }, [
|
|
499
536
|
h('div', { key: 'text', style: { flex: 1, minWidth: 0 } }, [
|
|
500
|
-
h('div', { key: 'label', style: S.rowTitle },
|
|
501
|
-
|
|
502
|
-
item.label + '(' + item.sites.length + ' 个域)',
|
|
503
|
-
]),
|
|
537
|
+
h('div', { key: 'label', style: S.rowTitle },
|
|
538
|
+
item.label + '(' + item.sites.length + ' 个域)'),
|
|
504
539
|
h('div', { key: 'desc', style: S.rowDesc },
|
|
505
|
-
sum(item.sites, (s) => s.cookies) + ' 个 cookie
|
|
506
|
-
+ sum(item.sites, (s) => s.httpOnly) + ' 会话 '
|
|
507
|
-
+ sum(item.sites, (s) => s.sessionLike) + ' 值像令牌 '
|
|
508
|
-
+ sum(item.sites, (s) => s.tokenLike)),
|
|
540
|
+
sum(item.sites, (s) => s.cookies) + ' 个 cookie'),
|
|
509
541
|
]),
|
|
510
542
|
h(Btn, {
|
|
511
543
|
key: 'toggle',
|
|
512
|
-
onClick: () =>
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
: openGroups.concat([item.key]));
|
|
516
|
-
},
|
|
544
|
+
onClick: () => setOpenGroups(open
|
|
545
|
+
? openGroups.filter((k) => k !== item.key)
|
|
546
|
+
: openGroups.concat([item.key])),
|
|
517
547
|
}, open ? '收起' : '展开'),
|
|
518
548
|
]),
|
|
519
|
-
open
|
|
520
|
-
? h('div', { key: 'members' }, item.sites.map((s) => rowFor(s, true)))
|
|
521
|
-
: null,
|
|
549
|
+
open ? h('div', { key: 'members' }, item.sites.map((s) => rowFor(s, true))) : null,
|
|
522
550
|
]);
|
|
523
551
|
};
|
|
524
552
|
return h('div', { key: 'loginsBody' }, [
|
|
525
553
|
h('div', { key: 'list', style: { ...S.list, marginTop: 8 } },
|
|
526
554
|
visible.length === 0
|
|
527
555
|
? h('div', { key: 'none', style: S.tip },
|
|
528
|
-
hiddenCount > 0
|
|
529
|
-
? '这一页都被你隐藏了。'
|
|
530
|
-
: '还没发现登录过的站点(用这个浏览器登录过网站后,这里会出现)。')
|
|
556
|
+
hiddenCount > 0 ? '这一页都被你隐藏了。' : '还没发现登录过的站点。')
|
|
531
557
|
: items.map(renderItem)),
|
|
532
558
|
h('div', { key: 'loginsFooter', style: S.footer }, [
|
|
533
559
|
h('span', { key: 'src', style: S.tip },
|
|
534
|
-
(logins.source === 'live' ? '
|
|
535
|
-
+ '
|
|
536
|
-
+ ' 你在 DSH 里访问过 ' + logins.total + ' 个域,其中 '
|
|
537
|
-
+ logins.likelyTotal + ' 个像登录态'
|
|
538
|
-
+ (logins.thirdPartyTotal > 0
|
|
539
|
-
? ';另有 ' + logins.thirdPartyTotal + ' 个第三方域(统计/广告)已排除'
|
|
540
|
-
: '')
|
|
541
|
-
+ (logins.attribution === 'unfiltered'
|
|
542
|
-
? '(这次没拿到浏览器打开的页面列表,未做第一方过滤:下面可能有第三方域)'
|
|
543
|
-
: '')),
|
|
560
|
+
(logins.source === 'live' ? '实时' : '上次快照')
|
|
561
|
+
+ (logins.attribution === 'unfiltered' ? '(未做第一方过滤)' : '')),
|
|
544
562
|
h(Btn, {
|
|
545
563
|
key: 'all',
|
|
546
564
|
onClick: () => setShowAll(!showAll),
|
|
547
|
-
}, showAll
|
|
548
|
-
? '只看像登录态的 ' + logins.likelyTotal + ' 个'
|
|
549
|
-
: '显示其余 ' + (logins.total - logins.likelyTotal) + ' 个'),
|
|
550
|
-
|
|
565
|
+
}, showAll ? '只看像登录态的' : '显示其余 ' + (logins.total - logins.likelyTotal) + ' 个'),
|
|
551
566
|
hiddenCount > 0
|
|
552
567
|
? h(Btn, {
|
|
553
568
|
key: 'reset',
|
|
554
|
-
onClick: () => {
|
|
555
|
-
|
|
556
|
-
writeHiddenDomains([]);
|
|
557
|
-
},
|
|
558
|
-
}, '恢复显示 ' + hiddenCount + ' 个被隐藏的')
|
|
569
|
+
onClick: () => { setHiddenDomains([]); writeHiddenDomains([]); },
|
|
570
|
+
}, '恢复 ' + hiddenCount + ' 个被隐藏的')
|
|
559
571
|
: null,
|
|
560
572
|
]),
|
|
561
573
|
]);
|
|
562
574
|
})(),
|
|
575
|
+
|
|
563
576
|
msg ? h('div', { key: 'msg', style: S.msg }, msg) : null,
|
|
577
|
+
h('div', { key: 'foot', style: S.footer }, [
|
|
578
|
+
h(Btn, { key: 'refresh', onClick: refresh, disabled: busy }, '刷新状态'),
|
|
579
|
+
]),
|
|
564
580
|
]);
|
|
565
581
|
}
|
|
566
582
|
|
package/lib/gates-patch.d.ts
CHANGED
|
@@ -16,15 +16,17 @@ export declare function homePatchPath(env?: NodeJS.ProcessEnv): string;
|
|
|
16
16
|
*
|
|
17
17
|
* @param allowEvaluate - register browser_evaluate
|
|
18
18
|
* @param allowCdp - register browser_cdp
|
|
19
|
+
* @param diagnostics - register the two diagnostics tools
|
|
19
20
|
* @returns the YAML block, markers included
|
|
20
21
|
*/
|
|
21
|
-
export declare function renderBrowserBlock(allowEvaluate: boolean, allowCdp: boolean): string;
|
|
22
|
+
export declare function renderBrowserBlock(allowEvaluate: boolean, allowCdp: boolean, diagnostics: boolean): string;
|
|
22
23
|
/** Merge the managed block into `original`, replacing an existing one in place. */
|
|
23
|
-
export declare function mergeBrowserBlock(original: string, allowEvaluate: boolean, allowCdp: boolean): string;
|
|
24
|
+
export declare function mergeBrowserBlock(original: string, allowEvaluate: boolean, allowCdp: boolean, diagnostics: boolean): string;
|
|
24
25
|
/** Read the current managed block, or undefined when there is none. */
|
|
25
26
|
export declare function parseBrowserBlock(text: string): {
|
|
26
27
|
allowEvaluate: boolean;
|
|
27
28
|
allowCdp: boolean;
|
|
29
|
+
diagnostics: boolean;
|
|
28
30
|
} | undefined;
|
|
29
31
|
/** Back-compat aliases used by the absorbed settings-card host half. */
|
|
30
32
|
export declare const parseGatesBlock: typeof parseBrowserBlock;
|
|
@@ -39,8 +41,9 @@ export declare const mergeGatesBlock: typeof mergeBrowserBlock;
|
|
|
39
41
|
* @param env - environment (DSH_HOME override for tests)
|
|
40
42
|
* @returns the file written and the values it now carries
|
|
41
43
|
*/
|
|
42
|
-
export declare function writeGates(allowEvaluate: boolean, allowCdp: boolean, env?: NodeJS.ProcessEnv): Promise<{
|
|
44
|
+
export declare function writeGates(allowEvaluate: boolean, allowCdp: boolean, diagnostics: boolean, env?: NodeJS.ProcessEnv): Promise<{
|
|
43
45
|
patch: string;
|
|
44
46
|
allowEvaluate: boolean;
|
|
45
47
|
allowCdp: boolean;
|
|
48
|
+
diagnostics: boolean;
|
|
46
49
|
}>;
|
package/lib/gates-patch.js
CHANGED
|
@@ -41,9 +41,10 @@ export function homePatchPath(env = process.env) {
|
|
|
41
41
|
*
|
|
42
42
|
* @param allowEvaluate - register browser_evaluate
|
|
43
43
|
* @param allowCdp - register browser_cdp
|
|
44
|
+
* @param diagnostics - register the two diagnostics tools
|
|
44
45
|
* @returns the YAML block, markers included
|
|
45
46
|
*/
|
|
46
|
-
export function renderBrowserBlock(allowEvaluate, allowCdp) {
|
|
47
|
+
export function renderBrowserBlock(allowEvaluate, allowCdp, diagnostics) {
|
|
47
48
|
return [
|
|
48
49
|
BEGIN,
|
|
49
50
|
'# allowEvaluate: 注册 browser_evaluate(页面任意 JS,能用你的登录态操作网站)',
|
|
@@ -72,6 +73,7 @@ export function renderBrowserBlock(allowEvaluate, allowCdp) {
|
|
|
72
73
|
' toolPrefix: "browser_"',
|
|
73
74
|
' allowEvaluate: ' + (allowEvaluate ? 'true' : 'false'),
|
|
74
75
|
' allowCdp: ' + (allowCdp ? 'true' : 'false'),
|
|
76
|
+
' diagnostics: ' + (diagnostics ? 'true' : 'false'),
|
|
75
77
|
' registerDisabledTools: false',
|
|
76
78
|
' diagnostics: false',
|
|
77
79
|
' maxWaitMs: 60000',
|
|
@@ -98,8 +100,8 @@ function findBlock(original) {
|
|
|
98
100
|
return undefined;
|
|
99
101
|
}
|
|
100
102
|
/** Merge the managed block into `original`, replacing an existing one in place. */
|
|
101
|
-
export function mergeBrowserBlock(original, allowEvaluate, allowCdp) {
|
|
102
|
-
const block = renderBrowserBlock(allowEvaluate, allowCdp);
|
|
103
|
+
export function mergeBrowserBlock(original, allowEvaluate, allowCdp, diagnostics) {
|
|
104
|
+
const block = renderBrowserBlock(allowEvaluate, allowCdp, diagnostics);
|
|
103
105
|
const found = findBlock(original);
|
|
104
106
|
if (found !== undefined)
|
|
105
107
|
return original.slice(0, found.start) + block + original.slice(found.after);
|
|
@@ -115,7 +117,12 @@ export function parseBrowserBlock(text) {
|
|
|
115
117
|
const cdp = /allowCdp:\s*(true|false)/.exec(body);
|
|
116
118
|
if (evaluate === null || cdp === null)
|
|
117
119
|
return undefined;
|
|
118
|
-
|
|
120
|
+
const diag = /diagnostics:\s*(true|false)/.exec(body);
|
|
121
|
+
return {
|
|
122
|
+
allowEvaluate: evaluate[1] === 'true',
|
|
123
|
+
allowCdp: cdp[1] === 'true',
|
|
124
|
+
diagnostics: diag === null ? false : diag[1] === 'true',
|
|
125
|
+
};
|
|
119
126
|
}
|
|
120
127
|
/** Back-compat aliases used by the absorbed settings-card host half. */
|
|
121
128
|
export const parseGatesBlock = parseBrowserBlock;
|
|
@@ -130,7 +137,7 @@ export const mergeGatesBlock = mergeBrowserBlock;
|
|
|
130
137
|
* @param env - environment (DSH_HOME override for tests)
|
|
131
138
|
* @returns the file written and the values it now carries
|
|
132
139
|
*/
|
|
133
|
-
export async function writeGates(allowEvaluate, allowCdp, env = process.env) {
|
|
140
|
+
export async function writeGates(allowEvaluate, allowCdp, diagnostics, env = process.env) {
|
|
134
141
|
const file = homePatchPath(env);
|
|
135
142
|
let original = '';
|
|
136
143
|
try {
|
|
@@ -139,7 +146,7 @@ export async function writeGates(allowEvaluate, allowCdp, env = process.env) {
|
|
|
139
146
|
catch {
|
|
140
147
|
original = '';
|
|
141
148
|
}
|
|
142
|
-
const next = mergeBrowserBlock(original, allowEvaluate, allowCdp);
|
|
149
|
+
const next = mergeBrowserBlock(original, allowEvaluate, allowCdp, diagnostics);
|
|
143
150
|
if (original !== '')
|
|
144
151
|
await copyFile(file, file + '.bak-browser-toggle').catch(() => { });
|
|
145
152
|
const tmp = file + '.tmp-browser-toggle';
|
|
@@ -147,12 +154,15 @@ export async function writeGates(allowEvaluate, allowCdp, env = process.env) {
|
|
|
147
154
|
await rename(tmp, file);
|
|
148
155
|
const check = await readFile(file, 'utf8');
|
|
149
156
|
const parsed = parseBrowserBlock(check);
|
|
150
|
-
if (parsed === undefined ||
|
|
157
|
+
if (parsed === undefined ||
|
|
158
|
+
parsed.allowEvaluate !== allowEvaluate ||
|
|
159
|
+
parsed.allowCdp !== allowCdp ||
|
|
160
|
+
parsed.diagnostics !== diagnostics) {
|
|
151
161
|
if (original !== '') {
|
|
152
162
|
await writeFile(file, original, 'utf8');
|
|
153
163
|
throw new Error('patch self-check failed after write; previous content restored');
|
|
154
164
|
}
|
|
155
165
|
throw new Error('patch self-check failed after write');
|
|
156
166
|
}
|
|
157
|
-
return { patch: file, allowEvaluate, allowCdp };
|
|
167
|
+
return { patch: file, allowEvaluate, allowCdp, diagnostics };
|
|
158
168
|
}
|
package/lib/runtime-state.d.ts
CHANGED
|
@@ -64,11 +64,13 @@ export declare function isEngineUserChosen(): boolean;
|
|
|
64
64
|
export declare function publishGates(gates: {
|
|
65
65
|
allowEvaluate: boolean;
|
|
66
66
|
allowCdp: boolean;
|
|
67
|
+
diagnostics: boolean;
|
|
67
68
|
}): void;
|
|
68
69
|
/** The published gates, or undefined while the tool family has not been applied. */
|
|
69
70
|
export declare function getGates(): {
|
|
70
71
|
allowEvaluate: boolean;
|
|
71
72
|
allowCdp: boolean;
|
|
73
|
+
diagnostics: boolean;
|
|
72
74
|
} | undefined;
|
|
73
75
|
/**
|
|
74
76
|
* The last computed "which sites look logged in" summary, or undefined before anything
|
package/lib/runtime-state.js
CHANGED
|
@@ -149,7 +149,11 @@ export function isEngineUserChosen() {
|
|
|
149
149
|
* @param gates - whether browser_evaluate / browser_cdp are registered.
|
|
150
150
|
*/
|
|
151
151
|
export function publishGates(gates) {
|
|
152
|
-
_gates = {
|
|
152
|
+
_gates = {
|
|
153
|
+
allowEvaluate: gates.allowEvaluate === true,
|
|
154
|
+
allowCdp: gates.allowCdp === true,
|
|
155
|
+
diagnostics: gates.diagnostics === true,
|
|
156
|
+
};
|
|
153
157
|
}
|
|
154
158
|
/** The published gates, or undefined while the tool family has not been applied. */
|
|
155
159
|
export function getGates() {
|
package/lib/settings-card.js
CHANGED
|
@@ -135,6 +135,7 @@ export function apply(ctx) {
|
|
|
135
135
|
// provider build in this profile is too old to report them.
|
|
136
136
|
allowEvaluate: gates ? gates.allowEvaluate : null,
|
|
137
137
|
allowCdp: gates ? gates.allowCdp : null,
|
|
138
|
+
diagnostics: gates ? gates.diagnostics : null,
|
|
138
139
|
// What the user's own layer says right now. Differs from the above
|
|
139
140
|
// between a write and the live reload that applies it.
|
|
140
141
|
patchGates: managedBlockGates() ?? null,
|
|
@@ -209,6 +210,8 @@ export function apply(ctx) {
|
|
|
209
210
|
catch {
|
|
210
211
|
return sendJson(res, 400, { error: { code: 'INVALID_JSON', message: 'body must be JSON' } });
|
|
211
212
|
}
|
|
213
|
+
if (typeof payload.diagnostics !== 'boolean')
|
|
214
|
+
payload.diagnostics = false;
|
|
212
215
|
if (typeof payload.allowEvaluate !== 'boolean' || typeof payload.allowCdp !== 'boolean')
|
|
213
216
|
return sendJson(res, 400, {
|
|
214
217
|
error: {
|
|
@@ -217,16 +220,20 @@ export function apply(ctx) {
|
|
|
217
220
|
},
|
|
218
221
|
});
|
|
219
222
|
const before = readGates();
|
|
220
|
-
if (before &&
|
|
223
|
+
if (before &&
|
|
224
|
+
before.allowEvaluate === payload.allowEvaluate &&
|
|
225
|
+
before.allowCdp === payload.allowCdp &&
|
|
226
|
+
before.diagnostics === payload.diagnostics)
|
|
221
227
|
return sendJson(res, 200, { ...before, changed: false, patch: null });
|
|
222
228
|
try {
|
|
223
|
-
const written = await writeGates(payload.allowEvaluate, payload.allowCdp);
|
|
229
|
+
const written = await writeGates(payload.allowEvaluate, payload.allowCdp, payload.diagnostics);
|
|
224
230
|
// The effective values only move once the loader re-applies the tree
|
|
225
231
|
// (profile patchReload: live); report what was written, and let the
|
|
226
232
|
// panel's next /state poll show whether it took effect.
|
|
227
233
|
sendJson(res, 200, {
|
|
228
234
|
allowEvaluate: written.allowEvaluate,
|
|
229
235
|
allowCdp: written.allowCdp,
|
|
236
|
+
diagnostics: written.diagnostics,
|
|
230
237
|
changed: true,
|
|
231
238
|
patch: written.patch,
|
|
232
239
|
});
|
package/lib/tool.js
CHANGED
|
@@ -60,7 +60,11 @@ export function apply(ctx, config) {
|
|
|
60
60
|
// home-level patch), so the tool family publishes it at registration and the
|
|
61
61
|
// switch shows the truth instead of a guess. Driven by the same values that
|
|
62
62
|
// decided which tools register, so the two can never disagree.
|
|
63
|
-
publishGates({
|
|
63
|
+
publishGates({
|
|
64
|
+
allowEvaluate: registered.has('evaluate'),
|
|
65
|
+
allowCdp: registered.has('cdp'),
|
|
66
|
+
diagnostics: registered.has('console_messages'),
|
|
67
|
+
});
|
|
64
68
|
/** Register one gated tool only when this configuration keeps it live. */
|
|
65
69
|
const registerMaybe = (suffix, definition) => {
|
|
66
70
|
if (registered.has(suffix))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-browser-playwright-codex",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Browser automation for DeepSeek Harness, one plugin: fill in forms, scrape lists and work across tabs in real pages, using the logins you already have. Runs in a visible window that stays quiet when minimised; native dialogs are parked instead of confirmed, CAPTCHA pages are flagged with a note that says stop and hand the page back, password / one-time-code / card fields are refused by browser_fill, and the Settings card (enable/disable, page-reading mode, capability gates) now lives inside this package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|