web-agent-bridge 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ar.md +18 -0
- package/README.md +18 -0
- package/package.json +1 -1
- package/sdk/index.d.ts +170 -0
- package/sdk/index.js +246 -1
- package/sdk/package.json +1 -1
- package/server/control-plane/index.js +301 -0
- package/server/data-plane/index.js +354 -0
- package/server/index.js +2 -0
- package/server/llm/index.js +404 -0
- package/server/observability/index.js +394 -0
- package/server/protocol/capabilities.js +223 -0
- package/server/protocol/index.js +243 -0
- package/server/protocol/schema.js +584 -0
- package/server/registry/index.js +326 -0
- package/server/routes/runtime.js +725 -0
- package/server/runtime/event-bus.js +210 -0
- package/server/runtime/index.js +233 -0
- package/server/runtime/sandbox.js +266 -0
- package/server/runtime/scheduler.js +395 -0
- package/server/runtime/state-manager.js +188 -0
- package/server/security/index.js +355 -0
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WAB Runtime API Routes
|
|
5
|
+
*
|
|
6
|
+
* Exposes the Agent OS runtime via HTTP:
|
|
7
|
+
* - Task management (submit, status, cancel)
|
|
8
|
+
* - Agent lifecycle (register, authenticate, deploy)
|
|
9
|
+
* - Protocol operations (discover, execute, negotiate)
|
|
10
|
+
* - Observability (metrics, traces, logs)
|
|
11
|
+
* - Registry (commands, sites, templates)
|
|
12
|
+
* - LLM operations (complete, models)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const express = require('express');
|
|
16
|
+
const router = express.Router();
|
|
17
|
+
|
|
18
|
+
// Core modules
|
|
19
|
+
const protocol = require('../protocol');
|
|
20
|
+
const { runtime, bus } = require('../runtime');
|
|
21
|
+
const { logger, tracer, metrics } = require('../observability');
|
|
22
|
+
const { identity, signer, isolation } = require('../security');
|
|
23
|
+
const { agentManager, policyEngine } = require('../control-plane');
|
|
24
|
+
const { executor } = require('../data-plane');
|
|
25
|
+
const { llm } = require('../llm');
|
|
26
|
+
const { commandRegistry, siteRegistry, templateRegistry } = require('../registry');
|
|
27
|
+
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
+
// PROTOCOL ENDPOINTS
|
|
30
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Protocol info & capabilities
|
|
34
|
+
*/
|
|
35
|
+
router.get('/protocol', (req, res) => {
|
|
36
|
+
res.json({
|
|
37
|
+
protocol: protocol.PROTOCOL_NAME,
|
|
38
|
+
version: protocol.PROTOCOL_VERSION,
|
|
39
|
+
commands: protocol.schema.listCommands().map(c => ({
|
|
40
|
+
name: c.name,
|
|
41
|
+
version: c.version,
|
|
42
|
+
category: c.category,
|
|
43
|
+
description: c.description,
|
|
44
|
+
capabilities: c.capabilities,
|
|
45
|
+
})),
|
|
46
|
+
capabilities: Object.keys(protocol.schema.Capabilities),
|
|
47
|
+
permissionLevels: protocol.schema.PermissionLevels,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Process a protocol message
|
|
53
|
+
*/
|
|
54
|
+
router.post('/protocol/message', async (req, res) => {
|
|
55
|
+
const endTimer = metrics.startTimer('api.protocol.message.duration');
|
|
56
|
+
try {
|
|
57
|
+
const msg = req.body;
|
|
58
|
+
if (!msg || !msg.command) {
|
|
59
|
+
return res.status(400).json({ error: 'Invalid protocol message' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create proper protocol request if not already
|
|
63
|
+
const request = msg.protocol === 'wabp' ? msg : protocol.createRequest(msg.command, msg.payload || msg.params || {}, {
|
|
64
|
+
agentId: msg.agentId,
|
|
65
|
+
traceId: msg.traceId,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const response = await protocolHandler.process(request);
|
|
69
|
+
endTimer();
|
|
70
|
+
metrics.increment('api.protocol.messages', 1, { command: msg.command });
|
|
71
|
+
res.json(response);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
endTimer();
|
|
74
|
+
res.status(500).json({ error: err.message });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
79
|
+
// AGENT IDENTITY & AUTH
|
|
80
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Register a new agent
|
|
84
|
+
*/
|
|
85
|
+
router.post('/agents/register', (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
const { name, type, capabilities, publicKey, metadata } = req.body;
|
|
88
|
+
if (!name || !type) return res.status(400).json({ error: 'name and type required' });
|
|
89
|
+
|
|
90
|
+
const result = identity.register(name, type, { capabilities, publicKey, metadata });
|
|
91
|
+
metrics.increment('agents.registered');
|
|
92
|
+
logger.info('Agent registered', { agentId: result.agentId, name, type });
|
|
93
|
+
|
|
94
|
+
res.json({
|
|
95
|
+
agentId: result.agentId,
|
|
96
|
+
apiKey: result.apiKey, // Only returned once!
|
|
97
|
+
message: 'Store your API key securely. It cannot be recovered.',
|
|
98
|
+
});
|
|
99
|
+
} catch (err) {
|
|
100
|
+
res.status(500).json({ error: err.message });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Authenticate agent
|
|
106
|
+
*/
|
|
107
|
+
router.post('/agents/authenticate', (req, res) => {
|
|
108
|
+
const { apiKey } = req.body;
|
|
109
|
+
if (!apiKey) return res.status(400).json({ error: 'apiKey required' });
|
|
110
|
+
|
|
111
|
+
const ip = req.ip || req.connection?.remoteAddress;
|
|
112
|
+
const session = identity.authenticate(apiKey, ip);
|
|
113
|
+
if (!session) {
|
|
114
|
+
metrics.increment('agents.auth.failed');
|
|
115
|
+
return res.status(401).json({ error: 'Invalid API key or agent revoked' });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
metrics.increment('agents.auth.success');
|
|
119
|
+
res.json(session);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get agent info
|
|
124
|
+
*/
|
|
125
|
+
router.get('/agents/:agentId', (req, res) => {
|
|
126
|
+
const agent = identity.getAgent(req.params.agentId);
|
|
127
|
+
if (!agent) return res.status(404).json({ error: 'Agent not found' });
|
|
128
|
+
res.json(agent);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* List agents
|
|
133
|
+
*/
|
|
134
|
+
router.get('/agents', (req, res) => {
|
|
135
|
+
const agents = identity.listAgents({ type: req.query.type, status: req.query.status || 'active' });
|
|
136
|
+
res.json({ agents, total: agents.length });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Negotiate capabilities
|
|
141
|
+
*/
|
|
142
|
+
router.post('/agents/:agentId/capabilities', (req, res) => {
|
|
143
|
+
const { capabilities, siteId, constraints } = req.body;
|
|
144
|
+
if (!capabilities || !Array.isArray(capabilities)) {
|
|
145
|
+
return res.status(400).json({ error: 'capabilities array required' });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = protocol.negotiator.negotiate(req.params.agentId, capabilities, siteId, constraints || {});
|
|
149
|
+
res.json(result);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Revoke agent
|
|
154
|
+
*/
|
|
155
|
+
router.delete('/agents/:agentId', (req, res) => {
|
|
156
|
+
identity.revoke(req.params.agentId);
|
|
157
|
+
protocol.negotiator.revokeAgent(req.params.agentId);
|
|
158
|
+
logger.info('Agent revoked', { agentId: req.params.agentId });
|
|
159
|
+
res.json({ success: true });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
163
|
+
// TASK MANAGEMENT (RUNTIME)
|
|
164
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Submit a task
|
|
168
|
+
*/
|
|
169
|
+
router.post('/tasks', (req, res) => {
|
|
170
|
+
try {
|
|
171
|
+
const result = runtime.submitTask(req.body);
|
|
172
|
+
metrics.increment('tasks.submitted', 1, { type: req.body.type });
|
|
173
|
+
res.json(result);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
res.status(400).json({ error: err.message });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get task status
|
|
181
|
+
*/
|
|
182
|
+
router.get('/tasks/:taskId', (req, res) => {
|
|
183
|
+
const task = runtime.scheduler.getTask(req.params.taskId);
|
|
184
|
+
if (!task) return res.status(404).json({ error: 'Task not found' });
|
|
185
|
+
res.json(task);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* List tasks
|
|
190
|
+
*/
|
|
191
|
+
router.get('/tasks', (req, res) => {
|
|
192
|
+
const tasks = runtime.scheduler.listTasks(req.query.state, parseInt(req.query.limit) || 50);
|
|
193
|
+
res.json({ tasks, total: tasks.length });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Cancel a task
|
|
198
|
+
*/
|
|
199
|
+
router.delete('/tasks/:taskId', (req, res) => {
|
|
200
|
+
const success = runtime.scheduler.cancel(req.params.taskId);
|
|
201
|
+
res.json({ success });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Pause a task
|
|
206
|
+
*/
|
|
207
|
+
router.post('/tasks/:taskId/pause', (req, res) => {
|
|
208
|
+
const success = runtime.scheduler.pause(req.params.taskId);
|
|
209
|
+
res.json({ success });
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Resume a task
|
|
214
|
+
*/
|
|
215
|
+
router.post('/tasks/:taskId/resume', (req, res) => {
|
|
216
|
+
const success = runtime.scheduler.resume(req.params.taskId);
|
|
217
|
+
res.json({ success });
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
221
|
+
// EXECUTION (DATA PLANE)
|
|
222
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Execute a semantic action
|
|
226
|
+
*/
|
|
227
|
+
router.post('/execute', async (req, res) => {
|
|
228
|
+
try {
|
|
229
|
+
const result = await executor.execute(req.body);
|
|
230
|
+
res.json(result);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
res.status(500).json({ error: err.message });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Execute semantic action (domain.action style)
|
|
238
|
+
*/
|
|
239
|
+
router.post('/execute/semantic', async (req, res) => {
|
|
240
|
+
try {
|
|
241
|
+
const { domain, action, params, siteId, agentId, siteDomain } = req.body;
|
|
242
|
+
if (!domain || !action) return res.status(400).json({ error: 'domain and action required' });
|
|
243
|
+
|
|
244
|
+
const result = await executor.execute({
|
|
245
|
+
type: 'semantic',
|
|
246
|
+
domain,
|
|
247
|
+
action,
|
|
248
|
+
params: params || {},
|
|
249
|
+
siteId,
|
|
250
|
+
agentId,
|
|
251
|
+
siteDomain,
|
|
252
|
+
});
|
|
253
|
+
res.json(result);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
res.status(500).json({ error: err.message });
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Execute a pipeline
|
|
261
|
+
*/
|
|
262
|
+
router.post('/execute/pipeline', async (req, res) => {
|
|
263
|
+
try {
|
|
264
|
+
const result = await executor.execute({ ...req.body, type: 'pipeline' });
|
|
265
|
+
res.json(result);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
res.status(500).json({ error: err.message });
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Resolve a semantic action (without executing)
|
|
273
|
+
*/
|
|
274
|
+
router.get('/execute/resolve', (req, res) => {
|
|
275
|
+
const { domain, action, siteDomain } = req.query;
|
|
276
|
+
if (!domain || !action) return res.status(400).json({ error: 'domain and action required' });
|
|
277
|
+
const impl = executor.resolver.resolve(siteDomain || '*', `${domain}.${action}`);
|
|
278
|
+
if (!impl) return res.status(404).json({ error: 'No implementation found' });
|
|
279
|
+
res.json(impl);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
283
|
+
// CONTROL PLANE
|
|
284
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Deploy an agent
|
|
288
|
+
*/
|
|
289
|
+
router.post('/deployments', (req, res) => {
|
|
290
|
+
try {
|
|
291
|
+
const { agentId, config } = req.body;
|
|
292
|
+
if (!agentId) return res.status(400).json({ error: 'agentId required' });
|
|
293
|
+
const deployment = agentManager.deploy(agentId, config || {});
|
|
294
|
+
res.json(deployment);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
res.status(400).json({ error: err.message });
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* List deployments
|
|
302
|
+
*/
|
|
303
|
+
router.get('/deployments', (req, res) => {
|
|
304
|
+
const deployments = agentManager.listDeployments({
|
|
305
|
+
status: req.query.status,
|
|
306
|
+
agentId: req.query.agentId,
|
|
307
|
+
});
|
|
308
|
+
res.json({ deployments, total: deployments.length });
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Create a policy
|
|
313
|
+
*/
|
|
314
|
+
router.post('/policies', (req, res) => {
|
|
315
|
+
try {
|
|
316
|
+
const policy = policyEngine.createPolicy(req.body);
|
|
317
|
+
res.json(policy);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
res.status(400).json({ error: err.message });
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Bind policy to entity
|
|
325
|
+
*/
|
|
326
|
+
router.post('/policies/:policyId/bind', (req, res) => {
|
|
327
|
+
const { entityId } = req.body;
|
|
328
|
+
if (!entityId) return res.status(400).json({ error: 'entityId required' });
|
|
329
|
+
policyEngine.bind(entityId, req.params.policyId);
|
|
330
|
+
res.json({ success: true });
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Evaluate policies
|
|
335
|
+
*/
|
|
336
|
+
router.post('/policies/evaluate', (req, res) => {
|
|
337
|
+
const { entityId, action, context } = req.body;
|
|
338
|
+
if (!entityId || !action) return res.status(400).json({ error: 'entityId and action required' });
|
|
339
|
+
const result = policyEngine.evaluate(entityId, action, context || {});
|
|
340
|
+
res.json(result);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* List policies
|
|
345
|
+
*/
|
|
346
|
+
router.get('/policies', (req, res) => {
|
|
347
|
+
const policies = policyEngine.listPolicies(req.query.entityId);
|
|
348
|
+
res.json({ policies, total: policies.length });
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
352
|
+
// SITE ISOLATION
|
|
353
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Configure site isolation
|
|
357
|
+
*/
|
|
358
|
+
router.post('/isolation/:siteId', (req, res) => {
|
|
359
|
+
isolation.configure(req.params.siteId, req.body);
|
|
360
|
+
res.json({ success: true });
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get site isolation config
|
|
365
|
+
*/
|
|
366
|
+
router.get('/isolation/:siteId', (req, res) => {
|
|
367
|
+
const config = isolation.getConfig(req.params.siteId);
|
|
368
|
+
if (!config) return res.status(404).json({ error: 'No isolation config' });
|
|
369
|
+
res.json(config);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
373
|
+
// OBSERVABILITY
|
|
374
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get metrics snapshot
|
|
378
|
+
*/
|
|
379
|
+
router.get('/observability/metrics', (req, res) => {
|
|
380
|
+
res.json(metrics.snapshot());
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get specific metric
|
|
385
|
+
*/
|
|
386
|
+
router.get('/observability/metrics/:name', (req, res) => {
|
|
387
|
+
const h = metrics.getHistogram(req.params.name);
|
|
388
|
+
if (h) return res.json({ type: 'histogram', name: req.params.name, ...h });
|
|
389
|
+
|
|
390
|
+
const c = metrics.getCounter(req.params.name);
|
|
391
|
+
if (c) return res.json({ type: 'counter', name: req.params.name, value: c });
|
|
392
|
+
|
|
393
|
+
const g = metrics.getGauge(req.params.name);
|
|
394
|
+
if (g) return res.json({ type: 'gauge', name: req.params.name, value: g });
|
|
395
|
+
|
|
396
|
+
res.status(404).json({ error: 'Metric not found' });
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* List traces
|
|
401
|
+
*/
|
|
402
|
+
router.get('/observability/traces', (req, res) => {
|
|
403
|
+
const traces = tracer.listTraces(
|
|
404
|
+
parseInt(req.query.limit) || 50,
|
|
405
|
+
{ status: req.query.status, name: req.query.name, since: parseInt(req.query.since) || undefined }
|
|
406
|
+
);
|
|
407
|
+
res.json({ traces, total: traces.length });
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Get trace details
|
|
412
|
+
*/
|
|
413
|
+
router.get('/observability/traces/:traceId', (req, res) => {
|
|
414
|
+
const trace = tracer.getTrace(req.params.traceId);
|
|
415
|
+
if (!trace) return res.status(404).json({ error: 'Trace not found' });
|
|
416
|
+
res.json(trace);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Query logs
|
|
421
|
+
*/
|
|
422
|
+
router.get('/observability/logs', (req, res) => {
|
|
423
|
+
const logs = logger.query({
|
|
424
|
+
level: req.query.level,
|
|
425
|
+
traceId: req.query.traceId,
|
|
426
|
+
agentId: req.query.agentId,
|
|
427
|
+
since: parseInt(req.query.since) || undefined,
|
|
428
|
+
message: req.query.message,
|
|
429
|
+
}, parseInt(req.query.limit) || 100);
|
|
430
|
+
res.json({ logs, total: logs.length });
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Runtime health
|
|
435
|
+
*/
|
|
436
|
+
router.get('/observability/health', (req, res) => {
|
|
437
|
+
const health = runtime.getHealth();
|
|
438
|
+
health.identity = identity.getStats();
|
|
439
|
+
health.registry = {
|
|
440
|
+
commands: commandRegistry.getStats(),
|
|
441
|
+
sites: siteRegistry.getStats(),
|
|
442
|
+
templates: templateRegistry.getStats(),
|
|
443
|
+
};
|
|
444
|
+
health.executor = executor.getStats();
|
|
445
|
+
health.llm = llm.getStatus();
|
|
446
|
+
res.json(health);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
450
|
+
// REGISTRY
|
|
451
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Register a command
|
|
455
|
+
*/
|
|
456
|
+
router.post('/registry/commands', (req, res) => {
|
|
457
|
+
try {
|
|
458
|
+
const { siteId, ...command } = req.body;
|
|
459
|
+
if (!siteId) return res.status(400).json({ error: 'siteId required' });
|
|
460
|
+
const entry = commandRegistry.register(siteId, command);
|
|
461
|
+
res.json(entry);
|
|
462
|
+
} catch (err) {
|
|
463
|
+
res.status(400).json({ error: err.message });
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Search commands
|
|
469
|
+
*/
|
|
470
|
+
router.get('/registry/commands', (req, res) => {
|
|
471
|
+
const results = commandRegistry.search({
|
|
472
|
+
siteId: req.query.siteId,
|
|
473
|
+
category: req.query.category,
|
|
474
|
+
name: req.query.name,
|
|
475
|
+
tag: req.query.tag,
|
|
476
|
+
capability: req.query.capability,
|
|
477
|
+
limit: parseInt(req.query.limit) || 50,
|
|
478
|
+
});
|
|
479
|
+
res.json({ commands: results, total: results.length });
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Register a site
|
|
484
|
+
*/
|
|
485
|
+
router.post('/registry/sites', (req, res) => {
|
|
486
|
+
const { domain, ...info } = req.body;
|
|
487
|
+
if (!domain) return res.status(400).json({ error: 'domain required' });
|
|
488
|
+
const entry = siteRegistry.register(domain, info);
|
|
489
|
+
res.json(entry);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Search sites
|
|
494
|
+
*/
|
|
495
|
+
router.get('/registry/sites', (req, res) => {
|
|
496
|
+
const results = siteRegistry.search({
|
|
497
|
+
tier: req.query.tier,
|
|
498
|
+
capability: req.query.capability,
|
|
499
|
+
name: req.query.name,
|
|
500
|
+
verified: req.query.verified === 'true' ? true : undefined,
|
|
501
|
+
limit: parseInt(req.query.limit) || 50,
|
|
502
|
+
});
|
|
503
|
+
res.json({ sites: results, total: results.length });
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Get site info
|
|
508
|
+
*/
|
|
509
|
+
router.get('/registry/sites/:domain', (req, res) => {
|
|
510
|
+
const site = siteRegistry.getSite(req.params.domain);
|
|
511
|
+
if (!site) return res.status(404).json({ error: 'Site not found' });
|
|
512
|
+
res.json(site);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Register a template
|
|
517
|
+
*/
|
|
518
|
+
router.post('/registry/templates', (req, res) => {
|
|
519
|
+
try {
|
|
520
|
+
const entry = templateRegistry.register(req.body);
|
|
521
|
+
res.json(entry);
|
|
522
|
+
} catch (err) {
|
|
523
|
+
res.status(400).json({ error: err.message });
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Search templates
|
|
529
|
+
*/
|
|
530
|
+
router.get('/registry/templates', (req, res) => {
|
|
531
|
+
const results = templateRegistry.search({
|
|
532
|
+
category: req.query.category,
|
|
533
|
+
name: req.query.name,
|
|
534
|
+
tag: req.query.tag,
|
|
535
|
+
limit: parseInt(req.query.limit) || 50,
|
|
536
|
+
});
|
|
537
|
+
res.json({ templates: results, total: results.length });
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Get template
|
|
542
|
+
*/
|
|
543
|
+
router.get('/registry/templates/:templateId', (req, res) => {
|
|
544
|
+
const tmpl = templateRegistry.getTemplate(req.params.templateId);
|
|
545
|
+
if (!tmpl) return res.status(404).json({ error: 'Template not found' });
|
|
546
|
+
templateRegistry.trackDownload(req.params.templateId);
|
|
547
|
+
res.json(tmpl);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
551
|
+
// LLM
|
|
552
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* LLM completion
|
|
556
|
+
*/
|
|
557
|
+
router.post('/llm/complete', async (req, res) => {
|
|
558
|
+
try {
|
|
559
|
+
const result = await llm.complete(req.body.prompt, req.body.options || req.body);
|
|
560
|
+
metrics.increment('llm.api.requests');
|
|
561
|
+
res.json(result);
|
|
562
|
+
} catch (err) {
|
|
563
|
+
res.status(500).json({ error: err.message });
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* LLM models
|
|
569
|
+
*/
|
|
570
|
+
router.get('/llm/models', (req, res) => {
|
|
571
|
+
res.json({ models: llm.listModels() });
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* LLM status
|
|
576
|
+
*/
|
|
577
|
+
router.get('/llm/status', (req, res) => {
|
|
578
|
+
res.json(llm.getStatus());
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* LLM embeddings
|
|
583
|
+
*/
|
|
584
|
+
router.post('/llm/embed', async (req, res) => {
|
|
585
|
+
try {
|
|
586
|
+
const result = await llm.embed(req.body.text, req.body.options || {});
|
|
587
|
+
res.json(result);
|
|
588
|
+
} catch (err) {
|
|
589
|
+
res.status(500).json({ error: err.message });
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
594
|
+
// COMMAND SIGNING
|
|
595
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Sign a command
|
|
599
|
+
*/
|
|
600
|
+
router.post('/sign', (req, res) => {
|
|
601
|
+
const { payload, agentId } = req.body;
|
|
602
|
+
if (!payload || !agentId) return res.status(400).json({ error: 'payload and agentId required' });
|
|
603
|
+
const signature = signer.sign(payload, agentId);
|
|
604
|
+
res.json(signature);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Verify a signed command
|
|
609
|
+
*/
|
|
610
|
+
router.post('/verify', (req, res) => {
|
|
611
|
+
const { payload, agentId, nonce, timestamp, signature } = req.body;
|
|
612
|
+
const result = signer.verify(payload, agentId, nonce, timestamp, signature);
|
|
613
|
+
res.json(result);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
617
|
+
// EVENT STREAM (SSE)
|
|
618
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Server-Sent Events for real-time updates
|
|
622
|
+
*/
|
|
623
|
+
router.get('/events', (req, res) => {
|
|
624
|
+
res.writeHead(200, {
|
|
625
|
+
'Content-Type': 'text/event-stream',
|
|
626
|
+
'Cache-Control': 'no-cache',
|
|
627
|
+
'Connection': 'keep-alive',
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
const filter = req.query.filter; // e.g., 'task.*' or 'agent.*'
|
|
631
|
+
|
|
632
|
+
const subId = bus.on(filter || '*', (data, meta) => {
|
|
633
|
+
res.write(`event: ${meta.event || 'message'}\n`);
|
|
634
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
req.on('close', () => {
|
|
638
|
+
bus.off(subId);
|
|
639
|
+
res.end();
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
644
|
+
// Protocol Handler Setup
|
|
645
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
646
|
+
|
|
647
|
+
const protocolHandler = new protocol.ProtocolHandler();
|
|
648
|
+
|
|
649
|
+
// Wire protocol commands to runtime
|
|
650
|
+
protocolHandler.handle('wab.discover', async (payload) => {
|
|
651
|
+
const commands = commandRegistry.search({ siteId: payload.siteId, category: payload.category });
|
|
652
|
+
return {
|
|
653
|
+
actions: commands.map(c => ({
|
|
654
|
+
name: c.name,
|
|
655
|
+
category: c.category,
|
|
656
|
+
params: c.input,
|
|
657
|
+
capabilities: c.capabilities,
|
|
658
|
+
})),
|
|
659
|
+
meta: {
|
|
660
|
+
protocol: protocol.PROTOCOL_VERSION,
|
|
661
|
+
timestamp: Date.now(),
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
protocolHandler.handle('wab.execute', async (payload, ctx) => {
|
|
667
|
+
const result = await executor.execute({
|
|
668
|
+
type: 'semantic',
|
|
669
|
+
domain: payload.domain || 'general',
|
|
670
|
+
action: payload.action,
|
|
671
|
+
params: payload.params,
|
|
672
|
+
agentId: ctx.message.agentId,
|
|
673
|
+
});
|
|
674
|
+
return result;
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
protocolHandler.handle('wab.task.submit', async (payload) => {
|
|
678
|
+
return runtime.submitTask(payload);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
protocolHandler.handle('wab.task.status', async (payload) => {
|
|
682
|
+
return runtime.scheduler.getTask(payload.taskId);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
protocolHandler.handle('wab.agent.register', async (payload) => {
|
|
686
|
+
const result = identity.register(payload.name, payload.type, {
|
|
687
|
+
capabilities: payload.capabilities,
|
|
688
|
+
publicKey: payload.publicKey,
|
|
689
|
+
metadata: payload.metadata,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// Negotiate requested capabilities
|
|
693
|
+
const negotiation = protocol.negotiator.negotiate(
|
|
694
|
+
result.agentId,
|
|
695
|
+
payload.capabilities,
|
|
696
|
+
payload.siteId || '*'
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
agentId: result.agentId,
|
|
701
|
+
token: result.apiKey,
|
|
702
|
+
grantedCapabilities: negotiation.granted,
|
|
703
|
+
expiresAt: negotiation.grant?.constraints?.expiresAt || Date.now() + 3600_000,
|
|
704
|
+
};
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
protocolHandler.handle('wab.ai.infer', async (payload) => {
|
|
708
|
+
return llm.complete(payload.prompt, {
|
|
709
|
+
model: payload.model,
|
|
710
|
+
provider: payload.provider,
|
|
711
|
+
...payload.options,
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
protocolHandler.handle('wab.commerce.compare', async (payload) => {
|
|
716
|
+
return executor.execute({
|
|
717
|
+
type: 'parallel',
|
|
718
|
+
tasks: (payload.sources || []).map(url => ({
|
|
719
|
+
type: 'extraction',
|
|
720
|
+
params: { url, query: payload.query },
|
|
721
|
+
})),
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
module.exports = router;
|