plugin-agent-orchestrator 1.0.28 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/client/index.js +1 -1
- package/dist/client-v2/{214.723affb37c13bf7a.js → 214.79650a549273f163.js} +1 -1
- package/dist/client-v2/264.718a107e43fc163c.js +10 -0
- package/dist/client-v2/373.f5d5292e53c4e832.js +10 -0
- package/dist/client-v2/{41.1805b2edfaa4afe2.js → 41.ba6e080cc0488143.js} +1 -1
- package/dist/client-v2/418.29e713f79131eece.js +10 -0
- package/dist/client-v2/619.bd3c5698b40705c3.js +10 -0
- package/dist/client-v2/677.a991ce0250ff5c77.js +10 -0
- package/dist/client-v2/{70.a15d7fcec7c41768.js → 70.bda9518881c05360.js} +1 -1
- package/dist/client-v2/925.f5370de8f6632d65.js +10 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +7 -10
- package/dist/locale/en-US.json +94 -25
- package/dist/locale/vi-VN.json +94 -25
- package/dist/locale/zh-CN.json +94 -25
- package/dist/server/collections/agent-execution-spans.js +37 -0
- package/dist/server/collections/agent-harness-profiles.js +2 -2
- package/dist/server/collections/agent-memory-contexts.js +125 -0
- package/dist/server/collections/orchestrator-logs.js +2 -2
- package/dist/server/migrations/20260425000000-add-interaction-schema.js +3 -1
- package/dist/server/migrations/20260427000000-change-packages-to-text.js +3 -1
- package/dist/server/migrations/20260427000001-change-other-json-to-text.js +6 -2
- package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +21 -19
- package/dist/server/migrations/20260621000000-native-policy-profile-defaults.js +193 -0
- package/dist/server/plugin.js +128 -74
- package/dist/server/resources/agent-monitor.js +454 -0
- package/dist/server/services/AgentHarness.js +24 -499
- package/dist/server/services/AgentMemoryContextService.js +216 -0
- package/dist/server/services/ExecutionSpanService.js +2 -2
- package/dist/server/services/NativeSubAgentObserver.js +413 -0
- package/dist/server/skill-hub/plugin.js +81 -5
- package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
- package/dist/server/tools/delegate-task.js +11 -589
- package/dist/server/utils/skill-settings.js +18 -1
- package/package.json +47 -49
- package/src/client/AIEmployeesContext.tsx +5 -18
- package/src/client/AgentRunsTab.tsx +2 -771
- package/src/client/HarnessProfilesTab.tsx +2 -257
- package/src/client/OrchestratorSettings.tsx +97 -106
- package/src/client/RulesTab.tsx +2 -788
- package/src/client/plugin.tsx +0 -2
- package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
- package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
- package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
- package/src/client/skill-hub/components/SkillManager.tsx +194 -181
- package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
- package/src/client/skill-hub/locale.ts +16 -16
- package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
- package/src/client/skill-hub/utils/jsonFields.ts +7 -3
- package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
- package/src/client-v2/components/AgentRunsTab.tsx +182 -455
- package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
- package/src/client-v2/components/RulesTab.tsx +2 -782
- package/src/client-v2/components/TracingTab.tsx +1 -1
- package/src/client-v2/hooks/useApiRequest.ts +8 -1
- package/src/client-v2/pages/RulesPage.tsx +2 -2
- package/src/client-v2/plugin.tsx +3 -3
- package/src/locale/en-US.json +94 -25
- package/src/locale/vi-VN.json +94 -25
- package/src/locale/zh-CN.json +94 -25
- package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
- package/src/server/__tests__/skill-settings.test.ts +6 -6
- package/src/server/__tests__/smoke.test.ts +1 -0
- package/src/server/collections/agent-execution-spans.ts +37 -0
- package/src/server/collections/agent-harness-profiles.ts +59 -59
- package/src/server/collections/agent-loop-events.ts +71 -71
- package/src/server/collections/agent-loop-steps.ts +144 -144
- package/src/server/collections/agent-memory-contexts.ts +95 -0
- package/src/server/collections/orchestrator-logs.ts +4 -4
- package/src/server/collections/skill-definitions.ts +111 -111
- package/src/server/collections/skill-executions.ts +106 -106
- package/src/server/collections/skill-loop-configs.ts +65 -65
- package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
- package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
- package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
- package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
- package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
- package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
- package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
- package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
- package/src/server/plugin.ts +151 -94
- package/src/server/resources/agent-monitor.ts +482 -0
- package/src/server/services/AgentHarness.ts +38 -623
- package/src/server/services/AgentMemoryContextService.ts +256 -0
- package/src/server/services/AgentPlanValidator.ts +73 -73
- package/src/server/services/ExecutionSpanService.ts +6 -2
- package/src/server/services/FileManager.ts +144 -144
- package/src/server/services/NativeSubAgentObserver.ts +507 -0
- package/src/server/services/SkillManager.ts +583 -583
- package/src/server/services/SkillRepositoryService.ts +5 -7
- package/src/server/services/TokenTracker.ts +3 -3
- package/src/server/services/WorkerEnvManager.ts +1 -2
- package/src/server/skill-hub/actions/git-import.ts +5 -7
- package/src/server/skill-hub/plugin.ts +89 -6
- package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
- package/src/server/skill-hub/utils/json-fields.ts +1 -1
- package/src/server/tools/delegate-task.ts +13 -847
- package/src/server/utils/skill-settings.ts +24 -6
- package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
- package/dist/client-v2/418.5ae055abf141820e.js +0 -10
- package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
- package/dist/client-v2/892.72db4161511c8a16.js +0 -10
- package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
- package/src/client/tools/PlanApprovalCard.tsx +0 -176
- package/src/client/tools/registerOrchestratorCards.ts +0 -17
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import type { Plugin } from '@nocobase/server';
|
|
2
|
+
import { asObject, currentUserId, isAdminUser, toPlain, trimText } from '../utils/ctx-utils';
|
|
3
|
+
import { makeNativeRunId, NATIVE_SOURCE } from '../services/NativeSubAgentObserver';
|
|
4
|
+
|
|
5
|
+
function normalizeString(value: unknown) {
|
|
6
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function readValue(record: unknown, key: string) {
|
|
10
|
+
const model = record as { get?: (name: string) => unknown; [key: string]: unknown };
|
|
11
|
+
return typeof model?.get === 'function' ? model.get(key) : model?.[key];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isAdmin(ctx: unknown) {
|
|
15
|
+
const context = ctx as { state?: { currentRoles?: string[] } };
|
|
16
|
+
if (isAdminUser(ctx)) return true;
|
|
17
|
+
const roles = context?.state?.currentRoles;
|
|
18
|
+
return Array.isArray(roles) && roles.some((role) => role === 'root' || role === 'admin');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function statusFromNative(status: unknown) {
|
|
22
|
+
if (status === 'completed' || status === 'done' || status === 'success') return 'success';
|
|
23
|
+
if (status === 'error' || status === 'failed') return 'error';
|
|
24
|
+
return 'running';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatDuration(ms: unknown) {
|
|
28
|
+
const value = Number(ms);
|
|
29
|
+
if (!Number.isFinite(value) || value <= 0) return null;
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatSpanListRow(raw: unknown) {
|
|
34
|
+
const row = toPlain(raw);
|
|
35
|
+
const input = asObject(row?.input);
|
|
36
|
+
const metadata = asObject(row?.metadata);
|
|
37
|
+
return {
|
|
38
|
+
id: row?.id,
|
|
39
|
+
rootRunId: row?.rootRunId,
|
|
40
|
+
source: row?.source || metadata.source,
|
|
41
|
+
parentSessionId: row?.parentSessionId || metadata.parentSessionId,
|
|
42
|
+
subSessionId: row?.subSessionId || metadata.subSessionId,
|
|
43
|
+
toolCallId: row?.toolCallId || metadata.toolCallId,
|
|
44
|
+
leaderUsername: row?.leaderUsername,
|
|
45
|
+
employeeUsername: row?.employeeUsername,
|
|
46
|
+
subAgentUsername: row?.employeeUsername,
|
|
47
|
+
toolName: row?.toolName || 'dispatch-sub-agent-task',
|
|
48
|
+
task: input.question || input.task || row?.title || '',
|
|
49
|
+
status: row?.status,
|
|
50
|
+
durationMs: row?.durationMs,
|
|
51
|
+
error: row?.error,
|
|
52
|
+
userId: row?.userId,
|
|
53
|
+
memoryContextApplied: Boolean(metadata.memoryContextApplied),
|
|
54
|
+
memoryScopes: metadata.memoryScopes || [],
|
|
55
|
+
harnessTag: metadata.harnessTag || 'default',
|
|
56
|
+
createdAt: row?.createdAt || row?.startedAt,
|
|
57
|
+
startedAt: row?.startedAt,
|
|
58
|
+
endedAt: row?.endedAt,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildSpanTree(rows: unknown[]) {
|
|
63
|
+
const byId = new Map<string, Record<string, unknown> & { children: unknown[] }>();
|
|
64
|
+
const roots: Array<Record<string, unknown> & { children: unknown[] }> = [];
|
|
65
|
+
|
|
66
|
+
for (const raw of rows) {
|
|
67
|
+
const row = toPlain(raw);
|
|
68
|
+
byId.set(String(row.id), { ...row, children: [] });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const row of byId.values()) {
|
|
72
|
+
const parentSpanId = normalizeString(row.parentSpanId);
|
|
73
|
+
if (parentSpanId && byId.has(parentSpanId)) {
|
|
74
|
+
byId.get(parentSpanId)?.children.push(row);
|
|
75
|
+
} else {
|
|
76
|
+
roots.push(row);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const sort = (items: Array<Record<string, unknown> & { children: unknown[] }>) => {
|
|
81
|
+
items.sort(
|
|
82
|
+
(a, b) =>
|
|
83
|
+
new Date(String(a.startedAt || a.createdAt || 0)).getTime() -
|
|
84
|
+
new Date(String(b.startedAt || b.createdAt || 0)).getTime(),
|
|
85
|
+
);
|
|
86
|
+
for (const item of items) sort(item.children as Array<Record<string, unknown> & { children: unknown[] }>);
|
|
87
|
+
};
|
|
88
|
+
sort(roots);
|
|
89
|
+
return roots;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function flattenTimeline(rows: unknown[]) {
|
|
93
|
+
return rows
|
|
94
|
+
.map(toPlain)
|
|
95
|
+
.sort(
|
|
96
|
+
(a, b) =>
|
|
97
|
+
new Date(String(a.startedAt || a.createdAt || 0)).getTime() -
|
|
98
|
+
new Date(String(b.startedAt || b.createdAt || 0)).getTime(),
|
|
99
|
+
)
|
|
100
|
+
.map((row) => ({
|
|
101
|
+
id: row.id,
|
|
102
|
+
type: row.type,
|
|
103
|
+
at: row.startedAt || row.createdAt,
|
|
104
|
+
title: row.title || row.toolName || row.type,
|
|
105
|
+
toolName: row.toolName,
|
|
106
|
+
status: row.status,
|
|
107
|
+
durationMs: row.durationMs,
|
|
108
|
+
content: row.output || row.error || asObject(row.input).question || '',
|
|
109
|
+
parentSpanId: row.parentSpanId,
|
|
110
|
+
input: row.input,
|
|
111
|
+
output: row.output,
|
|
112
|
+
error: row.error,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function findConversation(ctx: any, sessionId?: string) {
|
|
117
|
+
if (!sessionId) return null;
|
|
118
|
+
try {
|
|
119
|
+
return toPlain(
|
|
120
|
+
await ctx.db.getRepository('aiConversations').findOne({
|
|
121
|
+
filter: { sessionId },
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function findToolMessages(ctx: any, sessionIds: string[]) {
|
|
130
|
+
const repo = ctx.db.getRepository('aiToolMessages');
|
|
131
|
+
const rows: unknown[] = [];
|
|
132
|
+
for (const sessionId of Array.from(new Set(sessionIds.filter(Boolean)))) {
|
|
133
|
+
try {
|
|
134
|
+
const list = await repo.find({
|
|
135
|
+
filter: { sessionId },
|
|
136
|
+
sort: ['messageId', 'id'],
|
|
137
|
+
});
|
|
138
|
+
rows.push(...list);
|
|
139
|
+
} catch {
|
|
140
|
+
// Keep monitor read-only and best-effort across plugin-ai versions.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return rows.map(toPlain);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function findNativeMessages(ctx: any, parentSessionId?: string, subSessionId?: string) {
|
|
147
|
+
const repo = ctx.db.getRepository('aiMessages');
|
|
148
|
+
const rows: unknown[] = [];
|
|
149
|
+
for (const sessionId of [parentSessionId, subSessionId].filter(Boolean)) {
|
|
150
|
+
try {
|
|
151
|
+
const list = await repo.find({
|
|
152
|
+
filter: { sessionId },
|
|
153
|
+
sort: ['messageId'],
|
|
154
|
+
limit: 200,
|
|
155
|
+
});
|
|
156
|
+
rows.push(...list);
|
|
157
|
+
} catch {
|
|
158
|
+
// Optional native details only.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return rows.map(toPlain);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function backfillFromMetadata(ctx: any, spanRepo: any, limit: number) {
|
|
165
|
+
const messageRepo = ctx.db.getRepository('aiMessages');
|
|
166
|
+
const toolRepo = ctx.db.getRepository('aiToolMessages');
|
|
167
|
+
const conversationRepo = ctx.db.getRepository('aiConversations');
|
|
168
|
+
const messages = await messageRepo.find({
|
|
169
|
+
sort: ['-messageId'],
|
|
170
|
+
limit,
|
|
171
|
+
});
|
|
172
|
+
let created = 0;
|
|
173
|
+
let skipped = 0;
|
|
174
|
+
|
|
175
|
+
for (const rawMessage of messages) {
|
|
176
|
+
const message = toPlain(rawMessage);
|
|
177
|
+
const conversations = asObject(message.metadata).subAgentConversations;
|
|
178
|
+
if (!Array.isArray(conversations)) continue;
|
|
179
|
+
|
|
180
|
+
for (const item of conversations) {
|
|
181
|
+
const subSessionId = normalizeString(item?.sessionId);
|
|
182
|
+
const toolCallId = normalizeString(item?.toolCallId);
|
|
183
|
+
if (!subSessionId || !toolCallId) {
|
|
184
|
+
skipped += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const existing = await spanRepo.findOne({
|
|
189
|
+
filter: {
|
|
190
|
+
source: NATIVE_SOURCE,
|
|
191
|
+
subSessionId,
|
|
192
|
+
toolCallId,
|
|
193
|
+
type: 'sub_agent',
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
if (existing) {
|
|
197
|
+
skipped += 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const toolMessage = toPlain(
|
|
202
|
+
await toolRepo.findOne({
|
|
203
|
+
filter: { toolCallId },
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
const parentSessionId = normalizeString(toolMessage?.sessionId) || normalizeString(message.sessionId);
|
|
207
|
+
const parentConversation = parentSessionId
|
|
208
|
+
? toPlain(await conversationRepo.findOne({ filter: { sessionId: parentSessionId } }))
|
|
209
|
+
: null;
|
|
210
|
+
const subConversation = toPlain(await conversationRepo.findOne({ filter: { sessionId: subSessionId } }));
|
|
211
|
+
const invokeStart = Number(toolMessage?.invokeStartTime);
|
|
212
|
+
const invokeEnd = Number(toolMessage?.invokeEndTime);
|
|
213
|
+
const durationMs =
|
|
214
|
+
Number.isFinite(invokeStart) && Number.isFinite(invokeEnd) && invokeEnd >= invokeStart
|
|
215
|
+
? invokeEnd - invokeStart
|
|
216
|
+
: undefined;
|
|
217
|
+
const rootRunId = makeNativeRunId([
|
|
218
|
+
parentSessionId,
|
|
219
|
+
subSessionId,
|
|
220
|
+
toolCallId,
|
|
221
|
+
subConversation?.aiEmployeeUsername,
|
|
222
|
+
]);
|
|
223
|
+
const content = asObject(toolMessage?.content);
|
|
224
|
+
|
|
225
|
+
await spanRepo.create({
|
|
226
|
+
values: {
|
|
227
|
+
rootRunId,
|
|
228
|
+
parentSpanId: null,
|
|
229
|
+
source: NATIVE_SOURCE,
|
|
230
|
+
parentSessionId: parentSessionId || undefined,
|
|
231
|
+
subSessionId: subSessionId || undefined,
|
|
232
|
+
toolCallId,
|
|
233
|
+
type: 'sub_agent',
|
|
234
|
+
status: statusFromNative(item?.status || toolMessage?.status),
|
|
235
|
+
leaderUsername: parentConversation?.aiEmployeeUsername,
|
|
236
|
+
employeeUsername: subConversation?.aiEmployeeUsername,
|
|
237
|
+
toolName: 'dispatch-sub-agent-task',
|
|
238
|
+
title:
|
|
239
|
+
subConversation?.title ||
|
|
240
|
+
`${parentConversation?.aiEmployeeUsername || 'main'} -> ${
|
|
241
|
+
subConversation?.aiEmployeeUsername || 'sub-agent'
|
|
242
|
+
}`,
|
|
243
|
+
input: {
|
|
244
|
+
question: subConversation?.title,
|
|
245
|
+
parentSessionId: parentSessionId || undefined,
|
|
246
|
+
subSessionId: subSessionId || undefined,
|
|
247
|
+
},
|
|
248
|
+
output: trimText(content.answer || toolMessage?.content, 20000),
|
|
249
|
+
durationMs: formatDuration(durationMs),
|
|
250
|
+
startedAt: invokeStart ? new Date(invokeStart) : new Date(),
|
|
251
|
+
endedAt: invokeEnd ? new Date(invokeEnd) : undefined,
|
|
252
|
+
metadata: {
|
|
253
|
+
source: NATIVE_SOURCE,
|
|
254
|
+
backfilled: true,
|
|
255
|
+
messageId: message.messageId,
|
|
256
|
+
toolMessageId: toolMessage?.id,
|
|
257
|
+
nativeStatus: item?.status,
|
|
258
|
+
},
|
|
259
|
+
userId: parentConversation?.userId || subConversation?.userId,
|
|
260
|
+
createdAt: new Date(),
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
created += 1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return { created, skipped };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function backfillFromToolMessages(ctx: any, spanRepo: any, limit: number) {
|
|
271
|
+
const toolRepo = ctx.db.getRepository('aiToolMessages');
|
|
272
|
+
const toolMessages = await toolRepo.find({
|
|
273
|
+
filter: { toolName: 'dispatch-sub-agent-task' },
|
|
274
|
+
sort: ['-id'],
|
|
275
|
+
limit,
|
|
276
|
+
});
|
|
277
|
+
let created = 0;
|
|
278
|
+
let skipped = 0;
|
|
279
|
+
|
|
280
|
+
for (const rawToolMessage of toolMessages) {
|
|
281
|
+
const toolMessage = toPlain(rawToolMessage);
|
|
282
|
+
const content = asObject(toolMessage.content);
|
|
283
|
+
const subSessionId = normalizeString(content.sessionId);
|
|
284
|
+
const toolCallId = normalizeString(toolMessage.toolCallId);
|
|
285
|
+
if (!subSessionId || !toolCallId) {
|
|
286
|
+
skipped += 1;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const existing = await spanRepo.findOne({
|
|
291
|
+
filter: {
|
|
292
|
+
source: NATIVE_SOURCE,
|
|
293
|
+
subSessionId,
|
|
294
|
+
toolCallId,
|
|
295
|
+
type: 'sub_agent',
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
if (existing) {
|
|
299
|
+
skipped += 1;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const parentSessionId = normalizeString(toolMessage.sessionId);
|
|
304
|
+
const parentConversation = await findConversation(ctx, parentSessionId);
|
|
305
|
+
const subConversation = await findConversation(ctx, subSessionId);
|
|
306
|
+
const invokeStart = Number(toolMessage.invokeStartTime);
|
|
307
|
+
const invokeEnd = Number(toolMessage.invokeEndTime);
|
|
308
|
+
const rootRunId = makeNativeRunId([parentSessionId, subSessionId, toolCallId, subConversation?.aiEmployeeUsername]);
|
|
309
|
+
|
|
310
|
+
await spanRepo.create({
|
|
311
|
+
values: {
|
|
312
|
+
rootRunId,
|
|
313
|
+
parentSpanId: null,
|
|
314
|
+
source: NATIVE_SOURCE,
|
|
315
|
+
parentSessionId: parentSessionId || undefined,
|
|
316
|
+
subSessionId: subSessionId || undefined,
|
|
317
|
+
toolCallId,
|
|
318
|
+
type: 'sub_agent',
|
|
319
|
+
status: statusFromNative(toolMessage.status),
|
|
320
|
+
leaderUsername: parentConversation?.aiEmployeeUsername,
|
|
321
|
+
employeeUsername: subConversation?.aiEmployeeUsername,
|
|
322
|
+
toolName: 'dispatch-sub-agent-task',
|
|
323
|
+
title:
|
|
324
|
+
subConversation?.title ||
|
|
325
|
+
`${parentConversation?.aiEmployeeUsername || 'main'} -> ${
|
|
326
|
+
subConversation?.aiEmployeeUsername || 'sub-agent'
|
|
327
|
+
}`,
|
|
328
|
+
input: {
|
|
329
|
+
question: subConversation?.title,
|
|
330
|
+
parentSessionId: parentSessionId || undefined,
|
|
331
|
+
subSessionId: subSessionId || undefined,
|
|
332
|
+
},
|
|
333
|
+
output: trimText(content.answer || toolMessage.content, 20000),
|
|
334
|
+
durationMs:
|
|
335
|
+
Number.isFinite(invokeStart) && Number.isFinite(invokeEnd) && invokeEnd >= invokeStart
|
|
336
|
+
? invokeEnd - invokeStart
|
|
337
|
+
: undefined,
|
|
338
|
+
startedAt: invokeStart ? new Date(invokeStart) : new Date(),
|
|
339
|
+
endedAt: invokeEnd ? new Date(invokeEnd) : undefined,
|
|
340
|
+
metadata: {
|
|
341
|
+
source: NATIVE_SOURCE,
|
|
342
|
+
backfilled: true,
|
|
343
|
+
toolMessageId: toolMessage.id,
|
|
344
|
+
},
|
|
345
|
+
userId: parentConversation?.userId || subConversation?.userId,
|
|
346
|
+
createdAt: new Date(),
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
created += 1;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return { created, skipped };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function registerAgentMonitorResource(plugin: Plugin) {
|
|
356
|
+
plugin.app.resource({
|
|
357
|
+
name: 'agentMonitor',
|
|
358
|
+
actions: {
|
|
359
|
+
async list(ctx, next) {
|
|
360
|
+
const { page = 1, pageSize = 20, sort = ['-createdAt'], filter = {} } = ctx.action.params;
|
|
361
|
+
const requestedFilter = asObject(filter);
|
|
362
|
+
const spanFilter: Record<string, unknown> = {
|
|
363
|
+
source: NATIVE_SOURCE,
|
|
364
|
+
type: 'sub_agent',
|
|
365
|
+
parentSpanId: null,
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
for (const key of [
|
|
369
|
+
'leaderUsername',
|
|
370
|
+
'employeeUsername',
|
|
371
|
+
'status',
|
|
372
|
+
'parentSessionId',
|
|
373
|
+
'subSessionId',
|
|
374
|
+
'toolCallId',
|
|
375
|
+
]) {
|
|
376
|
+
if (requestedFilter[key]) spanFilter[key] = requestedFilter[key];
|
|
377
|
+
}
|
|
378
|
+
if (requestedFilter.subAgentUsername) spanFilter.employeeUsername = requestedFilter.subAgentUsername;
|
|
379
|
+
if (requestedFilter.sessionId) spanFilter.parentSessionId = requestedFilter.sessionId;
|
|
380
|
+
|
|
381
|
+
if (!isAdmin(ctx)) {
|
|
382
|
+
const userId = currentUserId(ctx);
|
|
383
|
+
if (!userId) ctx.throw(401, 'Not authenticated');
|
|
384
|
+
spanFilter.userId = userId;
|
|
385
|
+
} else if (requestedFilter.userId) {
|
|
386
|
+
spanFilter.userId = requestedFilter.userId;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const repo = ctx.db.getRepository('agentExecutionSpans');
|
|
390
|
+
const [rows, count] = await repo.findAndCount({
|
|
391
|
+
filter: spanFilter,
|
|
392
|
+
sort,
|
|
393
|
+
offset: (Number(page) - 1) * Number(pageSize),
|
|
394
|
+
limit: Number(pageSize),
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
ctx.body = {
|
|
398
|
+
data: rows.map(formatSpanListRow),
|
|
399
|
+
meta: {
|
|
400
|
+
count,
|
|
401
|
+
page: Number(page),
|
|
402
|
+
pageSize: Number(pageSize),
|
|
403
|
+
totalPage: Math.ceil(count / Number(pageSize)),
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
await next();
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
async get(ctx, next) {
|
|
410
|
+
const { filterByTk } = ctx.action.params;
|
|
411
|
+
if (!filterByTk) {
|
|
412
|
+
ctx.throw(400, 'span id is required');
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const repo = ctx.db.getRepository('agentExecutionSpans');
|
|
417
|
+
const span = toPlain(
|
|
418
|
+
await repo.findOne({
|
|
419
|
+
filter: { id: filterByTk },
|
|
420
|
+
}),
|
|
421
|
+
);
|
|
422
|
+
if (!span) {
|
|
423
|
+
ctx.throw(404, 'span not found');
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!isAdmin(ctx) && String(span.userId || '') !== String(currentUserId(ctx) || '')) {
|
|
428
|
+
ctx.throw(403, 'You cannot view this agent run.');
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const rows = await repo.find({
|
|
433
|
+
filter: { rootRunId: span.rootRunId },
|
|
434
|
+
sort: ['createdAt'],
|
|
435
|
+
});
|
|
436
|
+
const parentSessionId = normalizeString(span.parentSessionId || asObject(span.metadata).parentSessionId);
|
|
437
|
+
const subSessionId = normalizeString(span.subSessionId || asObject(span.metadata).subSessionId);
|
|
438
|
+
|
|
439
|
+
ctx.body = {
|
|
440
|
+
data: {
|
|
441
|
+
...formatSpanListRow(span),
|
|
442
|
+
input: span.input,
|
|
443
|
+
output: span.output,
|
|
444
|
+
metadata: span.metadata || {},
|
|
445
|
+
children: buildSpanTree(rows),
|
|
446
|
+
trace: flattenTimeline(rows),
|
|
447
|
+
nativeConversations: {
|
|
448
|
+
parent: await findConversation(ctx, parentSessionId),
|
|
449
|
+
subAgent: await findConversation(ctx, subSessionId),
|
|
450
|
+
},
|
|
451
|
+
nativeMessages: await findNativeMessages(ctx, parentSessionId, subSessionId),
|
|
452
|
+
toolMessages: await findToolMessages(ctx, [parentSessionId, subSessionId]),
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
await next();
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
async sync(ctx, next) {
|
|
459
|
+
if (!isAdmin(ctx)) {
|
|
460
|
+
ctx.throw(403, 'Admin role is required to sync native agent monitor spans.');
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const values = asObject(ctx.action.params?.values);
|
|
465
|
+
const limit = Math.min(Math.max(Number(values.limit || ctx.action.params?.limit || 200), 1), 1000);
|
|
466
|
+
const spanRepo = ctx.db.getRepository('agentExecutionSpans');
|
|
467
|
+
const metadataResult = await backfillFromMetadata(ctx, spanRepo, limit);
|
|
468
|
+
const toolResult = await backfillFromToolMessages(ctx, spanRepo, limit);
|
|
469
|
+
|
|
470
|
+
ctx.body = {
|
|
471
|
+
data: {
|
|
472
|
+
created: metadataResult.created + toolResult.created,
|
|
473
|
+
skipped: metadataResult.skipped + toolResult.skipped,
|
|
474
|
+
metadata: metadataResult,
|
|
475
|
+
toolMessages: toolResult,
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
await next();
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
}
|