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.
@@ -26,7 +26,7 @@
26
26
  (function(global) {
27
27
  'use strict';
28
28
 
29
- var VERSION = '2.0.0';
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
 
@@ -445,7 +445,7 @@
445
445
  </div>
446
446
 
447
447
  <footer class="footer">
448
- Web Agent Bridge v2.0.0 &mdash; Digital Fortress for Internet Sovereignty<br>
448
+ Web Agent Bridge v2.2.0 &mdash; 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.0.0';
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 };