@jobshimo/browser-link 0.15.0 → 0.17.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.
@@ -77,6 +77,16 @@ function body() {
77
77
  '`browser.evaluate` is the escape hatch. Every dedicated tool below is',
78
78
  'cheaper in tokens AND more reliable than a hand-rolled expression.',
79
79
  '',
80
+ '- Any multi-step interaction (find a target → act on it → wait → act',
81
+ ' again) → `browser.flow({ steps: [...] })` is now the DEFAULT, not a',
82
+ ' fallback. find → click → wait_for → type → press used to cost 5+',
83
+ ' separate MCP round trips — each one a full LLM inference — and now',
84
+ ' costs ONE `browser.flow` call (two if a step fails and you need a',
85
+ " corrective follow-up). A `find` step's selector becomes the implicit",
86
+ ' target for the very next click/type/press step that omits',
87
+ ' `selector`. Flow is strictly sequential and fails fast — reach for',
88
+ ' individual click/type/press/find/wait_for calls only when you need',
89
+ ' to branch on an intermediate result.',
80
90
  '- Finding an element by visible text → `browser.find({ text, role? })`,',
81
91
  ' NOT `browser.evaluate` with a `textContent` grep. `find` covers',
82
92
  ' `<div onclick>` (which a naive `querySelectorAll("button")` MISSES',
@@ -103,11 +113,23 @@ function body() {
103
113
  ' Never N separate evaluates for N reads.',
104
114
  '- Scrolling → `browser.evaluate` with `pane.scrollTop = N` or',
105
115
  ' `el.scrollIntoView({ block: "center" })`. No dedicated tool needed.',
106
- '- Special keys (Enter/Tab/Escape/arrows) → `browser.evaluate` with',
107
- ' `el.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }))`.',
108
- ' Vanilla AND React-delegated handlers (React 17+ root delegation)',
109
- ' pick up bubbled dispatched events. For TYPING text → `browser.type`',
110
- ' (native setter; controlled components update their state).',
116
+ '- Special keys (Enter/Tab/Escape/arrows/shortcuts) → `browser.press`,',
117
+ ' NOT `browser.evaluate` with a synthetic `KeyboardEvent`. Synthetic',
118
+ ' `dispatchEvent` produces `isTrusted:false` events rich editors,',
119
+ ' autocompletes, and non-DOM runtimes (Qt-WASM, WebGL) silently ignore',
120
+ ' them. `browser.press` dispatches a real CDP `Input.dispatchKeyEvent`',
121
+ ' sequence (`isTrusted:true`). For TYPING text → `browser.type` (native',
122
+ ' setter; controlled components update their state).',
123
+ '- After `click`/`type`/`press`, a separate `browser.wait_for` is',
124
+ ' usually unnecessary — all three accept `settle_ms` (default 150) and',
125
+ ' return a `settle` object (`{ settled, duration_ms, mutation_count,',
126
+ ' url_changed?, focus_moved? }`) once the page goes quiet. Settle',
127
+ ' proves QUIET, not EFFECT: mutations completing before the observer',
128
+ ' installs (right after dispatch) and async reactions starting after',
129
+ ' the quiet window are both invisible — `mutation_count: 0` does NOT',
130
+ ' mean the action did nothing. Reach for `wait_for` when you need a',
131
+ ' SPECIFIC condition (a particular selector, network request, or',
132
+ ' expression), not just "did anything change".',
111
133
  '- Paging `browser.events` → keep `lastId = result.latest_id` in your',
112
134
  ' working notes and pass as `since_id` next call. One variable. The',
113
135
  ' server does not maintain per-agent cursors.',
@@ -1 +1 @@
1
- {"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/agent-instructions/content.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/agent-instructions/content.ts"],"names":[],"mappings":"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"}
@@ -45,6 +45,8 @@ export const I18N_ABOUT = {
45
45
  ' browser.navigate change the tab URL',
46
46
  ' browser.click click an element by CSS selector',
47
47
  ' browser.type type into an input',
48
+ ' browser.press press a key (trusted CDP input, not synthetic)',
49
+ ' browser.flow run a find/click/type/press/wait_for sequence in one call',
48
50
  ' browser.evaluate run JavaScript in the page context',
49
51
  ' browser.console recent console messages (rolling 200)',
50
52
  ' browser.network recent network requests (rolling 200)',
@@ -141,6 +143,8 @@ export const I18N_ABOUT = {
141
143
  ' browser.navigate cambiar la URL de la pestaña',
142
144
  ' browser.click hacer click en un elemento por CSS selector',
143
145
  ' browser.type escribir en un input',
146
+ ' browser.press presionar una tecla (input CDP confiable, no sintético)',
147
+ ' browser.flow encadenar find/click/type/press/wait_for en una sola llamada',
144
148
  ' browser.evaluate ejecutar JavaScript en el contexto de la página',
145
149
  ' browser.console mensajes recientes de consola (200 últimos)',
146
150
  ' browser.network peticiones de red recientes (200 últimas)',
@@ -1 +1 @@
1
- {"version":3,"file":"about.js","sourceRoot":"","sources":["../../src/commands/about.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"about.js","sourceRoot":"","sources":["../../src/commands/about.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,7 @@
1
- import { buildSnapshotJs, buildFindJs, buildClickResolveJs, buildTypeResolveJs, } from './inpage/builders.js';
1
+ import { buildSnapshotJs, buildFindJs, buildClickResolveJs, buildTypeResolveJs, buildFocusJs, } from './inpage/builders.js';
2
+ import { buildKeyEventSequence, resolveKey, modifiersToBitmask, MODIFIER_BITS, } from './keymap.js';
3
+ import { resolveSettleParams, settleSafely } from './settle.js';
4
+ import { runFlow, } from './flow.js';
2
5
  const WS_URL = 'ws://127.0.0.1:17529';
3
6
  const CDP_VERSION = '1.3';
4
7
  const CONSOLE_BUFFER_MAX = 200;
@@ -106,6 +109,28 @@ function sleep(ms) {
106
109
  }
107
110
  });
108
111
  }
112
+ /** Run the shared post-action settle wait for a tab. Thin closure binder
113
+ * over `settleSafely` (see settle.ts) — which is guaranteed not to throw,
114
+ * so an action that navigated the page (destroying the settle expression's
115
+ * execution context) still returns ok:true with a degraded settle object
116
+ * instead of flipping the whole response to ok:false. */
117
+ function runSettle(tabId, settle) {
118
+ return settleSafely((expression) => evaluateInTab(tabId, expression), settle);
119
+ }
120
+ /**
121
+ * Dispatch the CDP `Input.dispatchKeyEvent` sequence for one resolved key.
122
+ * The event shapes come from `buildKeyEventSequence` (keymap.ts) — pure and
123
+ * unit-tested there; this wrapper only feeds them to chrome.debugger. This
124
+ * is the ONLY path in this extension that produces `isTrusted: true`
125
+ * keyboard events — see browser.press's doc block for why that matters
126
+ * (Qt-WASM and similar runtimes discard synthetic KeyboardEvents dispatched
127
+ * via browser.evaluate).
128
+ */
129
+ async function dispatchKeyEvent(tabId, def, modifiers) {
130
+ for (const event of buildKeyEventSequence(def, modifiers)) {
131
+ await cdp(tabId, 'Input.dispatchKeyEvent', event);
132
+ }
133
+ }
109
134
  /**
110
135
  * Wait for ONE CDP event of a given method on a given tab. Adds a temporary
111
136
  * chrome.debugger.onEvent listener for the duration of the wait. Resolves
@@ -504,6 +529,233 @@ async function waitForLoad(tabId, timeoutMs = 20_000) {
504
529
  chrome.tabs.onUpdated.addListener(handler);
505
530
  });
506
531
  }
532
+ /**
533
+ * `performFind` / `performClick` / `performType` / `performPress` /
534
+ * `performWaitFor` — the extracted bodies of the standalone `find` /
535
+ * `click` / `type` / `press` / `wait_for` cases in `handleTool` below.
536
+ *
537
+ * Both the standalone `case` handlers AND `browser.flow`'s step executor
538
+ * (`runFlow` in `./flow.js`) call these exact functions — there is one
539
+ * implementation of each action, never a copy that can drift. Each
540
+ * returns an `ActionOutcome<T>` (`{ok:true,result} | {ok:false,error}`)
541
+ * instead of a wire-level `tool.response`, so the two call sites can
542
+ * unwrap it however they need to (the standalone case wraps it straight
543
+ * into a `tool.response`; `runFlow` inspects `result` for its own
544
+ * fail-fast rules — e.g. `wait_for`'s `matched:false` is `ok:true` here,
545
+ * matching the standalone tool's contract, and it is `runFlow`, not this
546
+ * function, that decides a non-match should stop the flow).
547
+ *
548
+ * `performType`'s `selector` is optional here (the standalone tool's JSON
549
+ * schema still requires it, so that path never changes): when omitted,
550
+ * this skips element resolution entirely and types into whatever
551
+ * currently has focus, mirroring how `performPress` already behaves with
552
+ * no `selector`. That is what lets a `browser.flow` step continue typing
553
+ * into a freshly-appeared, auto-focused input (e.g. a search box that
554
+ * opened after a preceding click) without a selector for it.
555
+ */
556
+ async function performFind(tabId, params) {
557
+ try {
558
+ const { text } = params;
559
+ if (!text)
560
+ return { ok: false, error: 'find: text required' };
561
+ const value = await evaluateInTab(tabId, buildFindJs({ text, role: params.role, exact: params.exact === true }));
562
+ return { ok: true, result: value };
563
+ }
564
+ catch (err) {
565
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
566
+ }
567
+ }
568
+ async function performClick(tabId, params) {
569
+ try {
570
+ const { selector, force = false } = params;
571
+ const settle = resolveSettleParams({
572
+ settle_ms: params.settle_ms,
573
+ settle_timeout_ms: params.settle_timeout_ms,
574
+ });
575
+ const resolved = await evaluateInTab(tabId, buildClickResolveJs({ selector, force }));
576
+ if (!resolved.ok) {
577
+ if (resolved.reason === 'occluded') {
578
+ return {
579
+ ok: false,
580
+ error: `Element covered by ${resolved.blocker} — click the covering element or dismiss it first`,
581
+ };
582
+ }
583
+ return { ok: false, error: `Element not found: ${selector}` };
584
+ }
585
+ await cdp(tabId, 'Input.dispatchMouseEvent', {
586
+ type: 'mouseMoved',
587
+ x: resolved.x,
588
+ y: resolved.y,
589
+ });
590
+ await cdp(tabId, 'Input.dispatchMouseEvent', {
591
+ type: 'mousePressed',
592
+ x: resolved.x,
593
+ y: resolved.y,
594
+ button: 'left',
595
+ clickCount: 1,
596
+ });
597
+ await cdp(tabId, 'Input.dispatchMouseEvent', {
598
+ type: 'mouseReleased',
599
+ x: resolved.x,
600
+ y: resolved.y,
601
+ button: 'left',
602
+ clickCount: 1,
603
+ });
604
+ const settleResult = await runSettle(tabId, settle);
605
+ const result = { clicked: selector, tag: resolved.tag };
606
+ if (settleResult)
607
+ result.settle = settleResult;
608
+ return { ok: true, result };
609
+ }
610
+ catch (err) {
611
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
612
+ }
613
+ }
614
+ async function performType(tabId, params) {
615
+ try {
616
+ const { selector, text, clear = false } = params;
617
+ const settle = resolveSettleParams({
618
+ settle_ms: params.settle_ms,
619
+ settle_timeout_ms: params.settle_timeout_ms,
620
+ });
621
+ if (selector !== undefined) {
622
+ const focused = await evaluateInTab(tabId, buildTypeResolveJs({ selector, clear }));
623
+ if (!focused) {
624
+ return { ok: false, error: `Element not found: ${selector}` };
625
+ }
626
+ }
627
+ await cdp(tabId, 'Input.insertText', { text });
628
+ const settleResult = await runSettle(tabId, settle);
629
+ const result = { typed: text.length };
630
+ if (selector !== undefined)
631
+ result.selector = selector;
632
+ if (settleResult)
633
+ result.settle = settleResult;
634
+ return { ok: true, result };
635
+ }
636
+ catch (err) {
637
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
638
+ }
639
+ }
640
+ async function performPress(tabId, params) {
641
+ try {
642
+ const key = params.key;
643
+ if (!key)
644
+ return { ok: false, error: 'press: key required' };
645
+ const def = resolveKey(key);
646
+ if (!def)
647
+ return { ok: false, error: `press: unrecognized key "${key}"` };
648
+ const modifierNames = params.modifiers ?? [];
649
+ const modifiers = modifiersToBitmask(modifierNames) | (def.needsShift ? MODIFIER_BITS.Shift : 0);
650
+ const settle = resolveSettleParams({
651
+ settle_ms: params.settle_ms,
652
+ settle_timeout_ms: params.settle_timeout_ms,
653
+ });
654
+ if (params.selector) {
655
+ const focused = await evaluateInTab(tabId, buildFocusJs({ selector: params.selector }));
656
+ if (!focused) {
657
+ return { ok: false, error: `Element not found: ${params.selector}` };
658
+ }
659
+ }
660
+ await dispatchKeyEvent(tabId, def, modifiers);
661
+ const settleResult = await runSettle(tabId, settle);
662
+ const result = { key, modifiers: modifierNames };
663
+ if (settleResult)
664
+ result.settle = settleResult;
665
+ return { ok: true, result };
666
+ }
667
+ catch (err) {
668
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
669
+ }
670
+ }
671
+ async function performWaitFor(tabId, state, params) {
672
+ try {
673
+ const waitSelector = params.selector;
674
+ const waitExpression = params.expression;
675
+ const waitNetworkUrl = params.network_url;
676
+ const waitCondition = params.condition ?? 'visible';
677
+ // Defensive caps mirror the dispatcher's contract and keep CodeQL
678
+ // happy about setTimeout durations driven by request params.
679
+ const MAX_WAIT_TIMEOUT_MS = 30_000;
680
+ const MIN_POLL_MS = 50;
681
+ const MAX_POLL_MS = 1_000;
682
+ const requestedTimeout = params.timeout_ms ?? 5_000;
683
+ const timeoutMs = requestedTimeout < MAX_WAIT_TIMEOUT_MS ? requestedTimeout : MAX_WAIT_TIMEOUT_MS;
684
+ const requestedPoll = params.poll_interval_ms ?? 100;
685
+ const pollIntervalMs = requestedPoll < MIN_POLL_MS
686
+ ? MIN_POLL_MS
687
+ : requestedPoll > MAX_POLL_MS
688
+ ? MAX_POLL_MS
689
+ : requestedPoll;
690
+ // Build the check function based on which mode the caller picked. The
691
+ // dispatcher already enforced "exactly one of selector / expression /
692
+ // network_url" so we just pick the first defined one. If none are
693
+ // defined (shouldn't happen from a well-formed call), the check stays
694
+ // null and we return matched=false immediately.
695
+ let check = null;
696
+ if (waitSelector !== undefined) {
697
+ const expr = buildWaitSelectorExpr(waitSelector, waitCondition);
698
+ check = async () => {
699
+ try {
700
+ const result = await evaluateInTab(tabId, expr);
701
+ return Boolean(result);
702
+ }
703
+ catch {
704
+ // If the page is unreachable mid-poll, count as "not yet matched".
705
+ return false;
706
+ }
707
+ };
708
+ }
709
+ else if (waitExpression !== undefined) {
710
+ const wrapped = `Boolean(${waitExpression})`;
711
+ check = async () => {
712
+ try {
713
+ const result = await evaluateInTab(tabId, wrapped);
714
+ return Boolean(result);
715
+ }
716
+ catch {
717
+ return false;
718
+ }
719
+ };
720
+ }
721
+ else if (waitNetworkUrl !== undefined) {
722
+ const needle = waitNetworkUrl.toLowerCase();
723
+ check = () => {
724
+ for (const id of state.networkOrder) {
725
+ const r = state.networkBuffer.get(id);
726
+ if (!r)
727
+ continue;
728
+ if (r.finished_at === undefined)
729
+ continue;
730
+ if (r.url.toLowerCase().includes(needle))
731
+ return Promise.resolve(true);
732
+ }
733
+ return Promise.resolve(false);
734
+ };
735
+ }
736
+ const startedAt = Date.now();
737
+ let checks = 0;
738
+ let matched = false;
739
+ while (check) {
740
+ checks++;
741
+ if (await check()) {
742
+ matched = true;
743
+ break;
744
+ }
745
+ if (Date.now() - startedAt >= timeoutMs)
746
+ break;
747
+ await sleep(pollIntervalMs);
748
+ }
749
+ const elapsedMs = Date.now() - startedAt;
750
+ const result = matched
751
+ ? { matched: true, elapsed_ms: elapsedMs, checks }
752
+ : { matched: false, elapsed_ms: elapsedMs, checks, reason: 'timeout' };
753
+ return { ok: true, result };
754
+ }
755
+ catch (err) {
756
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
757
+ }
758
+ }
507
759
  async function handleTool(state, msg) {
508
760
  const tabId = state.tabId;
509
761
  // Params come over the wire as JSON: each field is unknown until we
@@ -549,21 +801,15 @@ async function handleTool(state, msg) {
549
801
  return { kind: 'tool.response', id: msg.id, ok: true, result: value };
550
802
  }
551
803
  case 'find': {
552
- const text = str('text');
553
- if (!text) {
554
- return {
555
- kind: 'tool.response',
556
- id: msg.id,
557
- ok: false,
558
- error: 'find: text required',
559
- };
560
- }
561
- const value = await evaluateInTab(tabId, buildFindJs({
562
- text,
804
+ const outcome = await performFind(tabId, {
805
+ text: str('text'),
563
806
  role: optStr('role'),
564
807
  exact: p.exact === true,
565
- }));
566
- return { kind: 'tool.response', id: msg.id, ok: true, result: value };
808
+ });
809
+ if (!outcome.ok) {
810
+ return { kind: 'tool.response', id: msg.id, ok: false, error: outcome.error };
811
+ }
812
+ return { kind: 'tool.response', id: msg.id, ok: true, result: outcome.result };
567
813
  }
568
814
  case 'canvas_screenshot': {
569
815
  const selector = optStr('selector');
@@ -605,71 +851,67 @@ async function handleTool(state, msg) {
605
851
  return { kind: 'tool.response', id: msg.id, ok: true, result: body };
606
852
  }
607
853
  case 'click': {
608
- const selector = str('selector');
609
- const force = p.force === true;
610
- const resolved = await evaluateInTab(tabId, buildClickResolveJs({ selector, force }));
611
- if (!resolved.ok) {
612
- if (resolved.reason === 'occluded') {
613
- return {
614
- kind: 'tool.response',
615
- id: msg.id,
616
- ok: false,
617
- error: `Element covered by ${resolved.blocker} — click the covering element or dismiss it first`,
618
- };
619
- }
620
- return {
621
- kind: 'tool.response',
622
- id: msg.id,
623
- ok: false,
624
- error: `Element not found: ${selector}`,
625
- };
626
- }
627
- await cdp(tabId, 'Input.dispatchMouseEvent', {
628
- type: 'mouseMoved',
629
- x: resolved.x,
630
- y: resolved.y,
854
+ const outcome = await performClick(tabId, {
855
+ selector: str('selector'),
856
+ force: p.force === true,
857
+ settle_ms: typeof p.settle_ms === 'number' ? p.settle_ms : undefined,
858
+ settle_timeout_ms: typeof p.settle_timeout_ms === 'number' ? p.settle_timeout_ms : undefined,
631
859
  });
632
- await cdp(tabId, 'Input.dispatchMouseEvent', {
633
- type: 'mousePressed',
634
- x: resolved.x,
635
- y: resolved.y,
636
- button: 'left',
637
- clickCount: 1,
638
- });
639
- await cdp(tabId, 'Input.dispatchMouseEvent', {
640
- type: 'mouseReleased',
641
- x: resolved.x,
642
- y: resolved.y,
643
- button: 'left',
644
- clickCount: 1,
645
- });
646
- return {
647
- kind: 'tool.response',
648
- id: msg.id,
649
- ok: true,
650
- result: { clicked: selector, tag: resolved.tag },
651
- };
860
+ if (!outcome.ok) {
861
+ return { kind: 'tool.response', id: msg.id, ok: false, error: outcome.error };
862
+ }
863
+ return { kind: 'tool.response', id: msg.id, ok: true, result: outcome.result };
652
864
  }
653
865
  case 'type': {
654
- const selector = str('selector');
655
- const text = str('text');
656
- const clear = p.clear === true;
657
- const focused = await evaluateInTab(tabId, buildTypeResolveJs({ selector, clear }));
658
- if (!focused) {
659
- return {
660
- kind: 'tool.response',
661
- id: msg.id,
662
- ok: false,
663
- error: `Element not found: ${selector}`,
664
- };
866
+ const outcome = await performType(tabId, {
867
+ selector: str('selector'),
868
+ text: str('text'),
869
+ clear: p.clear === true,
870
+ settle_ms: typeof p.settle_ms === 'number' ? p.settle_ms : undefined,
871
+ settle_timeout_ms: typeof p.settle_timeout_ms === 'number' ? p.settle_timeout_ms : undefined,
872
+ });
873
+ if (!outcome.ok) {
874
+ return { kind: 'tool.response', id: msg.id, ok: false, error: outcome.error };
665
875
  }
666
- await cdp(tabId, 'Input.insertText', { text });
667
- return {
668
- kind: 'tool.response',
669
- id: msg.id,
670
- ok: true,
671
- result: { typed: text.length, selector },
672
- };
876
+ return { kind: 'tool.response', id: msg.id, ok: true, result: outcome.result };
877
+ }
878
+ case 'press': {
879
+ const modifierNames = Array.isArray(p.modifiers)
880
+ ? p.modifiers.filter((m) => typeof m === 'string')
881
+ : [];
882
+ const outcome = await performPress(tabId, {
883
+ key: optStr('key'),
884
+ modifiers: modifierNames,
885
+ selector: optStr('selector'),
886
+ settle_ms: typeof p.settle_ms === 'number' ? p.settle_ms : undefined,
887
+ settle_timeout_ms: typeof p.settle_timeout_ms === 'number' ? p.settle_timeout_ms : undefined,
888
+ });
889
+ if (!outcome.ok) {
890
+ return { kind: 'tool.response', id: msg.id, ok: false, error: outcome.error };
891
+ }
892
+ return { kind: 'tool.response', id: msg.id, ok: true, result: outcome.result };
893
+ }
894
+ case 'flow': {
895
+ // Wire-boundary narrowing: `p.steps` is untrusted JSON. Keep only
896
+ // object-shaped entries here — `runFlow`'s own `stepKind()` guard
897
+ // re-validates each one at runtime regardless, so a malformed
898
+ // entry fails the flow cleanly instead of throwing.
899
+ const rawSteps = Array.isArray(p.steps)
900
+ ? p.steps.filter((s) => typeof s === 'object' && s !== null)
901
+ : [];
902
+ const flowResult = await runFlow(rawSteps, {
903
+ performFind: (params) => performFind(tabId, params),
904
+ performClick: (params) => performClick(tabId, params),
905
+ performType: (params) => performType(tabId, params),
906
+ performPress: (params) => performPress(tabId, params),
907
+ performWaitFor: (params) => performWaitFor(tabId, state, params),
908
+ buildRecoverySnapshot: () => evaluateInTab(tabId, buildSnapshotJs({ only_interactive: true, max_interactive: 40 })),
909
+ });
910
+ // The wire-level response is ok:true whenever the flow RAN (even a
911
+ // failed step is a legitimate business outcome, same pattern as
912
+ // wait_for's matched:false) — flowResult itself carries the
913
+ // ok:true/false the agent reads.
914
+ return { kind: 'tool.response', id: msg.id, ok: true, result: flowResult };
673
915
  }
674
916
  case 'drag': {
675
917
  const optNum = (key) => {
@@ -957,95 +1199,18 @@ async function handleTool(state, msg) {
957
1199
  return { kind: 'tool.response', id: msg.id, ok: true, result: value };
958
1200
  }
959
1201
  case 'wait_for': {
960
- const optNum = (key) => {
961
- const v = p[key];
962
- return typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined;
963
- };
964
- const waitSelector = optStr('selector');
965
- const waitExpression = optStr('expression');
966
- const waitNetworkUrl = optStr('network_url');
967
- const waitCondition = optStr('condition') ?? 'visible';
968
- // Defensive caps mirror the dispatcher's contract and keep CodeQL
969
- // happy about setTimeout durations driven by request params.
970
- const MAX_WAIT_TIMEOUT_MS = 30_000;
971
- const MIN_POLL_MS = 50;
972
- const MAX_POLL_MS = 1_000;
973
- const requestedTimeout = optNum('timeout_ms') ?? 5_000;
974
- const timeoutMs = requestedTimeout < MAX_WAIT_TIMEOUT_MS ? requestedTimeout : MAX_WAIT_TIMEOUT_MS;
975
- const requestedPoll = optNum('poll_interval_ms') ?? 100;
976
- const pollIntervalMs = requestedPoll < MIN_POLL_MS
977
- ? MIN_POLL_MS
978
- : requestedPoll > MAX_POLL_MS
979
- ? MAX_POLL_MS
980
- : requestedPoll;
981
- // Build the check function based on which mode the caller picked.
982
- // The dispatcher already enforced "exactly one of selector / expression
983
- // / network_url" so we just pick the first defined one. If none are
984
- // defined (shouldn't happen from a well-formed call), the check stays
985
- // null and we return matched=false immediately.
986
- let check = null;
987
- if (waitSelector !== undefined) {
988
- const expr = buildWaitSelectorExpr(waitSelector, waitCondition);
989
- check = async () => {
990
- try {
991
- const result = await evaluateInTab(tabId, expr);
992
- return Boolean(result);
993
- }
994
- catch {
995
- // If the page is unreachable mid-poll, count as "not yet matched".
996
- return false;
997
- }
998
- };
999
- }
1000
- else if (waitExpression !== undefined) {
1001
- const wrapped = `Boolean(${waitExpression})`;
1002
- check = async () => {
1003
- try {
1004
- const result = await evaluateInTab(tabId, wrapped);
1005
- return Boolean(result);
1006
- }
1007
- catch {
1008
- return false;
1009
- }
1010
- };
1011
- }
1012
- else if (waitNetworkUrl !== undefined) {
1013
- const needle = waitNetworkUrl.toLowerCase();
1014
- check = () => {
1015
- for (const id of state.networkOrder) {
1016
- const r = state.networkBuffer.get(id);
1017
- if (!r)
1018
- continue;
1019
- if (r.finished_at === undefined)
1020
- continue;
1021
- if (r.url.toLowerCase().includes(needle))
1022
- return Promise.resolve(true);
1023
- }
1024
- return Promise.resolve(false);
1025
- };
1026
- }
1027
- const startedAt = Date.now();
1028
- let checks = 0;
1029
- let matched = false;
1030
- while (check) {
1031
- checks++;
1032
- if (await check()) {
1033
- matched = true;
1034
- break;
1035
- }
1036
- if (Date.now() - startedAt >= timeoutMs)
1037
- break;
1038
- await sleep(pollIntervalMs);
1202
+ const outcome = await performWaitFor(tabId, state, {
1203
+ selector: optStr('selector'),
1204
+ expression: optStr('expression'),
1205
+ network_url: optStr('network_url'),
1206
+ condition: optStr('condition'),
1207
+ timeout_ms: typeof p.timeout_ms === 'number' ? p.timeout_ms : undefined,
1208
+ poll_interval_ms: typeof p.poll_interval_ms === 'number' ? p.poll_interval_ms : undefined,
1209
+ });
1210
+ if (!outcome.ok) {
1211
+ return { kind: 'tool.response', id: msg.id, ok: false, error: outcome.error };
1039
1212
  }
1040
- const elapsedMs = Date.now() - startedAt;
1041
- return {
1042
- kind: 'tool.response',
1043
- id: msg.id,
1044
- ok: true,
1045
- result: matched
1046
- ? { matched: true, elapsed_ms: elapsedMs, checks }
1047
- : { matched: false, elapsed_ms: elapsedMs, checks, reason: 'timeout' },
1048
- };
1213
+ return { kind: 'tool.response', id: msg.id, ok: true, result: outcome.result };
1049
1214
  }
1050
1215
  case 'dialog_respond': {
1051
1216
  const accept = p.accept === true;