prompt-language-shell 0.8.4 → 0.8.8
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/configuration/io.js +85 -0
- package/dist/configuration/messages.js +30 -0
- package/dist/configuration/schema.js +167 -0
- package/dist/configuration/transformation.js +55 -0
- package/dist/configuration/types.js +30 -0
- package/dist/configuration/validation.js +52 -0
- package/dist/execution/handlers.js +135 -0
- package/dist/execution/processing.js +36 -0
- package/dist/execution/reducer.js +148 -0
- package/dist/execution/types.js +12 -0
- package/dist/execution/validation.js +12 -0
- package/dist/index.js +1 -1
- package/dist/services/anthropic.js +2 -1
- package/dist/services/colors.js +22 -12
- package/dist/services/components.js +35 -11
- package/dist/services/config-labels.js +15 -15
- package/dist/services/logger.js +2 -1
- package/dist/services/messages.js +53 -1
- package/dist/services/refinement.js +11 -6
- package/dist/services/router.js +92 -52
- package/dist/skills/execute.md +79 -9
- package/dist/skills/schedule.md +121 -29
- package/dist/tools/execute.tool.js +4 -0
- package/dist/types/schemas.js +1 -0
- package/dist/ui/Answer.js +36 -15
- package/dist/ui/Command.js +43 -23
- package/dist/ui/Component.js +147 -33
- package/dist/ui/Config.js +73 -79
- package/dist/ui/Confirm.js +34 -21
- package/dist/ui/Execute.js +129 -329
- package/dist/ui/Feedback.js +2 -1
- package/dist/ui/Introspect.js +51 -24
- package/dist/ui/Label.js +4 -3
- package/dist/ui/List.js +3 -2
- package/dist/ui/Main.js +5 -1
- package/dist/ui/Refinement.js +8 -1
- package/dist/ui/Schedule.js +89 -61
- package/dist/ui/Validate.js +75 -77
- package/dist/ui/Workflow.js +47 -123
- package/package.json +1 -1
- package/dist/services/configuration.js +0 -409
package/dist/ui/Confirm.js
CHANGED
|
@@ -1,31 +1,53 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
-
import { ComponentStatus } from '../types/components.js';
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { Colors, getTextColor, Palette } from '../services/colors.js';
|
|
6
6
|
import { useInput } from '../services/keyboard.js';
|
|
7
7
|
import { UserQuery } from './UserQuery.js';
|
|
8
|
-
export
|
|
8
|
+
export const ConfirmView = ({ message, state, status }) => {
|
|
9
9
|
const isActive = status === ComponentStatus.Active;
|
|
10
|
-
const
|
|
10
|
+
const { selectedIndex } = state;
|
|
11
|
+
const options = [
|
|
12
|
+
{ label: 'yes', value: 'yes', color: Palette.BrightGreen },
|
|
13
|
+
{ label: 'no', value: 'no', color: Colors.Status.Error },
|
|
14
|
+
];
|
|
15
|
+
// Timeline rendering (Done status)
|
|
16
|
+
if (status === ComponentStatus.Done) {
|
|
17
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsxs(UserQuery, { children: ["> ", options[selectedIndex].label] })] }));
|
|
18
|
+
}
|
|
19
|
+
// Active/Pending rendering
|
|
20
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: message }) }), _jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
|
|
21
|
+
const isSelected = index === selectedIndex;
|
|
22
|
+
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, children: option.label }) }, option.value));
|
|
23
|
+
}) })] })] }));
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Confirm controller: Manages yes/no selection
|
|
27
|
+
*/
|
|
28
|
+
export function Confirm({ message, status, requestHandlers, onConfirmed, onCancelled, }) {
|
|
29
|
+
const isActive = status === ComponentStatus.Active;
|
|
30
|
+
const [selectedIndex, setSelectedIndex] = useState(0); // 0 = Yes, 1 = No
|
|
11
31
|
useInput((input, key) => {
|
|
12
32
|
if (!isActive)
|
|
13
33
|
return;
|
|
14
34
|
if (key.escape) {
|
|
15
35
|
// Escape: highlight "No" and cancel
|
|
16
|
-
|
|
17
|
-
|
|
36
|
+
const finalState = { selectedIndex: 1, confirmed: false };
|
|
37
|
+
requestHandlers.onCompleted(finalState);
|
|
18
38
|
onCancelled();
|
|
19
39
|
}
|
|
20
40
|
else if (key.tab) {
|
|
21
41
|
// Toggle between Yes (0) and No (1)
|
|
22
|
-
|
|
23
|
-
setSelectedIndex(newIndex);
|
|
24
|
-
stateHandlers?.updateState({ selectedIndex: newIndex });
|
|
42
|
+
setSelectedIndex((prev) => (prev === 0 ? 1 : 0));
|
|
25
43
|
}
|
|
26
44
|
else if (key.return) {
|
|
27
45
|
// Confirm selection
|
|
28
|
-
|
|
46
|
+
const finalState = {
|
|
47
|
+
selectedIndex,
|
|
48
|
+
confirmed: true,
|
|
49
|
+
};
|
|
50
|
+
requestHandlers.onCompleted(finalState);
|
|
29
51
|
if (selectedIndex === 0) {
|
|
30
52
|
onConfirmed();
|
|
31
53
|
}
|
|
@@ -34,16 +56,7 @@ export function Confirm({ message, state, status, stateHandlers, onConfirmed, on
|
|
|
34
56
|
}
|
|
35
57
|
}
|
|
36
58
|
}, { isActive });
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
];
|
|
41
|
-
if (!isActive) {
|
|
42
|
-
// When done, show both the message and user's choice in timeline
|
|
43
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsxs(UserQuery, { children: ["> ", options[selectedIndex].label] })] }));
|
|
44
|
-
}
|
|
45
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: message }) }), _jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
|
|
46
|
-
const isSelected = index === selectedIndex;
|
|
47
|
-
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, children: option.label }) }, option.value));
|
|
48
|
-
}) })] })] }));
|
|
59
|
+
// Controller always renders View, passing current state
|
|
60
|
+
const state = { selectedIndex, confirmed: false };
|
|
61
|
+
return _jsx(ConfirmView, { message: message, state: state, status: status });
|
|
49
62
|
}
|
package/dist/ui/Execute.js
CHANGED
|
@@ -2,182 +2,51 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useCallback, useEffect, useReducer } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { ComponentStatus, } from '../types/components.js';
|
|
5
|
-
import {
|
|
5
|
+
import { getTextColor } from '../services/colors.js';
|
|
6
6
|
import { useInput } from '../services/keyboard.js';
|
|
7
|
-
import {
|
|
8
|
-
import { formatErrorMessage } from '../services/messages.js';
|
|
9
|
-
import { replacePlaceholders } from '../services/resolver.js';
|
|
7
|
+
import { formatErrorMessage, getExecutionErrorMessage, } from '../services/messages.js';
|
|
10
8
|
import { ExecutionStatus } from '../services/shell.js';
|
|
11
9
|
import { ensureMinimumTime } from '../services/timing.js';
|
|
12
|
-
import {
|
|
10
|
+
import { buildAbortedState, handleTaskCompletion, handleTaskFailure, } from '../execution/handlers.js';
|
|
11
|
+
import { processTasks } from '../execution/processing.js';
|
|
12
|
+
import { executeReducer, initialState } from '../execution/reducer.js';
|
|
13
|
+
import { ExecuteActionType } from '../execution/types.js';
|
|
14
|
+
import { createMessage, markAsDone } from '../services/components.js';
|
|
15
|
+
import { Message } from './Message.js';
|
|
13
16
|
import { Spinner } from './Spinner.js';
|
|
14
17
|
import { Task } from './Task.js';
|
|
15
18
|
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;
|
|
26
|
+
// Return null only when loading completes with no commands
|
|
27
|
+
if (!isActive && taskInfos.length === 0 && !error) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
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 })] }));
|
|
33
|
+
};
|
|
16
34
|
/**
|
|
17
|
-
*
|
|
18
|
-
* Throws an error if unresolved placeholders are found.
|
|
35
|
+
* Execute controller: Runs tasks sequentially
|
|
19
36
|
*/
|
|
20
|
-
function
|
|
21
|
-
const unresolvedPattern = /\{[^}]+\}/g;
|
|
22
|
-
const matches = command.match(unresolvedPattern);
|
|
23
|
-
if (matches && matches.length > 0) {
|
|
24
|
-
throw new Error(`Unresolved placeholders in command: ${matches.join(', ')}\nCommand: ${original}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function executeReducer(state, action) {
|
|
28
|
-
switch (action.type) {
|
|
29
|
-
case 'PROCESSING_COMPLETE':
|
|
30
|
-
return {
|
|
31
|
-
...state,
|
|
32
|
-
message: action.payload.message,
|
|
33
|
-
hasProcessed: true,
|
|
34
|
-
};
|
|
35
|
-
case 'COMMANDS_READY':
|
|
36
|
-
return {
|
|
37
|
-
...state,
|
|
38
|
-
message: action.payload.message,
|
|
39
|
-
summary: action.payload.summary,
|
|
40
|
-
taskInfos: action.payload.taskInfos,
|
|
41
|
-
completed: 0,
|
|
42
|
-
};
|
|
43
|
-
case 'PROCESSING_ERROR':
|
|
44
|
-
return {
|
|
45
|
-
...state,
|
|
46
|
-
error: action.payload.error,
|
|
47
|
-
hasProcessed: true,
|
|
48
|
-
};
|
|
49
|
-
case 'TASK_COMPLETE': {
|
|
50
|
-
const updatedTimes = [
|
|
51
|
-
...state.taskExecutionTimes,
|
|
52
|
-
action.payload.elapsed,
|
|
53
|
-
];
|
|
54
|
-
const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
|
|
55
|
-
? {
|
|
56
|
-
...task,
|
|
57
|
-
status: ExecutionStatus.Success,
|
|
58
|
-
elapsed: action.payload.elapsed,
|
|
59
|
-
}
|
|
60
|
-
: task);
|
|
61
|
-
return {
|
|
62
|
-
...state,
|
|
63
|
-
taskInfos: updatedTaskInfos,
|
|
64
|
-
taskExecutionTimes: updatedTimes,
|
|
65
|
-
completed: action.payload.index + 1,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
case 'ALL_TASKS_COMPLETE': {
|
|
69
|
-
const updatedTimes = [
|
|
70
|
-
...state.taskExecutionTimes,
|
|
71
|
-
action.payload.elapsed,
|
|
72
|
-
];
|
|
73
|
-
const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
|
|
74
|
-
? {
|
|
75
|
-
...task,
|
|
76
|
-
status: ExecutionStatus.Success,
|
|
77
|
-
elapsed: action.payload.elapsed,
|
|
78
|
-
}
|
|
79
|
-
: task);
|
|
80
|
-
const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
|
|
81
|
-
const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
82
|
-
return {
|
|
83
|
-
...state,
|
|
84
|
-
taskInfos: updatedTaskInfos,
|
|
85
|
-
taskExecutionTimes: updatedTimes,
|
|
86
|
-
completed: action.payload.index + 1,
|
|
87
|
-
completionMessage: completion,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
case 'TASK_ERROR_CRITICAL': {
|
|
91
|
-
const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
|
|
92
|
-
? { ...task, status: ExecutionStatus.Failed, elapsed: 0 }
|
|
93
|
-
: task);
|
|
94
|
-
return {
|
|
95
|
-
...state,
|
|
96
|
-
taskInfos: updatedTaskInfos,
|
|
97
|
-
error: action.payload.error,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
case 'TASK_ERROR_CONTINUE': {
|
|
101
|
-
const updatedTimes = [
|
|
102
|
-
...state.taskExecutionTimes,
|
|
103
|
-
action.payload.elapsed,
|
|
104
|
-
];
|
|
105
|
-
const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
|
|
106
|
-
? {
|
|
107
|
-
...task,
|
|
108
|
-
status: ExecutionStatus.Failed,
|
|
109
|
-
elapsed: action.payload.elapsed,
|
|
110
|
-
}
|
|
111
|
-
: task);
|
|
112
|
-
return {
|
|
113
|
-
...state,
|
|
114
|
-
taskInfos: updatedTaskInfos,
|
|
115
|
-
taskExecutionTimes: updatedTimes,
|
|
116
|
-
completed: action.payload.index + 1,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
case 'LAST_TASK_ERROR': {
|
|
120
|
-
const updatedTimes = [
|
|
121
|
-
...state.taskExecutionTimes,
|
|
122
|
-
action.payload.elapsed,
|
|
123
|
-
];
|
|
124
|
-
const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
|
|
125
|
-
? {
|
|
126
|
-
...task,
|
|
127
|
-
status: ExecutionStatus.Failed,
|
|
128
|
-
elapsed: action.payload.elapsed,
|
|
129
|
-
}
|
|
130
|
-
: task);
|
|
131
|
-
const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
|
|
132
|
-
const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
133
|
-
return {
|
|
134
|
-
...state,
|
|
135
|
-
taskInfos: updatedTaskInfos,
|
|
136
|
-
taskExecutionTimes: updatedTimes,
|
|
137
|
-
completed: action.payload.index + 1,
|
|
138
|
-
completionMessage: completion,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
case 'CANCEL_EXECUTION': {
|
|
142
|
-
const updatedTaskInfos = state.taskInfos.map((task, taskIndex) => {
|
|
143
|
-
if (taskIndex < action.payload.completed) {
|
|
144
|
-
return { ...task, status: ExecutionStatus.Success };
|
|
145
|
-
}
|
|
146
|
-
else if (taskIndex === action.payload.completed) {
|
|
147
|
-
return { ...task, status: ExecutionStatus.Aborted };
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
return { ...task, status: ExecutionStatus.Cancelled };
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
return {
|
|
154
|
-
...state,
|
|
155
|
-
taskInfos: updatedTaskInfos,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
default:
|
|
159
|
-
return state;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
export function Execute({ tasks, state, status, service, stateHandlers, lifecycleHandlers, errorHandlers, workflowHandlers, }) {
|
|
37
|
+
export function Execute({ tasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
|
|
163
38
|
const isActive = status === ComponentStatus.Active;
|
|
164
|
-
const [localState, dispatch] = useReducer(executeReducer,
|
|
165
|
-
error: state?.error ?? null,
|
|
166
|
-
taskInfos: state?.taskInfos ?? [],
|
|
167
|
-
message: state?.message ?? '',
|
|
168
|
-
completed: state?.completed ?? 0,
|
|
169
|
-
hasProcessed: false,
|
|
170
|
-
taskExecutionTimes: state?.taskExecutionTimes ?? [],
|
|
171
|
-
completionMessage: state?.completionMessage ?? null,
|
|
172
|
-
summary: state?.summary ?? '',
|
|
173
|
-
});
|
|
39
|
+
const [localState, dispatch] = useReducer(executeReducer, initialState);
|
|
174
40
|
const { error, taskInfos, message, completed, hasProcessed, taskExecutionTimes, completionMessage, summary, } = localState;
|
|
175
41
|
// Derive loading state from current conditions
|
|
176
42
|
const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
|
|
177
43
|
const isExecuting = completed < taskInfos.length;
|
|
178
44
|
// Handle cancel with useCallback to ensure we capture latest state
|
|
179
45
|
const handleCancel = useCallback(() => {
|
|
180
|
-
dispatch({
|
|
46
|
+
dispatch({
|
|
47
|
+
type: ExecuteActionType.CancelExecution,
|
|
48
|
+
payload: { completed },
|
|
49
|
+
});
|
|
181
50
|
// Get updated task infos after cancel
|
|
182
51
|
const updatedTaskInfos = taskInfos.map((task, taskIndex) => {
|
|
183
52
|
if (taskIndex < completed) {
|
|
@@ -190,7 +59,8 @@ export function Execute({ tasks, state, status, service, stateHandlers, lifecycl
|
|
|
190
59
|
return { ...task, status: ExecutionStatus.Cancelled };
|
|
191
60
|
}
|
|
192
61
|
});
|
|
193
|
-
|
|
62
|
+
// Expose final state
|
|
63
|
+
const finalState = {
|
|
194
64
|
message,
|
|
195
65
|
summary,
|
|
196
66
|
taskInfos: updatedTaskInfos,
|
|
@@ -198,16 +68,16 @@ export function Execute({ tasks, state, status, service, stateHandlers, lifecycl
|
|
|
198
68
|
taskExecutionTimes,
|
|
199
69
|
completionMessage: null,
|
|
200
70
|
error: null,
|
|
201
|
-
}
|
|
202
|
-
|
|
71
|
+
};
|
|
72
|
+
requestHandlers.onCompleted(finalState);
|
|
73
|
+
requestHandlers.onAborted('execution');
|
|
203
74
|
}, [
|
|
204
75
|
message,
|
|
205
76
|
summary,
|
|
206
77
|
taskInfos,
|
|
207
78
|
completed,
|
|
208
79
|
taskExecutionTimes,
|
|
209
|
-
|
|
210
|
-
errorHandlers,
|
|
80
|
+
requestHandlers,
|
|
211
81
|
]);
|
|
212
82
|
useInput((_, key) => {
|
|
213
83
|
if (key.escape && (isLoading || isExecuting) && isActive) {
|
|
@@ -223,33 +93,40 @@ export function Execute({ tasks, state, status, service, stateHandlers, lifecycl
|
|
|
223
93
|
async function process(svc) {
|
|
224
94
|
const startTime = Date.now();
|
|
225
95
|
try {
|
|
226
|
-
|
|
227
|
-
const userConfig = loadUserConfig();
|
|
228
|
-
// Format tasks for the execute tool and resolve placeholders
|
|
229
|
-
const taskDescriptions = tasks
|
|
230
|
-
.map((task) => {
|
|
231
|
-
const resolvedAction = replacePlaceholders(task.action, userConfig);
|
|
232
|
-
const params = task.params
|
|
233
|
-
? ` (params: ${JSON.stringify(task.params)})`
|
|
234
|
-
: '';
|
|
235
|
-
return `- ${resolvedAction}${params}`;
|
|
236
|
-
})
|
|
237
|
-
.join('\n');
|
|
238
|
-
// Call execute tool to get commands
|
|
239
|
-
const result = await svc.processWithTool(taskDescriptions, 'execute');
|
|
96
|
+
const result = await processTasks(tasks, svc);
|
|
240
97
|
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
241
98
|
if (!mounted)
|
|
242
99
|
return;
|
|
243
100
|
// Add debug components to timeline if present
|
|
244
101
|
if (result.debug?.length) {
|
|
245
|
-
workflowHandlers
|
|
102
|
+
workflowHandlers.addToTimeline(...result.debug);
|
|
246
103
|
}
|
|
247
|
-
if (
|
|
104
|
+
if (result.commands.length === 0) {
|
|
105
|
+
// Check if this is an error response (has error field)
|
|
106
|
+
if (result.error) {
|
|
107
|
+
// Add error message to timeline
|
|
108
|
+
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);
|
|
121
|
+
lifecycleHandlers.completeActive();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// No commands and no error - just complete
|
|
248
125
|
dispatch({
|
|
249
|
-
type:
|
|
126
|
+
type: ExecuteActionType.ProcessingComplete,
|
|
250
127
|
payload: { message: result.message },
|
|
251
128
|
});
|
|
252
|
-
|
|
129
|
+
const finalState = {
|
|
253
130
|
message: result.message,
|
|
254
131
|
summary: '',
|
|
255
132
|
taskInfos: [],
|
|
@@ -257,51 +134,45 @@ export function Execute({ tasks, state, status, service, stateHandlers, lifecycl
|
|
|
257
134
|
taskExecutionTimes: [],
|
|
258
135
|
completionMessage: null,
|
|
259
136
|
error: null,
|
|
260
|
-
}
|
|
261
|
-
|
|
137
|
+
};
|
|
138
|
+
requestHandlers.onCompleted(finalState);
|
|
139
|
+
lifecycleHandlers.completeActive();
|
|
262
140
|
return;
|
|
263
141
|
}
|
|
264
|
-
//
|
|
265
|
-
const
|
|
266
|
-
const resolved = replacePlaceholders(cmd.command, userConfig);
|
|
267
|
-
validatePlaceholderResolution(resolved, cmd.command);
|
|
268
|
-
return { ...cmd, command: resolved };
|
|
269
|
-
});
|
|
270
|
-
// Set message, summary, and create task infos
|
|
271
|
-
const newMessage = result.message;
|
|
272
|
-
const newSummary = result.summary || '';
|
|
273
|
-
const infos = resolvedCommands.map((cmd, index) => ({
|
|
142
|
+
// Create task infos from commands
|
|
143
|
+
const infos = result.commands.map((cmd, index) => ({
|
|
274
144
|
label: tasks[index]?.action,
|
|
275
145
|
command: cmd,
|
|
276
146
|
}));
|
|
277
147
|
dispatch({
|
|
278
|
-
type:
|
|
148
|
+
type: ExecuteActionType.CommandsReady,
|
|
279
149
|
payload: {
|
|
280
|
-
message:
|
|
281
|
-
summary:
|
|
150
|
+
message: result.message,
|
|
151
|
+
summary: result.summary,
|
|
282
152
|
taskInfos: infos,
|
|
283
153
|
},
|
|
284
154
|
});
|
|
285
155
|
// Update state after AI processing
|
|
286
|
-
|
|
287
|
-
message:
|
|
288
|
-
summary:
|
|
156
|
+
const finalState = {
|
|
157
|
+
message: result.message,
|
|
158
|
+
summary: result.summary,
|
|
289
159
|
taskInfos: infos,
|
|
290
160
|
completed: 0,
|
|
291
161
|
taskExecutionTimes: [],
|
|
292
162
|
completionMessage: null,
|
|
293
163
|
error: null,
|
|
294
|
-
}
|
|
164
|
+
};
|
|
165
|
+
requestHandlers.onCompleted(finalState);
|
|
295
166
|
}
|
|
296
167
|
catch (err) {
|
|
297
168
|
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
298
169
|
if (mounted) {
|
|
299
170
|
const errorMessage = formatErrorMessage(err);
|
|
300
171
|
dispatch({
|
|
301
|
-
type:
|
|
172
|
+
type: ExecuteActionType.ProcessingError,
|
|
302
173
|
payload: { error: errorMessage },
|
|
303
174
|
});
|
|
304
|
-
|
|
175
|
+
const finalState = {
|
|
305
176
|
message: '',
|
|
306
177
|
summary: '',
|
|
307
178
|
taskInfos: [],
|
|
@@ -309,8 +180,9 @@ export function Execute({ tasks, state, status, service, stateHandlers, lifecycl
|
|
|
309
180
|
taskExecutionTimes: [],
|
|
310
181
|
completionMessage: null,
|
|
311
182
|
error: errorMessage,
|
|
312
|
-
}
|
|
313
|
-
|
|
183
|
+
};
|
|
184
|
+
requestHandlers.onCompleted(finalState);
|
|
185
|
+
requestHandlers.onError(errorMessage);
|
|
314
186
|
}
|
|
315
187
|
}
|
|
316
188
|
}
|
|
@@ -322,150 +194,78 @@ export function Execute({ tasks, state, status, service, stateHandlers, lifecycl
|
|
|
322
194
|
tasks,
|
|
323
195
|
isActive,
|
|
324
196
|
service,
|
|
325
|
-
|
|
197
|
+
requestHandlers,
|
|
326
198
|
lifecycleHandlers,
|
|
327
199
|
workflowHandlers,
|
|
328
|
-
errorHandlers,
|
|
329
200
|
taskInfos.length,
|
|
330
201
|
hasProcessed,
|
|
331
202
|
]);
|
|
332
203
|
// Handle task completion - move to next task
|
|
333
204
|
const handleTaskComplete = useCallback((index, _output, elapsed) => {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
taskInfos: updatedTaskInfos,
|
|
345
|
-
completed: index + 1,
|
|
346
|
-
taskExecutionTimes: updatedTimes,
|
|
347
|
-
completionMessage: null,
|
|
348
|
-
error: null,
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
// All tasks complete
|
|
353
|
-
const summaryText = summary.trim() || 'Execution completed';
|
|
354
|
-
dispatch({
|
|
355
|
-
type: 'ALL_TASKS_COMPLETE',
|
|
356
|
-
payload: { index, elapsed, summaryText },
|
|
357
|
-
});
|
|
358
|
-
const updatedTimes = [...taskExecutionTimes, elapsed];
|
|
359
|
-
const updatedTaskInfos = taskInfos.map((task, i) => i === index
|
|
360
|
-
? { ...task, status: ExecutionStatus.Success, elapsed }
|
|
361
|
-
: task);
|
|
362
|
-
const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
|
|
363
|
-
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
364
|
-
stateHandlers?.updateState({
|
|
365
|
-
message,
|
|
366
|
-
summary,
|
|
367
|
-
taskInfos: updatedTaskInfos,
|
|
368
|
-
completed: index + 1,
|
|
369
|
-
taskExecutionTimes: updatedTimes,
|
|
370
|
-
completionMessage: completion,
|
|
371
|
-
error: null,
|
|
372
|
-
});
|
|
373
|
-
lifecycleHandlers?.completeActive();
|
|
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();
|
|
374
215
|
}
|
|
375
216
|
}, [
|
|
376
217
|
taskInfos,
|
|
377
218
|
message,
|
|
378
|
-
lifecycleHandlers,
|
|
379
|
-
taskExecutionTimes,
|
|
380
219
|
summary,
|
|
381
|
-
|
|
220
|
+
taskExecutionTimes,
|
|
221
|
+
requestHandlers,
|
|
222
|
+
lifecycleHandlers,
|
|
382
223
|
]);
|
|
383
224
|
const handleTaskError = useCallback((index, error, elapsed) => {
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
summary,
|
|
395
|
-
taskInfos: updatedTaskInfos,
|
|
396
|
-
completed: index + 1,
|
|
397
|
-
taskExecutionTimes,
|
|
398
|
-
completionMessage: null,
|
|
399
|
-
error,
|
|
400
|
-
});
|
|
401
|
-
errorHandlers?.onError(error);
|
|
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);
|
|
402
235
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const updatedTimes = [...taskExecutionTimes, elapsed];
|
|
406
|
-
if (index < taskInfos.length - 1) {
|
|
407
|
-
dispatch({
|
|
408
|
-
type: 'TASK_ERROR_CONTINUE',
|
|
409
|
-
payload: { index, elapsed },
|
|
410
|
-
});
|
|
411
|
-
stateHandlers?.updateState({
|
|
412
|
-
message,
|
|
413
|
-
summary,
|
|
414
|
-
taskInfos: updatedTaskInfos,
|
|
415
|
-
completed: index + 1,
|
|
416
|
-
taskExecutionTimes: updatedTimes,
|
|
417
|
-
completionMessage: null,
|
|
418
|
-
error: null,
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
// Last task, complete execution
|
|
423
|
-
const summaryText = summary.trim() || 'Execution completed';
|
|
424
|
-
dispatch({
|
|
425
|
-
type: 'LAST_TASK_ERROR',
|
|
426
|
-
payload: { index, elapsed, summaryText },
|
|
427
|
-
});
|
|
428
|
-
const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
|
|
429
|
-
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
430
|
-
stateHandlers?.updateState({
|
|
431
|
-
message,
|
|
432
|
-
summary,
|
|
433
|
-
taskInfos: updatedTaskInfos,
|
|
434
|
-
completed: index + 1,
|
|
435
|
-
taskExecutionTimes: updatedTimes,
|
|
436
|
-
completionMessage: completion,
|
|
437
|
-
error: null,
|
|
438
|
-
});
|
|
439
|
-
lifecycleHandlers?.completeActive();
|
|
440
|
-
}
|
|
236
|
+
if (result.shouldComplete) {
|
|
237
|
+
lifecycleHandlers.completeActive();
|
|
441
238
|
}
|
|
442
239
|
}, [
|
|
443
240
|
taskInfos,
|
|
444
241
|
message,
|
|
445
|
-
stateHandlers,
|
|
446
|
-
lifecycleHandlers,
|
|
447
|
-
errorHandlers,
|
|
448
|
-
taskExecutionTimes,
|
|
449
242
|
summary,
|
|
243
|
+
taskExecutionTimes,
|
|
244
|
+
requestHandlers,
|
|
245
|
+
lifecycleHandlers,
|
|
450
246
|
]);
|
|
451
247
|
const handleTaskAbort = useCallback((_index) => {
|
|
452
248
|
// Task was aborted - execution already stopped by Escape handler
|
|
453
249
|
// Just update state, don't call onAborted (already called at Execute level)
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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 }));
|
|
471
271
|
}
|