clawmatrix 0.1.15 → 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.
@@ -0,0 +1,73 @@
1
+ import type { AnyAgentTool } from "openclaw/plugin-sdk";
2
+ import { getClusterRuntime } from "../cluster-service.ts";
3
+
4
+ export function createClusterHandoffReplyTool(): AnyAgentTool {
5
+ return {
6
+ name: "cluster_handoff_reply",
7
+ label: "Cluster Handoff Reply",
8
+ description:
9
+ "Reply to a remote agent that requested more input during a handoff. " +
10
+ "Use the handoff_id returned by cluster_handoff.",
11
+ parameters: {
12
+ type: "object",
13
+ properties: {
14
+ handoff_id: {
15
+ type: "string",
16
+ description: "The handoff ID from cluster_handoff's response",
17
+ },
18
+ message: {
19
+ type: "string",
20
+ description: "Your reply to the remote agent's question",
21
+ },
22
+ },
23
+ required: ["handoff_id", "message"],
24
+ },
25
+ async execute(_toolCallId, params) {
26
+ const { handoff_id, message } = params as { handoff_id: string; message: string };
27
+
28
+ try {
29
+ const runtime = getClusterRuntime();
30
+ const result = await runtime.handoffManager.sendHandoffInput(handoff_id, message);
31
+
32
+ if (result.inputRequired) {
33
+ return {
34
+ content: [
35
+ {
36
+ type: "text" as const,
37
+ text: `Input required (handoff_id:${result.handoffId}): ${result.result}`,
38
+ },
39
+ ],
40
+ details: result,
41
+ };
42
+ }
43
+
44
+ if (!result.success) {
45
+ return {
46
+ content: [{ type: "text" as const, text: `Handoff failed: ${result.error}` }],
47
+ details: result,
48
+ };
49
+ }
50
+
51
+ return {
52
+ content: [
53
+ {
54
+ type: "text" as const,
55
+ text: JSON.stringify({ nodeId: result.nodeId, agent: result.agent, result: result.result }),
56
+ },
57
+ ],
58
+ details: result,
59
+ };
60
+ } catch (err) {
61
+ return {
62
+ content: [
63
+ {
64
+ type: "text" as const,
65
+ text: `Handoff reply error: ${err instanceof Error ? err.message : String(err)}`,
66
+ },
67
+ ],
68
+ details: { error: true },
69
+ };
70
+ }
71
+ },
72
+ };
73
+ }
@@ -37,6 +37,18 @@ export function createClusterHandoffTool(): AnyAgentTool {
37
37
  const runtime = getClusterRuntime();
38
38
  const result = await runtime.handoffManager.handoff(target, task, context);
39
39
 
40
+ if (result.inputRequired) {
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text" as const,
45
+ text: `Input required (handoff_id:${result.handoffId}): ${result.result}`,
46
+ },
47
+ ],
48
+ details: result,
49
+ };
50
+ }
51
+
40
52
  if (!result.success) {
41
53
  return {
42
54
  content: [
@@ -53,15 +65,7 @@ export function createClusterHandoffTool(): AnyAgentTool {
53
65
  content: [
54
66
  {
55
67
  type: "text" as const,
56
- text: JSON.stringify(
57
- {
58
- nodeId: result.nodeId,
59
- agent: result.agent,
60
- result: result.result,
61
- },
62
- null,
63
- 2,
64
- ),
68
+ text: JSON.stringify({ nodeId: result.nodeId, agent: result.agent, result: result.result }),
65
69
  },
66
70
  ],
67
71
  details: result,
@@ -6,7 +6,7 @@ export function createClusterPeersTool(): AnyAgentTool {
6
6
  name: "cluster_peers",
7
7
  label: "Cluster Peers",
8
8
  description:
9
- "List all reachable peers in the cluster, their agents, available models, and connection status.",
9
+ "List all reachable peers in the cluster, their agents, models, tools, and connection status.",
10
10
  parameters: {
11
11
  type: "object",
12
12
  properties: {},
@@ -21,20 +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,
26
+ tools: entry.toolProxy?.enabled ? (entry.toolProxy.allow ?? []) : [],
29
27
  status: entry.connection?.isOpen ? "connected" : "unreachable",
30
28
  latencyMs: entry.latencyMs,
31
29
  }));
32
30
 
31
+ // Include satellite nodes (minimal fields — no agents/models)
32
+ const satellites = runtime.webHandler?.getSatelliteContexts() ?? runtime.peerManager.satelliteContexts;
33
+ for (const sat of satellites) {
34
+ if (Date.now() - sat.ts >= 600_000) continue;
35
+ peers.push({
36
+ nodeId: sat.nodeId,
37
+ tools: sat.tools ?? [],
38
+ status: "satellite",
39
+ } as (typeof peers)[number]);
40
+ }
41
+
33
42
  return {
34
43
  content: [
35
44
  {
36
45
  type: "text" as const,
37
- text: JSON.stringify(peers, null, 2),
46
+ text: JSON.stringify(peers),
38
47
  },
39
48
  ],
40
49
  details: peers,
@@ -30,7 +30,7 @@ export function createClusterReadTool(): AnyAgentTool {
30
30
  content: [
31
31
  {
32
32
  type: "text" as const,
33
- text: JSON.stringify(result, null, 2),
33
+ text: JSON.stringify(result),
34
34
  },
35
35
  ],
36
36
  details: result,
@@ -6,9 +6,7 @@ export function createClusterSendTool(): AnyAgentTool {
6
6
  name: "cluster_send",
7
7
  label: "Cluster Send",
8
8
  description:
9
- "Send a one-way message to another agent in the cluster. " +
10
- "The message is injected into the target agent's session as an inbound message. " +
11
- "Does not wait for a response.",
9
+ "Send a one-way message to a remote agent. Does not wait for a response.",
12
10
  parameters: {
13
11
  type: "object",
14
12
  properties: {
@@ -6,10 +6,7 @@ export function createClusterToolTool(): AnyAgentTool {
6
6
  name: "cluster_tool",
7
7
  label: "Cluster Tool",
8
8
  description:
9
- "Invoke any OpenClaw tool on a remote cluster node. " +
10
- "Supports all tools available on the remote node (exec, read, write, edit, " +
11
- "web_search, web_fetch, browser, process, etc.). " +
12
- "Use nodeId or 'tags:<tag>' to specify the target.",
9
+ "Invoke any OpenClaw tool on a remote cluster node. Use nodeId or 'tags:<tag>' to specify the target.",
13
10
  parameters: {
14
11
  type: "object",
15
12
  properties: {
@@ -19,7 +16,7 @@ export function createClusterToolTool(): AnyAgentTool {
19
16
  },
20
17
  tool: {
21
18
  type: "string",
22
- description: "Tool name to invoke (e.g. exec, read, write, edit, web_search, web_fetch, browser, process)",
19
+ description: "Tool name to invoke on the remote node",
23
20
  },
24
21
  args: {
25
22
  type: "object",
@@ -42,7 +39,7 @@ export function createClusterToolTool(): AnyAgentTool {
42
39
  content: [
43
40
  {
44
41
  type: "text" as const,
45
- text: JSON.stringify(result, null, 2),
42
+ text: JSON.stringify(result),
46
43
  },
47
44
  ],
48
45
  details: result,
@@ -42,7 +42,7 @@ export function createClusterWriteTool(): AnyAgentTool {
42
42
  content: [
43
43
  {
44
44
  type: "text" as const,
45
- text: JSON.stringify(result, null, 2),
45
+ text: JSON.stringify(result),
46
46
  },
47
47
  ],
48
48
  details: result,
package/src/types.ts CHANGED
@@ -24,6 +24,7 @@ export interface AuthRequest extends ClusterFrame {
24
24
  models?: ModelInfo[];
25
25
  tags?: string[];
26
26
  deviceInfo?: DeviceInfo;
27
+ toolProxy?: ToolProxyInfo;
27
28
  };
28
29
  }
29
30
 
@@ -35,6 +36,7 @@ export interface AuthOk extends ClusterFrame {
35
36
  models: ModelInfo[];
36
37
  tags: string[];
37
38
  deviceInfo?: DeviceInfo;
39
+ toolProxy?: ToolProxyInfo;
38
40
  };
39
41
  }
40
42
 
@@ -44,10 +46,27 @@ export interface AuthFail extends ClusterFrame {
44
46
  }
45
47
 
46
48
  // ── Peer discovery ─────────────────────────────────────────────────
49
+ export interface SatelliteContext {
50
+ nodeId: string;
51
+ ssid?: string;
52
+ ip?: string;
53
+ router?: string;
54
+ cellular: boolean;
55
+ country?: string;
56
+ tools?: string[];
57
+ ts: number;
58
+ // Extended context
59
+ battery?: number; // 0-100
60
+ charging?: boolean;
61
+ platform?: string; // "ios" | "macos"
62
+ location?: string; // user-defined location name from WiFi mapping
63
+ }
64
+
47
65
  export interface PeerSync extends ClusterFrame {
48
66
  type: "peer_sync";
49
67
  payload: {
50
68
  peers: PeerInfo[];
69
+ satellites?: SatelliteContext[];
51
70
  };
52
71
  }
53
72
 
@@ -113,6 +132,14 @@ export interface ModelStreamChunk extends ClusterFrame {
113
132
  }
114
133
 
115
134
  // ── Handoff ────────────────────────────────────────────────────────
135
+ export type HandoffStatus = "working" | "input_required" | "completed" | "failed" | "canceled";
136
+
137
+ export interface Artifact {
138
+ name: string;
139
+ mimeType: string;
140
+ data: string; // text content or base64-encoded binary
141
+ }
142
+
116
143
  export interface HandoffRequest extends ClusterFrame {
117
144
  type: "handoff_req";
118
145
  id: string;
@@ -129,6 +156,7 @@ export interface HandoffStreamChunk extends ClusterFrame {
129
156
  payload: {
130
157
  delta: string;
131
158
  done: boolean;
159
+ artifacts?: Artifact[];
132
160
  };
133
161
  }
134
162
 
@@ -141,6 +169,48 @@ export interface HandoffResponse extends ClusterFrame {
141
169
  agent?: string;
142
170
  result?: string;
143
171
  error?: string;
172
+ artifacts?: Artifact[];
173
+ inputRequired?: boolean;
174
+ handoffId?: string;
175
+ };
176
+ }
177
+
178
+ export interface HandoffCancel extends ClusterFrame {
179
+ type: "handoff_cancel";
180
+ id: string;
181
+ payload?: unknown;
182
+ }
183
+
184
+ export interface HandoffStatusQuery extends ClusterFrame {
185
+ type: "handoff_status";
186
+ id: string;
187
+ payload?: unknown;
188
+ }
189
+
190
+ export interface HandoffStatusResponse extends ClusterFrame {
191
+ type: "handoff_status_res";
192
+ id: string;
193
+ payload: {
194
+ status: HandoffStatus;
195
+ nodeId: string;
196
+ agent: string;
197
+ elapsedMs: number;
198
+ };
199
+ }
200
+
201
+ export interface HandoffInputRequired extends ClusterFrame {
202
+ type: "handoff_input_required";
203
+ id: string;
204
+ payload: {
205
+ message: string;
206
+ };
207
+ }
208
+
209
+ export interface HandoffInput extends ClusterFrame {
210
+ type: "handoff_input";
211
+ id: string;
212
+ payload: {
213
+ message: string;
144
214
  };
145
215
  }
146
216
 
@@ -213,6 +283,12 @@ export interface ModelInfo {
213
283
  compat?: ModelCompatInfo;
214
284
  }
215
285
 
286
+ export interface ToolProxyInfo {
287
+ enabled: boolean;
288
+ allow: string[];
289
+ deny: string[];
290
+ }
291
+
216
292
  export interface PeerInfo {
217
293
  nodeId: string;
218
294
  agents: AgentInfo[];
@@ -221,6 +297,7 @@ export interface PeerInfo {
221
297
  reachableVia?: string; // nodeId of the relay node
222
298
  directPeers?: string[]; // nodeIds this node has direct connections to
223
299
  deviceInfo?: DeviceInfo; // system/hardware info
300
+ toolProxy?: ToolProxyInfo;
224
301
  }
225
302
 
226
303
  export interface NodeCapabilities {
@@ -229,6 +306,17 @@ export interface NodeCapabilities {
229
306
  models: ModelInfo[];
230
307
  tags: string[];
231
308
  deviceInfo?: DeviceInfo;
309
+ toolProxy?: ToolProxyInfo;
310
+ }
311
+
312
+ // ── Ingested events (from Shortcuts automations, etc.) ────────────
313
+ export interface IngestedEvent {
314
+ id: string;
315
+ source: string; // e.g. "shortcuts", "iphone-14", "surge"
316
+ type: string; // e.g. "message_received", "call_missed", "location_changed"
317
+ data: Record<string, unknown>;
318
+ ts: number; // ingestion timestamp
319
+ consumed: boolean; // whether an agent has consumed this event
232
320
  }
233
321
 
234
322
  // ── Union of all frame types ───────────────────────────────────────
@@ -248,6 +336,11 @@ export type AnyClusterFrame =
248
336
  | HandoffRequest
249
337
  | HandoffStreamChunk
250
338
  | HandoffResponse
339
+ | HandoffCancel
340
+ | HandoffStatusQuery
341
+ | HandoffStatusResponse
251
342
  | SendMessage
343
+ | HandoffInputRequired
344
+ | HandoffInput
252
345
  | ToolProxyRequest
253
346
  | ToolProxyResponse;