prompt-language-shell 0.9.2 → 0.9.6

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.
Files changed (67) hide show
  1. package/dist/{ui/Main.js → Main.js} +12 -12
  2. package/dist/{ui → components}/Component.js +28 -26
  3. package/dist/{ui → components}/Workflow.js +14 -4
  4. package/dist/{ui → components/controllers}/Answer.js +18 -17
  5. package/dist/{ui → components/controllers}/Command.js +11 -18
  6. package/dist/{ui → components/controllers}/Config.js +8 -116
  7. package/dist/components/controllers/Confirm.js +42 -0
  8. package/dist/{ui → components/controllers}/Execute.js +75 -144
  9. package/dist/{ui → components/controllers}/Introspect.js +12 -28
  10. package/dist/components/controllers/Refinement.js +18 -0
  11. package/dist/components/controllers/Schedule.js +134 -0
  12. package/dist/{ui → components/controllers}/Validate.js +14 -32
  13. package/dist/components/views/Answer.js +28 -0
  14. package/dist/components/views/Command.js +11 -0
  15. package/dist/components/views/Config.js +115 -0
  16. package/dist/components/views/Confirm.js +24 -0
  17. package/dist/components/views/Debug.js +12 -0
  18. package/dist/components/views/Execute.js +60 -0
  19. package/dist/components/views/Feedback.js +8 -0
  20. package/dist/components/views/Introspect.js +17 -0
  21. package/dist/{ui → components/views}/Label.js +3 -3
  22. package/dist/{ui → components/views}/List.js +1 -1
  23. package/dist/{ui → components/views}/Output.js +2 -2
  24. package/dist/components/views/Refinement.js +9 -0
  25. package/dist/{ui → components/views}/Report.js +1 -1
  26. package/dist/components/views/Schedule.js +121 -0
  27. package/dist/{ui → components/views}/Separator.js +1 -1
  28. package/dist/{ui → components/views}/Spinner.js +1 -1
  29. package/dist/{ui → components/views}/Subtask.js +4 -4
  30. package/dist/components/views/Table.js +15 -0
  31. package/dist/components/views/Task.js +18 -0
  32. package/dist/components/views/Upcoming.js +30 -0
  33. package/dist/{ui → components/views}/UserQuery.js +1 -1
  34. package/dist/components/views/Validate.js +17 -0
  35. package/dist/{ui → components/views}/Welcome.js +1 -1
  36. package/dist/configuration/steps.js +1 -1
  37. package/dist/execution/handlers.js +19 -53
  38. package/dist/execution/reducer.js +26 -38
  39. package/dist/execution/runner.js +43 -25
  40. package/dist/execution/types.js +3 -4
  41. package/dist/execution/utils.js +1 -1
  42. package/dist/index.js +1 -1
  43. package/dist/services/anthropic.js +27 -31
  44. package/dist/services/colors.js +2 -1
  45. package/dist/services/logger.js +126 -13
  46. package/dist/services/messages.js +19 -0
  47. package/dist/services/parser.js +13 -5
  48. package/dist/services/refinement.js +8 -2
  49. package/dist/services/router.js +184 -89
  50. package/dist/services/shell.js +26 -6
  51. package/dist/services/skills.js +35 -7
  52. package/dist/services/timing.js +1 -0
  53. package/dist/skills/execute.md +15 -7
  54. package/dist/skills/schedule.md +155 -0
  55. package/dist/tools/execute.tool.js +0 -4
  56. package/dist/tools/schedule.tool.js +1 -1
  57. package/dist/types/schemas.js +0 -1
  58. package/package.json +4 -4
  59. package/dist/execution/hooks.js +0 -291
  60. package/dist/ui/Confirm.js +0 -62
  61. package/dist/ui/Debug.js +0 -7
  62. package/dist/ui/Feedback.js +0 -19
  63. package/dist/ui/Refinement.js +0 -23
  64. package/dist/ui/Schedule.js +0 -257
  65. package/dist/ui/Task.js +0 -11
  66. /package/dist/{ui → components/views}/Message.js +0 -0
  67. /package/dist/{ui → components/views}/Panel.js +0 -0
@@ -7,7 +7,7 @@ import { getTotalElapsed } from './utils.js';
7
7
  */
8
8
  export function handleTaskCompletion(index, elapsed, context) {
9
9
  const { tasks, message, summary } = context;
10
- const updatedTaskInfos = tasks.map((task, i) => i === index ? { ...task, status: ExecutionStatus.Success, elapsed } : task);
10
+ const updatedTasks = tasks.map((task, i) => i === index ? { ...task, status: ExecutionStatus.Success, elapsed } : task);
11
11
  if (index < tasks.length - 1) {
12
12
  // More tasks to execute
13
13
  return {
@@ -18,7 +18,7 @@ export function handleTaskCompletion(index, elapsed, context) {
18
18
  finalState: {
19
19
  message,
20
20
  summary,
21
- tasks: updatedTaskInfos,
21
+ tasks: updatedTasks,
22
22
  completionMessage: null,
23
23
  error: null,
24
24
  },
@@ -27,17 +27,17 @@ export function handleTaskCompletion(index, elapsed, context) {
27
27
  }
28
28
  // All tasks complete
29
29
  const summaryText = summary.trim() || 'Execution completed';
30
- const totalElapsed = getTotalElapsed(updatedTaskInfos);
30
+ const totalElapsed = getTotalElapsed(updatedTasks);
31
31
  const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
32
32
  return {
33
33
  action: {
34
- type: ExecuteActionType.AllTasksComplete,
34
+ type: ExecuteActionType.ExecutionComplete,
35
35
  payload: { index, elapsed, summaryText },
36
36
  },
37
37
  finalState: {
38
38
  message,
39
39
  summary,
40
- tasks: updatedTaskInfos,
40
+ tasks: updatedTasks,
41
41
  completionMessage: completion,
42
42
  error: null,
43
43
  },
@@ -47,63 +47,29 @@ export function handleTaskCompletion(index, elapsed, context) {
47
47
  /**
48
48
  * Handles task error logic and returns the appropriate action and state.
49
49
  */
50
- export function handleTaskFailure(index, error, elapsed, context) {
50
+ export function handleTaskFailure(index, error, context) {
51
51
  const { tasks, message, summary } = context;
52
- const task = tasks[index];
53
- const isCritical = task.command.critical !== false; // Default to true
54
- const updatedTaskInfos = tasks.map((task, i) => i === index ? { ...task, status: ExecutionStatus.Failed, elapsed } : task);
55
- if (isCritical) {
56
- // Critical failure - stop execution
57
- return {
58
- action: {
59
- type: ExecuteActionType.TaskErrorCritical,
60
- payload: { index, error },
61
- },
62
- finalState: {
63
- message,
64
- summary,
65
- tasks: updatedTaskInfos,
66
- completionMessage: null,
67
- error: null,
68
- },
69
- shouldComplete: true,
70
- };
71
- }
72
- // Non-critical failure - continue to next task
73
- if (index < tasks.length - 1) {
74
- return {
75
- action: {
76
- type: ExecuteActionType.TaskErrorContinue,
77
- payload: { index, elapsed },
78
- },
79
- finalState: {
80
- message,
81
- summary,
82
- tasks: updatedTaskInfos,
83
- completionMessage: null,
84
- error: null,
85
- },
86
- shouldComplete: false,
87
- };
88
- }
89
- // Last task failed (non-critical), complete execution
90
- // Non-critical failures still show completion message with summary
91
- const summaryText = summary.trim() || 'Execution completed';
92
- const totalElapsed = getTotalElapsed(updatedTaskInfos);
93
- const completion = `${summaryText} in ${formatDuration(totalElapsed)}.`;
52
+ const updatedTasks = tasks.map((task, i) => {
53
+ if (i === index) {
54
+ return { ...task, status: ExecutionStatus.Failed, elapsed: 0 };
55
+ }
56
+ else if (i > index && task.status === ExecutionStatus.Pending) {
57
+ return { ...task, status: ExecutionStatus.Cancelled };
58
+ }
59
+ return task;
60
+ });
94
61
  return {
95
62
  action: {
96
- type: ExecuteActionType.LastTaskError,
97
- payload: { index, elapsed, summaryText },
63
+ type: ExecuteActionType.TaskError,
64
+ payload: { index, error },
98
65
  },
99
66
  finalState: {
100
67
  message,
101
68
  summary,
102
- tasks: updatedTaskInfos,
103
- completionMessage: completion,
69
+ tasks: updatedTasks,
70
+ completionMessage: null,
104
71
  error: null,
105
72
  },
106
- shouldComplete: true,
107
73
  };
108
74
  }
109
75
  /**
@@ -33,84 +33,72 @@ export function executeReducer(state, action) {
33
33
  };
34
34
  case ExecuteActionType.TaskStarted: {
35
35
  const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
36
- ? { ...task, status: ExecutionStatus.Running }
36
+ ? {
37
+ ...task,
38
+ status: ExecutionStatus.Running,
39
+ startTime: action.payload.startTime,
40
+ }
37
41
  : task);
38
42
  return {
39
43
  ...state,
40
44
  tasks: updatedTasks,
41
45
  };
42
46
  }
43
- case ExecuteActionType.TaskComplete: {
44
- const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
47
+ case ExecuteActionType.TaskProgress: {
48
+ const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
45
49
  ? {
46
50
  ...task,
47
- status: ExecutionStatus.Success,
48
51
  elapsed: action.payload.elapsed,
52
+ output: action.payload.output,
49
53
  }
50
54
  : task);
51
55
  return {
52
56
  ...state,
53
- tasks: updatedTaskInfos,
57
+ tasks: updatedTasks,
54
58
  };
55
59
  }
56
- case ExecuteActionType.AllTasksComplete: {
57
- const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
60
+ case ExecuteActionType.TaskComplete: {
61
+ const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
58
62
  ? {
59
63
  ...task,
60
64
  status: ExecutionStatus.Success,
61
65
  elapsed: action.payload.elapsed,
62
66
  }
63
67
  : task);
64
- const totalElapsed = getTotalElapsed(updatedTaskInfos);
65
- const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
66
68
  return {
67
69
  ...state,
68
- tasks: updatedTaskInfos,
69
- completionMessage: completion,
70
- };
71
- }
72
- case ExecuteActionType.TaskErrorCritical: {
73
- const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
74
- ? { ...task, status: ExecutionStatus.Failed, elapsed: 0 }
75
- : task);
76
- return {
77
- ...state,
78
- tasks: updatedTaskInfos,
79
- error: action.payload.error,
70
+ tasks: updatedTasks,
80
71
  };
81
72
  }
82
- case ExecuteActionType.TaskErrorContinue: {
83
- const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
73
+ case ExecuteActionType.ExecutionComplete: {
74
+ const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
84
75
  ? {
85
76
  ...task,
86
- status: ExecutionStatus.Failed,
77
+ status: ExecutionStatus.Success,
87
78
  elapsed: action.payload.elapsed,
88
79
  }
89
80
  : task);
81
+ const totalElapsed = getTotalElapsed(updatedTasks);
82
+ const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
90
83
  return {
91
84
  ...state,
92
- tasks: updatedTaskInfos,
85
+ tasks: updatedTasks,
86
+ completionMessage: completion,
93
87
  };
94
88
  }
95
- case ExecuteActionType.LastTaskError: {
96
- const updatedTaskInfos = state.tasks.map((task, i) => i === action.payload.index
97
- ? {
98
- ...task,
99
- status: ExecutionStatus.Failed,
100
- elapsed: action.payload.elapsed,
101
- }
89
+ case ExecuteActionType.TaskError: {
90
+ const updatedTasks = state.tasks.map((task, i) => i === action.payload.index
91
+ ? { ...task, status: ExecutionStatus.Failed, elapsed: 0 }
102
92
  : task);
103
- const totalElapsed = getTotalElapsed(updatedTaskInfos);
104
- const completion = `${action.payload.summaryText} in ${formatDuration(totalElapsed)}.`;
105
93
  return {
106
94
  ...state,
107
- tasks: updatedTaskInfos,
108
- completionMessage: completion,
95
+ tasks: updatedTasks,
96
+ error: action.payload.error,
109
97
  };
110
98
  }
111
99
  case ExecuteActionType.CancelExecution: {
112
100
  // Mark running task as aborted, pending tasks as cancelled
113
- const updatedTaskInfos = state.tasks.map((task) => {
101
+ const updatedTasks = state.tasks.map((task) => {
114
102
  if (task.status === ExecutionStatus.Running) {
115
103
  return { ...task, status: ExecutionStatus.Aborted };
116
104
  }
@@ -121,7 +109,7 @@ export function executeReducer(state, action) {
121
109
  });
122
110
  return {
123
111
  ...state,
124
- tasks: updatedTaskInfos,
112
+ tasks: updatedTasks,
125
113
  };
126
114
  }
127
115
  default:
@@ -1,5 +1,14 @@
1
1
  import { ExecutionResult, ExecutionStatus, executeCommand, setOutputCallback, } from '../services/shell.js';
2
2
  import { calculateElapsed } from '../services/utils.js';
3
+ // Maximum number of output lines to keep in memory
4
+ const MAX_OUTPUT_LINES = 128;
5
+ /**
6
+ * Limit output to last MAX_OUTPUT_LINES lines to prevent memory exhaustion
7
+ */
8
+ function limitLines(output) {
9
+ const lines = output.split('\n');
10
+ return lines.slice(-MAX_OUTPUT_LINES).join('\n');
11
+ }
3
12
  /**
4
13
  * Execute a single task and track its progress.
5
14
  * All execution logic is contained here, outside of React components.
@@ -17,21 +26,38 @@ export async function executeTask(command, index, callbacks) {
17
26
  error,
18
27
  workdir,
19
28
  });
29
+ // Throttle updates to avoid excessive re-renders (100ms minimum interval)
30
+ let lastUpdateTime = 0;
31
+ let pendingTimeout;
32
+ const throttledUpdate = () => {
33
+ const now = Date.now();
34
+ if (now - lastUpdateTime >= 100) {
35
+ lastUpdateTime = now;
36
+ callbacks.onUpdate(createOutput());
37
+ }
38
+ else if (!pendingTimeout) {
39
+ pendingTimeout = setTimeout(() => {
40
+ pendingTimeout = undefined;
41
+ lastUpdateTime = Date.now();
42
+ callbacks.onUpdate(createOutput());
43
+ }, 100 - (now - lastUpdateTime));
44
+ }
45
+ };
20
46
  // Set up output streaming callback
21
47
  setOutputCallback((data, stream) => {
22
48
  if (stream === 'stdout') {
23
- stdout += data;
49
+ stdout = limitLines(stdout + data);
24
50
  }
25
51
  else {
26
- stderr += data;
52
+ stderr = limitLines(stderr + data);
27
53
  }
28
- callbacks.onOutputChange?.(createOutput());
54
+ throttledUpdate();
29
55
  });
30
- callbacks.onStart?.();
31
56
  try {
32
57
  const result = await executeCommand(command, undefined, index);
33
- // Clear callback
58
+ // Clear callback and pending timeout
34
59
  setOutputCallback(undefined);
60
+ clearTimeout(pendingTimeout);
35
61
  const elapsed = calculateElapsed(startTime);
36
62
  // Update final output from result
37
63
  stdout = result.output;
@@ -39,42 +65,34 @@ export async function executeTask(command, index, callbacks) {
39
65
  workdir = result.workdir;
40
66
  if (result.result === ExecutionResult.Success) {
41
67
  const output = createOutput();
42
- callbacks.onComplete?.(elapsed, output);
43
- return {
44
- status: ExecutionStatus.Success,
45
- elapsed,
46
- output,
47
- };
68
+ callbacks.onUpdate(output);
69
+ callbacks.onComplete(elapsed, output);
70
+ return { status: ExecutionStatus.Success, elapsed, output };
48
71
  }
49
72
  else {
50
73
  const errorMsg = result.errors || result.error || 'Command failed';
51
74
  error = errorMsg;
52
75
  const output = createOutput();
53
- callbacks.onError?.(errorMsg, elapsed, output);
54
- return {
55
- status: ExecutionStatus.Failed,
56
- elapsed,
57
- output,
58
- };
76
+ callbacks.onUpdate(output);
77
+ callbacks.onError(errorMsg, output);
78
+ return { status: ExecutionStatus.Failed, elapsed, output };
59
79
  }
60
80
  }
61
81
  catch (err) {
62
- // Clear callback
82
+ // Clear callback and pending timeout
63
83
  setOutputCallback(undefined);
84
+ clearTimeout(pendingTimeout);
64
85
  const elapsed = calculateElapsed(startTime);
65
86
  const errorMsg = err instanceof Error ? err.message : 'Unknown error';
66
87
  error = errorMsg;
67
88
  const output = createOutput();
68
- callbacks.onError?.(errorMsg, elapsed, output);
69
- return {
70
- status: ExecutionStatus.Failed,
71
- elapsed,
72
- output,
73
- };
89
+ callbacks.onUpdate(output);
90
+ callbacks.onError(errorMsg, output);
91
+ return { status: ExecutionStatus.Failed, elapsed, output };
74
92
  }
75
93
  }
76
94
  /**
77
- * Create an empty task output
95
+ * Create an empty execution output
78
96
  */
79
97
  export function createEmptyOutput() {
80
98
  return { stdout: '', stderr: '', error: '' };
@@ -4,10 +4,9 @@ export var ExecuteActionType;
4
4
  ExecuteActionType["CommandsReady"] = "COMMANDS_READY";
5
5
  ExecuteActionType["ProcessingError"] = "PROCESSING_ERROR";
6
6
  ExecuteActionType["TaskStarted"] = "TASK_STARTED";
7
+ ExecuteActionType["TaskProgress"] = "TASK_PROGRESS";
7
8
  ExecuteActionType["TaskComplete"] = "TASK_COMPLETE";
8
- ExecuteActionType["AllTasksComplete"] = "ALL_TASKS_COMPLETE";
9
- ExecuteActionType["TaskErrorCritical"] = "TASK_ERROR_CRITICAL";
10
- ExecuteActionType["TaskErrorContinue"] = "TASK_ERROR_CONTINUE";
11
- ExecuteActionType["LastTaskError"] = "LAST_TASK_ERROR";
9
+ ExecuteActionType["ExecutionComplete"] = "EXECUTION_COMPLETE";
10
+ ExecuteActionType["TaskError"] = "TASK_ERROR";
12
11
  ExecuteActionType["CancelExecution"] = "CANCEL_EXECUTION";
13
12
  })(ExecuteActionType || (ExecuteActionType = {}));
@@ -1,6 +1,6 @@
1
1
  import { ExecutionStatus } from '../services/shell.js';
2
2
  /**
3
- * Calculate total elapsed time from task infos
3
+ * Calculate total elapsed time from task data
4
4
  */
5
5
  export function getTotalElapsed(tasks) {
6
6
  return tasks.reduce((sum, task) => sum + task.elapsed, 0);
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { dirname, join } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { render } from 'ink';
7
7
  import { DebugLevel } from './configuration/types.js';
8
- import { Main } from './ui/Main.js';
8
+ import { Main } from './Main.js';
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
11
11
  // Get package info
@@ -1,7 +1,7 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
2
  import { getAvailableConfigStructure, getConfiguredKeys, } from '../configuration/schema.js';
3
3
  import { logPrompt, logResponse } from './logger.js';
4
- import { formatSkillsForPrompt, loadSkillsWithValidation } from './skills.js';
4
+ import { loadSkillsForPrompt } from './skills.js';
5
5
  import { toolRegistry } from './registry.js';
6
6
  import { CommandResultSchema, IntrospectResultSchema, } from '../types/schemas.js';
7
7
  /**
@@ -69,36 +69,32 @@ export class AnthropicService {
69
69
  async processWithTool(command, toolName, customInstructions) {
70
70
  // Load tool from registry
71
71
  const tool = toolRegistry.getSchema(toolName);
72
- // Use custom instructions if provided, otherwise load from registry
73
- let systemPrompt;
74
- if (customInstructions) {
75
- // Custom instructions provided (typically for testing)
76
- systemPrompt = customInstructions;
72
+ // Check if this tool uses skills
73
+ const usesSkills = toolName === 'schedule' ||
74
+ toolName === 'introspect' ||
75
+ toolName === 'execute' ||
76
+ toolName === 'validate';
77
+ // Load base instructions and skills
78
+ const baseInstructions = customInstructions || toolRegistry.getInstructions(toolName);
79
+ let formattedSkills = '';
80
+ let skillDefinitions = [];
81
+ let systemPrompt = baseInstructions;
82
+ if (!customInstructions && usesSkills) {
83
+ const skillsResult = loadSkillsForPrompt();
84
+ formattedSkills = skillsResult.formatted;
85
+ skillDefinitions = skillsResult.definitions;
86
+ systemPrompt += formattedSkills;
77
87
  }
78
- else {
79
- // Load and build system prompt automatically (production)
80
- const instructions = toolRegistry.getInstructions(toolName);
81
- systemPrompt = instructions;
82
- // Add skills section for applicable tools
83
- if (toolName === 'schedule' ||
84
- toolName === 'introspect' ||
85
- toolName === 'execute' ||
86
- toolName === 'validate') {
87
- const skills = loadSkillsWithValidation();
88
- const skillsSection = formatSkillsForPrompt(skills);
89
- systemPrompt += skillsSection;
90
- }
91
- // Add config structure for configure tool only
92
- if (toolName === 'configure') {
93
- const configStructure = getAvailableConfigStructure();
94
- const configuredKeys = getConfiguredKeys();
95
- const configSection = '\n## Available Configuration\n\n' +
96
- 'Config structure (key: description):\n' +
97
- JSON.stringify(configStructure, null, 2) +
98
- '\n\nConfigured keys (keys that exist in config file):\n' +
99
- JSON.stringify(configuredKeys, null, 2);
100
- systemPrompt += configSection;
101
- }
88
+ // Add config structure for configure tool only
89
+ if (!customInstructions && toolName === 'configure') {
90
+ const configStructure = getAvailableConfigStructure();
91
+ const configuredKeys = getConfiguredKeys();
92
+ const configSection = '\n## Available Configuration\n\n' +
93
+ 'Config structure (key: description):\n' +
94
+ JSON.stringify(configStructure, null, 2) +
95
+ '\n\nConfigured keys (keys that exist in config file):\n' +
96
+ JSON.stringify(configuredKeys, null, 2);
97
+ systemPrompt += configSection;
102
98
  }
103
99
  // Build tools array - add web search for answer tool
104
100
  const tools = [tool];
@@ -111,7 +107,7 @@ export class AnthropicService {
111
107
  // Collect debug components
112
108
  const debug = [];
113
109
  // Log prompt at Verbose level
114
- const promptDebug = logPrompt(toolName, command, systemPrompt);
110
+ const promptDebug = logPrompt(toolName, command, baseInstructions, formattedSkills, skillDefinitions);
115
111
  if (promptDebug) {
116
112
  debug.push(promptDebug);
117
113
  }
@@ -20,6 +20,7 @@ export const Palette = {
20
20
  Yellow: '#cccc5c',
21
21
  Orange: '#f48c80',
22
22
  MediumOrange: '#d07560',
23
+ WarmOrange: '#ce985e',
23
24
  DarkOrange: '#ab5e40',
24
25
  BurntOrange: '#cc7a5c',
25
26
  Red: '#cc5c5c',
@@ -132,7 +133,7 @@ const taskColors = {
132
133
  */
133
134
  const feedbackColors = {
134
135
  [FeedbackType.Info]: Colors.Status.Info,
135
- [FeedbackType.Warning]: Palette.Yellow,
136
+ [FeedbackType.Warning]: Palette.WarmOrange,
136
137
  [FeedbackType.Succeeded]: Colors.Status.Success,
137
138
  [FeedbackType.Aborted]: Palette.MediumOrange,
138
139
  [FeedbackType.Failed]: Colors.Status.Error,
@@ -1,7 +1,19 @@
1
1
  import { DebugLevel } from '../configuration/types.js';
2
- import { createDebug } from './components.js';
3
2
  import { loadDebugSetting } from '../configuration/io.js';
4
3
  import { Palette } from './colors.js';
4
+ import { createDebug } from './components.js';
5
+ /**
6
+ * Enum controlling what content is shown in debug prompt output
7
+ * - LLM: Exact prompt as sent to LLM (no display formatting)
8
+ * - Skills: Same content with visual separators for readability
9
+ * - Summary: Condensed view (Name, Steps, Execution only)
10
+ */
11
+ export var PromptDisplay;
12
+ (function (PromptDisplay) {
13
+ PromptDisplay["LLM"] = "llm";
14
+ PromptDisplay["Skills"] = "skills";
15
+ PromptDisplay["Summary"] = "summary";
16
+ })(PromptDisplay || (PromptDisplay = {}));
5
17
  /**
6
18
  * Debug logger for the application
7
19
  * Logs information based on the current debug level setting
@@ -49,24 +61,125 @@ export function getWarnings() {
49
61
  warnings.length = 0;
50
62
  return result;
51
63
  }
64
+ /**
65
+ * Content width for debug display (matches Debug component)
66
+ * Box width 80 - 2 borders - 4 padding = 74 chars
67
+ */
68
+ const DISPLAY_CONTENT_WIDTH = 74;
69
+ /**
70
+ * Join sections with separators matching display width
71
+ */
72
+ function joinWithSeparators(sections) {
73
+ const separator = '-'.repeat(DISPLAY_CONTENT_WIDTH);
74
+ return sections.join('\n\n' + separator + '\n\n');
75
+ }
76
+ /**
77
+ * Format a single skill definition as summary markdown
78
+ */
79
+ function formatSkillSummary(skill) {
80
+ const lines = [];
81
+ lines.push(`### Name`);
82
+ lines.push(skill.name);
83
+ lines.push('');
84
+ if (skill.steps.length > 0) {
85
+ lines.push(`### Steps`);
86
+ for (const step of skill.steps) {
87
+ lines.push(`- ${step}`);
88
+ }
89
+ lines.push('');
90
+ }
91
+ if (skill.execution.length > 0) {
92
+ lines.push(`### Execution`);
93
+ for (const cmd of skill.execution) {
94
+ lines.push(`- ${cmd}`);
95
+ }
96
+ }
97
+ return lines.join('\n').trim();
98
+ }
99
+ /**
100
+ * Format skill definitions as summary for debug display
101
+ * Shows only Name, Steps, and Execution with visual separators
102
+ */
103
+ export function formatSkillsSummary(definitions) {
104
+ if (definitions.length === 0) {
105
+ return '(no skills)';
106
+ }
107
+ const header = '## Available Skills';
108
+ const skillSummaries = definitions.map(formatSkillSummary);
109
+ return joinWithSeparators([header, ...skillSummaries]);
110
+ }
111
+ /**
112
+ * Format skills section with visual separators for debug display
113
+ * Layout: Header description -> separator -> skills separated by lines
114
+ */
115
+ function formatSkillsForDisplay(formattedSkills) {
116
+ if (!formattedSkills) {
117
+ return '(no skills)';
118
+ }
119
+ // Find the header (everything before first ### Name)
120
+ const firstNameIndex = formattedSkills.search(/^###\s+Name/m);
121
+ if (firstNameIndex === -1) {
122
+ return '(no skills)';
123
+ }
124
+ const header = formattedSkills.slice(0, firstNameIndex).trim();
125
+ const skillsContent = formattedSkills.slice(firstNameIndex);
126
+ // Split by ### Name to get individual skills
127
+ const skillParts = skillsContent
128
+ .split(/(?=^###\s+Name)/m)
129
+ .map((s) => s.trim())
130
+ .filter(Boolean);
131
+ if (skillParts.length === 0) {
132
+ return '(no skills)';
133
+ }
134
+ // Join header and skills with separators
135
+ return joinWithSeparators([header, ...skillParts]);
136
+ }
137
+ /**
138
+ * Format prompt content based on the specified detail level
139
+ *
140
+ * - LLM: Returns header + base instructions + formatted skills (as sent to LLM)
141
+ * - Skills: Returns header + skills with visual separators (no base instructions)
142
+ * - Summary: Returns header + skill summaries (Name, Steps, Execution)
143
+ */
144
+ export function formatPromptContent(toolName, command, baseInstructions, formattedSkills, mode, definitions) {
145
+ const header = ['', `Tool: ${toolName}`, `Command: ${command}`];
146
+ switch (mode) {
147
+ case PromptDisplay.LLM:
148
+ return [...header, '', baseInstructions + formattedSkills].join('\n');
149
+ case PromptDisplay.Skills: {
150
+ // Layout: header -> separator -> skills with visual separators
151
+ const headerString = header.join('\n');
152
+ const skillsDisplay = formatSkillsForDisplay(formattedSkills);
153
+ return joinWithSeparators([headerString, skillsDisplay]);
154
+ }
155
+ case PromptDisplay.Summary: {
156
+ const headerString = header.join('\n');
157
+ const summary = definitions
158
+ ? formatSkillsSummary(definitions)
159
+ : '(no skills)';
160
+ return joinWithSeparators([headerString, summary]);
161
+ }
162
+ }
163
+ }
52
164
  /**
53
165
  * Create debug component for system prompts sent to the LLM
54
166
  * Only creates at Verbose level
167
+ *
168
+ * @param toolName - Name of the tool being invoked
169
+ * @param command - User command being processed
170
+ * @param baseInstructions - Base tool instructions (without skills)
171
+ * @param formattedSkills - Formatted skills section (as sent to LLM)
172
+ * @param definitions - Parsed skill definitions for summary display
55
173
  */
56
- export function logPrompt(toolName, command, instructions) {
174
+ export function logPrompt(toolName, command, baseInstructions, formattedSkills, definitions = []) {
57
175
  if (currentDebugLevel !== DebugLevel.Verbose) {
58
176
  return null;
59
177
  }
60
- const content = [
61
- '',
62
- `Tool: ${toolName}`,
63
- `Command: ${command}`,
64
- '',
65
- instructions,
66
- ].join('\n');
67
- // Calculate stats for the instructions
68
- const lines = instructions.split('\n').length;
69
- const bytes = Buffer.byteLength(instructions, 'utf-8');
178
+ const content = formatPromptContent(toolName, command, baseInstructions, formattedSkills, PromptDisplay.Summary, definitions);
179
+ // Calculate stats for the full prompt
180
+ const fullPrompt = baseInstructions + formattedSkills;
181
+ const lines = fullPrompt.split('\n').length;
182
+ const bytes = Buffer.byteLength(fullPrompt, 'utf-8');
70
183
  const title = `SYSTEM PROMPT (${String(lines)} lines, ${String(bytes)} bytes)`;
71
184
  return createDebug({ title, content, color: Palette.Gray });
72
185
  }
@@ -85,5 +198,5 @@ export function logResponse(toolName, response, durationMs) {
85
198
  JSON.stringify(response, null, 2),
86
199
  ].join('\n');
87
200
  const title = `LLM RESPONSE (${String(durationMs)} ms)`;
88
- return createDebug({ title, content, color: Palette.AshGray });
201
+ return createDebug({ title, content, color: Palette.LightGray });
89
202
  }
@@ -162,3 +162,22 @@ export function getExecutionErrorMessage(_error) {
162
162
  ];
163
163
  return messages[Math.floor(Math.random() * messages.length)];
164
164
  }
165
+ /**
166
+ * Returns a loading message while fetching an answer.
167
+ * Randomly selects from variations to sound natural.
168
+ */
169
+ export function getAnswerLoadingMessage() {
170
+ const messages = [
171
+ 'Finding that out for you.',
172
+ 'Looking into this.',
173
+ 'Let me find out.',
174
+ 'One moment please.',
175
+ 'Checking on that.',
176
+ 'Let me look that up.',
177
+ 'Give me a moment.',
178
+ 'Looking that up now.',
179
+ 'Let me check.',
180
+ 'Just a moment.',
181
+ ];
182
+ return messages[Math.floor(Math.random() * messages.length)];
183
+ }