prompt-language-shell 0.4.9 → 0.5.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/dist/config/INTROSPECT.md +9 -6
- package/dist/config/PLAN.md +39 -1
- package/dist/config/VALIDATE.md +139 -0
- package/dist/handlers/config.js +23 -6
- package/dist/handlers/execute.js +10 -2
- package/dist/handlers/execution.js +68 -1
- package/dist/services/anthropic.js +3 -2
- package/dist/services/colors.js +2 -2
- package/dist/services/components.js +33 -1
- package/dist/services/config-loader.js +67 -0
- package/dist/services/execution-validator.js +110 -0
- package/dist/services/placeholder-resolver.js +120 -0
- package/dist/services/shell.js +1 -0
- package/dist/services/skill-expander.js +91 -0
- package/dist/services/skill-parser.js +169 -0
- package/dist/services/skills.js +26 -0
- package/dist/services/timing.js +38 -0
- package/dist/services/tool-registry.js +5 -0
- package/dist/tools/validate.tool.js +43 -0
- package/dist/types/skills.js +4 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/Answer.js +3 -9
- package/dist/ui/Command.js +3 -6
- package/dist/ui/Component.js +7 -1
- package/dist/ui/Config.js +2 -2
- package/dist/ui/Execute.js +59 -14
- package/dist/ui/Introspect.js +5 -7
- package/dist/ui/Validate.js +120 -0
- package/package.json +1 -1
|
@@ -75,12 +75,13 @@ These MUST appear FIRST, in this EXACT sequence:
|
|
|
75
75
|
3. **Answer** ← ALWAYS THIRD
|
|
76
76
|
4. **Execute** ← ALWAYS FOURTH
|
|
77
77
|
|
|
78
|
-
### Position 5-
|
|
78
|
+
### Position 5-7: Indirect Workflow Capabilities
|
|
79
79
|
|
|
80
80
|
These MUST appear AFTER Execute and BEFORE user skills:
|
|
81
81
|
|
|
82
82
|
5. **Plan** ← NEVER FIRST, ALWAYS position 5 (after Execute)
|
|
83
|
-
6. **
|
|
83
|
+
6. **Validate** ← ALWAYS position 6 (after Plan)
|
|
84
|
+
7. **Report** ← NEVER FIRST, ALWAYS position 7 (after Validate)
|
|
84
85
|
|
|
85
86
|
### 3. User-Defined Skills
|
|
86
87
|
|
|
@@ -132,8 +133,9 @@ Examples:
|
|
|
132
133
|
|
|
133
134
|
When user asks "list your skills", create an introductory message like "here
|
|
134
135
|
are my capabilities:" followed by tasks for built-in capabilities (Introspect,
|
|
135
|
-
Config, Answer, Execute), then indirect workflow capabilities (Plan,
|
|
136
|
-
Each task uses type "introspect" with an action describing the
|
|
136
|
+
Config, Answer, Execute), then indirect workflow capabilities (Validate, Plan,
|
|
137
|
+
Report). Each task uses type "introspect" with an action describing the
|
|
138
|
+
capability.
|
|
137
139
|
|
|
138
140
|
### Example 2: Filtered Skills
|
|
139
141
|
|
|
@@ -146,8 +148,9 @@ with its description.
|
|
|
146
148
|
|
|
147
149
|
When user asks "what can you do" and user-defined skills like "process data"
|
|
148
150
|
and "backup files" exist, create an introductory message like "i can help with
|
|
149
|
-
these operations:" followed by all built-in capabilities
|
|
150
|
-
|
|
151
|
+
these operations:" followed by all built-in capabilities (Introspect, Config,
|
|
152
|
+
Answer, Execute, Validate, Plan, Report) plus the user-defined skills. Each
|
|
153
|
+
capability and skill becomes a task with type "introspect".
|
|
151
154
|
|
|
152
155
|
## Final Validation
|
|
153
156
|
|
package/dist/config/PLAN.md
CHANGED
|
@@ -113,6 +113,26 @@ executable operations.
|
|
|
113
113
|
Extract the individual steps from the skill's "Execution" or "Steps"
|
|
114
114
|
section (prefer Execution if available)
|
|
115
115
|
- Replace ALL parameter placeholders with the specified value
|
|
116
|
+
- **CRITICAL - Variant Placeholder Resolution**: If the execution commands
|
|
117
|
+
contain variant placeholders (any uppercase word in a placeholder path,
|
|
118
|
+
e.g., {section.VARIANT.property}, {project.TARGET.path}, {env.TYPE.name}),
|
|
119
|
+
you MUST:
|
|
120
|
+
1. Identify the variant name from the user's request (e.g., "alpha", "beta")
|
|
121
|
+
2. Normalize the variant to lowercase (e.g., "alpha", "beta")
|
|
122
|
+
3. Replace the uppercase placeholder component with the actual variant name
|
|
123
|
+
in ALL task actions
|
|
124
|
+
4. Examples:
|
|
125
|
+
- User says "process alpha target" → variant is "alpha"
|
|
126
|
+
- Execution line: `cd {project.VARIANT.path}`
|
|
127
|
+
- Task action MUST be: `cd {project.alpha.path}` (NOT `cd {project.VARIANT.path}`)
|
|
128
|
+
- User says "deploy to staging environment" → variant is "staging"
|
|
129
|
+
- Execution line: `setup {env.TYPE.config}`
|
|
130
|
+
- Task action MUST be: `setup {env.staging.config}` (NOT `setup {env.TYPE.config}`)
|
|
131
|
+
5. This applies to ALL placeholders in task actions, whether from direct
|
|
132
|
+
execution lines or from referenced skills (e.g., [Navigate To Target])
|
|
133
|
+
6. The uppercase word can be ANY name (VARIANT, TARGET, TYPE, PRODUCT, etc.) -
|
|
134
|
+
all uppercase path components indicate variant placeholders that must
|
|
135
|
+
be resolved
|
|
116
136
|
|
|
117
137
|
4. **Handle partial execution:**
|
|
118
138
|
- Keywords indicating partial execution: "only", "just", specific verbs
|
|
@@ -129,11 +149,16 @@ executable operations.
|
|
|
129
149
|
- type: category of operation (if the skill specifies it or you can infer it)
|
|
130
150
|
- params: MUST include:
|
|
131
151
|
- skill: the skill name (REQUIRED for all skill-based tasks)
|
|
132
|
-
-
|
|
152
|
+
- variant: the resolved variant value (REQUIRED if skill has variant placeholders)
|
|
153
|
+
- All other parameter values used in the step (e.g., target, environment, etc.)
|
|
133
154
|
- Any other specific parameters mentioned in the step
|
|
134
155
|
- NEVER replace the skill's detailed steps with a generic restatement
|
|
135
156
|
- The params.skill field is CRITICAL for execution to use the skill's
|
|
136
157
|
Execution section
|
|
158
|
+
- The params.variant field is CRITICAL for config validation to resolve
|
|
159
|
+
variant placeholders in the skill's Execution section
|
|
160
|
+
- Example: If user selects "Deploy to production" and skill has {env.VARIANT.url},
|
|
161
|
+
params must include variant: "production" so validator can resolve to {env.production.url}
|
|
137
162
|
|
|
138
163
|
6. **Handle additional requirements beyond the skill:**
|
|
139
164
|
- If the user's query includes additional requirements beyond the skill,
|
|
@@ -157,6 +182,19 @@ Example 1 - Skill with parameter, variant specified:
|
|
|
157
182
|
params: { skill: "Process Data", target: "Alpha" } }
|
|
158
183
|
- WRONG: Tasks without params.skill or single task "Process Alpha"
|
|
159
184
|
|
|
185
|
+
Example 1b - Skill with variant placeholder in config:
|
|
186
|
+
- Skill name: "Navigate To Target"
|
|
187
|
+
- Skill config defines: target.alpha.path, target.beta.path, target.gamma.path
|
|
188
|
+
- Skill execution: "cd {target.VARIANT.path}"
|
|
189
|
+
- User: "navigate to beta"
|
|
190
|
+
- Variant matched: "beta"
|
|
191
|
+
- Correct task: { action: "Navigate to Beta target directory", type: "execute",
|
|
192
|
+
params: { skill: "Navigate To Target", variant: "beta" } }
|
|
193
|
+
- WRONG: params without variant field
|
|
194
|
+
- WRONG: task action "cd {target.VARIANT.path}" (uppercase VARIANT not resolved!)
|
|
195
|
+
- Note: The config validator will use params.variant="beta" to resolve
|
|
196
|
+
{target.VARIANT.path} → {target.beta.path}, then check if it exists in ~/.plsrc
|
|
197
|
+
|
|
160
198
|
Example 2 - Skill with parameter, variant NOT specified:
|
|
161
199
|
- Same skill as Example 1
|
|
162
200
|
- User: "process"
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
You are the validation component of "pls" (please), responsible for validating skill requirements and generating natural language descriptions for missing configuration values.
|
|
4
|
+
|
|
5
|
+
Your role is to help users understand what configuration values are needed and why, using context from skill descriptions to create clear, helpful prompts.
|
|
6
|
+
|
|
7
|
+
## Input
|
|
8
|
+
|
|
9
|
+
You will receive information about missing configuration values:
|
|
10
|
+
- Config path (e.g., "project.alpha.repo")
|
|
11
|
+
- Skill name that requires this config
|
|
12
|
+
- Variant (if applicable)
|
|
13
|
+
- Config type (string, boolean, number)
|
|
14
|
+
|
|
15
|
+
## Your Task
|
|
16
|
+
|
|
17
|
+
Generate a response with two required fields:
|
|
18
|
+
|
|
19
|
+
1. **message**: An empty string `""`
|
|
20
|
+
2. **tasks**: An array of CONFIG tasks, one for each missing config value
|
|
21
|
+
|
|
22
|
+
For each CONFIG task, create a natural language description that:
|
|
23
|
+
|
|
24
|
+
1. **Explains what the value is for** using context from the skill's description
|
|
25
|
+
2. **Keeps it SHORT** - one brief phrase (3-6 words max)
|
|
26
|
+
3. **Does NOT include the config path** - the path will be shown separately in debug mode
|
|
27
|
+
|
|
28
|
+
**CRITICAL**: You MUST include both the `message` field (set to empty string) and the `tasks` array in your response.
|
|
29
|
+
|
|
30
|
+
## Description Format
|
|
31
|
+
|
|
32
|
+
**Format:** "Brief description" (NO {config.path} at the end!)
|
|
33
|
+
|
|
34
|
+
The description should:
|
|
35
|
+
- Start with what the config value represents (e.g., "Path to...", "URL for...", "Name of...")
|
|
36
|
+
- Be SHORT and direct - no extra details or variant explanations
|
|
37
|
+
- NOT include the config path in curly brackets - that's added automatically
|
|
38
|
+
|
|
39
|
+
## Examples
|
|
40
|
+
|
|
41
|
+
### Example 1: Repository Path
|
|
42
|
+
|
|
43
|
+
**Input:**
|
|
44
|
+
- Config path: `project.alpha.repo`
|
|
45
|
+
- Skill: "Navigate To Project"
|
|
46
|
+
- Variant: "alpha"
|
|
47
|
+
|
|
48
|
+
**Correct output:**
|
|
49
|
+
```
|
|
50
|
+
message: ""
|
|
51
|
+
tasks: [
|
|
52
|
+
{
|
|
53
|
+
action: "Path to Alpha repository {project.alpha.repo}",
|
|
54
|
+
type: "config",
|
|
55
|
+
params: { key: "project.alpha.repo" }
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Example 2: Environment URL
|
|
61
|
+
|
|
62
|
+
**Input:**
|
|
63
|
+
- Config path: `env.staging.url`
|
|
64
|
+
- Skill: "Deploy Service"
|
|
65
|
+
- Variant: "staging"
|
|
66
|
+
|
|
67
|
+
**Correct output:**
|
|
68
|
+
```
|
|
69
|
+
message: ""
|
|
70
|
+
tasks: [
|
|
71
|
+
{
|
|
72
|
+
action: "Staging environment URL {env.staging.url}",
|
|
73
|
+
type: "config",
|
|
74
|
+
params: { key: "env.staging.url" }
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Example 3: Project Directory
|
|
80
|
+
|
|
81
|
+
**Input:**
|
|
82
|
+
- Config path: `workspace.beta.path`
|
|
83
|
+
- Skill: "Process Workspace"
|
|
84
|
+
- Variant: "beta"
|
|
85
|
+
|
|
86
|
+
**Correct output:**
|
|
87
|
+
```
|
|
88
|
+
message: ""
|
|
89
|
+
tasks: [
|
|
90
|
+
{
|
|
91
|
+
action: "Path to Beta workspace {workspace.beta.path}",
|
|
92
|
+
type: "config",
|
|
93
|
+
params: { key: "workspace.beta.path" }
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Guidelines
|
|
99
|
+
|
|
100
|
+
1. **Use skill context**: Read the skill's Description section to understand what the variant represents
|
|
101
|
+
2. **Be specific**: Don't just say "Repository path" - say "Alpha project repository path"
|
|
102
|
+
3. **Add helpful details**: Include information from the description (e.g., "legacy implementation")
|
|
103
|
+
4. **Keep it concise**: One sentence that clearly explains what's needed
|
|
104
|
+
5. **Always include the path**: End with `{config.path}` for technical reference
|
|
105
|
+
|
|
106
|
+
## Common Config Types
|
|
107
|
+
|
|
108
|
+
- **repo / repository**: "Path to [name] repository"
|
|
109
|
+
- **path / dir / directory**: "Path to [name] directory"
|
|
110
|
+
- **url**: "[Name] URL"
|
|
111
|
+
- **host**: "[Name] host address"
|
|
112
|
+
- **port**: "[Name] port number"
|
|
113
|
+
- **name**: "Name of [context]"
|
|
114
|
+
- **key / token / secret**: "[Name] authentication key/token/secret"
|
|
115
|
+
- **enabled**: "Enable/disable [feature]"
|
|
116
|
+
|
|
117
|
+
## Response Format
|
|
118
|
+
|
|
119
|
+
Return a message field (can be empty string) and an array of CONFIG tasks:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
message: ""
|
|
123
|
+
tasks: [
|
|
124
|
+
{
|
|
125
|
+
action: "Natural description {config.path}",
|
|
126
|
+
type: "config",
|
|
127
|
+
params: { key: "config.path" }
|
|
128
|
+
},
|
|
129
|
+
// ... more tasks
|
|
130
|
+
]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Important Notes
|
|
134
|
+
|
|
135
|
+
- All tasks must have type "config"
|
|
136
|
+
- All tasks must include params.key with the config path
|
|
137
|
+
- Descriptions should be helpful and contextual, not just technical
|
|
138
|
+
- Use information from Available Skills section to provide context
|
|
139
|
+
- Keep descriptions to one concise sentence
|
package/dist/handlers/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ComponentName, FeedbackType } from '../types/types.js';
|
|
2
2
|
import { createAnthropicService, } from '../services/anthropic.js';
|
|
3
|
-
import { createCommandDefinition, createFeedback, markAsDone, } from '../services/components.js';
|
|
3
|
+
import { createCommandDefinition, createExecuteDefinition, createFeedback, markAsDone, } from '../services/components.js';
|
|
4
4
|
import { saveAnthropicConfig, saveConfig } from '../services/configuration.js';
|
|
5
5
|
import { FeedbackMessages } from '../services/messages.js';
|
|
6
6
|
import { exitApp } from '../services/process.js';
|
|
@@ -33,18 +33,27 @@ export function createConfigHandlers(ops, handleAborted, command, commandHandler
|
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Creates config execution finished handler for CONFIG skill
|
|
36
|
-
* Saves arbitrary config keys and
|
|
36
|
+
* Saves arbitrary config keys and optionally continues with execution
|
|
37
37
|
*/
|
|
38
|
-
export function createConfigExecutionFinishedHandler(addToTimeline, keys) {
|
|
38
|
+
export function createConfigExecutionFinishedHandler(addToTimeline, keys, tasks, service, executeHandlers) {
|
|
39
39
|
return (config) => {
|
|
40
|
+
// Group by top-level section
|
|
40
41
|
const sections = {};
|
|
41
42
|
for (const fullKey of keys) {
|
|
42
43
|
const parts = fullKey.split('.');
|
|
43
44
|
const shortKey = parts[parts.length - 1];
|
|
44
|
-
const
|
|
45
|
-
|
|
45
|
+
const topSection = parts[0];
|
|
46
|
+
// Initialize section if needed
|
|
47
|
+
sections[topSection] = sections[topSection] ?? {};
|
|
46
48
|
if (shortKey in config) {
|
|
47
|
-
|
|
49
|
+
const value = config[shortKey];
|
|
50
|
+
// Build nested structure recursively
|
|
51
|
+
let current = sections[topSection];
|
|
52
|
+
for (let i = 1; i < parts.length - 1; i++) {
|
|
53
|
+
current[parts[i]] = current[parts[i]] ?? {};
|
|
54
|
+
current = current[parts[i]];
|
|
55
|
+
}
|
|
56
|
+
current[parts[parts.length - 1]] = value;
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
for (const [section, sectionConfig] of Object.entries(sections)) {
|
|
@@ -52,6 +61,14 @@ export function createConfigExecutionFinishedHandler(addToTimeline, keys) {
|
|
|
52
61
|
}
|
|
53
62
|
return withQueueHandler(ComponentName.Config, (first, rest) => {
|
|
54
63
|
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Succeeded, FeedbackMessages.ConfigurationComplete));
|
|
64
|
+
// If tasks are provided, continue with execution
|
|
65
|
+
if (tasks && service && executeHandlers) {
|
|
66
|
+
return [
|
|
67
|
+
...rest,
|
|
68
|
+
createExecuteDefinition(tasks, service, executeHandlers.onError, executeHandlers.onComplete, executeHandlers.onAborted),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
// Otherwise, exit (legacy behavior for initial setup)
|
|
55
72
|
exitApp(0);
|
|
56
73
|
return rest;
|
|
57
74
|
}, false, 0);
|
package/dist/handlers/execute.js
CHANGED
|
@@ -8,6 +8,7 @@ import { withQueueHandler } from '../services/queue.js';
|
|
|
8
8
|
* Creates all execute handlers
|
|
9
9
|
*/
|
|
10
10
|
export function createExecuteHandlers(ops, handleAborted) {
|
|
11
|
+
void handleAborted;
|
|
11
12
|
const onError = (error) => {
|
|
12
13
|
ops.setQueue(withQueueHandler(ComponentName.Execute, (first) => {
|
|
13
14
|
ops.addToTimeline(markAsDone(first), createFeedback(FeedbackType.Failed, error));
|
|
@@ -31,8 +32,15 @@ export function createExecuteHandlers(ops, handleAborted) {
|
|
|
31
32
|
return [];
|
|
32
33
|
}));
|
|
33
34
|
};
|
|
34
|
-
const onAborted = () => {
|
|
35
|
-
|
|
35
|
+
const onAborted = (elapsedTime) => {
|
|
36
|
+
ops.setQueue(withQueueHandler(ComponentName.Execute, (first) => {
|
|
37
|
+
const message = elapsedTime > 0
|
|
38
|
+
? `The execution was cancelled after ${formatDuration(elapsedTime)}.`
|
|
39
|
+
: 'The execution was cancelled.';
|
|
40
|
+
ops.addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, message));
|
|
41
|
+
exitApp(0);
|
|
42
|
+
return [];
|
|
43
|
+
}));
|
|
36
44
|
};
|
|
37
45
|
return { onError, onComplete, onAborted };
|
|
38
46
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ComponentName, FeedbackType, TaskType } from '../types/types.js';
|
|
2
|
-
import { createAnswerDefinition, createConfigDefinitionWithKeys, createExecuteDefinition, createFeedback, createIntrospectDefinition, markAsDone, } from '../services/components.js';
|
|
2
|
+
import { createAnswerDefinition, createConfigDefinitionWithKeys, createExecuteDefinition, createFeedback, createIntrospectDefinition, createValidateDefinition, markAsDone, } from '../services/components.js';
|
|
3
|
+
import { StepType } from '../ui/Config.js';
|
|
3
4
|
import { getCancellationMessage } from '../services/messages.js';
|
|
4
5
|
import { exitApp } from '../services/process.js';
|
|
5
6
|
import { withQueueHandler } from '../services/queue.js';
|
|
6
7
|
import { createConfigExecutionAbortedHandler, createConfigExecutionFinishedHandler, } from './config.js';
|
|
8
|
+
import { validateExecuteTasks } from '../services/execution-validator.js';
|
|
7
9
|
/**
|
|
8
10
|
* Creates all execution handlers
|
|
9
11
|
*/
|
|
@@ -49,6 +51,71 @@ export function createExecutionHandlers(ops, taskHandlers) {
|
|
|
49
51
|
];
|
|
50
52
|
}
|
|
51
53
|
else if (allExecute && tasks.length > 0) {
|
|
54
|
+
// Validate config requirements before execution
|
|
55
|
+
const missingConfig = validateExecuteTasks(tasks);
|
|
56
|
+
if (missingConfig.length > 0) {
|
|
57
|
+
// Config is missing - call VALIDATE tool to get contextual descriptions
|
|
58
|
+
const keys = missingConfig.map((req) => req.path);
|
|
59
|
+
const userRequest = tasks.map((t) => t.action).join(', ');
|
|
60
|
+
ops.addToTimeline(markAsDone(first));
|
|
61
|
+
// Create handlers for Validate completion
|
|
62
|
+
const handleValidateComplete = (configWithDescriptions) => {
|
|
63
|
+
ops.setQueue(withQueueHandler(ComponentName.Validate, (first) => {
|
|
64
|
+
// Create CONFIG component with descriptions from VALIDATE
|
|
65
|
+
const handleConfigFinished = (config) => {
|
|
66
|
+
ops.setQueue(createConfigExecutionFinishedHandler(ops.addToTimeline, keys, tasks, service, taskHandlers.execute)(config));
|
|
67
|
+
};
|
|
68
|
+
const handleConfigAborted = () => {
|
|
69
|
+
ops.setQueue(createConfigExecutionAbortedHandler(ops.addToTimeline)());
|
|
70
|
+
};
|
|
71
|
+
// Create config steps from validated descriptions
|
|
72
|
+
const steps = configWithDescriptions.map((req) => {
|
|
73
|
+
const keyParts = req.path.split('.');
|
|
74
|
+
const shortKey = keyParts[keyParts.length - 1];
|
|
75
|
+
// Extract description without the {path} suffix
|
|
76
|
+
// Format from VALIDATE: "Description {path}"
|
|
77
|
+
let description = req.description || req.path;
|
|
78
|
+
const pathPattern = /\s*\{[^}]+\}\s*$/;
|
|
79
|
+
description = description.replace(pathPattern, '').trim();
|
|
80
|
+
const step = {
|
|
81
|
+
description,
|
|
82
|
+
key: shortKey,
|
|
83
|
+
path: req.path,
|
|
84
|
+
type: StepType.Text,
|
|
85
|
+
value: null,
|
|
86
|
+
validate: () => true,
|
|
87
|
+
};
|
|
88
|
+
return step;
|
|
89
|
+
});
|
|
90
|
+
// Mark Validate as done and move to timeline
|
|
91
|
+
ops.addToTimeline(markAsDone(first));
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
id: crypto.randomUUID(),
|
|
95
|
+
name: ComponentName.Config,
|
|
96
|
+
state: { done: false },
|
|
97
|
+
props: {
|
|
98
|
+
steps,
|
|
99
|
+
onFinished: handleConfigFinished,
|
|
100
|
+
onAborted: handleConfigAborted,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
}));
|
|
105
|
+
};
|
|
106
|
+
const handleValidateError = (error) => {
|
|
107
|
+
ops.addToTimeline(createFeedback(FeedbackType.Failed, error));
|
|
108
|
+
exitApp(1);
|
|
109
|
+
};
|
|
110
|
+
const handleValidateAborted = () => {
|
|
111
|
+
ops.addToTimeline(createFeedback(FeedbackType.Aborted, 'Configuration validation cancelled'));
|
|
112
|
+
exitApp(0);
|
|
113
|
+
};
|
|
114
|
+
return [
|
|
115
|
+
createValidateDefinition(missingConfig, userRequest, service, handleValidateError, handleValidateComplete, handleValidateAborted),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
// No missing config - execute directly
|
|
52
119
|
ops.addToTimeline(markAsDone(first));
|
|
53
120
|
return [
|
|
54
121
|
createExecuteDefinition(tasks, service, taskHandlers.execute.onError, taskHandlers.execute.onComplete, taskHandlers.execute.onAborted),
|
|
@@ -18,7 +18,8 @@ export class AnthropicService {
|
|
|
18
18
|
// Add skills section for applicable tools
|
|
19
19
|
if (toolName === 'plan' ||
|
|
20
20
|
toolName === 'introspect' ||
|
|
21
|
-
toolName === 'execute'
|
|
21
|
+
toolName === 'execute' ||
|
|
22
|
+
toolName === 'validate') {
|
|
22
23
|
const skills = loadSkills();
|
|
23
24
|
const skillsSection = formatSkillsForPrompt(skills);
|
|
24
25
|
systemPrompt += skillsSection;
|
|
@@ -114,7 +115,7 @@ export class AnthropicService {
|
|
|
114
115
|
};
|
|
115
116
|
}
|
|
116
117
|
// Handle plan and introspect tool responses
|
|
117
|
-
if (
|
|
118
|
+
if (input.message === undefined || typeof input.message !== 'string') {
|
|
118
119
|
throw new Error('Invalid tool response: missing or invalid message field');
|
|
119
120
|
}
|
|
120
121
|
if (!input.tasks || !Array.isArray(input.tasks)) {
|
package/dist/services/colors.js
CHANGED
|
@@ -15,8 +15,8 @@ export const Palette = {
|
|
|
15
15
|
BrightGreen: '#3e9a3e',
|
|
16
16
|
Yellow: '#cccc5c',
|
|
17
17
|
LightYellow: '#d4d47a',
|
|
18
|
-
Orange: '#
|
|
19
|
-
DarkOrange: '#
|
|
18
|
+
Orange: '#f48c80',
|
|
19
|
+
DarkOrange: '#ab5e40',
|
|
20
20
|
BurntOrange: '#cc7a5c',
|
|
21
21
|
Red: '#cc5c5c',
|
|
22
22
|
Cyan: '#5c9ccc',
|
|
@@ -65,8 +65,22 @@ export function createConfigStepsFromSchema(keys) {
|
|
|
65
65
|
// Config doesn't exist yet, use defaults
|
|
66
66
|
}
|
|
67
67
|
return keys.map((key) => {
|
|
68
|
+
// Check if key is in schema (built-in config)
|
|
68
69
|
if (!(key in schema)) {
|
|
69
|
-
|
|
70
|
+
// Key is not in schema - it's from a skill
|
|
71
|
+
// Create a simple text step with placeholder description
|
|
72
|
+
const keyParts = key.split('.');
|
|
73
|
+
const shortKey = keyParts[keyParts.length - 1];
|
|
74
|
+
const description = keyParts
|
|
75
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
76
|
+
.join(' ');
|
|
77
|
+
return {
|
|
78
|
+
description: `${description} {${key}}`,
|
|
79
|
+
key: shortKey,
|
|
80
|
+
type: StepType.Text,
|
|
81
|
+
value: null,
|
|
82
|
+
validate: () => true, // Accept any string for now
|
|
83
|
+
};
|
|
70
84
|
}
|
|
71
85
|
const definition = schema[key];
|
|
72
86
|
const currentValue = getConfigValue(currentConfig, key);
|
|
@@ -318,3 +332,21 @@ export function createExecuteDefinition(tasks, service, onError, onComplete, onA
|
|
|
318
332
|
},
|
|
319
333
|
};
|
|
320
334
|
}
|
|
335
|
+
export function createValidateDefinition(missingConfig, userRequest, service, onError, onComplete, onAborted) {
|
|
336
|
+
return {
|
|
337
|
+
id: randomUUID(),
|
|
338
|
+
name: ComponentName.Validate,
|
|
339
|
+
state: {
|
|
340
|
+
done: false,
|
|
341
|
+
isLoading: true,
|
|
342
|
+
},
|
|
343
|
+
props: {
|
|
344
|
+
missingConfig,
|
|
345
|
+
userRequest,
|
|
346
|
+
service,
|
|
347
|
+
onError,
|
|
348
|
+
onComplete,
|
|
349
|
+
onAborted,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import YAML from 'yaml';
|
|
5
|
+
/**
|
|
6
|
+
* Load user config from ~/.plsrc
|
|
7
|
+
*/
|
|
8
|
+
export function loadUserConfig() {
|
|
9
|
+
const configPath = join(homedir(), '.plsrc');
|
|
10
|
+
if (!existsSync(configPath)) {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
15
|
+
const parsed = YAML.parse(content);
|
|
16
|
+
if (parsed && typeof parsed === 'object') {
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Check if config has a specific path
|
|
27
|
+
*/
|
|
28
|
+
export function hasConfigPath(config, path) {
|
|
29
|
+
const parts = path.split('.');
|
|
30
|
+
let current = config;
|
|
31
|
+
for (const part of parts) {
|
|
32
|
+
if (current === null || current === undefined) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (typeof current !== 'object') {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
current = current[part];
|
|
39
|
+
}
|
|
40
|
+
return (current !== null &&
|
|
41
|
+
current !== undefined &&
|
|
42
|
+
(typeof current === 'string' ||
|
|
43
|
+
typeof current === 'boolean' ||
|
|
44
|
+
typeof current === 'number'));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get config value at path
|
|
48
|
+
*/
|
|
49
|
+
export function getConfigValue(config, path) {
|
|
50
|
+
const parts = path.split('.');
|
|
51
|
+
let current = config;
|
|
52
|
+
for (const part of parts) {
|
|
53
|
+
if (current === null || current === undefined) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
if (typeof current !== 'object') {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
current = current[part];
|
|
60
|
+
}
|
|
61
|
+
if (typeof current === 'string' ||
|
|
62
|
+
typeof current === 'boolean' ||
|
|
63
|
+
typeof current === 'number') {
|
|
64
|
+
return current;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { extractPlaceholders, pathToString, resolveVariant, } from './placeholder-resolver.js';
|
|
2
|
+
import { loadUserConfig, hasConfigPath } from './config-loader.js';
|
|
3
|
+
import { loadSkills } from './skills.js';
|
|
4
|
+
import { expandSkillReferences } from './skill-expander.js';
|
|
5
|
+
import { getConfigType, parseSkillMarkdown } from './skill-parser.js';
|
|
6
|
+
/**
|
|
7
|
+
* Validate config requirements for execute tasks
|
|
8
|
+
* Returns missing config requirements
|
|
9
|
+
*/
|
|
10
|
+
export function validateExecuteTasks(tasks) {
|
|
11
|
+
const userConfig = loadUserConfig();
|
|
12
|
+
const missing = [];
|
|
13
|
+
const seenPaths = new Set();
|
|
14
|
+
// Load and parse all skills for validation
|
|
15
|
+
const skillContents = loadSkills();
|
|
16
|
+
const parsedSkills = skillContents
|
|
17
|
+
.map((content) => parseSkillMarkdown(content))
|
|
18
|
+
.filter((s) => s !== null);
|
|
19
|
+
const skillLookup = (name) => parsedSkills.find((s) => s.name === name) || null;
|
|
20
|
+
for (const task of tasks) {
|
|
21
|
+
// Check if task originates from a skill
|
|
22
|
+
const skillName = typeof task.params?.skill === 'string' ? task.params.skill : null;
|
|
23
|
+
if (skillName) {
|
|
24
|
+
// Task comes from a skill - check skill's Execution section
|
|
25
|
+
const skill = skillLookup(skillName);
|
|
26
|
+
if (!skill || !skill.execution) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Get variant from task params (if any)
|
|
30
|
+
// Try params.variant first, then look for other param keys that might be the variant
|
|
31
|
+
let variant = null;
|
|
32
|
+
if (typeof task.params?.variant === 'string') {
|
|
33
|
+
variant = task.params.variant.toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
else if (task.params && typeof task.params === 'object') {
|
|
36
|
+
// Look for other params that could be the variant (e.g., product, target, option, etc.)
|
|
37
|
+
// Exclude known non-variant params
|
|
38
|
+
const excludeKeys = new Set(['skill', 'type']);
|
|
39
|
+
for (const [key, value] of Object.entries(task.params)) {
|
|
40
|
+
if (!excludeKeys.has(key) && typeof value === 'string') {
|
|
41
|
+
variant = value.toLowerCase();
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Expand skill references to get actual commands
|
|
47
|
+
const expanded = expandSkillReferences(skill.execution, skillLookup);
|
|
48
|
+
// Extract placeholders from actual commands
|
|
49
|
+
for (const line of expanded) {
|
|
50
|
+
const placeholders = extractPlaceholders(line);
|
|
51
|
+
for (const placeholder of placeholders) {
|
|
52
|
+
let resolvedPath;
|
|
53
|
+
if (placeholder.hasVariant) {
|
|
54
|
+
// Variant placeholder - resolve with variant from params
|
|
55
|
+
if (!variant) {
|
|
56
|
+
// No variant provided - skip this placeholder
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const resolvedPathArray = resolveVariant(placeholder.path, variant);
|
|
60
|
+
resolvedPath = pathToString(resolvedPathArray);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Strict placeholder - use as-is
|
|
64
|
+
resolvedPath = pathToString(placeholder.path);
|
|
65
|
+
}
|
|
66
|
+
// Skip if already processed
|
|
67
|
+
if (seenPaths.has(resolvedPath)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
seenPaths.add(resolvedPath);
|
|
71
|
+
// Check if config exists
|
|
72
|
+
if (!hasConfigPath(userConfig, resolvedPath)) {
|
|
73
|
+
// Get type from skill config
|
|
74
|
+
const type = skill.config
|
|
75
|
+
? getConfigType(skill.config, resolvedPath)
|
|
76
|
+
: undefined;
|
|
77
|
+
missing.push({
|
|
78
|
+
path: resolvedPath,
|
|
79
|
+
type: type || 'string',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Task doesn't come from a skill - check task action for placeholders
|
|
87
|
+
const placeholders = extractPlaceholders(task.action);
|
|
88
|
+
for (const placeholder of placeholders) {
|
|
89
|
+
// Skip variant placeholders - they should have been resolved during planning
|
|
90
|
+
if (placeholder.hasVariant) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const path = placeholder.path.join('.');
|
|
94
|
+
// Skip if already processed
|
|
95
|
+
if (seenPaths.has(path)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
seenPaths.add(path);
|
|
99
|
+
// Check if config exists
|
|
100
|
+
if (!hasConfigPath(userConfig, path)) {
|
|
101
|
+
missing.push({
|
|
102
|
+
path,
|
|
103
|
+
type: 'string', // Default to string for now
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return missing;
|
|
110
|
+
}
|