@axiom-lattice/gateway 3.0.2 → 3.0.3
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/index.js +31 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/.eslintrc.json +0 -22
- package/.turbo/turbo-build.log +0 -46
- package/AGENTS.md +0 -112
- package/CHANGELOG.md +0 -1190
- package/LOGGER_CONFIG.md +0 -135
- package/RESUME_STREAM_CONTENT_BASED.md +0 -326
- package/RESUME_STREAM_README.md +0 -388
- package/jest.config.js +0 -72
- package/public/sdk/README.md +0 -695
- package/public/sdk/dashboard-engine-skill.md +0 -1122
- package/public/sdk/dashboard-single-file-spec.md +0 -357
- package/public/sdk/data-query-sdk-skill.md +0 -307
- package/public/sdk/data-query-sdk.d.ts +0 -252
- package/public/sdk/data-query-sdk.js +0 -970
- package/public/sdk/occupancy-dashboard.html +0 -363
- package/public/sdk/test-dashboard.html +0 -690
- package/scripts/seed-lark-installation.ts +0 -54
- package/src/__tests__/__mocks__/e2b.ts +0 -1
- package/src/__tests__/a2a.test.ts +0 -428
- package/src/__tests__/channel-installations.test.ts +0 -199
- package/src/__tests__/data-query.test.ts +0 -77
- package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
- package/src/__tests__/setup.ts +0 -5
- package/src/__tests__/workspace.test.ts +0 -400
- package/src/channels/__tests__/routes.test.ts +0 -50
- package/src/channels/lark/LarkChannelAdapter.ts +0 -217
- package/src/channels/lark/README.md +0 -187
- package/src/channels/lark/__tests__/controller.test.ts +0 -136
- package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
- package/src/channels/lark/__tests__/parser.test.ts +0 -72
- package/src/channels/lark/__tests__/sender.test.ts +0 -37
- package/src/channels/lark/__tests__/verification.test.ts +0 -157
- package/src/channels/lark/controller.ts +0 -48
- package/src/channels/lark/mapping-service.ts +0 -138
- package/src/channels/lark/parser.ts +0 -68
- package/src/channels/lark/routes.ts +0 -22
- package/src/channels/lark/sender.ts +0 -58
- package/src/channels/lark/types.ts +0 -20
- package/src/channels/lark/verification.ts +0 -32
- package/src/channels/registry.ts +0 -20
- package/src/channels/routes.ts +0 -30
- package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
- package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
- package/src/channels/wechat/context-store.ts +0 -54
- package/src/channels/wechat/controller.ts +0 -59
- package/src/channels/wechat/routes.ts +0 -7
- package/src/channels/wechat/types.ts +0 -62
- package/src/channels/wechat/wechat-client.ts +0 -162
- package/src/config/local-a2a-templates.ts +0 -59
- package/src/config.ts +0 -124
- package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
- package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
- package/src/controllers/__tests__/mime.test.ts +0 -74
- package/src/controllers/__tests__/resources.test.ts +0 -122
- package/src/controllers/__tests__/run.test.ts +0 -115
- package/src/controllers/__tests__/tasks.test.ts +0 -215
- package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
- package/src/controllers/agent_task.ts +0 -72
- package/src/controllers/assistant.ts +0 -490
- package/src/controllers/auth.ts +0 -524
- package/src/controllers/channel-bindings.ts +0 -135
- package/src/controllers/channel-installations.ts +0 -409
- package/src/controllers/collections.ts +0 -176
- package/src/controllers/config.ts +0 -126
- package/src/controllers/connections.ts +0 -181
- package/src/controllers/data-query.ts +0 -236
- package/src/controllers/database-configs.ts +0 -440
- package/src/controllers/embeddings.ts +0 -22
- package/src/controllers/entries.ts +0 -167
- package/src/controllers/eval.ts +0 -513
- package/src/controllers/export-import.ts +0 -166
- package/src/controllers/health.ts +0 -36
- package/src/controllers/local-a2a-templates.ts +0 -18
- package/src/controllers/local-a2a.ts +0 -149
- package/src/controllers/mcp-configs.ts +0 -673
- package/src/controllers/memory.ts +0 -257
- package/src/controllers/menu-items.ts +0 -114
- package/src/controllers/metrics-configs.ts +0 -1061
- package/src/controllers/middleware-types.ts +0 -15
- package/src/controllers/models.ts +0 -199
- package/src/controllers/personal-assistant.ts +0 -191
- package/src/controllers/resources.ts +0 -477
- package/src/controllers/run.ts +0 -338
- package/src/controllers/sandbox.ts +0 -255
- package/src/controllers/schedules.ts +0 -336
- package/src/controllers/skills.ts +0 -256
- package/src/controllers/tasks.ts +0 -213
- package/src/controllers/tenants.ts +0 -121
- package/src/controllers/thread_status.ts +0 -66
- package/src/controllers/threads.ts +0 -260
- package/src/controllers/tools.ts +0 -139
- package/src/controllers/users.ts +0 -135
- package/src/controllers/workflow-tracking.ts +0 -693
- package/src/controllers/workspace.ts +0 -879
- package/src/export_registrations/a2a-api-key.registration.ts +0 -89
- package/src/export_registrations/agent.registration.ts +0 -80
- package/src/export_registrations/binding.registration.ts +0 -106
- package/src/export_registrations/channel-installation.registration.ts +0 -88
- package/src/export_registrations/collection.registration.ts +0 -85
- package/src/export_registrations/connection.registration.ts +0 -97
- package/src/export_registrations/database-config.registration.ts +0 -94
- package/src/export_registrations/eval.registration.ts +0 -334
- package/src/export_registrations/index.ts +0 -31
- package/src/export_registrations/mcp-config.registration.ts +0 -97
- package/src/export_registrations/menu.registration.ts +0 -91
- package/src/export_registrations/metrics-config.registration.ts +0 -94
- package/src/export_registrations/skill.registration.ts +0 -88
- package/src/export_registrations/task.registration.ts +0 -99
- package/src/index.ts +0 -497
- package/src/logger/Logger.ts +0 -186
- package/src/router/MessageContext.ts +0 -14
- package/src/router/MessageRouter.ts +0 -490
- package/src/router/middlewares/auditLogger.ts +0 -34
- package/src/router/middlewares/deduplication.ts +0 -38
- package/src/router/middlewares/index.ts +0 -3
- package/src/router/middlewares/rateLimit.ts +0 -46
- package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
- package/src/routes/a2a-bridge.ts +0 -266
- package/src/routes/a2a-test-connection.ts +0 -126
- package/src/routes/a2a.ts +0 -851
- package/src/routes/channel-bindings.ts +0 -11
- package/src/routes/channel-installations.ts +0 -33
- package/src/routes/index.ts +0 -640
- package/src/routes/menu-items.ts +0 -10
- package/src/routes/resource-routes.ts +0 -35
- package/src/routes/stt.ts +0 -102
- package/src/schemas/data-query.ts +0 -69
- package/src/schemas/index.ts +0 -852
- package/src/services/ExportImportService.ts +0 -296
- package/src/services/__tests__/ExportImportService.test.ts +0 -130
- package/src/services/__tests__/eval-runner.test.ts +0 -131
- package/src/services/__tests__/local-a2a.test.ts +0 -124
- package/src/services/agent_task_consumer.ts +0 -348
- package/src/services/collection_service.ts +0 -258
- package/src/services/eval-runner.ts +0 -283
- package/src/services/local-a2a.ts +0 -354
- package/src/services/queue_service.ts +0 -104
- package/src/services/sandbox_service.ts +0 -223
- package/src/services/skill_service.ts +0 -97
- package/src/services/supabase.ts +0 -60
- package/src/swagger.ts +0 -77
- package/src/types/index.ts +0 -118
- package/src/types/koa.d.ts +0 -7
- package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
- package/src/utils/__tests__/path-containment.test.ts +0 -35
- package/src/utils/a2a-url.ts +0 -20
- package/src/utils/local-a2a-runtime.ts +0 -107
- package/src/utils/mime.ts +0 -44
- package/src/utils/path-containment.ts +0 -38
- package/tsconfig.json +0 -26
package/src/logger/Logger.ts
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import pino from "pino";
|
|
2
|
-
import "pino-pretty";
|
|
3
|
-
import "pino-roll";
|
|
4
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
5
|
-
|
|
6
|
-
export interface LoggerContext {
|
|
7
|
-
"x-user-id"?: string;
|
|
8
|
-
"x-tenant-id"?: string;
|
|
9
|
-
"x-request-id"?: string;
|
|
10
|
-
"x-task-id"?: string;
|
|
11
|
-
"x-thread-id"?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface LoggerOptions {
|
|
15
|
-
name?: string;
|
|
16
|
-
serviceName?: string;
|
|
17
|
-
context?: LoggerContext;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 单例的Pino日志工厂类,管理底层pino实例
|
|
22
|
-
*/
|
|
23
|
-
class PinoLoggerFactory {
|
|
24
|
-
private static instance: PinoLoggerFactory;
|
|
25
|
-
private pinoLogger: pino.Logger;
|
|
26
|
-
|
|
27
|
-
private constructor() {
|
|
28
|
-
const isProd = process.env.NODE_ENV === "production";
|
|
29
|
-
|
|
30
|
-
const loggerConfig: pino.LoggerOptions = {
|
|
31
|
-
// 自定义时间戳格式
|
|
32
|
-
timestamp: () => `,"@timestamp":"${new Date().toISOString()}"`,
|
|
33
|
-
|
|
34
|
-
// 关闭默认的时间戳键
|
|
35
|
-
base: {
|
|
36
|
-
"@version": "1",
|
|
37
|
-
app_name: "lattice",
|
|
38
|
-
service_name: "lattice/graph-server",
|
|
39
|
-
thread_name: "main",
|
|
40
|
-
logger_name: "lattice-graph-logger",
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
formatters: {
|
|
44
|
-
level: (label, number) => {
|
|
45
|
-
return {
|
|
46
|
-
level: label.toUpperCase(),
|
|
47
|
-
level_value: number * 1000,
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// 生产环境使用文件日志
|
|
54
|
-
if (isProd) {
|
|
55
|
-
try {
|
|
56
|
-
this.pinoLogger = pino(
|
|
57
|
-
loggerConfig,
|
|
58
|
-
pino.transport({
|
|
59
|
-
target: "pino-roll",
|
|
60
|
-
options: {
|
|
61
|
-
file: "./logs/fin_ai_graph_server",
|
|
62
|
-
frequency: "daily",
|
|
63
|
-
mkdir: true,
|
|
64
|
-
},
|
|
65
|
-
})
|
|
66
|
-
);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error(
|
|
69
|
-
"无法初始化 pino-roll 日志记录器,回退到控制台日志",
|
|
70
|
-
error
|
|
71
|
-
);
|
|
72
|
-
// 回退到开发环境的配置
|
|
73
|
-
this.pinoLogger = pino({
|
|
74
|
-
...loggerConfig,
|
|
75
|
-
transport: {
|
|
76
|
-
target: "pino-pretty",
|
|
77
|
-
options: {
|
|
78
|
-
colorize: true,
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
// 开发环境使用格式化输出
|
|
85
|
-
this.pinoLogger = pino({
|
|
86
|
-
...loggerConfig,
|
|
87
|
-
transport: {
|
|
88
|
-
target: "pino-pretty",
|
|
89
|
-
options: {
|
|
90
|
-
colorize: true,
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
public static getInstance(): PinoLoggerFactory {
|
|
98
|
-
if (!PinoLoggerFactory.instance) {
|
|
99
|
-
PinoLoggerFactory.instance = new PinoLoggerFactory();
|
|
100
|
-
}
|
|
101
|
-
return PinoLoggerFactory.instance;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
public getPinoLogger(): pino.Logger {
|
|
105
|
-
return this.pinoLogger;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Logger类,可以创建多个实例,每个实例有自己的上下文
|
|
111
|
-
*/
|
|
112
|
-
export class Logger {
|
|
113
|
-
private context: LoggerContext;
|
|
114
|
-
private name: string;
|
|
115
|
-
private serviceName: string;
|
|
116
|
-
|
|
117
|
-
constructor(options?: LoggerOptions) {
|
|
118
|
-
this.context = options?.context || {};
|
|
119
|
-
this.name = options?.name || "lattice-graph-logger";
|
|
120
|
-
this.serviceName = options?.serviceName || "lattice/graph-server";
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 获取合并了上下文的日志对象
|
|
125
|
-
* @param additionalContext 额外的上下文数据
|
|
126
|
-
* @returns 带有上下文的pino日志对象
|
|
127
|
-
*/
|
|
128
|
-
private getContextualLogger(additionalContext?: object): pino.Logger {
|
|
129
|
-
const pinoLogger = PinoLoggerFactory.getInstance().getPinoLogger();
|
|
130
|
-
|
|
131
|
-
// 合并Logger实例的上下文和额外上下文
|
|
132
|
-
const contextObj = {
|
|
133
|
-
"x-user-id": this.context["x-user-id"] || "",
|
|
134
|
-
"x-tenant-id": this.context["x-tenant-id"] || "",
|
|
135
|
-
"x-request-id": this.context["x-request-id"] || "",
|
|
136
|
-
"x-task-id": this.context["x-task-id"] || "",
|
|
137
|
-
"x-thread-id": this.context["x-thread-id"] || "",
|
|
138
|
-
service_name: this.serviceName,
|
|
139
|
-
logger_name: this.name,
|
|
140
|
-
...additionalContext,
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// 创建带有上下文的子日志记录器
|
|
144
|
-
return pinoLogger.child(contextObj);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
info(msg: string, obj?: object): void {
|
|
148
|
-
this.getContextualLogger(obj).info(msg);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
error(msg: string, obj?: object | Error): void {
|
|
152
|
-
this.getContextualLogger(obj).error(msg);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
warn(msg: string, obj?: object): void {
|
|
156
|
-
this.getContextualLogger(obj).warn(msg);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
debug(msg: string, obj?: object): void {
|
|
160
|
-
this.getContextualLogger(obj).debug(msg);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* 更新Logger实例的上下文
|
|
165
|
-
*/
|
|
166
|
-
updateContext(context: Partial<LoggerContext>): void {
|
|
167
|
-
this.context = {
|
|
168
|
-
...this.context,
|
|
169
|
-
...context,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 创建一个新的Logger实例,继承当前Logger的上下文
|
|
175
|
-
*/
|
|
176
|
-
child(options: Partial<LoggerOptions>): Logger {
|
|
177
|
-
return new Logger({
|
|
178
|
-
name: options.name || this.name,
|
|
179
|
-
serviceName: options.serviceName || this.serviceName,
|
|
180
|
-
context: {
|
|
181
|
-
...this.context,
|
|
182
|
-
...options.context,
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { InboundMessage, Binding } from "@axiom-lattice/protocols";
|
|
2
|
-
|
|
3
|
-
export interface MessageContext {
|
|
4
|
-
inboundMessage: InboundMessage;
|
|
5
|
-
binding?: Binding;
|
|
6
|
-
result?: string;
|
|
7
|
-
error?: Error;
|
|
8
|
-
metadata: Record<string, unknown>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type MessageMiddleware = (
|
|
12
|
-
ctx: MessageContext,
|
|
13
|
-
next: () => Promise<void>,
|
|
14
|
-
) => Promise<void>;
|
|
@@ -1,490 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getStoreLattice,
|
|
3
|
-
agentInstanceManager,
|
|
4
|
-
} from "@axiom-lattice/core";
|
|
5
|
-
import type {
|
|
6
|
-
InboundMessage,
|
|
7
|
-
DispatchResult,
|
|
8
|
-
BindingRegistry,
|
|
9
|
-
Binding,
|
|
10
|
-
ChannelInstallationStore,
|
|
11
|
-
} from "@axiom-lattice/protocols";
|
|
12
|
-
import { ChannelAdapterRegistry } from "../channels/registry";
|
|
13
|
-
import type { MessageContext, MessageMiddleware } from "./MessageContext";
|
|
14
|
-
import { randomUUID } from "crypto";
|
|
15
|
-
|
|
16
|
-
export class BindingNotFoundError extends Error {
|
|
17
|
-
constructor(message: string) {
|
|
18
|
-
super(message);
|
|
19
|
-
this.name = "BindingNotFoundError";
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Configuration for {@link MessageRouter}.
|
|
25
|
-
*
|
|
26
|
-
* @param middlewares - Ordered middleware chain executed before dispatch
|
|
27
|
-
* @param bindingRegistry - Resolves sender-to-agent bindings
|
|
28
|
-
* @param adapterRegistry - Registry of {@link ChannelAdapter} implementations
|
|
29
|
-
* @param installationStore - Persisted channel installation configs
|
|
30
|
-
*/
|
|
31
|
-
export interface MessageRouterConfig {
|
|
32
|
-
middlewares: MessageMiddleware[];
|
|
33
|
-
bindingRegistry: BindingRegistry;
|
|
34
|
-
adapterRegistry: ChannelAdapterRegistry;
|
|
35
|
-
installationStore: ChannelInstallationStore;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Core message router for external channel integration.
|
|
40
|
-
*
|
|
41
|
-
* Receives normalized {@link InboundMessage} objects from channel adapters,
|
|
42
|
-
* resolves the target agent via {@link BindingRegistry}, manages thread
|
|
43
|
-
* lifecycle, and sends AI responses back to the channel via
|
|
44
|
-
* `ChannelAdapter.sendReply`.
|
|
45
|
-
*
|
|
46
|
-
* ## Reply flow
|
|
47
|
-
*
|
|
48
|
-
* The router uses a counter-based deduplication scheme on the internal
|
|
49
|
-
* {@link Agent.reply:ready} event:
|
|
50
|
-
*
|
|
51
|
-
* 1. Before `agent.addMessage()`, it subscribes to `reply:ready` on the agent.
|
|
52
|
-
* 2. Multiple concurrent dispatches on the same thread increment a counter;
|
|
53
|
-
* only the first registers the `EventEmitter.once` listener.
|
|
54
|
-
* 3. When `reply:ready` fires, the callback extracts the last AI message from
|
|
55
|
-
* the LangGraph state and sends it via `ChannelAdapter.sendReply`.
|
|
56
|
-
* 4. The counter is decremented; the subscription is only removed when the
|
|
57
|
-
* counter reaches 0.
|
|
58
|
-
* 5. Stale subscriptions are cleaned up after a 1-hour timeout.
|
|
59
|
-
*
|
|
60
|
-
* The `replyTarget` is carried through by injecting `_replyTarget` into
|
|
61
|
-
* `custom_run_config` on {@link Agent.addMessage}, and retrieved from the
|
|
62
|
-
* `reply:ready` event payload.
|
|
63
|
-
*
|
|
64
|
-
* @see {@link ChannelAdapter.sendReply}
|
|
65
|
-
* @see {@link InboundMessage.replyTarget}
|
|
66
|
-
*/
|
|
67
|
-
export class MessageRouter {
|
|
68
|
-
private middlewares: MessageMiddleware[];
|
|
69
|
-
private bindingRegistry: BindingRegistry;
|
|
70
|
-
private adapterRegistry: ChannelAdapterRegistry;
|
|
71
|
-
private installationStore: ChannelInstallationStore;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Tracks reply subscriptions per thread+channel to avoid duplicate
|
|
75
|
-
* `subscribeOnce` registrations and ensure proper cleanup.
|
|
76
|
-
*
|
|
77
|
-
* Key format: `{threadId}:{adapterChannel}:reply`
|
|
78
|
-
*/
|
|
79
|
-
private _replySubs: Map<string, { count: number; timer: NodeJS.Timeout }> = new Map();
|
|
80
|
-
|
|
81
|
-
constructor(config: MessageRouterConfig) {
|
|
82
|
-
this.middlewares = [...config.middlewares];
|
|
83
|
-
this.bindingRegistry = config.bindingRegistry;
|
|
84
|
-
this.adapterRegistry = config.adapterRegistry;
|
|
85
|
-
this.installationStore = config.installationStore;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Register an additional middleware at the end of the chain.
|
|
90
|
-
*
|
|
91
|
-
* @param middleware - A {@link MessageMiddleware} function
|
|
92
|
-
*/
|
|
93
|
-
use(middleware: MessageMiddleware): void {
|
|
94
|
-
this.middlewares.push(middleware);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Dispatch an inbound channel message to the bound agent.
|
|
99
|
-
*
|
|
100
|
-
* Full pipeline: middleware chain → binding resolution → thread lifecycle
|
|
101
|
-
* → agent.addMessage() → (async) reply via {@link ChannelAdapter.sendReply}.
|
|
102
|
-
*
|
|
103
|
-
* If the message has a {@link InboundMessage.replyTarget}, the router subscribes
|
|
104
|
-
* to the agent's `reply:ready` event before enqueuing the message, and sends
|
|
105
|
-
* the AI response back to the channel when it arrives.
|
|
106
|
-
*
|
|
107
|
-
* @param message - Normalized inbound message from a channel adapter
|
|
108
|
-
* @returns {@link DispatchResult} with success status, bindingId, and threadId
|
|
109
|
-
*/
|
|
110
|
-
async dispatch(message: InboundMessage): Promise<DispatchResult> {
|
|
111
|
-
const ctx: MessageContext = {
|
|
112
|
-
inboundMessage: message,
|
|
113
|
-
metadata: {},
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
let binding: Binding | null = null;
|
|
117
|
-
let threadId: string | undefined;
|
|
118
|
-
let agentId: string | undefined;
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
await this.runMiddlewares(ctx, async () => {
|
|
122
|
-
// Resolve tenantId from installation if not provided
|
|
123
|
-
const tenantId = message.tenantId
|
|
124
|
-
|| (await this.installationStore.getInstallationById(message.channelInstallationId))?.tenantId;
|
|
125
|
-
|
|
126
|
-
if (!tenantId) {
|
|
127
|
-
console.error({
|
|
128
|
-
event: "dispatch:error",
|
|
129
|
-
error: "tenantId missing",
|
|
130
|
-
channel: message.channel,
|
|
131
|
-
channelInstallationId: message.channelInstallationId,
|
|
132
|
-
senderId: message.sender.id,
|
|
133
|
-
}, "tenantId is required");
|
|
134
|
-
throw new Error(
|
|
135
|
-
"tenantId is required: provide it in the message or ensure the channelInstallation has a tenantId"
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
console.log({ event: "dispatch:start", channel: message.channel, senderId: message.sender.id, tenantId }, "Message dispatch started");
|
|
140
|
-
|
|
141
|
-
// 检查 adapter 是否支持自定义 thread 策略
|
|
142
|
-
const adapter = this.adapterRegistry.get(message.channel);
|
|
143
|
-
const hasAdapterThreadStrategy = !!adapter?.resolveThreadId;
|
|
144
|
-
|
|
145
|
-
binding = await this.bindingRegistry.resolve({
|
|
146
|
-
channel: message.channel,
|
|
147
|
-
senderId: message.sender.id,
|
|
148
|
-
channelInstallationId: message.channelInstallationId,
|
|
149
|
-
tenantId,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
const installation = await this.installationStore.getInstallationById(
|
|
153
|
-
message.channelInstallationId,
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
if (!binding) {
|
|
157
|
-
if (installation?.rejectWhenNoBinding) {
|
|
158
|
-
console.warn({
|
|
159
|
-
event: "dispatch:no_binding",
|
|
160
|
-
channel: message.channel,
|
|
161
|
-
senderId: message.sender.id,
|
|
162
|
-
tenantId,
|
|
163
|
-
channelInstallationId: message.channelInstallationId,
|
|
164
|
-
}, "No binding found and rejectWhenNoBinding is enabled");
|
|
165
|
-
throw new BindingNotFoundError(
|
|
166
|
-
`No binding for sender "${message.sender.id}" on channel "${message.channel}"`,
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (installation?.fallbackAgentId) {
|
|
171
|
-
console.warn({
|
|
172
|
-
event: "dispatch:fallback",
|
|
173
|
-
channel: message.channel,
|
|
174
|
-
senderId: message.sender.id,
|
|
175
|
-
fallbackAgentId: installation.fallbackAgentId,
|
|
176
|
-
}, "No binding found, falling back to fallbackAgentId");
|
|
177
|
-
binding = {
|
|
178
|
-
id: "fallback",
|
|
179
|
-
channel: message.channel,
|
|
180
|
-
channelInstallationId: message.channelInstallationId,
|
|
181
|
-
tenantId,
|
|
182
|
-
senderId: message.sender.id,
|
|
183
|
-
agentId: installation.fallbackAgentId,
|
|
184
|
-
threadId: undefined,
|
|
185
|
-
threadMode: "fixed" as const,
|
|
186
|
-
enabled: true,
|
|
187
|
-
createdAt: new Date(),
|
|
188
|
-
updatedAt: new Date(),
|
|
189
|
-
};
|
|
190
|
-
} else if (!hasAdapterThreadStrategy) {
|
|
191
|
-
// 只有当 adapter 没有自定义 thread 策略且没有 fallback 时才报错
|
|
192
|
-
console.error({
|
|
193
|
-
event: "dispatch:no_fallback",
|
|
194
|
-
channel: message.channel,
|
|
195
|
-
senderId: message.sender.id,
|
|
196
|
-
tenantId,
|
|
197
|
-
}, "No binding found and no fallbackAgentId configured");
|
|
198
|
-
throw new BindingNotFoundError(
|
|
199
|
-
`No binding for sender "${message.sender.id}" and no fallback configured`,
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
ctx.binding = binding ?? undefined;
|
|
205
|
-
|
|
206
|
-
if (binding) {
|
|
207
|
-
console.log({
|
|
208
|
-
event: "dispatch:binding",
|
|
209
|
-
bindingId: binding.id,
|
|
210
|
-
agentId: binding.agentId,
|
|
211
|
-
threadId: binding.threadId,
|
|
212
|
-
threadMode: binding.threadMode,
|
|
213
|
-
workspaceId: binding.workspaceId,
|
|
214
|
-
projectId: binding.projectId,
|
|
215
|
-
}, "Binding resolved");
|
|
216
|
-
|
|
217
|
-
if (!binding.enabled) {
|
|
218
|
-
console.warn({
|
|
219
|
-
event: "dispatch:binding_disabled",
|
|
220
|
-
bindingId: binding.id,
|
|
221
|
-
agentId: binding.agentId,
|
|
222
|
-
senderId: message.sender.id,
|
|
223
|
-
}, "Binding is disabled, rejecting message");
|
|
224
|
-
throw new BindingNotFoundError(
|
|
225
|
-
`Binding for sender "${message.sender.id}" is disabled`,
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// 确定 agentId
|
|
231
|
-
agentId = binding?.agentId ?? installation?.fallbackAgentId;
|
|
232
|
-
if (!agentId) {
|
|
233
|
-
throw new BindingNotFoundError(
|
|
234
|
-
`No agent configured for sender "${message.sender.id}"`,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// 优先使用 adapter 的自定义 thread 策略
|
|
239
|
-
if (hasAdapterThreadStrategy) {
|
|
240
|
-
const resolvedThreadId = await adapter!.resolveThreadId!(message, binding);
|
|
241
|
-
threadId = resolvedThreadId;
|
|
242
|
-
console.log({
|
|
243
|
-
event: "dispatch:thread:adapter",
|
|
244
|
-
threadId,
|
|
245
|
-
channel: message.channel,
|
|
246
|
-
adapterChannel: adapter!.channel,
|
|
247
|
-
}, "Thread resolved by adapter strategy");
|
|
248
|
-
|
|
249
|
-
// 确保 thread 存在(adapter 的策略可能有重复,例如同一天)
|
|
250
|
-
const threadStore = getStoreLattice("default", "thread").store;
|
|
251
|
-
try {
|
|
252
|
-
await threadStore.createThread(
|
|
253
|
-
tenantId!,
|
|
254
|
-
agentId,
|
|
255
|
-
threadId,
|
|
256
|
-
{
|
|
257
|
-
metadata: {
|
|
258
|
-
channel: message.channel,
|
|
259
|
-
channelInstallationId: message.channelInstallationId,
|
|
260
|
-
senderId: message.sender.id,
|
|
261
|
-
bindingId: binding?.id,
|
|
262
|
-
...(message.conversation ? {
|
|
263
|
-
conversationId: message.conversation.id,
|
|
264
|
-
conversationType: message.conversation.type,
|
|
265
|
-
} : {}),
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
);
|
|
269
|
-
console.log({
|
|
270
|
-
event: "dispatch:thread:adapter:created",
|
|
271
|
-
threadId,
|
|
272
|
-
}, "Thread created by adapter strategy");
|
|
273
|
-
} catch {
|
|
274
|
-
// Thread already exists from a previous message — 复用
|
|
275
|
-
console.log({
|
|
276
|
-
event: "dispatch:thread:adapter:reuse",
|
|
277
|
-
threadId,
|
|
278
|
-
}, "Thread already exists, reusing");
|
|
279
|
-
}
|
|
280
|
-
} else if (binding) {
|
|
281
|
-
// 默认策略:per_conversation 总是创建新线程,fixed 复用已有线程
|
|
282
|
-
if (binding.threadMode === "per_conversation") {
|
|
283
|
-
threadId = undefined; // 创建新线程
|
|
284
|
-
} else {
|
|
285
|
-
threadId = binding.threadId; // 复用 fixed 线程
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (!threadId) {
|
|
290
|
-
const threadStore = getStoreLattice("default", "thread").store;
|
|
291
|
-
const newThreadId = randomUUID();
|
|
292
|
-
console.log({
|
|
293
|
-
event: "dispatch:thread:create",
|
|
294
|
-
agentId,
|
|
295
|
-
newThreadId,
|
|
296
|
-
tenantId: tenantId!,
|
|
297
|
-
}, "Creating new thread for binding");
|
|
298
|
-
const newThread = await threadStore.createThread(
|
|
299
|
-
tenantId!,
|
|
300
|
-
agentId,
|
|
301
|
-
newThreadId,
|
|
302
|
-
{
|
|
303
|
-
metadata: {
|
|
304
|
-
channel: message.channel,
|
|
305
|
-
channelInstallationId: message.channelInstallationId,
|
|
306
|
-
senderId: message.sender.id,
|
|
307
|
-
bindingId: binding?.id,
|
|
308
|
-
...(message.conversation ? {
|
|
309
|
-
conversationId: message.conversation.id,
|
|
310
|
-
conversationType: message.conversation.type,
|
|
311
|
-
} : {}),
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
);
|
|
315
|
-
threadId = newThread.id;
|
|
316
|
-
if (binding && binding.id !== "fallback") {
|
|
317
|
-
await this.bindingRegistry.update(binding.id, { threadId });
|
|
318
|
-
binding.threadId = threadId;
|
|
319
|
-
} else if (binding) {
|
|
320
|
-
binding.threadId = threadId;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
console.log({
|
|
325
|
-
event: "dispatch:agent",
|
|
326
|
-
agentId,
|
|
327
|
-
threadId,
|
|
328
|
-
senderId: message.sender.id,
|
|
329
|
-
contentLength: message.content.text.length,
|
|
330
|
-
}, "Dispatching to agent");
|
|
331
|
-
|
|
332
|
-
const agent = agentInstanceManager.getAgent({
|
|
333
|
-
tenant_id: tenantId!,
|
|
334
|
-
assistant_id: agentId,
|
|
335
|
-
thread_id: threadId,
|
|
336
|
-
workspace_id: binding?.workspaceId || "",
|
|
337
|
-
project_id: binding?.projectId || "",
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Subscribe to reply:ready before addMessage to avoid a race with
|
|
342
|
-
* an already-running queue processor.
|
|
343
|
-
*
|
|
344
|
-
* The `_replySubs` map uses a counter (not a boolean) so that multiple
|
|
345
|
-
* concurrent dispatches on the same thread each get a callback invocation.
|
|
346
|
-
* The `subscribeOnce` listener is only registered when the counter goes
|
|
347
|
-
* from 0 → 1, and removed when it goes back to 0. A 1-hour timeout
|
|
348
|
-
* protects against leaked subscriptions due to agent errors/aborts.
|
|
349
|
-
*/
|
|
350
|
-
if (message.replyTarget) {
|
|
351
|
-
const replySubKey = `${threadId}:${message.replyTarget.adapterChannel}:reply`;
|
|
352
|
-
const adapter = this.adapterRegistry.get(message.replyTarget.adapterChannel);
|
|
353
|
-
if (adapter) {
|
|
354
|
-
const installation = await this.installationStore.getInstallationById(
|
|
355
|
-
message.channelInstallationId,
|
|
356
|
-
);
|
|
357
|
-
if (installation) {
|
|
358
|
-
const existing = this._replySubs.get(replySubKey);
|
|
359
|
-
if (!existing || existing.count === 0) {
|
|
360
|
-
// First subscriber for this thread+channel — register the event listener
|
|
361
|
-
const timer = setTimeout(() => {
|
|
362
|
-
// Cleanup if reply never fires (agent aborted, errored, etc.)
|
|
363
|
-
const entry = this._replySubs.get(replySubKey);
|
|
364
|
-
if (entry) {
|
|
365
|
-
this._replySubs.delete(replySubKey);
|
|
366
|
-
console.warn({
|
|
367
|
-
event: "dispatch:reply:timeout",
|
|
368
|
-
threadId,
|
|
369
|
-
channel: message.replyTarget!.adapterChannel,
|
|
370
|
-
}, "Reply subscription timed out — no reply:ready fired within 1h");
|
|
371
|
-
}
|
|
372
|
-
}, 3_600_000);
|
|
373
|
-
|
|
374
|
-
this._replySubs.set(replySubKey, { count: 1, timer });
|
|
375
|
-
|
|
376
|
-
console.log({
|
|
377
|
-
event: "dispatch:reply:subscribed",
|
|
378
|
-
threadId,
|
|
379
|
-
channel: message.replyTarget.adapterChannel,
|
|
380
|
-
senderId: message.sender.id,
|
|
381
|
-
}, "Subscribed to reply:ready for thread");
|
|
382
|
-
|
|
383
|
-
agent.subscribeOnce('reply:ready', (data: any) => {
|
|
384
|
-
const messages = data.state?.values?.messages;
|
|
385
|
-
const lastAI = messages
|
|
386
|
-
?.filter((m: any) => m.type === 'ai' || m.getType?.() === 'ai')
|
|
387
|
-
.pop();
|
|
388
|
-
const replyText = lastAI?.content ?? '';
|
|
389
|
-
|
|
390
|
-
// Decrement counter; unsubscribe only when count reaches 0
|
|
391
|
-
const entry = this._replySubs.get(replySubKey);
|
|
392
|
-
if (entry) {
|
|
393
|
-
entry.count--;
|
|
394
|
-
if (entry.count <= 0) {
|
|
395
|
-
clearTimeout(entry.timer);
|
|
396
|
-
this._replySubs.delete(replySubKey);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (replyText) {
|
|
401
|
-
console.log({
|
|
402
|
-
event: "dispatch:reply:sending",
|
|
403
|
-
threadId,
|
|
404
|
-
channel: message.replyTarget!.adapterChannel,
|
|
405
|
-
replyLength: replyText.length,
|
|
406
|
-
}, "Sending channel reply");
|
|
407
|
-
adapter.sendReply(message.replyTarget!, { text: replyText }, installation)
|
|
408
|
-
.then(() => {
|
|
409
|
-
console.log({
|
|
410
|
-
event: "dispatch:reply:sent",
|
|
411
|
-
threadId,
|
|
412
|
-
channel: message.replyTarget!.adapterChannel,
|
|
413
|
-
}, "Channel reply sent successfully");
|
|
414
|
-
})
|
|
415
|
-
.catch((err) => console.error({
|
|
416
|
-
event: "dispatch:reply:failed",
|
|
417
|
-
threadId,
|
|
418
|
-
channel: message.replyTarget!.adapterChannel,
|
|
419
|
-
error: err instanceof Error ? err.message : String(err),
|
|
420
|
-
}, "Failed to send channel reply"));
|
|
421
|
-
} else {
|
|
422
|
-
console.warn({
|
|
423
|
-
event: "dispatch:reply:empty",
|
|
424
|
-
threadId,
|
|
425
|
-
channel: message.replyTarget!.adapterChannel,
|
|
426
|
-
}, "Agent produced no text output — skipping reply");
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
} else {
|
|
430
|
-
// Another dispatch already subscribed — increment counter
|
|
431
|
-
existing.count++;
|
|
432
|
-
console.log({
|
|
433
|
-
event: "dispatch:reply:incremented",
|
|
434
|
-
threadId,
|
|
435
|
-
channel: message.replyTarget.adapterChannel,
|
|
436
|
-
count: existing.count,
|
|
437
|
-
}, "Incremented reply counter for thread (already subscribed)");
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const addResult = await agent.addMessage({
|
|
444
|
-
input: { message: message.content.text },
|
|
445
|
-
custom_run_config: message.replyTarget
|
|
446
|
-
? { ...(message.content.metadata || {}), _replyTarget: message.replyTarget }
|
|
447
|
-
: (message.content.metadata || {}),
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
console.log({
|
|
451
|
-
event: "dispatch:complete",
|
|
452
|
-
agentId,
|
|
453
|
-
threadId,
|
|
454
|
-
messageId: (addResult as Record<string, unknown>)?.messageId,
|
|
455
|
-
result: JSON.stringify(addResult),
|
|
456
|
-
}, "Agent dispatch complete — messageId = " + ((addResult as Record<string, unknown>)?.messageId || "N/A"));
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
success: true,
|
|
461
|
-
bindingId: ctx.binding?.id,
|
|
462
|
-
threadId,
|
|
463
|
-
result: ctx.result,
|
|
464
|
-
};
|
|
465
|
-
} catch (error) {
|
|
466
|
-
ctx.error = error instanceof Error ? error : new Error(String(error));
|
|
467
|
-
console.error({ event: "dispatch:error", error: ctx.error.message, channel: message.channel, senderId: message.sender.id }, "Message dispatch failed");
|
|
468
|
-
return {
|
|
469
|
-
success: false,
|
|
470
|
-
bindingId: ctx.binding?.id,
|
|
471
|
-
threadId: ctx.binding?.threadId,
|
|
472
|
-
error: ctx.error,
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
private async runMiddlewares(
|
|
478
|
-
ctx: MessageContext,
|
|
479
|
-
finalHandler: () => Promise<void>,
|
|
480
|
-
): Promise<void> {
|
|
481
|
-
const dispatch = (index: number): Promise<void> => {
|
|
482
|
-
if (index >= this.middlewares.length) {
|
|
483
|
-
return finalHandler();
|
|
484
|
-
}
|
|
485
|
-
const middleware = this.middlewares[index];
|
|
486
|
-
return middleware(ctx, () => dispatch(index + 1));
|
|
487
|
-
};
|
|
488
|
-
return dispatch(0);
|
|
489
|
-
}
|
|
490
|
-
}
|