plugin-agent-orchestrator 1.0.16 → 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.
- package/dist/client/AgentRunsTab.d.ts +2 -0
- package/dist/client/HarnessProfilesTab.d.ts +2 -0
- package/dist/client/index.js +1 -1
- package/dist/client/skill-hub/components/LoopSettings.d.ts +2 -0
- package/dist/client/skill-hub/index.d.ts +2 -1
- package/dist/client/skill-hub/tools/InteractionSchemasProvider.d.ts +1 -14
- package/dist/client/skill-hub/tools/loopTemplates.d.ts +22 -0
- package/dist/client/skill-hub/tools/registerSkillLoopCards.d.ts +1 -0
- package/dist/client/tools/PlanApprovalCard.d.ts +3 -0
- package/dist/client/tools/registerOrchestratorCards.d.ts +1 -0
- package/dist/externalVersion.js +6 -6
- package/dist/server/collections/agent-harness-profiles.d.ts +2 -0
- package/dist/server/collections/agent-harness-profiles.js +89 -0
- package/dist/server/collections/agent-loop-events.d.ts +2 -0
- package/dist/server/collections/agent-loop-events.js +101 -0
- package/dist/server/collections/agent-loop-runs.d.ts +2 -0
- package/dist/server/collections/agent-loop-runs.js +188 -0
- package/dist/server/collections/agent-loop-steps.d.ts +2 -0
- package/dist/server/collections/agent-loop-steps.js +174 -0
- package/dist/server/collections/orchestrator-config.js +7 -0
- package/dist/server/collections/skill-executions.js +12 -0
- package/dist/server/collections/skill-loop-configs.d.ts +3 -0
- package/dist/server/collections/skill-loop-configs.js +94 -0
- package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.d.ts +7 -0
- package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.js +55 -0
- package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.d.ts +12 -0
- package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +162 -0
- package/dist/server/plugin.d.ts +2 -0
- package/dist/server/plugin.js +13 -0
- package/dist/server/resources/agent-loop.d.ts +3 -0
- package/dist/server/resources/agent-loop.js +205 -0
- package/dist/server/services/AgentHarness.d.ts +42 -0
- package/dist/server/services/AgentHarness.js +565 -0
- package/dist/server/services/AgentLoopController.d.ts +205 -0
- package/dist/server/services/AgentLoopController.js +940 -0
- package/dist/server/services/AgentLoopRepository.d.ts +20 -0
- package/dist/server/services/AgentLoopRepository.js +210 -0
- package/dist/server/services/AgentLoopService.d.ts +149 -0
- package/dist/server/services/AgentLoopService.js +133 -0
- package/dist/server/services/AgentPlanValidator.d.ts +4 -0
- package/dist/server/services/AgentPlanValidator.js +99 -0
- package/dist/server/services/AgentPlannerService.d.ts +8 -0
- package/dist/server/services/AgentPlannerService.js +119 -0
- package/dist/server/services/AgentRegistryService.d.ts +13 -0
- package/dist/server/services/AgentRegistryService.js +178 -0
- package/dist/server/services/ExecutionSpanService.d.ts +2 -0
- package/dist/server/skill-hub/plugin.d.ts +3 -0
- package/dist/server/skill-hub/plugin.js +137 -54
- package/dist/server/tools/agent-loop.d.ts +235 -0
- package/dist/server/tools/agent-loop.js +406 -0
- package/dist/server/tools/delegate-task.js +37 -350
- package/dist/server/tools/orchestrator-plan.d.ts +205 -0
- package/dist/server/tools/orchestrator-plan.js +291 -0
- package/dist/server/tools/skill-execute.js +2 -0
- package/package.json +2 -2
- package/src/client/AgentRunsTab.tsx +764 -0
- package/src/client/HarnessProfilesTab.tsx +247 -0
- package/src/client/OrchestratorSettings.tsx +40 -2
- package/src/client/RulesTab.tsx +103 -6
- package/src/client/plugin.tsx +27 -54
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -0
- package/src/client/skill-hub/index.tsx +51 -75
- package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +56 -16
- package/src/client/skill-hub/tools/SkillHubCard.tsx +35 -4
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -0
- package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -0
- package/src/client/tools/PlanApprovalCard.tsx +175 -0
- package/src/client/tools/registerOrchestratorCards.ts +7 -0
- package/src/server/collections/agent-harness-profiles.ts +59 -0
- package/src/server/collections/agent-loop-events.ts +71 -0
- package/src/server/collections/agent-loop-runs.ts +158 -0
- package/src/server/collections/agent-loop-steps.ts +144 -0
- package/src/server/collections/orchestrator-config.ts +7 -0
- package/src/server/collections/skill-executions.ts +63 -51
- package/src/server/collections/skill-loop-configs.ts +65 -0
- package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -0
- package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -0
- package/src/server/plugin.ts +15 -0
- package/src/server/resources/agent-loop.ts +183 -0
- package/src/server/services/AgentHarness.ts +663 -0
- package/src/server/services/AgentLoopController.ts +1128 -0
- package/src/server/services/AgentLoopRepository.ts +194 -0
- package/src/server/services/AgentLoopService.ts +161 -0
- package/src/server/services/AgentPlanValidator.ts +73 -0
- package/src/server/services/AgentPlannerService.ts +93 -0
- package/src/server/services/AgentRegistryService.ts +169 -0
- package/src/server/services/ExecutionSpanService.ts +2 -0
- package/src/server/skill-hub/plugin.ts +881 -771
- package/src/server/tools/agent-loop.ts +399 -0
- package/src/server/tools/delegate-task.ts +48 -463
- package/src/server/tools/orchestrator-plan.ts +279 -0
- package/src/server/tools/skill-execute.ts +68 -64
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
* Used to prevent circular/recursive delegation chains.
|
|
19
19
|
*/
|
|
20
20
|
const ORCHESTRATOR_DEPTH_KEY = '__orchestratorDepth';
|
|
21
|
+
/**
|
|
22
|
+
* Context key for tracking the full delegation path.
|
|
23
|
+
* Used to detect and prevent circular delegation chains.
|
|
24
|
+
*/
|
|
25
|
+
const ORCHESTRATOR_PATH_KEY = '__orchestratorPath';
|
|
21
26
|
|
|
22
27
|
/** Max sub-agents that the dispatch tool runs concurrently in one call. */
|
|
23
28
|
const MAX_DISPATCH_CONCURRENCY = 5;
|
|
@@ -59,10 +64,7 @@ function buildDispatchToolName(leaderUsername: string) {
|
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
function createRootRunId(seed = '') {
|
|
62
|
-
const hash = createHash('sha1')
|
|
63
|
-
.update(`${Date.now()}::${Math.random()}::${seed}`)
|
|
64
|
-
.digest('hex')
|
|
65
|
-
.slice(0, 10);
|
|
67
|
+
const hash = createHash('sha1').update(`${Date.now()}::${Math.random()}::${seed}`).digest('hex').slice(0, 10);
|
|
66
68
|
return `run_${Date.now()}_${hash}`;
|
|
67
69
|
}
|
|
68
70
|
|
|
@@ -142,7 +144,7 @@ function createDelegateToolOptions(
|
|
|
142
144
|
return {
|
|
143
145
|
scope: 'CUSTOM',
|
|
144
146
|
execution: 'backend',
|
|
145
|
-
defaultPermission: '
|
|
147
|
+
defaultPermission: 'ASK',
|
|
146
148
|
silence: false,
|
|
147
149
|
introduction: {
|
|
148
150
|
title: `[${leaderUsername}] ${subAgentEmployee.nickname || subAgentUsername}${legacyAlias ? ' (legacy)' : ''}`,
|
|
@@ -289,7 +291,7 @@ function createDispatchToolOptions(
|
|
|
289
291
|
return {
|
|
290
292
|
scope: 'CUSTOM',
|
|
291
293
|
execution: 'backend',
|
|
292
|
-
defaultPermission: '
|
|
294
|
+
defaultPermission: 'ASK',
|
|
293
295
|
silence: false,
|
|
294
296
|
introduction: {
|
|
295
297
|
title: `[${leaderUsername}] Dispatch sub-agents`,
|
|
@@ -788,15 +790,14 @@ async function invokeDelegateTask(
|
|
|
788
790
|
} = options;
|
|
789
791
|
|
|
790
792
|
// --- Snapshot ctx fields up-front ---
|
|
791
|
-
// Long-running agent execution (up to `timeout` ms) outlives the parent HTTP
|
|
792
|
-
// request, so middleware may have cleared `ctx.auth`, `ctx.state`, or even
|
|
793
|
-
// disposed the underlying socket by the time we finalize the log row.
|
|
794
|
-
// Capturing the values once here keeps log/audit fields stable.
|
|
795
793
|
const ctxSnapshot = captureCtxSnapshot(ctx);
|
|
796
794
|
|
|
797
|
-
// ---
|
|
795
|
+
// --- Depth enforcement & Circular Delegation Detection ---
|
|
798
796
|
const currentDepth: number = (ctx as any)[ORCHESTRATOR_DEPTH_KEY] ?? 0;
|
|
799
|
-
|
|
797
|
+
const currentPath: string[] = (ctx as any)[ORCHESTRATOR_PATH_KEY] ?? [leaderUsername];
|
|
798
|
+
|
|
799
|
+
if (currentPath.includes(subAgentUsername)) {
|
|
800
|
+
const loopChain = [...currentPath, subAgentUsername].join(' -> ');
|
|
800
801
|
await logDelegation(ctx, plugin, {
|
|
801
802
|
leaderUsername,
|
|
802
803
|
subAgentUsername,
|
|
@@ -807,347 +808,17 @@ async function invokeDelegateTask(
|
|
|
807
808
|
status: 'error',
|
|
808
809
|
depth: currentDepth,
|
|
809
810
|
durationMs: 0,
|
|
810
|
-
error: `
|
|
811
|
+
error: `Circular delegation detected: ${loopChain}.`,
|
|
811
812
|
snapshot: ctxSnapshot,
|
|
812
813
|
});
|
|
813
814
|
return {
|
|
814
815
|
status: 'error' as const,
|
|
815
|
-
content: `
|
|
816
|
+
content: `Circular delegation detected: ${loopChain}. Execution aborted to prevent infinite reasoning loops.`,
|
|
816
817
|
};
|
|
817
818
|
}
|
|
818
819
|
|
|
819
|
-
|
|
820
|
-
const upstreamTraceContext = getOrchestratorTraceContext(ctx);
|
|
821
|
-
const rootRunId =
|
|
822
|
-
providedRootRunId || upstreamTraceContext?.rootRunId || createRootRunId(`${leaderUsername}:${subAgentUsername}`);
|
|
823
|
-
const parentSpanId = providedParentSpanId || upstreamTraceContext?.spanId || upstreamTraceContext?.parentSpanId;
|
|
824
|
-
const startTime = Date.now();
|
|
825
|
-
const trace: TraceEvent[] = [
|
|
826
|
-
{
|
|
827
|
-
type: 'start',
|
|
828
|
-
at: nowIso(),
|
|
829
|
-
title: `Delegation started: ${leaderUsername} -> ${subAgentUsername}`,
|
|
830
|
-
content: task,
|
|
831
|
-
},
|
|
832
|
-
];
|
|
833
|
-
const executionSpan = await spanService.create({
|
|
834
|
-
rootRunId,
|
|
835
|
-
parentSpanId,
|
|
836
|
-
type: 'sub_agent',
|
|
837
|
-
status: 'running',
|
|
838
|
-
leaderUsername,
|
|
839
|
-
employeeUsername: subAgentUsername,
|
|
840
|
-
title: `Delegation: ${leaderUsername} -> ${subAgentUsername}`,
|
|
841
|
-
input: { task, context },
|
|
842
|
-
metadata: {
|
|
843
|
-
depth: currentDepth,
|
|
844
|
-
maxDepth,
|
|
845
|
-
toolName,
|
|
846
|
-
recursionLimit,
|
|
847
|
-
llmOverride: llmService && model ? { llmService, model } : undefined,
|
|
848
|
-
},
|
|
849
|
-
userId: ctxSnapshot.userId,
|
|
850
|
-
});
|
|
851
|
-
const executionSpanId = executionSpan?.id ? String(executionSpan.id) : undefined;
|
|
852
|
-
const logRecord = await logDelegation(ctx, plugin, {
|
|
853
|
-
leaderUsername,
|
|
854
|
-
subAgentUsername,
|
|
855
|
-
toolName,
|
|
856
|
-
task,
|
|
857
|
-
context,
|
|
858
|
-
result: '',
|
|
859
|
-
status: 'running',
|
|
860
|
-
depth: currentDepth,
|
|
861
|
-
durationMs: 0,
|
|
862
|
-
trace,
|
|
863
|
-
snapshot: ctxSnapshot,
|
|
864
|
-
});
|
|
865
|
-
if (executionSpanId && logRecord?.id) {
|
|
866
|
-
await spanService.update(executionSpanId, { orchestratorLogId: logRecord.id });
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
try {
|
|
870
|
-
const aiPlugin = ctx.app.pm.get('ai') as PluginAIServer;
|
|
871
|
-
if (!aiPlugin) {
|
|
872
|
-
throw new Error('Plugin AI is not installed or enabled');
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// --- Step 1: Resolve LLM model from sub-agent's employee config ---
|
|
876
|
-
let modelSettings = hasModelSettings(subAgentEmployee.modelSettings) ? subAgentEmployee.modelSettings : undefined;
|
|
877
|
-
|
|
878
|
-
// Override with orchestrator config if provided
|
|
879
|
-
if (llmService && model) {
|
|
880
|
-
modelSettings = { llmService, model };
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
if (!hasModelSettings(modelSettings)) {
|
|
884
|
-
// Fallback to leader's LLM model if sub-agent doesn't have one
|
|
885
|
-
const leaderEmployee = await plugin.db.getRepository('aiEmployees').findOne({
|
|
886
|
-
filter: { username: leaderUsername },
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
// The leader's model might be empty in the DB if it relies on the dynamic system default.
|
|
890
|
-
// In that case, we extract the dynamic `model` passed from the frontend request.
|
|
891
|
-
const dynamicModel = ctx.action?.params?.values?.model;
|
|
892
|
-
modelSettings = hasModelSettings(leaderEmployee?.modelSettings)
|
|
893
|
-
? leaderEmployee.modelSettings
|
|
894
|
-
: hasModelSettings(dynamicModel)
|
|
895
|
-
? dynamicModel
|
|
896
|
-
: undefined;
|
|
897
|
-
|
|
898
|
-
if (!hasModelSettings(modelSettings)) {
|
|
899
|
-
throw new Error(
|
|
900
|
-
`Sub-agent "${subAgentUsername}" has no LLM model configured (and leader fallback failed). Please configure a model in the Orchestrator Config or AI Employee settings.`,
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const { provider } = await aiPlugin.aiManager.getLLMService({
|
|
906
|
-
llmService: modelSettings.llmService,
|
|
907
|
-
model: modelSettings.model,
|
|
908
|
-
});
|
|
909
|
-
const chatModel = provider.createModel();
|
|
910
|
-
|
|
911
|
-
// --- Step 2: Resolve tools via CORE toolsManager ---
|
|
912
|
-
// Uses app.aiManager.toolsManager (same as AIEmployee.getToolsMap at ai-employee.ts:1286)
|
|
913
|
-
// NOT plugin-ai's local toolManager (which has a different grouped format).
|
|
914
|
-
const coreToolsManager = ctx.app.aiManager.toolsManager;
|
|
915
|
-
const allTools: ToolsEntry[] = await coreToolsManager.listTools();
|
|
916
|
-
|
|
917
|
-
// skillSettings.skills is { name: string, autoCall: boolean }[]
|
|
918
|
-
// (verified at ai-employee.ts:1028-1029)
|
|
919
|
-
const employeeSkills: EmployeeSkillConfig[] = (subAgentEmployee.skillSettings?.skills ?? [])
|
|
920
|
-
.map((s: any) =>
|
|
921
|
-
typeof s === 'string'
|
|
922
|
-
? { name: s, autoCall: false }
|
|
923
|
-
: { name: s?.name, autoCall: s?.autoCall === true },
|
|
924
|
-
)
|
|
925
|
-
.filter((s: EmployeeSkillConfig) => Boolean(s.name));
|
|
926
|
-
const employeeSkillMap = new Map(employeeSkills.map((skill) => [skill.name, skill]));
|
|
927
|
-
|
|
928
|
-
const langchainTools: DynamicStructuredTool[] = [];
|
|
929
|
-
|
|
930
|
-
for (const toolEntry of allTools) {
|
|
931
|
-
const entryName = toolEntry.definition.name;
|
|
932
|
-
if (!entryName) continue;
|
|
933
|
-
const employeeSkill = employeeSkillMap.get(entryName);
|
|
934
|
-
|
|
935
|
-
// Only include tools that the sub-agent employee is configured to use.
|
|
936
|
-
// Also skip our own orchestration tools to prevent circular delegation
|
|
937
|
-
// (belt-and-suspenders with the depth check above).
|
|
938
|
-
//
|
|
939
|
-
// Headless sub-agent execution has no human confirmation surface, so we
|
|
940
|
-
// require both the employee assignment and the tool definition to be
|
|
941
|
-
// explicitly auto-callable. This prevents ASK/interactionSchema Skill Hub
|
|
942
|
-
// tools from being executed silently by a delegated sub-agent.
|
|
943
|
-
if (
|
|
944
|
-
!employeeSkill ||
|
|
945
|
-
isDelegateToolName(plugin, entryName) ||
|
|
946
|
-
employeeSkill.autoCall !== true ||
|
|
947
|
-
toolEntry.defaultPermission !== 'ALLOW'
|
|
948
|
-
) {
|
|
949
|
-
continue;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
langchainTools.push(
|
|
953
|
-
new DynamicStructuredTool({
|
|
954
|
-
name: entryName.replace(/[^a-zA-Z0-9_-]/g, '_'),
|
|
955
|
-
description: toolEntry.definition.description || entryName,
|
|
956
|
-
schema: (toolEntry.definition.schema || z.object({})) as any,
|
|
957
|
-
func: async (toolArgs) => {
|
|
958
|
-
// Forward the invoke with depth tracking
|
|
959
|
-
const invokeCtx = Object.create(ctx);
|
|
960
|
-
(invokeCtx as any)[ORCHESTRATOR_DEPTH_KEY] = currentDepth + 1;
|
|
961
|
-
const toolStartedAt = Date.now();
|
|
962
|
-
const isSkillHubTool = entryName === 'skill_hub_execute' || entryName.startsWith('skill_hub_');
|
|
963
|
-
const toolSpan = await spanService.create({
|
|
964
|
-
rootRunId,
|
|
965
|
-
parentSpanId: executionSpanId,
|
|
966
|
-
type: isSkillHubTool ? 'skill' : 'tool',
|
|
967
|
-
status: 'running',
|
|
968
|
-
leaderUsername,
|
|
969
|
-
employeeUsername: subAgentUsername,
|
|
970
|
-
toolName: toolEntry.definition.name,
|
|
971
|
-
title: isSkillHubTool ? `Skill: ${toolEntry.definition.name}` : `Tool: ${toolEntry.definition.name}`,
|
|
972
|
-
input: toolArgs,
|
|
973
|
-
metadata: {
|
|
974
|
-
depth: currentDepth + 1,
|
|
975
|
-
toolCallId: `orch-${toolCallId}`,
|
|
976
|
-
defaultPermission: toolEntry.defaultPermission,
|
|
977
|
-
},
|
|
978
|
-
userId: ctxSnapshot.userId,
|
|
979
|
-
});
|
|
980
|
-
const toolSpanId = toolSpan?.id ? String(toolSpan.id) : undefined;
|
|
981
|
-
setOrchestratorTraceContext(invokeCtx, {
|
|
982
|
-
rootRunId,
|
|
983
|
-
spanId: toolSpanId,
|
|
984
|
-
parentSpanId: executionSpanId,
|
|
985
|
-
toolCallId: `orch-${toolCallId}`,
|
|
986
|
-
leaderUsername,
|
|
987
|
-
employeeUsername: subAgentUsername,
|
|
988
|
-
toolName: toolEntry.definition.name,
|
|
989
|
-
});
|
|
990
|
-
|
|
991
|
-
trace.push({
|
|
992
|
-
type: 'tool_call',
|
|
993
|
-
at: nowIso(),
|
|
994
|
-
title: `Calling tool: ${toolEntry.definition.name}`,
|
|
995
|
-
toolName: toolEntry.definition.name,
|
|
996
|
-
args: toolArgs,
|
|
997
|
-
});
|
|
998
|
-
|
|
999
|
-
try {
|
|
1000
|
-
const res = await toolEntry.invoke(invokeCtx, toolArgs, `orch-${toolCallId}`);
|
|
1001
|
-
const output = truncateText(res?.content ?? res?.result ?? res, 50000);
|
|
1002
|
-
trace.push({
|
|
1003
|
-
type: 'tool_result',
|
|
1004
|
-
at: nowIso(),
|
|
1005
|
-
title: `Tool finished: ${toolEntry.definition.name}`,
|
|
1006
|
-
toolName: toolEntry.definition.name,
|
|
1007
|
-
status: res?.status || 'success',
|
|
1008
|
-
content: truncateText(output, 2000),
|
|
1009
|
-
});
|
|
1010
|
-
if (res?.status === 'error') {
|
|
1011
|
-
await spanService.finish(toolSpanId, 'error', toolStartedAt, {
|
|
1012
|
-
output,
|
|
1013
|
-
error: truncateText(res.content || output, 10000),
|
|
1014
|
-
});
|
|
1015
|
-
throw new Error(`Tool <${toolEntry.definition.name}> failed: ${res.content}`);
|
|
1016
|
-
}
|
|
1017
|
-
await spanService.finish(toolSpanId, 'success', toolStartedAt, {
|
|
1018
|
-
output,
|
|
1019
|
-
skillExecutionId: res?.result?.execId || res?.execId,
|
|
1020
|
-
});
|
|
1021
|
-
return typeof res?.content === 'string' ? res.content : JSON.stringify(res);
|
|
1022
|
-
} catch (e: any) {
|
|
1023
|
-
trace.push({
|
|
1024
|
-
type: 'tool_error',
|
|
1025
|
-
at: nowIso(),
|
|
1026
|
-
title: `Tool failed: ${toolEntry.definition.name}`,
|
|
1027
|
-
toolName: toolEntry.definition.name,
|
|
1028
|
-
status: 'error',
|
|
1029
|
-
content: e.message,
|
|
1030
|
-
});
|
|
1031
|
-
await spanService.finish(toolSpanId, 'error', toolStartedAt, {
|
|
1032
|
-
error: truncateText(e.message, 10000),
|
|
1033
|
-
});
|
|
1034
|
-
throw e;
|
|
1035
|
-
}
|
|
1036
|
-
},
|
|
1037
|
-
}),
|
|
1038
|
-
);
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
// --- Step 3: Build the agent ---
|
|
1042
|
-
const abortController = new AbortController();
|
|
1043
|
-
const executor = createReactAgent({
|
|
1044
|
-
llm: chatModel,
|
|
1045
|
-
tools: langchainTools,
|
|
1046
|
-
});
|
|
1047
|
-
|
|
1048
|
-
// --- Step 4: Construct messages ---
|
|
1049
|
-
let systemPrompt =
|
|
1050
|
-
subAgentEmployee.chatSettings?.systemPrompt ||
|
|
1051
|
-
subAgentEmployee.bio ||
|
|
1052
|
-
`You are an AI assistant named "${subAgentEmployee.nickname || subAgentUsername}". ${
|
|
1053
|
-
subAgentEmployee.about || ''
|
|
1054
|
-
}`;
|
|
1055
|
-
|
|
1056
|
-
// --- Step 4b: Inject shared context from Knowledge Base (soft dependency) ---
|
|
1057
|
-
// If plugin-knowledge-base is installed, inject the session context summary
|
|
1058
|
-
// so the sub-agent is aware of findings from previous agents in this run.
|
|
1059
|
-
try {
|
|
1060
|
-
const kbPlugin = ctx.app.pm.get('plugin-knowledge-base') as any;
|
|
1061
|
-
if (kbPlugin?.sessionContext) {
|
|
1062
|
-
const sessionId =
|
|
1063
|
-
ctx.action?.params?.values?.sessionId ||
|
|
1064
|
-
ctx.action?.params?.sessionId ||
|
|
1065
|
-
ctx.state?.sessionId;
|
|
1066
|
-
|
|
1067
|
-
const contextSummary = await kbPlugin.sessionContext.buildSummary(
|
|
1068
|
-
{ rootRunId, ...(sessionId ? { sessionId } : {}) },
|
|
1069
|
-
6000,
|
|
1070
|
-
);
|
|
1071
|
-
if (contextSummary) {
|
|
1072
|
-
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>`;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
} catch (e: any) {
|
|
1076
|
-
// Graceful fallback — never block delegation due to context injection failure.
|
|
1077
|
-
ctx.app.log?.debug?.(`[AgentOrchestrator] Shared context injection skipped: ${e.message}`);
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
const combinedTask = context ? `Task: ${task}\n\nContext Provided:\n${context}` : `Task: ${task}`;
|
|
1081
|
-
|
|
1082
|
-
// --- Step 5: Execute with timeout + abort ---
|
|
1083
|
-
// P3 FIX: AbortController signal cancels the in-flight stream on timeout,
|
|
1084
|
-
// preventing continued token consumption after the timeout fires.
|
|
1085
|
-
const effectiveRecursionLimit =
|
|
1086
|
-
Number.isFinite(recursionLimit) && (recursionLimit as number) > 0 ? (recursionLimit as number) : 50;
|
|
1087
|
-
const invokePromise = executeAgent(
|
|
1088
|
-
executor,
|
|
1089
|
-
systemPrompt,
|
|
1090
|
-
combinedTask,
|
|
1091
|
-
abortController.signal,
|
|
1092
|
-
effectiveRecursionLimit,
|
|
1093
|
-
);
|
|
1094
|
-
|
|
1095
|
-
const timeoutHandle = createTimeout(timeout, subAgentUsername, abortController);
|
|
1096
|
-
let result: AgentExecutionResult;
|
|
1097
|
-
try {
|
|
1098
|
-
result = (await Promise.race([invokePromise, timeoutHandle.promise])) as AgentExecutionResult;
|
|
1099
|
-
} finally {
|
|
1100
|
-
// Always release the timer so it doesn't keep the event loop alive.
|
|
1101
|
-
timeoutHandle.cancel();
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
const content = result.content || 'Sub-agent completed the task but produced no output.';
|
|
1105
|
-
trace.push({
|
|
1106
|
-
type: 'finish',
|
|
1107
|
-
at: nowIso(),
|
|
1108
|
-
title: `Delegation finished: ${subAgentUsername}`,
|
|
1109
|
-
status: 'success',
|
|
1110
|
-
content: truncateText(content, 2000),
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
// Log successful execution for tracing
|
|
1114
|
-
await logDelegation(ctx, plugin, {
|
|
1115
|
-
id: logRecord?.id,
|
|
1116
|
-
leaderUsername,
|
|
1117
|
-
subAgentUsername,
|
|
1118
|
-
toolName,
|
|
1119
|
-
task,
|
|
1120
|
-
context,
|
|
1121
|
-
result: content,
|
|
1122
|
-
status: 'success',
|
|
1123
|
-
depth: currentDepth,
|
|
1124
|
-
durationMs: Date.now() - startTime,
|
|
1125
|
-
trace,
|
|
1126
|
-
messages: result.messages,
|
|
1127
|
-
snapshot: ctxSnapshot,
|
|
1128
|
-
});
|
|
1129
|
-
await spanService.finish(executionSpanId, 'success', startTime, {
|
|
1130
|
-
output: content,
|
|
1131
|
-
metadata: {
|
|
1132
|
-
depth: currentDepth,
|
|
1133
|
-
maxDepth,
|
|
1134
|
-
toolName,
|
|
1135
|
-
recursionLimit,
|
|
1136
|
-
messages: result.messages,
|
|
1137
|
-
traceCount: trace.length,
|
|
1138
|
-
},
|
|
1139
|
-
});
|
|
1140
|
-
|
|
1141
|
-
return {
|
|
1142
|
-
status: 'success' as const,
|
|
1143
|
-
content,
|
|
1144
|
-
};
|
|
1145
|
-
} catch (e) {
|
|
1146
|
-
plugin.app.log.error(`[AgentOrchestrator] Sub-agent ${subAgentUsername} failed`, e);
|
|
1147
|
-
|
|
1148
|
-
// Log failed execution for tracing
|
|
820
|
+
if (currentDepth >= maxDepth) {
|
|
1149
821
|
await logDelegation(ctx, plugin, {
|
|
1150
|
-
id: logRecord?.id,
|
|
1151
822
|
leaderUsername,
|
|
1152
823
|
subAgentUsername,
|
|
1153
824
|
toolName,
|
|
@@ -1156,38 +827,43 @@ async function invokeDelegateTask(
|
|
|
1156
827
|
result: '',
|
|
1157
828
|
status: 'error',
|
|
1158
829
|
depth: currentDepth,
|
|
1159
|
-
durationMs:
|
|
1160
|
-
error:
|
|
1161
|
-
trace: [
|
|
1162
|
-
...trace,
|
|
1163
|
-
{
|
|
1164
|
-
type: 'error',
|
|
1165
|
-
at: nowIso(),
|
|
1166
|
-
title: `Delegation failed: ${subAgentUsername}`,
|
|
1167
|
-
status: 'error',
|
|
1168
|
-
content: e.message,
|
|
1169
|
-
},
|
|
1170
|
-
],
|
|
830
|
+
durationMs: 0,
|
|
831
|
+
error: `Delegation depth limit reached (${currentDepth}/${maxDepth}).`,
|
|
1171
832
|
snapshot: ctxSnapshot,
|
|
1172
|
-
}).catch((logErr) => {
|
|
1173
|
-
plugin.app.log.warn('[AgentOrchestrator] Failed to save error log for delegation', logErr);
|
|
1174
|
-
});
|
|
1175
|
-
await spanService.finish(executionSpanId, 'error', startTime, {
|
|
1176
|
-
error: truncateText(e.message, 10000),
|
|
1177
|
-
metadata: {
|
|
1178
|
-
depth: currentDepth,
|
|
1179
|
-
maxDepth,
|
|
1180
|
-
toolName,
|
|
1181
|
-
recursionLimit,
|
|
1182
|
-
traceCount: trace.length + 1,
|
|
1183
|
-
},
|
|
1184
833
|
});
|
|
1185
|
-
|
|
1186
834
|
return {
|
|
1187
835
|
status: 'error' as const,
|
|
1188
|
-
content: `Sub-agent "${subAgentUsername}"
|
|
836
|
+
content: `Delegation depth limit reached (${currentDepth}/${maxDepth}). Sub-agent "${subAgentUsername}" cannot delegate further.`,
|
|
1189
837
|
};
|
|
1190
838
|
}
|
|
839
|
+
|
|
840
|
+
const upstreamTraceContext = getOrchestratorTraceContext(ctx);
|
|
841
|
+
const rootRunId =
|
|
842
|
+
providedRootRunId || upstreamTraceContext?.rootRunId || createRootRunId(`${leaderUsername}:${subAgentUsername}`);
|
|
843
|
+
const parentSpanId = providedParentSpanId || upstreamTraceContext?.spanId || upstreamTraceContext?.parentSpanId;
|
|
844
|
+
const agentLoopRunId = upstreamTraceContext?.agentLoopRunId;
|
|
845
|
+
const agentLoopStepId = upstreamTraceContext?.agentLoopStepId;
|
|
846
|
+
|
|
847
|
+
return plugin.agentLoopService.harness.runSubAgent(ctx, {
|
|
848
|
+
leaderUsername,
|
|
849
|
+
subAgentUsername,
|
|
850
|
+
subAgentEmployee,
|
|
851
|
+
task,
|
|
852
|
+
context,
|
|
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,
|
|
866
|
+
});
|
|
1191
867
|
}
|
|
1192
868
|
|
|
1193
869
|
/**
|
|
@@ -1220,15 +896,12 @@ async function logDelegation(
|
|
|
1220
896
|
return;
|
|
1221
897
|
}
|
|
1222
898
|
|
|
1223
|
-
// Prefer the early snapshot captured in invokeDelegateTask — by the time
|
|
1224
|
-
// the agent finishes, ctx may already be disposed. Fall back to ctx for
|
|
1225
|
-
// call sites that don't pass a snapshot (e.g. authz-failure short-circuit).
|
|
1226
899
|
let userId: number | undefined = data.snapshot?.userId;
|
|
1227
900
|
if (userId == null) {
|
|
1228
901
|
try {
|
|
1229
902
|
userId = ctx.auth?.user?.id || ctx.state?.currentUser?.id;
|
|
1230
903
|
} catch {
|
|
1231
|
-
// ctx lifecycle ended
|
|
904
|
+
// ctx lifecycle ended
|
|
1232
905
|
}
|
|
1233
906
|
}
|
|
1234
907
|
|
|
@@ -1268,91 +941,3 @@ async function logDelegation(
|
|
|
1268
941
|
plugin.app.log.warn('[AgentOrchestrator] Failed to log delegation event', e);
|
|
1269
942
|
}
|
|
1270
943
|
}
|
|
1271
|
-
|
|
1272
|
-
/**
|
|
1273
|
-
* Execute the agent and extract the final AI message content.
|
|
1274
|
-
* Uses executor.invoke to get the final state cleanly, avoiding chunk parsing issues.
|
|
1275
|
-
* Accepts an AbortSignal so the execution can be cancelled on timeout.
|
|
1276
|
-
*/
|
|
1277
|
-
async function executeAgent(
|
|
1278
|
-
executor: any,
|
|
1279
|
-
systemPrompt: string,
|
|
1280
|
-
task: string,
|
|
1281
|
-
signal?: AbortSignal,
|
|
1282
|
-
recursionLimit = 50,
|
|
1283
|
-
): Promise<AgentExecutionResult> {
|
|
1284
|
-
const config: any = { recursionLimit };
|
|
1285
|
-
if (signal) {
|
|
1286
|
-
config.signal = signal;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
const finalState = await executor.invoke(
|
|
1290
|
-
{
|
|
1291
|
-
messages: [new SystemMessage(systemPrompt), new HumanMessage(task)],
|
|
1292
|
-
},
|
|
1293
|
-
config,
|
|
1294
|
-
);
|
|
1295
|
-
|
|
1296
|
-
// finalState.messages contains the entire conversation history of this delegation
|
|
1297
|
-
const messages = finalState?.messages || [];
|
|
1298
|
-
|
|
1299
|
-
// Find the last AI message in the chain
|
|
1300
|
-
const lastAIMessage = [...messages].reverse().find((m) => m.getType() === 'ai');
|
|
1301
|
-
|
|
1302
|
-
if (!lastAIMessage || !lastAIMessage.content) {
|
|
1303
|
-
return { content: '', messages: serializeMessages(messages) };
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
let content = '';
|
|
1307
|
-
if (typeof lastAIMessage.content === 'string') {
|
|
1308
|
-
content = lastAIMessage.content;
|
|
1309
|
-
} else if (Array.isArray(lastAIMessage.content)) {
|
|
1310
|
-
content = lastAIMessage.content.map((c: any) => c.text || JSON.stringify(c)).join('\n');
|
|
1311
|
-
} else {
|
|
1312
|
-
content = String(lastAIMessage.content);
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
return { content, messages: serializeMessages(messages) };
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
function serializeMessages(messages: any[]) {
|
|
1319
|
-
return (messages || []).map((message, index) => {
|
|
1320
|
-
const type = typeof message.getType === 'function' ? message.getType() : message.type;
|
|
1321
|
-
return {
|
|
1322
|
-
index,
|
|
1323
|
-
type,
|
|
1324
|
-
name: message.name,
|
|
1325
|
-
content: truncateText(message.content, 10000),
|
|
1326
|
-
toolCalls: message.tool_calls || message.toolCalls || [],
|
|
1327
|
-
toolCallId: message.tool_call_id,
|
|
1328
|
-
additionalKwargs: message.additional_kwargs,
|
|
1329
|
-
responseMetadata: message.response_metadata,
|
|
1330
|
-
};
|
|
1331
|
-
});
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
/**
|
|
1335
|
-
* Schedule a rejection-on-timeout that also aborts the in-flight stream.
|
|
1336
|
-
* Returns the promise plus a `cancel()` so callers can release the timer
|
|
1337
|
-
* when the race resolves successfully (otherwise the handle keeps the event
|
|
1338
|
-
* loop alive until `ms` elapses).
|
|
1339
|
-
*/
|
|
1340
|
-
function createTimeout(
|
|
1341
|
-
ms: number,
|
|
1342
|
-
agentName: string,
|
|
1343
|
-
abortController?: AbortController,
|
|
1344
|
-
): { promise: Promise<never>; cancel: () => void } {
|
|
1345
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1346
|
-
const promise = new Promise<never>((_resolve, reject) => {
|
|
1347
|
-
timer = setTimeout(() => {
|
|
1348
|
-
abortController?.abort();
|
|
1349
|
-
reject(new Error(`Sub-agent "${agentName}" timed out after ${ms / 1000}s`));
|
|
1350
|
-
}, ms);
|
|
1351
|
-
});
|
|
1352
|
-
return {
|
|
1353
|
-
promise,
|
|
1354
|
-
cancel: () => {
|
|
1355
|
-
if (timer) clearTimeout(timer);
|
|
1356
|
-
},
|
|
1357
|
-
};
|
|
1358
|
-
}
|