apexbot 1.0.2 → 1.0.4

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.
@@ -7,6 +7,8 @@
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.AgentManager = void 0;
9
9
  const generative_ai_1 = require("@google/generative-ai");
10
+ const tools_1 = require("../tools");
11
+ const toolExecutor_1 = require("./toolExecutor");
10
12
  class AgentManager {
11
13
  config = null;
12
14
  googleClient = null;
@@ -42,6 +44,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
42
44
  maxTokens: 4096,
43
45
  temperature: 0.7,
44
46
  systemPrompt: this.defaultSystemPrompt,
47
+ enableTools: true, // Tools enabled by default
45
48
  ...config,
46
49
  };
47
50
  // Initialize client based on provider
@@ -68,6 +71,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
68
71
  return { text: 'Agent not configured. Please set up an AI provider.' };
69
72
  }
70
73
  const userText = message.text || '';
74
+ console.log(`[Agent] Processing message: ${userText.slice(0, 50)}...`);
71
75
  // Handle slash commands
72
76
  if (userText.startsWith('/')) {
73
77
  return this.handleCommand(session, userText);
@@ -95,6 +99,43 @@ You are running locally on the user's machine. No data leaves their computer. Yo
95
99
  default:
96
100
  response = { text: 'Unknown AI provider' };
97
101
  }
102
+ // Check for tool calls in response
103
+ if (this.config.enableTools) {
104
+ const toolCalls = (0, toolExecutor_1.parseToolCalls)(response.text);
105
+ if (toolCalls.length > 0) {
106
+ console.log(`[Agent] Found ${toolCalls.length} tool calls`);
107
+ // Build tool context
108
+ const context = (0, toolExecutor_1.buildToolContext)(session.id, message.userId || 'unknown', message.channel, { workspaceDir: process.cwd() });
109
+ // Execute tools
110
+ const toolResults = await (0, toolExecutor_1.executeToolCalls)(toolCalls, context);
111
+ response.toolCalls = toolResults;
112
+ // Format results and continue conversation
113
+ const resultsText = (0, toolExecutor_1.formatToolResults)(toolResults);
114
+ if (resultsText) {
115
+ // Add tool results to history and get follow-up response
116
+ const followUpHistory = [
117
+ ...history,
118
+ { role: 'assistant', content: response.text, timestamp: Date.now() },
119
+ { role: 'user', content: `Tool results:\n${resultsText}\n\nPlease continue based on these results.`, timestamp: Date.now() },
120
+ ];
121
+ // Get follow-up response
122
+ let followUp;
123
+ switch (this.config.provider) {
124
+ case 'ollama':
125
+ followUp = await this.processWithOllama(followUpHistory);
126
+ break;
127
+ case 'google':
128
+ followUp = await this.processWithGemini(followUpHistory);
129
+ break;
130
+ default:
131
+ followUp = { text: resultsText };
132
+ }
133
+ // Combine responses
134
+ response.text = followUp.text;
135
+ }
136
+ }
137
+ }
138
+ console.log(`[Agent] Generated response: ${response.text.slice(0, 50)}...`);
98
139
  // Save to session
99
140
  session.messages.push({ role: 'user', content: userText, timestamp: Date.now() });
100
141
  session.messages.push({ role: 'assistant', content: response.text, timestamp: Date.now() });
@@ -103,9 +144,8 @@ You are running locally on the user's machine. No data leaves their computer. Yo
103
144
  return response;
104
145
  }
105
146
  catch (e) {
106
- console.error('[Agent] Error:', e);
107
- // Escape special characters for Telegram
108
- const errorMsg = (e.message || 'Unknown error').slice(0, 200).replace(/[_*\[\]()~`>#+=|{}.!-]/g, '\\$&');
147
+ console.error('[Agent] Error processing:', e);
148
+ const errorMsg = (e.message || 'Unknown error').slice(0, 200);
109
149
  return { text: `Error: ${errorMsg}` };
110
150
  }
111
151
  }
@@ -160,6 +200,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
160
200
  const apiUrl = cfg.apiUrl || 'http://localhost:11434';
161
201
  const model = cfg.model || 'llama3.2';
162
202
  const temperature = cfg.temperature ?? 0.7;
203
+ console.log(`[Agent] Calling Ollama at ${apiUrl} with model ${model}...`);
163
204
  // Build messages array for Ollama chat API
164
205
  const messages = history.map(m => ({
165
206
  role: m.role,
@@ -183,10 +224,12 @@ You are running locally on the user's machine. No data leaves their computer. Yo
183
224
  });
184
225
  if (!res.ok) {
185
226
  const err = await res.text().catch(() => res.statusText);
227
+ console.error(`[Agent] Ollama error: ${err}`);
186
228
  throw new Error(`Ollama error: ${err}`);
187
229
  }
188
230
  const data = await res.json();
189
231
  const text = data.message?.content || '';
232
+ console.log(`[Agent] Ollama response received: ${text.slice(0, 50)}...`);
190
233
  return {
191
234
  text: String(text).trim() || 'No response from model',
192
235
  usage: {
@@ -196,6 +239,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
196
239
  };
197
240
  }
198
241
  catch (error) {
242
+ console.error('[Agent] Ollama fetch error:', error);
199
243
  if (error.code === 'ECONNREFUSED' || error.message?.includes('fetch failed')) {
200
244
  return {
201
245
  text: `Ollama not running.\n\nPlease:\n1. Install Ollama: https://ollama.com\n2. Pull a model: ollama pull llama3.2\n3. Start Ollama: ollama serve`,
@@ -206,8 +250,11 @@ You are running locally on the user's machine. No data leaves their computer. Yo
206
250
  }
207
251
  buildHistory(session, currentMessage) {
208
252
  const history = [];
209
- // System prompt
210
- const systemPrompt = session.systemPrompt || this.config?.systemPrompt || this.defaultSystemPrompt;
253
+ // System prompt with tools if enabled
254
+ let systemPrompt = session.systemPrompt || this.config?.systemPrompt || this.defaultSystemPrompt;
255
+ if (this.config?.enableTools && tools_1.toolRegistry.list().length > 0) {
256
+ systemPrompt += '\n\n' + (0, toolExecutor_1.getToolsSystemPrompt)();
257
+ }
211
258
  history.push({ role: 'system', content: systemPrompt, timestamp: 0 });
212
259
  // Previous messages
213
260
  for (const msg of session.messages) {
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ /**
3
+ * Tool Executor - Handles tool calling from AI models
4
+ *
5
+ * This module integrates the tool system with AI responses,
6
+ * parsing tool calls and executing them.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.parseToolCalls = parseToolCalls;
10
+ exports.executeTool = executeTool;
11
+ exports.executeToolCalls = executeToolCalls;
12
+ exports.formatToolResults = formatToolResults;
13
+ exports.getToolsSystemPrompt = getToolsSystemPrompt;
14
+ exports.buildToolContext = buildToolContext;
15
+ const tools_1 = require("../tools");
16
+ /**
17
+ * Parse tool calls from AI response
18
+ * Supports multiple formats:
19
+ * 1. JSON block: ```json\n{"tool": "name", "args": {...}}```
20
+ * 2. XML-style: <tool name="name">{"args": {...}}</tool>
21
+ * 3. Function call style: tool_name({"arg": "value"})
22
+ */
23
+ function parseToolCalls(text) {
24
+ const calls = [];
25
+ // Pattern 1: JSON blocks with tool calls
26
+ const jsonBlockRegex = /```(?:json)?\s*\n?\s*\{[\s\S]*?"(?:tool|function|name)"[\s\S]*?\}\s*```/gi;
27
+ const jsonBlocks = text.match(jsonBlockRegex) || [];
28
+ for (const block of jsonBlocks) {
29
+ try {
30
+ const jsonStr = block.replace(/```(?:json)?\s*\n?/gi, '').replace(/\s*```/g, '');
31
+ const parsed = JSON.parse(jsonStr);
32
+ if (parsed.tool || parsed.function || parsed.name) {
33
+ calls.push({
34
+ name: parsed.tool || parsed.function || parsed.name,
35
+ arguments: parsed.args || parsed.arguments || parsed.parameters || {},
36
+ });
37
+ }
38
+ }
39
+ catch (e) {
40
+ // Invalid JSON, skip
41
+ }
42
+ }
43
+ // Pattern 2: XML-style tool calls
44
+ const xmlRegex = /<tool\s+name=["']([^"']+)["']>\s*([\s\S]*?)\s*<\/tool>/gi;
45
+ let xmlMatch;
46
+ while ((xmlMatch = xmlRegex.exec(text)) !== null) {
47
+ try {
48
+ const name = xmlMatch[1];
49
+ const argsStr = xmlMatch[2].trim();
50
+ const args = argsStr ? JSON.parse(argsStr) : {};
51
+ calls.push({ name, arguments: args });
52
+ }
53
+ catch (e) {
54
+ // Invalid args, skip
55
+ }
56
+ }
57
+ // Pattern 3: Function call style: tool_name({"arg": "value"})
58
+ const funcRegex = /\b([a-z_][a-z0-9_]*)\s*\(\s*(\{[\s\S]*?\})\s*\)/gi;
59
+ let funcMatch;
60
+ while ((funcMatch = funcRegex.exec(text)) !== null) {
61
+ const name = funcMatch[1];
62
+ // Check if it's a registered tool
63
+ if (tools_1.toolRegistry.has(name)) {
64
+ try {
65
+ const args = JSON.parse(funcMatch[2]);
66
+ calls.push({ name, arguments: args });
67
+ }
68
+ catch (e) {
69
+ // Invalid args, skip
70
+ }
71
+ }
72
+ }
73
+ return calls;
74
+ }
75
+ /**
76
+ * Execute a single tool call
77
+ */
78
+ async function executeTool(call, context) {
79
+ console.log(`[ToolExecutor] Executing: ${call.name}`, call.arguments);
80
+ return tools_1.toolRegistry.execute(call.name, call.arguments, context);
81
+ }
82
+ /**
83
+ * Execute multiple tool calls in sequence
84
+ */
85
+ async function executeToolCalls(calls, context) {
86
+ const results = [];
87
+ for (const call of calls) {
88
+ const result = await executeTool(call, context);
89
+ results.push({ call, result });
90
+ }
91
+ return results;
92
+ }
93
+ /**
94
+ * Format tool results for inclusion in AI response
95
+ */
96
+ function formatToolResults(results) {
97
+ if (results.length === 0)
98
+ return '';
99
+ return results.map(({ call, result }) => {
100
+ const status = result.success ? 'SUCCESS' : 'ERROR';
101
+ const data = result.success
102
+ ? JSON.stringify(result.data, null, 2)
103
+ : result.error;
104
+ return `[Tool: ${call.name}] ${status}\n${data}`;
105
+ }).join('\n\n');
106
+ }
107
+ /**
108
+ * Get the system prompt addition for tool usage
109
+ */
110
+ function getToolsSystemPrompt() {
111
+ const tools = tools_1.toolRegistry.list();
112
+ if (tools.length === 0) {
113
+ return '';
114
+ }
115
+ const toolDescriptions = tools.map(t => {
116
+ const params = t.parameters.map(p => ` - ${p.name} (${p.type}${p.required ? ', required' : ''}): ${p.description}`).join('\n');
117
+ return `**${t.name}**: ${t.description}\nParameters:\n${params}`;
118
+ }).join('\n\n');
119
+ return `
120
+ You have access to the following tools. To use a tool, include a JSON block in your response:
121
+
122
+ \`\`\`json
123
+ {"tool": "tool_name", "args": {"param1": "value1"}}
124
+ \`\`\`
125
+
126
+ Available tools:
127
+
128
+ ${toolDescriptions}
129
+
130
+ After I execute the tool, I'll show you the result so you can incorporate it into your response.
131
+ `;
132
+ }
133
+ /**
134
+ * Build context for tool execution
135
+ */
136
+ function buildToolContext(sessionId, userId, channel, config) {
137
+ return {
138
+ sessionId,
139
+ userId,
140
+ channel,
141
+ workspaceDir: config?.workspaceDir || process.cwd(),
142
+ config,
143
+ };
144
+ }
@@ -47,10 +47,12 @@ class ChannelManager {
47
47
  if (!channel) {
48
48
  // For webchat, broadcast via gateway
49
49
  if (channelName === 'webchat') {
50
+ console.log(`[Channels] Broadcasting to webchat: ${text.slice(0, 50)}...`);
50
51
  this.gateway.broadcast({ type: 'response', text, to });
51
52
  return;
52
53
  }
53
- throw new Error(`Channel not found: ${channelName}`);
54
+ console.error(`[Channels] Channel not found: ${channelName}`);
55
+ return;
54
56
  }
55
57
  await channel.send(to, text, replyTo);
56
58
  }
package/dist/cli/index.js CHANGED
@@ -452,9 +452,12 @@ async function startGatewayServer(config, options = {}) {
452
452
  host: '127.0.0.1',
453
453
  token: config.gateway?.token,
454
454
  verbose: options.verbose,
455
+ configDir: CONFIG_DIR,
455
456
  });
456
457
  // Configure agent
457
458
  gateway.agents.configure(config.agent);
459
+ // Initialize tools and skills system
460
+ await gateway.initializeTools();
458
461
  // Register channels
459
462
  if (config.channels?.telegram?.botToken) {
460
463
  const telegram = new telegram_1.TelegramChannel({
@@ -466,6 +469,9 @@ async function startGatewayServer(config, options = {}) {
466
469
  // Start gateway
467
470
  await gateway.start();
468
471
  await gateway.channels.connectAll();
472
+ // Show tool info
473
+ const tools = gateway.getToolRegistry().list();
474
+ const skills = gateway.getSkillManager()?.list() || [];
469
475
  console.log('');
470
476
  console.log(chalk.green('ApexBot is running!'));
471
477
  console.log('');
@@ -473,6 +479,9 @@ async function startGatewayServer(config, options = {}) {
473
479
  console.log(` ${chalk.cyan('API:')} http://127.0.0.1:${port}`);
474
480
  console.log(` ${chalk.cyan('WebSocket:')} ws://127.0.0.1:${port}`);
475
481
  console.log('');
482
+ console.log(` ${chalk.cyan('Tools:')} ${tools.length} available`);
483
+ console.log(` ${chalk.cyan('Skills:')} ${skills.length} registered`);
484
+ console.log('');
476
485
  console.log(chalk.gray('Press Ctrl+C to stop.'));
477
486
  console.log('');
478
487
  // Handle shutdown
@@ -828,6 +837,111 @@ program
828
837
  }
829
838
  });
830
839
  // ─────────────────────────────────────────────────────────────────
840
+ // SKILLS Command - Manage skills
841
+ // ─────────────────────────────────────────────────────────────────
842
+ program
843
+ .command('skills [action]')
844
+ .alias('skill')
845
+ .description('Manage ApexBot skills (integrations)')
846
+ .option('-n, --name <name>', 'Skill name')
847
+ .action(async (action = 'list', options) => {
848
+ const config = loadConfig();
849
+ const port = config.gateway?.port || 18789;
850
+ switch (action) {
851
+ case 'list':
852
+ console.log('');
853
+ console.log(chalk.cyan('Available Skills:'));
854
+ console.log('');
855
+ try {
856
+ const res = await fetch(`http://127.0.0.1:${port}/api/skills`);
857
+ const data = await res.json();
858
+ for (const skill of data.skills) {
859
+ const status = skill.enabled ? chalk.green('[ENABLED]') : chalk.gray('[disabled]');
860
+ console.log(` ${status} ${chalk.white(skill.name)} v${skill.version}`);
861
+ console.log(` ${chalk.gray(skill.description)}`);
862
+ console.log(` Tools: ${skill.tools.join(', ')}`);
863
+ console.log('');
864
+ }
865
+ }
866
+ catch (e) {
867
+ console.log(chalk.yellow('Gateway not running. Available skills:'));
868
+ console.log('');
869
+ console.log(' - obsidian Obsidian note-taking integration');
870
+ console.log(' - weather Weather forecasts');
871
+ console.log(' - reminder Reminders and timers');
872
+ console.log(' - system System info and process management');
873
+ console.log('');
874
+ }
875
+ break;
876
+ case 'enable':
877
+ if (!options.name) {
878
+ console.log(chalk.red('Please specify skill name: --name <skill>'));
879
+ return;
880
+ }
881
+ console.log(chalk.yellow(`Enabling skill: ${options.name}...`));
882
+ console.log(chalk.gray('(Skill configuration will be saved to ~/.apexbot/skills.json)'));
883
+ break;
884
+ case 'disable':
885
+ if (!options.name) {
886
+ console.log(chalk.red('Please specify skill name: --name <skill>'));
887
+ return;
888
+ }
889
+ console.log(chalk.yellow(`Disabling skill: ${options.name}...`));
890
+ break;
891
+ default:
892
+ console.log('Usage: apexbot skills [list|enable|disable] --name <skill>');
893
+ }
894
+ });
895
+ // ─────────────────────────────────────────────────────────────────
896
+ // TOOLS Command - List available tools
897
+ // ─────────────────────────────────────────────────────────────────
898
+ program
899
+ .command('tools')
900
+ .alias('tool')
901
+ .description('List available tools')
902
+ .action(async () => {
903
+ const config = loadConfig();
904
+ const port = config.gateway?.port || 18789;
905
+ console.log('');
906
+ console.log(chalk.cyan('Available Tools:'));
907
+ console.log('');
908
+ try {
909
+ const res = await fetch(`http://127.0.0.1:${port}/api/tools`);
910
+ const data = await res.json();
911
+ // Group by category
912
+ const byCategory = {};
913
+ for (const tool of data.tools) {
914
+ const cat = tool.category || 'other';
915
+ if (!byCategory[cat])
916
+ byCategory[cat] = [];
917
+ byCategory[cat].push(tool);
918
+ }
919
+ for (const [category, tools] of Object.entries(byCategory)) {
920
+ console.log(chalk.yellow(` ${category.toUpperCase()}`));
921
+ for (const tool of tools) {
922
+ console.log(` ${chalk.white(tool.name)} - ${chalk.gray(tool.description)}`);
923
+ }
924
+ console.log('');
925
+ }
926
+ }
927
+ catch (e) {
928
+ console.log(chalk.yellow('Gateway not running. Built-in tools:'));
929
+ console.log('');
930
+ console.log(' CORE');
931
+ console.log(' shell Execute shell commands');
932
+ console.log(' read_file Read file contents');
933
+ console.log(' write_file Write to files');
934
+ console.log(' list_dir List directory contents');
935
+ console.log(' edit_file Edit files (search/replace)');
936
+ console.log(' fetch_url Fetch web pages');
937
+ console.log(' web_search Search the web');
938
+ console.log(' datetime Date/time operations');
939
+ console.log(' math Mathematical calculations');
940
+ console.log(' convert Unit conversions');
941
+ console.log('');
942
+ }
943
+ });
944
+ // ─────────────────────────────────────────────────────────────────
831
945
  // Default action - show help with banner
832
946
  // ─────────────────────────────────────────────────────────────────
833
947
  program
@@ -10,16 +10,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.Gateway = void 0;
11
11
  const ws_1 = require("ws");
12
12
  const http_1 = __importDefault(require("http"));
13
+ const path_1 = __importDefault(require("path"));
13
14
  const eventBus_1 = require("../core/eventBus");
14
15
  const sessionManager_1 = require("../sessions/sessionManager");
15
16
  const channelManager_1 = require("../channels/channelManager");
16
17
  const agentManager_1 = require("../agent/agentManager");
17
18
  const dashboard_1 = require("./dashboard");
19
+ const loader_1 = require("../tools/loader");
18
20
  class Gateway {
19
21
  config;
20
22
  server = null;
21
23
  wss = null;
22
24
  clients = new Map();
25
+ skillManager = null;
23
26
  sessions;
24
27
  channels;
25
28
  agents;
@@ -29,34 +32,69 @@ class Gateway {
29
32
  host: config.host || '127.0.0.1',
30
33
  token: config.token,
31
34
  verbose: config.verbose || false,
35
+ configDir: config.configDir || path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', '.apexbot'),
32
36
  };
33
37
  this.sessions = new sessionManager_1.SessionManager();
34
38
  this.channels = new channelManager_1.ChannelManager(this);
35
39
  this.agents = new agentManager_1.AgentManager();
36
40
  this.setupEventHandlers();
37
41
  }
42
+ /**
43
+ * Initialize tools and skills system
44
+ */
45
+ async initializeTools() {
46
+ console.log('[Gateway] Initializing tools system...');
47
+ this.skillManager = await (0, loader_1.initializeToolsSystem)(this.config.configDir);
48
+ console.log(`[Gateway] Tools ready: ${loader_1.toolRegistry.list().length} tools, ${this.skillManager.list().length} skills`);
49
+ }
50
+ getSkillManager() {
51
+ return this.skillManager;
52
+ }
53
+ getToolRegistry() {
54
+ return loader_1.toolRegistry;
55
+ }
38
56
  setupEventHandlers() {
39
57
  // Route incoming messages to agent
40
58
  (0, eventBus_1.on)('channel:message', async (msg) => {
41
- if (this.config.verbose) {
42
- console.log(`[Gateway] Message from ${msg.channel}:${msg.from}: ${msg.text?.slice(0, 50)}...`);
43
- }
59
+ console.log(`[Gateway] Message from ${msg.channel}:${msg.from}: ${msg.text?.slice(0, 50)}...`);
44
60
  // Get or create session
45
61
  const session = this.sessions.getOrCreate(msg.channel, msg.from, msg.isGroup);
46
- // Process with agent
47
- const response = await this.agents.process(session, msg);
48
- // Send response back
49
- if (response) {
62
+ try {
63
+ // Process with agent
64
+ const response = await this.agents.process(session, msg);
65
+ console.log(`[Gateway] Agent response: ${response?.text?.slice(0, 50)}...`);
66
+ // Send response back
67
+ if (response && response.text) {
68
+ (0, eventBus_1.emit)('channel:send', {
69
+ channel: msg.channel,
70
+ to: msg.from,
71
+ text: response.text,
72
+ replyTo: msg.id,
73
+ });
74
+ }
75
+ else {
76
+ console.warn('[Gateway] No response from agent');
77
+ (0, eventBus_1.emit)('channel:send', {
78
+ channel: msg.channel,
79
+ to: msg.from,
80
+ text: 'Sorry, I could not generate a response. Please try again.',
81
+ replyTo: msg.id,
82
+ });
83
+ }
84
+ }
85
+ catch (error) {
86
+ console.error('[Gateway] Error processing message:', error);
50
87
  (0, eventBus_1.emit)('channel:send', {
51
88
  channel: msg.channel,
52
89
  to: msg.from,
53
- text: response.text,
90
+ text: `Error: ${error.message || 'Unknown error'}`,
54
91
  replyTo: msg.id,
55
92
  });
56
93
  }
57
94
  });
58
95
  // Handle outgoing messages
59
96
  (0, eventBus_1.on)('channel:send', async (msg) => {
97
+ console.log(`[Gateway] Sending to ${msg.channel}:${msg.to}: ${msg.text?.slice(0, 50)}...`);
60
98
  await this.channels.send(msg.channel, msg.to, msg.text, msg.replyTo);
61
99
  });
62
100
  }
@@ -131,6 +169,22 @@ class Gateway {
131
169
  res.writeHead(200, { 'Content-Type': 'application/json' });
132
170
  res.end(JSON.stringify(this.sessions.getAll()));
133
171
  break;
172
+ case '/api/tools':
173
+ res.writeHead(200, { 'Content-Type': 'application/json' });
174
+ res.end(JSON.stringify({
175
+ tools: loader_1.toolRegistry.list(),
176
+ count: loader_1.toolRegistry.list().length,
177
+ categories: loader_1.toolRegistry.getCategories(),
178
+ }));
179
+ break;
180
+ case '/api/skills':
181
+ res.writeHead(200, { 'Content-Type': 'application/json' });
182
+ const sm = this.getSkillManager();
183
+ res.end(JSON.stringify({
184
+ skills: sm ? sm.list() : [],
185
+ enabled: sm ? sm.listEnabled() : [],
186
+ }));
187
+ break;
134
188
  default:
135
189
  // Serve Dashboard UI
136
190
  if (url.pathname.startsWith('/chat') || url.pathname === '/ui' || url.pathname === '/dashboard') {