winter-super-cli 2026.5.15 → 2026.5.17
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/package.json +1 -1
- package/src/cli/repl.js +59 -6
- package/src/cli/repl.test.js +50 -0
- package/src/tools/executor.js +26 -0
- package/src/tools/executor.test.js +5 -0
package/package.json
CHANGED
package/src/cli/repl.js
CHANGED
|
@@ -1374,13 +1374,14 @@ ${colors.reset}
|
|
|
1374
1374
|
for (const tc of toolCalls) {
|
|
1375
1375
|
const { toolName, toolArgs } = tc;
|
|
1376
1376
|
const canonicalToolName = this.tools.normalizeToolName(toolName);
|
|
1377
|
-
const
|
|
1377
|
+
const argParseError = toolArgs?.__toolArgParseError;
|
|
1378
|
+
const enrichedArgs = argParseError ? {} : this.enrichToolArgs(canonicalToolName, toolArgs, messages);
|
|
1378
1379
|
|
|
1379
1380
|
const icon = canonicalToolName === 'Bash' ? '⚙' : canonicalToolName === 'Read' ? '📖' : canonicalToolName === 'Write' ? '✏️' : canonicalToolName === 'Edit' ? '🔧' : canonicalToolName === 'Grep' ? '🔍' : canonicalToolName === 'Glob' ? '📂' : '⚡';
|
|
1380
1381
|
console.log(`${colors.magenta}│${colors.reset} ${icon} ${colors.cyan}${colors.bright}${toolName}${colors.reset}`);
|
|
1381
1382
|
|
|
1382
1383
|
let proceed = true;
|
|
1383
|
-
if (canonicalToolName === 'Bash') {
|
|
1384
|
+
if (canonicalToolName === 'Bash' && !argParseError) {
|
|
1384
1385
|
const cmd = enrichedArgs.command || enrichedArgs.cmd || 'unknown';
|
|
1385
1386
|
process.stdout.write(`${colors.magenta}│${colors.reset} ${colors.yellow}⚠ AI muốn chạy: ${colors.bright}${cmd}${colors.reset}\n`);
|
|
1386
1387
|
proceed = await new Promise(resolve => {
|
|
@@ -1394,7 +1395,13 @@ ${colors.reset}
|
|
|
1394
1395
|
}
|
|
1395
1396
|
|
|
1396
1397
|
let result;
|
|
1397
|
-
if (
|
|
1398
|
+
if (argParseError) {
|
|
1399
|
+
result = {
|
|
1400
|
+
success: false,
|
|
1401
|
+
error: `Invalid ${canonicalToolName} tool arguments JSON: ${toolArgs.__toolArgParseError}`,
|
|
1402
|
+
rawArgs: toolArgs.__rawToolArgs,
|
|
1403
|
+
};
|
|
1404
|
+
} else if (!proceed) {
|
|
1398
1405
|
result = { success: false, error: 'User denied permission to execute this command.' };
|
|
1399
1406
|
} else {
|
|
1400
1407
|
result = toolName
|
|
@@ -1922,13 +1929,59 @@ ${colors.reset}
|
|
|
1922
1929
|
if (typeof rawArgs === 'object') return rawArgs;
|
|
1923
1930
|
if (typeof rawArgs !== 'string') return {};
|
|
1924
1931
|
|
|
1932
|
+
const text = rawArgs.trim();
|
|
1933
|
+
if (!text) return {};
|
|
1934
|
+
|
|
1925
1935
|
try {
|
|
1926
|
-
return JSON.parse(
|
|
1927
|
-
} catch {
|
|
1928
|
-
|
|
1936
|
+
return JSON.parse(text);
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
const extracted = this.extractFirstJsonObject(text);
|
|
1939
|
+
if (extracted && extracted !== text) {
|
|
1940
|
+
try {
|
|
1941
|
+
return JSON.parse(extracted);
|
|
1942
|
+
} catch {}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
return {
|
|
1946
|
+
__toolArgParseError: error.message,
|
|
1947
|
+
__rawToolArgs: text.length > 800 ? `${text.slice(0, 800)}...` : text,
|
|
1948
|
+
};
|
|
1929
1949
|
}
|
|
1930
1950
|
}
|
|
1931
1951
|
|
|
1952
|
+
extractFirstJsonObject(text) {
|
|
1953
|
+
const start = text.indexOf('{');
|
|
1954
|
+
if (start === -1) return null;
|
|
1955
|
+
|
|
1956
|
+
let depth = 0;
|
|
1957
|
+
let inString = false;
|
|
1958
|
+
let escaped = false;
|
|
1959
|
+
|
|
1960
|
+
for (let i = start; i < text.length; i++) {
|
|
1961
|
+
const ch = text[i];
|
|
1962
|
+
if (escaped) {
|
|
1963
|
+
escaped = false;
|
|
1964
|
+
continue;
|
|
1965
|
+
}
|
|
1966
|
+
if (ch === '\\') {
|
|
1967
|
+
escaped = true;
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
if (ch === '"') {
|
|
1971
|
+
inString = !inString;
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1974
|
+
if (inString) continue;
|
|
1975
|
+
if (ch === '{') depth++;
|
|
1976
|
+
if (ch === '}') {
|
|
1977
|
+
depth--;
|
|
1978
|
+
if (depth === 0) return text.slice(start, i + 1);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
return null;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1932
1985
|
formatToolCallsForMessage(toolCalls) {
|
|
1933
1986
|
return toolCalls.map((tc) => ({
|
|
1934
1987
|
id: tc.id,
|
package/src/cli/repl.test.js
CHANGED
|
@@ -165,3 +165,53 @@ test('runConversation executes streamed tool calls then streams final answer', a
|
|
|
165
165
|
assert.equal(answer, 'Done');
|
|
166
166
|
assert.deepEqual(executed, [{ name: 'Read', args: { file_path: 'README.md' } }]);
|
|
167
167
|
});
|
|
168
|
+
|
|
169
|
+
test('runConversation reports malformed tool arguments instead of executing empty args', async () => {
|
|
170
|
+
const repl = new WinterREPL({ projectPath: process.cwd() });
|
|
171
|
+
|
|
172
|
+
let streamCount = 0;
|
|
173
|
+
const executed = [];
|
|
174
|
+
repl.ai = {
|
|
175
|
+
tools: [],
|
|
176
|
+
providers: { custom: { model: 'test-model' } },
|
|
177
|
+
getActiveProvider: () => 'custom',
|
|
178
|
+
setTools(tools) {
|
|
179
|
+
this.tools = tools;
|
|
180
|
+
},
|
|
181
|
+
async *streamRequest() {
|
|
182
|
+
streamCount++;
|
|
183
|
+
if (streamCount === 1) {
|
|
184
|
+
yield {
|
|
185
|
+
raw: {
|
|
186
|
+
choices: [{
|
|
187
|
+
delta: {
|
|
188
|
+
tool_calls: [{
|
|
189
|
+
index: 0,
|
|
190
|
+
id: 'call-bad',
|
|
191
|
+
type: 'function',
|
|
192
|
+
function: { name: 'Bash', arguments: '{"command":' },
|
|
193
|
+
}],
|
|
194
|
+
},
|
|
195
|
+
finish_reason: 'tool_calls',
|
|
196
|
+
}],
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
yield { content: 'Recovered' };
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
repl.tools = {
|
|
206
|
+
normalizeToolName: name => name,
|
|
207
|
+
async execute(name, args) {
|
|
208
|
+
executed.push({ name, args });
|
|
209
|
+
return { success: true };
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const answer = await repl.runConversation([{ role: 'user', content: 'run it' }], 'Test', [{ name: 'Bash' }]);
|
|
214
|
+
|
|
215
|
+
assert.equal(answer, 'Recovered');
|
|
216
|
+
assert.deepEqual(executed, []);
|
|
217
|
+
});
|
package/src/tools/executor.js
CHANGED
|
@@ -244,26 +244,52 @@ export class ToolExecutor {
|
|
|
244
244
|
const aliases = {
|
|
245
245
|
read: 'Read',
|
|
246
246
|
readfile: 'Read',
|
|
247
|
+
openfile: 'Read',
|
|
248
|
+
viewfile: 'Read',
|
|
249
|
+
cat: 'Read',
|
|
247
250
|
write: 'Write',
|
|
248
251
|
writefile: 'Write',
|
|
252
|
+
writetofile: 'Write',
|
|
253
|
+
createfile: 'Write',
|
|
254
|
+
savefile: 'Write',
|
|
249
255
|
edit: 'Edit',
|
|
250
256
|
editfile: 'Edit',
|
|
257
|
+
replaceinfile: 'Edit',
|
|
258
|
+
strreplace: 'Edit',
|
|
259
|
+
strreplaceeditor: 'Edit',
|
|
260
|
+
applydiff: 'Edit',
|
|
261
|
+
patch: 'Edit',
|
|
251
262
|
bash: 'Bash',
|
|
252
263
|
shell: 'Bash',
|
|
253
264
|
command: 'Bash',
|
|
265
|
+
executecommand: 'Bash',
|
|
266
|
+
runcommand: 'Bash',
|
|
267
|
+
terminal: 'Bash',
|
|
268
|
+
powershell: 'Bash',
|
|
254
269
|
glob: 'Glob',
|
|
270
|
+
listfiles: 'Glob',
|
|
271
|
+
ls: 'Glob',
|
|
272
|
+
findfiles: 'Glob',
|
|
255
273
|
grep: 'Grep',
|
|
256
274
|
search: 'Grep',
|
|
275
|
+
searchfiles: 'Grep',
|
|
276
|
+
searchtext: 'Grep',
|
|
277
|
+
rg: 'Grep',
|
|
257
278
|
lsp: 'LSP',
|
|
279
|
+
listcodedefinitionnames: 'LSP',
|
|
258
280
|
taskcreate: 'TaskCreate',
|
|
259
281
|
createtask: 'TaskCreate',
|
|
282
|
+
newtask: 'TaskCreate',
|
|
260
283
|
taskupdate: 'TaskUpdate',
|
|
261
284
|
updatetask: 'TaskUpdate',
|
|
262
285
|
tasklist: 'TaskList',
|
|
263
286
|
listtasks: 'TaskList',
|
|
287
|
+
plan: 'TaskList',
|
|
264
288
|
webfetch: 'WebFetch',
|
|
265
289
|
fetch: 'WebFetch',
|
|
290
|
+
fetchurl: 'WebFetch',
|
|
266
291
|
websearch: 'WebSearch',
|
|
292
|
+
searchweb: 'WebSearch',
|
|
267
293
|
browserdebug: 'BrowserDebug',
|
|
268
294
|
browser: 'BrowserDebug',
|
|
269
295
|
};
|
|
@@ -18,6 +18,11 @@ test('tool names accept common model aliases', () => {
|
|
|
18
18
|
const tools = new ToolExecutor({ projectPath: process.cwd() });
|
|
19
19
|
|
|
20
20
|
assert.equal(tools.normalizeToolName('read_file'), 'Read');
|
|
21
|
+
assert.equal(tools.normalizeToolName('write_to_file'), 'Write');
|
|
22
|
+
assert.equal(tools.normalizeToolName('replace_in_file'), 'Edit');
|
|
23
|
+
assert.equal(tools.normalizeToolName('execute_command'), 'Bash');
|
|
24
|
+
assert.equal(tools.normalizeToolName('list_files'), 'Glob');
|
|
25
|
+
assert.equal(tools.normalizeToolName('search_files'), 'Grep');
|
|
21
26
|
assert.equal(tools.normalizeToolName('shell'), 'Bash');
|
|
22
27
|
assert.equal(tools.normalizeToolName('web-search'), 'WebSearch');
|
|
23
28
|
});
|