plugin-agent-orchestrator 1.0.6 → 1.0.13
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/client/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/server/collections/orchestrator-config.js +10 -0
- package/dist/server/collections/orchestrator-logs.js +19 -2
- package/dist/server/migrations/20260427000000-add-tracing-detail-fields.d.ts +7 -0
- package/dist/server/migrations/20260427000000-add-tracing-detail-fields.js +62 -0
- package/dist/server/migrations/20260429000000-add-llm-fields.d.ts +7 -0
- package/dist/server/migrations/20260429000000-add-llm-fields.js +60 -0
- package/dist/server/resources/tracing.js +8 -3
- package/dist/server/tools/delegate-task.js +306 -95
- package/package.json +1 -1
- package/src/client/RulesTab.tsx +134 -8
- package/src/client/TracingTab.tsx +171 -21
- package/src/server/collections/orchestrator-config.ts +10 -0
- package/src/server/collections/orchestrator-logs.ts +19 -2
- package/src/server/migrations/20260427000000-add-tracing-detail-fields.ts +41 -0
- package/src/server/migrations/20260429000000-add-llm-fields.ts +37 -0
- package/src/server/resources/tracing.ts +6 -2
- package/src/server/tools/delegate-task.ts +363 -101
|
@@ -13,6 +13,128 @@ import type { ToolsEntry } from '@nocobase/ai';
|
|
|
13
13
|
*/
|
|
14
14
|
const ORCHESTRATOR_DEPTH_KEY = '__orchestratorDepth';
|
|
15
15
|
|
|
16
|
+
type TraceEvent = {
|
|
17
|
+
type: string;
|
|
18
|
+
at: string;
|
|
19
|
+
title: string;
|
|
20
|
+
content?: string;
|
|
21
|
+
toolName?: string;
|
|
22
|
+
args?: any;
|
|
23
|
+
status?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type AgentExecutionResult = {
|
|
27
|
+
content: string;
|
|
28
|
+
messages: any[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function sanitizeToolPart(value: string) {
|
|
32
|
+
return (value || '').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildDelegateToolName(leaderUsername: string, subAgentUsername: string) {
|
|
36
|
+
return `delegate_${sanitizeToolPart(leaderUsername)}_to_${sanitizeToolPart(subAgentUsername)}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isDelegateToolName(toolName: string) {
|
|
40
|
+
return toolName.startsWith('delegate_to_') || (toolName.startsWith('delegate_') && toolName.includes('_to_'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createDelegateToolOptions(
|
|
44
|
+
plugin: any,
|
|
45
|
+
options: {
|
|
46
|
+
leaderUsername: string;
|
|
47
|
+
subAgentUsername: string;
|
|
48
|
+
subAgentEmployee: any;
|
|
49
|
+
maxDepth?: number;
|
|
50
|
+
timeout?: number;
|
|
51
|
+
toolName: string;
|
|
52
|
+
legacyAlias?: boolean;
|
|
53
|
+
llmService?: string;
|
|
54
|
+
model?: string;
|
|
55
|
+
},
|
|
56
|
+
) {
|
|
57
|
+
const { leaderUsername, subAgentUsername, subAgentEmployee, maxDepth, timeout, toolName, legacyAlias, llmService, model } = options;
|
|
58
|
+
const toolDescription = [
|
|
59
|
+
`Delegate a task from "${leaderUsername}" to the AI Employee "${subAgentEmployee.nickname || subAgentUsername}".`,
|
|
60
|
+
legacyAlias ? 'This is a backward-compatible alias for existing skill assignments.' : '',
|
|
61
|
+
subAgentEmployee.about ? `Specialist profile: ${subAgentEmployee.about.substring(0, 200)}` : '',
|
|
62
|
+
'The sub-agent will execute the task independently and return its final answer.',
|
|
63
|
+
].filter(Boolean).join(' ');
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
scope: 'CUSTOM',
|
|
67
|
+
execution: 'backend',
|
|
68
|
+
defaultPermission: 'ALLOW',
|
|
69
|
+
silence: false,
|
|
70
|
+
introduction: {
|
|
71
|
+
title: `[${leaderUsername}] ${subAgentEmployee.nickname || subAgentUsername}${legacyAlias ? ' (legacy)' : ''}`,
|
|
72
|
+
about: toolDescription,
|
|
73
|
+
},
|
|
74
|
+
definition: {
|
|
75
|
+
name: toolName,
|
|
76
|
+
description: toolDescription,
|
|
77
|
+
schema: z.object({
|
|
78
|
+
task: z.string().describe('The detailed task description for the sub-agent to execute.'),
|
|
79
|
+
context: z.string().optional().describe('Optional additional context to help the sub-agent understand the task better.'),
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
invoke: async (ctx: Context, args: { task: string; context?: string }, id: string) => {
|
|
83
|
+
const callingEmployee = resolveCallingEmployee(ctx);
|
|
84
|
+
if (callingEmployee && callingEmployee !== leaderUsername) {
|
|
85
|
+
await logDelegation(ctx, plugin, {
|
|
86
|
+
leaderUsername,
|
|
87
|
+
subAgentUsername,
|
|
88
|
+
toolName,
|
|
89
|
+
task: args.task,
|
|
90
|
+
context: args.context,
|
|
91
|
+
result: '',
|
|
92
|
+
status: 'error',
|
|
93
|
+
depth: (ctx as any)[ORCHESTRATOR_DEPTH_KEY] ?? 0,
|
|
94
|
+
durationMs: 0,
|
|
95
|
+
error: `Employee "${callingEmployee}" is not authorized to use this delegation rule.`,
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
status: 'error' as const,
|
|
99
|
+
content: `Employee "${callingEmployee}" is not authorized to delegate to "${subAgentUsername}". Configure an orchestration rule first.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return invokeDelegateTask(ctx, plugin, {
|
|
104
|
+
leaderUsername,
|
|
105
|
+
subAgentUsername,
|
|
106
|
+
subAgentEmployee,
|
|
107
|
+
task: args.task,
|
|
108
|
+
context: args.context,
|
|
109
|
+
maxDepth: maxDepth ?? 1,
|
|
110
|
+
timeout: timeout ?? 120000,
|
|
111
|
+
toolCallId: id,
|
|
112
|
+
toolName,
|
|
113
|
+
llmService,
|
|
114
|
+
model,
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resolveCallingEmployee(ctx: Context) {
|
|
121
|
+
const raw =
|
|
122
|
+
(ctx as any)._currentAIEmployee ||
|
|
123
|
+
(ctx as any).state?.currentAIEmployee ||
|
|
124
|
+
(ctx as any).runtime?.context?.currentAIEmployee;
|
|
125
|
+
if (!raw) return null;
|
|
126
|
+
return typeof raw === 'string' ? raw : raw.username;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function truncateText(value: any, maxLen: number) {
|
|
130
|
+
const text = typeof value === 'string' ? value : value == null ? '' : JSON.stringify(value);
|
|
131
|
+
return text.length > maxLen ? `${text.slice(0, maxLen)}\n...[truncated]` : text;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function nowIso() {
|
|
135
|
+
return new Date().toISOString();
|
|
136
|
+
}
|
|
137
|
+
|
|
16
138
|
/**
|
|
17
139
|
* Creates one dynamic tool per configured sub-agent for a given leader.
|
|
18
140
|
* Uses Strategy B (Per-SubAgent Tool): each sub-agent becomes a separate tool
|
|
@@ -38,86 +160,67 @@ export function createDelegateToolsProvider(plugin: any) {
|
|
|
38
160
|
});
|
|
39
161
|
if (!configs?.length) return;
|
|
40
162
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const leadersByTool = new Map<string, Set<string>>();
|
|
163
|
+
const employeeCache = new Map<string, any>();
|
|
164
|
+
const tools = [];
|
|
165
|
+
const configsBySubAgent = new Map<string, any[]>();
|
|
45
166
|
for (const config of configs) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
leadersByTool.get(toolName)!.add(config.leaderUsername);
|
|
167
|
+
const items = configsBySubAgent.get(config.subAgentUsername) || [];
|
|
168
|
+
items.push(config);
|
|
169
|
+
configsBySubAgent.set(config.subAgentUsername, items);
|
|
51
170
|
}
|
|
52
171
|
|
|
53
|
-
// De-duplicate: register one tool per sub-agent (not per config row)
|
|
54
|
-
const seenSubAgents = new Set<string>();
|
|
55
|
-
|
|
56
172
|
for (const config of configs) {
|
|
57
173
|
const { leaderUsername, subAgentUsername, maxDepth, timeout } = config;
|
|
58
174
|
|
|
59
|
-
// Skip if we already registered this sub-agent's tool
|
|
60
|
-
if (seenSubAgents.has(subAgentUsername)) continue;
|
|
61
|
-
seenSubAgents.add(subAgentUsername);
|
|
62
|
-
|
|
63
175
|
// Fetch the sub-agent employee model for its description and LLM config
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
176
|
+
let subAgentEmployee = employeeCache.get(subAgentUsername);
|
|
177
|
+
if (!subAgentEmployee) {
|
|
178
|
+
subAgentEmployee = await plugin.db.getRepository('aiEmployees').findOne({
|
|
179
|
+
filter: { username: subAgentUsername },
|
|
180
|
+
});
|
|
181
|
+
if (subAgentEmployee) {
|
|
182
|
+
employeeCache.set(subAgentUsername, subAgentEmployee);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
67
185
|
if (!subAgentEmployee) continue;
|
|
68
186
|
|
|
69
|
-
const toolName =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
execution: 'backend',
|
|
82
|
-
defaultPermission: 'ALLOW',
|
|
83
|
-
silence: false,
|
|
84
|
-
introduction: {
|
|
85
|
-
title: `[Sub-Agent] ${subAgentEmployee.nickname || subAgentUsername}`,
|
|
86
|
-
about: toolDescription,
|
|
87
|
-
},
|
|
88
|
-
definition: {
|
|
89
|
-
name: toolName,
|
|
90
|
-
description: toolDescription,
|
|
91
|
-
schema: z.object({
|
|
92
|
-
task: z.string().describe('The detailed task description for the sub-agent to execute.'),
|
|
93
|
-
context: z.string().optional().describe('Optional additional context to help the sub-agent understand the task better.'),
|
|
94
|
-
}),
|
|
95
|
-
},
|
|
96
|
-
invoke: async (ctx: Context, args: { task: string; context?: string }, id: string) => {
|
|
97
|
-
// --- P2 FIX: Per-leader scoping at invoke time ---
|
|
98
|
-
// Core ToolsOptions doesn't support leaderUsername field, so
|
|
99
|
-
// we enforce scoping here by checking the calling employee.
|
|
100
|
-
const callingEmployee = (ctx as any)._currentAIEmployee?.username
|
|
101
|
-
|| (ctx as any).state?.currentAIEmployee;
|
|
102
|
-
if (callingEmployee && !allowedLeaders.has(callingEmployee)) {
|
|
103
|
-
return {
|
|
104
|
-
status: 'error' as const,
|
|
105
|
-
content: `Employee "${callingEmployee}" is not authorized to delegate to "${subAgentUsername}". Configure an orchestration rule first.`,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
187
|
+
const toolName = buildDelegateToolName(leaderUsername, subAgentUsername);
|
|
188
|
+
tools.push(createDelegateToolOptions(plugin, {
|
|
189
|
+
leaderUsername,
|
|
190
|
+
subAgentUsername,
|
|
191
|
+
subAgentEmployee,
|
|
192
|
+
maxDepth,
|
|
193
|
+
timeout,
|
|
194
|
+
toolName,
|
|
195
|
+
llmService: config.llmService,
|
|
196
|
+
model: config.model,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
108
199
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
200
|
+
// Compatibility for existing single-parent setups that already assigned
|
|
201
|
+
// delegate_to_<sub> to the parent employee's skills.
|
|
202
|
+
for (const [subAgentUsername, items] of configsBySubAgent.entries()) {
|
|
203
|
+
if (items.length !== 1) continue;
|
|
204
|
+
const config = items[0];
|
|
205
|
+
const subAgentEmployee = employeeCache.get(subAgentUsername);
|
|
206
|
+
if (!subAgentEmployee) continue;
|
|
207
|
+
const legacyToolName = `delegate_to_${sanitizeToolPart(subAgentUsername)}`;
|
|
208
|
+
if (tools.some((tool: any) => tool.definition.name === legacyToolName)) continue;
|
|
209
|
+
tools.push(createDelegateToolOptions(plugin, {
|
|
210
|
+
leaderUsername: config.leaderUsername,
|
|
211
|
+
subAgentUsername,
|
|
212
|
+
subAgentEmployee,
|
|
213
|
+
maxDepth: config.maxDepth,
|
|
214
|
+
timeout: config.timeout,
|
|
215
|
+
toolName: legacyToolName,
|
|
216
|
+
legacyAlias: true,
|
|
217
|
+
llmService: config.llmService,
|
|
218
|
+
model: config.model,
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (tools.length) {
|
|
223
|
+
register.registerTools(tools);
|
|
121
224
|
}
|
|
122
225
|
} catch (e) {
|
|
123
226
|
plugin.app.log.error('[AgentOrchestrator] Failed to register delegate tools', e);
|
|
@@ -153,13 +256,28 @@ async function invokeDelegateTask(
|
|
|
153
256
|
maxDepth: number;
|
|
154
257
|
timeout: number;
|
|
155
258
|
toolCallId: string;
|
|
259
|
+
toolName: string;
|
|
260
|
+
llmService?: string;
|
|
261
|
+
model?: string;
|
|
156
262
|
},
|
|
157
263
|
) {
|
|
158
|
-
const { leaderUsername, subAgentUsername, subAgentEmployee, task, context, maxDepth, timeout, toolCallId } = options;
|
|
264
|
+
const { leaderUsername, subAgentUsername, subAgentEmployee, task, context, maxDepth, timeout, toolCallId, toolName, llmService, model } = options;
|
|
159
265
|
|
|
160
266
|
// --- P1: Depth enforcement ---
|
|
161
267
|
const currentDepth: number = (ctx as any)[ORCHESTRATOR_DEPTH_KEY] ?? 0;
|
|
162
268
|
if (currentDepth >= maxDepth) {
|
|
269
|
+
await logDelegation(ctx, plugin, {
|
|
270
|
+
leaderUsername,
|
|
271
|
+
subAgentUsername,
|
|
272
|
+
toolName,
|
|
273
|
+
task,
|
|
274
|
+
context,
|
|
275
|
+
result: '',
|
|
276
|
+
status: 'error',
|
|
277
|
+
depth: currentDepth,
|
|
278
|
+
durationMs: 0,
|
|
279
|
+
error: `Delegation depth limit reached (${currentDepth}/${maxDepth}).`,
|
|
280
|
+
});
|
|
163
281
|
return {
|
|
164
282
|
status: 'error' as const,
|
|
165
283
|
content: `Delegation depth limit reached (${currentDepth}/${maxDepth}). Sub-agent "${subAgentUsername}" cannot delegate further.`,
|
|
@@ -167,6 +285,26 @@ async function invokeDelegateTask(
|
|
|
167
285
|
}
|
|
168
286
|
|
|
169
287
|
const startTime = Date.now();
|
|
288
|
+
const trace: TraceEvent[] = [
|
|
289
|
+
{
|
|
290
|
+
type: 'start',
|
|
291
|
+
at: nowIso(),
|
|
292
|
+
title: `Delegation started: ${leaderUsername} -> ${subAgentUsername}`,
|
|
293
|
+
content: task,
|
|
294
|
+
},
|
|
295
|
+
];
|
|
296
|
+
const logRecord = await logDelegation(ctx, plugin, {
|
|
297
|
+
leaderUsername,
|
|
298
|
+
subAgentUsername,
|
|
299
|
+
toolName,
|
|
300
|
+
task,
|
|
301
|
+
context,
|
|
302
|
+
result: '',
|
|
303
|
+
status: 'running',
|
|
304
|
+
depth: currentDepth,
|
|
305
|
+
durationMs: 0,
|
|
306
|
+
trace,
|
|
307
|
+
});
|
|
170
308
|
|
|
171
309
|
try {
|
|
172
310
|
const aiPlugin = ctx.app.pm.get('ai') as PluginAIServer;
|
|
@@ -175,9 +313,27 @@ async function invokeDelegateTask(
|
|
|
175
313
|
}
|
|
176
314
|
|
|
177
315
|
// --- Step 1: Resolve LLM model from sub-agent's employee config ---
|
|
178
|
-
|
|
316
|
+
let modelSettings = subAgentEmployee.modelSettings;
|
|
317
|
+
|
|
318
|
+
// Override with orchestrator config if provided
|
|
319
|
+
if (llmService && model) {
|
|
320
|
+
modelSettings = { llmService, model };
|
|
321
|
+
}
|
|
322
|
+
|
|
179
323
|
if (!modelSettings?.llmService || !modelSettings?.model) {
|
|
180
|
-
|
|
324
|
+
// Fallback to leader's LLM model if sub-agent doesn't have one
|
|
325
|
+
const leaderEmployee = await plugin.db.getRepository('aiEmployees').findOne({
|
|
326
|
+
filter: { username: leaderUsername },
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// The leader's model might be empty in the DB if it relies on the dynamic system default.
|
|
330
|
+
// In that case, we extract the dynamic `model` passed from the frontend request.
|
|
331
|
+
const dynamicModel = ctx.action?.params?.values?.model;
|
|
332
|
+
modelSettings = leaderEmployee?.modelSettings || dynamicModel;
|
|
333
|
+
|
|
334
|
+
if (!modelSettings?.llmService || !modelSettings?.model) {
|
|
335
|
+
throw new Error(`Sub-agent "${subAgentUsername}" has no LLM model configured (and leader fallback failed). Please configure a model in the Orchestrator Config or AI Employee settings.`);
|
|
336
|
+
}
|
|
181
337
|
}
|
|
182
338
|
|
|
183
339
|
const { provider } = await aiPlugin.aiManager.getLLMService({
|
|
@@ -207,7 +363,7 @@ async function invokeDelegateTask(
|
|
|
207
363
|
// Only include tools that the sub-agent employee is configured to use.
|
|
208
364
|
// Also skip our own delegate_to_* tools to prevent circular delegation
|
|
209
365
|
// (belt-and-suspenders with the depth check above).
|
|
210
|
-
if (!employeeSkills.includes(toolName) || toolName
|
|
366
|
+
if (!employeeSkills.includes(toolName) || isDelegateToolName(toolName)) {
|
|
211
367
|
continue;
|
|
212
368
|
}
|
|
213
369
|
|
|
@@ -221,11 +377,39 @@ async function invokeDelegateTask(
|
|
|
221
377
|
const invokeCtx = Object.create(ctx);
|
|
222
378
|
(invokeCtx as any)[ORCHESTRATOR_DEPTH_KEY] = currentDepth + 1;
|
|
223
379
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
380
|
+
trace.push({
|
|
381
|
+
type: 'tool_call',
|
|
382
|
+
at: nowIso(),
|
|
383
|
+
title: `Calling tool: ${toolEntry.definition.name}`,
|
|
384
|
+
toolName: toolEntry.definition.name,
|
|
385
|
+
args: toolArgs,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const res = await toolEntry.invoke(invokeCtx, toolArgs, `orch-${toolCallId}`);
|
|
390
|
+
trace.push({
|
|
391
|
+
type: 'tool_result',
|
|
392
|
+
at: nowIso(),
|
|
393
|
+
title: `Tool finished: ${toolEntry.definition.name}`,
|
|
394
|
+
toolName: toolEntry.definition.name,
|
|
395
|
+
status: res?.status || 'success',
|
|
396
|
+
content: truncateText(res?.content ?? res, 2000),
|
|
397
|
+
});
|
|
398
|
+
if (res?.status === 'error') {
|
|
399
|
+
throw new Error(`Tool <${toolEntry.definition.name}> failed: ${res.content}`);
|
|
400
|
+
}
|
|
401
|
+
return typeof res?.content === 'string' ? res.content : JSON.stringify(res);
|
|
402
|
+
} catch (e: any) {
|
|
403
|
+
trace.push({
|
|
404
|
+
type: 'tool_error',
|
|
405
|
+
at: nowIso(),
|
|
406
|
+
title: `Tool failed: ${toolEntry.definition.name}`,
|
|
407
|
+
toolName: toolEntry.definition.name,
|
|
408
|
+
status: 'error',
|
|
409
|
+
content: e.message,
|
|
410
|
+
});
|
|
411
|
+
throw e;
|
|
227
412
|
}
|
|
228
|
-
return typeof res?.content === 'string' ? res.content : JSON.stringify(res);
|
|
229
413
|
},
|
|
230
414
|
}),
|
|
231
415
|
);
|
|
@@ -255,19 +439,31 @@ async function invokeDelegateTask(
|
|
|
255
439
|
const result = await Promise.race([
|
|
256
440
|
invokePromise,
|
|
257
441
|
createTimeout(timeout, subAgentUsername, abortController),
|
|
258
|
-
]);
|
|
442
|
+
]) as AgentExecutionResult;
|
|
259
443
|
|
|
260
|
-
const content =
|
|
444
|
+
const content = result.content || 'Sub-agent completed the task but produced no output.';
|
|
445
|
+
trace.push({
|
|
446
|
+
type: 'finish',
|
|
447
|
+
at: nowIso(),
|
|
448
|
+
title: `Delegation finished: ${subAgentUsername}`,
|
|
449
|
+
status: 'success',
|
|
450
|
+
content: truncateText(content, 2000),
|
|
451
|
+
});
|
|
261
452
|
|
|
262
453
|
// Log successful execution for tracing
|
|
263
454
|
await logDelegation(ctx, plugin, {
|
|
455
|
+
id: logRecord?.id,
|
|
264
456
|
leaderUsername,
|
|
265
457
|
subAgentUsername,
|
|
458
|
+
toolName,
|
|
266
459
|
task,
|
|
460
|
+
context,
|
|
267
461
|
result: content,
|
|
268
462
|
status: 'success',
|
|
269
463
|
depth: currentDepth,
|
|
270
464
|
durationMs: Date.now() - startTime,
|
|
465
|
+
trace,
|
|
466
|
+
messages: result.messages,
|
|
271
467
|
});
|
|
272
468
|
|
|
273
469
|
return {
|
|
@@ -279,15 +475,30 @@ async function invokeDelegateTask(
|
|
|
279
475
|
|
|
280
476
|
// Log failed execution for tracing
|
|
281
477
|
await logDelegation(ctx, plugin, {
|
|
478
|
+
id: logRecord?.id,
|
|
282
479
|
leaderUsername,
|
|
283
480
|
subAgentUsername,
|
|
481
|
+
toolName,
|
|
284
482
|
task,
|
|
483
|
+
context,
|
|
285
484
|
result: '',
|
|
286
485
|
status: 'error',
|
|
287
486
|
depth: currentDepth,
|
|
288
487
|
durationMs: Date.now() - startTime,
|
|
289
488
|
error: e.message,
|
|
290
|
-
|
|
489
|
+
trace: [
|
|
490
|
+
...trace,
|
|
491
|
+
{
|
|
492
|
+
type: 'error',
|
|
493
|
+
at: nowIso(),
|
|
494
|
+
title: `Delegation failed: ${subAgentUsername}`,
|
|
495
|
+
status: 'error',
|
|
496
|
+
content: e.message,
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
}).catch((logErr) => {
|
|
500
|
+
plugin.app.log.warn('[AgentOrchestrator] Failed to save error log for delegation', logErr);
|
|
501
|
+
});
|
|
291
502
|
|
|
292
503
|
return {
|
|
293
504
|
status: 'error' as const,
|
|
@@ -303,34 +514,68 @@ async function logDelegation(
|
|
|
303
514
|
ctx: Context,
|
|
304
515
|
plugin: any,
|
|
305
516
|
data: {
|
|
517
|
+
id?: number | string;
|
|
306
518
|
leaderUsername: string;
|
|
307
519
|
subAgentUsername: string;
|
|
520
|
+
toolName: string;
|
|
308
521
|
task: string;
|
|
522
|
+
context?: string;
|
|
309
523
|
result: string;
|
|
310
524
|
status: string;
|
|
311
525
|
depth: number;
|
|
312
526
|
durationMs: number;
|
|
313
527
|
error?: string;
|
|
528
|
+
trace?: TraceEvent[];
|
|
529
|
+
messages?: any[];
|
|
314
530
|
},
|
|
315
531
|
) {
|
|
316
532
|
try {
|
|
317
533
|
const logsRepo = plugin.db.getRepository('orchestratorLogs');
|
|
318
|
-
if (!logsRepo)
|
|
534
|
+
if (!logsRepo) {
|
|
535
|
+
plugin.app.log.warn('[AgentOrchestrator] orchestratorLogs repository not found — skipping log');
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Safely resolve userId — ctx may be stale after long-running agent execution
|
|
540
|
+
let userId: number | undefined;
|
|
541
|
+
try {
|
|
542
|
+
userId = ctx.auth?.user?.id || ctx.state?.currentUser?.id;
|
|
543
|
+
} catch {
|
|
544
|
+
// ctx lifecycle ended — proceed without userId
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const values = {
|
|
548
|
+
leaderUsername: data.leaderUsername,
|
|
549
|
+
subAgentUsername: data.subAgentUsername,
|
|
550
|
+
toolName: data.toolName,
|
|
551
|
+
task: truncateText(data.task, 10000),
|
|
552
|
+
context: truncateText(data.context || '', 10000),
|
|
553
|
+
result: truncateText(data.result || '', 50000),
|
|
554
|
+
status: data.status,
|
|
555
|
+
depth: data.depth,
|
|
556
|
+
durationMs: data.durationMs,
|
|
557
|
+
error: truncateText(data.error || '', 10000),
|
|
558
|
+
trace: data.trace || [],
|
|
559
|
+
messages: data.messages || [],
|
|
560
|
+
userId,
|
|
561
|
+
updatedAt: new Date(),
|
|
562
|
+
};
|
|
319
563
|
|
|
320
|
-
|
|
564
|
+
if (data.id) {
|
|
565
|
+
await logsRepo.update({
|
|
566
|
+
filterByTk: data.id,
|
|
567
|
+
values,
|
|
568
|
+
});
|
|
569
|
+
return { id: data.id };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const record = await logsRepo.create({
|
|
321
573
|
values: {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
toolName: `delegate_to_${data.subAgentUsername}`,
|
|
325
|
-
task: (data.task || '').substring(0, 2000),
|
|
326
|
-
result: (data.result || '').substring(0, 5000),
|
|
327
|
-
status: data.status,
|
|
328
|
-
depth: data.depth,
|
|
329
|
-
durationMs: data.durationMs,
|
|
330
|
-
error: (data.error || '').substring(0, 2000),
|
|
331
|
-
userId: ctx.auth?.user?.id || ctx.state?.currentUser?.id,
|
|
574
|
+
...values,
|
|
575
|
+
createdAt: new Date(),
|
|
332
576
|
},
|
|
333
577
|
});
|
|
578
|
+
return record?.toJSON?.() || record;
|
|
334
579
|
} catch (e) {
|
|
335
580
|
plugin.app.log.warn('[AgentOrchestrator] Failed to log delegation event', e);
|
|
336
581
|
}
|
|
@@ -346,7 +591,7 @@ async function executeAgent(
|
|
|
346
591
|
systemPrompt: string,
|
|
347
592
|
task: string,
|
|
348
593
|
signal?: AbortSignal,
|
|
349
|
-
): Promise<
|
|
594
|
+
): Promise<AgentExecutionResult> {
|
|
350
595
|
const config: any = { recursionLimit: 50 };
|
|
351
596
|
if (signal) {
|
|
352
597
|
config.signal = signal;
|
|
@@ -366,20 +611,37 @@ async function executeAgent(
|
|
|
366
611
|
const lastAIMessage = [...messages].reverse().find(m => m.getType() === 'ai');
|
|
367
612
|
|
|
368
613
|
if (!lastAIMessage || !lastAIMessage.content) {
|
|
369
|
-
return '';
|
|
614
|
+
return { content: '', messages: serializeMessages(messages) };
|
|
370
615
|
}
|
|
371
616
|
|
|
617
|
+
let content = '';
|
|
372
618
|
if (typeof lastAIMessage.content === 'string') {
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (Array.isArray(lastAIMessage.content)) {
|
|
377
|
-
return lastAIMessage.content
|
|
619
|
+
content = lastAIMessage.content;
|
|
620
|
+
} else if (Array.isArray(lastAIMessage.content)) {
|
|
621
|
+
content = lastAIMessage.content
|
|
378
622
|
.map((c: any) => c.text || JSON.stringify(c))
|
|
379
623
|
.join('\n');
|
|
624
|
+
} else {
|
|
625
|
+
content = String(lastAIMessage.content);
|
|
380
626
|
}
|
|
381
627
|
|
|
382
|
-
return
|
|
628
|
+
return { content, messages: serializeMessages(messages) };
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function serializeMessages(messages: any[]) {
|
|
632
|
+
return (messages || []).map((message, index) => {
|
|
633
|
+
const type = typeof message.getType === 'function' ? message.getType() : message.type;
|
|
634
|
+
return {
|
|
635
|
+
index,
|
|
636
|
+
type,
|
|
637
|
+
name: message.name,
|
|
638
|
+
content: truncateText(message.content, 10000),
|
|
639
|
+
toolCalls: message.tool_calls || message.toolCalls || [],
|
|
640
|
+
toolCallId: message.tool_call_id,
|
|
641
|
+
additionalKwargs: message.additional_kwargs,
|
|
642
|
+
responseMetadata: message.response_metadata,
|
|
643
|
+
};
|
|
644
|
+
});
|
|
383
645
|
}
|
|
384
646
|
|
|
385
647
|
/**
|