skyloom 1.20.0 → 1.21.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 +42 -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 +186 -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 +177 -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 +177 -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 +152 -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 +81 -0
  29. package/dist/gateway/types.d.ts.map +1 -0
  30. package/dist/gateway/types.js +14 -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 +142 -0
  35. package/src/gateway/channels/qq.ts +140 -0
  36. package/src/gateway/channels/wecom.ts +142 -0
  37. package/src/gateway/gateway.ts +151 -0
  38. package/src/gateway/helpers.ts +82 -0
  39. package/src/gateway/registry.ts +45 -0
  40. package/src/gateway/types.ts +91 -0
  41. package/tests/gateway.test.ts +221 -0
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ /**
3
+ * QQ official-bot channel adapter (webhook mode, QQ 频道/群 机器人).
4
+ *
5
+ * Auth/crypto: QQ uses Ed25519. The signing seed is the bot secret repeated to
6
+ * 32 bytes. Two webhook concerns:
7
+ * - validation (op 13): the platform sends { d: { plain_token, event_ts } };
8
+ * we reply { plain_token, signature } where signature = ed25519(event_ts +
9
+ * plain_token).
10
+ * - event signature: each push carries X-Signature-Ed25519 (hex) and
11
+ * X-Signature-Timestamp; verify ed25519 over (timestamp + body).
12
+ *
13
+ * Inbound message events: GROUP_AT_MESSAGE_CREATE / C2C_MESSAGE_CREATE /
14
+ * AT_MESSAGE_CREATE. Outbound: getAppAccessToken (cached) then the v2 messages
15
+ * API (passive reply via msg_id). Config (channels.qq): { appId, secret,
16
+ * agent? }. Env fallback: QQ_BOT_APPID, QQ_BOT_SECRET.
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.qqSeed = qqSeed;
53
+ exports.qqSignValidation = qqSignValidation;
54
+ exports.qqVerify = qqVerify;
55
+ exports.createQQAdapter = createQQAdapter;
56
+ const crypto = __importStar(require("crypto"));
57
+ const logger_1 = require("../../core/logger");
58
+ const helpers_1 = require("../helpers");
59
+ const log = (0, logger_1.getLogger)('channel-qq');
60
+ const PKCS8_ED25519_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
61
+ /** Repeat the bot secret to a 32-byte Ed25519 seed (QQ's scheme). */
62
+ function qqSeed(secret) {
63
+ let s = secret;
64
+ while (s.length < 32)
65
+ s = s + s;
66
+ return Buffer.from(s.slice(0, 32), 'utf8');
67
+ }
68
+ function privKeyFromSeed(seed) {
69
+ return crypto.createPrivateKey({ key: Buffer.concat([PKCS8_ED25519_PREFIX, seed]), format: 'der', type: 'pkcs8' });
70
+ }
71
+ /** Sign `event_ts + plain_token` for the validation handshake; returns hex. */
72
+ function qqSignValidation(secret, eventTs, plainToken) {
73
+ const priv = privKeyFromSeed(qqSeed(secret));
74
+ return crypto.sign(null, Buffer.from(eventTs + plainToken, 'utf8'), priv).toString('hex');
75
+ }
76
+ /** Verify an event push signature (hex) over `timestamp + body`. */
77
+ function qqVerify(secret, timestamp, body, signatureHex) {
78
+ try {
79
+ const pub = crypto.createPublicKey(privKeyFromSeed(qqSeed(secret)));
80
+ const msg = Buffer.concat([Buffer.from(timestamp, 'utf8'), body]);
81
+ return crypto.verify(null, msg, pub, Buffer.from(signatureHex, 'hex'));
82
+ }
83
+ catch (e) {
84
+ log.warn('qq_verify_error', { error: String(e) });
85
+ return false;
86
+ }
87
+ }
88
+ function createQQAdapter(cfg, env) {
89
+ const appId = (0, helpers_1.resolveSecret)(cfg.appId != null ? String(cfg.appId) : undefined, env, 'QQ_BOT_APPID');
90
+ const secret = (0, helpers_1.resolveSecret)(cfg.secret, env, 'QQ_BOT_SECRET');
91
+ if (!appId || !secret)
92
+ return null;
93
+ const tokenCache = new helpers_1.TokenCache(async () => {
94
+ const data = await (0, helpers_1.postJson)('https://bots.qq.com/app/getAppAccessToken', {
95
+ appId, clientSecret: secret,
96
+ });
97
+ if (!data.access_token)
98
+ throw new Error(`qq token error: ${JSON.stringify(data).slice(0, 120)}`);
99
+ return { token: data.access_token, expiresInSec: Number(data.expires_in) || 7200 };
100
+ });
101
+ const authHeaders = async () => ({ Authorization: `QQBot ${await tokenCache.get()}`, 'X-Union-Appid': appId });
102
+ return {
103
+ id: 'qq',
104
+ name: 'QQ Bot',
105
+ defaultAgent: cfg.agent || 'fair',
106
+ async handleWebhook(req) {
107
+ let payload;
108
+ try {
109
+ payload = JSON.parse(req.body.toString('utf8') || '{}');
110
+ }
111
+ catch {
112
+ return { response: { status: 400, body: 'bad json' } };
113
+ }
114
+ // Validation handshake (op 13) — no signature header on this one.
115
+ if (payload.op === 13 && payload.d?.plain_token && payload.d?.event_ts) {
116
+ const signature = qqSignValidation(secret, String(payload.d.event_ts), String(payload.d.plain_token));
117
+ return { response: { status: 200, contentType: 'application/json', body: JSON.stringify({ plain_token: payload.d.plain_token, signature }) } };
118
+ }
119
+ // Verify the event push signature.
120
+ const sig = req.headers['x-signature-ed25519'] || '';
121
+ const ts = req.headers['x-signature-timestamp'] || '';
122
+ if (sig && ts && !qqVerify(secret, ts, req.body, sig)) {
123
+ return { response: { status: 403, body: 'bad signature' } };
124
+ }
125
+ if (payload.op !== 0)
126
+ return { response: { status: 200, body: '' } }; // not a dispatch
127
+ const t = payload.t;
128
+ const d = payload.d || {};
129
+ const content = String(d.content || '').replace(/<@!?\d+>/g, '').trim();
130
+ const msgId = d.id;
131
+ let replyTo = null;
132
+ if (t === 'GROUP_AT_MESSAGE_CREATE')
133
+ replyTo = { channel: 'qq', kind: 'group', groupOpenid: d.group_openid, msgId };
134
+ else if (t === 'C2C_MESSAGE_CREATE')
135
+ replyTo = { channel: 'qq', kind: 'c2c', userOpenid: d.author?.user_openid, msgId };
136
+ else if (t === 'AT_MESSAGE_CREATE' || t === 'MESSAGE_CREATE')
137
+ replyTo = { channel: 'qq', kind: 'channel', channelId: d.channel_id, msgId };
138
+ if (!replyTo || !content)
139
+ return { response: { status: 200, body: '' } };
140
+ const userId = d.author?.user_openid || d.author?.id || d.author?.member_openid || 'unknown';
141
+ return {
142
+ response: { status: 200, body: '' },
143
+ message: {
144
+ channel: 'qq',
145
+ conversationId: replyTo.groupOpenid || replyTo.channelId || userId,
146
+ userId,
147
+ text: content,
148
+ replyTo,
149
+ raw: payload,
150
+ },
151
+ };
152
+ },
153
+ async send(target, text) {
154
+ const headers = { ...(await authHeaders()), 'Content-Type': 'application/json' };
155
+ const msgId = target.msgId;
156
+ const payload = { msg_type: 0, content: text };
157
+ if (msgId)
158
+ payload.msg_id = msgId; // passive reply within the allowed window
159
+ let url;
160
+ if (target.kind === 'group')
161
+ url = `https://api.sgroup.qq.com/v2/groups/${target.groupOpenid}/messages`;
162
+ else if (target.kind === 'c2c')
163
+ url = `https://api.sgroup.qq.com/v2/users/${target.userOpenid}/messages`;
164
+ else
165
+ url = `https://api.sgroup.qq.com/channels/${target.channelId}/messages`;
166
+ try {
167
+ await (0, helpers_1.postJson)(url, payload, { headers });
168
+ }
169
+ catch (e) {
170
+ if (e?.response?.status === 401)
171
+ tokenCache.invalidate();
172
+ throw new Error(`qq send error: ${e?.response?.status || ''} ${String(e?.message || e).slice(0, 120)}`);
173
+ }
174
+ },
175
+ };
176
+ }
177
+ //# sourceMappingURL=qq.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq.js","sourceRoot":"","sources":["../../../src/gateway/channels/qq.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * WeCom (企业微信) channel adapter.
3
+ *
4
+ * Uses the official "receive messages" callback with the standard WeWork
5
+ * crypto: msg_signature = sha1(sort(token, timestamp, nonce, echostr|encrypt)),
6
+ * and AES-256-CBC (PKCS7) where the key is base64(EncodingAESKey + "=") and the
7
+ * plaintext is [16B random][4B big-endian msg-len][msg][receiveid].
8
+ *
9
+ * Inbound is XML. GET verifies the callback URL (echo the decrypted echostr);
10
+ * POST carries the encrypted message. We extract text from <Content>.
11
+ *
12
+ * Outbound uses the application message API (message/send) with the agent's
13
+ * gettoken. Config (channels.wecom): { corpId, corpSecret, token, encodingAesKey,
14
+ * agentId, agent? }. Env fallback: WECOM_CORP_ID, WECOM_CORP_SECRET,
15
+ * WECOM_TOKEN, WECOM_AES_KEY, WECOM_AGENT_ID.
16
+ */
17
+ import type { ChannelAdapter } from '../types';
18
+ /** WeWork msg_signature: sha1 over the sorted concatenation. */
19
+ export declare function wecomSignature(token: string, timestamp: string, nonce: string, encrypt: string): string;
20
+ /** Decrypt a WeWork AES message. Returns { message, receiveId }. */
21
+ export declare function decryptWecom(encrypt: string, encodingAesKey: string): {
22
+ message: string;
23
+ receiveId: string;
24
+ };
25
+ export declare function createWecomAdapter(cfg: any, env: NodeJS.ProcessEnv): ChannelAdapter | null;
26
+ //# sourceMappingURL=wecom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wecom.d.ts","sourceRoot":"","sources":["../../../src/gateway/channels/wecom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,cAAc,EAA2C,MAAM,UAAU,CAAC;AAIxF,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAGvG;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAe5G;AASD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,GAAG,IAAI,CAsF1F"}
@@ -0,0 +1,177 @@
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
+ if (msgType === 'text')
147
+ text = xmlField(inner, 'Content').trim();
148
+ else
149
+ text = `[${msgType} 消息]`;
150
+ // Ack the callback immediately (empty 200); reply is pushed via the API.
151
+ return {
152
+ response: { status: 200, body: '' },
153
+ message: text ? {
154
+ channel: 'wecom',
155
+ conversationId: fromUser,
156
+ userId: fromUser,
157
+ text,
158
+ replyTo: { channel: 'wecom', toUser: fromUser },
159
+ raw: inner,
160
+ } : undefined,
161
+ };
162
+ },
163
+ async send(target, text) {
164
+ const toUser = target.toUser;
165
+ if (!toUser || !agentId)
166
+ return;
167
+ const accessToken = await tokenCache.get();
168
+ 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 } });
169
+ if (data.errcode !== 0) {
170
+ if (data.errcode === 42001 || data.errcode === 40014)
171
+ tokenCache.invalidate();
172
+ throw new Error(`wecom send error ${data.errcode}: ${data.errmsg}`);
173
+ }
174
+ },
175
+ };
176
+ }
177
+ //# 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;AAyCH,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,CA4F3E"}
@@ -0,0 +1,152 @@
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 log = (0, logger_1.getLogger)('gateway');
22
+ /** Collect the full request body. */
23
+ async function readBody(req) {
24
+ const chunks = [];
25
+ for await (const chunk of req)
26
+ chunks.push(chunk);
27
+ return Buffer.concat(chunks);
28
+ }
29
+ /** Run an agent turn for an inbound message and collect the final text reply. */
30
+ async function runAgent(ctx, adapter, msg) {
31
+ const cfgChannels = ctx.config.channels || {};
32
+ const agentName = cfgChannels[adapter.id]?.agent || adapter.defaultAgent || 'fair';
33
+ const agent = ctx.agentMap.get(agentName) || ctx.agentMap.get('fair') || [...ctx.agentMap.values()][0];
34
+ if (!agent)
35
+ throw new Error('no agent available');
36
+ await agent.init();
37
+ let text = '';
38
+ try {
39
+ for await (const ev of agent.chatStream(msg.text)) {
40
+ if (ev.type === 'content')
41
+ text += ev.text;
42
+ }
43
+ }
44
+ catch (e) {
45
+ log.warn('gateway_agent_failed', { channel: adapter.id, error: String(e) });
46
+ return `[出错了] ${String(e)}`;
47
+ }
48
+ return text.trim() || '(无回复)';
49
+ }
50
+ async function startGateway(opts = {}) {
51
+ const ctx = (0, factory_1.createSystemContext)();
52
+ const adapters = (0, registry_1.buildAdapters)(ctx.config.channels || {}, process.env);
53
+ if (adapters.size === 0) {
54
+ log.warn('gateway_no_channels', {});
55
+ process.stdout.write('\n ⚠ 没有启用任何渠道。在 ~/.skyloom/config.yaml 配置 channels.feishu / channels.wecom / channels.qq,\n' +
56
+ ' 或设置对应环境变量(如 FEISHU_APP_ID/FEISHU_APP_SECRET)。\n\n');
57
+ return;
58
+ }
59
+ for (const adapter of adapters.values()) {
60
+ if (adapter.start) {
61
+ try {
62
+ await adapter.start();
63
+ }
64
+ catch (e) {
65
+ log.warn('adapter_start_failed', { channel: adapter.id, error: String(e) });
66
+ }
67
+ }
68
+ }
69
+ const port = opts.port ?? Number(process.env.SKYLOOM_GATEWAY_PORT) ?? 8848;
70
+ // Gateways receive inbound webhooks from the platform's servers, so unlike the
71
+ // local web UI they must bind to a reachable interface by default.
72
+ const host = opts.host || process.env.SKYLOOM_GATEWAY_HOST || '0.0.0.0';
73
+ const server = (0, http_1.createServer)(async (req, res) => {
74
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
75
+ try {
76
+ if (url.pathname === '/health' && req.method === 'GET') {
77
+ res.writeHead(200, { 'Content-Type': 'application/json' })
78
+ .end(JSON.stringify({ ok: true, channels: [...adapters.keys()] }));
79
+ return;
80
+ }
81
+ const m = url.pathname.match(/^\/webhook\/([a-z0-9_-]+)$/i);
82
+ if (!m) {
83
+ res.writeHead(404).end('Not found');
84
+ return;
85
+ }
86
+ const adapter = adapters.get(m[1].toLowerCase());
87
+ if (!adapter) {
88
+ res.writeHead(404).end(`Unknown channel: ${m[1]}`);
89
+ return;
90
+ }
91
+ const raw = {
92
+ method: req.method || 'POST',
93
+ headers: req.headers,
94
+ query: url.searchParams,
95
+ body: await readBody(req),
96
+ };
97
+ const outcome = await adapter.handleWebhook(raw);
98
+ // Immediate HTTP response (challenge / ack / signature failure).
99
+ if (outcome.response) {
100
+ res.writeHead(outcome.response.status, {
101
+ 'Content-Type': outcome.response.contentType || 'text/plain; charset=utf-8',
102
+ }).end(outcome.response.body ?? '');
103
+ }
104
+ else {
105
+ res.writeHead(200, { 'Content-Type': 'application/json' }).end('{}');
106
+ }
107
+ // Route to an agent and deliver the reply asynchronously (after the ack).
108
+ if (outcome.message) {
109
+ const msg = outcome.message;
110
+ void (async () => {
111
+ try {
112
+ const reply = await runAgent(ctx, adapter, msg);
113
+ await adapter.send(msg.replyTo, reply);
114
+ }
115
+ catch (e) {
116
+ log.warn('gateway_dispatch_failed', { channel: adapter.id, error: String(e) });
117
+ }
118
+ })();
119
+ }
120
+ }
121
+ catch (e) {
122
+ log.warn('gateway_request_error', { error: String(e) });
123
+ if (!res.headersSent)
124
+ res.writeHead(500).end('error');
125
+ }
126
+ });
127
+ await new Promise((resolve) => {
128
+ server.listen(port, host, () => {
129
+ const list = [...adapters.values()].map((a) => `${a.name}(/webhook/${a.id})`).join(' · ');
130
+ process.stdout.write(`\n 天空织机 · 渠道网关 · http://${host}:${port}\n 已启用: ${list}\n` +
131
+ ` 把对应平台的事件回调 URL 指向 http(s)://<你的域名>:${port}/webhook/<channel>\n\n`);
132
+ log.info('gateway_started', { port, host, channels: [...adapters.keys()] });
133
+ resolve();
134
+ });
135
+ });
136
+ const shutdown = async () => {
137
+ for (const a of adapters.values()) {
138
+ try {
139
+ await a.stop?.();
140
+ }
141
+ catch { /* ignore */ }
142
+ }
143
+ try {
144
+ await ctx.closeAll();
145
+ }
146
+ catch { /* ignore */ }
147
+ server.close();
148
+ };
149
+ process.on('SIGINT', () => { void shutdown().then(() => process.exit(0)); });
150
+ process.on('SIGTERM', () => { void shutdown().then(() => process.exit(0)); });
151
+ }
152
+ //# 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"}