@akshayram1/omnibrowser-agent 0.2.2 → 0.2.6
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/.github/workflows/ci.yml +41 -0
- package/README.md +9 -32
- package/dist/content.js +76 -36
- package/dist/content.js.map +3 -3
- package/dist/lib.js +168 -57
- package/dist/lib.js.map +3 -3
- package/dist/manifest.json +4 -4
- package/dist/popup.html +0 -1
- package/dist/types/core/executor.d.ts +2 -0
- package/dist/types/core/observer.d.ts +2 -0
- package/dist/types/core/planner.d.ts +2 -0
- package/dist/types/lib/index.d.ts +2 -1
- package/dist/types/shared/contracts.d.ts +31 -1
- package/dist/types/shared/parse-action.d.ts +17 -0
- package/docs/ARCHITECTURE.md +6 -14
- package/docs/DEPLOYMENT.md +67 -0
- package/docs/EMBEDDING.md +21 -42
- package/docs/ROADMAP.md +0 -1
- package/index.html +294 -84
- package/package.json +4 -3
- package/styles.css +287 -97
- package/vercel.json +2 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
ci:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: '22'
|
|
21
|
+
registry-url: 'https://registry.npmjs.org'
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: npm install
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: npm test
|
|
28
|
+
|
|
29
|
+
- name: Bump patch version
|
|
30
|
+
id: bump
|
|
31
|
+
run: |
|
|
32
|
+
NEW_VERSION=$(npm version patch --no-git-tag-version)
|
|
33
|
+
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
34
|
+
|
|
35
|
+
- name: Commit version bump
|
|
36
|
+
run: |
|
|
37
|
+
git config user.name "github-actions[bot]"
|
|
38
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
39
|
+
git add package.json
|
|
40
|
+
git commit -m "chore: bump version to ${{ steps.bump.outputs.version }} [skip ci]"
|
|
41
|
+
git push
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Local-first open-source browser AI operator using in-browser planning and page a
|
|
|
20
20
|
|
|
21
21
|
- MV3 browser extension runtime
|
|
22
22
|
- TypeScript + esbuild
|
|
23
|
-
- Pluggable planner bridges: WebLLM
|
|
23
|
+
- Pluggable planner bridges: WebLLM (local, in-browser)
|
|
24
24
|
|
|
25
25
|
## Project structure
|
|
26
26
|
|
|
@@ -139,8 +139,12 @@ It is preconfigured to use `webllm` planner mode and loads `@mlc-ai/web-llm` fro
|
|
|
139
139
|
|
|
140
140
|
### v0.2.2
|
|
141
141
|
|
|
142
|
-
-
|
|
143
|
-
-
|
|
142
|
+
- SDK/extension separation: core logic moved to `src/core/` shared between extension and npm library
|
|
143
|
+
- 22 unit tests across planner and safety modules
|
|
144
|
+
- Action verification in executor (disabled-check, value-verify, empty-check)
|
|
145
|
+
- `CandidateElement.label` from associated `<label>` elements
|
|
146
|
+
- Retry loop with `lastError` fed back to planner on failure
|
|
147
|
+
- `parseAction` utility exported from the library
|
|
144
148
|
|
|
145
149
|
### v0.2.0
|
|
146
150
|
|
|
@@ -164,39 +168,12 @@ It is preconfigured to use `webllm` planner mode and loads `@mlc-ai/web-llm` fro
|
|
|
164
168
|
| Mode | Description |
|
|
165
169
|
|---|---|
|
|
166
170
|
| `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
|
|
171
|
-
|
|
172
|
-
```ts
|
|
173
|
-
import { PageAgent } from "page-agent";
|
|
174
|
-
|
|
175
|
-
const pa = new PageAgent({
|
|
176
|
-
baseURL: "https://api.openai.com/v1",
|
|
177
|
-
model: "gpt-4o",
|
|
178
|
-
apiKey: "sk-..."
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
window.__browserAgentPageAgent = {
|
|
182
|
-
async plan(input) {
|
|
183
|
-
const result = await pa.execute(input.goal);
|
|
184
|
-
return { type: "done", reason: result.data };
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
Then configure:
|
|
190
|
-
|
|
191
|
-
```ts
|
|
192
|
-
planner: { kind: "page-agent" }
|
|
193
|
-
```
|
|
171
|
+
| `webllm` | Delegates to a local WebLLM bridge on `window.__browserAgentWebLLM`. Fully private, no API calls, runs on-device via WebGPU. |
|
|
194
172
|
|
|
195
173
|
## Notes
|
|
196
174
|
|
|
197
175
|
- Local inference has no API usage charges, but uses device CPU/GPU/memory.
|
|
198
|
-
- `webllm` mode expects a
|
|
199
|
-
- `page-agent` mode expects a bridge on `window.__browserAgentPageAgent`. The `page-agent` package is not bundled — bring your own instance.
|
|
176
|
+
- `webllm` mode expects a bridge implementation attached to `window.__browserAgentWebLLM`. See `docs/EMBEDDING.md` for a complete example.
|
|
200
177
|
|
|
201
178
|
## Roadmap
|
|
202
179
|
|
package/dist/content.js
CHANGED
|
@@ -33,7 +33,7 @@ function assessRisk(action) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
// src/
|
|
36
|
+
// src/core/executor.ts
|
|
37
37
|
function mustFind(selector) {
|
|
38
38
|
const node = document.querySelector(selector);
|
|
39
39
|
if (!(node instanceof HTMLElement)) {
|
|
@@ -48,7 +48,11 @@ function dispatchInputEvents(el) {
|
|
|
48
48
|
async function executeAction(action) {
|
|
49
49
|
switch (action.type) {
|
|
50
50
|
case "click": {
|
|
51
|
-
mustFind(action.selector)
|
|
51
|
+
const el = mustFind(action.selector);
|
|
52
|
+
if (el.disabled) {
|
|
53
|
+
throw new Error(`Element is disabled: ${action.selector}`);
|
|
54
|
+
}
|
|
55
|
+
el.click();
|
|
52
56
|
return `Clicked ${action.selector}`;
|
|
53
57
|
}
|
|
54
58
|
case "type": {
|
|
@@ -60,6 +64,9 @@ async function executeAction(action) {
|
|
|
60
64
|
}
|
|
61
65
|
input.value = `${input.value}${action.text}`;
|
|
62
66
|
dispatchInputEvents(input);
|
|
67
|
+
if (input.value.indexOf(action.text) === -1) {
|
|
68
|
+
throw new Error(`Type verification failed: value did not update for ${action.selector}`);
|
|
69
|
+
}
|
|
63
70
|
return `Typed into ${action.selector}`;
|
|
64
71
|
}
|
|
65
72
|
case "navigate": {
|
|
@@ -68,6 +75,9 @@ async function executeAction(action) {
|
|
|
68
75
|
}
|
|
69
76
|
case "extract": {
|
|
70
77
|
const value = mustFind(action.selector).innerText.trim();
|
|
78
|
+
if (!value) {
|
|
79
|
+
throw new Error(`Extract returned empty text from ${action.selector}`);
|
|
80
|
+
}
|
|
71
81
|
return `${action.label}: ${value}`;
|
|
72
82
|
}
|
|
73
83
|
case "scroll": {
|
|
@@ -91,7 +101,7 @@ async function executeAction(action) {
|
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
103
|
|
|
94
|
-
// src/
|
|
104
|
+
// src/core/observer.ts
|
|
95
105
|
var CANDIDATE_SELECTOR = "a,button,input,textarea,select,[role='button'],[role='link'],[contenteditable='true']";
|
|
96
106
|
var MAX_CANDIDATES = 60;
|
|
97
107
|
function cssPath(element) {
|
|
@@ -122,23 +132,50 @@ function cssPath(element) {
|
|
|
122
132
|
return parts.join(" > ");
|
|
123
133
|
}
|
|
124
134
|
function isVisible(el) {
|
|
125
|
-
if (el.offsetParent === null && el.tagName !== "BODY")
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
135
|
+
if (el.offsetParent === null && el.tagName !== "BODY") return false;
|
|
128
136
|
const style = window.getComputedStyle(el);
|
|
129
|
-
|
|
137
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;
|
|
138
|
+
const rect = el.getBoundingClientRect();
|
|
139
|
+
return rect.width > 0 || rect.height > 0;
|
|
140
|
+
}
|
|
141
|
+
function isInViewport(el) {
|
|
142
|
+
const rect = el.getBoundingClientRect();
|
|
143
|
+
return rect.bottom > 0 && rect.top < window.innerHeight && rect.right > 0 && rect.left < window.innerWidth;
|
|
144
|
+
}
|
|
145
|
+
function getAssociatedLabel(el) {
|
|
146
|
+
if (el.id) {
|
|
147
|
+
const label = document.querySelector(`label[for="${CSS.escape(el.id)}"]`);
|
|
148
|
+
if (label) return label.innerText.trim();
|
|
149
|
+
}
|
|
150
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
151
|
+
if (labelledBy) {
|
|
152
|
+
const labelEl = document.getElementById(labelledBy);
|
|
153
|
+
if (labelEl) return labelEl.innerText.trim();
|
|
154
|
+
}
|
|
155
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
156
|
+
if (ariaLabel) return ariaLabel.trim();
|
|
157
|
+
const parentLabel = el.closest("label");
|
|
158
|
+
if (parentLabel) {
|
|
159
|
+
return Array.from(parentLabel.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => n.textContent?.trim() ?? "").filter(Boolean).join(" ");
|
|
160
|
+
}
|
|
161
|
+
return "";
|
|
130
162
|
}
|
|
131
163
|
function collectSnapshot() {
|
|
132
|
-
const
|
|
164
|
+
const allNodes = Array.from(
|
|
133
165
|
document.querySelectorAll(CANDIDATE_SELECTOR)
|
|
134
|
-
).filter(isVisible)
|
|
166
|
+
).filter(isVisible);
|
|
167
|
+
const inView = allNodes.filter(isInViewport);
|
|
168
|
+
const offScreen = allNodes.filter((el) => !isInViewport(el));
|
|
169
|
+
const nodes = [...inView, ...offScreen].slice(0, MAX_CANDIDATES);
|
|
135
170
|
const candidates = nodes.map((node) => {
|
|
136
171
|
const placeholder = node.placeholder?.trim() || node.getAttribute("placeholder")?.trim();
|
|
172
|
+
const associatedLabel = getAssociatedLabel(node);
|
|
137
173
|
return {
|
|
138
174
|
selector: cssPath(node),
|
|
139
175
|
role: node.getAttribute("role") ?? node.tagName.toLowerCase(),
|
|
140
|
-
text: (node.innerText || node.getAttribute("
|
|
141
|
-
placeholder: placeholder || void 0
|
|
176
|
+
text: (node.innerText || node.getAttribute("name") || "").trim().slice(0, 120),
|
|
177
|
+
placeholder: placeholder || void 0,
|
|
178
|
+
label: associatedLabel || void 0
|
|
142
179
|
};
|
|
143
180
|
});
|
|
144
181
|
const textPreview = document.body.innerText.replace(/\s+/g, " ").trim().slice(0, 1500);
|
|
@@ -150,7 +187,7 @@ function collectSnapshot() {
|
|
|
150
187
|
};
|
|
151
188
|
}
|
|
152
189
|
|
|
153
|
-
// src/
|
|
190
|
+
// src/core/planner.ts
|
|
154
191
|
var URL_PATTERN = /(?:go to|navigate to|open)\s+(https?:\/\/\S+)/i;
|
|
155
192
|
var SEARCH_PATTERN = /search(?:\s+for)?\s+(.+)/i;
|
|
156
193
|
var FILL_PATTERN = /(?:fill|type|enter)\s+"?([^"]+)"?\s+(?:in(?:to)?|for|on)\s+(.+)/i;
|
|
@@ -158,7 +195,7 @@ var CLICK_PATTERN = /click(?:\s+(?:on|the))?\s+(.+)/i;
|
|
|
158
195
|
function findByText(candidates, text) {
|
|
159
196
|
const lower = text.toLowerCase();
|
|
160
197
|
return candidates.find(
|
|
161
|
-
(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)
|
|
162
199
|
);
|
|
163
200
|
}
|
|
164
201
|
function findInput(candidates) {
|
|
@@ -182,14 +219,14 @@ function heuristicPlan(input) {
|
|
|
182
219
|
const [, text, fieldHint] = fillMatch;
|
|
183
220
|
const target = findByText(snapshot.candidates, fieldHint) ?? findInput(snapshot.candidates);
|
|
184
221
|
if (target) {
|
|
185
|
-
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 };
|
|
186
223
|
}
|
|
187
224
|
}
|
|
188
225
|
const searchMatch = goal.match(SEARCH_PATTERN);
|
|
189
226
|
if (searchMatch) {
|
|
190
227
|
const input2 = findInput(snapshot.candidates);
|
|
191
228
|
if (input2) {
|
|
192
|
-
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 };
|
|
193
230
|
}
|
|
194
231
|
}
|
|
195
232
|
const clickMatch = goal.match(CLICK_PATTERN);
|
|
@@ -203,58 +240,61 @@ function heuristicPlan(input) {
|
|
|
203
240
|
const firstButton = findButton(snapshot.candidates);
|
|
204
241
|
if (firstInput && !history.some((h) => h.startsWith("Typed"))) {
|
|
205
242
|
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 };
|
|
243
|
+
return { type: "type", selector: firstInput.selector, text: searchTerm, clearFirst: true, label: firstInput.label || firstInput.text || firstInput.placeholder };
|
|
207
244
|
}
|
|
208
245
|
if (firstButton && !history.some((h) => h.startsWith("Clicked"))) {
|
|
209
246
|
return { type: "click", selector: firstButton.selector, label: firstButton.text };
|
|
210
247
|
}
|
|
211
248
|
return { type: "done", reason: "No further heuristic actions available" };
|
|
212
249
|
}
|
|
250
|
+
function toPlannerResult(raw) {
|
|
251
|
+
if ("action" in raw && typeof raw.action === "object") {
|
|
252
|
+
return raw;
|
|
253
|
+
}
|
|
254
|
+
return { action: raw };
|
|
255
|
+
}
|
|
213
256
|
async function planNextAction(config, input) {
|
|
214
257
|
if (config.kind === "heuristic") {
|
|
215
|
-
return heuristicPlan(input);
|
|
216
|
-
}
|
|
217
|
-
if (config.kind === "page-agent") {
|
|
218
|
-
const pageAgentBridge = window.__browserAgentPageAgent;
|
|
219
|
-
if (!pageAgentBridge) {
|
|
220
|
-
return {
|
|
221
|
-
type: "done",
|
|
222
|
-
reason: "page-agent bridge is not configured. Assign a PageAgentBridge to window.__browserAgentPageAgent."
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
return pageAgentBridge.plan(input);
|
|
258
|
+
return { action: heuristicPlan(input) };
|
|
226
259
|
}
|
|
227
260
|
const bridge = window.__browserAgentWebLLM;
|
|
228
261
|
if (!bridge) {
|
|
229
262
|
return {
|
|
230
|
-
|
|
231
|
-
|
|
263
|
+
action: {
|
|
264
|
+
type: "done",
|
|
265
|
+
reason: "WebLLM bridge is not configured. Use heuristic mode or wire a WebLLM bridge implementation."
|
|
266
|
+
}
|
|
232
267
|
};
|
|
233
268
|
}
|
|
234
|
-
|
|
269
|
+
const raw = await bridge.plan(input, config.modelId);
|
|
270
|
+
return toPlannerResult(raw);
|
|
235
271
|
}
|
|
236
272
|
|
|
237
273
|
// src/content/index.ts
|
|
238
274
|
var stopped = false;
|
|
239
275
|
async function runTick(session) {
|
|
240
276
|
const snapshot = collectSnapshot();
|
|
241
|
-
const
|
|
277
|
+
const plannerResult = await planNextAction(session.planner, {
|
|
242
278
|
goal: session.goal,
|
|
243
279
|
snapshot,
|
|
244
|
-
history: session.history
|
|
280
|
+
history: session.history,
|
|
281
|
+
lastError: session.lastError,
|
|
282
|
+
memory: session.memory
|
|
245
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;
|
|
246
286
|
const risk = assessRisk(action);
|
|
247
287
|
if (risk === "blocked") {
|
|
248
|
-
return { status: "blocked", action, message: `Blocked action: ${JSON.stringify(action)}
|
|
288
|
+
return { status: "blocked", action, message: `Blocked action: ${JSON.stringify(action)}`, reflection };
|
|
249
289
|
}
|
|
250
290
|
if (session.mode === "human-approved" && risk === "review") {
|
|
251
|
-
return { status: "needs_approval", action, message: `Approval needed for ${action.type}
|
|
291
|
+
return { status: "needs_approval", action, message: `Approval needed for ${action.type}`, reflection };
|
|
252
292
|
}
|
|
253
293
|
if (action.type === "done") {
|
|
254
|
-
return { status: "done", action, message: action.reason };
|
|
294
|
+
return { status: "done", action, message: action.reason, reflection };
|
|
255
295
|
}
|
|
256
296
|
const message = await executeAction(action);
|
|
257
|
-
return { status: "executed", action, message };
|
|
297
|
+
return { status: "executed", action, message, reflection };
|
|
258
298
|
}
|
|
259
299
|
async function executePendingAction(session) {
|
|
260
300
|
if (!session.pendingAction) {
|
package/dist/content.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/shared/safety.ts", "../src/
|
|
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\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 \"./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": ";AAEA,IAAM,iBAAiB;AAEvB,SAAS,iBAAiB,MAAwB;AAChD,SAAO,QAAQ,QAAQ,eAAe,KAAK,IAAI;AACjD;AAEO,SAAS,WAAW,QAAgC;AACzD,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK,YAAY;AACf,UAAI;AACF,cAAM,OAAO,IAAI,IAAI,OAAO,GAAG;AAC/B,YAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,SAAS,KAAK,QAAQ,GAAG;AAChD,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AACH,aAAO,iBAAiB,OAAO,KAAK,IAAI,WAAW;AAAA,IACrD,KAAK;AACH,aAAO,iBAAiB,OAAO,KAAK,IAAI,WAAW;AAAA,IACrD,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;;;AClCA,SAAS,SAAS,UAA+B;AAC/C,QAAM,OAAO,SAAS,cAAc,QAAQ;AAC5C,MAAI,EAAE,gBAAgB,cAAc;AAClC,UAAM,IAAI,MAAM,uBAAuB,QAAQ,EAAE;AAAA,EACnD;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,IAAkD;AAC7E,KAAG,cAAc,IAAI,WAAW,SAAS,EAAE,SAAS,MAAM,YAAY,KAAK,CAAC,CAAC;AAC7E,KAAG,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;AACzD;AAEA,eAAsB,cAAc,QAAsC;AACxE,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK,SAAS;AACZ,
|
|
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, 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, 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
|
}
|