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.
Files changed (35) hide show
  1. package/package.json +12 -4
  2. package/public/commander-dashboard.html +243 -0
  3. package/public/css/premium.css +317 -317
  4. package/public/demo.html +259 -259
  5. package/public/index.html +644 -644
  6. package/public/mesh-dashboard.html +309 -382
  7. package/public/premium-dashboard.html +2487 -2487
  8. package/public/premium.html +791 -791
  9. package/public/script/wab.min.js +124 -87
  10. package/script/ai-agent-bridge.js +154 -84
  11. package/sdk/agent-mesh.js +287 -171
  12. package/sdk/commander.js +262 -0
  13. package/sdk/index.js +260 -260
  14. package/server/index.js +8 -1
  15. package/server/migrations/002_premium_features.sql +418 -418
  16. package/server/models/db.js +24 -5
  17. package/server/routes/admin-premium.js +671 -671
  18. package/server/routes/commander.js +316 -0
  19. package/server/routes/mesh.js +370 -201
  20. package/server/routes/premium-v2.js +686 -686
  21. package/server/routes/premium.js +724 -724
  22. package/server/services/agent-learning.js +230 -77
  23. package/server/services/agent-memory.js +625 -625
  24. package/server/services/agent-mesh.js +260 -67
  25. package/server/services/agent-symphony.js +548 -518
  26. package/server/services/commander.js +738 -0
  27. package/server/services/edge-compute.js +440 -0
  28. package/server/services/local-ai.js +389 -0
  29. package/server/services/plugins.js +747 -747
  30. package/server/services/self-healing.js +843 -843
  31. package/server/services/swarm.js +788 -788
  32. package/server/services/vision.js +871 -871
  33. package/public/admin/dashboard.html +0 -848
  34. package/public/admin/login.html +0 -84
  35. package/public/video/tutorial.mp4 +0 -0
package/sdk/agent-mesh.js CHANGED
@@ -1,330 +1,446 @@
1
1
  /**
2
- * WAB Agent Mesh SDK
2
+ * WABAgentMesh — SDK Client for the Private Agent Mesh
3
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: [...] });
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 {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
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(serverUrl, options = {}) {
27
- this.serverUrl = serverUrl.replace(/\/$/, '');
28
- this.siteId = options.siteId || 'default';
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
- // ─── Mesh Management ──────────────────────────────────────────────
34
+ // ─── Lifecycle ───────────────────────────────────────────────────
36
35
 
37
36
  /**
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}>}
37
+ * Join the mesh. Registers the agent and starts heartbeat.
38
+ * @returns {Promise<object>} Registered agent data
43
39
  */
44
- async join(role, displayName, capabilities = []) {
45
- const data = await this._post('/api/mesh/agents', {
46
- siteId: this.siteId, role, displayName, capabilities
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
- 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;
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
- if (this._heartbeatTimer) {
65
- clearInterval(this._heartbeatTimer);
66
- this._heartbeatTimer = null;
67
- }
59
+ this._stopHeartbeat();
68
60
  if (this.agentId) {
69
- await this._patch(`/api/mesh/agents/${this.agentId}/status`, { status: 'offline' });
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
- * 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.
70
+ * Destroy the client leave mesh and clean up all resources.
83
71
  */
84
- async getAgentsByRole(role) {
85
- return this._get(`/api/mesh/agents/role/${encodeURIComponent(role)}`);
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(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
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 unread messages for this agent on a channel.
98
+ * Get messages for this agent.
103
99
  */
104
- async receive(channel, limit = 20) {
105
- this._requireAgent();
106
- return this._get(`/api/mesh/agents/${this.agentId}/messages/${encodeURIComponent(channel)}?limit=${limit}`);
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._requireAgent();
114
- return this._post(`/api/mesh/agents/${this.agentId}/messages/${messageId}/ack`, {});
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 getUnreadCount() {
121
- this._requireAgent();
122
- return this._get(`/api/mesh/agents/${this.agentId}/unread`);
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 mesh agents.
124
+ * Broadcast an alert to all agents.
127
125
  */
128
- async alert(subject, details = {}, priority = 2) {
129
- this._requireAgent();
130
- return this._post('/api/mesh/alerts', { senderId: this.agentId, subject, details, priority });
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(domain, tactic, confidence = 1.0) {
137
- this._requireAgent();
138
- return this._post('/api/mesh/tactics', {
139
- senderId: this.agentId, domain, tactic, confidence
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(subject, question, targetRole = null) {
147
- this._requireAgent();
148
- return this._post('/api/mesh/help', {
149
- senderId: this.agentId, subject, question, targetRole
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 to the mesh.
159
+ * Share knowledge with the mesh.
157
160
  */
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
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 key.
176
+ * Query knowledge by domain and/or type.
167
177
  */
168
- async queryKnowledge(domain, key) {
169
- return this._get(`/api/mesh/knowledge/${encodeURIComponent(domain)}/${encodeURIComponent(key)}`);
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 domain.
189
+ * Search knowledge by keyword.
174
190
  */
175
- async searchKnowledge(domain, limit = 20) {
176
- return this._get(`/api/mesh/knowledge/${encodeURIComponent(domain)}?limit=${limit}`);
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
- * 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
197
+ * Get knowledge domains with counts.
187
198
  */
188
- async symphony(task, taskType, inputData = {}, agentIds = {}) {
189
- return this._post('/api/mesh/symphony/perform', {
190
- siteId: this.siteId, task, taskType, inputData, agentIds
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
- * Compose a symphony step-by-step.
205
+ * Verify a knowledge entry.
196
206
  */
197
- async symphonyCompose(task, taskType, agentIds = {}) {
198
- return this._post('/api/mesh/symphony/compose', {
199
- siteId: this.siteId, task, taskType, agentIds
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
- * Execute a single symphony phase.
218
+ * Create a vote for other agents to participate in.
205
219
  */
206
- async symphonyPhase(compositionId, phase, input = {}) {
207
- return this._post(`/api/mesh/symphony/${compositionId}/phase`, { phase, input });
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
- * Get composition details.
229
+ * Cast a vote on an existing vote message.
212
230
  */
213
- async getComposition(compositionId) {
214
- return this._get(`/api/mesh/symphony/${compositionId}`);
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 available symphony templates.
240
+ * Get the tally for a vote.
219
241
  */
220
- async getTemplates() {
221
- return this._get('/api/mesh/symphony/templates/all');
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 Engine ──────────────────────────────────────────────
247
+ // ─── Learning Integration ─────────────────────────────────────
225
248
 
226
249
  /**
227
- * Record a decision for the learning engine.
250
+ * Record a decision for learning.
228
251
  */
229
252
  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
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
- return this._post(`/api/mesh/learning/decisions/${decisionId}/feedback`, { outcome, reward });
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 based on learned preferences.
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._requireAgent();
254
- return this._post('/api/mesh/learning/recommend', {
255
- siteId: this.siteId, agentId: this.agentId, domain, actions, context
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 learned preferences for a domain.
284
+ * Get learning stats.
261
285
  */
262
- async getPreferences(domain) {
263
- this._requireAgent();
264
- return this._get(`/api/mesh/learning/preferences/${encodeURIComponent(this.siteId)}/${this.agentId}/${encodeURIComponent(domain)}`);
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
- * Get learning stats.
294
+ * Execute a symphony composition.
269
295
  */
270
- async getLearningStats() {
271
- this._requireAgent();
272
- return this._get(`/api/mesh/learning/stats/${encodeURIComponent(this.siteId)}/${this.agentId}`);
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
- // ─── Stats ────────────────────────────────────────────────────────
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 statistics.
324
+ * Get mesh stats.
279
325
  */
280
326
  async getStats() {
281
- return this._get('/api/mesh/stats');
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
- * Get full dashboard data (agents, channels, templates, stats).
341
+ * Set own status.
286
342
  */
287
- async getDashboard() {
288
- return this._get('/api/mesh/dashboard');
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
- // ─── Internal ─────────────────────────────────────────────────────
349
+ // ─── Events ────────────────────────────────────────────────────
292
350
 
293
- _requireAgent() {
294
- if (!this.agentId) throw new Error('Must call join() first');
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
- 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}`);
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 res = await fetch(`${this.serverUrl}${path}`, {
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 res = await fetch(`${this.serverUrl}${path}`, {
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 err = await res.json().catch(() => ({ error: res.statusText }));
327
- throw new Error(err.error || `HTTP ${res.status}`);
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
  }