prompt-language-shell 0.8.4 → 0.8.8

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.
Files changed (41) hide show
  1. package/dist/configuration/io.js +85 -0
  2. package/dist/configuration/messages.js +30 -0
  3. package/dist/configuration/schema.js +167 -0
  4. package/dist/configuration/transformation.js +55 -0
  5. package/dist/configuration/types.js +30 -0
  6. package/dist/configuration/validation.js +52 -0
  7. package/dist/execution/handlers.js +135 -0
  8. package/dist/execution/processing.js +36 -0
  9. package/dist/execution/reducer.js +148 -0
  10. package/dist/execution/types.js +12 -0
  11. package/dist/execution/validation.js +12 -0
  12. package/dist/index.js +1 -1
  13. package/dist/services/anthropic.js +2 -1
  14. package/dist/services/colors.js +22 -12
  15. package/dist/services/components.js +35 -11
  16. package/dist/services/config-labels.js +15 -15
  17. package/dist/services/logger.js +2 -1
  18. package/dist/services/messages.js +53 -1
  19. package/dist/services/refinement.js +11 -6
  20. package/dist/services/router.js +92 -52
  21. package/dist/skills/execute.md +79 -9
  22. package/dist/skills/schedule.md +121 -29
  23. package/dist/tools/execute.tool.js +4 -0
  24. package/dist/types/schemas.js +1 -0
  25. package/dist/ui/Answer.js +36 -15
  26. package/dist/ui/Command.js +43 -23
  27. package/dist/ui/Component.js +147 -33
  28. package/dist/ui/Config.js +73 -79
  29. package/dist/ui/Confirm.js +34 -21
  30. package/dist/ui/Execute.js +129 -329
  31. package/dist/ui/Feedback.js +2 -1
  32. package/dist/ui/Introspect.js +51 -24
  33. package/dist/ui/Label.js +4 -3
  34. package/dist/ui/List.js +3 -2
  35. package/dist/ui/Main.js +5 -1
  36. package/dist/ui/Refinement.js +8 -1
  37. package/dist/ui/Schedule.js +89 -61
  38. package/dist/ui/Validate.js +75 -77
  39. package/dist/ui/Workflow.js +47 -123
  40. package/package.json +1 -1
  41. package/dist/services/configuration.js +0 -409
@@ -0,0 +1,148 @@
1
+ import { ExecutionStatus } from '../services/shell.js';
2
+ import { formatDuration } from '../services/utils.js';
3
+ import { ExecuteActionType, } from './types.js';
4
+ export const initialState = {
5
+ error: null,
6
+ taskInfos: [],
7
+ message: '',
8
+ completed: 0,
9
+ hasProcessed: false,
10
+ taskExecutionTimes: [],
11
+ completionMessage: null,
12
+ summary: '',
13
+ };
14
+ export function executeReducer(state, action) {
15
+ switch (action.type) {
16
+ case ExecuteActionType.ProcessingComplete:
17
+ return {
18
+ ...state,
19
+ message: action.payload.message,
20
+ hasProcessed: true,
21
+ };
22
+ case ExecuteActionType.CommandsReady:
23
+ return {
24
+ ...state,
25
+ message: action.payload.message,
26
+ summary: action.payload.summary,
27
+ taskInfos: action.payload.taskInfos,
28
+ completed: 0,
29
+ };
30
+ case ExecuteActionType.ProcessingError:
31
+ return {
32
+ ...state,
33
+ error: action.payload.error,
34
+ hasProcessed: true,
35
+ };
36
+ case ExecuteActionType.TaskComplete: {
37
+ const updatedTimes = [
38
+ ...state.taskExecutionTimes,
39
+ action.payload.elapsed,
40
+ ];
41
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
42
+ ? {
43
+ ...task,
44
+ status: ExecutionStatus.Success,
45
+ elapsed: action.payload.elapsed,
46
+ }
47
+ : task);
48
+ return {
49
+ ...state,
50
+ taskInfos: updatedTaskInfos,
51
+ taskExecutionTimes: updatedTimes,
52
+ completed: action.payload.index + 1,
53
+ };
54
+ }
55
+ case ExecuteActionType.AllTasksComplete: {
56
+ const updatedTimes = [
57
+ ...state.taskExecutionTimes,
58
+ action.payload.elapsed,
59
+ ];
60
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
61
+ ? {
62
+ ...task,
63
+ status: ExecutionStatus.Success,
64
+ elapsed: action.payload.elapsed,
65
+ }
66
+ : task);
67
+ const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
68
+ const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
69
+ return {
70
+ ...state,
71
+ taskInfos: updatedTaskInfos,
72
+ taskExecutionTimes: updatedTimes,
73
+ completed: action.payload.index + 1,
74
+ completionMessage: completion,
75
+ };
76
+ }
77
+ case ExecuteActionType.TaskErrorCritical: {
78
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
79
+ ? { ...task, status: ExecutionStatus.Failed, elapsed: 0 }
80
+ : task);
81
+ return {
82
+ ...state,
83
+ taskInfos: updatedTaskInfos,
84
+ error: action.payload.error,
85
+ };
86
+ }
87
+ case ExecuteActionType.TaskErrorContinue: {
88
+ const updatedTimes = [
89
+ ...state.taskExecutionTimes,
90
+ action.payload.elapsed,
91
+ ];
92
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
93
+ ? {
94
+ ...task,
95
+ status: ExecutionStatus.Failed,
96
+ elapsed: action.payload.elapsed,
97
+ }
98
+ : task);
99
+ return {
100
+ ...state,
101
+ taskInfos: updatedTaskInfos,
102
+ taskExecutionTimes: updatedTimes,
103
+ completed: action.payload.index + 1,
104
+ };
105
+ }
106
+ case ExecuteActionType.LastTaskError: {
107
+ const updatedTimes = [
108
+ ...state.taskExecutionTimes,
109
+ action.payload.elapsed,
110
+ ];
111
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
112
+ ? {
113
+ ...task,
114
+ status: ExecutionStatus.Failed,
115
+ elapsed: action.payload.elapsed,
116
+ }
117
+ : task);
118
+ const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
119
+ const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
120
+ return {
121
+ ...state,
122
+ taskInfos: updatedTaskInfos,
123
+ taskExecutionTimes: updatedTimes,
124
+ completed: action.payload.index + 1,
125
+ completionMessage: completion,
126
+ };
127
+ }
128
+ case ExecuteActionType.CancelExecution: {
129
+ const updatedTaskInfos = state.taskInfos.map((task, taskIndex) => {
130
+ if (taskIndex < action.payload.completed) {
131
+ return { ...task, status: ExecutionStatus.Success };
132
+ }
133
+ else if (taskIndex === action.payload.completed) {
134
+ return { ...task, status: ExecutionStatus.Aborted };
135
+ }
136
+ else {
137
+ return { ...task, status: ExecutionStatus.Cancelled };
138
+ }
139
+ });
140
+ return {
141
+ ...state,
142
+ taskInfos: updatedTaskInfos,
143
+ };
144
+ }
145
+ default:
146
+ return state;
147
+ }
148
+ }
@@ -0,0 +1,12 @@
1
+ export var ExecuteActionType;
2
+ (function (ExecuteActionType) {
3
+ ExecuteActionType["ProcessingComplete"] = "PROCESSING_COMPLETE";
4
+ ExecuteActionType["CommandsReady"] = "COMMANDS_READY";
5
+ ExecuteActionType["ProcessingError"] = "PROCESSING_ERROR";
6
+ ExecuteActionType["TaskComplete"] = "TASK_COMPLETE";
7
+ ExecuteActionType["AllTasksComplete"] = "ALL_TASKS_COMPLETE";
8
+ ExecuteActionType["TaskErrorCritical"] = "TASK_ERROR_CRITICAL";
9
+ ExecuteActionType["TaskErrorContinue"] = "TASK_ERROR_CONTINUE";
10
+ ExecuteActionType["LastTaskError"] = "LAST_TASK_ERROR";
11
+ ExecuteActionType["CancelExecution"] = "CANCEL_EXECUTION";
12
+ })(ExecuteActionType || (ExecuteActionType = {}));
@@ -0,0 +1,12 @@
1
+ import { getUnresolvedPlaceholdersMessage } from '../services/messages.js';
2
+ /**
3
+ * Validates that all placeholders in a command have been resolved.
4
+ * Throws an error if unresolved placeholders are found.
5
+ */
6
+ export function validatePlaceholderResolution(command) {
7
+ const unresolvedPattern = /\{[^}]+\}/g;
8
+ const matches = command.match(unresolvedPattern);
9
+ if (matches && matches.length > 0) {
10
+ throw new Error(getUnresolvedPlaceholdersMessage(matches.length));
11
+ }
12
+ }
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { existsSync, readFileSync } from 'fs';
4
4
  import { dirname, join } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { render } from 'ink';
7
- import { DebugLevel } from './services/configuration.js';
7
+ import { DebugLevel } from './configuration/types.js';
8
8
  import { Main } from './ui/Main.js';
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
@@ -1,5 +1,5 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
- import { getAvailableConfigStructure, getConfiguredKeys, } from './configuration.js';
2
+ import { getAvailableConfigStructure, getConfiguredKeys, } from '../configuration/schema.js';
3
3
  import { logPrompt, logResponse } from './logger.js';
4
4
  import { formatSkillsForPrompt, loadSkillsWithValidation } from './skills.js';
5
5
  import { toolRegistry } from './registry.js';
@@ -167,6 +167,7 @@ export class AnthropicService {
167
167
  summary: input.summary,
168
168
  tasks: [],
169
169
  commands: input.commands,
170
+ error: input.error,
170
171
  debug,
171
172
  });
172
173
  if (!validation.success) {
@@ -1,13 +1,16 @@
1
+ import { DebugLevel } from '../configuration/types.js';
1
2
  import { FeedbackType, Origin, TaskType } from '../types/types.js';
2
- import { DebugLevel } from './configuration.js';
3
3
  import { ExecutionStatus } from './shell.js';
4
+ import { ComponentStatus } from '../types/components.js';
4
5
  /**
5
6
  * Base color palette - raw color values with descriptive names.
6
7
  * All colors used in the interface are defined here.
7
8
  */
8
9
  export const Palette = {
9
10
  White: '#ffffff',
11
+ SoftWhite: '#fafafa',
10
12
  AshGray: '#d0d0d0',
13
+ LightGray: '#aaaaaa',
11
14
  Gray: '#888888',
12
15
  DarkGray: '#666666',
13
16
  CharcoalGray: '#282828',
@@ -32,6 +35,7 @@ export const Palette = {
32
35
  export const Colors = {
33
36
  Text: {
34
37
  Active: Palette.White,
38
+ Pending: Palette.SoftWhite,
35
39
  Inactive: Palette.AshGray,
36
40
  UserQuery: Palette.White,
37
41
  },
@@ -148,12 +152,18 @@ const originColors = {
148
152
  * - Colors.Text.Active for current items
149
153
  * - Colors.Text.Inactive (undefined) for historical items
150
154
  */
151
- function processColor(color, isCurrent) {
152
- return color === null
153
- ? isCurrent
154
- ? Colors.Text.Active
155
- : Colors.Text.Inactive
156
- : color;
155
+ function processColor(color, status) {
156
+ const getColor = () => {
157
+ switch (status) {
158
+ case ComponentStatus.Active:
159
+ return Colors.Text.Active;
160
+ case ComponentStatus.Pending:
161
+ return Colors.Text.Pending;
162
+ default:
163
+ return Colors.Text.Inactive;
164
+ }
165
+ };
166
+ return color || getColor();
157
167
  }
158
168
  /**
159
169
  * Get task colors with current/historical state handling.
@@ -162,11 +172,11 @@ function processColor(color, isCurrent) {
162
172
  * - Colors.Text.Inactive (undefined) for historical items
163
173
  * - Colors.Text.Active for current items
164
174
  */
165
- export function getTaskColors(type, isCurrent) {
175
+ export function getTaskColors(type, status) {
166
176
  const colors = taskColors[type];
167
177
  return {
168
- description: processColor(colors.description, isCurrent),
169
- type: processColor(colors.type, isCurrent),
178
+ description: processColor(colors.description, status),
179
+ type: processColor(colors.type, status),
170
180
  };
171
181
  }
172
182
  /**
@@ -176,8 +186,8 @@ export function getTaskColors(type, isCurrent) {
176
186
  * - Colors.Text.Inactive (undefined) for historical items
177
187
  * - Colors.Text.Active for current items
178
188
  */
179
- export function getFeedbackColor(type, isCurrent) {
180
- return processColor(feedbackColors[type], isCurrent);
189
+ export function getFeedbackColor(type, status = ComponentStatus.Done) {
190
+ return processColor(feedbackColors[type], status);
181
191
  }
182
192
  /**
183
193
  * Get color for capability origin.
@@ -1,8 +1,11 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { parse as parseYaml } from 'yaml';
3
+ import { ConfigDefinitionType, } from '../configuration/types.js';
3
4
  import { ComponentStatus, } from '../types/components.js';
4
5
  import { ComponentName } from '../types/types.js';
5
- import { ConfigDefinitionType, getConfigPath, getConfigSchema, loadConfig, } from './configuration.js';
6
+ import { getConfigPath, loadConfig } from '../configuration/io.js';
7
+ import { getConfigSchema } from '../configuration/schema.js';
8
+ import { getConfigLabel } from './config-labels.js';
6
9
  import { defaultFileSystem } from './filesystem.js';
7
10
  import { getConfirmationMessage } from './messages.js';
8
11
  import { StepType } from '../ui/Config.js';
@@ -11,6 +14,7 @@ export function createWelcomeDefinition(app) {
11
14
  id: randomUUID(),
12
15
  name: ComponentName.Welcome,
13
16
  props: { app },
17
+ status: ComponentStatus.Awaiting,
14
18
  };
15
19
  }
16
20
  export function createConfigSteps() {
@@ -81,7 +85,7 @@ export function createConfigStepsFromSchema(keys, fs = defaultFileSystem) {
81
85
  // Check if key is in schema (system config)
82
86
  if (!(key in schema)) {
83
87
  // Key is not in schema - it's from a skill or discovered config
84
- // Create a simple text step with the full path as description
88
+ // Create a simple text step with cached label or full path as description
85
89
  const keyParts = key.split('.');
86
90
  const shortKey = keyParts[keyParts.length - 1];
87
91
  // Load current value if it exists (use rawConfig since discovered keys aren't in validated config)
@@ -89,8 +93,10 @@ export function createConfigStepsFromSchema(keys, fs = defaultFileSystem) {
89
93
  const value = currentValue !== undefined && typeof currentValue === 'string'
90
94
  ? currentValue
91
95
  : null;
96
+ // Use cached label if available, fallback to key path
97
+ const cachedLabel = getConfigLabel(key, fs);
92
98
  return {
93
- description: key,
99
+ description: cachedLabel ?? key,
94
100
  key: shortKey,
95
101
  path: key,
96
102
  type: StepType.Text,
@@ -217,7 +223,11 @@ export function createCommandDefinition(command, service) {
217
223
  id: randomUUID(),
218
224
  name: ComponentName.Command,
219
225
  status: ComponentStatus.Awaiting,
220
- state: {},
226
+ state: {
227
+ error: null,
228
+ message: null,
229
+ tasks: [],
230
+ },
221
231
  props: {
222
232
  command,
223
233
  service,
@@ -249,6 +259,7 @@ export function createFeedback(type, ...messages) {
249
259
  type,
250
260
  message: messages.join('\n\n'),
251
261
  },
262
+ status: ComponentStatus.Awaiting,
252
263
  };
253
264
  }
254
265
  export function createMessage(text) {
@@ -258,6 +269,7 @@ export function createMessage(text) {
258
269
  props: {
259
270
  text,
260
271
  },
272
+ status: ComponentStatus.Awaiting,
261
273
  };
262
274
  }
263
275
  export function createDebugDefinition(title, content, color) {
@@ -269,6 +281,7 @@ export function createDebugDefinition(title, content, color) {
269
281
  content,
270
282
  color,
271
283
  },
284
+ status: ComponentStatus.Awaiting,
272
285
  };
273
286
  }
274
287
  export function createRefinement(text, onAborted) {
@@ -288,7 +301,10 @@ export function createConfirmDefinition(onConfirmed, onCancelled) {
288
301
  id: randomUUID(),
289
302
  name: ComponentName.Confirm,
290
303
  status: ComponentStatus.Awaiting,
291
- state: {},
304
+ state: {
305
+ confirmed: false,
306
+ selectedIndex: 0,
307
+ },
292
308
  props: {
293
309
  message: getConfirmationMessage(),
294
310
  onConfirmed,
@@ -301,7 +317,11 @@ export function createIntrospectDefinition(tasks, service) {
301
317
  id: randomUUID(),
302
318
  name: ComponentName.Introspect,
303
319
  status: ComponentStatus.Awaiting,
304
- state: {},
320
+ state: {
321
+ error: null,
322
+ capabilities: [],
323
+ message: null,
324
+ },
305
325
  props: {
306
326
  tasks,
307
327
  service,
@@ -316,6 +336,7 @@ export function createReportDefinition(message, capabilities) {
316
336
  message,
317
337
  capabilities,
318
338
  },
339
+ status: ComponentStatus.Awaiting,
319
340
  };
320
341
  }
321
342
  export function createAnswerDefinition(question, service) {
@@ -323,14 +344,17 @@ export function createAnswerDefinition(question, service) {
323
344
  id: randomUUID(),
324
345
  name: ComponentName.Answer,
325
346
  status: ComponentStatus.Awaiting,
326
- state: {},
347
+ state: {
348
+ error: null,
349
+ answer: null,
350
+ },
327
351
  props: {
328
352
  question,
329
353
  service,
330
354
  },
331
355
  };
332
356
  }
333
- export function isStateless(component) {
357
+ export function isSimple(component) {
334
358
  return !('state' in component);
335
359
  }
336
360
  /**
@@ -361,7 +385,7 @@ export function createExecuteDefinition(tasks, service) {
361
385
  },
362
386
  };
363
387
  }
364
- export function createValidateDefinition(missingConfig, userRequest, service, onError, onComplete, onAborted) {
388
+ export function createValidateDefinition(missingConfig, userRequest, service, onError, onValidationComplete, onAborted) {
365
389
  return {
366
390
  id: randomUUID(),
367
391
  name: ComponentName.Validate,
@@ -369,7 +393,7 @@ export function createValidateDefinition(missingConfig, userRequest, service, on
369
393
  state: {
370
394
  error: null,
371
395
  completionMessage: null,
372
- configRequirements: null,
396
+ configRequirements: [],
373
397
  validated: false,
374
398
  },
375
399
  props: {
@@ -377,7 +401,7 @@ export function createValidateDefinition(missingConfig, userRequest, service, on
377
401
  userRequest,
378
402
  service,
379
403
  onError,
380
- onComplete,
404
+ onValidationComplete,
381
405
  onAborted,
382
406
  },
383
407
  };
@@ -1,6 +1,6 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
1
  import { homedir } from 'os';
3
2
  import { join } from 'path';
3
+ import { defaultFileSystem } from './filesystem.js';
4
4
  /**
5
5
  * Get the path to the config labels cache file
6
6
  */
@@ -16,23 +16,23 @@ function getCacheDirectoryPath() {
16
16
  /**
17
17
  * Ensure the cache directory exists
18
18
  */
19
- function ensureCacheDirectoryExists() {
19
+ function ensureCacheDirectoryExists(fs = defaultFileSystem) {
20
20
  const cacheDir = getCacheDirectoryPath();
21
- if (!existsSync(cacheDir)) {
22
- mkdirSync(cacheDir, { recursive: true });
21
+ if (!fs.exists(cacheDir)) {
22
+ fs.createDirectory(cacheDir, { recursive: true });
23
23
  }
24
24
  }
25
25
  /**
26
26
  * Load config labels from cache file
27
27
  * Returns empty object if file doesn't exist or is corrupted
28
28
  */
29
- export function loadConfigLabels() {
29
+ export function loadConfigLabels(fs = defaultFileSystem) {
30
30
  try {
31
31
  const cachePath = getConfigLabelsCachePath();
32
- if (!existsSync(cachePath)) {
32
+ if (!fs.exists(cachePath)) {
33
33
  return {};
34
34
  }
35
- const content = readFileSync(cachePath, 'utf-8');
35
+ const content = fs.readFile(cachePath, 'utf-8');
36
36
  const parsed = JSON.parse(content);
37
37
  // Validate that parsed content is an object
38
38
  if (typeof parsed !== 'object' ||
@@ -50,26 +50,26 @@ export function loadConfigLabels() {
50
50
  /**
51
51
  * Save multiple config labels to cache
52
52
  */
53
- export function saveConfigLabels(labels) {
54
- ensureCacheDirectoryExists();
53
+ export function saveConfigLabels(labels, fs = defaultFileSystem) {
54
+ ensureCacheDirectoryExists(fs);
55
55
  // Load existing labels and merge with new ones
56
- const existing = loadConfigLabels();
56
+ const existing = loadConfigLabels(fs);
57
57
  const merged = { ...existing, ...labels };
58
58
  const cachePath = getConfigLabelsCachePath();
59
59
  const content = JSON.stringify(merged, null, 2);
60
- writeFileSync(cachePath, content, 'utf-8');
60
+ fs.writeFile(cachePath, content);
61
61
  }
62
62
  /**
63
63
  * Save a single config label to cache
64
64
  */
65
- export function saveConfigLabel(key, label) {
66
- saveConfigLabels({ [key]: label });
65
+ export function saveConfigLabel(key, label, fs = defaultFileSystem) {
66
+ saveConfigLabels({ [key]: label }, fs);
67
67
  }
68
68
  /**
69
69
  * Get a config label from cache
70
70
  * Returns undefined if label doesn't exist
71
71
  */
72
- export function getConfigLabel(key) {
73
- const labels = loadConfigLabels();
72
+ export function getConfigLabel(key, fs = defaultFileSystem) {
73
+ const labels = loadConfigLabels(fs);
74
74
  return labels[key];
75
75
  }
@@ -1,5 +1,6 @@
1
+ import { DebugLevel } from '../configuration/types.js';
1
2
  import { createDebugDefinition } from './components.js';
2
- import { DebugLevel, loadDebugSetting } from './configuration.js';
3
+ import { loadDebugSetting } from '../configuration/io.js';
3
4
  import { Palette } from './colors.js';
4
5
  /**
5
6
  * Debug logger for the application
@@ -1,4 +1,5 @@
1
- import { DebugLevel, loadDebugSetting } from './configuration.js';
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
  */
@@ -116,3 +147,24 @@ export function formatErrorMessage(error) {
116
147
  }
117
148
  return rawMessage;
118
149
  }
150
+ /**
151
+ * Returns an execution error message with varied phrasing.
152
+ * Randomly selects from variations to sound natural, like a concierge.
153
+ * Format: "[Cannot execute phrase]. [Error details]."
154
+ */
155
+ export function getExecutionErrorMessage(error) {
156
+ const prefixes = [
157
+ "I can't execute this",
158
+ "I'm unable to execute this",
159
+ "I can't proceed with this",
160
+ "I'm unable to proceed with this",
161
+ 'This cannot be executed',
162
+ ];
163
+ const prefix = prefixes[Math.floor(Math.random() * prefixes.length)];
164
+ // Capitalize first letter of error
165
+ const capitalizedError = error.charAt(0).toUpperCase() + error.slice(1);
166
+ const errorWithPeriod = capitalizedError.endsWith('.')
167
+ ? capitalizedError
168
+ : `${capitalizedError}.`;
169
+ return `${prefix}. ${errorWithPeriod}`;
170
+ }
@@ -1,3 +1,4 @@
1
+ import { TaskType } from '../types/types.js';
1
2
  import { createRefinement } from './components.js';
2
3
  import { formatErrorMessage, getRefiningMessage } from './messages.js';
3
4
  import { routeTasksWithConfirm } from './router.js';
@@ -5,36 +6,40 @@ import { routeTasksWithConfirm } from './router.js';
5
6
  * Handle refinement flow for DEFINE tasks
6
7
  * Called when user selects options from a plan with DEFINE tasks
7
8
  */
8
- export async function handleRefinement(selectedTasks, service, originalCommand, queueHandlers, lifecycleHandlers, workflowHandlers, errorHandlers) {
9
+ export async function handleRefinement(selectedTasks, service, originalCommand, lifecycleHandlers, workflowHandlers, requestHandlers) {
9
10
  // Create and add refinement component to queue
10
11
  const refinementDef = createRefinement(getRefiningMessage(), (operation) => {
11
- errorHandlers.onAborted(operation);
12
+ requestHandlers.onAborted(operation);
12
13
  });
13
- queueHandlers.addToQueue(refinementDef);
14
+ workflowHandlers.addToQueue(refinementDef);
14
15
  try {
15
16
  // Build refined command from selected tasks
16
17
  const refinedCommand = selectedTasks
17
18
  .map((task) => {
18
19
  const action = task.action.toLowerCase().replace(/,/g, ' -');
19
20
  const type = task.type;
21
+ // For execute/group tasks, use generic hint - let LLM decide based on skill
22
+ if (type === TaskType.Execute || type === TaskType.Group) {
23
+ return `${action} (shell execution)`;
24
+ }
20
25
  return `${action} (type: ${type})`;
21
26
  })
22
27
  .join(', ');
23
28
  // Call LLM to refine plan with selected tasks
24
29
  const refinedResult = await service.processWithTool(refinedCommand, 'schedule');
25
- // Complete the Refinement component
30
+ // Complete the Refinement component with success state
26
31
  lifecycleHandlers.completeActive();
27
32
  // Add debug components to timeline if present
28
33
  if (refinedResult.debug?.length) {
29
34
  workflowHandlers.addToTimeline(...refinedResult.debug);
30
35
  }
31
36
  // Route refined tasks to appropriate components
32
- routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, queueHandlers, workflowHandlers, errorHandlers, false // No DEFINE tasks in refined result
37
+ routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, lifecycleHandlers, workflowHandlers, requestHandlers, false // No DEFINE tasks in refined result
33
38
  );
34
39
  }
35
40
  catch (err) {
36
41
  lifecycleHandlers.completeActive();
37
42
  const errorMessage = formatErrorMessage(err);
38
- errorHandlers.onError(errorMessage);
43
+ requestHandlers.onError(errorMessage);
39
44
  }
40
45
  }