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
@@ -1,145 +1,141 @@
1
- import React, { useState } from 'react';
2
- import { Modal, Input, Button, Alert, Typography, Space, Spin } from 'antd';
3
- import { Upload } from '@nocobase/client';
4
- import { useApp } from '@nocobase/client-v2';
5
- import { useT } from '../locale';
6
- import { parseJsonText } from '../utils/jsonFields';
7
-
8
- const { TextArea } = Input;
9
-
10
- interface SkillTestPanelProps {
11
- skill: any;
12
- onClose: () => void;
13
- }
14
-
15
- export const SkillTestPanel: React.FC<SkillTestPanelProps> = ({ skill, onClose }) => {
16
- const api = useApp().apiClient;
17
- const t = useT();
18
- const inputSchema = parseJsonText(skill.inputSchema, null);
19
- const [input, setInput] = useState(
20
- inputSchema?.properties
21
- ? JSON.stringify(
22
- Object.fromEntries(
23
- Object.keys(inputSchema.properties).map((k) => [k, '']),
24
- ),
25
- null,
26
- 2,
27
- )
28
- : '{}',
29
- );
30
- const [running, setRunning] = useState(false);
31
- const [result, setResult] = useState<any>(null);
32
- const [error, setError] = useState('');
33
-
34
- const handleRun = async () => {
35
- setRunning(true);
36
- setResult(null);
37
- setError('');
38
-
39
- let parsedInput;
40
- try {
41
- parsedInput = JSON.parse(input);
42
- } catch {
43
- setError(t('Invalid JSON input'));
44
- setRunning(false);
45
- return;
46
- }
47
-
48
- try {
49
- const { data } = await api.request({
50
- url: 'skillHub:test',
51
- method: 'POST',
52
- data: { skillId: skill.id, input: parsedInput },
53
- });
54
- const responseData = data?.data?.data || data?.data || data;
55
- setResult(responseData);
56
- } catch (err: any) {
57
- setError(err?.response?.data?.errors?.[0]?.message || err.message || t('Execution failed'));
58
- } finally {
59
- setRunning(false);
60
- }
61
- };
62
-
63
- return (
64
- <Modal
65
- open
66
- title={`${t('Test Skill')}: ${skill.title}`}
67
- onCancel={onClose}
68
- footer={[
69
- <Button key="close" onClick={onClose}>
70
- {t('Close')}
71
- </Button>,
72
- <Button key="run" type="primary" onClick={handleRun} loading={running}>
73
- {t('Run')}
74
- </Button>,
75
- ]}
76
- width={640}
77
- destroyOnClose
78
- >
79
- <Space direction="vertical" style={{ width: '100%' }} size="middle">
80
- <div>
81
- <Typography.Text strong>{t('Input (JSON)')}</Typography.Text>
82
- <TextArea
83
- rows={6}
84
- value={input}
85
- onChange={(e) => setInput(e.target.value)}
86
- style={{ fontFamily: 'monospace', fontSize: 13, marginTop: 4 }}
87
- />
88
- </div>
89
-
90
- {running && <Spin tip={t('Running skill on worker...')} />}
91
-
92
- {error && <Alert type="error" message={error} showIcon />}
93
-
94
- {result && (
95
- <>
96
- <Alert
97
- type={result.status === 'succeeded' ? 'success' : 'error'}
98
- message={result.status === 'succeeded' ? t('Succeeded') : t('Failed')}
99
- description={`Duration: ${result.durationMs || 0}ms`}
100
- showIcon
101
- />
102
-
103
- {result.stdout && (
104
- <div>
105
- <Typography.Text strong>stdout:</Typography.Text>
106
- <pre style={{ background: '#f5f5f5', padding: 8, maxHeight: 200, overflow: 'auto', fontSize: 12 }}>
107
- {result.stdout}
108
- </pre>
109
- </div>
110
- )}
111
-
112
- {result.stderr && (
113
- <div>
114
- <Typography.Text strong type="danger">stderr:</Typography.Text>
115
- <pre style={{ background: '#fff2f0', padding: 8, maxHeight: 200, overflow: 'auto', fontSize: 12 }}>
116
- {result.stderr}
117
- </pre>
118
- </div>
119
- )}
120
-
121
- {result.files?.length > 0 && (
122
- <div>
123
- <Typography.Text strong>{t('Output Files')}:</Typography.Text>
124
- <div style={{ marginTop: 8 }}>
125
- <Upload.ReadPretty
126
- value={result.files.map((f: any, i: number) => ({
127
- id: `test-${f.name}-${i}`,
128
- title: f.name,
129
- filename: f.name,
130
- extname: f.name.includes('.') ? `.${f.name.split('.').pop()}` : '',
131
- url: f.downloadUrl,
132
- status: 'done'
133
- }))}
134
- multiple={true}
135
- showFileName={true}
136
- />
137
- </div>
138
- </div>
139
- )}
140
- </>
141
- )}
142
- </Space>
143
- </Modal>
144
- );
145
- };
1
+ import React, { useState } from 'react';
2
+ import { Modal, Input, Button, Alert, Typography, Space, Spin } from 'antd';
3
+ import { Upload } from '@nocobase/client';
4
+ import { useApp } from '@nocobase/client-v2';
5
+ import { useT } from '../locale';
6
+ import { parseJsonText } from '../utils/jsonFields';
7
+
8
+ const { TextArea } = Input;
9
+
10
+ interface SkillTestPanelProps {
11
+ skill: any;
12
+ onClose: () => void;
13
+ }
14
+
15
+ export const SkillTestPanel: React.FC<SkillTestPanelProps> = ({ skill, onClose }) => {
16
+ const api = useApp().apiClient;
17
+ const t = useT();
18
+ const inputSchema = parseJsonText(skill.inputSchema, null);
19
+ const [input, setInput] = useState(
20
+ inputSchema?.properties
21
+ ? JSON.stringify(Object.fromEntries(Object.keys(inputSchema.properties).map((k) => [k, ''])), null, 2)
22
+ : '{}',
23
+ );
24
+ const [running, setRunning] = useState(false);
25
+ const [result, setResult] = useState<any>(null);
26
+ const [error, setError] = useState('');
27
+
28
+ const handleRun = async () => {
29
+ setRunning(true);
30
+ setResult(null);
31
+ setError('');
32
+
33
+ let parsedInput;
34
+ try {
35
+ parsedInput = JSON.parse(input);
36
+ } catch {
37
+ setError(t('Invalid JSON input'));
38
+ setRunning(false);
39
+ return;
40
+ }
41
+
42
+ try {
43
+ const { data } = await api.request({
44
+ url: 'skillHub:test',
45
+ method: 'POST',
46
+ data: { skillId: skill.id, input: parsedInput },
47
+ });
48
+ const responseData = data?.data?.data || data?.data || data;
49
+ setResult(responseData);
50
+ } catch (err: any) {
51
+ setError(err?.response?.data?.errors?.[0]?.message || err.message || t('Execution failed'));
52
+ } finally {
53
+ setRunning(false);
54
+ }
55
+ };
56
+
57
+ return (
58
+ <Modal
59
+ open
60
+ title={`${t('Test Skill')}: ${skill.title}`}
61
+ onCancel={onClose}
62
+ footer={[
63
+ <Button key="close" onClick={onClose}>
64
+ {t('Close')}
65
+ </Button>,
66
+ <Button key="run" type="primary" onClick={handleRun} loading={running}>
67
+ {t('Run')}
68
+ </Button>,
69
+ ]}
70
+ width={640}
71
+ destroyOnClose
72
+ >
73
+ <Space direction="vertical" style={{ width: '100%' }} size="middle">
74
+ <div>
75
+ <Typography.Text strong>{t('Input (JSON)')}</Typography.Text>
76
+ <TextArea
77
+ rows={6}
78
+ value={input}
79
+ onChange={(e) => setInput(e.target.value)}
80
+ style={{ fontFamily: 'monospace', fontSize: 13, marginTop: 4 }}
81
+ />
82
+ </div>
83
+
84
+ {running && <Spin tip={t('Running skill on worker...')} />}
85
+
86
+ {error && <Alert type="error" message={error} showIcon />}
87
+
88
+ {result && (
89
+ <>
90
+ <Alert
91
+ type={result.status === 'succeeded' ? 'success' : 'error'}
92
+ message={result.status === 'succeeded' ? t('Succeeded') : t('Failed')}
93
+ description={`Duration: ${result.durationMs || 0}ms`}
94
+ showIcon
95
+ />
96
+
97
+ {result.stdout && (
98
+ <div>
99
+ <Typography.Text strong>stdout:</Typography.Text>
100
+ <pre style={{ background: '#f5f5f5', padding: 8, maxHeight: 200, overflow: 'auto', fontSize: 12 }}>
101
+ {result.stdout}
102
+ </pre>
103
+ </div>
104
+ )}
105
+
106
+ {result.stderr && (
107
+ <div>
108
+ <Typography.Text strong type="danger">
109
+ stderr:
110
+ </Typography.Text>
111
+ <pre style={{ background: '#fff2f0', padding: 8, maxHeight: 200, overflow: 'auto', fontSize: 12 }}>
112
+ {result.stderr}
113
+ </pre>
114
+ </div>
115
+ )}
116
+
117
+ {result.files?.length > 0 && (
118
+ <div>
119
+ <Typography.Text strong>{t('Output Files')}:</Typography.Text>
120
+ <div style={{ marginTop: 8 }}>
121
+ <Upload.ReadPretty
122
+ value={result.files.map((f: any, i: number) => ({
123
+ id: `test-${f.name}-${i}`,
124
+ title: f.name,
125
+ filename: f.name,
126
+ extname: f.name.includes('.') ? `.${f.name.split('.').pop()}` : '',
127
+ url: f.downloadUrl,
128
+ status: 'done',
129
+ }))}
130
+ multiple={true}
131
+ showFileName={true}
132
+ />
133
+ </div>
134
+ </div>
135
+ )}
136
+ </>
137
+ )}
138
+ </Space>
139
+ </Modal>
140
+ );
141
+ };
@@ -1,16 +1,16 @@
1
- import { useApp } from '@nocobase/client-v2';
2
- import { useCallback } from 'react';
3
- export const namespace = 'plugin-agent-orchestrator';
4
-
5
- export function useT() {
6
- const app = useApp();
7
- return useCallback(
8
- (str: string, options?: any): string =>
9
- app.i18n.t(str, { ns: [namespace, 'client'], ...options }) as unknown as string,
10
- [app.i18n]
11
- );
12
- }
13
-
14
- export function tStr(key: string) {
15
- return `{{t(${JSON.stringify(key)}, { ns: ['${namespace}', 'client'], nsMode: 'fallback' })}}`;
16
- }
1
+ import { useApp } from '@nocobase/client-v2';
2
+ import { useCallback } from 'react';
3
+ export const namespace = 'plugin-agent-orchestrator';
4
+
5
+ export function useT() {
6
+ const app = useApp();
7
+ return useCallback(
8
+ (str: string, options?: any): string =>
9
+ app.i18n.t(str, { ns: [namespace, 'client'], ...options }) as unknown as string,
10
+ [app.i18n],
11
+ );
12
+ }
13
+
14
+ export function tStr(key: string) {
15
+ return `{{t(${JSON.stringify(key)}, { ns: ['${namespace}', 'client'], nsMode: 'fallback' })}}`;
16
+ }
@@ -1,109 +1,104 @@
1
- import React from 'react';
2
- import { Form, Input, Select, Radio, InputNumber, Button, Space, Card, Typography } from 'antd';
3
- import { ToolsUIProperties } from '@nocobase/client';
4
- import { useInteractionSchemas } from './InteractionSchemasProvider';
5
-
6
- export const SkillHubCard: React.FC<ToolsUIProperties> = ({ toolCall, decisions }) => {
7
- const schemas = useInteractionSchemas();
8
- const [form] = Form.useForm();
9
-
10
- const sanitize = (name: string) =>
11
- name
12
- .toLowerCase()
13
- .replace(/[^a-z0-9_]/g, '_')
14
- .replace(/_+/g, '_')
15
- .replace(/^_|_$/g, '');
16
- const isGenericExecutor = toolCall.name === 'skill_hub_execute';
17
- const rawArgs = (toolCall.args as Record<string, any>) || {};
18
- const skillKey = isGenericExecutor ? sanitize(rawArgs.skillName || '') : toolCall.name.replace(/^skill_hub_/, '');
19
- const schema = schemas.get(skillKey);
20
-
21
- const interrupted = toolCall.invokeStatus === 'init' || toolCall.invokeStatus === 'interrupted';
22
- if (!interrupted) {
23
- return null;
24
- }
25
-
26
- if (!schema) {
27
- return (
28
- <Card size="small" style={{ marginTop: 8 }}>
29
- <Typography.Paragraph style={{ marginBottom: 12 }}>
30
- Review this Skill Hub tool call before execution.
31
- </Typography.Paragraph>
32
- <Space>
33
- <Button type="primary" onClick={() => decisions.approve()}>
34
- Run
35
- </Button>
36
- <Button onClick={() => decisions.reject('user_cancel')}>Cancel</Button>
37
- </Space>
38
- </Card>
39
- );
40
- }
41
-
42
- const onSubmit = async () => {
43
- const values = await form.validateFields();
44
- const args = isGenericExecutor
45
- ? {
46
- ...rawArgs,
47
- input: schema.type === 'select' ? values : { ...(rawArgs.input || {}), ...values },
48
- }
49
- : schema.type === 'select'
50
- ? values
51
- : { ...rawArgs, ...values };
52
- await decisions.edit(args);
53
- };
54
-
55
- const renderField = (key: string, f: any) => {
56
- if (f?.enum) {
57
- return <Select options={f.enum.map((v: any) => ({ value: v, label: String(v) }))} />;
58
- }
59
- if (f?.type === 'number' || f?.type === 'integer') {
60
- return <InputNumber style={{ width: '100%' }} />;
61
- }
62
- return <Input />;
63
- };
64
-
65
- return (
66
- <Card size="small" style={{ marginTop: 8 }}>
67
- <Typography.Paragraph style={{ marginBottom: 12 }}>{schema.prompt}</Typography.Paragraph>
68
-
69
- {schema.type !== 'confirm' && (
70
- <Form
71
- form={form}
72
- layout="vertical"
73
- initialValues={(isGenericExecutor ? rawArgs.input : rawArgs) || {}}
74
- style={{ marginBottom: 8 }}
75
- >
76
- {schema.type === 'select' && (
77
- <Form.Item name="choice" rules={[{ required: true }]}>
78
- <Radio.Group>
79
- {(schema.options ?? []).map((o) => (
80
- <Radio key={String(o.value)} value={o.value}>
81
- {o.label}
82
- </Radio>
83
- ))}
84
- </Radio.Group>
85
- </Form.Item>
86
- )}
87
- {schema.type === 'form' &&
88
- Object.entries(schema.fields ?? {}).map(([key, f]) => (
89
- <Form.Item
90
- key={key}
91
- name={key}
92
- label={f.title || key}
93
- rules={[{ required: !!f.required }]}
94
- >
95
- {renderField(key, f)}
96
- </Form.Item>
97
- ))}
98
- </Form>
99
- )}
100
-
101
- <Space>
102
- <Button type="primary" onClick={schema.type === 'confirm' ? () => decisions.approve() : onSubmit}>
103
- Run
104
- </Button>
105
- <Button onClick={() => decisions.reject('user_cancel')}>Cancel</Button>
106
- </Space>
107
- </Card>
108
- );
109
- };
1
+ import React from 'react';
2
+ import { Form, Input, Select, Radio, InputNumber, Button, Space, Card, Typography } from 'antd';
3
+ import { ToolsUIProperties } from '@nocobase/client';
4
+ import { useInteractionSchemas } from './InteractionSchemasProvider';
5
+
6
+ export const SkillHubCard: React.FC<ToolsUIProperties> = ({ toolCall, decisions }) => {
7
+ const schemas = useInteractionSchemas();
8
+ const [form] = Form.useForm();
9
+
10
+ const sanitize = (name: string) =>
11
+ name
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9_]/g, '_')
14
+ .replace(/_+/g, '_')
15
+ .replace(/^_|_$/g, '');
16
+ const isGenericExecutor = toolCall.name === 'skill_hub_execute';
17
+ const rawArgs = (toolCall.args as Record<string, any>) || {};
18
+ const skillKey = isGenericExecutor ? sanitize(rawArgs.skillName || '') : toolCall.name.replace(/^skill_hub_/, '');
19
+ const schema = schemas.get(skillKey);
20
+
21
+ const interrupted = toolCall.invokeStatus === 'init' || toolCall.invokeStatus === 'interrupted';
22
+ if (!interrupted) {
23
+ return null;
24
+ }
25
+
26
+ if (!schema) {
27
+ return (
28
+ <Card size="small" style={{ marginTop: 8 }}>
29
+ <Typography.Paragraph style={{ marginBottom: 12 }}>
30
+ Review this Skill Hub tool call before execution.
31
+ </Typography.Paragraph>
32
+ <Space>
33
+ <Button type="primary" onClick={() => decisions.approve()}>
34
+ Run
35
+ </Button>
36
+ <Button onClick={() => decisions.reject('user_cancel')}>Cancel</Button>
37
+ </Space>
38
+ </Card>
39
+ );
40
+ }
41
+
42
+ const onSubmit = async () => {
43
+ const values = await form.validateFields();
44
+ const args = isGenericExecutor
45
+ ? {
46
+ ...rawArgs,
47
+ input: schema.type === 'select' ? values : { ...(rawArgs.input || {}), ...values },
48
+ }
49
+ : schema.type === 'select'
50
+ ? values
51
+ : { ...rawArgs, ...values };
52
+ await decisions.edit(args);
53
+ };
54
+
55
+ const renderField = (key: string, f: any) => {
56
+ if (f?.enum) {
57
+ return <Select options={f.enum.map((v: any) => ({ value: v, label: String(v) }))} />;
58
+ }
59
+ if (f?.type === 'number' || f?.type === 'integer') {
60
+ return <InputNumber style={{ width: '100%' }} />;
61
+ }
62
+ return <Input />;
63
+ };
64
+
65
+ return (
66
+ <Card size="small" style={{ marginTop: 8 }}>
67
+ <Typography.Paragraph style={{ marginBottom: 12 }}>{schema.prompt}</Typography.Paragraph>
68
+
69
+ {schema.type !== 'confirm' && (
70
+ <Form
71
+ form={form}
72
+ layout="vertical"
73
+ initialValues={(isGenericExecutor ? rawArgs.input : rawArgs) || {}}
74
+ style={{ marginBottom: 8 }}
75
+ >
76
+ {schema.type === 'select' && (
77
+ <Form.Item name="choice" rules={[{ required: true }]}>
78
+ <Radio.Group>
79
+ {(schema.options ?? []).map((o) => (
80
+ <Radio key={String(o.value)} value={o.value}>
81
+ {o.label}
82
+ </Radio>
83
+ ))}
84
+ </Radio.Group>
85
+ </Form.Item>
86
+ )}
87
+ {schema.type === 'form' &&
88
+ Object.entries(schema.fields ?? {}).map(([key, f]) => (
89
+ <Form.Item key={key} name={key} label={f.title || key} rules={[{ required: !!f.required }]}>
90
+ {renderField(key, f)}
91
+ </Form.Item>
92
+ ))}
93
+ </Form>
94
+ )}
95
+
96
+ <Space>
97
+ <Button type="primary" onClick={schema.type === 'confirm' ? () => decisions.approve() : onSubmit}>
98
+ Run
99
+ </Button>
100
+ <Button onClick={() => decisions.reject('user_cancel')}>Cancel</Button>
101
+ </Space>
102
+ </Card>
103
+ );
104
+ };
@@ -1,52 +1,52 @@
1
- export type InteractionSchema = {
2
- type: 'form' | 'select' | 'confirm';
3
- prompt: string;
4
- options?: { label: string; value: string | number }[];
5
- fields?: Record<string, { type?: string; title?: string; required?: boolean; enum?: any[] }>;
6
- };
7
-
8
- export type LoopTemplate = {
9
- key: string;
10
- title: string;
11
- description: string;
12
- schema: InteractionSchema;
13
- };
14
-
15
- export const LOOP_TEMPLATES: LoopTemplate[] = [
16
- {
17
- key: 'confirm',
18
- title: 'Confirm before run',
19
- description: 'Ask the user to approve or cancel the skill call.',
20
- schema: {
21
- type: 'confirm',
22
- prompt: 'Review this skill call before execution.',
23
- },
24
- },
25
- {
26
- key: 'review-input',
27
- title: 'Review editable input',
28
- description: 'Show selected input fields so the user can edit them before running.',
29
- schema: {
30
- type: 'form',
31
- prompt: 'Review and edit the skill input before execution.',
32
- fields: {},
33
- },
34
- },
35
- {
36
- key: 'choose-option',
37
- title: 'Choose one option',
38
- description: 'Ask the user to choose one option before running.',
39
- schema: {
40
- type: 'select',
41
- prompt: 'Choose how to run this skill.',
42
- options: [
43
- { label: 'Default', value: 'default' },
44
- { label: 'Careful', value: 'careful' },
45
- ],
46
- },
47
- },
48
- ];
49
-
50
- export function getLoopTemplate(key?: string) {
51
- return LOOP_TEMPLATES.find((template) => template.key === key) || LOOP_TEMPLATES[0];
52
- }
1
+ export type InteractionSchema = {
2
+ type: 'form' | 'select' | 'confirm';
3
+ prompt: string;
4
+ options?: { label: string; value: string | number }[];
5
+ fields?: Record<string, { type?: string; title?: string; required?: boolean; enum?: any[] }>;
6
+ };
7
+
8
+ export type LoopTemplate = {
9
+ key: string;
10
+ title: string;
11
+ description: string;
12
+ schema: InteractionSchema;
13
+ };
14
+
15
+ export const LOOP_TEMPLATES: LoopTemplate[] = [
16
+ {
17
+ key: 'confirm',
18
+ title: 'Confirm before run',
19
+ description: 'Ask the user to approve or cancel the skill call.',
20
+ schema: {
21
+ type: 'confirm',
22
+ prompt: 'Review this skill call before execution.',
23
+ },
24
+ },
25
+ {
26
+ key: 'review-input',
27
+ title: 'Review editable input',
28
+ description: 'Show selected input fields so the user can edit them before running.',
29
+ schema: {
30
+ type: 'form',
31
+ prompt: 'Review and edit the skill input before execution.',
32
+ fields: {},
33
+ },
34
+ },
35
+ {
36
+ key: 'choose-option',
37
+ title: 'Choose one option',
38
+ description: 'Ask the user to choose one option before running.',
39
+ schema: {
40
+ type: 'select',
41
+ prompt: 'Choose how to run this skill.',
42
+ options: [
43
+ { label: 'Default', value: 'default' },
44
+ { label: 'Careful', value: 'careful' },
45
+ ],
46
+ },
47
+ },
48
+ ];
49
+
50
+ export function getLoopTemplate(key?: string) {
51
+ return LOOP_TEMPLATES.find((template) => template.key === key) || LOOP_TEMPLATES[0];
52
+ }