clawnexus 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2a/card.d.ts +1 -1
- package/dist/a2a/card.js +12 -7
- package/dist/a2a/fetcher.d.ts +26 -0
- package/dist/a2a/fetcher.js +152 -0
- package/dist/a2a/handler.d.ts +30 -0
- package/dist/a2a/handler.js +228 -0
- package/dist/a2a/store.d.ts +17 -0
- package/dist/a2a/store.js +134 -0
- package/dist/a2a/types.d.ts +45 -0
- package/dist/a2a/types.js +11 -0
- 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 +38 -4
- 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 +10 -2
- package/dist/api/server.js +198 -19
- package/dist/cli/index.js +6 -0
- package/dist/registry/auto-register.d.ts +5 -1
- package/dist/registry/auto-register.js +52 -21
- package/dist/registry/client.d.ts +17 -0
- package/dist/registry/client.js +2 -0
- package/dist/registry/store.js +10 -3
- package/dist/relay/connector.d.ts +5 -1
- package/dist/relay/connector.js +18 -3
- package/dist/types.d.ts +15 -1
- package/package.json +1 -1
package/dist/api/server.d.ts
CHANGED
|
@@ -8,17 +8,23 @@ import type { UnreachableInstance } from "../types.js";
|
|
|
8
8
|
import { PolicyEngine } from "../agent/engine.js";
|
|
9
9
|
import { TaskManager } from "../agent/tasks.js";
|
|
10
10
|
import { AgentRouter } from "../agent/router.js";
|
|
11
|
+
import { TaskExecutor } from "../agent/executor.js";
|
|
11
12
|
import { BroadcastDiscovery } from "../discovery/broadcast.js";
|
|
12
13
|
import type { IdentityKeys } from "../crypto/keys.js";
|
|
13
14
|
import { RegistryClient } from "../registry/client.js";
|
|
14
15
|
import { AutoRegister } from "../registry/auto-register.js";
|
|
15
16
|
import { RemoteDiscovery } from "../registry/discovery.js";
|
|
16
17
|
import { RelayConnector } from "../relay/connector.js";
|
|
17
|
-
|
|
18
|
+
import { CardFetcher } from "../a2a/fetcher.js";
|
|
19
|
+
import { A2AHandler } from "../a2a/handler.js";
|
|
20
|
+
import { SkillsRegistry } from "../agent/services.js";
|
|
21
|
+
export declare function registerRelayRoutes(app: FastifyInstance, getConnector: () => RelayConnector | null, getTokenRefresher?: () => (() => Promise<string>) | null): void;
|
|
18
22
|
export interface AgentDeps {
|
|
19
23
|
engine: PolicyEngine;
|
|
20
24
|
tasks: TaskManager;
|
|
21
25
|
getRouter: () => AgentRouter | null;
|
|
26
|
+
getExecutor: () => TaskExecutor | null;
|
|
27
|
+
skillsRegistry?: SkillsRegistry;
|
|
22
28
|
}
|
|
23
29
|
export declare function registerAgentRoutes(app: FastifyInstance, deps: AgentDeps): void;
|
|
24
30
|
export interface RegistryDeps {
|
|
@@ -39,7 +45,7 @@ export interface DiagnosticsDeps {
|
|
|
39
45
|
unreachable: UnreachableInstance[];
|
|
40
46
|
}
|
|
41
47
|
export declare function registerDiagnosticsRoutes(app: FastifyInstance, deps: DiagnosticsDeps): void;
|
|
42
|
-
export declare function registerA2aRoutes(app: FastifyInstance, store: RegistryStore, daemonVersion: string): void;
|
|
48
|
+
export declare function registerA2aRoutes(app: FastifyInstance, store: RegistryStore, daemonVersion: string, skillsRegistry?: SkillsRegistry, a2aHandler?: A2AHandler): void;
|
|
43
49
|
export interface DaemonOptions {
|
|
44
50
|
port?: number;
|
|
45
51
|
host?: string;
|
|
@@ -57,6 +63,8 @@ export interface DaemonHandle {
|
|
|
57
63
|
engine: PolicyEngine;
|
|
58
64
|
tasks: TaskManager;
|
|
59
65
|
getRouter: () => AgentRouter | null;
|
|
66
|
+
skillsRegistry: SkillsRegistry;
|
|
67
|
+
cardFetcher: CardFetcher;
|
|
60
68
|
registryClient: RegistryClient | null;
|
|
61
69
|
autoRegister: AutoRegister | null;
|
|
62
70
|
remoteDiscovery: RemoteDiscovery | null;
|
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,16 @@ 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 fetcher_js_1 = require("../a2a/fetcher.js");
|
|
34
|
+
const handler_js_1 = require("../a2a/handler.js");
|
|
35
|
+
const store_js_2 = require("../a2a/store.js");
|
|
36
|
+
const types_js_1 = require("../a2a/types.js");
|
|
37
|
+
const services_js_1 = require("../agent/services.js");
|
|
32
38
|
const node_fs_1 = require("node:fs");
|
|
33
39
|
const node_path_1 = require("node:path");
|
|
34
40
|
const PORT = parseInt(process.env.CLAWNEXUS_PORT ?? "17890", 10);
|
|
35
41
|
const HOST = process.env.CLAWNEXUS_HOST ?? "127.0.0.1";
|
|
36
|
-
function registerRelayRoutes(app, getConnector) {
|
|
42
|
+
function registerRelayRoutes(app, getConnector, getTokenRefresher) {
|
|
37
43
|
app.post("/relay/connect", async (request, reply) => {
|
|
38
44
|
const connector = getConnector();
|
|
39
45
|
if (!connector) {
|
|
@@ -47,6 +53,17 @@ function registerRelayRoutes(app, getConnector) {
|
|
|
47
53
|
error: "Missing target_claw_id",
|
|
48
54
|
});
|
|
49
55
|
}
|
|
56
|
+
// Refresh auth token before JOIN (relay JWTs expire after 5 min)
|
|
57
|
+
const refresher = getTokenRefresher?.();
|
|
58
|
+
if (refresher) {
|
|
59
|
+
try {
|
|
60
|
+
const freshToken = await refresher();
|
|
61
|
+
connector.updateAuthToken(freshToken);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
app.log.warn(`Token refresh before JOIN failed: ${err}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
50
67
|
connector.join(target_claw_id);
|
|
51
68
|
return { status: "connecting", target: target_claw_id };
|
|
52
69
|
});
|
|
@@ -71,7 +88,33 @@ function registerRelayRoutes(app, getConnector) {
|
|
|
71
88
|
});
|
|
72
89
|
}
|
|
73
90
|
function registerAgentRoutes(app, deps) {
|
|
74
|
-
const { engine, tasks, getRouter } = deps;
|
|
91
|
+
const { engine, tasks, getRouter, getExecutor, skillsRegistry } = deps;
|
|
92
|
+
// --- Skills ---
|
|
93
|
+
app.get("/agent/skills", async () => {
|
|
94
|
+
if (!skillsRegistry) {
|
|
95
|
+
return { skills: [], status: { source: "not_initialized" } };
|
|
96
|
+
}
|
|
97
|
+
return { skills: skillsRegistry.getSkills(), status: skillsRegistry.getStatus() };
|
|
98
|
+
});
|
|
99
|
+
app.post("/agent/skills/refresh", async () => {
|
|
100
|
+
if (!skillsRegistry) {
|
|
101
|
+
return { status: "error", message: "Skills registry not initialized" };
|
|
102
|
+
}
|
|
103
|
+
const ok = await skillsRegistry.refresh();
|
|
104
|
+
return {
|
|
105
|
+
status: ok ? "ok" : "error",
|
|
106
|
+
skills: skillsRegistry.getSkills(),
|
|
107
|
+
...skillsRegistry.getStatus(),
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
// --- Executor status ---
|
|
111
|
+
app.get("/agent/executor/status", async () => {
|
|
112
|
+
const executor = getExecutor();
|
|
113
|
+
if (!executor) {
|
|
114
|
+
return { gw_state: "not_initialized", queue_length: 0, executing: [], max_concurrent: 0 };
|
|
115
|
+
}
|
|
116
|
+
return executor.getStatus();
|
|
117
|
+
});
|
|
75
118
|
// --- Policy ---
|
|
76
119
|
app.get("/agent/policy", async () => engine.getConfig());
|
|
77
120
|
app.put("/agent/policy", async (request) => {
|
|
@@ -305,19 +348,20 @@ function registerDiagnosticsRoutes(app, deps) {
|
|
|
305
348
|
};
|
|
306
349
|
});
|
|
307
350
|
}
|
|
308
|
-
function registerA2aRoutes(app, store, daemonVersion) {
|
|
351
|
+
function registerA2aRoutes(app, store, daemonVersion, skillsRegistry, a2aHandler) {
|
|
309
352
|
// A2A standard well-known endpoint — returns card for the local (is_self) instance
|
|
310
353
|
app.get("/.well-known/agent-card.json", async (_request, reply) => {
|
|
311
354
|
const self = store.getAll().find((i) => i.is_self);
|
|
312
355
|
if (!self) {
|
|
313
356
|
return reply.status(404).send({ error: "No local instance discovered" });
|
|
314
357
|
}
|
|
315
|
-
return (0, card_js_1.buildAgentCard)(self, daemonVersion);
|
|
358
|
+
return (0, card_js_1.buildAgentCard)(self, daemonVersion, skillsRegistry?.getSkills());
|
|
316
359
|
});
|
|
317
360
|
// All instances as Agent Cards
|
|
318
361
|
app.get("/a2a/cards", async () => {
|
|
319
362
|
const instances = store.getAll();
|
|
320
|
-
const
|
|
363
|
+
const localSkills = skillsRegistry?.getSkills();
|
|
364
|
+
const cards = instances.map((i) => (0, card_js_1.buildAgentCard)(i, daemonVersion, i.is_self ? localSkills : undefined));
|
|
321
365
|
return { count: cards.length, cards };
|
|
322
366
|
});
|
|
323
367
|
// Single instance Agent Card by name
|
|
@@ -326,8 +370,69 @@ function registerA2aRoutes(app, store, daemonVersion) {
|
|
|
326
370
|
if (!inst) {
|
|
327
371
|
return reply.status(404).send({ error: "Instance not found" });
|
|
328
372
|
}
|
|
329
|
-
|
|
330
|
-
|
|
373
|
+
const localSkills = inst.is_self ? skillsRegistry?.getSkills() : undefined;
|
|
374
|
+
return (0, card_js_1.buildAgentCard)(inst, daemonVersion, localSkills);
|
|
375
|
+
});
|
|
376
|
+
// A2A JSON-RPC 2.0 endpoint
|
|
377
|
+
if (a2aHandler) {
|
|
378
|
+
app.post("/a2a", async (request, reply) => {
|
|
379
|
+
const body = request.body;
|
|
380
|
+
if (!body || body.jsonrpc !== "2.0" || typeof body.method !== "string") {
|
|
381
|
+
const id = body?.id ?? null;
|
|
382
|
+
const resp = {
|
|
383
|
+
jsonrpc: "2.0",
|
|
384
|
+
id: id,
|
|
385
|
+
error: { code: types_js_1.JSON_RPC_INVALID_REQUEST, message: "Invalid JSON-RPC 2.0 request" },
|
|
386
|
+
};
|
|
387
|
+
return reply.status(200).send(resp);
|
|
388
|
+
}
|
|
389
|
+
const req = body;
|
|
390
|
+
if (req.method === "tasks/send") {
|
|
391
|
+
const result = await a2aHandler.handleTaskSend(req.params);
|
|
392
|
+
// Check if result is an error
|
|
393
|
+
if ("code" in result && "message" in result && !("id" in result)) {
|
|
394
|
+
return reply.status(200).send({
|
|
395
|
+
jsonrpc: "2.0",
|
|
396
|
+
id: req.id,
|
|
397
|
+
error: result,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return reply.status(200).send({
|
|
401
|
+
jsonrpc: "2.0",
|
|
402
|
+
id: req.id,
|
|
403
|
+
result,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (req.method === "tasks/get") {
|
|
407
|
+
const params = req.params;
|
|
408
|
+
if (!params?.id) {
|
|
409
|
+
return reply.status(200).send({
|
|
410
|
+
jsonrpc: "2.0",
|
|
411
|
+
id: req.id,
|
|
412
|
+
error: { code: -32602, message: "Missing task id" },
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
const task = a2aHandler.getTask(params.id);
|
|
416
|
+
if (!task) {
|
|
417
|
+
return reply.status(200).send({
|
|
418
|
+
jsonrpc: "2.0",
|
|
419
|
+
id: req.id,
|
|
420
|
+
error: { code: -32001, message: "Task not found" },
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return reply.status(200).send({
|
|
424
|
+
jsonrpc: "2.0",
|
|
425
|
+
id: req.id,
|
|
426
|
+
result: task,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return reply.status(200).send({
|
|
430
|
+
jsonrpc: "2.0",
|
|
431
|
+
id: req.id,
|
|
432
|
+
error: { code: types_js_1.JSON_RPC_METHOD_NOT_FOUND, message: `Method not found: ${req.method}` },
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
}
|
|
331
436
|
}
|
|
332
437
|
async function startDaemon(options = {}) {
|
|
333
438
|
const port = options.port ?? PORT;
|
|
@@ -355,6 +460,15 @@ async function startDaemon(options = {}) {
|
|
|
355
460
|
await engine.init();
|
|
356
461
|
const taskManager = new tasks_js_1.TaskManager();
|
|
357
462
|
await taskManager.init();
|
|
463
|
+
// 6b. Create TaskExecutor
|
|
464
|
+
const taskExecutor = new executor_js_1.TaskExecutor({
|
|
465
|
+
tasks: taskManager,
|
|
466
|
+
maxConcurrent: engine.getConfig().max_concurrent_tasks,
|
|
467
|
+
});
|
|
468
|
+
// 6c. Create SkillsRegistry
|
|
469
|
+
const skillsRegistry = new services_js_1.SkillsRegistry();
|
|
470
|
+
// 6d. Create CardFetcher (fetches remote Agent Cards from discovered instances)
|
|
471
|
+
const cardFetcher = new fetcher_js_1.CardFetcher(store);
|
|
358
472
|
// 7. Detect WireGuard interfaces
|
|
359
473
|
const wgInfo = await (0, wireguard_js_1.detectWireGuard)();
|
|
360
474
|
// 8. Create and configure Fastify app
|
|
@@ -374,11 +488,12 @@ async function startDaemon(options = {}) {
|
|
|
374
488
|
else {
|
|
375
489
|
console.log("[clawnexus] [WireGuard] No WireGuard interfaces detected");
|
|
376
490
|
}
|
|
491
|
+
const daemonPkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "../../package.json"), "utf-8"));
|
|
377
492
|
// Health endpoint with component status
|
|
378
493
|
app.get("/health", async () => ({
|
|
379
494
|
status: "ok",
|
|
380
495
|
service: "clawnexus-daemon",
|
|
381
|
-
version:
|
|
496
|
+
version: daemonPkg.version,
|
|
382
497
|
timestamp: new Date().toISOString(),
|
|
383
498
|
components: {
|
|
384
499
|
registry: { instances: store.size },
|
|
@@ -401,12 +516,23 @@ async function startDaemon(options = {}) {
|
|
|
401
516
|
// Instance management routes
|
|
402
517
|
registerInstanceRoutes(app, store, scanner);
|
|
403
518
|
// Relay routes
|
|
404
|
-
registerRelayRoutes(app, () => connector)
|
|
519
|
+
registerRelayRoutes(app, () => connector, () => {
|
|
520
|
+
const rc = registryClient;
|
|
521
|
+
const clawName = autoRegister?.clawName;
|
|
522
|
+
if (!rc || !clawName)
|
|
523
|
+
return null;
|
|
524
|
+
return async () => {
|
|
525
|
+
const result = await rc.getToken(clawName);
|
|
526
|
+
return result.token;
|
|
527
|
+
};
|
|
528
|
+
});
|
|
405
529
|
// Agent routes (Layer B)
|
|
406
530
|
registerAgentRoutes(app, {
|
|
407
531
|
engine,
|
|
408
532
|
tasks: taskManager,
|
|
409
533
|
getRouter: () => agentRouter,
|
|
534
|
+
getExecutor: () => taskExecutor,
|
|
535
|
+
skillsRegistry,
|
|
410
536
|
});
|
|
411
537
|
// Diagnostics routes
|
|
412
538
|
registerDiagnosticsRoutes(app, {
|
|
@@ -418,9 +544,11 @@ async function startDaemon(options = {}) {
|
|
|
418
544
|
getAutoRegister: () => autoRegister,
|
|
419
545
|
unreachable,
|
|
420
546
|
});
|
|
421
|
-
// A2A Agent Card routes
|
|
422
|
-
const
|
|
423
|
-
|
|
547
|
+
// A2A Agent Card + JSON-RPC routes
|
|
548
|
+
const a2aTaskStore = new store_js_2.A2ATaskStore();
|
|
549
|
+
await a2aTaskStore.init();
|
|
550
|
+
const a2aHandler = new handler_js_1.A2AHandler({ store: a2aTaskStore });
|
|
551
|
+
registerA2aRoutes(app, store, daemonPkg.version, skillsRegistry, a2aHandler);
|
|
424
552
|
// 9. Initialize Registry integration (non-fatal — LAN must work without it)
|
|
425
553
|
let identityKeys = null;
|
|
426
554
|
let registryClient = null;
|
|
@@ -431,15 +559,29 @@ async function startDaemon(options = {}) {
|
|
|
431
559
|
localProbe.on("local:discovered", (instance) => {
|
|
432
560
|
app.log.info({ agent_id: instance.agent_id }, "Local OpenClaw instance discovered");
|
|
433
561
|
broadcast.sendAnnounce();
|
|
562
|
+
// Start skills registry once we know the local Gateway is available
|
|
563
|
+
skillsRegistry.start();
|
|
564
|
+
// Start fetching remote Agent Cards
|
|
565
|
+
cardFetcher.start();
|
|
434
566
|
});
|
|
435
567
|
localProbe.on("local:unavailable", () => {
|
|
436
568
|
app.log.info("No local OpenClaw instance on :18789");
|
|
569
|
+
// Still start CardFetcher — remote cards are useful even without local instance
|
|
570
|
+
cardFetcher.start();
|
|
437
571
|
});
|
|
438
572
|
// Initialize registry after LocalProbe (needs agentId for registration)
|
|
439
573
|
try {
|
|
440
574
|
identityKeys = await (0, keys_js_1.loadOrCreateKeys)();
|
|
441
575
|
registryClient = new client_js_1.RegistryClient(identityKeys);
|
|
442
|
-
autoRegister = new auto_register_js_1.AutoRegister(registryClient, store, localProbe, identityKeys)
|
|
576
|
+
autoRegister = new auto_register_js_1.AutoRegister(registryClient, store, localProbe, identityKeys, daemonPkg.version, () => {
|
|
577
|
+
const skills = skillsRegistry.getSkills();
|
|
578
|
+
if (!skills || skills.length === 0)
|
|
579
|
+
return null;
|
|
580
|
+
return {
|
|
581
|
+
skills_count: skills.length,
|
|
582
|
+
skills: skills.map((s) => s.name ?? s.id ?? "unknown"),
|
|
583
|
+
};
|
|
584
|
+
});
|
|
443
585
|
remoteDiscovery = new discovery_js_1.RemoteDiscovery(registryClient, store);
|
|
444
586
|
registerRegistryRoutes(app, {
|
|
445
587
|
autoRegister,
|
|
@@ -447,10 +589,12 @@ async function startDaemon(options = {}) {
|
|
|
447
589
|
registryClient,
|
|
448
590
|
identityKeys,
|
|
449
591
|
});
|
|
592
|
+
let relayInitializing = false;
|
|
450
593
|
autoRegister.on("registered", async (info) => {
|
|
451
594
|
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) {
|
|
595
|
+
// Initialize relay connector after successful registration (once only)
|
|
596
|
+
if (!connector && !relayInitializing && registryClient && info.claw_name) {
|
|
597
|
+
relayInitializing = true;
|
|
454
598
|
try {
|
|
455
599
|
const tokenResult = await registryClient.getToken(info.claw_name);
|
|
456
600
|
console.log(`[clawnexus] [Relay] Got auth token, relay_hint: ${tokenResult.relay_hint}`);
|
|
@@ -478,7 +622,8 @@ async function startDaemon(options = {}) {
|
|
|
478
622
|
});
|
|
479
623
|
newConnector.connect();
|
|
480
624
|
setConnector(newConnector);
|
|
481
|
-
// Start token refresh — every
|
|
625
|
+
// Start token refresh — every 55 minutes (relay keeps WebSocket alive after auth,
|
|
626
|
+
// only need to refresh for reconnection scenarios)
|
|
482
627
|
if (tokenRefreshTimer)
|
|
483
628
|
clearInterval(tokenRefreshTimer);
|
|
484
629
|
tokenRefreshTimer = setInterval(async () => {
|
|
@@ -486,8 +631,16 @@ async function startDaemon(options = {}) {
|
|
|
486
631
|
return;
|
|
487
632
|
try {
|
|
488
633
|
const fresh = await registryClient.getToken(autoRegister.clawName);
|
|
489
|
-
//
|
|
490
|
-
|
|
634
|
+
// Save peer claw_ids from existing rooms before reconnecting
|
|
635
|
+
const previousPeers = [];
|
|
636
|
+
const oldConnector = connector;
|
|
637
|
+
if (oldConnector) {
|
|
638
|
+
for (const room of oldConnector.getStatus().rooms) {
|
|
639
|
+
if (room.peer_claw_id)
|
|
640
|
+
previousPeers.push(room.peer_claw_id);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// Connect new first, then disconnect old (relay server handles replacement)
|
|
491
644
|
const refreshed = new connector_js_1.RelayConnector({
|
|
492
645
|
relayUrl: process.env.CLAWNEXUS_RELAY_URL ?? `wss://${fresh.relay_hint}/relay`,
|
|
493
646
|
clawId: autoRegister.clawName,
|
|
@@ -496,6 +649,15 @@ async function startDaemon(options = {}) {
|
|
|
496
649
|
});
|
|
497
650
|
refreshed.on("registered", (clawId) => {
|
|
498
651
|
console.log(`[clawnexus] [Relay] Reconnected (token refresh) as ${clawId}`);
|
|
652
|
+
// Old connector is now replaced on server side — disconnect it locally
|
|
653
|
+
if (oldConnector && oldConnector !== refreshed) {
|
|
654
|
+
oldConnector.disconnect();
|
|
655
|
+
}
|
|
656
|
+
// Re-join rooms with previous peers
|
|
657
|
+
for (const peerId of previousPeers) {
|
|
658
|
+
console.log(`[clawnexus] [Relay] Re-joining peer ${peerId} after token refresh`);
|
|
659
|
+
refreshed.join(peerId);
|
|
660
|
+
}
|
|
499
661
|
});
|
|
500
662
|
refreshed.on("relay_error", (code, message) => {
|
|
501
663
|
console.log(`[clawnexus] [Relay] Error: ${code} — ${message}`);
|
|
@@ -506,7 +668,7 @@ async function startDaemon(options = {}) {
|
|
|
506
668
|
catch (err) {
|
|
507
669
|
console.log(`[clawnexus] [Relay] Token refresh failed (non-fatal): ${err}`);
|
|
508
670
|
}
|
|
509
|
-
},
|
|
671
|
+
}, 55 * 60 * 1000);
|
|
510
672
|
}
|
|
511
673
|
catch (err) {
|
|
512
674
|
console.log(`[clawnexus] [Relay] Failed to initialize (non-fatal): ${err}`);
|
|
@@ -552,6 +714,9 @@ async function startDaemon(options = {}) {
|
|
|
552
714
|
app.addHook("onClose", async () => {
|
|
553
715
|
if (tokenRefreshTimer)
|
|
554
716
|
clearInterval(tokenRefreshTimer);
|
|
717
|
+
cardFetcher.stop();
|
|
718
|
+
skillsRegistry.stop();
|
|
719
|
+
await taskExecutor.close();
|
|
555
720
|
autoRegister?.stop();
|
|
556
721
|
agentRouter?.stop();
|
|
557
722
|
taskManager.close();
|
|
@@ -560,9 +725,13 @@ async function startDaemon(options = {}) {
|
|
|
560
725
|
mdns.stop();
|
|
561
726
|
await broadcast.stop();
|
|
562
727
|
connector?.disconnect();
|
|
728
|
+
a2aHandler.close();
|
|
729
|
+
await a2aTaskStore.close();
|
|
563
730
|
await store.close();
|
|
564
731
|
});
|
|
565
732
|
await app.listen({ port, host });
|
|
733
|
+
// Start task executor (independent of relay — connects to local OpenClaw Gateway)
|
|
734
|
+
taskExecutor.start();
|
|
566
735
|
const setConnector = (c) => {
|
|
567
736
|
connector = c;
|
|
568
737
|
// When relay connector is set, create and start AgentRouter
|
|
@@ -572,9 +741,17 @@ async function startDaemon(options = {}) {
|
|
|
572
741
|
engine,
|
|
573
742
|
tasks: taskManager,
|
|
574
743
|
localClawId: c.getStatus().claw_id ?? "",
|
|
744
|
+
skillsRegistry,
|
|
575
745
|
});
|
|
576
746
|
agentRouter.start();
|
|
577
747
|
app.log.info("Layer B agent router started");
|
|
748
|
+
// Give executor the router reference so it can send reports
|
|
749
|
+
taskExecutor.setRouter(agentRouter);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
// Update existing router with new connector (e.g. after token refresh)
|
|
753
|
+
agentRouter.setConnector(c);
|
|
754
|
+
app.log.info("Layer B agent router connector updated");
|
|
578
755
|
}
|
|
579
756
|
};
|
|
580
757
|
return {
|
|
@@ -590,6 +767,8 @@ async function startDaemon(options = {}) {
|
|
|
590
767
|
engine,
|
|
591
768
|
tasks: taskManager,
|
|
592
769
|
getRouter: () => agentRouter,
|
|
770
|
+
skillsRegistry,
|
|
771
|
+
cardFetcher,
|
|
593
772
|
registryClient,
|
|
594
773
|
autoRegister,
|
|
595
774
|
remoteDiscovery,
|
package/dist/cli/index.js
CHANGED
|
@@ -434,6 +434,12 @@ async function cmdInfo(args) {
|
|
|
434
434
|
if (inst.labels && Object.keys(inst.labels).length > 0) {
|
|
435
435
|
console.log(`Labels: ${JSON.stringify(inst.labels)}`);
|
|
436
436
|
}
|
|
437
|
+
if (inst.remote_card?.skills?.length) {
|
|
438
|
+
const names = inst.remote_card.skills.map((s) => s.name || s.id).join(", ");
|
|
439
|
+
console.log(`Skills: ${names} (${inst.remote_card.skills.length} skill${inst.remote_card.skills.length === 1 ? "" : "s"})`);
|
|
440
|
+
console.log(`Card URL: ${inst.remote_card.card_url}`);
|
|
441
|
+
console.log(`Card Fetched: ${new Date(inst.remote_card.fetched_at).toLocaleString()}`);
|
|
442
|
+
}
|
|
437
443
|
}
|
|
438
444
|
}
|
|
439
445
|
async function cmdForget(args) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import type { RegistryClient } from "./client.js";
|
|
3
|
+
import type { AgentCardSummary } from "./client.js";
|
|
3
4
|
import type { RegistryStore } from "./store.js";
|
|
4
5
|
import type { LocalProbe } from "../local/probe.js";
|
|
5
6
|
import type { IdentityKeys } from "../crypto/keys.js";
|
|
@@ -8,10 +9,13 @@ export declare class AutoRegister extends EventEmitter {
|
|
|
8
9
|
private readonly store;
|
|
9
10
|
private readonly localProbe;
|
|
10
11
|
private readonly keys;
|
|
12
|
+
private readonly daemonVersion;
|
|
13
|
+
private readonly getCardSummary?;
|
|
11
14
|
private heartbeatTimer;
|
|
12
15
|
private initialTimer;
|
|
13
16
|
private registeredClawName;
|
|
14
|
-
|
|
17
|
+
private readonly startedAt;
|
|
18
|
+
constructor(client: RegistryClient, store: RegistryStore, localProbe: LocalProbe, keys: IdentityKeys, daemonVersion?: string, getCardSummary?: (() => AgentCardSummary | null) | undefined);
|
|
15
19
|
get clawName(): string | null;
|
|
16
20
|
get publicKey(): string;
|
|
17
21
|
start(): void;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.AutoRegister = void 0;
|
|
6
6
|
const node_events_1 = require("node:events");
|
|
7
|
+
const node_os_1 = require("node:os");
|
|
7
8
|
const client_js_1 = require("./client.js");
|
|
8
9
|
const keys_js_1 = require("../crypto/keys.js");
|
|
9
10
|
const INITIAL_DELAY_MS = 5_000;
|
|
@@ -13,15 +14,20 @@ class AutoRegister extends node_events_1.EventEmitter {
|
|
|
13
14
|
store;
|
|
14
15
|
localProbe;
|
|
15
16
|
keys;
|
|
17
|
+
daemonVersion;
|
|
18
|
+
getCardSummary;
|
|
16
19
|
heartbeatTimer = null;
|
|
17
20
|
initialTimer = null;
|
|
18
21
|
registeredClawName = null;
|
|
19
|
-
|
|
22
|
+
startedAt = Date.now();
|
|
23
|
+
constructor(client, store, localProbe, keys, daemonVersion = "unknown", getCardSummary) {
|
|
20
24
|
super();
|
|
21
25
|
this.client = client;
|
|
22
26
|
this.store = store;
|
|
23
27
|
this.localProbe = localProbe;
|
|
24
28
|
this.keys = keys;
|
|
29
|
+
this.daemonVersion = daemonVersion;
|
|
30
|
+
this.getCardSummary = getCardSummary;
|
|
25
31
|
}
|
|
26
32
|
get clawName() {
|
|
27
33
|
return this.registeredClawName;
|
|
@@ -59,25 +65,51 @@ class AutoRegister extends node_events_1.EventEmitter {
|
|
|
59
65
|
this.emit("skip", "No local OpenClaw instance detected");
|
|
60
66
|
return;
|
|
61
67
|
}
|
|
62
|
-
//
|
|
68
|
+
// Build list of base names to try: agentId first, then auto_name as fallback
|
|
69
|
+
const selfInstance = this.store.getAll().find((i) => i.is_self);
|
|
70
|
+
const autoName = selfInstance?.auto_name;
|
|
71
|
+
const bases = [agentId];
|
|
72
|
+
if (autoName && autoName !== agentId)
|
|
73
|
+
bases.push(autoName);
|
|
74
|
+
// Build metadata and card summary for this heartbeat
|
|
75
|
+
const uptimeHours = Math.round((Date.now() - this.startedAt) / 3_600_000 * 100) / 100;
|
|
76
|
+
const metadata = {
|
|
77
|
+
software_version: this.daemonVersion,
|
|
78
|
+
uptime_hours: uptimeHours,
|
|
79
|
+
os_platform: (0, node_os_1.platform)(),
|
|
80
|
+
instance_count: this.store.getAll().length,
|
|
81
|
+
};
|
|
82
|
+
const cardSummary = this.getCardSummary?.() ?? undefined;
|
|
83
|
+
// Try each base with suffixes -1, -2, ... up to MAX_SUFFIX if taken by another owner
|
|
63
84
|
const MAX_SUFFIX = 10;
|
|
64
85
|
let result = null;
|
|
65
|
-
for (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
outer: for (const base of bases) {
|
|
87
|
+
for (let i = 0; i <= MAX_SUFFIX; i++) {
|
|
88
|
+
const clawId = i === 0 ? base : `${base}-${i}`;
|
|
89
|
+
try {
|
|
90
|
+
result = await this.client.register({
|
|
91
|
+
claw_id: clawId,
|
|
92
|
+
metadata,
|
|
93
|
+
agent_card: cardSummary,
|
|
94
|
+
});
|
|
95
|
+
break outer;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
if (err instanceof client_js_1.RegistryError && err.statusCode === 409 && i < MAX_SUFFIX) {
|
|
99
|
+
continue; // name taken by another owner, try next suffix
|
|
100
|
+
}
|
|
101
|
+
if (err instanceof client_js_1.RegistryError && err.statusCode === 409 && i === MAX_SUFFIX) {
|
|
102
|
+
break; // exhausted suffixes for this base, try next base
|
|
103
|
+
}
|
|
104
|
+
this.emit("error", err);
|
|
105
|
+
return;
|
|
74
106
|
}
|
|
75
|
-
this.emit("error", err);
|
|
76
|
-
return;
|
|
77
107
|
}
|
|
78
108
|
}
|
|
79
|
-
if (!result)
|
|
109
|
+
if (!result) {
|
|
110
|
+
this.emit("error", new Error(`All candidate names exhausted (bases: ${bases.join(", ")})`));
|
|
80
111
|
return;
|
|
112
|
+
}
|
|
81
113
|
this.registeredClawName = result.record.name;
|
|
82
114
|
// Start heartbeat on first successful registration
|
|
83
115
|
if (!this.heartbeatTimer) {
|
|
@@ -85,13 +117,12 @@ class AutoRegister extends node_events_1.EventEmitter {
|
|
|
85
117
|
this.tryRegister().catch(() => { });
|
|
86
118
|
}, HEARTBEAT_INTERVAL_MS);
|
|
87
119
|
}
|
|
88
|
-
// Write claw_name back to the local instance in store
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.store.upsert(selfInstance);
|
|
120
|
+
// Write claw_name back to the local instance in store (re-fetch after possible state change)
|
|
121
|
+
const registeredSelf = this.store.getAll().find((i) => i.is_self && i.agent_id === agentId);
|
|
122
|
+
if (registeredSelf) {
|
|
123
|
+
registeredSelf.claw_name = result.record.name;
|
|
124
|
+
registeredSelf.owner_pubkey = result.record.ownerPubkey;
|
|
125
|
+
this.store.upsert(registeredSelf);
|
|
95
126
|
}
|
|
96
127
|
this.emit("registered", {
|
|
97
128
|
action: result.action,
|
|
@@ -33,11 +33,28 @@ export interface CheckNameResult {
|
|
|
33
33
|
name: string;
|
|
34
34
|
available: boolean;
|
|
35
35
|
}
|
|
36
|
+
export interface InstanceMetadata {
|
|
37
|
+
software_version: string;
|
|
38
|
+
openclaw_version?: string;
|
|
39
|
+
uptime_hours: number;
|
|
40
|
+
os_platform: string;
|
|
41
|
+
instance_count: number;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentCardSummary {
|
|
44
|
+
skills_count: number;
|
|
45
|
+
skills: string[];
|
|
46
|
+
capabilities?: Record<string, unknown>;
|
|
47
|
+
input_modes?: string[];
|
|
48
|
+
output_modes?: string[];
|
|
49
|
+
card_url?: string;
|
|
50
|
+
}
|
|
36
51
|
export interface RegisterParams {
|
|
37
52
|
claw_id: string;
|
|
38
53
|
capabilities?: string[];
|
|
39
54
|
relay_hint?: string;
|
|
40
55
|
visibility?: "public" | "unlisted";
|
|
56
|
+
metadata?: InstanceMetadata;
|
|
57
|
+
agent_card?: AgentCardSummary;
|
|
41
58
|
}
|
|
42
59
|
export declare class RegistryClient {
|
|
43
60
|
private readonly keys;
|
package/dist/registry/client.js
CHANGED
|
@@ -36,6 +36,8 @@ class RegistryClient {
|
|
|
36
36
|
...(params.capabilities && { capabilities: params.capabilities }),
|
|
37
37
|
...(params.relay_hint && { relay_hint: params.relay_hint }),
|
|
38
38
|
...(params.visibility && { visibility: params.visibility }),
|
|
39
|
+
...(params.metadata && { metadata: params.metadata }),
|
|
40
|
+
...(params.agent_card && { agent_card: params.agent_card }),
|
|
39
41
|
};
|
|
40
42
|
const body = {
|
|
41
43
|
payload,
|
package/dist/registry/store.js
CHANGED
|
@@ -65,12 +65,16 @@ class RegistryStore extends node_events_1.EventEmitter {
|
|
|
65
65
|
try {
|
|
66
66
|
const raw = await fs.promises.readFile(this.registryPath, "utf-8");
|
|
67
67
|
const data = JSON.parse(raw);
|
|
68
|
-
if (data.schema_version === "5" && Array.isArray(data.instances)) {
|
|
69
|
-
// v5:
|
|
68
|
+
if ((data.schema_version === "6" || data.schema_version === "5") && Array.isArray(data.instances)) {
|
|
69
|
+
// v5/v6: compatible schemas (remote_card is optional), load directly
|
|
70
70
|
for (const inst of data.instances) {
|
|
71
71
|
const key = this.networkKey(inst.address, inst.gateway_port);
|
|
72
72
|
this.instances.set(key, inst);
|
|
73
73
|
}
|
|
74
|
+
if (data.schema_version === "5") {
|
|
75
|
+
// Bump to v6 on next flush
|
|
76
|
+
this.scheduleDirtyFlush();
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
79
|
else if (data.schema_version === "4" && Array.isArray(data.instances)) {
|
|
76
80
|
// v4 → v5 migration: no data changes, just bump version
|
|
@@ -203,6 +207,8 @@ class RegistryStore extends node_events_1.EventEmitter {
|
|
|
203
207
|
instance.owner_pubkey = instance.owner_pubkey ?? existing.owner_pubkey;
|
|
204
208
|
// Preserve implementation (prefer new value if provided)
|
|
205
209
|
instance.implementation = instance.implementation ?? existing.implementation;
|
|
210
|
+
// Preserve remote_card (fetched by CardFetcher)
|
|
211
|
+
instance.remote_card = instance.remote_card ?? existing.remote_card;
|
|
206
212
|
this.instances.set(key, instance);
|
|
207
213
|
this.scheduleDirtyFlush();
|
|
208
214
|
this.emit("upsert", instance);
|
|
@@ -318,6 +324,7 @@ class RegistryStore extends node_events_1.EventEmitter {
|
|
|
318
324
|
connectivity: incoming.connectivity ?? existing.connectivity,
|
|
319
325
|
is_self: existing.is_self || incoming.is_self,
|
|
320
326
|
implementation: incoming.implementation ?? existing.implementation,
|
|
327
|
+
remote_card: existing.remote_card ?? incoming.remote_card,
|
|
321
328
|
};
|
|
322
329
|
}
|
|
323
330
|
/** Numeric priority for network scope: local > vpn > public */
|
|
@@ -343,7 +350,7 @@ class RegistryStore extends node_events_1.EventEmitter {
|
|
|
343
350
|
}
|
|
344
351
|
async flushNow() {
|
|
345
352
|
const data = {
|
|
346
|
-
schema_version: "
|
|
353
|
+
schema_version: "6",
|
|
347
354
|
updated_at: new Date().toISOString(),
|
|
348
355
|
instances: Array.from(this.instances.values()),
|
|
349
356
|
};
|