prompt-language-shell 0.8.8 → 0.9.2

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 (45) hide show
  1. package/README.md +0 -1
  2. package/dist/configuration/io.js +22 -1
  3. package/dist/{services/config-labels.js → configuration/labels.js} +1 -1
  4. package/dist/configuration/schema.js +2 -2
  5. package/dist/configuration/steps.js +171 -0
  6. package/dist/configuration/transformation.js +17 -0
  7. package/dist/configuration/types.js +3 -4
  8. package/dist/execution/handlers.js +20 -35
  9. package/dist/execution/hooks.js +291 -0
  10. package/dist/execution/processing.js +15 -2
  11. package/dist/execution/reducer.js +30 -48
  12. package/dist/execution/runner.js +81 -0
  13. package/dist/execution/types.js +1 -0
  14. package/dist/execution/utils.js +28 -0
  15. package/dist/services/components.js +109 -395
  16. package/dist/services/filesystem.js +21 -1
  17. package/dist/services/logger.js +3 -3
  18. package/dist/services/messages.js +10 -16
  19. package/dist/services/process.js +7 -2
  20. package/dist/services/refinement.js +5 -2
  21. package/dist/services/router.js +120 -67
  22. package/dist/services/shell.js +179 -10
  23. package/dist/services/skills.js +2 -1
  24. package/dist/skills/answer.md +14 -12
  25. package/dist/skills/execute.md +98 -39
  26. package/dist/skills/introspect.md +9 -9
  27. package/dist/skills/schedule.md +0 -6
  28. package/dist/types/errors.js +47 -0
  29. package/dist/types/result.js +40 -0
  30. package/dist/ui/Command.js +11 -7
  31. package/dist/ui/Component.js +6 -3
  32. package/dist/ui/Config.js +9 -3
  33. package/dist/ui/Execute.js +249 -163
  34. package/dist/ui/Introspect.js +13 -14
  35. package/dist/ui/List.js +2 -2
  36. package/dist/ui/Main.js +14 -7
  37. package/dist/ui/Output.js +54 -0
  38. package/dist/ui/Schedule.js +3 -1
  39. package/dist/ui/Subtask.js +6 -3
  40. package/dist/ui/Task.js +10 -85
  41. package/dist/ui/Validate.js +26 -21
  42. package/dist/ui/Workflow.js +21 -4
  43. package/package.json +1 -1
  44. package/dist/parser.js +0 -13
  45. package/dist/services/config-utils.js +0 -20
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useReducer } from 'react';
2
+ import { useCallback, useEffect, useReducer, useRef, useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { ComponentStatus, } from '../types/components.js';
5
5
  import { getTextColor } from '../services/colors.js';
@@ -7,93 +7,155 @@ import { useInput } from '../services/keyboard.js';
7
7
  import { formatErrorMessage, getExecutionErrorMessage, } from '../services/messages.js';
8
8
  import { ExecutionStatus } from '../services/shell.js';
9
9
  import { ensureMinimumTime } from '../services/timing.js';
10
- import { buildAbortedState, handleTaskCompletion, handleTaskFailure, } from '../execution/handlers.js';
10
+ import { handleTaskCompletion, handleTaskFailure, } from '../execution/handlers.js';
11
11
  import { processTasks } from '../execution/processing.js';
12
12
  import { executeReducer, initialState } from '../execution/reducer.js';
13
+ import { executeTask } from '../execution/runner.js';
13
14
  import { ExecuteActionType } from '../execution/types.js';
14
- import { createMessage, markAsDone } from '../services/components.js';
15
- import { Message } from './Message.js';
15
+ import { getCurrentTaskIndex } from '../execution/utils.js';
16
+ import { createFeedback, createMessage } from '../services/components.js';
17
+ import { FeedbackType } from '../types/types.js';
16
18
  import { Spinner } from './Spinner.js';
17
- import { Task } from './Task.js';
19
+ import { TaskView } from './Task.js';
18
20
  const MINIMUM_PROCESSING_TIME = 400;
19
- export const ExecuteView = ({ state, status, onTaskComplete, onTaskAbort, onTaskError, }) => {
20
- const isActive = status === ComponentStatus.Active;
21
- const { error, taskInfos, message, completed, completionMessage } = state;
22
- const hasProcessed = taskInfos.length > 0;
23
- // Derive loading state from current conditions
24
- const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
25
- const isExecuting = completed < taskInfos.length;
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
+ /**
58
+ * Create an ExecuteState with defaults
59
+ */
60
+ function createExecuteState(overrides = {}) {
61
+ return {
62
+ message: '',
63
+ summary: '',
64
+ tasks: [],
65
+ completionMessage: null,
66
+ error: null,
67
+ ...overrides,
68
+ };
69
+ }
70
+ /**
71
+ * Execute view: Pure display component for task execution progress
72
+ */
73
+ export const ExecuteView = ({ isLoading, isExecuting, isActive, error, message, tasks, completionMessage, showTasks, }) => {
26
74
  // Return null only when loading completes with no commands
27
- if (!isActive && taskInfos.length === 0 && !error) {
75
+ if (!isActive && tasks.length === 0 && !error) {
28
76
  return null;
29
77
  }
30
- // Show completed steps when not active
31
- const showTasks = !isActive && taskInfos.length > 0;
32
- 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, {})] })), taskInfos.map((taskInfo, index) => (_jsx(Box, { marginBottom: index < taskInfos.length - 1 ? 1 : 0, children: _jsx(Task, { label: taskInfo.label, command: taskInfo.command, isActive: isActive && index === completed, index: index, initialStatus: taskInfo.status, initialElapsed: taskInfo.elapsed, onComplete: onTaskComplete, onAbort: onTaskAbort, onError: onTaskError }) }, index)))] })), completionMessage && !isActive && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(false), children: completionMessage }) })), error && _jsx(Message, { text: error, status: status })] }));
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 }) }))] }));
33
79
  };
34
80
  /**
35
- * Execute controller: Runs tasks sequentially
81
+ * Execute controller: Runs tasks sequentially and manages all execution state
36
82
  */
37
- export function Execute({ tasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
83
+ export function Execute({ tasks: inputTasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
38
84
  const isActive = status === ComponentStatus.Active;
39
85
  const [localState, dispatch] = useReducer(executeReducer, initialState);
40
- const { error, taskInfos, message, completed, hasProcessed, taskExecutionTimes, completionMessage, summary, } = localState;
41
- // Derive loading state from current conditions
42
- const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
43
- const isExecuting = completed < taskInfos.length;
44
- // Handle cancel with useCallback to ensure we capture latest state
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
+ // Track working directory across commands (persists cd changes)
95
+ const workdirRef = useRef(undefined);
96
+ // Ref to track if current task execution is cancelled
97
+ const cancelledRef = useRef(false);
98
+ const { error, tasks, message, hasProcessed, completionMessage, summary } = localState;
99
+ // Derive current task index from tasks
100
+ const currentTaskIndex = getCurrentTaskIndex(tasks);
101
+ // Derive states
102
+ const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
103
+ const isExecuting = isActive && currentTaskIndex < tasks.length;
104
+ const showTasks = !isActive && tasks.length > 0;
105
+ // Update elapsed time while task is running
106
+ useEffect(() => {
107
+ if (!taskStartTime || !isExecuting)
108
+ return;
109
+ const interval = setInterval(() => {
110
+ setLiveElapsed(Date.now() - taskStartTime);
111
+ }, ELAPSED_UPDATE_INTERVAL);
112
+ return () => {
113
+ clearInterval(interval);
114
+ };
115
+ }, [taskStartTime, isExecuting]);
116
+ // Handle cancel
45
117
  const handleCancel = useCallback(() => {
46
- dispatch({
47
- type: ExecuteActionType.CancelExecution,
48
- payload: { completed },
49
- });
50
- // Get updated task infos after cancel
51
- const updatedTaskInfos = taskInfos.map((task, taskIndex) => {
52
- if (taskIndex < completed) {
53
- return { ...task, status: ExecutionStatus.Success };
54
- }
55
- else if (taskIndex === completed) {
56
- return { ...task, status: ExecutionStatus.Aborted };
118
+ cancelledRef.current = true;
119
+ dispatch({ type: ExecuteActionType.CancelExecution });
120
+ // Build updated task infos with current output for the running task
121
+ const updatedTaskInfos = tasks.map((task) => {
122
+ if (task.status === ExecutionStatus.Running) {
123
+ return {
124
+ ...task,
125
+ status: ExecutionStatus.Aborted,
126
+ stdout: liveOutput.stdout,
127
+ stderr: liveOutput.stderr,
128
+ error: liveOutput.error,
129
+ };
57
130
  }
58
- else {
131
+ else if (task.status === ExecutionStatus.Pending) {
59
132
  return { ...task, status: ExecutionStatus.Cancelled };
60
133
  }
134
+ return task;
61
135
  });
62
- // Expose final state
63
- const finalState = {
136
+ const finalState = createExecuteState({
64
137
  message,
65
138
  summary,
66
- taskInfos: updatedTaskInfos,
67
- completed,
68
- taskExecutionTimes,
69
- completionMessage: null,
70
- error: null,
71
- };
139
+ tasks: updatedTaskInfos,
140
+ });
72
141
  requestHandlers.onCompleted(finalState);
73
142
  requestHandlers.onAborted('execution');
74
- }, [
75
- message,
76
- summary,
77
- taskInfos,
78
- completed,
79
- taskExecutionTimes,
80
- requestHandlers,
81
- ]);
143
+ }, [message, summary, tasks, liveOutput, requestHandlers]);
82
144
  useInput((_, key) => {
83
- if (key.escape && (isLoading || isExecuting) && isActive) {
145
+ if (key.escape && (isLoading || isExecuting)) {
84
146
  handleCancel();
85
147
  }
86
148
  }, { isActive: (isLoading || isExecuting) && isActive });
87
149
  // Process tasks to get commands from AI
88
150
  useEffect(() => {
89
- if (!isActive || taskInfos.length > 0 || hasProcessed) {
151
+ if (!isActive || tasks.length > 0 || hasProcessed) {
90
152
  return;
91
153
  }
92
154
  let mounted = true;
93
155
  async function process(svc) {
94
156
  const startTime = Date.now();
95
157
  try {
96
- const result = await processTasks(tasks, svc);
158
+ const result = await processTasks(inputTasks, svc);
97
159
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
98
160
  if (!mounted)
99
161
  return;
@@ -102,67 +164,41 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
102
164
  workflowHandlers.addToTimeline(...result.debug);
103
165
  }
104
166
  if (result.commands.length === 0) {
105
- // Check if this is an error response (has error field)
106
167
  if (result.error) {
107
- // Add error message to timeline
108
168
  const errorMessage = getExecutionErrorMessage(result.error);
109
- workflowHandlers.addToTimeline(markAsDone(createMessage(errorMessage)));
110
- // Complete without error in state (message already in timeline)
111
- const finalState = {
112
- message: result.message,
113
- summary: '',
114
- taskInfos: [],
115
- completed: 0,
116
- taskExecutionTimes: [],
117
- completionMessage: null,
118
- error: null,
119
- };
120
- requestHandlers.onCompleted(finalState);
169
+ workflowHandlers.addToTimeline(createMessage({ text: errorMessage }, ComponentStatus.Done));
170
+ requestHandlers.onCompleted(createExecuteState({ message: result.message }));
121
171
  lifecycleHandlers.completeActive();
122
172
  return;
123
173
  }
124
- // No commands and no error - just complete
125
174
  dispatch({
126
175
  type: ExecuteActionType.ProcessingComplete,
127
176
  payload: { message: result.message },
128
177
  });
129
- const finalState = {
130
- message: result.message,
131
- summary: '',
132
- taskInfos: [],
133
- completed: 0,
134
- taskExecutionTimes: [],
135
- completionMessage: null,
136
- error: null,
137
- };
138
- requestHandlers.onCompleted(finalState);
178
+ requestHandlers.onCompleted(createExecuteState({ message: result.message }));
139
179
  lifecycleHandlers.completeActive();
140
180
  return;
141
181
  }
142
182
  // Create task infos from commands
143
- const infos = result.commands.map((cmd, index) => ({
144
- label: tasks[index]?.action,
183
+ const taskInfos = result.commands.map((cmd, index) => ({
184
+ label: inputTasks[index]?.action ?? cmd.description,
145
185
  command: cmd,
186
+ status: ExecutionStatus.Pending,
187
+ elapsed: 0,
146
188
  }));
147
189
  dispatch({
148
190
  type: ExecuteActionType.CommandsReady,
149
191
  payload: {
150
192
  message: result.message,
151
193
  summary: result.summary,
152
- taskInfos: infos,
194
+ tasks: taskInfos,
153
195
  },
154
196
  });
155
- // Update state after AI processing
156
- const finalState = {
197
+ requestHandlers.onCompleted(createExecuteState({
157
198
  message: result.message,
158
199
  summary: result.summary,
159
- taskInfos: infos,
160
- completed: 0,
161
- taskExecutionTimes: [],
162
- completionMessage: null,
163
- error: null,
164
- };
165
- requestHandlers.onCompleted(finalState);
200
+ tasks: taskInfos,
201
+ }));
166
202
  }
167
203
  catch (err) {
168
204
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
@@ -172,16 +208,7 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
172
208
  type: ExecuteActionType.ProcessingError,
173
209
  payload: { error: errorMessage },
174
210
  });
175
- const finalState = {
176
- message: '',
177
- summary: '',
178
- taskInfos: [],
179
- completed: 0,
180
- taskExecutionTimes: [],
181
- completionMessage: null,
182
- error: errorMessage,
183
- };
184
- requestHandlers.onCompleted(finalState);
211
+ requestHandlers.onCompleted(createExecuteState({ error: errorMessage }));
185
212
  requestHandlers.onError(errorMessage);
186
213
  }
187
214
  }
@@ -191,81 +218,140 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
191
218
  mounted = false;
192
219
  };
193
220
  }, [
194
- tasks,
221
+ inputTasks,
195
222
  isActive,
196
223
  service,
197
224
  requestHandlers,
198
225
  lifecycleHandlers,
199
226
  workflowHandlers,
200
- taskInfos.length,
227
+ tasks.length,
201
228
  hasProcessed,
202
229
  ]);
203
- // Handle task completion - move to next task
204
- const handleTaskComplete = useCallback((index, _output, elapsed) => {
205
- const result = handleTaskCompletion(index, elapsed, {
206
- taskInfos,
207
- message,
208
- summary,
209
- taskExecutionTimes,
210
- });
211
- dispatch(result.action);
212
- requestHandlers.onCompleted(result.finalState);
213
- if (result.shouldComplete) {
214
- lifecycleHandlers.completeActive();
215
- }
216
- }, [
217
- taskInfos,
218
- message,
219
- summary,
220
- taskExecutionTimes,
221
- requestHandlers,
222
- lifecycleHandlers,
223
- ]);
224
- const handleTaskError = useCallback((index, error, elapsed) => {
225
- const result = handleTaskFailure(index, error, elapsed, {
226
- taskInfos,
227
- message,
228
- summary,
229
- taskExecutionTimes,
230
- });
231
- dispatch(result.action);
232
- requestHandlers.onCompleted(result.finalState);
233
- if (result.shouldReportError) {
234
- requestHandlers.onError(error);
230
+ // Execute current task
231
+ useEffect(() => {
232
+ if (!isActive ||
233
+ tasks.length === 0 ||
234
+ currentTaskIndex >= tasks.length ||
235
+ error) {
236
+ return;
235
237
  }
236
- if (result.shouldComplete) {
237
- lifecycleHandlers.completeActive();
238
+ const currentTask = tasks[currentTaskIndex];
239
+ if (currentTask.status !== ExecutionStatus.Pending) {
240
+ return;
238
241
  }
242
+ cancelledRef.current = false;
243
+ // Mark task as started (running)
244
+ dispatch({
245
+ type: ExecuteActionType.TaskStarted,
246
+ payload: { index: currentTaskIndex },
247
+ });
248
+ // Reset live state for new task
249
+ setLiveOutput({ stdout: '', stderr: '', error: '' });
250
+ setLiveElapsed(0);
251
+ setTaskStartTime(Date.now());
252
+ // Merge workdir into command
253
+ const command = workdirRef.current
254
+ ? { ...currentTask.command, workdir: workdirRef.current }
255
+ : currentTask.command;
256
+ void executeTask(command, currentTaskIndex, {
257
+ onOutputChange: (output) => {
258
+ if (!cancelledRef.current) {
259
+ setLiveOutput(output);
260
+ }
261
+ },
262
+ onComplete: (elapsed, output) => {
263
+ if (cancelledRef.current)
264
+ return;
265
+ setTaskStartTime(null);
266
+ // Track working directory
267
+ if (output.workdir) {
268
+ workdirRef.current = output.workdir;
269
+ }
270
+ const tasksWithOutput = tasks.map((task, i) => i === currentTaskIndex
271
+ ? {
272
+ ...task,
273
+ stdout: output.stdout,
274
+ stderr: output.stderr,
275
+ error: output.error,
276
+ }
277
+ : task);
278
+ const result = handleTaskCompletion(currentTaskIndex, elapsed, {
279
+ tasks: tasksWithOutput,
280
+ message,
281
+ summary,
282
+ });
283
+ dispatch(result.action);
284
+ requestHandlers.onCompleted(result.finalState);
285
+ if (result.shouldComplete) {
286
+ lifecycleHandlers.completeActive();
287
+ }
288
+ },
289
+ onError: (errorMsg, elapsed, output) => {
290
+ if (cancelledRef.current)
291
+ return;
292
+ setTaskStartTime(null);
293
+ // Track working directory
294
+ if (output.workdir) {
295
+ workdirRef.current = output.workdir;
296
+ }
297
+ const tasksWithOutput = tasks.map((task, i) => i === currentTaskIndex
298
+ ? {
299
+ ...task,
300
+ stdout: output.stdout,
301
+ stderr: output.stderr,
302
+ error: output.error,
303
+ }
304
+ : task);
305
+ const result = handleTaskFailure(currentTaskIndex, errorMsg, elapsed, {
306
+ tasks: tasksWithOutput,
307
+ message,
308
+ summary,
309
+ });
310
+ dispatch(result.action);
311
+ 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
+ }
319
+ },
320
+ });
239
321
  }, [
240
- taskInfos,
322
+ isActive,
323
+ tasks,
324
+ currentTaskIndex,
241
325
  message,
242
326
  summary,
243
- taskExecutionTimes,
327
+ error,
244
328
  requestHandlers,
245
329
  lifecycleHandlers,
330
+ workflowHandlers,
246
331
  ]);
247
- const handleTaskAbort = useCallback((_index) => {
248
- // Task was aborted - execution already stopped by Escape handler
249
- // Just update state, don't call onAborted (already called at Execute level)
250
- const finalState = buildAbortedState(taskInfos, message, summary, completed, taskExecutionTimes);
251
- requestHandlers.onCompleted(finalState);
252
- }, [
253
- taskInfos,
254
- message,
255
- summary,
256
- completed,
257
- taskExecutionTimes,
258
- requestHandlers,
259
- ]);
260
- // Controller always renders View with current state
261
- const viewState = {
262
- error,
263
- taskInfos,
264
- message,
265
- summary,
266
- completed,
267
- taskExecutionTimes,
268
- completionMessage,
269
- };
270
- return (_jsx(ExecuteView, { tasks: tasks, state: viewState, status: status, onTaskComplete: handleTaskComplete, onTaskAbort: handleTaskAbort, onTaskError: handleTaskError }));
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 }));
271
357
  }
@@ -2,8 +2,9 @@ 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
4
  import { ComponentStatus, } from '../types/components.js';
5
+ import { Origin } from '../types/types.js';
5
6
  import { Colors, getTextColor } from '../services/colors.js';
6
- import { createReportDefinition } from '../services/components.js';
7
+ import { createReport } from '../services/components.js';
7
8
  import { DebugLevel } from '../configuration/types.js';
8
9
  import { useInput } from '../services/keyboard.js';
9
10
  import { formatErrorMessage } from '../services/messages.js';
@@ -51,24 +52,22 @@ export function Introspect({ tasks, status, service, children, debug = DebugLeve
51
52
  if (result.debug?.length) {
52
53
  workflowHandlers.addToTimeline(...result.debug);
53
54
  }
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);
55
+ // Destructure message from result
56
+ const { message } = result;
57
+ // Filter out meta workflow capabilities when not in debug mode
58
+ const capabilities = debug === DebugLevel.None
59
+ ? result.capabilities.filter((cap) => cap.origin !== Origin.Indirect)
60
+ : result.capabilities;
61
+ setCapabilities(capabilities);
62
+ setMessage(message);
64
63
  const finalState = {
65
64
  error: null,
66
- capabilities: caps,
67
- message: result.message,
65
+ capabilities,
66
+ message,
68
67
  };
69
68
  requestHandlers.onCompleted(finalState);
70
69
  // Add Report component to queue
71
- workflowHandlers.addToQueue(createReportDefinition(result.message, caps));
70
+ workflowHandlers.addToQueue(createReport({ message, capabilities }));
72
71
  // Signal completion
73
72
  lifecycleHandlers.completeActive();
74
73
  }
package/dist/ui/List.js CHANGED
@@ -2,9 +2,9 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { Box, Text } from 'ink';
3
3
  import { Palette } from '../services/colors.js';
4
4
  import { Separator } from './Separator.js';
5
- export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, showType = false, }) => {
5
+ export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, showType = false, compact = false, }) => {
6
6
  const marginLeft = level > 0 ? 2 : 0;
7
- const gap = level === 0 ? 1 : 0;
7
+ const gap = level === 0 && !compact ? 1 : 0;
8
8
  return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, gap: gap, children: items.map((item, index) => {
9
9
  // At level 0, track which parent is active for child highlighting
10
10
  // At level > 0, only highlight if this parent is the active one
package/dist/ui/Main.js CHANGED
@@ -5,9 +5,10 @@ import { FeedbackType } from '../types/types.js';
5
5
  import { loadConfig, loadDebugSetting, saveConfig, saveDebugSetting, } from '../configuration/io.js';
6
6
  import { getConfigurationRequiredMessage } from '../configuration/messages.js';
7
7
  import { getMissingConfigKeys } from '../configuration/schema.js';
8
+ import { createConfigStepsFromSchema } from '../configuration/steps.js';
8
9
  import { unflattenConfig } from '../configuration/transformation.js';
9
10
  import { createAnthropicService } from '../services/anthropic.js';
10
- import { createCommandDefinition, createConfigDefinitionWithKeys, createFeedback, createMessage, createWelcomeDefinition, } from '../services/components.js';
11
+ import { createCommand, createConfig, createFeedback, createMessage, createWelcome, } from '../services/components.js';
11
12
  import { registerGlobalShortcut } from '../services/keyboard.js';
12
13
  import { initializeLogger, setDebugLevel } from '../services/logger.js';
13
14
  import { Workflow } from './Workflow.js';
@@ -56,7 +57,9 @@ export const Main = ({ app, command, serviceFactory = createAnthropicService, })
56
57
  const errorMessage = error instanceof Error
57
58
  ? error.message
58
59
  : 'Failed to initialize service';
59
- setInitialQueue([createFeedback(FeedbackType.Failed, errorMessage)]);
60
+ setInitialQueue([
61
+ createFeedback({ type: FeedbackType.Failed, message: errorMessage }),
62
+ ]);
60
63
  }
61
64
  }
62
65
  // If config is missing, service will be created after config completes
@@ -94,18 +97,22 @@ export const Main = ({ app, command, serviceFactory = createAnthropicService, })
94
97
  // Config was cancelled
95
98
  };
96
99
  setInitialQueue([
97
- createWelcomeDefinition(app),
98
- createMessage(getConfigurationRequiredMessage()),
99
- createConfigDefinitionWithKeys(missingKeys, handleConfigFinished, handleConfigAborted),
100
+ createWelcome({ app }),
101
+ createMessage({ text: getConfigurationRequiredMessage() }),
102
+ createConfig({
103
+ steps: createConfigStepsFromSchema(missingKeys),
104
+ onFinished: handleConfigFinished,
105
+ onAborted: handleConfigAborted,
106
+ }),
100
107
  ]);
101
108
  }
102
109
  else if (service && command) {
103
110
  // Valid service exists and command provided - execute command
104
- setInitialQueue([createCommandDefinition(command, service)]);
111
+ setInitialQueue([createCommand({ command, service })]);
105
112
  }
106
113
  else if (service && !command) {
107
114
  // Valid service exists, no command - show welcome
108
- setInitialQueue([createWelcomeDefinition(app)]);
115
+ setInitialQueue([createWelcome({ app })]);
109
116
  }
110
117
  // Wait for service to be initialized before setting queue
111
118
  }, [app, command, service, initialQueue]);