@axiom-lattice/gateway 2.1.92 → 2.1.94
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +22 -0
- package/dist/index.js +972 -385
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +876 -288
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/scripts/seed-lark-installation.ts +54 -0
- package/src/channels/lark/LarkChannelAdapter.ts +140 -18
- package/src/channels/lark/types.ts +0 -13
- package/src/channels/lark/verification.ts +1 -36
- package/src/controllers/__tests__/run.test.ts +115 -0
- package/src/controllers/__tests__/tasks.test.ts +215 -0
- package/src/controllers/agent_task.ts +7 -0
- package/src/controllers/assistant.ts +15 -2
- package/src/controllers/auth.ts +9 -0
- package/src/controllers/menu-items.ts +114 -0
- package/src/controllers/personal-assistant.ts +191 -0
- package/src/controllers/run.ts +47 -0
- package/src/controllers/tasks.ts +187 -0
- package/src/controllers/workspace.ts +12 -0
- package/src/index.ts +55 -20
- package/src/router/MessageRouter.ts +107 -41
- package/src/routes/index.ts +23 -0
- package/src/routes/menu-items.ts +10 -0
- package/src/services/agent_task_consumer.ts +2 -8
- package/src/channels/lark/__tests__/aggregator.test.ts +0 -23
- package/src/channels/lark/aggregator.ts +0 -16
- package/src/channels/lark/config.ts +0 -44
- package/src/channels/lark/runner.ts +0 -37
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { ChannelInstallationStore, BindingRegistry } from "@axiom-lattice/p
|
|
|
11
11
|
import { MessageRouter } from "./router/MessageRouter";
|
|
12
12
|
import { ChannelAdapterRegistry } from "./channels/registry";
|
|
13
13
|
import { createDeduplicationMiddleware, createRateLimitMiddleware, createAuditLoggerMiddleware } from "./router/middlewares";
|
|
14
|
-
import { setBindingRegistry } from "@axiom-lattice/core";
|
|
14
|
+
import { setBindingRegistry, setMenuRegistry } from "@axiom-lattice/core";
|
|
15
15
|
import { larkChannelAdapter } from "./channels/lark/LarkChannelAdapter";
|
|
16
16
|
import { extractUserFromAuthHeader } from "./controllers/auth";
|
|
17
17
|
import { configureSwagger } from "./swagger";
|
|
@@ -125,6 +125,11 @@ app.addHook("preHandler", async (request, reply) => {
|
|
|
125
125
|
// Public routes don't require authentication
|
|
126
126
|
if (PUBLIC_ROUTES.some((r) => request.url === r)) return;
|
|
127
127
|
|
|
128
|
+
// Allow direct URL access for iframe endpoints (viewfile, downloadfile)
|
|
129
|
+
// These carry tenant context via query params and are accessed by iframes
|
|
130
|
+
const urlPath = request.url.split("?")[0];
|
|
131
|
+
if (urlPath.includes("/viewfile") || urlPath.includes("/downloadfile")) return;
|
|
132
|
+
|
|
128
133
|
return reply.status(401).send({
|
|
129
134
|
success: false,
|
|
130
135
|
error: "Unauthorized - Missing or invalid token",
|
|
@@ -277,17 +282,22 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
277
282
|
// Access via: request.server.loggerLattice or app.loggerLattice
|
|
278
283
|
app.decorate("loggerLattice", loggerLattice);
|
|
279
284
|
|
|
280
|
-
// Channel infrastructure: use stores from StoreLatticeManager
|
|
281
|
-
//
|
|
285
|
+
// Channel infrastructure: use stores from StoreLatticeManager.
|
|
286
|
+
// Applications (e.g. deep_research) register stores via configureStores()
|
|
287
|
+
// before calling startAsHttpEndpoint(). If no stores are registered,
|
|
288
|
+
// channel features are gracefully skipped.
|
|
282
289
|
let channelDeps: { router: MessageRouter; installationStore: ChannelInstallationStore } | undefined;
|
|
290
|
+
const adapterRegistry = new ChannelAdapterRegistry();
|
|
291
|
+
adapterRegistry.register(larkChannelAdapter);
|
|
283
292
|
try {
|
|
284
|
-
const { getStoreLattice } = await import("@axiom-lattice/core");
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
setBindingRegistry(bindingStore);
|
|
293
|
+
const { getStoreLattice: getStore } = await import("@axiom-lattice/core");
|
|
294
|
+
let bindingStore: BindingRegistry;
|
|
295
|
+
let installationStore: ChannelInstallationStore;
|
|
288
296
|
|
|
289
|
-
|
|
290
|
-
|
|
297
|
+
bindingStore = getStore("default", "channelBinding").store as BindingRegistry;
|
|
298
|
+
installationStore = getStore("default", "channelInstallation").store as ChannelInstallationStore;
|
|
299
|
+
|
|
300
|
+
setBindingRegistry(bindingStore);
|
|
291
301
|
|
|
292
302
|
const router = new MessageRouter({
|
|
293
303
|
middlewares: [
|
|
@@ -305,7 +315,7 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
305
315
|
|
|
306
316
|
// Initialize A2A API key store
|
|
307
317
|
try {
|
|
308
|
-
const a2aKeyStore =
|
|
318
|
+
const a2aKeyStore = getStore("default", "a2aApiKey").store as import("@axiom-lattice/protocols").A2AApiKeyStore;
|
|
309
319
|
const a2a = await import("./routes/a2a");
|
|
310
320
|
a2a.setA2AKeyStore(a2aKeyStore);
|
|
311
321
|
await a2a.refreshStoreKeyMap();
|
|
@@ -313,8 +323,19 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
313
323
|
} catch {
|
|
314
324
|
// A2A key store optional — env-based keys still work
|
|
315
325
|
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
logger.warn("Channel infrastructure unavailable", {
|
|
328
|
+
error: err instanceof Error ? err.message : String(err),
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Initialize MenuRegistry for custom menu items
|
|
333
|
+
try {
|
|
334
|
+
const menuStore = getStoreLattice("default", "menu").store;
|
|
335
|
+
setMenuRegistry(menuStore);
|
|
336
|
+
logger.info("Menu registry initialized");
|
|
316
337
|
} catch {
|
|
317
|
-
//
|
|
338
|
+
// Menu store optional — custom menus won't work without it
|
|
318
339
|
}
|
|
319
340
|
|
|
320
341
|
// Register all routes (channel routes only active if channelDeps is set)
|
|
@@ -326,6 +347,21 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
326
347
|
logger.info("Registered sandbox manager from env configuration");
|
|
327
348
|
}
|
|
328
349
|
|
|
350
|
+
// Connect all enabled channel installations
|
|
351
|
+
if (channelDeps && process.env.CHANNELS_ENABLED !== "false") {
|
|
352
|
+
const { connectAllChannels } = await import("@axiom-lattice/core");
|
|
353
|
+
try {
|
|
354
|
+
await connectAllChannels(
|
|
355
|
+
(channel) => adapterRegistry.get(channel),
|
|
356
|
+
{ deps: { router: channelDeps.router } },
|
|
357
|
+
);
|
|
358
|
+
} catch (err) {
|
|
359
|
+
logger.error("Failed to start channel connections", {
|
|
360
|
+
error: err instanceof Error ? err.message : String(err),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
329
365
|
const target_port = config?.port || Number(process.env.PORT) || 4001;
|
|
330
366
|
|
|
331
367
|
await app.listen({ port: target_port, host: "0.0.0.0" });
|
|
@@ -348,15 +384,14 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
348
384
|
}
|
|
349
385
|
}
|
|
350
386
|
|
|
351
|
-
// Restore agent instances with pending messages
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
387
|
+
// Restore agent instances with pending messages (fire-and-forget to avoid blocking startup)
|
|
388
|
+
agentInstanceManager.restore()
|
|
389
|
+
.then((stats) => {
|
|
390
|
+
logger.info(`Agent recovery complete: ${stats.restored} threads restored, ${stats.errors} errors`);
|
|
391
|
+
})
|
|
392
|
+
.catch((error) => {
|
|
393
|
+
logger.error("Agent recovery failed", { error });
|
|
394
|
+
});
|
|
360
395
|
} catch (err) {
|
|
361
396
|
logger.error("Server start failed", { error: err });
|
|
362
397
|
process.exit(1);
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
InboundMessage,
|
|
7
7
|
DispatchResult,
|
|
8
8
|
BindingRegistry,
|
|
9
|
+
Binding,
|
|
9
10
|
ChannelInstallationStore,
|
|
10
11
|
} from "@axiom-lattice/protocols";
|
|
11
12
|
import { ChannelAdapterRegistry } from "../channels/registry";
|
|
@@ -112,6 +113,10 @@ export class MessageRouter {
|
|
|
112
113
|
metadata: {},
|
|
113
114
|
};
|
|
114
115
|
|
|
116
|
+
let binding: Binding | null = null;
|
|
117
|
+
let threadId: string | undefined;
|
|
118
|
+
let agentId: string | undefined;
|
|
119
|
+
|
|
115
120
|
try {
|
|
116
121
|
await this.runMiddlewares(ctx, async () => {
|
|
117
122
|
// Resolve tenantId from installation if not provided
|
|
@@ -133,18 +138,22 @@ export class MessageRouter {
|
|
|
133
138
|
|
|
134
139
|
console.log({ event: "dispatch:start", channel: message.channel, senderId: message.sender.id, tenantId }, "Message dispatch started");
|
|
135
140
|
|
|
136
|
-
|
|
141
|
+
// 检查 adapter 是否支持自定义 thread 策略
|
|
142
|
+
const adapter = this.adapterRegistry.get(message.channel);
|
|
143
|
+
const hasAdapterThreadStrategy = !!adapter?.resolveThreadId;
|
|
144
|
+
|
|
145
|
+
binding = await this.bindingRegistry.resolve({
|
|
137
146
|
channel: message.channel,
|
|
138
147
|
senderId: message.sender.id,
|
|
139
148
|
channelInstallationId: message.channelInstallationId,
|
|
140
149
|
tenantId,
|
|
141
150
|
});
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
);
|
|
152
|
+
const installation = await this.installationStore.getInstallationById(
|
|
153
|
+
message.channelInstallationId,
|
|
154
|
+
);
|
|
147
155
|
|
|
156
|
+
if (!binding) {
|
|
148
157
|
if (installation?.rejectWhenNoBinding) {
|
|
149
158
|
console.warn({
|
|
150
159
|
event: "dispatch:no_binding",
|
|
@@ -178,7 +187,8 @@ export class MessageRouter {
|
|
|
178
187
|
createdAt: new Date(),
|
|
179
188
|
updatedAt: new Date(),
|
|
180
189
|
};
|
|
181
|
-
} else {
|
|
190
|
+
} else if (!hasAdapterThreadStrategy) {
|
|
191
|
+
// 只有当 adapter 没有自定义 thread 策略且没有 fallback 时才报错
|
|
182
192
|
console.error({
|
|
183
193
|
event: "dispatch:no_fallback",
|
|
184
194
|
channel: message.channel,
|
|
@@ -191,53 +201,110 @@ export class MessageRouter {
|
|
|
191
201
|
}
|
|
192
202
|
}
|
|
193
203
|
|
|
194
|
-
ctx.binding = binding;
|
|
204
|
+
ctx.binding = binding ?? undefined;
|
|
195
205
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
agentId: binding.agentId,
|
|
200
|
-
threadId: binding.threadId,
|
|
201
|
-
threadMode: binding.threadMode,
|
|
202
|
-
workspaceId: binding.workspaceId,
|
|
203
|
-
projectId: binding.projectId,
|
|
204
|
-
}, "Binding resolved");
|
|
205
|
-
|
|
206
|
-
if (!binding.enabled) {
|
|
207
|
-
console.warn({
|
|
208
|
-
event: "dispatch:binding_disabled",
|
|
206
|
+
if (binding) {
|
|
207
|
+
console.log({
|
|
208
|
+
event: "dispatch:binding",
|
|
209
209
|
bindingId: binding.id,
|
|
210
210
|
agentId: binding.agentId,
|
|
211
|
-
|
|
212
|
-
|
|
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) {
|
|
213
233
|
throw new BindingNotFoundError(
|
|
214
|
-
`
|
|
234
|
+
`No agent configured for sender "${message.sender.id}"`,
|
|
215
235
|
);
|
|
216
236
|
}
|
|
217
237
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
+
}
|
|
221
288
|
|
|
222
289
|
if (!threadId) {
|
|
223
290
|
const threadStore = getStoreLattice("default", "thread").store;
|
|
224
291
|
const newThreadId = randomUUID();
|
|
225
292
|
console.log({
|
|
226
293
|
event: "dispatch:thread:create",
|
|
227
|
-
agentId
|
|
294
|
+
agentId,
|
|
228
295
|
newThreadId,
|
|
229
296
|
tenantId: tenantId!,
|
|
230
297
|
}, "Creating new thread for binding");
|
|
231
298
|
const newThread = await threadStore.createThread(
|
|
232
299
|
tenantId!,
|
|
233
|
-
|
|
300
|
+
agentId,
|
|
234
301
|
newThreadId,
|
|
235
302
|
{
|
|
236
303
|
metadata: {
|
|
237
304
|
channel: message.channel,
|
|
238
305
|
channelInstallationId: message.channelInstallationId,
|
|
239
306
|
senderId: message.sender.id,
|
|
240
|
-
bindingId:
|
|
307
|
+
bindingId: binding?.id,
|
|
241
308
|
...(message.conversation ? {
|
|
242
309
|
conversationId: message.conversation.id,
|
|
243
310
|
conversationType: message.conversation.type,
|
|
@@ -246,29 +313,28 @@ export class MessageRouter {
|
|
|
246
313
|
},
|
|
247
314
|
);
|
|
248
315
|
threadId = newThread.id;
|
|
249
|
-
if (
|
|
250
|
-
await this.bindingRegistry.update(
|
|
251
|
-
|
|
252
|
-
} else {
|
|
253
|
-
|
|
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;
|
|
254
321
|
}
|
|
255
322
|
}
|
|
256
323
|
|
|
257
324
|
console.log({
|
|
258
325
|
event: "dispatch:agent",
|
|
259
|
-
agentId
|
|
326
|
+
agentId,
|
|
260
327
|
threadId,
|
|
261
|
-
threadMode: ctx.binding.threadMode,
|
|
262
328
|
senderId: message.sender.id,
|
|
263
329
|
contentLength: message.content.text.length,
|
|
264
330
|
}, "Dispatching to agent");
|
|
265
331
|
|
|
266
332
|
const agent = agentInstanceManager.getAgent({
|
|
267
333
|
tenant_id: tenantId!,
|
|
268
|
-
assistant_id:
|
|
334
|
+
assistant_id: agentId,
|
|
269
335
|
thread_id: threadId,
|
|
270
|
-
workspace_id:
|
|
271
|
-
project_id:
|
|
336
|
+
workspace_id: binding?.workspaceId || "",
|
|
337
|
+
project_id: binding?.projectId || "",
|
|
272
338
|
});
|
|
273
339
|
|
|
274
340
|
/**
|
|
@@ -383,7 +449,7 @@ export class MessageRouter {
|
|
|
383
449
|
|
|
384
450
|
console.log({
|
|
385
451
|
event: "dispatch:complete",
|
|
386
|
-
agentId
|
|
452
|
+
agentId,
|
|
387
453
|
threadId,
|
|
388
454
|
messageId: (addResult as Record<string, unknown>)?.messageId,
|
|
389
455
|
result: JSON.stringify(addResult),
|
|
@@ -393,7 +459,7 @@ export class MessageRouter {
|
|
|
393
459
|
return {
|
|
394
460
|
success: true,
|
|
395
461
|
bindingId: ctx.binding?.id,
|
|
396
|
-
threadId
|
|
462
|
+
threadId,
|
|
397
463
|
result: ctx.result,
|
|
398
464
|
};
|
|
399
465
|
} catch (error) {
|
package/src/routes/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ import * as skillsController from "../controllers/skills";
|
|
|
14
14
|
import * as toolsController from "../controllers/tools";
|
|
15
15
|
import * as dataQueryController from "../controllers/data-query";
|
|
16
16
|
import * as workflowTrackingController from "../controllers/workflow-tracking";
|
|
17
|
+
import * as paController from "../controllers/personal-assistant";
|
|
18
|
+
import * as tasksController from "../controllers/tasks";
|
|
17
19
|
import {
|
|
18
20
|
createRunSchema,
|
|
19
21
|
getAllMemoryItemsSchema,
|
|
@@ -44,6 +46,7 @@ import { registerAuthRoutes } from "../controllers/auth";
|
|
|
44
46
|
import { registerChannelRoutes } from "../channels/routes";
|
|
45
47
|
import { registerChannelInstallationRoutes } from "./channel-installations";
|
|
46
48
|
import { registerChannelBindingRoutes } from "./channel-bindings";
|
|
49
|
+
import { registerMenuItemRoutes } from "./menu-items";
|
|
47
50
|
import type { MessageRouter } from "../router/MessageRouter";
|
|
48
51
|
import type { ChannelInstallationStore } from "@axiom-lattice/protocols";
|
|
49
52
|
// import {
|
|
@@ -76,6 +79,11 @@ export const registerLatticeRoutes = (app: FastifyInstance, channelDeps?: { rout
|
|
|
76
79
|
Params: { assistantId: string; threadId: string };
|
|
77
80
|
}>("/api/assistants/:assistantId/threads/:threadId/abort", runController.abortRun);
|
|
78
81
|
|
|
82
|
+
// Recover agent execution after a crash or manual abort
|
|
83
|
+
app.post<{
|
|
84
|
+
Params: { assistantId: string; threadId: string };
|
|
85
|
+
}>("/api/assistants/:assistantId/threads/:threadId/recover", runController.recoverRun);
|
|
86
|
+
|
|
79
87
|
// app.get<{
|
|
80
88
|
// Params: { id: string };
|
|
81
89
|
// }>("/api/runs/:id", runController.getRun);
|
|
@@ -158,6 +166,19 @@ export const registerLatticeRoutes = (app: FastifyInstance, channelDeps?: { rout
|
|
|
158
166
|
Params: { id: string };
|
|
159
167
|
}>("/api/assistants/:id", assistantController.deleteAssistant);
|
|
160
168
|
|
|
169
|
+
// Personal Assistant routes
|
|
170
|
+
app.post<{ Body: any }>("/api/personal-assistant", paController.createPersonalAssistant);
|
|
171
|
+
app.get("/api/personal-assistant", paController.getPersonalAssistant);
|
|
172
|
+
app.delete("/api/personal-assistant", paController.deletePersonalAssistant);
|
|
173
|
+
|
|
174
|
+
// Task CRUD routes
|
|
175
|
+
app.get("/api/tasks", tasksController.listTasks);
|
|
176
|
+
app.get("/api/tasks/:id", tasksController.getTask);
|
|
177
|
+
app.post("/api/tasks", tasksController.createTask);
|
|
178
|
+
app.put("/api/tasks/:id", tasksController.updateTask);
|
|
179
|
+
app.delete("/api/tasks/:id", tasksController.deleteTask);
|
|
180
|
+
app.patch("/api/tasks/:id/complete", tasksController.completeTask);
|
|
181
|
+
|
|
161
182
|
// 图表路由
|
|
162
183
|
app.get<{
|
|
163
184
|
Params: { assistantId: string };
|
|
@@ -361,6 +382,8 @@ export const registerLatticeRoutes = (app: FastifyInstance, channelDeps?: { rout
|
|
|
361
382
|
|
|
362
383
|
registerChannelInstallationRoutes(app);
|
|
363
384
|
|
|
385
|
+
registerMenuItemRoutes(app);
|
|
386
|
+
|
|
364
387
|
if (channelDeps) {
|
|
365
388
|
registerChannelBindingRoutes(app);
|
|
366
389
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import * as menuController from "../controllers/menu-items";
|
|
3
|
+
|
|
4
|
+
export function registerMenuItemRoutes(app: FastifyInstance): void {
|
|
5
|
+
app.get("/api/menu-items", menuController.getMenuItemList);
|
|
6
|
+
app.post("/api/menu-items", menuController.createMenuItem);
|
|
7
|
+
app.get("/api/menu-items/:id", menuController.getMenuItem);
|
|
8
|
+
app.put("/api/menu-items/:id", menuController.updateMenuItem);
|
|
9
|
+
app.delete("/api/menu-items/:id", menuController.deleteMenuItem);
|
|
10
|
+
}
|
|
@@ -54,7 +54,6 @@ const handleAgentTask = async (
|
|
|
54
54
|
eventBus.publish(callback_event, {
|
|
55
55
|
success: true,
|
|
56
56
|
state: evt.state,
|
|
57
|
-
config: { assistant_id, thread_id, tenant_id },
|
|
58
57
|
});
|
|
59
58
|
|
|
60
59
|
if (main_thread_id && main_tenant_id) {
|
|
@@ -63,6 +62,8 @@ const handleAgentTask = async (
|
|
|
63
62
|
assistant_id: main_assistant_id ?? assistant_id,
|
|
64
63
|
thread_id: main_thread_id,
|
|
65
64
|
tenant_id: main_tenant_id,
|
|
65
|
+
workspace_id: runConfig?.workspaceId,
|
|
66
|
+
project_id: runConfig?.projectId,
|
|
66
67
|
});
|
|
67
68
|
if (mainAgent) {
|
|
68
69
|
const messages = evt.state?.values?.messages;
|
|
@@ -93,7 +94,6 @@ const handleAgentTask = async (
|
|
|
93
94
|
eventBus.publish(callback_event, {
|
|
94
95
|
success: true,
|
|
95
96
|
state: evt.state,
|
|
96
|
-
config: { assistant_id, thread_id, tenant_id },
|
|
97
97
|
});
|
|
98
98
|
})
|
|
99
99
|
}
|
|
@@ -127,7 +127,6 @@ const handleAgentTask = async (
|
|
|
127
127
|
eventBus.publish(callback_event, {
|
|
128
128
|
success: false,
|
|
129
129
|
error: error instanceof Error ? error.message : String(error),
|
|
130
|
-
config: { assistant_id, thread_id, tenant_id },
|
|
131
130
|
});
|
|
132
131
|
}
|
|
133
132
|
|
|
@@ -334,11 +333,6 @@ export class AgentTaskConsumer {
|
|
|
334
333
|
eventBus.publish(taskRequest.callback_event, {
|
|
335
334
|
success: false,
|
|
336
335
|
error: error instanceof Error ? error.message : String(error),
|
|
337
|
-
config: {
|
|
338
|
-
assistant_id: taskRequest.assistant_id,
|
|
339
|
-
thread_id: taskRequest.thread_id,
|
|
340
|
-
tenant_id: taskRequest["x-tenant-id"],
|
|
341
|
-
},
|
|
342
336
|
});
|
|
343
337
|
}
|
|
344
338
|
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { MessageChunkTypes } from "@axiom-lattice/protocols";
|
|
2
|
-
import { aggregateLarkReply } from "../aggregator";
|
|
3
|
-
|
|
4
|
-
describe("aggregateLarkReply", () => {
|
|
5
|
-
it("joins ai chunks for the requested message id", () => {
|
|
6
|
-
const result = aggregateLarkReply("msg-1", [
|
|
7
|
-
{ type: MessageChunkTypes.AI, data: { id: "msg-1", content: "Hel" } },
|
|
8
|
-
{ type: MessageChunkTypes.AI, data: { id: "msg-2", content: "skip" } },
|
|
9
|
-
{ type: MessageChunkTypes.TOOL, data: { id: "msg-1", content: "ignore" } },
|
|
10
|
-
{ type: MessageChunkTypes.AI, data: { id: "msg-1", content: "lo" } },
|
|
11
|
-
]);
|
|
12
|
-
|
|
13
|
-
expect(result).toBe("Hello");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("returns an empty string when no ai content exists for the message", () => {
|
|
17
|
-
const result = aggregateLarkReply("msg-1", [
|
|
18
|
-
{ type: MessageChunkTypes.MESSAGE_COMPLETED, data: { id: "msg-1" } },
|
|
19
|
-
]);
|
|
20
|
-
|
|
21
|
-
expect(result).toBe("");
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { MessageChunk } from "@axiom-lattice/protocols";
|
|
2
|
-
import { MessageChunkTypes } from "@axiom-lattice/protocols";
|
|
3
|
-
|
|
4
|
-
export function aggregateLarkReply(
|
|
5
|
-
messageId: string,
|
|
6
|
-
chunks: MessageChunk[],
|
|
7
|
-
): string {
|
|
8
|
-
return chunks
|
|
9
|
-
.filter(
|
|
10
|
-
(chunk) =>
|
|
11
|
-
chunk.type === MessageChunkTypes.AI && chunk.data.id === messageId,
|
|
12
|
-
)
|
|
13
|
-
.map((chunk) => chunk.data.content || "")
|
|
14
|
-
.join("")
|
|
15
|
-
.trim();
|
|
16
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { LarkIngressConfig } from "./types";
|
|
2
|
-
|
|
3
|
-
export function loadLarkIngressConfig(): LarkIngressConfig {
|
|
4
|
-
return {
|
|
5
|
-
enabled: process.env.LARK_ENABLED !== "false",
|
|
6
|
-
appId: process.env.LARK_APP_ID || "",
|
|
7
|
-
appSecret: process.env.LARK_APP_SECRET || "",
|
|
8
|
-
verificationToken: process.env.LARK_VERIFICATION_TOKEN,
|
|
9
|
-
encryptKey: process.env.LARK_ENCRYPT_KEY,
|
|
10
|
-
tenantId: process.env.LARK_TENANT_ID || "default",
|
|
11
|
-
assistantId: process.env.LARK_ASSISTANT_ID || "default_agent",
|
|
12
|
-
workspaceId: process.env.LARK_WORKSPACE_ID,
|
|
13
|
-
projectId: process.env.LARK_PROJECT_ID,
|
|
14
|
-
mappingMode:
|
|
15
|
-
(process.env.LARK_MAPPING_MODE as "user" | "group" | "hybrid") ||
|
|
16
|
-
"hybrid",
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function isLarkIngressEnabled(config: LarkIngressConfig): boolean {
|
|
21
|
-
return config.enabled;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function assertLarkIngressConfig(config: LarkIngressConfig): void {
|
|
25
|
-
if (!config.enabled) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!config.appId) {
|
|
30
|
-
throw new Error("LARK_APP_ID is required when Lark ingress is enabled");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!config.appSecret) {
|
|
34
|
-
throw new Error("LARK_APP_SECRET is required when Lark ingress is enabled");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!config.tenantId) {
|
|
38
|
-
throw new Error("LARK_TENANT_ID is required when Lark ingress is enabled");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!config.assistantId) {
|
|
42
|
-
throw new Error("LARK_ASSISTANT_ID is required when Lark ingress is enabled");
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { agentInstanceManager } from "@axiom-lattice/core";
|
|
2
|
-
import { MessageChunkTypes } from "@axiom-lattice/protocols";
|
|
3
|
-
import { aggregateLarkReply } from "./aggregator";
|
|
4
|
-
|
|
5
|
-
export async function runAgentAndCollectLarkReply(input: {
|
|
6
|
-
tenantId: string;
|
|
7
|
-
assistantId: string;
|
|
8
|
-
threadId: string;
|
|
9
|
-
text: string;
|
|
10
|
-
workspaceId?: string;
|
|
11
|
-
projectId?: string;
|
|
12
|
-
}): Promise<string> {
|
|
13
|
-
const agent = agentInstanceManager.getAgent({
|
|
14
|
-
tenant_id: input.tenantId,
|
|
15
|
-
assistant_id: input.assistantId,
|
|
16
|
-
thread_id: input.threadId,
|
|
17
|
-
workspace_id: input.workspaceId,
|
|
18
|
-
project_id: input.projectId,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const result = await agent.addMessage({
|
|
22
|
-
input: {
|
|
23
|
-
message: input.text,
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const chunks = [];
|
|
28
|
-
const stream = agent.chunkStream(result.messageId, [
|
|
29
|
-
MessageChunkTypes.MESSAGE_COMPLETED,
|
|
30
|
-
]);
|
|
31
|
-
|
|
32
|
-
for await (const chunk of stream) {
|
|
33
|
-
chunks.push(chunk);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return aggregateLarkReply(result.messageId, chunks);
|
|
37
|
-
}
|