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.
- package/README.md +97 -40
- package/dist/components/Component.js +5 -3
- package/dist/components/controllers/Command.js +3 -19
- package/dist/components/controllers/Config.js +109 -75
- package/dist/components/controllers/Execute.js +14 -11
- package/dist/components/controllers/Introspect.js +2 -4
- package/dist/components/controllers/Validate.js +3 -2
- package/dist/components/views/Execute.js +1 -1
- package/dist/components/views/Output.js +84 -32
- package/dist/components/views/Subtask.js +12 -7
- package/dist/components/views/Task.js +4 -5
- package/dist/components/views/Upcoming.js +1 -1
- package/dist/execution/runner.js +45 -29
- package/dist/services/anthropic.js +5 -4
- package/dist/services/logger.js +37 -9
- package/dist/services/monitor.js +19 -3
- package/dist/services/refinement.js +1 -7
- package/dist/services/router.js +223 -95
- package/dist/services/shell.js +18 -3
- package/dist/services/utils.js +11 -0
- package/dist/skills/schedule.md +7 -0
- package/package.json +10 -10
|
@@ -1,17 +1,35 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
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
|
|
7
|
+
const INCOMPLETE_LINE_DELAY = 3000;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
|
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({
|
|
50
|
-
const config = computeDisplayConfig(
|
|
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 {
|
|
54
|
-
return (
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 (
|
|
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
|
|
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, {
|
|
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 (
|
|
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
|
};
|
package/dist/execution/runner.js
CHANGED
|
@@ -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
|
|
4
|
-
const
|
|
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
|
|
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
|
-
|
|
25
|
-
stderr,
|
|
17
|
+
chunks,
|
|
26
18
|
error,
|
|
27
19
|
workdir,
|
|
20
|
+
currentMemory,
|
|
28
21
|
});
|
|
29
|
-
// Throttle updates to avoid excessive re-renders (
|
|
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 >=
|
|
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
|
-
},
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
60
|
+
// Clear callbacks and pending timeout
|
|
59
61
|
setOutputCallback(undefined);
|
|
62
|
+
setMemoryCallback(undefined);
|
|
60
63
|
clearTimeout(pendingTimeout);
|
|
61
64
|
const elapsed = calculateElapsed(startTime);
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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 {
|
|
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,
|
|
111
|
+
const promptDebug = logPrompt(toolName, command, baseInstructions, formattedSkills, context);
|
|
111
112
|
if (promptDebug) {
|
|
112
113
|
debug.push(promptDebug);
|
|
113
114
|
}
|
package/dist/services/logger.js
CHANGED
|
@@ -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 +
|
|
246
|
+
* - Summary: Returns header + context (caller-provided summary)
|
|
247
247
|
*/
|
|
248
|
-
export function formatPromptContent(toolName, command, baseInstructions, formattedSkills, mode,
|
|
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
|
-
|
|
262
|
-
?
|
|
263
|
-
:
|
|
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
|
|
304
|
+
* @param context - Context summary to display in debug output
|
|
277
305
|
*/
|
|
278
|
-
export function logPrompt(toolName, command, baseInstructions, formattedSkills,
|
|
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,
|
|
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;
|
package/dist/services/monitor.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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(),
|