prompt-language-shell 0.8.4 → 0.8.6

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.
@@ -1,22 +1,35 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
- import { ComponentStatus } from '../types/components.js';
4
+ import { ComponentStatus, } from '../types/components.js';
5
5
  import { Colors, getTextColor } from '../services/colors.js';
6
6
  import { createReportDefinition } from '../services/components.js';
7
- import { DebugLevel } from '../services/configuration.js';
7
+ import { DebugLevel } from '../configuration/types.js';
8
8
  import { useInput } from '../services/keyboard.js';
9
9
  import { formatErrorMessage } from '../services/messages.js';
10
10
  import { ensureMinimumTime } from '../services/timing.js';
11
11
  import { Spinner } from './Spinner.js';
12
12
  const MIN_PROCESSING_TIME = 1000;
13
- export function Introspect({ tasks, state: _state, status, service, children, debug = DebugLevel.None, stateHandlers, lifecycleHandlers, queueHandlers, errorHandlers, workflowHandlers, }) {
13
+ export const IntrospectView = ({ state, status, children, }) => {
14
+ const isActive = status === ComponentStatus.Active;
15
+ const { error } = state;
16
+ // Don't render wrapper when done and nothing to show
17
+ if (!isActive && !error && !children) {
18
+ return null;
19
+ }
20
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
21
+ };
22
+ /**
23
+ * Introspect controller: Lists capabilities via LLM
24
+ */
25
+ export function Introspect({ tasks, status, service, children, debug = DebugLevel.None, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
14
26
  const isActive = status === ComponentStatus.Active;
15
- // isActive passed as prop
16
27
  const [error, setError] = useState(null);
28
+ const [capabilities, setCapabilities] = useState(null);
29
+ const [message, setMessage] = useState(null);
17
30
  useInput((input, key) => {
18
31
  if (key.escape && isActive) {
19
- errorHandlers?.onAborted('introspection');
32
+ requestHandlers.onAborted('introspection');
20
33
  }
21
34
  }, { isActive });
22
35
  useEffect(() => {
@@ -36,25 +49,28 @@ export function Introspect({ tasks, state: _state, status, service, children, de
36
49
  if (mounted) {
37
50
  // Add debug components to timeline if present
38
51
  if (result.debug?.length) {
39
- workflowHandlers?.addToTimeline(...result.debug);
52
+ workflowHandlers.addToTimeline(...result.debug);
40
53
  }
41
54
  // Capabilities come directly from result - no parsing needed
42
- let capabilities = result.capabilities;
55
+ let caps = result.capabilities;
43
56
  // Filter out internal capabilities when not in debug mode
44
57
  if (debug === DebugLevel.None) {
45
- capabilities = capabilities.filter((cap) => cap.name.toUpperCase() !== 'SCHEDULE' &&
58
+ caps = caps.filter((cap) => cap.name.toUpperCase() !== 'SCHEDULE' &&
46
59
  cap.name.toUpperCase() !== 'VALIDATE' &&
47
60
  cap.name.toUpperCase() !== 'REPORT');
48
61
  }
49
- // Save state before completing
50
- stateHandlers?.updateState({
51
- capabilities,
62
+ setCapabilities(caps);
63
+ setMessage(result.message);
64
+ const finalState = {
65
+ error: null,
66
+ capabilities: caps,
52
67
  message: result.message,
53
- });
68
+ };
69
+ requestHandlers.onCompleted(finalState);
54
70
  // Add Report component to queue
55
- queueHandlers?.addToQueue(createReportDefinition(result.message, capabilities));
71
+ workflowHandlers.addToQueue(createReportDefinition(result.message, caps));
56
72
  // Signal completion
57
- lifecycleHandlers?.completeActive();
73
+ lifecycleHandlers.completeActive();
58
74
  }
59
75
  }
60
76
  catch (err) {
@@ -62,11 +78,13 @@ export function Introspect({ tasks, state: _state, status, service, children, de
62
78
  if (mounted) {
63
79
  const errorMessage = formatErrorMessage(err);
64
80
  setError(errorMessage);
65
- // Save error state
66
- stateHandlers?.updateState({
81
+ const finalState = {
67
82
  error: errorMessage,
68
- });
69
- errorHandlers?.onError(errorMessage);
83
+ capabilities: [],
84
+ message: null,
85
+ };
86
+ requestHandlers.onCompleted(finalState);
87
+ requestHandlers.onError(errorMessage);
70
88
  }
71
89
  }
72
90
  }
@@ -74,10 +92,19 @@ export function Introspect({ tasks, state: _state, status, service, children, de
74
92
  return () => {
75
93
  mounted = false;
76
94
  };
77
- }, [tasks, isActive, service, debug]);
78
- // Don't render wrapper when done and nothing to show
79
- if (!isActive && !error && !children) {
80
- return null;
81
- }
82
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
95
+ }, [
96
+ tasks,
97
+ isActive,
98
+ service,
99
+ debug,
100
+ requestHandlers,
101
+ lifecycleHandlers,
102
+ workflowHandlers,
103
+ ]);
104
+ const state = {
105
+ error,
106
+ capabilities: capabilities || [],
107
+ message,
108
+ };
109
+ return (_jsx(IntrospectView, { state: state, status: status, children: children }));
83
110
  }
package/dist/ui/Label.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { getTaskColors, getTaskTypeLabel } from '../services/colors.js';
4
- import { DebugLevel } from '../services/configuration.js';
4
+ import { DebugLevel } from '../configuration/types.js';
5
5
  import { Separator } from './Separator.js';
6
6
  export function Label({ description, taskType, showType = false, isCurrent = false, debug = DebugLevel.None, }) {
7
7
  const colors = getTaskColors(taskType, isCurrent);
package/dist/ui/Main.js CHANGED
@@ -1,9 +1,13 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
+ import { DebugLevel } from '../configuration/types.js';
3
4
  import { FeedbackType } from '../types/types.js';
5
+ import { loadConfig, loadDebugSetting, saveConfig, saveDebugSetting, } from '../configuration/io.js';
6
+ import { getConfigurationRequiredMessage } from '../configuration/messages.js';
7
+ import { getMissingConfigKeys } from '../configuration/schema.js';
8
+ import { unflattenConfig } from '../configuration/transformation.js';
4
9
  import { createAnthropicService } from '../services/anthropic.js';
5
10
  import { createCommandDefinition, createConfigDefinitionWithKeys, createFeedback, createMessage, createWelcomeDefinition, } from '../services/components.js';
6
- import { DebugLevel, getConfigurationRequiredMessage, getMissingConfigKeys, loadConfig, loadDebugSetting, saveConfig, saveDebugSetting, unflattenConfig, } from '../services/configuration.js';
7
11
  import { registerGlobalShortcut } from '../services/keyboard.js';
8
12
  import { initializeLogger, setDebugLevel } from '../services/logger.js';
9
13
  import { Workflow } from './Workflow.js';
@@ -4,6 +4,13 @@ import { ComponentStatus } from '../types/components.js';
4
4
  import { useInput } from '../services/keyboard.js';
5
5
  import { Message } from './Message.js';
6
6
  import { Spinner } from './Spinner.js';
7
+ export const RefinementView = ({ text, status }) => {
8
+ const isActive = status === ComponentStatus.Active;
9
+ return (_jsxs(Box, { gap: 1, children: [_jsx(Message, { text: text, status: status }), isActive && _jsx(Spinner, {})] }));
10
+ };
11
+ /**
12
+ * Refinement controller: Handles abort input
13
+ */
7
14
  export const Refinement = ({ text, status, onAborted }) => {
8
15
  const isActive = status === ComponentStatus.Active;
9
16
  useInput((_, key) => {
@@ -12,5 +19,5 @@ export const Refinement = ({ text, status, onAborted }) => {
12
19
  return;
13
20
  }
14
21
  }, { isActive });
15
- return (_jsxs(Box, { gap: 1, children: [_jsx(Message, { text: text }), isActive && _jsx(Spinner, {})] }));
22
+ return _jsx(RefinementView, { text: text, status: status });
16
23
  };
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box } from 'ink';
4
- import { ComponentStatus } from '../types/components.js';
4
+ import { ComponentStatus, } from '../types/components.js';
5
5
  import { TaskType } from '../types/types.js';
6
6
  import { getTaskColors, getTaskTypeLabel } from '../services/colors.js';
7
- import { DebugLevel } from '../services/configuration.js';
7
+ import { DebugLevel } from '../configuration/types.js';
8
8
  import { useInput } from '../services/keyboard.js';
9
9
  import { Label } from './Label.js';
10
10
  import { List } from './List.js';
@@ -71,12 +71,54 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
71
71
  }
72
72
  return item;
73
73
  }
74
- export function Schedule({ message, tasks, state, status, debug = DebugLevel.None, stateHandlers, lifecycleHandlers, errorHandlers, onSelectionConfirmed, }) {
74
+ export const ScheduleView = ({ message, tasks, state, status, debug = DebugLevel.None, }) => {
75
75
  const isActive = status === ComponentStatus.Active;
76
- // isActive passed as prop
77
- const [highlightedIndex, setHighlightedIndex] = useState(state?.highlightedIndex ?? null);
78
- const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(state?.currentDefineGroupIndex ?? 0);
79
- const [completedSelections, setCompletedSelections] = useState(state?.completedSelections ?? []);
76
+ const { highlightedIndex, currentDefineGroupIndex, completedSelections } = state;
77
+ // Find all Define tasks
78
+ const defineTaskIndices = tasks
79
+ .map((t, idx) => (t.type === TaskType.Define ? idx : -1))
80
+ .filter((idx) => idx !== -1);
81
+ // Get the current active define task
82
+ const currentDefineTaskIndex = defineTaskIndices[currentDefineGroupIndex] ?? -1;
83
+ const listItems = tasks.map((task, idx) => {
84
+ // Find which define group this task belongs to (if any)
85
+ const defineGroupIndex = defineTaskIndices.indexOf(idx);
86
+ const isDefineTask = defineGroupIndex !== -1;
87
+ // Determine child selection state
88
+ let childIndex = null;
89
+ if (isDefineTask) {
90
+ if (defineGroupIndex < currentDefineGroupIndex) {
91
+ // Previously completed group - show the selection
92
+ childIndex = completedSelections[defineGroupIndex] ?? null;
93
+ }
94
+ else if (defineGroupIndex === currentDefineGroupIndex) {
95
+ // Current active group - show live navigation unless not active
96
+ if (!isActive) {
97
+ // If not active, show the completed selection for this group too
98
+ childIndex = completedSelections[defineGroupIndex] ?? null;
99
+ }
100
+ else {
101
+ childIndex = null;
102
+ }
103
+ }
104
+ }
105
+ // Show arrow on current active define task when no child is highlighted and is active
106
+ const isDefineWithoutSelection = isDefineTask &&
107
+ defineGroupIndex === currentDefineGroupIndex &&
108
+ highlightedIndex === null &&
109
+ isActive;
110
+ return taskToListItem(task, childIndex, isDefineWithoutSelection, isActive, debug);
111
+ });
112
+ return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Label, { description: message, taskType: TaskType.Schedule, showType: debug !== DebugLevel.None, isCurrent: isActive, debug: debug }) })), _jsx(Box, { marginLeft: 1, children: _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug !== DebugLevel.None }) })] }));
113
+ };
114
+ /**
115
+ * Schedule controller: Manages task selection and navigation
116
+ */
117
+ export function Schedule({ message, tasks, status, debug = DebugLevel.None, requestHandlers, lifecycleHandlers, onSelectionConfirmed, }) {
118
+ const isActive = status === ComponentStatus.Active;
119
+ const [highlightedIndex, setHighlightedIndex] = useState(null);
120
+ const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(0);
121
+ const [completedSelections, setCompletedSelections] = useState([]);
80
122
  // Find all Define tasks
81
123
  const defineTaskIndices = tasks
82
124
  .map((t, idx) => (t.type === TaskType.Define ? idx : -1))
@@ -93,9 +135,16 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
93
135
  if (isActive && defineTaskIndices.length === 0 && onSelectionConfirmed) {
94
136
  // No selection needed - all tasks are concrete
95
137
  const concreteTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
138
+ // Expose final state
139
+ const finalState = {
140
+ highlightedIndex,
141
+ currentDefineGroupIndex,
142
+ completedSelections,
143
+ };
144
+ requestHandlers.onCompleted(finalState);
96
145
  // Complete the selection phase - it goes to timeline
97
146
  // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
98
- lifecycleHandlers?.completeActive();
147
+ lifecycleHandlers.completeActive();
99
148
  void onSelectionConfirmed(concreteTasks);
100
149
  }
101
150
  }, [
@@ -104,6 +153,10 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
104
153
  tasks,
105
154
  onSelectionConfirmed,
106
155
  lifecycleHandlers,
156
+ highlightedIndex,
157
+ currentDefineGroupIndex,
158
+ completedSelections,
159
+ requestHandlers,
107
160
  ]);
108
161
  useInput((input, key) => {
109
162
  // Don't handle input if not active or no define task
@@ -111,7 +164,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
111
164
  return;
112
165
  }
113
166
  if (key.escape) {
114
- errorHandlers?.onAborted('task selection');
167
+ requestHandlers.onAborted('task selection');
115
168
  return;
116
169
  }
117
170
  if (key.downArrow) {
@@ -167,61 +220,31 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
167
220
  refinedTasks.push(task);
168
221
  }
169
222
  });
223
+ // Expose final state
224
+ const finalState = {
225
+ highlightedIndex: null,
226
+ currentDefineGroupIndex,
227
+ completedSelections: newCompletedSelections,
228
+ };
229
+ requestHandlers.onCompleted(finalState);
170
230
  if (onSelectionConfirmed) {
171
231
  // Complete the selection phase - it goes to timeline
172
232
  // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
173
- lifecycleHandlers?.completeActive();
233
+ lifecycleHandlers.completeActive();
174
234
  void onSelectionConfirmed(refinedTasks);
175
235
  }
176
236
  else {
177
237
  // No selection callback, just complete normally
178
- lifecycleHandlers?.completeActive();
238
+ lifecycleHandlers.completeActive();
179
239
  }
180
240
  }
181
241
  }
182
242
  }, { isActive: isActive && defineTask !== null });
183
- // Sync state back to component definition
184
- // This ensures timeline can render historical state and tests can validate behavior
185
- useEffect(() => {
186
- stateHandlers?.updateState({
187
- highlightedIndex,
188
- currentDefineGroupIndex,
189
- completedSelections,
190
- });
191
- }, [
243
+ // Controller always renders View, passing current state
244
+ const state = {
192
245
  highlightedIndex,
193
246
  currentDefineGroupIndex,
194
247
  completedSelections,
195
- stateHandlers,
196
- ]);
197
- const listItems = tasks.map((task, idx) => {
198
- // Find which define group this task belongs to (if any)
199
- const defineGroupIndex = defineTaskIndices.indexOf(idx);
200
- const isDefineTask = defineGroupIndex !== -1;
201
- // Determine child selection state
202
- let childIndex = null;
203
- if (isDefineTask) {
204
- if (defineGroupIndex < currentDefineGroupIndex) {
205
- // Previously completed group - show the selection
206
- childIndex = completedSelections[defineGroupIndex] ?? null;
207
- }
208
- else if (defineGroupIndex === currentDefineGroupIndex) {
209
- // Current active group - show live navigation unless not active
210
- if (!isActive) {
211
- // If not active, show the completed selection for this group too
212
- childIndex = completedSelections[defineGroupIndex] ?? null;
213
- }
214
- else {
215
- childIndex = null;
216
- }
217
- }
218
- }
219
- // Show arrow on current active define task when no child is highlighted and is active
220
- const isDefineWithoutSelection = isDefineTask &&
221
- defineGroupIndex === currentDefineGroupIndex &&
222
- highlightedIndex === null &&
223
- isActive;
224
- return taskToListItem(task, childIndex, isDefineWithoutSelection, isActive, debug);
225
- });
226
- return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Label, { description: message, taskType: TaskType.Schedule, showType: debug !== DebugLevel.None, isCurrent: isActive, debug: debug }) })), _jsx(Box, { marginLeft: 1, children: _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug !== DebugLevel.None }) })] }));
248
+ };
249
+ return (_jsx(ScheduleView, { message: message, tasks: tasks, state: state, status: status, debug: debug }));
227
250
  }
@@ -1,23 +1,35 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
- import { ComponentStatus } from '../types/components.js';
4
+ import { ComponentStatus, } from '../types/components.js';
5
5
  import { TaskType } from '../types/types.js';
6
+ import { saveConfig } from '../configuration/io.js';
7
+ import { unflattenConfig } from '../configuration/transformation.js';
6
8
  import { Colors, getTextColor } from '../services/colors.js';
7
- import { createConfigStepsFromSchema } from '../services/components.js';
8
- import { DebugLevel, saveConfig, unflattenConfig, } from '../services/configuration.js';
9
+ import { createConfigDefinitionWithKeys, createMessage, } from '../services/components.js';
9
10
  import { saveConfigLabels } from '../services/config-labels.js';
10
11
  import { useInput } from '../services/keyboard.js';
11
- import { formatErrorMessage } from '../services/messages.js';
12
+ import { formatErrorMessage, getUnresolvedPlaceholdersMessage, } from '../services/messages.js';
12
13
  import { ensureMinimumTime } from '../services/timing.js';
13
- import { Config } from './Config.js';
14
14
  import { Spinner } from './Spinner.js';
15
15
  const MIN_PROCESSING_TIME = 1000;
16
- export function Validate({ missingConfig, userRequest, state, status, service, children, debug = DebugLevel.None, onError, onComplete, onAborted, stateHandlers, lifecycleHandlers, workflowHandlers, }) {
16
+ export const ValidateView = ({ state, status }) => {
17
17
  const isActive = status === ComponentStatus.Active;
18
- const [error, setError] = useState(state?.error ?? null);
19
- const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
20
- const [configRequirements, setConfigRequirements] = useState(state?.configRequirements ?? null);
18
+ const { error, completionMessage } = state;
19
+ // Don't render when not active and nothing to show
20
+ if (!isActive && !completionMessage && !error) {
21
+ return null;
22
+ }
23
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !completionMessage && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: getTextColor(isActive), children: ["Validating configuration requirements.", ' '] }), _jsx(Spinner, {})] })), completionMessage && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
24
+ };
25
+ /**
26
+ * Validate controller: Validates missing config
27
+ */
28
+ export function Validate({ missingConfig, userRequest, status, service, onError, requestHandlers, onValidationComplete, onAborted, lifecycleHandlers, workflowHandlers, }) {
29
+ const isActive = status === ComponentStatus.Active;
30
+ const [error, setError] = useState(null);
31
+ const [completionMessage, setCompletionMessage] = useState(null);
32
+ const [configRequirements, setConfigRequirements] = useState([]);
21
33
  useInput((_, key) => {
22
34
  if (key.escape && isActive) {
23
35
  onAborted('validation');
@@ -40,7 +52,7 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
40
52
  if (mounted) {
41
53
  // Add debug components to timeline if present
42
54
  if (result.debug?.length) {
43
- workflowHandlers?.addToTimeline(...result.debug);
55
+ workflowHandlers.addToTimeline(...result.debug);
44
56
  }
45
57
  // Extract CONFIG tasks with descriptions from result
46
58
  const configTasks = result.tasks.filter((task) => task.type === TaskType.Config);
@@ -57,25 +69,50 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
57
69
  };
58
70
  });
59
71
  // Build completion message showing which config properties are needed
60
- const count = withDescriptions.length;
61
- const propertyWord = count === 1 ? 'property' : 'properties';
62
- // Shuffle between different message variations
63
- const messages = [
64
- `Additional configuration ${propertyWord} required.`,
65
- `Configuration ${propertyWord} needed.`,
66
- `Missing configuration ${propertyWord} detected.`,
67
- `Setup requires configuration ${propertyWord}.`,
68
- ];
69
- const message = messages[Math.floor(Math.random() * messages.length)];
72
+ const message = getUnresolvedPlaceholdersMessage(withDescriptions.length);
70
73
  setCompletionMessage(message);
71
74
  setConfigRequirements(withDescriptions);
72
- // Save state after validation completes
73
- stateHandlers?.updateState({
75
+ // Add validation message to timeline before Config component
76
+ workflowHandlers.addToTimeline(createMessage(message));
77
+ // Create Config component and add to queue
78
+ const keys = withDescriptions.map((req) => req.path);
79
+ const configDef = createConfigDefinitionWithKeys(keys, (config) => {
80
+ // Convert flat dotted keys to nested structure grouped by section
81
+ const configBySection = unflattenConfig(config);
82
+ // Extract and save labels to cache
83
+ const labels = {};
84
+ for (const req of withDescriptions) {
85
+ if (req.description) {
86
+ labels[req.path] = req.description;
87
+ }
88
+ }
89
+ saveConfigLabels(labels);
90
+ // Save each section
91
+ for (const [section, sectionConfig] of Object.entries(configBySection)) {
92
+ saveConfig(section, sectionConfig);
93
+ }
94
+ // After config is saved, invoke callback to add Execute component to queue
95
+ onValidationComplete(withDescriptions);
96
+ }, (operation) => {
97
+ onAborted(operation);
98
+ });
99
+ // Override descriptions with LLM-generated ones
100
+ if ('props' in configDef && 'steps' in configDef.props) {
101
+ configDef.props.steps = configDef.props.steps.map((step, index) => ({
102
+ ...step,
103
+ description: withDescriptions[index].description ||
104
+ withDescriptions[index].path,
105
+ }));
106
+ }
107
+ workflowHandlers.addToQueue(configDef);
108
+ lifecycleHandlers.completeActive();
109
+ const finalState = {
110
+ error: null,
74
111
  completionMessage: message,
75
112
  configRequirements: withDescriptions,
76
113
  validated: true,
77
- error: null,
78
- });
114
+ };
115
+ requestHandlers.onCompleted(finalState);
79
116
  }
80
117
  }
81
118
  catch (err) {
@@ -83,13 +120,13 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
83
120
  if (mounted) {
84
121
  const errorMessage = formatErrorMessage(err);
85
122
  setError(errorMessage);
86
- // Save error state
87
- stateHandlers?.updateState({
123
+ const finalState = {
88
124
  error: errorMessage,
89
125
  completionMessage: null,
90
- configRequirements: null,
126
+ configRequirements: [],
91
127
  validated: false,
92
- });
128
+ };
129
+ requestHandlers.onCompleted(finalState);
93
130
  onError(errorMessage);
94
131
  }
95
132
  }
@@ -103,59 +140,20 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
103
140
  userRequest,
104
141
  isActive,
105
142
  service,
106
- onComplete,
143
+ requestHandlers,
107
144
  onError,
108
145
  onAborted,
146
+ onValidationComplete,
147
+ lifecycleHandlers,
148
+ workflowHandlers,
109
149
  ]);
110
- // Don't render when not active and nothing to show
111
- if (!isActive && !completionMessage && !error && !children) {
112
- return null;
113
- }
114
- // Create ConfigSteps from requirements using createConfigStepsFromSchema
115
- // to load current values from config file, then override descriptions
116
- const configSteps = configRequirements
117
- ? (() => {
118
- const keys = configRequirements.map((req) => req.path);
119
- const steps = createConfigStepsFromSchema(keys);
120
- // Override descriptions with LLM-generated ones
121
- return steps.map((step, index) => ({
122
- ...step,
123
- description: configRequirements[index].description ||
124
- configRequirements[index].path,
125
- }));
126
- })()
127
- : null;
128
- const handleConfigFinished = (config) => {
129
- // Convert flat dotted keys to nested structure grouped by section
130
- const configBySection = unflattenConfig(config);
131
- // Extract and save labels to cache
132
- if (configRequirements) {
133
- const labels = {};
134
- for (const req of configRequirements) {
135
- if (req.description) {
136
- labels[req.path] = req.description;
137
- }
138
- }
139
- saveConfigLabels(labels);
140
- }
141
- // Save each section
142
- for (const [section, sectionConfig] of Object.entries(configBySection)) {
143
- saveConfig(section, sectionConfig);
144
- }
145
- // Mark validation component as complete before invoking callback
146
- // This allows the workflow to proceed to execution
147
- lifecycleHandlers?.completeActive();
148
- // Invoke callback which will queue the Execute component
149
- if (configRequirements) {
150
- onComplete(configRequirements);
151
- }
152
- };
153
- const handleConfigAborted = (operation) => {
154
- // Mark validation component as complete when aborted
155
- lifecycleHandlers?.completeActive();
156
- onAborted(operation);
150
+ const state = {
151
+ error,
152
+ completionMessage,
153
+ configRequirements,
154
+ validated: error === null && completionMessage !== null,
157
155
  };
158
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !completionMessage && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: getTextColor(isActive), children: ["Validating configuration requirements.", ' '] }), _jsx(Spinner, {})] })), completionMessage && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), configSteps && configSteps.length > 0 && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Config, { steps: configSteps, status: status, debug: debug, onFinished: handleConfigFinished, onAborted: handleConfigAborted }) })), configSteps && configSteps.length === 0 && !error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: Colors.Status.Error, children: "Error: No configuration steps generated. Please try again." }) })), children] }));
156
+ return _jsx(ValidateView, { state: state, status: status });
159
157
  }
160
158
  /**
161
159
  * Build prompt for VALIDATE tool