prompt-language-shell 0.9.8 → 1.0.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.
@@ -1,17 +1,35 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { Palette } from '../../services/colors.js';
4
4
  import { ExecutionStatus } from '../../services/shell.js';
5
5
  const MAX_LINES = 8;
6
6
  const MAX_WIDTH = 75;
7
- const MINIMAL_INFO_THRESHOLD = 2;
7
+ const INCOMPLETE_LINE_DELAY = 3000;
8
8
  /**
9
- * Get the last N lines from text, filtering out empty/whitespace-only lines.
10
- * Handles carriage returns used in progress output by keeping only the
11
- * content after the last \r in each line.
9
+ * Determine if an incomplete line should be shown.
10
+ * Shows incomplete lines if they are older than the delay threshold.
11
+ */
12
+ function shouldShowIncompleteLine(lastChunk) {
13
+ return Date.now() - lastChunk.timestamp >= INCOMPLETE_LINE_DELAY;
14
+ }
15
+ /**
16
+ * Split a line into chunks of maxWidth characters.
12
17
  */
13
- export function getLastLines(text, maxLines = MAX_LINES) {
14
- const lines = text
18
+ function splitIntoRows(line, maxWidth) {
19
+ if (line.length <= maxWidth)
20
+ return [line];
21
+ const rows = [];
22
+ for (let i = 0; i < line.length; i += maxWidth) {
23
+ rows.push(line.slice(i, i + maxWidth));
24
+ }
25
+ return rows;
26
+ }
27
+ /**
28
+ * Process text into terminal rows.
29
+ * Handles carriage returns and splits long lines into chunks.
30
+ */
31
+ function textToRows(text, maxWidth) {
32
+ return text
15
33
  .trim()
16
34
  .split(/\r?\n/)
17
35
  .map((line) => {
@@ -19,38 +37,72 @@ export function getLastLines(text, maxLines = MAX_LINES) {
19
37
  const lastCR = line.lastIndexOf('\r');
20
38
  return lastCR >= 0 ? line.slice(lastCR + 1) : line;
21
39
  })
22
- .filter((line) => line.trim().length > 0);
23
- return lines.length <= maxLines ? lines : lines.slice(-maxLines);
40
+ .filter((line) => line.trim().length > 0)
41
+ .flatMap((line) => splitIntoRows(line, maxWidth));
42
+ }
43
+ /**
44
+ * Get the last N terminal rows from text.
45
+ * Splits long lines into chunks of maxWidth characters, then takes the last N.
46
+ * Handles carriage returns used in progress output by keeping only the
47
+ * content after the last \r in each line.
48
+ */
49
+ export function getLastLines(text, maxLines = MAX_LINES, maxWidth = MAX_WIDTH) {
50
+ const rows = textToRows(text, maxWidth);
51
+ return rows.length <= maxLines ? rows : rows.slice(-maxLines);
52
+ }
53
+ /**
54
+ * Convert output chunks to terminal rows.
55
+ * Sorts by timestamp, combines text, deduplicates adjacent lines.
56
+ * When not finished, only shows complete lines (ending with newline),
57
+ * unless the last chunk is older than INCOMPLETE_LINE_DELAY.
58
+ */
59
+ export function chunksToRows(chunks, maxLines = MAX_LINES, maxWidth = MAX_WIDTH, isFinished = true) {
60
+ if (chunks.length === 0)
61
+ return [];
62
+ // Sort by timestamp and combine text (chunks already contain line endings)
63
+ const sorted = [...chunks].sort((a, b) => a.timestamp - b.timestamp);
64
+ let combined = sorted.map((c) => c.text).join('');
65
+ // When not finished, only show complete lines (strip trailing incomplete line)
66
+ // unless the last chunk is old enough to be shown
67
+ if (!isFinished && !combined.endsWith('\n')) {
68
+ const lastChunk = sorted[sorted.length - 1];
69
+ if (!shouldShowIncompleteLine(lastChunk)) {
70
+ const lastNewline = combined.lastIndexOf('\n');
71
+ if (lastNewline >= 0) {
72
+ combined = combined.slice(0, lastNewline + 1);
73
+ }
74
+ else {
75
+ // No complete lines yet
76
+ return [];
77
+ }
78
+ }
79
+ }
80
+ // Convert to rows
81
+ const rows = textToRows(combined, maxWidth);
82
+ // Deduplicate adjacent identical lines
83
+ const deduplicated = rows.filter((row, index) => index === 0 || row !== rows[index - 1]);
84
+ return deduplicated.length <= maxLines
85
+ ? deduplicated
86
+ : deduplicated.slice(-maxLines);
24
87
  }
25
88
  /**
26
89
  * Compute display configuration for output rendering.
27
- * Encapsulates the logic for what to show and how to style it.
28
90
  */
29
- export function computeDisplayConfig(stdout, stderr, status, isFinished) {
30
- const hasStdout = stdout.trim().length > 0;
31
- const hasStderr = stderr.trim().length > 0;
32
- if (!hasStdout && !hasStderr)
91
+ export function computeDisplayConfig(chunks, status, isFinished) {
92
+ if (chunks.length === 0)
93
+ return null;
94
+ const rows = chunksToRows(chunks, MAX_LINES, MAX_WIDTH, isFinished);
95
+ if (rows.length === 0)
33
96
  return null;
34
- const stdoutLines = hasStdout ? getLastLines(stdout) : [];
35
- const stderrLines = hasStderr ? getLastLines(stderr) : [];
36
- // Show stdout if no stderr, or if stderr is minimal (provides context)
37
- const showStdout = hasStdout && (!hasStderr || stderrLines.length <= MINIMAL_INFO_THRESHOLD);
38
- // Darker colors for finished tasks
97
+ // Use yellow for failed status, otherwise gray (darker if finished)
39
98
  const baseColor = isFinished ? Palette.DarkGray : Palette.Gray;
40
- const stderrColor = status === ExecutionStatus.Failed ? Palette.Yellow : baseColor;
41
- return {
42
- stdoutLines,
43
- stderrLines,
44
- showStdout,
45
- stdoutColor: baseColor,
46
- stderrColor,
47
- };
99
+ const color = status === ExecutionStatus.Failed ? Palette.Yellow : baseColor;
100
+ return { rows, color };
48
101
  }
49
- export function Output({ stdout, stderr, status, isFinished }) {
50
- const config = computeDisplayConfig(stdout, stderr, status, isFinished ?? false);
102
+ export function Output({ chunks, status, isFinished }) {
103
+ const config = computeDisplayConfig(chunks, status, isFinished ?? false);
51
104
  if (!config)
52
105
  return null;
53
- const { stdoutLines, stderrLines, showStdout, stdoutColor, stderrColor } = config;
54
- return (_jsxs(Box, { marginTop: 1, marginLeft: 5, flexDirection: "column", width: MAX_WIDTH, children: [showStdout &&
55
- stdoutLines.map((line, index) => (_jsx(Text, { color: stdoutColor, wrap: "wrap", children: line }, `out-${index}`))), stderrLines.map((line, index) => (_jsx(Text, { color: stderrColor, wrap: "wrap", children: line }, `err-${index}`)))] }));
106
+ const { rows, color } = config;
107
+ return (_jsx(Box, { marginTop: 1, marginLeft: 5, flexDirection: "column", width: MAX_WIDTH, children: rows.map((row, index) => (_jsx(Text, { color: color, wrap: "truncate", children: row }, index))) }));
56
108
  }
@@ -1,25 +1,30 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
+ import { loadDebugSetting } from '../../configuration/io.js';
4
+ import { DebugLevel } from '../../configuration/types.js';
3
5
  import { getStatusColors, Palette, STATUS_ICONS, } from '../../services/colors.js';
4
6
  import { ExecutionStatus } from '../../services/shell.js';
5
- import { formatDuration } from '../../services/utils.js';
7
+ import { formatDuration, formatMemory } from '../../services/utils.js';
6
8
  import { Spinner } from './Spinner.js';
7
9
  /**
8
10
  * Pure display component for a single subtask.
9
11
  * Shows label, command, status icon, and elapsed time.
10
12
  */
11
- export function SubtaskView({ label, command, status, elapsed, }) {
13
+ export function SubtaskView({ label, command, status, elapsed, currentMemory, }) {
12
14
  const colors = getStatusColors(status);
15
+ const debugLevel = loadDebugSetting();
16
+ const isVerbose = debugLevel === DebugLevel.Verbose;
13
17
  const isCancelled = status === ExecutionStatus.Cancelled;
14
18
  const isAborted = status === ExecutionStatus.Aborted;
19
+ const isRunning = status === ExecutionStatus.Running;
15
20
  const isFinished = status === ExecutionStatus.Success ||
16
21
  status === ExecutionStatus.Failed ||
17
22
  status === ExecutionStatus.Aborted;
18
23
  // Apply strikethrough for cancelled and aborted tasks
19
24
  const shouldStrikethrough = isCancelled || isAborted;
20
- const formatText = (text) => shouldStrikethrough ? text.split('').join('\u0336') + '\u0336' : text;
21
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: colors.icon, children: STATUS_ICONS[status] }), _jsx(Text, { color: colors.description, children: shouldStrikethrough
22
- ? formatText(label || command.description)
23
- : label || command.description }), (isFinished || status === ExecutionStatus.Running) &&
24
- elapsed !== undefined && (_jsxs(Text, { color: Palette.DarkGray, children: ["(", formatDuration(elapsed), ")"] }))] }), _jsxs(Box, { paddingLeft: 5, flexDirection: "row", children: [_jsx(Box, { children: _jsx(Text, { color: colors.symbol, children: "\u221F " }) }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: colors.command, children: command.command }), status === ExecutionStatus.Running && _jsx(Spinner, {})] })] })] }));
25
+ // Show memory in verbose mode while running
26
+ const showMemory = isVerbose && isRunning && currentMemory !== undefined;
27
+ // Build time/memory display
28
+ const showTimeInfo = (isFinished || isRunning) && elapsed !== undefined;
29
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: colors.icon, children: STATUS_ICONS[status] }), _jsx(Text, { color: colors.description, strikethrough: shouldStrikethrough, children: label || command.description }), showTimeInfo && (_jsxs(Text, { color: Palette.DarkGray, children: ["(", formatDuration(elapsed), ")"] })), showMemory && (_jsx(Text, { color: Palette.Yellow, children: formatMemory(currentMemory) }))] }), _jsxs(Box, { paddingLeft: 5, flexDirection: "row", children: [_jsx(Box, { children: _jsx(Text, { color: colors.symbol, children: "\u221F " }) }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: colors.command, children: command.command }), isRunning && _jsx(Spinner, {})] })] })] }));
25
30
  }
@@ -6,13 +6,12 @@ import { Output } from './Output.js';
6
6
  import { SubtaskView } from './Subtask.js';
7
7
  /**
8
8
  * Pure display component for a task.
9
- * Combines SubtaskView (label/command/status) with Output (stdout/stderr).
9
+ * Combines SubtaskView (label/command/status) with Output (chunks).
10
10
  * Output is shown during active execution, or in timeline only with debug mode.
11
11
  */
12
- export function TaskView({ label, command, status, elapsed, output, isFinished, isActive = false, }) {
13
- const stdout = output?.stdout ?? '';
14
- const stderr = output?.stderr ?? '';
12
+ export function TaskView({ label, command, status, elapsed, currentMemory, output, isFinished, isActive = false, }) {
13
+ const chunks = output?.chunks ?? [];
15
14
  // Show output during active execution, or in timeline only with debug enabled
16
15
  const showOutput = isActive || loadDebugSetting() !== DebugLevel.None;
17
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(SubtaskView, { label: label, command: command, status: status, elapsed: elapsed }), showOutput && (_jsx(Output, { stdout: stdout, stderr: stderr, isFinished: isFinished, status: status }, `${stdout.length}-${stderr.length}`))] }));
16
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(SubtaskView, { label: label, command: command, status: status, elapsed: elapsed, currentMemory: currentMemory }), showOutput && (_jsx(Output, { chunks: chunks, isFinished: isFinished, status: status }))] }));
18
17
  }
@@ -25,6 +25,6 @@ export const Upcoming = ({ items, status = ExecutionStatus.Pending, }) => {
25
25
  return (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsx(Text, { color: Palette.Gray, children: STATUS_LABELS[status] }), items.map((name, index) => {
26
26
  const isLast = index === items.length - 1;
27
27
  const symbol = isLast ? BRANCH_LAST : BRANCH_MIDDLE;
28
- return (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: Palette.DarkGray, strikethrough: strikethrough, children: [symbol, " ", name] }) }, index));
28
+ return (_jsxs(Box, { marginLeft: 1, gap: 1, children: [_jsx(Text, { color: Palette.DarkGray, children: symbol }), _jsx(Text, { color: Palette.DarkGray, strikethrough: strikethrough, children: name })] }, index));
29
29
  })] }));
30
30
  };
@@ -1,37 +1,31 @@
1
- import { ExecutionResult, ExecutionStatus, executeCommand, setOutputCallback, } from '../services/shell.js';
1
+ import { ExecutionResult, ExecutionStatus, executeCommand, setMemoryCallback, 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
+ // Maximum number of output chunks to keep in memory
4
+ const MAX_OUTPUT_CHUNKS = 256;
12
5
  /**
13
6
  * Execute a single task and track its progress.
14
7
  * All execution logic is contained here, outside of React components.
15
8
  */
16
9
  export async function executeTask(command, index, callbacks) {
17
10
  const startTime = Date.now();
18
- let stdout = '';
19
- let stderr = '';
11
+ let chunks = [];
20
12
  let error = '';
21
13
  let workdir;
14
+ let currentMemory;
22
15
  // Helper to create current output snapshot
23
16
  const createOutput = () => ({
24
- stdout,
25
- stderr,
17
+ chunks,
26
18
  error,
27
19
  workdir,
20
+ currentMemory,
28
21
  });
29
- // Throttle updates to avoid excessive re-renders (100ms minimum interval)
22
+ // Throttle updates to avoid excessive re-renders (80ms minimum interval)
30
23
  let lastUpdateTime = 0;
31
24
  let pendingTimeout;
25
+ const THROTTLE_INTERVAL = 80;
32
26
  const throttledUpdate = () => {
33
27
  const now = Date.now();
34
- if (now - lastUpdateTime >= 100) {
28
+ if (now - lastUpdateTime >= THROTTLE_INTERVAL) {
35
29
  lastUpdateTime = now;
36
30
  callbacks.onUpdate(createOutput());
37
31
  }
@@ -40,29 +34,50 @@ export async function executeTask(command, index, callbacks) {
40
34
  pendingTimeout = undefined;
41
35
  lastUpdateTime = Date.now();
42
36
  callbacks.onUpdate(createOutput());
43
- }, 100 - (now - lastUpdateTime));
37
+ }, THROTTLE_INTERVAL - (now - lastUpdateTime));
44
38
  }
45
39
  };
46
- // Set up output streaming callback
40
+ // Set up output streaming callback - store chunks with timestamps
47
41
  setOutputCallback((data, stream) => {
48
- if (stream === 'stdout') {
49
- stdout = limitLines(stdout + data);
50
- }
51
- else {
52
- stderr = limitLines(stderr + data);
42
+ chunks.push({
43
+ text: data,
44
+ timestamp: Date.now(),
45
+ source: stream,
46
+ });
47
+ // Limit chunks to prevent memory exhaustion
48
+ if (chunks.length > MAX_OUTPUT_CHUNKS) {
49
+ chunks = chunks.slice(-MAX_OUTPUT_CHUNKS);
53
50
  }
54
51
  throttledUpdate();
55
52
  });
53
+ // Set up memory callback to track current memory
54
+ setMemoryCallback((memoryMB) => {
55
+ currentMemory = memoryMB;
56
+ throttledUpdate();
57
+ });
56
58
  try {
57
59
  const result = await executeCommand(command, undefined, index);
58
- // Clear callback and pending timeout
60
+ // Clear callbacks and pending timeout
59
61
  setOutputCallback(undefined);
62
+ setMemoryCallback(undefined);
60
63
  clearTimeout(pendingTimeout);
61
64
  const elapsed = calculateElapsed(startTime);
62
- // Update final output from result
63
- stdout = result.output;
64
- stderr = result.errors;
65
+ const now = Date.now();
66
+ // Update workdir from result
65
67
  workdir = result.workdir;
68
+ // Add final output/errors as chunks only if not already captured during streaming
69
+ const hasStreamedStdout = chunks.some((c) => c.source === 'stdout');
70
+ const hasStreamedStderr = chunks.some((c) => c.source === 'stderr');
71
+ if (result.output && result.output.trim() && !hasStreamedStdout) {
72
+ chunks.push({ text: result.output, timestamp: now, source: 'stdout' });
73
+ }
74
+ if (result.errors && result.errors.trim() && !hasStreamedStderr) {
75
+ chunks.push({
76
+ text: result.errors,
77
+ timestamp: now + 1,
78
+ source: 'stderr',
79
+ });
80
+ }
66
81
  if (result.result === ExecutionResult.Success) {
67
82
  const output = createOutput();
68
83
  callbacks.onUpdate(output);
@@ -79,8 +94,9 @@ export async function executeTask(command, index, callbacks) {
79
94
  }
80
95
  }
81
96
  catch (err) {
82
- // Clear callback and pending timeout
97
+ // Clear callbacks and pending timeout
83
98
  setOutputCallback(undefined);
99
+ setMemoryCallback(undefined);
84
100
  clearTimeout(pendingTimeout);
85
101
  const elapsed = calculateElapsed(startTime);
86
102
  const errorMsg = err instanceof Error ? err.message : 'Unknown error';
@@ -95,5 +111,5 @@ export async function executeTask(command, index, callbacks) {
95
111
  * Create an empty execution output
96
112
  */
97
113
  export function createEmptyOutput() {
98
- return { stdout: '', stderr: '', error: '' };
114
+ return { chunks: [], error: '' };
99
115
  }
@@ -1,6 +1,6 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
2
  import { getAvailableConfigStructure, getConfiguredKeys, } from '../configuration/schema.js';
3
- import { logPrompt, logResponse } from './logger.js';
3
+ import { formatConfigContext, formatSkillsContext, logPrompt, logResponse, } from './logger.js';
4
4
  import { loadSkillsForPrompt } from './skills.js';
5
5
  import { toolRegistry } from './registry.js';
6
6
  import { CommandResultSchema, IntrospectResultSchema, } from '../types/schemas.js';
@@ -77,13 +77,13 @@ export class AnthropicService {
77
77
  // Load base instructions and skills
78
78
  const baseInstructions = customInstructions || toolRegistry.getInstructions(toolName);
79
79
  let formattedSkills = '';
80
- let skillDefinitions = [];
81
80
  let systemPrompt = baseInstructions;
81
+ let context;
82
82
  if (!customInstructions && usesSkills) {
83
83
  const skillsResult = loadSkillsForPrompt();
84
84
  formattedSkills = skillsResult.formatted;
85
- skillDefinitions = skillsResult.definitions;
86
85
  systemPrompt += formattedSkills;
86
+ context = formatSkillsContext(skillsResult.definitions);
87
87
  }
88
88
  // Add config structure for configure tool only
89
89
  if (!customInstructions && toolName === 'configure') {
@@ -95,6 +95,7 @@ export class AnthropicService {
95
95
  '\n\nConfigured keys (keys that exist in config file):\n' +
96
96
  JSON.stringify(configuredKeys, null, 2);
97
97
  systemPrompt += configSection;
98
+ context = formatConfigContext(configStructure, configuredKeys);
98
99
  }
99
100
  // Build tools array - add web search for answer tool
100
101
  const tools = [tool];
@@ -107,7 +108,7 @@ export class AnthropicService {
107
108
  // Collect debug components
108
109
  const debug = [];
109
110
  // Log prompt at Verbose level
110
- const promptDebug = logPrompt(toolName, command, baseInstructions, formattedSkills, skillDefinitions);
111
+ const promptDebug = logPrompt(toolName, command, baseInstructions, formattedSkills, context);
111
112
  if (promptDebug) {
112
113
  debug.push(promptDebug);
113
114
  }
@@ -243,9 +243,9 @@ function formatSkillsForDisplay(formattedSkills) {
243
243
  *
244
244
  * - LLM: Returns header + base instructions + formatted skills (as sent to LLM)
245
245
  * - Skills: Returns header + skills with visual separators (no base instructions)
246
- * - Summary: Returns header + skill summaries (Name, Steps, Execution)
246
+ * - Summary: Returns header + context (caller-provided summary)
247
247
  */
248
- export function formatPromptContent(toolName, command, baseInstructions, formattedSkills, mode, definitions) {
248
+ export function formatPromptContent(toolName, command, baseInstructions, formattedSkills, mode, context) {
249
249
  switch (mode) {
250
250
  case PromptDisplay.LLM: {
251
251
  const header = ['', `**Tool:** ${toolName}`];
@@ -258,13 +258,41 @@ export function formatPromptContent(toolName, command, baseInstructions, formatt
258
258
  }
259
259
  case PromptDisplay.Summary: {
260
260
  const header = `\nTool: ${toolName}\nCommand: ${command}`;
261
- const summary = definitions
262
- ? formatSkillsSummary(definitions)
263
- : '(no skills)';
264
- return joinWithSeparators([header, summary]);
261
+ return context
262
+ ? joinWithSeparators([header, context])
263
+ : joinWithSeparators([header]);
265
264
  }
266
265
  }
267
266
  }
267
+ /**
268
+ * Context for tools that have no skills or custom context
269
+ */
270
+ export const NO_SKILLS_CONTEXT = '(no skills)';
271
+ /**
272
+ * Format skill definitions as context for debug display
273
+ * Returns NO_SKILLS_CONTEXT when definitions array is empty
274
+ */
275
+ export function formatSkillsContext(definitions) {
276
+ if (definitions.length === 0) {
277
+ return NO_SKILLS_CONTEXT;
278
+ }
279
+ return formatSkillsSummary(definitions);
280
+ }
281
+ /**
282
+ * Format config structure as context for debug display
283
+ */
284
+ export function formatConfigContext(structure, configuredKeys) {
285
+ const structureYaml = Object.entries(structure)
286
+ .map(([key, desc]) => `${key}: ${String(desc)}`)
287
+ .join('\n');
288
+ const keysSection = configuredKeys.length > 0
289
+ ? configuredKeys.map((k) => `- ${k}`).join('\n')
290
+ : '(none)';
291
+ return ('## Config Structure\n\n' +
292
+ structureYaml +
293
+ '\n\n## Configured Keys\n\n' +
294
+ keysSection);
295
+ }
268
296
  /**
269
297
  * Create debug component for system prompts sent to the LLM
270
298
  * Creates UI component at Verbose level, writes to file at Info or Verbose
@@ -273,9 +301,9 @@ export function formatPromptContent(toolName, command, baseInstructions, formatt
273
301
  * @param command - User command being processed
274
302
  * @param baseInstructions - Base tool instructions (without skills)
275
303
  * @param formattedSkills - Formatted skills section (as sent to LLM)
276
- * @param definitions - Parsed skill definitions for summary display
304
+ * @param context - Context summary to display in debug output
277
305
  */
278
- export function logPrompt(toolName, command, baseInstructions, formattedSkills, definitions = []) {
306
+ export function logPrompt(toolName, command, baseInstructions, formattedSkills, context) {
279
307
  // Write to file at Info or Verbose level (full LLM format)
280
308
  if (currentDebugLevel !== DebugLevel.None) {
281
309
  const userPrompt = `# User Command\n\n\`\`\`\n${command}\n\`\`\`\n\n`;
@@ -286,7 +314,7 @@ export function logPrompt(toolName, command, baseInstructions, formattedSkills,
286
314
  if (currentDebugLevel !== DebugLevel.Verbose) {
287
315
  return null;
288
316
  }
289
- const content = formatPromptContent(toolName, command, baseInstructions, formattedSkills, PromptDisplay.Summary, definitions);
317
+ const content = formatPromptContent(toolName, command, baseInstructions, formattedSkills, PromptDisplay.Summary, context);
290
318
  // Calculate stats for the full prompt
291
319
  const fullPrompt = baseInstructions + formattedSkills;
292
320
  const lines = fullPrompt.split('\n').length;
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from 'fs';
3
3
  import { platform } from 'os';
4
4
  import { promisify } from 'util';
5
5
  // Memory monitoring constants
6
- const MEMORY_CHECK_INTERVAL = 1000;
6
+ const MEMORY_CHECK_INTERVAL = 250;
7
7
  const DEFAULT_PAGE_SIZE = 4096;
8
8
  export const SIGKILL_GRACE_PERIOD = 3000;
9
9
  /**
@@ -183,25 +183,29 @@ export class MemoryMonitor {
183
183
  memoryLimit;
184
184
  limitBytes;
185
185
  onExceeded;
186
+ onMemoryUpdate;
186
187
  state = MonitorState.Idle;
187
188
  getMemoryFn;
188
- constructor(child, memoryLimitMB, onExceeded, getMemoryFn) {
189
+ currentMemoryMB = 0;
190
+ constructor(child, memoryLimitMB, onExceeded, getMemoryFn, onMemoryUpdate) {
189
191
  this.child = child;
190
192
  this.memoryLimit = memoryLimitMB;
191
193
  this.limitBytes = memoryLimitMB * 1024 * 1024;
192
194
  this.onExceeded = onExceeded;
195
+ this.onMemoryUpdate = onMemoryUpdate;
193
196
  // Always monitor full process tree by default
194
197
  this.getMemoryFn = getMemoryFn ?? getProcessTreeMemoryBytes;
195
198
  }
196
199
  /**
197
200
  * Start monitoring the child process memory.
198
201
  * Uses async self-scheduling loop instead of setInterval for non-blocking.
202
+ * Performs an immediate check, then polls at regular intervals.
199
203
  */
200
204
  start() {
201
205
  if (!this.child.pid)
202
206
  return;
203
207
  this.state = MonitorState.Running;
204
- this.scheduleNextCheck();
208
+ void this.checkMemory();
205
209
  }
206
210
  /**
207
211
  * Schedule the next memory check after the configured interval.
@@ -231,6 +235,11 @@ export class MemoryMonitor {
231
235
  // Re-check after async operation - state may have changed
232
236
  if (this.state !== MonitorState.Running)
233
237
  return; // eslint-disable-line @typescript-eslint/no-unnecessary-condition
238
+ // Track current memory
239
+ if (memoryBytes !== undefined) {
240
+ this.currentMemoryMB = Math.ceil(memoryBytes / 1024 / 1024);
241
+ this.onMemoryUpdate?.(this.currentMemoryMB);
242
+ }
234
243
  if (memoryBytes !== undefined && memoryBytes >= this.limitBytes) {
235
244
  this.terminateProcess(memoryBytes);
236
245
  }
@@ -285,4 +294,11 @@ export class MemoryMonitor {
285
294
  wasKilledByMemoryLimit() {
286
295
  return this.state === MonitorState.Killed;
287
296
  }
297
+ /**
298
+ * Get current memory in MB.
299
+ * Returns 0 if no memory has been recorded yet.
300
+ */
301
+ getCurrentMemoryMB() {
302
+ return this.currentMemoryMB;
303
+ }
288
304
  }
@@ -1,6 +1,5 @@
1
- import { ComponentStatus, } from '../types/components.js';
2
1
  import { formatTaskAsYaml } from '../execution/processing.js';
3
- import { createCommand, createRefinement } from './components.js';
2
+ import { createRefinement } from './components.js';
4
3
  import { formatErrorMessage, getRefiningMessage } from './messages.js';
5
4
  import { routeTasksWithConfirm } from './router.js';
6
5
  /**
@@ -8,11 +7,6 @@ import { routeTasksWithConfirm } from './router.js';
8
7
  * Called when user selects options from a plan with DEFINE tasks
9
8
  */
10
9
  export async function handleRefinement(selectedTasks, service, originalCommand, lifecycleHandlers, workflowHandlers, requestHandlers) {
11
- // Display the resolved command (from user's selection)
12
- // The first task's action contains the full resolved command
13
- const resolvedCommand = selectedTasks[0]?.action || originalCommand;
14
- const commandDisplay = createCommand({ command: resolvedCommand, service, onAborted: requestHandlers.onAborted }, ComponentStatus.Done);
15
- workflowHandlers.addToTimeline(commandDisplay);
16
10
  // Create and add refinement component to queue
17
11
  const refinementDef = createRefinement({
18
12
  text: getRefiningMessage(),