prompt-language-shell 0.8.0 → 0.8.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.
@@ -1,9 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useState } from 'react';
2
+ import { useCallback, useEffect, useReducer } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { ComponentStatus, } from '../types/components.js';
5
5
  import { Colors, getTextColor } from '../services/colors.js';
6
- import { addDebugToTimeline } from '../services/components.js';
7
6
  import { useInput } from '../services/keyboard.js';
8
7
  import { loadUserConfig } from '../services/loader.js';
9
8
  import { formatErrorMessage } from '../services/messages.js';
@@ -14,41 +13,184 @@ import { formatDuration } from '../services/utils.js';
14
13
  import { Spinner } from './Spinner.js';
15
14
  import { Task } from './Task.js';
16
15
  const MINIMUM_PROCESSING_TIME = 400;
17
- export function Execute({ tasks, state, status, service, handlers, }) {
16
+ /**
17
+ * Validates that all placeholders in a command have been resolved.
18
+ * Throws an error if unresolved placeholders are found.
19
+ */
20
+ function validatePlaceholderResolution(command, original) {
21
+ const unresolvedPattern = /\{[^}]+\}/g;
22
+ const matches = command.match(unresolvedPattern);
23
+ if (matches && matches.length > 0) {
24
+ throw new Error(`Unresolved placeholders in command: ${matches.join(', ')}\nCommand: ${original}`);
25
+ }
26
+ }
27
+ function executeReducer(state, action) {
28
+ switch (action.type) {
29
+ case 'PROCESSING_COMPLETE':
30
+ return {
31
+ ...state,
32
+ message: action.payload.message,
33
+ hasProcessed: true,
34
+ };
35
+ case 'COMMANDS_READY':
36
+ return {
37
+ ...state,
38
+ message: action.payload.message,
39
+ summary: action.payload.summary,
40
+ taskInfos: action.payload.taskInfos,
41
+ completed: 0,
42
+ };
43
+ case 'PROCESSING_ERROR':
44
+ return {
45
+ ...state,
46
+ error: action.payload.error,
47
+ hasProcessed: true,
48
+ };
49
+ case 'TASK_COMPLETE': {
50
+ const updatedTimes = [
51
+ ...state.taskExecutionTimes,
52
+ action.payload.elapsed,
53
+ ];
54
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
55
+ ? {
56
+ ...task,
57
+ status: ExecutionStatus.Success,
58
+ elapsed: action.payload.elapsed,
59
+ }
60
+ : task);
61
+ return {
62
+ ...state,
63
+ taskInfos: updatedTaskInfos,
64
+ taskExecutionTimes: updatedTimes,
65
+ completed: action.payload.index + 1,
66
+ };
67
+ }
68
+ case 'ALL_TASKS_COMPLETE': {
69
+ const updatedTimes = [
70
+ ...state.taskExecutionTimes,
71
+ action.payload.elapsed,
72
+ ];
73
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
74
+ ? {
75
+ ...task,
76
+ status: ExecutionStatus.Success,
77
+ elapsed: action.payload.elapsed,
78
+ }
79
+ : task);
80
+ const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
81
+ const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
82
+ return {
83
+ ...state,
84
+ taskInfos: updatedTaskInfos,
85
+ taskExecutionTimes: updatedTimes,
86
+ completed: action.payload.index + 1,
87
+ completionMessage: completion,
88
+ };
89
+ }
90
+ case 'TASK_ERROR_CRITICAL': {
91
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
92
+ ? { ...task, status: ExecutionStatus.Failed, elapsed: 0 }
93
+ : task);
94
+ return {
95
+ ...state,
96
+ taskInfos: updatedTaskInfos,
97
+ error: action.payload.error,
98
+ };
99
+ }
100
+ case 'TASK_ERROR_CONTINUE': {
101
+ const updatedTimes = [
102
+ ...state.taskExecutionTimes,
103
+ action.payload.elapsed,
104
+ ];
105
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
106
+ ? {
107
+ ...task,
108
+ status: ExecutionStatus.Failed,
109
+ elapsed: action.payload.elapsed,
110
+ }
111
+ : task);
112
+ return {
113
+ ...state,
114
+ taskInfos: updatedTaskInfos,
115
+ taskExecutionTimes: updatedTimes,
116
+ completed: action.payload.index + 1,
117
+ };
118
+ }
119
+ case 'LAST_TASK_ERROR': {
120
+ const updatedTimes = [
121
+ ...state.taskExecutionTimes,
122
+ action.payload.elapsed,
123
+ ];
124
+ const updatedTaskInfos = state.taskInfos.map((task, i) => i === action.payload.index
125
+ ? {
126
+ ...task,
127
+ status: ExecutionStatus.Failed,
128
+ elapsed: action.payload.elapsed,
129
+ }
130
+ : task);
131
+ const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
132
+ const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
133
+ return {
134
+ ...state,
135
+ taskInfos: updatedTaskInfos,
136
+ taskExecutionTimes: updatedTimes,
137
+ completed: action.payload.index + 1,
138
+ completionMessage: completion,
139
+ };
140
+ }
141
+ case 'CANCEL_EXECUTION': {
142
+ const updatedTaskInfos = state.taskInfos.map((task, taskIndex) => {
143
+ if (taskIndex < action.payload.completed) {
144
+ return { ...task, status: ExecutionStatus.Success };
145
+ }
146
+ else if (taskIndex === action.payload.completed) {
147
+ return { ...task, status: ExecutionStatus.Aborted };
148
+ }
149
+ else {
150
+ return { ...task, status: ExecutionStatus.Cancelled };
151
+ }
152
+ });
153
+ return {
154
+ ...state,
155
+ taskInfos: updatedTaskInfos,
156
+ };
157
+ }
158
+ default:
159
+ return state;
160
+ }
161
+ }
162
+ export function Execute({ tasks, state, status, service, stateHandlers, lifecycleHandlers, errorHandlers, workflowHandlers, }) {
18
163
  const isActive = status === ComponentStatus.Active;
19
- const [error, setError] = useState(state?.error ?? null);
20
- const [taskInfos, setTaskInfos] = useState(state?.taskInfos ?? []);
21
- const [message, setMessage] = useState(state?.message ?? '');
22
- const [completed, setCompleted] = useState(state?.completed ?? 0);
23
- const [hasProcessed, setHasProcessed] = useState(false);
24
- const [taskExecutionTimes, setTaskExecutionTimes] = useState(state?.taskExecutionTimes ?? []);
25
- const [completionMessage, setCompletionMessage] = useState(state?.completionMessage ?? null);
26
- const [summary, setSummary] = useState(state?.summary ?? '');
164
+ const [localState, dispatch] = useReducer(executeReducer, {
165
+ error: state?.error ?? null,
166
+ taskInfos: state?.taskInfos ?? [],
167
+ message: state?.message ?? '',
168
+ completed: state?.completed ?? 0,
169
+ hasProcessed: false,
170
+ taskExecutionTimes: state?.taskExecutionTimes ?? [],
171
+ completionMessage: state?.completionMessage ?? null,
172
+ summary: state?.summary ?? '',
173
+ });
174
+ const { error, taskInfos, message, completed, hasProcessed, taskExecutionTimes, completionMessage, summary, } = localState;
27
175
  // Derive loading state from current conditions
28
176
  const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
29
177
  const isExecuting = completed < taskInfos.length;
30
178
  // Handle cancel with useCallback to ensure we capture latest state
31
179
  const handleCancel = useCallback(() => {
32
- // Mark tasks based on their status relative to completed:
33
- // - Before completed: finished (Success)
34
- // - At completed: interrupted (Aborted)
35
- // - After completed: never started (Cancelled)
180
+ dispatch({ type: 'CANCEL_EXECUTION', payload: { completed } });
181
+ // Get updated task infos after cancel
36
182
  const updatedTaskInfos = taskInfos.map((task, taskIndex) => {
37
183
  if (taskIndex < completed) {
38
- // Tasks that completed before interruption
39
184
  return { ...task, status: ExecutionStatus.Success };
40
185
  }
41
186
  else if (taskIndex === completed) {
42
- // Task that was running when interrupted
43
187
  return { ...task, status: ExecutionStatus.Aborted };
44
188
  }
45
189
  else {
46
- // Tasks that haven't started yet
47
190
  return { ...task, status: ExecutionStatus.Cancelled };
48
191
  }
49
192
  });
50
- setTaskInfos(updatedTaskInfos);
51
- handlers?.updateState({
193
+ stateHandlers?.updateState({
52
194
  message,
53
195
  summary,
54
196
  taskInfos: updatedTaskInfos,
@@ -57,8 +199,16 @@ export function Execute({ tasks, state, status, service, handlers, }) {
57
199
  completionMessage: null,
58
200
  error: null,
59
201
  });
60
- handlers?.onAborted('execution');
61
- }, [message, summary, taskInfos, completed, taskExecutionTimes, handlers]);
202
+ errorHandlers?.onAborted('execution');
203
+ }, [
204
+ message,
205
+ summary,
206
+ taskInfos,
207
+ completed,
208
+ taskExecutionTimes,
209
+ stateHandlers,
210
+ errorHandlers,
211
+ ]);
62
212
  useInput((_, key) => {
63
213
  if (key.escape && (isLoading || isExecuting) && isActive) {
64
214
  handleCancel();
@@ -69,10 +219,6 @@ export function Execute({ tasks, state, status, service, handlers, }) {
69
219
  if (!isActive || taskInfos.length > 0 || hasProcessed) {
70
220
  return;
71
221
  }
72
- if (!service) {
73
- setError('No service available');
74
- return;
75
- }
76
222
  let mounted = true;
77
223
  async function process(svc) {
78
224
  const startTime = Date.now();
@@ -95,10 +241,15 @@ export function Execute({ tasks, state, status, service, handlers, }) {
95
241
  if (!mounted)
96
242
  return;
97
243
  // Add debug components to timeline if present
98
- addDebugToTimeline(result.debug, handlers);
244
+ if (result.debug?.length) {
245
+ workflowHandlers?.addToTimeline(...result.debug);
246
+ }
99
247
  if (!result.commands || result.commands.length === 0) {
100
- setHasProcessed(true);
101
- handlers?.updateState({
248
+ dispatch({
249
+ type: 'PROCESSING_COMPLETE',
250
+ payload: { message: result.message },
251
+ });
252
+ stateHandlers?.updateState({
102
253
  message: result.message,
103
254
  summary: '',
104
255
  taskInfos: [],
@@ -107,14 +258,15 @@ export function Execute({ tasks, state, status, service, handlers, }) {
107
258
  completionMessage: null,
108
259
  error: null,
109
260
  });
110
- handlers?.completeActive();
261
+ lifecycleHandlers?.completeActive();
111
262
  return;
112
263
  }
113
264
  // Resolve placeholders in command strings
114
- const resolvedCommands = result.commands.map((cmd) => ({
115
- ...cmd,
116
- command: replacePlaceholders(cmd.command, userConfig),
117
- }));
265
+ const resolvedCommands = result.commands.map((cmd) => {
266
+ const resolved = replacePlaceholders(cmd.command, userConfig);
267
+ validatePlaceholderResolution(resolved, cmd.command);
268
+ return { ...cmd, command: resolved };
269
+ });
118
270
  // Set message, summary, and create task infos
119
271
  const newMessage = result.message;
120
272
  const newSummary = result.summary || '';
@@ -122,12 +274,16 @@ export function Execute({ tasks, state, status, service, handlers, }) {
122
274
  label: tasks[index]?.action,
123
275
  command: cmd,
124
276
  }));
125
- setMessage(newMessage);
126
- setSummary(newSummary);
127
- setTaskInfos(infos);
128
- setCompleted(0); // Start with first task
277
+ dispatch({
278
+ type: 'COMMANDS_READY',
279
+ payload: {
280
+ message: newMessage,
281
+ summary: newSummary,
282
+ taskInfos: infos,
283
+ },
284
+ });
129
285
  // Update state after AI processing
130
- handlers?.updateState({
286
+ stateHandlers?.updateState({
131
287
  message: newMessage,
132
288
  summary: newSummary,
133
289
  taskInfos: infos,
@@ -141,9 +297,11 @@ export function Execute({ tasks, state, status, service, handlers, }) {
141
297
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
142
298
  if (mounted) {
143
299
  const errorMessage = formatErrorMessage(err);
144
- setError(errorMessage);
145
- setHasProcessed(true);
146
- handlers?.updateState({
300
+ dispatch({
301
+ type: 'PROCESSING_ERROR',
302
+ payload: { error: errorMessage },
303
+ });
304
+ stateHandlers?.updateState({
147
305
  message: '',
148
306
  summary: '',
149
307
  taskInfos: [],
@@ -152,28 +310,35 @@ export function Execute({ tasks, state, status, service, handlers, }) {
152
310
  completionMessage: null,
153
311
  error: errorMessage,
154
312
  });
155
- handlers?.onError(errorMessage);
313
+ errorHandlers?.onError(errorMessage);
156
314
  }
157
315
  }
158
316
  }
159
- process(service);
317
+ void process(service);
160
318
  return () => {
161
319
  mounted = false;
162
320
  };
163
- }, [tasks, isActive, service, handlers, taskInfos.length, hasProcessed]);
321
+ }, [
322
+ tasks,
323
+ isActive,
324
+ service,
325
+ stateHandlers,
326
+ lifecycleHandlers,
327
+ workflowHandlers,
328
+ errorHandlers,
329
+ taskInfos.length,
330
+ hasProcessed,
331
+ ]);
164
332
  // Handle task completion - move to next task
165
333
  const handleTaskComplete = useCallback((index, _output, elapsed) => {
166
- const updatedTimes = [...taskExecutionTimes, elapsed];
167
- setTaskExecutionTimes(updatedTimes);
168
- // Update task with elapsed time and success status
169
- const updatedTaskInfos = taskInfos.map((task, i) => i === index
170
- ? { ...task, status: ExecutionStatus.Success, elapsed }
171
- : task);
172
- setTaskInfos(updatedTaskInfos);
173
334
  if (index < taskInfos.length - 1) {
174
335
  // More tasks to execute
175
- setCompleted(index + 1);
176
- handlers?.updateState({
336
+ dispatch({ type: 'TASK_COMPLETE', payload: { index, elapsed } });
337
+ const updatedTimes = [...taskExecutionTimes, elapsed];
338
+ const updatedTaskInfos = taskInfos.map((task, i) => i === index
339
+ ? { ...task, status: ExecutionStatus.Success, elapsed }
340
+ : task);
341
+ stateHandlers?.updateState({
177
342
  message,
178
343
  summary,
179
344
  taskInfos: updatedTaskInfos,
@@ -185,11 +350,18 @@ export function Execute({ tasks, state, status, service, handlers, }) {
185
350
  }
186
351
  else {
187
352
  // All tasks complete
353
+ const summaryText = summary.trim() || 'Execution completed';
354
+ dispatch({
355
+ type: 'ALL_TASKS_COMPLETE',
356
+ payload: { index, elapsed, summaryText },
357
+ });
358
+ const updatedTimes = [...taskExecutionTimes, elapsed];
359
+ const updatedTaskInfos = taskInfos.map((task, i) => i === index
360
+ ? { ...task, status: ExecutionStatus.Success, elapsed }
361
+ : task);
188
362
  const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
189
- const summaryText = summary?.trim() || 'Execution completed';
190
363
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
191
- setCompletionMessage(completion);
192
- handlers?.updateState({
364
+ stateHandlers?.updateState({
193
365
  message,
194
366
  summary,
195
367
  taskInfos: updatedTaskInfos,
@@ -198,21 +370,26 @@ export function Execute({ tasks, state, status, service, handlers, }) {
198
370
  completionMessage: completion,
199
371
  error: null,
200
372
  });
201
- handlers?.completeActive();
373
+ lifecycleHandlers?.completeActive();
202
374
  }
203
- }, [taskInfos, message, handlers, taskExecutionTimes, summary]);
375
+ }, [
376
+ taskInfos,
377
+ message,
378
+ lifecycleHandlers,
379
+ taskExecutionTimes,
380
+ summary,
381
+ stateHandlers,
382
+ ]);
204
383
  const handleTaskError = useCallback((index, error, elapsed) => {
205
384
  const task = taskInfos[index];
206
- const isCritical = task?.command.critical !== false; // Default to true
207
- // Update task with elapsed time and failed status
385
+ const isCritical = task.command.critical !== false; // Default to true
208
386
  const updatedTaskInfos = taskInfos.map((task, i) => i === index
209
387
  ? { ...task, status: ExecutionStatus.Failed, elapsed }
210
388
  : task);
211
- setTaskInfos(updatedTaskInfos);
212
389
  if (isCritical) {
213
390
  // Critical failure - stop execution
214
- setError(error);
215
- handlers?.updateState({
391
+ dispatch({ type: 'TASK_ERROR_CRITICAL', payload: { index, error } });
392
+ stateHandlers?.updateState({
216
393
  message,
217
394
  summary,
218
395
  taskInfos: updatedTaskInfos,
@@ -221,15 +398,17 @@ export function Execute({ tasks, state, status, service, handlers, }) {
221
398
  completionMessage: null,
222
399
  error,
223
400
  });
224
- handlers?.onError(error);
401
+ errorHandlers?.onError(error);
225
402
  }
226
403
  else {
227
404
  // Non-critical failure - continue to next task
228
405
  const updatedTimes = [...taskExecutionTimes, elapsed];
229
- setTaskExecutionTimes(updatedTimes);
230
406
  if (index < taskInfos.length - 1) {
231
- setCompleted(index + 1);
232
- handlers?.updateState({
407
+ dispatch({
408
+ type: 'TASK_ERROR_CONTINUE',
409
+ payload: { index, elapsed },
410
+ });
411
+ stateHandlers?.updateState({
233
412
  message,
234
413
  summary,
235
414
  taskInfos: updatedTaskInfos,
@@ -241,11 +420,14 @@ export function Execute({ tasks, state, status, service, handlers, }) {
241
420
  }
242
421
  else {
243
422
  // Last task, complete execution
423
+ const summaryText = summary.trim() || 'Execution completed';
424
+ dispatch({
425
+ type: 'LAST_TASK_ERROR',
426
+ payload: { index, elapsed, summaryText },
427
+ });
244
428
  const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
245
- const summaryText = summary?.trim() || 'Execution completed';
246
429
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
247
- setCompletionMessage(completion);
248
- handlers?.updateState({
430
+ stateHandlers?.updateState({
249
431
  message,
250
432
  summary,
251
433
  taskInfos: updatedTaskInfos,
@@ -254,14 +436,22 @@ export function Execute({ tasks, state, status, service, handlers, }) {
254
436
  completionMessage: completion,
255
437
  error: null,
256
438
  });
257
- handlers?.completeActive();
439
+ lifecycleHandlers?.completeActive();
258
440
  }
259
441
  }
260
- }, [taskInfos, message, handlers, taskExecutionTimes, summary]);
442
+ }, [
443
+ taskInfos,
444
+ message,
445
+ stateHandlers,
446
+ lifecycleHandlers,
447
+ errorHandlers,
448
+ taskExecutionTimes,
449
+ summary,
450
+ ]);
261
451
  const handleTaskAbort = useCallback((_index) => {
262
452
  // Task was aborted - execution already stopped by Escape handler
263
453
  // Just update state, don't call onAborted (already called at Execute level)
264
- handlers?.updateState({
454
+ stateHandlers?.updateState({
265
455
  message,
266
456
  summary,
267
457
  taskInfos,
@@ -270,7 +460,7 @@ export function Execute({ tasks, state, status, service, handlers, }) {
270
460
  completionMessage: null,
271
461
  error: null,
272
462
  });
273
- }, [taskInfos, message, summary, completed, taskExecutionTimes, handlers]);
463
+ }, [taskInfos, message, summary, completed, taskExecutionTimes, stateHandlers]);
274
464
  // Return null only when loading completes with no commands
275
465
  if (!isActive && taskInfos.length === 0 && !error) {
276
466
  return null;
@@ -5,6 +5,7 @@ import { getFeedbackColor } from '../services/colors.js';
5
5
  function getSymbol(type) {
6
6
  return {
7
7
  [FeedbackType.Info]: 'ℹ',
8
+ [FeedbackType.Warning]: '⚠',
8
9
  [FeedbackType.Succeeded]: '✓',
9
10
  [FeedbackType.Aborted]: '⊘',
10
11
  [FeedbackType.Failed]: '✗',
@@ -1,64 +1,22 @@
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
+ import { ComponentStatus } from '../types/components.js';
5
5
  import { Colors, getTextColor } from '../services/colors.js';
6
- import { addDebugToTimeline, createReportDefinition, } from '../services/components.js';
6
+ import { createReportDefinition } from '../services/components.js';
7
7
  import { DebugLevel } from '../services/configuration.js';
8
8
  import { useInput } from '../services/keyboard.js';
9
9
  import { formatErrorMessage } from '../services/messages.js';
10
10
  import { ensureMinimumTime } from '../services/timing.js';
11
11
  import { Spinner } from './Spinner.js';
12
12
  const MIN_PROCESSING_TIME = 1000;
13
- const BUILT_IN_CAPABILITIES = new Set([
14
- 'CONFIGURE',
15
- 'SCHEDULE',
16
- 'INTROSPECT',
17
- 'ANSWER',
18
- 'EXECUTE',
19
- 'VALIDATE',
20
- 'REPORT',
21
- ]);
22
- const INDIRECT_CAPABILITIES = new Set(['SCHEDULE', 'VALIDATE', 'REPORT']);
23
- function parseCapabilityFromTask(task) {
24
- // Parse "NAME: Description" format from task.action
25
- const colonIndex = task.action.indexOf(':');
26
- if (colonIndex === -1) {
27
- const upperName = task.action.toUpperCase();
28
- // Check for status markers
29
- const isIncomplete = task.action.includes('(INCOMPLETE)');
30
- const cleanName = task.action.replace(/\s*\(INCOMPLETE\)\s*/gi, '').trim();
31
- return {
32
- name: cleanName,
33
- description: '',
34
- isBuiltIn: BUILT_IN_CAPABILITIES.has(upperName),
35
- isIndirect: INDIRECT_CAPABILITIES.has(upperName),
36
- isIncomplete,
37
- };
38
- }
39
- const name = task.action.substring(0, colonIndex).trim();
40
- const description = task.action.substring(colonIndex + 1).trim();
41
- // Check for status markers
42
- const isIncomplete = name.includes('(INCOMPLETE)');
43
- const cleanName = name.replace(/\s*\(INCOMPLETE\)\s*/gi, '').trim();
44
- const upperName = cleanName.toUpperCase();
45
- const isBuiltIn = BUILT_IN_CAPABILITIES.has(upperName);
46
- const isIndirect = INDIRECT_CAPABILITIES.has(upperName);
47
- return {
48
- name: cleanName,
49
- description,
50
- isBuiltIn,
51
- isIndirect,
52
- isIncomplete,
53
- };
54
- }
55
- export function Introspect({ tasks, state, status, service, children, debug = DebugLevel.None, handlers, }) {
13
+ export function Introspect({ tasks, state: _state, status, service, children, debug = DebugLevel.None, stateHandlers, lifecycleHandlers, queueHandlers, errorHandlers, workflowHandlers, }) {
56
14
  const isActive = status === ComponentStatus.Active;
57
15
  // isActive passed as prop
58
16
  const [error, setError] = useState(null);
59
17
  useInput((input, key) => {
60
18
  if (key.escape && isActive) {
61
- handlers?.onAborted('introspection');
19
+ errorHandlers?.onAborted('introspection');
62
20
  }
63
21
  }, { isActive });
64
22
  useEffect(() => {
@@ -66,11 +24,6 @@ export function Introspect({ tasks, state, status, service, children, debug = De
66
24
  if (!isActive) {
67
25
  return;
68
26
  }
69
- // Skip processing if no service available
70
- if (!service) {
71
- setError('No service available');
72
- return;
73
- }
74
27
  let mounted = true;
75
28
  async function process(svc) {
76
29
  const startTime = Date.now();
@@ -82,9 +35,11 @@ export function Introspect({ tasks, state, status, service, children, debug = De
82
35
  await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
83
36
  if (mounted) {
84
37
  // Add debug components to timeline if present
85
- addDebugToTimeline(result.debug, handlers);
86
- // Parse capabilities from returned tasks
87
- let capabilities = result.tasks.map(parseCapabilityFromTask);
38
+ if (result.debug?.length) {
39
+ workflowHandlers?.addToTimeline(...result.debug);
40
+ }
41
+ // Capabilities come directly from result - no parsing needed
42
+ let capabilities = result.capabilities;
88
43
  // Filter out internal capabilities when not in debug mode
89
44
  if (debug === DebugLevel.None) {
90
45
  capabilities = capabilities.filter((cap) => cap.name.toUpperCase() !== 'SCHEDULE' &&
@@ -92,14 +47,14 @@ export function Introspect({ tasks, state, status, service, children, debug = De
92
47
  cap.name.toUpperCase() !== 'REPORT');
93
48
  }
94
49
  // Save state before completing
95
- handlers?.updateState({
50
+ stateHandlers?.updateState({
96
51
  capabilities,
97
52
  message: result.message,
98
53
  });
99
54
  // Add Report component to queue
100
- handlers?.addToQueue(createReportDefinition(result.message, capabilities));
55
+ queueHandlers?.addToQueue(createReportDefinition(result.message, capabilities));
101
56
  // Signal completion
102
- handlers?.completeActive();
57
+ lifecycleHandlers?.completeActive();
103
58
  }
104
59
  }
105
60
  catch (err) {
@@ -108,18 +63,18 @@ export function Introspect({ tasks, state, status, service, children, debug = De
108
63
  const errorMessage = formatErrorMessage(err);
109
64
  setError(errorMessage);
110
65
  // Save error state
111
- handlers?.updateState({
66
+ stateHandlers?.updateState({
112
67
  error: errorMessage,
113
68
  });
114
- handlers?.onError(errorMessage);
69
+ errorHandlers?.onError(errorMessage);
115
70
  }
116
71
  }
117
72
  }
118
- process(service);
73
+ void process(service);
119
74
  return () => {
120
75
  mounted = false;
121
76
  };
122
- }, [tasks, isActive, service, debug, handlers]);
77
+ }, [tasks, isActive, service, debug]);
123
78
  // Don't render wrapper when done and nothing to show
124
79
  if (!isActive && !error && !children) {
125
80
  return null;