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 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:0A0E14,50:1A1F2E,100:59C2FF&height=200&section=header&text=OmniWire&fontSize=72&fontColor=59C2FF&animation=fadeIn&fontAlignY=35&desc=Unified%20Mesh%20Control%20Layer&descSize=18&descColor=8B949E&descAlignY=55" />
4
- <source media="(prefers-color-scheme: light)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:E8EAED,50:D4D8DE,100:59C2FF&height=200&section=header&text=OmniWire&fontSize=72&fontColor=0A0E14&animation=fadeIn&fontAlignY=35&desc=Unified%20Mesh%20Control%20Layer&descSize=18&descColor=586069&descAlignY=55" />
5
- <img alt="OmniWire" src="https://capsule-render.vercel.app/api?type=waving&color=0:0A0E14,50:1A1F2E,100:59C2FF&height=200&section=header&text=OmniWire&fontSize=72&fontColor=59C2FF&animation=fadeIn&fontAlignY=35&desc=Unified%20Mesh%20Control%20Layer&descSize=18&descColor=8B949E&descAlignY=55" />
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&section=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&section=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&section=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=0A0E14" alt="npm" /></a>
11
- <img src="https://img.shields.io/badge/MCP-49_tools-59C2FF?style=for-the-badge&labelColor=0A0E14" alt="tools" />
12
- <img src="https://img.shields.io/badge/A2A-ready-91B362?style=for-the-badge&labelColor=0A0E14" alt="A2A" />
13
- <img src="https://img.shields.io/badge/transport-stdio_%7C_SSE_%7C_REST-E6B450?style=for-the-badge&labelColor=0A0E14" alt="transports" />
14
- <img src="https://img.shields.io/badge/node-%E2%89%A520-CC93E6?style=for-the-badge&logo=node.js&labelColor=0A0E14" alt="node" />
15
- <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-8B949E?style=for-the-badge&labelColor=0A0E14" alt="license" /></a>
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
- <p align="center">
19
- <b>One MCP server to control all your machines.</b><br/>
20
- <sub>49 tools. Multi-agent orchestration. A2A messaging. Distributed locking. Cross-node pipelines.</sub><br/>
21
- <sub>SSH2 failover, adaptive file transfers, encrypted config sync, agentic chaining.</sub>
22
- </p>
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** &mdash; 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["49 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:#0A0E14,stroke:#59C2FF,stroke-width:2px,color:#C6D0E1
109
- style clients fill:#1A1F2E,stroke:#91B362,stroke-width:1px,color:#C6D0E1
110
- style mesh fill:#1A1F2E,stroke:#E6B450,stroke-width:1px,color:#C6D0E1
111
- style tools fill:#141922,stroke:#59C2FF,stroke-width:1px,color:#C6D0E1
112
- style engine fill:#141922,stroke:#CC93E6,stroke-width:1px,color:#C6D0E1
113
- style MCP fill:#1A1F2E,stroke:#59C2FF,color:#59C2FF
114
- style DB fill:#1A1F2E,stroke:#CC93E6,color:#CC93E6
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 49 Tools
233
+ ## All 54 Tools
234
+
235
+ > **Every tool** supports `background: true` — returns a task ID immediately. Poll with `omniwire_bg`.
211
236
 
212
- ### Execution (5)
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 (9)
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 | Details |
301
- |-----------|---------|---------|
302
- | Command exec | ~120ms | SSH2 channel on persistent connection |
303
- | Mesh status (all nodes) | ~150ms | Parallel probes, 5s cache |
304
- | File read (<1MB) | ~80ms | SFTP, binary-safe |
305
- | Transfer (10MB) | ~200ms | gzip netcat over WireGuard |
306
- | Pipeline (5 steps) | ~600ms | Sequential with interpolation |
307
- | Health check (4 nodes) | ~200ms | Parallel, structured output |
308
- | A2A message send | ~130ms | File-based queue |
309
- | Config push (all nodes) | ~200ms | Parallel + Obsidian mirror |
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 (49 tools, 3 transports)
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
- <p align="center">
417
- <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-E6B450?style=flat-square&labelColor=0A0E14" alt="MIT License" /></a>
418
- </p>
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:0A0E14,50:1A1F2E,100:59C2FF&height=100&section=footer" />
423
- <source media="(prefers-color-scheme: light)" srcset="https://capsule-render.vercel.app/api?type=waving&color=0:E8EAED,50:D4D8DE,100:59C2FF&height=100&section=footer" />
424
- <img alt="footer" src="https://capsule-render.vercel.app/api?type=waving&color=0:0A0E14,50:1A1F2E,100:59C2FF&height=100&section=footer" />
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&section=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&section=footer" />
481
+ <img alt="footer" src="https://capsule-render.vercel.app/api?type=waving&color=0:59C2FF,50:162B44,100:0D1117&height=120&section=footer" />
425
482
  </picture>
426
483
  </p>
@@ -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.4.0',
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