@botcord/daemon 0.2.35 → 0.2.37
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/dist/config.d.ts +30 -1
- package/dist/config.js +27 -0
- package/dist/daemon-config-map.d.ts +3 -0
- package/dist/daemon-config-map.js +30 -0
- package/dist/daemon.d.ts +15 -1
- package/dist/daemon.js +56 -11
- package/dist/gateway/channels/botcord.js +44 -0
- package/dist/gateway/channels/http-types.d.ts +19 -0
- package/dist/gateway/channels/http-types.js +1 -0
- package/dist/gateway/channels/index.d.ts +5 -0
- package/dist/gateway/channels/index.js +5 -0
- package/dist/gateway/channels/login-session.d.ts +83 -0
- package/dist/gateway/channels/login-session.js +99 -0
- package/dist/gateway/channels/secret-store.d.ts +21 -0
- package/dist/gateway/channels/secret-store.js +75 -0
- package/dist/gateway/channels/state-store.d.ts +60 -0
- package/dist/gateway/channels/state-store.js +173 -0
- package/dist/gateway/channels/telegram.d.ts +31 -0
- package/dist/gateway/channels/telegram.js +371 -0
- package/dist/gateway/channels/text-split.d.ts +13 -0
- package/dist/gateway/channels/text-split.js +33 -0
- package/dist/gateway/channels/url-guard.d.ts +18 -0
- package/dist/gateway/channels/url-guard.js +53 -0
- package/dist/gateway/channels/wechat-http.d.ts +18 -0
- package/dist/gateway/channels/wechat-http.js +28 -0
- package/dist/gateway/channels/wechat-login.d.ts +36 -0
- package/dist/gateway/channels/wechat-login.js +62 -0
- package/dist/gateway/channels/wechat.d.ts +40 -0
- package/dist/gateway/channels/wechat.js +472 -0
- package/dist/gateway/runtimes/openclaw-acp.js +211 -6
- package/dist/gateway/types.d.ts +10 -0
- package/dist/gateway-control.d.ts +53 -0
- package/dist/gateway-control.js +638 -0
- package/dist/openclaw-discovery.js +1 -1
- package/dist/provision.d.ts +7 -0
- package/dist/provision.js +255 -5
- package/package.json +1 -1
- package/src/__tests__/gateway-control.test.ts +499 -0
- package/src/__tests__/openclaw-acp.test.ts +63 -0
- package/src/__tests__/openclaw-discovery.test.ts +36 -0
- package/src/__tests__/provision.test.ts +179 -0
- package/src/__tests__/secret-store.test.ts +70 -0
- package/src/__tests__/state-store.test.ts +119 -0
- package/src/__tests__/third-party-gateway.test.ts +126 -0
- package/src/__tests__/url-guard.test.ts +85 -0
- package/src/__tests__/wechat-channel.test.ts +1134 -0
- package/src/config.ts +72 -1
- package/src/daemon-config-map.ts +24 -0
- package/src/daemon.ts +70 -11
- package/src/gateway/__tests__/botcord-channel.test.ts +1 -1
- package/src/gateway/__tests__/telegram-channel.test.ts +555 -0
- package/src/gateway/channels/botcord.ts +39 -0
- package/src/gateway/channels/http-types.ts +22 -0
- package/src/gateway/channels/index.ts +22 -0
- package/src/gateway/channels/login-session.ts +135 -0
- package/src/gateway/channels/secret-store.ts +100 -0
- package/src/gateway/channels/state-store.ts +213 -0
- package/src/gateway/channels/telegram.ts +469 -0
- package/src/gateway/channels/text-split.ts +29 -0
- package/src/gateway/channels/url-guard.ts +55 -0
- package/src/gateway/channels/wechat-http.ts +35 -0
- package/src/gateway/channels/wechat-login.ts +90 -0
- package/src/gateway/channels/wechat.ts +572 -0
- package/src/gateway/runtimes/openclaw-acp.ts +211 -7
- package/src/gateway/types.ts +10 -0
- package/src/gateway-control.ts +709 -0
- package/src/openclaw-discovery.ts +1 -1
- package/src/provision.ts +336 -5
package/src/config.ts
CHANGED
|
@@ -95,10 +95,35 @@ export interface OpenclawDiscoveryConfig {
|
|
|
95
95
|
searchPaths?: string[];
|
|
96
96
|
/** Overrides the local loopback ports to probe. */
|
|
97
97
|
defaultPorts?: number[];
|
|
98
|
-
/** Defaults to
|
|
98
|
+
/** Defaults to false. When false, discovery only persists gateways. */
|
|
99
99
|
autoProvision?: boolean;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/** Third-party messaging provider supported by the daemon's channel factory. */
|
|
103
|
+
export type ThirdPartyGatewayType = "telegram" | "wechat";
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* One third-party gateway profile bound to a BotCord agent. `id` is the
|
|
107
|
+
* channel id (typically `gw_...` minted by the Hub); `accountId` is the
|
|
108
|
+
* BotCord agent the inbound traffic should be attributed to. Secrets and
|
|
109
|
+
* provider cursors live outside this struct — see `secretFile` and
|
|
110
|
+
* `stateFile`. When omitted, the daemon derives them as
|
|
111
|
+
* `~/.botcord/daemon/gateways/{id}.json` and `{id}.state.json`.
|
|
112
|
+
*/
|
|
113
|
+
export interface ThirdPartyGatewayProfile {
|
|
114
|
+
id: string;
|
|
115
|
+
type: ThirdPartyGatewayType;
|
|
116
|
+
accountId: string;
|
|
117
|
+
label?: string;
|
|
118
|
+
enabled?: boolean;
|
|
119
|
+
secretFile?: string;
|
|
120
|
+
stateFile?: string;
|
|
121
|
+
allowedSenderIds?: string[];
|
|
122
|
+
allowedChatIds?: string[];
|
|
123
|
+
splitAt?: number;
|
|
124
|
+
baseUrl?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
102
127
|
export interface DaemonConfig {
|
|
103
128
|
/**
|
|
104
129
|
* @deprecated Kept for backward compatibility with pre-multi-agent configs.
|
|
@@ -148,6 +173,13 @@ export interface DaemonConfig {
|
|
|
148
173
|
* search paths/ports and automatic adoption of discovered agents.
|
|
149
174
|
*/
|
|
150
175
|
openclawDiscovery?: OpenclawDiscoveryConfig;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Third-party messaging gateways (Telegram, WeChat, …) bound to BotCord
|
|
179
|
+
* agents on this daemon. Each entry becomes one channel in the gateway
|
|
180
|
+
* runtime; `enabled === false` entries are filtered out at boot.
|
|
181
|
+
*/
|
|
182
|
+
thirdPartyGateways?: ThirdPartyGatewayProfile[];
|
|
151
183
|
}
|
|
152
184
|
|
|
153
185
|
/**
|
|
@@ -393,6 +425,45 @@ export function loadConfig(): DaemonConfig {
|
|
|
393
425
|
}
|
|
394
426
|
out.openclawDiscovery = copy;
|
|
395
427
|
}
|
|
428
|
+
const tpg = (parsed as Partial<DaemonConfig>).thirdPartyGateways;
|
|
429
|
+
if (tpg !== undefined) {
|
|
430
|
+
if (!Array.isArray(tpg)) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`daemon config "thirdPartyGateways" must be an array (${CONFIG_PATH})`,
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const seen = new Set<string>();
|
|
436
|
+
for (const [i, g] of tpg.entries()) {
|
|
437
|
+
if (!g || typeof g !== "object") {
|
|
438
|
+
throw new Error(
|
|
439
|
+
`daemon config thirdPartyGateways[${i}] is not an object (${CONFIG_PATH})`,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
const gg = g as Partial<ThirdPartyGatewayProfile>;
|
|
443
|
+
if (typeof gg.id !== "string" || gg.id.length === 0) {
|
|
444
|
+
throw new Error(
|
|
445
|
+
`daemon config thirdPartyGateways[${i}].id must be a non-empty string (${CONFIG_PATH})`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
if (gg.type !== "telegram" && gg.type !== "wechat") {
|
|
449
|
+
throw new Error(
|
|
450
|
+
`daemon config thirdPartyGateways[${i}].type must be "telegram" or "wechat" (${CONFIG_PATH})`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
if (typeof gg.accountId !== "string" || gg.accountId.length === 0) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`daemon config thirdPartyGateways[${i}].accountId must be a non-empty string (${CONFIG_PATH})`,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
if (seen.has(gg.id)) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
`daemon config thirdPartyGateways[${i}].id "${gg.id}" duplicated (${CONFIG_PATH})`,
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
seen.add(gg.id);
|
|
464
|
+
}
|
|
465
|
+
out.thirdPartyGateways = (tpg as ThirdPartyGatewayProfile[]).map((g) => ({ ...g }));
|
|
466
|
+
}
|
|
396
467
|
return out;
|
|
397
468
|
}
|
|
398
469
|
|
package/src/daemon-config-map.ts
CHANGED
|
@@ -135,6 +135,10 @@ export const DEFAULT_BOTCORD_CHANNEL_ID = "botcord-main";
|
|
|
135
135
|
/** Channel `type` tag used by `createBotCordChannel`. */
|
|
136
136
|
export const BOTCORD_CHANNEL_TYPE = "botcord";
|
|
137
137
|
|
|
138
|
+
/** Channel `type` tags for built-in third-party providers. */
|
|
139
|
+
export const TELEGRAM_CHANNEL_TYPE = "telegram";
|
|
140
|
+
export const WECHAT_CHANNEL_TYPE = "wechat";
|
|
141
|
+
|
|
138
142
|
/**
|
|
139
143
|
* Map daemon's historical narrower TrustLevel ("owner" | "untrusted") onto
|
|
140
144
|
* gateway's ("owner" | "trusted" | "public"). Matches the adapter-level
|
|
@@ -241,6 +245,26 @@ export function toGatewayConfig(
|
|
|
241
245
|
agentId,
|
|
242
246
|
}));
|
|
243
247
|
|
|
248
|
+
// Append one channel per enabled third-party gateway. Disabled entries are
|
|
249
|
+
// dropped here so the gateway runtime never sees them; re-enabling requires
|
|
250
|
+
// an `upsert_gateway` (Phase B) or a config reload.
|
|
251
|
+
for (const g of cfg.thirdPartyGateways ?? []) {
|
|
252
|
+
if (g.enabled === false) continue;
|
|
253
|
+
const ch: GatewayChannelConfig = {
|
|
254
|
+
id: g.id,
|
|
255
|
+
type: g.type,
|
|
256
|
+
accountId: g.accountId,
|
|
257
|
+
};
|
|
258
|
+
if (g.label !== undefined) ch.label = g.label;
|
|
259
|
+
if (g.secretFile !== undefined) ch.secretFile = g.secretFile;
|
|
260
|
+
if (g.stateFile !== undefined) ch.stateFile = g.stateFile;
|
|
261
|
+
if (g.allowedSenderIds !== undefined) ch.allowedSenderIds = g.allowedSenderIds;
|
|
262
|
+
if (g.allowedChatIds !== undefined) ch.allowedChatIds = g.allowedChatIds;
|
|
263
|
+
if (g.splitAt !== undefined) ch.splitAt = g.splitAt;
|
|
264
|
+
if (g.baseUrl !== undefined) ch.baseUrl = g.baseUrl;
|
|
265
|
+
channels.push(ch);
|
|
266
|
+
}
|
|
267
|
+
|
|
244
268
|
// DaemonConfig's typed surface doesn't carry `trustLevel`, but we read it
|
|
245
269
|
// defensively so future config extensions can propagate without a shape bump.
|
|
246
270
|
const profiles = prepareGatewayProfiles(cfg.openclawGateways);
|
package/src/daemon.ts
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
Gateway,
|
|
8
8
|
createBotCordChannel,
|
|
9
|
+
createTelegramChannel,
|
|
10
|
+
createWechatChannel,
|
|
9
11
|
resolveTranscriptEnabled,
|
|
10
12
|
sanitizeUntrustedContent,
|
|
11
13
|
type ChannelAdapter,
|
|
@@ -116,6 +118,69 @@ export function createActivityRecorder(opts: {
|
|
|
116
118
|
};
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
/** Per-call dependencies for {@link createDaemonChannel}. */
|
|
122
|
+
export interface CreateDaemonChannelDeps {
|
|
123
|
+
credentialPathByAgentId: Map<string, string>;
|
|
124
|
+
defaultCredentialsPath?: string;
|
|
125
|
+
hubBaseUrl?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Dispatch a `GatewayChannelConfig` to the right adapter constructor based on
|
|
130
|
+
* `chCfg.type`. Phase A wires up the BotCord adapter and stub constructors
|
|
131
|
+
* for telegram/wechat (which throw "not implemented"); Phase B will fill the
|
|
132
|
+
* latter in. Unknown types throw so misconfigured channels fail loudly at
|
|
133
|
+
* boot rather than silently dropping inbound traffic.
|
|
134
|
+
*/
|
|
135
|
+
export function createDaemonChannel(
|
|
136
|
+
chCfg: GatewayChannelConfig,
|
|
137
|
+
deps: CreateDaemonChannelDeps,
|
|
138
|
+
): ChannelAdapter {
|
|
139
|
+
switch (chCfg.type) {
|
|
140
|
+
case "botcord": {
|
|
141
|
+
const agentId =
|
|
142
|
+
typeof chCfg.agentId === "string" ? chCfg.agentId : chCfg.accountId;
|
|
143
|
+
return createBotCordChannel({
|
|
144
|
+
id: chCfg.id,
|
|
145
|
+
accountId: chCfg.accountId,
|
|
146
|
+
agentId,
|
|
147
|
+
credentialsPath:
|
|
148
|
+
deps.credentialPathByAgentId.get(agentId) ?? deps.defaultCredentialsPath,
|
|
149
|
+
hubBaseUrl: deps.hubBaseUrl,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
case "telegram":
|
|
153
|
+
return createTelegramChannel({
|
|
154
|
+
id: chCfg.id,
|
|
155
|
+
accountId: chCfg.accountId,
|
|
156
|
+
...(typeof chCfg.baseUrl === "string" ? { baseUrl: chCfg.baseUrl } : {}),
|
|
157
|
+
...(Array.isArray(chCfg.allowedSenderIds)
|
|
158
|
+
? { allowedSenderIds: chCfg.allowedSenderIds as string[] }
|
|
159
|
+
: {}),
|
|
160
|
+
...(Array.isArray(chCfg.allowedChatIds)
|
|
161
|
+
? { allowedChatIds: chCfg.allowedChatIds as string[] }
|
|
162
|
+
: {}),
|
|
163
|
+
...(typeof chCfg.splitAt === "number" ? { splitAt: chCfg.splitAt } : {}),
|
|
164
|
+
...(typeof chCfg.secretFile === "string" ? { secretFile: chCfg.secretFile } : {}),
|
|
165
|
+
...(typeof chCfg.stateFile === "string" ? { stateFile: chCfg.stateFile } : {}),
|
|
166
|
+
});
|
|
167
|
+
case "wechat":
|
|
168
|
+
return createWechatChannel({
|
|
169
|
+
id: chCfg.id,
|
|
170
|
+
accountId: chCfg.accountId,
|
|
171
|
+
...(typeof chCfg.baseUrl === "string" ? { baseUrl: chCfg.baseUrl } : {}),
|
|
172
|
+
...(Array.isArray(chCfg.allowedSenderIds)
|
|
173
|
+
? { allowedSenderIds: chCfg.allowedSenderIds as string[] }
|
|
174
|
+
: {}),
|
|
175
|
+
...(typeof chCfg.splitAt === "number" ? { splitAt: chCfg.splitAt } : {}),
|
|
176
|
+
...(typeof chCfg.secretFile === "string" ? { secretFile: chCfg.secretFile } : {}),
|
|
177
|
+
...(typeof chCfg.stateFile === "string" ? { stateFile: chCfg.stateFile } : {}),
|
|
178
|
+
});
|
|
179
|
+
default:
|
|
180
|
+
throw new Error(`unknown channel type "${chCfg.type}"`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
119
184
|
/**
|
|
120
185
|
* Minimal send-capable surface used by {@link pushRuntimeSnapshot}.
|
|
121
186
|
* Exists so the helper is trivially mockable from unit tests without needing
|
|
@@ -414,18 +479,12 @@ export async function startDaemon(opts: DaemonRuntimeOptions): Promise<DaemonHan
|
|
|
414
479
|
const gateway = new Gateway({
|
|
415
480
|
config: gwConfig,
|
|
416
481
|
sessionStorePath: opts.sessionStorePath ?? SESSIONS_PATH,
|
|
417
|
-
createChannel: (chCfg: GatewayChannelConfig): ChannelAdapter =>
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
id: chCfg.id,
|
|
422
|
-
accountId: chCfg.accountId,
|
|
423
|
-
agentId,
|
|
424
|
-
credentialsPath:
|
|
425
|
-
credentialPathByAgentId.get(agentId) ?? opts.credentialsPath,
|
|
482
|
+
createChannel: (chCfg: GatewayChannelConfig): ChannelAdapter =>
|
|
483
|
+
createDaemonChannel(chCfg, {
|
|
484
|
+
credentialPathByAgentId,
|
|
485
|
+
defaultCredentialsPath: opts.credentialsPath,
|
|
426
486
|
hubBaseUrl: opts.hubBaseUrl,
|
|
427
|
-
})
|
|
428
|
-
},
|
|
487
|
+
}),
|
|
429
488
|
log: logger,
|
|
430
489
|
turnTimeoutMs: DEFAULT_TURN_TIMEOUT_MS,
|
|
431
490
|
buildSystemContext,
|
|
@@ -648,7 +648,7 @@ describe("createBotCordChannel — streamBlock()", () => {
|
|
|
648
648
|
expect(body.block).toEqual({
|
|
649
649
|
kind: "thinking",
|
|
650
650
|
seq: 7,
|
|
651
|
-
payload: { phase: "updated", label: "Searching web", source: "runtime" },
|
|
651
|
+
payload: { phase: "updated", label: "Searching web", source: "runtime", details: "Searching web" },
|
|
652
652
|
});
|
|
653
653
|
} finally {
|
|
654
654
|
globalThis.fetch = realFetch;
|