MCPcopy
hub / github.com/Doorman11991/smallcode / executeTool

Function executeTool

bin/executor.js:78–965  ·  view source on GitHub ↗
(name, args, ctx)

Source from the content-addressed store, hash-verified

76}
77
78async function executeTool(name, args, ctx) {
79 const { _fullscreenRef, mcpCall, memoryStore, pluginLoader, mcpClient, flags, config, tui } = ctx;
80 const { execSync } = require('child_process');
81 const cwd = process.cwd();
82
83 // Sanitize all string args — strip ANSI escape sequences the model may have
84 // hallucinated into command strings (e.g. color codes in bash arguments).
85 // Uses the comprehensive ANSI stripper from src/security/sanitize.js so
86 // we cover OSC, DCS, 8-bit C1, and other escape forms too — not just CSI.
87 function stripAnsi(str) { return secStripAnsi(str); }
88 if (args && typeof args === 'object') {
89 for (const key of Object.keys(args)) {
90 if (typeof args[key] === 'string') args[key] = stripAnsi(args[key]);
91 }
92 }
93
94 switch (name) {
95 case 'read_file': {
96 const safe = safeResolvePath(args.path, cwd);
97 if (!safe.ok) return { error: `read_file rejected: ${safe.reason}` };
98 const filePath = safe.fullPath;
99 if (!fs.existsSync(filePath)) return { error: `File not found: ${args.path} (checked: ${filePath})` };
100 // Mark as read so the write-guard (Feature 5) lets subsequent writes through
101 try { getReadTracker().recordRead(filePath, cwd); } catch {}
102 const content = fs.readFileSync(filePath, 'utf-8');
103 const lines = content.split('\n');
104 const start = (args.start_line || 1) - 1;
105 const end = args.end_line || lines.length;
106 const slice = lines.slice(start, end);
107 // Sanitize before sending to the model: strip ANSI/control chars and
108 // redact any secrets the file may contain (e.g. .env, token files).
109 const safeSlice = slice.map(l => sanitizeToolOutput(l));
110 const numbered = safeSlice.map((l, i) => `${String(start + i + 1).padStart(4)}│ ${l}`).join('\n');
111
112 // Diff-based context (Feature #16): when SMALLCODE_DIFF_CONTEXT=true
113 // and the model has already read this file, return a diff instead of the
114 // full content. Falls back to full content if diff is too large or if the
115 // file hasn't changed. Only applies when no line range is requested.
116 if (!args.start_line && !args.end_line) {
117 try {
118 const tracker = getFileStateTracker();
119 const result = tracker.record(filePath, content);
120 if (result.mode === 'unchanged') {
121 return { result: `${args.path} (${lines.length} lines — unchanged since last read, no diff)` };
122 }
123 if (result.mode === 'diff') {
124 return { result: `${args.path} changes since last read (${result.fullLength} lines total):\n${sanitizeToolOutput(result.diff)}` };
125 }
126 // mode === 'full' — fall through to normal path below
127 } catch {} // diff tracker failure is always non-fatal
128 }
129
130 // Feature 2: summarize large files (>200 lines, no line range requested)
131 // This saves context by replacing the full file with signatures + key logic
132 if (lines.length > 200 && !args.start_line && !args.end_line) {
133 try {
134 const { summarizeFileCompiled } = require('./features_adapter');
135 if (summarizeFileCompiled) {

Callers 1

verifyAndFixCompiledFunction · 0.50

Calls 15

safeResolvePathFunction · 0.85
getReadTrackerFunction · 0.85
sanitizeToolOutputFunction · 0.85
getFileStateTrackerFunction · 0.85
summarizeFileCompiledFunction · 0.85
getSnapshotManagerFunction · 0.85
semanticMergeFunction · 0.85
_rtkRewriteFunction · 0.85
getShellFunction · 0.85
diagnoseErrorFunction · 0.85
buildCommandFunction · 0.85
escapeShellArgFunction · 0.85

Tested by

no test coverage detected