@nomad-e/bluma-cli 0.0.108 → 0.0.110

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/dist/main.js CHANGED
@@ -1,491 +1,660 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
2
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
4
  var __esm = (fn, res) => function __init() {
4
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
6
  };
6
-
7
- // src/app/agent/core/llm/tool_call_normalizer.ts
8
- import { randomUUID } from "crypto";
9
- var ToolCallNormalizer;
10
- var init_tool_call_normalizer = __esm({
11
- "src/app/agent/core/llm/tool_call_normalizer.ts"() {
12
- "use strict";
13
- ToolCallNormalizer = class {
14
- /**
15
- * Normaliza a mensagem do assistant, convertendo diferentes formatos de tool calls
16
- */
17
- static normalizeAssistantMessage(message) {
18
- if (message.tool_calls && this.isOpenAIFormat(message.tool_calls)) {
19
- return message;
20
- }
21
- const toolCalls = this.extractToolCalls(message);
22
- if (toolCalls.length > 0) {
23
- return {
24
- role: message.role || "assistant",
25
- content: message.content || null,
26
- tool_calls: toolCalls
27
- };
28
- }
29
- return message;
30
- }
31
- /**
32
- * Verifica se já está no formato OpenAI
33
- */
34
- static isOpenAIFormat(toolCalls) {
35
- if (!Array.isArray(toolCalls) || toolCalls.length === 0) return false;
36
- const firstCall = toolCalls[0];
37
- return typeof firstCall.id === "string" && firstCall.type === "function" && typeof firstCall.function?.name === "string" && typeof firstCall.function?.arguments === "string";
38
- }
39
- /**
40
- * Extrai tool calls de diversos formatos possíveis
41
- */
42
- static extractToolCalls(message) {
43
- const results = [];
44
- if (message.tool_calls && Array.isArray(message.tool_calls)) {
45
- for (const call of message.tool_calls) {
46
- const normalized = this.normalizeToolCall(call);
47
- if (normalized) results.push(normalized);
48
- }
49
- }
50
- if (typeof message.content === "string" && message.content.trim()) {
51
- const extracted = this.extractFromContent(message.content);
52
- results.push(...extracted);
53
- }
54
- if (message.function_call) {
55
- const normalized = this.normalizeToolCall(message.function_call);
56
- if (normalized) results.push(normalized);
57
- }
58
- return results;
59
- }
60
- /**
61
- * Normaliza um único tool call para o formato OpenAI
62
- */
63
- static normalizeToolCall(call) {
64
- try {
65
- if (call.id && call.function?.name) {
66
- return {
67
- id: call.id,
68
- type: "function",
69
- function: {
70
- name: call.function.name,
71
- arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments)
72
- }
73
- };
74
- }
75
- if (call.name) {
76
- return {
77
- id: call.id || randomUUID(),
78
- type: "function",
79
- function: {
80
- name: call.name,
81
- arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments || {})
82
- }
83
- };
84
- }
85
- if (call.function && typeof call.function === "object") {
86
- return {
87
- id: call.id || randomUUID(),
88
- type: "function",
89
- function: {
90
- name: call.function.name,
91
- arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments || {})
92
- }
93
- };
94
- }
95
- return null;
96
- } catch (error) {
97
- console.error("Error normalizing tool call:", error, call);
98
- return null;
99
- }
100
- }
101
- /**
102
- * Extrai tool calls do content (pode estar em markdown, JSON, etc)
103
- */
104
- static extractFromContent(content) {
105
- const results = [];
106
- const cleanContent = content.replace(/```(?:json)?\s*([\s\S]*?)```/g, "$1");
107
- const jsonMatches = this.extractJsonObjects(cleanContent);
108
- for (const jsonStr of jsonMatches) {
109
- try {
110
- const parsed = JSON.parse(jsonStr);
111
- if (Array.isArray(parsed)) {
112
- for (const call of parsed) {
113
- const normalized = this.normalizeToolCall(call);
114
- if (normalized) results.push(normalized);
115
- }
116
- } else if (parsed.name || parsed.function) {
117
- const normalized = this.normalizeToolCall(parsed);
118
- if (normalized) results.push(normalized);
119
- } else if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
120
- for (const call of parsed.tool_calls) {
121
- const normalized = this.normalizeToolCall(call);
122
- if (normalized) results.push(normalized);
123
- }
124
- }
125
- } catch (e) {
126
- }
127
- }
128
- return results;
129
- }
130
- /**
131
- * Extrai objetos JSON de uma string (suporta múltiplos objetos)
132
- */
133
- static extractJsonObjects(text) {
134
- const results = [];
135
- let depth = 0;
136
- let start = -1;
137
- for (let i = 0; i < text.length; i++) {
138
- if (text[i] === "{") {
139
- if (depth === 0) start = i;
140
- depth++;
141
- } else if (text[i] === "}") {
142
- depth--;
143
- if (depth === 0 && start !== -1) {
144
- results.push(text.substring(start, i + 1));
145
- start = -1;
146
- }
147
- }
148
- }
149
- return results;
150
- }
151
- /**
152
- * Valida se um tool call normalizado é válido
153
- */
154
- static isValidToolCall(call) {
155
- return !!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string");
156
- }
157
- };
158
- }
159
- });
160
-
161
- // src/main.ts
162
- import React10 from "react";
163
- import { render } from "ink";
164
- import { EventEmitter as EventEmitter2 } from "events";
165
- import { v4 as uuidv43 } from "uuid";
166
-
167
- // src/app/ui/App.tsx
168
- import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback2, memo as memo10 } from "react";
169
- import { Box as Box16, Text as Text15, Static } from "ink";
170
-
171
- // src/app/ui/layout.tsx
172
- import { Box, Text } from "ink";
173
- import { memo } from "react";
174
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
175
- var HeaderComponent = ({
176
- sessionId: sessionId2,
177
- workdir
178
- }) => {
179
- const dirName = workdir.split("/").pop() || workdir;
180
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
181
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "bluma \u2014 coding agent" }) }),
182
- /* @__PURE__ */ jsxs(Box, { children: [
183
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "cwd " }),
184
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: dirName }),
185
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
186
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "session " }),
187
- /* @__PURE__ */ jsx(Text, { color: "gray", children: sessionId2.slice(0, 8) })
188
- ] })
189
- ] });
190
- };
191
- var Header = memo(HeaderComponent);
192
- var SessionInfoComponent = ({
193
- sessionId: sessionId2,
194
- workdir,
195
- toolsCount,
196
- mcpStatus
197
- }) => /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
198
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "mcp " }),
199
- /* @__PURE__ */ jsx(Text, { color: mcpStatus === "connected" ? "green" : "yellow", children: mcpStatus === "connected" ? "+" : "-" }),
200
- toolsCount !== null && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
201
- " ",
202
- toolsCount,
203
- " tools"
204
- ] }) })
205
- ] });
206
- var SessionInfo = memo(SessionInfoComponent);
207
- var TaskStatusBarComponent = ({
208
- taskName,
209
- mode,
210
- status
211
- }) => {
212
- const modeColors = {
213
- PLANNING: "blue",
214
- EXECUTION: "green",
215
- VERIFICATION: "yellow"
216
- };
217
- return /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
218
- /* @__PURE__ */ jsxs(Text, { color: modeColors[mode], bold: true, children: [
219
- "[",
220
- mode.charAt(0),
221
- "]"
222
- ] }),
223
- /* @__PURE__ */ jsxs(Text, { children: [
224
- " ",
225
- taskName
226
- ] }),
227
- status && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
228
- " - ",
229
- status
230
- ] })
231
- ] });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
232
10
  };
233
- var TaskStatusBar = memo(TaskStatusBarComponent);
234
-
235
- // src/app/ui/components/InputPrompt.tsx
236
- import { Box as Box2, Text as Text2, useStdout, useInput as useInput2 } from "ink";
237
11
 
238
- // src/app/ui/utils/useSimpleInputBuffer.ts
239
- import { useReducer, useRef, useCallback, useEffect } from "react";
240
- import { useInput } from "ink";
241
- function getLineStart(text, cursorPos) {
242
- let pos = cursorPos - 1;
243
- while (pos >= 0 && text[pos] !== "\n") {
244
- pos--;
12
+ // src/app/agent/tools/natives/async_command.ts
13
+ var async_command_exports = {};
14
+ __export(async_command_exports, {
15
+ commandStatus: () => commandStatus,
16
+ killCommand: () => killCommand,
17
+ runCommandAsync: () => runCommandAsync,
18
+ sendCommandInput: () => sendCommandInput
19
+ });
20
+ import os2 from "os";
21
+ import { spawn } from "child_process";
22
+ import { v4 as uuidv42 } from "uuid";
23
+ function cleanupOldCommands() {
24
+ if (runningCommands.size <= MAX_STORED_COMMANDS) return;
25
+ const commands = Array.from(runningCommands.entries()).filter(([_, cmd]) => cmd.status !== "running").sort((a, b) => (a[1].endTime || 0) - (b[1].endTime || 0));
26
+ const toRemove = commands.slice(0, commands.length - MAX_STORED_COMMANDS + 10);
27
+ for (const [id] of toRemove) {
28
+ runningCommands.delete(id);
245
29
  }
246
- return pos + 1;
247
30
  }
248
- function getLineEnd(text, cursorPos) {
249
- let pos = cursorPos;
250
- while (pos < text.length && text[pos] !== "\n") {
251
- pos++;
31
+ function isDangerousCommand(command) {
32
+ const trimmed = command.trim();
33
+ for (const pattern of DANGEROUS_PATTERNS) {
34
+ if (pattern.test(trimmed)) {
35
+ return "Command requires elevated privileges (sudo/doas) which cannot be handled in async mode";
36
+ }
252
37
  }
253
- return pos;
254
- }
255
- function moveToAdjacentLine(text, cursorPos, direction) {
256
- const currentLineStart = getLineStart(text, cursorPos);
257
- const currentLineEnd = getLineEnd(text, cursorPos);
258
- const column = cursorPos - currentLineStart;
259
- if (direction === "up") {
260
- if (currentLineStart === 0) return cursorPos;
261
- const prevLineEnd = currentLineStart - 1;
262
- const prevLineStart = getLineStart(text, prevLineEnd);
263
- const prevLineLength = prevLineEnd - prevLineStart;
264
- return prevLineStart + Math.min(column, prevLineLength);
265
- } else {
266
- if (currentLineEnd === text.length) return cursorPos;
267
- const nextLineStart = currentLineEnd + 1;
268
- const nextLineEnd = getLineEnd(text, nextLineStart);
269
- const nextLineLength = nextLineEnd - nextLineStart;
270
- return nextLineStart + Math.min(column, nextLineLength);
38
+ for (const pattern of INTERACTIVE_PATTERNS) {
39
+ if (pattern.test(trimmed)) {
40
+ return "Interactive commands are not supported in async mode";
41
+ }
271
42
  }
43
+ return null;
272
44
  }
273
- function inputReducer(state, action, viewWidth) {
274
- const adjustView = (newCursorPos, currentViewStart) => {
275
- if (newCursorPos < currentViewStart) {
276
- return newCursorPos;
277
- }
278
- if (newCursorPos >= currentViewStart + viewWidth) {
279
- return newCursorPos - viewWidth + 1;
45
+ async function runCommandAsync(args) {
46
+ try {
47
+ const {
48
+ command,
49
+ cwd = process.cwd(),
50
+ timeout = 0
51
+ } = args;
52
+ if (!command || typeof command !== "string") {
53
+ return {
54
+ success: false,
55
+ error: "command is required and must be a string"
56
+ };
280
57
  }
281
- return currentViewStart;
282
- };
283
- switch (action.type) {
284
- case "INPUT": {
285
- const cleanInput = action.payload.replace(/(\r\n|\r)/gm, "\n");
286
- const newText = state.text.slice(0, state.cursorPosition) + cleanInput + state.text.slice(state.cursorPosition);
287
- const newCursorPosition = state.cursorPosition + cleanInput.length;
288
- const newViewStart = adjustView(newCursorPosition, state.viewStart);
289
- if (newText === state.text && newCursorPosition === state.cursorPosition) {
290
- return state;
291
- }
292
- return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
58
+ const dangerReason = isDangerousCommand(command);
59
+ if (dangerReason) {
60
+ return {
61
+ success: false,
62
+ error: dangerReason
63
+ };
293
64
  }
294
- case "NEWLINE": {
295
- const newText = state.text.slice(0, state.cursorPosition) + "\n" + state.text.slice(state.cursorPosition);
296
- const newCursorPosition = state.cursorPosition + 1;
297
- const newViewStart = adjustView(newCursorPosition, state.viewStart);
298
- return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
65
+ const commandId = uuidv42().substring(0, 8);
66
+ const platform = os2.platform();
67
+ let shellCmd;
68
+ let shellArgs;
69
+ if (platform === "win32") {
70
+ shellCmd = process.env.COMSPEC || "cmd.exe";
71
+ shellArgs = ["/c", command];
72
+ } else {
73
+ shellCmd = process.env.SHELL || "/bin/bash";
74
+ shellArgs = ["-c", command];
299
75
  }
300
- case "MOVE_CURSOR": {
301
- let newCursorPosition = state.cursorPosition;
302
- if (action.direction === "left" && state.cursorPosition > 0) {
303
- newCursorPosition--;
304
- } else if (action.direction === "right" && state.cursorPosition < state.text.length) {
305
- newCursorPosition++;
306
- } else if (action.direction === "up") {
307
- newCursorPosition = moveToAdjacentLine(state.text, state.cursorPosition, "up");
308
- } else if (action.direction === "down") {
309
- newCursorPosition = moveToAdjacentLine(state.text, state.cursorPosition, "down");
76
+ const entry = {
77
+ id: commandId,
78
+ command,
79
+ status: "running",
80
+ stdout: "",
81
+ stderr: "",
82
+ exitCode: null,
83
+ startTime: Date.now(),
84
+ process: null
85
+ };
86
+ const child = spawn(shellCmd, shellArgs, {
87
+ cwd,
88
+ env: process.env,
89
+ windowsHide: true
90
+ });
91
+ entry.process = child;
92
+ let stdoutTruncated = false;
93
+ child.stdout?.on("data", (data) => {
94
+ if (!stdoutTruncated) {
95
+ entry.stdout += data.toString();
96
+ if (entry.stdout.length > MAX_OUTPUT_SIZE) {
97
+ entry.stdout = entry.stdout.substring(0, MAX_OUTPUT_SIZE) + OUTPUT_TRUNCATION_MSG;
98
+ stdoutTruncated = true;
99
+ }
310
100
  }
311
- if (newCursorPosition === state.cursorPosition) {
312
- return state;
101
+ });
102
+ let stderrTruncated = false;
103
+ child.stderr?.on("data", (data) => {
104
+ if (!stderrTruncated) {
105
+ entry.stderr += data.toString();
106
+ if (entry.stderr.length > MAX_OUTPUT_SIZE) {
107
+ entry.stderr = entry.stderr.substring(0, MAX_OUTPUT_SIZE) + OUTPUT_TRUNCATION_MSG;
108
+ stderrTruncated = true;
109
+ }
313
110
  }
314
- const newViewStart = adjustView(newCursorPosition, state.viewStart);
315
- return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
111
+ });
112
+ child.on("close", (code) => {
113
+ entry.exitCode = code;
114
+ entry.status = code === 0 ? "completed" : "error";
115
+ entry.endTime = Date.now();
116
+ entry.process = null;
117
+ });
118
+ child.on("error", (error) => {
119
+ entry.stderr += `
120
+ Process error: ${error.message}`;
121
+ entry.status = "error";
122
+ entry.endTime = Date.now();
123
+ entry.process = null;
124
+ });
125
+ if (timeout > 0) {
126
+ setTimeout(() => {
127
+ if (entry.status === "running") {
128
+ child.kill("SIGTERM");
129
+ setTimeout(() => {
130
+ if (entry.status === "running") {
131
+ child.kill("SIGKILL");
132
+ }
133
+ }, 2e3);
134
+ entry.status = "timeout";
135
+ entry.stderr += `
136
+ Command timed out after ${timeout} seconds`;
137
+ }
138
+ }, timeout * 1e3);
316
139
  }
317
- case "MOVE_LINE_START": {
318
- const newCursorPosition = getLineStart(state.text, state.cursorPosition);
319
- if (newCursorPosition === state.cursorPosition) {
320
- return state;
321
- }
322
- const newViewStart = adjustView(newCursorPosition, state.viewStart);
323
- return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
140
+ runningCommands.set(commandId, entry);
141
+ cleanupOldCommands();
142
+ return {
143
+ success: true,
144
+ command_id: commandId,
145
+ command,
146
+ message: `Command started in background. Use command_status with id "${commandId}" to check progress.`
147
+ };
148
+ } catch (error) {
149
+ return {
150
+ success: false,
151
+ error: `Unexpected error: ${error.message}`
152
+ };
153
+ }
154
+ }
155
+ async function commandStatus(args) {
156
+ try {
157
+ const {
158
+ command_id,
159
+ wait_seconds = 0,
160
+ output_limit = 1e4
161
+ } = args;
162
+ if (!command_id) {
163
+ return {
164
+ success: false,
165
+ command_id: "",
166
+ status: "error",
167
+ error: "command_id is required"
168
+ };
324
169
  }
325
- case "MOVE_LINE_END": {
326
- const newCursorPosition = getLineEnd(state.text, state.cursorPosition);
327
- if (newCursorPosition === state.cursorPosition) {
328
- return state;
329
- }
330
- const newViewStart = adjustView(newCursorPosition, state.viewStart);
331
- return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
170
+ const entry = runningCommands.get(command_id);
171
+ if (!entry) {
172
+ return {
173
+ success: false,
174
+ command_id,
175
+ status: "not_found",
176
+ error: `Command with id "${command_id}" not found. It may have expired or never existed.`
177
+ };
332
178
  }
333
- case "BACKSPACE": {
334
- if (state.cursorPosition > 0) {
335
- const newText = state.text.slice(0, state.cursorPosition - 1) + state.text.slice(state.cursorPosition);
336
- const newCursorPosition = state.cursorPosition - 1;
337
- const newViewStart = adjustView(newCursorPosition, state.viewStart);
338
- return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
339
- }
340
- return state;
179
+ const maxWait = Math.min(wait_seconds, 15);
180
+ if (maxWait > 0 && entry.status === "running") {
181
+ await new Promise((resolve) => {
182
+ const checkInterval = setInterval(() => {
183
+ if (entry.status !== "running") {
184
+ clearInterval(checkInterval);
185
+ resolve();
186
+ }
187
+ }, 100);
188
+ setTimeout(() => {
189
+ clearInterval(checkInterval);
190
+ resolve();
191
+ }, maxWait * 1e3);
192
+ });
341
193
  }
342
- case "DELETE": {
343
- if (state.cursorPosition < state.text.length) {
344
- const newText = state.text.slice(0, state.cursorPosition) + state.text.slice(state.cursorPosition + 1);
345
- const newViewStart = adjustView(state.cursorPosition, state.viewStart);
346
- return { text: newText, cursorPosition: state.cursorPosition, viewStart: newViewStart };
347
- }
348
- return state;
194
+ let stdout = entry.stdout;
195
+ let stderr = entry.stderr;
196
+ let truncated = false;
197
+ if (stdout.length > output_limit) {
198
+ stdout = "..." + stdout.substring(stdout.length - output_limit);
199
+ truncated = true;
349
200
  }
350
- case "SUBMIT": {
351
- return { text: "", cursorPosition: 0, viewStart: 0 };
201
+ if (stderr.length > output_limit) {
202
+ stderr = "..." + stderr.substring(stderr.length - output_limit);
203
+ truncated = true;
352
204
  }
353
- case "SET": {
354
- const t = action.payload.text.replace(/(\r\n|\r)/gm, "\n");
355
- let newCursorPosition;
356
- if (typeof action.payload.cursorPosition === "number") {
357
- newCursorPosition = Math.min(action.payload.cursorPosition, t.length);
358
- } else if (action.payload.moveCursorToEnd ?? true) {
359
- newCursorPosition = t.length;
360
- } else {
361
- newCursorPosition = Math.min(state.cursorPosition, t.length);
362
- }
363
- const newText = t;
364
- const newViewStart = adjustView(newCursorPosition, 0);
365
- return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
205
+ const duration = entry.endTime ? (entry.endTime - entry.startTime) / 1e3 : (Date.now() - entry.startTime) / 1e3;
206
+ return {
207
+ success: true,
208
+ command_id,
209
+ status: entry.status,
210
+ stdout: stdout || void 0,
211
+ stderr: stderr || void 0,
212
+ exit_code: entry.exitCode,
213
+ duration_seconds: Math.round(duration * 10) / 10,
214
+ truncated
215
+ };
216
+ } catch (error) {
217
+ return {
218
+ success: false,
219
+ command_id: args.command_id || "",
220
+ status: "error",
221
+ error: `Unexpected error: ${error.message}`
222
+ };
223
+ }
224
+ }
225
+ async function sendCommandInput(args) {
226
+ try {
227
+ const { command_id, input } = args;
228
+ if (!command_id || input === void 0) {
229
+ return {
230
+ success: false,
231
+ error: "command_id and input are required"
232
+ };
366
233
  }
367
- case "SET_CURSOR": {
368
- const newCursorPosition = Math.max(0, Math.min(action.payload, state.text.length));
369
- if (newCursorPosition === state.cursorPosition) {
370
- return state;
371
- }
372
- const newViewStart = adjustView(newCursorPosition, state.viewStart);
373
- return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
234
+ const entry = runningCommands.get(command_id);
235
+ if (!entry) {
236
+ return {
237
+ success: false,
238
+ error: `Command with id "${command_id}" not found`
239
+ };
374
240
  }
375
- default:
376
- return state;
241
+ if (entry.status !== "running" || !entry.process) {
242
+ return {
243
+ success: false,
244
+ error: `Command is not running (status: ${entry.status})`
245
+ };
246
+ }
247
+ entry.process.stdin?.write(input);
248
+ return {
249
+ success: true,
250
+ message: `Sent ${input.length} characters to command ${command_id}`
251
+ };
252
+ } catch (error) {
253
+ return {
254
+ success: false,
255
+ error: `Failed to send input: ${error.message}`
256
+ };
377
257
  }
378
258
  }
379
- var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
380
- const [state, dispatch] = useReducer(
381
- (s, a) => inputReducer(s, a, viewWidth),
382
- { text: "", cursorPosition: 0, viewStart: 0 }
383
- );
384
- const inputBuffer = useRef("");
385
- const flushScheduled = useRef(false);
386
- const flushInputBuffer = useCallback(() => {
387
- if (inputBuffer.current.length > 0) {
388
- const buffered = inputBuffer.current;
389
- inputBuffer.current = "";
390
- dispatch({ type: "INPUT", payload: buffered });
259
+ async function killCommand(args) {
260
+ try {
261
+ const { command_id } = args;
262
+ const entry = runningCommands.get(command_id);
263
+ if (!entry) {
264
+ return {
265
+ success: false,
266
+ error: `Command with id "${command_id}" not found`
267
+ };
391
268
  }
392
- flushScheduled.current = false;
393
- }, []);
394
- useEffect(() => {
395
- return () => {
396
- if (flushScheduled.current) {
397
- flushInputBuffer();
398
- }
269
+ if (entry.status !== "running" || !entry.process) {
270
+ return {
271
+ success: false,
272
+ error: `Command is not running (status: ${entry.status})`
273
+ };
274
+ }
275
+ entry.process.kill("SIGTERM");
276
+ entry.status = "killed";
277
+ entry.endTime = Date.now();
278
+ return {
279
+ success: true,
280
+ message: `Command ${command_id} killed`
399
281
  };
400
- }, [flushInputBuffer]);
401
- useInput(
402
- (input, key) => {
403
- const hasBackspaceFlag = key.backspace;
404
- const hasDeleteFlag = key.delete;
405
- const hasBackspaceChar = input === "\x7F" || input === "\b" || input === "\b" || input.charCodeAt(0) === 127 || input.charCodeAt(0) === 8;
406
- if (hasBackspaceFlag || hasBackspaceChar) {
407
- if (inputBuffer.current.length > 0) {
408
- flushInputBuffer();
409
- }
410
- dispatch({ type: "BACKSPACE" });
411
- return;
412
- }
413
- if (hasDeleteFlag && (key.ctrl || key.meta)) {
414
- if (inputBuffer.current.length > 0) {
415
- flushInputBuffer();
416
- }
417
- dispatch({ type: "DELETE" });
418
- return;
419
- }
420
- if (hasDeleteFlag && !key.ctrl && !key.meta) {
421
- if (inputBuffer.current.length > 0) {
422
- flushInputBuffer();
423
- }
424
- dispatch({ type: "BACKSPACE" });
425
- return;
426
- }
427
- if (inputBuffer.current.length > 0 && (key.ctrl || key.meta || key.escape || key.return || key.leftArrow || key.rightArrow || key.upArrow || key.downArrow || key.tab || key.shift)) {
428
- flushInputBuffer();
282
+ } catch (error) {
283
+ return {
284
+ success: false,
285
+ error: `Failed to kill command: ${error.message}`
286
+ };
287
+ }
288
+ }
289
+ var runningCommands, MAX_OUTPUT_SIZE, MAX_STORED_COMMANDS, OUTPUT_TRUNCATION_MSG, DANGEROUS_PATTERNS, INTERACTIVE_PATTERNS;
290
+ var init_async_command = __esm({
291
+ "src/app/agent/tools/natives/async_command.ts"() {
292
+ "use strict";
293
+ runningCommands = /* @__PURE__ */ new Map();
294
+ MAX_OUTPUT_SIZE = 3e4;
295
+ MAX_STORED_COMMANDS = 50;
296
+ OUTPUT_TRUNCATION_MSG = "\n[OUTPUT TRUNCATED - 30KB/200 lines max]";
297
+ DANGEROUS_PATTERNS = [
298
+ // Elevação de privilégios
299
+ /^sudo\s+/i,
300
+ /^doas\s+/i,
301
+ /^su\s+/i,
302
+ /^pkexec\s+/i,
303
+ /\|\s*sudo\s+/i,
304
+ /;\s*sudo\s+/i,
305
+ /&&\s*sudo\s+/i,
306
+ // Comandos destrutivos
307
+ /\brm\s+(-[rf]+\s+)*[\/~]/i,
308
+ /\brm\s+-[rf]*\s+\*/i,
309
+ /\bchmod\s+(777|666)\s+\//i,
310
+ /\bdd\s+.*of=\/dev\/(sd|hd|nvme)/i,
311
+ /\bmkfs\./i,
312
+ // Fork bombs
313
+ /:\(\)\s*\{\s*:\|:&\s*\}\s*;:/,
314
+ // Remote code exec
315
+ /\bcurl\s+.*\|\s*(ba)?sh/i,
316
+ /\bwget\s+.*\|\s*(ba)?sh/i
317
+ ];
318
+ INTERACTIVE_PATTERNS = [
319
+ /^(vim|vi|nano|emacs|pico)\s*/i,
320
+ /^(less|more|most)\s*/i,
321
+ /^(top|htop|btop|atop|nmon)\s*/i,
322
+ /^(ssh|telnet|ftp|sftp)\s+/i,
323
+ /^(mysql|psql|redis-cli|mongo|mongosh)\s*$/i,
324
+ /^(python|python3|node|ruby|irb|lua)\s*$/i,
325
+ /^(gdb|lldb)\s*/i
326
+ ];
327
+ }
328
+ });
329
+
330
+ // src/main.ts
331
+ import React10 from "react";
332
+ import { render } from "ink";
333
+ import { EventEmitter as EventEmitter2 } from "events";
334
+ import { v4 as uuidv43 } from "uuid";
335
+
336
+ // src/app/ui/App.tsx
337
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback2, memo as memo10 } from "react";
338
+ import { Box as Box16, Text as Text15, Static } from "ink";
339
+
340
+ // src/app/ui/layout.tsx
341
+ import { Box, Text } from "ink";
342
+ import { memo } from "react";
343
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
344
+ var HeaderComponent = ({
345
+ sessionId: sessionId2,
346
+ workdir
347
+ }) => {
348
+ const dirName = workdir.split("/").pop() || workdir;
349
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
350
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "bluma \u2014 coding agent" }) }),
351
+ /* @__PURE__ */ jsxs(Box, { children: [
352
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "cwd " }),
353
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: dirName }),
354
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
355
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "session " }),
356
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: sessionId2.slice(0, 8) })
357
+ ] })
358
+ ] });
359
+ };
360
+ var Header = memo(HeaderComponent);
361
+ var SessionInfoComponent = ({
362
+ sessionId: sessionId2,
363
+ workdir,
364
+ toolsCount,
365
+ mcpStatus
366
+ }) => /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
367
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "mcp " }),
368
+ /* @__PURE__ */ jsx(Text, { color: mcpStatus === "connected" ? "green" : "yellow", children: mcpStatus === "connected" ? "+" : "-" }),
369
+ toolsCount !== null && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
370
+ " ",
371
+ toolsCount,
372
+ " tools"
373
+ ] }) })
374
+ ] });
375
+ var SessionInfo = memo(SessionInfoComponent);
376
+ var TaskStatusBarComponent = ({
377
+ taskName,
378
+ mode,
379
+ status
380
+ }) => {
381
+ const modeColors = {
382
+ PLANNING: "blue",
383
+ EXECUTION: "green",
384
+ VERIFICATION: "yellow"
385
+ };
386
+ return /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
387
+ /* @__PURE__ */ jsxs(Text, { color: modeColors[mode], bold: true, children: [
388
+ "[",
389
+ mode.charAt(0),
390
+ "]"
391
+ ] }),
392
+ /* @__PURE__ */ jsxs(Text, { children: [
393
+ " ",
394
+ taskName
395
+ ] }),
396
+ status && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
397
+ " - ",
398
+ status
399
+ ] })
400
+ ] });
401
+ };
402
+ var TaskStatusBar = memo(TaskStatusBarComponent);
403
+
404
+ // src/app/ui/components/InputPrompt.tsx
405
+ import { Box as Box2, Text as Text2, useStdout, useInput as useInput2 } from "ink";
406
+
407
+ // src/app/ui/utils/useSimpleInputBuffer.ts
408
+ import { useReducer, useRef, useCallback, useEffect } from "react";
409
+ import { useInput } from "ink";
410
+ function getLineStart(text, cursorPos) {
411
+ let pos = cursorPos - 1;
412
+ while (pos >= 0 && text[pos] !== "\n") {
413
+ pos--;
414
+ }
415
+ return pos + 1;
416
+ }
417
+ function getLineEnd(text, cursorPos) {
418
+ let pos = cursorPos;
419
+ while (pos < text.length && text[pos] !== "\n") {
420
+ pos++;
421
+ }
422
+ return pos;
423
+ }
424
+ function moveToAdjacentLine(text, cursorPos, direction) {
425
+ const currentLineStart = getLineStart(text, cursorPos);
426
+ const currentLineEnd = getLineEnd(text, cursorPos);
427
+ const column = cursorPos - currentLineStart;
428
+ if (direction === "up") {
429
+ if (currentLineStart === 0) return cursorPos;
430
+ const prevLineEnd = currentLineStart - 1;
431
+ const prevLineStart = getLineStart(text, prevLineEnd);
432
+ const prevLineLength = prevLineEnd - prevLineStart;
433
+ return prevLineStart + Math.min(column, prevLineLength);
434
+ } else {
435
+ if (currentLineEnd === text.length) return cursorPos;
436
+ const nextLineStart = currentLineEnd + 1;
437
+ const nextLineEnd = getLineEnd(text, nextLineStart);
438
+ const nextLineLength = nextLineEnd - nextLineStart;
439
+ return nextLineStart + Math.min(column, nextLineLength);
440
+ }
441
+ }
442
+ function inputReducer(state, action, viewWidth) {
443
+ const adjustView = (newCursorPos, currentViewStart) => {
444
+ if (newCursorPos < currentViewStart) {
445
+ return newCursorPos;
446
+ }
447
+ if (newCursorPos >= currentViewStart + viewWidth) {
448
+ return newCursorPos - viewWidth + 1;
449
+ }
450
+ return currentViewStart;
451
+ };
452
+ switch (action.type) {
453
+ case "INPUT": {
454
+ const cleanInput = action.payload.replace(/(\r\n|\r)/gm, "\n");
455
+ const newText = state.text.slice(0, state.cursorPosition) + cleanInput + state.text.slice(state.cursorPosition);
456
+ const newCursorPosition = state.cursorPosition + cleanInput.length;
457
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
458
+ if (newText === state.text && newCursorPosition === state.cursorPosition) {
459
+ return state;
429
460
  }
430
- if (key.escape) {
431
- onInterrupt();
432
- return;
461
+ return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
462
+ }
463
+ case "NEWLINE": {
464
+ const newText = state.text.slice(0, state.cursorPosition) + "\n" + state.text.slice(state.cursorPosition);
465
+ const newCursorPosition = state.cursorPosition + 1;
466
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
467
+ return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
468
+ }
469
+ case "MOVE_CURSOR": {
470
+ let newCursorPosition = state.cursorPosition;
471
+ if (action.direction === "left" && state.cursorPosition > 0) {
472
+ newCursorPosition--;
473
+ } else if (action.direction === "right" && state.cursorPosition < state.text.length) {
474
+ newCursorPosition++;
475
+ } else if (action.direction === "up") {
476
+ newCursorPosition = moveToAdjacentLine(state.text, state.cursorPosition, "up");
477
+ } else if (action.direction === "down") {
478
+ newCursorPosition = moveToAdjacentLine(state.text, state.cursorPosition, "down");
433
479
  }
434
- if (isReadOnly) {
435
- if (key.return && !key.shift) {
436
- if (state.text.trim().length > 0) {
437
- onSubmit(state.text);
438
- dispatch({ type: "SUBMIT" });
439
- }
440
- return;
441
- }
442
- if (key.shift && key.return) {
443
- dispatch({ type: "NEWLINE" });
444
- return;
445
- }
446
- if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
447
- if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
448
- if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
449
- if (key.downArrow) return dispatch({ type: "MOVE_CURSOR", direction: "down" });
450
- if (key.ctrl || key.meta || key.tab) return;
451
- inputBuffer.current += input;
452
- if (!flushScheduled.current) {
453
- flushScheduled.current = true;
454
- queueMicrotask(flushInputBuffer);
455
- }
456
- return;
480
+ if (newCursorPosition === state.cursorPosition) {
481
+ return state;
457
482
  }
458
- if (key.return && key.shift) {
459
- dispatch({ type: "NEWLINE" });
460
- return;
483
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
484
+ return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
485
+ }
486
+ case "MOVE_LINE_START": {
487
+ const newCursorPosition = getLineStart(state.text, state.cursorPosition);
488
+ if (newCursorPosition === state.cursorPosition) {
489
+ return state;
461
490
  }
462
- if (key.return) {
463
- if (globalThis.__BLUMA_AT_OPEN__) return;
464
- if (globalThis.__BLUMA_SUPPRESS_SUBMIT__) {
465
- globalThis.__BLUMA_SUPPRESS_SUBMIT__ = false;
466
- return;
467
- }
468
- if (state.text.trim().length > 0) {
469
- onSubmit(state.text);
470
- dispatch({ type: "SUBMIT" });
471
- }
472
- return;
491
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
492
+ return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
493
+ }
494
+ case "MOVE_LINE_END": {
495
+ const newCursorPosition = getLineEnd(state.text, state.cursorPosition);
496
+ if (newCursorPosition === state.cursorPosition) {
497
+ return state;
473
498
  }
474
- if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
475
- if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
476
- if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
477
- if (key.downArrow) return dispatch({ type: "MOVE_CURSOR", direction: "down" });
478
- if (key.ctrl && input === "a") {
479
- return dispatch({ type: "MOVE_LINE_START" });
499
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
500
+ return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
501
+ }
502
+ case "BACKSPACE": {
503
+ if (state.cursorPosition > 0) {
504
+ const newText = state.text.slice(0, state.cursorPosition - 1) + state.text.slice(state.cursorPosition);
505
+ const newCursorPosition = state.cursorPosition - 1;
506
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
507
+ return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
480
508
  }
481
- if (key.ctrl && input === "e") {
482
- return dispatch({ type: "MOVE_LINE_END" });
509
+ return state;
510
+ }
511
+ case "DELETE": {
512
+ if (state.cursorPosition < state.text.length) {
513
+ const newText = state.text.slice(0, state.cursorPosition) + state.text.slice(state.cursorPosition + 1);
514
+ const newViewStart = adjustView(state.cursorPosition, state.viewStart);
515
+ return { text: newText, cursorPosition: state.cursorPosition, viewStart: newViewStart };
483
516
  }
484
- if (key.ctrl || key.meta || key.tab) return;
485
- inputBuffer.current += input;
486
- if (!flushScheduled.current) {
487
- flushScheduled.current = true;
488
- queueMicrotask(flushInputBuffer);
517
+ return state;
518
+ }
519
+ case "SUBMIT": {
520
+ return { text: "", cursorPosition: 0, viewStart: 0 };
521
+ }
522
+ case "SET": {
523
+ const t = action.payload.text.replace(/(\r\n|\r)/gm, "\n");
524
+ let newCursorPosition;
525
+ if (typeof action.payload.cursorPosition === "number") {
526
+ newCursorPosition = Math.min(action.payload.cursorPosition, t.length);
527
+ } else if (action.payload.moveCursorToEnd ?? true) {
528
+ newCursorPosition = t.length;
529
+ } else {
530
+ newCursorPosition = Math.min(state.cursorPosition, t.length);
531
+ }
532
+ const newText = t;
533
+ const newViewStart = adjustView(newCursorPosition, 0);
534
+ return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
535
+ }
536
+ case "SET_CURSOR": {
537
+ const newCursorPosition = Math.max(0, Math.min(action.payload, state.text.length));
538
+ if (newCursorPosition === state.cursorPosition) {
539
+ return state;
540
+ }
541
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
542
+ return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
543
+ }
544
+ default:
545
+ return state;
546
+ }
547
+ }
548
+ var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
549
+ const [state, dispatch] = useReducer(
550
+ (s, a) => inputReducer(s, a, viewWidth),
551
+ { text: "", cursorPosition: 0, viewStart: 0 }
552
+ );
553
+ const inputBuffer = useRef("");
554
+ const flushScheduled = useRef(false);
555
+ const flushInputBuffer = useCallback(() => {
556
+ if (inputBuffer.current.length > 0) {
557
+ const buffered = inputBuffer.current;
558
+ inputBuffer.current = "";
559
+ dispatch({ type: "INPUT", payload: buffered });
560
+ }
561
+ flushScheduled.current = false;
562
+ }, []);
563
+ useEffect(() => {
564
+ return () => {
565
+ if (flushScheduled.current) {
566
+ flushInputBuffer();
567
+ }
568
+ };
569
+ }, [flushInputBuffer]);
570
+ useInput(
571
+ (input, key) => {
572
+ const hasBackspaceFlag = key.backspace;
573
+ const hasDeleteFlag = key.delete;
574
+ const hasBackspaceChar = input === "\x7F" || input === "\b" || input === "\b" || input.charCodeAt(0) === 127 || input.charCodeAt(0) === 8;
575
+ if (hasBackspaceFlag || hasBackspaceChar) {
576
+ if (inputBuffer.current.length > 0) {
577
+ flushInputBuffer();
578
+ }
579
+ dispatch({ type: "BACKSPACE" });
580
+ return;
581
+ }
582
+ if (hasDeleteFlag && (key.ctrl || key.meta)) {
583
+ if (inputBuffer.current.length > 0) {
584
+ flushInputBuffer();
585
+ }
586
+ dispatch({ type: "DELETE" });
587
+ return;
588
+ }
589
+ if (hasDeleteFlag && !key.ctrl && !key.meta) {
590
+ if (inputBuffer.current.length > 0) {
591
+ flushInputBuffer();
592
+ }
593
+ dispatch({ type: "BACKSPACE" });
594
+ return;
595
+ }
596
+ if (inputBuffer.current.length > 0 && (key.ctrl || key.meta || key.escape || key.return || key.leftArrow || key.rightArrow || key.upArrow || key.downArrow || key.tab || key.shift)) {
597
+ flushInputBuffer();
598
+ }
599
+ if (key.escape) {
600
+ onInterrupt();
601
+ return;
602
+ }
603
+ if (isReadOnly) {
604
+ if (key.return && !key.shift) {
605
+ if (state.text.trim().length > 0) {
606
+ onSubmit(state.text);
607
+ dispatch({ type: "SUBMIT" });
608
+ }
609
+ return;
610
+ }
611
+ if (key.shift && key.return) {
612
+ dispatch({ type: "NEWLINE" });
613
+ return;
614
+ }
615
+ if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
616
+ if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
617
+ if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
618
+ if (key.downArrow) return dispatch({ type: "MOVE_CURSOR", direction: "down" });
619
+ if (key.ctrl || key.meta || key.tab) return;
620
+ inputBuffer.current += input;
621
+ if (!flushScheduled.current) {
622
+ flushScheduled.current = true;
623
+ queueMicrotask(flushInputBuffer);
624
+ }
625
+ return;
626
+ }
627
+ if (key.return && key.shift) {
628
+ dispatch({ type: "NEWLINE" });
629
+ return;
630
+ }
631
+ if (key.return) {
632
+ if (globalThis.__BLUMA_AT_OPEN__) return;
633
+ if (globalThis.__BLUMA_SUPPRESS_SUBMIT__) {
634
+ globalThis.__BLUMA_SUPPRESS_SUBMIT__ = false;
635
+ return;
636
+ }
637
+ if (state.text.trim().length > 0) {
638
+ onSubmit(state.text);
639
+ dispatch({ type: "SUBMIT" });
640
+ }
641
+ return;
642
+ }
643
+ if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
644
+ if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
645
+ if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
646
+ if (key.downArrow) return dispatch({ type: "MOVE_CURSOR", direction: "down" });
647
+ if (key.ctrl && input === "a") {
648
+ return dispatch({ type: "MOVE_LINE_START" });
649
+ }
650
+ if (key.ctrl && input === "e") {
651
+ return dispatch({ type: "MOVE_LINE_END" });
652
+ }
653
+ if (key.ctrl || key.meta || key.tab) return;
654
+ inputBuffer.current += input;
655
+ if (!flushScheduled.current) {
656
+ flushScheduled.current = true;
657
+ queueMicrotask(flushInputBuffer);
489
658
  }
490
659
  },
491
660
  { isActive: true }
@@ -1313,10 +1482,9 @@ var ConfirmationPromptComponent = ({ toolCalls, preview, onDecision }) => {
1313
1482
  var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
1314
1483
 
1315
1484
  // src/app/agent/agent.ts
1316
- import OpenAI from "openai";
1317
1485
  import * as dotenv from "dotenv";
1318
- import path15 from "path";
1319
- import os8 from "os";
1486
+ import path16 from "path";
1487
+ import os9 from "os";
1320
1488
 
1321
1489
  // src/app/agent/tool_invoker.ts
1322
1490
  import { promises as fs8 } from "fs";
@@ -1665,20 +1833,20 @@ ${finalDiff}`,
1665
1833
 
1666
1834
  // src/app/agent/tools/natives/message.ts
1667
1835
  import { v4 as uuidv4 } from "uuid";
1668
- function messageNotifyuser(args) {
1669
- const { message } = args;
1670
- const notification = {
1671
- type: "message_notify_user",
1672
- id: `notify_${uuidv4()}`,
1836
+ function message(args) {
1837
+ const { content, message_type } = args;
1838
+ const result = {
1839
+ type: "message",
1840
+ message_type,
1841
+ id: `msg_${uuidv4()}`,
1673
1842
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1674
1843
  content: {
1675
- format: "markdown",
1676
- body: message
1844
+ body: content
1677
1845
  },
1678
1846
  success: true,
1679
1847
  delivered: true
1680
1848
  };
1681
- return Promise.resolve(notification);
1849
+ return Promise.resolve(result);
1682
1850
  }
1683
1851
 
1684
1852
  // src/app/agent/tools/natives/ls.ts
@@ -1830,7 +1998,7 @@ import * as path5 from "path";
1830
1998
  var taskStore = [];
1831
1999
  var nextId = 1;
1832
2000
  function getTodoFilePath() {
1833
- return path5.join(process.cwd(), ".bluma", "todo.json");
2001
+ return path5.join(process.cwd(), ".bluma-cli", "todo.json");
1834
2002
  }
1835
2003
  function loadTasksFromFile() {
1836
2004
  try {
@@ -1881,10 +2049,10 @@ function validateDescription(desc) {
1881
2049
  }
1882
2050
  return null;
1883
2051
  }
1884
- function createResult(success, message) {
2052
+ function createResult(success, message2) {
1885
2053
  return {
1886
2054
  success,
1887
- message,
2055
+ message: message2,
1888
2056
  tasks: taskStore,
1889
2057
  stats: calculateStats()
1890
2058
  };
@@ -2064,999 +2232,698 @@ function shouldIgnore(name, ignorePatterns, includeHidden) {
2064
2232
  return true;
2065
2233
  }
2066
2234
  }
2067
- return false;
2068
- }
2069
- function matchesExtensions(filename, extensions) {
2070
- if (!extensions || extensions.length === 0) return true;
2071
- const ext = path6.extname(filename).toLowerCase();
2072
- return extensions.some((e) => {
2073
- const normalizedExt = e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`;
2074
- return ext === normalizedExt;
2075
- });
2076
- }
2077
- async function searchDirectory(dir, pattern, baseDir, options, results) {
2078
- if (options.currentDepth > options.maxDepth || results.length >= MAX_RESULTS2) {
2079
- return;
2080
- }
2081
- let entries;
2082
- try {
2083
- entries = await fsPromises.readdir(dir, { withFileTypes: true });
2084
- } catch (error) {
2085
- return;
2086
- }
2087
- for (const entry of entries) {
2088
- if (results.length >= MAX_RESULTS2) break;
2089
- const name = entry.name;
2090
- if (shouldIgnore(name, options.ignorePatterns, options.includeHidden)) {
2091
- continue;
2092
- }
2093
- const fullPath = path6.join(dir, name);
2094
- const relativePath = path6.relative(baseDir, fullPath);
2095
- if (entry.isDirectory()) {
2096
- if (pattern.test(name)) {
2097
- results.push({
2098
- path: fullPath,
2099
- relative_path: relativePath,
2100
- type: "directory"
2101
- });
2102
- }
2103
- await searchDirectory(fullPath, pattern, baseDir, {
2104
- ...options,
2105
- currentDepth: options.currentDepth + 1
2106
- }, results);
2107
- } else if (entry.isFile()) {
2108
- if (pattern.test(name) && matchesExtensions(name, options.extensions)) {
2109
- try {
2110
- const stats = await fsPromises.stat(fullPath);
2111
- results.push({
2112
- path: fullPath,
2113
- relative_path: relativePath,
2114
- type: "file",
2115
- size: stats.size,
2116
- modified: stats.mtime.toISOString()
2117
- });
2118
- } catch {
2119
- results.push({
2120
- path: fullPath,
2121
- relative_path: relativePath,
2122
- type: "file"
2123
- });
2124
- }
2125
- }
2126
- }
2127
- }
2128
- }
2129
- async function findByName(args) {
2130
- try {
2131
- const {
2132
- pattern,
2133
- directory = process.cwd(),
2134
- extensions,
2135
- max_depth = MAX_DEPTH_DEFAULT,
2136
- include_hidden = false,
2137
- exclude_patterns = []
2138
- } = args;
2139
- if (!pattern || typeof pattern !== "string") {
2140
- return {
2141
- success: false,
2142
- pattern: pattern || "",
2143
- directory,
2144
- results: [],
2145
- total_found: 0,
2146
- truncated: false,
2147
- error: "Pattern is required and must be a string"
2148
- };
2149
- }
2150
- const resolvedDir = path6.resolve(directory);
2151
- try {
2152
- const stats = await fsPromises.stat(resolvedDir);
2153
- if (!stats.isDirectory()) {
2154
- return {
2155
- success: false,
2156
- pattern,
2157
- directory: resolvedDir,
2158
- results: [],
2159
- total_found: 0,
2160
- truncated: false,
2161
- error: `Path is not a directory: ${resolvedDir}`
2162
- };
2163
- }
2164
- } catch (error) {
2165
- return {
2166
- success: false,
2167
- pattern,
2168
- directory: resolvedDir,
2169
- results: [],
2170
- total_found: 0,
2171
- truncated: false,
2172
- error: `Directory not found: ${resolvedDir}`
2173
- };
2174
- }
2175
- const ignorePatterns = [...DEFAULT_IGNORE_PATTERNS, ...exclude_patterns];
2176
- const patternRegex = globToRegex(pattern);
2177
- const results = [];
2178
- await searchDirectory(resolvedDir, patternRegex, resolvedDir, {
2179
- extensions,
2180
- maxDepth: max_depth,
2181
- currentDepth: 0,
2182
- includeHidden: include_hidden,
2183
- ignorePatterns
2184
- }, results);
2185
- results.sort((a, b) => {
2186
- if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
2187
- return a.relative_path.localeCompare(b.relative_path);
2188
- });
2189
- return {
2190
- success: true,
2191
- pattern,
2192
- directory: resolvedDir,
2193
- results,
2194
- total_found: results.length,
2195
- truncated: results.length >= MAX_RESULTS2
2196
- };
2197
- } catch (error) {
2198
- return {
2199
- success: false,
2200
- pattern: args.pattern || "",
2201
- directory: args.directory || process.cwd(),
2202
- results: [],
2203
- total_found: 0,
2204
- truncated: false,
2205
- error: `Unexpected error: ${error.message}`
2206
- };
2207
- }
2208
- }
2209
-
2210
- // src/app/agent/tools/natives/grep_search.ts
2211
- import path7 from "path";
2212
- import { promises as fsPromises2 } from "fs";
2213
- var MAX_RESULTS3 = 100;
2214
- var MAX_FILE_SIZE2 = 1024 * 1024;
2215
- var MAX_LINE_LENGTH = 500;
2216
- var DEFAULT_IGNORE2 = [
2217
- "node_modules",
2218
- ".git",
2219
- ".venv",
2220
- "venv",
2221
- "__pycache__",
2222
- ".cache",
2223
- "dist",
2224
- "build",
2225
- ".next",
2226
- "coverage",
2227
- "*.min.js",
2228
- "*.min.css",
2229
- "*.map",
2230
- "*.lock",
2231
- "package-lock.json",
2232
- "yarn.lock",
2233
- "pnpm-lock.yaml"
2234
- ];
2235
- var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
2236
- ".ts",
2237
- ".tsx",
2238
- ".js",
2239
- ".jsx",
2240
- ".mjs",
2241
- ".cjs",
2242
- ".py",
2243
- ".pyw",
2244
- ".java",
2245
- ".kt",
2246
- ".scala",
2247
- ".go",
2248
- ".rs",
2249
- ".rb",
2250
- ".php",
2251
- ".cs",
2252
- ".cpp",
2253
- ".c",
2254
- ".h",
2255
- ".hpp",
2256
- ".swift",
2257
- ".vue",
2258
- ".svelte",
2259
- ".html",
2260
- ".htm",
2261
- ".xml",
2262
- ".svg",
2263
- ".css",
2264
- ".scss",
2265
- ".sass",
2266
- ".less",
2267
- ".json",
2268
- ".yaml",
2269
- ".yml",
2270
- ".toml",
2271
- ".md",
2272
- ".mdx",
2273
- ".txt",
2274
- ".rst",
2275
- ".sh",
2276
- ".bash",
2277
- ".zsh",
2278
- ".fish",
2279
- ".sql",
2280
- ".graphql",
2281
- ".gql",
2282
- ".env",
2283
- ".env.local",
2284
- ".env.example",
2285
- ".gitignore",
2286
- ".dockerignore",
2287
- ".eslintrc",
2288
- ".prettierrc",
2289
- "Dockerfile",
2290
- "Makefile",
2291
- "Rakefile"
2292
- ]);
2293
- function isTextFile(filepath) {
2294
- const ext = path7.extname(filepath).toLowerCase();
2295
- const basename = path7.basename(filepath);
2296
- if (TEXT_EXTENSIONS.has(ext)) return true;
2297
- if (TEXT_EXTENSIONS.has(basename)) return true;
2298
- if (basename.startsWith(".") && !ext) return true;
2299
- return false;
2300
- }
2301
- function shouldIgnore2(name) {
2302
- for (const pattern of DEFAULT_IGNORE2) {
2303
- if (pattern.includes("*")) {
2304
- const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
2305
- if (regex.test(name)) return true;
2306
- } else if (name === pattern) {
2307
- return true;
2308
- }
2309
- }
2310
- return false;
2311
- }
2312
- function createSearchPattern(query, isRegex, caseInsensitive) {
2313
- try {
2314
- const flags = caseInsensitive ? "gi" : "g";
2315
- if (isRegex) {
2316
- return new RegExp(query, flags);
2317
- } else {
2318
- const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2319
- return new RegExp(escaped, flags);
2320
- }
2321
- } catch (error) {
2322
- throw new Error(`Invalid regex pattern: ${error.message}`);
2323
- }
2324
- }
2325
- function matchesIncludePattern(filename, includePatterns) {
2326
- if (!includePatterns || includePatterns.length === 0) return true;
2327
- for (const pattern of includePatterns) {
2328
- const regex = new RegExp("^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$", "i");
2329
- if (regex.test(filename)) return true;
2330
- }
2331
- return false;
2332
- }
2333
- async function searchFile(filepath, baseDir, pattern, contextLines, matches, maxResults) {
2334
- let fileMatches = 0;
2335
- try {
2336
- const stats = await fsPromises2.stat(filepath);
2337
- if (stats.size > MAX_FILE_SIZE2) return 0;
2338
- const content = await fsPromises2.readFile(filepath, "utf-8");
2339
- const lines = content.split("\n");
2340
- const relativePath = path7.relative(baseDir, filepath);
2341
- for (let i = 0; i < lines.length && matches.length < maxResults; i++) {
2342
- const line = lines[i];
2343
- pattern.lastIndex = 0;
2344
- let match;
2345
- while ((match = pattern.exec(line)) !== null && matches.length < maxResults) {
2346
- const lineContent = line.length > MAX_LINE_LENGTH ? line.substring(0, MAX_LINE_LENGTH) + "..." : line;
2347
- const searchMatch = {
2348
- file: filepath,
2349
- relative_path: relativePath,
2350
- line_number: i + 1,
2351
- line_content: lineContent.trim(),
2352
- match_start: match.index,
2353
- match_end: match.index + match[0].length
2354
- };
2355
- if (contextLines > 0) {
2356
- searchMatch.context_before = lines.slice(Math.max(0, i - contextLines), i).map((l) => l.length > MAX_LINE_LENGTH ? l.substring(0, MAX_LINE_LENGTH) + "..." : l);
2357
- searchMatch.context_after = lines.slice(i + 1, i + 1 + contextLines).map((l) => l.length > MAX_LINE_LENGTH ? l.substring(0, MAX_LINE_LENGTH) + "..." : l);
2358
- }
2359
- matches.push(searchMatch);
2360
- fileMatches++;
2361
- if (!pattern.global) break;
2362
- }
2363
- }
2364
- } catch (error) {
2365
- }
2366
- return fileMatches;
2367
- }
2368
- async function searchDirectory2(dir, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats) {
2369
- if (matches.length >= maxResults) return;
2370
- let entries;
2371
- try {
2372
- entries = await fsPromises2.readdir(dir, { withFileTypes: true });
2373
- } catch {
2374
- return;
2375
- }
2376
- for (const entry of entries) {
2377
- if (matches.length >= maxResults) break;
2378
- const name = entry.name;
2379
- if (shouldIgnore2(name)) continue;
2380
- const fullPath = path7.join(dir, name);
2381
- if (entry.isDirectory()) {
2382
- await searchDirectory2(fullPath, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats);
2383
- } else if (entry.isFile()) {
2384
- if (!isTextFile(name)) continue;
2385
- if (!matchesIncludePattern(name, includePatterns)) continue;
2386
- stats.filesSearched++;
2387
- const found = await searchFile(fullPath, baseDir, pattern, contextLines, matches, maxResults);
2388
- if (found > 0) stats.filesWithMatches++;
2389
- }
2390
- }
2391
- }
2392
- async function grepSearch(args) {
2393
- try {
2394
- const {
2395
- query,
2396
- path: searchPath,
2397
- case_insensitive = true,
2398
- is_regex = false,
2399
- include_patterns,
2400
- max_results = MAX_RESULTS3,
2401
- context_lines = 0
2402
- } = args;
2403
- if (!query || typeof query !== "string") {
2404
- return {
2405
- success: false,
2406
- query: query || "",
2407
- search_path: searchPath || "",
2408
- matches: [],
2409
- files_searched: 0,
2410
- files_with_matches: 0,
2411
- total_matches: 0,
2412
- truncated: false,
2413
- error: "Query is required and must be a string"
2414
- };
2415
- }
2416
- if (!searchPath) {
2417
- return {
2418
- success: false,
2419
- query,
2420
- search_path: "",
2421
- matches: [],
2422
- files_searched: 0,
2423
- files_with_matches: 0,
2424
- total_matches: 0,
2425
- truncated: false,
2426
- error: "Search path is required"
2427
- };
2428
- }
2429
- const resolvedPath = path7.resolve(searchPath);
2430
- let stats;
2431
- try {
2432
- stats = await fsPromises2.stat(resolvedPath);
2433
- } catch {
2434
- return {
2435
- success: false,
2436
- query,
2437
- search_path: resolvedPath,
2438
- matches: [],
2439
- files_searched: 0,
2440
- files_with_matches: 0,
2441
- total_matches: 0,
2442
- truncated: false,
2443
- error: `Path not found: ${resolvedPath}`
2444
- };
2445
- }
2446
- let pattern;
2447
- try {
2448
- pattern = createSearchPattern(query, is_regex, case_insensitive);
2449
- } catch (error) {
2450
- return {
2451
- success: false,
2452
- query,
2453
- search_path: resolvedPath,
2454
- matches: [],
2455
- files_searched: 0,
2456
- files_with_matches: 0,
2457
- total_matches: 0,
2458
- truncated: false,
2459
- error: error.message
2460
- };
2461
- }
2462
- const matches = [];
2463
- const searchStats = { filesSearched: 0, filesWithMatches: 0 };
2464
- if (stats.isDirectory()) {
2465
- await searchDirectory2(
2466
- resolvedPath,
2467
- resolvedPath,
2468
- pattern,
2469
- include_patterns,
2470
- context_lines,
2471
- matches,
2472
- max_results,
2473
- searchStats
2474
- );
2475
- } else if (stats.isFile()) {
2476
- searchStats.filesSearched = 1;
2477
- const found = await searchFile(resolvedPath, path7.dirname(resolvedPath), pattern, context_lines, matches, max_results);
2478
- if (found > 0) searchStats.filesWithMatches = 1;
2479
- }
2480
- return {
2481
- success: true,
2482
- query,
2483
- search_path: resolvedPath,
2484
- matches,
2485
- files_searched: searchStats.filesSearched,
2486
- files_with_matches: searchStats.filesWithMatches,
2487
- total_matches: matches.length,
2488
- truncated: matches.length >= max_results
2489
- };
2490
- } catch (error) {
2491
- return {
2492
- success: false,
2493
- query: args.query || "",
2494
- search_path: args.path || "",
2495
- matches: [],
2496
- files_searched: 0,
2497
- files_with_matches: 0,
2498
- total_matches: 0,
2499
- truncated: false,
2500
- error: `Unexpected error: ${error.message}`
2501
- };
2502
- }
2503
- }
2504
-
2505
- // src/app/agent/tools/natives/view_file_outline.ts
2506
- import path8 from "path";
2507
- import { promises as fsPromises3 } from "fs";
2508
- var LANGUAGE_MAP = {
2509
- ".ts": "typescript",
2510
- ".tsx": "typescript",
2511
- ".js": "javascript",
2512
- ".jsx": "javascript",
2513
- ".mjs": "javascript",
2514
- ".cjs": "javascript",
2515
- ".py": "python",
2516
- ".java": "java",
2517
- ".go": "go",
2518
- ".rs": "rust",
2519
- ".rb": "ruby",
2520
- ".php": "php",
2521
- ".cs": "csharp",
2522
- ".cpp": "cpp",
2523
- ".c": "c",
2524
- ".h": "c",
2525
- ".hpp": "cpp",
2526
- ".swift": "swift",
2527
- ".kt": "kotlin",
2528
- ".scala": "scala"
2529
- };
2530
- var PATTERNS = {
2531
- typescript: [
2532
- // Classes
2533
- /^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w,\s]+)?/,
2534
- // Interfaces
2535
- /^(?:export\s+)?interface\s+(\w+)/,
2536
- // Types
2537
- /^(?:export\s+)?type\s+(\w+)/,
2538
- // Functions (standalone)
2539
- /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
2540
- // Arrow functions assigned to const/let
2541
- /^(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
2542
- // Methods inside classes (indented)
2543
- /^\s+(?:public|private|protected|static|async|readonly|\s)*(\w+)\s*\([^)]*\)\s*(?::\s*[\w<>\[\]|&\s]+)?\s*\{/,
2544
- // Const exports
2545
- /^(?:export\s+)?const\s+(\w+)\s*[:=]/
2546
- ],
2547
- javascript: [
2548
- /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/,
2549
- /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
2550
- /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
2551
- /^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/,
2552
- /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/
2553
- ],
2554
- python: [
2555
- /^class\s+(\w+)(?:\([^)]*\))?:/,
2556
- /^(?:async\s+)?def\s+(\w+)\s*\(/,
2557
- /^(\w+)\s*=\s*(?:lambda|def)/
2558
- ],
2559
- java: [
2560
- /^(?:public|private|protected)?\s*(?:static)?\s*(?:final)?\s*(?:abstract)?\s*class\s+(\w+)/,
2561
- /^(?:public|private|protected)?\s*(?:static)?\s*(?:final)?\s*interface\s+(\w+)/,
2562
- /^\s*(?:public|private|protected)?\s*(?:static)?\s*(?:final)?\s*(?:synchronized)?\s*(?:\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/
2563
- ],
2564
- go: [
2565
- /^type\s+(\w+)\s+struct\s*\{/,
2566
- /^type\s+(\w+)\s+interface\s*\{/,
2567
- /^func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/
2568
- ],
2569
- rust: [
2570
- /^(?:pub\s+)?struct\s+(\w+)/,
2571
- /^(?:pub\s+)?enum\s+(\w+)/,
2572
- /^(?:pub\s+)?trait\s+(\w+)/,
2573
- /^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/,
2574
- /^impl(?:<[^>]+>)?\s+(?:(\w+)|for\s+(\w+))/
2575
- ],
2576
- ruby: [
2577
- /^class\s+(\w+)/,
2578
- /^module\s+(\w+)/,
2579
- /^def\s+(\w+)/
2580
- ],
2581
- php: [
2582
- /^(?:abstract\s+)?class\s+(\w+)/,
2583
- /^interface\s+(\w+)/,
2584
- /^(?:public|private|protected)?\s*(?:static)?\s*function\s+(\w+)/
2585
- ],
2586
- csharp: [
2587
- /^(?:public|private|protected|internal)?\s*(?:static)?\s*(?:partial)?\s*class\s+(\w+)/,
2588
- /^(?:public|private|protected|internal)?\s*interface\s+(\w+)/,
2589
- /^\s*(?:public|private|protected|internal)?\s*(?:static)?\s*(?:async)?\s*(?:\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/
2590
- ],
2591
- swift: [
2592
- /^(?:public|private|internal|open)?\s*class\s+(\w+)/,
2593
- /^(?:public|private|internal)?\s*struct\s+(\w+)/,
2594
- /^(?:public|private|internal)?\s*protocol\s+(\w+)/,
2595
- /^(?:public|private|internal)?\s*(?:static)?\s*func\s+(\w+)/
2596
- ],
2597
- kotlin: [
2598
- /^(?:open|abstract|sealed)?\s*class\s+(\w+)/,
2599
- /^interface\s+(\w+)/,
2600
- /^(?:private|public|internal)?\s*fun\s+(\w+)/
2601
- ],
2602
- scala: [
2603
- /^(?:sealed\s+)?(?:abstract\s+)?class\s+(\w+)/,
2604
- /^object\s+(\w+)/,
2605
- /^trait\s+(\w+)/,
2606
- /^def\s+(\w+)/
2607
- ],
2608
- c: [
2609
- /^(?:static\s+)?(?:inline\s+)?(?:const\s+)?(?:\w+(?:\s*\*)*)\s+(\w+)\s*\([^)]*\)\s*\{/,
2610
- /^typedef\s+struct\s*\w*\s*\{[^}]*\}\s*(\w+)/,
2611
- /^struct\s+(\w+)\s*\{/
2612
- ],
2613
- cpp: [
2614
- /^class\s+(\w+)/,
2615
- /^(?:virtual\s+)?(?:\w+(?:\s*[*&])*)\s+(\w+)\s*\([^)]*\)\s*(?:const)?\s*(?:override)?\s*\{/,
2616
- /^namespace\s+(\w+)\s*\{/
2617
- ]
2618
- };
2619
- function detectLanguage(filepath) {
2620
- const ext = path8.extname(filepath).toLowerCase();
2621
- return LANGUAGE_MAP[ext] || "unknown";
2622
- }
2623
- function determineItemType(line, language) {
2624
- const normalizedLine = line.trim().toLowerCase();
2625
- if (normalizedLine.includes("class ")) return "class";
2626
- if (normalizedLine.includes("interface ")) return "interface";
2627
- if (normalizedLine.includes("type ")) return "type";
2628
- if (normalizedLine.startsWith("import ")) return "import";
2629
- if (normalizedLine.includes("export ") && !normalizedLine.includes("function") && !normalizedLine.includes("class")) return "export";
2630
- if (line.startsWith(" ") || line.startsWith(" ")) {
2631
- if (normalizedLine.includes("function") || normalizedLine.includes("def ") || normalizedLine.includes("fn ") || normalizedLine.includes("func ")) {
2632
- return "method";
2633
- }
2634
- if (/^\s+\w+\s*\(/.test(line)) return "method";
2235
+ return false;
2236
+ }
2237
+ function matchesExtensions(filename, extensions) {
2238
+ if (!extensions || extensions.length === 0) return true;
2239
+ const ext = path6.extname(filename).toLowerCase();
2240
+ return extensions.some((e) => {
2241
+ const normalizedExt = e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`;
2242
+ return ext === normalizedExt;
2243
+ });
2244
+ }
2245
+ async function searchDirectory(dir, pattern, baseDir, options, results) {
2246
+ if (options.currentDepth > options.maxDepth || results.length >= MAX_RESULTS2) {
2247
+ return;
2635
2248
  }
2636
- if (normalizedLine.includes("function ") || normalizedLine.includes("def ") || normalizedLine.includes("fn ") || normalizedLine.includes("func ")) {
2637
- return "function";
2249
+ let entries;
2250
+ try {
2251
+ entries = await fsPromises.readdir(dir, { withFileTypes: true });
2252
+ } catch (error) {
2253
+ return;
2638
2254
  }
2639
- if (normalizedLine.includes("const ") || normalizedLine.includes("let ") || normalizedLine.includes("var ")) {
2640
- return normalizedLine.includes("=>") ? "function" : "const";
2255
+ for (const entry of entries) {
2256
+ if (results.length >= MAX_RESULTS2) break;
2257
+ const name = entry.name;
2258
+ if (shouldIgnore(name, options.ignorePatterns, options.includeHidden)) {
2259
+ continue;
2260
+ }
2261
+ const fullPath = path6.join(dir, name);
2262
+ const relativePath = path6.relative(baseDir, fullPath);
2263
+ if (entry.isDirectory()) {
2264
+ if (pattern.test(name)) {
2265
+ results.push({
2266
+ path: fullPath,
2267
+ relative_path: relativePath,
2268
+ type: "directory"
2269
+ });
2270
+ }
2271
+ await searchDirectory(fullPath, pattern, baseDir, {
2272
+ ...options,
2273
+ currentDepth: options.currentDepth + 1
2274
+ }, results);
2275
+ } else if (entry.isFile()) {
2276
+ if (pattern.test(name) && matchesExtensions(name, options.extensions)) {
2277
+ try {
2278
+ const stats = await fsPromises.stat(fullPath);
2279
+ results.push({
2280
+ path: fullPath,
2281
+ relative_path: relativePath,
2282
+ type: "file",
2283
+ size: stats.size,
2284
+ modified: stats.mtime.toISOString()
2285
+ });
2286
+ } catch {
2287
+ results.push({
2288
+ path: fullPath,
2289
+ relative_path: relativePath,
2290
+ type: "file"
2291
+ });
2292
+ }
2293
+ }
2294
+ }
2641
2295
  }
2642
- return "function";
2643
2296
  }
2644
- async function extractOutline(filepath, content, language) {
2645
- const items = [];
2646
- const lines = content.split("\n");
2647
- const patterns = PATTERNS[language] || PATTERNS.javascript;
2648
- let currentClass = null;
2649
- let braceCount = 0;
2650
- for (let i = 0; i < lines.length; i++) {
2651
- const line = lines[i];
2652
- const lineNumber = i + 1;
2653
- const openBraces = (line.match(/\{/g) || []).length;
2654
- const closeBraces = (line.match(/\}/g) || []).length;
2655
- braceCount += openBraces - closeBraces;
2656
- if (braceCount <= 0 && currentClass) {
2657
- currentClass = null;
2297
+ async function findByName(args) {
2298
+ try {
2299
+ const {
2300
+ pattern,
2301
+ directory = process.cwd(),
2302
+ extensions,
2303
+ max_depth = MAX_DEPTH_DEFAULT,
2304
+ include_hidden = false,
2305
+ exclude_patterns = []
2306
+ } = args;
2307
+ if (!pattern || typeof pattern !== "string") {
2308
+ return {
2309
+ success: false,
2310
+ pattern: pattern || "",
2311
+ directory,
2312
+ results: [],
2313
+ total_found: 0,
2314
+ truncated: false,
2315
+ error: "Pattern is required and must be a string"
2316
+ };
2658
2317
  }
2659
- for (const pattern of patterns) {
2660
- const match = line.match(pattern);
2661
- if (match) {
2662
- const name = match[1] || match[2];
2663
- if (!name) continue;
2664
- const itemType = determineItemType(line, language);
2665
- if (!itemType) continue;
2666
- const exported = line.includes("export ");
2667
- const isInsideClass = currentClass && (line.startsWith(" ") || line.startsWith(" "));
2668
- const item = {
2669
- type: itemType,
2670
- name,
2671
- line_start: lineNumber,
2672
- signature: line.trim().substring(0, 120),
2673
- exported
2318
+ const resolvedDir = path6.resolve(directory);
2319
+ try {
2320
+ const stats = await fsPromises.stat(resolvedDir);
2321
+ if (!stats.isDirectory()) {
2322
+ return {
2323
+ success: false,
2324
+ pattern,
2325
+ directory: resolvedDir,
2326
+ results: [],
2327
+ total_found: 0,
2328
+ truncated: false,
2329
+ error: `Path is not a directory: ${resolvedDir}`
2674
2330
  };
2675
- if (isInsideClass && itemType === "method" && currentClass) {
2676
- item.parent = currentClass;
2677
- }
2678
- if (itemType === "class") {
2679
- currentClass = name;
2680
- }
2681
- items.push(item);
2682
- break;
2683
2331
  }
2332
+ } catch (error) {
2333
+ return {
2334
+ success: false,
2335
+ pattern,
2336
+ directory: resolvedDir,
2337
+ results: [],
2338
+ total_found: 0,
2339
+ truncated: false,
2340
+ error: `Directory not found: ${resolvedDir}`
2341
+ };
2684
2342
  }
2343
+ const ignorePatterns = [...DEFAULT_IGNORE_PATTERNS, ...exclude_patterns];
2344
+ const patternRegex = globToRegex(pattern);
2345
+ const results = [];
2346
+ await searchDirectory(resolvedDir, patternRegex, resolvedDir, {
2347
+ extensions,
2348
+ maxDepth: max_depth,
2349
+ currentDepth: 0,
2350
+ includeHidden: include_hidden,
2351
+ ignorePatterns
2352
+ }, results);
2353
+ results.sort((a, b) => {
2354
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
2355
+ return a.relative_path.localeCompare(b.relative_path);
2356
+ });
2357
+ return {
2358
+ success: true,
2359
+ pattern,
2360
+ directory: resolvedDir,
2361
+ results,
2362
+ total_found: results.length,
2363
+ truncated: results.length >= MAX_RESULTS2
2364
+ };
2365
+ } catch (error) {
2366
+ return {
2367
+ success: false,
2368
+ pattern: args.pattern || "",
2369
+ directory: args.directory || process.cwd(),
2370
+ results: [],
2371
+ total_found: 0,
2372
+ truncated: false,
2373
+ error: `Unexpected error: ${error.message}`
2374
+ };
2685
2375
  }
2686
- return items;
2687
2376
  }
2688
- function generateSummary(items, totalLines) {
2689
- const counts = {};
2690
- for (const item of items) {
2691
- counts[item.type] = (counts[item.type] || 0) + 1;
2377
+
2378
+ // src/app/agent/tools/natives/grep_search.ts
2379
+ import path7 from "path";
2380
+ import { promises as fsPromises2 } from "fs";
2381
+ var MAX_RESULTS3 = 100;
2382
+ var MAX_FILE_SIZE2 = 1024 * 1024;
2383
+ var MAX_LINE_LENGTH = 500;
2384
+ var DEFAULT_IGNORE2 = [
2385
+ "node_modules",
2386
+ ".git",
2387
+ ".venv",
2388
+ "venv",
2389
+ "__pycache__",
2390
+ ".cache",
2391
+ "dist",
2392
+ "build",
2393
+ ".next",
2394
+ "coverage",
2395
+ "*.min.js",
2396
+ "*.min.css",
2397
+ "*.map",
2398
+ "*.lock",
2399
+ "package-lock.json",
2400
+ "yarn.lock",
2401
+ "pnpm-lock.yaml"
2402
+ ];
2403
+ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
2404
+ ".ts",
2405
+ ".tsx",
2406
+ ".js",
2407
+ ".jsx",
2408
+ ".mjs",
2409
+ ".cjs",
2410
+ ".py",
2411
+ ".pyw",
2412
+ ".java",
2413
+ ".kt",
2414
+ ".scala",
2415
+ ".go",
2416
+ ".rs",
2417
+ ".rb",
2418
+ ".php",
2419
+ ".cs",
2420
+ ".cpp",
2421
+ ".c",
2422
+ ".h",
2423
+ ".hpp",
2424
+ ".swift",
2425
+ ".vue",
2426
+ ".svelte",
2427
+ ".html",
2428
+ ".htm",
2429
+ ".xml",
2430
+ ".svg",
2431
+ ".css",
2432
+ ".scss",
2433
+ ".sass",
2434
+ ".less",
2435
+ ".json",
2436
+ ".yaml",
2437
+ ".yml",
2438
+ ".toml",
2439
+ ".md",
2440
+ ".mdx",
2441
+ ".txt",
2442
+ ".rst",
2443
+ ".sh",
2444
+ ".bash",
2445
+ ".zsh",
2446
+ ".fish",
2447
+ ".sql",
2448
+ ".graphql",
2449
+ ".gql",
2450
+ ".env",
2451
+ ".env.local",
2452
+ ".env.example",
2453
+ ".gitignore",
2454
+ ".dockerignore",
2455
+ ".eslintrc",
2456
+ ".prettierrc",
2457
+ "Dockerfile",
2458
+ "Makefile",
2459
+ "Rakefile"
2460
+ ]);
2461
+ function isTextFile(filepath) {
2462
+ const ext = path7.extname(filepath).toLowerCase();
2463
+ const basename = path7.basename(filepath);
2464
+ if (TEXT_EXTENSIONS.has(ext)) return true;
2465
+ if (TEXT_EXTENSIONS.has(basename)) return true;
2466
+ if (basename.startsWith(".") && !ext) return true;
2467
+ return false;
2468
+ }
2469
+ function shouldIgnore2(name) {
2470
+ for (const pattern of DEFAULT_IGNORE2) {
2471
+ if (pattern.includes("*")) {
2472
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
2473
+ if (regex.test(name)) return true;
2474
+ } else if (name === pattern) {
2475
+ return true;
2476
+ }
2692
2477
  }
2693
- const parts = [`${totalLines} lines`];
2694
- if (counts.class) parts.push(`${counts.class} class${counts.class > 1 ? "es" : ""}`);
2695
- if (counts.interface) parts.push(`${counts.interface} interface${counts.interface > 1 ? "s" : ""}`);
2696
- if (counts.function) parts.push(`${counts.function} function${counts.function > 1 ? "s" : ""}`);
2697
- if (counts.method) parts.push(`${counts.method} method${counts.method > 1 ? "s" : ""}`);
2698
- if (counts.type) parts.push(`${counts.type} type${counts.type > 1 ? "s" : ""}`);
2699
- if (counts.const) parts.push(`${counts.const} const${counts.const > 1 ? "s" : ""}`);
2700
- return parts.join(", ");
2478
+ return false;
2701
2479
  }
2702
- async function viewFileOutline(args) {
2480
+ function createSearchPattern(query, isRegex, caseInsensitive) {
2703
2481
  try {
2704
- const { file_path } = args;
2705
- if (!file_path || typeof file_path !== "string") {
2706
- return {
2707
- success: false,
2708
- file_path: file_path || "",
2709
- language: "unknown",
2710
- total_lines: 0,
2711
- items: [],
2712
- summary: "",
2713
- error: "file_path is required and must be a string"
2714
- };
2715
- }
2716
- const resolvedPath = path8.resolve(file_path);
2717
- let content;
2718
- try {
2719
- content = await fsPromises3.readFile(resolvedPath, "utf-8");
2720
- } catch (error) {
2721
- return {
2722
- success: false,
2723
- file_path: resolvedPath,
2724
- language: "unknown",
2725
- total_lines: 0,
2726
- items: [],
2727
- summary: "",
2728
- error: error.code === "ENOENT" ? `File not found: ${resolvedPath}` : `Error reading file: ${error.message}`
2729
- };
2482
+ const flags = caseInsensitive ? "gi" : "g";
2483
+ if (isRegex) {
2484
+ return new RegExp(query, flags);
2485
+ } else {
2486
+ const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2487
+ return new RegExp(escaped, flags);
2730
2488
  }
2731
- const language = detectLanguage(resolvedPath);
2732
- const lines = content.split("\n");
2733
- const totalLines = lines.length;
2734
- const items = await extractOutline(resolvedPath, content, language);
2735
- return {
2736
- success: true,
2737
- file_path: resolvedPath,
2738
- language,
2739
- total_lines: totalLines,
2740
- items,
2741
- summary: generateSummary(items, totalLines)
2742
- };
2743
2489
  } catch (error) {
2744
- return {
2745
- success: false,
2746
- file_path: args.file_path || "",
2747
- language: "unknown",
2748
- total_lines: 0,
2749
- items: [],
2750
- summary: "",
2751
- error: `Unexpected error: ${error.message}`
2752
- };
2490
+ throw new Error(`Invalid regex pattern: ${error.message}`);
2753
2491
  }
2754
2492
  }
2755
-
2756
- // src/app/agent/tools/natives/async_command.ts
2757
- import os2 from "os";
2758
- import { spawn } from "child_process";
2759
- import { v4 as uuidv42 } from "uuid";
2760
- var runningCommands = /* @__PURE__ */ new Map();
2761
- var MAX_OUTPUT_SIZE = 3e4;
2762
- var MAX_STORED_COMMANDS = 50;
2763
- var OUTPUT_TRUNCATION_MSG = "\n[OUTPUT TRUNCATED - 30KB/200 lines max]";
2764
- var DANGEROUS_PATTERNS = [
2765
- // Elevação de privilégios
2766
- /^sudo\s+/i,
2767
- /^doas\s+/i,
2768
- /^su\s+/i,
2769
- /^pkexec\s+/i,
2770
- /\|\s*sudo\s+/i,
2771
- /;\s*sudo\s+/i,
2772
- /&&\s*sudo\s+/i,
2773
- // Comandos destrutivos
2774
- /\brm\s+(-[rf]+\s+)*[\/~]/i,
2775
- /\brm\s+-[rf]*\s+\*/i,
2776
- /\bchmod\s+(777|666)\s+\//i,
2777
- /\bdd\s+.*of=\/dev\/(sd|hd|nvme)/i,
2778
- /\bmkfs\./i,
2779
- // Fork bombs
2780
- /:\(\)\s*\{\s*:\|:&\s*\}\s*;:/,
2781
- // Remote code exec
2782
- /\bcurl\s+.*\|\s*(ba)?sh/i,
2783
- /\bwget\s+.*\|\s*(ba)?sh/i
2784
- ];
2785
- var INTERACTIVE_PATTERNS = [
2786
- /^(vim|vi|nano|emacs|pico)\s*/i,
2787
- /^(less|more|most)\s*/i,
2788
- /^(top|htop|btop|atop|nmon)\s*/i,
2789
- /^(ssh|telnet|ftp|sftp)\s+/i,
2790
- /^(mysql|psql|redis-cli|mongo|mongosh)\s*$/i,
2791
- /^(python|python3|node|ruby|irb|lua)\s*$/i,
2792
- /^(gdb|lldb)\s*/i
2793
- ];
2794
- function cleanupOldCommands() {
2795
- if (runningCommands.size <= MAX_STORED_COMMANDS) return;
2796
- const commands = Array.from(runningCommands.entries()).filter(([_, cmd]) => cmd.status !== "running").sort((a, b) => (a[1].endTime || 0) - (b[1].endTime || 0));
2797
- const toRemove = commands.slice(0, commands.length - MAX_STORED_COMMANDS + 10);
2798
- for (const [id] of toRemove) {
2799
- runningCommands.delete(id);
2493
+ function matchesIncludePattern(filename, includePatterns) {
2494
+ if (!includePatterns || includePatterns.length === 0) return true;
2495
+ for (const pattern of includePatterns) {
2496
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$", "i");
2497
+ if (regex.test(filename)) return true;
2800
2498
  }
2499
+ return false;
2801
2500
  }
2802
- function isDangerousCommand(command) {
2803
- const trimmed = command.trim();
2804
- for (const pattern of DANGEROUS_PATTERNS) {
2805
- if (pattern.test(trimmed)) {
2806
- return "Command requires elevated privileges (sudo/doas) which cannot be handled in async mode";
2807
- }
2808
- }
2809
- for (const pattern of INTERACTIVE_PATTERNS) {
2810
- if (pattern.test(trimmed)) {
2811
- return "Interactive commands are not supported in async mode";
2501
+ async function searchFile(filepath, baseDir, pattern, contextLines, matches, maxResults) {
2502
+ let fileMatches = 0;
2503
+ try {
2504
+ const stats = await fsPromises2.stat(filepath);
2505
+ if (stats.size > MAX_FILE_SIZE2) return 0;
2506
+ const content = await fsPromises2.readFile(filepath, "utf-8");
2507
+ const lines = content.split("\n");
2508
+ const relativePath = path7.relative(baseDir, filepath);
2509
+ for (let i = 0; i < lines.length && matches.length < maxResults; i++) {
2510
+ const line = lines[i];
2511
+ pattern.lastIndex = 0;
2512
+ let match;
2513
+ while ((match = pattern.exec(line)) !== null && matches.length < maxResults) {
2514
+ const lineContent = line.length > MAX_LINE_LENGTH ? line.substring(0, MAX_LINE_LENGTH) + "..." : line;
2515
+ const searchMatch = {
2516
+ file: filepath,
2517
+ relative_path: relativePath,
2518
+ line_number: i + 1,
2519
+ line_content: lineContent.trim(),
2520
+ match_start: match.index,
2521
+ match_end: match.index + match[0].length
2522
+ };
2523
+ if (contextLines > 0) {
2524
+ searchMatch.context_before = lines.slice(Math.max(0, i - contextLines), i).map((l) => l.length > MAX_LINE_LENGTH ? l.substring(0, MAX_LINE_LENGTH) + "..." : l);
2525
+ searchMatch.context_after = lines.slice(i + 1, i + 1 + contextLines).map((l) => l.length > MAX_LINE_LENGTH ? l.substring(0, MAX_LINE_LENGTH) + "..." : l);
2526
+ }
2527
+ matches.push(searchMatch);
2528
+ fileMatches++;
2529
+ if (!pattern.global) break;
2530
+ }
2812
2531
  }
2532
+ } catch (error) {
2813
2533
  }
2814
- return null;
2534
+ return fileMatches;
2815
2535
  }
2816
- async function runCommandAsync(args) {
2536
+ async function searchDirectory2(dir, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats) {
2537
+ if (matches.length >= maxResults) return;
2538
+ let entries;
2817
2539
  try {
2818
- const {
2819
- command,
2820
- cwd = process.cwd(),
2821
- timeout = 0
2822
- } = args;
2823
- if (!command || typeof command !== "string") {
2824
- return {
2825
- success: false,
2826
- error: "command is required and must be a string"
2827
- };
2828
- }
2829
- const dangerReason = isDangerousCommand(command);
2830
- if (dangerReason) {
2831
- return {
2832
- success: false,
2833
- error: dangerReason
2834
- };
2835
- }
2836
- const commandId = uuidv42().substring(0, 8);
2837
- const platform = os2.platform();
2838
- let shellCmd;
2839
- let shellArgs;
2840
- if (platform === "win32") {
2841
- shellCmd = process.env.COMSPEC || "cmd.exe";
2842
- shellArgs = ["/c", command];
2843
- } else {
2844
- shellCmd = process.env.SHELL || "/bin/bash";
2845
- shellArgs = ["-c", command];
2846
- }
2847
- const entry = {
2848
- id: commandId,
2849
- command,
2850
- status: "running",
2851
- stdout: "",
2852
- stderr: "",
2853
- exitCode: null,
2854
- startTime: Date.now(),
2855
- process: null
2856
- };
2857
- const child = spawn(shellCmd, shellArgs, {
2858
- cwd,
2859
- env: process.env,
2860
- windowsHide: true
2861
- });
2862
- entry.process = child;
2863
- let stdoutTruncated = false;
2864
- child.stdout?.on("data", (data) => {
2865
- if (!stdoutTruncated) {
2866
- entry.stdout += data.toString();
2867
- if (entry.stdout.length > MAX_OUTPUT_SIZE) {
2868
- entry.stdout = entry.stdout.substring(0, MAX_OUTPUT_SIZE) + OUTPUT_TRUNCATION_MSG;
2869
- stdoutTruncated = true;
2870
- }
2871
- }
2872
- });
2873
- let stderrTruncated = false;
2874
- child.stderr?.on("data", (data) => {
2875
- if (!stderrTruncated) {
2876
- entry.stderr += data.toString();
2877
- if (entry.stderr.length > MAX_OUTPUT_SIZE) {
2878
- entry.stderr = entry.stderr.substring(0, MAX_OUTPUT_SIZE) + OUTPUT_TRUNCATION_MSG;
2879
- stderrTruncated = true;
2880
- }
2881
- }
2882
- });
2883
- child.on("close", (code) => {
2884
- entry.exitCode = code;
2885
- entry.status = code === 0 ? "completed" : "error";
2886
- entry.endTime = Date.now();
2887
- entry.process = null;
2888
- });
2889
- child.on("error", (error) => {
2890
- entry.stderr += `
2891
- Process error: ${error.message}`;
2892
- entry.status = "error";
2893
- entry.endTime = Date.now();
2894
- entry.process = null;
2895
- });
2896
- if (timeout > 0) {
2897
- setTimeout(() => {
2898
- if (entry.status === "running") {
2899
- child.kill("SIGTERM");
2900
- setTimeout(() => {
2901
- if (entry.status === "running") {
2902
- child.kill("SIGKILL");
2903
- }
2904
- }, 2e3);
2905
- entry.status = "timeout";
2906
- entry.stderr += `
2907
- Command timed out after ${timeout} seconds`;
2908
- }
2909
- }, timeout * 1e3);
2540
+ entries = await fsPromises2.readdir(dir, { withFileTypes: true });
2541
+ } catch {
2542
+ return;
2543
+ }
2544
+ for (const entry of entries) {
2545
+ if (matches.length >= maxResults) break;
2546
+ const name = entry.name;
2547
+ if (shouldIgnore2(name)) continue;
2548
+ const fullPath = path7.join(dir, name);
2549
+ if (entry.isDirectory()) {
2550
+ await searchDirectory2(fullPath, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats);
2551
+ } else if (entry.isFile()) {
2552
+ if (!isTextFile(name)) continue;
2553
+ if (!matchesIncludePattern(name, includePatterns)) continue;
2554
+ stats.filesSearched++;
2555
+ const found = await searchFile(fullPath, baseDir, pattern, contextLines, matches, maxResults);
2556
+ if (found > 0) stats.filesWithMatches++;
2910
2557
  }
2911
- runningCommands.set(commandId, entry);
2912
- cleanupOldCommands();
2913
- return {
2914
- success: true,
2915
- command_id: commandId,
2916
- command,
2917
- message: `Command started in background. Use command_status with id "${commandId}" to check progress.`
2918
- };
2919
- } catch (error) {
2920
- return {
2921
- success: false,
2922
- error: `Unexpected error: ${error.message}`
2923
- };
2924
2558
  }
2925
2559
  }
2926
- async function commandStatus(args) {
2560
+ async function grepSearch(args) {
2927
2561
  try {
2928
2562
  const {
2929
- command_id,
2930
- wait_seconds = 0,
2931
- output_limit = 1e4
2563
+ query,
2564
+ path: searchPath,
2565
+ case_insensitive = true,
2566
+ is_regex = false,
2567
+ include_patterns,
2568
+ max_results = MAX_RESULTS3,
2569
+ context_lines = 0
2932
2570
  } = args;
2933
- if (!command_id) {
2571
+ if (!query || typeof query !== "string") {
2934
2572
  return {
2935
2573
  success: false,
2936
- command_id: "",
2937
- status: "error",
2938
- error: "command_id is required"
2574
+ query: query || "",
2575
+ search_path: searchPath || "",
2576
+ matches: [],
2577
+ files_searched: 0,
2578
+ files_with_matches: 0,
2579
+ total_matches: 0,
2580
+ truncated: false,
2581
+ error: "Query is required and must be a string"
2939
2582
  };
2940
2583
  }
2941
- const entry = runningCommands.get(command_id);
2942
- if (!entry) {
2584
+ if (!searchPath) {
2943
2585
  return {
2944
2586
  success: false,
2945
- command_id,
2946
- status: "not_found",
2947
- error: `Command with id "${command_id}" not found. It may have expired or never existed.`
2587
+ query,
2588
+ search_path: "",
2589
+ matches: [],
2590
+ files_searched: 0,
2591
+ files_with_matches: 0,
2592
+ total_matches: 0,
2593
+ truncated: false,
2594
+ error: "Search path is required"
2948
2595
  };
2949
2596
  }
2950
- if (wait_seconds > 0 && entry.status === "running") {
2951
- await new Promise((resolve) => {
2952
- const checkInterval = setInterval(() => {
2953
- if (entry.status !== "running") {
2954
- clearInterval(checkInterval);
2955
- resolve();
2956
- }
2957
- }, 100);
2958
- setTimeout(() => {
2959
- clearInterval(checkInterval);
2960
- resolve();
2961
- }, wait_seconds * 1e3);
2962
- });
2597
+ const resolvedPath = path7.resolve(searchPath);
2598
+ let stats;
2599
+ try {
2600
+ stats = await fsPromises2.stat(resolvedPath);
2601
+ } catch {
2602
+ return {
2603
+ success: false,
2604
+ query,
2605
+ search_path: resolvedPath,
2606
+ matches: [],
2607
+ files_searched: 0,
2608
+ files_with_matches: 0,
2609
+ total_matches: 0,
2610
+ truncated: false,
2611
+ error: `Path not found: ${resolvedPath}`
2612
+ };
2963
2613
  }
2964
- let stdout = entry.stdout;
2965
- let stderr = entry.stderr;
2966
- let truncated = false;
2967
- if (stdout.length > output_limit) {
2968
- stdout = "..." + stdout.substring(stdout.length - output_limit);
2969
- truncated = true;
2614
+ let pattern;
2615
+ try {
2616
+ pattern = createSearchPattern(query, is_regex, case_insensitive);
2617
+ } catch (error) {
2618
+ return {
2619
+ success: false,
2620
+ query,
2621
+ search_path: resolvedPath,
2622
+ matches: [],
2623
+ files_searched: 0,
2624
+ files_with_matches: 0,
2625
+ total_matches: 0,
2626
+ truncated: false,
2627
+ error: error.message
2628
+ };
2970
2629
  }
2971
- if (stderr.length > output_limit) {
2972
- stderr = "..." + stderr.substring(stderr.length - output_limit);
2973
- truncated = true;
2630
+ const matches = [];
2631
+ const searchStats = { filesSearched: 0, filesWithMatches: 0 };
2632
+ if (stats.isDirectory()) {
2633
+ await searchDirectory2(
2634
+ resolvedPath,
2635
+ resolvedPath,
2636
+ pattern,
2637
+ include_patterns,
2638
+ context_lines,
2639
+ matches,
2640
+ max_results,
2641
+ searchStats
2642
+ );
2643
+ } else if (stats.isFile()) {
2644
+ searchStats.filesSearched = 1;
2645
+ const found = await searchFile(resolvedPath, path7.dirname(resolvedPath), pattern, context_lines, matches, max_results);
2646
+ if (found > 0) searchStats.filesWithMatches = 1;
2647
+ }
2648
+ return {
2649
+ success: true,
2650
+ query,
2651
+ search_path: resolvedPath,
2652
+ matches,
2653
+ files_searched: searchStats.filesSearched,
2654
+ files_with_matches: searchStats.filesWithMatches,
2655
+ total_matches: matches.length,
2656
+ truncated: matches.length >= max_results
2657
+ };
2658
+ } catch (error) {
2659
+ return {
2660
+ success: false,
2661
+ query: args.query || "",
2662
+ search_path: args.path || "",
2663
+ matches: [],
2664
+ files_searched: 0,
2665
+ files_with_matches: 0,
2666
+ total_matches: 0,
2667
+ truncated: false,
2668
+ error: `Unexpected error: ${error.message}`
2669
+ };
2670
+ }
2671
+ }
2672
+
2673
+ // src/app/agent/tools/natives/view_file_outline.ts
2674
+ import path8 from "path";
2675
+ import { promises as fsPromises3 } from "fs";
2676
+ var LANGUAGE_MAP = {
2677
+ ".ts": "typescript",
2678
+ ".tsx": "typescript",
2679
+ ".js": "javascript",
2680
+ ".jsx": "javascript",
2681
+ ".mjs": "javascript",
2682
+ ".cjs": "javascript",
2683
+ ".py": "python",
2684
+ ".java": "java",
2685
+ ".go": "go",
2686
+ ".rs": "rust",
2687
+ ".rb": "ruby",
2688
+ ".php": "php",
2689
+ ".cs": "csharp",
2690
+ ".cpp": "cpp",
2691
+ ".c": "c",
2692
+ ".h": "c",
2693
+ ".hpp": "cpp",
2694
+ ".swift": "swift",
2695
+ ".kt": "kotlin",
2696
+ ".scala": "scala"
2697
+ };
2698
+ var PATTERNS = {
2699
+ typescript: [
2700
+ // Classes
2701
+ /^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w,\s]+)?/,
2702
+ // Interfaces
2703
+ /^(?:export\s+)?interface\s+(\w+)/,
2704
+ // Types
2705
+ /^(?:export\s+)?type\s+(\w+)/,
2706
+ // Functions (standalone)
2707
+ /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
2708
+ // Arrow functions assigned to const/let
2709
+ /^(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
2710
+ // Methods inside classes (indented)
2711
+ /^\s+(?:public|private|protected|static|async|readonly|\s)*(\w+)\s*\([^)]*\)\s*(?::\s*[\w<>\[\]|&\s]+)?\s*\{/,
2712
+ // Const exports
2713
+ /^(?:export\s+)?const\s+(\w+)\s*[:=]/
2714
+ ],
2715
+ javascript: [
2716
+ /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/,
2717
+ /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
2718
+ /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
2719
+ /^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/,
2720
+ /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/
2721
+ ],
2722
+ python: [
2723
+ /^class\s+(\w+)(?:\([^)]*\))?:/,
2724
+ /^(?:async\s+)?def\s+(\w+)\s*\(/,
2725
+ /^(\w+)\s*=\s*(?:lambda|def)/
2726
+ ],
2727
+ java: [
2728
+ /^(?:public|private|protected)?\s*(?:static)?\s*(?:final)?\s*(?:abstract)?\s*class\s+(\w+)/,
2729
+ /^(?:public|private|protected)?\s*(?:static)?\s*(?:final)?\s*interface\s+(\w+)/,
2730
+ /^\s*(?:public|private|protected)?\s*(?:static)?\s*(?:final)?\s*(?:synchronized)?\s*(?:\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/
2731
+ ],
2732
+ go: [
2733
+ /^type\s+(\w+)\s+struct\s*\{/,
2734
+ /^type\s+(\w+)\s+interface\s*\{/,
2735
+ /^func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/
2736
+ ],
2737
+ rust: [
2738
+ /^(?:pub\s+)?struct\s+(\w+)/,
2739
+ /^(?:pub\s+)?enum\s+(\w+)/,
2740
+ /^(?:pub\s+)?trait\s+(\w+)/,
2741
+ /^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/,
2742
+ /^impl(?:<[^>]+>)?\s+(?:(\w+)|for\s+(\w+))/
2743
+ ],
2744
+ ruby: [
2745
+ /^class\s+(\w+)/,
2746
+ /^module\s+(\w+)/,
2747
+ /^def\s+(\w+)/
2748
+ ],
2749
+ php: [
2750
+ /^(?:abstract\s+)?class\s+(\w+)/,
2751
+ /^interface\s+(\w+)/,
2752
+ /^(?:public|private|protected)?\s*(?:static)?\s*function\s+(\w+)/
2753
+ ],
2754
+ csharp: [
2755
+ /^(?:public|private|protected|internal)?\s*(?:static)?\s*(?:partial)?\s*class\s+(\w+)/,
2756
+ /^(?:public|private|protected|internal)?\s*interface\s+(\w+)/,
2757
+ /^\s*(?:public|private|protected|internal)?\s*(?:static)?\s*(?:async)?\s*(?:\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/
2758
+ ],
2759
+ swift: [
2760
+ /^(?:public|private|internal|open)?\s*class\s+(\w+)/,
2761
+ /^(?:public|private|internal)?\s*struct\s+(\w+)/,
2762
+ /^(?:public|private|internal)?\s*protocol\s+(\w+)/,
2763
+ /^(?:public|private|internal)?\s*(?:static)?\s*func\s+(\w+)/
2764
+ ],
2765
+ kotlin: [
2766
+ /^(?:open|abstract|sealed)?\s*class\s+(\w+)/,
2767
+ /^interface\s+(\w+)/,
2768
+ /^(?:private|public|internal)?\s*fun\s+(\w+)/
2769
+ ],
2770
+ scala: [
2771
+ /^(?:sealed\s+)?(?:abstract\s+)?class\s+(\w+)/,
2772
+ /^object\s+(\w+)/,
2773
+ /^trait\s+(\w+)/,
2774
+ /^def\s+(\w+)/
2775
+ ],
2776
+ c: [
2777
+ /^(?:static\s+)?(?:inline\s+)?(?:const\s+)?(?:\w+(?:\s*\*)*)\s+(\w+)\s*\([^)]*\)\s*\{/,
2778
+ /^typedef\s+struct\s*\w*\s*\{[^}]*\}\s*(\w+)/,
2779
+ /^struct\s+(\w+)\s*\{/
2780
+ ],
2781
+ cpp: [
2782
+ /^class\s+(\w+)/,
2783
+ /^(?:virtual\s+)?(?:\w+(?:\s*[*&])*)\s+(\w+)\s*\([^)]*\)\s*(?:const)?\s*(?:override)?\s*\{/,
2784
+ /^namespace\s+(\w+)\s*\{/
2785
+ ]
2786
+ };
2787
+ function detectLanguage(filepath) {
2788
+ const ext = path8.extname(filepath).toLowerCase();
2789
+ return LANGUAGE_MAP[ext] || "unknown";
2790
+ }
2791
+ function determineItemType(line, language) {
2792
+ const normalizedLine = line.trim().toLowerCase();
2793
+ if (normalizedLine.includes("class ")) return "class";
2794
+ if (normalizedLine.includes("interface ")) return "interface";
2795
+ if (normalizedLine.includes("type ")) return "type";
2796
+ if (normalizedLine.startsWith("import ")) return "import";
2797
+ if (normalizedLine.includes("export ") && !normalizedLine.includes("function") && !normalizedLine.includes("class")) return "export";
2798
+ if (line.startsWith(" ") || line.startsWith(" ")) {
2799
+ if (normalizedLine.includes("function") || normalizedLine.includes("def ") || normalizedLine.includes("fn ") || normalizedLine.includes("func ")) {
2800
+ return "method";
2974
2801
  }
2975
- const duration = entry.endTime ? (entry.endTime - entry.startTime) / 1e3 : (Date.now() - entry.startTime) / 1e3;
2976
- return {
2977
- success: true,
2978
- command_id,
2979
- status: entry.status,
2980
- stdout: stdout || void 0,
2981
- stderr: stderr || void 0,
2982
- exit_code: entry.exitCode,
2983
- duration_seconds: Math.round(duration * 10) / 10,
2984
- truncated
2985
- };
2986
- } catch (error) {
2987
- return {
2988
- success: false,
2989
- command_id: args.command_id || "",
2990
- status: "error",
2991
- error: `Unexpected error: ${error.message}`
2992
- };
2802
+ if (/^\s+\w+\s*\(/.test(line)) return "method";
2803
+ }
2804
+ if (normalizedLine.includes("function ") || normalizedLine.includes("def ") || normalizedLine.includes("fn ") || normalizedLine.includes("func ")) {
2805
+ return "function";
2806
+ }
2807
+ if (normalizedLine.includes("const ") || normalizedLine.includes("let ") || normalizedLine.includes("var ")) {
2808
+ return normalizedLine.includes("=>") ? "function" : "const";
2993
2809
  }
2810
+ return "function";
2994
2811
  }
2995
- async function sendCommandInput(args) {
2996
- try {
2997
- const { command_id, input } = args;
2998
- if (!command_id || input === void 0) {
2999
- return {
3000
- success: false,
3001
- error: "command_id and input are required"
3002
- };
3003
- }
3004
- const entry = runningCommands.get(command_id);
3005
- if (!entry) {
3006
- return {
3007
- success: false,
3008
- error: `Command with id "${command_id}" not found`
3009
- };
2812
+ async function extractOutline(filepath, content, language) {
2813
+ const items = [];
2814
+ const lines = content.split("\n");
2815
+ const patterns = PATTERNS[language] || PATTERNS.javascript;
2816
+ let currentClass = null;
2817
+ let braceCount = 0;
2818
+ for (let i = 0; i < lines.length; i++) {
2819
+ const line = lines[i];
2820
+ const lineNumber = i + 1;
2821
+ const openBraces = (line.match(/\{/g) || []).length;
2822
+ const closeBraces = (line.match(/\}/g) || []).length;
2823
+ braceCount += openBraces - closeBraces;
2824
+ if (braceCount <= 0 && currentClass) {
2825
+ currentClass = null;
3010
2826
  }
3011
- if (entry.status !== "running" || !entry.process) {
3012
- return {
3013
- success: false,
3014
- error: `Command is not running (status: ${entry.status})`
3015
- };
2827
+ for (const pattern of patterns) {
2828
+ const match = line.match(pattern);
2829
+ if (match) {
2830
+ const name = match[1] || match[2];
2831
+ if (!name) continue;
2832
+ const itemType = determineItemType(line, language);
2833
+ if (!itemType) continue;
2834
+ const exported = line.includes("export ");
2835
+ const isInsideClass = currentClass && (line.startsWith(" ") || line.startsWith(" "));
2836
+ const item = {
2837
+ type: itemType,
2838
+ name,
2839
+ line_start: lineNumber,
2840
+ signature: line.trim().substring(0, 120),
2841
+ exported
2842
+ };
2843
+ if (isInsideClass && itemType === "method" && currentClass) {
2844
+ item.parent = currentClass;
2845
+ }
2846
+ if (itemType === "class") {
2847
+ currentClass = name;
2848
+ }
2849
+ items.push(item);
2850
+ break;
2851
+ }
3016
2852
  }
3017
- entry.process.stdin?.write(input);
3018
- return {
3019
- success: true,
3020
- message: `Sent ${input.length} characters to command ${command_id}`
3021
- };
3022
- } catch (error) {
3023
- return {
3024
- success: false,
3025
- error: `Failed to send input: ${error.message}`
3026
- };
3027
2853
  }
2854
+ return items;
3028
2855
  }
3029
- async function killCommand(args) {
2856
+ function generateSummary(items, totalLines) {
2857
+ const counts = {};
2858
+ for (const item of items) {
2859
+ counts[item.type] = (counts[item.type] || 0) + 1;
2860
+ }
2861
+ const parts = [`${totalLines} lines`];
2862
+ if (counts.class) parts.push(`${counts.class} class${counts.class > 1 ? "es" : ""}`);
2863
+ if (counts.interface) parts.push(`${counts.interface} interface${counts.interface > 1 ? "s" : ""}`);
2864
+ if (counts.function) parts.push(`${counts.function} function${counts.function > 1 ? "s" : ""}`);
2865
+ if (counts.method) parts.push(`${counts.method} method${counts.method > 1 ? "s" : ""}`);
2866
+ if (counts.type) parts.push(`${counts.type} type${counts.type > 1 ? "s" : ""}`);
2867
+ if (counts.const) parts.push(`${counts.const} const${counts.const > 1 ? "s" : ""}`);
2868
+ return parts.join(", ");
2869
+ }
2870
+ async function viewFileOutline(args) {
3030
2871
  try {
3031
- const { command_id } = args;
3032
- const entry = runningCommands.get(command_id);
3033
- if (!entry) {
2872
+ const { file_path } = args;
2873
+ if (!file_path || typeof file_path !== "string") {
3034
2874
  return {
3035
2875
  success: false,
3036
- error: `Command with id "${command_id}" not found`
2876
+ file_path: file_path || "",
2877
+ language: "unknown",
2878
+ total_lines: 0,
2879
+ items: [],
2880
+ summary: "",
2881
+ error: "file_path is required and must be a string"
3037
2882
  };
3038
2883
  }
3039
- if (entry.status !== "running" || !entry.process) {
2884
+ const resolvedPath = path8.resolve(file_path);
2885
+ let content;
2886
+ try {
2887
+ content = await fsPromises3.readFile(resolvedPath, "utf-8");
2888
+ } catch (error) {
3040
2889
  return {
3041
2890
  success: false,
3042
- error: `Command is not running (status: ${entry.status})`
2891
+ file_path: resolvedPath,
2892
+ language: "unknown",
2893
+ total_lines: 0,
2894
+ items: [],
2895
+ summary: "",
2896
+ error: error.code === "ENOENT" ? `File not found: ${resolvedPath}` : `Error reading file: ${error.message}`
3043
2897
  };
3044
2898
  }
3045
- entry.process.kill("SIGTERM");
3046
- entry.status = "killed";
3047
- entry.endTime = Date.now();
2899
+ const language = detectLanguage(resolvedPath);
2900
+ const lines = content.split("\n");
2901
+ const totalLines = lines.length;
2902
+ const items = await extractOutline(resolvedPath, content, language);
3048
2903
  return {
3049
2904
  success: true,
3050
- message: `Command ${command_id} killed`
2905
+ file_path: resolvedPath,
2906
+ language,
2907
+ total_lines: totalLines,
2908
+ items,
2909
+ summary: generateSummary(items, totalLines)
3051
2910
  };
3052
2911
  } catch (error) {
3053
2912
  return {
3054
2913
  success: false,
3055
- error: `Failed to kill command: ${error.message}`
2914
+ file_path: args.file_path || "",
2915
+ language: "unknown",
2916
+ total_lines: 0,
2917
+ items: [],
2918
+ summary: "",
2919
+ error: `Unexpected error: ${error.message}`
3056
2920
  };
3057
2921
  }
3058
2922
  }
3059
2923
 
2924
+ // src/app/agent/tool_invoker.ts
2925
+ init_async_command();
2926
+
3060
2927
  // src/app/agent/tools/natives/task_boundary.ts
3061
2928
  import path9 from "path";
3062
2929
  import { promises as fs7 } from "fs";
@@ -3066,7 +2933,7 @@ var artifactsDir = null;
3066
2933
  async function getArtifactsDir() {
3067
2934
  if (artifactsDir) return artifactsDir;
3068
2935
  const homeDir = os3.homedir();
3069
- const baseDir = path9.join(homeDir, ".bluma", "artifacts");
2936
+ const baseDir = path9.join(homeDir, ".bluma-cli", "artifacts");
3070
2937
  const sessionId2 = Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
3071
2938
  artifactsDir = path9.join(baseDir, sessionId2);
3072
2939
  await fs7.mkdir(artifactsDir, { recursive: true });
@@ -3388,6 +3255,44 @@ async function searchWeb(args) {
3388
3255
  }
3389
3256
  }
3390
3257
 
3258
+ // src/app/agent/tools/natives/load_skill.ts
3259
+ var globalContext = null;
3260
+ function initializeSkillContext(ctx) {
3261
+ globalContext = ctx;
3262
+ }
3263
+ async function loadSkill(args) {
3264
+ const { skill_name } = args;
3265
+ if (!skill_name || typeof skill_name !== "string") {
3266
+ return {
3267
+ success: false,
3268
+ message: "skill_name is required"
3269
+ };
3270
+ }
3271
+ if (!globalContext?.skillLoader) {
3272
+ return {
3273
+ success: false,
3274
+ message: "Skill system not initialized. No skills available."
3275
+ };
3276
+ }
3277
+ const skill = globalContext.skillLoader.load(skill_name);
3278
+ if (!skill) {
3279
+ const available = globalContext.skillLoader.listAvailable();
3280
+ const availableNames = available.map((s) => s.name).join(", ") || "none";
3281
+ return {
3282
+ success: false,
3283
+ message: `Skill "${skill_name}" not found. Available skills: ${availableNames}`
3284
+ };
3285
+ }
3286
+ return {
3287
+ success: true,
3288
+ message: `Skill "${skill_name}" loaded. Follow these instructions:
3289
+
3290
+ ${skill.content}`,
3291
+ skill_name: skill.name,
3292
+ description: skill.description
3293
+ };
3294
+ }
3295
+
3391
3296
  // src/app/agent/tool_invoker.ts
3392
3297
  var ToolInvoker = class {
3393
3298
  // Mapa privado para associar nomes de ferramentas às suas funções de implementação.
@@ -3431,13 +3336,13 @@ var ToolInvoker = class {
3431
3336
  this.toolImplementations.set("command_status", commandStatus);
3432
3337
  this.toolImplementations.set("send_command_input", sendCommandInput);
3433
3338
  this.toolImplementations.set("kill_command", killCommand);
3434
- this.toolImplementations.set("message_notify_user", messageNotifyuser);
3339
+ this.toolImplementations.set("message", message);
3435
3340
  this.toolImplementations.set("todo", todo);
3436
3341
  this.toolImplementations.set("task_boundary", taskBoundary);
3437
3342
  this.toolImplementations.set("create_artifact", createArtifact);
3438
3343
  this.toolImplementations.set("read_artifact", readArtifact);
3439
3344
  this.toolImplementations.set("search_web", searchWeb);
3440
- this.toolImplementations.set("agent_end_turn", async () => ({ success: true, message: "Task ended by agent." }));
3345
+ this.toolImplementations.set("load_skill", loadSkill);
3441
3346
  }
3442
3347
  /**
3443
3348
  * Retorna a lista de definições de todas as ferramentas nativas carregadas.
@@ -3667,7 +3572,7 @@ var AdvancedFeedbackSystem = class {
3667
3572
  };
3668
3573
 
3669
3574
  // src/app/agent/bluma/core/bluma.ts
3670
- import path14 from "path";
3575
+ import path15 from "path";
3671
3576
 
3672
3577
  // src/app/agent/session_manager/session_manager.ts
3673
3578
  import path12 from "path";
@@ -3804,39 +3709,209 @@ async function doSaveSessionHistory(sessionFile, history) {
3804
3709
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
3805
3710
  conversation_history: []
3806
3711
  };
3807
- try {
3808
- await fs10.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
3809
- } catch {
3712
+ try {
3713
+ await fs10.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
3714
+ } catch {
3715
+ }
3716
+ }
3717
+ sessionData.conversation_history = history;
3718
+ sessionData.last_updated = (/* @__PURE__ */ new Date()).toISOString();
3719
+ const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
3720
+ try {
3721
+ await fs10.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
3722
+ await safeRenameWithRetry(tempSessionFile, sessionFile);
3723
+ } catch (writeError) {
3724
+ if (writeError instanceof Error) {
3725
+ console.error(`Fatal error saving session to ${sessionFile}: ${writeError.message}`);
3726
+ } else {
3727
+ console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
3728
+ }
3729
+ try {
3730
+ await fs10.unlink(tempSessionFile);
3731
+ } catch {
3732
+ }
3733
+ }
3734
+ });
3735
+ }
3736
+ async function saveSessionHistory(sessionFile, history) {
3737
+ debouncedSave(sessionFile, history);
3738
+ }
3739
+
3740
+ // src/app/agent/core/prompt/prompt_builder.ts
3741
+ import os7 from "os";
3742
+ import fs12 from "fs";
3743
+ import path14 from "path";
3744
+ import { execSync } from "child_process";
3745
+
3746
+ // src/app/agent/skills/skill_loader.ts
3747
+ import fs11 from "fs";
3748
+ import path13 from "path";
3749
+ import os6 from "os";
3750
+ var SkillLoader = class {
3751
+ projectSkillsDir;
3752
+ globalSkillsDir;
3753
+ cache = /* @__PURE__ */ new Map();
3754
+ constructor(projectRoot) {
3755
+ this.projectSkillsDir = path13.join(projectRoot, ".bluma", "skills");
3756
+ this.globalSkillsDir = path13.join(os6.homedir(), ".bluma", "skills");
3757
+ }
3758
+ /**
3759
+ * Lista skills disponíveis de ambas as fontes
3760
+ * Skills de projeto têm prioridade sobre globais com mesmo nome
3761
+ */
3762
+ listAvailable() {
3763
+ const skills = /* @__PURE__ */ new Map();
3764
+ const globalSkills = this.listFromDir(this.globalSkillsDir, "global");
3765
+ for (const skill of globalSkills) {
3766
+ skills.set(skill.name, skill);
3767
+ }
3768
+ const projectSkills = this.listFromDir(this.projectSkillsDir, "project");
3769
+ for (const skill of projectSkills) {
3770
+ skills.set(skill.name, skill);
3771
+ }
3772
+ return Array.from(skills.values());
3773
+ }
3774
+ /**
3775
+ * Lista skills de um diretório específico
3776
+ */
3777
+ listFromDir(dir, source) {
3778
+ if (!fs11.existsSync(dir)) return [];
3779
+ try {
3780
+ return fs11.readdirSync(dir).filter((d) => {
3781
+ const fullPath = path13.join(dir, d);
3782
+ return fs11.statSync(fullPath).isDirectory() && fs11.existsSync(path13.join(fullPath, "SKILL.md"));
3783
+ }).map((d) => this.loadMetadataFromPath(path13.join(dir, d, "SKILL.md"), d, source)).filter((m) => m !== null);
3784
+ } catch {
3785
+ return [];
3786
+ }
3787
+ }
3788
+ /**
3789
+ * Carrega metadata de um path específico
3790
+ */
3791
+ loadMetadataFromPath(skillPath, skillName, source) {
3792
+ if (!fs11.existsSync(skillPath)) return null;
3793
+ try {
3794
+ const raw = fs11.readFileSync(skillPath, "utf-8");
3795
+ const parsed = this.parseFrontmatter(raw);
3796
+ return {
3797
+ name: parsed.name || skillName,
3798
+ description: parsed.description || "",
3799
+ source,
3800
+ version: parsed.version,
3801
+ author: parsed.author,
3802
+ license: parsed.license
3803
+ };
3804
+ } catch {
3805
+ return null;
3806
+ }
3807
+ }
3808
+ /**
3809
+ * Carrega skill completa - procura primeiro em projeto, depois global
3810
+ */
3811
+ load(name) {
3812
+ if (this.cache.has(name)) return this.cache.get(name);
3813
+ const projectPath = path13.join(this.projectSkillsDir, name, "SKILL.md");
3814
+ if (fs11.existsSync(projectPath)) {
3815
+ const skill = this.loadFromPath(projectPath, name, "project");
3816
+ if (skill) {
3817
+ this.cache.set(name, skill);
3818
+ return skill;
3810
3819
  }
3811
3820
  }
3812
- sessionData.conversation_history = history;
3813
- sessionData.last_updated = (/* @__PURE__ */ new Date()).toISOString();
3814
- const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
3821
+ const globalPath = path13.join(this.globalSkillsDir, name, "SKILL.md");
3822
+ if (fs11.existsSync(globalPath)) {
3823
+ const skill = this.loadFromPath(globalPath, name, "global");
3824
+ if (skill) {
3825
+ this.cache.set(name, skill);
3826
+ return skill;
3827
+ }
3828
+ }
3829
+ return null;
3830
+ }
3831
+ /**
3832
+ * Carrega skill de um path específico
3833
+ */
3834
+ loadFromPath(skillPath, name, source) {
3815
3835
  try {
3816
- await fs10.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
3817
- await safeRenameWithRetry(tempSessionFile, sessionFile);
3818
- } catch (writeError) {
3819
- if (writeError instanceof Error) {
3820
- console.error(`Fatal error saving session to ${sessionFile}: ${writeError.message}`);
3821
- } else {
3822
- console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
3836
+ const raw = fs11.readFileSync(skillPath, "utf-8");
3837
+ const parsed = this.parseFrontmatter(raw);
3838
+ return {
3839
+ name: parsed.name || name,
3840
+ description: parsed.description || "",
3841
+ content: parsed.content.trim(),
3842
+ source,
3843
+ version: parsed.version,
3844
+ author: parsed.author,
3845
+ license: parsed.license
3846
+ };
3847
+ } catch {
3848
+ return null;
3849
+ }
3850
+ }
3851
+ /**
3852
+ * Parse simples de YAML frontmatter
3853
+ */
3854
+ parseFrontmatter(raw) {
3855
+ const lines = raw.split("\n");
3856
+ if (lines[0]?.trim() !== "---") {
3857
+ return { content: raw };
3858
+ }
3859
+ let endIndex = -1;
3860
+ for (let i = 1; i < lines.length; i++) {
3861
+ if (lines[i]?.trim() === "---") {
3862
+ endIndex = i;
3863
+ break;
3823
3864
  }
3824
- try {
3825
- await fs10.unlink(tempSessionFile);
3826
- } catch {
3865
+ }
3866
+ if (endIndex === -1) {
3867
+ return { content: raw };
3868
+ }
3869
+ const frontmatter = {};
3870
+ for (let i = 1; i < endIndex; i++) {
3871
+ const line = lines[i];
3872
+ const colonIndex = line.indexOf(":");
3873
+ if (colonIndex > 0) {
3874
+ const key = line.slice(0, colonIndex).trim();
3875
+ const value = line.slice(colonIndex + 1).trim();
3876
+ frontmatter[key] = value;
3827
3877
  }
3828
3878
  }
3829
- });
3830
- }
3831
- async function saveSessionHistory(sessionFile, history) {
3832
- debouncedSave(sessionFile, history);
3833
- }
3879
+ const content = lines.slice(endIndex + 1).join("\n");
3880
+ return {
3881
+ name: frontmatter["name"],
3882
+ description: frontmatter["description"],
3883
+ version: frontmatter["version"],
3884
+ author: frontmatter["author"],
3885
+ license: frontmatter["license"],
3886
+ content
3887
+ };
3888
+ }
3889
+ /**
3890
+ * Limpa o cache
3891
+ */
3892
+ clearCache() {
3893
+ this.cache.clear();
3894
+ }
3895
+ /**
3896
+ * Verifica se uma skill existe (em projeto ou global)
3897
+ */
3898
+ exists(name) {
3899
+ const projectPath = path13.join(this.projectSkillsDir, name, "SKILL.md");
3900
+ const globalPath = path13.join(this.globalSkillsDir, name, "SKILL.md");
3901
+ return fs11.existsSync(projectPath) || fs11.existsSync(globalPath);
3902
+ }
3903
+ /**
3904
+ * Retorna os diretórios de skills
3905
+ */
3906
+ getSkillsDirs() {
3907
+ return {
3908
+ project: this.projectSkillsDir,
3909
+ global: this.globalSkillsDir
3910
+ };
3911
+ }
3912
+ };
3834
3913
 
3835
3914
  // src/app/agent/core/prompt/prompt_builder.ts
3836
- import os6 from "os";
3837
- import fs11 from "fs";
3838
- import path13 from "path";
3839
- import { execSync } from "child_process";
3840
3915
  function getNodeVersion() {
3841
3916
  try {
3842
3917
  return process.version;
@@ -3864,10 +3939,10 @@ function getGitBranch(dir) {
3864
3939
  }
3865
3940
  function getPackageManager(dir) {
3866
3941
  try {
3867
- if (fs11.existsSync(path13.join(dir, "pnpm-lock.yaml"))) return "pnpm";
3868
- if (fs11.existsSync(path13.join(dir, "yarn.lock"))) return "yarn";
3869
- if (fs11.existsSync(path13.join(dir, "bun.lockb"))) return "bun";
3870
- if (fs11.existsSync(path13.join(dir, "package-lock.json"))) return "npm";
3942
+ if (fs12.existsSync(path14.join(dir, "pnpm-lock.yaml"))) return "pnpm";
3943
+ if (fs12.existsSync(path14.join(dir, "yarn.lock"))) return "yarn";
3944
+ if (fs12.existsSync(path14.join(dir, "bun.lockb"))) return "bun";
3945
+ if (fs12.existsSync(path14.join(dir, "package-lock.json"))) return "npm";
3871
3946
  return "unknown";
3872
3947
  } catch {
3873
3948
  return "unknown";
@@ -3875,9 +3950,9 @@ function getPackageManager(dir) {
3875
3950
  }
3876
3951
  function getProjectType(dir) {
3877
3952
  try {
3878
- const files = fs11.readdirSync(dir);
3953
+ const files = fs12.readdirSync(dir);
3879
3954
  if (files.includes("package.json")) {
3880
- const pkg = JSON.parse(fs11.readFileSync(path13.join(dir, "package.json"), "utf-8"));
3955
+ const pkg = JSON.parse(fs12.readFileSync(path14.join(dir, "package.json"), "utf-8"));
3881
3956
  if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
3882
3957
  if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
3883
3958
  if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
@@ -3896,9 +3971,9 @@ function getProjectType(dir) {
3896
3971
  }
3897
3972
  function getTestFramework(dir) {
3898
3973
  try {
3899
- const pkgPath = path13.join(dir, "package.json");
3900
- if (fs11.existsSync(pkgPath)) {
3901
- const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
3974
+ const pkgPath = path14.join(dir, "package.json");
3975
+ if (fs12.existsSync(pkgPath)) {
3976
+ const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
3902
3977
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3903
3978
  if (deps.jest) return "jest";
3904
3979
  if (deps.vitest) return "vitest";
@@ -3907,7 +3982,7 @@ function getTestFramework(dir) {
3907
3982
  if (deps["@playwright/test"]) return "playwright";
3908
3983
  if (deps.cypress) return "cypress";
3909
3984
  }
3910
- if (fs11.existsSync(path13.join(dir, "pytest.ini")) || fs11.existsSync(path13.join(dir, "conftest.py"))) return "pytest";
3985
+ if (fs12.existsSync(path14.join(dir, "pytest.ini")) || fs12.existsSync(path14.join(dir, "conftest.py"))) return "pytest";
3911
3986
  return "unknown";
3912
3987
  } catch {
3913
3988
  return "unknown";
@@ -3915,9 +3990,9 @@ function getTestFramework(dir) {
3915
3990
  }
3916
3991
  function getTestCommand(dir) {
3917
3992
  try {
3918
- const pkgPath = path13.join(dir, "package.json");
3919
- if (fs11.existsSync(pkgPath)) {
3920
- const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
3993
+ const pkgPath = path14.join(dir, "package.json");
3994
+ if (fs12.existsSync(pkgPath)) {
3995
+ const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
3921
3996
  if (pkg.scripts?.test) return `npm test`;
3922
3997
  if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
3923
3998
  }
@@ -3985,7 +4060,7 @@ You MUST adapt all commands to this environment. Use the correct package manager
3985
4060
  ## How You Work
3986
4061
 
3987
4062
  ### For Multi-Step Tasks:
3988
- 1. **Acknowledge** - Confirm the request immediately via message_notify_user
4063
+ 1. **Acknowledge** - Confirm the request immediately via message
3989
4064
  2. **Plan** - Use the TODO tool to create a structured plan
3990
4065
  3. **Execute** - Implement with clear narration of each step
3991
4066
  4. **Test** - Run tests after any code changes (MANDATORY for production code)
@@ -3996,25 +4071,11 @@ You MUST adapt all commands to this environment. Use the correct package manager
3996
4071
  - Quick acknowledgment + immediate action
3997
4072
  - No TODO needed for single operations
3998
4073
 
3999
- ### Testing Philosophy (CRITICAL):
4000
- You are a **teammate who writes tests**, not an assistant who skips them.
4074
+ ### Testing Philosophy:
4075
+ Run tests when modifying code. Use the \`testing\` skill for detailed best practices.
4001
4076
 
4002
- **ALWAYS run tests when:**
4003
- - You modify any function or module
4004
- - You add new functionality
4005
- - You fix a bug (write a regression test first)
4006
- - The user asks for a feature
4007
-
4008
- **Test workflow:**
4009
- 1. Before editing: Run existing tests to ensure baseline passes
4010
- 2. After editing: Run tests to verify nothing broke
4011
- 3. For new features: Write tests alongside implementation
4012
- 4. For bugs: Write failing test \u2192 fix \u2192 verify test passes
4013
-
4014
- **Commands you should know:**
4077
+ **Commands:**
4015
4078
  - Run tests: {test_command}
4016
- - Run single test: Check project conventions
4017
- - Watch mode: Usually \`npm run test:watch\` or \`npm test -- --watch\`
4018
4079
  </workflow>
4019
4080
 
4020
4081
  ---
@@ -4029,8 +4090,8 @@ You are a **teammate who writes tests**, not an assistant who skips them.
4029
4090
  - **Check file exists** before attempting edits
4030
4091
 
4031
4092
  ### Safe Auto-Approved Tools (no confirmation needed):
4032
- - message_notify_user
4033
- - ls_tool
4093
+ - message
4094
+ - ls_tool
4034
4095
  - read_file_lines
4035
4096
  - count_file_lines
4036
4097
  - todo
@@ -4081,7 +4142,7 @@ You MUST use \`command_status\` to get the output.
4081
4142
  [Step 2] command_status({ command_id: "cmd_001", wait_seconds: 120 })
4082
4143
  \u2192 { status: "completed", exit_code: 0, stdout: "added 150 packages..." }
4083
4144
 
4084
- [Step 3] message_notify_user("Dependencies installed successfully.")
4145
+ [Step 3] message({ content: "Dependencies installed.", message_type: "info" })
4085
4146
  \`\`\`
4086
4147
 
4087
4148
  **[OK] Run tests:**
@@ -4110,7 +4171,7 @@ You MUST use \`command_status\` to get the output.
4110
4171
  [Step 2] command_status({ command_id: "cmd_004", wait_seconds: 10 })
4111
4172
  \u2192 { status: "running" }
4112
4173
 
4113
- [Step 3] message_notify_user("Building Docker image, please wait...")
4174
+ [Step 3] message({ content: "Building Docker image...", message_type: "info" })
4114
4175
 
4115
4176
  [Step 4] command_status({ command_id: "cmd_004", wait_seconds: 10 })
4116
4177
  \u2192 { status: "completed", exit_code: 0, stdout: "Successfully built abc123" }
@@ -4119,7 +4180,7 @@ You MUST use \`command_status\` to get the output.
4119
4180
  **[WRONG] Never forget command_status:**
4120
4181
  \`\`\`
4121
4182
  shell_command({ command: "npm install" })
4122
- message_notify_user("Installed!") // WRONG: You don't know if it succeeded!
4183
+ message({ content: "Installed!", message_type: "info" }) // WRONG: You don't know if it succeeded!
4123
4184
  \`\`\`
4124
4185
 
4125
4186
  **[WRONG] Never use long waits (blocks user):**
@@ -4164,39 +4225,29 @@ Examples:
4164
4225
  - Celebrate wins, acknowledge mistakes
4165
4226
 
4166
4227
  **Message via tool only:**
4167
- - Use message_notify_user for ALL communication
4168
- - Never assume the user sees internal state
4169
- - Report progress proactively
4228
+ - message with \`result\` = END your turn (after questions/completions)
4229
+ - message with \`info\` = progress update, you continue working
4170
4230
 
4171
- **Example flow:**
4231
+ **CRITICAL: After asking questions, ALWAYS use result!**
4232
+
4233
+ **Asking question (result):**
4172
4234
  \`\`\`
4173
- message_notify_user("Alex, understood. Adding the auth middleware. Let me check the current implementation first.")
4174
- [uses read_file_lines]
4175
- message_notify_user("Found the router at /src/routes/index.ts. I'll add the middleware before the protected routes.")
4176
- [uses edit_tool]
4177
- message_notify_user("Done. Running tests to verify...")
4178
- [uses shell_command with {test_command}]
4179
- message_notify_user("All 47 tests pass. The middleware is working correctly.")
4235
+ message({ content: "Project or global?", message_type: "result" })
4236
+ // STOP and wait for user
4180
4237
  \`\`\`
4181
- </communication_style>
4182
4238
 
4183
- ---
4184
-
4185
- <git_guidelines>
4186
- ## Git Best Practices
4187
-
4188
- When in a git repository:
4189
- - **Review changes**: \`git diff HEAD\` before any commit
4190
- - **Commit format**: Conventional Commits (feat:, fix:, chore:, docs:, test:, refactor:)
4191
- - **NEVER push** unless explicitly asked
4192
- - **NEVER use destructive commands**: reset --hard, rebase -i, force push
4239
+ **Progress then continue (info):**
4240
+ \`\`\`
4241
+ message({ content: "Installing...", message_type: "info" })
4242
+ // continue with next tool
4243
+ \`\`\`
4193
4244
 
4194
- Good commit messages:
4195
- - feat: add user authentication middleware
4196
- - fix: resolve race condition in payment processing
4197
- - test: add unit tests for auth module
4198
- - chore: update dependencies
4199
- </git_guidelines>
4245
+ **Task done (result):**
4246
+ \`\`\`
4247
+ message({ content: "Done. All tests pass.", message_type: "result" })
4248
+ // STOP - finished
4249
+ \`\`\`
4250
+ </communication_style>
4200
4251
 
4201
4252
  ---
4202
4253
 
@@ -4233,15 +4284,15 @@ You communicate because you respect your teammate.
4233
4284
  Let's build something great, {username}.
4234
4285
  </personality>
4235
4286
  `;
4236
- function getUnifiedSystemPrompt() {
4287
+ function getUnifiedSystemPrompt(availableSkills) {
4237
4288
  const cwd = process.cwd();
4238
4289
  const env = {
4239
- os_type: os6.type(),
4240
- os_version: os6.release(),
4241
- architecture: os6.arch(),
4290
+ os_type: os7.type(),
4291
+ os_version: os7.release(),
4292
+ architecture: os7.arch(),
4242
4293
  workdir: cwd,
4243
4294
  shell_type: process.env.SHELL || process.env.COMSPEC || "unknown",
4244
- username: os6.userInfo().username,
4295
+ username: os7.userInfo().username,
4245
4296
  current_date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
4246
4297
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
4247
4298
  is_git_repo: isGitRepo(cwd) ? "yes" : "no",
@@ -4253,15 +4304,164 @@ function getUnifiedSystemPrompt() {
4253
4304
  test_framework: getTestFramework(cwd),
4254
4305
  test_command: getTestCommand(cwd)
4255
4306
  };
4256
- return Object.entries(env).reduce(
4257
- (prompt, [key, value]) => prompt.replaceAll(`{${key}}`, value),
4307
+ let prompt = Object.entries(env).reduce(
4308
+ (p, [key, value]) => p.replaceAll(`{${key}}`, value),
4258
4309
  SYSTEM_PROMPT
4259
4310
  );
4311
+ if (availableSkills && availableSkills.length > 0) {
4312
+ const skillsList = availableSkills.map((s) => `- ${s.name}: ${s.description}`).join("\n");
4313
+ prompt += `
4314
+
4315
+ ---
4316
+
4317
+ <available_skills>
4318
+ ## Specialized Skills
4319
+
4320
+ You have access to specialized knowledge modules called **skills**. Skills extend your capabilities with domain-specific expertise, workflows, and best practices.
4321
+
4322
+ **Available skills:**
4323
+ ${skillsList}
4324
+
4325
+ ---
4326
+
4327
+ ### Skill Selection Process
4328
+
4329
+ **BEFORE starting any task, follow this decision process:**
4330
+
4331
+ 1. **Understand the task** - What is the user trying to accomplish?
4332
+ 2. **Identify the domain** - What area of expertise does this task require? (e.g., version control, testing, deployment, documentation)
4333
+ 3. **Match against skills** - Read each skill's description above. Does any skill's domain match the task?
4334
+ 4. **Load if matched** - If a skill matches, load it FIRST with \`load_skill\` before taking any action
4335
+ 5. **Follow skill instructions** - Once loaded, the skill provides specific workflows to follow
4336
+
4337
+ **Decision examples:**
4338
+ \`\`\`
4339
+ Task: "Save my changes with a good message"
4340
+ \u2192 Domain: Version control, commits
4341
+ \u2192 Check skills: Is there a skill for git/commits/version control?
4342
+ \u2192 If yes: load_skill({ skill_name: "<matching-skill>" })
4343
+
4344
+ Task: "Make sure this code works correctly"
4345
+ \u2192 Domain: Testing, validation, quality assurance
4346
+ \u2192 Check skills: Is there a skill for testing?
4347
+ \u2192 If yes: load_skill({ skill_name: "<matching-skill>" })
4348
+
4349
+ Task: "Release this to users"
4350
+ \u2192 Domain: Publishing, deployment, release
4351
+ \u2192 Check skills: Is there a skill for publishing/releasing?
4352
+ \u2192 If yes: load_skill({ skill_name: "<matching-skill>" })
4353
+ \`\`\`
4354
+
4355
+ **Key principle:** Match the TASK DOMAIN to the SKILL DESCRIPTION, not specific words.
4356
+
4357
+ ---
4358
+
4359
+ ### Skill Interpretation Rules (CRITICAL)
4360
+
4361
+ When you load a skill with \`load_skill\`, you receive a SKILL.md file. **ALWAYS read the YAML frontmatter header FIRST** before reading the body.
4362
+
4363
+ **Frontmatter Structure:**
4364
+ \`\`\`yaml
4365
+ ---
4366
+ # IDENTITY
4367
+ name: skill-name # Unique identifier
4368
+ description: ... # Purpose of the skill
4369
+ version: 1.0.0
4370
+ author: ...
4371
+ license: ...
4372
+
4373
+ # DEPENDENCIES
4374
+ depends_on: # Other skills this skill may delegate to
4375
+ - other-skill-name
4376
+
4377
+ # TOOLS
4378
+ tools:
4379
+ required: # Tools you MUST use for this skill
4380
+ - shell_command
4381
+ - command_status
4382
+ recommended: # Tools that enhance execution
4383
+ - read_file_lines
4384
+ ---
4385
+ \`\`\`
4386
+
4387
+ **Interpretation Flow:**
4388
+ \`\`\`
4389
+ 1. LOAD SKILL
4390
+ load_skill({ skill_name: "npm-publish" })
4391
+
4392
+ 2. READ FRONTMATTER FIRST
4393
+ - name: npm-publish
4394
+ - depends_on: [git-conventional]
4395
+ - tools.required: [shell_command, command_status]
4396
+ - tools.recommended: [read_file_lines, message]
4397
+
4398
+ 3. VERIFY TOOLS
4399
+ Are required tools available? If not, inform user.
4400
+
4401
+ 4. READ SKILL BODY
4402
+ Workflow instructions, examples, rules
4403
+
4404
+ 5. EXECUTE WORKFLOW
4405
+ - Use the REQUIRED tools from frontmatter
4406
+ - When body says "delegate to X":
4407
+ a. Verify X is in depends_on
4408
+ b. load_skill({ skill_name: "X" })
4409
+ c. Execute X workflow
4410
+ d. Return to primary skill
4411
+ - Continue until workflow complete
4412
+ \`\`\`
4413
+
4414
+ **Field Meanings:**
4415
+ | Field | Purpose | Your Action |
4416
+ |-------|---------|-------------|
4417
+ | \`name\` | Skill identifier | Confirm correct skill loaded |
4418
+ | \`description\` | Skill purpose | Understand what this skill does |
4419
+ | \`depends_on\` | Skill dependencies | Know which skills can be delegated to |
4420
+ | \`tools.required\` | Mandatory tools | MUST use these tools for execution |
4421
+ | \`tools.recommended\` | Optional tools | Use if helpful |
4422
+
4423
+ **Orchestration Example:**
4424
+ \`\`\`
4425
+ User: "Publish the package"
4426
+
4427
+ 1. Match domain -> npm-publish skill
4428
+ 2. load_skill({ skill_name: "npm-publish" })
4429
+
4430
+ 3. Read frontmatter:
4431
+ - depends_on: [git-conventional]
4432
+ - tools.required: [shell_command, command_status]
4433
+
4434
+ 4. Read body -> Workflow says:
4435
+ "Step 1: If uncommitted changes, delegate to git-conventional"
4436
+
4437
+ 5. Check git status with shell_command (required tool)
4438
+ Found changes
4439
+
4440
+ 6. Delegate: load_skill({ skill_name: "git-conventional" })
4441
+ - Read git-conventional frontmatter
4442
+ - Follow its workflow for commit
4443
+ - Commit done
4444
+
4445
+ 7. Return to npm-publish Step 2: npm version patch
4446
+ 8. Step 3: npm publish
4447
+ 9. Done
4448
+ \`\`\`
4449
+
4450
+ **Rules:**
4451
+ - Frontmatter is the SPECIFICATION - read it first
4452
+ - Body is the WORKFLOW - execute after understanding spec
4453
+ - \`depends_on\` authorizes delegation - only delegate to listed skills
4454
+ - \`tools.required\` are mandatory - use them for execution
4455
+ - The skill body tells you WHEN to delegate, not the frontmatter
4456
+ </available_skills>
4457
+ `;
4458
+ }
4459
+ return prompt;
4260
4460
  }
4261
4461
  function isGitRepo(dir) {
4262
4462
  try {
4263
- const gitPath = path13.join(dir, ".git");
4264
- return fs11.existsSync(gitPath) && fs11.lstatSync(gitPath).isDirectory();
4463
+ const gitPath = path14.join(dir, ".git");
4464
+ return fs12.existsSync(gitPath) && fs12.lstatSync(gitPath).isDirectory();
4265
4465
  } catch {
4266
4466
  return false;
4267
4467
  }
@@ -4317,8 +4517,155 @@ function createApiContextWindow(fullHistory, maxTurns) {
4317
4517
  return finalContext;
4318
4518
  }
4319
4519
 
4520
+ // src/app/agent/core/llm/tool_call_normalizer.ts
4521
+ import { randomUUID } from "crypto";
4522
+ var ToolCallNormalizer = class {
4523
+ /**
4524
+ * Normaliza a mensagem do assistant, convertendo diferentes formatos de tool calls
4525
+ */
4526
+ static normalizeAssistantMessage(message2) {
4527
+ if (message2.tool_calls && this.isOpenAIFormat(message2.tool_calls)) {
4528
+ return message2;
4529
+ }
4530
+ const toolCalls = this.extractToolCalls(message2);
4531
+ if (toolCalls.length > 0) {
4532
+ return {
4533
+ role: message2.role || "assistant",
4534
+ content: message2.content || null,
4535
+ tool_calls: toolCalls
4536
+ };
4537
+ }
4538
+ return message2;
4539
+ }
4540
+ /**
4541
+ * Verifica se já está no formato OpenAI
4542
+ */
4543
+ static isOpenAIFormat(toolCalls) {
4544
+ if (!Array.isArray(toolCalls) || toolCalls.length === 0) return false;
4545
+ const firstCall = toolCalls[0];
4546
+ return typeof firstCall.id === "string" && firstCall.type === "function" && typeof firstCall.function?.name === "string" && typeof firstCall.function?.arguments === "string";
4547
+ }
4548
+ /**
4549
+ * Extrai tool calls de diversos formatos possíveis
4550
+ */
4551
+ static extractToolCalls(message2) {
4552
+ const results = [];
4553
+ if (message2.tool_calls && Array.isArray(message2.tool_calls)) {
4554
+ for (const call of message2.tool_calls) {
4555
+ const normalized = this.normalizeToolCall(call);
4556
+ if (normalized) results.push(normalized);
4557
+ }
4558
+ }
4559
+ if (typeof message2.content === "string" && message2.content.trim()) {
4560
+ const extracted = this.extractFromContent(message2.content);
4561
+ results.push(...extracted);
4562
+ }
4563
+ if (message2.function_call) {
4564
+ const normalized = this.normalizeToolCall(message2.function_call);
4565
+ if (normalized) results.push(normalized);
4566
+ }
4567
+ return results;
4568
+ }
4569
+ /**
4570
+ * Normaliza um único tool call para o formato OpenAI
4571
+ */
4572
+ static normalizeToolCall(call) {
4573
+ try {
4574
+ if (call.id && call.function?.name) {
4575
+ return {
4576
+ id: call.id,
4577
+ type: "function",
4578
+ function: {
4579
+ name: call.function.name,
4580
+ arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments)
4581
+ }
4582
+ };
4583
+ }
4584
+ if (call.name) {
4585
+ return {
4586
+ id: call.id || randomUUID(),
4587
+ type: "function",
4588
+ function: {
4589
+ name: call.name,
4590
+ arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments || {})
4591
+ }
4592
+ };
4593
+ }
4594
+ if (call.function && typeof call.function === "object") {
4595
+ return {
4596
+ id: call.id || randomUUID(),
4597
+ type: "function",
4598
+ function: {
4599
+ name: call.function.name,
4600
+ arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments || {})
4601
+ }
4602
+ };
4603
+ }
4604
+ return null;
4605
+ } catch (error) {
4606
+ console.error("Error normalizing tool call:", error, call);
4607
+ return null;
4608
+ }
4609
+ }
4610
+ /**
4611
+ * Extrai tool calls do content (pode estar em markdown, JSON, etc)
4612
+ */
4613
+ static extractFromContent(content) {
4614
+ const results = [];
4615
+ const cleanContent = content.replace(/```(?:json)?\s*([\s\S]*?)```/g, "$1");
4616
+ const jsonMatches = this.extractJsonObjects(cleanContent);
4617
+ for (const jsonStr of jsonMatches) {
4618
+ try {
4619
+ const parsed = JSON.parse(jsonStr);
4620
+ if (Array.isArray(parsed)) {
4621
+ for (const call of parsed) {
4622
+ const normalized = this.normalizeToolCall(call);
4623
+ if (normalized) results.push(normalized);
4624
+ }
4625
+ } else if (parsed.name || parsed.function) {
4626
+ const normalized = this.normalizeToolCall(parsed);
4627
+ if (normalized) results.push(normalized);
4628
+ } else if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
4629
+ for (const call of parsed.tool_calls) {
4630
+ const normalized = this.normalizeToolCall(call);
4631
+ if (normalized) results.push(normalized);
4632
+ }
4633
+ }
4634
+ } catch (e) {
4635
+ }
4636
+ }
4637
+ return results;
4638
+ }
4639
+ /**
4640
+ * Extrai objetos JSON de uma string (suporta múltiplos objetos)
4641
+ */
4642
+ static extractJsonObjects(text) {
4643
+ const results = [];
4644
+ let depth = 0;
4645
+ let start = -1;
4646
+ for (let i = 0; i < text.length; i++) {
4647
+ if (text[i] === "{") {
4648
+ if (depth === 0) start = i;
4649
+ depth++;
4650
+ } else if (text[i] === "}") {
4651
+ depth--;
4652
+ if (depth === 0 && start !== -1) {
4653
+ results.push(text.substring(start, i + 1));
4654
+ start = -1;
4655
+ }
4656
+ }
4657
+ }
4658
+ return results;
4659
+ }
4660
+ /**
4661
+ * Valida se um tool call normalizado é válido
4662
+ */
4663
+ static isValidToolCall(call) {
4664
+ return !!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string");
4665
+ }
4666
+ };
4667
+
4320
4668
  // src/app/agent/bluma/core/bluma.ts
4321
- init_tool_call_normalizer();
4322
4669
  var BluMaAgent = class {
4323
4670
  llm;
4324
4671
  deploymentName;
@@ -4328,6 +4675,7 @@ var BluMaAgent = class {
4328
4675
  eventBus;
4329
4676
  mcpClient;
4330
4677
  feedbackSystem;
4678
+ skillLoader;
4331
4679
  maxContextTurns = 30;
4332
4680
  isInterrupted = false;
4333
4681
  constructor(sessionId2, eventBus2, llm, deploymentName, mcpClient, feedbackSystem) {
@@ -4337,6 +4685,7 @@ var BluMaAgent = class {
4337
4685
  this.deploymentName = deploymentName;
4338
4686
  this.mcpClient = mcpClient;
4339
4687
  this.feedbackSystem = feedbackSystem;
4688
+ this.skillLoader = new SkillLoader(process.cwd());
4340
4689
  this.eventBus.on("user_interrupt", () => {
4341
4690
  this.isInterrupted = true;
4342
4691
  this.eventBus.emit("backend_message", { type: "done", status: "interrupted" });
@@ -4348,6 +4697,10 @@ var BluMaAgent = class {
4348
4697
  try {
4349
4698
  if (this.sessionFile) {
4350
4699
  await saveSessionHistory(this.sessionFile, this.history);
4700
+ } else {
4701
+ const [sessionFile, _] = await loadOrcreateSession(this.sessionId);
4702
+ this.sessionFile = sessionFile;
4703
+ await saveSessionHistory(this.sessionFile, this.history);
4351
4704
  }
4352
4705
  } catch (e) {
4353
4706
  this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s user_overlay: ${e.message}` });
@@ -4360,8 +4713,13 @@ var BluMaAgent = class {
4360
4713
  const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
4361
4714
  this.sessionFile = sessionFile;
4362
4715
  this.history = history;
4716
+ initializeSkillContext({
4717
+ history: this.history,
4718
+ skillLoader: this.skillLoader
4719
+ });
4363
4720
  if (this.history.length === 0) {
4364
- const systemPrompt = getUnifiedSystemPrompt();
4721
+ const availableSkills = this.skillLoader.listAvailable();
4722
+ const systemPrompt = getUnifiedSystemPrompt(availableSkills);
4365
4723
  this.history.push({ role: "system", content: systemPrompt });
4366
4724
  await saveSessionHistory(this.sessionFile, this.history);
4367
4725
  }
@@ -4376,6 +4734,9 @@ var BluMaAgent = class {
4376
4734
  this.isInterrupted = false;
4377
4735
  const inputText = String(userInput.content || "").trim();
4378
4736
  this.history.push({ role: "user", content: inputText });
4737
+ if (inputText === "/init") {
4738
+ this.eventBus.emit("dispatch", inputText);
4739
+ }
4379
4740
  await this._continueConversation();
4380
4741
  }
4381
4742
  async handleToolResponse(decisionData) {
@@ -4474,9 +4835,15 @@ var BluMaAgent = class {
4474
4835
  });
4475
4836
  }
4476
4837
  this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
4477
- if (toolName.includes("agent_end_turn")) {
4478
- shouldContinueConversation = false;
4479
- this.eventBus.emit("backend_message", { type: "done", status: "completed" });
4838
+ if (toolName === "message") {
4839
+ try {
4840
+ const resultObj = typeof toolResultContent === "string" ? JSON.parse(toolResultContent) : toolResultContent;
4841
+ if (resultObj.message_type === "result") {
4842
+ shouldContinueConversation = false;
4843
+ this.eventBus.emit("backend_message", { type: "done", status: "completed" });
4844
+ }
4845
+ } catch {
4846
+ }
4480
4847
  }
4481
4848
  } else {
4482
4849
  toolResultContent = "The system rejected this action. Verify that the command you are executing contributes to the tasks intent and try again.";
@@ -4503,7 +4870,7 @@ var BluMaAgent = class {
4503
4870
 
4504
4871
  ${editData.error.display}`;
4505
4872
  }
4506
- const filename = path14.basename(toolArgs.file_path);
4873
+ const filename = path15.basename(toolArgs.file_path);
4507
4874
  return createDiff(filename, editData.currentContent || "", editData.newContent);
4508
4875
  } catch (e) {
4509
4876
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -4529,18 +4896,18 @@ ${editData.error.display}`;
4529
4896
  this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
4530
4897
  return;
4531
4898
  }
4532
- let message = response.choices[0].message;
4533
- message = ToolCallNormalizer.normalizeAssistantMessage(message);
4534
- if (message.reasoning_content || message.reasoning) {
4535
- const reasoningText = message.reasoning_content || message.reasoning;
4899
+ let message2 = response.choices[0].message;
4900
+ message2 = ToolCallNormalizer.normalizeAssistantMessage(message2);
4901
+ if (message2.reasoning_content || message2.reasoning) {
4902
+ const reasoningText = message2.reasoning_content || message2.reasoning;
4536
4903
  this.eventBus.emit("backend_message", {
4537
4904
  type: "reasoning",
4538
4905
  content: typeof reasoningText === "string" ? reasoningText : JSON.stringify(reasoningText)
4539
4906
  });
4540
4907
  }
4541
- this.history.push(message);
4542
- if (message.tool_calls && message.tool_calls.length > 0) {
4543
- const validToolCalls = message.tool_calls.filter(
4908
+ this.history.push(message2);
4909
+ if (message2.tool_calls && message2.tool_calls.length > 0) {
4910
+ const validToolCalls = message2.tool_calls.filter(
4544
4911
  (call) => ToolCallNormalizer.isValidToolCall(call)
4545
4912
  );
4546
4913
  if (validToolCalls.length === 0) {
@@ -4552,12 +4919,12 @@ ${editData.error.display}`;
4552
4919
  return;
4553
4920
  }
4554
4921
  const autoApprovedTools = [
4555
- "agent_end_turn",
4556
- "message_notify_user",
4922
+ "message",
4557
4923
  "ls_tool",
4558
4924
  "count_file_lines",
4559
4925
  "read_file_lines",
4560
- "todo"
4926
+ "todo",
4927
+ "load_skill"
4561
4928
  ];
4562
4929
  const toolToCall = validToolCalls[0];
4563
4930
  const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
@@ -4573,13 +4940,13 @@ ${editData.error.display}`;
4573
4940
  this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: validToolCalls });
4574
4941
  }
4575
4942
  }
4576
- } else if (message.content) {
4577
- this.eventBus.emit("backend_message", { type: "assistant_message", content: message.content });
4943
+ } else if (message2.content) {
4944
+ this.eventBus.emit("backend_message", { type: "assistant_message", content: message2.content });
4578
4945
  const feedback = this.feedbackSystem.generateFeedback({
4579
4946
  event: "protocol_violation_direct_text",
4580
- details: { violationContent: message.content }
4947
+ details: { violationContent: message2.content }
4581
4948
  });
4582
- this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.message, content: message.content });
4949
+ this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.message, content: message2.content });
4583
4950
  this.history.push({ role: "system", content: feedback.correction });
4584
4951
  await this._continueConversation();
4585
4952
  } else {
@@ -4596,14 +4963,20 @@ ${editData.error.display}`;
4596
4963
  };
4597
4964
 
4598
4965
  // src/app/agent/core/llm/llm.ts
4599
- var OpenAIAdapter = class {
4966
+ import OpenAI from "openai";
4967
+ var LLMService = class {
4600
4968
  client;
4601
- constructor(client) {
4602
- this.client = client;
4969
+ defaultModel;
4970
+ constructor(config2) {
4971
+ this.client = new OpenAI({
4972
+ apiKey: config2.apiKey,
4973
+ baseURL: config2.baseUrl || "http://109.51.171.144/mistrall/v1"
4974
+ });
4975
+ this.defaultModel = config2.model || "";
4603
4976
  }
4604
4977
  async chatCompletion(params) {
4605
4978
  const resp = await this.client.chat.completions.create({
4606
- model: params.model,
4979
+ model: params.model || this.defaultModel,
4607
4980
  messages: params.messages,
4608
4981
  tools: params.tools,
4609
4982
  tool_choice: params.tool_choice,
@@ -4613,6 +4986,12 @@ var OpenAIAdapter = class {
4613
4986
  });
4614
4987
  return resp;
4615
4988
  }
4989
+ /**
4990
+ * Retorna o modelo padrão configurado
4991
+ */
4992
+ getDefaultModel() {
4993
+ return this.defaultModel;
4994
+ }
4616
4995
  };
4617
4996
 
4618
4997
  // src/app/agent/subagents/registry.ts
@@ -4627,7 +5006,7 @@ function getSubAgentByCommand(cmd) {
4627
5006
  }
4628
5007
 
4629
5008
  // src/app/agent/subagents/init/init_system_prompt.ts
4630
- import os7 from "os";
5009
+ import os8 from "os";
4631
5010
  var SYSTEM_PROMPT2 = `
4632
5011
 
4633
5012
  ### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
@@ -4641,7 +5020,7 @@ You extend the BluMa multi-agent architecture and handle the project bootstrappi
4641
5020
  You are BluMa InitSubAgent. Maintain professionalism and technical language.
4642
5021
 
4643
5022
  - Communication:
4644
- ALL messages must be sent via 'message_notify_user'.
5023
+ ALL messages must be sent via 'message'.
4645
5024
  No direct text replies to the user.
4646
5025
 
4647
5026
  - Task Completion:
@@ -4766,8 +5145,8 @@ Rule Summary:
4766
5145
  - Never invent file content. Read files via tools to confirm.
4767
5146
 
4768
5147
  ## OUTPUT
4769
- - Emit 'backend_message' events through tools only (message_notify_user) for progress updates.
4770
- - Before writing BluMa.md, propose structure via message_notify_user and proceed using edit_tool.
5148
+ - Emit 'backend_message' events through tools only (message) for progress updates.
5149
+ - Before writing BluMa.md, propose structure via message and proceed using edit_tool.
4771
5150
  - If an irreversible operation is needed (e.g., overwriting an existing BluMa.md), issue 'confirmation_request' unless user policy indicates auto-approval.
4772
5151
  - Never send or present draft versions of BluMa.md. Only produce and deliver the final, validated BluMa.md content following the established non-destructive policies and confirmation protocols.
4773
5152
  - On successful generation of BluMa.md, emit 'done' with status 'completed' and call agent_end_turn.
@@ -4781,7 +5160,7 @@ Rule Summary:
4781
5160
  ## EXEMPLAR FLOW (GUIDELINE)
4782
5161
  1) Explore repo: ls + targeted readLines for key files (package.json, tsconfig.json, README, etc.)
4783
5162
  2) Synthesize stack and structure with citations of evidence (file paths) in the notebook
4784
- 3) Draft BluMa.md structure (message_notify_user)
5163
+ 3) Draft BluMa.md structure (message)
4785
5164
  4) Write BluMa.md via edit_tool
4786
5165
  5) Announce completion and agent_end_turn
4787
5166
 
@@ -4790,12 +5169,12 @@ Rule Summary:
4790
5169
  function getInitPrompt() {
4791
5170
  const now = /* @__PURE__ */ new Date();
4792
5171
  const collectedData = {
4793
- os_type: os7.type(),
4794
- os_version: os7.release(),
4795
- architecture: os7.arch(),
5172
+ os_type: os8.type(),
5173
+ os_version: os8.release(),
5174
+ architecture: os8.arch(),
4796
5175
  workdir: process.cwd(),
4797
5176
  shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
4798
- username: os7.userInfo().username || "Unknown",
5177
+ username: os8.userInfo().username || "Unknown",
4799
5178
  current_date: now.toISOString().split("T")[0],
4800
5179
  // Formato YYYY-MM-DD
4801
5180
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
@@ -4886,17 +5265,17 @@ ${editData.error.display}`;
4886
5265
  this.emitEvent("info", { message: "SubAgent task cancelled byuserr." });
4887
5266
  return;
4888
5267
  }
4889
- const message = response.choices[0].message;
4890
- this.history.push(message);
4891
- if (message.tool_calls) {
4892
- await this._handleToolExecution({ type: "user_decision_execute", tool_calls: message.tool_calls });
4893
- const lastToolName = message.tool_calls[0].function.name;
5268
+ const message2 = response.choices[0].message;
5269
+ this.history.push(message2);
5270
+ if (message2.tool_calls) {
5271
+ await this._handleToolExecution({ type: "user_decision_execute", tool_calls: message2.tool_calls });
5272
+ const lastToolName = message2.tool_calls[0].function.name;
4894
5273
  if (!lastToolName.includes("agent_end_turn") && !this.isInterrupted) {
4895
5274
  await this._continueConversation();
4896
5275
  }
4897
- } else if (message.content) {
4898
- this.emitEvent("assistant_message", { content: message.content });
4899
- this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content: message.content });
5276
+ } else if (message2.content) {
5277
+ this.emitEvent("assistant_message", { content: message2.content });
5278
+ this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content: message2.content });
4900
5279
  } else {
4901
5280
  this.emitEvent("info", { message: "SubAgent is thinking... continuing reasoning cycle." });
4902
5281
  }
@@ -5047,21 +5426,43 @@ var SubAgentsBluMa = class {
5047
5426
  }
5048
5427
  };
5049
5428
 
5429
+ // src/app/agent/routeManager.ts
5430
+ var RouteManager = class {
5431
+ routeHandlers;
5432
+ subAgents;
5433
+ core;
5434
+ constructor(subAgents, core) {
5435
+ this.routeHandlers = /* @__PURE__ */ new Map();
5436
+ this.subAgents = subAgents;
5437
+ this.core = core;
5438
+ }
5439
+ registerRoute(path18, handler) {
5440
+ this.routeHandlers.set(path18, handler);
5441
+ }
5442
+ async handleRoute(payload) {
5443
+ const inputText = String(payload.content || "").trim();
5444
+ for (const [path18, handler] of this.routeHandlers) {
5445
+ if (inputText === path18 || inputText.startsWith(`${path18} `)) {
5446
+ return handler({ content: inputText });
5447
+ }
5448
+ }
5449
+ await this.core.processTurn({ content: inputText });
5450
+ }
5451
+ };
5452
+
5050
5453
  // src/app/agent/agent.ts
5051
- var globalEnvPath = path15.join(os8.homedir(), ".bluma-cli", ".env");
5454
+ var globalEnvPath = path16.join(os9.homedir(), ".bluma-cli", ".env");
5052
5455
  dotenv.config({ path: globalEnvPath });
5053
5456
  var Agent = class {
5054
5457
  sessionId;
5055
5458
  eventBus;
5056
5459
  mcpClient;
5057
5460
  feedbackSystem;
5058
- // Mantido caso UI dependa de eventos
5059
5461
  llm;
5060
- deploymentName;
5462
+ routeManager;
5463
+ model;
5061
5464
  core;
5062
- // Delegado
5063
5465
  subAgents;
5064
- // Orquestrador de subagentes
5065
5466
  toolInvoker;
5066
5467
  constructor(sessionId2, eventBus2) {
5067
5468
  this.sessionId = sessionId2;
@@ -5070,75 +5471,53 @@ var Agent = class {
5070
5471
  this.toolInvoker = nativeToolInvoker;
5071
5472
  this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
5072
5473
  this.feedbackSystem = new AdvancedFeedbackSystem();
5073
- const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
5074
- const apiKey = process.env.OPENROUTER_API_KEY;
5075
- const apiVersion = process.env.AZURE_OPENAI_API_VERSION;
5076
- this.deploymentName = process.env.AZURE_OPENAI_DEPLOYMENT || "";
5077
- const missing = [];
5078
- if (!apiKey) missing.push("OPENROUTER_API_KEY");
5079
- if (missing.length > 0) {
5474
+ const apiKey = process.env.NOMAD_API_KEY;
5475
+ const baseUrl = process.env.NOMAD_BASE_URL;
5476
+ this.model = process.env.MODEL_NOMAD || "";
5477
+ if (!apiKey) {
5080
5478
  const platform = process.platform;
5081
- const varList = missing.join(", ");
5082
5479
  let guidance = "";
5083
5480
  if (platform === "win32") {
5084
5481
  guidance = [
5085
5482
  "Windows (PowerShell):",
5086
- ` $env:${missing[0]}="<value>" # sess\xE3o atual`,
5087
- ...missing.slice(1).map((v) => ` $env:${v}="<value>"`),
5088
- " # Persistir para o utilizador:",
5089
- ...missing.map((v) => ` [System.Environment]::SetEnvironmentVariable("${v}", "<value>", "User")`),
5090
- "",
5091
- "Windows (cmd.exe):",
5092
- ...missing.map((v) => ` setx ${v} "<value>"`)
5093
- ].join("");
5483
+ ' $env:NOMAD_API_KEY="<your-key>"',
5484
+ " # Para persistir:",
5485
+ ' [System.Environment]::SetEnvironmentVariable("NOMAD_API_KEY", "<your-key>", "User")'
5486
+ ].join("\n");
5094
5487
  } else if (platform === "darwin" || platform === "linux") {
5095
5488
  guidance = [
5096
5489
  "macOS/Linux (bash/zsh):",
5097
- ...missing.map((v) => ` echo 'export ${v}="<value>"' >> ~/.bashrc # ou ~/.zshrc`),
5098
- " source ~/.bashrc # ou: source ~/.zshrc"
5099
- ].join("");
5490
+ ` echo 'export NOMAD_API_KEY="<your-key>"' >> ~/.bashrc`,
5491
+ " source ~/.bashrc"
5492
+ ].join("\n");
5100
5493
  } else {
5101
- guidance = [
5102
- "Generic POSIX:",
5103
- ...missing.map((v) => ` export ${v}="<value>"`)
5104
- ].join("");
5494
+ guidance = ' export NOMAD_API_KEY="<your-key>"';
5105
5495
  }
5106
- const message = [
5107
- `Missing required environment variables: ${varList}.`,
5108
- `Configure them globally using the commands below (based on your OS), or set them in the config file: ${globalEnvPath}.`,
5496
+ const message2 = [
5497
+ "Missing required environment variable: NOMAD_API_KEY.",
5498
+ `Configure it globally using the commands below, or set it in: ${globalEnvPath}`,
5109
5499
  "",
5110
5500
  guidance
5111
- ].join("");
5501
+ ].join("\n");
5112
5502
  this.eventBus.emit("backend_message", {
5113
5503
  type: "error",
5114
5504
  code: "missing_env",
5115
- missing,
5505
+ missing: ["NOMAD_API_KEY", "MODEL_NOMAD"],
5116
5506
  path: globalEnvPath,
5117
- message
5507
+ message: message2
5118
5508
  });
5119
- throw new Error(message);
5120
- }
5121
- const ollama = new OpenAI({
5122
- //baseURL: "http://127.0.0.1:11434/v1",
5123
- //baseURL: "http://localhost:8000/v1",
5124
- //baseURL: "https://api.groq.com/openai/v1",
5125
- //baseURL: "https://api.cerebras.ai/v1",
5126
- baseURL: "https://openrouter.ai/api/v1",
5127
- apiKey: apiKey || "",
5128
- // Buscar do environment do sistema
5129
- defaultHeaders: {
5130
- "HTTP-Referer": "<YOUR_SITE_URL>",
5131
- // Optional. Site URL for rankings on openrouter.ai.
5132
- "X-Title": "<YOUR_SITE_NAME>"
5133
- // Optional. Site title for rankings on openrouter.ai.
5134
- }
5509
+ throw new Error(message2);
5510
+ }
5511
+ this.llm = new LLMService({
5512
+ apiKey,
5513
+ baseUrl,
5514
+ model: this.model
5135
5515
  });
5136
- this.llm = new OpenAIAdapter(ollama);
5137
5516
  this.core = new BluMaAgent(
5138
5517
  this.sessionId,
5139
5518
  this.eventBus,
5140
5519
  this.llm,
5141
- this.deploymentName,
5520
+ this.model,
5142
5521
  this.mcpClient,
5143
5522
  this.feedbackSystem
5144
5523
  );
@@ -5147,9 +5526,10 @@ var Agent = class {
5147
5526
  mcpClient: this.mcpClient,
5148
5527
  toolInvoker: this.toolInvoker,
5149
5528
  llm: this.llm,
5150
- deploymentName: this.deploymentName,
5529
+ deploymentName: this.model,
5151
5530
  projectRoot: process.cwd()
5152
5531
  });
5532
+ this.routeManager = new RouteManager(this.subAgents, this.core);
5153
5533
  }
5154
5534
  async initialize() {
5155
5535
  await this.core.initialize();
@@ -5163,10 +5543,9 @@ var Agent = class {
5163
5543
  async processTurn(userInput) {
5164
5544
  const inputText = String(userInput.content || "").trim();
5165
5545
  if (inputText === "/init" || inputText.startsWith("/init ")) {
5166
- await this.dispatchToSubAgent({ command: "/init", content: inputText });
5167
- return;
5546
+ this.routeManager.registerRoute("/init", this.dispatchToSubAgent);
5168
5547
  }
5169
- await this.core.processTurn({ content: inputText });
5548
+ await this.routeManager.handleRoute({ content: inputText });
5170
5549
  }
5171
5550
  async handleToolResponse(decisionData) {
5172
5551
  await this.core.handleToolResponse(decisionData);
@@ -5265,12 +5644,12 @@ var renderShellCommand2 = ({ args }) => {
5265
5644
  };
5266
5645
  var renderLsTool2 = ({ args }) => {
5267
5646
  const parsed = parseArgs(args);
5268
- const path17 = parsed.directory_path || ".";
5647
+ const path18 = parsed.directory_path || ".";
5269
5648
  return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
5270
5649
  /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "ls" }),
5271
5650
  /* @__PURE__ */ jsxs8(Text8, { children: [
5272
5651
  " ",
5273
- path17
5652
+ path18
5274
5653
  ] })
5275
5654
  ] });
5276
5655
  };
@@ -5401,7 +5780,7 @@ var renderFindByName = ({ args }) => {
5401
5780
  var renderGrepSearch = ({ args }) => {
5402
5781
  const parsed = parseArgs(args);
5403
5782
  const query = parsed.query || "";
5404
- const path17 = parsed.path || ".";
5783
+ const path18 = parsed.path || ".";
5405
5784
  return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
5406
5785
  /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "grep" }),
5407
5786
  /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
@@ -5411,7 +5790,7 @@ var renderGrepSearch = ({ args }) => {
5411
5790
  ] }),
5412
5791
  /* @__PURE__ */ jsxs8(Text8, { children: [
5413
5792
  " ",
5414
- path17
5793
+ path18
5415
5794
  ] })
5416
5795
  ] });
5417
5796
  };
@@ -5479,6 +5858,17 @@ var renderSearchWeb = ({ args }) => {
5479
5858
  ] })
5480
5859
  ] });
5481
5860
  };
5861
+ var renderLoadSkill = ({ args }) => {
5862
+ const parsed = parseArgs(args);
5863
+ const skillName = parsed.skill_name || "[no skill]";
5864
+ return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
5865
+ /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "load skill" }),
5866
+ /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
5867
+ " ",
5868
+ skillName
5869
+ ] })
5870
+ ] });
5871
+ };
5482
5872
  var renderGeneric2 = ({ toolName, args }) => {
5483
5873
  const parsed = parseArgs(args);
5484
5874
  const keys = Object.keys(parsed).slice(0, 2);
@@ -5503,13 +5893,14 @@ var ToolRenderDisplay = {
5503
5893
  view_file_outline: renderViewFileOutline,
5504
5894
  command_status: renderCommandStatus,
5505
5895
  task_boundary: renderTaskBoundary,
5506
- search_web: renderSearchWeb
5896
+ search_web: renderSearchWeb,
5897
+ load_skill: renderLoadSkill
5507
5898
  };
5508
5899
 
5509
5900
  // src/app/ui/components/ToolCallDisplay.tsx
5510
5901
  import { jsx as jsx9 } from "react/jsx-runtime";
5511
5902
  var ToolCallDisplayComponent = ({ toolName, args, preview }) => {
5512
- if (toolName.includes("message_notify_user") || toolName.includes("agent_end_turn")) {
5903
+ if (toolName.includes("message")) {
5513
5904
  return null;
5514
5905
  }
5515
5906
  const Renderer = ToolRenderDisplay[toolName] || ((props2) => renderGeneric2({ ...props2, toolName }));
@@ -5710,11 +6101,11 @@ var truncateLines = (text, max) => {
5710
6101
  };
5711
6102
  };
5712
6103
  var ToolResultDisplayComponent = ({ toolName, result }) => {
5713
- if (toolName.includes("agent_end_turn") || toolName.includes("task_boundary")) {
6104
+ if (toolName.includes("task_boundary")) {
5714
6105
  return null;
5715
6106
  }
5716
6107
  const parsed = parseResult(result);
5717
- if (toolName.includes("message_notify_user")) {
6108
+ if (toolName.includes("message")) {
5718
6109
  const body = parsed?.content?.body || parsed?.body || parsed?.message;
5719
6110
  if (!body) return null;
5720
6111
  return /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(MarkdownRenderer, { markdown: body }) });
@@ -5797,6 +6188,23 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
5797
6188
  ] })
5798
6189
  ] });
5799
6190
  }
6191
+ if (toolName.includes("load_skill") && parsed) {
6192
+ if (!parsed.success) {
6193
+ return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "red", children: [
6194
+ "\u2717 Skill not found: ",
6195
+ parsed.message
6196
+ ] }) });
6197
+ }
6198
+ return /* @__PURE__ */ jsxs10(Box11, { paddingLeft: 3, marginBottom: 2, children: [
6199
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: "green", children: "Skill loaded: " }),
6200
+ /* @__PURE__ */ jsx11(Text10, { color: "green", bold: true, children: parsed.skill_name }),
6201
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
6202
+ " \u2014 ",
6203
+ parsed.description?.slice(0, 50),
6204
+ parsed.description?.length > 100 ? "..." : ""
6205
+ ] })
6206
+ ] });
6207
+ }
5800
6208
  if (toolName.includes("todo")) {
5801
6209
  return null;
5802
6210
  }
@@ -5998,16 +6406,16 @@ var SlashCommands_default = SlashCommands;
5998
6406
  // src/app/agent/utils/update_check.ts
5999
6407
  import updateNotifier from "update-notifier";
6000
6408
  import { fileURLToPath as fileURLToPath3 } from "url";
6001
- import path16 from "path";
6002
- import fs12 from "fs";
6409
+ import path17 from "path";
6410
+ import fs13 from "fs";
6003
6411
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
6004
6412
  function findBlumaPackageJson(startDir) {
6005
6413
  let dir = startDir;
6006
6414
  for (let i = 0; i < 10; i++) {
6007
- const candidate = path16.join(dir, "package.json");
6008
- if (fs12.existsSync(candidate)) {
6415
+ const candidate = path17.join(dir, "package.json");
6416
+ if (fs13.existsSync(candidate)) {
6009
6417
  try {
6010
- const raw = fs12.readFileSync(candidate, "utf8");
6418
+ const raw = fs13.readFileSync(candidate, "utf8");
6011
6419
  const parsed = JSON.parse(raw);
6012
6420
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
6013
6421
  return { name: parsed.name, version: parsed.version };
@@ -6015,7 +6423,7 @@ function findBlumaPackageJson(startDir) {
6015
6423
  } catch {
6016
6424
  }
6017
6425
  }
6018
- const parent = path16.dirname(dir);
6426
+ const parent = path17.dirname(dir);
6019
6427
  if (parent === dir) break;
6020
6428
  dir = parent;
6021
6429
  }
@@ -6028,12 +6436,12 @@ async function checkForUpdates() {
6028
6436
  }
6029
6437
  const binPath = process.argv?.[1];
6030
6438
  let pkg = null;
6031
- if (binPath && fs12.existsSync(binPath)) {
6032
- pkg = findBlumaPackageJson(path16.dirname(binPath));
6439
+ if (binPath && fs13.existsSync(binPath)) {
6440
+ pkg = findBlumaPackageJson(path17.dirname(binPath));
6033
6441
  }
6034
6442
  if (!pkg) {
6035
6443
  const __filename = fileURLToPath3(import.meta.url);
6036
- const __dirname = path16.dirname(__filename);
6444
+ const __dirname = path17.dirname(__filename);
6037
6445
  pkg = findBlumaPackageJson(__dirname);
6038
6446
  }
6039
6447
  if (!pkg) {
@@ -6079,11 +6487,11 @@ function parseUpdateMessage(msg) {
6079
6487
  hint: hintLine || void 0
6080
6488
  };
6081
6489
  }
6082
- var UpdateNotice = ({ message }) => {
6083
- const { name, current, latest, hint } = parseUpdateMessage(message);
6490
+ var UpdateNotice = ({ message: message2 }) => {
6491
+ const { name, current, latest, hint } = parseUpdateMessage(message2);
6084
6492
  return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginBottom: 1, children: [
6085
6493
  /* @__PURE__ */ jsx13(Text12, { color: "yellow", bold: true, children: "Update Available" }),
6086
- name && current && latest ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: `${name}: ${current} \u2192 ${latest}` }) : /* @__PURE__ */ jsx13(Text12, { color: "gray", children: message }),
6494
+ name && current && latest ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: `${name}: ${current} \u2192 ${latest}` }) : /* @__PURE__ */ jsx13(Text12, { color: "gray", children: message2 }),
6087
6495
  hint ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: hint }) : null
6088
6496
  ] });
6089
6497
  };
@@ -6092,13 +6500,13 @@ var UpdateNotice_default = UpdateNotice;
6092
6500
  // src/app/ui/components/ErrorMessage.tsx
6093
6501
  import { Box as Box14, Text as Text13 } from "ink";
6094
6502
  import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
6095
- var ErrorMessage = ({ message, details, hint }) => {
6503
+ var ErrorMessage = ({ message: message2, details, hint }) => {
6096
6504
  return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
6097
6505
  /* @__PURE__ */ jsxs13(Box14, { children: [
6098
6506
  /* @__PURE__ */ jsx14(Text13, { color: "red", children: "\u2717" }),
6099
6507
  /* @__PURE__ */ jsx14(Text13, { color: "red", bold: true, children: " Error" })
6100
6508
  ] }),
6101
- /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { color: "red", children: message }) }),
6509
+ /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { color: "red", children: message2 }) }),
6102
6510
  details && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: details }) }),
6103
6511
  hint && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
6104
6512
  "hint: ",
@@ -6134,7 +6542,7 @@ var ReasoningDisplay = memo9(ReasoningDisplayComponent);
6134
6542
  import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
6135
6543
  var SAFE_AUTO_APPROVE_TOOLS = [
6136
6544
  // Comunicação/UI
6137
- "message_notify_user",
6545
+ "message",
6138
6546
  "agent_end_turn",
6139
6547
  "todo",
6140
6548
  "task_boundary",
@@ -6219,6 +6627,61 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
6219
6627
  ]);
6220
6628
  return;
6221
6629
  }
6630
+ if (text.startsWith("!")) {
6631
+ const command = text.slice(1).trim();
6632
+ if (!command) {
6633
+ setIsProcessing(false);
6634
+ return;
6635
+ }
6636
+ setHistory((prev) => [
6637
+ ...prev,
6638
+ {
6639
+ id: prev.length,
6640
+ component: /* @__PURE__ */ jsxs15(Box16, { marginBottom: 1, children: [
6641
+ /* @__PURE__ */ jsx16(Text15, { color: "white", bold: true, children: "$ " }),
6642
+ /* @__PURE__ */ jsx16(Text15, { color: "white", children: command })
6643
+ ] })
6644
+ }
6645
+ ]);
6646
+ Promise.resolve().then(() => (init_async_command(), async_command_exports)).then(async ({ runCommandAsync: runCommandAsync2 }) => {
6647
+ try {
6648
+ const result = await runCommandAsync2({ command, cwd: workdir });
6649
+ if (result.success && result.command_id) {
6650
+ const contextMessage = `[User executed shell command directly]
6651
+ Command: ${command}
6652
+ Command ID: ${result.command_id}
6653
+
6654
+ Please use command_status to check the result and report back to the user.`;
6655
+ agentInstance.current?.processTurn({ content: contextMessage });
6656
+ } else {
6657
+ setHistory((prev) => [
6658
+ ...prev,
6659
+ {
6660
+ id: prev.length,
6661
+ component: /* @__PURE__ */ jsxs15(Text15, { color: "red", children: [
6662
+ "Failed to execute: ",
6663
+ result.error || result.message
6664
+ ] })
6665
+ }
6666
+ ]);
6667
+ setIsProcessing(false);
6668
+ }
6669
+ } catch (err) {
6670
+ setHistory((prev) => [
6671
+ ...prev,
6672
+ {
6673
+ id: prev.length,
6674
+ component: /* @__PURE__ */ jsxs15(Text15, { color: "red", children: [
6675
+ "Error: ",
6676
+ err.message
6677
+ ] })
6678
+ }
6679
+ ]);
6680
+ setIsProcessing(false);
6681
+ }
6682
+ });
6683
+ return;
6684
+ }
6222
6685
  setIsProcessing(true);
6223
6686
  const displayText = text.length > 1e4 ? text.substring(0, 1e4) + "..." : text;
6224
6687
  setHistory((prev) => [