prompt-language-shell 0.8.2 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/configuration/io.js +85 -0
- package/dist/configuration/messages.js +30 -0
- package/dist/configuration/schema.js +167 -0
- package/dist/configuration/transformation.js +55 -0
- package/dist/configuration/types.js +30 -0
- package/dist/configuration/validation.js +52 -0
- package/dist/execution/handlers.js +135 -0
- package/dist/execution/processing.js +35 -0
- package/dist/execution/reducer.js +148 -0
- package/dist/execution/types.js +12 -0
- package/dist/execution/validation.js +12 -0
- package/dist/index.js +1 -1
- package/dist/services/anthropic.js +43 -56
- package/dist/services/colors.js +2 -1
- package/dist/services/components.js +40 -24
- package/dist/services/config-labels.js +15 -15
- package/dist/services/filesystem.js +114 -0
- package/dist/services/loader.js +8 -5
- package/dist/services/logger.js +26 -1
- package/dist/services/messages.js +32 -1
- package/dist/services/parser.js +3 -1
- package/dist/services/refinement.js +10 -10
- package/dist/services/router.js +43 -27
- package/dist/services/skills.js +12 -11
- package/dist/services/validator.js +4 -3
- package/dist/types/guards.js +4 -6
- package/dist/types/handlers.js +1 -0
- package/dist/types/schemas.js +103 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/Answer.js +38 -16
- package/dist/ui/Command.js +48 -22
- package/dist/ui/Component.js +147 -33
- package/dist/ui/Config.js +69 -78
- package/dist/ui/Confirm.js +34 -21
- package/dist/ui/Execute.js +151 -178
- package/dist/ui/Feedback.js +1 -0
- package/dist/ui/Introspect.js +54 -25
- package/dist/ui/Label.js +1 -1
- package/dist/ui/Main.js +10 -6
- package/dist/ui/Refinement.js +8 -1
- package/dist/ui/Schedule.js +76 -53
- package/dist/ui/Validate.js +77 -77
- package/dist/ui/Workflow.js +60 -61
- package/package.json +3 -2
- package/dist/services/configuration.js +0 -409
package/dist/ui/Schedule.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box } from 'ink';
|
|
4
|
-
import { ComponentStatus } from '../types/components.js';
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { TaskType } from '../types/types.js';
|
|
6
6
|
import { getTaskColors, getTaskTypeLabel } from '../services/colors.js';
|
|
7
|
-
import { DebugLevel } from '../
|
|
7
|
+
import { DebugLevel } from '../configuration/types.js';
|
|
8
8
|
import { useInput } from '../services/keyboard.js';
|
|
9
9
|
import { Label } from './Label.js';
|
|
10
10
|
import { List } from './List.js';
|
|
@@ -71,12 +71,54 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
71
71
|
}
|
|
72
72
|
return item;
|
|
73
73
|
}
|
|
74
|
-
export
|
|
74
|
+
export const ScheduleView = ({ message, tasks, state, status, debug = DebugLevel.None, }) => {
|
|
75
75
|
const isActive = status === ComponentStatus.Active;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
76
|
+
const { highlightedIndex, currentDefineGroupIndex, completedSelections } = state;
|
|
77
|
+
// Find all Define tasks
|
|
78
|
+
const defineTaskIndices = tasks
|
|
79
|
+
.map((t, idx) => (t.type === TaskType.Define ? idx : -1))
|
|
80
|
+
.filter((idx) => idx !== -1);
|
|
81
|
+
// Get the current active define task
|
|
82
|
+
const currentDefineTaskIndex = defineTaskIndices[currentDefineGroupIndex] ?? -1;
|
|
83
|
+
const listItems = tasks.map((task, idx) => {
|
|
84
|
+
// Find which define group this task belongs to (if any)
|
|
85
|
+
const defineGroupIndex = defineTaskIndices.indexOf(idx);
|
|
86
|
+
const isDefineTask = defineGroupIndex !== -1;
|
|
87
|
+
// Determine child selection state
|
|
88
|
+
let childIndex = null;
|
|
89
|
+
if (isDefineTask) {
|
|
90
|
+
if (defineGroupIndex < currentDefineGroupIndex) {
|
|
91
|
+
// Previously completed group - show the selection
|
|
92
|
+
childIndex = completedSelections[defineGroupIndex] ?? null;
|
|
93
|
+
}
|
|
94
|
+
else if (defineGroupIndex === currentDefineGroupIndex) {
|
|
95
|
+
// Current active group - show live navigation unless not active
|
|
96
|
+
if (!isActive) {
|
|
97
|
+
// If not active, show the completed selection for this group too
|
|
98
|
+
childIndex = completedSelections[defineGroupIndex] ?? null;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
childIndex = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Show arrow on current active define task when no child is highlighted and is active
|
|
106
|
+
const isDefineWithoutSelection = isDefineTask &&
|
|
107
|
+
defineGroupIndex === currentDefineGroupIndex &&
|
|
108
|
+
highlightedIndex === null &&
|
|
109
|
+
isActive;
|
|
110
|
+
return taskToListItem(task, childIndex, isDefineWithoutSelection, isActive, debug);
|
|
111
|
+
});
|
|
112
|
+
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Label, { description: message, taskType: TaskType.Schedule, showType: debug !== DebugLevel.None, isCurrent: isActive, debug: debug }) })), _jsx(Box, { marginLeft: 1, children: _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug !== DebugLevel.None }) })] }));
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Schedule controller: Manages task selection and navigation
|
|
116
|
+
*/
|
|
117
|
+
export function Schedule({ message, tasks, status, debug = DebugLevel.None, requestHandlers, lifecycleHandlers, onSelectionConfirmed, }) {
|
|
118
|
+
const isActive = status === ComponentStatus.Active;
|
|
119
|
+
const [highlightedIndex, setHighlightedIndex] = useState(null);
|
|
120
|
+
const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(0);
|
|
121
|
+
const [completedSelections, setCompletedSelections] = useState([]);
|
|
80
122
|
// Find all Define tasks
|
|
81
123
|
const defineTaskIndices = tasks
|
|
82
124
|
.map((t, idx) => (t.type === TaskType.Define ? idx : -1))
|
|
@@ -93,9 +135,16 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
|
|
|
93
135
|
if (isActive && defineTaskIndices.length === 0 && onSelectionConfirmed) {
|
|
94
136
|
// No selection needed - all tasks are concrete
|
|
95
137
|
const concreteTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
|
|
138
|
+
// Expose final state
|
|
139
|
+
const finalState = {
|
|
140
|
+
highlightedIndex,
|
|
141
|
+
currentDefineGroupIndex,
|
|
142
|
+
completedSelections,
|
|
143
|
+
};
|
|
144
|
+
requestHandlers.onCompleted(finalState);
|
|
96
145
|
// Complete the selection phase - it goes to timeline
|
|
97
146
|
// Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
|
|
98
|
-
|
|
147
|
+
lifecycleHandlers.completeActive();
|
|
99
148
|
void onSelectionConfirmed(concreteTasks);
|
|
100
149
|
}
|
|
101
150
|
}, [
|
|
@@ -103,7 +152,11 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
|
|
|
103
152
|
defineTaskIndices.length,
|
|
104
153
|
tasks,
|
|
105
154
|
onSelectionConfirmed,
|
|
106
|
-
|
|
155
|
+
lifecycleHandlers,
|
|
156
|
+
highlightedIndex,
|
|
157
|
+
currentDefineGroupIndex,
|
|
158
|
+
completedSelections,
|
|
159
|
+
requestHandlers,
|
|
107
160
|
]);
|
|
108
161
|
useInput((input, key) => {
|
|
109
162
|
// Don't handle input if not active or no define task
|
|
@@ -111,7 +164,7 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
|
|
|
111
164
|
return;
|
|
112
165
|
}
|
|
113
166
|
if (key.escape) {
|
|
114
|
-
|
|
167
|
+
requestHandlers.onAborted('task selection');
|
|
115
168
|
return;
|
|
116
169
|
}
|
|
117
170
|
if (key.downArrow) {
|
|
@@ -167,61 +220,31 @@ export function Schedule({ message, tasks, state, status, debug = DebugLevel.Non
|
|
|
167
220
|
refinedTasks.push(task);
|
|
168
221
|
}
|
|
169
222
|
});
|
|
223
|
+
// Expose final state
|
|
224
|
+
const finalState = {
|
|
225
|
+
highlightedIndex: null,
|
|
226
|
+
currentDefineGroupIndex,
|
|
227
|
+
completedSelections: newCompletedSelections,
|
|
228
|
+
};
|
|
229
|
+
requestHandlers.onCompleted(finalState);
|
|
170
230
|
if (onSelectionConfirmed) {
|
|
171
231
|
// Complete the selection phase - it goes to timeline
|
|
172
232
|
// Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
|
|
173
|
-
|
|
233
|
+
lifecycleHandlers.completeActive();
|
|
174
234
|
void onSelectionConfirmed(refinedTasks);
|
|
175
235
|
}
|
|
176
236
|
else {
|
|
177
237
|
// No selection callback, just complete normally
|
|
178
|
-
|
|
238
|
+
lifecycleHandlers.completeActive();
|
|
179
239
|
}
|
|
180
240
|
}
|
|
181
241
|
}
|
|
182
242
|
}, { isActive: isActive && defineTask !== null });
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
useEffect(() => {
|
|
186
|
-
handlers?.updateState({
|
|
187
|
-
highlightedIndex,
|
|
188
|
-
currentDefineGroupIndex,
|
|
189
|
-
completedSelections,
|
|
190
|
-
});
|
|
191
|
-
}, [
|
|
243
|
+
// Controller always renders View, passing current state
|
|
244
|
+
const state = {
|
|
192
245
|
highlightedIndex,
|
|
193
246
|
currentDefineGroupIndex,
|
|
194
247
|
completedSelections,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const listItems = tasks.map((task, idx) => {
|
|
198
|
-
// Find which define group this task belongs to (if any)
|
|
199
|
-
const defineGroupIndex = defineTaskIndices.indexOf(idx);
|
|
200
|
-
const isDefineTask = defineGroupIndex !== -1;
|
|
201
|
-
// Determine child selection state
|
|
202
|
-
let childIndex = null;
|
|
203
|
-
if (isDefineTask) {
|
|
204
|
-
if (defineGroupIndex < currentDefineGroupIndex) {
|
|
205
|
-
// Previously completed group - show the selection
|
|
206
|
-
childIndex = completedSelections[defineGroupIndex] ?? null;
|
|
207
|
-
}
|
|
208
|
-
else if (defineGroupIndex === currentDefineGroupIndex) {
|
|
209
|
-
// Current active group - show live navigation unless not active
|
|
210
|
-
if (!isActive) {
|
|
211
|
-
// If not active, show the completed selection for this group too
|
|
212
|
-
childIndex = completedSelections[defineGroupIndex] ?? null;
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
childIndex = null;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
// Show arrow on current active define task when no child is highlighted and is active
|
|
220
|
-
const isDefineWithoutSelection = isDefineTask &&
|
|
221
|
-
defineGroupIndex === currentDefineGroupIndex &&
|
|
222
|
-
highlightedIndex === null &&
|
|
223
|
-
isActive;
|
|
224
|
-
return taskToListItem(task, childIndex, isDefineWithoutSelection, isActive, debug);
|
|
225
|
-
});
|
|
226
|
-
return (_jsxs(Box, { flexDirection: "column", children: [message && (_jsx(Box, { marginBottom: 1, marginLeft: 1, children: _jsx(Label, { description: message, taskType: TaskType.Schedule, showType: debug !== DebugLevel.None, isCurrent: isActive, debug: debug }) })), _jsx(Box, { marginLeft: 1, children: _jsx(List, { items: listItems, highlightedIndex: currentDefineTaskIndex >= 0 ? highlightedIndex : null, highlightedParentIndex: currentDefineTaskIndex, showType: debug !== DebugLevel.None }) })] }));
|
|
248
|
+
};
|
|
249
|
+
return (_jsx(ScheduleView, { message: message, tasks: tasks, state: state, status: status, debug: debug }));
|
|
227
250
|
}
|
package/dist/ui/Validate.js
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
-
import { ComponentStatus } from '../types/components.js';
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { TaskType } from '../types/types.js';
|
|
6
|
+
import { saveConfig } from '../configuration/io.js';
|
|
7
|
+
import { unflattenConfig } from '../configuration/transformation.js';
|
|
6
8
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
7
|
-
import {
|
|
8
|
-
import { DebugLevel, saveConfig, unflattenConfig, } from '../services/configuration.js';
|
|
9
|
+
import { createConfigDefinitionWithKeys, createMessage, } from '../services/components.js';
|
|
9
10
|
import { saveConfigLabels } from '../services/config-labels.js';
|
|
10
11
|
import { useInput } from '../services/keyboard.js';
|
|
11
|
-
import { formatErrorMessage } from '../services/messages.js';
|
|
12
|
+
import { formatErrorMessage, getUnresolvedPlaceholdersMessage, } from '../services/messages.js';
|
|
12
13
|
import { ensureMinimumTime } from '../services/timing.js';
|
|
13
|
-
import { Config } from './Config.js';
|
|
14
14
|
import { Spinner } from './Spinner.js';
|
|
15
15
|
const MIN_PROCESSING_TIME = 1000;
|
|
16
|
-
export
|
|
16
|
+
export const ValidateView = ({ state, status }) => {
|
|
17
17
|
const isActive = status === ComponentStatus.Active;
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const { error, completionMessage } = state;
|
|
19
|
+
// Don't render when not active and nothing to show
|
|
20
|
+
if (!isActive && !completionMessage && !error) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !completionMessage && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: getTextColor(isActive), children: ["Validating configuration requirements.", ' '] }), _jsx(Spinner, {})] })), completionMessage && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Validate controller: Validates missing config
|
|
27
|
+
*/
|
|
28
|
+
export function Validate({ missingConfig, userRequest, status, service, onError, requestHandlers, onValidationComplete, onAborted, lifecycleHandlers, workflowHandlers, }) {
|
|
29
|
+
const isActive = status === ComponentStatus.Active;
|
|
30
|
+
const [error, setError] = useState(null);
|
|
31
|
+
const [completionMessage, setCompletionMessage] = useState(null);
|
|
32
|
+
const [configRequirements, setConfigRequirements] = useState([]);
|
|
21
33
|
useInput((_, key) => {
|
|
22
34
|
if (key.escape && isActive) {
|
|
23
35
|
onAborted('validation');
|
|
@@ -39,7 +51,9 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
|
|
|
39
51
|
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
40
52
|
if (mounted) {
|
|
41
53
|
// Add debug components to timeline if present
|
|
42
|
-
|
|
54
|
+
if (result.debug?.length) {
|
|
55
|
+
workflowHandlers.addToTimeline(...result.debug);
|
|
56
|
+
}
|
|
43
57
|
// Extract CONFIG tasks with descriptions from result
|
|
44
58
|
const configTasks = result.tasks.filter((task) => task.type === TaskType.Config);
|
|
45
59
|
// Build ConfigRequirements with descriptions
|
|
@@ -55,25 +69,50 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
|
|
|
55
69
|
};
|
|
56
70
|
});
|
|
57
71
|
// Build completion message showing which config properties are needed
|
|
58
|
-
const
|
|
59
|
-
const propertyWord = count === 1 ? 'property' : 'properties';
|
|
60
|
-
// Shuffle between different message variations
|
|
61
|
-
const messages = [
|
|
62
|
-
`Additional configuration ${propertyWord} required.`,
|
|
63
|
-
`Configuration ${propertyWord} needed.`,
|
|
64
|
-
`Missing configuration ${propertyWord} detected.`,
|
|
65
|
-
`Setup requires configuration ${propertyWord}.`,
|
|
66
|
-
];
|
|
67
|
-
const message = messages[Math.floor(Math.random() * messages.length)];
|
|
72
|
+
const message = getUnresolvedPlaceholdersMessage(withDescriptions.length);
|
|
68
73
|
setCompletionMessage(message);
|
|
69
74
|
setConfigRequirements(withDescriptions);
|
|
70
|
-
//
|
|
71
|
-
|
|
75
|
+
// Add validation message to timeline before Config component
|
|
76
|
+
workflowHandlers.addToTimeline(createMessage(message));
|
|
77
|
+
// Create Config component and add to queue
|
|
78
|
+
const keys = withDescriptions.map((req) => req.path);
|
|
79
|
+
const configDef = createConfigDefinitionWithKeys(keys, (config) => {
|
|
80
|
+
// Convert flat dotted keys to nested structure grouped by section
|
|
81
|
+
const configBySection = unflattenConfig(config);
|
|
82
|
+
// Extract and save labels to cache
|
|
83
|
+
const labels = {};
|
|
84
|
+
for (const req of withDescriptions) {
|
|
85
|
+
if (req.description) {
|
|
86
|
+
labels[req.path] = req.description;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
saveConfigLabels(labels);
|
|
90
|
+
// Save each section
|
|
91
|
+
for (const [section, sectionConfig] of Object.entries(configBySection)) {
|
|
92
|
+
saveConfig(section, sectionConfig);
|
|
93
|
+
}
|
|
94
|
+
// After config is saved, invoke callback to add Execute component to queue
|
|
95
|
+
onValidationComplete(withDescriptions);
|
|
96
|
+
}, (operation) => {
|
|
97
|
+
onAborted(operation);
|
|
98
|
+
});
|
|
99
|
+
// Override descriptions with LLM-generated ones
|
|
100
|
+
if ('props' in configDef && 'steps' in configDef.props) {
|
|
101
|
+
configDef.props.steps = configDef.props.steps.map((step, index) => ({
|
|
102
|
+
...step,
|
|
103
|
+
description: withDescriptions[index].description ||
|
|
104
|
+
withDescriptions[index].path,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
workflowHandlers.addToQueue(configDef);
|
|
108
|
+
lifecycleHandlers.completeActive();
|
|
109
|
+
const finalState = {
|
|
110
|
+
error: null,
|
|
72
111
|
completionMessage: message,
|
|
73
112
|
configRequirements: withDescriptions,
|
|
74
113
|
validated: true,
|
|
75
|
-
|
|
76
|
-
|
|
114
|
+
};
|
|
115
|
+
requestHandlers.onCompleted(finalState);
|
|
77
116
|
}
|
|
78
117
|
}
|
|
79
118
|
catch (err) {
|
|
@@ -81,13 +120,13 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
|
|
|
81
120
|
if (mounted) {
|
|
82
121
|
const errorMessage = formatErrorMessage(err);
|
|
83
122
|
setError(errorMessage);
|
|
84
|
-
|
|
85
|
-
handlers?.updateState({
|
|
123
|
+
const finalState = {
|
|
86
124
|
error: errorMessage,
|
|
87
125
|
completionMessage: null,
|
|
88
|
-
configRequirements:
|
|
126
|
+
configRequirements: [],
|
|
89
127
|
validated: false,
|
|
90
|
-
}
|
|
128
|
+
};
|
|
129
|
+
requestHandlers.onCompleted(finalState);
|
|
91
130
|
onError(errorMessage);
|
|
92
131
|
}
|
|
93
132
|
}
|
|
@@ -101,59 +140,20 @@ export function Validate({ missingConfig, userRequest, state, status, service, c
|
|
|
101
140
|
userRequest,
|
|
102
141
|
isActive,
|
|
103
142
|
service,
|
|
104
|
-
|
|
143
|
+
requestHandlers,
|
|
105
144
|
onError,
|
|
106
145
|
onAborted,
|
|
146
|
+
onValidationComplete,
|
|
147
|
+
lifecycleHandlers,
|
|
148
|
+
workflowHandlers,
|
|
107
149
|
]);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// to load current values from config file, then override descriptions
|
|
114
|
-
const configSteps = configRequirements
|
|
115
|
-
? (() => {
|
|
116
|
-
const keys = configRequirements.map((req) => req.path);
|
|
117
|
-
const steps = createConfigStepsFromSchema(keys);
|
|
118
|
-
// Override descriptions with LLM-generated ones
|
|
119
|
-
return steps.map((step, index) => ({
|
|
120
|
-
...step,
|
|
121
|
-
description: configRequirements[index].description ||
|
|
122
|
-
configRequirements[index].path,
|
|
123
|
-
}));
|
|
124
|
-
})()
|
|
125
|
-
: null;
|
|
126
|
-
const handleConfigFinished = (config) => {
|
|
127
|
-
// Convert flat dotted keys to nested structure grouped by section
|
|
128
|
-
const configBySection = unflattenConfig(config);
|
|
129
|
-
// Extract and save labels to cache
|
|
130
|
-
if (configRequirements) {
|
|
131
|
-
const labels = {};
|
|
132
|
-
for (const req of configRequirements) {
|
|
133
|
-
if (req.description) {
|
|
134
|
-
labels[req.path] = req.description;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
saveConfigLabels(labels);
|
|
138
|
-
}
|
|
139
|
-
// Save each section
|
|
140
|
-
for (const [section, sectionConfig] of Object.entries(configBySection)) {
|
|
141
|
-
saveConfig(section, sectionConfig);
|
|
142
|
-
}
|
|
143
|
-
// Mark validation component as complete before invoking callback
|
|
144
|
-
// This allows the workflow to proceed to execution
|
|
145
|
-
handlers?.completeActive();
|
|
146
|
-
// Invoke callback which will queue the Execute component
|
|
147
|
-
if (configRequirements) {
|
|
148
|
-
onComplete(configRequirements);
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
const handleConfigAborted = (operation) => {
|
|
152
|
-
// Mark validation component as complete when aborted
|
|
153
|
-
handlers?.completeActive();
|
|
154
|
-
onAborted(operation);
|
|
150
|
+
const state = {
|
|
151
|
+
error,
|
|
152
|
+
completionMessage,
|
|
153
|
+
configRequirements,
|
|
154
|
+
validated: error === null && completionMessage !== null,
|
|
155
155
|
};
|
|
156
|
-
return
|
|
156
|
+
return _jsx(ValidateView, { state: state, status: status });
|
|
157
157
|
}
|
|
158
158
|
/**
|
|
159
159
|
* Build prompt for VALIDATE tool
|
package/dist/ui/Workflow.js
CHANGED
|
@@ -3,11 +3,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
3
3
|
import { Box, Static } from 'ink';
|
|
4
4
|
import { ComponentStatus, } from '../types/components.js';
|
|
5
5
|
import { ComponentName, FeedbackType } from '../types/types.js';
|
|
6
|
-
import { createFeedback,
|
|
7
|
-
import {
|
|
6
|
+
import { createFeedback, isSimple, markAsDone, } from '../services/components.js';
|
|
7
|
+
import { getWarnings } from '../services/logger.js';
|
|
8
8
|
import { getCancellationMessage } from '../services/messages.js';
|
|
9
9
|
import { exitApp } from '../services/process.js';
|
|
10
|
-
import {
|
|
10
|
+
import { SimpleComponent, ControllerComponent, TimelineComponent, } from './Component.js';
|
|
11
11
|
export const Workflow = ({ initialQueue, debug }) => {
|
|
12
12
|
const [timeline, setTimeline] = useState([]);
|
|
13
13
|
const [current, setCurrent] = useState({ active: null, pending: null });
|
|
@@ -35,27 +35,43 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
35
35
|
return { active: null, pending };
|
|
36
36
|
});
|
|
37
37
|
}, []);
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
// Request handlers - manages errors, aborts, and completions
|
|
39
|
+
const requestHandlers = useMemo(() => ({
|
|
40
|
+
onError: (error) => {
|
|
41
|
+
moveActiveToTimeline();
|
|
42
|
+
// Add feedback to queue
|
|
43
|
+
setQueue((queue) => [
|
|
44
|
+
...queue,
|
|
45
|
+
createFeedback(FeedbackType.Failed, error),
|
|
46
|
+
]);
|
|
42
47
|
},
|
|
43
|
-
|
|
48
|
+
onAborted: (operation) => {
|
|
49
|
+
moveActiveToTimeline();
|
|
50
|
+
// Add feedback to queue
|
|
51
|
+
const message = getCancellationMessage(operation);
|
|
52
|
+
setQueue((queue) => [
|
|
53
|
+
...queue,
|
|
54
|
+
createFeedback(FeedbackType.Aborted, message),
|
|
55
|
+
]);
|
|
56
|
+
},
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
|
58
|
+
onCompleted: (finalState) => {
|
|
44
59
|
setCurrent((curr) => {
|
|
45
60
|
const { active, pending } = curr;
|
|
46
61
|
if (!active || !('state' in active))
|
|
47
62
|
return curr;
|
|
48
|
-
|
|
63
|
+
// Save final state to definition
|
|
64
|
+
const managed = active;
|
|
49
65
|
const updated = {
|
|
50
|
-
...
|
|
51
|
-
state:
|
|
52
|
-
...stateful.state,
|
|
53
|
-
...newState,
|
|
54
|
-
},
|
|
66
|
+
...managed,
|
|
67
|
+
state: finalState,
|
|
55
68
|
};
|
|
56
69
|
return { active: updated, pending };
|
|
57
70
|
});
|
|
58
71
|
},
|
|
72
|
+
}), [moveActiveToTimeline]);
|
|
73
|
+
// Lifecycle handlers - for components with active/pending states
|
|
74
|
+
const lifecycleHandlers = useMemo(() => ({
|
|
59
75
|
completeActive: (...items) => {
|
|
60
76
|
moveActiveToPending();
|
|
61
77
|
if (items.length > 0) {
|
|
@@ -80,27 +96,16 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
80
96
|
setQueue((queue) => [...items, ...queue]);
|
|
81
97
|
}
|
|
82
98
|
},
|
|
99
|
+
}), [moveActiveToPending]);
|
|
100
|
+
// Workflow handlers - manages queue and timeline
|
|
101
|
+
const workflowHandlers = useMemo(() => ({
|
|
102
|
+
addToQueue: (...items) => {
|
|
103
|
+
setQueue((queue) => [...queue, ...items]);
|
|
104
|
+
},
|
|
83
105
|
addToTimeline: (...items) => {
|
|
84
106
|
setTimeline((prev) => [...prev, ...items]);
|
|
85
107
|
},
|
|
86
|
-
|
|
87
|
-
moveActiveToTimeline();
|
|
88
|
-
// Add feedback to queue
|
|
89
|
-
const message = getCancellationMessage(operation);
|
|
90
|
-
setQueue((queue) => [
|
|
91
|
-
...queue,
|
|
92
|
-
createFeedback(FeedbackType.Aborted, message),
|
|
93
|
-
]);
|
|
94
|
-
},
|
|
95
|
-
onError: (error) => {
|
|
96
|
-
moveActiveToTimeline();
|
|
97
|
-
// Add feedback to queue
|
|
98
|
-
setQueue((queue) => [
|
|
99
|
-
...queue,
|
|
100
|
-
createFeedback(FeedbackType.Failed, error),
|
|
101
|
-
]);
|
|
102
|
-
},
|
|
103
|
-
}), [moveActiveToPending, moveActiveToTimeline]);
|
|
108
|
+
}), []);
|
|
104
109
|
// Global Esc handler removed - components handle their own Esc individually
|
|
105
110
|
// Move next item from queue to active
|
|
106
111
|
useEffect(() => {
|
|
@@ -130,14 +135,22 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
130
135
|
const { active, pending } = current;
|
|
131
136
|
if (!active)
|
|
132
137
|
return;
|
|
133
|
-
if (
|
|
134
|
-
//
|
|
138
|
+
if (isSimple(active)) {
|
|
139
|
+
// Simple components move directly to timeline
|
|
135
140
|
const doneComponent = markAsDone(active);
|
|
136
141
|
setTimeline((prev) => [...prev, doneComponent]);
|
|
137
142
|
setCurrent({ active: null, pending });
|
|
138
143
|
}
|
|
139
|
-
//
|
|
144
|
+
// Managed components stay in active until handlers move them to pending
|
|
140
145
|
}, [current]);
|
|
146
|
+
// Check for accumulated warnings and add them to timeline
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const warningMessages = getWarnings();
|
|
149
|
+
if (warningMessages.length > 0) {
|
|
150
|
+
const warningComponents = warningMessages.map((msg) => markAsDone(createFeedback(FeedbackType.Warning, msg)));
|
|
151
|
+
setTimeline((prev) => [...prev, ...warningComponents]);
|
|
152
|
+
}
|
|
153
|
+
}, [timeline, current]);
|
|
141
154
|
// Move final pending to timeline and exit when all done
|
|
142
155
|
useEffect(() => {
|
|
143
156
|
const { active, pending } = current;
|
|
@@ -162,32 +175,18 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
162
175
|
lastItem.props.type === FeedbackType.Failed;
|
|
163
176
|
exitApp(isFailed ? 1 : 0);
|
|
164
177
|
}, [current, queue, timeline]);
|
|
165
|
-
// Render active and pending
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
if (!active)
|
|
178
|
+
// Render component with handlers (used for both active and pending)
|
|
179
|
+
const renderComponent = useCallback((def, status) => {
|
|
180
|
+
if (!def)
|
|
169
181
|
return null;
|
|
170
|
-
// For
|
|
171
|
-
if (
|
|
172
|
-
return _jsx(
|
|
182
|
+
// For simple components, render as-is
|
|
183
|
+
if (isSimple(def)) {
|
|
184
|
+
return _jsx(SimpleComponent, { def: def }, def.id);
|
|
173
185
|
}
|
|
174
|
-
// For
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
handlers,
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
return _jsx(Component, { def: wrappedDef, debug: debug }, active.id);
|
|
184
|
-
}, [current, debug, handlers]);
|
|
185
|
-
const pendingComponent = useMemo(() => {
|
|
186
|
-
const { pending } = current;
|
|
187
|
-
if (!pending)
|
|
188
|
-
return null;
|
|
189
|
-
// Pending components don't receive input
|
|
190
|
-
return _jsx(Component, { def: pending, debug: debug }, pending.id);
|
|
191
|
-
}, [current, debug]);
|
|
192
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: timeline, children: (item) => (_jsx(Box, { marginTop: 1, children: _jsx(Component, { def: item, debug: DebugLevel.None }) }, item.id)) }, "timeline"), pendingComponent && _jsx(Box, { marginTop: 1, children: pendingComponent }), activeComponent && _jsx(Box, { marginTop: 1, children: activeComponent })] }));
|
|
186
|
+
// For managed components, inject handlers via ControllerComponent
|
|
187
|
+
return (_jsx(ControllerComponent, { def: { ...def, status }, debug: debug, requestHandlers: requestHandlers, lifecycleHandlers: lifecycleHandlers, workflowHandlers: workflowHandlers }, def.id));
|
|
188
|
+
}, [debug, requestHandlers, lifecycleHandlers, workflowHandlers]);
|
|
189
|
+
const activeComponent = useMemo(() => renderComponent(current.active, ComponentStatus.Active), [current.active, renderComponent]);
|
|
190
|
+
const pendingComponent = useMemo(() => renderComponent(current.pending, ComponentStatus.Pending), [current.pending, renderComponent]);
|
|
191
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: timeline, children: (item) => (_jsx(Box, { marginTop: 1, children: _jsx(TimelineComponent, { def: item }) }, item.id)) }, "timeline"), pendingComponent && _jsx(Box, { marginTop: 1, children: pendingComponent }), activeComponent && _jsx(Box, { marginTop: 1, children: activeComponent })] }));
|
|
193
192
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prompt-language-shell",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "Your personal command-line concierge. Ask politely, and it gets things done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"ink": "^6.6.0",
|
|
52
52
|
"ink-text-input": "^6.0.0",
|
|
53
53
|
"react": "^19.2.3",
|
|
54
|
-
"yaml": "^2.8.2"
|
|
54
|
+
"yaml": "^2.8.2",
|
|
55
|
+
"zod": "^4.2.1"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@types/node": "^25.0.3",
|