prompt-language-shell 0.9.0 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ui/Main.js → Main.js} +24 -17
- package/dist/{ui → components}/Component.js +31 -26
- package/dist/{ui → components}/Workflow.js +23 -7
- package/dist/{ui → components/controllers}/Answer.js +18 -17
- package/dist/{ui → components/controllers}/Command.js +21 -24
- package/dist/{ui → components/controllers}/Config.js +17 -119
- package/dist/components/controllers/Confirm.js +42 -0
- package/dist/components/controllers/Execute.js +288 -0
- package/dist/{ui → components/controllers}/Introspect.js +22 -39
- package/dist/components/controllers/Refinement.js +18 -0
- package/dist/{ui → components/controllers}/Schedule.js +8 -124
- package/dist/{ui → components/controllers}/Validate.js +37 -50
- package/dist/components/views/Answer.js +28 -0
- package/dist/components/views/Command.js +11 -0
- package/dist/components/views/Config.js +115 -0
- package/dist/components/views/Confirm.js +24 -0
- package/dist/components/views/Execute.js +60 -0
- package/dist/{ui → components/views}/Feedback.js +3 -3
- package/dist/components/views/Introspect.js +17 -0
- package/dist/{ui → components/views}/Label.js +3 -3
- package/dist/{ui → components/views}/List.js +3 -3
- package/dist/{ui → components/views}/Output.js +2 -2
- package/dist/components/views/Refinement.js +9 -0
- package/dist/{ui → components/views}/Report.js +1 -1
- package/dist/components/views/Schedule.js +120 -0
- package/dist/{ui → components/views}/Separator.js +1 -1
- package/dist/{ui → components/views}/Spinner.js +1 -1
- package/dist/{ui → components/views}/Subtask.js +10 -7
- package/dist/components/views/Task.js +18 -0
- package/dist/components/views/Upcoming.js +30 -0
- package/dist/{ui → components/views}/UserQuery.js +1 -1
- package/dist/components/views/Validate.js +17 -0
- package/dist/{ui → components/views}/Welcome.js +1 -1
- 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 +20 -60
- package/dist/execution/processing.js +3 -1
- package/dist/execution/reducer.js +34 -44
- package/dist/execution/runner.js +99 -0
- package/dist/execution/types.js +4 -4
- package/dist/execution/utils.js +23 -1
- package/dist/index.js +1 -1
- package/dist/services/components.js +109 -394
- package/dist/services/logger.js +3 -3
- package/dist/services/messages.js +19 -0
- package/dist/services/refinement.js +5 -2
- package/dist/services/router.js +136 -55
- package/dist/services/shell.js +26 -6
- package/dist/services/timing.js +1 -0
- package/dist/skills/execute.md +40 -14
- package/dist/tools/execute.tool.js +0 -4
- package/dist/types/schemas.js +0 -1
- package/package.json +1 -1
- package/dist/parser.js +0 -13
- package/dist/services/config-utils.js +0 -20
- package/dist/ui/Confirm.js +0 -62
- package/dist/ui/Execute.js +0 -294
- package/dist/ui/Refinement.js +0 -23
- package/dist/ui/Task.js +0 -175
- /package/dist/{ui → components/views}/Debug.js +0 -0
- /package/dist/{ui → components/views}/Message.js +0 -0
- /package/dist/{ui → components/views}/Panel.js +0 -0
package/dist/ui/Execute.js
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useReducer, useRef } 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 { 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 { createFeedback, createMessage, markAsDone, } from '../services/components.js';
|
|
15
|
-
import { FeedbackType } from '../types/types.js';
|
|
16
|
-
import { Message } from './Message.js';
|
|
17
|
-
import { Spinner } from './Spinner.js';
|
|
18
|
-
import { Task } from './Task.js';
|
|
19
|
-
const MINIMUM_PROCESSING_TIME = 400;
|
|
20
|
-
/**
|
|
21
|
-
* Create an ExecuteState with defaults
|
|
22
|
-
*/
|
|
23
|
-
function createExecuteState(overrides = {}) {
|
|
24
|
-
return {
|
|
25
|
-
message: '',
|
|
26
|
-
summary: '',
|
|
27
|
-
tasks: [],
|
|
28
|
-
completed: 0,
|
|
29
|
-
completionMessage: null,
|
|
30
|
-
error: null,
|
|
31
|
-
...overrides,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
export const ExecuteView = ({ state, status, workdir, onOutputChange, onTaskComplete, onTaskAbort, onTaskError, }) => {
|
|
35
|
-
const isActive = status === ComponentStatus.Active;
|
|
36
|
-
const { error, tasks, message, completed, completionMessage } = state;
|
|
37
|
-
const hasProcessed = tasks.length > 0;
|
|
38
|
-
// Derive loading state from current conditions
|
|
39
|
-
const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
|
|
40
|
-
const isExecuting = completed < tasks.length;
|
|
41
|
-
// Return null only when loading completes with no commands
|
|
42
|
-
if (!isActive && tasks.length === 0 && !error) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
// Show completed steps when not active
|
|
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 })] }));
|
|
60
|
-
};
|
|
61
|
-
/**
|
|
62
|
-
* Execute controller: Runs tasks sequentially
|
|
63
|
-
*/
|
|
64
|
-
export function Execute({ tasks: inputTasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
|
|
65
|
-
const isActive = status === ComponentStatus.Active;
|
|
66
|
-
const [localState, dispatch] = useReducer(executeReducer, initialState);
|
|
67
|
-
// Ref to store current output for each task (avoids re-renders)
|
|
68
|
-
const taskOutputRef = useRef(new Map());
|
|
69
|
-
// Track working directory across commands (persists cd changes)
|
|
70
|
-
const workdirRef = useRef(undefined);
|
|
71
|
-
const { error, tasks, message, completed, hasProcessed, completionMessage, summary, } = localState;
|
|
72
|
-
// Derive loading state from current conditions
|
|
73
|
-
const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
|
|
74
|
-
const isExecuting = completed < tasks.length;
|
|
75
|
-
// Handle output changes from Task - store in ref (no re-render)
|
|
76
|
-
const handleOutputChange = useCallback((index, taskOutput) => {
|
|
77
|
-
taskOutputRef.current.set(index, taskOutput);
|
|
78
|
-
}, []);
|
|
79
|
-
// Handle cancel with useCallback to ensure we capture latest state
|
|
80
|
-
const handleCancel = useCallback(() => {
|
|
81
|
-
dispatch({
|
|
82
|
-
type: ExecuteActionType.CancelExecution,
|
|
83
|
-
payload: { completed },
|
|
84
|
-
});
|
|
85
|
-
// Get updated task infos after cancel, merging output from ref
|
|
86
|
-
const updatedTaskInfos = tasks.map((task, taskIndex) => {
|
|
87
|
-
const output = taskOutputRef.current.get(taskIndex);
|
|
88
|
-
const baseTask = output
|
|
89
|
-
? {
|
|
90
|
-
...task,
|
|
91
|
-
stdout: output.stdout,
|
|
92
|
-
stderr: output.stderr,
|
|
93
|
-
error: output.error,
|
|
94
|
-
}
|
|
95
|
-
: task;
|
|
96
|
-
if (taskIndex < completed) {
|
|
97
|
-
return { ...baseTask, status: ExecutionStatus.Success };
|
|
98
|
-
}
|
|
99
|
-
else if (taskIndex === completed) {
|
|
100
|
-
return { ...baseTask, status: ExecutionStatus.Aborted };
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
return { ...baseTask, status: ExecutionStatus.Cancelled };
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
// Expose final state
|
|
107
|
-
const finalState = createExecuteState({
|
|
108
|
-
message,
|
|
109
|
-
summary,
|
|
110
|
-
tasks: updatedTaskInfos,
|
|
111
|
-
completed,
|
|
112
|
-
});
|
|
113
|
-
requestHandlers.onCompleted(finalState);
|
|
114
|
-
requestHandlers.onAborted('execution');
|
|
115
|
-
}, [message, summary, tasks, completed, requestHandlers]);
|
|
116
|
-
useInput((_, key) => {
|
|
117
|
-
if (key.escape && (isLoading || isExecuting) && isActive) {
|
|
118
|
-
handleCancel();
|
|
119
|
-
}
|
|
120
|
-
}, { isActive: (isLoading || isExecuting) && isActive });
|
|
121
|
-
// Process tasks to get commands from AI
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
if (!isActive || tasks.length > 0 || hasProcessed) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
let mounted = true;
|
|
127
|
-
async function process(svc) {
|
|
128
|
-
const startTime = Date.now();
|
|
129
|
-
try {
|
|
130
|
-
const result = await processTasks(inputTasks, svc);
|
|
131
|
-
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
132
|
-
if (!mounted)
|
|
133
|
-
return;
|
|
134
|
-
// Add debug components to timeline if present
|
|
135
|
-
if (result.debug?.length) {
|
|
136
|
-
workflowHandlers.addToTimeline(...result.debug);
|
|
137
|
-
}
|
|
138
|
-
if (result.commands.length === 0) {
|
|
139
|
-
// Check if this is an error response (has error field)
|
|
140
|
-
if (result.error) {
|
|
141
|
-
// Add error message to timeline
|
|
142
|
-
const errorMessage = getExecutionErrorMessage(result.error);
|
|
143
|
-
workflowHandlers.addToTimeline(markAsDone(createMessage(errorMessage)));
|
|
144
|
-
// Complete without error in state (message already in timeline)
|
|
145
|
-
requestHandlers.onCompleted(createExecuteState({ message: result.message }));
|
|
146
|
-
lifecycleHandlers.completeActive();
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
// No commands and no error - just complete
|
|
150
|
-
dispatch({
|
|
151
|
-
type: ExecuteActionType.ProcessingComplete,
|
|
152
|
-
payload: { message: result.message },
|
|
153
|
-
});
|
|
154
|
-
requestHandlers.onCompleted(createExecuteState({ message: result.message }));
|
|
155
|
-
lifecycleHandlers.completeActive();
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
// Create task infos from commands
|
|
159
|
-
const tasks = result.commands.map((cmd, index) => ({
|
|
160
|
-
label: inputTasks[index]?.action ?? cmd.description,
|
|
161
|
-
command: cmd,
|
|
162
|
-
status: ExecutionStatus.Pending,
|
|
163
|
-
elapsed: 0,
|
|
164
|
-
}));
|
|
165
|
-
dispatch({
|
|
166
|
-
type: ExecuteActionType.CommandsReady,
|
|
167
|
-
payload: {
|
|
168
|
-
message: result.message,
|
|
169
|
-
summary: result.summary,
|
|
170
|
-
tasks,
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
// Update state after AI processing
|
|
174
|
-
requestHandlers.onCompleted(createExecuteState({
|
|
175
|
-
message: result.message,
|
|
176
|
-
summary: result.summary,
|
|
177
|
-
tasks,
|
|
178
|
-
}));
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
182
|
-
if (mounted) {
|
|
183
|
-
const errorMessage = formatErrorMessage(err);
|
|
184
|
-
dispatch({
|
|
185
|
-
type: ExecuteActionType.ProcessingError,
|
|
186
|
-
payload: { error: errorMessage },
|
|
187
|
-
});
|
|
188
|
-
requestHandlers.onCompleted(createExecuteState({ error: errorMessage }));
|
|
189
|
-
requestHandlers.onError(errorMessage);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
void process(service);
|
|
194
|
-
return () => {
|
|
195
|
-
mounted = false;
|
|
196
|
-
};
|
|
197
|
-
}, [
|
|
198
|
-
inputTasks,
|
|
199
|
-
isActive,
|
|
200
|
-
service,
|
|
201
|
-
requestHandlers,
|
|
202
|
-
lifecycleHandlers,
|
|
203
|
-
workflowHandlers,
|
|
204
|
-
tasks.length,
|
|
205
|
-
hasProcessed,
|
|
206
|
-
]);
|
|
207
|
-
// Handle task completion - move to next task
|
|
208
|
-
const handleTaskComplete = useCallback((index, elapsed, taskOutput) => {
|
|
209
|
-
// Track working directory for subsequent commands
|
|
210
|
-
if (taskOutput.workdir) {
|
|
211
|
-
workdirRef.current = taskOutput.workdir;
|
|
212
|
-
}
|
|
213
|
-
// Update tasks with output before calling handler
|
|
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();
|
|
231
|
-
}
|
|
232
|
-
}, [tasks, message, summary, requestHandlers, lifecycleHandlers]);
|
|
233
|
-
const handleTaskError = useCallback((index, error, elapsed, taskOutput) => {
|
|
234
|
-
// Track working directory for subsequent commands (even on error)
|
|
235
|
-
if (taskOutput.workdir) {
|
|
236
|
-
workdirRef.current = taskOutput.workdir;
|
|
237
|
-
}
|
|
238
|
-
// Update tasks with output before calling handler
|
|
239
|
-
const tasksWithOutput = tasks.map((task, i) => i === index
|
|
240
|
-
? {
|
|
241
|
-
...task,
|
|
242
|
-
stdout: taskOutput.stdout,
|
|
243
|
-
stderr: taskOutput.stderr,
|
|
244
|
-
error: taskOutput.error,
|
|
245
|
-
}
|
|
246
|
-
: task);
|
|
247
|
-
const result = handleTaskFailure(index, error, elapsed, {
|
|
248
|
-
tasks: tasksWithOutput,
|
|
249
|
-
message,
|
|
250
|
-
summary,
|
|
251
|
-
});
|
|
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
|
-
}, [
|
|
263
|
-
tasks,
|
|
264
|
-
message,
|
|
265
|
-
summary,
|
|
266
|
-
requestHandlers,
|
|
267
|
-
lifecycleHandlers,
|
|
268
|
-
workflowHandlers,
|
|
269
|
-
]);
|
|
270
|
-
const handleTaskAbort = useCallback((index, taskOutput) => {
|
|
271
|
-
// Task was aborted - execution already stopped by Escape handler
|
|
272
|
-
// Update tasks with output before building state
|
|
273
|
-
const tasksWithOutput = tasks.map((task, i) => i === index
|
|
274
|
-
? {
|
|
275
|
-
...task,
|
|
276
|
-
stdout: taskOutput.stdout,
|
|
277
|
-
stderr: taskOutput.stderr,
|
|
278
|
-
error: taskOutput.error,
|
|
279
|
-
}
|
|
280
|
-
: task);
|
|
281
|
-
const finalState = buildAbortedState(tasksWithOutput, message, summary, completed);
|
|
282
|
-
requestHandlers.onCompleted(finalState);
|
|
283
|
-
}, [tasks, message, summary, completed, requestHandlers]);
|
|
284
|
-
// Controller always renders View with current state
|
|
285
|
-
const viewState = createExecuteState({
|
|
286
|
-
error,
|
|
287
|
-
tasks,
|
|
288
|
-
message,
|
|
289
|
-
summary,
|
|
290
|
-
completed,
|
|
291
|
-
completionMessage,
|
|
292
|
-
});
|
|
293
|
-
return (_jsx(ExecuteView, { state: viewState, status: status, workdir: workdirRef.current, onOutputChange: handleOutputChange, onTaskComplete: handleTaskComplete, onTaskAbort: handleTaskAbort, onTaskError: handleTaskError }));
|
|
294
|
-
}
|
package/dist/ui/Refinement.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box } from 'ink';
|
|
3
|
-
import { ComponentStatus } from '../types/components.js';
|
|
4
|
-
import { useInput } from '../services/keyboard.js';
|
|
5
|
-
import { Message } from './Message.js';
|
|
6
|
-
import { Spinner } from './Spinner.js';
|
|
7
|
-
export const RefinementView = ({ text, status }) => {
|
|
8
|
-
const isActive = status === ComponentStatus.Active;
|
|
9
|
-
return (_jsxs(Box, { gap: 1, children: [_jsx(Message, { text: text, status: status }), isActive && _jsx(Spinner, {})] }));
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Refinement controller: Handles abort input
|
|
13
|
-
*/
|
|
14
|
-
export const Refinement = ({ text, status, onAborted }) => {
|
|
15
|
-
const isActive = status === ComponentStatus.Active;
|
|
16
|
-
useInput((_, key) => {
|
|
17
|
-
if (key.escape && isActive) {
|
|
18
|
-
onAborted('plan refinement');
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
}, { isActive });
|
|
22
|
-
return _jsx(RefinementView, { text: text, status: status });
|
|
23
|
-
};
|
package/dist/ui/Task.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { Box } from 'ink';
|
|
4
|
-
import { ExecutionResult, ExecutionStatus, executeCommand, setOutputCallback, } from '../services/shell.js';
|
|
5
|
-
import { calculateElapsed } from '../services/utils.js';
|
|
6
|
-
import { Output } from './Output.js';
|
|
7
|
-
import { Subtask } from './Subtask.js';
|
|
8
|
-
export function Task({ label, command, isActive, isFinished, index, initialStatus, initialElapsed, initialOutput, onOutputChange, onComplete, onAbort, onError, }) {
|
|
9
|
-
const [status, setStatus] = useState(initialStatus ?? ExecutionStatus.Pending);
|
|
10
|
-
const [startTime, setStartTime] = useState();
|
|
11
|
-
const [endTime, setEndTime] = useState();
|
|
12
|
-
const [elapsed, setElapsed] = useState(initialElapsed);
|
|
13
|
-
const [currentElapsed, setCurrentElapsed] = useState(0);
|
|
14
|
-
const [stdout, setStdout] = useState(initialOutput?.stdout ?? '');
|
|
15
|
-
const [stderr, setStderr] = useState(initialOutput?.stderr ?? '');
|
|
16
|
-
const [error, setError] = useState(initialOutput?.error ?? '');
|
|
17
|
-
// Refs to track current output for callbacks (avoid stale closure)
|
|
18
|
-
const stdoutRef = useRef(stdout);
|
|
19
|
-
const stderrRef = useRef(stderr);
|
|
20
|
-
const errorRef = useRef(error);
|
|
21
|
-
stdoutRef.current = stdout;
|
|
22
|
-
stderrRef.current = stderr;
|
|
23
|
-
errorRef.current = error;
|
|
24
|
-
// Refs for callbacks to avoid stale closures in async effects
|
|
25
|
-
const onOutputChangeRef = useRef(onOutputChange);
|
|
26
|
-
const onCompleteRef = useRef(onComplete);
|
|
27
|
-
const onErrorRef = useRef(onError);
|
|
28
|
-
const onAbortRef = useRef(onAbort);
|
|
29
|
-
onOutputChangeRef.current = onOutputChange;
|
|
30
|
-
onCompleteRef.current = onComplete;
|
|
31
|
-
onErrorRef.current = onError;
|
|
32
|
-
onAbortRef.current = onAbort;
|
|
33
|
-
// Update elapsed time while running
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (status !== ExecutionStatus.Running || !startTime)
|
|
36
|
-
return;
|
|
37
|
-
const interval = setInterval(() => {
|
|
38
|
-
setCurrentElapsed((prev) => {
|
|
39
|
-
const next = Date.now() - startTime;
|
|
40
|
-
return next !== prev ? next : prev;
|
|
41
|
-
});
|
|
42
|
-
}, 1000);
|
|
43
|
-
return () => {
|
|
44
|
-
clearInterval(interval);
|
|
45
|
-
};
|
|
46
|
-
}, [status, startTime]);
|
|
47
|
-
// Execute command when becoming active
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
// Don't execute if task is cancelled or if not active
|
|
50
|
-
if (!isActive ||
|
|
51
|
-
status === ExecutionStatus.Cancelled ||
|
|
52
|
-
status !== ExecutionStatus.Pending) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
let mounted = true;
|
|
56
|
-
async function execute() {
|
|
57
|
-
const start = Date.now();
|
|
58
|
-
setStatus(ExecutionStatus.Running);
|
|
59
|
-
setStartTime(start);
|
|
60
|
-
setCurrentElapsed(0);
|
|
61
|
-
setStdout('');
|
|
62
|
-
setStderr('');
|
|
63
|
-
setError('');
|
|
64
|
-
// Set up output callback to capture real-time output
|
|
65
|
-
setOutputCallback((data, stream) => {
|
|
66
|
-
if (!mounted)
|
|
67
|
-
return;
|
|
68
|
-
if (stream === 'stdout') {
|
|
69
|
-
setStdout((prev) => {
|
|
70
|
-
const newStdout = prev + data;
|
|
71
|
-
stdoutRef.current = newStdout;
|
|
72
|
-
// Report output change to parent using refs for current values
|
|
73
|
-
onOutputChangeRef.current?.(index, {
|
|
74
|
-
stdout: newStdout,
|
|
75
|
-
stderr: stderrRef.current,
|
|
76
|
-
error: errorRef.current,
|
|
77
|
-
});
|
|
78
|
-
return newStdout;
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
setStderr((prev) => {
|
|
83
|
-
const newStderr = prev + data;
|
|
84
|
-
stderrRef.current = newStderr;
|
|
85
|
-
// Report output change to parent using refs for current values
|
|
86
|
-
onOutputChangeRef.current?.(index, {
|
|
87
|
-
stdout: stdoutRef.current,
|
|
88
|
-
stderr: newStderr,
|
|
89
|
-
error: errorRef.current,
|
|
90
|
-
});
|
|
91
|
-
return newStderr;
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
try {
|
|
96
|
-
const output = await executeCommand(command, undefined, index);
|
|
97
|
-
setOutputCallback(undefined); // Clear callback
|
|
98
|
-
if (!mounted)
|
|
99
|
-
return;
|
|
100
|
-
const end = Date.now();
|
|
101
|
-
setEndTime(end);
|
|
102
|
-
const taskDuration = calculateElapsed(start);
|
|
103
|
-
setElapsed(taskDuration);
|
|
104
|
-
setStatus(output.result === ExecutionResult.Success
|
|
105
|
-
? ExecutionStatus.Success
|
|
106
|
-
: ExecutionStatus.Failed);
|
|
107
|
-
if (output.result === ExecutionResult.Success) {
|
|
108
|
-
const taskOutput = {
|
|
109
|
-
stdout: output.output,
|
|
110
|
-
stderr: output.errors,
|
|
111
|
-
error: '',
|
|
112
|
-
workdir: output.workdir,
|
|
113
|
-
};
|
|
114
|
-
onCompleteRef.current?.(index, taskDuration, taskOutput);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
const errorMsg = output.errors || output.error || 'Command failed';
|
|
118
|
-
setError(errorMsg);
|
|
119
|
-
const taskOutput = {
|
|
120
|
-
stdout: output.output,
|
|
121
|
-
stderr: output.errors,
|
|
122
|
-
error: errorMsg,
|
|
123
|
-
workdir: output.workdir,
|
|
124
|
-
};
|
|
125
|
-
onErrorRef.current?.(index, errorMsg, taskDuration, taskOutput);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
setOutputCallback(undefined); // Clear callback
|
|
130
|
-
if (!mounted)
|
|
131
|
-
return;
|
|
132
|
-
const end = Date.now();
|
|
133
|
-
setEndTime(end);
|
|
134
|
-
const errorDuration = calculateElapsed(start);
|
|
135
|
-
setElapsed(errorDuration);
|
|
136
|
-
setStatus(ExecutionStatus.Failed);
|
|
137
|
-
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
138
|
-
setError(errorMsg);
|
|
139
|
-
const taskOutput = {
|
|
140
|
-
stdout: stdoutRef.current,
|
|
141
|
-
stderr: stderrRef.current,
|
|
142
|
-
error: errorMsg,
|
|
143
|
-
};
|
|
144
|
-
// Use try/catch to prevent callback errors from propagating
|
|
145
|
-
try {
|
|
146
|
-
onErrorRef.current?.(index, errorMsg, errorDuration, taskOutput);
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
// Callback error - already set error state above
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
void execute();
|
|
154
|
-
return () => {
|
|
155
|
-
mounted = false;
|
|
156
|
-
};
|
|
157
|
-
}, [isActive]);
|
|
158
|
-
// Handle abort when task becomes inactive while running
|
|
159
|
-
useEffect(() => {
|
|
160
|
-
if (!isActive && status === ExecutionStatus.Running && startTime) {
|
|
161
|
-
// Task was aborted mid-execution
|
|
162
|
-
const end = Date.now();
|
|
163
|
-
setEndTime(end);
|
|
164
|
-
setElapsed(calculateElapsed(startTime));
|
|
165
|
-
setStatus(ExecutionStatus.Aborted);
|
|
166
|
-
const taskOutput = {
|
|
167
|
-
stdout: stdoutRef.current,
|
|
168
|
-
stderr: stderrRef.current,
|
|
169
|
-
error: errorRef.current,
|
|
170
|
-
};
|
|
171
|
-
onAbortRef.current?.(index, taskOutput);
|
|
172
|
-
}
|
|
173
|
-
}, [isActive, status, startTime, index]);
|
|
174
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Subtask, { label: label, command: command, status: status, isActive: isActive, startTime: startTime, endTime: endTime, elapsed: status === ExecutionStatus.Running ? currentElapsed : elapsed }), _jsx(Output, { stdout: stdout, stderr: stderr, isFinished: isFinished, status: status }, `${stdout.length}-${stderr.length}`)] }));
|
|
175
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|