prompt-language-shell 0.9.0 → 0.9.4

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 (64) hide show
  1. package/dist/{ui/Main.js → Main.js} +24 -17
  2. package/dist/{ui → components}/Component.js +31 -26
  3. package/dist/{ui → components}/Workflow.js +23 -7
  4. package/dist/{ui → components/controllers}/Answer.js +18 -17
  5. package/dist/{ui → components/controllers}/Command.js +21 -24
  6. package/dist/{ui → components/controllers}/Config.js +17 -119
  7. package/dist/components/controllers/Confirm.js +42 -0
  8. package/dist/components/controllers/Execute.js +288 -0
  9. package/dist/{ui → components/controllers}/Introspect.js +22 -39
  10. package/dist/components/controllers/Refinement.js +18 -0
  11. package/dist/{ui → components/controllers}/Schedule.js +8 -124
  12. package/dist/{ui → components/controllers}/Validate.js +37 -50
  13. package/dist/components/views/Answer.js +28 -0
  14. package/dist/components/views/Command.js +11 -0
  15. package/dist/components/views/Config.js +115 -0
  16. package/dist/components/views/Confirm.js +24 -0
  17. package/dist/components/views/Execute.js +60 -0
  18. package/dist/{ui → components/views}/Feedback.js +3 -3
  19. package/dist/components/views/Introspect.js +17 -0
  20. package/dist/{ui → components/views}/Label.js +3 -3
  21. package/dist/{ui → components/views}/List.js +3 -3
  22. package/dist/{ui → components/views}/Output.js +2 -2
  23. package/dist/components/views/Refinement.js +9 -0
  24. package/dist/{ui → components/views}/Report.js +1 -1
  25. package/dist/components/views/Schedule.js +120 -0
  26. package/dist/{ui → components/views}/Separator.js +1 -1
  27. package/dist/{ui → components/views}/Spinner.js +1 -1
  28. package/dist/{ui → components/views}/Subtask.js +10 -7
  29. package/dist/components/views/Task.js +18 -0
  30. package/dist/components/views/Upcoming.js +30 -0
  31. package/dist/{ui → components/views}/UserQuery.js +1 -1
  32. package/dist/components/views/Validate.js +17 -0
  33. package/dist/{ui → components/views}/Welcome.js +1 -1
  34. package/dist/{services/config-labels.js → configuration/labels.js} +1 -1
  35. package/dist/configuration/schema.js +2 -2
  36. package/dist/configuration/steps.js +171 -0
  37. package/dist/configuration/transformation.js +17 -0
  38. package/dist/execution/handlers.js +20 -60
  39. package/dist/execution/processing.js +3 -1
  40. package/dist/execution/reducer.js +34 -44
  41. package/dist/execution/runner.js +99 -0
  42. package/dist/execution/types.js +4 -4
  43. package/dist/execution/utils.js +23 -1
  44. package/dist/index.js +1 -1
  45. package/dist/services/components.js +109 -394
  46. package/dist/services/logger.js +3 -3
  47. package/dist/services/messages.js +19 -0
  48. package/dist/services/refinement.js +5 -2
  49. package/dist/services/router.js +136 -55
  50. package/dist/services/shell.js +26 -6
  51. package/dist/services/timing.js +1 -0
  52. package/dist/skills/execute.md +40 -14
  53. package/dist/tools/execute.tool.js +0 -4
  54. package/dist/types/schemas.js +0 -1
  55. package/package.json +1 -1
  56. package/dist/parser.js +0 -13
  57. package/dist/services/config-utils.js +0 -20
  58. package/dist/ui/Confirm.js +0 -62
  59. package/dist/ui/Execute.js +0 -294
  60. package/dist/ui/Refinement.js +0 -23
  61. package/dist/ui/Task.js +0 -175
  62. /package/dist/{ui → components/views}/Debug.js +0 -0
  63. /package/dist/{ui → components/views}/Message.js +0 -0
  64. /package/dist/{ui → components/views}/Panel.js +0 -0
@@ -0,0 +1,288 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useReducer, useRef } from 'react';
3
+ import { ComponentStatus, } from '../../types/components.js';
4
+ import { useInput } from '../../services/keyboard.js';
5
+ import { formatErrorMessage, getExecutionErrorMessage, } from '../../services/messages.js';
6
+ import { ExecutionStatus } from '../../services/shell.js';
7
+ import { ELAPSED_UPDATE_INTERVAL, ensureMinimumTime, } from '../../services/timing.js';
8
+ import { handleTaskCompletion, handleTaskFailure, } from '../../execution/handlers.js';
9
+ import { processTasks } from '../../execution/processing.js';
10
+ import { executeReducer, initialState } from '../../execution/reducer.js';
11
+ import { executeTask } from '../../execution/runner.js';
12
+ import { ExecuteActionType } from '../../execution/types.js';
13
+ import { getCurrentTaskIndex } from '../../execution/utils.js';
14
+ import { createMessage } from '../../services/components.js';
15
+ import { ExecuteView } from '../views/Execute.js';
16
+ export { ExecuteView, mapStateToViewProps, } from '../views/Execute.js';
17
+ const MINIMUM_PROCESSING_TIME = 400;
18
+ /**
19
+ * Create an ExecuteState with defaults
20
+ */
21
+ function createExecuteState(overrides = {}) {
22
+ return {
23
+ message: '',
24
+ summary: '',
25
+ tasks: [],
26
+ completionMessage: null,
27
+ error: null,
28
+ ...overrides,
29
+ };
30
+ }
31
+ /**
32
+ * Execute controller: Runs tasks sequentially and manages all execution state
33
+ */
34
+ export function Execute({ tasks: inputTasks, status, service, upcoming, label, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
35
+ const isActive = status === ComponentStatus.Active;
36
+ const [localState, dispatch] = useReducer(executeReducer, initialState);
37
+ // Track working directory across commands (persists cd changes)
38
+ const workdirRef = useRef(undefined);
39
+ // Ref to collect live output during execution (dispatched every second)
40
+ const outputRef = useRef({ stdout: '', stderr: '' });
41
+ // Ref to track if current task execution is cancelled
42
+ const cancelledRef = useRef(false);
43
+ const { error, tasks, message, hasProcessed, completionMessage, summary } = localState;
44
+ // Derive current task index from tasks
45
+ const currentTaskIndex = getCurrentTaskIndex(tasks);
46
+ // Derive states
47
+ const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
48
+ const isExecuting = isActive && currentTaskIndex < tasks.length;
49
+ const showTasks = !isActive && tasks.length > 0;
50
+ // Get current running task for progress updates
51
+ const runningTask = tasks.find((t) => t.status === ExecutionStatus.Running);
52
+ // Update reducer with progress every second while task is running
53
+ useEffect(() => {
54
+ if (!runningTask?.startTime || !isExecuting)
55
+ return;
56
+ const taskStartTime = runningTask.startTime;
57
+ const interval = setInterval(() => {
58
+ const elapsed = Date.now() - taskStartTime;
59
+ dispatch({
60
+ type: ExecuteActionType.TaskProgress,
61
+ payload: {
62
+ index: currentTaskIndex,
63
+ elapsed,
64
+ output: {
65
+ stdout: outputRef.current.stdout,
66
+ stderr: outputRef.current.stderr,
67
+ },
68
+ },
69
+ });
70
+ }, ELAPSED_UPDATE_INTERVAL);
71
+ return () => {
72
+ clearInterval(interval);
73
+ };
74
+ }, [runningTask?.startTime, isExecuting, currentTaskIndex]);
75
+ // Handle cancel - state already in reducer, just need to update final output
76
+ const handleCancel = useCallback(() => {
77
+ cancelledRef.current = true;
78
+ dispatch({ type: ExecuteActionType.CancelExecution });
79
+ // Build final state with current output for the running task
80
+ const updatedTasks = tasks.map((task) => {
81
+ if (task.status === ExecutionStatus.Running) {
82
+ return {
83
+ ...task,
84
+ status: ExecutionStatus.Aborted,
85
+ output: {
86
+ stdout: outputRef.current.stdout,
87
+ stderr: outputRef.current.stderr,
88
+ },
89
+ };
90
+ }
91
+ else if (task.status === ExecutionStatus.Pending) {
92
+ return { ...task, status: ExecutionStatus.Cancelled };
93
+ }
94
+ return task;
95
+ });
96
+ const finalState = createExecuteState({
97
+ message,
98
+ summary,
99
+ tasks: updatedTasks,
100
+ });
101
+ requestHandlers.onCompleted(finalState);
102
+ requestHandlers.onAborted('execution');
103
+ }, [message, summary, tasks, requestHandlers]);
104
+ useInput((_, key) => {
105
+ if (key.escape && (isLoading || isExecuting)) {
106
+ handleCancel();
107
+ }
108
+ }, { isActive: (isLoading || isExecuting) && isActive });
109
+ // Process tasks to get commands from AI
110
+ useEffect(() => {
111
+ if (!isActive || tasks.length > 0 || hasProcessed) {
112
+ return;
113
+ }
114
+ let mounted = true;
115
+ async function process(svc) {
116
+ const startTime = Date.now();
117
+ try {
118
+ const result = await processTasks(inputTasks, svc);
119
+ await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
120
+ if (!mounted)
121
+ return;
122
+ // Add debug components to timeline if present
123
+ if (result.debug?.length) {
124
+ workflowHandlers.addToTimeline(...result.debug);
125
+ }
126
+ if (result.commands.length === 0) {
127
+ if (result.error) {
128
+ const errorMessage = getExecutionErrorMessage(result.error);
129
+ workflowHandlers.addToTimeline(createMessage({ text: errorMessage }, ComponentStatus.Done));
130
+ requestHandlers.onCompleted(createExecuteState({ message: result.message }));
131
+ lifecycleHandlers.completeActive();
132
+ return;
133
+ }
134
+ dispatch({
135
+ type: ExecuteActionType.ProcessingComplete,
136
+ payload: { message: result.message },
137
+ });
138
+ requestHandlers.onCompleted(createExecuteState({ message: result.message }));
139
+ lifecycleHandlers.completeActive();
140
+ return;
141
+ }
142
+ // Create task data from commands
143
+ const tasks = result.commands.map((cmd, index) => ({
144
+ label: inputTasks[index]?.action ?? cmd.description,
145
+ command: cmd,
146
+ status: ExecutionStatus.Pending,
147
+ elapsed: 0,
148
+ output: null,
149
+ }));
150
+ dispatch({
151
+ type: ExecuteActionType.CommandsReady,
152
+ payload: {
153
+ message: result.message,
154
+ summary: result.summary,
155
+ tasks,
156
+ },
157
+ });
158
+ requestHandlers.onCompleted(createExecuteState({
159
+ message: result.message,
160
+ summary: result.summary,
161
+ tasks,
162
+ }));
163
+ }
164
+ catch (err) {
165
+ await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
166
+ if (mounted) {
167
+ const errorMessage = formatErrorMessage(err);
168
+ dispatch({
169
+ type: ExecuteActionType.ProcessingError,
170
+ payload: { error: errorMessage },
171
+ });
172
+ requestHandlers.onCompleted(createExecuteState({ error: errorMessage }));
173
+ requestHandlers.onError(errorMessage);
174
+ }
175
+ }
176
+ }
177
+ void process(service);
178
+ return () => {
179
+ mounted = false;
180
+ };
181
+ }, [
182
+ inputTasks,
183
+ isActive,
184
+ service,
185
+ requestHandlers,
186
+ lifecycleHandlers,
187
+ workflowHandlers,
188
+ tasks.length,
189
+ hasProcessed,
190
+ ]);
191
+ // Execute current task
192
+ useEffect(() => {
193
+ if (!isActive ||
194
+ tasks.length === 0 ||
195
+ currentTaskIndex >= tasks.length ||
196
+ error) {
197
+ return;
198
+ }
199
+ const currentTask = tasks[currentTaskIndex];
200
+ if (currentTask.status !== ExecutionStatus.Pending) {
201
+ return;
202
+ }
203
+ cancelledRef.current = false;
204
+ // Mark task as started (running)
205
+ dispatch({
206
+ type: ExecuteActionType.TaskStarted,
207
+ payload: { index: currentTaskIndex, startTime: Date.now() },
208
+ });
209
+ // Reset output ref for new task
210
+ outputRef.current = { stdout: '', stderr: '' };
211
+ // Merge workdir into command
212
+ const command = workdirRef.current
213
+ ? { ...currentTask.command, workdir: workdirRef.current }
214
+ : currentTask.command;
215
+ void executeTask(command, currentTaskIndex, {
216
+ onUpdate: (output) => {
217
+ if (!cancelledRef.current) {
218
+ outputRef.current = { stdout: output.stdout, stderr: output.stderr };
219
+ }
220
+ },
221
+ onComplete: (elapsed, execOutput) => {
222
+ if (cancelledRef.current)
223
+ return;
224
+ // Track working directory
225
+ if (execOutput.workdir) {
226
+ workdirRef.current = execOutput.workdir;
227
+ }
228
+ const tasksWithOutput = tasks.map((task, i) => i === currentTaskIndex
229
+ ? {
230
+ ...task,
231
+ output: {
232
+ stdout: execOutput.stdout,
233
+ stderr: execOutput.stderr,
234
+ },
235
+ }
236
+ : task);
237
+ const result = handleTaskCompletion(currentTaskIndex, elapsed, {
238
+ tasks: tasksWithOutput,
239
+ message,
240
+ summary,
241
+ });
242
+ dispatch(result.action);
243
+ requestHandlers.onCompleted(result.finalState);
244
+ if (result.shouldComplete) {
245
+ lifecycleHandlers.completeActive();
246
+ }
247
+ },
248
+ onError: (errorMsg, execOutput) => {
249
+ if (cancelledRef.current)
250
+ return;
251
+ // Track working directory
252
+ if (execOutput.workdir) {
253
+ workdirRef.current = execOutput.workdir;
254
+ }
255
+ const tasksWithOutput = tasks.map((task, i) => i === currentTaskIndex
256
+ ? {
257
+ ...task,
258
+ output: {
259
+ stdout: execOutput.stdout,
260
+ stderr: execOutput.stderr,
261
+ },
262
+ error: execOutput.error || undefined,
263
+ }
264
+ : task);
265
+ const result = handleTaskFailure(currentTaskIndex, errorMsg, {
266
+ tasks: tasksWithOutput,
267
+ message,
268
+ summary,
269
+ });
270
+ dispatch(result.action);
271
+ requestHandlers.onCompleted(result.finalState);
272
+ const errorMessage = getExecutionErrorMessage(errorMsg);
273
+ requestHandlers.onError(errorMessage);
274
+ },
275
+ });
276
+ }, [
277
+ isActive,
278
+ tasks,
279
+ currentTaskIndex,
280
+ message,
281
+ summary,
282
+ error,
283
+ requestHandlers,
284
+ lifecycleHandlers,
285
+ workflowHandlers,
286
+ ]);
287
+ return (_jsx(ExecuteView, { isLoading: isLoading, isExecuting: isExecuting, isActive: isActive, error: error, message: message, tasks: tasks, completionMessage: completionMessage, showTasks: showTasks, upcoming: upcoming, label: label }));
288
+ }
@@ -1,24 +1,15 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { Box, Text } from 'ink';
4
- import { ComponentStatus, } from '../types/components.js';
5
- import { Colors, getTextColor } from '../services/colors.js';
6
- import { createReportDefinition } from '../services/components.js';
7
- import { DebugLevel } from '../configuration/types.js';
8
- import { useInput } from '../services/keyboard.js';
9
- import { formatErrorMessage } from '../services/messages.js';
10
- import { ensureMinimumTime } from '../services/timing.js';
11
- import { Spinner } from './Spinner.js';
3
+ import { ComponentStatus, } from '../../types/components.js';
4
+ import { Origin } from '../../types/types.js';
5
+ import { createReport } from '../../services/components.js';
6
+ import { DebugLevel } from '../../configuration/types.js';
7
+ import { useInput } from '../../services/keyboard.js';
8
+ import { formatErrorMessage } from '../../services/messages.js';
9
+ import { ensureMinimumTime } from '../../services/timing.js';
10
+ import { IntrospectView } from '../views/Introspect.js';
11
+ export { IntrospectView } from '../views/Introspect.js';
12
12
  const MIN_PROCESSING_TIME = 1000;
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
13
  /**
23
14
  * Introspect controller: Lists capabilities via LLM
24
15
  */
@@ -26,7 +17,6 @@ export function Introspect({ tasks, status, service, children, debug = DebugLeve
26
17
  const isActive = status === ComponentStatus.Active;
27
18
  const [error, setError] = useState(null);
28
19
  const [capabilities, setCapabilities] = useState(null);
29
- const [message, setMessage] = useState(null);
30
20
  useInput((input, key) => {
31
21
  if (key.escape && isActive) {
32
22
  requestHandlers.onAborted('introspection');
@@ -51,24 +41,21 @@ export function Introspect({ tasks, status, service, children, debug = DebugLeve
51
41
  if (result.debug?.length) {
52
42
  workflowHandlers.addToTimeline(...result.debug);
53
43
  }
54
- // Capabilities come directly from result - no parsing needed
55
- let caps = result.capabilities;
56
- // Filter out internal capabilities when not in debug mode
57
- if (debug === DebugLevel.None) {
58
- caps = caps.filter((cap) => cap.name.toUpperCase() !== 'SCHEDULE' &&
59
- cap.name.toUpperCase() !== 'VALIDATE' &&
60
- cap.name.toUpperCase() !== 'REPORT');
61
- }
62
- setCapabilities(caps);
63
- setMessage(result.message);
44
+ // Destructure message from result
45
+ const { message } = result;
46
+ // Filter out meta workflow capabilities when not in debug mode
47
+ const capabilities = debug === DebugLevel.None
48
+ ? result.capabilities.filter((cap) => cap.origin !== Origin.Indirect)
49
+ : result.capabilities;
50
+ setCapabilities(capabilities);
64
51
  const finalState = {
65
52
  error: null,
66
- capabilities: caps,
67
- message: result.message,
53
+ capabilities,
54
+ message,
68
55
  };
69
56
  requestHandlers.onCompleted(finalState);
70
57
  // Add Report component to queue
71
- workflowHandlers.addToQueue(createReportDefinition(result.message, caps));
58
+ workflowHandlers.addToQueue(createReport({ message, capabilities }));
72
59
  // Signal completion
73
60
  lifecycleHandlers.completeActive();
74
61
  }
@@ -101,10 +88,6 @@ export function Introspect({ tasks, status, service, children, debug = DebugLeve
101
88
  lifecycleHandlers,
102
89
  workflowHandlers,
103
90
  ]);
104
- const state = {
105
- error,
106
- capabilities: capabilities || [],
107
- message,
108
- };
109
- return (_jsx(IntrospectView, { state: state, status: status, children: children }));
91
+ const hasCapabilities = capabilities !== null && capabilities.length > 0;
92
+ return (_jsx(IntrospectView, { status: status, hasCapabilities: hasCapabilities, error: error, children: children }));
110
93
  }
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ComponentStatus } from '../../types/components.js';
3
+ import { useInput } from '../../services/keyboard.js';
4
+ import { RefinementView } from '../views/Refinement.js';
5
+ export { RefinementView } from '../views/Refinement.js';
6
+ /**
7
+ * Refinement controller: Handles abort input
8
+ */
9
+ export const Refinement = ({ text, status, onAborted }) => {
10
+ const isActive = status === ComponentStatus.Active;
11
+ useInput((_, key) => {
12
+ if (key.escape && isActive) {
13
+ onAborted('plan refinement');
14
+ return;
15
+ }
16
+ }, { isActive });
17
+ return _jsx(RefinementView, { text: text, status: status });
18
+ };
@@ -1,121 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { Box } from 'ink';
4
- import { ComponentStatus, } from '../types/components.js';
5
- import { TaskType } from '../types/types.js';
6
- import { getTaskColors, getTaskTypeLabel, Palette, } from '../services/colors.js';
7
- import { DebugLevel } from '../configuration/types.js';
8
- import { useInput } from '../services/keyboard.js';
9
- import { Label } from './Label.js';
10
- import { List } from './List.js';
11
- export function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutSelection = false, status = ComponentStatus.Done, debug = DebugLevel.None) {
12
- const taskColors = getTaskColors(task.type, status);
13
- // Determine description color based on status
14
- let descriptionColor = taskColors.description;
15
- if (status === ComponentStatus.Pending) {
16
- descriptionColor = Palette.SoftWhite;
17
- }
18
- const item = {
19
- description: {
20
- text: task.action,
21
- color: descriptionColor,
22
- },
23
- type: { text: getTaskTypeLabel(task.type, debug), color: taskColors.type },
24
- children: [],
25
- };
26
- // Mark define tasks with right arrow when no selection has been made
27
- if (isDefineTaskWithoutSelection) {
28
- item.marker = ' → ';
29
- item.markerColor = getTaskColors(TaskType.Schedule, status).type;
30
- }
31
- // Add children for Define tasks with options
32
- if (task.type === TaskType.Define && Array.isArray(task.params?.options)) {
33
- item.children = task.params.options.map((option, index) => {
34
- // Determine the type based on selection state
35
- let childType = TaskType.Select;
36
- if (highlightedChildIndex !== null) {
37
- // A selection was made - mark others as discarded
38
- childType =
39
- index === highlightedChildIndex ? TaskType.Execute : TaskType.Discard;
40
- }
41
- const colors = getTaskColors(childType, status);
42
- const planColors = getTaskColors(TaskType.Schedule, status);
43
- return {
44
- description: {
45
- text: option,
46
- color: colors.description,
47
- highlightedColor: planColors.description,
48
- },
49
- type: {
50
- text: getTaskTypeLabel(childType, debug),
51
- color: colors.type,
52
- highlightedColor: planColors.type,
53
- },
54
- };
55
- });
56
- }
57
- // Add children for Group tasks with subtasks
58
- const scheduledTask = task;
59
- if (task.type === TaskType.Group &&
60
- scheduledTask.subtasks &&
61
- Array.isArray(scheduledTask.subtasks) &&
62
- scheduledTask.subtasks.length > 0) {
63
- item.children = scheduledTask.subtasks.map((subtask) => {
64
- const subtaskColors = getTaskColors(subtask.type, status);
65
- return {
66
- description: {
67
- text: subtask.action,
68
- color: Palette.AshGray,
69
- },
70
- type: {
71
- text: getTaskTypeLabel(subtask.type, debug),
72
- color: subtaskColors.type,
73
- },
74
- };
75
- });
76
- }
77
- return item;
78
- }
79
- export const ScheduleView = ({ message, tasks, state, status, debug = DebugLevel.None, }) => {
80
- const isActive = status === ComponentStatus.Active;
81
- const { highlightedIndex, currentDefineGroupIndex, completedSelections } = state;
82
- // Find all Define tasks
83
- const defineTaskIndices = tasks
84
- .map((t, idx) => (t.type === TaskType.Define ? idx : -1))
85
- .filter((idx) => idx !== -1);
86
- // Get the current active define task
87
- const currentDefineTaskIndex = defineTaskIndices[currentDefineGroupIndex] ?? -1;
88
- const listItems = tasks.map((task, idx) => {
89
- // Find which define group this task belongs to (if any)
90
- const defineGroupIndex = defineTaskIndices.indexOf(idx);
91
- const isDefineTask = defineGroupIndex !== -1;
92
- // Determine child selection state
93
- let childIndex = null;
94
- if (isDefineTask) {
95
- if (defineGroupIndex < currentDefineGroupIndex) {
96
- // Previously completed group - show the selection
97
- childIndex = completedSelections[defineGroupIndex] ?? null;
98
- }
99
- else if (defineGroupIndex === currentDefineGroupIndex) {
100
- // Current active group - show live navigation unless not active
101
- if (!isActive) {
102
- // If not active, show the completed selection for this group too
103
- childIndex = completedSelections[defineGroupIndex] ?? null;
104
- }
105
- else {
106
- childIndex = null;
107
- }
108
- }
109
- }
110
- // Show arrow on current active define task when no child is highlighted and is active
111
- const isDefineWithoutSelection = isDefineTask &&
112
- defineGroupIndex === currentDefineGroupIndex &&
113
- highlightedIndex === null &&
114
- isActive;
115
- return taskToListItem(task, childIndex, isDefineWithoutSelection, status, debug);
116
- });
117
- 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, status: status, debug: debug }) })), _jsx(Box, { marginLeft: 1, children: _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug !== DebugLevel.None }) })] }));
118
- };
3
+ import { ComponentStatus, } from '../../types/components.js';
4
+ import { TaskType } from '../../types/types.js';
5
+ import { DebugLevel } from '../../configuration/types.js';
6
+ import { useInput } from '../../services/keyboard.js';
7
+ import { ScheduleView } from '../views/Schedule.js';
8
+ export { ScheduleView, taskToListItem, } from '../views/Schedule.js';
119
9
  /**
120
10
  * Schedule controller: Manages task selection and navigation
121
11
  */
@@ -245,11 +135,5 @@ export function Schedule({ message, tasks, status, debug = DebugLevel.None, requ
245
135
  }
246
136
  }
247
137
  }, { isActive: isActive && defineTask !== null });
248
- // Controller always renders View, passing current state
249
- const state = {
250
- highlightedIndex,
251
- currentDefineGroupIndex,
252
- completedSelections,
253
- };
254
- return (_jsx(ScheduleView, { message: message, tasks: tasks, state: state, status: status, debug: debug }));
138
+ return (_jsx(ScheduleView, { status: status, message: message, tasks: tasks, highlightedIndex: highlightedIndex, currentDefineGroupIndex: currentDefineGroupIndex, completedSelections: completedSelections, debug: debug }));
255
139
  }
@@ -1,27 +1,18 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { Box, Text } from 'ink';
4
- import { ComponentStatus, } from '../types/components.js';
5
- import { TaskType } from '../types/types.js';
6
- import { saveConfig } from '../configuration/io.js';
7
- import { unflattenConfig } from '../configuration/transformation.js';
8
- import { Colors, getTextColor } from '../services/colors.js';
9
- import { createConfigDefinitionWithKeys, createMessage, } from '../services/components.js';
10
- import { saveConfigLabels } from '../services/config-labels.js';
11
- import { useInput } from '../services/keyboard.js';
12
- import { formatErrorMessage, getUnresolvedPlaceholdersMessage, } from '../services/messages.js';
13
- import { ensureMinimumTime } from '../services/timing.js';
14
- import { Spinner } from './Spinner.js';
3
+ import { ComponentStatus, } from '../../types/components.js';
4
+ import { TaskType } from '../../types/types.js';
5
+ import { saveConfig } from '../../configuration/io.js';
6
+ import { createConfigStepsFromSchema } from '../../configuration/steps.js';
7
+ import { unflattenConfig } from '../../configuration/transformation.js';
8
+ import { createConfig, createMessage } from '../../services/components.js';
9
+ import { saveConfigLabels } from '../../configuration/labels.js';
10
+ import { useInput } from '../../services/keyboard.js';
11
+ import { formatErrorMessage, getUnresolvedPlaceholdersMessage, } from '../../services/messages.js';
12
+ import { ensureMinimumTime } from '../../services/timing.js';
13
+ import { ValidateView } from '../views/Validate.js';
14
+ export { ValidateView } from '../views/Validate.js';
15
15
  const MIN_PROCESSING_TIME = 1000;
16
- export const ValidateView = ({ state, status }) => {
17
- const isActive = status === ComponentStatus.Active;
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
16
  /**
26
17
  * Validate controller: Validates missing config
27
18
  */
@@ -29,7 +20,6 @@ export function Validate({ missingConfig, userRequest, status, service, onError,
29
20
  const isActive = status === ComponentStatus.Active;
30
21
  const [error, setError] = useState(null);
31
22
  const [completionMessage, setCompletionMessage] = useState(null);
32
- const [configRequirements, setConfigRequirements] = useState([]);
33
23
  useInput((_, key) => {
34
24
  if (key.escape && isActive) {
35
25
  onAborted('validation');
@@ -71,30 +61,33 @@ export function Validate({ missingConfig, userRequest, status, service, onError,
71
61
  // Build completion message showing which config properties are needed
72
62
  const message = getUnresolvedPlaceholdersMessage(withDescriptions.length);
73
63
  setCompletionMessage(message);
74
- setConfigRequirements(withDescriptions);
75
64
  // Add validation message to timeline before Config component
76
- workflowHandlers.addToTimeline(createMessage(message));
65
+ workflowHandlers.addToTimeline(createMessage({ text: message }));
77
66
  // Create Config component and add to queue
78
67
  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;
68
+ const configDef = createConfig({
69
+ steps: createConfigStepsFromSchema(keys),
70
+ onFinished: (config) => {
71
+ // Convert flat dotted keys to nested structure grouped by section
72
+ const configBySection = unflattenConfig(config);
73
+ // Extract and save labels to cache
74
+ const labels = {};
75
+ for (const req of withDescriptions) {
76
+ if (req.description) {
77
+ labels[req.path] = req.description;
78
+ }
79
+ }
80
+ saveConfigLabels(labels);
81
+ // Save each section
82
+ for (const [section, sectionConfig] of Object.entries(configBySection)) {
83
+ saveConfig(section, sectionConfig);
87
84
  }
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);
85
+ // After config is saved, invoke callback to add Execute component to queue
86
+ onValidationComplete(withDescriptions);
87
+ },
88
+ onAborted: (operation) => {
89
+ onAborted(operation);
90
+ },
98
91
  });
99
92
  // Override descriptions with LLM-generated ones
100
93
  if ('props' in configDef && 'steps' in configDef.props) {
@@ -147,13 +140,7 @@ export function Validate({ missingConfig, userRequest, status, service, onError,
147
140
  lifecycleHandlers,
148
141
  workflowHandlers,
149
142
  ]);
150
- const state = {
151
- error,
152
- completionMessage,
153
- configRequirements,
154
- validated: error === null && completionMessage !== null,
155
- };
156
- return _jsx(ValidateView, { state: state, status: status });
143
+ return (_jsx(ValidateView, { status: status, completionMessage: completionMessage, error: error }));
157
144
  }
158
145
  /**
159
146
  * Build prompt for VALIDATE tool