clawmatrix 0.4.2 → 0.5.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/README.md +17 -21
- package/cli/bin/clawmatrix.mjs +300 -1
- package/package.json +8 -1
- package/src/acp-proxy.ts +122 -50
- package/src/{web.ts → api.ts} +646 -25
- package/src/audit.ts +37 -2
- package/src/auth.ts +5 -10
- package/src/automation.ts +625 -0
- package/src/cluster-service.ts +172 -16
- package/src/compat.ts +103 -0
- package/src/config.ts +75 -27
- package/src/connection.ts +225 -37
- package/src/crypto.ts +72 -5
- package/src/device-info.ts +21 -2
- package/src/file-transfer.ts +3 -2
- package/src/handoff.ts +90 -32
- package/src/health-tracker.ts +91 -356
- package/src/index.ts +421 -13
- package/src/kanban.ts +507 -0
- package/src/knowledge-sync.ts +158 -7
- package/src/local-tools.ts +65 -2
- package/src/log-replication.ts +198 -0
- package/src/model-proxy.ts +152 -60
- package/src/peer-approval.ts +3 -2
- package/src/peer-manager.ts +237 -47
- package/src/retry.ts +81 -0
- package/src/router.ts +152 -104
- package/src/sentinel.ts +86 -52
- package/src/store.ts +578 -0
- package/src/terminal.ts +17 -8
- package/src/tool-proxy.ts +6 -5
- package/src/tools/cluster-events.ts +6 -6
- package/src/tools/cluster-kanban.ts +345 -0
- package/src/tools/cluster-peers.ts +1 -1
- package/src/tools/cluster-query.ts +145 -0
- package/src/types.ts +95 -9
package/src/index.ts
CHANGED
|
@@ -18,6 +18,9 @@ import { createClusterTerminalTool } from "./tools/cluster-terminal.ts";
|
|
|
18
18
|
import { createClusterToolInvokeTool } from "./tools/cluster-tool.ts";
|
|
19
19
|
import { createClusterTransferTool } from "./tools/cluster-transfer.ts";
|
|
20
20
|
import { createClusterNotifyTool } from "./tools/cluster-notify.ts";
|
|
21
|
+
import { createClusterKanbanTool } from "./tools/cluster-kanban.ts";
|
|
22
|
+
import { createClusterQueryTool } from "./tools/cluster-query.ts";
|
|
23
|
+
import { nanoid } from "nanoid";
|
|
21
24
|
import { spawnProcess } from "./compat.ts";
|
|
22
25
|
|
|
23
26
|
/**
|
|
@@ -80,6 +83,33 @@ function discoverModels(
|
|
|
80
83
|
return result;
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Auto-discover agents from OpenClaw's agents.list config.
|
|
88
|
+
* Only used when no agents are explicitly configured in ClawMatrix.
|
|
89
|
+
*/
|
|
90
|
+
function discoverAgents(
|
|
91
|
+
openclawConfig: OpenClawConfig,
|
|
92
|
+
): ClawMatrixConfig["agents"] {
|
|
93
|
+
const cfg = openclawConfig as Record<string, unknown>;
|
|
94
|
+
const agentsConfig = cfg.agents as { list?: Array<{ id: string; name?: string }> } | undefined;
|
|
95
|
+
if (!agentsConfig?.list?.length) return [];
|
|
96
|
+
|
|
97
|
+
const result: ClawMatrixConfig["agents"] = [];
|
|
98
|
+
for (const a of agentsConfig.list) {
|
|
99
|
+
if (!a.id || typeof a.id !== "string") continue;
|
|
100
|
+
result.push({
|
|
101
|
+
id: a.id,
|
|
102
|
+
description: a.name ?? a.id,
|
|
103
|
+
tags: [],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (result.length > 0) {
|
|
108
|
+
debug("agents", `Auto-discovered ${result.length} agent(s) from OpenClaw config: ${result.map((a) => a.id).join(", ")}`);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
83
113
|
const plugin = {
|
|
84
114
|
id: "clawmatrix",
|
|
85
115
|
name: "ClawMatrix",
|
|
@@ -106,6 +136,14 @@ const plugin = {
|
|
|
106
136
|
return;
|
|
107
137
|
}
|
|
108
138
|
|
|
139
|
+
// Auto-discover agents from OpenClaw config when none explicitly configured
|
|
140
|
+
if (config.agents.length === 0) {
|
|
141
|
+
const discovered = discoverAgents(api.config);
|
|
142
|
+
if (discovered.length > 0) {
|
|
143
|
+
config = { ...config, agents: discovered };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
109
147
|
// Auto-discover models from OpenClaw providers and merge with explicit config
|
|
110
148
|
{
|
|
111
149
|
const discovered = discoverModels(api.config, config);
|
|
@@ -305,6 +343,8 @@ const plugin = {
|
|
|
305
343
|
api.registerTool(createClusterToolInvokeTool(), { optional: true });
|
|
306
344
|
api.registerTool(createClusterTransferTool(), { optional: true });
|
|
307
345
|
api.registerTool(createClusterNotifyTool(), { optional: true });
|
|
346
|
+
api.registerTool(createClusterKanbanTool(), { optional: true });
|
|
347
|
+
api.registerTool(createClusterQueryTool(), { optional: true });
|
|
308
348
|
|
|
309
349
|
// Wire up peer approval with OpenClaw channel API
|
|
310
350
|
if (config.peerApproval.enabled) {
|
|
@@ -326,7 +366,7 @@ const plugin = {
|
|
|
326
366
|
to: params.to,
|
|
327
367
|
message: params.message,
|
|
328
368
|
channel: params.channel,
|
|
329
|
-
idempotencyKey:
|
|
369
|
+
idempotencyKey: nanoid(),
|
|
330
370
|
};
|
|
331
371
|
if (params.accountId) sendParams.accountId = params.accountId;
|
|
332
372
|
if (params.threadId) sendParams.threadId = params.threadId;
|
|
@@ -687,14 +727,14 @@ const plugin = {
|
|
|
687
727
|
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
688
728
|
try {
|
|
689
729
|
const runtime = getClusterRuntime();
|
|
690
|
-
if (!runtime.
|
|
691
|
-
respond(false, { error: "Events not
|
|
730
|
+
if (!runtime.apiHandler) {
|
|
731
|
+
respond(false, { error: "Events not available (listen mode not enabled)" });
|
|
692
732
|
return;
|
|
693
733
|
}
|
|
694
734
|
const { type, source, since, unconsumed, limit } = (params ?? {}) as {
|
|
695
735
|
type?: string; source?: string; since?: number; unconsumed?: boolean; limit?: number;
|
|
696
736
|
};
|
|
697
|
-
const events = runtime.
|
|
737
|
+
const events = runtime.apiHandler.queryEvents({
|
|
698
738
|
type,
|
|
699
739
|
source,
|
|
700
740
|
since,
|
|
@@ -713,8 +753,8 @@ const plugin = {
|
|
|
713
753
|
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
714
754
|
try {
|
|
715
755
|
const runtime = getClusterRuntime();
|
|
716
|
-
if (!runtime.
|
|
717
|
-
respond(false, { error: "Events not
|
|
756
|
+
if (!runtime.apiHandler) {
|
|
757
|
+
respond(false, { error: "Events not available (listen mode not enabled)" });
|
|
718
758
|
return;
|
|
719
759
|
}
|
|
720
760
|
const { ids } = (params ?? {}) as { ids?: string[] };
|
|
@@ -722,7 +762,7 @@ const plugin = {
|
|
|
722
762
|
respond(false, { error: "Missing required param: ids (array of event IDs)" });
|
|
723
763
|
return;
|
|
724
764
|
}
|
|
725
|
-
const consumed = runtime.
|
|
765
|
+
const consumed = runtime.apiHandler.consumeEvents(ids);
|
|
726
766
|
respond(true, { consumed, ids });
|
|
727
767
|
} catch {
|
|
728
768
|
respond(false, { error: "ClawMatrix service not running" });
|
|
@@ -1174,6 +1214,329 @@ const plugin = {
|
|
|
1174
1214
|
},
|
|
1175
1215
|
);
|
|
1176
1216
|
|
|
1217
|
+
// ── Kanban board gateway methods ──────────────────────────────────
|
|
1218
|
+
|
|
1219
|
+
api.registerGatewayMethod(
|
|
1220
|
+
"clawmatrix.board.summary",
|
|
1221
|
+
({ respond }: GatewayRequestHandlerOptions) => {
|
|
1222
|
+
try {
|
|
1223
|
+
const runtime = getClusterRuntime();
|
|
1224
|
+
if (!runtime.kanbanManager) {
|
|
1225
|
+
respond(false, { error: "Kanban not enabled" });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
respond(true, runtime.kanbanManager.getSummary());
|
|
1229
|
+
} catch {
|
|
1230
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1231
|
+
}
|
|
1232
|
+
},
|
|
1233
|
+
);
|
|
1234
|
+
|
|
1235
|
+
api.registerGatewayMethod(
|
|
1236
|
+
"clawmatrix.board.list",
|
|
1237
|
+
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
1238
|
+
try {
|
|
1239
|
+
const runtime = getClusterRuntime();
|
|
1240
|
+
if (!runtime.kanbanManager) {
|
|
1241
|
+
respond(false, { error: "Kanban not enabled" });
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
const { stage, label, assignedNode, priority } = (params ?? {}) as {
|
|
1245
|
+
stage?: string; label?: string; assignedNode?: string; priority?: string;
|
|
1246
|
+
};
|
|
1247
|
+
const cards = runtime.kanbanManager.listCards({
|
|
1248
|
+
stage: stage as import("./types.ts").CardStage | undefined,
|
|
1249
|
+
label,
|
|
1250
|
+
assignedNode,
|
|
1251
|
+
priority: priority as import("./types.ts").CardPriority | undefined,
|
|
1252
|
+
});
|
|
1253
|
+
respond(true, cards);
|
|
1254
|
+
} catch {
|
|
1255
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1256
|
+
}
|
|
1257
|
+
},
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
api.registerGatewayMethod(
|
|
1261
|
+
"clawmatrix.board.get",
|
|
1262
|
+
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
1263
|
+
try {
|
|
1264
|
+
const runtime = getClusterRuntime();
|
|
1265
|
+
if (!runtime.kanbanManager) {
|
|
1266
|
+
respond(false, { error: "Kanban not enabled" });
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
const { cardId } = (params ?? {}) as { cardId?: string };
|
|
1270
|
+
if (!cardId) {
|
|
1271
|
+
respond(false, { error: "Missing required param: cardId" });
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const card = runtime.kanbanManager.getCard(cardId);
|
|
1275
|
+
if (!card) {
|
|
1276
|
+
respond(false, { error: `Card not found: ${cardId}` });
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
respond(true, card);
|
|
1280
|
+
} catch {
|
|
1281
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1282
|
+
}
|
|
1283
|
+
},
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
api.registerGatewayMethod(
|
|
1287
|
+
"clawmatrix.board.create",
|
|
1288
|
+
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
1289
|
+
try {
|
|
1290
|
+
const runtime = getClusterRuntime();
|
|
1291
|
+
if (!runtime.kanbanManager) {
|
|
1292
|
+
respond(false, { error: "Kanban not enabled" });
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const { title, description, priority, targetNode, targetAgent, cwd, labels } = (params ?? {}) as {
|
|
1296
|
+
title?: string; description?: string; priority?: string;
|
|
1297
|
+
targetNode?: string; targetAgent?: string; cwd?: string; labels?: string[];
|
|
1298
|
+
};
|
|
1299
|
+
if (!title) {
|
|
1300
|
+
respond(false, { error: "Missing required param: title" });
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
const card = runtime.kanbanManager.createCard({
|
|
1304
|
+
title,
|
|
1305
|
+
description,
|
|
1306
|
+
priority: priority as import("./types.ts").CardPriority | undefined,
|
|
1307
|
+
targetNode,
|
|
1308
|
+
targetAgent,
|
|
1309
|
+
cwd,
|
|
1310
|
+
labels,
|
|
1311
|
+
});
|
|
1312
|
+
respond(true, card);
|
|
1313
|
+
} catch {
|
|
1314
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1315
|
+
}
|
|
1316
|
+
},
|
|
1317
|
+
);
|
|
1318
|
+
|
|
1319
|
+
api.registerGatewayMethod(
|
|
1320
|
+
"clawmatrix.board.claim",
|
|
1321
|
+
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
1322
|
+
try {
|
|
1323
|
+
const runtime = getClusterRuntime();
|
|
1324
|
+
if (!runtime.kanbanManager) {
|
|
1325
|
+
respond(false, { error: "Kanban not enabled" });
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
const { cardId, agent } = (params ?? {}) as { cardId?: string; agent?: string };
|
|
1329
|
+
if (!cardId) {
|
|
1330
|
+
respond(false, { error: "Missing required param: cardId" });
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
const card = runtime.kanbanManager.claimCard(
|
|
1334
|
+
cardId,
|
|
1335
|
+
runtime.config.nodeId,
|
|
1336
|
+
agent ?? runtime.config.agents[0]?.id ?? "unknown",
|
|
1337
|
+
);
|
|
1338
|
+
if (!card) {
|
|
1339
|
+
respond(false, { error: `Cannot claim card ${cardId}` });
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
respond(true, card);
|
|
1343
|
+
} catch {
|
|
1344
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
);
|
|
1348
|
+
|
|
1349
|
+
api.registerGatewayMethod(
|
|
1350
|
+
"clawmatrix.board.move",
|
|
1351
|
+
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
1352
|
+
try {
|
|
1353
|
+
const runtime = getClusterRuntime();
|
|
1354
|
+
if (!runtime.kanbanManager) {
|
|
1355
|
+
respond(false, { error: "Kanban not enabled" });
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const { cardId, stage } = (params ?? {}) as { cardId?: string; stage?: string };
|
|
1359
|
+
if (!cardId || !stage) {
|
|
1360
|
+
respond(false, { error: "Missing required params: cardId, stage" });
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
const card = runtime.kanbanManager.moveCard(cardId, stage as import("./types.ts").CardStage);
|
|
1364
|
+
if (!card) {
|
|
1365
|
+
respond(false, { error: `Cannot move card ${cardId}` });
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
respond(true, card);
|
|
1369
|
+
} catch {
|
|
1370
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1371
|
+
}
|
|
1372
|
+
},
|
|
1373
|
+
);
|
|
1374
|
+
|
|
1375
|
+
api.registerGatewayMethod(
|
|
1376
|
+
"clawmatrix.board.annotate",
|
|
1377
|
+
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
1378
|
+
try {
|
|
1379
|
+
const runtime = getClusterRuntime();
|
|
1380
|
+
if (!runtime.kanbanManager) {
|
|
1381
|
+
respond(false, { error: "Kanban not enabled" });
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
const { cardId, content, annotationType, agent } = (params ?? {}) as {
|
|
1385
|
+
cardId?: string; content?: string; annotationType?: string; agent?: string;
|
|
1386
|
+
};
|
|
1387
|
+
if (!cardId || !content) {
|
|
1388
|
+
respond(false, { error: "Missing required params: cardId, content" });
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
const card = runtime.kanbanManager.annotateCard(cardId, {
|
|
1392
|
+
nodeId: runtime.config.nodeId,
|
|
1393
|
+
agent: agent ?? runtime.config.agents[0]?.id ?? "unknown",
|
|
1394
|
+
type: (annotationType ?? "note") as import("./types.ts").CardAnnotation["type"],
|
|
1395
|
+
content,
|
|
1396
|
+
});
|
|
1397
|
+
if (!card) {
|
|
1398
|
+
respond(false, { error: `Card not found: ${cardId}` });
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
respond(true, card);
|
|
1402
|
+
} catch {
|
|
1403
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1404
|
+
}
|
|
1405
|
+
},
|
|
1406
|
+
);
|
|
1407
|
+
|
|
1408
|
+
api.registerGatewayMethod(
|
|
1409
|
+
"clawmatrix.board.delete",
|
|
1410
|
+
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
1411
|
+
try {
|
|
1412
|
+
const runtime = getClusterRuntime();
|
|
1413
|
+
if (!runtime.kanbanManager) {
|
|
1414
|
+
respond(false, { error: "Kanban not enabled" });
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
const { cardId } = (params ?? {}) as { cardId?: string };
|
|
1418
|
+
if (!cardId) {
|
|
1419
|
+
respond(false, { error: "Missing required param: cardId" });
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
const ok = runtime.kanbanManager.deleteCard(cardId);
|
|
1423
|
+
respond(true, { deleted: ok, cardId });
|
|
1424
|
+
} catch {
|
|
1425
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1426
|
+
}
|
|
1427
|
+
},
|
|
1428
|
+
);
|
|
1429
|
+
|
|
1430
|
+
// ── Knowledge sync gateway methods ──────────────────────────────
|
|
1431
|
+
|
|
1432
|
+
api.registerGatewayMethod(
|
|
1433
|
+
"clawmatrix.kb.files",
|
|
1434
|
+
({ respond }: GatewayRequestHandlerOptions) => {
|
|
1435
|
+
try {
|
|
1436
|
+
const runtime = getClusterRuntime();
|
|
1437
|
+
if (!runtime.knowledgeSync) {
|
|
1438
|
+
respond(false, { error: "Knowledge sync not enabled" });
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
respond(true, runtime.knowledgeSync.listSyncedFiles());
|
|
1442
|
+
} catch {
|
|
1443
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1444
|
+
}
|
|
1445
|
+
},
|
|
1446
|
+
);
|
|
1447
|
+
|
|
1448
|
+
api.registerGatewayMethod(
|
|
1449
|
+
"clawmatrix.kb.history",
|
|
1450
|
+
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
1451
|
+
try {
|
|
1452
|
+
const runtime = getClusterRuntime();
|
|
1453
|
+
if (!runtime.knowledgeSync) {
|
|
1454
|
+
respond(false, { error: "Knowledge sync not enabled" });
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const { path } = (params ?? {}) as { path?: string };
|
|
1458
|
+
if (!path) {
|
|
1459
|
+
respond(false, { error: "Missing required param: path" });
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
const rawHistory = runtime.knowledgeSync.getFileHistory(path);
|
|
1463
|
+
const history = rawHistory.map((entry) => {
|
|
1464
|
+
let nodeId: string | undefined;
|
|
1465
|
+
let agentId: string | undefined;
|
|
1466
|
+
let agentType: string | undefined;
|
|
1467
|
+
let sessionKey: string | undefined;
|
|
1468
|
+
try {
|
|
1469
|
+
const parsed = JSON.parse(entry.message);
|
|
1470
|
+
nodeId = parsed.nodeId;
|
|
1471
|
+
agentId = parsed.agentId;
|
|
1472
|
+
agentType = parsed.agentType;
|
|
1473
|
+
sessionKey = parsed.sessionKey;
|
|
1474
|
+
} catch {
|
|
1475
|
+
// message is not JSON
|
|
1476
|
+
}
|
|
1477
|
+
return {
|
|
1478
|
+
timestamp: entry.timestamp,
|
|
1479
|
+
actor: entry.actor,
|
|
1480
|
+
...(nodeId ? { nodeId } : {}),
|
|
1481
|
+
...(agentId ? { agentId } : {}),
|
|
1482
|
+
...(agentType ? { agentType } : {}),
|
|
1483
|
+
...(sessionKey ? { sessionKey } : {}),
|
|
1484
|
+
};
|
|
1485
|
+
});
|
|
1486
|
+
respond(true, { path, history });
|
|
1487
|
+
} catch {
|
|
1488
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
);
|
|
1492
|
+
|
|
1493
|
+
api.registerGatewayMethod(
|
|
1494
|
+
"clawmatrix.kb.blame",
|
|
1495
|
+
({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
1496
|
+
try {
|
|
1497
|
+
const runtime = getClusterRuntime();
|
|
1498
|
+
if (!runtime.knowledgeSync) {
|
|
1499
|
+
respond(false, { error: "Knowledge sync not enabled" });
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
const { path } = (params ?? {}) as { path?: string };
|
|
1503
|
+
if (!path) {
|
|
1504
|
+
respond(false, { error: "Missing required param: path" });
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
const blame = runtime.knowledgeSync.getFileBlame(path);
|
|
1508
|
+
if (!blame) {
|
|
1509
|
+
respond(false, { error: "File not found in sync" });
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
// Parse JSON attribution from message field
|
|
1513
|
+
const enriched = blame.map((entry) => {
|
|
1514
|
+
let nodeId: string | undefined;
|
|
1515
|
+
let agentId: string | undefined;
|
|
1516
|
+
let agentType: string | undefined;
|
|
1517
|
+
let sessionKey: string | undefined;
|
|
1518
|
+
try {
|
|
1519
|
+
const parsed = JSON.parse(entry.message);
|
|
1520
|
+
nodeId = parsed.nodeId;
|
|
1521
|
+
agentId = parsed.agentId;
|
|
1522
|
+
agentType = parsed.agentType;
|
|
1523
|
+
sessionKey = parsed.sessionKey;
|
|
1524
|
+
} catch { /* not JSON */ }
|
|
1525
|
+
return {
|
|
1526
|
+
...entry,
|
|
1527
|
+
...(nodeId ? { nodeId } : {}),
|
|
1528
|
+
...(agentId ? { agentId } : {}),
|
|
1529
|
+
...(agentType ? { agentType } : {}),
|
|
1530
|
+
...(sessionKey ? { sessionKey } : {}),
|
|
1531
|
+
};
|
|
1532
|
+
});
|
|
1533
|
+
respond(true, { path, blame: enriched });
|
|
1534
|
+
} catch {
|
|
1535
|
+
respond(false, { error: "ClawMatrix service not running" });
|
|
1536
|
+
}
|
|
1537
|
+
},
|
|
1538
|
+
);
|
|
1539
|
+
|
|
1177
1540
|
// Log model selection on each LLM call (fire-and-forget)
|
|
1178
1541
|
api.on("llm_input", (event) => {
|
|
1179
1542
|
api.logger.debug(`[clawmatrix] llm_input: provider=${event.provider} model=${event.model}`);
|
|
@@ -1256,7 +1619,6 @@ const plugin = {
|
|
|
1256
1619
|
|
|
1257
1620
|
let cachedPeerCount = -1;
|
|
1258
1621
|
let cachedSystemContext = "";
|
|
1259
|
-
|
|
1260
1622
|
api.on("before_prompt_build", () => {
|
|
1261
1623
|
try {
|
|
1262
1624
|
const runtime = getClusterRuntime();
|
|
@@ -1274,21 +1636,47 @@ const plugin = {
|
|
|
1274
1636
|
}
|
|
1275
1637
|
|
|
1276
1638
|
// Per-turn: only push pending events (agent must react proactively)
|
|
1277
|
-
const pendingEvents = runtime.
|
|
1278
|
-
|
|
1639
|
+
const pendingEvents = runtime.apiHandler?.getUnconsumedEvents(5) ?? [];
|
|
1640
|
+
const contextLines: string[] = [];
|
|
1641
|
+
|
|
1279
1642
|
if (pendingEvents.length > 0) {
|
|
1280
|
-
|
|
1643
|
+
contextLines.push("Pending events (use cluster_events to query details or consume):");
|
|
1281
1644
|
for (const evt of pendingEvents) {
|
|
1282
1645
|
const age = Math.floor((Date.now() - evt.ts) / 1000);
|
|
1283
1646
|
const dataStr = Object.entries(evt.data)
|
|
1284
1647
|
.map(([k, v]) => `${k}:${typeof v === "string" ? v : JSON.stringify(v)}`)
|
|
1285
1648
|
.join(",");
|
|
1286
1649
|
const truncated = dataStr.length > 120 ? dataStr.slice(0, 120) + "…" : dataStr;
|
|
1287
|
-
|
|
1650
|
+
contextLines.push(` [${evt.type}] ${evt.source} (${age}s,id:${evt.id}): ${truncated}`);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// Kanban board summary
|
|
1655
|
+
if (runtime.kanbanManager) {
|
|
1656
|
+
const claimable = runtime.kanbanManager.getClaimableCards(config.nodeId).length;
|
|
1657
|
+
const inProgress = runtime.kanbanManager.listCards({ stage: "in_progress", assignedNode: config.nodeId }).length;
|
|
1658
|
+
if (claimable > 0 || inProgress > 0) {
|
|
1659
|
+
const parts: string[] = [];
|
|
1660
|
+
if (claimable > 0) parts.push(`${claimable} claimable`);
|
|
1661
|
+
if (inProgress > 0) parts.push(`${inProgress} in-progress`);
|
|
1662
|
+
contextLines.push(`[Kanban] ${parts.join(", ")} card(s). Use cluster_kanban tool to interact.`);
|
|
1288
1663
|
}
|
|
1289
|
-
prependContext = evtLines.join("\n");
|
|
1290
1664
|
}
|
|
1291
1665
|
|
|
1666
|
+
// Knowledge base hint
|
|
1667
|
+
if (runtime.knowledgeSync) {
|
|
1668
|
+
const syncPaths = config.knowledge?.paths ?? [];
|
|
1669
|
+
if (syncPaths.length > 0) {
|
|
1670
|
+
contextLines.push(
|
|
1671
|
+
`[Knowledge Base] Files in synced paths (${syncPaths.join(", ")}) are shared across all cluster nodes via CRDT.` +
|
|
1672
|
+
` When producing docs, analysis, or plans, save to a synced path so other nodes can access them.` +
|
|
1673
|
+
` For project-specific docs, write in the project directory and suggest the user sync to the knowledge base if useful.`,
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
const prependContext = contextLines.length > 0 ? contextLines.join("\n") : undefined;
|
|
1679
|
+
|
|
1292
1680
|
return {
|
|
1293
1681
|
prependSystemContext: cachedSystemContext,
|
|
1294
1682
|
...(prependContext ? { prependContext } : {}),
|
|
@@ -1297,6 +1685,26 @@ const plugin = {
|
|
|
1297
1685
|
return;
|
|
1298
1686
|
}
|
|
1299
1687
|
});
|
|
1688
|
+
|
|
1689
|
+
// Track file write attribution for knowledge-sync CRDT history
|
|
1690
|
+
api.on("after_tool_call", (event, ctx) => {
|
|
1691
|
+
try {
|
|
1692
|
+
const runtime = getClusterRuntime();
|
|
1693
|
+
if (!runtime.knowledgeSync) return;
|
|
1694
|
+
const toolName = (event.toolName || "").toLowerCase();
|
|
1695
|
+
if (toolName !== "write" && toolName !== "edit") return;
|
|
1696
|
+
const filePath = (event.params?.file_path ?? event.params?.path) as string | undefined;
|
|
1697
|
+
if (!filePath) return;
|
|
1698
|
+
runtime.knowledgeSync.setPendingAttribution(filePath, {
|
|
1699
|
+
nodeId: config.nodeId,
|
|
1700
|
+
agentId: ctx.agentId ?? "unknown",
|
|
1701
|
+
agentType: "OpenClaw",
|
|
1702
|
+
sessionKey: ctx.sessionKey,
|
|
1703
|
+
});
|
|
1704
|
+
} catch {
|
|
1705
|
+
// non-critical, silently ignore
|
|
1706
|
+
}
|
|
1707
|
+
});
|
|
1300
1708
|
},
|
|
1301
1709
|
};
|
|
1302
1710
|
|