skyloom 1.19.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.
- package/README.md +52 -6
- package/dist/cli/main.js +3 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/core/agent.d.ts +4 -1
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +83 -64
- package/dist/core/agent.js.map +1 -1
- package/dist/gateway/channels/feishu.d.ts +19 -0
- package/dist/gateway/channels/feishu.d.ts.map +1 -0
- package/dist/gateway/channels/feishu.js +186 -0
- package/dist/gateway/channels/feishu.js.map +1 -0
- package/dist/gateway/channels/qq.d.ts +25 -0
- package/dist/gateway/channels/qq.d.ts.map +1 -0
- package/dist/gateway/channels/qq.js +177 -0
- package/dist/gateway/channels/qq.js.map +1 -0
- package/dist/gateway/channels/wecom.d.ts +26 -0
- package/dist/gateway/channels/wecom.d.ts.map +1 -0
- package/dist/gateway/channels/wecom.js +177 -0
- package/dist/gateway/channels/wecom.js.map +1 -0
- package/dist/gateway/gateway.d.ts +19 -0
- package/dist/gateway/gateway.d.ts.map +1 -0
- package/dist/gateway/gateway.js +152 -0
- package/dist/gateway/gateway.js.map +1 -0
- package/dist/gateway/helpers.d.ts +39 -0
- package/dist/gateway/helpers.d.ts.map +1 -0
- package/dist/gateway/helpers.js +81 -0
- package/dist/gateway/helpers.js.map +1 -0
- package/dist/gateway/registry.d.ts +12 -0
- package/dist/gateway/registry.d.ts.map +1 -0
- package/dist/gateway/registry.js +44 -0
- package/dist/gateway/registry.js.map +1 -0
- package/dist/gateway/types.d.ts +81 -0
- package/dist/gateway/types.d.ts.map +1 -0
- package/dist/gateway/types.js +14 -0
- package/dist/gateway/types.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/main.ts +3 -0
- package/src/core/agent.ts +83 -62
- package/src/gateway/channels/feishu.ts +142 -0
- package/src/gateway/channels/qq.ts +140 -0
- package/src/gateway/channels/wecom.ts +142 -0
- package/src/gateway/gateway.ts +151 -0
- package/src/gateway/helpers.ts +82 -0
- package/src/gateway/registry.ts +45 -0
- package/src/gateway/types.ts +91 -0
- package/tests/agent.test.ts +45 -19
- package/tests/gateway.test.ts +221 -0
|
@@ -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"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel gateway contract — the minimal, channel-agnostic interface every
|
|
3
|
+
* messaging integration (Feishu / WeCom / QQ / …) implements.
|
|
4
|
+
*
|
|
5
|
+
* Distilled from OpenClaw's channel architecture (which spans ~250 files per
|
|
6
|
+
* channel) down to the essence: a channel receives an inbound webhook, the
|
|
7
|
+
* gateway normalizes it to an InboundMessage, routes it to an agent, and the
|
|
8
|
+
* channel sends the reply back out. Signature verification and the platform's
|
|
9
|
+
* URL-verification handshake live behind `handleWebhook`, so the gateway core
|
|
10
|
+
* stays platform-neutral.
|
|
11
|
+
*/
|
|
12
|
+
import type { IncomingHttpHeaders } from 'http';
|
|
13
|
+
/** A normalized inbound message from any channel. */
|
|
14
|
+
export interface InboundMessage {
|
|
15
|
+
/** Channel id, e.g. 'feishu'. */
|
|
16
|
+
channel: string;
|
|
17
|
+
/** Stable conversation/session key (chat id or user id) for memory routing. */
|
|
18
|
+
conversationId: string;
|
|
19
|
+
/** Platform user id of the sender. */
|
|
20
|
+
userId: string;
|
|
21
|
+
/** Plain text the user sent (media/stripped to text where possible). */
|
|
22
|
+
text: string;
|
|
23
|
+
/** Where to send the reply (channel-specific opaque target). */
|
|
24
|
+
replyTo: ReplyTarget;
|
|
25
|
+
/** Raw event for adapters that need more than the normalized fields. */
|
|
26
|
+
raw?: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** Opaque, channel-specific destination for an outbound reply. */
|
|
29
|
+
export interface ReplyTarget {
|
|
30
|
+
channel: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The result of an adapter handling a raw webhook request. Exactly one of:
|
|
35
|
+
* - `response`: reply immediately to the HTTP request (URL verification
|
|
36
|
+
* challenge, ack, or signature failure) and do NOT route to an agent.
|
|
37
|
+
* - `message`: a normalized inbound message to route to an agent. `response`
|
|
38
|
+
* may also be set to ack the webhook synchronously (most platforms require a
|
|
39
|
+
* fast 200) while the agent reply is delivered asynchronously via `send`.
|
|
40
|
+
* - neither: nothing to do (duplicate/ignored event); gateway returns 200.
|
|
41
|
+
*/
|
|
42
|
+
export interface WebhookOutcome {
|
|
43
|
+
response?: HttpResponse;
|
|
44
|
+
message?: InboundMessage;
|
|
45
|
+
}
|
|
46
|
+
export interface HttpResponse {
|
|
47
|
+
status: number;
|
|
48
|
+
body?: string;
|
|
49
|
+
contentType?: string;
|
|
50
|
+
}
|
|
51
|
+
/** The raw HTTP request passed to an adapter's webhook handler. */
|
|
52
|
+
export interface RawRequest {
|
|
53
|
+
method: string;
|
|
54
|
+
headers: IncomingHttpHeaders;
|
|
55
|
+
query: URLSearchParams;
|
|
56
|
+
body: Buffer;
|
|
57
|
+
}
|
|
58
|
+
/** A channel integration. Constructed from its config block by a factory. */
|
|
59
|
+
export interface ChannelAdapter {
|
|
60
|
+
/** Channel id (matches the /webhook/<id> route and config key). */
|
|
61
|
+
readonly id: string;
|
|
62
|
+
/** Human label for logs/status. */
|
|
63
|
+
readonly name: string;
|
|
64
|
+
/** Default agent to route this channel's messages to (config override wins). */
|
|
65
|
+
readonly defaultAgent?: string;
|
|
66
|
+
/** Optional one-time setup (token prefetch, websocket connect, …). */
|
|
67
|
+
start?(): Promise<void>;
|
|
68
|
+
/** Optional teardown on gateway shutdown. */
|
|
69
|
+
stop?(): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Handle a raw webhook request: verify signature, answer the platform's
|
|
72
|
+
* URL-verification handshake, decrypt if needed, and either return an
|
|
73
|
+
* immediate HTTP response and/or a normalized message to route.
|
|
74
|
+
*/
|
|
75
|
+
handleWebhook(req: RawRequest): Promise<WebhookOutcome>;
|
|
76
|
+
/** Send a text reply back to the channel. */
|
|
77
|
+
send(target: ReplyTarget, text: string): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/** Factory signature: build an adapter from its config block (or null if disabled/misconfigured). */
|
|
80
|
+
export type ChannelFactory = (cfg: any, env: NodeJS.ProcessEnv) => ChannelAdapter | null;
|
|
81
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/gateway/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAEhD,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,WAAW,CAAC;IACrB,wEAAwE;IACxE,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,mEAAmE;AACnE,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,mBAAmB,CAAC;IAC7B,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAE/B,sEAAsE;IACtE,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,6CAA6C;IAC7C,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;;OAIG;IACH,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAExD,6CAA6C;IAC7C,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAED,qGAAqG;AACrG,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,cAAc,GAAG,IAAI,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Channel gateway contract — the minimal, channel-agnostic interface every
|
|
4
|
+
* messaging integration (Feishu / WeCom / QQ / …) implements.
|
|
5
|
+
*
|
|
6
|
+
* Distilled from OpenClaw's channel architecture (which spans ~250 files per
|
|
7
|
+
* channel) down to the essence: a channel receives an inbound webhook, the
|
|
8
|
+
* gateway normalizes it to an InboundMessage, routes it to an agent, and the
|
|
9
|
+
* channel sends the reply back out. Signature verification and the platform's
|
|
10
|
+
* URL-verification handshake live behind `handleWebhook`, so the gateway core
|
|
11
|
+
* stays platform-neutral.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/gateway/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG"}
|
package/package.json
CHANGED
package/src/cli/main.ts
CHANGED
|
@@ -35,6 +35,9 @@ program.command("task").argument("[goal]", "task goal")
|
|
|
35
35
|
program.command("web").option("-p,--port <p>", "port", "7777")
|
|
36
36
|
.action((o: { port?: string }) => { import("../web/server").then(m => m.startWebServer(parseInt(o.port || "7777"))); });
|
|
37
37
|
program.command("mcp").action(() => { import("../core/mcp_server").then(m => m.startMCPServer()); });
|
|
38
|
+
program.command("gateway").description("Run the channel gateway (Feishu / WeCom / QQ)")
|
|
39
|
+
.option("-p,--port <p>", "port", "8848")
|
|
40
|
+
.action((o: { port?: string }) => { import("../gateway/gateway").then(m => m.startGateway({ port: parseInt(o.port || "8848") })); });
|
|
38
41
|
program.command("config").action(() => { const c = loadConfig(); process.stdout.write(chalk.cyan("\nConfig: ") + USER_CONFIG_DIR + "\n"); for (const [n, a] of Object.entries(c.agents || {})) process.stdout.write(` ${chalk.bold(n)}: ${(a as any).model || "default"}\n`); });
|
|
39
42
|
program.command("init").action(() => { if (!fs.existsSync(USER_CONFIG_DIR)) fs.mkdirSync(USER_CONFIG_DIR, { recursive: true }); process.stdout.write(chalk.green("✓ ") + USER_CONFIG_DIR + "\n"); });
|
|
40
43
|
program.command("apikey").description("Manage API keys (persisted to ~/.skyloom/config.yaml)")
|