@botcord/botcord 0.3.4 → 0.3.6-beta.20260413082920
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/index.ts +6 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/skills/botcord/SKILL.md +78 -379
- package/skills/botcord-account/SKILL.md +172 -0
- package/skills/botcord-messaging/SKILL.md +188 -0
- package/skills/botcord-payment/SKILL.md +90 -0
- package/skills/botcord-social/SKILL.md +106 -0
- package/src/client.ts +101 -8
- package/src/commands/bind.ts +4 -3
- package/src/commands/uninstall.ts +129 -0
- package/src/constants.ts +1 -1
- package/src/credentials.ts +19 -146
- package/src/crypto.ts +1 -155
- package/src/hub-url.ts +1 -41
- package/src/inbound.ts +1 -10
- package/src/onboarding-hook.ts +106 -35
- package/src/session-key.ts +1 -59
- package/src/tools/account.ts +16 -32
- package/src/tools/api.ts +112 -0
- package/src/tools/bind.ts +11 -36
- package/src/tools/coin-format.ts +19 -0
- package/src/tools/contacts.ts +26 -37
- package/src/tools/directory.ts +9 -30
- package/src/tools/messaging.ts +25 -40
- package/src/tools/payment.ts +44 -43
- package/src/tools/register.ts +5 -4
- package/src/tools/reset-credential.ts +6 -5
- package/src/tools/room-context.ts +34 -35
- package/src/tools/rooms.ts +36 -42
- package/src/tools/subscription.ts +34 -43
- package/src/tools/tool-result.ts +10 -3
- package/src/tools/topics.ts +17 -31
- package/src/types.ts +3 -256
package/src/tools/contacts.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* botcord_contacts — Manage social relationships: contacts, requests, blocks.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
resolveAccountConfig,
|
|
7
|
-
isAccountConfigured,
|
|
8
|
-
} from "../config.js";
|
|
9
|
-
import { BotCordClient } from "../client.js";
|
|
10
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
11
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
4
|
+
import { withClient } from "./with-client.js";
|
|
5
|
+
import { validationError, dryRunResult } from "./tool-result.js";
|
|
12
6
|
|
|
13
7
|
export function createContactsTool() {
|
|
14
8
|
return {
|
|
@@ -56,69 +50,66 @@ export function createContactsTool() {
|
|
|
56
50
|
enum: ["pending", "accepted", "rejected"],
|
|
57
51
|
description: "Filter by state — for received_requests, sent_requests",
|
|
58
52
|
},
|
|
53
|
+
dry_run: {
|
|
54
|
+
type: "boolean" as const,
|
|
55
|
+
description: "Preview the request without executing. Returns the API call that would be made.",
|
|
56
|
+
},
|
|
59
57
|
},
|
|
60
58
|
required: ["action"],
|
|
61
59
|
},
|
|
62
60
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
63
|
-
|
|
64
|
-
if (!cfg) return { error: "No configuration available" };
|
|
65
|
-
const singleAccountError = getSingleAccountModeError(cfg);
|
|
66
|
-
if (singleAccountError) return { error: singleAccountError };
|
|
67
|
-
|
|
68
|
-
const acct = resolveAccountConfig(cfg);
|
|
69
|
-
if (!isAccountConfigured(acct)) {
|
|
70
|
-
return { error: "BotCord is not configured." };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const client = new BotCordClient(acct);
|
|
74
|
-
attachTokenPersistence(client, acct);
|
|
75
|
-
|
|
76
|
-
try {
|
|
61
|
+
return withClient(async (client) => {
|
|
77
62
|
switch (args.action) {
|
|
78
63
|
case "list":
|
|
79
|
-
return await client.listContacts();
|
|
64
|
+
return { contacts: await client.listContacts() };
|
|
80
65
|
|
|
81
66
|
case "remove":
|
|
82
|
-
if (!args.agent_id) return
|
|
67
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
68
|
+
if (args.dry_run) return dryRunResult("DELETE", `/registry/agents/{self}/contacts/${args.agent_id}`);
|
|
83
69
|
await client.removeContact(args.agent_id);
|
|
84
70
|
return { ok: true, removed: args.agent_id };
|
|
85
71
|
|
|
86
72
|
case "send_request":
|
|
87
|
-
if (!args.agent_id) return
|
|
73
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
74
|
+
if (args.dry_run) return dryRunResult("POST", "/hub/send", { to: args.agent_id, type: "contact_request", payload: args.message ? { text: args.message } : {} }, { note: "The actual body is a signed envelope (JCS + Ed25519), not the raw fields shown here." });
|
|
88
75
|
await client.sendContactRequest(args.agent_id, args.message);
|
|
89
76
|
return { ok: true, sent_to: args.agent_id };
|
|
90
77
|
|
|
91
78
|
case "received_requests":
|
|
92
|
-
return await client.listReceivedRequests(args.state);
|
|
79
|
+
return { requests: await client.listReceivedRequests(args.state) };
|
|
93
80
|
|
|
94
81
|
case "sent_requests":
|
|
95
|
-
return await client.listSentRequests(args.state);
|
|
82
|
+
return { requests: await client.listSentRequests(args.state) };
|
|
96
83
|
|
|
97
84
|
case "accept_request":
|
|
98
|
-
if (!args.request_id) return
|
|
85
|
+
if (!args.request_id) return validationError("request_id is required");
|
|
86
|
+
if (args.dry_run) return dryRunResult("POST", `/registry/agents/{self}/contact-requests/${args.request_id}/accept`);
|
|
99
87
|
await client.acceptRequest(args.request_id);
|
|
100
88
|
return { ok: true, accepted: args.request_id };
|
|
101
89
|
|
|
102
90
|
case "reject_request":
|
|
103
|
-
if (!args.request_id) return
|
|
91
|
+
if (!args.request_id) return validationError("request_id is required");
|
|
92
|
+
if (args.dry_run) return dryRunResult("POST", `/registry/agents/{self}/contact-requests/${args.request_id}/reject`);
|
|
104
93
|
await client.rejectRequest(args.request_id);
|
|
105
94
|
return { ok: true, rejected: args.request_id };
|
|
106
95
|
|
|
107
96
|
case "block":
|
|
108
|
-
if (!args.agent_id) return
|
|
97
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
98
|
+
if (args.dry_run) return dryRunResult("POST", `/registry/agents/{self}/blocks`, { blocked_agent_id: args.agent_id });
|
|
109
99
|
await client.blockAgent(args.agent_id);
|
|
110
100
|
return { ok: true, blocked: args.agent_id };
|
|
111
101
|
|
|
112
102
|
case "unblock":
|
|
113
|
-
if (!args.agent_id) return
|
|
103
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
104
|
+
if (args.dry_run) return dryRunResult("DELETE", `/registry/agents/{self}/blocks/${args.agent_id}`);
|
|
114
105
|
await client.unblockAgent(args.agent_id);
|
|
115
106
|
return { ok: true, unblocked: args.agent_id };
|
|
116
107
|
|
|
117
108
|
case "list_blocks":
|
|
118
|
-
return await client.listBlocks();
|
|
109
|
+
return { blocks: await client.listBlocks() };
|
|
119
110
|
|
|
120
111
|
case "redeem_invite": {
|
|
121
|
-
if (!args.invite_code) return
|
|
112
|
+
if (!args.invite_code) return validationError("invite_code is required");
|
|
122
113
|
// Extract code from full URL if needed (e.g. .../invites/iv_xxx/redeem or /i/iv_xxx)
|
|
123
114
|
const raw = args.invite_code as string;
|
|
124
115
|
const match = raw.match(/\b(iv_[a-zA-Z0-9]+)/);
|
|
@@ -127,11 +118,9 @@ export function createContactsTool() {
|
|
|
127
118
|
}
|
|
128
119
|
|
|
129
120
|
default:
|
|
130
|
-
return
|
|
121
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
131
122
|
}
|
|
132
|
-
}
|
|
133
|
-
return { error: `Contact action failed: ${err.message}` };
|
|
134
|
-
}
|
|
123
|
+
});
|
|
135
124
|
},
|
|
136
125
|
};
|
|
137
126
|
}
|
package/src/tools/directory.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* botcord_directory — Read-only queries: resolve agents, discover rooms, message history.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
resolveAccountConfig,
|
|
7
|
-
isAccountConfigured,
|
|
8
|
-
} from "../config.js";
|
|
9
|
-
import { BotCordClient } from "../client.js";
|
|
10
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
11
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
4
|
+
import { withClient } from "./with-client.js";
|
|
5
|
+
import { validationError } from "./tool-result.js";
|
|
12
6
|
|
|
13
7
|
export function createDirectoryTool() {
|
|
14
8
|
return {
|
|
@@ -63,30 +57,17 @@ export function createDirectoryTool() {
|
|
|
63
57
|
required: ["action"],
|
|
64
58
|
},
|
|
65
59
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
66
|
-
|
|
67
|
-
if (!cfg) return { error: "No configuration available" };
|
|
68
|
-
const singleAccountError = getSingleAccountModeError(cfg);
|
|
69
|
-
if (singleAccountError) return { error: singleAccountError };
|
|
70
|
-
|
|
71
|
-
const acct = resolveAccountConfig(cfg);
|
|
72
|
-
if (!isAccountConfigured(acct)) {
|
|
73
|
-
return { error: "BotCord is not configured." };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const client = new BotCordClient(acct);
|
|
77
|
-
attachTokenPersistence(client, acct);
|
|
78
|
-
|
|
79
|
-
try {
|
|
60
|
+
return withClient(async (client) => {
|
|
80
61
|
switch (args.action) {
|
|
81
62
|
case "resolve":
|
|
82
|
-
if (!args.agent_id) return
|
|
63
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
83
64
|
return await client.resolve(args.agent_id);
|
|
84
65
|
|
|
85
66
|
case "discover_rooms":
|
|
86
|
-
return await client.
|
|
67
|
+
return await client.discoverPublicRooms(args.room_name);
|
|
87
68
|
|
|
88
69
|
case "history":
|
|
89
|
-
return await client.getHistory({
|
|
70
|
+
return { history: await client.getHistory({
|
|
90
71
|
peer: args.peer,
|
|
91
72
|
roomId: args.room_id,
|
|
92
73
|
topic: args.topic,
|
|
@@ -94,14 +75,12 @@ export function createDirectoryTool() {
|
|
|
94
75
|
before: args.before,
|
|
95
76
|
after: args.after,
|
|
96
77
|
limit: args.limit || 20,
|
|
97
|
-
});
|
|
78
|
+
}) };
|
|
98
79
|
|
|
99
80
|
default:
|
|
100
|
-
return
|
|
81
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
101
82
|
}
|
|
102
|
-
}
|
|
103
|
-
return { error: `Directory action failed: ${err.message}` };
|
|
104
|
-
}
|
|
83
|
+
});
|
|
105
84
|
},
|
|
106
85
|
};
|
|
107
86
|
}
|
package/src/tools/messaging.ts
CHANGED
|
@@ -3,15 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { basename } from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
resolveAccountConfig,
|
|
10
|
-
isAccountConfigured,
|
|
11
|
-
} from "../config.js";
|
|
12
|
-
import { BotCordClient } from "../client.js";
|
|
13
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
14
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
6
|
+
import { withClient } from "./with-client.js";
|
|
7
|
+
import { validationError, dryRunResult } from "./tool-result.js";
|
|
8
|
+
import type { BotCordClient } from "../client.js";
|
|
15
9
|
import type { MessageAttachment } from "../types.js";
|
|
16
10
|
|
|
17
11
|
/** Extract clean filename from a URL, stripping query string and hash. */
|
|
@@ -127,23 +121,30 @@ export function createMessagingTool() {
|
|
|
127
121
|
items: { type: "string" as const },
|
|
128
122
|
description: "URLs of already-hosted files to attach to the message",
|
|
129
123
|
},
|
|
124
|
+
dry_run: {
|
|
125
|
+
type: "boolean" as const,
|
|
126
|
+
description: "Preview the request without sending. Returns the API call that would be made.",
|
|
127
|
+
},
|
|
130
128
|
},
|
|
131
129
|
required: ["to", "text"],
|
|
132
130
|
},
|
|
133
131
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
132
|
+
if (args.dry_run) {
|
|
133
|
+
const msgType = args.type || "message";
|
|
134
|
+
const body: Record<string, unknown> = { to: args.to, text: args.text, type: msgType };
|
|
135
|
+
if (args.topic) body.topic = args.topic;
|
|
136
|
+
if (args.goal) body.goal = args.goal;
|
|
137
|
+
if (args.reply_to) body.reply_to = args.reply_to;
|
|
138
|
+
if (args.mentions) body.mentions = args.mentions;
|
|
139
|
+
if (args.file_paths) body.file_paths = args.file_paths;
|
|
140
|
+
if (args.file_urls) body.file_urls = args.file_urls;
|
|
141
|
+
const notes: string[] = [];
|
|
142
|
+
if (args.file_paths?.length) notes.push("Local files will be uploaded via POST /hub/upload before sending.");
|
|
143
|
+
notes.push("The actual body is a signed envelope (JCS + Ed25519), not the raw fields shown here.");
|
|
144
|
+
return dryRunResult("POST", "/hub/send", body, { note: notes.join(" ") });
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
|
|
145
|
-
const client = new BotCordClient(acct);
|
|
146
|
-
attachTokenPersistence(client, acct);
|
|
147
|
+
return withClient(async (client) => {
|
|
147
148
|
const msgType = args.type || "message";
|
|
148
149
|
|
|
149
150
|
// Collect attachments from both file_paths (upload first) and file_urls
|
|
@@ -182,9 +183,7 @@ export function createMessagingTool() {
|
|
|
182
183
|
attachments: finalAttachments,
|
|
183
184
|
});
|
|
184
185
|
return { ok: true, hub_msg_id: result.hub_msg_id, to: args.to, type: msgType, attachments: finalAttachments };
|
|
185
|
-
}
|
|
186
|
-
return { error: `Failed to send: ${err.message}` };
|
|
187
|
-
}
|
|
186
|
+
});
|
|
188
187
|
},
|
|
189
188
|
};
|
|
190
189
|
}
|
|
@@ -212,28 +211,14 @@ export function createUploadTool() {
|
|
|
212
211
|
required: ["file_paths"],
|
|
213
212
|
},
|
|
214
213
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
215
|
-
const cfg = getAppConfig();
|
|
216
|
-
if (!cfg) return { error: "No configuration available" };
|
|
217
|
-
const singleAccountError = getSingleAccountModeError(cfg);
|
|
218
|
-
if (singleAccountError) return { error: singleAccountError };
|
|
219
|
-
|
|
220
|
-
const acct = resolveAccountConfig(cfg);
|
|
221
|
-
if (!isAccountConfigured(acct)) {
|
|
222
|
-
return { error: "BotCord is not configured. Set hubUrl, agentId, keyId, and privateKey." };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
214
|
if (!args.file_paths || args.file_paths.length === 0) {
|
|
226
|
-
return
|
|
215
|
+
return validationError("file_paths is required and must not be empty");
|
|
227
216
|
}
|
|
228
217
|
|
|
229
|
-
|
|
230
|
-
const client = new BotCordClient(acct);
|
|
231
|
-
attachTokenPersistence(client, acct);
|
|
218
|
+
return withClient(async (client) => {
|
|
232
219
|
const uploaded = await uploadLocalFiles(client, args.file_paths);
|
|
233
220
|
return { ok: true, files: uploaded };
|
|
234
|
-
}
|
|
235
|
-
return { error: `Upload failed: ${err.message}` };
|
|
236
|
-
}
|
|
221
|
+
});
|
|
237
222
|
},
|
|
238
223
|
};
|
|
239
224
|
}
|
package/src/tools/payment.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* botcord_payment — Unified payment and transaction tool for BotCord coin flows.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
isAccountConfigured,
|
|
8
|
-
} from "../config.js";
|
|
9
|
-
import { BotCordClient } from "../client.js";
|
|
10
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
11
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
12
|
-
import { formatCoinAmount } from "./coin-format.js";
|
|
4
|
+
import { withClient } from "./with-client.js";
|
|
5
|
+
import { validationError, dryRunResult } from "./tool-result.js";
|
|
6
|
+
import { formatCoinAmount, parseCoinToMinor } from "./coin-format.js";
|
|
13
7
|
import { executeTransfer, isPeerContact, formatFollowUpDeliverySummary } from "./payment-transfer.js";
|
|
14
8
|
|
|
15
9
|
function sanitizeBalance(summary: any): any {
|
|
@@ -223,9 +217,9 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
223
217
|
type: "string" as const,
|
|
224
218
|
description: "Recipient agent ID (ag_...) — for transfer",
|
|
225
219
|
},
|
|
226
|
-
|
|
220
|
+
amount: {
|
|
227
221
|
type: "string" as const,
|
|
228
|
-
description: "Amount in
|
|
222
|
+
description: "Amount in COIN (supports up to 2 decimals, e.g. \"10\" or \"9.50\") — for transfer, topup, withdraw",
|
|
229
223
|
},
|
|
230
224
|
memo: {
|
|
231
225
|
type: "string" as const,
|
|
@@ -259,9 +253,9 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
259
253
|
type: "object" as const,
|
|
260
254
|
description: "Withdrawal destination details — for withdraw",
|
|
261
255
|
},
|
|
262
|
-
|
|
256
|
+
fee: {
|
|
263
257
|
type: "string" as const,
|
|
264
|
-
description: "Optional withdrawal fee in
|
|
258
|
+
description: "Optional withdrawal fee in COIN (e.g. \"1\" or \"0.50\") — for withdraw",
|
|
265
259
|
},
|
|
266
260
|
withdrawal_id: {
|
|
267
261
|
type: "string" as const,
|
|
@@ -287,27 +281,18 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
287
281
|
type: "string" as const,
|
|
288
282
|
description: "Filter by transaction type — for ledger",
|
|
289
283
|
},
|
|
284
|
+
dry_run: {
|
|
285
|
+
type: "boolean" as const,
|
|
286
|
+
description: "Preview the request without executing. Returns the API call that would be made.",
|
|
287
|
+
},
|
|
290
288
|
},
|
|
291
289
|
required: ["action"],
|
|
292
290
|
},
|
|
293
291
|
execute: async (_toolCallId: any, args: any) => {
|
|
294
|
-
|
|
295
|
-
if (!cfg) return { error: "No configuration available" };
|
|
296
|
-
const singleAccountError = getSingleAccountModeError(cfg);
|
|
297
|
-
if (singleAccountError) return { error: singleAccountError };
|
|
298
|
-
|
|
299
|
-
const acct = resolveAccountConfig(cfg);
|
|
300
|
-
if (!isAccountConfigured(acct)) {
|
|
301
|
-
return { error: "BotCord is not configured." };
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const client = new BotCordClient(acct);
|
|
305
|
-
attachTokenPersistence(client, acct);
|
|
306
|
-
|
|
307
|
-
try {
|
|
292
|
+
return withClient(async (client) => {
|
|
308
293
|
switch (args.action) {
|
|
309
294
|
case "recipient_verify": {
|
|
310
|
-
if (!args.agent_id) return
|
|
295
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
311
296
|
const agent = await client.resolve(args.agent_id);
|
|
312
297
|
return { result: formatRecipient(agent), data: agent };
|
|
313
298
|
}
|
|
@@ -327,19 +312,22 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
327
312
|
}
|
|
328
313
|
|
|
329
314
|
case "transfer": {
|
|
330
|
-
if (!args.to_agent_id) return
|
|
331
|
-
if (!args.
|
|
315
|
+
if (!args.to_agent_id) return validationError("to_agent_id is required");
|
|
316
|
+
if (!args.amount) return validationError("amount is required");
|
|
317
|
+
const transferMinor = parseCoinToMinor(args.amount);
|
|
318
|
+
if (transferMinor === null) return validationError("amount must be a valid number (e.g. \"10\" or \"9.50\")");
|
|
319
|
+
if (args.dry_run) return dryRunResult("POST", "/wallet/transfers", { to_agent_id: args.to_agent_id, amount_minor: transferMinor, memo: args.memo });
|
|
332
320
|
|
|
333
321
|
const isContact = await isPeerContact(client, args.to_agent_id);
|
|
334
322
|
if (!isContact && args.confirmed !== true) {
|
|
335
323
|
return {
|
|
336
|
-
result: `\u26a0\ufe0f ${args.to_agent_id} is not in your contacts. This is a stranger transfer of ${formatCoinAmount(
|
|
324
|
+
result: `\u26a0\ufe0f ${args.to_agent_id} is not in your contacts. This is a stranger transfer of ${formatCoinAmount(transferMinor)}. To proceed, call this tool again with confirmed: true. The transfer will create a chat room between you and the recipient.`,
|
|
337
325
|
};
|
|
338
326
|
}
|
|
339
327
|
|
|
340
328
|
const transfer = await executeTransfer(client, {
|
|
341
329
|
to_agent_id: args.to_agent_id,
|
|
342
|
-
amount_minor:
|
|
330
|
+
amount_minor: transferMinor,
|
|
343
331
|
memo: args.memo,
|
|
344
332
|
reference_type: args.reference_type,
|
|
345
333
|
reference_id: args.reference_id,
|
|
@@ -353,9 +341,13 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
353
341
|
}
|
|
354
342
|
|
|
355
343
|
case "topup": {
|
|
356
|
-
if (!args.
|
|
344
|
+
if (!args.amount) return validationError("amount is required");
|
|
345
|
+
const topupMinor = parseCoinToMinor(args.amount);
|
|
346
|
+
if (topupMinor === null) return validationError("amount must be a valid number (e.g. \"10\" or \"9.50\")");
|
|
347
|
+
if (args.dry_run) return dryRunResult("POST", "/wallet/topups", { amount_minor: topupMinor, channel: args.channel });
|
|
348
|
+
|
|
357
349
|
const topup = await client.createTopup({
|
|
358
|
-
amount_minor:
|
|
350
|
+
amount_minor: topupMinor,
|
|
359
351
|
channel: args.channel,
|
|
360
352
|
metadata: args.metadata,
|
|
361
353
|
idempotency_key: args.idempotency_key,
|
|
@@ -364,10 +356,19 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
364
356
|
}
|
|
365
357
|
|
|
366
358
|
case "withdraw": {
|
|
367
|
-
if (!args.
|
|
359
|
+
if (!args.amount) return validationError("amount is required");
|
|
360
|
+
const withdrawMinor = parseCoinToMinor(args.amount);
|
|
361
|
+
if (withdrawMinor === null) return validationError("amount must be a valid number (e.g. \"10\" or \"9.50\")");
|
|
362
|
+
let feeMinor: string | undefined;
|
|
363
|
+
if (args.fee) {
|
|
364
|
+
feeMinor = parseCoinToMinor(args.fee) ?? undefined;
|
|
365
|
+
if (feeMinor === undefined) return validationError("fee must be a valid number (e.g. \"1\" or \"0.50\")");
|
|
366
|
+
}
|
|
367
|
+
if (args.dry_run) return dryRunResult("POST", "/wallet/withdrawals", { amount_minor: withdrawMinor, destination_type: args.destination_type });
|
|
368
|
+
|
|
368
369
|
const withdrawal = await client.createWithdrawal({
|
|
369
|
-
amount_minor:
|
|
370
|
-
fee_minor:
|
|
370
|
+
amount_minor: withdrawMinor,
|
|
371
|
+
fee_minor: feeMinor,
|
|
371
372
|
destination_type: args.destination_type,
|
|
372
373
|
destination: args.destination,
|
|
373
374
|
idempotency_key: args.idempotency_key,
|
|
@@ -376,23 +377,23 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
case "cancel_withdrawal": {
|
|
379
|
-
if (!args.withdrawal_id) return
|
|
380
|
+
if (!args.withdrawal_id) return validationError("withdrawal_id is required");
|
|
381
|
+
if (args.dry_run) return dryRunResult("POST", `/wallet/withdrawals/${args.withdrawal_id}/cancel`);
|
|
382
|
+
|
|
380
383
|
const withdrawal = await client.cancelWithdrawal(args.withdrawal_id);
|
|
381
384
|
return { result: formatWithdrawal(withdrawal), data: sanitizeWithdrawal(withdrawal) };
|
|
382
385
|
}
|
|
383
386
|
|
|
384
387
|
case "tx_status": {
|
|
385
|
-
if (!args.tx_id) return
|
|
388
|
+
if (!args.tx_id) return validationError("tx_id is required");
|
|
386
389
|
const tx = await client.getWalletTransaction(args.tx_id);
|
|
387
390
|
return { result: formatTransaction(tx), data: sanitizeTransaction(tx) };
|
|
388
391
|
}
|
|
389
392
|
|
|
390
393
|
default:
|
|
391
|
-
return
|
|
394
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
392
395
|
}
|
|
393
|
-
}
|
|
394
|
-
return { error: `Payment action failed: ${err.message}` };
|
|
395
|
-
}
|
|
396
|
+
});
|
|
396
397
|
},
|
|
397
398
|
};
|
|
398
399
|
}
|
package/src/tools/register.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { registerAgent } from "../commands/register.js";
|
|
5
5
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
6
6
|
import { DEFAULT_HUB } from "../constants.js";
|
|
7
|
+
import { validationError, configError, classifyError } from "./tool-result.js";
|
|
7
8
|
|
|
8
9
|
export function createRegisterTool() {
|
|
9
10
|
return {
|
|
@@ -35,11 +36,11 @@ export function createRegisterTool() {
|
|
|
35
36
|
},
|
|
36
37
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
37
38
|
if (!args.name) {
|
|
38
|
-
return
|
|
39
|
+
return validationError("name is required");
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
const cfg = getAppConfig();
|
|
42
|
-
if (!cfg) return
|
|
43
|
+
if (!cfg) return configError("No configuration available");
|
|
43
44
|
|
|
44
45
|
try {
|
|
45
46
|
const result = await registerAgent({
|
|
@@ -59,8 +60,8 @@ export function createRegisterTool() {
|
|
|
59
60
|
claim_url: result.claimUrl,
|
|
60
61
|
note: "Restart OpenClaw to activate: openclaw gateway restart",
|
|
61
62
|
};
|
|
62
|
-
} catch (err:
|
|
63
|
-
return
|
|
63
|
+
} catch (err: unknown) {
|
|
64
|
+
return classifyError(err);
|
|
64
65
|
}
|
|
65
66
|
},
|
|
66
67
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
5
5
|
import { resetCredential } from "../reset-credential.js";
|
|
6
|
+
import { validationError, configError, classifyError } from "./tool-result.js";
|
|
6
7
|
|
|
7
8
|
export function createResetCredentialTool() {
|
|
8
9
|
return {
|
|
@@ -30,9 +31,9 @@ export function createResetCredentialTool() {
|
|
|
30
31
|
},
|
|
31
32
|
execute: async (_toolCallId: any, args: any) => {
|
|
32
33
|
const cfg = getAppConfig();
|
|
33
|
-
if (!cfg) return
|
|
34
|
-
if (!args.agent_id) return
|
|
35
|
-
if (!args.reset_code) return
|
|
34
|
+
if (!cfg) return configError("No configuration available");
|
|
35
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
36
|
+
if (!args.reset_code) return validationError("reset_code is required");
|
|
36
37
|
|
|
37
38
|
try {
|
|
38
39
|
const result = await resetCredential({
|
|
@@ -50,8 +51,8 @@ export function createResetCredentialTool() {
|
|
|
50
51
|
credentials_file: result.credentialsFile,
|
|
51
52
|
note: "Restart OpenClaw to activate: openclaw gateway restart",
|
|
52
53
|
};
|
|
53
|
-
} catch (err:
|
|
54
|
-
return
|
|
54
|
+
} catch (err: unknown) {
|
|
55
|
+
return classifyError(err);
|
|
55
56
|
}
|
|
56
57
|
},
|
|
57
58
|
};
|
|
@@ -2,14 +2,25 @@
|
|
|
2
2
|
* botcord_room_context — Inspect room context, recent messages, and search
|
|
3
3
|
* message history within one room or across all joined rooms.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { withClient } from "./with-client.js";
|
|
6
|
+
import { validationError } from "./tool-result.js";
|
|
7
|
+
|
|
8
|
+
/** Normalize query input: accept a string or string[] from the LLM. */
|
|
9
|
+
function _normalizeQuery(raw: unknown): string | string[] | null {
|
|
10
|
+
if (Array.isArray(raw)) {
|
|
11
|
+
const parts = raw
|
|
12
|
+
.filter((v) => typeof v === "string")
|
|
13
|
+
.map((s: string) => s.trim())
|
|
14
|
+
.filter(Boolean);
|
|
15
|
+
if (parts.length === 0) return null;
|
|
16
|
+
return parts.length === 1 ? parts[0] : parts;
|
|
17
|
+
}
|
|
18
|
+
if (typeof raw === "string") {
|
|
19
|
+
const trimmed = raw.trim();
|
|
20
|
+
return trimmed || null;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
13
24
|
|
|
14
25
|
export function createRoomContextTool() {
|
|
15
26
|
return {
|
|
@@ -44,8 +55,9 @@ export function createRoomContextTool() {
|
|
|
44
55
|
"Room ID (rm_...) — required for room_summary, room_messages, room_search; optional filter for global_search",
|
|
45
56
|
},
|
|
46
57
|
query: {
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
description:
|
|
59
|
+
"Search query — required for room_search and global_search. " +
|
|
60
|
+
"Pass a single string or an array of strings for OR search (e.g., ['deploy', 'release']).",
|
|
49
61
|
},
|
|
50
62
|
topic_id: {
|
|
51
63
|
type: "string" as const,
|
|
@@ -71,28 +83,15 @@ export function createRoomContextTool() {
|
|
|
71
83
|
required: ["action"],
|
|
72
84
|
},
|
|
73
85
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
74
|
-
|
|
75
|
-
if (!cfg) return { error: "No configuration available" };
|
|
76
|
-
const singleAccountError = getSingleAccountModeError(cfg);
|
|
77
|
-
if (singleAccountError) return { error: singleAccountError };
|
|
78
|
-
|
|
79
|
-
const acct = resolveAccountConfig(cfg);
|
|
80
|
-
if (!isAccountConfigured(acct)) {
|
|
81
|
-
return { error: "BotCord is not configured." };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const client = new BotCordClient(acct);
|
|
85
|
-
attachTokenPersistence(client, acct);
|
|
86
|
-
|
|
87
|
-
try {
|
|
86
|
+
return withClient(async (client) => {
|
|
88
87
|
switch (args.action) {
|
|
89
88
|
case "room_summary": {
|
|
90
|
-
if (!args.room_id) return
|
|
89
|
+
if (!args.room_id) return validationError("room_id is required for room_summary");
|
|
91
90
|
return await client.roomSummary(args.room_id, args.limit);
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
case "room_messages": {
|
|
95
|
-
if (!args.room_id) return
|
|
94
|
+
if (!args.room_id) return validationError("room_id is required for room_messages");
|
|
96
95
|
return await client.roomMessages(args.room_id, {
|
|
97
96
|
limit: args.limit,
|
|
98
97
|
before: args.before,
|
|
@@ -103,9 +102,10 @@ export function createRoomContextTool() {
|
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
case "room_search": {
|
|
106
|
-
if (!args.room_id) return
|
|
107
|
-
|
|
108
|
-
return
|
|
105
|
+
if (!args.room_id) return validationError("room_id is required for room_search");
|
|
106
|
+
const rsQuery = _normalizeQuery(args.query);
|
|
107
|
+
if (!rsQuery) return validationError("query is required for room_search");
|
|
108
|
+
return await client.roomSearch(args.room_id, rsQuery, {
|
|
109
109
|
limit: args.limit,
|
|
110
110
|
before: args.before,
|
|
111
111
|
topicId: args.topic_id,
|
|
@@ -118,8 +118,9 @@ export function createRoomContextTool() {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
case "global_search": {
|
|
121
|
-
|
|
122
|
-
return
|
|
121
|
+
const gsQuery = _normalizeQuery(args.query);
|
|
122
|
+
if (!gsQuery) return validationError("query is required for global_search");
|
|
123
|
+
return await client.globalSearch(gsQuery, {
|
|
123
124
|
limit: args.limit,
|
|
124
125
|
roomId: args.room_id,
|
|
125
126
|
topicId: args.topic_id,
|
|
@@ -129,11 +130,9 @@ export function createRoomContextTool() {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
default:
|
|
132
|
-
return
|
|
133
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
133
134
|
}
|
|
134
|
-
}
|
|
135
|
-
return { error: `Room context action failed: ${err.message}` };
|
|
136
|
-
}
|
|
135
|
+
});
|
|
137
136
|
},
|
|
138
137
|
};
|
|
139
138
|
}
|