web-agent-bridge 2.3.0 → 2.4.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 (66) hide show
  1. package/README.ar.md +506 -31
  2. package/README.md +574 -47
  3. package/bin/agent-runner.js +10 -1
  4. package/package.json +12 -4
  5. package/public/agent-workspace.html +347 -0
  6. package/public/browser.html +484 -0
  7. package/public/commander-dashboard.html +243 -0
  8. package/public/css/agent-workspace.css +1713 -0
  9. package/public/css/premium.css +317 -317
  10. package/public/demo.html +259 -259
  11. package/public/index.html +738 -644
  12. package/public/js/agent-workspace.js +1740 -0
  13. package/public/mesh-dashboard.html +309 -382
  14. package/public/premium-dashboard.html +2487 -2487
  15. package/public/premium.html +791 -791
  16. package/public/script/wab.min.js +124 -87
  17. package/script/ai-agent-bridge.js +154 -84
  18. package/sdk/agent-mesh.js +287 -171
  19. package/sdk/commander.js +262 -0
  20. package/sdk/index.d.ts +83 -0
  21. package/sdk/index.js +374 -260
  22. package/sdk/package.json +1 -1
  23. package/server/config/secrets.js +13 -5
  24. package/server/index.js +191 -5
  25. package/server/middleware/adminAuth.js +6 -1
  26. package/server/middleware/auth.js +11 -2
  27. package/server/middleware/rateLimits.js +78 -2
  28. package/server/migrations/002_premium_features.sql +418 -418
  29. package/server/migrations/003_ads_integer_cents.sql +33 -0
  30. package/server/models/db.js +121 -1
  31. package/server/routes/admin-premium.js +671 -671
  32. package/server/routes/admin.js +16 -2
  33. package/server/routes/ads.js +130 -0
  34. package/server/routes/agent-workspace.js +378 -0
  35. package/server/routes/api.js +21 -2
  36. package/server/routes/auth.js +26 -6
  37. package/server/routes/commander.js +316 -0
  38. package/server/routes/mesh.js +370 -201
  39. package/server/routes/premium-v2.js +686 -686
  40. package/server/routes/premium.js +724 -724
  41. package/server/routes/sovereign.js +78 -0
  42. package/server/routes/universal.js +177 -0
  43. package/server/routes/wab-api.js +20 -5
  44. package/server/services/agent-chat.js +506 -0
  45. package/server/services/agent-learning.js +230 -77
  46. package/server/services/agent-memory.js +625 -625
  47. package/server/services/agent-mesh.js +260 -67
  48. package/server/services/agent-symphony.js +553 -517
  49. package/server/services/agent-tasks.js +1807 -0
  50. package/server/services/commander.js +738 -0
  51. package/server/services/edge-compute.js +440 -0
  52. package/server/services/fairness-engine.js +409 -0
  53. package/server/services/local-ai.js +389 -0
  54. package/server/services/plugins.js +771 -747
  55. package/server/services/price-intelligence.js +565 -0
  56. package/server/services/price-shield.js +1137 -0
  57. package/server/services/search-engine.js +357 -0
  58. package/server/services/security.js +513 -0
  59. package/server/services/self-healing.js +843 -843
  60. package/server/services/swarm.js +788 -788
  61. package/server/services/universal-scraper.js +661 -0
  62. package/server/services/vision.js +871 -871
  63. package/server/ws.js +61 -1
  64. package/public/admin/dashboard.html +0 -848
  65. package/public/admin/login.html +0 -84
  66. package/public/video/tutorial.mp4 +0 -0
package/server/index.js CHANGED
@@ -11,7 +11,10 @@ const rateLimit = require('express-rate-limit');
11
11
  const path = require('path');
12
12
  const { setupWebSocket } = require('./ws');
13
13
  const { runMigrations } = require('./utils/migrate');
14
- const { maybeBootstrapAdmin } = require('./models/db');
14
+ const { maybeBootstrapAdmin, db } = require('./models/db');
15
+ const { initSearchEngine, search, getSuggestions, getTrendingSearches, getSearchStats, purgeOldCache } = require('./services/search-engine');
16
+ const { processMessage: agentChat } = require('./services/agent-chat');
17
+ const agentTasks = require('./services/agent-tasks');
15
18
 
16
19
  const authRoutes = require('./routes/auth');
17
20
  const apiRoutes = require('./routes/api');
@@ -20,6 +23,15 @@ const adminRoutes = require('./routes/admin');
20
23
  const billingRoutes = require('./routes/billing');
21
24
  const sovereignRoutes = require('./routes/sovereign');
22
25
  const meshRoutes = require('./routes/mesh');
26
+ const commanderRoutes = require('./routes/commander');
27
+ const adsRoutes = require('./routes/ads');
28
+ const wabApiRoutes = require('./routes/wab-api');
29
+ const noscriptRoutes = require('./routes/noscript');
30
+ const discoveryRoutes = require('./routes/discovery');
31
+ const premiumRoutes = require('./routes/premium');
32
+ const adminPremiumRoutes = require('./routes/admin-premium');
33
+ const workspaceRoutes = require('./routes/agent-workspace');
34
+ const universalRoutes = require('./routes/universal');
23
35
  const { handleWebhookRequest } = require('./services/stripe');
24
36
 
25
37
  const app = express();
@@ -60,14 +72,16 @@ app.use(
60
72
  directives: {
61
73
  defaultSrc: ["'self'"],
62
74
  scriptSrc,
63
- styleSrc,
75
+ scriptSrcAttr: scriptSrc,
76
+ styleSrc: [...styleSrc, 'https://fonts.googleapis.com'],
64
77
  imgSrc: ["'self'", 'data:', 'https:'],
65
78
  connectSrc: ["'self'", 'ws:', 'wss:'],
66
- fontSrc: ["'self'", 'https:', 'data:'],
67
- frameSrc: ["'none'"],
79
+ fontSrc: ["'self'", 'https://fonts.gstatic.com', 'https:', 'data:'],
80
+ frameSrc: ["'self'", 'https:', 'http:'],
68
81
  frameAncestors: ["'none'"],
69
82
  objectSrc: ["'none'"],
70
- baseUri: ["'self'"]
83
+ baseUri: ["'self'"],
84
+ formAction: ["'self'"]
71
85
  }
72
86
  },
73
87
  crossOriginEmbedderPolicy: false
@@ -115,6 +129,52 @@ app.use('/api/admin', apiLimiter, adminRoutes);
115
129
  app.use('/api/billing', apiLimiter, billingRoutes);
116
130
  app.use('/api/sovereign', apiLimiter, sovereignRoutes);
117
131
  app.use('/api/mesh', apiLimiter, meshRoutes);
132
+ app.use('/api/commander', apiLimiter, commanderRoutes);
133
+ app.use('/api/ads', apiLimiter, adsRoutes);
134
+ app.use('/api/wab', wabApiRoutes);
135
+ app.use('/api/noscript', apiLimiter, noscriptRoutes);
136
+ app.use('/api/discovery', apiLimiter, discoveryRoutes);
137
+ app.use('/api/premium', apiLimiter, premiumRoutes);
138
+ app.use('/api/admin/premium', apiLimiter, adminPremiumRoutes);
139
+ app.use('/api/workspace', apiLimiter, workspaceRoutes);
140
+ app.use('/api/universal', apiLimiter, universalRoutes);
141
+
142
+ // ─── WAB Search Engine ────────────────────────────────────────────────
143
+
144
+ const searchLimiter = rateLimit({
145
+ windowMs: 60 * 1000,
146
+ max: 30,
147
+ standardHeaders: true,
148
+ legacyHeaders: false,
149
+ message: { error: 'Too many search requests, please slow down' }
150
+ });
151
+
152
+ app.get('/api/search', searchLimiter, async (req, res) => {
153
+ const q = (req.query.q || '').trim();
154
+ if (!q) return res.json({ results: [], cached: false });
155
+ if (q.length > 200) return res.status(400).json({ error: 'Query too long' });
156
+ const crypto = require('crypto');
157
+ const ipHash = crypto.createHash('sha256').update(req.ip || '').digest('hex').slice(0, 16);
158
+ const result = await search(q, ipHash);
159
+ res.json(result);
160
+ });
161
+
162
+ app.get('/api/search/suggest', searchLimiter, (req, res) => {
163
+ const q = (req.query.q || '').trim();
164
+ if (!q) return res.json({ suggestions: [] });
165
+ const suggestions = getSuggestions(q, 8);
166
+ res.json({ suggestions });
167
+ });
168
+
169
+ app.get('/api/search/trending', apiLimiter, (req, res) => {
170
+ const trending = getTrendingSearches(10);
171
+ res.json({ trending });
172
+ });
173
+
174
+ app.get('/api/search/stats', apiLimiter, (req, res) => {
175
+ const stats = getSearchStats();
176
+ res.json(stats);
177
+ });
118
178
 
119
179
  app.get('/dashboard', (req, res) => {
120
180
  res.sendFile(path.join(__dirname, '..', 'public', 'dashboard.html'));
@@ -122,6 +182,9 @@ app.get('/dashboard', (req, res) => {
122
182
  app.get('/mesh-dashboard', (req, res) => {
123
183
  res.sendFile(path.join(__dirname, '..', 'public', 'mesh-dashboard.html'));
124
184
  });
185
+ app.get('/commander-dashboard', (req, res) => {
186
+ res.sendFile(path.join(__dirname, '..', 'public', 'commander-dashboard.html'));
187
+ });
125
188
  app.get('/docs', (req, res) => {
126
189
  res.sendFile(path.join(__dirname, '..', 'public', 'docs.html'));
127
190
  });
@@ -146,6 +209,125 @@ app.get('/terms', (req, res) => {
146
209
  app.get('/cookies', (req, res) => {
147
210
  res.sendFile(path.join(__dirname, '..', 'public', 'cookies.html'));
148
211
  });
212
+ app.get('/browser', (req, res) => {
213
+ res.sendFile(path.join(__dirname, '..', 'public', 'browser.html'));
214
+ });
215
+ app.get('/workspace', (req, res) => {
216
+ res.sendFile(path.join(__dirname, '..', 'public', 'agent-workspace.html'));
217
+ });
218
+
219
+ // Browser downloads
220
+ app.use('/downloads', express.static(path.join(__dirname, '..', 'downloads'), {
221
+ maxAge: '1d',
222
+ setHeaders: (res, filePath) => {
223
+ res.set('Content-Disposition', 'attachment');
224
+ }
225
+ }));
226
+
227
+ // Agent chat endpoint for WAB Browser — Real AI Agent
228
+ const chatLimiter = rateLimit({
229
+ windowMs: 60 * 1000,
230
+ max: 20,
231
+ standardHeaders: true,
232
+ legacyHeaders: false,
233
+ message: { error: 'Too many messages, please slow down' }
234
+ });
235
+
236
+ app.post('/api/wab/agent-chat', chatLimiter, async (req, res) => {
237
+ const { message, context, sessionId, taskId, taskAction } = req.body || {};
238
+ if (!message || typeof message !== 'string') {
239
+ return res.status(400).json({ error: 'Message required' });
240
+ }
241
+ if (message.length > 3000) {
242
+ return res.status(400).json({ error: 'Message too long' });
243
+ }
244
+
245
+ const sid = sessionId || req.ip || 'anonymous';
246
+
247
+ try {
248
+ // ── Task actions (user responding to an active task) ──
249
+ if (taskId && taskAction) {
250
+ if (taskAction === 'answer') {
251
+ const result = agentTasks.answerClarification(taskId, message);
252
+ if (result.status === 'planning') {
253
+ // Auto-execute after planning
254
+ const execResult = await agentTasks.executeTask(taskId);
255
+ return res.json({ ...execResult, type: 'task' });
256
+ }
257
+ return res.json({ ...result, type: 'task' });
258
+ }
259
+ if (taskAction === 'select') {
260
+ const idx = parseInt(message.replace(/\D/g, '')) - 1;
261
+ const result = agentTasks.selectOffer(taskId, idx);
262
+ return res.json({ ...result, type: 'task' });
263
+ }
264
+ if (taskAction === 'cancel') {
265
+ const result = agentTasks.cancelTask(taskId);
266
+ return res.json({ ...result, type: 'task' });
267
+ }
268
+ }
269
+
270
+ // ── Check if user wants to select from existing offers ──
271
+ if (!taskId) {
272
+ const selectMatch = message.match(/(?:اختر|اخت(?:ا|ي)ر|select|choose|pick)\s*(\d+)/i);
273
+ if (selectMatch) {
274
+ const tasks = agentTasks.getSessionTasks(sid, 1);
275
+ if (tasks.length > 0 && tasks[0].status === 'presenting') {
276
+ const idx = parseInt(selectMatch[1]) - 1;
277
+ const result = agentTasks.selectOffer(tasks[0].id, idx);
278
+ return res.json({ ...result, type: 'task' });
279
+ }
280
+ }
281
+ }
282
+
283
+ // ── Detect URL paste — create URL negotiation task ──
284
+ const urlData = agentTasks.parseBookingUrl(message);
285
+ if (urlData) {
286
+ const task = agentTasks.createUrlTask(sid, message, urlData);
287
+ const execResult = await agentTasks.executeUrlTask(task.taskId);
288
+ return res.json({ ...execResult, type: 'task', urlData });
289
+ }
290
+
291
+ // ── Detect if this is a task-type request (booking, shopping, etc.) ──
292
+ const intent = agentTasks.detectIntent(message);
293
+ if (intent.confidence >= 0.7 && intent.intent !== 'general') {
294
+ const task = agentTasks.createTask(sid, message);
295
+
296
+ if (task.status === 'clarifying') {
297
+ return res.json({ ...task, type: 'task' });
298
+ }
299
+
300
+ // If requirements are complete, auto-execute
301
+ const execResult = await agentTasks.executeTask(task.taskId);
302
+ return res.json({ ...execResult, type: 'task' });
303
+ }
304
+
305
+ // ── Regular chat (not a task) ──
306
+ const chatContext = {
307
+ url: context?.url || '',
308
+ platform: context?.platform || 'unknown',
309
+ sessionId: sid,
310
+ };
311
+ const result = await agentChat(message, chatContext);
312
+ res.json(result);
313
+ } catch (err) {
314
+ console.error('[agent-chat] Error:', err.message);
315
+ res.json({ reply: '🤖 عذراً، حدث خطأ. حاول مرة أخرى.', type: 'text' });
316
+ }
317
+ });
318
+
319
+ // Agent task status & history
320
+ app.get('/api/wab/agent-task/:id', chatLimiter, (req, res) => {
321
+ const state = agentTasks.getTaskState(req.params.id);
322
+ if (!state) return res.status(404).json({ error: 'Task not found' });
323
+ res.json(state);
324
+ });
325
+
326
+ app.get('/api/wab/agent-tasks', chatLimiter, (req, res) => {
327
+ const sid = req.query.sessionId || req.ip || 'anonymous';
328
+ const tasks = agentTasks.getSessionTasks(sid, 20);
329
+ res.json({ tasks });
330
+ });
149
331
 
150
332
  const pkg = require('../package.json');
151
333
  app.use(`/v${pkg.version.split('.')[0]}`, express.static(path.join(__dirname, '..', 'script')));
@@ -163,6 +345,10 @@ if (process.env.NODE_ENV !== 'test') {
163
345
  console.log('Running database migrations...');
164
346
  runMigrations();
165
347
  maybeBootstrapAdmin();
348
+ initSearchEngine(db);
349
+
350
+ // Purge old search cache every hour
351
+ setInterval(purgeOldCache, 60 * 60 * 1000);
166
352
 
167
353
  const server = http.createServer(app);
168
354
  setupWebSocket(server);
@@ -1,9 +1,10 @@
1
1
  const { signAdminToken, verifyAdminToken } = require('../config/secrets');
2
+ const { isJWTRevoked } = require('../services/security');
2
3
 
3
4
  function generateAdminToken(admin) {
4
5
  return signAdminToken(
5
6
  { id: admin.id, email: admin.email, name: admin.name, role: admin.role, isAdmin: true },
6
- { expiresIn: '12h' }
7
+ { expiresIn: '4h' }
7
8
  );
8
9
  }
9
10
 
@@ -16,11 +17,15 @@ function authenticateAdmin(req, res, next) {
16
17
  }
17
18
 
18
19
  try {
20
+ if (isJWTRevoked(token)) {
21
+ return res.status(403).json({ error: 'Token has been revoked' });
22
+ }
19
23
  const decoded = verifyAdminToken(token);
20
24
  if (!decoded.isAdmin) {
21
25
  return res.status(403).json({ error: 'Admin privileges required' });
22
26
  }
23
27
  req.admin = decoded;
28
+ req._rawToken = token;
24
29
  next();
25
30
  } catch (err) {
26
31
  return res.status(403).json({ error: 'Invalid or expired admin token' });
@@ -1,9 +1,10 @@
1
1
  const { signUserToken, verifyUserToken } = require('../config/secrets');
2
+ const { isJWTRevoked } = require('../services/security');
2
3
 
3
4
  function generateToken(user) {
4
5
  return signUserToken(
5
6
  { id: user.id, email: user.email, name: user.name },
6
- { expiresIn: '7d' }
7
+ { expiresIn: '24h' }
7
8
  );
8
9
  }
9
10
 
@@ -16,8 +17,13 @@ function authenticateToken(req, res, next) {
16
17
  }
17
18
 
18
19
  try {
20
+ // Check revocation list
21
+ if (isJWTRevoked(token)) {
22
+ return res.status(403).json({ error: 'Token has been revoked' });
23
+ }
19
24
  const decoded = verifyUserToken(token);
20
25
  req.user = decoded;
26
+ req._rawToken = token;
21
27
  next();
22
28
  } catch (err) {
23
29
  return res.status(403).json({ error: 'Invalid or expired token' });
@@ -30,7 +36,10 @@ function optionalAuth(req, res, next) {
30
36
 
31
37
  if (token) {
32
38
  try {
33
- req.user = verifyUserToken(token);
39
+ if (!isJWTRevoked(token)) {
40
+ req.user = verifyUserToken(token);
41
+ req._rawToken = token;
42
+ }
34
43
  } catch (e) {
35
44
  // ignore invalid tokens for optional auth
36
45
  }
@@ -1,9 +1,75 @@
1
1
  /**
2
- * Stricter rate limits for license token / track endpoints (used inside license router).
2
+ * Comprehensive rate limits for all security-sensitive endpoints.
3
3
  */
4
4
 
5
5
  const rateLimit = require('express-rate-limit');
6
6
 
7
+ // ─── Auth endpoints ──────────────────────────────────────────────────
8
+
9
+ const authLimiter = rateLimit({
10
+ windowMs: 15 * 60 * 1000,
11
+ max: 10,
12
+ standardHeaders: true,
13
+ legacyHeaders: false,
14
+ message: { error: 'Too many authentication attempts, please try again later' }
15
+ });
16
+
17
+ const registerLimiter = rateLimit({
18
+ windowMs: 60 * 60 * 1000,
19
+ max: 5,
20
+ standardHeaders: true,
21
+ legacyHeaders: false,
22
+ message: { error: 'Too many registration attempts, please try again later' }
23
+ });
24
+
25
+ const adminLoginLimiter = rateLimit({
26
+ windowMs: 15 * 60 * 1000,
27
+ max: 5,
28
+ standardHeaders: true,
29
+ legacyHeaders: false,
30
+ message: { error: 'Too many admin login attempts, please try again later' }
31
+ });
32
+
33
+ // ─── WAB API endpoints ───────────────────────────────────────────────
34
+
35
+ const wabAuthenticateLimiter = rateLimit({
36
+ windowMs: 15 * 60 * 1000,
37
+ max: 20,
38
+ standardHeaders: true,
39
+ legacyHeaders: false,
40
+ keyGenerator: (req) => `${req.ip}:${req.body?.siteId || req.body?.apiKey || 'anon'}`,
41
+ message: { error: 'Too many WAB authentication attempts' }
42
+ });
43
+
44
+ const wabActionLimiter = rateLimit({
45
+ windowMs: 60 * 1000,
46
+ max: 60,
47
+ standardHeaders: true,
48
+ legacyHeaders: false,
49
+ keyGenerator: (req) => `${req.ip}:${req.wabSession?.siteId || 'anon'}`,
50
+ message: { error: 'Too many action requests, please slow down' }
51
+ });
52
+
53
+ // ─── General API endpoints ───────────────────────────────────────────
54
+
55
+ const apiLimiter = rateLimit({
56
+ windowMs: 60 * 1000,
57
+ max: 100,
58
+ standardHeaders: true,
59
+ legacyHeaders: false,
60
+ message: { error: 'Too many requests, please try again later' }
61
+ });
62
+
63
+ const searchLimiter = rateLimit({
64
+ windowMs: 60 * 1000,
65
+ max: 30,
66
+ standardHeaders: true,
67
+ legacyHeaders: false,
68
+ message: { error: 'Too many search requests' }
69
+ });
70
+
71
+ // ─── License endpoints (existing) ────────────────────────────────────
72
+
7
73
  const licenseTokenLimiter = rateLimit({
8
74
  windowMs: 15 * 60 * 1000,
9
75
  max: 30,
@@ -21,4 +87,14 @@ const licenseTrackLimiter = rateLimit({
21
87
  message: { error: 'Too many track requests, please try again later' }
22
88
  });
23
89
 
24
- module.exports = { licenseTokenLimiter, licenseTrackLimiter };
90
+ module.exports = {
91
+ authLimiter,
92
+ registerLimiter,
93
+ adminLoginLimiter,
94
+ wabAuthenticateLimiter,
95
+ wabActionLimiter,
96
+ apiLimiter,
97
+ searchLimiter,
98
+ licenseTokenLimiter,
99
+ licenseTrackLimiter,
100
+ };