plugin-agent-orchestrator 1.0.27 → 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.
Files changed (110) hide show
  1. package/README.md +9 -7
  2. package/dist/client/index.js +1 -1
  3. package/dist/client-v2/{214.723affb37c13bf7a.js → 214.79650a549273f163.js} +1 -1
  4. package/dist/client-v2/264.718a107e43fc163c.js +10 -0
  5. package/dist/client-v2/373.f5d5292e53c4e832.js +10 -0
  6. package/dist/client-v2/{41.1805b2edfaa4afe2.js → 41.ba6e080cc0488143.js} +1 -1
  7. package/dist/client-v2/418.29e713f79131eece.js +10 -0
  8. package/dist/client-v2/619.bd3c5698b40705c3.js +10 -0
  9. package/dist/client-v2/677.a991ce0250ff5c77.js +10 -0
  10. package/dist/client-v2/{70.a15d7fcec7c41768.js → 70.bda9518881c05360.js} +1 -1
  11. package/dist/client-v2/925.f5370de8f6632d65.js +10 -0
  12. package/dist/client-v2/index.js +1 -1
  13. package/dist/externalVersion.js +7 -10
  14. package/dist/locale/en-US.json +94 -25
  15. package/dist/locale/vi-VN.json +94 -25
  16. package/dist/locale/zh-CN.json +94 -25
  17. package/dist/server/collections/agent-execution-spans.js +37 -0
  18. package/dist/server/collections/agent-harness-profiles.js +2 -2
  19. package/dist/server/collections/agent-memory-contexts.js +125 -0
  20. package/dist/server/collections/orchestrator-logs.js +2 -2
  21. package/dist/server/migrations/20260425000000-add-interaction-schema.js +3 -1
  22. package/dist/server/migrations/20260427000000-change-packages-to-text.js +3 -1
  23. package/dist/server/migrations/20260427000001-change-other-json-to-text.js +6 -2
  24. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +21 -19
  25. package/dist/server/migrations/20260621000000-native-policy-profile-defaults.js +193 -0
  26. package/dist/server/plugin.js +128 -74
  27. package/dist/server/resources/agent-monitor.js +454 -0
  28. package/dist/server/services/AgentHarness.js +24 -499
  29. package/dist/server/services/AgentMemoryContextService.js +216 -0
  30. package/dist/server/services/ExecutionSpanService.js +2 -2
  31. package/dist/server/services/NativeSubAgentObserver.js +413 -0
  32. package/dist/server/skill-hub/mcp/McpController.js +16 -5
  33. package/dist/server/skill-hub/plugin.js +81 -5
  34. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
  35. package/dist/server/tools/delegate-task.js +11 -589
  36. package/dist/server/utils/skill-settings.js +18 -1
  37. package/package.json +47 -49
  38. package/src/client/AIEmployeesContext.tsx +5 -18
  39. package/src/client/AgentRunsTab.tsx +2 -771
  40. package/src/client/HarnessProfilesTab.tsx +2 -257
  41. package/src/client/OrchestratorSettings.tsx +97 -106
  42. package/src/client/RulesTab.tsx +2 -788
  43. package/src/client/plugin.tsx +0 -2
  44. package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
  45. package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
  46. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  47. package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
  48. package/src/client/skill-hub/components/SkillManager.tsx +194 -181
  49. package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
  50. package/src/client/skill-hub/locale.ts +16 -16
  51. package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
  52. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  53. package/src/client/skill-hub/utils/jsonFields.ts +7 -3
  54. package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
  55. package/src/client-v2/components/AgentRunsTab.tsx +182 -455
  56. package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
  57. package/src/client-v2/components/RulesTab.tsx +2 -782
  58. package/src/client-v2/components/TracingTab.tsx +1 -1
  59. package/src/client-v2/hooks/useApiRequest.ts +8 -1
  60. package/src/client-v2/pages/RulesPage.tsx +2 -2
  61. package/src/client-v2/plugin.tsx +3 -3
  62. package/src/locale/en-US.json +94 -25
  63. package/src/locale/vi-VN.json +94 -25
  64. package/src/locale/zh-CN.json +94 -25
  65. package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
  66. package/src/server/__tests__/skill-settings.test.ts +6 -6
  67. package/src/server/__tests__/smoke.test.ts +1 -0
  68. package/src/server/collections/agent-execution-spans.ts +37 -0
  69. package/src/server/collections/agent-harness-profiles.ts +59 -59
  70. package/src/server/collections/agent-loop-events.ts +71 -71
  71. package/src/server/collections/agent-loop-steps.ts +144 -144
  72. package/src/server/collections/agent-memory-contexts.ts +95 -0
  73. package/src/server/collections/orchestrator-logs.ts +4 -4
  74. package/src/server/collections/skill-definitions.ts +111 -111
  75. package/src/server/collections/skill-executions.ts +106 -106
  76. package/src/server/collections/skill-loop-configs.ts +65 -65
  77. package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
  78. package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
  79. package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
  80. package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
  81. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  82. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
  83. package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
  84. package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
  85. package/src/server/plugin.ts +151 -94
  86. package/src/server/resources/agent-monitor.ts +482 -0
  87. package/src/server/services/AgentHarness.ts +38 -623
  88. package/src/server/services/AgentMemoryContextService.ts +256 -0
  89. package/src/server/services/AgentPlanValidator.ts +73 -73
  90. package/src/server/services/ExecutionSpanService.ts +6 -2
  91. package/src/server/services/FileManager.ts +144 -144
  92. package/src/server/services/NativeSubAgentObserver.ts +507 -0
  93. package/src/server/services/SkillManager.ts +583 -583
  94. package/src/server/services/SkillRepositoryService.ts +5 -7
  95. package/src/server/services/TokenTracker.ts +3 -3
  96. package/src/server/services/WorkerEnvManager.ts +1 -2
  97. package/src/server/skill-hub/actions/git-import.ts +5 -7
  98. package/src/server/skill-hub/mcp/McpController.ts +41 -14
  99. package/src/server/skill-hub/plugin.ts +89 -6
  100. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
  101. package/src/server/skill-hub/utils/json-fields.ts +1 -1
  102. package/src/server/tools/delegate-task.ts +13 -847
  103. package/src/server/utils/skill-settings.ts +24 -6
  104. package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
  105. package/dist/client-v2/418.5ae055abf141820e.js +0 -10
  106. package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
  107. package/dist/client-v2/892.72db4161511c8a16.js +0 -10
  108. package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
  109. package/src/client/tools/PlanApprovalCard.tsx +0 -176
  110. package/src/client/tools/registerOrchestratorCards.ts +0 -17
@@ -282,44 +282,44 @@ export const SkillEditor: React.FC<SkillEditorProps> = ({ skill, onClose }) => {
282
282
 
283
283
  {storageType === 'plugin' && (
284
284
  <>
285
- <Form.Item name="inputSchema" hidden>
286
- <TextArea />
287
- </Form.Item>
288
- <Form.Item name="packages" hidden>
289
- <Input />
290
- </Form.Item>
291
- <Form.Item
292
- name="pluginSource"
293
- label={t('Select Plugin Skill')}
294
- rules={[{ required: true }]}
295
- style={{
296
- marginBottom: 24,
297
- padding: 12,
298
- background: '#e6f4ff',
299
- borderRadius: 8,
300
- border: '1px solid #91caff',
301
- }}
302
- extra={
303
- <div style={{ fontSize: 12, color: '#1677ff', marginTop: 8 }}>
304
- {t(
305
- 'Binding dynamically delegates execution to the plugin logic. Code, Language, and Schemas are managed externally.',
306
- )}
307
- </div>
308
- }
309
- >
310
- <Select
311
- placeholder={t('Choose an enabled plugin skill to attach')}
312
- onChange={handleTemplateSelect}
313
- allowClear
285
+ <Form.Item name="inputSchema" hidden>
286
+ <TextArea />
287
+ </Form.Item>
288
+ <Form.Item name="packages" hidden>
289
+ <Input />
290
+ </Form.Item>
291
+ <Form.Item
292
+ name="pluginSource"
293
+ label={t('Select Plugin Skill')}
294
+ rules={[{ required: true }]}
295
+ style={{
296
+ marginBottom: 24,
297
+ padding: 12,
298
+ background: '#e6f4ff',
299
+ borderRadius: 8,
300
+ border: '1px solid #91caff',
301
+ }}
302
+ extra={
303
+ <div style={{ fontSize: 12, color: '#1677ff', marginTop: 8 }}>
304
+ {t(
305
+ 'Binding dynamically delegates execution to the plugin logic. Code, Language, and Schemas are managed externally.',
306
+ )}
307
+ </div>
308
+ }
314
309
  >
315
- {Array.isArray(templates) &&
316
- templates.map((tmpl) => (
317
- <Select.Option key={tmpl.name} value={tmpl.name}>
318
- {tmpl.title} ({tmpl.pluginSource || tmpl.name})
319
- </Select.Option>
320
- ))}
321
- </Select>
322
- </Form.Item>
310
+ <Select
311
+ placeholder={t('Choose an enabled plugin skill to attach')}
312
+ onChange={handleTemplateSelect}
313
+ allowClear
314
+ >
315
+ {Array.isArray(templates) &&
316
+ templates.map((tmpl) => (
317
+ <Select.Option key={tmpl.name} value={tmpl.name}>
318
+ {tmpl.title} ({tmpl.pluginSource || tmpl.name})
319
+ </Select.Option>
320
+ ))}
321
+ </Select>
322
+ </Form.Item>
323
323
  </>
324
324
  )}
325
325
 
@@ -417,12 +417,16 @@ export const SkillEditor: React.FC<SkillEditorProps> = ({ skill, onClose }) => {
417
417
  <Form.Item
418
418
  name="interactionSchema"
419
419
  label={t('Interaction Schema (optional)')}
420
- extra={t('If set, user is prompted to fill / confirm input before execution. Type: form | select | confirm.')}
420
+ extra={t(
421
+ 'If set, user is prompted to fill / confirm input before execution. Type: form | select | confirm.',
422
+ )}
421
423
  >
422
424
  <TextArea
423
425
  rows={6}
424
426
  style={{ fontFamily: 'monospace', fontSize: 13 }}
425
- placeholder={'{\n "type": "form",\n "prompt": "Confirm parameters",\n "fields": {\n "fileName": { "type": "string", "title": "File name", "required": true }\n }\n}'}
427
+ placeholder={
428
+ '{\n "type": "form",\n "prompt": "Confirm parameters",\n "fields": {\n "fileName": { "type": "string", "title": "File name", "required": true }\n }\n}'
429
+ }
426
430
  />
427
431
  </Form.Item>
428
432
 
@@ -1,181 +1,194 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
2
- import {
3
- Card,
4
- Button,
5
- Space,
6
- Modal,
7
- Form,
8
- Input,
9
- Select,
10
- Switch,
11
- InputNumber,
12
- message,
13
- Popconfirm,
14
- Tag,
15
- List,
16
- Typography,
17
- Tooltip,
18
- } from 'antd';
19
- import { PlusOutlined, EditOutlined, DeleteOutlined, PlayCircleOutlined, BranchesOutlined } from '@ant-design/icons';
20
- import { useApp } from '@nocobase/client-v2';
21
- import { useT } from '../locale';
22
- import { SkillEditor } from './SkillEditor';
23
- import { SkillTestPanel } from './SkillTestPanel';
24
- import { GitSkillImport } from './GitSkillImport';
25
-
26
- const { TextArea } = Input;
27
-
28
- export const SkillManager: React.FC = () => {
29
- const api = useApp().apiClient;
30
- const t = useT();
31
- const [skills, setSkills] = useState<any[]>([]);
32
- const [loading, setLoading] = useState(false);
33
- const [editorVisible, setEditorVisible] = useState(false);
34
- const [testVisible, setTestVisible] = useState(false);
35
- const [editingSkill, setEditingSkill] = useState<any>(null);
36
- const [testingSkill, setTestingSkill] = useState<any>(null);
37
- const [gitImportVisible, setGitImportVisible] = useState(false);
38
-
39
- const fetchSkills = useCallback(async () => {
40
- setLoading(true);
41
- try {
42
- const { data } = await api.request({ url: 'skillDefinitions:list', params: { pageSize: 100 } });
43
- const rawData = data?.data?.data ?? data?.data ?? [];
44
- setSkills(Array.isArray(rawData) ? rawData : []);
45
- } catch {
46
- message.error(t('Failed to load skills'));
47
- } finally {
48
- setLoading(false);
49
- }
50
- }, [api, t]);
51
-
52
- useEffect(() => {
53
- fetchSkills();
54
- }, [fetchSkills]);
55
-
56
- const handleCreate = () => {
57
- setEditingSkill(null);
58
- setEditorVisible(true);
59
- };
60
-
61
- const handleEdit = (record: any) => {
62
- setEditingSkill(record);
63
- setEditorVisible(true);
64
- };
65
-
66
- const handleTest = (record: any) => {
67
- setTestingSkill(record);
68
- setTestVisible(true);
69
- };
70
-
71
- const handleDelete = async (id: number) => {
72
- try {
73
- await api.request({ url: 'skillDefinitions:destroy', method: 'POST', params: { filterByTk: id } });
74
- message.success(t('Deleted'));
75
- fetchSkills();
76
- } catch {
77
- message.error(t('Failed to delete'));
78
- }
79
- };
80
-
81
- const handleToggleEnabled = async (record: any) => {
82
- try {
83
- await api.request({
84
- url: 'skillDefinitions:update',
85
- method: 'POST',
86
- params: { filterByTk: record.id },
87
- data: { enabled: !record.enabled },
88
- });
89
- fetchSkills();
90
- } catch {
91
- message.error(t('Failed to update'));
92
- }
93
- };
94
-
95
- const handleEditorClose = (saved?: boolean) => {
96
- setEditorVisible(false);
97
- setEditingSkill(null);
98
- if (saved) fetchSkills();
99
- };
100
-
101
- // Table columns definition removed in favor of List rendering
102
-
103
- return (
104
- <Card
105
- title={t('Skill Definitions')}
106
- extra={
107
- <Space>
108
- <Button icon={<BranchesOutlined />} onClick={() => setGitImportVisible(true)}>
109
- {t('Import from Git')}
110
- </Button>
111
- <Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
112
- {t('New Skill')}
113
- </Button>
114
- </Space>
115
- }
116
- >
117
- <List
118
- grid={{ gutter: 16, xs: 1, sm: 2, md: 3, lg: 3, xl: 4, xxl: 4 }}
119
- dataSource={skills}
120
- loading={loading}
121
- renderItem={(skill) => (
122
- <List.Item>
123
- <Card
124
- size="small"
125
- title={<Typography.Text ellipsis title={skill.title}>{skill.title}</Typography.Text>}
126
- extra={<Tag color={skill.language === 'python' ? 'blue' : 'green'}>{skill.language}</Tag>}
127
- actions={[
128
- <Tooltip title={t('Test')}>
129
- <PlayCircleOutlined key="test" onClick={() => handleTest(skill)} />
130
- </Tooltip>,
131
- <Tooltip title={t('Edit')}>
132
- <EditOutlined key="edit" onClick={() => handleEdit(skill)} />
133
- </Tooltip>,
134
- <Popconfirm title={t('Delete?')} onConfirm={() => handleDelete(skill.id)}>
135
- <Tooltip title={t('Delete')}>
136
- <DeleteOutlined key="delete" style={{ color: 'red' }} />
137
- </Tooltip>
138
- </Popconfirm>,
139
- ]}
140
- style={{ boxShadow: '0 2px 8px rgba(0,0,0,0.05)', borderRadius: 8 }}
141
- >
142
- <Card.Meta
143
- title={<Typography.Text type="secondary" style={{ fontSize: 13 }}>{skill.name}</Typography.Text>}
144
- description={
145
- <div style={{ height: 60, overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical', fontSize: 13 }}>
146
- {skill.description || t('No description')}
147
- </div>
148
- }
149
- />
150
- <div style={{ marginTop: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
151
- <Space size={4}>
152
- <Switch checked={skill.enabled} onChange={() => handleToggleEnabled(skill)} size="small" />
153
- <span style={{ fontSize: 12 }}>{skill.enabled ? t('Enabled') : t('Disabled')}</span>
154
- </Space>
155
- <Tag color="purple" style={{ margin: 0, fontSize: 11 }}>
156
- {skill.storageType ? skill.storageType.toUpperCase() : 'DB'}
157
- </Tag>
158
- </div>
159
- </Card>
160
- </List.Item>
161
- )}
162
- />
163
-
164
- {editorVisible && (
165
- <SkillEditor skill={editingSkill} onClose={handleEditorClose} />
166
- )}
167
-
168
- {testVisible && testingSkill && (
169
- <SkillTestPanel skill={testingSkill} onClose={() => setTestVisible(false)} />
170
- )}
171
-
172
- <GitSkillImport
173
- open={gitImportVisible}
174
- onClose={(synced) => {
175
- setGitImportVisible(false);
176
- if (synced) fetchSkills();
177
- }}
178
- />
179
- </Card>
180
- );
181
- };
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import {
3
+ Card,
4
+ Button,
5
+ Space,
6
+ Modal,
7
+ Form,
8
+ Input,
9
+ Select,
10
+ Switch,
11
+ InputNumber,
12
+ message,
13
+ Popconfirm,
14
+ Tag,
15
+ List,
16
+ Typography,
17
+ Tooltip,
18
+ } from 'antd';
19
+ import { PlusOutlined, EditOutlined, DeleteOutlined, PlayCircleOutlined, BranchesOutlined } from '@ant-design/icons';
20
+ import { useApp } from '@nocobase/client-v2';
21
+ import { useT } from '../locale';
22
+ import { SkillEditor } from './SkillEditor';
23
+ import { SkillTestPanel } from './SkillTestPanel';
24
+ import { GitSkillImport } from './GitSkillImport';
25
+
26
+ const { TextArea } = Input;
27
+
28
+ export const SkillManager: React.FC = () => {
29
+ const api = useApp().apiClient;
30
+ const t = useT();
31
+ const [skills, setSkills] = useState<any[]>([]);
32
+ const [loading, setLoading] = useState(false);
33
+ const [editorVisible, setEditorVisible] = useState(false);
34
+ const [testVisible, setTestVisible] = useState(false);
35
+ const [editingSkill, setEditingSkill] = useState<any>(null);
36
+ const [testingSkill, setTestingSkill] = useState<any>(null);
37
+ const [gitImportVisible, setGitImportVisible] = useState(false);
38
+
39
+ const fetchSkills = useCallback(async () => {
40
+ setLoading(true);
41
+ try {
42
+ const { data } = await api.request({ url: 'skillDefinitions:list', params: { pageSize: 100 } });
43
+ const rawData = data?.data?.data ?? data?.data ?? [];
44
+ setSkills(Array.isArray(rawData) ? rawData : []);
45
+ } catch {
46
+ message.error(t('Failed to load skills'));
47
+ } finally {
48
+ setLoading(false);
49
+ }
50
+ }, [api, t]);
51
+
52
+ useEffect(() => {
53
+ fetchSkills();
54
+ }, [fetchSkills]);
55
+
56
+ const handleCreate = () => {
57
+ setEditingSkill(null);
58
+ setEditorVisible(true);
59
+ };
60
+
61
+ const handleEdit = (record: any) => {
62
+ setEditingSkill(record);
63
+ setEditorVisible(true);
64
+ };
65
+
66
+ const handleTest = (record: any) => {
67
+ setTestingSkill(record);
68
+ setTestVisible(true);
69
+ };
70
+
71
+ const handleDelete = async (id: number) => {
72
+ try {
73
+ await api.request({ url: 'skillDefinitions:destroy', method: 'POST', params: { filterByTk: id } });
74
+ message.success(t('Deleted'));
75
+ fetchSkills();
76
+ } catch {
77
+ message.error(t('Failed to delete'));
78
+ }
79
+ };
80
+
81
+ const handleToggleEnabled = async (record: any) => {
82
+ try {
83
+ await api.request({
84
+ url: 'skillDefinitions:update',
85
+ method: 'POST',
86
+ params: { filterByTk: record.id },
87
+ data: { enabled: !record.enabled },
88
+ });
89
+ fetchSkills();
90
+ } catch {
91
+ message.error(t('Failed to update'));
92
+ }
93
+ };
94
+
95
+ const handleEditorClose = (saved?: boolean) => {
96
+ setEditorVisible(false);
97
+ setEditingSkill(null);
98
+ if (saved) fetchSkills();
99
+ };
100
+
101
+ // Table columns definition removed in favor of List rendering
102
+
103
+ return (
104
+ <Card
105
+ title={t('Skill Definitions')}
106
+ extra={
107
+ <Space>
108
+ <Button icon={<BranchesOutlined />} onClick={() => setGitImportVisible(true)}>
109
+ {t('Import from Git')}
110
+ </Button>
111
+ <Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
112
+ {t('New Skill')}
113
+ </Button>
114
+ </Space>
115
+ }
116
+ >
117
+ <List
118
+ grid={{ gutter: 16, xs: 1, sm: 2, md: 3, lg: 3, xl: 4, xxl: 4 }}
119
+ dataSource={skills}
120
+ loading={loading}
121
+ renderItem={(skill) => (
122
+ <List.Item>
123
+ <Card
124
+ size="small"
125
+ title={
126
+ <Typography.Text ellipsis title={skill.title}>
127
+ {skill.title}
128
+ </Typography.Text>
129
+ }
130
+ extra={<Tag color={skill.language === 'python' ? 'blue' : 'green'}>{skill.language}</Tag>}
131
+ actions={[
132
+ <Tooltip key="test" title={t('Test')}>
133
+ <PlayCircleOutlined onClick={() => handleTest(skill)} />
134
+ </Tooltip>,
135
+ <Tooltip key="edit" title={t('Edit')}>
136
+ <EditOutlined onClick={() => handleEdit(skill)} />
137
+ </Tooltip>,
138
+ <Popconfirm key="delete" title={t('Delete?')} onConfirm={() => handleDelete(skill.id)}>
139
+ <Tooltip title={t('Delete')}>
140
+ <DeleteOutlined style={{ color: 'red' }} />
141
+ </Tooltip>
142
+ </Popconfirm>,
143
+ ]}
144
+ style={{ boxShadow: '0 2px 8px rgba(0,0,0,0.05)', borderRadius: 8 }}
145
+ >
146
+ <Card.Meta
147
+ title={
148
+ <Typography.Text type="secondary" style={{ fontSize: 13 }}>
149
+ {skill.name}
150
+ </Typography.Text>
151
+ }
152
+ description={
153
+ <div
154
+ style={{
155
+ height: 60,
156
+ overflow: 'hidden',
157
+ display: '-webkit-box',
158
+ WebkitLineClamp: 3,
159
+ WebkitBoxOrient: 'vertical',
160
+ fontSize: 13,
161
+ }}
162
+ >
163
+ {skill.description || t('No description')}
164
+ </div>
165
+ }
166
+ />
167
+ <div style={{ marginTop: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
168
+ <Space size={4}>
169
+ <Switch checked={skill.enabled} onChange={() => handleToggleEnabled(skill)} size="small" />
170
+ <span style={{ fontSize: 12 }}>{skill.enabled ? t('Enabled') : t('Disabled')}</span>
171
+ </Space>
172
+ <Tag color="purple" style={{ margin: 0, fontSize: 11 }}>
173
+ {skill.storageType ? skill.storageType.toUpperCase() : 'DB'}
174
+ </Tag>
175
+ </div>
176
+ </Card>
177
+ </List.Item>
178
+ )}
179
+ />
180
+
181
+ {editorVisible && <SkillEditor skill={editingSkill} onClose={handleEditorClose} />}
182
+
183
+ {testVisible && testingSkill && <SkillTestPanel skill={testingSkill} onClose={() => setTestVisible(false)} />}
184
+
185
+ <GitSkillImport
186
+ open={gitImportVisible}
187
+ onClose={(synced) => {
188
+ setGitImportVisible(false);
189
+ if (synced) fetchSkills();
190
+ }}
191
+ />
192
+ </Card>
193
+ );
194
+ };