@isoldex/sentinel 0.1.0 → 2.0.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/README.md +368 -62
- package/dist/__tests__/action-engine.test.d.ts +2 -0
- package/dist/__tests__/action-engine.test.d.ts.map +1 -0
- package/dist/__tests__/action-engine.test.js +144 -0
- package/dist/__tests__/action-engine.test.js.map +1 -0
- package/dist/__tests__/state-parser.test.d.ts +2 -0
- package/dist/__tests__/state-parser.test.d.ts.map +1 -0
- package/dist/__tests__/state-parser.test.js +176 -0
- package/dist/__tests__/state-parser.test.js.map +1 -0
- package/dist/__tests__/verifier.test.d.ts +2 -0
- package/dist/__tests__/verifier.test.d.ts.map +1 -0
- package/dist/__tests__/verifier.test.js +68 -0
- package/dist/__tests__/verifier.test.js.map +1 -0
- package/dist/__tests__/workflow-recorder.test.d.ts +2 -0
- package/dist/__tests__/workflow-recorder.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-recorder.test.js +71 -0
- package/dist/__tests__/workflow-recorder.test.js.map +1 -0
- package/dist/agent/agent-loop.d.ts +36 -0
- package/dist/agent/agent-loop.d.ts.map +1 -0
- package/dist/agent/agent-loop.js +127 -0
- package/dist/agent/agent-loop.js.map +1 -0
- package/dist/agent/memory.d.ts +24 -0
- package/dist/agent/memory.d.ts.map +1 -0
- package/dist/agent/memory.js +34 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/planner.d.ts +18 -0
- package/dist/agent/planner.d.ts.map +1 -0
- package/dist/agent/planner.js +68 -0
- package/dist/agent/planner.js.map +1 -0
- package/dist/api/act.d.ts +4 -2
- package/dist/api/act.d.ts.map +1 -1
- package/dist/api/act.js +174 -30
- package/dist/api/act.js.map +1 -1
- package/dist/api/extract.d.ts +2 -3
- package/dist/api/extract.d.ts.map +1 -1
- package/dist/api/extract.js +0 -1
- package/dist/api/extract.js.map +1 -1
- package/dist/api/observe.d.ts +2 -2
- package/dist/api/observe.d.ts.map +1 -1
- package/dist/api/observe.js +0 -1
- package/dist/api/observe.js.map +1 -1
- package/dist/core/driver.d.ts +21 -1
- package/dist/core/driver.d.ts.map +1 -1
- package/dist/core/driver.js +109 -19
- package/dist/core/driver.js.map +1 -1
- package/dist/core/state-parser.d.ts +1 -0
- package/dist/core/state-parser.d.ts.map +1 -1
- package/dist/core/state-parser.js +60 -0
- package/dist/core/state-parser.js.map +1 -1
- package/dist/core/vision-grounding.d.ts +36 -0
- package/dist/core/vision-grounding.d.ts.map +1 -0
- package/dist/core/vision-grounding.js +118 -0
- package/dist/core/vision-grounding.js.map +1 -0
- package/dist/index.d.ts +111 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +168 -4
- package/dist/index.js.map +1 -1
- package/dist/recorder/workflow-recorder.d.ts +30 -0
- package/dist/recorder/workflow-recorder.d.ts.map +1 -0
- package/dist/recorder/workflow-recorder.js +83 -0
- package/dist/recorder/workflow-recorder.js.map +1 -0
- package/dist/reliability/verifier.d.ts +2 -2
- package/dist/reliability/verifier.d.ts.map +1 -1
- package/dist/reliability/verifier.js +0 -1
- package/dist/reliability/verifier.js.map +1 -1
- package/dist/types/errors.d.ts +45 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +69 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/utils/gemini.d.ts +3 -6
- package/dist/utils/gemini.d.ts.map +1 -1
- package/dist/utils/gemini.js +9 -94
- package/dist/utils/gemini.js.map +1 -1
- package/dist/utils/llm-provider.d.ts +28 -0
- package/dist/utils/llm-provider.d.ts.map +1 -0
- package/dist/utils/llm-provider.js +2 -0
- package/dist/utils/llm-provider.js.map +1 -0
- package/dist/utils/providers/claude-provider.d.ts +17 -0
- package/dist/utils/providers/claude-provider.d.ts.map +1 -0
- package/dist/utils/providers/claude-provider.js +49 -0
- package/dist/utils/providers/claude-provider.js.map +1 -0
- package/dist/utils/providers/gemini-provider.d.ts +14 -0
- package/dist/utils/providers/gemini-provider.d.ts.map +1 -0
- package/dist/utils/providers/gemini-provider.js +95 -0
- package/dist/utils/providers/gemini-provider.js.map +1 -0
- package/dist/utils/providers/ollama-provider.d.ts +18 -0
- package/dist/utils/providers/ollama-provider.d.ts.map +1 -0
- package/dist/utils/providers/ollama-provider.js +62 -0
- package/dist/utils/providers/ollama-provider.js.map +1 -0
- package/dist/utils/providers/openai-provider.d.ts +18 -0
- package/dist/utils/providers/openai-provider.d.ts.map +1 -0
- package/dist/utils/providers/openai-provider.js +51 -0
- package/dist/utils/providers/openai-provider.js.map +1 -0
- package/dist/utils/token-tracker.d.ts +25 -0
- package/dist/utils/token-tracker.d.ts.map +1 -0
- package/dist/utils/token-tracker.js +45 -0
- package/dist/utils/token-tracker.js.map +1 -0
- package/dist/whatsapp-test.js +58 -17
- package/dist/whatsapp-test.js.map +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
# Sentinel 🛡️
|
|
2
2
|
|
|
3
|
-
Sentinel is a high-performance, AI-driven browser automation framework built on
|
|
3
|
+
**Sentinel** is a high-performance, AI-driven browser automation framework built on **Playwright** and powered by **Google Gemini** (or any LLM of your choice). Automate complex web tasks with natural language, extract structured data with Zod, run autonomous multi-step agents, and record & replay workflows — all with self-healing reliability.
|
|
4
4
|
|
|
5
|
-
Think of it as a **fast, lightweight, and cost-effective alternative to Stagehand
|
|
5
|
+
> Think of it as a **fast, lightweight, and cost-effective alternative to BrowserUse, Stagehand, and AutoGPT** — with vision grounding, multi-LLM support, and a full agent loop built in.
|
|
6
|
+
|
|
7
|
+
---
|
|
6
8
|
|
|
7
9
|
## ✨ Features
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
| Feature | Description |
|
|
12
|
+
|---|---|
|
|
13
|
+
| 🗣️ **Natural Language Actions** | `act('Click the login button')` — no CSS selectors needed |
|
|
14
|
+
| 📊 **Structured Extraction** | Zod-typed `extract()` with full TypeScript inference |
|
|
15
|
+
| 🤖 **Autonomous Agent Loop** | `run(goal)` — Plan → Execute → Verify → Reflect cycle |
|
|
16
|
+
| 👁️ **Vision Grounding** | Gemini Vision fallback when AOM can't find an element |
|
|
17
|
+
| 🗂️ **Multi-Tab & Multi-Browser** | Chromium, Firefox, WebKit + tab management |
|
|
18
|
+
| 🔄 **Record & Replay** | Record workflows, export as TypeScript or JSON, replay anytime |
|
|
19
|
+
| 🔌 **Multi-LLM Support** | Gemini, OpenAI, Claude, Ollama (local) — plug in any provider |
|
|
20
|
+
| 💾 **Session Persistence** | Save & load cookies/localStorage for authenticated workflows |
|
|
21
|
+
| 🕵️ **Stealth & Proxy** | Human-like delays, User-Agent rotation, proxy support |
|
|
22
|
+
| 📡 **Event System** | `sentinel.on('action', ...)` — full observability |
|
|
23
|
+
| 💰 **Token Tracking** | Monitor LLM usage and estimated cost per session |
|
|
24
|
+
| ⚡ **High Performance** | Parallel CDP requests, smart AOM state caching (TTL 500ms) |
|
|
25
|
+
| 🛡️ **Self-Healing** | Semantic verification loop with automatic retry & fallback |
|
|
15
26
|
|
|
16
27
|
---
|
|
17
28
|
|
|
@@ -22,101 +33,396 @@ Think of it as a **fast, lightweight, and cost-effective alternative to Stagehan
|
|
|
22
33
|
```bash
|
|
23
34
|
npm install @isoldex/sentinel playwright
|
|
24
35
|
```
|
|
25
|
-
> [!NOTE]
|
|
26
|
-
> Playwright is a peer dependency. Make sure to install it alongside Sentinel.
|
|
27
36
|
|
|
28
|
-
|
|
37
|
+
> **Note:** Playwright is a peer dependency. Install it alongside Sentinel.
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
### 2. Configuration
|
|
31
40
|
|
|
32
41
|
```env
|
|
33
42
|
GEMINI_API_KEY=your_api_key_here
|
|
34
|
-
GEMINI_VERSION=gemini-
|
|
43
|
+
GEMINI_VERSION=gemini-2.0-flash # recommended for speed
|
|
35
44
|
```
|
|
36
45
|
|
|
37
|
-
### 3. Usage
|
|
46
|
+
### 3. Basic Usage
|
|
38
47
|
|
|
39
48
|
```typescript
|
|
40
49
|
import { Sentinel, z } from '@isoldex/sentinel';
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
const sentinel = new Sentinel({
|
|
52
|
+
apiKey: process.env.GEMINI_API_KEY!,
|
|
53
|
+
headless: false,
|
|
54
|
+
verbose: 1,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await sentinel.init();
|
|
58
|
+
await sentinel.goto('https://news.ycombinator.com');
|
|
59
|
+
|
|
60
|
+
// Extract structured data with Zod
|
|
61
|
+
const data = await sentinel.extract('Get the top 3 stories', z.object({
|
|
62
|
+
stories: z.array(z.object({
|
|
63
|
+
title: z.string(),
|
|
64
|
+
points: z.number(),
|
|
65
|
+
}))
|
|
66
|
+
}));
|
|
67
|
+
console.log(data.stories);
|
|
68
|
+
|
|
69
|
+
// Natural language action
|
|
70
|
+
await sentinel.act('Click on the "new" link in the header');
|
|
71
|
+
|
|
72
|
+
await sentinel.close();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 🤖 Autonomous Agent
|
|
78
|
+
|
|
79
|
+
Run a high-level goal autonomously — Sentinel plans, executes, verifies, and reflects until the goal is reached:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const result = await sentinel.run(
|
|
83
|
+
'Go to Amazon, search for "mechanical keyboard under 100 euros", and extract the top 5 results',
|
|
84
|
+
{
|
|
85
|
+
maxSteps: 20,
|
|
86
|
+
onStep: (event) => console.log(`Step ${event.step}: ${event.action}`),
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
console.log(result.success, result.message);
|
|
91
|
+
console.log(result.extractedData);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 🔄 Record & Replay
|
|
97
|
+
|
|
98
|
+
Record any workflow and replay it later — or export it as TypeScript code:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Record
|
|
102
|
+
sentinel.startRecording('my-workflow');
|
|
103
|
+
await sentinel.goto('https://example.com');
|
|
104
|
+
await sentinel.act('Click the sign in button');
|
|
105
|
+
await sentinel.act('Fill "user@example.com" into the email field');
|
|
106
|
+
const workflow = sentinel.stopRecording();
|
|
107
|
+
|
|
108
|
+
// Export as TypeScript
|
|
109
|
+
const code = sentinel.exportWorkflowAsCode(workflow);
|
|
110
|
+
console.log(code);
|
|
111
|
+
|
|
112
|
+
// Export as JSON
|
|
113
|
+
const json = sentinel.exportWorkflowAsJSON(workflow);
|
|
114
|
+
|
|
115
|
+
// Replay
|
|
116
|
+
await sentinel.replay(workflow);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 🗂️ Multi-Tab & Multi-Browser
|
|
48
122
|
|
|
49
|
-
|
|
123
|
+
```typescript
|
|
124
|
+
// Use Firefox instead of Chromium
|
|
125
|
+
const sentinel = new Sentinel({ apiKey: '...', browser: 'firefox' });
|
|
126
|
+
|
|
127
|
+
// Open a second tab
|
|
128
|
+
const tabIndex = await sentinel.newTab('https://google.com');
|
|
129
|
+
|
|
130
|
+
// Switch between tabs
|
|
131
|
+
await sentinel.switchTab(0);
|
|
132
|
+
await sentinel.switchTab(tabIndex);
|
|
133
|
+
|
|
134
|
+
// Close a tab
|
|
135
|
+
await sentinel.closeTab(tabIndex);
|
|
136
|
+
|
|
137
|
+
console.log(sentinel.tabCount); // number of open tabs
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
50
141
|
|
|
51
|
-
|
|
52
|
-
await sentinel.goto('https://news.ycombinator.com');
|
|
142
|
+
## 💾 Session Persistence
|
|
53
143
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
144
|
+
Save and restore authenticated sessions — no need to log in every time:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// First run: log in and save session
|
|
148
|
+
await sentinel.goto('https://github.com/login');
|
|
149
|
+
await sentinel.act('Fill "myuser" into the username field');
|
|
150
|
+
await sentinel.act('Fill "mypassword" into the password field');
|
|
151
|
+
await sentinel.act('Click the sign in button');
|
|
152
|
+
await sentinel.saveSession('./sessions/github.json');
|
|
153
|
+
|
|
154
|
+
// Subsequent runs: load session and skip login
|
|
155
|
+
const sentinel = new Sentinel({
|
|
156
|
+
apiKey: '...',
|
|
157
|
+
sessionPath: './sessions/github.json', // auto-loaded on init()
|
|
158
|
+
});
|
|
159
|
+
await sentinel.init();
|
|
160
|
+
await sentinel.goto('https://github.com'); // already logged in!
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 🔌 Multi-LLM Providers
|
|
166
|
+
|
|
167
|
+
Swap out Gemini for OpenAI, Claude, or a local Ollama model:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { Sentinel, OpenAIProvider, ClaudeProvider, OllamaProvider } from '@isoldex/sentinel';
|
|
171
|
+
|
|
172
|
+
// OpenAI GPT-4o
|
|
173
|
+
const sentinel = new Sentinel({
|
|
174
|
+
apiKey: 'gemini-key', // still required for fallback
|
|
175
|
+
provider: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o' }),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Anthropic Claude
|
|
179
|
+
const sentinel = new Sentinel({
|
|
180
|
+
apiKey: 'gemini-key',
|
|
181
|
+
provider: new ClaudeProvider({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-3-5-sonnet-20241022' }),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Local Ollama (no API key needed)
|
|
185
|
+
const sentinel = new Sentinel({
|
|
186
|
+
apiKey: 'gemini-key',
|
|
187
|
+
provider: new OllamaProvider({ model: 'llama3.2', baseUrl: 'http://localhost:11434' }),
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 👁️ Vision Grounding
|
|
194
|
+
|
|
195
|
+
Enable Gemini Vision as a fallback when the Accessibility Tree can't locate an element (e.g. canvas, shadow DOM, custom components):
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
const sentinel = new Sentinel({
|
|
199
|
+
apiKey: process.env.GEMINI_API_KEY!,
|
|
200
|
+
visionFallback: true, // enables screenshot-based fallback
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Take a screenshot
|
|
204
|
+
const png = await sentinel.screenshot();
|
|
205
|
+
|
|
206
|
+
// Describe the current screen visually
|
|
207
|
+
const description = await sentinel.describeScreen();
|
|
208
|
+
console.log(description);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 🕵️ Stealth & Proxy
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const sentinel = new Sentinel({
|
|
217
|
+
apiKey: '...',
|
|
218
|
+
humanLike: true, // random delays between actions
|
|
219
|
+
proxy: {
|
|
220
|
+
server: 'http://proxy.example.com:8080',
|
|
221
|
+
username: 'user',
|
|
222
|
+
password: 'pass',
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 📡 Events & Observability
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
sentinel.on('action', (event) => {
|
|
233
|
+
console.log('Action performed:', event.instruction, event.result);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
sentinel.on('navigate', (event) => {
|
|
237
|
+
console.log('Navigated to:', event.url);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
sentinel.on('close', () => {
|
|
241
|
+
console.log('Browser closed');
|
|
242
|
+
});
|
|
243
|
+
```
|
|
62
244
|
|
|
63
|
-
|
|
64
|
-
await sentinel.act('Click on the "new" link in the header');
|
|
245
|
+
---
|
|
65
246
|
|
|
66
|
-
|
|
67
|
-
}
|
|
247
|
+
## 💰 Token Usage & Cost Tracking
|
|
68
248
|
|
|
69
|
-
|
|
249
|
+
```typescript
|
|
250
|
+
const usage = sentinel.getTokenUsage();
|
|
251
|
+
console.log(usage);
|
|
252
|
+
// {
|
|
253
|
+
// totalTokens: 12400,
|
|
254
|
+
// inputTokens: 9800,
|
|
255
|
+
// outputTokens: 2600,
|
|
256
|
+
// estimatedCostUsd: 0.003,
|
|
257
|
+
// calls: 14
|
|
258
|
+
// }
|
|
259
|
+
|
|
260
|
+
// Export full log as JSON
|
|
261
|
+
sentinel.exportLogs('./logs/session.json');
|
|
70
262
|
```
|
|
71
263
|
|
|
72
264
|
---
|
|
73
265
|
|
|
74
|
-
## 🛠️ API Reference
|
|
266
|
+
## 🛠️ Full API Reference
|
|
75
267
|
|
|
76
268
|
### `new Sentinel(options: SentinelOptions)`
|
|
77
|
-
- `apiKey`: Your Google AI API key.
|
|
78
|
-
- `headless`: Whether to run the browser in the background (default: `false`).
|
|
79
|
-
- `verbose`: Logging level (`0` = silent, `1` = actions, `2` = full debug).
|
|
80
|
-
- `enableCaching`: Enable state caching between calls (default: `true`).
|
|
81
269
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
270
|
+
| Option | Type | Default | Description |
|
|
271
|
+
|---|---|---|---|
|
|
272
|
+
| `apiKey` | `string` | — | Google Gemini API key |
|
|
273
|
+
| `headless` | `boolean` | `false` | Run browser headlessly |
|
|
274
|
+
| `browser` | `'chromium' \| 'firefox' \| 'webkit'` | `'chromium'` | Browser engine |
|
|
275
|
+
| `viewport` | `{ width, height }` | `1280×720` | Viewport size |
|
|
276
|
+
| `verbose` | `0 \| 1 \| 2` | `1` | Log level (0=silent, 2=debug) |
|
|
277
|
+
| `enableCaching` | `boolean` | `true` | Cache AOM state between calls |
|
|
278
|
+
| `visionFallback` | `boolean` | `false` | Enable Gemini Vision fallback |
|
|
279
|
+
| `provider` | `LLMProvider` | Gemini | Custom LLM provider |
|
|
280
|
+
| `sessionPath` | `string` | — | Path to session file (auto-loaded) |
|
|
281
|
+
| `proxy` | `ProxyOptions` | — | Proxy server configuration |
|
|
282
|
+
| `humanLike` | `boolean` | `false` | Random delays between actions |
|
|
283
|
+
| `domSettleTimeoutMs` | `number` | `3000` | DOM settle wait time (ms) |
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### Core Methods
|
|
288
|
+
|
|
289
|
+
#### `sentinel.init(): Promise<void>`
|
|
290
|
+
Initialize the browser and all internal engines. Must be called before any other method.
|
|
291
|
+
|
|
292
|
+
#### `sentinel.goto(url: string): Promise<void>`
|
|
293
|
+
Navigate to a URL and wait for the DOM to settle.
|
|
294
|
+
|
|
295
|
+
#### `sentinel.close(): Promise<void>`
|
|
296
|
+
Close the browser and release all resources.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
### `sentinel.act(instruction, options?): Promise<ActionResult>`
|
|
301
|
+
Perform a natural language action on the page.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
await sentinel.act('Click the "Add to Cart" button');
|
|
305
|
+
await sentinel.act('Fill %email% into the email field', { variables: { email: 'user@example.com' } });
|
|
306
|
+
await sentinel.act('Press Enter');
|
|
307
|
+
await sentinel.act('Scroll down');
|
|
308
|
+
await sentinel.act('Select "Germany" from the country dropdown');
|
|
309
|
+
await sentinel.act('Double-click the image');
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Supported action types:** `click`, `fill`, `hover`, `press`, `select`, `double-click`, `right-click`, `scroll-down`, `scroll-up`, `scroll-to`
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
### `sentinel.extract<T>(instruction, schema): Promise<T>`
|
|
317
|
+
Extract structured data from the current page using a Zod schema or JSON Schema.
|
|
86
318
|
|
|
87
|
-
|
|
88
|
-
|
|
319
|
+
```typescript
|
|
320
|
+
const result = await sentinel.extract('Get all product names and prices', z.object({
|
|
321
|
+
products: z.array(z.object({ name: z.string(), price: z.number() }))
|
|
322
|
+
}));
|
|
323
|
+
```
|
|
89
324
|
|
|
90
|
-
|
|
91
|
-
Returns a list of possible interactive elements and their purposes.
|
|
325
|
+
---
|
|
92
326
|
|
|
93
|
-
### `sentinel.
|
|
94
|
-
|
|
327
|
+
### `sentinel.observe(instruction?): Promise<ObserveResult[]>`
|
|
328
|
+
Return a list of interactive elements and their purposes.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
const elements = await sentinel.observe('Find all navigation links');
|
|
332
|
+
```
|
|
95
333
|
|
|
96
334
|
---
|
|
97
335
|
|
|
98
|
-
|
|
336
|
+
### `sentinel.run(goal, options?): Promise<AgentResult>`
|
|
337
|
+
Run an autonomous multi-step agent to achieve a high-level goal.
|
|
99
338
|
|
|
100
|
-
|
|
339
|
+
| Option | Type | Default | Description |
|
|
340
|
+
|---|---|---|---|
|
|
341
|
+
| `maxSteps` | `number` | `15` | Maximum number of steps |
|
|
342
|
+
| `onStep` | `(event: AgentStepEvent) => void` | — | Step callback |
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
### Tab Management
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
const idx = await sentinel.newTab(url?) // open new tab, returns index
|
|
350
|
+
await sentinel.switchTab(index) // switch active tab
|
|
351
|
+
await sentinel.closeTab(index) // close tab by index
|
|
352
|
+
sentinel.tabCount // number of open tabs
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Session Management
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
await sentinel.saveSession(filePath) // save cookies & storage to JSON
|
|
361
|
+
// auto-load via sessionPath option in constructor
|
|
362
|
+
await sentinel.hasLoginForm() // detect if page has a login form
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### Recording & Replay
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
sentinel.startRecording(name?) // start recording actions
|
|
371
|
+
const workflow = sentinel.stopRecording() // stop and get RecordedWorkflow
|
|
372
|
+
sentinel.exportWorkflowAsCode(workflow) // export as TypeScript string
|
|
373
|
+
sentinel.exportWorkflowAsJSON(workflow) // export as JSON string
|
|
374
|
+
await sentinel.replay(workflow) // replay a recorded workflow
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### Vision
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const png = await sentinel.screenshot() // take a screenshot (Buffer)
|
|
383
|
+
const desc = await sentinel.describeScreen() // visual description via Gemini Vision
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
### Observability
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
sentinel.on('action', handler) // emitted after every act()
|
|
392
|
+
sentinel.on('navigate', handler) // emitted after every goto()
|
|
393
|
+
sentinel.on('close', handler) // emitted on close()
|
|
394
|
+
sentinel.getTokenUsage() // token usage + estimated cost
|
|
395
|
+
sentinel.exportLogs(filePath) // export usage log as JSON
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## 📁 Examples
|
|
401
|
+
|
|
402
|
+
See the [`examples/`](./examples) folder for ready-to-run scripts:
|
|
403
|
+
|
|
404
|
+
- **`hacker-news.ts`** — Extract top stories from Hacker News
|
|
405
|
+
- **`google-search.ts`** — Search Google, extract results, and demo Record & Replay
|
|
406
|
+
- **`agent-amazon.ts`** — Autonomous shopping agent on Amazon
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 🏗️ Development
|
|
101
411
|
|
|
102
412
|
```bash
|
|
103
413
|
git clone https://github.com/ArasHuseyin/sentinel.ai.git
|
|
104
414
|
cd sentinel.ai
|
|
105
415
|
npm install
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
### Running Tests
|
|
110
|
-
A comprehensive WhatsApp Web test is included in the source:
|
|
111
|
-
```bash
|
|
112
|
-
npx tsc
|
|
113
|
-
node dist/whatsapp-test.js
|
|
416
|
+
npx tsc --noEmit # type-check
|
|
417
|
+
npm run build # compile to dist/
|
|
114
418
|
```
|
|
115
419
|
|
|
116
420
|
---
|
|
117
421
|
|
|
118
422
|
## 🧠 Why Sentinel?
|
|
119
423
|
|
|
120
|
-
Unlike standard automation tools that rely on brittle XPaths, Sentinel "sees" the page
|
|
424
|
+
Unlike standard automation tools that rely on brittle XPaths or CSS selectors, Sentinel "sees" the page through the **Accessibility Object Model (AOM)** and falls back to **Gemini Vision** when needed. It understands context, verifies its own actions, and can autonomously plan and execute multi-step goals — making it a true AI agent framework, not just a scripting library.
|
|
425
|
+
|
|
426
|
+
---
|
|
121
427
|
|
|
122
428
|
Licensed under [ISC](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-engine.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/action-engine.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { jest, describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { ActionEngine } from '../api/act.js';
|
|
3
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
4
|
+
function makeState(overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
url: 'https://example.com',
|
|
7
|
+
title: 'Example',
|
|
8
|
+
elements: [
|
|
9
|
+
{ id: 0, role: 'button', name: 'Submit', boundingClientRect: { x: 10, y: 20, width: 80, height: 30 } },
|
|
10
|
+
{ id: 1, role: 'textbox', name: 'Email', boundingClientRect: { x: 10, y: 60, width: 200, height: 30 } },
|
|
11
|
+
{ id: 2, role: 'link', name: 'Home', boundingClientRect: { x: 10, y: 100, width: 60, height: 20 } },
|
|
12
|
+
],
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function makeMockStateParser(state) {
|
|
17
|
+
return {
|
|
18
|
+
parse: jest.fn(async () => state),
|
|
19
|
+
invalidateCache: jest.fn(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function makeMockLocator() {
|
|
23
|
+
const locator = {
|
|
24
|
+
click: jest.fn(async () => { }),
|
|
25
|
+
dblclick: jest.fn(async () => { }),
|
|
26
|
+
hover: jest.fn(async () => { }),
|
|
27
|
+
fill: jest.fn(async () => { }),
|
|
28
|
+
focus: jest.fn(async () => { }),
|
|
29
|
+
press: jest.fn(async () => { }),
|
|
30
|
+
pressSequentially: jest.fn(async () => { }),
|
|
31
|
+
selectOption: jest.fn(async () => []),
|
|
32
|
+
scrollIntoViewIfNeeded: jest.fn(async () => { }),
|
|
33
|
+
evaluate: jest.fn(async () => { }),
|
|
34
|
+
boundingBox: jest.fn(async () => ({ x: 10, y: 20, width: 80, height: 30 })),
|
|
35
|
+
first: jest.fn(() => locator),
|
|
36
|
+
};
|
|
37
|
+
return locator;
|
|
38
|
+
}
|
|
39
|
+
function makeMockPage() {
|
|
40
|
+
return {
|
|
41
|
+
url: () => 'https://example.com',
|
|
42
|
+
waitForLoadState: jest.fn(async () => { }),
|
|
43
|
+
mouse: {
|
|
44
|
+
click: jest.fn(async () => { }),
|
|
45
|
+
dblclick: jest.fn(async () => { }),
|
|
46
|
+
move: jest.fn(async () => { }),
|
|
47
|
+
},
|
|
48
|
+
keyboard: {
|
|
49
|
+
press: jest.fn(async () => { }),
|
|
50
|
+
type: jest.fn(async () => { }),
|
|
51
|
+
},
|
|
52
|
+
evaluate: jest.fn(async () => { }),
|
|
53
|
+
waitForTimeout: jest.fn(async () => { }),
|
|
54
|
+
locator: jest.fn(() => makeMockLocator()),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function makeMockLLM(decision) {
|
|
58
|
+
return {
|
|
59
|
+
generateStructuredData: jest.fn(async () => decision),
|
|
60
|
+
generateText: jest.fn(async () => ''),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// ─── Tests ────────────────────────────────────────────────────────────────────
|
|
64
|
+
describe('ActionEngine', () => {
|
|
65
|
+
it('returns success for a click action', async () => {
|
|
66
|
+
const page = makeMockPage();
|
|
67
|
+
const stateParser = makeMockStateParser(makeState());
|
|
68
|
+
const llm = makeMockLLM({ elementId: 0, action: 'click', reasoning: 'Submit button found' });
|
|
69
|
+
const engine = new ActionEngine(page, stateParser, llm);
|
|
70
|
+
const result = await engine.act('Click the submit button');
|
|
71
|
+
expect(result.success).toBe(true);
|
|
72
|
+
expect(result.action).toContain('click');
|
|
73
|
+
});
|
|
74
|
+
it('returns success for a fill action with value', async () => {
|
|
75
|
+
const page = makeMockPage();
|
|
76
|
+
const stateParser = makeMockStateParser(makeState());
|
|
77
|
+
const llm = makeMockLLM({ elementId: 1, action: 'fill', value: 'test@example.com', reasoning: 'Email field' });
|
|
78
|
+
const engine = new ActionEngine(page, stateParser, llm);
|
|
79
|
+
const result = await engine.act('Fill in the email field with test@example.com');
|
|
80
|
+
expect(result.success).toBe(true);
|
|
81
|
+
expect(result.action).toContain('fill');
|
|
82
|
+
});
|
|
83
|
+
it('interpolates %variable% placeholders', async () => {
|
|
84
|
+
const page = makeMockPage();
|
|
85
|
+
const stateParser = makeMockStateParser(makeState());
|
|
86
|
+
const llm = makeMockLLM({ elementId: 1, action: 'fill', value: 'user@test.com', reasoning: 'Email field' });
|
|
87
|
+
const engine = new ActionEngine(page, stateParser, llm);
|
|
88
|
+
await engine.act('Fill %email% into the email field', { variables: { email: 'user@test.com' } });
|
|
89
|
+
// The prompt passed to LLM should contain the resolved value
|
|
90
|
+
const promptArg = llm.generateStructuredData.mock.calls[0][0];
|
|
91
|
+
expect(promptArg).toContain('user@test.com');
|
|
92
|
+
expect(promptArg).not.toContain('%email%');
|
|
93
|
+
});
|
|
94
|
+
it('returns failure when element id not found', async () => {
|
|
95
|
+
const page = makeMockPage();
|
|
96
|
+
const stateParser = makeMockStateParser(makeState());
|
|
97
|
+
// LLM returns an elementId that does not exist in state
|
|
98
|
+
const llm = makeMockLLM({ elementId: 99, action: 'click', reasoning: 'Non-existent element' });
|
|
99
|
+
const engine = new ActionEngine(page, stateParser, llm);
|
|
100
|
+
const result = await engine.act('Click something that does not exist');
|
|
101
|
+
expect(result.success).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
it('handles scroll-down without target element (elementId 0)', async () => {
|
|
104
|
+
const page = makeMockPage();
|
|
105
|
+
const stateParser = makeMockStateParser(makeState());
|
|
106
|
+
const llm = makeMockLLM({ elementId: 0, action: 'scroll-down', reasoning: 'Scroll page down' });
|
|
107
|
+
// Use a state with NO element id=0 so isScrollWithoutTarget logic triggers correctly
|
|
108
|
+
const emptyState = makeState({ elements: [] });
|
|
109
|
+
const emptyParser = makeMockStateParser(emptyState);
|
|
110
|
+
const engine = new ActionEngine(page, emptyParser, llm);
|
|
111
|
+
const result = await engine.act('Scroll down the page');
|
|
112
|
+
expect(result.success).toBe(true);
|
|
113
|
+
expect(result.action).toContain('scroll-down');
|
|
114
|
+
});
|
|
115
|
+
it('handles press action with keyboard shortcut', async () => {
|
|
116
|
+
const page = makeMockPage();
|
|
117
|
+
const stateParser = makeMockStateParser(makeState());
|
|
118
|
+
const llm = makeMockLLM({ elementId: 0, action: 'press', value: 'Enter', reasoning: 'Press Enter' });
|
|
119
|
+
const engine = new ActionEngine(page, stateParser, llm);
|
|
120
|
+
const result = await engine.act('Press Enter');
|
|
121
|
+
expect(result.success).toBe(true);
|
|
122
|
+
expect(result.action).toContain('press');
|
|
123
|
+
});
|
|
124
|
+
it('calls stateParser.parse() to get current state', async () => {
|
|
125
|
+
const page = makeMockPage();
|
|
126
|
+
const stateParser = makeMockStateParser(makeState());
|
|
127
|
+
const llm = makeMockLLM({ elementId: 0, action: 'click', reasoning: 'OK' });
|
|
128
|
+
const engine = new ActionEngine(page, stateParser, llm);
|
|
129
|
+
await engine.act('Click submit');
|
|
130
|
+
expect(stateParser.parse).toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
it('includes page url and title in LLM prompt', async () => {
|
|
133
|
+
const page = makeMockPage();
|
|
134
|
+
const state = makeState({ url: 'https://shop.example.com', title: 'My Shop' });
|
|
135
|
+
const stateParser = makeMockStateParser(state);
|
|
136
|
+
const llm = makeMockLLM({ elementId: 0, action: 'click', reasoning: 'OK' });
|
|
137
|
+
const engine = new ActionEngine(page, stateParser, llm);
|
|
138
|
+
await engine.act('Click submit');
|
|
139
|
+
const promptArg = llm.generateStructuredData.mock.calls[0][0];
|
|
140
|
+
expect(promptArg).toContain('https://shop.example.com');
|
|
141
|
+
expect(promptArg).toContain('My Shop');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=action-engine.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-engine.test.js","sourceRoot":"","sources":["../../src/__tests__/action-engine.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-parser.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/state-parser.test.ts"],"names":[],"mappings":""}
|