plugin-agent-orchestrator 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/client/hooks/useRunEventStream.d.ts +22 -0
  2. package/dist/client/index.d.ts +1 -0
  3. package/dist/client/index.js +1 -1
  4. package/dist/externalVersion.js +6 -6
  5. package/dist/server/collections/agent-execution-spans.js +24 -0
  6. package/dist/server/collections/agent-loop-runs.js +36 -0
  7. package/dist/server/collections/orchestrator-config.js +14 -0
  8. package/dist/server/migrations/20260601000000-add-token-fields.d.ts +7 -0
  9. package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
  10. package/dist/server/plugin.js +47 -0
  11. package/dist/server/resources/agent-loop.js +33 -25
  12. package/dist/server/resources/tracing.js +5 -8
  13. package/dist/server/services/AgentHarness.d.ts +2 -0
  14. package/dist/server/services/AgentHarness.js +56 -90
  15. package/dist/server/services/AgentLoopController.d.ts +33 -20
  16. package/dist/server/services/AgentLoopController.js +164 -125
  17. package/dist/server/services/AgentLoopRepository.js +16 -34
  18. package/dist/server/services/AgentLoopService.d.ts +28 -18
  19. package/dist/server/services/AgentLoopService.js +7 -1
  20. package/dist/server/services/AgentPlannerService.js +5 -25
  21. package/dist/server/services/AgentRegistryService.d.ts +8 -0
  22. package/dist/server/services/AgentRegistryService.js +34 -24
  23. package/dist/server/services/CircuitBreaker.d.ts +40 -0
  24. package/dist/server/services/CircuitBreaker.js +120 -0
  25. package/dist/server/services/ContextAggregator.d.ts +45 -0
  26. package/dist/server/services/ContextAggregator.js +201 -0
  27. package/dist/server/services/ExecutionSpanService.js +2 -5
  28. package/dist/server/services/RunEventBus.d.ts +9 -0
  29. package/dist/server/services/RunEventBus.js +73 -0
  30. package/dist/server/services/TokenTracker.d.ts +62 -0
  31. package/dist/server/services/TokenTracker.js +173 -0
  32. package/dist/server/tools/agent-loop.d.ts +8 -8
  33. package/dist/server/tools/agent-loop.js +30 -63
  34. package/dist/server/tools/delegate-task.js +14 -72
  35. package/dist/server/tools/orchestrator-plan.d.ts +6 -6
  36. package/dist/server/tools/orchestrator-plan.js +10 -47
  37. package/dist/server/types.d.ts +47 -0
  38. package/dist/server/types.js +24 -0
  39. package/dist/server/utils/ctx-utils.d.ts +30 -0
  40. package/dist/server/utils/ctx-utils.js +152 -0
  41. package/dist/server/utils/logging.d.ts +6 -0
  42. package/dist/server/utils/logging.js +86 -0
  43. package/package.json +44 -44
  44. package/src/client/AgentRunsTab.tsx +764 -764
  45. package/src/client/HarnessProfilesTab.tsx +247 -247
  46. package/src/client/OrchestratorSettings.tsx +106 -106
  47. package/src/client/RulesTab.tsx +716 -716
  48. package/src/client/hooks/useRunEventStream.ts +76 -0
  49. package/src/client/index.tsx +2 -1
  50. package/src/client/plugin.tsx +27 -27
  51. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  52. package/src/client/skill-hub/index.tsx +51 -51
  53. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
  54. package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
  55. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  56. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
  57. package/src/client/tools/PlanApprovalCard.tsx +175 -175
  58. package/src/client/tools/registerOrchestratorCards.ts +7 -7
  59. package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
  60. package/src/server/__tests__/circuit-breaker.test.ts +169 -0
  61. package/src/server/__tests__/context-aggregator.test.ts +222 -0
  62. package/src/server/__tests__/parallel-execution.test.ts +318 -0
  63. package/src/server/__tests__/smoke.test.ts +120 -0
  64. package/src/server/collections/agent-execution-spans.ts +24 -0
  65. package/src/server/collections/agent-harness-profiles.ts +59 -59
  66. package/src/server/collections/agent-loop-events.ts +71 -71
  67. package/src/server/collections/agent-loop-runs.ts +38 -1
  68. package/src/server/collections/agent-loop-steps.ts +144 -144
  69. package/src/server/collections/orchestrator-config.ts +14 -0
  70. package/src/server/collections/skill-executions.ts +106 -106
  71. package/src/server/collections/skill-loop-configs.ts +65 -65
  72. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  73. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -142
  74. package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
  75. package/src/server/plugin.ts +53 -0
  76. package/src/server/resources/agent-loop.ts +21 -12
  77. package/src/server/resources/tracing.ts +3 -7
  78. package/src/server/services/AgentHarness.ts +78 -116
  79. package/src/server/services/AgentLoopController.ts +197 -122
  80. package/src/server/services/AgentLoopRepository.ts +9 -25
  81. package/src/server/services/AgentLoopService.ts +13 -1
  82. package/src/server/services/AgentPlanValidator.ts +73 -73
  83. package/src/server/services/AgentPlannerService.ts +2 -25
  84. package/src/server/services/AgentRegistryService.ts +40 -31
  85. package/src/server/services/CircuitBreaker.ts +116 -0
  86. package/src/server/services/ContextAggregator.ts +239 -0
  87. package/src/server/services/ExecutionSpanService.ts +2 -4
  88. package/src/server/services/RunEventBus.ts +45 -0
  89. package/src/server/services/TokenTracker.ts +209 -0
  90. package/src/server/skill-hub/plugin.ts +898 -898
  91. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -460
  92. package/src/server/tools/agent-loop.ts +18 -57
  93. package/src/server/tools/delegate-task.ts +11 -93
  94. package/src/server/tools/orchestrator-plan.ts +26 -50
  95. package/src/server/tools/skill-execute.ts +160 -160
  96. package/src/server/types.ts +55 -0
  97. package/src/server/utils/ctx-utils.ts +118 -0
  98. package/src/server/utils/logging.ts +63 -0
@@ -1,51 +1,51 @@
1
- import { Plugin } from '@nocobase/client';
2
- import { SkillManager } from './components/SkillManager';
3
- import { ExecutionHistory } from './components/ExecutionHistory';
4
- import { SkillMetrics } from './components/SkillMetrics';
5
- import { LoopSettings } from './components/LoopSettings';
6
- import { InteractionSchemasProvider } from './tools/InteractionSchemasProvider';
7
- import { registerSkillLoopCards } from './tools/registerSkillLoopCards';
8
-
9
- export class PluginSkillHubClient extends Plugin {
10
- async load() {
11
- (this as any).app.use(InteractionSchemasProvider);
12
-
13
- (this as any).app.pluginSettingsManager.add('skill-hub', {
14
- title: (this as any).t('Skill Hub'),
15
- icon: 'CodeOutlined',
16
- });
17
-
18
- (this as any).app.pluginSettingsManager.add('skill-hub.definitions', {
19
- title: (this as any).t('Skill Definitions'),
20
- Component: SkillManager,
21
- aclSnippet: 'pm.skill-hub',
22
- });
23
-
24
- (this as any).app.pluginSettingsManager.add('skill-hub.loop-settings', {
25
- title: (this as any).t('Skill Review Settings'),
26
- Component: LoopSettings,
27
- aclSnippet: 'pm.skill-hub',
28
- });
29
-
30
- (this as any).app.pluginSettingsManager.add('skill-hub.executions', {
31
- title: (this as any).t('Execution History'),
32
- Component: ExecutionHistory,
33
- aclSnippet: 'pm.skill-hub',
34
- });
35
-
36
- (this as any).app.pluginSettingsManager.add('skill-hub.metrics', {
37
- title: (this as any).t('Dashboard Metrics'),
38
- Component: SkillMetrics,
39
- aclSnippet: 'pm.skill-hub',
40
- });
41
-
42
- await this.registerSkillUiCards();
43
- }
44
-
45
- private async registerSkillUiCards() {
46
- await registerSkillLoopCards((this as any).app);
47
- }
48
- }
49
-
50
- export { SkillManager, ExecutionHistory, SkillMetrics, LoopSettings };
51
- export default PluginSkillHubClient;
1
+ import { Plugin } from '@nocobase/client';
2
+ import { SkillManager } from './components/SkillManager';
3
+ import { ExecutionHistory } from './components/ExecutionHistory';
4
+ import { SkillMetrics } from './components/SkillMetrics';
5
+ import { LoopSettings } from './components/LoopSettings';
6
+ import { InteractionSchemasProvider } from './tools/InteractionSchemasProvider';
7
+ import { registerSkillLoopCards } from './tools/registerSkillLoopCards';
8
+
9
+ export class PluginSkillHubClient extends Plugin {
10
+ async load() {
11
+ (this as any).app.use(InteractionSchemasProvider);
12
+
13
+ (this as any).app.pluginSettingsManager.add('skill-hub', {
14
+ title: (this as any).t('Skill Hub'),
15
+ icon: 'CodeOutlined',
16
+ });
17
+
18
+ (this as any).app.pluginSettingsManager.add('skill-hub.definitions', {
19
+ title: (this as any).t('Skill Definitions'),
20
+ Component: SkillManager,
21
+ aclSnippet: 'pm.skill-hub',
22
+ });
23
+
24
+ (this as any).app.pluginSettingsManager.add('skill-hub.loop-settings', {
25
+ title: (this as any).t('Skill Review Settings'),
26
+ Component: LoopSettings,
27
+ aclSnippet: 'pm.skill-hub',
28
+ });
29
+
30
+ (this as any).app.pluginSettingsManager.add('skill-hub.executions', {
31
+ title: (this as any).t('Execution History'),
32
+ Component: ExecutionHistory,
33
+ aclSnippet: 'pm.skill-hub',
34
+ });
35
+
36
+ (this as any).app.pluginSettingsManager.add('skill-hub.metrics', {
37
+ title: (this as any).t('Dashboard Metrics'),
38
+ Component: SkillMetrics,
39
+ aclSnippet: 'pm.skill-hub',
40
+ });
41
+
42
+ await this.registerSkillUiCards();
43
+ }
44
+
45
+ private async registerSkillUiCards() {
46
+ await registerSkillLoopCards((this as any).app);
47
+ }
48
+ }
49
+
50
+ export { SkillManager, ExecutionHistory, SkillMetrics, LoopSettings };
51
+ export default PluginSkillHubClient;
@@ -1,99 +1,99 @@
1
- import React, { createContext, useContext, useEffect, useState } from 'react';
2
- import { useAPIClient } from '@nocobase/client';
3
- import { parseJsonText } from '../utils/jsonFields';
4
- import { InteractionSchema } from './loopTemplates';
5
-
6
- const Ctx = createContext<Map<string, InteractionSchema>>(new Map());
7
-
8
- export const useInteractionSchemas = () => useContext(Ctx);
9
-
10
- const sanitize = (name: string) =>
11
- name
12
- .toLowerCase()
13
- .replace(/[^a-z0-9_]/g, '_')
14
- .replace(/_+/g, '_')
15
- .replace(/^_|_$/g, '');
16
-
17
- export const InteractionSchemasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
18
- const api = useAPIClient();
19
- const [map, setMap] = useState<Map<string, InteractionSchema>>(new Map());
20
- const [version, setVersion] = useState(0);
21
-
22
- useEffect(() => {
23
- const refresh = () => setVersion((current) => current + 1);
24
- window.addEventListener('skill-hub-loop-settings-changed', refresh);
25
- return () => window.removeEventListener('skill-hub-loop-settings-changed', refresh);
26
- }, []);
27
-
28
- useEffect(() => {
29
- let cancelled = false;
30
- const extractList = (data: any) => {
31
- const value = data?.data?.data ?? data?.data ?? data ?? [];
32
- return Array.isArray(value) ? value : [];
33
- };
34
- const schemaFromLoopConfig = (config: any): InteractionSchema | null => {
35
- const schema = parseJsonText<InteractionSchema | null>(config.schema, null);
36
- if (schema) {
37
- return config.prompt && !schema.prompt ? { ...schema, prompt: config.prompt } : schema;
38
- }
39
- if (config.prompt) {
40
- return {
41
- type: 'confirm',
42
- prompt: config.prompt,
43
- };
44
- }
45
- return null;
46
- };
47
-
48
- Promise.all([
49
- api.request({
50
- url: 'skillDefinitions:list',
51
- params: {
52
- filter: { enabled: true },
53
- fields: ['id', 'name', 'autoCall', 'interactionSchema'],
54
- pageSize: 500,
55
- },
56
- }),
57
- api.request({
58
- url: 'skillLoopConfigs:list',
59
- params: {
60
- filter: { enabled: true },
61
- fields: ['skillId', 'enabled', 'schema', 'prompt', 'templateKey'],
62
- pageSize: 500,
63
- },
64
- }).catch(() => ({ data: [] })),
65
- ])
66
- .then(([skillsResponse, loopConfigsResponse]) => {
67
- if (cancelled) return;
68
- const next = new Map<string, InteractionSchema>();
69
- const skills = extractList(skillsResponse.data);
70
- const loopConfigs = extractList(loopConfigsResponse.data);
71
- const skillsById = new Map(skills.map((skill: any) => [String(skill.id), skill]));
72
-
73
- for (const s of skills) {
74
- if (s.autoCall) continue;
75
- const schema = parseJsonText<InteractionSchema | null>(s.interactionSchema, null);
76
- if (!schema) continue;
77
- next.set(sanitize(s.name), schema);
78
- }
79
-
80
- for (const config of loopConfigs) {
81
- const skill = skillsById.get(String(config.skillId));
82
- if (!skill?.name) continue;
83
- const schema = schemaFromLoopConfig(config);
84
- if (!schema) continue;
85
- next.set(sanitize(skill.name), schema);
86
- }
87
-
88
- setMap(next);
89
- })
90
- .catch(() => {
91
- // silently ignore — user may lack permission to list definitions
92
- });
93
- return () => {
94
- cancelled = true;
95
- };
96
- }, [api, version]);
97
-
98
- return <Ctx.Provider value={map}>{children}</Ctx.Provider>;
99
- };
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+ import { useAPIClient } from '@nocobase/client';
3
+ import { parseJsonText } from '../utils/jsonFields';
4
+ import { InteractionSchema } from './loopTemplates';
5
+
6
+ const Ctx = createContext<Map<string, InteractionSchema>>(new Map());
7
+
8
+ export const useInteractionSchemas = () => useContext(Ctx);
9
+
10
+ const sanitize = (name: string) =>
11
+ name
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9_]/g, '_')
14
+ .replace(/_+/g, '_')
15
+ .replace(/^_|_$/g, '');
16
+
17
+ export const InteractionSchemasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
18
+ const api = useAPIClient();
19
+ const [map, setMap] = useState<Map<string, InteractionSchema>>(new Map());
20
+ const [version, setVersion] = useState(0);
21
+
22
+ useEffect(() => {
23
+ const refresh = () => setVersion((current) => current + 1);
24
+ window.addEventListener('skill-hub-loop-settings-changed', refresh);
25
+ return () => window.removeEventListener('skill-hub-loop-settings-changed', refresh);
26
+ }, []);
27
+
28
+ useEffect(() => {
29
+ let cancelled = false;
30
+ const extractList = (data: any) => {
31
+ const value = data?.data?.data ?? data?.data ?? data ?? [];
32
+ return Array.isArray(value) ? value : [];
33
+ };
34
+ const schemaFromLoopConfig = (config: any): InteractionSchema | null => {
35
+ const schema = parseJsonText<InteractionSchema | null>(config.schema, null);
36
+ if (schema) {
37
+ return config.prompt && !schema.prompt ? { ...schema, prompt: config.prompt } : schema;
38
+ }
39
+ if (config.prompt) {
40
+ return {
41
+ type: 'confirm',
42
+ prompt: config.prompt,
43
+ };
44
+ }
45
+ return null;
46
+ };
47
+
48
+ Promise.all([
49
+ api.request({
50
+ url: 'skillDefinitions:list',
51
+ params: {
52
+ filter: { enabled: true },
53
+ fields: ['id', 'name', 'autoCall', 'interactionSchema'],
54
+ pageSize: 500,
55
+ },
56
+ }),
57
+ api.request({
58
+ url: 'skillLoopConfigs:list',
59
+ params: {
60
+ filter: { enabled: true },
61
+ fields: ['skillId', 'enabled', 'schema', 'prompt', 'templateKey'],
62
+ pageSize: 500,
63
+ },
64
+ }).catch(() => ({ data: [] })),
65
+ ])
66
+ .then(([skillsResponse, loopConfigsResponse]) => {
67
+ if (cancelled) return;
68
+ const next = new Map<string, InteractionSchema>();
69
+ const skills = extractList(skillsResponse.data);
70
+ const loopConfigs = extractList(loopConfigsResponse.data);
71
+ const skillsById = new Map(skills.map((skill: any) => [String(skill.id), skill]));
72
+
73
+ for (const s of skills) {
74
+ if (s.autoCall) continue;
75
+ const schema = parseJsonText<InteractionSchema | null>(s.interactionSchema, null);
76
+ if (!schema) continue;
77
+ next.set(sanitize(s.name), schema);
78
+ }
79
+
80
+ for (const config of loopConfigs) {
81
+ const skill = skillsById.get(String(config.skillId));
82
+ if (!skill?.name) continue;
83
+ const schema = schemaFromLoopConfig(config);
84
+ if (!schema) continue;
85
+ next.set(sanitize(skill.name), schema);
86
+ }
87
+
88
+ setMap(next);
89
+ })
90
+ .catch(() => {
91
+ // silently ignore — user may lack permission to list definitions
92
+ });
93
+ return () => {
94
+ cancelled = true;
95
+ };
96
+ }, [api, version]);
97
+
98
+ return <Ctx.Provider value={map}>{children}</Ctx.Provider>;
99
+ };
@@ -1,109 +1,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
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
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,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
+ }