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