@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.
- package/dist/agent-instructions/content.js +27 -5
- package/dist/agent-instructions/content.js.map +1 -1
- package/dist/commands/about.js +4 -0
- package/dist/commands/about.js.map +1 -1
- package/dist/extension/background.js +328 -163
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/flow.d.ts +161 -0
- package/dist/extension/flow.js +213 -0
- package/dist/extension/flow.js.map +1 -0
- package/dist/extension/inpage/builders.d.ts +41 -0
- package/dist/extension/inpage/builders.js +71 -4
- package/dist/extension/inpage/builders.js.map +1 -1
- package/dist/extension/keymap.d.ts +82 -0
- package/dist/extension/keymap.js +205 -0
- package/dist/extension/keymap.js.map +1 -0
- package/dist/extension/manifest.json +1 -1
- package/dist/extension/settle.d.ts +39 -0
- package/dist/extension/settle.js +69 -0
- package/dist/extension/settle.js.map +1 -0
- package/dist/permissions.js +12 -0
- package/dist/permissions.js.map +1 -1
- package/dist/tools/browser-definitions.js +223 -2
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.js +278 -6
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/tools/server-instructions.js +36 -7
- package/dist/tools/server-instructions.js.map +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
107
|
-
' `
|
|
108
|
-
'
|
|
109
|
-
'
|
|
110
|
-
'
|
|
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":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC;;;;;;;;;;GAUG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,sCAAsC,CAAC;AACnE,MAAM,CAAC,MAAM,UAAU,GAAG,wCAAwC,CAAC;AAEnE,MAAM,UAAU,WAAW,CAAC,OAAO,GAAG,OAAO;IAC3C,OAAO,GAAG,YAAY,KAAK,OAAO,MAAM,CAAC;AAC3C,CAAC;AAED;;;;yCAIyC;AACzC,MAAM,CAAC,MAAM,QAAQ,GACnB,gFAAgF,CAAC;AACnF,MAAM,CAAC,MAAM,MAAM,GAAG,4CAA4C,CAAC;AAEnE;;;;mBAImB;AACnB,SAAS,IAAI;IACX,OAAO;QACL,mDAAmD;QACnD,EAAE;QACF,kFAAkF;QAClF,wEAAwE;QACxE,+DAA+D;QAC/D,EAAE;QACF,0DAA0D;QAC1D,EAAE;QACF,kDAAkD;QAClD,qEAAqE;QACrE,+DAA+D;QAC/D,yEAAyE;QACzE,oDAAoD;QACpD,yEAAyE;QACzE,sEAAsE;QACtE,kFAAkF;QAClF,EAAE;QACF,qDAAqD;QACrD,qEAAqE;QACrE,EAAE;QACF,yCAAyC;QACzC,gEAAgE;QAChE,EAAE;QACF,yEAAyE;QACzE,mBAAmB;QACnB,qEAAqE;QACrE,EAAE;QACF,4DAA4D;QAC5D,wEAAwE;QACxE,0CAA0C;QAC1C,EAAE;QACF,wCAAwC;QACxC,EAAE;QACF,8EAA8E;QAC9E,2EAA2E;QAC3E,EAAE;QACF,WAAW;QACX,EAAE;QACF,iDAAiD;QACjD,4EAA4E;QAC5E,2DAA2D;QAC3D,2EAA2E;QAC3E,EAAE;QACF,8EAA8E;QAC9E,EAAE;QACF,uEAAuE;QACvE,oEAAoE;QACpE,EAAE;QACF,yEAAyE;QACzE,mEAAmE;QACnE,sEAAsE;QACtE,iEAAiE;QACjE,kEAAkE;QAClE,gDAAgD;QAChD,yEAAyE;QACzE,sEAAsE;QACtE,8DAA8D;QAC9D,kEAAkE;QAClE,uEAAuE;QACvE,oEAAoE;QACpE,4BAA4B;QAC5B,oEAAoE;QACpE,sEAAsE;QACtE,wEAAwE;QACxE,6DAA6D;QAC7D,iHAAiH;QACjH,2EAA2E;QAC3E,oEAAoE;QACpE,mDAAmD;QACnD,kEAAkE;QAClE,oEAAoE;QACpE,2CAA2C;QAC3C,+DAA+D;QAC/D,uEAAuE;QACvE,
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/agent-instructions/content.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/about.js
CHANGED
|
@@ -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
|
|
553
|
-
|
|
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
|
-
|
|
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
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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
|
-
|
|
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;
|