-
Notifications
You must be signed in to change notification settings - Fork 20
feat(connectors): browser.evaluate connector + owletto submodule bump #828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
51c0fbf
279f66d
a73d964
4293fb9
bfbf80b
9363776
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,120 @@ | ||||||||||
| /** | ||||||||||
| * Browser Evaluate Connector — Owletto for Chrome only. | ||||||||||
| * | ||||||||||
| * Runs on the Owletto Chrome extension, which advertises capability | ||||||||||
| * `browser.debugger`. The extension attaches `chrome.debugger` to a tab, | ||||||||||
| * optionally navigates + waits for a selector, runs the supplied JS via | ||||||||||
| * `Runtime.evaluate`, and emits one event with the JSON-serialised result. | ||||||||||
| * | ||||||||||
| * This is the generic "agent runs JS in a user's signed-in Chrome" primitive | ||||||||||
| * — most bridge connectors (Revolut feed, banking, sites that fingerprint | ||||||||||
| * a managed Chromium) compose on top of `browser.evaluate` rather than | ||||||||||
| * shipping their own connector. The trust boundary is `config.script`: only | ||||||||||
| * the gateway-side connector author should mint it. The extension defaults | ||||||||||
| * to opening a fresh background tab so a compromised gateway / leaked token | ||||||||||
| * can't drive the tab a user is actively using; see executor.js in | ||||||||||
| * owletto-web for the full threat model. | ||||||||||
| * | ||||||||||
| * Cloud-side `sync()` / `execute()` throw — actual work happens in the | ||||||||||
| * extension's service worker (lobu-ai/owletto: apps/chrome/executor.js). | ||||||||||
| */ | ||||||||||
|
|
||||||||||
| import { | ||||||||||
| type ActionResult, | ||||||||||
| type ConnectorDefinition, | ||||||||||
| ConnectorRuntime, | ||||||||||
| type SyncContext, | ||||||||||
| type SyncResult, | ||||||||||
| } from '@lobu/connector-sdk'; | ||||||||||
|
|
||||||||||
| const BRIDGE_ONLY = | ||||||||||
| 'browser.evaluate runs only on a worker advertising capability "browser.debugger" (Owletto for Chrome).'; | ||||||||||
|
|
||||||||||
| export default class BrowserEvaluateConnector extends ConnectorRuntime { | ||||||||||
| readonly definition: ConnectorDefinition = { | ||||||||||
| key: 'browser.evaluate', | ||||||||||
| name: 'Browser Evaluate', | ||||||||||
| description: | ||||||||||
| 'Runs a JS snippet in a page via chrome.debugger and emits the result. The primitive most bridge connectors build on.', | ||||||||||
| version: '0.1.0', | ||||||||||
| faviconDomain: 'google.com', | ||||||||||
| requiredCapability: 'browser.debugger', | ||||||||||
| runtime: { platforms: ['chrome-extension'] }, | ||||||||||
| authSchema: { methods: [{ type: 'none' }] }, | ||||||||||
| feeds: { | ||||||||||
| evaluate: { | ||||||||||
| key: 'evaluate', | ||||||||||
| name: 'Evaluate JS', | ||||||||||
| description: | ||||||||||
| 'Executes a JS expression in the page and emits one event with the JSON-serialised return value.', | ||||||||||
| // `script` is required and gateway-author-supplied. Auto-wire would | ||||||||||
| // insert a feed row with config=NULL and produce a runs-but-fails | ||||||||||
| // loop. Bridge connectors (Revolut, banking, etc.) compose by | ||||||||||
| // creating explicit feed instances per call site. | ||||||||||
| userManaged: true, | ||||||||||
| configSchema: { | ||||||||||
| type: 'object', | ||||||||||
| required: ['script'], | ||||||||||
|
Comment on lines
+45
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because this device connector declares Useful? React with 👍 / 👎. |
||||||||||
| properties: { | ||||||||||
| url: { | ||||||||||
| type: 'string', | ||||||||||
| format: 'uri', | ||||||||||
| description: 'If set, navigate the tab here before evaluating.', | ||||||||||
| }, | ||||||||||
| script: { | ||||||||||
| type: 'string', | ||||||||||
| description: | ||||||||||
| 'JS expression evaluated with Runtime.evaluate(awaitPromise: true). Return value is JSON-serialised — keep it small.', | ||||||||||
| }, | ||||||||||
| wait_for_selector: { | ||||||||||
| type: 'string', | ||||||||||
| description: | ||||||||||
| 'CSS selector to wait for before evaluating (polled every 200ms via Runtime.evaluate).', | ||||||||||
| }, | ||||||||||
| wait_timeout_ms: { | ||||||||||
| type: 'integer', | ||||||||||
| minimum: 100, | ||||||||||
| maximum: 60_000, | ||||||||||
| description: 'Timeout for wait_for_selector. Default 10000.', | ||||||||||
| }, | ||||||||||
| open_in_new_tab: { | ||||||||||
| type: 'boolean', | ||||||||||
| description: | ||||||||||
| 'Open a fresh background tab instead of driving the active tab. DEFAULT TRUE — opt out only when you specifically need the user-active tab.', | ||||||||||
| }, | ||||||||||
| close_tab_after: { | ||||||||||
| type: 'boolean', | ||||||||||
| description: | ||||||||||
| 'Close the tab when the run completes. Defaults to true when open_in_new_tab is true.', | ||||||||||
| }, | ||||||||||
| }, | ||||||||||
| }, | ||||||||||
| eventKinds: { | ||||||||||
| browser_evaluate: { | ||||||||||
| description: | ||||||||||
| 'One event per run with the JSON-serialised Runtime.evaluate result.', | ||||||||||
| metadataSchema: { | ||||||||||
| type: 'object', | ||||||||||
| required: ['source', 'origin_id'], | ||||||||||
| properties: { | ||||||||||
| source: { type: 'string', const: 'browser_evaluate' }, | ||||||||||
| origin_id: { type: 'string' }, | ||||||||||
| url: { type: 'string' }, | ||||||||||
| title: { type: 'string' }, | ||||||||||
| tab_id: { type: 'integer' }, | ||||||||||
| }, | ||||||||||
| }, | ||||||||||
| }, | ||||||||||
| }, | ||||||||||
| }, | ||||||||||
| }, | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| async sync(_ctx: SyncContext): Promise<SyncResult> { | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win Drop the unused Use Suggested change- async sync(_ctx: SyncContext): Promise<SyncResult> {
+ async sync(): Promise<SyncResult> {
throw new Error(BRIDGE_ONLY);
}As per coding guidelines, “When fixing unused-parameter errors, delete the parameter rather than prefixing with 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| throw new Error(BRIDGE_ONLY); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| async execute(): Promise<ActionResult> { | ||||||||||
| throw new Error(BRIDGE_ONLY); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| /** | ||
| * Browser Fill Form Connector — Owletto for Chrome only. | ||
| * | ||
| * Thin wrapper around browser.evaluate that bakes in a "fill these inputs | ||
| * by selector and dispatch the right input/change events" script. | ||
| * | ||
| * The extension's executor branch for `browser.fill_form` substitutes the | ||
| * canonical fill-form script when this connector_key is dispatched. The | ||
| * server-side definition just exposes the URL + fields config to the | ||
| * admin UI. | ||
| * | ||
| * Cloud-side `sync()` / `execute()` throw — actual work happens in the | ||
| * extension's service worker (lobu-ai/owletto: apps/chrome/executor.js). | ||
| */ | ||
|
|
||
| import { | ||
| type ActionResult, | ||
| type ConnectorDefinition, | ||
| ConnectorRuntime, | ||
| type SyncContext, | ||
| type SyncResult, | ||
| } from '@lobu/connector-sdk'; | ||
|
|
||
| const BRIDGE_ONLY = | ||
| 'browser.fill_form runs only on a worker advertising capability "browser.debugger" (Owletto for Chrome).'; | ||
|
|
||
| export default class BrowserFillFormConnector extends ConnectorRuntime { | ||
| readonly definition: ConnectorDefinition = { | ||
| key: 'browser.fill_form', | ||
| name: 'Browser Fill Form', | ||
| description: | ||
| 'Fills inputs on a page by CSS selector and dispatches input/change events. Returns the filled field count.', | ||
| version: '0.1.0', | ||
| faviconDomain: 'google.com', | ||
| requiredCapability: 'browser.debugger', | ||
| runtime: { platforms: ['chrome-extension'] }, | ||
| authSchema: { methods: [{ type: 'none' }] }, | ||
| feeds: { | ||
| fill: { | ||
| key: 'fill', | ||
| name: 'Fill form', | ||
| description: | ||
| 'Sets values on input/textarea/select elements matched by CSS selector.', | ||
| // Required url + fields; instances are minted by composing bridge | ||
| // connectors, not auto-wired by device-reconcile. | ||
| userManaged: true, | ||
| configSchema: { | ||
| type: 'object', | ||
| required: ['url', 'fields'], | ||
| properties: { | ||
| url: { | ||
| type: 'string', | ||
| format: 'uri', | ||
| description: 'Page to load before filling.', | ||
| }, | ||
| fields: { | ||
| type: 'object', | ||
| description: | ||
| 'Map of CSS selector → value to set. e.g. { "#email": "x@y.com", "#submit": "click" } — the literal string "click" triggers a click instead of a value set.', | ||
| additionalProperties: { type: 'string' }, | ||
| }, | ||
| wait_for_selector: { | ||
| type: 'string', | ||
| description: | ||
| 'CSS selector to wait for before filling (defaults to the first key of fields).', | ||
| }, | ||
| wait_timeout_ms: { | ||
| type: 'integer', | ||
| minimum: 100, | ||
| maximum: 60_000, | ||
| }, | ||
| submit_selector: { | ||
| type: 'string', | ||
| description: | ||
| 'Optional selector to click after filling all fields (e.g. "button[type=submit]").', | ||
| }, | ||
| }, | ||
| }, | ||
| eventKinds: { | ||
| form_filled: { | ||
| description: | ||
| 'One event per run with the count of fields filled + whether submit was clicked.', | ||
| metadataSchema: { | ||
| type: 'object', | ||
| required: ['source', 'origin_id', 'url', 'filled_count'], | ||
| properties: { | ||
| source: { type: 'string', const: 'browser_fill_form' }, | ||
| origin_id: { type: 'string' }, | ||
| url: { type: 'string', format: 'uri' }, | ||
| filled_count: { type: 'integer' }, | ||
| submitted: { type: 'boolean' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| async sync(_ctx: SyncContext): Promise<SyncResult> { | ||
| throw new Error(BRIDGE_ONLY); | ||
| } | ||
|
Comment on lines
+100
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the unused
Proposed fix- async sync(_ctx: SyncContext): Promise<SyncResult> {
+ async sync(): Promise<SyncResult> {
throw new Error(BRIDGE_ONLY);
}As per coding guidelines, “When fixing unused-parameter errors, delete the parameter rather than prefixing with 🤖 Prompt for AI Agents |
||
|
|
||
| async execute(): Promise<ActionResult> { | ||
| throw new Error(BRIDGE_ONLY); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path validation breaks on Windows due to hardcoded forward slash.
The check uses a hardcoded
/in the template literal, butresolve()returns platform-specific separators. On Windows,dirisC:\app\connectorsandfilePathisC:\app\connectors\browser\evaluate.ts, so the check becomes!filePath.startsWith("C:\app\connectors/")(mixed separators) which always fails.🔧 Proposed fix using path.sep for cross-platform compatibility
Alternatively, use
path.join()for clarity:📝 Committable suggestion
🤖 Prompt for AI Agents