prompt-language-shell 0.7.6 → 0.7.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/services/anthropic.js +1 -0
- package/dist/services/colors.js +59 -0
- package/dist/services/components.js +8 -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 +3 -0
- package/dist/ui/Command.js +3 -5
- package/dist/ui/Execute.js +106 -202
- package/dist/ui/Introspect.js +3 -1
- package/dist/ui/Subtask.js +11 -0
- package/dist/ui/Task.js +81 -0
- package/dist/ui/Validate.js +3 -0
- package/package.json +1 -1
package/dist/services/colors.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FeedbackType, TaskType } from '../types/types.js';
|
|
2
2
|
import { DebugLevel } from './configuration.js';
|
|
3
|
+
import { ExecutionStatus } from './shell.js';
|
|
3
4
|
/**
|
|
4
5
|
* Base color palette - raw color values with descriptive names.
|
|
5
6
|
* All colors used in the interface are defined here.
|
|
@@ -210,3 +211,61 @@ export function getTaskTypeLabel(type, debug) {
|
|
|
210
211
|
}
|
|
211
212
|
return type;
|
|
212
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Status icons for execution states
|
|
216
|
+
*/
|
|
217
|
+
export const STATUS_ICONS = {
|
|
218
|
+
[ExecutionStatus.Pending]: '- ',
|
|
219
|
+
[ExecutionStatus.Running]: '• ',
|
|
220
|
+
[ExecutionStatus.Success]: '✓ ',
|
|
221
|
+
[ExecutionStatus.Failed]: '✗ ',
|
|
222
|
+
[ExecutionStatus.Aborted]: '⊘ ',
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Get colors for different execution status states.
|
|
226
|
+
*
|
|
227
|
+
* Returns color scheme for:
|
|
228
|
+
* - Icon: Status indicator symbol
|
|
229
|
+
* - Description: Task description text
|
|
230
|
+
* - Command: Command text
|
|
231
|
+
* - Symbol: Command prefix symbol
|
|
232
|
+
*/
|
|
233
|
+
export function getStatusColors(status) {
|
|
234
|
+
switch (status) {
|
|
235
|
+
case ExecutionStatus.Pending:
|
|
236
|
+
return {
|
|
237
|
+
icon: Palette.Gray,
|
|
238
|
+
description: Palette.Gray,
|
|
239
|
+
command: Palette.DarkGray,
|
|
240
|
+
symbol: Palette.DarkGray,
|
|
241
|
+
};
|
|
242
|
+
case ExecutionStatus.Running:
|
|
243
|
+
return {
|
|
244
|
+
icon: Palette.Gray,
|
|
245
|
+
description: getTextColor(true),
|
|
246
|
+
command: Palette.LightGreen,
|
|
247
|
+
symbol: Palette.AshGray,
|
|
248
|
+
};
|
|
249
|
+
case ExecutionStatus.Success:
|
|
250
|
+
return {
|
|
251
|
+
icon: Colors.Status.Success,
|
|
252
|
+
description: getTextColor(true),
|
|
253
|
+
command: Palette.Gray,
|
|
254
|
+
symbol: Palette.Gray,
|
|
255
|
+
};
|
|
256
|
+
case ExecutionStatus.Failed:
|
|
257
|
+
return {
|
|
258
|
+
icon: Colors.Status.Error,
|
|
259
|
+
description: Colors.Status.Error,
|
|
260
|
+
command: Colors.Status.Error,
|
|
261
|
+
symbol: Palette.Gray,
|
|
262
|
+
};
|
|
263
|
+
case ExecutionStatus.Aborted:
|
|
264
|
+
return {
|
|
265
|
+
icon: Palette.DarkOrange,
|
|
266
|
+
description: getTextColor(true),
|
|
267
|
+
command: Palette.DarkOrange,
|
|
268
|
+
symbol: Palette.Gray,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -361,3 +361,11 @@ export function createValidateDefinition(missingConfig, userRequest, service, on
|
|
|
361
361
|
},
|
|
362
362
|
};
|
|
363
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Add debug components to timeline if present in result
|
|
366
|
+
*/
|
|
367
|
+
export function addDebugToTimeline(debugComponents, handlers) {
|
|
368
|
+
if (debugComponents && debugComponents.length > 0 && handlers) {
|
|
369
|
+
handlers.addToTimeline(...debugComponents);
|
|
370
|
+
}
|
|
371
|
+
}
|
package/dist/services/utils.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates elapsed time from a start timestamp, rounded to seconds.
|
|
3
|
+
*/
|
|
4
|
+
export function calculateElapsed(start) {
|
|
5
|
+
return Math.floor((Date.now() - start) / 1000) * 1000;
|
|
6
|
+
}
|
|
1
7
|
/**
|
|
2
8
|
* Formats a duration in milliseconds to a human-readable string.
|
|
3
9
|
* Uses correct singular/plural forms.
|
package/dist/skills/execute.md
CHANGED
|
@@ -88,7 +88,12 @@ these commands specifically for their environment and workflow.
|
|
|
88
88
|
Return a structured response with commands to execute:
|
|
89
89
|
|
|
90
90
|
**Response structure:**
|
|
91
|
-
- **message**: Brief status message (max 64 characters,
|
|
91
|
+
- **message**: Brief status message in imperative mood (max 64 characters,
|
|
92
|
+
end with colon)
|
|
93
|
+
- **summary**: Natural language summary as if execution has finished,
|
|
94
|
+
like a concierge would report (max 48 characters, no period, time will
|
|
95
|
+
be appended). Use varied expressions and synonyms, not necessarily the
|
|
96
|
+
same verb as the message. MUST NOT be empty.
|
|
92
97
|
- **commands**: Array of command objects to execute sequentially
|
|
93
98
|
|
|
94
99
|
**Command object structure:**
|
|
@@ -133,7 +138,8 @@ Task: {
|
|
|
133
138
|
|
|
134
139
|
Response:
|
|
135
140
|
```
|
|
136
|
-
message: "
|
|
141
|
+
message: "Create the file:"
|
|
142
|
+
summary: "File created"
|
|
137
143
|
commands:
|
|
138
144
|
- description: "Create test.txt"
|
|
139
145
|
command: "touch test.txt"
|
|
@@ -148,7 +154,8 @@ Task: {
|
|
|
148
154
|
|
|
149
155
|
Response:
|
|
150
156
|
```
|
|
151
|
-
message: "
|
|
157
|
+
message: "List directory contents:"
|
|
158
|
+
summary: "I've listed the directory contents"
|
|
152
159
|
commands:
|
|
153
160
|
- description: "List files with details"
|
|
154
161
|
command: "ls -la"
|
|
@@ -167,7 +174,8 @@ Tasks:
|
|
|
167
174
|
|
|
168
175
|
Response:
|
|
169
176
|
```
|
|
170
|
-
message: "
|
|
177
|
+
message: "Set up the project:"
|
|
178
|
+
summary: "Project ready to go"
|
|
171
179
|
commands:
|
|
172
180
|
- description: "Create project directory"
|
|
173
181
|
command: "mkdir -p my-project"
|
|
@@ -188,7 +196,8 @@ Task: {
|
|
|
188
196
|
|
|
189
197
|
Response:
|
|
190
198
|
```
|
|
191
|
-
message: "
|
|
199
|
+
message: "Install dependencies:"
|
|
200
|
+
summary: "Dependencies installed successfully"
|
|
192
201
|
commands:
|
|
193
202
|
- description: "Install npm packages"
|
|
194
203
|
command: "npm install"
|
|
@@ -224,7 +233,8 @@ The "Process Data" skill's Execution section specifies:
|
|
|
224
233
|
|
|
225
234
|
Response (using skill's Execution commands):
|
|
226
235
|
```
|
|
227
|
-
message: "
|
|
236
|
+
message: "Process sales data:"
|
|
237
|
+
summary: "Sales data transformed and exported"
|
|
228
238
|
commands:
|
|
229
239
|
- description: "Load the sales dataset"
|
|
230
240
|
command: "curl -O https://data.example.com/sales.csv"
|
|
@@ -250,7 +260,8 @@ Task: {
|
|
|
250
260
|
|
|
251
261
|
Response:
|
|
252
262
|
```
|
|
253
|
-
message: "
|
|
263
|
+
message: "Create backup:"
|
|
264
|
+
summary: "Backup complete"
|
|
254
265
|
commands:
|
|
255
266
|
- description: "Copy config directory"
|
|
256
267
|
command: "cp -r ~/.config/app ~/.config/app.backup"
|
|
@@ -265,7 +276,8 @@ Task: {
|
|
|
265
276
|
|
|
266
277
|
Response:
|
|
267
278
|
```
|
|
268
|
-
message: "
|
|
279
|
+
message: "Check disk space:"
|
|
280
|
+
summary: "Disk space verified"
|
|
269
281
|
commands:
|
|
270
282
|
- description: "Show disk usage"
|
|
271
283
|
command: "df -h"
|
package/dist/skills/schedule.md
CHANGED
|
@@ -7,13 +7,17 @@ task structures with high-level tasks and their subtasks.
|
|
|
7
7
|
## Response Format
|
|
8
8
|
|
|
9
9
|
Every response MUST include a brief message (single sentence, max 64
|
|
10
|
-
characters, ending with period) that introduces the schedule.
|
|
10
|
+
characters, ending with period) that introduces the schedule. Use
|
|
11
|
+
either imperative mood or present tense statements, but NEVER use
|
|
12
|
+
present continuous ("-ing" form).
|
|
11
13
|
|
|
12
|
-
**Examples**: "
|
|
13
|
-
"
|
|
14
|
+
**Examples**: "Build the application." / "Here's the schedule." /
|
|
15
|
+
"Deploy to production." / "I've organized the work."
|
|
14
16
|
|
|
15
17
|
**Critical rules**:
|
|
16
18
|
- Message is MANDATORY
|
|
19
|
+
- Use imperative mood OR present tense statements
|
|
20
|
+
- NEVER use present continuous ("-ing" form)
|
|
17
21
|
- NEVER repeat the same message
|
|
18
22
|
- ALWAYS end with period (.)
|
|
19
23
|
- Vary phrasing naturally
|
|
@@ -8,6 +8,10 @@ export const executeTool = {
|
|
|
8
8
|
type: 'string',
|
|
9
9
|
description: 'Brief status message about the execution. Must be a single sentence, maximum 64 characters, ending with a period.',
|
|
10
10
|
},
|
|
11
|
+
summary: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Natural language summary as if execution has finished, like a concierge would report. Shown after execution completes with time appended. Use varied expressions and synonyms, not necessarily the same verb as the message. Must be a single sentence without period, maximum 48 characters. MUST NOT be empty. Example: "Project ready to go" (time will be appended as " in X seconds").',
|
|
14
|
+
},
|
|
11
15
|
commands: {
|
|
12
16
|
type: 'array',
|
|
13
17
|
description: 'Array of commands to execute sequentially',
|
|
@@ -39,6 +43,6 @@ export const executeTool = {
|
|
|
39
43
|
},
|
|
40
44
|
},
|
|
41
45
|
},
|
|
42
|
-
required: ['message', 'commands'],
|
|
46
|
+
required: ['message', 'summary', 'commands'],
|
|
43
47
|
},
|
|
44
48
|
};
|
package/dist/ui/Answer.js
CHANGED
|
@@ -3,6 +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 { addDebugToTimeline } from '../services/components.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
7
8
|
import { formatErrorMessage } from '../services/messages.js';
|
|
8
9
|
import { withMinimumTime } from '../services/timing.js';
|
|
@@ -33,6 +34,8 @@ export function Answer({ question, state, status, service, handlers, }) {
|
|
|
33
34
|
// Call answer tool with minimum processing time for UX polish
|
|
34
35
|
const result = await withMinimumTime(() => svc.processWithTool(question, 'answer'), MINIMUM_PROCESSING_TIME);
|
|
35
36
|
if (mounted) {
|
|
37
|
+
// Add debug components to timeline if present
|
|
38
|
+
addDebugToTimeline(result.debug, handlers);
|
|
36
39
|
// Extract answer from result
|
|
37
40
|
const answerText = result.answer || '';
|
|
38
41
|
setAnswer(answerText);
|
package/dist/ui/Command.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Box, Text } from 'ink';
|
|
|
4
4
|
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { TaskType } from '../types/types.js';
|
|
6
6
|
import { Colors } from '../services/colors.js';
|
|
7
|
-
import { createScheduleDefinition } from '../services/components.js';
|
|
7
|
+
import { addDebugToTimeline, createScheduleDefinition, } from '../services/components.js';
|
|
8
8
|
import { formatErrorMessage } from '../services/messages.js';
|
|
9
9
|
import { useInput } from '../services/keyboard.js';
|
|
10
10
|
import { handleRefinement } from '../services/refinement.js';
|
|
@@ -53,12 +53,10 @@ export function Command({ command, state, status, service, handlers, onAborted,
|
|
|
53
53
|
// Add debug components to timeline if present
|
|
54
54
|
// If we delegated to configure, include both schedule and configure debug
|
|
55
55
|
// If not, only include schedule debug (result.debug is same as scheduleDebug)
|
|
56
|
-
const
|
|
56
|
+
const debugComponents = allConfig
|
|
57
57
|
? [...scheduleDebug, ...(result.debug || [])]
|
|
58
58
|
: scheduleDebug;
|
|
59
|
-
|
|
60
|
-
handlers?.addToTimeline(...allDebug);
|
|
61
|
-
}
|
|
59
|
+
addDebugToTimeline(debugComponents, handlers);
|
|
62
60
|
// Save result to state for timeline display
|
|
63
61
|
handlers?.updateState({
|
|
64
62
|
message: result.message,
|
package/dist/ui/Execute.js
CHANGED
|
@@ -1,168 +1,41 @@
|
|
|
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
4
|
import { ComponentStatus } from '../types/components.js';
|
|
5
|
-
import { Colors, getTextColor
|
|
5
|
+
import { Colors, getTextColor } from '../services/colors.js';
|
|
6
|
+
import { addDebugToTimeline } from '../services/components.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
7
8
|
import { formatErrorMessage } from '../services/messages.js';
|
|
8
|
-
import { formatDuration } from '../services/utils.js';
|
|
9
|
-
import { ExecutionStatus, executeCommands, } from '../services/shell.js';
|
|
10
9
|
import { replacePlaceholders } from '../services/resolver.js';
|
|
11
10
|
import { loadUserConfig } from '../services/loader.js';
|
|
12
11
|
import { ensureMinimumTime } from '../services/timing.js';
|
|
12
|
+
import { formatDuration } from '../services/utils.js';
|
|
13
13
|
import { Spinner } from './Spinner.js';
|
|
14
|
+
import { Task } from './Task.js';
|
|
14
15
|
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
16
|
export function Execute({ tasks, state, status, service, handlers, }) {
|
|
90
17
|
const isActive = status === ComponentStatus.Active;
|
|
91
|
-
// isActive passed as prop
|
|
92
18
|
const [error, setError] = useState(state?.error ?? null);
|
|
93
|
-
const [
|
|
94
|
-
const [commandStatuses, setCommandStatuses] = useState(state?.commandStatuses ?? []);
|
|
19
|
+
const [taskInfos, setTaskInfos] = useState(state?.taskInfos ?? []);
|
|
95
20
|
const [message, setMessage] = useState(state?.message ?? '');
|
|
96
|
-
const [
|
|
97
|
-
const [runningIndex, setRunningIndex] = useState(null);
|
|
98
|
-
const [outputs, setOutputs] = useState([]);
|
|
21
|
+
const [activeTaskIndex, setActiveTaskIndex] = useState(state?.activeTaskIndex ?? -1);
|
|
99
22
|
const [hasProcessed, setHasProcessed] = useState(false);
|
|
23
|
+
const [taskExecutionTimes, setTaskExecutionTimes] = useState(state?.taskExecutionTimes ?? []);
|
|
24
|
+
const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
|
|
25
|
+
const [summary, setSummary] = useState(state?.summary ?? '');
|
|
100
26
|
// Derive loading state from current conditions
|
|
101
|
-
const isLoading = isActive &&
|
|
102
|
-
|
|
103
|
-
!error &&
|
|
104
|
-
!isExecuting &&
|
|
105
|
-
!hasProcessed;
|
|
27
|
+
const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
|
|
28
|
+
const isExecuting = activeTaskIndex >= 0 && activeTaskIndex < taskInfos.length;
|
|
106
29
|
useInput((_, key) => {
|
|
107
30
|
if (key.escape && (isLoading || isExecuting) && isActive) {
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
});
|
|
31
|
+
// Cancel execution
|
|
32
|
+
setActiveTaskIndex(-1);
|
|
134
33
|
handlers?.onAborted('execution');
|
|
135
34
|
}
|
|
136
35
|
}, { isActive: (isLoading || isExecuting) && isActive });
|
|
137
|
-
//
|
|
138
|
-
useEffect(() => {
|
|
139
|
-
if (runningIndex === null)
|
|
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]);
|
|
36
|
+
// Process tasks to get commands from AI
|
|
164
37
|
useEffect(() => {
|
|
165
|
-
if (!isActive) {
|
|
38
|
+
if (!isActive || taskInfos.length > 0 || hasProcessed) {
|
|
166
39
|
return;
|
|
167
40
|
}
|
|
168
41
|
if (!service) {
|
|
@@ -178,7 +51,6 @@ export function Execute({ tasks, state, status, service, handlers, }) {
|
|
|
178
51
|
// Format tasks for the execute tool and resolve placeholders
|
|
179
52
|
const taskDescriptions = tasks
|
|
180
53
|
.map((task) => {
|
|
181
|
-
// Resolve placeholders in task action
|
|
182
54
|
const resolvedAction = replacePlaceholders(task.action, userConfig);
|
|
183
55
|
const params = task.params
|
|
184
56
|
? ` (params: ${JSON.stringify(task.params)})`
|
|
@@ -191,81 +63,39 @@ export function Execute({ tasks, state, status, service, handlers, }) {
|
|
|
191
63
|
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
192
64
|
if (!mounted)
|
|
193
65
|
return;
|
|
66
|
+
// Add debug components to timeline if present
|
|
67
|
+
addDebugToTimeline(result.debug, handlers);
|
|
194
68
|
if (!result.commands || result.commands.length === 0) {
|
|
195
|
-
setOutputs([]);
|
|
196
69
|
setHasProcessed(true);
|
|
197
|
-
// Save state before completing
|
|
198
70
|
handlers?.updateState({
|
|
199
71
|
message: result.message,
|
|
200
|
-
|
|
72
|
+
taskInfos: [],
|
|
201
73
|
});
|
|
202
74
|
handlers?.completeActive();
|
|
203
75
|
return;
|
|
204
76
|
}
|
|
205
|
-
// Resolve placeholders in command strings
|
|
77
|
+
// Resolve placeholders in command strings
|
|
206
78
|
const resolvedCommands = result.commands.map((cmd) => ({
|
|
207
79
|
...cmd,
|
|
208
80
|
command: replacePlaceholders(cmd.command, userConfig),
|
|
209
81
|
}));
|
|
210
|
-
// Set message and
|
|
82
|
+
// Set message, summary, and create task infos
|
|
211
83
|
setMessage(result.message);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
status: ExecutionStatus.Pending,
|
|
84
|
+
setSummary(result.summary || '');
|
|
85
|
+
const infos = resolvedCommands.map((cmd, index) => ({
|
|
215
86
|
label: tasks[index]?.action,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (!mounted)
|
|
221
|
-
return;
|
|
222
|
-
const now = Date.now();
|
|
223
|
-
setCommandStatuses((prev) => prev.map((item, idx) => {
|
|
224
|
-
if (idx === progress.currentIndex) {
|
|
225
|
-
const isStarting = progress.status === ExecutionStatus.Running &&
|
|
226
|
-
!item.startTime;
|
|
227
|
-
const isEnding = progress.status !== ExecutionStatus.Running &&
|
|
228
|
-
progress.status !== ExecutionStatus.Pending;
|
|
229
|
-
const endTime = isEnding ? now : item.endTime;
|
|
230
|
-
const elapsed = isEnding && item.startTime
|
|
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
|
-
}
|
|
252
|
-
});
|
|
253
|
-
if (mounted) {
|
|
254
|
-
setOutputs(outputs);
|
|
255
|
-
setIsExecuting(false);
|
|
256
|
-
}
|
|
87
|
+
command: cmd,
|
|
88
|
+
}));
|
|
89
|
+
setTaskInfos(infos);
|
|
90
|
+
setActiveTaskIndex(0); // Start with first task
|
|
257
91
|
}
|
|
258
92
|
catch (err) {
|
|
259
93
|
await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
|
|
260
94
|
if (mounted) {
|
|
261
95
|
const errorMessage = formatErrorMessage(err);
|
|
262
|
-
setIsExecuting(false);
|
|
263
96
|
setError(errorMessage);
|
|
264
97
|
setHasProcessed(true);
|
|
265
|
-
|
|
266
|
-
handlers?.updateState({
|
|
267
|
-
error: errorMessage,
|
|
268
|
-
});
|
|
98
|
+
handlers?.updateState({ error: errorMessage });
|
|
269
99
|
handlers?.onError(errorMessage);
|
|
270
100
|
}
|
|
271
101
|
}
|
|
@@ -274,12 +104,86 @@ export function Execute({ tasks, state, status, service, handlers, }) {
|
|
|
274
104
|
return () => {
|
|
275
105
|
mounted = false;
|
|
276
106
|
};
|
|
277
|
-
}, [tasks, isActive, service, handlers]);
|
|
107
|
+
}, [tasks, isActive, service, handlers, taskInfos.length, hasProcessed]);
|
|
108
|
+
// Handle task completion - move to next task
|
|
109
|
+
const handleTaskComplete = useCallback((index, _output, elapsed) => {
|
|
110
|
+
const updatedTimes = [...taskExecutionTimes, elapsed];
|
|
111
|
+
setTaskExecutionTimes(updatedTimes);
|
|
112
|
+
if (index < taskInfos.length - 1) {
|
|
113
|
+
// More tasks to execute
|
|
114
|
+
setActiveTaskIndex(index + 1);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// All tasks complete
|
|
118
|
+
setActiveTaskIndex(-1);
|
|
119
|
+
const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
|
|
120
|
+
const summaryText = summary?.trim() || 'Execution completed';
|
|
121
|
+
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
122
|
+
setCompletionMessage(completion);
|
|
123
|
+
handlers?.updateState({
|
|
124
|
+
message,
|
|
125
|
+
summary,
|
|
126
|
+
taskInfos,
|
|
127
|
+
activeTaskIndex: -1,
|
|
128
|
+
taskExecutionTimes: updatedTimes,
|
|
129
|
+
completionMessage: completion,
|
|
130
|
+
});
|
|
131
|
+
handlers?.completeActive();
|
|
132
|
+
}
|
|
133
|
+
}, [taskInfos, message, handlers, taskExecutionTimes, summary]);
|
|
134
|
+
const handleTaskError = useCallback((index, error) => {
|
|
135
|
+
const task = taskInfos[index];
|
|
136
|
+
const isCritical = task?.command.critical !== false; // Default to true
|
|
137
|
+
if (isCritical) {
|
|
138
|
+
// Critical failure - stop execution
|
|
139
|
+
setActiveTaskIndex(-1);
|
|
140
|
+
setError(error);
|
|
141
|
+
handlers?.updateState({
|
|
142
|
+
message,
|
|
143
|
+
taskInfos,
|
|
144
|
+
activeTaskIndex: -1,
|
|
145
|
+
error,
|
|
146
|
+
});
|
|
147
|
+
handlers?.onError(error);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Non-critical failure - continue to next task
|
|
151
|
+
if (index < taskInfos.length - 1) {
|
|
152
|
+
setActiveTaskIndex(index + 1);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Last task, complete execution
|
|
156
|
+
setActiveTaskIndex(-1);
|
|
157
|
+
const totalElapsed = taskExecutionTimes.reduce((sum, time) => sum + time, 0);
|
|
158
|
+
const summaryText = summary?.trim() || 'Execution completed';
|
|
159
|
+
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
160
|
+
setCompletionMessage(completion);
|
|
161
|
+
handlers?.updateState({
|
|
162
|
+
message,
|
|
163
|
+
summary,
|
|
164
|
+
taskInfos,
|
|
165
|
+
activeTaskIndex: -1,
|
|
166
|
+
taskExecutionTimes,
|
|
167
|
+
completionMessage: completion,
|
|
168
|
+
});
|
|
169
|
+
handlers?.completeActive();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}, [taskInfos, message, handlers, taskExecutionTimes, summary]);
|
|
173
|
+
const handleTaskAbort = useCallback((_index) => {
|
|
174
|
+
// Task was aborted - execution already stopped by Escape handler
|
|
175
|
+
// Just update state, don't call onAborted (already called at Execute level)
|
|
176
|
+
handlers?.updateState({
|
|
177
|
+
message,
|
|
178
|
+
taskInfos,
|
|
179
|
+
activeTaskIndex: -1,
|
|
180
|
+
});
|
|
181
|
+
}, [taskInfos, message, handlers]);
|
|
278
182
|
// Return null only when loading completes with no commands
|
|
279
|
-
if (!isActive &&
|
|
183
|
+
if (!isActive && taskInfos.length === 0 && !error) {
|
|
280
184
|
return null;
|
|
281
185
|
}
|
|
282
186
|
// 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 ||
|
|
187
|
+
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] }) }))] }));
|
|
285
189
|
}
|
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
|
|
@@ -0,0 +1,11 @@
|
|
|
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 { formatDuration } from '../services/utils.js';
|
|
5
|
+
import { ExecutionStatus } from '../services/shell.js';
|
|
6
|
+
import { Spinner } from './Spinner.js';
|
|
7
|
+
export function Subtask({ label, command, status, startTime, endTime, elapsed, }) {
|
|
8
|
+
const colors = getStatusColors(status);
|
|
9
|
+
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, {})] })] }));
|
|
11
|
+
}
|
package/dist/ui/Task.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
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, onComplete, onAbort, onError, }) {
|
|
7
|
+
const [status, setStatus] = useState(ExecutionStatus.Pending);
|
|
8
|
+
const [startTime, setStartTime] = useState();
|
|
9
|
+
const [endTime, setEndTime] = useState();
|
|
10
|
+
const [elapsed, setElapsed] = useState();
|
|
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
|
+
if (!isActive || status !== ExecutionStatus.Pending) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let mounted = true;
|
|
30
|
+
async function execute() {
|
|
31
|
+
const start = Date.now();
|
|
32
|
+
setStatus(ExecutionStatus.Running);
|
|
33
|
+
setStartTime(start);
|
|
34
|
+
setCurrentElapsed(0);
|
|
35
|
+
try {
|
|
36
|
+
const output = await executeCommand(command, undefined, index);
|
|
37
|
+
if (!mounted)
|
|
38
|
+
return;
|
|
39
|
+
const end = Date.now();
|
|
40
|
+
setEndTime(end);
|
|
41
|
+
const taskDuration = calculateElapsed(start);
|
|
42
|
+
setElapsed(taskDuration);
|
|
43
|
+
setStatus(output.result === 'success'
|
|
44
|
+
? ExecutionStatus.Success
|
|
45
|
+
: ExecutionStatus.Failed);
|
|
46
|
+
if (output.result === 'success') {
|
|
47
|
+
onComplete?.(index, output, taskDuration);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
onError?.(index, output.errors || 'Command failed');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (!mounted)
|
|
55
|
+
return;
|
|
56
|
+
const end = Date.now();
|
|
57
|
+
setEndTime(end);
|
|
58
|
+
setElapsed(calculateElapsed(start));
|
|
59
|
+
setStatus(ExecutionStatus.Failed);
|
|
60
|
+
onError?.(index, err instanceof Error ? err.message : 'Unknown error');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
execute();
|
|
64
|
+
return () => {
|
|
65
|
+
mounted = false;
|
|
66
|
+
};
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
|
+
}, [isActive]);
|
|
69
|
+
// Handle abort when task becomes inactive while running
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!isActive && status === ExecutionStatus.Running && startTime) {
|
|
72
|
+
// Task was aborted mid-execution
|
|
73
|
+
const end = Date.now();
|
|
74
|
+
setEndTime(end);
|
|
75
|
+
setElapsed(calculateElapsed(startTime));
|
|
76
|
+
setStatus(ExecutionStatus.Aborted);
|
|
77
|
+
onAbort?.(index);
|
|
78
|
+
}
|
|
79
|
+
}, [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 }));
|
|
81
|
+
}
|
package/dist/ui/Validate.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Box, Text } from 'ink';
|
|
|
4
4
|
import { ComponentStatus } from '../types/components.js';
|
|
5
5
|
import { TaskType } from '../types/types.js';
|
|
6
6
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
7
|
+
import { addDebugToTimeline } from '../services/components.js';
|
|
7
8
|
import { useInput } from '../services/keyboard.js';
|
|
8
9
|
import { formatErrorMessage } from '../services/messages.js';
|
|
9
10
|
import { ensureMinimumTime } from '../services/timing.js';
|
|
@@ -42,6 +43,8 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
|
|
|
42
43
|
const result = await svc.processWithTool(prompt, 'validate');
|
|
43
44
|
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
44
45
|
if (mounted) {
|
|
46
|
+
// Add debug components to timeline if present
|
|
47
|
+
addDebugToTimeline(result.debug, handlers);
|
|
45
48
|
// Extract CONFIG tasks with descriptions from result
|
|
46
49
|
const configTasks = result.tasks.filter((task) => task.type === TaskType.Config);
|
|
47
50
|
// Build ConfigRequirements with descriptions
|