prompt-language-shell 0.9.2 → 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 (56) hide show
  1. package/dist/{ui/Main.js → Main.js} +12 -12
  2. package/dist/{ui → components}/Component.js +28 -26
  3. package/dist/{ui → components}/Workflow.js +2 -3
  4. package/dist/{ui → components/controllers}/Answer.js +18 -17
  5. package/dist/{ui → components/controllers}/Command.js +11 -18
  6. package/dist/{ui → components/controllers}/Config.js +8 -116
  7. package/dist/components/controllers/Confirm.js +42 -0
  8. package/dist/{ui → components/controllers}/Execute.js +75 -144
  9. package/dist/{ui → components/controllers}/Introspect.js +12 -28
  10. package/dist/components/controllers/Refinement.js +18 -0
  11. package/dist/components/controllers/Schedule.js +139 -0
  12. package/dist/{ui → components/controllers}/Validate.js +14 -32
  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 +1 -1
  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 +4 -4
  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/configuration/steps.js +1 -1
  35. package/dist/execution/handlers.js +19 -53
  36. package/dist/execution/reducer.js +26 -38
  37. package/dist/execution/runner.js +43 -25
  38. package/dist/execution/types.js +3 -4
  39. package/dist/execution/utils.js +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/services/messages.js +19 -0
  42. package/dist/services/router.js +69 -11
  43. package/dist/services/shell.js +26 -6
  44. package/dist/services/timing.js +1 -0
  45. package/dist/skills/execute.md +15 -7
  46. package/dist/tools/execute.tool.js +0 -4
  47. package/dist/types/schemas.js +0 -1
  48. package/package.json +1 -1
  49. package/dist/execution/hooks.js +0 -291
  50. package/dist/ui/Confirm.js +0 -62
  51. package/dist/ui/Refinement.js +0 -23
  52. package/dist/ui/Schedule.js +0 -257
  53. package/dist/ui/Task.js +0 -11
  54. /package/dist/{ui → components/views}/Debug.js +0 -0
  55. /package/dist/{ui → components/views}/Message.js +0 -0
  56. /package/dist/{ui → components/views}/Panel.js +0 -0
@@ -1,59 +1,20 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useReducer, useRef, useState } from 'react';
3
- import { Box, Text } from 'ink';
4
- import { ComponentStatus, } from '../types/components.js';
5
- import { getTextColor } from '../services/colors.js';
6
- import { useInput } from '../services/keyboard.js';
7
- import { formatErrorMessage, getExecutionErrorMessage, } from '../services/messages.js';
8
- import { ExecutionStatus } from '../services/shell.js';
9
- import { ensureMinimumTime } from '../services/timing.js';
10
- import { handleTaskCompletion, handleTaskFailure, } from '../execution/handlers.js';
11
- import { processTasks } from '../execution/processing.js';
12
- import { executeReducer, initialState } from '../execution/reducer.js';
13
- import { executeTask } from '../execution/runner.js';
14
- import { ExecuteActionType } from '../execution/types.js';
15
- import { getCurrentTaskIndex } from '../execution/utils.js';
16
- import { createFeedback, createMessage } from '../services/components.js';
17
- import { FeedbackType } from '../types/types.js';
18
- import { Spinner } from './Spinner.js';
19
- import { TaskView } from './Task.js';
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';
20
17
  const MINIMUM_PROCESSING_TIME = 400;
21
- const ELAPSED_UPDATE_INTERVAL = 1000;
22
- /**
23
- * Check if a task is finished (success, failed, or aborted)
24
- */
25
- function isTaskFinished(task) {
26
- return (task.status === ExecutionStatus.Success ||
27
- task.status === ExecutionStatus.Failed ||
28
- task.status === ExecutionStatus.Aborted);
29
- }
30
- /**
31
- * Map ExecuteState to view props for rendering in timeline
32
- */
33
- export function mapStateToViewProps(state, isActive) {
34
- const taskViewData = state.tasks.map((task) => {
35
- return {
36
- label: task.label,
37
- command: task.command,
38
- status: task.status,
39
- elapsed: task.elapsed,
40
- stdout: task.stdout ?? '',
41
- stderr: task.stderr ?? '',
42
- isActive: false, // In timeline, no task is active
43
- isFinished: isTaskFinished(task),
44
- };
45
- });
46
- return {
47
- isLoading: false,
48
- isExecuting: false,
49
- isActive,
50
- error: state.error,
51
- message: state.message,
52
- tasks: taskViewData,
53
- completionMessage: state.completionMessage,
54
- showTasks: state.tasks.length > 0,
55
- };
56
- }
57
18
  /**
58
19
  * Create an ExecuteState with defaults
59
20
  */
@@ -67,32 +28,16 @@ function createExecuteState(overrides = {}) {
67
28
  ...overrides,
68
29
  };
69
30
  }
70
- /**
71
- * Execute view: Pure display component for task execution progress
72
- */
73
- export const ExecuteView = ({ isLoading, isExecuting, isActive, error, message, tasks, completionMessage, showTasks, }) => {
74
- // Return null only when loading completes with no commands
75
- if (!isActive && tasks.length === 0 && !error) {
76
- return null;
77
- }
78
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Preparing commands. " }), _jsx(Spinner, {})] })), (isExecuting || showTasks) && (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [message && (_jsxs(Box, { marginBottom: 1, gap: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: message }), isExecuting && _jsx(Spinner, {})] })), tasks.map((task, index) => (_jsx(Box, { marginBottom: index < tasks.length - 1 ? 1 : 0, children: _jsx(TaskView, { label: task.label, command: task.command, status: task.status, elapsed: task.elapsed, stdout: task.stdout, stderr: task.stderr, isFinished: task.isFinished }) }, index)))] })), completionMessage && !isActive && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(false), children: completionMessage }) })), error && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { children: error }) }))] }));
79
- };
80
31
  /**
81
32
  * Execute controller: Runs tasks sequentially and manages all execution state
82
33
  */
83
- export function Execute({ tasks: inputTasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
34
+ export function Execute({ tasks: inputTasks, status, service, upcoming, label, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
84
35
  const isActive = status === ComponentStatus.Active;
85
36
  const [localState, dispatch] = useReducer(executeReducer, initialState);
86
- // Live output state for currently executing task
87
- const [liveOutput, setLiveOutput] = useState({
88
- stdout: '',
89
- stderr: '',
90
- error: '',
91
- });
92
- const [liveElapsed, setLiveElapsed] = useState(0);
93
- const [taskStartTime, setTaskStartTime] = useState(null);
94
37
  // Track working directory across commands (persists cd changes)
95
38
  const workdirRef = useRef(undefined);
39
+ // Ref to collect live output during execution (dispatched every second)
40
+ const outputRef = useRef({ stdout: '', stderr: '' });
96
41
  // Ref to track if current task execution is cancelled
97
42
  const cancelledRef = useRef(false);
98
43
  const { error, tasks, message, hasProcessed, completionMessage, summary } = localState;
@@ -102,30 +47,45 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
102
47
  const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
103
48
  const isExecuting = isActive && currentTaskIndex < tasks.length;
104
49
  const showTasks = !isActive && tasks.length > 0;
105
- // Update elapsed time while task is running
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
106
53
  useEffect(() => {
107
- if (!taskStartTime || !isExecuting)
54
+ if (!runningTask?.startTime || !isExecuting)
108
55
  return;
56
+ const taskStartTime = runningTask.startTime;
109
57
  const interval = setInterval(() => {
110
- setLiveElapsed(Date.now() - taskStartTime);
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
+ });
111
70
  }, ELAPSED_UPDATE_INTERVAL);
112
71
  return () => {
113
72
  clearInterval(interval);
114
73
  };
115
- }, [taskStartTime, isExecuting]);
116
- // Handle cancel
74
+ }, [runningTask?.startTime, isExecuting, currentTaskIndex]);
75
+ // Handle cancel - state already in reducer, just need to update final output
117
76
  const handleCancel = useCallback(() => {
118
77
  cancelledRef.current = true;
119
78
  dispatch({ type: ExecuteActionType.CancelExecution });
120
- // Build updated task infos with current output for the running task
121
- const updatedTaskInfos = tasks.map((task) => {
79
+ // Build final state with current output for the running task
80
+ const updatedTasks = tasks.map((task) => {
122
81
  if (task.status === ExecutionStatus.Running) {
123
82
  return {
124
83
  ...task,
125
84
  status: ExecutionStatus.Aborted,
126
- stdout: liveOutput.stdout,
127
- stderr: liveOutput.stderr,
128
- error: liveOutput.error,
85
+ output: {
86
+ stdout: outputRef.current.stdout,
87
+ stderr: outputRef.current.stderr,
88
+ },
129
89
  };
130
90
  }
131
91
  else if (task.status === ExecutionStatus.Pending) {
@@ -136,11 +96,11 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
136
96
  const finalState = createExecuteState({
137
97
  message,
138
98
  summary,
139
- tasks: updatedTaskInfos,
99
+ tasks: updatedTasks,
140
100
  });
141
101
  requestHandlers.onCompleted(finalState);
142
102
  requestHandlers.onAborted('execution');
143
- }, [message, summary, tasks, liveOutput, requestHandlers]);
103
+ }, [message, summary, tasks, requestHandlers]);
144
104
  useInput((_, key) => {
145
105
  if (key.escape && (isLoading || isExecuting)) {
146
106
  handleCancel();
@@ -179,25 +139,26 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
179
139
  lifecycleHandlers.completeActive();
180
140
  return;
181
141
  }
182
- // Create task infos from commands
183
- const taskInfos = result.commands.map((cmd, index) => ({
142
+ // Create task data from commands
143
+ const tasks = result.commands.map((cmd, index) => ({
184
144
  label: inputTasks[index]?.action ?? cmd.description,
185
145
  command: cmd,
186
146
  status: ExecutionStatus.Pending,
187
147
  elapsed: 0,
148
+ output: null,
188
149
  }));
189
150
  dispatch({
190
151
  type: ExecuteActionType.CommandsReady,
191
152
  payload: {
192
153
  message: result.message,
193
154
  summary: result.summary,
194
- tasks: taskInfos,
155
+ tasks,
195
156
  },
196
157
  });
197
158
  requestHandlers.onCompleted(createExecuteState({
198
159
  message: result.message,
199
160
  summary: result.summary,
200
- tasks: taskInfos,
161
+ tasks,
201
162
  }));
202
163
  }
203
164
  catch (err) {
@@ -243,36 +204,34 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
243
204
  // Mark task as started (running)
244
205
  dispatch({
245
206
  type: ExecuteActionType.TaskStarted,
246
- payload: { index: currentTaskIndex },
207
+ payload: { index: currentTaskIndex, startTime: Date.now() },
247
208
  });
248
- // Reset live state for new task
249
- setLiveOutput({ stdout: '', stderr: '', error: '' });
250
- setLiveElapsed(0);
251
- setTaskStartTime(Date.now());
209
+ // Reset output ref for new task
210
+ outputRef.current = { stdout: '', stderr: '' };
252
211
  // Merge workdir into command
253
212
  const command = workdirRef.current
254
213
  ? { ...currentTask.command, workdir: workdirRef.current }
255
214
  : currentTask.command;
256
215
  void executeTask(command, currentTaskIndex, {
257
- onOutputChange: (output) => {
216
+ onUpdate: (output) => {
258
217
  if (!cancelledRef.current) {
259
- setLiveOutput(output);
218
+ outputRef.current = { stdout: output.stdout, stderr: output.stderr };
260
219
  }
261
220
  },
262
- onComplete: (elapsed, output) => {
221
+ onComplete: (elapsed, execOutput) => {
263
222
  if (cancelledRef.current)
264
223
  return;
265
- setTaskStartTime(null);
266
224
  // Track working directory
267
- if (output.workdir) {
268
- workdirRef.current = output.workdir;
225
+ if (execOutput.workdir) {
226
+ workdirRef.current = execOutput.workdir;
269
227
  }
270
228
  const tasksWithOutput = tasks.map((task, i) => i === currentTaskIndex
271
229
  ? {
272
230
  ...task,
273
- stdout: output.stdout,
274
- stderr: output.stderr,
275
- error: output.error,
231
+ output: {
232
+ stdout: execOutput.stdout,
233
+ stderr: execOutput.stderr,
234
+ },
276
235
  }
277
236
  : task);
278
237
  const result = handleTaskCompletion(currentTaskIndex, elapsed, {
@@ -286,36 +245,32 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
286
245
  lifecycleHandlers.completeActive();
287
246
  }
288
247
  },
289
- onError: (errorMsg, elapsed, output) => {
248
+ onError: (errorMsg, execOutput) => {
290
249
  if (cancelledRef.current)
291
250
  return;
292
- setTaskStartTime(null);
293
251
  // Track working directory
294
- if (output.workdir) {
295
- workdirRef.current = output.workdir;
252
+ if (execOutput.workdir) {
253
+ workdirRef.current = execOutput.workdir;
296
254
  }
297
255
  const tasksWithOutput = tasks.map((task, i) => i === currentTaskIndex
298
256
  ? {
299
257
  ...task,
300
- stdout: output.stdout,
301
- stderr: output.stderr,
302
- error: output.error,
258
+ output: {
259
+ stdout: execOutput.stdout,
260
+ stderr: execOutput.stderr,
261
+ },
262
+ error: execOutput.error || undefined,
303
263
  }
304
264
  : task);
305
- const result = handleTaskFailure(currentTaskIndex, errorMsg, elapsed, {
265
+ const result = handleTaskFailure(currentTaskIndex, errorMsg, {
306
266
  tasks: tasksWithOutput,
307
267
  message,
308
268
  summary,
309
269
  });
310
270
  dispatch(result.action);
311
271
  requestHandlers.onCompleted(result.finalState);
312
- if (result.action.type === ExecuteActionType.TaskErrorCritical) {
313
- const errorMessage = getExecutionErrorMessage(errorMsg);
314
- workflowHandlers.addToQueue(createFeedback({ type: FeedbackType.Failed, message: errorMessage }));
315
- }
316
- if (result.shouldComplete) {
317
- lifecycleHandlers.completeActive();
318
- }
272
+ const errorMessage = getExecutionErrorMessage(errorMsg);
273
+ requestHandlers.onError(errorMessage);
319
274
  },
320
275
  });
321
276
  }, [
@@ -329,29 +284,5 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
329
284
  lifecycleHandlers,
330
285
  workflowHandlers,
331
286
  ]);
332
- // Build view data for each task
333
- const taskViewData = tasks.map((task) => {
334
- const isTaskActive = isActive && task.status === ExecutionStatus.Running;
335
- const finished = isTaskFinished(task);
336
- // Use live output for active task, stored output for finished tasks
337
- const stdout = isTaskActive ? liveOutput.stdout : (task.stdout ?? '');
338
- const stderr = isTaskActive ? liveOutput.stderr : (task.stderr ?? '');
339
- // Use live elapsed for active running task
340
- const elapsed = isTaskActive ? liveElapsed : task.elapsed;
341
- // Merge workdir for active task
342
- const command = isTaskActive && workdirRef.current
343
- ? { ...task.command, workdir: workdirRef.current }
344
- : task.command;
345
- return {
346
- label: task.label,
347
- command,
348
- status: task.status,
349
- elapsed,
350
- stdout,
351
- stderr,
352
- isActive: isTaskActive,
353
- isFinished: finished,
354
- };
355
- });
356
- return (_jsx(ExecuteView, { isLoading: isLoading, isExecuting: isExecuting, isActive: isActive, error: error, message: message, tasks: taskViewData, completionMessage: completionMessage, showTasks: showTasks }));
287
+ return (_jsx(ExecuteView, { isLoading: isLoading, isExecuting: isExecuting, isActive: isActive, error: error, message: message, tasks: tasks, completionMessage: completionMessage, showTasks: showTasks, upcoming: upcoming, label: label }));
357
288
  }
@@ -1,25 +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 { Origin } from '../types/types.js';
6
- import { Colors, getTextColor } from '../services/colors.js';
7
- import { createReport } from '../services/components.js';
8
- import { DebugLevel } from '../configuration/types.js';
9
- import { useInput } from '../services/keyboard.js';
10
- import { formatErrorMessage } from '../services/messages.js';
11
- import { ensureMinimumTime } from '../services/timing.js';
12
- 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';
13
12
  const MIN_PROCESSING_TIME = 1000;
14
- export const IntrospectView = ({ state, status, children, }) => {
15
- const isActive = status === ComponentStatus.Active;
16
- const { error } = state;
17
- // Don't render wrapper when done and nothing to show
18
- if (!isActive && !error && !children) {
19
- return null;
20
- }
21
- 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] }));
22
- };
23
13
  /**
24
14
  * Introspect controller: Lists capabilities via LLM
25
15
  */
@@ -27,7 +17,6 @@ export function Introspect({ tasks, status, service, children, debug = DebugLeve
27
17
  const isActive = status === ComponentStatus.Active;
28
18
  const [error, setError] = useState(null);
29
19
  const [capabilities, setCapabilities] = useState(null);
30
- const [message, setMessage] = useState(null);
31
20
  useInput((input, key) => {
32
21
  if (key.escape && isActive) {
33
22
  requestHandlers.onAborted('introspection');
@@ -59,7 +48,6 @@ export function Introspect({ tasks, status, service, children, debug = DebugLeve
59
48
  ? result.capabilities.filter((cap) => cap.origin !== Origin.Indirect)
60
49
  : result.capabilities;
61
50
  setCapabilities(capabilities);
62
- setMessage(message);
63
51
  const finalState = {
64
52
  error: null,
65
53
  capabilities,
@@ -100,10 +88,6 @@ export function Introspect({ tasks, status, service, children, debug = DebugLeve
100
88
  lifecycleHandlers,
101
89
  workflowHandlers,
102
90
  ]);
103
- const state = {
104
- error,
105
- capabilities: capabilities || [],
106
- message,
107
- };
108
- 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 }));
109
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
+ };
@@ -0,0 +1,139 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
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';
9
+ /**
10
+ * Schedule controller: Manages task selection and navigation
11
+ */
12
+ export function Schedule({ message, tasks, status, debug = DebugLevel.None, requestHandlers, lifecycleHandlers, onSelectionConfirmed, }) {
13
+ const isActive = status === ComponentStatus.Active;
14
+ const [highlightedIndex, setHighlightedIndex] = useState(null);
15
+ const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(0);
16
+ const [completedSelections, setCompletedSelections] = useState([]);
17
+ // Find all Define tasks
18
+ const defineTaskIndices = tasks
19
+ .map((t, idx) => (t.type === TaskType.Define ? idx : -1))
20
+ .filter((idx) => idx !== -1);
21
+ // Get the current active define task
22
+ const currentDefineTaskIndex = defineTaskIndices[currentDefineGroupIndex] ?? -1;
23
+ const defineTask = currentDefineTaskIndex >= 0 ? tasks[currentDefineTaskIndex] : null;
24
+ const optionsCount = Array.isArray(defineTask?.params?.options)
25
+ ? defineTask.params.options.length
26
+ : 0;
27
+ const hasMoreGroups = currentDefineGroupIndex < defineTaskIndices.length - 1;
28
+ // If no DEFINE tasks, immediately confirm with all tasks
29
+ useEffect(() => {
30
+ if (isActive && defineTaskIndices.length === 0 && onSelectionConfirmed) {
31
+ // No selection needed - all tasks are concrete
32
+ const concreteTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
33
+ // Expose final state
34
+ const finalState = {
35
+ highlightedIndex,
36
+ currentDefineGroupIndex,
37
+ completedSelections,
38
+ };
39
+ requestHandlers.onCompleted(finalState);
40
+ // Complete the selection phase - it goes to timeline
41
+ // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
42
+ lifecycleHandlers.completeActive();
43
+ void onSelectionConfirmed(concreteTasks);
44
+ }
45
+ }, [
46
+ isActive,
47
+ defineTaskIndices.length,
48
+ tasks,
49
+ onSelectionConfirmed,
50
+ lifecycleHandlers,
51
+ highlightedIndex,
52
+ currentDefineGroupIndex,
53
+ completedSelections,
54
+ requestHandlers,
55
+ ]);
56
+ useInput((input, key) => {
57
+ // Don't handle input if not active or no define task
58
+ if (!isActive || !defineTask) {
59
+ return;
60
+ }
61
+ if (key.escape) {
62
+ requestHandlers.onAborted('task selection');
63
+ return;
64
+ }
65
+ if (key.downArrow) {
66
+ setHighlightedIndex((prev) => {
67
+ if (prev === null) {
68
+ return 0; // Select first
69
+ }
70
+ return (prev + 1) % optionsCount; // Wrap around
71
+ });
72
+ }
73
+ else if (key.upArrow) {
74
+ setHighlightedIndex((prev) => {
75
+ if (prev === null) {
76
+ return optionsCount - 1; // Select last
77
+ }
78
+ return (prev - 1 + optionsCount) % optionsCount; // Wrap around
79
+ });
80
+ }
81
+ else if (key.return && highlightedIndex !== null) {
82
+ // Record the selection for this group
83
+ const newCompletedSelections = [...completedSelections];
84
+ newCompletedSelections[currentDefineGroupIndex] = highlightedIndex;
85
+ setCompletedSelections(newCompletedSelections);
86
+ if (hasMoreGroups) {
87
+ // Advance to next group
88
+ const newGroupIndex = currentDefineGroupIndex + 1;
89
+ setCurrentDefineGroupIndex(newGroupIndex);
90
+ setHighlightedIndex(null);
91
+ }
92
+ else {
93
+ // Clear highlight to show Execute color
94
+ setHighlightedIndex(null);
95
+ // Build refined task list with only selected options (no discarded or ignored ones)
96
+ const refinedTasks = [];
97
+ tasks.forEach((task, idx) => {
98
+ const defineGroupIndex = defineTaskIndices.indexOf(idx);
99
+ if (defineGroupIndex !== -1 &&
100
+ Array.isArray(task.params?.options)) {
101
+ // This is a Define task - only include the selected option
102
+ const options = task.params.options;
103
+ const selectedIndex = newCompletedSelections[defineGroupIndex];
104
+ const selectedOption = options[selectedIndex];
105
+ // Use Execute as default - LLM will properly classify during refinement
106
+ refinedTasks.push({
107
+ action: selectedOption,
108
+ type: TaskType.Execute,
109
+ config: [],
110
+ });
111
+ }
112
+ else if (task.type !== TaskType.Ignore &&
113
+ task.type !== TaskType.Discard) {
114
+ // Regular task - keep as is, but skip Ignore and Discard tasks
115
+ refinedTasks.push(task);
116
+ }
117
+ });
118
+ // Expose final state
119
+ const finalState = {
120
+ highlightedIndex: null,
121
+ currentDefineGroupIndex,
122
+ completedSelections: newCompletedSelections,
123
+ };
124
+ requestHandlers.onCompleted(finalState);
125
+ if (onSelectionConfirmed) {
126
+ // Complete the selection phase - it goes to timeline
127
+ // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
128
+ lifecycleHandlers.completeActive();
129
+ void onSelectionConfirmed(refinedTasks);
130
+ }
131
+ else {
132
+ // No selection callback, just complete normally
133
+ lifecycleHandlers.completeActive();
134
+ }
135
+ }
136
+ }
137
+ }, { isActive: isActive && defineTask !== null });
138
+ return (_jsx(ScheduleView, { status: status, message: message, tasks: tasks, highlightedIndex: highlightedIndex, currentDefineGroupIndex: currentDefineGroupIndex, completedSelections: completedSelections, debug: debug }));
139
+ }
@@ -1,28 +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 { createConfigStepsFromSchema } from '../configuration/steps.js';
8
- import { unflattenConfig } from '../configuration/transformation.js';
9
- import { Colors, getTextColor } from '../services/colors.js';
10
- import { createConfig, createMessage } from '../services/components.js';
11
- import { saveConfigLabels } from '../configuration/labels.js';
12
- import { useInput } from '../services/keyboard.js';
13
- import { formatErrorMessage, getUnresolvedPlaceholdersMessage, } from '../services/messages.js';
14
- import { ensureMinimumTime } from '../services/timing.js';
15
- 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';
16
15
  const MIN_PROCESSING_TIME = 1000;
17
- export const ValidateView = ({ state, status }) => {
18
- const isActive = status === ComponentStatus.Active;
19
- const { error, completionMessage } = state;
20
- // Don't render when not active and nothing to show
21
- if (!isActive && !completionMessage && !error) {
22
- return null;
23
- }
24
- 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] }) }))] }));
25
- };
26
16
  /**
27
17
  * Validate controller: Validates missing config
28
18
  */
@@ -30,7 +20,6 @@ export function Validate({ missingConfig, userRequest, status, service, onError,
30
20
  const isActive = status === ComponentStatus.Active;
31
21
  const [error, setError] = useState(null);
32
22
  const [completionMessage, setCompletionMessage] = useState(null);
33
- const [configRequirements, setConfigRequirements] = useState([]);
34
23
  useInput((_, key) => {
35
24
  if (key.escape && isActive) {
36
25
  onAborted('validation');
@@ -72,7 +61,6 @@ export function Validate({ missingConfig, userRequest, status, service, onError,
72
61
  // Build completion message showing which config properties are needed
73
62
  const message = getUnresolvedPlaceholdersMessage(withDescriptions.length);
74
63
  setCompletionMessage(message);
75
- setConfigRequirements(withDescriptions);
76
64
  // Add validation message to timeline before Config component
77
65
  workflowHandlers.addToTimeline(createMessage({ text: message }));
78
66
  // Create Config component and add to queue
@@ -152,13 +140,7 @@ export function Validate({ missingConfig, userRequest, status, service, onError,
152
140
  lifecycleHandlers,
153
141
  workflowHandlers,
154
142
  ]);
155
- const state = {
156
- error,
157
- completionMessage,
158
- configRequirements,
159
- validated: error === null && completionMessage !== null,
160
- };
161
- return _jsx(ValidateView, { state: state, status: status });
143
+ return (_jsx(ValidateView, { status: status, completionMessage: completionMessage, error: error }));
162
144
  }
163
145
  /**
164
146
  * Build prompt for VALIDATE tool