@axiom-lattice/gateway 2.1.91 → 2.1.93
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 +9 -9
- package/CHANGELOG.md +24 -0
- package/dist/index.js +1019 -396
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +923 -299
- 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/workflow-tracking.ts +66 -13
- package/src/index.ts +50 -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
|
@@ -126,7 +126,7 @@ export async function getAllWorkflowDefinitions(
|
|
|
126
126
|
export async function getAllWorkflowRuns(
|
|
127
127
|
request: FastifyRequest<{ Querystring: { assistantId?: string; status?: string } }>,
|
|
128
128
|
reply: FastifyReply
|
|
129
|
-
): Promise<ApiResponse<{ records:
|
|
129
|
+
): Promise<ApiResponse<{ records: any[]; total: number }>> {
|
|
130
130
|
const tenantId = getTenantId(request);
|
|
131
131
|
const { assistantId, status } = request.query;
|
|
132
132
|
|
|
@@ -136,6 +136,19 @@ export async function getAllWorkflowRuns(
|
|
|
136
136
|
return reply.status(404).send({ success: false, message: "No workflow tracking store configured" });
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// Resolve assistant names
|
|
140
|
+
const nameMap: Record<string, string> = {};
|
|
141
|
+
try {
|
|
142
|
+
const asStoreLattice = getStoreLattice("default", "assistant");
|
|
143
|
+
const assistantStore = asStoreLattice.store;
|
|
144
|
+
const assistants = await assistantStore.getAllAssistants(tenantId);
|
|
145
|
+
for (const a of assistants) {
|
|
146
|
+
nameMap[a.id] = a.name;
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// names unavailable, will fall back to ID
|
|
150
|
+
}
|
|
151
|
+
|
|
139
152
|
let runs: WorkflowRun[];
|
|
140
153
|
if (assistantId) {
|
|
141
154
|
runs = await store.getWorkflowRunsByAssistantId(tenantId, assistantId);
|
|
@@ -147,10 +160,24 @@ export async function getAllWorkflowRuns(
|
|
|
147
160
|
runs = runs.filter(r => r.status === status);
|
|
148
161
|
}
|
|
149
162
|
|
|
163
|
+
// Enrich runs with step counts (more accurate than edge-based counts for branching workflows)
|
|
164
|
+
const enrichedRuns = await Promise.all(
|
|
165
|
+
runs.map(async (run) => {
|
|
166
|
+
try {
|
|
167
|
+
const steps = await store.getRunSteps(run.id);
|
|
168
|
+
const totalSteps = steps.length;
|
|
169
|
+
const completedSteps = steps.filter(s => s.status === 'completed').length;
|
|
170
|
+
return { ...run, totalSteps, completedSteps, assistantName: nameMap[run.assistantId] || run.assistantId };
|
|
171
|
+
} catch {
|
|
172
|
+
return { ...run, totalSteps: 0, completedSteps: 0, assistantName: nameMap[run.assistantId] || run.assistantId };
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
|
|
150
177
|
return {
|
|
151
178
|
success: true,
|
|
152
179
|
message: "Successfully retrieved workflow runs",
|
|
153
|
-
data: { records:
|
|
180
|
+
data: { records: enrichedRuns, total: enrichedRuns.length },
|
|
154
181
|
};
|
|
155
182
|
} catch (error) {
|
|
156
183
|
request.log.error(error, "Failed to get workflow runs");
|
|
@@ -185,25 +212,49 @@ export async function getInboxItems(
|
|
|
185
212
|
}
|
|
186
213
|
|
|
187
214
|
const runs = await store.getWorkflowRunsByTenantId(tenantId);
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
190
|
-
return { success: true, message: "No
|
|
215
|
+
const pendingRuns = runs.filter(r => r.status === "interrupted");
|
|
216
|
+
if (pendingRuns.length === 0) {
|
|
217
|
+
return { success: true, message: "No pending workflows", data: { records: [] } };
|
|
191
218
|
}
|
|
192
219
|
|
|
193
|
-
// Parallelize agent checks across
|
|
194
|
-
const checkPromises =
|
|
220
|
+
// Parallelize agent checks across interrupted workflows
|
|
221
|
+
const checkPromises = pendingRuns.map(async (r) => {
|
|
195
222
|
try {
|
|
196
|
-
const agent =
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
223
|
+
const [steps, agent] = await Promise.all([
|
|
224
|
+
store.getRunSteps(r.id).catch(() => []),
|
|
225
|
+
(async () => {
|
|
226
|
+
try {
|
|
227
|
+
return agentInstanceManager.getAgent({
|
|
228
|
+
assistant_id: r.assistantId,
|
|
229
|
+
thread_id: r.threadId,
|
|
230
|
+
tenant_id: r.tenantId,
|
|
231
|
+
});
|
|
232
|
+
} catch {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
})(),
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
if (!agent) {
|
|
239
|
+
// Agent not available — still return raw item so UI knows a human step exists
|
|
240
|
+
return [{
|
|
241
|
+
runId: r.id,
|
|
242
|
+
assistantId: r.assistantId,
|
|
243
|
+
assistantName: nameMap[r.assistantId] || r.assistantId,
|
|
244
|
+
threadId: r.threadId,
|
|
245
|
+
tenantId: r.tenantId,
|
|
246
|
+
status: r.status,
|
|
247
|
+
startedAt: r.startedAt,
|
|
248
|
+
totalEdges: r.totalEdges,
|
|
249
|
+
completedEdges: r.completedEdges,
|
|
250
|
+
steps,
|
|
251
|
+
}];
|
|
252
|
+
}
|
|
201
253
|
|
|
202
254
|
const runStatus = await agent.getRunStatus();
|
|
203
255
|
if (runStatus !== "interrupted") return [];
|
|
204
256
|
|
|
205
257
|
const state = await agent.getCurrentState();
|
|
206
|
-
|
|
207
258
|
const interrupts = state.tasks
|
|
208
259
|
?.flatMap((t: any) => t.interrupts || []) || [];
|
|
209
260
|
|
|
@@ -215,9 +266,11 @@ export async function getInboxItems(
|
|
|
215
266
|
tenantId: r.tenantId,
|
|
216
267
|
interruptId: i.id,
|
|
217
268
|
interruptValue: i.value,
|
|
269
|
+
status: r.status,
|
|
218
270
|
startedAt: r.startedAt,
|
|
219
271
|
totalEdges: r.totalEdges,
|
|
220
272
|
completedEdges: r.completedEdges,
|
|
273
|
+
steps,
|
|
221
274
|
}));
|
|
222
275
|
} catch (err) {
|
|
223
276
|
request.log.warn({ runId: r.id, error: (err as Error).message }, "Agent check skipped");
|
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";
|
|
@@ -277,17 +277,22 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
277
277
|
// Access via: request.server.loggerLattice or app.loggerLattice
|
|
278
278
|
app.decorate("loggerLattice", loggerLattice);
|
|
279
279
|
|
|
280
|
-
// Channel infrastructure: use stores from StoreLatticeManager
|
|
281
|
-
//
|
|
280
|
+
// Channel infrastructure: use stores from StoreLatticeManager.
|
|
281
|
+
// Applications (e.g. deep_research) register stores via configureStores()
|
|
282
|
+
// before calling startAsHttpEndpoint(). If no stores are registered,
|
|
283
|
+
// channel features are gracefully skipped.
|
|
282
284
|
let channelDeps: { router: MessageRouter; installationStore: ChannelInstallationStore } | undefined;
|
|
285
|
+
const adapterRegistry = new ChannelAdapterRegistry();
|
|
286
|
+
adapterRegistry.register(larkChannelAdapter);
|
|
283
287
|
try {
|
|
284
|
-
const { getStoreLattice } = await import("@axiom-lattice/core");
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
+
const { getStoreLattice: getStore } = await import("@axiom-lattice/core");
|
|
289
|
+
let bindingStore: BindingRegistry;
|
|
290
|
+
let installationStore: ChannelInstallationStore;
|
|
291
|
+
|
|
292
|
+
bindingStore = getStore("default", "channelBinding").store as BindingRegistry;
|
|
293
|
+
installationStore = getStore("default", "channelInstallation").store as ChannelInstallationStore;
|
|
288
294
|
|
|
289
|
-
|
|
290
|
-
adapterRegistry.register(larkChannelAdapter);
|
|
295
|
+
setBindingRegistry(bindingStore);
|
|
291
296
|
|
|
292
297
|
const router = new MessageRouter({
|
|
293
298
|
middlewares: [
|
|
@@ -305,7 +310,7 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
305
310
|
|
|
306
311
|
// Initialize A2A API key store
|
|
307
312
|
try {
|
|
308
|
-
const a2aKeyStore =
|
|
313
|
+
const a2aKeyStore = getStore("default", "a2aApiKey").store as import("@axiom-lattice/protocols").A2AApiKeyStore;
|
|
309
314
|
const a2a = await import("./routes/a2a");
|
|
310
315
|
a2a.setA2AKeyStore(a2aKeyStore);
|
|
311
316
|
await a2a.refreshStoreKeyMap();
|
|
@@ -313,8 +318,19 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
313
318
|
} catch {
|
|
314
319
|
// A2A key store optional — env-based keys still work
|
|
315
320
|
}
|
|
321
|
+
} catch (err) {
|
|
322
|
+
logger.warn("Channel infrastructure unavailable", {
|
|
323
|
+
error: err instanceof Error ? err.message : String(err),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Initialize MenuRegistry for custom menu items
|
|
328
|
+
try {
|
|
329
|
+
const menuStore = getStoreLattice("default", "menu").store;
|
|
330
|
+
setMenuRegistry(menuStore);
|
|
331
|
+
logger.info("Menu registry initialized");
|
|
316
332
|
} catch {
|
|
317
|
-
//
|
|
333
|
+
// Menu store optional — custom menus won't work without it
|
|
318
334
|
}
|
|
319
335
|
|
|
320
336
|
// Register all routes (channel routes only active if channelDeps is set)
|
|
@@ -326,6 +342,21 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
326
342
|
logger.info("Registered sandbox manager from env configuration");
|
|
327
343
|
}
|
|
328
344
|
|
|
345
|
+
// Connect all enabled channel installations
|
|
346
|
+
if (channelDeps && process.env.CHANNELS_ENABLED !== "false") {
|
|
347
|
+
const { connectAllChannels } = await import("@axiom-lattice/core");
|
|
348
|
+
try {
|
|
349
|
+
await connectAllChannels(
|
|
350
|
+
(channel) => adapterRegistry.get(channel),
|
|
351
|
+
{ deps: { router: channelDeps.router } },
|
|
352
|
+
);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
logger.error("Failed to start channel connections", {
|
|
355
|
+
error: err instanceof Error ? err.message : String(err),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
329
360
|
const target_port = config?.port || Number(process.env.PORT) || 4001;
|
|
330
361
|
|
|
331
362
|
await app.listen({ port: target_port, host: "0.0.0.0" });
|
|
@@ -348,15 +379,14 @@ const start = async (config?: LatticeGatewayConfig) => {
|
|
|
348
379
|
}
|
|
349
380
|
}
|
|
350
381
|
|
|
351
|
-
// Restore agent instances with pending messages
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
382
|
+
// Restore agent instances with pending messages (fire-and-forget to avoid blocking startup)
|
|
383
|
+
agentInstanceManager.restore()
|
|
384
|
+
.then((stats) => {
|
|
385
|
+
logger.info(`Agent recovery complete: ${stats.restored} threads restored, ${stats.errors} errors`);
|
|
386
|
+
})
|
|
387
|
+
.catch((error) => {
|
|
388
|
+
logger.error("Agent recovery failed", { error });
|
|
389
|
+
});
|
|
360
390
|
} catch (err) {
|
|
361
391
|
logger.error("Server start failed", { error: err });
|
|
362
392
|
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
|
-
}
|