prompt-language-shell 0.5.2 → 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/ANSWER.md +4 -0
- package/dist/config/CONFIG.md +4 -2
- package/dist/config/PLAN.md +23 -0
- package/dist/config/VALIDATE.md +12 -11
- package/dist/services/anthropic.js +49 -4
- package/dist/services/components.js +56 -66
- package/dist/services/configuration.js +169 -13
- package/dist/services/messages.js +22 -0
- package/dist/services/queue.js +2 -2
- package/dist/services/refinement.js +37 -0
- package/dist/services/task-router.js +141 -0
- package/dist/types/types.js +0 -1
- package/dist/ui/Answer.js +18 -27
- package/dist/ui/Command.js +44 -27
- package/dist/ui/Component.js +23 -50
- package/dist/ui/Config.js +77 -55
- 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 +132 -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
|
@@ -123,6 +123,7 @@ export function saveConfig(section, config) {
|
|
|
123
123
|
}
|
|
124
124
|
export function saveAnthropicConfig(config) {
|
|
125
125
|
saveConfig('anthropic', config);
|
|
126
|
+
return loadConfig();
|
|
126
127
|
}
|
|
127
128
|
export function saveDebugSetting(debug) {
|
|
128
129
|
saveConfig('settings', { debug });
|
|
@@ -201,21 +202,75 @@ export function getConfigSchema() {
|
|
|
201
202
|
};
|
|
202
203
|
}
|
|
203
204
|
/**
|
|
204
|
-
* Get
|
|
205
|
-
* Returns keys
|
|
205
|
+
* Get missing required configuration keys
|
|
206
|
+
* Returns array of keys that are required but not present or invalid in config
|
|
206
207
|
*/
|
|
207
|
-
export function
|
|
208
|
+
export function getMissingConfigKeys() {
|
|
208
209
|
const schema = getConfigSchema();
|
|
209
|
-
const
|
|
210
|
-
|
|
210
|
+
const missing = [];
|
|
211
|
+
let currentConfig = null;
|
|
212
|
+
try {
|
|
213
|
+
currentConfig = loadConfig();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Config doesn't exist
|
|
217
|
+
}
|
|
211
218
|
for (const [key, definition] of Object.entries(schema)) {
|
|
212
|
-
|
|
219
|
+
if (!definition.required) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
// Get current value for this key
|
|
223
|
+
const parts = key.split('.');
|
|
224
|
+
let value = currentConfig;
|
|
225
|
+
for (const part of parts) {
|
|
226
|
+
if (value && typeof value === 'object' && part in value) {
|
|
227
|
+
value = value[part];
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
value = undefined;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Check if value is missing or invalid
|
|
235
|
+
if (value === undefined || value === null) {
|
|
236
|
+
missing.push(key);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
// Validate based on type
|
|
240
|
+
let isValid = false;
|
|
241
|
+
switch (definition.type) {
|
|
242
|
+
case 'regexp':
|
|
243
|
+
isValid = typeof value === 'string' && definition.pattern.test(value);
|
|
244
|
+
break;
|
|
245
|
+
case 'string':
|
|
246
|
+
isValid = typeof value === 'string';
|
|
247
|
+
break;
|
|
248
|
+
case 'enum':
|
|
249
|
+
isValid =
|
|
250
|
+
typeof value === 'string' && definition.values.includes(value);
|
|
251
|
+
break;
|
|
252
|
+
case 'number':
|
|
253
|
+
isValid = typeof value === 'number';
|
|
254
|
+
break;
|
|
255
|
+
case 'boolean':
|
|
256
|
+
isValid = typeof value === 'boolean';
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
if (!isValid) {
|
|
260
|
+
missing.push(key);
|
|
261
|
+
}
|
|
213
262
|
}
|
|
214
|
-
|
|
263
|
+
return missing;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get list of configured keys from config file
|
|
267
|
+
* Returns array of dot-notation keys that exist in the config file
|
|
268
|
+
*/
|
|
269
|
+
export function getConfiguredKeys() {
|
|
215
270
|
try {
|
|
216
271
|
const configFile = getConfigFile();
|
|
217
272
|
if (!existsSync(configFile)) {
|
|
218
|
-
return
|
|
273
|
+
return [];
|
|
219
274
|
}
|
|
220
275
|
const content = readFileSync(configFile, 'utf-8');
|
|
221
276
|
const parsed = YAML.parse(content);
|
|
@@ -234,15 +289,116 @@ export function getAvailableConfigStructure() {
|
|
|
234
289
|
return result;
|
|
235
290
|
}
|
|
236
291
|
const flatConfig = flattenConfig(parsed);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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;
|
|
241
326
|
}
|
|
327
|
+
flatConfig = flattenConfig(parsed);
|
|
242
328
|
}
|
|
243
329
|
}
|
|
244
330
|
catch {
|
|
245
|
-
// 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
|
+
}
|
|
246
349
|
}
|
|
247
350
|
return structure;
|
|
248
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
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Unflatten dotted keys into nested structure
|
|
382
|
+
* Example: { "product.alpha.path": "value" } -> { product: { alpha: { path: "value" } } }
|
|
383
|
+
* Converts string values to appropriate types based on config schema
|
|
384
|
+
*/
|
|
385
|
+
export function unflattenConfig(dotted) {
|
|
386
|
+
const result = {};
|
|
387
|
+
const schema = getConfigSchema();
|
|
388
|
+
for (const [dottedKey, stringValue] of Object.entries(dotted)) {
|
|
389
|
+
const parts = dottedKey.split('.');
|
|
390
|
+
const section = parts[0];
|
|
391
|
+
// Initialize section if needed
|
|
392
|
+
result[section] = result[section] ?? {};
|
|
393
|
+
// Build nested structure for this section
|
|
394
|
+
let current = result[section];
|
|
395
|
+
for (let i = 1; i < parts.length - 1; i++) {
|
|
396
|
+
current[parts[i]] = current[parts[i]] ?? {};
|
|
397
|
+
current = current[parts[i]];
|
|
398
|
+
}
|
|
399
|
+
// Convert string value to appropriate type and set
|
|
400
|
+
const typedValue = parseConfigValue(dottedKey, stringValue, schema);
|
|
401
|
+
current[parts[parts.length - 1]] = typedValue;
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
@@ -43,6 +43,28 @@ export function getCancellationMessage(operation) {
|
|
|
43
43
|
];
|
|
44
44
|
return templates[Math.floor(Math.random() * templates.length)];
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns an error message when the request cannot be understood.
|
|
48
|
+
* Randomly selects from variations to sound natural.
|
|
49
|
+
*/
|
|
50
|
+
export function getUnknownRequestMessage() {
|
|
51
|
+
const messages = [
|
|
52
|
+
'I do not understand the request.',
|
|
53
|
+
'I cannot understand what you want me to do.',
|
|
54
|
+
"I'm not sure what you're asking for.",
|
|
55
|
+
'I cannot determine what action to take.',
|
|
56
|
+
'This request is unclear to me.',
|
|
57
|
+
'I do not recognize this command.',
|
|
58
|
+
];
|
|
59
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns an error message for mixed task types.
|
|
63
|
+
*/
|
|
64
|
+
export function getMixedTaskTypesError(types) {
|
|
65
|
+
const typeList = types.join(', ');
|
|
66
|
+
return `Mixed task types are not supported. Found: ${typeList}. All tasks in a plan must have the same type.`;
|
|
67
|
+
}
|
|
46
68
|
/**
|
|
47
69
|
* Feedback messages for various operations
|
|
48
70
|
*/
|
package/dist/services/queue.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FeedbackType } from '../types/types.js';
|
|
2
|
-
import { createFeedback
|
|
2
|
+
import { createFeedback } from './components.js';
|
|
3
3
|
import { FeedbackMessages } from './messages.js';
|
|
4
4
|
import { exitApp } from './process.js';
|
|
5
5
|
/**
|
|
@@ -37,7 +37,7 @@ export function withQueueHandler(componentName, callback, shouldExit = false, ex
|
|
|
37
37
|
*/
|
|
38
38
|
export function createErrorHandler(componentName, addToTimeline) {
|
|
39
39
|
return (error) => withQueueHandler(componentName, (first) => {
|
|
40
|
-
addToTimeline(
|
|
40
|
+
addToTimeline(first, createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, error));
|
|
41
41
|
return undefined;
|
|
42
42
|
}, true, 1);
|
|
43
43
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createRefinement } from './components.js';
|
|
2
|
+
import { formatErrorMessage, getRefiningMessage } from './messages.js';
|
|
3
|
+
import { routeTasksWithConfirm } from './task-router.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handle refinement flow for DEFINE tasks
|
|
6
|
+
* Called when user selects options from a plan with DEFINE tasks
|
|
7
|
+
*/
|
|
8
|
+
export async function handleRefinement(selectedTasks, service, originalCommand, handlers) {
|
|
9
|
+
// Create and add refinement component to queue
|
|
10
|
+
const refinementDef = createRefinement(getRefiningMessage(), (operation) => {
|
|
11
|
+
handlers.onAborted(operation);
|
|
12
|
+
});
|
|
13
|
+
handlers.addToQueue(refinementDef);
|
|
14
|
+
try {
|
|
15
|
+
// Build refined command from selected tasks
|
|
16
|
+
const refinedCommand = selectedTasks
|
|
17
|
+
.map((task) => {
|
|
18
|
+
const action = task.action.toLowerCase().replace(/,/g, ' -');
|
|
19
|
+
const type = task.type;
|
|
20
|
+
return `${action} (type: ${type})`;
|
|
21
|
+
})
|
|
22
|
+
.join(', ');
|
|
23
|
+
// Call LLM to refine plan with selected tasks
|
|
24
|
+
const refinedResult = await service.processWithTool(refinedCommand, 'plan');
|
|
25
|
+
// Complete the Refinement component
|
|
26
|
+
handlers.completeActive();
|
|
27
|
+
// Route refined tasks to appropriate components
|
|
28
|
+
routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, handlers, false, // No DEFINE tasks in refined result
|
|
29
|
+
undefined // No commandComponent - use normal flow
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
handlers.completeActive();
|
|
34
|
+
const errorMessage = formatErrorMessage(err);
|
|
35
|
+
handlers.onError(errorMessage);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
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, commandComponent) {
|
|
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
|
+
// Use atomic update if commandComponent provided, else normal flow
|
|
53
|
+
if (commandComponent) {
|
|
54
|
+
handlers.completeActive(planDefinition);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
handlers.addToTimeline(planDefinition);
|
|
58
|
+
}
|
|
59
|
+
handlers.addToQueue(confirmDefinition);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validate that all tasks have the same type
|
|
64
|
+
* Per FLOWS.md: "Mixed types → Error (not supported)"
|
|
65
|
+
*/
|
|
66
|
+
function validateTaskTypes(tasks) {
|
|
67
|
+
if (tasks.length === 0)
|
|
68
|
+
return;
|
|
69
|
+
const types = new Set(tasks.map((task) => task.type));
|
|
70
|
+
if (types.size > 1) {
|
|
71
|
+
throw new Error(getMixedTaskTypesError(Array.from(types)));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Execute tasks after confirmation (internal helper)
|
|
76
|
+
* Validates task types after user has seen and confirmed the plan
|
|
77
|
+
*/
|
|
78
|
+
function executeTasksAfterConfirm(tasks, service, userRequest, handlers) {
|
|
79
|
+
// Validate all tasks have the same type after user confirmation
|
|
80
|
+
// Per FLOWS.md: "Confirm component completes → Execution handler analyzes task types"
|
|
81
|
+
try {
|
|
82
|
+
validateTaskTypes(tasks);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
handlers.onError(error instanceof Error ? error.message : String(error));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
89
|
+
const allAnswer = tasks.every((task) => task.type === TaskType.Answer);
|
|
90
|
+
const allConfig = tasks.every((task) => task.type === TaskType.Config);
|
|
91
|
+
if (allAnswer) {
|
|
92
|
+
const question = tasks[0].action;
|
|
93
|
+
handlers.addToQueue(createAnswerDefinition(question, service));
|
|
94
|
+
}
|
|
95
|
+
else if (allIntrospect) {
|
|
96
|
+
handlers.addToQueue(createIntrospectDefinition(tasks, service));
|
|
97
|
+
}
|
|
98
|
+
else if (allConfig) {
|
|
99
|
+
// Route to Config flow - extract keys from task params
|
|
100
|
+
const configKeys = tasks
|
|
101
|
+
.map((task) => task.params?.key)
|
|
102
|
+
.filter((key) => key !== undefined);
|
|
103
|
+
handlers.addToQueue(createConfigDefinitionWithKeys(configKeys, (config) => {
|
|
104
|
+
// Save config using the same pattern as Validate component
|
|
105
|
+
try {
|
|
106
|
+
// Convert flat dotted keys to nested structure grouped by section
|
|
107
|
+
const configBySection = unflattenConfig(config);
|
|
108
|
+
// Save each section
|
|
109
|
+
for (const [section, sectionConfig] of Object.entries(configBySection)) {
|
|
110
|
+
saveConfig(section, sectionConfig);
|
|
111
|
+
}
|
|
112
|
+
handlers.completeActive();
|
|
113
|
+
handlers.addToQueue(createFeedback(FeedbackType.Succeeded, 'Configuration updated successfully.'));
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
const errorMessage = error instanceof Error
|
|
117
|
+
? error.message
|
|
118
|
+
: 'Failed to save configuration';
|
|
119
|
+
handlers.onError(errorMessage);
|
|
120
|
+
}
|
|
121
|
+
}, (operation) => {
|
|
122
|
+
handlers.onAborted(operation);
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Execute tasks with validation
|
|
127
|
+
const missingConfig = validateExecuteTasks(tasks);
|
|
128
|
+
if (missingConfig.length > 0) {
|
|
129
|
+
handlers.addToQueue(createValidateDefinition(missingConfig, userRequest, service, (error) => {
|
|
130
|
+
handlers.onError(error);
|
|
131
|
+
}, () => {
|
|
132
|
+
handlers.addToQueue(createExecuteDefinition(tasks, service));
|
|
133
|
+
}, (operation) => {
|
|
134
|
+
handlers.onAborted(operation);
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
handlers.addToQueue(createExecuteDefinition(tasks, service));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
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?.completeActive();
|
|
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,38 @@ 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
|
+
// DEFINE tasks: Move Command to timeline, add Plan to queue
|
|
65
|
+
handlers?.completeActive();
|
|
66
|
+
handlers?.addToQueue(planDefinition);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
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
|
+
);
|
|
72
|
+
}
|
|
50
73
|
}
|
|
51
74
|
}
|
|
52
75
|
catch (err) {
|
|
53
76
|
await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
|
|
54
77
|
if (mounted) {
|
|
55
78
|
const errorMessage = formatErrorMessage(err);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
onError(errorMessage);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
setError(errorMessage);
|
|
62
|
-
}
|
|
79
|
+
setError(errorMessage);
|
|
80
|
+
handlers?.onError(errorMessage);
|
|
63
81
|
}
|
|
64
82
|
}
|
|
65
83
|
}
|
|
@@ -67,7 +85,6 @@ export function Command({ command, state, service, children, onError, onComplete
|
|
|
67
85
|
return () => {
|
|
68
86
|
mounted = false;
|
|
69
87
|
};
|
|
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] }));
|
|
88
|
+
}, [command, isActive, service, handlers]);
|
|
89
|
+
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
90
|
}
|