web-agent-bridge 2.3.1 → 2.5.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.
Files changed (53) hide show
  1. package/README.ar.md +524 -31
  2. package/README.md +592 -47
  3. package/bin/agent-runner.js +10 -1
  4. package/package.json +1 -1
  5. package/public/agent-workspace.html +347 -0
  6. package/public/browser.html +484 -0
  7. package/public/css/agent-workspace.css +1713 -0
  8. package/public/index.html +94 -0
  9. package/public/js/agent-workspace.js +1740 -0
  10. package/sdk/index.d.ts +253 -0
  11. package/sdk/index.js +360 -1
  12. package/sdk/package.json +1 -1
  13. package/server/config/secrets.js +13 -5
  14. package/server/control-plane/index.js +301 -0
  15. package/server/data-plane/index.js +354 -0
  16. package/server/index.js +185 -4
  17. package/server/llm/index.js +404 -0
  18. package/server/middleware/adminAuth.js +6 -1
  19. package/server/middleware/auth.js +11 -2
  20. package/server/middleware/rateLimits.js +78 -2
  21. package/server/migrations/003_ads_integer_cents.sql +33 -0
  22. package/server/models/db.js +126 -25
  23. package/server/observability/index.js +394 -0
  24. package/server/protocol/capabilities.js +223 -0
  25. package/server/protocol/index.js +243 -0
  26. package/server/protocol/schema.js +584 -0
  27. package/server/registry/index.js +326 -0
  28. package/server/routes/admin.js +16 -2
  29. package/server/routes/ads.js +130 -0
  30. package/server/routes/agent-workspace.js +378 -0
  31. package/server/routes/api.js +21 -2
  32. package/server/routes/auth.js +26 -6
  33. package/server/routes/runtime.js +725 -0
  34. package/server/routes/sovereign.js +78 -0
  35. package/server/routes/universal.js +177 -0
  36. package/server/routes/wab-api.js +20 -5
  37. package/server/runtime/event-bus.js +210 -0
  38. package/server/runtime/index.js +233 -0
  39. package/server/runtime/sandbox.js +266 -0
  40. package/server/runtime/scheduler.js +395 -0
  41. package/server/runtime/state-manager.js +188 -0
  42. package/server/security/index.js +355 -0
  43. package/server/services/agent-chat.js +506 -0
  44. package/server/services/agent-symphony.js +6 -0
  45. package/server/services/agent-tasks.js +1807 -0
  46. package/server/services/fairness-engine.js +409 -0
  47. package/server/services/plugins.js +27 -3
  48. package/server/services/price-intelligence.js +565 -0
  49. package/server/services/price-shield.js +1137 -0
  50. package/server/services/search-engine.js +357 -0
  51. package/server/services/security.js +513 -0
  52. package/server/services/universal-scraper.js +661 -0
  53. package/server/ws.js +61 -1
@@ -0,0 +1,233 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * WAB Runtime - Main Entry Point
5
+ *
6
+ * The Runtime is the core of the Agent OS. It provides:
7
+ * - Task scheduling with retries and timeouts
8
+ * - Agent state management with checkpoints
9
+ * - Execution sandboxing
10
+ * - Event-driven architecture
11
+ *
12
+ * This is NOT a browser wrapper - it's a real execution runtime
13
+ * comparable to Ray or Temporal.
14
+ */
15
+
16
+ const { EventBus, bus } = require('./event-bus');
17
+ const { Scheduler, TaskState } = require('./scheduler');
18
+ const { StateManager } = require('./state-manager');
19
+ const { ExecutionSandbox } = require('./sandbox');
20
+
21
+ class WABRuntime {
22
+ constructor(options = {}) {
23
+ this.scheduler = new Scheduler({
24
+ maxConcurrent: options.maxConcurrentTasks || 20,
25
+ maxQueueSize: options.maxQueueSize || 1000,
26
+ defaultRetries: options.defaultRetries || 3,
27
+ defaultTimeout: options.defaultTimeout || 30000,
28
+ });
29
+
30
+ this.state = new StateManager({
31
+ maxCheckpoints: options.maxCheckpoints || 50,
32
+ ttl: options.stateTTL || 24 * 3600_000,
33
+ });
34
+
35
+ this.sandbox = new ExecutionSandbox({
36
+ maxConcurrent: options.maxSandboxes || 100,
37
+ defaultTimeout: options.sandboxTimeout || 30000,
38
+ });
39
+
40
+ this.events = bus;
41
+ this._started = false;
42
+ this._cleanupTimer = null;
43
+
44
+ // Register built-in task handlers
45
+ this._registerBuiltinHandlers();
46
+
47
+ // Wire events
48
+ this._wireEventHandlers();
49
+ }
50
+
51
+ /**
52
+ * Start the runtime
53
+ */
54
+ start() {
55
+ if (this._started) return;
56
+ this._started = true;
57
+
58
+ // Periodic cleanup
59
+ this._cleanupTimer = setInterval(() => {
60
+ this.state.cleanup();
61
+ this.sandbox.cleanup();
62
+ }, 600_000); // 10 min
63
+ if (this._cleanupTimer.unref) this._cleanupTimer.unref();
64
+
65
+ this.events.emit('runtime.started', {
66
+ timestamp: Date.now(),
67
+ capabilities: this.getCapabilities(),
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Stop the runtime
73
+ */
74
+ stop() {
75
+ if (!this._started) return;
76
+ this._started = false;
77
+ if (this._cleanupTimer) clearInterval(this._cleanupTimer);
78
+ this.events.emit('runtime.stopped', { timestamp: Date.now() });
79
+ }
80
+
81
+ /**
82
+ * Submit a task
83
+ */
84
+ submitTask(task) {
85
+ if (!this._started) throw new Error('Runtime not started');
86
+ return this.scheduler.submit(task);
87
+ }
88
+
89
+ /**
90
+ * Execute a task in a sandbox with full lifecycle
91
+ */
92
+ async executeInSandbox(taskId, handler, options = {}) {
93
+ // Create sandbox
94
+ const sbx = this.sandbox.create(taskId, options);
95
+
96
+ // Save initial state
97
+ this.state.save(taskId, { status: 'sandbox_created', sandboxId: sbx.id });
98
+ this.state.checkpoint(taskId, 'pre-execution');
99
+
100
+ try {
101
+ const result = await this.sandbox.execute(sbx.id, handler);
102
+
103
+ if (result.success) {
104
+ this.state.save(taskId, { status: 'completed', result: result.result });
105
+ } else {
106
+ this.state.save(taskId, { status: 'failed', error: result.error });
107
+ }
108
+
109
+ return result;
110
+ } finally {
111
+ // Cleanup sandbox after a delay
112
+ setTimeout(() => this.sandbox.destroy(sbx.id), 60000);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Register a task type handler
118
+ */
119
+ registerTaskHandler(taskType, handler) {
120
+ this.scheduler.registerHandler(taskType, handler);
121
+ }
122
+
123
+ /**
124
+ * Get runtime capabilities for protocol exposure
125
+ */
126
+ getCapabilities() {
127
+ return {
128
+ scheduler: { maxConcurrent: 20, retries: true, deadlines: true, dependencies: true },
129
+ state: { checkpoints: true, rollback: true, ttl: true },
130
+ sandbox: { isolation: true, resourceLimits: true, auditTrail: true },
131
+ events: { async: true, replay: true, wildcards: true, deadLetter: true },
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Get runtime health and stats
137
+ */
138
+ getHealth() {
139
+ return {
140
+ status: this._started ? 'running' : 'stopped',
141
+ uptime: this._started ? Date.now() : 0,
142
+ scheduler: this.scheduler.getStats(),
143
+ state: this.state.getStats(),
144
+ sandbox: this.sandbox.getStats(),
145
+ events: this.events.getStats(),
146
+ };
147
+ }
148
+
149
+ // ─── Built-in Handlers ──────────────────────────────────────────────────
150
+
151
+ _registerBuiltinHandlers() {
152
+ // Browser task handler (delegated to data plane)
153
+ this.scheduler.registerHandler('browser', async (params, ctx) => {
154
+ ctx.reportProgress(10, 0);
155
+ // Browser tasks are handled by data-plane executor
156
+ return { delegated: 'data-plane', params };
157
+ });
158
+
159
+ // API task handler
160
+ this.scheduler.registerHandler('api', async (params, ctx) => {
161
+ ctx.reportProgress(10, 0);
162
+ return { delegated: 'data-plane', params };
163
+ });
164
+
165
+ // Extraction task handler
166
+ this.scheduler.registerHandler('extraction', async (params, ctx) => {
167
+ ctx.reportProgress(10, 0);
168
+ return { delegated: 'data-plane', params };
169
+ });
170
+
171
+ // Workflow (composite) handler
172
+ this.scheduler.registerHandler('workflow', async (params, ctx) => {
173
+ const results = [];
174
+ for (let i = 0; i < ctx.steps.length; i++) {
175
+ ctx.reportProgress(Math.floor((i / ctx.steps.length) * 100), i);
176
+ ctx.checkpoint({ step: i, results });
177
+
178
+ const step = ctx.steps[i];
179
+ const handler = this.scheduler._handlers.get(step.type || 'general');
180
+ if (handler) {
181
+ const result = await handler(step.params || {}, ctx);
182
+ results.push({ step: i, result });
183
+ }
184
+ }
185
+ ctx.reportProgress(100, ctx.steps.length);
186
+ return { steps: results };
187
+ });
188
+
189
+ // Composite task (parallel execution)
190
+ this.scheduler.registerHandler('composite', async (params, ctx) => {
191
+ if (!params.tasks || !Array.isArray(params.tasks)) {
192
+ throw new Error('Composite task requires tasks array');
193
+ }
194
+ const promises = params.tasks.map((t, i) => {
195
+ const handler = this.scheduler._handlers.get(t.type || 'general');
196
+ if (!handler) return { step: i, error: `No handler for: ${t.type}` };
197
+ return handler(t.params || {}, ctx).then(r => ({ step: i, result: r }));
198
+ });
199
+ return { results: await Promise.allSettled(promises) };
200
+ });
201
+
202
+ // General purpose handler (pass-through)
203
+ this.scheduler.registerHandler('general', async (params) => {
204
+ return { received: params };
205
+ });
206
+ }
207
+
208
+ // ─── Event Wiring ────────────────────────────────────────────────────────
209
+
210
+ _wireEventHandlers() {
211
+ // Track task states in state manager
212
+ this.events.on('task.started', (data) => {
213
+ this.state.save(data.taskId, { status: 'running', startedAt: Date.now(), attempt: data.attempt });
214
+ });
215
+
216
+ this.events.on('task.completed', (data) => {
217
+ this.state.merge(data.taskId, { status: 'completed', duration: data.duration });
218
+ });
219
+
220
+ this.events.on('task.failed', (data) => {
221
+ this.state.merge(data.taskId, { status: 'failed', error: data.error, attempts: data.attempts });
222
+ });
223
+
224
+ this.events.on('task.progress', (data) => {
225
+ this.state.merge(data.taskId, { progress: data.progress, currentStep: data.step });
226
+ });
227
+ }
228
+ }
229
+
230
+ // Singleton runtime
231
+ const runtime = new WABRuntime();
232
+
233
+ module.exports = { WABRuntime, runtime, EventBus, bus, Scheduler, TaskState, StateManager, ExecutionSandbox };
@@ -0,0 +1,266 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * WAB Runtime - Execution Sandbox
5
+ *
6
+ * Isolates task execution. Each task runs in its own sandbox with:
7
+ * - Resource limits (memory, CPU time, network)
8
+ * - Permission boundaries
9
+ * - Isolated state
10
+ * - Audit trail
11
+ */
12
+
13
+ const crypto = require('crypto');
14
+
15
+ class ExecutionSandbox {
16
+ constructor(options = {}) {
17
+ this._sandboxes = new Map();
18
+ this._maxConcurrent = options.maxConcurrent || 100;
19
+ this._defaultTimeout = options.defaultTimeout || 30000;
20
+ this._stats = { created: 0, completed: 0, failed: 0, timedOut: 0 };
21
+ }
22
+
23
+ /**
24
+ * Create a new sandbox for a task
25
+ */
26
+ create(taskId, options = {}) {
27
+ if (this._sandboxes.size >= this._maxConcurrent) {
28
+ throw new Error('Maximum concurrent sandbox limit reached');
29
+ }
30
+
31
+ const sandbox = {
32
+ id: `sbx_${crypto.randomBytes(12).toString('hex')}`,
33
+ taskId,
34
+ agentId: options.agentId || null,
35
+ siteId: options.siteId || null,
36
+
37
+ // Resource limits
38
+ limits: {
39
+ timeout: options.timeout || this._defaultTimeout,
40
+ maxMemory: options.maxMemory || 128 * 1024 * 1024, // 128MB
41
+ maxNetworkCalls: options.maxNetworkCalls || 100,
42
+ maxDomOperations: options.maxDomOperations || 1000,
43
+ allowedDomains: options.allowedDomains || ['*'],
44
+ blockedSelectors: options.blockedSelectors || [],
45
+ },
46
+
47
+ // Runtime state
48
+ state: 'created',
49
+ usage: {
50
+ networkCalls: 0,
51
+ domOperations: 0,
52
+ startedAt: null,
53
+ completedAt: null,
54
+ },
55
+
56
+ // Permission boundaries
57
+ capabilities: new Set(options.capabilities || []),
58
+
59
+ // Audit trail
60
+ audit: [],
61
+
62
+ // Isolated key-value store
63
+ store: new Map(),
64
+
65
+ createdAt: Date.now(),
66
+ };
67
+
68
+ this._sandboxes.set(sandbox.id, sandbox);
69
+ this._stats.created++;
70
+ return sandbox;
71
+ }
72
+
73
+ /**
74
+ * Execute a function within a sandbox
75
+ */
76
+ async execute(sandboxId, fn) {
77
+ const sandbox = this._sandboxes.get(sandboxId);
78
+ if (!sandbox) throw new Error(`Sandbox not found: ${sandboxId}`);
79
+ if (sandbox.state !== 'created' && sandbox.state !== 'running') {
80
+ throw new Error(`Sandbox ${sandboxId} is in state ${sandbox.state}, cannot execute`);
81
+ }
82
+
83
+ sandbox.state = 'running';
84
+ sandbox.usage.startedAt = Date.now();
85
+
86
+ // Create scoped context for the function
87
+ const context = this._createContext(sandbox);
88
+
89
+ try {
90
+ const result = await _withTimeout(fn(context), sandbox.limits.timeout);
91
+ sandbox.state = 'completed';
92
+ sandbox.usage.completedAt = Date.now();
93
+ this._stats.completed++;
94
+
95
+ sandbox.audit.push({
96
+ action: 'complete',
97
+ timestamp: Date.now(),
98
+ duration: sandbox.usage.completedAt - sandbox.usage.startedAt,
99
+ });
100
+
101
+ return { success: true, result, sandbox: this._getSandboxSummary(sandbox) };
102
+ } catch (err) {
103
+ sandbox.state = err.message.includes('timed out') ? 'timeout' : 'failed';
104
+ sandbox.usage.completedAt = Date.now();
105
+
106
+ if (sandbox.state === 'timeout') this._stats.timedOut++;
107
+ else this._stats.failed++;
108
+
109
+ sandbox.audit.push({
110
+ action: sandbox.state,
111
+ timestamp: Date.now(),
112
+ error: err.message,
113
+ });
114
+
115
+ return { success: false, error: err.message, sandbox: this._getSandboxSummary(sandbox) };
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Create a scoped execution context
121
+ */
122
+ _createContext(sandbox) {
123
+ const self = this;
124
+ return {
125
+ taskId: sandbox.taskId,
126
+ agentId: sandbox.agentId,
127
+ siteId: sandbox.siteId,
128
+
129
+ // Capability check
130
+ hasCapability(cap) {
131
+ return sandbox.capabilities.has(cap);
132
+ },
133
+
134
+ requireCapability(cap) {
135
+ if (!sandbox.capabilities.has(cap)) {
136
+ throw new Error(`Sandbox lacks capability: ${cap}`);
137
+ }
138
+ },
139
+
140
+ // Domain check
141
+ checkDomain(domain) {
142
+ if (sandbox.limits.allowedDomains[0] === '*') return true;
143
+ return sandbox.limits.allowedDomains.some(d => domain.endsWith(d));
144
+ },
145
+
146
+ // Resource tracking
147
+ trackNetworkCall() {
148
+ sandbox.usage.networkCalls++;
149
+ if (sandbox.usage.networkCalls > sandbox.limits.maxNetworkCalls) {
150
+ throw new Error('Network call limit exceeded');
151
+ }
152
+ },
153
+
154
+ trackDomOperation() {
155
+ sandbox.usage.domOperations++;
156
+ if (sandbox.usage.domOperations > sandbox.limits.maxDomOperations) {
157
+ throw new Error('DOM operation limit exceeded');
158
+ }
159
+ },
160
+
161
+ // Isolated store
162
+ set(key, value) { sandbox.store.set(key, value); },
163
+ get(key) { return sandbox.store.get(key); },
164
+
165
+ // Audit
166
+ log(action, details) {
167
+ sandbox.audit.push({ action, details, timestamp: Date.now() });
168
+ },
169
+
170
+ // Selector validation
171
+ checkSelector(selector) {
172
+ for (const blocked of sandbox.limits.blockedSelectors) {
173
+ if (selector.includes(blocked)) {
174
+ throw new Error(`Selector blocked by sandbox policy: ${blocked}`);
175
+ }
176
+ }
177
+ return true;
178
+ },
179
+
180
+ // Read sandbox time remaining
181
+ get timeRemaining() {
182
+ if (!sandbox.usage.startedAt) return sandbox.limits.timeout;
183
+ return Math.max(0, sandbox.limits.timeout - (Date.now() - sandbox.usage.startedAt));
184
+ },
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Get sandbox summary (safe to expose)
190
+ */
191
+ _getSandboxSummary(sandbox) {
192
+ return {
193
+ id: sandbox.id,
194
+ taskId: sandbox.taskId,
195
+ state: sandbox.state,
196
+ usage: { ...sandbox.usage },
197
+ auditCount: sandbox.audit.length,
198
+ duration: sandbox.usage.completedAt
199
+ ? sandbox.usage.completedAt - sandbox.usage.startedAt
200
+ : null,
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Destroy a sandbox and free resources
206
+ */
207
+ destroy(sandboxId) {
208
+ const sandbox = this._sandboxes.get(sandboxId);
209
+ if (sandbox) {
210
+ sandbox.store.clear();
211
+ this._sandboxes.delete(sandboxId);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Get audit trail for a sandbox
217
+ */
218
+ getAudit(sandboxId) {
219
+ const sandbox = this._sandboxes.get(sandboxId);
220
+ return sandbox ? [...sandbox.audit] : [];
221
+ }
222
+
223
+ /**
224
+ * List active sandboxes
225
+ */
226
+ listActive() {
227
+ const active = [];
228
+ for (const [, sb] of this._sandboxes) {
229
+ if (sb.state === 'created' || sb.state === 'running') {
230
+ active.push(this._getSandboxSummary(sb));
231
+ }
232
+ }
233
+ return active;
234
+ }
235
+
236
+ getStats() {
237
+ return { ...this._stats, active: this._sandboxes.size };
238
+ }
239
+
240
+ /**
241
+ * Cleanup completed/failed sandboxes older than maxAge
242
+ */
243
+ cleanup(maxAge = 3600_000) {
244
+ const cutoff = Date.now() - maxAge;
245
+ let cleaned = 0;
246
+ for (const [id, sb] of this._sandboxes) {
247
+ if (sb.state !== 'created' && sb.state !== 'running' && sb.createdAt < cutoff) {
248
+ sb.store.clear();
249
+ this._sandboxes.delete(id);
250
+ cleaned++;
251
+ }
252
+ }
253
+ return cleaned;
254
+ }
255
+ }
256
+
257
+ function _withTimeout(promise, ms) {
258
+ if (!ms || ms <= 0) return promise;
259
+ return new Promise((resolve, reject) => {
260
+ const timer = setTimeout(() => reject(new Error(`Sandbox execution timed out after ${ms}ms`)), ms);
261
+ promise.then(r => { clearTimeout(timer); resolve(r); })
262
+ .catch(e => { clearTimeout(timer); reject(e); });
263
+ });
264
+ }
265
+
266
+ module.exports = { ExecutionSandbox };