clawnexus 0.0.1 → 0.2.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.
Files changed (51) hide show
  1. package/README.md +108 -8
  2. package/dist/agent/engine.d.ts +17 -0
  3. package/dist/agent/engine.js +231 -0
  4. package/dist/agent/protocol.d.ts +12 -0
  5. package/dist/agent/protocol.js +159 -0
  6. package/dist/agent/router.d.ts +41 -0
  7. package/dist/agent/router.js +179 -0
  8. package/dist/agent/tasks.d.ts +28 -0
  9. package/dist/agent/tasks.js +294 -0
  10. package/dist/agent/types.d.ts +141 -0
  11. package/dist/agent/types.js +3 -0
  12. package/dist/api/server.d.ts +64 -0
  13. package/dist/api/server.js +564 -0
  14. package/dist/cli/index.d.ts +16 -0
  15. package/dist/cli/index.js +1153 -0
  16. package/dist/crypto/keys.d.ts +21 -0
  17. package/dist/crypto/keys.js +97 -0
  18. package/dist/discovery/broadcast.d.ts +27 -0
  19. package/dist/discovery/broadcast.js +289 -0
  20. package/dist/health/checker.d.ts +14 -0
  21. package/dist/health/checker.js +108 -0
  22. package/dist/index.d.ts +27 -0
  23. package/dist/index.js +51 -0
  24. package/dist/local/probe.d.ts +15 -0
  25. package/dist/local/probe.js +137 -0
  26. package/dist/mdns/listener.d.ts +22 -0
  27. package/dist/mdns/listener.js +159 -0
  28. package/dist/registry/auto-name.d.ts +15 -0
  29. package/dist/registry/auto-name.js +57 -0
  30. package/dist/registry/auto-register.d.ts +20 -0
  31. package/dist/registry/auto-register.js +102 -0
  32. package/dist/registry/client.d.ts +53 -0
  33. package/dist/registry/client.js +96 -0
  34. package/dist/registry/discovery.d.ts +14 -0
  35. package/dist/registry/discovery.js +57 -0
  36. package/dist/registry/store.d.ts +40 -0
  37. package/dist/registry/store.js +289 -0
  38. package/dist/relay/connector.d.ts +38 -0
  39. package/dist/relay/connector.js +232 -0
  40. package/dist/relay/crypto.d.ts +23 -0
  41. package/dist/relay/crypto.js +102 -0
  42. package/dist/relay/types.d.ts +77 -0
  43. package/dist/relay/types.js +3 -0
  44. package/dist/scanner/active.d.ts +21 -0
  45. package/dist/scanner/active.js +206 -0
  46. package/dist/scanner/wireguard.d.ts +14 -0
  47. package/dist/scanner/wireguard.js +180 -0
  48. package/dist/types.d.ts +49 -0
  49. package/dist/types.js +3 -0
  50. package/package.json +54 -23
  51. package/index.js +0 -2
package/README.md CHANGED
@@ -1,8 +1,108 @@
1
- # ClawNexus
2
-
3
- > Discover, name, and connect OpenClaw instances across your network.
4
-
5
- **This package is under active development. Full release coming soon.**
6
-
7
- - GitHub: https://github.com/silverstreamsAI/ClawNexus
8
- - Author: alan-silverstreams <alan@silverstream.tech>
1
+ # clawnexus
2
+
3
+ ClawNexus daemon and CLI AI instance registry for OpenClaw.
4
+
5
+ Discovers OpenClaw instances on your local network, assigns human-readable aliases, and exposes an HTTP API for querying and managing them.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g clawnexus
11
+ ```
12
+
13
+ Requires Node.js >= 22.
14
+
15
+ ## CLI Usage
16
+
17
+ ### Daemon Management
18
+
19
+ ```bash
20
+ clawnexus start # Start the daemon (background process)
21
+ clawnexus stop # Stop the daemon
22
+ clawnexus restart # Restart the daemon
23
+ clawnexus status # Show daemon status
24
+ ```
25
+
26
+ ### Instance Discovery
27
+
28
+ ```bash
29
+ clawnexus scan # Scan local network for OpenClaw instances
30
+ clawnexus list # List all known instances
31
+ clawnexus list --json # Machine-readable JSON output
32
+ ```
33
+
34
+ ### Instance Management
35
+
36
+ ```bash
37
+ clawnexus alias <id|address> <name> # Set a friendly alias
38
+ clawnexus info <name|address> # Show instance details
39
+ clawnexus forget <name|address> # Remove from registry
40
+ ```
41
+
42
+ ### Connection
43
+
44
+ ```bash
45
+ clawnexus connect <name> # Output ws:// address for an instance
46
+ clawnexus open <name> # Open WebChat UI in browser
47
+ clawnexus relay status # Show relay connection status
48
+ clawnexus connect <name.claw> # Connect via relay (v0.4)
49
+ ```
50
+
51
+ ### Global Flags
52
+
53
+ | Flag | Description | Default |
54
+ |------|-------------|---------|
55
+ | `--json` | Machine-readable JSON output | `false` |
56
+ | `--timeout <ms>` | Request timeout | `5000` |
57
+ | `--api <url>` | Daemon API URL | `http://localhost:17890` |
58
+
59
+ ## Daemon HTTP API
60
+
61
+ The daemon listens on `http://localhost:17890` by default.
62
+
63
+ | Method | Path | Description |
64
+ |--------|------|-------------|
65
+ | `GET` | `/health` | Daemon health status |
66
+ | `GET` | `/instances` | List all instances |
67
+ | `GET` | `/instances/:id` | Get a single instance |
68
+ | `PUT` | `/instances/:id/alias` | Set/update alias |
69
+ | `DELETE` | `/instances/:id` | Remove instance |
70
+ | `POST` | `/scan` | Trigger network scan |
71
+ | `POST` | `/relay/connect` | Connect via relay (v0.4) |
72
+ | `GET` | `/relay/status` | Relay connection status |
73
+ | `DELETE` | `/relay/disconnect/:room_id` | Disconnect relay room |
74
+
75
+ See [docs/api.md](../../docs/api.md) for full request/response examples.
76
+
77
+ ## Configuration
78
+
79
+ | Environment Variable | Description | Default |
80
+ |---------------------|-------------|---------|
81
+ | `CLAWNEXUS_PORT` | Daemon API port | `17890` |
82
+ | `CLAWNEXUS_HOST` | Daemon bind address | `127.0.0.1` |
83
+ | `CLAWNEXUS_API` | CLI target API URL | `http://localhost:17890` |
84
+
85
+ Data is stored in `~/.clawnexus/`:
86
+
87
+ ```
88
+ ~/.clawnexus/
89
+ ├── registry.json # Instance registry
90
+ ├── daemon.pid # PID file
91
+ └── policy.json # Agent policy (v1.0)
92
+ ```
93
+
94
+ ## Programmatic Usage
95
+
96
+ ```typescript
97
+ import { startDaemon } from "clawnexus";
98
+
99
+ const handle = await startDaemon({ port: 17890, host: "127.0.0.1" });
100
+
101
+ // Access components
102
+ console.log(handle.store.getAll()); // List instances
103
+ await handle.app.close(); // Graceful shutdown
104
+ ```
105
+
106
+ ## License
107
+
108
+ MIT
@@ -0,0 +1,17 @@
1
+ import type { PolicyConfig, PolicyDecision, LayerBEnvelope } from "./types.js";
2
+ export declare class PolicyEngine {
3
+ private config;
4
+ private readonly configDir;
5
+ private readonly configPath;
6
+ private rateCounters;
7
+ constructor(configDir?: string);
8
+ init(): Promise<void>;
9
+ evaluate(envelope: LayerBEnvelope, peerTrustScore?: number): PolicyDecision;
10
+ getConfig(): PolicyConfig;
11
+ updateConfig(full: PolicyConfig): Promise<void>;
12
+ patchConfig(partial: Partial<PolicyConfig>): Promise<void>;
13
+ resetConfig(): Promise<void>;
14
+ private isRateLimited;
15
+ private incrementRate;
16
+ private saveConfig;
17
+ }
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ // Layer B — Policy Decision Engine
3
+ // Evaluates inbound proposals against local policy config
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.PolicyEngine = void 0;
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ const os = __importStar(require("node:os"));
42
+ const CLAWNEXUS_DIR = path.join(os.homedir(), ".clawnexus");
43
+ const POLICY_PATH = path.join(CLAWNEXUS_DIR, "policy.json");
44
+ const DEFAULT_POLICY = {
45
+ mode: "queue",
46
+ trust_threshold: 50,
47
+ rate_limit: {
48
+ max_per_minute: 10,
49
+ max_per_peer_minute: 3,
50
+ },
51
+ delegation: {
52
+ allow: false,
53
+ max_depth: 3,
54
+ },
55
+ capability_filter: [],
56
+ access_control: {
57
+ whitelist: [],
58
+ blacklist: [],
59
+ },
60
+ auto_approve_types: [],
61
+ max_concurrent_tasks: 5,
62
+ };
63
+ class PolicyEngine {
64
+ config = { ...DEFAULT_POLICY };
65
+ configDir;
66
+ configPath;
67
+ rateCounters = new Map();
68
+ constructor(configDir) {
69
+ this.configDir = configDir ?? CLAWNEXUS_DIR;
70
+ this.configPath = path.join(this.configDir, "policy.json");
71
+ }
72
+ async init() {
73
+ await fs.promises.mkdir(this.configDir, { recursive: true });
74
+ if (fs.existsSync(this.configPath)) {
75
+ try {
76
+ const raw = await fs.promises.readFile(this.configPath, "utf-8");
77
+ const data = JSON.parse(raw);
78
+ this.config = { ...DEFAULT_POLICY, ...data };
79
+ }
80
+ catch {
81
+ // Corrupted — use defaults
82
+ this.config = { ...DEFAULT_POLICY };
83
+ }
84
+ }
85
+ else {
86
+ await this.saveConfig();
87
+ }
88
+ }
89
+ evaluate(envelope, peerTrustScore = 0) {
90
+ const peer = envelope.from;
91
+ // 1. Blacklist check
92
+ if (this.config.access_control.blacklist.includes(peer)) {
93
+ return { result: "reject", reason: "policy_denied", details: "Peer is blacklisted" };
94
+ }
95
+ // 2. Rate limit check
96
+ if (this.isRateLimited(peer)) {
97
+ return { result: "reject", reason: "rate_limited", details: "Rate limit exceeded" };
98
+ }
99
+ this.incrementRate(peer);
100
+ // 3. Whitelist check — whitelisted peers bypass trust/capability checks
101
+ const isWhitelisted = this.config.access_control.whitelist.includes(peer);
102
+ // 4. Trust score check (skip for whitelisted)
103
+ if (!isWhitelisted && peerTrustScore < this.config.trust_threshold) {
104
+ return { result: "reject", reason: "trust_insufficient", details: `Score ${peerTrustScore} < threshold ${this.config.trust_threshold}` };
105
+ }
106
+ // 5. Delegation depth check
107
+ if (envelope.type === "delegate") {
108
+ const dp = envelope.payload;
109
+ if (!this.config.delegation.allow) {
110
+ return { result: "reject", reason: "policy_denied", details: "Delegation not allowed" };
111
+ }
112
+ if ((dp.task?.delegation_depth ?? 0) > this.config.delegation.max_depth) {
113
+ return { result: "reject", reason: "policy_denied", details: "Delegation depth exceeded" };
114
+ }
115
+ }
116
+ // 6. Capability filter (if non-empty, task_type must match)
117
+ if (envelope.type === "propose" || envelope.type === "delegate") {
118
+ const task = envelope.type === "propose"
119
+ ? envelope.payload.task
120
+ : envelope.payload.task;
121
+ if (this.config.capability_filter.length > 0) {
122
+ const matches = this.config.capability_filter.some((pattern) => task.task_type === pattern || matchGlob(pattern, task.task_type));
123
+ if (!matches) {
124
+ return { result: "reject", reason: "capability_mismatch", details: `task_type "${task.task_type}" not in capability filter` };
125
+ }
126
+ }
127
+ }
128
+ // 7. Approval mode
129
+ switch (this.config.mode) {
130
+ case "auto":
131
+ return { result: "accept", reason: "auto_approved" };
132
+ case "queue":
133
+ return { result: "queue", reason: "queued_for_review" };
134
+ case "hybrid": {
135
+ if (isWhitelisted) {
136
+ return { result: "accept", reason: "auto_approved", details: "Whitelisted peer" };
137
+ }
138
+ // Check auto_approve_types
139
+ if (envelope.type === "propose") {
140
+ const taskType = envelope.payload.task.task_type;
141
+ if (this.config.auto_approve_types.includes(taskType)) {
142
+ return { result: "accept", reason: "auto_approved", details: `task_type "${taskType}" auto-approved` };
143
+ }
144
+ }
145
+ return { result: "queue", reason: "queued_for_review" };
146
+ }
147
+ default:
148
+ return { result: "queue", reason: "queued_for_review" };
149
+ }
150
+ }
151
+ getConfig() {
152
+ return { ...this.config };
153
+ }
154
+ async updateConfig(full) {
155
+ this.config = { ...full };
156
+ await this.saveConfig();
157
+ }
158
+ async patchConfig(partial) {
159
+ this.config = deepMerge(this.config, partial);
160
+ await this.saveConfig();
161
+ }
162
+ async resetConfig() {
163
+ this.config = { ...DEFAULT_POLICY };
164
+ await this.saveConfig();
165
+ }
166
+ isRateLimited(peer) {
167
+ const now = Date.now();
168
+ // Global rate
169
+ const global = this.rateCounters.get("__global__");
170
+ if (global && global.resetAt > now && global.count >= this.config.rate_limit.max_per_minute) {
171
+ return true;
172
+ }
173
+ // Per-peer rate
174
+ const peerRate = this.rateCounters.get(peer);
175
+ if (peerRate && peerRate.resetAt > now && peerRate.count >= this.config.rate_limit.max_per_peer_minute) {
176
+ return true;
177
+ }
178
+ return false;
179
+ }
180
+ incrementRate(peer) {
181
+ const now = Date.now();
182
+ const windowEnd = now + 60_000;
183
+ for (const key of ["__global__", peer]) {
184
+ const existing = this.rateCounters.get(key);
185
+ if (!existing || existing.resetAt <= now) {
186
+ this.rateCounters.set(key, { count: 1, resetAt: windowEnd });
187
+ }
188
+ else {
189
+ existing.count++;
190
+ }
191
+ }
192
+ }
193
+ async saveConfig() {
194
+ const json = JSON.stringify(this.config, null, 2);
195
+ const tmpPath = this.configPath + ".tmp";
196
+ await fs.promises.writeFile(tmpPath, json, "utf-8");
197
+ await fs.promises.rename(tmpPath, this.configPath);
198
+ }
199
+ }
200
+ exports.PolicyEngine = PolicyEngine;
201
+ function matchGlob(pattern, value) {
202
+ // Simple glob: only supports trailing *
203
+ if (pattern.endsWith("*")) {
204
+ return value.startsWith(pattern.slice(0, -1));
205
+ }
206
+ return pattern === value;
207
+ }
208
+ function deepMerge(target, source) {
209
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
+ const result = { ...target };
211
+ const src = source;
212
+ const tgt = target;
213
+ for (const key of Object.keys(src)) {
214
+ const sv = src[key];
215
+ const tv = tgt[key];
216
+ if (sv !== null &&
217
+ sv !== undefined &&
218
+ typeof sv === "object" &&
219
+ !Array.isArray(sv) &&
220
+ tv !== null &&
221
+ tv !== undefined &&
222
+ typeof tv === "object" &&
223
+ !Array.isArray(tv)) {
224
+ result[key] = { ...tv, ...sv };
225
+ }
226
+ else if (sv !== undefined) {
227
+ result[key] = sv;
228
+ }
229
+ }
230
+ return result;
231
+ }
@@ -0,0 +1,12 @@
1
+ import type { LayerBEnvelope, LayerBMessageType, LayerBPayload } from "./types.js";
2
+ export interface EnvelopeOptions {
3
+ in_reply_to?: string;
4
+ ttl?: number;
5
+ }
6
+ export declare function createEnvelope(from: string, to: string, type: LayerBMessageType, payload: LayerBPayload, opts?: EnvelopeOptions): LayerBEnvelope;
7
+ export declare class ProtocolError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ export declare function parseEnvelope(raw: string): LayerBEnvelope;
11
+ export declare function validatePayload(type: LayerBMessageType, payload: LayerBPayload): void;
12
+ export declare function isExpired(envelope: LayerBEnvelope): boolean;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ // Layer B — Message Protocol
3
+ // Pure functions: envelope construction, parsing, validation
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.ProtocolError = void 0;
6
+ exports.createEnvelope = createEnvelope;
7
+ exports.parseEnvelope = parseEnvelope;
8
+ exports.validatePayload = validatePayload;
9
+ exports.isExpired = isExpired;
10
+ const node_crypto_1 = require("node:crypto");
11
+ const PROTOCOL = "clawnexus-agent";
12
+ const VERSION = "1.0";
13
+ const DEFAULT_TTL = 300; // 5 minutes
14
+ const VALID_TYPES = new Set([
15
+ "query", "propose", "accept", "reject", "delegate",
16
+ "report", "cancel", "capability", "heartbeat",
17
+ ]);
18
+ function createEnvelope(from, to, type, payload, opts) {
19
+ return {
20
+ protocol: PROTOCOL,
21
+ version: VERSION,
22
+ message_id: (0, node_crypto_1.randomUUID)(),
23
+ in_reply_to: opts?.in_reply_to,
24
+ from,
25
+ to,
26
+ type,
27
+ payload,
28
+ timestamp: new Date().toISOString(),
29
+ ttl: opts?.ttl ?? DEFAULT_TTL,
30
+ };
31
+ }
32
+ class ProtocolError extends Error {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "ProtocolError";
36
+ }
37
+ }
38
+ exports.ProtocolError = ProtocolError;
39
+ function parseEnvelope(raw) {
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(raw);
43
+ }
44
+ catch {
45
+ throw new ProtocolError("Invalid JSON");
46
+ }
47
+ const obj = parsed;
48
+ if (obj.protocol !== PROTOCOL) {
49
+ throw new ProtocolError(`Unknown protocol: ${obj.protocol}`);
50
+ }
51
+ if (obj.version !== VERSION) {
52
+ throw new ProtocolError(`Unsupported version: ${obj.version}`);
53
+ }
54
+ if (!obj.message_id || typeof obj.message_id !== "string") {
55
+ throw new ProtocolError("Missing message_id");
56
+ }
57
+ if (!obj.from || typeof obj.from !== "string") {
58
+ throw new ProtocolError("Missing from");
59
+ }
60
+ if (!obj.to || typeof obj.to !== "string") {
61
+ throw new ProtocolError("Missing to");
62
+ }
63
+ if (!VALID_TYPES.has(obj.type)) {
64
+ throw new ProtocolError(`Invalid type: ${obj.type}`);
65
+ }
66
+ if (!obj.payload || typeof obj.payload !== "object") {
67
+ throw new ProtocolError("Missing payload");
68
+ }
69
+ if (!obj.timestamp || typeof obj.timestamp !== "string") {
70
+ throw new ProtocolError("Missing timestamp");
71
+ }
72
+ validatePayload(obj.type, obj.payload);
73
+ return obj;
74
+ }
75
+ function validatePayload(type, payload) {
76
+ switch (type) {
77
+ case "query": {
78
+ const p = payload;
79
+ if (!p.query_type)
80
+ throw new ProtocolError("query: missing query_type");
81
+ if (!["capabilities", "status", "availability"].includes(p.query_type)) {
82
+ throw new ProtocolError(`query: invalid query_type: ${p.query_type}`);
83
+ }
84
+ break;
85
+ }
86
+ case "propose": {
87
+ const p = payload;
88
+ if (!p.task)
89
+ throw new ProtocolError("propose: missing task");
90
+ if (!p.task.task_type)
91
+ throw new ProtocolError("propose: missing task.task_type");
92
+ if (!p.task.description)
93
+ throw new ProtocolError("propose: missing task.description");
94
+ if (p.task.delegation_depth !== undefined && p.task.delegation_depth > 5) {
95
+ throw new ProtocolError("propose: delegation_depth exceeds hard cap (5)");
96
+ }
97
+ break;
98
+ }
99
+ case "accept": {
100
+ const p = payload;
101
+ if (!p.task_id)
102
+ throw new ProtocolError("accept: missing task_id");
103
+ break;
104
+ }
105
+ case "reject": {
106
+ const p = payload;
107
+ if (!p.task_id)
108
+ throw new ProtocolError("reject: missing task_id");
109
+ if (!p.reason)
110
+ throw new ProtocolError("reject: missing reason");
111
+ break;
112
+ }
113
+ case "delegate": {
114
+ const p = payload;
115
+ if (!p.task_id)
116
+ throw new ProtocolError("delegate: missing task_id");
117
+ if (!p.original_from)
118
+ throw new ProtocolError("delegate: missing original_from");
119
+ if (!p.task)
120
+ throw new ProtocolError("delegate: missing task");
121
+ break;
122
+ }
123
+ case "report": {
124
+ const p = payload;
125
+ if (!p.task_id)
126
+ throw new ProtocolError("report: missing task_id");
127
+ if (!p.status)
128
+ throw new ProtocolError("report: missing status");
129
+ if (!["completed", "failed", "progress"].includes(p.status)) {
130
+ throw new ProtocolError(`report: invalid status: ${p.status}`);
131
+ }
132
+ break;
133
+ }
134
+ case "cancel": {
135
+ const p = payload;
136
+ if (!p.task_id)
137
+ throw new ProtocolError("cancel: missing task_id");
138
+ break;
139
+ }
140
+ case "capability": {
141
+ const p = payload;
142
+ if (!Array.isArray(p.capabilities)) {
143
+ throw new ProtocolError("capability: missing capabilities array");
144
+ }
145
+ break;
146
+ }
147
+ case "heartbeat": {
148
+ const p = payload;
149
+ if (!p.task_id)
150
+ throw new ProtocolError("heartbeat: missing task_id");
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ function isExpired(envelope) {
156
+ const ttl = envelope.ttl ?? DEFAULT_TTL;
157
+ const created = new Date(envelope.timestamp).getTime();
158
+ return Date.now() - created > ttl * 1000;
159
+ }
@@ -0,0 +1,41 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { RelayConnector } from "../relay/connector.js";
3
+ import type { PolicyEngine } from "./engine.js";
4
+ import type { TaskManager } from "./tasks.js";
5
+ import type { LayerBEnvelope, ProposePayload, TaskRecord } from "./types.js";
6
+ export interface AgentRouterOptions {
7
+ connector: RelayConnector;
8
+ engine: PolicyEngine;
9
+ tasks: TaskManager;
10
+ localClawId: string;
11
+ }
12
+ export declare class AgentRouter extends EventEmitter {
13
+ private readonly connector;
14
+ private readonly engine;
15
+ private readonly tasks;
16
+ private readonly localClawId;
17
+ private dataHandler;
18
+ private inbox;
19
+ constructor(opts: AgentRouterOptions);
20
+ start(): void;
21
+ stop(): void;
22
+ sendMessage(roomId: string, envelope: LayerBEnvelope): boolean;
23
+ /** Initiate a propose to a peer (outbound task) */
24
+ propose(roomId: string, targetClawId: string, task: ProposePayload["task"]): TaskRecord;
25
+ /** Send a query to a peer */
26
+ query(roomId: string, targetClawId: string, queryType: "capabilities" | "status" | "availability"): LayerBEnvelope;
27
+ /** Approve a queued inbound proposal */
28
+ approveInbox(messageId: string): TaskRecord | null;
29
+ /** Deny a queued inbound proposal */
30
+ denyInbox(messageId: string, reason?: string): void;
31
+ /** Get pending inbox items */
32
+ getInbox(): Array<{
33
+ message_id: string;
34
+ envelope: LayerBEnvelope;
35
+ roomId: string;
36
+ }>;
37
+ private handleData;
38
+ private handleProposal;
39
+ private acceptProposal;
40
+ private handleQuery;
41
+ }