plugin-agent-orchestrator 1.0.28 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/client/index.js +1 -1
- package/dist/client-v2/{214.723affb37c13bf7a.js → 214.79650a549273f163.js} +1 -1
- package/dist/client-v2/264.718a107e43fc163c.js +10 -0
- package/dist/client-v2/373.f5d5292e53c4e832.js +10 -0
- package/dist/client-v2/{41.1805b2edfaa4afe2.js → 41.ba6e080cc0488143.js} +1 -1
- package/dist/client-v2/418.29e713f79131eece.js +10 -0
- package/dist/client-v2/619.bd3c5698b40705c3.js +10 -0
- package/dist/client-v2/677.a991ce0250ff5c77.js +10 -0
- package/dist/client-v2/{70.a15d7fcec7c41768.js → 70.bda9518881c05360.js} +1 -1
- package/dist/client-v2/925.f5370de8f6632d65.js +10 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +7 -10
- package/dist/locale/en-US.json +94 -25
- package/dist/locale/vi-VN.json +94 -25
- package/dist/locale/zh-CN.json +94 -25
- package/dist/server/collections/agent-execution-spans.js +37 -0
- package/dist/server/collections/agent-harness-profiles.js +2 -2
- package/dist/server/collections/agent-memory-contexts.js +125 -0
- package/dist/server/collections/orchestrator-logs.js +2 -2
- package/dist/server/migrations/20260425000000-add-interaction-schema.js +3 -1
- package/dist/server/migrations/20260427000000-change-packages-to-text.js +3 -1
- package/dist/server/migrations/20260427000001-change-other-json-to-text.js +6 -2
- package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +21 -19
- package/dist/server/migrations/20260621000000-native-policy-profile-defaults.js +193 -0
- package/dist/server/plugin.js +128 -74
- package/dist/server/resources/agent-monitor.js +454 -0
- package/dist/server/services/AgentHarness.js +24 -499
- package/dist/server/services/AgentMemoryContextService.js +216 -0
- package/dist/server/services/ExecutionSpanService.js +2 -2
- package/dist/server/services/NativeSubAgentObserver.js +413 -0
- package/dist/server/skill-hub/plugin.js +81 -5
- package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
- package/dist/server/tools/delegate-task.js +11 -589
- package/dist/server/utils/skill-settings.js +18 -1
- package/package.json +47 -49
- package/src/client/AIEmployeesContext.tsx +5 -18
- package/src/client/AgentRunsTab.tsx +2 -771
- package/src/client/HarnessProfilesTab.tsx +2 -257
- package/src/client/OrchestratorSettings.tsx +97 -106
- package/src/client/RulesTab.tsx +2 -788
- package/src/client/plugin.tsx +0 -2
- package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
- package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
- package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
- package/src/client/skill-hub/components/SkillManager.tsx +194 -181
- package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
- package/src/client/skill-hub/locale.ts +16 -16
- package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
- package/src/client/skill-hub/utils/jsonFields.ts +7 -3
- package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
- package/src/client-v2/components/AgentRunsTab.tsx +182 -455
- package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
- package/src/client-v2/components/RulesTab.tsx +2 -782
- package/src/client-v2/components/TracingTab.tsx +1 -1
- package/src/client-v2/hooks/useApiRequest.ts +8 -1
- package/src/client-v2/pages/RulesPage.tsx +2 -2
- package/src/client-v2/plugin.tsx +3 -3
- package/src/locale/en-US.json +94 -25
- package/src/locale/vi-VN.json +94 -25
- package/src/locale/zh-CN.json +94 -25
- package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
- package/src/server/__tests__/skill-settings.test.ts +6 -6
- package/src/server/__tests__/smoke.test.ts +1 -0
- package/src/server/collections/agent-execution-spans.ts +37 -0
- package/src/server/collections/agent-harness-profiles.ts +59 -59
- package/src/server/collections/agent-loop-events.ts +71 -71
- package/src/server/collections/agent-loop-steps.ts +144 -144
- package/src/server/collections/agent-memory-contexts.ts +95 -0
- package/src/server/collections/orchestrator-logs.ts +4 -4
- package/src/server/collections/skill-definitions.ts +111 -111
- package/src/server/collections/skill-executions.ts +106 -106
- package/src/server/collections/skill-loop-configs.ts +65 -65
- package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
- package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
- package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
- package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
- package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
- package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
- package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
- package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
- package/src/server/plugin.ts +151 -94
- package/src/server/resources/agent-monitor.ts +482 -0
- package/src/server/services/AgentHarness.ts +38 -623
- package/src/server/services/AgentMemoryContextService.ts +256 -0
- package/src/server/services/AgentPlanValidator.ts +73 -73
- package/src/server/services/ExecutionSpanService.ts +6 -2
- package/src/server/services/FileManager.ts +144 -144
- package/src/server/services/NativeSubAgentObserver.ts +507 -0
- package/src/server/services/SkillManager.ts +583 -583
- package/src/server/services/SkillRepositoryService.ts +5 -7
- package/src/server/services/TokenTracker.ts +3 -3
- package/src/server/services/WorkerEnvManager.ts +1 -2
- package/src/server/skill-hub/actions/git-import.ts +5 -7
- package/src/server/skill-hub/plugin.ts +89 -6
- package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
- package/src/server/skill-hub/utils/json-fields.ts +1 -1
- package/src/server/tools/delegate-task.ts +13 -847
- package/src/server/utils/skill-settings.ts +24 -6
- package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
- package/dist/client-v2/418.5ae055abf141820e.js +0 -10
- package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
- package/dist/client-v2/892.72db4161511c8a16.js +0 -10
- package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
- package/src/client/tools/PlanApprovalCard.tsx +0 -176
- package/src/client/tools/registerOrchestratorCards.ts +0 -17
|
@@ -1,652 +1,67 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { createHash } from 'crypto';
|
|
3
|
-
import { createReactAgent } from '@langchain/langgraph/prebuilt';
|
|
4
|
-
import { DynamicStructuredTool } from '@langchain/core/tools';
|
|
5
|
-
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
6
|
-
import { ExecutionSpanService, setOrchestratorTraceContext } from './ExecutionSpanService';
|
|
7
1
|
import { AgentRegistryService } from './AgentRegistryService';
|
|
8
|
-
import { TokenTracker
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import type { ToolsRuntime } from '@nocobase/ai';
|
|
17
|
-
|
|
18
|
-
const ORCHESTRATOR_DEPTH_KEY = '__orchestratorDepth';
|
|
19
|
-
const ORCHESTRATOR_PATH_KEY = '__orchestratorPath';
|
|
20
|
-
|
|
21
|
-
function sanitizeToolPart(value: string) {
|
|
22
|
-
return (value || '').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
2
|
+
import { TokenTracker } from './TokenTracker';
|
|
3
|
+
import { asObject } from '../utils/ctx-utils';
|
|
4
|
+
|
|
5
|
+
function retiredExecutionError(stepType?: string) {
|
|
6
|
+
return new Error(
|
|
7
|
+
`Legacy AgentHarness ${stepType || 'agent'} execution is retired. ` +
|
|
8
|
+
'Use @nocobase/plugin-ai native AIEmployee/SubAgentsDispatcher flow for new sub-agent runs.',
|
|
9
|
+
);
|
|
23
10
|
}
|
|
24
11
|
|
|
25
|
-
function
|
|
26
|
-
return
|
|
12
|
+
function harnessTagFrom(run: unknown, step: unknown) {
|
|
13
|
+
return (
|
|
14
|
+
asObject((run as { metadata?: unknown })?.metadata).harnessTag ||
|
|
15
|
+
asObject((step as { metadata?: unknown })?.metadata).harnessTag ||
|
|
16
|
+
'default'
|
|
17
|
+
);
|
|
27
18
|
}
|
|
28
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Compatibility shim for historical agent loop data.
|
|
22
|
+
*
|
|
23
|
+
* Runtime execution moved to @nocobase/plugin-ai native SubAgentsDispatcher.
|
|
24
|
+
* Keep this class so legacy services/tests can still instantiate AgentLoopService
|
|
25
|
+
* without pulling LangChain 0.x into plugin-agent-orchestrator.
|
|
26
|
+
*/
|
|
29
27
|
export class AgentHarness {
|
|
30
|
-
private readonly spanService: ExecutionSpanService;
|
|
31
|
-
private readonly tokenTracker: TokenTracker;
|
|
32
|
-
private readonly contextAggregator: ContextAggregator;
|
|
33
|
-
|
|
34
28
|
constructor(
|
|
35
|
-
private readonly plugin:
|
|
29
|
+
private readonly plugin: unknown,
|
|
36
30
|
private readonly registryService: AgentRegistryService,
|
|
37
|
-
tokenTracker?: TokenTracker,
|
|
38
|
-
) {
|
|
39
|
-
this.spanService = new ExecutionSpanService(plugin);
|
|
40
|
-
this.tokenTracker = tokenTracker || new TokenTracker(plugin);
|
|
41
|
-
this.contextAggregator = new ContextAggregator(plugin);
|
|
42
|
-
}
|
|
31
|
+
private readonly tokenTracker?: TokenTracker,
|
|
32
|
+
) {}
|
|
43
33
|
|
|
44
34
|
get db() {
|
|
45
|
-
return this.plugin
|
|
35
|
+
return (this.plugin as { db?: unknown })?.db;
|
|
46
36
|
}
|
|
47
37
|
|
|
48
38
|
get app() {
|
|
49
|
-
return this.plugin
|
|
39
|
+
return (this.plugin as { app?: unknown })?.app;
|
|
50
40
|
}
|
|
51
41
|
|
|
52
|
-
async executeStep(run:
|
|
53
|
-
const harnessTag =
|
|
54
|
-
const profile = await this.registryService.getHarnessProfile(harnessTag);
|
|
55
|
-
const settings = asObject(profile?.settings);
|
|
56
|
-
|
|
57
|
-
if (step.type === 'sub_agent') {
|
|
58
|
-
if (settings.allowSubAgents === false) {
|
|
59
|
-
throw new Error(`Harness profile "${harnessTag}" does not allow sub-agent execution.`);
|
|
60
|
-
}
|
|
61
|
-
return this.invokeSubAgentStep(run, step, settings, options);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if ((step.type === 'tool' || step.type === 'skill') && step.target) {
|
|
65
|
-
if (settings.allowToolCalls === false) {
|
|
66
|
-
throw new Error(`Harness profile "${harnessTag}" does not allow tool/skill execution.`);
|
|
67
|
-
}
|
|
68
|
-
return this.invokeNamedTool(run, step, step.target, step.input || {}, settings, options);
|
|
69
|
-
}
|
|
42
|
+
async executeStep(run: unknown, step: any, _options: { userId?: string | number; ctx?: unknown } = {}) {
|
|
43
|
+
const harnessTag = harnessTagFrom(run, step);
|
|
70
44
|
|
|
71
|
-
if (step
|
|
45
|
+
if (step?.type === 'verification') {
|
|
72
46
|
return {
|
|
73
47
|
passed: true,
|
|
74
|
-
summary: 'Verification completed by orchestrator
|
|
48
|
+
summary: 'Verification completed by legacy orchestrator compatibility shim.',
|
|
75
49
|
checkedDependencies: Array.isArray(step.dependsOn) ? step.dependsOn.map(String) : [],
|
|
76
50
|
harnessTag,
|
|
51
|
+
retired: true,
|
|
77
52
|
};
|
|
78
53
|
}
|
|
79
54
|
|
|
80
|
-
|
|
81
|
-
summary: `${step.title || step.planKey} completed by orchestrator controller.`,
|
|
82
|
-
description: step.description || '',
|
|
83
|
-
input: step.input || {},
|
|
84
|
-
harnessTag,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private async invokeSubAgentStep(
|
|
89
|
-
run: any,
|
|
90
|
-
step: any,
|
|
91
|
-
settings: any,
|
|
92
|
-
options: { userId?: string | number; ctx?: any },
|
|
93
|
-
) {
|
|
94
|
-
const target = step.target || asObject(step.metadata).subAgentUsername || step.input?.subAgentUsername;
|
|
95
|
-
if (!target) {
|
|
96
|
-
throw new Error(`Sub-agent step "${step.planKey}" is missing target sub-agent username.`);
|
|
97
|
-
}
|
|
98
|
-
if (!run.leaderUsername) {
|
|
99
|
-
throw new Error(`Sub-agent step "${step.planKey}" requires a leader AI employee username.`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const toolName = String(target).startsWith('delegate_')
|
|
103
|
-
? String(target)
|
|
104
|
-
: buildDelegateToolName(run.leaderUsername, target);
|
|
105
|
-
|
|
106
|
-
const task = step.description || step.title || run.goal;
|
|
107
|
-
const context = trimText(
|
|
108
|
-
{
|
|
109
|
-
goal: run.goal,
|
|
110
|
-
input: step.input || {},
|
|
111
|
-
harnessTag: asObject(run.metadata).harnessTag || asObject(step.metadata).harnessTag || 'default',
|
|
112
|
-
agentLoopRunId: String(run.id),
|
|
113
|
-
agentLoopStepId: String(step.id),
|
|
114
|
-
},
|
|
115
|
-
20000,
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
const circuitBreaker = getCircuitBreaker({ appLog: this.app });
|
|
119
|
-
const circuitKey = subAgentCircuitKey(run.leaderUsername, target);
|
|
120
|
-
|
|
121
|
-
// ── Circuit breaker check before invoking sub-agent ──────────────
|
|
122
|
-
if (!circuitBreaker.isAllowed(circuitKey)) {
|
|
123
|
-
const state = circuitBreaker.getState(circuitKey);
|
|
124
|
-
throw new Error(
|
|
125
|
-
`Sub-agent "${target}" circuit is open (${state?.failures || 0} failures). Retry after the recovery timeout.`,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
const result = await this.invokeNamedTool(run, step, toolName, { task, context }, settings, options);
|
|
131
|
-
circuitBreaker.recordSuccess(circuitKey);
|
|
132
|
-
return result;
|
|
133
|
-
} catch (e: any) {
|
|
134
|
-
// Don't count approval pauses as circuit failures
|
|
135
|
-
if (e?.message !== 'requires_approval') {
|
|
136
|
-
circuitBreaker.recordFailure(circuitKey);
|
|
137
|
-
}
|
|
138
|
-
throw e;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private async invokeNamedTool(
|
|
143
|
-
run: any,
|
|
144
|
-
step: any,
|
|
145
|
-
toolName: string,
|
|
146
|
-
args: any,
|
|
147
|
-
settings: any,
|
|
148
|
-
options: { userId?: string | number; ctx?: any },
|
|
149
|
-
) {
|
|
150
|
-
if (String(toolName).startsWith('orchestrator_')) {
|
|
151
|
-
throw new Error(`Tool step "${step.planKey}" cannot call internal orchestrator control tool "${toolName}".`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (Array.isArray(settings.allowTools) && settings.allowTools.length > 0) {
|
|
155
|
-
if (!settings.allowTools.includes(toolName)) {
|
|
156
|
-
throw new Error(`Tool "${toolName}" is not on the allowed list for harness profile.`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (Array.isArray(settings.denyTools) && settings.denyTools.length > 0) {
|
|
160
|
-
if (settings.denyTools.includes(toolName)) {
|
|
161
|
-
throw new Error(`Tool "${toolName}" is on the denied list for harness profile.`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const toolsManager = tryGetAIToolsManager(this.app);
|
|
166
|
-
const tool = await toolsManager?.getTools?.(toolName);
|
|
167
|
-
if (!tool?.invoke) {
|
|
168
|
-
throw new Error(`Tool "${toolName}" was not found or is missing standard invoke handler.`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const isDelegationTool = await this.registryService.isRegisteredDelegationTool(toolName);
|
|
172
|
-
const isStepAlreadyApproved = step?.approval?.approved === true;
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
!isDelegationTool &&
|
|
176
|
-
!isStepAlreadyApproved &&
|
|
177
|
-
(tool.defaultPermission === 'ASK' ||
|
|
178
|
-
(settings.requireApprovalRiskLevel && tool.riskLevel && tool.riskLevel >= settings.requireApprovalRiskLevel))
|
|
179
|
-
) {
|
|
180
|
-
throw new Error('requires_approval');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const ctx = options.ctx || {};
|
|
184
|
-
const previousEmployee = ctx._currentAIEmployee;
|
|
185
|
-
const previousStateEmployee = ctx.state?.currentAIEmployee;
|
|
186
|
-
|
|
187
|
-
if (run.leaderUsername) {
|
|
188
|
-
ctx._currentAIEmployee = run.leaderUsername;
|
|
189
|
-
ctx.state = { ...(ctx.state || {}), currentAIEmployee: run.leaderUsername };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const previousRuntime = ctx.runtime;
|
|
193
|
-
const toolCallId = `agent-loop-${run.id}-${step.id}`;
|
|
194
|
-
const runtime: ToolsRuntime = {
|
|
195
|
-
toolCallId,
|
|
196
|
-
writer: (chunk: any) => {
|
|
197
|
-
this.app.log.trace(`[AgentHarness] Tool output:`, chunk);
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
ctx.runtime = runtime;
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
const result = await tool.invoke(ctx, args, runtime);
|
|
204
|
-
if (result?.status === 'error') {
|
|
205
|
-
throw new Error(result.content || `Tool "${toolName}" returned an error.`);
|
|
206
|
-
}
|
|
55
|
+
if (!step?.target && (step?.type === 'reasoning' || !step?.type)) {
|
|
207
56
|
return {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (previousRuntime === undefined) {
|
|
214
|
-
delete ctx.runtime;
|
|
215
|
-
} else {
|
|
216
|
-
ctx.runtime = previousRuntime;
|
|
217
|
-
}
|
|
218
|
-
if (previousEmployee === undefined) {
|
|
219
|
-
delete ctx._currentAIEmployee;
|
|
220
|
-
} else {
|
|
221
|
-
ctx._currentAIEmployee = previousEmployee;
|
|
222
|
-
}
|
|
223
|
-
if (ctx.state) {
|
|
224
|
-
if (previousStateEmployee === undefined) {
|
|
225
|
-
delete ctx.state.currentAIEmployee;
|
|
226
|
-
} else {
|
|
227
|
-
ctx.state.currentAIEmployee = previousStateEmployee;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async runSubAgent(
|
|
234
|
-
ctx: any,
|
|
235
|
-
options: {
|
|
236
|
-
leaderUsername: string;
|
|
237
|
-
subAgentUsername: string;
|
|
238
|
-
subAgentEmployee: any;
|
|
239
|
-
task: string;
|
|
240
|
-
context?: string;
|
|
241
|
-
currentDepth?: number;
|
|
242
|
-
currentPath?: string[];
|
|
243
|
-
maxDepth: number;
|
|
244
|
-
timeout: number;
|
|
245
|
-
toolCallId: string;
|
|
246
|
-
toolName: string;
|
|
247
|
-
llmService?: string;
|
|
248
|
-
model?: string;
|
|
249
|
-
recursionLimit?: number;
|
|
250
|
-
rootRunId?: string;
|
|
251
|
-
parentSpanId?: string;
|
|
252
|
-
agentLoopRunId?: string;
|
|
253
|
-
agentLoopStepId?: string;
|
|
254
|
-
},
|
|
255
|
-
) {
|
|
256
|
-
const {
|
|
257
|
-
leaderUsername,
|
|
258
|
-
subAgentUsername,
|
|
259
|
-
subAgentEmployee,
|
|
260
|
-
task,
|
|
261
|
-
context,
|
|
262
|
-
maxDepth,
|
|
263
|
-
timeout,
|
|
264
|
-
toolCallId,
|
|
265
|
-
toolName,
|
|
266
|
-
llmService,
|
|
267
|
-
model,
|
|
268
|
-
recursionLimit,
|
|
269
|
-
rootRunId,
|
|
270
|
-
parentSpanId,
|
|
271
|
-
agentLoopRunId,
|
|
272
|
-
agentLoopStepId,
|
|
273
|
-
} = options;
|
|
274
|
-
const effectiveRootRunId = rootRunId || `run_${Date.now()}`;
|
|
275
|
-
|
|
276
|
-
const startTime = Date.now();
|
|
277
|
-
const currentDepth = options.currentDepth ?? 0;
|
|
278
|
-
const currentPath = options.currentPath ?? [leaderUsername];
|
|
279
|
-
const trace: TraceEvent[] = [
|
|
280
|
-
{
|
|
281
|
-
type: 'start',
|
|
282
|
-
at: nowIso(),
|
|
283
|
-
title: `Delegation started: ${leaderUsername} -> ${subAgentUsername}`,
|
|
284
|
-
content: task,
|
|
285
|
-
},
|
|
286
|
-
];
|
|
287
|
-
|
|
288
|
-
const executionSpan = await this.spanService.create({
|
|
289
|
-
rootRunId: effectiveRootRunId,
|
|
290
|
-
parentSpanId,
|
|
291
|
-
type: 'sub_agent',
|
|
292
|
-
status: 'running',
|
|
293
|
-
leaderUsername,
|
|
294
|
-
employeeUsername: subAgentUsername,
|
|
295
|
-
title: `Delegation: ${leaderUsername} -> ${subAgentUsername}`,
|
|
296
|
-
input: { task, context },
|
|
297
|
-
metadata: {
|
|
298
|
-
depth: maxDepth,
|
|
299
|
-
toolName,
|
|
300
|
-
recursionLimit,
|
|
301
|
-
agentLoopRunId,
|
|
302
|
-
agentLoopStepId,
|
|
303
|
-
},
|
|
304
|
-
userId: ctx?.auth?.user?.id || ctx?.state?.currentUser?.id,
|
|
305
|
-
});
|
|
306
|
-
const executionSpanId = executionSpan?.id ? String(executionSpan.id) : undefined;
|
|
307
|
-
|
|
308
|
-
const logRecord = await this.logDelegation(ctx, {
|
|
309
|
-
leaderUsername,
|
|
310
|
-
subAgentUsername,
|
|
311
|
-
toolName,
|
|
312
|
-
task,
|
|
313
|
-
context,
|
|
314
|
-
result: '',
|
|
315
|
-
status: 'running',
|
|
316
|
-
depth: maxDepth,
|
|
317
|
-
durationMs: 0,
|
|
318
|
-
trace,
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
if (executionSpanId && logRecord?.id) {
|
|
322
|
-
await this.spanService.update(executionSpanId, { orchestratorLogId: logRecord.id });
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
try {
|
|
326
|
-
const aiPlugin = ctx.app.pm.get('ai');
|
|
327
|
-
if (!aiPlugin) {
|
|
328
|
-
throw new Error('Plugin AI is not enabled.');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const modelSettings = await this.registryService.resolveModelSettings(
|
|
332
|
-
subAgentUsername,
|
|
333
|
-
leaderUsername,
|
|
334
|
-
llmService && model ? { llmService, model } : undefined,
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
if (!modelSettings) {
|
|
338
|
-
throw new Error(`Sub-agent "${subAgentUsername}" has no LLM model configured.`);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const { provider } = await aiPlugin.aiManager.getLLMService({
|
|
342
|
-
llmService: modelSettings.llmService,
|
|
343
|
-
model: modelSettings.model,
|
|
344
|
-
});
|
|
345
|
-
const chatModel = provider.createModel();
|
|
346
|
-
|
|
347
|
-
const coreToolsManager = getAIToolsManager(ctx.app);
|
|
348
|
-
const allTools = await coreToolsManager.listTools();
|
|
349
|
-
|
|
350
|
-
const normalizedSkillSettings = normalizeAIEmployeeSkillSettings(subAgentEmployee.skillSettings).skillSettings;
|
|
351
|
-
const employeeTools = normalizedSkillSettings.tools.filter((tool) => Boolean(tool.name));
|
|
352
|
-
const employeeToolMap = new Map(employeeTools.map((tool) => [tool.name, tool]));
|
|
353
|
-
|
|
354
|
-
const langchainTools: DynamicStructuredTool[] = [];
|
|
355
|
-
|
|
356
|
-
for (const toolEntry of allTools) {
|
|
357
|
-
const entryName = toolEntry.definition.name;
|
|
358
|
-
if (!entryName) continue;
|
|
359
|
-
const employeeTool = employeeToolMap.get(entryName);
|
|
360
|
-
|
|
361
|
-
if (!employeeTool || employeeTool.autoCall !== true || toolEntry.execution === 'frontend') {
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
langchainTools.push(
|
|
366
|
-
new DynamicStructuredTool({
|
|
367
|
-
name: entryName.replace(/[^a-zA-Z0-9_-]/g, '_'),
|
|
368
|
-
description: toolEntry.definition.description || entryName,
|
|
369
|
-
schema: (toolEntry.definition.schema || z.object({})) as any,
|
|
370
|
-
func: async (toolArgs) => {
|
|
371
|
-
const invokeCtx = Object.create(ctx);
|
|
372
|
-
invokeCtx._currentAIEmployee = subAgentUsername;
|
|
373
|
-
invokeCtx[ORCHESTRATOR_DEPTH_KEY] = currentDepth + 1;
|
|
374
|
-
invokeCtx[ORCHESTRATOR_PATH_KEY] = [...currentPath, subAgentUsername];
|
|
375
|
-
if (ctx.state) {
|
|
376
|
-
invokeCtx.state = Object.create(ctx.state);
|
|
377
|
-
invokeCtx.state.currentAIEmployee = subAgentUsername;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const toolStartedAt = Date.now();
|
|
381
|
-
const isSkillHubTool = entryName === 'skill_hub_execute' || entryName.startsWith('skill_hub_');
|
|
382
|
-
const toolSpan = await this.spanService.create({
|
|
383
|
-
rootRunId: effectiveRootRunId,
|
|
384
|
-
parentSpanId: executionSpanId,
|
|
385
|
-
type: isSkillHubTool ? 'skill' : 'tool',
|
|
386
|
-
status: 'running',
|
|
387
|
-
leaderUsername,
|
|
388
|
-
employeeUsername: subAgentUsername,
|
|
389
|
-
toolName: toolEntry.definition.name,
|
|
390
|
-
title: isSkillHubTool ? `Skill: ${toolEntry.definition.name}` : `Tool: ${toolEntry.definition.name}`,
|
|
391
|
-
input: toolArgs,
|
|
392
|
-
metadata: {
|
|
393
|
-
depth: currentDepth + 1,
|
|
394
|
-
toolCallId: `orch-${toolCallId}`,
|
|
395
|
-
agentLoopRunId,
|
|
396
|
-
agentLoopStepId,
|
|
397
|
-
},
|
|
398
|
-
userId: ctx?.auth?.user?.id || ctx?.state?.currentUser?.id,
|
|
399
|
-
});
|
|
400
|
-
const toolSpanId = toolSpan?.id ? String(toolSpan.id) : undefined;
|
|
401
|
-
setOrchestratorTraceContext(invokeCtx, {
|
|
402
|
-
rootRunId: effectiveRootRunId,
|
|
403
|
-
spanId: toolSpanId,
|
|
404
|
-
parentSpanId: executionSpanId,
|
|
405
|
-
toolCallId: `orch-${toolCallId}`,
|
|
406
|
-
leaderUsername,
|
|
407
|
-
employeeUsername: subAgentUsername,
|
|
408
|
-
toolName: toolEntry.definition.name,
|
|
409
|
-
agentLoopRunId,
|
|
410
|
-
agentLoopStepId,
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
trace.push({
|
|
414
|
-
type: 'tool_call',
|
|
415
|
-
at: nowIso(),
|
|
416
|
-
title: `Calling tool: ${toolEntry.definition.name}`,
|
|
417
|
-
toolName: toolEntry.definition.name,
|
|
418
|
-
args: toolArgs,
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
const subToolCallId = `orch-${toolCallId}`;
|
|
422
|
-
const runtime: ToolsRuntime = {
|
|
423
|
-
toolCallId: subToolCallId,
|
|
424
|
-
writer: (chunk: any) => {
|
|
425
|
-
this.app.log.trace(`[AgentHarness] Tool output:`, chunk);
|
|
426
|
-
},
|
|
427
|
-
};
|
|
428
|
-
invokeCtx.runtime = runtime;
|
|
429
|
-
|
|
430
|
-
try {
|
|
431
|
-
const res = await toolEntry.invoke(invokeCtx, toolArgs, runtime);
|
|
432
|
-
const output = trimText(res?.content ?? res?.result ?? res, 50000);
|
|
433
|
-
trace.push({
|
|
434
|
-
type: 'tool_result',
|
|
435
|
-
at: nowIso(),
|
|
436
|
-
title: `Tool finished: ${toolEntry.definition.name}`,
|
|
437
|
-
toolName: toolEntry.definition.name,
|
|
438
|
-
status: res?.status || 'success',
|
|
439
|
-
content: trimText(output, 2000),
|
|
440
|
-
});
|
|
441
|
-
if (res?.status === 'error') {
|
|
442
|
-
await this.spanService.finish(toolSpanId, 'error', toolStartedAt, {
|
|
443
|
-
output,
|
|
444
|
-
error: trimText(res.content || output, 10000),
|
|
445
|
-
});
|
|
446
|
-
throw new Error(`Tool <${toolEntry.definition.name}> failed: ${res.content}`);
|
|
447
|
-
}
|
|
448
|
-
await this.spanService.finish(toolSpanId, 'success', toolStartedAt, {
|
|
449
|
-
output,
|
|
450
|
-
skillExecutionId: res?.result?.execId || res?.execId,
|
|
451
|
-
});
|
|
452
|
-
return typeof res?.content === 'string' ? res.content : JSON.stringify(res);
|
|
453
|
-
} catch (e: any) {
|
|
454
|
-
trace.push({
|
|
455
|
-
type: 'tool_error',
|
|
456
|
-
at: nowIso(),
|
|
457
|
-
title: `Tool failed: ${toolEntry.definition.name}`,
|
|
458
|
-
toolName: toolEntry.definition.name,
|
|
459
|
-
status: 'error',
|
|
460
|
-
content: e.message,
|
|
461
|
-
});
|
|
462
|
-
await this.spanService.finish(toolSpanId, 'error', toolStartedAt, {
|
|
463
|
-
error: trimText(e.message, 10000),
|
|
464
|
-
});
|
|
465
|
-
throw e;
|
|
466
|
-
}
|
|
467
|
-
},
|
|
468
|
-
}),
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const abortController = new AbortController();
|
|
473
|
-
const executor = createReactAgent({
|
|
474
|
-
llm: chatModel,
|
|
475
|
-
tools: langchainTools as any,
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
let systemPrompt =
|
|
479
|
-
subAgentEmployee.chatSettings?.systemPrompt ||
|
|
480
|
-
subAgentEmployee.bio ||
|
|
481
|
-
`You are an AI assistant named "${subAgentEmployee.nickname || subAgentUsername}". ${
|
|
482
|
-
subAgentEmployee.about || ''
|
|
483
|
-
}`;
|
|
484
|
-
|
|
485
|
-
try {
|
|
486
|
-
const kbPlugin = ctx.app.pm.get('plugin-knowledge-base');
|
|
487
|
-
if (kbPlugin?.sessionContext) {
|
|
488
|
-
const sessionId =
|
|
489
|
-
ctx.action?.params?.values?.sessionId || ctx.action?.params?.sessionId || ctx.state?.sessionId;
|
|
490
|
-
const contextSummary = await kbPlugin.sessionContext.buildSummary(
|
|
491
|
-
{ rootRunId: effectiveRootRunId, ...(sessionId ? { sessionId } : {}) },
|
|
492
|
-
6000,
|
|
493
|
-
);
|
|
494
|
-
if (contextSummary) {
|
|
495
|
-
systemPrompt += `\n\n<shared_context>\nThe following context was shared by other agents in this workflow:\n${contextSummary}\n</shared_context>`;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
} catch (e) {
|
|
499
|
-
// ignore — kb integration is best-effort
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// ── Step context enrichment ────────────────────────────────────
|
|
503
|
-
if (agentLoopRunId) {
|
|
504
|
-
try {
|
|
505
|
-
systemPrompt = await this.contextAggregator.enrichSystemPrompt(systemPrompt, agentLoopRunId, agentLoopStepId);
|
|
506
|
-
} catch {
|
|
507
|
-
// Best-effort: context enrichment failure should not break execution
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const combinedTask = context ? `Task: ${task}\n\nContext:\n${context}` : `Task: ${task}`;
|
|
512
|
-
const effectiveLimit = recursionLimit && recursionLimit > 0 ? recursionLimit : 50;
|
|
513
|
-
|
|
514
|
-
let timeoutId: any;
|
|
515
|
-
const timeoutMs = Number(timeout) > 0 ? Number(timeout) : 120000;
|
|
516
|
-
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
|
517
|
-
timeoutId = setTimeout(() => {
|
|
518
|
-
abortController.abort();
|
|
519
|
-
reject(new Error(`Sub-agent execution timed out after ${timeoutMs}ms.`));
|
|
520
|
-
}, timeoutMs);
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
const invokePromise = executor.invoke(
|
|
524
|
-
{
|
|
525
|
-
messages: [new SystemMessage(systemPrompt), new HumanMessage(combinedTask)] as any,
|
|
526
|
-
},
|
|
527
|
-
{ recursionLimit: effectiveLimit, signal: abortController.signal },
|
|
528
|
-
);
|
|
529
|
-
|
|
530
|
-
let finalState: any;
|
|
531
|
-
try {
|
|
532
|
-
finalState = await Promise.race([invokePromise, timeoutPromise]);
|
|
533
|
-
} finally {
|
|
534
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// ── Token tracking ────────────────────────────────────────────────
|
|
538
|
-
const tokenUsage = extractTokenUsage(finalState);
|
|
539
|
-
if (tokenUsage) {
|
|
540
|
-
await this.tokenTracker.trackSpan(executionSpanId, tokenUsage, agentLoopRunId);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const messages = finalState?.messages || [];
|
|
544
|
-
const lastAIMessage = [...messages].reverse().find((m) => m.getType() === 'ai');
|
|
545
|
-
let content = '';
|
|
546
|
-
if (lastAIMessage) {
|
|
547
|
-
content =
|
|
548
|
-
typeof lastAIMessage.content === 'string'
|
|
549
|
-
? lastAIMessage.content
|
|
550
|
-
: Array.isArray(lastAIMessage.content)
|
|
551
|
-
? lastAIMessage.content.map((c: any) => c.text || JSON.stringify(c)).join('\n')
|
|
552
|
-
: String(lastAIMessage.content);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
trace.push({
|
|
556
|
-
type: 'finish',
|
|
557
|
-
at: nowIso(),
|
|
558
|
-
title: `Delegation finished: ${subAgentUsername}`,
|
|
559
|
-
status: 'success',
|
|
560
|
-
content: trimText(content, 2000),
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
await this.logDelegation(ctx, {
|
|
564
|
-
id: logRecord?.id,
|
|
565
|
-
leaderUsername,
|
|
566
|
-
subAgentUsername,
|
|
567
|
-
toolName,
|
|
568
|
-
task,
|
|
569
|
-
context,
|
|
570
|
-
result: content,
|
|
571
|
-
status: 'success',
|
|
572
|
-
depth: maxDepth,
|
|
573
|
-
durationMs: Date.now() - startTime,
|
|
574
|
-
trace,
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
await this.spanService.finish(executionSpanId, 'success', startTime, {
|
|
578
|
-
output: content,
|
|
579
|
-
metadata: {
|
|
580
|
-
depth: currentDepth,
|
|
581
|
-
toolName,
|
|
582
|
-
recursionLimit,
|
|
583
|
-
agentLoopRunId,
|
|
584
|
-
agentLoopStepId,
|
|
585
|
-
},
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
return {
|
|
589
|
-
status: 'success' as const,
|
|
590
|
-
content,
|
|
591
|
-
};
|
|
592
|
-
} catch (e: any) {
|
|
593
|
-
trace.push({
|
|
594
|
-
type: 'error',
|
|
595
|
-
at: nowIso(),
|
|
596
|
-
title: `Delegation failed: ${subAgentUsername}`,
|
|
597
|
-
status: 'error',
|
|
598
|
-
content: e.message,
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
await this.logDelegation(ctx, {
|
|
602
|
-
id: logRecord?.id,
|
|
603
|
-
leaderUsername,
|
|
604
|
-
subAgentUsername,
|
|
605
|
-
toolName,
|
|
606
|
-
task,
|
|
607
|
-
context,
|
|
608
|
-
result: '',
|
|
609
|
-
status: 'error',
|
|
610
|
-
depth: maxDepth,
|
|
611
|
-
durationMs: Date.now() - startTime,
|
|
612
|
-
error: e.message,
|
|
613
|
-
trace,
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
await this.spanService.finish(executionSpanId, 'error', startTime, {
|
|
617
|
-
error: trimText(e.message, 10000),
|
|
618
|
-
metadata: {
|
|
619
|
-
depth: currentDepth,
|
|
620
|
-
toolName,
|
|
621
|
-
recursionLimit,
|
|
622
|
-
agentLoopRunId,
|
|
623
|
-
agentLoopStepId,
|
|
624
|
-
},
|
|
625
|
-
});
|
|
626
|
-
return {
|
|
627
|
-
status: 'error' as const,
|
|
628
|
-
content: e.message,
|
|
57
|
+
summary: `${step?.title || step?.planKey || 'Step'} is retained as a legacy planning record.`,
|
|
58
|
+
description: step?.description || '',
|
|
59
|
+
input: step?.input || {},
|
|
60
|
+
harnessTag,
|
|
61
|
+
retired: true,
|
|
629
62
|
};
|
|
630
63
|
}
|
|
631
|
-
}
|
|
632
64
|
|
|
633
|
-
|
|
634
|
-
ctx: any,
|
|
635
|
-
data: {
|
|
636
|
-
id?: number | string;
|
|
637
|
-
leaderUsername: string;
|
|
638
|
-
subAgentUsername: string;
|
|
639
|
-
toolName: string;
|
|
640
|
-
task: string;
|
|
641
|
-
context?: string;
|
|
642
|
-
result: string;
|
|
643
|
-
status: string;
|
|
644
|
-
depth: number;
|
|
645
|
-
durationMs: number;
|
|
646
|
-
error?: string;
|
|
647
|
-
trace?: TraceEvent[];
|
|
648
|
-
},
|
|
649
|
-
) {
|
|
650
|
-
return sharedLogDelegation(ctx, this.plugin, data);
|
|
65
|
+
throw retiredExecutionError(step?.type);
|
|
651
66
|
}
|
|
652
67
|
}
|