plugin-agent-orchestrator 1.0.20 → 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 (98) 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/tools/agent-loop.d.ts +8 -8
  33. package/dist/server/tools/agent-loop.js +30 -63
  34. package/dist/server/tools/delegate-task.js +14 -72
  35. package/dist/server/tools/orchestrator-plan.d.ts +6 -6
  36. package/dist/server/tools/orchestrator-plan.js +10 -47
  37. package/dist/server/types.d.ts +47 -0
  38. package/dist/server/types.js +24 -0
  39. package/dist/server/utils/ctx-utils.d.ts +30 -0
  40. package/dist/server/utils/ctx-utils.js +152 -0
  41. package/dist/server/utils/logging.d.ts +6 -0
  42. package/dist/server/utils/logging.js +86 -0
  43. package/package.json +44 -44
  44. package/src/client/AgentRunsTab.tsx +764 -764
  45. package/src/client/HarnessProfilesTab.tsx +247 -247
  46. package/src/client/OrchestratorSettings.tsx +106 -106
  47. package/src/client/RulesTab.tsx +716 -716
  48. package/src/client/hooks/useRunEventStream.ts +76 -0
  49. package/src/client/index.tsx +2 -1
  50. package/src/client/plugin.tsx +27 -27
  51. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  52. package/src/client/skill-hub/index.tsx +51 -51
  53. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
  54. package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
  55. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  56. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
  57. package/src/client/tools/PlanApprovalCard.tsx +175 -175
  58. package/src/client/tools/registerOrchestratorCards.ts +7 -7
  59. package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
  60. package/src/server/__tests__/circuit-breaker.test.ts +169 -0
  61. package/src/server/__tests__/context-aggregator.test.ts +222 -0
  62. package/src/server/__tests__/parallel-execution.test.ts +318 -0
  63. package/src/server/__tests__/smoke.test.ts +120 -0
  64. package/src/server/collections/agent-execution-spans.ts +24 -0
  65. package/src/server/collections/agent-harness-profiles.ts +59 -59
  66. package/src/server/collections/agent-loop-events.ts +71 -71
  67. package/src/server/collections/agent-loop-runs.ts +38 -1
  68. package/src/server/collections/agent-loop-steps.ts +144 -144
  69. package/src/server/collections/orchestrator-config.ts +14 -0
  70. package/src/server/collections/skill-executions.ts +106 -106
  71. package/src/server/collections/skill-loop-configs.ts +65 -65
  72. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  73. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -142
  74. package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
  75. package/src/server/plugin.ts +53 -0
  76. package/src/server/resources/agent-loop.ts +21 -12
  77. package/src/server/resources/tracing.ts +3 -7
  78. package/src/server/services/AgentHarness.ts +78 -116
  79. package/src/server/services/AgentLoopController.ts +197 -122
  80. package/src/server/services/AgentLoopRepository.ts +9 -25
  81. package/src/server/services/AgentLoopService.ts +13 -1
  82. package/src/server/services/AgentPlanValidator.ts +73 -73
  83. package/src/server/services/AgentPlannerService.ts +2 -25
  84. package/src/server/services/AgentRegistryService.ts +40 -31
  85. package/src/server/services/CircuitBreaker.ts +116 -0
  86. package/src/server/services/ContextAggregator.ts +239 -0
  87. package/src/server/services/ExecutionSpanService.ts +2 -4
  88. package/src/server/services/RunEventBus.ts +45 -0
  89. package/src/server/services/TokenTracker.ts +209 -0
  90. package/src/server/skill-hub/plugin.ts +898 -898
  91. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -460
  92. package/src/server/tools/agent-loop.ts +18 -57
  93. package/src/server/tools/delegate-task.ts +11 -93
  94. package/src/server/tools/orchestrator-plan.ts +26 -50
  95. package/src/server/tools/skill-execute.ts +160 -160
  96. package/src/server/types.ts +55 -0
  97. package/src/server/utils/ctx-utils.ts +118 -0
  98. 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
+ }