aicq-openclaw 3.16.3

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/src/tools.js ADDED
@@ -0,0 +1,210 @@
1
+ /**
2
+ * AICQ Plugin — Agent Tool Definitions
3
+ *
4
+ * These tools let the OpenClaw AI agent manage friends and send messages
5
+ * via the AICQ encrypted chat network. The tools are registered via
6
+ * api.registerTool() in the channel plugin's registerFull() callback.
7
+ *
8
+ * Tools:
9
+ * 1. chat-friend — Friend management (add/accept/reject/list/remove)
10
+ * 2. chat-send — Send messages (private or group)
11
+ * 3. chat-export-key — Export the agent's public key
12
+ */
13
+
14
+ /**
15
+ * Create the three AICQ agent tools.
16
+ * @param {object} runtime — The plugin runtime store (has chat, handshake, identity, serverClient, db, handleGateway)
17
+ * @returns {Array} Array of tool definition objects
18
+ */
19
+ export function createAicqTools(runtime) {
20
+ // Helper: get the current agent id (always "main" for now)
21
+ function getCurrentAgentId() {
22
+ if (runtime.identity) {
23
+ const agents = runtime.identity.listAgents();
24
+ if (agents.length > 0) return agents[0].agent_id;
25
+ }
26
+ return "main";
27
+ }
28
+
29
+ // Helper: format tool result
30
+ function ok(text, details) {
31
+ return {
32
+ content: [{ type: "text", text: typeof text === "string" ? text : JSON.stringify(text, null, 2) }],
33
+ details: details || text,
34
+ };
35
+ }
36
+ function err(text) {
37
+ return {
38
+ content: [{ type: "text", text: `Error: ${text}` }],
39
+ details: { error: text },
40
+ };
41
+ }
42
+
43
+ // ── Tool 1: chat-friend ──────────────────────────────────────────
44
+ const chatFriendTool = {
45
+ name: "chat-friend",
46
+ label: "AICQ Friend Management",
47
+ description:
48
+ "Manage AICQ friends: add (send friend request by account ID), accept, reject, list (show all friends), or remove. " +
49
+ "Use action 'list' to see all friends with their online status. " +
50
+ "Use action 'add' with account_id to send a friend request.",
51
+ parameters: {
52
+ type: "object",
53
+ additionalProperties: false,
54
+ properties: {
55
+ action: {
56
+ type: "string",
57
+ enum: ["add", "accept", "reject", "list", "remove"],
58
+ description: "Friend management action",
59
+ },
60
+ account_id: {
61
+ type: "string",
62
+ description: "Target account ID (for add/accept/reject/remove). e.g. '1000008'",
63
+ },
64
+ friend_name: {
65
+ type: "string",
66
+ description: "Optional friend nickname for add",
67
+ },
68
+ },
69
+ required: ["action"],
70
+ },
71
+ execute: async (_toolCallId, params) => {
72
+ try {
73
+ const agentId = getCurrentAgentId();
74
+ const action = params.action;
75
+ const accountId = params.account_id || "";
76
+
77
+ switch (action) {
78
+ case "list": {
79
+ const result = await runtime.handleGateway("aicq.friends.list", {});
80
+ const friends = result.friends || [];
81
+ if (friends.length === 0) return ok("You have no friends yet.");
82
+ const lines = friends.map((f) =>
83
+ ` ${f.id} (${f.friend_type || "unknown"}) ${f.ai_name || ""} ${f.is_online ? "[online]" : "[offline]"}`
84
+ );
85
+ return ok(`Friends (${friends.length}):\n${lines.join("\n")}`, { count: friends.length, friends });
86
+ }
87
+
88
+ case "add": {
89
+ if (!accountId) return err("account_id is required for 'add' action");
90
+ await runtime.serverClient.ensureAuth(agentId);
91
+ const result = await runtime.serverClient.sendFriendRequest(accountId, `Hi, I'd like to add you as a friend!`);
92
+ return ok(`Friend request sent to ${accountId}. Status: ${result.status || "pending"}`, result);
93
+ }
94
+
95
+ case "accept": {
96
+ if (!accountId) return err("account_id (request_id) is required for 'accept' action");
97
+ const result = await runtime.handleGateway("aicq.friends.acceptRequest", { request_id: accountId });
98
+ return ok(`Friend request ${accountId} accepted.`, result);
99
+ }
100
+
101
+ case "reject": {
102
+ if (!accountId) return err("account_id (request_id) is required for 'reject' action");
103
+ const result = await runtime.handleGateway("aicq.friends.rejectRequest", { request_id: accountId });
104
+ return ok(`Friend request ${accountId} rejected.`, result);
105
+ }
106
+
107
+ case "remove": {
108
+ if (!accountId) return err("account_id is required for 'remove' action");
109
+ runtime.db.removeFriend(agentId, accountId);
110
+ return ok(`Friend ${accountId} removed.`);
111
+ }
112
+
113
+ default:
114
+ return err(`Unknown action: ${action}. Use add/accept/reject/list/remove.`);
115
+ }
116
+ } catch (e) {
117
+ return err(e.message);
118
+ }
119
+ },
120
+ };
121
+
122
+ // ── Tool 2: chat-send ────────────────────────────────────────────
123
+ const chatSendTool = {
124
+ name: "chat-send",
125
+ label: "AICQ Send Message",
126
+ description:
127
+ "Send a message to a friend or group via AICQ encrypted chat. " +
128
+ "Use type 'private' for direct messages (requires friend's account_id as 'to') " +
129
+ "or type 'group' for group messages (requires group_id as 'to').",
130
+ parameters: {
131
+ type: "object",
132
+ additionalProperties: false,
133
+ properties: {
134
+ to: { type: "string", description: "Recipient account ID (private) or group ID (group). e.g. '1000008'" },
135
+ content: { type: "string", description: "Message content to send" },
136
+ type: {
137
+ type: "string",
138
+ enum: ["private", "group"],
139
+ description: "Message type: private (default) or group",
140
+ },
141
+ },
142
+ required: ["to", "content"],
143
+ },
144
+ execute: async (_toolCallId, params) => {
145
+ try {
146
+ const agentId = getCurrentAgentId();
147
+ const targetId = params.to;
148
+ const content = params.content;
149
+ const isGroup = params.type === "group";
150
+
151
+ if (!targetId) return err("'to' (recipient) is required");
152
+ if (!content) return err("'content' (message body) is required");
153
+
154
+ await runtime.chat.sendMessage(agentId, targetId, content, { isGroup });
155
+ return ok(`Message sent to ${targetId} (${isGroup ? "group" : "private"}): ${content.substring(0, 100)}`, {
156
+ to: targetId,
157
+ type: isGroup ? "group" : "private",
158
+ length: content.length,
159
+ });
160
+ } catch (e) {
161
+ return err(e.message);
162
+ }
163
+ },
164
+ };
165
+
166
+ // ── Tool 3: chat-export-key ──────────────────────────────────────
167
+ const chatExportKeyTool = {
168
+ name: "chat-export-key",
169
+ label: "AICQ Export Identity Key",
170
+ description:
171
+ "Export the AI agent's public key for friend verification. " +
172
+ "The public key can be shared with friends so they can verify your identity. " +
173
+ "Supports hex (default) or base64 format.",
174
+ parameters: {
175
+ type: "object",
176
+ additionalProperties: false,
177
+ properties: {
178
+ format: {
179
+ type: "string",
180
+ enum: ["hex", "base64"],
181
+ description: "Output format: hex (default) or base64",
182
+ },
183
+ },
184
+ },
185
+ execute: async (_toolCallId, params) => {
186
+ try {
187
+ const agentId = getCurrentAgentId();
188
+ const format = params.format || "hex";
189
+ const info = runtime.identity.getInfo(agentId);
190
+ if (!info) return err("Agent identity not found");
191
+
192
+ let publicKey = info.signing_public_key || "";
193
+ if (format === "base64") {
194
+ const buf = Buffer.from(publicKey, "hex");
195
+ publicKey = buf.toString("base64");
196
+ }
197
+
198
+ return ok(
199
+ `Agent: ${agentId}\nPublic Key (${format}): ${publicKey}\nFingerprint: ${info.fingerprint || "N/A"}`,
200
+ { agent_id: agentId, public_key: publicKey, format, fingerprint: info.fingerprint }
201
+ );
202
+ } catch (e) {
203
+ return err(e.message);
204
+ }
205
+ },
206
+ };
207
+
208
+ return [chatFriendTool, chatSendTool, chatExportKeyTool];
209
+ }
210
+