ralphwiggums 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +454 -2
- package/dist/src/checkpoint-do.d.ts +69 -0
- package/dist/src/checkpoint-do.js +232 -0
- package/dist/src/index.d.ts +137 -0
- package/dist/src/index.js +647 -0
- package/package.json +94 -8
- package/index.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jordan Coeyman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,4 +1,456 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ralphwiggums
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Effect-first browser automation.** Give it a prompt, get a completed task.
|
|
4
4
|
|
|
5
|
+
```typescript
|
|
6
|
+
import { run } from "ralphwiggums";
|
|
7
|
+
|
|
8
|
+
// That's it. Just tell it what to do.
|
|
9
|
+
const result = await run("Go to example.com and get the page title");
|
|
10
|
+
|
|
11
|
+
console.log(result.data); // "Example Domain"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install ralphwiggums
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Built with [Effect-TS](https://effect.website) for typed error handling and functional composition. Uses [Stagehand](https://docs.stagehand.dev/v3) for AI-powered browser automation.
|
|
19
|
+
|
|
20
|
+
## Inspiration
|
|
21
|
+
|
|
22
|
+
This library is inspired by the **Ralph Loop** pattern discovered by [Geoffrey Huntley](https://ghuntley.com/). The Ralph Loop is a simple but powerful pattern: give an AI agent a task, let it iterate until completion, and handle failures gracefully. As Geoffrey puts it, "Ralph Wiggum as a software engineer" — persistent, determined, and surprisingly effective.
|
|
23
|
+
|
|
24
|
+
Learn more about Ralph Loops and Geoffrey's work on [his blog](https://ghuntley.com/) and [Twitter](https://x.com/GeoffreyHuntley).
|
|
25
|
+
|
|
26
|
+
## What it does
|
|
27
|
+
|
|
28
|
+
1. **Prompt → Action** - Send natural language instructions ("click submit button")
|
|
29
|
+
2. **Retry Loop** - Automatically retries until task completes or max iterations reached
|
|
30
|
+
3. **Error Handling** - Typed errors for every failure mode (timeout, max iterations, browser crash)
|
|
31
|
+
4. **Browser Cleanup** - Automatically closes browsers after each task (prevents memory leaks)
|
|
32
|
+
|
|
33
|
+
## Architecture
|
|
34
|
+
|
|
35
|
+
ralphwiggums uses a **three-tier architecture** for scalable browser automation:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
┌─────────────────────────────────────────────────────┐
|
|
39
|
+
│ SvelteKit App (Demo UI) │
|
|
40
|
+
│ src/routes/ │
|
|
41
|
+
│ ├── +page.svelte - Main landing page │
|
|
42
|
+
│ ├── +layout.svelte - Layout with sidebar │
|
|
43
|
+
│ └── api/product-research/ - API endpoint │
|
|
44
|
+
├─────────────────────────────────────────────────────┤
|
|
45
|
+
│ Worker (ralphwiggums-api) │
|
|
46
|
+
│ src/worker.ts │
|
|
47
|
+
│ └── OrchestratorDO - Task scheduling │
|
|
48
|
+
├─────────────────────────────────────────────────────┤
|
|
49
|
+
│ Container (ralph-container) │
|
|
50
|
+
│ container/server.ts │
|
|
51
|
+
│ └── Stagehand browser - Real browser control │
|
|
52
|
+
└─────────────────────────────────────────────────────┘
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Component Responsibilities:**
|
|
56
|
+
- **Orchestrator DO** (Durable Object): Manages task scheduling, persistence, and session state using ironalarm
|
|
57
|
+
- **Container Server** (port 8081): Manages browser pool and executes individual automation tasks
|
|
58
|
+
- **Worker/API**: REST endpoints for queueing tasks and monitoring status
|
|
59
|
+
|
|
60
|
+
**Why this architecture?**
|
|
61
|
+
- **Orchestrator**: Handles persistence, retries, and concurrent task management
|
|
62
|
+
- **Container**: Owns browser lifecycle and resource management
|
|
63
|
+
- **Worker**: Provides HTTP API interface to the orchestrator
|
|
64
|
+
|
|
65
|
+
This separation enables reliable, resumable browser automation with proper resource management.
|
|
66
|
+
|
|
67
|
+
## AI Provider
|
|
68
|
+
|
|
69
|
+
ralphwiggums uses **OpenCode Zen** for browser automation. Zen offers free models to get started.
|
|
70
|
+
|
|
71
|
+
### OpenCode Zen
|
|
72
|
+
|
|
73
|
+
**Required environment variable:**
|
|
74
|
+
```bash
|
|
75
|
+
ZEN_API_KEY=your_zen_api_key_here
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Getting your API key:**
|
|
79
|
+
1. Sign up for a free OpenCode Zen account
|
|
80
|
+
2. Get your API key from the Zen dashboard
|
|
81
|
+
3. Use that key as `ZEN_API_KEY` in your environment
|
|
82
|
+
|
|
83
|
+
**Optional configuration:**
|
|
84
|
+
```bash
|
|
85
|
+
AI_PROVIDER=zen # Default, can be omitted
|
|
86
|
+
ZEN_MODEL=claude-sonnet-4-5-20250929 # Default model (free tier available)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Free tier:** OpenCode Zen offers free models to get started with browser automation.
|
|
90
|
+
|
|
91
|
+
## Response
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
interface RalphResult {
|
|
95
|
+
success: boolean;
|
|
96
|
+
message: string;
|
|
97
|
+
data?: T; // Extracted data
|
|
98
|
+
iterations: number;
|
|
99
|
+
checkpointId?: string; // For resuming if interrupted
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Error Types
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
type RalphError =
|
|
107
|
+
| MaxIterationsError // Task exceeded maxIterations
|
|
108
|
+
| TimeoutError // Task timed out
|
|
109
|
+
| BrowserError // Browser operation failed
|
|
110
|
+
| ValidationError // Invalid prompt/input
|
|
111
|
+
| RateLimitError // Too many requests
|
|
112
|
+
| UnauthorizedError // Missing/invalid API key
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Error Handling Examples
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { run, MaxIterationsError, TimeoutError, BrowserError } from "ralphwiggums";
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = await run("Go to example.com and click submit", {
|
|
122
|
+
maxIterations: 3,
|
|
123
|
+
timeout: 30000
|
|
124
|
+
});
|
|
125
|
+
console.log(result.data);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof MaxIterationsError) {
|
|
128
|
+
console.error(`Task failed after ${error.maxIterations} iterations`);
|
|
129
|
+
} else if (error instanceof TimeoutError) {
|
|
130
|
+
console.error(`Task timed out after ${error.duration}ms`);
|
|
131
|
+
} else if (error instanceof BrowserError) {
|
|
132
|
+
console.error(`Browser error: ${error.reason}`);
|
|
133
|
+
} else {
|
|
134
|
+
console.error("Unknown error:", error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Installation
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm install ralphwiggums
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Note**: All examples work in TypeScript. Types are included in the package.
|
|
146
|
+
|
|
147
|
+
## Prerequisites
|
|
148
|
+
|
|
149
|
+
- **Node.js 18+** required
|
|
150
|
+
- **AI Provider** required for browser automation:
|
|
151
|
+
- **OpenCode Zen** - Requires `ZEN_API_KEY`
|
|
152
|
+
- Model: `claude-sonnet-4-5-20250929`
|
|
153
|
+
- See `.env.example` for all environment variables
|
|
154
|
+
|
|
155
|
+
## Quick Start
|
|
156
|
+
|
|
157
|
+
1. **Install the package:**
|
|
158
|
+
```bash
|
|
159
|
+
npm install ralphwiggums
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
2. **Set up environment variables:**
|
|
163
|
+
```bash
|
|
164
|
+
# Copy .env.example to .env
|
|
165
|
+
cp .env.example .env
|
|
166
|
+
|
|
167
|
+
# Edit .env and set your ZEN_API_KEY
|
|
168
|
+
# ZEN_API_KEY=your_zen_api_key
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
3. **Run your first automation:**
|
|
172
|
+
```typescript
|
|
173
|
+
import { run } from "ralphwiggums";
|
|
174
|
+
|
|
175
|
+
const result = await run("Go to example.com and get the page title");
|
|
176
|
+
console.log(result.data); // "Example Domain"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Local Development
|
|
180
|
+
|
|
181
|
+
ralphwiggums requires a **two-terminal setup** for local development.
|
|
182
|
+
|
|
183
|
+
### Setup
|
|
184
|
+
|
|
185
|
+
**Terminal 1: Container server** (runs browser automation)
|
|
186
|
+
```bash
|
|
187
|
+
# From the ralphwiggums directory
|
|
188
|
+
source .env
|
|
189
|
+
PORT=8081 bun run --hot container/server.ts
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Terminal 2: SvelteKit app** (API endpoints + demo UI)
|
|
193
|
+
```bash
|
|
194
|
+
# From the ralphwiggums directory
|
|
195
|
+
bun run dev
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Visit http://localhost:5173 to use the demo UI, or call the API directly at http://localhost:5173/api/.
|
|
199
|
+
|
|
200
|
+
### Verify Everything Works
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Check container is running
|
|
204
|
+
curl http://localhost:8081/health
|
|
205
|
+
# Expected: {"success":true,"data":{"status":"healthy","browser":false}}
|
|
206
|
+
|
|
207
|
+
# Check worker is responding
|
|
208
|
+
curl http://localhost:5173/health
|
|
209
|
+
# Expected: {"status":"healthy",...}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### One-Command Startup (Optional)
|
|
213
|
+
|
|
214
|
+
For convenience, use the provided script to start both terminals:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Starts both container and dev server in one command
|
|
218
|
+
./dev.sh
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Stop with `Ctrl+C` (stops both terminals).
|
|
222
|
+
|
|
223
|
+
### Troubleshooting
|
|
224
|
+
|
|
225
|
+
| Error | Fix |
|
|
226
|
+
|-------|-----|
|
|
227
|
+
| "Container binding not set" | Container server isn't running. Start Terminal 1. |
|
|
228
|
+
| ECONNREFUSED on port 8081 | Port in use. Kill existing: `lsof -ti:8081 | xargs kill` |
|
|
229
|
+
| Browser won't start | Check `ZEN_API_KEY` is set in `.env` |
|
|
230
|
+
| Port 8080 conflict | Alchemy dev uses port 8080. Container server uses 8081 by default. |
|
|
231
|
+
| Docker containers accumulating | Clean up before deploy: `docker ps -a \| grep -E "ralph\|desktop-linux" \| awk '{print $1}' \| xargs -r docker rm -f && docker system prune -f` |
|
|
232
|
+
|
|
233
|
+
#### Stagehand Extraction Behavior
|
|
234
|
+
|
|
235
|
+
Understanding how Stagehand handles extraction is important for getting reliable results:
|
|
236
|
+
|
|
237
|
+
- **`extract()` returns `{ extraction: "text" }`** - The response object has an `extraction` property, not `text`
|
|
238
|
+
- **`act()` handles both actions AND extraction** - Stagehand v3's `act()` is "intelligent" - it can navigate, extract, and interact based on natural language
|
|
239
|
+
- **Best approach**: Use `extract()` for extraction prompts
|
|
240
|
+
- **Prompt format matters**:
|
|
241
|
+
- ✅ Works: "Go to URL and get all visible text"
|
|
242
|
+
- ❌ Doesn't work: "Extract from URL: instructions"
|
|
243
|
+
- ✅ Fixed: Auto-transform "Extract from URL: instructions" → "Go to URL and instructions"
|
|
244
|
+
- **Zod schema optional**: Pass `undefined` to `extract()` for raw text
|
|
245
|
+
|
|
246
|
+
#### Docker Cleanup
|
|
247
|
+
|
|
248
|
+
Before deploying, clean up old Docker containers to prevent memory issues:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# Clean up old containers from alchemy dev
|
|
252
|
+
docker ps -a | grep -E "ralph|desktop-linux" | awk '{print $1}' | xargs -r docker rm -f
|
|
253
|
+
|
|
254
|
+
# Prune Docker system to free memory
|
|
255
|
+
docker system prune -f
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Alchemy creates new Docker containers on each deploy. Old containers accumulate and fill up memory if not cleaned regularly.
|
|
259
|
+
|
|
260
|
+
## Usage
|
|
261
|
+
|
|
262
|
+
### Direct API
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { run } from "ralphwiggums";
|
|
266
|
+
|
|
267
|
+
// Simple extraction
|
|
268
|
+
const result = await run("Go to https://example.com and get the page title");
|
|
269
|
+
console.log(result.data); // "Example Domain"
|
|
270
|
+
|
|
271
|
+
// Form filling
|
|
272
|
+
const result2 = await run(
|
|
273
|
+
"Go to https://example.com/contact, find the name field and type 'John Smith'"
|
|
274
|
+
);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Options
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
interface RalphOptions {
|
|
281
|
+
maxIterations?: number; // Default: 10
|
|
282
|
+
timeout?: number; // Default: 300000ms (5 minutes)
|
|
283
|
+
resumeFrom?: string; // Checkpoint ID to resume from
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const result = await run("Long running task", {
|
|
287
|
+
maxIterations: 5,
|
|
288
|
+
timeout: 60000, // 1 minute
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Worker Integration
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { createHandlers, setContainerBinding } from "ralphwiggums";
|
|
296
|
+
|
|
297
|
+
export class RalphAgent extends DurableObject {
|
|
298
|
+
async fetch(request) {
|
|
299
|
+
// Use Container binding in production
|
|
300
|
+
setContainerBinding(this.env.CONTAINER);
|
|
301
|
+
const app = createHandlers();
|
|
302
|
+
return app.fetch(request, this.env);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Advanced: Checkpoints
|
|
308
|
+
|
|
309
|
+
Tasks return a `checkpointId` that you can use to resume interrupted tasks:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
const result = await run("Long running task", { maxIterations: 10 });
|
|
313
|
+
|
|
314
|
+
// If task is interrupted, save the checkpointId
|
|
315
|
+
const checkpointId = result.checkpointId; // e.g., "task-123-5"
|
|
316
|
+
|
|
317
|
+
// Later, resume from checkpoint
|
|
318
|
+
const resumed = await run("", { resumeFrom: checkpointId });
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Note**: Checkpoints expire after 1 hour. They're useful for:
|
|
322
|
+
- Long-running tasks that might be interrupted
|
|
323
|
+
- Network failures
|
|
324
|
+
- Rate limit recovery
|
|
325
|
+
|
|
326
|
+
## Rate Limiting
|
|
327
|
+
|
|
328
|
+
By default, ralphwiggums limits requests to **60 per minute per IP address**.
|
|
329
|
+
|
|
330
|
+
When rate limited, the error response includes a `retryAfter` field (in seconds):
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
try {
|
|
334
|
+
const result = await run("...");
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (error instanceof RateLimitError) {
|
|
337
|
+
console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
|
|
338
|
+
await new Promise(r => setTimeout(r, error.retryAfter * 1000));
|
|
339
|
+
// Retry...
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Concurrency Limits
|
|
345
|
+
|
|
346
|
+
By default, ralphwiggums processes **5 concurrent requests** at a time. Additional requests are queued automatically.
|
|
347
|
+
|
|
348
|
+
Configure via environment variable:
|
|
349
|
+
```bash
|
|
350
|
+
RALPH_MAX_CONCURRENT=10 # Allow 10 concurrent requests
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Deployment
|
|
354
|
+
|
|
355
|
+
### Prerequisites
|
|
356
|
+
|
|
357
|
+
- OpenCode Zen API key (`ZEN_API_KEY`)
|
|
358
|
+
- Alchemy CLI installed (for infrastructure management)
|
|
359
|
+
|
|
360
|
+
### Steps
|
|
361
|
+
|
|
362
|
+
1. **Set environment variables:**
|
|
363
|
+
```bash
|
|
364
|
+
export ZEN_API_KEY=your_api_key
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
2. **Deploy:**
|
|
368
|
+
```bash
|
|
369
|
+
bun run deploy
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
3. **Verify:**
|
|
373
|
+
```bash
|
|
374
|
+
curl https://your-worker.workers.dev/health
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The deployment uses Alchemy to manage:
|
|
378
|
+
- Container for browser automation
|
|
379
|
+
- Worker with Container binding
|
|
380
|
+
- KV namespace for rate limiting
|
|
381
|
+
|
|
382
|
+
See `alchemy.run.ts` for infrastructure configuration.
|
|
383
|
+
|
|
384
|
+
### Troubleshooting
|
|
385
|
+
|
|
386
|
+
| Error | Fix |
|
|
387
|
+
|-------|-----|
|
|
388
|
+
| "Container binding not set" | Verify Container binding is configured in `alchemy.run.ts` |
|
|
389
|
+
| Browser crashes or timeouts | Verify ZEN_API_KEY is valid: `ZEN_API_KEY` |
|
|
390
|
+
| Rate limit errors | Default: 60 requests/minute per IP. Wait for `retryAfter` seconds before retrying. |
|
|
391
|
+
| Port conflicts | Container server uses port 8081 by default. Change with: `PORT=8082 bun run container/server.ts` |
|
|
392
|
+
|
|
393
|
+
## Package Exports
|
|
394
|
+
|
|
395
|
+
ralphwiggums provides multiple exports for different use cases:
|
|
396
|
+
|
|
397
|
+
1. **Main export** (direct API usage):
|
|
398
|
+
```typescript
|
|
399
|
+
import { run, doThis, createHandlers } from "ralphwiggums";
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
2. **Orchestrator components** (advanced usage):
|
|
403
|
+
```typescript
|
|
404
|
+
import { OrchestratorDO, createPool, dispatchTasks } from "ralphwiggums";
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
3. **Checkpoint Durable Object** (production deployments):
|
|
408
|
+
```typescript
|
|
409
|
+
import { CheckpointDO } from "ralphwiggums/checkpoint-do";
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Orchestrator API
|
|
413
|
+
|
|
414
|
+
For advanced usage with the orchestrator:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// Queue a task
|
|
418
|
+
const response = await fetch('/orchestrator/queue', {
|
|
419
|
+
method: 'POST',
|
|
420
|
+
headers: { 'Content-Type': 'application/json' },
|
|
421
|
+
body: JSON.stringify({
|
|
422
|
+
prompt: "Go to example.com and extract the title",
|
|
423
|
+
maxIterations: 5
|
|
424
|
+
})
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Check task status
|
|
428
|
+
const status = await fetch(`/orchestrator/tasks/${taskId}`);
|
|
429
|
+
|
|
430
|
+
// List all tasks
|
|
431
|
+
const tasks = await fetch('/orchestrator/tasks');
|
|
432
|
+
|
|
433
|
+
// Get pool status
|
|
434
|
+
const pool = await fetch('/orchestrator/pool');
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Documentation
|
|
438
|
+
|
|
439
|
+
### Core Libraries
|
|
440
|
+
- **Stagehand** ([docs](https://docs.stagehand.dev/v3), [GitHub](https://github.com/browserbase/stagehand)) - AI-powered browser automation
|
|
441
|
+
- **Effect** ([docs](https://effect.website), [GitHub](https://github.com/effect-ts/effect)) - Functional programming library
|
|
442
|
+
- **Hono** ([docs](https://hono.dev), [GitHub](https://github.com/honojs/hono)) - Lightweight web framework
|
|
443
|
+
|
|
444
|
+
### Reference Implementations
|
|
445
|
+
- **AgentCast** ([GitHub](https://github.com/acoyfellow/agentcast)) - Container-based browser automation pattern
|
|
446
|
+
|
|
447
|
+
## Tests
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
# Run tests (E2E tests require container server running)
|
|
451
|
+
bun test
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## Version
|
|
455
|
+
|
|
456
|
+
0.0.1 - Initial release
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ralphwiggums - Durable Object Checkpoint Storage
|
|
3
|
+
* Production-ready checkpoint persistence using Cloudflare Durable Objects.
|
|
4
|
+
*/
|
|
5
|
+
import type { DurableObjectState } from "@cloudflare/workers-types";
|
|
6
|
+
declare const CheckpointError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
|
|
7
|
+
readonly _tag: "CheckpointError";
|
|
8
|
+
} & Readonly<A>;
|
|
9
|
+
export declare class CheckpointError extends CheckpointError_base<{
|
|
10
|
+
checkpointId: string;
|
|
11
|
+
reason: string;
|
|
12
|
+
}> {
|
|
13
|
+
}
|
|
14
|
+
export interface CheckpointData {
|
|
15
|
+
checkpointId: string;
|
|
16
|
+
taskId: string;
|
|
17
|
+
iteration: number;
|
|
18
|
+
url?: string;
|
|
19
|
+
pageState?: string;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
expiresAt: number;
|
|
22
|
+
}
|
|
23
|
+
export interface CheckpointStore {
|
|
24
|
+
save(data: CheckpointData): Promise<void>;
|
|
25
|
+
load(checkpointId: string): Promise<CheckpointData | null>;
|
|
26
|
+
delete(checkpointId: string): Promise<void>;
|
|
27
|
+
list(taskId: string): Promise<CheckpointData[]>;
|
|
28
|
+
gc(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates an in-memory checkpoint store (for development/testing).
|
|
32
|
+
*/
|
|
33
|
+
export declare function createInMemoryCheckpointStore(): CheckpointStore;
|
|
34
|
+
export interface CheckpointDOState {
|
|
35
|
+
storage: DurableObjectStorage;
|
|
36
|
+
}
|
|
37
|
+
export interface DurableObjectStorage {
|
|
38
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
39
|
+
put(key: string, value: unknown): Promise<void>;
|
|
40
|
+
delete(key: string): Promise<void>;
|
|
41
|
+
list<T>(options?: {
|
|
42
|
+
start?: string;
|
|
43
|
+
end?: string;
|
|
44
|
+
limit?: number;
|
|
45
|
+
}): Promise<Map<string, T>>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Checkpoint Durable Object utility class for Cloudflare Workers.
|
|
49
|
+
*
|
|
50
|
+
* Usage in worker:
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { CheckpointDO } from "ralphwiggums/checkpoint-do";
|
|
53
|
+
*
|
|
54
|
+
* export class RalphAgent extends DurableObject {
|
|
55
|
+
* async fetch(request) {
|
|
56
|
+
* return CheckpointDO.fetch(this.state, this.env, request);
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare class CheckpointDO {
|
|
62
|
+
static fetch(state: DurableObjectState, env: Record<string, unknown>, request: Request): Promise<Response>;
|
|
63
|
+
private static save;
|
|
64
|
+
private static load;
|
|
65
|
+
private static delete;
|
|
66
|
+
private static list;
|
|
67
|
+
private static gc;
|
|
68
|
+
}
|
|
69
|
+
export {};
|