prompt-language-shell 0.8.2 → 0.8.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/configuration/io.js +85 -0
- package/dist/configuration/messages.js +30 -0
- package/dist/configuration/schema.js +167 -0
- package/dist/configuration/transformation.js +55 -0
- package/dist/configuration/types.js +30 -0
- package/dist/configuration/validation.js +52 -0
- package/dist/execution/handlers.js +135 -0
- package/dist/execution/processing.js +35 -0
- package/dist/execution/reducer.js +148 -0
- package/dist/execution/types.js +12 -0
- package/dist/execution/validation.js +12 -0
- package/dist/index.js +1 -1
- package/dist/services/anthropic.js +43 -56
- package/dist/services/colors.js +2 -1
- package/dist/services/components.js +40 -24
- package/dist/services/config-labels.js +15 -15
- package/dist/services/filesystem.js +114 -0
- package/dist/services/loader.js +8 -5
- package/dist/services/logger.js +26 -1
- package/dist/services/messages.js +32 -1
- package/dist/services/parser.js +3 -1
- package/dist/services/refinement.js +10 -10
- package/dist/services/router.js +43 -27
- package/dist/services/skills.js +12 -11
- package/dist/services/validator.js +4 -3
- package/dist/types/guards.js +4 -6
- package/dist/types/handlers.js +1 -0
- package/dist/types/schemas.js +103 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/Answer.js +38 -16
- package/dist/ui/Command.js +48 -22
- package/dist/ui/Component.js +147 -33
- package/dist/ui/Config.js +69 -78
- package/dist/ui/Confirm.js +34 -21
- package/dist/ui/Execute.js +151 -178
- package/dist/ui/Feedback.js +1 -0
- package/dist/ui/Introspect.js +54 -25
- package/dist/ui/Label.js +1 -1
- package/dist/ui/Main.js +10 -6
- package/dist/ui/Refinement.js +8 -1
- package/dist/ui/Schedule.js +76 -53
- package/dist/ui/Validate.js +77 -77
- package/dist/ui/Workflow.js +60 -61
- package/package.json +3 -2
- package/dist/services/configuration.js +0 -409
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'fs';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Real filesystem implementation using Node's fs module
|
|
5
|
+
*/
|
|
6
|
+
export class RealFileSystem {
|
|
7
|
+
exists(path) {
|
|
8
|
+
return existsSync(path);
|
|
9
|
+
}
|
|
10
|
+
readFile(path, encoding) {
|
|
11
|
+
return readFileSync(path, encoding);
|
|
12
|
+
}
|
|
13
|
+
writeFile(path, data) {
|
|
14
|
+
writeFileSync(path, data, 'utf-8');
|
|
15
|
+
}
|
|
16
|
+
readDirectory(path) {
|
|
17
|
+
return readdirSync(path);
|
|
18
|
+
}
|
|
19
|
+
createDirectory(path, options) {
|
|
20
|
+
mkdirSync(path, options);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* In-memory filesystem implementation for testing
|
|
25
|
+
* Simulates filesystem behavior without touching disk
|
|
26
|
+
*/
|
|
27
|
+
export class MemoryFileSystem {
|
|
28
|
+
files = new Map();
|
|
29
|
+
directories = new Set();
|
|
30
|
+
exists(path) {
|
|
31
|
+
return this.files.has(path) || this.directories.has(path);
|
|
32
|
+
}
|
|
33
|
+
readFile(path, _encoding) {
|
|
34
|
+
const content = this.files.get(path);
|
|
35
|
+
if (content === undefined) {
|
|
36
|
+
throw new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
37
|
+
}
|
|
38
|
+
return content;
|
|
39
|
+
}
|
|
40
|
+
writeFile(path, data) {
|
|
41
|
+
// Auto-create parent directories
|
|
42
|
+
const dir = dirname(path);
|
|
43
|
+
if (dir !== '.' && dir !== path) {
|
|
44
|
+
this.createDirectory(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
this.files.set(path, data);
|
|
47
|
+
}
|
|
48
|
+
readDirectory(path) {
|
|
49
|
+
if (!this.directories.has(path)) {
|
|
50
|
+
throw new Error(`ENOENT: no such file or directory, scandir '${path}'`);
|
|
51
|
+
}
|
|
52
|
+
const results = [];
|
|
53
|
+
const prefix = path.endsWith('/') ? path : `${path}/`;
|
|
54
|
+
// Find all direct children (files and directories)
|
|
55
|
+
for (const filePath of this.files.keys()) {
|
|
56
|
+
if (filePath.startsWith(prefix)) {
|
|
57
|
+
const relative = filePath.slice(prefix.length);
|
|
58
|
+
const firstSlash = relative.indexOf('/');
|
|
59
|
+
if (firstSlash === -1) {
|
|
60
|
+
// Direct file child
|
|
61
|
+
results.push(relative);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const dirPath of this.directories) {
|
|
66
|
+
if (dirPath.startsWith(prefix) && dirPath !== path) {
|
|
67
|
+
const relative = dirPath.slice(prefix.length);
|
|
68
|
+
const firstSlash = relative.indexOf('/');
|
|
69
|
+
if (firstSlash === -1) {
|
|
70
|
+
// Direct directory child
|
|
71
|
+
results.push(relative);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
createDirectory(path, options) {
|
|
78
|
+
if (options?.recursive) {
|
|
79
|
+
// Create all parent directories
|
|
80
|
+
const parts = path.split('/').filter((p) => p);
|
|
81
|
+
let current = path.startsWith('/') ? '/' : '';
|
|
82
|
+
for (const part of parts) {
|
|
83
|
+
current = current === '/' ? `/${part}` : `${current}/${part}`;
|
|
84
|
+
this.directories.add(current);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Non-recursive: parent must exist
|
|
89
|
+
const parent = dirname(path);
|
|
90
|
+
if (parent !== '.' && parent !== path && !this.directories.has(parent)) {
|
|
91
|
+
throw new Error(`ENOENT: no such file or directory, mkdir '${path}'`);
|
|
92
|
+
}
|
|
93
|
+
this.directories.add(path);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Clear all files and directories (useful for test cleanup)
|
|
98
|
+
*/
|
|
99
|
+
clear() {
|
|
100
|
+
this.files.clear();
|
|
101
|
+
this.directories.clear();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get all files for debugging
|
|
105
|
+
*/
|
|
106
|
+
getFiles() {
|
|
107
|
+
return new Map(this.files);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Default filesystem instance (uses real fs)
|
|
112
|
+
* Services can accept optional FileSystem parameter for testing
|
|
113
|
+
*/
|
|
114
|
+
export const defaultFileSystem = new RealFileSystem();
|
package/dist/services/loader.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'fs';
|
|
2
1
|
import { homedir } from 'os';
|
|
3
2
|
import { join } from 'path';
|
|
4
3
|
import YAML from 'yaml';
|
|
4
|
+
import { defaultFileSystem } from './filesystem.js';
|
|
5
|
+
import { displayWarning } from './logger.js';
|
|
5
6
|
/**
|
|
6
7
|
* Load user config from ~/.plsrc
|
|
7
8
|
*/
|
|
8
|
-
export function loadUserConfig() {
|
|
9
|
+
export function loadUserConfig(fs = defaultFileSystem) {
|
|
9
10
|
const configPath = join(homedir(), '.plsrc');
|
|
10
|
-
if (!
|
|
11
|
+
if (!fs.exists(configPath)) {
|
|
11
12
|
return {};
|
|
12
13
|
}
|
|
13
14
|
try {
|
|
14
|
-
const content =
|
|
15
|
+
const content = fs.readFile(configPath, 'utf-8');
|
|
15
16
|
const parsed = YAML.parse(content);
|
|
16
17
|
if (parsed && typeof parsed === 'object') {
|
|
17
18
|
return parsed;
|
|
18
19
|
}
|
|
20
|
+
displayWarning('User config file exists but is not a valid object');
|
|
19
21
|
return {};
|
|
20
22
|
}
|
|
21
|
-
catch {
|
|
23
|
+
catch (error) {
|
|
24
|
+
displayWarning('Failed to load user config', error);
|
|
22
25
|
return {};
|
|
23
26
|
}
|
|
24
27
|
}
|
package/dist/services/logger.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import { DebugLevel } from '../configuration/types.js';
|
|
1
2
|
import { createDebugDefinition } from './components.js';
|
|
2
|
-
import {
|
|
3
|
+
import { loadDebugSetting } from '../configuration/io.js';
|
|
3
4
|
import { Palette } from './colors.js';
|
|
4
5
|
/**
|
|
5
6
|
* Debug logger for the application
|
|
6
7
|
* Logs information based on the current debug level setting
|
|
7
8
|
*/
|
|
8
9
|
let currentDebugLevel = DebugLevel.None;
|
|
10
|
+
/**
|
|
11
|
+
* Accumulated warnings to be displayed in the timeline
|
|
12
|
+
*/
|
|
13
|
+
const warnings = [];
|
|
9
14
|
/**
|
|
10
15
|
* Initialize the logger with the current debug level from config
|
|
11
16
|
*/
|
|
@@ -24,6 +29,26 @@ export function setDebugLevel(debug) {
|
|
|
24
29
|
export function getDebugLevel() {
|
|
25
30
|
return currentDebugLevel;
|
|
26
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Store a warning message to be displayed in the timeline
|
|
34
|
+
* Only stores warnings at Info or Verbose debug levels
|
|
35
|
+
*/
|
|
36
|
+
export function displayWarning(message, error) {
|
|
37
|
+
if (currentDebugLevel === DebugLevel.None) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const errorDetails = error instanceof Error ? `: ${error.message}` : '';
|
|
41
|
+
warnings.push(`${message}${errorDetails}`);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get all accumulated warnings and clear the list
|
|
45
|
+
* Returns array of warning messages
|
|
46
|
+
*/
|
|
47
|
+
export function getWarnings() {
|
|
48
|
+
const result = [...warnings];
|
|
49
|
+
warnings.length = 0;
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
27
52
|
/**
|
|
28
53
|
* Create debug component for system prompts sent to the LLM
|
|
29
54
|
* Only creates at Verbose level
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DebugLevel
|
|
1
|
+
import { DebugLevel } from '../configuration/types.js';
|
|
2
|
+
import { loadDebugSetting } from '../configuration/io.js';
|
|
2
3
|
export { formatDuration } from './utils.js';
|
|
3
4
|
/**
|
|
4
5
|
* Returns a natural language confirmation message for plan execution.
|
|
@@ -80,6 +81,36 @@ export function getMixedTaskTypesError(types) {
|
|
|
80
81
|
const typeList = types.join(', ');
|
|
81
82
|
return `Mixed task types are not supported. Found: ${typeList}. All tasks in a plan must have the same type.`;
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns a message for unresolved placeholders/missing configuration.
|
|
86
|
+
* Each message has two sentences: what's missing + what will be done.
|
|
87
|
+
* Both sentences are randomly selected independently for variety.
|
|
88
|
+
* Supports singular and plural forms.
|
|
89
|
+
*/
|
|
90
|
+
export function getUnresolvedPlaceholdersMessage(count) {
|
|
91
|
+
const plural = count === 1 ? '' : 's';
|
|
92
|
+
const it = count === 1 ? 'it' : 'them';
|
|
93
|
+
const valueWord = count === 1 ? 'value' : 'values';
|
|
94
|
+
// First sentence: what's missing
|
|
95
|
+
const firstSentences = [
|
|
96
|
+
`Missing configuration ${valueWord} detected.`,
|
|
97
|
+
`Configuration ${valueWord} needed.`,
|
|
98
|
+
`Found unresolved placeholder${plural}.`,
|
|
99
|
+
`Additional configuration ${valueWord} required.`,
|
|
100
|
+
`Setup requires configuration ${valueWord}.`,
|
|
101
|
+
];
|
|
102
|
+
// Second sentence: what will be done
|
|
103
|
+
const secondSentences = [
|
|
104
|
+
`Let me gather ${it} now.`,
|
|
105
|
+
`I'll set ${it} up for you.`,
|
|
106
|
+
`Let me configure ${it} first.`,
|
|
107
|
+
`I'll help you provide ${it}.`,
|
|
108
|
+
`Let me collect ${it} from you.`,
|
|
109
|
+
];
|
|
110
|
+
const first = firstSentences[Math.floor(Math.random() * firstSentences.length)];
|
|
111
|
+
const second = secondSentences[Math.floor(Math.random() * secondSentences.length)];
|
|
112
|
+
return `${first} ${second}`;
|
|
113
|
+
}
|
|
83
114
|
/**
|
|
84
115
|
* Feedback messages for various operations
|
|
85
116
|
*/
|
package/dist/services/parser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import YAML from 'yaml';
|
|
2
|
+
import { displayWarning } from './logger.js';
|
|
2
3
|
/**
|
|
3
4
|
* Validate a skill without parsing it fully
|
|
4
5
|
* Returns validation error if skill is invalid, null if valid
|
|
@@ -188,7 +189,8 @@ function parseConfigSchema(content) {
|
|
|
188
189
|
}
|
|
189
190
|
return parsed;
|
|
190
191
|
}
|
|
191
|
-
catch {
|
|
192
|
+
catch (error) {
|
|
193
|
+
displayWarning('Failed to parse config schema in skill', error);
|
|
192
194
|
return undefined;
|
|
193
195
|
}
|
|
194
196
|
}
|
|
@@ -5,12 +5,12 @@ import { routeTasksWithConfirm } from './router.js';
|
|
|
5
5
|
* Handle refinement flow for DEFINE tasks
|
|
6
6
|
* Called when user selects options from a plan with DEFINE tasks
|
|
7
7
|
*/
|
|
8
|
-
export async function handleRefinement(selectedTasks, service, originalCommand,
|
|
8
|
+
export async function handleRefinement(selectedTasks, service, originalCommand, lifecycleHandlers, workflowHandlers, requestHandlers) {
|
|
9
9
|
// Create and add refinement component to queue
|
|
10
10
|
const refinementDef = createRefinement(getRefiningMessage(), (operation) => {
|
|
11
|
-
|
|
11
|
+
requestHandlers.onAborted(operation);
|
|
12
12
|
});
|
|
13
|
-
|
|
13
|
+
workflowHandlers.addToQueue(refinementDef);
|
|
14
14
|
try {
|
|
15
15
|
// Build refined command from selected tasks
|
|
16
16
|
const refinedCommand = selectedTasks
|
|
@@ -22,19 +22,19 @@ export async function handleRefinement(selectedTasks, service, originalCommand,
|
|
|
22
22
|
.join(', ');
|
|
23
23
|
// Call LLM to refine plan with selected tasks
|
|
24
24
|
const refinedResult = await service.processWithTool(refinedCommand, 'schedule');
|
|
25
|
-
// Complete the Refinement component
|
|
26
|
-
|
|
25
|
+
// Complete the Refinement component with success state
|
|
26
|
+
lifecycleHandlers.completeActive();
|
|
27
27
|
// Add debug components to timeline if present
|
|
28
|
-
if (refinedResult.debug
|
|
29
|
-
|
|
28
|
+
if (refinedResult.debug?.length) {
|
|
29
|
+
workflowHandlers.addToTimeline(...refinedResult.debug);
|
|
30
30
|
}
|
|
31
31
|
// Route refined tasks to appropriate components
|
|
32
|
-
routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand,
|
|
32
|
+
routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, lifecycleHandlers, workflowHandlers, requestHandlers, false // No DEFINE tasks in refined result
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
catch (err) {
|
|
36
|
-
|
|
36
|
+
lifecycleHandlers.completeActive();
|
|
37
37
|
const errorMessage = formatErrorMessage(err);
|
|
38
|
-
|
|
38
|
+
requestHandlers.onError(errorMessage);
|
|
39
39
|
}
|
|
40
40
|
}
|
package/dist/services/router.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { asScheduledTasks } from '../types/guards.js';
|
|
2
2
|
import { FeedbackType, TaskType } from '../types/types.js';
|
|
3
|
+
import { saveConfig } from '../configuration/io.js';
|
|
4
|
+
import { getConfigSchema } from '../configuration/schema.js';
|
|
5
|
+
import { unflattenConfig } from '../configuration/transformation.js';
|
|
6
|
+
import { saveConfigLabels } from './config-labels.js';
|
|
3
7
|
import { createAnswerDefinition, createConfigDefinitionWithKeys, createConfirmDefinition, createExecuteDefinition, createFeedback, createIntrospectDefinition, createMessage, createScheduleDefinition, createValidateDefinition, } from './components.js';
|
|
4
|
-
import { saveConfig, unflattenConfig } from './configuration.js';
|
|
5
8
|
import { getCancellationMessage, getMixedTaskTypesError, getUnknownRequestMessage, } from './messages.js';
|
|
6
9
|
import { validateExecuteTasks } from './validator.js';
|
|
7
10
|
/**
|
|
@@ -20,7 +23,7 @@ export function getOperationName(tasks) {
|
|
|
20
23
|
* Route tasks to appropriate components with Confirm flow
|
|
21
24
|
* Handles the complete flow: Plan → Confirm → Execute/Answer/Introspect
|
|
22
25
|
*/
|
|
23
|
-
export function routeTasksWithConfirm(tasks, message, service, userRequest,
|
|
26
|
+
export function routeTasksWithConfirm(tasks, message, service, userRequest, lifecycleHandlers, workflowHandlers, requestHandlers, hasDefineTask = false) {
|
|
24
27
|
if (tasks.length === 0)
|
|
25
28
|
return;
|
|
26
29
|
// Filter out ignore and discard tasks early
|
|
@@ -28,7 +31,7 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
|
|
|
28
31
|
// Check if no valid tasks remain after filtering
|
|
29
32
|
if (validTasks.length === 0) {
|
|
30
33
|
const message = createMessage(getUnknownRequestMessage());
|
|
31
|
-
|
|
34
|
+
workflowHandlers.addToQueue(message);
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
37
|
const operation = getOperationName(validTasks);
|
|
@@ -36,7 +39,7 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
|
|
|
36
39
|
// Has DEFINE tasks - add Schedule to queue for user selection
|
|
37
40
|
// Refinement flow will call this function again with refined tasks
|
|
38
41
|
const scheduleDefinition = createScheduleDefinition(message, validTasks);
|
|
39
|
-
|
|
42
|
+
workflowHandlers.addToQueue(scheduleDefinition);
|
|
40
43
|
}
|
|
41
44
|
else {
|
|
42
45
|
// No DEFINE tasks - Schedule auto-completes and adds Confirm to queue
|
|
@@ -47,17 +50,17 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
|
|
|
47
50
|
// Schedule completed - add Confirm to queue
|
|
48
51
|
const confirmDefinition = createConfirmDefinition(() => {
|
|
49
52
|
// User confirmed - complete both Confirm and Schedule, then route to appropriate component
|
|
50
|
-
|
|
51
|
-
executeTasksAfterConfirm(validTasks, service, userRequest,
|
|
53
|
+
lifecycleHandlers.completeActiveAndPending();
|
|
54
|
+
executeTasksAfterConfirm(validTasks, service, userRequest, workflowHandlers, requestHandlers);
|
|
52
55
|
}, () => {
|
|
53
56
|
// User cancelled - complete both Confirm and Schedule, then show cancellation
|
|
54
|
-
|
|
57
|
+
lifecycleHandlers.completeActiveAndPending();
|
|
55
58
|
const message = getCancellationMessage(operation);
|
|
56
|
-
|
|
59
|
+
workflowHandlers.addToQueue(createFeedback(FeedbackType.Aborted, message));
|
|
57
60
|
});
|
|
58
|
-
|
|
61
|
+
workflowHandlers.addToQueue(confirmDefinition);
|
|
59
62
|
});
|
|
60
|
-
|
|
63
|
+
workflowHandlers.addToQueue(scheduleDefinition);
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
/**
|
|
@@ -88,13 +91,13 @@ function validateTaskTypes(tasks) {
|
|
|
88
91
|
* Validates task types and routes each type appropriately
|
|
89
92
|
* Supports mixed types at top level with Groups
|
|
90
93
|
*/
|
|
91
|
-
function executeTasksAfterConfirm(tasks, service, userRequest,
|
|
94
|
+
function executeTasksAfterConfirm(tasks, service, userRequest, workflowHandlers, requestHandlers) {
|
|
92
95
|
// Validate task types (Groups must have uniform subtasks)
|
|
93
96
|
try {
|
|
94
97
|
validateTaskTypes(tasks);
|
|
95
98
|
}
|
|
96
99
|
catch (error) {
|
|
97
|
-
|
|
100
|
+
requestHandlers.onError(error instanceof Error ? error.message : String(error));
|
|
98
101
|
return;
|
|
99
102
|
}
|
|
100
103
|
const scheduledTasks = asScheduledTasks(tasks);
|
|
@@ -117,7 +120,7 @@ function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
|
|
|
117
120
|
const taskType = type;
|
|
118
121
|
if (typeTasks.length === 0)
|
|
119
122
|
continue;
|
|
120
|
-
routeTasksByType(taskType, typeTasks, service, userRequest,
|
|
123
|
+
routeTasksByType(taskType, typeTasks, service, userRequest, workflowHandlers, requestHandlers);
|
|
121
124
|
}
|
|
122
125
|
consecutiveStandaloneTasks = [];
|
|
123
126
|
};
|
|
@@ -130,7 +133,7 @@ function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
|
|
|
130
133
|
if (task.subtasks.length > 0) {
|
|
131
134
|
const subtasks = task.subtasks;
|
|
132
135
|
const taskType = subtasks[0].type;
|
|
133
|
-
routeTasksByType(taskType, subtasks, service, userRequest,
|
|
136
|
+
routeTasksByType(taskType, subtasks, service, userRequest, workflowHandlers, requestHandlers);
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
else {
|
|
@@ -145,22 +148,35 @@ function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
|
|
|
145
148
|
* Route tasks by type to appropriate components
|
|
146
149
|
* Extracted to allow reuse for both Groups and standalone tasks
|
|
147
150
|
*/
|
|
148
|
-
function routeTasksByType(taskType, typeTasks, service, userRequest,
|
|
151
|
+
function routeTasksByType(taskType, typeTasks, service, userRequest, workflowHandlers, requestHandlers) {
|
|
149
152
|
if (taskType === TaskType.Answer) {
|
|
150
153
|
// Create separate Answer component for each question
|
|
151
154
|
for (const task of typeTasks) {
|
|
152
|
-
|
|
155
|
+
workflowHandlers.addToQueue(createAnswerDefinition(task.action, service));
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
else if (taskType === TaskType.Introspect) {
|
|
156
|
-
|
|
159
|
+
workflowHandlers.addToQueue(createIntrospectDefinition(typeTasks, service));
|
|
157
160
|
}
|
|
158
161
|
else if (taskType === TaskType.Config) {
|
|
159
|
-
// Route to Config flow - extract keys from task params
|
|
162
|
+
// Route to Config flow - extract keys and descriptions from task params
|
|
160
163
|
const configKeys = typeTasks
|
|
161
164
|
.map((task) => task.params?.key)
|
|
162
165
|
.filter((key) => key !== undefined);
|
|
163
|
-
|
|
166
|
+
// Extract and cache labels from task descriptions
|
|
167
|
+
// Only cache labels for dynamically discovered keys (not in schema)
|
|
168
|
+
const schema = getConfigSchema();
|
|
169
|
+
const labels = {};
|
|
170
|
+
for (const task of typeTasks) {
|
|
171
|
+
const key = task.params?.key;
|
|
172
|
+
if (key && task.action && !(key in schema)) {
|
|
173
|
+
labels[key] = task.action;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (Object.keys(labels).length > 0) {
|
|
177
|
+
saveConfigLabels(labels);
|
|
178
|
+
}
|
|
179
|
+
workflowHandlers.addToQueue(createConfigDefinitionWithKeys(configKeys, (config) => {
|
|
164
180
|
// Save config - Config component will handle completion and feedback
|
|
165
181
|
try {
|
|
166
182
|
// Convert flat dotted keys to nested structure grouped by section
|
|
@@ -177,7 +193,7 @@ function routeTasksByType(taskType, typeTasks, service, userRequest, handlers) {
|
|
|
177
193
|
throw new Error(errorMessage);
|
|
178
194
|
}
|
|
179
195
|
}, (operation) => {
|
|
180
|
-
|
|
196
|
+
requestHandlers.onAborted(operation);
|
|
181
197
|
}));
|
|
182
198
|
}
|
|
183
199
|
else if (taskType === TaskType.Execute) {
|
|
@@ -192,26 +208,26 @@ function routeTasksByType(taskType, typeTasks, service, userRequest, handlers) {
|
|
|
192
208
|
.join('\n');
|
|
193
209
|
return `Invalid skill definition "${error.skill}":\n\n${issuesList}`;
|
|
194
210
|
});
|
|
195
|
-
|
|
211
|
+
workflowHandlers.addToQueue(createFeedback(FeedbackType.Failed, errorMessages.join('\n\n')));
|
|
196
212
|
}
|
|
197
213
|
else if (validation.missingConfig.length > 0) {
|
|
198
|
-
|
|
199
|
-
|
|
214
|
+
workflowHandlers.addToQueue(createValidateDefinition(validation.missingConfig, userRequest, service, (error) => {
|
|
215
|
+
requestHandlers.onError(error);
|
|
200
216
|
}, () => {
|
|
201
|
-
|
|
217
|
+
workflowHandlers.addToQueue(createExecuteDefinition(typeTasks, service));
|
|
202
218
|
}, (operation) => {
|
|
203
|
-
|
|
219
|
+
requestHandlers.onAborted(operation);
|
|
204
220
|
}));
|
|
205
221
|
}
|
|
206
222
|
else {
|
|
207
|
-
|
|
223
|
+
workflowHandlers.addToQueue(createExecuteDefinition(typeTasks, service));
|
|
208
224
|
}
|
|
209
225
|
}
|
|
210
226
|
catch (error) {
|
|
211
227
|
// Handle skill reference errors (e.g., unknown skills)
|
|
212
228
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
213
229
|
const message = createMessage(errorMessage);
|
|
214
|
-
|
|
230
|
+
workflowHandlers.addToQueue(message);
|
|
215
231
|
}
|
|
216
232
|
}
|
|
217
233
|
}
|
package/dist/services/skills.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
2
1
|
import { homedir } from 'os';
|
|
3
2
|
import { join } from 'path';
|
|
3
|
+
import { defaultFileSystem } from './filesystem.js';
|
|
4
|
+
import { displayWarning } from './logger.js';
|
|
4
5
|
import { getUnknownSkillMessage } from './messages.js';
|
|
5
6
|
import { parseSkillMarkdown, displayNameToKey } from './parser.js';
|
|
6
7
|
/**
|
|
@@ -48,14 +49,14 @@ export function getSkillsDirectory() {
|
|
|
48
49
|
* Returns an array of objects with filename (key) and content
|
|
49
50
|
* Filters out invalid filenames and conflicts with system skills
|
|
50
51
|
*/
|
|
51
|
-
export function loadSkills() {
|
|
52
|
+
export function loadSkills(fs = defaultFileSystem) {
|
|
52
53
|
const skillsDir = getSkillsDirectory();
|
|
53
54
|
// Return empty array if directory doesn't exist
|
|
54
|
-
if (!
|
|
55
|
+
if (!fs.exists(skillsDir)) {
|
|
55
56
|
return [];
|
|
56
57
|
}
|
|
57
58
|
try {
|
|
58
|
-
const files =
|
|
59
|
+
const files = fs.readDirectory(skillsDir);
|
|
59
60
|
// Filter and map valid skill files
|
|
60
61
|
return files
|
|
61
62
|
.filter((file) => {
|
|
@@ -75,12 +76,12 @@ export function loadSkills() {
|
|
|
75
76
|
// Extract key (filename without extension, handles both .md and .MD)
|
|
76
77
|
const key = file.slice(0, -3);
|
|
77
78
|
const filePath = join(skillsDir, file);
|
|
78
|
-
const content =
|
|
79
|
+
const content = fs.readFile(filePath, 'utf-8');
|
|
79
80
|
return { key, content };
|
|
80
81
|
});
|
|
81
82
|
}
|
|
82
|
-
catch {
|
|
83
|
-
|
|
83
|
+
catch (error) {
|
|
84
|
+
displayWarning('Failed to load skills directory', error);
|
|
84
85
|
return [];
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -88,16 +89,16 @@ export function loadSkills() {
|
|
|
88
89
|
* Load and parse all skill definitions
|
|
89
90
|
* Returns structured skill definitions (including invalid skills)
|
|
90
91
|
*/
|
|
91
|
-
export function loadSkillDefinitions() {
|
|
92
|
-
const skills = loadSkills();
|
|
92
|
+
export function loadSkillDefinitions(fs = defaultFileSystem) {
|
|
93
|
+
const skills = loadSkills(fs);
|
|
93
94
|
return skills.map(({ key, content }) => parseSkillMarkdown(key, content));
|
|
94
95
|
}
|
|
95
96
|
/**
|
|
96
97
|
* Load skills and mark incomplete ones in their markdown
|
|
97
98
|
* Returns array of skill markdown with status markers
|
|
98
99
|
*/
|
|
99
|
-
export function loadSkillsWithValidation() {
|
|
100
|
-
const skills = loadSkills();
|
|
100
|
+
export function loadSkillsWithValidation(fs = defaultFileSystem) {
|
|
101
|
+
const skills = loadSkills(fs);
|
|
101
102
|
return skills.map(({ key, content }) => {
|
|
102
103
|
const parsed = parseSkillMarkdown(key, content);
|
|
103
104
|
// If skill is incomplete (either validation failed or needs more documentation), append (INCOMPLETE) to the name
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import { defaultFileSystem } from './filesystem.js';
|
|
1
2
|
import { loadUserConfig, hasConfigPath } from './loader.js';
|
|
2
3
|
import { loadSkillDefinitions, createSkillLookup } from './skills.js';
|
|
3
4
|
/**
|
|
4
5
|
* Validate config requirements for execute tasks
|
|
5
6
|
* Returns validation result with missing config and validation errors
|
|
6
7
|
*/
|
|
7
|
-
export function validateExecuteTasks(tasks) {
|
|
8
|
-
const userConfig = loadUserConfig();
|
|
8
|
+
export function validateExecuteTasks(tasks, fs = defaultFileSystem) {
|
|
9
|
+
const userConfig = loadUserConfig(fs);
|
|
9
10
|
const missing = [];
|
|
10
11
|
const seenPaths = new Set();
|
|
11
12
|
const validationErrors = [];
|
|
12
13
|
const seenSkills = new Set();
|
|
13
14
|
// Load all skills (including invalid ones for validation)
|
|
14
|
-
const parsedSkills = loadSkillDefinitions();
|
|
15
|
+
const parsedSkills = loadSkillDefinitions(fs);
|
|
15
16
|
const skillLookup = createSkillLookup(parsedSkills);
|
|
16
17
|
// Check for invalid skills being used in tasks
|
|
17
18
|
for (const task of tasks) {
|
package/dist/types/guards.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TaskType } from './types.js';
|
|
2
|
+
import { TaskSchema } from './schemas.js';
|
|
2
3
|
/**
|
|
3
4
|
* Type guard to check if a task is a ScheduledTask
|
|
4
5
|
* ScheduledTask has optional subtasks property or is a Group type
|
|
@@ -14,12 +15,9 @@ export function asScheduledTasks(tasks) {
|
|
|
14
15
|
return tasks;
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
|
-
* Type guard to check if a value is a valid Task
|
|
18
|
+
* Type guard to check if a value is a valid Task.
|
|
19
|
+
* Uses Zod schema for comprehensive runtime validation.
|
|
18
20
|
*/
|
|
19
21
|
export function isTask(value) {
|
|
20
|
-
return (
|
|
21
|
-
value !== null &&
|
|
22
|
-
'action' in value &&
|
|
23
|
-
typeof value.action === 'string' &&
|
|
24
|
-
'type' in value);
|
|
22
|
+
return TaskSchema.safeParse(value).success;
|
|
25
23
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|