omniwire 2.4.0 → 2.5.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/README.md +99 -42
- package/dist/mcp/server.js +211 -5
- package/dist/mcp/server.js.map +1 -1
- package/dist/nodes/manager.js +67 -122
- package/dist/nodes/manager.js.map +1 -1
- package/dist/nodes/transfer.d.ts +1 -1
- package/dist/nodes/transfer.js +59 -51
- package/dist/nodes/transfer.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:
|
|
4
|
-
<source media="(prefers-color-scheme: light)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:
|
|
5
|
-
<img alt="OmniWire" src="https://capsule-render.vercel.app/api?type=waving&color=0:
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:0D1117,25:0D1B2A,50:162B44,75:1A3A5C,100:59C2FF&height=200§ion=header&text=⚡%20OmniWire&fontSize=72&fontColor=59C2FF&animation=twinkling&fontAlignY=35&desc=Infrastructure%20Layer%20for%20AI%20Agent%20Swarms&descSize=18&descColor=8B949E&descAlignY=60" />
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:FFFFFF,25:F0F4F8,50:D8E2EE,75:B8CCE0,100:59C2FF&height=200§ion=header&text=⚡%20OmniWire&fontSize=72&fontColor=0D1117&animation=twinkling&fontAlignY=35&desc=Infrastructure%20Layer%20for%20AI%20Agent%20Swarms&descSize=18&descColor=586069&descAlignY=60" />
|
|
5
|
+
<img alt="OmniWire" src="https://capsule-render.vercel.app/api?type=waving&color=0:0D1117,25:0D1B2A,50:162B44,75:1A3A5C,100:59C2FF&height=200§ion=header&text=⚡%20OmniWire&fontSize=72&fontColor=59C2FF&animation=twinkling&fontAlignY=35&desc=Infrastructure%20Layer%20for%20AI%20Agent%20Swarms&descSize=18&descColor=8B949E&descAlignY=60" />
|
|
6
6
|
</picture>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://www.npmjs.com/package/omniwire"><img src="https://img.shields.io/npm/v/omniwire?style=for-the-badge&logo=npm&color=CB3837&labelColor=
|
|
11
|
-
<img src="https://img.shields.io/badge/
|
|
12
|
-
<img src="https://img.shields.io/badge/A2A-
|
|
13
|
-
<img src="https://img.shields.io/badge/
|
|
14
|
-
<img src="https://img.shields.io/badge/
|
|
15
|
-
<a href="LICENSE"><img src="https://img.shields.io/badge/
|
|
10
|
+
<a href="https://www.npmjs.com/package/omniwire"><img src="https://img.shields.io/npm/v/omniwire?style=for-the-badge&logo=npm&color=CB3837&labelColor=0D1117" alt="npm" /></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/MCP_Tools-54-59C2FF?style=for-the-badge&logo=lightning&logoColor=59C2FF&labelColor=0D1117" alt="tools" />
|
|
12
|
+
<img src="https://img.shields.io/badge/A2A-Protocol-00C853?style=for-the-badge&logo=link&logoColor=00C853&labelColor=0D1117" alt="A2A" />
|
|
13
|
+
<img src="https://img.shields.io/badge/Latency-~80ms-FF6D00?style=for-the-badge&logo=bolt&logoColor=FF6D00&labelColor=0D1117" alt="latency" />
|
|
14
|
+
<img src="https://img.shields.io/badge/LZ4-Transfer-CC93E6?style=for-the-badge&logo=files&logoColor=CC93E6&labelColor=0D1117" alt="lz4" />
|
|
15
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-GPL--3.0-8B949E?style=for-the-badge&logo=gnu&logoColor=8B949E&labelColor=0D1117" alt="license" /></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
<br/>
|
|
19
|
+
|
|
20
|
+
<div align="center">
|
|
21
|
+
|
|
22
|
+
**54 MCP tools** · **Agent-to-Agent messaging** · **Distributed task queues** · **Capability routing**
|
|
23
|
+
|
|
24
|
+
AES-128-GCM SSH2 · LZ4 transfers · Circuit breakers · Multi-path failover · Pipeline DAGs · Blackboard architecture
|
|
25
|
+
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<br/>
|
|
29
|
+
|
|
30
|
+
<div align="center">
|
|
31
|
+
<table>
|
|
32
|
+
<tr>
|
|
33
|
+
<td align="center">⚡ <b>~80ms</b><br/><sub>exec latency</sub></td>
|
|
34
|
+
<td align="center">🔧 <b>54</b><br/><sub>MCP tools</sub></td>
|
|
35
|
+
<td align="center">🔗 <b>A2A</b><br/><sub>agent protocol</sub></td>
|
|
36
|
+
<td align="center">📦 <b>LZ4</b><br/><sub>fast transfer</sub></td>
|
|
37
|
+
<td align="center">🛡️ <b>AES-128</b><br/><sub>GCM cipher</sub></td>
|
|
38
|
+
<td align="center">🔄 <b>300ms</b><br/><sub>reconnect</sub></td>
|
|
39
|
+
</tr>
|
|
40
|
+
</table>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
> **v2.5** — AES-128-GCM cipher, 2s keepalive, LZ4 compression, SFTP-first reads, agent registry, blackboard, task queues, capability routing. [Changelog →](#changelog)
|
|
44
|
+
|
|
45
|
+
<br/>
|
|
23
46
|
|
|
24
47
|
---
|
|
25
48
|
|
|
@@ -71,7 +94,7 @@ graph TB
|
|
|
71
94
|
direction TB
|
|
72
95
|
MCP["MCP Protocol Layer<br/>stdio | SSE | REST"]
|
|
73
96
|
|
|
74
|
-
subgraph tools["
|
|
97
|
+
subgraph tools["54 Tools"]
|
|
75
98
|
direction LR
|
|
76
99
|
EXEC["Execution<br/>exec run batch<br/>broadcast pipeline"]
|
|
77
100
|
AGENT["Agentic<br/>store watch task<br/>a2a events locks"]
|
|
@@ -105,13 +128,13 @@ graph TB
|
|
|
105
128
|
POOL -->|"local exec"| N4
|
|
106
129
|
CSYNC --> DB
|
|
107
130
|
|
|
108
|
-
style omniwire fill:#
|
|
109
|
-
style clients fill:#
|
|
110
|
-
style mesh fill:#
|
|
111
|
-
style tools fill:#
|
|
112
|
-
style engine fill:#
|
|
113
|
-
style MCP fill:#
|
|
114
|
-
style DB fill:#
|
|
131
|
+
style omniwire fill:#0D1117,stroke:#59C2FF,stroke-width:2px,color:#C6D0E1
|
|
132
|
+
style clients fill:#161B22,stroke:#91B362,stroke-width:1px,color:#C6D0E1
|
|
133
|
+
style mesh fill:#161B22,stroke:#E6B450,stroke-width:1px,color:#C6D0E1
|
|
134
|
+
style tools fill:#0D1117,stroke:#59C2FF,stroke-width:1px,color:#C6D0E1
|
|
135
|
+
style engine fill:#0D1117,stroke:#CC93E6,stroke-width:1px,color:#C6D0E1
|
|
136
|
+
style MCP fill:#162B44,stroke:#59C2FF,color:#59C2FF
|
|
137
|
+
style DB fill:#162B44,stroke:#CC93E6,color:#CC93E6
|
|
115
138
|
```
|
|
116
139
|
|
|
117
140
|
---
|
|
@@ -207,9 +230,11 @@ Nodes --push--> PostgreSQL (cyberbase)
|
|
|
207
230
|
|
|
208
231
|
---
|
|
209
232
|
|
|
210
|
-
## All
|
|
233
|
+
## All 54 Tools
|
|
234
|
+
|
|
235
|
+
> **Every tool** supports `background: true` — returns a task ID immediately. Poll with `omniwire_bg`.
|
|
211
236
|
|
|
212
|
-
### Execution (
|
|
237
|
+
### Execution (6)
|
|
213
238
|
|
|
214
239
|
| Tool | Description |
|
|
215
240
|
|------|-------------|
|
|
@@ -218,8 +243,9 @@ Nodes --push--> PostgreSQL (cyberbase)
|
|
|
218
243
|
| `omniwire_batch` | N commands in 1 call. Chaining with `{{prev}}`, `abort_on_fail`, parallel or sequential. |
|
|
219
244
|
| `omniwire_broadcast` | Execute on all nodes simultaneously. JSON format support. |
|
|
220
245
|
| `omniwire_pipeline` | Multi-step DAG. `{{prev}}`/`{{stepN}}` interpolation, per-step error handling, cross-node. |
|
|
246
|
+
| `omniwire_bg` | List, poll, or retrieve results from background tasks. |
|
|
221
247
|
|
|
222
|
-
### Agentic / A2A (
|
|
248
|
+
### Agentic / A2A (13)
|
|
223
249
|
|
|
224
250
|
| Tool | Description |
|
|
225
251
|
|------|-------------|
|
|
@@ -231,6 +257,10 @@ Nodes --push--> PostgreSQL (cyberbase)
|
|
|
231
257
|
| `omniwire_semaphore` | Distributed locking. Atomic acquire/release to prevent race conditions. |
|
|
232
258
|
| `omniwire_event` | Pub/sub events. Emit/poll timestamped events per topic. ACP/A2A/ACPX compatible. |
|
|
233
259
|
| `omniwire_workflow` | Define and run reusable named workflows (DAGs). Stored on disk, triggered by any agent. |
|
|
260
|
+
| `omniwire_agent_registry` | Register/discover agents by capabilities. Dynamic A2A routing. Heartbeat. |
|
|
261
|
+
| `omniwire_blackboard` | Shared blackboard for agent swarms. Post findings, read, search across topics. |
|
|
262
|
+
| `omniwire_task_queue` | Distributed task queue. Enqueue/dequeue with priorities. Complete/fail reporting. |
|
|
263
|
+
| `omniwire_capability` | Query node capabilities (tools, runtimes, GPU). Intelligent task routing. |
|
|
234
264
|
|
|
235
265
|
### Files & Transfer (6)
|
|
236
266
|
|
|
@@ -297,16 +327,30 @@ Nodes --push--> PostgreSQL (cyberbase)
|
|
|
297
327
|
|
|
298
328
|
## Performance
|
|
299
329
|
|
|
300
|
-
| Operation | Latency |
|
|
301
|
-
|
|
302
|
-
| Command exec |
|
|
303
|
-
| Mesh status
|
|
304
|
-
| File read (<1MB) |
|
|
305
|
-
| Transfer (10MB) |
|
|
306
|
-
|
|
|
307
|
-
|
|
|
308
|
-
|
|
|
309
|
-
|
|
|
330
|
+
| Operation | Latency | v2.5 Optimization |
|
|
331
|
+
|-----------|---------|-------------------|
|
|
332
|
+
| **Command exec** | **~80ms** | AES-128-GCM cipher, persistent SSH2 channel, zero-fork `:` ping |
|
|
333
|
+
| **Mesh status** | **~100ms** | Parallel probes, 5s cache, single `/proc` read (no pipes) |
|
|
334
|
+
| **File read (<1MB)** | **~60ms** | SFTP-first path (skips `cat` shell fork) |
|
|
335
|
+
| **Transfer (10MB)** | **~120ms** | LZ4 compression (10x faster than gzip), 50ms bind delay |
|
|
336
|
+
| **Transfer (1GB)** | **~8s** | aria2c 16-connection parallel, 150ms server startup |
|
|
337
|
+
| **Pipeline (5 steps)** | **~400ms** | `{{prev}}` interpolation, no extra tool calls |
|
|
338
|
+
| **Health check (all)** | **~90ms** | Parallel Promise.allSettled, structured JSON |
|
|
339
|
+
| **A2A message** | **~85ms** | File-append queue, atomic dequeue |
|
|
340
|
+
| **Config push** | **~150ms** | Parallel deploy + Obsidian mirror |
|
|
341
|
+
| **Reconnect** | **~300ms** | 300ms initial delay (was 500ms), 2s keepalive detection |
|
|
342
|
+
|
|
343
|
+
**Optimizations in v2.5:**
|
|
344
|
+
- **Cipher**: AES-128-GCM (AES-NI accelerated) preferred over default negotiation
|
|
345
|
+
- **Key exchange**: curve25519-sha256 preferred (fastest modern KEX)
|
|
346
|
+
- **Keepalive**: 2s interval, 2 retries = 4s dead detection (was 6s)
|
|
347
|
+
- **Port finder**: `shuf` (pure bash) replaces `python3 -c socket` (saves ~30ms per transfer)
|
|
348
|
+
- **Compression**: LZ4-1 for transfers (10x faster than gzip, ~same ratio for mixed data)
|
|
349
|
+
- **Buffer**: Array push + join replaces string concatenation (O(n) vs O(n^2) for large outputs)
|
|
350
|
+
- **Status**: Single `/proc` read replaces multiple piped commands
|
|
351
|
+
- **Health ping**: `:` builtin replaces `true` (no hash lookup, no fork)
|
|
352
|
+
- **Reads**: SFTP subsystem tried first, falls back to `cat` only on failure
|
|
353
|
+
- **Circuit breaker**: 15s recovery (was 20s), 10s reconnect cap (was 15s)
|
|
310
354
|
|
|
311
355
|
---
|
|
312
356
|
|
|
@@ -357,6 +401,17 @@ Create `~/.omniwire/mesh.json`:
|
|
|
357
401
|
|
|
358
402
|
## Changelog
|
|
359
403
|
|
|
404
|
+
<details>
|
|
405
|
+
<summary><b>v2.5.0 -- Performance Overhaul, A2A Protocol Expansion</b></summary>
|
|
406
|
+
|
|
407
|
+
**Performance**: AES-128-GCM cipher, curve25519-sha256 KEX, 2s keepalive, LZ4 transfers (10x faster), `shuf` port finder (-30ms), SFTP-first reads, array buffer concat, `/proc` single-read status, `:` builtin health ping, 300ms reconnect start, 15s circuit breaker.
|
|
408
|
+
|
|
409
|
+
**4 new A2A tools** (49 -> 53): agent_registry (capability discovery), blackboard (swarm collaboration), task_queue (distributed work), capability (node routing).
|
|
410
|
+
|
|
411
|
+
**Connectivity**: Always-on 2s keepalive with 4s dead detection. 5s connect timeout. 10s reconnect cap. 15s circuit recovery.
|
|
412
|
+
|
|
413
|
+
</details>
|
|
414
|
+
|
|
360
415
|
<details>
|
|
361
416
|
<summary><b>v2.4.0 -- Agentic Loop, A2A, Multi-Agent Orchestration</b></summary>
|
|
362
417
|
|
|
@@ -396,7 +451,7 @@ Multi-path SSH (WireGuard/Tailscale/Public), SSH key caching, CyberBase integrat
|
|
|
396
451
|
```
|
|
397
452
|
omniwire/
|
|
398
453
|
src/
|
|
399
|
-
mcp/ MCP server (
|
|
454
|
+
mcp/ MCP server (54 tools, 3 transports)
|
|
400
455
|
nodes/ SSH2 pool, transfer engine, PTY, tunnels
|
|
401
456
|
sync/ CyberSync + CyberBase (PostgreSQL, Obsidian, encryption)
|
|
402
457
|
protocol/ Mesh config, types, path parsing
|
|
@@ -413,14 +468,16 @@ omniwire/
|
|
|
413
468
|
|
|
414
469
|
---
|
|
415
470
|
|
|
416
|
-
<
|
|
417
|
-
|
|
418
|
-
|
|
471
|
+
<br/>
|
|
472
|
+
|
|
473
|
+
<div align="center">
|
|
474
|
+
<sub>Built for the machines that build for us.</sub>
|
|
475
|
+
</div>
|
|
419
476
|
|
|
420
477
|
<p align="center">
|
|
421
478
|
<picture>
|
|
422
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:
|
|
423
|
-
<source media="(prefers-color-scheme: light)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:
|
|
424
|
-
<img alt="footer" src="https://capsule-render.vercel.app/api?type=waving&color=0:
|
|
479
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:59C2FF,50:162B44,100:0D1117&height=120§ion=footer" />
|
|
480
|
+
<source media="(prefers-color-scheme: light)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:59C2FF,50:B8CCE0,100:FFFFFF&height=120§ion=footer" />
|
|
481
|
+
<img alt="footer" src="https://capsule-render.vercel.app/api?type=waving&color=0:59C2FF,50:162B44,100:0D1117&height=120§ion=footer" />
|
|
425
482
|
</picture>
|
|
426
483
|
</p>
|
package/dist/mcp/server.js
CHANGED
|
@@ -63,15 +63,74 @@ function multiResultJson(results) {
|
|
|
63
63
|
}
|
|
64
64
|
// -- Agentic state -- shared across tool calls in the same MCP session --------
|
|
65
65
|
const resultStore = new Map(); // key -> value store for chaining
|
|
66
|
+
const bgTasks = new Map();
|
|
67
|
+
function bgId() { return `bg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`; }
|
|
68
|
+
function dispatchBg(node, label, fn) {
|
|
69
|
+
const id = bgId();
|
|
70
|
+
const task = { id, node, label, startedAt: Date.now(), promise: fn() };
|
|
71
|
+
task.promise.then((r) => { task.result = r; });
|
|
72
|
+
bgTasks.set(id, task);
|
|
73
|
+
return okBrief(`BACKGROUND ${id} dispatched on ${node}: ${label}`);
|
|
74
|
+
}
|
|
66
75
|
// -----------------------------------------------------------------------------
|
|
67
76
|
export function createOmniWireServer(manager, transfer) {
|
|
68
77
|
const server = new McpServer({
|
|
69
78
|
name: 'omniwire',
|
|
70
|
-
version: '2.
|
|
79
|
+
version: '2.5.1',
|
|
71
80
|
});
|
|
81
|
+
// -- Auto-inject `background` param into every tool -------------------------
|
|
82
|
+
const origTool = server.tool.bind(server);
|
|
83
|
+
server.tool = (name, desc, schema, handler) => {
|
|
84
|
+
// Skip bg meta-tool itself
|
|
85
|
+
if (name === 'omniwire_bg')
|
|
86
|
+
return origTool(name, desc, schema, handler);
|
|
87
|
+
const augSchema = { ...schema, background: z.boolean().optional().describe('Run in background. Returns task ID immediately — poll with omniwire_bg.') };
|
|
88
|
+
const wrappedHandler = async (args) => {
|
|
89
|
+
if (args.background) {
|
|
90
|
+
const lbl = args.label ?? args.command?.slice(0, 60) ?? name;
|
|
91
|
+
const nd = args.node ?? args.src_node ?? 'omniwire';
|
|
92
|
+
return dispatchBg(nd, lbl, () => handler(args));
|
|
93
|
+
}
|
|
94
|
+
return handler(args);
|
|
95
|
+
};
|
|
96
|
+
return origTool(name, desc, augSchema, wrappedHandler);
|
|
97
|
+
};
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
72
99
|
const shells = new ShellManager(manager);
|
|
73
100
|
const realtime = new RealtimeChannel(manager);
|
|
74
101
|
const tunnels = new TunnelManager(manager);
|
|
102
|
+
// --- Tool 0: omniwire_bg (background task manager) ---
|
|
103
|
+
origTool('omniwire_bg', 'Poll, list, or retrieve results from background tasks dispatched with background=true on any tool.', {
|
|
104
|
+
action: z.enum(['list', 'poll', 'result']).describe('list=show all tasks, poll=check if done, result=get output'),
|
|
105
|
+
task_id: z.string().optional().describe('Task ID (required for poll/result)'),
|
|
106
|
+
}, async ({ action, task_id }) => {
|
|
107
|
+
if (action === 'list') {
|
|
108
|
+
if (bgTasks.size === 0)
|
|
109
|
+
return okBrief('No background tasks.');
|
|
110
|
+
const lines = [...bgTasks.values()].map((bg) => {
|
|
111
|
+
const status = bg.result ? 'DONE' : 'RUNNING';
|
|
112
|
+
const elapsed = Date.now() - bg.startedAt;
|
|
113
|
+
return `${bg.id} ${status} ${bg.node} ${t(elapsed)} ${bg.label}`;
|
|
114
|
+
});
|
|
115
|
+
return okBrief(lines.join('\n'));
|
|
116
|
+
}
|
|
117
|
+
if (!task_id)
|
|
118
|
+
return fail('task_id required for poll/result');
|
|
119
|
+
const task = bgTasks.get(task_id);
|
|
120
|
+
if (!task)
|
|
121
|
+
return fail(`task ${task_id} not found`);
|
|
122
|
+
if (action === 'poll') {
|
|
123
|
+
const status = task.result ? 'DONE' : 'RUNNING';
|
|
124
|
+
const elapsed = Date.now() - task.startedAt;
|
|
125
|
+
return okBrief(`${task_id} ${status} (${t(elapsed)}) ${task.label}`);
|
|
126
|
+
}
|
|
127
|
+
if (action === 'result') {
|
|
128
|
+
if (!task.result)
|
|
129
|
+
return okBrief(`${task_id} still RUNNING (${t(Date.now() - task.startedAt)})`);
|
|
130
|
+
return task.result;
|
|
131
|
+
}
|
|
132
|
+
return fail('invalid action');
|
|
133
|
+
});
|
|
75
134
|
// --- Tool 1: omniwire_exec ---
|
|
76
135
|
server.tool('omniwire_exec', 'Execute a command on a mesh node. Supports retry, assertions, JSON output, and result storage for agentic chaining.', {
|
|
77
136
|
node: z.string().optional().describe('Target node id (windows, contabo, hostinger, thinkpad). Auto-selects if omitted.'),
|
|
@@ -91,7 +150,6 @@ export function createOmniWireServer(manager, transfer) {
|
|
|
91
150
|
const timeoutSec = timeout ?? 30;
|
|
92
151
|
const maxRetries = retry ?? 0;
|
|
93
152
|
const useJson = format === 'json';
|
|
94
|
-
// Interpolate stored results: {{key}} -> value
|
|
95
153
|
let resolvedCmd = command;
|
|
96
154
|
if (resolvedCmd) {
|
|
97
155
|
resolvedCmd = resolvedCmd.replace(/\{\{(\w+)\}\}/g, (_, key) => resultStore.get(key) ?? `{{${key}}}`);
|
|
@@ -106,17 +164,14 @@ export function createOmniWireServer(manager, transfer) {
|
|
|
106
164
|
? `timeout ${timeoutSec} bash -c '${resolvedCmd.replace(/'/g, "'\\''")}'`
|
|
107
165
|
: resolvedCmd;
|
|
108
166
|
}
|
|
109
|
-
// Execute with retry
|
|
110
167
|
let result = await manager.exec(nodeId, effectiveCmd);
|
|
111
168
|
for (let attempt = 0; attempt < maxRetries && result.code !== 0; attempt++) {
|
|
112
169
|
await new Promise((r) => setTimeout(r, 1000));
|
|
113
170
|
result = await manager.exec(nodeId, effectiveCmd);
|
|
114
171
|
}
|
|
115
|
-
// Store result if requested
|
|
116
172
|
if (store_as && result.code === 0) {
|
|
117
173
|
resultStore.set(store_as, result.stdout.trim());
|
|
118
174
|
}
|
|
119
|
-
// Assert pattern in output
|
|
120
175
|
if (assertPattern && result.code === 0) {
|
|
121
176
|
const regex = new RegExp(assertPattern);
|
|
122
177
|
if (!regex.test(result.stdout)) {
|
|
@@ -1156,6 +1211,157 @@ tags: ${meshNode.tags.join(', ')}`;
|
|
|
1156
1211
|
}
|
|
1157
1212
|
return fail('invalid action');
|
|
1158
1213
|
});
|
|
1214
|
+
// --- Tool 42: omniwire_agent_registry ---
|
|
1215
|
+
server.tool('omniwire_agent_registry', 'Register/discover agents on the mesh. Agents announce their capabilities and other agents can discover them. Enables dynamic A2A routing and capability-based task delegation.', {
|
|
1216
|
+
action: z.enum(['register', 'deregister', 'discover', 'list', 'heartbeat']).describe('Action'),
|
|
1217
|
+
node: z.string().optional().describe('Node hosting registry (default: contabo)'),
|
|
1218
|
+
agent_id: z.string().optional().describe('Unique agent ID'),
|
|
1219
|
+
capabilities: z.array(z.string()).optional().describe('Agent capabilities (e.g., ["scan", "exploit", "report"])'),
|
|
1220
|
+
metadata: z.string().optional().describe('JSON metadata about the agent'),
|
|
1221
|
+
capability: z.string().optional().describe('Capability to search for (discover action)'),
|
|
1222
|
+
}, async ({ action, node, agent_id, capabilities, metadata, capability }) => {
|
|
1223
|
+
const nodeId = node ?? 'contabo';
|
|
1224
|
+
const regDir = '/tmp/.omniwire-agents';
|
|
1225
|
+
if (action === 'register') {
|
|
1226
|
+
if (!agent_id)
|
|
1227
|
+
return fail('agent_id required');
|
|
1228
|
+
const entry = JSON.stringify({ id: agent_id, capabilities: capabilities ?? [], metadata: metadata ?? '{}', ts: Date.now(), node: nodeId });
|
|
1229
|
+
const escaped = entry.replace(/'/g, "'\\''");
|
|
1230
|
+
await manager.exec(nodeId, `mkdir -p ${regDir} && echo '${escaped}' > ${regDir}/${agent_id}.json`);
|
|
1231
|
+
return okBrief(`agent ${agent_id} registered (${(capabilities ?? []).join(', ')})`);
|
|
1232
|
+
}
|
|
1233
|
+
if (action === 'deregister') {
|
|
1234
|
+
if (!agent_id)
|
|
1235
|
+
return fail('agent_id required');
|
|
1236
|
+
await manager.exec(nodeId, `rm -f ${regDir}/${agent_id}.json`);
|
|
1237
|
+
return okBrief(`agent ${agent_id} deregistered`);
|
|
1238
|
+
}
|
|
1239
|
+
if (action === 'heartbeat') {
|
|
1240
|
+
if (!agent_id)
|
|
1241
|
+
return fail('agent_id required');
|
|
1242
|
+
await manager.exec(nodeId, `[ -f ${regDir}/${agent_id}.json ] && tmp=$(cat ${regDir}/${agent_id}.json) && echo "$tmp" | sed 's/"ts":[0-9]*/"ts":${Date.now()}/' > ${regDir}/${agent_id}.json`);
|
|
1243
|
+
return okBrief(`agent ${agent_id} heartbeat`);
|
|
1244
|
+
}
|
|
1245
|
+
if (action === 'discover' && capability) {
|
|
1246
|
+
const result = await manager.exec(nodeId, `grep -l '"${capability}"' ${regDir}/*.json 2>/dev/null | xargs -I{} cat {} 2>/dev/null`);
|
|
1247
|
+
return ok(nodeId, result.durationMs, result.stdout || '(no agents with that capability)', `discover ${capability}`);
|
|
1248
|
+
}
|
|
1249
|
+
if (action === 'list') {
|
|
1250
|
+
const result = await manager.exec(nodeId, `cat ${regDir}/*.json 2>/dev/null || echo '(no agents)'`);
|
|
1251
|
+
return ok(nodeId, result.durationMs, result.stdout, 'agent registry');
|
|
1252
|
+
}
|
|
1253
|
+
return fail('invalid action');
|
|
1254
|
+
});
|
|
1255
|
+
// --- Tool 43: omniwire_blackboard ---
|
|
1256
|
+
server.tool('omniwire_blackboard', 'Shared blackboard for multi-agent collaboration. Agents post findings, hypotheses, and decisions to topic-scoped boards. Other agents read and build on them. Classic AI blackboard architecture for agent swarms.', {
|
|
1257
|
+
action: z.enum(['post', 'read', 'topics', 'clear', 'search']).describe('Action'),
|
|
1258
|
+
node: z.string().optional(),
|
|
1259
|
+
topic: z.string().optional().describe('Board topic (e.g., "recon-findings", "vuln-analysis")'),
|
|
1260
|
+
content: z.string().optional().describe('Content to post'),
|
|
1261
|
+
author: z.string().optional().describe('Author agent ID'),
|
|
1262
|
+
query: z.string().optional().describe('Search query (grep pattern) for search action'),
|
|
1263
|
+
limit: z.number().optional().describe('Max entries (default: 20)'),
|
|
1264
|
+
}, async ({ action, node, topic, content, author, query, limit }) => {
|
|
1265
|
+
const nodeId = node ?? 'contabo';
|
|
1266
|
+
const bbDir = '/tmp/.omniwire-blackboard';
|
|
1267
|
+
const n = limit ?? 20;
|
|
1268
|
+
if (action === 'post') {
|
|
1269
|
+
if (!topic || !content)
|
|
1270
|
+
return fail('topic and content required');
|
|
1271
|
+
const entry = JSON.stringify({ ts: Date.now(), author: author ?? 'agent', content });
|
|
1272
|
+
const escaped = entry.replace(/'/g, "'\\''");
|
|
1273
|
+
await manager.exec(nodeId, `mkdir -p ${bbDir} && echo '${escaped}' >> ${bbDir}/${topic}.log`);
|
|
1274
|
+
return okBrief(`posted to ${topic} (${content.length} chars)`);
|
|
1275
|
+
}
|
|
1276
|
+
if (action === 'read') {
|
|
1277
|
+
if (!topic)
|
|
1278
|
+
return fail('topic required');
|
|
1279
|
+
const result = await manager.exec(nodeId, `tail -${n} ${bbDir}/${topic}.log 2>/dev/null || echo '(empty board)'`);
|
|
1280
|
+
return ok(nodeId, result.durationMs, result.stdout, `board:${topic}`);
|
|
1281
|
+
}
|
|
1282
|
+
if (action === 'topics') {
|
|
1283
|
+
const result = await manager.exec(nodeId, `ls -1 ${bbDir}/*.log 2>/dev/null | xargs -I{} sh -c 'echo "$(basename {} .log) $(wc -l < {})"' 2>/dev/null || echo '(no topics)'`);
|
|
1284
|
+
return ok(nodeId, result.durationMs, result.stdout, 'blackboard topics');
|
|
1285
|
+
}
|
|
1286
|
+
if (action === 'search' && query) {
|
|
1287
|
+
const escaped = query.replace(/'/g, "'\\''");
|
|
1288
|
+
const topicFilter = topic ? `${bbDir}/${topic}.log` : `${bbDir}/*.log`;
|
|
1289
|
+
const result = await manager.exec(nodeId, `grep -h '${escaped}' ${topicFilter} 2>/dev/null | tail -${n}`);
|
|
1290
|
+
return ok(nodeId, result.durationMs, result.stdout || '(no matches)', `search:${query}`);
|
|
1291
|
+
}
|
|
1292
|
+
if (action === 'clear') {
|
|
1293
|
+
if (!topic)
|
|
1294
|
+
return fail('topic required');
|
|
1295
|
+
await manager.exec(nodeId, `rm -f ${bbDir}/${topic}.log`);
|
|
1296
|
+
return okBrief(`board ${topic} cleared`);
|
|
1297
|
+
}
|
|
1298
|
+
return fail('invalid action');
|
|
1299
|
+
});
|
|
1300
|
+
// --- Tool 44: omniwire_task_queue ---
|
|
1301
|
+
server.tool('omniwire_task_queue', 'Distributed task queue for agent swarms. Producers enqueue tasks, consumer agents dequeue and process them. Supports priorities, deadlines, and result reporting. Core A2A work distribution primitive.', {
|
|
1302
|
+
action: z.enum(['enqueue', 'dequeue', 'complete', 'fail', 'status', 'pending']).describe('Action'),
|
|
1303
|
+
node: z.string().optional(),
|
|
1304
|
+
queue: z.string().optional().describe('Queue name (default: "default")'),
|
|
1305
|
+
task: z.string().optional().describe('Task payload (JSON) for enqueue'),
|
|
1306
|
+
priority: z.number().optional().describe('Priority 0-9, higher = more urgent (default: 5)'),
|
|
1307
|
+
task_id: z.string().optional().describe('Task ID for complete/fail'),
|
|
1308
|
+
result: z.string().optional().describe('Result data for complete'),
|
|
1309
|
+
error: z.string().optional().describe('Error message for fail'),
|
|
1310
|
+
}, async ({ action, node, queue, task, priority, task_id, result: taskResult, error: taskError }) => {
|
|
1311
|
+
const nodeId = node ?? 'contabo';
|
|
1312
|
+
const qName = queue ?? 'default';
|
|
1313
|
+
const qDir = `/tmp/.omniwire-taskq/${qName}`;
|
|
1314
|
+
if (action === 'enqueue') {
|
|
1315
|
+
if (!task)
|
|
1316
|
+
return fail('task required');
|
|
1317
|
+
const id = `t-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1318
|
+
const pri = priority ?? 5;
|
|
1319
|
+
const entry = JSON.stringify({ id, priority: pri, ts: Date.now(), status: 'pending', task: JSON.parse(task) });
|
|
1320
|
+
const escaped = entry.replace(/'/g, "'\\''");
|
|
1321
|
+
await manager.exec(nodeId, `mkdir -p ${qDir} && echo '${escaped}' > ${qDir}/${pri}_${id}.task`);
|
|
1322
|
+
return okBrief(`enqueued ${id} (priority ${pri})`);
|
|
1323
|
+
}
|
|
1324
|
+
if (action === 'dequeue') {
|
|
1325
|
+
// Take highest priority task (9 first, then 8, ...)
|
|
1326
|
+
const result = await manager.exec(nodeId, `f=$(ls -r ${qDir}/*.task 2>/dev/null | head -1); [ -n "$f" ] && cat "$f" && rm -f "$f" || echo '(empty queue)'`);
|
|
1327
|
+
return ok(nodeId, result.durationMs, result.stdout, `dequeue:${qName}`);
|
|
1328
|
+
}
|
|
1329
|
+
if (action === 'complete' && task_id) {
|
|
1330
|
+
const entry = JSON.stringify({ id: task_id, status: 'complete', ts: Date.now(), result: taskResult ?? '' });
|
|
1331
|
+
const escaped = entry.replace(/'/g, "'\\''");
|
|
1332
|
+
await manager.exec(nodeId, `mkdir -p ${qDir}/done && echo '${escaped}' > ${qDir}/done/${task_id}.result`);
|
|
1333
|
+
return okBrief(`task ${task_id} completed`);
|
|
1334
|
+
}
|
|
1335
|
+
if (action === 'fail' && task_id) {
|
|
1336
|
+
const entry = JSON.stringify({ id: task_id, status: 'failed', ts: Date.now(), error: taskError ?? 'unknown' });
|
|
1337
|
+
const escaped = entry.replace(/'/g, "'\\''");
|
|
1338
|
+
await manager.exec(nodeId, `mkdir -p ${qDir}/failed && echo '${escaped}' > ${qDir}/failed/${task_id}.result`);
|
|
1339
|
+
return okBrief(`task ${task_id} failed`);
|
|
1340
|
+
}
|
|
1341
|
+
if (action === 'status') {
|
|
1342
|
+
const result = await manager.exec(nodeId, `echo "pending: $(ls ${qDir}/*.task 2>/dev/null | wc -l)"; echo "done: $(ls ${qDir}/done/*.result 2>/dev/null | wc -l)"; echo "failed: $(ls ${qDir}/failed/*.result 2>/dev/null | wc -l)"`);
|
|
1343
|
+
return ok(nodeId, result.durationMs, result.stdout, `queue:${qName}`);
|
|
1344
|
+
}
|
|
1345
|
+
if (action === 'pending') {
|
|
1346
|
+
const result = await manager.exec(nodeId, `cat ${qDir}/*.task 2>/dev/null | head -20 || echo '(empty)'`);
|
|
1347
|
+
return ok(nodeId, result.durationMs, result.stdout, `pending:${qName}`);
|
|
1348
|
+
}
|
|
1349
|
+
return fail('invalid action');
|
|
1350
|
+
});
|
|
1351
|
+
// --- Tool 45: omniwire_capability ---
|
|
1352
|
+
server.tool('omniwire_capability', 'Query node capabilities for intelligent task routing. Returns what tools, runtimes, and resources each node has. Agents use this to decide WHERE to dispatch tasks.', {
|
|
1353
|
+
node: z.string().optional().describe('Specific node (default: all online)'),
|
|
1354
|
+
check: z.array(z.string()).optional().describe('Specific capabilities to check (e.g., ["docker", "python3", "gpu", "nmap"])'),
|
|
1355
|
+
}, async ({ node, check }) => {
|
|
1356
|
+
const checks = check ?? ['docker', 'python3', 'node', 'go', 'nmap', 'ffuf', 'git', 'psql', 'lz4', 'aria2c', 'gcc'];
|
|
1357
|
+
const cmd = checks.map((c) => `command -v ${c} >/dev/null 2>&1 && echo "${c}:yes" || echo "${c}:no"`).join('; ');
|
|
1358
|
+
if (node) {
|
|
1359
|
+
const result = await manager.exec(node, cmd);
|
|
1360
|
+
return ok(node, result.durationMs, result.stdout, 'capabilities');
|
|
1361
|
+
}
|
|
1362
|
+
const results = await manager.execAll(cmd);
|
|
1363
|
+
return multiResult(results);
|
|
1364
|
+
});
|
|
1159
1365
|
return server;
|
|
1160
1366
|
}
|
|
1161
1367
|
//# sourceMappingURL=server.js.map
|