prompt-language-shell 0.8.8 → 0.9.0

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,47 @@
1
+ /**
2
+ * Error codes for categorization and programmatic handling
3
+ */
4
+ export var ErrorCode;
5
+ (function (ErrorCode) {
6
+ // User errors - display to user, usually recoverable
7
+ ErrorCode["InvalidInput"] = "INVALID_INPUT";
8
+ ErrorCode["MissingConfig"] = "MISSING_CONFIG";
9
+ ErrorCode["SkillNotFound"] = "SKILL_NOT_FOUND";
10
+ // System errors - log + display, may be recoverable
11
+ ErrorCode["FileReadError"] = "FILE_READ_ERROR";
12
+ ErrorCode["FileWriteError"] = "FILE_WRITE_ERROR";
13
+ ErrorCode["NetworkError"] = "NETWORK_ERROR";
14
+ ErrorCode["ApiError"] = "API_ERROR";
15
+ ErrorCode["ParseError"] = "PARSE_ERROR";
16
+ // Fatal errors - must abort
17
+ ErrorCode["CircularReference"] = "CIRCULAR_REFERENCE";
18
+ ErrorCode["InvalidState"] = "INVALID_STATE";
19
+ ErrorCode["ConfigCorruption"] = "CONFIG_CORRUPTION";
20
+ })(ErrorCode || (ErrorCode = {}));
21
+ /**
22
+ * Base error class with cause chain support
23
+ * Provides consistent error structure throughout the application
24
+ */
25
+ export class AppError extends Error {
26
+ code;
27
+ cause;
28
+ constructor(message, code, cause) {
29
+ super(message);
30
+ this.code = code;
31
+ this.cause = cause;
32
+ this.name = 'AppError';
33
+ }
34
+ }
35
+ /**
36
+ * Type guard for AppError
37
+ */
38
+ export function isAppError(error) {
39
+ return error instanceof AppError;
40
+ }
41
+ /**
42
+ * Helper to wrap unknown errors with context
43
+ */
44
+ export function wrapError(error, code, message) {
45
+ const cause = error instanceof Error ? error : undefined;
46
+ return new AppError(message, code, cause);
47
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Create a successful result
3
+ */
4
+ export function ok(value) {
5
+ return { ok: true, value };
6
+ }
7
+ /**
8
+ * Create a failed result
9
+ */
10
+ export function err(error) {
11
+ return { ok: false, error };
12
+ }
13
+ /**
14
+ * Unwrap a result, throwing if it's an error
15
+ */
16
+ export function unwrap(result) {
17
+ if (result.ok)
18
+ return result.value;
19
+ throw result.error;
20
+ }
21
+ /**
22
+ * Map the value of a successful result
23
+ */
24
+ export function mapResult(result, fn) {
25
+ if (result.ok)
26
+ return ok(fn(result.value));
27
+ return result;
28
+ }
29
+ /**
30
+ * Check if a result is successful
31
+ */
32
+ export function isOk(result) {
33
+ return result.ok;
34
+ }
35
+ /**
36
+ * Check if a result is an error
37
+ */
38
+ export function isErr(result) {
39
+ return !result.ok;
40
+ }
@@ -107,8 +107,8 @@ export const ViewComponent = memo(function ViewComponent({ def, }) {
107
107
  return (_jsx(ScheduleView, { message: message, tasks: tasks, state: state, status: status }));
108
108
  }
109
109
  case ComponentName.Execute: {
110
- const { props: { tasks }, state, status, } = def;
111
- return _jsx(ExecuteView, { tasks: tasks, state: state, status: status });
110
+ const { state, status } = def;
111
+ return _jsx(ExecuteView, { state: state, status: status });
112
112
  }
113
113
  case ComponentName.Answer: {
114
114
  const { props: { question }, state, status, } = def;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useReducer } from 'react';
2
+ import { useCallback, useEffect, useReducer, useRef } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { ComponentStatus, } from '../types/components.js';
5
5
  import { getTextColor } from '../services/colors.js';
@@ -11,74 +11,108 @@ import { buildAbortedState, handleTaskCompletion, handleTaskFailure, } from '../
11
11
  import { processTasks } from '../execution/processing.js';
12
12
  import { executeReducer, initialState } from '../execution/reducer.js';
13
13
  import { ExecuteActionType } from '../execution/types.js';
14
- import { createMessage, markAsDone } from '../services/components.js';
14
+ import { createFeedback, createMessage, markAsDone, } from '../services/components.js';
15
+ import { FeedbackType } from '../types/types.js';
15
16
  import { Message } from './Message.js';
16
17
  import { Spinner } from './Spinner.js';
17
18
  import { Task } from './Task.js';
18
19
  const MINIMUM_PROCESSING_TIME = 400;
19
- export const ExecuteView = ({ state, status, onTaskComplete, onTaskAbort, onTaskError, }) => {
20
+ /**
21
+ * Create an ExecuteState with defaults
22
+ */
23
+ function createExecuteState(overrides = {}) {
24
+ return {
25
+ message: '',
26
+ summary: '',
27
+ tasks: [],
28
+ completed: 0,
29
+ completionMessage: null,
30
+ error: null,
31
+ ...overrides,
32
+ };
33
+ }
34
+ export const ExecuteView = ({ state, status, workdir, onOutputChange, onTaskComplete, onTaskAbort, onTaskError, }) => {
20
35
  const isActive = status === ComponentStatus.Active;
21
- const { error, taskInfos, message, completed, completionMessage } = state;
22
- const hasProcessed = taskInfos.length > 0;
36
+ const { error, tasks, message, completed, completionMessage } = state;
37
+ const hasProcessed = tasks.length > 0;
23
38
  // Derive loading state from current conditions
24
- const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
25
- const isExecuting = completed < taskInfos.length;
39
+ const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
40
+ const isExecuting = completed < tasks.length;
26
41
  // Return null only when loading completes with no commands
27
- if (!isActive && taskInfos.length === 0 && !error) {
42
+ if (!isActive && tasks.length === 0 && !error) {
28
43
  return null;
29
44
  }
30
45
  // Show completed steps when not active
31
- const showTasks = !isActive && taskInfos.length > 0;
32
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Preparing commands. " }), _jsx(Spinner, {})] })), (isExecuting || showTasks) && (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [message && (_jsxs(Box, { marginBottom: 1, gap: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: message }), isExecuting && _jsx(Spinner, {})] })), taskInfos.map((taskInfo, index) => (_jsx(Box, { marginBottom: index < taskInfos.length - 1 ? 1 : 0, children: _jsx(Task, { label: taskInfo.label, command: taskInfo.command, isActive: isActive && index === completed, index: index, initialStatus: taskInfo.status, initialElapsed: taskInfo.elapsed, onComplete: onTaskComplete, onAbort: onTaskAbort, onError: onTaskError }) }, index)))] })), completionMessage && !isActive && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(false), children: completionMessage }) })), error && _jsx(Message, { text: error, status: status })] }));
46
+ const showTasks = !isActive && tasks.length > 0;
47
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: "Preparing commands. " }), _jsx(Spinner, {})] })), (isExecuting || showTasks) && (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [message && (_jsxs(Box, { marginBottom: 1, gap: 1, children: [_jsx(Text, { color: getTextColor(isActive), children: message }), isExecuting && _jsx(Spinner, {})] })), tasks.map((taskInfo, index) => {
48
+ // Merge workdir into active task's command
49
+ const taskCommand = isActive && index === completed && workdir
50
+ ? { ...taskInfo.command, workdir }
51
+ : taskInfo.command;
52
+ return (_jsx(Box, { marginBottom: index < tasks.length - 1 ? 1 : 0, children: _jsx(Task, { label: taskInfo.label, command: taskCommand, isActive: isActive && index === completed, isFinished: index < completed, index: index, initialStatus: taskInfo.status, initialElapsed: taskInfo.elapsed, initialOutput: taskInfo.stdout || taskInfo.stderr || taskInfo.error
53
+ ? {
54
+ stdout: taskInfo.stdout ?? '',
55
+ stderr: taskInfo.stderr ?? '',
56
+ error: taskInfo.error ?? '',
57
+ }
58
+ : undefined, onOutputChange: onOutputChange, onComplete: onTaskComplete, onAbort: onTaskAbort, onError: onTaskError }) }, index));
59
+ })] })), completionMessage && !isActive && (_jsx(Box, { marginTop: 1, marginLeft: 1, children: _jsx(Text, { color: getTextColor(false), children: completionMessage }) })), error && _jsx(Message, { text: error, status: status })] }));
33
60
  };
34
61
  /**
35
62
  * Execute controller: Runs tasks sequentially
36
63
  */
37
- export function Execute({ tasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
64
+ export function Execute({ tasks: inputTasks, status, service, requestHandlers, lifecycleHandlers, workflowHandlers, }) {
38
65
  const isActive = status === ComponentStatus.Active;
39
66
  const [localState, dispatch] = useReducer(executeReducer, initialState);
40
- const { error, taskInfos, message, completed, hasProcessed, taskExecutionTimes, completionMessage, summary, } = localState;
67
+ // Ref to store current output for each task (avoids re-renders)
68
+ const taskOutputRef = useRef(new Map());
69
+ // Track working directory across commands (persists cd changes)
70
+ const workdirRef = useRef(undefined);
71
+ const { error, tasks, message, completed, hasProcessed, completionMessage, summary, } = localState;
41
72
  // Derive loading state from current conditions
42
- const isLoading = isActive && taskInfos.length === 0 && !error && !hasProcessed;
43
- const isExecuting = completed < taskInfos.length;
73
+ const isLoading = isActive && tasks.length === 0 && !error && !hasProcessed;
74
+ const isExecuting = completed < tasks.length;
75
+ // Handle output changes from Task - store in ref (no re-render)
76
+ const handleOutputChange = useCallback((index, taskOutput) => {
77
+ taskOutputRef.current.set(index, taskOutput);
78
+ }, []);
44
79
  // Handle cancel with useCallback to ensure we capture latest state
45
80
  const handleCancel = useCallback(() => {
46
81
  dispatch({
47
82
  type: ExecuteActionType.CancelExecution,
48
83
  payload: { completed },
49
84
  });
50
- // Get updated task infos after cancel
51
- const updatedTaskInfos = taskInfos.map((task, taskIndex) => {
85
+ // Get updated task infos after cancel, merging output from ref
86
+ const updatedTaskInfos = tasks.map((task, taskIndex) => {
87
+ const output = taskOutputRef.current.get(taskIndex);
88
+ const baseTask = output
89
+ ? {
90
+ ...task,
91
+ stdout: output.stdout,
92
+ stderr: output.stderr,
93
+ error: output.error,
94
+ }
95
+ : task;
52
96
  if (taskIndex < completed) {
53
- return { ...task, status: ExecutionStatus.Success };
97
+ return { ...baseTask, status: ExecutionStatus.Success };
54
98
  }
55
99
  else if (taskIndex === completed) {
56
- return { ...task, status: ExecutionStatus.Aborted };
100
+ return { ...baseTask, status: ExecutionStatus.Aborted };
57
101
  }
58
102
  else {
59
- return { ...task, status: ExecutionStatus.Cancelled };
103
+ return { ...baseTask, status: ExecutionStatus.Cancelled };
60
104
  }
61
105
  });
62
106
  // Expose final state
63
- const finalState = {
107
+ const finalState = createExecuteState({
64
108
  message,
65
109
  summary,
66
- taskInfos: updatedTaskInfos,
110
+ tasks: updatedTaskInfos,
67
111
  completed,
68
- taskExecutionTimes,
69
- completionMessage: null,
70
- error: null,
71
- };
112
+ });
72
113
  requestHandlers.onCompleted(finalState);
73
114
  requestHandlers.onAborted('execution');
74
- }, [
75
- message,
76
- summary,
77
- taskInfos,
78
- completed,
79
- taskExecutionTimes,
80
- requestHandlers,
81
- ]);
115
+ }, [message, summary, tasks, completed, requestHandlers]);
82
116
  useInput((_, key) => {
83
117
  if (key.escape && (isLoading || isExecuting) && isActive) {
84
118
  handleCancel();
@@ -86,14 +120,14 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
86
120
  }, { isActive: (isLoading || isExecuting) && isActive });
87
121
  // Process tasks to get commands from AI
88
122
  useEffect(() => {
89
- if (!isActive || taskInfos.length > 0 || hasProcessed) {
123
+ if (!isActive || tasks.length > 0 || hasProcessed) {
90
124
  return;
91
125
  }
92
126
  let mounted = true;
93
127
  async function process(svc) {
94
128
  const startTime = Date.now();
95
129
  try {
96
- const result = await processTasks(tasks, svc);
130
+ const result = await processTasks(inputTasks, svc);
97
131
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
98
132
  if (!mounted)
99
133
  return;
@@ -108,16 +142,7 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
108
142
  const errorMessage = getExecutionErrorMessage(result.error);
109
143
  workflowHandlers.addToTimeline(markAsDone(createMessage(errorMessage)));
110
144
  // Complete without error in state (message already in timeline)
111
- const finalState = {
112
- message: result.message,
113
- summary: '',
114
- taskInfos: [],
115
- completed: 0,
116
- taskExecutionTimes: [],
117
- completionMessage: null,
118
- error: null,
119
- };
120
- requestHandlers.onCompleted(finalState);
145
+ requestHandlers.onCompleted(createExecuteState({ message: result.message }));
121
146
  lifecycleHandlers.completeActive();
122
147
  return;
123
148
  }
@@ -126,43 +151,31 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
126
151
  type: ExecuteActionType.ProcessingComplete,
127
152
  payload: { message: result.message },
128
153
  });
129
- const finalState = {
130
- message: result.message,
131
- summary: '',
132
- taskInfos: [],
133
- completed: 0,
134
- taskExecutionTimes: [],
135
- completionMessage: null,
136
- error: null,
137
- };
138
- requestHandlers.onCompleted(finalState);
154
+ requestHandlers.onCompleted(createExecuteState({ message: result.message }));
139
155
  lifecycleHandlers.completeActive();
140
156
  return;
141
157
  }
142
158
  // Create task infos from commands
143
- const infos = result.commands.map((cmd, index) => ({
144
- label: tasks[index]?.action,
159
+ const tasks = result.commands.map((cmd, index) => ({
160
+ label: inputTasks[index]?.action ?? cmd.description,
145
161
  command: cmd,
162
+ status: ExecutionStatus.Pending,
163
+ elapsed: 0,
146
164
  }));
147
165
  dispatch({
148
166
  type: ExecuteActionType.CommandsReady,
149
167
  payload: {
150
168
  message: result.message,
151
169
  summary: result.summary,
152
- taskInfos: infos,
170
+ tasks,
153
171
  },
154
172
  });
155
173
  // Update state after AI processing
156
- const finalState = {
174
+ requestHandlers.onCompleted(createExecuteState({
157
175
  message: result.message,
158
176
  summary: result.summary,
159
- taskInfos: infos,
160
- completed: 0,
161
- taskExecutionTimes: [],
162
- completionMessage: null,
163
- error: null,
164
- };
165
- requestHandlers.onCompleted(finalState);
177
+ tasks,
178
+ }));
166
179
  }
167
180
  catch (err) {
168
181
  await ensureMinimumTime(startTime, MINIMUM_PROCESSING_TIME);
@@ -172,16 +185,7 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
172
185
  type: ExecuteActionType.ProcessingError,
173
186
  payload: { error: errorMessage },
174
187
  });
175
- const finalState = {
176
- message: '',
177
- summary: '',
178
- taskInfos: [],
179
- completed: 0,
180
- taskExecutionTimes: [],
181
- completionMessage: null,
182
- error: errorMessage,
183
- };
184
- requestHandlers.onCompleted(finalState);
188
+ requestHandlers.onCompleted(createExecuteState({ error: errorMessage }));
185
189
  requestHandlers.onError(errorMessage);
186
190
  }
187
191
  }
@@ -191,81 +195,100 @@ export function Execute({ tasks, status, service, requestHandlers, lifecycleHand
191
195
  mounted = false;
192
196
  };
193
197
  }, [
194
- tasks,
198
+ inputTasks,
195
199
  isActive,
196
200
  service,
197
201
  requestHandlers,
198
202
  lifecycleHandlers,
199
203
  workflowHandlers,
200
- taskInfos.length,
204
+ tasks.length,
201
205
  hasProcessed,
202
206
  ]);
203
207
  // Handle task completion - move to next task
204
- const handleTaskComplete = useCallback((index, _output, elapsed) => {
208
+ const handleTaskComplete = useCallback((index, elapsed, taskOutput) => {
209
+ // Track working directory for subsequent commands
210
+ if (taskOutput.workdir) {
211
+ workdirRef.current = taskOutput.workdir;
212
+ }
213
+ // Update tasks with output before calling handler
214
+ const tasksWithOutput = tasks.map((task, i) => i === index
215
+ ? {
216
+ ...task,
217
+ stdout: taskOutput.stdout,
218
+ stderr: taskOutput.stderr,
219
+ error: taskOutput.error,
220
+ }
221
+ : task);
205
222
  const result = handleTaskCompletion(index, elapsed, {
206
- taskInfos,
223
+ tasks: tasksWithOutput,
207
224
  message,
208
225
  summary,
209
- taskExecutionTimes,
210
226
  });
211
227
  dispatch(result.action);
212
228
  requestHandlers.onCompleted(result.finalState);
213
229
  if (result.shouldComplete) {
214
230
  lifecycleHandlers.completeActive();
215
231
  }
216
- }, [
217
- taskInfos,
218
- message,
219
- summary,
220
- taskExecutionTimes,
221
- requestHandlers,
222
- lifecycleHandlers,
223
- ]);
224
- const handleTaskError = useCallback((index, error, elapsed) => {
232
+ }, [tasks, message, summary, requestHandlers, lifecycleHandlers]);
233
+ const handleTaskError = useCallback((index, error, elapsed, taskOutput) => {
234
+ // Track working directory for subsequent commands (even on error)
235
+ if (taskOutput.workdir) {
236
+ workdirRef.current = taskOutput.workdir;
237
+ }
238
+ // Update tasks with output before calling handler
239
+ const tasksWithOutput = tasks.map((task, i) => i === index
240
+ ? {
241
+ ...task,
242
+ stdout: taskOutput.stdout,
243
+ stderr: taskOutput.stderr,
244
+ error: taskOutput.error,
245
+ }
246
+ : task);
225
247
  const result = handleTaskFailure(index, error, elapsed, {
226
- taskInfos,
248
+ tasks: tasksWithOutput,
227
249
  message,
228
250
  summary,
229
- taskExecutionTimes,
230
251
  });
231
252
  dispatch(result.action);
232
253
  requestHandlers.onCompleted(result.finalState);
233
- if (result.shouldReportError) {
234
- requestHandlers.onError(error);
254
+ // Add error feedback to queue for critical failures
255
+ if (result.action.type === ExecuteActionType.TaskErrorCritical) {
256
+ const errorMessage = getExecutionErrorMessage(error);
257
+ workflowHandlers.addToQueue(createFeedback(FeedbackType.Failed, errorMessage));
235
258
  }
236
259
  if (result.shouldComplete) {
237
260
  lifecycleHandlers.completeActive();
238
261
  }
239
262
  }, [
240
- taskInfos,
263
+ tasks,
241
264
  message,
242
265
  summary,
243
- taskExecutionTimes,
244
266
  requestHandlers,
245
267
  lifecycleHandlers,
268
+ workflowHandlers,
246
269
  ]);
247
- const handleTaskAbort = useCallback((_index) => {
270
+ const handleTaskAbort = useCallback((index, taskOutput) => {
248
271
  // Task was aborted - execution already stopped by Escape handler
249
- // Just update state, don't call onAborted (already called at Execute level)
250
- const finalState = buildAbortedState(taskInfos, message, summary, completed, taskExecutionTimes);
272
+ // Update tasks with output before building state
273
+ const tasksWithOutput = tasks.map((task, i) => i === index
274
+ ? {
275
+ ...task,
276
+ stdout: taskOutput.stdout,
277
+ stderr: taskOutput.stderr,
278
+ error: taskOutput.error,
279
+ }
280
+ : task);
281
+ const finalState = buildAbortedState(tasksWithOutput, message, summary, completed);
251
282
  requestHandlers.onCompleted(finalState);
252
- }, [
253
- taskInfos,
254
- message,
255
- summary,
256
- completed,
257
- taskExecutionTimes,
258
- requestHandlers,
259
- ]);
283
+ }, [tasks, message, summary, completed, requestHandlers]);
260
284
  // Controller always renders View with current state
261
- const viewState = {
285
+ const viewState = createExecuteState({
262
286
  error,
263
- taskInfos,
287
+ tasks,
264
288
  message,
265
289
  summary,
266
290
  completed,
267
- taskExecutionTimes,
268
291
  completionMessage,
269
- };
270
- return (_jsx(ExecuteView, { tasks: tasks, state: viewState, status: status, onTaskComplete: handleTaskComplete, onTaskAbort: handleTaskAbort, onTaskError: handleTaskError }));
292
+ });
293
+ return (_jsx(ExecuteView, { state: viewState, status: status, workdir: workdirRef.current, onOutputChange: handleOutputChange, onTaskComplete: handleTaskComplete, onTaskAbort: handleTaskAbort, onTaskError: handleTaskError }));
271
294
  }
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { Palette } from '../services/colors.js';
4
+ import { ExecutionStatus } from '../services/shell.js';
5
+ const MAX_LINES = 8;
6
+ const MAX_WIDTH = 75;
7
+ const SHORT_OUTPUT_THRESHOLD = 4;
8
+ const MINIMAL_INFO_THRESHOLD = 2;
9
+ /**
10
+ * Get the last N lines from text, filtering out empty/whitespace-only lines
11
+ */
12
+ export function getLastLines(text, maxLines = MAX_LINES) {
13
+ const lines = text
14
+ .trim()
15
+ .split(/\r?\n/)
16
+ .filter((line) => line.trim().length > 0);
17
+ return lines.length <= maxLines ? lines : lines.slice(-maxLines);
18
+ }
19
+ /**
20
+ * Compute display configuration for output rendering.
21
+ * Encapsulates the logic for what to show and how to style it.
22
+ */
23
+ export function computeDisplayConfig(stdout, stderr, status, isFinished) {
24
+ const hasStdout = stdout.trim().length > 0;
25
+ const hasStderr = stderr.trim().length > 0;
26
+ if (!hasStdout && !hasStderr)
27
+ return null;
28
+ const stdoutLines = hasStdout ? getLastLines(stdout) : [];
29
+ const stderrLines = hasStderr ? getLastLines(stderr) : [];
30
+ // Show stdout if no stderr, or if stderr is minimal (provides context)
31
+ const showStdout = hasStdout && (!hasStderr || stderrLines.length <= MINIMAL_INFO_THRESHOLD);
32
+ // Use word wrapping for short outputs to show more detail
33
+ const totalLines = stdoutLines.length + stderrLines.length;
34
+ const wrapMode = totalLines <= SHORT_OUTPUT_THRESHOLD ? 'wrap' : 'truncate-end';
35
+ // Darker colors for finished tasks
36
+ const baseColor = isFinished ? Palette.DarkGray : Palette.Gray;
37
+ const stderrColor = status === ExecutionStatus.Failed ? Palette.Yellow : baseColor;
38
+ return {
39
+ stdoutLines,
40
+ stderrLines,
41
+ showStdout,
42
+ wrapMode,
43
+ stdoutColor: baseColor,
44
+ stderrColor,
45
+ };
46
+ }
47
+ export function Output({ stdout, stderr, status, isFinished }) {
48
+ const config = computeDisplayConfig(stdout, stderr, status, isFinished ?? false);
49
+ if (!config)
50
+ return null;
51
+ const { stdoutLines, stderrLines, showStdout, wrapMode, stdoutColor, stderrColor, } = config;
52
+ return (_jsxs(Box, { marginTop: 1, marginLeft: 5, flexDirection: "column", width: MAX_WIDTH, children: [showStdout &&
53
+ stdoutLines.map((line, index) => (_jsx(Text, { color: stdoutColor, wrap: wrapMode, children: line }, `out-${index}`))), stderrLines.map((line, index) => (_jsx(Text, { color: stderrColor, wrap: wrapMode, children: line }, `err-${index}`)))] }));
54
+ }