@akshayram1/omnibrowser-agent 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Akshay Chame
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # omnibrowser-agent
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
+ [![Version](https://img.shields.io/badge/version-0.2.0-green.svg)](package.json)
5
+
6
+ Local-first open-source browser AI operator using in-browser planning and page actions.
7
+
8
+ ## Why this project
9
+
10
+ - Privacy-first: run agent logic in browser
11
+ - No per-request cloud token costs
12
+ - Dual delivery:
13
+ - Browser extension mode
14
+ - Embeddable library mode for web apps
15
+ - Hybrid control modes:
16
+ - Autonomous
17
+ - Human-approved
18
+
19
+ ## Stack
20
+
21
+ - MV3 browser extension runtime
22
+ - TypeScript + esbuild
23
+ - Pluggable local WebLLM bridge
24
+
25
+ ## Project structure
26
+
27
+ - `src/background` session orchestration
28
+ - `src/content` page observer/planner/executor
29
+ - `src/popup` control panel
30
+ - `src/lib` embeddable runtime API
31
+ - `src/shared` contracts and safety
32
+
33
+ ## Quick start
34
+
35
+ 1. Install dependencies:
36
+
37
+ ```bash
38
+ npm install
39
+ ```
40
+
41
+ 2. Build extension:
42
+
43
+ ```bash
44
+ npm run build
45
+ ```
46
+
47
+ 3. Load extension in Chromium:
48
+
49
+ - Open `chrome://extensions`
50
+ - Enable Developer Mode
51
+ - Click **Load unpacked**
52
+ - Select `dist`
53
+
54
+ ## How to use
55
+
56
+ 1. Open a target website tab
57
+ 2. Open extension popup
58
+ 3. Enter goal (for example: `search contact John Doe in CRM and open profile`)
59
+ 4. Select mode/planner
60
+ 5. Click Start
61
+ 6. If mode is `human-approved`, click **Approve pending action** on review steps
62
+
63
+ ## Use as a web library
64
+
65
+ ```ts
66
+ import { createBrowserAgent } from "@akshaychame/omnibrowser-agent";
67
+
68
+ const agent = createBrowserAgent({
69
+ goal: "Open CRM and find customer John Smith",
70
+ mode: "human-approved",
71
+ planner: { kind: "heuristic" }
72
+ }, {
73
+ onStep: (result) => console.log(result.message),
74
+ onApprovalRequired: (action) => console.log("Needs approval:", action),
75
+ onDone: (result) => console.log("Done:", result.message),
76
+ onMaxStepsReached: (session) => console.log("Max steps hit", session.history)
77
+ });
78
+
79
+ await agent.start();
80
+
81
+ // Resume after approval:
82
+ await agent.resume();
83
+
84
+ // Inspect state at any time:
85
+ console.log(agent.isRunning, agent.hasPendingAction);
86
+
87
+ // Stop at any time:
88
+ agent.stop();
89
+ ```
90
+
91
+ ### Supported actions
92
+
93
+ | Action | Description |
94
+ |------------|------------------------------------------|
95
+ | `click` | Click an element by CSS selector |
96
+ | `type` | Type text into an input or textarea |
97
+ | `navigate` | Navigate to a URL |
98
+ | `extract` | Extract text from an element |
99
+ | `scroll` | Scroll a container or the page |
100
+ | `focus` | Focus an element (useful for dropdowns) |
101
+ | `wait` | Pause for a given number of milliseconds |
102
+ | `done` | Signal task completion |
103
+
104
+ ### AbortSignal support
105
+
106
+ ```ts
107
+ const controller = new AbortController();
108
+ const agent = createBrowserAgent({ goal: "...", signal: controller.signal });
109
+ agent.start();
110
+
111
+ // Cancel from outside:
112
+ controller.abort();
113
+ ```
114
+
115
+ See full integration guide in `docs/EMBEDDING.md`.
116
+
117
+ ## Example site (embedded usage)
118
+
119
+ 1. Build library assets:
120
+
121
+ ```bash
122
+ npm run build
123
+ ```
124
+
125
+ 2. Serve the repository root (required for browser ESM import paths):
126
+
127
+ ```bash
128
+ python3 -m http.server 4173
129
+ ```
130
+
131
+ 3. Open:
132
+
133
+ - `http://localhost:4173/examples/simple-site/`
134
+
135
+ The example uses `createBrowserAgent` from `dist/lib.js` and includes UI buttons for start/approve/stop.
136
+ It is preconfigured to use `webllm` planner mode and loads `@mlc-ai/web-llm` from CDN in the example page.
137
+
138
+ ## Changelog
139
+
140
+ ### v0.2.0
141
+
142
+ - **New actions**: `scroll` and `focus`
143
+ - **Smarter safety**: risk assessment now checks element label/text rather than CSS selector strings
144
+ - **Improved heuristic planner**: handles navigate, fill, click, and search goal patterns with regex matching
145
+ - **Better page observation**: filters hidden/invisible elements, includes `placeholder` in candidate data, captures up to 60 candidates
146
+ - **Library API**: added `resume()`, `isRunning` and `hasPendingAction` getters, `onMaxStepsReached` event, and `AbortSignal` support
147
+ - **Executor**: uses `InputEvent` for proper framework compatibility, added keyboard event dispatch
148
+ - **License**: added author name
149
+
150
+ ### v0.1.0
151
+
152
+ - Extension runtime loop
153
+ - Shared action contracts
154
+ - Heuristic + WebLLM planner switch
155
+ - Human-approved mode
156
+
157
+ ## Notes
158
+
159
+ - Local inference has no API usage charges, but uses device CPU/GPU/memory.
160
+ - `webllm` mode expects a local bridge implementation attached to `window.__browserAgentWebLLM`.
161
+
162
+ ## Roadmap
163
+
164
+ See [docs/ROADMAP.md](docs/ROADMAP.md).
165
+
166
+ ## License
167
+
168
+ MIT © Akshay Chame
@@ -0,0 +1,84 @@
1
+ // src/background/index.ts
2
+ var sessions = /* @__PURE__ */ new Map();
3
+ function makeSession(tabId, goal, mode, plannerKind) {
4
+ return {
5
+ id: crypto.randomUUID(),
6
+ tabId,
7
+ goal,
8
+ mode,
9
+ planner: {
10
+ kind: plannerKind
11
+ },
12
+ history: [],
13
+ isRunning: true
14
+ };
15
+ }
16
+ async function tick(tabId) {
17
+ const session = sessions.get(tabId);
18
+ if (!session || !session.isRunning) {
19
+ return;
20
+ }
21
+ const result = await chrome.tabs.sendMessage(tabId, {
22
+ type: "AGENT_TICK",
23
+ session
24
+ });
25
+ session.history.push(result.message);
26
+ if (result.status === "needs_approval") {
27
+ session.pendingAction = result.action;
28
+ session.isRunning = false;
29
+ return;
30
+ }
31
+ session.pendingAction = void 0;
32
+ if (["done", "blocked", "error"].includes(result.status)) {
33
+ session.isRunning = false;
34
+ return;
35
+ }
36
+ setTimeout(() => tick(tabId), 600);
37
+ }
38
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
39
+ if (message.type === "START_AGENT") {
40
+ const session = makeSession(message.tabId, message.goal, message.mode, message.planner);
41
+ sessions.set(message.tabId, session);
42
+ tick(message.tabId).catch((error) => {
43
+ const failed = sessions.get(message.tabId);
44
+ if (failed) {
45
+ failed.history.push(`Error: ${String(error)}`);
46
+ failed.isRunning = false;
47
+ }
48
+ });
49
+ sendResponse({ ok: true });
50
+ return true;
51
+ }
52
+ if (message.type === "APPROVE_ACTION") {
53
+ const session = sessions.get(message.tabId);
54
+ if (!session) {
55
+ sendResponse({ ok: false, error: "No active session" });
56
+ return true;
57
+ }
58
+ session.isRunning = true;
59
+ tick(message.tabId).catch((error) => {
60
+ session.history.push(`Error: ${String(error)}`);
61
+ session.isRunning = false;
62
+ });
63
+ sendResponse({ ok: true });
64
+ return true;
65
+ }
66
+ if (message.type === "STOP_AGENT") {
67
+ const session = sessions.get(message.tabId);
68
+ if (session) {
69
+ session.isRunning = false;
70
+ }
71
+ chrome.tabs.sendMessage(message.tabId, { type: "AGENT_STOP" }).catch(() => void 0);
72
+ sendResponse({ ok: true });
73
+ return true;
74
+ }
75
+ if (message.type === "GET_STATUS") {
76
+ const lines = Array.from(sessions.values()).map(
77
+ (session) => `${session.isRunning ? "RUNNING" : "IDLE"} ${session.tabId}: ${session.goal.slice(0, 45)}${session.goal.length > 45 ? "..." : ""}`
78
+ );
79
+ sendResponse({ status: lines.length > 0 ? lines.join("\n") : "Idle" });
80
+ return true;
81
+ }
82
+ return false;
83
+ });
84
+ //# sourceMappingURL=background.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/background/index.ts"],
4
+ "sourcesContent": ["import type { AgentMode, AgentSession, PlannerKind } from \"../shared/contracts\";\n\nconst sessions = new Map<number, AgentSession>();\n\nfunction makeSession(tabId: number, goal: string, mode: AgentMode, plannerKind: PlannerKind): AgentSession {\n return {\n id: crypto.randomUUID(),\n tabId: tabId,\n goal,\n mode,\n planner: {\n kind: plannerKind\n },\n history: [],\n isRunning: true\n };\n}\n\nasync function tick(tabId: number) {\n const session = sessions.get(tabId);\n if (!session || !session.isRunning) {\n return;\n }\n\n const result = await chrome.tabs.sendMessage(tabId, {\n type: \"AGENT_TICK\",\n session\n });\n\n session.history.push(result.message);\n\n if (result.status === \"needs_approval\") {\n session.pendingAction = result.action;\n session.isRunning = false;\n return;\n }\n\n session.pendingAction = undefined;\n\n if ([\"done\", \"blocked\", \"error\"].includes(result.status)) {\n session.isRunning = false;\n return;\n }\n\n setTimeout(() => tick(tabId), 600);\n}\n\nchrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {\n if (message.type === \"START_AGENT\") {\n const session = makeSession(message.tabId, message.goal, message.mode, message.planner);\n sessions.set(message.tabId, session);\n tick(message.tabId).catch((error) => {\n const failed = sessions.get(message.tabId);\n if (failed) {\n failed.history.push(`Error: ${String(error)}`);\n failed.isRunning = false;\n }\n });\n sendResponse({ ok: true });\n return true;\n }\n\n if (message.type === \"APPROVE_ACTION\") {\n const session = sessions.get(message.tabId);\n if (!session) {\n sendResponse({ ok: false, error: \"No active session\" });\n return true;\n }\n\n session.isRunning = true;\n tick(message.tabId).catch((error) => {\n session.history.push(`Error: ${String(error)}`);\n session.isRunning = false;\n });\n sendResponse({ ok: true });\n return true;\n }\n\n if (message.type === \"STOP_AGENT\") {\n const session = sessions.get(message.tabId);\n if (session) {\n session.isRunning = false;\n }\n chrome.tabs.sendMessage(message.tabId, { type: \"AGENT_STOP\" }).catch(() => undefined);\n sendResponse({ ok: true });\n return true;\n }\n\n if (message.type === \"GET_STATUS\") {\n const lines = Array.from(sessions.values()).map(\n (session) =>\n `${session.isRunning ? \"RUNNING\" : \"IDLE\"} ${session.tabId}: ${session.goal.slice(0, 45)}${session.goal.length > 45 ? \"...\" : \"\"}`\n );\n\n sendResponse({ status: lines.length > 0 ? lines.join(\"\\n\") : \"Idle\" });\n return true;\n }\n\n return false;\n});\n"],
5
+ "mappings": ";AAEA,IAAM,WAAW,oBAAI,IAA0B;AAE/C,SAAS,YAAY,OAAe,MAAc,MAAiB,aAAwC;AACzG,SAAO;AAAA,IACL,IAAI,OAAO,WAAW;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,SAAS,CAAC;AAAA,IACV,WAAW;AAAA,EACb;AACF;AAEA,eAAe,KAAK,OAAe;AACjC,QAAM,UAAU,SAAS,IAAI,KAAK;AAClC,MAAI,CAAC,WAAW,CAAC,QAAQ,WAAW;AAClC;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,OAAO,KAAK,YAAY,OAAO;AAAA,IAClD,MAAM;AAAA,IACN;AAAA,EACF,CAAC;AAED,UAAQ,QAAQ,KAAK,OAAO,OAAO;AAEnC,MAAI,OAAO,WAAW,kBAAkB;AACtC,YAAQ,gBAAgB,OAAO;AAC/B,YAAQ,YAAY;AACpB;AAAA,EACF;AAEA,UAAQ,gBAAgB;AAExB,MAAI,CAAC,QAAQ,WAAW,OAAO,EAAE,SAAS,OAAO,MAAM,GAAG;AACxD,YAAQ,YAAY;AACpB;AAAA,EACF;AAEA,aAAW,MAAM,KAAK,KAAK,GAAG,GAAG;AACnC;AAEA,OAAO,QAAQ,UAAU,YAAY,CAAC,SAAS,SAAS,iBAAiB;AACvE,MAAI,QAAQ,SAAS,eAAe;AAClC,UAAM,UAAU,YAAY,QAAQ,OAAO,QAAQ,MAAM,QAAQ,MAAM,QAAQ,OAAO;AACtF,aAAS,IAAI,QAAQ,OAAO,OAAO;AACnC,SAAK,QAAQ,KAAK,EAAE,MAAM,CAAC,UAAU;AACnC,YAAM,SAAS,SAAS,IAAI,QAAQ,KAAK;AACzC,UAAI,QAAQ;AACV,eAAO,QAAQ,KAAK,UAAU,OAAO,KAAK,CAAC,EAAE;AAC7C,eAAO,YAAY;AAAA,MACrB;AAAA,IACF,CAAC;AACD,iBAAa,EAAE,IAAI,KAAK,CAAC;AACzB,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,SAAS,kBAAkB;AACrC,UAAM,UAAU,SAAS,IAAI,QAAQ,KAAK;AAC1C,QAAI,CAAC,SAAS;AACZ,mBAAa,EAAE,IAAI,OAAO,OAAO,oBAAoB,CAAC;AACtD,aAAO;AAAA,IACT;AAEA,YAAQ,YAAY;AACpB,SAAK,QAAQ,KAAK,EAAE,MAAM,CAAC,UAAU;AACnC,cAAQ,QAAQ,KAAK,UAAU,OAAO,KAAK,CAAC,EAAE;AAC9C,cAAQ,YAAY;AAAA,IACtB,CAAC;AACD,iBAAa,EAAE,IAAI,KAAK,CAAC;AACzB,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,SAAS,cAAc;AACjC,UAAM,UAAU,SAAS,IAAI,QAAQ,KAAK;AAC1C,QAAI,SAAS;AACX,cAAQ,YAAY;AAAA,IACtB;AACA,WAAO,KAAK,YAAY,QAAQ,OAAO,EAAE,MAAM,aAAa,CAAC,EAAE,MAAM,MAAM,MAAS;AACpF,iBAAa,EAAE,IAAI,KAAK,CAAC;AACzB,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,SAAS,cAAc;AACjC,UAAM,QAAQ,MAAM,KAAK,SAAS,OAAO,CAAC,EAAE;AAAA,MAC1C,CAAC,YACC,GAAG,QAAQ,YAAY,YAAY,MAAM,IAAI,QAAQ,KAAK,KAAK,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG,QAAQ,KAAK,SAAS,KAAK,QAAQ,EAAE;AAAA,IACpI;AAEA,iBAAa,EAAE,QAAQ,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC;AACrE,WAAO;AAAA,EACT;AAEA,SAAO;AACT,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,278 @@
1
+ // src/shared/safety.ts
2
+ var RISKY_KEYWORDS = /\b(delete|remove|pay|purchase|submit|confirm|checkout|transfer|withdraw|send)\b/i;
3
+ function elementTextRisky(text) {
4
+ return text != null && RISKY_KEYWORDS.test(text);
5
+ }
6
+ function assessRisk(action) {
7
+ switch (action.type) {
8
+ case "navigate": {
9
+ try {
10
+ const next = new URL(action.url);
11
+ if (!["http:", "https:"].includes(next.protocol)) {
12
+ return "blocked";
13
+ }
14
+ } catch {
15
+ return "blocked";
16
+ }
17
+ return "safe";
18
+ }
19
+ case "click":
20
+ return elementTextRisky(action.label) ? "review" : "safe";
21
+ case "type":
22
+ return elementTextRisky(action.label) ? "review" : "safe";
23
+ case "focus":
24
+ case "scroll":
25
+ case "wait":
26
+ return "safe";
27
+ case "extract":
28
+ return "review";
29
+ case "done":
30
+ return "safe";
31
+ default:
32
+ return "review";
33
+ }
34
+ }
35
+
36
+ // src/content/executor.ts
37
+ function mustFind(selector) {
38
+ const node = document.querySelector(selector);
39
+ if (!(node instanceof HTMLElement)) {
40
+ throw new Error(`Selector not found: ${selector}`);
41
+ }
42
+ return node;
43
+ }
44
+ function dispatchInputEvents(el) {
45
+ el.dispatchEvent(new InputEvent("input", { bubbles: true, cancelable: true }));
46
+ el.dispatchEvent(new Event("change", { bubbles: true }));
47
+ }
48
+ async function executeAction(action) {
49
+ switch (action.type) {
50
+ case "click": {
51
+ mustFind(action.selector).click();
52
+ return `Clicked ${action.selector}`;
53
+ }
54
+ case "type": {
55
+ const input = mustFind(action.selector);
56
+ input.focus();
57
+ if (action.clearFirst) {
58
+ input.value = "";
59
+ dispatchInputEvents(input);
60
+ }
61
+ input.value = `${input.value}${action.text}`;
62
+ dispatchInputEvents(input);
63
+ return `Typed into ${action.selector}`;
64
+ }
65
+ case "navigate": {
66
+ window.location.href = action.url;
67
+ return `Navigated to ${action.url}`;
68
+ }
69
+ case "extract": {
70
+ const value = mustFind(action.selector).innerText.trim();
71
+ return `${action.label}: ${value}`;
72
+ }
73
+ case "scroll": {
74
+ const target = action.selector ? mustFind(action.selector) : document.documentElement;
75
+ target.scrollBy({ top: action.deltaY, behavior: "smooth" });
76
+ return `Scrolled ${action.deltaY > 0 ? "down" : "up"} ${Math.abs(action.deltaY)}px`;
77
+ }
78
+ case "focus": {
79
+ mustFind(action.selector).focus();
80
+ return `Focused ${action.selector}`;
81
+ }
82
+ case "wait": {
83
+ await new Promise((resolve) => setTimeout(resolve, action.ms));
84
+ return `Waited ${action.ms}ms`;
85
+ }
86
+ case "done": {
87
+ return action.reason;
88
+ }
89
+ default:
90
+ return "No-op";
91
+ }
92
+ }
93
+
94
+ // src/content/pageObserver.ts
95
+ var CANDIDATE_SELECTOR = "a,button,input,textarea,select,[role='button'],[role='link'],[contenteditable='true']";
96
+ var MAX_CANDIDATES = 60;
97
+ function cssPath(element) {
98
+ if (!(element instanceof HTMLElement)) {
99
+ return element.tagName.toLowerCase();
100
+ }
101
+ if (element.id) {
102
+ return `#${CSS.escape(element.id)}`;
103
+ }
104
+ const parts = [];
105
+ let current = element;
106
+ while (current && parts.length < 4) {
107
+ let part = current.tagName.toLowerCase();
108
+ if (current.classList.length > 0) {
109
+ part += `.${Array.from(current.classList).slice(0, 2).map(CSS.escape).join(".")}`;
110
+ }
111
+ const parent = current.parentElement;
112
+ if (parent) {
113
+ const siblings = Array.from(parent.children).filter((s) => s.tagName === current.tagName);
114
+ if (siblings.length > 1) {
115
+ const index = siblings.indexOf(current) + 1;
116
+ part += `:nth-of-type(${index})`;
117
+ }
118
+ }
119
+ parts.unshift(part);
120
+ current = parent;
121
+ }
122
+ return parts.join(" > ");
123
+ }
124
+ function isVisible(el) {
125
+ if (el.offsetParent === null && el.tagName !== "BODY") {
126
+ return false;
127
+ }
128
+ const style = window.getComputedStyle(el);
129
+ return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
130
+ }
131
+ function collectSnapshot() {
132
+ const nodes = Array.from(
133
+ document.querySelectorAll(CANDIDATE_SELECTOR)
134
+ ).filter(isVisible).slice(0, MAX_CANDIDATES);
135
+ const candidates = nodes.map((node) => {
136
+ const placeholder = node.placeholder?.trim() || node.getAttribute("placeholder")?.trim();
137
+ return {
138
+ selector: cssPath(node),
139
+ role: node.getAttribute("role") ?? node.tagName.toLowerCase(),
140
+ text: (node.innerText || node.getAttribute("aria-label") || node.getAttribute("name") || "").trim().slice(0, 120),
141
+ placeholder: placeholder || void 0
142
+ };
143
+ });
144
+ const textPreview = document.body.innerText.replace(/\s+/g, " ").trim().slice(0, 1500);
145
+ return {
146
+ url: window.location.href,
147
+ title: document.title,
148
+ textPreview,
149
+ candidates
150
+ };
151
+ }
152
+
153
+ // src/content/planner.ts
154
+ var URL_PATTERN = /(?:go to|navigate to|open)\s+(https?:\/\/\S+)/i;
155
+ var SEARCH_PATTERN = /search(?:\s+for)?\s+(.+)/i;
156
+ var FILL_PATTERN = /(?:fill|type|enter)\s+"?([^"]+)"?\s+(?:in(?:to)?|for|on)\s+(.+)/i;
157
+ var CLICK_PATTERN = /click(?:\s+(?:on|the))?\s+(.+)/i;
158
+ function findByText(candidates, text) {
159
+ const lower = text.toLowerCase();
160
+ return candidates.find(
161
+ (c) => c.text.toLowerCase().includes(lower) || (c.placeholder?.toLowerCase().includes(lower) ?? false)
162
+ );
163
+ }
164
+ function findInput(candidates) {
165
+ return candidates.find(
166
+ (c) => c.role === "input" || c.role === "textarea" || c.selector.includes("input") || c.selector.includes("textarea")
167
+ );
168
+ }
169
+ function findButton(candidates) {
170
+ return candidates.find(
171
+ (c) => c.role === "button" || c.role === "a" || c.selector.includes("button") || c.selector.includes("a")
172
+ );
173
+ }
174
+ function heuristicPlan(input) {
175
+ const { goal, snapshot, history } = input;
176
+ const navMatch = goal.match(URL_PATTERN);
177
+ if (navMatch) {
178
+ return { type: "navigate", url: navMatch[1] };
179
+ }
180
+ const fillMatch = goal.match(FILL_PATTERN);
181
+ if (fillMatch) {
182
+ const [, text, fieldHint] = fillMatch;
183
+ const target = findByText(snapshot.candidates, fieldHint) ?? findInput(snapshot.candidates);
184
+ if (target) {
185
+ return { type: "type", selector: target.selector, text, clearFirst: true, label: target.text || target.placeholder };
186
+ }
187
+ }
188
+ const searchMatch = goal.match(SEARCH_PATTERN);
189
+ if (searchMatch) {
190
+ const input2 = findInput(snapshot.candidates);
191
+ if (input2) {
192
+ return { type: "type", selector: input2.selector, text: searchMatch[1].trim(), clearFirst: true, label: input2.text || input2.placeholder };
193
+ }
194
+ }
195
+ const clickMatch = goal.match(CLICK_PATTERN);
196
+ if (clickMatch) {
197
+ const target = findByText(snapshot.candidates, clickMatch[1].trim());
198
+ if (target) {
199
+ return { type: "click", selector: target.selector, label: target.text };
200
+ }
201
+ }
202
+ const firstInput = findInput(snapshot.candidates);
203
+ const firstButton = findButton(snapshot.candidates);
204
+ if (firstInput && !history.some((h) => h.startsWith("Typed"))) {
205
+ const searchTerm = goal.replace(/.*(?:search|find|look up)\s+/i, "").trim();
206
+ return { type: "type", selector: firstInput.selector, text: searchTerm, clearFirst: true, label: firstInput.text || firstInput.placeholder };
207
+ }
208
+ if (firstButton && !history.some((h) => h.startsWith("Clicked"))) {
209
+ return { type: "click", selector: firstButton.selector, label: firstButton.text };
210
+ }
211
+ return { type: "done", reason: "No further heuristic actions available" };
212
+ }
213
+ async function planNextAction(config, input) {
214
+ if (config.kind === "heuristic") {
215
+ return heuristicPlan(input);
216
+ }
217
+ const bridge = window.__browserAgentWebLLM;
218
+ if (!bridge) {
219
+ return {
220
+ type: "done",
221
+ reason: "WebLLM bridge is not configured. Use heuristic mode or wire a local bridge implementation."
222
+ };
223
+ }
224
+ return bridge.plan(input, config.modelId);
225
+ }
226
+
227
+ // src/content/index.ts
228
+ var stopped = false;
229
+ async function runTick(session) {
230
+ const snapshot = collectSnapshot();
231
+ const action = await planNextAction(session.planner, {
232
+ goal: session.goal,
233
+ snapshot,
234
+ history: session.history
235
+ });
236
+ const risk = assessRisk(action);
237
+ if (risk === "blocked") {
238
+ return { status: "blocked", action, message: `Blocked action: ${JSON.stringify(action)}` };
239
+ }
240
+ if (session.mode === "human-approved" && risk === "review") {
241
+ return { status: "needs_approval", action, message: `Approval needed for ${action.type}` };
242
+ }
243
+ if (action.type === "done") {
244
+ return { status: "done", action, message: action.reason };
245
+ }
246
+ const message = await executeAction(action);
247
+ return { status: "executed", action, message };
248
+ }
249
+ async function executePendingAction(session) {
250
+ if (!session.pendingAction) {
251
+ return { status: "error", message: "No pending action to approve" };
252
+ }
253
+ const message = await executeAction(session.pendingAction);
254
+ return { status: "executed", action: session.pendingAction, message };
255
+ }
256
+ chrome.runtime.onMessage.addListener((command, _sender, sendResponse) => {
257
+ if (command.type === "AGENT_STOP") {
258
+ stopped = true;
259
+ sendResponse({ status: "done", message: "Stopped by user" });
260
+ return true;
261
+ }
262
+ if (command.type !== "AGENT_TICK") {
263
+ return false;
264
+ }
265
+ const session = command.session;
266
+ const exec = session.pendingAction ? executePendingAction(session) : runTick(session);
267
+ exec.then((result) => {
268
+ if (stopped) {
269
+ sendResponse({ status: "done", message: "Stopped" });
270
+ return;
271
+ }
272
+ sendResponse(result);
273
+ }).catch((error) => {
274
+ sendResponse({ status: "error", message: String(error) });
275
+ });
276
+ return true;
277
+ });
278
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/shared/safety.ts", "../src/content/executor.ts", "../src/content/pageObserver.ts", "../src/content/planner.ts", "../src/content/index.ts"],
4
+ "sourcesContent": ["import type { AgentAction, RiskLevel } from \"./contracts\";\n\nconst RISKY_KEYWORDS = /\\b(delete|remove|pay|purchase|submit|confirm|checkout|transfer|withdraw|send)\\b/i;\n\nfunction elementTextRisky(text?: string): boolean {\n return text != null && RISKY_KEYWORDS.test(text);\n}\n\nexport function assessRisk(action: AgentAction): RiskLevel {\n switch (action.type) {\n case \"navigate\": {\n try {\n const next = new URL(action.url);\n if (![\"http:\", \"https:\"].includes(next.protocol)) {\n return \"blocked\";\n }\n } catch {\n return \"blocked\";\n }\n return \"safe\";\n }\n case \"click\":\n return elementTextRisky(action.label) ? \"review\" : \"safe\";\n case \"type\":\n return elementTextRisky(action.label) ? \"review\" : \"safe\";\n case \"focus\":\n case \"scroll\":\n case \"wait\":\n return \"safe\";\n case \"extract\":\n return \"review\";\n case \"done\":\n return \"safe\";\n default:\n return \"review\";\n }\n}\n", "import type { AgentAction } from \"../shared/contracts\";\n\nfunction mustFind(selector: string): HTMLElement {\n const node = document.querySelector(selector);\n if (!(node instanceof HTMLElement)) {\n throw new Error(`Selector not found: ${selector}`);\n }\n return node;\n}\n\nfunction dispatchInputEvents(el: HTMLInputElement | HTMLTextAreaElement): void {\n el.dispatchEvent(new InputEvent(\"input\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true }));\n}\n\nexport async function executeAction(action: AgentAction): Promise<string> {\n switch (action.type) {\n case \"click\": {\n mustFind(action.selector).click();\n return `Clicked ${action.selector}`;\n }\n case \"type\": {\n const input = mustFind(action.selector) as HTMLInputElement | HTMLTextAreaElement;\n input.focus();\n if (action.clearFirst) {\n input.value = \"\";\n dispatchInputEvents(input);\n }\n input.value = `${input.value}${action.text}`;\n dispatchInputEvents(input);\n return `Typed into ${action.selector}`;\n }\n case \"navigate\": {\n window.location.href = action.url;\n return `Navigated to ${action.url}`;\n }\n case \"extract\": {\n const value = mustFind(action.selector).innerText.trim();\n return `${action.label}: ${value}`;\n }\n case \"scroll\": {\n const target = action.selector ? mustFind(action.selector) : document.documentElement;\n target.scrollBy({ top: action.deltaY, behavior: \"smooth\" });\n return `Scrolled ${action.deltaY > 0 ? \"down\" : \"up\"} ${Math.abs(action.deltaY)}px`;\n }\n case \"focus\": {\n mustFind(action.selector).focus();\n return `Focused ${action.selector}`;\n }\n case \"wait\": {\n await new Promise((resolve) => setTimeout(resolve, action.ms));\n return `Waited ${action.ms}ms`;\n }\n case \"done\": {\n return action.reason;\n }\n default:\n return \"No-op\";\n }\n}\n", "import type { CandidateElement, PageSnapshot } from \"../shared/contracts\";\n\nconst CANDIDATE_SELECTOR =\n \"a,button,input,textarea,select,[role='button'],[role='link'],[contenteditable='true']\";\n\nconst MAX_CANDIDATES = 60;\n\nfunction cssPath(element: Element): string {\n if (!(element instanceof HTMLElement)) {\n return element.tagName.toLowerCase();\n }\n\n if (element.id) {\n return `#${CSS.escape(element.id)}`;\n }\n\n const parts: string[] = [];\n let current: HTMLElement | null = element;\n while (current && parts.length < 4) {\n let part = current.tagName.toLowerCase();\n if (current.classList.length > 0) {\n part += `.${Array.from(current.classList).slice(0, 2).map(CSS.escape).join(\".\")}`;\n }\n const parent: HTMLElement | null = current.parentElement;\n if (parent) {\n const siblings = Array.from(parent.children).filter((s: Element) => s.tagName === current!.tagName);\n if (siblings.length > 1) {\n const index = siblings.indexOf(current) + 1;\n part += `:nth-of-type(${index})`;\n }\n }\n parts.unshift(part);\n current = parent;\n }\n return parts.join(\" > \");\n}\n\nfunction isVisible(el: HTMLElement): boolean {\n if (el.offsetParent === null && el.tagName !== \"BODY\") {\n return false;\n }\n const style = window.getComputedStyle(el);\n return style.display !== \"none\" && style.visibility !== \"hidden\" && style.opacity !== \"0\";\n}\n\nexport function collectSnapshot(): PageSnapshot {\n const nodes = Array.from(\n document.querySelectorAll<HTMLElement>(CANDIDATE_SELECTOR)\n )\n .filter(isVisible)\n .slice(0, MAX_CANDIDATES);\n\n const candidates: CandidateElement[] = nodes.map((node) => {\n const placeholder =\n (node as HTMLInputElement).placeholder?.trim() || node.getAttribute(\"placeholder\")?.trim();\n return {\n selector: cssPath(node),\n role: node.getAttribute(\"role\") ?? node.tagName.toLowerCase(),\n text: (node.innerText || node.getAttribute(\"aria-label\") || node.getAttribute(\"name\") || \"\").trim().slice(0, 120),\n placeholder: placeholder || undefined\n };\n });\n\n const textPreview = document.body.innerText.replace(/\\s+/g, \" \").trim().slice(0, 1500);\n\n return {\n url: window.location.href,\n title: document.title,\n textPreview,\n candidates\n };\n}\n", "import type { AgentAction, CandidateElement, PlannerConfig, PlannerInput } from \"../shared/contracts\";\n\ntype WebLLMBridge = {\n plan(input: PlannerInput, modelId?: string): Promise<AgentAction>;\n};\n\nconst URL_PATTERN = /(?:go to|navigate to|open)\\s+(https?:\\/\\/\\S+)/i;\nconst SEARCH_PATTERN = /search(?:\\s+for)?\\s+(.+)/i;\nconst FILL_PATTERN = /(?:fill|type|enter)\\s+\"?([^\"]+)\"?\\s+(?:in(?:to)?|for|on)\\s+(.+)/i;\nconst CLICK_PATTERN = /click(?:\\s+(?:on|the))?\\s+(.+)/i;\n\nfunction findByText(candidates: CandidateElement[], text: string): CandidateElement | undefined {\n const lower = text.toLowerCase();\n return candidates.find(\n (c) =>\n c.text.toLowerCase().includes(lower) ||\n (c.placeholder?.toLowerCase().includes(lower) ?? false)\n );\n}\n\nfunction findInput(candidates: CandidateElement[]): CandidateElement | undefined {\n return candidates.find(\n (c) => c.role === \"input\" || c.role === \"textarea\" || c.selector.includes(\"input\") || c.selector.includes(\"textarea\")\n );\n}\n\nfunction findButton(candidates: CandidateElement[]): CandidateElement | undefined {\n return candidates.find(\n (c) => c.role === \"button\" || c.role === \"a\" || c.selector.includes(\"button\") || c.selector.includes(\"a\")\n );\n}\n\nfunction heuristicPlan(input: PlannerInput): AgentAction {\n const { goal, snapshot, history } = input;\n\n const navMatch = goal.match(URL_PATTERN);\n if (navMatch) {\n return { type: \"navigate\", url: navMatch[1] };\n }\n\n const fillMatch = goal.match(FILL_PATTERN);\n if (fillMatch) {\n const [, text, fieldHint] = fillMatch;\n const target = findByText(snapshot.candidates, fieldHint) ?? findInput(snapshot.candidates);\n if (target) {\n return { type: \"type\", selector: target.selector, text, clearFirst: true, label: target.text || target.placeholder };\n }\n }\n\n const searchMatch = goal.match(SEARCH_PATTERN);\n if (searchMatch) {\n const input = findInput(snapshot.candidates);\n if (input) {\n return { type: \"type\", selector: input.selector, text: searchMatch[1].trim(), clearFirst: true, label: input.text || input.placeholder };\n }\n }\n\n const clickMatch = goal.match(CLICK_PATTERN);\n if (clickMatch) {\n const target = findByText(snapshot.candidates, clickMatch[1].trim());\n if (target) {\n return { type: \"click\", selector: target.selector, label: target.text };\n }\n }\n\n const firstInput = findInput(snapshot.candidates);\n const firstButton = findButton(snapshot.candidates);\n\n if (firstInput && !history.some((h) => h.startsWith(\"Typed\"))) {\n const searchTerm = goal.replace(/.*(?:search|find|look up)\\s+/i, \"\").trim();\n return { type: \"type\", selector: firstInput.selector, text: searchTerm, clearFirst: true, label: firstInput.text || firstInput.placeholder };\n }\n\n if (firstButton && !history.some((h) => h.startsWith(\"Clicked\"))) {\n return { type: \"click\", selector: firstButton.selector, label: firstButton.text };\n }\n\n return { type: \"done\", reason: \"No further heuristic actions available\" };\n}\n\nexport async function planNextAction(config: PlannerConfig, input: PlannerInput): Promise<AgentAction> {\n if (config.kind === \"heuristic\") {\n return heuristicPlan(input);\n }\n\n const bridge = (window as Window & { __browserAgentWebLLM?: WebLLMBridge }).__browserAgentWebLLM;\n if (!bridge) {\n return {\n type: \"done\",\n reason: \"WebLLM bridge is not configured. Use heuristic mode or wire a local bridge implementation.\"\n };\n }\n\n return bridge.plan(input, config.modelId);\n}\n", "import type { AgentSession, ContentCommand, ContentResult } from \"../shared/contracts\";\nimport { assessRisk } from \"../shared/safety\";\nimport { executeAction } from \"./executor\";\nimport { collectSnapshot } from \"./pageObserver\";\nimport { planNextAction } from \"./planner\";\n\nlet stopped = false;\n\nasync function runTick(session: AgentSession): Promise<ContentResult> {\n const snapshot = collectSnapshot();\n const action = await planNextAction(session.planner, {\n goal: session.goal,\n snapshot,\n history: session.history\n });\n\n const risk = assessRisk(action);\n if (risk === \"blocked\") {\n return { status: \"blocked\", action, message: `Blocked action: ${JSON.stringify(action)}` };\n }\n\n if (session.mode === \"human-approved\" && risk === \"review\") {\n return { status: \"needs_approval\", action, message: `Approval needed for ${action.type}` };\n }\n\n if (action.type === \"done\") {\n return { status: \"done\", action, message: action.reason };\n }\n\n const message = await executeAction(action);\n return { status: \"executed\", action, message };\n}\n\nasync function executePendingAction(session: AgentSession): Promise<ContentResult> {\n if (!session.pendingAction) {\n return { status: \"error\", message: \"No pending action to approve\" };\n }\n\n const message = await executeAction(session.pendingAction);\n return { status: \"executed\", action: session.pendingAction, message };\n}\n\nchrome.runtime.onMessage.addListener((command: ContentCommand, _sender, sendResponse) => {\n if (command.type === \"AGENT_STOP\") {\n stopped = true;\n sendResponse({ status: \"done\", message: \"Stopped by user\" } satisfies ContentResult);\n return true;\n }\n\n if (command.type !== \"AGENT_TICK\") {\n return false;\n }\n\n const session = command.session;\n const exec = session.pendingAction ? executePendingAction(session) : runTick(session);\n\n exec\n .then((result) => {\n if (stopped) {\n sendResponse({ status: \"done\", message: \"Stopped\" } satisfies ContentResult);\n return;\n }\n sendResponse(result);\n })\n .catch((error) => {\n sendResponse({ status: \"error\", message: String(error) } satisfies ContentResult);\n });\n\n return true;\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["input"]
7
+ }