prompt-language-shell 0.8.2 → 0.8.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.
@@ -20,7 +20,7 @@ export function getOperationName(tasks) {
20
20
  * Route tasks to appropriate components with Confirm flow
21
21
  * Handles the complete flow: Plan → Confirm → Execute/Answer/Introspect
22
22
  */
23
- export function routeTasksWithConfirm(tasks, message, service, userRequest, handlers, hasDefineTask = false) {
23
+ export function routeTasksWithConfirm(tasks, message, service, userRequest, queueHandlers, workflowHandlers, errorHandlers, hasDefineTask = false) {
24
24
  if (tasks.length === 0)
25
25
  return;
26
26
  // Filter out ignore and discard tasks early
@@ -28,7 +28,7 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
28
28
  // Check if no valid tasks remain after filtering
29
29
  if (validTasks.length === 0) {
30
30
  const message = createMessage(getUnknownRequestMessage());
31
- handlers.addToQueue(message);
31
+ queueHandlers.addToQueue(message);
32
32
  return;
33
33
  }
34
34
  const operation = getOperationName(validTasks);
@@ -36,7 +36,7 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
36
36
  // Has DEFINE tasks - add Schedule to queue for user selection
37
37
  // Refinement flow will call this function again with refined tasks
38
38
  const scheduleDefinition = createScheduleDefinition(message, validTasks);
39
- handlers.addToQueue(scheduleDefinition);
39
+ queueHandlers.addToQueue(scheduleDefinition);
40
40
  }
41
41
  else {
42
42
  // No DEFINE tasks - Schedule auto-completes and adds Confirm to queue
@@ -47,17 +47,17 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
47
47
  // Schedule completed - add Confirm to queue
48
48
  const confirmDefinition = createConfirmDefinition(() => {
49
49
  // User confirmed - complete both Confirm and Schedule, then route to appropriate component
50
- handlers.completeActiveAndPending();
51
- executeTasksAfterConfirm(validTasks, service, userRequest, handlers);
50
+ workflowHandlers.completeActiveAndPending();
51
+ executeTasksAfterConfirm(validTasks, service, userRequest, queueHandlers, errorHandlers);
52
52
  }, () => {
53
53
  // User cancelled - complete both Confirm and Schedule, then show cancellation
54
- handlers.completeActiveAndPending();
54
+ workflowHandlers.completeActiveAndPending();
55
55
  const message = getCancellationMessage(operation);
56
- handlers.addToQueue(createFeedback(FeedbackType.Aborted, message));
56
+ queueHandlers.addToQueue(createFeedback(FeedbackType.Aborted, message));
57
57
  });
58
- handlers.addToQueue(confirmDefinition);
58
+ queueHandlers.addToQueue(confirmDefinition);
59
59
  });
60
- handlers.addToQueue(scheduleDefinition);
60
+ queueHandlers.addToQueue(scheduleDefinition);
61
61
  }
62
62
  }
63
63
  /**
@@ -88,13 +88,13 @@ function validateTaskTypes(tasks) {
88
88
  * Validates task types and routes each type appropriately
89
89
  * Supports mixed types at top level with Groups
90
90
  */
91
- function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
91
+ function executeTasksAfterConfirm(tasks, service, userRequest, queueHandlers, errorHandlers) {
92
92
  // Validate task types (Groups must have uniform subtasks)
93
93
  try {
94
94
  validateTaskTypes(tasks);
95
95
  }
96
96
  catch (error) {
97
- handlers.onError(error instanceof Error ? error.message : String(error));
97
+ errorHandlers.onError(error instanceof Error ? error.message : String(error));
98
98
  return;
99
99
  }
100
100
  const scheduledTasks = asScheduledTasks(tasks);
@@ -117,7 +117,7 @@ function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
117
117
  const taskType = type;
118
118
  if (typeTasks.length === 0)
119
119
  continue;
120
- routeTasksByType(taskType, typeTasks, service, userRequest, handlers);
120
+ routeTasksByType(taskType, typeTasks, service, userRequest, queueHandlers, errorHandlers);
121
121
  }
122
122
  consecutiveStandaloneTasks = [];
123
123
  };
@@ -130,7 +130,7 @@ function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
130
130
  if (task.subtasks.length > 0) {
131
131
  const subtasks = task.subtasks;
132
132
  const taskType = subtasks[0].type;
133
- routeTasksByType(taskType, subtasks, service, userRequest, handlers);
133
+ routeTasksByType(taskType, subtasks, service, userRequest, queueHandlers, errorHandlers);
134
134
  }
135
135
  }
136
136
  else {
@@ -145,22 +145,22 @@ function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
145
145
  * Route tasks by type to appropriate components
146
146
  * Extracted to allow reuse for both Groups and standalone tasks
147
147
  */
148
- function routeTasksByType(taskType, typeTasks, service, userRequest, handlers) {
148
+ function routeTasksByType(taskType, typeTasks, service, userRequest, queueHandlers, errorHandlers) {
149
149
  if (taskType === TaskType.Answer) {
150
150
  // Create separate Answer component for each question
151
151
  for (const task of typeTasks) {
152
- handlers.addToQueue(createAnswerDefinition(task.action, service));
152
+ queueHandlers.addToQueue(createAnswerDefinition(task.action, service));
153
153
  }
154
154
  }
155
155
  else if (taskType === TaskType.Introspect) {
156
- handlers.addToQueue(createIntrospectDefinition(typeTasks, service));
156
+ queueHandlers.addToQueue(createIntrospectDefinition(typeTasks, service));
157
157
  }
158
158
  else if (taskType === TaskType.Config) {
159
159
  // Route to Config flow - extract keys from task params
160
160
  const configKeys = typeTasks
161
161
  .map((task) => task.params?.key)
162
162
  .filter((key) => key !== undefined);
163
- handlers.addToQueue(createConfigDefinitionWithKeys(configKeys, (config) => {
163
+ queueHandlers.addToQueue(createConfigDefinitionWithKeys(configKeys, (config) => {
164
164
  // Save config - Config component will handle completion and feedback
165
165
  try {
166
166
  // Convert flat dotted keys to nested structure grouped by section
@@ -177,7 +177,7 @@ function routeTasksByType(taskType, typeTasks, service, userRequest, handlers) {
177
177
  throw new Error(errorMessage);
178
178
  }
179
179
  }, (operation) => {
180
- handlers.onAborted(operation);
180
+ errorHandlers.onAborted(operation);
181
181
  }));
182
182
  }
183
183
  else if (taskType === TaskType.Execute) {
@@ -192,26 +192,26 @@ function routeTasksByType(taskType, typeTasks, service, userRequest, handlers) {
192
192
  .join('\n');
193
193
  return `Invalid skill definition "${error.skill}":\n\n${issuesList}`;
194
194
  });
195
- handlers.addToQueue(createFeedback(FeedbackType.Failed, errorMessages.join('\n\n')));
195
+ queueHandlers.addToQueue(createFeedback(FeedbackType.Failed, errorMessages.join('\n\n')));
196
196
  }
197
197
  else if (validation.missingConfig.length > 0) {
198
- handlers.addToQueue(createValidateDefinition(validation.missingConfig, userRequest, service, (error) => {
199
- handlers.onError(error);
198
+ queueHandlers.addToQueue(createValidateDefinition(validation.missingConfig, userRequest, service, (error) => {
199
+ errorHandlers.onError(error);
200
200
  }, () => {
201
- handlers.addToQueue(createExecuteDefinition(typeTasks, service));
201
+ queueHandlers.addToQueue(createExecuteDefinition(typeTasks, service));
202
202
  }, (operation) => {
203
- handlers.onAborted(operation);
203
+ errorHandlers.onAborted(operation);
204
204
  }));
205
205
  }
206
206
  else {
207
- handlers.addToQueue(createExecuteDefinition(typeTasks, service));
207
+ queueHandlers.addToQueue(createExecuteDefinition(typeTasks, service));
208
208
  }
209
209
  }
210
210
  catch (error) {
211
211
  // Handle skill reference errors (e.g., unknown skills)
212
212
  const errorMessage = error instanceof Error ? error.message : String(error);
213
213
  const message = createMessage(errorMessage);
214
- handlers.addToQueue(message);
214
+ queueHandlers.addToQueue(message);
215
215
  }
216
216
  }
217
217
  }
@@ -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 (!existsSync(skillsDir)) {
55
+ if (!fs.exists(skillsDir)) {
55
56
  return [];
56
57
  }
57
58
  try {
58
- const files = readdirSync(skillsDir);
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 = readFileSync(filePath, 'utf-8');
79
+ const content = fs.readFile(filePath, 'utf-8');
79
80
  return { key, content };
80
81
  });
81
82
  }
82
- catch {
83
- // Return empty array if there's any error reading the directory
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) {
@@ -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 (typeof value === 'object' &&
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 {};
@@ -0,0 +1,103 @@
1
+ import { z } from 'zod';
2
+ import { Origin, TaskType } from './types.js';
3
+ /**
4
+ * Zod schema for TaskType enum values.
5
+ * Validates that task types match the expected enum values.
6
+ */
7
+ export const TaskTypeSchema = z.enum([
8
+ TaskType.Config,
9
+ TaskType.Schedule,
10
+ TaskType.Execute,
11
+ TaskType.Answer,
12
+ TaskType.Introspect,
13
+ TaskType.Report,
14
+ TaskType.Define,
15
+ TaskType.Ignore,
16
+ TaskType.Select,
17
+ TaskType.Discard,
18
+ TaskType.Group,
19
+ ]);
20
+ /**
21
+ * Zod schema for Origin enum values.
22
+ * Validates capability origin types.
23
+ */
24
+ export const OriginSchema = z.enum([
25
+ Origin.BuiltIn,
26
+ Origin.UserProvided,
27
+ Origin.Indirect,
28
+ ]);
29
+ /**
30
+ * Zod schema for base Task type.
31
+ * Validates task structure with required action and type fields.
32
+ */
33
+ export const TaskSchema = z.object({
34
+ action: z.string().min(1),
35
+ type: TaskTypeSchema,
36
+ params: z.record(z.string(), z.unknown()).optional(),
37
+ config: z.array(z.string()).optional(),
38
+ });
39
+ /**
40
+ * Zod schema for recursive ScheduledTask type.
41
+ * Uses z.lazy for self-referential subtasks validation.
42
+ */
43
+ export const ScheduledTaskSchema = z.object({
44
+ action: z.string().min(1),
45
+ type: TaskTypeSchema,
46
+ params: z.record(z.string(), z.unknown()).optional(),
47
+ config: z.array(z.string()).optional(),
48
+ subtasks: z.lazy(() => ScheduledTaskSchema.array()).optional(),
49
+ });
50
+ /**
51
+ * Zod schema for ExecuteCommand type.
52
+ * Validates shell command execution parameters.
53
+ */
54
+ export const ExecuteCommandSchema = z.object({
55
+ description: z.string().min(1),
56
+ command: z.string().min(1),
57
+ workdir: z.string().optional(),
58
+ timeout: z.number().int().positive().optional(),
59
+ critical: z.boolean().optional(),
60
+ });
61
+ /**
62
+ * Zod schema for Capability type.
63
+ * Validates skill and capability definitions.
64
+ */
65
+ export const CapabilitySchema = z.object({
66
+ name: z.string().min(1),
67
+ description: z.string().min(1),
68
+ origin: OriginSchema,
69
+ isIncomplete: z.boolean().optional(),
70
+ });
71
+ /**
72
+ * Zod schema for ComponentDefinition type.
73
+ * Flexible schema for debug component validation.
74
+ * Accepts both stateless and stateful component structures.
75
+ */
76
+ export const ComponentDefinitionSchema = z.object({
77
+ id: z.string(),
78
+ name: z.string(),
79
+ props: z.record(z.string(), z.unknown()),
80
+ state: z.record(z.string(), z.unknown()).optional(),
81
+ status: z.string().optional(),
82
+ });
83
+ /**
84
+ * Zod schema for CommandResult type.
85
+ * Validates LLM responses from execute, answer, and schedule tools.
86
+ */
87
+ export const CommandResultSchema = z.object({
88
+ message: z.string(),
89
+ summary: z.string().optional(),
90
+ tasks: z.array(ScheduledTaskSchema),
91
+ answer: z.string().optional(),
92
+ commands: z.array(ExecuteCommandSchema).optional(),
93
+ debug: z.array(ComponentDefinitionSchema).optional(),
94
+ });
95
+ /**
96
+ * Zod schema for IntrospectResult type.
97
+ * Validates LLM responses from introspect tool.
98
+ */
99
+ export const IntrospectResultSchema = z.object({
100
+ message: z.string(),
101
+ capabilities: z.array(CapabilitySchema),
102
+ debug: z.array(ComponentDefinitionSchema).optional(),
103
+ });
@@ -38,6 +38,7 @@ export var Origin;
38
38
  export var FeedbackType;
39
39
  (function (FeedbackType) {
40
40
  FeedbackType["Info"] = "info";
41
+ FeedbackType["Warning"] = "warning";
41
42
  FeedbackType["Succeeded"] = "succeeded";
42
43
  FeedbackType["Aborted"] = "aborted";
43
44
  FeedbackType["Failed"] = "failed";
package/dist/ui/Answer.js CHANGED
@@ -3,19 +3,18 @@ import { useEffect, useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { ComponentStatus } from '../types/components.js';
5
5
  import { Colors, getTextColor } from '../services/colors.js';
6
- import { addDebugToTimeline } from '../services/components.js';
7
6
  import { useInput } from '../services/keyboard.js';
8
7
  import { formatErrorMessage } from '../services/messages.js';
9
8
  import { withMinimumTime } from '../services/timing.js';
10
9
  import { Spinner } from './Spinner.js';
11
10
  const MINIMUM_PROCESSING_TIME = 400;
12
- export function Answer({ question, state, status, service, handlers, }) {
11
+ export function Answer({ question, state, status, service, stateHandlers, lifecycleHandlers, errorHandlers, workflowHandlers, }) {
13
12
  const isActive = status === ComponentStatus.Active;
14
13
  const [error, setError] = useState(null);
15
14
  const [answer, setAnswer] = useState(state?.answer ?? null);
16
15
  useInput((input, key) => {
17
16
  if (key.escape && isActive) {
18
- handlers?.onAborted('answer');
17
+ errorHandlers?.onAborted('answer');
19
18
  }
20
19
  }, { isActive });
21
20
  useEffect(() => {
@@ -30,26 +29,28 @@ export function Answer({ question, state, status, service, handlers, }) {
30
29
  const result = await withMinimumTime(() => svc.processWithTool(question, 'answer'), MINIMUM_PROCESSING_TIME);
31
30
  if (mounted) {
32
31
  // Add debug components to timeline if present
33
- addDebugToTimeline(result.debug, handlers);
32
+ if (result.debug?.length) {
33
+ workflowHandlers?.addToTimeline(...result.debug);
34
+ }
34
35
  // Extract answer from result
35
36
  const answerText = result.answer || '';
36
37
  setAnswer(answerText);
37
38
  // Update component state so answer persists in timeline
38
- handlers?.updateState({
39
+ stateHandlers?.updateState({
39
40
  answer: answerText,
40
41
  });
41
42
  // Signal completion
42
- handlers?.completeActive();
43
+ lifecycleHandlers?.completeActive();
43
44
  }
44
45
  }
45
46
  catch (err) {
46
47
  if (mounted) {
47
48
  const errorMessage = formatErrorMessage(err);
48
49
  setError(errorMessage);
49
- handlers?.updateState({
50
+ stateHandlers?.updateState({
50
51
  error: errorMessage,
51
52
  });
52
- handlers?.onError(errorMessage);
53
+ errorHandlers?.onError(errorMessage);
53
54
  }
54
55
  }
55
56
  }
@@ -57,7 +58,7 @@ export function Answer({ question, state, status, service, handlers, }) {
57
58
  return () => {
58
59
  mounted = false;
59
60
  };
60
- }, [question, isActive, service, handlers]);
61
+ }, [question, isActive, service]);
61
62
  const lines = answer ? answer.split('\n') : [];
62
63
  return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !answer && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Finding answer. " }), _jsx(Spinner, {})] })), answer && (_jsxs(_Fragment, { children: [_jsx(Box, { marginLeft: 1, marginBottom: 1, children: _jsx(Text, { color: getTextColor(isActive), children: question }) }), _jsx(Box, { flexDirection: "column", paddingLeft: 3, children: lines.map((line, index) => (_jsx(Text, { color: getTextColor(isActive), children: line }, index))) })] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
63
64
  }
@@ -4,7 +4,7 @@ import { Box, Text } from 'ink';
4
4
  import { ComponentStatus } from '../types/components.js';
5
5
  import { TaskType } from '../types/types.js';
6
6
  import { Colors } from '../services/colors.js';
7
- import { addDebugToTimeline, createScheduleDefinition, } from '../services/components.js';
7
+ import { createScheduleDefinition } from '../services/components.js';
8
8
  import { useInput } from '../services/keyboard.js';
9
9
  import { formatErrorMessage } from '../services/messages.js';
10
10
  import { handleRefinement } from '../services/refinement.js';
@@ -13,12 +13,12 @@ import { ensureMinimumTime } from '../services/timing.js';
13
13
  import { Spinner } from './Spinner.js';
14
14
  import { UserQuery } from './UserQuery.js';
15
15
  const MIN_PROCESSING_TIME = 400; // purely for visual effect
16
- export function Command({ command, state, status, service, handlers, onAborted, }) {
16
+ export function Command({ command, state, status, service, stateHandlers, lifecycleHandlers, queueHandlers, errorHandlers, workflowHandlers, onAborted, }) {
17
17
  const isActive = status === ComponentStatus.Active;
18
18
  const [error, setError] = useState(state?.error ?? null);
19
19
  useInput((_, key) => {
20
20
  if (key.escape && isActive) {
21
- handlers?.onAborted('request');
21
+ errorHandlers?.onAborted('request');
22
22
  onAborted?.('request');
23
23
  }
24
24
  }, { isActive });
@@ -51,33 +51,39 @@ export function Command({ command, state, status, service, handlers, onAborted,
51
51
  const debugComponents = allConfig
52
52
  ? [...scheduleDebug, ...(result.debug || [])]
53
53
  : scheduleDebug;
54
- addDebugToTimeline(debugComponents, handlers);
54
+ if (debugComponents.length > 0) {
55
+ workflowHandlers?.addToTimeline(...debugComponents);
56
+ }
55
57
  // Save result to state for timeline display
56
- handlers?.updateState({
58
+ stateHandlers?.updateState({
57
59
  message: result.message,
58
60
  tasks: result.tasks,
59
61
  });
60
62
  // Check if tasks contain DEFINE type (variant selection needed)
61
63
  const hasDefineTask = result.tasks.some((task) => task.type === TaskType.Define);
62
- if (!handlers) {
64
+ // Guard: ensure all required handlers are present
65
+ if (!queueHandlers ||
66
+ !lifecycleHandlers ||
67
+ !workflowHandlers ||
68
+ !errorHandlers) {
63
69
  return;
64
70
  }
65
71
  // Create Schedule definition
66
72
  const scheduleDefinition = createScheduleDefinition(result.message, result.tasks, hasDefineTask
67
73
  ? async (selectedTasks) => {
68
74
  // Refinement flow for DEFINE tasks
69
- await handleRefinement(selectedTasks, svc, command, handlers);
75
+ await handleRefinement(selectedTasks, svc, command, queueHandlers, lifecycleHandlers, workflowHandlers, errorHandlers);
70
76
  }
71
77
  : undefined);
72
78
  if (hasDefineTask) {
73
79
  // DEFINE tasks: Move Command to timeline, add Schedule to queue
74
- handlers.completeActive();
75
- handlers.addToQueue(scheduleDefinition);
80
+ lifecycleHandlers.completeActive();
81
+ queueHandlers.addToQueue(scheduleDefinition);
76
82
  }
77
83
  else {
78
84
  // No DEFINE tasks: Complete Command, then route to Confirm flow
79
- handlers.completeActive();
80
- routeTasksWithConfirm(result.tasks, result.message, svc, command, handlers, false);
85
+ lifecycleHandlers.completeActive();
86
+ routeTasksWithConfirm(result.tasks, result.message, svc, command, queueHandlers, workflowHandlers, errorHandlers, false);
81
87
  }
82
88
  }
83
89
  }
@@ -86,10 +92,10 @@ export function Command({ command, state, status, service, handlers, onAborted,
86
92
  if (mounted) {
87
93
  const errorMessage = formatErrorMessage(err);
88
94
  setError(errorMessage);
89
- handlers?.updateState({
95
+ stateHandlers?.updateState({
90
96
  error: errorMessage,
91
97
  });
92
- handlers?.onError(errorMessage);
98
+ errorHandlers?.onError(errorMessage);
93
99
  }
94
100
  }
95
101
  }
@@ -97,6 +103,6 @@ export function Command({ command, state, status, service, handlers, onAborted,
97
103
  return () => {
98
104
  mounted = false;
99
105
  };
100
- }, [command, isActive, service, handlers]);
106
+ }, [command, isActive, service]);
101
107
  return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [!isActive ? (_jsxs(UserQuery, { children: ["> pls ", command] })) : (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: Colors.Text.Active, children: ["> pls ", command] }), _jsx(Text, { children: " " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
102
108
  }
package/dist/ui/Config.js CHANGED
@@ -78,7 +78,8 @@ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
78
78
  return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
79
79
  }) }));
80
80
  }
81
- export function Config({ steps, state, status, debug = DebugLevel.None, handlers, onFinished, onAborted, }) {
81
+ export function Config(props) {
82
+ const { steps, state, status, debug = DebugLevel.None, stateHandlers, lifecycleHandlers, onFinished, onAborted, } = props;
82
83
  const isActive = status === ComponentStatus.Active;
83
84
  const [step, setStep] = useState(!isActive ? (state?.completedStep ?? steps.length) : 0);
84
85
  const [values, setValues] = useState(() => {
@@ -170,7 +171,7 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
170
171
  setValues({ ...values, [configKey]: currentValue });
171
172
  }
172
173
  // Save state before aborting
173
- handlers?.updateState({
174
+ stateHandlers?.updateState({
174
175
  values,
175
176
  completedStep: step,
176
177
  selectedIndex,
@@ -179,7 +180,7 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
179
180
  onAborted('configuration');
180
181
  }
181
182
  // Complete with abort feedback
182
- handlers?.completeActive(createFeedback(FeedbackType.Aborted, 'Configuration cancelled.'));
183
+ lifecycleHandlers?.completeActive(createFeedback(FeedbackType.Aborted, 'Configuration cancelled.'));
183
184
  return;
184
185
  }
185
186
  // Handle selection step navigation
@@ -236,19 +237,19 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
236
237
  completedStep: steps.length,
237
238
  selectedIndex,
238
239
  };
239
- handlers?.updateState(stateUpdate);
240
+ stateHandlers?.updateState(stateUpdate);
240
241
  // Call onFinished callback and handle result
241
242
  try {
242
243
  if (onFinished) {
243
244
  onFinished(newValues);
244
245
  }
245
246
  // Success - complete with success feedback
246
- handlers?.completeActive(createFeedback(FeedbackType.Succeeded, 'Configuration saved successfully.'));
247
+ lifecycleHandlers?.completeActive(createFeedback(FeedbackType.Succeeded, 'Configuration saved successfully.'));
247
248
  }
248
249
  catch (error) {
249
250
  // Failure - complete with error feedback
250
251
  const errorMessage = error instanceof Error ? error.message : 'Configuration failed';
251
- handlers?.completeActive(createFeedback(FeedbackType.Failed, errorMessage));
252
+ lifecycleHandlers?.completeActive(createFeedback(FeedbackType.Failed, errorMessage));
252
253
  }
253
254
  setStep(steps.length);
254
255
  }
@@ -259,7 +260,7 @@ export function Config({ steps, state, status, debug = DebugLevel.None, handlers
259
260
  completedStep: step + 1,
260
261
  selectedIndex,
261
262
  };
262
- handlers?.updateState(stateUpdate);
263
+ stateHandlers?.updateState(stateUpdate);
263
264
  const nextStep = step + 1;
264
265
  setStep(nextStep);
265
266
  // Reset selectedIndex for next step
@@ -5,7 +5,7 @@ import { ComponentStatus } from '../types/components.js';
5
5
  import { Colors, getTextColor, Palette } from '../services/colors.js';
6
6
  import { useInput } from '../services/keyboard.js';
7
7
  import { UserQuery } from './UserQuery.js';
8
- export function Confirm({ message, state, status, handlers, onConfirmed, onCancelled, }) {
8
+ export function Confirm({ message, state, status, stateHandlers, onConfirmed, onCancelled, }) {
9
9
  const isActive = status === ComponentStatus.Active;
10
10
  const [selectedIndex, setSelectedIndex] = useState(state?.selectedIndex ?? 0); // 0 = Yes, 1 = No
11
11
  useInput((input, key) => {
@@ -14,18 +14,18 @@ export function Confirm({ message, state, status, handlers, onConfirmed, onCance
14
14
  if (key.escape) {
15
15
  // Escape: highlight "No" and cancel
16
16
  setSelectedIndex(1);
17
- handlers?.updateState({ selectedIndex: 1 });
17
+ stateHandlers?.updateState({ selectedIndex: 1 });
18
18
  onCancelled();
19
19
  }
20
20
  else if (key.tab) {
21
21
  // Toggle between Yes (0) and No (1)
22
22
  const newIndex = selectedIndex === 0 ? 1 : 0;
23
23
  setSelectedIndex(newIndex);
24
- handlers?.updateState({ selectedIndex: newIndex });
24
+ stateHandlers?.updateState({ selectedIndex: newIndex });
25
25
  }
26
26
  else if (key.return) {
27
27
  // Confirm selection
28
- handlers?.updateState({ selectedIndex, confirmed: true });
28
+ stateHandlers?.updateState({ selectedIndex, confirmed: true });
29
29
  if (selectedIndex === 0) {
30
30
  onConfirmed();
31
31
  }