clarity-ai 3.1.0 → 3.3.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/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Change Log
2
+
3
+ ---
4
+
5
+ ## 3.1.0 (2026-06-05)
6
+
7
+ ### UI Rewrite — OpenCode Style
8
+ - **User messages**: Light purple background (`#2D1B4E`), `#C39BD3` text, `❯ YOU` header bar
9
+ - **AI responses**: Left border only (`│`), no box, no background, `◆ CLARITY` header
10
+ - **File edits**: OpenCode diff format — `✎ filename` bar + `+`/`-` lines with line numbers
11
+ - **File writes**: `+ filename` header with line count written
12
+ - **Tool calls**: Inline `⚙ toolname · input` with preview output (max 8 lines)
13
+ - **Info/Error/Success/Warning**: Clean single-line renders with icons
14
+
15
+ ### System Prompt — Anti-Hallucination
16
+ - Strict rules: NEVER fabricate content, NEVER confirm before acting
17
+ - Always ground responses in actual tool output
18
+ - Read files before editing, never overwrite unread files
19
+ - Short, direct responses — no filler
20
+
21
+ ### Agent Loop — Inline Diff Display
22
+ - `executeToolCall()` handles write_file, edit_file, bash, read_file natively
23
+ - edit_file computes unified diff using `diff` package (`diffLines`)
24
+ - `filterContextHunks()` shows ±2 context lines around changes
25
+ - Dynamic fallback for other tools via `import()` from `tools/` directory
26
+
27
+ ---
28
+
29
+ ## 3.0.2 (2026-06-05)
30
+
31
+ - Complete rebuild per CLARITY_V3_PROMPT.md spec
32
+ - Removed all Ink+React code (15 files) — replaced with readline-based TUI
33
+ - 25 tool modules in `src/tools/`, 20 command modules in `src/commands/`
34
+ - 6 provider modules with streaming SSE parsing
35
+ - ReAct agent loop with JSON-only tool call parsing
36
+ - Figlet+gradient animated banner, first-run setup wizard
37
+
38
+ ---
39
+
40
+ ## 3.0.1 (2026-06-05)
41
+
42
+ - Fixed JSX → createElement conversion in all Ink components
43
+ - Fixed import errors and TTY check
44
+ - Fixed Termux bin wrapper (symlink → bash script)
45
+
46
+ ---
47
+
48
+ ## 3.0.0 (2026-06-05)
49
+
50
+ - Complete Ink+React TUI rewrite
51
+ - 11 React components, global state with reducer
52
+ - Parallel subagent spawning
53
+ - 15 tools, 17 commands
54
+
55
+ ---
56
+
57
+ ## 2.0.0 (2026-06-04)
58
+
59
+ - Figlet+gradient banner, 12 block types with timestamps
60
+ - 22-token color palette
61
+ - ReAct agent loop with JSON parsing and 3 retry attempts
62
+ - 20 tools, first-run wizard, split commands
63
+
64
+ ---
65
+
66
+ ## 1.3.0 (2026-06-04)
67
+
68
+ - Provider model lists updated to 2026
69
+ - Interactive `/model` selector, `/provider` command
70
+ - Grey-fill + purple-outline prompt bar
71
+ - Tab completer on readline
package/bin/clarity.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // bin/clarity.js
3
2
  import { showBanner } from '../src/ui/banner.js';
4
3
  import { isFirstRun, loadConfig } from '../src/config/settings.js';
5
4
  import { runSetupWizard } from '../src/core/setup.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-ai",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Autonomous AI Agent CLI for Termux",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,142 +1,117 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { execSync } from 'child_process';
3
+ import { dirname } from 'path';
4
+ import chalk from 'chalk';
5
+ import { Spinner } from '../ui/spinner.js';
6
+ import { renderAI, renderWrite, renderEdit } from '../ui/blocks.js';
1
7
  import { callProvider } from '../providers/index.js';
2
- import { buildSystemPrompt } from '../core/context.js';
3
- import { addMessage } from '../core/history.js';
4
- import { renderEdit, renderWrite, renderTool } from '../ui/blocks.js';
8
+ import { addMessage, saveHistory } from '../core/history.js';
5
9
  import { diffLines } from 'diff';
6
- import { execSync } from 'child_process';
7
- import { readFileSync, writeFileSync, existsSync } from 'fs';
8
-
9
- export const SYSTEM_PROMPT = 'You are CLARITY, an autonomous AI agent CLI running in Termux on Android.\n\n' +
10
- '## CRITICAL RULES NEVER VIOLATE\n\n' +
11
- '1. NEVER fabricate file contents, directory structures, or command outputs.\n' +
12
- ' - If you have not read a file, say "I have not read that file yet."\n' +
13
- ' - If a command has not run, say "I will run that now."\n' +
14
- ' - NEVER invent results.\n\n' +
15
- '2. NEVER hallucinate tool calls — only call tools you actually have.\n' +
16
- ' Available tools: bash, read_file, write_file, edit_file, delete_file,\n' +
17
- ' list_directory, search_files, grep, web_search, web_fetch, git,\n' +
18
- ' run_tests, memory, task_planner, code_runner, pkg_manager, env,\n' +
19
- ' diff, compress, screenshot, clipboard, notify, version_check,\n' +
20
- ' context, agent_spawn.\n\n' +
21
- '3. NEVER confirm an action before doing it — just do it.\n' +
22
- ' Bad: "I will create the workspace folder now." [then waits]\n' +
23
- ' Good: [calls bash tool: mkdir workspace] then "Created workspace/."\n\n' +
24
- '4. ALWAYS ground responses in actual tool output.\n' +
25
- ' If a tool returns an error, report the actual error. Do not pretend it succeeded.\n\n' +
26
- '5. SHORT responses. No filler. No "Certainly!" No "Great question!"\n' +
27
- ' Just: action -> result -> next step if needed.\n\n' +
28
- '6. When editing files, ALWAYS read the file first, then make targeted edits.\n' +
29
- ' Never overwrite a file you have not read.\n\n' +
30
- '7. For multi-step tasks, plan first with task_planner, then execute step by step.\n' +
31
- ' Show each step as you complete it.\n\n' +
32
- '## RESPONSE FORMAT\n\n' +
33
- '- Plain text for explanations. No markdown headers in chat.\n' +
34
- '- File edits shown as diffs automatically by the UI — do not describe edits in text.\n' +
35
- '- Code in responses: just write it plainly, no fences needed unless showing a standalone snippet.\n' +
36
- '- Errors: state the actual error and what you will try next.\n\n' +
37
- '## IDENTITY\n\n' +
38
- 'You are CLARITY v3.0. You are running on ' + process.platform + '.\n' +
39
- 'Current working directory: ' + process.cwd() + '\n' +
40
- 'Agent mode: ON — you can and should use tools without asking permission.\n' +
41
- '\nAvailable tools with JSON schema:\n' +
42
- '- bash { command: string }\n' +
43
- '- read_file { path: string }\n' +
44
- '- write_file { path: string, content: string }\n' +
45
- '- edit_file { path: string, old_str: string, new_str: string }\n' +
46
- '- delete_file { path: string }\n' +
47
- '- list_directory { dir: string }\n' +
48
- '- search_files { pattern: string }\n' +
49
- '- grep { pattern: string, path: string }\n' +
50
- '- web_search { query: string }\n' +
51
- '- web_fetch { url: string }\n' +
52
- '- git { subcommand: string }\n' +
53
- '- memory { action: string, key?: string, value?: string }\n' +
54
- '- code_runner { language: string, code: string }\n' +
55
- '- pkg_manager { manager: string, action: string, packages: string }\n\n' +
56
- 'Respond with JSON:\n' +
57
- '{"tool": "tool_name", "args": {"arg1": "val1"}}\n' +
58
- 'or to respond to user:\n' +
59
- '{"response": "your final answer here"}';
60
-
61
- function filterContextHunks(hunks, ctx) {
62
- const changedIdx = hunks
63
- .map((h, i) => (h.type !== 'context' ? i : -1))
64
- .filter((i) => i >= 0);
65
- return hunks.filter((_, i) => {
66
- if (hunks[i].type !== 'context') return true;
67
- for (const c of changedIdx) {
68
- if (Math.abs(i - c) <= ctx) return true;
69
- }
70
- return false;
71
- });
10
+
11
+ const SYSTEM_PROMPT = 'You are CLARITY, an autonomous AI agent CLI running in Termux on Android.\n\n' +
12
+ '## CRITICAL RULES\n' +
13
+ '1. NEVER fabricate file contents or command outputs.\n' +
14
+ '2. NEVER confirm before acting \u2014 just do it.\n' +
15
+ '3. ALWAYS ground responses in actual tool output.\n' +
16
+ '4. SHORT responses. No filler.\n' +
17
+ '5. When editing files, read first then edit.\n\n' +
18
+ '## RESPONSE FORMAT\n' +
19
+ 'Respond with JSON ONLY:\n' +
20
+ 'To use a tool: {"tool": "tool_name", "args": {"key": "value"}}\n' +
21
+ 'To answer: {"response": "your message"}\n\n' +
22
+ 'Available tools: bash, read_file, write_file, edit_file, list_directory, search_files, grep, web_search, web_fetch, git, memory, code_runner, pkg_manager';
23
+
24
+ function detectIntent(message) {
25
+ const lower = message.toLowerCase().trim();
26
+ const chatPatterns = [
27
+ /^(hi|hello|hey|sup|yo|what'?s up|howdy)/,
28
+ /^(how are you|how do you|what are you|who are you|what can you)/,
29
+ /^(thanks|thank you|ok|okay|cool|nice|great|good|sure|yes|no|yep|nope)/,
30
+ /^(what is|what's|explain|tell me about|describe|define)/,
31
+ /^(can you|could you|would you|will you)\s+(explain|tell|describe|help me understand)/,
32
+ ];
33
+ for (const p of chatPatterns) {
34
+ if (p.test(lower)) return 'chat';
35
+ }
36
+ const toolPatterns = [
37
+ { re: /(create|make|mkdir|new)\s+(a\s+)?(dir|directory|folder)/ },
38
+ { re: /(create|make|write|generate)\s+(a\s+)?(file|script|code|program)/ },
39
+ { re: /(run|execute|launch|start)\s+/ },
40
+ { re: /(edit|modify|change|update|fix)\s+(the\s+)?file/ },
41
+ { re: /(read|show|display|cat|open)\s+(the\s+)?file/ },
42
+ { re: /(list|ls|show)\s+(files|dir|directory|folders)/ },
43
+ { re: /(search|find|grep|look for)/ },
44
+ { re: /(install|npm|pip|pkg)\s+/ },
45
+ { re: /(git\s+)/ },
46
+ { re: /(cd\s+|navigate to|go to)/ },
47
+ ];
48
+ for (const { re } of toolPatterns) {
49
+ if (re.test(lower)) return 'agent';
50
+ }
51
+ return 'chat';
72
52
  }
73
53
 
74
- async function executeToolCall(toolName, toolInput) {
75
- switch (toolName) {
54
+ async function executeTool(name, args) {
55
+ switch (name) {
56
+ case 'bash': {
57
+ try {
58
+ const out = execSync(args.command, {
59
+ encoding: 'utf-8', timeout: 30000,
60
+ cwd: process.env.HOME || '/data/data/com.termux/files/home',
61
+ });
62
+ return out.trim() || '(no output)';
63
+ } catch (e) {
64
+ return 'ERROR: ' + (e.stderr || e.message || String(e));
65
+ }
66
+ }
67
+ case 'read_file': {
68
+ if (!existsSync(args.path)) return 'ERROR: File not found: ' + args.path;
69
+ return readFileSync(args.path, 'utf-8');
70
+ }
76
71
  case 'write_file': {
77
- const { path, content } = toolInput;
78
- const lines = content.split('\n').length;
79
- writeFileSync(path, content, 'utf-8');
80
- console.log(renderWrite(path, lines));
81
- return 'Written ' + lines + ' lines to ' + path;
72
+ const dir = dirname(args.path);
73
+ if (dir !== '.') mkdirSync(dir, { recursive: true });
74
+ writeFileSync(args.path, args.content, 'utf-8');
75
+ const lines = args.content.split('\n').length;
76
+ return 'Written ' + lines + ' lines to ' + args.path;
82
77
  }
83
-
84
78
  case 'edit_file': {
85
- const { path, old_str, new_str } = toolInput;
86
- if (!existsSync(path)) return 'Error: File not found: ' + path;
87
- const original = readFileSync(path, 'utf-8');
88
- const updated = original.replace(old_str, new_str);
89
-
90
- const changes = diffLines(original, updated);
91
- const hunks = [];
92
- let lineNum = 1;
93
- for (const part of changes) {
94
- for (const line of part.value.split('\n').slice(0, -1)) {
95
- if (part.added) {
96
- hunks.push({ type: 'add', line, lineNum });
97
- } else if (part.removed) {
98
- hunks.push({ type: 'remove', line, lineNum });
99
- } else {
100
- hunks.push({ type: 'context', line, lineNum });
101
- }
102
- lineNum++;
103
- }
79
+ if (!existsSync(args.path)) return 'ERROR: File not found: ' + args.path;
80
+ const original = readFileSync(args.path, 'utf-8');
81
+ if (!original.includes(args.old_str)) return 'ERROR: String not found in ' + args.path;
82
+ const updated = original.replace(args.old_str, args.new_str);
83
+ writeFileSync(args.path, updated, 'utf-8');
84
+ return 'Edited ' + args.path;
85
+ }
86
+ case 'list_directory': {
87
+ try {
88
+ const out = execSync('ls -la "' + (args.path || '.') + '"', { encoding: 'utf-8' });
89
+ return out.trim();
90
+ } catch (e) {
91
+ return 'ERROR: ' + e.message;
104
92
  }
105
-
106
- const filtered = filterContextHunks(hunks, 2);
107
- writeFileSync(path, updated, 'utf-8');
108
- console.log(renderEdit(path, filtered));
109
- return 'Edited ' + path;
110
93
  }
111
-
112
- case 'bash': {
113
- const { command } = toolInput;
94
+ case 'search_files': {
114
95
  try {
115
- const result = execSync(command, { encoding: 'utf-8', timeout: 30000 });
116
- const out = result || '(no output)';
117
- console.log(renderTool('bash', command, out));
118
- return out;
119
- } catch (err) {
120
- const errMsg = err.stderr || err.message;
121
- console.log(renderTool('bash', command, errMsg));
122
- return 'Error: ' + errMsg;
96
+ const out = execSync('find "' + (args.path || '.') + '" -name "' + args.pattern + '" 2>/dev/null | head -20', { encoding: 'utf-8' });
97
+ return out.trim() || 'No files found';
98
+ } catch (e) {
99
+ return 'ERROR: ' + e.message;
123
100
  }
124
101
  }
125
-
126
- case 'read_file': {
127
- const { path } = toolInput;
128
- if (!existsSync(path)) return 'Error: File not found: ' + path;
129
- const content = readFileSync(path, 'utf-8');
130
- console.log(renderTool('read_file', path, content.slice(0, 200)));
131
- return content;
102
+ case 'grep': {
103
+ try {
104
+ const out = execSync('grep -rn "' + args.pattern + '" "' + args.path + '" 2>/dev/null | head -20', { encoding: 'utf-8' });
105
+ return out.trim() || 'No matches';
106
+ } catch (e) {
107
+ return 'No matches or error: ' + e.message;
108
+ }
132
109
  }
133
-
134
110
  default: {
135
111
  try {
136
- const mod = await import('../tools/' + toolName + '.js');
112
+ const mod = await import('../tools/' + name + '.js');
137
113
  const toolFn = Object.values(mod)[0];
138
- const result = await toolFn(toolInput);
139
- console.log(renderTool(toolName, JSON.stringify(toolInput).slice(0, 60), result));
114
+ const result = await toolFn(args);
140
115
  return result;
141
116
  } catch (e) {
142
117
  return 'Tool error: ' + e.message;
@@ -145,61 +120,184 @@ async function executeToolCall(toolName, toolInput) {
145
120
  }
146
121
  }
147
122
 
148
- export async function agentLoop(userMessage, config, history) {
149
- const messages = [
150
- { role: 'system', content: buildSystemPrompt(SYSTEM_PROMPT) },
151
- ...history.slice(-20).map((m) => ({
152
- role: m.role === 'assistant' ? 'assistant' : 'user',
153
- content: m.content,
154
- })),
155
- { role: 'user', content: userMessage },
156
- ];
157
-
158
- let finalResponse = '';
159
- let toolCalls = 0;
160
- const maxToolCalls = 20;
123
+ function renderToolStep(name, args, result, elapsed) {
124
+ const argPreview = (() => {
125
+ if (args.command) return String(args.command).slice(0, 50);
126
+ if (args.path) return args.path;
127
+ if (args.pattern) return args.pattern;
128
+ return JSON.stringify(args).slice(0, 50);
129
+ })();
161
130
 
162
- while (toolCalls < maxToolCalls) {
163
- let fullResponse = '';
164
- const stream = callProvider(config, messages);
165
- for await (const chunk of stream) {
166
- fullResponse += chunk;
167
- }
131
+ const header =
132
+ chalk.hex('#00FFFF')('\u2699 ') +
133
+ chalk.hex('#00FFFF').bold(name) +
134
+ chalk.dim(' \u00b7 ' + argPreview) +
135
+ chalk.dim(' (' + elapsed + 'ms)');
136
+ console.log(header);
168
137
 
169
- const jsonMatch = fullResponse.match(/\{[^]*\}/);
170
- if (!jsonMatch) {
171
- finalResponse = fullResponse;
172
- break;
173
- }
138
+ if (name === 'write_file' && args.content) {
139
+ const lineCount = args.content.split('\n').length;
140
+ console.log(renderWrite(args.path, lineCount));
141
+ return;
142
+ }
174
143
 
175
- let parsed;
144
+ if (name === 'edit_file' && existsSync(args.path)) {
176
145
  try {
177
- parsed = JSON.parse(jsonMatch[0]);
178
- } catch {
179
- finalResponse = fullResponse;
180
- break;
146
+ const original = readFileSync(args.path, 'utf-8');
147
+ const updated = original.replace(args.old_str || '', args.new_str || '');
148
+ const changes = diffLines(original, updated);
149
+ const hunks = [];
150
+ let lineNum = 1;
151
+ for (const part of changes) {
152
+ const partLines = part.value.split('\n');
153
+ if (partLines[partLines.length - 1] === '') partLines.pop();
154
+ for (const line of partLines) {
155
+ if (part.added) hunks.push({ type: 'add', line, lineNum });
156
+ else if (part.removed) hunks.push({ type: 'remove', line, lineNum });
157
+ else hunks.push({ type: 'context', line, lineNum });
158
+ lineNum++;
159
+ }
160
+ }
161
+ const changed = new Set(hunks.map((h, i) => h.type !== 'context' ? i : -1).filter(i => i >= 0));
162
+ const filtered = hunks.filter((_, i) => {
163
+ if (hunks[i].type !== 'context') return true;
164
+ for (const c of changed) if (Math.abs(i - c) <= 2) return true;
165
+ return false;
166
+ });
167
+ console.log(renderEdit(args.path, filtered));
168
+ } catch {}
169
+ return;
170
+ }
171
+
172
+ if (result && result !== '(no output)' && !result.startsWith('ERROR') && name !== 'read_file') {
173
+ const resultLines = String(result).split('\n').slice(0, 6);
174
+ for (const l of resultLines) {
175
+ console.log(chalk.dim(' \u2502 ') + chalk.hex('#AAAAAA')(l));
176
+ }
177
+ if (String(result).split('\n').length > 6) {
178
+ console.log(chalk.dim(' \u2502 ...'));
181
179
  }
180
+ }
182
181
 
183
- if (parsed.response) {
184
- finalResponse = parsed.response;
185
- break;
182
+ if (result && String(result).startsWith('ERROR')) {
183
+ console.log(chalk.hex('#FF4757')(' \u2716 ') + chalk.hex('#FF4757')(result));
184
+ }
185
+ }
186
+
187
+ export async function agentLoop(userMessage, config, history) {
188
+ try {
189
+ const intent = detectIntent(userMessage);
190
+ const spinner = new Spinner();
191
+ const msgHistory = Array.isArray(history) ? history : (history.messages || []);
192
+
193
+ if (intent === 'chat') {
194
+ spinner.start('Thinking', 'think');
195
+ const t0 = Date.now();
196
+
197
+ const messages = [
198
+ { role: 'system', content: 'You are CLARITY-AI, a helpful assistant. Keep responses short.' },
199
+ ...msgHistory.slice(-10).map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),
200
+ { role: 'user', content: userMessage },
201
+ ];
202
+
203
+ const result = await callProvider(config, messages);
204
+ const elapsed = Date.now() - t0;
205
+ spinner.stop('Thought ' + (elapsed < 1000 ? elapsed + 'ms' : (elapsed/1000).toFixed(1) + 's'), 'done');
206
+
207
+ addMessage(msgHistory, 'user', userMessage);
208
+ addMessage(msgHistory, 'assistant', result.content);
209
+ saveHistory(msgHistory);
210
+
211
+ console.log();
212
+ console.log(renderAI(result.content));
213
+ console.log();
214
+ return;
186
215
  }
187
216
 
188
- if (parsed.tool) {
189
- toolCalls++;
190
- const toolName = parsed.tool;
191
- const args = parsed.args || {};
217
+ let messages = [
218
+ { role: 'system', content: SYSTEM_PROMPT },
219
+ ...msgHistory.slice(-20).map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),
220
+ { role: 'user', content: userMessage },
221
+ ];
222
+
223
+ let loopCount = 0;
224
+ const MAX_LOOPS = 15;
192
225
 
193
- const result = await executeToolCall(toolName, args);
226
+ while (loopCount < MAX_LOOPS) {
227
+ loopCount++;
194
228
 
195
- messages.push({ role: 'assistant', content: fullResponse });
196
- messages.push({ role: 'user', content: 'Tool result: ' + result });
229
+ spinner.start('Thinking', 'think');
230
+ const t0 = Date.now();
231
+
232
+ const result = await callProvider(config, messages);
233
+ const fullResponse = result.content;
234
+
235
+ const thinkMs = Date.now() - t0;
236
+ spinner.stop('Thought ' + (thinkMs < 1000 ? thinkMs + 'ms' : (thinkMs/1000).toFixed(1) + 's'), 'done');
237
+
238
+ const jsonMatch = fullResponse.match(/\{[^]*\}/);
239
+ if (!jsonMatch) {
240
+ console.log();
241
+ console.log(renderAI(fullResponse));
242
+ console.log();
243
+ addMessage(msgHistory, 'user', userMessage);
244
+ addMessage(msgHistory, 'assistant', fullResponse);
245
+ saveHistory(msgHistory);
246
+ return;
247
+ }
248
+
249
+ let parsed;
250
+ try {
251
+ parsed = JSON.parse(jsonMatch[0]);
252
+ } catch {
253
+ console.log();
254
+ console.log(renderAI(fullResponse));
255
+ console.log();
256
+ addMessage(msgHistory, 'user', userMessage);
257
+ addMessage(msgHistory, 'assistant', fullResponse);
258
+ saveHistory(msgHistory);
259
+ return;
260
+ }
261
+
262
+ if (parsed.response) {
263
+ const finalText = parsed.response;
264
+ console.log();
265
+ console.log(renderAI(finalText));
266
+ console.log();
267
+ addMessage(msgHistory, 'user', userMessage);
268
+ addMessage(msgHistory, 'assistant', finalText);
269
+ saveHistory(msgHistory);
270
+ return;
271
+ }
272
+
273
+ if (parsed.tool) {
274
+ const name = parsed.tool;
275
+ const args = parsed.args || {};
276
+ const argPreview = args.command || args.path || args.pattern || '';
277
+
278
+ spinner.start('Running ' + name, 'tool');
279
+ spinner.update('Running ' + name, argPreview.slice(0, 40));
280
+
281
+ const ts = Date.now();
282
+ const toolResult = await executeTool(name, args);
283
+ const te = Date.now() - ts;
284
+
285
+ spinner.stop(name + ' \u00b7 ' + argPreview.slice(0, 30), 'done');
286
+ renderToolStep(name, args, toolResult, te);
287
+
288
+ messages.push({ role: 'assistant', content: fullResponse });
289
+ messages.push({ role: 'user', content: 'Tool result: ' + toolResult });
290
+ }
197
291
  }
198
- }
199
292
 
200
- if (toolCalls >= maxToolCalls) {
201
- finalResponse += '\n\n(Max tool calls reached)';
293
+ console.log(chalk.hex('#FFB800')('\u26a0 Max tool calls reached. Stopping.'));
294
+ } catch (err) {
295
+ console.log();
296
+ console.log(chalk.hex('#FF4757')('\u2716 Error: ') + chalk.white(err.message.split('\n')[0]));
297
+ const extra = err.message.split('\n').slice(1);
298
+ for (const line of extra) {
299
+ console.log(chalk.dim(' ' + line));
300
+ }
301
+ console.log();
202
302
  }
203
-
204
- return finalResponse || 'No response generated';
205
303
  }