@@ -436,8 +436,8 @@ static const std::vector<std::string> CODE_EXTENSIONS = {
436436//
437437static std::string settings_path () {
438438 // Attempt to read settings from the current working directory first
439- if (fs::exists (" settings.json" )) {
440- return " settings.json" ;
439+ if (fs::exists (" nitro. settings.json" )) {
440+ return " nitro. settings.json" ;
441441 }
442442 const char *home = getenv (" HOME" );
443443 std::string base = home ? std::string (home) : " ." ;
@@ -581,7 +581,7 @@ static void load_settings(NitroConfig &cfg) {
581581static constexpr std::string ICON_ERR = " ⚡ ▏" ;
582582static constexpr std::string ICON_THINK = " 🤔 ▏" ;
583583static constexpr std::string ICON_TOOL = " 🔧 ▏" ;
584- static constexpr std::string ICON_SYS = " 🤖 ▏" ;
584+ static constexpr std::string ICON_SYS = " ✨ ▏" ;
585585
586586static std::string introspect (const NitroConfig &cfg) {
587587 static constexpr std::string_view tmpl =
@@ -1229,38 +1229,54 @@ void TuiState::redraw_chat() {
12291229 unsigned rows, cols;
12301230 ncplane_dim_yx (chatpl, &rows, &cols);
12311231 std::lock_guard<std::mutex> lk (lines_mutex);
1232- int total = static_cast <int >(chat_lines.size ());
1233- int visible = static_cast <int >(rows);
1234- int start = std::max (0 , total - visible - scroll_offset);
1235- int end = std::min (total, start + visible);
1236- for (int i = start, row = 0 ; i < end; ++i, ++row) {
1237- const std::string &line = chat_lines[i];
1232+
1233+ // Pre-compute wrapped lines so we know total visual rows.
1234+ struct VisualLine {
1235+ std::string text;
1236+ uint64_t ch;
1237+ };
1238+ std::vector<VisualLine> visual;
1239+ visual.reserve (chat_lines.size ());
1240+
1241+ for (const std::string &line : chat_lines) {
12381242 uint64_t ch;
1239- // Logo lines use prefix "[logo_N]" where N is the row index 0-6.
1240- // We interpolate a cyan→magenta gradient across the 7 art rows.
12411243 if (line.rfind (" [logo_" , 0 ) == 0 && line.size () > 7 && line[7 ] == ' ]' ) {
12421244 int logo_row = line[6 ] - ' 0' ;
1243- // Gradient: cyan (0,230,255) → green (80,255,160) → magenta (220,80,255)
1244- // 7 steps, indices 0-6.
12451245 static const uint32_t GRAD_R [] = { 0 , 20 , 60 , 120 , 180 , 210 , 220 };
12461246 static const uint32_t GRAD_G [] = { 230 , 255 , 255 , 255 , 200 , 130 , 80 };
12471247 static const uint32_t GRAD_B [] = { 255 , 200 , 140 , 80 , 100 , 200 , 255 };
12481248 int gi = std::max (0 , std::min (logo_row, 6 ));
12491249 ch = chat_ch (GRAD_R [gi], GRAD_G [gi], GRAD_B [gi]);
12501250 }
1251- else if (line.rfind (" You: " , 0 ) == 0 ) ch = chat_ch (100 , 200 , 255 );
1252- else if (line.rfind (" Nitro: " , 0 ) == 0 ) ch = chat_ch (180 , 255 , 180 );
1253- else if (line.rfind (ICON_SYS , 0 ) == 0 ) ch = chat_ch (140 , 140 , 200 );
1254- else if (line.rfind (ICON_TOOL , 0 ) == 0 ) ch = chat_ch (255 , 180 , 80 );
1255- else if (line.rfind (ICON_ERR , 0 ) == 0 ) ch = chat_ch (255 , 80 , 80 );
1251+ else if (line.rfind (" You: " , 0 ) == 0 ) ch = chat_ch (100 , 200 , 255 );
1252+ else if (line.rfind (" Nitro: " , 0 ) == 0 ) ch = chat_ch (180 , 255 , 180 );
1253+ else if (line.rfind (ICON_SYS , 0 ) == 0 ) ch = chat_ch (160 , 82 , 45 );
1254+ else if (line.rfind (ICON_TOOL , 0 ) == 0 ) ch = chat_ch (255 , 180 , 80 );
1255+ else if (line.rfind (ICON_ERR , 0 ) == 0 ) ch = chat_ch (255 , 80 , 80 );
12561256 else if (line.rfind (ICON_THINK , 0 ) == 0 ) ch = chat_ch (140 , 140 , 200 );
12571257 else ch = chat_ch (210 , 210 , 210 );
1258- ncplane_set_channels (chatpl, ch);
1259- // Strip the [logo_N] prefix before rendering.
1258+
12601259 std::string display = (line.rfind (" [logo_" , 0 ) == 0 && line.size () > 8 )
12611260 ? line.substr (8 ) : line;
1262- if (display.size () > cols) display = display.substr (0 , cols);
1263- ncplane_putstr_yx (chatpl, row, 0 , display.c_str ());
1261+
1262+ // Split into cols-wide chunks, each carrying the same channel.
1263+ if (display.empty ()) {
1264+ visual.push_back ({" " , ch});
1265+ } else {
1266+ for (size_t off = 0 ; off < display.size (); off += cols) {
1267+ visual.push_back ({display.substr (off, cols), ch});
1268+ }
1269+ }
1270+ }
1271+
1272+ int total = static_cast <int >(visual.size ());
1273+ int visible = static_cast <int >(rows);
1274+ int start = std::max (0 , total - visible - scroll_offset);
1275+ int end = std::min (total, start + visible);
1276+
1277+ for (int i = start, row = 0 ; i < end; ++i, ++row) {
1278+ ncplane_set_channels (chatpl, visual[i].ch );
1279+ ncplane_putstr_yx (chatpl, row, 0 , visual[i].text .c_str ());
12641280 }
12651281}
12661282
@@ -2081,19 +2097,16 @@ std::string AgentState::process_tool(const std::string &cmd, const NitroConfig &
20812097 if (op == " TOOL:LIST" ) {
20822098 std::string dir = resolve (arg1);
20832099 show_tool (" listing: " + dir);
2084- if (!path_in_sandbox (sandbox, dir)) return " ERROR: path outside sandbox" ;
20852100 return list_dir (dir);
20862101 }
20872102 if (op == " TOOL:EXISTS" ) {
20882103 std::string p = resolve (arg1);
20892104 show_tool (" checking: " + p);
2090- if (!path_in_sandbox (sandbox, p)) return " NO" ;
20912105 return fs::exists (p) ? " YES" : " NO" ;
20922106 }
20932107 if (op == " TOOL:READ" ) {
20942108 show_tool (" reading: " + arg1);
20952109 std::string p = resolve (arg1);
2096- if (!path_in_sandbox (sandbox, p)) return " ERROR: path outside sandbox" ;
20972110 return read_file (p);
20982111 }
20992112 if (op == " TOOL:WRITE" ) {
@@ -2125,9 +2138,15 @@ std::string AgentState::process_tool(const std::string &cmd, const NitroConfig &
21252138 return introspect (cfg);
21262139 }
21272140 if (op == " TOOL:ASK" ) {
2128- show_tool (" asking: " + arg1);
2141+ tui.set_thinking (false );
2142+ show_tool (" asking: " + arg1 + " " + arg2);
21292143 return tui.readline_blocking ();
21302144 }
2145+ if (op == " TOOL:PERMISSION" ) {
2146+ tui.set_thinking (false );
2147+ show_tool (" asking permission: " + arg1 + " " + arg2);
2148+ return tui.confirm_dialog (arg1 + " " + arg2) ? " YES" : " NO" ;
2149+ }
21312150 if (op == " TOOL:RUN" ) {
21322151 if (!run_allowed.empty ()) {
21332152 bool permitted = ranges::any_of (run_allowed, [&](const std::string &a) {return a == arg1;});
@@ -2217,6 +2236,17 @@ bool AgentState::run_turn(const std::string &user_message, const NitroConfig &cf
22172236 std::string content = TOOL_RESULT + std::vformat (template_str, std::make_format_args (result));
22182237 log_write (" tool: [%s] result: [%s]" , tool.c_str (), result.c_str ());
22192238
2239+ if (content.size () > llama->max_tool_result_size ()) {
2240+ // Index the content into RAG and tell the model where to find it
2241+ if (embed_llama && rag_db && rag_session) {
2242+ content = std::format (" Tool result too large ({} bytes). The content has been indexed. "
2243+ " Use TOOL:RAG with a relevant query to retrieve the information you need." ,
2244+ content.size ());
2245+ } else {
2246+ content = std::format (" Tool result too large ({} bytes)." , content.size ());
2247+ }
2248+ tui.append_line (ICON_ERR + content);
2249+ }
22202250 if (!llama->add_message (*iter, " tool_result" , content)) {
22212251 tui.append_line (ICON_ERR + " tool result inject: " + llama->last_error ());
22222252 }
@@ -2258,7 +2288,7 @@ bool AgentState::run_turn(const std::string &user_message, const NitroConfig &cf
22582288 }
22592289 return ni.id == NCKEY_ESC ;
22602290 };
2261-
2291+
22622292 auto fetch_all = [&]() -> void {
22632293 while (iter->_has_next && !is_escape ()) {
22642294 std::string tok = llama->next (*iter);
0 commit comments