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.
@@ -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
+ }
@@ -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, service, onError, onComplete, onAborted, }) {
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 [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
12
+ const [answer, setAnswer] = useState(state?.answer ?? null);
15
13
  useInput((input, key) => {
16
- if (key.escape && isLoading && !done) {
17
- setIsLoading(false);
18
- onAborted();
14
+ if (key.escape && isActive) {
15
+ handlers?.onAborted('answer');
19
16
  }
20
- }, { isActive: isLoading && !done });
17
+ }, { isActive });
21
18
  useEffect(() => {
22
19
  // Skip processing if done
23
- if (done) {
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 answer = result.answer || '';
40
- setIsLoading(false);
41
- onComplete?.(answer);
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
- setIsLoading(false);
48
- if (onError) {
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, done, service, onComplete, onError]);
62
- // Return null when done (like Introspect)
63
- if (done || (!isLoading && !error)) {
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
  }
@@ -1,32 +1,33 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
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 { useInput } from '../services/keyboard.js';
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
- const MIN_PROCESSING_TIME = 1000; // purely for visual effect
11
- export function Command({ command, state, service, children, onError, onComplete, onAborted, }) {
12
- const done = state?.done ?? false;
13
- const [error, setError] = useState(null);
14
- const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
15
- useInput((input, key) => {
16
- if (key.escape && isLoading && !done) {
17
- setIsLoading(false);
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: isLoading && !done });
22
+ }, { isActive });
21
23
  useEffect(() => {
22
- // Skip processing if done (showing historical/final state)
23
- if (done) {
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
- setIsLoading(false);
49
- onComplete?.(result.message, result.tasks);
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
- setIsLoading(false);
57
- if (onError) {
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, done, service]);
71
- const isCurrent = done === false;
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
  }
@@ -1,8 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import React from 'react';
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 = React.memo(function Component({ def, debug, }) {
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
- const props = def.props;
24
- const state = def.state;
25
- return _jsx(Config, { ...props, state: state, debug: debug });
26
- }
27
- case ComponentName.Command: {
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
- const props = def.props;
43
- const state = def.state;
44
- return _jsx(Refinement, { ...props, state: state });
45
- }
46
- case ComponentName.Confirm: {
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
- const props = def.props;
60
- const state = def.state;
61
- return _jsx(Answer, { ...props, state: state });
62
- }
63
- case ComponentName.AnswerDisplay:
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 React from 'react';
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] = React.useState(value);
14
- const [validationFailed, setValidationFailed] = React.useState(false);
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
- const done = state?.done ?? false;
62
- const [step, setStep] = React.useState(done ? steps.length : 0);
63
- const [values, setValues] = React.useState(() => {
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[stepConfig.key] = stepConfig.value;
76
+ initial[configKey] = stepConfig.value;
70
77
  }
71
78
  break;
72
79
  case StepType.Selection:
73
- initial[stepConfig.key] =
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] = React.useState('');
85
- const [selectedIndex, setSelectedIndex] = React.useState(() => {
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 && !done && step < steps.length) {
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[currentStepConfig.key] || '';
111
+ currentValue = inputValue || values[configKey] || '';
104
112
  break;
105
113
  case StepType.Selection:
106
114
  currentValue =
107
115
  currentStepConfig.options[selectedIndex]?.value ||
108
- values[currentStepConfig.key] ||
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, [currentStepConfig.key]: currentValue });
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 (!done && step < steps.length && currentStep) {
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
- const newValues = { ...values, [currentStepConfig.key]: finalValue };
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: values[stepConfig.key] || '' });
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 === values[stepConfig.key]);
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 && !done;
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 && done;
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
  }