prompt-language-shell 0.7.8 → 0.8.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.
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, 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 } from '../services/colors.js';
6
6
  import { addDebugToTimeline } from '../services/components.js';
7
7
  import { useInput } from '../services/keyboard.js';
8
+ import { loadUserConfig } from '../services/loader.js';
8
9
  import { formatErrorMessage } from '../services/messages.js';
9
10
  import { replacePlaceholders } from '../services/resolver.js';
10
- import { loadUserConfig } from '../services/loader.js';
11
+ import { ExecutionStatus } from '../services/shell.js';
11
12
  import { ensureMinimumTime } from '../services/timing.js';
12
13
  import { formatDuration } from '../services/utils.js';
13
14
  import { Spinner } from './Spinner.js';
@@ -18,19 +19,49 @@ export function Execute({ tasks, state, status, service, handlers, }) {
18
19
  const [error, setError] = useState(state?.error ?? null);
19
20
  const [taskInfos, setTaskInfos] = useState(state?.taskInfos ?? []);
20
21
  const [message, setMessage] = useState(state?.message ?? '');
21
- const [activeTaskIndex, setActiveTaskIndex] = useState(state?.activeTaskIndex ?? -1);
22
+ const [completed, setCompleted] = useState(state?.completed ?? 0);
22
23
  const [hasProcessed, setHasProcessed] = useState(false);
23
24
  const [taskExecutionTimes, setTaskExecutionTimes] = useState(state?.taskExecutionTimes ?? []);
24
25
  const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
25
26
  const [summary, setSummary] = useState(state?.summary ?? '');
26
27
  // Derive loading state from current conditions
27
28
  const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
28
- const isExecuting = activeTaskIndex >= 0 && activeTaskIndex < taskInfos.length;
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]);
29
62
  useInput((_, key) => {
30
63
  if (key.escape && (isLoading || isExecuting) && isActive) {
31
- // Cancel execution
32
- setActiveTaskIndex(-1);
33
- handlers?.onAborted('execution');
64
+ handleCancel();
34
65
  }
35
66
  }, { isActive: (isLoading || isExecuting) && isActive });
36
67
  // Process tasks to get commands from AI
@@ -38,10 +69,6 @@ export function Execute({ tasks, state, status, service, handlers, }) {
38
69
  if (!isActive || taskInfos.length > 0 || hasProcessed) {
39
70
  return;
40
71
  }
41
- if (!service) {
42
- setError('No service available');
43
- return;
44
- }
45
72
  let mounted = true;
46
73
  async function process(svc) {
47
74
  const startTime = Date.now();
@@ -69,7 +96,12 @@ export function Execute({ tasks, state, status, service, handlers, }) {
69
96
  setHasProcessed(true);
70
97
  handlers?.updateState({
71
98
  message: result.message,
99
+ summary: '',
72
100
  taskInfos: [],
101
+ completed: 0,
102
+ taskExecutionTimes: [],
103
+ completionMessage: null,
104
+ error: null,
73
105
  });
74
106
  handlers?.completeActive();
75
107
  return;
@@ -80,14 +112,26 @@ export function Execute({ tasks, state, status, service, handlers, }) {
80
112
  command: replacePlaceholders(cmd.command, userConfig),
81
113
  }));
82
114
  // Set message, summary, and create task infos
83
- setMessage(result.message);
84
- setSummary(result.summary || '');
115
+ const newMessage = result.message;
116
+ const newSummary = result.summary || '';
85
117
  const infos = resolvedCommands.map((cmd, index) => ({
86
118
  label: tasks[index]?.action,
87
119
  command: cmd,
88
120
  }));
121
+ setMessage(newMessage);
122
+ setSummary(newSummary);
89
123
  setTaskInfos(infos);
90
- setActiveTaskIndex(0); // Start with first task
124
+ setCompleted(0); // Start with first task
125
+ // Update state after AI processing
126
+ handlers?.updateState({
127
+ message: newMessage,
128
+ summary: newSummary,
129
+ taskInfos: infos,
130
+ completed: 0,
131
+ taskExecutionTimes: [],
132
+ completionMessage: null,
133
+ error: null,
134
+ });
91
135
  }
92
136
  catch (err) {
93
137
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
@@ -95,12 +139,20 @@ export function Execute({ tasks, state, status, service, handlers, }) {
95
139
  const errorMessage = formatErrorMessage(err);
96
140
  setError(errorMessage);
97
141
  setHasProcessed(true);
98
- handlers?.updateState({ error: errorMessage });
142
+ handlers?.updateState({
143
+ message: '',
144
+ summary: '',
145
+ taskInfos: [],
146
+ completed: 0,
147
+ taskExecutionTimes: [],
148
+ completionMessage: null,
149
+ error: errorMessage,
150
+ });
99
151
  handlers?.onError(errorMessage);
100
152
  }
101
153
  }
102
154
  }
103
- process(service);
155
+ void process(service);
104
156
  return () => {
105
157
  mounted = false;
106
158
  };
@@ -109,62 +161,94 @@ export function Execute({ tasks, state, status, service, handlers, }) {
109
161
  const handleTaskComplete = useCallback((index, _output, elapsed) => {
110
162
  const updatedTimes = [...taskExecutionTimes, elapsed];
111
163
  setTaskExecutionTimes(updatedTimes);
164
+ // Update task with elapsed time and success status
165
+ const updatedTaskInfos = taskInfos.map((task, i) => i === index
166
+ ? { ...task, status: ExecutionStatus.Success, elapsed }
167
+ : task);
168
+ setTaskInfos(updatedTaskInfos);
112
169
  if (index < taskInfos.length - 1) {
113
170
  // More tasks to execute
114
- setActiveTaskIndex(index + 1);
171
+ setCompleted(index + 1);
172
+ handlers?.updateState({
173
+ message,
174
+ summary,
175
+ taskInfos: updatedTaskInfos,
176
+ completed: index + 1,
177
+ taskExecutionTimes: updatedTimes,
178
+ completionMessage: null,
179
+ error: null,
180
+ });
115
181
  }
116
182
  else {
117
183
  // All tasks complete
118
- setActiveTaskIndex(-1);
119
184
  const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
120
- const summaryText = summary?.trim() || 'Execution completed';
185
+ const summaryText = summary.trim() || 'Execution completed';
121
186
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
122
187
  setCompletionMessage(completion);
123
188
  handlers?.updateState({
124
189
  message,
125
190
  summary,
126
- taskInfos,
127
- activeTaskIndex: -1,
191
+ taskInfos: updatedTaskInfos,
192
+ completed: index + 1,
128
193
  taskExecutionTimes: updatedTimes,
129
194
  completionMessage: completion,
195
+ error: null,
130
196
  });
131
197
  handlers?.completeActive();
132
198
  }
133
199
  }, [taskInfos, message, handlers, taskExecutionTimes, summary]);
134
- const handleTaskError = useCallback((index, error) => {
200
+ const handleTaskError = useCallback((index, error, elapsed) => {
135
201
  const task = taskInfos[index];
136
- const isCritical = task?.command.critical !== false; // Default to true
202
+ const isCritical = task.command.critical !== false; // Default to true
203
+ // Update task with elapsed time and failed status
204
+ const updatedTaskInfos = taskInfos.map((task, i) => i === index
205
+ ? { ...task, status: ExecutionStatus.Failed, elapsed }
206
+ : task);
207
+ setTaskInfos(updatedTaskInfos);
137
208
  if (isCritical) {
138
209
  // Critical failure - stop execution
139
- setActiveTaskIndex(-1);
140
210
  setError(error);
141
211
  handlers?.updateState({
142
212
  message,
143
- taskInfos,
144
- activeTaskIndex: -1,
213
+ summary,
214
+ taskInfos: updatedTaskInfos,
215
+ completed: index + 1,
216
+ taskExecutionTimes,
217
+ completionMessage: null,
145
218
  error,
146
219
  });
147
220
  handlers?.onError(error);
148
221
  }
149
222
  else {
150
223
  // Non-critical failure - continue to next task
224
+ const updatedTimes = [...taskExecutionTimes, elapsed];
225
+ setTaskExecutionTimes(updatedTimes);
151
226
  if (index < taskInfos.length - 1) {
152
- setActiveTaskIndex(index + 1);
227
+ setCompleted(index + 1);
228
+ handlers?.updateState({
229
+ message,
230
+ summary,
231
+ taskInfos: updatedTaskInfos,
232
+ completed: index + 1,
233
+ taskExecutionTimes: updatedTimes,
234
+ completionMessage: null,
235
+ error: null,
236
+ });
153
237
  }
154
238
  else {
155
239
  // Last task, complete execution
156
- setActiveTaskIndex(-1);
157
- const totalElapsed = taskExecutionTimes.reduce((sum, time) => sum + time, 0);
158
- const summaryText = summary?.trim() || 'Execution completed';
240
+ const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
241
+ const summaryText = summary.trim() || 'Execution completed';
159
242
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
160
243
  setCompletionMessage(completion);
161
244
  handlers?.updateState({
162
245
  message,
163
246
  summary,
164
- taskInfos,
165
- activeTaskIndex: -1,
166
- taskExecutionTimes,
247
+ taskInfos: updatedTaskInfos,
248
+ completed: index + 1,
249
+ taskExecutionTimes: updatedTimes,
167
250
  completionMessage: completion,
251
+ error: null,
168
252
  });
169
253
  handlers?.completeActive();
170
254
  }
@@ -175,15 +259,19 @@ export function Execute({ tasks, state, status, service, handlers, }) {
175
259
  // Just update state, don't call onAborted (already called at Execute level)
176
260
  handlers?.updateState({
177
261
  message,
262
+ summary,
178
263
  taskInfos,
179
- activeTaskIndex: -1,
264
+ completed,
265
+ taskExecutionTimes,
266
+ completionMessage: null,
267
+ error: null,
180
268
  });
181
- }, [taskInfos, message, handlers]);
269
+ }, [taskInfos, message, summary, completed, taskExecutionTimes, handlers]);
182
270
  // Return null only when loading completes with no commands
183
271
  if (!isActive && taskInfos.length === 0 && !error) {
184
272
  return null;
185
273
  }
186
274
  // Show completed steps when not active
187
275
  const showTasks = !isActive && taskInfos.length > 0;
188
- 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 === activeTaskIndex, index: index, 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] }) }))] }));
276
+ 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] }) }))] }));
189
277
  }
@@ -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]: 'ℹ',
@@ -1,7 +1,7 @@
1
1
  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
- import { ComponentStatus, } from '../types/components.js';
4
+ import { ComponentStatus } from '../types/components.js';
5
5
  import { Colors, getTextColor } from '../services/colors.js';
6
6
  import { addDebugToTimeline, createReportDefinition, } from '../services/components.js';
7
7
  import { DebugLevel } from '../services/configuration.js';
@@ -10,49 +10,7 @@ import { formatErrorMessage } from '../services/messages.js';
10
10
  import { ensureMinimumTime } from '../services/timing.js';
11
11
  import { Spinner } from './Spinner.js';
12
12
  const MIN_PROCESSING_TIME = 1000;
13
- const BUILT_IN_CAPABILITIES = new Set([
14
- 'CONFIGURE',
15
- 'SCHEDULE',
16
- 'INTROSPECT',
17
- 'ANSWER',
18
- 'EXECUTE',
19
- 'VALIDATE',
20
- 'REPORT',
21
- ]);
22
- const INDIRECT_CAPABILITIES = new Set(['SCHEDULE', 'VALIDATE', 'REPORT']);
23
- function parseCapabilityFromTask(task) {
24
- // Parse "NAME: Description" format from task.action
25
- const colonIndex = task.action.indexOf(':');
26
- if (colonIndex === -1) {
27
- const upperName = task.action.toUpperCase();
28
- // Check for status markers
29
- const isIncomplete = task.action.includes('(INCOMPLETE)');
30
- const cleanName = task.action.replace(/\s*\(INCOMPLETE\)\s*/gi, '').trim();
31
- return {
32
- name: cleanName,
33
- description: '',
34
- isBuiltIn: BUILT_IN_CAPABILITIES.has(upperName),
35
- isIndirect: INDIRECT_CAPABILITIES.has(upperName),
36
- isIncomplete,
37
- };
38
- }
39
- const name = task.action.substring(0, colonIndex).trim();
40
- const description = task.action.substring(colonIndex + 1).trim();
41
- // Check for status markers
42
- const isIncomplete = name.includes('(INCOMPLETE)');
43
- const cleanName = name.replace(/\s*\(INCOMPLETE\)\s*/gi, '').trim();
44
- const upperName = cleanName.toUpperCase();
45
- const isBuiltIn = BUILT_IN_CAPABILITIES.has(upperName);
46
- const isIndirect = INDIRECT_CAPABILITIES.has(upperName);
47
- return {
48
- name: cleanName,
49
- description,
50
- isBuiltIn,
51
- isIndirect,
52
- isIncomplete,
53
- };
54
- }
55
- export function Introspect({ tasks, state, status, service, children, debug = DebugLevel.None, handlers, }) {
13
+ export function Introspect({ tasks, state: _state, status, service, children, debug = DebugLevel.None, handlers, }) {
56
14
  const isActive = status === ComponentStatus.Active;
57
15
  // isActive passed as prop
58
16
  const [error, setError] = useState(null);
@@ -66,11 +24,6 @@ export function Introspect({ tasks, state, status, service, children, debug = De
66
24
  if (!isActive) {
67
25
  return;
68
26
  }
69
- // Skip processing if no service available
70
- if (!service) {
71
- setError('No service available');
72
- return;
73
- }
74
27
  let mounted = true;
75
28
  async function process(svc) {
76
29
  const startTime = Date.now();
@@ -83,8 +36,8 @@ export function Introspect({ tasks, state, status, service, children, debug = De
83
36
  if (mounted) {
84
37
  // Add debug components to timeline if present
85
38
  addDebugToTimeline(result.debug, handlers);
86
- // Parse capabilities from returned tasks
87
- let capabilities = result.tasks.map(parseCapabilityFromTask);
39
+ // Capabilities come directly from result - no parsing needed
40
+ let capabilities = result.capabilities;
88
41
  // Filter out internal capabilities when not in debug mode
89
42
  if (debug === DebugLevel.None) {
90
43
  capabilities = capabilities.filter((cap) => cap.name.toUpperCase() !== 'SCHEDULE' &&
@@ -115,7 +68,7 @@ export function Introspect({ tasks, state, status, service, children, debug = De
115
68
  }
116
69
  }
117
70
  }
118
- process(service);
71
+ void process(service);
119
72
  return () => {
120
73
  mounted = false;
121
74
  };
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
- : 'whiteBright');
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/Main.js CHANGED
@@ -86,7 +86,7 @@ export const Main = ({ app, command }) => {
86
86
  throw new Error(errorMessage);
87
87
  }
88
88
  };
89
- const handleConfigAborted = (operation) => {
89
+ const handleConfigAborted = (_operation) => {
90
90
  // Config was cancelled
91
91
  };
92
92
  setInitialQueue([
package/dist/ui/Report.js CHANGED
@@ -1,14 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- import { Colors } from '../services/colors.js';
4
- function CapabilityItem({ name, description, isBuiltIn, isIndirect, isIncomplete, }) {
5
- const color = isIndirect
6
- ? Colors.Origin.Indirect
7
- : isBuiltIn
8
- ? Colors.Origin.BuiltIn
9
- : Colors.Origin.UserProvided;
3
+ import { Colors, getOriginColor } from '../services/colors.js';
4
+ function CapabilityItem({ name, description, origin, isIncomplete, }) {
5
+ const color = getOriginColor(origin);
10
6
  return (_jsxs(Box, { children: [_jsx(Text, { children: "- " }), _jsx(Text, { color: color, children: name }), _jsxs(Text, { children: [" - ", description] }), isIncomplete && _jsx(Text, { color: Colors.Status.Warning, children: " (incomplete)" })] }));
11
7
  }
12
8
  export function Report({ message, capabilities }) {
13
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 1, children: _jsx(Text, { children: message }) }), _jsx(Box, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: capabilities.map((capability, index) => (_jsx(CapabilityItem, { name: capability.name, description: capability.description, isBuiltIn: capability.isBuiltIn, isIndirect: capability.isIndirect, isIncomplete: capability.isIncomplete }, index))) })] }));
9
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 1, children: _jsx(Text, { children: message }) }), _jsx(Box, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: capabilities.map((capability, index) => (_jsx(CapabilityItem, { name: capability.name, description: capability.description, origin: capability.origin, isIncomplete: capability.isIncomplete }, index))) })] }));
14
10
  }
@@ -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';
@@ -37,7 +37,7 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
37
37
  const planColors = getTaskColors(TaskType.Schedule, isCurrent);
38
38
  return {
39
39
  description: {
40
- text: String(option),
40
+ text: option,
41
41
  color: colors.description,
42
42
  highlightedColor: planColors.description,
43
43
  },
@@ -96,7 +96,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
96
96
  // Complete the selection phase - it goes to timeline
97
97
  // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
98
98
  handlers?.completeActive();
99
- onSelectionConfirmed(concreteTasks);
99
+ void onSelectionConfirmed(concreteTasks);
100
100
  }
101
101
  }, [
102
102
  isActive,
@@ -153,7 +153,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
153
153
  // This is a Define task - only include the selected option
154
154
  const options = task.params.options;
155
155
  const selectedIndex = newCompletedSelections[defineGroupIndex];
156
- const selectedOption = String(options[selectedIndex]);
156
+ const selectedOption = options[selectedIndex];
157
157
  // Use Execute as default - LLM will properly classify during refinement
158
158
  refinedTasks.push({
159
159
  action: selectedOption,
@@ -171,7 +171,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
171
171
  // Complete the selection phase - it goes to timeline
172
172
  // Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
173
173
  handlers?.completeActive();
174
- onSelectionConfirmed(refinedTasks);
174
+ void onSelectionConfirmed(refinedTasks);
175
175
  }
176
176
  else {
177
177
  // No selection callback, just complete normally
@@ -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;
@@ -16,7 +17,9 @@ export function Spinner() {
16
17
  return next !== prev ? next : prev;
17
18
  });
18
19
  }, INTERVAL);
19
- return () => clearInterval(timer);
20
+ return () => {
21
+ clearInterval(timer);
22
+ };
20
23
  }, []);
21
- return _jsx(Text, { color: "blueBright", children: FRAMES[frame] });
24
+ return _jsx(Text, { color: Palette.Cyan, children: FRAMES[frame] });
22
25
  }
@@ -1,11 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { getStatusColors, Palette, STATUS_ICONS } from '../services/colors.js';
4
- import { formatDuration } from '../services/utils.js';
5
4
  import { ExecutionStatus } from '../services/shell.js';
5
+ import { formatDuration } from '../services/utils.js';
6
6
  import { Spinner } from './Spinner.js';
7
- export function Subtask({ label, command, status, startTime, endTime, elapsed, }) {
7
+ export function Subtask({ label, command, status, isActive: _isActive, startTime, endTime, elapsed, }) {
8
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;
9
15
  const elapsedTime = elapsed ?? (startTime && endTime ? endTime - startTime : undefined);
10
- 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: label || command.description }), elapsedTime !== undefined && (_jsxs(Text, { color: Palette.DarkGray, children: ["(", formatDuration(elapsedTime), ")"] }))] }), _jsxs(Box, { paddingLeft: 5, gap: 1, children: [_jsx(Text, { color: colors.symbol, children: "\u221F" }), _jsx(Text, { color: colors.command, children: command.command }), status === ExecutionStatus.Running && _jsx(Spinner, {})] })] }));
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, {})] })] })] }));
11
22
  }
package/dist/ui/Task.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { ExecutionStatus, executeCommand, } from '../services/shell.js';
3
+ import { ExecutionResult, ExecutionStatus, executeCommand, } from '../services/shell.js';
4
4
  import { calculateElapsed } from '../services/utils.js';
5
5
  import { Subtask } from './Subtask.js';
6
- export function Task({ label, command, isActive, index, onComplete, onAbort, onError, }) {
7
- const [status, setStatus] = useState(ExecutionStatus.Pending);
6
+ export function Task({ label, command, isActive, index, initialStatus, initialElapsed, onComplete, onAbort, onError, }) {
7
+ const [status, setStatus] = useState(initialStatus ?? ExecutionStatus.Pending);
8
8
  const [startTime, setStartTime] = useState();
9
9
  const [endTime, setEndTime] = useState();
10
- const [elapsed, setElapsed] = useState();
10
+ const [elapsed, setElapsed] = useState(initialElapsed);
11
11
  const [currentElapsed, setCurrentElapsed] = useState(0);
12
12
  // Update elapsed time while running
13
13
  useEffect(() => {
@@ -19,11 +19,16 @@ export function Task({ label, command, isActive, index, onComplete, onAbort, onE
19
19
  return next !== prev ? next : prev;
20
20
  });
21
21
  }, 1000);
22
- return () => clearInterval(interval);
22
+ return () => {
23
+ clearInterval(interval);
24
+ };
23
25
  }, [status, startTime]);
24
26
  // Execute command when becoming active
25
27
  useEffect(() => {
26
- if (!isActive || status !== ExecutionStatus.Pending) {
28
+ // Don't execute if task is cancelled or if not active
29
+ if (!isActive ||
30
+ status === ExecutionStatus.Cancelled ||
31
+ status !== ExecutionStatus.Pending) {
27
32
  return;
28
33
  }
29
34
  let mounted = true;
@@ -40,14 +45,14 @@ export function Task({ label, command, isActive, index, onComplete, onAbort, onE
40
45
  setEndTime(end);
41
46
  const taskDuration = calculateElapsed(start);
42
47
  setElapsed(taskDuration);
43
- setStatus(output.result === 'success'
48
+ setStatus(output.result === ExecutionResult.Success
44
49
  ? ExecutionStatus.Success
45
50
  : ExecutionStatus.Failed);
46
- if (output.result === 'success') {
51
+ if (output.result === ExecutionResult.Success) {
47
52
  onComplete?.(index, output, taskDuration);
48
53
  }
49
54
  else {
50
- onError?.(index, output.errors || 'Command failed');
55
+ onError?.(index, output.errors || 'Command failed', taskDuration);
51
56
  }
52
57
  }
53
58
  catch (err) {
@@ -55,16 +60,16 @@ export function Task({ label, command, isActive, index, onComplete, onAbort, onE
55
60
  return;
56
61
  const end = Date.now();
57
62
  setEndTime(end);
58
- setElapsed(calculateElapsed(start));
63
+ const errorDuration = calculateElapsed(start);
64
+ setElapsed(errorDuration);
59
65
  setStatus(ExecutionStatus.Failed);
60
- onError?.(index, err instanceof Error ? err.message : 'Unknown error');
66
+ onError?.(index, err instanceof Error ? err.message : 'Unknown error', errorDuration);
61
67
  }
62
68
  }
63
- execute();
69
+ void execute();
64
70
  return () => {
65
71
  mounted = false;
66
72
  };
67
- // eslint-disable-next-line react-hooks/exhaustive-deps
68
73
  }, [isActive]);
69
74
  // Handle abort when task becomes inactive while running
70
75
  useEffect(() => {
@@ -77,5 +82,5 @@ export function Task({ label, command, isActive, index, onComplete, onAbort, onE
77
82
  onAbort?.(index);
78
83
  }
79
84
  }, [isActive, status, startTime, index, onAbort]);
80
- return (_jsx(Subtask, { label: label, command: command, status: status, startTime: startTime, endTime: endTime, elapsed: status === ExecutionStatus.Running ? currentElapsed : elapsed }));
85
+ return (_jsx(Subtask, { label: label, command: command, status: status, isActive: isActive, startTime: startTime, endTime: endTime, elapsed: status === ExecutionStatus.Running ? currentElapsed : elapsed }));
81
86
  }