prompt-language-shell 0.9.8 → 1.0.0
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 +97 -40
- package/dist/components/Component.js +5 -3
- package/dist/components/controllers/Command.js +3 -19
- package/dist/components/controllers/Config.js +109 -75
- package/dist/components/controllers/Execute.js +14 -11
- package/dist/components/controllers/Introspect.js +2 -4
- package/dist/components/controllers/Validate.js +3 -2
- package/dist/components/views/Execute.js +1 -1
- package/dist/components/views/Output.js +84 -32
- package/dist/components/views/Subtask.js +12 -7
- package/dist/components/views/Task.js +4 -5
- package/dist/components/views/Upcoming.js +1 -1
- package/dist/execution/runner.js +45 -29
- package/dist/services/anthropic.js +5 -4
- package/dist/services/logger.js +37 -9
- package/dist/services/monitor.js +19 -3
- package/dist/services/refinement.js +1 -7
- package/dist/services/router.js +223 -95
- package/dist/services/shell.js +18 -3
- package/dist/services/utils.js +11 -0
- package/dist/skills/schedule.md +7 -0
- package/package.json +10 -10
package/dist/services/router.js
CHANGED
|
@@ -8,6 +8,20 @@ import { saveConfigLabels } from '../configuration/labels.js';
|
|
|
8
8
|
import { createAnswer, createConfig, createConfirm, createExecute, createFeedback, createIntrospect, createSchedule, createValidate, } from './components.js';
|
|
9
9
|
import { getCancellationMessage, getConfirmationMessage, getUnknownRequestMessage, } from './messages.js';
|
|
10
10
|
import { validateExecuteTasks } from './validator.js';
|
|
11
|
+
/**
|
|
12
|
+
* Task Routing Architecture
|
|
13
|
+
*
|
|
14
|
+
* Flow: Command -> SCHEDULE -> routeTasksWithConfirm() -> extractTaskGroups()
|
|
15
|
+
* -> routeAllGroups() -> routeGroupTasks() -> Workflow queue
|
|
16
|
+
*
|
|
17
|
+
* Key Concepts:
|
|
18
|
+
* - TaskGroup: Logical grouping for sequential processing
|
|
19
|
+
* - Routing Category: Determines which tasks can be grouped together
|
|
20
|
+
* - Two-Phase Routing: Config/Introspect first, then Execute/Answer
|
|
21
|
+
*
|
|
22
|
+
* Isolation Principle: Explicit Group tasks always become their own
|
|
23
|
+
* TaskGroup, preventing cross-contamination between user-defined groups.
|
|
24
|
+
*/
|
|
11
25
|
/**
|
|
12
26
|
* Flatten inner task structure completely - removes all nested groups.
|
|
13
27
|
* Used internally to flatten subtasks within a top-level group.
|
|
@@ -92,10 +106,10 @@ export function getOperationName(tasks) {
|
|
|
92
106
|
export function routeTasksWithConfirm(tasks, message, service, userRequest, lifecycleHandlers, workflowHandlers, requestHandlers, hasDefineTask = false) {
|
|
93
107
|
if (tasks.length === 0)
|
|
94
108
|
return;
|
|
95
|
-
//
|
|
96
|
-
const
|
|
97
|
-
// Check if no
|
|
98
|
-
if (
|
|
109
|
+
// Check executable tasks (ignore/discard are shown but not executed)
|
|
110
|
+
const executableTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
|
|
111
|
+
// Check if no executable tasks remain after filtering
|
|
112
|
+
if (executableTasks.length === 0) {
|
|
99
113
|
// Use action from first ignore task if available, otherwise generic message
|
|
100
114
|
const ignoreTask = tasks.find((task) => task.type === TaskType.Ignore);
|
|
101
115
|
const message = ignoreTask?.action
|
|
@@ -104,7 +118,7 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, life
|
|
|
104
118
|
workflowHandlers.addToQueue(createFeedback({ type: FeedbackType.Warning, message }));
|
|
105
119
|
return;
|
|
106
120
|
}
|
|
107
|
-
const operation = getOperationName(
|
|
121
|
+
const operation = getOperationName(executableTasks);
|
|
108
122
|
// Create routing context for downstream functions
|
|
109
123
|
const context = {
|
|
110
124
|
service,
|
|
@@ -115,7 +129,8 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, life
|
|
|
115
129
|
if (hasDefineTask) {
|
|
116
130
|
// Has DEFINE tasks - add Schedule to queue for user selection
|
|
117
131
|
// Refinement flow will call this function again with refined tasks
|
|
118
|
-
|
|
132
|
+
// Show all tasks (including ignore) for display
|
|
133
|
+
const scheduleDefinition = createSchedule({ message, tasks });
|
|
119
134
|
workflowHandlers.addToQueue(scheduleDefinition);
|
|
120
135
|
}
|
|
121
136
|
else {
|
|
@@ -123,9 +138,10 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, life
|
|
|
123
138
|
// When Schedule activates, Command moves to timeline
|
|
124
139
|
// When Schedule completes, it moves to pending
|
|
125
140
|
// When Confirm activates, Schedule stays pending (visible for context)
|
|
141
|
+
// Show all tasks (including ignore) for display
|
|
126
142
|
const scheduleDefinition = createSchedule({
|
|
127
143
|
message,
|
|
128
|
-
tasks
|
|
144
|
+
tasks,
|
|
129
145
|
onSelectionConfirmed: () => {
|
|
130
146
|
// Schedule completed - add Confirm to queue
|
|
131
147
|
const confirmDefinition = createConfirm({
|
|
@@ -133,7 +149,8 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, life
|
|
|
133
149
|
onConfirmed: () => {
|
|
134
150
|
// User confirmed - complete both Confirm and Schedule, then route
|
|
135
151
|
lifecycleHandlers.completeActiveAndPending();
|
|
136
|
-
|
|
152
|
+
// Only execute non-ignore/non-discard tasks
|
|
153
|
+
executeTasksAfterConfirm(executableTasks, context);
|
|
137
154
|
},
|
|
138
155
|
onCancelled: () => {
|
|
139
156
|
// User cancelled - complete both Confirm and Schedule, then show cancellation
|
|
@@ -230,88 +247,187 @@ function executeTasksAfterConfirm(tasks, context) {
|
|
|
230
247
|
routeTasksAfterConfig(flatTasks, context);
|
|
231
248
|
}
|
|
232
249
|
/**
|
|
233
|
-
*
|
|
250
|
+
* Collect action names for upcoming display.
|
|
251
|
+
* All task types are shown so users see the full queue of work ahead.
|
|
234
252
|
*/
|
|
235
|
-
|
|
253
|
+
function collectUpcomingNames(tasks) {
|
|
254
|
+
return tasks.map((t) => t.action);
|
|
255
|
+
}
|
|
236
256
|
/**
|
|
237
|
-
*
|
|
238
|
-
*
|
|
257
|
+
* Get the routing category for a task type.
|
|
258
|
+
* Tasks in the same category can be grouped together.
|
|
259
|
+
* Config and Introspect are special categories that should be isolated.
|
|
239
260
|
*/
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
.
|
|
261
|
+
export function getRoutingCategory(task) {
|
|
262
|
+
// Groups with subtasks get their own category (based on subtask types)
|
|
263
|
+
if (task.type === TaskType.Group &&
|
|
264
|
+
task.subtasks &&
|
|
265
|
+
task.subtasks.length > 0) {
|
|
266
|
+
// Check what types of subtasks this group has
|
|
267
|
+
const hasConfig = task.subtasks.some((t) => t.type === TaskType.Config);
|
|
268
|
+
const hasIntrospect = task.subtasks.some((t) => t.type === TaskType.Introspect);
|
|
269
|
+
const hasExecute = task.subtasks.some((t) => t.type === TaskType.Execute);
|
|
270
|
+
const hasAnswer = task.subtasks.some((t) => t.type === TaskType.Answer);
|
|
271
|
+
// Mixed types get unique category to ensure isolation
|
|
272
|
+
const typeCount = (hasConfig ? 1 : 0) +
|
|
273
|
+
(hasIntrospect ? 1 : 0) +
|
|
274
|
+
(hasExecute ? 1 : 0) +
|
|
275
|
+
(hasAnswer ? 1 : 0);
|
|
276
|
+
if (typeCount > 1) {
|
|
277
|
+
return `mixed:${task.action}`;
|
|
278
|
+
}
|
|
279
|
+
// Single type groups use that type's category
|
|
280
|
+
if (hasConfig)
|
|
281
|
+
return 'config';
|
|
282
|
+
if (hasIntrospect)
|
|
283
|
+
return 'introspect';
|
|
284
|
+
if (hasExecute)
|
|
285
|
+
return 'execute';
|
|
286
|
+
if (hasAnswer)
|
|
287
|
+
return 'answer';
|
|
288
|
+
return 'group';
|
|
289
|
+
}
|
|
290
|
+
// Standalone tasks use their type as category
|
|
291
|
+
switch (task.type) {
|
|
292
|
+
case TaskType.Config:
|
|
293
|
+
return 'config';
|
|
294
|
+
case TaskType.Introspect:
|
|
295
|
+
return 'introspect';
|
|
296
|
+
case TaskType.Execute:
|
|
297
|
+
return 'execute';
|
|
298
|
+
case TaskType.Answer:
|
|
299
|
+
return 'answer';
|
|
300
|
+
default:
|
|
301
|
+
return task.type;
|
|
302
|
+
}
|
|
244
303
|
}
|
|
245
304
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* Config tasks are grouped together; Execute/Answer are routed individually.
|
|
305
|
+
* Extract logical task groups from a flat task list.
|
|
306
|
+
* Each explicit Group (TaskType.Group) becomes its own TaskGroup for isolation.
|
|
307
|
+
* Consecutive standalone tasks of the same routing category are grouped together.
|
|
250
308
|
*/
|
|
251
|
-
function
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
309
|
+
export function extractTaskGroups(tasks) {
|
|
310
|
+
const groups = [];
|
|
311
|
+
let currentGroup = null;
|
|
312
|
+
let currentCategory = null;
|
|
313
|
+
for (const task of tasks) {
|
|
314
|
+
// Skip empty groups
|
|
315
|
+
if (task.type === TaskType.Group &&
|
|
316
|
+
(!task.subtasks || task.subtasks.length === 0)) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
// Explicit Groups always become their own TaskGroup for isolation
|
|
320
|
+
if (task.type === TaskType.Group) {
|
|
321
|
+
// Save current group if exists
|
|
322
|
+
if (currentGroup !== null) {
|
|
323
|
+
groups.push(currentGroup);
|
|
324
|
+
currentGroup = null;
|
|
325
|
+
currentCategory = null;
|
|
265
326
|
}
|
|
266
|
-
|
|
267
|
-
|
|
327
|
+
// Create standalone TaskGroup for this Group
|
|
328
|
+
groups.push({
|
|
329
|
+
name: task.action,
|
|
330
|
+
tasks: [task],
|
|
331
|
+
});
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const category = getRoutingCategory(task);
|
|
335
|
+
// Start new group when category changes (or first task)
|
|
336
|
+
if (currentGroup === null || category !== currentCategory) {
|
|
337
|
+
if (currentGroup !== null) {
|
|
338
|
+
groups.push(currentGroup);
|
|
268
339
|
}
|
|
340
|
+
currentGroup = {
|
|
341
|
+
name: task.action,
|
|
342
|
+
tasks: [task],
|
|
343
|
+
};
|
|
344
|
+
currentCategory = category;
|
|
269
345
|
}
|
|
270
|
-
|
|
271
|
-
|
|
346
|
+
else {
|
|
347
|
+
// Same category - add to current group
|
|
348
|
+
currentGroup.tasks.push(task);
|
|
272
349
|
}
|
|
273
350
|
}
|
|
274
|
-
//
|
|
351
|
+
// Don't forget the last group
|
|
352
|
+
if (currentGroup !== null) {
|
|
353
|
+
groups.push(currentGroup);
|
|
354
|
+
}
|
|
355
|
+
return groups;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Route all groups in order, calculating correct upcoming for each.
|
|
359
|
+
* Groups are processed sequentially by the Workflow's queue mechanism.
|
|
360
|
+
*/
|
|
361
|
+
function routeAllGroups(groups, context) {
|
|
362
|
+
for (let i = 0; i < groups.length; i++) {
|
|
363
|
+
const group = groups[i];
|
|
364
|
+
// Calculate upcoming from LATER groups (not current group)
|
|
365
|
+
const laterGroups = groups.slice(i + 1);
|
|
366
|
+
const laterUpcoming = laterGroups.flatMap((g) => collectUpcomingNames(g.tasks));
|
|
367
|
+
// Route this group's tasks with upcoming from later groups
|
|
368
|
+
routeGroupTasks(group, context, laterUpcoming);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Route all tasks within a single group in the order received from LLM.
|
|
373
|
+
* Standalone tasks become individual components.
|
|
374
|
+
* Group subtasks are batched by type (Execute subtasks together, etc.).
|
|
375
|
+
*/
|
|
376
|
+
function routeGroupTasks(group, context, groupUpcoming) {
|
|
377
|
+
const tasks = group.tasks;
|
|
378
|
+
const withinGroupUpcoming = collectUpcomingNames(tasks);
|
|
275
379
|
for (let i = 0; i < tasks.length; i++) {
|
|
276
380
|
const task = tasks[i];
|
|
277
381
|
const taskType = task.type;
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
382
|
+
// Calculate upcoming: remaining tasks in this group + tasks from later groups
|
|
383
|
+
const remainingInGroup = withinGroupUpcoming.slice(i + 1);
|
|
384
|
+
const taskUpcoming = [...remainingInGroup, ...groupUpcoming];
|
|
385
|
+
// Handle Group tasks with subtasks - batch subtasks by type
|
|
281
386
|
if (taskType === TaskType.Group && task.subtasks) {
|
|
282
|
-
//
|
|
283
|
-
const upcoming = allUpcomingNames.slice(upcomingIndex + 1);
|
|
284
|
-
upcomingIndex++;
|
|
285
|
-
// Separate subtasks by type
|
|
387
|
+
// Batch Execute subtasks together (sent to LLM as single request)
|
|
286
388
|
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
389
|
if (executeSubtasks.length > 0) {
|
|
290
|
-
routeExecuteTasks(executeSubtasks, context,
|
|
390
|
+
routeExecuteTasks(executeSubtasks, context, taskUpcoming, task.action);
|
|
291
391
|
}
|
|
292
|
-
// Route Answer subtasks
|
|
392
|
+
// Route Answer subtasks (each becomes its own component)
|
|
393
|
+
const answerSubtasks = task.subtasks.filter((t) => t.type === TaskType.Answer);
|
|
293
394
|
if (answerSubtasks.length > 0) {
|
|
294
|
-
routeAnswerTasks(answerSubtasks, context,
|
|
395
|
+
routeAnswerTasks(answerSubtasks, context, taskUpcoming);
|
|
396
|
+
}
|
|
397
|
+
// Route other subtask types individually
|
|
398
|
+
for (const subtask of task.subtasks) {
|
|
399
|
+
if (subtask.type !== TaskType.Execute &&
|
|
400
|
+
subtask.type !== TaskType.Answer) {
|
|
401
|
+
routeTasksByType(subtask.type, [subtask], context, taskUpcoming);
|
|
402
|
+
}
|
|
295
403
|
}
|
|
296
404
|
}
|
|
297
405
|
else if (taskType === TaskType.Execute) {
|
|
298
|
-
|
|
299
|
-
const upcoming = allUpcomingNames.slice(upcomingIndex + 1);
|
|
300
|
-
upcomingIndex++;
|
|
301
|
-
routeExecuteTasks([task], context, upcoming);
|
|
406
|
+
routeExecuteTasks([task], context, taskUpcoming);
|
|
302
407
|
}
|
|
303
408
|
else if (taskType === TaskType.Answer) {
|
|
304
|
-
|
|
305
|
-
const upcoming = allUpcomingNames.slice(upcomingIndex + 1);
|
|
306
|
-
upcomingIndex++;
|
|
307
|
-
routeTasksByType(taskType, [task], context, upcoming);
|
|
409
|
+
routeAnswerTasks([task], context, taskUpcoming);
|
|
308
410
|
}
|
|
309
411
|
else {
|
|
310
|
-
|
|
311
|
-
routeTasksByType(taskType, [task], context, []);
|
|
412
|
+
routeTasksByType(taskType, [task], context, taskUpcoming);
|
|
312
413
|
}
|
|
313
414
|
}
|
|
314
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Route tasks after config is complete (or when no config is needed)
|
|
418
|
+
* Processes task groups in order - groups with different task types are
|
|
419
|
+
* kept separate to ensure proper lifecycle handling.
|
|
420
|
+
*/
|
|
421
|
+
function routeTasksAfterConfig(tasks, context) {
|
|
422
|
+
if (tasks.length === 0)
|
|
423
|
+
return;
|
|
424
|
+
// Extract logical task groups
|
|
425
|
+
const groups = extractTaskGroups(tasks);
|
|
426
|
+
if (groups.length === 0)
|
|
427
|
+
return;
|
|
428
|
+
// Route all groups in order
|
|
429
|
+
routeAllGroups(groups, context);
|
|
430
|
+
}
|
|
315
431
|
/**
|
|
316
432
|
* Route Answer tasks - creates separate Answer component for each question
|
|
317
433
|
*/
|
|
@@ -335,48 +451,60 @@ function routeIntrospectTasks(tasks, context, _upcoming) {
|
|
|
335
451
|
context.workflowHandlers.addToQueue(createIntrospect({ tasks, service: context.service }));
|
|
336
452
|
}
|
|
337
453
|
/**
|
|
338
|
-
* Route Config tasks - extracts keys
|
|
454
|
+
* Route Config tasks - extracts keys or uses query, creates Config component
|
|
339
455
|
*/
|
|
340
456
|
function routeConfigTasks(tasks, context, _upcoming) {
|
|
457
|
+
// Extract specific keys from task params
|
|
341
458
|
const configKeys = tasks
|
|
342
459
|
.map((task) => task.params?.key)
|
|
343
460
|
.filter((key) => key !== undefined);
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
461
|
+
// Handler for saving config values
|
|
462
|
+
const onFinished = (config) => {
|
|
463
|
+
try {
|
|
464
|
+
const configBySection = unflattenConfig(config);
|
|
465
|
+
for (const [section, sectionConfig] of Object.entries(configBySection)) {
|
|
466
|
+
saveConfig(section, sectionConfig);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to save configuration';
|
|
471
|
+
throw new Error(errorMessage);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
const onAborted = (operation) => {
|
|
475
|
+
context.requestHandlers.onAborted(operation);
|
|
476
|
+
};
|
|
477
|
+
if (configKeys.length > 0) {
|
|
478
|
+
// Has specific keys - create steps directly
|
|
479
|
+
const schema = getConfigSchema();
|
|
480
|
+
const labels = {};
|
|
481
|
+
for (const task of tasks) {
|
|
482
|
+
const key = task.params?.key;
|
|
483
|
+
if (key && task.action && !(key in schema)) {
|
|
484
|
+
labels[key] = task.action;
|
|
485
|
+
}
|
|
352
486
|
}
|
|
487
|
+
if (Object.keys(labels).length > 0) {
|
|
488
|
+
saveConfigLabels(labels);
|
|
489
|
+
}
|
|
490
|
+
context.workflowHandlers.addToQueue(createConfig({
|
|
491
|
+
steps: createConfigStepsFromSchema(configKeys),
|
|
492
|
+
onFinished,
|
|
493
|
+
onAborted,
|
|
494
|
+
}));
|
|
353
495
|
}
|
|
354
|
-
|
|
355
|
-
|
|
496
|
+
else {
|
|
497
|
+
// No keys - use query (Config will resolve via CONFIGURE tool)
|
|
498
|
+
const query = tasks[0]?.params?.query;
|
|
499
|
+
if (query) {
|
|
500
|
+
context.workflowHandlers.addToQueue(createConfig({
|
|
501
|
+
query,
|
|
502
|
+
service: context.service,
|
|
503
|
+
onFinished,
|
|
504
|
+
onAborted,
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
356
507
|
}
|
|
357
|
-
context.workflowHandlers.addToQueue(createConfig({
|
|
358
|
-
steps: createConfigStepsFromSchema(configKeys),
|
|
359
|
-
onFinished: (config) => {
|
|
360
|
-
// Save config - Config component will handle completion and feedback
|
|
361
|
-
try {
|
|
362
|
-
// Convert flat dotted keys to nested structure grouped by section
|
|
363
|
-
const configBySection = unflattenConfig(config);
|
|
364
|
-
// Save each section
|
|
365
|
-
for (const [section, sectionConfig] of Object.entries(configBySection)) {
|
|
366
|
-
saveConfig(section, sectionConfig);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
catch (error) {
|
|
370
|
-
const errorMessage = error instanceof Error
|
|
371
|
-
? error.message
|
|
372
|
-
: 'Failed to save configuration';
|
|
373
|
-
throw new Error(errorMessage);
|
|
374
|
-
}
|
|
375
|
-
},
|
|
376
|
-
onAborted: (operation) => {
|
|
377
|
-
context.requestHandlers.onAborted(operation);
|
|
378
|
-
},
|
|
379
|
-
}));
|
|
380
508
|
}
|
|
381
509
|
/**
|
|
382
510
|
* Route Execute tasks - creates Execute component (validation already done)
|
package/dist/services/shell.js
CHANGED
|
@@ -105,8 +105,10 @@ export class OutputStreamer {
|
|
|
105
105
|
// Collapse when we have too many chunks to prevent memory growth
|
|
106
106
|
if (this.chunks.length > 16) {
|
|
107
107
|
const accumulated = this.chunks.join('');
|
|
108
|
-
|
|
109
|
-
this.
|
|
108
|
+
const limited = limitLines(accumulated);
|
|
109
|
+
this.chunks = [limited];
|
|
110
|
+
// Mark all collapsed content as emitted to prevent re-emission
|
|
111
|
+
this.emittedLength = limited.length;
|
|
110
112
|
}
|
|
111
113
|
if (!this.callback)
|
|
112
114
|
return;
|
|
@@ -146,6 +148,7 @@ export class OutputStreamer {
|
|
|
146
148
|
*/
|
|
147
149
|
export class RealExecutor {
|
|
148
150
|
outputCallback;
|
|
151
|
+
memoryCallback;
|
|
149
152
|
constructor(outputCallback) {
|
|
150
153
|
this.outputCallback = outputCallback;
|
|
151
154
|
}
|
|
@@ -155,6 +158,12 @@ export class RealExecutor {
|
|
|
155
158
|
setOutputCallback(callback) {
|
|
156
159
|
this.outputCallback = callback;
|
|
157
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Set or update the memory callback
|
|
163
|
+
*/
|
|
164
|
+
setMemoryCallback(callback) {
|
|
165
|
+
this.memoryCallback = callback;
|
|
166
|
+
}
|
|
158
167
|
execute(cmd, onProgress, _ = 0) {
|
|
159
168
|
return new Promise((resolve) => {
|
|
160
169
|
onProgress?.(ExecutionStatus.Running);
|
|
@@ -197,7 +206,7 @@ export class RealExecutor {
|
|
|
197
206
|
if (cmd.memoryLimit) {
|
|
198
207
|
memoryMonitor = new MemoryMonitor(child, cmd.memoryLimit, (info) => {
|
|
199
208
|
memoryInfo = info;
|
|
200
|
-
});
|
|
209
|
+
}, undefined, this.memoryCallback);
|
|
201
210
|
memoryMonitor.start();
|
|
202
211
|
}
|
|
203
212
|
// Use OutputStreamer for buffered stdout streaming
|
|
@@ -278,6 +287,12 @@ const executor = realExecutor;
|
|
|
278
287
|
export function setOutputCallback(callback) {
|
|
279
288
|
realExecutor.setOutputCallback(callback);
|
|
280
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Set a callback to receive memory updates during execution
|
|
292
|
+
*/
|
|
293
|
+
export function setMemoryCallback(callback) {
|
|
294
|
+
realExecutor.setMemoryCallback(callback);
|
|
295
|
+
}
|
|
281
296
|
/**
|
|
282
297
|
* Execute a single shell command
|
|
283
298
|
*/
|
package/dist/services/utils.js
CHANGED
|
@@ -25,6 +25,17 @@ export function formatDuration(ms) {
|
|
|
25
25
|
}
|
|
26
26
|
return parts.join(' ');
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Formats memory in megabytes to a human-readable string.
|
|
30
|
+
* Uses MB for values under 1024 MB, GB for larger values.
|
|
31
|
+
*/
|
|
32
|
+
export function formatMemory(mb) {
|
|
33
|
+
if (mb >= 1024) {
|
|
34
|
+
const gb = mb / 1024;
|
|
35
|
+
return `${gb.toFixed(1)} GB`;
|
|
36
|
+
}
|
|
37
|
+
return `${mb} MB`;
|
|
38
|
+
}
|
|
28
39
|
/**
|
|
29
40
|
* Recursively extracts all leaf tasks from a hierarchical task structure.
|
|
30
41
|
* Leaf tasks are tasks without subtasks.
|
package/dist/skills/schedule.md
CHANGED
|
@@ -129,6 +129,13 @@ Before creating tasks, evaluate the request type:
|
|
|
129
129
|
capabilities", "show skills"
|
|
130
130
|
- Example: "flex" → introspect type
|
|
131
131
|
|
|
132
|
+
**CRITICAL - Introspection is ALWAYS a single task:**
|
|
133
|
+
- Introspection requests MUST result in exactly ONE introspect leaf task
|
|
134
|
+
- NEVER create multiple introspect tasks for a single request
|
|
135
|
+
- NEVER nest introspect tasks within groups
|
|
136
|
+
- NEVER break down capabilities into separate introspect tasks
|
|
137
|
+
- The single introspect task will list ALL capabilities
|
|
138
|
+
|
|
132
139
|
2. **Information requests** (questions) - Use question keywords:
|
|
133
140
|
- "explain", "describe", "tell me", "what is", "how does", "find",
|
|
134
141
|
"search"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prompt-language-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Your personal command-line concierge. Ask politely, and it gets things done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"dev": "npm run build && tsc --watch",
|
|
18
18
|
"prepare": "husky",
|
|
19
19
|
"prepublishOnly": "npm run check",
|
|
20
|
-
"test": "vitest run --exclude 'tests/tools/
|
|
21
|
-
"test:watch": "vitest --exclude 'tests/tools/
|
|
20
|
+
"test": "vitest run --exclude 'tests/tools/' --exclude 'tests/shell/'",
|
|
21
|
+
"test:watch": "vitest --exclude 'tests/tools/' --exclude 'tests/shell/'",
|
|
22
22
|
"test:llm": "vitest run tests/tools/schedule/*.test.tsx",
|
|
23
23
|
"test:shell": "vitest run tests/shell/*.test.ts",
|
|
24
24
|
"format": "prettier --write '**/*.{ts,tsx}'",
|
|
@@ -53,18 +53,18 @@
|
|
|
53
53
|
"ink-text-input": "^6.0.0",
|
|
54
54
|
"react": "^19.2.3",
|
|
55
55
|
"yaml": "^2.8.2",
|
|
56
|
-
"zod": "^4.
|
|
56
|
+
"zod": "^4.3.6"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@types/node": "^25.0.
|
|
60
|
-
"@types/react": "^19.2.
|
|
61
|
-
"@vitest/coverage-v8": "^4.0.
|
|
59
|
+
"@types/node": "^25.0.10",
|
|
60
|
+
"@types/react": "^19.2.9",
|
|
61
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
62
62
|
"eslint": "^9.39.2",
|
|
63
63
|
"husky": "^9.1.7",
|
|
64
64
|
"ink-testing-library": "^4.0.0",
|
|
65
|
-
"prettier": "^3.
|
|
65
|
+
"prettier": "^3.8.1",
|
|
66
66
|
"typescript": "^5.9.3",
|
|
67
|
-
"typescript-eslint": "^8.
|
|
68
|
-
"vitest": "^4.0.
|
|
67
|
+
"typescript-eslint": "^8.53.1",
|
|
68
|
+
"vitest": "^4.0.18"
|
|
69
69
|
}
|
|
70
70
|
}
|