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.
@@ -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 **optional** config keys that are marked as "(discovered)" (they exist in user's config file)
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 'regexp':
43
+ case ConfigDefinitionType.RegExp:
43
44
  return (value) => definition.pattern.test(value);
44
- case 'string':
45
+ case ConfigDefinitionType.String:
45
46
  return () => true; // Strings are always valid
46
- case 'enum':
47
+ case ConfigDefinitionType.Enum:
47
48
  return (value) => definition.values.includes(value);
48
- case 'number':
49
+ case ConfigDefinitionType.Number:
49
50
  return (value) => !isNaN(Number(value));
50
- case 'boolean':
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 'regexp':
107
- case 'string': {
107
+ case ConfigDefinitionType.RegExp:
108
+ case ConfigDefinitionType.String: {
108
109
  const value = currentValue !== undefined && typeof currentValue === 'string'
109
110
  ? currentValue
110
- : definition.type === 'string'
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 'number': {
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 'enum': {
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 'boolean': {
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: 'Yes', value: 'true' },
168
- { label: 'No', value: 'false' },
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 simply returns the component as-is.
320
+ * so this function sets the status to Done and returns the updated component.
312
321
  */
313
322
  export function markAsDone(component) {
314
- // State already updated via handlers.updateState
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: 'regexp',
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: 'enum',
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: 'boolean',
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 'regexp':
250
+ case ConfigDefinitionType.RegExp:
243
251
  isValid = typeof value === 'string' && definition.pattern.test(value);
244
252
  break;
245
- case 'string':
253
+ case ConfigDefinitionType.String:
246
254
  isValid = typeof value === 'string';
247
255
  break;
248
- case 'enum':
256
+ case ConfigDefinitionType.Enum:
249
257
  isValid =
250
258
  typeof value === 'string' && definition.values.includes(value);
251
259
  break;
252
- case 'number':
260
+ case ConfigDefinitionType.Number:
253
261
  isValid = typeof value === 'number';
254
262
  break;
255
- case 'boolean':
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 available config structure for CONFIG tool
267
- * Returns keys with descriptions only (no values for privacy)
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 getAvailableConfigStructure() {
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 structure;
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
- // Add discovered keys that aren't in schema
300
- for (const key of Object.keys(flatConfig)) {
301
- if (!structure[key]) {
302
- structure[key] = `${key} (discovered)`;
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, only use schema
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
- for (const [dottedKey, value] of Object.entries(dotted)) {
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
- // Set final value
329
- current[parts[parts.length - 1]] = value;
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 - 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);
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.addToTimeline(planDefinition);
53
- handlers.addToQueue(confirmDefinition);
60
+ handlers.addToQueue(planDefinition);
54
61
  }
55
62
  }
56
63
  /**
@@ -1 +1,8 @@
1
- export {};
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, isActive = true, service, handlers, }) {
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?.onComplete();
42
+ handlers?.completeActive();
41
43
  }
42
44
  }
43
45
  catch (err) {
@@ -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, isActive = true, service, handlers, onAborted, }) {
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
- // Has DEFINE tasks: Add Plan to queue for selection
65
- // The refinement callback will handle routing after user selects
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: Use routing service for Confirm flow
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) {