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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { asObject, toPlain, trimText } from '../utils/ctx-utils';
|
|
2
|
+
|
|
3
|
+
type MemoryScope = 'public' | 'user' | 'agent_user';
|
|
4
|
+
|
|
5
|
+
type PolicySettings = {
|
|
6
|
+
nativeObserverEnabled?: boolean;
|
|
7
|
+
memoryInjectionEnabled?: boolean;
|
|
8
|
+
memoryScopes?: string[];
|
|
9
|
+
maxMemoryContextChars?: number;
|
|
10
|
+
maxContextChars?: number;
|
|
11
|
+
harnessTag?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type ContextRecord = {
|
|
15
|
+
scope?: string;
|
|
16
|
+
userId?: string | number | null;
|
|
17
|
+
aiEmployeeUsername?: string | null;
|
|
18
|
+
contentMd?: string | null;
|
|
19
|
+
graphMd?: string | null;
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type BuildContextOptions = {
|
|
24
|
+
userId?: string | number;
|
|
25
|
+
aiEmployeeUsername?: string;
|
|
26
|
+
settings?: PolicySettings;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type ContextSection = {
|
|
30
|
+
scope: string;
|
|
31
|
+
title: string;
|
|
32
|
+
body: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const DEFAULT_MEMORY_SCOPES: MemoryScope[] = ['public', 'user', 'agent_user'];
|
|
36
|
+
const DEFAULT_MAX_CONTEXT_CHARS = 6000;
|
|
37
|
+
|
|
38
|
+
function readModelValue(record: unknown, key: string) {
|
|
39
|
+
const model = record as { get?: (name: string) => unknown; [key: string]: unknown };
|
|
40
|
+
return typeof model?.get === 'function' ? model.get(key) : model?.[key];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeScopes(value: unknown): MemoryScope[] {
|
|
44
|
+
if (!Array.isArray(value)) return DEFAULT_MEMORY_SCOPES;
|
|
45
|
+
const scopes = value.filter(
|
|
46
|
+
(item): item is MemoryScope => item === 'public' || item === 'user' || item === 'agent_user',
|
|
47
|
+
);
|
|
48
|
+
return scopes.length ? scopes : DEFAULT_MEMORY_SCOPES;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeText(value: unknown) {
|
|
52
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatRecordSection(record: ContextRecord) {
|
|
56
|
+
const content = normalizeText(record.contentMd);
|
|
57
|
+
const graph = normalizeText(record.graphMd);
|
|
58
|
+
return [content, graph ? `## Graph\n${graph}` : ''].filter(Boolean).join('\n\n').trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class AgentMemoryContextService {
|
|
62
|
+
constructor(private readonly plugin: { app: any; db: any }) {}
|
|
63
|
+
|
|
64
|
+
async resolvePolicySettings(task?: {
|
|
65
|
+
ctx?: unknown;
|
|
66
|
+
employee?: unknown;
|
|
67
|
+
skillSettings?: unknown;
|
|
68
|
+
}): Promise<PolicySettings> {
|
|
69
|
+
const directSettings = asObject(task?.skillSettings);
|
|
70
|
+
const employeeSettings = asObject(readModelValue(task?.employee, 'skillSettings'));
|
|
71
|
+
const values = asObject((task?.ctx as { action?: { params?: { values?: unknown } } })?.action?.params?.values);
|
|
72
|
+
const tag =
|
|
73
|
+
normalizeText(values.harnessTag) ||
|
|
74
|
+
normalizeText(values.orchestratorHarnessTag) ||
|
|
75
|
+
normalizeText(directSettings.harnessTag) ||
|
|
76
|
+
normalizeText(directSettings.orchestratorHarnessTag) ||
|
|
77
|
+
normalizeText(employeeSettings.harnessTag) ||
|
|
78
|
+
normalizeText(employeeSettings.orchestratorHarnessTag) ||
|
|
79
|
+
'default';
|
|
80
|
+
|
|
81
|
+
const profile = await this.findHarnessProfile(tag);
|
|
82
|
+
const fallbackProfile = profile || (tag === 'default' ? null : await this.findHarnessProfile('default'));
|
|
83
|
+
const profileSettings = asObject(fallbackProfile ? readModelValue(fallbackProfile, 'settings') : {});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
...profileSettings,
|
|
87
|
+
harnessTag: tag,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async buildContext(options: BuildContextOptions) {
|
|
92
|
+
const { userId, aiEmployeeUsername } = options;
|
|
93
|
+
const settings = options.settings || {};
|
|
94
|
+
if (!userId || settings.memoryInjectionEnabled === false) {
|
|
95
|
+
return {
|
|
96
|
+
context: '',
|
|
97
|
+
appliedScopes: [] as string[],
|
|
98
|
+
chars: 0,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const sections: ContextSection[] = [];
|
|
103
|
+
const scopes = normalizeScopes(settings.memoryScopes);
|
|
104
|
+
|
|
105
|
+
if (scopes.includes('public')) {
|
|
106
|
+
sections.push(...(await this.loadStoredSections('public', userId, aiEmployeeUsername)));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (scopes.includes('user')) {
|
|
110
|
+
const userMemory = await this.loadUserMemory(userId);
|
|
111
|
+
if (userMemory) {
|
|
112
|
+
sections.push({
|
|
113
|
+
scope: 'user-memory',
|
|
114
|
+
title: 'User Memory',
|
|
115
|
+
body: userMemory,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
sections.push(...(await this.loadStoredSections('user', userId, aiEmployeeUsername)));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (scopes.includes('agent_user')) {
|
|
123
|
+
sections.push(...(await this.loadStoredSections('agent_user', userId, aiEmployeeUsername)));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!sections.length) {
|
|
127
|
+
return {
|
|
128
|
+
context: '',
|
|
129
|
+
appliedScopes: [] as string[],
|
|
130
|
+
chars: 0,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const body = sections
|
|
135
|
+
.map((section) => `### ${section.title}\n${section.body}`)
|
|
136
|
+
.join('\n\n')
|
|
137
|
+
.trim();
|
|
138
|
+
const maxChars = Number(settings.maxMemoryContextChars || settings.maxContextChars || DEFAULT_MAX_CONTEXT_CHARS);
|
|
139
|
+
const context = `<agent_memory_context>\n${trimText(
|
|
140
|
+
body,
|
|
141
|
+
Number.isFinite(maxChars) ? maxChars : DEFAULT_MAX_CONTEXT_CHARS,
|
|
142
|
+
)}\n</agent_memory_context>`;
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
context,
|
|
146
|
+
appliedScopes: Array.from(new Set(sections.map((section) => section.scope))),
|
|
147
|
+
chars: context.length,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private async findHarnessProfile(tag: string) {
|
|
152
|
+
try {
|
|
153
|
+
const repo = this.plugin.db.getRepository('agentHarnessProfiles');
|
|
154
|
+
if (!repo) return null;
|
|
155
|
+
return repo.findOne({
|
|
156
|
+
filter: {
|
|
157
|
+
tag,
|
|
158
|
+
enabled: true,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
} catch (error) {
|
|
162
|
+
this.plugin.app.logger?.warn?.('[AgentOrchestrator] Failed to load policy profile', error);
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async loadStoredSections(scope: MemoryScope, userId?: string | number, aiEmployeeUsername?: string) {
|
|
168
|
+
try {
|
|
169
|
+
const repo = this.plugin.db.getRepository('agentMemoryContexts');
|
|
170
|
+
if (!repo) return [];
|
|
171
|
+
|
|
172
|
+
const filter: Record<string, unknown> = {
|
|
173
|
+
scope,
|
|
174
|
+
enabled: true,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (scope === 'public') {
|
|
178
|
+
filter.userId = null;
|
|
179
|
+
} else if (userId) {
|
|
180
|
+
filter.userId = userId;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const records = await repo.find({
|
|
184
|
+
filter,
|
|
185
|
+
sort: ['scope', 'aiEmployeeUsername', 'updatedAt'],
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return records
|
|
189
|
+
.map((record: unknown) => toPlain(record) as ContextRecord)
|
|
190
|
+
.filter((record) => {
|
|
191
|
+
if (scope === 'agent_user') {
|
|
192
|
+
return (
|
|
193
|
+
String(record.userId || '') === String(userId || '') &&
|
|
194
|
+
normalizeText(record.aiEmployeeUsername) === normalizeText(aiEmployeeUsername)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
const username = normalizeText(record.aiEmployeeUsername);
|
|
198
|
+
return !username || username === normalizeText(aiEmployeeUsername);
|
|
199
|
+
})
|
|
200
|
+
.map((record) => ({
|
|
201
|
+
scope,
|
|
202
|
+
title: this.sectionTitle(scope, record.aiEmployeeUsername),
|
|
203
|
+
body: formatRecordSection(record),
|
|
204
|
+
}))
|
|
205
|
+
.filter((section) => section.body);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
this.plugin.app.logger?.warn?.(`[AgentOrchestrator] Failed to load ${scope} memory context`, error);
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private async loadUserMemory(userId: string | number) {
|
|
213
|
+
try {
|
|
214
|
+
const pluginManager = this.plugin.app.pm;
|
|
215
|
+
const candidates = ['plugin-user-memory', '@nocobase/plugin-user-memory', 'user-memory'];
|
|
216
|
+
let userMemoryPlugin: {
|
|
217
|
+
memoryInjector?: { getMemoryPromptSection?: (userId: number) => Promise<string> };
|
|
218
|
+
} | null = null;
|
|
219
|
+
|
|
220
|
+
for (const name of candidates) {
|
|
221
|
+
try {
|
|
222
|
+
const candidate = pluginManager?.get?.(name);
|
|
223
|
+
if (candidate?.memoryInjector?.getMemoryPromptSection) {
|
|
224
|
+
userMemoryPlugin = candidate;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
// Missing plugin aliases are expected in installations without user memory.
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!userMemoryPlugin && typeof pluginManager?.getPlugins === 'function') {
|
|
233
|
+
for (const [, candidate] of pluginManager.getPlugins()) {
|
|
234
|
+
if (candidate?.memoryInjector?.getMemoryPromptSection) {
|
|
235
|
+
userMemoryPlugin = candidate;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const injector = userMemoryPlugin?.memoryInjector;
|
|
242
|
+
if (!injector?.getMemoryPromptSection) return '';
|
|
243
|
+
return normalizeText(await injector.getMemoryPromptSection(Number(userId)));
|
|
244
|
+
} catch (error) {
|
|
245
|
+
this.plugin.app.logger?.warn?.('[AgentOrchestrator] Failed to read plugin-user-memory context', error);
|
|
246
|
+
return '';
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private sectionTitle(scope: MemoryScope, aiEmployeeUsername?: string | null) {
|
|
251
|
+
const suffix = aiEmployeeUsername ? ` (${aiEmployeeUsername})` : '';
|
|
252
|
+
if (scope === 'public') return `Public Agent Knowledge${suffix}`;
|
|
253
|
+
if (scope === 'user') return `User Context${suffix}`;
|
|
254
|
+
return `Private Agent/User Context${suffix}`;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import { AgentLoopPlanStepInput } from './AgentLoopService';
|
|
2
|
-
|
|
3
|
-
function normalizeStepType(value: any) {
|
|
4
|
-
return ['reasoning', 'skill', 'tool', 'sub_agent', 'verification'].includes(value) ? value : 'tool';
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function normalizePlanKey(step: any, index: number) {
|
|
8
|
-
return String(step.planKey || step.key || step.id || `step_${index + 1}`);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function asArray(value: any): any[] {
|
|
12
|
-
return Array.isArray(value) ? value : [];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const ORCHESTRATOR_CONTROLLER_MAX_STEPS = 100;
|
|
16
|
-
|
|
17
|
-
export class AgentPlanValidator {
|
|
18
|
-
validate(plan: AgentLoopPlanStepInput[]) {
|
|
19
|
-
if (!Array.isArray(plan) || plan.length === 0) {
|
|
20
|
-
throw new Error('Plan must include at least one step.');
|
|
21
|
-
}
|
|
22
|
-
if (plan.length > ORCHESTRATOR_CONTROLLER_MAX_STEPS) {
|
|
23
|
-
throw new Error(`Plan cannot exceed ${ORCHESTRATOR_CONTROLLER_MAX_STEPS} steps.`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const keys = new Set<string>();
|
|
27
|
-
const graph = new Map<string, string[]>();
|
|
28
|
-
for (let i = 0; i < plan.length; i++) {
|
|
29
|
-
const key = normalizePlanKey(plan[i], i).trim();
|
|
30
|
-
if (!key) {
|
|
31
|
-
throw new Error(`Plan step ${i + 1} has an empty planKey.`);
|
|
32
|
-
}
|
|
33
|
-
if (keys.has(key)) {
|
|
34
|
-
throw new Error(`Duplicate planKey "${key}" in plan.`);
|
|
35
|
-
}
|
|
36
|
-
const type = normalizeStepType(plan[i].type);
|
|
37
|
-
if (['tool', 'skill', 'sub_agent'].includes(type) && !plan[i].target) {
|
|
38
|
-
throw new Error(`Step "${key}" of type "${type}" must include a target.`);
|
|
39
|
-
}
|
|
40
|
-
keys.add(key);
|
|
41
|
-
graph.set(key, asArray(plan[i].dependsOn).map(String));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (const [key, dependencies] of graph.entries()) {
|
|
45
|
-
for (const dependency of dependencies) {
|
|
46
|
-
if (!keys.has(dependency)) {
|
|
47
|
-
throw new Error(`Step "${key}" depends on unknown step "${dependency}".`);
|
|
48
|
-
}
|
|
49
|
-
if (dependency === key) {
|
|
50
|
-
throw new Error(`Step "${key}" cannot depend on itself.`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const visiting = new Set<string>();
|
|
56
|
-
const visited = new Set<string>();
|
|
57
|
-
const visit = (key: string) => {
|
|
58
|
-
if (visited.has(key)) return;
|
|
59
|
-
if (visiting.has(key)) {
|
|
60
|
-
throw new Error(`Plan has a dependency cycle at "${key}".`);
|
|
61
|
-
}
|
|
62
|
-
visiting.add(key);
|
|
63
|
-
for (const dependency of graph.get(key) || []) {
|
|
64
|
-
visit(dependency);
|
|
65
|
-
}
|
|
66
|
-
visiting.delete(key);
|
|
67
|
-
visited.add(key);
|
|
68
|
-
};
|
|
69
|
-
for (const key of graph.keys()) {
|
|
70
|
-
visit(key);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
1
|
+
import { AgentLoopPlanStepInput } from './AgentLoopService';
|
|
2
|
+
|
|
3
|
+
function normalizeStepType(value: any) {
|
|
4
|
+
return ['reasoning', 'skill', 'tool', 'sub_agent', 'verification'].includes(value) ? value : 'tool';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizePlanKey(step: any, index: number) {
|
|
8
|
+
return String(step.planKey || step.key || step.id || `step_${index + 1}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function asArray(value: any): any[] {
|
|
12
|
+
return Array.isArray(value) ? value : [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ORCHESTRATOR_CONTROLLER_MAX_STEPS = 100;
|
|
16
|
+
|
|
17
|
+
export class AgentPlanValidator {
|
|
18
|
+
validate(plan: AgentLoopPlanStepInput[]) {
|
|
19
|
+
if (!Array.isArray(plan) || plan.length === 0) {
|
|
20
|
+
throw new Error('Plan must include at least one step.');
|
|
21
|
+
}
|
|
22
|
+
if (plan.length > ORCHESTRATOR_CONTROLLER_MAX_STEPS) {
|
|
23
|
+
throw new Error(`Plan cannot exceed ${ORCHESTRATOR_CONTROLLER_MAX_STEPS} steps.`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const keys = new Set<string>();
|
|
27
|
+
const graph = new Map<string, string[]>();
|
|
28
|
+
for (let i = 0; i < plan.length; i++) {
|
|
29
|
+
const key = normalizePlanKey(plan[i], i).trim();
|
|
30
|
+
if (!key) {
|
|
31
|
+
throw new Error(`Plan step ${i + 1} has an empty planKey.`);
|
|
32
|
+
}
|
|
33
|
+
if (keys.has(key)) {
|
|
34
|
+
throw new Error(`Duplicate planKey "${key}" in plan.`);
|
|
35
|
+
}
|
|
36
|
+
const type = normalizeStepType(plan[i].type);
|
|
37
|
+
if (['tool', 'skill', 'sub_agent'].includes(type) && !plan[i].target) {
|
|
38
|
+
throw new Error(`Step "${key}" of type "${type}" must include a target.`);
|
|
39
|
+
}
|
|
40
|
+
keys.add(key);
|
|
41
|
+
graph.set(key, asArray(plan[i].dependsOn).map(String));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const [key, dependencies] of graph.entries()) {
|
|
45
|
+
for (const dependency of dependencies) {
|
|
46
|
+
if (!keys.has(dependency)) {
|
|
47
|
+
throw new Error(`Step "${key}" depends on unknown step "${dependency}".`);
|
|
48
|
+
}
|
|
49
|
+
if (dependency === key) {
|
|
50
|
+
throw new Error(`Step "${key}" cannot depend on itself.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const visiting = new Set<string>();
|
|
56
|
+
const visited = new Set<string>();
|
|
57
|
+
const visit = (key: string) => {
|
|
58
|
+
if (visited.has(key)) return;
|
|
59
|
+
if (visiting.has(key)) {
|
|
60
|
+
throw new Error(`Plan has a dependency cycle at "${key}".`);
|
|
61
|
+
}
|
|
62
|
+
visiting.add(key);
|
|
63
|
+
for (const dependency of graph.get(key) || []) {
|
|
64
|
+
visit(dependency);
|
|
65
|
+
}
|
|
66
|
+
visiting.delete(key);
|
|
67
|
+
visited.add(key);
|
|
68
|
+
};
|
|
69
|
+
for (const key of graph.keys()) {
|
|
70
|
+
visit(key);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -17,6 +17,10 @@ export type OrchestratorTraceContext = {
|
|
|
17
17
|
type SpanValues = {
|
|
18
18
|
rootRunId: string;
|
|
19
19
|
parentSpanId?: string;
|
|
20
|
+
source?: string;
|
|
21
|
+
parentSessionId?: string;
|
|
22
|
+
subSessionId?: string;
|
|
23
|
+
toolCallId?: string;
|
|
20
24
|
type: string;
|
|
21
25
|
status: string;
|
|
22
26
|
leaderUsername?: string;
|
|
@@ -52,7 +56,7 @@ export class ExecutionSpanService {
|
|
|
52
56
|
});
|
|
53
57
|
return toPlain(record);
|
|
54
58
|
} catch (error) {
|
|
55
|
-
this.plugin.app.
|
|
59
|
+
this.plugin.app.logger?.warn?.('[AgentOrchestrator] Failed to create execution span', error);
|
|
56
60
|
return null;
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -71,7 +75,7 @@ export class ExecutionSpanService {
|
|
|
71
75
|
});
|
|
72
76
|
return { id: spanId };
|
|
73
77
|
} catch (error) {
|
|
74
|
-
this.plugin.app.
|
|
78
|
+
this.plugin.app.logger?.warn?.(`[AgentOrchestrator] Failed to update execution span ${spanId}`, error);
|
|
75
79
|
return null;
|
|
76
80
|
}
|
|
77
81
|
}
|