clawmatrix 0.1.17 → 0.1.18
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/BOOTSTRAP.md +123 -121
- package/README.md +76 -71
- package/openclaw.plugin.json +1 -67
- package/package.json +5 -3
- package/src/cluster-service.ts +58 -0
- package/src/config.ts +14 -2
- package/src/index.ts +10 -14
- package/src/knowledge-sync.ts +426 -0
- package/src/types.ts +10 -1
package/openclaw.plugin.json
CHANGED
|
@@ -5,73 +5,7 @@
|
|
|
5
5
|
"providers": ["clawmatrix"],
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
|
-
"
|
|
9
|
-
"nodeId": { "type": "string" },
|
|
10
|
-
"secret": { "type": "string" },
|
|
11
|
-
"listen": { "type": "boolean", "default": false },
|
|
12
|
-
"listenHost": { "type": "string", "default": "0.0.0.0" },
|
|
13
|
-
"listenPort": { "type": "number", "default": 19000 },
|
|
14
|
-
"peers": {
|
|
15
|
-
"type": "array",
|
|
16
|
-
"items": {
|
|
17
|
-
"type": "object",
|
|
18
|
-
"properties": {
|
|
19
|
-
"nodeId": { "type": "string" },
|
|
20
|
-
"url": { "type": "string" }
|
|
21
|
-
},
|
|
22
|
-
"required": ["nodeId", "url"]
|
|
23
|
-
},
|
|
24
|
-
"default": []
|
|
25
|
-
},
|
|
26
|
-
"agents": {
|
|
27
|
-
"type": "array",
|
|
28
|
-
"items": {
|
|
29
|
-
"type": "object",
|
|
30
|
-
"properties": {
|
|
31
|
-
"id": { "type": "string" },
|
|
32
|
-
"description": { "type": "string" },
|
|
33
|
-
"tags": { "type": "array", "items": { "type": "string" }, "default": [] }
|
|
34
|
-
},
|
|
35
|
-
"required": ["id", "description"]
|
|
36
|
-
},
|
|
37
|
-
"default": []
|
|
38
|
-
},
|
|
39
|
-
"models": {
|
|
40
|
-
"type": "array",
|
|
41
|
-
"items": {
|
|
42
|
-
"type": "object",
|
|
43
|
-
"properties": {
|
|
44
|
-
"id": { "type": "string" },
|
|
45
|
-
"provider": { "type": "string" },
|
|
46
|
-
"description": { "type": "string" }
|
|
47
|
-
},
|
|
48
|
-
"required": ["id", "provider"]
|
|
49
|
-
},
|
|
50
|
-
"default": []
|
|
51
|
-
},
|
|
52
|
-
"tags": { "type": "array", "items": { "type": "string" }, "default": [] },
|
|
53
|
-
"proxyPort": { "type": "number", "default": 19001 },
|
|
54
|
-
"toolProxy": {
|
|
55
|
-
"type": "object",
|
|
56
|
-
"properties": {
|
|
57
|
-
"enabled": { "type": "boolean", "default": false },
|
|
58
|
-
"allow": {
|
|
59
|
-
"type": "array",
|
|
60
|
-
"items": { "type": "string" },
|
|
61
|
-
"default": [],
|
|
62
|
-
"description": "Allowed OpenClaw tool names. Use [\"*\"] for all. Empty or [\"*\"] = all allowed."
|
|
63
|
-
},
|
|
64
|
-
"deny": {
|
|
65
|
-
"type": "array",
|
|
66
|
-
"items": { "type": "string" },
|
|
67
|
-
"default": [],
|
|
68
|
-
"description": "Denied OpenClaw tool names (takes precedence over allow)."
|
|
69
|
-
},
|
|
70
|
-
"maxOutputBytes": { "type": "number", "default": 1048576 }
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
"required": ["nodeId", "secret"]
|
|
8
|
+
"additionalProperties": true
|
|
75
9
|
},
|
|
76
10
|
"uiHints": {
|
|
77
11
|
"secret": { "sensitive": true, "label": "Cluster Secret", "help": "Shared secret for HMAC authentication between nodes" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawmatrix",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Decentralized mesh cluster plugin for OpenClaw — inter-gateway communication, model proxy, task handoff, and tool proxy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,9 +32,11 @@
|
|
|
32
32
|
"release": "bunx bumpp"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@automerge/automerge": "^3.2.4",
|
|
36
|
+
"@mariozechner/pi-coding-agent": ">=0.55.0",
|
|
37
|
+
"ignore": "^7.0.5",
|
|
35
38
|
"ws": "^8.19.0",
|
|
36
|
-
"zod": "^4.3.6"
|
|
37
|
-
"@mariozechner/pi-coding-agent": ">=0.55.0"
|
|
39
|
+
"zod": "^4.3.6"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
42
|
"@types/bun": "latest",
|
package/src/cluster-service.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
OpenClawConfig,
|
|
5
5
|
PluginLogger,
|
|
6
6
|
} from "openclaw/plugin-sdk";
|
|
7
|
+
import path from "node:path";
|
|
7
8
|
import type { ClawMatrixConfig } from "./config.ts";
|
|
8
9
|
import { spawnProcess } from "./compat.ts";
|
|
9
10
|
import { debug } from "./debug.ts";
|
|
@@ -12,6 +13,7 @@ import { HandoffManager } from "./handoff.ts";
|
|
|
12
13
|
import { ModelProxy } from "./model-proxy.ts";
|
|
13
14
|
import { ToolProxy, type GatewayInfo } from "./tool-proxy.ts";
|
|
14
15
|
import { WebHandler } from "./web.ts";
|
|
16
|
+
import { KnowledgeSync } from "./knowledge-sync.ts";
|
|
15
17
|
import type {
|
|
16
18
|
AnyClusterFrame,
|
|
17
19
|
HandoffRequest,
|
|
@@ -22,6 +24,7 @@ import type {
|
|
|
22
24
|
HandoffStatusResponse,
|
|
23
25
|
HandoffInputRequired,
|
|
24
26
|
HandoffInput,
|
|
27
|
+
KnowledgeSyncFrame,
|
|
25
28
|
ModelRequest,
|
|
26
29
|
ModelResponse,
|
|
27
30
|
ModelStreamChunk,
|
|
@@ -49,12 +52,15 @@ export class ClusterRuntime {
|
|
|
49
52
|
readonly handoffManager: HandoffManager;
|
|
50
53
|
readonly modelProxy: ModelProxy;
|
|
51
54
|
readonly toolProxy: ToolProxy;
|
|
55
|
+
knowledgeSync: KnowledgeSync | null = null;
|
|
52
56
|
webHandler: WebHandler | null = null;
|
|
53
57
|
private logger: PluginLogger;
|
|
58
|
+
private openclawConfig: OpenClawConfig;
|
|
54
59
|
|
|
55
60
|
constructor(config: ClawMatrixConfig, logger: PluginLogger, openclawConfig: OpenClawConfig, openclawVersion?: string) {
|
|
56
61
|
this.config = config;
|
|
57
62
|
this.logger = logger;
|
|
63
|
+
this.openclawConfig = openclawConfig;
|
|
58
64
|
const gatewayInfo = resolveGatewayInfo(openclawConfig);
|
|
59
65
|
this.peerManager = new PeerManager(config, openclawVersion);
|
|
60
66
|
this.handoffManager = new HandoffManager(config, this.peerManager);
|
|
@@ -85,6 +91,34 @@ export class ClusterRuntime {
|
|
|
85
91
|
this.logger.info(`[clawmatrix] Web dashboard enabled on listen port`);
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
// Knowledge sync
|
|
95
|
+
if (this.config.knowledge?.enabled) {
|
|
96
|
+
const workspacePath = this.resolveWorkspacePath();
|
|
97
|
+
if (workspacePath) {
|
|
98
|
+
const stateDir = path.join(path.dirname(workspacePath), ".clawmatrix");
|
|
99
|
+
this.knowledgeSync = new KnowledgeSync({
|
|
100
|
+
workspacePath,
|
|
101
|
+
storePath: path.join(stateDir, "knowledge.automerge"),
|
|
102
|
+
nodeId: this.config.nodeId,
|
|
103
|
+
debounce: this.config.knowledge.debounce ?? 5000,
|
|
104
|
+
peerManager: this.peerManager,
|
|
105
|
+
});
|
|
106
|
+
this.knowledgeSync.start().then(() => {
|
|
107
|
+
this.logger.info(`[clawmatrix] Knowledge sync started: ${workspacePath}`);
|
|
108
|
+
}).catch((err) => {
|
|
109
|
+
this.logger.error(`[clawmatrix] Knowledge sync failed to start: ${err}`);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Sync with peers on connect/disconnect
|
|
113
|
+
this.peerManager.on("peerConnected", (nodeId) => {
|
|
114
|
+
this.knowledgeSync?.initPeerSync(nodeId);
|
|
115
|
+
});
|
|
116
|
+
this.peerManager.on("peerDisconnected", (nodeId) => {
|
|
117
|
+
this.knowledgeSync?.removePeerSync(nodeId);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
88
122
|
// Start subsystems
|
|
89
123
|
this.peerManager.start();
|
|
90
124
|
this.modelProxy.start();
|
|
@@ -97,6 +131,7 @@ export class ClusterRuntime {
|
|
|
97
131
|
}
|
|
98
132
|
|
|
99
133
|
async stop() {
|
|
134
|
+
await this.knowledgeSync?.stop();
|
|
100
135
|
this.webHandler?.destroy();
|
|
101
136
|
this.handoffManager.destroy();
|
|
102
137
|
this.modelProxy.stop();
|
|
@@ -105,6 +140,24 @@ export class ClusterRuntime {
|
|
|
105
140
|
this.logger.info(`[clawmatrix] Node "${this.config.nodeId}" stopped`);
|
|
106
141
|
}
|
|
107
142
|
|
|
143
|
+
private resolveWorkspacePath(): string | null {
|
|
144
|
+
// Read workspace from OpenClaw agent config (first agent or default agent)
|
|
145
|
+
const agents = (this.openclawConfig as Record<string, unknown>).agents as
|
|
146
|
+
| { list?: Array<{ id?: string; default?: boolean; workspace?: string }>; defaults?: { workspace?: string } }
|
|
147
|
+
| undefined;
|
|
148
|
+
if (!agents) return null;
|
|
149
|
+
const list = agents.list ?? [];
|
|
150
|
+
const defaultAgent = list.find((a) => a.default) ?? list[0];
|
|
151
|
+
const workspace = defaultAgent?.workspace ?? agents.defaults?.workspace;
|
|
152
|
+
if (!workspace) return null;
|
|
153
|
+
// Resolve ~ to home directory
|
|
154
|
+
if (workspace.startsWith("~/") || workspace === "~") {
|
|
155
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
156
|
+
return path.resolve(home, workspace.slice(2));
|
|
157
|
+
}
|
|
158
|
+
return path.resolve(workspace);
|
|
159
|
+
}
|
|
160
|
+
|
|
108
161
|
private dispatchFrame(frame: AnyClusterFrame) {
|
|
109
162
|
if (frame.type.startsWith("model_")) {
|
|
110
163
|
debug("dispatch", `${frame.type} id=${frame.id} from=${frame.from}`);
|
|
@@ -161,6 +214,11 @@ export class ClusterRuntime {
|
|
|
161
214
|
case "send":
|
|
162
215
|
this.handleSendMessage(frame as SendMessage);
|
|
163
216
|
break;
|
|
217
|
+
case "knowledge_sync":
|
|
218
|
+
this.knowledgeSync?.handleSyncMessage(frame as KnowledgeSyncFrame).catch((err) => {
|
|
219
|
+
this.logger.error(`[clawmatrix] Knowledge sync error: ${err}`);
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
164
222
|
}
|
|
165
223
|
}
|
|
166
224
|
|
package/src/config.ts
CHANGED
|
@@ -77,6 +77,11 @@ const ProxyModelGroupSchema = z.object({
|
|
|
77
77
|
models: z.array(ProxyModelEntrySchema),
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
const KnowledgeConfigSchema = z.object({
|
|
81
|
+
enabled: z.boolean().default(false),
|
|
82
|
+
debounce: z.number().default(5000),
|
|
83
|
+
}).optional();
|
|
84
|
+
|
|
80
85
|
const WebConfigSchema = z.object({
|
|
81
86
|
enabled: z.boolean().default(false),
|
|
82
87
|
token: z.string().min(8, "web token must be at least 8 characters"),
|
|
@@ -97,6 +102,7 @@ const RawClawMatrixConfigSchema = z.object({
|
|
|
97
102
|
toolProxy: ToolProxyConfigSchema.optional(),
|
|
98
103
|
handoffTimeout: z.number().default(600_000),
|
|
99
104
|
web: WebConfigSchema,
|
|
105
|
+
knowledge: KnowledgeConfigSchema,
|
|
100
106
|
});
|
|
101
107
|
|
|
102
108
|
/** Flat proxy model after group expansion (used internally). */
|
|
@@ -122,9 +128,15 @@ export { RawClawMatrixConfigSchema as ClawMatrixConfigSchema };
|
|
|
122
128
|
export type PeerConfig = z.infer<typeof PeerConfigSchema>;
|
|
123
129
|
export type ToolProxyConfig = z.infer<typeof ToolProxyConfigSchema>;
|
|
124
130
|
|
|
125
|
-
/** Parse and flatten grouped proxyModels into flat array.
|
|
131
|
+
/** Parse and flatten grouped proxyModels into flat array.
|
|
132
|
+
* Uses passthrough() to ignore unknown fields and safeParse to avoid crashing. */
|
|
126
133
|
export function parseConfig(raw: unknown): ClawMatrixConfig {
|
|
127
|
-
const
|
|
134
|
+
const result = RawClawMatrixConfigSchema.passthrough().safeParse(raw);
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
137
|
+
throw new Error(`ClawMatrix config error: ${issues}`);
|
|
138
|
+
}
|
|
139
|
+
const parsed = result.data;
|
|
128
140
|
|
|
129
141
|
// Flatten proxy model groups
|
|
130
142
|
const proxyModels: ProxyModel[] = [];
|
package/src/index.ts
CHANGED
|
@@ -21,24 +21,20 @@ const plugin = {
|
|
|
21
21
|
|
|
22
22
|
configSchema: {
|
|
23
23
|
safeParse(value: unknown) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return { success: true, data: result.data };
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
success: false,
|
|
30
|
-
error: {
|
|
31
|
-
issues: result.error.issues.map((i) => ({
|
|
32
|
-
path: i.path.map(String),
|
|
33
|
-
message: i.message,
|
|
34
|
-
})),
|
|
35
|
-
},
|
|
36
|
-
};
|
|
24
|
+
// Always pass — validation is handled internally with warnings
|
|
25
|
+
return { success: true, data: value };
|
|
37
26
|
},
|
|
38
27
|
},
|
|
39
28
|
|
|
40
29
|
register(api: OpenClawPluginApi) {
|
|
41
|
-
|
|
30
|
+
let config: ReturnType<typeof parseConfig>;
|
|
31
|
+
try {
|
|
32
|
+
config = parseConfig(api.pluginConfig);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
35
|
+
console.warn(`[clawmatrix] Config error (plugin disabled): ${message}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
42
38
|
|
|
43
39
|
// Background service: manages mesh connections, WS listener, heartbeat
|
|
44
40
|
api.registerService(createClusterService(config, api.config, api.runtime.version));
|