@router-agent/channel 0.0.0-stage → 0.3.31
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 +12 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -0
- package/dist/feishu/index.js +146 -0
- package/dist/feishu/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/ingress.d.ts +46 -0
- package/dist/ingress.js +90 -0
- package/dist/ingress.js.map +1 -0
- package/dist/legacy.js +26 -0
- package/dist/legacy.js.map +1 -0
- package/dist/plugins/loader.d.ts +33 -0
- package/dist/plugins/loader.js +79 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/types.d.ts +30 -0
- package/dist/plugins/types.js +2 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/port.d.ts +111 -0
- package/dist/port.js +8 -0
- package/dist/port.js.map +1 -0
- package/dist/registry.d.ts +21 -0
- package/dist/registry.js +50 -0
- package/dist/registry.js.map +1 -0
- package/dist/router.d.ts +24 -0
- package/dist/router.js +36 -0
- package/dist/router.js.map +1 -0
- package/dist/sources/feishu-bot-admin.js +119 -0
- package/dist/sources/feishu-bot-admin.js.map +1 -0
- package/dist/sources/feishu-directory.js +77 -0
- package/dist/sources/feishu-directory.js.map +1 -0
- package/dist/sources/feishu-identity.js +646 -0
- package/dist/sources/feishu-identity.js.map +1 -0
- package/dist/sources/feishu-image.js +338 -0
- package/dist/sources/feishu-image.js.map +1 -0
- package/dist/sources/feishu.js +645 -0
- package/dist/sources/feishu.js.map +1 -0
- package/dist/sources/lark.js +214 -0
- package/dist/sources/lark.js.map +1 -0
- package/package.json +35 -4
- package/README.md +0 -3
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** 飞书通道自己的配置视图;宿主 AppConfig 结构上兼容,通道包不反向依赖宿主。 */
|
|
2
|
+
export interface FeishuChannelSettings {
|
|
3
|
+
appId: string;
|
|
4
|
+
allowed: string[];
|
|
5
|
+
chats: import('@router-agent/core/config.js').ChatAdmissionSettings[];
|
|
6
|
+
directMessages: boolean;
|
|
7
|
+
botOpenIds: string[];
|
|
8
|
+
botNames: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface FeishuIdentityConfigView {
|
|
11
|
+
feishu: FeishuChannelSettings;
|
|
12
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../packages/channel/src/config.ts"],"names":[],"mappings":""}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** 通道层错误:区分可重试与永久失败,避免 Outbox 对永久错误无限退避。 */
|
|
2
|
+
export declare class ChannelError extends Error {
|
|
3
|
+
readonly channelId: string;
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly retryable: boolean;
|
|
6
|
+
constructor(channelId: string, code: string, message: string, retryable?: boolean);
|
|
7
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** 通道层错误:区分可重试与永久失败,避免 Outbox 对永久错误无限退避。 */
|
|
2
|
+
export class ChannelError extends Error {
|
|
3
|
+
channelId;
|
|
4
|
+
code;
|
|
5
|
+
retryable;
|
|
6
|
+
constructor(channelId, code, message, retryable = true) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.channelId = channelId;
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.retryable = retryable;
|
|
11
|
+
this.name = 'ChannelError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../../packages/channel/src/errors.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,MAAM,OAAO,YAAa,SAAQ,KAAK;IAE1B;IACA;IAEA;IAJX,YACW,SAAiB,EACjB,IAAY,EACrB,OAAe,EACN,YAAY,IAAI;QAEzB,KAAK,CAAC,OAAO,CAAC,CAAA;QALL,cAAS,GAAT,SAAS,CAAQ;QACjB,SAAI,GAAJ,IAAI,CAAQ;QAEZ,cAAS,GAAT,SAAS,CAAO;QAGzB,IAAI,CAAC,IAAI,GAAG,cAAc,CAAA;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { noopLogger } from '@router-agent/infra/logger.js';
|
|
2
|
+
import { FeishuBotAdmin } from '../sources/feishu-bot-admin.js';
|
|
3
|
+
import { attachImageText } from '../sources/feishu-image.js';
|
|
4
|
+
import { FeishuConsumer, FeishuSender, attachQuotedMessage, normalizeFeishuEventDetailed, shouldSendAsText, } from '../sources/feishu.js';
|
|
5
|
+
import { createLarkRunner, larkEnv } from '../sources/lark.js';
|
|
6
|
+
import { DEFAULT_CHANNEL_ID } from '../port.js';
|
|
7
|
+
/**
|
|
8
|
+
* 飞书通道适配器。
|
|
9
|
+
*
|
|
10
|
+
* 把原先散在 CLI 与 Service 里的飞书细节收口到这里:事件消费、归一化、引用/图片富化、
|
|
11
|
+
* 文本/卡片渲染、回执、云文档、凭据与授权。内核只看到 `ChannelAdapter`。
|
|
12
|
+
*/
|
|
13
|
+
export class FeishuChannel {
|
|
14
|
+
id = DEFAULT_CHANNEL_ID;
|
|
15
|
+
kind = 'feishu';
|
|
16
|
+
capabilities;
|
|
17
|
+
admin;
|
|
18
|
+
config;
|
|
19
|
+
runner;
|
|
20
|
+
reaction;
|
|
21
|
+
directory;
|
|
22
|
+
transcriber;
|
|
23
|
+
logger;
|
|
24
|
+
sender;
|
|
25
|
+
opts;
|
|
26
|
+
consumer = null;
|
|
27
|
+
constructor(opts) {
|
|
28
|
+
this.opts = opts;
|
|
29
|
+
this.config = opts.config;
|
|
30
|
+
this.runner = opts.runner;
|
|
31
|
+
this.reaction = opts.reaction ?? '';
|
|
32
|
+
this.directory = opts.directory;
|
|
33
|
+
this.transcriber = opts.transcriber;
|
|
34
|
+
this.logger = opts.logger ?? noopLogger;
|
|
35
|
+
this.sender = opts.sender ?? new FeishuSender(opts.runner, this.logger);
|
|
36
|
+
this.admin = opts.admin ?? new FeishuBotAdmin({ lark: opts.runner });
|
|
37
|
+
this.capabilities = {
|
|
38
|
+
threadReply: true,
|
|
39
|
+
ack: Boolean(opts.reaction),
|
|
40
|
+
richText: true,
|
|
41
|
+
documents: true,
|
|
42
|
+
directory: true,
|
|
43
|
+
attachmentsIn: true,
|
|
44
|
+
attachmentsOut: false,
|
|
45
|
+
idempotency: true,
|
|
46
|
+
maxTextLength: 3500,
|
|
47
|
+
directMessage: opts.config.directMessages,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
normalize(raw) {
|
|
51
|
+
const event = (raw && typeof raw === 'object' ? raw : {});
|
|
52
|
+
const chatId = String(event.chat_id ?? '');
|
|
53
|
+
const targetName = this.directory?.nameOf(chatId) ?? '';
|
|
54
|
+
const result = normalizeFeishuEventDetailed(event, this.config, targetName, this.opts.allowUnaddressed);
|
|
55
|
+
if (!result.trigger) {
|
|
56
|
+
return {
|
|
57
|
+
dropped: result.dropped,
|
|
58
|
+
...(result.chatId ? { targetId: result.chatId } : {}),
|
|
59
|
+
...(result.chatId ? { targetName } : {}),
|
|
60
|
+
...(result.actorId ? { actorId: result.actorId } : {}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
trigger: { ...result.trigger, channelId: this.id },
|
|
65
|
+
targetId: result.chatId,
|
|
66
|
+
targetName,
|
|
67
|
+
...(result.actorId ? { actorId: result.actorId } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** 图片消息要先下载识别,耗时较久;CLI 据此决定是否提前加回执。 */
|
|
71
|
+
needsEnrichment(trigger) {
|
|
72
|
+
const raw = trigger.raw;
|
|
73
|
+
return Boolean(trigger.mediaOnly) || Array.isArray(raw?.imageKeys);
|
|
74
|
+
}
|
|
75
|
+
async enrich(trigger) {
|
|
76
|
+
const quoted = await attachQuotedMessage(trigger, this.runner, { logger: this.logger });
|
|
77
|
+
return attachImageText(quoted, {
|
|
78
|
+
runner: this.runner,
|
|
79
|
+
transcriber: this.transcriber,
|
|
80
|
+
...(this.opts.imagesRoot ? { imagesRoot: this.opts.imagesRoot } : {}),
|
|
81
|
+
logger: this.logger,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async send(message) {
|
|
85
|
+
const text = message.text.trim();
|
|
86
|
+
if (!text)
|
|
87
|
+
return {};
|
|
88
|
+
const key = message.idempotencyKey;
|
|
89
|
+
if (shouldSendAsText(text)) {
|
|
90
|
+
if (message.threadRef)
|
|
91
|
+
await this.sender.reply(message.threadRef, text, key);
|
|
92
|
+
else
|
|
93
|
+
await this.sender.send(message.targetId, text, key);
|
|
94
|
+
return { degradedTo: 'plain' };
|
|
95
|
+
}
|
|
96
|
+
// Markdown 走互动卡片;卡片失败时 FeishuSender 内部降级为 post 富文本。
|
|
97
|
+
if (message.threadRef)
|
|
98
|
+
await this.sender.replyCard(message.threadRef, text, key);
|
|
99
|
+
else
|
|
100
|
+
await this.sender.sendCard(message.targetId, text, key);
|
|
101
|
+
return { degradedTo: 'rich' };
|
|
102
|
+
}
|
|
103
|
+
async ack(messageId) {
|
|
104
|
+
if (!this.reaction)
|
|
105
|
+
return null;
|
|
106
|
+
return this.sender.react(messageId, this.reaction);
|
|
107
|
+
}
|
|
108
|
+
async clearAck(messageId, handle) {
|
|
109
|
+
await this.sender.unreact(messageId, handle);
|
|
110
|
+
}
|
|
111
|
+
async createDocument(title, markdown) {
|
|
112
|
+
return this.sender.createDocument(title, markdown);
|
|
113
|
+
}
|
|
114
|
+
async start(sink) {
|
|
115
|
+
if (this.consumer)
|
|
116
|
+
return;
|
|
117
|
+
this.consumer = new FeishuConsumer({
|
|
118
|
+
...(this.opts.bin ? { bin: this.opts.bin } : {}),
|
|
119
|
+
env: this.opts.env ?? larkEnv(),
|
|
120
|
+
onEvent: sink,
|
|
121
|
+
...(this.opts.hooks?.onReady ? { onReady: this.opts.hooks.onReady } : {}),
|
|
122
|
+
...(this.opts.hooks?.onReconnect ? { onReconnect: this.opts.hooks.onReconnect } : {}),
|
|
123
|
+
...(this.opts.hooks?.onError ? { onError: this.opts.hooks.onError } : {}),
|
|
124
|
+
...(this.opts.hooks?.onLine ? { onLine: this.opts.hooks.onLine } : {}),
|
|
125
|
+
});
|
|
126
|
+
this.consumer.start();
|
|
127
|
+
}
|
|
128
|
+
stop() {
|
|
129
|
+
this.consumer?.stop();
|
|
130
|
+
this.consumer = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** 生产装配:用固定 profile 的 lark-cli runner 构造飞书通道。 */
|
|
134
|
+
export function createFeishuChannel(opts) {
|
|
135
|
+
return new FeishuChannel({ ...opts, runner: opts.runner ?? createLarkRunner() });
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* `ra doc` 用的轻量文档器。
|
|
139
|
+
*
|
|
140
|
+
* 一期仍直连飞书;等接入第二通道时改走 Router API,按工单所在通道选择实现。
|
|
141
|
+
*/
|
|
142
|
+
export function createFeishuDocumenter(opts = {}) {
|
|
143
|
+
const sender = new FeishuSender(opts.runner ?? createLarkRunner());
|
|
144
|
+
return { createDocument: (title, markdown) => sender.createDocument(title, markdown) };
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/channel/src/feishu/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './port.js';
|
|
2
|
+
export { ChannelRegistry } from './registry.js';
|
|
3
|
+
export { ChannelRouter } from './router.js';
|
|
4
|
+
export { ChannelError } from './errors.js';
|
|
5
|
+
export { ChannelIngress, type AckStore, type IngressSink } from './ingress.js';
|
|
6
|
+
export * from './plugins/types.js';
|
|
7
|
+
export { DEFAULT_CHANNEL_PLUGINS, channelPluginEntryOf, describeChannelPluginLoadError, loadChannelPlugins, } from './plugins/loader.js';
|
|
8
|
+
export type { ChannelPluginConfigView, ChannelPluginDeps, LoadedChannelPlugins } from './plugins/loader.js';
|
|
9
|
+
export * from './config.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './port.js';
|
|
2
|
+
export { ChannelRegistry } from './registry.js';
|
|
3
|
+
export { ChannelRouter } from './router.js';
|
|
4
|
+
export { ChannelError } from './errors.js';
|
|
5
|
+
export { ChannelIngress } from './ingress.js';
|
|
6
|
+
export * from './plugins/types.js';
|
|
7
|
+
export { DEFAULT_CHANNEL_PLUGINS, channelPluginEntryOf, describeChannelPluginLoadError, loadChannelPlugins, } from './plugins/loader.js';
|
|
8
|
+
export * from './config.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/channel/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAmC,MAAM,cAAc,CAAA;AAC9E,cAAc,oBAAoB,CAAA;AAClC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,8BAA8B,EAC9B,kBAAkB,GACnB,MAAM,qBAAqB,CAAA;AAE5B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Trigger } from '@router-agent/core/model.js';
|
|
2
|
+
import { type Logger } from '@router-agent/infra/logger.js';
|
|
3
|
+
import type { ChannelAdapter } from './port.js';
|
|
4
|
+
/**
|
|
5
|
+
* 入库端口:通道包只要求「能把 Trigger 落库」,不依赖宿主 Engine 的具体实现。
|
|
6
|
+
* `false` 表示重复投递(已被去重)。
|
|
7
|
+
*/
|
|
8
|
+
export interface IngressSink {
|
|
9
|
+
ingest(trigger: Trigger): boolean;
|
|
10
|
+
}
|
|
11
|
+
/** 回执存储端口:只读写「消息 → 回执句柄」这一条最小信息。 */
|
|
12
|
+
export interface AckStore {
|
|
13
|
+
getReaction(messageId: string): {
|
|
14
|
+
messageId: string;
|
|
15
|
+
reactionId: string;
|
|
16
|
+
} | null;
|
|
17
|
+
}
|
|
18
|
+
export interface ChannelIngressOptions {
|
|
19
|
+
adapter: ChannelAdapter;
|
|
20
|
+
engine: IngressSink;
|
|
21
|
+
store: AckStore;
|
|
22
|
+
logger?: Logger;
|
|
23
|
+
/** 这条消息是否会被回复;决定是否加回执。 */
|
|
24
|
+
shouldReply: (trigger: Trigger) => boolean;
|
|
25
|
+
/** 回执句柄落盘,回复成功后用于撤销。 */
|
|
26
|
+
onAck?: (messageId: string, handle: string) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 入站管线:归一化 -> 富化 -> 入库 -> 回执。
|
|
30
|
+
*
|
|
31
|
+
* 顺序与原先 CLI 内联逻辑保持一致:
|
|
32
|
+
* - 图片消息需要下载识别,先加回执再富化;
|
|
33
|
+
* - 其他消息先富化入库,再补回执;
|
|
34
|
+
* - 重复投递只记日志,不重复回执。
|
|
35
|
+
*/
|
|
36
|
+
export declare class ChannelIngress {
|
|
37
|
+
private readonly adapter;
|
|
38
|
+
private readonly engine;
|
|
39
|
+
private readonly store;
|
|
40
|
+
private readonly logger;
|
|
41
|
+
private readonly shouldReply;
|
|
42
|
+
private readonly onAck?;
|
|
43
|
+
constructor(opts: ChannelIngressOptions);
|
|
44
|
+
handleRaw(raw: unknown): void;
|
|
45
|
+
private react;
|
|
46
|
+
}
|
package/dist/ingress.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { errorMessage, noopLogger } from '@router-agent/infra/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* 入站管线:归一化 -> 富化 -> 入库 -> 回执。
|
|
4
|
+
*
|
|
5
|
+
* 顺序与原先 CLI 内联逻辑保持一致:
|
|
6
|
+
* - 图片消息需要下载识别,先加回执再富化;
|
|
7
|
+
* - 其他消息先富化入库,再补回执;
|
|
8
|
+
* - 重复投递只记日志,不重复回执。
|
|
9
|
+
*/
|
|
10
|
+
export class ChannelIngress {
|
|
11
|
+
adapter;
|
|
12
|
+
engine;
|
|
13
|
+
store;
|
|
14
|
+
logger;
|
|
15
|
+
shouldReply;
|
|
16
|
+
onAck;
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
this.adapter = opts.adapter;
|
|
19
|
+
this.engine = opts.engine;
|
|
20
|
+
this.store = opts.store;
|
|
21
|
+
this.logger = opts.logger ?? noopLogger;
|
|
22
|
+
this.shouldReply = opts.shouldReply;
|
|
23
|
+
this.onAck = opts.onAck;
|
|
24
|
+
}
|
|
25
|
+
handleRaw(raw) {
|
|
26
|
+
const result = this.adapter.normalize(raw);
|
|
27
|
+
if (!result.trigger) {
|
|
28
|
+
// 收到了但被过滤:把原因写进日志,否则用户只会看到「没反应」。
|
|
29
|
+
this.logger.info('trigger:dropped', {
|
|
30
|
+
channel: this.adapter.id,
|
|
31
|
+
reason: result.dropped,
|
|
32
|
+
chat: result.targetId,
|
|
33
|
+
name: result.targetName,
|
|
34
|
+
actor: result.actorId,
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const base = result.trigger;
|
|
39
|
+
const willReply = this.shouldReply(base);
|
|
40
|
+
const needsEnrichment = this.adapter.needsEnrichment?.(base) ?? false;
|
|
41
|
+
const canAck = this.adapter.capabilities.ack && Boolean(this.adapter.ack);
|
|
42
|
+
if (canAck && willReply && needsEnrichment && !this.store.getReaction(base.externalId)) {
|
|
43
|
+
this.react(base.externalId);
|
|
44
|
+
}
|
|
45
|
+
void (async () => {
|
|
46
|
+
const trigger = this.adapter.enrich ? await this.adapter.enrich(base) : base;
|
|
47
|
+
const stored = this.engine.ingest(trigger);
|
|
48
|
+
if (!stored) {
|
|
49
|
+
// 重放 / 重复投递:消息已入库但被去重,日志里说清楚,避免误判成「消息丢了」。
|
|
50
|
+
this.logger.info('trigger:duplicate', { channel: this.adapter.id, id: trigger.id, chat: trigger.chatId });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const raw = trigger.raw;
|
|
54
|
+
const threadKeys = trigger.threadKeys ?? [];
|
|
55
|
+
this.logger.info('trigger:ingested', {
|
|
56
|
+
channel: this.adapter.id,
|
|
57
|
+
id: trigger.id,
|
|
58
|
+
chat: trigger.chatId,
|
|
59
|
+
addressed: trigger.addressed,
|
|
60
|
+
// 没 @ 但被放行的消息(tk-59):处理它是因为话题绑着在办工单,不是「漏了 @ 判断」。
|
|
61
|
+
admitted: Boolean(trigger.unaddressedAdmitted),
|
|
62
|
+
quoted: Boolean(raw?.quote),
|
|
63
|
+
images: Array.isArray(raw?.images) ? raw.images.length : 0,
|
|
64
|
+
// 事件到底带了哪些线程标识:排查「消息没进工单」时先看 topic 是否为 true。
|
|
65
|
+
threadKeys: threadKeys.length,
|
|
66
|
+
topic: threadKeys.some(key => key.startsWith('omt_')),
|
|
67
|
+
});
|
|
68
|
+
// 纯附件在入库前已经加过,这里跳过避免重复。
|
|
69
|
+
if (canAck && willReply && !needsEnrichment)
|
|
70
|
+
this.react(trigger.externalId);
|
|
71
|
+
})().catch(error => {
|
|
72
|
+
this.logger.warn('trigger:ingest-failed', { channel: this.adapter.id, message: errorMessage(error) });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
react(messageId) {
|
|
76
|
+
void this.adapter.ack(messageId)
|
|
77
|
+
.then(handle => {
|
|
78
|
+
if (handle)
|
|
79
|
+
this.onAck?.(messageId, handle);
|
|
80
|
+
})
|
|
81
|
+
.catch(error => {
|
|
82
|
+
this.logger.warn('channel:ack-failed', {
|
|
83
|
+
channel: this.adapter.id,
|
|
84
|
+
messageId,
|
|
85
|
+
message: errorMessage(error),
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=ingress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingress.js","sourceRoot":"","sources":["../../../../packages/channel/src/ingress.ts"],"names":[],"mappings":"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"}
|
package/dist/legacy.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { shouldSendAsText } from './sources/feishu.js';
|
|
2
|
+
export function createEffectsSender(sender) {
|
|
3
|
+
return {
|
|
4
|
+
async send(item) {
|
|
5
|
+
if (item.channelKind !== 'feishu')
|
|
6
|
+
return;
|
|
7
|
+
const text = item.text.trim();
|
|
8
|
+
if (!text)
|
|
9
|
+
return;
|
|
10
|
+
// 简单消息走纯文本:不开卡片,少一次渲染、少一类兼容问题。
|
|
11
|
+
if (shouldSendAsText(text)) {
|
|
12
|
+
if (item.threadId)
|
|
13
|
+
await sender.reply(item.threadId, text, item.key);
|
|
14
|
+
else
|
|
15
|
+
await sender.send(item.channelId, text, item.key);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// 其余走互动卡片:Markdown 先在 feishuCard 内归一化,卡片失败时 Sender 内部降级为 post。
|
|
19
|
+
if (item.threadId)
|
|
20
|
+
await sender.replyCard(item.threadId, text, item.key);
|
|
21
|
+
else
|
|
22
|
+
await sender.sendCard(item.channelId, text, item.key);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=legacy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy.js","sourceRoot":"","sources":["../../../../packages/channel/src/legacy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAwBtD,MAAM,UAAU,mBAAmB,CAAC,MAAsB;IACxD,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,IAAI;YACb,IAAI,IAAI,CAAC,WAAW,KAAK,QAAQ;gBAAE,OAAM;YACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;YAC7B,IAAI,CAAC,IAAI;gBAAE,OAAM;YACjB,+BAA+B;YAC/B,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,IAAI,IAAI,CAAC,QAAQ;oBAAE,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;;oBAC/D,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;gBACtD,OAAM;YACR,CAAC;YACD,+DAA+D;YAC/D,IAAI,IAAI,CAAC,QAAQ;gBAAE,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;;gBACnE,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;QAC5D,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Logger } from '@router-agent/infra/logger.js';
|
|
2
|
+
import type { CommandRunner } from '@router-agent/infra/process.js';
|
|
3
|
+
import { type PluginFailure } from '@router-agent/infra/plugin-loader.js';
|
|
4
|
+
import { ChannelRegistry } from '../registry.js';
|
|
5
|
+
import type { ChannelAdapter } from '../port.js';
|
|
6
|
+
import type { ChannelPluginEntry, ChannelPluginMeta, ChannelPluginModuleLoader } from './types.js';
|
|
7
|
+
export declare const DEFAULT_CHANNEL_PLUGINS: Readonly<Record<string, string>>;
|
|
8
|
+
export interface ChannelPluginConfigView {
|
|
9
|
+
home?: string;
|
|
10
|
+
channels?: Record<string, ChannelPluginEntry>;
|
|
11
|
+
}
|
|
12
|
+
export declare function channelPluginEntryOf(config: ChannelPluginConfigView, id: string): string;
|
|
13
|
+
export declare function describeChannelPluginLoadError(id: string, error: unknown): string;
|
|
14
|
+
export interface ChannelPluginDeps {
|
|
15
|
+
home?: string;
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
logger?: Logger;
|
|
18
|
+
runner?: (bin: string) => CommandRunner;
|
|
19
|
+
services?: Record<string, unknown>;
|
|
20
|
+
loader?: ChannelPluginModuleLoader;
|
|
21
|
+
/** 测试注入:跳过内置包加载。 */
|
|
22
|
+
builtins?: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
export interface LoadedChannelPlugins {
|
|
25
|
+
registry: ChannelRegistry;
|
|
26
|
+
metas: ChannelPluginMeta[];
|
|
27
|
+
adapters: ChannelAdapter[];
|
|
28
|
+
/** 配置 id → 实际注册的通道实例 id;一个插件包可以注册多个。 */
|
|
29
|
+
loadedIds: Record<string, string[]>;
|
|
30
|
+
failures: PluginFailure[];
|
|
31
|
+
}
|
|
32
|
+
/** 加载 channel 插件并注册 adapter;单个插件失败只记录,不阻塞其它通道。 */
|
|
33
|
+
export declare function loadChannelPlugins(config: ChannelPluginConfigView, deps?: ChannelPluginDeps): Promise<LoadedChannelPlugins>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { errorMessage, noopLogger } from '@router-agent/infra/logger.js';
|
|
2
|
+
import { loadPluginModules, pluginEntryTarget, pluginSpecifier, throwRunner, } from '@router-agent/infra/plugin-loader.js';
|
|
3
|
+
import { ChannelRegistry } from '../registry.js';
|
|
4
|
+
export const DEFAULT_CHANNEL_PLUGINS = {
|
|
5
|
+
feishu: '@router-agent/channel-feishu',
|
|
6
|
+
};
|
|
7
|
+
export function channelPluginEntryOf(config, id) {
|
|
8
|
+
return pluginSpecifier(config.channels?.[id]) ?? DEFAULT_CHANNEL_PLUGINS[id] ?? id;
|
|
9
|
+
}
|
|
10
|
+
export function describeChannelPluginLoadError(id, error) {
|
|
11
|
+
return `channel 插件 ${id} 加载失败:${errorMessage(error)}`;
|
|
12
|
+
}
|
|
13
|
+
/** 加载 channel 插件并注册 adapter;单个插件失败只记录,不阻塞其它通道。 */
|
|
14
|
+
export async function loadChannelPlugins(config, deps = {}) {
|
|
15
|
+
const logger = deps.logger ?? noopLogger;
|
|
16
|
+
const home = deps.home ?? config.home ?? '';
|
|
17
|
+
const ids = [...new Set([...(deps.builtins ?? Object.keys(DEFAULT_CHANNEL_PLUGINS)), ...Object.keys(config.channels ?? {})])];
|
|
18
|
+
const registry = new ChannelRegistry(logger);
|
|
19
|
+
const metas = [];
|
|
20
|
+
const adapters = [];
|
|
21
|
+
const loadedIds = {};
|
|
22
|
+
const failures = [];
|
|
23
|
+
const loaded = await loadPluginModules({
|
|
24
|
+
kind: 'channel',
|
|
25
|
+
home,
|
|
26
|
+
ids,
|
|
27
|
+
...(deps.loader ? { loader: deps.loader } : {}),
|
|
28
|
+
logger,
|
|
29
|
+
specifierOf: id => {
|
|
30
|
+
const entry = config.channels?.[id];
|
|
31
|
+
if (entry !== undefined)
|
|
32
|
+
return pluginSpecifier(entry);
|
|
33
|
+
return DEFAULT_CHANNEL_PLUGINS[id] ?? id;
|
|
34
|
+
},
|
|
35
|
+
entryOf: id => pluginEntryTarget(config.channels?.[id]),
|
|
36
|
+
});
|
|
37
|
+
failures.push(...loaded.failures);
|
|
38
|
+
for (const { id: configId, plugin } of loaded.entries) {
|
|
39
|
+
try {
|
|
40
|
+
const ctx = {
|
|
41
|
+
home,
|
|
42
|
+
config: config,
|
|
43
|
+
env: deps.env ?? process.env,
|
|
44
|
+
logger,
|
|
45
|
+
runner: deps.runner ?? (bin => throwRunner('channel', bin)),
|
|
46
|
+
...(deps.services ? { services: deps.services } : {}),
|
|
47
|
+
};
|
|
48
|
+
const created = plugin.createAll ? await plugin.createAll(ctx) : [await plugin.create(ctx)];
|
|
49
|
+
const ids = [];
|
|
50
|
+
for (const adapter of created) {
|
|
51
|
+
if (!adapter)
|
|
52
|
+
continue;
|
|
53
|
+
registry.register(adapter);
|
|
54
|
+
adapters.push(adapter);
|
|
55
|
+
ids.push(adapter.id);
|
|
56
|
+
logger.info('plugin:loaded', { kind: 'channel', id: adapter.id });
|
|
57
|
+
}
|
|
58
|
+
loadedIds[configId] = ids;
|
|
59
|
+
if (plugin.meta) {
|
|
60
|
+
if (plugin.meta.id !== configId) {
|
|
61
|
+
logger.warn('plugin:id-mismatch', {
|
|
62
|
+
kind: 'channel',
|
|
63
|
+
id: configId,
|
|
64
|
+
declared: plugin.meta.id,
|
|
65
|
+
detail: '配置里的插件 id 与插件声明的 meta.id 不一致,请改成一致以便面板回链',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
metas.push({ ...plugin.meta, id: configId });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const failureId = configId || plugin.meta?.id || 'unknown';
|
|
73
|
+
logger.error('plugin:create-failed', { kind: 'channel', id: failureId, detail: errorMessage(error) });
|
|
74
|
+
failures.push({ kind: 'channel', id: failureId, specifier: channelPluginEntryOf(config, failureId), message: errorMessage(error) });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { registry, metas, adapters, loadedIds, failures };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../../../packages/channel/src/plugins/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CommandRunner } from '@router-agent/infra/process.js';
|
|
2
|
+
import type { Logger } from '@router-agent/infra/logger.js';
|
|
3
|
+
import type { PluginEntry, PluginModuleLoader } from '@router-agent/infra/plugin-loader.js';
|
|
4
|
+
import type { ChannelAdapter } from '../port.js';
|
|
5
|
+
export type ChannelPluginEntry = PluginEntry;
|
|
6
|
+
export type ChannelPluginModuleLoader = PluginModuleLoader;
|
|
7
|
+
export interface ChannelPluginMeta {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
hint: string;
|
|
11
|
+
default?: boolean;
|
|
12
|
+
packageName?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ChannelPluginContext {
|
|
15
|
+
home: string;
|
|
16
|
+
config: Record<string, unknown>;
|
|
17
|
+
env: NodeJS.ProcessEnv;
|
|
18
|
+
logger: Logger;
|
|
19
|
+
/** 插件自行创建的运行器;宿主不假设通道实现。 */
|
|
20
|
+
runner(bin: string): CommandRunner;
|
|
21
|
+
/** 宿主已有的可选服务(如目录、附件转写);插件按需取用。 */
|
|
22
|
+
services?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface ChannelPlugin {
|
|
25
|
+
kind: 'channel';
|
|
26
|
+
meta?: ChannelPluginMeta;
|
|
27
|
+
create(ctx: ChannelPluginContext): ChannelAdapter | null | Promise<ChannelAdapter | null>;
|
|
28
|
+
/** 一个通道包可以注册多个实例;不实现时等价于 create()。 */
|
|
29
|
+
createAll?(ctx: ChannelPluginContext): Array<ChannelAdapter | null> | Promise<Array<ChannelAdapter | null>>;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../packages/channel/src/plugins/types.ts"],"names":[],"mappings":""}
|