clawnexus 0.2.8 → 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 +29 -0
- package/dist/a2a/card.js +30 -0
- package/dist/adapter/index.js +4 -0
- package/dist/adapter/nanobot.d.ts +1 -0
- package/dist/adapter/nanobot.js +17 -2
- package/dist/adapter/nanoclaw.d.ts +21 -4
- package/dist/adapter/nanoclaw.js +229 -44
- 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 +3 -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 +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 -1
- package/dist/api/server.js +134 -10
- package/dist/discovery/broadcast.js +5 -4
- package/dist/health/checker.d.ts +4 -0
- package/dist/health/checker.js +84 -20
- package/dist/local/probe.d.ts +4 -0
- package/dist/local/probe.js +126 -12
- package/dist/registry/auto-register.js +32 -20
- package/dist/relay/connector.d.ts +5 -1
- package/dist/relay/connector.js +13 -2
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ClawInstance } from "../types.js";
|
|
2
|
+
export interface AgentSkill {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
tags: string[];
|
|
7
|
+
examples?: string[];
|
|
8
|
+
inputModes?: string[];
|
|
9
|
+
outputModes?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface AgentCard {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
url: string;
|
|
15
|
+
version: string;
|
|
16
|
+
capabilities: {
|
|
17
|
+
streaming: boolean;
|
|
18
|
+
pushNotifications: boolean;
|
|
19
|
+
stateTransitionHistory: boolean;
|
|
20
|
+
};
|
|
21
|
+
skills: AgentSkill[];
|
|
22
|
+
defaultInputModes: string[];
|
|
23
|
+
defaultOutputModes: string[];
|
|
24
|
+
provider: {
|
|
25
|
+
name: string;
|
|
26
|
+
url?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare function buildAgentCard(instance: ClawInstance, daemonVersion: string, skills?: AgentSkill[]): AgentCard;
|
package/dist/a2a/card.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// A2A Agent Card generation — maps ClawInstance to A2A v0.3.0 Agent Card
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.buildAgentCard = buildAgentCard;
|
|
5
|
+
const DEFAULT_SKILL = {
|
|
6
|
+
id: "general-assistant",
|
|
7
|
+
name: "General Assistant",
|
|
8
|
+
description: "General-purpose AI assistant",
|
|
9
|
+
tags: ["general"],
|
|
10
|
+
};
|
|
11
|
+
function buildAgentCard(instance, daemonVersion, skills) {
|
|
12
|
+
return {
|
|
13
|
+
name: instance.alias ?? instance.auto_name,
|
|
14
|
+
description: instance.display_name || instance.assistant_name,
|
|
15
|
+
url: `http://${instance.address}:17890`,
|
|
16
|
+
version: daemonVersion,
|
|
17
|
+
capabilities: {
|
|
18
|
+
streaming: false,
|
|
19
|
+
pushNotifications: false,
|
|
20
|
+
stateTransitionHistory: false,
|
|
21
|
+
},
|
|
22
|
+
skills: skills && skills.length > 0 ? skills : [DEFAULT_SKILL],
|
|
23
|
+
defaultInputModes: ["text/plain"],
|
|
24
|
+
defaultOutputModes: ["text/plain"],
|
|
25
|
+
provider: {
|
|
26
|
+
name: "ClawNexus",
|
|
27
|
+
url: "https://github.com/SilverstreamsAI/ClawNexus",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
package/dist/adapter/index.js
CHANGED
|
@@ -4,11 +4,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.ADAPTERS = void 0;
|
|
5
5
|
exports.getAdapter = getAdapter;
|
|
6
6
|
exports.getAllAdapterPorts = getAllAdapterPorts;
|
|
7
|
+
const openclaw_js_1 = require("./openclaw.js");
|
|
7
8
|
const nanoclaw_js_1 = require("./nanoclaw.js");
|
|
8
9
|
const nanobot_js_1 = require("./nanobot.js");
|
|
10
|
+
const openfang_js_1 = require("./openfang.js");
|
|
9
11
|
exports.ADAPTERS = [
|
|
12
|
+
new openclaw_js_1.OpenClawAdapter(),
|
|
10
13
|
new nanoclaw_js_1.NanoClawAdapter(),
|
|
11
14
|
new nanobot_js_1.NanoBotAdapter(),
|
|
15
|
+
new openfang_js_1.OpenFangAdapter(),
|
|
12
16
|
];
|
|
13
17
|
function getAdapter(name) {
|
|
14
18
|
return exports.ADAPTERS.find((a) => a.name === name);
|
|
@@ -5,6 +5,7 @@ export declare class NanoBotAdapter implements FrameworkAdapter {
|
|
|
5
5
|
readonly defaultPorts: number[];
|
|
6
6
|
probe(host: string, port: number): Promise<ProbeResult | null>;
|
|
7
7
|
toClawInstance(host: string, port: number, probe: ProbeResult): Partial<ClawInstance>;
|
|
8
|
+
healthCheck(host: string, port: number): Promise<boolean>;
|
|
8
9
|
private probeHealth;
|
|
9
10
|
private probeApiHealth;
|
|
10
11
|
private extractProbeResult;
|
package/dist/adapter/nanobot.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// NanoBot adapter — Python variant, default ports 8000/8080
|
|
2
|
+
// NanoBot adapter — Python variant, default ports 18790/8000/8080
|
|
3
3
|
// Probe: /health → check for framework/app: "nanobot", fallback /api/health
|
|
4
4
|
// Heuristic: python_version field on expected port → infer nanobot
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -7,7 +7,7 @@ exports.NanoBotAdapter = void 0;
|
|
|
7
7
|
const PROBE_TIMEOUT = 2_000;
|
|
8
8
|
class NanoBotAdapter {
|
|
9
9
|
name = "nanobot";
|
|
10
|
-
defaultPorts = [8000, 8080];
|
|
10
|
+
defaultPorts = [18790, 8000, 8080];
|
|
11
11
|
async probe(host, port) {
|
|
12
12
|
// Try /health first
|
|
13
13
|
const healthResult = await this.probeHealth(host, port);
|
|
@@ -30,6 +30,21 @@ class NanoBotAdapter {
|
|
|
30
30
|
implementation: "nanobot",
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
async healthCheck(host, port) {
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(`http://${host}:${port}/health`, {
|
|
36
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT),
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
return false;
|
|
40
|
+
const data = (await res.json());
|
|
41
|
+
return data.framework === "nanobot" || data.app === "nanobot" ||
|
|
42
|
+
(typeof data.python_version === "string" && this.defaultPorts.includes(port));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
33
48
|
async probeHealth(host, port) {
|
|
34
49
|
try {
|
|
35
50
|
const res = await fetch(`http://${host}:${port}/health`, {
|
|
@@ -3,8 +3,25 @@ import type { FrameworkAdapter, ProbeResult } from "./types.js";
|
|
|
3
3
|
export declare class NanoClawAdapter implements FrameworkAdapter {
|
|
4
4
|
readonly name = "nanoclaw";
|
|
5
5
|
readonly defaultPorts: number[];
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
private _lastProjectDir;
|
|
7
|
+
probe(_host: string, _port: number): Promise<ProbeResult | null>;
|
|
8
|
+
probeLocal(): Promise<ProbeResult | null>;
|
|
9
|
+
healthCheckLocal(): Promise<boolean>;
|
|
10
|
+
toClawInstance(host: string, _port: number, probe: ProbeResult): Partial<ClawInstance>;
|
|
11
|
+
healthCheck(_host: string, _port: number): Promise<boolean>;
|
|
12
|
+
/** Scan /proc for Node.js processes whose cwd contains a nanoclaw package.json */
|
|
13
|
+
private _findFromProc;
|
|
14
|
+
/** Check common paths under home directory */
|
|
15
|
+
private _findFromCandidateDirs;
|
|
16
|
+
/** Check if a directory is a nanoclaw project */
|
|
17
|
+
private _isNanoClawDir;
|
|
18
|
+
/** Read package.json and verify it's nanoclaw */
|
|
19
|
+
private _readPackageJson;
|
|
20
|
+
/** Extract ASSISTANT_NAME from .env file */
|
|
21
|
+
private _readAssistantName;
|
|
22
|
+
/** Find a running process with cwd matching the project dir */
|
|
23
|
+
private _findRunningPid;
|
|
24
|
+
/** Count JSON files in data/ipc/{channel}/messages/ as a proxy for active tasks */
|
|
25
|
+
private _countIpcTasks;
|
|
26
|
+
private _fileExists;
|
|
10
27
|
}
|
package/dist/adapter/nanoclaw.js
CHANGED
|
@@ -1,74 +1,259 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// NanoClaw adapter —
|
|
3
|
-
//
|
|
2
|
+
// NanoClaw adapter — ProjectProbe implementation
|
|
3
|
+
// NanoClaw has no HTTP server, so discovery uses local filesystem probing:
|
|
4
|
+
// /proc scan for running processes, package.json, .env, data/ipc/ directory.
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
4
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
39
|
exports.NanoClawAdapter = void 0;
|
|
6
|
-
const
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const os = __importStar(require("node:os"));
|
|
43
|
+
const NANOCLAW_PACKAGE_NAME = "nanoclaw";
|
|
44
|
+
// Common paths where nanoclaw might be installed
|
|
45
|
+
const CANDIDATE_DIRS = [
|
|
46
|
+
"nanoclaw",
|
|
47
|
+
"NanoClaw",
|
|
48
|
+
"projects/nanoclaw",
|
|
49
|
+
"projects/NanoClaw",
|
|
50
|
+
];
|
|
7
51
|
class NanoClawAdapter {
|
|
8
52
|
name = "nanoclaw";
|
|
9
|
-
defaultPorts = [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
53
|
+
defaultPorts = [];
|
|
54
|
+
// Cached project dir from last successful probe
|
|
55
|
+
_lastProjectDir = null;
|
|
56
|
+
async probe(_host, _port) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
async probeLocal() {
|
|
60
|
+
// 1. Try to find via running Node.js processes (/proc scan)
|
|
61
|
+
let projectDir = await this._findFromProc();
|
|
62
|
+
// 2. Fallback: scan common home directory paths
|
|
63
|
+
if (!projectDir) {
|
|
64
|
+
projectDir = await this._findFromCandidateDirs();
|
|
65
|
+
}
|
|
66
|
+
if (!projectDir)
|
|
67
|
+
return null;
|
|
68
|
+
this._lastProjectDir = projectDir;
|
|
69
|
+
// Read package.json for version
|
|
70
|
+
const pkgInfo = await this._readPackageJson(projectDir);
|
|
71
|
+
if (!pkgInfo)
|
|
72
|
+
return null; // not actually nanoclaw
|
|
73
|
+
// Read .env for ASSISTANT_NAME (optional)
|
|
74
|
+
const assistantName = await this._readAssistantName(projectDir);
|
|
75
|
+
// Check if process is running
|
|
76
|
+
const procInfo = await this._findRunningPid(projectDir);
|
|
77
|
+
// Count active IPC tasks (data/ipc/*/messages/*.json)
|
|
78
|
+
const activeTasks = await this._countIpcTasks(projectDir);
|
|
79
|
+
// Check if store/messages.db exists (has been run before)
|
|
80
|
+
const hasMessageDb = await this._fileExists(path.join(projectDir, "store", "messages.db"));
|
|
81
|
+
return {
|
|
82
|
+
name: NANOCLAW_PACKAGE_NAME,
|
|
83
|
+
version: pkgInfo.version,
|
|
84
|
+
display_name: assistantName ?? undefined,
|
|
85
|
+
metadata: {
|
|
86
|
+
project_dir: projectDir,
|
|
87
|
+
is_running: procInfo !== null,
|
|
88
|
+
pid: procInfo,
|
|
89
|
+
active_tasks: activeTasks,
|
|
90
|
+
has_message_db: hasMessageDb,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async healthCheckLocal() {
|
|
95
|
+
const projectDir = this._lastProjectDir;
|
|
96
|
+
if (!projectDir)
|
|
97
|
+
return false;
|
|
98
|
+
// Check if a nanoclaw process is running with this project dir
|
|
99
|
+
const pid = await this._findRunningPid(projectDir);
|
|
100
|
+
if (pid !== null)
|
|
101
|
+
return true;
|
|
102
|
+
// Fallback: check store/messages.db mtime (recent activity = likely alive)
|
|
103
|
+
try {
|
|
104
|
+
const dbPath = path.join(projectDir, "store", "messages.db");
|
|
105
|
+
const stat = await fs.promises.stat(dbPath);
|
|
106
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
107
|
+
// Consider "healthy" if db was modified in last 5 minutes
|
|
108
|
+
return ageMs < 5 * 60 * 1000;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
17
113
|
}
|
|
18
|
-
toClawInstance(host,
|
|
114
|
+
toClawInstance(host, _port, probe) {
|
|
115
|
+
const meta = probe.metadata;
|
|
19
116
|
return {
|
|
20
117
|
agent_id: `nanoclaw@${host}`,
|
|
21
118
|
assistant_name: probe.display_name ?? "",
|
|
22
119
|
display_name: probe.display_name ?? "nanoclaw",
|
|
23
120
|
lan_host: host,
|
|
24
121
|
address: host,
|
|
25
|
-
gateway_port:
|
|
122
|
+
gateway_port: 0,
|
|
26
123
|
tls: false,
|
|
27
|
-
discovery_source: "
|
|
28
|
-
status: "online",
|
|
124
|
+
discovery_source: "local",
|
|
29
125
|
implementation: "nanoclaw",
|
|
126
|
+
labels: meta?.project_dir ? { project_dir: meta.project_dir } : undefined,
|
|
30
127
|
};
|
|
31
128
|
}
|
|
32
|
-
async
|
|
129
|
+
async healthCheck(_host, _port) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
/** Scan /proc for Node.js processes whose cwd contains a nanoclaw package.json */
|
|
133
|
+
async _findFromProc() {
|
|
134
|
+
if (process.platform !== "linux")
|
|
135
|
+
return null;
|
|
33
136
|
try {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
137
|
+
const entries = await fs.promises.readdir("/proc");
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
// Only numeric dirs (PIDs)
|
|
140
|
+
if (!/^\d+$/.test(entry))
|
|
141
|
+
continue;
|
|
142
|
+
try {
|
|
143
|
+
const cwd = await fs.promises.readlink(`/proc/${entry}/cwd`);
|
|
144
|
+
if (await this._isNanoClawDir(cwd)) {
|
|
145
|
+
return cwd;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Permission denied or process gone — skip
|
|
150
|
+
}
|
|
46
151
|
}
|
|
47
|
-
return null;
|
|
48
152
|
}
|
|
49
153
|
catch {
|
|
50
|
-
|
|
154
|
+
// /proc not available
|
|
51
155
|
}
|
|
156
|
+
return null;
|
|
52
157
|
}
|
|
53
|
-
|
|
158
|
+
/** Check common paths under home directory */
|
|
159
|
+
async _findFromCandidateDirs() {
|
|
160
|
+
const home = os.homedir();
|
|
161
|
+
for (const rel of CANDIDATE_DIRS) {
|
|
162
|
+
const dir = path.join(home, rel);
|
|
163
|
+
if (await this._isNanoClawDir(dir)) {
|
|
164
|
+
return dir;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
/** Check if a directory is a nanoclaw project */
|
|
170
|
+
async _isNanoClawDir(dir) {
|
|
171
|
+
const info = await this._readPackageJson(dir);
|
|
172
|
+
return info !== null;
|
|
173
|
+
}
|
|
174
|
+
/** Read package.json and verify it's nanoclaw */
|
|
175
|
+
async _readPackageJson(dir) {
|
|
54
176
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
177
|
+
const raw = await fs.promises.readFile(path.join(dir, "package.json"), "utf-8");
|
|
178
|
+
const pkg = JSON.parse(raw);
|
|
179
|
+
if (pkg.name === NANOCLAW_PACKAGE_NAME) {
|
|
180
|
+
return { version: pkg.version };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Not found or invalid
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
/** Extract ASSISTANT_NAME from .env file */
|
|
189
|
+
async _readAssistantName(dir) {
|
|
190
|
+
try {
|
|
191
|
+
const raw = await fs.promises.readFile(path.join(dir, ".env"), "utf-8");
|
|
192
|
+
const match = raw.match(/^ASSISTANT_NAME=(.*)$/m);
|
|
193
|
+
if (match?.[1]) {
|
|
194
|
+
return match[1].trim().replace(/^["']|["']$/g, "");
|
|
67
195
|
}
|
|
68
|
-
return null;
|
|
69
196
|
}
|
|
70
197
|
catch {
|
|
198
|
+
// .env not found
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
/** Find a running process with cwd matching the project dir */
|
|
203
|
+
async _findRunningPid(dir) {
|
|
204
|
+
if (process.platform !== "linux")
|
|
71
205
|
return null;
|
|
206
|
+
try {
|
|
207
|
+
const entries = await fs.promises.readdir("/proc");
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (!/^\d+$/.test(entry))
|
|
210
|
+
continue;
|
|
211
|
+
try {
|
|
212
|
+
const cwd = await fs.promises.readlink(`/proc/${entry}/cwd`);
|
|
213
|
+
if (cwd === dir) {
|
|
214
|
+
return parseInt(entry, 10);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// skip
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// /proc not available
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
/** Count JSON files in data/ipc/{channel}/messages/ as a proxy for active tasks */
|
|
228
|
+
async _countIpcTasks(dir) {
|
|
229
|
+
const ipcDir = path.join(dir, "data", "ipc");
|
|
230
|
+
try {
|
|
231
|
+
const channels = await fs.promises.readdir(ipcDir);
|
|
232
|
+
let count = 0;
|
|
233
|
+
for (const ch of channels) {
|
|
234
|
+
const messagesDir = path.join(ipcDir, ch, "messages");
|
|
235
|
+
try {
|
|
236
|
+
const files = await fs.promises.readdir(messagesDir);
|
|
237
|
+
count += files.filter((f) => f.endsWith(".json")).length;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// messages dir doesn't exist for this channel
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return count;
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// data/ipc doesn't exist
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async _fileExists(filePath) {
|
|
251
|
+
try {
|
|
252
|
+
await fs.promises.access(filePath);
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return false;
|
|
72
257
|
}
|
|
73
258
|
}
|
|
74
259
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ClawInstance } from "../types.js";
|
|
2
|
+
import type { FrameworkAdapter, ProbeResult } from "./types.js";
|
|
3
|
+
export declare class OpenClawAdapter implements FrameworkAdapter {
|
|
4
|
+
readonly name = "openclaw";
|
|
5
|
+
readonly defaultPorts: number[];
|
|
6
|
+
probe(host: string, port: number): Promise<ProbeResult | null>;
|
|
7
|
+
toClawInstance(host: string, port: number, probe: ProbeResult): Partial<ClawInstance>;
|
|
8
|
+
healthCheck(host: string, port: number): Promise<boolean>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// OpenClaw adapter — canonical OpenClaw (and compatible variants like GoClaw)
|
|
3
|
+
// Probe: /__openclaw/control-ui-config.json → extract agentId, assistantName, displayName
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.OpenClawAdapter = void 0;
|
|
6
|
+
const PROBE_TIMEOUT = 2_000;
|
|
7
|
+
const CONFIG_PATH = "/__openclaw/control-ui-config.json";
|
|
8
|
+
class OpenClawAdapter {
|
|
9
|
+
name = "openclaw";
|
|
10
|
+
defaultPorts = [18789];
|
|
11
|
+
async probe(host, port) {
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(`http://${host}:${port}${CONFIG_PATH}`, {
|
|
14
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT),
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok)
|
|
17
|
+
return null;
|
|
18
|
+
const config = (await res.json());
|
|
19
|
+
if (!config.assistantAgentId)
|
|
20
|
+
return null;
|
|
21
|
+
return {
|
|
22
|
+
name: "openclaw",
|
|
23
|
+
display_name: config.displayName ?? config.assistantName ?? undefined,
|
|
24
|
+
metadata: {
|
|
25
|
+
assistantAgentId: config.assistantAgentId,
|
|
26
|
+
assistantName: config.assistantName,
|
|
27
|
+
displayName: config.displayName,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
toClawInstance(host, port, probe) {
|
|
36
|
+
const meta = probe.metadata;
|
|
37
|
+
return {
|
|
38
|
+
agent_id: meta?.assistantAgentId ?? `openclaw@${host}`,
|
|
39
|
+
assistant_name: meta?.assistantName ?? "",
|
|
40
|
+
display_name: meta?.displayName ?? meta?.assistantName ?? "",
|
|
41
|
+
lan_host: host,
|
|
42
|
+
address: host,
|
|
43
|
+
gateway_port: port,
|
|
44
|
+
tls: false,
|
|
45
|
+
discovery_source: "scan",
|
|
46
|
+
status: "online",
|
|
47
|
+
implementation: "openclaw",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async healthCheck(host, port) {
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(`http://${host}:${port}${CONFIG_PATH}`, {
|
|
53
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT),
|
|
54
|
+
});
|
|
55
|
+
return res.ok;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.OpenClawAdapter = OpenClawAdapter;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ClawInstance } from "../types.js";
|
|
2
|
+
import type { FrameworkAdapter, ProbeResult } from "./types.js";
|
|
3
|
+
export declare class OpenFangAdapter implements FrameworkAdapter {
|
|
4
|
+
readonly name = "openfang";
|
|
5
|
+
readonly defaultPorts: number[];
|
|
6
|
+
probe(host: string, port: number): Promise<ProbeResult | null>;
|
|
7
|
+
toClawInstance(host: string, port: number, probe: ProbeResult): Partial<ClawInstance>;
|
|
8
|
+
healthCheck(host: string, port: number): Promise<boolean>;
|
|
9
|
+
private isOpenFang;
|
|
10
|
+
private fetchAgentJson;
|
|
11
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// OpenFang adapter — port 4200, REST API
|
|
3
|
+
// Probe: /api/health → confirm OpenFang, identity from /.well-known/agent.json
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.OpenFangAdapter = void 0;
|
|
6
|
+
const PROBE_TIMEOUT = 2_000;
|
|
7
|
+
class OpenFangAdapter {
|
|
8
|
+
name = "openfang";
|
|
9
|
+
defaultPorts = [4200];
|
|
10
|
+
async probe(host, port) {
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch(`http://${host}:${port}/api/health`, {
|
|
13
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT),
|
|
14
|
+
});
|
|
15
|
+
if (!res.ok)
|
|
16
|
+
return null;
|
|
17
|
+
const data = (await res.json());
|
|
18
|
+
if (!this.isOpenFang(data))
|
|
19
|
+
return null;
|
|
20
|
+
// Try to get identity from /.well-known/agent.json
|
|
21
|
+
const agent = await this.fetchAgentJson(host, port);
|
|
22
|
+
return {
|
|
23
|
+
name: "openfang",
|
|
24
|
+
version: data.version ?? agent?.version,
|
|
25
|
+
display_name: agent?.display_name ?? agent?.name,
|
|
26
|
+
metadata: {
|
|
27
|
+
agent_id: agent?.agent_id,
|
|
28
|
+
agent_name: agent?.name,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
toClawInstance(host, port, probe) {
|
|
37
|
+
const meta = probe.metadata;
|
|
38
|
+
return {
|
|
39
|
+
agent_id: meta?.agent_id ?? `openfang@${host}`,
|
|
40
|
+
assistant_name: meta?.agent_name ?? "",
|
|
41
|
+
display_name: probe.display_name ?? "openfang",
|
|
42
|
+
lan_host: host,
|
|
43
|
+
address: host,
|
|
44
|
+
gateway_port: port,
|
|
45
|
+
tls: false,
|
|
46
|
+
discovery_source: "scan",
|
|
47
|
+
status: "online",
|
|
48
|
+
implementation: "openfang",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async healthCheck(host, port) {
|
|
52
|
+
try {
|
|
53
|
+
const res = await fetch(`http://${host}:${port}/api/health`, {
|
|
54
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT),
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok)
|
|
57
|
+
return false;
|
|
58
|
+
const data = (await res.json());
|
|
59
|
+
return data.status === "ok";
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
isOpenFang(data) {
|
|
66
|
+
// Confirm OpenFang by framework field or status shape
|
|
67
|
+
if (data.framework === "openfang")
|
|
68
|
+
return true;
|
|
69
|
+
// Heuristic: /api/health with status: "ok" on default port
|
|
70
|
+
if (data.status === "ok")
|
|
71
|
+
return true;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
async fetchAgentJson(host, port) {
|
|
75
|
+
try {
|
|
76
|
+
const res = await fetch(`http://${host}:${port}/.well-known/agent.json`, {
|
|
77
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT),
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok)
|
|
80
|
+
return null;
|
|
81
|
+
return (await res.json());
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.OpenFangAdapter = OpenFangAdapter;
|
package/dist/adapter/types.d.ts
CHANGED
|
@@ -10,4 +10,7 @@ export interface FrameworkAdapter {
|
|
|
10
10
|
readonly defaultPorts: number[];
|
|
11
11
|
probe(host: string, port: number): Promise<ProbeResult | null>;
|
|
12
12
|
toClawInstance(host: string, port: number, probe: ProbeResult): Partial<ClawInstance>;
|
|
13
|
+
healthCheck(host: string, port: number): Promise<boolean>;
|
|
14
|
+
probeLocal?(): Promise<ProbeResult | null>;
|
|
15
|
+
healthCheckLocal?(): Promise<boolean>;
|
|
13
16
|
}
|
package/dist/agent/engine.js
CHANGED
|
@@ -99,8 +99,8 @@ class PolicyEngine {
|
|
|
99
99
|
this.incrementRate(peer);
|
|
100
100
|
// 3. Whitelist check — whitelisted peers bypass trust/capability checks
|
|
101
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) {
|
|
102
|
+
// 4. Trust score check (skip for whitelisted and auto mode)
|
|
103
|
+
if (this.config.mode !== "auto" && !isWhitelisted && peerTrustScore < this.config.trust_threshold) {
|
|
104
104
|
return { result: "reject", reason: "trust_insufficient", details: `Score ${peerTrustScore} < threshold ${this.config.trust_threshold}` };
|
|
105
105
|
}
|
|
106
106
|
// 5. Delegation depth check
|
|
@@ -189,6 +189,13 @@ class PolicyEngine {
|
|
|
189
189
|
existing.count++;
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
+
// Periodic cleanup: remove expired entries when map grows large
|
|
193
|
+
if (this.rateCounters.size > 100) {
|
|
194
|
+
for (const [key, val] of this.rateCounters) {
|
|
195
|
+
if (val.resetAt <= now)
|
|
196
|
+
this.rateCounters.delete(key);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
192
199
|
}
|
|
193
200
|
async saveConfig() {
|
|
194
201
|
const json = JSON.stringify(this.config, null, 2);
|