web-agent-bridge 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -4
- package/public/commander-dashboard.html +243 -0
- package/public/css/premium.css +317 -317
- package/public/demo.html +259 -259
- package/public/index.html +644 -644
- package/public/mesh-dashboard.html +309 -382
- package/public/premium-dashboard.html +2487 -2487
- package/public/premium.html +791 -791
- package/public/script/wab.min.js +124 -87
- package/script/ai-agent-bridge.js +154 -84
- package/sdk/agent-mesh.js +287 -171
- package/sdk/commander.js +262 -0
- package/sdk/index.js +260 -260
- package/server/index.js +8 -1
- package/server/migrations/002_premium_features.sql +418 -418
- package/server/models/db.js +24 -5
- package/server/routes/admin-premium.js +671 -671
- package/server/routes/commander.js +316 -0
- package/server/routes/mesh.js +370 -201
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/services/agent-learning.js +230 -77
- package/server/services/agent-memory.js +625 -625
- package/server/services/agent-mesh.js +260 -67
- package/server/services/agent-symphony.js +548 -518
- package/server/services/commander.js +738 -0
- package/server/services/edge-compute.js +440 -0
- package/server/services/local-ai.js +389 -0
- package/server/services/plugins.js +747 -747
- package/server/services/self-healing.js +843 -843
- package/server/services/swarm.js +788 -788
- package/server/services/vision.js +871 -871
- package/public/admin/dashboard.html +0 -848
- package/public/admin/login.html +0 -84
- package/public/video/tutorial.mp4 +0 -0
package/sdk/agent-mesh.js
CHANGED
|
@@ -1,330 +1,446 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* WABAgentMesh — SDK Client for the Private Agent Mesh
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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: [...] });
|
|
4
|
+
* High-level client for agents to join the mesh, communicate,
|
|
5
|
+
* share knowledge, participate in votes, and learn from decisions.
|
|
6
|
+
* Includes automatic heartbeat, reconnection, and response validation.
|
|
17
7
|
*/
|
|
18
8
|
|
|
19
9
|
class WABAgentMesh {
|
|
20
10
|
/**
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {string}
|
|
24
|
-
* @param {
|
|
11
|
+
* @param {object} options
|
|
12
|
+
* @param {string} options.serverUrl - WAB server URL
|
|
13
|
+
* @param {string} options.role - Agent role (e.g., 'monitor', 'optimizer')
|
|
14
|
+
* @param {string} [options.displayName] - Human-readable agent name
|
|
15
|
+
* @param {string[]} [options.capabilities] - List of capabilities
|
|
16
|
+
* @param {string} [options.siteId] - Site identifier
|
|
17
|
+
* @param {number} [options.heartbeatInterval=30000] - Heartbeat interval in ms
|
|
25
18
|
*/
|
|
26
|
-
constructor(
|
|
27
|
-
this.serverUrl = serverUrl.replace(/\/$/, '');
|
|
28
|
-
this.
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.serverUrl = (options.serverUrl || '').replace(/\/$/, '');
|
|
21
|
+
this.role = options.role || 'agent';
|
|
22
|
+
this.displayName = options.displayName || null;
|
|
23
|
+
this.capabilities = options.capabilities || [];
|
|
24
|
+
this.siteId = options.siteId || null;
|
|
29
25
|
this.heartbeatInterval = options.heartbeatInterval || 30000;
|
|
26
|
+
|
|
30
27
|
this.agentId = null;
|
|
31
|
-
this.role = null;
|
|
32
28
|
this._heartbeatTimer = null;
|
|
29
|
+
this._retryCount = 0;
|
|
30
|
+
this._maxRetries = 5;
|
|
31
|
+
this._listeners = {};
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
// ───
|
|
34
|
+
// ─── Lifecycle ───────────────────────────────────────────────────
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
|
-
* Join the agent
|
|
39
|
-
* @
|
|
40
|
-
* @param {string} [displayName] — Human-readable name
|
|
41
|
-
* @param {string[]} [capabilities] — List of capabilities
|
|
42
|
-
* @returns {Promise<{id: string, role: string, displayName: string}>}
|
|
37
|
+
* Join the mesh. Registers the agent and starts heartbeat.
|
|
38
|
+
* @returns {Promise<object>} Registered agent data
|
|
43
39
|
*/
|
|
44
|
-
async join(
|
|
45
|
-
const
|
|
46
|
-
siteId: this.siteId,
|
|
40
|
+
async join() {
|
|
41
|
+
const res = await this._post('/api/mesh/agents', {
|
|
42
|
+
siteId: this.siteId,
|
|
43
|
+
role: this.role,
|
|
44
|
+
displayName: this.displayName,
|
|
45
|
+
capabilities: this.capabilities,
|
|
47
46
|
});
|
|
48
|
-
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.
|
|
53
|
-
|
|
54
|
-
}, this.heartbeatInterval);
|
|
55
|
-
if (this._heartbeatTimer.unref) this._heartbeatTimer.unref();
|
|
56
|
-
|
|
57
|
-
return data;
|
|
47
|
+
const data = await this._json(res);
|
|
48
|
+
this.agentId = data.agent.id;
|
|
49
|
+
this._retryCount = 0;
|
|
50
|
+
this._startHeartbeat();
|
|
51
|
+
this._emit('joined', data.agent);
|
|
52
|
+
return data.agent;
|
|
58
53
|
}
|
|
59
54
|
|
|
60
55
|
/**
|
|
61
|
-
* Leave the mesh.
|
|
56
|
+
* Leave the mesh. Deregisters and stops heartbeat.
|
|
62
57
|
*/
|
|
63
58
|
async leave() {
|
|
64
|
-
|
|
65
|
-
clearInterval(this._heartbeatTimer);
|
|
66
|
-
this._heartbeatTimer = null;
|
|
67
|
-
}
|
|
59
|
+
this._stopHeartbeat();
|
|
68
60
|
if (this.agentId) {
|
|
69
|
-
|
|
61
|
+
try {
|
|
62
|
+
await this._delete(`/api/mesh/agents/${this.agentId}`);
|
|
63
|
+
} catch (_) { /* ignore errors during cleanup */ }
|
|
64
|
+
this._emit('left', { agentId: this.agentId });
|
|
70
65
|
this.agentId = null;
|
|
71
66
|
}
|
|
72
67
|
}
|
|
73
68
|
|
|
74
69
|
/**
|
|
75
|
-
*
|
|
76
|
-
*/
|
|
77
|
-
async getAgents() {
|
|
78
|
-
return this._get('/api/mesh/agents');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Get agents by role.
|
|
70
|
+
* Destroy the client — leave mesh and clean up all resources.
|
|
83
71
|
*/
|
|
84
|
-
async
|
|
85
|
-
|
|
72
|
+
async destroy() {
|
|
73
|
+
await this.leave();
|
|
74
|
+
this._listeners = {};
|
|
86
75
|
}
|
|
87
76
|
|
|
88
|
-
// ─── Messaging
|
|
77
|
+
// ─── Messaging ─────────────────────────────────────────────────
|
|
89
78
|
|
|
90
79
|
/**
|
|
91
80
|
* Publish a message to a channel.
|
|
92
81
|
*/
|
|
93
|
-
async publish(
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
82
|
+
async publish(type, subject, payload = {}, options = {}) {
|
|
83
|
+
this._requireJoined();
|
|
84
|
+
const res = await this._post('/api/mesh/messages', {
|
|
85
|
+
channelName: options.channel || 'general',
|
|
86
|
+
senderId: this.agentId,
|
|
87
|
+
targetId: options.targetId || null,
|
|
88
|
+
type,
|
|
89
|
+
subject,
|
|
90
|
+
payload,
|
|
91
|
+
priority: options.priority || 0,
|
|
92
|
+
ttl: options.ttl,
|
|
98
93
|
});
|
|
94
|
+
return (await this._json(res)).message;
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
/**
|
|
102
|
-
* Get
|
|
98
|
+
* Get messages for this agent.
|
|
103
99
|
*/
|
|
104
|
-
async
|
|
105
|
-
this.
|
|
106
|
-
|
|
100
|
+
async getMessages(limit = 50) {
|
|
101
|
+
this._requireJoined();
|
|
102
|
+
const res = await this._get(`/api/mesh/messages?agentId=${this.agentId}&limit=${limit}`);
|
|
103
|
+
return (await this._json(res)).messages;
|
|
107
104
|
}
|
|
108
105
|
|
|
109
106
|
/**
|
|
110
107
|
* Acknowledge a message.
|
|
111
108
|
*/
|
|
112
109
|
async acknowledge(messageId) {
|
|
113
|
-
this.
|
|
114
|
-
return this.
|
|
110
|
+
const res = await this._post(`/api/mesh/messages/${encodeURIComponent(messageId)}/acknowledge`);
|
|
111
|
+
return (await this._json(res));
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
/**
|
|
118
|
-
* Get unread count.
|
|
115
|
+
* Get unread count and breakdown by channel.
|
|
119
116
|
*/
|
|
120
|
-
async
|
|
121
|
-
this.
|
|
122
|
-
|
|
117
|
+
async getUnread() {
|
|
118
|
+
this._requireJoined();
|
|
119
|
+
const res = await this._get(`/api/mesh/agents/${this.agentId}/unread`);
|
|
120
|
+
return await this._json(res);
|
|
123
121
|
}
|
|
124
122
|
|
|
125
123
|
/**
|
|
126
|
-
* Broadcast an alert to all
|
|
124
|
+
* Broadcast an alert to all agents.
|
|
127
125
|
*/
|
|
128
|
-
async alert(subject, details
|
|
129
|
-
this.
|
|
130
|
-
|
|
126
|
+
async alert(subject, details, priority = 2) {
|
|
127
|
+
this._requireJoined();
|
|
128
|
+
const res = await this._post('/api/mesh/alert', {
|
|
129
|
+
senderId: this.agentId, subject, details, priority,
|
|
130
|
+
});
|
|
131
|
+
return (await this._json(res)).message;
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
/**
|
|
134
135
|
* Share a tactic with the mesh.
|
|
135
136
|
*/
|
|
136
|
-
async shareTactic(
|
|
137
|
-
this.
|
|
138
|
-
|
|
139
|
-
senderId: this.agentId,
|
|
137
|
+
async shareTactic(name, tactic) {
|
|
138
|
+
this._requireJoined();
|
|
139
|
+
const res = await this._post('/api/mesh/tactic', {
|
|
140
|
+
senderId: this.agentId, name, tactic,
|
|
140
141
|
});
|
|
142
|
+
return (await this._json(res)).message;
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
/**
|
|
144
146
|
* Request help from other agents.
|
|
145
147
|
*/
|
|
146
|
-
async requestHelp(
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
senderId: this.agentId,
|
|
148
|
+
async requestHelp(problem, context = {}) {
|
|
149
|
+
this._requireJoined();
|
|
150
|
+
const res = await this._post('/api/mesh/help', {
|
|
151
|
+
senderId: this.agentId, problem, context,
|
|
150
152
|
});
|
|
153
|
+
return (await this._json(res)).message;
|
|
151
154
|
}
|
|
152
155
|
|
|
153
|
-
// ─── Knowledge
|
|
156
|
+
// ─── Knowledge ─────────────────────────────────────────────────
|
|
154
157
|
|
|
155
158
|
/**
|
|
156
|
-
* Share knowledge
|
|
159
|
+
* Share knowledge with the mesh.
|
|
157
160
|
*/
|
|
158
|
-
async shareKnowledge(
|
|
159
|
-
this.
|
|
160
|
-
|
|
161
|
-
agentId: this.agentId,
|
|
161
|
+
async shareKnowledge(type, key, value, options = {}) {
|
|
162
|
+
this._requireJoined();
|
|
163
|
+
const res = await this._post('/api/mesh/knowledge', {
|
|
164
|
+
agentId: this.agentId,
|
|
165
|
+
type,
|
|
166
|
+
domain: options.domain,
|
|
167
|
+
key,
|
|
168
|
+
value,
|
|
169
|
+
confidence: options.confidence,
|
|
170
|
+
source: options.source,
|
|
162
171
|
});
|
|
172
|
+
return (await this._json(res)).knowledge;
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
/**
|
|
166
|
-
* Query knowledge by domain and
|
|
176
|
+
* Query knowledge by domain and/or type.
|
|
167
177
|
*/
|
|
168
|
-
async queryKnowledge(
|
|
169
|
-
|
|
178
|
+
async queryKnowledge(params = {}) {
|
|
179
|
+
const qs = new URLSearchParams();
|
|
180
|
+
if (params.domain) qs.set('domain', params.domain);
|
|
181
|
+
if (params.type) qs.set('type', params.type);
|
|
182
|
+
if (params.agentId) qs.set('agentId', params.agentId);
|
|
183
|
+
if (params.limit) qs.set('limit', params.limit);
|
|
184
|
+
const res = await this._get(`/api/mesh/knowledge?${qs.toString()}`);
|
|
185
|
+
return (await this._json(res)).knowledge;
|
|
170
186
|
}
|
|
171
187
|
|
|
172
188
|
/**
|
|
173
|
-
* Search knowledge by
|
|
189
|
+
* Search knowledge by keyword.
|
|
174
190
|
*/
|
|
175
|
-
async searchKnowledge(
|
|
176
|
-
|
|
191
|
+
async searchKnowledge(query, limit = 20) {
|
|
192
|
+
const res = await this._get(`/api/mesh/knowledge/search/${encodeURIComponent(query)}?limit=${limit}`);
|
|
193
|
+
return (await this._json(res)).knowledge;
|
|
177
194
|
}
|
|
178
195
|
|
|
179
|
-
// ─── Symphony Orchestrator ────────────────────────────────────────
|
|
180
|
-
|
|
181
196
|
/**
|
|
182
|
-
*
|
|
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
|
|
197
|
+
* Get knowledge domains with counts.
|
|
187
198
|
*/
|
|
188
|
-
async
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
});
|
|
199
|
+
async getKnowledgeDomains() {
|
|
200
|
+
const res = await this._get('/api/mesh/knowledge-domains');
|
|
201
|
+
return (await this._json(res)).domains;
|
|
192
202
|
}
|
|
193
203
|
|
|
194
204
|
/**
|
|
195
|
-
*
|
|
205
|
+
* Verify a knowledge entry.
|
|
196
206
|
*/
|
|
197
|
-
async
|
|
198
|
-
|
|
199
|
-
|
|
207
|
+
async verifyKnowledge(knowledgeId, confidence) {
|
|
208
|
+
this._requireJoined();
|
|
209
|
+
const res = await this._post(`/api/mesh/knowledge/${encodeURIComponent(knowledgeId)}/verify`, {
|
|
210
|
+
verifierId: this.agentId, confidence,
|
|
200
211
|
});
|
|
212
|
+
return await this._json(res);
|
|
201
213
|
}
|
|
202
214
|
|
|
215
|
+
// ─── Voting ────────────────────────────────────────────────────
|
|
216
|
+
|
|
203
217
|
/**
|
|
204
|
-
*
|
|
218
|
+
* Create a vote for other agents to participate in.
|
|
205
219
|
*/
|
|
206
|
-
async
|
|
207
|
-
|
|
220
|
+
async createVote(subject, options, deadlineSeconds = 60) {
|
|
221
|
+
this._requireJoined();
|
|
222
|
+
const res = await this._post('/api/mesh/votes', {
|
|
223
|
+
senderId: this.agentId, subject, options, deadlineSeconds,
|
|
224
|
+
});
|
|
225
|
+
return (await this._json(res)).vote;
|
|
208
226
|
}
|
|
209
227
|
|
|
210
228
|
/**
|
|
211
|
-
*
|
|
229
|
+
* Cast a vote on an existing vote message.
|
|
212
230
|
*/
|
|
213
|
-
async
|
|
214
|
-
|
|
231
|
+
async castVote(voteMessageId, choice, weight = 1, reason = '') {
|
|
232
|
+
this._requireJoined();
|
|
233
|
+
const res = await this._post(`/api/mesh/votes/${encodeURIComponent(voteMessageId)}/cast`, {
|
|
234
|
+
voterId: this.agentId, choice, weight, reason,
|
|
235
|
+
});
|
|
236
|
+
return (await this._json(res)).result;
|
|
215
237
|
}
|
|
216
238
|
|
|
217
239
|
/**
|
|
218
|
-
* Get
|
|
240
|
+
* Get the tally for a vote.
|
|
219
241
|
*/
|
|
220
|
-
async
|
|
221
|
-
|
|
242
|
+
async tallyVote(voteMessageId) {
|
|
243
|
+
const res = await this._get(`/api/mesh/votes/${encodeURIComponent(voteMessageId)}/tally`);
|
|
244
|
+
return (await this._json(res)).tally;
|
|
222
245
|
}
|
|
223
246
|
|
|
224
|
-
// ─── Learning
|
|
247
|
+
// ─── Learning Integration ─────────────────────────────────────
|
|
225
248
|
|
|
226
249
|
/**
|
|
227
|
-
* Record a decision for
|
|
250
|
+
* Record a decision for learning.
|
|
228
251
|
*/
|
|
229
252
|
async recordDecision(domain, action, context = {}, features = {}) {
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
|
|
253
|
+
const res = await this._post('/api/mesh/learning/decisions', {
|
|
254
|
+
siteId: this.siteId || 'default',
|
|
255
|
+
agentId: this.agentId || this.role,
|
|
256
|
+
domain, action, context, features,
|
|
233
257
|
});
|
|
258
|
+
return await this._json(res);
|
|
234
259
|
}
|
|
235
260
|
|
|
236
261
|
/**
|
|
237
262
|
* 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
263
|
*/
|
|
242
264
|
async feedback(decisionId, outcome, reward) {
|
|
243
|
-
|
|
265
|
+
const res = await this._post('/api/mesh/learning/feedback', {
|
|
266
|
+
decisionId, outcome, reward,
|
|
267
|
+
});
|
|
268
|
+
return await this._json(res);
|
|
244
269
|
}
|
|
245
270
|
|
|
246
271
|
/**
|
|
247
|
-
* Get recommendation
|
|
248
|
-
* @param {string} domain
|
|
249
|
-
* @param {string[]} actions — Available actions to choose from
|
|
250
|
-
* @param {object} [context] — Current context
|
|
272
|
+
* Get a recommendation for the best action.
|
|
251
273
|
*/
|
|
252
274
|
async recommend(domain, actions, context = {}) {
|
|
253
|
-
this.
|
|
254
|
-
|
|
255
|
-
|
|
275
|
+
const res = await this._post('/api/mesh/learning/recommend', {
|
|
276
|
+
siteId: this.siteId || 'default',
|
|
277
|
+
agentId: this.agentId || this.role,
|
|
278
|
+
domain, actions, context,
|
|
256
279
|
});
|
|
280
|
+
return await this._json(res);
|
|
257
281
|
}
|
|
258
282
|
|
|
259
283
|
/**
|
|
260
|
-
* Get
|
|
284
|
+
* Get learning stats.
|
|
261
285
|
*/
|
|
262
|
-
async
|
|
263
|
-
this.
|
|
264
|
-
return
|
|
286
|
+
async getLearningStats() {
|
|
287
|
+
const res = await this._get(`/api/mesh/learning/stats?siteId=${this.siteId || 'default'}&agentId=${this.agentId || this.role}`);
|
|
288
|
+
return (await this._json(res)).stats;
|
|
265
289
|
}
|
|
266
290
|
|
|
291
|
+
// ─── Symphony Integration ─────────────────────────────────────
|
|
292
|
+
|
|
267
293
|
/**
|
|
268
|
-
*
|
|
294
|
+
* Execute a symphony composition.
|
|
269
295
|
*/
|
|
270
|
-
async
|
|
271
|
-
this.
|
|
272
|
-
|
|
296
|
+
async compose(template, inputData = {}, schema = null) {
|
|
297
|
+
const res = await this._post('/api/mesh/symphony/compose', {
|
|
298
|
+
siteId: this.siteId || 'default',
|
|
299
|
+
template, inputData, schema,
|
|
300
|
+
});
|
|
301
|
+
return await this._json(res);
|
|
273
302
|
}
|
|
274
303
|
|
|
275
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Get available symphony templates.
|
|
306
|
+
*/
|
|
307
|
+
async getTemplates() {
|
|
308
|
+
const res = await this._get('/api/mesh/symphony/templates');
|
|
309
|
+
return (await this._json(res)).templates;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ─── Mesh Info ─────────────────────────────────────────────────
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get all active agents in the mesh.
|
|
316
|
+
*/
|
|
317
|
+
async getAgents(role = null) {
|
|
318
|
+
const qs = role ? `?role=${encodeURIComponent(role)}` : '';
|
|
319
|
+
const res = await this._get(`/api/mesh/agents${qs}`);
|
|
320
|
+
return (await this._json(res)).agents;
|
|
321
|
+
}
|
|
276
322
|
|
|
277
323
|
/**
|
|
278
|
-
* Get mesh
|
|
324
|
+
* Get mesh stats.
|
|
279
325
|
*/
|
|
280
326
|
async getStats() {
|
|
281
|
-
|
|
327
|
+
const res = await this._get('/api/mesh/stats');
|
|
328
|
+
return (await this._json(res)).stats;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Update own agent metadata.
|
|
333
|
+
*/
|
|
334
|
+
async updateMeta(metadata) {
|
|
335
|
+
this._requireJoined();
|
|
336
|
+
const res = await this._patch(`/api/mesh/agents/${this.agentId}/meta`, { metadata });
|
|
337
|
+
return await this._json(res);
|
|
282
338
|
}
|
|
283
339
|
|
|
284
340
|
/**
|
|
285
|
-
*
|
|
341
|
+
* Set own status.
|
|
286
342
|
*/
|
|
287
|
-
async
|
|
288
|
-
|
|
343
|
+
async setStatus(status) {
|
|
344
|
+
this._requireJoined();
|
|
345
|
+
const res = await this._put(`/api/mesh/agents/${this.agentId}/status`, { status });
|
|
346
|
+
return await this._json(res);
|
|
289
347
|
}
|
|
290
348
|
|
|
291
|
-
// ───
|
|
349
|
+
// ─── Events ────────────────────────────────────────────────────
|
|
292
350
|
|
|
293
|
-
|
|
294
|
-
if (!this.
|
|
351
|
+
on(event, fn) {
|
|
352
|
+
if (!this._listeners[event]) this._listeners[event] = [];
|
|
353
|
+
this._listeners[event].push(fn);
|
|
354
|
+
return this;
|
|
295
355
|
}
|
|
296
356
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
357
|
+
off(event, fn) {
|
|
358
|
+
if (!this._listeners[event]) return this;
|
|
359
|
+
this._listeners[event] = this._listeners[event].filter((f) => f !== fn);
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
_emit(event, data) {
|
|
364
|
+
if (this._listeners[event]) {
|
|
365
|
+
for (const fn of this._listeners[event]) {
|
|
366
|
+
try { fn(data); } catch (e) { console.error(`[WABAgentMesh] listener error on ${event}:`, e.message); }
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ─── Internal ──────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
_requireJoined() {
|
|
374
|
+
if (!this.agentId) throw new Error('Agent not joined. Call join() first.');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
_startHeartbeat() {
|
|
378
|
+
this._stopHeartbeat();
|
|
379
|
+
this._heartbeatTimer = setInterval(async () => {
|
|
380
|
+
try {
|
|
381
|
+
await this._post(`/api/mesh/agents/${this.agentId}/heartbeat`);
|
|
382
|
+
this._retryCount = 0;
|
|
383
|
+
} catch (e) {
|
|
384
|
+
this._retryCount++;
|
|
385
|
+
this._emit('heartbeat-error', { retryCount: this._retryCount, error: e.message });
|
|
386
|
+
if (this._retryCount >= this._maxRetries) {
|
|
387
|
+
this._stopHeartbeat();
|
|
388
|
+
this._emit('disconnected', { reason: 'heartbeat-failed', retries: this._retryCount });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}, this.heartbeatInterval);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
_stopHeartbeat() {
|
|
395
|
+
if (this._heartbeatTimer) {
|
|
396
|
+
clearInterval(this._heartbeatTimer);
|
|
397
|
+
this._heartbeatTimer = null;
|
|
302
398
|
}
|
|
303
|
-
return res.json();
|
|
304
399
|
}
|
|
305
400
|
|
|
306
401
|
async _post(path, body) {
|
|
307
|
-
const
|
|
402
|
+
const fetch = globalThis.fetch || require('node-fetch');
|
|
403
|
+
return fetch(`${this.serverUrl}${path}`, {
|
|
308
404
|
method: 'POST',
|
|
309
405
|
headers: { 'Content-Type': 'application/json' },
|
|
310
|
-
body: JSON.stringify(body)
|
|
406
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async _get(path) {
|
|
411
|
+
const fetch = globalThis.fetch || require('node-fetch');
|
|
412
|
+
return fetch(`${this.serverUrl}${path}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async _put(path, body) {
|
|
416
|
+
const fetch = globalThis.fetch || require('node-fetch');
|
|
417
|
+
return fetch(`${this.serverUrl}${path}`, {
|
|
418
|
+
method: 'PUT',
|
|
419
|
+
headers: { 'Content-Type': 'application/json' },
|
|
420
|
+
body: JSON.stringify(body),
|
|
311
421
|
});
|
|
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
422
|
}
|
|
318
423
|
|
|
319
424
|
async _patch(path, body) {
|
|
320
|
-
const
|
|
425
|
+
const fetch = globalThis.fetch || require('node-fetch');
|
|
426
|
+
return fetch(`${this.serverUrl}${path}`, {
|
|
321
427
|
method: 'PATCH',
|
|
322
428
|
headers: { 'Content-Type': 'application/json' },
|
|
323
|
-
body: JSON.stringify(body)
|
|
429
|
+
body: JSON.stringify(body),
|
|
324
430
|
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async _delete(path) {
|
|
434
|
+
const fetch = globalThis.fetch || require('node-fetch');
|
|
435
|
+
return fetch(`${this.serverUrl}${path}`, { method: 'DELETE' });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async _json(res) {
|
|
325
439
|
if (!res.ok) {
|
|
326
|
-
const
|
|
327
|
-
|
|
440
|
+
const text = await res.text().catch(() => '');
|
|
441
|
+
let msg;
|
|
442
|
+
try { msg = JSON.parse(text).error; } catch (_) { msg = text || res.statusText; }
|
|
443
|
+
throw new Error(`WABAgentMesh HTTP ${res.status}: ${msg}`);
|
|
328
444
|
}
|
|
329
445
|
return res.json();
|
|
330
446
|
}
|