@router-agent/channel 0.0.0-stage → 0.3.32

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.
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../packages/channel/src/config.ts"],"names":[],"mappings":""}
@@ -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,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
+ }
@@ -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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"}
@@ -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":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAe,MAAM,+BAA+B,CAAA;AAErF,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,WAAW,GAEZ,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAShD,MAAM,CAAC,MAAM,uBAAuB,GAAqC;IACvE,MAAM,EAAE,8BAA8B;CACvC,CAAA;AAOD,MAAM,UAAU,oBAAoB,CAAC,MAA+B,EAAE,EAAU;IAC9E,OAAO,eAAe,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAA;AACpF,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,EAAU,EAAE,KAAc;IACvE,OAAO,cAAc,EAAE,SAAS,YAAY,CAAC,KAAK,CAAC,EAAE,CAAA;AACvD,CAAC;AAsBD,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAA+B,EAC/B,OAA0B,EAAE;IAE5B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,UAAU,CAAA;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,EAAE,CAAA;IAC3C,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7H,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,KAAK,GAAwB,EAAE,CAAA;IACrC,MAAM,QAAQ,GAAqB,EAAE,CAAA;IACrC,MAAM,SAAS,GAA6B,EAAE,CAAA;IAC9C,MAAM,QAAQ,GAAoB,EAAE,CAAA;IAEpC,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAgB;QACpD,IAAI,EAAE,SAAS;QACf,IAAI;QACJ,GAAG;QACH,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,MAAM;QACN,WAAW,EAAE,EAAE,CAAC,EAAE;YAChB,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;YACnC,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAO,eAAe,CAAC,KAAK,CAAC,CAAA;YACtD,OAAO,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAA;QAC1C,CAAC;QACD,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;KACxD,CAAC,CAAA;IACF,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IACjC,KAAK,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACtD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG;gBACV,IAAI;gBACJ,MAAM,EAAE,MAAiC;gBACzC,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;gBAC5B,MAAM;gBACN,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;gBAC3D,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACtD,CAAA;YACD,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;YAC3F,MAAM,GAAG,GAAa,EAAE,CAAA;YACxB,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;gBAC9B,IAAI,CAAC,OAAO;oBAAE,SAAQ;gBACtB,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;gBAC1B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBACtB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;gBACpB,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAA;YACnE,CAAC;YACD,SAAS,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAA;YACzB,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAChB,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;oBAChC,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;wBAChC,IAAI,EAAE,SAAS;wBACf,EAAE,EAAE,QAAQ;wBACZ,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;wBACxB,MAAM,EAAE,0CAA0C;qBACnD,CAAC,CAAA;gBACJ,CAAC;gBACD,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC9C,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,SAAS,GAAG,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,SAAS,CAAA;YAC1D,MAAM,CAAC,KAAK,CAAC,sBAAsB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACrG,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,oBAAoB,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACrI,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;AAC3D,CAAC"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../packages/channel/src/plugins/types.ts"],"names":[],"mappings":""}
package/dist/port.d.ts ADDED
@@ -0,0 +1,111 @@
1
+ import type { Trigger } from '@router-agent/core/model.js';
2
+ /**
3
+ * 默认通道实例 ID。
4
+ *
5
+ * 一期只有飞书一个通道,所有历史数据都归属于这个实例;等接入第二个通道时,
6
+ * 实例 ID 会来自配置(`channels.<id>`),这里的常量只作为兼容默认值。
7
+ */
8
+ export declare const DEFAULT_CHANNEL_ID = "feishu";
9
+ /** 通道能力声明。内核按能力降级,不假设所有通道长得一样。 */
10
+ export interface ChannelCapabilities {
11
+ /** 支持话题/线程内回复。 */
12
+ threadReply: boolean;
13
+ /** 支持消息回执(表情或已读)。 */
14
+ ack: boolean;
15
+ /** 支持富文本渲染。 */
16
+ richText: boolean;
17
+ /** 支持创建可分享的长文档。 */
18
+ documents: boolean;
19
+ /** 支持枚举可接收目标。 */
20
+ directory: boolean;
21
+ /** 支持拉取入站附件。 */
22
+ attachmentsIn: boolean;
23
+ /** 支持发送出站附件(一期全部 false)。 */
24
+ attachmentsOut: boolean;
25
+ /** 支持服务端幂等键。 */
26
+ idempotency: boolean;
27
+ /** 单条消息字符上限,用于切块。 */
28
+ maxTextLength: number;
29
+ /** 私聊是否可用。 */
30
+ directMessage: boolean;
31
+ }
32
+ /** 出站消息的中立形态;具体渲染由通道适配器负责。 */
33
+ export interface OutboundMessage {
34
+ channelId: string;
35
+ targetId: string;
36
+ /** 希望回复到的话题根;为空时发到 targetId。 */
37
+ threadRef?: string;
38
+ text: string;
39
+ format: 'plain' | 'markdown';
40
+ idempotencyKey: string;
41
+ /** 触发这条回复的用户消息,用于发送成功后撤销回执。 */
42
+ originMessageId?: string;
43
+ }
44
+ export interface SendReceipt {
45
+ messageId?: string;
46
+ /** 实际发生的降级,便于日志与排障。 */
47
+ degradedTo?: 'plain' | 'rich' | 'document';
48
+ }
49
+ /** 归一化结果:要么得到领域 Trigger,要么给出丢弃原因。 */
50
+ export interface NormalizedInbound {
51
+ trigger?: Trigger;
52
+ dropped?: string;
53
+ targetId?: string;
54
+ targetName?: string;
55
+ actorId?: string;
56
+ }
57
+ export interface ChannelStatus {
58
+ appId?: string;
59
+ bot?: {
60
+ available?: boolean;
61
+ status?: string;
62
+ message?: string;
63
+ };
64
+ user?: {
65
+ available?: boolean;
66
+ status?: string;
67
+ message?: string;
68
+ openId?: string;
69
+ userName?: string;
70
+ };
71
+ }
72
+ export interface ChannelAdmin {
73
+ readonly profileHome: string;
74
+ status(): Promise<ChannelStatus>;
75
+ configure(appId: string, appSecret: string): Promise<{
76
+ ok: boolean;
77
+ message?: string;
78
+ }>;
79
+ startLogin(): Promise<any>;
80
+ completeLogin(code: string): Promise<any>;
81
+ }
82
+ /**
83
+ * 通道公共契约。
84
+ *
85
+ * 内核只依赖这个接口;飞书的 lark-cli 调用、事件过滤、卡片渲染全部封装在实现里。
86
+ * 一期只实现飞书,但签名预留了话题、回执、文档与附件能力,接第二通道时不用改内核。
87
+ */
88
+ export interface ChannelAdapter {
89
+ readonly id: string;
90
+ readonly kind: string;
91
+ readonly capabilities: ChannelCapabilities;
92
+ /** 启动入站消费;主动拉取型通道可以不实现。 */
93
+ start?(sink: (raw: unknown) => void): Promise<void> | void;
94
+ stop?(): void;
95
+ /** 原始事件 -> 领域 Trigger;只做传输合法性,不做需要跨消息的富化。 */
96
+ normalize(raw: unknown): NormalizedInbound;
97
+ /** 这条消息入库前是否需要富化(引用回查、附件下载/OCR)。 */
98
+ needsEnrichment?(trigger: Trigger): boolean;
99
+ /** 通道内富化;失败不应阻塞主流程,实现方负责降级与记日志。 */
100
+ enrich?(trigger: Trigger): Promise<Trigger>;
101
+ send(message: OutboundMessage): Promise<SendReceipt>;
102
+ /** 给一条消息加回执,返回通道侧句柄;不支持时返回 null。 */
103
+ ack?(messageId: string): Promise<string | null>;
104
+ clearAck?(messageId: string, handle: string): Promise<void>;
105
+ listTargets?(): Promise<{
106
+ id: string;
107
+ name: string;
108
+ }[]>;
109
+ createDocument?(title: string, markdown: string): Promise<string>;
110
+ readonly admin?: ChannelAdmin;
111
+ }
package/dist/port.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 默认通道实例 ID。
3
+ *
4
+ * 一期只有飞书一个通道,所有历史数据都归属于这个实例;等接入第二个通道时,
5
+ * 实例 ID 会来自配置(`channels.<id>`),这里的常量只作为兼容默认值。
6
+ */
7
+ export const DEFAULT_CHANNEL_ID = 'feishu';
8
+ //# sourceMappingURL=port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.js","sourceRoot":"","sources":["../../../../packages/channel/src/port.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAQ,CAAA"}
@@ -0,0 +1,21 @@
1
+ import { type Logger } from '@router-agent/infra/logger.js';
2
+ import type { ChannelAdapter, ChannelCapabilities } from './port.js';
3
+ /**
4
+ * 通道注册表:按实例 ID 管理适配器。
5
+ *
6
+ * 一期只注册飞书;接第二通道时只需在这里多注册一个实现,内核代码不动。
7
+ */
8
+ export declare class ChannelRegistry {
9
+ private readonly adapters;
10
+ private readonly logger;
11
+ constructor(logger?: Logger);
12
+ register(adapter: ChannelAdapter): void;
13
+ get(id: string): ChannelAdapter | undefined;
14
+ /** 未注册属于永久失败:重试也不会变好,交给调用方决定是否进死信。 */
15
+ require(id: string): ChannelAdapter;
16
+ capabilities(id: string): ChannelCapabilities;
17
+ list(): ChannelAdapter[];
18
+ /** 依次启动所有实现 `start` 的适配器,并把原始事件标上通道 ID。 */
19
+ startAll(sink: (channelId: string, raw: unknown) => void): Promise<void>;
20
+ stopAll(): void;
21
+ }
@@ -0,0 +1,50 @@
1
+ import { noopLogger } from '@router-agent/infra/logger.js';
2
+ import { ChannelError } from './errors.js';
3
+ /**
4
+ * 通道注册表:按实例 ID 管理适配器。
5
+ *
6
+ * 一期只注册飞书;接第二通道时只需在这里多注册一个实现,内核代码不动。
7
+ */
8
+ export class ChannelRegistry {
9
+ adapters = new Map();
10
+ logger;
11
+ constructor(logger = noopLogger) {
12
+ this.logger = logger;
13
+ }
14
+ register(adapter) {
15
+ if (this.adapters.has(adapter.id)) {
16
+ this.logger.warn('channel:duplicate-id', { channel: adapter.id, kind: adapter.kind });
17
+ }
18
+ this.adapters.set(adapter.id, adapter);
19
+ }
20
+ get(id) {
21
+ return this.adapters.get(id);
22
+ }
23
+ /** 未注册属于永久失败:重试也不会变好,交给调用方决定是否进死信。 */
24
+ require(id) {
25
+ const adapter = this.adapters.get(id);
26
+ if (!adapter) {
27
+ throw new ChannelError(id, 'channel:not-registered', `通道未注册: ${id}`, false);
28
+ }
29
+ return adapter;
30
+ }
31
+ capabilities(id) {
32
+ return this.require(id).capabilities;
33
+ }
34
+ list() {
35
+ return [...this.adapters.values()];
36
+ }
37
+ /** 依次启动所有实现 `start` 的适配器,并把原始事件标上通道 ID。 */
38
+ async startAll(sink) {
39
+ for (const adapter of this.adapters.values()) {
40
+ if (!adapter.start)
41
+ continue;
42
+ await adapter.start(raw => sink(adapter.id, raw));
43
+ }
44
+ }
45
+ stopAll() {
46
+ for (const adapter of this.adapters.values())
47
+ adapter.stop?.();
48
+ }
49
+ }
50
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../packages/channel/src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAe,MAAM,+BAA+B,CAAA;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG1C;;;;GAIG;AACH,MAAM,OAAO,eAAe;IACT,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAA;IAC5C,MAAM,CAAQ;IAE/B,YAAY,SAAiB,UAAU;QACrC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAED,QAAQ,CAAC,OAAuB;QAC9B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;QACvF,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IACxC,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAC9B,CAAC;IAED,sCAAsC;IACtC,OAAO,CAAC,EAAU;QAChB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,YAAY,CAAC,EAAE,EAAE,wBAAwB,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,CAAC,CAAA;QAC7E,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,YAAY,CAAC,EAAU;QACrB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,YAAY,CAAA;IACtC,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IACpC,CAAC;IAED,2CAA2C;IAC3C,KAAK,CAAC,QAAQ,CAAC,IAA+C;QAC5D,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC,OAAO,CAAC,KAAK;gBAAE,SAAQ;YAC5B,MAAM,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QACnD,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;YAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAA;IAChE,CAAC;CACF"}
@@ -0,0 +1,24 @@
1
+ import { type Logger } from '@router-agent/infra/logger.js';
2
+ import type { ChannelRegistry } from './registry.js';
3
+ /** Outbox 交给通道层的最小字段集;与 store 的 OutboxItem 结构兼容。 */
4
+ export interface ChannelOutboxItem {
5
+ channelKind: string;
6
+ channelId: string;
7
+ threadId: string;
8
+ text: string;
9
+ key: string;
10
+ originMessageId?: string;
11
+ }
12
+ /**
13
+ * 出站分发:按通道实例 ID 找到适配器,把中立消息交给它渲染与发送。
14
+ *
15
+ * 一期 `channelKind` 仍然是 `'feishu'`,与实例 ID 相同;接第二通道时
16
+ * 由配置把实例 ID 与实现 ID 分开。未知通道按旧行为跳过并记日志,
17
+ * 避免历史脏数据把 Outbox 卡死。
18
+ */
19
+ export declare class ChannelRouter {
20
+ private readonly registry;
21
+ private readonly logger;
22
+ constructor(registry: ChannelRegistry, logger?: Logger);
23
+ send(item: ChannelOutboxItem): Promise<void>;
24
+ }
package/dist/router.js ADDED
@@ -0,0 +1,36 @@
1
+ import { noopLogger } from '@router-agent/infra/logger.js';
2
+ /**
3
+ * 出站分发:按通道实例 ID 找到适配器,把中立消息交给它渲染与发送。
4
+ *
5
+ * 一期 `channelKind` 仍然是 `'feishu'`,与实例 ID 相同;接第二通道时
6
+ * 由配置把实例 ID 与实现 ID 分开。未知通道按旧行为跳过并记日志,
7
+ * 避免历史脏数据把 Outbox 卡死。
8
+ */
9
+ export class ChannelRouter {
10
+ registry;
11
+ logger;
12
+ constructor(registry, logger = noopLogger) {
13
+ this.registry = registry;
14
+ this.logger = logger;
15
+ }
16
+ async send(item) {
17
+ const text = item.text.trim();
18
+ if (!text)
19
+ return;
20
+ const adapter = this.registry.get(item.channelKind);
21
+ if (!adapter) {
22
+ this.logger.warn('channel:not-registered', { channel: item.channelKind, target: item.channelId, key: item.key });
23
+ return;
24
+ }
25
+ await adapter.send({
26
+ channelId: item.channelKind,
27
+ targetId: item.channelId,
28
+ ...(item.threadId ? { threadRef: item.threadId } : {}),
29
+ text,
30
+ format: 'markdown',
31
+ idempotencyKey: item.key,
32
+ ...(item.originMessageId ? { originMessageId: item.originMessageId } : {}),
33
+ });
34
+ }
35
+ }
36
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../../../packages/channel/src/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAe,MAAM,+BAA+B,CAAA;AAavE;;;;;;GAMG;AACH,MAAM,OAAO,aAAa;IAEL;IACA;IAFnB,YACmB,QAAyB,EACzB,SAAiB,UAAU;QAD3B,aAAQ,GAAR,QAAQ,CAAiB;QACzB,WAAM,GAAN,MAAM,CAAqB;IAC3C,CAAC;IAEJ,KAAK,CAAC,IAAI,CAAC,IAAuB;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;QAC7B,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACnD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;YAChH,OAAM;QACR,CAAC;QACD,MAAM,OAAO,CAAC,IAAI,CAAC;YACjB,SAAS,EAAE,IAAI,CAAC,WAAW;YAC3B,QAAQ,EAAE,IAAI,CAAC,SAAS;YACxB,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtD,IAAI;YACJ,MAAM,EAAE,UAAU;YAClB,cAAc,EAAE,IAAI,CAAC,GAAG;YACxB,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3E,CAAC,CAAA;IACJ,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,37 @@
1
1
  {
2
2
  "name": "@router-agent/channel",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.32",
4
+ "type": "module",
5
+ "description": "消息通道 SDK:端口、注册表、插件协议与入站管线",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/blankPen/router-agent.git",
10
+ "directory": "packages/channel"
11
+ },
12
+ "homepage": "https://github.com/blankPen/router-agent#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/blankPen/router-agent/issues"
15
+ },
16
+ "main": "./dist/index.js",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ },
22
+ "./*.js": "./dist/*.js",
23
+ "./*": "./dist/*"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "types": "./dist/index.d.ts",
29
+ "publishConfig": {
30
+ "access": "public",
31
+ "registry": "https://registry.npmjs.org/"
32
+ },
33
+ "dependencies": {
34
+ "@router-agent/core": "0.3.32",
35
+ "@router-agent/infra": "0.3.32"
36
+ }
37
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.