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
@@ -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
+ }