plugin-agent-orchestrator 1.0.20 → 1.0.21

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 (98) hide show
  1. package/dist/client/hooks/useRunEventStream.d.ts +22 -0
  2. package/dist/client/index.d.ts +1 -0
  3. package/dist/client/index.js +1 -1
  4. package/dist/externalVersion.js +6 -6
  5. package/dist/server/collections/agent-execution-spans.js +24 -0
  6. package/dist/server/collections/agent-loop-runs.js +36 -0
  7. package/dist/server/collections/orchestrator-config.js +14 -0
  8. package/dist/server/migrations/20260601000000-add-token-fields.d.ts +7 -0
  9. package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
  10. package/dist/server/plugin.js +47 -0
  11. package/dist/server/resources/agent-loop.js +33 -25
  12. package/dist/server/resources/tracing.js +5 -8
  13. package/dist/server/services/AgentHarness.d.ts +2 -0
  14. package/dist/server/services/AgentHarness.js +56 -90
  15. package/dist/server/services/AgentLoopController.d.ts +33 -20
  16. package/dist/server/services/AgentLoopController.js +164 -125
  17. package/dist/server/services/AgentLoopRepository.js +16 -34
  18. package/dist/server/services/AgentLoopService.d.ts +28 -18
  19. package/dist/server/services/AgentLoopService.js +7 -1
  20. package/dist/server/services/AgentPlannerService.js +5 -25
  21. package/dist/server/services/AgentRegistryService.d.ts +8 -0
  22. package/dist/server/services/AgentRegistryService.js +34 -24
  23. package/dist/server/services/CircuitBreaker.d.ts +40 -0
  24. package/dist/server/services/CircuitBreaker.js +120 -0
  25. package/dist/server/services/ContextAggregator.d.ts +45 -0
  26. package/dist/server/services/ContextAggregator.js +201 -0
  27. package/dist/server/services/ExecutionSpanService.js +2 -5
  28. package/dist/server/services/RunEventBus.d.ts +9 -0
  29. package/dist/server/services/RunEventBus.js +73 -0
  30. package/dist/server/services/TokenTracker.d.ts +62 -0
  31. package/dist/server/services/TokenTracker.js +173 -0
  32. package/dist/server/tools/agent-loop.d.ts +8 -8
  33. package/dist/server/tools/agent-loop.js +30 -63
  34. package/dist/server/tools/delegate-task.js +14 -72
  35. package/dist/server/tools/orchestrator-plan.d.ts +6 -6
  36. package/dist/server/tools/orchestrator-plan.js +10 -47
  37. package/dist/server/types.d.ts +47 -0
  38. package/dist/server/types.js +24 -0
  39. package/dist/server/utils/ctx-utils.d.ts +30 -0
  40. package/dist/server/utils/ctx-utils.js +152 -0
  41. package/dist/server/utils/logging.d.ts +6 -0
  42. package/dist/server/utils/logging.js +86 -0
  43. package/package.json +44 -44
  44. package/src/client/AgentRunsTab.tsx +764 -764
  45. package/src/client/HarnessProfilesTab.tsx +247 -247
  46. package/src/client/OrchestratorSettings.tsx +106 -106
  47. package/src/client/RulesTab.tsx +716 -716
  48. package/src/client/hooks/useRunEventStream.ts +76 -0
  49. package/src/client/index.tsx +2 -1
  50. package/src/client/plugin.tsx +27 -27
  51. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  52. package/src/client/skill-hub/index.tsx +51 -51
  53. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
  54. package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
  55. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  56. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
  57. package/src/client/tools/PlanApprovalCard.tsx +175 -175
  58. package/src/client/tools/registerOrchestratorCards.ts +7 -7
  59. package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
  60. package/src/server/__tests__/circuit-breaker.test.ts +169 -0
  61. package/src/server/__tests__/context-aggregator.test.ts +222 -0
  62. package/src/server/__tests__/parallel-execution.test.ts +318 -0
  63. package/src/server/__tests__/smoke.test.ts +120 -0
  64. package/src/server/collections/agent-execution-spans.ts +24 -0
  65. package/src/server/collections/agent-harness-profiles.ts +59 -59
  66. package/src/server/collections/agent-loop-events.ts +71 -71
  67. package/src/server/collections/agent-loop-runs.ts +38 -1
  68. package/src/server/collections/agent-loop-steps.ts +144 -144
  69. package/src/server/collections/orchestrator-config.ts +14 -0
  70. package/src/server/collections/skill-executions.ts +106 -106
  71. package/src/server/collections/skill-loop-configs.ts +65 -65
  72. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  73. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -142
  74. package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
  75. package/src/server/plugin.ts +53 -0
  76. package/src/server/resources/agent-loop.ts +21 -12
  77. package/src/server/resources/tracing.ts +3 -7
  78. package/src/server/services/AgentHarness.ts +78 -116
  79. package/src/server/services/AgentLoopController.ts +197 -122
  80. package/src/server/services/AgentLoopRepository.ts +9 -25
  81. package/src/server/services/AgentLoopService.ts +13 -1
  82. package/src/server/services/AgentPlanValidator.ts +73 -73
  83. package/src/server/services/AgentPlannerService.ts +2 -25
  84. package/src/server/services/AgentRegistryService.ts +40 -31
  85. package/src/server/services/CircuitBreaker.ts +116 -0
  86. package/src/server/services/ContextAggregator.ts +239 -0
  87. package/src/server/services/ExecutionSpanService.ts +2 -4
  88. package/src/server/services/RunEventBus.ts +45 -0
  89. package/src/server/services/TokenTracker.ts +209 -0
  90. package/src/server/skill-hub/plugin.ts +898 -898
  91. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -460
  92. package/src/server/tools/agent-loop.ts +18 -57
  93. package/src/server/tools/delegate-task.ts +11 -93
  94. package/src/server/tools/orchestrator-plan.ts +26 -50
  95. package/src/server/tools/skill-execute.ts +160 -160
  96. package/src/server/types.ts +55 -0
  97. package/src/server/utils/ctx-utils.ts +118 -0
  98. package/src/server/utils/logging.ts +63 -0
@@ -5,41 +5,16 @@ import { DynamicStructuredTool } from '@langchain/core/tools';
5
5
  import { HumanMessage, SystemMessage } from '@langchain/core/messages';
6
6
  import { ExecutionSpanService, setOrchestratorTraceContext } from './ExecutionSpanService';
7
7
  import { AgentRegistryService } from './AgentRegistryService';
8
+ import { TokenTracker, extractTokenUsage } from './TokenTracker';
9
+ import { ContextAggregator } from './ContextAggregator';
10
+ import { getCircuitBreaker } from './CircuitBreaker';
11
+ import { toPlain, asObject, trimText, nowIso } from '../utils/ctx-utils';
12
+ import { logDelegation as sharedLogDelegation } from '../utils/logging';
13
+ import { TraceEvent } from '../types';
8
14
 
9
15
  const ORCHESTRATOR_DEPTH_KEY = '__orchestratorDepth';
10
16
  const ORCHESTRATOR_PATH_KEY = '__orchestratorPath';
11
17
 
12
- function toPlain(record: any) {
13
- return record?.toJSON?.() || record;
14
- }
15
-
16
- function asObject(value: any) {
17
- if (value && typeof value === 'object' && !Array.isArray(value)) return value;
18
- if (typeof value === 'string' && value.trim()) {
19
- try {
20
- const parsed = JSON.parse(value);
21
- return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
22
- } catch {
23
- return {};
24
- }
25
- }
26
- return {};
27
- }
28
-
29
- function trimText(value: any, max = 50000) {
30
- let text = '';
31
- if (typeof value === 'string') {
32
- text = value;
33
- } else if (value != null) {
34
- try {
35
- text = JSON.stringify(value);
36
- } catch {
37
- text = String(value);
38
- }
39
- }
40
- return text.length > max ? `${text.slice(0, max)}\n...[truncated]` : text;
41
- }
42
-
43
18
  function sanitizeToolPart(value: string) {
44
19
  return (value || '').replace(/[^a-zA-Z0-9_-]/g, '_');
45
20
  }
@@ -48,28 +23,18 @@ function buildDelegateToolName(leaderUsername: string, subAgentUsername: string)
48
23
  return `delegate_${sanitizeToolPart(leaderUsername)}_to_${sanitizeToolPart(subAgentUsername)}`;
49
24
  }
50
25
 
51
- function nowIso() {
52
- return new Date().toISOString();
53
- }
54
-
55
- type TraceEvent = {
56
- type: string;
57
- at: string;
58
- title: string;
59
- content?: string;
60
- toolName?: string;
61
- args?: any;
62
- status?: string;
63
- };
64
-
65
26
  export class AgentHarness {
66
27
  private readonly spanService: ExecutionSpanService;
28
+ private readonly tokenTracker: TokenTracker;
29
+ private readonly contextAggregator: ContextAggregator;
67
30
 
68
31
  constructor(
69
32
  private readonly plugin: any,
70
- private readonly registryService: AgentRegistryService
33
+ private readonly registryService: AgentRegistryService,
71
34
  ) {
72
35
  this.spanService = new ExecutionSpanService(plugin);
36
+ this.tokenTracker = new TokenTracker(plugin);
37
+ this.contextAggregator = new ContextAggregator(plugin);
73
38
  }
74
39
 
75
40
  get db() {
@@ -80,11 +45,7 @@ export class AgentHarness {
80
45
  return this.plugin.app;
81
46
  }
82
47
 
83
- async executeStep(
84
- run: any,
85
- step: any,
86
- options: { userId?: string | number; ctx?: any } = {}
87
- ): Promise<any> {
48
+ async executeStep(run: any, step: any, options: { userId?: string | number; ctx?: any } = {}): Promise<any> {
88
49
  const harnessTag = asObject(run.metadata).harnessTag || asObject(step.metadata).harnessTag || 'default';
89
50
  const profile = await this.registryService.getHarnessProfile(harnessTag);
90
51
  const settings = asObject(profile?.settings);
@@ -124,7 +85,7 @@ export class AgentHarness {
124
85
  run: any,
125
86
  step: any,
126
87
  settings: any,
127
- options: { userId?: string | number; ctx?: any }
88
+ options: { userId?: string | number; ctx?: any },
128
89
  ) {
129
90
  const target = step.target || asObject(step.metadata).subAgentUsername || step.input?.subAgentUsername;
130
91
  if (!target) {
@@ -147,10 +108,30 @@ export class AgentHarness {
147
108
  agentLoopRunId: String(run.id),
148
109
  agentLoopStepId: String(step.id),
149
110
  },
150
- 20000
111
+ 20000,
151
112
  );
152
113
 
153
- return this.invokeNamedTool(run, step, toolName, { task, context }, settings, options);
114
+ const circuitBreaker = getCircuitBreaker({ appLog: this.app });
115
+
116
+ // ── Circuit breaker check before invoking sub-agent ──────────────
117
+ if (!circuitBreaker.isAllowed(target)) {
118
+ const state = circuitBreaker.getState(target);
119
+ throw new Error(
120
+ `Sub-agent "${target}" circuit is open (${state?.failures || 0} failures). Retry after the recovery timeout.`,
121
+ );
122
+ }
123
+
124
+ try {
125
+ const result = await this.invokeNamedTool(run, step, toolName, { task, context }, settings, options);
126
+ circuitBreaker.recordSuccess(target);
127
+ return result;
128
+ } catch (e: any) {
129
+ // Don't count approval pauses as circuit failures
130
+ if (e?.message !== 'requires_approval') {
131
+ circuitBreaker.recordFailure(target);
132
+ }
133
+ throw e;
134
+ }
154
135
  }
155
136
 
156
137
  private async invokeNamedTool(
@@ -159,7 +140,7 @@ export class AgentHarness {
159
140
  toolName: string,
160
141
  args: any,
161
142
  settings: any,
162
- options: { userId?: string | number; ctx?: any }
143
+ options: { userId?: string | number; ctx?: any },
163
144
  ) {
164
145
  if (String(toolName).startsWith('orchestrator_')) {
165
146
  throw new Error(`Tool step "${step.planKey}" cannot call internal orchestrator control tool "${toolName}".`);
@@ -185,7 +166,12 @@ export class AgentHarness {
185
166
  const isDelegationTool = await this.registryService.isRegisteredDelegationTool(toolName);
186
167
  const isStepAlreadyApproved = step?.approval?.approved === true;
187
168
 
188
- if (!isDelegationTool && !isStepAlreadyApproved && (tool.defaultPermission === 'ASK' || (settings.requireApprovalRiskLevel && tool.riskLevel && tool.riskLevel >= settings.requireApprovalRiskLevel))) {
169
+ if (
170
+ !isDelegationTool &&
171
+ !isStepAlreadyApproved &&
172
+ (tool.defaultPermission === 'ASK' ||
173
+ (settings.requireApprovalRiskLevel && tool.riskLevel && tool.riskLevel >= settings.requireApprovalRiskLevel))
174
+ ) {
189
175
  throw new Error('requires_approval');
190
176
  }
191
177
 
@@ -245,7 +231,7 @@ export class AgentHarness {
245
231
  parentSpanId?: string;
246
232
  agentLoopRunId?: string;
247
233
  agentLoopStepId?: string;
248
- }
234
+ },
249
235
  ) {
250
236
  const {
251
237
  leaderUsername,
@@ -324,7 +310,7 @@ export class AgentHarness {
324
310
  const modelSettings = await this.registryService.resolveModelSettings(
325
311
  subAgentUsername,
326
312
  leaderUsername,
327
- llmService && model ? { llmService, model } : undefined
313
+ llmService && model ? { llmService, model } : undefined,
328
314
  );
329
315
 
330
316
  if (!modelSettings) {
@@ -342,7 +328,7 @@ export class AgentHarness {
342
328
 
343
329
  const employeeSkills = (subAgentEmployee.skillSettings?.skills ?? [])
344
330
  .map((s: any) =>
345
- typeof s === 'string' ? { name: s, autoCall: false } : { name: s?.name, autoCall: s?.autoCall === true }
331
+ typeof s === 'string' ? { name: s, autoCall: false } : { name: s?.name, autoCall: s?.autoCall === true },
346
332
  )
347
333
  .filter((s: any) => Boolean(s.name));
348
334
  const employeeSkillMap = new Map<string, any>(employeeSkills.map((s: any) => [s.name, s]));
@@ -354,11 +340,7 @@ export class AgentHarness {
354
340
  if (!entryName) continue;
355
341
  const employeeSkill = employeeSkillMap.get(entryName);
356
342
 
357
- if (
358
- !employeeSkill ||
359
- employeeSkill.autoCall !== true ||
360
- toolEntry.defaultPermission !== 'ALLOW'
361
- ) {
343
+ if (!employeeSkill || employeeSkill.autoCall !== true || toolEntry.defaultPermission !== 'ALLOW') {
362
344
  continue;
363
345
  }
364
346
 
@@ -456,14 +438,14 @@ export class AgentHarness {
456
438
  throw e;
457
439
  }
458
440
  },
459
- })
441
+ }),
460
442
  );
461
443
  }
462
444
 
463
445
  const abortController = new AbortController();
464
446
  const executor = createReactAgent({
465
447
  llm: chatModel,
466
- tools: langchainTools,
448
+ tools: langchainTools as any,
467
449
  });
468
450
 
469
451
  let systemPrompt =
@@ -480,20 +462,31 @@ export class AgentHarness {
480
462
  ctx.action?.params?.values?.sessionId || ctx.action?.params?.sessionId || ctx.state?.sessionId;
481
463
  const contextSummary = await kbPlugin.sessionContext.buildSummary(
482
464
  { rootRunId, ...(sessionId ? { sessionId } : {}) },
483
- 6000
465
+ 6000,
484
466
  );
485
467
  if (contextSummary) {
486
468
  systemPrompt += `\n\n<shared_context>\nThe following context was shared by other agents in this workflow:\n${contextSummary}\n</shared_context>`;
487
469
  }
488
470
  }
489
- } catch {}
471
+ } catch (e) {
472
+ // ignore — kb integration is best-effort
473
+ }
474
+
475
+ // ── Step context enrichment ────────────────────────────────────
476
+ if (agentLoopRunId) {
477
+ try {
478
+ systemPrompt = await this.contextAggregator.enrichSystemPrompt(systemPrompt, agentLoopRunId, agentLoopStepId);
479
+ } catch {
480
+ // Best-effort: context enrichment failure should not break execution
481
+ }
482
+ }
490
483
 
491
484
  const combinedTask = context ? `Task: ${task}\n\nContext:\n${context}` : `Task: ${task}`;
492
485
  const effectiveLimit = recursionLimit && recursionLimit > 0 ? recursionLimit : 50;
493
486
 
494
487
  let timeoutId: any;
495
488
  const timeoutMs = Number(timeout) > 0 ? Number(timeout) : 120000;
496
- const timeoutPromise = new Promise<never>((_, reject) => {
489
+ const timeoutPromise = new Promise<never>((_resolve, reject) => {
497
490
  timeoutId = setTimeout(() => {
498
491
  abortController.abort();
499
492
  reject(new Error(`Sub-agent execution timed out after ${timeoutMs}ms.`));
@@ -502,9 +495,9 @@ export class AgentHarness {
502
495
 
503
496
  const invokePromise = executor.invoke(
504
497
  {
505
- messages: [new SystemMessage(systemPrompt), new HumanMessage(combinedTask)],
498
+ messages: [new SystemMessage(systemPrompt), new HumanMessage(combinedTask)] as any,
506
499
  },
507
- { recursionLimit: effectiveLimit, signal: abortController.signal }
500
+ { recursionLimit: effectiveLimit, signal: abortController.signal },
508
501
  );
509
502
 
510
503
  let finalState: any;
@@ -514,15 +507,22 @@ export class AgentHarness {
514
507
  if (timeoutId) clearTimeout(timeoutId);
515
508
  }
516
509
 
510
+ // ── Token tracking ────────────────────────────────────────────────
511
+ const tokenUsage = extractTokenUsage(finalState);
512
+ if (tokenUsage) {
513
+ await this.tokenTracker.trackSpan(executionSpanId, tokenUsage, agentLoopRunId);
514
+ }
515
+
517
516
  const messages = finalState?.messages || [];
518
517
  const lastAIMessage = [...messages].reverse().find((m) => m.getType() === 'ai');
519
518
  let content = '';
520
519
  if (lastAIMessage) {
521
- content = typeof lastAIMessage.content === 'string'
522
- ? lastAIMessage.content
523
- : Array.isArray(lastAIMessage.content)
524
- ? lastAIMessage.content.map((c: any) => c.text || JSON.stringify(c)).join('\n')
525
- : String(lastAIMessage.content);
520
+ content =
521
+ typeof lastAIMessage.content === 'string'
522
+ ? lastAIMessage.content
523
+ : Array.isArray(lastAIMessage.content)
524
+ ? lastAIMessage.content.map((c: any) => c.text || JSON.stringify(c)).join('\n')
525
+ : String(lastAIMessage.content);
526
526
  }
527
527
 
528
528
  trace.push({
@@ -618,46 +618,8 @@ export class AgentHarness {
618
618
  durationMs: number;
619
619
  error?: string;
620
620
  trace?: TraceEvent[];
621
- }
621
+ },
622
622
  ) {
623
- try {
624
- const logsRepo = this.db.getRepository('orchestratorLogs');
625
- if (!logsRepo) return null;
626
-
627
- const userId = ctx?.auth?.user?.id || ctx?.state?.currentUser?.id;
628
- const values = {
629
- leaderUsername: data.leaderUsername,
630
- subAgentUsername: data.subAgentUsername,
631
- toolName: data.toolName,
632
- task: trimText(data.task, 10000),
633
- context: trimText(data.context || '', 10000),
634
- result: trimText(data.result || '', 50000),
635
- status: data.status,
636
- depth: data.depth,
637
- durationMs: data.durationMs,
638
- error: trimText(data.error || '', 10000),
639
- trace: data.trace || [],
640
- userId,
641
- updatedAt: new Date(),
642
- };
643
-
644
- if (data.id) {
645
- await logsRepo.update({
646
- filterByTk: data.id,
647
- values,
648
- });
649
- return { id: data.id };
650
- }
651
-
652
- const record = await logsRepo.create({
653
- values: {
654
- ...values,
655
- createdAt: new Date(),
656
- },
657
- });
658
- return toPlain(record);
659
- } catch {
660
- return null;
661
- }
623
+ return sharedLogDelegation(ctx, this.plugin, data);
662
624
  }
663
625
  }