prompt-language-shell 0.6.0 → 0.6.2
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/CONFIG.md +4 -2
- package/dist/services/anthropic.js +49 -4
- package/dist/services/components.js +2 -2
- package/dist/services/configuration.js +91 -19
- package/dist/services/refinement.js +2 -1
- package/dist/services/task-router.js +8 -2
- package/dist/ui/Answer.js +1 -1
- package/dist/ui/Command.js +5 -6
- package/dist/ui/Config.js +42 -45
- package/dist/ui/Execute.js +2 -2
- package/dist/ui/Introspect.js +1 -1
- package/dist/ui/Plan.js +3 -3
- package/dist/ui/Workflow.js +29 -16
- package/package.json +1 -1
package/dist/config/CONFIG.md
CHANGED
|
@@ -7,7 +7,8 @@ based on their query.
|
|
|
7
7
|
## Input
|
|
8
8
|
|
|
9
9
|
You will receive:
|
|
10
|
-
- `configStructure`: Object mapping config keys to descriptions (e.g., {"anthropic.key": "Anthropic API key"})
|
|
10
|
+
- `configStructure`: Object mapping config keys to descriptions (e.g., {"anthropic.key": "Anthropic API key", "settings.debug": "Debug mode (optional)"})
|
|
11
|
+
- `configuredKeys`: Array of keys that exist in the user's config file (e.g., ["anthropic.key", "anthropic.model", "settings.debug"])
|
|
11
12
|
- `query`: User's request (e.g., "app", "mode", "anthropic", or empty)
|
|
12
13
|
|
|
13
14
|
## Task
|
|
@@ -18,7 +19,8 @@ Determine which config keys the user wants to configure and return them as tasks
|
|
|
18
19
|
|
|
19
20
|
### Query: "app" or empty/unclear
|
|
20
21
|
- Return all **required** config keys (those needed for the app to work)
|
|
21
|
-
- Also include any
|
|
22
|
+
- Also include any keys marked as "(optional)" that appear in `configuredKeys` (optional settings that exist in user's config file)
|
|
23
|
+
- Also include any keys marked as "(discovered)" (they exist in user's config file but aren't in schema)
|
|
22
24
|
- Required keys: `anthropic.key`, `anthropic.model`
|
|
23
25
|
|
|
24
26
|
### Query: "mode"
|
|
@@ -1,7 +1,49 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
-
import { getAvailableConfigStructure, } from './configuration.js';
|
|
2
|
+
import { getAvailableConfigStructure, getConfiguredKeys, } from './configuration.js';
|
|
3
3
|
import { formatSkillsForPrompt, loadSkills } from './skills.js';
|
|
4
4
|
import { toolRegistry } from './tool-registry.js';
|
|
5
|
+
/**
|
|
6
|
+
* Wraps text to ensure no line exceeds the specified width.
|
|
7
|
+
* Breaks at word boundaries to maintain readability.
|
|
8
|
+
*/
|
|
9
|
+
function wrapText(text, maxWidth) {
|
|
10
|
+
const words = text.split(/\s+/);
|
|
11
|
+
const lines = [];
|
|
12
|
+
let currentLine = '';
|
|
13
|
+
for (const word of words) {
|
|
14
|
+
// If adding this word would exceed max width, start a new line
|
|
15
|
+
if (currentLine.length > 0 &&
|
|
16
|
+
currentLine.length + 1 + word.length > maxWidth) {
|
|
17
|
+
lines.push(currentLine);
|
|
18
|
+
currentLine = word;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
currentLine = currentLine.length > 0 ? `${currentLine} ${word}` : word;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Add the last line if not empty
|
|
25
|
+
if (currentLine.length > 0) {
|
|
26
|
+
lines.push(currentLine);
|
|
27
|
+
}
|
|
28
|
+
return lines.join('\n');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Removes citation tags and other markup from answer text.
|
|
32
|
+
* Web search responses may include <cite> tags that should be stripped.
|
|
33
|
+
* Also wraps text to ensure lines don't exceed 80 characters.
|
|
34
|
+
*/
|
|
35
|
+
export function cleanAnswerText(text) {
|
|
36
|
+
// Remove citation tags like <cite index="1-1">content</cite>
|
|
37
|
+
// Replace with just the content
|
|
38
|
+
let cleaned = text.replace(/<cite[^>]*>(.*?)<\/cite>/g, '$1');
|
|
39
|
+
// Remove any other XML/HTML tags that might appear
|
|
40
|
+
cleaned = cleaned.replace(/<[^>]+>/g, '');
|
|
41
|
+
// Normalize whitespace, converting all whitespace to single spaces
|
|
42
|
+
cleaned = cleaned.replace(/\s+/g, ' ').trim();
|
|
43
|
+
// Wrap text to 80 characters per line
|
|
44
|
+
cleaned = wrapText(cleaned, 80);
|
|
45
|
+
return cleaned;
|
|
46
|
+
}
|
|
5
47
|
export class AnthropicService {
|
|
6
48
|
client;
|
|
7
49
|
model;
|
|
@@ -27,9 +69,12 @@ export class AnthropicService {
|
|
|
27
69
|
// Add config structure for config tool only
|
|
28
70
|
if (toolName === 'config') {
|
|
29
71
|
const configStructure = getAvailableConfigStructure();
|
|
72
|
+
const configuredKeys = getConfiguredKeys();
|
|
30
73
|
const configSection = '\n\n## Available Configuration\n\n' +
|
|
31
74
|
'Config structure (key: description):\n' +
|
|
32
|
-
JSON.stringify(configStructure, null, 2)
|
|
75
|
+
JSON.stringify(configStructure, null, 2) +
|
|
76
|
+
'\n\nConfigured keys (keys that exist in config file):\n' +
|
|
77
|
+
JSON.stringify(configuredKeys, null, 2);
|
|
33
78
|
systemPrompt += configSection;
|
|
34
79
|
}
|
|
35
80
|
// Build tools array - add web search for answer tool
|
|
@@ -67,7 +112,7 @@ export class AnthropicService {
|
|
|
67
112
|
return {
|
|
68
113
|
message: '',
|
|
69
114
|
tasks: [],
|
|
70
|
-
answer: textContent.text,
|
|
115
|
+
answer: cleanAnswerText(textContent.text),
|
|
71
116
|
};
|
|
72
117
|
}
|
|
73
118
|
}
|
|
@@ -111,7 +156,7 @@ export class AnthropicService {
|
|
|
111
156
|
return {
|
|
112
157
|
message: '',
|
|
113
158
|
tasks: [],
|
|
114
|
-
answer: input.answer,
|
|
159
|
+
answer: cleanAnswerText(input.answer),
|
|
115
160
|
};
|
|
116
161
|
}
|
|
117
162
|
// Handle plan and introspect tool responses
|
|
@@ -164,8 +164,8 @@ export function createConfigStepsFromSchema(keys) {
|
|
|
164
164
|
path: key,
|
|
165
165
|
type: StepType.Selection,
|
|
166
166
|
options: [
|
|
167
|
-
{ label: '
|
|
168
|
-
{ label: '
|
|
167
|
+
{ label: 'yes', value: 'true' },
|
|
168
|
+
{ label: 'no', value: 'false' },
|
|
169
169
|
],
|
|
170
170
|
defaultIndex: currentBool !== undefined ? (currentBool ? 0 : 1) : 0,
|
|
171
171
|
validate: getValidator(definition),
|
|
@@ -263,21 +263,14 @@ export function getMissingConfigKeys() {
|
|
|
263
263
|
return missing;
|
|
264
264
|
}
|
|
265
265
|
/**
|
|
266
|
-
* Get
|
|
267
|
-
* Returns keys
|
|
266
|
+
* Get list of configured keys from config file
|
|
267
|
+
* Returns array of dot-notation keys that exist in the config file
|
|
268
268
|
*/
|
|
269
|
-
export function
|
|
270
|
-
const schema = getConfigSchema();
|
|
271
|
-
const structure = {};
|
|
272
|
-
// Add core schema keys with descriptions
|
|
273
|
-
for (const [key, definition] of Object.entries(schema)) {
|
|
274
|
-
structure[key] = definition.description;
|
|
275
|
-
}
|
|
276
|
-
// Add discovered keys from config file (if it exists)
|
|
269
|
+
export function getConfiguredKeys() {
|
|
277
270
|
try {
|
|
278
271
|
const configFile = getConfigFile();
|
|
279
272
|
if (!existsSync(configFile)) {
|
|
280
|
-
return
|
|
273
|
+
return [];
|
|
281
274
|
}
|
|
282
275
|
const content = readFileSync(configFile, 'utf-8');
|
|
283
276
|
const parsed = YAML.parse(content);
|
|
@@ -296,25 +289,103 @@ export function getAvailableConfigStructure() {
|
|
|
296
289
|
return result;
|
|
297
290
|
}
|
|
298
291
|
const flatConfig = flattenConfig(parsed);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
292
|
+
return Object.keys(flatConfig);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get available config structure for CONFIG tool
|
|
300
|
+
* Returns keys with descriptions only (no values for privacy)
|
|
301
|
+
* Marks optional keys as "(optional)"
|
|
302
|
+
*/
|
|
303
|
+
export function getAvailableConfigStructure() {
|
|
304
|
+
const schema = getConfigSchema();
|
|
305
|
+
const structure = {};
|
|
306
|
+
// Try to load existing config to see which keys are already set
|
|
307
|
+
let flatConfig = {};
|
|
308
|
+
try {
|
|
309
|
+
const configFile = getConfigFile();
|
|
310
|
+
if (existsSync(configFile)) {
|
|
311
|
+
const content = readFileSync(configFile, 'utf-8');
|
|
312
|
+
const parsed = YAML.parse(content);
|
|
313
|
+
// Flatten nested config to dot notation
|
|
314
|
+
function flattenConfig(obj, prefix = '') {
|
|
315
|
+
const result = {};
|
|
316
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
317
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
318
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
319
|
+
Object.assign(result, flattenConfig(value, fullKey));
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
result[fullKey] = value;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return result;
|
|
303
326
|
}
|
|
327
|
+
flatConfig = flattenConfig(parsed);
|
|
304
328
|
}
|
|
305
329
|
}
|
|
306
330
|
catch {
|
|
307
|
-
// Config file doesn't exist or can't be read
|
|
331
|
+
// Config file doesn't exist or can't be read
|
|
332
|
+
}
|
|
333
|
+
// Add schema keys with descriptions
|
|
334
|
+
// Mark optional keys as (optional)
|
|
335
|
+
for (const [key, definition] of Object.entries(schema)) {
|
|
336
|
+
const isOptional = !definition.required;
|
|
337
|
+
if (isOptional) {
|
|
338
|
+
structure[key] = `${definition.description} (optional)`;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
structure[key] = definition.description;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Add discovered keys that aren't in schema
|
|
345
|
+
for (const key of Object.keys(flatConfig)) {
|
|
346
|
+
if (!(key in structure)) {
|
|
347
|
+
structure[key] = `${key} (discovered)`;
|
|
348
|
+
}
|
|
308
349
|
}
|
|
309
350
|
return structure;
|
|
310
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Convert string value to appropriate type based on schema definition
|
|
354
|
+
*/
|
|
355
|
+
function parseConfigValue(key, stringValue, schema) {
|
|
356
|
+
// If we have a schema definition, use its type
|
|
357
|
+
if (key in schema) {
|
|
358
|
+
const definition = schema[key];
|
|
359
|
+
switch (definition.type) {
|
|
360
|
+
case 'boolean':
|
|
361
|
+
return stringValue === 'true';
|
|
362
|
+
case 'number':
|
|
363
|
+
return Number(stringValue);
|
|
364
|
+
case 'string':
|
|
365
|
+
case 'regexp':
|
|
366
|
+
case 'enum':
|
|
367
|
+
return stringValue;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// No schema definition - try to infer type from string value
|
|
371
|
+
// This handles skill-defined configs that may not be in schema yet
|
|
372
|
+
if (stringValue === 'true' || stringValue === 'false') {
|
|
373
|
+
return stringValue === 'true';
|
|
374
|
+
}
|
|
375
|
+
if (!isNaN(Number(stringValue)) && stringValue.trim() !== '') {
|
|
376
|
+
return Number(stringValue);
|
|
377
|
+
}
|
|
378
|
+
return stringValue;
|
|
379
|
+
}
|
|
311
380
|
/**
|
|
312
381
|
* Unflatten dotted keys into nested structure
|
|
313
382
|
* Example: { "product.alpha.path": "value" } -> { product: { alpha: { path: "value" } } }
|
|
383
|
+
* Converts string values to appropriate types based on config schema
|
|
314
384
|
*/
|
|
315
385
|
export function unflattenConfig(dotted) {
|
|
316
386
|
const result = {};
|
|
317
|
-
|
|
387
|
+
const schema = getConfigSchema();
|
|
388
|
+
for (const [dottedKey, stringValue] of Object.entries(dotted)) {
|
|
318
389
|
const parts = dottedKey.split('.');
|
|
319
390
|
const section = parts[0];
|
|
320
391
|
// Initialize section if needed
|
|
@@ -325,8 +396,9 @@ export function unflattenConfig(dotted) {
|
|
|
325
396
|
current[parts[i]] = current[parts[i]] ?? {};
|
|
326
397
|
current = current[parts[i]];
|
|
327
398
|
}
|
|
328
|
-
//
|
|
329
|
-
|
|
399
|
+
// Convert string value to appropriate type and set
|
|
400
|
+
const typedValue = parseConfigValue(dottedKey, stringValue, schema);
|
|
401
|
+
current[parts[parts.length - 1]] = typedValue;
|
|
330
402
|
}
|
|
331
403
|
return result;
|
|
332
404
|
}
|
|
@@ -25,7 +25,8 @@ export async function handleRefinement(selectedTasks, service, originalCommand,
|
|
|
25
25
|
// Complete the Refinement component
|
|
26
26
|
handlers.completeActive();
|
|
27
27
|
// Route refined tasks to appropriate components
|
|
28
|
-
routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, handlers, false // No DEFINE tasks in refined result
|
|
28
|
+
routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, handlers, false, // No DEFINE tasks in refined result
|
|
29
|
+
undefined // No commandComponent - use normal flow
|
|
29
30
|
);
|
|
30
31
|
}
|
|
31
32
|
catch (err) {
|
|
@@ -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, handlers, hasDefineTask = false, commandComponent) {
|
|
24
24
|
if (tasks.length === 0)
|
|
25
25
|
return;
|
|
26
26
|
// Filter out ignore and discard tasks early
|
|
@@ -49,7 +49,13 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
|
|
|
49
49
|
// User cancelled
|
|
50
50
|
handlers.onAborted(operation);
|
|
51
51
|
});
|
|
52
|
-
|
|
52
|
+
// Use atomic update if commandComponent provided, else normal flow
|
|
53
|
+
if (commandComponent) {
|
|
54
|
+
handlers.completeActive(planDefinition);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
handlers.addToTimeline(planDefinition);
|
|
58
|
+
}
|
|
53
59
|
handlers.addToQueue(confirmDefinition);
|
|
54
60
|
}
|
|
55
61
|
}
|
package/dist/ui/Answer.js
CHANGED
|
@@ -37,7 +37,7 @@ export function Answer({ question, state, isActive = true, service, handlers, })
|
|
|
37
37
|
// Update component state so answer persists in timeline
|
|
38
38
|
handlers?.updateState({ answer: answerText });
|
|
39
39
|
// Signal completion
|
|
40
|
-
handlers?.
|
|
40
|
+
handlers?.completeActive();
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
catch (err) {
|
package/dist/ui/Command.js
CHANGED
|
@@ -61,16 +61,15 @@ export function Command({ command, state, isActive = true, service, handlers, on
|
|
|
61
61
|
}
|
|
62
62
|
: undefined);
|
|
63
63
|
if (hasDefineTask) {
|
|
64
|
-
//
|
|
65
|
-
|
|
64
|
+
// DEFINE tasks: Move Command to timeline, add Plan to queue
|
|
65
|
+
handlers?.completeActive();
|
|
66
66
|
handlers?.addToQueue(planDefinition);
|
|
67
67
|
}
|
|
68
68
|
else {
|
|
69
|
-
// No DEFINE tasks:
|
|
70
|
-
routeTasksWithConfirm(result.tasks, result.message, svc, command, handlers, false
|
|
69
|
+
// No DEFINE tasks: Pass Plan to be added atomically with Command
|
|
70
|
+
routeTasksWithConfirm(result.tasks, result.message, svc, command, handlers, false, planDefinition // Pass Plan for atomic update
|
|
71
|
+
);
|
|
71
72
|
}
|
|
72
|
-
// Move Command to timeline
|
|
73
|
-
handlers?.onComplete();
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
catch (err) {
|
package/dist/ui/Config.js
CHANGED
|
@@ -90,8 +90,13 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
90
90
|
});
|
|
91
91
|
const [inputValue, setInputValue] = useState('');
|
|
92
92
|
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
// Initialize selectedIndex based on current step's defaultIndex
|
|
94
|
+
if (isActive &&
|
|
95
|
+
step < steps.length &&
|
|
96
|
+
steps[step].type === StepType.Selection) {
|
|
97
|
+
return steps[step].defaultIndex;
|
|
98
|
+
}
|
|
99
|
+
return 0;
|
|
95
100
|
});
|
|
96
101
|
const normalizeValue = (value) => {
|
|
97
102
|
if (value === null || value === undefined) {
|
|
@@ -99,10 +104,12 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
99
104
|
}
|
|
100
105
|
return value.replace(/\n/g, '').trim();
|
|
101
106
|
};
|
|
102
|
-
useInput((
|
|
103
|
-
if (
|
|
107
|
+
useInput((_, key) => {
|
|
108
|
+
if (!isActive || step >= steps.length)
|
|
109
|
+
return;
|
|
110
|
+
const currentStepConfig = steps[step];
|
|
111
|
+
if (key.escape) {
|
|
104
112
|
// Save current value before aborting
|
|
105
|
-
const currentStepConfig = steps[step];
|
|
106
113
|
if (currentStepConfig) {
|
|
107
114
|
const configKey = currentStepConfig.path || currentStepConfig.key;
|
|
108
115
|
let currentValue = '';
|
|
@@ -111,10 +118,7 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
111
118
|
currentValue = inputValue || values[configKey] || '';
|
|
112
119
|
break;
|
|
113
120
|
case StepType.Selection:
|
|
114
|
-
currentValue =
|
|
115
|
-
currentStepConfig.options[selectedIndex]?.value ||
|
|
116
|
-
values[configKey] ||
|
|
117
|
-
'';
|
|
121
|
+
currentValue = values[configKey] || '';
|
|
118
122
|
break;
|
|
119
123
|
default: {
|
|
120
124
|
const exhaustiveCheck = currentStepConfig;
|
|
@@ -130,24 +134,13 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
130
134
|
}
|
|
131
135
|
return;
|
|
132
136
|
}
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
else if (key.return) {
|
|
141
|
-
handleSubmit(currentStep.options[selectedIndex].value);
|
|
142
|
-
}
|
|
143
|
-
break;
|
|
144
|
-
case StepType.Text:
|
|
145
|
-
// Text input handled by TextInput component
|
|
146
|
-
break;
|
|
147
|
-
default: {
|
|
148
|
-
const exhaustiveCheck = currentStep;
|
|
149
|
-
throw new Error(`Unsupported step type: ${exhaustiveCheck}`);
|
|
150
|
-
}
|
|
137
|
+
// Handle selection step navigation
|
|
138
|
+
if (currentStepConfig.type === StepType.Selection) {
|
|
139
|
+
if (key.tab) {
|
|
140
|
+
setSelectedIndex((prev) => (prev + 1) % currentStepConfig.options.length);
|
|
141
|
+
}
|
|
142
|
+
else if (key.return) {
|
|
143
|
+
handleSubmit(currentStepConfig.options[selectedIndex].value);
|
|
151
144
|
}
|
|
152
145
|
}
|
|
153
146
|
});
|
|
@@ -188,35 +181,38 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
188
181
|
setInputValue('');
|
|
189
182
|
if (step === steps.length - 1) {
|
|
190
183
|
// Last step completed
|
|
184
|
+
// IMPORTANT: Update state BEFORE calling onFinished
|
|
185
|
+
// onFinished may call handlers.completeActive(), so state must be saved first
|
|
186
|
+
const stateUpdate = {
|
|
187
|
+
values: newValues,
|
|
188
|
+
completedStep: steps.length,
|
|
189
|
+
};
|
|
190
|
+
handlers?.updateState(stateUpdate);
|
|
191
|
+
// Now call onFinished - this may trigger completeActive()
|
|
191
192
|
if (onFinished) {
|
|
192
193
|
onFinished(newValues);
|
|
193
194
|
}
|
|
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();
|
|
201
195
|
setStep(steps.length);
|
|
202
196
|
}
|
|
203
197
|
else {
|
|
204
198
|
// Save state after each step
|
|
205
|
-
|
|
199
|
+
const stateUpdate = {
|
|
206
200
|
values: newValues,
|
|
207
201
|
completedStep: step + 1,
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
};
|
|
203
|
+
handlers?.updateState(stateUpdate);
|
|
204
|
+
const nextStep = step + 1;
|
|
205
|
+
setStep(nextStep);
|
|
206
|
+
// Reset selectedIndex for next step
|
|
207
|
+
if (nextStep < steps.length &&
|
|
208
|
+
steps[nextStep].type === StepType.Selection) {
|
|
209
|
+
setSelectedIndex(steps[nextStep].defaultIndex);
|
|
214
210
|
}
|
|
215
211
|
}
|
|
216
212
|
};
|
|
217
213
|
const renderStepInput = (stepConfig, isCurrentStep) => {
|
|
218
214
|
const configKey = stepConfig.path || stepConfig.key;
|
|
219
|
-
// Use state values
|
|
215
|
+
// Use state values when inactive, local values when active
|
|
220
216
|
const displayValue = !isActive && state?.values ? state.values[configKey] : values[configKey];
|
|
221
217
|
switch (stepConfig.type) {
|
|
222
218
|
case StepType.Text:
|
|
@@ -226,10 +222,11 @@ export function Config({ steps, state, isActive = true, debug, handlers, onFinis
|
|
|
226
222
|
return (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: displayValue || '' }));
|
|
227
223
|
case StepType.Selection: {
|
|
228
224
|
if (!isCurrentStep) {
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
// Find the option that matches the saved/current value
|
|
226
|
+
const option = stepConfig.options.find((opt) => opt.value === displayValue);
|
|
227
|
+
return _jsx(Text, { dimColor: true, children: option?.label || '' });
|
|
231
228
|
}
|
|
232
|
-
return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep:
|
|
229
|
+
return (_jsx(SelectionStep, { options: stepConfig.options, selectedIndex: selectedIndex, isCurrentStep: true }));
|
|
233
230
|
}
|
|
234
231
|
default: {
|
|
235
232
|
const exhaustiveCheck = stepConfig;
|
package/dist/ui/Execute.js
CHANGED
|
@@ -157,7 +157,7 @@ export function Execute({ tasks, state, isActive = true, service, handlers, }) {
|
|
|
157
157
|
commandStatuses,
|
|
158
158
|
error,
|
|
159
159
|
});
|
|
160
|
-
handlers?.
|
|
160
|
+
handlers?.completeActive();
|
|
161
161
|
}, [isExecuting, commandStatuses, outputs, handlers, message, error]);
|
|
162
162
|
useEffect(() => {
|
|
163
163
|
if (!isActive) {
|
|
@@ -197,7 +197,7 @@ export function Execute({ tasks, state, isActive = true, service, handlers, }) {
|
|
|
197
197
|
message: result.message,
|
|
198
198
|
commandStatuses: [],
|
|
199
199
|
});
|
|
200
|
-
handlers?.
|
|
200
|
+
handlers?.completeActive();
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
203
203
|
// Resolve placeholders in command strings before execution
|
package/dist/ui/Introspect.js
CHANGED
|
@@ -86,7 +86,7 @@ export function Introspect({ tasks, state, isActive = true, service, children, d
|
|
|
86
86
|
// Add Report component to queue
|
|
87
87
|
handlers?.addToQueue(createReportDefinition(result.message, capabilities));
|
|
88
88
|
// Signal completion
|
|
89
|
-
handlers?.
|
|
89
|
+
handlers?.completeActive();
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
catch (err) {
|
package/dist/ui/Plan.js
CHANGED
|
@@ -72,7 +72,7 @@ export function Plan({ message, tasks, state, isActive = true, debug = false, ha
|
|
|
72
72
|
const concreteTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
|
|
73
73
|
onSelectionConfirmed(concreteTasks);
|
|
74
74
|
// Signal Plan completion after adding Confirm to queue
|
|
75
|
-
handlers?.
|
|
75
|
+
handlers?.completeActive();
|
|
76
76
|
}
|
|
77
77
|
}, [
|
|
78
78
|
isActive,
|
|
@@ -145,12 +145,12 @@ export function Plan({ message, tasks, state, isActive = true, debug = false, ha
|
|
|
145
145
|
if (onSelectionConfirmed) {
|
|
146
146
|
// Callback will handle the entire flow (Refinement, refined Plan, Confirm)
|
|
147
147
|
// So we need to complete the Plan first
|
|
148
|
-
handlers?.
|
|
148
|
+
handlers?.completeActive();
|
|
149
149
|
onSelectionConfirmed(refinedTasks);
|
|
150
150
|
}
|
|
151
151
|
else {
|
|
152
152
|
// No selection callback, just complete normally
|
|
153
|
-
handlers?.
|
|
153
|
+
handlers?.completeActive();
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
}
|
package/dist/ui/Workflow.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { Box, Static } from 'ink';
|
|
4
4
|
import { ComponentName, FeedbackType } from '../types/types.js';
|
|
5
5
|
import { createFeedback, isStateless, markAsDone, } from '../services/components.js';
|
|
@@ -10,21 +10,31 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
10
10
|
const [timeline, setTimeline] = useState([]);
|
|
11
11
|
const [active, setActive] = useState(null);
|
|
12
12
|
const [queue, setQueue] = useState(initialQueue);
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
// Ref to track active component for synchronous access
|
|
14
|
+
const activeRef = useRef(null);
|
|
15
|
+
// Keep ref in sync with active state
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
activeRef.current = active;
|
|
18
|
+
}, [active]);
|
|
19
|
+
// Function to move active component to timeline with optional additional items
|
|
20
|
+
const moveActiveToTimeline = useCallback((...items) => {
|
|
21
|
+
const curr = activeRef.current;
|
|
22
|
+
if (!curr) {
|
|
23
|
+
// No active component, just add items if provided
|
|
24
|
+
if (items.length > 0) {
|
|
25
|
+
setTimeline((prev) => [...prev, ...items]);
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const doneComponent = markAsDone(curr);
|
|
30
|
+
// Atomic update: add active component and any additional items
|
|
31
|
+
setTimeline((prev) => items.length > 0
|
|
32
|
+
? [...prev, doneComponent, ...items]
|
|
33
|
+
: [...prev, doneComponent]);
|
|
34
|
+
setActive(null);
|
|
22
35
|
}, []);
|
|
23
36
|
// Global handlers for all stateful components
|
|
24
37
|
const handlers = useMemo(() => ({
|
|
25
|
-
onComplete: () => {
|
|
26
|
-
moveActiveToTimeline();
|
|
27
|
-
},
|
|
28
38
|
onAborted: (operation) => {
|
|
29
39
|
moveActiveToTimeline();
|
|
30
40
|
// Add feedback to queue and exit
|
|
@@ -48,21 +58,24 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
48
58
|
addToTimeline: (...items) => {
|
|
49
59
|
setTimeline((prev) => [...prev, ...items]);
|
|
50
60
|
},
|
|
51
|
-
completeActive: () => {
|
|
52
|
-
moveActiveToTimeline();
|
|
61
|
+
completeActive: (...items) => {
|
|
62
|
+
moveActiveToTimeline(...items);
|
|
53
63
|
},
|
|
54
64
|
updateState: (newState) => {
|
|
55
65
|
setActive((curr) => {
|
|
56
66
|
if (!curr || !('state' in curr))
|
|
57
67
|
return curr;
|
|
58
68
|
const stateful = curr;
|
|
59
|
-
|
|
69
|
+
const updated = {
|
|
60
70
|
...stateful,
|
|
61
71
|
state: {
|
|
62
72
|
...stateful.state,
|
|
63
73
|
...newState,
|
|
64
74
|
},
|
|
65
75
|
};
|
|
76
|
+
// Update ref synchronously so moveActiveToTimeline sees the latest state
|
|
77
|
+
activeRef.current = updated;
|
|
78
|
+
return updated;
|
|
66
79
|
});
|
|
67
80
|
},
|
|
68
81
|
}), [moveActiveToTimeline]);
|