plugin-agent-orchestrator 1.0.17 → 1.0.18

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 (139) hide show
  1. package/dist/client/AIEmployeeSelect.d.ts +11 -0
  2. package/dist/client/AIEmployeesContext.d.ts +30 -0
  3. package/dist/client/AgentRunsTab.d.ts +2 -0
  4. package/dist/client/HarnessProfilesTab.d.ts +2 -0
  5. package/dist/client/OrchestratorSettings.d.ts +3 -0
  6. package/dist/client/RulesTab.d.ts +2 -0
  7. package/dist/client/TracingTab.d.ts +2 -0
  8. package/dist/client/index.d.ts +1 -0
  9. package/dist/client/index.js +1 -1
  10. package/dist/client/plugin.d.ts +6 -0
  11. package/dist/client/skill-hub/components/ExecutionHistory.d.ts +2 -0
  12. package/dist/client/skill-hub/components/ExecutionProgress.d.ts +20 -0
  13. package/dist/client/skill-hub/components/GitSkillImport.d.ts +7 -0
  14. package/dist/client/skill-hub/components/LoopSettings.d.ts +2 -0
  15. package/dist/client/skill-hub/components/SkillEditor.d.ts +7 -0
  16. package/dist/client/skill-hub/components/SkillManager.d.ts +2 -0
  17. package/dist/client/skill-hub/components/SkillMetrics.d.ts +2 -0
  18. package/dist/client/skill-hub/components/SkillTestPanel.d.ts +7 -0
  19. package/dist/client/skill-hub/index.d.ts +11 -0
  20. package/dist/client/skill-hub/locale.d.ts +3 -0
  21. package/dist/client/skill-hub/tools/InteractionSchemasProvider.d.ts +6 -0
  22. package/dist/client/skill-hub/tools/SkillHubCard.d.ts +3 -0
  23. package/dist/client/skill-hub/tools/loopTemplates.d.ts +22 -0
  24. package/dist/client/skill-hub/tools/registerSkillLoopCards.d.ts +1 -0
  25. package/dist/client/skill-hub/utils/jsonFields.d.ts +3 -0
  26. package/dist/client/tools/PlanApprovalCard.d.ts +3 -0
  27. package/dist/client/tools/registerOrchestratorCards.d.ts +1 -0
  28. package/dist/externalVersion.js +6 -6
  29. package/dist/index.d.ts +2 -0
  30. package/dist/server/collections/agent-execution-spans.d.ts +9 -0
  31. package/dist/server/collections/agent-harness-profiles.d.ts +2 -0
  32. package/dist/server/collections/agent-harness-profiles.js +89 -0
  33. package/dist/server/collections/agent-loop-events.d.ts +2 -0
  34. package/dist/server/collections/agent-loop-events.js +101 -0
  35. package/dist/server/collections/agent-loop-runs.d.ts +2 -0
  36. package/dist/server/collections/agent-loop-runs.js +188 -0
  37. package/dist/server/collections/agent-loop-steps.d.ts +2 -0
  38. package/dist/server/collections/agent-loop-steps.js +174 -0
  39. package/dist/server/collections/orchestrator-config.d.ts +2 -0
  40. package/dist/server/collections/orchestrator-config.js +7 -0
  41. package/dist/server/collections/orchestrator-logs.d.ts +8 -0
  42. package/dist/server/collections/skill-definitions.d.ts +3 -0
  43. package/dist/server/collections/skill-executions.d.ts +3 -0
  44. package/dist/server/collections/skill-executions.js +12 -0
  45. package/dist/server/collections/skill-loop-configs.d.ts +3 -0
  46. package/dist/server/collections/skill-loop-configs.js +94 -0
  47. package/dist/server/collections/skill-worker-configs.d.ts +3 -0
  48. package/dist/server/index.d.ts +1 -0
  49. package/dist/server/migrations/20260423000000-add-progress-fields.d.ts +4 -0
  50. package/dist/server/migrations/20260425000000-add-interaction-schema.d.ts +4 -0
  51. package/dist/server/migrations/20260427000000-add-tracing-detail-fields.d.ts +7 -0
  52. package/dist/server/migrations/20260427000000-change-packages-to-text.d.ts +4 -0
  53. package/dist/server/migrations/20260427000001-change-other-json-to-text.d.ts +4 -0
  54. package/dist/server/migrations/20260429000000-add-llm-fields.d.ts +7 -0
  55. package/dist/server/migrations/20260429000000-fix-inputargs-json-to-text.d.ts +16 -0
  56. package/dist/server/migrations/20260503000000-add-orchestrator-trace-fields.d.ts +7 -0
  57. package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.d.ts +7 -0
  58. package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.js +55 -0
  59. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.d.ts +12 -0
  60. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +162 -0
  61. package/dist/server/plugin.d.ts +16 -0
  62. package/dist/server/plugin.js +13 -0
  63. package/dist/server/resources/agent-loop.d.ts +3 -0
  64. package/dist/server/resources/agent-loop.js +205 -0
  65. package/dist/server/resources/tracing.d.ts +7 -0
  66. package/dist/server/services/AgentHarness.d.ts +42 -0
  67. package/dist/server/services/AgentHarness.js +565 -0
  68. package/dist/server/services/AgentLoopController.d.ts +205 -0
  69. package/dist/server/services/AgentLoopController.js +940 -0
  70. package/dist/server/services/AgentLoopRepository.d.ts +20 -0
  71. package/dist/server/services/AgentLoopRepository.js +210 -0
  72. package/dist/server/services/AgentLoopService.d.ts +149 -0
  73. package/dist/server/services/AgentLoopService.js +133 -0
  74. package/dist/server/services/AgentPlanValidator.d.ts +4 -0
  75. package/dist/server/services/AgentPlanValidator.js +99 -0
  76. package/dist/server/services/AgentPlannerService.d.ts +8 -0
  77. package/dist/server/services/AgentPlannerService.js +119 -0
  78. package/dist/server/services/AgentRegistryService.d.ts +13 -0
  79. package/dist/server/services/AgentRegistryService.js +178 -0
  80. package/dist/server/services/CodeValidator.d.ts +32 -0
  81. package/dist/server/services/ExecutionSpanService.d.ts +46 -0
  82. package/dist/server/services/FileManager.d.ts +28 -0
  83. package/dist/server/services/SandboxRunner.d.ts +41 -0
  84. package/dist/server/services/SkillManager.d.ts +6 -0
  85. package/dist/server/services/SkillRepositoryService.d.ts +22 -0
  86. package/dist/server/services/WorkerEnvManager.d.ts +26 -0
  87. package/dist/server/skill-hub/actions/git-import.d.ts +21 -0
  88. package/dist/server/skill-hub/mcp/McpController.d.ts +15 -0
  89. package/dist/server/skill-hub/plugin.d.ts +61 -0
  90. package/dist/server/skill-hub/plugin.js +137 -54
  91. package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +16 -0
  92. package/dist/server/skill-hub/utils/json-fields.d.ts +7 -0
  93. package/dist/server/tools/agent-loop.d.ts +235 -0
  94. package/dist/server/tools/agent-loop.js +406 -0
  95. package/dist/server/tools/delegate-task.d.ts +19 -0
  96. package/dist/server/tools/delegate-task.js +19 -368
  97. package/dist/server/tools/external-rag-search.d.ts +42 -0
  98. package/dist/server/tools/orchestrator-plan.d.ts +205 -0
  99. package/dist/server/tools/orchestrator-plan.js +291 -0
  100. package/dist/server/tools/skill-execute.d.ts +36 -0
  101. package/dist/server/tools/skill-execute.js +2 -0
  102. package/package.json +1 -1
  103. package/src/client/AgentRunsTab.tsx +764 -0
  104. package/src/client/HarnessProfilesTab.tsx +247 -0
  105. package/src/client/OrchestratorSettings.tsx +40 -2
  106. package/src/client/RulesTab.tsx +103 -6
  107. package/src/client/plugin.tsx +27 -54
  108. package/src/client/skill-hub/components/LoopSettings.tsx +331 -0
  109. package/src/client/skill-hub/index.tsx +51 -75
  110. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +56 -16
  111. package/src/client/skill-hub/tools/SkillHubCard.tsx +35 -4
  112. package/src/client/skill-hub/tools/loopTemplates.ts +52 -0
  113. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -0
  114. package/src/client/tools/PlanApprovalCard.tsx +175 -0
  115. package/src/client/tools/registerOrchestratorCards.ts +7 -0
  116. package/src/server/collections/agent-harness-profiles.ts +59 -0
  117. package/src/server/collections/agent-loop-events.ts +71 -0
  118. package/src/server/collections/agent-loop-runs.ts +158 -0
  119. package/src/server/collections/agent-loop-steps.ts +144 -0
  120. package/src/server/collections/orchestrator-config.ts +7 -0
  121. package/src/server/collections/skill-executions.ts +63 -51
  122. package/src/server/collections/skill-loop-configs.ts +65 -0
  123. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -0
  124. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -0
  125. package/src/server/plugin.ts +15 -0
  126. package/src/server/resources/agent-loop.ts +183 -0
  127. package/src/server/services/AgentHarness.ts +663 -0
  128. package/src/server/services/AgentLoopController.ts +1128 -0
  129. package/src/server/services/AgentLoopRepository.ts +194 -0
  130. package/src/server/services/AgentLoopService.ts +161 -0
  131. package/src/server/services/AgentPlanValidator.ts +73 -0
  132. package/src/server/services/AgentPlannerService.ts +93 -0
  133. package/src/server/services/AgentRegistryService.ts +169 -0
  134. package/src/server/services/ExecutionSpanService.ts +2 -0
  135. package/src/server/skill-hub/plugin.ts +881 -771
  136. package/src/server/tools/agent-loop.ts +399 -0
  137. package/src/server/tools/delegate-task.ts +23 -485
  138. package/src/server/tools/orchestrator-plan.ts +279 -0
  139. package/src/server/tools/skill-execute.ts +68 -64
@@ -24,7 +24,6 @@ const ORCHESTRATOR_DEPTH_KEY = '__orchestratorDepth';
24
24
  */
25
25
  const ORCHESTRATOR_PATH_KEY = '__orchestratorPath';
26
26
 
27
-
28
27
  /** Max sub-agents that the dispatch tool runs concurrently in one call. */
29
28
  const MAX_DISPATCH_CONCURRENCY = 5;
30
29
  /** Hard cap on tasks per dispatch call to keep output bounded. */
@@ -65,10 +64,7 @@ function buildDispatchToolName(leaderUsername: string) {
65
64
  }
66
65
 
67
66
  function createRootRunId(seed = '') {
68
- const hash = createHash('sha1')
69
- .update(`${Date.now()}::${Math.random()}::${seed}`)
70
- .digest('hex')
71
- .slice(0, 10);
67
+ const hash = createHash('sha1').update(`${Date.now()}::${Math.random()}::${seed}`).digest('hex').slice(0, 10);
72
68
  return `run_${Date.now()}_${hash}`;
73
69
  }
74
70
 
@@ -148,7 +144,7 @@ function createDelegateToolOptions(
148
144
  return {
149
145
  scope: 'CUSTOM',
150
146
  execution: 'backend',
151
- defaultPermission: 'ALLOW',
147
+ defaultPermission: 'ASK',
152
148
  silence: false,
153
149
  introduction: {
154
150
  title: `[${leaderUsername}] ${subAgentEmployee.nickname || subAgentUsername}${legacyAlias ? ' (legacy)' : ''}`,
@@ -295,7 +291,7 @@ function createDispatchToolOptions(
295
291
  return {
296
292
  scope: 'CUSTOM',
297
293
  execution: 'backend',
298
- defaultPermission: 'ALLOW',
294
+ defaultPermission: 'ASK',
299
295
  silence: false,
300
296
  introduction: {
301
297
  title: `[${leaderUsername}] Dispatch sub-agents`,
@@ -794,13 +790,9 @@ async function invokeDelegateTask(
794
790
  } = options;
795
791
 
796
792
  // --- Snapshot ctx fields up-front ---
797
- // Long-running agent execution (up to `timeout` ms) outlives the parent HTTP
798
- // request, so middleware may have cleared `ctx.auth`, `ctx.state`, or even
799
- // disposed the underlying socket by the time we finalize the log row.
800
- // Capturing the values once here keeps log/audit fields stable.
801
793
  const ctxSnapshot = captureCtxSnapshot(ctx);
802
794
 
803
- // --- P1: Depth enforcement & Circular Delegation Detection ---
795
+ // --- Depth enforcement & Circular Delegation Detection ---
804
796
  const currentDepth: number = (ctx as any)[ORCHESTRATOR_DEPTH_KEY] ?? 0;
805
797
  const currentPath: string[] = (ctx as any)[ORCHESTRATOR_PATH_KEY] ?? [leaderUsername];
806
798
 
@@ -845,396 +837,33 @@ async function invokeDelegateTask(
845
837
  };
846
838
  }
847
839
 
848
- const spanService = new ExecutionSpanService(plugin);
849
840
  const upstreamTraceContext = getOrchestratorTraceContext(ctx);
850
841
  const rootRunId =
851
842
  providedRootRunId || upstreamTraceContext?.rootRunId || createRootRunId(`${leaderUsername}:${subAgentUsername}`);
852
843
  const parentSpanId = providedParentSpanId || upstreamTraceContext?.spanId || upstreamTraceContext?.parentSpanId;
853
- const startTime = Date.now();
854
- const trace: TraceEvent[] = [
855
- {
856
- type: 'start',
857
- at: nowIso(),
858
- title: `Delegation started: ${leaderUsername} -> ${subAgentUsername}`,
859
- content: task,
860
- },
861
- ];
862
- const executionSpan = await spanService.create({
863
- rootRunId,
864
- parentSpanId,
865
- type: 'sub_agent',
866
- status: 'running',
867
- leaderUsername,
868
- employeeUsername: subAgentUsername,
869
- title: `Delegation: ${leaderUsername} -> ${subAgentUsername}`,
870
- input: { task, context },
871
- metadata: {
872
- depth: currentDepth,
873
- maxDepth,
874
- toolName,
875
- recursionLimit,
876
- llmOverride: llmService && model ? { llmService, model } : undefined,
877
- },
878
- userId: ctxSnapshot.userId,
879
- });
880
- const executionSpanId = executionSpan?.id ? String(executionSpan.id) : undefined;
881
- const logRecord = await logDelegation(ctx, plugin, {
844
+ const agentLoopRunId = upstreamTraceContext?.agentLoopRunId;
845
+ const agentLoopStepId = upstreamTraceContext?.agentLoopStepId;
846
+
847
+ return plugin.agentLoopService.harness.runSubAgent(ctx, {
882
848
  leaderUsername,
883
849
  subAgentUsername,
884
- toolName,
850
+ subAgentEmployee,
885
851
  task,
886
852
  context,
887
- result: '',
888
- status: 'running',
889
- depth: currentDepth,
890
- durationMs: 0,
891
- trace,
892
- snapshot: ctxSnapshot,
853
+ currentDepth,
854
+ currentPath,
855
+ maxDepth,
856
+ timeout,
857
+ toolCallId,
858
+ toolName,
859
+ llmService,
860
+ model,
861
+ recursionLimit,
862
+ rootRunId,
863
+ parentSpanId,
864
+ agentLoopRunId,
865
+ agentLoopStepId,
893
866
  });
894
- if (executionSpanId && logRecord?.id) {
895
- await spanService.update(executionSpanId, { orchestratorLogId: logRecord.id });
896
- }
897
-
898
- try {
899
- const aiPlugin = ctx.app.pm.get('ai') as PluginAIServer;
900
- if (!aiPlugin) {
901
- throw new Error('Plugin AI is not installed or enabled');
902
- }
903
-
904
- // --- Step 1: Resolve LLM model from sub-agent's employee config ---
905
- let modelSettings = hasModelSettings(subAgentEmployee.modelSettings) ? subAgentEmployee.modelSettings : undefined;
906
-
907
- // Override with orchestrator config if provided
908
- if (llmService && model) {
909
- modelSettings = { llmService, model };
910
- }
911
-
912
- if (!hasModelSettings(modelSettings)) {
913
- // Fallback to leader's LLM model if sub-agent doesn't have one
914
- const leaderEmployee = await plugin.db.getRepository('aiEmployees').findOne({
915
- filter: { username: leaderUsername },
916
- });
917
-
918
- // The leader's model might be empty in the DB if it relies on the dynamic system default.
919
- // In that case, we extract the dynamic `model` passed from the frontend request.
920
- const dynamicModel = ctx.action?.params?.values?.model;
921
- modelSettings = hasModelSettings(leaderEmployee?.modelSettings)
922
- ? leaderEmployee.modelSettings
923
- : hasModelSettings(dynamicModel)
924
- ? dynamicModel
925
- : undefined;
926
-
927
- if (!hasModelSettings(modelSettings)) {
928
- throw new Error(
929
- `Sub-agent "${subAgentUsername}" has no LLM model configured (and leader fallback failed). Please configure a model in the Orchestrator Config or AI Employee settings.`,
930
- );
931
- }
932
- }
933
-
934
- const { provider } = await aiPlugin.aiManager.getLLMService({
935
- llmService: modelSettings.llmService,
936
- model: modelSettings.model,
937
- });
938
- const chatModel = provider.createModel();
939
-
940
- // --- Step 2: Resolve tools via CORE toolsManager ---
941
- // Uses app.aiManager.toolsManager (same as AIEmployee.getToolsMap at ai-employee.ts:1286)
942
- // NOT plugin-ai's local toolManager (which has a different grouped format).
943
- const coreToolsManager = ctx.app.aiManager.toolsManager;
944
- const allTools: ToolsEntry[] = await coreToolsManager.listTools();
945
-
946
- // skillSettings.skills is { name: string, autoCall: boolean }[]
947
- // (verified at ai-employee.ts:1028-1029)
948
- const employeeSkills: EmployeeSkillConfig[] = (subAgentEmployee.skillSettings?.skills ?? [])
949
- .map((s: any) =>
950
- typeof s === 'string'
951
- ? { name: s, autoCall: false }
952
- : { name: s?.name, autoCall: s?.autoCall === true },
953
- )
954
- .filter((s: EmployeeSkillConfig) => Boolean(s.name));
955
- const employeeSkillMap = new Map(employeeSkills.map((skill) => [skill.name, skill]));
956
-
957
- const langchainTools: DynamicStructuredTool[] = [];
958
-
959
- for (const toolEntry of allTools) {
960
- const entryName = toolEntry.definition.name;
961
- if (!entryName) continue;
962
- const employeeSkill = employeeSkillMap.get(entryName);
963
-
964
- // Only include tools that the sub-agent employee is configured to use.
965
- // Also skip our own orchestration tools to prevent circular delegation
966
- // (belt-and-suspenders with the depth check above).
967
- //
968
- // Headless sub-agent execution has no human confirmation surface, so we
969
- // require both the employee assignment and the tool definition to be
970
- // explicitly auto-callable. This prevents ASK/interactionSchema Skill Hub
971
- // tools from being executed silently by a delegated sub-agent.
972
- if (
973
- !employeeSkill ||
974
- isDelegateToolName(plugin, entryName) ||
975
- employeeSkill.autoCall !== true
976
- ) {
977
- continue;
978
- }
979
-
980
- langchainTools.push(
981
- new DynamicStructuredTool({
982
- name: entryName.replace(/[^a-zA-Z0-9_-]/g, '_'),
983
- description: toolEntry.definition.description || entryName,
984
- schema: (toolEntry.definition.schema || z.object({})) as any,
985
- func: async (toolArgs) => {
986
- // Forward the invoke with depth tracking, circular path tracking and identity overrides
987
- const invokeCtx = Object.create(ctx);
988
- (invokeCtx as any)[ORCHESTRATOR_DEPTH_KEY] = currentDepth + 1;
989
- (invokeCtx as any)[ORCHESTRATOR_PATH_KEY] = [...currentPath, subAgentUsername];
990
- (invokeCtx as any)._currentAIEmployee = subAgentUsername;
991
- if (ctx.state) {
992
- invokeCtx.state = Object.create(ctx.state);
993
- invokeCtx.state.currentAIEmployee = subAgentUsername;
994
- }
995
- const toolStartedAt = Date.now();
996
- const isSkillHubTool = entryName === 'skill_hub_execute' || entryName.startsWith('skill_hub_');
997
- const toolSpan = await spanService.create({
998
- rootRunId,
999
- parentSpanId: executionSpanId,
1000
- type: isSkillHubTool ? 'skill' : 'tool',
1001
- status: 'running',
1002
- leaderUsername,
1003
- employeeUsername: subAgentUsername,
1004
- toolName: toolEntry.definition.name,
1005
- title: isSkillHubTool ? `Skill: ${toolEntry.definition.name}` : `Tool: ${toolEntry.definition.name}`,
1006
- input: toolArgs,
1007
- metadata: {
1008
- depth: currentDepth + 1,
1009
- toolCallId: `orch-${toolCallId}`,
1010
- defaultPermission: toolEntry.defaultPermission,
1011
- },
1012
- userId: ctxSnapshot.userId,
1013
- });
1014
- const toolSpanId = toolSpan?.id ? String(toolSpan.id) : undefined;
1015
- setOrchestratorTraceContext(invokeCtx, {
1016
- rootRunId,
1017
- spanId: toolSpanId,
1018
- parentSpanId: executionSpanId,
1019
- toolCallId: `orch-${toolCallId}`,
1020
- leaderUsername,
1021
- employeeUsername: subAgentUsername,
1022
- toolName: toolEntry.definition.name,
1023
- });
1024
-
1025
- trace.push({
1026
- type: 'tool_call',
1027
- at: nowIso(),
1028
- title: `Calling tool: ${toolEntry.definition.name}`,
1029
- toolName: toolEntry.definition.name,
1030
- args: toolArgs,
1031
- });
1032
-
1033
- try {
1034
- const res = await toolEntry.invoke(invokeCtx, toolArgs, `orch-${toolCallId}`);
1035
- const output = truncateText(res?.content ?? res?.result ?? res, 50000);
1036
- trace.push({
1037
- type: 'tool_result',
1038
- at: nowIso(),
1039
- title: `Tool finished: ${toolEntry.definition.name}`,
1040
- toolName: toolEntry.definition.name,
1041
- status: res?.status || 'success',
1042
- content: truncateText(output, 2000),
1043
- });
1044
- if (res?.status === 'error') {
1045
- await spanService.finish(toolSpanId, 'error', toolStartedAt, {
1046
- output,
1047
- error: truncateText(res.content || output, 10000),
1048
- });
1049
- throw new Error(`Tool <${toolEntry.definition.name}> failed: ${res.content}`);
1050
- }
1051
- await spanService.finish(toolSpanId, 'success', toolStartedAt, {
1052
- output,
1053
- skillExecutionId: res?.result?.execId || res?.execId,
1054
- });
1055
- return typeof res?.content === 'string' ? res.content : JSON.stringify(res);
1056
- } catch (e: any) {
1057
- trace.push({
1058
- type: 'tool_error',
1059
- at: nowIso(),
1060
- title: `Tool failed: ${toolEntry.definition.name}`,
1061
- toolName: toolEntry.definition.name,
1062
- status: 'error',
1063
- content: e.message,
1064
- });
1065
- await spanService.finish(toolSpanId, 'error', toolStartedAt, {
1066
- error: truncateText(e.message, 10000),
1067
- });
1068
- throw e;
1069
- }
1070
- },
1071
- }),
1072
- );
1073
- }
1074
-
1075
- // --- Step 3: Build the agent ---
1076
- const abortController = new AbortController();
1077
- const executor = createReactAgent({
1078
- llm: chatModel,
1079
- tools: langchainTools,
1080
- });
1081
-
1082
- // --- Step 4: Construct messages ---
1083
- let systemPrompt =
1084
- subAgentEmployee.chatSettings?.systemPrompt ||
1085
- subAgentEmployee.bio ||
1086
- `You are an AI assistant named "${subAgentEmployee.nickname || subAgentUsername}". ${
1087
- subAgentEmployee.about || ''
1088
- }`;
1089
-
1090
- // --- Step 4b: Inject shared context from Knowledge Base (soft dependency) ---
1091
- // If plugin-knowledge-base is installed, inject the session context summary
1092
- // so the sub-agent is aware of findings from previous agents in this run.
1093
- try {
1094
- const kbPlugin = ctx.app.pm.get('plugin-knowledge-base') as any;
1095
- if (kbPlugin?.sessionContext) {
1096
- const sessionId =
1097
- ctx.action?.params?.values?.sessionId ||
1098
- ctx.action?.params?.sessionId ||
1099
- ctx.state?.sessionId;
1100
-
1101
- const contextSummary = await kbPlugin.sessionContext.buildSummary(
1102
- { rootRunId, ...(sessionId ? { sessionId } : {}) },
1103
- 6000,
1104
- );
1105
- if (contextSummary) {
1106
- systemPrompt += `\n\n<shared_context>\nThe following context was shared by other agents in this workflow. Use it to avoid redundant work:\n${contextSummary}\n</shared_context>`;
1107
- }
1108
- }
1109
- } catch (e: any) {
1110
- // Graceful fallback — never block delegation due to context injection failure.
1111
- ctx.app.log?.debug?.(`[AgentOrchestrator] Shared context injection skipped: ${e.message}`);
1112
- }
1113
-
1114
- const combinedTask = context ? `Task: ${task}\n\nContext Provided:\n${context}` : `Task: ${task}`;
1115
-
1116
- // --- Step 5: Execute with timeout + abort ---
1117
- // P3 FIX: AbortController signal cancels the in-flight stream on timeout,
1118
- // preventing continued token consumption after the timeout fires.
1119
- const effectiveRecursionLimit =
1120
- Number.isFinite(recursionLimit) && (recursionLimit as number) > 0 ? (recursionLimit as number) : 50;
1121
- const invokePromise = executeAgent(
1122
- executor,
1123
- systemPrompt,
1124
- combinedTask,
1125
- abortController.signal,
1126
- effectiveRecursionLimit,
1127
- );
1128
-
1129
- const timeoutHandle = createTimeout(timeout, subAgentUsername, abortController);
1130
- let result: AgentExecutionResult;
1131
- try {
1132
- result = (await Promise.race([invokePromise, timeoutHandle.promise])) as AgentExecutionResult;
1133
- } finally {
1134
- // Always release the timer so it doesn't keep the event loop alive.
1135
- timeoutHandle.cancel();
1136
- }
1137
-
1138
- const content = result.content || 'Sub-agent completed the task but produced no output.';
1139
- trace.push({
1140
- type: 'finish',
1141
- at: nowIso(),
1142
- title: `Delegation finished: ${subAgentUsername}`,
1143
- status: 'success',
1144
- content: truncateText(content, 2000),
1145
- });
1146
-
1147
- // Log successful execution for tracing
1148
- await logDelegation(ctx, plugin, {
1149
- id: logRecord?.id,
1150
- leaderUsername,
1151
- subAgentUsername,
1152
- toolName,
1153
- task,
1154
- context,
1155
- result: content,
1156
- status: 'success',
1157
- depth: currentDepth,
1158
- durationMs: Date.now() - startTime,
1159
- trace,
1160
- messages: result.messages,
1161
- snapshot: ctxSnapshot,
1162
- });
1163
- await spanService.finish(executionSpanId, 'success', startTime, {
1164
- output: content,
1165
- metadata: {
1166
- depth: currentDepth,
1167
- maxDepth,
1168
- toolName,
1169
- recursionLimit,
1170
- messages: result.messages,
1171
- traceCount: trace.length,
1172
- },
1173
- });
1174
-
1175
- return {
1176
- status: 'success' as const,
1177
- content,
1178
- };
1179
- } catch (e) {
1180
- plugin.app.log.error(`[AgentOrchestrator] Sub-agent ${subAgentUsername} failed`, e);
1181
-
1182
- // Log failed execution for tracing
1183
- await logDelegation(ctx, plugin, {
1184
- id: logRecord?.id,
1185
- leaderUsername,
1186
- subAgentUsername,
1187
- toolName,
1188
- task,
1189
- context,
1190
- result: '',
1191
- status: 'error',
1192
- depth: currentDepth,
1193
- durationMs: Date.now() - startTime,
1194
- error: e.message,
1195
- trace: [
1196
- ...trace,
1197
- {
1198
- type: 'error',
1199
- at: nowIso(),
1200
- title: `Delegation failed: ${subAgentUsername}`,
1201
- status: 'error',
1202
- content: e.message,
1203
- },
1204
- ],
1205
- snapshot: ctxSnapshot,
1206
- }).catch((logErr) => {
1207
- plugin.app.log.warn('[AgentOrchestrator] Failed to save error log for delegation', logErr);
1208
- });
1209
- await spanService.finish(executionSpanId, 'error', startTime, {
1210
- error: truncateText(e.message, 10000),
1211
- metadata: {
1212
- depth: currentDepth,
1213
- maxDepth,
1214
- toolName,
1215
- recursionLimit,
1216
- traceCount: trace.length + 1,
1217
- },
1218
- });
1219
-
1220
- const diagnosticTrace = trace
1221
- .filter((t) => t.type === 'tool_error' || t.type === 'error')
1222
- .map((t) => `[${t.toolName ? `Tool: ${t.toolName}` : 'Sub-agent'}] Error: ${t.content || t.title}`)
1223
- .join('\n');
1224
-
1225
- const formattedError = [
1226
- `Sub-agent "${subAgentUsername}" failed execution: ${e.message}`,
1227
- diagnosticTrace ? `\nDiagnostic Details of internal failures:\n${diagnosticTrace}` : '',
1228
- `Suggestion: Review the tool parameters above or try dividing the task into simpler independent tasks.`,
1229
- ]
1230
- .filter(Boolean)
1231
- .join('\n');
1232
-
1233
- return {
1234
- status: 'error' as const,
1235
- content: formattedError,
1236
- };
1237
- }
1238
867
  }
1239
868
 
1240
869
  /**
@@ -1267,15 +896,12 @@ async function logDelegation(
1267
896
  return;
1268
897
  }
1269
898
 
1270
- // Prefer the early snapshot captured in invokeDelegateTask — by the time
1271
- // the agent finishes, ctx may already be disposed. Fall back to ctx for
1272
- // call sites that don't pass a snapshot (e.g. authz-failure short-circuit).
1273
899
  let userId: number | undefined = data.snapshot?.userId;
1274
900
  if (userId == null) {
1275
901
  try {
1276
902
  userId = ctx.auth?.user?.id || ctx.state?.currentUser?.id;
1277
903
  } catch {
1278
- // ctx lifecycle ended — proceed without userId
904
+ // ctx lifecycle ended
1279
905
  }
1280
906
  }
1281
907
 
@@ -1315,91 +941,3 @@ async function logDelegation(
1315
941
  plugin.app.log.warn('[AgentOrchestrator] Failed to log delegation event', e);
1316
942
  }
1317
943
  }
1318
-
1319
- /**
1320
- * Execute the agent and extract the final AI message content.
1321
- * Uses executor.invoke to get the final state cleanly, avoiding chunk parsing issues.
1322
- * Accepts an AbortSignal so the execution can be cancelled on timeout.
1323
- */
1324
- async function executeAgent(
1325
- executor: any,
1326
- systemPrompt: string,
1327
- task: string,
1328
- signal?: AbortSignal,
1329
- recursionLimit = 50,
1330
- ): Promise<AgentExecutionResult> {
1331
- const config: any = { recursionLimit };
1332
- if (signal) {
1333
- config.signal = signal;
1334
- }
1335
-
1336
- const finalState = await executor.invoke(
1337
- {
1338
- messages: [new SystemMessage(systemPrompt), new HumanMessage(task)],
1339
- },
1340
- config,
1341
- );
1342
-
1343
- // finalState.messages contains the entire conversation history of this delegation
1344
- const messages = finalState?.messages || [];
1345
-
1346
- // Find the last AI message in the chain
1347
- const lastAIMessage = [...messages].reverse().find((m) => m.getType() === 'ai');
1348
-
1349
- if (!lastAIMessage || !lastAIMessage.content) {
1350
- return { content: '', messages: serializeMessages(messages) };
1351
- }
1352
-
1353
- let content = '';
1354
- if (typeof lastAIMessage.content === 'string') {
1355
- content = lastAIMessage.content;
1356
- } else if (Array.isArray(lastAIMessage.content)) {
1357
- content = lastAIMessage.content.map((c: any) => c.text || JSON.stringify(c)).join('\n');
1358
- } else {
1359
- content = String(lastAIMessage.content);
1360
- }
1361
-
1362
- return { content, messages: serializeMessages(messages) };
1363
- }
1364
-
1365
- function serializeMessages(messages: any[]) {
1366
- return (messages || []).map((message, index) => {
1367
- const type = typeof message.getType === 'function' ? message.getType() : message.type;
1368
- return {
1369
- index,
1370
- type,
1371
- name: message.name,
1372
- content: truncateText(message.content, 10000),
1373
- toolCalls: message.tool_calls || message.toolCalls || [],
1374
- toolCallId: message.tool_call_id,
1375
- additionalKwargs: message.additional_kwargs,
1376
- responseMetadata: message.response_metadata,
1377
- };
1378
- });
1379
- }
1380
-
1381
- /**
1382
- * Schedule a rejection-on-timeout that also aborts the in-flight stream.
1383
- * Returns the promise plus a `cancel()` so callers can release the timer
1384
- * when the race resolves successfully (otherwise the handle keeps the event
1385
- * loop alive until `ms` elapses).
1386
- */
1387
- function createTimeout(
1388
- ms: number,
1389
- agentName: string,
1390
- abortController?: AbortController,
1391
- ): { promise: Promise<never>; cancel: () => void } {
1392
- let timer: ReturnType<typeof setTimeout> | undefined;
1393
- const promise = new Promise<never>((_resolve, reject) => {
1394
- timer = setTimeout(() => {
1395
- abortController?.abort();
1396
- reject(new Error(`Sub-agent "${agentName}" timed out after ${ms / 1000}s`));
1397
- }, ms);
1398
- });
1399
- return {
1400
- promise,
1401
- cancel: () => {
1402
- if (timer) clearTimeout(timer);
1403
- },
1404
- };
1405
- }