prompt-language-shell 0.8.8 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/configuration/io.js +22 -1
- package/dist/{services/config-labels.js → configuration/labels.js} +1 -1
- package/dist/configuration/schema.js +2 -2
- package/dist/configuration/steps.js +171 -0
- package/dist/configuration/transformation.js +17 -0
- package/dist/configuration/types.js +3 -4
- package/dist/execution/handlers.js +20 -35
- package/dist/execution/hooks.js +291 -0
- package/dist/execution/processing.js +15 -2
- package/dist/execution/reducer.js +30 -48
- package/dist/execution/runner.js +81 -0
- package/dist/execution/types.js +1 -0
- package/dist/execution/utils.js +28 -0
- package/dist/services/components.js +109 -395
- package/dist/services/filesystem.js +21 -1
- package/dist/services/logger.js +3 -3
- package/dist/services/messages.js +10 -16
- package/dist/services/process.js +7 -2
- package/dist/services/refinement.js +5 -2
- package/dist/services/router.js +120 -67
- package/dist/services/shell.js +179 -10
- package/dist/services/skills.js +2 -1
- package/dist/skills/answer.md +14 -12
- package/dist/skills/execute.md +98 -39
- package/dist/skills/introspect.md +9 -9
- package/dist/skills/schedule.md +0 -6
- package/dist/types/errors.js +47 -0
- package/dist/types/result.js +40 -0
- package/dist/ui/Command.js +11 -7
- package/dist/ui/Component.js +6 -3
- package/dist/ui/Config.js +9 -3
- package/dist/ui/Execute.js +249 -163
- package/dist/ui/Introspect.js +13 -14
- package/dist/ui/List.js +2 -2
- package/dist/ui/Main.js +14 -7
- package/dist/ui/Output.js +54 -0
- package/dist/ui/Schedule.js +3 -1
- package/dist/ui/Subtask.js +6 -3
- package/dist/ui/Task.js +10 -85
- package/dist/ui/Validate.js +26 -21
- package/dist/ui/Workflow.js +21 -4
- package/package.json +1 -1
- package/dist/parser.js +0 -13
- package/dist/services/config-utils.js +0 -20
package/dist/services/router.js
CHANGED
|
@@ -2,10 +2,11 @@ import { asScheduledTasks } from '../types/guards.js';
|
|
|
2
2
|
import { FeedbackType, TaskType } from '../types/types.js';
|
|
3
3
|
import { saveConfig } from '../configuration/io.js';
|
|
4
4
|
import { getConfigSchema } from '../configuration/schema.js';
|
|
5
|
+
import { createConfigStepsFromSchema } from '../configuration/steps.js';
|
|
5
6
|
import { unflattenConfig } from '../configuration/transformation.js';
|
|
6
|
-
import { saveConfigLabels } from '
|
|
7
|
-
import {
|
|
8
|
-
import { getCancellationMessage, getMixedTaskTypesError, getUnknownRequestMessage, } from './messages.js';
|
|
7
|
+
import { saveConfigLabels } from '../configuration/labels.js';
|
|
8
|
+
import { createAnswer, createConfig, createConfirm, createExecute, createFeedback, createIntrospect, createMessage, createSchedule, createValidate, } from './components.js';
|
|
9
|
+
import { getCancellationMessage, getConfirmationMessage, getMixedTaskTypesError, getUnknownRequestMessage, } from './messages.js';
|
|
9
10
|
import { validateExecuteTasks } from './validator.js';
|
|
10
11
|
/**
|
|
11
12
|
* Determine the operation name based on task types
|
|
@@ -30,15 +31,22 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, life
|
|
|
30
31
|
const validTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
|
|
31
32
|
// Check if no valid tasks remain after filtering
|
|
32
33
|
if (validTasks.length === 0) {
|
|
33
|
-
const
|
|
34
|
-
workflowHandlers.addToQueue(
|
|
34
|
+
const msg = createMessage({ text: getUnknownRequestMessage() });
|
|
35
|
+
workflowHandlers.addToQueue(msg);
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
const operation = getOperationName(validTasks);
|
|
39
|
+
// Create routing context for downstream functions
|
|
40
|
+
const context = {
|
|
41
|
+
service,
|
|
42
|
+
userRequest,
|
|
43
|
+
workflowHandlers,
|
|
44
|
+
requestHandlers: requestHandlers,
|
|
45
|
+
};
|
|
38
46
|
if (hasDefineTask) {
|
|
39
47
|
// Has DEFINE tasks - add Schedule to queue for user selection
|
|
40
48
|
// Refinement flow will call this function again with refined tasks
|
|
41
|
-
const scheduleDefinition =
|
|
49
|
+
const scheduleDefinition = createSchedule({ message, tasks: validTasks });
|
|
42
50
|
workflowHandlers.addToQueue(scheduleDefinition);
|
|
43
51
|
}
|
|
44
52
|
else {
|
|
@@ -46,19 +54,27 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, life
|
|
|
46
54
|
// When Schedule activates, Command moves to timeline
|
|
47
55
|
// When Schedule completes, it moves to pending
|
|
48
56
|
// When Confirm activates, Schedule stays pending (visible for context)
|
|
49
|
-
const scheduleDefinition =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
const scheduleDefinition = createSchedule({
|
|
58
|
+
message,
|
|
59
|
+
tasks: validTasks,
|
|
60
|
+
onSelectionConfirmed: () => {
|
|
61
|
+
// Schedule completed - add Confirm to queue
|
|
62
|
+
const confirmDefinition = createConfirm({
|
|
63
|
+
message: getConfirmationMessage(),
|
|
64
|
+
onConfirmed: () => {
|
|
65
|
+
// User confirmed - complete both Confirm and Schedule, then route
|
|
66
|
+
lifecycleHandlers.completeActiveAndPending();
|
|
67
|
+
executeTasksAfterConfirm(validTasks, context);
|
|
68
|
+
},
|
|
69
|
+
onCancelled: () => {
|
|
70
|
+
// User cancelled - complete both Confirm and Schedule, then show cancellation
|
|
71
|
+
lifecycleHandlers.completeActiveAndPending();
|
|
72
|
+
const message = getCancellationMessage(operation);
|
|
73
|
+
workflowHandlers.addToQueue(createFeedback({ type: FeedbackType.Aborted, message }));
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
workflowHandlers.addToQueue(confirmDefinition);
|
|
77
|
+
},
|
|
62
78
|
});
|
|
63
79
|
workflowHandlers.addToQueue(scheduleDefinition);
|
|
64
80
|
}
|
|
@@ -91,7 +107,8 @@ function validateTaskTypes(tasks) {
|
|
|
91
107
|
* Validates task types and routes each type appropriately
|
|
92
108
|
* Supports mixed types at top level with Groups
|
|
93
109
|
*/
|
|
94
|
-
function executeTasksAfterConfirm(tasks,
|
|
110
|
+
function executeTasksAfterConfirm(tasks, context) {
|
|
111
|
+
const { service, userRequest, workflowHandlers, requestHandlers } = context;
|
|
95
112
|
// Validate task types (Groups must have uniform subtasks)
|
|
96
113
|
try {
|
|
97
114
|
validateTaskTypes(tasks);
|
|
@@ -126,18 +143,28 @@ function executeTasksAfterConfirm(tasks, service, userRequest, workflowHandlers,
|
|
|
126
143
|
.join('\n');
|
|
127
144
|
return `Invalid skill definition "${error.skill}":\n\n${issuesList}`;
|
|
128
145
|
});
|
|
129
|
-
workflowHandlers.addToQueue(createFeedback(
|
|
146
|
+
workflowHandlers.addToQueue(createFeedback({
|
|
147
|
+
type: FeedbackType.Failed,
|
|
148
|
+
message: errorMessages.join('\n\n'),
|
|
149
|
+
}));
|
|
130
150
|
return;
|
|
131
151
|
}
|
|
132
152
|
else if (validation.missingConfig.length > 0) {
|
|
133
153
|
// Missing config detected - create ONE Validate component for ALL missing config
|
|
134
|
-
workflowHandlers.addToQueue(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
workflowHandlers.addToQueue(createValidate({
|
|
155
|
+
missingConfig: validation.missingConfig,
|
|
156
|
+
userRequest,
|
|
157
|
+
service,
|
|
158
|
+
onError: (error) => {
|
|
159
|
+
requestHandlers.onError(error);
|
|
160
|
+
},
|
|
161
|
+
onValidationComplete: () => {
|
|
162
|
+
// After config is complete, resume task routing
|
|
163
|
+
routeTasksAfterConfig(scheduledTasks, context);
|
|
164
|
+
},
|
|
165
|
+
onAborted: (operation) => {
|
|
166
|
+
requestHandlers.onAborted(operation);
|
|
167
|
+
},
|
|
141
168
|
}));
|
|
142
169
|
return;
|
|
143
170
|
}
|
|
@@ -148,13 +175,13 @@ function executeTasksAfterConfirm(tasks, service, userRequest, workflowHandlers,
|
|
|
148
175
|
}
|
|
149
176
|
}
|
|
150
177
|
// No missing config - proceed with normal routing
|
|
151
|
-
routeTasksAfterConfig(scheduledTasks,
|
|
178
|
+
routeTasksAfterConfig(scheduledTasks, context);
|
|
152
179
|
}
|
|
153
180
|
/**
|
|
154
181
|
* Route tasks after config is complete (or when no config is needed)
|
|
155
182
|
* Processes tasks in order, grouping by type
|
|
156
183
|
*/
|
|
157
|
-
function routeTasksAfterConfig(scheduledTasks,
|
|
184
|
+
function routeTasksAfterConfig(scheduledTasks, context) {
|
|
158
185
|
// Process tasks in order, preserving Group boundaries
|
|
159
186
|
// Track consecutive standalone tasks to group them by type
|
|
160
187
|
let consecutiveStandaloneTasks = [];
|
|
@@ -174,7 +201,7 @@ function routeTasksAfterConfig(scheduledTasks, service, userRequest, workflowHan
|
|
|
174
201
|
const taskType = type;
|
|
175
202
|
if (typeTasks.length === 0)
|
|
176
203
|
continue;
|
|
177
|
-
routeTasksByType(taskType, typeTasks,
|
|
204
|
+
routeTasksByType(taskType, typeTasks, context);
|
|
178
205
|
}
|
|
179
206
|
consecutiveStandaloneTasks = [];
|
|
180
207
|
};
|
|
@@ -187,7 +214,7 @@ function routeTasksAfterConfig(scheduledTasks, service, userRequest, workflowHan
|
|
|
187
214
|
if (task.subtasks.length > 0) {
|
|
188
215
|
const subtasks = task.subtasks;
|
|
189
216
|
const taskType = subtasks[0].type;
|
|
190
|
-
routeTasksByType(taskType, subtasks,
|
|
217
|
+
routeTasksByType(taskType, subtasks, context);
|
|
191
218
|
}
|
|
192
219
|
}
|
|
193
220
|
else {
|
|
@@ -199,38 +226,42 @@ function routeTasksAfterConfig(scheduledTasks, service, userRequest, workflowHan
|
|
|
199
226
|
processStandaloneTasks();
|
|
200
227
|
}
|
|
201
228
|
/**
|
|
202
|
-
* Route tasks
|
|
203
|
-
|
|
229
|
+
* Route Answer tasks - creates separate Answer component for each question
|
|
230
|
+
*/
|
|
231
|
+
function routeAnswerTasks(tasks, context) {
|
|
232
|
+
for (const task of tasks) {
|
|
233
|
+
context.workflowHandlers.addToQueue(createAnswer({ question: task.action, service: context.service }));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Route Introspect tasks - creates single Introspect component for all tasks
|
|
238
|
+
*/
|
|
239
|
+
function routeIntrospectTasks(tasks, context) {
|
|
240
|
+
context.workflowHandlers.addToQueue(createIntrospect({ tasks, service: context.service }));
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Route Config tasks - extracts keys, caches labels, creates Config component
|
|
204
244
|
*/
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
function routeConfigTasks(tasks, context) {
|
|
246
|
+
const configKeys = tasks
|
|
247
|
+
.map((task) => task.params?.key)
|
|
248
|
+
.filter((key) => key !== undefined);
|
|
249
|
+
// Extract and cache labels from task descriptions
|
|
250
|
+
// Only cache labels for dynamically discovered keys (not in schema)
|
|
251
|
+
const schema = getConfigSchema();
|
|
252
|
+
const labels = {};
|
|
253
|
+
for (const task of tasks) {
|
|
254
|
+
const key = task.params?.key;
|
|
255
|
+
if (key && task.action && !(key in schema)) {
|
|
256
|
+
labels[key] = task.action;
|
|
210
257
|
}
|
|
211
258
|
}
|
|
212
|
-
|
|
213
|
-
|
|
259
|
+
if (Object.keys(labels).length > 0) {
|
|
260
|
+
saveConfigLabels(labels);
|
|
214
261
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
.map((task) => task.params?.key)
|
|
219
|
-
.filter((key) => key !== undefined);
|
|
220
|
-
// Extract and cache labels from task descriptions
|
|
221
|
-
// Only cache labels for dynamically discovered keys (not in schema)
|
|
222
|
-
const schema = getConfigSchema();
|
|
223
|
-
const labels = {};
|
|
224
|
-
for (const task of typeTasks) {
|
|
225
|
-
const key = task.params?.key;
|
|
226
|
-
if (key && task.action && !(key in schema)) {
|
|
227
|
-
labels[key] = task.action;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (Object.keys(labels).length > 0) {
|
|
231
|
-
saveConfigLabels(labels);
|
|
232
|
-
}
|
|
233
|
-
workflowHandlers.addToQueue(createConfigDefinitionWithKeys(configKeys, (config) => {
|
|
262
|
+
context.workflowHandlers.addToQueue(createConfig({
|
|
263
|
+
steps: createConfigStepsFromSchema(configKeys),
|
|
264
|
+
onFinished: (config) => {
|
|
234
265
|
// Save config - Config component will handle completion and feedback
|
|
235
266
|
try {
|
|
236
267
|
// Convert flat dotted keys to nested structure grouped by section
|
|
@@ -246,12 +277,34 @@ function routeTasksByType(taskType, typeTasks, service, userRequest, workflowHan
|
|
|
246
277
|
: 'Failed to save configuration';
|
|
247
278
|
throw new Error(errorMessage);
|
|
248
279
|
}
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
280
|
+
},
|
|
281
|
+
onAborted: (operation) => {
|
|
282
|
+
context.requestHandlers.onAborted(operation);
|
|
283
|
+
},
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Route Execute tasks - creates Execute component (validation already done)
|
|
288
|
+
*/
|
|
289
|
+
function routeExecuteTasks(tasks, context) {
|
|
290
|
+
context.workflowHandlers.addToQueue(createExecute({ tasks, service: context.service }));
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Registry mapping task types to their route handlers
|
|
294
|
+
*/
|
|
295
|
+
const taskRouteHandlers = {
|
|
296
|
+
[TaskType.Answer]: routeAnswerTasks,
|
|
297
|
+
[TaskType.Introspect]: routeIntrospectTasks,
|
|
298
|
+
[TaskType.Config]: routeConfigTasks,
|
|
299
|
+
[TaskType.Execute]: routeExecuteTasks,
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Route tasks by type to appropriate components
|
|
303
|
+
* Uses registry pattern for extensibility
|
|
304
|
+
*/
|
|
305
|
+
function routeTasksByType(taskType, tasks, context) {
|
|
306
|
+
const handler = taskRouteHandlers[taskType];
|
|
307
|
+
if (handler) {
|
|
308
|
+
handler(tasks, context);
|
|
256
309
|
}
|
|
257
310
|
}
|
package/dist/services/shell.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
1
2
|
export var ExecutionStatus;
|
|
2
3
|
(function (ExecutionStatus) {
|
|
3
4
|
ExecutionStatus["Pending"] = "pending";
|
|
@@ -58,18 +59,186 @@ export class DummyExecutor {
|
|
|
58
59
|
});
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
// Marker for extracting pwd from command output
|
|
63
|
+
const PWD_MARKER = '__PWD_MARKER_7x9k2m__';
|
|
61
64
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* - Spawns process with cmd.command in shell mode using child_process.spawn()
|
|
65
|
-
* - Sets working directory from cmd.workdir
|
|
66
|
-
* - Handles cmd.timeout for command timeout
|
|
67
|
-
* - Captures stdout and stderr streams
|
|
68
|
-
* - Calls onProgress with Running/Success/Failed status
|
|
69
|
-
* - Returns CommandOutput with actual stdout, stderr, exitCode
|
|
70
|
-
* - Handles errors (spawn failures, timeouts, non-zero exit codes)
|
|
65
|
+
* Parse stdout to extract workdir and clean output.
|
|
66
|
+
* Returns the cleaned output and the extracted workdir.
|
|
71
67
|
*/
|
|
72
|
-
|
|
68
|
+
function parseWorkdir(rawOutput) {
|
|
69
|
+
const markerIndex = rawOutput.lastIndexOf(PWD_MARKER);
|
|
70
|
+
if (markerIndex === -1) {
|
|
71
|
+
return { output: rawOutput };
|
|
72
|
+
}
|
|
73
|
+
const output = rawOutput.slice(0, markerIndex).trimEnd();
|
|
74
|
+
const pwdPart = rawOutput.slice(markerIndex + PWD_MARKER.length).trim();
|
|
75
|
+
const lines = pwdPart.split('\n').filter((l) => l.trim());
|
|
76
|
+
const workdir = lines[0];
|
|
77
|
+
return { output, workdir };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Manages streaming output while filtering out the PWD marker.
|
|
81
|
+
* Buffers output to avoid emitting partial markers to the callback.
|
|
82
|
+
*/
|
|
83
|
+
class OutputStreamer {
|
|
84
|
+
chunks = [];
|
|
85
|
+
emittedLength = 0;
|
|
86
|
+
callback;
|
|
87
|
+
constructor(callback) {
|
|
88
|
+
this.callback = callback;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Add new stdout data and emit safe content to callback.
|
|
92
|
+
* Buffers data to avoid emitting partial PWD markers.
|
|
93
|
+
*/
|
|
94
|
+
pushStdout(data) {
|
|
95
|
+
this.chunks.push(data);
|
|
96
|
+
if (!this.callback)
|
|
97
|
+
return;
|
|
98
|
+
const accumulated = this.chunks.join('');
|
|
99
|
+
const markerIndex = accumulated.indexOf(PWD_MARKER);
|
|
100
|
+
if (markerIndex !== -1) {
|
|
101
|
+
// Marker found - emit everything before it (trimmed)
|
|
102
|
+
this.emitUpTo(accumulated.slice(0, markerIndex).trimEnd().length);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// No marker yet - emit all but buffer for potential partial marker
|
|
106
|
+
const bufferSize = PWD_MARKER.length + 5;
|
|
107
|
+
const safeLength = Math.max(this.emittedLength, accumulated.length - bufferSize);
|
|
108
|
+
this.emitUpTo(safeLength);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Emit content up to the specified length if there's new content.
|
|
113
|
+
*/
|
|
114
|
+
emitUpTo(length) {
|
|
115
|
+
if (length > this.emittedLength && this.callback) {
|
|
116
|
+
const accumulated = this.chunks.join('');
|
|
117
|
+
const newContent = accumulated.slice(this.emittedLength, length);
|
|
118
|
+
this.callback(newContent, 'stdout');
|
|
119
|
+
this.emittedLength = length;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get the accumulated raw output.
|
|
124
|
+
*/
|
|
125
|
+
getAccumulated() {
|
|
126
|
+
return this.chunks.join('');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Real executor that spawns shell processes and captures output.
|
|
131
|
+
*/
|
|
132
|
+
export class RealExecutor {
|
|
133
|
+
outputCallback;
|
|
134
|
+
constructor(outputCallback) {
|
|
135
|
+
this.outputCallback = outputCallback;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Set or update the output callback
|
|
139
|
+
*/
|
|
140
|
+
setOutputCallback(callback) {
|
|
141
|
+
this.outputCallback = callback;
|
|
142
|
+
}
|
|
143
|
+
execute(cmd, onProgress, _ = 0) {
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
onProgress?.(ExecutionStatus.Running);
|
|
146
|
+
const stderr = [];
|
|
147
|
+
// Wrap command to capture final working directory
|
|
148
|
+
const wrappedCommand = `${cmd.command}; __exit=$?; echo ""; echo "${PWD_MARKER}"; pwd; exit $__exit`;
|
|
149
|
+
// Wrap spawn in try/catch to handle synchronous errors
|
|
150
|
+
let child;
|
|
151
|
+
try {
|
|
152
|
+
child = spawn(wrappedCommand, {
|
|
153
|
+
shell: true,
|
|
154
|
+
cwd: cmd.workdir || process.cwd(),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to spawn process';
|
|
159
|
+
const commandResult = {
|
|
160
|
+
description: cmd.description,
|
|
161
|
+
command: cmd.command,
|
|
162
|
+
output: '',
|
|
163
|
+
errors: errorMessage,
|
|
164
|
+
result: ExecutionResult.Error,
|
|
165
|
+
error: errorMessage,
|
|
166
|
+
};
|
|
167
|
+
onProgress?.(ExecutionStatus.Failed);
|
|
168
|
+
resolve(commandResult);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Handle timeout if specified
|
|
172
|
+
const SIGKILL_GRACE_PERIOD = 3000;
|
|
173
|
+
let timeoutId;
|
|
174
|
+
let killTimeoutId;
|
|
175
|
+
if (cmd.timeout && cmd.timeout > 0) {
|
|
176
|
+
timeoutId = setTimeout(() => {
|
|
177
|
+
child.kill('SIGTERM');
|
|
178
|
+
// Escalate to SIGKILL if process doesn't terminate
|
|
179
|
+
killTimeoutId = setTimeout(() => {
|
|
180
|
+
child.kill('SIGKILL');
|
|
181
|
+
}, SIGKILL_GRACE_PERIOD);
|
|
182
|
+
}, cmd.timeout);
|
|
183
|
+
}
|
|
184
|
+
// Use OutputStreamer for buffered stdout streaming
|
|
185
|
+
const stdoutStreamer = new OutputStreamer(this.outputCallback);
|
|
186
|
+
child.stdout.on('data', (data) => {
|
|
187
|
+
stdoutStreamer.pushStdout(data.toString());
|
|
188
|
+
});
|
|
189
|
+
child.stderr.on('data', (data) => {
|
|
190
|
+
const text = data.toString();
|
|
191
|
+
stderr.push(text);
|
|
192
|
+
this.outputCallback?.(text, 'stderr');
|
|
193
|
+
});
|
|
194
|
+
child.on('error', (error) => {
|
|
195
|
+
if (timeoutId)
|
|
196
|
+
clearTimeout(timeoutId);
|
|
197
|
+
if (killTimeoutId)
|
|
198
|
+
clearTimeout(killTimeoutId);
|
|
199
|
+
const commandResult = {
|
|
200
|
+
description: cmd.description,
|
|
201
|
+
command: cmd.command,
|
|
202
|
+
output: stdoutStreamer.getAccumulated(),
|
|
203
|
+
errors: error.message,
|
|
204
|
+
result: ExecutionResult.Error,
|
|
205
|
+
error: error.message,
|
|
206
|
+
};
|
|
207
|
+
onProgress?.(ExecutionStatus.Failed);
|
|
208
|
+
resolve(commandResult);
|
|
209
|
+
});
|
|
210
|
+
child.on('close', (code) => {
|
|
211
|
+
if (timeoutId)
|
|
212
|
+
clearTimeout(timeoutId);
|
|
213
|
+
if (killTimeoutId)
|
|
214
|
+
clearTimeout(killTimeoutId);
|
|
215
|
+
const success = code === 0;
|
|
216
|
+
const { output, workdir } = parseWorkdir(stdoutStreamer.getAccumulated());
|
|
217
|
+
const commandResult = {
|
|
218
|
+
description: cmd.description,
|
|
219
|
+
command: cmd.command,
|
|
220
|
+
output,
|
|
221
|
+
errors: stderr.join(''),
|
|
222
|
+
result: success ? ExecutionResult.Success : ExecutionResult.Error,
|
|
223
|
+
error: success ? undefined : `Exit code: ${code}`,
|
|
224
|
+
workdir,
|
|
225
|
+
};
|
|
226
|
+
onProgress?.(success ? ExecutionStatus.Success : ExecutionStatus.Failed);
|
|
227
|
+
resolve(commandResult);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Create real executor instance
|
|
233
|
+
const realExecutor = new RealExecutor();
|
|
234
|
+
// Default executor for production use
|
|
235
|
+
const executor = realExecutor;
|
|
236
|
+
/**
|
|
237
|
+
* Set a callback to receive command output in real-time
|
|
238
|
+
*/
|
|
239
|
+
export function setOutputCallback(callback) {
|
|
240
|
+
realExecutor.setOutputCallback(callback);
|
|
241
|
+
}
|
|
73
242
|
/**
|
|
74
243
|
* Execute a single shell command
|
|
75
244
|
*/
|
package/dist/services/skills.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir } from 'os';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { AppError, ErrorCode } from '../types/errors.js';
|
|
3
4
|
import { defaultFileSystem } from './filesystem.js';
|
|
4
5
|
import { displayWarning } from './logger.js';
|
|
5
6
|
import { getUnknownSkillMessage } from './messages.js';
|
|
@@ -189,7 +190,7 @@ export function expandSkillReferences(execution, skillLookup, visited = new Set(
|
|
|
189
190
|
}
|
|
190
191
|
// Check for circular reference
|
|
191
192
|
if (visited.has(skillName)) {
|
|
192
|
-
throw new
|
|
193
|
+
throw new AppError(`Circular skill reference detected: ${Array.from(visited).join(' → ')} → ${skillName}`, ErrorCode.CircularReference);
|
|
193
194
|
}
|
|
194
195
|
// Second: Match against skill name
|
|
195
196
|
const skill = skillLookup(skillName);
|
package/dist/skills/answer.md
CHANGED
|
@@ -110,15 +110,17 @@ They enable cleaner, more reusable component logic.
|
|
|
110
110
|
|
|
111
111
|
## Common Mistakes to Avoid
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
**DO NOT:**
|
|
114
|
+
- Start with "Here's the answer:" or "Let me explain:"
|
|
115
|
+
- Exceed 4 lines or 80 characters per line
|
|
116
|
+
- Include unnecessary details
|
|
117
|
+
- Use overly technical jargon without explanation
|
|
118
|
+
- Repeat the question in the answer
|
|
119
|
+
- Use citation tags like `<cite>` or any HTML/XML markup
|
|
120
|
+
|
|
121
|
+
**DO:**
|
|
122
|
+
- Give direct, concise answers
|
|
123
|
+
- Use proper line breaks at natural phrase boundaries
|
|
124
|
+
- Include essential information only
|
|
125
|
+
- Use clear, accessible language
|
|
126
|
+
- Use plain text only - no markup tags
|