@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/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: true });
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
+ }
@@ -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();
@@ -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
  }
@@ -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) {
@@ -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) {
@@ -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 notifySession = acct.notifySession;
38
- if (!notifySession) {
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
- try {
49
- await deliverNotification(core, cfg, notifySession, text);
50
- return { ok: true, notifySession };
51
- } catch (err: any) {
52
- return { error: `notify failed: ${err?.message ?? err}` };
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
- const lines = [
72
- `Transfer record message: ${result.transfer_record_message.sent ? "sent" : "failed"}`,
73
- `Payer notification: ${result.notifications.payer.sent ? "sent" : "failed"}`,
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 [recordMessage, payerNotification, payeeNotification] = await Promise.all([
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
  }
@@ -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 (string) — for transfer, topup, withdraw",
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
+ }
@@ -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: "private",
185
- join_policy: "invite_only",
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: "private",
206
- join_policy: "invite_only",
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,
@@ -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