@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/dist/lib.js ADDED
@@ -0,0 +1,388 @@
1
+ // src/content/executor.ts
2
+ function mustFind(selector) {
3
+ const node = document.querySelector(selector);
4
+ if (!(node instanceof HTMLElement)) {
5
+ throw new Error(`Selector not found: ${selector}`);
6
+ }
7
+ return node;
8
+ }
9
+ function dispatchInputEvents(el) {
10
+ el.dispatchEvent(new InputEvent("input", { bubbles: true, cancelable: true }));
11
+ el.dispatchEvent(new Event("change", { bubbles: true }));
12
+ }
13
+ async function executeAction(action) {
14
+ switch (action.type) {
15
+ case "click": {
16
+ mustFind(action.selector).click();
17
+ return `Clicked ${action.selector}`;
18
+ }
19
+ case "type": {
20
+ const input = mustFind(action.selector);
21
+ input.focus();
22
+ if (action.clearFirst) {
23
+ input.value = "";
24
+ dispatchInputEvents(input);
25
+ }
26
+ input.value = `${input.value}${action.text}`;
27
+ dispatchInputEvents(input);
28
+ return `Typed into ${action.selector}`;
29
+ }
30
+ case "navigate": {
31
+ window.location.href = action.url;
32
+ return `Navigated to ${action.url}`;
33
+ }
34
+ case "extract": {
35
+ const value = mustFind(action.selector).innerText.trim();
36
+ return `${action.label}: ${value}`;
37
+ }
38
+ case "scroll": {
39
+ const target = action.selector ? mustFind(action.selector) : document.documentElement;
40
+ target.scrollBy({ top: action.deltaY, behavior: "smooth" });
41
+ return `Scrolled ${action.deltaY > 0 ? "down" : "up"} ${Math.abs(action.deltaY)}px`;
42
+ }
43
+ case "focus": {
44
+ mustFind(action.selector).focus();
45
+ return `Focused ${action.selector}`;
46
+ }
47
+ case "wait": {
48
+ await new Promise((resolve) => setTimeout(resolve, action.ms));
49
+ return `Waited ${action.ms}ms`;
50
+ }
51
+ case "done": {
52
+ return action.reason;
53
+ }
54
+ default:
55
+ return "No-op";
56
+ }
57
+ }
58
+
59
+ // src/content/pageObserver.ts
60
+ var CANDIDATE_SELECTOR = "a,button,input,textarea,select,[role='button'],[role='link'],[contenteditable='true']";
61
+ var MAX_CANDIDATES = 60;
62
+ function cssPath(element) {
63
+ if (!(element instanceof HTMLElement)) {
64
+ return element.tagName.toLowerCase();
65
+ }
66
+ if (element.id) {
67
+ return `#${CSS.escape(element.id)}`;
68
+ }
69
+ const parts = [];
70
+ let current = element;
71
+ while (current && parts.length < 4) {
72
+ let part = current.tagName.toLowerCase();
73
+ if (current.classList.length > 0) {
74
+ part += `.${Array.from(current.classList).slice(0, 2).map(CSS.escape).join(".")}`;
75
+ }
76
+ const parent = current.parentElement;
77
+ if (parent) {
78
+ const siblings = Array.from(parent.children).filter((s) => s.tagName === current.tagName);
79
+ if (siblings.length > 1) {
80
+ const index = siblings.indexOf(current) + 1;
81
+ part += `:nth-of-type(${index})`;
82
+ }
83
+ }
84
+ parts.unshift(part);
85
+ current = parent;
86
+ }
87
+ return parts.join(" > ");
88
+ }
89
+ function isVisible(el) {
90
+ if (el.offsetParent === null && el.tagName !== "BODY") {
91
+ return false;
92
+ }
93
+ const style = window.getComputedStyle(el);
94
+ return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
95
+ }
96
+ function collectSnapshot() {
97
+ const nodes = Array.from(
98
+ document.querySelectorAll(CANDIDATE_SELECTOR)
99
+ ).filter(isVisible).slice(0, MAX_CANDIDATES);
100
+ const candidates = nodes.map((node) => {
101
+ const placeholder = node.placeholder?.trim() || node.getAttribute("placeholder")?.trim();
102
+ return {
103
+ selector: cssPath(node),
104
+ role: node.getAttribute("role") ?? node.tagName.toLowerCase(),
105
+ text: (node.innerText || node.getAttribute("aria-label") || node.getAttribute("name") || "").trim().slice(0, 120),
106
+ placeholder: placeholder || void 0
107
+ };
108
+ });
109
+ const textPreview = document.body.innerText.replace(/\s+/g, " ").trim().slice(0, 1500);
110
+ return {
111
+ url: window.location.href,
112
+ title: document.title,
113
+ textPreview,
114
+ candidates
115
+ };
116
+ }
117
+
118
+ // src/content/planner.ts
119
+ var URL_PATTERN = /(?:go to|navigate to|open)\s+(https?:\/\/\S+)/i;
120
+ var SEARCH_PATTERN = /search(?:\s+for)?\s+(.+)/i;
121
+ var FILL_PATTERN = /(?:fill|type|enter)\s+"?([^"]+)"?\s+(?:in(?:to)?|for|on)\s+(.+)/i;
122
+ var CLICK_PATTERN = /click(?:\s+(?:on|the))?\s+(.+)/i;
123
+ function findByText(candidates, text) {
124
+ const lower = text.toLowerCase();
125
+ return candidates.find(
126
+ (c) => c.text.toLowerCase().includes(lower) || (c.placeholder?.toLowerCase().includes(lower) ?? false)
127
+ );
128
+ }
129
+ function findInput(candidates) {
130
+ return candidates.find(
131
+ (c) => c.role === "input" || c.role === "textarea" || c.selector.includes("input") || c.selector.includes("textarea")
132
+ );
133
+ }
134
+ function findButton(candidates) {
135
+ return candidates.find(
136
+ (c) => c.role === "button" || c.role === "a" || c.selector.includes("button") || c.selector.includes("a")
137
+ );
138
+ }
139
+ function heuristicPlan(input) {
140
+ const { goal, snapshot, history } = input;
141
+ const navMatch = goal.match(URL_PATTERN);
142
+ if (navMatch) {
143
+ return { type: "navigate", url: navMatch[1] };
144
+ }
145
+ const fillMatch = goal.match(FILL_PATTERN);
146
+ if (fillMatch) {
147
+ const [, text, fieldHint] = fillMatch;
148
+ const target = findByText(snapshot.candidates, fieldHint) ?? findInput(snapshot.candidates);
149
+ if (target) {
150
+ return { type: "type", selector: target.selector, text, clearFirst: true, label: target.text || target.placeholder };
151
+ }
152
+ }
153
+ const searchMatch = goal.match(SEARCH_PATTERN);
154
+ if (searchMatch) {
155
+ const input2 = findInput(snapshot.candidates);
156
+ if (input2) {
157
+ return { type: "type", selector: input2.selector, text: searchMatch[1].trim(), clearFirst: true, label: input2.text || input2.placeholder };
158
+ }
159
+ }
160
+ const clickMatch = goal.match(CLICK_PATTERN);
161
+ if (clickMatch) {
162
+ const target = findByText(snapshot.candidates, clickMatch[1].trim());
163
+ if (target) {
164
+ return { type: "click", selector: target.selector, label: target.text };
165
+ }
166
+ }
167
+ const firstInput = findInput(snapshot.candidates);
168
+ const firstButton = findButton(snapshot.candidates);
169
+ if (firstInput && !history.some((h) => h.startsWith("Typed"))) {
170
+ const searchTerm = goal.replace(/.*(?:search|find|look up)\s+/i, "").trim();
171
+ return { type: "type", selector: firstInput.selector, text: searchTerm, clearFirst: true, label: firstInput.text || firstInput.placeholder };
172
+ }
173
+ if (firstButton && !history.some((h) => h.startsWith("Clicked"))) {
174
+ return { type: "click", selector: firstButton.selector, label: firstButton.text };
175
+ }
176
+ return { type: "done", reason: "No further heuristic actions available" };
177
+ }
178
+ async function planNextAction(config, input) {
179
+ if (config.kind === "heuristic") {
180
+ return heuristicPlan(input);
181
+ }
182
+ const bridge = window.__browserAgentWebLLM;
183
+ if (!bridge) {
184
+ return {
185
+ type: "done",
186
+ reason: "WebLLM bridge is not configured. Use heuristic mode or wire a local bridge implementation."
187
+ };
188
+ }
189
+ return bridge.plan(input, config.modelId);
190
+ }
191
+
192
+ // src/shared/safety.ts
193
+ var RISKY_KEYWORDS = /\b(delete|remove|pay|purchase|submit|confirm|checkout|transfer|withdraw|send)\b/i;
194
+ function elementTextRisky(text) {
195
+ return text != null && RISKY_KEYWORDS.test(text);
196
+ }
197
+ function assessRisk(action) {
198
+ switch (action.type) {
199
+ case "navigate": {
200
+ try {
201
+ const next = new URL(action.url);
202
+ if (!["http:", "https:"].includes(next.protocol)) {
203
+ return "blocked";
204
+ }
205
+ } catch {
206
+ return "blocked";
207
+ }
208
+ return "safe";
209
+ }
210
+ case "click":
211
+ return elementTextRisky(action.label) ? "review" : "safe";
212
+ case "type":
213
+ return elementTextRisky(action.label) ? "review" : "safe";
214
+ case "focus":
215
+ case "scroll":
216
+ case "wait":
217
+ return "safe";
218
+ case "extract":
219
+ return "review";
220
+ case "done":
221
+ return "safe";
222
+ default:
223
+ return "review";
224
+ }
225
+ }
226
+
227
+ // src/lib/index.ts
228
+ var DEFAULT_PLANNER = { kind: "heuristic" };
229
+ var BrowserAgent = class {
230
+ session;
231
+ maxSteps;
232
+ stepDelayMs;
233
+ events;
234
+ isStopped = false;
235
+ signal;
236
+ constructor(config, events = {}) {
237
+ this.session = {
238
+ id: crypto.randomUUID(),
239
+ tabId: null,
240
+ goal: config.goal,
241
+ mode: config.mode ?? "human-approved",
242
+ planner: config.planner ?? DEFAULT_PLANNER,
243
+ history: [],
244
+ isRunning: false
245
+ };
246
+ this.maxSteps = config.maxSteps ?? 20;
247
+ this.stepDelayMs = config.stepDelayMs ?? 500;
248
+ this.events = events;
249
+ this.signal = config.signal;
250
+ }
251
+ getSession() {
252
+ return { ...this.session, history: [...this.session.history] };
253
+ }
254
+ get isRunning() {
255
+ return this.session.isRunning;
256
+ }
257
+ get hasPendingAction() {
258
+ return this.session.pendingAction != null;
259
+ }
260
+ async start() {
261
+ this.isStopped = false;
262
+ this.session.isRunning = true;
263
+ this.events.onStart?.(this.getSession());
264
+ return this.runLoop();
265
+ }
266
+ async resume() {
267
+ if (this.session.pendingAction) {
268
+ const approvalResult = await this.approvePendingAction();
269
+ if (approvalResult.status === "error") {
270
+ return approvalResult;
271
+ }
272
+ }
273
+ this.session.isRunning = true;
274
+ return this.runLoop();
275
+ }
276
+ async runLoop() {
277
+ for (let step = 0; step < this.maxSteps; step += 1) {
278
+ if (this.isStopped || !this.session.isRunning) {
279
+ return { status: "done", message: "Stopped" };
280
+ }
281
+ if (this.signal?.aborted) {
282
+ this.session.isRunning = false;
283
+ return { status: "done", message: "Aborted" };
284
+ }
285
+ const result2 = await this.tick();
286
+ this.session.history.push(result2.message);
287
+ this.events.onStep?.(result2, this.getSession());
288
+ if (result2.status === "needs_approval") {
289
+ this.session.pendingAction = result2.action;
290
+ this.session.isRunning = false;
291
+ if (result2.action) {
292
+ this.events.onApprovalRequired?.(result2.action, this.getSession());
293
+ }
294
+ return result2;
295
+ }
296
+ if (["done", "blocked", "error"].includes(result2.status)) {
297
+ this.session.isRunning = false;
298
+ this.events.onDone?.(result2, this.getSession());
299
+ return result2;
300
+ }
301
+ await this.delay(this.stepDelayMs);
302
+ }
303
+ const result = { status: "done", message: "Reached max steps" };
304
+ this.session.history.push(result.message);
305
+ this.session.isRunning = false;
306
+ this.events.onMaxStepsReached?.(this.getSession());
307
+ this.events.onDone?.(result, this.getSession());
308
+ return result;
309
+ }
310
+ async approvePendingAction() {
311
+ if (!this.session.pendingAction) {
312
+ return { status: "error", message: "No pending action to approve" };
313
+ }
314
+ try {
315
+ const message = await executeAction(this.session.pendingAction);
316
+ const result = {
317
+ status: "executed",
318
+ message,
319
+ action: this.session.pendingAction
320
+ };
321
+ this.session.history.push(message);
322
+ this.session.pendingAction = void 0;
323
+ this.session.isRunning = true;
324
+ this.events.onStep?.(result, this.getSession());
325
+ return result;
326
+ } catch (error) {
327
+ this.session.isRunning = false;
328
+ this.events.onError?.(error, this.getSession());
329
+ return { status: "error", message: String(error) };
330
+ }
331
+ }
332
+ stop() {
333
+ this.isStopped = true;
334
+ this.session.isRunning = false;
335
+ }
336
+ async tick() {
337
+ try {
338
+ const snapshot = collectSnapshot();
339
+ const action = await planNextAction(this.session.planner, {
340
+ goal: this.session.goal,
341
+ snapshot,
342
+ history: this.session.history
343
+ });
344
+ return this.processAction(action);
345
+ } catch (error) {
346
+ this.session.isRunning = false;
347
+ this.events.onError?.(error, this.getSession());
348
+ return { status: "error", message: String(error) };
349
+ }
350
+ }
351
+ async processAction(action) {
352
+ const risk = assessRisk(action);
353
+ if (risk === "blocked") {
354
+ return {
355
+ status: "blocked",
356
+ action,
357
+ message: `Blocked action: ${JSON.stringify(action)}`
358
+ };
359
+ }
360
+ if (this.session.mode === "human-approved" && risk === "review") {
361
+ return {
362
+ status: "needs_approval",
363
+ action,
364
+ message: `Approval needed for ${action.type}`
365
+ };
366
+ }
367
+ if (action.type === "done") {
368
+ return {
369
+ status: "done",
370
+ action,
371
+ message: action.reason
372
+ };
373
+ }
374
+ const message = await executeAction(action);
375
+ return { status: "executed", action, message };
376
+ }
377
+ async delay(ms) {
378
+ await new Promise((resolve) => setTimeout(resolve, ms));
379
+ }
380
+ };
381
+ function createBrowserAgent(config, events) {
382
+ return new BrowserAgent(config, events);
383
+ }
384
+ export {
385
+ BrowserAgent,
386
+ createBrowserAgent
387
+ };
388
+ //# sourceMappingURL=lib.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/content/executor.ts", "../src/content/pageObserver.ts", "../src/content/planner.ts", "../src/shared/safety.ts", "../src/lib/index.ts"],
4
+ "sourcesContent": ["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 { 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 { executeAction } from \"../content/executor\";\nimport { collectSnapshot } from \"../content/pageObserver\";\nimport { planNextAction } from \"../content/planner\";\nimport type {\n AgentAction,\n AgentSession,\n ContentResult,\n LibraryAgentConfig,\n LibraryAgentEvents,\n PlannerConfig\n} from \"../shared/contracts\";\nimport { assessRisk } from \"../shared/safety\";\n\nconst DEFAULT_PLANNER: PlannerConfig = { kind: \"heuristic\" };\n\nexport class BrowserAgent {\n private session: AgentSession;\n private maxSteps: number;\n private stepDelayMs: number;\n private events: LibraryAgentEvents;\n private isStopped = false;\n private signal?: AbortSignal;\n\n constructor(config: LibraryAgentConfig, events: LibraryAgentEvents = {}) {\n this.session = {\n id: crypto.randomUUID(),\n tabId: null,\n goal: config.goal,\n mode: config.mode ?? \"human-approved\",\n planner: config.planner ?? DEFAULT_PLANNER,\n history: [],\n isRunning: false\n };\n\n this.maxSteps = config.maxSteps ?? 20;\n this.stepDelayMs = config.stepDelayMs ?? 500;\n this.events = events;\n this.signal = config.signal;\n }\n\n getSession(): AgentSession {\n return { ...this.session, history: [...this.session.history] };\n }\n\n get isRunning(): boolean {\n return this.session.isRunning;\n }\n\n get hasPendingAction(): boolean {\n return this.session.pendingAction != null;\n }\n\n async start(): Promise<ContentResult> {\n this.isStopped = false;\n this.session.isRunning = true;\n this.events.onStart?.(this.getSession());\n\n return this.runLoop();\n }\n\n async resume(): Promise<ContentResult> {\n if (this.session.pendingAction) {\n const approvalResult = await this.approvePendingAction();\n if (approvalResult.status === \"error\") {\n return approvalResult;\n }\n }\n this.session.isRunning = true;\n return this.runLoop();\n }\n\n private async runLoop(): Promise<ContentResult> {\n for (let step = 0; step < this.maxSteps; step += 1) {\n if (this.isStopped || !this.session.isRunning) {\n return { status: \"done\", message: \"Stopped\" };\n }\n\n if (this.signal?.aborted) {\n this.session.isRunning = false;\n return { status: \"done\", message: \"Aborted\" };\n }\n\n const result = await this.tick();\n this.session.history.push(result.message);\n this.events.onStep?.(result, this.getSession());\n\n if (result.status === \"needs_approval\") {\n this.session.pendingAction = result.action;\n this.session.isRunning = false;\n if (result.action) {\n this.events.onApprovalRequired?.(result.action, this.getSession());\n }\n return result;\n }\n\n if ([\"done\", \"blocked\", \"error\"].includes(result.status)) {\n this.session.isRunning = false;\n this.events.onDone?.(result, this.getSession());\n return result;\n }\n\n await this.delay(this.stepDelayMs);\n }\n\n const result: ContentResult = { status: \"done\", message: \"Reached max steps\" };\n this.session.history.push(result.message);\n this.session.isRunning = false;\n this.events.onMaxStepsReached?.(this.getSession());\n this.events.onDone?.(result, this.getSession());\n return result;\n }\n\n async approvePendingAction(): Promise<ContentResult> {\n if (!this.session.pendingAction) {\n return { status: \"error\", message: \"No pending action to approve\" };\n }\n\n try {\n const message = await executeAction(this.session.pendingAction);\n const result: ContentResult = {\n status: \"executed\",\n message,\n action: this.session.pendingAction\n };\n this.session.history.push(message);\n this.session.pendingAction = undefined;\n this.session.isRunning = true;\n this.events.onStep?.(result, this.getSession());\n return result;\n } catch (error) {\n this.session.isRunning = false;\n this.events.onError?.(error, this.getSession());\n return { status: \"error\", message: String(error) };\n }\n }\n\n stop(): void {\n this.isStopped = true;\n this.session.isRunning = false;\n }\n\n private async tick(): Promise<ContentResult> {\n try {\n const snapshot = collectSnapshot();\n const action = await planNextAction(this.session.planner, {\n goal: this.session.goal,\n snapshot,\n history: this.session.history\n });\n\n return this.processAction(action);\n } catch (error) {\n this.session.isRunning = false;\n this.events.onError?.(error, this.getSession());\n return { status: \"error\", message: String(error) };\n }\n }\n\n private async processAction(action: AgentAction): Promise<ContentResult> {\n const risk = assessRisk(action);\n if (risk === \"blocked\") {\n return {\n status: \"blocked\",\n action,\n message: `Blocked action: ${JSON.stringify(action)}`\n };\n }\n\n if (this.session.mode === \"human-approved\" && risk === \"review\") {\n return {\n status: \"needs_approval\",\n action,\n message: `Approval needed for ${action.type}`\n };\n }\n\n if (action.type === \"done\") {\n return {\n status: \"done\",\n action,\n message: action.reason\n };\n }\n\n const message = await executeAction(action);\n return { status: \"executed\", action, message };\n }\n\n private async delay(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\nexport function createBrowserAgent(config: LibraryAgentConfig, events?: LibraryAgentEvents): BrowserAgent {\n return new BrowserAgent(config, events);\n}\n\nexport type {\n AgentAction,\n AgentMode,\n AgentSession,\n ContentResult,\n LibraryAgentConfig,\n LibraryAgentEvents,\n PlannerConfig,\n PlannerInput,\n PlannerKind,\n RiskLevel\n} from \"../shared/contracts\";\n"],
5
+ "mappings": "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",
6
+ "names": ["input", "result"]
7
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "OmniBrowser Agent",
4
+ "version": "0.2.0",
5
+ "description": "Local-first AI website operator",
6
+ "permissions": ["activeTab", "tabs", "scripting", "storage"],
7
+ "host_permissions": ["<all_urls>"],
8
+ "background": {
9
+ "service_worker": "background.js",
10
+ "type": "module"
11
+ },
12
+ "action": {
13
+ "default_title": "OmniBrowser Agent",
14
+ "default_popup": "popup.html"
15
+ },
16
+ "content_scripts": [
17
+ {
18
+ "matches": ["<all_urls>"],
19
+ "js": ["content.js"],
20
+ "run_at": "document_idle"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,45 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>OmniBrowser Agent</title>
7
+ <style>
8
+ body { font-family: system-ui, sans-serif; margin: 12px; width: 340px; }
9
+ h1 { font-size: 16px; margin: 0 0 8px; }
10
+ textarea, select, button { width: 100%; margin-top: 8px; }
11
+ textarea { min-height: 86px; }
12
+ .row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
13
+ #status { margin-top: 10px; font-size: 12px; color: #444; white-space: pre-wrap; }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <h1>OmniBrowser Agent</h1>
18
+ <label for="goal">Goal</label>
19
+ <textarea id="goal" placeholder="Example: Open Drupal CRM contacts and update Jane Doe phone number"></textarea>
20
+
21
+ <div class="row">
22
+ <div>
23
+ <label for="mode">Mode</label>
24
+ <select id="mode">
25
+ <option value="autonomous">Autonomous</option>
26
+ <option value="human-approved">Human Approved</option>
27
+ </select>
28
+ </div>
29
+ <div>
30
+ <label for="planner">Planner</label>
31
+ <select id="planner">
32
+ <option value="heuristic">Heuristic</option>
33
+ <option value="webllm">WebLLM</option>
34
+ </select>
35
+ </div>
36
+ </div>
37
+
38
+ <button id="start">Start</button>
39
+ <button id="approve">Approve pending action</button>
40
+ <button id="stop">Stop</button>
41
+ <div id="status">Idle</div>
42
+
43
+ <script type="module" src="popup.js"></script>
44
+ </body>
45
+ </html>
package/dist/popup.js ADDED
@@ -0,0 +1,46 @@
1
+ // src/popup/index.ts
2
+ var goal = document.getElementById("goal");
3
+ var mode = document.getElementById("mode");
4
+ var planner = document.getElementById("planner");
5
+ var status = document.getElementById("status");
6
+ var start = document.getElementById("start");
7
+ var approve = document.getElementById("approve");
8
+ var stop = document.getElementById("stop");
9
+ async function withActiveTab(fn) {
10
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
11
+ if (!tab?.id) {
12
+ throw new Error("No active tab found");
13
+ }
14
+ return fn(tab.id);
15
+ }
16
+ start.addEventListener("click", async () => {
17
+ try {
18
+ status.textContent = "Starting...";
19
+ await withActiveTab(
20
+ (tabId) => chrome.runtime.sendMessage({
21
+ type: "START_AGENT",
22
+ tabId,
23
+ goal: goal.value.trim(),
24
+ mode: mode.value,
25
+ planner: planner.value
26
+ })
27
+ );
28
+ status.textContent = "Agent started";
29
+ } catch (error) {
30
+ status.textContent = `Error: ${String(error)}`;
31
+ }
32
+ });
33
+ approve.addEventListener("click", async () => {
34
+ await withActiveTab((tabId) => chrome.runtime.sendMessage({ type: "APPROVE_ACTION", tabId }));
35
+ status.textContent = "Approved pending action";
36
+ });
37
+ stop.addEventListener("click", async () => {
38
+ await withActiveTab((tabId) => chrome.runtime.sendMessage({ type: "STOP_AGENT", tabId }));
39
+ status.textContent = "Stopped";
40
+ });
41
+ chrome.runtime.sendMessage({ type: "GET_STATUS" }, (resp) => {
42
+ if (resp?.status) {
43
+ status.textContent = resp.status;
44
+ }
45
+ });
46
+ //# sourceMappingURL=popup.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/popup/index.ts"],
4
+ "sourcesContent": ["import type { AgentMode, PlannerKind } from \"../shared/contracts\";\n\nconst goal = document.getElementById(\"goal\") as HTMLTextAreaElement;\nconst mode = document.getElementById(\"mode\") as HTMLSelectElement;\nconst planner = document.getElementById(\"planner\") as HTMLSelectElement;\nconst status = document.getElementById(\"status\") as HTMLDivElement;\n\nconst start = document.getElementById(\"start\") as HTMLButtonElement;\nconst approve = document.getElementById(\"approve\") as HTMLButtonElement;\nconst stop = document.getElementById(\"stop\") as HTMLButtonElement;\n\nasync function withActiveTab<T>(fn: (tabId: number) => Promise<T>) {\n const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });\n if (!tab?.id) {\n throw new Error(\"No active tab found\");\n }\n return fn(tab.id);\n}\n\nstart.addEventListener(\"click\", async () => {\n try {\n status.textContent = \"Starting...\";\n await withActiveTab((tabId) =>\n chrome.runtime.sendMessage({\n type: \"START_AGENT\",\n tabId,\n goal: goal.value.trim(),\n mode: mode.value as AgentMode,\n planner: planner.value as PlannerKind\n })\n );\n status.textContent = \"Agent started\";\n } catch (error) {\n status.textContent = `Error: ${String(error)}`;\n }\n});\n\napprove.addEventListener(\"click\", async () => {\n await withActiveTab((tabId) => chrome.runtime.sendMessage({ type: \"APPROVE_ACTION\", tabId }));\n status.textContent = \"Approved pending action\";\n});\n\nstop.addEventListener(\"click\", async () => {\n await withActiveTab((tabId) => chrome.runtime.sendMessage({ type: \"STOP_AGENT\", tabId }));\n status.textContent = \"Stopped\";\n});\n\nchrome.runtime.sendMessage({ type: \"GET_STATUS\" }, (resp) => {\n if (resp?.status) {\n status.textContent = resp.status;\n }\n});\n"],
5
+ "mappings": ";AAEA,IAAM,OAAO,SAAS,eAAe,MAAM;AAC3C,IAAM,OAAO,SAAS,eAAe,MAAM;AAC3C,IAAM,UAAU,SAAS,eAAe,SAAS;AACjD,IAAM,SAAS,SAAS,eAAe,QAAQ;AAE/C,IAAM,QAAQ,SAAS,eAAe,OAAO;AAC7C,IAAM,UAAU,SAAS,eAAe,SAAS;AACjD,IAAM,OAAO,SAAS,eAAe,MAAM;AAE3C,eAAe,cAAiB,IAAmC;AACjE,QAAM,CAAC,GAAG,IAAI,MAAM,OAAO,KAAK,MAAM,EAAE,QAAQ,MAAM,eAAe,KAAK,CAAC;AAC3E,MAAI,CAAC,KAAK,IAAI;AACZ,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AACA,SAAO,GAAG,IAAI,EAAE;AAClB;AAEA,MAAM,iBAAiB,SAAS,YAAY;AAC1C,MAAI;AACF,WAAO,cAAc;AACrB,UAAM;AAAA,MAAc,CAAC,UACnB,OAAO,QAAQ,YAAY;AAAA,QACzB,MAAM;AAAA,QACN;AAAA,QACA,MAAM,KAAK,MAAM,KAAK;AAAA,QACtB,MAAM,KAAK;AAAA,QACX,SAAS,QAAQ;AAAA,MACnB,CAAC;AAAA,IACH;AACA,WAAO,cAAc;AAAA,EACvB,SAAS,OAAO;AACd,WAAO,cAAc,UAAU,OAAO,KAAK,CAAC;AAAA,EAC9C;AACF,CAAC;AAED,QAAQ,iBAAiB,SAAS,YAAY;AAC5C,QAAM,cAAc,CAAC,UAAU,OAAO,QAAQ,YAAY,EAAE,MAAM,kBAAkB,MAAM,CAAC,CAAC;AAC5F,SAAO,cAAc;AACvB,CAAC;AAED,KAAK,iBAAiB,SAAS,YAAY;AACzC,QAAM,cAAc,CAAC,UAAU,OAAO,QAAQ,YAAY,EAAE,MAAM,cAAc,MAAM,CAAC,CAAC;AACxF,SAAO,cAAc;AACvB,CAAC;AAED,OAAO,QAAQ,YAAY,EAAE,MAAM,aAAa,GAAG,CAAC,SAAS;AAC3D,MAAI,MAAM,QAAQ;AAChB,WAAO,cAAc,KAAK;AAAA,EAC5B;AACF,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ import type { AgentAction } from "../shared/contracts";
2
+ export declare function executeAction(action: AgentAction): Promise<string>;
@@ -0,0 +1,2 @@
1
+ import type { PageSnapshot } from "../shared/contracts";
2
+ export declare function collectSnapshot(): PageSnapshot;
@@ -0,0 +1,2 @@
1
+ import type { AgentAction, PlannerConfig, PlannerInput } from "../shared/contracts";
2
+ export declare function planNextAction(config: PlannerConfig, input: PlannerInput): Promise<AgentAction>;
@@ -0,0 +1,23 @@
1
+ import type { AgentSession, ContentResult, LibraryAgentConfig, LibraryAgentEvents } from "../shared/contracts";
2
+ export declare class BrowserAgent {
3
+ private session;
4
+ private maxSteps;
5
+ private stepDelayMs;
6
+ private events;
7
+ private isStopped;
8
+ private signal?;
9
+ constructor(config: LibraryAgentConfig, events?: LibraryAgentEvents);
10
+ getSession(): AgentSession;
11
+ get isRunning(): boolean;
12
+ get hasPendingAction(): boolean;
13
+ start(): Promise<ContentResult>;
14
+ resume(): Promise<ContentResult>;
15
+ private runLoop;
16
+ approvePendingAction(): Promise<ContentResult>;
17
+ stop(): void;
18
+ private tick;
19
+ private processAction;
20
+ private delay;
21
+ }
22
+ export declare function createBrowserAgent(config: LibraryAgentConfig, events?: LibraryAgentEvents): BrowserAgent;
23
+ export type { AgentAction, AgentMode, AgentSession, ContentResult, LibraryAgentConfig, LibraryAgentEvents, PlannerConfig, PlannerInput, PlannerKind, RiskLevel } from "../shared/contracts";