@angelky7/dsh-browser-control 1.7.6 → 1.7.7

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
@@ -240,7 +240,7 @@ curl -X POST http://127.0.0.1:9777/api/command \
240
240
  |---|---|
241
241
  | `browser_navigate` | 导航到 URL |
242
242
  | `browser_read` | 读取页面文本/HTML,正文直接给模型(`offset`/`limit` 窗口,长文分页) |
243
- | `browser_snapshot` | 页面快照 → 逐条列出 ref 的交互树(`mode:"ax"` 走 Chrome 无障碍树,拿 role + 可访问名) |
243
+ | `browser_snapshot` | 页面快照 → 逐条列出 ref 的交互树(`mode:"ax"` 走 Chrome 无障碍树,拿 role + 可访问名);v1.7.7 起支持 `offset` **分页**:页头写明「第几到第几 / 页面总数 / 下一页 offset」,超出扩展 200 条窗口的部分如实说明用 find/read 补齐 |
244
244
  | `browser_click` | 点击元素(by ref / selector) |
245
245
  | `browser_type` | 在输入框填入文本 |
246
246
  | `browser_press` | 模拟键盘按键(回车、Tab、方向键,以及 "Ctrl+A" / "Shift+ArrowRight" 这类组合键;一次按键只插入一次) |
package/lib/index.js CHANGED
@@ -2032,9 +2032,13 @@ function applyBrowserTools(ctx, controller) {
2032
2032
  type: "number",
2033
2033
  description: "Target tab; defaults to the active tab."
2034
2034
  },
2035
+ offset: {
2036
+ type: "number",
2037
+ description: "Skip this many items into the inventory (paging); default 0. Refs are valid for THIS call only — take the next page in the same breath if you need stable refs."
2038
+ },
2035
2039
  limit: {
2036
2040
  type: "number",
2037
- description: "Max elements returned; defaults to 120, capped at 200."
2041
+ description: "Page size: max items returned by this call; defaults to 120. The extension supplies up to a 200-item window per call."
2038
2042
  },
2039
2043
  mode: {
2040
2044
  type: "string",
@@ -2060,6 +2064,17 @@ function applyBrowserTools(ctx, controller) {
2060
2064
  },
2061
2065
  mode: { type: "string" },
2062
2066
  axFallback: { type: "string" },
2067
+ offset: {
2068
+ type: "number",
2069
+ required: true
2070
+ },
2071
+ total: { type: "number" },
2072
+ returned: { type: "number" },
2073
+ nextOffset: { type: "number" },
2074
+ hasMore: {
2075
+ type: "boolean",
2076
+ required: true
2077
+ },
2063
2078
  items: {
2064
2079
  type: "array",
2065
2080
  required: true,
@@ -2084,9 +2099,13 @@ function applyBrowserTools(ctx, controller) {
2084
2099
  }
2085
2100
  },
2086
2101
  render: (_args, value) => {
2102
+ const shown = value.items.length;
2103
+ const start = value.offset + 1;
2104
+ const end = value.offset + shown;
2105
+ const totalText = value.total === void 0 ? `${value.returned ?? shown} available` : `${value.total} on the page`;
2087
2106
  return [{
2088
2107
  type: "text",
2089
- text: clampRendered([`${value.items.length} interactive elements on ${value.title} (mode ${value.mode ?? "dom"}); click or fill them by ref:` + (value.axFallback === void 0 ? "" : `\n(mode ax fell back to dom: ${value.axFallback})`), ...value.items.map((item) => {
2108
+ text: clampRendered([`${shown === 0 ? `no items at offset ${value.offset}` : `items ${start}–${end} of ${totalText}`}${value.total !== void 0 && value.returned !== void 0 && value.total > value.returned ? ` (this extension returns at most ${value.returned}-item windows — use browser_find/browser_read to reach the rest)` : ""}${value.nextOffset === void 0 ? "" : `; next page: offset ${value.nextOffset}`} (mode ${value.mode ?? "dom"}); click or fill them by ref:` + (value.axFallback === void 0 ? "" : `\n(mode ax fell back to dom: ${value.axFallback})`), ...value.items.map((item) => {
2090
2109
  const name = item.name === void 0 ? "" : ` "${flattenOneLine(item.name, 80)}"`;
2091
2110
  const href = item.href === void 0 ? "" : ` -> ${flattenOneLine(item.href, 100)}`;
2092
2111
  return `[${item.ref}] ${item.role ?? item.tag}${name}${href}`;
@@ -2100,20 +2119,33 @@ function applyBrowserTools(ctx, controller) {
2100
2119
  kind: "other"
2101
2120
  }),
2102
2121
  async execute(args, exec) {
2103
- const params = { limit: Math.min(200, Math.max(1, args.limit ?? 120)) };
2122
+ const offset = Math.max(0, Math.trunc(typeof args.offset === "number" && Number.isFinite(args.offset) ? args.offset : 0));
2123
+ const limit = Math.min(200, Math.max(1, typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.trunc(args.limit) : 120));
2124
+ const params = { limit: 200 };
2104
2125
  if (typeof args.tabId === "number") params.tabId = args.tabId;
2105
2126
  if (typeof args.mode === "string") params.mode = args.mode;
2106
2127
  const raw = await controller.execute("snapshot", params, exec.signal);
2107
2128
  const requestedAx = args.mode === "ax";
2108
2129
  const gotAx = raw.mode === "ax" || raw.ax !== void 0;
2109
2130
  const axFallback = typeof raw.axFallback === "string" && raw.axFallback.length > 0 ? raw.axFallback : requestedAx && !gotAx ? "this extension ignored mode:\"ax\" and returned CSS-DOM matches (AX mode needs DSH Browser Control extension v1.0.11 or newer) — the names below are tag names, not accessible names" : void 0;
2131
+ const window = Array.isArray(raw.items) ? raw.items : [];
2132
+ const returned = window.length;
2133
+ const reportedTotal = typeof raw.total === "number" && Number.isFinite(raw.total) ? Math.trunc(raw.total) : void 0;
2134
+ const total = reportedTotal === void 0 ? void 0 : Math.max(returned, reportedTotal);
2135
+ const page = window.slice(offset, offset + limit);
2136
+ const hasMore = offset + page.length < returned;
2110
2137
  return {
2111
2138
  tabId: raw.tabId,
2112
2139
  title: raw.title ?? "",
2113
2140
  url: raw.url ?? "",
2114
2141
  ...raw.mode === void 0 ? {} : { mode: raw.mode },
2115
2142
  ...axFallback === void 0 ? {} : { axFallback },
2116
- items: (raw.items ?? []).map((item) => ({
2143
+ offset,
2144
+ ...total === void 0 ? {} : { total },
2145
+ returned,
2146
+ hasMore,
2147
+ ...hasMore ? { nextOffset: offset + page.length } : {},
2148
+ items: page.map((item) => ({
2117
2149
  ref: item.ref,
2118
2150
  tag: item.tag,
2119
2151
  ...item.role === void 0 ? {} : { role: item.role },
@@ -809,7 +809,8 @@ function applyBrowserTools(ctx, controller) {
809
809
  description: 'Inventory the page\'s interactive elements with stable refs; pass a ref to browser_click/browser_type afterwards. mode:"ax" numbers them by Chrome\'s accessibility tree (role + accessible name), which survives a DOM redesign; the default mode matches CSS selectors.',
810
810
  parameters: {
811
811
  tabId: { type: 'number', description: 'Target tab; defaults to the active tab.' },
812
- limit: { type: 'number', description: 'Max elements returned; defaults to 120, capped at 200.' },
812
+ offset: { type: 'number', description: 'Skip this many items into the inventory (paging); default 0. Refs are valid for THIS call only — take the next page in the same breath if you need stable refs.' },
813
+ limit: { type: 'number', description: 'Page size: max items returned by this call; defaults to 120. The extension supplies up to a 200-item window per call.' },
813
814
  mode: { type: 'string', description: '"ax" for the accessibility tree, "dom" (default) for the selector list. "ax" falls back to "dom" with axFallback set if the page gives no a11y tree.' },
814
815
  },
815
816
  output: {
@@ -822,6 +823,11 @@ function applyBrowserTools(ctx, controller) {
822
823
  url: { type: 'string', required: true },
823
824
  mode: { type: 'string' },
824
825
  axFallback: { type: 'string' },
826
+ offset: { type: 'number', required: true },
827
+ total: { type: 'number' },
828
+ returned: { type: 'number' },
829
+ nextOffset: { type: 'number' },
830
+ hasMore: { type: 'boolean', required: true },
825
831
  items: {
826
832
  type: 'array',
827
833
  required: true,
@@ -840,7 +846,16 @@ function applyBrowserTools(ctx, controller) {
840
846
  },
841
847
  },
842
848
  render: (_args, value) => {
843
- const head = `${value.items.length} interactive elements on ${value.title}`
849
+ const shown = value.items.length;
850
+ const start = value.offset + 1;
851
+ const end = value.offset + shown;
852
+ const totalText = value.total === undefined ? `${value.returned ?? shown} available` : `${value.total} on the page`;
853
+ const range = shown === 0 ? `no items at offset ${value.offset}` : `items ${start}–${end} of ${totalText}`;
854
+ const capped = value.total !== undefined && value.returned !== undefined && value.total > value.returned
855
+ ? ` (this extension returns at most ${value.returned}-item windows — use browser_find/browser_read to reach the rest)`
856
+ : '';
857
+ const next = value.nextOffset === undefined ? '' : `; next page: offset ${value.nextOffset}`;
858
+ const head = `${range}${capped}${next}`
844
859
  + ` (mode ${value.mode ?? 'dom'}); click or fill them by ref:`
845
860
  + (value.axFallback === undefined ? '' : `\n(mode ax fell back to dom: ${value.axFallback})`);
846
861
  const lines = value.items.map(item => {
@@ -853,8 +868,12 @@ function applyBrowserTools(ctx, controller) {
853
868
  },
854
869
  presentCall: () => ({ card: 'generic', title: 'Snapshot browser page', kind: 'other' }),
855
870
  async execute(args, exec) {
856
- const limit = Math.min(200, Math.max(1, args.limit ?? 120));
857
- const params = { limit };
871
+ const offset = Math.max(0, Math.trunc(typeof args.offset === 'number' && Number.isFinite(args.offset) ? args.offset : 0));
872
+ const limit = Math.min(200, Math.max(1, typeof args.limit === 'number' && Number.isFinite(args.limit) ? Math.trunc(args.limit) : 120));
873
+ // Ask for the largest window the extension offers (200) and page in the
874
+ // plugin: older surfaces never learned an offset parameter, and they do
875
+ // report `total`, so paging stays plugin-side (v1.7.7).
876
+ const params = { limit: 200 };
858
877
  if (typeof args.tabId === 'number')
859
878
  params.tabId = args.tabId;
860
879
  if (typeof args.mode === 'string')
@@ -872,13 +891,24 @@ function applyBrowserTools(ctx, controller) {
872
891
  : (requestedAx && !gotAx
873
892
  ? 'this extension ignored mode:"ax" and returned CSS-DOM matches (AX mode needs DSH Browser Control extension v1.0.11 or newer) — the names below are tag names, not accessible names'
874
893
  : undefined);
875
- return {
894
+ const window = Array.isArray(raw.items) ? raw.items : [];
895
+ const returned = window.length;
896
+ const reportedTotal = typeof raw.total === 'number' && Number.isFinite(raw.total) ? Math.trunc(raw.total) : undefined;
897
+ const total = reportedTotal === undefined ? undefined : Math.max(returned, reportedTotal);
898
+ const page = window.slice(offset, offset + limit);
899
+ const hasMore = offset + page.length < returned;
900
+ const receipt = {
876
901
  tabId: raw.tabId,
877
902
  title: raw.title ?? '',
878
903
  url: raw.url ?? '',
879
904
  ...(raw.mode === undefined ? {} : { mode: raw.mode }),
880
905
  ...(axFallback === undefined ? {} : { axFallback }),
881
- items: (raw.items ?? []).map(item => ({
906
+ offset,
907
+ ...(total === undefined ? {} : { total }),
908
+ returned,
909
+ hasMore,
910
+ ...(hasMore ? { nextOffset: offset + page.length } : {}),
911
+ items: page.map(item => ({
882
912
  ref: item.ref,
883
913
  tag: item.tag,
884
914
  ...(item.role === undefined ? {} : { role: item.role }),
@@ -886,6 +916,7 @@ function applyBrowserTools(ctx, controller) {
886
916
  ...(item.href === undefined ? {} : { href: item.href }),
887
917
  })),
888
918
  };
919
+ return receipt;
889
920
  },
890
921
  }));
891
922
  ctx.tools.register(defineTool({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@angelky7/dsh-browser-control",
3
3
  "description": "Local WebSocket bridge between dsh and the DSH Browser Control browser extension, with model-facing browser_* tools and a Settings-managed on/off switch",
4
- "version": "1.7.6",
4
+ "version": "1.7.7",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
@@ -37,7 +37,7 @@
37
37
  "build": "tsc -p tsconfig.json && tsdown",
38
38
  "typecheck": "tsc -p tsconfig.json --noEmit",
39
39
  "check": "npm run typecheck && npm run build && publint",
40
- "verify": "node verify/verify_config_patch.mjs && node verify/verify_peers.mjs && node verify/verify_surface.mjs && node verify/verify_tab_groups.mjs && node verify/verify_tabs_close_many.mjs && node verify/verify_cdp_retry.mjs && node verify/verify_snapshot_ax.mjs && node verify/verify_hard_lock.mjs && node verify/verify_policy.mjs && node verify/verify_cookies.mjs && node verify/verify_extension_e2e.mjs && node verify/verify_plugin.mjs legacy && node verify/verify_plugin.mjs conflict && node verify/verify_plugin.mjs new && node verify/verify_http.mjs && node verify/verify_errors.mjs && node verify/verify_hold.mjs && node verify/verify_protocol.mjs && node verify/verify_bundle_http.mjs && node verify/verify_nav_report.mjs && node verify/verify_nav_landing.mjs && node verify/verify_render_contract.mjs && node verify/verify_press_events.mjs && node verify/verify_nav_timeout.mjs && node verify/verify_nav_waituntil.mjs && node verify/verify_archive.mjs && node verify/verify_windows.mjs && node verify/verify_history_nav.mjs && node verify/verify_metrics_throttle.mjs && node verify/verify_drag.mjs && node verify/verify_audit.mjs && node verify/verify_journal.mjs && node verify/verify_sessions.mjs && node verify/verify_compat_matrix.mjs"
40
+ "verify": "node verify/verify_config_patch.mjs && node verify/verify_peers.mjs && node verify/verify_surface.mjs && node verify/verify_tab_groups.mjs && node verify/verify_tabs_close_many.mjs && node verify/verify_cdp_retry.mjs && node verify/verify_snapshot_ax.mjs && node verify/verify_hard_lock.mjs && node verify/verify_policy.mjs && node verify/verify_cookies.mjs && node verify/verify_extension_e2e.mjs && node verify/verify_plugin.mjs legacy && node verify/verify_plugin.mjs conflict && node verify/verify_plugin.mjs new && node verify/verify_http.mjs && node verify/verify_errors.mjs && node verify/verify_hold.mjs && node verify/verify_protocol.mjs && node verify/verify_bundle_http.mjs && node verify/verify_nav_report.mjs && node verify/verify_nav_landing.mjs && node verify/verify_render_contract.mjs && node verify/verify_press_events.mjs && node verify/verify_nav_timeout.mjs && node verify/verify_nav_waituntil.mjs && node verify/verify_archive.mjs && node verify/verify_windows.mjs && node verify/verify_history_nav.mjs && node verify/verify_metrics_throttle.mjs && node verify/verify_drag.mjs && node verify/verify_audit.mjs && node verify/verify_journal.mjs && node verify/verify_sessions.mjs && node verify/verify_snapshot_page.mjs && node verify/verify_compat_matrix.mjs"
41
41
  },
42
42
  "publishConfig": {
43
43
  "access": "public",