@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/config/native_tools.json +26 -10
- package/dist/main.js +2079 -1616
- package/package.json +2 -2
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
|
-
|
|
8
|
-
|
|
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/
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
315
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
351
|
-
|
|
201
|
+
if (stderr.length > output_limit) {
|
|
202
|
+
stderr = "..." + stderr.substring(stderr.length - output_limit);
|
|
203
|
+
truncated = true;
|
|
352
204
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
376
|
-
return
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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 (
|
|
435
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
if (
|
|
479
|
-
|
|
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
|
-
|
|
482
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
|
1319
|
-
import
|
|
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
|
|
1669
|
-
const {
|
|
1670
|
-
const
|
|
1671
|
-
type: "
|
|
1672
|
-
|
|
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
|
-
|
|
1676
|
-
body: message
|
|
1844
|
+
body: content
|
|
1677
1845
|
},
|
|
1678
1846
|
success: true,
|
|
1679
1847
|
delivered: true
|
|
1680
1848
|
};
|
|
1681
|
-
return Promise.resolve(
|
|
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,
|
|
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
|
-
|
|
2637
|
-
|
|
2249
|
+
let entries;
|
|
2250
|
+
try {
|
|
2251
|
+
entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
return;
|
|
2638
2254
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
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
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
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
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2480
|
+
function createSearchPattern(query, isRegex, caseInsensitive) {
|
|
2703
2481
|
try {
|
|
2704
|
-
const
|
|
2705
|
-
if (
|
|
2706
|
-
return
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
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
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
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
|
|
2534
|
+
return fileMatches;
|
|
2815
2535
|
}
|
|
2816
|
-
async function
|
|
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
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
if (
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
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
|
|
2560
|
+
async function grepSearch(args) {
|
|
2927
2561
|
try {
|
|
2928
2562
|
const {
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
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 (!
|
|
2571
|
+
if (!query || typeof query !== "string") {
|
|
2934
2572
|
return {
|
|
2935
2573
|
success: false,
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
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
|
-
|
|
2942
|
-
if (!entry) {
|
|
2584
|
+
if (!searchPath) {
|
|
2943
2585
|
return {
|
|
2944
2586
|
success: false,
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
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
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
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
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
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
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
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
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
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
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
const
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
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
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
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
|
-
|
|
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 {
|
|
3032
|
-
|
|
3033
|
-
if (!entry) {
|
|
2872
|
+
const { file_path } = args;
|
|
2873
|
+
if (!file_path || typeof file_path !== "string") {
|
|
3034
2874
|
return {
|
|
3035
2875
|
success: false,
|
|
3036
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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
|
|
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
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
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
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
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
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
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
|
-
|
|
3832
|
-
|
|
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 (
|
|
3868
|
-
if (
|
|
3869
|
-
if (
|
|
3870
|
-
if (
|
|
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 =
|
|
3953
|
+
const files = fs12.readdirSync(dir);
|
|
3879
3954
|
if (files.includes("package.json")) {
|
|
3880
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
3900
|
-
if (
|
|
3901
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
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 =
|
|
3919
|
-
if (
|
|
3920
|
-
const pkg = JSON.parse(
|
|
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
|
|
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
|
|
4000
|
-
|
|
4074
|
+
### Testing Philosophy:
|
|
4075
|
+
Run tests when modifying code. Use the \`testing\` skill for detailed best practices.
|
|
4001
4076
|
|
|
4002
|
-
**
|
|
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
|
-
-
|
|
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]
|
|
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]
|
|
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
|
-
|
|
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
|
-
-
|
|
4168
|
-
-
|
|
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
|
-
**
|
|
4231
|
+
**CRITICAL: After asking questions, ALWAYS use result!**
|
|
4232
|
+
|
|
4233
|
+
**Asking question (result):**
|
|
4172
4234
|
\`\`\`
|
|
4173
|
-
|
|
4174
|
-
|
|
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
|
-
|
|
4186
|
-
|
|
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
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
</
|
|
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:
|
|
4240
|
-
os_version:
|
|
4241
|
-
architecture:
|
|
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:
|
|
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
|
-
|
|
4257
|
-
(
|
|
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 =
|
|
4264
|
-
return
|
|
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
|
|
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
|
|
4478
|
-
|
|
4479
|
-
|
|
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 =
|
|
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
|
|
4533
|
-
|
|
4534
|
-
if (
|
|
4535
|
-
const reasoningText =
|
|
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(
|
|
4542
|
-
if (
|
|
4543
|
-
const validToolCalls =
|
|
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
|
-
"
|
|
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 (
|
|
4577
|
-
this.eventBus.emit("backend_message", { type: "assistant_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:
|
|
4947
|
+
details: { violationContent: message2.content }
|
|
4581
4948
|
});
|
|
4582
|
-
this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.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
|
-
|
|
4966
|
+
import OpenAI from "openai";
|
|
4967
|
+
var LLMService = class {
|
|
4600
4968
|
client;
|
|
4601
|
-
|
|
4602
|
-
|
|
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
|
|
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 '
|
|
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 (
|
|
4770
|
-
- Before writing BluMa.md, propose structure via
|
|
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 (
|
|
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:
|
|
4794
|
-
os_version:
|
|
4795
|
-
architecture:
|
|
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:
|
|
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
|
|
4890
|
-
this.history.push(
|
|
4891
|
-
if (
|
|
4892
|
-
await this._handleToolExecution({ type: "user_decision_execute", tool_calls:
|
|
4893
|
-
const lastToolName =
|
|
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 (
|
|
4898
|
-
this.emitEvent("assistant_message", { content:
|
|
4899
|
-
this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", 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 =
|
|
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
|
-
|
|
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
|
|
5074
|
-
const
|
|
5075
|
-
|
|
5076
|
-
|
|
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
|
-
|
|
5087
|
-
|
|
5088
|
-
"
|
|
5089
|
-
|
|
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
|
-
|
|
5098
|
-
" source ~/.bashrc
|
|
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
|
|
5107
|
-
|
|
5108
|
-
`Configure
|
|
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(
|
|
5120
|
-
}
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
5167
|
-
return;
|
|
5546
|
+
this.routeManager.registerRoute("/init", this.dispatchToSubAgent);
|
|
5168
5547
|
}
|
|
5169
|
-
await this.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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("
|
|
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("
|
|
6104
|
+
if (toolName.includes("task_boundary")) {
|
|
5714
6105
|
return null;
|
|
5715
6106
|
}
|
|
5716
6107
|
const parsed = parseResult(result);
|
|
5717
|
-
if (toolName.includes("
|
|
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
|
|
6002
|
-
import
|
|
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 =
|
|
6008
|
-
if (
|
|
6415
|
+
const candidate = path17.join(dir, "package.json");
|
|
6416
|
+
if (fs13.existsSync(candidate)) {
|
|
6009
6417
|
try {
|
|
6010
|
-
const raw =
|
|
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 =
|
|
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 &&
|
|
6032
|
-
pkg = findBlumaPackageJson(
|
|
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 =
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
-
"
|
|
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) => [
|