@vama/openclaw 2026.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.
@@ -0,0 +1,367 @@
1
+ import { promises } from "node:fs";
2
+ import { basename, extname } from "node:path";
3
+ import { Agent, FormData, fetch } from "undici";
4
+ //#region \0rolldown/runtime.js
5
+ var __defProp = Object.defineProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ return target;
14
+ };
15
+ //#endregion
16
+ //#region extensions/vama/src/client.ts
17
+ var client_exports = /* @__PURE__ */ __exportAll({
18
+ BOTHUB_DEFAULT_URL: () => BOTHUB_DEFAULT_URL,
19
+ FetchWithRetryError: () => FetchWithRetryError,
20
+ attachmentHintFromExtension: () => attachmentHintFromExtension,
21
+ createBotHubClient: () => createBotHubClient,
22
+ mimeFromExtension: () => mimeFromExtension,
23
+ provisionBot: () => provisionBot
24
+ });
25
+ /** Canonical BotHub API endpoint. Can be overridden via channels.vama.bothubUrl for self-hosted deployments. */
26
+ const BOTHUB_DEFAULT_URL = "https://bothub.vama.com";
27
+ const botHubDispatcher = new Agent({
28
+ keepAliveTimeout: 1e4,
29
+ keepAliveMaxTimeout: 3e4,
30
+ connectTimeout: 5e3
31
+ });
32
+ /**
33
+ * Thrown by `fetchWithRetry` when all attempts fail. Carries the number
34
+ * of attempts actually made so the caller can log it accurately —
35
+ * previously the catch path hardcoded `attemptsUsed: 3`, which silently
36
+ * broke if anyone passed a non-default `attempts` value or refactored
37
+ * the throw path. Wrapping `cause` matches the convention `request<T>`
38
+ * uses for its own thrown errors.
39
+ */
40
+ var FetchWithRetryError = class extends Error {
41
+ constructor(cause, attemptsUsed) {
42
+ const causeMsg = cause instanceof Error ? cause.message : String(cause);
43
+ super(causeMsg);
44
+ this.name = "FetchWithRetryError";
45
+ this.attemptsUsed = attemptsUsed;
46
+ this.cause = cause;
47
+ }
48
+ };
49
+ async function fetchWithRetry(url, init, attempts = 3) {
50
+ let lastErr;
51
+ for (let i = 1; i <= attempts; i++) try {
52
+ return {
53
+ res: await fetch(url, {
54
+ ...init,
55
+ dispatcher: botHubDispatcher
56
+ }),
57
+ attemptsUsed: i
58
+ };
59
+ } catch (err) {
60
+ lastErr = err;
61
+ if (i === attempts) throw new FetchWithRetryError(lastErr, i);
62
+ await new Promise((resolve) => setTimeout(resolve, 200 * i));
63
+ }
64
+ throw new FetchWithRetryError(lastErr, attempts);
65
+ }
66
+ /** Truncate to 200 chars and signal it was cut. Used by error log lines
67
+ * so a reader knows there's more to the body than what's printed. */
68
+ function truncateForLog(text) {
69
+ return text.length > 200 ? text.slice(0, 200) + "…" : text;
70
+ }
71
+ /**
72
+ * Best-effort extension → MIME map for outbound file uploads. Kept minimal
73
+ * on purpose: the message-service detects the real MIME from magic bytes
74
+ * during upload and overrides whatever we send, so the only thing this
75
+ * affects is the Content-Type header on the multipart Blob — useful for
76
+ * downstream debugging tools and HTTP intermediaries but not load-bearing.
77
+ * Unknown extensions fall through to "application/octet-stream" upstream.
78
+ */
79
+ function mimeFromExtension(ext) {
80
+ switch (ext) {
81
+ case "jpg":
82
+ case "jpeg": return "image/jpeg";
83
+ case "png": return "image/png";
84
+ case "webp": return "image/webp";
85
+ case "gif": return "image/gif";
86
+ case "heic": return "image/heic";
87
+ case "heif": return "image/heif";
88
+ case "mp4":
89
+ case "m4v": return "video/mp4";
90
+ case "mov": return "video/quicktime";
91
+ case "webm": return "video/webm";
92
+ case "mp3": return "audio/mpeg";
93
+ case "m4a":
94
+ case "aac": return "audio/mp4";
95
+ case "wav": return "audio/wav";
96
+ case "ogg":
97
+ case "opus": return "audio/ogg";
98
+ case "flac": return "audio/flac";
99
+ case "pdf": return "application/pdf";
100
+ case "txt":
101
+ case "md": return "text/plain";
102
+ case "json": return "application/json";
103
+ case "html":
104
+ case "htm": return "text/html";
105
+ case "csv": return "text/csv";
106
+ case "zip": return "application/zip";
107
+ default: return;
108
+ }
109
+ }
110
+ /** Map our internal extension hint to BotHub's `attachment_type` enum. */
111
+ function attachmentHintFromExtension(ext) {
112
+ switch (ext) {
113
+ case "jpg":
114
+ case "jpeg":
115
+ case "png":
116
+ case "webp":
117
+ case "heic":
118
+ case "heif": return "image";
119
+ case "gif": return "gif";
120
+ case "mp4":
121
+ case "mov":
122
+ case "webm":
123
+ case "m4v": return "video";
124
+ case "mp3":
125
+ case "m4a":
126
+ case "wav":
127
+ case "ogg":
128
+ case "opus":
129
+ case "flac":
130
+ case "aac": return "audio";
131
+ default: return "file";
132
+ }
133
+ }
134
+ /** Unwrap the inner transport error message from a thrown value, peeling
135
+ * through `FetchWithRetryError.cause` when present. Shared by the two
136
+ * transport-error catch paths so the unwrap stays consistent if the
137
+ * retry-error shape ever changes. */
138
+ function unwrapFetchErrorMsg(err) {
139
+ const inner = err instanceof FetchWithRetryError ? err.cause : err;
140
+ return inner instanceof Error ? inner.message : String(inner);
141
+ }
142
+ /** Read the actual `attemptsUsed` from a thrown value. Falls back to 1 for
143
+ * non-retry errors (e.g. a sync throw before `fetchWithRetry` ran). */
144
+ function attemptsUsedFromError(err) {
145
+ return err instanceof FetchWithRetryError ? err.attemptsUsed : 1;
146
+ }
147
+ /**
148
+ * Log a single BotHub round-trip with timing, status, retry count and
149
+ * (where applicable) message_id. Shared by `request<T>` (per-message
150
+ * authenticated calls) and `provisionBot` (one-off unauthenticated
151
+ * provisioning) so neither call site is missed.
152
+ *
153
+ * Three severity levels:
154
+ * - error: transport throw OR non-2xx HTTP status
155
+ * - warn: slow success (> 500ms) OR success after a retry
156
+ * - log: fast happy path
157
+ */
158
+ function logBothubCall(opts) {
159
+ const elapsed = Date.now() - opts.start;
160
+ const retrySuffix = opts.attemptsUsed > 1 ? ` retries=${opts.attemptsUsed - 1}` : "";
161
+ const idSuffix = opts.messageId ? ` message_id=${opts.messageId}` : "";
162
+ if (opts.status === null) {
163
+ console.error(`[bothub] ${opts.method} ${opts.path} TRANSPORT_ERROR ${elapsed}ms${retrySuffix}: ${opts.errorMsg ?? ""}`);
164
+ return;
165
+ }
166
+ if (opts.status < 200 || opts.status >= 300) {
167
+ console.error(`[bothub] ${opts.method} ${opts.path} ${opts.status} ${elapsed}ms${retrySuffix}: ${truncateForLog(opts.bodyText ?? "")}`);
168
+ return;
169
+ }
170
+ if (elapsed > 500 || opts.attemptsUsed > 1) {
171
+ console.warn(`[bothub] ${opts.method} ${opts.path} ${opts.status} ${elapsed}ms${retrySuffix}${idSuffix}`);
172
+ return;
173
+ }
174
+ console.log(`[bothub] ${opts.method} ${opts.path} ${opts.status} ${elapsed}ms${idSuffix}`);
175
+ }
176
+ function createBotHubClient(account) {
177
+ const { botToken, bothubUrl } = account;
178
+ if (!botToken || !bothubUrl) throw new Error(`Vama account "${account.accountId}" missing botToken or bothubUrl`);
179
+ const baseUrl = bothubUrl.replace(/\/+$/, "");
180
+ async function request(method, path, body) {
181
+ const url = `${baseUrl}${path}`;
182
+ const headers = {
183
+ Authorization: `Bot ${botToken}`,
184
+ "Content-Type": "application/json"
185
+ };
186
+ const start = Date.now();
187
+ let resPair;
188
+ try {
189
+ resPair = await fetchWithRetry(url, {
190
+ method,
191
+ headers,
192
+ body: body ? JSON.stringify(body) : void 0
193
+ });
194
+ } catch (err) {
195
+ logBothubCall({
196
+ method,
197
+ path,
198
+ start,
199
+ status: null,
200
+ attemptsUsed: attemptsUsedFromError(err),
201
+ errorMsg: unwrapFetchErrorMsg(err)
202
+ });
203
+ throw err;
204
+ }
205
+ const { res, attemptsUsed } = resPair;
206
+ if (!res.ok) {
207
+ const text = await res.text().catch(() => "");
208
+ logBothubCall({
209
+ method,
210
+ path,
211
+ start,
212
+ status: res.status,
213
+ attemptsUsed,
214
+ bodyText: text
215
+ });
216
+ throw new Error(`BotHub API ${method} ${path} failed (${res.status}): ${text}`);
217
+ }
218
+ const json = await res.json();
219
+ const messageId = typeof json === "object" && json !== null && "message_id" in json ? json.message_id : void 0;
220
+ logBothubCall({
221
+ method,
222
+ path,
223
+ start,
224
+ status: res.status,
225
+ attemptsUsed,
226
+ messageId: typeof messageId === "string" ? messageId : void 0
227
+ });
228
+ return json;
229
+ }
230
+ return {
231
+ async sendMessage(params) {
232
+ const body = { text: params.text };
233
+ if (params.channelId) body.channel_id = params.channelId;
234
+ if (params.userId) body.user_id = params.userId;
235
+ if (params.parentId) body.parent_id = params.parentId;
236
+ if (params.idempotencyKey) body.idempotency_key = params.idempotencyKey;
237
+ return request("POST", "/v1/bot/messages", body);
238
+ },
239
+ async sendTyping(params) {
240
+ await request("POST", "/v1/bot/typing", { channel_id: params.channelId });
241
+ },
242
+ async sendFile(params) {
243
+ const path = "/v1/bot/messages/file";
244
+ const url = `${baseUrl}${path}`;
245
+ let bytes;
246
+ try {
247
+ bytes = await promises.readFile(params.path);
248
+ } catch (err) {
249
+ const msg = err instanceof Error ? err.message : String(err);
250
+ throw new Error(`Vama sendFile: failed to read ${params.path}: ${msg}`, { cause: err });
251
+ }
252
+ const fileName = params.fileName ?? basename(params.path) ?? "attachment";
253
+ const ext = extname(fileName).replace(/^\./, "").toLowerCase();
254
+ const contentType = params.contentType ?? mimeFromExtension(ext) ?? "application/octet-stream";
255
+ const form = new FormData();
256
+ form.append("file", new Blob([bytes], { type: contentType }), fileName);
257
+ if (params.channelId) form.append("channel_id", params.channelId);
258
+ if (params.userId) form.append("user_id", params.userId);
259
+ if (params.caption !== void 0 && params.caption !== "") form.append("caption", params.caption);
260
+ if (params.parentId) form.append("parent_id", params.parentId);
261
+ if (params.idempotencyKey) form.append("idempotency_key", params.idempotencyKey);
262
+ if (params.attachmentType) form.append("attachment_type", params.attachmentType);
263
+ const start = Date.now();
264
+ let resPair;
265
+ try {
266
+ resPair = await fetchWithRetry(url, {
267
+ method: "POST",
268
+ headers: { Authorization: `Bot ${botToken}` },
269
+ body: form
270
+ });
271
+ } catch (err) {
272
+ logBothubCall({
273
+ method: "POST",
274
+ path,
275
+ start,
276
+ status: null,
277
+ attemptsUsed: attemptsUsedFromError(err),
278
+ errorMsg: unwrapFetchErrorMsg(err)
279
+ });
280
+ throw err;
281
+ }
282
+ const { res, attemptsUsed } = resPair;
283
+ if (!res.ok) {
284
+ const text = await res.text().catch(() => "");
285
+ logBothubCall({
286
+ method: "POST",
287
+ path,
288
+ start,
289
+ status: res.status,
290
+ attemptsUsed,
291
+ bodyText: text
292
+ });
293
+ throw new Error(`BotHub API POST ${path} failed (${res.status}): ${text}`);
294
+ }
295
+ const json = await res.json();
296
+ logBothubCall({
297
+ method: "POST",
298
+ path,
299
+ start,
300
+ status: res.status,
301
+ attemptsUsed,
302
+ messageId: json.message_id
303
+ });
304
+ return json;
305
+ },
306
+ async getMe() {
307
+ return request("GET", "/v1/bot/me");
308
+ },
309
+ async registerWebhook(params) {
310
+ return request("PUT", "/v1/bot/webhook", { url: params.url });
311
+ }
312
+ };
313
+ }
314
+ /**
315
+ * Provision a new bot via the BotHub provisioning API (unauthenticated).
316
+ */
317
+ async function provisionBot(params) {
318
+ const baseUrl = params.bothubUrl.replace(/\/+$/, "");
319
+ const body = {
320
+ target_username: params.targetUsername,
321
+ platform: params.platform ?? "openclaw"
322
+ };
323
+ if (params.displayName) body.display_name = params.displayName;
324
+ const path = "/v1/provision/create";
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(body)
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
+ throw new Error(`BotHub provisioning failed (${res.status}): ${text}`);
356
+ }
357
+ logBothubCall({
358
+ method: "POST",
359
+ path,
360
+ start,
361
+ status: res.status,
362
+ attemptsUsed
363
+ });
364
+ return await res.json();
365
+ }
366
+ //#endregion
367
+ export { provisionBot as a, createBotHubClient as i, attachmentHintFromExtension as n, client_exports as r, BOTHUB_DEFAULT_URL as t };
package/dist/index.js ADDED
@@ -0,0 +1,55 @@
1
+ import { n as monitorVamaProvider, r as sendMessageVama, t as probeVama } from "./probe-B2hFOc2Y.js";
2
+ import { n as setVamaRuntime } from "./runtime-w-1oL50p.js";
3
+ import { t as registerVamaSubagentKeepaliveHooks } from "./api-C0vtNv5b.js";
4
+ import { t as vamaPlugin } from "./channel-plugin-api-CcZ_y9pT.js";
5
+ import "./runtime-api.js";
6
+ //#region extensions/vama/index.ts
7
+ const channelEntry = {
8
+ kind: "bundled-channel-entry",
9
+ id: "vama",
10
+ name: "Vama",
11
+ description: "Vama channel plugin via BotHub",
12
+ configSchema: {
13
+ schema: {
14
+ type: "object",
15
+ additionalProperties: false,
16
+ properties: {}
17
+ },
18
+ runtime: { safeParse(value) {
19
+ if (value === void 0) return {
20
+ success: true,
21
+ data: void 0
22
+ };
23
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {
24
+ success: false,
25
+ issues: [{
26
+ path: [],
27
+ message: "expected config object"
28
+ }]
29
+ };
30
+ if (Object.keys(value).length > 0) return {
31
+ success: false,
32
+ issues: [{
33
+ path: [],
34
+ message: "config must be empty"
35
+ }]
36
+ };
37
+ return {
38
+ success: true,
39
+ data: value
40
+ };
41
+ } }
42
+ },
43
+ register(api) {
44
+ if (api.registrationMode === "cli-metadata") return;
45
+ setVamaRuntime(api.runtime);
46
+ api.registerChannel({ plugin: vamaPlugin });
47
+ registerVamaSubagentKeepaliveHooks(api);
48
+ },
49
+ loadChannelPlugin: () => vamaPlugin,
50
+ setChannelRuntime: (runtime) => {
51
+ setVamaRuntime(runtime);
52
+ }
53
+ };
54
+ //#endregion
55
+ export { channelEntry as default, monitorVamaProvider, probeVama, sendMessageVama, vamaPlugin };