prompt-language-shell 0.8.2 → 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();
@@ -91,10 +241,15 @@ export function Execute({ tasks, state, status, service, handlers, }) {
91
241
  if (!mounted)
92
242
  return;
93
243
  // Add debug components to timeline if present
94
- addDebugToTimeline(result.debug, handlers);
244
+ if (result.debug?.length) {
245
+ workflowHandlers?.addToTimeline(...result.debug);
246
+ }
95
247
  if (!result.commands || result.commands.length === 0) {
96
- setHasProcessed(true);
97
- handlers?.updateState({
248
+ dispatch({
249
+ type: 'PROCESSING_COMPLETE',
250
+ payload: { message: result.message },
251
+ });
252
+ stateHandlers?.updateState({
98
253
  message: result.message,
99
254
  summary: '',
100
255
  taskInfos: [],
@@ -103,14 +258,15 @@ export function Execute({ tasks, state, status, service, handlers, }) {
103
258
  completionMessage: null,
104
259
  error: null,
105
260
  });
106
- handlers?.completeActive();
261
+ lifecycleHandlers?.completeActive();
107
262
  return;
108
263
  }
109
264
  // Resolve placeholders in command strings
110
- const resolvedCommands = result.commands.map((cmd) => ({
111
- ...cmd,
112
- command: replacePlaceholders(cmd.command, userConfig),
113
- }));
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
+ });
114
270
  // Set message, summary, and create task infos
115
271
  const newMessage = result.message;
116
272
  const newSummary = result.summary || '';
@@ -118,12 +274,16 @@ export function Execute({ tasks, state, status, service, handlers, }) {
118
274
  label: tasks[index]?.action,
119
275
  command: cmd,
120
276
  }));
121
- setMessage(newMessage);
122
- setSummary(newSummary);
123
- setTaskInfos(infos);
124
- 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
+ });
125
285
  // Update state after AI processing
126
- handlers?.updateState({
286
+ stateHandlers?.updateState({
127
287
  message: newMessage,
128
288
  summary: newSummary,
129
289
  taskInfos: infos,
@@ -137,9 +297,11 @@ export function Execute({ tasks, state, status, service, handlers, }) {
137
297
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
138
298
  if (mounted) {
139
299
  const errorMessage = formatErrorMessage(err);
140
- setError(errorMessage);
141
- setHasProcessed(true);
142
- handlers?.updateState({
300
+ dispatch({
301
+ type: 'PROCESSING_ERROR',
302
+ payload: { error: errorMessage },
303
+ });
304
+ stateHandlers?.updateState({
143
305
  message: '',
144
306
  summary: '',
145
307
  taskInfos: [],
@@ -148,7 +310,7 @@ export function Execute({ tasks, state, status, service, handlers, }) {
148
310
  completionMessage: null,
149
311
  error: errorMessage,
150
312
  });
151
- handlers?.onError(errorMessage);
313
+ errorHandlers?.onError(errorMessage);
152
314
  }
153
315
  }
154
316
  }
@@ -156,20 +318,27 @@ export function Execute({ tasks, state, status, service, handlers, }) {
156
318
  return () => {
157
319
  mounted = false;
158
320
  };
159
- }, [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
+ ]);
160
332
  // Handle task completion - move to next task
161
333
  const handleTaskComplete = useCallback((index, _output, elapsed) => {
162
- const updatedTimes = [...taskExecutionTimes, elapsed];
163
- setTaskExecutionTimes(updatedTimes);
164
- // Update task with elapsed time and success status
165
- const updatedTaskInfos = taskInfos.map((task, i) => i === index
166
- ? { ...task, status: ExecutionStatus.Success, elapsed }
167
- : task);
168
- setTaskInfos(updatedTaskInfos);
169
334
  if (index < taskInfos.length - 1) {
170
335
  // More tasks to execute
171
- setCompleted(index + 1);
172
- 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({
173
342
  message,
174
343
  summary,
175
344
  taskInfos: updatedTaskInfos,
@@ -181,11 +350,18 @@ export function Execute({ tasks, state, status, service, handlers, }) {
181
350
  }
182
351
  else {
183
352
  // All tasks complete
184
- const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
185
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);
362
+ const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
186
363
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
187
- setCompletionMessage(completion);
188
- handlers?.updateState({
364
+ stateHandlers?.updateState({
189
365
  message,
190
366
  summary,
191
367
  taskInfos: updatedTaskInfos,
@@ -194,21 +370,26 @@ export function Execute({ tasks, state, status, service, handlers, }) {
194
370
  completionMessage: completion,
195
371
  error: null,
196
372
  });
197
- handlers?.completeActive();
373
+ lifecycleHandlers?.completeActive();
198
374
  }
199
- }, [taskInfos, message, handlers, taskExecutionTimes, summary]);
375
+ }, [
376
+ taskInfos,
377
+ message,
378
+ lifecycleHandlers,
379
+ taskExecutionTimes,
380
+ summary,
381
+ stateHandlers,
382
+ ]);
200
383
  const handleTaskError = useCallback((index, error, elapsed) => {
201
384
  const task = taskInfos[index];
202
385
  const isCritical = task.command.critical !== false; // Default to true
203
- // Update task with elapsed time and failed status
204
386
  const updatedTaskInfos = taskInfos.map((task, i) => i === index
205
387
  ? { ...task, status: ExecutionStatus.Failed, elapsed }
206
388
  : task);
207
- setTaskInfos(updatedTaskInfos);
208
389
  if (isCritical) {
209
390
  // Critical failure - stop execution
210
- setError(error);
211
- handlers?.updateState({
391
+ dispatch({ type: 'TASK_ERROR_CRITICAL', payload: { index, error } });
392
+ stateHandlers?.updateState({
212
393
  message,
213
394
  summary,
214
395
  taskInfos: updatedTaskInfos,
@@ -217,15 +398,17 @@ export function Execute({ tasks, state, status, service, handlers, }) {
217
398
  completionMessage: null,
218
399
  error,
219
400
  });
220
- handlers?.onError(error);
401
+ errorHandlers?.onError(error);
221
402
  }
222
403
  else {
223
404
  // Non-critical failure - continue to next task
224
405
  const updatedTimes = [...taskExecutionTimes, elapsed];
225
- setTaskExecutionTimes(updatedTimes);
226
406
  if (index < taskInfos.length - 1) {
227
- setCompleted(index + 1);
228
- handlers?.updateState({
407
+ dispatch({
408
+ type: 'TASK_ERROR_CONTINUE',
409
+ payload: { index, elapsed },
410
+ });
411
+ stateHandlers?.updateState({
229
412
  message,
230
413
  summary,
231
414
  taskInfos: updatedTaskInfos,
@@ -237,11 +420,14 @@ export function Execute({ tasks, state, status, service, handlers, }) {
237
420
  }
238
421
  else {
239
422
  // Last task, complete execution
240
- const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
241
423
  const summaryText = summary.trim() || 'Execution completed';
424
+ dispatch({
425
+ type: 'LAST_TASK_ERROR',
426
+ payload: { index, elapsed, summaryText },
427
+ });
428
+ const totalElapsed = updatedTimes.reduce((sum, time) => sum + time, 0);
242
429
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
243
- setCompletionMessage(completion);
244
- handlers?.updateState({
430
+ stateHandlers?.updateState({
245
431
  message,
246
432
  summary,
247
433
  taskInfos: updatedTaskInfos,
@@ -250,14 +436,22 @@ export function Execute({ tasks, state, status, service, handlers, }) {
250
436
  completionMessage: completion,
251
437
  error: null,
252
438
  });
253
- handlers?.completeActive();
439
+ lifecycleHandlers?.completeActive();
254
440
  }
255
441
  }
256
- }, [taskInfos, message, handlers, taskExecutionTimes, summary]);
442
+ }, [
443
+ taskInfos,
444
+ message,
445
+ stateHandlers,
446
+ lifecycleHandlers,
447
+ errorHandlers,
448
+ taskExecutionTimes,
449
+ summary,
450
+ ]);
257
451
  const handleTaskAbort = useCallback((_index) => {
258
452
  // Task was aborted - execution already stopped by Escape handler
259
453
  // Just update state, don't call onAborted (already called at Execute level)
260
- handlers?.updateState({
454
+ stateHandlers?.updateState({
261
455
  message,
262
456
  summary,
263
457
  taskInfos,
@@ -266,7 +460,7 @@ export function Execute({ tasks, state, status, service, handlers, }) {
266
460
  completionMessage: null,
267
461
  error: null,
268
462
  });
269
- }, [taskInfos, message, summary, completed, taskExecutionTimes, handlers]);
463
+ }, [taskInfos, message, summary, completed, taskExecutionTimes, stateHandlers]);
270
464
  // Return null only when loading completes with no commands
271
465
  if (!isActive && taskInfos.length === 0 && !error) {
272
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]: '✗',
@@ -3,20 +3,20 @@ import { useEffect, useState } 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, 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
- export function Introspect({ tasks, state: _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, }) {
14
14
  const isActive = status === ComponentStatus.Active;
15
15
  // isActive passed as prop
16
16
  const [error, setError] = useState(null);
17
17
  useInput((input, key) => {
18
18
  if (key.escape && isActive) {
19
- handlers?.onAborted('introspection');
19
+ errorHandlers?.onAborted('introspection');
20
20
  }
21
21
  }, { isActive });
22
22
  useEffect(() => {
@@ -35,7 +35,9 @@ export function Introspect({ tasks, state: _state, status, service, children, de
35
35
  await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
36
36
  if (mounted) {
37
37
  // Add debug components to timeline if present
38
- addDebugToTimeline(result.debug, handlers);
38
+ if (result.debug?.length) {
39
+ workflowHandlers?.addToTimeline(...result.debug);
40
+ }
39
41
  // Capabilities come directly from result - no parsing needed
40
42
  let capabilities = result.capabilities;
41
43
  // Filter out internal capabilities when not in debug mode
@@ -45,14 +47,14 @@ export function Introspect({ tasks, state: _state, status, service, children, de
45
47
  cap.name.toUpperCase() !== 'REPORT');
46
48
  }
47
49
  // Save state before completing
48
- handlers?.updateState({
50
+ stateHandlers?.updateState({
49
51
  capabilities,
50
52
  message: result.message,
51
53
  });
52
54
  // Add Report component to queue
53
- handlers?.addToQueue(createReportDefinition(result.message, capabilities));
55
+ queueHandlers?.addToQueue(createReportDefinition(result.message, capabilities));
54
56
  // Signal completion
55
- handlers?.completeActive();
57
+ lifecycleHandlers?.completeActive();
56
58
  }
57
59
  }
58
60
  catch (err) {
@@ -61,10 +63,10 @@ export function Introspect({ tasks, state: _state, status, service, children, de
61
63
  const errorMessage = formatErrorMessage(err);
62
64
  setError(errorMessage);
63
65
  // Save error state
64
- handlers?.updateState({
66
+ stateHandlers?.updateState({
65
67
  error: errorMessage,
66
68
  });
67
- handlers?.onError(errorMessage);
69
+ errorHandlers?.onError(errorMessage);
68
70
  }
69
71
  }
70
72
  }
@@ -72,7 +74,7 @@ export function Introspect({ tasks, state: _state, status, service, children, de
72
74
  return () => {
73
75
  mounted = false;
74
76
  };
75
- }, [tasks, isActive, service, debug, handlers]);
77
+ }, [tasks, isActive, service, debug]);
76
78
  // Don't render wrapper when done and nothing to show
77
79
  if (!isActive && !error && !children) {
78
80
  return null;
package/dist/ui/Main.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { FeedbackType } from '../types/types.js';
4
- import { createAnthropicService, } from '../services/anthropic.js';
4
+ import { createAnthropicService } from '../services/anthropic.js';
5
5
  import { createCommandDefinition, createConfigDefinitionWithKeys, createFeedback, createMessage, createWelcomeDefinition, } from '../services/components.js';
6
6
  import { DebugLevel, getConfigurationRequiredMessage, getMissingConfigKeys, loadConfig, loadDebugSetting, saveConfig, saveDebugSetting, unflattenConfig, } from '../services/configuration.js';
7
7
  import { registerGlobalShortcut } from '../services/keyboard.js';
8
8
  import { initializeLogger, setDebugLevel } from '../services/logger.js';
9
9
  import { Workflow } from './Workflow.js';
10
- export const Main = ({ app, command }) => {
10
+ export const Main = ({ app, command, serviceFactory = createAnthropicService, }) => {
11
11
  const [service, setService] = useState(null);
12
12
  const [initialQueue, setInitialQueue] = useState(null);
13
13
  const [debug, setDebugLevelState] = useState(() => loadDebugSetting());
@@ -44,7 +44,7 @@ export const Main = ({ app, command }) => {
44
44
  // Config exists - create service immediately
45
45
  try {
46
46
  const config = loadConfig();
47
- const newService = createAnthropicService(config.anthropic);
47
+ const newService = serviceFactory(config.anthropic);
48
48
  setService(newService);
49
49
  }
50
50
  catch (error) {
@@ -56,7 +56,7 @@ export const Main = ({ app, command }) => {
56
56
  }
57
57
  }
58
58
  // If config is missing, service will be created after config completes
59
- }, [service]);
59
+ }, [service, serviceFactory]);
60
60
  // Initialize queue after service is ready
61
61
  useEffect(() => {
62
62
  // Only set initial queue once
@@ -75,7 +75,7 @@ export const Main = ({ app, command }) => {
75
75
  }
76
76
  // Load config and create service
77
77
  const newConfig = loadConfig();
78
- const newService = createAnthropicService(newConfig.anthropic);
78
+ const newService = serviceFactory(newConfig.anthropic);
79
79
  setService(newService);
80
80
  }
81
81
  catch (error) {