prompt-language-shell 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ui/Main.js → Main.js} +12 -12
- package/dist/{ui → components}/Component.js +28 -26
- package/dist/{ui → components}/Workflow.js +2 -3
- package/dist/{ui → components/controllers}/Answer.js +18 -17
- package/dist/{ui → components/controllers}/Command.js +11 -18
- package/dist/{ui → components/controllers}/Config.js +8 -116
- package/dist/components/controllers/Confirm.js +42 -0
- package/dist/{ui → components/controllers}/Execute.js +75 -144
- package/dist/{ui → components/controllers}/Introspect.js +12 -28
- package/dist/components/controllers/Refinement.js +18 -0
- package/dist/components/controllers/Schedule.js +139 -0
- package/dist/{ui → components/controllers}/Validate.js +14 -32
- package/dist/components/views/Answer.js +28 -0
- package/dist/components/views/Command.js +11 -0
- package/dist/components/views/Config.js +115 -0
- package/dist/components/views/Confirm.js +24 -0
- package/dist/components/views/Execute.js +60 -0
- package/dist/{ui → components/views}/Feedback.js +3 -3
- package/dist/components/views/Introspect.js +17 -0
- package/dist/{ui → components/views}/Label.js +3 -3
- package/dist/{ui → components/views}/List.js +1 -1
- package/dist/{ui → components/views}/Output.js +2 -2
- package/dist/components/views/Refinement.js +9 -0
- package/dist/{ui → components/views}/Report.js +1 -1
- package/dist/components/views/Schedule.js +120 -0
- package/dist/{ui → components/views}/Separator.js +1 -1
- package/dist/{ui → components/views}/Spinner.js +1 -1
- package/dist/{ui → components/views}/Subtask.js +4 -4
- package/dist/components/views/Task.js +18 -0
- package/dist/components/views/Upcoming.js +30 -0
- package/dist/{ui → components/views}/UserQuery.js +1 -1
- package/dist/components/views/Validate.js +17 -0
- package/dist/{ui → components/views}/Welcome.js +1 -1
- package/dist/configuration/steps.js +1 -1
- package/dist/execution/handlers.js +19 -53
- package/dist/execution/reducer.js +26 -38
- package/dist/execution/runner.js +43 -25
- package/dist/execution/types.js +3 -4
- package/dist/execution/utils.js +1 -1
- package/dist/index.js +1 -1
- package/dist/services/messages.js +19 -0
- package/dist/services/router.js +69 -11
- package/dist/services/shell.js +26 -6
- package/dist/services/timing.js +1 -0
- package/dist/skills/execute.md +15 -7
- package/dist/tools/execute.tool.js +0 -4
- package/dist/types/schemas.js +0 -1
- package/package.json +1 -1
- package/dist/execution/hooks.js +0 -291
- package/dist/ui/Confirm.js +0 -62
- package/dist/ui/Refinement.js +0 -23
- package/dist/ui/Schedule.js +0 -257
- package/dist/ui/Task.js +0 -11
- /package/dist/{ui → components/views}/Debug.js +0 -0
- /package/dist/{ui → components/views}/Message.js +0 -0
- /package/dist/{ui → components/views}/Panel.js +0 -0
|
@@ -7,7 +7,7 @@ import { getTotalElapsed } from './utils.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export function handleTaskCompletion(index, elapsed, context) {
|
|
9
9
|
const { tasks, message, summary } = context;
|
|
10
|
-
const
|
|
10
|
+
const updatedTasks = tasks.map((task, i) => i === index ? { ...task, status: ExecutionStatus.Success, elapsed } : task);
|
|
11
11
|
if (index < tasks.length - 1) {
|
|
12
12
|
// More tasks to execute
|
|
13
13
|
return {
|
|
@@ -18,7 +18,7 @@ export function handleTaskCompletion(index, elapsed, context) {
|
|
|
18
18
|
finalState: {
|
|
19
19
|
message,
|
|
20
20
|
summary,
|
|
21
|
-
tasks:
|
|
21
|
+
tasks: updatedTasks,
|
|
22
22
|
completionMessage: null,
|
|
23
23
|
error: null,
|
|
24
24
|
},
|
|
@@ -27,17 +27,17 @@ export function handleTaskCompletion(index, elapsed, context) {
|
|
|
27
27
|
}
|
|
28
28
|
// All tasks complete
|
|
29
29
|
const summaryText = summary.trim() || 'Execution completed';
|
|
30
|
-
const totalElapsed = getTotalElapsed(
|
|
30
|
+
const totalElapsed = getTotalElapsed(updatedTasks);
|
|
31
31
|
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
32
32
|
return {
|
|
33
33
|
action: {
|
|
34
|
-
type: ExecuteActionType.
|
|
34
|
+
type: ExecuteActionType.ExecutionComplete,
|
|
35
35
|
payload: { index, elapsed, summaryText },
|
|
36
36
|
},
|
|
37
37
|
finalState: {
|
|
38
38
|
message,
|
|
39
39
|
summary,
|
|
40
|
-
tasks:
|
|
40
|
+
tasks: updatedTasks,
|
|
41
41
|
completionMessage: completion,
|
|
42
42
|
error: null,
|
|
43
43
|
},
|
|
@@ -47,63 +47,29 @@ export function handleTaskCompletion(index, elapsed, context) {
|
|
|
47
47
|
/**
|
|
48
48
|
* Handles task error logic and returns the appropriate action and state.
|
|
49
49
|
*/
|
|
50
|
-
export function handleTaskFailure(index, error,
|
|
50
|
+
export function handleTaskFailure(index, error, context) {
|
|
51
51
|
const { tasks, message, summary } = context;
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
finalState: {
|
|
63
|
-
message,
|
|
64
|
-
summary,
|
|
65
|
-
tasks: updatedTaskInfos,
|
|
66
|
-
completionMessage: null,
|
|
67
|
-
error: null,
|
|
68
|
-
},
|
|
69
|
-
shouldComplete: true,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
// Non-critical failure - continue to next task
|
|
73
|
-
if (index < tasks.length - 1) {
|
|
74
|
-
return {
|
|
75
|
-
action: {
|
|
76
|
-
type: ExecuteActionType.TaskErrorContinue,
|
|
77
|
-
payload: { index, elapsed },
|
|
78
|
-
},
|
|
79
|
-
finalState: {
|
|
80
|
-
message,
|
|
81
|
-
summary,
|
|
82
|
-
tasks: updatedTaskInfos,
|
|
83
|
-
completionMessage: null,
|
|
84
|
-
error: null,
|
|
85
|
-
},
|
|
86
|
-
shouldComplete: false,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
// Last task failed (non-critical), complete execution
|
|
90
|
-
// Non-critical failures still show completion message with summary
|
|
91
|
-
const summaryText = summary.trim() || 'Execution completed';
|
|
92
|
-
const totalElapsed = getTotalElapsed(updatedTaskInfos);
|
|
93
|
-
const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
52
|
+
const updatedTasks = tasks.map((task, i) => {
|
|
53
|
+
if (i === index) {
|
|
54
|
+
return { ...task, status: ExecutionStatus.Failed, elapsed: 0 };
|
|
55
|
+
}
|
|
56
|
+
else if (i > index && task.status === ExecutionStatus.Pending) {
|
|
57
|
+
return { ...task, status: ExecutionStatus.Cancelled };
|
|
58
|
+
}
|
|
59
|
+
return task;
|
|
60
|
+
});
|
|
94
61
|
return {
|
|
95
62
|
action: {
|
|
96
|
-
type: ExecuteActionType.
|
|
97
|
-
payload: { index,
|
|
63
|
+
type: ExecuteActionType.TaskError,
|
|
64
|
+
payload: { index, error },
|
|
98
65
|
},
|
|
99
66
|
finalState: {
|
|
100
67
|
message,
|
|
101
68
|
summary,
|
|
102
|
-
tasks:
|
|
103
|
-
completionMessage:
|
|
69
|
+
tasks: updatedTasks,
|
|
70
|
+
completionMessage: null,
|
|
104
71
|
error: null,
|
|
105
72
|
},
|
|
106
|
-
shouldComplete: true,
|
|
107
73
|
};
|
|
108
74
|
}
|
|
109
75
|
/**
|
|
@@ -33,84 +33,72 @@ export function executeReducer(state, action) {
|
|
|
33
33
|
};
|
|
34
34
|
case ExecuteActionType.TaskStarted: {
|
|
35
35
|
const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
|
|
36
|
-
? {
|
|
36
|
+
? {
|
|
37
|
+
...task,
|
|
38
|
+
status: ExecutionStatus.Running,
|
|
39
|
+
startTime: action.payload.startTime,
|
|
40
|
+
}
|
|
37
41
|
: task);
|
|
38
42
|
return {
|
|
39
43
|
...state,
|
|
40
44
|
tasks: updatedTasks,
|
|
41
45
|
};
|
|
42
46
|
}
|
|
43
|
-
case ExecuteActionType.
|
|
44
|
-
const
|
|
47
|
+
case ExecuteActionType.TaskProgress: {
|
|
48
|
+
const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
|
|
45
49
|
? {
|
|
46
50
|
...task,
|
|
47
|
-
status: ExecutionStatus.Success,
|
|
48
51
|
elapsed: action.payload.elapsed,
|
|
52
|
+
output: action.payload.output,
|
|
49
53
|
}
|
|
50
54
|
: task);
|
|
51
55
|
return {
|
|
52
56
|
...state,
|
|
53
|
-
tasks:
|
|
57
|
+
tasks: updatedTasks,
|
|
54
58
|
};
|
|
55
59
|
}
|
|
56
|
-
case ExecuteActionType.
|
|
57
|
-
const
|
|
60
|
+
case ExecuteActionType.TaskComplete: {
|
|
61
|
+
const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
|
|
58
62
|
? {
|
|
59
63
|
...task,
|
|
60
64
|
status: ExecutionStatus.Success,
|
|
61
65
|
elapsed: action.payload.elapsed,
|
|
62
66
|
}
|
|
63
67
|
: task);
|
|
64
|
-
const totalElapsed = getTotalElapsed(updatedTaskInfos);
|
|
65
|
-
const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
66
68
|
return {
|
|
67
69
|
...state,
|
|
68
|
-
tasks:
|
|
69
|
-
completionMessage: completion,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
case ExecuteActionType.TaskErrorCritical: {
|
|
73
|
-
const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
|
|
74
|
-
? { ...task, status: ExecutionStatus.Failed, elapsed: 0 }
|
|
75
|
-
: task);
|
|
76
|
-
return {
|
|
77
|
-
...state,
|
|
78
|
-
tasks: updatedTaskInfos,
|
|
79
|
-
error: action.payload.error,
|
|
70
|
+
tasks: updatedTasks,
|
|
80
71
|
};
|
|
81
72
|
}
|
|
82
|
-
case ExecuteActionType.
|
|
83
|
-
const
|
|
73
|
+
case ExecuteActionType.ExecutionComplete: {
|
|
74
|
+
const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
|
|
84
75
|
? {
|
|
85
76
|
...task,
|
|
86
|
-
status: ExecutionStatus.
|
|
77
|
+
status: ExecutionStatus.Success,
|
|
87
78
|
elapsed: action.payload.elapsed,
|
|
88
79
|
}
|
|
89
80
|
: task);
|
|
81
|
+
const totalElapsed = getTotalElapsed(updatedTasks);
|
|
82
|
+
const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
90
83
|
return {
|
|
91
84
|
...state,
|
|
92
|
-
tasks:
|
|
85
|
+
tasks: updatedTasks,
|
|
86
|
+
completionMessage: completion,
|
|
93
87
|
};
|
|
94
88
|
}
|
|
95
|
-
case ExecuteActionType.
|
|
96
|
-
const
|
|
97
|
-
? {
|
|
98
|
-
...task,
|
|
99
|
-
status: ExecutionStatus.Failed,
|
|
100
|
-
elapsed: action.payload.elapsed,
|
|
101
|
-
}
|
|
89
|
+
case ExecuteActionType.TaskError: {
|
|
90
|
+
const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
|
|
91
|
+
? { ...task, status: ExecutionStatus.Failed, elapsed: 0 }
|
|
102
92
|
: task);
|
|
103
|
-
const totalElapsed = getTotalElapsed(updatedTaskInfos);
|
|
104
|
-
const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
|
|
105
93
|
return {
|
|
106
94
|
...state,
|
|
107
|
-
tasks:
|
|
108
|
-
|
|
95
|
+
tasks: updatedTasks,
|
|
96
|
+
error: action.payload.error,
|
|
109
97
|
};
|
|
110
98
|
}
|
|
111
99
|
case ExecuteActionType.CancelExecution: {
|
|
112
100
|
// Mark running task as aborted, pending tasks as cancelled
|
|
113
|
-
const
|
|
101
|
+
const updatedTasks = state.tasks.map((task) => {
|
|
114
102
|
if (task.status === ExecutionStatus.Running) {
|
|
115
103
|
return { ...task, status: ExecutionStatus.Aborted };
|
|
116
104
|
}
|
|
@@ -121,7 +109,7 @@ export function executeReducer(state, action) {
|
|
|
121
109
|
});
|
|
122
110
|
return {
|
|
123
111
|
...state,
|
|
124
|
-
tasks:
|
|
112
|
+
tasks: updatedTasks,
|
|
125
113
|
};
|
|
126
114
|
}
|
|
127
115
|
default:
|
package/dist/execution/runner.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { ExecutionResult, ExecutionStatus, executeCommand, setOutputCallback, } from '../services/shell.js';
|
|
2
2
|
import { calculateElapsed } from '../services/utils.js';
|
|
3
|
+
// Maximum number of output lines to keep in memory
|
|
4
|
+
const MAX_OUTPUT_LINES = 128;
|
|
5
|
+
/**
|
|
6
|
+
* Limit output to last MAX_OUTPUT_LINES lines to prevent memory exhaustion
|
|
7
|
+
*/
|
|
8
|
+
function limitLines(output) {
|
|
9
|
+
const lines = output.split('\n');
|
|
10
|
+
return lines.slice(-MAX_OUTPUT_LINES).join('\n');
|
|
11
|
+
}
|
|
3
12
|
/**
|
|
4
13
|
* Execute a single task and track its progress.
|
|
5
14
|
* All execution logic is contained here, outside of React components.
|
|
@@ -17,21 +26,38 @@ export async function executeTask(command, index, callbacks) {
|
|
|
17
26
|
error,
|
|
18
27
|
workdir,
|
|
19
28
|
});
|
|
29
|
+
// Throttle updates to avoid excessive re-renders (100ms minimum interval)
|
|
30
|
+
let lastUpdateTime = 0;
|
|
31
|
+
let pendingTimeout;
|
|
32
|
+
const throttledUpdate = () => {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
if (now - lastUpdateTime >= 100) {
|
|
35
|
+
lastUpdateTime = now;
|
|
36
|
+
callbacks.onUpdate(createOutput());
|
|
37
|
+
}
|
|
38
|
+
else if (!pendingTimeout) {
|
|
39
|
+
pendingTimeout = setTimeout(() => {
|
|
40
|
+
pendingTimeout = undefined;
|
|
41
|
+
lastUpdateTime = Date.now();
|
|
42
|
+
callbacks.onUpdate(createOutput());
|
|
43
|
+
}, 100 - (now - lastUpdateTime));
|
|
44
|
+
}
|
|
45
|
+
};
|
|
20
46
|
// Set up output streaming callback
|
|
21
47
|
setOutputCallback((data, stream) => {
|
|
22
48
|
if (stream === 'stdout') {
|
|
23
|
-
stdout
|
|
49
|
+
stdout = limitLines(stdout + data);
|
|
24
50
|
}
|
|
25
51
|
else {
|
|
26
|
-
stderr
|
|
52
|
+
stderr = limitLines(stderr + data);
|
|
27
53
|
}
|
|
28
|
-
|
|
54
|
+
throttledUpdate();
|
|
29
55
|
});
|
|
30
|
-
callbacks.onStart?.();
|
|
31
56
|
try {
|
|
32
57
|
const result = await executeCommand(command, undefined, index);
|
|
33
|
-
// Clear callback
|
|
58
|
+
// Clear callback and pending timeout
|
|
34
59
|
setOutputCallback(undefined);
|
|
60
|
+
clearTimeout(pendingTimeout);
|
|
35
61
|
const elapsed = calculateElapsed(startTime);
|
|
36
62
|
// Update final output from result
|
|
37
63
|
stdout = result.output;
|
|
@@ -39,42 +65,34 @@ export async function executeTask(command, index, callbacks) {
|
|
|
39
65
|
workdir = result.workdir;
|
|
40
66
|
if (result.result === ExecutionResult.Success) {
|
|
41
67
|
const output = createOutput();
|
|
42
|
-
callbacks.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
elapsed,
|
|
46
|
-
output,
|
|
47
|
-
};
|
|
68
|
+
callbacks.onUpdate(output);
|
|
69
|
+
callbacks.onComplete(elapsed, output);
|
|
70
|
+
return { status: ExecutionStatus.Success, elapsed, output };
|
|
48
71
|
}
|
|
49
72
|
else {
|
|
50
73
|
const errorMsg = result.errors || result.error || 'Command failed';
|
|
51
74
|
error = errorMsg;
|
|
52
75
|
const output = createOutput();
|
|
53
|
-
callbacks.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
elapsed,
|
|
57
|
-
output,
|
|
58
|
-
};
|
|
76
|
+
callbacks.onUpdate(output);
|
|
77
|
+
callbacks.onError(errorMsg, output);
|
|
78
|
+
return { status: ExecutionStatus.Failed, elapsed, output };
|
|
59
79
|
}
|
|
60
80
|
}
|
|
61
81
|
catch (err) {
|
|
62
|
-
// Clear callback
|
|
82
|
+
// Clear callback and pending timeout
|
|
63
83
|
setOutputCallback(undefined);
|
|
84
|
+
clearTimeout(pendingTimeout);
|
|
64
85
|
const elapsed = calculateElapsed(startTime);
|
|
65
86
|
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
66
87
|
error = errorMsg;
|
|
67
88
|
const output = createOutput();
|
|
68
|
-
callbacks.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
elapsed,
|
|
72
|
-
output,
|
|
73
|
-
};
|
|
89
|
+
callbacks.onUpdate(output);
|
|
90
|
+
callbacks.onError(errorMsg, output);
|
|
91
|
+
return { status: ExecutionStatus.Failed, elapsed, output };
|
|
74
92
|
}
|
|
75
93
|
}
|
|
76
94
|
/**
|
|
77
|
-
* Create an empty
|
|
95
|
+
* Create an empty execution output
|
|
78
96
|
*/
|
|
79
97
|
export function createEmptyOutput() {
|
|
80
98
|
return { stdout: '', stderr: '', error: '' };
|
package/dist/execution/types.js
CHANGED
|
@@ -4,10 +4,9 @@ export var ExecuteActionType;
|
|
|
4
4
|
ExecuteActionType["CommandsReady"] = "COMMANDS_READY";
|
|
5
5
|
ExecuteActionType["ProcessingError"] = "PROCESSING_ERROR";
|
|
6
6
|
ExecuteActionType["TaskStarted"] = "TASK_STARTED";
|
|
7
|
+
ExecuteActionType["TaskProgress"] = "TASK_PROGRESS";
|
|
7
8
|
ExecuteActionType["TaskComplete"] = "TASK_COMPLETE";
|
|
8
|
-
ExecuteActionType["
|
|
9
|
-
ExecuteActionType["
|
|
10
|
-
ExecuteActionType["TaskErrorContinue"] = "TASK_ERROR_CONTINUE";
|
|
11
|
-
ExecuteActionType["LastTaskError"] = "LAST_TASK_ERROR";
|
|
9
|
+
ExecuteActionType["ExecutionComplete"] = "EXECUTION_COMPLETE";
|
|
10
|
+
ExecuteActionType["TaskError"] = "TASK_ERROR";
|
|
12
11
|
ExecuteActionType["CancelExecution"] = "CANCEL_EXECUTION";
|
|
13
12
|
})(ExecuteActionType || (ExecuteActionType = {}));
|
package/dist/execution/utils.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, join } from 'path';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { render } from 'ink';
|
|
7
7
|
import { DebugLevel } from './configuration/types.js';
|
|
8
|
-
import { Main } from './
|
|
8
|
+
import { Main } from './Main.js';
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = dirname(__filename);
|
|
11
11
|
// Get package info
|
|
@@ -162,3 +162,22 @@ export function getExecutionErrorMessage(_error) {
|
|
|
162
162
|
];
|
|
163
163
|
return messages[Math.floor(Math.random() * messages.length)];
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Returns a loading message while fetching an answer.
|
|
167
|
+
* Randomly selects from variations to sound natural.
|
|
168
|
+
*/
|
|
169
|
+
export function getAnswerLoadingMessage() {
|
|
170
|
+
const messages = [
|
|
171
|
+
'Finding that out for you.',
|
|
172
|
+
'Looking into this.',
|
|
173
|
+
'Let me find out.',
|
|
174
|
+
'One moment please.',
|
|
175
|
+
'Checking on that.',
|
|
176
|
+
'Let me look that up.',
|
|
177
|
+
'Give me a moment.',
|
|
178
|
+
'Looking that up now.',
|
|
179
|
+
'Let me check.',
|
|
180
|
+
'Just a moment.',
|
|
181
|
+
];
|
|
182
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
183
|
+
}
|
package/dist/services/router.js
CHANGED
|
@@ -177,11 +177,37 @@ function executeTasksAfterConfirm(tasks, context) {
|
|
|
177
177
|
// No missing config - proceed with normal routing
|
|
178
178
|
routeTasksAfterConfig(scheduledTasks, context);
|
|
179
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Task types that should appear in the upcoming display
|
|
182
|
+
*/
|
|
183
|
+
const UPCOMING_TASK_TYPES = [TaskType.Execute, TaskType.Answer];
|
|
184
|
+
/**
|
|
185
|
+
* Collect names of all upcoming execution units (groups and standalone tasks)
|
|
186
|
+
* for display during task execution
|
|
187
|
+
*/
|
|
188
|
+
function collectUpcomingNames(scheduledTasks) {
|
|
189
|
+
const names = [];
|
|
190
|
+
for (const task of scheduledTasks) {
|
|
191
|
+
if (task.type === TaskType.Group && task.subtasks?.length) {
|
|
192
|
+
const subtasks = task.subtasks;
|
|
193
|
+
if (UPCOMING_TASK_TYPES.includes(subtasks[0].type)) {
|
|
194
|
+
names.push(task.action);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (UPCOMING_TASK_TYPES.includes(task.type)) {
|
|
198
|
+
names.push(task.action);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return names;
|
|
202
|
+
}
|
|
180
203
|
/**
|
|
181
204
|
* Route tasks after config is complete (or when no config is needed)
|
|
182
205
|
* Processes tasks in order, grouping by type
|
|
183
206
|
*/
|
|
184
207
|
function routeTasksAfterConfig(scheduledTasks, context) {
|
|
208
|
+
// Collect all unit names for upcoming display
|
|
209
|
+
const allUnitNames = collectUpcomingNames(scheduledTasks);
|
|
210
|
+
let currentUnitIndex = 0;
|
|
185
211
|
// Process tasks in order, preserving Group boundaries
|
|
186
212
|
// Track consecutive standalone tasks to group them by type
|
|
187
213
|
let consecutiveStandaloneTasks = [];
|
|
@@ -201,7 +227,18 @@ function routeTasksAfterConfig(scheduledTasks, context) {
|
|
|
201
227
|
const taskType = type;
|
|
202
228
|
if (typeTasks.length === 0)
|
|
203
229
|
continue;
|
|
204
|
-
|
|
230
|
+
// For tasks that appear in upcoming, calculate from remaining units
|
|
231
|
+
if (UPCOMING_TASK_TYPES.includes(taskType)) {
|
|
232
|
+
// Each task advances the unit index
|
|
233
|
+
for (const task of typeTasks) {
|
|
234
|
+
const upcoming = allUnitNames.slice(currentUnitIndex + 1);
|
|
235
|
+
currentUnitIndex++;
|
|
236
|
+
routeTasksByType(taskType, [task], context, upcoming);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
routeTasksByType(taskType, typeTasks, context, []);
|
|
241
|
+
}
|
|
205
242
|
}
|
|
206
243
|
consecutiveStandaloneTasks = [];
|
|
207
244
|
};
|
|
@@ -214,7 +251,20 @@ function routeTasksAfterConfig(scheduledTasks, context) {
|
|
|
214
251
|
if (task.subtasks.length > 0) {
|
|
215
252
|
const subtasks = task.subtasks;
|
|
216
253
|
const taskType = subtasks[0].type;
|
|
217
|
-
|
|
254
|
+
// Calculate upcoming (all units after this one)
|
|
255
|
+
const upcoming = UPCOMING_TASK_TYPES.includes(taskType)
|
|
256
|
+
? allUnitNames.slice(currentUnitIndex + 1)
|
|
257
|
+
: [];
|
|
258
|
+
if (UPCOMING_TASK_TYPES.includes(taskType)) {
|
|
259
|
+
currentUnitIndex++;
|
|
260
|
+
}
|
|
261
|
+
// Pass group name as label for Execute groups
|
|
262
|
+
if (taskType === TaskType.Execute) {
|
|
263
|
+
routeExecuteTasks(subtasks, context, upcoming, task.action);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
routeTasksByType(taskType, subtasks, context, upcoming);
|
|
267
|
+
}
|
|
218
268
|
}
|
|
219
269
|
}
|
|
220
270
|
else {
|
|
@@ -228,21 +278,29 @@ function routeTasksAfterConfig(scheduledTasks, context) {
|
|
|
228
278
|
/**
|
|
229
279
|
* Route Answer tasks - creates separate Answer component for each question
|
|
230
280
|
*/
|
|
231
|
-
function routeAnswerTasks(tasks, context) {
|
|
232
|
-
for (
|
|
233
|
-
|
|
281
|
+
function routeAnswerTasks(tasks, context, upcoming) {
|
|
282
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
283
|
+
const task = tasks[i];
|
|
284
|
+
// Calculate upcoming: remaining answer tasks + original upcoming
|
|
285
|
+
const remainingAnswers = tasks.slice(i + 1).map((t) => t.action);
|
|
286
|
+
const taskUpcoming = [...remainingAnswers, ...upcoming];
|
|
287
|
+
context.workflowHandlers.addToQueue(createAnswer({
|
|
288
|
+
question: task.action,
|
|
289
|
+
service: context.service,
|
|
290
|
+
upcoming: taskUpcoming,
|
|
291
|
+
}));
|
|
234
292
|
}
|
|
235
293
|
}
|
|
236
294
|
/**
|
|
237
295
|
* Route Introspect tasks - creates single Introspect component for all tasks
|
|
238
296
|
*/
|
|
239
|
-
function routeIntrospectTasks(tasks, context) {
|
|
297
|
+
function routeIntrospectTasks(tasks, context, _upcoming) {
|
|
240
298
|
context.workflowHandlers.addToQueue(createIntrospect({ tasks, service: context.service }));
|
|
241
299
|
}
|
|
242
300
|
/**
|
|
243
301
|
* Route Config tasks - extracts keys, caches labels, creates Config component
|
|
244
302
|
*/
|
|
245
|
-
function routeConfigTasks(tasks, context) {
|
|
303
|
+
function routeConfigTasks(tasks, context, _upcoming) {
|
|
246
304
|
const configKeys = tasks
|
|
247
305
|
.map((task) => task.params?.key)
|
|
248
306
|
.filter((key) => key !== undefined);
|
|
@@ -286,8 +344,8 @@ function routeConfigTasks(tasks, context) {
|
|
|
286
344
|
/**
|
|
287
345
|
* Route Execute tasks - creates Execute component (validation already done)
|
|
288
346
|
*/
|
|
289
|
-
function routeExecuteTasks(tasks, context) {
|
|
290
|
-
context.workflowHandlers.addToQueue(createExecute({ tasks, service: context.service }));
|
|
347
|
+
function routeExecuteTasks(tasks, context, upcoming, label) {
|
|
348
|
+
context.workflowHandlers.addToQueue(createExecute({ tasks, service: context.service, upcoming, label }));
|
|
291
349
|
}
|
|
292
350
|
/**
|
|
293
351
|
* Registry mapping task types to their route handlers
|
|
@@ -302,9 +360,9 @@ const taskRouteHandlers = {
|
|
|
302
360
|
* Route tasks by type to appropriate components
|
|
303
361
|
* Uses registry pattern for extensibility
|
|
304
362
|
*/
|
|
305
|
-
function routeTasksByType(taskType, tasks, context) {
|
|
363
|
+
function routeTasksByType(taskType, tasks, context, upcoming) {
|
|
306
364
|
const handler = taskRouteHandlers[taskType];
|
|
307
365
|
if (handler) {
|
|
308
|
-
handler(tasks, context);
|
|
366
|
+
handler(tasks, context, upcoming);
|
|
309
367
|
}
|
|
310
368
|
}
|
package/dist/services/shell.js
CHANGED
|
@@ -61,6 +61,14 @@ export class DummyExecutor {
|
|
|
61
61
|
}
|
|
62
62
|
// Marker for extracting pwd from command output
|
|
63
63
|
const PWD_MARKER = '__PWD_MARKER_7x9k2m__';
|
|
64
|
+
const MAX_OUTPUT_LINES = 128;
|
|
65
|
+
/**
|
|
66
|
+
* Limit output to last MAX_OUTPUT_LINES lines.
|
|
67
|
+
*/
|
|
68
|
+
function limitLines(output) {
|
|
69
|
+
const lines = output.split('\n');
|
|
70
|
+
return lines.slice(-MAX_OUTPUT_LINES).join('\n');
|
|
71
|
+
}
|
|
64
72
|
/**
|
|
65
73
|
* Parse stdout to extract workdir and clean output.
|
|
66
74
|
* Returns the cleaned output and the extracted workdir.
|
|
@@ -93,6 +101,12 @@ class OutputStreamer {
|
|
|
93
101
|
*/
|
|
94
102
|
pushStdout(data) {
|
|
95
103
|
this.chunks.push(data);
|
|
104
|
+
// Collapse when we have too many chunks to prevent memory growth
|
|
105
|
+
if (this.chunks.length > 16) {
|
|
106
|
+
const accumulated = this.chunks.join('');
|
|
107
|
+
this.chunks = [limitLines(accumulated)];
|
|
108
|
+
this.emittedLength = 0;
|
|
109
|
+
}
|
|
96
110
|
if (!this.callback)
|
|
97
111
|
return;
|
|
98
112
|
const accumulated = this.chunks.join('');
|
|
@@ -123,7 +137,7 @@ class OutputStreamer {
|
|
|
123
137
|
* Get the accumulated raw output.
|
|
124
138
|
*/
|
|
125
139
|
getAccumulated() {
|
|
126
|
-
return this.chunks.join('');
|
|
140
|
+
return limitLines(this.chunks.join(''));
|
|
127
141
|
}
|
|
128
142
|
}
|
|
129
143
|
/**
|
|
@@ -189,6 +203,13 @@ export class RealExecutor {
|
|
|
189
203
|
child.stderr.on('data', (data) => {
|
|
190
204
|
const text = data.toString();
|
|
191
205
|
stderr.push(text);
|
|
206
|
+
// Collapse when we have too many chunks to prevent memory growth
|
|
207
|
+
if (stderr.length > 16) {
|
|
208
|
+
const accumulated = stderr.join('');
|
|
209
|
+
const limited = limitLines(accumulated);
|
|
210
|
+
stderr.length = 0;
|
|
211
|
+
stderr.push(limited);
|
|
212
|
+
}
|
|
192
213
|
this.outputCallback?.(text, 'stderr');
|
|
193
214
|
});
|
|
194
215
|
child.on('error', (error) => {
|
|
@@ -200,7 +221,7 @@ export class RealExecutor {
|
|
|
200
221
|
description: cmd.description,
|
|
201
222
|
command: cmd.command,
|
|
202
223
|
output: stdoutStreamer.getAccumulated(),
|
|
203
|
-
errors: error.message,
|
|
224
|
+
errors: limitLines(stderr.join('')) || error.message,
|
|
204
225
|
result: ExecutionResult.Error,
|
|
205
226
|
error: error.message,
|
|
206
227
|
};
|
|
@@ -218,7 +239,7 @@ export class RealExecutor {
|
|
|
218
239
|
description: cmd.description,
|
|
219
240
|
command: cmd.command,
|
|
220
241
|
output,
|
|
221
|
-
errors: stderr.join(''),
|
|
242
|
+
errors: limitLines(stderr.join('')),
|
|
222
243
|
result: success ? ExecutionResult.Success : ExecutionResult.Error,
|
|
223
244
|
error: success ? undefined : `Exit code: ${code}`,
|
|
224
245
|
workdir,
|
|
@@ -278,9 +299,8 @@ export async function executeCommands(commands, onProgress) {
|
|
|
278
299
|
: ExecutionStatus.Failed,
|
|
279
300
|
output,
|
|
280
301
|
});
|
|
281
|
-
// Stop
|
|
282
|
-
|
|
283
|
-
if (output.result !== ExecutionResult.Success && isCritical) {
|
|
302
|
+
// Stop on failure
|
|
303
|
+
if (output.result !== ExecutionResult.Success) {
|
|
284
304
|
break;
|
|
285
305
|
}
|
|
286
306
|
}
|