@akshayram1/omnibrowser-agent 0.2.3 → 0.2.8
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/README.md +210 -135
- package/dist/content.js +26 -24
- package/dist/content.js.map +2 -2
- package/dist/lib.js +61 -39
- package/dist/lib.js.map +2 -2
- package/dist/manifest.json +1 -1
- package/dist/popup.html +0 -1
- package/dist/types/core/planner.d.ts +2 -2
- package/dist/types/lib/index.d.ts +2 -2
- package/dist/types/shared/contracts.d.ts +31 -3
- package/dist/types/shared/parse-action.d.ts +12 -8
- package/docs/ARCHITECTURE.md +6 -14
- package/docs/EMBEDDING.md +21 -42
- package/docs/ROADMAP.md +0 -1
- package/docs/arch.md +220 -0
- package/index.html +275 -204
- package/package.json +1 -1
- package/styles.css +5 -0
package/README.md
CHANGED
|
@@ -1,206 +1,281 @@
|
|
|
1
1
|
# omnibrowser-agent
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@akshayram1/omnibrowser-agent)
|
|
3
4
|
[](LICENSE)
|
|
4
|
-
[](package.json)
|
|
5
5
|
|
|
6
|
-
Local-first
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
6
|
+
Local-first browser AI operator. Plans and executes DOM actions entirely in the browser — no API keys, no cloud costs, no data leaving your machine.
|
|
7
|
+
|
|
8
|
+
[Live Demo](https://omnibrowser-agent.vercel.app/examples/chatbot/) · [Embedding Guide](docs/EMBEDDING.md) · [Architecture](docs/arch.md) · [Deployment](docs/DEPLOYMENT.md) · [Roadmap](docs/ROADMAP.md)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart TB
|
|
16
|
+
subgraph DELIVERY["Delivery Layer"]
|
|
17
|
+
EXT["🧩 Chrome Extension\npopup + background worker"]
|
|
18
|
+
LIB["📦 npm Library\ncreateBrowserAgent()"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subgraph ORCHESTRATION["Orchestration"]
|
|
22
|
+
BG["background/index.ts\nSession & tick loop"]
|
|
23
|
+
BA["BrowserAgent class\nrunLoop() / resume() / stop()"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
subgraph CORE["Core (src/core/)"]
|
|
27
|
+
PL["planner.ts\nheuristicPlan() / webllm bridge\nplanNextAction()"]
|
|
28
|
+
OB["observer.ts\ncollectSnapshot()\nDOM candidates + visibility filter"]
|
|
29
|
+
EX["executor.ts\nexecuteAction()\nclick / type / navigate\nscroll / focus / wait"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
subgraph SHARED["Shared (src/shared/)"]
|
|
33
|
+
CT["contracts.ts\nAgentAction · PageSnapshot\nAgentSession · PlannerResult"]
|
|
34
|
+
SF["safety.ts\nassessRisk()\nsafe / review / blocked"]
|
|
35
|
+
PA["parse-action.ts\nparseAction()\nparsePlannerResult()"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
subgraph OUTCOMES["Action Outcomes"]
|
|
39
|
+
direction LR
|
|
40
|
+
OK["✅ safe → execute"]
|
|
41
|
+
RV["⚠️ review → needs approval"]
|
|
42
|
+
BL["🚫 blocked → stop"]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
subgraph PLANNERS["Planner Modes"]
|
|
46
|
+
direction LR
|
|
47
|
+
HP["Heuristic\nzero deps · offline\nregex patterns"]
|
|
48
|
+
WL["WebLLM\non-device · WebGPU\nwindow.__browserAgentWebLLM"]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
EXT --> BG
|
|
52
|
+
LIB --> BA
|
|
53
|
+
BG -. "chrome.tabs.sendMessage" .-> CORE
|
|
54
|
+
BA --> CORE
|
|
55
|
+
|
|
56
|
+
PL --> OB
|
|
57
|
+
PL --> SHARED
|
|
58
|
+
OB --> SHARED
|
|
59
|
+
EX --> SHARED
|
|
60
|
+
|
|
61
|
+
SF --> OUTCOMES
|
|
62
|
+
PL --> PLANNERS
|
|
63
|
+
```
|
|
24
64
|
|
|
25
|
-
|
|
65
|
+
---
|
|
26
66
|
|
|
27
|
-
|
|
28
|
-
- `src/content` page observer/planner/executor
|
|
29
|
-
- `src/popup` control panel
|
|
30
|
-
- `src/lib` embeddable runtime API
|
|
31
|
-
- `src/shared` contracts and safety
|
|
67
|
+
## How it works — one tick
|
|
32
68
|
|
|
33
|
-
|
|
69
|
+
```
|
|
70
|
+
goal + history + memory
|
|
71
|
+
│
|
|
72
|
+
▼
|
|
73
|
+
observer.collectSnapshot() ──→ PageSnapshot (url, title, candidates[])
|
|
74
|
+
│
|
|
75
|
+
▼
|
|
76
|
+
planner.planNextAction() ──→ PlannerResult { action, evaluation?, memory?, nextGoal? }
|
|
77
|
+
│
|
|
78
|
+
▼
|
|
79
|
+
safety.assessRisk(action) ──→ safe | review | blocked
|
|
80
|
+
│
|
|
81
|
+
┌────┴─────────────────────┐
|
|
82
|
+
blocked review (human-approved mode)
|
|
83
|
+
│ │
|
|
84
|
+
stop pause → user approves → resume
|
|
85
|
+
│
|
|
86
|
+
safe / approved
|
|
87
|
+
│
|
|
88
|
+
▼
|
|
89
|
+
executor.executeAction(action) ──→ result string
|
|
90
|
+
│
|
|
91
|
+
▼
|
|
92
|
+
session.history.push(result)
|
|
93
|
+
→ next tick
|
|
94
|
+
```
|
|
34
95
|
|
|
35
|
-
|
|
96
|
+
The planner uses a **reflection loop** before each action: it evaluates what happened last step, maintains working memory across steps, and states its next goal — giving the agent much better multi-step reasoning.
|
|
36
97
|
|
|
37
|
-
|
|
38
|
-
npm install
|
|
39
|
-
```
|
|
98
|
+
---
|
|
40
99
|
|
|
41
|
-
|
|
100
|
+
## Install
|
|
42
101
|
|
|
43
102
|
```bash
|
|
44
|
-
npm
|
|
103
|
+
npm install @akshayram1/omnibrowser-agent
|
|
45
104
|
```
|
|
46
105
|
|
|
47
|
-
|
|
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
|
|
106
|
+
---
|
|
62
107
|
|
|
63
|
-
##
|
|
108
|
+
## Quick start
|
|
64
109
|
|
|
65
110
|
```ts
|
|
66
111
|
import { createBrowserAgent } from "@akshayram1/omnibrowser-agent";
|
|
67
112
|
|
|
68
113
|
const agent = createBrowserAgent({
|
|
69
|
-
goal: "
|
|
70
|
-
mode: "human-approved",
|
|
71
|
-
planner: { kind: "heuristic" }
|
|
114
|
+
goal: "Search for contact Jane Doe and open her profile",
|
|
115
|
+
mode: "human-approved", // or "autonomous"
|
|
116
|
+
planner: { kind: "heuristic" } // or "webllm"
|
|
72
117
|
}, {
|
|
73
|
-
onStep:
|
|
74
|
-
onApprovalRequired: (action) => console.log("
|
|
75
|
-
onDone:
|
|
76
|
-
|
|
118
|
+
onStep: (result, session) => console.log(result.message),
|
|
119
|
+
onApprovalRequired: (action, session) => console.log("Review:", action),
|
|
120
|
+
onDone: (result, session) => console.log("Done:", result.message),
|
|
121
|
+
onError: (err, session) => console.error(err),
|
|
122
|
+
onMaxStepsReached: (session) => console.log("Max steps hit"),
|
|
77
123
|
});
|
|
78
124
|
|
|
79
125
|
await agent.start();
|
|
80
126
|
|
|
81
|
-
//
|
|
127
|
+
// After onApprovalRequired fires:
|
|
82
128
|
await agent.resume();
|
|
83
129
|
|
|
84
|
-
//
|
|
85
|
-
console.log(agent.isRunning, agent.hasPendingAction);
|
|
86
|
-
|
|
87
|
-
// Stop at any time:
|
|
130
|
+
// Cancel at any time:
|
|
88
131
|
agent.stop();
|
|
89
132
|
```
|
|
90
133
|
|
|
91
|
-
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Planner modes
|
|
137
|
+
|
|
138
|
+
| Mode | Description | When to use |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `heuristic` | Zero-dependency regex planner. Works fully offline. | Simple, predictable goals — navigate, fill, click |
|
|
141
|
+
| `webllm` | On-device LLM via WebGPU. Fully private, no API calls. | Open-ended, multi-step, language-heavy goals |
|
|
142
|
+
|
|
143
|
+
### WebLLM with a custom system prompt
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const agent = createBrowserAgent({
|
|
147
|
+
goal: "Fill the checkout form",
|
|
148
|
+
planner: {
|
|
149
|
+
kind: "webllm",
|
|
150
|
+
systemPrompt: "You are a careful checkout assistant. Never submit before all required fields are filled."
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
See [docs/EMBEDDING.md](docs/EMBEDDING.md) for the full WebLLM bridge wiring guide.
|
|
156
|
+
|
|
157
|
+
---
|
|
92
158
|
|
|
93
|
-
|
|
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 |
|
|
159
|
+
## Agent modes
|
|
103
160
|
|
|
104
|
-
|
|
161
|
+
| Mode | Behaviour |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `autonomous` | All `safe` and `review` actions execute without pause |
|
|
164
|
+
| `human-approved` | `review`-rated actions pause and emit `onApprovalRequired` — call `resume()` to continue |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Supported actions
|
|
169
|
+
|
|
170
|
+
| Action | Description | Risk |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `navigate` | Navigate to a URL (http/https only) | safe |
|
|
173
|
+
| `click` | Click an element by CSS selector | safe / review |
|
|
174
|
+
| `type` | Type text into an input or textarea | safe / review |
|
|
175
|
+
| `scroll` | Scroll a container or the page | safe |
|
|
176
|
+
| `focus` | Focus an element | safe |
|
|
177
|
+
| `wait` | Pause for N milliseconds | safe |
|
|
178
|
+
| `extract` | Extract text from an element | review |
|
|
179
|
+
| `done` | Signal task completion | safe |
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## AbortSignal support
|
|
105
184
|
|
|
106
185
|
```ts
|
|
107
186
|
const controller = new AbortController();
|
|
108
187
|
const agent = createBrowserAgent({ goal: "...", signal: controller.signal });
|
|
109
188
|
agent.start();
|
|
110
189
|
|
|
111
|
-
//
|
|
112
|
-
controller.abort();
|
|
190
|
+
controller.abort(); // cancel from outside
|
|
113
191
|
```
|
|
114
192
|
|
|
115
|
-
|
|
193
|
+
---
|
|
116
194
|
|
|
117
|
-
##
|
|
195
|
+
## Chrome Extension
|
|
118
196
|
|
|
119
|
-
1. Build
|
|
197
|
+
1. Build:
|
|
120
198
|
|
|
121
199
|
```bash
|
|
122
200
|
npm run build
|
|
123
201
|
```
|
|
124
202
|
|
|
125
|
-
2.
|
|
203
|
+
2. Open `chrome://extensions`, enable **Developer Mode**, click **Load unpacked**, select `dist/`.
|
|
126
204
|
|
|
127
|
-
|
|
128
|
-
python3 -m http.server 4173
|
|
129
|
-
```
|
|
205
|
+
3. Open any tab, enter a goal in the popup, pick a mode, and click **Start**.
|
|
130
206
|
|
|
131
|
-
|
|
207
|
+
See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for publishing and CI pipeline details.
|
|
132
208
|
|
|
133
|
-
|
|
209
|
+
---
|
|
134
210
|
|
|
135
|
-
|
|
136
|
-
It is preconfigured to use `webllm` planner mode and loads `@mlc-ai/web-llm` from CDN in the example page.
|
|
137
|
-
|
|
138
|
-
## Changelog
|
|
211
|
+
## Project structure
|
|
139
212
|
|
|
140
|
-
|
|
213
|
+
```
|
|
214
|
+
src/
|
|
215
|
+
├── background/ Extension service worker — session management
|
|
216
|
+
├── content/ Extension content script — runs in page context
|
|
217
|
+
├── core/ Shared engine (planner, observer, executor)
|
|
218
|
+
│ ├── planner.ts
|
|
219
|
+
│ ├── observer.ts
|
|
220
|
+
│ └── executor.ts
|
|
221
|
+
├── lib/ npm library entry — BrowserAgent class
|
|
222
|
+
│ └── index.ts
|
|
223
|
+
├── popup/ Extension popup UI
|
|
224
|
+
└── shared/ Types, safety, and parse utilities
|
|
225
|
+
├── contracts.ts
|
|
226
|
+
├── safety.ts
|
|
227
|
+
└── parse-action.ts
|
|
228
|
+
```
|
|
141
229
|
|
|
142
|
-
|
|
143
|
-
- **Popup**: added page-agent option to the planner dropdown
|
|
230
|
+
---
|
|
144
231
|
|
|
145
|
-
|
|
232
|
+
## Changelog
|
|
146
233
|
|
|
147
|
-
|
|
148
|
-
- **Smarter safety**: risk assessment now checks element label/text rather than CSS selector strings
|
|
149
|
-
- **Improved heuristic planner**: handles navigate, fill, click, and search goal patterns with regex matching
|
|
150
|
-
- **Better page observation**: filters hidden/invisible elements, includes `placeholder` in candidate data, captures up to 60 candidates
|
|
151
|
-
- **Library API**: added `resume()`, `isRunning` and `hasPendingAction` getters, `onMaxStepsReached` event, and `AbortSignal` support
|
|
152
|
-
- **Executor**: uses `InputEvent` for proper framework compatibility, added keyboard event dispatch
|
|
153
|
-
- **License**: added author name
|
|
234
|
+
### v0.2.6
|
|
154
235
|
|
|
155
|
-
|
|
236
|
+
- Reflection-before-action pattern (`evaluation → memory → next_goal → action`) — agent reasons about each step before acting
|
|
237
|
+
- Working memory carried across ticks for better multi-step goals
|
|
238
|
+
- `parsePlannerResult()` exported from the library
|
|
239
|
+
- `systemPrompt` option in `PlannerConfig` — pass your own prompt without rewriting the bridge
|
|
240
|
+
- Thought bubble (💭) messages in the live demo chat showing the agent's next intent
|
|
156
241
|
|
|
157
|
-
|
|
158
|
-
- Shared action contracts
|
|
159
|
-
- Heuristic + WebLLM planner switch
|
|
160
|
-
- Human-approved mode
|
|
242
|
+
### v0.2.4 — v0.2.5
|
|
161
243
|
|
|
162
|
-
|
|
244
|
+
- CI pipeline: auto version bump on push to main
|
|
245
|
+
- Removed page-agent dependency — reflection pattern implemented natively
|
|
246
|
+
- Chatbot demo redesign: right-aligned user messages, typing indicator, tab navigation (CRM + Task Manager)
|
|
247
|
+
- `parsePlannerResult()` and `PlannerResult` type exported from library
|
|
163
248
|
|
|
164
|
-
|
|
165
|
-
|---|---|
|
|
166
|
-
| `heuristic` | Zero-dependency regex-based planner. Works offline. Good for simple, predictable goals. |
|
|
167
|
-
| `webllm` | Delegates to a local WebLLM bridge on `window.__browserAgentWebLLM`. Fully private, no API calls. |
|
|
168
|
-
| `page-agent` | Delegates to an [alibaba/page-agent](https://github.com/alibaba/page-agent) bridge on `window.__browserAgentPageAgent`. Use for complex multi-step tasks with LLM planning. |
|
|
169
|
-
|
|
170
|
-
### page-agent bridge example
|
|
249
|
+
### v0.2.2
|
|
171
250
|
|
|
172
|
-
|
|
173
|
-
|
|
251
|
+
- SDK/extension separation: core logic in `src/core/` shared between extension and npm library
|
|
252
|
+
- 22 unit tests across planner and safety modules
|
|
253
|
+
- Action verification in executor (disabled-check, value-verify, empty-check)
|
|
254
|
+
- `CandidateElement.label` from associated `<label>` elements
|
|
255
|
+
- Retry loop with `lastError` fed back to planner on failure
|
|
174
256
|
|
|
175
|
-
|
|
176
|
-
baseURL: "https://api.openai.com/v1",
|
|
177
|
-
model: "gpt-4o",
|
|
178
|
-
apiKey: "sk-..."
|
|
179
|
-
});
|
|
257
|
+
### v0.2.0
|
|
180
258
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
};
|
|
187
|
-
```
|
|
259
|
+
- New actions: `scroll` and `focus`
|
|
260
|
+
- Smarter safety: risk assessment checks element label/text
|
|
261
|
+
- Improved heuristic planner with regex pattern matching
|
|
262
|
+
- Better page observation: filters invisible elements, up to 60 candidates
|
|
263
|
+
- Library API: `resume()`, `isRunning`, `hasPendingAction`, `onMaxStepsReached`, `AbortSignal`
|
|
188
264
|
|
|
189
|
-
|
|
265
|
+
### v0.1.0
|
|
190
266
|
|
|
191
|
-
|
|
192
|
-
planner: { kind: "page-agent" }
|
|
193
|
-
```
|
|
267
|
+
- Extension runtime loop, shared action contracts, heuristic + WebLLM planner, human-approved mode
|
|
194
268
|
|
|
195
|
-
|
|
269
|
+
---
|
|
196
270
|
|
|
197
|
-
|
|
198
|
-
- `webllm` mode expects a local bridge implementation attached to `window.__browserAgentWebLLM`.
|
|
199
|
-
- `page-agent` mode expects a bridge on `window.__browserAgentPageAgent`. The `page-agent` package is not bundled — bring your own instance.
|
|
271
|
+
## Docs
|
|
200
272
|
|
|
201
|
-
|
|
273
|
+
- [Embedding Guide](docs/EMBEDDING.md) — integrate into any web app
|
|
274
|
+
- [Architecture](docs/arch.md) — layer-by-layer breakdown
|
|
275
|
+
- [Deployment](docs/DEPLOYMENT.md) — npm publish, Vercel, Chrome extension, CI
|
|
276
|
+
- [Roadmap](docs/ROADMAP.md) — planned features
|
|
202
277
|
|
|
203
|
-
|
|
278
|
+
---
|
|
204
279
|
|
|
205
280
|
## License
|
|
206
281
|
|
package/dist/content.js
CHANGED
|
@@ -195,7 +195,7 @@ var CLICK_PATTERN = /click(?:\s+(?:on|the))?\s+(.+)/i;
|
|
|
195
195
|
function findByText(candidates, text) {
|
|
196
196
|
const lower = text.toLowerCase();
|
|
197
197
|
return candidates.find(
|
|
198
|
-
(c) => c.text.toLowerCase().includes(lower) || (c.placeholder?.toLowerCase().includes(lower) ?? false)
|
|
198
|
+
(c) => c.text.toLowerCase().includes(lower) || (c.placeholder?.toLowerCase().includes(lower) ?? false) || (c.label?.toLowerCase().includes(lower) ?? false)
|
|
199
199
|
);
|
|
200
200
|
}
|
|
201
201
|
function findInput(candidates) {
|
|
@@ -219,14 +219,14 @@ function heuristicPlan(input) {
|
|
|
219
219
|
const [, text, fieldHint] = fillMatch;
|
|
220
220
|
const target = findByText(snapshot.candidates, fieldHint) ?? findInput(snapshot.candidates);
|
|
221
221
|
if (target) {
|
|
222
|
-
return { type: "type", selector: target.selector, text, clearFirst: true, label: target.text || target.placeholder };
|
|
222
|
+
return { type: "type", selector: target.selector, text, clearFirst: true, label: target.label || target.text || target.placeholder };
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
const searchMatch = goal.match(SEARCH_PATTERN);
|
|
226
226
|
if (searchMatch) {
|
|
227
227
|
const input2 = findInput(snapshot.candidates);
|
|
228
228
|
if (input2) {
|
|
229
|
-
return { type: "type", selector: input2.selector, text: searchMatch[1].trim(), clearFirst: true, label: input2.text || input2.placeholder };
|
|
229
|
+
return { type: "type", selector: input2.selector, text: searchMatch[1].trim(), clearFirst: true, label: input2.label || input2.text || input2.placeholder };
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
const clickMatch = goal.match(CLICK_PATTERN);
|
|
@@ -240,59 +240,61 @@ function heuristicPlan(input) {
|
|
|
240
240
|
const firstButton = findButton(snapshot.candidates);
|
|
241
241
|
if (firstInput && !history.some((h) => h.startsWith("Typed"))) {
|
|
242
242
|
const searchTerm = goal.replace(/.*(?:search|find|look up)\s+/i, "").trim();
|
|
243
|
-
return { type: "type", selector: firstInput.selector, text: searchTerm, clearFirst: true, label: firstInput.text || firstInput.placeholder };
|
|
243
|
+
return { type: "type", selector: firstInput.selector, text: searchTerm, clearFirst: true, label: firstInput.label || firstInput.text || firstInput.placeholder };
|
|
244
244
|
}
|
|
245
245
|
if (firstButton && !history.some((h) => h.startsWith("Clicked"))) {
|
|
246
246
|
return { type: "click", selector: firstButton.selector, label: firstButton.text };
|
|
247
247
|
}
|
|
248
248
|
return { type: "done", reason: "No further heuristic actions available" };
|
|
249
249
|
}
|
|
250
|
+
function toPlannerResult(raw) {
|
|
251
|
+
if ("action" in raw && typeof raw.action === "object") {
|
|
252
|
+
return raw;
|
|
253
|
+
}
|
|
254
|
+
return { action: raw };
|
|
255
|
+
}
|
|
250
256
|
async function planNextAction(config, input) {
|
|
251
257
|
if (config.kind === "heuristic") {
|
|
252
|
-
return heuristicPlan(input);
|
|
253
|
-
}
|
|
254
|
-
if (config.kind === "page-agent") {
|
|
255
|
-
const pageAgentBridge = window.__browserAgentPageAgent;
|
|
256
|
-
if (!pageAgentBridge) {
|
|
257
|
-
return {
|
|
258
|
-
type: "done",
|
|
259
|
-
reason: "page-agent bridge is not configured. Assign a PageAgentBridge to window.__browserAgentPageAgent."
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
return pageAgentBridge.plan(input);
|
|
258
|
+
return { action: heuristicPlan(input) };
|
|
263
259
|
}
|
|
264
260
|
const bridge = window.__browserAgentWebLLM;
|
|
265
261
|
if (!bridge) {
|
|
266
262
|
return {
|
|
267
|
-
|
|
268
|
-
|
|
263
|
+
action: {
|
|
264
|
+
type: "done",
|
|
265
|
+
reason: "WebLLM bridge is not configured. Use heuristic mode or wire a WebLLM bridge implementation."
|
|
266
|
+
}
|
|
269
267
|
};
|
|
270
268
|
}
|
|
271
|
-
|
|
269
|
+
const raw = await bridge.plan({ ...input, systemPrompt: config.systemPrompt }, config.modelId);
|
|
270
|
+
return toPlannerResult(raw);
|
|
272
271
|
}
|
|
273
272
|
|
|
274
273
|
// src/content/index.ts
|
|
275
274
|
var stopped = false;
|
|
276
275
|
async function runTick(session) {
|
|
277
276
|
const snapshot = collectSnapshot();
|
|
278
|
-
const
|
|
277
|
+
const plannerResult = await planNextAction(session.planner, {
|
|
279
278
|
goal: session.goal,
|
|
280
279
|
snapshot,
|
|
281
280
|
history: session.history,
|
|
282
|
-
lastError: session.lastError
|
|
281
|
+
lastError: session.lastError,
|
|
282
|
+
memory: session.memory
|
|
283
283
|
});
|
|
284
|
+
const { action } = plannerResult;
|
|
285
|
+
const reflection = plannerResult.evaluation !== void 0 || plannerResult.memory !== void 0 || plannerResult.nextGoal !== void 0 ? { evaluation: plannerResult.evaluation, memory: plannerResult.memory, nextGoal: plannerResult.nextGoal } : void 0;
|
|
284
286
|
const risk = assessRisk(action);
|
|
285
287
|
if (risk === "blocked") {
|
|
286
|
-
return { status: "blocked", action, message: `Blocked action: ${JSON.stringify(action)}
|
|
288
|
+
return { status: "blocked", action, message: `Blocked action: ${JSON.stringify(action)}`, reflection };
|
|
287
289
|
}
|
|
288
290
|
if (session.mode === "human-approved" && risk === "review") {
|
|
289
|
-
return { status: "needs_approval", action, message: `Approval needed for ${action.type}
|
|
291
|
+
return { status: "needs_approval", action, message: `Approval needed for ${action.type}`, reflection };
|
|
290
292
|
}
|
|
291
293
|
if (action.type === "done") {
|
|
292
|
-
return { status: "done", action, message: action.reason };
|
|
294
|
+
return { status: "done", action, message: action.reason, reflection };
|
|
293
295
|
}
|
|
294
296
|
const message = await executeAction(action);
|
|
295
|
-
return { status: "executed", action, message };
|
|
297
|
+
return { status: "executed", action, message, reflection };
|
|
296
298
|
}
|
|
297
299
|
async function executePendingAction(session) {
|
|
298
300
|
if (!session.pendingAction) {
|
package/dist/content.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/shared/safety.ts", "../src/core/executor.ts", "../src/core/observer.ts", "../src/core/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 const el = mustFind(action.selector);\n if ((el as HTMLButtonElement).disabled) {\n throw new Error(`Element is disabled: ${action.selector}`);\n }\n el.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 if (input.value.indexOf(action.text) === -1) {\n throw new Error(`Type verification failed: value did not update for ${action.selector}`);\n }\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 if (!value) {\n throw new Error(`Extract returned empty text from ${action.selector}`);\n }\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\") return false;\n const style = window.getComputedStyle(el);\n if (style.display === \"none\" || style.visibility === \"hidden\" || style.opacity === \"0\") return false;\n // Zero-dimension elements are functionally hidden\n const rect = el.getBoundingClientRect();\n return rect.width > 0 || rect.height > 0;\n}\n\nfunction isInViewport(el: HTMLElement): boolean {\n const rect = el.getBoundingClientRect();\n return (\n rect.bottom > 0 &&\n rect.top < window.innerHeight &&\n rect.right > 0 &&\n rect.left < window.innerWidth\n );\n}\n\n/** Resolve the visible label text via for/id, aria-labelledby, aria-label, or wrapping <label>. */\nfunction getAssociatedLabel(el: HTMLElement): string {\n if (el.id) {\n const label = document.querySelector<HTMLLabelElement>(`label[for=\"${CSS.escape(el.id)}\"]`);\n if (label) return label.innerText.trim();\n }\n\n const labelledBy = el.getAttribute(\"aria-labelledby\");\n if (labelledBy) {\n const labelEl = document.getElementById(labelledBy);\n if (labelEl) return labelEl.innerText.trim();\n }\n\n const ariaLabel = el.getAttribute(\"aria-label\");\n if (ariaLabel) return ariaLabel.trim();\n\n const parentLabel = el.closest(\"label\");\n if (parentLabel) {\n return Array.from(parentLabel.childNodes)\n .filter((n) => n.nodeType === Node.TEXT_NODE)\n .map((n) => n.textContent?.trim() ?? \"\")\n .filter(Boolean)\n .join(\" \");\n }\n\n return \"\";\n}\n\nexport function collectSnapshot(): PageSnapshot {\n const allNodes = Array.from(\n document.querySelectorAll<HTMLElement>(CANDIDATE_SELECTOR)\n ).filter(isVisible);\n\n // In-viewport elements first so the model sees the most relevant candidates first\n const inView = allNodes.filter(isInViewport);\n const offScreen = allNodes.filter((el) => !isInViewport(el));\n const nodes = [...inView, ...offScreen].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 const associatedLabel = getAssociatedLabel(node);\n return {\n selector: cssPath(node),\n role: node.getAttribute(\"role\") ?? node.tagName.toLowerCase(),\n text: (node.innerText || node.getAttribute(\"name\") || \"\").trim().slice(0, 120),\n placeholder: placeholder || undefined,\n label: associatedLabel || 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\ntype PageAgentBridge = {\n plan(input: PlannerInput): 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 if (config.kind === \"page-agent\") {\n const pageAgentBridge = (window as Window & { __browserAgentPageAgent?: PageAgentBridge }).__browserAgentPageAgent;\n if (!pageAgentBridge) {\n return {\n type: \"done\",\n reason: \"page-agent bridge is not configured. Assign a PageAgentBridge to window.__browserAgentPageAgent.\"\n };\n }\n return pageAgentBridge.plan(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 \"../core/executor\";\nimport { collectSnapshot } from \"../core/observer\";\nimport { planNextAction } from \"../core/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 lastError: session.lastError\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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
|
|
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 const el = mustFind(action.selector);\n if ((el as HTMLButtonElement).disabled) {\n throw new Error(`Element is disabled: ${action.selector}`);\n }\n el.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 if (input.value.indexOf(action.text) === -1) {\n throw new Error(`Type verification failed: value did not update for ${action.selector}`);\n }\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 if (!value) {\n throw new Error(`Extract returned empty text from ${action.selector}`);\n }\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\") return false;\n const style = window.getComputedStyle(el);\n if (style.display === \"none\" || style.visibility === \"hidden\" || style.opacity === \"0\") return false;\n // Zero-dimension elements are functionally hidden\n const rect = el.getBoundingClientRect();\n return rect.width > 0 || rect.height > 0;\n}\n\nfunction isInViewport(el: HTMLElement): boolean {\n const rect = el.getBoundingClientRect();\n return (\n rect.bottom > 0 &&\n rect.top < window.innerHeight &&\n rect.right > 0 &&\n rect.left < window.innerWidth\n );\n}\n\n/** Resolve the visible label text via for/id, aria-labelledby, aria-label, or wrapping <label>. */\nfunction getAssociatedLabel(el: HTMLElement): string {\n if (el.id) {\n const label = document.querySelector<HTMLLabelElement>(`label[for=\"${CSS.escape(el.id)}\"]`);\n if (label) return label.innerText.trim();\n }\n\n const labelledBy = el.getAttribute(\"aria-labelledby\");\n if (labelledBy) {\n const labelEl = document.getElementById(labelledBy);\n if (labelEl) return labelEl.innerText.trim();\n }\n\n const ariaLabel = el.getAttribute(\"aria-label\");\n if (ariaLabel) return ariaLabel.trim();\n\n const parentLabel = el.closest(\"label\");\n if (parentLabel) {\n return Array.from(parentLabel.childNodes)\n .filter((n) => n.nodeType === Node.TEXT_NODE)\n .map((n) => n.textContent?.trim() ?? \"\")\n .filter(Boolean)\n .join(\" \");\n }\n\n return \"\";\n}\n\nexport function collectSnapshot(): PageSnapshot {\n const allNodes = Array.from(\n document.querySelectorAll<HTMLElement>(CANDIDATE_SELECTOR)\n ).filter(isVisible);\n\n // In-viewport elements first so the model sees the most relevant candidates first\n const inView = allNodes.filter(isInViewport);\n const offScreen = allNodes.filter((el) => !isInViewport(el));\n const nodes = [...inView, ...offScreen].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 const associatedLabel = getAssociatedLabel(node);\n return {\n selector: cssPath(node),\n role: node.getAttribute(\"role\") ?? node.tagName.toLowerCase(),\n text: (node.innerText || node.getAttribute(\"name\") || \"\").trim().slice(0, 120),\n placeholder: placeholder || undefined,\n label: associatedLabel || 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, PlannerResult } from \"../shared/contracts\";\n\ntype WebLLMBridge = {\n // Bridge may return either PlannerResult (new, with reflection) or AgentAction (legacy)\n plan(input: PlannerInput, modelId?: string): Promise<PlannerResult | 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 (c.label?.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.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.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.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\n/** Normalize whatever a bridge returns into a PlannerResult. */\nfunction toPlannerResult(raw: PlannerResult | AgentAction): PlannerResult {\n // New format: has an `action` key that is an object\n if (\"action\" in raw && typeof (raw as PlannerResult).action === \"object\") {\n return raw as PlannerResult;\n }\n // Legacy format: bare AgentAction\n return { action: raw as AgentAction };\n}\n\nexport async function planNextAction(config: PlannerConfig, input: PlannerInput): Promise<PlannerResult> {\n if (config.kind === \"heuristic\") {\n return { action: heuristicPlan(input) };\n }\n\n const bridge = (window as Window & { __browserAgentWebLLM?: WebLLMBridge }).__browserAgentWebLLM;\n if (!bridge) {\n return {\n action: {\n type: \"done\",\n reason: \"WebLLM bridge is not configured. Use heuristic mode or wire a WebLLM bridge implementation.\"\n }\n };\n }\n\n const raw = await bridge.plan({ ...input, systemPrompt: config.systemPrompt }, config.modelId);\n return toPlannerResult(raw);\n}\n", "import type { AgentSession, ContentCommand, ContentResult } from \"../shared/contracts\";\nimport { assessRisk } from \"../shared/safety\";\nimport { executeAction } from \"../core/executor\";\nimport { collectSnapshot } from \"../core/observer\";\nimport { planNextAction } from \"../core/planner\";\n\nlet stopped = false;\n\nasync function runTick(session: AgentSession): Promise<ContentResult> {\n const snapshot = collectSnapshot();\n const plannerResult = await planNextAction(session.planner, {\n goal: session.goal,\n snapshot,\n history: session.history,\n lastError: session.lastError,\n memory: session.memory\n });\n\n const { action } = plannerResult;\n const reflection = plannerResult.evaluation !== undefined || plannerResult.memory !== undefined || plannerResult.nextGoal !== undefined\n ? { evaluation: plannerResult.evaluation, memory: plannerResult.memory, nextGoal: plannerResult.nextGoal }\n : undefined;\n\n const risk = assessRisk(action);\n if (risk === \"blocked\") {\n return { status: \"blocked\", action, message: `Blocked action: ${JSON.stringify(action)}`, reflection };\n }\n\n if (session.mode === \"human-approved\" && risk === \"review\") {\n return { status: \"needs_approval\", action, message: `Approval needed for ${action.type}`, reflection };\n }\n\n if (action.type === \"done\") {\n return { status: \"done\", action, message: action.reason, reflection };\n }\n\n const message = await executeAction(action);\n return { status: \"executed\", action, message, reflection };\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
6
|
"names": ["input"]
|
|
7
7
|
}
|