@vama/openclaw 2026.5.5-5 → 2026.5.5-6
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 +5 -0
- package/channel-plugin-api.ts +1 -0
- package/index.ts +88 -0
- package/package.json +1 -17
- package/runtime-api.ts +2 -0
- package/src/accounts.test.ts +236 -0
- package/src/accounts.ts +96 -0
- package/src/bot.test.ts +520 -0
- package/src/bot.ts +345 -0
- package/src/channel-actions.test.ts +289 -0
- package/src/channel-actions.ts +242 -0
- package/src/channel.test.ts +150 -0
- package/src/channel.ts +256 -0
- package/src/cli-metadata.ts +19 -0
- package/src/cli.test.ts +311 -0
- package/src/cli.ts +341 -0
- package/src/client.test.ts +550 -0
- package/src/client.ts +685 -0
- package/src/connect-code.test.ts +98 -0
- package/src/connect-code.ts +108 -0
- package/src/connect-verify.test.ts +210 -0
- package/src/connect-verify.ts +201 -0
- package/src/dedup.ts +53 -0
- package/src/fc-keepalive.test.ts +200 -0
- package/src/fc-keepalive.ts +184 -0
- package/src/host-pairing-access.ts +48 -0
- package/src/host-sdk.ts +43 -0
- package/src/monitor-secret-file.test.ts +151 -0
- package/src/monitor-ws.test.ts +155 -0
- package/src/monitor.ts +344 -0
- package/src/outbound.ts +92 -0
- package/src/probe.test.ts +191 -0
- package/src/probe.ts +90 -0
- package/src/register.test.ts +158 -0
- package/src/register.ts +116 -0
- package/src/reply-dispatcher.test.ts +419 -0
- package/src/reply-dispatcher.ts +398 -0
- package/src/runtime.ts +14 -0
- package/src/send.test.ts +65 -0
- package/src/send.ts +30 -0
- package/src/setup-surface.ts +388 -0
- package/src/subagent-keepalive-hooks.test.ts +50 -0
- package/src/subagent-keepalive-hooks.ts +47 -0
- package/src/types.ts +162 -0
- package/src/webhook.test.ts +88 -0
- package/src/webhook.ts +51 -0
- package/src/ws.test.ts +341 -0
- package/src/ws.ts +294 -0
- package/dist/api-DftFHS2s.js +0 -13
- package/dist/api.js +0 -4
- package/dist/channel-plugin-api-KLKTLQFV.js +0 -790
- package/dist/channel-plugin-api.js +0 -2
- package/dist/cli-F0a1J9Rj.js +0 -150
- package/dist/client-BzhfASX8.js +0 -427
- package/dist/connect-code-Bbp9J6TH.js +0 -49
- package/dist/index.js +0 -73
- package/dist/monitor-DLgOtdLQ.js +0 -962
- package/dist/probe-Cf1xor23.js +0 -319
- package/dist/runtime-Cq09Y5cd.js +0 -11
- package/dist/runtime-api.js +0 -2
package/api.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { registerVamaCliMetadata } from "./src/cli-metadata.js";
|
|
2
|
+
export { monitorVamaProvider } from "./src/monitor.js";
|
|
3
|
+
export { probeVama } from "./src/probe.js";
|
|
4
|
+
export { sendMessageVama } from "./src/send.js";
|
|
5
|
+
export { registerVamaSubagentKeepaliveHooks } from "./src/subagent-keepalive-hooks.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { vamaPlugin } from "./src/channel.js";
|
package/index.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BundledChannelEntryContract,
|
|
3
|
+
OpenClawPluginApi,
|
|
4
|
+
} from "openclaw/plugin-sdk/channel-entry-contract";
|
|
5
|
+
// Entry files must not import "./src/" statically (bundled.shape-guard);
|
|
6
|
+
// root-level barrels like ./api.js are the sanctioned indirection.
|
|
7
|
+
import { registerVamaCliMetadata, registerVamaSubagentKeepaliveHooks } from "./api.js";
|
|
8
|
+
import { vamaPlugin } from "./channel-plugin-api.js";
|
|
9
|
+
import { setVamaRuntime } from "./runtime-api.js";
|
|
10
|
+
import type { PluginRuntime } from "./runtime-api.js";
|
|
11
|
+
|
|
12
|
+
export { monitorVamaProvider, probeVama, sendMessageVama } from "./api.js";
|
|
13
|
+
export { vamaPlugin } from "./channel-plugin-api.js";
|
|
14
|
+
|
|
15
|
+
// We satisfy the bundled-channel-entry contract shape so the runtime loader
|
|
16
|
+
// (src/plugins/loader.ts → resolveBundledRuntimeChannelRegistration) and the
|
|
17
|
+
// build-time smoke check (scripts/test-built-bundled-channel-entry-smoke.mjs)
|
|
18
|
+
// both accept us, but we deliberately bypass `defineBundledChannelEntry`.
|
|
19
|
+
//
|
|
20
|
+
// Why: the generic bundled-entry helper resolves `runtime.specifier` through
|
|
21
|
+
// `loadBundledEntryExportSync`, which uses jiti to load runtime-api.js as a
|
|
22
|
+
// SEPARATE module instance from the one vamaPlugin (loaded via normal ESM
|
|
23
|
+
// `import` from ./src/channel.js) imports. setVamaRuntime() then writes to
|
|
24
|
+
// the jiti copy of `_runtime`; vamaPlugin reads from the ESM copy and finds
|
|
25
|
+
// undefined → "Vama runtime not initialized" on every webhook delivery.
|
|
26
|
+
//
|
|
27
|
+
// By importing vamaPlugin and setVamaRuntime directly here, we keep them in
|
|
28
|
+
// the same module instance as the channel handlers, so the runtime setter
|
|
29
|
+
// and reader share state. This is the same pattern that worked through
|
|
30
|
+
// 2026.4.15-1; only the wrapper around it has changed.
|
|
31
|
+
const channelEntry: BundledChannelEntryContract = {
|
|
32
|
+
kind: "bundled-channel-entry",
|
|
33
|
+
id: "vama",
|
|
34
|
+
name: "Vama",
|
|
35
|
+
description: "Vama channel plugin via BotHub",
|
|
36
|
+
// Inlined empty channel config schema: vama takes no plugin config,
|
|
37
|
+
// and we hand-roll the contract object (rather than calling
|
|
38
|
+
// emptyChannelConfigSchema()) to keep this entry self-contained for
|
|
39
|
+
// the same module-identity reasons that drive the bypass above.
|
|
40
|
+
configSchema: {
|
|
41
|
+
schema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
properties: {},
|
|
45
|
+
},
|
|
46
|
+
runtime: {
|
|
47
|
+
safeParse(value: unknown) {
|
|
48
|
+
if (value === undefined) {
|
|
49
|
+
return { success: true, data: undefined };
|
|
50
|
+
}
|
|
51
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
issues: [{ path: [], message: "expected config object" }],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (Object.keys(value as Record<string, unknown>).length > 0) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
issues: [{ path: [], message: "config must be empty" }],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return { success: true, data: value };
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
register(api: OpenClawPluginApi) {
|
|
68
|
+
const mode = api.registrationMode as string;
|
|
69
|
+
if (mode === "cli-metadata") {
|
|
70
|
+
registerVamaCliMetadata(api);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
setVamaRuntime(api.runtime);
|
|
74
|
+
api.registerChannel({ plugin: vamaPlugin });
|
|
75
|
+
registerVamaSubagentKeepaliveHooks(api);
|
|
76
|
+
// Match defineBundledChannelEntry's mode handling: CLI surfaces register
|
|
77
|
+
// in discovery + full, but not tool-discovery.
|
|
78
|
+
if (mode === "discovery" || mode === "full") {
|
|
79
|
+
registerVamaCliMetadata(api);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
loadChannelPlugin: () => vamaPlugin,
|
|
83
|
+
setChannelRuntime: (runtime: PluginRuntime) => {
|
|
84
|
+
setVamaRuntime(runtime);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default channelEntry;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vama/openclaw",
|
|
3
|
-
"version": "2026.5.5-
|
|
3
|
+
"version": "2026.5.5-6",
|
|
4
4
|
"description": "OpenClaw Vama channel plugin via BotHub",
|
|
5
5
|
"homepage": "https://web.vama.com/connect-guide",
|
|
6
6
|
"repository": {
|
|
@@ -39,22 +39,6 @@
|
|
|
39
39
|
"release": {
|
|
40
40
|
"publishToNpm": true,
|
|
41
41
|
"publishToClawHub": true
|
|
42
|
-
},
|
|
43
|
-
"runtimeExtensions": [
|
|
44
|
-
"./dist/index.js"
|
|
45
|
-
]
|
|
46
|
-
},
|
|
47
|
-
"files": [
|
|
48
|
-
"dist/**",
|
|
49
|
-
"openclaw.plugin.json",
|
|
50
|
-
"README.md"
|
|
51
|
-
],
|
|
52
|
-
"peerDependencies": {
|
|
53
|
-
"openclaw": ">=2026.5.12"
|
|
54
|
-
},
|
|
55
|
-
"peerDependenciesMeta": {
|
|
56
|
-
"openclaw": {
|
|
57
|
-
"optional": true
|
|
58
42
|
}
|
|
59
43
|
}
|
|
60
44
|
}
|
package/runtime-api.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
2
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk/vama";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
listEnabledVamaAccounts,
|
|
6
|
+
listVamaAccountIds,
|
|
7
|
+
resolveDefaultVamaAccountId,
|
|
8
|
+
resolveVamaAccount,
|
|
9
|
+
} from "./accounts.js";
|
|
10
|
+
|
|
11
|
+
function makeCfg(vama?: Record<string, unknown>): ClawdbotConfig {
|
|
12
|
+
return { channels: { vama } } as ClawdbotConfig;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("listVamaAccountIds", () => {
|
|
16
|
+
it("returns DEFAULT_ACCOUNT_ID when no accounts configured", () => {
|
|
17
|
+
expect(listVamaAccountIds(makeCfg({ enabled: true }))).toEqual([DEFAULT_ACCOUNT_ID]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns DEFAULT_ACCOUNT_ID when channels.vama is missing", () => {
|
|
21
|
+
expect(listVamaAccountIds({} as ClawdbotConfig)).toEqual([DEFAULT_ACCOUNT_ID]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns sorted account IDs when accounts are configured", () => {
|
|
25
|
+
const cfg = makeCfg({
|
|
26
|
+
accounts: {
|
|
27
|
+
beta: { botToken: "t2" },
|
|
28
|
+
alpha: { botToken: "t1" },
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
expect(listVamaAccountIds(cfg)).toEqual(["alpha", "beta"]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("filters empty account keys", () => {
|
|
35
|
+
const cfg = makeCfg({
|
|
36
|
+
accounts: {
|
|
37
|
+
"": { botToken: "t1" },
|
|
38
|
+
valid: { botToken: "t2" },
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
expect(listVamaAccountIds(cfg)).toEqual(["valid"]);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("resolveDefaultVamaAccountId", () => {
|
|
46
|
+
it("returns DEFAULT_ACCOUNT_ID for single-account config", () => {
|
|
47
|
+
expect(resolveDefaultVamaAccountId(makeCfg({ enabled: true }))).toBe(DEFAULT_ACCOUNT_ID);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns first sorted account when no default exists", () => {
|
|
51
|
+
const cfg = makeCfg({ accounts: { zulu: {}, alpha: {} } });
|
|
52
|
+
expect(resolveDefaultVamaAccountId(cfg)).toBe("alpha");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("resolveVamaAccount", () => {
|
|
57
|
+
it("resolves default single-account config", () => {
|
|
58
|
+
const cfg = makeCfg({
|
|
59
|
+
enabled: true,
|
|
60
|
+
botToken: "tok-123",
|
|
61
|
+
bothubUrl: "https://bothub.example.com",
|
|
62
|
+
webhookSecret: "secret-abc",
|
|
63
|
+
});
|
|
64
|
+
const account = resolveVamaAccount({ cfg });
|
|
65
|
+
expect(account.accountId).toBe(DEFAULT_ACCOUNT_ID);
|
|
66
|
+
expect(account.enabled).toBe(true);
|
|
67
|
+
expect(account.configured).toBe(true);
|
|
68
|
+
expect(account.botToken).toBe("tok-123");
|
|
69
|
+
expect(account.bothubUrl).toBe("https://bothub.example.com");
|
|
70
|
+
expect(account.webhookSecret).toBe("secret-abc");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("reports not configured when botToken is missing", () => {
|
|
74
|
+
const cfg = makeCfg({ enabled: true, bothubUrl: "https://bothub.example.com" });
|
|
75
|
+
const account = resolveVamaAccount({ cfg });
|
|
76
|
+
expect(account.configured).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("surfaces webhookSecretFile alongside webhookSecret", () => {
|
|
80
|
+
// Both fields can coexist — monitor resolver prefers the file
|
|
81
|
+
// (warm-pool hot-swap path). Pre-PR-F configs that only set
|
|
82
|
+
// webhookSecret continue to work unchanged.
|
|
83
|
+
const cfg = makeCfg({
|
|
84
|
+
enabled: true,
|
|
85
|
+
botToken: "tok",
|
|
86
|
+
webhookSecret: "literal-fallback",
|
|
87
|
+
webhookSecretFile: "/etc/openclaw/vama-secret",
|
|
88
|
+
});
|
|
89
|
+
const account = resolveVamaAccount({ cfg });
|
|
90
|
+
expect(account.webhookSecret).toBe("literal-fallback");
|
|
91
|
+
expect(account.webhookSecretFile).toBe("/etc/openclaw/vama-secret");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("trims webhookSecretFile and treats empty as undefined", () => {
|
|
95
|
+
// Whitespace-only file paths must collapse to undefined so the
|
|
96
|
+
// monitor's optional-resolver branch behaves correctly. Otherwise
|
|
97
|
+
// a misconfigured deployment could end up calling fs.statSync("")
|
|
98
|
+
// and getting an ENOENT on every webhook.
|
|
99
|
+
const cfg = makeCfg({
|
|
100
|
+
enabled: true,
|
|
101
|
+
botToken: "tok",
|
|
102
|
+
webhookSecretFile: " ",
|
|
103
|
+
});
|
|
104
|
+
const account = resolveVamaAccount({ cfg });
|
|
105
|
+
expect(account.webhookSecretFile).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("round-trips webhookUrl and treats empty/whitespace as unset", () => {
|
|
109
|
+
// Empty string must collapse to undefined: the Vama app's config
|
|
110
|
+
// snippet ships `"webhookUrl": ""` as a visible fill-me-in
|
|
111
|
+
// placeholder, which must NOT trigger a registration attempt.
|
|
112
|
+
const set = resolveVamaAccount({
|
|
113
|
+
cfg: makeCfg({
|
|
114
|
+
enabled: true,
|
|
115
|
+
botToken: "tok",
|
|
116
|
+
webhookUrl: "https://claw.example.com/vama/events",
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
expect(set.webhookUrl).toBe("https://claw.example.com/vama/events");
|
|
120
|
+
|
|
121
|
+
const empty = resolveVamaAccount({
|
|
122
|
+
cfg: makeCfg({ enabled: true, botToken: "tok", webhookUrl: " " }),
|
|
123
|
+
});
|
|
124
|
+
expect(empty.webhookUrl).toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("account-level webhookUrl overrides base", () => {
|
|
128
|
+
const cfg = makeCfg({
|
|
129
|
+
enabled: true,
|
|
130
|
+
botToken: "tok",
|
|
131
|
+
webhookUrl: "https://base.example.com/vama/events",
|
|
132
|
+
accounts: {
|
|
133
|
+
staging: { webhookUrl: "https://staging.example.com/vama/events" },
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
const account = resolveVamaAccount({ cfg, accountId: "staging" });
|
|
137
|
+
expect(account.webhookUrl).toBe("https://staging.example.com/vama/events");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("is configured when only botToken is set (bothubUrl defaults to canonical)", () => {
|
|
141
|
+
const cfg = makeCfg({ enabled: true, botToken: "tok" });
|
|
142
|
+
const account = resolveVamaAccount({ cfg });
|
|
143
|
+
expect(account.configured).toBe(true);
|
|
144
|
+
expect(account.bothubUrl).toBe("https://bothub.vama.com");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("treats whitespace-only credentials as missing", () => {
|
|
148
|
+
const cfg = makeCfg({ enabled: true, botToken: " ", bothubUrl: " " });
|
|
149
|
+
const account = resolveVamaAccount({ cfg });
|
|
150
|
+
expect(account.configured).toBe(false);
|
|
151
|
+
expect(account.botToken).toBeUndefined();
|
|
152
|
+
// bothubUrl falls back to the canonical default even when config is whitespace-only
|
|
153
|
+
expect(account.bothubUrl).toBe("https://bothub.vama.com");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("merges named account config over base", () => {
|
|
157
|
+
const cfg = makeCfg({
|
|
158
|
+
enabled: true,
|
|
159
|
+
botToken: "base-token",
|
|
160
|
+
bothubUrl: "https://base.example.com",
|
|
161
|
+
accounts: {
|
|
162
|
+
staging: {
|
|
163
|
+
botToken: "staging-token",
|
|
164
|
+
bothubUrl: "https://staging.example.com",
|
|
165
|
+
name: "Staging Bot",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
const account = resolveVamaAccount({ cfg, accountId: "staging" });
|
|
170
|
+
expect(account.accountId).toBe("staging");
|
|
171
|
+
expect(account.botToken).toBe("staging-token");
|
|
172
|
+
expect(account.bothubUrl).toBe("https://staging.example.com");
|
|
173
|
+
expect(account.name).toBe("Staging Bot");
|
|
174
|
+
expect(account.configured).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("inherits base config when account does not override", () => {
|
|
178
|
+
const cfg = makeCfg({
|
|
179
|
+
enabled: true,
|
|
180
|
+
botToken: "base-token",
|
|
181
|
+
bothubUrl: "https://base.example.com",
|
|
182
|
+
accounts: {
|
|
183
|
+
minimal: { name: "Minimal" },
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
const account = resolveVamaAccount({ cfg, accountId: "minimal" });
|
|
187
|
+
expect(account.botToken).toBe("base-token");
|
|
188
|
+
expect(account.bothubUrl).toBe("https://base.example.com");
|
|
189
|
+
expect(account.name).toBe("Minimal");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("disabled when base is disabled", () => {
|
|
193
|
+
const cfg = makeCfg({
|
|
194
|
+
enabled: false,
|
|
195
|
+
botToken: "tok",
|
|
196
|
+
bothubUrl: "https://bothub.example.com",
|
|
197
|
+
});
|
|
198
|
+
const account = resolveVamaAccount({ cfg });
|
|
199
|
+
expect(account.enabled).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("disabled when account is disabled", () => {
|
|
203
|
+
const cfg = makeCfg({
|
|
204
|
+
enabled: true,
|
|
205
|
+
accounts: {
|
|
206
|
+
off: { enabled: false, botToken: "tok", bothubUrl: "https://bothub.example.com" },
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
const account = resolveVamaAccount({ cfg, accountId: "off" });
|
|
210
|
+
expect(account.enabled).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("normalizes null accountId to default", () => {
|
|
214
|
+
const cfg = makeCfg({ enabled: true, botToken: "tok", bothubUrl: "https://b.com" });
|
|
215
|
+
const account = resolveVamaAccount({ cfg, accountId: null });
|
|
216
|
+
expect(account.accountId).toBe(DEFAULT_ACCOUNT_ID);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("listEnabledVamaAccounts", () => {
|
|
221
|
+
it("returns only enabled and configured accounts", () => {
|
|
222
|
+
const cfg = makeCfg({
|
|
223
|
+
enabled: true,
|
|
224
|
+
accounts: {
|
|
225
|
+
good: { botToken: "tok2", bothubUrl: "https://b2.com" },
|
|
226
|
+
disabled: { enabled: false, botToken: "tok3", bothubUrl: "https://b3.com" },
|
|
227
|
+
unconfigured: { name: "no creds" },
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
const accounts = listEnabledVamaAccounts(cfg);
|
|
231
|
+
const ids = accounts.map((a) => a.accountId);
|
|
232
|
+
expect(ids).toContain("good");
|
|
233
|
+
expect(ids).not.toContain("disabled");
|
|
234
|
+
expect(ids).not.toContain("unconfigured");
|
|
235
|
+
});
|
|
236
|
+
});
|
package/src/accounts.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
2
|
+
import { BOTHUB_DEFAULT_URL } from "./client.js";
|
|
3
|
+
import type { ClawdbotConfig } from "./host-sdk.js";
|
|
4
|
+
import type { VamaConfig, VamaAccountConfig, ResolvedVamaAccount } from "./types.js";
|
|
5
|
+
|
|
6
|
+
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
|
7
|
+
const accounts = (cfg.channels?.vama as VamaConfig)?.accounts;
|
|
8
|
+
if (!accounts || typeof accounts !== "object") {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
return Object.keys(accounts).filter(Boolean);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function listVamaAccountIds(cfg: ClawdbotConfig): string[] {
|
|
15
|
+
const ids = listConfiguredAccountIds(cfg);
|
|
16
|
+
if (ids.length === 0) {
|
|
17
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
18
|
+
}
|
|
19
|
+
return [...ids].toSorted((a, b) => a.localeCompare(b));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveDefaultVamaAccountId(cfg: ClawdbotConfig): string {
|
|
23
|
+
const ids = listVamaAccountIds(cfg);
|
|
24
|
+
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
25
|
+
return DEFAULT_ACCOUNT_ID;
|
|
26
|
+
}
|
|
27
|
+
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveAccountConfig(
|
|
31
|
+
cfg: ClawdbotConfig,
|
|
32
|
+
accountId: string,
|
|
33
|
+
): VamaAccountConfig | undefined {
|
|
34
|
+
const accounts = (cfg.channels?.vama as VamaConfig)?.accounts;
|
|
35
|
+
if (!accounts || typeof accounts !== "object") {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return accounts[accountId];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function mergeVamaAccountConfig(cfg: ClawdbotConfig, accountId: string): VamaConfig {
|
|
42
|
+
const vamaCfg = cfg.channels?.vama as VamaConfig | undefined;
|
|
43
|
+
const { accounts: _ignored, ...base } = vamaCfg ?? {};
|
|
44
|
+
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
45
|
+
return { ...base, ...account } as VamaConfig;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveVamaAccount(params: {
|
|
49
|
+
cfg: ClawdbotConfig;
|
|
50
|
+
accountId?: string | null;
|
|
51
|
+
}): ResolvedVamaAccount {
|
|
52
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
53
|
+
const vamaCfg = params.cfg.channels?.vama as VamaConfig | undefined;
|
|
54
|
+
|
|
55
|
+
const baseEnabled = vamaCfg?.enabled !== false;
|
|
56
|
+
const merged = mergeVamaAccountConfig(params.cfg, accountId);
|
|
57
|
+
const accountEnabled = merged.enabled !== false;
|
|
58
|
+
const enabled = baseEnabled && accountEnabled;
|
|
59
|
+
|
|
60
|
+
const botToken = merged.botToken?.trim() || undefined;
|
|
61
|
+
const webhookSecret = merged.webhookSecret?.trim() || undefined;
|
|
62
|
+
const webhookSecretFile = merged.webhookSecretFile?.trim() || undefined;
|
|
63
|
+
// Fall back to the canonical BotHub endpoint; overridable for self-hosted deployments.
|
|
64
|
+
const bothubUrl = merged.bothubUrl?.trim() || BOTHUB_DEFAULT_URL;
|
|
65
|
+
// Empty string is treated as unset so config templates can ship the key
|
|
66
|
+
// as a visible "fill me in" placeholder without triggering registration.
|
|
67
|
+
const webhookUrl = merged.webhookUrl?.trim() || undefined;
|
|
68
|
+
// Unknown values fall back to undefined (= "auto") rather than erroring:
|
|
69
|
+
// a typo'd transport must never take the channel down.
|
|
70
|
+
const rawTransport = merged.transport?.trim();
|
|
71
|
+
const transport =
|
|
72
|
+
rawTransport === "auto" || rawTransport === "webhook" || rawTransport === "websocket"
|
|
73
|
+
? rawTransport
|
|
74
|
+
: undefined;
|
|
75
|
+
const configured = Boolean(botToken);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
accountId,
|
|
79
|
+
enabled,
|
|
80
|
+
configured,
|
|
81
|
+
name: (merged as VamaAccountConfig).name?.trim() || undefined,
|
|
82
|
+
botToken,
|
|
83
|
+
webhookSecret,
|
|
84
|
+
webhookSecretFile,
|
|
85
|
+
bothubUrl,
|
|
86
|
+
webhookUrl,
|
|
87
|
+
transport,
|
|
88
|
+
config: merged,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function listEnabledVamaAccounts(cfg: ClawdbotConfig): ResolvedVamaAccount[] {
|
|
93
|
+
return listVamaAccountIds(cfg)
|
|
94
|
+
.map((accountId) => resolveVamaAccount({ cfg, accountId }))
|
|
95
|
+
.filter((account) => account.enabled && account.configured);
|
|
96
|
+
}
|