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.
Files changed (108) hide show
  1. package/README.md +9 -7
  2. package/dist/client/index.js +1 -1
  3. package/dist/client-v2/{214.723affb37c13bf7a.js → 214.79650a549273f163.js} +1 -1
  4. package/dist/client-v2/264.718a107e43fc163c.js +10 -0
  5. package/dist/client-v2/373.f5d5292e53c4e832.js +10 -0
  6. package/dist/client-v2/{41.1805b2edfaa4afe2.js → 41.ba6e080cc0488143.js} +1 -1
  7. package/dist/client-v2/418.29e713f79131eece.js +10 -0
  8. package/dist/client-v2/619.bd3c5698b40705c3.js +10 -0
  9. package/dist/client-v2/677.a991ce0250ff5c77.js +10 -0
  10. package/dist/client-v2/{70.a15d7fcec7c41768.js → 70.bda9518881c05360.js} +1 -1
  11. package/dist/client-v2/925.f5370de8f6632d65.js +10 -0
  12. package/dist/client-v2/index.js +1 -1
  13. package/dist/externalVersion.js +7 -10
  14. package/dist/locale/en-US.json +94 -25
  15. package/dist/locale/vi-VN.json +94 -25
  16. package/dist/locale/zh-CN.json +94 -25
  17. package/dist/server/collections/agent-execution-spans.js +37 -0
  18. package/dist/server/collections/agent-harness-profiles.js +2 -2
  19. package/dist/server/collections/agent-memory-contexts.js +125 -0
  20. package/dist/server/collections/orchestrator-logs.js +2 -2
  21. package/dist/server/migrations/20260425000000-add-interaction-schema.js +3 -1
  22. package/dist/server/migrations/20260427000000-change-packages-to-text.js +3 -1
  23. package/dist/server/migrations/20260427000001-change-other-json-to-text.js +6 -2
  24. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +21 -19
  25. package/dist/server/migrations/20260621000000-native-policy-profile-defaults.js +193 -0
  26. package/dist/server/plugin.js +128 -74
  27. package/dist/server/resources/agent-monitor.js +454 -0
  28. package/dist/server/services/AgentHarness.js +24 -499
  29. package/dist/server/services/AgentMemoryContextService.js +216 -0
  30. package/dist/server/services/ExecutionSpanService.js +2 -2
  31. package/dist/server/services/NativeSubAgentObserver.js +413 -0
  32. package/dist/server/skill-hub/plugin.js +81 -5
  33. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
  34. package/dist/server/tools/delegate-task.js +11 -589
  35. package/dist/server/utils/skill-settings.js +18 -1
  36. package/package.json +47 -49
  37. package/src/client/AIEmployeesContext.tsx +5 -18
  38. package/src/client/AgentRunsTab.tsx +2 -771
  39. package/src/client/HarnessProfilesTab.tsx +2 -257
  40. package/src/client/OrchestratorSettings.tsx +97 -106
  41. package/src/client/RulesTab.tsx +2 -788
  42. package/src/client/plugin.tsx +0 -2
  43. package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
  44. package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
  45. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  46. package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
  47. package/src/client/skill-hub/components/SkillManager.tsx +194 -181
  48. package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
  49. package/src/client/skill-hub/locale.ts +16 -16
  50. package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
  51. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  52. package/src/client/skill-hub/utils/jsonFields.ts +7 -3
  53. package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
  54. package/src/client-v2/components/AgentRunsTab.tsx +182 -455
  55. package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
  56. package/src/client-v2/components/RulesTab.tsx +2 -782
  57. package/src/client-v2/components/TracingTab.tsx +1 -1
  58. package/src/client-v2/hooks/useApiRequest.ts +8 -1
  59. package/src/client-v2/pages/RulesPage.tsx +2 -2
  60. package/src/client-v2/plugin.tsx +3 -3
  61. package/src/locale/en-US.json +94 -25
  62. package/src/locale/vi-VN.json +94 -25
  63. package/src/locale/zh-CN.json +94 -25
  64. package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
  65. package/src/server/__tests__/skill-settings.test.ts +6 -6
  66. package/src/server/__tests__/smoke.test.ts +1 -0
  67. package/src/server/collections/agent-execution-spans.ts +37 -0
  68. package/src/server/collections/agent-harness-profiles.ts +59 -59
  69. package/src/server/collections/agent-loop-events.ts +71 -71
  70. package/src/server/collections/agent-loop-steps.ts +144 -144
  71. package/src/server/collections/agent-memory-contexts.ts +95 -0
  72. package/src/server/collections/orchestrator-logs.ts +4 -4
  73. package/src/server/collections/skill-definitions.ts +111 -111
  74. package/src/server/collections/skill-executions.ts +106 -106
  75. package/src/server/collections/skill-loop-configs.ts +65 -65
  76. package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
  77. package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
  78. package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
  79. package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
  80. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  81. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
  82. package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
  83. package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
  84. package/src/server/plugin.ts +151 -94
  85. package/src/server/resources/agent-monitor.ts +482 -0
  86. package/src/server/services/AgentHarness.ts +38 -623
  87. package/src/server/services/AgentMemoryContextService.ts +256 -0
  88. package/src/server/services/AgentPlanValidator.ts +73 -73
  89. package/src/server/services/ExecutionSpanService.ts +6 -2
  90. package/src/server/services/FileManager.ts +144 -144
  91. package/src/server/services/NativeSubAgentObserver.ts +507 -0
  92. package/src/server/services/SkillManager.ts +583 -583
  93. package/src/server/services/SkillRepositoryService.ts +5 -7
  94. package/src/server/services/TokenTracker.ts +3 -3
  95. package/src/server/services/WorkerEnvManager.ts +1 -2
  96. package/src/server/skill-hub/actions/git-import.ts +5 -7
  97. package/src/server/skill-hub/plugin.ts +89 -6
  98. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
  99. package/src/server/skill-hub/utils/json-fields.ts +1 -1
  100. package/src/server/tools/delegate-task.ts +13 -847
  101. package/src/server/utils/skill-settings.ts +24 -6
  102. package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
  103. package/dist/client-v2/418.5ae055abf141820e.js +0 -10
  104. package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
  105. package/dist/client-v2/892.72db4161511c8a16.js +0 -10
  106. package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
  107. package/src/client/tools/PlanApprovalCard.tsx +0 -176
  108. 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
+ }