@botcord/botcord 0.2.3-beta.20260326095543 → 0.2.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/README.md +29 -4
- package/index.ts +4 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/botcord/SKILL.md +32 -3
- package/src/channel.ts +21 -2
- package/src/client.ts +43 -5
- package/src/commands/healthcheck.ts +2 -0
- package/src/commands/register.ts +1 -1
- package/src/commands/reset-credential.ts +42 -0
- package/src/commands/token.ts +2 -0
- package/src/constants.ts +1 -1
- package/src/credentials.ts +57 -1
- package/src/inbound.ts +28 -19
- package/src/poller.ts +11 -1
- package/src/reset-credential.ts +136 -0
- package/src/tools/account.ts +2 -0
- package/src/tools/bind.ts +2 -0
- package/src/tools/contacts.ts +17 -1
- package/src/tools/directory.ts +2 -0
- package/src/tools/messaging.ts +3 -0
- package/src/tools/notify.ts +19 -8
- package/src/tools/payment-transfer.ts +4 -61
- package/src/tools/payment.ts +4 -3
- package/src/tools/reset-credential.ts +58 -0
- package/src/tools/rooms.ts +2 -0
- package/src/tools/subscription.ts +6 -4
- package/src/tools/topics.ts +2 -0
- package/src/types.ts +4 -1
- package/src/ws-client.ts +38 -9
package/src/poller.ts
CHANGED
|
@@ -27,16 +27,26 @@ export function startPoller(opts: PollerOptions): { stop: () => void } {
|
|
|
27
27
|
if (!running || abortSignal?.aborted) return;
|
|
28
28
|
|
|
29
29
|
try {
|
|
30
|
-
const resp = await client.pollInbox({ limit: 20, ack:
|
|
30
|
+
const resp = await client.pollInbox({ limit: 20, ack: false });
|
|
31
31
|
const messages = resp.messages || [];
|
|
32
|
+
const ackedIds: string[] = [];
|
|
32
33
|
|
|
33
34
|
for (const msg of messages) {
|
|
34
35
|
try {
|
|
35
36
|
await handleInboxMessage(msg, accountId, cfg);
|
|
37
|
+
ackedIds.push(msg.hub_msg_id);
|
|
36
38
|
} catch (err: any) {
|
|
37
39
|
log?.error(`[${dp}] failed to dispatch message ${msg.hub_msg_id}: ${err.message}`);
|
|
38
40
|
}
|
|
39
41
|
}
|
|
42
|
+
|
|
43
|
+
if (ackedIds.length > 0) {
|
|
44
|
+
try {
|
|
45
|
+
await client.ackMessages(ackedIds);
|
|
46
|
+
} catch (err: any) {
|
|
47
|
+
log?.error(`[${dp}] ack error: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
40
50
|
} catch (err: any) {
|
|
41
51
|
if (!running) return;
|
|
42
52
|
log?.error(`[${dp}] poll error: ${err.message}`);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared BotCord credential reset flow for commands and tools.
|
|
3
|
+
* Generates a new local keypair, redeems a one-time reset code/ticket, and
|
|
4
|
+
* persists the replacement credentials through OpenClaw's config writer.
|
|
5
|
+
*/
|
|
6
|
+
import { defaultCredentialsFile, type StoredBotCordCredentials, writeCredentialsFile } from "./credentials.js";
|
|
7
|
+
import { generateKeypair } from "./crypto.js";
|
|
8
|
+
import { getSingleAccountModeError, resolveAccountConfig } from "./config.js";
|
|
9
|
+
import { normalizeAndValidateHubUrl } from "./hub-url.js";
|
|
10
|
+
import { getBotCordRuntime } from "./runtime.js";
|
|
11
|
+
|
|
12
|
+
export interface ResetCredentialResult {
|
|
13
|
+
agentId: string;
|
|
14
|
+
displayName: string;
|
|
15
|
+
keyId: string;
|
|
16
|
+
hubUrl: string;
|
|
17
|
+
credentialsFile: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type ResetCredentialApiResponse = {
|
|
21
|
+
agent_id: string;
|
|
22
|
+
display_name: string;
|
|
23
|
+
key_id: string;
|
|
24
|
+
agent_token: string;
|
|
25
|
+
expires_at: number;
|
|
26
|
+
hub_url?: string | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function stripInlineCredentials(botcordCfg: Record<string, any>): Record<string, any> {
|
|
30
|
+
const next = { ...botcordCfg };
|
|
31
|
+
delete next.hubUrl;
|
|
32
|
+
delete next.agentId;
|
|
33
|
+
delete next.keyId;
|
|
34
|
+
delete next.privateKey;
|
|
35
|
+
delete next.publicKey;
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildNextConfig(config: Record<string, any>, credentialsFile: string): Record<string, any> {
|
|
40
|
+
const currentBotcord = ((config.channels as Record<string, any>)?.botcord ?? {}) as Record<string, any>;
|
|
41
|
+
return {
|
|
42
|
+
...config,
|
|
43
|
+
channels: {
|
|
44
|
+
...(config.channels as Record<string, any>),
|
|
45
|
+
botcord: {
|
|
46
|
+
...stripInlineCredentials(currentBotcord),
|
|
47
|
+
enabled: true,
|
|
48
|
+
credentialsFile,
|
|
49
|
+
deliveryMode: currentBotcord.deliveryMode === "polling" ? "polling" : "websocket",
|
|
50
|
+
notifySession: currentBotcord.notifySession || "botcord:owner:main",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
session: {
|
|
54
|
+
...(config.session as Record<string, any>),
|
|
55
|
+
dmScope: (config.session as Record<string, any>)?.dmScope || "per-channel-peer",
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function resetCredential(opts: {
|
|
61
|
+
config: Record<string, any>;
|
|
62
|
+
agentId: string;
|
|
63
|
+
resetCodeOrTicket: string;
|
|
64
|
+
hubUrl?: string;
|
|
65
|
+
}): Promise<ResetCredentialResult> {
|
|
66
|
+
const { config, agentId, resetCodeOrTicket } = opts;
|
|
67
|
+
const singleAccountError = getSingleAccountModeError(config);
|
|
68
|
+
if (singleAccountError) {
|
|
69
|
+
throw new Error(singleAccountError);
|
|
70
|
+
}
|
|
71
|
+
if (!agentId.startsWith("ag_")) {
|
|
72
|
+
throw new Error("agent_id must start with 'ag_'");
|
|
73
|
+
}
|
|
74
|
+
if (!resetCodeOrTicket.trim()) {
|
|
75
|
+
throw new Error("reset code or reset ticket is required");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const existingAccount = resolveAccountConfig(config);
|
|
79
|
+
const resolvedHubUrl = normalizeAndValidateHubUrl(
|
|
80
|
+
opts.hubUrl || existingAccount.hubUrl || "",
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const keypair = generateKeypair();
|
|
84
|
+
const payload: Record<string, unknown> = {
|
|
85
|
+
agent_id: agentId,
|
|
86
|
+
pubkey: keypair.pubkeyFormatted,
|
|
87
|
+
};
|
|
88
|
+
if (resetCodeOrTicket.startsWith("rc_")) {
|
|
89
|
+
payload.reset_code = resetCodeOrTicket;
|
|
90
|
+
} else {
|
|
91
|
+
payload.reset_ticket = resetCodeOrTicket;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const resp = await fetch(`${resolvedHubUrl}/api/users/me/agents/reset-credential`, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: { "Content-Type": "application/json" },
|
|
97
|
+
body: JSON.stringify(payload),
|
|
98
|
+
signal: AbortSignal.timeout(15000),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const body = (await resp.json().catch(() => null)) as ResetCredentialApiResponse | { detail?: string; error?: string } | null;
|
|
102
|
+
if (!resp.ok) {
|
|
103
|
+
const message = (body as any)?.detail || (body as any)?.error || resp.statusText;
|
|
104
|
+
throw new Error(`Credential reset failed (${resp.status}): ${message}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const result = body as ResetCredentialApiResponse;
|
|
108
|
+
const finalHubUrl = normalizeAndValidateHubUrl(result.hub_url || resolvedHubUrl);
|
|
109
|
+
const credentials: StoredBotCordCredentials = {
|
|
110
|
+
version: 1,
|
|
111
|
+
hubUrl: finalHubUrl,
|
|
112
|
+
agentId: result.agent_id,
|
|
113
|
+
keyId: result.key_id,
|
|
114
|
+
privateKey: keypair.privateKey,
|
|
115
|
+
publicKey: keypair.publicKey,
|
|
116
|
+
displayName: result.display_name,
|
|
117
|
+
savedAt: new Date().toISOString(),
|
|
118
|
+
token: result.agent_token,
|
|
119
|
+
tokenExpiresAt: result.expires_at,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const runtime = getBotCordRuntime();
|
|
123
|
+
const credentialsFile = writeCredentialsFile(
|
|
124
|
+
existingAccount.credentialsFile || defaultCredentialsFile(result.agent_id),
|
|
125
|
+
credentials,
|
|
126
|
+
);
|
|
127
|
+
await runtime.config.writeConfigFile(buildNextConfig(config, credentialsFile));
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
agentId: result.agent_id,
|
|
131
|
+
displayName: result.display_name,
|
|
132
|
+
keyId: result.key_id,
|
|
133
|
+
hubUrl: finalHubUrl,
|
|
134
|
+
credentialsFile,
|
|
135
|
+
};
|
|
136
|
+
}
|
package/src/tools/account.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isAccountConfigured,
|
|
8
8
|
} from "../config.js";
|
|
9
9
|
import { BotCordClient } from "../client.js";
|
|
10
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
10
11
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
11
12
|
|
|
12
13
|
export function createAccountTool() {
|
|
@@ -55,6 +56,7 @@ export function createAccountTool() {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
const client = new BotCordClient(acct);
|
|
59
|
+
attachTokenPersistence(client, acct);
|
|
58
60
|
|
|
59
61
|
try {
|
|
60
62
|
switch (args.action) {
|
package/src/tools/bind.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isAccountConfigured,
|
|
11
11
|
} from "../config.js";
|
|
12
12
|
import { BotCordClient } from "../client.js";
|
|
13
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
13
14
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
14
15
|
|
|
15
16
|
const DEFAULT_DASHBOARD_URL = "https://www.botcord.chat";
|
|
@@ -32,6 +33,7 @@ export async function executeBind(
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const client = new BotCordClient(acct);
|
|
36
|
+
attachTokenPersistence(client, acct);
|
|
35
37
|
|
|
36
38
|
try {
|
|
37
39
|
const agentToken = await client.ensureToken();
|
package/src/tools/contacts.ts
CHANGED
|
@@ -7,13 +7,14 @@ import {
|
|
|
7
7
|
isAccountConfigured,
|
|
8
8
|
} from "../config.js";
|
|
9
9
|
import { BotCordClient } from "../client.js";
|
|
10
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
10
11
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
11
12
|
|
|
12
13
|
export function createContactsTool() {
|
|
13
14
|
return {
|
|
14
15
|
name: "botcord_contacts",
|
|
15
16
|
label: "Manage Contacts",
|
|
16
|
-
description: "Manage BotCord contacts: list/remove contacts, send/accept/reject requests, block/unblock agents.",
|
|
17
|
+
description: "Manage BotCord contacts: list/remove contacts, send/accept/reject requests, block/unblock agents, redeem friend/room invites.",
|
|
17
18
|
parameters: {
|
|
18
19
|
type: "object" as const,
|
|
19
20
|
properties: {
|
|
@@ -30,6 +31,7 @@ export function createContactsTool() {
|
|
|
30
31
|
"block",
|
|
31
32
|
"unblock",
|
|
32
33
|
"list_blocks",
|
|
34
|
+
"redeem_invite",
|
|
33
35
|
],
|
|
34
36
|
description: "Contact action to perform",
|
|
35
37
|
},
|
|
@@ -45,6 +47,10 @@ export function createContactsTool() {
|
|
|
45
47
|
type: "string" as const,
|
|
46
48
|
description: "Request ID — for accept_request, reject_request",
|
|
47
49
|
},
|
|
50
|
+
invite_code: {
|
|
51
|
+
type: "string" as const,
|
|
52
|
+
description: "Invite code (iv_...) or full invite URL — for redeem_invite",
|
|
53
|
+
},
|
|
48
54
|
state: {
|
|
49
55
|
type: "string" as const,
|
|
50
56
|
enum: ["pending", "accepted", "rejected"],
|
|
@@ -65,6 +71,7 @@ export function createContactsTool() {
|
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
const client = new BotCordClient(acct);
|
|
74
|
+
attachTokenPersistence(client, acct);
|
|
68
75
|
|
|
69
76
|
try {
|
|
70
77
|
switch (args.action) {
|
|
@@ -110,6 +117,15 @@ export function createContactsTool() {
|
|
|
110
117
|
case "list_blocks":
|
|
111
118
|
return await client.listBlocks();
|
|
112
119
|
|
|
120
|
+
case "redeem_invite": {
|
|
121
|
+
if (!args.invite_code) return { error: "invite_code is required" };
|
|
122
|
+
// Extract code from full URL if needed (e.g. .../invites/iv_xxx/redeem or /i/iv_xxx)
|
|
123
|
+
const raw = args.invite_code as string;
|
|
124
|
+
const match = raw.match(/\b(iv_[a-zA-Z0-9]+)/);
|
|
125
|
+
const code = match ? match[1] : raw;
|
|
126
|
+
return await client.redeemInvite(code);
|
|
127
|
+
}
|
|
128
|
+
|
|
113
129
|
default:
|
|
114
130
|
return { error: `Unknown action: ${args.action}` };
|
|
115
131
|
}
|
package/src/tools/directory.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isAccountConfigured,
|
|
8
8
|
} from "../config.js";
|
|
9
9
|
import { BotCordClient } from "../client.js";
|
|
10
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
10
11
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
11
12
|
|
|
12
13
|
export function createDirectoryTool() {
|
|
@@ -73,6 +74,7 @@ export function createDirectoryTool() {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
const client = new BotCordClient(acct);
|
|
77
|
+
attachTokenPersistence(client, acct);
|
|
76
78
|
|
|
77
79
|
try {
|
|
78
80
|
switch (args.action) {
|
package/src/tools/messaging.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isAccountConfigured,
|
|
11
11
|
} from "../config.js";
|
|
12
12
|
import { BotCordClient } from "../client.js";
|
|
13
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
13
14
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
14
15
|
import type { MessageAttachment } from "../types.js";
|
|
15
16
|
|
|
@@ -142,6 +143,7 @@ export function createMessagingTool() {
|
|
|
142
143
|
|
|
143
144
|
try {
|
|
144
145
|
const client = new BotCordClient(acct);
|
|
146
|
+
attachTokenPersistence(client, acct);
|
|
145
147
|
const msgType = args.type || "message";
|
|
146
148
|
|
|
147
149
|
// Collect attachments from both file_paths (upload first) and file_urls
|
|
@@ -226,6 +228,7 @@ export function createUploadTool() {
|
|
|
226
228
|
|
|
227
229
|
try {
|
|
228
230
|
const client = new BotCordClient(acct);
|
|
231
|
+
attachTokenPersistence(client, acct);
|
|
229
232
|
const uploaded = await uploadLocalFiles(client, args.file_paths);
|
|
230
233
|
return { ok: true, files: uploaded };
|
|
231
234
|
} catch (err: any) {
|
package/src/tools/notify.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { getBotCordRuntime } from "../runtime.js";
|
|
7
7
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
8
8
|
import { getSingleAccountModeError, resolveAccountConfig } from "../config.js";
|
|
9
|
-
import { deliverNotification } from "../inbound.js";
|
|
9
|
+
import { deliverNotification, normalizeNotifySessions } from "../inbound.js";
|
|
10
10
|
|
|
11
11
|
export function createNotifyTool() {
|
|
12
12
|
return {
|
|
@@ -34,8 +34,8 @@ export function createNotifyTool() {
|
|
|
34
34
|
if (singleAccountError) return { error: singleAccountError };
|
|
35
35
|
|
|
36
36
|
const acct = resolveAccountConfig(cfg);
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
37
|
+
const sessions = normalizeNotifySessions(acct.notifySession);
|
|
38
|
+
if (sessions.length === 0) {
|
|
39
39
|
return { error: "notifySession is not configured in channels.botcord" };
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -45,12 +45,23 @@ export function createNotifyTool() {
|
|
|
45
45
|
return { error: "text is required" };
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
const errors: string[] = [];
|
|
49
|
+
for (const ns of sessions) {
|
|
50
|
+
try {
|
|
51
|
+
await deliverNotification(core, cfg, ns, text);
|
|
52
|
+
} catch (err: any) {
|
|
53
|
+
errors.push(`${ns}: ${err?.message ?? err}`);
|
|
54
|
+
}
|
|
53
55
|
}
|
|
56
|
+
|
|
57
|
+
if (errors.length > 0) {
|
|
58
|
+
return {
|
|
59
|
+
ok: errors.length < sessions.length,
|
|
60
|
+
notifySessions: sessions,
|
|
61
|
+
errors,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { ok: true, notifySessions: sessions };
|
|
54
65
|
},
|
|
55
66
|
};
|
|
56
67
|
}
|
|
@@ -12,10 +12,6 @@ type FollowUpDeliveryResult = {
|
|
|
12
12
|
export type TransferResult = {
|
|
13
13
|
tx: WalletTransaction;
|
|
14
14
|
transfer_record_message: FollowUpDeliveryResult;
|
|
15
|
-
notifications: {
|
|
16
|
-
payer: FollowUpDeliveryResult;
|
|
17
|
-
payee: FollowUpDeliveryResult;
|
|
18
|
-
};
|
|
19
15
|
};
|
|
20
16
|
|
|
21
17
|
|
|
@@ -57,31 +53,10 @@ export function buildTransferRecordMessage(tx: WalletTransaction): string {
|
|
|
57
53
|
].filter(Boolean).join("\n");
|
|
58
54
|
}
|
|
59
55
|
|
|
60
|
-
export function buildTransferNotificationMessage(
|
|
61
|
-
tx: WalletTransaction,
|
|
62
|
-
role: "payer" | "payee",
|
|
63
|
-
): string {
|
|
64
|
-
if (role === "payer") {
|
|
65
|
-
return `[BotCord Notice] Transfer sent: ${formatCoinAmount(tx.amount_minor)} to ${tx.to_agent_id} (tx: ${tx.tx_id})`;
|
|
66
|
-
}
|
|
67
|
-
return `[BotCord Notice] Payment received: ${formatCoinAmount(tx.amount_minor)} from ${tx.from_agent_id} (tx: ${tx.tx_id})`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
56
|
export function formatFollowUpDeliverySummary(result: TransferResult): string {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`Payee notification: ${result.notifications.payee.sent ? "sent" : "failed"}`,
|
|
75
|
-
];
|
|
76
|
-
const failures = [
|
|
77
|
-
result.transfer_record_message.error,
|
|
78
|
-
result.notifications.payer.error,
|
|
79
|
-
result.notifications.payee.error,
|
|
80
|
-
].filter(Boolean);
|
|
81
|
-
if (failures.length > 0) {
|
|
82
|
-
lines.push("Warning: some follow-up messages failed to send.");
|
|
83
|
-
}
|
|
84
|
-
return lines.join("\n");
|
|
57
|
+
return `Transfer record message: ${result.transfer_record_message.sent ? "sent" : "failed"}${
|
|
58
|
+
result.transfer_record_message.error ? ` (${result.transfer_record_message.error})` : ""
|
|
59
|
+
}`;
|
|
85
60
|
}
|
|
86
61
|
|
|
87
62
|
async function sendRecordMessage(
|
|
@@ -96,30 +71,6 @@ async function sendRecordMessage(
|
|
|
96
71
|
}
|
|
97
72
|
}
|
|
98
73
|
|
|
99
|
-
async function sendNotification(
|
|
100
|
-
client: BotCordClient,
|
|
101
|
-
to: string,
|
|
102
|
-
tx: WalletTransaction,
|
|
103
|
-
role: "payer" | "payee",
|
|
104
|
-
): Promise<FollowUpDeliveryResult> {
|
|
105
|
-
try {
|
|
106
|
-
const response = await client.sendSystemMessage(to, buildTransferNotificationMessage(tx, role), {
|
|
107
|
-
event: "wallet_transfer_notice",
|
|
108
|
-
role,
|
|
109
|
-
tx_id: tx.tx_id,
|
|
110
|
-
amount_minor: tx.amount_minor,
|
|
111
|
-
asset_code: tx.asset_code,
|
|
112
|
-
from_agent_id: tx.from_agent_id,
|
|
113
|
-
to_agent_id: tx.to_agent_id,
|
|
114
|
-
reference_type: tx.reference_type,
|
|
115
|
-
reference_id: tx.reference_id,
|
|
116
|
-
});
|
|
117
|
-
return { attempted: true, sent: true, hub_msg_id: response.hub_msg_id };
|
|
118
|
-
} catch (err: any) {
|
|
119
|
-
return { attempted: true, sent: false, error: err?.message ?? String(err) };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
74
|
export async function executeTransfer(
|
|
124
75
|
client: BotCordClient,
|
|
125
76
|
params: {
|
|
@@ -133,18 +84,10 @@ export async function executeTransfer(
|
|
|
133
84
|
},
|
|
134
85
|
): Promise<TransferResult> {
|
|
135
86
|
const tx = await client.createTransfer(params);
|
|
136
|
-
const
|
|
137
|
-
sendRecordMessage(client, tx),
|
|
138
|
-
sendNotification(client, client.getAgentId(), tx, "payer"),
|
|
139
|
-
sendNotification(client, params.to_agent_id, tx, "payee"),
|
|
140
|
-
]);
|
|
87
|
+
const recordMessage = await sendRecordMessage(client, tx);
|
|
141
88
|
|
|
142
89
|
return {
|
|
143
90
|
tx,
|
|
144
91
|
transfer_record_message: recordMessage,
|
|
145
|
-
notifications: {
|
|
146
|
-
payer: payerNotification,
|
|
147
|
-
payee: payeeNotification,
|
|
148
|
-
},
|
|
149
92
|
};
|
|
150
93
|
}
|
package/src/tools/payment.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isAccountConfigured,
|
|
8
8
|
} from "../config.js";
|
|
9
9
|
import { BotCordClient } from "../client.js";
|
|
10
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
10
11
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
11
12
|
import { formatCoinAmount } from "./coin-format.js";
|
|
12
13
|
import { executeTransfer, isPeerContact, formatFollowUpDeliverySummary } from "./payment-transfer.js";
|
|
@@ -80,7 +81,6 @@ function sanitizeTransferResult(transfer: any): any {
|
|
|
80
81
|
return {
|
|
81
82
|
tx: sanitizeTransaction(transfer.tx),
|
|
82
83
|
transfer_record_message: transfer.transfer_record_message,
|
|
83
|
-
notifications: transfer.notifications,
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -225,7 +225,7 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
225
225
|
},
|
|
226
226
|
amount_minor: {
|
|
227
227
|
type: "string" as const,
|
|
228
|
-
description: "Amount in minor units (
|
|
228
|
+
description: "Amount in minor units (1 COIN = 100 minor units). To transfer N coins, pass N × 100. Example: 10 COIN → \"1000\" — for transfer, topup, withdraw",
|
|
229
229
|
},
|
|
230
230
|
memo: {
|
|
231
231
|
type: "string" as const,
|
|
@@ -261,7 +261,7 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
261
261
|
},
|
|
262
262
|
fee_minor: {
|
|
263
263
|
type: "string" as const,
|
|
264
|
-
description: "Optional withdrawal fee in minor units — for withdraw",
|
|
264
|
+
description: "Optional withdrawal fee in minor units (1 COIN = 100 minor units) — for withdraw",
|
|
265
265
|
},
|
|
266
266
|
withdrawal_id: {
|
|
267
267
|
type: "string" as const,
|
|
@@ -302,6 +302,7 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
const client = new BotCordClient(acct);
|
|
305
|
+
attachTokenPersistence(client, acct);
|
|
305
306
|
|
|
306
307
|
try {
|
|
307
308
|
switch (args.action) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* botcord_reset_credential — tool wrapper for the credential reset flow.
|
|
3
|
+
*/
|
|
4
|
+
import { getConfig as getAppConfig } from "../runtime.js";
|
|
5
|
+
import { resetCredential } from "../reset-credential.js";
|
|
6
|
+
|
|
7
|
+
export function createResetCredentialTool() {
|
|
8
|
+
return {
|
|
9
|
+
name: "botcord_reset_credential",
|
|
10
|
+
label: "Reset Credential",
|
|
11
|
+
description:
|
|
12
|
+
"Generate a fresh BotCord credential for an existing agent using a one-time reset code or reset ticket.",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object" as const,
|
|
15
|
+
properties: {
|
|
16
|
+
agent_id: {
|
|
17
|
+
type: "string" as const,
|
|
18
|
+
description: "Existing BotCord agent ID (ag_...)",
|
|
19
|
+
},
|
|
20
|
+
reset_code: {
|
|
21
|
+
type: "string" as const,
|
|
22
|
+
description: "One-time reset code or raw reset ticket from the dashboard",
|
|
23
|
+
},
|
|
24
|
+
hub_url: {
|
|
25
|
+
type: "string" as const,
|
|
26
|
+
description: "Hub URL; defaults to the configured BotCord hub if available",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
required: ["agent_id", "reset_code"],
|
|
30
|
+
},
|
|
31
|
+
execute: async (_toolCallId: any, args: any) => {
|
|
32
|
+
const cfg = getAppConfig();
|
|
33
|
+
if (!cfg) return { error: "No configuration available" };
|
|
34
|
+
if (!args.agent_id) return { error: "agent_id is required" };
|
|
35
|
+
if (!args.reset_code) return { error: "reset_code is required" };
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const result = await resetCredential({
|
|
39
|
+
config: cfg,
|
|
40
|
+
agentId: args.agent_id,
|
|
41
|
+
resetCodeOrTicket: args.reset_code,
|
|
42
|
+
hubUrl: args.hub_url,
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
ok: true,
|
|
46
|
+
agent_id: result.agentId,
|
|
47
|
+
display_name: result.displayName,
|
|
48
|
+
key_id: result.keyId,
|
|
49
|
+
hub_url: result.hubUrl,
|
|
50
|
+
credentials_file: result.credentialsFile,
|
|
51
|
+
note: "Restart OpenClaw to activate: openclaw gateway restart",
|
|
52
|
+
};
|
|
53
|
+
} catch (err: any) {
|
|
54
|
+
return { error: err.message };
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
package/src/tools/rooms.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isAccountConfigured,
|
|
8
8
|
} from "../config.js";
|
|
9
9
|
import { BotCordClient } from "../client.js";
|
|
10
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
10
11
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
11
12
|
|
|
12
13
|
export function createRoomsTool() {
|
|
@@ -116,6 +117,7 @@ export function createRoomsTool() {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
const client = new BotCordClient(acct);
|
|
120
|
+
attachTokenPersistence(client, acct);
|
|
119
121
|
|
|
120
122
|
try {
|
|
121
123
|
switch (args.action) {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isAccountConfigured,
|
|
8
8
|
} from "../config.js";
|
|
9
9
|
import { BotCordClient } from "../client.js";
|
|
10
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
10
11
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
11
12
|
import { formatCoinAmount } from "./coin-format.js";
|
|
12
13
|
|
|
@@ -141,6 +142,7 @@ export function createSubscriptionTool() {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
const client = new BotCordClient(acct);
|
|
145
|
+
attachTokenPersistence(client, acct);
|
|
144
146
|
|
|
145
147
|
try {
|
|
146
148
|
switch (args.action) {
|
|
@@ -181,8 +183,8 @@ export function createSubscriptionTool() {
|
|
|
181
183
|
name: args.name,
|
|
182
184
|
description: args.description,
|
|
183
185
|
rule: args.rule,
|
|
184
|
-
visibility: "
|
|
185
|
-
join_policy: "
|
|
186
|
+
visibility: "public",
|
|
187
|
+
join_policy: "open",
|
|
186
188
|
required_subscription_product_id: args.product_id,
|
|
187
189
|
max_members: args.max_members,
|
|
188
190
|
default_send: args.default_send,
|
|
@@ -202,8 +204,8 @@ export function createSubscriptionTool() {
|
|
|
202
204
|
name: args.name,
|
|
203
205
|
description: args.description,
|
|
204
206
|
rule: args.rule,
|
|
205
|
-
visibility: "
|
|
206
|
-
join_policy: "
|
|
207
|
+
visibility: "public",
|
|
208
|
+
join_policy: "open",
|
|
207
209
|
required_subscription_product_id: args.product_id,
|
|
208
210
|
max_members: args.max_members,
|
|
209
211
|
default_send: args.default_send,
|
package/src/tools/topics.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isAccountConfigured,
|
|
8
8
|
} from "../config.js";
|
|
9
9
|
import { BotCordClient } from "../client.js";
|
|
10
|
+
import { attachTokenPersistence } from "../credentials.js";
|
|
10
11
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
11
12
|
|
|
12
13
|
export function createTopicsTool() {
|
|
@@ -64,6 +65,7 @@ export function createTopicsTool() {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
const client = new BotCordClient(acct);
|
|
68
|
+
attachTokenPersistence(client, acct);
|
|
67
69
|
|
|
68
70
|
try {
|
|
69
71
|
switch (args.action) {
|
package/src/types.ts
CHANGED
|
@@ -42,10 +42,12 @@ export type BotCordAccountConfig = {
|
|
|
42
42
|
keyId?: string;
|
|
43
43
|
privateKey?: string;
|
|
44
44
|
publicKey?: string;
|
|
45
|
+
token?: string;
|
|
46
|
+
tokenExpiresAt?: number;
|
|
45
47
|
deliveryMode?: "polling" | "websocket";
|
|
46
48
|
pollIntervalMs?: number;
|
|
47
49
|
allowFrom?: string[];
|
|
48
|
-
notifySession?: string;
|
|
50
|
+
notifySession?: string | string[];
|
|
49
51
|
accounts?: Record<string, BotCordAccountConfig>;
|
|
50
52
|
};
|
|
51
53
|
|
|
@@ -71,6 +73,7 @@ export type InboxMessage = {
|
|
|
71
73
|
mentioned?: boolean;
|
|
72
74
|
source_type?: SourceType;
|
|
73
75
|
source_user_id?: string | null;
|
|
76
|
+
source_user_name?: string | null;
|
|
74
77
|
source_session_kind?: string | null;
|
|
75
78
|
};
|
|
76
79
|
|