prompt-language-shell 0.7.6 → 0.8.0
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/anthropic.js +1 -0
- package/dist/services/colors.js +70 -4
- package/dist/services/components.js +35 -6
- package/dist/services/router.js +2 -3
- package/dist/services/shell.js +1 -0
- package/dist/services/utils.js +6 -0
- package/dist/skills/execute.md +20 -8
- package/dist/skills/schedule.md +7 -3
- package/dist/tools/execute.tool.js +5 -1
- package/dist/ui/Answer.js +9 -1
- package/dist/ui/Command.js +7 -6
- package/dist/ui/Component.js +2 -2
- package/dist/ui/Config.js +31 -2
- package/dist/ui/Confirm.js +1 -1
- package/dist/ui/Execute.js +198 -202
- package/dist/ui/Feedback.js +1 -1
- package/dist/ui/Introspect.js +3 -1
- package/dist/ui/Label.js +1 -1
- package/dist/ui/List.js +2 -1
- package/dist/ui/Schedule.js +1 -1
- package/dist/ui/Spinner.js +2 -1
- package/dist/ui/Subtask.js +22 -0
- package/dist/ui/Task.js +85 -0
- package/dist/ui/Validate.js +26 -17
- package/dist/ui/Workflow.js +1 -1
- package/package.json +12 -12
package/dist/ui/Execute.js
CHANGED
|
@@ -1,168 +1,72 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
-
import { ComponentStatus } from '../types/components.js';
|
|
5
|
-
import { Colors, getTextColor
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
5
|
+
import { Colors, getTextColor } from '../services/colors.js';
|
|
6
|
+
import { addDebugToTimeline } from '../services/components.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
8
|
+
import { loadUserConfig } from '../services/loader.js';
|
|
7
9
|
import { formatErrorMessage } from '../services/messages.js';
|
|
8
|
-
import { formatDuration } from '../services/utils.js';
|
|
9
|
-
import { ExecutionStatus, executeCommands, } from '../services/shell.js';
|
|
10
10
|
import { replacePlaceholders } from '../services/resolver.js';
|
|
11
|
-
import {
|
|
11
|
+
import { ExecutionStatus } from '../services/shell.js';
|
|
12
12
|
import { ensureMinimumTime } from '../services/timing.js';
|
|
13
|
+
import { formatDuration } from '../services/utils.js';
|
|
13
14
|
import { Spinner } from './Spinner.js';
|
|
15
|
+
import { Task } from './Task.js';
|
|
14
16
|
const MINIMUM_PROCESSING_TIME = 400;
|
|
15
|
-
const STATUS_ICONS = {
|
|
16
|
-
[ExecutionStatus.Pending]: '- ',
|
|
17
|
-
[ExecutionStatus.Running]: '• ',
|
|
18
|
-
[ExecutionStatus.Success]: '✓ ',
|
|
19
|
-
[ExecutionStatus.Failed]: '✗ ',
|
|
20
|
-
[ExecutionStatus.Aborted]: '⊘ ',
|
|
21
|
-
};
|
|
22
|
-
function calculateTotalElapsed(commandStatuses) {
|
|
23
|
-
return commandStatuses.reduce((sum, cmd) => {
|
|
24
|
-
if (cmd.elapsed !== undefined) {
|
|
25
|
-
return sum + cmd.elapsed;
|
|
26
|
-
}
|
|
27
|
-
if (cmd.startTime) {
|
|
28
|
-
const elapsed = cmd.endTime
|
|
29
|
-
? cmd.endTime - cmd.startTime
|
|
30
|
-
: Date.now() - cmd.startTime;
|
|
31
|
-
return sum + elapsed;
|
|
32
|
-
}
|
|
33
|
-
return sum;
|
|
34
|
-
}, 0);
|
|
35
|
-
}
|
|
36
|
-
function getStatusColors(status) {
|
|
37
|
-
switch (status) {
|
|
38
|
-
case ExecutionStatus.Pending:
|
|
39
|
-
return {
|
|
40
|
-
icon: Palette.Gray,
|
|
41
|
-
description: Palette.Gray,
|
|
42
|
-
command: Palette.DarkGray,
|
|
43
|
-
symbol: Palette.DarkGray,
|
|
44
|
-
};
|
|
45
|
-
case ExecutionStatus.Running:
|
|
46
|
-
return {
|
|
47
|
-
icon: Palette.Gray,
|
|
48
|
-
description: getTextColor(true),
|
|
49
|
-
command: Palette.LightGreen,
|
|
50
|
-
symbol: Palette.AshGray,
|
|
51
|
-
};
|
|
52
|
-
case ExecutionStatus.Success:
|
|
53
|
-
return {
|
|
54
|
-
icon: Colors.Status.Success,
|
|
55
|
-
description: getTextColor(true),
|
|
56
|
-
command: Palette.Gray,
|
|
57
|
-
symbol: Palette.Gray,
|
|
58
|
-
};
|
|
59
|
-
case ExecutionStatus.Failed:
|
|
60
|
-
return {
|
|
61
|
-
icon: Colors.Status.Error,
|
|
62
|
-
description: Colors.Status.Error,
|
|
63
|
-
command: Colors.Status.Error,
|
|
64
|
-
symbol: Palette.Gray,
|
|
65
|
-
};
|
|
66
|
-
case ExecutionStatus.Aborted:
|
|
67
|
-
return {
|
|
68
|
-
icon: Palette.DarkOrange,
|
|
69
|
-
description: getTextColor(true),
|
|
70
|
-
command: Palette.DarkOrange,
|
|
71
|
-
symbol: Palette.Gray,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function CommandStatusDisplay({ item, elapsed }) {
|
|
76
|
-
const colors = getStatusColors(item.status);
|
|
77
|
-
const getElapsedTime = () => {
|
|
78
|
-
if (item.status === ExecutionStatus.Running && elapsed !== undefined) {
|
|
79
|
-
return elapsed;
|
|
80
|
-
}
|
|
81
|
-
else if (item.startTime && item.endTime) {
|
|
82
|
-
return item.endTime - item.startTime;
|
|
83
|
-
}
|
|
84
|
-
return undefined;
|
|
85
|
-
};
|
|
86
|
-
const elapsedTime = getElapsedTime();
|
|
87
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: colors.icon, children: STATUS_ICONS[item.status] }), _jsx(Text, { color: colors.description, children: item.label || item.command.description }), elapsedTime !== undefined && (_jsxs(Text, { color: Palette.DarkGray, children: [" (", formatDuration(elapsedTime), ")"] }))] }), _jsxs(Box, { paddingLeft: 5, children: [_jsx(Text, { color: colors.symbol, children: "\u221F " }), _jsx(Text, { color: colors.command, children: item.command.command }), item.status === ExecutionStatus.Running && (_jsxs(Text, { children: [' ', _jsx(Spinner, {})] }))] })] }));
|
|
88
|
-
}
|
|
89
17
|
export function Execute({ tasks, state, status, service, handlers, }) {
|
|
90
18
|
const isActive = status === ComponentStatus.Active;
|
|
91
|
-
// isActive passed as prop
|
|
92
19
|
const [error, setError] = useState(state?.error ?? null);
|
|
93
|
-
const [
|
|
94
|
-
const [commandStatuses, setCommandStatuses] = useState(state?.commandStatuses ?? []);
|
|
20
|
+
const [taskInfos, setTaskInfos] = useState(state?.taskInfos ?? []);
|
|
95
21
|
const [message, setMessage] = useState(state?.message ?? '');
|
|
96
|
-
const [
|
|
97
|
-
const [runningIndex, setRunningIndex] = useState(null);
|
|
98
|
-
const [outputs, setOutputs] = useState([]);
|
|
22
|
+
const [completed, setCompleted] = useState(state?.completed ?? 0);
|
|
99
23
|
const [hasProcessed, setHasProcessed] = useState(false);
|
|
24
|
+
const [taskExecutionTimes, setTaskExecutionTimes] = useState(state?.taskExecutionTimes ?? []);
|
|
25
|
+
const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
|
|
26
|
+
const [summary, setSummary] = useState(state?.summary ?? '');
|
|
100
27
|
// Derive loading state from current conditions
|
|
101
|
-
const isLoading = isActive &&
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
28
|
+
const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
|
|
29
|
+
const isExecuting = completed < taskInfos.length;
|
|
30
|
+
// Handle cancel with useCallback to ensure we capture latest state
|
|
31
|
+
const handleCancel = useCallback(() => {
|
|
32
|
+
// Mark tasks based on their status relative to completed:
|
|
33
|
+
// - Before completed: finished (Success)
|
|
34
|
+
// - At completed: interrupted (Aborted)
|
|
35
|
+
// - After completed: never started (Cancelled)
|
|
36
|
+
const updatedTaskInfos = taskInfos.map((task, taskIndex) => {
|
|
37
|
+
if (taskIndex < completed) {
|
|
38
|
+
// Tasks that completed before interruption
|
|
39
|
+
return { ...task, status: ExecutionStatus.Success };
|
|
40
|
+
}
|
|
41
|
+
else if (taskIndex === completed) {
|
|
42
|
+
// Task that was running when interrupted
|
|
43
|
+
return { ...task, status: ExecutionStatus.Aborted };
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Tasks that haven't started yet
|
|
47
|
+
return { ...task, status: ExecutionStatus.Cancelled };
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
setTaskInfos(updatedTaskInfos);
|
|
51
|
+
handlers?.updateState({
|
|
52
|
+
message,
|
|
53
|
+
summary,
|
|
54
|
+
taskInfos: updatedTaskInfos,
|
|
55
|
+
completed,
|
|
56
|
+
taskExecutionTimes,
|
|
57
|
+
completionMessage: null,
|
|
58
|
+
error: null,
|
|
59
|
+
});
|
|
60
|
+
handlers?.onAborted('execution');
|
|
61
|
+
}, [message, summary, taskInfos, completed, taskExecutionTimes, handlers]);
|
|
106
62
|
useInput((_, key) => {
|
|
107
63
|
if (key.escape && (isLoading || isExecuting) && isActive) {
|
|
108
|
-
|
|
109
|
-
setRunningIndex(null);
|
|
110
|
-
// Mark any running command as aborted when cancelled
|
|
111
|
-
const now = Date.now();
|
|
112
|
-
setCommandStatuses((prev) => {
|
|
113
|
-
const updated = prev.map((item) => {
|
|
114
|
-
if (item.status === ExecutionStatus.Running) {
|
|
115
|
-
const elapsed = item.startTime
|
|
116
|
-
? Math.floor((now - item.startTime) / 1000) * 1000
|
|
117
|
-
: undefined;
|
|
118
|
-
return {
|
|
119
|
-
...item,
|
|
120
|
-
status: ExecutionStatus.Aborted,
|
|
121
|
-
endTime: now,
|
|
122
|
-
elapsed,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
return item;
|
|
126
|
-
});
|
|
127
|
-
// Save state after updating
|
|
128
|
-
handlers?.updateState({
|
|
129
|
-
commandStatuses: updated,
|
|
130
|
-
message,
|
|
131
|
-
});
|
|
132
|
-
return updated;
|
|
133
|
-
});
|
|
134
|
-
handlers?.onAborted('execution');
|
|
64
|
+
handleCancel();
|
|
135
65
|
}
|
|
136
66
|
}, { isActive: (isLoading || isExecuting) && isActive });
|
|
137
|
-
//
|
|
67
|
+
// Process tasks to get commands from AI
|
|
138
68
|
useEffect(() => {
|
|
139
|
-
if (
|
|
140
|
-
return;
|
|
141
|
-
const item = commandStatuses[runningIndex];
|
|
142
|
-
if (!item?.startTime)
|
|
143
|
-
return;
|
|
144
|
-
const interval = setInterval(() => {
|
|
145
|
-
setCurrentElapsed((prev) => {
|
|
146
|
-
const next = Date.now() - item.startTime;
|
|
147
|
-
return next !== prev ? next : prev;
|
|
148
|
-
});
|
|
149
|
-
}, 1000);
|
|
150
|
-
return () => clearInterval(interval);
|
|
151
|
-
}, [runningIndex, commandStatuses]);
|
|
152
|
-
// Handle completion callback when execution finishes
|
|
153
|
-
useEffect(() => {
|
|
154
|
-
if (isExecuting || commandStatuses.length === 0 || !outputs.length)
|
|
155
|
-
return;
|
|
156
|
-
// Save state before completing
|
|
157
|
-
handlers?.updateState({
|
|
158
|
-
message,
|
|
159
|
-
commandStatuses,
|
|
160
|
-
error,
|
|
161
|
-
});
|
|
162
|
-
handlers?.completeActive();
|
|
163
|
-
}, [isExecuting, commandStatuses, outputs, handlers, message, error]);
|
|
164
|
-
useEffect(() => {
|
|
165
|
-
if (!isActive) {
|
|
69
|
+
if (!isActive || taskInfos.length > 0 || hasProcessed) {
|
|
166
70
|
return;
|
|
167
71
|
}
|
|
168
72
|
if (!service) {
|
|
@@ -178,7 +82,6 @@ export function Execute({ tasks, state, status, service, handlers, }) {
|
|
|
178
82
|
// Format tasks for the execute tool and resolve placeholders
|
|
179
83
|
const taskDescriptions = tasks
|
|
180
84
|
.map((task) => {
|
|
181
|
-
// Resolve placeholders in task action
|
|
182
85
|
const resolvedAction = replacePlaceholders(task.action, userConfig);
|
|
183
86
|
const params = task.params
|
|
184
87
|
? ` (params: ${JSON.stringify(task.params)})`
|
|
@@ -191,79 +94,62 @@ export function Execute({ tasks, state, status, service, handlers, }) {
|
|
|
191
94
|
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
192
95
|
if (!mounted)
|
|
193
96
|
return;
|
|
97
|
+
// Add debug components to timeline if present
|
|
98
|
+
addDebugToTimeline(result.debug, handlers);
|
|
194
99
|
if (!result.commands || result.commands.length === 0) {
|
|
195
|
-
setOutputs([]);
|
|
196
100
|
setHasProcessed(true);
|
|
197
|
-
// Save state before completing
|
|
198
101
|
handlers?.updateState({
|
|
199
102
|
message: result.message,
|
|
200
|
-
|
|
103
|
+
summary: '',
|
|
104
|
+
taskInfos: [],
|
|
105
|
+
completed: 0,
|
|
106
|
+
taskExecutionTimes: [],
|
|
107
|
+
completionMessage: null,
|
|
108
|
+
error: null,
|
|
201
109
|
});
|
|
202
110
|
handlers?.completeActive();
|
|
203
111
|
return;
|
|
204
112
|
}
|
|
205
|
-
// Resolve placeholders in command strings
|
|
113
|
+
// Resolve placeholders in command strings
|
|
206
114
|
const resolvedCommands = result.commands.map((cmd) => ({
|
|
207
115
|
...cmd,
|
|
208
116
|
command: replacePlaceholders(cmd.command, userConfig),
|
|
209
117
|
}));
|
|
210
|
-
// Set message and
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
status: ExecutionStatus.Pending,
|
|
118
|
+
// Set message, summary, and create task infos
|
|
119
|
+
const newMessage = result.message;
|
|
120
|
+
const newSummary = result.summary || '';
|
|
121
|
+
const infos = resolvedCommands.map((cmd, index) => ({
|
|
215
122
|
label: tasks[index]?.action,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
? Math.floor((now - item.startTime) / 1000) * 1000
|
|
232
|
-
: item.elapsed;
|
|
233
|
-
return {
|
|
234
|
-
...item,
|
|
235
|
-
status: progress.status,
|
|
236
|
-
output: progress.output,
|
|
237
|
-
startTime: isStarting ? now : item.startTime,
|
|
238
|
-
endTime,
|
|
239
|
-
elapsed,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
return item;
|
|
243
|
-
}));
|
|
244
|
-
if (progress.status === ExecutionStatus.Running) {
|
|
245
|
-
setRunningIndex((prev) => prev !== progress.currentIndex ? progress.currentIndex : prev);
|
|
246
|
-
setCurrentElapsed((prev) => (prev !== 0 ? 0 : prev));
|
|
247
|
-
}
|
|
248
|
-
else if (progress.status === ExecutionStatus.Success ||
|
|
249
|
-
progress.status === ExecutionStatus.Failed) {
|
|
250
|
-
setRunningIndex((prev) => (prev !== null ? null : prev));
|
|
251
|
-
}
|
|
123
|
+
command: cmd,
|
|
124
|
+
}));
|
|
125
|
+
setMessage(newMessage);
|
|
126
|
+
setSummary(newSummary);
|
|
127
|
+
setTaskInfos(infos);
|
|
128
|
+
setCompleted(0); // Start with first task
|
|
129
|
+
// Update state after AI processing
|
|
130
|
+
handlers?.updateState({
|
|
131
|
+
message: newMessage,
|
|
132
|
+
summary: newSummary,
|
|
133
|
+
taskInfos: infos,
|
|
134
|
+
completed: 0,
|
|
135
|
+
taskExecutionTimes: [],
|
|
136
|
+
completionMessage: null,
|
|
137
|
+
error: null,
|
|
252
138
|
});
|
|
253
|
-
if (mounted) {
|
|
254
|
-
setOutputs(outputs);
|
|
255
|
-
setIsExecuting(false);
|
|
256
|
-
}
|
|
257
139
|
}
|
|
258
140
|
catch (err) {
|
|
259
141
|
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
260
142
|
if (mounted) {
|
|
261
143
|
const errorMessage = formatErrorMessage(err);
|
|
262
|
-
setIsExecuting(false);
|
|
263
144
|
setError(errorMessage);
|
|
264
145
|
setHasProcessed(true);
|
|
265
|
-
// Save error state
|
|
266
146
|
handlers?.updateState({
|
|
147
|
+
message: '',
|
|
148
|
+
summary: '',
|
|
149
|
+
taskInfos: [],
|
|
150
|
+
completed: 0,
|
|
151
|
+
taskExecutionTimes: [],
|
|
152
|
+
completionMessage: null,
|
|
267
153
|
error: errorMessage,
|
|
268
154
|
});
|
|
269
155
|
handlers?.onError(errorMessage);
|
|
@@ -274,12 +160,122 @@ export function Execute({ tasks, state, status, service, handlers, }) {
|
|
|
274
160
|
return () => {
|
|
275
161
|
mounted = false;
|
|
276
162
|
};
|
|
277
|
-
}, [tasks, isActive, service, handlers]);
|
|
163
|
+
}, [tasks, isActive, service, handlers, taskInfos.length, hasProcessed]);
|
|
164
|
+
// Handle task completion - move to next task
|
|
165
|
+
const handleTaskComplete = useCallback((index, _output, elapsed) => {
|
|
166
|
+
const updatedTimes = [...taskExecutionTimes, elapsed];
|
|
167
|
+
setTaskExecutionTimes(updatedTimes);
|
|
168
|
+
// Update task with elapsed time and success status
|
|
169
|
+
const updatedTaskInfos = taskInfos.map((task, i) => i === index
|
|
170
|
+
? { ...task, status: ExecutionStatus.Success, elapsed }
|
|
171
|
+
: task);
|
|
172
|
+
setTaskInfos(updatedTaskInfos);
|
|
173
|
+
if (index < taskInfos.length - 1) {
|
|
174
|
+
// More tasks to execute
|
|
175
|
+
setCompleted(index + 1);
|
|
176
|
+
handlers?.updateState({
|
|
177
|
+
message,
|
|
178
|
+
summary,
|
|
179
|
+
taskInfos: updatedTaskInfos,
|
|
180
|
+
completed: index + 1,
|
|
181
|
+
taskExecutionTimes: updatedTimes,
|
|
182
|
+
completionMessage: null,
|
|
183
|
+
error: null,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// All tasks complete
|
|
188
|
+
const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
|
|
189
|
+
const summaryText = summary?.trim() || 'Execution completed';
|
|
190
|
+
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
191
|
+
setCompletionMessage(completion);
|
|
192
|
+
handlers?.updateState({
|
|
193
|
+
message,
|
|
194
|
+
summary,
|
|
195
|
+
taskInfos: updatedTaskInfos,
|
|
196
|
+
completed: index + 1,
|
|
197
|
+
taskExecutionTimes: updatedTimes,
|
|
198
|
+
completionMessage: completion,
|
|
199
|
+
error: null,
|
|
200
|
+
});
|
|
201
|
+
handlers?.completeActive();
|
|
202
|
+
}
|
|
203
|
+
}, [taskInfos, message, handlers, taskExecutionTimes, summary]);
|
|
204
|
+
const handleTaskError = useCallback((index, error, elapsed) => {
|
|
205
|
+
const task = taskInfos[index];
|
|
206
|
+
const isCritical = task?.command.critical !== false; // Default to true
|
|
207
|
+
// Update task with elapsed time and failed status
|
|
208
|
+
const updatedTaskInfos = taskInfos.map((task, i) => i === index
|
|
209
|
+
? { ...task, status: ExecutionStatus.Failed, elapsed }
|
|
210
|
+
: task);
|
|
211
|
+
setTaskInfos(updatedTaskInfos);
|
|
212
|
+
if (isCritical) {
|
|
213
|
+
// Critical failure - stop execution
|
|
214
|
+
setError(error);
|
|
215
|
+
handlers?.updateState({
|
|
216
|
+
message,
|
|
217
|
+
summary,
|
|
218
|
+
taskInfos: updatedTaskInfos,
|
|
219
|
+
completed: index + 1,
|
|
220
|
+
taskExecutionTimes,
|
|
221
|
+
completionMessage: null,
|
|
222
|
+
error,
|
|
223
|
+
});
|
|
224
|
+
handlers?.onError(error);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
// Non-critical failure - continue to next task
|
|
228
|
+
const updatedTimes = [...taskExecutionTimes, elapsed];
|
|
229
|
+
setTaskExecutionTimes(updatedTimes);
|
|
230
|
+
if (index < taskInfos.length - 1) {
|
|
231
|
+
setCompleted(index + 1);
|
|
232
|
+
handlers?.updateState({
|
|
233
|
+
message,
|
|
234
|
+
summary,
|
|
235
|
+
taskInfos: updatedTaskInfos,
|
|
236
|
+
completed: index + 1,
|
|
237
|
+
taskExecutionTimes: updatedTimes,
|
|
238
|
+
completionMessage: null,
|
|
239
|
+
error: null,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
// Last task, complete execution
|
|
244
|
+
const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
|
|
245
|
+
const summaryText = summary?.trim() || 'Execution completed';
|
|
246
|
+
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
247
|
+
setCompletionMessage(completion);
|
|
248
|
+
handlers?.updateState({
|
|
249
|
+
message,
|
|
250
|
+
summary,
|
|
251
|
+
taskInfos: updatedTaskInfos,
|
|
252
|
+
completed: index + 1,
|
|
253
|
+
taskExecutionTimes: updatedTimes,
|
|
254
|
+
completionMessage: completion,
|
|
255
|
+
error: null,
|
|
256
|
+
});
|
|
257
|
+
handlers?.completeActive();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}, [taskInfos, message, handlers, taskExecutionTimes, summary]);
|
|
261
|
+
const handleTaskAbort = useCallback((_index) => {
|
|
262
|
+
// Task was aborted - execution already stopped by Escape handler
|
|
263
|
+
// Just update state, don't call onAborted (already called at Execute level)
|
|
264
|
+
handlers?.updateState({
|
|
265
|
+
message,
|
|
266
|
+
summary,
|
|
267
|
+
taskInfos,
|
|
268
|
+
completed,
|
|
269
|
+
taskExecutionTimes,
|
|
270
|
+
completionMessage: null,
|
|
271
|
+
error: null,
|
|
272
|
+
});
|
|
273
|
+
}, [taskInfos, message, summary, completed, taskExecutionTimes, handlers]);
|
|
278
274
|
// Return null only when loading completes with no commands
|
|
279
|
-
if (!isActive &&
|
|
275
|
+
if (!isActive && taskInfos.length === 0 && !error) {
|
|
280
276
|
return null;
|
|
281
277
|
}
|
|
282
278
|
// Show completed steps when not active
|
|
283
|
-
const
|
|
284
|
-
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 ||
|
|
279
|
+
const showTasks = !isActive && taskInfos.length > 0;
|
|
280
|
+
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: handleTaskComplete, onAbort: handleTaskAbort, onError: handleTaskError }) }, index)))] })), completionMessage && !isActive && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(false), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
|
|
285
281
|
}
|
package/dist/ui/Feedback.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import { getFeedbackColor } from '../services/colors.js';
|
|
4
3
|
import { FeedbackType } from '../types/types.js';
|
|
4
|
+
import { getFeedbackColor } from '../services/colors.js';
|
|
5
5
|
function getSymbol(type) {
|
|
6
6
|
return {
|
|
7
7
|
[FeedbackType.Info]: 'ℹ',
|
package/dist/ui/Introspect.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
6
|
-
import { createReportDefinition } from '../services/components.js';
|
|
6
|
+
import { addDebugToTimeline, createReportDefinition, } from '../services/components.js';
|
|
7
7
|
import { DebugLevel } from '../services/configuration.js';
|
|
8
8
|
import { useInput } from '../services/keyboard.js';
|
|
9
9
|
import { formatErrorMessage } from '../services/messages.js';
|
|
@@ -81,6 +81,8 @@ export function Introspect({ tasks, state, status, service, children, debug = De
|
|
|
81
81
|
const result = await svc.processWithTool(introspectAction, 'introspect');
|
|
82
82
|
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
83
83
|
if (mounted) {
|
|
84
|
+
// Add debug components to timeline if present
|
|
85
|
+
addDebugToTimeline(result.debug, handlers);
|
|
84
86
|
// Parse capabilities from returned tasks
|
|
85
87
|
let capabilities = result.tasks.map(parseCapabilityFromTask);
|
|
86
88
|
// Filter out internal capabilities when not in debug mode
|
package/dist/ui/Label.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import { DebugLevel } from '../services/configuration.js';
|
|
4
3
|
import { getTaskColors, getTaskTypeLabel } from '../services/colors.js';
|
|
4
|
+
import { DebugLevel } from '../services/configuration.js';
|
|
5
5
|
import { Separator } from './Separator.js';
|
|
6
6
|
export function Label({ description, taskType, showType = false, isCurrent = false, debug = DebugLevel.None, }) {
|
|
7
7
|
const colors = getTaskColors(taskType, isCurrent);
|
package/dist/ui/List.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { Palette } from '../services/colors.js';
|
|
3
4
|
import { Separator } from './Separator.js';
|
|
4
5
|
export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, showType = false, }) => {
|
|
5
6
|
const marginLeft = level > 0 ? 2 : 0;
|
|
@@ -21,7 +22,7 @@ export const List = ({ items, level = 0, highlightedIndex = null, highlightedPar
|
|
|
21
22
|
const markerColor = item.markerColor ||
|
|
22
23
|
(isHighlighted && item.type.highlightedColor
|
|
23
24
|
? item.type.highlightedColor
|
|
24
|
-
:
|
|
25
|
+
: Palette.White);
|
|
25
26
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: markerColor, children: marker }), _jsx(Text, { color: descriptionColor, children: item.description.text }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: typeColor, children: item.type.text })] }))] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null, showType: showType }))] }, index));
|
|
26
27
|
}) }));
|
|
27
28
|
};
|
package/dist/ui/Schedule.js
CHANGED
|
@@ -3,8 +3,8 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { Box } from 'ink';
|
|
4
4
|
import { ComponentStatus } from '../types/components.js';
|
|
5
5
|
import { TaskType } from '../types/types.js';
|
|
6
|
-
import { DebugLevel } from '../services/configuration.js';
|
|
7
6
|
import { getTaskColors, getTaskTypeLabel } from '../services/colors.js';
|
|
7
|
+
import { DebugLevel } from '../services/configuration.js';
|
|
8
8
|
import { useInput } from '../services/keyboard.js';
|
|
9
9
|
import { Label } from './Label.js';
|
|
10
10
|
import { List } from './List.js';
|
package/dist/ui/Spinner.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Text } from 'ink';
|
|
4
|
+
import { Palette } from '../services/colors.js';
|
|
4
5
|
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
5
6
|
const INTERVAL = 80;
|
|
6
7
|
const CYCLE = FRAMES.length * INTERVAL;
|
|
@@ -18,5 +19,5 @@ export function Spinner() {
|
|
|
18
19
|
}, INTERVAL);
|
|
19
20
|
return () => clearInterval(timer);
|
|
20
21
|
}, []);
|
|
21
|
-
return _jsx(Text, { color:
|
|
22
|
+
return _jsx(Text, { color: Palette.Cyan, children: FRAMES[frame] });
|
|
22
23
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { getStatusColors, Palette, STATUS_ICONS } from '../services/colors.js';
|
|
4
|
+
import { ExecutionStatus } from '../services/shell.js';
|
|
5
|
+
import { formatDuration } from '../services/utils.js';
|
|
6
|
+
import { Spinner } from './Spinner.js';
|
|
7
|
+
export function Subtask({ label, command, status, isActive, startTime, endTime, elapsed, }) {
|
|
8
|
+
const colors = getStatusColors(status);
|
|
9
|
+
const isCancelled = status === ExecutionStatus.Cancelled;
|
|
10
|
+
const isAborted = status === ExecutionStatus.Aborted;
|
|
11
|
+
const shouldStrikethrough = isCancelled || isAborted;
|
|
12
|
+
const isFinished = status === ExecutionStatus.Success ||
|
|
13
|
+
status === ExecutionStatus.Failed ||
|
|
14
|
+
status === ExecutionStatus.Aborted;
|
|
15
|
+
const elapsedTime = elapsed ?? (startTime && endTime ? endTime - startTime : undefined);
|
|
16
|
+
// Apply strikethrough for cancelled and aborted tasks
|
|
17
|
+
const formatText = (text) => shouldStrikethrough ? text.split('').join('\u0336') + '\u0336' : text;
|
|
18
|
+
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
|
+
? formatText(label || command.description)
|
|
20
|
+
: label || command.description }), (isFinished || status === ExecutionStatus.Running) &&
|
|
21
|
+
elapsedTime !== undefined && (_jsxs(Text, { color: Palette.DarkGray, children: ["(", formatDuration(elapsedTime), ")"] }))] }), _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
|
+
}
|
package/dist/ui/Task.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { ExecutionStatus, executeCommand, } from '../services/shell.js';
|
|
4
|
+
import { calculateElapsed } from '../services/utils.js';
|
|
5
|
+
import { Subtask } from './Subtask.js';
|
|
6
|
+
export function Task({ label, command, isActive, index, initialStatus, initialElapsed, onComplete, onAbort, onError, }) {
|
|
7
|
+
const [status, setStatus] = useState(initialStatus ?? ExecutionStatus.Pending);
|
|
8
|
+
const [startTime, setStartTime] = useState();
|
|
9
|
+
const [endTime, setEndTime] = useState();
|
|
10
|
+
const [elapsed, setElapsed] = useState(initialElapsed);
|
|
11
|
+
const [currentElapsed, setCurrentElapsed] = useState(0);
|
|
12
|
+
// Update elapsed time while running
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (status !== ExecutionStatus.Running || !startTime)
|
|
15
|
+
return;
|
|
16
|
+
const interval = setInterval(() => {
|
|
17
|
+
setCurrentElapsed((prev) => {
|
|
18
|
+
const next = Date.now() - startTime;
|
|
19
|
+
return next !== prev ? next : prev;
|
|
20
|
+
});
|
|
21
|
+
}, 1000);
|
|
22
|
+
return () => clearInterval(interval);
|
|
23
|
+
}, [status, startTime]);
|
|
24
|
+
// Execute command when becoming active
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
// Don't execute if task is cancelled or if not active
|
|
27
|
+
if (!isActive ||
|
|
28
|
+
status === ExecutionStatus.Cancelled ||
|
|
29
|
+
status !== ExecutionStatus.Pending) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let mounted = true;
|
|
33
|
+
async function execute() {
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
setStatus(ExecutionStatus.Running);
|
|
36
|
+
setStartTime(start);
|
|
37
|
+
setCurrentElapsed(0);
|
|
38
|
+
try {
|
|
39
|
+
const output = await executeCommand(command, undefined, index);
|
|
40
|
+
if (!mounted)
|
|
41
|
+
return;
|
|
42
|
+
const end = Date.now();
|
|
43
|
+
setEndTime(end);
|
|
44
|
+
const taskDuration = calculateElapsed(start);
|
|
45
|
+
setElapsed(taskDuration);
|
|
46
|
+
setStatus(output.result === 'success'
|
|
47
|
+
? ExecutionStatus.Success
|
|
48
|
+
: ExecutionStatus.Failed);
|
|
49
|
+
if (output.result === 'success') {
|
|
50
|
+
onComplete?.(index, output, taskDuration);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
onError?.(index, output.errors || 'Command failed', taskDuration);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (!mounted)
|
|
58
|
+
return;
|
|
59
|
+
const end = Date.now();
|
|
60
|
+
setEndTime(end);
|
|
61
|
+
const errorDuration = calculateElapsed(start);
|
|
62
|
+
setElapsed(errorDuration);
|
|
63
|
+
setStatus(ExecutionStatus.Failed);
|
|
64
|
+
onError?.(index, err instanceof Error ? err.message : 'Unknown error', errorDuration);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
execute();
|
|
68
|
+
return () => {
|
|
69
|
+
mounted = false;
|
|
70
|
+
};
|
|
71
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
|
+
}, [isActive]);
|
|
73
|
+
// Handle abort when task becomes inactive while running
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!isActive && status === ExecutionStatus.Running && startTime) {
|
|
76
|
+
// Task was aborted mid-execution
|
|
77
|
+
const end = Date.now();
|
|
78
|
+
setEndTime(end);
|
|
79
|
+
setElapsed(calculateElapsed(startTime));
|
|
80
|
+
setStatus(ExecutionStatus.Aborted);
|
|
81
|
+
onAbort?.(index);
|
|
82
|
+
}
|
|
83
|
+
}, [isActive, status, startTime, index, onAbort]);
|
|
84
|
+
return (_jsx(Subtask, { label: label, command: command, status: status, isActive: isActive, startTime: startTime, endTime: endTime, elapsed: status === ExecutionStatus.Running ? currentElapsed : elapsed }));
|
|
85
|
+
}
|