clawmatrix 0.1.16 → 0.1.17
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/package.json +1 -1
- package/src/index.ts +41 -48
- package/src/tools/cluster-events.ts +8 -8
- package/src/tools/cluster-exec.ts +1 -1
- package/src/tools/cluster-handoff-reply.ts +2 -6
- package/src/tools/cluster-handoff.ts +2 -10
- package/src/tools/cluster-peers.ts +4 -11
- package/src/tools/cluster-read.ts +1 -1
- package/src/tools/cluster-tool.ts +1 -1
- package/src/tools/cluster-write.ts +1 -1
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -164,70 +164,63 @@ const plugin = {
|
|
|
164
164
|
// CLI subcommand
|
|
165
165
|
api.registerCli(registerClusterCli, { commands: ["clawmatrix"] });
|
|
166
166
|
|
|
167
|
-
// Inject cluster context into agent prompts
|
|
167
|
+
// Inject cluster context into agent prompts.
|
|
168
|
+
//
|
|
169
|
+
// Minimal-injection strategy:
|
|
170
|
+
// prependSystemContext (cached) = static identity + guidance + peer count.
|
|
171
|
+
// Content is stable across turns → prompt caching works.
|
|
172
|
+
// Peer count is included so the agent knows the cluster exists;
|
|
173
|
+
// detailed topology is on-demand via cluster_peers tool.
|
|
174
|
+
// prependContext (per-turn) = only pending event notifications.
|
|
175
|
+
// Events need proactive push so the agent can react without being asked.
|
|
176
|
+
// Everything else (peer details, satellites) is pull-based via tools.
|
|
177
|
+
|
|
178
|
+
let cachedPeerCount = -1;
|
|
179
|
+
let cachedSystemContext = "";
|
|
180
|
+
|
|
168
181
|
api.on("before_prompt_build", () => {
|
|
169
182
|
try {
|
|
170
183
|
const runtime = getClusterRuntime();
|
|
171
|
-
const
|
|
172
|
-
if (peers.length === 0) return;
|
|
173
|
-
|
|
174
|
-
const lines = [
|
|
175
|
-
`[ClawMatrix Cluster]`,
|
|
176
|
-
`You are on node "${config.nodeId}"${config.tags.length ? ` (tags: ${config.tags.join(", ")})` : ""}.`,
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
if (config.agents.length > 0) {
|
|
180
|
-
const localAgent = config.agents[0]!;
|
|
181
|
-
lines.push(`Your role: ${localAgent.description}`);
|
|
182
|
-
}
|
|
184
|
+
const peerCount = runtime.peerManager.router.getAllPeers().length;
|
|
183
185
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
for (const sat of activeSatellites) {
|
|
201
|
-
const age = Math.floor((Date.now() - sat.ts) / 1000);
|
|
202
|
-
const country = sat.country ? `, ${sat.country}` : "";
|
|
203
|
-
lines.push(` - ${sat.nodeId} (satellite${country}, ${age}s ago)`);
|
|
204
|
-
if (sat.tools?.length) {
|
|
205
|
-
lines.push(` tools: ${sat.tools.join(", ")}`);
|
|
186
|
+
// Rebuild system context only when peer count changes
|
|
187
|
+
if (peerCount !== cachedPeerCount) {
|
|
188
|
+
cachedPeerCount = peerCount;
|
|
189
|
+
const lines: string[] = [];
|
|
190
|
+
if (peerCount === 0) {
|
|
191
|
+
lines.push("[ClawMatrix] No peers online. Use cluster_peers to check cluster status.");
|
|
192
|
+
} else {
|
|
193
|
+
lines.push(
|
|
194
|
+
`[ClawMatrix Cluster] node="${config.nodeId}"${config.tags.length ? ` tags=${config.tags.join(",")}` : ""}`,
|
|
195
|
+
...(config.agents.length > 0 ? [`Role: ${config.agents[0]!.description}`] : []),
|
|
196
|
+
`${peerCount} remote peer(s) online. Use cluster_peers to see topology, agents, and models.`,
|
|
197
|
+
"Prefer cluster_exec/read/write for simple ops; cluster_handoff for complex multi-step tasks.",
|
|
198
|
+
"IMPORTANT: Always tell user which remote node you're targeting before calling cluster tools.",
|
|
199
|
+
);
|
|
206
200
|
}
|
|
201
|
+
cachedSystemContext = lines.join("\n");
|
|
207
202
|
}
|
|
208
203
|
|
|
209
|
-
//
|
|
204
|
+
// Per-turn: only push pending events (agent must react proactively)
|
|
210
205
|
const pendingEvents = runtime.webHandler?.getUnconsumedEvents(5) ?? [];
|
|
206
|
+
let prependContext: string | undefined;
|
|
211
207
|
if (pendingEvents.length > 0) {
|
|
212
|
-
|
|
208
|
+
const evtLines = ["Pending events (use cluster_events to query details or consume):"];
|
|
213
209
|
for (const evt of pendingEvents) {
|
|
214
210
|
const age = Math.floor((Date.now() - evt.ts) / 1000);
|
|
215
211
|
const dataStr = Object.entries(evt.data)
|
|
216
|
-
.map(([k, v]) => `${k}
|
|
217
|
-
.join(",
|
|
212
|
+
.map(([k, v]) => `${k}:${typeof v === "string" ? v : JSON.stringify(v)}`)
|
|
213
|
+
.join(",");
|
|
218
214
|
const truncated = dataStr.length > 120 ? dataStr.slice(0, 120) + "…" : dataStr;
|
|
219
|
-
|
|
215
|
+
evtLines.push(` [${evt.type}] ${evt.source} (${age}s,id:${evt.id}): ${truncated}`);
|
|
220
216
|
}
|
|
221
|
-
|
|
217
|
+
prependContext = evtLines.join("\n");
|
|
222
218
|
}
|
|
223
219
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
return { prependSystemContext: lines.join("\n") };
|
|
220
|
+
return {
|
|
221
|
+
prependSystemContext: cachedSystemContext,
|
|
222
|
+
...(prependContext ? { prependContext } : {}),
|
|
223
|
+
};
|
|
231
224
|
} catch {
|
|
232
225
|
return;
|
|
233
226
|
}
|
|
@@ -13,32 +13,32 @@ export function createClusterEventsTool(): AnyAgentTool {
|
|
|
13
13
|
action: {
|
|
14
14
|
type: "string",
|
|
15
15
|
enum: ["query", "consume"],
|
|
16
|
-
description: '"query" to list events, "consume" to mark
|
|
16
|
+
description: '"query" to list events (type/source/unconsumed/since/limit filters apply), "consume" to mark as processed (requires ids)',
|
|
17
17
|
},
|
|
18
18
|
type: {
|
|
19
19
|
type: "string",
|
|
20
|
-
description: 'Filter by event type (e.g. "message_received"
|
|
20
|
+
description: 'Filter by event type (e.g. "message_received")',
|
|
21
21
|
},
|
|
22
22
|
source: {
|
|
23
23
|
type: "string",
|
|
24
|
-
description: 'Filter by source (e.g. "shortcuts"
|
|
24
|
+
description: 'Filter by source (e.g. "shortcuts")',
|
|
25
25
|
},
|
|
26
26
|
unconsumed: {
|
|
27
27
|
type: "boolean",
|
|
28
|
-
description: "Only
|
|
28
|
+
description: "Only unconsumed events (default true)",
|
|
29
29
|
},
|
|
30
30
|
since: {
|
|
31
31
|
type: "number",
|
|
32
|
-
description: "
|
|
32
|
+
description: "Events after this unix timestamp (ms)",
|
|
33
33
|
},
|
|
34
34
|
limit: {
|
|
35
35
|
type: "number",
|
|
36
|
-
description: "Max events to return
|
|
36
|
+
description: "Max events to return (default 20)",
|
|
37
37
|
},
|
|
38
38
|
ids: {
|
|
39
39
|
type: "array",
|
|
40
40
|
items: { type: "string" },
|
|
41
|
-
description: "Event IDs to
|
|
41
|
+
description: "Event IDs to consume",
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
required: ["action"],
|
|
@@ -105,7 +105,7 @@ export function createClusterEventsTool(): AnyAgentTool {
|
|
|
105
105
|
}));
|
|
106
106
|
|
|
107
107
|
return {
|
|
108
|
-
content: [{ type: "text" as const, text: JSON.stringify(summary
|
|
108
|
+
content: [{ type: "text" as const, text: JSON.stringify(summary) }],
|
|
109
109
|
details: { events: summary },
|
|
110
110
|
};
|
|
111
111
|
} catch (err) {
|
|
@@ -34,7 +34,7 @@ export function createClusterHandoffReplyTool(): AnyAgentTool {
|
|
|
34
34
|
content: [
|
|
35
35
|
{
|
|
36
36
|
type: "text" as const,
|
|
37
|
-
text: `
|
|
37
|
+
text: `Input required (handoff_id:${result.handoffId}): ${result.result}`,
|
|
38
38
|
},
|
|
39
39
|
],
|
|
40
40
|
details: result,
|
|
@@ -52,11 +52,7 @@ export function createClusterHandoffReplyTool(): AnyAgentTool {
|
|
|
52
52
|
content: [
|
|
53
53
|
{
|
|
54
54
|
type: "text" as const,
|
|
55
|
-
text: JSON.stringify(
|
|
56
|
-
{ nodeId: result.nodeId, agent: result.agent, result: result.result },
|
|
57
|
-
null,
|
|
58
|
-
2,
|
|
59
|
-
),
|
|
55
|
+
text: JSON.stringify({ nodeId: result.nodeId, agent: result.agent, result: result.result }),
|
|
60
56
|
},
|
|
61
57
|
],
|
|
62
58
|
details: result,
|
|
@@ -42,7 +42,7 @@ export function createClusterHandoffTool(): AnyAgentTool {
|
|
|
42
42
|
content: [
|
|
43
43
|
{
|
|
44
44
|
type: "text" as const,
|
|
45
|
-
text: `
|
|
45
|
+
text: `Input required (handoff_id:${result.handoffId}): ${result.result}`,
|
|
46
46
|
},
|
|
47
47
|
],
|
|
48
48
|
details: result,
|
|
@@ -65,15 +65,7 @@ export function createClusterHandoffTool(): AnyAgentTool {
|
|
|
65
65
|
content: [
|
|
66
66
|
{
|
|
67
67
|
type: "text" as const,
|
|
68
|
-
text: JSON.stringify(
|
|
69
|
-
{
|
|
70
|
-
nodeId: result.nodeId,
|
|
71
|
-
agent: result.agent,
|
|
72
|
-
result: result.result,
|
|
73
|
-
},
|
|
74
|
-
null,
|
|
75
|
-
2,
|
|
76
|
-
),
|
|
68
|
+
text: JSON.stringify({ nodeId: result.nodeId, agent: result.agent, result: result.result }),
|
|
77
69
|
},
|
|
78
70
|
],
|
|
79
71
|
details: result,
|
|
@@ -21,36 +21,29 @@ export function createClusterPeersTool(): AnyAgentTool {
|
|
|
21
21
|
description: a.description,
|
|
22
22
|
tags: a.tags,
|
|
23
23
|
})),
|
|
24
|
-
models: entry.models.map((m) =>
|
|
25
|
-
id: m.id,
|
|
26
|
-
provider: m.provider,
|
|
27
|
-
})),
|
|
24
|
+
models: entry.models.map((m) => m.id),
|
|
28
25
|
tags: entry.tags,
|
|
29
26
|
tools: entry.toolProxy?.enabled ? (entry.toolProxy.allow ?? []) : [],
|
|
30
27
|
status: entry.connection?.isOpen ? "connected" : "unreachable",
|
|
31
28
|
latencyMs: entry.latencyMs,
|
|
32
29
|
}));
|
|
33
30
|
|
|
34
|
-
// Include satellite nodes
|
|
31
|
+
// Include satellite nodes (minimal fields — no agents/models)
|
|
35
32
|
const satellites = runtime.webHandler?.getSatelliteContexts() ?? runtime.peerManager.satelliteContexts;
|
|
36
33
|
for (const sat of satellites) {
|
|
37
34
|
if (Date.now() - sat.ts >= 600_000) continue;
|
|
38
35
|
peers.push({
|
|
39
36
|
nodeId: sat.nodeId,
|
|
40
|
-
agents: [],
|
|
41
|
-
models: [],
|
|
42
|
-
tags: [],
|
|
43
37
|
tools: sat.tools ?? [],
|
|
44
38
|
status: "satellite",
|
|
45
|
-
|
|
46
|
-
});
|
|
39
|
+
} as (typeof peers)[number]);
|
|
47
40
|
}
|
|
48
41
|
|
|
49
42
|
return {
|
|
50
43
|
content: [
|
|
51
44
|
{
|
|
52
45
|
type: "text" as const,
|
|
53
|
-
text: JSON.stringify(peers
|
|
46
|
+
text: JSON.stringify(peers),
|
|
54
47
|
},
|
|
55
48
|
],
|
|
56
49
|
details: peers,
|