prompt-language-shell 0.7.0 → 0.7.4

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 CHANGED
@@ -159,7 +159,6 @@ $ pls build test
159
159
 
160
160
  ## Roadmap
161
161
 
162
- - **0.7** - Comprehend skill, simplified prompts, better debugging
163
162
  - **0.8** - Sequential and interlaced skill execution
164
163
  - **0.9** - Learn skill, codebase refinement, complex dependency handling
165
164
  - **1.0** - Production release
@@ -52,31 +52,39 @@ export class AnthropicService {
52
52
  this.client = new Anthropic({ apiKey: key });
53
53
  this.model = model;
54
54
  }
55
- async processWithTool(command, toolName) {
55
+ async processWithTool(command, toolName, customInstructions) {
56
56
  // Load tool from registry
57
57
  const tool = toolRegistry.getSchema(toolName);
58
- const instructions = toolRegistry.getInstructions(toolName);
59
- // Build system prompt with additional context based on tool
60
- let systemPrompt = instructions;
61
- // Add skills section for applicable tools
62
- if (toolName === 'plan' ||
63
- toolName === 'introspect' ||
64
- toolName === 'execute' ||
65
- toolName === 'validate') {
66
- const skills = loadSkillsWithValidation();
67
- const skillsSection = formatSkillsForPrompt(skills);
68
- systemPrompt += skillsSection;
58
+ // Use custom instructions if provided, otherwise load from registry
59
+ let systemPrompt;
60
+ if (customInstructions) {
61
+ // Custom instructions provided (typically for testing)
62
+ systemPrompt = customInstructions;
69
63
  }
70
- // Add config structure for config tool only
71
- if (toolName === 'config') {
72
- const configStructure = getAvailableConfigStructure();
73
- const configuredKeys = getConfiguredKeys();
74
- const configSection = '\n## Available Configuration\n\n' +
75
- 'Config structure (key: description):\n' +
76
- JSON.stringify(configStructure, null, 2) +
77
- '\n\nConfigured keys (keys that exist in config file):\n' +
78
- JSON.stringify(configuredKeys, null, 2);
79
- systemPrompt += configSection;
64
+ else {
65
+ // Load and build system prompt automatically (production)
66
+ const instructions = toolRegistry.getInstructions(toolName);
67
+ systemPrompt = instructions;
68
+ // Add skills section for applicable tools
69
+ if (toolName === 'schedule' ||
70
+ toolName === 'introspect' ||
71
+ toolName === 'execute' ||
72
+ toolName === 'validate') {
73
+ const skills = loadSkillsWithValidation();
74
+ const skillsSection = formatSkillsForPrompt(skills);
75
+ systemPrompt += skillsSection;
76
+ }
77
+ // Add config structure for config tool only
78
+ if (toolName === 'config') {
79
+ const configStructure = getAvailableConfigStructure();
80
+ const configuredKeys = getConfiguredKeys();
81
+ const configSection = '\n## Available Configuration\n\n' +
82
+ 'Config structure (key: description):\n' +
83
+ JSON.stringify(configStructure, null, 2) +
84
+ '\n\nConfigured keys (keys that exist in config file):\n' +
85
+ JSON.stringify(configuredKeys, null, 2);
86
+ systemPrompt += configSection;
87
+ }
80
88
  }
81
89
  // Build tools array - add web search for answer tool
82
90
  const tools = [tool];
@@ -177,7 +185,7 @@ export class AnthropicService {
177
185
  debug,
178
186
  };
179
187
  }
180
- // Handle plan and introspect tool responses
188
+ // Handle schedule and introspect tool responses
181
189
  if (input.message === undefined || typeof input.message !== 'string') {
182
190
  throw new Error('Invalid tool response: missing or invalid message field');
183
191
  }
@@ -1,4 +1,5 @@
1
1
  import { FeedbackType, TaskType } from '../types/types.js';
2
+ import { DebugLevel } from './configuration.js';
2
3
  /**
3
4
  * Base color palette - raw color values with descriptive names.
4
5
  * All colors used in the interface are defined here.
@@ -56,7 +57,7 @@ export const Colors = {
56
57
  },
57
58
  Type: {
58
59
  Config: Palette.Cyan,
59
- Plan: Palette.LightCyan,
60
+ Schedule: Palette.LightCyan,
60
61
  Execute: Palette.Green,
61
62
  Answer: Palette.Purple,
62
63
  Introspect: Palette.Purple,
@@ -65,6 +66,7 @@ export const Colors = {
65
66
  Ignore: Palette.BurntOrange,
66
67
  Select: Palette.SteelBlue,
67
68
  Discard: Palette.DarkOrange,
69
+ Group: Palette.Yellow,
68
70
  },
69
71
  Origin: {
70
72
  BuiltIn: Palette.Cyan,
@@ -80,9 +82,9 @@ const taskColors = {
80
82
  description: Colors.Label.Default,
81
83
  type: Colors.Type.Config,
82
84
  },
83
- [TaskType.Plan]: {
85
+ [TaskType.Schedule]: {
84
86
  description: Colors.Label.Default,
85
- type: Colors.Type.Plan,
87
+ type: Colors.Type.Schedule,
86
88
  },
87
89
  [TaskType.Execute]: {
88
90
  description: Colors.Label.Default,
@@ -116,6 +118,10 @@ const taskColors = {
116
118
  description: Colors.Label.Discarded,
117
119
  type: Colors.Type.Discard,
118
120
  },
121
+ [TaskType.Group]: {
122
+ description: Colors.Label.Default,
123
+ type: Colors.Type.Group,
124
+ },
119
125
  };
120
126
  /**
121
127
  * Feedback-specific color mappings (internal)
@@ -174,3 +180,33 @@ export function getFeedbackColor(type, isCurrent) {
174
180
  export function getTextColor(isCurrent) {
175
181
  return isCurrent ? Colors.Text.Active : Colors.Text.Inactive;
176
182
  }
183
+ /**
184
+ * Verbose task type labels - two-word descriptions that start with the same
185
+ * keyword as the short version
186
+ */
187
+ const verboseTaskTypeLabels = {
188
+ [TaskType.Config]: 'configure option',
189
+ [TaskType.Schedule]: 'schedule tasks',
190
+ [TaskType.Execute]: 'execute command',
191
+ [TaskType.Answer]: 'answer question',
192
+ [TaskType.Introspect]: 'introspect capabilities',
193
+ [TaskType.Report]: 'report results',
194
+ [TaskType.Define]: 'define options',
195
+ [TaskType.Ignore]: 'ignore request',
196
+ [TaskType.Select]: 'select option',
197
+ [TaskType.Discard]: 'discard option',
198
+ [TaskType.Group]: 'group tasks',
199
+ };
200
+ /**
201
+ * Get task type label based on debug level.
202
+ *
203
+ * Returns:
204
+ * - Verbose label (2 words) in verbose mode
205
+ * - Short label (1 word) in info mode or when debug is off
206
+ */
207
+ export function getTaskTypeLabel(type, debug) {
208
+ if (debug === DebugLevel.Verbose) {
209
+ return verboseTaskTypeLabels[type];
210
+ }
211
+ return type;
212
+ }
@@ -216,10 +216,10 @@ export function createCommandDefinition(command, service) {
216
216
  },
217
217
  };
218
218
  }
219
- export function createPlanDefinition(message, tasks, onSelectionConfirmed) {
219
+ export function createScheduleDefinition(message, tasks, onSelectionConfirmed) {
220
220
  return {
221
221
  id: randomUUID(),
222
- name: ComponentName.Plan,
222
+ name: ComponentName.Schedule,
223
223
  status: ComponentStatus.Awaiting,
224
224
  state: {
225
225
  highlightedIndex: null,
@@ -21,7 +21,7 @@ export async function handleRefinement(selectedTasks, service, originalCommand,
21
21
  })
22
22
  .join(', ');
23
23
  // Call LLM to refine plan with selected tasks
24
- const refinedResult = await service.processWithTool(refinedCommand, 'plan');
24
+ const refinedResult = await service.processWithTool(refinedCommand, 'schedule');
25
25
  // Complete the Refinement component
26
26
  handlers.completeActive();
27
27
  // Add debug components to timeline if present
@@ -34,17 +34,17 @@ class ToolRegistry {
34
34
  export const toolRegistry = new ToolRegistry();
35
35
  // Register built-in tools
36
36
  import { answerTool } from '../tools/answer.tool.js';
37
- import { configTool } from '../tools/config.tool.js';
37
+ import { configureTool } from '../tools/configure.tool.js';
38
38
  import { executeTool } from '../tools/execute.tool.js';
39
39
  import { introspectTool } from '../tools/introspect.tool.js';
40
- import { planTool } from '../tools/plan.tool.js';
40
+ import { scheduleTool } from '../tools/schedule.tool.js';
41
41
  import { validateTool } from '../tools/validate.tool.js';
42
42
  const tools = {
43
43
  answer: answerTool,
44
- config: configTool,
44
+ configure: configureTool,
45
45
  execute: executeTool,
46
46
  introspect: introspectTool,
47
- plan: planTool,
47
+ schedule: scheduleTool,
48
48
  validate: validateTool,
49
49
  };
50
50
  for (const [name, schema] of Object.entries(tools)) {
@@ -1,5 +1,5 @@
1
1
  import { TaskType } from '../types/types.js';
2
- import { createAnswerDefinition, createConfigDefinitionWithKeys, createConfirmDefinition, createExecuteDefinition, createFeedback, createIntrospectDefinition, createMessage, createPlanDefinition, createValidateDefinition, } from './components.js';
2
+ import { createAnswerDefinition, createConfigDefinitionWithKeys, createConfirmDefinition, createExecuteDefinition, createFeedback, createIntrospectDefinition, createMessage, createScheduleDefinition, createValidateDefinition, } from './components.js';
3
3
  import { saveConfig, unflattenConfig } from './configuration.js';
4
4
  import { FeedbackType } from '../types/types.js';
5
5
  import { validateExecuteTasks } from './validator.js';
@@ -33,52 +33,63 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
33
33
  }
34
34
  const operation = getOperationName(validTasks);
35
35
  if (hasDefineTask) {
36
- // Has DEFINE tasks - add Plan to queue for user selection
36
+ // Has DEFINE tasks - add Schedule to queue for user selection
37
37
  // Refinement flow will call this function again with refined tasks
38
- const planDefinition = createPlanDefinition(message, validTasks);
39
- handlers.addToQueue(planDefinition);
38
+ const scheduleDefinition = createScheduleDefinition(message, validTasks);
39
+ handlers.addToQueue(scheduleDefinition);
40
40
  }
41
41
  else {
42
- // No DEFINE tasks - Plan auto-completes and adds Confirm to queue
43
- // When Plan activates, Command moves to timeline
44
- // When Plan completes, it moves to pending
45
- // When Confirm activates, Plan stays pending (visible for context)
46
- const planDefinition = createPlanDefinition(message, validTasks, () => {
47
- // Plan completed - add Confirm to queue
42
+ // No DEFINE tasks - Schedule auto-completes and adds Confirm to queue
43
+ // When Schedule activates, Command moves to timeline
44
+ // When Schedule completes, it moves to pending
45
+ // When Confirm activates, Schedule stays pending (visible for context)
46
+ const scheduleDefinition = createScheduleDefinition(message, validTasks, () => {
47
+ // Schedule completed - add Confirm to queue
48
48
  const confirmDefinition = createConfirmDefinition(() => {
49
- // User confirmed - complete both Confirm and Plan, then route to appropriate component
49
+ // User confirmed - complete both Confirm and Schedule, then route to appropriate component
50
50
  handlers.completeActiveAndPending();
51
51
  executeTasksAfterConfirm(validTasks, service, userRequest, handlers);
52
52
  }, () => {
53
- // User cancelled - complete both Confirm and Plan, then show cancellation
53
+ // User cancelled - complete both Confirm and Schedule, then show cancellation
54
54
  handlers.completeActiveAndPending();
55
55
  const message = getCancellationMessage(operation);
56
56
  handlers.addToQueue(createFeedback(FeedbackType.Aborted, message));
57
57
  });
58
58
  handlers.addToQueue(confirmDefinition);
59
59
  });
60
- handlers.addToQueue(planDefinition);
60
+ handlers.addToQueue(scheduleDefinition);
61
61
  }
62
62
  }
63
63
  /**
64
- * Validate that all tasks have the same type
65
- * Per FLOWS.md: "Mixed types Error (not supported)"
64
+ * Validate task types - allows mixed types at top level with Groups,
65
+ * but each Group must have uniform subtask types
66
66
  */
67
67
  function validateTaskTypes(tasks) {
68
68
  if (tasks.length === 0)
69
69
  return;
70
- const types = new Set(tasks.map((task) => task.type));
71
- if (types.size > 1) {
72
- throw new Error(getMixedTaskTypesError(Array.from(types)));
70
+ // Cast to ScheduledTask to access subtasks property
71
+ const scheduledTasks = tasks;
72
+ // Check each Group task's subtasks for uniform types
73
+ for (const task of scheduledTasks) {
74
+ if (task.type === TaskType.Group &&
75
+ task.subtasks &&
76
+ task.subtasks.length > 0) {
77
+ const subtaskTypes = new Set(task.subtasks.map((t) => t.type));
78
+ if (subtaskTypes.size > 1) {
79
+ throw new Error(getMixedTaskTypesError(Array.from(subtaskTypes)));
80
+ }
81
+ // Recursively validate nested groups
82
+ validateTaskTypes(task.subtasks);
83
+ }
73
84
  }
74
85
  }
75
86
  /**
76
87
  * Execute tasks after confirmation (internal helper)
77
- * Validates task types after user has seen and confirmed the plan
88
+ * Validates task types and routes each type appropriately
89
+ * Supports mixed types at top level with Groups
78
90
  */
79
91
  function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
80
- // Validate all tasks have the same type after user confirmation
81
- // Per FLOWS.md: "Confirm component completes → Execution handler analyzes task types"
92
+ // Validate task types (Groups must have uniform subtasks)
82
93
  try {
83
94
  validateTaskTypes(tasks);
84
95
  }
@@ -86,75 +97,99 @@ function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
86
97
  handlers.onError(error instanceof Error ? error.message : String(error));
87
98
  return;
88
99
  }
89
- const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
90
- const allAnswer = tasks.every((task) => task.type === TaskType.Answer);
91
- const allConfig = tasks.every((task) => task.type === TaskType.Config);
92
- if (allAnswer) {
93
- const question = tasks[0].action;
94
- handlers.addToQueue(createAnswerDefinition(question, service));
100
+ // Flatten Group tasks to get actual executable subtasks
101
+ const flattenedTasks = [];
102
+ const scheduledTasks = tasks;
103
+ for (const task of scheduledTasks) {
104
+ if (task.type === TaskType.Group && task.subtasks) {
105
+ // Add all subtasks from the group
106
+ flattenedTasks.push(...task.subtasks);
107
+ }
108
+ else {
109
+ // Add non-group tasks as-is
110
+ flattenedTasks.push(task);
111
+ }
95
112
  }
96
- else if (allIntrospect) {
97
- handlers.addToQueue(createIntrospectDefinition(tasks, service));
113
+ // Group flattened tasks by type - initialize all TaskType keys with empty arrays
114
+ const tasksByType = {};
115
+ for (const type of Object.values(TaskType)) {
116
+ tasksByType[type] = [];
98
117
  }
99
- else if (allConfig) {
100
- // Route to Config flow - extract keys from task params
101
- const configKeys = tasks
102
- .map((task) => task.params?.key)
103
- .filter((key) => key !== undefined);
104
- handlers.addToQueue(createConfigDefinitionWithKeys(configKeys, (config) => {
105
- // Save config using the same pattern as Validate component
118
+ for (const task of flattenedTasks) {
119
+ tasksByType[task.type].push(task);
120
+ }
121
+ // Route each type group appropriately
122
+ for (const [type, typeTasks] of Object.entries(tasksByType)) {
123
+ const taskType = type;
124
+ // Skip empty task groups (pre-initialized but unused)
125
+ if (typeTasks.length === 0) {
126
+ continue;
127
+ }
128
+ if (taskType === TaskType.Answer) {
129
+ const question = typeTasks[0].action;
130
+ handlers.addToQueue(createAnswerDefinition(question, service));
131
+ }
132
+ else if (taskType === TaskType.Introspect) {
133
+ handlers.addToQueue(createIntrospectDefinition(typeTasks, service));
134
+ }
135
+ else if (taskType === TaskType.Config) {
136
+ // Route to Config flow - extract keys from task params
137
+ const configKeys = typeTasks
138
+ .map((task) => task.params?.key)
139
+ .filter((key) => key !== undefined);
140
+ handlers.addToQueue(createConfigDefinitionWithKeys(configKeys, (config) => {
141
+ // Save config - Config component will handle completion and feedback
142
+ try {
143
+ // Convert flat dotted keys to nested structure grouped by section
144
+ const configBySection = unflattenConfig(config);
145
+ // Save each section
146
+ for (const [section, sectionConfig] of Object.entries(configBySection)) {
147
+ saveConfig(section, sectionConfig);
148
+ }
149
+ }
150
+ catch (error) {
151
+ const errorMessage = error instanceof Error
152
+ ? error.message
153
+ : 'Failed to save configuration';
154
+ throw new Error(errorMessage);
155
+ }
156
+ }, (operation) => {
157
+ handlers.onAborted(operation);
158
+ }));
159
+ }
160
+ else if (taskType === TaskType.Execute) {
161
+ // Execute tasks with validation
106
162
  try {
107
- // Convert flat dotted keys to nested structure grouped by section
108
- const configBySection = unflattenConfig(config);
109
- // Save each section
110
- for (const [section, sectionConfig] of Object.entries(configBySection)) {
111
- saveConfig(section, sectionConfig);
163
+ const validation = validateExecuteTasks(typeTasks);
164
+ if (validation.validationErrors.length > 0) {
165
+ // Show error feedback for invalid skills
166
+ const errorMessages = validation.validationErrors.map((error) => {
167
+ const issuesList = error.issues
168
+ .map((issue) => ` - ${issue}`)
169
+ .join('\n');
170
+ return `Invalid skill definition "${error.skill}":\n\n${issuesList}`;
171
+ });
172
+ handlers.addToQueue(createFeedback(FeedbackType.Failed, errorMessages.join('\n\n')));
173
+ }
174
+ else if (validation.missingConfig.length > 0) {
175
+ handlers.addToQueue(createValidateDefinition(validation.missingConfig, userRequest, service, (error) => {
176
+ handlers.onError(error);
177
+ }, () => {
178
+ handlers.addToQueue(createExecuteDefinition(typeTasks, service));
179
+ }, (operation) => {
180
+ handlers.onAborted(operation);
181
+ }));
182
+ }
183
+ else {
184
+ handlers.addToQueue(createExecuteDefinition(typeTasks, service));
112
185
  }
113
- handlers.completeActive();
114
- handlers.addToQueue(createFeedback(FeedbackType.Succeeded, 'Configuration updated successfully.'));
115
186
  }
116
187
  catch (error) {
117
- const errorMessage = error instanceof Error
118
- ? error.message
119
- : 'Failed to save configuration';
120
- handlers.onError(errorMessage);
121
- }
122
- }, (operation) => {
123
- handlers.onAborted(operation);
124
- }));
125
- }
126
- else {
127
- // Execute tasks with validation
128
- try {
129
- const validation = validateExecuteTasks(tasks);
130
- if (validation.validationErrors.length > 0) {
131
- // Show error feedback for invalid skills
132
- const errorMessages = validation.validationErrors.map((error) => {
133
- const issuesList = error.issues
134
- .map((issue) => ` - ${issue}`)
135
- .join('\n');
136
- return `Invalid skill definition "${error.skill}":\n\n${issuesList}`;
137
- });
138
- handlers.addToQueue(createFeedback(FeedbackType.Failed, errorMessages.join('\n\n')));
139
- }
140
- else if (validation.missingConfig.length > 0) {
141
- handlers.addToQueue(createValidateDefinition(validation.missingConfig, userRequest, service, (error) => {
142
- handlers.onError(error);
143
- }, () => {
144
- handlers.addToQueue(createExecuteDefinition(tasks, service));
145
- }, (operation) => {
146
- handlers.onAborted(operation);
147
- }));
188
+ // Handle skill reference errors (e.g., unknown skills)
189
+ const errorMessage = error instanceof Error ? error.message : String(error);
190
+ const message = createMessage(errorMessage);
191
+ handlers.addToQueue(message);
148
192
  }
149
- else {
150
- handlers.addToQueue(createExecuteDefinition(tasks, service));
151
- }
152
- }
153
- catch (error) {
154
- // Handle skill reference errors (e.g., unknown skills)
155
- const errorMessage = error instanceof Error ? error.message : String(error);
156
- const message = createMessage(errorMessage);
157
- handlers.addToQueue(message);
158
193
  }
159
194
  }
160
195
  }
@@ -146,8 +146,11 @@ brackets for additional information. Use commas instead. For example:
146
146
  - WRONG: "Build project Alpha (the legacy version)"
147
147
 
148
148
  `;
149
- const skillsContent = skills.map((s) => s.trim()).join('\n\n');
150
- return header + skillsContent;
149
+ const separator = '-'.repeat(64);
150
+ const skillsContent = skills
151
+ .map((s) => s.trim())
152
+ .join('\n\n' + separator + '\n\n');
153
+ return header + separator + '\n\n' + skillsContent;
151
154
  }
152
155
  /**
153
156
  * Parse skill reference from execution line
@@ -19,3 +19,19 @@ export function formatDuration(ms) {
19
19
  }
20
20
  return parts.join(' ');
21
21
  }
22
+ /**
23
+ * Recursively extracts all leaf tasks from a hierarchical task structure.
24
+ * Leaf tasks are tasks without subtasks.
25
+ */
26
+ export function getAllLeafTasks(tasks) {
27
+ const leafTasks = [];
28
+ for (const task of tasks) {
29
+ if (!task.subtasks || task.subtasks.length === 0) {
30
+ leafTasks.push(task);
31
+ }
32
+ else {
33
+ leafTasks.push(...getAllLeafTasks(task.subtasks));
34
+ }
35
+ }
36
+ return leafTasks;
37
+ }
@@ -1,7 +1,5 @@
1
- import { extractPlaceholders, pathToString, resolveVariant, } from './resolver.js';
2
1
  import { loadUserConfig, hasConfigPath } from './loader.js';
3
- import { loadSkillDefinitions, createSkillLookup, expandSkillReferences, } from './skills.js';
4
- import { getConfigType } from './parser.js';
2
+ import { loadSkillDefinitions, createSkillLookup } from './skills.js';
5
3
  /**
6
4
  * Validate config requirements for execute tasks
7
5
  * Returns validation result with missing config and validation errors
@@ -38,88 +36,22 @@ export function validateExecuteTasks(tasks) {
38
36
  };
39
37
  }
40
38
  for (const task of tasks) {
41
- // Check if task originates from a skill
42
- const skillName = typeof task.params?.skill === 'string' ? task.params.skill : null;
43
- if (skillName) {
44
- // Task comes from a skill - check skill's Execution section
45
- const skill = skillLookup(skillName);
46
- if (!skill) {
47
- continue;
48
- }
49
- // Get variant from task params (if any)
50
- // Try params.variant first, then look for other param keys that might be the variant
51
- let variant = null;
52
- if (typeof task.params?.variant === 'string') {
53
- variant = task.params.variant.toLowerCase();
54
- }
55
- else if (task.params && typeof task.params === 'object') {
56
- // Look for other params that could be the variant (e.g., product, target, option, etc.)
57
- // Exclude known non-variant params
58
- const excludeKeys = new Set(['skill', 'type']);
59
- for (const [key, value] of Object.entries(task.params)) {
60
- if (!excludeKeys.has(key) && typeof value === 'string') {
61
- variant = value.toLowerCase();
62
- break;
63
- }
64
- }
65
- }
66
- // Expand skill references to get actual commands
67
- const expanded = expandSkillReferences(skill.execution, skillLookup);
68
- // Extract placeholders from actual commands
69
- for (const line of expanded) {
70
- const placeholders = extractPlaceholders(line);
71
- for (const placeholder of placeholders) {
72
- let resolvedPath;
73
- if (placeholder.hasVariant) {
74
- // Variant placeholder - resolve with variant from params
75
- if (!variant) {
76
- // No variant provided - skip this placeholder
77
- continue;
78
- }
79
- const resolvedPathArray = resolveVariant(placeholder.path, variant);
80
- resolvedPath = pathToString(resolvedPathArray);
81
- }
82
- else {
83
- // Strict placeholder - use as-is
84
- resolvedPath = pathToString(placeholder.path);
85
- }
86
- // Skip if already processed
87
- if (seenPaths.has(resolvedPath)) {
88
- continue;
89
- }
90
- seenPaths.add(resolvedPath);
91
- // Check if config exists
92
- if (!hasConfigPath(userConfig, resolvedPath)) {
93
- // Get type from skill config
94
- const type = skill.config
95
- ? getConfigType(skill.config, resolvedPath)
96
- : undefined;
97
- missing.push({
98
- path: resolvedPath,
99
- type: type || 'string',
100
- });
101
- }
102
- }
103
- }
104
- }
105
- else {
106
- // Task doesn't come from a skill - check task action for placeholders
107
- const placeholders = extractPlaceholders(task.action);
108
- for (const placeholder of placeholders) {
109
- // Skip variant placeholders - they should have been resolved during planning
110
- if (placeholder.hasVariant) {
39
+ // Check task's config array from SCHEDULE tool
40
+ // This is the authoritative source for required configuration
41
+ if (task.config && task.config.length > 0) {
42
+ for (const configPath of task.config) {
43
+ if (typeof configPath !== 'string') {
111
44
  continue;
112
45
  }
113
- const path = placeholder.path.join('.');
114
46
  // Skip if already processed
115
- if (seenPaths.has(path)) {
47
+ if (seenPaths.has(configPath)) {
116
48
  continue;
117
49
  }
118
- seenPaths.add(path);
50
+ seenPaths.add(configPath);
119
51
  // Check if config exists
120
- if (!hasConfigPath(userConfig, path)) {
52
+ if (!hasConfigPath(userConfig, configPath)) {
121
53
  missing.push({
122
- path,
54
+ path: configPath,
123
55
  type: 'string', // Default to string for now
124
56
  });
125
57
  }
@@ -49,14 +49,14 @@ as tasks.
49
49
  "tasks": [
50
50
  {
51
51
  "action": "Anthropic API key",
52
- "type": "config",
52
+ "type": "configure",
53
53
  "params": {
54
54
  "key": "anthropic.key"
55
55
  }
56
56
  },
57
57
  {
58
58
  "action": "Model",
59
- "type": "config",
59
+ "type": "configure",
60
60
  "params": {
61
61
  "key": "anthropic.model"
62
62
  }
@@ -69,7 +69,7 @@ as tasks.
69
69
 
70
70
  - Use the exact config keys from `configStructure`
71
71
  - Use the descriptions from `configStructure` as the action text
72
- - Always use type "config"
72
+ - Always use type "configure"
73
73
  - Always include the key in params
74
74
  - Keep message concise (≤64 characters)
75
75
  - Return at least one task (required keys if unsure)