prompt-language-shell 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ import { ExecutionResult, ExecutionStatus, executeCommand, setOutputCallback, } from '../services/shell.js';
2
+ import { calculateElapsed } from '../services/utils.js';
3
+ /**
4
+ * Execute a single task and track its progress.
5
+ * All execution logic is contained here, outside of React components.
6
+ */
7
+ export async function executeTask(command, index, callbacks) {
8
+ const startTime = Date.now();
9
+ let stdout = '';
10
+ let stderr = '';
11
+ let error = '';
12
+ let workdir;
13
+ // Helper to create current output snapshot
14
+ const createOutput = () => ({
15
+ stdout,
16
+ stderr,
17
+ error,
18
+ workdir,
19
+ });
20
+ // Set up output streaming callback
21
+ setOutputCallback((data, stream) => {
22
+ if (stream === 'stdout') {
23
+ stdout += data;
24
+ }
25
+ else {
26
+ stderr += data;
27
+ }
28
+ callbacks.onOutputChange?.(createOutput());
29
+ });
30
+ callbacks.onStart?.();
31
+ try {
32
+ const result = await executeCommand(command, undefined, index);
33
+ // Clear callback
34
+ setOutputCallback(undefined);
35
+ const elapsed = calculateElapsed(startTime);
36
+ // Update final output from result
37
+ stdout = result.output;
38
+ stderr = result.errors;
39
+ workdir = result.workdir;
40
+ if (result.result === ExecutionResult.Success) {
41
+ const output = createOutput();
42
+ callbacks.onComplete?.(elapsed, output);
43
+ return {
44
+ status: ExecutionStatus.Success,
45
+ elapsed,
46
+ output,
47
+ };
48
+ }
49
+ else {
50
+ const errorMsg = result.errors || result.error || 'Command failed';
51
+ error = errorMsg;
52
+ const output = createOutput();
53
+ callbacks.onError?.(errorMsg, elapsed, output);
54
+ return {
55
+ status: ExecutionStatus.Failed,
56
+ elapsed,
57
+ output,
58
+ };
59
+ }
60
+ }
61
+ catch (err) {
62
+ // Clear callback
63
+ setOutputCallback(undefined);
64
+ const elapsed = calculateElapsed(startTime);
65
+ const errorMsg = err instanceof Error ? err.message : 'Unknown error';
66
+ error = errorMsg;
67
+ const output = createOutput();
68
+ callbacks.onError?.(errorMsg, elapsed, output);
69
+ return {
70
+ status: ExecutionStatus.Failed,
71
+ elapsed,
72
+ output,
73
+ };
74
+ }
75
+ }
76
+ /**
77
+ * Create an empty task output
78
+ */
79
+ export function createEmptyOutput() {
80
+ return { stdout: '', stderr: '', error: '' };
81
+ }
@@ -3,6 +3,7 @@ export var ExecuteActionType;
3
3
  ExecuteActionType["ProcessingComplete"] = "PROCESSING_COMPLETE";
4
4
  ExecuteActionType["CommandsReady"] = "COMMANDS_READY";
5
5
  ExecuteActionType["ProcessingError"] = "PROCESSING_ERROR";
6
+ ExecuteActionType["TaskStarted"] = "TASK_STARTED";
6
7
  ExecuteActionType["TaskComplete"] = "TASK_COMPLETE";
7
8
  ExecuteActionType["AllTasksComplete"] = "ALL_TASKS_COMPLETE";
8
9
  ExecuteActionType["TaskErrorCritical"] = "TASK_ERROR_CRITICAL";
@@ -1,6 +1,28 @@
1
+ import { ExecutionStatus } from '../services/shell.js';
1
2
  /**
2
3
  * Calculate total elapsed time from task infos
3
4
  */
4
5
  export function getTotalElapsed(tasks) {
5
6
  return tasks.reduce((sum, task) => sum + task.elapsed, 0);
6
7
  }
8
+ /**
9
+ * Calculate the number of finished tasks (success, failed, or aborted)
10
+ */
11
+ export function getCompletedCount(tasks) {
12
+ return tasks.filter((task) => task.status === ExecutionStatus.Success ||
13
+ task.status === ExecutionStatus.Failed ||
14
+ task.status === ExecutionStatus.Aborted).length;
15
+ }
16
+ /**
17
+ * Get the index of the current task to execute.
18
+ * Returns the index of the first Running or Pending task, or tasks.length if all done.
19
+ */
20
+ export function getCurrentTaskIndex(tasks) {
21
+ const runningIndex = tasks.findIndex((t) => t.status === ExecutionStatus.Running);
22
+ if (runningIndex !== -1)
23
+ return runningIndex;
24
+ const pendingIndex = tasks.findIndex((t) => t.status === ExecutionStatus.Pending);
25
+ if (pendingIndex !== -1)
26
+ return pendingIndex;
27
+ return tasks.length;
28
+ }
@@ -1,407 +1,122 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { parse as parseYaml } from 'yaml';
3
- import { ConfigDefinitionType, } from '../configuration/types.js';
4
2
  import { ComponentStatus, } from '../types/components.js';
5
3
  import { ComponentName } from '../types/types.js';
6
- import { getConfigPath, loadConfig } from '../configuration/io.js';
7
- import { getConfigSchema } from '../configuration/schema.js';
8
- import { getConfigLabel } from './config-labels.js';
9
- import { defaultFileSystem } from './filesystem.js';
10
- import { getConfirmationMessage } from './messages.js';
11
- import { StepType } from '../ui/Config.js';
12
- export function createWelcomeDefinition(app) {
13
- return {
14
- id: randomUUID(),
15
- name: ComponentName.Welcome,
16
- props: { app },
17
- status: ComponentStatus.Awaiting,
18
- };
19
- }
20
- export function createConfigSteps() {
21
- // Use schema-based config step generation for required Anthropic settings
22
- return createConfigStepsFromSchema(['anthropic.key', 'anthropic.model']);
23
- }
24
4
  /**
25
- * Get current config value for a dotted key path
5
+ * Shared component creation utility
26
6
  */
27
- function getConfigValue(config, key) {
28
- if (!config)
29
- return undefined;
30
- const parts = key.split('.');
31
- let value = config;
32
- for (const part of parts) {
33
- if (value && typeof value === 'object' && part in value) {
34
- value = value[part];
35
- }
36
- else {
37
- return undefined;
38
- }
39
- }
40
- return value;
41
- }
7
+ const createComponent = (name, props, state, status = ComponentStatus.Awaiting) => ({
8
+ id: randomUUID(),
9
+ name,
10
+ props,
11
+ ...(state !== undefined ? { state } : {}),
12
+ status,
13
+ });
42
14
  /**
43
- * Get validation function for a config definition
15
+ * Create a simple component without state
44
16
  */
45
- function getValidator(definition) {
46
- switch (definition.type) {
47
- case ConfigDefinitionType.RegExp:
48
- return (value) => definition.pattern.test(value);
49
- case ConfigDefinitionType.String:
50
- return () => true; // Strings are always valid
51
- case ConfigDefinitionType.Enum:
52
- return (value) => definition.values.includes(value);
53
- case ConfigDefinitionType.Number:
54
- return (value) => !isNaN(Number(value));
55
- case ConfigDefinitionType.Boolean:
56
- return (value) => value === 'true' || value === 'false';
57
- }
58
- }
17
+ const createSimpleComponent = (name, props, status) => createComponent(name, props, undefined, status);
59
18
  /**
60
- * Create config steps from schema for specified keys
19
+ * Create a managed component with state
61
20
  */
62
- export function createConfigStepsFromSchema(keys, fs = defaultFileSystem) {
63
- const schema = getConfigSchema();
64
- let currentConfig = null;
65
- let rawConfig = null;
66
- // Load validated config (may fail if config has validation errors)
67
- try {
68
- currentConfig = loadConfig(fs);
69
- }
70
- catch {
71
- // Config doesn't exist or has validation errors, use defaults
72
- }
73
- // Load raw config separately (for discovered keys not in schema)
74
- try {
75
- const configFile = getConfigPath();
76
- if (fs.exists(configFile)) {
77
- const content = fs.readFile(configFile, 'utf-8');
78
- rawConfig = parseYaml(content);
79
- }
80
- }
81
- catch {
82
- // Config file doesn't exist or can't be parsed
83
- }
84
- return keys.map((key) => {
85
- // Check if key is in schema (system config)
86
- if (!(key in schema)) {
87
- // Key is not in schema - it's from a skill or discovered config
88
- // Create a simple text step with cached label or full path as description
89
- const keyParts = key.split('.');
90
- const shortKey = keyParts[keyParts.length - 1];
91
- // Load current value if it exists (use rawConfig since discovered keys aren't in validated config)
92
- const currentValue = getConfigValue(rawConfig, key);
93
- const value = currentValue !== undefined && typeof currentValue === 'string'
94
- ? currentValue
95
- : null;
96
- // Use cached label if available, fallback to key path
97
- const cachedLabel = getConfigLabel(key, fs);
98
- return {
99
- description: cachedLabel ?? key,
100
- key: shortKey,
101
- path: key,
102
- type: StepType.Text,
103
- value,
104
- validate: () => true, // Accept any string for now
105
- };
106
- }
107
- const definition = schema[key];
108
- const currentValue = getConfigValue(currentConfig, key);
109
- const keyParts = key.split('.');
110
- const shortKey = keyParts[keyParts.length - 1];
111
- // Map definition to ConfigStep based on type
112
- switch (definition.type) {
113
- case ConfigDefinitionType.RegExp:
114
- case ConfigDefinitionType.String: {
115
- const value = currentValue !== undefined && typeof currentValue === 'string'
116
- ? currentValue
117
- : definition.type === ConfigDefinitionType.String
118
- ? (definition.default ?? '')
119
- : null;
120
- return {
121
- description: definition.description,
122
- key: shortKey,
123
- path: key,
124
- type: StepType.Text,
125
- value,
126
- validate: getValidator(definition),
127
- };
128
- }
129
- case ConfigDefinitionType.Number: {
130
- const value = currentValue !== undefined && typeof currentValue === 'number'
131
- ? String(currentValue)
132
- : definition.default !== undefined
133
- ? String(definition.default)
134
- : '0';
135
- return {
136
- description: definition.description,
137
- key: shortKey,
138
- path: key,
139
- type: StepType.Text,
140
- value,
141
- validate: getValidator(definition),
142
- };
143
- }
144
- case ConfigDefinitionType.Enum: {
145
- const currentStr = currentValue !== undefined && typeof currentValue === 'string'
146
- ? currentValue
147
- : definition.default;
148
- const defaultIndex = currentStr
149
- ? definition.values.indexOf(currentStr)
150
- : 0;
151
- return {
152
- description: definition.description,
153
- key: shortKey,
154
- path: key,
155
- type: StepType.Selection,
156
- options: definition.values.map((value) => ({
157
- label: value,
158
- value,
159
- })),
160
- defaultIndex: Math.max(0, defaultIndex),
161
- validate: getValidator(definition),
162
- };
163
- }
164
- case ConfigDefinitionType.Boolean: {
165
- const currentBool = currentValue !== undefined && typeof currentValue === 'boolean'
166
- ? currentValue
167
- : undefined;
168
- return {
169
- description: definition.description,
170
- key: shortKey,
171
- path: key,
172
- type: StepType.Selection,
173
- options: [
174
- { label: 'yes', value: 'true' },
175
- { label: 'no', value: 'false' },
176
- ],
177
- defaultIndex: currentBool !== undefined ? (currentBool ? 0 : 1) : 0,
178
- validate: getValidator(definition),
179
- };
180
- }
181
- }
182
- });
183
- }
184
- export function createConfigDefinition(onFinished, onAborted) {
185
- return {
186
- id: randomUUID(),
187
- name: ComponentName.Config,
188
- status: ComponentStatus.Awaiting,
189
- state: {
190
- values: {},
191
- completedStep: 0,
192
- selectedIndex: 0,
193
- },
194
- props: {
195
- steps: createConfigSteps(),
196
- onFinished,
197
- onAborted,
198
- },
199
- };
200
- }
21
+ const createManagedComponent = (name, props, state, status) => createComponent(name, props, state, status);
201
22
  /**
202
- * Create config definition with specific keys
23
+ * Initial state constants for managed components
203
24
  */
204
- export function createConfigDefinitionWithKeys(keys, onFinished, onAborted) {
205
- return {
206
- id: randomUUID(),
207
- name: ComponentName.Config,
208
- status: ComponentStatus.Awaiting,
209
- state: {
210
- values: {},
211
- completedStep: 0,
212
- selectedIndex: 0,
213
- },
214
- props: {
215
- steps: createConfigStepsFromSchema(keys),
216
- onFinished,
217
- onAborted,
218
- },
219
- };
220
- }
221
- export function createCommandDefinition(command, service) {
222
- return {
223
- id: randomUUID(),
224
- name: ComponentName.Command,
225
- status: ComponentStatus.Awaiting,
226
- state: {
227
- error: null,
228
- message: null,
229
- tasks: [],
230
- },
231
- props: {
232
- command,
233
- service,
234
- },
235
- };
236
- }
237
- export function createScheduleDefinition(message, tasks, onSelectionConfirmed) {
238
- return {
239
- id: randomUUID(),
240
- name: ComponentName.Schedule,
241
- status: ComponentStatus.Awaiting,
242
- state: {
243
- highlightedIndex: null,
244
- currentDefineGroupIndex: 0,
245
- completedSelections: [],
246
- },
247
- props: {
248
- message,
249
- tasks,
250
- onSelectionConfirmed,
251
- },
252
- };
253
- }
254
- export function createFeedback(type, ...messages) {
255
- return {
256
- id: randomUUID(),
257
- name: ComponentName.Feedback,
258
- props: {
259
- type,
260
- message: messages.join('\n\n'),
261
- },
262
- status: ComponentStatus.Awaiting,
263
- };
264
- }
265
- export function createMessage(text) {
266
- return {
267
- id: randomUUID(),
268
- name: ComponentName.Message,
269
- props: {
270
- text,
271
- },
272
- status: ComponentStatus.Awaiting,
273
- };
274
- }
275
- export function createDebugDefinition(title, content, color) {
276
- return {
277
- id: randomUUID(),
278
- name: ComponentName.Debug,
279
- props: {
280
- title,
281
- content,
282
- color,
283
- },
284
- status: ComponentStatus.Awaiting,
285
- };
286
- }
287
- export function createRefinement(text, onAborted) {
288
- return {
289
- id: randomUUID(),
290
- name: ComponentName.Refinement,
291
- status: ComponentStatus.Awaiting,
292
- state: {},
293
- props: {
294
- text,
295
- onAborted,
296
- },
297
- };
298
- }
299
- export function createConfirmDefinition(onConfirmed, onCancelled) {
300
- return {
301
- id: randomUUID(),
302
- name: ComponentName.Confirm,
303
- status: ComponentStatus.Awaiting,
304
- state: {
305
- confirmed: false,
306
- selectedIndex: 0,
307
- },
308
- props: {
309
- message: getConfirmationMessage(),
310
- onConfirmed,
311
- onCancelled,
312
- },
313
- };
314
- }
315
- export function createIntrospectDefinition(tasks, service) {
316
- return {
317
- id: randomUUID(),
318
- name: ComponentName.Introspect,
319
- status: ComponentStatus.Awaiting,
320
- state: {
321
- error: null,
322
- capabilities: [],
323
- message: null,
324
- },
325
- props: {
326
- tasks,
327
- service,
328
- },
329
- };
330
- }
331
- export function createReportDefinition(message, capabilities) {
332
- return {
333
- id: randomUUID(),
334
- name: ComponentName.Report,
335
- props: {
336
- message,
337
- capabilities,
338
- },
339
- status: ComponentStatus.Awaiting,
340
- };
341
- }
342
- export function createAnswerDefinition(question, service) {
343
- return {
344
- id: randomUUID(),
345
- name: ComponentName.Answer,
346
- status: ComponentStatus.Awaiting,
347
- state: {
348
- error: null,
349
- answer: null,
350
- },
351
- props: {
352
- question,
353
- service,
354
- },
355
- };
356
- }
357
- export function isSimple(component) {
358
- return !('state' in component);
359
- }
25
+ const InitialConfigState = {
26
+ values: {},
27
+ completedStep: 0,
28
+ selectedIndex: 0,
29
+ };
30
+ const InitialCommandState = {
31
+ error: null,
32
+ message: null,
33
+ tasks: [],
34
+ };
35
+ const InitialScheduleState = {
36
+ highlightedIndex: null,
37
+ currentDefineGroupIndex: 0,
38
+ completedSelections: [],
39
+ };
40
+ const InitialRefinementState = {};
41
+ const InitialConfirmState = {
42
+ confirmed: false,
43
+ selectedIndex: 0,
44
+ };
45
+ const InitialIntrospectState = {
46
+ error: null,
47
+ capabilities: [],
48
+ message: null,
49
+ };
50
+ const InitialAnswerState = {
51
+ error: null,
52
+ answer: null,
53
+ };
54
+ const InitialExecuteState = {
55
+ error: null,
56
+ message: '',
57
+ summary: '',
58
+ tasks: [],
59
+ completionMessage: null,
60
+ };
61
+ const InitialValidateState = {
62
+ error: null,
63
+ completionMessage: null,
64
+ configRequirements: [],
65
+ validated: false,
66
+ };
360
67
  /**
361
- * Mark a component as done. Returns the component to be added to timeline.
362
- * Components use handlers.updateState to save their state before completion,
363
- * so this function sets the status to Done and returns the updated component.
68
+ * Create a welcome component that displays application information
364
69
  */
365
- export function markAsDone(component) {
366
- return { ...component, status: ComponentStatus.Done };
367
- }
368
- export function createExecuteDefinition(tasks, service) {
369
- return {
370
- id: randomUUID(),
371
- name: ComponentName.Execute,
372
- status: ComponentStatus.Awaiting,
373
- state: {
374
- error: null,
375
- message: '',
376
- summary: '',
377
- tasks: [],
378
- completed: 0,
379
- completionMessage: null,
380
- },
381
- props: {
382
- tasks,
383
- service,
384
- },
385
- };
386
- }
387
- export function createValidateDefinition(missingConfig, userRequest, service, onError, onValidationComplete, onAborted) {
388
- return {
389
- id: randomUUID(),
390
- name: ComponentName.Validate,
391
- status: ComponentStatus.Awaiting,
392
- state: {
393
- error: null,
394
- completionMessage: null,
395
- configRequirements: [],
396
- validated: false,
397
- },
398
- props: {
399
- missingConfig,
400
- userRequest,
401
- service,
402
- onError,
403
- onValidationComplete,
404
- onAborted,
405
- },
406
- };
407
- }
70
+ export const createWelcome = (props, status) => createSimpleComponent(ComponentName.Welcome, props, status);
71
+ /**
72
+ * Create a feedback component that displays status messages
73
+ */
74
+ export const createFeedback = (props, status) => createSimpleComponent(ComponentName.Feedback, props, status);
75
+ /**
76
+ * Create a message component that displays informational text
77
+ */
78
+ export const createMessage = (props, status) => createSimpleComponent(ComponentName.Message, props, status);
79
+ /**
80
+ * Create a debug component that displays diagnostic information
81
+ */
82
+ export const createDebug = (props, status) => createSimpleComponent(ComponentName.Debug, props, status);
83
+ /**
84
+ * Create a report component that displays capability listings
85
+ */
86
+ export const createReport = (props, status) => createSimpleComponent(ComponentName.Report, props, status);
87
+ /**
88
+ * Create a configuration component for multi-step user input
89
+ */
90
+ export const createConfig = (props, status) => createManagedComponent(ComponentName.Config, props, InitialConfigState, status);
91
+ /**
92
+ * Create a command component that processes user requests via LLM
93
+ */
94
+ export const createCommand = (props, status) => createManagedComponent(ComponentName.Command, props, InitialCommandState, status);
95
+ /**
96
+ * Create a schedule component that displays and manages task execution plans
97
+ */
98
+ export const createSchedule = (props, status) => createManagedComponent(ComponentName.Schedule, props, InitialScheduleState, status);
99
+ /**
100
+ * Create a refinement component for interactive task selection
101
+ */
102
+ export const createRefinement = (props, status) => createManagedComponent(ComponentName.Refinement, props, InitialRefinementState, status);
103
+ /**
104
+ * Create a confirmation component that prompts user for yes/no decisions
105
+ */
106
+ export const createConfirm = (props, status) => createManagedComponent(ComponentName.Confirm, props, InitialConfirmState, status);
107
+ /**
108
+ * Create an introspect component that lists available capabilities
109
+ */
110
+ export const createIntrospect = (props, status) => createManagedComponent(ComponentName.Introspect, props, InitialIntrospectState, status);
111
+ /**
112
+ * Create an answer component that responds to information requests via LLM
113
+ */
114
+ export const createAnswer = (props, status) => createManagedComponent(ComponentName.Answer, props, InitialAnswerState, status);
115
+ /**
116
+ * Create an execute component that runs shell commands and processes operations
117
+ */
118
+ export const createExecute = (props, status) => createManagedComponent(ComponentName.Execute, props, InitialExecuteState, status);
119
+ /**
120
+ * Create a validate component that checks and collects missing configuration
121
+ */
122
+ export const createValidate = (props, status) => createManagedComponent(ComponentName.Validate, props, InitialValidateState, status);
@@ -1,5 +1,5 @@
1
1
  import { DebugLevel } from '../configuration/types.js';
2
- import { createDebugDefinition } from './components.js';
2
+ import { createDebug } from './components.js';
3
3
  import { loadDebugSetting } from '../configuration/io.js';
4
4
  import { Palette } from './colors.js';
5
5
  /**
@@ -68,7 +68,7 @@ export function logPrompt(toolName, command, instructions) {
68
68
  const lines = instructions.split('\n').length;
69
69
  const bytes = Buffer.byteLength(instructions, 'utf-8');
70
70
  const title = `SYSTEM PROMPT (${String(lines)} lines, ${String(bytes)} bytes)`;
71
- return createDebugDefinition(title, content, Palette.Gray);
71
+ return createDebug({ title, content, color: Palette.Gray });
72
72
  }
73
73
  /**
74
74
  * Create debug component for LLM responses received
@@ -85,5 +85,5 @@ export function logResponse(toolName, response, durationMs) {
85
85
  JSON.stringify(response, null, 2),
86
86
  ].join('\n');
87
87
  const title = `LLM RESPONSE (${String(durationMs)} ms)`;
88
- return createDebugDefinition(title, content, Palette.AshGray);
88
+ return createDebug({ title, content, color: Palette.AshGray });
89
89
  }
@@ -8,8 +8,11 @@ import { routeTasksWithConfirm } from './router.js';
8
8
  */
9
9
  export async function handleRefinement(selectedTasks, service, originalCommand, lifecycleHandlers, workflowHandlers, requestHandlers) {
10
10
  // Create and add refinement component to queue
11
- const refinementDef = createRefinement(getRefiningMessage(), (operation) => {
12
- requestHandlers.onAborted(operation);
11
+ const refinementDef = createRefinement({
12
+ text: getRefiningMessage(),
13
+ onAborted: (operation) => {
14
+ requestHandlers.onAborted(operation);
15
+ },
13
16
  });
14
17
  workflowHandlers.addToQueue(refinementDef);
15
18
  try {