plugin-agent-orchestrator 1.0.19 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/client/hooks/useRunEventStream.d.ts +22 -0
  2. package/dist/client/index.d.ts +1 -0
  3. package/dist/client/index.js +1 -1
  4. package/dist/externalVersion.js +6 -6
  5. package/dist/server/collections/agent-execution-spans.js +24 -0
  6. package/dist/server/collections/agent-loop-runs.js +36 -0
  7. package/dist/server/collections/orchestrator-config.js +14 -0
  8. package/dist/server/migrations/20260601000000-add-token-fields.d.ts +7 -0
  9. package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
  10. package/dist/server/plugin.js +47 -0
  11. package/dist/server/resources/agent-loop.js +33 -25
  12. package/dist/server/resources/tracing.js +5 -8
  13. package/dist/server/services/AgentHarness.d.ts +2 -0
  14. package/dist/server/services/AgentHarness.js +56 -90
  15. package/dist/server/services/AgentLoopController.d.ts +33 -20
  16. package/dist/server/services/AgentLoopController.js +164 -125
  17. package/dist/server/services/AgentLoopRepository.js +16 -34
  18. package/dist/server/services/AgentLoopService.d.ts +28 -18
  19. package/dist/server/services/AgentLoopService.js +7 -1
  20. package/dist/server/services/AgentPlannerService.js +5 -25
  21. package/dist/server/services/AgentRegistryService.d.ts +8 -0
  22. package/dist/server/services/AgentRegistryService.js +34 -24
  23. package/dist/server/services/CircuitBreaker.d.ts +40 -0
  24. package/dist/server/services/CircuitBreaker.js +120 -0
  25. package/dist/server/services/ContextAggregator.d.ts +45 -0
  26. package/dist/server/services/ContextAggregator.js +201 -0
  27. package/dist/server/services/ExecutionSpanService.js +2 -5
  28. package/dist/server/services/RunEventBus.d.ts +9 -0
  29. package/dist/server/services/RunEventBus.js +73 -0
  30. package/dist/server/services/TokenTracker.d.ts +62 -0
  31. package/dist/server/services/TokenTracker.js +173 -0
  32. package/dist/server/skill-hub/plugin.js +6 -6
  33. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +6 -6
  34. package/dist/server/tools/agent-loop.d.ts +8 -8
  35. package/dist/server/tools/agent-loop.js +30 -63
  36. package/dist/server/tools/delegate-task.js +14 -72
  37. package/dist/server/tools/orchestrator-plan.d.ts +6 -6
  38. package/dist/server/tools/orchestrator-plan.js +10 -47
  39. package/dist/server/types.d.ts +47 -0
  40. package/dist/server/types.js +24 -0
  41. package/dist/server/utils/ctx-utils.d.ts +30 -0
  42. package/dist/server/utils/ctx-utils.js +152 -0
  43. package/dist/server/utils/logging.d.ts +6 -0
  44. package/dist/server/utils/logging.js +86 -0
  45. package/package.json +44 -44
  46. package/src/client/AgentRunsTab.tsx +764 -764
  47. package/src/client/HarnessProfilesTab.tsx +247 -247
  48. package/src/client/OrchestratorSettings.tsx +106 -106
  49. package/src/client/RulesTab.tsx +716 -716
  50. package/src/client/hooks/useRunEventStream.ts +76 -0
  51. package/src/client/index.tsx +2 -1
  52. package/src/client/plugin.tsx +27 -27
  53. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  54. package/src/client/skill-hub/index.tsx +51 -51
  55. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
  56. package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
  57. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  58. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
  59. package/src/client/tools/PlanApprovalCard.tsx +175 -175
  60. package/src/client/tools/registerOrchestratorCards.ts +7 -7
  61. package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
  62. package/src/server/__tests__/circuit-breaker.test.ts +169 -0
  63. package/src/server/__tests__/context-aggregator.test.ts +222 -0
  64. package/src/server/__tests__/parallel-execution.test.ts +318 -0
  65. package/src/server/__tests__/smoke.test.ts +120 -0
  66. package/src/server/collections/agent-execution-spans.ts +24 -0
  67. package/src/server/collections/agent-harness-profiles.ts +59 -59
  68. package/src/server/collections/agent-loop-events.ts +71 -71
  69. package/src/server/collections/agent-loop-runs.ts +38 -1
  70. package/src/server/collections/agent-loop-steps.ts +144 -144
  71. package/src/server/collections/orchestrator-config.ts +14 -0
  72. package/src/server/collections/skill-executions.ts +106 -106
  73. package/src/server/collections/skill-loop-configs.ts +65 -65
  74. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  75. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -142
  76. package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
  77. package/src/server/plugin.ts +53 -0
  78. package/src/server/resources/agent-loop.ts +21 -12
  79. package/src/server/resources/tracing.ts +3 -7
  80. package/src/server/services/AgentHarness.ts +78 -116
  81. package/src/server/services/AgentLoopController.ts +197 -122
  82. package/src/server/services/AgentLoopRepository.ts +9 -25
  83. package/src/server/services/AgentLoopService.ts +13 -1
  84. package/src/server/services/AgentPlanValidator.ts +73 -73
  85. package/src/server/services/AgentPlannerService.ts +2 -25
  86. package/src/server/services/AgentRegistryService.ts +40 -31
  87. package/src/server/services/CircuitBreaker.ts +116 -0
  88. package/src/server/services/ContextAggregator.ts +239 -0
  89. package/src/server/services/ExecutionSpanService.ts +2 -4
  90. package/src/server/services/RunEventBus.ts +45 -0
  91. package/src/server/services/TokenTracker.ts +209 -0
  92. package/src/server/skill-hub/plugin.ts +898 -897
  93. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -458
  94. package/src/server/tools/agent-loop.ts +18 -57
  95. package/src/server/tools/delegate-task.ts +11 -93
  96. package/src/server/tools/orchestrator-plan.ts +26 -50
  97. package/src/server/tools/skill-execute.ts +160 -160
  98. package/src/server/types.ts +55 -0
  99. package/src/server/utils/ctx-utils.ts +118 -0
  100. package/src/server/utils/logging.ts +63 -0
@@ -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
+ }