prompt-language-shell 0.6.2 → 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/services/components.js +25 -15
- package/dist/services/configuration.js +21 -13
- package/dist/services/refinement.js +1 -2
- package/dist/services/task-router.js +21 -20
- package/dist/types/components.js +8 -1
- package/dist/ui/Answer.js +3 -1
- package/dist/ui/Command.js +6 -4
- package/dist/ui/Component.js +14 -17
- package/dist/ui/Config.js +3 -2
- package/dist/ui/Confirm.js +6 -7
- package/dist/ui/Execute.js +3 -1
- package/dist/ui/Introspect.js +3 -1
- package/dist/ui/Plan.js +8 -5
- package/dist/ui/Refinement.js +3 -1
- package/dist/ui/Validate.js +4 -3
- package/dist/ui/Workflow.js +134 -74
- package/package.json +1 -1
- package/dist/services/queue.js +0 -52
|
@@ -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;
|
|
@@ -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
|
}
|
|
@@ -357,13 +365,13 @@ function parseConfigValue(key, stringValue, schema) {
|
|
|
357
365
|
if (key in schema) {
|
|
358
366
|
const definition = schema[key];
|
|
359
367
|
switch (definition.type) {
|
|
360
|
-
case
|
|
368
|
+
case ConfigDefinitionType.Boolean:
|
|
361
369
|
return stringValue === 'true';
|
|
362
|
-
case
|
|
370
|
+
case ConfigDefinitionType.Number:
|
|
363
371
|
return Number(stringValue);
|
|
364
|
-
case
|
|
365
|
-
case
|
|
366
|
-
case
|
|
372
|
+
case ConfigDefinitionType.String:
|
|
373
|
+
case ConfigDefinitionType.RegExp:
|
|
374
|
+
case ConfigDefinitionType.Enum:
|
|
367
375
|
return stringValue;
|
|
368
376
|
}
|
|
369
377
|
}
|
|
@@ -25,8 +25,7 @@ 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
|
|
29
|
-
undefined // No commandComponent - use normal flow
|
|
28
|
+
routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, handlers, false // No DEFINE tasks in refined result
|
|
30
29
|
);
|
|
31
30
|
}
|
|
32
31
|
catch (err) {
|
|
@@ -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
|
*/
|
|
@@ -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) {
|
|
24
24
|
if (tasks.length === 0)
|
|
25
25
|
return;
|
|
26
26
|
// Filter out ignore and discard tasks early
|
|
@@ -32,31 +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
|
-
|
|
53
|
-
if (commandComponent) {
|
|
54
|
-
handlers.completeActive(planDefinition);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
handlers.addToTimeline(planDefinition);
|
|
58
|
-
}
|
|
59
|
-
handlers.addToQueue(confirmDefinition);
|
|
60
|
+
handlers.addToQueue(planDefinition);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
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) => {
|
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) {
|
|
@@ -66,9 +68,9 @@ export function Command({ command, state, isActive = true, service, handlers, on
|
|
|
66
68
|
handlers?.addToQueue(planDefinition);
|
|
67
69
|
}
|
|
68
70
|
else {
|
|
69
|
-
// No DEFINE tasks:
|
|
70
|
-
|
|
71
|
-
);
|
|
71
|
+
// No DEFINE tasks: Complete Command, then route to Confirm flow
|
|
72
|
+
handlers?.completeActive();
|
|
73
|
+
routeTasksWithConfirm(result.tasks, result.message, svc, command, handlers, false);
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
}
|
package/dist/ui/Component.js
CHANGED
|
@@ -14,36 +14,33 @@ import { Refinement } from './Refinement.js';
|
|
|
14
14
|
import { Report } from './Report.js';
|
|
15
15
|
import { Validate } from './Validate.js';
|
|
16
16
|
import { Welcome } from './Welcome.js';
|
|
17
|
-
export const Component = memo(function Component({ def,
|
|
18
|
-
// For stateless components, always inactive
|
|
19
|
-
const isStatelessComponent = !('state' in def);
|
|
20
|
-
const componentIsActive = isStatelessComponent ? false : isActive;
|
|
17
|
+
export const Component = memo(function Component({ def, debug, }) {
|
|
21
18
|
switch (def.name) {
|
|
22
19
|
case ComponentName.Welcome:
|
|
23
|
-
return _jsx(Welcome, { ...def.props });
|
|
20
|
+
return _jsx(Welcome, { ...def.props, status: def.status });
|
|
24
21
|
case ComponentName.Config:
|
|
25
|
-
return (_jsx(Config, { ...def.props, state: def.state,
|
|
22
|
+
return (_jsx(Config, { ...def.props, state: def.state, status: def.status, debug: debug }));
|
|
26
23
|
case ComponentName.Command:
|
|
27
|
-
return _jsx(Command, { ...def.props, state: def.state,
|
|
24
|
+
return _jsx(Command, { ...def.props, state: def.state, status: def.status });
|
|
28
25
|
case ComponentName.Plan:
|
|
29
|
-
return (_jsx(Plan, { ...def.props, state: def.state,
|
|
26
|
+
return (_jsx(Plan, { ...def.props, state: def.state, status: def.status, debug: debug }));
|
|
30
27
|
case ComponentName.Feedback:
|
|
31
|
-
return _jsx(Feedback, { ...def.props });
|
|
28
|
+
return _jsx(Feedback, { ...def.props, status: def.status });
|
|
32
29
|
case ComponentName.Message:
|
|
33
|
-
return _jsx(Message, { ...def.props });
|
|
30
|
+
return _jsx(Message, { ...def.props, status: def.status });
|
|
34
31
|
case ComponentName.Refinement:
|
|
35
|
-
return (_jsx(Refinement, { ...def.props, state: def.state,
|
|
32
|
+
return (_jsx(Refinement, { ...def.props, state: def.state, status: def.status }));
|
|
36
33
|
case ComponentName.Confirm:
|
|
37
|
-
return _jsx(Confirm, { ...def.props, state: def.state,
|
|
34
|
+
return _jsx(Confirm, { ...def.props, state: def.state, status: def.status });
|
|
38
35
|
case ComponentName.Introspect:
|
|
39
|
-
return (_jsx(Introspect, { ...def.props, state: def.state,
|
|
36
|
+
return (_jsx(Introspect, { ...def.props, state: def.state, status: def.status, debug: debug }));
|
|
40
37
|
case ComponentName.Report:
|
|
41
|
-
return _jsx(Report, { ...def.props });
|
|
38
|
+
return _jsx(Report, { ...def.props, status: def.status });
|
|
42
39
|
case ComponentName.Answer:
|
|
43
|
-
return _jsx(Answer, { ...def.props, state: def.state,
|
|
40
|
+
return _jsx(Answer, { ...def.props, state: def.state, status: def.status });
|
|
44
41
|
case ComponentName.Execute:
|
|
45
|
-
return _jsx(Execute, { ...def.props, state: def.state,
|
|
42
|
+
return _jsx(Execute, { ...def.props, state: def.state, status: def.status });
|
|
46
43
|
case ComponentName.Validate:
|
|
47
|
-
return (_jsx(Validate, { ...def.props, state: def.state,
|
|
44
|
+
return (_jsx(Validate, { ...def.props, state: def.state, status: def.status, debug: debug }));
|
|
48
45
|
}
|
|
49
46
|
});
|
package/dist/ui/Config.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text, useFocus } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
|
+
import { ComponentStatus } from '../types/components.js';
|
|
5
6
|
import { Colors } from '../services/colors.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
7
8
|
export var StepType;
|
|
@@ -57,8 +58,8 @@ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
|
|
|
57
58
|
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
|
|
58
59
|
}) }));
|
|
59
60
|
}
|
|
60
|
-
export function Config({ steps, state,
|
|
61
|
-
|
|
61
|
+
export function Config({ steps, state, status, debug, handlers, onFinished, onAborted, }) {
|
|
62
|
+
const isActive = status === ComponentStatus.Active;
|
|
62
63
|
const [step, setStep] = useState(!isActive ? (state?.completedStep ?? steps.length) : 0);
|
|
63
64
|
const [values, setValues] = useState(() => {
|
|
64
65
|
// If not active and we have saved state values, use those
|
package/dist/ui/Confirm.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
4
5
|
import { Colors, Palette } from '../services/colors.js';
|
|
5
6
|
import { useInput } from '../services/keyboard.js';
|
|
6
7
|
import { UserQuery } from './UserQuery.js';
|
|
7
|
-
export function Confirm({ message, state,
|
|
8
|
-
|
|
8
|
+
export function Confirm({ message, state, status, handlers, onConfirmed, onCancelled, }) {
|
|
9
|
+
const isActive = status === ComponentStatus.Active;
|
|
9
10
|
const [selectedIndex, setSelectedIndex] = useState(state?.selectedIndex ?? 0); // 0 = Yes, 1 = No
|
|
10
11
|
useInput((input, key) => {
|
|
11
12
|
if (!isActive)
|
|
@@ -18,11 +19,9 @@ export function Confirm({ message, state, isActive = true, handlers, onConfirmed
|
|
|
18
19
|
}
|
|
19
20
|
else if (key.tab) {
|
|
20
21
|
// Toggle between Yes (0) and No (1)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return newIndex;
|
|
25
|
-
});
|
|
22
|
+
const newIndex = selectedIndex === 0 ? 1 : 0;
|
|
23
|
+
setSelectedIndex(newIndex);
|
|
24
|
+
handlers?.updateState({ selectedIndex: newIndex });
|
|
26
25
|
}
|
|
27
26
|
else if (key.return) {
|
|
28
27
|
// Confirm selection
|
package/dist/ui/Execute.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } 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, Palette } from '../services/colors.js';
|
|
5
6
|
import { useInput } from '../services/keyboard.js';
|
|
6
7
|
import { formatErrorMessage } from '../services/messages.js';
|
|
@@ -85,7 +86,8 @@ function CommandStatusDisplay({ item, elapsed }) {
|
|
|
85
86
|
const elapsedTime = getElapsedTime();
|
|
86
87
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: colors.icon, children: STATUS_ICONS[item.status] }), _jsx(Text, { color: colors.description, children: item.label || item.command.description }), elapsedTime !== undefined && (_jsxs(Text, { color: Palette.DarkGray, children: [" (", formatDuration(elapsedTime), ")"] }))] }), _jsxs(Box, { paddingLeft: 5, children: [_jsx(Text, { color: colors.symbol, children: "\u221F " }), _jsx(Text, { color: colors.command, children: item.command.command }), item.status === ExecutionStatus.Running && (_jsxs(Text, { children: [' ', _jsx(Spinner, {})] }))] })] }));
|
|
87
88
|
}
|
|
88
|
-
export function Execute({ tasks, state,
|
|
89
|
+
export function Execute({ tasks, state, status, service, handlers, }) {
|
|
90
|
+
const isActive = status === ComponentStatus.Active;
|
|
89
91
|
// isActive passed as prop
|
|
90
92
|
const [error, setError] = useState(state?.error ?? null);
|
|
91
93
|
const [isExecuting, setIsExecuting] = useState(false);
|
package/dist/ui/Introspect.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } 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 { createReportDefinition } from '../services/components.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
@@ -42,7 +43,8 @@ function parseCapabilityFromTask(task) {
|
|
|
42
43
|
isIndirect,
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
|
-
export function Introspect({ tasks, state,
|
|
46
|
+
export function Introspect({ tasks, state, status, service, children, debug = false, handlers, }) {
|
|
47
|
+
const isActive = status === ComponentStatus.Active;
|
|
46
48
|
// isActive passed as prop
|
|
47
49
|
const [error, setError] = useState(null);
|
|
48
50
|
useInput((input, key) => {
|
package/dist/ui/Plan.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box } from 'ink';
|
|
4
|
+
import { ComponentStatus } from '../types/components.js';
|
|
4
5
|
import { TaskType } from '../types/types.js';
|
|
5
6
|
import { getTaskColors } from '../services/colors.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
@@ -49,7 +50,8 @@ function taskToListItem(task, highlightedChildIndex = null, isDefineTaskWithoutS
|
|
|
49
50
|
}
|
|
50
51
|
return item;
|
|
51
52
|
}
|
|
52
|
-
export function Plan({ message, tasks, state,
|
|
53
|
+
export function Plan({ message, tasks, state, status, debug = false, handlers, onSelectionConfirmed, }) {
|
|
54
|
+
const isActive = status === ComponentStatus.Active;
|
|
53
55
|
// isActive passed as prop
|
|
54
56
|
const [highlightedIndex, setHighlightedIndex] = useState(state?.highlightedIndex ?? null);
|
|
55
57
|
const [currentDefineGroupIndex, setCurrentDefineGroupIndex] = useState(state?.currentDefineGroupIndex ?? 0);
|
|
@@ -70,9 +72,10 @@ export function Plan({ message, tasks, state, isActive = true, debug = false, ha
|
|
|
70
72
|
if (isActive && defineTaskIndices.length === 0 && onSelectionConfirmed) {
|
|
71
73
|
// No selection needed - all tasks are concrete
|
|
72
74
|
const concreteTasks = tasks.filter((task) => task.type !== TaskType.Ignore && task.type !== TaskType.Discard);
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
+
// Complete the selection phase - it goes to timeline
|
|
76
|
+
// Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
|
|
75
77
|
handlers?.completeActive();
|
|
78
|
+
onSelectionConfirmed(concreteTasks);
|
|
76
79
|
}
|
|
77
80
|
}, [
|
|
78
81
|
isActive,
|
|
@@ -143,8 +146,8 @@ export function Plan({ message, tasks, state, isActive = true, debug = false, ha
|
|
|
143
146
|
}
|
|
144
147
|
});
|
|
145
148
|
if (onSelectionConfirmed) {
|
|
146
|
-
//
|
|
147
|
-
//
|
|
149
|
+
// Complete the selection phase - it goes to timeline
|
|
150
|
+
// Callback will create a new Plan showing refined tasks (pending) + Confirm (active)
|
|
148
151
|
handlers?.completeActive();
|
|
149
152
|
onSelectionConfirmed(refinedTasks);
|
|
150
153
|
}
|
package/dist/ui/Refinement.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box } from 'ink';
|
|
3
|
+
import { ComponentStatus } from '../types/components.js';
|
|
3
4
|
import { useInput } from '../services/keyboard.js';
|
|
4
5
|
import { Message } from './Message.js';
|
|
5
6
|
import { Spinner } from './Spinner.js';
|
|
6
|
-
export const Refinement = ({ text,
|
|
7
|
+
export const Refinement = ({ text, status, onAborted }) => {
|
|
8
|
+
const isActive = status === ComponentStatus.Active;
|
|
7
9
|
useInput((_, key) => {
|
|
8
10
|
if (key.escape && isActive) {
|
|
9
11
|
onAborted('plan refinement');
|
package/dist/ui/Validate.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, getTextColor } from '../services/colors.js';
|
|
6
7
|
import { useInput } from '../services/keyboard.js';
|
|
@@ -10,8 +11,8 @@ import { saveConfig, unflattenConfig } from '../services/configuration.js';
|
|
|
10
11
|
import { Config, StepType } from './Config.js';
|
|
11
12
|
import { Spinner } from './Spinner.js';
|
|
12
13
|
const MIN_PROCESSING_TIME = 1000;
|
|
13
|
-
export function Validate({ missingConfig, userRequest, state,
|
|
14
|
-
|
|
14
|
+
export function Validate({ missingConfig, userRequest, state, status, service, children, debug, onError, onComplete, onAborted, handlers, }) {
|
|
15
|
+
const isActive = status === ComponentStatus.Active;
|
|
15
16
|
const [error, setError] = useState(null);
|
|
16
17
|
const [completionMessage, setCompletionMessage] = useState(null);
|
|
17
18
|
const [configRequirements, setConfigRequirements] = useState(null);
|
|
@@ -132,7 +133,7 @@ export function Validate({ missingConfig, userRequest, state, isActive = true, s
|
|
|
132
133
|
const handleConfigAborted = (operation) => {
|
|
133
134
|
onAborted(operation);
|
|
134
135
|
};
|
|
135
|
-
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !completionMessage && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: getTextColor(isActive), children: ["Validating configuration requirements.", ' '] }), _jsx(Spinner, {})] })), completionMessage && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), configSteps && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Config, { steps: configSteps,
|
|
136
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isActive && !completionMessage && !error && (_jsxs(Box, { marginLeft: 1, children: [_jsxs(Text, { color: getTextColor(isActive), children: ["Validating configuration requirements.", ' '] }), _jsx(Spinner, {})] })), completionMessage && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: getTextColor(isActive), children: completionMessage }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), configSteps && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Config, { steps: configSteps, status: status, debug: debug, onFinished: handleConfigFinished, onAborted: handleConfigAborted, handlers: handlers }) })), children] }));
|
|
136
137
|
}
|
|
137
138
|
/**
|
|
138
139
|
* Build prompt for VALIDATE tool
|
package/dist/ui/Workflow.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useMemo,
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { Box, Static } from 'ink';
|
|
4
|
+
import { ComponentStatus, } from '../types/components.js';
|
|
4
5
|
import { ComponentName, FeedbackType } from '../types/types.js';
|
|
5
6
|
import { createFeedback, isStateless, markAsDone, } from '../services/components.js';
|
|
6
7
|
import { exitApp } from '../services/process.js';
|
|
@@ -8,36 +9,82 @@ import { getCancellationMessage } from '../services/messages.js';
|
|
|
8
9
|
import { Component } from './Component.js';
|
|
9
10
|
export const Workflow = ({ initialQueue, debug }) => {
|
|
10
11
|
const [timeline, setTimeline] = useState([]);
|
|
11
|
-
const [
|
|
12
|
+
const [current, setCurrent] = useState({ active: null, pending: null });
|
|
12
13
|
const [queue, setQueue] = useState(initialQueue);
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
:
|
|
34
|
-
|
|
14
|
+
// Function to move active to pending (component just completed)
|
|
15
|
+
const moveActiveToPending = useCallback(() => {
|
|
16
|
+
setCurrent((curr) => {
|
|
17
|
+
const { active } = curr;
|
|
18
|
+
if (!active)
|
|
19
|
+
return curr;
|
|
20
|
+
// Move active to pending without marking as done
|
|
21
|
+
const pendingComponent = { ...active, status: ComponentStatus.Pending };
|
|
22
|
+
return { active: null, pending: pendingComponent };
|
|
23
|
+
});
|
|
24
|
+
}, []);
|
|
25
|
+
// Function to move active directly to timeline (error/abort)
|
|
26
|
+
const moveActiveToTimeline = useCallback(() => {
|
|
27
|
+
setCurrent((curr) => {
|
|
28
|
+
const { active, pending } = curr;
|
|
29
|
+
if (!active)
|
|
30
|
+
return curr;
|
|
31
|
+
// Mark as done and add to timeline
|
|
32
|
+
const doneComponent = markAsDone(active);
|
|
33
|
+
setTimeline((prev) => [...prev, doneComponent]);
|
|
34
|
+
return { active: null, pending };
|
|
35
|
+
});
|
|
35
36
|
}, []);
|
|
36
37
|
// Global handlers for all stateful components
|
|
37
38
|
const handlers = useMemo(() => ({
|
|
39
|
+
addToQueue: (...items) => {
|
|
40
|
+
setQueue((queue) => [...queue, ...items]);
|
|
41
|
+
},
|
|
42
|
+
updateState: (newState) => {
|
|
43
|
+
setCurrent((curr) => {
|
|
44
|
+
const { active, pending } = curr;
|
|
45
|
+
if (!active || !('state' in active))
|
|
46
|
+
return curr;
|
|
47
|
+
const stateful = active;
|
|
48
|
+
const updated = {
|
|
49
|
+
...stateful,
|
|
50
|
+
state: {
|
|
51
|
+
...stateful.state,
|
|
52
|
+
...newState,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
return { active: updated, pending };
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
completeActive: (...items) => {
|
|
59
|
+
moveActiveToPending();
|
|
60
|
+
if (items.length > 0) {
|
|
61
|
+
setQueue((queue) => [...items, ...queue]);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
completeActiveAndPending: (...items) => {
|
|
65
|
+
setCurrent((curr) => {
|
|
66
|
+
const { active, pending } = curr;
|
|
67
|
+
// Move both to timeline - pending first (Plan), then active (Confirm)
|
|
68
|
+
if (pending) {
|
|
69
|
+
const donePending = markAsDone(pending);
|
|
70
|
+
setTimeline((prev) => [...prev, donePending]);
|
|
71
|
+
}
|
|
72
|
+
if (active) {
|
|
73
|
+
const doneActive = markAsDone(active);
|
|
74
|
+
setTimeline((prev) => [...prev, doneActive]);
|
|
75
|
+
}
|
|
76
|
+
return { active: null, pending: null };
|
|
77
|
+
});
|
|
78
|
+
if (items.length > 0) {
|
|
79
|
+
setQueue((queue) => [...items, ...queue]);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
addToTimeline: (...items) => {
|
|
83
|
+
setTimeline((prev) => [...prev, ...items]);
|
|
84
|
+
},
|
|
38
85
|
onAborted: (operation) => {
|
|
39
86
|
moveActiveToTimeline();
|
|
40
|
-
// Add feedback to queue
|
|
87
|
+
// Add feedback to queue
|
|
41
88
|
const message = getCancellationMessage(operation);
|
|
42
89
|
setQueue((queue) => [
|
|
43
90
|
...queue,
|
|
@@ -46,76 +93,82 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
46
93
|
},
|
|
47
94
|
onError: (error) => {
|
|
48
95
|
moveActiveToTimeline();
|
|
49
|
-
// Add feedback to queue
|
|
96
|
+
// Add feedback to queue
|
|
50
97
|
setQueue((queue) => [
|
|
51
98
|
...queue,
|
|
52
99
|
createFeedback(FeedbackType.Failed, error),
|
|
53
100
|
]);
|
|
54
101
|
},
|
|
55
|
-
|
|
56
|
-
setQueue((queue) => [...queue, ...items]);
|
|
57
|
-
},
|
|
58
|
-
addToTimeline: (...items) => {
|
|
59
|
-
setTimeline((prev) => [...prev, ...items]);
|
|
60
|
-
},
|
|
61
|
-
completeActive: (...items) => {
|
|
62
|
-
moveActiveToTimeline(...items);
|
|
63
|
-
},
|
|
64
|
-
updateState: (newState) => {
|
|
65
|
-
setActive((curr) => {
|
|
66
|
-
if (!curr || !('state' in curr))
|
|
67
|
-
return curr;
|
|
68
|
-
const stateful = curr;
|
|
69
|
-
const updated = {
|
|
70
|
-
...stateful,
|
|
71
|
-
state: {
|
|
72
|
-
...stateful.state,
|
|
73
|
-
...newState,
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
// Update ref synchronously so moveActiveToTimeline sees the latest state
|
|
77
|
-
activeRef.current = updated;
|
|
78
|
-
return updated;
|
|
79
|
-
});
|
|
80
|
-
},
|
|
81
|
-
}), [moveActiveToTimeline]);
|
|
102
|
+
}), [moveActiveToPending, moveActiveToTimeline]);
|
|
82
103
|
// Global Esc handler removed - components handle their own Esc individually
|
|
83
104
|
// Move next item from queue to active
|
|
84
105
|
useEffect(() => {
|
|
85
|
-
|
|
86
|
-
|
|
106
|
+
const { active, pending } = current;
|
|
107
|
+
// Early return: not ready to activate next
|
|
108
|
+
if (queue.length === 0 || active !== null) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const [first, ...rest] = queue;
|
|
112
|
+
const activeComponent = { ...first, status: ComponentStatus.Active };
|
|
113
|
+
// Confirm - keep pending visible (Plan showing what will execute)
|
|
114
|
+
if (first.name === ComponentName.Confirm) {
|
|
87
115
|
setQueue(rest);
|
|
88
|
-
|
|
116
|
+
setCurrent({ active: activeComponent, pending });
|
|
117
|
+
return;
|
|
89
118
|
}
|
|
90
|
-
|
|
119
|
+
// Other components - move pending to timeline first, then activate
|
|
120
|
+
if (pending) {
|
|
121
|
+
const donePending = markAsDone(pending);
|
|
122
|
+
setTimeline((prev) => [...prev, donePending]);
|
|
123
|
+
}
|
|
124
|
+
setQueue(rest);
|
|
125
|
+
setCurrent({ active: activeComponent, pending: null });
|
|
126
|
+
}, [queue, current]);
|
|
91
127
|
// Process active component - stateless components auto-move to timeline
|
|
92
128
|
useEffect(() => {
|
|
129
|
+
const { active, pending } = current;
|
|
93
130
|
if (!active)
|
|
94
131
|
return;
|
|
95
132
|
if (isStateless(active)) {
|
|
133
|
+
// Stateless components move directly to timeline
|
|
96
134
|
const doneComponent = markAsDone(active);
|
|
97
135
|
setTimeline((prev) => [...prev, doneComponent]);
|
|
98
|
-
|
|
136
|
+
setCurrent({ active: null, pending });
|
|
99
137
|
}
|
|
100
|
-
// Stateful components stay in active until handlers move them to
|
|
101
|
-
}, [
|
|
102
|
-
//
|
|
138
|
+
// Stateful components stay in active until handlers move them to pending
|
|
139
|
+
}, [current]);
|
|
140
|
+
// Move final pending to timeline and exit when all done
|
|
103
141
|
useEffect(() => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
142
|
+
const { active, pending } = current;
|
|
143
|
+
// Early return: not ready to finish
|
|
144
|
+
if (active !== null || queue.length > 0) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Handle pending component
|
|
148
|
+
if (pending) {
|
|
149
|
+
const donePending = markAsDone(pending);
|
|
150
|
+
setTimeline((prev) => [...prev, donePending]);
|
|
151
|
+
setCurrent({ active: null, pending: null });
|
|
152
|
+
return;
|
|
110
153
|
}
|
|
111
|
-
|
|
112
|
-
|
|
154
|
+
// Early return: nothing to exit with
|
|
155
|
+
if (timeline.length === 0) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Everything is done, exit
|
|
159
|
+
const lastItem = timeline[timeline.length - 1];
|
|
160
|
+
const isFailed = lastItem.name === ComponentName.Feedback &&
|
|
161
|
+
lastItem.props.type === FeedbackType.Failed;
|
|
162
|
+
exitApp(isFailed ? 1 : 0);
|
|
163
|
+
}, [current, queue, timeline]);
|
|
164
|
+
// Render active and pending components
|
|
113
165
|
const activeComponent = useMemo(() => {
|
|
166
|
+
const { active } = current;
|
|
114
167
|
if (!active)
|
|
115
168
|
return null;
|
|
116
|
-
// For stateless components, render as-is
|
|
169
|
+
// For stateless components, render as-is
|
|
117
170
|
if (isStateless(active)) {
|
|
118
|
-
return
|
|
171
|
+
return _jsx(Component, { def: active, debug: debug }, active.id);
|
|
119
172
|
}
|
|
120
173
|
// For stateful components, inject global handlers
|
|
121
174
|
const statefulActive = active;
|
|
@@ -126,7 +179,14 @@ export const Workflow = ({ initialQueue, debug }) => {
|
|
|
126
179
|
handlers,
|
|
127
180
|
},
|
|
128
181
|
};
|
|
129
|
-
return
|
|
130
|
-
}, [
|
|
131
|
-
|
|
182
|
+
return _jsx(Component, { def: wrappedDef, debug: debug }, active.id);
|
|
183
|
+
}, [current, debug, handlers]);
|
|
184
|
+
const pendingComponent = useMemo(() => {
|
|
185
|
+
const { pending } = current;
|
|
186
|
+
if (!pending)
|
|
187
|
+
return null;
|
|
188
|
+
// Pending components don't receive input
|
|
189
|
+
return _jsx(Component, { def: pending, debug: debug }, pending.id);
|
|
190
|
+
}, [current, debug]);
|
|
191
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: timeline, children: (item) => (_jsx(Box, { marginTop: 1, children: _jsx(Component, { def: item, debug: false }) }, item.id)) }, "timeline"), pendingComponent && _jsx(Box, { marginTop: 1, children: pendingComponent }), activeComponent && _jsx(Box, { marginTop: 1, children: activeComponent })] }));
|
|
132
192
|
};
|
package/package.json
CHANGED
package/dist/services/queue.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { FeedbackType } from '../types/types.js';
|
|
2
|
-
import { createFeedback } from './components.js';
|
|
3
|
-
import { FeedbackMessages } from './messages.js';
|
|
4
|
-
import { exitApp } from './process.js';
|
|
5
|
-
/**
|
|
6
|
-
* Higher-order function that wraps queue handler logic with common patterns:
|
|
7
|
-
* - Check if queue is empty
|
|
8
|
-
* - Extract first element
|
|
9
|
-
* - Optionally check component name
|
|
10
|
-
* - Execute callback with first element
|
|
11
|
-
* - Return new queue state
|
|
12
|
-
*/
|
|
13
|
-
export function withQueueHandler(componentName, callback, shouldExit = false, exitCode = 0) {
|
|
14
|
-
return (currentQueue) => {
|
|
15
|
-
if (currentQueue.length === 0)
|
|
16
|
-
return currentQueue;
|
|
17
|
-
const [first, ...rest] = currentQueue;
|
|
18
|
-
// If componentName is specified, check if it matches
|
|
19
|
-
if (componentName && first.name !== componentName) {
|
|
20
|
-
if (shouldExit) {
|
|
21
|
-
exitApp(exitCode);
|
|
22
|
-
}
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
// Execute callback with first and rest
|
|
26
|
-
const result = callback(first, rest);
|
|
27
|
-
// Exit if specified
|
|
28
|
-
if (shouldExit) {
|
|
29
|
-
exitApp(exitCode);
|
|
30
|
-
}
|
|
31
|
-
// Return result or empty queue
|
|
32
|
-
return result || [];
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Creates a generic error handler for a component
|
|
37
|
-
*/
|
|
38
|
-
export function createErrorHandler(componentName, addToTimeline) {
|
|
39
|
-
return (error) => withQueueHandler(componentName, (first) => {
|
|
40
|
-
addToTimeline(first, createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, error));
|
|
41
|
-
return undefined;
|
|
42
|
-
}, true, 1);
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Creates a generic completion handler for a component
|
|
46
|
-
*/
|
|
47
|
-
export function createCompletionHandler(componentName, addToTimeline, onComplete) {
|
|
48
|
-
return withQueueHandler(componentName, (first) => {
|
|
49
|
-
onComplete(first);
|
|
50
|
-
return undefined;
|
|
51
|
-
}, true, 0);
|
|
52
|
-
}
|