@botcord/botcord 0.1.2 → 0.1.3-beta.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.
package/api.ts ADDED
@@ -0,0 +1,3 @@
1
+ // api.ts — setup-only public surface (no runtime deps)
2
+ export { botCordPlugin } from "./src/channel.js";
3
+ export type { BotCordChannelConfig, BotCordAccountConfig } from "./src/types.js";
package/index.ts CHANGED
@@ -1,14 +1,7 @@
1
1
  /**
2
2
  * @botcord/botcord — OpenClaw plugin for BotCord A2A messaging protocol.
3
- *
4
- * Registers:
5
- * - Channel plugin (botcord) with WebSocket + polling gateway
6
- * - Agent tools: botcord_send, botcord_upload, botcord_rooms, botcord_topics, botcord_contacts, botcord_account, botcord_directory, botcord_payment, botcord_subscription, botcord_bind
7
- * - Commands: /botcord_healthcheck, /botcord_token, /botcord_bind
8
- * - CLI: openclaw botcord-register, openclaw botcord-import, openclaw botcord-export
9
3
  */
10
- import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk";
11
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
4
+ import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
12
5
  import { botCordPlugin } from "./src/channel.js";
13
6
  import { setBotCordRuntime, setConfigGetter } from "./src/runtime.js";
14
7
  import { createMessagingTool, createUploadTool } from "./src/tools/messaging.js";
@@ -33,33 +26,34 @@ import {
33
26
  shouldRunBotCordLoopRiskCheck,
34
27
  } from "./src/loop-risk.js";
35
28
 
36
- const plugin = {
29
+ export default defineChannelPluginEntry({
37
30
  id: "botcord",
38
31
  name: "BotCord",
39
32
  description: "BotCord A2A messaging protocol — secure agent-to-agent communication with Ed25519 signing",
40
- configSchema: emptyPluginConfigSchema(),
33
+ plugin: botCordPlugin,
41
34
 
42
- register(api: OpenClawPluginApi) {
43
- // Store runtime reference and config getter
44
- setBotCordRuntime(api.runtime);
45
- setConfigGetter(() => api.config);
35
+ setRuntime(runtime) {
36
+ setBotCordRuntime(runtime);
37
+ },
46
38
 
47
- // Register channel plugin
48
- api.registerChannel({ plugin: botCordPlugin as ChannelPlugin });
39
+ registerFull(api) {
40
+ setConfigGetter(() => api.config);
49
41
 
50
- // Register agent tools
42
+ // Agent tools — `as any` needed until tool execute() return types are
43
+ // migrated to the AgentToolResult<T> shape (P2 task).
51
44
  api.registerTool(createMessagingTool() as any);
45
+ api.registerTool(createUploadTool() as any);
52
46
  api.registerTool(createRoomsTool() as any);
53
47
  api.registerTool(createTopicsTool() as any);
54
48
  api.registerTool(createContactsTool() as any);
55
49
  api.registerTool(createAccountTool() as any);
56
50
  api.registerTool(createDirectoryTool() as any);
57
- api.registerTool(createUploadTool() as any);
58
51
  api.registerTool(createPaymentTool() as any);
59
52
  api.registerTool(createSubscriptionTool() as any);
60
53
  api.registerTool(createNotifyTool() as any);
61
54
  api.registerTool(createBindTool() as any);
62
55
 
56
+ // Hooks
63
57
  api.on("after_tool_call", async (event, ctx) => {
64
58
  if (ctx.toolName !== "botcord_send") return;
65
59
  if (!didBotCordSendSucceed(event.result, event.error)) return;
@@ -92,18 +86,16 @@ const plugin = {
92
86
  clearBotCordLoopRiskSession(ctx.sessionKey);
93
87
  });
94
88
 
95
- // Register commands
96
- api.registerCommand(createHealthcheckCommand() as any);
97
- api.registerCommand(createTokenCommand() as any);
98
- api.registerCommand(createBindCommand() as any);
89
+ // Commands
90
+ api.registerCommand(createHealthcheckCommand());
91
+ api.registerCommand(createTokenCommand());
92
+ api.registerCommand(createBindCommand());
99
93
 
100
- // Register CLI command
94
+ // CLI
101
95
  const registerCli = createRegisterCli();
102
96
  api.registerCli(registerCli.setup, { commands: registerCli.commands });
103
97
  },
104
- };
98
+ });
105
99
 
106
100
  export { TopicTracker } from "./src/topic-tracker.js";
107
101
  export type { TopicState, TopicInfo } from "./src/topic-tracker.js";
108
-
109
- export default plugin;
@@ -2,7 +2,7 @@
2
2
  "id": "botcord",
3
3
  "name": "BotCord",
4
4
  "description": "Secure agent-to-agent messaging via the BotCord A2A protocol (Ed25519 signed envelopes)",
5
- "version": "0.1.2",
5
+ "version": "0.1.3-beta.1",
6
6
  "channels": [
7
7
  "botcord"
8
8
  ],
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@botcord/botcord",
3
- "version": "0.1.2",
3
+ "version": "0.1.3-beta.1",
4
4
  "description": "OpenClaw channel plugin for BotCord A2A messaging protocol (Ed25519 signed envelopes)",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
7
7
  "license": "MIT",
8
8
  "files": [
9
9
  "index.ts",
10
+ "setup-entry.ts",
11
+ "api.ts",
12
+ "runtime-api.ts",
10
13
  "src/",
11
14
  "!src/__tests__/",
12
15
  "skills/",
@@ -35,11 +38,11 @@
35
38
  "ws": "^8.18.0"
36
39
  },
37
40
  "peerDependencies": {
38
- "openclaw": ">=2026.1.0"
41
+ "openclaw": ">=2026.3.22"
39
42
  },
40
43
  "devDependencies": {
41
44
  "@types/ws": "^8.5.0",
42
- "openclaw": ">=2026.1.0",
45
+ "openclaw": "^2026.3.23",
43
46
  "typescript": "^5.9.3",
44
47
  "vitest": "^4.0.18"
45
48
  },
@@ -47,6 +50,12 @@
47
50
  "extensions": [
48
51
  "./index.ts"
49
52
  ],
53
+ "setupEntry": "./setup-entry.ts",
54
+ "install": {
55
+ "npmSpec": "@botcord/botcord",
56
+ "defaultChoice": "npm",
57
+ "minHostVersion": ">=2026.3.22"
58
+ },
50
59
  "channel": {
51
60
  "id": "botcord",
52
61
  "label": "BotCord",
package/runtime-api.ts ADDED
@@ -0,0 +1,7 @@
1
+ // runtime-api.ts — full runtime surface
2
+ export { botCordPlugin } from "./src/channel.js";
3
+ export { BotCordClient } from "./src/client.js";
4
+ export { getBotCordRuntime } from "./src/runtime.js";
5
+ export { TopicTracker } from "./src/topic-tracker.js";
6
+ export type { TopicState, TopicInfo } from "./src/topic-tracker.js";
7
+ export type { BotCordChannelConfig, BotCordAccountConfig, MessageAttachment } from "./src/types.js";
package/setup-entry.ts ADDED
@@ -0,0 +1,5 @@
1
+ // setup-entry.ts — lightweight entry for onboarding/config (no heavy deps like ws)
2
+ import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
3
+ import { botCordPlugin } from "./src/channel.js";
4
+
5
+ export default defineSetupPluginEntry(botCordPlugin);
package/src/channel.ts CHANGED
@@ -3,28 +3,32 @@
3
3
  * outbound (send via signed envelopes), gateway (start websocket/polling),
4
4
  * security, messaging, and status adapters.
5
5
  */
6
- import type { ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk";
6
+ import type { ChannelPlugin, OpenClawConfig as ClawdbotConfig } from "openclaw/plugin-sdk/core";
7
7
  import {
8
8
  buildBaseChannelStatusSummary,
9
9
  createDefaultChannelRuntimeState,
10
- DEFAULT_ACCOUNT_ID,
11
- } from "openclaw/plugin-sdk";
10
+ } from "openclaw/plugin-sdk/status-helpers";
11
+ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
12
12
  import {
13
13
  resolveChannelConfig,
14
14
  resolveAccounts,
15
15
  isAccountConfigured,
16
16
  displayPrefix,
17
17
  } from "./config.js";
18
- import { BotCordClient } from "./client.js";
19
- import { getBotCordRuntime } from "./runtime.js";
20
- import { startPoller, stopPoller } from "./poller.js";
21
- import { startWsClient, stopWsClient } from "./ws-client.js";
22
18
  import type {
23
19
  BotCordAccountConfig,
24
20
  BotCordChannelConfig,
25
21
  MessageAttachment,
26
22
  } from "./types.js";
27
23
 
24
+ // runtime.js is lightweight (no ws/network deps) — safe for static import.
25
+ // Heavy deps (client, poller, ws-client) are lazy-loaded so that setup-entry.ts
26
+ // can import botCordPlugin without pulling in ws at module level.
27
+ import { getBotCordRuntime } from "./runtime.js";
28
+ const lazyClient = () => import("./client.js").then((m) => m.BotCordClient);
29
+ const lazyPoller = () => import("./poller.js");
30
+ const lazyWsClient = () => import("./ws-client.js");
31
+
28
32
  // ── Types ────────────────────────────────────────────────────────
29
33
 
30
34
  export interface ResolvedBotCordAccount {
@@ -291,7 +295,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
291
295
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId });
292
296
  if (!account.configured || !account.agentId) return null;
293
297
  try {
294
- const client = new BotCordClient(account.config);
298
+ const Client = await lazyClient();
299
+ const client = new Client(account.config);
295
300
  const info = await client.resolve(account.agentId);
296
301
  return { kind: "user", id: info.agent_id, name: info.display_name || info.agent_id };
297
302
  } catch {
@@ -302,7 +307,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
302
307
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId });
303
308
  if (!account.configured) return [];
304
309
  try {
305
- const client = new BotCordClient(account.config);
310
+ const Client = await lazyClient();
311
+ const client = new Client(account.config);
306
312
  const contacts = await client.listContacts();
307
313
  const q = query?.trim().toLowerCase() ?? "";
308
314
  return contacts
@@ -326,7 +332,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
326
332
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId });
327
333
  if (!account.configured) return [];
328
334
  try {
329
- const client = new BotCordClient(account.config);
335
+ const Client = await lazyClient();
336
+ const client = new Client(account.config);
330
337
  const rooms = await client.listMyRooms();
331
338
  const q = query?.trim().toLowerCase() ?? "";
332
339
  return rooms
@@ -345,7 +352,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
345
352
  textChunkLimit: 4000,
346
353
  sendText: async ({ cfg, to, text, accountId }) => {
347
354
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId: accountId ?? undefined });
348
- const client = new BotCordClient(account.config);
355
+ const Client = await lazyClient();
356
+ const client = new Client(account.config);
349
357
  const result = await client.sendMessage(to, text);
350
358
  return {
351
359
  channel: "botcord",
@@ -355,7 +363,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
355
363
  },
356
364
  sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => {
357
365
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId: accountId ?? undefined });
358
- const client = new BotCordClient(account.config);
366
+ const Client = await lazyClient();
367
+ const client = new Client(account.config);
359
368
  const attachments: MessageAttachment[] = [];
360
369
  if (mediaUrl) {
361
370
  const filename = mediaUrl.split("/").pop() || "attachment";
@@ -402,11 +411,13 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
402
411
  const dp = displayPrefix(account.accountId, ctx.cfg);
403
412
  ctx.log?.info(`[${dp}] starting BotCord gateway (${account.deliveryMode} mode)`);
404
413
 
405
- const client = new BotCordClient(account.config);
414
+ const Client = await lazyClient();
415
+ const client = new Client(account.config);
406
416
  const mode = account.deliveryMode || "websocket";
407
417
 
408
418
  if (mode === "websocket") {
409
419
  ctx.log?.info(`[${dp}] starting WebSocket connection to Hub`);
420
+ const { startWsClient } = await lazyWsClient();
410
421
  startWsClient({
411
422
  client,
412
423
  accountId: account.accountId,
@@ -415,6 +426,7 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
415
426
  log: ctx.log,
416
427
  });
417
428
  } else {
429
+ const { startPoller } = await lazyPoller();
418
430
  startPoller({
419
431
  client,
420
432
  accountId: account.accountId,
@@ -438,6 +450,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
438
450
  ctx.abortSignal?.addEventListener("abort", () => resolve(), { once: true });
439
451
  });
440
452
 
453
+ const { stopWsClient } = await lazyWsClient();
454
+ const { stopPoller } = await lazyPoller();
441
455
  stopWsClient(account.accountId);
442
456
  stopPoller(account.accountId);
443
457
  ctx.setStatus({ accountId: ctx.accountId, running: false, lastStopAt: Date.now() });
@@ -10,8 +10,8 @@ export function createBindCommand() {
10
10
  "Bind this agent to a BotCord web dashboard account using a bind ticket.",
11
11
  acceptsArgs: true,
12
12
  requireAuth: true,
13
- handler: async (args?: string) => {
14
- const bindTicket = (args || "").trim();
13
+ handler: async (ctx: any) => {
14
+ const bindTicket = (ctx.args || "").trim();
15
15
  if (!bindTicket) {
16
16
  return { text: "[FAIL] Usage: /botcord_bind <bind_ticket>" };
17
17
  }
@@ -18,7 +18,7 @@ export function createHealthcheckCommand() {
18
18
  description: "Check BotCord integration health: config, Hub connectivity, token, delivery mode.",
19
19
  acceptsArgs: false,
20
20
  requireAuth: true,
21
- handler: async () => {
21
+ handler: async (_ctx: any) => {
22
22
  const lines: string[] = [];
23
23
  let pass = 0;
24
24
  let warn = 0;
@@ -25,8 +25,7 @@ import {
25
25
  } from "../config.js";
26
26
  import { normalizeAndValidateHubUrl } from "../hub-url.js";
27
27
  import { getBotCordRuntime } from "../runtime.js";
28
-
29
- const DEFAULT_HUB = "https://api.botcord.chat";
28
+ import { DEFAULT_HUB } from "../constants.js";
30
29
 
31
30
  interface RegisterResult {
32
31
  agentId: string;
@@ -15,7 +15,7 @@ export function createTokenCommand() {
15
15
  description: "Fetch and display the current BotCord JWT token.",
16
16
  acceptsArgs: false,
17
17
  requireAuth: true,
18
- handler: async () => {
18
+ handler: async (_ctx: any) => {
19
19
  const cfg = getAppConfig();
20
20
  if (!cfg) {
21
21
  return { text: "[FAIL] No OpenClaw configuration available" };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Release channel and environment-dependent defaults.
3
+ *
4
+ * In the stable (production) build this file ships as-is.
5
+ * The CI pipeline replaces the RELEASE_CHANNEL value with "beta"
6
+ * before publishing to the `beta` npm dist-tag, so that beta
7
+ * installations automatically point at the test hub.
8
+ */
9
+
10
+ export type ReleaseChannel = "stable" | "beta";
11
+
12
+ export const RELEASE_CHANNEL: ReleaseChannel = "beta";
13
+
14
+ const HUB_URLS: Record<ReleaseChannel, string> = {
15
+ stable: "https://api.botcord.chat",
16
+ beta: "https://test.botcord.chat",
17
+ };
18
+
19
+ export const DEFAULT_HUB = HUB_URLS[RELEASE_CHANNEL];
package/src/inbound.ts CHANGED
@@ -227,7 +227,7 @@ async function handleA2AMessage(
227
227
  // Prompt the agent to notify its owner when receiving contact requests
228
228
  const notifyOwnerHint =
229
229
  envelope.type === "contact_request"
230
- ? `\n\n[You received a contact request from ${senderId}. Use the botcord_notify tool to inform your owner about this request so they can decide whether to accept or reject it. Include the sender's agent ID and any message they attached.]`
230
+ ? `\n\n[You received a contact request from ${sanitizedSender}. Use the botcord_notify tool to inform your owner about this request so they can decide whether to accept or reject it. Include the sender's agent ID and any message they attached.]`
231
231
  : "";
232
232
 
233
233
  const sanitizedContent = sanitizeUntrustedContent(rawContent);
package/src/runtime.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Plugin runtime store — holds a reference to OpenClaw's PluginRuntime
3
3
  * and a config getter for tools/hooks that need the full app config.
4
4
  */
5
- import type { PluginRuntime } from "openclaw/plugin-sdk";
5
+ import type { PluginRuntime } from "openclaw/plugin-sdk/core";
6
6
 
7
7
  let runtime: PluginRuntime | null = null;
8
8
  let configGetter: (() => any) | null = null;
package/src/sanitize.ts CHANGED
@@ -33,11 +33,14 @@ export function sanitizeUntrustedContent(text: string): string {
33
33
  }
34
34
 
35
35
  /**
36
- * Sanitize sender name — must not contain newlines or structural markers.
36
+ * Sanitize sender name — must not contain newlines, structural markers,
37
+ * or characters that could break XML attribute boundaries.
37
38
  */
38
39
  export function sanitizeSenderName(name: string): string {
39
40
  return name
40
41
  .replace(/[\n\r]/g, " ")
41
42
  .replace(/\[/g, "⟦").replace(/\]/g, "⟧")
43
+ .replace(/"/g, "'")
44
+ .replace(/</g, "<").replace(/>/g, ">")
42
45
  .slice(0, 100);
43
46
  }
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createAccountTool() {
13
13
  return {
14
14
  name: "botcord_account",
15
+ label: "Manage Account",
15
16
  description:
16
17
  "Manage your own BotCord agent: view identity, update profile, get/set message policy, check message delivery status.",
17
18
  parameters: {
package/src/tools/bind.ts CHANGED
@@ -70,6 +70,7 @@ export async function executeBind(
70
70
  export function createBindTool() {
71
71
  return {
72
72
  name: "botcord_bind",
73
+ label: "Bind Dashboard",
73
74
  description:
74
75
  "Bind this BotCord agent to a user's web dashboard account using a bind ticket.",
75
76
  parameters: {
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createContactsTool() {
13
13
  return {
14
14
  name: "botcord_contacts",
15
+ label: "Manage Contacts",
15
16
  description: "Manage BotCord contacts: list/remove contacts, send/accept/reject requests, block/unblock agents.",
16
17
  parameters: {
17
18
  type: "object" as const,
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createDirectoryTool() {
13
13
  return {
14
14
  name: "botcord_directory",
15
+ label: "Search Directory",
15
16
  description: "Look up agents, discover public rooms, and query message history on BotCord.",
16
17
  parameters: {
17
18
  type: "object" as const,
@@ -77,6 +77,7 @@ async function uploadLocalFiles(
77
77
  export function createMessagingTool() {
78
78
  return {
79
79
  name: "botcord_send",
80
+ label: "Send Message",
80
81
  description:
81
82
  "Send a message to another agent or room via BotCord. " +
82
83
  "Use ag_* for direct messages, rm_* for rooms. " +
@@ -192,6 +193,7 @@ export function createMessagingTool() {
192
193
  export function createUploadTool() {
193
194
  return {
194
195
  name: "botcord_upload",
196
+ label: "Upload Files",
195
197
  description:
196
198
  "Upload one or more local files to BotCord Hub. " +
197
199
  "Returns file URLs that can be used later in botcord_send's file_urls parameter. " +
@@ -11,6 +11,7 @@ import { deliverNotification } from "../inbound.js";
11
11
  export function createNotifyTool() {
12
12
  return {
13
13
  name: "botcord_notify",
14
+ label: "Send Notification",
14
15
  description:
15
16
  "Send a notification to the owner's configured channel (e.g. Telegram, Discord). " +
16
17
  "Use this when you receive an important BotCord message that the owner should know about — " +
@@ -194,6 +194,7 @@ function sanitizeLedger(data: any): any {
194
194
  export function createPaymentTool(opts?: { name?: string; description?: string }) {
195
195
  return {
196
196
  name: opts?.name || "botcord_payment",
197
+ label: "Manage Payments",
197
198
  description:
198
199
  opts?.description ||
199
200
  "Manage BotCord coin payments and transactions: verify recipients, check balance, view ledger, transfer coins, create topups and withdrawals, cancel withdrawals, and query transaction status.",
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createRoomsTool() {
13
13
  return {
14
14
  name: "botcord_rooms",
15
+ label: "Manage Rooms",
15
16
  description:
16
17
  "Manage BotCord rooms: create, list, join, leave, update, invite/remove members, " +
17
18
  "set permissions, promote/transfer/dissolve, discover public rooms.",
@@ -47,6 +47,7 @@ function formatSubscriptionList(subscriptions: any[]): string {
47
47
  export function createSubscriptionTool() {
48
48
  return {
49
49
  name: "botcord_subscription",
50
+ label: "Manage Subscriptions",
50
51
  description:
51
52
  "Create subscription products priced in BotCord coin, subscribe to products, list active subscriptions, and manage cancellation or product archiving.",
52
53
  parameters: {
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createTopicsTool() {
13
13
  return {
14
14
  name: "botcord_topics",
15
+ label: "Manage Topics",
15
16
  description:
16
17
  "Manage topics within BotCord rooms. Topics are goal-driven conversation units " +
17
18
  "with lifecycle states: open → completed/failed/expired.",