clawnexus 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2a/card.d.ts +1 -1
- package/dist/a2a/card.js +2 -2
- package/dist/agent/engine.js +9 -2
- package/dist/agent/executor.d.ts +48 -0
- package/dist/agent/executor.js +374 -0
- package/dist/agent/gateway.d.ts +26 -0
- package/dist/agent/gateway.js +298 -0
- package/dist/agent/protocol.js +2 -0
- package/dist/agent/router.d.ts +10 -1
- package/dist/agent/router.js +31 -2
- package/dist/agent/services.d.ts +36 -0
- package/dist/agent/services.js +153 -0
- package/dist/agent/tasks.js +4 -2
- package/dist/api/server.d.ts +7 -2
- package/dist/api/server.js +108 -15
- package/dist/registry/auto-register.js +32 -20
- package/dist/relay/connector.d.ts +5 -1
- package/dist/relay/connector.js +13 -2
- package/package.json +1 -1
package/dist/api/server.js
CHANGED
|
@@ -22,6 +22,7 @@ const probe_js_1 = require("../local/probe.js");
|
|
|
22
22
|
const engine_js_1 = require("../agent/engine.js");
|
|
23
23
|
const tasks_js_1 = require("../agent/tasks.js");
|
|
24
24
|
const router_js_1 = require("../agent/router.js");
|
|
25
|
+
const executor_js_1 = require("../agent/executor.js");
|
|
25
26
|
const broadcast_js_1 = require("../discovery/broadcast.js");
|
|
26
27
|
const keys_js_1 = require("../crypto/keys.js");
|
|
27
28
|
const client_js_1 = require("../registry/client.js");
|
|
@@ -29,11 +30,12 @@ const auto_register_js_1 = require("../registry/auto-register.js");
|
|
|
29
30
|
const discovery_js_1 = require("../registry/discovery.js");
|
|
30
31
|
const connector_js_1 = require("../relay/connector.js");
|
|
31
32
|
const card_js_1 = require("../a2a/card.js");
|
|
33
|
+
const services_js_1 = require("../agent/services.js");
|
|
32
34
|
const node_fs_1 = require("node:fs");
|
|
33
35
|
const node_path_1 = require("node:path");
|
|
34
36
|
const PORT = parseInt(process.env.CLAWNEXUS_PORT ?? "17890", 10);
|
|
35
37
|
const HOST = process.env.CLAWNEXUS_HOST ?? "127.0.0.1";
|
|
36
|
-
function registerRelayRoutes(app, getConnector) {
|
|
38
|
+
function registerRelayRoutes(app, getConnector, getTokenRefresher) {
|
|
37
39
|
app.post("/relay/connect", async (request, reply) => {
|
|
38
40
|
const connector = getConnector();
|
|
39
41
|
if (!connector) {
|
|
@@ -47,6 +49,17 @@ function registerRelayRoutes(app, getConnector) {
|
|
|
47
49
|
error: "Missing target_claw_id",
|
|
48
50
|
});
|
|
49
51
|
}
|
|
52
|
+
// Refresh auth token before JOIN (relay JWTs expire after 5 min)
|
|
53
|
+
const refresher = getTokenRefresher?.();
|
|
54
|
+
if (refresher) {
|
|
55
|
+
try {
|
|
56
|
+
const freshToken = await refresher();
|
|
57
|
+
connector.updateAuthToken(freshToken);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
app.log.warn(`Token refresh before JOIN failed: ${err}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
50
63
|
connector.join(target_claw_id);
|
|
51
64
|
return { status: "connecting", target: target_claw_id };
|
|
52
65
|
});
|
|
@@ -71,7 +84,33 @@ function registerRelayRoutes(app, getConnector) {
|
|
|
71
84
|
});
|
|
72
85
|
}
|
|
73
86
|
function registerAgentRoutes(app, deps) {
|
|
74
|
-
const { engine, tasks, getRouter } = deps;
|
|
87
|
+
const { engine, tasks, getRouter, getExecutor, skillsRegistry } = deps;
|
|
88
|
+
// --- Skills ---
|
|
89
|
+
app.get("/agent/skills", async () => {
|
|
90
|
+
if (!skillsRegistry) {
|
|
91
|
+
return { skills: [], status: { source: "not_initialized" } };
|
|
92
|
+
}
|
|
93
|
+
return { skills: skillsRegistry.getSkills(), status: skillsRegistry.getStatus() };
|
|
94
|
+
});
|
|
95
|
+
app.post("/agent/skills/refresh", async () => {
|
|
96
|
+
if (!skillsRegistry) {
|
|
97
|
+
return { status: "error", message: "Skills registry not initialized" };
|
|
98
|
+
}
|
|
99
|
+
const ok = await skillsRegistry.refresh();
|
|
100
|
+
return {
|
|
101
|
+
status: ok ? "ok" : "error",
|
|
102
|
+
skills: skillsRegistry.getSkills(),
|
|
103
|
+
...skillsRegistry.getStatus(),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
// --- Executor status ---
|
|
107
|
+
app.get("/agent/executor/status", async () => {
|
|
108
|
+
const executor = getExecutor();
|
|
109
|
+
if (!executor) {
|
|
110
|
+
return { gw_state: "not_initialized", queue_length: 0, executing: [], max_concurrent: 0 };
|
|
111
|
+
}
|
|
112
|
+
return executor.getStatus();
|
|
113
|
+
});
|
|
75
114
|
// --- Policy ---
|
|
76
115
|
app.get("/agent/policy", async () => engine.getConfig());
|
|
77
116
|
app.put("/agent/policy", async (request) => {
|
|
@@ -305,19 +344,20 @@ function registerDiagnosticsRoutes(app, deps) {
|
|
|
305
344
|
};
|
|
306
345
|
});
|
|
307
346
|
}
|
|
308
|
-
function registerA2aRoutes(app, store, daemonVersion) {
|
|
347
|
+
function registerA2aRoutes(app, store, daemonVersion, skillsRegistry) {
|
|
309
348
|
// A2A standard well-known endpoint — returns card for the local (is_self) instance
|
|
310
349
|
app.get("/.well-known/agent-card.json", async (_request, reply) => {
|
|
311
350
|
const self = store.getAll().find((i) => i.is_self);
|
|
312
351
|
if (!self) {
|
|
313
352
|
return reply.status(404).send({ error: "No local instance discovered" });
|
|
314
353
|
}
|
|
315
|
-
return (0, card_js_1.buildAgentCard)(self, daemonVersion);
|
|
354
|
+
return (0, card_js_1.buildAgentCard)(self, daemonVersion, skillsRegistry?.getSkills());
|
|
316
355
|
});
|
|
317
356
|
// All instances as Agent Cards
|
|
318
357
|
app.get("/a2a/cards", async () => {
|
|
319
358
|
const instances = store.getAll();
|
|
320
|
-
const
|
|
359
|
+
const skills = skillsRegistry?.getSkills();
|
|
360
|
+
const cards = instances.map((i) => (0, card_js_1.buildAgentCard)(i, daemonVersion, skills));
|
|
321
361
|
return { count: cards.length, cards };
|
|
322
362
|
});
|
|
323
363
|
// Single instance Agent Card by name
|
|
@@ -326,7 +366,7 @@ function registerA2aRoutes(app, store, daemonVersion) {
|
|
|
326
366
|
if (!inst) {
|
|
327
367
|
return reply.status(404).send({ error: "Instance not found" });
|
|
328
368
|
}
|
|
329
|
-
return (0, card_js_1.buildAgentCard)(inst, daemonVersion);
|
|
369
|
+
return (0, card_js_1.buildAgentCard)(inst, daemonVersion, skillsRegistry?.getSkills());
|
|
330
370
|
});
|
|
331
371
|
}
|
|
332
372
|
async function startDaemon(options = {}) {
|
|
@@ -355,6 +395,13 @@ async function startDaemon(options = {}) {
|
|
|
355
395
|
await engine.init();
|
|
356
396
|
const taskManager = new tasks_js_1.TaskManager();
|
|
357
397
|
await taskManager.init();
|
|
398
|
+
// 6b. Create TaskExecutor
|
|
399
|
+
const taskExecutor = new executor_js_1.TaskExecutor({
|
|
400
|
+
tasks: taskManager,
|
|
401
|
+
maxConcurrent: engine.getConfig().max_concurrent_tasks,
|
|
402
|
+
});
|
|
403
|
+
// 6c. Create SkillsRegistry
|
|
404
|
+
const skillsRegistry = new services_js_1.SkillsRegistry();
|
|
358
405
|
// 7. Detect WireGuard interfaces
|
|
359
406
|
const wgInfo = await (0, wireguard_js_1.detectWireGuard)();
|
|
360
407
|
// 8. Create and configure Fastify app
|
|
@@ -378,7 +425,7 @@ async function startDaemon(options = {}) {
|
|
|
378
425
|
app.get("/health", async () => ({
|
|
379
426
|
status: "ok",
|
|
380
427
|
service: "clawnexus-daemon",
|
|
381
|
-
version: "0.
|
|
428
|
+
version: "0.3.1",
|
|
382
429
|
timestamp: new Date().toISOString(),
|
|
383
430
|
components: {
|
|
384
431
|
registry: { instances: store.size },
|
|
@@ -401,12 +448,23 @@ async function startDaemon(options = {}) {
|
|
|
401
448
|
// Instance management routes
|
|
402
449
|
registerInstanceRoutes(app, store, scanner);
|
|
403
450
|
// Relay routes
|
|
404
|
-
registerRelayRoutes(app, () => connector)
|
|
451
|
+
registerRelayRoutes(app, () => connector, () => {
|
|
452
|
+
const rc = registryClient;
|
|
453
|
+
const clawName = autoRegister?.clawName;
|
|
454
|
+
if (!rc || !clawName)
|
|
455
|
+
return null;
|
|
456
|
+
return async () => {
|
|
457
|
+
const result = await rc.getToken(clawName);
|
|
458
|
+
return result.token;
|
|
459
|
+
};
|
|
460
|
+
});
|
|
405
461
|
// Agent routes (Layer B)
|
|
406
462
|
registerAgentRoutes(app, {
|
|
407
463
|
engine,
|
|
408
464
|
tasks: taskManager,
|
|
409
465
|
getRouter: () => agentRouter,
|
|
466
|
+
getExecutor: () => taskExecutor,
|
|
467
|
+
skillsRegistry,
|
|
410
468
|
});
|
|
411
469
|
// Diagnostics routes
|
|
412
470
|
registerDiagnosticsRoutes(app, {
|
|
@@ -420,7 +478,7 @@ async function startDaemon(options = {}) {
|
|
|
420
478
|
});
|
|
421
479
|
// A2A Agent Card routes
|
|
422
480
|
const daemonPkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "../../package.json"), "utf-8"));
|
|
423
|
-
registerA2aRoutes(app, store, daemonPkg.version);
|
|
481
|
+
registerA2aRoutes(app, store, daemonPkg.version, skillsRegistry);
|
|
424
482
|
// 9. Initialize Registry integration (non-fatal — LAN must work without it)
|
|
425
483
|
let identityKeys = null;
|
|
426
484
|
let registryClient = null;
|
|
@@ -431,6 +489,8 @@ async function startDaemon(options = {}) {
|
|
|
431
489
|
localProbe.on("local:discovered", (instance) => {
|
|
432
490
|
app.log.info({ agent_id: instance.agent_id }, "Local OpenClaw instance discovered");
|
|
433
491
|
broadcast.sendAnnounce();
|
|
492
|
+
// Start skills registry once we know the local Gateway is available
|
|
493
|
+
skillsRegistry.start();
|
|
434
494
|
});
|
|
435
495
|
localProbe.on("local:unavailable", () => {
|
|
436
496
|
app.log.info("No local OpenClaw instance on :18789");
|
|
@@ -447,10 +507,12 @@ async function startDaemon(options = {}) {
|
|
|
447
507
|
registryClient,
|
|
448
508
|
identityKeys,
|
|
449
509
|
});
|
|
510
|
+
let relayInitializing = false;
|
|
450
511
|
autoRegister.on("registered", async (info) => {
|
|
451
512
|
console.log(`[clawnexus] [Registry] Registered as ${info.claw_name} (${info.action})`);
|
|
452
|
-
// Initialize relay connector after successful registration
|
|
453
|
-
if (!connector && registryClient && info.claw_name) {
|
|
513
|
+
// Initialize relay connector after successful registration (once only)
|
|
514
|
+
if (!connector && !relayInitializing && registryClient && info.claw_name) {
|
|
515
|
+
relayInitializing = true;
|
|
454
516
|
try {
|
|
455
517
|
const tokenResult = await registryClient.getToken(info.claw_name);
|
|
456
518
|
console.log(`[clawnexus] [Relay] Got auth token, relay_hint: ${tokenResult.relay_hint}`);
|
|
@@ -478,7 +540,8 @@ async function startDaemon(options = {}) {
|
|
|
478
540
|
});
|
|
479
541
|
newConnector.connect();
|
|
480
542
|
setConnector(newConnector);
|
|
481
|
-
// Start token refresh — every
|
|
543
|
+
// Start token refresh — every 55 minutes (relay keeps WebSocket alive after auth,
|
|
544
|
+
// only need to refresh for reconnection scenarios)
|
|
482
545
|
if (tokenRefreshTimer)
|
|
483
546
|
clearInterval(tokenRefreshTimer);
|
|
484
547
|
tokenRefreshTimer = setInterval(async () => {
|
|
@@ -486,8 +549,16 @@ async function startDaemon(options = {}) {
|
|
|
486
549
|
return;
|
|
487
550
|
try {
|
|
488
551
|
const fresh = await registryClient.getToken(autoRegister.clawName);
|
|
489
|
-
//
|
|
490
|
-
|
|
552
|
+
// Save peer claw_ids from existing rooms before reconnecting
|
|
553
|
+
const previousPeers = [];
|
|
554
|
+
const oldConnector = connector;
|
|
555
|
+
if (oldConnector) {
|
|
556
|
+
for (const room of oldConnector.getStatus().rooms) {
|
|
557
|
+
if (room.peer_claw_id)
|
|
558
|
+
previousPeers.push(room.peer_claw_id);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// Connect new first, then disconnect old (relay server handles replacement)
|
|
491
562
|
const refreshed = new connector_js_1.RelayConnector({
|
|
492
563
|
relayUrl: process.env.CLAWNEXUS_RELAY_URL ?? `wss://${fresh.relay_hint}/relay`,
|
|
493
564
|
clawId: autoRegister.clawName,
|
|
@@ -496,6 +567,15 @@ async function startDaemon(options = {}) {
|
|
|
496
567
|
});
|
|
497
568
|
refreshed.on("registered", (clawId) => {
|
|
498
569
|
console.log(`[clawnexus] [Relay] Reconnected (token refresh) as ${clawId}`);
|
|
570
|
+
// Old connector is now replaced on server side — disconnect it locally
|
|
571
|
+
if (oldConnector && oldConnector !== refreshed) {
|
|
572
|
+
oldConnector.disconnect();
|
|
573
|
+
}
|
|
574
|
+
// Re-join rooms with previous peers
|
|
575
|
+
for (const peerId of previousPeers) {
|
|
576
|
+
console.log(`[clawnexus] [Relay] Re-joining peer ${peerId} after token refresh`);
|
|
577
|
+
refreshed.join(peerId);
|
|
578
|
+
}
|
|
499
579
|
});
|
|
500
580
|
refreshed.on("relay_error", (code, message) => {
|
|
501
581
|
console.log(`[clawnexus] [Relay] Error: ${code} — ${message}`);
|
|
@@ -506,7 +586,7 @@ async function startDaemon(options = {}) {
|
|
|
506
586
|
catch (err) {
|
|
507
587
|
console.log(`[clawnexus] [Relay] Token refresh failed (non-fatal): ${err}`);
|
|
508
588
|
}
|
|
509
|
-
},
|
|
589
|
+
}, 55 * 60 * 1000);
|
|
510
590
|
}
|
|
511
591
|
catch (err) {
|
|
512
592
|
console.log(`[clawnexus] [Relay] Failed to initialize (non-fatal): ${err}`);
|
|
@@ -552,6 +632,8 @@ async function startDaemon(options = {}) {
|
|
|
552
632
|
app.addHook("onClose", async () => {
|
|
553
633
|
if (tokenRefreshTimer)
|
|
554
634
|
clearInterval(tokenRefreshTimer);
|
|
635
|
+
skillsRegistry.stop();
|
|
636
|
+
await taskExecutor.close();
|
|
555
637
|
autoRegister?.stop();
|
|
556
638
|
agentRouter?.stop();
|
|
557
639
|
taskManager.close();
|
|
@@ -563,6 +645,8 @@ async function startDaemon(options = {}) {
|
|
|
563
645
|
await store.close();
|
|
564
646
|
});
|
|
565
647
|
await app.listen({ port, host });
|
|
648
|
+
// Start task executor (independent of relay — connects to local OpenClaw Gateway)
|
|
649
|
+
taskExecutor.start();
|
|
566
650
|
const setConnector = (c) => {
|
|
567
651
|
connector = c;
|
|
568
652
|
// When relay connector is set, create and start AgentRouter
|
|
@@ -572,9 +656,17 @@ async function startDaemon(options = {}) {
|
|
|
572
656
|
engine,
|
|
573
657
|
tasks: taskManager,
|
|
574
658
|
localClawId: c.getStatus().claw_id ?? "",
|
|
659
|
+
skillsRegistry,
|
|
575
660
|
});
|
|
576
661
|
agentRouter.start();
|
|
577
662
|
app.log.info("Layer B agent router started");
|
|
663
|
+
// Give executor the router reference so it can send reports
|
|
664
|
+
taskExecutor.setRouter(agentRouter);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
// Update existing router with new connector (e.g. after token refresh)
|
|
668
|
+
agentRouter.setConnector(c);
|
|
669
|
+
app.log.info("Layer B agent router connector updated");
|
|
578
670
|
}
|
|
579
671
|
};
|
|
580
672
|
return {
|
|
@@ -590,6 +682,7 @@ async function startDaemon(options = {}) {
|
|
|
590
682
|
engine,
|
|
591
683
|
tasks: taskManager,
|
|
592
684
|
getRouter: () => agentRouter,
|
|
685
|
+
skillsRegistry,
|
|
593
686
|
registryClient,
|
|
594
687
|
autoRegister,
|
|
595
688
|
remoteDiscovery,
|
|
@@ -59,25 +59,38 @@ class AutoRegister extends node_events_1.EventEmitter {
|
|
|
59
59
|
this.emit("skip", "No local OpenClaw instance detected");
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
-
//
|
|
62
|
+
// Build list of base names to try: agentId first, then auto_name as fallback
|
|
63
|
+
const selfInstance = this.store.getAll().find((i) => i.is_self);
|
|
64
|
+
const autoName = selfInstance?.auto_name;
|
|
65
|
+
const bases = [agentId];
|
|
66
|
+
if (autoName && autoName !== agentId)
|
|
67
|
+
bases.push(autoName);
|
|
68
|
+
// Try each base with suffixes -1, -2, ... up to MAX_SUFFIX if taken by another owner
|
|
63
69
|
const MAX_SUFFIX = 10;
|
|
64
70
|
let result = null;
|
|
65
|
-
for (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
outer: for (const base of bases) {
|
|
72
|
+
for (let i = 0; i <= MAX_SUFFIX; i++) {
|
|
73
|
+
const clawId = i === 0 ? base : `${base}-${i}`;
|
|
74
|
+
try {
|
|
75
|
+
result = await this.client.register({ claw_id: clawId });
|
|
76
|
+
break outer;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (err instanceof client_js_1.RegistryError && err.statusCode === 409 && i < MAX_SUFFIX) {
|
|
80
|
+
continue; // name taken by another owner, try next suffix
|
|
81
|
+
}
|
|
82
|
+
if (err instanceof client_js_1.RegistryError && err.statusCode === 409 && i === MAX_SUFFIX) {
|
|
83
|
+
break; // exhausted suffixes for this base, try next base
|
|
84
|
+
}
|
|
85
|
+
this.emit("error", err);
|
|
86
|
+
return;
|
|
74
87
|
}
|
|
75
|
-
this.emit("error", err);
|
|
76
|
-
return;
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
|
-
if (!result)
|
|
90
|
+
if (!result) {
|
|
91
|
+
this.emit("error", new Error(`All candidate names exhausted (bases: ${bases.join(", ")})`));
|
|
80
92
|
return;
|
|
93
|
+
}
|
|
81
94
|
this.registeredClawName = result.record.name;
|
|
82
95
|
// Start heartbeat on first successful registration
|
|
83
96
|
if (!this.heartbeatTimer) {
|
|
@@ -85,13 +98,12 @@ class AutoRegister extends node_events_1.EventEmitter {
|
|
|
85
98
|
this.tryRegister().catch(() => { });
|
|
86
99
|
}, HEARTBEAT_INTERVAL_MS);
|
|
87
100
|
}
|
|
88
|
-
// Write claw_name back to the local instance in store
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.store.upsert(selfInstance);
|
|
101
|
+
// Write claw_name back to the local instance in store (re-fetch after possible state change)
|
|
102
|
+
const registeredSelf = this.store.getAll().find((i) => i.is_self && i.agent_id === agentId);
|
|
103
|
+
if (registeredSelf) {
|
|
104
|
+
registeredSelf.claw_name = result.record.name;
|
|
105
|
+
registeredSelf.owner_pubkey = result.record.ownerPubkey;
|
|
106
|
+
this.store.upsert(registeredSelf);
|
|
95
107
|
}
|
|
96
108
|
this.emit("registered", {
|
|
97
109
|
action: result.action,
|
|
@@ -8,7 +8,7 @@ export interface RelayConnectorOptions {
|
|
|
8
8
|
autoAccept?: boolean;
|
|
9
9
|
}
|
|
10
10
|
export declare class RelayConnector extends EventEmitter {
|
|
11
|
-
private
|
|
11
|
+
private options;
|
|
12
12
|
private ws;
|
|
13
13
|
private state;
|
|
14
14
|
private rooms;
|
|
@@ -17,10 +17,14 @@ export declare class RelayConnector extends EventEmitter {
|
|
|
17
17
|
private reconnectTimer;
|
|
18
18
|
private closed;
|
|
19
19
|
constructor(options: RelayConnectorOptions);
|
|
20
|
+
/** Update the auth token (e.g. after token refresh). */
|
|
21
|
+
updateAuthToken(token: string): void;
|
|
20
22
|
/** Connect to the relay and REGISTER this claw_id. */
|
|
21
23
|
connect(): void;
|
|
22
24
|
/** Disconnect and stop reconnecting. */
|
|
23
25
|
disconnect(): void;
|
|
26
|
+
private pendingJoins;
|
|
27
|
+
private lastJoinTarget;
|
|
24
28
|
/** Initiate a connection to a remote claw_id through the relay. */
|
|
25
29
|
join(targetClawId: string): void;
|
|
26
30
|
/** Send an encrypted message to a peer in a room. */
|
package/dist/relay/connector.js
CHANGED
|
@@ -20,6 +20,10 @@ class RelayConnector extends node_events_1.EventEmitter {
|
|
|
20
20
|
this.options = options;
|
|
21
21
|
this.keyPair = (0, crypto_js_1.generateKeyPair)();
|
|
22
22
|
}
|
|
23
|
+
/** Update the auth token (e.g. after token refresh). */
|
|
24
|
+
updateAuthToken(token) {
|
|
25
|
+
this.options = { ...this.options, authToken: token };
|
|
26
|
+
}
|
|
23
27
|
/** Connect to the relay and REGISTER this claw_id. */
|
|
24
28
|
connect() {
|
|
25
29
|
if (this.state !== "disconnected")
|
|
@@ -66,8 +70,12 @@ class RelayConnector extends node_events_1.EventEmitter {
|
|
|
66
70
|
this.ws?.close();
|
|
67
71
|
this.cleanup();
|
|
68
72
|
}
|
|
73
|
+
// Tracks target claw_id for pending JOIN requests
|
|
74
|
+
pendingJoins = new Map(); // target_claw_id → target_claw_id (for JOINED room_id lookup)
|
|
75
|
+
lastJoinTarget = null;
|
|
69
76
|
/** Initiate a connection to a remote claw_id through the relay. */
|
|
70
77
|
join(targetClawId) {
|
|
78
|
+
this.lastJoinTarget = targetClawId;
|
|
71
79
|
this.send({
|
|
72
80
|
type: "JOIN",
|
|
73
81
|
claw_id: this.options.clawId,
|
|
@@ -78,8 +86,10 @@ class RelayConnector extends node_events_1.EventEmitter {
|
|
|
78
86
|
/** Send an encrypted message to a peer in a room. */
|
|
79
87
|
sendData(roomId, plaintext) {
|
|
80
88
|
const room = this.rooms.get(roomId);
|
|
81
|
-
if (!room || !room.session_key)
|
|
89
|
+
if (!room || !room.session_key) {
|
|
90
|
+
console.log(`[clawnexus] [Relay] sendData failed: room=${roomId} exists=${!!room} hasKey=${!!room?.session_key}`);
|
|
82
91
|
return false;
|
|
92
|
+
}
|
|
83
93
|
const payload = (0, crypto_js_1.encrypt)(room.session_key, plaintext);
|
|
84
94
|
this.send({ type: "DATA", room_id: roomId, payload });
|
|
85
95
|
return true;
|
|
@@ -98,6 +108,7 @@ class RelayConnector extends node_events_1.EventEmitter {
|
|
|
98
108
|
room_id: r.room_id,
|
|
99
109
|
peer_claw_id: r.peer_claw_id,
|
|
100
110
|
state: r.state,
|
|
111
|
+
has_session_key: !!r.session_key,
|
|
101
112
|
})),
|
|
102
113
|
};
|
|
103
114
|
}
|
|
@@ -139,7 +150,7 @@ class RelayConnector extends node_events_1.EventEmitter {
|
|
|
139
150
|
// We are the initiator — create room entry
|
|
140
151
|
this.rooms.set(msg.room_id, {
|
|
141
152
|
room_id: msg.room_id,
|
|
142
|
-
peer_claw_id: "",
|
|
153
|
+
peer_claw_id: this.lastJoinTarget ?? "",
|
|
143
154
|
state: "active",
|
|
144
155
|
});
|
|
145
156
|
// Send our public key
|