web-agent-bridge 2.1.0 → 2.3.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.ar.md +8 -0
- package/README.md +8 -0
- package/examples/cross-site-agent.js +91 -0
- package/package.json +1 -1
- package/public/index.html +64 -1
- package/public/llms.txt +1 -0
- package/public/mesh-dashboard.html +401 -0
- package/public/script/wab.min.js +139 -1
- package/public/sovereign.html +1 -1
- package/script/ai-agent-bridge.js +127 -1
- package/sdk/README.md +44 -0
- package/sdk/agent-mesh.js +333 -0
- package/sdk/index.d.ts +93 -0
- package/sdk/index.js +4 -1
- package/sdk/multi-agent.js +318 -0
- package/sdk/package.json +1 -1
- package/server/index.js +5 -0
- package/server/routes/mesh.js +300 -0
- package/server/services/agent-learning.js +422 -0
- package/server/services/agent-mesh.js +346 -0
- package/server/services/agent-symphony.js +681 -0
package/public/script/wab.min.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
(function(global) {
|
|
27
27
|
'use strict';
|
|
28
28
|
|
|
29
|
-
var VERSION = '2.
|
|
29
|
+
var VERSION = '2.2.0';
|
|
30
30
|
|
|
31
31
|
// ── WAB Instance ──────────────────────────────────────────────────
|
|
32
32
|
function WABInstance(config) {
|
|
@@ -356,6 +356,99 @@
|
|
|
356
356
|
}).then(function(r) { return r.json(); });
|
|
357
357
|
};
|
|
358
358
|
|
|
359
|
+
// ── Agent Mesh Protocol ───────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
WABInstance.prototype.meshJoin = function(role, displayName, capabilities) {
|
|
362
|
+
if (!this.serverUrl) return Promise.resolve({ error: 'Mesh requires a server URL' });
|
|
363
|
+
var self = this;
|
|
364
|
+
return fetch(this.serverUrl + '/api/mesh/agents', {
|
|
365
|
+
method: 'POST',
|
|
366
|
+
headers: { 'Content-Type': 'application/json' },
|
|
367
|
+
body: JSON.stringify({ siteId: this.name, role: role, displayName: displayName, capabilities: capabilities })
|
|
368
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
369
|
+
self._meshAgentId = data.id;
|
|
370
|
+
self._emit('mesh:joined', data);
|
|
371
|
+
return data;
|
|
372
|
+
});
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
WABInstance.prototype.meshPublish = function(channel, messageType, subject, payload, opts) {
|
|
376
|
+
if (!this.serverUrl || !this._meshAgentId) return Promise.resolve({ error: 'Must join mesh first' });
|
|
377
|
+
return fetch(this.serverUrl + '/api/mesh/channels/' + encodeURIComponent(channel) + '/messages', {
|
|
378
|
+
method: 'POST',
|
|
379
|
+
headers: { 'Content-Type': 'application/json' },
|
|
380
|
+
body: JSON.stringify(Object.assign({ senderId: this._meshAgentId, messageType: messageType, subject: subject, payload: payload }, opts || {}))
|
|
381
|
+
}).then(function(r) { return r.json(); });
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
WABInstance.prototype.meshReceive = function(channel, limit) {
|
|
385
|
+
if (!this.serverUrl || !this._meshAgentId) return Promise.resolve({ error: 'Must join mesh first' });
|
|
386
|
+
return fetch(this.serverUrl + '/api/mesh/agents/' + this._meshAgentId + '/messages/' + encodeURIComponent(channel) + '?limit=' + (limit || 20))
|
|
387
|
+
.then(function(r) { return r.json(); });
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
WABInstance.prototype.meshShareKnowledge = function(knowledgeType, domain, key, value, confidence) {
|
|
391
|
+
if (!this.serverUrl || !this._meshAgentId) return Promise.resolve({ error: 'Must join mesh first' });
|
|
392
|
+
return fetch(this.serverUrl + '/api/mesh/knowledge', {
|
|
393
|
+
method: 'POST',
|
|
394
|
+
headers: { 'Content-Type': 'application/json' },
|
|
395
|
+
body: JSON.stringify({ agentId: this._meshAgentId, knowledgeType: knowledgeType, domain: domain, key: key, value: value, confidence: confidence })
|
|
396
|
+
}).then(function(r) { return r.json(); });
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
WABInstance.prototype.meshAlert = function(subject, details, priority) {
|
|
400
|
+
if (!this.serverUrl || !this._meshAgentId) return Promise.resolve({ error: 'Must join mesh first' });
|
|
401
|
+
return fetch(this.serverUrl + '/api/mesh/alerts', {
|
|
402
|
+
method: 'POST',
|
|
403
|
+
headers: { 'Content-Type': 'application/json' },
|
|
404
|
+
body: JSON.stringify({ senderId: this._meshAgentId, subject: subject, details: details, priority: priority })
|
|
405
|
+
}).then(function(r) { return r.json(); });
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// ── Agent Symphony Orchestrator ───────────────────────────────────
|
|
409
|
+
|
|
410
|
+
WABInstance.prototype.symphonyPerform = function(task, taskType, inputData) {
|
|
411
|
+
if (!this.serverUrl) return Promise.resolve({ error: 'Symphony requires a server URL' });
|
|
412
|
+
var self = this;
|
|
413
|
+
return fetch(this.serverUrl + '/api/mesh/symphony/perform', {
|
|
414
|
+
method: 'POST',
|
|
415
|
+
headers: { 'Content-Type': 'application/json' },
|
|
416
|
+
body: JSON.stringify({ siteId: this.name, task: task, taskType: taskType, inputData: inputData })
|
|
417
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
418
|
+
self._emit('symphony:completed', data);
|
|
419
|
+
return data;
|
|
420
|
+
});
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// ── Agent Learning Engine ─────────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
WABInstance.prototype.learnRecord = function(domain, action, context, features) {
|
|
426
|
+
if (!this.serverUrl || !this._meshAgentId) return Promise.resolve({ error: 'Must join mesh first' });
|
|
427
|
+
return fetch(this.serverUrl + '/api/mesh/learning/decisions', {
|
|
428
|
+
method: 'POST',
|
|
429
|
+
headers: { 'Content-Type': 'application/json' },
|
|
430
|
+
body: JSON.stringify({ siteId: this.name, agentId: this._meshAgentId, domain: domain, action: action, context: context, features: features })
|
|
431
|
+
}).then(function(r) { return r.json(); });
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
WABInstance.prototype.learnFeedback = function(decisionId, outcome, reward) {
|
|
435
|
+
if (!this.serverUrl) return Promise.resolve({ error: 'Learning requires a server URL' });
|
|
436
|
+
return fetch(this.serverUrl + '/api/mesh/learning/decisions/' + decisionId + '/feedback', {
|
|
437
|
+
method: 'POST',
|
|
438
|
+
headers: { 'Content-Type': 'application/json' },
|
|
439
|
+
body: JSON.stringify({ outcome: outcome, reward: reward })
|
|
440
|
+
}).then(function(r) { return r.json(); });
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
WABInstance.prototype.learnRecommend = function(domain, actions, context) {
|
|
444
|
+
if (!this.serverUrl || !this._meshAgentId) return Promise.resolve({ error: 'Must join mesh first' });
|
|
445
|
+
return fetch(this.serverUrl + '/api/mesh/learning/recommend', {
|
|
446
|
+
method: 'POST',
|
|
447
|
+
headers: { 'Content-Type': 'application/json' },
|
|
448
|
+
body: JSON.stringify({ siteId: this.name, agentId: this._meshAgentId, domain: domain, actions: actions, context: context })
|
|
449
|
+
}).then(function(r) { return r.json(); });
|
|
450
|
+
};
|
|
451
|
+
|
|
359
452
|
// ── Static API ────────────────────────────────────────────────────
|
|
360
453
|
|
|
361
454
|
var WAB = {
|
|
@@ -397,6 +490,51 @@
|
|
|
397
490
|
return Promise.resolve({ error: 'WAB not initialized' });
|
|
398
491
|
},
|
|
399
492
|
|
|
493
|
+
meshJoin: function(role, displayName, capabilities) {
|
|
494
|
+
if (WAB._instance) return WAB._instance.meshJoin(role, displayName, capabilities);
|
|
495
|
+
return Promise.resolve({ error: 'WAB not initialized' });
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
meshPublish: function(channel, messageType, subject, payload, opts) {
|
|
499
|
+
if (WAB._instance) return WAB._instance.meshPublish(channel, messageType, subject, payload, opts);
|
|
500
|
+
return Promise.resolve({ error: 'WAB not initialized' });
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
meshReceive: function(channel, limit) {
|
|
504
|
+
if (WAB._instance) return WAB._instance.meshReceive(channel, limit);
|
|
505
|
+
return Promise.resolve({ error: 'WAB not initialized' });
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
meshShareKnowledge: function(knowledgeType, domain, key, value, confidence) {
|
|
509
|
+
if (WAB._instance) return WAB._instance.meshShareKnowledge(knowledgeType, domain, key, value, confidence);
|
|
510
|
+
return Promise.resolve({ error: 'WAB not initialized' });
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
meshAlert: function(subject, details, priority) {
|
|
514
|
+
if (WAB._instance) return WAB._instance.meshAlert(subject, details, priority);
|
|
515
|
+
return Promise.resolve({ error: 'WAB not initialized' });
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
symphonyPerform: function(task, taskType, inputData) {
|
|
519
|
+
if (WAB._instance) return WAB._instance.symphonyPerform(task, taskType, inputData);
|
|
520
|
+
return Promise.resolve({ error: 'WAB not initialized' });
|
|
521
|
+
},
|
|
522
|
+
|
|
523
|
+
learnRecord: function(domain, action, context, features) {
|
|
524
|
+
if (WAB._instance) return WAB._instance.learnRecord(domain, action, context, features);
|
|
525
|
+
return Promise.resolve({ error: 'WAB not initialized' });
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
learnFeedback: function(decisionId, outcome, reward) {
|
|
529
|
+
if (WAB._instance) return WAB._instance.learnFeedback(decisionId, outcome, reward);
|
|
530
|
+
return Promise.resolve({ error: 'WAB not initialized' });
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
learnRecommend: function(domain, actions, context) {
|
|
534
|
+
if (WAB._instance) return WAB._instance.learnRecommend(domain, actions, context);
|
|
535
|
+
return Promise.resolve({ error: 'WAB not initialized' });
|
|
536
|
+
},
|
|
537
|
+
|
|
400
538
|
_instance: null
|
|
401
539
|
};
|
|
402
540
|
|
package/public/sovereign.html
CHANGED
|
@@ -445,7 +445,7 @@
|
|
|
445
445
|
</div>
|
|
446
446
|
|
|
447
447
|
<footer class="footer">
|
|
448
|
-
Web Agent Bridge v2.
|
|
448
|
+
Web Agent Bridge v2.2.0 — Digital Fortress for Internet Sovereignty<br>
|
|
449
449
|
<span style="color:var(--accent-blue);">Digital fairness starts here</span>
|
|
450
450
|
</footer>
|
|
451
451
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
(function (global) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
-
const VERSION = '2.
|
|
10
|
+
const VERSION = '2.2.0';
|
|
11
11
|
const LICENSING_SERVER = 'https://api.webagentbridge.com';
|
|
12
12
|
|
|
13
13
|
// ─── Default Configuration ────────────────────────────────────────────
|
|
@@ -1426,6 +1426,132 @@
|
|
|
1426
1426
|
this.logger.log('refresh', {});
|
|
1427
1427
|
}
|
|
1428
1428
|
|
|
1429
|
+
// ── Agent Mesh Protocol (Client-Side) ───────────────────────────────
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* Join the agent mesh — register this agent and return its mesh identity.
|
|
1433
|
+
*/
|
|
1434
|
+
async meshJoin(role, displayName, capabilities) {
|
|
1435
|
+
const base = this._resolveApiBase();
|
|
1436
|
+
const res = await fetch(`${base}/api/mesh/agents`, {
|
|
1437
|
+
method: 'POST',
|
|
1438
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1439
|
+
body: JSON.stringify({ siteId: this.config.siteId, role, displayName, capabilities })
|
|
1440
|
+
});
|
|
1441
|
+
const data = await res.json();
|
|
1442
|
+
this._meshAgentId = data.id;
|
|
1443
|
+
this.events.emit('mesh:joined', data);
|
|
1444
|
+
return data;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* Publish a message to a mesh channel.
|
|
1449
|
+
*/
|
|
1450
|
+
async meshPublish(channel, messageType, subject, payload, opts) {
|
|
1451
|
+
if (!this._meshAgentId) throw new Error('Must call meshJoin() first');
|
|
1452
|
+
const base = this._resolveApiBase();
|
|
1453
|
+
const res = await fetch(`${base}/api/mesh/channels/${encodeURIComponent(channel)}/messages`, {
|
|
1454
|
+
method: 'POST',
|
|
1455
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1456
|
+
body: JSON.stringify({ senderId: this._meshAgentId, messageType, subject, payload, ...opts })
|
|
1457
|
+
});
|
|
1458
|
+
return res.json();
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/**
|
|
1462
|
+
* Get unread mesh messages for this agent on a channel.
|
|
1463
|
+
*/
|
|
1464
|
+
async meshReceive(channel, limit) {
|
|
1465
|
+
if (!this._meshAgentId) throw new Error('Must call meshJoin() first');
|
|
1466
|
+
const base = this._resolveApiBase();
|
|
1467
|
+
const res = await fetch(`${base}/api/mesh/agents/${this._meshAgentId}/messages/${encodeURIComponent(channel)}?limit=${limit || 20}`);
|
|
1468
|
+
return res.json();
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* Share knowledge to the mesh.
|
|
1473
|
+
*/
|
|
1474
|
+
async meshShareKnowledge(knowledgeType, domain, key, value, confidence) {
|
|
1475
|
+
if (!this._meshAgentId) throw new Error('Must call meshJoin() first');
|
|
1476
|
+
const base = this._resolveApiBase();
|
|
1477
|
+
const res = await fetch(`${base}/api/mesh/knowledge`, {
|
|
1478
|
+
method: 'POST',
|
|
1479
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1480
|
+
body: JSON.stringify({ agentId: this._meshAgentId, knowledgeType, domain, key, value, confidence })
|
|
1481
|
+
});
|
|
1482
|
+
return res.json();
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Broadcast an alert to all mesh agents.
|
|
1487
|
+
*/
|
|
1488
|
+
async meshAlert(subject, details, priority) {
|
|
1489
|
+
if (!this._meshAgentId) throw new Error('Must call meshJoin() first');
|
|
1490
|
+
const base = this._resolveApiBase();
|
|
1491
|
+
const res = await fetch(`${base}/api/mesh/alerts`, {
|
|
1492
|
+
method: 'POST',
|
|
1493
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1494
|
+
body: JSON.stringify({ senderId: this._meshAgentId, subject, details, priority })
|
|
1495
|
+
});
|
|
1496
|
+
return res.json();
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* Run a full symphony — orchestrate specialized agents for a task.
|
|
1501
|
+
*/
|
|
1502
|
+
async symphonyPerform(task, taskType, inputData) {
|
|
1503
|
+
const base = this._resolveApiBase();
|
|
1504
|
+
const res = await fetch(`${base}/api/mesh/symphony/perform`, {
|
|
1505
|
+
method: 'POST',
|
|
1506
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1507
|
+
body: JSON.stringify({ siteId: this.config.siteId, task, taskType, inputData })
|
|
1508
|
+
});
|
|
1509
|
+
const data = await res.json();
|
|
1510
|
+
this.events.emit('symphony:completed', data);
|
|
1511
|
+
return data;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Record a decision for the learning engine and get prediction.
|
|
1516
|
+
*/
|
|
1517
|
+
async learnRecord(domain, action, context, features) {
|
|
1518
|
+
if (!this._meshAgentId) throw new Error('Must call meshJoin() first');
|
|
1519
|
+
const base = this._resolveApiBase();
|
|
1520
|
+
const res = await fetch(`${base}/api/mesh/learning/decisions`, {
|
|
1521
|
+
method: 'POST',
|
|
1522
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1523
|
+
body: JSON.stringify({ siteId: this.config.siteId, agentId: this._meshAgentId, domain, action, context, features })
|
|
1524
|
+
});
|
|
1525
|
+
return res.json();
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Provide feedback on a decision — the learning signal.
|
|
1530
|
+
*/
|
|
1531
|
+
async learnFeedback(decisionId, outcome, reward) {
|
|
1532
|
+
const base = this._resolveApiBase();
|
|
1533
|
+
const res = await fetch(`${base}/api/mesh/learning/decisions/${decisionId}/feedback`, {
|
|
1534
|
+
method: 'POST',
|
|
1535
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1536
|
+
body: JSON.stringify({ outcome, reward })
|
|
1537
|
+
});
|
|
1538
|
+
return res.json();
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
/**
|
|
1542
|
+
* Get recommendation from learning engine.
|
|
1543
|
+
*/
|
|
1544
|
+
async learnRecommend(domain, actions, context) {
|
|
1545
|
+
if (!this._meshAgentId) throw new Error('Must call meshJoin() first');
|
|
1546
|
+
const base = this._resolveApiBase();
|
|
1547
|
+
const res = await fetch(`${base}/api/mesh/learning/recommend`, {
|
|
1548
|
+
method: 'POST',
|
|
1549
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1550
|
+
body: JSON.stringify({ siteId: this.config.siteId, agentId: this._meshAgentId, domain, actions, context })
|
|
1551
|
+
});
|
|
1552
|
+
return res.json();
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1429
1555
|
destroy() {
|
|
1430
1556
|
this.events.emit('destroy');
|
|
1431
1557
|
if (this._mutationObserver) {
|
package/sdk/README.md
CHANGED
|
@@ -52,4 +52,48 @@ await agent.execute('click-login');
|
|
|
52
52
|
| `authenticate(apiKey, meta?)` | Authenticate the agent |
|
|
53
53
|
| `navigateAndWait(url)` | Navigate and wait for bridge |
|
|
54
54
|
| `executeSteps(steps)` | Execute multiple actions in sequence |
|
|
55
|
+
| `executeParallel(actions)` | Execute multiple actions in parallel |
|
|
55
56
|
| `getBiDiContext()` | Get BiDi context (BiDi mode only) |
|
|
57
|
+
|
|
58
|
+
## Cross-Site Agent Orchestration
|
|
59
|
+
|
|
60
|
+
Manage multiple WAB-enabled sites simultaneously with `WABMultiAgent`:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const { WABMultiAgent } = require('web-agent-bridge-sdk');
|
|
64
|
+
|
|
65
|
+
const multiAgent = new WABMultiAgent([
|
|
66
|
+
'https://site1.com',
|
|
67
|
+
'https://site2.com',
|
|
68
|
+
'https://site3.com'
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
await multiAgent.launch();
|
|
72
|
+
|
|
73
|
+
// Compare prices across all sites
|
|
74
|
+
const comparison = await multiAgent.comparePrices('product-sku');
|
|
75
|
+
console.log(comparison.cheapest); // { site, price, currency }
|
|
76
|
+
console.log(`You save: $${comparison.savings}`);
|
|
77
|
+
|
|
78
|
+
// Execute any action on all sites in parallel
|
|
79
|
+
const infos = await multiAgent.executeAll('getPageInfo');
|
|
80
|
+
|
|
81
|
+
// Discover capabilities across all sites
|
|
82
|
+
const discoveries = await multiAgent.discoverAll();
|
|
83
|
+
|
|
84
|
+
await multiAgent.close();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### WABMultiAgent API
|
|
88
|
+
|
|
89
|
+
| Method | Description |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `launch()` | Connect to all sites |
|
|
92
|
+
| `discoverAll()` | Discover actions on all sites |
|
|
93
|
+
| `executeAll(action, params?)` | Run action on all sites in parallel |
|
|
94
|
+
| `comparePrices(sku)` | Compare prices and find cheapest deal |
|
|
95
|
+
| `compareAction(action, params?, rankFn?)` | Compare action results with custom ranking |
|
|
96
|
+
| `navigateAll(path)` | Navigate all sessions to a path |
|
|
97
|
+
| `screenshotAll(opts?)` | Screenshot all sites |
|
|
98
|
+
| `status()` | Get connection summary |
|
|
99
|
+
| `close()` | Close all browser sessions |
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WAB Agent Mesh SDK
|
|
3
|
+
*
|
|
4
|
+
* Provides a high-level client for interacting with the Private Agent Mesh:
|
|
5
|
+
* - Join the mesh and communicate with other agents
|
|
6
|
+
* - Share and query collective knowledge
|
|
7
|
+
* - Run symphony orchestrations (Researcher → Analyst → Negotiator → Guardian)
|
|
8
|
+
* - Learn from user decisions via local reinforcement learning
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const { WABAgentMesh } = require('web-agent-bridge-sdk/agent-mesh');
|
|
12
|
+
* const mesh = new WABAgentMesh('https://your-wab-server.com');
|
|
13
|
+
*
|
|
14
|
+
* const agent = await mesh.join('researcher', 'PriceBot');
|
|
15
|
+
* await mesh.shareKnowledge('price', 'amazon.com', 'iphone-15', { price: 999, currency: 'USD' });
|
|
16
|
+
* const result = await mesh.symphony('Find the best deal on iPhone 15', 'purchase', { products: [...] });
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
class WABAgentMesh {
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} serverUrl — WAB server base URL
|
|
22
|
+
* @param {object} [options]
|
|
23
|
+
* @param {string} [options.siteId] — Site identifier
|
|
24
|
+
* @param {number} [options.heartbeatInterval=30000] — Heartbeat interval in ms
|
|
25
|
+
*/
|
|
26
|
+
constructor(serverUrl, options = {}) {
|
|
27
|
+
this.serverUrl = serverUrl.replace(/\/$/, '');
|
|
28
|
+
this.siteId = options.siteId || 'default';
|
|
29
|
+
this.heartbeatInterval = options.heartbeatInterval || 30000;
|
|
30
|
+
this.agentId = null;
|
|
31
|
+
this.role = null;
|
|
32
|
+
this._heartbeatTimer = null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── Mesh Management ──────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Join the agent mesh with a role.
|
|
39
|
+
* @param {string} role — Agent role (researcher, negotiator, analyst, guardian, etc.)
|
|
40
|
+
* @param {string} [displayName] — Human-readable name
|
|
41
|
+
* @param {string[]} [capabilities] — List of capabilities
|
|
42
|
+
* @returns {Promise<{id: string, role: string, displayName: string}>}
|
|
43
|
+
*/
|
|
44
|
+
async join(role, displayName, capabilities = []) {
|
|
45
|
+
const data = await this._post('/api/mesh/agents', {
|
|
46
|
+
siteId: this.siteId, role, displayName, capabilities
|
|
47
|
+
});
|
|
48
|
+
this.agentId = data.id;
|
|
49
|
+
this.role = role;
|
|
50
|
+
|
|
51
|
+
// Start heartbeat
|
|
52
|
+
this._heartbeatTimer = setInterval(() => {
|
|
53
|
+
this._post(`/api/mesh/agents/${this.agentId}/heartbeat`, {}).catch(() => {});
|
|
54
|
+
}, this.heartbeatInterval);
|
|
55
|
+
if (this._heartbeatTimer.unref) this._heartbeatTimer.unref();
|
|
56
|
+
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Leave the mesh.
|
|
62
|
+
*/
|
|
63
|
+
async leave() {
|
|
64
|
+
if (this._heartbeatTimer) {
|
|
65
|
+
clearInterval(this._heartbeatTimer);
|
|
66
|
+
this._heartbeatTimer = null;
|
|
67
|
+
}
|
|
68
|
+
if (this.agentId) {
|
|
69
|
+
await this._patch(`/api/mesh/agents/${this.agentId}/status`, { status: 'offline' });
|
|
70
|
+
this.agentId = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get all active agents in the mesh.
|
|
76
|
+
*/
|
|
77
|
+
async getAgents() {
|
|
78
|
+
return this._get('/api/mesh/agents');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get agents by role.
|
|
83
|
+
*/
|
|
84
|
+
async getAgentsByRole(role) {
|
|
85
|
+
return this._get(`/api/mesh/agents/role/${encodeURIComponent(role)}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ─── Messaging ────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Publish a message to a channel.
|
|
92
|
+
*/
|
|
93
|
+
async publish(channel, messageType, subject, payload = {}, options = {}) {
|
|
94
|
+
this._requireAgent();
|
|
95
|
+
return this._post(`/api/mesh/channels/${encodeURIComponent(channel)}/messages`, {
|
|
96
|
+
senderId: this.agentId, messageType, subject, payload,
|
|
97
|
+
priority: options.priority, ttl: options.ttl, targetId: options.targetId
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get unread messages for this agent on a channel.
|
|
103
|
+
*/
|
|
104
|
+
async receive(channel, limit = 20) {
|
|
105
|
+
this._requireAgent();
|
|
106
|
+
return this._get(`/api/mesh/agents/${this.agentId}/messages/${encodeURIComponent(channel)}?limit=${limit}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Acknowledge a message.
|
|
111
|
+
*/
|
|
112
|
+
async acknowledge(messageId) {
|
|
113
|
+
this._requireAgent();
|
|
114
|
+
return this._post(`/api/mesh/agents/${this.agentId}/messages/${messageId}/ack`, {});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get unread count.
|
|
119
|
+
*/
|
|
120
|
+
async getUnreadCount() {
|
|
121
|
+
this._requireAgent();
|
|
122
|
+
return this._get(`/api/mesh/agents/${this.agentId}/unread`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Broadcast an alert to all mesh agents.
|
|
127
|
+
*/
|
|
128
|
+
async alert(subject, details = {}, priority = 2) {
|
|
129
|
+
this._requireAgent();
|
|
130
|
+
return this._post('/api/mesh/alerts', { senderId: this.agentId, subject, details, priority });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Share a tactic with the mesh.
|
|
135
|
+
*/
|
|
136
|
+
async shareTactic(domain, tactic, confidence = 1.0) {
|
|
137
|
+
this._requireAgent();
|
|
138
|
+
return this._post('/api/mesh/tactics', {
|
|
139
|
+
senderId: this.agentId, domain, tactic, confidence
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Request help from other agents.
|
|
145
|
+
*/
|
|
146
|
+
async requestHelp(subject, question, targetRole = null) {
|
|
147
|
+
this._requireAgent();
|
|
148
|
+
return this._post('/api/mesh/help', {
|
|
149
|
+
senderId: this.agentId, subject, question, targetRole
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── Knowledge ────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Share knowledge to the mesh.
|
|
157
|
+
*/
|
|
158
|
+
async shareKnowledge(knowledgeType, domain, key, value, confidence = 1.0) {
|
|
159
|
+
this._requireAgent();
|
|
160
|
+
return this._post('/api/mesh/knowledge', {
|
|
161
|
+
agentId: this.agentId, knowledgeType, domain, key, value, confidence
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Query knowledge by domain and key.
|
|
167
|
+
*/
|
|
168
|
+
async queryKnowledge(domain, key) {
|
|
169
|
+
return this._get(`/api/mesh/knowledge/${encodeURIComponent(domain)}/${encodeURIComponent(key)}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Search knowledge by domain.
|
|
174
|
+
*/
|
|
175
|
+
async searchKnowledge(domain, limit = 20) {
|
|
176
|
+
return this._get(`/api/mesh/knowledge/${encodeURIComponent(domain)}?limit=${limit}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─── Symphony Orchestrator ────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Run a full symphony — coordinate researcher, analyst, negotiator, guardian.
|
|
183
|
+
* @param {string} task — Task description
|
|
184
|
+
* @param {string} taskType — purchase, price_comparison, negotiation, exploration, verification
|
|
185
|
+
* @param {object} [inputData] — Data to pass to symphony phases
|
|
186
|
+
* @param {object} [agentIds] — Map role → agentId for specific agents
|
|
187
|
+
*/
|
|
188
|
+
async symphony(task, taskType, inputData = {}, agentIds = {}) {
|
|
189
|
+
return this._post('/api/mesh/symphony/perform', {
|
|
190
|
+
siteId: this.siteId, task, taskType, inputData, agentIds
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Compose a symphony step-by-step.
|
|
196
|
+
*/
|
|
197
|
+
async symphonyCompose(task, taskType, agentIds = {}) {
|
|
198
|
+
return this._post('/api/mesh/symphony/compose', {
|
|
199
|
+
siteId: this.siteId, task, taskType, agentIds
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Execute a single symphony phase.
|
|
205
|
+
*/
|
|
206
|
+
async symphonyPhase(compositionId, phase, input = {}) {
|
|
207
|
+
return this._post(`/api/mesh/symphony/${compositionId}/phase`, { phase, input });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get composition details.
|
|
212
|
+
*/
|
|
213
|
+
async getComposition(compositionId) {
|
|
214
|
+
return this._get(`/api/mesh/symphony/${compositionId}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get available symphony templates.
|
|
219
|
+
*/
|
|
220
|
+
async getTemplates() {
|
|
221
|
+
return this._get('/api/mesh/symphony/templates/all');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ─── Learning Engine ──────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Record a decision for the learning engine.
|
|
228
|
+
*/
|
|
229
|
+
async recordDecision(domain, action, context = {}, features = {}) {
|
|
230
|
+
this._requireAgent();
|
|
231
|
+
return this._post('/api/mesh/learning/decisions', {
|
|
232
|
+
siteId: this.siteId, agentId: this.agentId, domain, action, context, features
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Provide feedback on a decision.
|
|
238
|
+
* @param {string} decisionId
|
|
239
|
+
* @param {string} outcome — accepted, rejected, modified
|
|
240
|
+
* @param {number} reward — 0.0 to 1.0
|
|
241
|
+
*/
|
|
242
|
+
async feedback(decisionId, outcome, reward) {
|
|
243
|
+
return this._post(`/api/mesh/learning/decisions/${decisionId}/feedback`, { outcome, reward });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get recommendation based on learned preferences.
|
|
248
|
+
* @param {string} domain
|
|
249
|
+
* @param {string[]} actions — Available actions to choose from
|
|
250
|
+
* @param {object} [context] — Current context
|
|
251
|
+
*/
|
|
252
|
+
async recommend(domain, actions, context = {}) {
|
|
253
|
+
this._requireAgent();
|
|
254
|
+
return this._post('/api/mesh/learning/recommend', {
|
|
255
|
+
siteId: this.siteId, agentId: this.agentId, domain, actions, context
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get learned preferences for a domain.
|
|
261
|
+
*/
|
|
262
|
+
async getPreferences(domain) {
|
|
263
|
+
this._requireAgent();
|
|
264
|
+
return this._get(`/api/mesh/learning/preferences/${encodeURIComponent(this.siteId)}/${this.agentId}/${encodeURIComponent(domain)}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get learning stats.
|
|
269
|
+
*/
|
|
270
|
+
async getLearningStats() {
|
|
271
|
+
this._requireAgent();
|
|
272
|
+
return this._get(`/api/mesh/learning/stats/${encodeURIComponent(this.siteId)}/${this.agentId}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ─── Stats ────────────────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get mesh statistics.
|
|
279
|
+
*/
|
|
280
|
+
async getStats() {
|
|
281
|
+
return this._get('/api/mesh/stats');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get full dashboard data (agents, channels, templates, stats).
|
|
286
|
+
*/
|
|
287
|
+
async getDashboard() {
|
|
288
|
+
return this._get('/api/mesh/dashboard');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ─── Internal ─────────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
_requireAgent() {
|
|
294
|
+
if (!this.agentId) throw new Error('Must call join() first');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async _get(path) {
|
|
298
|
+
const res = await fetch(`${this.serverUrl}${path}`);
|
|
299
|
+
if (!res.ok) {
|
|
300
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
301
|
+
throw new Error(err.error || `HTTP ${res.status}`);
|
|
302
|
+
}
|
|
303
|
+
return res.json();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async _post(path, body) {
|
|
307
|
+
const res = await fetch(`${this.serverUrl}${path}`, {
|
|
308
|
+
method: 'POST',
|
|
309
|
+
headers: { 'Content-Type': 'application/json' },
|
|
310
|
+
body: JSON.stringify(body)
|
|
311
|
+
});
|
|
312
|
+
if (!res.ok) {
|
|
313
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
314
|
+
throw new Error(err.error || `HTTP ${res.status}`);
|
|
315
|
+
}
|
|
316
|
+
return res.json();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async _patch(path, body) {
|
|
320
|
+
const res = await fetch(`${this.serverUrl}${path}`, {
|
|
321
|
+
method: 'PATCH',
|
|
322
|
+
headers: { 'Content-Type': 'application/json' },
|
|
323
|
+
body: JSON.stringify(body)
|
|
324
|
+
});
|
|
325
|
+
if (!res.ok) {
|
|
326
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
327
|
+
throw new Error(err.error || `HTTP ${res.status}`);
|
|
328
|
+
}
|
|
329
|
+
return res.json();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
module.exports = { WABAgentMesh };
|