@nomad-e/bluma-cli 0.0.107 → 0.0.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +2293 -2206
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1,1266 +1,1588 @@
|
|
|
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
|
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
6
11
|
|
|
7
|
-
// src/app/agent/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
console.error("Error normalizing tool call:", error, call);
|
|
98
|
-
return null;
|
|
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);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
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
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const pattern of INTERACTIVE_PATTERNS) {
|
|
39
|
+
if (pattern.test(trimmed)) {
|
|
40
|
+
return "Interactive commands are not supported in async mode";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
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
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const dangerReason = isDangerousCommand(command);
|
|
59
|
+
if (dangerReason) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: dangerReason
|
|
63
|
+
};
|
|
64
|
+
}
|
|
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];
|
|
75
|
+
}
|
|
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
99
|
}
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
}
|
|
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;
|
|
127
109
|
}
|
|
128
|
-
return results;
|
|
129
110
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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");
|
|
146
132
|
}
|
|
147
|
-
}
|
|
133
|
+
}, 2e3);
|
|
134
|
+
entry.status = "timeout";
|
|
135
|
+
entry.stderr += `
|
|
136
|
+
Command timed out after ${timeout} seconds`;
|
|
148
137
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
138
|
+
}, timeout * 1e3);
|
|
139
|
+
}
|
|
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}`
|
|
157
152
|
};
|
|
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
|
-
] });
|
|
232
|
-
};
|
|
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
|
-
|
|
238
|
-
// src/app/ui/utils/useSimpleInputBuffer.ts
|
|
239
|
-
import { useReducer, useRef, useCallback, useEffect } from "react";
|
|
240
|
-
import { useInput } from "ink";
|
|
241
|
-
function getLineStart(text, cursorPos) {
|
|
242
|
-
let pos = cursorPos - 1;
|
|
243
|
-
while (pos >= 0 && text[pos] !== "\n") {
|
|
244
|
-
pos--;
|
|
245
|
-
}
|
|
246
|
-
return pos + 1;
|
|
247
|
-
}
|
|
248
|
-
function getLineEnd(text, cursorPos) {
|
|
249
|
-
let pos = cursorPos;
|
|
250
|
-
while (pos < text.length && text[pos] !== "\n") {
|
|
251
|
-
pos++;
|
|
252
|
-
}
|
|
253
|
-
return pos;
|
|
254
|
-
}
|
|
255
|
-
function moveToAdjacentLine(text, cursorPos, direction) {
|
|
256
|
-
const currentLineStart = getLineStart(text, cursorPos);
|
|
257
|
-
const currentLineEnd = getLineEnd(text, cursorPos);
|
|
258
|
-
const column = cursorPos - currentLineStart;
|
|
259
|
-
if (direction === "up") {
|
|
260
|
-
if (currentLineStart === 0) return cursorPos;
|
|
261
|
-
const prevLineEnd = currentLineStart - 1;
|
|
262
|
-
const prevLineStart = getLineStart(text, prevLineEnd);
|
|
263
|
-
const prevLineLength = prevLineEnd - prevLineStart;
|
|
264
|
-
return prevLineStart + Math.min(column, prevLineLength);
|
|
265
|
-
} else {
|
|
266
|
-
if (currentLineEnd === text.length) return cursorPos;
|
|
267
|
-
const nextLineStart = currentLineEnd + 1;
|
|
268
|
-
const nextLineEnd = getLineEnd(text, nextLineStart);
|
|
269
|
-
const nextLineLength = nextLineEnd - nextLineStart;
|
|
270
|
-
return nextLineStart + Math.min(column, nextLineLength);
|
|
271
153
|
}
|
|
272
154
|
}
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
+
};
|
|
280
169
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (newText === state.text && newCursorPosition === state.cursorPosition) {
|
|
290
|
-
return state;
|
|
291
|
-
}
|
|
292
|
-
return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
170
|
+
const entry = runningCommands.get(command_id);
|
|
171
|
+
if (!entry) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
command_id,
|
|
175
|
+
status: "not_found",
|
|
176
|
+
error: `Command with id "${command_id}" not found. It may have expired or never existed.`
|
|
177
|
+
};
|
|
293
178
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
179
|
+
if (wait_seconds > 0 && entry.status === "running") {
|
|
180
|
+
await new Promise((resolve) => {
|
|
181
|
+
const checkInterval = setInterval(() => {
|
|
182
|
+
if (entry.status !== "running") {
|
|
183
|
+
clearInterval(checkInterval);
|
|
184
|
+
resolve();
|
|
185
|
+
}
|
|
186
|
+
}, 100);
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
clearInterval(checkInterval);
|
|
189
|
+
resolve();
|
|
190
|
+
}, wait_seconds * 1e3);
|
|
191
|
+
});
|
|
299
192
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
} else if (action.direction === "up") {
|
|
307
|
-
newCursorPosition = moveToAdjacentLine(state.text, state.cursorPosition, "up");
|
|
308
|
-
} else if (action.direction === "down") {
|
|
309
|
-
newCursorPosition = moveToAdjacentLine(state.text, state.cursorPosition, "down");
|
|
310
|
-
}
|
|
311
|
-
if (newCursorPosition === state.cursorPosition) {
|
|
312
|
-
return state;
|
|
313
|
-
}
|
|
314
|
-
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
315
|
-
return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
193
|
+
let stdout = entry.stdout;
|
|
194
|
+
let stderr = entry.stderr;
|
|
195
|
+
let truncated = false;
|
|
196
|
+
if (stdout.length > output_limit) {
|
|
197
|
+
stdout = "..." + stdout.substring(stdout.length - output_limit);
|
|
198
|
+
truncated = true;
|
|
316
199
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
return state;
|
|
321
|
-
}
|
|
322
|
-
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
323
|
-
return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
200
|
+
if (stderr.length > output_limit) {
|
|
201
|
+
stderr = "..." + stderr.substring(stderr.length - output_limit);
|
|
202
|
+
truncated = true;
|
|
324
203
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
204
|
+
const duration = entry.endTime ? (entry.endTime - entry.startTime) / 1e3 : (Date.now() - entry.startTime) / 1e3;
|
|
205
|
+
return {
|
|
206
|
+
success: true,
|
|
207
|
+
command_id,
|
|
208
|
+
status: entry.status,
|
|
209
|
+
stdout: stdout || void 0,
|
|
210
|
+
stderr: stderr || void 0,
|
|
211
|
+
exit_code: entry.exitCode,
|
|
212
|
+
duration_seconds: Math.round(duration * 10) / 10,
|
|
213
|
+
truncated
|
|
214
|
+
};
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
command_id: args.command_id || "",
|
|
219
|
+
status: "error",
|
|
220
|
+
error: `Unexpected error: ${error.message}`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function sendCommandInput(args) {
|
|
225
|
+
try {
|
|
226
|
+
const { command_id, input } = args;
|
|
227
|
+
if (!command_id || input === void 0) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
error: "command_id and input are required"
|
|
231
|
+
};
|
|
332
232
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
return state;
|
|
341
|
-
}
|
|
342
|
-
case "DELETE": {
|
|
343
|
-
if (state.cursorPosition < state.text.length) {
|
|
344
|
-
const newText = state.text.slice(0, state.cursorPosition) + state.text.slice(state.cursorPosition + 1);
|
|
345
|
-
const newViewStart = adjustView(state.cursorPosition, state.viewStart);
|
|
346
|
-
return { text: newText, cursorPosition: state.cursorPosition, viewStart: newViewStart };
|
|
347
|
-
}
|
|
348
|
-
return state;
|
|
349
|
-
}
|
|
350
|
-
case "SUBMIT": {
|
|
351
|
-
return { text: "", cursorPosition: 0, viewStart: 0 };
|
|
352
|
-
}
|
|
353
|
-
case "SET": {
|
|
354
|
-
const t = action.payload.text.replace(/(\r\n|\r)/gm, "\n");
|
|
355
|
-
let newCursorPosition;
|
|
356
|
-
if (typeof action.payload.cursorPosition === "number") {
|
|
357
|
-
newCursorPosition = Math.min(action.payload.cursorPosition, t.length);
|
|
358
|
-
} else if (action.payload.moveCursorToEnd ?? true) {
|
|
359
|
-
newCursorPosition = t.length;
|
|
360
|
-
} else {
|
|
361
|
-
newCursorPosition = Math.min(state.cursorPosition, t.length);
|
|
362
|
-
}
|
|
363
|
-
const newText = t;
|
|
364
|
-
const newViewStart = adjustView(newCursorPosition, 0);
|
|
365
|
-
return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
233
|
+
const entry = runningCommands.get(command_id);
|
|
234
|
+
if (!entry) {
|
|
235
|
+
return {
|
|
236
|
+
success: false,
|
|
237
|
+
error: `Command with id "${command_id}" not found`
|
|
238
|
+
};
|
|
366
239
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
373
|
-
return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
240
|
+
if (entry.status !== "running" || !entry.process) {
|
|
241
|
+
return {
|
|
242
|
+
success: false,
|
|
243
|
+
error: `Command is not running (status: ${entry.status})`
|
|
244
|
+
};
|
|
374
245
|
}
|
|
375
|
-
|
|
376
|
-
|
|
246
|
+
entry.process.stdin?.write(input);
|
|
247
|
+
return {
|
|
248
|
+
success: true,
|
|
249
|
+
message: `Sent ${input.length} characters to command ${command_id}`
|
|
250
|
+
};
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return {
|
|
253
|
+
success: false,
|
|
254
|
+
error: `Failed to send input: ${error.message}`
|
|
255
|
+
};
|
|
377
256
|
}
|
|
378
257
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const buffered = inputBuffer.current;
|
|
389
|
-
inputBuffer.current = "";
|
|
390
|
-
dispatch({ type: "INPUT", payload: buffered });
|
|
258
|
+
async function killCommand(args) {
|
|
259
|
+
try {
|
|
260
|
+
const { command_id } = args;
|
|
261
|
+
const entry = runningCommands.get(command_id);
|
|
262
|
+
if (!entry) {
|
|
263
|
+
return {
|
|
264
|
+
success: false,
|
|
265
|
+
error: `Command with id "${command_id}" not found`
|
|
266
|
+
};
|
|
391
267
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
268
|
+
if (entry.status !== "running" || !entry.process) {
|
|
269
|
+
return {
|
|
270
|
+
success: false,
|
|
271
|
+
error: `Command is not running (status: ${entry.status})`
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
entry.process.kill("SIGTERM");
|
|
275
|
+
entry.status = "killed";
|
|
276
|
+
entry.endTime = Date.now();
|
|
277
|
+
return {
|
|
278
|
+
success: true,
|
|
279
|
+
message: `Command ${command_id} killed`
|
|
399
280
|
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
281
|
+
} catch (error) {
|
|
282
|
+
return {
|
|
283
|
+
success: false,
|
|
284
|
+
error: `Failed to kill command: ${error.message}`
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
var runningCommands, MAX_OUTPUT_SIZE, MAX_STORED_COMMANDS, OUTPUT_TRUNCATION_MSG, DANGEROUS_PATTERNS, INTERACTIVE_PATTERNS;
|
|
289
|
+
var init_async_command = __esm({
|
|
290
|
+
"src/app/agent/tools/natives/async_command.ts"() {
|
|
291
|
+
"use strict";
|
|
292
|
+
runningCommands = /* @__PURE__ */ new Map();
|
|
293
|
+
MAX_OUTPUT_SIZE = 3e4;
|
|
294
|
+
MAX_STORED_COMMANDS = 50;
|
|
295
|
+
OUTPUT_TRUNCATION_MSG = "\n[OUTPUT TRUNCATED - 30KB/200 lines max]";
|
|
296
|
+
DANGEROUS_PATTERNS = [
|
|
297
|
+
// Elevação de privilégios
|
|
298
|
+
/^sudo\s+/i,
|
|
299
|
+
/^doas\s+/i,
|
|
300
|
+
/^su\s+/i,
|
|
301
|
+
/^pkexec\s+/i,
|
|
302
|
+
/\|\s*sudo\s+/i,
|
|
303
|
+
/;\s*sudo\s+/i,
|
|
304
|
+
/&&\s*sudo\s+/i,
|
|
305
|
+
// Comandos destrutivos
|
|
306
|
+
/\brm\s+(-[rf]+\s+)*[\/~]/i,
|
|
307
|
+
/\brm\s+-[rf]*\s+\*/i,
|
|
308
|
+
/\bchmod\s+(777|666)\s+\//i,
|
|
309
|
+
/\bdd\s+.*of=\/dev\/(sd|hd|nvme)/i,
|
|
310
|
+
/\bmkfs\./i,
|
|
311
|
+
// Fork bombs
|
|
312
|
+
/:\(\)\s*\{\s*:\|:&\s*\}\s*;:/,
|
|
313
|
+
// Remote code exec
|
|
314
|
+
/\bcurl\s+.*\|\s*(ba)?sh/i,
|
|
315
|
+
/\bwget\s+.*\|\s*(ba)?sh/i
|
|
316
|
+
];
|
|
317
|
+
INTERACTIVE_PATTERNS = [
|
|
318
|
+
/^(vim|vi|nano|emacs|pico)\s*/i,
|
|
319
|
+
/^(less|more|most)\s*/i,
|
|
320
|
+
/^(top|htop|btop|atop|nmon)\s*/i,
|
|
321
|
+
/^(ssh|telnet|ftp|sftp)\s+/i,
|
|
322
|
+
/^(mysql|psql|redis-cli|mongo|mongosh)\s*$/i,
|
|
323
|
+
/^(python|python3|node|ruby|irb|lua)\s*$/i,
|
|
324
|
+
/^(gdb|lldb)\s*/i
|
|
325
|
+
];
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// src/app/agent/core/llm/tool_call_normalizer.ts
|
|
330
|
+
import { randomUUID } from "crypto";
|
|
331
|
+
var ToolCallNormalizer;
|
|
332
|
+
var init_tool_call_normalizer = __esm({
|
|
333
|
+
"src/app/agent/core/llm/tool_call_normalizer.ts"() {
|
|
334
|
+
"use strict";
|
|
335
|
+
ToolCallNormalizer = class {
|
|
336
|
+
/**
|
|
337
|
+
* Normaliza a mensagem do assistant, convertendo diferentes formatos de tool calls
|
|
338
|
+
*/
|
|
339
|
+
static normalizeAssistantMessage(message) {
|
|
340
|
+
if (message.tool_calls && this.isOpenAIFormat(message.tool_calls)) {
|
|
341
|
+
return message;
|
|
416
342
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
343
|
+
const toolCalls = this.extractToolCalls(message);
|
|
344
|
+
if (toolCalls.length > 0) {
|
|
345
|
+
return {
|
|
346
|
+
role: message.role || "assistant",
|
|
347
|
+
content: message.content || null,
|
|
348
|
+
tool_calls: toolCalls
|
|
349
|
+
};
|
|
423
350
|
}
|
|
424
|
-
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
if (inputBuffer.current.length > 0 && (key.ctrl || key.meta || key.escape || key.return || key.leftArrow || key.rightArrow || key.upArrow || key.downArrow || key.tab || key.shift)) {
|
|
428
|
-
flushInputBuffer();
|
|
351
|
+
return message;
|
|
429
352
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Verifica se já está no formato OpenAI
|
|
355
|
+
*/
|
|
356
|
+
static isOpenAIFormat(toolCalls) {
|
|
357
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0) return false;
|
|
358
|
+
const firstCall = toolCalls[0];
|
|
359
|
+
return typeof firstCall.id === "string" && firstCall.type === "function" && typeof firstCall.function?.name === "string" && typeof firstCall.function?.arguments === "string";
|
|
433
360
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
361
|
+
/**
|
|
362
|
+
* Extrai tool calls de diversos formatos possíveis
|
|
363
|
+
*/
|
|
364
|
+
static extractToolCalls(message) {
|
|
365
|
+
const results = [];
|
|
366
|
+
if (message.tool_calls && Array.isArray(message.tool_calls)) {
|
|
367
|
+
for (const call of message.tool_calls) {
|
|
368
|
+
const normalized = this.normalizeToolCall(call);
|
|
369
|
+
if (normalized) results.push(normalized);
|
|
439
370
|
}
|
|
440
|
-
return;
|
|
441
371
|
}
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
|
|
372
|
+
if (typeof message.content === "string" && message.content.trim()) {
|
|
373
|
+
const extracted = this.extractFromContent(message.content);
|
|
374
|
+
results.push(...extracted);
|
|
445
375
|
}
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
|
|
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);
|
|
376
|
+
if (message.function_call) {
|
|
377
|
+
const normalized = this.normalizeToolCall(message.function_call);
|
|
378
|
+
if (normalized) results.push(normalized);
|
|
455
379
|
}
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
if (key.return && key.shift) {
|
|
459
|
-
dispatch({ type: "NEWLINE" });
|
|
460
|
-
return;
|
|
380
|
+
return results;
|
|
461
381
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Normaliza um único tool call para o formato OpenAI
|
|
384
|
+
*/
|
|
385
|
+
static normalizeToolCall(call) {
|
|
386
|
+
try {
|
|
387
|
+
if (call.id && call.function?.name) {
|
|
388
|
+
return {
|
|
389
|
+
id: call.id,
|
|
390
|
+
type: "function",
|
|
391
|
+
function: {
|
|
392
|
+
name: call.function.name,
|
|
393
|
+
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments)
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
if (call.name) {
|
|
398
|
+
return {
|
|
399
|
+
id: call.id || randomUUID(),
|
|
400
|
+
type: "function",
|
|
401
|
+
function: {
|
|
402
|
+
name: call.name,
|
|
403
|
+
arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments || {})
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (call.function && typeof call.function === "object") {
|
|
408
|
+
return {
|
|
409
|
+
id: call.id || randomUUID(),
|
|
410
|
+
type: "function",
|
|
411
|
+
function: {
|
|
412
|
+
name: call.function.name,
|
|
413
|
+
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments || {})
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error("Error normalizing tool call:", error, call);
|
|
420
|
+
return null;
|
|
467
421
|
}
|
|
468
|
-
if (state.text.trim().length > 0) {
|
|
469
|
-
onSubmit(state.text);
|
|
470
|
-
dispatch({ type: "SUBMIT" });
|
|
471
|
-
}
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
|
|
475
|
-
if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
|
|
476
|
-
if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
|
|
477
|
-
if (key.downArrow) return dispatch({ type: "MOVE_CURSOR", direction: "down" });
|
|
478
|
-
if (key.ctrl && input === "a") {
|
|
479
|
-
return dispatch({ type: "MOVE_LINE_START" });
|
|
480
|
-
}
|
|
481
|
-
if (key.ctrl && input === "e") {
|
|
482
|
-
return dispatch({ type: "MOVE_LINE_END" });
|
|
483
|
-
}
|
|
484
|
-
if (key.ctrl || key.meta || key.tab) return;
|
|
485
|
-
inputBuffer.current += input;
|
|
486
|
-
if (!flushScheduled.current) {
|
|
487
|
-
flushScheduled.current = true;
|
|
488
|
-
queueMicrotask(flushInputBuffer);
|
|
489
422
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
423
|
+
/**
|
|
424
|
+
* Extrai tool calls do content (pode estar em markdown, JSON, etc)
|
|
425
|
+
*/
|
|
426
|
+
static extractFromContent(content) {
|
|
427
|
+
const results = [];
|
|
428
|
+
const cleanContent = content.replace(/```(?:json)?\s*([\s\S]*?)```/g, "$1");
|
|
429
|
+
const jsonMatches = this.extractJsonObjects(cleanContent);
|
|
430
|
+
for (const jsonStr of jsonMatches) {
|
|
431
|
+
try {
|
|
432
|
+
const parsed = JSON.parse(jsonStr);
|
|
433
|
+
if (Array.isArray(parsed)) {
|
|
434
|
+
for (const call of parsed) {
|
|
435
|
+
const normalized = this.normalizeToolCall(call);
|
|
436
|
+
if (normalized) results.push(normalized);
|
|
437
|
+
}
|
|
438
|
+
} else if (parsed.name || parsed.function) {
|
|
439
|
+
const normalized = this.normalizeToolCall(parsed);
|
|
440
|
+
if (normalized) results.push(normalized);
|
|
441
|
+
} else if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
|
|
442
|
+
for (const call of parsed.tool_calls) {
|
|
443
|
+
const normalized = this.normalizeToolCall(call);
|
|
444
|
+
if (normalized) results.push(normalized);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
} catch (e) {
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return results;
|
|
500
451
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
452
|
+
/**
|
|
453
|
+
* Extrai objetos JSON de uma string (suporta múltiplos objetos)
|
|
454
|
+
*/
|
|
455
|
+
static extractJsonObjects(text) {
|
|
456
|
+
const results = [];
|
|
457
|
+
let depth = 0;
|
|
458
|
+
let start = -1;
|
|
459
|
+
for (let i = 0; i < text.length; i++) {
|
|
460
|
+
if (text[i] === "{") {
|
|
461
|
+
if (depth === 0) start = i;
|
|
462
|
+
depth++;
|
|
463
|
+
} else if (text[i] === "}") {
|
|
464
|
+
depth--;
|
|
465
|
+
if (depth === 0 && start !== -1) {
|
|
466
|
+
results.push(text.substring(start, i + 1));
|
|
467
|
+
start = -1;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return results;
|
|
505
472
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
473
|
+
/**
|
|
474
|
+
* Valida se um tool call normalizado é válido
|
|
475
|
+
*/
|
|
476
|
+
static isValidToolCall(call) {
|
|
477
|
+
return !!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string");
|
|
510
478
|
}
|
|
511
|
-
|
|
512
|
-
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// src/main.ts
|
|
484
|
+
import React10 from "react";
|
|
485
|
+
import { render } from "ink";
|
|
486
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
487
|
+
import { v4 as uuidv43 } from "uuid";
|
|
488
|
+
|
|
489
|
+
// src/app/ui/App.tsx
|
|
490
|
+
import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback2, memo as memo10 } from "react";
|
|
491
|
+
import { Box as Box16, Text as Text15, Static } from "ink";
|
|
492
|
+
|
|
493
|
+
// src/app/ui/layout.tsx
|
|
494
|
+
import { Box, Text } from "ink";
|
|
495
|
+
import { memo } from "react";
|
|
496
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
497
|
+
var HeaderComponent = ({
|
|
498
|
+
sessionId: sessionId2,
|
|
499
|
+
workdir
|
|
500
|
+
}) => {
|
|
501
|
+
const dirName = workdir.split("/").pop() || workdir;
|
|
502
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
503
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "bluma \u2014 coding agent" }) }),
|
|
504
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
505
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "cwd " }),
|
|
506
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: dirName }),
|
|
507
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
|
|
508
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "session " }),
|
|
509
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: sessionId2.slice(0, 8) })
|
|
510
|
+
] })
|
|
511
|
+
] });
|
|
512
|
+
};
|
|
513
|
+
var Header = memo(HeaderComponent);
|
|
514
|
+
var SessionInfoComponent = ({
|
|
515
|
+
sessionId: sessionId2,
|
|
516
|
+
workdir,
|
|
517
|
+
toolsCount,
|
|
518
|
+
mcpStatus
|
|
519
|
+
}) => /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
|
|
520
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "mcp " }),
|
|
521
|
+
/* @__PURE__ */ jsx(Text, { color: mcpStatus === "connected" ? "green" : "yellow", children: mcpStatus === "connected" ? "+" : "-" }),
|
|
522
|
+
toolsCount !== null && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
523
|
+
" ",
|
|
524
|
+
toolsCount,
|
|
525
|
+
" tools"
|
|
526
|
+
] }) })
|
|
527
|
+
] });
|
|
528
|
+
var SessionInfo = memo(SessionInfoComponent);
|
|
529
|
+
var TaskStatusBarComponent = ({
|
|
530
|
+
taskName,
|
|
531
|
+
mode,
|
|
532
|
+
status
|
|
533
|
+
}) => {
|
|
534
|
+
const modeColors = {
|
|
535
|
+
PLANNING: "blue",
|
|
536
|
+
EXECUTION: "green",
|
|
537
|
+
VERIFICATION: "yellow"
|
|
513
538
|
};
|
|
539
|
+
return /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
|
|
540
|
+
/* @__PURE__ */ jsxs(Text, { color: modeColors[mode], bold: true, children: [
|
|
541
|
+
"[",
|
|
542
|
+
mode.charAt(0),
|
|
543
|
+
"]"
|
|
544
|
+
] }),
|
|
545
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
546
|
+
" ",
|
|
547
|
+
taskName
|
|
548
|
+
] }),
|
|
549
|
+
status && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
550
|
+
" - ",
|
|
551
|
+
status
|
|
552
|
+
] })
|
|
553
|
+
] });
|
|
514
554
|
};
|
|
555
|
+
var TaskStatusBar = memo(TaskStatusBarComponent);
|
|
515
556
|
|
|
516
557
|
// src/app/ui/components/InputPrompt.tsx
|
|
517
|
-
import {
|
|
518
|
-
import { EventEmitter } from "events";
|
|
519
|
-
|
|
520
|
-
// src/app/ui/utils/slashRegistry.ts
|
|
521
|
-
var getSlashCommands = () => [
|
|
522
|
-
{ name: "/help", description: "list commands" },
|
|
523
|
-
{ name: "/mcp", description: "list tools connected via MCP" },
|
|
524
|
-
{ name: "/tools", description: "list native tools" },
|
|
525
|
-
{ name: "/init", description: "create a new BluMa.md file with codebase documentation" },
|
|
526
|
-
{ name: "/clear", description: "clear history" }
|
|
527
|
-
];
|
|
528
|
-
var filterSlashCommands = (query) => {
|
|
529
|
-
const list = getSlashCommands();
|
|
530
|
-
const q = (query || "").toLowerCase();
|
|
531
|
-
if (!q) return list;
|
|
532
|
-
const scored = list.map((c) => {
|
|
533
|
-
const name = c.name.toLowerCase();
|
|
534
|
-
const desc = c.description.toLowerCase();
|
|
535
|
-
const isPrefix = name.startsWith(q);
|
|
536
|
-
const nameIdx = name.indexOf(q);
|
|
537
|
-
const descIdx = desc.indexOf(q);
|
|
538
|
-
let tier = 3;
|
|
539
|
-
let scorePrimary = 0;
|
|
540
|
-
let scoreSecondary = Number.MAX_SAFE_INTEGER;
|
|
541
|
-
let scoreTertiary = name.length;
|
|
542
|
-
if (isPrefix) {
|
|
543
|
-
tier = 0;
|
|
544
|
-
scorePrimary = q.length * -1;
|
|
545
|
-
scoreSecondary = 0;
|
|
546
|
-
} else if (nameIdx >= 0) {
|
|
547
|
-
tier = 1;
|
|
548
|
-
scorePrimary = 0;
|
|
549
|
-
scoreSecondary = nameIdx;
|
|
550
|
-
} else if (descIdx >= 0) {
|
|
551
|
-
tier = 2;
|
|
552
|
-
scorePrimary = 0;
|
|
553
|
-
scoreSecondary = descIdx;
|
|
554
|
-
}
|
|
555
|
-
return { cmd: c, tier, scorePrimary, scoreSecondary, scoreTertiary };
|
|
556
|
-
}).filter((s) => s.tier !== 3).sort((a, b) => {
|
|
557
|
-
if (a.tier !== b.tier) return a.tier - b.tier;
|
|
558
|
-
if (a.scorePrimary !== b.scorePrimary) return a.scorePrimary - b.scorePrimary;
|
|
559
|
-
if (a.scoreSecondary !== b.scoreSecondary) return a.scoreSecondary - b.scoreSecondary;
|
|
560
|
-
if (a.scoreTertiary !== b.scoreTertiary) return a.scoreTertiary - b.scoreTertiary;
|
|
561
|
-
return a.cmd.name.localeCompare(b.cmd.name);
|
|
562
|
-
});
|
|
563
|
-
return scored.map((s) => s.cmd);
|
|
564
|
-
};
|
|
558
|
+
import { Box as Box2, Text as Text2, useStdout, useInput as useInput2 } from "ink";
|
|
565
559
|
|
|
566
|
-
// src/app/ui/
|
|
567
|
-
import {
|
|
568
|
-
import
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const raw = pattern || "";
|
|
574
|
-
const patternEndsWithSlash = raw.endsWith("/");
|
|
575
|
-
const relDir = raw.replace(/^\/+|\/+$/g, "");
|
|
576
|
-
const filterPrefix = patternEndsWithSlash ? "" : relDir.split("/").slice(-1)[0] || "";
|
|
577
|
-
const listDir = path.resolve(baseDir, relDir || ".");
|
|
578
|
-
const results = [];
|
|
579
|
-
const IGNORED_DIRS = ["node_modules", ".git", ".venv", "dist", "build"];
|
|
580
|
-
const IGNORED_EXTS = [".pyc", ".class", ".o", ".map", ".log", ".tmp"];
|
|
581
|
-
function isIgnoredName(name) {
|
|
582
|
-
if (!name) return false;
|
|
583
|
-
if (IGNORED_DIRS.includes(name)) return true;
|
|
584
|
-
if (name.startsWith(".")) return true;
|
|
585
|
-
return false;
|
|
560
|
+
// src/app/ui/utils/useSimpleInputBuffer.ts
|
|
561
|
+
import { useReducer, useRef, useCallback, useEffect } from "react";
|
|
562
|
+
import { useInput } from "ink";
|
|
563
|
+
function getLineStart(text, cursorPos) {
|
|
564
|
+
let pos = cursorPos - 1;
|
|
565
|
+
while (pos >= 0 && text[pos] !== "\n") {
|
|
566
|
+
pos--;
|
|
586
567
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
568
|
+
return pos + 1;
|
|
569
|
+
}
|
|
570
|
+
function getLineEnd(text, cursorPos) {
|
|
571
|
+
let pos = cursorPos;
|
|
572
|
+
while (pos < text.length && text[pos] !== "\n") {
|
|
573
|
+
pos++;
|
|
591
574
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
575
|
+
return pos;
|
|
576
|
+
}
|
|
577
|
+
function moveToAdjacentLine(text, cursorPos, direction) {
|
|
578
|
+
const currentLineStart = getLineStart(text, cursorPos);
|
|
579
|
+
const currentLineEnd = getLineEnd(text, cursorPos);
|
|
580
|
+
const column = cursorPos - currentLineStart;
|
|
581
|
+
if (direction === "up") {
|
|
582
|
+
if (currentLineStart === 0) return cursorPos;
|
|
583
|
+
const prevLineEnd = currentLineStart - 1;
|
|
584
|
+
const prevLineStart = getLineStart(text, prevLineEnd);
|
|
585
|
+
const prevLineLength = prevLineEnd - prevLineStart;
|
|
586
|
+
return prevLineStart + Math.min(column, prevLineLength);
|
|
587
|
+
} else {
|
|
588
|
+
if (currentLineEnd === text.length) return cursorPos;
|
|
589
|
+
const nextLineStart = currentLineEnd + 1;
|
|
590
|
+
const nextLineEnd = getLineEnd(text, nextLineStart);
|
|
591
|
+
const nextLineLength = nextLineEnd - nextLineStart;
|
|
592
|
+
return nextLineStart + Math.min(column, nextLineLength);
|
|
596
593
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
} catch (e) {
|
|
617
|
-
}
|
|
594
|
+
}
|
|
595
|
+
function inputReducer(state, action, viewWidth) {
|
|
596
|
+
const adjustView = (newCursorPos, currentViewStart) => {
|
|
597
|
+
if (newCursorPos < currentViewStart) {
|
|
598
|
+
return newCursorPos;
|
|
599
|
+
}
|
|
600
|
+
if (newCursorPos >= currentViewStart + viewWidth) {
|
|
601
|
+
return newCursorPos - viewWidth + 1;
|
|
602
|
+
}
|
|
603
|
+
return currentViewStart;
|
|
604
|
+
};
|
|
605
|
+
switch (action.type) {
|
|
606
|
+
case "INPUT": {
|
|
607
|
+
const cleanInput = action.payload.replace(/(\r\n|\r)/gm, "\n");
|
|
608
|
+
const newText = state.text.slice(0, state.cursorPosition) + cleanInput + state.text.slice(state.cursorPosition);
|
|
609
|
+
const newCursorPosition = state.cursorPosition + cleanInput.length;
|
|
610
|
+
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
611
|
+
if (newText === state.text && newCursorPosition === state.cursorPosition) {
|
|
612
|
+
return state;
|
|
618
613
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
614
|
+
return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
615
|
+
}
|
|
616
|
+
case "NEWLINE": {
|
|
617
|
+
const newText = state.text.slice(0, state.cursorPosition) + "\n" + state.text.slice(state.cursorPosition);
|
|
618
|
+
const newCursorPosition = state.cursorPosition + 1;
|
|
619
|
+
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
620
|
+
return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
621
|
+
}
|
|
622
|
+
case "MOVE_CURSOR": {
|
|
623
|
+
let newCursorPosition = state.cursorPosition;
|
|
624
|
+
if (action.direction === "left" && state.cursorPosition > 0) {
|
|
625
|
+
newCursorPosition--;
|
|
626
|
+
} else if (action.direction === "right" && state.cursorPosition < state.text.length) {
|
|
627
|
+
newCursorPosition++;
|
|
628
|
+
} else if (action.direction === "up") {
|
|
629
|
+
newCursorPosition = moveToAdjacentLine(state.text, state.cursorPosition, "up");
|
|
630
|
+
} else if (action.direction === "down") {
|
|
631
|
+
newCursorPosition = moveToAdjacentLine(state.text, state.cursorPosition, "down");
|
|
629
632
|
}
|
|
633
|
+
if (newCursorPosition === state.cursorPosition) {
|
|
634
|
+
return state;
|
|
635
|
+
}
|
|
636
|
+
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
637
|
+
return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
630
638
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
cursorPosition,
|
|
639
|
-
setText
|
|
640
|
-
}) {
|
|
641
|
-
const [open, setOpen] = useState(false);
|
|
642
|
-
const [selected, setSelected] = useState(0);
|
|
643
|
-
const [suggestions, setSuggestions] = useState([]);
|
|
644
|
-
const lastQuery = useRef2("");
|
|
645
|
-
function scanForAt(text2, pos) {
|
|
646
|
-
const before = text2.slice(0, pos);
|
|
647
|
-
const m = before.match(/@([\w\/.\-_]*)$/);
|
|
648
|
-
if (!m) return { pattern: null, insertStart: -1 };
|
|
649
|
-
return { pattern: m[1] || "", insertStart: m.index + 1 };
|
|
650
|
-
}
|
|
651
|
-
function update(newText, newCursor) {
|
|
652
|
-
const res = scanForAt(newText, newCursor);
|
|
653
|
-
if (res.pattern !== null && res.pattern.length >= 0) {
|
|
654
|
-
setOpen(true);
|
|
655
|
-
globalThis.__BLUMA_AT_OPEN__ = true;
|
|
656
|
-
const suggs = listPathSuggestions(cwd, res.pattern);
|
|
657
|
-
setSuggestions(suggs);
|
|
658
|
-
setSelected(0);
|
|
659
|
-
lastQuery.current = res.pattern;
|
|
660
|
-
} else {
|
|
661
|
-
setOpen(false);
|
|
662
|
-
globalThis.__BLUMA_AT_OPEN__ = false;
|
|
663
|
-
setSuggestions([]);
|
|
639
|
+
case "MOVE_LINE_START": {
|
|
640
|
+
const newCursorPosition = getLineStart(state.text, state.cursorPosition);
|
|
641
|
+
if (newCursorPosition === state.cursorPosition) {
|
|
642
|
+
return state;
|
|
643
|
+
}
|
|
644
|
+
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
645
|
+
return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
664
646
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
function insertAtSelection() {
|
|
670
|
-
if (!open || !suggestions[selected]) return;
|
|
671
|
-
const res = scanForAt(text, cursorPosition);
|
|
672
|
-
if (!res || res.insertStart < 0) return;
|
|
673
|
-
let chosen = suggestions[selected].label;
|
|
674
|
-
const isDir = suggestions[selected].isDir;
|
|
675
|
-
chosen = chosen.replace(/\\/g, "/").replace(/\|/g, "");
|
|
676
|
-
let insertVal = chosen.replace(/\/+$/g, "");
|
|
677
|
-
const currentPattern = res.pattern || "";
|
|
678
|
-
if (currentPattern.length > 0) {
|
|
679
|
-
const normalizedPattern = currentPattern.replace(/\/+$/g, "");
|
|
680
|
-
if (insertVal.startsWith(normalizedPattern)) {
|
|
681
|
-
insertVal = insertVal.slice(normalizedPattern.length);
|
|
682
|
-
insertVal = insertVal.replace(/^\/+/, "");
|
|
647
|
+
case "MOVE_LINE_END": {
|
|
648
|
+
const newCursorPosition = getLineEnd(state.text, state.cursorPosition);
|
|
649
|
+
if (newCursorPosition === state.cursorPosition) {
|
|
650
|
+
return state;
|
|
683
651
|
}
|
|
652
|
+
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
653
|
+
return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
684
654
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
const newText = before + insertVal + after;
|
|
694
|
-
const finalText = newText + " ";
|
|
695
|
-
setText(finalText, finalText.length);
|
|
696
|
-
globalThis.__BLUMA_FORCE_CURSOR_END__ = true;
|
|
697
|
-
update(finalText, finalText.length);
|
|
698
|
-
if (isDir) {
|
|
699
|
-
setOpen(false);
|
|
700
|
-
setSuggestions([]);
|
|
701
|
-
setTimeout(() => {
|
|
702
|
-
setOpen(true);
|
|
703
|
-
update(finalText, finalText.length);
|
|
704
|
-
}, 0);
|
|
705
|
-
} else {
|
|
706
|
-
setOpen(false);
|
|
707
|
-
setSuggestions([]);
|
|
655
|
+
case "BACKSPACE": {
|
|
656
|
+
if (state.cursorPosition > 0) {
|
|
657
|
+
const newText = state.text.slice(0, state.cursorPosition - 1) + state.text.slice(state.cursorPosition);
|
|
658
|
+
const newCursorPosition = state.cursorPosition - 1;
|
|
659
|
+
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
660
|
+
return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
661
|
+
}
|
|
662
|
+
return state;
|
|
708
663
|
}
|
|
664
|
+
case "DELETE": {
|
|
665
|
+
if (state.cursorPosition < state.text.length) {
|
|
666
|
+
const newText = state.text.slice(0, state.cursorPosition) + state.text.slice(state.cursorPosition + 1);
|
|
667
|
+
const newViewStart = adjustView(state.cursorPosition, state.viewStart);
|
|
668
|
+
return { text: newText, cursorPosition: state.cursorPosition, viewStart: newViewStart };
|
|
669
|
+
}
|
|
670
|
+
return state;
|
|
671
|
+
}
|
|
672
|
+
case "SUBMIT": {
|
|
673
|
+
return { text: "", cursorPosition: 0, viewStart: 0 };
|
|
674
|
+
}
|
|
675
|
+
case "SET": {
|
|
676
|
+
const t = action.payload.text.replace(/(\r\n|\r)/gm, "\n");
|
|
677
|
+
let newCursorPosition;
|
|
678
|
+
if (typeof action.payload.cursorPosition === "number") {
|
|
679
|
+
newCursorPosition = Math.min(action.payload.cursorPosition, t.length);
|
|
680
|
+
} else if (action.payload.moveCursorToEnd ?? true) {
|
|
681
|
+
newCursorPosition = t.length;
|
|
682
|
+
} else {
|
|
683
|
+
newCursorPosition = Math.min(state.cursorPosition, t.length);
|
|
684
|
+
}
|
|
685
|
+
const newText = t;
|
|
686
|
+
const newViewStart = adjustView(newCursorPosition, 0);
|
|
687
|
+
return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
688
|
+
}
|
|
689
|
+
case "SET_CURSOR": {
|
|
690
|
+
const newCursorPosition = Math.max(0, Math.min(action.payload, state.text.length));
|
|
691
|
+
if (newCursorPosition === state.cursorPosition) {
|
|
692
|
+
return state;
|
|
693
|
+
}
|
|
694
|
+
const newViewStart = adjustView(newCursorPosition, state.viewStart);
|
|
695
|
+
return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
|
|
696
|
+
}
|
|
697
|
+
default:
|
|
698
|
+
return state;
|
|
709
699
|
}
|
|
710
|
-
function close() {
|
|
711
|
-
setOpen(false);
|
|
712
|
-
setSuggestions([]);
|
|
713
|
-
}
|
|
714
|
-
return { open, suggestions, selected, setSelected, insertAtSelection, close, update };
|
|
715
700
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
})
|
|
728
|
-
const isCursorLine = lineIndex === cursorLine;
|
|
729
|
-
if (!isCursorLine || !showCursor) {
|
|
730
|
-
return /* @__PURE__ */ jsx2(Text2, { children: line });
|
|
731
|
-
}
|
|
732
|
-
const before = line.slice(0, cursorCol);
|
|
733
|
-
const char = line[cursorCol] || " ";
|
|
734
|
-
const after = line.slice(cursorCol + 1);
|
|
735
|
-
return /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
736
|
-
before,
|
|
737
|
-
/* @__PURE__ */ jsx2(Text2, { inverse: true, color: "white", children: char }),
|
|
738
|
-
after
|
|
739
|
-
] });
|
|
740
|
-
}, (prev, next) => {
|
|
741
|
-
if (prev.line !== next.line) return false;
|
|
742
|
-
if (prev.showCursor !== next.showCursor) return false;
|
|
743
|
-
const prevIsCursorLine = prev.lineIndex === prev.cursorLine;
|
|
744
|
-
const nextIsCursorLine = next.lineIndex === next.cursorLine;
|
|
745
|
-
if (prevIsCursorLine !== nextIsCursorLine) return false;
|
|
746
|
-
if (nextIsCursorLine && prev.cursorCol !== next.cursorCol) return false;
|
|
747
|
-
return true;
|
|
748
|
-
});
|
|
749
|
-
TextLine.displayName = "TextLine";
|
|
750
|
-
var PathSuggestions = memo2(({
|
|
751
|
-
suggestions,
|
|
752
|
-
selected
|
|
753
|
-
}) => {
|
|
754
|
-
const VISIBLE = 7;
|
|
755
|
-
const total = suggestions.length;
|
|
756
|
-
const sel = Math.max(0, Math.min(selected, total - 1));
|
|
757
|
-
let start = Math.max(0, sel - Math.floor(VISIBLE / 2));
|
|
758
|
-
if (start + VISIBLE > total) start = Math.max(0, total - VISIBLE);
|
|
759
|
-
const windowItems = suggestions.slice(start, start + VISIBLE);
|
|
760
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, height: Math.min(VISIBLE, total), children: windowItems.map((s, idx) => {
|
|
761
|
-
const realIdx = start + idx;
|
|
762
|
-
const isSelected = realIdx === selected;
|
|
763
|
-
return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, paddingY: 0, children: [
|
|
764
|
-
/* @__PURE__ */ jsx2(Text2, { color: isSelected ? "magenta" : "gray", children: isSelected ? "\u276F " : " " }),
|
|
765
|
-
/* @__PURE__ */ jsx2(Text2, { color: isSelected ? "magenta" : "white", bold: isSelected, dimColor: !isSelected, children: s.label })
|
|
766
|
-
] }, s.fullPath);
|
|
767
|
-
}) });
|
|
768
|
-
});
|
|
769
|
-
PathSuggestions.displayName = "PathSuggestions";
|
|
770
|
-
var SlashSuggestions = memo2(({
|
|
771
|
-
suggestions,
|
|
772
|
-
selectedIndex
|
|
773
|
-
}) => /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: suggestions.map((s, idx) => {
|
|
774
|
-
const isSelected = idx === selectedIndex;
|
|
775
|
-
return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, paddingY: 0, children: [
|
|
776
|
-
/* @__PURE__ */ jsx2(Text2, { color: isSelected ? "magenta" : "gray", children: isSelected ? "\u276F " : " " }),
|
|
777
|
-
/* @__PURE__ */ jsxs2(Text2, { color: isSelected ? "magenta" : "white", bold: isSelected, dimColor: !isSelected, children: [
|
|
778
|
-
s.name,
|
|
779
|
-
" ",
|
|
780
|
-
/* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
781
|
-
"- ",
|
|
782
|
-
s.description
|
|
783
|
-
] })
|
|
784
|
-
] })
|
|
785
|
-
] }, s.name);
|
|
786
|
-
}) }));
|
|
787
|
-
SlashSuggestions.displayName = "SlashSuggestions";
|
|
788
|
-
var Footer = memo2(({ isReadOnly }) => /* @__PURE__ */ jsx2(Box2, { paddingX: 1, justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, children: isReadOnly ? "ctrl+c to exit | Enter to send message | Shift+Enter for new line | esc interrupt" : "ctrl+c to exit | Enter to submit | Shift+Enter for new line | /help commands | esc interrupt" }) }));
|
|
789
|
-
Footer.displayName = "Footer";
|
|
790
|
-
var TextLinesRenderer = memo2(({
|
|
791
|
-
lines,
|
|
792
|
-
cursorLine,
|
|
793
|
-
cursorCol,
|
|
794
|
-
showCursor,
|
|
795
|
-
showPlaceholder,
|
|
796
|
-
placeholder
|
|
797
|
-
}) => {
|
|
798
|
-
return /* @__PURE__ */ jsx2(Fragment2, { children: lines.map((line, idx) => {
|
|
799
|
-
const isFirstLine = idx === 0;
|
|
800
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", paddingX: 1, children: [
|
|
801
|
-
isFirstLine && /* @__PURE__ */ jsxs2(Text2, { color: "white", children: [
|
|
802
|
-
">",
|
|
803
|
-
" "
|
|
804
|
-
] }),
|
|
805
|
-
!isFirstLine && /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
806
|
-
"\u2502",
|
|
807
|
-
" "
|
|
808
|
-
] }),
|
|
809
|
-
showPlaceholder && isFirstLine && line.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsx2(
|
|
810
|
-
TextLine,
|
|
811
|
-
{
|
|
812
|
-
line,
|
|
813
|
-
lineIndex: idx,
|
|
814
|
-
cursorLine,
|
|
815
|
-
cursorCol,
|
|
816
|
-
showCursor
|
|
817
|
-
}
|
|
818
|
-
)
|
|
819
|
-
] }, idx);
|
|
820
|
-
}) });
|
|
821
|
-
});
|
|
822
|
-
TextLinesRenderer.displayName = "TextLinesRenderer";
|
|
823
|
-
var InputPrompt = memo2(({
|
|
824
|
-
onSubmit,
|
|
825
|
-
isReadOnly,
|
|
826
|
-
onInterrupt,
|
|
827
|
-
disableWhileProcessing = false
|
|
828
|
-
}) => {
|
|
829
|
-
const { stdout } = useStdout();
|
|
830
|
-
const [viewWidth] = useState2(() => stdout.columns - 6);
|
|
831
|
-
const [slashOpen, setSlashOpen] = useState2(false);
|
|
832
|
-
const [slashIndex, setSlashIndex] = useState2(0);
|
|
833
|
-
const permissiveOnSubmit = (value) => {
|
|
834
|
-
const trimmed = (value || "").trim();
|
|
835
|
-
if (isReadOnly) {
|
|
836
|
-
if (trimmed.length > 0) {
|
|
837
|
-
uiEventBus.emit("user_overlay", { kind: "message", payload: trimmed, ts: Date.now() });
|
|
838
|
-
}
|
|
839
|
-
return;
|
|
701
|
+
var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
|
|
702
|
+
const [state, dispatch] = useReducer(
|
|
703
|
+
(s, a) => inputReducer(s, a, viewWidth),
|
|
704
|
+
{ text: "", cursorPosition: 0, viewStart: 0 }
|
|
705
|
+
);
|
|
706
|
+
const inputBuffer = useRef("");
|
|
707
|
+
const flushScheduled = useRef(false);
|
|
708
|
+
const flushInputBuffer = useCallback(() => {
|
|
709
|
+
if (inputBuffer.current.length > 0) {
|
|
710
|
+
const buffered = inputBuffer.current;
|
|
711
|
+
inputBuffer.current = "";
|
|
712
|
+
dispatch({ type: "INPUT", payload: buffered });
|
|
840
713
|
}
|
|
841
|
-
|
|
842
|
-
};
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
if (
|
|
846
|
-
|
|
847
|
-
permissiveOnSubmit(value);
|
|
848
|
-
},
|
|
849
|
-
viewWidth,
|
|
850
|
-
isReadOnly,
|
|
851
|
-
onInterrupt
|
|
852
|
-
});
|
|
853
|
-
const linesData = useMemo(() => {
|
|
854
|
-
const lines = text.split("\n");
|
|
855
|
-
let remainingChars = cursorPosition;
|
|
856
|
-
let cursorLine = 0;
|
|
857
|
-
let cursorCol = 0;
|
|
858
|
-
for (let i = 0; i < lines.length; i++) {
|
|
859
|
-
const lineLength = lines[i].length;
|
|
860
|
-
if (remainingChars <= lineLength) {
|
|
861
|
-
cursorLine = i;
|
|
862
|
-
cursorCol = remainingChars;
|
|
863
|
-
break;
|
|
714
|
+
flushScheduled.current = false;
|
|
715
|
+
}, []);
|
|
716
|
+
useEffect(() => {
|
|
717
|
+
return () => {
|
|
718
|
+
if (flushScheduled.current) {
|
|
719
|
+
flushInputBuffer();
|
|
864
720
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
721
|
+
};
|
|
722
|
+
}, [flushInputBuffer]);
|
|
723
|
+
useInput(
|
|
724
|
+
(input, key) => {
|
|
725
|
+
const hasBackspaceFlag = key.backspace;
|
|
726
|
+
const hasDeleteFlag = key.delete;
|
|
727
|
+
const hasBackspaceChar = input === "\x7F" || input === "\b" || input === "\b" || input.charCodeAt(0) === 127 || input.charCodeAt(0) === 8;
|
|
728
|
+
if (hasBackspaceFlag || hasBackspaceChar) {
|
|
729
|
+
if (inputBuffer.current.length > 0) {
|
|
730
|
+
flushInputBuffer();
|
|
731
|
+
}
|
|
732
|
+
dispatch({ type: "BACKSPACE" });
|
|
733
|
+
return;
|
|
869
734
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const displayData = linesData;
|
|
874
|
-
const placeholder = isReadOnly ? " Press Esc to cancel | Enter message while agent runs" : "";
|
|
875
|
-
const showPlaceholder = text.length === 0 && isReadOnly;
|
|
876
|
-
const slashQuery = useMemo(() => text.startsWith("/") ? text : "", [text]);
|
|
877
|
-
const slashSuggestions = useMemo(() => {
|
|
878
|
-
if (!slashQuery) return [];
|
|
879
|
-
return filterSlashCommands(slashQuery);
|
|
880
|
-
}, [slashQuery]);
|
|
881
|
-
useEffect3(() => {
|
|
882
|
-
if (isReadOnly) {
|
|
883
|
-
setSlashOpen(false);
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
if (text.startsWith("/")) {
|
|
887
|
-
setSlashOpen(true);
|
|
888
|
-
setSlashIndex(0);
|
|
889
|
-
} else {
|
|
890
|
-
setSlashOpen(false);
|
|
891
|
-
}
|
|
892
|
-
}, [text, isReadOnly]);
|
|
893
|
-
useInput2((input, key) => {
|
|
894
|
-
if (!slashOpen) return;
|
|
895
|
-
if (key.downArrow) {
|
|
896
|
-
setSlashIndex((i) => Math.min(i + 1, Math.max(0, slashSuggestions.length - 1)));
|
|
897
|
-
} else if (key.upArrow) {
|
|
898
|
-
setSlashIndex((i) => Math.max(i - 1, 0));
|
|
899
|
-
} else if (key.return) {
|
|
900
|
-
const choice = slashSuggestions[slashIndex];
|
|
901
|
-
if (choice) {
|
|
902
|
-
setSlashOpen(false);
|
|
903
|
-
try {
|
|
904
|
-
setText(`${choice.name} `);
|
|
905
|
-
} catch (e) {
|
|
906
|
-
permissiveOnSubmit(`${choice.name} `);
|
|
735
|
+
if (hasDeleteFlag && (key.ctrl || key.meta)) {
|
|
736
|
+
if (inputBuffer.current.length > 0) {
|
|
737
|
+
flushInputBuffer();
|
|
907
738
|
}
|
|
739
|
+
dispatch({ type: "DELETE" });
|
|
740
|
+
return;
|
|
908
741
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
if (globalThis.__BLUMA_FORCE_CURSOR_END__) {
|
|
915
|
-
setText(text, text.length);
|
|
916
|
-
delete globalThis.__BLUMA_FORCE_CURSOR_END__;
|
|
917
|
-
}
|
|
918
|
-
}, [text, setText]);
|
|
919
|
-
const cwd = process.cwd();
|
|
920
|
-
const pathAutocomplete = useAtCompletion({ cwd, text, cursorPosition, setText });
|
|
921
|
-
useInput2((input, key) => {
|
|
922
|
-
if (key.backspace || key.delete || key.ctrl || key.meta) return;
|
|
923
|
-
if (pathAutocomplete.open) {
|
|
924
|
-
if (key.downArrow) {
|
|
925
|
-
pathAutocomplete.setSelected((i) => Math.min(i + 1, Math.max(0, pathAutocomplete.suggestions.length - 1)));
|
|
742
|
+
if (hasDeleteFlag && !key.ctrl && !key.meta) {
|
|
743
|
+
if (inputBuffer.current.length > 0) {
|
|
744
|
+
flushInputBuffer();
|
|
745
|
+
}
|
|
746
|
+
dispatch({ type: "BACKSPACE" });
|
|
926
747
|
return;
|
|
927
|
-
}
|
|
928
|
-
|
|
748
|
+
}
|
|
749
|
+
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)) {
|
|
750
|
+
flushInputBuffer();
|
|
751
|
+
}
|
|
752
|
+
if (key.escape) {
|
|
753
|
+
onInterrupt();
|
|
929
754
|
return;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
if (
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
globalThis.__BLUMA_SUPPRESS_SUBMIT__ = true;
|
|
937
|
-
pathAutocomplete.insertAtSelection();
|
|
938
|
-
return;
|
|
755
|
+
}
|
|
756
|
+
if (isReadOnly) {
|
|
757
|
+
if (key.return && !key.shift) {
|
|
758
|
+
if (state.text.trim().length > 0) {
|
|
759
|
+
onSubmit(state.text);
|
|
760
|
+
dispatch({ type: "SUBMIT" });
|
|
939
761
|
}
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (key.shift && key.return) {
|
|
765
|
+
dispatch({ type: "NEWLINE" });
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
|
|
769
|
+
if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
|
|
770
|
+
if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
|
|
771
|
+
if (key.downArrow) return dispatch({ type: "MOVE_CURSOR", direction: "down" });
|
|
772
|
+
if (key.ctrl || key.meta || key.tab) return;
|
|
773
|
+
inputBuffer.current += input;
|
|
774
|
+
if (!flushScheduled.current) {
|
|
775
|
+
flushScheduled.current = true;
|
|
776
|
+
queueMicrotask(flushInputBuffer);
|
|
940
777
|
}
|
|
941
778
|
return;
|
|
942
|
-
}
|
|
943
|
-
|
|
779
|
+
}
|
|
780
|
+
if (key.return && key.shift) {
|
|
781
|
+
dispatch({ type: "NEWLINE" });
|
|
944
782
|
return;
|
|
945
783
|
}
|
|
946
|
-
return
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
/* @__PURE__ */ jsxs2(Text2, { color: "white", children: [
|
|
952
|
-
">",
|
|
953
|
-
" "
|
|
954
|
-
] }),
|
|
955
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "ctrl+c to exit" })
|
|
956
|
-
] }) }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
957
|
-
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsx2(
|
|
958
|
-
TextLinesRenderer,
|
|
959
|
-
{
|
|
960
|
-
lines: displayData.lines,
|
|
961
|
-
cursorLine: displayData.cursorLine,
|
|
962
|
-
cursorCol: displayData.cursorCol,
|
|
963
|
-
showCursor: !isReadOnly,
|
|
964
|
-
showPlaceholder,
|
|
965
|
-
placeholder
|
|
966
|
-
}
|
|
967
|
-
) }),
|
|
968
|
-
pathAutocomplete.open && pathAutocomplete.suggestions.length > 0 && /* @__PURE__ */ jsx2(
|
|
969
|
-
PathSuggestions,
|
|
970
|
-
{
|
|
971
|
-
suggestions: pathAutocomplete.suggestions,
|
|
972
|
-
selected: pathAutocomplete.selected
|
|
784
|
+
if (key.return) {
|
|
785
|
+
if (globalThis.__BLUMA_AT_OPEN__) return;
|
|
786
|
+
if (globalThis.__BLUMA_SUPPRESS_SUBMIT__) {
|
|
787
|
+
globalThis.__BLUMA_SUPPRESS_SUBMIT__ = false;
|
|
788
|
+
return;
|
|
973
789
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
{
|
|
978
|
-
suggestions: slashSuggestions,
|
|
979
|
-
selectedIndex: slashIndex
|
|
790
|
+
if (state.text.trim().length > 0) {
|
|
791
|
+
onSubmit(state.text);
|
|
792
|
+
dispatch({ type: "SUBMIT" });
|
|
980
793
|
}
|
|
981
|
-
|
|
982
|
-
] }),
|
|
983
|
-
/* @__PURE__ */ jsx2(Footer, { isReadOnly })
|
|
984
|
-
] });
|
|
985
|
-
});
|
|
986
|
-
InputPrompt.displayName = "InputPrompt";
|
|
987
|
-
|
|
988
|
-
// src/app/ui/ConfirmationPrompt.tsx
|
|
989
|
-
import { memo as memo4 } from "react";
|
|
990
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
991
|
-
|
|
992
|
-
// src/app/ui/InteractiveMenu.tsx
|
|
993
|
-
import { useState as useState3, memo as memo3 } from "react";
|
|
994
|
-
import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
|
|
995
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
996
|
-
var InteractiveMenuComponent = ({ onDecision }) => {
|
|
997
|
-
const options = [
|
|
998
|
-
{ key: "y", label: "yes", value: "accept", color: "green" },
|
|
999
|
-
{ key: "n", label: "no", value: "decline", color: "red" },
|
|
1000
|
-
{ key: "a", label: "always", value: "accept_always", color: "yellow" }
|
|
1001
|
-
];
|
|
1002
|
-
const [selected, setSelected] = useState3(0);
|
|
1003
|
-
useInput3((input, key) => {
|
|
1004
|
-
if (key.upArrow || key.leftArrow) {
|
|
1005
|
-
setSelected((i) => i > 0 ? i - 1 : options.length - 1);
|
|
1006
|
-
}
|
|
1007
|
-
if (key.downArrow || key.rightArrow) {
|
|
1008
|
-
setSelected((i) => i < options.length - 1 ? i + 1 : 0);
|
|
1009
|
-
}
|
|
1010
|
-
if (key.escape) {
|
|
1011
|
-
onDecision("decline");
|
|
1012
|
-
}
|
|
1013
|
-
if (key.return) {
|
|
1014
|
-
onDecision(options[selected].value);
|
|
1015
|
-
}
|
|
1016
|
-
const opt = options.find((o) => o.key === input.toLowerCase());
|
|
1017
|
-
if (opt) {
|
|
1018
|
-
onDecision(opt.value);
|
|
1019
|
-
}
|
|
1020
|
-
});
|
|
1021
|
-
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
1022
|
-
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
1023
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "approve? " }),
|
|
1024
|
-
options.map((opt, idx) => {
|
|
1025
|
-
const isSelected = idx === selected;
|
|
1026
|
-
return /* @__PURE__ */ jsx3(Box3, { marginRight: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: isSelected ? opt.color : "gray", bold: isSelected, children: [
|
|
1027
|
-
"[",
|
|
1028
|
-
opt.key,
|
|
1029
|
-
"]",
|
|
1030
|
-
opt.label
|
|
1031
|
-
] }) }, opt.value);
|
|
1032
|
-
})
|
|
1033
|
-
] }),
|
|
1034
|
-
/* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "arrows to select, enter to confirm, esc to cancel" }) })
|
|
1035
|
-
] });
|
|
1036
|
-
};
|
|
1037
|
-
var InteractiveMenu = memo3(InteractiveMenuComponent);
|
|
1038
|
-
|
|
1039
|
-
// src/app/ui/components/promptRenderers.tsx
|
|
1040
|
-
import { Box as Box5, Text as Text5 } from "ink";
|
|
1041
|
-
import path2 from "path";
|
|
1042
|
-
|
|
1043
|
-
// src/app/ui/components/SimpleDiff.tsx
|
|
1044
|
-
import { Box as Box4, Text as Text4 } from "ink";
|
|
1045
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1046
|
-
var SimpleDiff = ({ text, maxHeight }) => {
|
|
1047
|
-
const allLines = (text || "").split("\n").filter((line) => line !== "");
|
|
1048
|
-
const isTruncated = maxHeight > 0 && allLines.length > maxHeight;
|
|
1049
|
-
const linesToRender = isTruncated ? allLines.slice(-maxHeight) : allLines;
|
|
1050
|
-
const hiddenCount = allLines.length - linesToRender.length;
|
|
1051
|
-
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 2, children: [
|
|
1052
|
-
isTruncated && /* @__PURE__ */ jsx4(Box4, { marginBottom: 0, children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1053
|
-
"\u22EF ",
|
|
1054
|
-
hiddenCount,
|
|
1055
|
-
" lines hidden"
|
|
1056
|
-
] }) }),
|
|
1057
|
-
linesToRender.map((line, index) => {
|
|
1058
|
-
if (line.startsWith("---") || line.startsWith("+++")) {
|
|
1059
|
-
return null;
|
|
794
|
+
return;
|
|
1060
795
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if (
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
796
|
+
if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
|
|
797
|
+
if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
|
|
798
|
+
if (key.upArrow) return dispatch({ type: "MOVE_CURSOR", direction: "up" });
|
|
799
|
+
if (key.downArrow) return dispatch({ type: "MOVE_CURSOR", direction: "down" });
|
|
800
|
+
if (key.ctrl && input === "a") {
|
|
801
|
+
return dispatch({ type: "MOVE_LINE_START" });
|
|
802
|
+
}
|
|
803
|
+
if (key.ctrl && input === "e") {
|
|
804
|
+
return dispatch({ type: "MOVE_LINE_END" });
|
|
805
|
+
}
|
|
806
|
+
if (key.ctrl || key.meta || key.tab) return;
|
|
807
|
+
inputBuffer.current += input;
|
|
808
|
+
if (!flushScheduled.current) {
|
|
809
|
+
flushScheduled.current = true;
|
|
810
|
+
queueMicrotask(flushInputBuffer);
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
{ isActive: true }
|
|
814
|
+
);
|
|
815
|
+
return {
|
|
816
|
+
text: state.text,
|
|
817
|
+
cursorPosition: state.cursorPosition,
|
|
818
|
+
viewStart: state.viewStart,
|
|
819
|
+
setText: useCallback((t, pos) => {
|
|
820
|
+
if (inputBuffer.current.length > 0) {
|
|
821
|
+
flushInputBuffer();
|
|
822
|
+
}
|
|
823
|
+
if (typeof pos === "number") {
|
|
824
|
+
dispatch({ type: "SET", payload: { text: t, moveCursorToEnd: false, cursorPosition: pos } });
|
|
1072
825
|
} else {
|
|
1073
|
-
|
|
1074
|
-
prefix = " ";
|
|
826
|
+
dispatch({ type: "SET", payload: { text: t, moveCursorToEnd: true } });
|
|
1075
827
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
828
|
+
}, [flushInputBuffer]),
|
|
829
|
+
setCursor: useCallback((pos) => {
|
|
830
|
+
if (inputBuffer.current.length > 0) {
|
|
831
|
+
flushInputBuffer();
|
|
832
|
+
}
|
|
833
|
+
dispatch({ type: "SET_CURSOR", payload: pos });
|
|
834
|
+
}, [flushInputBuffer])
|
|
835
|
+
};
|
|
1082
836
|
};
|
|
1083
837
|
|
|
1084
|
-
// src/app/ui/components/
|
|
1085
|
-
import {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
var
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
838
|
+
// src/app/ui/components/InputPrompt.tsx
|
|
839
|
+
import { useEffect as useEffect3, useMemo, useState as useState2, memo as memo2 } from "react";
|
|
840
|
+
import { EventEmitter } from "events";
|
|
841
|
+
|
|
842
|
+
// src/app/ui/utils/slashRegistry.ts
|
|
843
|
+
var getSlashCommands = () => [
|
|
844
|
+
{ name: "/help", description: "list commands" },
|
|
845
|
+
{ name: "/mcp", description: "list tools connected via MCP" },
|
|
846
|
+
{ name: "/tools", description: "list native tools" },
|
|
847
|
+
{ name: "/init", description: "create a new BluMa.md file with codebase documentation" },
|
|
848
|
+
{ name: "/clear", description: "clear history" }
|
|
849
|
+
];
|
|
850
|
+
var filterSlashCommands = (query) => {
|
|
851
|
+
const list = getSlashCommands();
|
|
852
|
+
const q = (query || "").toLowerCase();
|
|
853
|
+
if (!q) return list;
|
|
854
|
+
const scored = list.map((c) => {
|
|
855
|
+
const name = c.name.toLowerCase();
|
|
856
|
+
const desc = c.description.toLowerCase();
|
|
857
|
+
const isPrefix = name.startsWith(q);
|
|
858
|
+
const nameIdx = name.indexOf(q);
|
|
859
|
+
const descIdx = desc.indexOf(q);
|
|
860
|
+
let tier = 3;
|
|
861
|
+
let scorePrimary = 0;
|
|
862
|
+
let scoreSecondary = Number.MAX_SAFE_INTEGER;
|
|
863
|
+
let scoreTertiary = name.length;
|
|
864
|
+
if (isPrefix) {
|
|
865
|
+
tier = 0;
|
|
866
|
+
scorePrimary = q.length * -1;
|
|
867
|
+
scoreSecondary = 0;
|
|
868
|
+
} else if (nameIdx >= 0) {
|
|
869
|
+
tier = 1;
|
|
870
|
+
scorePrimary = 0;
|
|
871
|
+
scoreSecondary = nameIdx;
|
|
872
|
+
} else if (descIdx >= 0) {
|
|
873
|
+
tier = 2;
|
|
874
|
+
scorePrimary = 0;
|
|
875
|
+
scoreSecondary = descIdx;
|
|
876
|
+
}
|
|
877
|
+
return { cmd: c, tier, scorePrimary, scoreSecondary, scoreTertiary };
|
|
878
|
+
}).filter((s) => s.tier !== 3).sort((a, b) => {
|
|
879
|
+
if (a.tier !== b.tier) return a.tier - b.tier;
|
|
880
|
+
if (a.scorePrimary !== b.scorePrimary) return a.scorePrimary - b.scorePrimary;
|
|
881
|
+
if (a.scoreSecondary !== b.scoreSecondary) return a.scoreSecondary - b.scoreSecondary;
|
|
882
|
+
if (a.scoreTertiary !== b.scoreTertiary) return a.scoreTertiary - b.scoreTertiary;
|
|
883
|
+
return a.cmd.name.localeCompare(b.cmd.name);
|
|
884
|
+
});
|
|
885
|
+
return scored.map((s) => s.cmd);
|
|
1104
886
|
};
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
887
|
+
|
|
888
|
+
// src/app/ui/hooks/useAtCompletion.ts
|
|
889
|
+
import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
|
|
890
|
+
import fs from "fs";
|
|
891
|
+
import path from "path";
|
|
892
|
+
var MAX_RESULTS = 50;
|
|
893
|
+
var DEFAULT_RECURSIVE_DEPTH = 2;
|
|
894
|
+
function listPathSuggestions(baseDir, pattern) {
|
|
895
|
+
const raw = pattern || "";
|
|
896
|
+
const patternEndsWithSlash = raw.endsWith("/");
|
|
897
|
+
const relDir = raw.replace(/^\/+|\/+$/g, "");
|
|
898
|
+
const filterPrefix = patternEndsWithSlash ? "" : relDir.split("/").slice(-1)[0] || "";
|
|
899
|
+
const listDir = path.resolve(baseDir, relDir || ".");
|
|
900
|
+
const results = [];
|
|
901
|
+
const IGNORED_DIRS = ["node_modules", ".git", ".venv", "dist", "build"];
|
|
902
|
+
const IGNORED_EXTS = [".pyc", ".class", ".o", ".map", ".log", ".tmp"];
|
|
903
|
+
function isIgnoredName(name) {
|
|
904
|
+
if (!name) return false;
|
|
905
|
+
if (IGNORED_DIRS.includes(name)) return true;
|
|
906
|
+
if (name.startsWith(".")) return true;
|
|
907
|
+
return false;
|
|
1112
908
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
] }),
|
|
1118
|
-
/* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: directoryPath }) })
|
|
1119
|
-
] });
|
|
1120
|
-
};
|
|
1121
|
-
var renderCountFilesLinesTool = ({ toolCall }) => {
|
|
1122
|
-
let filepath = "[path not specified]";
|
|
1123
|
-
try {
|
|
1124
|
-
const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
|
|
1125
|
-
filepath = args.filepath || "[path not specified]";
|
|
1126
|
-
} catch (e) {
|
|
1127
|
-
filepath = "Error parsing arguments";
|
|
909
|
+
function isIgnoredFile(name) {
|
|
910
|
+
if (!name) return false;
|
|
911
|
+
for (const e of IGNORED_EXTS) if (name.endsWith(e)) return true;
|
|
912
|
+
return false;
|
|
1128
913
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
] }),
|
|
1134
|
-
/* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: filepath }) })
|
|
1135
|
-
] });
|
|
1136
|
-
};
|
|
1137
|
-
var renderReadFileLines = ({ toolCall }) => {
|
|
1138
|
-
let filepath = "[path not specified]";
|
|
1139
|
-
let startLine = 0;
|
|
1140
|
-
let endLine = 0;
|
|
1141
|
-
try {
|
|
1142
|
-
const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
|
|
1143
|
-
filepath = args.filepath || "[path not specified]";
|
|
1144
|
-
startLine = args.start_line || 0;
|
|
1145
|
-
endLine = args.end_line || 0;
|
|
1146
|
-
} catch (e) {
|
|
1147
|
-
filepath = "Error parsing arguments";
|
|
914
|
+
function pushEntry(entryPath, label, isDir) {
|
|
915
|
+
if (results.length >= MAX_RESULTS) return;
|
|
916
|
+
const clean = label.split(path.sep).join("/").replace(/[]+/g, "");
|
|
917
|
+
results.push({ label: clean + (isDir ? "/" : ""), fullPath: entryPath, isDir });
|
|
1148
918
|
}
|
|
1149
|
-
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1150
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1151
|
-
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1152
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " read" })
|
|
1153
|
-
] }),
|
|
1154
|
-
/* @__PURE__ */ jsxs5(Box5, { paddingLeft: 2, flexDirection: "column", children: [
|
|
1155
|
-
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: filepath }),
|
|
1156
|
-
/* @__PURE__ */ jsxs5(Box5, { paddingLeft: 2, children: [
|
|
1157
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "lines " }),
|
|
1158
|
-
/* @__PURE__ */ jsx5(Text5, { color: "magenta", children: startLine }),
|
|
1159
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " to " }),
|
|
1160
|
-
/* @__PURE__ */ jsx5(Text5, { color: "magenta", children: endLine })
|
|
1161
|
-
] })
|
|
1162
|
-
] })
|
|
1163
|
-
] });
|
|
1164
|
-
};
|
|
1165
|
-
var renderEditTool = ({ toolCall, preview }) => {
|
|
1166
|
-
const diffMaxHeight = 5;
|
|
1167
|
-
let filepath = "[path not specified]";
|
|
1168
919
|
try {
|
|
1169
|
-
|
|
1170
|
-
|
|
920
|
+
if (raw.length === 0 || patternEndsWithSlash) {
|
|
921
|
+
const queue = [{ dir: listDir, depth: 0, rel: relDir }];
|
|
922
|
+
while (queue.length && results.length < MAX_RESULTS) {
|
|
923
|
+
const node = queue.shift();
|
|
924
|
+
try {
|
|
925
|
+
const entries = fs.readdirSync(node.dir, { withFileTypes: true });
|
|
926
|
+
for (const entry of entries) {
|
|
927
|
+
if (isIgnoredName(entry.name)) continue;
|
|
928
|
+
const entryAbs = path.join(node.dir, entry.name);
|
|
929
|
+
const entryRel = node.rel ? path.posix.join(node.rel, entry.name) : entry.name;
|
|
930
|
+
if (entryRel.split("/").includes("node_modules")) continue;
|
|
931
|
+
if (!entry.isDirectory() && isIgnoredFile(entry.name)) continue;
|
|
932
|
+
pushEntry(entryAbs, entryRel, entry.isDirectory());
|
|
933
|
+
if (entry.isDirectory() && node.depth < DEFAULT_RECURSIVE_DEPTH) {
|
|
934
|
+
queue.push({ dir: entryAbs, depth: node.depth + 1, rel: node.rel ? node.rel + "/" + entry.name : entry.name + "/" });
|
|
935
|
+
}
|
|
936
|
+
if (results.length >= MAX_RESULTS) break;
|
|
937
|
+
}
|
|
938
|
+
} catch (e) {
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
} else {
|
|
942
|
+
const entries = fs.readdirSync(listDir, { withFileTypes: true });
|
|
943
|
+
for (const entry of entries) {
|
|
944
|
+
if (filterPrefix && !entry.name.startsWith(filterPrefix)) continue;
|
|
945
|
+
if (isIgnoredName(entry.name)) continue;
|
|
946
|
+
if (!entry.isDirectory() && isIgnoredFile(entry.name)) continue;
|
|
947
|
+
const entryAbs = path.join(listDir, entry.name);
|
|
948
|
+
const label = relDir ? path.posix.join(relDir, entry.name) : entry.name;
|
|
949
|
+
pushEntry(entryAbs, label, entry.isDirectory());
|
|
950
|
+
if (results.length >= MAX_RESULTS) break;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
1171
953
|
} catch (e) {
|
|
1172
|
-
filepath = "Error parsing arguments";
|
|
1173
954
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
]
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
955
|
+
return results.slice(0, MAX_RESULTS);
|
|
956
|
+
}
|
|
957
|
+
function useAtCompletion({
|
|
958
|
+
cwd,
|
|
959
|
+
text,
|
|
960
|
+
cursorPosition,
|
|
961
|
+
setText
|
|
962
|
+
}) {
|
|
963
|
+
const [open, setOpen] = useState(false);
|
|
964
|
+
const [selected, setSelected] = useState(0);
|
|
965
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
966
|
+
const lastQuery = useRef2("");
|
|
967
|
+
function scanForAt(text2, pos) {
|
|
968
|
+
const before = text2.slice(0, pos);
|
|
969
|
+
const m = before.match(/@([\w\/.\-_]*)$/);
|
|
970
|
+
if (!m) return { pattern: null, insertStart: -1 };
|
|
971
|
+
return { pattern: m[1] || "", insertStart: m.index + 1 };
|
|
972
|
+
}
|
|
973
|
+
function update(newText, newCursor) {
|
|
974
|
+
const res = scanForAt(newText, newCursor);
|
|
975
|
+
if (res.pattern !== null && res.pattern.length >= 0) {
|
|
976
|
+
setOpen(true);
|
|
977
|
+
globalThis.__BLUMA_AT_OPEN__ = true;
|
|
978
|
+
const suggs = listPathSuggestions(cwd, res.pattern);
|
|
979
|
+
setSuggestions(suggs);
|
|
980
|
+
setSelected(0);
|
|
981
|
+
lastQuery.current = res.pattern;
|
|
982
|
+
} else {
|
|
983
|
+
setOpen(false);
|
|
984
|
+
globalThis.__BLUMA_AT_OPEN__ = false;
|
|
985
|
+
setSuggestions([]);
|
|
1197
986
|
}
|
|
1198
|
-
} else {
|
|
1199
|
-
formattedArgsString = JSON.stringify(rawArguments, null, 2);
|
|
1200
987
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
] })
|
|
1220
|
-
] })
|
|
1221
|
-
] });
|
|
1222
|
-
};
|
|
1223
|
-
var renderTodoTool = ({ toolCall }) => {
|
|
1224
|
-
try {
|
|
1225
|
-
const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
|
|
1226
|
-
const tasks = args.tasks || [];
|
|
1227
|
-
if (tasks.length === 0) {
|
|
1228
|
-
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1229
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1230
|
-
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1231
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " todo" })
|
|
1232
|
-
] }),
|
|
1233
|
-
/* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Empty task list" }) })
|
|
1234
|
-
] });
|
|
988
|
+
useEffect2(() => {
|
|
989
|
+
update(text, cursorPosition);
|
|
990
|
+
}, [text, cursorPosition, cwd]);
|
|
991
|
+
function insertAtSelection() {
|
|
992
|
+
if (!open || !suggestions[selected]) return;
|
|
993
|
+
const res = scanForAt(text, cursorPosition);
|
|
994
|
+
if (!res || res.insertStart < 0) return;
|
|
995
|
+
let chosen = suggestions[selected].label;
|
|
996
|
+
const isDir = suggestions[selected].isDir;
|
|
997
|
+
chosen = chosen.replace(/\\/g, "/").replace(/\|/g, "");
|
|
998
|
+
let insertVal = chosen.replace(/\/+$/g, "");
|
|
999
|
+
const currentPattern = res.pattern || "";
|
|
1000
|
+
if (currentPattern.length > 0) {
|
|
1001
|
+
const normalizedPattern = currentPattern.replace(/\/+$/g, "");
|
|
1002
|
+
if (insertVal.startsWith(normalizedPattern)) {
|
|
1003
|
+
insertVal = insertVal.slice(normalizedPattern.length);
|
|
1004
|
+
insertVal = insertVal.replace(/^\/+/, "");
|
|
1005
|
+
}
|
|
1235
1006
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1007
|
+
insertVal = insertVal.split("\\").join("/");
|
|
1008
|
+
if (isDir && !insertVal.endsWith("/")) insertVal = insertVal + "/";
|
|
1009
|
+
const pattern = res.pattern || "";
|
|
1010
|
+
const lastSlash = pattern.lastIndexOf("/");
|
|
1011
|
+
const segmentOffset = lastSlash >= 0 ? lastSlash + 1 : 0;
|
|
1012
|
+
const segmentStart = res.insertStart + segmentOffset;
|
|
1013
|
+
const before = text.slice(0, segmentStart);
|
|
1014
|
+
const after = text.slice(cursorPosition);
|
|
1015
|
+
const newText = before + insertVal + after;
|
|
1016
|
+
const finalText = newText + " ";
|
|
1017
|
+
setText(finalText, finalText.length);
|
|
1018
|
+
globalThis.__BLUMA_FORCE_CURSOR_END__ = true;
|
|
1019
|
+
update(finalText, finalText.length);
|
|
1020
|
+
if (isDir) {
|
|
1021
|
+
setOpen(false);
|
|
1022
|
+
setSuggestions([]);
|
|
1023
|
+
setTimeout(() => {
|
|
1024
|
+
setOpen(true);
|
|
1025
|
+
update(finalText, finalText.length);
|
|
1026
|
+
}, 0);
|
|
1027
|
+
} else {
|
|
1028
|
+
setOpen(false);
|
|
1029
|
+
setSuggestions([]);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function close() {
|
|
1033
|
+
setOpen(false);
|
|
1034
|
+
setSuggestions([]);
|
|
1035
|
+
}
|
|
1036
|
+
return { open, suggestions, selected, setSelected, insertAtSelection, close, update };
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/app/ui/components/InputPrompt.tsx
|
|
1040
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1041
|
+
var uiEventBus = global.__bluma_ui_eventbus__ || new EventEmitter();
|
|
1042
|
+
global.__bluma_ui_eventbus__ = uiEventBus;
|
|
1043
|
+
var TextLine = memo2(({
|
|
1044
|
+
line,
|
|
1045
|
+
lineIndex,
|
|
1046
|
+
cursorLine,
|
|
1047
|
+
cursorCol,
|
|
1048
|
+
showCursor
|
|
1049
|
+
}) => {
|
|
1050
|
+
const isCursorLine = lineIndex === cursorLine;
|
|
1051
|
+
if (!isCursorLine || !showCursor) {
|
|
1052
|
+
return /* @__PURE__ */ jsx2(Text2, { children: line });
|
|
1053
|
+
}
|
|
1054
|
+
const before = line.slice(0, cursorCol);
|
|
1055
|
+
const char = line[cursorCol] || " ";
|
|
1056
|
+
const after = line.slice(cursorCol + 1);
|
|
1057
|
+
return /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
1058
|
+
before,
|
|
1059
|
+
/* @__PURE__ */ jsx2(Text2, { inverse: true, color: "white", children: char }),
|
|
1060
|
+
after
|
|
1061
|
+
] });
|
|
1062
|
+
}, (prev, next) => {
|
|
1063
|
+
if (prev.line !== next.line) return false;
|
|
1064
|
+
if (prev.showCursor !== next.showCursor) return false;
|
|
1065
|
+
const prevIsCursorLine = prev.lineIndex === prev.cursorLine;
|
|
1066
|
+
const nextIsCursorLine = next.lineIndex === next.cursorLine;
|
|
1067
|
+
if (prevIsCursorLine !== nextIsCursorLine) return false;
|
|
1068
|
+
if (nextIsCursorLine && prev.cursorCol !== next.cursorCol) return false;
|
|
1069
|
+
return true;
|
|
1070
|
+
});
|
|
1071
|
+
TextLine.displayName = "TextLine";
|
|
1072
|
+
var PathSuggestions = memo2(({
|
|
1073
|
+
suggestions,
|
|
1074
|
+
selected
|
|
1075
|
+
}) => {
|
|
1076
|
+
const VISIBLE = 7;
|
|
1077
|
+
const total = suggestions.length;
|
|
1078
|
+
const sel = Math.max(0, Math.min(selected, total - 1));
|
|
1079
|
+
let start = Math.max(0, sel - Math.floor(VISIBLE / 2));
|
|
1080
|
+
if (start + VISIBLE > total) start = Math.max(0, total - VISIBLE);
|
|
1081
|
+
const windowItems = suggestions.slice(start, start + VISIBLE);
|
|
1082
|
+
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, height: Math.min(VISIBLE, total), children: windowItems.map((s, idx) => {
|
|
1083
|
+
const realIdx = start + idx;
|
|
1084
|
+
const isSelected = realIdx === selected;
|
|
1085
|
+
return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, paddingY: 0, children: [
|
|
1086
|
+
/* @__PURE__ */ jsx2(Text2, { color: isSelected ? "magenta" : "gray", children: isSelected ? "\u276F " : " " }),
|
|
1087
|
+
/* @__PURE__ */ jsx2(Text2, { color: isSelected ? "magenta" : "white", bold: isSelected, dimColor: !isSelected, children: s.label })
|
|
1088
|
+
] }, s.fullPath);
|
|
1089
|
+
}) });
|
|
1090
|
+
});
|
|
1091
|
+
PathSuggestions.displayName = "PathSuggestions";
|
|
1092
|
+
var SlashSuggestions = memo2(({
|
|
1093
|
+
suggestions,
|
|
1094
|
+
selectedIndex
|
|
1095
|
+
}) => /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: suggestions.map((s, idx) => {
|
|
1096
|
+
const isSelected = idx === selectedIndex;
|
|
1097
|
+
return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, paddingY: 0, children: [
|
|
1098
|
+
/* @__PURE__ */ jsx2(Text2, { color: isSelected ? "magenta" : "gray", children: isSelected ? "\u276F " : " " }),
|
|
1099
|
+
/* @__PURE__ */ jsxs2(Text2, { color: isSelected ? "magenta" : "white", bold: isSelected, dimColor: !isSelected, children: [
|
|
1100
|
+
s.name,
|
|
1101
|
+
" ",
|
|
1102
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
1103
|
+
"- ",
|
|
1104
|
+
s.description
|
|
1105
|
+
] })
|
|
1106
|
+
] })
|
|
1107
|
+
] }, s.name);
|
|
1108
|
+
}) }));
|
|
1109
|
+
SlashSuggestions.displayName = "SlashSuggestions";
|
|
1110
|
+
var Footer = memo2(({ isReadOnly }) => /* @__PURE__ */ jsx2(Box2, { paddingX: 1, justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, children: isReadOnly ? "ctrl+c to exit | Enter to send message | Shift+Enter for new line | esc interrupt" : "ctrl+c to exit | Enter to submit | Shift+Enter for new line | /help commands | esc interrupt" }) }));
|
|
1111
|
+
Footer.displayName = "Footer";
|
|
1112
|
+
var TextLinesRenderer = memo2(({
|
|
1113
|
+
lines,
|
|
1114
|
+
cursorLine,
|
|
1115
|
+
cursorCol,
|
|
1116
|
+
showCursor,
|
|
1117
|
+
showPlaceholder,
|
|
1118
|
+
placeholder
|
|
1119
|
+
}) => {
|
|
1120
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: lines.map((line, idx) => {
|
|
1121
|
+
const isFirstLine = idx === 0;
|
|
1122
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", paddingX: 1, children: [
|
|
1123
|
+
isFirstLine && /* @__PURE__ */ jsxs2(Text2, { color: "white", children: [
|
|
1124
|
+
">",
|
|
1125
|
+
" "
|
|
1126
|
+
] }),
|
|
1127
|
+
!isFirstLine && /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
1128
|
+
"\u2502",
|
|
1129
|
+
" "
|
|
1130
|
+
] }),
|
|
1131
|
+
showPlaceholder && isFirstLine && line.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsx2(
|
|
1132
|
+
TextLine,
|
|
1133
|
+
{
|
|
1134
|
+
line,
|
|
1135
|
+
lineIndex: idx,
|
|
1136
|
+
cursorLine,
|
|
1137
|
+
cursorCol,
|
|
1138
|
+
showCursor
|
|
1139
|
+
}
|
|
1140
|
+
)
|
|
1141
|
+
] }, idx);
|
|
1142
|
+
}) });
|
|
1143
|
+
});
|
|
1144
|
+
TextLinesRenderer.displayName = "TextLinesRenderer";
|
|
1145
|
+
var InputPrompt = memo2(({
|
|
1146
|
+
onSubmit,
|
|
1147
|
+
isReadOnly,
|
|
1148
|
+
onInterrupt,
|
|
1149
|
+
disableWhileProcessing = false
|
|
1150
|
+
}) => {
|
|
1151
|
+
const { stdout } = useStdout();
|
|
1152
|
+
const [viewWidth] = useState2(() => stdout.columns - 6);
|
|
1153
|
+
const [slashOpen, setSlashOpen] = useState2(false);
|
|
1154
|
+
const [slashIndex, setSlashIndex] = useState2(0);
|
|
1155
|
+
const permissiveOnSubmit = (value) => {
|
|
1156
|
+
const trimmed = (value || "").trim();
|
|
1157
|
+
if (isReadOnly) {
|
|
1158
|
+
if (trimmed.length > 0) {
|
|
1159
|
+
uiEventBus.emit("user_overlay", { kind: "message", payload: trimmed, ts: Date.now() });
|
|
1160
|
+
}
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
onSubmit(value);
|
|
1164
|
+
};
|
|
1165
|
+
const { text, cursorPosition, setText } = useCustomInput({
|
|
1166
|
+
onSubmit: (value) => {
|
|
1167
|
+
if (disableWhileProcessing && isReadOnly) return;
|
|
1168
|
+
if (pathAutocomplete.open) return;
|
|
1169
|
+
permissiveOnSubmit(value);
|
|
1170
|
+
},
|
|
1171
|
+
viewWidth,
|
|
1172
|
+
isReadOnly,
|
|
1173
|
+
onInterrupt
|
|
1174
|
+
});
|
|
1175
|
+
const linesData = useMemo(() => {
|
|
1176
|
+
const lines = text.split("\n");
|
|
1177
|
+
let remainingChars = cursorPosition;
|
|
1178
|
+
let cursorLine = 0;
|
|
1179
|
+
let cursorCol = 0;
|
|
1180
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1181
|
+
const lineLength = lines[i].length;
|
|
1182
|
+
if (remainingChars <= lineLength) {
|
|
1183
|
+
cursorLine = i;
|
|
1184
|
+
cursorCol = remainingChars;
|
|
1185
|
+
break;
|
|
1186
|
+
}
|
|
1187
|
+
remainingChars -= lineLength + 1;
|
|
1188
|
+
if (i === lines.length - 1) {
|
|
1189
|
+
cursorLine = i;
|
|
1190
|
+
cursorCol = lineLength;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return { lines, cursorLine, cursorCol, totalLines: lines.length };
|
|
1194
|
+
}, [text, cursorPosition]);
|
|
1195
|
+
const displayData = linesData;
|
|
1196
|
+
const placeholder = isReadOnly ? " Press Esc to cancel | Enter message while agent runs" : "";
|
|
1197
|
+
const showPlaceholder = text.length === 0 && isReadOnly;
|
|
1198
|
+
const slashQuery = useMemo(() => text.startsWith("/") ? text : "", [text]);
|
|
1199
|
+
const slashSuggestions = useMemo(() => {
|
|
1200
|
+
if (!slashQuery) return [];
|
|
1201
|
+
return filterSlashCommands(slashQuery);
|
|
1202
|
+
}, [slashQuery]);
|
|
1203
|
+
useEffect3(() => {
|
|
1204
|
+
if (isReadOnly) {
|
|
1205
|
+
setSlashOpen(false);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
if (text.startsWith("/")) {
|
|
1209
|
+
setSlashOpen(true);
|
|
1210
|
+
setSlashIndex(0);
|
|
1211
|
+
} else {
|
|
1212
|
+
setSlashOpen(false);
|
|
1213
|
+
}
|
|
1214
|
+
}, [text, isReadOnly]);
|
|
1215
|
+
useInput2((input, key) => {
|
|
1216
|
+
if (!slashOpen) return;
|
|
1217
|
+
if (key.downArrow) {
|
|
1218
|
+
setSlashIndex((i) => Math.min(i + 1, Math.max(0, slashSuggestions.length - 1)));
|
|
1219
|
+
} else if (key.upArrow) {
|
|
1220
|
+
setSlashIndex((i) => Math.max(i - 1, 0));
|
|
1221
|
+
} else if (key.return) {
|
|
1222
|
+
const choice = slashSuggestions[slashIndex];
|
|
1223
|
+
if (choice) {
|
|
1224
|
+
setSlashOpen(false);
|
|
1225
|
+
try {
|
|
1226
|
+
setText(`${choice.name} `);
|
|
1227
|
+
} catch (e) {
|
|
1228
|
+
permissiveOnSubmit(`${choice.name} `);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
} else if (key.escape) {
|
|
1232
|
+
setSlashOpen(false);
|
|
1233
|
+
}
|
|
1234
|
+
}, { isActive: slashOpen });
|
|
1235
|
+
useEffect3(() => {
|
|
1236
|
+
if (globalThis.__BLUMA_FORCE_CURSOR_END__) {
|
|
1237
|
+
setText(text, text.length);
|
|
1238
|
+
delete globalThis.__BLUMA_FORCE_CURSOR_END__;
|
|
1239
|
+
}
|
|
1240
|
+
}, [text, setText]);
|
|
1241
|
+
const cwd = process.cwd();
|
|
1242
|
+
const pathAutocomplete = useAtCompletion({ cwd, text, cursorPosition, setText });
|
|
1243
|
+
useInput2((input, key) => {
|
|
1244
|
+
if (key.backspace || key.delete || key.ctrl || key.meta) return;
|
|
1245
|
+
if (pathAutocomplete.open) {
|
|
1246
|
+
if (key.downArrow) {
|
|
1247
|
+
pathAutocomplete.setSelected((i) => Math.min(i + 1, Math.max(0, pathAutocomplete.suggestions.length - 1)));
|
|
1248
|
+
return;
|
|
1249
|
+
} else if (key.upArrow) {
|
|
1250
|
+
pathAutocomplete.setSelected((i) => Math.max(i - 1, 0));
|
|
1251
|
+
return;
|
|
1252
|
+
} else if (key.return || key.tab) {
|
|
1253
|
+
const selected = pathAutocomplete.suggestions[pathAutocomplete.selected];
|
|
1254
|
+
if (selected) {
|
|
1255
|
+
const before = text.slice(0, cursorPosition);
|
|
1256
|
+
const m = before.match(/@([\w\/.\-_]*)$/);
|
|
1257
|
+
if (m) {
|
|
1258
|
+
globalThis.__BLUMA_SUPPRESS_SUBMIT__ = true;
|
|
1259
|
+
pathAutocomplete.insertAtSelection();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return;
|
|
1264
|
+
} else if (key.escape) {
|
|
1265
|
+
pathAutocomplete.close();
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
}, { isActive: true });
|
|
1271
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1272
|
+
disableWhileProcessing ? /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", paddingX: 1, flexWrap: "nowrap", children: [
|
|
1273
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "white", children: [
|
|
1274
|
+
">",
|
|
1275
|
+
" "
|
|
1276
|
+
] }),
|
|
1277
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "ctrl+c to exit" })
|
|
1278
|
+
] }) }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1279
|
+
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsx2(
|
|
1280
|
+
TextLinesRenderer,
|
|
1281
|
+
{
|
|
1282
|
+
lines: displayData.lines,
|
|
1283
|
+
cursorLine: displayData.cursorLine,
|
|
1284
|
+
cursorCol: displayData.cursorCol,
|
|
1285
|
+
showCursor: !isReadOnly,
|
|
1286
|
+
showPlaceholder,
|
|
1287
|
+
placeholder
|
|
1288
|
+
}
|
|
1289
|
+
) }),
|
|
1290
|
+
pathAutocomplete.open && pathAutocomplete.suggestions.length > 0 && /* @__PURE__ */ jsx2(
|
|
1291
|
+
PathSuggestions,
|
|
1292
|
+
{
|
|
1293
|
+
suggestions: pathAutocomplete.suggestions,
|
|
1294
|
+
selected: pathAutocomplete.selected
|
|
1295
|
+
}
|
|
1296
|
+
),
|
|
1297
|
+
slashOpen && slashSuggestions.length > 0 && /* @__PURE__ */ jsx2(
|
|
1298
|
+
SlashSuggestions,
|
|
1299
|
+
{
|
|
1300
|
+
suggestions: slashSuggestions,
|
|
1301
|
+
selectedIndex: slashIndex
|
|
1302
|
+
}
|
|
1303
|
+
)
|
|
1304
|
+
] }),
|
|
1305
|
+
/* @__PURE__ */ jsx2(Footer, { isReadOnly })
|
|
1306
|
+
] });
|
|
1307
|
+
});
|
|
1308
|
+
InputPrompt.displayName = "InputPrompt";
|
|
1309
|
+
|
|
1310
|
+
// src/app/ui/ConfirmationPrompt.tsx
|
|
1311
|
+
import { memo as memo4 } from "react";
|
|
1312
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
1313
|
+
|
|
1314
|
+
// src/app/ui/InteractiveMenu.tsx
|
|
1315
|
+
import { useState as useState3, memo as memo3 } from "react";
|
|
1316
|
+
import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
|
|
1317
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1318
|
+
var InteractiveMenuComponent = ({ onDecision }) => {
|
|
1319
|
+
const options = [
|
|
1320
|
+
{ key: "y", label: "yes", value: "accept", color: "green" },
|
|
1321
|
+
{ key: "n", label: "no", value: "decline", color: "red" },
|
|
1322
|
+
{ key: "a", label: "always", value: "accept_always", color: "yellow" }
|
|
1323
|
+
];
|
|
1324
|
+
const [selected, setSelected] = useState3(0);
|
|
1325
|
+
useInput3((input, key) => {
|
|
1326
|
+
if (key.upArrow || key.leftArrow) {
|
|
1327
|
+
setSelected((i) => i > 0 ? i - 1 : options.length - 1);
|
|
1328
|
+
}
|
|
1329
|
+
if (key.downArrow || key.rightArrow) {
|
|
1330
|
+
setSelected((i) => i < options.length - 1 ? i + 1 : 0);
|
|
1331
|
+
}
|
|
1332
|
+
if (key.escape) {
|
|
1333
|
+
onDecision("decline");
|
|
1334
|
+
}
|
|
1335
|
+
if (key.return) {
|
|
1336
|
+
onDecision(options[selected].value);
|
|
1337
|
+
}
|
|
1338
|
+
const opt = options.find((o) => o.key === input.toLowerCase());
|
|
1339
|
+
if (opt) {
|
|
1340
|
+
onDecision(opt.value);
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
|
|
1344
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
1345
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "approve? " }),
|
|
1346
|
+
options.map((opt, idx) => {
|
|
1347
|
+
const isSelected = idx === selected;
|
|
1348
|
+
return /* @__PURE__ */ jsx3(Box3, { marginRight: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: isSelected ? opt.color : "gray", bold: isSelected, children: [
|
|
1349
|
+
"[",
|
|
1350
|
+
opt.key,
|
|
1351
|
+
"]",
|
|
1352
|
+
opt.label
|
|
1353
|
+
] }) }, opt.value);
|
|
1354
|
+
})
|
|
1355
|
+
] }),
|
|
1356
|
+
/* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "arrows to select, enter to confirm, esc to cancel" }) })
|
|
1357
|
+
] });
|
|
1358
|
+
};
|
|
1359
|
+
var InteractiveMenu = memo3(InteractiveMenuComponent);
|
|
1360
|
+
|
|
1361
|
+
// src/app/ui/components/promptRenderers.tsx
|
|
1362
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
1363
|
+
import path2 from "path";
|
|
1364
|
+
|
|
1365
|
+
// src/app/ui/components/SimpleDiff.tsx
|
|
1366
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
1367
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1368
|
+
var SimpleDiff = ({ text, maxHeight }) => {
|
|
1369
|
+
const allLines = (text || "").split("\n").filter((line) => line !== "");
|
|
1370
|
+
const isTruncated = maxHeight > 0 && allLines.length > maxHeight;
|
|
1371
|
+
const linesToRender = isTruncated ? allLines.slice(-maxHeight) : allLines;
|
|
1372
|
+
const hiddenCount = allLines.length - linesToRender.length;
|
|
1373
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 2, children: [
|
|
1374
|
+
isTruncated && /* @__PURE__ */ jsx4(Box4, { marginBottom: 0, children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1375
|
+
"\u22EF ",
|
|
1376
|
+
hiddenCount,
|
|
1377
|
+
" lines hidden"
|
|
1378
|
+
] }) }),
|
|
1379
|
+
linesToRender.map((line, index) => {
|
|
1380
|
+
if (line.startsWith("---") || line.startsWith("+++")) {
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
let color = "white";
|
|
1384
|
+
let prefix = "";
|
|
1385
|
+
if (line.startsWith("+")) {
|
|
1386
|
+
color = "green";
|
|
1387
|
+
prefix = "+ ";
|
|
1388
|
+
} else if (line.startsWith("-")) {
|
|
1389
|
+
color = "red";
|
|
1390
|
+
prefix = "- ";
|
|
1391
|
+
} else if (line.startsWith("@@")) {
|
|
1392
|
+
color = "cyan";
|
|
1393
|
+
prefix = "";
|
|
1394
|
+
} else {
|
|
1395
|
+
color = "gray";
|
|
1396
|
+
prefix = " ";
|
|
1397
|
+
}
|
|
1398
|
+
return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color, children: [
|
|
1399
|
+
prefix,
|
|
1400
|
+
line.replace(/^[+\-]/, "")
|
|
1401
|
+
] }) }, index);
|
|
1402
|
+
})
|
|
1403
|
+
] });
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
// src/app/ui/components/promptRenderers.tsx
|
|
1407
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1408
|
+
var getBasePath = (filePath) => {
|
|
1409
|
+
return path2.basename(filePath);
|
|
1410
|
+
};
|
|
1411
|
+
var renderShellCommand = ({ toolCall }) => {
|
|
1412
|
+
let command = "";
|
|
1413
|
+
try {
|
|
1414
|
+
const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
|
|
1415
|
+
command = args.command || "[command not found]";
|
|
1416
|
+
} catch (e) {
|
|
1417
|
+
command = "Error parsing command";
|
|
1418
|
+
}
|
|
1419
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1420
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1421
|
+
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1422
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " shell" })
|
|
1423
|
+
] }),
|
|
1424
|
+
/* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: command }) })
|
|
1425
|
+
] });
|
|
1426
|
+
};
|
|
1427
|
+
var renderLsTool = ({ toolCall }) => {
|
|
1428
|
+
let directoryPath = "[path not specified]";
|
|
1429
|
+
try {
|
|
1430
|
+
const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
|
|
1431
|
+
directoryPath = args.directory_path || "[path not specified]";
|
|
1432
|
+
} catch (e) {
|
|
1433
|
+
directoryPath = "Error parsing arguments";
|
|
1434
|
+
}
|
|
1435
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1436
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1437
|
+
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1438
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " ls" })
|
|
1439
|
+
] }),
|
|
1440
|
+
/* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: directoryPath }) })
|
|
1441
|
+
] });
|
|
1442
|
+
};
|
|
1443
|
+
var renderCountFilesLinesTool = ({ toolCall }) => {
|
|
1444
|
+
let filepath = "[path not specified]";
|
|
1445
|
+
try {
|
|
1446
|
+
const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
|
|
1447
|
+
filepath = args.filepath || "[path not specified]";
|
|
1448
|
+
} catch (e) {
|
|
1449
|
+
filepath = "Error parsing arguments";
|
|
1450
|
+
}
|
|
1451
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1452
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1453
|
+
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1454
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " count lines" })
|
|
1455
|
+
] }),
|
|
1456
|
+
/* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: filepath }) })
|
|
1457
|
+
] });
|
|
1458
|
+
};
|
|
1459
|
+
var renderReadFileLines = ({ toolCall }) => {
|
|
1460
|
+
let filepath = "[path not specified]";
|
|
1461
|
+
let startLine = 0;
|
|
1462
|
+
let endLine = 0;
|
|
1463
|
+
try {
|
|
1464
|
+
const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
|
|
1465
|
+
filepath = args.filepath || "[path not specified]";
|
|
1466
|
+
startLine = args.start_line || 0;
|
|
1467
|
+
endLine = args.end_line || 0;
|
|
1468
|
+
} catch (e) {
|
|
1469
|
+
filepath = "Error parsing arguments";
|
|
1470
|
+
}
|
|
1471
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1472
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1473
|
+
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1474
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " read" })
|
|
1475
|
+
] }),
|
|
1476
|
+
/* @__PURE__ */ jsxs5(Box5, { paddingLeft: 2, flexDirection: "column", children: [
|
|
1477
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: filepath }),
|
|
1478
|
+
/* @__PURE__ */ jsxs5(Box5, { paddingLeft: 2, children: [
|
|
1479
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "lines " }),
|
|
1480
|
+
/* @__PURE__ */ jsx5(Text5, { color: "magenta", children: startLine }),
|
|
1481
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " to " }),
|
|
1482
|
+
/* @__PURE__ */ jsx5(Text5, { color: "magenta", children: endLine })
|
|
1483
|
+
] })
|
|
1484
|
+
] })
|
|
1485
|
+
] });
|
|
1486
|
+
};
|
|
1487
|
+
var renderEditTool = ({ toolCall, preview }) => {
|
|
1488
|
+
const diffMaxHeight = 5;
|
|
1489
|
+
let filepath = "[path not specified]";
|
|
1490
|
+
try {
|
|
1491
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
1492
|
+
filepath = args.file_path || "[path not specified]";
|
|
1493
|
+
} catch (e) {
|
|
1494
|
+
filepath = "Error parsing arguments";
|
|
1495
|
+
}
|
|
1496
|
+
const finalFileName = getBasePath(filepath);
|
|
1497
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1498
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1499
|
+
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1500
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " edit " }),
|
|
1501
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: finalFileName })
|
|
1502
|
+
] }),
|
|
1503
|
+
preview ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(SimpleDiff, { text: preview, maxHeight: diffMaxHeight }) }) : /* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "Generating preview..." }) })
|
|
1504
|
+
] });
|
|
1505
|
+
};
|
|
1506
|
+
var renderGeneric = ({ toolCall }) => {
|
|
1507
|
+
const toolName = toolCall.function.name;
|
|
1508
|
+
const rawArguments = toolCall.function.arguments;
|
|
1509
|
+
const MAX_LINES2 = 5;
|
|
1510
|
+
let formattedArgsString;
|
|
1511
|
+
if (!rawArguments) {
|
|
1512
|
+
formattedArgsString = "";
|
|
1513
|
+
} else if (typeof rawArguments === "string") {
|
|
1514
|
+
try {
|
|
1515
|
+
const parsedJson = JSON.parse(rawArguments);
|
|
1516
|
+
formattedArgsString = JSON.stringify(parsedJson, null, 2);
|
|
1517
|
+
} catch (e) {
|
|
1518
|
+
formattedArgsString = rawArguments;
|
|
1519
|
+
}
|
|
1520
|
+
} else {
|
|
1521
|
+
formattedArgsString = JSON.stringify(rawArguments, null, 2);
|
|
1522
|
+
}
|
|
1523
|
+
const lines = formattedArgsString.split("\n");
|
|
1524
|
+
const isTruncated = lines.length > MAX_LINES2;
|
|
1525
|
+
const visibleLines = isTruncated ? lines.slice(0, MAX_LINES2) : lines;
|
|
1526
|
+
const remainingCount = lines.length - MAX_LINES2;
|
|
1527
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1528
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1529
|
+
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1530
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1531
|
+
" ",
|
|
1532
|
+
toolName
|
|
1533
|
+
] })
|
|
1534
|
+
] }),
|
|
1535
|
+
formattedArgsString && /* @__PURE__ */ jsxs5(Box5, { paddingLeft: 2, flexDirection: "column", children: [
|
|
1536
|
+
visibleLines.map((line, idx) => /* @__PURE__ */ jsx5(Text5, { color: "gray", children: line }, idx)),
|
|
1537
|
+
isTruncated && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1538
|
+
"\u22EF ",
|
|
1539
|
+
remainingCount,
|
|
1540
|
+
" more lines"
|
|
1541
|
+
] })
|
|
1542
|
+
] })
|
|
1543
|
+
] });
|
|
1544
|
+
};
|
|
1545
|
+
var renderTodoTool = ({ toolCall }) => {
|
|
1546
|
+
try {
|
|
1547
|
+
const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
|
|
1548
|
+
const tasks = args.tasks || [];
|
|
1549
|
+
if (tasks.length === 0) {
|
|
1550
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1551
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1552
|
+
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1553
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " todo" })
|
|
1554
|
+
] }),
|
|
1555
|
+
/* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Empty task list" }) })
|
|
1556
|
+
] });
|
|
1557
|
+
}
|
|
1558
|
+
const completed = tasks.filter((t) => t.isComplete === true).length;
|
|
1559
|
+
const pending = tasks.length - completed;
|
|
1560
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1561
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1562
|
+
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
|
|
1563
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " todo" })
|
|
1564
|
+
] }),
|
|
1565
|
+
/* @__PURE__ */ jsxs5(Box5, { paddingLeft: 2, flexDirection: "column", children: [
|
|
1566
|
+
/* @__PURE__ */ jsxs5(Text5, { color: "magenta", children: [
|
|
1567
|
+
"\u{1F4CB} ",
|
|
1568
|
+
pending,
|
|
1569
|
+
" pending, ",
|
|
1570
|
+
completed,
|
|
1571
|
+
" completed"
|
|
1572
|
+
] }),
|
|
1573
|
+
tasks.length > 0 && tasks.length <= 10 && /* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, flexDirection: "column", marginTop: 1, children: tasks.map((task, idx) => {
|
|
1574
|
+
const isComplete = task.isComplete === true;
|
|
1575
|
+
const checkbox = isComplete ? "[X]" : "[ ]";
|
|
1576
|
+
const description = task.description || "No description";
|
|
1577
|
+
const displayText = description.length > 60 ? description.substring(0, 57) + "..." : description;
|
|
1578
|
+
const color = isComplete ? "green" : "yellow";
|
|
1579
|
+
return /* @__PURE__ */ jsxs5(
|
|
1580
|
+
Text5,
|
|
1581
|
+
{
|
|
1582
|
+
color,
|
|
1583
|
+
strikethrough: isComplete,
|
|
1584
|
+
dimColor: isComplete,
|
|
1585
|
+
children: [
|
|
1264
1586
|
checkbox,
|
|
1265
1587
|
" ",
|
|
1266
1588
|
displayText
|
|
@@ -1919,1144 +2241,843 @@ function addTasks(tasks) {
|
|
|
1919
2241
|
taskStore.push(newTask);
|
|
1920
2242
|
}
|
|
1921
2243
|
saveTasksToFile();
|
|
1922
|
-
return createResult(true, `Added ${tasks.length} task(s)`);
|
|
1923
|
-
}
|
|
1924
|
-
function completeTask(taskId) {
|
|
1925
|
-
loadTasksFromFile();
|
|
1926
|
-
const task = taskStore.find((t) => t.id === taskId);
|
|
1927
|
-
if (!task) {
|
|
1928
|
-
return createResult(false, `Task #${taskId} not found`);
|
|
1929
|
-
}
|
|
1930
|
-
task.status = "completed";
|
|
1931
|
-
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1932
|
-
saveTasksToFile();
|
|
1933
|
-
return createResult(true, `Completed: ${task.description}`);
|
|
1934
|
-
}
|
|
1935
|
-
function updateTask(taskId, description, priority) {
|
|
1936
|
-
loadTasksFromFile();
|
|
1937
|
-
const task = taskStore.find((t) => t.id === taskId);
|
|
1938
|
-
if (!task) {
|
|
1939
|
-
return createResult(false, `Task #${taskId} not found`);
|
|
1940
|
-
}
|
|
1941
|
-
if (description) {
|
|
1942
|
-
const error = validateDescription(description);
|
|
1943
|
-
if (error) {
|
|
1944
|
-
return createResult(false, error);
|
|
1945
|
-
}
|
|
1946
|
-
task.description = description.trim();
|
|
1947
|
-
}
|
|
1948
|
-
if (priority) {
|
|
1949
|
-
task.priority = priority;
|
|
1950
|
-
}
|
|
1951
|
-
saveTasksToFile();
|
|
1952
|
-
return createResult(true, `Updated task #${taskId}`);
|
|
1953
|
-
}
|
|
1954
|
-
function removeTask(taskId) {
|
|
1955
|
-
loadTasksFromFile();
|
|
1956
|
-
const index = taskStore.findIndex((t) => t.id === taskId);
|
|
1957
|
-
if (index === -1) {
|
|
1958
|
-
return createResult(false, `Task #${taskId} not found`);
|
|
1959
|
-
}
|
|
1960
|
-
const removed = taskStore.splice(index, 1)[0];
|
|
1961
|
-
saveTasksToFile();
|
|
1962
|
-
return createResult(true, `Removed: ${removed.description}`);
|
|
1963
|
-
}
|
|
1964
|
-
function clearCompleted() {
|
|
1965
|
-
loadTasksFromFile();
|
|
1966
|
-
const before = taskStore.length;
|
|
1967
|
-
taskStore = taskStore.filter((t) => t.status !== "completed");
|
|
1968
|
-
const removed = before - taskStore.length;
|
|
1969
|
-
saveTasksToFile();
|
|
1970
|
-
return createResult(true, `Cleared ${removed} completed task(s)`);
|
|
1971
|
-
}
|
|
1972
|
-
function generateProgressBar(percent) {
|
|
1973
|
-
const width = 10;
|
|
1974
|
-
const filled = Math.round(percent / 100 * width);
|
|
1975
|
-
const empty = width - filled;
|
|
1976
|
-
return "[" + "=".repeat(filled) + " ".repeat(empty) + "]";
|
|
1977
|
-
}
|
|
1978
|
-
async function todo(args) {
|
|
1979
|
-
if ("tasks" in args && !("action" in args)) {
|
|
1980
|
-
loadTasksFromFile();
|
|
1981
|
-
taskStore = [];
|
|
1982
|
-
nextId = 1;
|
|
1983
|
-
if (args.tasks && args.tasks.length > 0) {
|
|
1984
|
-
for (const task of args.tasks) {
|
|
1985
|
-
taskStore.push({
|
|
1986
|
-
id: nextId++,
|
|
1987
|
-
description: task.description,
|
|
1988
|
-
status: task.isComplete ? "completed" : "pending",
|
|
1989
|
-
priority: "medium",
|
|
1990
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1991
|
-
completedAt: task.isComplete ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
1992
|
-
});
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
saveTasksToFile();
|
|
1996
|
-
const stats = calculateStats();
|
|
1997
|
-
const progressBar = generateProgressBar(stats.progress);
|
|
1998
|
-
return createResult(true, `Synced ${taskStore.length} tasks ${progressBar} ${stats.progress}%`);
|
|
1999
|
-
}
|
|
2000
|
-
const fullArgs = args;
|
|
2001
|
-
switch (fullArgs.action) {
|
|
2002
|
-
case "list":
|
|
2003
|
-
return listTasks();
|
|
2004
|
-
case "add":
|
|
2005
|
-
return addTasks(fullArgs.tasks);
|
|
2006
|
-
case "complete":
|
|
2007
|
-
if (!fullArgs.taskId) {
|
|
2008
|
-
return createResult(false, "taskId required for complete action");
|
|
2009
|
-
}
|
|
2010
|
-
return completeTask(fullArgs.taskId);
|
|
2011
|
-
case "update":
|
|
2012
|
-
if (!fullArgs.taskId) {
|
|
2013
|
-
return createResult(false, "taskId required for update action");
|
|
2014
|
-
}
|
|
2015
|
-
return updateTask(fullArgs.taskId, fullArgs.description, fullArgs.priority);
|
|
2016
|
-
case "remove":
|
|
2017
|
-
if (!fullArgs.taskId) {
|
|
2018
|
-
return createResult(false, "taskId required for remove action");
|
|
2019
|
-
}
|
|
2020
|
-
return removeTask(fullArgs.taskId);
|
|
2021
|
-
case "clear":
|
|
2022
|
-
return clearCompleted();
|
|
2023
|
-
case "sync":
|
|
2024
|
-
return addTasks(fullArgs.tasks);
|
|
2025
|
-
default:
|
|
2026
|
-
return createResult(false, `Unknown action: ${fullArgs.action}`);
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
// src/app/agent/tools/natives/find_by_name.ts
|
|
2031
|
-
import path6 from "path";
|
|
2032
|
-
import { promises as fsPromises } from "fs";
|
|
2033
|
-
var DEFAULT_IGNORE_PATTERNS = [
|
|
2034
|
-
"node_modules",
|
|
2035
|
-
".git",
|
|
2036
|
-
".venv",
|
|
2037
|
-
"venv",
|
|
2038
|
-
"__pycache__",
|
|
2039
|
-
".cache",
|
|
2040
|
-
"dist",
|
|
2041
|
-
"build",
|
|
2042
|
-
".next",
|
|
2043
|
-
".nuxt",
|
|
2044
|
-
"coverage",
|
|
2045
|
-
".nyc_output",
|
|
2046
|
-
".pytest_cache",
|
|
2047
|
-
"target",
|
|
2048
|
-
// Rust
|
|
2049
|
-
"vendor"
|
|
2050
|
-
// Go, PHP
|
|
2051
|
-
];
|
|
2052
|
-
var MAX_RESULTS2 = 100;
|
|
2053
|
-
var MAX_DEPTH_DEFAULT = 10;
|
|
2054
|
-
function globToRegex(glob) {
|
|
2055
|
-
let regex = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{DOUBLESTAR}}/g, ".*");
|
|
2056
|
-
return new RegExp(`^${regex}$`, "i");
|
|
2057
|
-
}
|
|
2058
|
-
function shouldIgnore(name, ignorePatterns, includeHidden) {
|
|
2059
|
-
if (!includeHidden && name.startsWith(".")) {
|
|
2060
|
-
return true;
|
|
2061
|
-
}
|
|
2062
|
-
for (const pattern of ignorePatterns) {
|
|
2063
|
-
if (name === pattern || name.match(globToRegex(pattern))) {
|
|
2064
|
-
return true;
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
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
|
-
}
|
|
2244
|
+
return createResult(true, `Added ${tasks.length} task(s)`);
|
|
2245
|
+
}
|
|
2246
|
+
function completeTask(taskId) {
|
|
2247
|
+
loadTasksFromFile();
|
|
2248
|
+
const task = taskStore.find((t) => t.id === taskId);
|
|
2249
|
+
if (!task) {
|
|
2250
|
+
return createResult(false, `Task #${taskId} not found`);
|
|
2127
2251
|
}
|
|
2252
|
+
task.status = "completed";
|
|
2253
|
+
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2254
|
+
saveTasksToFile();
|
|
2255
|
+
return createResult(true, `Completed: ${task.description}`);
|
|
2128
2256
|
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
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
|
-
};
|
|
2257
|
+
function updateTask(taskId, description, priority) {
|
|
2258
|
+
loadTasksFromFile();
|
|
2259
|
+
const task = taskStore.find((t) => t.id === taskId);
|
|
2260
|
+
if (!task) {
|
|
2261
|
+
return createResult(false, `Task #${taskId} not found`);
|
|
2262
|
+
}
|
|
2263
|
+
if (description) {
|
|
2264
|
+
const error = validateDescription(description);
|
|
2265
|
+
if (error) {
|
|
2266
|
+
return createResult(false, error);
|
|
2149
2267
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2268
|
+
task.description = description.trim();
|
|
2269
|
+
}
|
|
2270
|
+
if (priority) {
|
|
2271
|
+
task.priority = priority;
|
|
2272
|
+
}
|
|
2273
|
+
saveTasksToFile();
|
|
2274
|
+
return createResult(true, `Updated task #${taskId}`);
|
|
2275
|
+
}
|
|
2276
|
+
function removeTask(taskId) {
|
|
2277
|
+
loadTasksFromFile();
|
|
2278
|
+
const index = taskStore.findIndex((t) => t.id === taskId);
|
|
2279
|
+
if (index === -1) {
|
|
2280
|
+
return createResult(false, `Task #${taskId} not found`);
|
|
2281
|
+
}
|
|
2282
|
+
const removed = taskStore.splice(index, 1)[0];
|
|
2283
|
+
saveTasksToFile();
|
|
2284
|
+
return createResult(true, `Removed: ${removed.description}`);
|
|
2285
|
+
}
|
|
2286
|
+
function clearCompleted() {
|
|
2287
|
+
loadTasksFromFile();
|
|
2288
|
+
const before = taskStore.length;
|
|
2289
|
+
taskStore = taskStore.filter((t) => t.status !== "completed");
|
|
2290
|
+
const removed = before - taskStore.length;
|
|
2291
|
+
saveTasksToFile();
|
|
2292
|
+
return createResult(true, `Cleared ${removed} completed task(s)`);
|
|
2293
|
+
}
|
|
2294
|
+
function generateProgressBar(percent) {
|
|
2295
|
+
const width = 10;
|
|
2296
|
+
const filled = Math.round(percent / 100 * width);
|
|
2297
|
+
const empty = width - filled;
|
|
2298
|
+
return "[" + "=".repeat(filled) + " ".repeat(empty) + "]";
|
|
2299
|
+
}
|
|
2300
|
+
async function todo(args) {
|
|
2301
|
+
if ("tasks" in args && !("action" in args)) {
|
|
2302
|
+
loadTasksFromFile();
|
|
2303
|
+
taskStore = [];
|
|
2304
|
+
nextId = 1;
|
|
2305
|
+
if (args.tasks && args.tasks.length > 0) {
|
|
2306
|
+
for (const task of args.tasks) {
|
|
2307
|
+
taskStore.push({
|
|
2308
|
+
id: nextId++,
|
|
2309
|
+
description: task.description,
|
|
2310
|
+
status: task.isComplete ? "completed" : "pending",
|
|
2311
|
+
priority: "medium",
|
|
2312
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2313
|
+
completedAt: task.isComplete ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
2314
|
+
});
|
|
2163
2315
|
}
|
|
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
2316
|
}
|
|
2175
|
-
|
|
2176
|
-
const
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2317
|
+
saveTasksToFile();
|
|
2318
|
+
const stats = calculateStats();
|
|
2319
|
+
const progressBar = generateProgressBar(stats.progress);
|
|
2320
|
+
return createResult(true, `Synced ${taskStore.length} tasks ${progressBar} ${stats.progress}%`);
|
|
2321
|
+
}
|
|
2322
|
+
const fullArgs = args;
|
|
2323
|
+
switch (fullArgs.action) {
|
|
2324
|
+
case "list":
|
|
2325
|
+
return listTasks();
|
|
2326
|
+
case "add":
|
|
2327
|
+
return addTasks(fullArgs.tasks);
|
|
2328
|
+
case "complete":
|
|
2329
|
+
if (!fullArgs.taskId) {
|
|
2330
|
+
return createResult(false, "taskId required for complete action");
|
|
2331
|
+
}
|
|
2332
|
+
return completeTask(fullArgs.taskId);
|
|
2333
|
+
case "update":
|
|
2334
|
+
if (!fullArgs.taskId) {
|
|
2335
|
+
return createResult(false, "taskId required for update action");
|
|
2336
|
+
}
|
|
2337
|
+
return updateTask(fullArgs.taskId, fullArgs.description, fullArgs.priority);
|
|
2338
|
+
case "remove":
|
|
2339
|
+
if (!fullArgs.taskId) {
|
|
2340
|
+
return createResult(false, "taskId required for remove action");
|
|
2341
|
+
}
|
|
2342
|
+
return removeTask(fullArgs.taskId);
|
|
2343
|
+
case "clear":
|
|
2344
|
+
return clearCompleted();
|
|
2345
|
+
case "sync":
|
|
2346
|
+
return addTasks(fullArgs.tasks);
|
|
2347
|
+
default:
|
|
2348
|
+
return createResult(false, `Unknown action: ${fullArgs.action}`);
|
|
2207
2349
|
}
|
|
2208
2350
|
}
|
|
2209
|
-
|
|
2210
|
-
// src/app/agent/tools/natives/
|
|
2211
|
-
import
|
|
2212
|
-
import { promises as
|
|
2213
|
-
var
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
"
|
|
2218
|
-
"
|
|
2219
|
-
".
|
|
2220
|
-
"
|
|
2221
|
-
"
|
|
2222
|
-
".
|
|
2223
|
-
"
|
|
2224
|
-
"
|
|
2225
|
-
".
|
|
2226
|
-
"
|
|
2227
|
-
"
|
|
2228
|
-
|
|
2229
|
-
"
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
"
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
".
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
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) {
|
|
2351
|
+
|
|
2352
|
+
// src/app/agent/tools/natives/find_by_name.ts
|
|
2353
|
+
import path6 from "path";
|
|
2354
|
+
import { promises as fsPromises } from "fs";
|
|
2355
|
+
var DEFAULT_IGNORE_PATTERNS = [
|
|
2356
|
+
"node_modules",
|
|
2357
|
+
".git",
|
|
2358
|
+
".venv",
|
|
2359
|
+
"venv",
|
|
2360
|
+
"__pycache__",
|
|
2361
|
+
".cache",
|
|
2362
|
+
"dist",
|
|
2363
|
+
"build",
|
|
2364
|
+
".next",
|
|
2365
|
+
".nuxt",
|
|
2366
|
+
"coverage",
|
|
2367
|
+
".nyc_output",
|
|
2368
|
+
".pytest_cache",
|
|
2369
|
+
"target",
|
|
2370
|
+
// Rust
|
|
2371
|
+
"vendor"
|
|
2372
|
+
// Go, PHP
|
|
2373
|
+
];
|
|
2374
|
+
var MAX_RESULTS2 = 100;
|
|
2375
|
+
var MAX_DEPTH_DEFAULT = 10;
|
|
2376
|
+
function globToRegex(glob) {
|
|
2377
|
+
let regex = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{DOUBLESTAR}}/g, ".*");
|
|
2378
|
+
return new RegExp(`^${regex}$`, "i");
|
|
2379
|
+
}
|
|
2380
|
+
function shouldIgnore(name, ignorePatterns, includeHidden) {
|
|
2381
|
+
if (!includeHidden && name.startsWith(".")) {
|
|
2382
|
+
return true;
|
|
2383
|
+
}
|
|
2384
|
+
for (const pattern of ignorePatterns) {
|
|
2385
|
+
if (name === pattern || name.match(globToRegex(pattern))) {
|
|
2307
2386
|
return true;
|
|
2308
2387
|
}
|
|
2309
2388
|
}
|
|
2310
2389
|
return false;
|
|
2311
2390
|
}
|
|
2312
|
-
function
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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;
|
|
2391
|
+
function matchesExtensions(filename, extensions) {
|
|
2392
|
+
if (!extensions || extensions.length === 0) return true;
|
|
2393
|
+
const ext = path6.extname(filename).toLowerCase();
|
|
2394
|
+
return extensions.some((e) => {
|
|
2395
|
+
const normalizedExt = e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`;
|
|
2396
|
+
return ext === normalizedExt;
|
|
2397
|
+
});
|
|
2332
2398
|
}
|
|
2333
|
-
async function
|
|
2334
|
-
|
|
2335
|
-
|
|
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) {
|
|
2399
|
+
async function searchDirectory(dir, pattern, baseDir, options, results) {
|
|
2400
|
+
if (options.currentDepth > options.maxDepth || results.length >= MAX_RESULTS2) {
|
|
2401
|
+
return;
|
|
2365
2402
|
}
|
|
2366
|
-
return fileMatches;
|
|
2367
|
-
}
|
|
2368
|
-
async function searchDirectory2(dir, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats) {
|
|
2369
|
-
if (matches.length >= maxResults) return;
|
|
2370
2403
|
let entries;
|
|
2371
2404
|
try {
|
|
2372
|
-
entries = await
|
|
2373
|
-
} catch {
|
|
2405
|
+
entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
2406
|
+
} catch (error) {
|
|
2374
2407
|
return;
|
|
2375
2408
|
}
|
|
2376
2409
|
for (const entry of entries) {
|
|
2377
|
-
if (
|
|
2410
|
+
if (results.length >= MAX_RESULTS2) break;
|
|
2378
2411
|
const name = entry.name;
|
|
2379
|
-
if (
|
|
2380
|
-
|
|
2412
|
+
if (shouldIgnore(name, options.ignorePatterns, options.includeHidden)) {
|
|
2413
|
+
continue;
|
|
2414
|
+
}
|
|
2415
|
+
const fullPath = path6.join(dir, name);
|
|
2416
|
+
const relativePath = path6.relative(baseDir, fullPath);
|
|
2381
2417
|
if (entry.isDirectory()) {
|
|
2382
|
-
|
|
2418
|
+
if (pattern.test(name)) {
|
|
2419
|
+
results.push({
|
|
2420
|
+
path: fullPath,
|
|
2421
|
+
relative_path: relativePath,
|
|
2422
|
+
type: "directory"
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
await searchDirectory(fullPath, pattern, baseDir, {
|
|
2426
|
+
...options,
|
|
2427
|
+
currentDepth: options.currentDepth + 1
|
|
2428
|
+
}, results);
|
|
2383
2429
|
} else if (entry.isFile()) {
|
|
2384
|
-
if (
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2430
|
+
if (pattern.test(name) && matchesExtensions(name, options.extensions)) {
|
|
2431
|
+
try {
|
|
2432
|
+
const stats = await fsPromises.stat(fullPath);
|
|
2433
|
+
results.push({
|
|
2434
|
+
path: fullPath,
|
|
2435
|
+
relative_path: relativePath,
|
|
2436
|
+
type: "file",
|
|
2437
|
+
size: stats.size,
|
|
2438
|
+
modified: stats.mtime.toISOString()
|
|
2439
|
+
});
|
|
2440
|
+
} catch {
|
|
2441
|
+
results.push({
|
|
2442
|
+
path: fullPath,
|
|
2443
|
+
relative_path: relativePath,
|
|
2444
|
+
type: "file"
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2389
2448
|
}
|
|
2390
2449
|
}
|
|
2391
2450
|
}
|
|
2392
|
-
async function
|
|
2451
|
+
async function findByName(args) {
|
|
2393
2452
|
try {
|
|
2394
2453
|
const {
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
context_lines = 0
|
|
2454
|
+
pattern,
|
|
2455
|
+
directory = process.cwd(),
|
|
2456
|
+
extensions,
|
|
2457
|
+
max_depth = MAX_DEPTH_DEFAULT,
|
|
2458
|
+
include_hidden = false,
|
|
2459
|
+
exclude_patterns = []
|
|
2402
2460
|
} = args;
|
|
2403
|
-
if (!
|
|
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 {
|
|
2461
|
+
if (!pattern || typeof pattern !== "string") {
|
|
2434
2462
|
return {
|
|
2435
2463
|
success: false,
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
files_with_matches: 0,
|
|
2441
|
-
total_matches: 0,
|
|
2464
|
+
pattern: pattern || "",
|
|
2465
|
+
directory,
|
|
2466
|
+
results: [],
|
|
2467
|
+
total_found: 0,
|
|
2442
2468
|
truncated: false,
|
|
2443
|
-
error:
|
|
2469
|
+
error: "Pattern is required and must be a string"
|
|
2444
2470
|
};
|
|
2445
2471
|
}
|
|
2446
|
-
|
|
2472
|
+
const resolvedDir = path6.resolve(directory);
|
|
2447
2473
|
try {
|
|
2448
|
-
|
|
2474
|
+
const stats = await fsPromises.stat(resolvedDir);
|
|
2475
|
+
if (!stats.isDirectory()) {
|
|
2476
|
+
return {
|
|
2477
|
+
success: false,
|
|
2478
|
+
pattern,
|
|
2479
|
+
directory: resolvedDir,
|
|
2480
|
+
results: [],
|
|
2481
|
+
total_found: 0,
|
|
2482
|
+
truncated: false,
|
|
2483
|
+
error: `Path is not a directory: ${resolvedDir}`
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2449
2486
|
} catch (error) {
|
|
2450
2487
|
return {
|
|
2451
2488
|
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
2489
|
pattern,
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
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;
|
|
2490
|
+
directory: resolvedDir,
|
|
2491
|
+
results: [],
|
|
2492
|
+
total_found: 0,
|
|
2493
|
+
truncated: false,
|
|
2494
|
+
error: `Directory not found: ${resolvedDir}`
|
|
2495
|
+
};
|
|
2479
2496
|
}
|
|
2497
|
+
const ignorePatterns = [...DEFAULT_IGNORE_PATTERNS, ...exclude_patterns];
|
|
2498
|
+
const patternRegex = globToRegex(pattern);
|
|
2499
|
+
const results = [];
|
|
2500
|
+
await searchDirectory(resolvedDir, patternRegex, resolvedDir, {
|
|
2501
|
+
extensions,
|
|
2502
|
+
maxDepth: max_depth,
|
|
2503
|
+
currentDepth: 0,
|
|
2504
|
+
includeHidden: include_hidden,
|
|
2505
|
+
ignorePatterns
|
|
2506
|
+
}, results);
|
|
2507
|
+
results.sort((a, b) => {
|
|
2508
|
+
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
2509
|
+
return a.relative_path.localeCompare(b.relative_path);
|
|
2510
|
+
});
|
|
2480
2511
|
return {
|
|
2481
2512
|
success: true,
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
total_matches: matches.length,
|
|
2488
|
-
truncated: matches.length >= max_results
|
|
2513
|
+
pattern,
|
|
2514
|
+
directory: resolvedDir,
|
|
2515
|
+
results,
|
|
2516
|
+
total_found: results.length,
|
|
2517
|
+
truncated: results.length >= MAX_RESULTS2
|
|
2489
2518
|
};
|
|
2490
2519
|
} catch (error) {
|
|
2491
2520
|
return {
|
|
2492
2521
|
success: false,
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
files_with_matches: 0,
|
|
2498
|
-
total_matches: 0,
|
|
2522
|
+
pattern: args.pattern || "",
|
|
2523
|
+
directory: args.directory || process.cwd(),
|
|
2524
|
+
results: [],
|
|
2525
|
+
total_found: 0,
|
|
2499
2526
|
truncated: false,
|
|
2500
2527
|
error: `Unexpected error: ${error.message}`
|
|
2501
2528
|
};
|
|
2502
2529
|
}
|
|
2503
2530
|
}
|
|
2504
2531
|
|
|
2505
|
-
// src/app/agent/tools/natives/
|
|
2506
|
-
import
|
|
2507
|
-
import { promises as
|
|
2508
|
-
var
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
"
|
|
2513
|
-
".
|
|
2514
|
-
".
|
|
2515
|
-
"
|
|
2516
|
-
"
|
|
2517
|
-
".
|
|
2518
|
-
"
|
|
2519
|
-
"
|
|
2520
|
-
".
|
|
2521
|
-
"
|
|
2522
|
-
".
|
|
2523
|
-
".
|
|
2524
|
-
"
|
|
2525
|
-
"
|
|
2526
|
-
".
|
|
2527
|
-
".
|
|
2528
|
-
".
|
|
2529
|
-
|
|
2530
|
-
var
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
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";
|
|
2532
|
+
// src/app/agent/tools/natives/grep_search.ts
|
|
2533
|
+
import path7 from "path";
|
|
2534
|
+
import { promises as fsPromises2 } from "fs";
|
|
2535
|
+
var MAX_RESULTS3 = 100;
|
|
2536
|
+
var MAX_FILE_SIZE2 = 1024 * 1024;
|
|
2537
|
+
var MAX_LINE_LENGTH = 500;
|
|
2538
|
+
var DEFAULT_IGNORE2 = [
|
|
2539
|
+
"node_modules",
|
|
2540
|
+
".git",
|
|
2541
|
+
".venv",
|
|
2542
|
+
"venv",
|
|
2543
|
+
"__pycache__",
|
|
2544
|
+
".cache",
|
|
2545
|
+
"dist",
|
|
2546
|
+
"build",
|
|
2547
|
+
".next",
|
|
2548
|
+
"coverage",
|
|
2549
|
+
"*.min.js",
|
|
2550
|
+
"*.min.css",
|
|
2551
|
+
"*.map",
|
|
2552
|
+
"*.lock",
|
|
2553
|
+
"package-lock.json",
|
|
2554
|
+
"yarn.lock",
|
|
2555
|
+
"pnpm-lock.yaml"
|
|
2556
|
+
];
|
|
2557
|
+
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2558
|
+
".ts",
|
|
2559
|
+
".tsx",
|
|
2560
|
+
".js",
|
|
2561
|
+
".jsx",
|
|
2562
|
+
".mjs",
|
|
2563
|
+
".cjs",
|
|
2564
|
+
".py",
|
|
2565
|
+
".pyw",
|
|
2566
|
+
".java",
|
|
2567
|
+
".kt",
|
|
2568
|
+
".scala",
|
|
2569
|
+
".go",
|
|
2570
|
+
".rs",
|
|
2571
|
+
".rb",
|
|
2572
|
+
".php",
|
|
2573
|
+
".cs",
|
|
2574
|
+
".cpp",
|
|
2575
|
+
".c",
|
|
2576
|
+
".h",
|
|
2577
|
+
".hpp",
|
|
2578
|
+
".swift",
|
|
2579
|
+
".vue",
|
|
2580
|
+
".svelte",
|
|
2581
|
+
".html",
|
|
2582
|
+
".htm",
|
|
2583
|
+
".xml",
|
|
2584
|
+
".svg",
|
|
2585
|
+
".css",
|
|
2586
|
+
".scss",
|
|
2587
|
+
".sass",
|
|
2588
|
+
".less",
|
|
2589
|
+
".json",
|
|
2590
|
+
".yaml",
|
|
2591
|
+
".yml",
|
|
2592
|
+
".toml",
|
|
2593
|
+
".md",
|
|
2594
|
+
".mdx",
|
|
2595
|
+
".txt",
|
|
2596
|
+
".rst",
|
|
2597
|
+
".sh",
|
|
2598
|
+
".bash",
|
|
2599
|
+
".zsh",
|
|
2600
|
+
".fish",
|
|
2601
|
+
".sql",
|
|
2602
|
+
".graphql",
|
|
2603
|
+
".gql",
|
|
2604
|
+
".env",
|
|
2605
|
+
".env.local",
|
|
2606
|
+
".env.example",
|
|
2607
|
+
".gitignore",
|
|
2608
|
+
".dockerignore",
|
|
2609
|
+
".eslintrc",
|
|
2610
|
+
".prettierrc",
|
|
2611
|
+
"Dockerfile",
|
|
2612
|
+
"Makefile",
|
|
2613
|
+
"Rakefile"
|
|
2614
|
+
]);
|
|
2615
|
+
function isTextFile(filepath) {
|
|
2616
|
+
const ext = path7.extname(filepath).toLowerCase();
|
|
2617
|
+
const basename = path7.basename(filepath);
|
|
2618
|
+
if (TEXT_EXTENSIONS.has(ext)) return true;
|
|
2619
|
+
if (TEXT_EXTENSIONS.has(basename)) return true;
|
|
2620
|
+
if (basename.startsWith(".") && !ext) return true;
|
|
2621
|
+
return false;
|
|
2622
2622
|
}
|
|
2623
|
-
function
|
|
2624
|
-
const
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
if (line.startsWith(" ") || line.startsWith(" ")) {
|
|
2631
|
-
if (normalizedLine.includes("function") || normalizedLine.includes("def ") || normalizedLine.includes("fn ") || normalizedLine.includes("func ")) {
|
|
2632
|
-
return "method";
|
|
2623
|
+
function shouldIgnore2(name) {
|
|
2624
|
+
for (const pattern of DEFAULT_IGNORE2) {
|
|
2625
|
+
if (pattern.includes("*")) {
|
|
2626
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
2627
|
+
if (regex.test(name)) return true;
|
|
2628
|
+
} else if (name === pattern) {
|
|
2629
|
+
return true;
|
|
2633
2630
|
}
|
|
2634
|
-
if (/^\s+\w+\s*\(/.test(line)) return "method";
|
|
2635
2631
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2634
|
+
function createSearchPattern(query, isRegex, caseInsensitive) {
|
|
2635
|
+
try {
|
|
2636
|
+
const flags = caseInsensitive ? "gi" : "g";
|
|
2637
|
+
if (isRegex) {
|
|
2638
|
+
return new RegExp(query, flags);
|
|
2639
|
+
} else {
|
|
2640
|
+
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2641
|
+
return new RegExp(escaped, flags);
|
|
2642
|
+
}
|
|
2643
|
+
} catch (error) {
|
|
2644
|
+
throw new Error(`Invalid regex pattern: ${error.message}`);
|
|
2638
2645
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2646
|
+
}
|
|
2647
|
+
function matchesIncludePattern(filename, includePatterns) {
|
|
2648
|
+
if (!includePatterns || includePatterns.length === 0) return true;
|
|
2649
|
+
for (const pattern of includePatterns) {
|
|
2650
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$", "i");
|
|
2651
|
+
if (regex.test(filename)) return true;
|
|
2641
2652
|
}
|
|
2642
|
-
return
|
|
2653
|
+
return false;
|
|
2643
2654
|
}
|
|
2644
|
-
async function
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
const
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
if (!itemType) continue;
|
|
2666
|
-
const exported = line.includes("export ");
|
|
2667
|
-
const isInsideClass = currentClass && (line.startsWith(" ") || line.startsWith(" "));
|
|
2668
|
-
const item = {
|
|
2669
|
-
type: itemType,
|
|
2670
|
-
name,
|
|
2671
|
-
line_start: lineNumber,
|
|
2672
|
-
signature: line.trim().substring(0, 120),
|
|
2673
|
-
exported
|
|
2655
|
+
async function searchFile(filepath, baseDir, pattern, contextLines, matches, maxResults) {
|
|
2656
|
+
let fileMatches = 0;
|
|
2657
|
+
try {
|
|
2658
|
+
const stats = await fsPromises2.stat(filepath);
|
|
2659
|
+
if (stats.size > MAX_FILE_SIZE2) return 0;
|
|
2660
|
+
const content = await fsPromises2.readFile(filepath, "utf-8");
|
|
2661
|
+
const lines = content.split("\n");
|
|
2662
|
+
const relativePath = path7.relative(baseDir, filepath);
|
|
2663
|
+
for (let i = 0; i < lines.length && matches.length < maxResults; i++) {
|
|
2664
|
+
const line = lines[i];
|
|
2665
|
+
pattern.lastIndex = 0;
|
|
2666
|
+
let match;
|
|
2667
|
+
while ((match = pattern.exec(line)) !== null && matches.length < maxResults) {
|
|
2668
|
+
const lineContent = line.length > MAX_LINE_LENGTH ? line.substring(0, MAX_LINE_LENGTH) + "..." : line;
|
|
2669
|
+
const searchMatch = {
|
|
2670
|
+
file: filepath,
|
|
2671
|
+
relative_path: relativePath,
|
|
2672
|
+
line_number: i + 1,
|
|
2673
|
+
line_content: lineContent.trim(),
|
|
2674
|
+
match_start: match.index,
|
|
2675
|
+
match_end: match.index + match[0].length
|
|
2674
2676
|
};
|
|
2675
|
-
if (
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
if (itemType === "class") {
|
|
2679
|
-
currentClass = name;
|
|
2677
|
+
if (contextLines > 0) {
|
|
2678
|
+
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);
|
|
2679
|
+
searchMatch.context_after = lines.slice(i + 1, i + 1 + contextLines).map((l) => l.length > MAX_LINE_LENGTH ? l.substring(0, MAX_LINE_LENGTH) + "..." : l);
|
|
2680
2680
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2681
|
+
matches.push(searchMatch);
|
|
2682
|
+
fileMatches++;
|
|
2683
|
+
if (!pattern.global) break;
|
|
2683
2684
|
}
|
|
2684
2685
|
}
|
|
2686
|
+
} catch (error) {
|
|
2685
2687
|
}
|
|
2686
|
-
return
|
|
2688
|
+
return fileMatches;
|
|
2687
2689
|
}
|
|
2688
|
-
function
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2690
|
+
async function searchDirectory2(dir, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats) {
|
|
2691
|
+
if (matches.length >= maxResults) return;
|
|
2692
|
+
let entries;
|
|
2693
|
+
try {
|
|
2694
|
+
entries = await fsPromises2.readdir(dir, { withFileTypes: true });
|
|
2695
|
+
} catch {
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
for (const entry of entries) {
|
|
2699
|
+
if (matches.length >= maxResults) break;
|
|
2700
|
+
const name = entry.name;
|
|
2701
|
+
if (shouldIgnore2(name)) continue;
|
|
2702
|
+
const fullPath = path7.join(dir, name);
|
|
2703
|
+
if (entry.isDirectory()) {
|
|
2704
|
+
await searchDirectory2(fullPath, baseDir, pattern, includePatterns, contextLines, matches, maxResults, stats);
|
|
2705
|
+
} else if (entry.isFile()) {
|
|
2706
|
+
if (!isTextFile(name)) continue;
|
|
2707
|
+
if (!matchesIncludePattern(name, includePatterns)) continue;
|
|
2708
|
+
stats.filesSearched++;
|
|
2709
|
+
const found = await searchFile(fullPath, baseDir, pattern, contextLines, matches, maxResults);
|
|
2710
|
+
if (found > 0) stats.filesWithMatches++;
|
|
2711
|
+
}
|
|
2692
2712
|
}
|
|
2693
|
-
const parts = [`${totalLines} lines`];
|
|
2694
|
-
if (counts.class) parts.push(`${counts.class} class${counts.class > 1 ? "es" : ""}`);
|
|
2695
|
-
if (counts.interface) parts.push(`${counts.interface} interface${counts.interface > 1 ? "s" : ""}`);
|
|
2696
|
-
if (counts.function) parts.push(`${counts.function} function${counts.function > 1 ? "s" : ""}`);
|
|
2697
|
-
if (counts.method) parts.push(`${counts.method} method${counts.method > 1 ? "s" : ""}`);
|
|
2698
|
-
if (counts.type) parts.push(`${counts.type} type${counts.type > 1 ? "s" : ""}`);
|
|
2699
|
-
if (counts.const) parts.push(`${counts.const} const${counts.const > 1 ? "s" : ""}`);
|
|
2700
|
-
return parts.join(", ");
|
|
2701
2713
|
}
|
|
2702
|
-
async function
|
|
2714
|
+
async function grepSearch(args) {
|
|
2703
2715
|
try {
|
|
2704
|
-
const {
|
|
2705
|
-
|
|
2716
|
+
const {
|
|
2717
|
+
query,
|
|
2718
|
+
path: searchPath,
|
|
2719
|
+
case_insensitive = true,
|
|
2720
|
+
is_regex = false,
|
|
2721
|
+
include_patterns,
|
|
2722
|
+
max_results = MAX_RESULTS3,
|
|
2723
|
+
context_lines = 0
|
|
2724
|
+
} = args;
|
|
2725
|
+
if (!query || typeof query !== "string") {
|
|
2706
2726
|
return {
|
|
2707
2727
|
success: false,
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2728
|
+
query: query || "",
|
|
2729
|
+
search_path: searchPath || "",
|
|
2730
|
+
matches: [],
|
|
2731
|
+
files_searched: 0,
|
|
2732
|
+
files_with_matches: 0,
|
|
2733
|
+
total_matches: 0,
|
|
2734
|
+
truncated: false,
|
|
2735
|
+
error: "Query is required and must be a string"
|
|
2714
2736
|
};
|
|
2715
2737
|
}
|
|
2716
|
-
|
|
2717
|
-
let content;
|
|
2718
|
-
try {
|
|
2719
|
-
content = await fsPromises3.readFile(resolvedPath, "utf-8");
|
|
2720
|
-
} catch (error) {
|
|
2738
|
+
if (!searchPath) {
|
|
2721
2739
|
return {
|
|
2722
2740
|
success: false,
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2741
|
+
query,
|
|
2742
|
+
search_path: "",
|
|
2743
|
+
matches: [],
|
|
2744
|
+
files_searched: 0,
|
|
2745
|
+
files_with_matches: 0,
|
|
2746
|
+
total_matches: 0,
|
|
2747
|
+
truncated: false,
|
|
2748
|
+
error: "Search path is required"
|
|
2729
2749
|
};
|
|
2730
2750
|
}
|
|
2731
|
-
const
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
success: true,
|
|
2737
|
-
file_path: resolvedPath,
|
|
2738
|
-
language,
|
|
2739
|
-
total_lines: totalLines,
|
|
2740
|
-
items,
|
|
2741
|
-
summary: generateSummary(items, totalLines)
|
|
2742
|
-
};
|
|
2743
|
-
} catch (error) {
|
|
2744
|
-
return {
|
|
2745
|
-
success: false,
|
|
2746
|
-
file_path: args.file_path || "",
|
|
2747
|
-
language: "unknown",
|
|
2748
|
-
total_lines: 0,
|
|
2749
|
-
items: [],
|
|
2750
|
-
summary: "",
|
|
2751
|
-
error: `Unexpected error: ${error.message}`
|
|
2752
|
-
};
|
|
2753
|
-
}
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
|
-
// src/app/agent/tools/natives/async_command.ts
|
|
2757
|
-
import os2 from "os";
|
|
2758
|
-
import { spawn } from "child_process";
|
|
2759
|
-
import { v4 as uuidv42 } from "uuid";
|
|
2760
|
-
var runningCommands = /* @__PURE__ */ new Map();
|
|
2761
|
-
var MAX_OUTPUT_SIZE = 3e4;
|
|
2762
|
-
var MAX_STORED_COMMANDS = 50;
|
|
2763
|
-
var OUTPUT_TRUNCATION_MSG = "\n[OUTPUT TRUNCATED - 30KB/200 lines max]";
|
|
2764
|
-
var DANGEROUS_PATTERNS = [
|
|
2765
|
-
// Elevação de privilégios
|
|
2766
|
-
/^sudo\s+/i,
|
|
2767
|
-
/^doas\s+/i,
|
|
2768
|
-
/^su\s+/i,
|
|
2769
|
-
/^pkexec\s+/i,
|
|
2770
|
-
/\|\s*sudo\s+/i,
|
|
2771
|
-
/;\s*sudo\s+/i,
|
|
2772
|
-
/&&\s*sudo\s+/i,
|
|
2773
|
-
// Comandos destrutivos
|
|
2774
|
-
/\brm\s+(-[rf]+\s+)*[\/~]/i,
|
|
2775
|
-
/\brm\s+-[rf]*\s+\*/i,
|
|
2776
|
-
/\bchmod\s+(777|666)\s+\//i,
|
|
2777
|
-
/\bdd\s+.*of=\/dev\/(sd|hd|nvme)/i,
|
|
2778
|
-
/\bmkfs\./i,
|
|
2779
|
-
// Fork bombs
|
|
2780
|
-
/:\(\)\s*\{\s*:\|:&\s*\}\s*;:/,
|
|
2781
|
-
// Remote code exec
|
|
2782
|
-
/\bcurl\s+.*\|\s*(ba)?sh/i,
|
|
2783
|
-
/\bwget\s+.*\|\s*(ba)?sh/i
|
|
2784
|
-
];
|
|
2785
|
-
var INTERACTIVE_PATTERNS = [
|
|
2786
|
-
/^(vim|vi|nano|emacs|pico)\s*/i,
|
|
2787
|
-
/^(less|more|most)\s*/i,
|
|
2788
|
-
/^(top|htop|btop|atop|nmon)\s*/i,
|
|
2789
|
-
/^(ssh|telnet|ftp|sftp)\s+/i,
|
|
2790
|
-
/^(mysql|psql|redis-cli|mongo|mongosh)\s*$/i,
|
|
2791
|
-
/^(python|python3|node|ruby|irb|lua)\s*$/i,
|
|
2792
|
-
/^(gdb|lldb)\s*/i
|
|
2793
|
-
];
|
|
2794
|
-
function cleanupOldCommands() {
|
|
2795
|
-
if (runningCommands.size <= MAX_STORED_COMMANDS) return;
|
|
2796
|
-
const commands = Array.from(runningCommands.entries()).filter(([_, cmd]) => cmd.status !== "running").sort((a, b) => (a[1].endTime || 0) - (b[1].endTime || 0));
|
|
2797
|
-
const toRemove = commands.slice(0, commands.length - MAX_STORED_COMMANDS + 10);
|
|
2798
|
-
for (const [id] of toRemove) {
|
|
2799
|
-
runningCommands.delete(id);
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
function isDangerousCommand(command) {
|
|
2803
|
-
const trimmed = command.trim();
|
|
2804
|
-
for (const pattern of DANGEROUS_PATTERNS) {
|
|
2805
|
-
if (pattern.test(trimmed)) {
|
|
2806
|
-
return "Command requires elevated privileges (sudo/doas) which cannot be handled in async mode";
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
for (const pattern of INTERACTIVE_PATTERNS) {
|
|
2810
|
-
if (pattern.test(trimmed)) {
|
|
2811
|
-
return "Interactive commands are not supported in async mode";
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
return null;
|
|
2815
|
-
}
|
|
2816
|
-
async function runCommandAsync(args) {
|
|
2817
|
-
try {
|
|
2818
|
-
const {
|
|
2819
|
-
command,
|
|
2820
|
-
cwd = process.cwd(),
|
|
2821
|
-
timeout = 0
|
|
2822
|
-
} = args;
|
|
2823
|
-
if (!command || typeof command !== "string") {
|
|
2751
|
+
const resolvedPath = path7.resolve(searchPath);
|
|
2752
|
+
let stats;
|
|
2753
|
+
try {
|
|
2754
|
+
stats = await fsPromises2.stat(resolvedPath);
|
|
2755
|
+
} catch {
|
|
2824
2756
|
return {
|
|
2825
2757
|
success: false,
|
|
2826
|
-
|
|
2758
|
+
query,
|
|
2759
|
+
search_path: resolvedPath,
|
|
2760
|
+
matches: [],
|
|
2761
|
+
files_searched: 0,
|
|
2762
|
+
files_with_matches: 0,
|
|
2763
|
+
total_matches: 0,
|
|
2764
|
+
truncated: false,
|
|
2765
|
+
error: `Path not found: ${resolvedPath}`
|
|
2827
2766
|
};
|
|
2828
2767
|
}
|
|
2829
|
-
|
|
2830
|
-
|
|
2768
|
+
let pattern;
|
|
2769
|
+
try {
|
|
2770
|
+
pattern = createSearchPattern(query, is_regex, case_insensitive);
|
|
2771
|
+
} catch (error) {
|
|
2831
2772
|
return {
|
|
2832
2773
|
success: false,
|
|
2833
|
-
|
|
2774
|
+
query,
|
|
2775
|
+
search_path: resolvedPath,
|
|
2776
|
+
matches: [],
|
|
2777
|
+
files_searched: 0,
|
|
2778
|
+
files_with_matches: 0,
|
|
2779
|
+
total_matches: 0,
|
|
2780
|
+
truncated: false,
|
|
2781
|
+
error: error.message
|
|
2834
2782
|
};
|
|
2835
2783
|
}
|
|
2836
|
-
const
|
|
2837
|
-
const
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2784
|
+
const matches = [];
|
|
2785
|
+
const searchStats = { filesSearched: 0, filesWithMatches: 0 };
|
|
2786
|
+
if (stats.isDirectory()) {
|
|
2787
|
+
await searchDirectory2(
|
|
2788
|
+
resolvedPath,
|
|
2789
|
+
resolvedPath,
|
|
2790
|
+
pattern,
|
|
2791
|
+
include_patterns,
|
|
2792
|
+
context_lines,
|
|
2793
|
+
matches,
|
|
2794
|
+
max_results,
|
|
2795
|
+
searchStats
|
|
2796
|
+
);
|
|
2797
|
+
} else if (stats.isFile()) {
|
|
2798
|
+
searchStats.filesSearched = 1;
|
|
2799
|
+
const found = await searchFile(resolvedPath, path7.dirname(resolvedPath), pattern, context_lines, matches, max_results);
|
|
2800
|
+
if (found > 0) searchStats.filesWithMatches = 1;
|
|
2846
2801
|
}
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2802
|
+
return {
|
|
2803
|
+
success: true,
|
|
2804
|
+
query,
|
|
2805
|
+
search_path: resolvedPath,
|
|
2806
|
+
matches,
|
|
2807
|
+
files_searched: searchStats.filesSearched,
|
|
2808
|
+
files_with_matches: searchStats.filesWithMatches,
|
|
2809
|
+
total_matches: matches.length,
|
|
2810
|
+
truncated: matches.length >= max_results
|
|
2856
2811
|
};
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
return {
|
|
2814
|
+
success: false,
|
|
2815
|
+
query: args.query || "",
|
|
2816
|
+
search_path: args.path || "",
|
|
2817
|
+
matches: [],
|
|
2818
|
+
files_searched: 0,
|
|
2819
|
+
files_with_matches: 0,
|
|
2820
|
+
total_matches: 0,
|
|
2821
|
+
truncated: false,
|
|
2822
|
+
error: `Unexpected error: ${error.message}`
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
// src/app/agent/tools/natives/view_file_outline.ts
|
|
2828
|
+
import path8 from "path";
|
|
2829
|
+
import { promises as fsPromises3 } from "fs";
|
|
2830
|
+
var LANGUAGE_MAP = {
|
|
2831
|
+
".ts": "typescript",
|
|
2832
|
+
".tsx": "typescript",
|
|
2833
|
+
".js": "javascript",
|
|
2834
|
+
".jsx": "javascript",
|
|
2835
|
+
".mjs": "javascript",
|
|
2836
|
+
".cjs": "javascript",
|
|
2837
|
+
".py": "python",
|
|
2838
|
+
".java": "java",
|
|
2839
|
+
".go": "go",
|
|
2840
|
+
".rs": "rust",
|
|
2841
|
+
".rb": "ruby",
|
|
2842
|
+
".php": "php",
|
|
2843
|
+
".cs": "csharp",
|
|
2844
|
+
".cpp": "cpp",
|
|
2845
|
+
".c": "c",
|
|
2846
|
+
".h": "c",
|
|
2847
|
+
".hpp": "cpp",
|
|
2848
|
+
".swift": "swift",
|
|
2849
|
+
".kt": "kotlin",
|
|
2850
|
+
".scala": "scala"
|
|
2851
|
+
};
|
|
2852
|
+
var PATTERNS = {
|
|
2853
|
+
typescript: [
|
|
2854
|
+
// Classes
|
|
2855
|
+
/^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w,\s]+)?/,
|
|
2856
|
+
// Interfaces
|
|
2857
|
+
/^(?:export\s+)?interface\s+(\w+)/,
|
|
2858
|
+
// Types
|
|
2859
|
+
/^(?:export\s+)?type\s+(\w+)/,
|
|
2860
|
+
// Functions (standalone)
|
|
2861
|
+
/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
|
|
2862
|
+
// Arrow functions assigned to const/let
|
|
2863
|
+
/^(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
|
|
2864
|
+
// Methods inside classes (indented)
|
|
2865
|
+
/^\s+(?:public|private|protected|static|async|readonly|\s)*(\w+)\s*\([^)]*\)\s*(?::\s*[\w<>\[\]|&\s]+)?\s*\{/,
|
|
2866
|
+
// Const exports
|
|
2867
|
+
/^(?:export\s+)?const\s+(\w+)\s*[:=]/
|
|
2868
|
+
],
|
|
2869
|
+
javascript: [
|
|
2870
|
+
/^(?:export\s+)?(?:default\s+)?class\s+(\w+)/,
|
|
2871
|
+
/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
|
|
2872
|
+
/^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
|
|
2873
|
+
/^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/,
|
|
2874
|
+
/^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/
|
|
2875
|
+
],
|
|
2876
|
+
python: [
|
|
2877
|
+
/^class\s+(\w+)(?:\([^)]*\))?:/,
|
|
2878
|
+
/^(?:async\s+)?def\s+(\w+)\s*\(/,
|
|
2879
|
+
/^(\w+)\s*=\s*(?:lambda|def)/
|
|
2880
|
+
],
|
|
2881
|
+
java: [
|
|
2882
|
+
/^(?:public|private|protected)?\s*(?:static)?\s*(?:final)?\s*(?:abstract)?\s*class\s+(\w+)/,
|
|
2883
|
+
/^(?:public|private|protected)?\s*(?:static)?\s*(?:final)?\s*interface\s+(\w+)/,
|
|
2884
|
+
/^\s*(?:public|private|protected)?\s*(?:static)?\s*(?:final)?\s*(?:synchronized)?\s*(?:\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/
|
|
2885
|
+
],
|
|
2886
|
+
go: [
|
|
2887
|
+
/^type\s+(\w+)\s+struct\s*\{/,
|
|
2888
|
+
/^type\s+(\w+)\s+interface\s*\{/,
|
|
2889
|
+
/^func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/
|
|
2890
|
+
],
|
|
2891
|
+
rust: [
|
|
2892
|
+
/^(?:pub\s+)?struct\s+(\w+)/,
|
|
2893
|
+
/^(?:pub\s+)?enum\s+(\w+)/,
|
|
2894
|
+
/^(?:pub\s+)?trait\s+(\w+)/,
|
|
2895
|
+
/^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/,
|
|
2896
|
+
/^impl(?:<[^>]+>)?\s+(?:(\w+)|for\s+(\w+))/
|
|
2897
|
+
],
|
|
2898
|
+
ruby: [
|
|
2899
|
+
/^class\s+(\w+)/,
|
|
2900
|
+
/^module\s+(\w+)/,
|
|
2901
|
+
/^def\s+(\w+)/
|
|
2902
|
+
],
|
|
2903
|
+
php: [
|
|
2904
|
+
/^(?:abstract\s+)?class\s+(\w+)/,
|
|
2905
|
+
/^interface\s+(\w+)/,
|
|
2906
|
+
/^(?:public|private|protected)?\s*(?:static)?\s*function\s+(\w+)/
|
|
2907
|
+
],
|
|
2908
|
+
csharp: [
|
|
2909
|
+
/^(?:public|private|protected|internal)?\s*(?:static)?\s*(?:partial)?\s*class\s+(\w+)/,
|
|
2910
|
+
/^(?:public|private|protected|internal)?\s*interface\s+(\w+)/,
|
|
2911
|
+
/^\s*(?:public|private|protected|internal)?\s*(?:static)?\s*(?:async)?\s*(?:\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/
|
|
2912
|
+
],
|
|
2913
|
+
swift: [
|
|
2914
|
+
/^(?:public|private|internal|open)?\s*class\s+(\w+)/,
|
|
2915
|
+
/^(?:public|private|internal)?\s*struct\s+(\w+)/,
|
|
2916
|
+
/^(?:public|private|internal)?\s*protocol\s+(\w+)/,
|
|
2917
|
+
/^(?:public|private|internal)?\s*(?:static)?\s*func\s+(\w+)/
|
|
2918
|
+
],
|
|
2919
|
+
kotlin: [
|
|
2920
|
+
/^(?:open|abstract|sealed)?\s*class\s+(\w+)/,
|
|
2921
|
+
/^interface\s+(\w+)/,
|
|
2922
|
+
/^(?:private|public|internal)?\s*fun\s+(\w+)/
|
|
2923
|
+
],
|
|
2924
|
+
scala: [
|
|
2925
|
+
/^(?:sealed\s+)?(?:abstract\s+)?class\s+(\w+)/,
|
|
2926
|
+
/^object\s+(\w+)/,
|
|
2927
|
+
/^trait\s+(\w+)/,
|
|
2928
|
+
/^def\s+(\w+)/
|
|
2929
|
+
],
|
|
2930
|
+
c: [
|
|
2931
|
+
/^(?:static\s+)?(?:inline\s+)?(?:const\s+)?(?:\w+(?:\s*\*)*)\s+(\w+)\s*\([^)]*\)\s*\{/,
|
|
2932
|
+
/^typedef\s+struct\s*\w*\s*\{[^}]*\}\s*(\w+)/,
|
|
2933
|
+
/^struct\s+(\w+)\s*\{/
|
|
2934
|
+
],
|
|
2935
|
+
cpp: [
|
|
2936
|
+
/^class\s+(\w+)/,
|
|
2937
|
+
/^(?:virtual\s+)?(?:\w+(?:\s*[*&])*)\s+(\w+)\s*\([^)]*\)\s*(?:const)?\s*(?:override)?\s*\{/,
|
|
2938
|
+
/^namespace\s+(\w+)\s*\{/
|
|
2939
|
+
]
|
|
2940
|
+
};
|
|
2941
|
+
function detectLanguage(filepath) {
|
|
2942
|
+
const ext = path8.extname(filepath).toLowerCase();
|
|
2943
|
+
return LANGUAGE_MAP[ext] || "unknown";
|
|
2944
|
+
}
|
|
2945
|
+
function determineItemType(line, language) {
|
|
2946
|
+
const normalizedLine = line.trim().toLowerCase();
|
|
2947
|
+
if (normalizedLine.includes("class ")) return "class";
|
|
2948
|
+
if (normalizedLine.includes("interface ")) return "interface";
|
|
2949
|
+
if (normalizedLine.includes("type ")) return "type";
|
|
2950
|
+
if (normalizedLine.startsWith("import ")) return "import";
|
|
2951
|
+
if (normalizedLine.includes("export ") && !normalizedLine.includes("function") && !normalizedLine.includes("class")) return "export";
|
|
2952
|
+
if (line.startsWith(" ") || line.startsWith(" ")) {
|
|
2953
|
+
if (normalizedLine.includes("function") || normalizedLine.includes("def ") || normalizedLine.includes("fn ") || normalizedLine.includes("func ")) {
|
|
2954
|
+
return "method";
|
|
2910
2955
|
}
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
};
|
|
2919
|
-
} catch (error) {
|
|
2920
|
-
return {
|
|
2921
|
-
success: false,
|
|
2922
|
-
error: `Unexpected error: ${error.message}`
|
|
2923
|
-
};
|
|
2956
|
+
if (/^\s+\w+\s*\(/.test(line)) return "method";
|
|
2957
|
+
}
|
|
2958
|
+
if (normalizedLine.includes("function ") || normalizedLine.includes("def ") || normalizedLine.includes("fn ") || normalizedLine.includes("func ")) {
|
|
2959
|
+
return "function";
|
|
2960
|
+
}
|
|
2961
|
+
if (normalizedLine.includes("const ") || normalizedLine.includes("let ") || normalizedLine.includes("var ")) {
|
|
2962
|
+
return normalizedLine.includes("=>") ? "function" : "const";
|
|
2924
2963
|
}
|
|
2964
|
+
return "function";
|
|
2925
2965
|
}
|
|
2926
|
-
async function
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
}
|
|
2941
|
-
const entry = runningCommands.get(command_id);
|
|
2942
|
-
if (!entry) {
|
|
2943
|
-
return {
|
|
2944
|
-
success: false,
|
|
2945
|
-
command_id,
|
|
2946
|
-
status: "not_found",
|
|
2947
|
-
error: `Command with id "${command_id}" not found. It may have expired or never existed.`
|
|
2948
|
-
};
|
|
2949
|
-
}
|
|
2950
|
-
if (wait_seconds > 0 && entry.status === "running") {
|
|
2951
|
-
await new Promise((resolve) => {
|
|
2952
|
-
const checkInterval = setInterval(() => {
|
|
2953
|
-
if (entry.status !== "running") {
|
|
2954
|
-
clearInterval(checkInterval);
|
|
2955
|
-
resolve();
|
|
2956
|
-
}
|
|
2957
|
-
}, 100);
|
|
2958
|
-
setTimeout(() => {
|
|
2959
|
-
clearInterval(checkInterval);
|
|
2960
|
-
resolve();
|
|
2961
|
-
}, wait_seconds * 1e3);
|
|
2962
|
-
});
|
|
2963
|
-
}
|
|
2964
|
-
let stdout = entry.stdout;
|
|
2965
|
-
let stderr = entry.stderr;
|
|
2966
|
-
let truncated = false;
|
|
2967
|
-
if (stdout.length > output_limit) {
|
|
2968
|
-
stdout = "..." + stdout.substring(stdout.length - output_limit);
|
|
2969
|
-
truncated = true;
|
|
2966
|
+
async function extractOutline(filepath, content, language) {
|
|
2967
|
+
const items = [];
|
|
2968
|
+
const lines = content.split("\n");
|
|
2969
|
+
const patterns = PATTERNS[language] || PATTERNS.javascript;
|
|
2970
|
+
let currentClass = null;
|
|
2971
|
+
let braceCount = 0;
|
|
2972
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2973
|
+
const line = lines[i];
|
|
2974
|
+
const lineNumber = i + 1;
|
|
2975
|
+
const openBraces = (line.match(/\{/g) || []).length;
|
|
2976
|
+
const closeBraces = (line.match(/\}/g) || []).length;
|
|
2977
|
+
braceCount += openBraces - closeBraces;
|
|
2978
|
+
if (braceCount <= 0 && currentClass) {
|
|
2979
|
+
currentClass = null;
|
|
2970
2980
|
}
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2981
|
+
for (const pattern of patterns) {
|
|
2982
|
+
const match = line.match(pattern);
|
|
2983
|
+
if (match) {
|
|
2984
|
+
const name = match[1] || match[2];
|
|
2985
|
+
if (!name) continue;
|
|
2986
|
+
const itemType = determineItemType(line, language);
|
|
2987
|
+
if (!itemType) continue;
|
|
2988
|
+
const exported = line.includes("export ");
|
|
2989
|
+
const isInsideClass = currentClass && (line.startsWith(" ") || line.startsWith(" "));
|
|
2990
|
+
const item = {
|
|
2991
|
+
type: itemType,
|
|
2992
|
+
name,
|
|
2993
|
+
line_start: lineNumber,
|
|
2994
|
+
signature: line.trim().substring(0, 120),
|
|
2995
|
+
exported
|
|
2996
|
+
};
|
|
2997
|
+
if (isInsideClass && itemType === "method" && currentClass) {
|
|
2998
|
+
item.parent = currentClass;
|
|
2999
|
+
}
|
|
3000
|
+
if (itemType === "class") {
|
|
3001
|
+
currentClass = name;
|
|
3002
|
+
}
|
|
3003
|
+
items.push(item);
|
|
3004
|
+
break;
|
|
3005
|
+
}
|
|
2974
3006
|
}
|
|
2975
|
-
const duration = entry.endTime ? (entry.endTime - entry.startTime) / 1e3 : (Date.now() - entry.startTime) / 1e3;
|
|
2976
|
-
return {
|
|
2977
|
-
success: true,
|
|
2978
|
-
command_id,
|
|
2979
|
-
status: entry.status,
|
|
2980
|
-
stdout: stdout || void 0,
|
|
2981
|
-
stderr: stderr || void 0,
|
|
2982
|
-
exit_code: entry.exitCode,
|
|
2983
|
-
duration_seconds: Math.round(duration * 10) / 10,
|
|
2984
|
-
truncated
|
|
2985
|
-
};
|
|
2986
|
-
} catch (error) {
|
|
2987
|
-
return {
|
|
2988
|
-
success: false,
|
|
2989
|
-
command_id: args.command_id || "",
|
|
2990
|
-
status: "error",
|
|
2991
|
-
error: `Unexpected error: ${error.message}`
|
|
2992
|
-
};
|
|
2993
3007
|
}
|
|
3008
|
+
return items;
|
|
2994
3009
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
return {
|
|
3000
|
-
success: false,
|
|
3001
|
-
error: "command_id and input are required"
|
|
3002
|
-
};
|
|
3003
|
-
}
|
|
3004
|
-
const entry = runningCommands.get(command_id);
|
|
3005
|
-
if (!entry) {
|
|
3006
|
-
return {
|
|
3007
|
-
success: false,
|
|
3008
|
-
error: `Command with id "${command_id}" not found`
|
|
3009
|
-
};
|
|
3010
|
-
}
|
|
3011
|
-
if (entry.status !== "running" || !entry.process) {
|
|
3012
|
-
return {
|
|
3013
|
-
success: false,
|
|
3014
|
-
error: `Command is not running (status: ${entry.status})`
|
|
3015
|
-
};
|
|
3016
|
-
}
|
|
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
|
-
};
|
|
3010
|
+
function generateSummary(items, totalLines) {
|
|
3011
|
+
const counts = {};
|
|
3012
|
+
for (const item of items) {
|
|
3013
|
+
counts[item.type] = (counts[item.type] || 0) + 1;
|
|
3027
3014
|
}
|
|
3015
|
+
const parts = [`${totalLines} lines`];
|
|
3016
|
+
if (counts.class) parts.push(`${counts.class} class${counts.class > 1 ? "es" : ""}`);
|
|
3017
|
+
if (counts.interface) parts.push(`${counts.interface} interface${counts.interface > 1 ? "s" : ""}`);
|
|
3018
|
+
if (counts.function) parts.push(`${counts.function} function${counts.function > 1 ? "s" : ""}`);
|
|
3019
|
+
if (counts.method) parts.push(`${counts.method} method${counts.method > 1 ? "s" : ""}`);
|
|
3020
|
+
if (counts.type) parts.push(`${counts.type} type${counts.type > 1 ? "s" : ""}`);
|
|
3021
|
+
if (counts.const) parts.push(`${counts.const} const${counts.const > 1 ? "s" : ""}`);
|
|
3022
|
+
return parts.join(", ");
|
|
3028
3023
|
}
|
|
3029
|
-
async function
|
|
3024
|
+
async function viewFileOutline(args) {
|
|
3030
3025
|
try {
|
|
3031
|
-
const {
|
|
3032
|
-
|
|
3033
|
-
if (!entry) {
|
|
3026
|
+
const { file_path } = args;
|
|
3027
|
+
if (!file_path || typeof file_path !== "string") {
|
|
3034
3028
|
return {
|
|
3035
3029
|
success: false,
|
|
3036
|
-
|
|
3030
|
+
file_path: file_path || "",
|
|
3031
|
+
language: "unknown",
|
|
3032
|
+
total_lines: 0,
|
|
3033
|
+
items: [],
|
|
3034
|
+
summary: "",
|
|
3035
|
+
error: "file_path is required and must be a string"
|
|
3037
3036
|
};
|
|
3038
3037
|
}
|
|
3039
|
-
|
|
3038
|
+
const resolvedPath = path8.resolve(file_path);
|
|
3039
|
+
let content;
|
|
3040
|
+
try {
|
|
3041
|
+
content = await fsPromises3.readFile(resolvedPath, "utf-8");
|
|
3042
|
+
} catch (error) {
|
|
3040
3043
|
return {
|
|
3041
3044
|
success: false,
|
|
3042
|
-
|
|
3045
|
+
file_path: resolvedPath,
|
|
3046
|
+
language: "unknown",
|
|
3047
|
+
total_lines: 0,
|
|
3048
|
+
items: [],
|
|
3049
|
+
summary: "",
|
|
3050
|
+
error: error.code === "ENOENT" ? `File not found: ${resolvedPath}` : `Error reading file: ${error.message}`
|
|
3043
3051
|
};
|
|
3044
3052
|
}
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3053
|
+
const language = detectLanguage(resolvedPath);
|
|
3054
|
+
const lines = content.split("\n");
|
|
3055
|
+
const totalLines = lines.length;
|
|
3056
|
+
const items = await extractOutline(resolvedPath, content, language);
|
|
3048
3057
|
return {
|
|
3049
3058
|
success: true,
|
|
3050
|
-
|
|
3059
|
+
file_path: resolvedPath,
|
|
3060
|
+
language,
|
|
3061
|
+
total_lines: totalLines,
|
|
3062
|
+
items,
|
|
3063
|
+
summary: generateSummary(items, totalLines)
|
|
3051
3064
|
};
|
|
3052
3065
|
} catch (error) {
|
|
3053
3066
|
return {
|
|
3054
3067
|
success: false,
|
|
3055
|
-
|
|
3068
|
+
file_path: args.file_path || "",
|
|
3069
|
+
language: "unknown",
|
|
3070
|
+
total_lines: 0,
|
|
3071
|
+
items: [],
|
|
3072
|
+
summary: "",
|
|
3073
|
+
error: `Unexpected error: ${error.message}`
|
|
3056
3074
|
};
|
|
3057
3075
|
}
|
|
3058
3076
|
}
|
|
3059
3077
|
|
|
3078
|
+
// src/app/agent/tool_invoker.ts
|
|
3079
|
+
init_async_command();
|
|
3080
|
+
|
|
3060
3081
|
// src/app/agent/tools/natives/task_boundary.ts
|
|
3061
3082
|
import path9 from "path";
|
|
3062
3083
|
import { promises as fs7 } from "fs";
|
|
@@ -4107,8 +4128,13 @@ You MUST use \`command_status\` to get the output.
|
|
|
4107
4128
|
[Step 1] shell_command({ command: "docker build -t myapp .", timeout: 600 })
|
|
4108
4129
|
\u2192 { command_id: "cmd_004" }
|
|
4109
4130
|
|
|
4110
|
-
[Step 2] command_status({ command_id: "cmd_004", wait_seconds:
|
|
4111
|
-
\u2192 { status: "
|
|
4131
|
+
[Step 2] command_status({ command_id: "cmd_004", wait_seconds: 10 })
|
|
4132
|
+
\u2192 { status: "running" }
|
|
4133
|
+
|
|
4134
|
+
[Step 3] message_notify_user("Building Docker image, please wait...")
|
|
4135
|
+
|
|
4136
|
+
[Step 4] command_status({ command_id: "cmd_004", wait_seconds: 10 })
|
|
4137
|
+
\u2192 { status: "completed", exit_code: 0, stdout: "Successfully built abc123" }
|
|
4112
4138
|
\`\`\`
|
|
4113
4139
|
|
|
4114
4140
|
**[WRONG] Never forget command_status:**
|
|
@@ -4117,6 +4143,11 @@ shell_command({ command: "npm install" })
|
|
|
4117
4143
|
message_notify_user("Installed!") // WRONG: You don't know if it succeeded!
|
|
4118
4144
|
\`\`\`
|
|
4119
4145
|
|
|
4146
|
+
**[WRONG] Never use long waits (blocks user):**
|
|
4147
|
+
\`\`\`
|
|
4148
|
+
command_status({ id: "...", wait_seconds: 300 }) // WRONG: User can't interrupt!
|
|
4149
|
+
\`\`\`
|
|
4150
|
+
|
|
4120
4151
|
**[WRONG] Never use blocked commands:**
|
|
4121
4152
|
\`\`\`
|
|
4122
4153
|
shell_command({ command: "sudo apt install ..." }) // BLOCKED
|
|
@@ -4126,8 +4157,9 @@ shell_command({ command: "vim file.txt" }) // BLOCKED (interactive)
|
|
|
4126
4157
|
|
|
4127
4158
|
### Tips:
|
|
4128
4159
|
- Always check \`exit_code\` - 0 means success, non-zero is error
|
|
4129
|
-
-
|
|
4130
|
-
- If \`
|
|
4160
|
+
- Use wait_seconds: 5-10 for polling (allows user interrupts)
|
|
4161
|
+
- If \`status: "running"\`, notify user and poll again
|
|
4162
|
+
- If \`truncated: true\`, output was too long - use \`| head\` or \`| tail\`
|
|
4131
4163
|
- Use \`send_command_input\` for commands that need input (y/n prompts)
|
|
4132
4164
|
- Use \`kill_command\` to stop a stuck command
|
|
4133
4165
|
</shell_command>
|
|
@@ -6208,6 +6240,61 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
6208
6240
|
]);
|
|
6209
6241
|
return;
|
|
6210
6242
|
}
|
|
6243
|
+
if (text.startsWith("!")) {
|
|
6244
|
+
const command = text.slice(1).trim();
|
|
6245
|
+
if (!command) {
|
|
6246
|
+
setIsProcessing(false);
|
|
6247
|
+
return;
|
|
6248
|
+
}
|
|
6249
|
+
setHistory((prev) => [
|
|
6250
|
+
...prev,
|
|
6251
|
+
{
|
|
6252
|
+
id: prev.length,
|
|
6253
|
+
component: /* @__PURE__ */ jsxs15(Box16, { marginBottom: 1, children: [
|
|
6254
|
+
/* @__PURE__ */ jsx16(Text15, { color: "white", bold: true, children: "$ " }),
|
|
6255
|
+
/* @__PURE__ */ jsx16(Text15, { color: "white", children: command })
|
|
6256
|
+
] })
|
|
6257
|
+
}
|
|
6258
|
+
]);
|
|
6259
|
+
Promise.resolve().then(() => (init_async_command(), async_command_exports)).then(async ({ runCommandAsync: runCommandAsync2 }) => {
|
|
6260
|
+
try {
|
|
6261
|
+
const result = await runCommandAsync2({ command, cwd: workdir });
|
|
6262
|
+
if (result.success && result.command_id) {
|
|
6263
|
+
const contextMessage = `[User executed shell command directly]
|
|
6264
|
+
Command: ${command}
|
|
6265
|
+
Command ID: ${result.command_id}
|
|
6266
|
+
|
|
6267
|
+
Please use command_status to check the result and report back to the user.`;
|
|
6268
|
+
agentInstance.current?.processTurn({ content: contextMessage });
|
|
6269
|
+
} else {
|
|
6270
|
+
setHistory((prev) => [
|
|
6271
|
+
...prev,
|
|
6272
|
+
{
|
|
6273
|
+
id: prev.length,
|
|
6274
|
+
component: /* @__PURE__ */ jsxs15(Text15, { color: "red", children: [
|
|
6275
|
+
"Failed to execute: ",
|
|
6276
|
+
result.error || result.message
|
|
6277
|
+
] })
|
|
6278
|
+
}
|
|
6279
|
+
]);
|
|
6280
|
+
setIsProcessing(false);
|
|
6281
|
+
}
|
|
6282
|
+
} catch (err) {
|
|
6283
|
+
setHistory((prev) => [
|
|
6284
|
+
...prev,
|
|
6285
|
+
{
|
|
6286
|
+
id: prev.length,
|
|
6287
|
+
component: /* @__PURE__ */ jsxs15(Text15, { color: "red", children: [
|
|
6288
|
+
"Error: ",
|
|
6289
|
+
err.message
|
|
6290
|
+
] })
|
|
6291
|
+
}
|
|
6292
|
+
]);
|
|
6293
|
+
setIsProcessing(false);
|
|
6294
|
+
}
|
|
6295
|
+
});
|
|
6296
|
+
return;
|
|
6297
|
+
}
|
|
6211
6298
|
setIsProcessing(true);
|
|
6212
6299
|
const displayText = text.length > 1e4 ? text.substring(0, 1e4) + "..." : text;
|
|
6213
6300
|
setHistory((prev) => [
|