prompt-language-shell 0.9.2 → 0.9.6
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 +14 -4
- 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 +134 -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/Debug.js +12 -0
- package/dist/components/views/Execute.js +60 -0
- package/dist/components/views/Feedback.js +8 -0
- 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 +121 -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/Table.js +15 -0
- 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/anthropic.js +27 -31
- package/dist/services/colors.js +2 -1
- package/dist/services/logger.js +126 -13
- package/dist/services/messages.js +19 -0
- package/dist/services/parser.js +13 -5
- package/dist/services/refinement.js +8 -2
- package/dist/services/router.js +184 -89
- package/dist/services/shell.js +26 -6
- package/dist/services/skills.js +35 -7
- package/dist/services/timing.js +1 -0
- package/dist/skills/execute.md +15 -7
- package/dist/skills/schedule.md +155 -0
- package/dist/tools/execute.tool.js +0 -4
- package/dist/tools/schedule.tool.js +1 -1
- package/dist/types/schemas.js +0 -1
- package/package.json +4 -4
- package/dist/execution/hooks.js +0 -291
- package/dist/ui/Confirm.js +0 -62
- package/dist/ui/Debug.js +0 -7
- package/dist/ui/Feedback.js +0 -19
- 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}/Message.js +0 -0
- /package/dist/{ui → components/views}/Panel.js +0 -0
package/dist/services/parser.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import YAML from 'yaml';
|
|
2
2
|
import { displayWarning } from './logger.js';
|
|
3
3
|
/**
|
|
4
|
-
* Validate
|
|
4
|
+
* Validate extracted sections from a skill
|
|
5
5
|
* Returns validation error if skill is invalid, null if valid
|
|
6
6
|
* Note: Name section is optional - key from filename is used as fallback
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
const sections = extractSections(content);
|
|
8
|
+
function validateSections(sections, key) {
|
|
10
9
|
// Use key for error reporting if name not present
|
|
11
10
|
const skillName = sections.name || key;
|
|
12
11
|
// Check required sections (Name is now optional)
|
|
@@ -37,6 +36,15 @@ export function validateSkillStructure(content, key) {
|
|
|
37
36
|
}
|
|
38
37
|
return null;
|
|
39
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Validate a skill without parsing it fully
|
|
41
|
+
* Returns validation error if skill is invalid, null if valid
|
|
42
|
+
* Note: Name section is optional - key from filename is used as fallback
|
|
43
|
+
*/
|
|
44
|
+
export function validateSkillStructure(content, key) {
|
|
45
|
+
const sections = extractSections(content);
|
|
46
|
+
return validateSections(sections, key);
|
|
47
|
+
}
|
|
40
48
|
/**
|
|
41
49
|
* Convert kebab-case key to Title Case display name
|
|
42
50
|
* Examples: "deploy-app" -> "Deploy App", "build-project-2" -> "Build Project 2"
|
|
@@ -64,8 +72,8 @@ export function parseSkillMarkdown(key, content) {
|
|
|
64
72
|
const sections = extractSections(content);
|
|
65
73
|
// Determine display name: prefer Name section, otherwise derive from key
|
|
66
74
|
const displayName = sections.name || keyToDisplayName(key);
|
|
67
|
-
// Validate
|
|
68
|
-
const validationError =
|
|
75
|
+
// Validate using already-extracted sections (avoids re-parsing)
|
|
76
|
+
const validationError = validateSections(sections, key);
|
|
69
77
|
// For invalid skills, return minimal definition with error
|
|
70
78
|
if (validationError) {
|
|
71
79
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
1
2
|
import { TaskType } from '../types/types.js';
|
|
2
|
-
import { createRefinement } from './components.js';
|
|
3
|
+
import { createCommand, createRefinement } from './components.js';
|
|
3
4
|
import { formatErrorMessage, getRefiningMessage } from './messages.js';
|
|
4
5
|
import { routeTasksWithConfirm } from './router.js';
|
|
5
6
|
/**
|
|
@@ -7,6 +8,11 @@ import { routeTasksWithConfirm } from './router.js';
|
|
|
7
8
|
* Called when user selects options from a plan with DEFINE tasks
|
|
8
9
|
*/
|
|
9
10
|
export async function handleRefinement(selectedTasks, service, originalCommand, lifecycleHandlers, workflowHandlers, requestHandlers) {
|
|
11
|
+
// Display the resolved command (from user's selection)
|
|
12
|
+
// The first task's action contains the full resolved command
|
|
13
|
+
const resolvedCommand = selectedTasks[0]?.action || originalCommand;
|
|
14
|
+
const commandDisplay = createCommand({ command: resolvedCommand, service, onAborted: requestHandlers.onAborted }, ComponentStatus.Done);
|
|
15
|
+
workflowHandlers.addToTimeline(commandDisplay);
|
|
10
16
|
// Create and add refinement component to queue
|
|
11
17
|
const refinementDef = createRefinement({
|
|
12
18
|
text: getRefiningMessage(),
|
|
@@ -19,7 +25,7 @@ export async function handleRefinement(selectedTasks, service, originalCommand,
|
|
|
19
25
|
// Build refined command from selected tasks
|
|
20
26
|
const refinedCommand = selectedTasks
|
|
21
27
|
.map((task) => {
|
|
22
|
-
const action = task.action.
|
|
28
|
+
const action = task.action.replace(/,/g, ' -');
|
|
23
29
|
const type = task.type;
|
|
24
30
|
// For execute/group tasks, use generic hint - let LLM decide based on skill
|
|
25
31
|
if (type === TaskType.Execute || type === TaskType.Group) {
|
package/dist/services/router.js
CHANGED
|
@@ -5,9 +5,74 @@ import { getConfigSchema } from '../configuration/schema.js';
|
|
|
5
5
|
import { createConfigStepsFromSchema } from '../configuration/steps.js';
|
|
6
6
|
import { unflattenConfig } from '../configuration/transformation.js';
|
|
7
7
|
import { saveConfigLabels } from '../configuration/labels.js';
|
|
8
|
-
import { createAnswer, createConfig, createConfirm, createExecute, createFeedback, createIntrospect,
|
|
9
|
-
import { getCancellationMessage, getConfirmationMessage,
|
|
8
|
+
import { createAnswer, createConfig, createConfirm, createExecute, createFeedback, createIntrospect, createSchedule, createValidate, } from './components.js';
|
|
9
|
+
import { getCancellationMessage, getConfirmationMessage, getUnknownRequestMessage, } from './messages.js';
|
|
10
10
|
import { validateExecuteTasks } from './validator.js';
|
|
11
|
+
/**
|
|
12
|
+
* Flatten inner task structure completely - removes all nested groups.
|
|
13
|
+
* Used internally to flatten subtasks within a top-level group.
|
|
14
|
+
*/
|
|
15
|
+
function flattenInnerTasks(tasks) {
|
|
16
|
+
const result = [];
|
|
17
|
+
for (const task of tasks) {
|
|
18
|
+
if (task.type === TaskType.Group &&
|
|
19
|
+
task.subtasks &&
|
|
20
|
+
task.subtasks.length > 0) {
|
|
21
|
+
// Recursively flatten inner group
|
|
22
|
+
result.push(...flattenInnerTasks(task.subtasks));
|
|
23
|
+
}
|
|
24
|
+
else if (task.type !== TaskType.Group) {
|
|
25
|
+
// Leaf task - add as-is
|
|
26
|
+
const leafTask = {
|
|
27
|
+
action: task.action,
|
|
28
|
+
type: task.type,
|
|
29
|
+
};
|
|
30
|
+
if (task.params)
|
|
31
|
+
leafTask.params = task.params;
|
|
32
|
+
if (task.config)
|
|
33
|
+
leafTask.config = task.config;
|
|
34
|
+
result.push(leafTask);
|
|
35
|
+
}
|
|
36
|
+
// Skip empty groups
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Flatten hierarchical task structure, preserving top-level groups.
|
|
42
|
+
* Top-level groups are kept with their subtasks flattened.
|
|
43
|
+
* Inner nested groups are removed and their subtasks extracted recursively.
|
|
44
|
+
*/
|
|
45
|
+
export function flattenTasks(tasks) {
|
|
46
|
+
const result = [];
|
|
47
|
+
for (const task of tasks) {
|
|
48
|
+
if (task.type === TaskType.Group &&
|
|
49
|
+
task.subtasks &&
|
|
50
|
+
task.subtasks.length > 0) {
|
|
51
|
+
// Preserve top-level group but flatten its subtasks
|
|
52
|
+
const flattenedSubtasks = flattenInnerTasks(task.subtasks);
|
|
53
|
+
const groupTask = {
|
|
54
|
+
action: task.action,
|
|
55
|
+
type: task.type,
|
|
56
|
+
subtasks: flattenedSubtasks,
|
|
57
|
+
};
|
|
58
|
+
result.push(groupTask);
|
|
59
|
+
}
|
|
60
|
+
else if (task.type !== TaskType.Group) {
|
|
61
|
+
// Non-group task - add as-is
|
|
62
|
+
const leafTask = {
|
|
63
|
+
action: task.action,
|
|
64
|
+
type: task.type,
|
|
65
|
+
};
|
|
66
|
+
if (task.params)
|
|
67
|
+
leafTask.params = task.params;
|
|
68
|
+
if (task.config)
|
|
69
|
+
leafTask.config = task.config;
|
|
70
|
+
result.push(leafTask);
|
|
71
|
+
}
|
|
72
|
+
// Skip empty groups (group with no subtasks)
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
11
76
|
/**
|
|
12
77
|
* Determine the operation name based on task types
|
|
13
78
|
*/
|
|
@@ -31,8 +96,12 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, life
|
|
|
31
96
|
const validTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
|
|
32
97
|
// Check if no valid tasks remain after filtering
|
|
33
98
|
if (validTasks.length === 0) {
|
|
34
|
-
|
|
35
|
-
|
|
99
|
+
// Use action from first ignore task if available, otherwise generic message
|
|
100
|
+
const ignoreTask = tasks.find((task) => task.type === TaskType.Ignore);
|
|
101
|
+
const message = ignoreTask?.action
|
|
102
|
+
? `${ignoreTask.action}.`
|
|
103
|
+
: getUnknownRequestMessage();
|
|
104
|
+
workflowHandlers.addToQueue(createFeedback({ type: FeedbackType.Warning, message }));
|
|
36
105
|
return;
|
|
37
106
|
}
|
|
38
107
|
const operation = getOperationName(validTasks);
|
|
@@ -80,61 +149,44 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, life
|
|
|
80
149
|
}
|
|
81
150
|
}
|
|
82
151
|
/**
|
|
83
|
-
* Validate task
|
|
84
|
-
*
|
|
152
|
+
* Validate task structure after flattening.
|
|
153
|
+
* Currently no-op since flattening removes Groups and mixed types are allowed.
|
|
85
154
|
*/
|
|
86
|
-
function validateTaskTypes(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Convert to ScheduledTask to access subtasks property
|
|
90
|
-
const scheduledTasks = asScheduledTasks(tasks);
|
|
91
|
-
// Check each Group task's subtasks for uniform types
|
|
92
|
-
for (const task of scheduledTasks) {
|
|
93
|
-
if (task.type === TaskType.Group &&
|
|
94
|
-
task.subtasks &&
|
|
95
|
-
task.subtasks.length > 0) {
|
|
96
|
-
const subtaskTypes = new Set(task.subtasks.map((t) => t.type));
|
|
97
|
-
if (subtaskTypes.size > 1) {
|
|
98
|
-
throw new Error(getMixedTaskTypesError(Array.from(subtaskTypes)));
|
|
99
|
-
}
|
|
100
|
-
// Recursively validate nested groups
|
|
101
|
-
validateTaskTypes(task.subtasks);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
155
|
+
function validateTaskTypes(_tasks) {
|
|
156
|
+
// After flattening, Groups are removed and mixed leaf types are allowed.
|
|
157
|
+
// The router handles different task types by routing each to its handler.
|
|
104
158
|
}
|
|
105
159
|
/**
|
|
106
160
|
* Execute tasks after confirmation (internal helper)
|
|
107
|
-
*
|
|
108
|
-
* Supports mixed types at top level with Groups
|
|
161
|
+
* Flattens hierarchical structure, validates task types, and routes appropriately
|
|
109
162
|
*/
|
|
110
163
|
function executeTasksAfterConfirm(tasks, context) {
|
|
111
164
|
const { service, userRequest, workflowHandlers, requestHandlers } = context;
|
|
112
|
-
//
|
|
165
|
+
// Flatten hierarchical structure into flat list of leaf tasks
|
|
166
|
+
const scheduledTasks = asScheduledTasks(tasks);
|
|
167
|
+
const flatTasks = flattenTasks(scheduledTasks);
|
|
168
|
+
// Validate that all tasks have uniform type
|
|
113
169
|
try {
|
|
114
|
-
validateTaskTypes(
|
|
170
|
+
validateTaskTypes(flatTasks);
|
|
115
171
|
}
|
|
116
172
|
catch (error) {
|
|
117
173
|
requestHandlers.onError(error instanceof Error ? error.message : String(error));
|
|
118
174
|
return;
|
|
119
175
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
for (const task of scheduledTasks) {
|
|
176
|
+
// Collect all Execute tasks for validation (including those inside groups)
|
|
177
|
+
const executeTasks = [];
|
|
178
|
+
for (const task of flatTasks) {
|
|
124
179
|
if (task.type === TaskType.Execute) {
|
|
125
|
-
|
|
180
|
+
executeTasks.push(task);
|
|
126
181
|
}
|
|
127
182
|
else if (task.type === TaskType.Group && task.subtasks) {
|
|
128
|
-
|
|
129
|
-
if (subtasks.length > 0 && subtasks[0].type === TaskType.Execute) {
|
|
130
|
-
allExecuteTasks.push(...subtasks);
|
|
131
|
-
}
|
|
183
|
+
executeTasks.push(...task.subtasks.filter((t) => t.type === TaskType.Execute));
|
|
132
184
|
}
|
|
133
185
|
}
|
|
134
|
-
// Validate
|
|
135
|
-
if (
|
|
186
|
+
// Validate Execute tasks to collect missing config upfront
|
|
187
|
+
if (executeTasks.length > 0) {
|
|
136
188
|
try {
|
|
137
|
-
const validation = validateExecuteTasks(
|
|
189
|
+
const validation = validateExecuteTasks(executeTasks);
|
|
138
190
|
if (validation.validationErrors.length > 0) {
|
|
139
191
|
// Show error feedback for invalid skills
|
|
140
192
|
const errorMessages = validation.validationErrors.map((error) => {
|
|
@@ -150,7 +202,7 @@ function executeTasksAfterConfirm(tasks, context) {
|
|
|
150
202
|
return;
|
|
151
203
|
}
|
|
152
204
|
else if (validation.missingConfig.length > 0) {
|
|
153
|
-
// Missing config detected - create
|
|
205
|
+
// Missing config detected - create Validate component for all missing config
|
|
154
206
|
workflowHandlers.addToQueue(createValidate({
|
|
155
207
|
missingConfig: validation.missingConfig,
|
|
156
208
|
userRequest,
|
|
@@ -160,7 +212,7 @@ function executeTasksAfterConfirm(tasks, context) {
|
|
|
160
212
|
},
|
|
161
213
|
onValidationComplete: () => {
|
|
162
214
|
// After config is complete, resume task routing
|
|
163
|
-
routeTasksAfterConfig(
|
|
215
|
+
routeTasksAfterConfig(flatTasks, context);
|
|
164
216
|
},
|
|
165
217
|
onAborted: (operation) => {
|
|
166
218
|
requestHandlers.onAborted(operation);
|
|
@@ -175,74 +227,117 @@ function executeTasksAfterConfirm(tasks, context) {
|
|
|
175
227
|
}
|
|
176
228
|
}
|
|
177
229
|
// No missing config - proceed with normal routing
|
|
178
|
-
routeTasksAfterConfig(
|
|
230
|
+
routeTasksAfterConfig(flatTasks, context);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Task types that should appear in the upcoming display
|
|
234
|
+
*/
|
|
235
|
+
const UPCOMING_TASK_TYPES = [TaskType.Execute, TaskType.Answer, TaskType.Group];
|
|
236
|
+
/**
|
|
237
|
+
* Collect action names for tasks that appear in upcoming display.
|
|
238
|
+
* Groups are included with their group name (not individual subtask names).
|
|
239
|
+
*/
|
|
240
|
+
function collectUpcomingNames(tasks) {
|
|
241
|
+
return tasks
|
|
242
|
+
.filter((t) => UPCOMING_TASK_TYPES.includes(t.type))
|
|
243
|
+
.map((t) => t.action);
|
|
179
244
|
}
|
|
180
245
|
/**
|
|
181
246
|
* Route tasks after config is complete (or when no config is needed)
|
|
182
|
-
* Processes
|
|
247
|
+
* Processes task list, routing each task type to its handler.
|
|
248
|
+
* Top-level groups are preserved: their subtasks are routed with the group name.
|
|
249
|
+
* Config tasks are grouped together; Execute/Answer are routed individually.
|
|
183
250
|
*/
|
|
184
|
-
function routeTasksAfterConfig(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
251
|
+
function routeTasksAfterConfig(tasks, context) {
|
|
252
|
+
if (tasks.length === 0)
|
|
253
|
+
return;
|
|
254
|
+
// Collect all upcoming names for display (Execute, Answer, and Group tasks)
|
|
255
|
+
const allUpcomingNames = collectUpcomingNames(tasks);
|
|
256
|
+
let upcomingIndex = 0;
|
|
257
|
+
// Task types that should be grouped together (one component for all tasks)
|
|
258
|
+
const groupedTypes = [TaskType.Config, TaskType.Introspect];
|
|
259
|
+
// Route grouped task types together (collect from all tasks including subtasks)
|
|
260
|
+
for (const groupedType of groupedTypes) {
|
|
261
|
+
const typeTasks = [];
|
|
262
|
+
for (const task of tasks) {
|
|
263
|
+
if (task.type === groupedType) {
|
|
264
|
+
typeTasks.push(task);
|
|
265
|
+
}
|
|
266
|
+
else if (task.type === TaskType.Group && task.subtasks) {
|
|
267
|
+
typeTasks.push(...task.subtasks.filter((t) => t.type === groupedType));
|
|
268
|
+
}
|
|
198
269
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const taskType = type;
|
|
202
|
-
if (typeTasks.length === 0)
|
|
203
|
-
continue;
|
|
204
|
-
routeTasksByType(taskType, typeTasks, context);
|
|
270
|
+
if (typeTasks.length > 0) {
|
|
271
|
+
routeTasksByType(groupedType, typeTasks, context, []);
|
|
205
272
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
273
|
+
}
|
|
274
|
+
// Process Execute, Answer, and Group tasks individually (with upcoming support)
|
|
275
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
276
|
+
const task = tasks[i];
|
|
277
|
+
const taskType = task.type;
|
|
278
|
+
// Skip grouped task types (already routed above)
|
|
279
|
+
if (groupedTypes.includes(taskType))
|
|
280
|
+
continue;
|
|
281
|
+
if (taskType === TaskType.Group && task.subtasks) {
|
|
282
|
+
// Route group's subtasks - Execute tasks get group label, others routed normally
|
|
283
|
+
const upcoming = allUpcomingNames.slice(upcomingIndex + 1);
|
|
284
|
+
upcomingIndex++;
|
|
285
|
+
// Separate subtasks by type
|
|
286
|
+
const executeSubtasks = task.subtasks.filter((t) => t.type === TaskType.Execute);
|
|
287
|
+
const answerSubtasks = task.subtasks.filter((t) => t.type === TaskType.Answer);
|
|
288
|
+
// Route Execute subtasks with group name as label
|
|
289
|
+
if (executeSubtasks.length > 0) {
|
|
290
|
+
routeExecuteTasks(executeSubtasks, context, upcoming, task.action);
|
|
291
|
+
}
|
|
292
|
+
// Route Answer subtasks individually
|
|
293
|
+
if (answerSubtasks.length > 0) {
|
|
294
|
+
routeAnswerTasks(answerSubtasks, context, upcoming);
|
|
218
295
|
}
|
|
219
296
|
}
|
|
297
|
+
else if (taskType === TaskType.Execute) {
|
|
298
|
+
// Calculate upcoming for this Execute task
|
|
299
|
+
const upcoming = allUpcomingNames.slice(upcomingIndex + 1);
|
|
300
|
+
upcomingIndex++;
|
|
301
|
+
routeExecuteTasks([task], context, upcoming);
|
|
302
|
+
}
|
|
303
|
+
else if (taskType === TaskType.Answer) {
|
|
304
|
+
// Calculate upcoming for this Answer task
|
|
305
|
+
const upcoming = allUpcomingNames.slice(upcomingIndex + 1);
|
|
306
|
+
upcomingIndex++;
|
|
307
|
+
routeTasksByType(taskType, [task], context, upcoming);
|
|
308
|
+
}
|
|
220
309
|
else {
|
|
221
|
-
//
|
|
222
|
-
|
|
310
|
+
// For other types (Report, etc.), route without upcoming
|
|
311
|
+
routeTasksByType(taskType, [task], context, []);
|
|
223
312
|
}
|
|
224
313
|
}
|
|
225
|
-
// Process any remaining standalone tasks
|
|
226
|
-
processStandaloneTasks();
|
|
227
314
|
}
|
|
228
315
|
/**
|
|
229
316
|
* Route Answer tasks - creates separate Answer component for each question
|
|
230
317
|
*/
|
|
231
|
-
function routeAnswerTasks(tasks, context) {
|
|
232
|
-
for (
|
|
233
|
-
|
|
318
|
+
function routeAnswerTasks(tasks, context, upcoming) {
|
|
319
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
320
|
+
const task = tasks[i];
|
|
321
|
+
// Calculate upcoming: remaining answer tasks + original upcoming
|
|
322
|
+
const remainingAnswers = tasks.slice(i + 1).map((t) => t.action);
|
|
323
|
+
const taskUpcoming = [...remainingAnswers, ...upcoming];
|
|
324
|
+
context.workflowHandlers.addToQueue(createAnswer({
|
|
325
|
+
question: task.action,
|
|
326
|
+
service: context.service,
|
|
327
|
+
upcoming: taskUpcoming,
|
|
328
|
+
}));
|
|
234
329
|
}
|
|
235
330
|
}
|
|
236
331
|
/**
|
|
237
332
|
* Route Introspect tasks - creates single Introspect component for all tasks
|
|
238
333
|
*/
|
|
239
|
-
function routeIntrospectTasks(tasks, context) {
|
|
334
|
+
function routeIntrospectTasks(tasks, context, _upcoming) {
|
|
240
335
|
context.workflowHandlers.addToQueue(createIntrospect({ tasks, service: context.service }));
|
|
241
336
|
}
|
|
242
337
|
/**
|
|
243
338
|
* Route Config tasks - extracts keys, caches labels, creates Config component
|
|
244
339
|
*/
|
|
245
|
-
function routeConfigTasks(tasks, context) {
|
|
340
|
+
function routeConfigTasks(tasks, context, _upcoming) {
|
|
246
341
|
const configKeys = tasks
|
|
247
342
|
.map((task) => task.params?.key)
|
|
248
343
|
.filter((key) => key !== undefined);
|
|
@@ -286,8 +381,8 @@ function routeConfigTasks(tasks, context) {
|
|
|
286
381
|
/**
|
|
287
382
|
* Route Execute tasks - creates Execute component (validation already done)
|
|
288
383
|
*/
|
|
289
|
-
function routeExecuteTasks(tasks, context) {
|
|
290
|
-
context.workflowHandlers.addToQueue(createExecute({ tasks, service: context.service }));
|
|
384
|
+
function routeExecuteTasks(tasks, context, upcoming, label) {
|
|
385
|
+
context.workflowHandlers.addToQueue(createExecute({ tasks, service: context.service, upcoming, label }));
|
|
291
386
|
}
|
|
292
387
|
/**
|
|
293
388
|
* Registry mapping task types to their route handlers
|
|
@@ -302,9 +397,9 @@ const taskRouteHandlers = {
|
|
|
302
397
|
* Route tasks by type to appropriate components
|
|
303
398
|
* Uses registry pattern for extensibility
|
|
304
399
|
*/
|
|
305
|
-
function routeTasksByType(taskType, tasks, context) {
|
|
400
|
+
function routeTasksByType(taskType, tasks, context, upcoming) {
|
|
306
401
|
const handler = taskRouteHandlers[taskType];
|
|
307
402
|
if (handler) {
|
|
308
|
-
handler(tasks, context);
|
|
403
|
+
handler(tasks, context, upcoming);
|
|
309
404
|
}
|
|
310
405
|
}
|
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
|
}
|
package/dist/services/skills.js
CHANGED
|
@@ -94,17 +94,47 @@ export function loadSkillDefinitions(fs = defaultFileSystem) {
|
|
|
94
94
|
const skills = loadSkills(fs);
|
|
95
95
|
return skills.map(({ key, content }) => parseSkillMarkdown(key, content));
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Mark incomplete skill in markdown by appending (INCOMPLETE) to name
|
|
99
|
+
*/
|
|
100
|
+
function markIncompleteSkill(content) {
|
|
101
|
+
return content.replace(/^(#{1,6}\s+Name\s*\n+)(.+?)(\n|$)/im, `$1$2 (INCOMPLETE)$3`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Load skills with both formatted prompt section and parsed definitions
|
|
105
|
+
* Single source of truth for both LLM prompts and debug display
|
|
106
|
+
* Parses each skill only once for efficiency
|
|
107
|
+
*/
|
|
108
|
+
export function loadSkillsForPrompt(fs = defaultFileSystem) {
|
|
109
|
+
const skills = loadSkills(fs);
|
|
110
|
+
// Parse each skill once and build both outputs
|
|
111
|
+
const definitions = [];
|
|
112
|
+
const markedContent = [];
|
|
113
|
+
for (const { key, content } of skills) {
|
|
114
|
+
const parsed = parseSkillMarkdown(key, content);
|
|
115
|
+
definitions.push(parsed);
|
|
116
|
+
// Mark incomplete skills in markdown for LLM
|
|
117
|
+
if (parsed.isIncomplete) {
|
|
118
|
+
markedContent.push(markIncompleteSkill(content));
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
markedContent.push(content);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const formatted = formatSkillsForPrompt(markedContent);
|
|
125
|
+
return { formatted, definitions };
|
|
126
|
+
}
|
|
97
127
|
/**
|
|
98
128
|
* Load skills and mark incomplete ones in their markdown
|
|
99
129
|
* Returns array of skill markdown with status markers
|
|
130
|
+
* Uses loadSkillsForPrompt internally to avoid duplicating logic
|
|
100
131
|
*/
|
|
101
132
|
export function loadSkillsWithValidation(fs = defaultFileSystem) {
|
|
102
133
|
const skills = loadSkills(fs);
|
|
103
134
|
return skills.map(({ key, content }) => {
|
|
104
135
|
const parsed = parseSkillMarkdown(key, content);
|
|
105
|
-
// If skill is incomplete (either validation failed or needs more documentation), append (INCOMPLETE) to the name
|
|
106
136
|
if (parsed.isIncomplete) {
|
|
107
|
-
return content
|
|
137
|
+
return markIncompleteSkill(content);
|
|
108
138
|
}
|
|
109
139
|
return content;
|
|
110
140
|
});
|
|
@@ -127,6 +157,7 @@ export function createSkillLookup(definitions) {
|
|
|
127
157
|
}
|
|
128
158
|
/**
|
|
129
159
|
* Format skills for inclusion in the planning prompt
|
|
160
|
+
* Skills are joined with double newlines (skill headers provide separation)
|
|
130
161
|
*/
|
|
131
162
|
export function formatSkillsForPrompt(skills) {
|
|
132
163
|
if (skills.length === 0) {
|
|
@@ -148,11 +179,8 @@ brackets for additional information. Use commas instead. For example:
|
|
|
148
179
|
- WRONG: "Build project Alpha (the legacy version)"
|
|
149
180
|
|
|
150
181
|
`;
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
.map((s) => s.trim())
|
|
154
|
-
.join('\n\n' + separator + '\n\n');
|
|
155
|
-
return header + separator + '\n\n' + skillsContent;
|
|
182
|
+
const skillsContent = skills.map((s) => s.trim()).join('\n\n');
|
|
183
|
+
return header + skillsContent;
|
|
156
184
|
}
|
|
157
185
|
/**
|
|
158
186
|
* Parse skill reference from execution line
|
package/dist/services/timing.js
CHANGED
package/dist/skills/execute.md
CHANGED
|
@@ -139,6 +139,18 @@ Given tasks from this skill:
|
|
|
139
139
|
Do NOT invent different commands - use exactly what the skill specifies,
|
|
140
140
|
with parameter placeholders replaced by actual values.
|
|
141
141
|
|
|
142
|
+
### Handling Skipped Steps
|
|
143
|
+
|
|
144
|
+
**CRITICAL - STEP ORDER PRESERVATION**: When some steps from a skill are
|
|
145
|
+
omitted during scheduling, you MUST maintain alignment with the
|
|
146
|
+
original step positions in both the Steps and Execution sections. Each
|
|
147
|
+
task corresponds to a specific line number in the skill definition, NOT
|
|
148
|
+
to its sequential position in the task list. If you receive tasks for
|
|
149
|
+
steps 1 and 3 (with step 2 skipped), use Execution lines 1 and 3
|
|
150
|
+
(NOT lines 1 and 2). The step numbers in the task actions indicate
|
|
151
|
+
which Execution line to use - always match by original position, never
|
|
152
|
+
by sequential task index.
|
|
153
|
+
|
|
142
154
|
**CRITICAL - VERBATIM EXECUTION**: Run shell commands EXACTLY as written in
|
|
143
155
|
the ### Execution section. Do NOT:
|
|
144
156
|
- Modify the command string in any way
|
|
@@ -180,7 +192,6 @@ Return a structured response with commands to execute:
|
|
|
180
192
|
- **workdir**: Optional working directory for the command (defaults to
|
|
181
193
|
current)
|
|
182
194
|
- **timeout**: Optional timeout in milliseconds (defaults to 30000)
|
|
183
|
-
- **critical**: Whether failure should stop execution (defaults to true)
|
|
184
195
|
|
|
185
196
|
## Command Generation Guidelines
|
|
186
197
|
|
|
@@ -364,14 +375,12 @@ commands:
|
|
|
364
375
|
|
|
365
376
|
For complex multi-step operations:
|
|
366
377
|
|
|
367
|
-
1. **Sequential dependencies**:
|
|
368
|
-
|
|
378
|
+
1. **Sequential dependencies**: Commands execute in order; any failure stops
|
|
379
|
+
the chain
|
|
369
380
|
2. **Long-running processes**: Set appropriate timeouts (build processes
|
|
370
381
|
may need 10+ minutes)
|
|
371
382
|
3. **Working directories**: Use workdir to ensure commands run in the
|
|
372
383
|
right location
|
|
373
|
-
4. **Error handling**: For non-critical cleanup steps, set critical:
|
|
374
|
-
false
|
|
375
384
|
|
|
376
385
|
## Handling Config Placeholders
|
|
377
386
|
|
|
@@ -444,8 +453,7 @@ Before returning commands:
|
|
|
444
453
|
7. Check that all task params are incorporated
|
|
445
454
|
8. Ensure paths are properly quoted
|
|
446
455
|
9. Confirm timeouts are reasonable for each operation
|
|
447
|
-
10.
|
|
448
|
-
11. Review for any safety concerns
|
|
456
|
+
10. Review for any safety concerns
|
|
449
457
|
|
|
450
458
|
## Confirmed Schedule
|
|
451
459
|
|