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.
@@ -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"; // active = in-memory session with daemon, idle = persisted on disk
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 {