plugin-agent-orchestrator 1.0.20 → 1.0.22
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/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/server/collections/agent-execution-spans.js +24 -0
- package/dist/server/collections/agent-loop-runs.js +36 -0
- package/dist/server/collections/orchestrator-config.js +14 -0
- package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
- package/dist/server/plugin.js +56 -0
- package/dist/server/resources/agent-loop.js +33 -25
- package/dist/server/resources/tracing.js +5 -8
- package/dist/server/services/AgentHarness.js +56 -90
- package/dist/server/services/AgentLoopController.js +164 -125
- package/dist/server/services/AgentLoopRepository.js +16 -34
- package/dist/server/services/AgentLoopService.js +7 -1
- package/dist/server/services/AgentPlannerService.js +5 -25
- package/dist/server/services/AgentRegistryService.js +34 -24
- package/dist/server/services/CircuitBreaker.js +120 -0
- package/dist/server/services/ContextAggregator.js +201 -0
- package/dist/server/services/ExecutionSpanService.js +2 -5
- package/dist/server/services/RunEventBus.js +73 -0
- package/dist/server/services/TokenTracker.js +173 -0
- package/dist/server/tools/agent-loop.js +30 -63
- package/dist/server/tools/delegate-task.js +14 -72
- package/dist/server/tools/orchestrator-plan.js +10 -47
- package/dist/server/types.js +24 -0
- package/dist/server/utils/ctx-utils.js +152 -0
- package/dist/server/utils/logging.js +86 -0
- package/package.json +44 -44
- package/src/client/AgentRunsTab.tsx +764 -764
- package/src/client/HarnessProfilesTab.tsx +247 -247
- package/src/client/OrchestratorSettings.tsx +106 -106
- package/src/client/RulesTab.tsx +716 -716
- package/src/client/hooks/useRunEventStream.ts +76 -0
- package/src/client/index.tsx +2 -1
- package/src/client/plugin.tsx +27 -27
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
- package/src/client/skill-hub/index.tsx +51 -51
- package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
- package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
- package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
- package/src/client/tools/PlanApprovalCard.tsx +175 -175
- package/src/client/tools/registerOrchestratorCards.ts +7 -7
- package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
- package/src/server/__tests__/circuit-breaker.test.ts +169 -0
- package/src/server/__tests__/context-aggregator.test.ts +222 -0
- package/src/server/__tests__/parallel-execution.test.ts +318 -0
- package/src/server/__tests__/smoke.test.ts +120 -0
- package/src/server/collections/agent-execution-spans.ts +24 -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-runs.ts +38 -1
- package/src/server/collections/agent-loop-steps.ts +144 -144
- package/src/server/collections/orchestrator-config.ts +14 -0
- package/src/server/collections/skill-executions.ts +106 -106
- package/src/server/collections/skill-loop-configs.ts +65 -65
- 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 +142 -142
- package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
- package/src/server/plugin.ts +68 -0
- package/src/server/resources/agent-loop.ts +21 -12
- package/src/server/resources/tracing.ts +3 -7
- package/src/server/services/AgentHarness.ts +78 -116
- package/src/server/services/AgentLoopController.ts +197 -122
- package/src/server/services/AgentLoopRepository.ts +9 -25
- package/src/server/services/AgentLoopService.ts +13 -1
- package/src/server/services/AgentPlanValidator.ts +73 -73
- package/src/server/services/AgentPlannerService.ts +2 -25
- package/src/server/services/AgentRegistryService.ts +40 -31
- package/src/server/services/CircuitBreaker.ts +116 -0
- package/src/server/services/ContextAggregator.ts +239 -0
- package/src/server/services/ExecutionSpanService.ts +2 -4
- package/src/server/services/RunEventBus.ts +45 -0
- package/src/server/services/TokenTracker.ts +209 -0
- package/src/server/skill-hub/plugin.ts +898 -898
- package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -460
- package/src/server/tools/agent-loop.ts +18 -57
- package/src/server/tools/delegate-task.ts +11 -93
- package/src/server/tools/orchestrator-plan.ts +26 -50
- package/src/server/tools/skill-execute.ts +160 -160
- package/src/server/types.ts +55 -0
- package/src/server/utils/ctx-utils.ts +118 -0
- package/src/server/utils/logging.ts +63 -0
- package/dist/client/AIEmployeeSelect.d.ts +0 -11
- package/dist/client/AIEmployeesContext.d.ts +0 -30
- package/dist/client/AgentRunsTab.d.ts +0 -2
- package/dist/client/HarnessProfilesTab.d.ts +0 -2
- package/dist/client/OrchestratorSettings.d.ts +0 -3
- package/dist/client/RulesTab.d.ts +0 -2
- package/dist/client/TracingTab.d.ts +0 -2
- package/dist/client/index.d.ts +0 -1
- package/dist/client/plugin.d.ts +0 -6
- package/dist/client/skill-hub/components/ExecutionHistory.d.ts +0 -2
- package/dist/client/skill-hub/components/ExecutionProgress.d.ts +0 -20
- package/dist/client/skill-hub/components/GitSkillImport.d.ts +0 -7
- package/dist/client/skill-hub/components/LoopSettings.d.ts +0 -2
- package/dist/client/skill-hub/components/SkillEditor.d.ts +0 -7
- package/dist/client/skill-hub/components/SkillManager.d.ts +0 -2
- package/dist/client/skill-hub/components/SkillMetrics.d.ts +0 -2
- package/dist/client/skill-hub/components/SkillTestPanel.d.ts +0 -7
- package/dist/client/skill-hub/index.d.ts +0 -11
- package/dist/client/skill-hub/locale.d.ts +0 -3
- package/dist/client/skill-hub/tools/InteractionSchemasProvider.d.ts +0 -6
- package/dist/client/skill-hub/tools/SkillHubCard.d.ts +0 -3
- package/dist/client/skill-hub/tools/loopTemplates.d.ts +0 -22
- package/dist/client/skill-hub/tools/registerSkillLoopCards.d.ts +0 -1
- package/dist/client/skill-hub/utils/jsonFields.d.ts +0 -3
- package/dist/client/tools/PlanApprovalCard.d.ts +0 -3
- package/dist/client/tools/registerOrchestratorCards.d.ts +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/server/collections/agent-execution-spans.d.ts +0 -9
- package/dist/server/collections/agent-harness-profiles.d.ts +0 -2
- package/dist/server/collections/agent-loop-events.d.ts +0 -2
- package/dist/server/collections/agent-loop-runs.d.ts +0 -2
- package/dist/server/collections/agent-loop-steps.d.ts +0 -2
- package/dist/server/collections/orchestrator-config.d.ts +0 -2
- package/dist/server/collections/orchestrator-logs.d.ts +0 -8
- package/dist/server/collections/skill-definitions.d.ts +0 -3
- package/dist/server/collections/skill-executions.d.ts +0 -3
- package/dist/server/collections/skill-loop-configs.d.ts +0 -3
- package/dist/server/collections/skill-worker-configs.d.ts +0 -3
- package/dist/server/index.d.ts +0 -1
- package/dist/server/migrations/20260423000000-add-progress-fields.d.ts +0 -4
- package/dist/server/migrations/20260425000000-add-interaction-schema.d.ts +0 -4
- package/dist/server/migrations/20260427000000-add-tracing-detail-fields.d.ts +0 -7
- package/dist/server/migrations/20260427000000-change-packages-to-text.d.ts +0 -4
- package/dist/server/migrations/20260427000001-change-other-json-to-text.d.ts +0 -4
- package/dist/server/migrations/20260429000000-add-llm-fields.d.ts +0 -7
- package/dist/server/migrations/20260429000000-fix-inputargs-json-to-text.d.ts +0 -16
- package/dist/server/migrations/20260503000000-add-orchestrator-trace-fields.d.ts +0 -7
- package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.d.ts +0 -7
- package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.d.ts +0 -12
- package/dist/server/plugin.d.ts +0 -16
- package/dist/server/resources/agent-loop.d.ts +0 -3
- package/dist/server/resources/tracing.d.ts +0 -7
- package/dist/server/services/AgentHarness.d.ts +0 -42
- package/dist/server/services/AgentLoopController.d.ts +0 -205
- package/dist/server/services/AgentLoopRepository.d.ts +0 -20
- package/dist/server/services/AgentLoopService.d.ts +0 -149
- package/dist/server/services/AgentPlanValidator.d.ts +0 -4
- package/dist/server/services/AgentPlannerService.d.ts +0 -8
- package/dist/server/services/AgentRegistryService.d.ts +0 -13
- package/dist/server/services/CodeValidator.d.ts +0 -32
- package/dist/server/services/ExecutionSpanService.d.ts +0 -46
- package/dist/server/services/FileManager.d.ts +0 -28
- package/dist/server/services/SandboxRunner.d.ts +0 -41
- package/dist/server/services/SkillManager.d.ts +0 -6
- package/dist/server/services/SkillRepositoryService.d.ts +0 -22
- package/dist/server/services/WorkerEnvManager.d.ts +0 -26
- package/dist/server/skill-hub/actions/git-import.d.ts +0 -21
- package/dist/server/skill-hub/mcp/McpController.d.ts +0 -15
- package/dist/server/skill-hub/plugin.d.ts +0 -61
- package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +0 -16
- package/dist/server/skill-hub/utils/json-fields.d.ts +0 -7
- package/dist/server/tools/agent-loop.d.ts +0 -235
- package/dist/server/tools/delegate-task.d.ts +0 -19
- package/dist/server/tools/external-rag-search.d.ts +0 -42
- package/dist/server/tools/orchestrator-plan.d.ts +0 -205
- package/dist/server/tools/skill-execute.d.ts +0 -36
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import { SkillHubCard } from './SkillHubCard';
|
|
2
|
-
import { parseJsonText } from '../utils/jsonFields';
|
|
3
|
-
|
|
4
|
-
const sanitize = (name: string) =>
|
|
5
|
-
name
|
|
6
|
-
.toLowerCase()
|
|
7
|
-
.replace(/[^a-z0-9_]/g, '_')
|
|
8
|
-
.replace(/_+/g, '_')
|
|
9
|
-
.replace(/^_|_$/g, '');
|
|
10
|
-
|
|
11
|
-
const extractList = (data: any) => {
|
|
12
|
-
const value = data?.data?.data ?? data?.data ?? data ?? [];
|
|
13
|
-
return Array.isArray(value) ? value : [];
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export async function registerSkillLoopCards(app: any) {
|
|
17
|
-
const toolsManager = app.aiManager?.toolsManager;
|
|
18
|
-
if (!toolsManager) return;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const skillsResponse = await app.apiClient.request({
|
|
22
|
-
url: 'skillDefinitions:list',
|
|
23
|
-
params: {
|
|
24
|
-
filter: { enabled: true },
|
|
25
|
-
fields: ['id', 'name', 'autoCall', 'interactionSchema'],
|
|
26
|
-
pageSize: 500,
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
let loopSkillIds = new Set<string>();
|
|
31
|
-
try {
|
|
32
|
-
const loopConfigsResponse = await app.apiClient.request({
|
|
33
|
-
url: 'skillLoopConfigs:list',
|
|
34
|
-
params: {
|
|
35
|
-
filter: { enabled: true },
|
|
36
|
-
fields: ['skillId'],
|
|
37
|
-
pageSize: 500,
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
loopSkillIds = new Set(extractList(loopConfigsResponse.data).map((config: any) => String(config.skillId)));
|
|
41
|
-
if (loopSkillIds.size > 0) {
|
|
42
|
-
toolsManager.registerTools('skill_hub_execute', { ui: { card: SkillHubCard } });
|
|
43
|
-
}
|
|
44
|
-
} catch {
|
|
45
|
-
// Older deployments may not have the collection before migration/sync.
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const skills = extractList(skillsResponse.data);
|
|
49
|
-
for (const skill of skills) {
|
|
50
|
-
const hasLoopConfig = loopSkillIds.has(String(skill.id));
|
|
51
|
-
const hasLegacySchema = !skill.autoCall && !!parseJsonText(skill.interactionSchema, null);
|
|
52
|
-
if (!hasLoopConfig && !hasLegacySchema) continue;
|
|
53
|
-
toolsManager.registerTools(`skill_hub_${sanitize(skill.name)}`, { ui: { card: SkillHubCard } });
|
|
54
|
-
}
|
|
55
|
-
} catch {
|
|
56
|
-
// user without ACL or backend unavailable - skip silently
|
|
57
|
-
}
|
|
58
|
-
}
|
|
1
|
+
import { SkillHubCard } from './SkillHubCard';
|
|
2
|
+
import { parseJsonText } from '../utils/jsonFields';
|
|
3
|
+
|
|
4
|
+
const sanitize = (name: string) =>
|
|
5
|
+
name
|
|
6
|
+
.toLowerCase()
|
|
7
|
+
.replace(/[^a-z0-9_]/g, '_')
|
|
8
|
+
.replace(/_+/g, '_')
|
|
9
|
+
.replace(/^_|_$/g, '');
|
|
10
|
+
|
|
11
|
+
const extractList = (data: any) => {
|
|
12
|
+
const value = data?.data?.data ?? data?.data ?? data ?? [];
|
|
13
|
+
return Array.isArray(value) ? value : [];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function registerSkillLoopCards(app: any) {
|
|
17
|
+
const toolsManager = app.aiManager?.toolsManager;
|
|
18
|
+
if (!toolsManager) return;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const skillsResponse = await app.apiClient.request({
|
|
22
|
+
url: 'skillDefinitions:list',
|
|
23
|
+
params: {
|
|
24
|
+
filter: { enabled: true },
|
|
25
|
+
fields: ['id', 'name', 'autoCall', 'interactionSchema'],
|
|
26
|
+
pageSize: 500,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
let loopSkillIds = new Set<string>();
|
|
31
|
+
try {
|
|
32
|
+
const loopConfigsResponse = await app.apiClient.request({
|
|
33
|
+
url: 'skillLoopConfigs:list',
|
|
34
|
+
params: {
|
|
35
|
+
filter: { enabled: true },
|
|
36
|
+
fields: ['skillId'],
|
|
37
|
+
pageSize: 500,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
loopSkillIds = new Set(extractList(loopConfigsResponse.data).map((config: any) => String(config.skillId)));
|
|
41
|
+
if (loopSkillIds.size > 0) {
|
|
42
|
+
toolsManager.registerTools('skill_hub_execute', { ui: { card: SkillHubCard } });
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Older deployments may not have the collection before migration/sync.
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const skills = extractList(skillsResponse.data);
|
|
49
|
+
for (const skill of skills) {
|
|
50
|
+
const hasLoopConfig = loopSkillIds.has(String(skill.id));
|
|
51
|
+
const hasLegacySchema = !skill.autoCall && !!parseJsonText(skill.interactionSchema, null);
|
|
52
|
+
if (!hasLoopConfig && !hasLegacySchema) continue;
|
|
53
|
+
toolsManager.registerTools(`skill_hub_${sanitize(skill.name)}`, { ui: { card: SkillHubCard } });
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// user without ACL or backend unavailable - skip silently
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1,175 +1,175 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Alert, Button, Card, Input, List, Space, Tag, Typography, message } from 'antd';
|
|
3
|
-
import { ToolsUIProperties, useAPIClient } from '@nocobase/client';
|
|
4
|
-
|
|
5
|
-
const { Paragraph, Text } = Typography;
|
|
6
|
-
|
|
7
|
-
const extractData = (response: any) => response?.data?.data ?? response?.data ?? response;
|
|
8
|
-
|
|
9
|
-
const summarizeArgsPlan = (plan: any[]) =>
|
|
10
|
-
(Array.isArray(plan) ? plan : []).map((step, index) => ({
|
|
11
|
-
id: step.id || step.planKey || index,
|
|
12
|
-
planKey: step.planKey || step.key || step.id || `step_${index + 1}`,
|
|
13
|
-
title: step.title || `Step ${index + 1}`,
|
|
14
|
-
description: step.description || '',
|
|
15
|
-
type: step.type || 'tool',
|
|
16
|
-
target: step.target || '',
|
|
17
|
-
dependsOn: step.dependsOn || [],
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
export const PlanApprovalCard: React.FC<ToolsUIProperties> = ({ toolCall, decisions }) => {
|
|
21
|
-
const api = useAPIClient();
|
|
22
|
-
const rawArgs = (toolCall.args as Record<string, any>) || {};
|
|
23
|
-
const runId = rawArgs.runId;
|
|
24
|
-
const [detail, setDetail] = React.useState<any>(null);
|
|
25
|
-
const [loading, setLoading] = React.useState(false);
|
|
26
|
-
const [submitting, setSubmitting] = React.useState(false);
|
|
27
|
-
const [feedback, setFeedback] = React.useState('');
|
|
28
|
-
|
|
29
|
-
const interrupted = toolCall.invokeStatus === 'init' || toolCall.invokeStatus === 'interrupted';
|
|
30
|
-
|
|
31
|
-
React.useEffect(() => {
|
|
32
|
-
if (!interrupted || !runId) return;
|
|
33
|
-
let mounted = true;
|
|
34
|
-
setLoading(true);
|
|
35
|
-
api
|
|
36
|
-
.request({
|
|
37
|
-
url: 'agentLoops:get',
|
|
38
|
-
params: { filterByTk: runId },
|
|
39
|
-
})
|
|
40
|
-
.then((response) => {
|
|
41
|
-
if (mounted) setDetail(extractData(response));
|
|
42
|
-
})
|
|
43
|
-
.catch(() => {
|
|
44
|
-
if (mounted) setDetail(null);
|
|
45
|
-
})
|
|
46
|
-
.finally(() => {
|
|
47
|
-
if (mounted) setLoading(false);
|
|
48
|
-
});
|
|
49
|
-
return () => {
|
|
50
|
-
mounted = false;
|
|
51
|
-
};
|
|
52
|
-
}, [api, interrupted, runId]);
|
|
53
|
-
|
|
54
|
-
if (!interrupted) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const run = detail?.run || {};
|
|
59
|
-
const steps = summarizeArgsPlan(detail?.steps || rawArgs.plan || []);
|
|
60
|
-
const goal = run.goal || rawArgs.goal || '';
|
|
61
|
-
|
|
62
|
-
const rejectPlan = async () => {
|
|
63
|
-
if (!runId) {
|
|
64
|
-
await decisions.reject('missing_run_id');
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
setSubmitting(true);
|
|
68
|
-
try {
|
|
69
|
-
await api.request({
|
|
70
|
-
url: 'agentLoops:rejectPlan',
|
|
71
|
-
method: 'post',
|
|
72
|
-
data: { runId, reason: feedback || 'Plan rejected by user.' },
|
|
73
|
-
});
|
|
74
|
-
await decisions.reject(JSON.stringify({ reason: 'plan_rejected', runId, feedback }));
|
|
75
|
-
} catch (error: any) {
|
|
76
|
-
message.error(error?.message || 'Failed to reject plan');
|
|
77
|
-
} finally {
|
|
78
|
-
setSubmitting(false);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const requestChanges = async () => {
|
|
83
|
-
if (!feedback.trim()) {
|
|
84
|
-
message.warning('Add feedback before requesting changes.');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
setSubmitting(true);
|
|
88
|
-
try {
|
|
89
|
-
await api.request({
|
|
90
|
-
url: 'agentLoops:requestPlanChanges',
|
|
91
|
-
method: 'post',
|
|
92
|
-
data: { runId, feedback },
|
|
93
|
-
});
|
|
94
|
-
await decisions.reject(JSON.stringify({ reason: 'changes_requested', runId, feedback }));
|
|
95
|
-
} catch (error: any) {
|
|
96
|
-
message.error(error?.message || 'Failed to request plan changes');
|
|
97
|
-
} finally {
|
|
98
|
-
setSubmitting(false);
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<Card size="small" style={{ marginTop: 8 }}>
|
|
104
|
-
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
|
105
|
-
<Alert
|
|
106
|
-
type="info"
|
|
107
|
-
showIcon
|
|
108
|
-
message="Review orchestrator plan"
|
|
109
|
-
description="Execution starts only after you approve this plan."
|
|
110
|
-
/>
|
|
111
|
-
|
|
112
|
-
<Space size={8} wrap>
|
|
113
|
-
{runId && <Tag color="blue">Run #{runId}</Tag>}
|
|
114
|
-
<Tag color="purple">Plan v{run.planVersion || rawArgs.planVersion || 1}</Tag>
|
|
115
|
-
<Tag color={run.approvalStatus === 'pending' ? 'gold' : 'default'}>
|
|
116
|
-
{run.approvalStatus || 'pending'}
|
|
117
|
-
</Tag>
|
|
118
|
-
{run.metadata?.harnessTag && <Tag>{run.metadata.harnessTag}</Tag>}
|
|
119
|
-
</Space>
|
|
120
|
-
|
|
121
|
-
{goal && (
|
|
122
|
-
<Paragraph style={{ marginBottom: 0 }}>
|
|
123
|
-
<Text strong>Goal: </Text>
|
|
124
|
-
{goal}
|
|
125
|
-
</Paragraph>
|
|
126
|
-
)}
|
|
127
|
-
|
|
128
|
-
<List
|
|
129
|
-
size="small"
|
|
130
|
-
loading={loading}
|
|
131
|
-
dataSource={steps}
|
|
132
|
-
locale={{ emptyText: 'No plan steps found.' }}
|
|
133
|
-
renderItem={(step: any, index) => (
|
|
134
|
-
<List.Item>
|
|
135
|
-
<Space direction="vertical" size={2} style={{ width: '100%' }}>
|
|
136
|
-
<Space size={8} wrap>
|
|
137
|
-
<Text strong>
|
|
138
|
-
{index + 1}. {step.title}
|
|
139
|
-
</Text>
|
|
140
|
-
<Tag>{step.type}</Tag>
|
|
141
|
-
{step.target && <Tag color="green">{step.target}</Tag>}
|
|
142
|
-
</Space>
|
|
143
|
-
{step.description && <Text type="secondary">{step.description}</Text>}
|
|
144
|
-
{step.dependsOn?.length > 0 && (
|
|
145
|
-
<Text type="secondary" style={{ fontSize: 12 }}>
|
|
146
|
-
Depends on: {step.dependsOn.join(', ')}
|
|
147
|
-
</Text>
|
|
148
|
-
)}
|
|
149
|
-
</Space>
|
|
150
|
-
</List.Item>
|
|
151
|
-
)}
|
|
152
|
-
/>
|
|
153
|
-
|
|
154
|
-
<Input.TextArea
|
|
155
|
-
rows={3}
|
|
156
|
-
value={feedback}
|
|
157
|
-
onChange={(event) => setFeedback(event.target.value)}
|
|
158
|
-
placeholder="Optional rejection reason or requested changes"
|
|
159
|
-
/>
|
|
160
|
-
|
|
161
|
-
<Space wrap>
|
|
162
|
-
<Button type="primary" loading={submitting} onClick={() => decisions.approve()}>
|
|
163
|
-
Accept & Run
|
|
164
|
-
</Button>
|
|
165
|
-
<Button loading={submitting} onClick={requestChanges}>
|
|
166
|
-
Request changes
|
|
167
|
-
</Button>
|
|
168
|
-
<Button danger loading={submitting} onClick={rejectPlan}>
|
|
169
|
-
Reject
|
|
170
|
-
</Button>
|
|
171
|
-
</Space>
|
|
172
|
-
</Space>
|
|
173
|
-
</Card>
|
|
174
|
-
);
|
|
175
|
-
};
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Alert, Button, Card, Input, List, Space, Tag, Typography, message } from 'antd';
|
|
3
|
+
import { ToolsUIProperties, useAPIClient } from '@nocobase/client';
|
|
4
|
+
|
|
5
|
+
const { Paragraph, Text } = Typography;
|
|
6
|
+
|
|
7
|
+
const extractData = (response: any) => response?.data?.data ?? response?.data ?? response;
|
|
8
|
+
|
|
9
|
+
const summarizeArgsPlan = (plan: any[]) =>
|
|
10
|
+
(Array.isArray(plan) ? plan : []).map((step, index) => ({
|
|
11
|
+
id: step.id || step.planKey || index,
|
|
12
|
+
planKey: step.planKey || step.key || step.id || `step_${index + 1}`,
|
|
13
|
+
title: step.title || `Step ${index + 1}`,
|
|
14
|
+
description: step.description || '',
|
|
15
|
+
type: step.type || 'tool',
|
|
16
|
+
target: step.target || '',
|
|
17
|
+
dependsOn: step.dependsOn || [],
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
export const PlanApprovalCard: React.FC<ToolsUIProperties> = ({ toolCall, decisions }) => {
|
|
21
|
+
const api = useAPIClient();
|
|
22
|
+
const rawArgs = (toolCall.args as Record<string, any>) || {};
|
|
23
|
+
const runId = rawArgs.runId;
|
|
24
|
+
const [detail, setDetail] = React.useState<any>(null);
|
|
25
|
+
const [loading, setLoading] = React.useState(false);
|
|
26
|
+
const [submitting, setSubmitting] = React.useState(false);
|
|
27
|
+
const [feedback, setFeedback] = React.useState('');
|
|
28
|
+
|
|
29
|
+
const interrupted = toolCall.invokeStatus === 'init' || toolCall.invokeStatus === 'interrupted';
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (!interrupted || !runId) return;
|
|
33
|
+
let mounted = true;
|
|
34
|
+
setLoading(true);
|
|
35
|
+
api
|
|
36
|
+
.request({
|
|
37
|
+
url: 'agentLoops:get',
|
|
38
|
+
params: { filterByTk: runId },
|
|
39
|
+
})
|
|
40
|
+
.then((response) => {
|
|
41
|
+
if (mounted) setDetail(extractData(response));
|
|
42
|
+
})
|
|
43
|
+
.catch(() => {
|
|
44
|
+
if (mounted) setDetail(null);
|
|
45
|
+
})
|
|
46
|
+
.finally(() => {
|
|
47
|
+
if (mounted) setLoading(false);
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
mounted = false;
|
|
51
|
+
};
|
|
52
|
+
}, [api, interrupted, runId]);
|
|
53
|
+
|
|
54
|
+
if (!interrupted) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const run = detail?.run || {};
|
|
59
|
+
const steps = summarizeArgsPlan(detail?.steps || rawArgs.plan || []);
|
|
60
|
+
const goal = run.goal || rawArgs.goal || '';
|
|
61
|
+
|
|
62
|
+
const rejectPlan = async () => {
|
|
63
|
+
if (!runId) {
|
|
64
|
+
await decisions.reject('missing_run_id');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setSubmitting(true);
|
|
68
|
+
try {
|
|
69
|
+
await api.request({
|
|
70
|
+
url: 'agentLoops:rejectPlan',
|
|
71
|
+
method: 'post',
|
|
72
|
+
data: { runId, reason: feedback || 'Plan rejected by user.' },
|
|
73
|
+
});
|
|
74
|
+
await decisions.reject(JSON.stringify({ reason: 'plan_rejected', runId, feedback }));
|
|
75
|
+
} catch (error: any) {
|
|
76
|
+
message.error(error?.message || 'Failed to reject plan');
|
|
77
|
+
} finally {
|
|
78
|
+
setSubmitting(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const requestChanges = async () => {
|
|
83
|
+
if (!feedback.trim()) {
|
|
84
|
+
message.warning('Add feedback before requesting changes.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
setSubmitting(true);
|
|
88
|
+
try {
|
|
89
|
+
await api.request({
|
|
90
|
+
url: 'agentLoops:requestPlanChanges',
|
|
91
|
+
method: 'post',
|
|
92
|
+
data: { runId, feedback },
|
|
93
|
+
});
|
|
94
|
+
await decisions.reject(JSON.stringify({ reason: 'changes_requested', runId, feedback }));
|
|
95
|
+
} catch (error: any) {
|
|
96
|
+
message.error(error?.message || 'Failed to request plan changes');
|
|
97
|
+
} finally {
|
|
98
|
+
setSubmitting(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<Card size="small" style={{ marginTop: 8 }}>
|
|
104
|
+
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
|
105
|
+
<Alert
|
|
106
|
+
type="info"
|
|
107
|
+
showIcon
|
|
108
|
+
message="Review orchestrator plan"
|
|
109
|
+
description="Execution starts only after you approve this plan."
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
<Space size={8} wrap>
|
|
113
|
+
{runId && <Tag color="blue">Run #{runId}</Tag>}
|
|
114
|
+
<Tag color="purple">Plan v{run.planVersion || rawArgs.planVersion || 1}</Tag>
|
|
115
|
+
<Tag color={run.approvalStatus === 'pending' ? 'gold' : 'default'}>
|
|
116
|
+
{run.approvalStatus || 'pending'}
|
|
117
|
+
</Tag>
|
|
118
|
+
{run.metadata?.harnessTag && <Tag>{run.metadata.harnessTag}</Tag>}
|
|
119
|
+
</Space>
|
|
120
|
+
|
|
121
|
+
{goal && (
|
|
122
|
+
<Paragraph style={{ marginBottom: 0 }}>
|
|
123
|
+
<Text strong>Goal: </Text>
|
|
124
|
+
{goal}
|
|
125
|
+
</Paragraph>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
<List
|
|
129
|
+
size="small"
|
|
130
|
+
loading={loading}
|
|
131
|
+
dataSource={steps}
|
|
132
|
+
locale={{ emptyText: 'No plan steps found.' }}
|
|
133
|
+
renderItem={(step: any, index) => (
|
|
134
|
+
<List.Item>
|
|
135
|
+
<Space direction="vertical" size={2} style={{ width: '100%' }}>
|
|
136
|
+
<Space size={8} wrap>
|
|
137
|
+
<Text strong>
|
|
138
|
+
{index + 1}. {step.title}
|
|
139
|
+
</Text>
|
|
140
|
+
<Tag>{step.type}</Tag>
|
|
141
|
+
{step.target && <Tag color="green">{step.target}</Tag>}
|
|
142
|
+
</Space>
|
|
143
|
+
{step.description && <Text type="secondary">{step.description}</Text>}
|
|
144
|
+
{step.dependsOn?.length > 0 && (
|
|
145
|
+
<Text type="secondary" style={{ fontSize: 12 }}>
|
|
146
|
+
Depends on: {step.dependsOn.join(', ')}
|
|
147
|
+
</Text>
|
|
148
|
+
)}
|
|
149
|
+
</Space>
|
|
150
|
+
</List.Item>
|
|
151
|
+
)}
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
<Input.TextArea
|
|
155
|
+
rows={3}
|
|
156
|
+
value={feedback}
|
|
157
|
+
onChange={(event) => setFeedback(event.target.value)}
|
|
158
|
+
placeholder="Optional rejection reason or requested changes"
|
|
159
|
+
/>
|
|
160
|
+
|
|
161
|
+
<Space wrap>
|
|
162
|
+
<Button type="primary" loading={submitting} onClick={() => decisions.approve()}>
|
|
163
|
+
Accept & Run
|
|
164
|
+
</Button>
|
|
165
|
+
<Button loading={submitting} onClick={requestChanges}>
|
|
166
|
+
Request changes
|
|
167
|
+
</Button>
|
|
168
|
+
<Button danger loading={submitting} onClick={rejectPlan}>
|
|
169
|
+
Reject
|
|
170
|
+
</Button>
|
|
171
|
+
</Space>
|
|
172
|
+
</Space>
|
|
173
|
+
</Card>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { PlanApprovalCard } from './PlanApprovalCard';
|
|
2
|
-
|
|
3
|
-
export async function registerOrchestratorCards(app: any) {
|
|
4
|
-
const toolsManager = app.aiManager?.toolsManager;
|
|
5
|
-
if (!toolsManager) return;
|
|
6
|
-
toolsManager.registerTools('orchestrator_execute_plan', { ui: { card: PlanApprovalCard } });
|
|
7
|
-
}
|
|
1
|
+
import { PlanApprovalCard } from './PlanApprovalCard';
|
|
2
|
+
|
|
3
|
+
export async function registerOrchestratorCards(app: any) {
|
|
4
|
+
const toolsManager = app.aiManager?.toolsManager;
|
|
5
|
+
if (!toolsManager) return;
|
|
6
|
+
toolsManager.registerTools('orchestrator_execute_plan', { ui: { card: PlanApprovalCard } });
|
|
7
|
+
}
|