prompt-language-shell 0.9.0 → 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.
- package/dist/{services/config-labels.js → configuration/labels.js} +1 -1
- package/dist/configuration/schema.js +2 -2
- package/dist/configuration/steps.js +171 -0
- package/dist/configuration/transformation.js +17 -0
- package/dist/execution/handlers.js +1 -7
- package/dist/execution/hooks.js +291 -0
- package/dist/execution/processing.js +3 -1
- package/dist/execution/reducer.js +14 -12
- package/dist/execution/runner.js +81 -0
- package/dist/execution/types.js +1 -0
- package/dist/execution/utils.js +22 -0
- package/dist/services/components.js +109 -394
- package/dist/services/logger.js +3 -3
- package/dist/services/refinement.js +5 -2
- package/dist/services/router.js +69 -46
- package/dist/skills/execute.md +28 -10
- package/dist/ui/Command.js +11 -7
- package/dist/ui/Component.js +5 -2
- package/dist/ui/Config.js +9 -3
- package/dist/ui/Execute.js +211 -148
- package/dist/ui/Introspect.js +13 -14
- package/dist/ui/List.js +2 -2
- package/dist/ui/Main.js +14 -7
- package/dist/ui/Schedule.js +3 -1
- package/dist/ui/Subtask.js +6 -3
- package/dist/ui/Task.js +7 -171
- package/dist/ui/Validate.js +26 -21
- package/dist/ui/Workflow.js +21 -4
- package/package.json +1 -1
- package/dist/parser.js +0 -13
- package/dist/services/config-utils.js +0 -20
package/dist/ui/Execute.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useReducer, useRef } 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,16 +7,53 @@ 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 {
|
|
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 {
|
|
15
|
+
import { getCurrentTaskIndex } from '../execution/utils.js';
|
|
16
|
+
import { createFeedback, createMessage } from '../services/components.js';
|
|
15
17
|
import { FeedbackType } from '../types/types.js';
|
|
16
|
-
import { Message } from './Message.js';
|
|
17
18
|
import { Spinner } from './Spinner.js';
|
|
18
|
-
import {
|
|
19
|
+
import { TaskView } from './Task.js';
|
|
19
20
|
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
|
+
}
|
|
20
57
|
/**
|
|
21
58
|
* Create an ExecuteState with defaults
|
|
22
59
|
*/
|
|
@@ -25,96 +62,87 @@ function createExecuteState(overrides = {}) {
|
|
|
25
62
|
message: '',
|
|
26
63
|
summary: '',
|
|
27
64
|
tasks: [],
|
|
28
|
-
completed: 0,
|
|
29
65
|
completionMessage: null,
|
|
30
66
|
error: null,
|
|
31
67
|
...overrides,
|
|
32
68
|
};
|
|
33
69
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// Derive loading state from current conditions
|
|
39
|
-
const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
|
|
40
|
-
const isExecuting = completed < tasks.length;
|
|
70
|
+
/**
|
|
71
|
+
* Execute view: Pure display component for task execution progress
|
|
72
|
+
*/
|
|
73
|
+
export const ExecuteView = ({ isLoading, isExecuting, isActive, error, message, tasks, completionMessage, showTasks, }) => {
|
|
41
74
|
// Return null only when loading completes with no commands
|
|
42
75
|
if (!isActive && tasks.length === 0 && !error) {
|
|
43
76
|
return null;
|
|
44
77
|
}
|
|
45
|
-
|
|
46
|
-
const showTasks = !isActive && tasks.length > 0;
|
|
47
|
-
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((taskInfo, index) => {
|
|
48
|
-
// Merge workdir into active task's command
|
|
49
|
-
const taskCommand = isActive && index === completed && workdir
|
|
50
|
-
? { ...taskInfo.command, workdir }
|
|
51
|
-
: taskInfo.command;
|
|
52
|
-
return (_jsx(Box, { marginBottom: index < tasks.length - 1 ? 1 : 0, children: _jsx(Task, { label: taskInfo.label, command: taskCommand, isActive: isActive && index === completed, isFinished: index < completed, index: index, initialStatus: taskInfo.status, initialElapsed: taskInfo.elapsed, initialOutput: taskInfo.stdout || taskInfo.stderr || taskInfo.error
|
|
53
|
-
? {
|
|
54
|
-
stdout: taskInfo.stdout ?? '',
|
|
55
|
-
stderr: taskInfo.stderr ?? '',
|
|
56
|
-
error: taskInfo.error ?? '',
|
|
57
|
-
}
|
|
58
|
-
: undefined, onOutputChange: onOutputChange, onComplete: onTaskComplete, onAbort: onTaskAbort, onError: onTaskError }) }, index));
|
|
59
|
-
})] })), 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 }) }))] }));
|
|
60
79
|
};
|
|
61
80
|
/**
|
|
62
|
-
* Execute controller: Runs tasks sequentially
|
|
81
|
+
* Execute controller: Runs tasks sequentially and manages all execution state
|
|
63
82
|
*/
|
|
64
83
|
export function Execute({ tasks: inputTasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
|
|
65
84
|
const isActive = status === ComponentStatus.Active;
|
|
66
85
|
const [localState, dispatch] = useReducer(executeReducer, initialState);
|
|
67
|
-
//
|
|
68
|
-
const
|
|
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);
|
|
69
94
|
// Track working directory across commands (persists cd changes)
|
|
70
95
|
const workdirRef = useRef(undefined);
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
73
102
|
const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
|
|
74
|
-
const isExecuting =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
80
117
|
const handleCancel = useCallback(() => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const output = taskOutputRef.current.get(taskIndex);
|
|
88
|
-
const baseTask = output
|
|
89
|
-
? {
|
|
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 {
|
|
90
124
|
...task,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (taskIndex < completed) {
|
|
97
|
-
return { ...baseTask, status: ExecutionStatus.Success };
|
|
98
|
-
}
|
|
99
|
-
else if (taskIndex === completed) {
|
|
100
|
-
return { ...baseTask, status: ExecutionStatus.Aborted };
|
|
125
|
+
status: ExecutionStatus.Aborted,
|
|
126
|
+
stdout: liveOutput.stdout,
|
|
127
|
+
stderr: liveOutput.stderr,
|
|
128
|
+
error: liveOutput.error,
|
|
129
|
+
};
|
|
101
130
|
}
|
|
102
|
-
else {
|
|
103
|
-
return { ...
|
|
131
|
+
else if (task.status === ExecutionStatus.Pending) {
|
|
132
|
+
return { ...task, status: ExecutionStatus.Cancelled };
|
|
104
133
|
}
|
|
134
|
+
return task;
|
|
105
135
|
});
|
|
106
|
-
// Expose final state
|
|
107
136
|
const finalState = createExecuteState({
|
|
108
137
|
message,
|
|
109
138
|
summary,
|
|
110
139
|
tasks: updatedTaskInfos,
|
|
111
|
-
completed,
|
|
112
140
|
});
|
|
113
141
|
requestHandlers.onCompleted(finalState);
|
|
114
142
|
requestHandlers.onAborted('execution');
|
|
115
|
-
}, [message, summary, tasks,
|
|
143
|
+
}, [message, summary, tasks, liveOutput, requestHandlers]);
|
|
116
144
|
useInput((_, key) => {
|
|
117
|
-
if (key.escape && (isLoading || isExecuting)
|
|
145
|
+
if (key.escape && (isLoading || isExecuting)) {
|
|
118
146
|
handleCancel();
|
|
119
147
|
}
|
|
120
148
|
}, { isActive: (isLoading || isExecuting) && isActive });
|
|
@@ -136,17 +164,13 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
|
|
|
136
164
|
workflowHandlers.addToTimeline(...result.debug);
|
|
137
165
|
}
|
|
138
166
|
if (result.commands.length === 0) {
|
|
139
|
-
// Check if this is an error response (has error field)
|
|
140
167
|
if (result.error) {
|
|
141
|
-
// Add error message to timeline
|
|
142
168
|
const errorMessage = getExecutionErrorMessage(result.error);
|
|
143
|
-
workflowHandlers.addToTimeline(
|
|
144
|
-
// Complete without error in state (message already in timeline)
|
|
169
|
+
workflowHandlers.addToTimeline(createMessage({ text: errorMessage }, ComponentStatus.Done));
|
|
145
170
|
requestHandlers.onCompleted(createExecuteState({ message: result.message }));
|
|
146
171
|
lifecycleHandlers.completeActive();
|
|
147
172
|
return;
|
|
148
173
|
}
|
|
149
|
-
// No commands and no error - just complete
|
|
150
174
|
dispatch({
|
|
151
175
|
type: ExecuteActionType.ProcessingComplete,
|
|
152
176
|
payload: { message: result.message },
|
|
@@ -156,7 +180,7 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
|
|
|
156
180
|
return;
|
|
157
181
|
}
|
|
158
182
|
// Create task infos from commands
|
|
159
|
-
const
|
|
183
|
+
const taskInfos = result.commands.map((cmd, index) => ({
|
|
160
184
|
label: inputTasks[index]?.action ?? cmd.description,
|
|
161
185
|
command: cmd,
|
|
162
186
|
status: ExecutionStatus.Pending,
|
|
@@ -167,14 +191,13 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
|
|
|
167
191
|
payload: {
|
|
168
192
|
message: result.message,
|
|
169
193
|
summary: result.summary,
|
|
170
|
-
tasks,
|
|
194
|
+
tasks: taskInfos,
|
|
171
195
|
},
|
|
172
196
|
});
|
|
173
|
-
// Update state after AI processing
|
|
174
197
|
requestHandlers.onCompleted(createExecuteState({
|
|
175
198
|
message: result.message,
|
|
176
199
|
summary: result.summary,
|
|
177
|
-
tasks,
|
|
200
|
+
tasks: taskInfos,
|
|
178
201
|
}));
|
|
179
202
|
}
|
|
180
203
|
catch (err) {
|
|
@@ -204,91 +227,131 @@ export function Execute({ tasks: inputTasks, status, service, requestHandlers, l
|
|
|
204
227
|
tasks.length,
|
|
205
228
|
hasProcessed,
|
|
206
229
|
]);
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const tasksWithOutput = tasks.map((task, i) => i === index
|
|
215
|
-
? {
|
|
216
|
-
...task,
|
|
217
|
-
stdout: taskOutput.stdout,
|
|
218
|
-
stderr: taskOutput.stderr,
|
|
219
|
-
error: taskOutput.error,
|
|
220
|
-
}
|
|
221
|
-
: task);
|
|
222
|
-
const result = handleTaskCompletion(index, elapsed, {
|
|
223
|
-
tasks: tasksWithOutput,
|
|
224
|
-
message,
|
|
225
|
-
summary,
|
|
226
|
-
});
|
|
227
|
-
dispatch(result.action);
|
|
228
|
-
requestHandlers.onCompleted(result.finalState);
|
|
229
|
-
if (result.shouldComplete) {
|
|
230
|
-
lifecycleHandlers.completeActive();
|
|
230
|
+
// Execute current task
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
if (!isActive ||
|
|
233
|
+
tasks.length === 0 ||
|
|
234
|
+
currentTaskIndex >= tasks.length ||
|
|
235
|
+
error) {
|
|
236
|
+
return;
|
|
231
237
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (taskOutput.workdir) {
|
|
236
|
-
workdirRef.current = taskOutput.workdir;
|
|
238
|
+
const currentTask = tasks[currentTaskIndex];
|
|
239
|
+
if (currentTask.status !== ExecutionStatus.Pending) {
|
|
240
|
+
return;
|
|
237
241
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
+
},
|
|
251
320
|
});
|
|
252
|
-
dispatch(result.action);
|
|
253
|
-
requestHandlers.onCompleted(result.finalState);
|
|
254
|
-
// Add error feedback to queue for critical failures
|
|
255
|
-
if (result.action.type === ExecuteActionType.TaskErrorCritical) {
|
|
256
|
-
const errorMessage = getExecutionErrorMessage(error);
|
|
257
|
-
workflowHandlers.addToQueue(createFeedback(FeedbackType.Failed, errorMessage));
|
|
258
|
-
}
|
|
259
|
-
if (result.shouldComplete) {
|
|
260
|
-
lifecycleHandlers.completeActive();
|
|
261
|
-
}
|
|
262
321
|
}, [
|
|
322
|
+
isActive,
|
|
263
323
|
tasks,
|
|
324
|
+
currentTaskIndex,
|
|
264
325
|
message,
|
|
265
326
|
summary,
|
|
327
|
+
error,
|
|
266
328
|
requestHandlers,
|
|
267
329
|
lifecycleHandlers,
|
|
268
330
|
workflowHandlers,
|
|
269
331
|
]);
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
};
|
|
292
355
|
});
|
|
293
|
-
return (_jsx(ExecuteView, {
|
|
356
|
+
return (_jsx(ExecuteView, { isLoading: isLoading, isExecuting: isExecuting, isActive: isActive, error: error, message: message, tasks: taskViewData, completionMessage: completionMessage, showTasks: showTasks }));
|
|
294
357
|
}
|
package/dist/ui/Introspect.js
CHANGED
|
@@ -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 {
|
|
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
|
-
//
|
|
55
|
-
|
|
56
|
-
// Filter out
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
67
|
-
message
|
|
65
|
+
capabilities,
|
|
66
|
+
message,
|
|
68
67
|
};
|
|
69
68
|
requestHandlers.onCompleted(finalState);
|
|
70
69
|
// Add Report component to queue
|
|
71
|
-
workflowHandlers.addToQueue(
|
|
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 {
|
|
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([
|
|
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
|
-
|
|
98
|
-
createMessage(getConfigurationRequiredMessage()),
|
|
99
|
-
|
|
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([
|
|
111
|
+
setInitialQueue([createCommand({ command, service })]);
|
|
105
112
|
}
|
|
106
113
|
else if (service && !command) {
|
|
107
114
|
// Valid service exists, no command - show welcome
|
|
108
|
-
setInitialQueue([
|
|
115
|
+
setInitialQueue([createWelcome({ app })]);
|
|
109
116
|
}
|
|
110
117
|
// Wait for service to be initialized before setting queue
|
|
111
118
|
}, [app, command, service, initialQueue]);
|
package/dist/ui/Schedule.js
CHANGED
|
@@ -79,6 +79,8 @@ export function taskToListItem(task, highlightedChildIndex = null, isDefineTaskW
|
|
|
79
79
|
export const ScheduleView = ({ message, tasks, state, status, debug = DebugLevel.None, }) => {
|
|
80
80
|
const isActive = status === ComponentStatus.Active;
|
|
81
81
|
const { highlightedIndex, currentDefineGroupIndex, completedSelections } = state;
|
|
82
|
+
// Use compact mode when all tasks are Config type
|
|
83
|
+
const isCompact = tasks.every((task) => task.type === TaskType.Config);
|
|
82
84
|
// Find all Define tasks
|
|
83
85
|
const defineTaskIndices = tasks
|
|
84
86
|
.map((t, idx) => (t.type === TaskType.Define ? idx : -1))
|
|
@@ -114,7 +116,7 @@ export const ScheduleView = ({ message, tasks, state, status, debug = DebugLevel
|
|
|
114
116
|
isActive;
|
|
115
117
|
return taskToListItem(task, childIndex, isDefineWithoutSelection, status, debug);
|
|
116
118
|
});
|
|
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 }) })] }));
|
|
119
|
+
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, compact: isCompact }) })] }));
|
|
118
120
|
};
|
|
119
121
|
/**
|
|
120
122
|
* Schedule controller: Manages task selection and navigation
|
package/dist/ui/Subtask.js
CHANGED
|
@@ -4,7 +4,11 @@ import { getStatusColors, Palette, STATUS_ICONS } from '../services/colors.js';
|
|
|
4
4
|
import { ExecutionStatus } from '../services/shell.js';
|
|
5
5
|
import { formatDuration } from '../services/utils.js';
|
|
6
6
|
import { Spinner } from './Spinner.js';
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Pure display component for a single subtask.
|
|
9
|
+
* Shows label, command, status icon, and elapsed time.
|
|
10
|
+
*/
|
|
11
|
+
export function SubtaskView({ label, command, status, elapsed, }) {
|
|
8
12
|
const colors = getStatusColors(status);
|
|
9
13
|
const isCancelled = status === ExecutionStatus.Cancelled;
|
|
10
14
|
const isAborted = status === ExecutionStatus.Aborted;
|
|
@@ -12,11 +16,10 @@ export function Subtask({ label, command, status, isActive: _isActive, startTime
|
|
|
12
16
|
const isFinished = status === ExecutionStatus.Success ||
|
|
13
17
|
status === ExecutionStatus.Failed ||
|
|
14
18
|
status === ExecutionStatus.Aborted;
|
|
15
|
-
const elapsedTime = elapsed ?? (startTime && endTime ? endTime - startTime : undefined);
|
|
16
19
|
// Apply strikethrough for cancelled and aborted tasks
|
|
17
20
|
const formatText = (text) => shouldStrikethrough ? text.split('').join('\u0336') + '\u0336' : text;
|
|
18
21
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: colors.icon, children: STATUS_ICONS[status] }), _jsx(Text, { color: colors.description, children: shouldStrikethrough
|
|
19
22
|
? formatText(label || command.description)
|
|
20
23
|
: label || command.description }), (isFinished || status === ExecutionStatus.Running) &&
|
|
21
|
-
|
|
24
|
+
elapsed !== undefined && (_jsxs(Text, { color: Palette.DarkGray, children: ["(", formatDuration(elapsed), ")"] }))] }), _jsxs(Box, { paddingLeft: 5, flexDirection: "row", children: [_jsx(Box, { children: _jsx(Text, { color: colors.symbol, children: "\u221F " }) }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: colors.command, children: command.command }), status === ExecutionStatus.Running && _jsx(Spinner, {})] })] })] }));
|
|
22
25
|
}
|