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/server/routes/mesh.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mesh API
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Mesh Routes — REST API for the Private Agent Mesh
|
|
3
|
+
*
|
|
4
|
+
* Exposes the mesh, learning, and symphony services through
|
|
5
|
+
* RESTful endpoints. All routes are site-scoped and require
|
|
6
|
+
* no external authentication (local-only mesh).
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
const express = require('express');
|
|
@@ -12,289 +13,457 @@ const mesh = require('../services/agent-mesh');
|
|
|
12
13
|
const learning = require('../services/agent-learning');
|
|
13
14
|
const symphony = require('../services/agent-symphony');
|
|
14
15
|
|
|
15
|
-
//
|
|
16
|
-
// AGENT MESH PROTOCOL
|
|
17
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
16
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
function ok(res, data) { res.json({ ok: true, ...data }); }
|
|
19
|
+
function fail(res, status, message) { res.status(status).json({ ok: false, error: message }); }
|
|
20
|
+
|
|
21
|
+
function requireBody(req, res, ...fields) {
|
|
22
|
+
for (const f of fields) {
|
|
23
|
+
if (req.body[f] === undefined || req.body[f] === null) {
|
|
24
|
+
fail(res, 400, `Missing required field: ${f}`);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
32
|
+
// MESH — Agent Registration & Messaging
|
|
33
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
34
|
+
|
|
35
|
+
// Register a new agent
|
|
20
36
|
router.post('/agents', (req, res) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
try {
|
|
38
|
+
if (!requireBody(req, res, 'role')) return;
|
|
39
|
+
const { siteId, role, displayName, capabilities } = req.body;
|
|
40
|
+
const agent = mesh.registerAgent(siteId || null, role, displayName, capabilities);
|
|
41
|
+
ok(res, { agent });
|
|
42
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Deregister an agent
|
|
46
|
+
router.delete('/agents/:id', (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
mesh.deregisterAgent(req.params.id);
|
|
49
|
+
ok(res, { deregistered: req.params.id });
|
|
50
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
25
51
|
});
|
|
26
52
|
|
|
27
|
-
// Get
|
|
53
|
+
// Get active agents
|
|
28
54
|
router.get('/agents', (req, res) => {
|
|
29
|
-
|
|
55
|
+
try {
|
|
56
|
+
const agents = req.query.role
|
|
57
|
+
? mesh.getAgentsByRole(req.query.role)
|
|
58
|
+
: mesh.getActiveAgents();
|
|
59
|
+
ok(res, { agents });
|
|
60
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Get single agent
|
|
64
|
+
router.get('/agents/:id', (req, res) => {
|
|
65
|
+
try {
|
|
66
|
+
const agent = mesh.getAgent(req.params.id);
|
|
67
|
+
if (!agent) return fail(res, 404, 'Agent not found');
|
|
68
|
+
ok(res, { agent });
|
|
69
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
30
70
|
});
|
|
31
71
|
|
|
32
|
-
//
|
|
33
|
-
router.
|
|
34
|
-
|
|
72
|
+
// Update agent metadata
|
|
73
|
+
router.patch('/agents/:id/meta', (req, res) => {
|
|
74
|
+
try {
|
|
75
|
+
if (!requireBody(req, res, 'metadata')) return;
|
|
76
|
+
mesh.updateAgentMeta(req.params.id, req.body.metadata);
|
|
77
|
+
ok(res, { updated: true });
|
|
78
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
35
79
|
});
|
|
36
80
|
|
|
37
|
-
//
|
|
38
|
-
router.post('/agents/:
|
|
39
|
-
|
|
40
|
-
|
|
81
|
+
// Heartbeat
|
|
82
|
+
router.post('/agents/:id/heartbeat', (req, res) => {
|
|
83
|
+
try {
|
|
84
|
+
mesh.heartbeat(req.params.id);
|
|
85
|
+
ok(res, { alive: true });
|
|
86
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
41
87
|
});
|
|
42
88
|
|
|
43
89
|
// Set agent status
|
|
44
|
-
router.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
90
|
+
router.put('/agents/:id/status', (req, res) => {
|
|
91
|
+
try {
|
|
92
|
+
if (!requireBody(req, res, 'status')) return;
|
|
93
|
+
mesh.setAgentStatus(req.params.id, req.body.status);
|
|
94
|
+
ok(res, { status: req.body.status });
|
|
95
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
49
96
|
});
|
|
50
97
|
|
|
98
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
99
|
+
// MESH — Channels & Messaging
|
|
100
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
101
|
+
|
|
51
102
|
// Get channels
|
|
52
103
|
router.get('/channels', (req, res) => {
|
|
53
|
-
res.json(mesh.getChannels());
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Publish message to channel
|
|
57
|
-
router.post('/channels/:channel/messages', (req, res) => {
|
|
58
|
-
const { senderId, messageType, subject, payload, priority, ttl, targetId } = req.body;
|
|
59
|
-
if (!senderId || !messageType || !subject) {
|
|
60
|
-
return res.status(400).json({ error: 'senderId, messageType, and subject are required' });
|
|
61
|
-
}
|
|
62
104
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
} catch (err) {
|
|
66
|
-
res.status(400).json({ error: err.message });
|
|
67
|
-
}
|
|
105
|
+
ok(res, { channels: mesh.getChannels() });
|
|
106
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
68
107
|
});
|
|
69
108
|
|
|
70
|
-
//
|
|
71
|
-
router.
|
|
72
|
-
|
|
73
|
-
|
|
109
|
+
// Create channel
|
|
110
|
+
router.post('/channels', (req, res) => {
|
|
111
|
+
try {
|
|
112
|
+
if (!requireBody(req, res, 'name')) return;
|
|
113
|
+
const channel = mesh.createChannel(req.body.name, req.body.description, req.body.type);
|
|
114
|
+
ok(res, { channel });
|
|
115
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
74
116
|
});
|
|
75
117
|
|
|
76
|
-
//
|
|
77
|
-
router.
|
|
78
|
-
|
|
79
|
-
|
|
118
|
+
// Publish message
|
|
119
|
+
router.post('/messages', (req, res) => {
|
|
120
|
+
try {
|
|
121
|
+
if (!requireBody(req, res, 'senderId', 'type', 'subject')) return;
|
|
122
|
+
const { channelName, senderId, targetId, type, subject, payload, priority, ttl } = req.body;
|
|
123
|
+
const msg = mesh.publish(channelName || 'general', senderId, targetId, type, subject, payload, priority, ttl);
|
|
124
|
+
ok(res, { message: msg });
|
|
125
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
80
126
|
});
|
|
81
127
|
|
|
82
|
-
//
|
|
83
|
-
router.
|
|
84
|
-
|
|
85
|
-
|
|
128
|
+
// Get messages (for a channel or for a specific agent)
|
|
129
|
+
router.get('/messages', (req, res) => {
|
|
130
|
+
try {
|
|
131
|
+
const limit = parseInt(req.query.limit) || 50;
|
|
132
|
+
let messages;
|
|
133
|
+
if (req.query.agentId) {
|
|
134
|
+
messages = mesh.getMessagesForAgent(req.query.agentId, limit);
|
|
135
|
+
} else {
|
|
136
|
+
messages = mesh.getMessages(req.query.channel || 'general', limit);
|
|
137
|
+
}
|
|
138
|
+
ok(res, { messages });
|
|
139
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Acknowledge message
|
|
143
|
+
router.post('/messages/:id/acknowledge', (req, res) => {
|
|
144
|
+
try {
|
|
145
|
+
mesh.acknowledge(req.params.id);
|
|
146
|
+
ok(res, { acknowledged: true });
|
|
147
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
86
148
|
});
|
|
87
149
|
|
|
88
|
-
//
|
|
89
|
-
router.get('/agents/:
|
|
90
|
-
|
|
150
|
+
// Unread count
|
|
151
|
+
router.get('/agents/:id/unread', (req, res) => {
|
|
152
|
+
try {
|
|
153
|
+
const count = mesh.getUnreadCount(req.params.id);
|
|
154
|
+
const byChannel = mesh.getUnreadByChannel(req.params.id);
|
|
155
|
+
ok(res, { unreadCount: count, byChannel });
|
|
156
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
91
157
|
});
|
|
92
158
|
|
|
93
|
-
//
|
|
94
|
-
router.post('/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
159
|
+
// Convenience: broadcast alert
|
|
160
|
+
router.post('/alert', (req, res) => {
|
|
161
|
+
try {
|
|
162
|
+
if (!requireBody(req, res, 'senderId', 'subject', 'details')) return;
|
|
163
|
+
const msg = mesh.broadcastAlert(req.body.senderId, req.body.subject, req.body.details, req.body.priority);
|
|
164
|
+
ok(res, { message: msg });
|
|
165
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
98
166
|
});
|
|
99
167
|
|
|
100
|
-
//
|
|
101
|
-
router.post('/
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
168
|
+
// Convenience: share tactic
|
|
169
|
+
router.post('/tactic', (req, res) => {
|
|
170
|
+
try {
|
|
171
|
+
if (!requireBody(req, res, 'senderId', 'name', 'tactic')) return;
|
|
172
|
+
const msg = mesh.shareTactic(req.body.senderId, req.body.name, req.body.tactic);
|
|
173
|
+
ok(res, { message: msg });
|
|
174
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
107
175
|
});
|
|
108
176
|
|
|
109
|
-
//
|
|
177
|
+
// Convenience: request help
|
|
110
178
|
router.post('/help', (req, res) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
179
|
+
try {
|
|
180
|
+
if (!requireBody(req, res, 'senderId', 'problem', 'context')) return;
|
|
181
|
+
const msg = mesh.requestHelp(req.body.senderId, req.body.problem, req.body.context);
|
|
182
|
+
ok(res, { message: msg });
|
|
183
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
116
184
|
});
|
|
117
185
|
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
186
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
187
|
+
// MESH — Knowledge
|
|
188
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
121
189
|
|
|
122
|
-
// Share knowledge
|
|
190
|
+
// Share knowledge
|
|
123
191
|
router.post('/knowledge', (req, res) => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
192
|
+
try {
|
|
193
|
+
if (!requireBody(req, res, 'agentId', 'type', 'key', 'value')) return;
|
|
194
|
+
const { agentId, type, domain, key, value, confidence, source } = req.body;
|
|
195
|
+
const entry = mesh.shareKnowledge(agentId, type, domain, key, value, confidence, source);
|
|
196
|
+
ok(res, { knowledge: entry });
|
|
197
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
129
198
|
});
|
|
130
199
|
|
|
131
200
|
// Query knowledge
|
|
132
|
-
router.get('/knowledge
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
201
|
+
router.get('/knowledge', (req, res) => {
|
|
202
|
+
try {
|
|
203
|
+
const limit = parseInt(req.query.limit) || 20;
|
|
204
|
+
let results;
|
|
205
|
+
if (req.query.domain) {
|
|
206
|
+
results = mesh.queryKnowledge(req.query.domain, req.query.type, limit);
|
|
207
|
+
} else if (req.query.type) {
|
|
208
|
+
results = mesh.searchKnowledgeByType(req.query.type, limit);
|
|
209
|
+
} else if (req.query.agentId) {
|
|
210
|
+
results = mesh.searchKnowledgeByAgent(req.query.agentId, limit);
|
|
211
|
+
} else {
|
|
212
|
+
results = mesh.getRecentKnowledge(limit);
|
|
213
|
+
}
|
|
214
|
+
ok(res, { knowledge: results });
|
|
215
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Get single knowledge entry
|
|
219
|
+
router.get('/knowledge/:id', (req, res) => {
|
|
220
|
+
try {
|
|
221
|
+
const entry = mesh.getKnowledgeById(req.params.id);
|
|
222
|
+
if (!entry) return fail(res, 404, 'Knowledge entry not found');
|
|
223
|
+
ok(res, { knowledge: entry });
|
|
224
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
136
225
|
});
|
|
137
226
|
|
|
138
|
-
// Search knowledge
|
|
139
|
-
router.get('/knowledge/:
|
|
140
|
-
|
|
141
|
-
|
|
227
|
+
// Search knowledge
|
|
228
|
+
router.get('/knowledge/search/:query', (req, res) => {
|
|
229
|
+
try {
|
|
230
|
+
const results = mesh.searchKnowledge(req.params.query, parseInt(req.query.limit) || 20);
|
|
231
|
+
ok(res, { knowledge: results });
|
|
232
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Knowledge domains
|
|
236
|
+
router.get('/knowledge-domains', (req, res) => {
|
|
237
|
+
try {
|
|
238
|
+
ok(res, { domains: mesh.getKnowledgeDomains() });
|
|
239
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Recent knowledge
|
|
243
|
+
router.get('/knowledge-recent', (req, res) => {
|
|
244
|
+
try {
|
|
245
|
+
const limit = parseInt(req.query.limit) || 20;
|
|
246
|
+
ok(res, { knowledge: mesh.getRecentKnowledge(limit) });
|
|
247
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
142
248
|
});
|
|
143
249
|
|
|
144
250
|
// Verify knowledge
|
|
145
|
-
router.post('/knowledge/:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
251
|
+
router.post('/knowledge/:id/verify', (req, res) => {
|
|
252
|
+
try {
|
|
253
|
+
if (!requireBody(req, res, 'verifierId', 'confidence')) return;
|
|
254
|
+
mesh.verifyKnowledge(req.params.id, req.body.verifierId, req.body.confidence);
|
|
255
|
+
ok(res, { verified: true });
|
|
256
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
150
257
|
});
|
|
151
258
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
259
|
+
// Update knowledge value
|
|
260
|
+
router.put('/knowledge/:id', (req, res) => {
|
|
261
|
+
try {
|
|
262
|
+
if (!requireBody(req, res, 'value')) return;
|
|
263
|
+
mesh.updateKnowledge(req.params.id, req.body.value);
|
|
264
|
+
ok(res, { updated: true });
|
|
265
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
269
|
+
// MESH — Voting
|
|
270
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
271
|
+
|
|
272
|
+
// Create vote
|
|
273
|
+
router.post('/votes', (req, res) => {
|
|
274
|
+
try {
|
|
275
|
+
if (!requireBody(req, res, 'senderId', 'subject', 'options', 'deadlineSeconds')) return;
|
|
276
|
+
const { senderId, subject, options, deadlineSeconds } = req.body;
|
|
277
|
+
const vote = mesh.createVote(senderId, subject, options, deadlineSeconds);
|
|
278
|
+
ok(res, { vote });
|
|
279
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Cast vote
|
|
283
|
+
router.post('/votes/:messageId/cast', (req, res) => {
|
|
284
|
+
try {
|
|
285
|
+
if (!requireBody(req, res, 'voterId', 'choice')) return;
|
|
286
|
+
const { voterId, choice, weight, reason } = req.body;
|
|
287
|
+
const result = mesh.castVote(req.params.messageId, voterId, choice, weight, reason);
|
|
288
|
+
ok(res, { result });
|
|
289
|
+
} catch (e) { fail(res, 400, e.message); }
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Tally votes
|
|
293
|
+
router.get('/votes/:messageId/tally', (req, res) => {
|
|
294
|
+
try {
|
|
295
|
+
const tally = mesh.tallyVotes(req.params.messageId);
|
|
296
|
+
ok(res, { tally });
|
|
297
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
301
|
+
// MESH — Stats
|
|
302
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
303
|
+
|
|
304
|
+
router.get('/stats', (req, res) => {
|
|
305
|
+
try {
|
|
306
|
+
ok(res, { stats: mesh.getStats() });
|
|
307
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
311
|
+
// LEARNING — Decision Recording & Recommendations
|
|
312
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
155
313
|
|
|
156
314
|
// Record a decision
|
|
157
315
|
router.post('/learning/decisions', (req, res) => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
316
|
+
try {
|
|
317
|
+
if (!requireBody(req, res, 'siteId', 'agentId', 'domain', 'action')) return;
|
|
318
|
+
const { siteId, agentId, domain, action, context, features } = req.body;
|
|
319
|
+
const result = learning.recordDecision(siteId, agentId, domain, action, context, features);
|
|
320
|
+
ok(res, result);
|
|
321
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
163
322
|
});
|
|
164
323
|
|
|
165
|
-
// Provide feedback
|
|
166
|
-
router.post('/learning/
|
|
167
|
-
const { outcome, reward } = req.body;
|
|
168
|
-
if (!outcome || reward === undefined) {
|
|
169
|
-
return res.status(400).json({ error: 'outcome and reward are required' });
|
|
170
|
-
}
|
|
324
|
+
// Provide feedback
|
|
325
|
+
router.post('/learning/feedback', (req, res) => {
|
|
171
326
|
try {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
res
|
|
175
|
-
}
|
|
327
|
+
if (!requireBody(req, res, 'decisionId', 'outcome', 'reward')) return;
|
|
328
|
+
const result = learning.feedback(req.body.decisionId, req.body.outcome, req.body.reward);
|
|
329
|
+
ok(res, result);
|
|
330
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Batch feedback
|
|
334
|
+
router.post('/learning/feedback/batch', (req, res) => {
|
|
335
|
+
try {
|
|
336
|
+
if (!requireBody(req, res, 'feedbackList')) return;
|
|
337
|
+
const results = learning.batchFeedback(req.body.feedbackList);
|
|
338
|
+
ok(res, { results });
|
|
339
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
176
340
|
});
|
|
177
341
|
|
|
178
342
|
// Get recommendation
|
|
179
343
|
router.post('/learning/recommend', (req, res) => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
344
|
+
try {
|
|
345
|
+
if (!requireBody(req, res, 'siteId', 'agentId', 'domain', 'actions')) return;
|
|
346
|
+
const { siteId, agentId, domain, actions, context } = req.body;
|
|
347
|
+
const result = learning.recommend(siteId, agentId, domain, actions, context);
|
|
348
|
+
ok(res, result);
|
|
349
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
185
350
|
});
|
|
186
351
|
|
|
187
352
|
// Get preferences
|
|
188
|
-
router.get('/learning/preferences
|
|
189
|
-
|
|
353
|
+
router.get('/learning/preferences', (req, res) => {
|
|
354
|
+
try {
|
|
355
|
+
const { siteId, agentId, domain } = req.query;
|
|
356
|
+
if (!siteId || !agentId || !domain) return fail(res, 400, 'Missing siteId, agentId, or domain');
|
|
357
|
+
const prefs = learning.getPreferences(siteId, agentId, domain);
|
|
358
|
+
ok(res, { preferences: prefs });
|
|
359
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
190
360
|
});
|
|
191
361
|
|
|
192
|
-
//
|
|
193
|
-
router.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
362
|
+
// Get reward history
|
|
363
|
+
router.get('/learning/rewards', (req, res) => {
|
|
364
|
+
try {
|
|
365
|
+
const { siteId, agentId } = req.query;
|
|
366
|
+
if (!siteId || !agentId) return fail(res, 400, 'Missing siteId or agentId');
|
|
367
|
+
const history = learning.getRewardHistory(siteId, agentId, parseInt(req.query.limit) || 30);
|
|
368
|
+
ok(res, { rewardHistory: history });
|
|
369
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
197
370
|
});
|
|
198
371
|
|
|
199
|
-
//
|
|
200
|
-
router.
|
|
201
|
-
|
|
202
|
-
|
|
372
|
+
// Reset domain
|
|
373
|
+
router.delete('/learning/domain', (req, res) => {
|
|
374
|
+
try {
|
|
375
|
+
const { siteId, agentId, domain } = req.query;
|
|
376
|
+
if (!siteId || !agentId || !domain) return fail(res, 400, 'Missing siteId, agentId, or domain');
|
|
377
|
+
const result = learning.resetDomain(siteId, agentId, domain);
|
|
378
|
+
ok(res, result);
|
|
379
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
203
380
|
});
|
|
204
381
|
|
|
205
382
|
// Learning stats
|
|
206
|
-
router.get('/learning/stats
|
|
207
|
-
|
|
383
|
+
router.get('/learning/stats', (req, res) => {
|
|
384
|
+
try {
|
|
385
|
+
const { siteId, agentId } = req.query;
|
|
386
|
+
if (!siteId || !agentId) return fail(res, 400, 'Missing siteId or agentId');
|
|
387
|
+
const stats = learning.getStats(siteId, agentId);
|
|
388
|
+
ok(res, { stats });
|
|
389
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
208
390
|
});
|
|
209
391
|
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
392
|
+
// Learning sessions
|
|
393
|
+
router.post('/learning/sessions', (req, res) => {
|
|
394
|
+
try {
|
|
395
|
+
if (!requireBody(req, res, 'siteId', 'agentId')) return;
|
|
396
|
+
const session = learning.startSession(req.body.siteId, req.body.agentId);
|
|
397
|
+
ok(res, session);
|
|
398
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
399
|
+
});
|
|
213
400
|
|
|
214
|
-
|
|
215
|
-
router.post('/symphony/perform', (req, res) => {
|
|
216
|
-
const { siteId, task, taskType, inputData, agentIds } = req.body;
|
|
217
|
-
if (!siteId || !task || !taskType) {
|
|
218
|
-
return res.status(400).json({ error: 'siteId, task, and taskType are required' });
|
|
219
|
-
}
|
|
401
|
+
router.put('/learning/sessions/:id', (req, res) => {
|
|
220
402
|
try {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
403
|
+
if (!requireBody(req, res, 'decisionsMade', 'correctPredictions')) return;
|
|
404
|
+
const result = learning.endSession(req.params.id, req.body.decisionsMade, req.body.correctPredictions);
|
|
405
|
+
ok(res, result);
|
|
406
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
226
407
|
});
|
|
227
408
|
|
|
228
|
-
//
|
|
409
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
410
|
+
// SYMPHONY — Composition Orchestration
|
|
411
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
412
|
+
|
|
413
|
+
// Execute composition
|
|
229
414
|
router.post('/symphony/compose', (req, res) => {
|
|
230
|
-
const { siteId, task, taskType, agentIds } = req.body;
|
|
231
|
-
if (!siteId || !task || !taskType) {
|
|
232
|
-
return res.status(400).json({ error: 'siteId, task, and taskType are required' });
|
|
233
|
-
}
|
|
234
415
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
416
|
+
if (!requireBody(req, res, 'siteId', 'template')) return;
|
|
417
|
+
const { siteId, template, inputData, schema } = req.body;
|
|
418
|
+
const result = symphony.perform(siteId, template, inputData, schema);
|
|
419
|
+
ok(res, result);
|
|
420
|
+
} catch (e) { fail(res, 400, e.message); }
|
|
239
421
|
});
|
|
240
422
|
|
|
241
|
-
//
|
|
242
|
-
router.
|
|
243
|
-
const { phase, input } = req.body;
|
|
244
|
-
if (!phase) return res.status(400).json({ error: 'phase is required' });
|
|
423
|
+
// Get templates
|
|
424
|
+
router.get('/symphony/templates', (req, res) => {
|
|
245
425
|
try {
|
|
246
|
-
res
|
|
247
|
-
} catch (
|
|
248
|
-
res.status(400).json({ error: err.message });
|
|
249
|
-
}
|
|
426
|
+
ok(res, { templates: symphony.getTemplates() });
|
|
427
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
250
428
|
});
|
|
251
429
|
|
|
252
|
-
// Get composition
|
|
253
|
-
router.get('/symphony/:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
430
|
+
// Get composition by ID
|
|
431
|
+
router.get('/symphony/compositions/:id', (req, res) => {
|
|
432
|
+
try {
|
|
433
|
+
const comp = symphony.getComposition(req.params.id);
|
|
434
|
+
if (!comp) return fail(res, 404, 'Composition not found');
|
|
435
|
+
ok(res, { composition: comp });
|
|
436
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
257
437
|
});
|
|
258
438
|
|
|
259
|
-
// Get compositions
|
|
260
|
-
router.get('/symphony/
|
|
261
|
-
|
|
262
|
-
|
|
439
|
+
// Get compositions list
|
|
440
|
+
router.get('/symphony/compositions', (req, res) => {
|
|
441
|
+
try {
|
|
442
|
+
const { siteId, template } = req.query;
|
|
443
|
+
if (!siteId) return fail(res, 400, 'Missing siteId');
|
|
444
|
+
const comps = template
|
|
445
|
+
? symphony.getCompositionsByTemplate(siteId, template, parseInt(req.query.limit) || 10)
|
|
446
|
+
: symphony.getCompositions(siteId, parseInt(req.query.limit) || 20);
|
|
447
|
+
ok(res, { compositions: comps });
|
|
448
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
263
449
|
});
|
|
264
450
|
|
|
265
|
-
// Get
|
|
266
|
-
router.get('/symphony/
|
|
267
|
-
|
|
451
|
+
// Get phase logs
|
|
452
|
+
router.get('/symphony/compositions/:id/phases', (req, res) => {
|
|
453
|
+
try {
|
|
454
|
+
const logs = symphony.getPhaseLogs(req.params.id);
|
|
455
|
+
ok(res, { phases: logs });
|
|
456
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
268
457
|
});
|
|
269
458
|
|
|
270
459
|
// Symphony stats
|
|
271
|
-
router.get('/symphony/stats
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// Overall mesh stats
|
|
280
|
-
router.get('/stats', (req, res) => {
|
|
281
|
-
const meshStats = mesh.getStats();
|
|
282
|
-
res.json(meshStats);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// Dashboard aggregate data
|
|
286
|
-
router.get('/dashboard', (req, res) => {
|
|
287
|
-
const meshStats = mesh.getStats();
|
|
288
|
-
const channels = mesh.getChannels();
|
|
289
|
-
const agents = mesh.getActiveAgents();
|
|
290
|
-
const templates = symphony.getTemplates();
|
|
291
|
-
|
|
292
|
-
res.json({
|
|
293
|
-
mesh: meshStats,
|
|
294
|
-
channels,
|
|
295
|
-
agents,
|
|
296
|
-
symphonyTemplates: templates,
|
|
297
|
-
});
|
|
460
|
+
router.get('/symphony/stats', (req, res) => {
|
|
461
|
+
try {
|
|
462
|
+
const { siteId } = req.query;
|
|
463
|
+
if (!siteId) return fail(res, 400, 'Missing siteId');
|
|
464
|
+
const stats = symphony.getStats(siteId);
|
|
465
|
+
ok(res, { stats });
|
|
466
|
+
} catch (e) { fail(res, 500, e.message); }
|
|
298
467
|
});
|
|
299
468
|
|
|
300
469
|
module.exports = router;
|