prompt-language-shell 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/ANSWER.md +4 -0
- package/dist/config/PLAN.md +23 -0
- package/dist/config/VALIDATE.md +12 -11
- package/dist/services/components.js +54 -64
- package/dist/services/configuration.js +84 -0
- package/dist/services/messages.js +22 -0
- package/dist/services/queue.js +2 -2
- package/dist/services/refinement.js +36 -0
- package/dist/services/task-router.js +135 -0
- package/dist/types/types.js +0 -1
- package/dist/ui/Answer.js +18 -27
- package/dist/ui/Command.js +45 -27
- package/dist/ui/Component.js +23 -50
- package/dist/ui/Config.js +49 -24
- package/dist/ui/Confirm.js +17 -11
- package/dist/ui/Execute.js +66 -45
- package/dist/ui/Feedback.js +1 -1
- package/dist/ui/Introspect.js +26 -23
- package/dist/ui/Main.js +71 -100
- package/dist/ui/Message.js +1 -1
- package/dist/ui/Plan.js +54 -32
- package/dist/ui/Refinement.js +6 -7
- package/dist/ui/Report.js +1 -1
- package/dist/ui/UserQuery.js +6 -0
- package/dist/ui/Validate.js +49 -19
- package/dist/ui/Welcome.js +1 -1
- package/dist/ui/Workflow.js +119 -0
- package/package.json +1 -1
- package/dist/handlers/answer.js +0 -21
- package/dist/handlers/command.js +0 -34
- package/dist/handlers/config.js +0 -88
- package/dist/handlers/execute.js +0 -46
- package/dist/handlers/execution.js +0 -140
- package/dist/handlers/introspect.js +0 -21
- package/dist/handlers/plan.js +0 -79
- package/dist/types/handlers.js +0 -1
- package/dist/ui/AnswerDisplay.js +0 -8
- package/dist/ui/Column.js +0 -7
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { TaskType } from '../types/types.js';
|
|
2
|
+
import { createAnswerDefinition, createConfigDefinitionWithKeys, createConfirmDefinition, createExecuteDefinition, createFeedback, createIntrospectDefinition, createMessage, createPlanDefinition, createValidateDefinition, } from './components.js';
|
|
3
|
+
import { saveConfig, unflattenConfig } from './configuration.js';
|
|
4
|
+
import { FeedbackType } from '../types/types.js';
|
|
5
|
+
import { validateExecuteTasks } from './execution-validator.js';
|
|
6
|
+
import { getMixedTaskTypesError, getUnknownRequestMessage, } from './messages.js';
|
|
7
|
+
/**
|
|
8
|
+
* Determine the operation name based on task types
|
|
9
|
+
*/
|
|
10
|
+
export function getOperationName(tasks) {
|
|
11
|
+
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
12
|
+
const allAnswer = tasks.every((task) => task.type === TaskType.Answer);
|
|
13
|
+
if (allIntrospect)
|
|
14
|
+
return 'introspection';
|
|
15
|
+
if (allAnswer)
|
|
16
|
+
return 'answer';
|
|
17
|
+
return 'execution';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Route tasks to appropriate components with Confirm flow
|
|
21
|
+
* Handles the complete flow: Plan → Confirm → Execute/Answer/Introspect
|
|
22
|
+
*/
|
|
23
|
+
export function routeTasksWithConfirm(tasks, message, service, userRequest, handlers, hasDefineTask = false) {
|
|
24
|
+
if (tasks.length === 0)
|
|
25
|
+
return;
|
|
26
|
+
// Filter out ignore and discard tasks early
|
|
27
|
+
const validTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
|
|
28
|
+
// Check if no valid tasks remain after filtering
|
|
29
|
+
if (validTasks.length === 0) {
|
|
30
|
+
const message = createMessage(getUnknownRequestMessage());
|
|
31
|
+
handlers.addToQueue(message);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const operation = getOperationName(validTasks);
|
|
35
|
+
// Create plan definition with valid tasks only
|
|
36
|
+
const planDefinition = createPlanDefinition(message, validTasks);
|
|
37
|
+
if (hasDefineTask) {
|
|
38
|
+
// Has DEFINE tasks - add Plan to queue for user selection
|
|
39
|
+
// Refinement flow will call this function again with refined tasks
|
|
40
|
+
handlers.addToQueue(planDefinition);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// No DEFINE tasks - add Plan to timeline, create Confirm
|
|
44
|
+
const confirmDefinition = createConfirmDefinition(() => {
|
|
45
|
+
// User confirmed - route to appropriate component
|
|
46
|
+
handlers.completeActive();
|
|
47
|
+
executeTasksAfterConfirm(validTasks, service, userRequest, handlers);
|
|
48
|
+
}, () => {
|
|
49
|
+
// User cancelled
|
|
50
|
+
handlers.onAborted(operation);
|
|
51
|
+
});
|
|
52
|
+
handlers.addToTimeline(planDefinition);
|
|
53
|
+
handlers.addToQueue(confirmDefinition);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Validate that all tasks have the same type
|
|
58
|
+
* Per FLOWS.md: "Mixed types → Error (not supported)"
|
|
59
|
+
*/
|
|
60
|
+
function validateTaskTypes(tasks) {
|
|
61
|
+
if (tasks.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
const types = new Set(tasks.map((task) => task.type));
|
|
64
|
+
if (types.size > 1) {
|
|
65
|
+
throw new Error(getMixedTaskTypesError(Array.from(types)));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Execute tasks after confirmation (internal helper)
|
|
70
|
+
* Validates task types after user has seen and confirmed the plan
|
|
71
|
+
*/
|
|
72
|
+
function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
|
|
73
|
+
// Validate all tasks have the same type after user confirmation
|
|
74
|
+
// Per FLOWS.md: "Confirm component completes → Execution handler analyzes task types"
|
|
75
|
+
try {
|
|
76
|
+
validateTaskTypes(tasks);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
handlers.onError(error instanceof Error ? error.message : String(error));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
83
|
+
const allAnswer = tasks.every((task) => task.type === TaskType.Answer);
|
|
84
|
+
const allConfig = tasks.every((task) => task.type === TaskType.Config);
|
|
85
|
+
if (allAnswer) {
|
|
86
|
+
const question = tasks[0].action;
|
|
87
|
+
handlers.addToQueue(createAnswerDefinition(question, service));
|
|
88
|
+
}
|
|
89
|
+
else if (allIntrospect) {
|
|
90
|
+
handlers.addToQueue(createIntrospectDefinition(tasks, service));
|
|
91
|
+
}
|
|
92
|
+
else if (allConfig) {
|
|
93
|
+
// Route to Config flow - extract keys from task params
|
|
94
|
+
const configKeys = tasks
|
|
95
|
+
.map((task) => task.params?.key)
|
|
96
|
+
.filter((key) => key !== undefined);
|
|
97
|
+
handlers.addToQueue(createConfigDefinitionWithKeys(configKeys, (config) => {
|
|
98
|
+
// Save config using the same pattern as Validate component
|
|
99
|
+
try {
|
|
100
|
+
// Convert flat dotted keys to nested structure grouped by section
|
|
101
|
+
const configBySection = unflattenConfig(config);
|
|
102
|
+
// Save each section
|
|
103
|
+
for (const [section, sectionConfig] of Object.entries(configBySection)) {
|
|
104
|
+
saveConfig(section, sectionConfig);
|
|
105
|
+
}
|
|
106
|
+
handlers.completeActive();
|
|
107
|
+
handlers.addToQueue(createFeedback(FeedbackType.Succeeded, 'Configuration updated successfully.'));
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const errorMessage = error instanceof Error
|
|
111
|
+
? error.message
|
|
112
|
+
: 'Failed to save configuration';
|
|
113
|
+
handlers.onError(errorMessage);
|
|
114
|
+
}
|
|
115
|
+
}, (operation) => {
|
|
116
|
+
handlers.onAborted(operation);
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Execute tasks with validation
|
|
121
|
+
const missingConfig = validateExecuteTasks(tasks);
|
|
122
|
+
if (missingConfig.length > 0) {
|
|
123
|
+
handlers.addToQueue(createValidateDefinition(missingConfig, userRequest, service, (error) => {
|
|
124
|
+
handlers.onError(error);
|
|
125
|
+
}, () => {
|
|
126
|
+
handlers.addToQueue(createExecuteDefinition(tasks, service));
|
|
127
|
+
}, (operation) => {
|
|
128
|
+
handlers.onAborted(operation);
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
handlers.addToQueue(createExecuteDefinition(tasks, service));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
package/dist/types/types.js
CHANGED
|
@@ -11,7 +11,6 @@ export var ComponentName;
|
|
|
11
11
|
ComponentName["Introspect"] = "introspect";
|
|
12
12
|
ComponentName["Report"] = "report";
|
|
13
13
|
ComponentName["Answer"] = "answer";
|
|
14
|
-
ComponentName["AnswerDisplay"] = "answerDisplay";
|
|
15
14
|
ComponentName["Execute"] = "execute";
|
|
16
15
|
ComponentName["Validate"] = "validate";
|
|
17
16
|
})(ComponentName || (ComponentName = {}));
|
package/dist/ui/Answer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
@@ -7,26 +7,22 @@ import { formatErrorMessage } from '../services/messages.js';
|
|
|
7
7
|
import { withMinimumTime } from '../services/timing.js';
|
|
8
8
|
import { Spinner } from './Spinner.js';
|
|
9
9
|
const MINIMUM_PROCESSING_TIME = 400;
|
|
10
|
-
export function Answer({ question, state,
|
|
11
|
-
const done = state?.done ?? false;
|
|
12
|
-
const isCurrent = done === false;
|
|
10
|
+
export function Answer({ question, state, isActive = true, service, handlers, }) {
|
|
13
11
|
const [error, setError] = useState(null);
|
|
14
|
-
const [
|
|
12
|
+
const [answer, setAnswer] = useState(state?.answer ?? null);
|
|
15
13
|
useInput((input, key) => {
|
|
16
|
-
if (key.escape &&
|
|
17
|
-
|
|
18
|
-
onAborted();
|
|
14
|
+
if (key.escape && isActive) {
|
|
15
|
+
handlers?.onAborted('answer');
|
|
19
16
|
}
|
|
20
|
-
}, { isActive
|
|
17
|
+
}, { isActive });
|
|
21
18
|
useEffect(() => {
|
|
22
19
|
// Skip processing if done
|
|
23
|
-
if (
|
|
20
|
+
if (!isActive) {
|
|
24
21
|
return;
|
|
25
22
|
}
|
|
26
23
|
// Skip processing if no service available
|
|
27
24
|
if (!service) {
|
|
28
25
|
setError('No service available');
|
|
29
|
-
setIsLoading(false);
|
|
30
26
|
return;
|
|
31
27
|
}
|
|
32
28
|
let mounted = true;
|
|
@@ -36,21 +32,19 @@ export function Answer({ question, state, service, onError, onComplete, onAborte
|
|
|
36
32
|
const result = await withMinimumTime(() => svc.processWithTool(question, 'answer'), MINIMUM_PROCESSING_TIME);
|
|
37
33
|
if (mounted) {
|
|
38
34
|
// Extract answer from result
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
const answerText = result.answer || '';
|
|
36
|
+
setAnswer(answerText);
|
|
37
|
+
// Update component state so answer persists in timeline
|
|
38
|
+
handlers?.updateState({ answer: answerText });
|
|
39
|
+
// Signal completion
|
|
40
|
+
handlers?.onComplete();
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
catch (err) {
|
|
45
44
|
if (mounted) {
|
|
46
45
|
const errorMessage = formatErrorMessage(err);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
onError(errorMessage);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
setError(errorMessage);
|
|
53
|
-
}
|
|
46
|
+
setError(errorMessage);
|
|
47
|
+
handlers?.onError(errorMessage);
|
|
54
48
|
}
|
|
55
49
|
}
|
|
56
50
|
}
|
|
@@ -58,10 +52,7 @@ export function Answer({ question, state, service, onError, onComplete, onAborte
|
|
|
58
52
|
return () => {
|
|
59
53
|
mounted = false;
|
|
60
54
|
};
|
|
61
|
-
}, [question,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Finding answer. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
|
|
55
|
+
}, [question, isActive, service, handlers]);
|
|
56
|
+
const lines = answer ? answer.split('\n') : [];
|
|
57
|
+
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] }) }))] }));
|
|
67
58
|
}
|
package/dist/ui/Command.js
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx
|
|
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
4
|
import { TaskType } from '../types/types.js';
|
|
5
5
|
import { Colors } from '../services/colors.js';
|
|
6
|
-
import {
|
|
6
|
+
import { createPlanDefinition } from '../services/components.js';
|
|
7
7
|
import { formatErrorMessage } from '../services/messages.js';
|
|
8
|
+
import { useInput } from '../services/keyboard.js';
|
|
9
|
+
import { handleRefinement } from '../services/refinement.js';
|
|
10
|
+
import { routeTasksWithConfirm } from '../services/task-router.js';
|
|
8
11
|
import { ensureMinimumTime } from '../services/timing.js';
|
|
9
12
|
import { Spinner } from './Spinner.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const [error, setError] = useState(null);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
onAborted();
|
|
13
|
+
import { UserQuery } from './UserQuery.js';
|
|
14
|
+
const MIN_PROCESSING_TIME = 400; // purely for visual effect
|
|
15
|
+
export function Command({ command, state, isActive = true, service, handlers, onAborted, }) {
|
|
16
|
+
const [error, setError] = useState(state?.error ?? null);
|
|
17
|
+
useInput((_, key) => {
|
|
18
|
+
if (key.escape && isActive) {
|
|
19
|
+
handlers?.onAborted('request');
|
|
20
|
+
onAborted?.('request');
|
|
19
21
|
}
|
|
20
|
-
}, { isActive
|
|
22
|
+
}, { isActive });
|
|
21
23
|
useEffect(() => {
|
|
22
|
-
// Skip processing if
|
|
23
|
-
if (
|
|
24
|
+
// Skip processing if not active (showing historical/final state)
|
|
25
|
+
if (!isActive) {
|
|
24
26
|
return;
|
|
25
27
|
}
|
|
26
28
|
// Skip processing if no service available
|
|
27
29
|
if (!service) {
|
|
28
30
|
setError('No service available');
|
|
29
|
-
setIsLoading(false);
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
let mounted = true;
|
|
@@ -45,21 +46,39 @@ export function Command({ command, state, service, children, onError, onComplete
|
|
|
45
46
|
}
|
|
46
47
|
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
47
48
|
if (mounted) {
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
// Save result to state for timeline display
|
|
50
|
+
handlers?.updateState({
|
|
51
|
+
message: result.message,
|
|
52
|
+
tasks: result.tasks,
|
|
53
|
+
});
|
|
54
|
+
// Check if tasks contain DEFINE type (variant selection needed)
|
|
55
|
+
const hasDefineTask = result.tasks.some((task) => task.type === TaskType.Define);
|
|
56
|
+
// Create Plan definition
|
|
57
|
+
const planDefinition = createPlanDefinition(result.message, result.tasks, hasDefineTask
|
|
58
|
+
? async (selectedTasks) => {
|
|
59
|
+
// Refinement flow for DEFINE tasks
|
|
60
|
+
await handleRefinement(selectedTasks, svc, command, handlers);
|
|
61
|
+
}
|
|
62
|
+
: undefined);
|
|
63
|
+
if (hasDefineTask) {
|
|
64
|
+
// Has DEFINE tasks: Add Plan to queue for selection
|
|
65
|
+
// The refinement callback will handle routing after user selects
|
|
66
|
+
handlers?.addToQueue(planDefinition);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// No DEFINE tasks: Use routing service for Confirm flow
|
|
70
|
+
routeTasksWithConfirm(result.tasks, result.message, svc, command, handlers, false);
|
|
71
|
+
}
|
|
72
|
+
// Move Command to timeline
|
|
73
|
+
handlers?.onComplete();
|
|
50
74
|
}
|
|
51
75
|
}
|
|
52
76
|
catch (err) {
|
|
53
77
|
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
54
78
|
if (mounted) {
|
|
55
79
|
const errorMessage = formatErrorMessage(err);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
onError(errorMessage);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
setError(errorMessage);
|
|
62
|
-
}
|
|
80
|
+
setError(errorMessage);
|
|
81
|
+
handlers?.onError(errorMessage);
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
84
|
}
|
|
@@ -67,7 +86,6 @@ export function Command({ command, state, service, children, onError, onComplete
|
|
|
67
86
|
return () => {
|
|
68
87
|
mounted = false;
|
|
69
88
|
};
|
|
70
|
-
}, [command,
|
|
71
|
-
|
|
72
|
-
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [_jsxs(Box, { paddingX: done ? 1 : 0, marginX: done ? -1 : 0, backgroundColor: done ? Colors.Background.UserQuery : undefined, children: [_jsxs(Text, { color: isCurrent ? Colors.Text.Active : Colors.Text.UserQuery, children: ["> pls ", command] }), isLoading && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Spinner, {})] }))] }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
|
|
89
|
+
}, [command, isActive, service, handlers]);
|
|
90
|
+
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] }) }))] }));
|
|
73
91
|
}
|
package/dist/ui/Component.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import { memo } from 'react';
|
|
3
3
|
import { ComponentName } from '../types/types.js';
|
|
4
4
|
import { Answer } from './Answer.js';
|
|
5
|
-
import { AnswerDisplay } from './AnswerDisplay.js';
|
|
6
5
|
import { Command } from './Command.js';
|
|
7
6
|
import { Confirm } from './Confirm.js';
|
|
8
7
|
import { Config } from './Config.js';
|
|
@@ -15,62 +14,36 @@ import { Refinement } from './Refinement.js';
|
|
|
15
14
|
import { Report } from './Report.js';
|
|
16
15
|
import { Validate } from './Validate.js';
|
|
17
16
|
import { Welcome } from './Welcome.js';
|
|
18
|
-
export const Component =
|
|
17
|
+
export const Component = memo(function Component({ def, isActive, debug, }) {
|
|
18
|
+
// For stateless components, always inactive
|
|
19
|
+
const isStatelessComponent = !('state' in def);
|
|
20
|
+
const componentIsActive = isStatelessComponent ? false : isActive;
|
|
19
21
|
switch (def.name) {
|
|
20
22
|
case ComponentName.Welcome:
|
|
21
23
|
return _jsx(Welcome, { ...def.props });
|
|
22
|
-
case ComponentName.Config:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return _jsx(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const props = def.props;
|
|
29
|
-
const state = def.state;
|
|
30
|
-
return _jsx(Command, { ...props, state: state });
|
|
31
|
-
}
|
|
32
|
-
case ComponentName.Plan: {
|
|
33
|
-
const props = def.props;
|
|
34
|
-
const state = def.state;
|
|
35
|
-
return _jsx(Plan, { ...props, state: state, debug: debug });
|
|
36
|
-
}
|
|
24
|
+
case ComponentName.Config:
|
|
25
|
+
return (_jsx(Config, { ...def.props, state: def.state, isActive: componentIsActive, debug: debug }));
|
|
26
|
+
case ComponentName.Command:
|
|
27
|
+
return _jsx(Command, { ...def.props, state: def.state, isActive: isActive });
|
|
28
|
+
case ComponentName.Plan:
|
|
29
|
+
return (_jsx(Plan, { ...def.props, state: def.state, isActive: componentIsActive, debug: debug }));
|
|
37
30
|
case ComponentName.Feedback:
|
|
38
31
|
return _jsx(Feedback, { ...def.props });
|
|
39
32
|
case ComponentName.Message:
|
|
40
33
|
return _jsx(Message, { ...def.props });
|
|
41
|
-
case ComponentName.Refinement:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return _jsx(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const props = def.props;
|
|
48
|
-
const state = def.state;
|
|
49
|
-
return _jsx(Confirm, { ...props, state: state });
|
|
50
|
-
}
|
|
51
|
-
case ComponentName.Introspect: {
|
|
52
|
-
const props = def.props;
|
|
53
|
-
const state = def.state;
|
|
54
|
-
return _jsx(Introspect, { ...props, state: state, debug: debug });
|
|
55
|
-
}
|
|
34
|
+
case ComponentName.Refinement:
|
|
35
|
+
return (_jsx(Refinement, { ...def.props, state: def.state, isActive: isActive }));
|
|
36
|
+
case ComponentName.Confirm:
|
|
37
|
+
return _jsx(Confirm, { ...def.props, state: def.state, isActive: isActive });
|
|
38
|
+
case ComponentName.Introspect:
|
|
39
|
+
return (_jsx(Introspect, { ...def.props, state: def.state, isActive: componentIsActive, debug: debug }));
|
|
56
40
|
case ComponentName.Report:
|
|
57
41
|
return _jsx(Report, { ...def.props });
|
|
58
|
-
case ComponentName.Answer:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return _jsx(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return _jsx(AnswerDisplay, { ...def.props });
|
|
65
|
-
case ComponentName.Execute: {
|
|
66
|
-
const props = def.props;
|
|
67
|
-
const state = def.state;
|
|
68
|
-
return _jsx(Execute, { ...props, state: state });
|
|
69
|
-
}
|
|
70
|
-
case ComponentName.Validate: {
|
|
71
|
-
const props = def.props;
|
|
72
|
-
const state = def.state;
|
|
73
|
-
return _jsx(Validate, { ...props, state: state });
|
|
74
|
-
}
|
|
42
|
+
case ComponentName.Answer:
|
|
43
|
+
return _jsx(Answer, { ...def.props, state: def.state, isActive: isActive });
|
|
44
|
+
case ComponentName.Execute:
|
|
45
|
+
return _jsx(Execute, { ...def.props, state: def.state, isActive: isActive });
|
|
46
|
+
case ComponentName.Validate:
|
|
47
|
+
return (_jsx(Validate, { ...def.props, state: def.state, isActive: isActive, debug: debug }));
|
|
75
48
|
}
|
|
76
49
|
});
|
package/dist/ui/Config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import { useState } from 'react';
|
|
3
3
|
import { Box, Text, useFocus } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
5
|
import { Colors } from '../services/colors.js';
|
|
@@ -10,8 +10,8 @@ export var StepType;
|
|
|
10
10
|
StepType["Selection"] = "selection";
|
|
11
11
|
})(StepType || (StepType = {}));
|
|
12
12
|
function TextStep({ value, placeholder, validate, onChange, onSubmit, }) {
|
|
13
|
-
const [inputValue, setInputValue] =
|
|
14
|
-
const [validationFailed, setValidationFailed] =
|
|
13
|
+
const [inputValue, setInputValue] = useState(value);
|
|
14
|
+
const [validationFailed, setValidationFailed] = useState(false);
|
|
15
15
|
const { isFocused } = useFocus({ autoFocus: true });
|
|
16
16
|
const handleChange = (newValue) => {
|
|
17
17
|
setInputValue(newValue);
|
|
@@ -57,20 +57,27 @@ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
|
|
|
57
57
|
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
|
|
58
58
|
}) }));
|
|
59
59
|
}
|
|
60
|
-
export function Config({ steps, state, debug, onFinished, onAborted }) {
|
|
61
|
-
|
|
62
|
-
const [step, setStep] =
|
|
63
|
-
const [values, setValues] =
|
|
60
|
+
export function Config({ steps, state, isActive = true, debug, handlers, onFinished, onAborted, }) {
|
|
61
|
+
// isActive passed as prop
|
|
62
|
+
const [step, setStep] = useState(!isActive ? (state?.completedStep ?? steps.length) : 0);
|
|
63
|
+
const [values, setValues] = useState(() => {
|
|
64
|
+
// If not active and we have saved state values, use those
|
|
65
|
+
if (!isActive && state?.values) {
|
|
66
|
+
return state.values;
|
|
67
|
+
}
|
|
68
|
+
// Otherwise initialize from step defaults
|
|
64
69
|
const initial = {};
|
|
65
70
|
steps.forEach((stepConfig) => {
|
|
71
|
+
// Use full path if available, otherwise use key
|
|
72
|
+
const configKey = stepConfig.path || stepConfig.key;
|
|
66
73
|
switch (stepConfig.type) {
|
|
67
74
|
case StepType.Text:
|
|
68
75
|
if (stepConfig.value !== null) {
|
|
69
|
-
initial[
|
|
76
|
+
initial[configKey] = stepConfig.value;
|
|
70
77
|
}
|
|
71
78
|
break;
|
|
72
79
|
case StepType.Selection:
|
|
73
|
-
initial[
|
|
80
|
+
initial[configKey] =
|
|
74
81
|
stepConfig.options[stepConfig.defaultIndex].value;
|
|
75
82
|
break;
|
|
76
83
|
default: {
|
|
@@ -81,8 +88,8 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
|
|
|
81
88
|
});
|
|
82
89
|
return initial;
|
|
83
90
|
});
|
|
84
|
-
const [inputValue, setInputValue] =
|
|
85
|
-
const [selectedIndex, setSelectedIndex] =
|
|
91
|
+
const [inputValue, setInputValue] = useState('');
|
|
92
|
+
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
86
93
|
const firstStep = steps[0];
|
|
87
94
|
return firstStep?.type === StepType.Selection ? firstStep.defaultIndex : 0;
|
|
88
95
|
});
|
|
@@ -93,19 +100,20 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
|
|
|
93
100
|
return value.replace(/\n/g, '').trim();
|
|
94
101
|
};
|
|
95
102
|
useInput((input, key) => {
|
|
96
|
-
if (key.escape &&
|
|
103
|
+
if (key.escape && isActive && step < steps.length) {
|
|
97
104
|
// Save current value before aborting
|
|
98
105
|
const currentStepConfig = steps[step];
|
|
99
106
|
if (currentStepConfig) {
|
|
107
|
+
const configKey = currentStepConfig.path || currentStepConfig.key;
|
|
100
108
|
let currentValue = '';
|
|
101
109
|
switch (currentStepConfig.type) {
|
|
102
110
|
case StepType.Text:
|
|
103
|
-
currentValue = inputValue || values[
|
|
111
|
+
currentValue = inputValue || values[configKey] || '';
|
|
104
112
|
break;
|
|
105
113
|
case StepType.Selection:
|
|
106
114
|
currentValue =
|
|
107
115
|
currentStepConfig.options[selectedIndex]?.value ||
|
|
108
|
-
values[
|
|
116
|
+
values[configKey] ||
|
|
109
117
|
'';
|
|
110
118
|
break;
|
|
111
119
|
default: {
|
|
@@ -114,16 +122,16 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
|
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
124
|
if (currentValue) {
|
|
117
|
-
setValues({ ...values, [
|
|
125
|
+
setValues({ ...values, [configKey]: currentValue });
|
|
118
126
|
}
|
|
119
127
|
}
|
|
120
128
|
if (onAborted) {
|
|
121
|
-
onAborted();
|
|
129
|
+
onAborted('configuration');
|
|
122
130
|
}
|
|
123
131
|
return;
|
|
124
132
|
}
|
|
125
133
|
const currentStep = steps[step];
|
|
126
|
-
if (
|
|
134
|
+
if (isActive && step < steps.length && currentStep) {
|
|
127
135
|
switch (currentStep.type) {
|
|
128
136
|
case StepType.Selection:
|
|
129
137
|
if (key.tab) {
|
|
@@ -173,7 +181,9 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
|
|
|
173
181
|
if (!finalValue) {
|
|
174
182
|
return;
|
|
175
183
|
}
|
|
176
|
-
|
|
184
|
+
// Use full path if available, otherwise use key
|
|
185
|
+
const configKey = currentStepConfig.path || currentStepConfig.key;
|
|
186
|
+
const newValues = { ...values, [configKey]: finalValue };
|
|
177
187
|
setValues(newValues);
|
|
178
188
|
setInputValue('');
|
|
179
189
|
if (step === steps.length - 1) {
|
|
@@ -181,9 +191,21 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
|
|
|
181
191
|
if (onFinished) {
|
|
182
192
|
onFinished(newValues);
|
|
183
193
|
}
|
|
194
|
+
// Save state before completing
|
|
195
|
+
handlers?.updateState({
|
|
196
|
+
values: newValues,
|
|
197
|
+
completedStep: steps.length,
|
|
198
|
+
});
|
|
199
|
+
// Signal Workflow that config is complete
|
|
200
|
+
handlers?.onComplete();
|
|
184
201
|
setStep(steps.length);
|
|
185
202
|
}
|
|
186
203
|
else {
|
|
204
|
+
// Save state after each step
|
|
205
|
+
handlers?.updateState({
|
|
206
|
+
values: newValues,
|
|
207
|
+
completedStep: step + 1,
|
|
208
|
+
});
|
|
187
209
|
setStep(step + 1);
|
|
188
210
|
// Reset selection index for next step
|
|
189
211
|
const nextStep = steps[step + 1];
|
|
@@ -193,15 +215,18 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
|
|
|
193
215
|
}
|
|
194
216
|
};
|
|
195
217
|
const renderStepInput = (stepConfig, isCurrentStep) => {
|
|
218
|
+
const configKey = stepConfig.path || stepConfig.key;
|
|
219
|
+
// Use state values if not active (in timeline), otherwise use local values
|
|
220
|
+
const displayValue = !isActive && state?.values ? state.values[configKey] : values[configKey];
|
|
196
221
|
switch (stepConfig.type) {
|
|
197
222
|
case StepType.Text:
|
|
198
223
|
if (isCurrentStep) {
|
|
199
224
|
return (_jsx(TextStep, { value: inputValue, placeholder: stepConfig.value || undefined, validate: stepConfig.validate, onChange: setInputValue, onSubmit: handleSubmit }));
|
|
200
225
|
}
|
|
201
|
-
return _jsx(Text, { dimColor: true, children:
|
|
226
|
+
return (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: displayValue || '' }));
|
|
202
227
|
case StepType.Selection: {
|
|
203
228
|
if (!isCurrentStep) {
|
|
204
|
-
const selectedOption = stepConfig.options.find((opt) => opt.value ===
|
|
229
|
+
const selectedOption = stepConfig.options.find((opt) => opt.value === displayValue);
|
|
205
230
|
return _jsx(Text, { dimColor: true, children: selectedOption?.label || '' });
|
|
206
231
|
}
|
|
207
232
|
return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: isCurrentStep }));
|
|
@@ -212,14 +237,14 @@ export function Config({ steps, state, debug, onFinished, onAborted }) {
|
|
|
212
237
|
}
|
|
213
238
|
}
|
|
214
239
|
};
|
|
215
|
-
return (_jsx(Box, { flexDirection: "column", children: steps.map((stepConfig, index) => {
|
|
216
|
-
const isCurrentStep = index === step &&
|
|
240
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: 1, children: steps.map((stepConfig, index) => {
|
|
241
|
+
const isCurrentStep = index === step && isActive;
|
|
217
242
|
const isCompleted = index < step;
|
|
218
|
-
const wasAborted = index === step &&
|
|
243
|
+
const wasAborted = index === step && !isActive;
|
|
219
244
|
const shouldShow = isCompleted || isCurrentStep || wasAborted;
|
|
220
245
|
if (!shouldShow) {
|
|
221
246
|
return null;
|
|
222
247
|
}
|
|
223
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), debug && stepConfig.path && (_jsxs(Text, { color: Colors.Type.Define, children: ['{', stepConfig.path, '}'] }))] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.key));
|
|
248
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), debug && stepConfig.path && (_jsxs(Text, { color: Colors.Type.Define, children: ['{', stepConfig.path, '}'] }))] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.path || stepConfig.key));
|
|
224
249
|
}) }));
|
|
225
250
|
}
|