skyloom 1.20.0 → 1.22.0

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.
Files changed (41) hide show
  1. package/README.md +46 -0
  2. package/dist/cli/main.js +3 -0
  3. package/dist/cli/main.js.map +1 -1
  4. package/dist/gateway/channels/feishu.d.ts +19 -0
  5. package/dist/gateway/channels/feishu.d.ts.map +1 -0
  6. package/dist/gateway/channels/feishu.js +290 -0
  7. package/dist/gateway/channels/feishu.js.map +1 -0
  8. package/dist/gateway/channels/qq.d.ts +25 -0
  9. package/dist/gateway/channels/qq.d.ts.map +1 -0
  10. package/dist/gateway/channels/qq.js +187 -0
  11. package/dist/gateway/channels/qq.js.map +1 -0
  12. package/dist/gateway/channels/wecom.d.ts +26 -0
  13. package/dist/gateway/channels/wecom.d.ts.map +1 -0
  14. package/dist/gateway/channels/wecom.js +196 -0
  15. package/dist/gateway/channels/wecom.js.map +1 -0
  16. package/dist/gateway/gateway.d.ts +19 -0
  17. package/dist/gateway/gateway.d.ts.map +1 -0
  18. package/dist/gateway/gateway.js +177 -0
  19. package/dist/gateway/gateway.js.map +1 -0
  20. package/dist/gateway/helpers.d.ts +39 -0
  21. package/dist/gateway/helpers.d.ts.map +1 -0
  22. package/dist/gateway/helpers.js +81 -0
  23. package/dist/gateway/helpers.js.map +1 -0
  24. package/dist/gateway/registry.d.ts +12 -0
  25. package/dist/gateway/registry.d.ts.map +1 -0
  26. package/dist/gateway/registry.js +44 -0
  27. package/dist/gateway/registry.js.map +1 -0
  28. package/dist/gateway/types.d.ts +104 -0
  29. package/dist/gateway/types.d.ts.map +1 -0
  30. package/dist/gateway/types.js +26 -0
  31. package/dist/gateway/types.js.map +1 -0
  32. package/package.json +1 -1
  33. package/src/cli/main.ts +3 -0
  34. package/src/gateway/channels/feishu.ts +242 -0
  35. package/src/gateway/channels/qq.ts +151 -0
  36. package/src/gateway/channels/wecom.ts +151 -0
  37. package/src/gateway/gateway.ts +178 -0
  38. package/src/gateway/helpers.ts +82 -0
  39. package/src/gateway/registry.ts +45 -0
  40. package/src/gateway/types.ts +125 -0
  41. package/tests/gateway.test.ts +293 -0
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ /**
3
+ * WeCom (企业微信) channel adapter.
4
+ *
5
+ * Uses the official "receive messages" callback with the standard WeWork
6
+ * crypto: msg_signature = sha1(sort(token, timestamp, nonce, echostr|encrypt)),
7
+ * and AES-256-CBC (PKCS7) where the key is base64(EncodingAESKey + "=") and the
8
+ * plaintext is [16B random][4B big-endian msg-len][msg][receiveid].
9
+ *
10
+ * Inbound is XML. GET verifies the callback URL (echo the decrypted echostr);
11
+ * POST carries the encrypted message. We extract text from <Content>.
12
+ *
13
+ * Outbound uses the application message API (message/send) with the agent's
14
+ * gettoken. Config (channels.wecom): { corpId, corpSecret, token, encodingAesKey,
15
+ * agentId, agent? }. Env fallback: WECOM_CORP_ID, WECOM_CORP_SECRET,
16
+ * WECOM_TOKEN, WECOM_AES_KEY, WECOM_AGENT_ID.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.wecomSignature = wecomSignature;
53
+ exports.decryptWecom = decryptWecom;
54
+ exports.createWecomAdapter = createWecomAdapter;
55
+ const crypto = __importStar(require("crypto"));
56
+ const logger_1 = require("../../core/logger");
57
+ const helpers_1 = require("../helpers");
58
+ const log = (0, logger_1.getLogger)('channel-wecom');
59
+ /** WeWork msg_signature: sha1 over the sorted concatenation. */
60
+ function wecomSignature(token, timestamp, nonce, encrypt) {
61
+ const arr = [token, timestamp, nonce, encrypt].sort();
62
+ return crypto.createHash('sha1').update(arr.join('')).digest('hex');
63
+ }
64
+ /** Decrypt a WeWork AES message. Returns { message, receiveId }. */
65
+ function decryptWecom(encrypt, encodingAesKey) {
66
+ const key = Buffer.from(encodingAesKey + '=', 'base64'); // 32 bytes
67
+ const iv = key.subarray(0, 16);
68
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
69
+ decipher.setAutoPadding(false);
70
+ let decrypted = Buffer.concat([decipher.update(Buffer.from(encrypt, 'base64')), decipher.final()]);
71
+ // PKCS7 unpad
72
+ const pad = decrypted[decrypted.length - 1];
73
+ if (pad > 0 && pad <= 32)
74
+ decrypted = decrypted.subarray(0, decrypted.length - pad);
75
+ // [16B random][4B msg len BE][msg][receiveid]
76
+ const content = decrypted.subarray(16);
77
+ const msgLen = content.readUInt32BE(0);
78
+ const message = content.subarray(4, 4 + msgLen).toString('utf8');
79
+ const receiveId = content.subarray(4 + msgLen).toString('utf8');
80
+ return { message, receiveId };
81
+ }
82
+ function xmlField(xml, tag) {
83
+ const cdata = xml.match(new RegExp(`<${tag}><!\\[CDATA\\[([\\s\\S]*?)\\]\\]></${tag}>`));
84
+ if (cdata)
85
+ return cdata[1];
86
+ const plain = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
87
+ return plain ? plain[1] : '';
88
+ }
89
+ function createWecomAdapter(cfg, env) {
90
+ const corpId = (0, helpers_1.resolveSecret)(cfg.corpId, env, 'WECOM_CORP_ID');
91
+ const corpSecret = (0, helpers_1.resolveSecret)(cfg.corpSecret, env, 'WECOM_CORP_SECRET');
92
+ const token = (0, helpers_1.resolveSecret)(cfg.token, env, 'WECOM_TOKEN');
93
+ const aesKey = (0, helpers_1.resolveSecret)(cfg.encodingAesKey, env, 'WECOM_AES_KEY');
94
+ const agentId = (0, helpers_1.resolveSecret)(cfg.agentId != null ? String(cfg.agentId) : undefined, env, 'WECOM_AGENT_ID');
95
+ if (!corpId || !corpSecret || !token || !aesKey)
96
+ return null;
97
+ const tokenCache = new helpers_1.TokenCache(async () => {
98
+ const data = await (0, helpers_1.getJson)(`https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${encodeURIComponent(corpId)}&corpsecret=${encodeURIComponent(corpSecret)}`);
99
+ if (data.errcode !== 0)
100
+ throw new Error(`wecom token error ${data.errcode}: ${data.errmsg}`);
101
+ return { token: data.access_token, expiresInSec: data.expires_in ?? 7200 };
102
+ });
103
+ const verify = (req, encrypt) => {
104
+ const sig = req.query.get('msg_signature') || '';
105
+ const ts = req.query.get('timestamp') || '';
106
+ const nonce = req.query.get('nonce') || '';
107
+ return sig === wecomSignature(token, ts, nonce, encrypt);
108
+ };
109
+ return {
110
+ id: 'wecom',
111
+ name: 'WeCom (企业微信)',
112
+ defaultAgent: cfg.agent || 'fair',
113
+ async handleWebhook(req) {
114
+ // URL verification: GET with echostr.
115
+ if (req.method === 'GET') {
116
+ const echostr = req.query.get('echostr') || '';
117
+ if (!verify(req, echostr))
118
+ return { response: { status: 403, body: 'bad signature' } };
119
+ try {
120
+ const { message } = decryptWecom(echostr, aesKey);
121
+ return { response: { status: 200, body: message } };
122
+ }
123
+ catch (e) {
124
+ log.warn('wecom_echostr_decrypt_failed', { error: String(e) });
125
+ return { response: { status: 400, body: 'decrypt failed' } };
126
+ }
127
+ }
128
+ // Message callback: POST with <Encrypt> XML.
129
+ const xml = req.body.toString('utf8');
130
+ const encrypt = xmlField(xml, 'Encrypt');
131
+ if (!encrypt)
132
+ return { response: { status: 400, body: 'no encrypt' } };
133
+ if (!verify(req, encrypt))
134
+ return { response: { status: 403, body: 'bad signature' } };
135
+ let inner;
136
+ try {
137
+ inner = decryptWecom(encrypt, aesKey).message;
138
+ }
139
+ catch (e) {
140
+ log.warn('wecom_decrypt_failed', { error: String(e) });
141
+ return { response: { status: 400, body: 'decrypt failed' } };
142
+ }
143
+ const msgType = xmlField(inner, 'MsgType');
144
+ const fromUser = xmlField(inner, 'FromUserName');
145
+ let text = '';
146
+ const media = [];
147
+ switch (msgType) {
148
+ case 'text':
149
+ text = xmlField(inner, 'Content').trim();
150
+ break;
151
+ case 'image':
152
+ media.push({ kind: 'image', ref: xmlField(inner, 'MediaId'), url: xmlField(inner, 'PicUrl') || undefined });
153
+ break;
154
+ case 'voice':
155
+ media.push({ kind: 'audio', ref: xmlField(inner, 'MediaId'), filename: xmlField(inner, 'MediaId') + '.' + (xmlField(inner, 'Format') || 'amr') });
156
+ break;
157
+ case 'video':
158
+ media.push({ kind: 'video', ref: xmlField(inner, 'MediaId') });
159
+ break;
160
+ case 'file':
161
+ media.push({ kind: 'file', ref: xmlField(inner, 'MediaId'), filename: xmlField(inner, 'FileName') || undefined });
162
+ break;
163
+ case 'location':
164
+ text = `[位置] ${xmlField(inner, 'Label')} (${xmlField(inner, 'Location_X')},${xmlField(inner, 'Location_Y')})`;
165
+ break;
166
+ default: text = `[${msgType} 消息]`;
167
+ }
168
+ // Ack the callback immediately (empty 200); reply is pushed via the API.
169
+ return {
170
+ response: { status: 200, body: '' },
171
+ message: (text || media.length) ? {
172
+ channel: 'wecom',
173
+ conversationId: fromUser,
174
+ userId: fromUser,
175
+ text,
176
+ media: media.length ? media : undefined,
177
+ replyTo: { channel: 'wecom', toUser: fromUser },
178
+ raw: inner,
179
+ } : undefined,
180
+ };
181
+ },
182
+ async send(target, text) {
183
+ const toUser = target.toUser;
184
+ if (!toUser || !agentId)
185
+ return;
186
+ const accessToken = await tokenCache.get();
187
+ const data = await (0, helpers_1.postJson)(`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${encodeURIComponent(accessToken)}`, { touser: toUser, msgtype: 'text', agentid: Number(agentId), text: { content: text } });
188
+ if (data.errcode !== 0) {
189
+ if (data.errcode === 42001 || data.errcode === 40014)
190
+ tokenCache.invalidate();
191
+ throw new Error(`wecom send error ${data.errcode}: ${data.errmsg}`);
192
+ }
193
+ },
194
+ };
195
+ }
196
+ //# sourceMappingURL=wecom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wecom.js","sourceRoot":"","sources":["../../../src/gateway/channels/wecom.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Channel gateway — runs the registered channel adapters behind one HTTP server
3
+ * and bridges inbound platform messages to Skyloom agents.
4
+ *
5
+ * platform → POST /webhook/<channel> → adapter.handleWebhook (verify+normalize)
6
+ * → route to agent → agent.chatStream → adapter.send (reply)
7
+ *
8
+ * The HTTP layer mirrors web/server.ts. Each channel handles its own signature
9
+ * verification and URL-verification handshake inside handleWebhook, so the
10
+ * gateway core never knows platform specifics. Agent replies are delivered
11
+ * asynchronously (after the webhook is acked) because all three platforms
12
+ * require a fast 200.
13
+ */
14
+ export interface GatewayOptions {
15
+ port?: number;
16
+ host?: string;
17
+ }
18
+ export declare function startGateway(opts?: GatewayOptions): Promise<void>;
19
+ //# sourceMappingURL=gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../src/gateway/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA0EH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,YAAY,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsF3E"}
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ /**
3
+ * Channel gateway — runs the registered channel adapters behind one HTTP server
4
+ * and bridges inbound platform messages to Skyloom agents.
5
+ *
6
+ * platform → POST /webhook/<channel> → adapter.handleWebhook (verify+normalize)
7
+ * → route to agent → agent.chatStream → adapter.send (reply)
8
+ *
9
+ * The HTTP layer mirrors web/server.ts. Each channel handles its own signature
10
+ * verification and URL-verification handshake inside handleWebhook, so the
11
+ * gateway core never knows platform specifics. Agent replies are delivered
12
+ * asynchronously (after the webhook is acked) because all three platforms
13
+ * require a fast 200.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.startGateway = startGateway;
17
+ const http_1 = require("http");
18
+ const logger_1 = require("../core/logger");
19
+ const factory_1 = require("../core/factory");
20
+ const registry_1 = require("./registry");
21
+ const types_1 = require("./types");
22
+ const log = (0, logger_1.getLogger)('gateway');
23
+ /** Collect the full request body. */
24
+ async function readBody(req) {
25
+ const chunks = [];
26
+ for await (const chunk of req)
27
+ chunks.push(chunk);
28
+ return Buffer.concat(chunks);
29
+ }
30
+ /** Run an agent turn for an inbound message and collect the final text reply. */
31
+ /** Build the agent prompt from an inbound message (text + media description). */
32
+ function buildPrompt(msg) {
33
+ const mediaDesc = (0, types_1.describeMedia)(msg.media);
34
+ if (!mediaDesc)
35
+ return msg.text;
36
+ return msg.text ? `${msg.text}\n\n(用户还发送了媒体: ${mediaDesc})` : `用户发送了媒体: ${mediaDesc}`;
37
+ }
38
+ /** Resolve the agent for a channel message. */
39
+ function resolveAgent(ctx, adapter) {
40
+ const cfgChannels = ctx.config.channels || {};
41
+ const agentName = cfgChannels[adapter.id]?.agent || adapter.defaultAgent || 'fair';
42
+ return ctx.agentMap.get(agentName) || ctx.agentMap.get('fair') || [...ctx.agentMap.values()][0];
43
+ }
44
+ /** Dispatch one inbound message to its agent and deliver the reply. */
45
+ async function dispatch(ctx, adapter, msg) {
46
+ const agent = resolveAgent(ctx, adapter);
47
+ if (!agent)
48
+ throw new Error('no agent available');
49
+ await agent.init();
50
+ const prompt = buildPrompt(msg);
51
+ // Streaming path: stream content chunks straight to the adapter (e.g. a Feishu
52
+ // card patched as text arrives). Falls back to collect-then-send otherwise.
53
+ const cfgStreaming = (ctx.config.channels || {})[adapter.id]?.streaming !== false;
54
+ if (adapter.sendStreaming && cfgStreaming) {
55
+ async function* contentChunks() {
56
+ try {
57
+ for await (const ev of agent.chatStream(prompt)) {
58
+ if (ev.type === 'content')
59
+ yield ev.text;
60
+ }
61
+ }
62
+ catch (e) {
63
+ log.warn('gateway_agent_failed', { channel: adapter.id, error: String(e) });
64
+ yield `\n[出错了] ${String(e)}`;
65
+ }
66
+ }
67
+ await adapter.sendStreaming(msg.replyTo, contentChunks());
68
+ return;
69
+ }
70
+ let text = '';
71
+ try {
72
+ for await (const ev of agent.chatStream(prompt)) {
73
+ if (ev.type === 'content')
74
+ text += ev.text;
75
+ }
76
+ }
77
+ catch (e) {
78
+ log.warn('gateway_agent_failed', { channel: adapter.id, error: String(e) });
79
+ text = `[出错了] ${String(e)}`;
80
+ }
81
+ await adapter.send(msg.replyTo, text.trim() || '(无回复)');
82
+ }
83
+ async function startGateway(opts = {}) {
84
+ const ctx = (0, factory_1.createSystemContext)();
85
+ const adapters = (0, registry_1.buildAdapters)(ctx.config.channels || {}, process.env);
86
+ if (adapters.size === 0) {
87
+ log.warn('gateway_no_channels', {});
88
+ process.stdout.write('\n ⚠ 没有启用任何渠道。在 ~/.skyloom/config.yaml 配置 channels.feishu / channels.wecom / channels.qq,\n' +
89
+ ' 或设置对应环境变量(如 FEISHU_APP_ID/FEISHU_APP_SECRET)。\n\n');
90
+ return;
91
+ }
92
+ for (const adapter of adapters.values()) {
93
+ if (adapter.start) {
94
+ try {
95
+ await adapter.start();
96
+ }
97
+ catch (e) {
98
+ log.warn('adapter_start_failed', { channel: adapter.id, error: String(e) });
99
+ }
100
+ }
101
+ }
102
+ const port = opts.port ?? Number(process.env.SKYLOOM_GATEWAY_PORT) ?? 8848;
103
+ // Gateways receive inbound webhooks from the platform's servers, so unlike the
104
+ // local web UI they must bind to a reachable interface by default.
105
+ const host = opts.host || process.env.SKYLOOM_GATEWAY_HOST || '0.0.0.0';
106
+ const server = (0, http_1.createServer)(async (req, res) => {
107
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
108
+ try {
109
+ if (url.pathname === '/health' && req.method === 'GET') {
110
+ res.writeHead(200, { 'Content-Type': 'application/json' })
111
+ .end(JSON.stringify({ ok: true, channels: [...adapters.keys()] }));
112
+ return;
113
+ }
114
+ const m = url.pathname.match(/^\/webhook\/([a-z0-9_-]+)$/i);
115
+ if (!m) {
116
+ res.writeHead(404).end('Not found');
117
+ return;
118
+ }
119
+ const adapter = adapters.get(m[1].toLowerCase());
120
+ if (!adapter) {
121
+ res.writeHead(404).end(`Unknown channel: ${m[1]}`);
122
+ return;
123
+ }
124
+ const raw = {
125
+ method: req.method || 'POST',
126
+ headers: req.headers,
127
+ query: url.searchParams,
128
+ body: await readBody(req),
129
+ };
130
+ const outcome = await adapter.handleWebhook(raw);
131
+ // Immediate HTTP response (challenge / ack / signature failure).
132
+ if (outcome.response) {
133
+ res.writeHead(outcome.response.status, {
134
+ 'Content-Type': outcome.response.contentType || 'text/plain; charset=utf-8',
135
+ }).end(outcome.response.body ?? '');
136
+ }
137
+ else {
138
+ res.writeHead(200, { 'Content-Type': 'application/json' }).end('{}');
139
+ }
140
+ // Route to an agent and deliver the reply asynchronously (after the ack).
141
+ if (outcome.message) {
142
+ const msg = outcome.message;
143
+ void dispatch(ctx, adapter, msg).catch((e) => log.warn('gateway_dispatch_failed', { channel: adapter.id, error: String(e) }));
144
+ }
145
+ }
146
+ catch (e) {
147
+ log.warn('gateway_request_error', { error: String(e) });
148
+ if (!res.headersSent)
149
+ res.writeHead(500).end('error');
150
+ }
151
+ });
152
+ await new Promise((resolve) => {
153
+ server.listen(port, host, () => {
154
+ const list = [...adapters.values()].map((a) => `${a.name}(/webhook/${a.id})`).join(' · ');
155
+ process.stdout.write(`\n 天空织机 · 渠道网关 · http://${host}:${port}\n 已启用: ${list}\n` +
156
+ ` 把对应平台的事件回调 URL 指向 http(s)://<你的域名>:${port}/webhook/<channel>\n\n`);
157
+ log.info('gateway_started', { port, host, channels: [...adapters.keys()] });
158
+ resolve();
159
+ });
160
+ });
161
+ const shutdown = async () => {
162
+ for (const a of adapters.values()) {
163
+ try {
164
+ await a.stop?.();
165
+ }
166
+ catch { /* ignore */ }
167
+ }
168
+ try {
169
+ await ctx.closeAll();
170
+ }
171
+ catch { /* ignore */ }
172
+ server.close();
173
+ };
174
+ process.on('SIGINT', () => { void shutdown().then(() => process.exit(0)); });
175
+ process.on('SIGTERM', () => { void shutdown().then(() => process.exit(0)); });
176
+ }
177
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../src/gateway/gateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Shared helpers for channel adapters: secret resolution (config value or env
3
+ * fallback), and a tiny JSON HTTP client. Kept dependency-light (axios is
4
+ * already a project dep) and injectable-free — adapters call these directly.
5
+ */
6
+ /**
7
+ * Resolve a secret/config value. Accepts a literal string, or an env-ref object
8
+ * `{ source: 'env', id: 'NAME' }` (OpenClaw-compatible), falling back to the
9
+ * given env var name. Returns undefined if unresolved.
10
+ */
11
+ export declare function resolveSecret(value: unknown, env: NodeJS.ProcessEnv, envFallback?: string): string | undefined;
12
+ /** POST JSON, return parsed JSON. Throws on non-2xx. */
13
+ export declare function postJson(url: string, body: any, opts?: {
14
+ headers?: Record<string, string>;
15
+ timeoutMs?: number;
16
+ }): Promise<any>;
17
+ /** GET JSON, return parsed JSON. Throws on non-2xx. */
18
+ export declare function getJson(url: string, opts?: {
19
+ headers?: Record<string, string>;
20
+ timeoutMs?: number;
21
+ }): Promise<any>;
22
+ /**
23
+ * A small token cache: fetch an access token via `fetcher`, cache it until it
24
+ * is near expiry, and refresh transparently. Channels (Feishu/WeCom) all need
25
+ * a short-lived tenant/access token; this avoids re-fetching per message.
26
+ */
27
+ export declare class TokenCache {
28
+ private fetcher;
29
+ private token;
30
+ private expiresAt;
31
+ constructor(fetcher: () => Promise<{
32
+ token: string;
33
+ expiresInSec: number;
34
+ }>);
35
+ get(): Promise<string>;
36
+ /** Force the next get() to refetch (e.g. after a 401). */
37
+ invalidate(): void;
38
+ }
39
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/gateway/helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,GAAG,SAAS,CAcpB;AAED,wDAAwD;AACxD,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,GAAG,EACT,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9D,OAAO,CAAC,GAAG,CAAC,CAOd;AAED,uDAAuD;AACvD,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9D,OAAO,CAAC,GAAG,CAAC,CAOd;AAED;;;;GAIG;AACH,qBAAa,UAAU;IAGT,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,SAAS,CAAK;gBACF,OAAO,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAE7E,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;IAS5B,0DAA0D;IAC1D,UAAU,IAAI,IAAI;CACnB"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Shared helpers for channel adapters: secret resolution (config value or env
4
+ * fallback), and a tiny JSON HTTP client. Kept dependency-light (axios is
5
+ * already a project dep) and injectable-free — adapters call these directly.
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.TokenCache = void 0;
12
+ exports.resolveSecret = resolveSecret;
13
+ exports.postJson = postJson;
14
+ exports.getJson = getJson;
15
+ const axios_1 = __importDefault(require("axios"));
16
+ /**
17
+ * Resolve a secret/config value. Accepts a literal string, or an env-ref object
18
+ * `{ source: 'env', id: 'NAME' }` (OpenClaw-compatible), falling back to the
19
+ * given env var name. Returns undefined if unresolved.
20
+ */
21
+ function resolveSecret(value, env, envFallback) {
22
+ if (typeof value === 'string' && value.trim())
23
+ return value.trim();
24
+ if (value && typeof value === 'object') {
25
+ const v = value;
26
+ if (v.source === 'env' && typeof v.id === 'string') {
27
+ const got = env[v.id];
28
+ if (got && got.trim())
29
+ return got.trim();
30
+ }
31
+ }
32
+ if (envFallback) {
33
+ const got = env[envFallback];
34
+ if (got && got.trim())
35
+ return got.trim();
36
+ }
37
+ return undefined;
38
+ }
39
+ /** POST JSON, return parsed JSON. Throws on non-2xx. */
40
+ async function postJson(url, body, opts) {
41
+ const res = await axios_1.default.post(url, body, {
42
+ headers: { 'Content-Type': 'application/json', ...(opts?.headers || {}) },
43
+ timeout: opts?.timeoutMs ?? 15000,
44
+ validateStatus: (s) => s >= 200 && s < 300,
45
+ });
46
+ return res.data;
47
+ }
48
+ /** GET JSON, return parsed JSON. Throws on non-2xx. */
49
+ async function getJson(url, opts) {
50
+ const res = await axios_1.default.get(url, {
51
+ headers: { Accept: 'application/json', ...(opts?.headers || {}) },
52
+ timeout: opts?.timeoutMs ?? 15000,
53
+ validateStatus: (s) => s >= 200 && s < 300,
54
+ });
55
+ return res.data;
56
+ }
57
+ /**
58
+ * A small token cache: fetch an access token via `fetcher`, cache it until it
59
+ * is near expiry, and refresh transparently. Channels (Feishu/WeCom) all need
60
+ * a short-lived tenant/access token; this avoids re-fetching per message.
61
+ */
62
+ class TokenCache {
63
+ constructor(fetcher) {
64
+ this.fetcher = fetcher;
65
+ this.token = null;
66
+ this.expiresAt = 0;
67
+ }
68
+ async get() {
69
+ const now = Date.now();
70
+ if (this.token && now < this.expiresAt - 60000)
71
+ return this.token;
72
+ const { token, expiresInSec } = await this.fetcher();
73
+ this.token = token;
74
+ this.expiresAt = now + Math.max(60, expiresInSec) * 1000;
75
+ return token;
76
+ }
77
+ /** Force the next get() to refetch (e.g. after a 401). */
78
+ invalidate() { this.token = null; this.expiresAt = 0; }
79
+ }
80
+ exports.TokenCache = TokenCache;
81
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/gateway/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Channel registry — maps channel ids to their adapter factories and builds the
3
+ * set of enabled adapters from the `channels` config block.
4
+ *
5
+ * A channel is enabled when its config block exists and is not `enabled: false`
6
+ * and the factory can resolve its required credentials (from config or env).
7
+ */
8
+ import type { ChannelAdapter } from './types';
9
+ /** Build all enabled, well-configured adapters from the channels config. */
10
+ export declare function buildAdapters(channelsCfg: Record<string, any>, env: NodeJS.ProcessEnv): Map<string, ChannelAdapter>;
11
+ export declare const SUPPORTED_CHANNELS: string[];
12
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/gateway/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,SAAS,CAAC;AAa9D,4EAA4E;AAC5E,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAChC,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAgB7B;AAED,eAAO,MAAM,kBAAkB,UAAyB,CAAC"}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * Channel registry — maps channel ids to their adapter factories and builds the
4
+ * set of enabled adapters from the `channels` config block.
5
+ *
6
+ * A channel is enabled when its config block exists and is not `enabled: false`
7
+ * and the factory can resolve its required credentials (from config or env).
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SUPPORTED_CHANNELS = void 0;
11
+ exports.buildAdapters = buildAdapters;
12
+ const logger_1 = require("../core/logger");
13
+ const feishu_1 = require("./channels/feishu");
14
+ const wecom_1 = require("./channels/wecom");
15
+ const qq_1 = require("./channels/qq");
16
+ const log = (0, logger_1.getLogger)('gateway-registry');
17
+ const FACTORIES = {
18
+ feishu: feishu_1.createFeishuAdapter,
19
+ wecom: wecom_1.createWecomAdapter,
20
+ qq: qq_1.createQQAdapter,
21
+ };
22
+ /** Build all enabled, well-configured adapters from the channels config. */
23
+ function buildAdapters(channelsCfg, env) {
24
+ const adapters = new Map();
25
+ for (const [id, factory] of Object.entries(FACTORIES)) {
26
+ const cfg = channelsCfg[id];
27
+ // A channel can be enabled purely via env vars (no config block) — pass an
28
+ // empty object so the factory still tries env fallback. Skip only when the
29
+ // block is explicitly disabled.
30
+ if (cfg && cfg.enabled === false)
31
+ continue;
32
+ try {
33
+ const adapter = factory(cfg || {}, env);
34
+ if (adapter)
35
+ adapters.set(id, adapter);
36
+ }
37
+ catch (e) {
38
+ log.warn('adapter_build_failed', { channel: id, error: String(e) });
39
+ }
40
+ }
41
+ return adapters;
42
+ }
43
+ exports.SUPPORTED_CHANNELS = Object.keys(FACTORIES);
44
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/gateway/registry.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAiBH,sCAmBC;AAlCD,2CAA2C;AAE3C,8CAAwD;AACxD,4CAAsD;AACtD,sCAAgD;AAEhD,MAAM,GAAG,GAAG,IAAA,kBAAS,EAAC,kBAAkB,CAAC,CAAC;AAE1C,MAAM,SAAS,GAAmC;IAChD,MAAM,EAAE,4BAAmB;IAC3B,KAAK,EAAE,0BAAkB;IACzB,EAAE,EAAE,oBAAe;CACpB,CAAC;AAEF,4EAA4E;AAC5E,SAAgB,aAAa,CAC3B,WAAgC,EAChC,GAAsB;IAEtB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;IACnD,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC5B,2EAA2E;QAC3E,2EAA2E;QAC3E,gCAAgC;QAChC,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,KAAK,KAAK;YAAE,SAAS;QAC3C,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;YACxC,IAAI,OAAO;gBAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAEY,QAAA,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC"}