plugin-agent-orchestrator 1.0.28 → 1.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) 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/plugin.js +81 -5
  33. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
  34. package/dist/server/tools/delegate-task.js +11 -589
  35. package/dist/server/utils/skill-settings.js +18 -1
  36. package/package.json +47 -49
  37. package/src/client/AIEmployeesContext.tsx +5 -18
  38. package/src/client/AgentRunsTab.tsx +2 -771
  39. package/src/client/HarnessProfilesTab.tsx +2 -257
  40. package/src/client/OrchestratorSettings.tsx +97 -106
  41. package/src/client/RulesTab.tsx +2 -788
  42. package/src/client/plugin.tsx +0 -2
  43. package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
  44. package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
  45. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  46. package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
  47. package/src/client/skill-hub/components/SkillManager.tsx +194 -181
  48. package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
  49. package/src/client/skill-hub/locale.ts +16 -16
  50. package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
  51. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  52. package/src/client/skill-hub/utils/jsonFields.ts +7 -3
  53. package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
  54. package/src/client-v2/components/AgentRunsTab.tsx +182 -455
  55. package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
  56. package/src/client-v2/components/RulesTab.tsx +2 -782
  57. package/src/client-v2/components/TracingTab.tsx +1 -1
  58. package/src/client-v2/hooks/useApiRequest.ts +8 -1
  59. package/src/client-v2/pages/RulesPage.tsx +2 -2
  60. package/src/client-v2/plugin.tsx +3 -3
  61. package/src/locale/en-US.json +94 -25
  62. package/src/locale/vi-VN.json +94 -25
  63. package/src/locale/zh-CN.json +94 -25
  64. package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
  65. package/src/server/__tests__/skill-settings.test.ts +6 -6
  66. package/src/server/__tests__/smoke.test.ts +1 -0
  67. package/src/server/collections/agent-execution-spans.ts +37 -0
  68. package/src/server/collections/agent-harness-profiles.ts +59 -59
  69. package/src/server/collections/agent-loop-events.ts +71 -71
  70. package/src/server/collections/agent-loop-steps.ts +144 -144
  71. package/src/server/collections/agent-memory-contexts.ts +95 -0
  72. package/src/server/collections/orchestrator-logs.ts +4 -4
  73. package/src/server/collections/skill-definitions.ts +111 -111
  74. package/src/server/collections/skill-executions.ts +106 -106
  75. package/src/server/collections/skill-loop-configs.ts +65 -65
  76. package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
  77. package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
  78. package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
  79. package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
  80. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  81. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
  82. package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
  83. package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
  84. package/src/server/plugin.ts +151 -94
  85. package/src/server/resources/agent-monitor.ts +482 -0
  86. package/src/server/services/AgentHarness.ts +38 -623
  87. package/src/server/services/AgentMemoryContextService.ts +256 -0
  88. package/src/server/services/AgentPlanValidator.ts +73 -73
  89. package/src/server/services/ExecutionSpanService.ts +6 -2
  90. package/src/server/services/FileManager.ts +144 -144
  91. package/src/server/services/NativeSubAgentObserver.ts +507 -0
  92. package/src/server/services/SkillManager.ts +583 -583
  93. package/src/server/services/SkillRepositoryService.ts +5 -7
  94. package/src/server/services/TokenTracker.ts +3 -3
  95. package/src/server/services/WorkerEnvManager.ts +1 -2
  96. package/src/server/skill-hub/actions/git-import.ts +5 -7
  97. package/src/server/skill-hub/plugin.ts +89 -6
  98. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
  99. package/src/server/skill-hub/utils/json-fields.ts +1 -1
  100. package/src/server/tools/delegate-task.ts +13 -847
  101. package/src/server/utils/skill-settings.ts +24 -6
  102. package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
  103. package/dist/client-v2/418.5ae055abf141820e.js +0 -10
  104. package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
  105. package/dist/client-v2/892.72db4161511c8a16.js +0 -10
  106. package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
  107. package/src/client/tools/PlanApprovalCard.tsx +0 -176
  108. 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
+ };