prompt-language-shell 0.6.0 → 0.6.4
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 +27 -17
- package/dist/services/configuration.js +107 -27
- package/dist/services/task-router.js +20 -13
- package/dist/types/components.js +8 -1
- package/dist/ui/Answer.js +4 -2
- package/dist/ui/Command.js +7 -6
- package/dist/ui/Component.js +14 -17
- package/dist/ui/Config.js +45 -47
- package/dist/ui/Confirm.js +6 -7
- package/dist/ui/Execute.js +5 -3
- package/dist/ui/Introspect.js +4 -2
- package/dist/ui/Plan.js +10 -7
- package/dist/ui/Refinement.js +3 -1
- package/dist/ui/Validate.js +4 -3
- package/dist/ui/Workflow.js +129 -56
- package/package.json +1 -1
- package/dist/services/queue.js +0 -52
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
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { ComponentName } from '../types/types.js';
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
4
5
|
import { parse as parseYaml } from 'yaml';
|
|
5
|
-
import { getConfigPath, getConfigSchema, loadConfig, } from './configuration.js';
|
|
6
|
+
import { ConfigDefinitionType, getConfigPath, getConfigSchema, loadConfig, } from './configuration.js';
|
|
6
7
|
import { getConfirmationMessage } from './messages.js';
|
|
7
8
|
import { StepType } from '../ui/Config.js';
|
|
8
9
|
export function createWelcomeDefinition(app) {
|
|
@@ -39,15 +40,15 @@ function getConfigValue(config, key) {
|
|
|
39
40
|
*/
|
|
40
41
|
function getValidator(definition) {
|
|
41
42
|
switch (definition.type) {
|
|
42
|
-
case
|
|
43
|
+
case ConfigDefinitionType.RegExp:
|
|
43
44
|
return (value) => definition.pattern.test(value);
|
|
44
|
-
case
|
|
45
|
+
case ConfigDefinitionType.String:
|
|
45
46
|
return () => true; // Strings are always valid
|
|
46
|
-
case
|
|
47
|
+
case ConfigDefinitionType.Enum:
|
|
47
48
|
return (value) => definition.values.includes(value);
|
|
48
|
-
case
|
|
49
|
+
case ConfigDefinitionType.Number:
|
|
49
50
|
return (value) => !isNaN(Number(value));
|
|
50
|
-
case
|
|
51
|
+
case ConfigDefinitionType.Boolean:
|
|
51
52
|
return (value) => value === 'true' || value === 'false';
|
|
52
53
|
}
|
|
53
54
|
}
|
|
@@ -103,11 +104,11 @@ export function createConfigStepsFromSchema(keys) {
|
|
|
103
104
|
const shortKey = keyParts[keyParts.length - 1];
|
|
104
105
|
// Map definition to ConfigStep based on type
|
|
105
106
|
switch (definition.type) {
|
|
106
|
-
case
|
|
107
|
-
case
|
|
107
|
+
case ConfigDefinitionType.RegExp:
|
|
108
|
+
case ConfigDefinitionType.String: {
|
|
108
109
|
const value = currentValue !== undefined && typeof currentValue === 'string'
|
|
109
110
|
? currentValue
|
|
110
|
-
: definition.type ===
|
|
111
|
+
: definition.type === ConfigDefinitionType.String
|
|
111
112
|
? (definition.default ?? '')
|
|
112
113
|
: null;
|
|
113
114
|
return {
|
|
@@ -119,7 +120,7 @@ export function createConfigStepsFromSchema(keys) {
|
|
|
119
120
|
validate: getValidator(definition),
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
|
-
case
|
|
123
|
+
case ConfigDefinitionType.Number: {
|
|
123
124
|
const value = currentValue !== undefined && typeof currentValue === 'number'
|
|
124
125
|
? String(currentValue)
|
|
125
126
|
: definition.default !== undefined
|
|
@@ -134,7 +135,7 @@ export function createConfigStepsFromSchema(keys) {
|
|
|
134
135
|
validate: getValidator(definition),
|
|
135
136
|
};
|
|
136
137
|
}
|
|
137
|
-
case
|
|
138
|
+
case ConfigDefinitionType.Enum: {
|
|
138
139
|
const currentStr = currentValue !== undefined && typeof currentValue === 'string'
|
|
139
140
|
? currentValue
|
|
140
141
|
: definition.default;
|
|
@@ -154,7 +155,7 @@ export function createConfigStepsFromSchema(keys) {
|
|
|
154
155
|
validate: getValidator(definition),
|
|
155
156
|
};
|
|
156
157
|
}
|
|
157
|
-
case
|
|
158
|
+
case ConfigDefinitionType.Boolean: {
|
|
158
159
|
const currentBool = currentValue !== undefined && typeof currentValue === 'boolean'
|
|
159
160
|
? currentValue
|
|
160
161
|
: undefined;
|
|
@@ -164,8 +165,8 @@ export function createConfigStepsFromSchema(keys) {
|
|
|
164
165
|
path: key,
|
|
165
166
|
type: StepType.Selection,
|
|
166
167
|
options: [
|
|
167
|
-
{ label: '
|
|
168
|
-
{ label: '
|
|
168
|
+
{ label: 'yes', value: 'true' },
|
|
169
|
+
{ label: 'no', value: 'false' },
|
|
169
170
|
],
|
|
170
171
|
defaultIndex: currentBool !== undefined ? (currentBool ? 0 : 1) : 0,
|
|
171
172
|
validate: getValidator(definition),
|
|
@@ -178,6 +179,7 @@ export function createConfigDefinition(onFinished, onAborted) {
|
|
|
178
179
|
return {
|
|
179
180
|
id: randomUUID(),
|
|
180
181
|
name: ComponentName.Config,
|
|
182
|
+
status: ComponentStatus.Awaiting,
|
|
181
183
|
state: {},
|
|
182
184
|
props: {
|
|
183
185
|
steps: createConfigSteps(),
|
|
@@ -193,6 +195,7 @@ export function createConfigDefinitionWithKeys(keys, onFinished, onAborted) {
|
|
|
193
195
|
return {
|
|
194
196
|
id: randomUUID(),
|
|
195
197
|
name: ComponentName.Config,
|
|
198
|
+
status: ComponentStatus.Awaiting,
|
|
196
199
|
state: {},
|
|
197
200
|
props: {
|
|
198
201
|
steps: createConfigStepsFromSchema(keys),
|
|
@@ -205,6 +208,7 @@ export function createCommandDefinition(command, service) {
|
|
|
205
208
|
return {
|
|
206
209
|
id: randomUUID(),
|
|
207
210
|
name: ComponentName.Command,
|
|
211
|
+
status: ComponentStatus.Awaiting,
|
|
208
212
|
state: {},
|
|
209
213
|
props: {
|
|
210
214
|
command,
|
|
@@ -216,6 +220,7 @@ export function createPlanDefinition(message, tasks, onSelectionConfirmed) {
|
|
|
216
220
|
return {
|
|
217
221
|
id: randomUUID(),
|
|
218
222
|
name: ComponentName.Plan,
|
|
223
|
+
status: ComponentStatus.Awaiting,
|
|
219
224
|
state: {
|
|
220
225
|
highlightedIndex: null,
|
|
221
226
|
currentDefineGroupIndex: 0,
|
|
@@ -251,6 +256,7 @@ export function createRefinement(text, onAborted) {
|
|
|
251
256
|
return {
|
|
252
257
|
id: randomUUID(),
|
|
253
258
|
name: ComponentName.Refinement,
|
|
259
|
+
status: ComponentStatus.Awaiting,
|
|
254
260
|
state: {},
|
|
255
261
|
props: {
|
|
256
262
|
text,
|
|
@@ -262,6 +268,7 @@ export function createConfirmDefinition(onConfirmed, onCancelled) {
|
|
|
262
268
|
return {
|
|
263
269
|
id: randomUUID(),
|
|
264
270
|
name: ComponentName.Confirm,
|
|
271
|
+
status: ComponentStatus.Awaiting,
|
|
265
272
|
state: {},
|
|
266
273
|
props: {
|
|
267
274
|
message: getConfirmationMessage(),
|
|
@@ -274,6 +281,7 @@ export function createIntrospectDefinition(tasks, service) {
|
|
|
274
281
|
return {
|
|
275
282
|
id: randomUUID(),
|
|
276
283
|
name: ComponentName.Introspect,
|
|
284
|
+
status: ComponentStatus.Awaiting,
|
|
277
285
|
state: {},
|
|
278
286
|
props: {
|
|
279
287
|
tasks,
|
|
@@ -295,6 +303,7 @@ export function createAnswerDefinition(question, service) {
|
|
|
295
303
|
return {
|
|
296
304
|
id: randomUUID(),
|
|
297
305
|
name: ComponentName.Answer,
|
|
306
|
+
status: ComponentStatus.Awaiting,
|
|
298
307
|
state: {},
|
|
299
308
|
props: {
|
|
300
309
|
question,
|
|
@@ -308,16 +317,16 @@ export function isStateless(component) {
|
|
|
308
317
|
/**
|
|
309
318
|
* Mark a component as done. Returns the component to be added to timeline.
|
|
310
319
|
* Components use handlers.updateState to save their state before completion,
|
|
311
|
-
* so this function
|
|
320
|
+
* so this function sets the status to Done and returns the updated component.
|
|
312
321
|
*/
|
|
313
322
|
export function markAsDone(component) {
|
|
314
|
-
|
|
315
|
-
return component;
|
|
323
|
+
return { ...component, status: ComponentStatus.Done };
|
|
316
324
|
}
|
|
317
325
|
export function createExecuteDefinition(tasks, service) {
|
|
318
326
|
return {
|
|
319
327
|
id: randomUUID(),
|
|
320
328
|
name: ComponentName.Execute,
|
|
329
|
+
status: ComponentStatus.Awaiting,
|
|
321
330
|
state: {},
|
|
322
331
|
props: {
|
|
323
332
|
tasks,
|
|
@@ -329,6 +338,7 @@ export function createValidateDefinition(missingConfig, userRequest, service, on
|
|
|
329
338
|
return {
|
|
330
339
|
id: randomUUID(),
|
|
331
340
|
name: ComponentName.Validate,
|
|
341
|
+
status: ComponentStatus.Awaiting,
|
|
332
342
|
state: {},
|
|
333
343
|
props: {
|
|
334
344
|
missingConfig,
|
|
@@ -9,6 +9,14 @@ export var AnthropicModel;
|
|
|
9
9
|
AnthropicModel["Opus"] = "claude-opus-4-1";
|
|
10
10
|
})(AnthropicModel || (AnthropicModel = {}));
|
|
11
11
|
export const SUPPORTED_MODELS = Object.values(AnthropicModel);
|
|
12
|
+
export var ConfigDefinitionType;
|
|
13
|
+
(function (ConfigDefinitionType) {
|
|
14
|
+
ConfigDefinitionType["RegExp"] = "regexp";
|
|
15
|
+
ConfigDefinitionType["String"] = "string";
|
|
16
|
+
ConfigDefinitionType["Enum"] = "enum";
|
|
17
|
+
ConfigDefinitionType["Number"] = "number";
|
|
18
|
+
ConfigDefinitionType["Boolean"] = "boolean";
|
|
19
|
+
})(ConfigDefinitionType || (ConfigDefinitionType = {}));
|
|
12
20
|
export class ConfigError extends Error {
|
|
13
21
|
origin;
|
|
14
22
|
constructor(message, origin) {
|
|
@@ -172,20 +180,20 @@ export function getConfigurationRequiredMessage(forFutureUse = false) {
|
|
|
172
180
|
*/
|
|
173
181
|
const coreConfigSchema = {
|
|
174
182
|
'anthropic.key': {
|
|
175
|
-
type:
|
|
183
|
+
type: ConfigDefinitionType.RegExp,
|
|
176
184
|
required: true,
|
|
177
185
|
pattern: /^sk-ant-api03-[A-Za-z0-9_-]{95}$/,
|
|
178
186
|
description: 'Anthropic API key',
|
|
179
187
|
},
|
|
180
188
|
'anthropic.model': {
|
|
181
|
-
type:
|
|
189
|
+
type: ConfigDefinitionType.Enum,
|
|
182
190
|
required: true,
|
|
183
191
|
values: SUPPORTED_MODELS,
|
|
184
192
|
default: AnthropicModel.Haiku,
|
|
185
193
|
description: 'Anthropic model',
|
|
186
194
|
},
|
|
187
195
|
'settings.debug': {
|
|
188
|
-
type:
|
|
196
|
+
type: ConfigDefinitionType.Boolean,
|
|
189
197
|
required: false,
|
|
190
198
|
description: 'Debug mode',
|
|
191
199
|
},
|
|
@@ -239,20 +247,20 @@ export function getMissingConfigKeys() {
|
|
|
239
247
|
// Validate based on type
|
|
240
248
|
let isValid = false;
|
|
241
249
|
switch (definition.type) {
|
|
242
|
-
case
|
|
250
|
+
case ConfigDefinitionType.RegExp:
|
|
243
251
|
isValid = typeof value === 'string' && definition.pattern.test(value);
|
|
244
252
|
break;
|
|
245
|
-
case
|
|
253
|
+
case ConfigDefinitionType.String:
|
|
246
254
|
isValid = typeof value === 'string';
|
|
247
255
|
break;
|
|
248
|
-
case
|
|
256
|
+
case ConfigDefinitionType.Enum:
|
|
249
257
|
isValid =
|
|
250
258
|
typeof value === 'string' && definition.values.includes(value);
|
|
251
259
|
break;
|
|
252
|
-
case
|
|
260
|
+
case ConfigDefinitionType.Number:
|
|
253
261
|
isValid = typeof value === 'number';
|
|
254
262
|
break;
|
|
255
|
-
case
|
|
263
|
+
case ConfigDefinitionType.Boolean:
|
|
256
264
|
isValid = typeof value === 'boolean';
|
|
257
265
|
break;
|
|
258
266
|
}
|
|
@@ -263,21 +271,14 @@ export function getMissingConfigKeys() {
|
|
|
263
271
|
return missing;
|
|
264
272
|
}
|
|
265
273
|
/**
|
|
266
|
-
* Get
|
|
267
|
-
* Returns keys
|
|
274
|
+
* Get list of configured keys from config file
|
|
275
|
+
* Returns array of dot-notation keys that exist in the config file
|
|
268
276
|
*/
|
|
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)
|
|
277
|
+
export function getConfiguredKeys() {
|
|
277
278
|
try {
|
|
278
279
|
const configFile = getConfigFile();
|
|
279
280
|
if (!existsSync(configFile)) {
|
|
280
|
-
return
|
|
281
|
+
return [];
|
|
281
282
|
}
|
|
282
283
|
const content = readFileSync(configFile, 'utf-8');
|
|
283
284
|
const parsed = YAML.parse(content);
|
|
@@ -296,25 +297,103 @@ export function getAvailableConfigStructure() {
|
|
|
296
297
|
return result;
|
|
297
298
|
}
|
|
298
299
|
const flatConfig = flattenConfig(parsed);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
300
|
+
return Object.keys(flatConfig);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get available config structure for CONFIG tool
|
|
308
|
+
* Returns keys with descriptions only (no values for privacy)
|
|
309
|
+
* Marks optional keys as "(optional)"
|
|
310
|
+
*/
|
|
311
|
+
export function getAvailableConfigStructure() {
|
|
312
|
+
const schema = getConfigSchema();
|
|
313
|
+
const structure = {};
|
|
314
|
+
// Try to load existing config to see which keys are already set
|
|
315
|
+
let flatConfig = {};
|
|
316
|
+
try {
|
|
317
|
+
const configFile = getConfigFile();
|
|
318
|
+
if (existsSync(configFile)) {
|
|
319
|
+
const content = readFileSync(configFile, 'utf-8');
|
|
320
|
+
const parsed = YAML.parse(content);
|
|
321
|
+
// Flatten nested config to dot notation
|
|
322
|
+
function flattenConfig(obj, prefix = '') {
|
|
323
|
+
const result = {};
|
|
324
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
325
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
326
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
327
|
+
Object.assign(result, flattenConfig(value, fullKey));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
result[fullKey] = value;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
303
334
|
}
|
|
335
|
+
flatConfig = flattenConfig(parsed);
|
|
304
336
|
}
|
|
305
337
|
}
|
|
306
338
|
catch {
|
|
307
|
-
// Config file doesn't exist or can't be read
|
|
339
|
+
// Config file doesn't exist or can't be read
|
|
340
|
+
}
|
|
341
|
+
// Add schema keys with descriptions
|
|
342
|
+
// Mark optional keys as (optional)
|
|
343
|
+
for (const [key, definition] of Object.entries(schema)) {
|
|
344
|
+
const isOptional = !definition.required;
|
|
345
|
+
if (isOptional) {
|
|
346
|
+
structure[key] = `${definition.description} (optional)`;
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
structure[key] = definition.description;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Add discovered keys that aren't in schema
|
|
353
|
+
for (const key of Object.keys(flatConfig)) {
|
|
354
|
+
if (!(key in structure)) {
|
|
355
|
+
structure[key] = `${key} (discovered)`;
|
|
356
|
+
}
|
|
308
357
|
}
|
|
309
358
|
return structure;
|
|
310
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Convert string value to appropriate type based on schema definition
|
|
362
|
+
*/
|
|
363
|
+
function parseConfigValue(key, stringValue, schema) {
|
|
364
|
+
// If we have a schema definition, use its type
|
|
365
|
+
if (key in schema) {
|
|
366
|
+
const definition = schema[key];
|
|
367
|
+
switch (definition.type) {
|
|
368
|
+
case ConfigDefinitionType.Boolean:
|
|
369
|
+
return stringValue === 'true';
|
|
370
|
+
case ConfigDefinitionType.Number:
|
|
371
|
+
return Number(stringValue);
|
|
372
|
+
case ConfigDefinitionType.String:
|
|
373
|
+
case ConfigDefinitionType.RegExp:
|
|
374
|
+
case ConfigDefinitionType.Enum:
|
|
375
|
+
return stringValue;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// No schema definition - try to infer type from string value
|
|
379
|
+
// This handles skill-defined configs that may not be in schema yet
|
|
380
|
+
if (stringValue === 'true' || stringValue === 'false') {
|
|
381
|
+
return stringValue === 'true';
|
|
382
|
+
}
|
|
383
|
+
if (!isNaN(Number(stringValue)) && stringValue.trim() !== '') {
|
|
384
|
+
return Number(stringValue);
|
|
385
|
+
}
|
|
386
|
+
return stringValue;
|
|
387
|
+
}
|
|
311
388
|
/**
|
|
312
389
|
* Unflatten dotted keys into nested structure
|
|
313
390
|
* Example: { "product.alpha.path": "value" } -> { product: { alpha: { path: "value" } } }
|
|
391
|
+
* Converts string values to appropriate types based on config schema
|
|
314
392
|
*/
|
|
315
393
|
export function unflattenConfig(dotted) {
|
|
316
394
|
const result = {};
|
|
317
|
-
|
|
395
|
+
const schema = getConfigSchema();
|
|
396
|
+
for (const [dottedKey, stringValue] of Object.entries(dotted)) {
|
|
318
397
|
const parts = dottedKey.split('.');
|
|
319
398
|
const section = parts[0];
|
|
320
399
|
// Initialize section if needed
|
|
@@ -325,8 +404,9 @@ export function unflattenConfig(dotted) {
|
|
|
325
404
|
current[parts[i]] = current[parts[i]] ?? {};
|
|
326
405
|
current = current[parts[i]];
|
|
327
406
|
}
|
|
328
|
-
//
|
|
329
|
-
|
|
407
|
+
// Convert string value to appropriate type and set
|
|
408
|
+
const typedValue = parseConfigValue(dottedKey, stringValue, schema);
|
|
409
|
+
current[parts[parts.length - 1]] = typedValue;
|
|
330
410
|
}
|
|
331
411
|
return result;
|
|
332
412
|
}
|
|
@@ -3,7 +3,7 @@ import { createAnswerDefinition, createConfigDefinitionWithKeys, createConfirmDe
|
|
|
3
3
|
import { saveConfig, unflattenConfig } from './configuration.js';
|
|
4
4
|
import { FeedbackType } from '../types/types.js';
|
|
5
5
|
import { validateExecuteTasks } from './execution-validator.js';
|
|
6
|
-
import { getMixedTaskTypesError, getUnknownRequestMessage, } from './messages.js';
|
|
6
|
+
import { getCancellationMessage, getMixedTaskTypesError, getUnknownRequestMessage, } from './messages.js';
|
|
7
7
|
/**
|
|
8
8
|
* Determine the operation name based on task types
|
|
9
9
|
*/
|
|
@@ -32,25 +32,32 @@ export function routeTasksWithConfirm(tasks, message, service, userRequest, hand
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
const operation = getOperationName(validTasks);
|
|
35
|
-
// Create plan definition with valid tasks only
|
|
36
|
-
const planDefinition = createPlanDefinition(message, validTasks);
|
|
37
35
|
if (hasDefineTask) {
|
|
38
36
|
// Has DEFINE tasks - add Plan to queue for user selection
|
|
39
37
|
// Refinement flow will call this function again with refined tasks
|
|
38
|
+
const planDefinition = createPlanDefinition(message, validTasks);
|
|
40
39
|
handlers.addToQueue(planDefinition);
|
|
41
40
|
}
|
|
42
41
|
else {
|
|
43
|
-
// No DEFINE tasks -
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
// No DEFINE tasks - Plan auto-completes and adds Confirm to queue
|
|
43
|
+
// When Plan activates, Command moves to timeline
|
|
44
|
+
// When Plan completes, it moves to pending
|
|
45
|
+
// When Confirm activates, Plan stays pending (visible for context)
|
|
46
|
+
const planDefinition = createPlanDefinition(message, validTasks, () => {
|
|
47
|
+
// Plan completed - add Confirm to queue
|
|
48
|
+
const confirmDefinition = createConfirmDefinition(() => {
|
|
49
|
+
// User confirmed - complete both Confirm and Plan, then route to appropriate component
|
|
50
|
+
handlers.completeActiveAndPending();
|
|
51
|
+
executeTasksAfterConfirm(validTasks, service, userRequest, handlers);
|
|
52
|
+
}, () => {
|
|
53
|
+
// User cancelled - complete both Confirm and Plan, then show cancellation
|
|
54
|
+
handlers.completeActiveAndPending();
|
|
55
|
+
const message = getCancellationMessage(operation);
|
|
56
|
+
handlers.addToQueue(createFeedback(FeedbackType.Aborted, message));
|
|
57
|
+
});
|
|
58
|
+
handlers.addToQueue(confirmDefinition);
|
|
51
59
|
});
|
|
52
|
-
handlers.
|
|
53
|
-
handlers.addToQueue(confirmDefinition);
|
|
60
|
+
handlers.addToQueue(planDefinition);
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
/**
|
package/dist/types/components.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
// Component lifecycle status
|
|
2
|
+
export var ComponentStatus;
|
|
3
|
+
(function (ComponentStatus) {
|
|
4
|
+
ComponentStatus["Awaiting"] = "awaiting";
|
|
5
|
+
ComponentStatus["Active"] = "active";
|
|
6
|
+
ComponentStatus["Pending"] = "pending";
|
|
7
|
+
ComponentStatus["Done"] = "done";
|
|
8
|
+
})(ComponentStatus || (ComponentStatus = {}));
|
package/dist/ui/Answer.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
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
|
+
import { ComponentStatus } from '../types/components.js';
|
|
4
5
|
import { Colors, getTextColor } from '../services/colors.js';
|
|
5
6
|
import { useInput } from '../services/keyboard.js';
|
|
6
7
|
import { formatErrorMessage } from '../services/messages.js';
|
|
7
8
|
import { withMinimumTime } from '../services/timing.js';
|
|
8
9
|
import { Spinner } from './Spinner.js';
|
|
9
10
|
const MINIMUM_PROCESSING_TIME = 400;
|
|
10
|
-
export function Answer({ question, state,
|
|
11
|
+
export function Answer({ question, state, status, service, handlers, }) {
|
|
12
|
+
const isActive = status === ComponentStatus.Active;
|
|
11
13
|
const [error, setError] = useState(null);
|
|
12
14
|
const [answer, setAnswer] = useState(state?.answer ?? null);
|
|
13
15
|
useInput((input, key) => {
|
|
@@ -37,7 +39,7 @@ export function Answer({ question, state, isActive = true, service, handlers, })
|
|
|
37
39
|
// Update component state so answer persists in timeline
|
|
38
40
|
handlers?.updateState({ answer: answerText });
|
|
39
41
|
// Signal completion
|
|
40
|
-
handlers?.
|
|
42
|
+
handlers?.completeActive();
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
catch (err) {
|
package/dist/ui/Command.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
4
5
|
import { TaskType } from '../types/types.js';
|
|
5
6
|
import { Colors } from '../services/colors.js';
|
|
6
7
|
import { createPlanDefinition } from '../services/components.js';
|
|
@@ -12,7 +13,8 @@ import { ensureMinimumTime } from '../services/timing.js';
|
|
|
12
13
|
import { Spinner } from './Spinner.js';
|
|
13
14
|
import { UserQuery } from './UserQuery.js';
|
|
14
15
|
const MIN_PROCESSING_TIME = 400; // purely for visual effect
|
|
15
|
-
export function Command({ command, state,
|
|
16
|
+
export function Command({ command, state, status, service, handlers, onAborted, }) {
|
|
17
|
+
const isActive = status === ComponentStatus.Active;
|
|
16
18
|
const [error, setError] = useState(state?.error ?? null);
|
|
17
19
|
useInput((_, key) => {
|
|
18
20
|
if (key.escape && isActive) {
|
|
@@ -61,16 +63,15 @@ export function Command({ command, state, isActive = true, service, handlers, on
|
|
|
61
63
|
}
|
|
62
64
|
: undefined);
|
|
63
65
|
if (hasDefineTask) {
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
+
// DEFINE tasks: Move Command to timeline, add Plan to queue
|
|
67
|
+
handlers?.completeActive();
|
|
66
68
|
handlers?.addToQueue(planDefinition);
|
|
67
69
|
}
|
|
68
70
|
else {
|
|
69
|
-
// No DEFINE tasks:
|
|
71
|
+
// No DEFINE tasks: Complete Command, then route to Confirm flow
|
|
72
|
+
handlers?.completeActive();
|
|
70
73
|
routeTasksWithConfirm(result.tasks, result.message, svc, command, handlers, false);
|
|
71
74
|
}
|
|
72
|
-
// Move Command to timeline
|
|
73
|
-
handlers?.onComplete();
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
catch (err) {
|