clawmatrix 0.1.17 → 0.1.19

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.
@@ -5,73 +5,7 @@
5
5
  "providers": ["clawmatrix"],
6
6
  "configSchema": {
7
7
  "type": "object",
8
- "properties": {
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.17",
3
+ "version": "0.1.19",
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",
@@ -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 parsed = RawClawMatrixConfigSchema.parse(raw);
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
- const result = ClawMatrixConfigSchema.safeParse(value);
25
- if (result.success) {
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
- const config = parseConfig(api.pluginConfig);
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));