@jobshimo/browser-link 0.36.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/agent-instructions/content.js +5 -349
  2. package/dist/agent-instructions/content.js.map +1 -1
  3. package/dist/bridge/server.d.ts +5 -0
  4. package/dist/bridge/server.js +1 -1
  5. package/dist/bridge/server.js.map +1 -1
  6. package/dist/cdp/flow.d.ts +44 -3
  7. package/dist/cdp/flow.js +65 -19
  8. package/dist/cdp/flow.js.map +1 -1
  9. package/dist/cdp/inpage/builders.d.ts +110 -24
  10. package/dist/cdp/inpage/builders.js +203 -53
  11. package/dist/cdp/inpage/builders.js.map +1 -1
  12. package/dist/cdp/inpage/deep-query.d.ts +1 -1
  13. package/dist/cdp/inpage/deep-query.js +12 -9
  14. package/dist/cdp/inpage/deep-query.js.map +1 -1
  15. package/dist/cdp/inpage/observer.d.ts +36 -7
  16. package/dist/cdp/inpage/observer.js +242 -16
  17. package/dist/cdp/inpage/observer.js.map +1 -1
  18. package/dist/cdp/transport.js +96 -9
  19. package/dist/cdp/transport.js.map +1 -1
  20. package/dist/commands/about.js +4 -2
  21. package/dist/commands/about.js.map +1 -1
  22. package/dist/extension/background.js +97 -13
  23. package/dist/extension/background.js.map +1 -1
  24. package/dist/extension/flow.d.ts +44 -3
  25. package/dist/extension/flow.js +65 -19
  26. package/dist/extension/flow.js.map +1 -1
  27. package/dist/extension/inpage/builders.d.ts +110 -24
  28. package/dist/extension/inpage/builders.js +203 -53
  29. package/dist/extension/inpage/builders.js.map +1 -1
  30. package/dist/extension/inpage/deep-query.d.ts +1 -1
  31. package/dist/extension/inpage/deep-query.js +12 -9
  32. package/dist/extension/inpage/deep-query.js.map +1 -1
  33. package/dist/extension/inpage/observer.d.ts +36 -7
  34. package/dist/extension/inpage/observer.js +242 -16
  35. package/dist/extension/inpage/observer.js.map +1 -1
  36. package/dist/extension/manifest.json +1 -1
  37. package/dist/manual/core.d.ts +20 -0
  38. package/dist/manual/core.js +71 -0
  39. package/dist/manual/core.js.map +1 -0
  40. package/dist/manual/render.d.ts +22 -0
  41. package/dist/manual/render.js +56 -0
  42. package/dist/manual/render.js.map +1 -0
  43. package/dist/manual/status.d.ts +23 -0
  44. package/dist/manual/status.js +52 -0
  45. package/dist/manual/status.js.map +1 -0
  46. package/dist/manual/tool.d.ts +20 -0
  47. package/dist/manual/tool.js +106 -0
  48. package/dist/manual/tool.js.map +1 -0
  49. package/dist/manual/topics.d.ts +18 -0
  50. package/dist/manual/topics.js +60 -0
  51. package/dist/manual/topics.js.map +1 -0
  52. package/dist/map/queries.d.ts +2 -0
  53. package/dist/map/queries.js +23 -1
  54. package/dist/map/queries.js.map +1 -1
  55. package/dist/map/tools.js +26 -4
  56. package/dist/map/tools.js.map +1 -1
  57. package/dist/permissions.js +6 -0
  58. package/dist/permissions.js.map +1 -1
  59. package/dist/server.js +11 -14
  60. package/dist/server.js.map +1 -1
  61. package/dist/tools/browser-definitions.js +113 -23
  62. package/dist/tools/browser-definitions.js.map +1 -1
  63. package/dist/tools/browser-dispatch.d.ts +24 -26
  64. package/dist/tools/browser-dispatch.js +169 -14
  65. package/dist/tools/browser-dispatch.js.map +1 -1
  66. package/dist/tools/server-instructions.d.ts +24 -13
  67. package/dist/tools/server-instructions.js +25 -303
  68. package/dist/tools/server-instructions.js.map +1 -1
  69. package/package.json +1 -1
@@ -22,7 +22,7 @@
22
22
  * `cdp/builders-parity.test.ts` checks the built output.
23
23
  */
24
24
  /** Bump whenever the in-page protocol (global shape, messages, methods) changes. */
25
- export const OBSERVER_VERSION = 2;
25
+ export const OBSERVER_VERSION = 3;
26
26
  /** Name of the `Runtime.addBinding` function the observer pushes messages through. */
27
27
  export const OBSERVER_BINDING = '__blPageObserverPush';
28
28
  /** Well-known global that holds the installed observer instance. */
@@ -43,10 +43,30 @@ function clampInt(value, fallback, min, max) {
43
43
  * `{ ok: true, installed, version, doc_id, coverage }`.
44
44
  *
45
45
  * The instance also owns element identity (spec v4 §5.6): `refFor(el)` issues
46
- * `e<N>` (monotonic per document instance, from `e1`) the first time an
47
- * element is seen and reuses it while the node lives; `resolveRef(ref)`
48
- * returns the element while it is connected, else `null`. Refs are only valid
49
- * for the `doc_id` that issued them. Neither method throws. `nextSnapshotId()`
46
+ * `<doc_tag>:e<N>` (`doc_tag` = the first 6 characters of `doc_id`, N
47
+ * monotonic per document instance, from 1) the first time an element is seen
48
+ * and reuses it while the node lives; `resolveRef(ref)` returns the element
49
+ * while it is connected, else `null`. Refs are only valid for the document
50
+ * instance that issued them: a ref whose tag is not this instance's tag, or a
51
+ * bare `e<N>` with no tag, is `null` everywhere (resolve, re-resolve, key)
52
+ * without consulting the registries. Neither method throws.
53
+ *
54
+ * Secondary identity (spec v4 §5.6, decided by lab 10): `semanticKeyFor(el)`
55
+ * returns `{ role, name, path }` — role from the `role` attribute or a small
56
+ * implicit table, accessible-ish name (aria-label, aria-labelledby, associated
57
+ * label, text ≤ 80 chars, placeholder, title, button-like input value) and up
58
+ * to 8 `{ tag, index }` ancestor steps (1-based same-tag sibling index, a
59
+ * shadow boundary as `{ tag: '#shadow', index: 0 }`). `refFor` stores the key
60
+ * when it issues a ref. `resolveRefDetailed(ref)` returns `{ el, reresolved }`:
61
+ * the live node with `reresolved: false`, else the element with the same role,
62
+ * name and tag-only path when exactly one exists, rebinding the ref to it
63
+ * (`reresolved: true`); zero or several candidates give `null` (sibling
64
+ * indices are kept in the key as data but never pick among candidates). Candidates come from the document, every open shadow root reachable
65
+ * from it, and every root the `attachShadow` patch observed (closed ones
66
+ * included); closed roots attached before install are not reachable.
67
+ * `resolveRef(ref)` is `resolveRefDetailed(ref)?.el ?? null`. `keyForRef(ref)`
68
+ * returns a copy of the stored key or `null`. None of these throws.
69
+ * `nextSnapshotId()`
50
70
  * issues `<doc_id>:s<N>` (N monotonic per document instance, from 1) for each
51
71
  * `snapshot` result and returns `null` once the instance is torn down; the
52
72
  * `doc_id` prefix lets a stale id from another document be rejected.
@@ -97,7 +117,14 @@ export function buildObserverInstallJs(opts = {}) {
97
117
  const observed = new WeakSet();
98
118
  let refs = new WeakMap();
99
119
  const refNodes = new Map();
120
+ // Semantic key captured when each ref is issued; kept while the ref lives so
121
+ // a replaced node can be found again (spec v4 §5.6 secondary identity).
122
+ const refKeys = new Map();
100
123
  let refSeq = 0;
124
+ // Every ref carries the issuing document's tag, so a ref from another
125
+ // document instance (or a bare e<N>) never reaches the registries.
126
+ const refPrefix = g.doc_id.slice(0, 6) + ':e';
127
+ const ownsRef = (ref) => typeof ref === 'string' && ref.startsWith(refPrefix);
101
128
  let snapshotSeq = 0;
102
129
  // Last SNAPSHOT_KEEP snapshot records (opaque to the observer; the snapshot
103
130
  // builder owns their shape), keyed by snapshot_id in insertion order.
@@ -225,7 +252,7 @@ export function buildObserverInstallJs(opts = {}) {
225
252
  try {
226
253
  const mo = new MutationObserver(onRecords(kind));
227
254
  mo.observe(root, { subtree: true, childList: true, attributes: true, characterData: true });
228
- observers.push({ mo: mo, kind: kind });
255
+ observers.push({ mo: mo, kind: kind, root: root });
229
256
  g.roots++;
230
257
  } catch (e) {}
231
258
  }
@@ -293,13 +320,179 @@ export function buildObserverInstallJs(opts = {}) {
293
320
 
294
321
  g.flush = flush;
295
322
 
323
+ const KEY_PATH_STEPS = 8;
324
+ const KEY_TEXT_CAP = 80;
325
+ // Elements an HTML <label> can be associated with.
326
+ const LABELABLE = ['button', 'input', 'meter', 'output', 'progress', 'select', 'textarea'];
327
+ const BUTTON_INPUT_TYPES = ['button', 'submit', 'reset'];
328
+
329
+ function tagOf(el) {
330
+ return el.localName || String(el.nodeName).toLowerCase();
331
+ }
332
+
333
+ function collapse(text) {
334
+ return String(text || '').replace(/\\s+/g, ' ').trim();
335
+ }
336
+
337
+ function inputType(el) {
338
+ return (el.getAttribute('type') || 'text').toLowerCase();
339
+ }
340
+
341
+ function roleOf(el) {
342
+ const explicit = (el.getAttribute('role') || '').trim();
343
+ if (explicit) return explicit;
344
+ const tag = tagOf(el);
345
+ if (tag === 'a' && el.hasAttribute('href')) return 'link';
346
+ if (tag === 'button' || tag === 'summary') return 'button';
347
+ if (tag === 'input') {
348
+ const type = inputType(el);
349
+ if (type === 'checkbox' || type === 'radio') return type;
350
+ if (BUTTON_INPUT_TYPES.indexOf(type) >= 0) return 'button';
351
+ return 'textbox';
352
+ }
353
+ if (tag === 'textarea') return 'textbox';
354
+ if (tag === 'select') return 'combobox';
355
+ if (el.hasAttribute('contenteditable') && el.getAttribute('contenteditable') !== 'false') return 'textbox';
356
+ return tag;
357
+ }
358
+
359
+ function labelTextOf(el, root) {
360
+ if (LABELABLE.indexOf(tagOf(el)) < 0) return '';
361
+ const id = el.getAttribute('id');
362
+ if (id && root && typeof root.querySelectorAll === 'function') {
363
+ for (const label of root.querySelectorAll('label')) {
364
+ if (label.getAttribute('for') !== id) continue;
365
+ const text = collapse(label.textContent);
366
+ if (text) return text;
367
+ }
368
+ }
369
+ const wrapping = typeof el.closest === 'function' ? el.closest('label') : null;
370
+ return wrapping ? collapse(wrapping.textContent) : '';
371
+ }
372
+
373
+ function nameOf(el) {
374
+ const ariaLabel = collapse(el.getAttribute('aria-label'));
375
+ if (ariaLabel) return ariaLabel;
376
+ const root = typeof el.getRootNode === 'function' ? el.getRootNode() : document;
377
+ const labelledBy = collapse(el.getAttribute('aria-labelledby'));
378
+ if (labelledBy && root && typeof root.getElementById === 'function') {
379
+ const parts = [];
380
+ for (const id of labelledBy.split(' ')) {
381
+ const target = root.getElementById(id);
382
+ const text = target ? collapse(target.textContent) : '';
383
+ if (text) parts.push(text);
384
+ }
385
+ if (parts.length > 0) return parts.join(' ');
386
+ }
387
+ const label = labelTextOf(el, root);
388
+ if (label) return label;
389
+ const text = collapse(el.textContent).slice(0, KEY_TEXT_CAP);
390
+ if (text) return text;
391
+ const placeholder = collapse(el.getAttribute('placeholder'));
392
+ if (placeholder) return placeholder;
393
+ const title = collapse(el.getAttribute('title'));
394
+ if (title) return title;
395
+ if (tagOf(el) === 'input' && BUTTON_INPUT_TYPES.indexOf(inputType(el)) >= 0) return collapse(el.value);
396
+ return '';
397
+ }
398
+
399
+ function sameTagIndex(el) {
400
+ const tag = el.localName;
401
+ let index = 1;
402
+ for (let s = el.previousElementSibling; s; s = s.previousElementSibling) {
403
+ if (s.localName === tag) index++;
404
+ }
405
+ return index;
406
+ }
407
+
408
+ // Unlike pathOf (descriptors, pinned settle output), this path carries the
409
+ // same-tag sibling index: role + name alone collides (spec v4 §4.6).
410
+ function keyPathOf(el) {
411
+ const steps = [];
412
+ let n = el;
413
+ while (n && steps.length < KEY_PATH_STEPS) {
414
+ if (n.nodeType === 1) {
415
+ if (n === document.documentElement) break;
416
+ steps.unshift({ tag: tagOf(n), index: sameTagIndex(n) });
417
+ n = n.parentNode;
418
+ } else if (n.nodeType === 11 && n.host) {
419
+ steps.unshift({ tag: '#shadow', index: 0 });
420
+ n = n.host;
421
+ } else {
422
+ break;
423
+ }
424
+ }
425
+ return steps;
426
+ }
427
+
428
+ function semanticKeyFor(el) {
429
+ try {
430
+ if (!el || el.nodeType !== 1) return null;
431
+ return { role: roleOf(el), name: nameOf(el), path: keyPathOf(el) };
432
+ } catch (e) {
433
+ return null;
434
+ }
435
+ }
436
+
437
+ // The document, every open shadow root reachable from it, and every root the
438
+ // attachShadow patch observed (closed ones included) whose host is connected.
439
+ function searchRoots() {
440
+ const roots = [document];
441
+ const seen = new Set(roots);
442
+ const add = (root) => {
443
+ if (!root || seen.has(root)) return;
444
+ if (root.nodeType === 11 && !(root.host && root.host.isConnected)) return;
445
+ seen.add(root);
446
+ roots.push(root);
447
+ };
448
+ for (const o of observers) add(o.root);
449
+ for (let i = 0; i < roots.length; i++) {
450
+ let all;
451
+ try { all = roots[i].querySelectorAll('*'); } catch (e) { continue; }
452
+ for (const el of all) {
453
+ if (el.shadowRoot) add(el.shadowRoot);
454
+ }
455
+ }
456
+ return roots;
457
+ }
458
+
459
+ // Same role and name and an identical tag-only path, and exactly one such
460
+ // element: identical rows are refused, never guessed, so a ref never jumps
461
+ // across rows. Sibling indices stay in the key but select nothing.
462
+ function findByKey(key) {
463
+ const last = key.path[key.path.length - 1];
464
+ if (!last) return null;
465
+ let found = null;
466
+ for (const root of searchRoots()) {
467
+ let all;
468
+ try { all = root.querySelectorAll('*'); } catch (e) { continue; }
469
+ for (const el of all) {
470
+ if (tagOf(el) !== last.tag || roleOf(el) !== key.role || nameOf(el) !== key.name) continue;
471
+ const path = keyPathOf(el);
472
+ if (path.length !== key.path.length) continue;
473
+ let same = true;
474
+ for (let i = 0; i < path.length && same; i++) {
475
+ if (path[i].tag !== key.path[i].tag) same = false;
476
+ }
477
+ if (!same) continue;
478
+ if (found) return null;
479
+ found = el;
480
+ }
481
+ }
482
+ return found;
483
+ }
484
+
485
+ g.semanticKeyFor = semanticKeyFor;
486
+
296
487
  g.refFor = function (el) {
297
488
  try {
298
489
  if (g.dead || !el || el.nodeType !== 1) return null;
299
490
  let ref = refs.get(el);
300
491
  if (!ref) {
301
- ref = 'e' + ++refSeq;
492
+ ref = refPrefix + ++refSeq;
302
493
  refs.set(el, ref);
494
+ const key = semanticKeyFor(el);
495
+ if (key) refKeys.set(ref, key);
303
496
  }
304
497
  // Same node, same ref: re-register one pruned while it was detached.
305
498
  if (!refNodes.has(ref)) refNodes.set(ref, holdWeakly(el));
@@ -309,20 +502,40 @@ export function buildObserverInstallJs(opts = {}) {
309
502
  }
310
503
  };
311
504
 
312
- g.resolveRef = function (ref) {
505
+ g.resolveRefDetailed = function (ref) {
313
506
  try {
314
- if (typeof ref !== 'string') return null;
507
+ if (g.dead || !ownsRef(ref)) return null;
315
508
  const held = refNodes.get(ref);
316
- if (!held) return null;
317
- const el = held.deref();
318
- if (el && el.isConnected) return el;
509
+ const old = held ? held.deref() : null;
510
+ if (old && old.isConnected) return { el: old, reresolved: false };
319
511
  refNodes.delete(ref);
320
- return null;
512
+ const key = refKeys.get(ref);
513
+ if (!key) return null;
514
+ const el = findByKey(key);
515
+ if (!el) return null;
516
+ if (old && refs.get(old) === ref) refs.delete(old);
517
+ // A node that already has its own ref keeps it for refFor; this ref
518
+ // still resolves to it through refNodes.
519
+ if (!refs.has(el)) refs.set(el, ref);
520
+ refNodes.set(ref, holdWeakly(el));
521
+ return { el: el, reresolved: true };
321
522
  } catch (e) {
322
523
  return null;
323
524
  }
324
525
  };
325
526
 
527
+ g.resolveRef = function (ref) {
528
+ const result = g.resolveRefDetailed(ref);
529
+ return result ? result.el : null;
530
+ };
531
+
532
+ g.keyForRef = function (ref) {
533
+ if (g.dead || !ownsRef(ref)) return null;
534
+ const key = refKeys.get(ref);
535
+ if (!key) return null;
536
+ return { role: key.role, name: key.name, path: key.path.map((s) => ({ tag: s.tag, index: s.index })) };
537
+ };
538
+
326
539
  g.nextSnapshotId = function () {
327
540
  if (g.dead) return null;
328
541
  return g.doc_id + ':s' + ++snapshotSeq;
@@ -422,6 +635,7 @@ export function buildObserverInstallJs(opts = {}) {
422
635
  g.baselines.clear();
423
636
  refs = new WeakMap();
424
637
  refNodes.clear();
638
+ refKeys.clear();
425
639
  snapshotSeq = 0;
426
640
  snapshots.clear();
427
641
  if (window[opts.key] === g) {
@@ -490,12 +704,24 @@ function jsStringLiteral(value) {
490
704
  /**
491
705
  * Build the ref-resolve expression, for tests and debugging. Evaluates to
492
706
  * `{ ok: true, found: true, tag }` when `ref` names an element of the current
493
- * document that is still connected, `{ ok: true, found: false }` when it is
494
- * unknown or stale, or `{ ok: false, reason: 'observer-unavailable' }`.
707
+ * document that is still connected — plus `reresolved: true` when the node was
708
+ * gone and the ref was just rebound by its semantic key —
709
+ * `{ ok: true, found: false }` when it is unknown or stale, or
710
+ * `{ ok: false, reason: 'observer-unavailable' }`.
495
711
  */
496
712
  export function buildObserverRefResolveJs(ref) {
497
713
  const refJson = jsStringLiteral(typeof ref === 'string' ? ref : '');
498
- return callInstance(`((el) => el ? { ok: true, found: true, tag: el.localName || String(el.nodeName).toLowerCase() } : { ok: true, found: false })(g.resolveRef(${refJson}))`);
714
+ return callInstance(`((r) => { if (!r) return { ok: true, found: false }; const tag = r.el.localName || String(r.el.nodeName).toLowerCase(); return r.reresolved ? { ok: true, found: true, tag: tag, reresolved: true } : { ok: true, found: true, tag: tag }; })(g.resolveRefDetailed(${refJson}))`);
715
+ }
716
+ /**
717
+ * Build the semantic-key read expression. Evaluates to `{ ok: true, key }`
718
+ * with the `{ role, name, path }` stored when `ref` was issued, `{ ok: true,
719
+ * key: null }` for a ref this document instance does not know, or
720
+ * `{ ok: false, reason: 'observer-unavailable' }`.
721
+ */
722
+ export function buildObserverSemanticKeyJs(ref) {
723
+ const refJson = jsStringLiteral(typeof ref === 'string' ? ref : '');
724
+ return callInstance(`({ ok: true, key: g.keyForRef(${refJson}) })`);
499
725
  }
500
726
  /**
501
727
  * Build the teardown expression: disconnects every observer, restores the
@@ -1 +1 @@
1
- {"version":3,"file":"observer.js","sourceRoot":"","sources":["../../src/inpage/observer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"observer.js","sourceRoot":"","sources":["../../src/inpage/observer.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "browser-link",
4
- "version": "0.36.0",
4
+ "version": "0.38.0",
5
5
  "description": "Bridge between Chrome and an MCP client (Claude Code, OpenCode, GitHub Copilot CLI, …). Per-tab manual activation.",
6
6
  "permissions": [
7
7
  "debugger",
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The hand-written core of the agent manual: the part every agent must see
3
+ * before its first browser call. It is rendered first in the MCP
4
+ * `initialize.instructions` (hosts truncate that text; Claude Code cut it at
5
+ * ~4,090 characters) and first in the installed instructions block, so it is
6
+ * pinned at 3,800 characters by `manual.test.ts`.
7
+ *
8
+ * Rule for editing: every line must change what the agent does next. Depth
9
+ * lives in the per-tool `doc` blocks and in `topics.ts`, reachable through
10
+ * `browser.manual`.
11
+ *
12
+ * The bilingual phrases in the trigger list are DATA (what users actually
13
+ * type), not instructions; tests pin them verbatim.
14
+ */
15
+ export interface CoreOptions {
16
+ /** One line placed before everything else, e.g. an outdated-block notice. */
17
+ notice?: string;
18
+ }
19
+ /** The core as one string; the optional notice is its first line. */
20
+ export declare function renderCore(opts?: CoreOptions): string;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The hand-written core of the agent manual: the part every agent must see
3
+ * before its first browser call. It is rendered first in the MCP
4
+ * `initialize.instructions` (hosts truncate that text; Claude Code cut it at
5
+ * ~4,090 characters) and first in the installed instructions block, so it is
6
+ * pinned at 3,800 characters by `manual.test.ts`.
7
+ *
8
+ * Rule for editing: every line must change what the agent does next. Depth
9
+ * lives in the per-tool `doc` blocks and in `topics.ts`, reachable through
10
+ * `browser.manual`.
11
+ *
12
+ * The bilingual phrases in the trigger list are DATA (what users actually
13
+ * type), not instructions; tests pin them verbatim.
14
+ */
15
+ import { TOPIC_NAMES } from './topics.js';
16
+ const CORE_LINES = [
17
+ '## browser-link — reflex protocol (ALWAYS ACTIVE)',
18
+ '',
19
+ '`browser.*` tools see only the Chrome tabs the user connected through the companion extension. Never reason about page state you have not seen.',
20
+ '',
21
+ '### TRIGGER LIST — call before responding when ANY apply',
22
+ '',
23
+ 'User mentions any of these (English or Spanish):',
24
+ '- "the button doesn\'t work" / "no anda el botón" / "no funciona X"',
25
+ '- "broken layout" / "está roto" / "se ve mal" / "looks wrong"',
26
+ '- "check if X works" / "fíjate si anda X" / "¿anda X?" / "does X work?"',
27
+ '- "the page" / "la página" / "la web" / "the site"',
28
+ '- "open this in the browser" / "abrí esto en el navegador" / "navegá a"',
29
+ '- a UI element, web app name, URL, browser tab, layout, dialog, form',
30
+ '→ call `browser.list_tabs` FIRST. A tab with a `map` field: read its `inline` map when present, otherwise call `browser.map.recall` BEFORE `browser.snapshot`.',
31
+ '',
32
+ '- Before suggesting a UI code change or a click, `browser.snapshot` and verify the element exists.',
33
+ '- "Tab not connected" → `browser.events` for the renamed tab_id, then retry.',
34
+ '- SELF-CHECK after each user message: is this about something visible in a browser? Did I run `browser.list_tabs`?',
35
+ '',
36
+ '### Success is not proof',
37
+ '',
38
+ 'An `ok` result can still mean the action missed. Check these fields:',
39
+ '- `hit_element` on `click` (and flow click steps) appears when a `pointer-events: none` target passes the click on, or on `force: true`; it names the real receiver: if it is not your target or inside it, the click missed → do not proceed; `find` the element that receives clicks by its text.',
40
+ '- `retargeted` error on `click`: the click would land on an ancestor of your target (clipped or covered by its own container) → scroll or expand the container, or `find` the element that receives clicks; `force: true` clicks there anyway.',
41
+ '- `settle.settled: false` or `mutation_count: 0`: quiet is not effect → `expect` or `wait_for` the effect you need.',
42
+ '- `since_status` on `snapshot`: you received a full snapshot → keep its new `snapshot_id`.',
43
+ '- `reresolved: true`: the node was replaced and re-found → fine.',
44
+ '- `ref-stale` → take a fresh `snapshot`/`find`; never retry the same ref.',
45
+ '- `wait_for` returning `matched: false` is not an error → decide.',
46
+ '',
47
+ '### Habits',
48
+ '',
49
+ '- One full `snapshot` per view, then `snapshot({ since: snapshot_id })` for every later look.',
50
+ '- Act on the `ref` from the last `snapshot`/`find`, not a selector copied earlier; never inject attributes with `evaluate` to build selectors.',
51
+ '- Locate with `find` by text (`role` optional). `evaluate` only reads, never clicks or types: synthetic events are `isTrusted: false`, apps ignore them, and nothing records what happened.',
52
+ '- Downloads only through `browser.download` (`url` or `selector`), never a click on a download control.',
53
+ '- Pass `expect` to `click`/`type`/`press` when the effect matters.',
54
+ '',
55
+ '### Shared tabs and cdp-direct',
56
+ '',
57
+ '- Several agents can share the bridge: `browser.claim_tab` before a multi-step flow, `browser.release_tab` after.',
58
+ '- On a claim conflict do not spin-retry: use another tab or ask the user.',
59
+ '- `cdp:` tabs (`transport: "cdp"`) lack drag, console, network, network_body, canvas_screenshot, dialog_respond, set_permission, wait_for_tab and download by selector.',
60
+ '- "cdp-direct is disabled" / "requires an active grant" → relay `browser-link config set cdp-direct.enabled true` or `browser-link cdp allow` to the user; there is no workaround.',
61
+ '',
62
+ '### Depth on demand',
63
+ '',
64
+ `\`browser.manual()\` lists tools and topics; \`browser.manual({ tool: "browser.click" })\` gives one tool's full doc; \`browser.manual({ topic })\` gives one topic: ${TOPIC_NAMES.join(', ')}.`,
65
+ ];
66
+ /** The core as one string; the optional notice is its first line. */
67
+ export function renderCore(opts = {}) {
68
+ const lines = opts.notice ? [opts.notice, '', ...CORE_LINES] : CORE_LINES;
69
+ return lines.join('\n');
70
+ }
71
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/manual/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,UAAU,GAAsB;IACpC,mDAAmD;IACnD,EAAE;IACF,iJAAiJ;IACjJ,EAAE;IACF,0DAA0D;IAC1D,EAAE;IACF,kDAAkD;IAClD,qEAAqE;IACrE,+DAA+D;IAC/D,yEAAyE;IACzE,oDAAoD;IACpD,yEAAyE;IACzE,sEAAsE;IACtE,gKAAgK;IAChK,EAAE;IACF,oGAAoG;IACpG,8EAA8E;IAC9E,oHAAoH;IACpH,EAAE;IACF,0BAA0B;IAC1B,EAAE;IACF,sEAAsE;IACtE,qSAAqS;IACrS,gPAAgP;IAChP,qHAAqH;IACrH,4FAA4F;IAC5F,kEAAkE;IAClE,2EAA2E;IAC3E,mEAAmE;IACnE,EAAE;IACF,YAAY;IACZ,EAAE;IACF,+FAA+F;IAC/F,gJAAgJ;IAChJ,6LAA6L;IAC7L,yGAAyG;IACzG,oEAAoE;IACpE,EAAE;IACF,gCAAgC;IAChC,EAAE;IACF,mHAAmH;IACnH,2EAA2E;IAC3E,yKAAyK;IACzK,oLAAoL;IACpL,EAAE;IACF,qBAAqB;IACrB,EAAE;IACF,wKAAwK,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;CAClM,CAAC;AAOF,qEAAqE;AACrE,MAAM,UAAU,UAAU,CAAC,OAAoB,EAAE;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IAC1E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Renderers for the agent manual. One source, three outputs:
3
+ *
4
+ * - `renderServerInstructions` — the MCP `initialize.instructions` text:
5
+ * the core first (hosts truncate this text, so what comes first is what
6
+ * arrives), then one section per tool rendered from its `doc` block.
7
+ * - `renderInstallBody` — the body of the block `browser-link instructions
8
+ * install` writes into each client's instructions file: the core, then
9
+ * the topics an agent needs every session.
10
+ * - `renderTopic` (in `topics.ts`) — one topic on demand.
11
+ */
12
+ import type { ToolDefinition } from '../tools/types.js';
13
+ import { type CoreOptions } from './core.js';
14
+ /** Render a single tool's documentation as a markdown section. Skips
15
+ * tools without a `doc` block. */
16
+ export declare function renderTool(def: ToolDefinition): string | null;
17
+ /** The MCP `initialize.instructions` text: core, then `## Tools` and one
18
+ * section per documented tool, browser tools first, then map tools. */
19
+ export declare function renderServerInstructions(opts?: CoreOptions): string;
20
+ /** The installed block body as single lines: the core, then the install
21
+ * topics, each separated by one blank line. */
22
+ export declare function renderInstallBody(): string[];
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Renderers for the agent manual. One source, three outputs:
3
+ *
4
+ * - `renderServerInstructions` — the MCP `initialize.instructions` text:
5
+ * the core first (hosts truncate this text, so what comes first is what
6
+ * arrives), then one section per tool rendered from its `doc` block.
7
+ * - `renderInstallBody` — the body of the block `browser-link instructions
8
+ * install` writes into each client's instructions file: the core, then
9
+ * the topics an agent needs every session.
10
+ * - `renderTopic` (in `topics.ts`) — one topic on demand.
11
+ */
12
+ import { BROWSER_TOOL_DEFINITIONS } from '../tools/browser-definitions.js';
13
+ import { MAP_TOOL_DEFINITIONS } from '../map/tools.js';
14
+ import { renderCore } from './core.js';
15
+ import { renderTopic } from './topics.js';
16
+ /** Topics embedded in the installed block, in order. The block is read in
17
+ * full every session, so these are the ones worth that cost. */
18
+ const INSTALL_TOPICS = ['token-efficiency', 'downloads', 'flows', 'map'];
19
+ /** Render a single tool's documentation as a markdown section. Skips
20
+ * tools without a `doc` block. */
21
+ export function renderTool(def) {
22
+ if (!def.doc)
23
+ return null;
24
+ const { purpose, when_to_use, gotchas, example } = def.doc;
25
+ const lines = [`### ${def.name}`, '', purpose, '', '**When to use:**'];
26
+ for (const w of when_to_use)
27
+ lines.push(`- ${w}`);
28
+ if (gotchas && gotchas.length > 0) {
29
+ lines.push('', '**Gotchas:**');
30
+ for (const g of gotchas)
31
+ lines.push(`- ${g}`);
32
+ }
33
+ if (example !== undefined && example !== '') {
34
+ lines.push('', '**Example:**', '```', example, '```');
35
+ }
36
+ return lines.join('\n');
37
+ }
38
+ /** The MCP `initialize.instructions` text: core, then `## Tools` and one
39
+ * section per documented tool, browser tools first, then map tools. */
40
+ export function renderServerInstructions(opts = {}) {
41
+ const all = [...BROWSER_TOOL_DEFINITIONS, ...MAP_TOOL_DEFINITIONS];
42
+ const sections = [renderCore(opts), '', '## Tools'];
43
+ for (const def of all) {
44
+ const rendered = renderTool(def);
45
+ if (rendered)
46
+ sections.push('', rendered);
47
+ }
48
+ return sections.join('\n');
49
+ }
50
+ /** The installed block body as single lines: the core, then the install
51
+ * topics, each separated by one blank line. */
52
+ export function renderInstallBody() {
53
+ const text = [renderCore({}), ...INSTALL_TOPICS.map(renderTopic)].join('\n\n');
54
+ return text.split('\n');
55
+ }
56
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/manual/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAoB,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,WAAW,EAAkB,MAAM,aAAa,CAAC;AAE1D;gEACgE;AAChE,MAAM,cAAc,GAAyB,CAAC,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;AAE/F;kCACkC;AAClC,MAAM,UAAU,UAAU,CAAC,GAAmB;IAC5C,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IAC3D,MAAM,KAAK,GAAa,CAAC,OAAO,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,kBAAkB,CAAC,CAAC;IACjF,KAAK,MAAM,CAAC,IAAI,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClD,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;uEACuE;AACvE,MAAM,UAAU,wBAAwB,CAAC,OAAoB,EAAE;IAC7D,MAAM,GAAG,GAAqB,CAAC,GAAG,wBAAwB,EAAE,GAAG,oBAAoB,CAAC,CAAC;IACrF,MAAM,QAAQ,GAAa,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;IAC9D,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,QAAQ;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;+CAC+C;AAC/C,MAAM,UAAU,iBAAiB;IAC/B,MAAM,IAAI,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/E,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Read-only status of the installed instructions block, per client, shared
3
+ * by the primary's start-time notice (`server.ts`) and the `browser.manual`
4
+ * index. Detection only reads the clients' instructions files through each
5
+ * installer's `detect()` (home-guarded as the installers already are); this
6
+ * module never writes anything.
7
+ */
8
+ import { type ClientId, type InstructionsInstaller, type InstructionsState } from '../agent-instructions/index.js';
9
+ export interface ClientInstructionsStatus {
10
+ id: ClientId;
11
+ displayName: string;
12
+ state: InstructionsState;
13
+ }
14
+ /** Detect every client's block. A client whose detection throws (permissions,
15
+ * EBADF) is skipped, never fatal. */
16
+ export declare function detectInstructionsStatus(installers?: readonly InstructionsInstaller[]): ClientInstructionsStatus[];
17
+ /** One status line for the `browser.manual` index. */
18
+ export declare function formatClientStatus(s: ClientInstructionsStatus, version?: string): string;
19
+ /** The one-line notice placed before the core when a client's block exists
20
+ * and is older than the server (`installed-outdated`); `undefined` otherwise.
21
+ * A client without a block (`not-installed`, `no-file`) is a choice and a
22
+ * `corrupt` file is the CLI's business: neither ever produces a notice. */
23
+ export declare function instructionsNotice(statuses: readonly ClientInstructionsStatus[], version?: string): string | undefined;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Read-only status of the installed instructions block, per client, shared
3
+ * by the primary's start-time notice (`server.ts`) and the `browser.manual`
4
+ * index. Detection only reads the clients' instructions files through each
5
+ * installer's `detect()` (home-guarded as the installers already are); this
6
+ * module never writes anything.
7
+ */
8
+ import { VERSION } from '../version.js';
9
+ import { INSTRUCTIONS_INSTALLERS, } from '../agent-instructions/index.js';
10
+ /** Detect every client's block. A client whose detection throws (permissions,
11
+ * EBADF) is skipped, never fatal. */
12
+ export function detectInstructionsStatus(installers = INSTRUCTIONS_INSTALLERS) {
13
+ const out = [];
14
+ for (const installer of installers) {
15
+ try {
16
+ const { state } = installer.detect();
17
+ out.push({ id: installer.id, displayName: installer.displayName, state });
18
+ }
19
+ catch {
20
+ /* best effort: an unreadable client is left out */
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+ const v = (version) => (version === null ? 'unversioned' : `v${version}`);
26
+ /** One status line for the `browser.manual` index. */
27
+ export function formatClientStatus(s, version = VERSION) {
28
+ const { state } = s;
29
+ switch (state.kind) {
30
+ case 'installed':
31
+ return `${s.displayName}: installed ${v(state.version)}`;
32
+ case 'installed-outdated':
33
+ return `${s.displayName}: installed-outdated ${v(state.version)} → v${version}: run browser-link instructions install`;
34
+ case 'corrupt':
35
+ return `${s.displayName}: corrupt (${state.reason})`;
36
+ default:
37
+ return `${s.displayName}: ${state.kind}`;
38
+ }
39
+ }
40
+ /** The one-line notice placed before the core when a client's block exists
41
+ * and is older than the server (`installed-outdated`); `undefined` otherwise.
42
+ * A client without a block (`not-installed`, `no-file`) is a choice and a
43
+ * `corrupt` file is the CLI's business: neither ever produces a notice. */
44
+ export function instructionsNotice(statuses, version = VERSION) {
45
+ for (const s of statuses) {
46
+ if (s.state.kind === 'installed-outdated') {
47
+ return `Your browser-link instructions block is installed-outdated (installed ${v(s.state.version)}, server v${version}): run \`browser-link instructions install\` and restart the client.`;
48
+ }
49
+ }
50
+ return undefined;
51
+ }
52
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/manual/status.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EACL,uBAAuB,GAIxB,MAAM,gCAAgC,CAAC;AAQxC;qCACqC;AACrC,MAAM,UAAU,wBAAwB,CACtC,aAA+C,uBAAuB;IAEtE,MAAM,GAAG,GAA+B,EAAE,CAAC;IAC3C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;YACrC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,WAAW,EAAE,SAAS,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5E,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,GAAG,CAAC,OAAsB,EAAU,EAAE,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC;AAEjG,sDAAsD;AACtD,MAAM,UAAU,kBAAkB,CAAC,CAA2B,EAAE,OAAO,GAAG,OAAO;IAC/E,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACpB,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,WAAW;YACd,OAAO,GAAG,CAAC,CAAC,WAAW,eAAe,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3D,KAAK,oBAAoB;YACvB,OAAO,GAAG,CAAC,CAAC,WAAW,wBAAwB,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,OAAO,yCAAyC,CAAC;QACzH,KAAK,SAAS;YACZ,OAAO,GAAG,CAAC,CAAC,WAAW,cAAc,KAAK,CAAC,MAAM,GAAG,CAAC;QACvD;YACE,OAAO,GAAG,CAAC,CAAC,WAAW,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7C,CAAC;AACH,CAAC;AAED;;;2EAG2E;AAC3E,MAAM,UAAU,kBAAkB,CAChC,QAA6C,EAC7C,OAAO,GAAG,OAAO;IAEjB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;YAC1C,OAAO,yEAAyE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,OAAO,sEAAsE,CAAC;QAC/L,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `browser.manual` — the agent manual on demand. Server-only: it reads the
3
+ * tool definitions, the topics and the instructions-block status, never a
4
+ * tab or the bridge.
5
+ *
6
+ * - no arguments → the index: version, block status per client, one line
7
+ * per tool, the topic list;
8
+ * - `{ tool }` → that tool's `doc` plus one line per `inputSchema` property;
9
+ * - `{ topic }` → one hand-written topic.
10
+ */
11
+ import { type ClientInstructionsStatus } from './status.js';
12
+ export interface ManualOptions {
13
+ /** Current block status per client (read-only detection). */
14
+ statuses: () => readonly ClientInstructionsStatus[];
15
+ }
16
+ /** Text up to and including the first sentence end followed by a capital
17
+ * letter or the end of the text, so "e.g. `x`" does not cut it short. */
18
+ export declare function firstSentence(text: string): string;
19
+ /** Render `browser.manual`'s answer for `args`; throws on invalid input. */
20
+ export declare function renderManual(args: unknown, opts: ManualOptions): string;