@vama/openclaw 2026.5.5-4 → 2026.5.5-5

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.
@@ -1,4 +1,5 @@
1
- import { a as dispatchStarted, i as dispatchEnded } from "./monitor-CHFjRu2J.js";
1
+ import "./probe-Cf1xor23.js";
2
+ import { i as dispatchStarted, r as dispatchEnded } from "./monitor-DLgOtdLQ.js";
2
3
  //#region extensions/vama/src/subagent-keepalive-hooks.ts
3
4
  function registerVamaSubagentKeepaliveHooks(api) {
4
5
  api.on("subagent_spawned", () => {
package/dist/api.js CHANGED
@@ -1,3 +1,4 @@
1
- import { n as probeVama, r as sendMessageVama, t as monitorVamaProvider } from "./monitor-CHFjRu2J.js";
2
- import { t as registerVamaSubagentKeepaliveHooks } from "./api-lhR0QgC_.js";
1
+ import { n as probeVama } from "./probe-Cf1xor23.js";
2
+ import { n as sendMessageVama, t as monitorVamaProvider } from "./monitor-DLgOtdLQ.js";
3
+ import { t as registerVamaSubagentKeepaliveHooks } from "./api-DftFHS2s.js";
3
4
  export { monitorVamaProvider, probeVama, registerVamaSubagentKeepaliveHooks, sendMessageVama };
@@ -1,6 +1,8 @@
1
- import { a as provisionBot, i as createBotHubClient, n as attachmentHintFromExtension } from "./client-AsD46gcK.js";
2
- import { c as buildBaseChannelStatusSummary, d as resolveDefaultVamaAccountId, f as resolveVamaAccount, l as createDefaultChannelRuntimeState, n as probeVama, o as DEFAULT_ACCOUNT_ID$1, r as sendMessageVama, s as PAIRING_APPROVED_MESSAGE, t as monitorVamaProvider, u as listVamaAccountIds } from "./monitor-CHFjRu2J.js";
3
- import { t as getVamaRuntime } from "./runtime-w-1oL50p.js";
1
+ import { a as exchangeConnectCode, i as createBotHubClient, n as attachmentHintFromExtension, o as provisionBot } from "./client-BzhfASX8.js";
2
+ import { a as resolveDefaultVamaAccountId, i as listVamaAccountIds, n as probeVama, o as resolveVamaAccount } from "./probe-Cf1xor23.js";
3
+ import { a as DEFAULT_ACCOUNT_ID$1, c as createDefaultChannelRuntimeState, n as sendMessageVama, o as PAIRING_APPROVED_MESSAGE, s as buildBaseChannelStatusSummary, t as monitorVamaProvider } from "./monitor-DLgOtdLQ.js";
4
+ import { t as getVamaRuntime } from "./runtime-Cq09Y5cd.js";
5
+ import { t as parseVamaConnectCode } from "./connect-code-Bbp9J6TH.js";
4
6
  import { jsonResult, readStringOrNumberParam, readStringParam } from "openclaw/plugin-sdk/channel-actions";
5
7
  import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
6
8
  import { DEFAULT_ACCOUNT_ID, addWildcardAllowFrom, createStandardChannelSetupStatus, formatDocsLink, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
@@ -334,7 +336,7 @@ const vamaPlugin = {
334
336
  const account = resolveVamaAccount({ cfg });
335
337
  if (!account.configured) return;
336
338
  try {
337
- const { createBotHubClient } = await import("./client-AsD46gcK.js").then((n) => n.r);
339
+ const { createBotHubClient } = await import("./client-BzhfASX8.js").then((n) => n.r);
338
340
  await createBotHubClient(account).sendMessage({
339
341
  userId: id,
340
342
  text: PAIRING_APPROVED_MESSAGE
@@ -554,8 +556,8 @@ const vamaPlugin = {
554
556
  introNote: {
555
557
  title: "Vama setup",
556
558
  lines: [
557
- "Vama needs BotHub credentials.",
558
- "You will be prompted for your Vama username to auto-provision a bot.",
559
+ "Easiest: paste a connect code from the Vama app (Settings → AI Agents).",
560
+ "Alternative: provision a bot by Vama username.",
559
561
  `Docs: ${formatDocsLink("/channels/vama", "channels/vama")}`
560
562
  ],
561
563
  shouldShow: ({ cfg }) => !resolveVamaAccount({ cfg }).configured
@@ -585,6 +587,65 @@ const vamaPlugin = {
585
587
  return { cfg: next };
586
588
  }
587
589
  }
590
+ if (await prompter.select({
591
+ message: "How do you want to connect to Vama?",
592
+ options: [{
593
+ value: "code",
594
+ label: "Paste a connect code (recommended)",
595
+ hint: "Vama app → Settings → AI Agents → Connect your own — no tunnel needed"
596
+ }, {
597
+ value: "username",
598
+ label: "Provision by Vama username",
599
+ hint: "creates/reuses a bot for your account; webhook URL needed later"
600
+ }],
601
+ initialValue: "code"
602
+ }) === "code") {
603
+ const parsedCode = parseVamaConnectCode((await prompter.text({
604
+ message: "Connect code (vmc1_… or 64-char hex)",
605
+ validate: (value) => {
606
+ try {
607
+ parseVamaConnectCode(value ?? "");
608
+ return;
609
+ } catch (err) {
610
+ return err instanceof Error ? err.message : String(err);
611
+ }
612
+ }
613
+ })).trim());
614
+ const codeBothubUrl = parsedCode.bothubUrl || vamaCfg?.bothubUrl?.trim() || "https://bothub.vama.com";
615
+ await prompter.note("Exchanging connect code with BotHub...", "Vama setup");
616
+ try {
617
+ const exchanged = await exchangeConnectCode({
618
+ bothubUrl: codeBothubUrl,
619
+ code: parsedCode.code
620
+ });
621
+ next = {
622
+ ...next,
623
+ channels: {
624
+ ...next.channels,
625
+ vama: {
626
+ ...next.channels?.vama,
627
+ enabled: true,
628
+ botToken: exchanged.bot_token,
629
+ webhookSecret: exchanged.webhook_secret,
630
+ ...codeBothubUrl !== "https://bothub.vama.com" ? { bothubUrl: codeBothubUrl } : {}
631
+ }
632
+ }
633
+ };
634
+ const label = exchanged.display_name || exchanged.username || exchanged.bot_id;
635
+ await prompter.note(exchanged.websocket_enabled ? `Connected as ${label}. WebSocket delivery is enabled — no tunnel or public URL needed.` : `Connected as ${label}. This bot uses webhook delivery — set channels.vama.webhookUrl once your gateway is publicly reachable.`, "Vama connect");
636
+ } catch (err) {
637
+ await prompter.note(`Connect code exchange failed: ${err instanceof Error ? err.message : String(err)}\nMint a fresh code in Vama settings, or re-run setup and choose username provisioning.`, "Vama connect error");
638
+ return { cfg: next };
639
+ }
640
+ const account = resolveVamaAccount({ cfg: next });
641
+ try {
642
+ const probe = await probeVama(account);
643
+ await prompter.note(probe.ok ? `Connected successfully (bot ${probe.botId ?? "unknown"})` : `Connection test failed: ${probe.error ?? "unknown error"}`, "Vama connection test");
644
+ } catch (err) {
645
+ await prompter.note(`Connection test failed: ${String(err)}`, "Vama connection test");
646
+ }
647
+ return { cfg: next };
648
+ }
588
649
  const bothubUrl = vamaCfg?.bothubUrl?.trim() || "https://bothub.vama.com";
589
650
  const username = (await prompter.text({
590
651
  message: "Your Vama username (for bot provisioning)",
@@ -1,2 +1,2 @@
1
- import { t as vamaPlugin } from "./channel-plugin-api-YGbkWmVM.js";
1
+ import { t as vamaPlugin } from "./channel-plugin-api-KLKTLQFV.js";
2
2
  export { vamaPlugin };
@@ -0,0 +1,150 @@
1
+ import { a as exchangeConnectCode } from "./client-BzhfASX8.js";
2
+ import { n as probeVama, o as resolveVamaAccount, t as clearProbeCache } from "./probe-Cf1xor23.js";
3
+ import { t as getVamaRuntime } from "./runtime-Cq09Y5cd.js";
4
+ import { t as parseVamaConnectCode } from "./connect-code-Bbp9J6TH.js";
5
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
6
+ //#region extensions/vama/src/cli.ts
7
+ /**
8
+ * Apply freshly exchanged credentials to the config. Default account writes
9
+ * top-level under channels.vama (matching the setup wizard); named accounts
10
+ * write under channels.vama.accounts.<id>. Existing unrelated keys
11
+ * (dmPolicy, webhookPort, …) are preserved.
12
+ */
13
+ function applyVamaConnectCredentials(params) {
14
+ const { cfg, accountId } = params;
15
+ const vama = cfg.channels?.vama ?? {};
16
+ const credentialPatch = {
17
+ enabled: true,
18
+ botToken: params.botToken,
19
+ webhookSecret: params.webhookSecret
20
+ };
21
+ if (params.bothubUrl !== "https://bothub.vama.com") credentialPatch.bothubUrl = params.bothubUrl;
22
+ if (accountId === DEFAULT_ACCOUNT_ID) {
23
+ const next = {
24
+ ...vama,
25
+ ...credentialPatch
26
+ };
27
+ if (params.bothubUrl === "https://bothub.vama.com") delete next.bothubUrl;
28
+ return {
29
+ ...cfg,
30
+ channels: {
31
+ ...cfg.channels,
32
+ vama: {
33
+ ...next,
34
+ enabled: true
35
+ }
36
+ }
37
+ };
38
+ }
39
+ const nextAccount = {
40
+ ...vama.accounts?.[accountId] ?? {},
41
+ ...credentialPatch
42
+ };
43
+ if (params.bothubUrl === "https://bothub.vama.com") delete nextAccount.bothubUrl;
44
+ return {
45
+ ...cfg,
46
+ channels: {
47
+ ...cfg.channels,
48
+ vama: {
49
+ ...vama,
50
+ enabled: true,
51
+ accounts: {
52
+ ...vama.accounts,
53
+ [accountId]: nextAccount
54
+ }
55
+ }
56
+ }
57
+ };
58
+ }
59
+ /**
60
+ * `openclaw vama connect <code>` — the whole BYO onboarding in one command:
61
+ * exchange the code for credentials, write the config, verify with a live
62
+ * probe. WebSocket-enabled bots (the default for codes minted from Vama
63
+ * settings) need no tunnel, no public URL, no webhook registration.
64
+ */
65
+ async function runVamaConnect(params, deps = {}) {
66
+ const exchange = deps.exchange ?? exchangeConnectCode;
67
+ const probe = deps.probe ?? probeVama;
68
+ const log = deps.log ?? ((msg) => console.log(msg));
69
+ const parsed = parseVamaConnectCode(params.rawCode);
70
+ const runtime = getVamaRuntime();
71
+ const cfg = runtime.config.current();
72
+ const accountId = normalizeAccountId(params.account?.trim() || DEFAULT_ACCOUNT_ID);
73
+ const existingUrl = (cfg.channels?.vama ?? {}).bothubUrl?.trim();
74
+ const bothubUrl = (params.bothubUrlFlag?.trim() || parsed.bothubUrl || existingUrl || "https://bothub.vama.com").replace(/\/+$/, "");
75
+ log(`Exchanging connect code with ${bothubUrl}…`);
76
+ const exchanged = await exchange({
77
+ bothubUrl,
78
+ code: parsed.code
79
+ });
80
+ const updated = applyVamaConnectCredentials({
81
+ cfg,
82
+ accountId,
83
+ botToken: exchanged.bot_token,
84
+ webhookSecret: exchanged.webhook_secret,
85
+ bothubUrl
86
+ });
87
+ await runtime.config.replaceConfigFile({
88
+ nextConfig: updated,
89
+ afterWrite: { mode: "auto" }
90
+ });
91
+ clearProbeCache();
92
+ const result = {
93
+ accountId,
94
+ botId: exchanged.bot_id,
95
+ username: exchanged.username,
96
+ displayName: exchanged.display_name,
97
+ websocketEnabled: exchanged.websocket_enabled === true,
98
+ bothubUrl,
99
+ created: exchanged.created
100
+ };
101
+ try {
102
+ const probed = await probe(resolveVamaAccount({
103
+ cfg: updated,
104
+ accountId
105
+ }));
106
+ result.probeOk = probed.ok;
107
+ if (!probed.ok) result.probeError = probed.error;
108
+ } catch (err) {
109
+ result.probeOk = false;
110
+ result.probeError = err instanceof Error ? err.message : String(err);
111
+ }
112
+ return result;
113
+ }
114
+ function printConnectResult(result) {
115
+ const botLabel = result.displayName || result.username || result.botId;
116
+ console.log("");
117
+ console.log(`✔ Connected to Vama as ${botLabel}${result.username ? ` (@${result.username})` : ""}`);
118
+ if (result.websocketEnabled) console.log(" Transport: WebSocket — no tunnel or public URL needed.");
119
+ else console.log(" Transport: webhook — set channels.vama.webhookUrl to a public URL (or mint a new code from Vama settings to enable WebSocket).");
120
+ if (result.probeOk === false && result.probeError) {
121
+ console.log(` Verification warning: ${result.probeError}`);
122
+ console.log(" Re-check later with: openclaw channels status --probe");
123
+ }
124
+ console.log("");
125
+ console.log(" If your gateway is running it reloads automatically; otherwise start it:");
126
+ console.log(" openclaw gateway");
127
+ }
128
+ function registerVamaCli({ program }) {
129
+ program.command("vama").description("Vama channel tools (connect codes, account state)").command("connect").description("Connect this OpenClaw to Vama with a one-time code from Vama settings (Settings → AI Agents)").argument("<code>", "connect code (vmc1_… token or 64-char hex)").option("--bothub-url <url>", "BotHub endpoint override (defaults to the URL embedded in the code)").option("--account <id>", "Vama account id to write credentials to", DEFAULT_ACCOUNT_ID).option("--json", "print the result as JSON", false).action(async (code, opts) => {
130
+ try {
131
+ const result = await runVamaConnect({
132
+ rawCode: code,
133
+ bothubUrlFlag: opts.bothubUrl,
134
+ account: opts.account
135
+ }, { log: opts.json ? () => {} : void 0 });
136
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
137
+ else printConnectResult(result);
138
+ } catch (err) {
139
+ const message = err instanceof Error ? err.message : String(err);
140
+ if (opts.json) console.log(JSON.stringify({
141
+ ok: false,
142
+ error: message
143
+ }, null, 2));
144
+ else console.error(`✖ Connect failed: ${message}`);
145
+ process.exitCode = 1;
146
+ }
147
+ });
148
+ }
149
+ //#endregion
150
+ export { registerVamaCli };
@@ -19,6 +19,7 @@ var client_exports = /* @__PURE__ */ __exportAll({
19
19
  FetchWithRetryError: () => FetchWithRetryError,
20
20
  attachmentHintFromExtension: () => attachmentHintFromExtension,
21
21
  createBotHubClient: () => createBotHubClient,
22
+ exchangeConnectCode: () => exchangeConnectCode,
22
23
  mimeFromExtension: () => mimeFromExtension,
23
24
  provisionBot: () => provisionBot
24
25
  });
@@ -312,6 +313,65 @@ function createBotHubClient(account) {
312
313
  };
313
314
  }
314
315
  /**
316
+ * Exchange a single-use connect code for bot credentials
317
+ * (POST /v1/provision/exchange — unauthenticated; the code IS the
318
+ * credential). BotHub consumes the code atomically: a second attempt with
319
+ * the same code fails with `invalid_code`, so callers should treat errors
320
+ * as "mint a fresh code in Vama settings", not retry-the-same-code.
321
+ */
322
+ async function exchangeConnectCode(params) {
323
+ const baseUrl = params.bothubUrl.replace(/\/+$/, "");
324
+ const path = "/v1/provision/exchange";
325
+ const start = Date.now();
326
+ let resPair;
327
+ try {
328
+ resPair = await fetchWithRetry(`${baseUrl}${path}`, {
329
+ method: "POST",
330
+ headers: { "Content-Type": "application/json" },
331
+ body: JSON.stringify({ code: params.code })
332
+ });
333
+ } catch (err) {
334
+ logBothubCall({
335
+ method: "POST",
336
+ path,
337
+ start,
338
+ status: null,
339
+ attemptsUsed: attemptsUsedFromError(err),
340
+ errorMsg: unwrapFetchErrorMsg(err)
341
+ });
342
+ throw err;
343
+ }
344
+ const { res, attemptsUsed } = resPair;
345
+ if (!res.ok) {
346
+ const text = await res.text().catch(() => "");
347
+ logBothubCall({
348
+ method: "POST",
349
+ path,
350
+ start,
351
+ status: res.status,
352
+ attemptsUsed,
353
+ bodyText: text
354
+ });
355
+ let errorCode = "";
356
+ let message = text;
357
+ try {
358
+ const parsed = JSON.parse(text);
359
+ errorCode = parsed.error ?? "";
360
+ message = parsed.message ?? text;
361
+ } catch {}
362
+ if (errorCode === "invalid_code") throw new Error("Connect code is invalid, expired, or already used. Mint a fresh one in Vama settings and paste the new command.");
363
+ throw new Error(`BotHub exchange failed (${res.status}): ${message}`);
364
+ }
365
+ logBothubCall({
366
+ method: "POST",
367
+ path,
368
+ start,
369
+ status: res.status,
370
+ attemptsUsed
371
+ });
372
+ return await res.json();
373
+ }
374
+ /**
315
375
  * Provision a new bot via the BotHub provisioning API (unauthenticated).
316
376
  */
317
377
  async function provisionBot(params) {
@@ -364,4 +424,4 @@ async function provisionBot(params) {
364
424
  return await res.json();
365
425
  }
366
426
  //#endregion
367
- export { provisionBot as a, createBotHubClient as i, attachmentHintFromExtension as n, client_exports as r, BOTHUB_DEFAULT_URL as t };
427
+ export { exchangeConnectCode as a, createBotHubClient as i, attachmentHintFromExtension as n, provisionBot as o, client_exports as r, BOTHUB_DEFAULT_URL as t };
@@ -0,0 +1,49 @@
1
+ const HEX_CODE_RE = /^[0-9a-f]{64}$/i;
2
+ function parseWrappedCode(raw) {
3
+ const payload = raw.slice(5);
4
+ let decoded;
5
+ try {
6
+ decoded = Buffer.from(payload, "base64url").toString("utf8");
7
+ } catch {
8
+ throw new Error("Invalid connect code: not valid base64url");
9
+ }
10
+ let parsed;
11
+ try {
12
+ parsed = JSON.parse(decoded);
13
+ } catch {
14
+ throw new Error("Invalid connect code: wrapper payload is not JSON");
15
+ }
16
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Invalid connect code: wrapper payload is not an object");
17
+ const { u, c } = parsed;
18
+ if (typeof c !== "string" || !HEX_CODE_RE.test(c)) throw new Error("Invalid connect code: missing or malformed exchange code");
19
+ let bothubUrl;
20
+ if (u !== void 0) {
21
+ if (typeof u !== "string") throw new Error("Invalid connect code: BotHub URL must be a string");
22
+ let parsedUrl;
23
+ try {
24
+ parsedUrl = new URL(u);
25
+ } catch {
26
+ throw new Error(`Invalid connect code: BotHub URL is not a valid URL: ${u}`);
27
+ }
28
+ if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") throw new Error(`Invalid connect code: BotHub URL must be http(s): ${u}`);
29
+ bothubUrl = u.replace(/\/+$/, "");
30
+ }
31
+ return {
32
+ code: c.toLowerCase(),
33
+ bothubUrl
34
+ };
35
+ }
36
+ /**
37
+ * Parse a connect code as pasted by the user. Accepts the `vmc1_` wrapped
38
+ * form (URL + code in one token) and the bare 64-hex form. Throws a
39
+ * user-presentable Error for anything else.
40
+ */
41
+ function parseVamaConnectCode(raw) {
42
+ const trimmed = raw.trim();
43
+ if (!trimmed) throw new Error("Connect code is required");
44
+ if (trimmed.startsWith("vmc1_")) return parseWrappedCode(trimmed);
45
+ if (HEX_CODE_RE.test(trimmed)) return { code: trimmed.toLowerCase() };
46
+ throw new Error("Invalid connect code: expected a vmc1_… token or a 64-character hex code (copy the full command from Vama settings — codes expire after 15 minutes)");
47
+ }
48
+ //#endregion
49
+ export { parseVamaConnectCode as t };
package/dist/index.js CHANGED
@@ -1,8 +1,21 @@
1
- import { n as probeVama, r as sendMessageVama, t as monitorVamaProvider } from "./monitor-CHFjRu2J.js";
2
- import { n as setVamaRuntime } from "./runtime-w-1oL50p.js";
3
- import { t as registerVamaSubagentKeepaliveHooks } from "./api-lhR0QgC_.js";
4
- import { t as vamaPlugin } from "./channel-plugin-api-YGbkWmVM.js";
1
+ import { n as probeVama } from "./probe-Cf1xor23.js";
2
+ import { n as sendMessageVama, t as monitorVamaProvider } from "./monitor-DLgOtdLQ.js";
3
+ import { n as setVamaRuntime } from "./runtime-Cq09Y5cd.js";
4
+ import { t as registerVamaSubagentKeepaliveHooks } from "./api-DftFHS2s.js";
5
+ import { t as vamaPlugin } from "./channel-plugin-api-KLKTLQFV.js";
5
6
  import "./runtime-api.js";
7
+ //#region extensions/vama/src/cli-metadata.ts
8
+ function registerVamaCliMetadata(api) {
9
+ api.registerCli(async ({ program }) => {
10
+ const { registerVamaCli } = await import("./cli-F0a1J9Rj.js");
11
+ registerVamaCli({ program });
12
+ }, { descriptors: [{
13
+ name: "vama",
14
+ description: "Connect this OpenClaw to Vama and manage the Vama channel",
15
+ hasSubcommands: true
16
+ }] });
17
+ }
18
+ //#endregion
6
19
  //#region extensions/vama/index.ts
7
20
  const channelEntry = {
8
21
  kind: "bundled-channel-entry",
@@ -41,10 +54,15 @@ const channelEntry = {
41
54
  } }
42
55
  },
43
56
  register(api) {
44
- if (api.registrationMode === "cli-metadata") return;
57
+ const mode = api.registrationMode;
58
+ if (mode === "cli-metadata") {
59
+ registerVamaCliMetadata(api);
60
+ return;
61
+ }
45
62
  setVamaRuntime(api.runtime);
46
63
  api.registerChannel({ plugin: vamaPlugin });
47
64
  registerVamaSubagentKeepaliveHooks(api);
65
+ if (mode === "discovery" || mode === "full") registerVamaCliMetadata(api);
48
66
  },
49
67
  loadChannelPlugin: () => vamaPlugin,
50
68
  setChannelRuntime: (runtime) => {
@@ -1,11 +1,11 @@
1
- import { i as createBotHubClient, n as attachmentHintFromExtension } from "./client-AsD46gcK.js";
2
- import { t as getVamaRuntime } from "./runtime-w-1oL50p.js";
1
+ import { i as createBotHubClient, n as attachmentHintFromExtension } from "./client-BzhfASX8.js";
2
+ import { o as resolveVamaAccount, r as startVamaWebSocket, t as clearProbeCache } from "./probe-Cf1xor23.js";
3
+ import { t as getVamaRuntime } from "./runtime-Cq09Y5cd.js";
3
4
  import * as fs from "node:fs";
4
5
  import { promises } from "node:fs";
5
6
  import * as http from "node:http";
6
- import { DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
7
+ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
7
8
  import path from "node:path";
8
- import { WebSocket, fetch } from "undici";
9
9
  import { homedir } from "node:os";
10
10
  import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
11
11
  import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
@@ -17,63 +17,6 @@ import { createPersistentDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
17
17
  import { buildBaseChannelStatusSummary, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
18
18
  import * as crypto from "node:crypto";
19
19
  import { createHash } from "node:crypto";
20
- //#region extensions/vama/src/accounts.ts
21
- function listConfiguredAccountIds(cfg) {
22
- const accounts = (cfg.channels?.vama)?.accounts;
23
- if (!accounts || typeof accounts !== "object") return [];
24
- return Object.keys(accounts).filter(Boolean);
25
- }
26
- function listVamaAccountIds(cfg) {
27
- const ids = listConfiguredAccountIds(cfg);
28
- if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
29
- return [...ids].toSorted((a, b) => a.localeCompare(b));
30
- }
31
- function resolveDefaultVamaAccountId(cfg) {
32
- const ids = listVamaAccountIds(cfg);
33
- if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
34
- return ids[0] ?? DEFAULT_ACCOUNT_ID;
35
- }
36
- function resolveAccountConfig(cfg, accountId) {
37
- const accounts = (cfg.channels?.vama)?.accounts;
38
- if (!accounts || typeof accounts !== "object") return;
39
- return accounts[accountId];
40
- }
41
- function mergeVamaAccountConfig(cfg, accountId) {
42
- const { accounts: _ignored, ...base } = cfg.channels?.vama ?? {};
43
- const account = resolveAccountConfig(cfg, accountId) ?? {};
44
- return {
45
- ...base,
46
- ...account
47
- };
48
- }
49
- function resolveVamaAccount(params) {
50
- const accountId = normalizeAccountId(params.accountId);
51
- const baseEnabled = (params.cfg.channels?.vama)?.enabled !== false;
52
- const merged = mergeVamaAccountConfig(params.cfg, accountId);
53
- const accountEnabled = merged.enabled !== false;
54
- const enabled = baseEnabled && accountEnabled;
55
- const botToken = merged.botToken?.trim() || void 0;
56
- const webhookSecret = merged.webhookSecret?.trim() || void 0;
57
- const webhookSecretFile = merged.webhookSecretFile?.trim() || void 0;
58
- const bothubUrl = merged.bothubUrl?.trim() || "https://bothub.vama.com";
59
- const webhookUrl = merged.webhookUrl?.trim() || void 0;
60
- const rawTransport = merged.transport?.trim();
61
- const transport = rawTransport === "auto" || rawTransport === "webhook" || rawTransport === "websocket" ? rawTransport : void 0;
62
- return {
63
- accountId,
64
- enabled,
65
- configured: Boolean(botToken),
66
- name: merged.name?.trim() || void 0,
67
- botToken,
68
- webhookSecret,
69
- webhookSecretFile,
70
- bothubUrl,
71
- webhookUrl,
72
- transport,
73
- config: merged
74
- };
75
- }
76
- //#endregion
77
20
  //#region extensions/vama/src/host-pairing-access.ts
78
21
  /** Scope pairing store operations to one channel/account pair for plugin-facing helpers. */
79
22
  function createScopedPairingAccess(params) {
@@ -709,264 +652,6 @@ async function handleVamaMessage(params) {
709
652
  }
710
653
  }
711
654
  //#endregion
712
- //#region extensions/vama/src/ws.ts
713
- /**
714
- * WebSocket transport for inbound BotHub events.
715
- *
716
- * BotHub exposes `GET /v1/bot/ws` (Authorization: Bot <token>). When a bot
717
- * has `websocket_enabled` set server-side, BotHub prefers delivering events
718
- * over the live WebSocket and only falls back to the registered webhook when
719
- * no connection is up. Frames are the exact same JSON envelopes as webhook
720
- * POST bodies (`{type, timestamp, bot_id, data}`) — no HMAC headers, since
721
- * authentication already happened at the upgrade.
722
- *
723
- * Why this exists: a connected WebSocket needs NO public URL — no tunnel,
724
- * no reverse proxy, works behind any NAT. That makes it the easiest possible
725
- * "bring your own claw" transport. The flag is off by default server-side,
726
- * so this client is written to probe first and fall back to webhook-only
727
- * silently — the day BotHub enables WebSocket for a bot, the very next
728
- * gateway start picks it up with zero config changes.
729
- *
730
- * Server behavior this client is built against (BotHub `internal/ws/hub.go`):
731
- * - server pings every 30s, expects a pong within 60s (undici auto-pongs)
732
- * - client frames are ignored (bots send via REST) — we never send
733
- * - a new connection for the same bot replaces the old one server-side
734
- */
735
- /** Reconnect schedule. Index advances per consecutive failure, clamps at the tail. */
736
- const DEFAULT_BACKOFF_MS = [
737
- 1e3,
738
- 2e3,
739
- 5e3,
740
- 1e4,
741
- 3e4
742
- ];
743
- function resolveVamaWsUrl(bothubUrl) {
744
- const base = new URL(bothubUrl);
745
- base.protocol = base.protocol === "http:" ? "ws:" : "wss:";
746
- base.pathname = `${base.pathname.replace(/\/$/, "")}/v1/bot/ws`;
747
- base.search = "";
748
- base.hash = "";
749
- return base.toString();
750
- }
751
- /**
752
- * Plain authed GET against the WS endpoint to learn whether an upgrade can
753
- * ever succeed, without burning a real upgrade attempt. The server checks
754
- * token auth and `websocket_enabled` BEFORE upgrading, so a non-upgrade GET
755
- * deterministically distinguishes "would work" (400/426 from the upgrader
756
- * rejecting a plain GET) from "will never work" (403 disabled / 401 / 404).
757
- *
758
- * Throws on transport errors — callers treat those as retryable.
759
- */
760
- async function checkVamaWebSocketSupport(account, deps = {}) {
761
- const fetchImpl = deps.fetchImpl ?? fetch;
762
- const url = new URL(account.bothubUrl ?? "https://bothub.vama.com");
763
- url.pathname = `${url.pathname.replace(/\/$/, "")}/v1/bot/ws`;
764
- const res = await fetchImpl(url.toString(), {
765
- method: "GET",
766
- headers: { Authorization: `Bot ${account.botToken ?? ""}` }
767
- });
768
- const body = await res.text().catch(() => "");
769
- if (res.status === 400 || res.status === 426) return { supported: true };
770
- if (res.status === 403) {
771
- let code;
772
- try {
773
- code = JSON.parse(body).error;
774
- } catch {}
775
- return {
776
- supported: false,
777
- reason: code === "websocket_disabled" ? "disabled" : "http_403"
778
- };
779
- }
780
- if (res.status === 401) return {
781
- supported: false,
782
- reason: "unauthorized"
783
- };
784
- if (res.status === 404) return {
785
- supported: false,
786
- reason: "unsupported"
787
- };
788
- return {
789
- supported: false,
790
- reason: `http_${res.status}`
791
- };
792
- }
793
- function sleep$1(ms, abortSignal) {
794
- return new Promise((resolve) => {
795
- if (ms <= 0 || abortSignal?.aborted) {
796
- resolve();
797
- return;
798
- }
799
- const timer = setTimeout(() => {
800
- abortSignal?.removeEventListener("abort", onAbort);
801
- resolve();
802
- }, ms);
803
- const onAbort = () => {
804
- clearTimeout(timer);
805
- resolve();
806
- };
807
- abortSignal?.addEventListener("abort", onAbort, { once: true });
808
- });
809
- }
810
- /**
811
- * Starts the WebSocket transport loop. Never throws; the loop ends either on
812
- * abort, or when the preflight rules WS out (caller stays on webhook
813
- * delivery — BotHub falls back server-side automatically).
814
- */
815
- function startVamaWebSocket(opts) {
816
- const { account, onEvent, log, error } = opts;
817
- const accountId = account.accountId;
818
- const mode = opts.mode ?? "auto";
819
- const backoff = opts.backoffMs ?? DEFAULT_BACKOFF_MS;
820
- const WebSocketCtor = opts.deps?.webSocketCtor ?? WebSocket;
821
- let state = "connecting";
822
- let active;
823
- const abort = new AbortController();
824
- if (opts.abortSignal?.aborted) abort.abort();
825
- else opts.abortSignal?.addEventListener("abort", () => abort.abort(), { once: true });
826
- const aborted = () => abort.signal.aborted;
827
- const connectOnce = () => new Promise((resolve) => {
828
- const ws = new WebSocketCtor(resolveVamaWsUrl(account.bothubUrl ?? "https://bothub.vama.com"), { headers: { Authorization: `Bot ${account.botToken ?? ""}` } });
829
- active = ws;
830
- let opened = false;
831
- let settled = false;
832
- const settle = () => {
833
- if (!settled) {
834
- settled = true;
835
- active = void 0;
836
- resolve({ opened });
837
- }
838
- };
839
- ws.addEventListener("open", () => {
840
- opened = true;
841
- state = "open";
842
- log(`vama[${accountId}]: WebSocket connected to BotHub — events arrive in real time`);
843
- });
844
- ws.addEventListener("message", (ev) => {
845
- let parsed;
846
- try {
847
- parsed = JSON.parse(String(ev.data));
848
- } catch {
849
- error(`vama[${accountId}]: ignoring malformed WebSocket frame`);
850
- return;
851
- }
852
- try {
853
- onEvent(parsed);
854
- } catch (err) {
855
- error(`vama[${accountId}]: WebSocket event handler error: ${String(err)}`);
856
- }
857
- });
858
- ws.addEventListener("error", () => {});
859
- ws.addEventListener("close", () => settle());
860
- });
861
- const loop = async () => {
862
- let failures = 0;
863
- while (!aborted()) {
864
- let preflight;
865
- try {
866
- preflight = await checkVamaWebSocketSupport(account, opts.deps);
867
- } catch (err) {
868
- failures += 1;
869
- const delay = backoff[Math.min(failures - 1, backoff.length - 1)];
870
- if (failures === 1) log(`vama[${accountId}]: WebSocket preflight failed (${err instanceof Error ? err.message : String(err)}); retrying in ${Math.round(delay / 1e3)}s`);
871
- await sleep$1(delay, abort.signal);
872
- continue;
873
- }
874
- if (!preflight.supported) {
875
- if (mode === "websocket") error(`vama[${accountId}]: transport is set to "websocket" but BotHub refused (${preflight.reason}). ` + (preflight.reason === "disabled" ? "WebSocket delivery is not enabled for this bot — falling back to webhook delivery. Remove transport or set it to \"auto\"." : "Falling back to webhook delivery."));
876
- else if (preflight.reason !== "disabled") log(`vama[${accountId}]: WebSocket unavailable (${preflight.reason}); using webhook delivery`);
877
- state = "fallback";
878
- return;
879
- }
880
- if (aborted()) break;
881
- state = "connecting";
882
- const { opened } = await connectOnce();
883
- if (aborted()) break;
884
- if (opened) {
885
- failures = 0;
886
- log(`vama[${accountId}]: WebSocket closed; reconnecting`);
887
- } else failures += 1;
888
- const delay = opened ? backoff[0] : backoff[Math.min(failures - 1, backoff.length - 1)];
889
- await sleep$1(Math.round(delay * (.8 + Math.random() * .4)), abort.signal);
890
- }
891
- state = "stopped";
892
- };
893
- return {
894
- state: () => state,
895
- done: loop().catch((err) => {
896
- state = "stopped";
897
- error(`vama[${accountId}]: WebSocket transport loop crashed: ${String(err)}`);
898
- }),
899
- stop: () => {
900
- abort.abort();
901
- try {
902
- active?.close();
903
- } catch {}
904
- }
905
- };
906
- }
907
- //#endregion
908
- //#region extensions/vama/src/probe.ts
909
- const probeCache = /* @__PURE__ */ new Map();
910
- const PROBE_CACHE_TTL_MS = 600 * 1e3;
911
- const MAX_PROBE_CACHE_SIZE = 64;
912
- async function probeVama(account) {
913
- if (!account.botToken) return {
914
- ok: false,
915
- error: "missing credentials (botToken)"
916
- };
917
- const cacheKey = account.accountId;
918
- const cached = probeCache.get(cacheKey);
919
- if (cached && cached.expiresAt > Date.now()) return cached.result;
920
- try {
921
- const me = await createBotHubClient(account).getMe();
922
- const botId = me.bot_id;
923
- const registeredUrl = me.webhook_url?.trim() || void 0;
924
- if (!registeredUrl) {
925
- let wsEnabled = false;
926
- if (account.transport !== "webhook") try {
927
- wsEnabled = (await checkVamaWebSocketSupport(account)).supported;
928
- } catch {}
929
- if (!wsEnabled) return {
930
- ok: false,
931
- botId,
932
- error: "bot token is valid, but no webhook URL is registered with BotHub — Vama cannot deliver messages to this gateway (the agent shows \"Awaiting claw\"). Set channels.vama.webhookUrl to your gateway's public URL (e.g. https://your-host/vama/events) and restart the gateway, or re-run `openclaw onboard`."
933
- };
934
- const wsResult = {
935
- ok: true,
936
- botId,
937
- wsEnabled: true
938
- };
939
- probeCache.set(cacheKey, {
940
- result: wsResult,
941
- expiresAt: Date.now() + PROBE_CACHE_TTL_MS
942
- });
943
- return wsResult;
944
- }
945
- const result = {
946
- ok: true,
947
- botId,
948
- webhookUrl: registeredUrl
949
- };
950
- probeCache.set(cacheKey, {
951
- result,
952
- expiresAt: Date.now() + PROBE_CACHE_TTL_MS
953
- });
954
- if (probeCache.size > MAX_PROBE_CACHE_SIZE) {
955
- const oldest = probeCache.keys().next().value;
956
- if (oldest !== void 0) probeCache.delete(oldest);
957
- }
958
- return result;
959
- } catch (err) {
960
- return {
961
- ok: false,
962
- error: err instanceof Error ? err.message : String(err)
963
- };
964
- }
965
- }
966
- function clearProbeCache() {
967
- probeCache.clear();
968
- }
969
- //#endregion
970
655
  //#region extensions/vama/src/register.ts
971
656
  /**
972
657
  * Outer retry schedule for startup webhook registration. The BotHub
@@ -1274,4 +959,4 @@ async function monitorVamaProvider(opts = {}) {
1274
959
  });
1275
960
  }
1276
961
  //#endregion
1277
- export { dispatchStarted as a, buildBaseChannelStatusSummary as c, resolveDefaultVamaAccountId as d, resolveVamaAccount as f, dispatchEnded as i, createDefaultChannelRuntimeState as l, probeVama as n, DEFAULT_ACCOUNT_ID$1 as o, sendMessageVama as r, PAIRING_APPROVED_MESSAGE as s, monitorVamaProvider as t, listVamaAccountIds as u };
962
+ export { DEFAULT_ACCOUNT_ID$1 as a, createDefaultChannelRuntimeState as c, dispatchStarted as i, sendMessageVama as n, PAIRING_APPROVED_MESSAGE as o, dispatchEnded as r, buildBaseChannelStatusSummary as s, monitorVamaProvider as t };
@@ -0,0 +1,319 @@
1
+ import { i as createBotHubClient } from "./client-BzhfASX8.js";
2
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
3
+ import { WebSocket, fetch } from "undici";
4
+ //#region extensions/vama/src/accounts.ts
5
+ function listConfiguredAccountIds(cfg) {
6
+ const accounts = (cfg.channels?.vama)?.accounts;
7
+ if (!accounts || typeof accounts !== "object") return [];
8
+ return Object.keys(accounts).filter(Boolean);
9
+ }
10
+ function listVamaAccountIds(cfg) {
11
+ const ids = listConfiguredAccountIds(cfg);
12
+ if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
13
+ return [...ids].toSorted((a, b) => a.localeCompare(b));
14
+ }
15
+ function resolveDefaultVamaAccountId(cfg) {
16
+ const ids = listVamaAccountIds(cfg);
17
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
18
+ return ids[0] ?? DEFAULT_ACCOUNT_ID;
19
+ }
20
+ function resolveAccountConfig(cfg, accountId) {
21
+ const accounts = (cfg.channels?.vama)?.accounts;
22
+ if (!accounts || typeof accounts !== "object") return;
23
+ return accounts[accountId];
24
+ }
25
+ function mergeVamaAccountConfig(cfg, accountId) {
26
+ const { accounts: _ignored, ...base } = cfg.channels?.vama ?? {};
27
+ const account = resolveAccountConfig(cfg, accountId) ?? {};
28
+ return {
29
+ ...base,
30
+ ...account
31
+ };
32
+ }
33
+ function resolveVamaAccount(params) {
34
+ const accountId = normalizeAccountId(params.accountId);
35
+ const baseEnabled = (params.cfg.channels?.vama)?.enabled !== false;
36
+ const merged = mergeVamaAccountConfig(params.cfg, accountId);
37
+ const accountEnabled = merged.enabled !== false;
38
+ const enabled = baseEnabled && accountEnabled;
39
+ const botToken = merged.botToken?.trim() || void 0;
40
+ const webhookSecret = merged.webhookSecret?.trim() || void 0;
41
+ const webhookSecretFile = merged.webhookSecretFile?.trim() || void 0;
42
+ const bothubUrl = merged.bothubUrl?.trim() || "https://bothub.vama.com";
43
+ const webhookUrl = merged.webhookUrl?.trim() || void 0;
44
+ const rawTransport = merged.transport?.trim();
45
+ const transport = rawTransport === "auto" || rawTransport === "webhook" || rawTransport === "websocket" ? rawTransport : void 0;
46
+ return {
47
+ accountId,
48
+ enabled,
49
+ configured: Boolean(botToken),
50
+ name: merged.name?.trim() || void 0,
51
+ botToken,
52
+ webhookSecret,
53
+ webhookSecretFile,
54
+ bothubUrl,
55
+ webhookUrl,
56
+ transport,
57
+ config: merged
58
+ };
59
+ }
60
+ //#endregion
61
+ //#region extensions/vama/src/ws.ts
62
+ /**
63
+ * WebSocket transport for inbound BotHub events.
64
+ *
65
+ * BotHub exposes `GET /v1/bot/ws` (Authorization: Bot <token>). When a bot
66
+ * has `websocket_enabled` set server-side, BotHub prefers delivering events
67
+ * over the live WebSocket and only falls back to the registered webhook when
68
+ * no connection is up. Frames are the exact same JSON envelopes as webhook
69
+ * POST bodies (`{type, timestamp, bot_id, data}`) — no HMAC headers, since
70
+ * authentication already happened at the upgrade.
71
+ *
72
+ * Why this exists: a connected WebSocket needs NO public URL — no tunnel,
73
+ * no reverse proxy, works behind any NAT. That makes it the easiest possible
74
+ * "bring your own claw" transport. The flag is off by default server-side,
75
+ * so this client is written to probe first and fall back to webhook-only
76
+ * silently — the day BotHub enables WebSocket for a bot, the very next
77
+ * gateway start picks it up with zero config changes.
78
+ *
79
+ * Server behavior this client is built against (BotHub `internal/ws/hub.go`):
80
+ * - server pings every 30s, expects a pong within 60s (undici auto-pongs)
81
+ * - client frames are ignored (bots send via REST) — we never send
82
+ * - a new connection for the same bot replaces the old one server-side
83
+ */
84
+ /** Reconnect schedule. Index advances per consecutive failure, clamps at the tail. */
85
+ const DEFAULT_BACKOFF_MS = [
86
+ 1e3,
87
+ 2e3,
88
+ 5e3,
89
+ 1e4,
90
+ 3e4
91
+ ];
92
+ function resolveVamaWsUrl(bothubUrl) {
93
+ const base = new URL(bothubUrl);
94
+ base.protocol = base.protocol === "http:" ? "ws:" : "wss:";
95
+ base.pathname = `${base.pathname.replace(/\/$/, "")}/v1/bot/ws`;
96
+ base.search = "";
97
+ base.hash = "";
98
+ return base.toString();
99
+ }
100
+ /**
101
+ * Plain authed GET against the WS endpoint to learn whether an upgrade can
102
+ * ever succeed, without burning a real upgrade attempt. The server checks
103
+ * token auth and `websocket_enabled` BEFORE upgrading, so a non-upgrade GET
104
+ * deterministically distinguishes "would work" (400/426 from the upgrader
105
+ * rejecting a plain GET) from "will never work" (403 disabled / 401 / 404).
106
+ *
107
+ * Throws on transport errors — callers treat those as retryable.
108
+ */
109
+ async function checkVamaWebSocketSupport(account, deps = {}) {
110
+ const fetchImpl = deps.fetchImpl ?? fetch;
111
+ const url = new URL(account.bothubUrl ?? "https://bothub.vama.com");
112
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/v1/bot/ws`;
113
+ const res = await fetchImpl(url.toString(), {
114
+ method: "GET",
115
+ headers: { Authorization: `Bot ${account.botToken ?? ""}` }
116
+ });
117
+ const body = await res.text().catch(() => "");
118
+ if (res.status === 400 || res.status === 426) return { supported: true };
119
+ if (res.status === 403) {
120
+ let code;
121
+ try {
122
+ code = JSON.parse(body).error;
123
+ } catch {}
124
+ return {
125
+ supported: false,
126
+ reason: code === "websocket_disabled" ? "disabled" : "http_403"
127
+ };
128
+ }
129
+ if (res.status === 401) return {
130
+ supported: false,
131
+ reason: "unauthorized"
132
+ };
133
+ if (res.status === 404) return {
134
+ supported: false,
135
+ reason: "unsupported"
136
+ };
137
+ return {
138
+ supported: false,
139
+ reason: `http_${res.status}`
140
+ };
141
+ }
142
+ function sleep(ms, abortSignal) {
143
+ return new Promise((resolve) => {
144
+ if (ms <= 0 || abortSignal?.aborted) {
145
+ resolve();
146
+ return;
147
+ }
148
+ const timer = setTimeout(() => {
149
+ abortSignal?.removeEventListener("abort", onAbort);
150
+ resolve();
151
+ }, ms);
152
+ const onAbort = () => {
153
+ clearTimeout(timer);
154
+ resolve();
155
+ };
156
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
157
+ });
158
+ }
159
+ /**
160
+ * Starts the WebSocket transport loop. Never throws; the loop ends either on
161
+ * abort, or when the preflight rules WS out (caller stays on webhook
162
+ * delivery — BotHub falls back server-side automatically).
163
+ */
164
+ function startVamaWebSocket(opts) {
165
+ const { account, onEvent, log, error } = opts;
166
+ const accountId = account.accountId;
167
+ const mode = opts.mode ?? "auto";
168
+ const backoff = opts.backoffMs ?? DEFAULT_BACKOFF_MS;
169
+ const WebSocketCtor = opts.deps?.webSocketCtor ?? WebSocket;
170
+ let state = "connecting";
171
+ let active;
172
+ const abort = new AbortController();
173
+ if (opts.abortSignal?.aborted) abort.abort();
174
+ else opts.abortSignal?.addEventListener("abort", () => abort.abort(), { once: true });
175
+ const aborted = () => abort.signal.aborted;
176
+ const connectOnce = () => new Promise((resolve) => {
177
+ const ws = new WebSocketCtor(resolveVamaWsUrl(account.bothubUrl ?? "https://bothub.vama.com"), { headers: { Authorization: `Bot ${account.botToken ?? ""}` } });
178
+ active = ws;
179
+ let opened = false;
180
+ let settled = false;
181
+ const settle = () => {
182
+ if (!settled) {
183
+ settled = true;
184
+ active = void 0;
185
+ resolve({ opened });
186
+ }
187
+ };
188
+ ws.addEventListener("open", () => {
189
+ opened = true;
190
+ state = "open";
191
+ log(`vama[${accountId}]: WebSocket connected to BotHub — events arrive in real time`);
192
+ });
193
+ ws.addEventListener("message", (ev) => {
194
+ let parsed;
195
+ try {
196
+ parsed = JSON.parse(String(ev.data));
197
+ } catch {
198
+ error(`vama[${accountId}]: ignoring malformed WebSocket frame`);
199
+ return;
200
+ }
201
+ try {
202
+ onEvent(parsed);
203
+ } catch (err) {
204
+ error(`vama[${accountId}]: WebSocket event handler error: ${String(err)}`);
205
+ }
206
+ });
207
+ ws.addEventListener("error", () => {});
208
+ ws.addEventListener("close", () => settle());
209
+ });
210
+ const loop = async () => {
211
+ let failures = 0;
212
+ while (!aborted()) {
213
+ let preflight;
214
+ try {
215
+ preflight = await checkVamaWebSocketSupport(account, opts.deps);
216
+ } catch (err) {
217
+ failures += 1;
218
+ const delay = backoff[Math.min(failures - 1, backoff.length - 1)];
219
+ if (failures === 1) log(`vama[${accountId}]: WebSocket preflight failed (${err instanceof Error ? err.message : String(err)}); retrying in ${Math.round(delay / 1e3)}s`);
220
+ await sleep(delay, abort.signal);
221
+ continue;
222
+ }
223
+ if (!preflight.supported) {
224
+ if (mode === "websocket") error(`vama[${accountId}]: transport is set to "websocket" but BotHub refused (${preflight.reason}). ` + (preflight.reason === "disabled" ? "WebSocket delivery is not enabled for this bot — falling back to webhook delivery. Remove transport or set it to \"auto\"." : "Falling back to webhook delivery."));
225
+ else if (preflight.reason !== "disabled") log(`vama[${accountId}]: WebSocket unavailable (${preflight.reason}); using webhook delivery`);
226
+ state = "fallback";
227
+ return;
228
+ }
229
+ if (aborted()) break;
230
+ state = "connecting";
231
+ const { opened } = await connectOnce();
232
+ if (aborted()) break;
233
+ if (opened) {
234
+ failures = 0;
235
+ log(`vama[${accountId}]: WebSocket closed; reconnecting`);
236
+ } else failures += 1;
237
+ const delay = opened ? backoff[0] : backoff[Math.min(failures - 1, backoff.length - 1)];
238
+ await sleep(Math.round(delay * (.8 + Math.random() * .4)), abort.signal);
239
+ }
240
+ state = "stopped";
241
+ };
242
+ return {
243
+ state: () => state,
244
+ done: loop().catch((err) => {
245
+ state = "stopped";
246
+ error(`vama[${accountId}]: WebSocket transport loop crashed: ${String(err)}`);
247
+ }),
248
+ stop: () => {
249
+ abort.abort();
250
+ try {
251
+ active?.close();
252
+ } catch {}
253
+ }
254
+ };
255
+ }
256
+ //#endregion
257
+ //#region extensions/vama/src/probe.ts
258
+ const probeCache = /* @__PURE__ */ new Map();
259
+ const PROBE_CACHE_TTL_MS = 600 * 1e3;
260
+ const MAX_PROBE_CACHE_SIZE = 64;
261
+ async function probeVama(account) {
262
+ if (!account.botToken) return {
263
+ ok: false,
264
+ error: "missing credentials (botToken)"
265
+ };
266
+ const cacheKey = account.accountId;
267
+ const cached = probeCache.get(cacheKey);
268
+ if (cached && cached.expiresAt > Date.now()) return cached.result;
269
+ try {
270
+ const me = await createBotHubClient(account).getMe();
271
+ const botId = me.bot_id;
272
+ const registeredUrl = me.webhook_url?.trim() || void 0;
273
+ if (!registeredUrl) {
274
+ let wsEnabled = false;
275
+ if (account.transport !== "webhook") try {
276
+ wsEnabled = (await checkVamaWebSocketSupport(account)).supported;
277
+ } catch {}
278
+ if (!wsEnabled) return {
279
+ ok: false,
280
+ botId,
281
+ error: "bot token is valid, but no webhook URL is registered with BotHub — Vama cannot deliver messages to this gateway (the agent shows \"Awaiting claw\"). Set channels.vama.webhookUrl to your gateway's public URL (e.g. https://your-host/vama/events) and restart the gateway, or re-run `openclaw onboard`."
282
+ };
283
+ const wsResult = {
284
+ ok: true,
285
+ botId,
286
+ wsEnabled: true
287
+ };
288
+ probeCache.set(cacheKey, {
289
+ result: wsResult,
290
+ expiresAt: Date.now() + PROBE_CACHE_TTL_MS
291
+ });
292
+ return wsResult;
293
+ }
294
+ const result = {
295
+ ok: true,
296
+ botId,
297
+ webhookUrl: registeredUrl
298
+ };
299
+ probeCache.set(cacheKey, {
300
+ result,
301
+ expiresAt: Date.now() + PROBE_CACHE_TTL_MS
302
+ });
303
+ if (probeCache.size > MAX_PROBE_CACHE_SIZE) {
304
+ const oldest = probeCache.keys().next().value;
305
+ if (oldest !== void 0) probeCache.delete(oldest);
306
+ }
307
+ return result;
308
+ } catch (err) {
309
+ return {
310
+ ok: false,
311
+ error: err instanceof Error ? err.message : String(err)
312
+ };
313
+ }
314
+ }
315
+ function clearProbeCache() {
316
+ probeCache.clear();
317
+ }
318
+ //#endregion
319
+ export { resolveDefaultVamaAccountId as a, listVamaAccountIds as i, probeVama as n, resolveVamaAccount as o, startVamaWebSocket as r, clearProbeCache as t };
@@ -1,2 +1,2 @@
1
- import { n as setVamaRuntime } from "./runtime-w-1oL50p.js";
1
+ import { n as setVamaRuntime } from "./runtime-Cq09Y5cd.js";
2
2
  export { setVamaRuntime };
@@ -37,6 +37,15 @@
37
37
  "type": "string",
38
38
  "description": "BotHub base URL. Leave blank to use the default."
39
39
  },
40
+ "webhookUrl": {
41
+ "type": "string",
42
+ "description": "Public URL BotHub should deliver webhook events to. Registered automatically on every gateway start when set."
43
+ },
44
+ "transport": {
45
+ "type": "string",
46
+ "enum": ["auto", "webhook", "websocket"],
47
+ "description": "Inbound event transport. auto (default) probes for WebSocket delivery and falls back to webhook; websocket requires it; webhook never attempts it."
48
+ },
40
49
  "webhookPath": {
41
50
  "type": "string",
42
51
  "description": "HTTP path the gateway serves for inbound BotHub webhook events."
@@ -80,6 +89,8 @@
80
89
  "webhookSecret": { "type": "string" },
81
90
  "webhookSecretFile": { "type": "string" },
82
91
  "bothubUrl": { "type": "string" },
92
+ "webhookUrl": { "type": "string" },
93
+ "transport": { "type": "string", "enum": ["auto", "webhook", "websocket"] },
83
94
  "webhookPath": { "type": "string" },
84
95
  "webhookPort": { "type": "integer", "minimum": 1, "maximum": 65535 },
85
96
  "webhookHost": { "type": "string" }
@@ -115,6 +126,16 @@
115
126
  "label": "BotHub URL",
116
127
  "help": "Leave blank to use the default Vama BotHub endpoint."
117
128
  },
129
+ "webhookUrl": {
130
+ "label": "Public webhook URL",
131
+ "help": "Only needed for webhook transport. WebSocket-connected bots need no public URL.",
132
+ "advanced": true
133
+ },
134
+ "transport": {
135
+ "label": "Transport",
136
+ "help": "auto picks WebSocket when BotHub allows it, else webhook.",
137
+ "advanced": true
138
+ },
118
139
  "webhookPath": {
119
140
  "label": "Webhook path",
120
141
  "advanced": true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vama/openclaw",
3
- "version": "2026.5.5-4",
3
+ "version": "2026.5.5-5",
4
4
  "description": "OpenClaw Vama channel plugin via BotHub",
5
5
  "homepage": "https://web.vama.com/connect-guide",
6
6
  "repository": {