clawnexus 0.2.7 → 0.3.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 +29 -0
- package/dist/a2a/card.js +30 -0
- package/dist/adapter/index.d.ts +4 -0
- package/dist/adapter/index.js +28 -0
- package/dist/adapter/nanobot.d.ts +12 -0
- package/dist/adapter/nanobot.js +97 -0
- package/dist/adapter/nanoclaw.d.ts +27 -0
- package/dist/adapter/nanoclaw.js +260 -0
- package/dist/adapter/openclaw.d.ts +9 -0
- package/dist/adapter/openclaw.js +62 -0
- package/dist/adapter/openfang.d.ts +11 -0
- package/dist/adapter/openfang.js +88 -0
- package/dist/adapter/types.d.ts +16 -0
- package/dist/adapter/types.js +4 -0
- package/dist/api/server.d.ts +1 -0
- package/dist/api/server.js +31 -0
- package/dist/cli/index.js +7 -2
- package/dist/discovery/broadcast.js +5 -4
- package/dist/health/checker.d.ts +4 -0
- package/dist/health/checker.js +84 -20
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -1
- package/dist/local/probe.d.ts +4 -0
- package/dist/local/probe.js +127 -12
- package/dist/registry/store.js +3 -0
- package/dist/scanner/active.d.ts +1 -0
- package/dist/scanner/active.js +73 -19
- package/dist/scanner/fingerprint.d.ts +22 -0
- package/dist/scanner/fingerprint.js +135 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/api/server.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export interface DiagnosticsDeps {
|
|
|
39
39
|
unreachable: UnreachableInstance[];
|
|
40
40
|
}
|
|
41
41
|
export declare function registerDiagnosticsRoutes(app: FastifyInstance, deps: DiagnosticsDeps): void;
|
|
42
|
+
export declare function registerA2aRoutes(app: FastifyInstance, store: RegistryStore, daemonVersion: string): void;
|
|
42
43
|
export interface DaemonOptions {
|
|
43
44
|
port?: number;
|
|
44
45
|
host?: string;
|
package/dist/api/server.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.registerAgentRoutes = registerAgentRoutes;
|
|
|
10
10
|
exports.registerRegistryRoutes = registerRegistryRoutes;
|
|
11
11
|
exports.registerInstanceRoutes = registerInstanceRoutes;
|
|
12
12
|
exports.registerDiagnosticsRoutes = registerDiagnosticsRoutes;
|
|
13
|
+
exports.registerA2aRoutes = registerA2aRoutes;
|
|
13
14
|
exports.startDaemon = startDaemon;
|
|
14
15
|
const fastify_1 = __importDefault(require("fastify"));
|
|
15
16
|
const store_js_1 = require("../registry/store.js");
|
|
@@ -27,6 +28,9 @@ const client_js_1 = require("../registry/client.js");
|
|
|
27
28
|
const auto_register_js_1 = require("../registry/auto-register.js");
|
|
28
29
|
const discovery_js_1 = require("../registry/discovery.js");
|
|
29
30
|
const connector_js_1 = require("../relay/connector.js");
|
|
31
|
+
const card_js_1 = require("../a2a/card.js");
|
|
32
|
+
const node_fs_1 = require("node:fs");
|
|
33
|
+
const node_path_1 = require("node:path");
|
|
30
34
|
const PORT = parseInt(process.env.CLAWNEXUS_PORT ?? "17890", 10);
|
|
31
35
|
const HOST = process.env.CLAWNEXUS_HOST ?? "127.0.0.1";
|
|
32
36
|
function registerRelayRoutes(app, getConnector) {
|
|
@@ -301,6 +305,30 @@ function registerDiagnosticsRoutes(app, deps) {
|
|
|
301
305
|
};
|
|
302
306
|
});
|
|
303
307
|
}
|
|
308
|
+
function registerA2aRoutes(app, store, daemonVersion) {
|
|
309
|
+
// A2A standard well-known endpoint — returns card for the local (is_self) instance
|
|
310
|
+
app.get("/.well-known/agent-card.json", async (_request, reply) => {
|
|
311
|
+
const self = store.getAll().find((i) => i.is_self);
|
|
312
|
+
if (!self) {
|
|
313
|
+
return reply.status(404).send({ error: "No local instance discovered" });
|
|
314
|
+
}
|
|
315
|
+
return (0, card_js_1.buildAgentCard)(self, daemonVersion);
|
|
316
|
+
});
|
|
317
|
+
// All instances as Agent Cards
|
|
318
|
+
app.get("/a2a/cards", async () => {
|
|
319
|
+
const instances = store.getAll();
|
|
320
|
+
const cards = instances.map((i) => (0, card_js_1.buildAgentCard)(i, daemonVersion));
|
|
321
|
+
return { count: cards.length, cards };
|
|
322
|
+
});
|
|
323
|
+
// Single instance Agent Card by name
|
|
324
|
+
app.get("/a2a/cards/:name", async (request, reply) => {
|
|
325
|
+
const inst = store.resolve(request.params.name);
|
|
326
|
+
if (!inst) {
|
|
327
|
+
return reply.status(404).send({ error: "Instance not found" });
|
|
328
|
+
}
|
|
329
|
+
return (0, card_js_1.buildAgentCard)(inst, daemonVersion);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
304
332
|
async function startDaemon(options = {}) {
|
|
305
333
|
const port = options.port ?? PORT;
|
|
306
334
|
const host = options.host ?? HOST;
|
|
@@ -390,6 +418,9 @@ async function startDaemon(options = {}) {
|
|
|
390
418
|
getAutoRegister: () => autoRegister,
|
|
391
419
|
unreachable,
|
|
392
420
|
});
|
|
421
|
+
// A2A Agent Card routes
|
|
422
|
+
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);
|
|
393
424
|
// 9. Initialize Registry integration (non-fatal — LAN must work without it)
|
|
394
425
|
let identityKeys = null;
|
|
395
426
|
let registryClient = null;
|
package/dist/cli/index.js
CHANGED
|
@@ -156,6 +156,7 @@ function printTable(instances) {
|
|
|
156
156
|
}
|
|
157
157
|
const header = {
|
|
158
158
|
name: "NAME",
|
|
159
|
+
impl: "IMPL",
|
|
159
160
|
address: "ADDRESS",
|
|
160
161
|
status: "STATUS",
|
|
161
162
|
channel: "CHANNEL",
|
|
@@ -166,6 +167,7 @@ function printTable(instances) {
|
|
|
166
167
|
const baseName = i.alias ?? i.auto_name;
|
|
167
168
|
return {
|
|
168
169
|
name: i.is_self ? `${baseName} (self)` : baseName,
|
|
170
|
+
impl: i.implementation ?? "-",
|
|
169
171
|
address: `${i.address}:${i.gateway_port}`,
|
|
170
172
|
status: i.status,
|
|
171
173
|
channel: getChannel(i),
|
|
@@ -175,15 +177,16 @@ function printTable(instances) {
|
|
|
175
177
|
});
|
|
176
178
|
const colWidths = {
|
|
177
179
|
name: Math.max(header.name.length, ...rows.map((r) => r.name.length)),
|
|
180
|
+
impl: Math.max(header.impl.length, ...rows.map((r) => r.impl.length)),
|
|
178
181
|
address: Math.max(header.address.length, ...rows.map((r) => r.address.length)),
|
|
179
182
|
status: Math.max(header.status.length, ...rows.map((r) => r.status.length)),
|
|
180
183
|
channel: Math.max(header.channel.length, ...rows.map((r) => r.channel.length)),
|
|
181
184
|
source: Math.max(header.source.length, ...rows.map((r) => r.source.length)),
|
|
182
185
|
lastSeen: Math.max(header.lastSeen.length, ...rows.map((r) => r.lastSeen.length)),
|
|
183
186
|
};
|
|
184
|
-
const line = (r) => `${r.name.padEnd(colWidths.name)} ${r.address.padEnd(colWidths.address)} ${r.status.padEnd(colWidths.status)} ${r.channel.padEnd(colWidths.channel)} ${r.source.padEnd(colWidths.source)} ${r.lastSeen}`;
|
|
187
|
+
const line = (r) => `${r.name.padEnd(colWidths.name)} ${r.impl.padEnd(colWidths.impl)} ${r.address.padEnd(colWidths.address)} ${r.status.padEnd(colWidths.status)} ${r.channel.padEnd(colWidths.channel)} ${r.source.padEnd(colWidths.source)} ${r.lastSeen}`;
|
|
185
188
|
console.log(line(header));
|
|
186
|
-
console.log("-".repeat(colWidths.name + colWidths.address + colWidths.status + colWidths.channel + colWidths.source + colWidths.lastSeen +
|
|
189
|
+
console.log("-".repeat(colWidths.name + colWidths.impl + colWidths.address + colWidths.status + colWidths.channel + colWidths.source + colWidths.lastSeen + 12));
|
|
187
190
|
for (const row of rows) {
|
|
188
191
|
console.log(line(row));
|
|
189
192
|
}
|
|
@@ -404,6 +407,8 @@ async function cmdInfo(args) {
|
|
|
404
407
|
const inst = data;
|
|
405
408
|
console.log(`Auto Name: ${inst.auto_name}${inst.is_self ? " (self)" : ""}`);
|
|
406
409
|
console.log(`Agent ID: ${inst.agent_id}`);
|
|
410
|
+
if (inst.implementation)
|
|
411
|
+
console.log(`Implementation: ${inst.implementation}`);
|
|
407
412
|
console.log(`Display Name: ${inst.display_name}`);
|
|
408
413
|
console.log(`Assistant: ${inst.assistant_name}`);
|
|
409
414
|
if (inst.alias)
|
|
@@ -46,7 +46,6 @@ const CDP_PORT = 17891;
|
|
|
46
46
|
const TCP_PROBE_TIMEOUT = 2_000;
|
|
47
47
|
const ANNOUNCE_INTERVAL_BASE = 60_000;
|
|
48
48
|
const ANNOUNCE_JITTER = 10_000;
|
|
49
|
-
const CONFIG_PATH = "/__openclaw/control-ui-config.json";
|
|
50
49
|
class BroadcastDiscovery extends node_events_1.EventEmitter {
|
|
51
50
|
store;
|
|
52
51
|
getLocalInstance;
|
|
@@ -274,12 +273,14 @@ class BroadcastDiscovery extends node_events_1.EventEmitter {
|
|
|
274
273
|
});
|
|
275
274
|
}
|
|
276
275
|
async _tcpProbe(address, port) {
|
|
277
|
-
|
|
276
|
+
// Quick HTTP liveness check — just confirm the port is alive.
|
|
277
|
+
// Framework identification is left to HealthChecker / subsequent scans.
|
|
278
278
|
try {
|
|
279
|
-
const res = await fetch(
|
|
279
|
+
const res = await fetch(`http://${address}:${port}/`, {
|
|
280
280
|
signal: AbortSignal.timeout(TCP_PROBE_TIMEOUT),
|
|
281
281
|
});
|
|
282
|
-
|
|
282
|
+
// Any HTTP response (including 404, 500) means the port is alive
|
|
283
|
+
return res.status > 0;
|
|
283
284
|
}
|
|
284
285
|
catch {
|
|
285
286
|
return false;
|
package/dist/health/checker.d.ts
CHANGED
|
@@ -11,4 +11,8 @@ export declare class HealthChecker extends EventEmitter {
|
|
|
11
11
|
stop(): void;
|
|
12
12
|
checkAll(): Promise<void>;
|
|
13
13
|
private checkOne;
|
|
14
|
+
/** OpenClaw-specific health check with name updates */
|
|
15
|
+
private checkOpenClaw;
|
|
16
|
+
/** Fallback health check for instances without a known adapter */
|
|
17
|
+
private genericHttpCheck;
|
|
14
18
|
}
|
package/dist/health/checker.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Health Checker — periodically pings known instances and updates status
|
|
3
3
|
// Enhanced with dual-channel connectivity detection
|
|
4
|
+
// Adapter-aware: uses framework-specific health checks for non-OpenClaw instances
|
|
4
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
6
|
exports.HealthChecker = void 0;
|
|
6
7
|
const node_events_1 = require("node:events");
|
|
8
|
+
const index_js_1 = require("../adapter/index.js");
|
|
7
9
|
const CHECK_INTERVAL = 30_000;
|
|
8
10
|
const PING_TIMEOUT = 5_000;
|
|
9
11
|
const CONFIG_PATH = "/__openclaw/control-ui-config.json";
|
|
@@ -41,36 +43,51 @@ class HealthChecker extends node_events_1.EventEmitter {
|
|
|
41
43
|
if (inst.is_self)
|
|
42
44
|
return;
|
|
43
45
|
const networkKey = this.store.networkKey(inst.address, inst.gateway_port);
|
|
44
|
-
const
|
|
45
|
-
const url = `${protocol}://${inst.address}:${inst.gateway_port}${CONFIG_PATH}`;
|
|
46
|
+
const impl = inst.implementation ?? "openclaw";
|
|
46
47
|
const now = new Date().toISOString();
|
|
47
48
|
let lanOk = false;
|
|
48
49
|
let lanLatency;
|
|
49
50
|
let unreachableReason;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
if (impl === "openclaw" || impl === "goclaw") {
|
|
52
|
+
// OpenClaw path: detailed check with name updates (original behavior)
|
|
53
|
+
const result = await this.checkOpenClaw(inst, now);
|
|
54
|
+
lanOk = result.ok;
|
|
55
|
+
lanLatency = result.latency;
|
|
56
|
+
unreachableReason = result.reason;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Non-OpenClaw: use adapter healthCheck
|
|
60
|
+
const adapter = (0, index_js_1.getAdapter)(impl);
|
|
61
|
+
if (adapter) {
|
|
62
|
+
try {
|
|
63
|
+
const start = performance.now();
|
|
64
|
+
// Use healthCheckLocal for port-0 instances (no HTTP server)
|
|
65
|
+
if (inst.gateway_port === 0 && adapter.healthCheckLocal) {
|
|
66
|
+
lanOk = await adapter.healthCheckLocal();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
lanOk = await adapter.healthCheck(inst.address, inst.gateway_port);
|
|
70
|
+
}
|
|
71
|
+
lanLatency = Math.round(performance.now() - start);
|
|
72
|
+
if (lanOk) {
|
|
73
|
+
inst.last_seen = now;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
unreachableReason = "Health check failed";
|
|
77
|
+
}
|
|
62
78
|
}
|
|
63
|
-
|
|
64
|
-
|
|
79
|
+
catch (err) {
|
|
80
|
+
unreachableReason = err instanceof Error ? err.message : "Connection failed";
|
|
65
81
|
}
|
|
66
82
|
}
|
|
67
83
|
else {
|
|
68
|
-
|
|
84
|
+
// Unknown implementation: generic HTTP check
|
|
85
|
+
const result = await this.genericHttpCheck(inst, now);
|
|
86
|
+
lanOk = result.ok;
|
|
87
|
+
lanLatency = result.latency;
|
|
88
|
+
unreachableReason = result.reason;
|
|
69
89
|
}
|
|
70
90
|
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
unreachableReason = err instanceof Error ? err.message : "Connection failed";
|
|
73
|
-
}
|
|
74
91
|
// Check relay availability
|
|
75
92
|
const relayAvailable = this.relayChecker?.(inst.agent_id) ?? false;
|
|
76
93
|
// Update connectivity
|
|
@@ -104,5 +121,52 @@ class HealthChecker extends node_events_1.EventEmitter {
|
|
|
104
121
|
});
|
|
105
122
|
}
|
|
106
123
|
}
|
|
124
|
+
/** OpenClaw-specific health check with name updates */
|
|
125
|
+
async checkOpenClaw(inst, now) {
|
|
126
|
+
const protocol = inst.tls ? "https" : "http";
|
|
127
|
+
const url = `${protocol}://${inst.address}:${inst.gateway_port}${CONFIG_PATH}`;
|
|
128
|
+
try {
|
|
129
|
+
const start = performance.now();
|
|
130
|
+
const res = await fetch(url, {
|
|
131
|
+
signal: AbortSignal.timeout(PING_TIMEOUT),
|
|
132
|
+
});
|
|
133
|
+
const latency = Math.round(performance.now() - start);
|
|
134
|
+
if (res.ok) {
|
|
135
|
+
const config = (await res.json());
|
|
136
|
+
inst.last_seen = now;
|
|
137
|
+
if (config.assistantName) {
|
|
138
|
+
inst.assistant_name = config.assistantName;
|
|
139
|
+
}
|
|
140
|
+
if (config.displayName) {
|
|
141
|
+
inst.display_name = config.displayName;
|
|
142
|
+
}
|
|
143
|
+
return { ok: true, latency };
|
|
144
|
+
}
|
|
145
|
+
return { ok: false, latency, reason: `HTTP ${res.status}` };
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
return { ok: false, reason: err instanceof Error ? err.message : "Connection failed" };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Fallback health check for instances without a known adapter */
|
|
152
|
+
async genericHttpCheck(inst, now) {
|
|
153
|
+
const protocol = inst.tls ? "https" : "http";
|
|
154
|
+
const url = `${protocol}://${inst.address}:${inst.gateway_port}/`;
|
|
155
|
+
try {
|
|
156
|
+
const start = performance.now();
|
|
157
|
+
const res = await fetch(url, {
|
|
158
|
+
signal: AbortSignal.timeout(PING_TIMEOUT),
|
|
159
|
+
});
|
|
160
|
+
const latency = Math.round(performance.now() - start);
|
|
161
|
+
if (res.ok) {
|
|
162
|
+
inst.last_seen = now;
|
|
163
|
+
return { ok: true, latency };
|
|
164
|
+
}
|
|
165
|
+
return { ok: false, latency, reason: `HTTP ${res.status}` };
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
return { ok: false, reason: err instanceof Error ? err.message : "Connection failed" };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
107
171
|
}
|
|
108
172
|
exports.HealthChecker = HealthChecker;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export { HealthChecker } from './health/checker.js';
|
|
|
6
6
|
export { LocalProbe } from './local/probe.js';
|
|
7
7
|
export { ActiveScanner } from './scanner/active.js';
|
|
8
8
|
export type { ScanOptions } from './scanner/active.js';
|
|
9
|
+
export { ADAPTERS, getAdapter, getAllAdapterPorts } from './adapter/index.js';
|
|
10
|
+
export { NanoClawAdapter } from './adapter/nanoclaw.js';
|
|
11
|
+
export { NanoBotAdapter } from './adapter/nanobot.js';
|
|
12
|
+
export type { FrameworkAdapter, ProbeResult } from './adapter/types.js';
|
|
9
13
|
export { BroadcastDiscovery } from './discovery/broadcast.js';
|
|
10
14
|
export { registerRelayRoutes, registerInstanceRoutes, registerAgentRoutes, registerDiagnosticsRoutes, registerRegistryRoutes, startDaemon } from './api/server.js';
|
|
11
15
|
export type { DaemonOptions, DaemonHandle, AgentDeps, DiagnosticsDeps, RegistryDeps } from './api/server.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ProtocolError = exports.isExpired = exports.validatePayload = exports.parseEnvelope = exports.createEnvelope = exports.AgentRouter = exports.TaskManager = exports.PolicyEngine = exports.RelayConnector = exports.RemoteDiscovery = exports.AutoRegister = exports.RegistryError = exports.RegistryClient = exports.getPublicKeyString = exports.sign = exports.loadOrCreateKeys = exports.startDaemon = exports.registerRegistryRoutes = exports.registerDiagnosticsRoutes = exports.registerAgentRoutes = exports.registerInstanceRoutes = exports.registerRelayRoutes = exports.BroadcastDiscovery = exports.ActiveScanner = exports.LocalProbe = exports.HealthChecker = exports.MdnsListener = exports.NotFoundError = exports.AliasConflictError = exports.AliasError = exports.RegistryStore = void 0;
|
|
3
|
+
exports.ProtocolError = exports.isExpired = exports.validatePayload = exports.parseEnvelope = exports.createEnvelope = exports.AgentRouter = exports.TaskManager = exports.PolicyEngine = exports.RelayConnector = exports.RemoteDiscovery = exports.AutoRegister = exports.RegistryError = exports.RegistryClient = exports.getPublicKeyString = exports.sign = exports.loadOrCreateKeys = exports.startDaemon = exports.registerRegistryRoutes = exports.registerDiagnosticsRoutes = exports.registerAgentRoutes = exports.registerInstanceRoutes = exports.registerRelayRoutes = exports.BroadcastDiscovery = exports.NanoBotAdapter = exports.NanoClawAdapter = exports.getAllAdapterPorts = exports.getAdapter = exports.ADAPTERS = exports.ActiveScanner = exports.LocalProbe = exports.HealthChecker = exports.MdnsListener = exports.NotFoundError = exports.AliasConflictError = exports.AliasError = exports.RegistryStore = void 0;
|
|
4
4
|
var store_js_1 = require("./registry/store.js");
|
|
5
5
|
Object.defineProperty(exports, "RegistryStore", { enumerable: true, get: function () { return store_js_1.RegistryStore; } });
|
|
6
6
|
Object.defineProperty(exports, "AliasError", { enumerable: true, get: function () { return store_js_1.AliasError; } });
|
|
@@ -14,6 +14,14 @@ var probe_js_1 = require("./local/probe.js");
|
|
|
14
14
|
Object.defineProperty(exports, "LocalProbe", { enumerable: true, get: function () { return probe_js_1.LocalProbe; } });
|
|
15
15
|
var active_js_1 = require("./scanner/active.js");
|
|
16
16
|
Object.defineProperty(exports, "ActiveScanner", { enumerable: true, get: function () { return active_js_1.ActiveScanner; } });
|
|
17
|
+
var index_js_1 = require("./adapter/index.js");
|
|
18
|
+
Object.defineProperty(exports, "ADAPTERS", { enumerable: true, get: function () { return index_js_1.ADAPTERS; } });
|
|
19
|
+
Object.defineProperty(exports, "getAdapter", { enumerable: true, get: function () { return index_js_1.getAdapter; } });
|
|
20
|
+
Object.defineProperty(exports, "getAllAdapterPorts", { enumerable: true, get: function () { return index_js_1.getAllAdapterPorts; } });
|
|
21
|
+
var nanoclaw_js_1 = require("./adapter/nanoclaw.js");
|
|
22
|
+
Object.defineProperty(exports, "NanoClawAdapter", { enumerable: true, get: function () { return nanoclaw_js_1.NanoClawAdapter; } });
|
|
23
|
+
var nanobot_js_1 = require("./adapter/nanobot.js");
|
|
24
|
+
Object.defineProperty(exports, "NanoBotAdapter", { enumerable: true, get: function () { return nanobot_js_1.NanoBotAdapter; } });
|
|
17
25
|
var broadcast_js_1 = require("./discovery/broadcast.js");
|
|
18
26
|
Object.defineProperty(exports, "BroadcastDiscovery", { enumerable: true, get: function () { return broadcast_js_1.BroadcastDiscovery; } });
|
|
19
27
|
var server_js_1 = require("./api/server.js");
|
package/dist/local/probe.d.ts
CHANGED
|
@@ -11,5 +11,9 @@ export declare class LocalProbe extends EventEmitter {
|
|
|
11
11
|
start(): Promise<void>;
|
|
12
12
|
stop(): void;
|
|
13
13
|
private _markOffline;
|
|
14
|
+
/** Mark offline any previously-known self instances that were not rediscovered this cycle */
|
|
15
|
+
private _markOfflineStaleSelf;
|
|
14
16
|
probe(): Promise<ClawInstance | null>;
|
|
17
|
+
/** Original OpenClaw probe logic (kept as primary path for backward compatibility) */
|
|
18
|
+
private probeOpenClaw;
|
|
15
19
|
}
|
package/dist/local/probe.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// LocalProbe — detects
|
|
2
|
+
// LocalProbe — detects AI instances on localhost and registers them
|
|
3
3
|
// Runs on daemon startup, then periodically re-checks
|
|
4
|
+
// Adapter-aware: tries all registered adapters on their default ports
|
|
4
5
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
6
|
if (k2 === undefined) k2 = k;
|
|
6
7
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -38,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
38
39
|
exports.LocalProbe = void 0;
|
|
39
40
|
const node_events_1 = require("node:events");
|
|
40
41
|
const os = __importStar(require("node:os"));
|
|
42
|
+
const index_js_1 = require("../adapter/index.js");
|
|
41
43
|
const LOCAL_HOST = "127.0.0.1";
|
|
42
44
|
const DEFAULT_PORT = 18789;
|
|
43
45
|
const CONFIG_PATH = "/__openclaw/control-ui-config.json";
|
|
@@ -71,8 +73,8 @@ class LocalProbe extends node_events_1.EventEmitter {
|
|
|
71
73
|
this.timer = null;
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
|
-
_markOffline() {
|
|
75
|
-
const existing = this.store.getByNetworkKey(LOCAL_HOST,
|
|
76
|
+
_markOffline(port) {
|
|
77
|
+
const existing = this.store.getByNetworkKey(LOCAL_HOST, port);
|
|
76
78
|
if (existing && existing.status !== "offline") {
|
|
77
79
|
this.store.upsert({
|
|
78
80
|
...existing,
|
|
@@ -81,21 +83,135 @@ class LocalProbe extends node_events_1.EventEmitter {
|
|
|
81
83
|
});
|
|
82
84
|
}
|
|
83
85
|
}
|
|
86
|
+
/** Mark offline any previously-known self instances that were not rediscovered this cycle */
|
|
87
|
+
_markOfflineStaleSelf(staleKeys) {
|
|
88
|
+
for (const key of staleKeys) {
|
|
89
|
+
const [address, portStr] = key.split(":");
|
|
90
|
+
this._markOffline(Number(portStr));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
84
93
|
async probe() {
|
|
85
|
-
|
|
94
|
+
// Snapshot all current is_self instances so we can mark stale ones offline later
|
|
95
|
+
const previousSelfKeys = new Set(this.store
|
|
96
|
+
.getAll()
|
|
97
|
+
.filter((inst) => inst.is_self)
|
|
98
|
+
.map((inst) => `${inst.address}:${inst.gateway_port}`));
|
|
99
|
+
// 1. Try OpenClaw on the configured port (backward-compatible primary path)
|
|
100
|
+
const openClawResult = await this.probeOpenClaw(this.port);
|
|
101
|
+
if (openClawResult) {
|
|
102
|
+
previousSelfKeys.delete(`${LOCAL_HOST}:${this.port}`);
|
|
103
|
+
this._markOfflineStaleSelf(previousSelfKeys);
|
|
104
|
+
return openClawResult;
|
|
105
|
+
}
|
|
106
|
+
// 2. Try all adapters on their default ports (skip OpenClaw on this.port, already tried)
|
|
107
|
+
let found = null;
|
|
108
|
+
for (const adapter of index_js_1.ADAPTERS) {
|
|
109
|
+
if (adapter.name === "openclaw")
|
|
110
|
+
continue; // already tried above
|
|
111
|
+
// Try probeLocal first (for frameworks without HTTP servers, e.g. NanoClaw)
|
|
112
|
+
if (adapter.probeLocal) {
|
|
113
|
+
const probeResult = await adapter.probeLocal();
|
|
114
|
+
if (probeResult) {
|
|
115
|
+
const now = new Date().toISOString();
|
|
116
|
+
const partial = adapter.toClawInstance(LOCAL_HOST, 0, probeResult);
|
|
117
|
+
const instance = {
|
|
118
|
+
agent_id: partial.agent_id ?? `${adapter.name}@localhost`,
|
|
119
|
+
auto_name: "",
|
|
120
|
+
assistant_name: partial.assistant_name ?? "",
|
|
121
|
+
display_name: partial.display_name ?? adapter.name,
|
|
122
|
+
lan_host: os.hostname(),
|
|
123
|
+
address: LOCAL_HOST,
|
|
124
|
+
gateway_port: partial.gateway_port ?? 0,
|
|
125
|
+
tls: false,
|
|
126
|
+
discovery_source: "local",
|
|
127
|
+
network_scope: "local",
|
|
128
|
+
status: "online",
|
|
129
|
+
last_seen: now,
|
|
130
|
+
discovered_at: now,
|
|
131
|
+
implementation: partial.implementation,
|
|
132
|
+
connectivity: {
|
|
133
|
+
lan_reachable: false,
|
|
134
|
+
relay_available: false,
|
|
135
|
+
preferred_channel: "local",
|
|
136
|
+
last_lan_check: now,
|
|
137
|
+
},
|
|
138
|
+
is_self: true,
|
|
139
|
+
labels: partial.labels,
|
|
140
|
+
};
|
|
141
|
+
this.store.upsert(instance);
|
|
142
|
+
this.emit("local:discovered", instance);
|
|
143
|
+
previousSelfKeys.delete(`${LOCAL_HOST}:${instance.gateway_port}`);
|
|
144
|
+
found = instance;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Then try HTTP probe on default ports
|
|
149
|
+
if (!found) {
|
|
150
|
+
for (const port of adapter.defaultPorts) {
|
|
151
|
+
const probeResult = await adapter.probe(LOCAL_HOST, port);
|
|
152
|
+
if (probeResult) {
|
|
153
|
+
const now = new Date().toISOString();
|
|
154
|
+
const partial = adapter.toClawInstance(LOCAL_HOST, port, probeResult);
|
|
155
|
+
const instance = {
|
|
156
|
+
agent_id: partial.agent_id ?? `${adapter.name}@localhost`,
|
|
157
|
+
auto_name: "",
|
|
158
|
+
assistant_name: partial.assistant_name ?? "",
|
|
159
|
+
display_name: partial.display_name ?? adapter.name,
|
|
160
|
+
lan_host: os.hostname(),
|
|
161
|
+
address: LOCAL_HOST,
|
|
162
|
+
gateway_port: port,
|
|
163
|
+
tls: false,
|
|
164
|
+
discovery_source: "local",
|
|
165
|
+
network_scope: "local",
|
|
166
|
+
status: "online",
|
|
167
|
+
last_seen: now,
|
|
168
|
+
discovered_at: now,
|
|
169
|
+
implementation: partial.implementation,
|
|
170
|
+
connectivity: {
|
|
171
|
+
lan_reachable: true,
|
|
172
|
+
relay_available: false,
|
|
173
|
+
preferred_channel: "local",
|
|
174
|
+
last_lan_check: now,
|
|
175
|
+
},
|
|
176
|
+
is_self: true,
|
|
177
|
+
};
|
|
178
|
+
this.store.upsert(instance);
|
|
179
|
+
this.emit("local:discovered", instance);
|
|
180
|
+
previousSelfKeys.delete(`${LOCAL_HOST}:${port}`);
|
|
181
|
+
found = instance;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (found)
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
if (found) {
|
|
190
|
+
this._markOfflineStaleSelf(previousSelfKeys);
|
|
191
|
+
return found;
|
|
192
|
+
}
|
|
193
|
+
// Nothing found
|
|
194
|
+
this.localAgentId = null;
|
|
195
|
+
this._markOfflineStaleSelf(previousSelfKeys);
|
|
196
|
+
this.emit("local:unavailable");
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
/** Original OpenClaw probe logic (kept as primary path for backward compatibility) */
|
|
200
|
+
async probeOpenClaw(port) {
|
|
201
|
+
const url = `http://${LOCAL_HOST}:${port}${CONFIG_PATH}`;
|
|
86
202
|
try {
|
|
87
203
|
const res = await fetch(url, {
|
|
88
204
|
signal: AbortSignal.timeout(PROBE_TIMEOUT),
|
|
89
205
|
});
|
|
90
206
|
if (!res.ok) {
|
|
91
|
-
|
|
92
|
-
this.
|
|
207
|
+
// Port responded but not valid OpenClaw — don't mark offline or emit unreachable,
|
|
208
|
+
// because another adapter may be occupying this port. Let the adapter loop try.
|
|
93
209
|
return null;
|
|
94
210
|
}
|
|
95
211
|
const config = (await res.json());
|
|
96
212
|
if (!config.assistantAgentId) {
|
|
97
|
-
|
|
98
|
-
|
|
213
|
+
// Has an HTTP server but no assistantAgentId — not a valid OpenClaw instance.
|
|
214
|
+
// Don't mark offline; another adapter may match this endpoint.
|
|
99
215
|
return null;
|
|
100
216
|
}
|
|
101
217
|
this.localAgentId = config.assistantAgentId;
|
|
@@ -107,13 +223,14 @@ class LocalProbe extends node_events_1.EventEmitter {
|
|
|
107
223
|
display_name: config.displayName ?? config.assistantName ?? "",
|
|
108
224
|
lan_host: os.hostname(),
|
|
109
225
|
address: LOCAL_HOST,
|
|
110
|
-
gateway_port:
|
|
226
|
+
gateway_port: port,
|
|
111
227
|
tls: false,
|
|
112
228
|
discovery_source: "local",
|
|
113
229
|
network_scope: "local",
|
|
114
230
|
status: "online",
|
|
115
231
|
last_seen: now,
|
|
116
232
|
discovered_at: now,
|
|
233
|
+
implementation: "openclaw",
|
|
117
234
|
connectivity: {
|
|
118
235
|
lan_reachable: true,
|
|
119
236
|
relay_available: false,
|
|
@@ -127,9 +244,7 @@ class LocalProbe extends node_events_1.EventEmitter {
|
|
|
127
244
|
return instance;
|
|
128
245
|
}
|
|
129
246
|
catch {
|
|
130
|
-
this
|
|
131
|
-
this._markOffline();
|
|
132
|
-
this.emit("local:unavailable");
|
|
247
|
+
// OpenClaw not running on this port — don't emit yet, try adapters first
|
|
133
248
|
return null;
|
|
134
249
|
}
|
|
135
250
|
}
|
package/dist/registry/store.js
CHANGED
|
@@ -201,6 +201,8 @@ class RegistryStore extends node_events_1.EventEmitter {
|
|
|
201
201
|
// Preserve registry fields
|
|
202
202
|
instance.claw_name = instance.claw_name ?? existing.claw_name;
|
|
203
203
|
instance.owner_pubkey = instance.owner_pubkey ?? existing.owner_pubkey;
|
|
204
|
+
// Preserve implementation (prefer new value if provided)
|
|
205
|
+
instance.implementation = instance.implementation ?? existing.implementation;
|
|
204
206
|
this.instances.set(key, instance);
|
|
205
207
|
this.scheduleDirtyFlush();
|
|
206
208
|
this.emit("upsert", instance);
|
|
@@ -315,6 +317,7 @@ class RegistryStore extends node_events_1.EventEmitter {
|
|
|
315
317
|
labels: existing.labels ?? incoming.labels,
|
|
316
318
|
connectivity: incoming.connectivity ?? existing.connectivity,
|
|
317
319
|
is_self: existing.is_self || incoming.is_self,
|
|
320
|
+
implementation: incoming.implementation ?? existing.implementation,
|
|
318
321
|
};
|
|
319
322
|
}
|
|
320
323
|
/** Numeric priority for network scope: local > vpn > public */
|
package/dist/scanner/active.d.ts
CHANGED