clawmatrix 0.3.1 → 0.4.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/cli/bin/clawmatrix.mjs +1006 -0
- package/cli/package.json +27 -0
- package/cli/skills/clawmatrix/SKILL.md +104 -0
- package/openclaw.plugin.json +1 -0
- package/package.json +2 -1
- package/src/acp-proxy.ts +425 -37
- package/src/cluster-service.ts +82 -3
- package/src/config.ts +2 -1
- package/src/handoff.ts +8 -0
- package/src/health-tracker.ts +80 -19
- package/src/index.ts +471 -28
- package/src/knowledge-sync.ts +18 -4
- package/src/model-proxy.ts +16 -0
- package/src/peer-manager.ts +318 -25
- package/src/router.ts +93 -1
- package/src/tool-proxy.ts +40 -2
- package/src/tools/cluster-notify.ts +132 -0
- package/src/tools/cluster-peers.ts +2 -0
- package/src/types.ts +1 -1
- package/src/cli.ts +0 -711
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { AnyAgentTool } from "openclaw/plugin-sdk";
|
|
2
|
+
import { getClusterRuntime } from "../cluster-service.ts";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
|
|
5
|
+
export function createClusterNotifyTool(): AnyAgentTool {
|
|
6
|
+
return {
|
|
7
|
+
name: "cluster_notify",
|
|
8
|
+
label: "Cluster Notify",
|
|
9
|
+
description:
|
|
10
|
+
"Push a notification to mobile devices in the cluster (triggers Dynamic Island / Live Activity on iOS). " +
|
|
11
|
+
"Use this when you are about to start a long-running task so the user can track progress on their phone. " +
|
|
12
|
+
'Actions: "start" begins a new activity, "update" changes detail/progress, "end" dismisses it.',
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
action: {
|
|
17
|
+
type: "string",
|
|
18
|
+
enum: ["start", "update", "end"],
|
|
19
|
+
description: 'Action to perform. Default: "start"',
|
|
20
|
+
},
|
|
21
|
+
taskId: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Task ID for update/end actions. Returned by start action. Omit for start to auto-generate.",
|
|
24
|
+
},
|
|
25
|
+
title: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Activity title (shown in Dynamic Island and lock screen)",
|
|
28
|
+
},
|
|
29
|
+
detail: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Activity detail text (e.g. current step description)",
|
|
32
|
+
},
|
|
33
|
+
progress: {
|
|
34
|
+
type: "number",
|
|
35
|
+
description: "Progress value 0.0 to 1.0 (optional, shows progress bar when provided)",
|
|
36
|
+
},
|
|
37
|
+
tool: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Current tool being executed (shown as a tag in the activity)",
|
|
40
|
+
},
|
|
41
|
+
success: {
|
|
42
|
+
type: "boolean",
|
|
43
|
+
description: 'For "end" action: true for completed, false for failed. Default: true',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
required: [],
|
|
47
|
+
},
|
|
48
|
+
async execute(_toolCallId, params) {
|
|
49
|
+
const {
|
|
50
|
+
action = "start",
|
|
51
|
+
taskId: providedTaskId,
|
|
52
|
+
title,
|
|
53
|
+
detail,
|
|
54
|
+
progress,
|
|
55
|
+
tool,
|
|
56
|
+
success = true,
|
|
57
|
+
} = params as {
|
|
58
|
+
action?: "start" | "update" | "end";
|
|
59
|
+
taskId?: string;
|
|
60
|
+
title?: string;
|
|
61
|
+
detail?: string;
|
|
62
|
+
progress?: number;
|
|
63
|
+
tool?: string;
|
|
64
|
+
success?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const runtime = getClusterRuntime();
|
|
69
|
+
const peers = runtime.peerManager.router.getAllPeers();
|
|
70
|
+
const mobileTargets = peers.filter((p) =>
|
|
71
|
+
p.tags.some((t) => t === "mobile" || t === "ios" || t === "phone"),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (mobileTargets.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text" as const, text: "No mobile peers connected" }],
|
|
77
|
+
details: { error: true },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const taskId = providedTaskId || randomUUID();
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
|
|
84
|
+
let status: string;
|
|
85
|
+
if (action === "start") status = "started";
|
|
86
|
+
else if (action === "end") status = success ? "completed" : "failed";
|
|
87
|
+
else status = "progress";
|
|
88
|
+
|
|
89
|
+
const frame = {
|
|
90
|
+
type: "task_activity" as const,
|
|
91
|
+
id: randomUUID(),
|
|
92
|
+
from: runtime.config.nodeId,
|
|
93
|
+
timestamp: now,
|
|
94
|
+
payload: {
|
|
95
|
+
taskId,
|
|
96
|
+
taskType: "notify" as const,
|
|
97
|
+
status,
|
|
98
|
+
agent: title || runtime.config.nodeId,
|
|
99
|
+
nodeId: runtime.config.nodeId,
|
|
100
|
+
title: title || runtime.config.nodeId,
|
|
101
|
+
detail: detail || (action === "end" ? (success ? "已完成" : "失败") : undefined),
|
|
102
|
+
startedAt: now,
|
|
103
|
+
elapsedMs: 0,
|
|
104
|
+
progress,
|
|
105
|
+
tool,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
for (const target of mobileTargets) {
|
|
110
|
+
runtime.peerManager.sendTo(target.nodeId, { ...frame, to: target.nodeId });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const targetNames = mobileTargets.map((t) => t.nodeId).join(", ");
|
|
114
|
+
return {
|
|
115
|
+
content: [{
|
|
116
|
+
type: "text" as const,
|
|
117
|
+
text: `Notification ${action}ed → ${targetNames} (taskId: ${taskId})`,
|
|
118
|
+
}],
|
|
119
|
+
details: { taskId, action, targets: mobileTargets.length },
|
|
120
|
+
};
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return {
|
|
123
|
+
content: [{
|
|
124
|
+
type: "text" as const,
|
|
125
|
+
text: `Notify error: ${err instanceof Error ? err.message : String(err)}`,
|
|
126
|
+
}],
|
|
127
|
+
details: { error: true },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -41,6 +41,7 @@ export function createClusterPeersTool(): AnyAgentTool {
|
|
|
41
41
|
const sentinelOnline = sentinelStatus === "direct" || sentinelStatus === "relay";
|
|
42
42
|
const effectiveStatus = status === "unreachable" && sentinelOnline ? "sentinel-only" : status;
|
|
43
43
|
|
|
44
|
+
const channelCount = runtime.peerManager.router.getChannelCount(entry.nodeId);
|
|
44
45
|
return {
|
|
45
46
|
nodeId: entry.nodeId,
|
|
46
47
|
agents: entry.agents.map((a) => ({
|
|
@@ -53,6 +54,7 @@ export function createClusterPeersTool(): AnyAgentTool {
|
|
|
53
54
|
tools: entry.toolProxy?.enabled ? (entry.toolProxy.allow ?? []) : [],
|
|
54
55
|
status: effectiveStatus,
|
|
55
56
|
latencyMs: entry.latencyMs,
|
|
57
|
+
...(channelCount > 1 ? { channels: channelCount } : {}),
|
|
56
58
|
...(hasSentinel ? {
|
|
57
59
|
sentinel: sentinelOnline ? "online" : "offline",
|
|
58
60
|
} : {}),
|
package/src/types.ts
CHANGED
|
@@ -620,7 +620,7 @@ export interface AcpSessionInfo {
|
|
|
620
620
|
description?: string; // first user message (for display in session list)
|
|
621
621
|
updatedAt?: string;
|
|
622
622
|
agent?: string; // which ACP agent (claude, codex, etc.)
|
|
623
|
-
status?: "active" | "idle"; //
|
|
623
|
+
status?: "busy" | "active" | "idle"; // busy = currently executing a prompt, active = daemon alive, idle = on disk only
|
|
624
624
|
}
|
|
625
625
|
|
|
626
626
|
export interface AcpListRequest extends ClusterFrame {
|