@akshayram1/omnibrowser-agent 0.2.1 → 0.2.2
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 +42 -3
- package/dist/content.js +10 -0
- package/dist/content.js.map +2 -2
- package/dist/lib.js +10 -0
- package/dist/lib.js.map +2 -2
- package/dist/popup.html +1 -0
- package/dist/types/shared/contracts.d.ts +1 -1
- package/docs/ARCHITECTURE.md +24 -8
- package/docs/EMBEDDING.md +34 -0
- package/docs/ROADMAP.md +10 -2
- package/index.html +207 -0
- package/package.json +1 -1
- package/styles.css +294 -0
- package/vercel.json +4 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# omnibrowser-agent
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
|
-
[](package.json)
|
|
5
5
|
|
|
6
6
|
Local-first open-source browser AI operator using in-browser planning and page actions.
|
|
7
7
|
|
|
@@ -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
|
|
23
|
+
- Pluggable planner bridges: WebLLM and page-agent
|
|
24
24
|
|
|
25
25
|
## Project structure
|
|
26
26
|
|
|
@@ -63,7 +63,7 @@ npm run build
|
|
|
63
63
|
## Use as a web library
|
|
64
64
|
|
|
65
65
|
```ts
|
|
66
|
-
import { createBrowserAgent } from "@
|
|
66
|
+
import { createBrowserAgent } from "@akshayram1/omnibrowser-agent";
|
|
67
67
|
|
|
68
68
|
const agent = createBrowserAgent({
|
|
69
69
|
goal: "Open CRM and find customer John Smith",
|
|
@@ -137,6 +137,11 @@ It is preconfigured to use `webllm` planner mode and loads `@mlc-ai/web-llm` fro
|
|
|
137
137
|
|
|
138
138
|
## Changelog
|
|
139
139
|
|
|
140
|
+
### v0.2.2
|
|
141
|
+
|
|
142
|
+
- **page-agent planner**: added `"page-agent"` as a third `PlannerKind`, backed by a `window.__browserAgentPageAgent` bridge (same zero-deps pattern as WebLLM)
|
|
143
|
+
- **Popup**: added page-agent option to the planner dropdown
|
|
144
|
+
|
|
140
145
|
### v0.2.0
|
|
141
146
|
|
|
142
147
|
- **New actions**: `scroll` and `focus`
|
|
@@ -154,10 +159,44 @@ It is preconfigured to use `webllm` planner mode and loads `@mlc-ai/web-llm` fro
|
|
|
154
159
|
- Heuristic + WebLLM planner switch
|
|
155
160
|
- Human-approved mode
|
|
156
161
|
|
|
162
|
+
## Planner modes
|
|
163
|
+
|
|
164
|
+
| Mode | Description |
|
|
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
|
|
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
|
+
```
|
|
194
|
+
|
|
157
195
|
## Notes
|
|
158
196
|
|
|
159
197
|
- Local inference has no API usage charges, but uses device CPU/GPU/memory.
|
|
160
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.
|
|
161
200
|
|
|
162
201
|
## Roadmap
|
|
163
202
|
|
package/dist/content.js
CHANGED
|
@@ -214,6 +214,16 @@ async function planNextAction(config, input) {
|
|
|
214
214
|
if (config.kind === "heuristic") {
|
|
215
215
|
return heuristicPlan(input);
|
|
216
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);
|
|
226
|
+
}
|
|
217
227
|
const bridge = window.__browserAgentWebLLM;
|
|
218
228
|
if (!bridge) {
|
|
219
229
|
return {
|
package/dist/content.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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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
|
|
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": "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",
|
|
6
6
|
"names": ["input"]
|
|
7
7
|
}
|
package/dist/lib.js
CHANGED
|
@@ -179,6 +179,16 @@ async function planNextAction(config, input) {
|
|
|
179
179
|
if (config.kind === "heuristic") {
|
|
180
180
|
return heuristicPlan(input);
|
|
181
181
|
}
|
|
182
|
+
if (config.kind === "page-agent") {
|
|
183
|
+
const pageAgentBridge = window.__browserAgentPageAgent;
|
|
184
|
+
if (!pageAgentBridge) {
|
|
185
|
+
return {
|
|
186
|
+
type: "done",
|
|
187
|
+
reason: "page-agent bridge is not configured. Assign a PageAgentBridge to window.__browserAgentPageAgent."
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return pageAgentBridge.plan(input);
|
|
191
|
+
}
|
|
182
192
|
const bridge = window.__browserAgentWebLLM;
|
|
183
193
|
if (!bridge) {
|
|
184
194
|
return {
|
package/dist/lib.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/content/executor.ts", "../src/content/pageObserver.ts", "../src/content/planner.ts", "../src/shared/safety.ts", "../src/lib/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AgentAction } from \"../shared/contracts\";\n\nfunction mustFind(selector: string): HTMLElement {\n const node = document.querySelector(selector);\n if (!(node instanceof HTMLElement)) {\n throw new Error(`Selector not found: ${selector}`);\n }\n return node;\n}\n\nfunction dispatchInputEvents(el: HTMLInputElement | HTMLTextAreaElement): void {\n el.dispatchEvent(new InputEvent(\"input\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true }));\n}\n\nexport async function executeAction(action: AgentAction): Promise<string> {\n switch (action.type) {\n case \"click\": {\n mustFind(action.selector).click();\n return `Clicked ${action.selector}`;\n }\n case \"type\": {\n const input = mustFind(action.selector) as HTMLInputElement | HTMLTextAreaElement;\n input.focus();\n if (action.clearFirst) {\n input.value = \"\";\n dispatchInputEvents(input);\n }\n input.value = `${input.value}${action.text}`;\n dispatchInputEvents(input);\n return `Typed into ${action.selector}`;\n }\n case \"navigate\": {\n window.location.href = action.url;\n return `Navigated to ${action.url}`;\n }\n case \"extract\": {\n const value = mustFind(action.selector).innerText.trim();\n return `${action.label}: ${value}`;\n }\n case \"scroll\": {\n const target = action.selector ? mustFind(action.selector) : document.documentElement;\n target.scrollBy({ top: action.deltaY, behavior: \"smooth\" });\n return `Scrolled ${action.deltaY > 0 ? \"down\" : \"up\"} ${Math.abs(action.deltaY)}px`;\n }\n case \"focus\": {\n mustFind(action.selector).focus();\n return `Focused ${action.selector}`;\n }\n case \"wait\": {\n await new Promise((resolve) => setTimeout(resolve, action.ms));\n return `Waited ${action.ms}ms`;\n }\n case \"done\": {\n return action.reason;\n }\n default:\n return \"No-op\";\n }\n}\n", "import type { CandidateElement, PageSnapshot } from \"../shared/contracts\";\n\nconst CANDIDATE_SELECTOR =\n \"a,button,input,textarea,select,[role='button'],[role='link'],[contenteditable='true']\";\n\nconst MAX_CANDIDATES = 60;\n\nfunction cssPath(element: Element): string {\n if (!(element instanceof HTMLElement)) {\n return element.tagName.toLowerCase();\n }\n\n if (element.id) {\n return `#${CSS.escape(element.id)}`;\n }\n\n const parts: string[] = [];\n let current: HTMLElement | null = element;\n while (current && parts.length < 4) {\n let part = current.tagName.toLowerCase();\n if (current.classList.length > 0) {\n part += `.${Array.from(current.classList).slice(0, 2).map(CSS.escape).join(\".\")}`;\n }\n const parent: HTMLElement | null = current.parentElement;\n if (parent) {\n const siblings = Array.from(parent.children).filter((s: Element) => s.tagName === current!.tagName);\n if (siblings.length > 1) {\n const index = siblings.indexOf(current) + 1;\n part += `:nth-of-type(${index})`;\n }\n }\n parts.unshift(part);\n current = parent;\n }\n return parts.join(\" > \");\n}\n\nfunction isVisible(el: HTMLElement): boolean {\n if (el.offsetParent === null && el.tagName !== \"BODY\") {\n return false;\n }\n const style = window.getComputedStyle(el);\n return style.display !== \"none\" && style.visibility !== \"hidden\" && style.opacity !== \"0\";\n}\n\nexport function collectSnapshot(): PageSnapshot {\n const nodes = Array.from(\n document.querySelectorAll<HTMLElement>(CANDIDATE_SELECTOR)\n )\n .filter(isVisible)\n .slice(0, MAX_CANDIDATES);\n\n const candidates: CandidateElement[] = nodes.map((node) => {\n const placeholder =\n (node as HTMLInputElement).placeholder?.trim() || node.getAttribute(\"placeholder\")?.trim();\n return {\n selector: cssPath(node),\n role: node.getAttribute(\"role\") ?? node.tagName.toLowerCase(),\n text: (node.innerText || node.getAttribute(\"aria-label\") || node.getAttribute(\"name\") || \"\").trim().slice(0, 120),\n placeholder: placeholder || undefined\n };\n });\n\n const textPreview = document.body.innerText.replace(/\\s+/g, \" \").trim().slice(0, 1500);\n\n return {\n url: window.location.href,\n title: document.title,\n textPreview,\n candidates\n };\n}\n", "import type { AgentAction, CandidateElement, PlannerConfig, PlannerInput } from \"../shared/contracts\";\n\ntype WebLLMBridge = {\n plan(input: PlannerInput, modelId?: string): Promise<AgentAction>;\n};\n\nconst URL_PATTERN = /(?:go to|navigate to|open)\\s+(https?:\\/\\/\\S+)/i;\nconst SEARCH_PATTERN = /search(?:\\s+for)?\\s+(.+)/i;\nconst FILL_PATTERN = /(?:fill|type|enter)\\s+\"?([^\"]+)\"?\\s+(?:in(?:to)?|for|on)\\s+(.+)/i;\nconst CLICK_PATTERN = /click(?:\\s+(?:on|the))?\\s+(.+)/i;\n\nfunction findByText(candidates: CandidateElement[], text: string): CandidateElement | undefined {\n const lower = text.toLowerCase();\n return candidates.find(\n (c) =>\n c.text.toLowerCase().includes(lower) ||\n (c.placeholder?.toLowerCase().includes(lower) ?? false)\n );\n}\n\nfunction findInput(candidates: CandidateElement[]): CandidateElement | undefined {\n return candidates.find(\n (c) => c.role === \"input\" || c.role === \"textarea\" || c.selector.includes(\"input\") || c.selector.includes(\"textarea\")\n );\n}\n\nfunction findButton(candidates: CandidateElement[]): CandidateElement | undefined {\n return candidates.find(\n (c) => c.role === \"button\" || c.role === \"a\" || c.selector.includes(\"button\") || c.selector.includes(\"a\")\n );\n}\n\nfunction heuristicPlan(input: PlannerInput): AgentAction {\n const { goal, snapshot, history } = input;\n\n const navMatch = goal.match(URL_PATTERN);\n if (navMatch) {\n return { type: \"navigate\", url: navMatch[1] };\n }\n\n const fillMatch = goal.match(FILL_PATTERN);\n if (fillMatch) {\n const [, text, fieldHint] = fillMatch;\n const target = findByText(snapshot.candidates, fieldHint) ?? findInput(snapshot.candidates);\n if (target) {\n return { type: \"type\", selector: target.selector, text, clearFirst: true, label: target.text || target.placeholder };\n }\n }\n\n const searchMatch = goal.match(SEARCH_PATTERN);\n if (searchMatch) {\n const input = findInput(snapshot.candidates);\n if (input) {\n return { type: \"type\", selector: input.selector, text: searchMatch[1].trim(), clearFirst: true, label: input.text || input.placeholder };\n }\n }\n\n const clickMatch = goal.match(CLICK_PATTERN);\n if (clickMatch) {\n const target = findByText(snapshot.candidates, clickMatch[1].trim());\n if (target) {\n return { type: \"click\", selector: target.selector, label: target.text };\n }\n }\n\n const firstInput = findInput(snapshot.candidates);\n const firstButton = findButton(snapshot.candidates);\n\n if (firstInput && !history.some((h) => h.startsWith(\"Typed\"))) {\n const searchTerm = goal.replace(/.*(?:search|find|look up)\\s+/i, \"\").trim();\n return { type: \"type\", selector: firstInput.selector, text: searchTerm, clearFirst: true, label: firstInput.text || firstInput.placeholder };\n }\n\n if (firstButton && !history.some((h) => h.startsWith(\"Clicked\"))) {\n return { type: \"click\", selector: firstButton.selector, label: firstButton.text };\n }\n\n return { type: \"done\", reason: \"No further heuristic actions available\" };\n}\n\nexport async function planNextAction(config: PlannerConfig, input: PlannerInput): Promise<AgentAction> {\n if (config.kind === \"heuristic\") {\n return heuristicPlan(input);\n }\n\n const bridge = (window as Window & { __browserAgentWebLLM?: WebLLMBridge }).__browserAgentWebLLM;\n if (!bridge) {\n return {\n type: \"done\",\n reason: \"WebLLM bridge is not configured. Use heuristic mode or wire a local bridge implementation.\"\n };\n }\n\n return bridge.plan(input, config.modelId);\n}\n", "import type { AgentAction, RiskLevel } from \"./contracts\";\n\nconst RISKY_KEYWORDS = /\\b(delete|remove|pay|purchase|submit|confirm|checkout|transfer|withdraw|send)\\b/i;\n\nfunction elementTextRisky(text?: string): boolean {\n return text != null && RISKY_KEYWORDS.test(text);\n}\n\nexport function assessRisk(action: AgentAction): RiskLevel {\n switch (action.type) {\n case \"navigate\": {\n try {\n const next = new URL(action.url);\n if (![\"http:\", \"https:\"].includes(next.protocol)) {\n return \"blocked\";\n }\n } catch {\n return \"blocked\";\n }\n return \"safe\";\n }\n case \"click\":\n return elementTextRisky(action.label) ? \"review\" : \"safe\";\n case \"type\":\n return elementTextRisky(action.label) ? \"review\" : \"safe\";\n case \"focus\":\n case \"scroll\":\n case \"wait\":\n return \"safe\";\n case \"extract\":\n return \"review\";\n case \"done\":\n return \"safe\";\n default:\n return \"review\";\n }\n}\n", "import { executeAction } from \"../content/executor\";\nimport { collectSnapshot } from \"../content/pageObserver\";\nimport { planNextAction } from \"../content/planner\";\nimport type {\n AgentAction,\n AgentSession,\n ContentResult,\n LibraryAgentConfig,\n LibraryAgentEvents,\n PlannerConfig\n} from \"../shared/contracts\";\nimport { assessRisk } from \"../shared/safety\";\n\nconst DEFAULT_PLANNER: PlannerConfig = { kind: \"heuristic\" };\n\nexport class BrowserAgent {\n private session: AgentSession;\n private maxSteps: number;\n private stepDelayMs: number;\n private events: LibraryAgentEvents;\n private isStopped = false;\n private signal?: AbortSignal;\n\n constructor(config: LibraryAgentConfig, events: LibraryAgentEvents = {}) {\n this.session = {\n id: crypto.randomUUID(),\n tabId: null,\n goal: config.goal,\n mode: config.mode ?? \"human-approved\",\n planner: config.planner ?? DEFAULT_PLANNER,\n history: [],\n isRunning: false\n };\n\n this.maxSteps = config.maxSteps ?? 20;\n this.stepDelayMs = config.stepDelayMs ?? 500;\n this.events = events;\n this.signal = config.signal;\n }\n\n getSession(): AgentSession {\n return { ...this.session, history: [...this.session.history] };\n }\n\n get isRunning(): boolean {\n return this.session.isRunning;\n }\n\n get hasPendingAction(): boolean {\n return this.session.pendingAction != null;\n }\n\n async start(): Promise<ContentResult> {\n this.isStopped = false;\n this.session.isRunning = true;\n this.events.onStart?.(this.getSession());\n\n return this.runLoop();\n }\n\n async resume(): Promise<ContentResult> {\n if (this.session.pendingAction) {\n const approvalResult = await this.approvePendingAction();\n if (approvalResult.status === \"error\") {\n return approvalResult;\n }\n }\n this.session.isRunning = true;\n return this.runLoop();\n }\n\n private async runLoop(): Promise<ContentResult> {\n for (let step = 0; step < this.maxSteps; step += 1) {\n if (this.isStopped || !this.session.isRunning) {\n return { status: \"done\", message: \"Stopped\" };\n }\n\n if (this.signal?.aborted) {\n this.session.isRunning = false;\n return { status: \"done\", message: \"Aborted\" };\n }\n\n const result = await this.tick();\n this.session.history.push(result.message);\n this.events.onStep?.(result, this.getSession());\n\n if (result.status === \"needs_approval\") {\n this.session.pendingAction = result.action;\n this.session.isRunning = false;\n if (result.action) {\n this.events.onApprovalRequired?.(result.action, this.getSession());\n }\n return result;\n }\n\n if ([\"done\", \"blocked\", \"error\"].includes(result.status)) {\n this.session.isRunning = false;\n this.events.onDone?.(result, this.getSession());\n return result;\n }\n\n await this.delay(this.stepDelayMs);\n }\n\n const result: ContentResult = { status: \"done\", message: \"Reached max steps\" };\n this.session.history.push(result.message);\n this.session.isRunning = false;\n this.events.onMaxStepsReached?.(this.getSession());\n this.events.onDone?.(result, this.getSession());\n return result;\n }\n\n async approvePendingAction(): Promise<ContentResult> {\n if (!this.session.pendingAction) {\n return { status: \"error\", message: \"No pending action to approve\" };\n }\n\n try {\n const message = await executeAction(this.session.pendingAction);\n const result: ContentResult = {\n status: \"executed\",\n message,\n action: this.session.pendingAction\n };\n this.session.history.push(message);\n this.session.pendingAction = undefined;\n this.session.isRunning = true;\n this.events.onStep?.(result, this.getSession());\n return result;\n } catch (error) {\n this.session.isRunning = false;\n this.events.onError?.(error, this.getSession());\n return { status: \"error\", message: String(error) };\n }\n }\n\n stop(): void {\n this.isStopped = true;\n this.session.isRunning = false;\n }\n\n private async tick(): Promise<ContentResult> {\n try {\n const snapshot = collectSnapshot();\n const action = await planNextAction(this.session.planner, {\n goal: this.session.goal,\n snapshot,\n history: this.session.history\n });\n\n return this.processAction(action);\n } catch (error) {\n this.session.isRunning = false;\n this.events.onError?.(error, this.getSession());\n return { status: \"error\", message: String(error) };\n }\n }\n\n private async processAction(action: AgentAction): Promise<ContentResult> {\n const risk = assessRisk(action);\n if (risk === \"blocked\") {\n return {\n status: \"blocked\",\n action,\n message: `Blocked action: ${JSON.stringify(action)}`\n };\n }\n\n if (this.session.mode === \"human-approved\" && risk === \"review\") {\n return {\n status: \"needs_approval\",\n action,\n message: `Approval needed for ${action.type}`\n };\n }\n\n if (action.type === \"done\") {\n return {\n status: \"done\",\n action,\n message: action.reason\n };\n }\n\n const message = await executeAction(action);\n return { status: \"executed\", action, message };\n }\n\n private async delay(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\nexport function createBrowserAgent(config: LibraryAgentConfig, events?: LibraryAgentEvents): BrowserAgent {\n return new BrowserAgent(config, events);\n}\n\nexport type {\n AgentAction,\n AgentMode,\n AgentSession,\n ContentResult,\n LibraryAgentConfig,\n LibraryAgentEvents,\n PlannerConfig,\n PlannerInput,\n PlannerKind,\n RiskLevel\n} from \"../shared/contracts\";\n"],
|
|
5
|
-
"mappings": ";AAEA,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,eAAS,OAAO,QAAQ,EAAE,MAAM;AAChC,aAAO,WAAW,OAAO,QAAQ;AAAA,IACnC;AAAA,IACA,KAAK,QAAQ;AACX,YAAM,QAAQ,SAAS,OAAO,QAAQ;AACtC,YAAM,MAAM;AACZ,UAAI,OAAO,YAAY;AACrB,cAAM,QAAQ;AACd,4BAAoB,KAAK;AAAA,MAC3B;AACA,YAAM,QAAQ,GAAG,MAAM,KAAK,GAAG,OAAO,IAAI;AAC1C,0BAAoB,KAAK;AACzB,aAAO,cAAc,OAAO,QAAQ;AAAA,IACtC;AAAA,IACA,KAAK,YAAY;AACf,aAAO,SAAS,OAAO,OAAO;AAC9B,aAAO,gBAAgB,OAAO,GAAG;AAAA,IACnC;AAAA,IACA,KAAK,WAAW;AACd,YAAM,QAAQ,SAAS,OAAO,QAAQ,EAAE,UAAU,KAAK;AACvD,aAAO,GAAG,OAAO,KAAK,KAAK,KAAK;AAAA,IAClC;AAAA,IACA,KAAK,UAAU;AACb,YAAM,SAAS,OAAO,WAAW,SAAS,OAAO,QAAQ,IAAI,SAAS;AACtE,aAAO,SAAS,EAAE,KAAK,OAAO,QAAQ,UAAU,SAAS,CAAC;AAC1D,aAAO,YAAY,OAAO,SAAS,IAAI,SAAS,IAAI,IAAI,KAAK,IAAI,OAAO,MAAM,CAAC;AAAA,IACjF;AAAA,IACA,KAAK,SAAS;AACZ,eAAS,OAAO,QAAQ,EAAE,MAAM;AAChC,aAAO,WAAW,OAAO,QAAQ;AAAA,IACnC;AAAA,IACA,KAAK,QAAQ;AACX,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,OAAO,EAAE,CAAC;AAC7D,aAAO,UAAU,OAAO,EAAE;AAAA,IAC5B;AAAA,IACA,KAAK,QAAQ;AACX,aAAO,OAAO;AAAA,IAChB;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;;;ACzDA,IAAM,qBACJ;AAEF,IAAM,iBAAiB;AAEvB,SAAS,QAAQ,SAA0B;AACzC,MAAI,EAAE,mBAAmB,cAAc;AACrC,WAAO,QAAQ,QAAQ,YAAY;AAAA,EACrC;AAEA,MAAI,QAAQ,IAAI;AACd,WAAO,IAAI,IAAI,OAAO,QAAQ,EAAE,CAAC;AAAA,EACnC;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,UAA8B;AAClC,SAAO,WAAW,MAAM,SAAS,GAAG;AAClC,QAAI,OAAO,QAAQ,QAAQ,YAAY;AACvC,QAAI,QAAQ,UAAU,SAAS,GAAG;AAChC,cAAQ,IAAI,MAAM,KAAK,QAAQ,SAAS,EAAE,MAAM,GAAG,CAAC,EAAE,IAAI,IAAI,MAAM,EAAE,KAAK,GAAG,CAAC;AAAA,IACjF;AACA,UAAM,SAA6B,QAAQ;AAC3C,QAAI,QAAQ;AACV,YAAM,WAAW,MAAM,KAAK,OAAO,QAAQ,EAAE,OAAO,CAAC,MAAe,EAAE,YAAY,QAAS,OAAO;AAClG,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,QAAQ,SAAS,QAAQ,OAAO,IAAI;AAC1C,gBAAQ,gBAAgB,KAAK;AAAA,MAC/B;AAAA,IACF;AACA,UAAM,QAAQ,IAAI;AAClB,cAAU;AAAA,EACZ;AACA,SAAO,MAAM,KAAK,KAAK;AACzB;AAEA,SAAS,UAAU,IAA0B;AAC3C,MAAI,GAAG,iBAAiB,QAAQ,GAAG,YAAY,QAAQ;AACrD,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,OAAO,iBAAiB,EAAE;AACxC,SAAO,MAAM,YAAY,UAAU,MAAM,eAAe,YAAY,MAAM,YAAY;AACxF;AAEO,SAAS,kBAAgC;AAC9C,QAAM,QAAQ,MAAM;AAAA,IAClB,SAAS,iBAA8B,kBAAkB;AAAA,EAC3D,EACG,OAAO,SAAS,EAChB,MAAM,GAAG,cAAc;AAE1B,QAAM,aAAiC,MAAM,IAAI,CAAC,SAAS;AACzD,UAAM,cACH,KAA0B,aAAa,KAAK,KAAK,KAAK,aAAa,aAAa,GAAG,KAAK;AAC3F,WAAO;AAAA,MACL,UAAU,QAAQ,IAAI;AAAA,MACtB,MAAM,KAAK,aAAa,MAAM,KAAK,KAAK,QAAQ,YAAY;AAAA,MAC5D,OAAO,KAAK,aAAa,KAAK,aAAa,YAAY,KAAK,KAAK,aAAa,MAAM,KAAK,IAAI,KAAK,EAAE,MAAM,GAAG,GAAG;AAAA,MAChH,aAAa,eAAe;AAAA,IAC9B;AAAA,EACF,CAAC;AAED,QAAM,cAAc,SAAS,KAAK,UAAU,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;AAErF,SAAO;AAAA,IACL,KAAK,OAAO,SAAS;AAAA,IACrB,OAAO,SAAS;AAAA,IAChB;AAAA,IACA;AAAA,EACF;AACF;;;
|
|
4
|
+
"sourcesContent": ["import type { AgentAction } from \"../shared/contracts\";\n\nfunction mustFind(selector: string): HTMLElement {\n const node = document.querySelector(selector);\n if (!(node instanceof HTMLElement)) {\n throw new Error(`Selector not found: ${selector}`);\n }\n return node;\n}\n\nfunction dispatchInputEvents(el: HTMLInputElement | HTMLTextAreaElement): void {\n el.dispatchEvent(new InputEvent(\"input\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true }));\n}\n\nexport async function executeAction(action: AgentAction): Promise<string> {\n switch (action.type) {\n case \"click\": {\n mustFind(action.selector).click();\n return `Clicked ${action.selector}`;\n }\n case \"type\": {\n const input = mustFind(action.selector) as HTMLInputElement | HTMLTextAreaElement;\n input.focus();\n if (action.clearFirst) {\n input.value = \"\";\n dispatchInputEvents(input);\n }\n input.value = `${input.value}${action.text}`;\n dispatchInputEvents(input);\n return `Typed into ${action.selector}`;\n }\n case \"navigate\": {\n window.location.href = action.url;\n return `Navigated to ${action.url}`;\n }\n case \"extract\": {\n const value = mustFind(action.selector).innerText.trim();\n return `${action.label}: ${value}`;\n }\n case \"scroll\": {\n const target = action.selector ? mustFind(action.selector) : document.documentElement;\n target.scrollBy({ top: action.deltaY, behavior: \"smooth\" });\n return `Scrolled ${action.deltaY > 0 ? \"down\" : \"up\"} ${Math.abs(action.deltaY)}px`;\n }\n case \"focus\": {\n mustFind(action.selector).focus();\n return `Focused ${action.selector}`;\n }\n case \"wait\": {\n await new Promise((resolve) => setTimeout(resolve, action.ms));\n return `Waited ${action.ms}ms`;\n }\n case \"done\": {\n return action.reason;\n }\n default:\n return \"No-op\";\n }\n}\n", "import type { CandidateElement, PageSnapshot } from \"../shared/contracts\";\n\nconst CANDIDATE_SELECTOR =\n \"a,button,input,textarea,select,[role='button'],[role='link'],[contenteditable='true']\";\n\nconst MAX_CANDIDATES = 60;\n\nfunction cssPath(element: Element): string {\n if (!(element instanceof HTMLElement)) {\n return element.tagName.toLowerCase();\n }\n\n if (element.id) {\n return `#${CSS.escape(element.id)}`;\n }\n\n const parts: string[] = [];\n let current: HTMLElement | null = element;\n while (current && parts.length < 4) {\n let part = current.tagName.toLowerCase();\n if (current.classList.length > 0) {\n part += `.${Array.from(current.classList).slice(0, 2).map(CSS.escape).join(\".\")}`;\n }\n const parent: HTMLElement | null = current.parentElement;\n if (parent) {\n const siblings = Array.from(parent.children).filter((s: Element) => s.tagName === current!.tagName);\n if (siblings.length > 1) {\n const index = siblings.indexOf(current) + 1;\n part += `:nth-of-type(${index})`;\n }\n }\n parts.unshift(part);\n current = parent;\n }\n return parts.join(\" > \");\n}\n\nfunction isVisible(el: HTMLElement): boolean {\n if (el.offsetParent === null && el.tagName !== \"BODY\") {\n return false;\n }\n const style = window.getComputedStyle(el);\n return style.display !== \"none\" && style.visibility !== \"hidden\" && style.opacity !== \"0\";\n}\n\nexport function collectSnapshot(): PageSnapshot {\n const nodes = Array.from(\n document.querySelectorAll<HTMLElement>(CANDIDATE_SELECTOR)\n )\n .filter(isVisible)\n .slice(0, MAX_CANDIDATES);\n\n const candidates: CandidateElement[] = nodes.map((node) => {\n const placeholder =\n (node as HTMLInputElement).placeholder?.trim() || node.getAttribute(\"placeholder\")?.trim();\n return {\n selector: cssPath(node),\n role: node.getAttribute(\"role\") ?? node.tagName.toLowerCase(),\n text: (node.innerText || node.getAttribute(\"aria-label\") || node.getAttribute(\"name\") || \"\").trim().slice(0, 120),\n placeholder: placeholder || undefined\n };\n });\n\n const textPreview = document.body.innerText.replace(/\\s+/g, \" \").trim().slice(0, 1500);\n\n return {\n url: window.location.href,\n title: document.title,\n textPreview,\n candidates\n };\n}\n", "import type { AgentAction, CandidateElement, PlannerConfig, PlannerInput } from \"../shared/contracts\";\n\ntype WebLLMBridge = {\n plan(input: PlannerInput, modelId?: string): Promise<AgentAction>;\n};\n\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 { AgentAction, RiskLevel } from \"./contracts\";\n\nconst RISKY_KEYWORDS = /\\b(delete|remove|pay|purchase|submit|confirm|checkout|transfer|withdraw|send)\\b/i;\n\nfunction elementTextRisky(text?: string): boolean {\n return text != null && RISKY_KEYWORDS.test(text);\n}\n\nexport function assessRisk(action: AgentAction): RiskLevel {\n switch (action.type) {\n case \"navigate\": {\n try {\n const next = new URL(action.url);\n if (![\"http:\", \"https:\"].includes(next.protocol)) {\n return \"blocked\";\n }\n } catch {\n return \"blocked\";\n }\n return \"safe\";\n }\n case \"click\":\n return elementTextRisky(action.label) ? \"review\" : \"safe\";\n case \"type\":\n return elementTextRisky(action.label) ? \"review\" : \"safe\";\n case \"focus\":\n case \"scroll\":\n case \"wait\":\n return \"safe\";\n case \"extract\":\n return \"review\";\n case \"done\":\n return \"safe\";\n default:\n return \"review\";\n }\n}\n", "import { executeAction } from \"../content/executor\";\nimport { collectSnapshot } from \"../content/pageObserver\";\nimport { planNextAction } from \"../content/planner\";\nimport type {\n AgentAction,\n AgentSession,\n ContentResult,\n LibraryAgentConfig,\n LibraryAgentEvents,\n PlannerConfig\n} from \"../shared/contracts\";\nimport { assessRisk } from \"../shared/safety\";\n\nconst DEFAULT_PLANNER: PlannerConfig = { kind: \"heuristic\" };\n\nexport class BrowserAgent {\n private session: AgentSession;\n private maxSteps: number;\n private stepDelayMs: number;\n private events: LibraryAgentEvents;\n private isStopped = false;\n private signal?: AbortSignal;\n\n constructor(config: LibraryAgentConfig, events: LibraryAgentEvents = {}) {\n this.session = {\n id: crypto.randomUUID(),\n tabId: null,\n goal: config.goal,\n mode: config.mode ?? \"human-approved\",\n planner: config.planner ?? DEFAULT_PLANNER,\n history: [],\n isRunning: false\n };\n\n this.maxSteps = config.maxSteps ?? 20;\n this.stepDelayMs = config.stepDelayMs ?? 500;\n this.events = events;\n this.signal = config.signal;\n }\n\n getSession(): AgentSession {\n return { ...this.session, history: [...this.session.history] };\n }\n\n get isRunning(): boolean {\n return this.session.isRunning;\n }\n\n get hasPendingAction(): boolean {\n return this.session.pendingAction != null;\n }\n\n async start(): Promise<ContentResult> {\n this.isStopped = false;\n this.session.isRunning = true;\n this.events.onStart?.(this.getSession());\n\n return this.runLoop();\n }\n\n async resume(): Promise<ContentResult> {\n if (this.session.pendingAction) {\n const approvalResult = await this.approvePendingAction();\n if (approvalResult.status === \"error\") {\n return approvalResult;\n }\n }\n this.session.isRunning = true;\n return this.runLoop();\n }\n\n private async runLoop(): Promise<ContentResult> {\n for (let step = 0; step < this.maxSteps; step += 1) {\n if (this.isStopped || !this.session.isRunning) {\n return { status: \"done\", message: \"Stopped\" };\n }\n\n if (this.signal?.aborted) {\n this.session.isRunning = false;\n return { status: \"done\", message: \"Aborted\" };\n }\n\n const result = await this.tick();\n this.session.history.push(result.message);\n this.events.onStep?.(result, this.getSession());\n\n if (result.status === \"needs_approval\") {\n this.session.pendingAction = result.action;\n this.session.isRunning = false;\n if (result.action) {\n this.events.onApprovalRequired?.(result.action, this.getSession());\n }\n return result;\n }\n\n if ([\"done\", \"blocked\", \"error\"].includes(result.status)) {\n this.session.isRunning = false;\n this.events.onDone?.(result, this.getSession());\n return result;\n }\n\n await this.delay(this.stepDelayMs);\n }\n\n const result: ContentResult = { status: \"done\", message: \"Reached max steps\" };\n this.session.history.push(result.message);\n this.session.isRunning = false;\n this.events.onMaxStepsReached?.(this.getSession());\n this.events.onDone?.(result, this.getSession());\n return result;\n }\n\n async approvePendingAction(): Promise<ContentResult> {\n if (!this.session.pendingAction) {\n return { status: \"error\", message: \"No pending action to approve\" };\n }\n\n try {\n const message = await executeAction(this.session.pendingAction);\n const result: ContentResult = {\n status: \"executed\",\n message,\n action: this.session.pendingAction\n };\n this.session.history.push(message);\n this.session.pendingAction = undefined;\n this.session.isRunning = true;\n this.events.onStep?.(result, this.getSession());\n return result;\n } catch (error) {\n this.session.isRunning = false;\n this.events.onError?.(error, this.getSession());\n return { status: \"error\", message: String(error) };\n }\n }\n\n stop(): void {\n this.isStopped = true;\n this.session.isRunning = false;\n }\n\n private async tick(): Promise<ContentResult> {\n try {\n const snapshot = collectSnapshot();\n const action = await planNextAction(this.session.planner, {\n goal: this.session.goal,\n snapshot,\n history: this.session.history\n });\n\n return this.processAction(action);\n } catch (error) {\n this.session.isRunning = false;\n this.events.onError?.(error, this.getSession());\n return { status: \"error\", message: String(error) };\n }\n }\n\n private async processAction(action: AgentAction): Promise<ContentResult> {\n const risk = assessRisk(action);\n if (risk === \"blocked\") {\n return {\n status: \"blocked\",\n action,\n message: `Blocked action: ${JSON.stringify(action)}`\n };\n }\n\n if (this.session.mode === \"human-approved\" && risk === \"review\") {\n return {\n status: \"needs_approval\",\n action,\n message: `Approval needed for ${action.type}`\n };\n }\n\n if (action.type === \"done\") {\n return {\n status: \"done\",\n action,\n message: action.reason\n };\n }\n\n const message = await executeAction(action);\n return { status: \"executed\", action, message };\n }\n\n private async delay(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\nexport function createBrowserAgent(config: LibraryAgentConfig, events?: LibraryAgentEvents): BrowserAgent {\n return new BrowserAgent(config, events);\n}\n\nexport type {\n AgentAction,\n AgentMode,\n AgentSession,\n ContentResult,\n LibraryAgentConfig,\n LibraryAgentEvents,\n PlannerConfig,\n PlannerInput,\n PlannerKind,\n RiskLevel\n} from \"../shared/contracts\";\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["input", "result"]
|
|
7
7
|
}
|
package/dist/popup.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type AgentMode = "autonomous" | "human-approved";
|
|
2
|
-
export type PlannerKind = "heuristic" | "webllm";
|
|
2
|
+
export type PlannerKind = "heuristic" | "webllm" | "page-agent";
|
|
3
3
|
export type RiskLevel = "safe" | "review" | "blocked";
|
|
4
4
|
export type AgentAction = {
|
|
5
5
|
type: "click";
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# OmniBrowser Agent Architecture (v0.
|
|
1
|
+
# OmniBrowser Agent Architecture (v0.2)
|
|
2
2
|
|
|
3
3
|
## Goals
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
1. Popup UI (`src/popup`)
|
|
13
13
|
- Starts/stops sessions
|
|
14
14
|
- Picks mode (`autonomous`, `human-approved`)
|
|
15
|
-
- Picks planner (`heuristic`, `webllm`)
|
|
15
|
+
- Picks planner (`heuristic`, `webllm`, `page-agent`)
|
|
16
16
|
|
|
17
17
|
2. Background Service Worker (`src/background`)
|
|
18
18
|
- Session state machine per tab
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
3. Content Agent (`src/content`)
|
|
23
23
|
- `pageObserver`: page snapshot extraction
|
|
24
|
-
- `planner`: next-action decision (heuristic/WebLLM)
|
|
24
|
+
- `planner`: next-action decision (heuristic / WebLLM / page-agent)
|
|
25
25
|
- `safety`: risk gating (`safe`, `review`, `blocked`)
|
|
26
26
|
- `executor`: DOM action execution
|
|
27
27
|
|
|
@@ -42,13 +42,29 @@
|
|
|
42
42
|
- Review risky actions (submit/delete/pay-like selectors)
|
|
43
43
|
- In `human-approved` mode, review-level actions require manual approval
|
|
44
44
|
|
|
45
|
-
##
|
|
45
|
+
## Planner Bridges
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
- v0.1 bridge entrypoint: `window.__browserAgentWebLLM.plan(input, modelId)`
|
|
49
|
-
- Full in-extension worker integration is planned for v0.2
|
|
47
|
+
All planner bridges follow the same pattern: an object attached to `window` that implements a `plan()` method returning an `AgentAction`. The core library has zero runtime dependencies — bridge implementations are provided by the consumer.
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
### WebLLM bridge
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
window.__browserAgentWebLLM = {
|
|
53
|
+
async plan(input, modelId) { /* ... */ }
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### page-agent bridge
|
|
58
|
+
|
|
59
|
+
Uses [alibaba/page-agent](https://github.com/alibaba/page-agent) (MIT) as the planning backend. The library calls `window.__browserAgentPageAgent.plan(input)`.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
window.__browserAgentPageAgent = {
|
|
63
|
+
async plan(input) { /* ... */ }
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Limitations (v0.2)
|
|
52
68
|
|
|
53
69
|
- No persistent long-term memory yet
|
|
54
70
|
- No task DSL/skills registry yet
|
package/docs/EMBEDDING.md
CHANGED
|
@@ -66,7 +66,41 @@ Then configure:
|
|
|
66
66
|
planner: { kind: "webllm", modelId: "Llama-3.2-1B-Instruct-q4f16_1-MLC" }
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
## page-agent mode in embedded app
|
|
70
|
+
|
|
71
|
+
To use planner mode `page-agent`, install [page-agent](https://github.com/alibaba/page-agent) in your project and wire the bridge:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm install page-agent
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { PageAgent } from "page-agent";
|
|
79
|
+
|
|
80
|
+
const pa = new PageAgent({
|
|
81
|
+
baseURL: "https://api.openai.com/v1",
|
|
82
|
+
model: "gpt-4o",
|
|
83
|
+
apiKey: "sk-..."
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
window.__browserAgentPageAgent = {
|
|
87
|
+
async plan(input) {
|
|
88
|
+
const result = await pa.execute(input.goal);
|
|
89
|
+
return { type: "done", reason: result.data };
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then configure:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
planner: { kind: "page-agent" }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use page-agent mode when your goals are complex, multi-step, or ambiguous — it uses LLM reasoning to determine the next action rather than regex heuristics.
|
|
101
|
+
|
|
69
102
|
## Notes
|
|
70
103
|
|
|
71
104
|
- For production, mount this inside an authenticated app shell and add your own permission checks.
|
|
72
105
|
- `human-approved` mode is recommended for CRM/finance/admin actions.
|
|
106
|
+
- `page-agent` is not bundled with this library — it must be installed separately by the consumer.
|
package/docs/ROADMAP.md
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
## v0.1
|
|
3
|
+
## v0.1
|
|
4
4
|
|
|
5
5
|
- Extension runtime loop
|
|
6
6
|
- Shared action contracts
|
|
7
7
|
- Heuristic + WebLLM planner switch
|
|
8
8
|
- Human-approved mode
|
|
9
9
|
|
|
10
|
-
## v0.2
|
|
10
|
+
## v0.2 (current)
|
|
11
|
+
|
|
12
|
+
- New actions: `scroll`, `focus`
|
|
13
|
+
- Improved heuristic planner with regex goal patterns
|
|
14
|
+
- Better page observation (visibility filtering, placeholder capture)
|
|
15
|
+
- Library API: `resume()`, `isRunning`, `hasPendingAction`, `AbortSignal`, `onMaxStepsReached`
|
|
16
|
+
- **page-agent planner bridge** (`window.__browserAgentPageAgent`)
|
|
17
|
+
|
|
18
|
+
## v0.3
|
|
11
19
|
|
|
12
20
|
- Site profile + policy engine (allowlist, blocked domains)
|
|
13
21
|
- Selector healing and fallback strategy
|
package/index.html
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>OmniBrowser Agent</title>
|
|
7
|
+
<meta
|
|
8
|
+
name="description"
|
|
9
|
+
content="OmniBrowser Agent - local-first browser AI operator library."
|
|
10
|
+
/>
|
|
11
|
+
<link rel="stylesheet" href="./styles.css" />
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<header class="header">
|
|
15
|
+
<div class="wrap header-row">
|
|
16
|
+
<a class="brand" href="#home">OmniBrowser Agent</a>
|
|
17
|
+
<nav class="nav">
|
|
18
|
+
<a href="#home">Home</a>
|
|
19
|
+
<a href="#docs">Docs</a>
|
|
20
|
+
<a href="#contact">Contact</a>
|
|
21
|
+
</nav>
|
|
22
|
+
</div>
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
<main>
|
|
26
|
+
<section id="home" class="section hero">
|
|
27
|
+
<div class="wrap">
|
|
28
|
+
<p class="eyebrow">Open-source browser automation SDK</p>
|
|
29
|
+
<h1>Local-first browser AI automation library</h1>
|
|
30
|
+
<p>
|
|
31
|
+
OmniBrowser Agent helps you run page observation, planning, and execution flows directly in the browser.
|
|
32
|
+
</p>
|
|
33
|
+
<div class="chips">
|
|
34
|
+
<span>Privacy-first</span>
|
|
35
|
+
<span>WebLLM-ready</span>
|
|
36
|
+
<span>Human-approved mode</span>
|
|
37
|
+
<span>Embeddable API</span>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="actions">
|
|
40
|
+
<a
|
|
41
|
+
class="btn primary"
|
|
42
|
+
href="https://www.npmjs.com/package/@akshayram1/omnibrowser-agent"
|
|
43
|
+
target="_blank"
|
|
44
|
+
rel="noreferrer"
|
|
45
|
+
>NPM Package</a
|
|
46
|
+
>
|
|
47
|
+
<a
|
|
48
|
+
class="btn"
|
|
49
|
+
href="https://github.com/akshayram1/omnibrowser-agent"
|
|
50
|
+
target="_blank"
|
|
51
|
+
rel="noreferrer"
|
|
52
|
+
>GitHub Repo</a
|
|
53
|
+
>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="stats" aria-label="project stats">
|
|
56
|
+
<div class="stat"><strong>2</strong><span>Execution Modes</span></div>
|
|
57
|
+
<div class="stat"><strong>3</strong><span>Planner Options</span></div>
|
|
58
|
+
<div class="stat"><strong>MIT</strong><span>License</span></div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="home-grid">
|
|
61
|
+
<article class="card">
|
|
62
|
+
<h3>Use Cases</h3>
|
|
63
|
+
<ul>
|
|
64
|
+
<li>CRM profile lookup automation</li>
|
|
65
|
+
<li>Guided web task execution</li>
|
|
66
|
+
<li>Assisted data extraction flows</li>
|
|
67
|
+
</ul>
|
|
68
|
+
</article>
|
|
69
|
+
<article class="card">
|
|
70
|
+
<h3>Core Modules</h3>
|
|
71
|
+
<ul>
|
|
72
|
+
<li><strong>Observer:</strong> page signals and candidates</li>
|
|
73
|
+
<li><strong>Planner:</strong> next best action selection</li>
|
|
74
|
+
<li><strong>Executor:</strong> safe browser action runtime</li>
|
|
75
|
+
</ul>
|
|
76
|
+
</article>
|
|
77
|
+
<article class="card">
|
|
78
|
+
<h3>Project Links</h3>
|
|
79
|
+
<ul>
|
|
80
|
+
<li><a href="https://www.npmjs.com/package/@akshayram1/omnibrowser-agent" target="_blank" rel="noreferrer">NPM package</a></li>
|
|
81
|
+
<li><a href="https://github.com/akshayram1/omnibrowser-agent" target="_blank" rel="noreferrer">GitHub repository</a></li>
|
|
82
|
+
<li><a href="./README.md" target="_blank" rel="noreferrer">README</a></li>
|
|
83
|
+
</ul>
|
|
84
|
+
</article>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
|
|
89
|
+
<section id="docs" class="section">
|
|
90
|
+
<div class="wrap">
|
|
91
|
+
<div class="surface">
|
|
92
|
+
<h2>Docs</h2>
|
|
93
|
+
<p>
|
|
94
|
+
Everything you need to install, initialize, and run your first browser agent with clear defaults.
|
|
95
|
+
</p>
|
|
96
|
+
|
|
97
|
+
<h3>Installation</h3>
|
|
98
|
+
<pre><code>npm install @akshayram1/omnibrowser-agent</code></pre>
|
|
99
|
+
|
|
100
|
+
<h3>Quick Start</h3>
|
|
101
|
+
<pre><code>import { createBrowserAgent } from "@akshayram1/omnibrowser-agent";
|
|
102
|
+
|
|
103
|
+
const agent = createBrowserAgent(
|
|
104
|
+
{
|
|
105
|
+
goal: "Open CRM and find customer John Smith",
|
|
106
|
+
mode: "human-approved"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
onStep: (result) => console.log(result.message)
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
await agent.start();</code></pre>
|
|
114
|
+
|
|
115
|
+
<h3>Execution Modes</h3>
|
|
116
|
+
<div class="docs-grid">
|
|
117
|
+
<article class="doc-card">
|
|
118
|
+
<h4>human-approved</h4>
|
|
119
|
+
<p>Requires explicit approval for sensitive actions. Best for production-like workflows.</p>
|
|
120
|
+
</article>
|
|
121
|
+
<article class="doc-card">
|
|
122
|
+
<h4>autonomous</h4>
|
|
123
|
+
<p>Runs actions continuously with fewer pauses. Best for rapid iteration and demos.</p>
|
|
124
|
+
</article>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<h3>Planner Options</h3>
|
|
128
|
+
<div class="docs-grid">
|
|
129
|
+
<article class="doc-card">
|
|
130
|
+
<h4>heuristic</h4>
|
|
131
|
+
<p>Zero-dependency regex-based planner. Works offline. Best for simple, predictable goals.</p>
|
|
132
|
+
</article>
|
|
133
|
+
<article class="doc-card">
|
|
134
|
+
<h4>webllm</h4>
|
|
135
|
+
<p>Delegates to a local WebLLM bridge (<code>window.__browserAgentWebLLM</code>). Fully private, no API calls.</p>
|
|
136
|
+
</article>
|
|
137
|
+
<article class="doc-card">
|
|
138
|
+
<h4>page-agent</h4>
|
|
139
|
+
<p>Delegates to an <a href="https://github.com/alibaba/page-agent" target="_blank" rel="noreferrer">alibaba/page-agent</a> bridge (<code>window.__browserAgentPageAgent</code>). Best for complex multi-step goals.</p>
|
|
140
|
+
</article>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<h3>Recommended Integration Flow</h3>
|
|
144
|
+
<ol>
|
|
145
|
+
<li>Create an agent with your goal and preferred mode.</li>
|
|
146
|
+
<li>Attach callbacks (`onStep`, `onApprovalRequired`, `onDone`) to drive UI state.</li>
|
|
147
|
+
<li>Call <code>start()</code> to begin planning and execution.</li>
|
|
148
|
+
<li>If approval is required, surface the action and call <code>resume()</code>.</li>
|
|
149
|
+
<li>Use <code>stop()</code> for cancellation and reset.</li>
|
|
150
|
+
</ol>
|
|
151
|
+
|
|
152
|
+
<h3>Safety Notes</h3>
|
|
153
|
+
<ul>
|
|
154
|
+
<li>Prefer scoped selectors for deterministic action targeting.</li>
|
|
155
|
+
<li>Use human-approved mode for workflows that mutate critical data.</li>
|
|
156
|
+
<li>Log `onStep` output for auditability and debugging.</li>
|
|
157
|
+
</ul>
|
|
158
|
+
|
|
159
|
+
<p>
|
|
160
|
+
Full project docs are available in this repository under
|
|
161
|
+
<a href="./docs/ARCHITECTURE.md" target="_blank" rel="noreferrer">Architecture</a>,
|
|
162
|
+
<a href="./docs/EMBEDDING.md" target="_blank" rel="noreferrer">Embedding</a>, and
|
|
163
|
+
<a href="./docs/ROADMAP.md" target="_blank" rel="noreferrer">Roadmap</a>.
|
|
164
|
+
</p>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</section>
|
|
168
|
+
|
|
169
|
+
<section id="contact" class="section">
|
|
170
|
+
<div class="wrap">
|
|
171
|
+
<div class="surface">
|
|
172
|
+
<h2>Contact</h2>
|
|
173
|
+
<p>Maintainer: Akshay Chame</p>
|
|
174
|
+
<ul>
|
|
175
|
+
<li>
|
|
176
|
+
Email:
|
|
177
|
+
<a href="mailto:akshaychame2@gmail.com">akshaychame2@gmail.com</a>
|
|
178
|
+
</li>
|
|
179
|
+
<li>
|
|
180
|
+
GitHub:
|
|
181
|
+
<a href="https://github.com/akshayram1" target="_blank" rel="noreferrer">@akshayram1</a>
|
|
182
|
+
</li>
|
|
183
|
+
<li>
|
|
184
|
+
Package:
|
|
185
|
+
<a
|
|
186
|
+
href="https://www.npmjs.com/package/@akshayram1/omnibrowser-agent"
|
|
187
|
+
target="_blank"
|
|
188
|
+
rel="noreferrer"
|
|
189
|
+
>@akshayram1/omnibrowser-agent</a
|
|
190
|
+
>
|
|
191
|
+
</li>
|
|
192
|
+
</ul>
|
|
193
|
+
<p class="contact-note">
|
|
194
|
+
For feature requests or bugs, please open an issue on GitHub with reproduction steps.
|
|
195
|
+
</p>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</section>
|
|
199
|
+
</main>
|
|
200
|
+
|
|
201
|
+
<footer class="footer">
|
|
202
|
+
<div class="wrap">
|
|
203
|
+
<p>© 2026 OmniBrowser Agent · MIT License</p>
|
|
204
|
+
</div>
|
|
205
|
+
</footer>
|
|
206
|
+
</body>
|
|
207
|
+
</html>
|
package/package.json
CHANGED
package/styles.css
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
* {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
color: #182033;
|
|
13
|
+
background:
|
|
14
|
+
radial-gradient(circle at 12% -8%, #dce7ff 0%, transparent 36%),
|
|
15
|
+
radial-gradient(circle at 88% 0%, #e4f0ff 0%, transparent 32%),
|
|
16
|
+
#f5f7fc;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.wrap {
|
|
20
|
+
width: min(960px, 92vw);
|
|
21
|
+
margin: 0 auto;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.header {
|
|
25
|
+
position: sticky;
|
|
26
|
+
top: 0;
|
|
27
|
+
border-bottom: 1px solid #dce3f3;
|
|
28
|
+
background: rgba(255, 255, 255, 0.92);
|
|
29
|
+
backdrop-filter: blur(8px);
|
|
30
|
+
z-index: 10;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.header-row {
|
|
34
|
+
min-height: 64px;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.brand {
|
|
41
|
+
font-weight: 700;
|
|
42
|
+
text-decoration: none;
|
|
43
|
+
color: inherit;
|
|
44
|
+
letter-spacing: 0.2px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.nav {
|
|
48
|
+
display: flex;
|
|
49
|
+
gap: 16px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.nav a {
|
|
53
|
+
text-decoration: none;
|
|
54
|
+
color: #304265;
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
padding: 6px 10px;
|
|
57
|
+
border-radius: 8px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.nav a:hover {
|
|
61
|
+
background: #edf2ff;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.section {
|
|
65
|
+
padding: 56px 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.hero {
|
|
69
|
+
background: radial-gradient(circle at top right, #dbe6ff 0%, transparent 38%);
|
|
70
|
+
padding-top: 64px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.eyebrow {
|
|
74
|
+
display: inline-block;
|
|
75
|
+
margin: 0;
|
|
76
|
+
font-size: 12px;
|
|
77
|
+
font-weight: 700;
|
|
78
|
+
letter-spacing: 0.08em;
|
|
79
|
+
text-transform: uppercase;
|
|
80
|
+
color: #3356a6;
|
|
81
|
+
background: #e9efff;
|
|
82
|
+
border: 1px solid #ccdafc;
|
|
83
|
+
border-radius: 999px;
|
|
84
|
+
padding: 6px 10px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.hero h1 {
|
|
88
|
+
margin: 14px 0 0;
|
|
89
|
+
font-size: clamp(28px, 4vw, 42px);
|
|
90
|
+
line-height: 1.12;
|
|
91
|
+
max-width: 860px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.hero p {
|
|
95
|
+
margin-top: 14px;
|
|
96
|
+
font-size: 18px;
|
|
97
|
+
line-height: 1.55;
|
|
98
|
+
max-width: 720px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.chips {
|
|
102
|
+
margin-top: 18px;
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-wrap: wrap;
|
|
105
|
+
gap: 8px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.chips span {
|
|
109
|
+
font-size: 13px;
|
|
110
|
+
color: #2a3f72;
|
|
111
|
+
background: #e9efff;
|
|
112
|
+
border: 1px solid #ccdafc;
|
|
113
|
+
border-radius: 999px;
|
|
114
|
+
padding: 6px 10px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.actions {
|
|
118
|
+
margin-top: 22px;
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-wrap: wrap;
|
|
121
|
+
gap: 12px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.btn {
|
|
125
|
+
display: inline-block;
|
|
126
|
+
padding: 10px 16px;
|
|
127
|
+
border-radius: 10px;
|
|
128
|
+
text-decoration: none;
|
|
129
|
+
border: 1px solid #c8d4f1;
|
|
130
|
+
color: #1a2742;
|
|
131
|
+
background: #eef3ff;
|
|
132
|
+
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.btn.primary {
|
|
136
|
+
background: #1f3a8a;
|
|
137
|
+
border-color: #1f3a8a;
|
|
138
|
+
color: #fff;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.btn:hover {
|
|
142
|
+
transform: translateY(-1px);
|
|
143
|
+
box-shadow: 0 8px 20px rgba(32, 58, 138, 0.12);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.stats {
|
|
147
|
+
margin-top: 18px;
|
|
148
|
+
display: grid;
|
|
149
|
+
grid-template-columns: repeat(3, minmax(120px, 1fr));
|
|
150
|
+
gap: 10px;
|
|
151
|
+
max-width: 560px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.stat {
|
|
155
|
+
background: #fff;
|
|
156
|
+
border: 1px solid #dce3f3;
|
|
157
|
+
border-radius: 12px;
|
|
158
|
+
padding: 10px 12px;
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
gap: 4px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.stat strong {
|
|
165
|
+
font-size: 18px;
|
|
166
|
+
color: #1f3a8a;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.stat span {
|
|
170
|
+
font-size: 12px;
|
|
171
|
+
color: #4f6290;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.home-grid {
|
|
175
|
+
margin-top: 24px;
|
|
176
|
+
display: grid;
|
|
177
|
+
gap: 14px;
|
|
178
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.card {
|
|
182
|
+
background: #fff;
|
|
183
|
+
border: 1px solid #dce3f3;
|
|
184
|
+
border-radius: 12px;
|
|
185
|
+
padding: 14px;
|
|
186
|
+
box-shadow: 0 8px 24px rgba(18, 33, 73, 0.05);
|
|
187
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.card:hover {
|
|
191
|
+
transform: translateY(-2px);
|
|
192
|
+
box-shadow: 0 14px 30px rgba(18, 33, 73, 0.11);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.card h3 {
|
|
196
|
+
margin-top: 0;
|
|
197
|
+
margin-bottom: 10px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
h2 {
|
|
201
|
+
margin-top: 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
h3 {
|
|
205
|
+
margin-bottom: 10px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
h4 {
|
|
209
|
+
margin: 0 0 6px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.docs-grid {
|
|
213
|
+
display: grid;
|
|
214
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
215
|
+
gap: 12px;
|
|
216
|
+
margin-bottom: 14px;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.doc-card {
|
|
220
|
+
border: 1px solid #dce3f3;
|
|
221
|
+
background: #fff;
|
|
222
|
+
border-radius: 12px;
|
|
223
|
+
padding: 12px;
|
|
224
|
+
box-shadow: 0 5px 14px rgba(18, 33, 73, 0.04);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.surface {
|
|
228
|
+
background: rgba(255, 255, 255, 0.86);
|
|
229
|
+
border: 1px solid #dce3f3;
|
|
230
|
+
border-radius: 16px;
|
|
231
|
+
padding: 24px;
|
|
232
|
+
box-shadow: 0 14px 28px rgba(18, 33, 73, 0.06);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
pre {
|
|
236
|
+
overflow: auto;
|
|
237
|
+
border: 1px solid #dce3f3;
|
|
238
|
+
border-radius: 10px;
|
|
239
|
+
background: #ffffff;
|
|
240
|
+
padding: 14px;
|
|
241
|
+
line-height: 1.45;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
code {
|
|
245
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
ol {
|
|
249
|
+
padding-left: 20px;
|
|
250
|
+
line-height: 1.6;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
ul {
|
|
254
|
+
padding-left: 18px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
li {
|
|
258
|
+
margin: 8px 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
a {
|
|
262
|
+
color: #2647a8;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.contact-note {
|
|
266
|
+
margin-top: 14px;
|
|
267
|
+
color: #4b5d84;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.footer {
|
|
271
|
+
border-top: 1px solid #dce3f3;
|
|
272
|
+
padding: 20px 0;
|
|
273
|
+
color: #5b6c91;
|
|
274
|
+
font-size: 14px;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
@media (max-width: 720px) {
|
|
278
|
+
.header-row {
|
|
279
|
+
min-height: 58px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.nav {
|
|
283
|
+
gap: 8px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.stats {
|
|
287
|
+
grid-template-columns: 1fr;
|
|
288
|
+
max-width: 100%;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.surface {
|
|
292
|
+
padding: 18px;
|
|
293
|
+
}
|
|
294
|
+
}
|
package/vercel.json
ADDED