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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "winter-super-cli",
3
- "version": "2026.5.15",
3
+ "version": "2026.5.17",
4
4
  "description": "❄️ AI-Powered Development CLI with Interactive REPL",
5
5
  "type": "module",
6
6
  "main": "bin/winter.js",
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 enrichedArgs = this.enrichToolArgs(canonicalToolName, toolArgs, messages);
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 (!proceed) {
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(rawArgs);
1927
- } catch {
1928
- return {};
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,
@@ -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
+ });
@@ -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
  });