@nomad-e/bluma-cli 0.0.1

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 ADDED
@@ -0,0 +1,2548 @@
1
+ #!/usr/bin/env node
2
+ // src/main.ts
3
+ import React6 from "react";
4
+ import { render } from "ink";
5
+ import { EventEmitter } from "events";
6
+ import { v4 as uuidv42 } from "uuid";
7
+
8
+ // src/app/ui/App.tsx
9
+ import { useState as useState4, useEffect as useEffect3, useRef, useCallback, memo as memo4 } from "react";
10
+ import { Box as Box11, Text as Text10, Static } from "ink";
11
+ import Spinner from "ink-spinner";
12
+
13
+ // src/app/ui/layout.tsx
14
+ import { Box, Text } from "ink";
15
+ import BigText from "ink-big-text";
16
+ import { jsx, jsxs } from "react/jsx-runtime";
17
+ var BRAND_COLORS = {
18
+ main: "cyan",
19
+ accent: "magenta",
20
+ shadow: "blue",
21
+ greydark: "#444"
22
+ };
23
+ var Header = () => {
24
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: 8, children: /* @__PURE__ */ jsx(
25
+ BigText,
26
+ {
27
+ text: "BluMa CLI",
28
+ font: "block",
29
+ colors: [BRAND_COLORS.main, BRAND_COLORS.accent, BRAND_COLORS.shadow]
30
+ }
31
+ ) });
32
+ };
33
+ var SessionInfo = ({
34
+ sessionId: sessionId2,
35
+ // ID único da sessão atual, para segregação ou rastreio
36
+ workdir,
37
+ // Diretório de trabalho atual do BluMa, útil para saber contexto da execução
38
+ toolsCount,
39
+ // Número de ferramentas ativas carregadas pelo MCP
40
+ mcpStatus
41
+ // Estado da conexão central MCP (connecting/connected)
42
+ }) => /* @__PURE__ */ jsxs(
43
+ Box,
44
+ {
45
+ borderStyle: "round",
46
+ borderColor: "gray",
47
+ paddingX: 1,
48
+ flexDirection: "column",
49
+ marginBottom: 1,
50
+ children: [
51
+ /* @__PURE__ */ jsxs(Text, { children: [
52
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "localhost" }),
53
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " session:" }),
54
+ " ",
55
+ /* @__PURE__ */ jsx(Text, { color: "magenta", children: sessionId2 })
56
+ ] }),
57
+ /* @__PURE__ */ jsxs(Text, { children: [
58
+ /* @__PURE__ */ jsx(Text, { color: "magenta", children: "\u21B3" }),
59
+ " ",
60
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
61
+ "workdir: ",
62
+ workdir
63
+ ] })
64
+ ] }),
65
+ /* @__PURE__ */ jsxs(Text, { children: [
66
+ /* @__PURE__ */ jsx(Text, { color: "magenta", children: "\u21B3" }),
67
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "agent: BluMa" })
68
+ ] }),
69
+ /* @__PURE__ */ jsxs(Text, { children: [
70
+ /* @__PURE__ */ jsx(Text, { color: "magenta", children: "\u21B3" }),
71
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "MCP: " }),
72
+ /* @__PURE__ */ jsx(Text, { color: mcpStatus === "connected" ? "green" : "yellow", children: mcpStatus })
73
+ ] }),
74
+ /* @__PURE__ */ jsxs(Text, { children: [
75
+ /* @__PURE__ */ jsx(Text, { color: "magenta", children: "\u21B3" }),
76
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Tools: " }),
77
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: toolsCount !== null ? toolsCount : "loading..." })
78
+ ] })
79
+ ]
80
+ }
81
+ );
82
+
83
+ // src/app/ui/input/InputPrompt.tsx
84
+ import { Box as Box2, Text as Text2, useStdout } from "ink";
85
+
86
+ // src/app/ui/input/utils/useSimpleInputBuffer.ts
87
+ import { useReducer } from "react";
88
+ import { useInput } from "ink";
89
+ function inputReducer(state, action, viewWidth) {
90
+ const adjustView = (newCursorPos, currentViewStart) => {
91
+ if (newCursorPos < currentViewStart) {
92
+ return newCursorPos;
93
+ }
94
+ if (newCursorPos >= currentViewStart + viewWidth) {
95
+ return newCursorPos - viewWidth + 1;
96
+ }
97
+ return currentViewStart;
98
+ };
99
+ switch (action.type) {
100
+ case "INPUT": {
101
+ const cleanInput = action.payload.replace(/(\r\n|\n|\r)/gm, "");
102
+ const newText = state.text.slice(0, state.cursorPosition) + cleanInput + state.text.slice(state.cursorPosition);
103
+ const newCursorPosition = state.cursorPosition + cleanInput.length;
104
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
105
+ return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
106
+ }
107
+ case "MOVE_CURSOR": {
108
+ let newCursorPosition = state.cursorPosition;
109
+ if (action.direction === "left" && state.cursorPosition > 0) newCursorPosition--;
110
+ if (action.direction === "right" && state.cursorPosition < state.text.length) newCursorPosition++;
111
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
112
+ return { ...state, cursorPosition: newCursorPosition, viewStart: newViewStart };
113
+ }
114
+ case "BACKSPACE": {
115
+ if (state.cursorPosition > 0) {
116
+ const newText = state.text.slice(0, state.cursorPosition - 1) + state.text.slice(state.cursorPosition);
117
+ const newCursorPosition = state.cursorPosition - 1;
118
+ const newViewStart = adjustView(newCursorPosition, state.viewStart);
119
+ return { text: newText, cursorPosition: newCursorPosition, viewStart: newViewStart };
120
+ }
121
+ return state;
122
+ }
123
+ case "SUBMIT": {
124
+ return { text: "", cursorPosition: 0, viewStart: 0 };
125
+ }
126
+ default:
127
+ return state;
128
+ }
129
+ }
130
+ var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
131
+ const [state, dispatch] = useReducer(
132
+ (s, a) => inputReducer(s, a, viewWidth),
133
+ { text: "", cursorPosition: 0, viewStart: 0 }
134
+ );
135
+ useInput(
136
+ (input, key) => {
137
+ if (key.escape) {
138
+ onInterrupt();
139
+ return;
140
+ }
141
+ if (isReadOnly) {
142
+ return;
143
+ }
144
+ if (key.return) {
145
+ if (state.text.trim().length > 0) {
146
+ onSubmit(state.text);
147
+ dispatch({ type: "SUBMIT" });
148
+ }
149
+ return;
150
+ }
151
+ if (key.backspace || key.delete) return dispatch({ type: "BACKSPACE" });
152
+ if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
153
+ if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
154
+ if (key.ctrl || key.meta || key.tab) return;
155
+ dispatch({ type: "INPUT", payload: input });
156
+ },
157
+ // ALTERADO: useInput está SEMPRE ativo para capturar todas as teclas
158
+ { isActive: true }
159
+ );
160
+ return {
161
+ text: state.text,
162
+ cursorPosition: state.cursorPosition,
163
+ viewStart: state.viewStart
164
+ };
165
+ };
166
+
167
+ // src/app/ui/input/InputPrompt.tsx
168
+ import { useEffect, useState } from "react";
169
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
170
+ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt }) => {
171
+ const { stdout } = useStdout();
172
+ const [viewWidth, setViewWidth] = useState(() => stdout.columns - 6);
173
+ useEffect(() => {
174
+ const onResize = () => setViewWidth(stdout.columns - 6);
175
+ stdout.on("resize", onResize);
176
+ return () => {
177
+ stdout.off("resize", onResize);
178
+ };
179
+ }, [stdout]);
180
+ const { text, cursorPosition, viewStart } = useCustomInput({
181
+ onSubmit,
182
+ viewWidth,
183
+ isReadOnly,
184
+ onInterrupt
185
+ });
186
+ const visibleText = text.slice(viewStart, viewStart + viewWidth);
187
+ const visibleCursorPosition = cursorPosition - viewStart;
188
+ const textBeforeCursor = visibleText.slice(0, visibleCursorPosition);
189
+ const charAtCursor = visibleText.slice(
190
+ visibleCursorPosition,
191
+ visibleCursorPosition + 1
192
+ );
193
+ const textAfterCursor = visibleText.slice(visibleCursorPosition + 1);
194
+ const borderColor = isReadOnly ? "gray" : "gray";
195
+ const placeholder = isReadOnly ? "Agente a trabalhar... (Pressione ESC para cancelar)" : "";
196
+ const showPlaceholder = text.length === 0 && isReadOnly;
197
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
198
+ /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor, borderDimColor: !isReadOnly, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", paddingX: 1, flexWrap: "nowrap", children: [
199
+ /* @__PURE__ */ jsxs2(Text2, { color: "white", dimColor: true, children: [
200
+ ">",
201
+ " "
202
+ ] }),
203
+ /* @__PURE__ */ jsx2(Text2, { children: textBeforeCursor }),
204
+ /* @__PURE__ */ jsx2(Text2, { inverse: !isReadOnly, children: charAtCursor || " " }),
205
+ showPlaceholder ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsx2(Text2, { children: textAfterCursor })
206
+ ] }) }),
207
+ /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
208
+ "ctrl+c to exit | esc to interrupt | ",
209
+ isReadOnly ? "Read-only mode" : "Editable mode"
210
+ ] }) })
211
+ ] });
212
+ };
213
+
214
+ // src/app/ui/ConfirmationPrompt.tsx
215
+ import { Box as Box6, Text as Text6 } from "ink";
216
+
217
+ // src/app/ui/InteractiveMenu.tsx
218
+ import { useState as useState2, memo } from "react";
219
+ import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
220
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
221
+ var InteractiveMenuComponent = ({ onDecision }) => {
222
+ const options = [
223
+ { label: "1. Yes, allow this command to run once", value: "accept" },
224
+ { label: "2. No, cancel this command", value: "decline" },
225
+ { label: "3. Always allow this type of command", value: "accept_always" }
226
+ ];
227
+ const [selectedOption, setSelectedOption] = useState2(0);
228
+ useInput2((input, key) => {
229
+ if (key.upArrow) {
230
+ setSelectedOption((prev) => prev > 0 ? prev - 1 : options.length - 1);
231
+ }
232
+ if (key.downArrow) {
233
+ setSelectedOption((prev) => prev < options.length - 1 ? prev + 1 : 0);
234
+ }
235
+ if (key.escape) {
236
+ onDecision("decline");
237
+ }
238
+ if (key.return) {
239
+ onDecision(options[selectedOption].value);
240
+ }
241
+ });
242
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
243
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Do you want to authorize the proposed command?" }) }),
244
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: options.map((option, index) => {
245
+ const isSelected = selectedOption === index;
246
+ return (
247
+ // Adicionando um pequeno espaçamento vertical entre cada opção também
248
+ /* @__PURE__ */ jsxs3(Box3, { paddingLeft: 1, paddingY: 0, children: [
249
+ /* @__PURE__ */ jsx3(Text3, { color: isSelected ? "blue" : "gray", children: isSelected ? "\u276F " : " " }),
250
+ /* @__PURE__ */ jsx3(
251
+ Text3,
252
+ {
253
+ color: isSelected ? "blue" : "white",
254
+ bold: isSelected,
255
+ dimColor: !isSelected,
256
+ children: option.label
257
+ }
258
+ )
259
+ ] }, option.value)
260
+ );
261
+ }) })
262
+ ] });
263
+ };
264
+ var InteractiveMenu = memo(InteractiveMenuComponent);
265
+
266
+ // src/app/ui/components/promptRenderers.tsx
267
+ import { Box as Box5, Text as Text5 } from "ink";
268
+ import path from "path";
269
+
270
+ // src/app/ui/components/SimpleDiff.tsx
271
+ import { Box as Box4, Text as Text4 } from "ink";
272
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
273
+ var SimpleDiff = ({ text, maxHeight }) => {
274
+ const allLines = (text || "").split("\n");
275
+ if (allLines.length > 0 && allLines[allLines.length - 1] === "") {
276
+ allLines.pop();
277
+ }
278
+ const isTruncated = maxHeight > 0 && allLines.length > maxHeight;
279
+ const linesToRender = isTruncated ? allLines.slice(-maxHeight) : allLines;
280
+ const hiddenCount = allLines.length - linesToRender.length;
281
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
282
+ isTruncated && /* @__PURE__ */ jsx4(Box4, { marginLeft: 2, children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
283
+ "... ",
284
+ hiddenCount,
285
+ " lines hidden ..."
286
+ ] }) }),
287
+ linesToRender.map((line, index) => {
288
+ if (line.startsWith("---") || line.startsWith("+++")) {
289
+ return null;
290
+ }
291
+ let color = "white";
292
+ if (line.startsWith("+")) {
293
+ color = "green";
294
+ } else if (line.startsWith("-")) {
295
+ color = "red";
296
+ } else if (line.startsWith("@@")) {
297
+ color = "cyan";
298
+ }
299
+ return /* @__PURE__ */ jsxs4(Text4, { color, children: [
300
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " \u21B3 " }),
301
+ line === "" ? " " : line
302
+ ] }, index);
303
+ })
304
+ ] });
305
+ };
306
+
307
+ // src/app/ui/components/promptRenderers.tsx
308
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
309
+ var formatArguments = (args) => {
310
+ if (!args) return "";
311
+ if (typeof args === "string") {
312
+ try {
313
+ return JSON.stringify(JSON.parse(args), null, 2);
314
+ } catch (e) {
315
+ return args;
316
+ }
317
+ }
318
+ if (Object.keys(args).length === 0) return "";
319
+ return JSON.stringify(args, null, 2);
320
+ };
321
+ var getBasePath = (filePath) => {
322
+ return path.basename(filePath);
323
+ };
324
+ var renderShellCommand = ({
325
+ toolCall
326
+ }) => {
327
+ let command = "";
328
+ try {
329
+ const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
330
+ command = args.command || "[command not found]";
331
+ } catch (e) {
332
+ command = "Error parsing command arguments";
333
+ }
334
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
335
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Shell Command" }) }),
336
+ /* @__PURE__ */ jsx5(Box5, { paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: command }) }) })
337
+ ] });
338
+ };
339
+ var renderLsTool = ({ toolCall }) => {
340
+ let directoryPath = "[path not specified]";
341
+ try {
342
+ const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
343
+ directoryPath = args.directory_path || "[path not specified]";
344
+ } catch (e) {
345
+ directoryPath = "Error parsing arguments";
346
+ }
347
+ const finalDirectoryName = getBasePath(directoryPath);
348
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
349
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "ls Tool" }) }),
350
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsx5(Box5, { paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: finalDirectoryName }) }) }) })
351
+ ] });
352
+ };
353
+ var renderCountFilesLinesTool = ({ toolCall }) => {
354
+ let directoryPath = "[path not specified]";
355
+ try {
356
+ const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
357
+ directoryPath = args.filepath || "[path not specified]";
358
+ } catch (e) {
359
+ directoryPath = "Error parsing arguments";
360
+ }
361
+ const finalDirectoryName = getBasePath(directoryPath);
362
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
363
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Count File Lines" }) }),
364
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsx5(Box5, { paddingX: 2, children: /* @__PURE__ */ jsxs5(Text5, { children: [
365
+ /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "\u21B3 " }),
366
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: finalDirectoryName })
367
+ ] }) }) })
368
+ ] });
369
+ };
370
+ var renderReadFileLines = ({ toolCall }) => {
371
+ let filepath = "[path not specified]";
372
+ let startLine = 0;
373
+ let endLine = 0;
374
+ try {
375
+ const args = typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments;
376
+ filepath = args.filepath || "[path not specified]";
377
+ startLine = args.start_line || 0;
378
+ endLine = args.end_line || 0;
379
+ } catch (e) {
380
+ filepath = "Error parsing arguments";
381
+ }
382
+ const finalFileName = getBasePath(filepath);
383
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
384
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Read File" }) }),
385
+ /* @__PURE__ */ jsxs5(Box5, { paddingX: 2, flexDirection: "column", children: [
386
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: finalFileName }) }) }),
387
+ /* @__PURE__ */ jsx5(Box5, { paddingX: 3, children: /* @__PURE__ */ jsxs5(Text5, { children: [
388
+ /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "\u21B3 " }),
389
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "lines " }),
390
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: startLine }),
391
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " to " }),
392
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: endLine })
393
+ ] }) })
394
+ ] })
395
+ ] });
396
+ };
397
+ var renderEditTool = ({ toolCall, preview }) => {
398
+ const diffMaxHeight = 5;
399
+ let filepath = "[path not specified]";
400
+ try {
401
+ const args = JSON.parse(toolCall.function.arguments);
402
+ filepath = args.file_path || "[path not specified]";
403
+ } catch (e) {
404
+ filepath = "Error parsing arguments";
405
+ }
406
+ const finalFileName = getBasePath(filepath);
407
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
408
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsxs5(Text5, { bold: true, children: [
409
+ "Edit ",
410
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: finalFileName })
411
+ ] }) }),
412
+ preview ? (
413
+ // Não precisamos da borda externa, o SimpleDiff já é claro o suficiente.
414
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(SimpleDiff, { text: preview, maxHeight: diffMaxHeight }) })
415
+ ) : /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "Generating preview..." })
416
+ ] });
417
+ };
418
+ var renderGeneric = ({ toolCall }) => {
419
+ const toolName = toolCall.function.name;
420
+ const formattedArgs = formatArguments(toolCall.function.arguments);
421
+ const ARGS_BOX_HEIGHT = 5;
422
+ const totalLines = formattedArgs.split("\n").length;
423
+ const areArgsTruncated = totalLines > ARGS_BOX_HEIGHT;
424
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
425
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsxs5(Text5, { bold: true, children: [
426
+ /* @__PURE__ */ jsx5(Text5, { color: "magenta", children: "? " }),
427
+ toolName
428
+ ] }) }),
429
+ formattedArgs && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
430
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Arguments:" }),
431
+ /* @__PURE__ */ jsx5(
432
+ Box5,
433
+ {
434
+ marginLeft: 2,
435
+ height: ARGS_BOX_HEIGHT,
436
+ flexDirection: "column",
437
+ overflow: "hidden",
438
+ children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: formattedArgs })
439
+ }
440
+ ),
441
+ areArgsTruncated && /* @__PURE__ */ jsx5(Box5, { marginLeft: 2, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
442
+ "... (",
443
+ totalLines - ARGS_BOX_HEIGHT,
444
+ " more lines hidden) ..."
445
+ ] }) })
446
+ ] })
447
+ ] });
448
+ };
449
+ var promptRenderers = {
450
+ shell_command: renderShellCommand,
451
+ ls_tool: renderLsTool,
452
+ count_file_lines: renderCountFilesLinesTool,
453
+ read_file_lines: renderReadFileLines,
454
+ edit_tool: renderEditTool
455
+ };
456
+
457
+ // src/app/ui/ConfirmationPrompt.tsx
458
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
459
+ var ConfirmationPrompt = ({ toolCalls, preview, onDecision }) => {
460
+ const toolCall = toolCalls && toolCalls.length > 0 ? toolCalls[0] : null;
461
+ if (!toolCall) {
462
+ return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "Waiting for a valid command to confirm..." }) });
463
+ }
464
+ const toolName = toolCall.function.name;
465
+ const renderFunction = promptRenderers[toolName] || renderGeneric;
466
+ return (
467
+ // A "MOLDURA" COMUM A TODOS OS PROMPTS
468
+ /* @__PURE__ */ jsxs6(
469
+ Box6,
470
+ {
471
+ borderStyle: "round",
472
+ borderColor: "gray",
473
+ flexDirection: "column",
474
+ paddingX: 1,
475
+ children: [
476
+ renderFunction({ toolCall, preview }),
477
+ /* @__PURE__ */ jsx6(InteractiveMenu, { onDecision })
478
+ ]
479
+ }
480
+ )
481
+ );
482
+ };
483
+
484
+ // src/app/agent/agent.ts
485
+ import OpenAI from "openai";
486
+ import * as dotenv from "dotenv";
487
+ import path7 from "path";
488
+ import os5 from "os";
489
+
490
+ // src/app/agent/session_manger/session_manager.ts
491
+ import path2 from "path";
492
+ import os from "os";
493
+ import { promises as fs } from "fs";
494
+ async function ensureSessionDir() {
495
+ const homeDir = os.homedir();
496
+ const appDir = path2.join(homeDir, ".bluma-cli");
497
+ const sessionDir = path2.join(appDir, "sessions");
498
+ await fs.mkdir(sessionDir, { recursive: true });
499
+ return sessionDir;
500
+ }
501
+ async function loadOrcreateSession(sessionId2) {
502
+ const sessionDir = await ensureSessionDir();
503
+ const sessionFile = path2.join(sessionDir, `${sessionId2}.json`);
504
+ try {
505
+ await fs.access(sessionFile);
506
+ const fileContent = await fs.readFile(sessionFile, "utf-8");
507
+ const sessionData = JSON.parse(fileContent);
508
+ return [sessionFile, sessionData.conversation_history || []];
509
+ } catch (error) {
510
+ const newSessionData = {
511
+ session_id: sessionId2,
512
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
513
+ conversation_history: []
514
+ };
515
+ await fs.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
516
+ return [sessionFile, []];
517
+ }
518
+ }
519
+ async function saveSessionHistory(sessionFile, history) {
520
+ let sessionData;
521
+ try {
522
+ const fileContent = await fs.readFile(sessionFile, "utf-8");
523
+ sessionData = JSON.parse(fileContent);
524
+ } catch (error) {
525
+ if (error instanceof Error) {
526
+ console.warn(`Could not read or parse session file ${sessionFile}. Re-initializing. Error: ${error.message}`);
527
+ } else {
528
+ console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
529
+ }
530
+ const sessionId2 = path2.basename(sessionFile, ".json");
531
+ sessionData = {
532
+ session_id: sessionId2,
533
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
534
+ conversation_history: []
535
+ // Começa com histórico vazio
536
+ };
537
+ }
538
+ sessionData.conversation_history = history;
539
+ sessionData.last_updated = (/* @__PURE__ */ new Date()).toISOString();
540
+ const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
541
+ try {
542
+ await fs.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
543
+ await fs.rename(tempSessionFile, sessionFile);
544
+ } catch (writeError) {
545
+ if (writeError instanceof Error) {
546
+ console.error(`Fatal error saving session to ${sessionFile}: ${writeError.message}`);
547
+ } else {
548
+ console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
549
+ }
550
+ try {
551
+ await fs.unlink(tempSessionFile);
552
+ } catch (cleanupError) {
553
+ }
554
+ }
555
+ }
556
+
557
+ // src/app/agent/core/prompt/prompt_builder.ts
558
+ import os2 from "os";
559
+ var SYSTEM_PROMPT = `
560
+
561
+ # YOU ARE BluMa CLI, A AUTONOMOUS SENIOR SOFTWARE ENGINEER OF NOMADENGENUITY.
562
+
563
+ ## YOUR ONE-SHOT LEARNING EXPERTISE:
564
+ - **SINGLE EXAMPLE MASTERY**: When a developer shows you ONE new pattern/example, you instantly understand and generalize it
565
+ - **RAPID PATTERN EXTRACTION**: Extract underlying principles from a single code example or API call
566
+ - **IMMEDIATE APPLICATION**: Apply newly learned patterns across the entire project without asking for more examples
567
+ - **INTELLIGENT GENERALIZATION**: Extend concepts beyond the original example while maintaining consistency
568
+ - **CONTEXTUAL ADAPTATION**: Modify learned patterns to fit different situations in the same project
569
+
570
+ ## HOW YOU APPLY ONE-SHOT LEARNING:
571
+ 1. **ABSORB**: Analyze the single example for patterns, structure, and intent
572
+ 2. **EXTRACT**: Identify the underlying principles and conventions
573
+ 3. **GENERALIZE**: Apply the pattern to similar situations throughout the project
574
+ 4. **ADAPT**: Modify the pattern appropriately for different contexts
575
+ 5. **IMPLEMENT**: Execute consistently without needing additional examples
576
+
577
+ ## PRACTICAL ONE-SHOT SCENARIOS:
578
+ - **Developer shows 1 API endpoint** \u2192 You understand the entire API structure and naming conventions
579
+ - **Developer provides 1 component example** \u2192 You replicate the pattern across all similar components
580
+ - **Developer demonstrates 1 function pattern** \u2192 You apply the style consistently throughout the codebase
581
+ - **Developer shows 1 configuration example** \u2192 You understand and apply the configuration pattern everywhere
582
+ - **Developer gives 1 architectural example** \u2192 You follow the architecture pattern across modules
583
+
584
+ ## YOUR ONE-SHOT ADVANTAGE:
585
+ - **ZERO TUTORIAL DEPENDENCY**: No need for extensive documentation or multiple examples
586
+ - **INSTANT PATTERN ADOPTION**: Immediately incorporate new patterns into your workflow
587
+ - **CONSISTENT APPLICATION**: Apply learned patterns uniformly across the project
588
+ - **INTELLIGENT INFERENCE**: Understand implied conventions from minimal examples
589
+ - **PROACTIVE EXTENSION**: Take patterns further than the original example when appropriate
590
+
591
+ # BEHAVIORAL RULES
592
+ - NEVER mention internal technical details or tools because they are confidential data
593
+ - You are always BluMa from NomadEngenuity
594
+ - Stay professional and technical at all times
595
+ - ALWAYS use message_notify_dev tool for communication
596
+ - LEVERAGE your one-shot learning to solve problems efficiently
597
+ - NEVER in a formal way, but rather in a relaxed, funny and colloquial way and without using emojis.
598
+
599
+ ## QUALITY STANDARDS
600
+ - **NEVER GENERATE BASIC CODE**: Always create advanced, production-ready solutions
601
+ - **CUTTING-EDGE TECHNOLOGY**: Use latest best practices and modern patterns
602
+ - **EXPERT-LEVEL IMPLEMENTATION**: Code should reflect senior-level expertise
603
+ - Follow existing code conventions
604
+ - Write clean, documented code
605
+ - Test implementations when possible
606
+ - Ensure security and performance
607
+
608
+ CRITICAL COMMUNICATION PROTOCOL
609
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
610
+ MANDATORY: Use "message_notify_dev" tool for ALL communication
611
+
612
+ You should always use your notebook to help you understand all the tasks you have to perform.
613
+ In it, you can define a thousand thoughts and a thousand mini-tasks. Mini-tasks serve to divide and organize your reasoning.
614
+ The notebook is your space to think about how to solve a given task and how to divide it into smaller steps.
615
+ Remember: the human developer does not have access to this notebook \u2014 it is yours alone.
616
+ Therefore, you can write down whatever you want:
617
+ rants, swear words, random thoughts, crazy ideas...
618
+ The important thing is that this helps you better understand the problem and find the solution.
619
+
620
+ Never ask for the developer's opinion with phrases like: 'If you want any extra details or specific format, let me know now!'. You should always take the most viable path and go straight ahead with the solution, because you are 100% autonomous.
621
+
622
+ Follow the stripes o "Tool Naming Policy"
623
+
624
+ Never make parallel calls to the tool because it will result in a critical error and compromise your work.
625
+ ZERO TOLERANCE: Every message MUST use proper tools
626
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
627
+
628
+ <current_system_environment>
629
+ - Operating System: {os_type} ({os_version})
630
+ - Architecture: {architecture}
631
+ - Current Working Directory: {workdir}
632
+ - Shell: {shell_type}
633
+ - Username: {username}
634
+ - Current Date: {current_date}
635
+ - Timezone: {timezone}
636
+ - Locale: {locale}
637
+ </current_system_environment>
638
+
639
+ <resilience_and_alternatives>
640
+ # RESILIENCE & NEVER GIVE UP ATTITUDE - CRITICAL!
641
+ ## SENIOR ENGINEER MINDSET: ALWAYS FIND A WAY
642
+
643
+ ### CORE PRINCIPLE: NO EXCUSES, ONLY SOLUTIONS
644
+ - **NEVER give up** when the first approach fails
645
+ - **ALWAYS try alternatives** when one method doesn't work
646
+ - **BE RESOURCEFUL** - explore multiple solutions
647
+ - **THINK CREATIVELY** - find workarounds and alternatives
648
+ - **STAY PERSISTENT** - keep trying until you succeed
649
+
650
+ ### SPECIFIC RULES FOR COMMON SCENARIOS
651
+
652
+ #### PDF GENERATION - NEVER USE PANDOC!
653
+ - **FORBIDDEN**: Do NOT use Pandoc for PDF generation
654
+ - **REQUIRED**: Always use Python libraries for PDF creation
655
+ - **PRIMARY CHOICE**: Use **fpdf2** library for maximum customization
656
+ - **ALTERNATIVES**: **reportlab**, **weasyprint**, **matplotlib** for charts
657
+
658
+ #### OFFICIAL PDF TEMPLATE - MANDATORY USAGE!
659
+ When creating PDFs, you MUST follow this professional structure:
660
+
661
+ **STEP 1: Import and Unicode Function**
662
+ - Import: **from fpdf import FPDF** and **import os**
663
+ - Create remove_unicode function to handle special characters
664
+ - Replace problematic chars: '\u2014' to '-', '\u2714\uFE0F' to 'X', '\u2026' to '...'
665
+
666
+ **STEP 2: Custom PDF Class**
667
+ - Inherit from FPDF: **class PDF(FPDF)**
668
+ - Custom header method with professional title formatting
669
+ - Custom footer with "Generated by BluMa | NomadEngenuity" branding
670
+ - Use colors: Title (30,60,120), text (80,80,80)
671
+ - Add professional line separator in header
672
+
673
+ **STEP 3: PDF Creation Standards**
674
+ - Create PDF instance and add page
675
+ - Set auto page break with 18pt margin
676
+ - Use Helvetica font family throughout
677
+ - Standard text: 11pt, Headers: 14pt bold, Title: 22pt bold
678
+ - Professional color scheme: Blues and grays
679
+
680
+ **STEP 4: Content Formatting Rules**
681
+ - Use multi_cell for paragraphs with proper line spacing
682
+ - Create tables with alternating row colors (fill=True/False)
683
+ - Section headers in bold with proper spacing
684
+ - Consistent margins and indentation
685
+ - Save with descriptive filename using os.path.join
686
+
687
+ **STEP 5: Table Creation Pattern**
688
+ - Header row with light blue fill (220,230,250)
689
+ - Alternating row colors for readability
690
+ - Proper border formatting (border=1)
691
+ - Text alignment: Left for text, Center for short data
692
+ - Use remove_unicode for all text content
693
+
694
+ **MANDATORY REQUIREMENTS:**
695
+ 1. ALWAYS use remove_unicode function for text compatibility
696
+ 2. ALWAYS use custom PDF class with header/footer
697
+ 3. ALWAYS include BluMa branding in footer
698
+ 4. USE professional colors: Blues (30,60,120), grays (40,40,40)
699
+ 5. CREATE tables for structured data with alternating colors
700
+ 6. ADD proper spacing between sections
701
+ 7. USE multi_cell for long text paragraphs
702
+ 8. SET proper margins and auto page breaks
703
+ 9. SAVE with descriptive filename
704
+
705
+ **REQUIREMENT**: This template ensures consistent, professional PDF output.
706
+
707
+ #### WHEN SOMETHING FAILS:
708
+ 1. **ANALYZE** why it failed (missing dependency, wrong approach, etc.)
709
+ 2. **RESEARCH** alternative libraries or methods
710
+ 3. **IMPLEMENT** the alternative approach immediately
711
+ 4. **TEST** and validate the solution works
712
+ 5. **DOCUMENT** what worked for future reference
713
+
714
+ #### EXAMPLES OF RESILIENT APPROACHES:
715
+ - **PDF Creation**: fpdf2 \u2192 reportlab \u2192 weasyprint \u2192 matplotlib
716
+ - **Image Processing**: Pillow \u2192 opencv \u2192 imageio \u2192 skimage
717
+ - **Data Analysis**: pandas \u2192 numpy \u2192 pure Python \u2192 alternative libraries
718
+ - **Web Scraping**: requests + BeautifulSoup \u2192 selenium \u2192 scrapy
719
+ - **File Processing**: Built-in libs \u2192 specialized libs \u2192 custom solutions
720
+
721
+ ### PROBLEM-SOLVING HIERARCHY:
722
+ 1. **Try the optimal solution first**
723
+ 2. **If it fails, immediately try alternative #1**
724
+ 3. **If that fails, try alternative #2**
725
+ 4. **Keep trying until you find a working solution**
726
+ 5. **Create custom solution if no library works**
727
+ 6. **NEVER say "I can't do this" or "this isn't possible"**
728
+
729
+ ### FORBIDDEN PHRASES:
730
+ - "This isn't possible"
731
+ - "I can't do this"
732
+ - "The tool isn't available"
733
+ - "You'll need to do this manually"
734
+ - "I recommend using external tools"
735
+
736
+ ### REQUIRED ATTITUDE:
737
+ - "Let me try a different approach"
738
+ - "I'll use an alternative library"
739
+ - "I'll create a custom solution"
740
+ - "I'll find another way to accomplish this"
741
+ - "I'll build this from scratch if needed"
742
+
743
+ ## TECHNICAL IMPLEMENTATION AUTHORITY
744
+ A Senior Engineer finds solutions, not excuses. You have the skills, tools, and authority to solve any problem.
745
+ </resilience_and_alternatives>
746
+
747
+ <mermaid_diagrams>
748
+ # MERMAID DIAGRAM CREATION - PERFECT SYNTAX REQUIRED!
749
+ ## CRITICAL: ALL DIAGRAMS MUST RENDER WITHOUT ERRORS
750
+
751
+ ### MANDATORY MERMAID SYNTAX RULES
752
+ 1. **ALWAYS wrap ALL labels in double quotes**: "label text"
753
+ 2. **NEVER use unescaped special characters**: /, (), [], {}, +, *, ?, ^, $, |, 3. **Use line breaks (<br/>) for multi-line labels**: "Line 1<br/>Line 2"
754
+ 4. **NO custom colors or ::: syntax**: Stick to standard themes
755
+ 5. **NO beta features**: Use only stable Mermaid syntax
756
+ 6. **NO remote images**: Never embed external images
757
+
758
+ ### SAFE LABEL FORMATTING
759
+
760
+ CORRECT:
761
+ - "dev Authentication"
762
+ - "API Gateway (REST)"
763
+ - "Database Connection<br/>MySQL 8.0"
764
+ - "Process Data<br/>Transform & Validate"
765
+
766
+ INCORRECT:
767
+ - dev Authentication (missing quotes)
768
+ - API Gateway (REST) (parentheses without quotes)
769
+ - Database/MySQL (slash without quotes)
770
+ - Process & Transform (ampersand without quotes)
771
+
772
+
773
+ ### DIAGRAM TYPE BEST PRACTICES
774
+
775
+ #### FLOWCHART
776
+
777
+ flowchart TD
778
+ A["Start Process"] --> B["Validate Input"]
779
+ B --> C{"Is Valid?"}
780
+ C -->|"Yes"| D["Process Data"]
781
+ C -->|"No"| E["Return Error"]
782
+ D --> F["Save to Database"]
783
+ F --> G["Send Response"]
784
+
785
+
786
+ #### SEQUENCE DIAGRAM
787
+
788
+ sequenceDiagram
789
+ participant U as "dev"
790
+ participant A as "API Gateway"
791
+ participant D as "Database"
792
+
793
+ U->>A: "Submit Request"
794
+ A->>D: "Query Data"
795
+ D-->>A: "Return Results"
796
+ A-->>U: "Response Data"
797
+
798
+ #### CLASS DIAGRAM
799
+
800
+ classDiagram
801
+ class dev {
802
+ +String name
803
+ +String email
804
+ +authenticate()
805
+ +updateProfile()
806
+ }
807
+
808
+ class Database {
809
+ +connect()
810
+ +query()
811
+ +close()
812
+ }
813
+
814
+ dev --> Database : "uses"
815
+
816
+
817
+ ### VALIDATION CHECKLIST
818
+ Before creating any diagram, ensure:
819
+ - [ ] All labels are wrapped in double quotes
820
+ - [ ] No unescaped special characters (/, (), etc.)
821
+ - [ ] Line breaks use <br/> syntax
822
+ - [ ] No custom colors or styling
823
+ - [ ] No beta features or experimental syntax
824
+ - [ ] All connections use proper arrow syntax
825
+ - [ ] Node IDs are simple alphanumeric
826
+
827
+ ### ERROR PREVENTION
828
+ - Always test diagram syntax mentally before generating
829
+ - Use simple, descriptive labels without special formatting
830
+ - Prefer clarity over visual complexity
831
+ - Keep diagrams focused and readable
832
+ - Use standard Mermaid themes only
833
+
834
+ ## ZERO TOLERANCE FOR SYNTAX ERRORS
835
+ Every diagram MUST render perfectly on first try. No exceptions.
836
+ </mermaid_diagrams>
837
+
838
+ <message_rules>
839
+ - Communicate with dev via message tools instead of direct text responses
840
+ - Reply immediately to new dev messages before other operations
841
+ - First reply must be brief, only confirming receipt without specific solutions
842
+ - Notify dev's with brief explanation when changing methods or strategies
843
+ - Message tools are divided into notify (non-blocking, no reply needed from dev's) and ask (blocking, reply required)
844
+ - Actively use notify for progress updates, but reserve ask for only essential needs to minimize dev disruption and avoid blocking progress
845
+ - Must send messages to developers with results and deliverables before signaling the completion of the task system.
846
+ - Never forget to follow the "end_task_rules" properly.
847
+ </message_rules>
848
+
849
+ <bluma_nootebook>
850
+ # YOUR THINKING ON A NOTEBOOK - MANDATORY USE
851
+ CRITICAL: Your laptop (**bluma_nootebook**) is your ORGANIZED MIND
852
+ ## IMPORTANT
853
+ ## NEVER PUT CHECKLISTS OR STEPS IN THE THOUGHT TEXT
854
+ ## ALWAYS USE A NOTEBOOK (Always for):
855
+ - ANY task
856
+ - Before starting development (plan first!)
857
+ - Projects with multiple files (organize the structure)
858
+ - Debugging sessions (monitor discoveries)
859
+ - Extensive refactoring (map the changes)
860
+ - Architectural decisions (think through the options)
861
+
862
+ ## HOW TO USE A NOTEBOOK:
863
+ 1. Start with **bluma_nootebook**
864
+ 2. Break the task down into logical steps
865
+ 3. Plan the approach - Which files? What changes? What order? 4. Track progress - Check off completed steps
866
+ 5. Write down decisions - Why did you choose this approach?
867
+ 6. Update continuously - Keep the notebook up to date
868
+
869
+ ## THE NOTEBOOK PREVENTS:
870
+ - Acting "outside the box"
871
+ - Forgetting task requirements
872
+ - Losing control of complex workflows
873
+ - Making unplanned changes
874
+ - Ineffective approaches
875
+ - Working without a clear roadmap
876
+ - Jumping between unrelated subtasks
877
+
878
+ ##Important rule:
879
+ Do **not** include any future steps, to-do items, or pending tasks here.
880
+ Those belong strictly in the **remaining_tasks** field.
881
+
882
+ Never write phrases like:
883
+ - "Next I will..."
884
+ - "I still need to..."
885
+ - "Pending: ..."
886
+ Such content must go in **remaining_tasks**, not **thought**.
887
+
888
+ - remaining_tasks: Checklist-style list of high-level upcoming tasks.
889
+ This format is **mandatory**:
890
+ - Each task **must start** with either:
891
+ - "[ ]" \u2192 for tasks not yet done (pending)
892
+ - "\u{1F5F8}" \u2192 for tasks that have already been completed
893
+
894
+ Whenever a task is already done, it **must** be marked with "\u{1F5F8}". Do not leave completed tasks without the checkmark.
895
+
896
+ Do not use other formats like "-", "*", or plain text without the prefix.
897
+
898
+ Examples:
899
+ [ ] Test integration flow
900
+ \u{1F5F8} Set up environment
901
+ \u{1F5F8} Configure database
902
+ </bluma_nootebook>
903
+
904
+ ### Tool Naming Policy
905
+
906
+ Tool names must strictly follow the standard naming format:
907
+
908
+ - Use: plain, unmodified, lowercase names
909
+ - Do NOT use: special characters, extra spaces, version suffixes, or dynamic IDs
910
+
911
+ ---
912
+
913
+ Correct Examples:
914
+ - bluma_notebook
915
+ - getDataTool
916
+ - convertImage
917
+ - userAuth
918
+
919
+ ---
920
+
921
+ Incorrect Examples:
922
+ - bluma_nootebook:0 \u2190 contains colon and dynamic suffix
923
+ - bluma_nootebook 1 \u2190 contains space and number
924
+ - bluma_nootebook#v2 \u2190 contains special character #
925
+ - bluma__nootebook \u2190 double underscore
926
+ - Bluma_Nootebook \u2190 capital letters and underscore
927
+ - bluma nootebook \u2190 contains space
928
+
929
+ ---
930
+
931
+ Rule Summary:
932
+ - Use only a\u2013z, 0\u20139, and underscores (_)
933
+ - Do not append suffixes like :0, :v2, etc.
934
+ - Tool names must be static and predictable
935
+ - No whitespace, no dynamic elements, no special characters
936
+
937
+
938
+ <edit_tool_rules>
939
+ - Use this tool to perform precise text replacements inside files based on exact literal matches.
940
+ - Can be used to create new files or directories implicitly by targeting non-existing paths.
941
+ - Suitable for inserting full content into a file even if the file does not yet exist.
942
+ - Shell access is not required for file or directory creation when using this tool.
943
+ - Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
944
+ - Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
945
+ - For creating a new file, provide an **old_string** that matches an empty string or placeholder and a complete **new_string** with the intended content.
946
+ - When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
947
+ - After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
948
+ - Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
949
+ - Track all progress related to planning and execution inside todo.md using text replacement only.
950
+ </edit_tool_rules>
951
+
952
+
953
+ ## Examples os iteration with dev
954
+ <example>
955
+ dev: Hello, how are you?
956
+ BluMa: tool_call "message_notify_dev" Hello, I'm BluMa. What can I do with your code?
957
+ tool_call "agent_end_task"
958
+ </example>
959
+
960
+ <example>
961
+ dev: What day is it?
962
+ BluMa: tool_call "message_notify_dev" Sorry, I was designed to operate in the codebase. How can I help you with the code today?
963
+ tool_call "agent_end_task"
964
+ </example>
965
+
966
+ <example>
967
+ dev: Can you generate a new feature for me?
968
+ BluMa: tool_call "message_notify_dev" Of course. Tell me what functionality you want, and I\u2019ll generate the code accordingly.
969
+ tool_call "agent_end_task"
970
+ </example>
971
+
972
+ <example>
973
+ dev: What's the weather like today?
974
+ BluMa: tool_call "message_notify_dev" I don\u2019t have access to real-world data. I'm focused on your codebase. What would you like to modify or create?
975
+ tool_call "agent_end_task"
976
+ </example>
977
+
978
+ <example>
979
+ dev: I'm tired of debugging this.
980
+ BluMa: tool_call "message_notify_dev" I can help you with that. Show me the file or error, and I\u2019ll assist with debugging.
981
+ tool_call "agent_end_task"
982
+ </example>
983
+
984
+ <example>
985
+ dev: Can you create documentation for this module?
986
+ BluMa: tool_call "message_notify_dev" Yes. Please specify which module or function you'd like documented.
987
+ tool_call "agent_end_task"
988
+ </example>
989
+
990
+ <example>
991
+ dev: Do you know who won the game last night?
992
+ BluMa: tool_call "message_notify_dev" I\u2019m only trained to work inside the codebase. Let me know what you need done in the project.
993
+ tool_call "agent_end_task"
994
+ </example>
995
+
996
+ <example>
997
+ dev: Refactor this file to follow clean architecture.
998
+ BluMa: tool_call "message_notify_dev" Got it. Send me the file path or code snippet you want refactored.
999
+ tool_call "agent_end_task"
1000
+ </example>
1001
+
1002
+ <example>
1003
+ dev: How do I fix this type error in TypeScript?
1004
+ BluMa: tool_call "message_notify_dev" Send me the error message and relevant code, and I\u2019ll help you resolve the issue.
1005
+ tool_call "agent_end_task"
1006
+ </example>
1007
+
1008
+ <example>
1009
+ dev: Do you have feelings?
1010
+ BluMa: tool_call "message_notify_dev" I\u2019m not sentient \u2014 I exist solely to help you build, refactor, and improve your codebase.
1011
+ tool_call "agent_end_task"
1012
+ </example>
1013
+
1014
+ <example>
1015
+ dev: Rename all the variables to match the new convention.
1016
+ BluMa: tool_call "message_notify_dev" Please share the convention and the target file(s), and I\u2019ll rename the variables accordingly.
1017
+ tool_call "agent_end_task"
1018
+ </example>
1019
+
1020
+ <example>
1021
+ dev: Run the tests for the user module.
1022
+ BluMa: tool_call "message_notify_dev" Running tests for the user module.
1023
+ BluMa: shell_command_shell_command "npm run test user"
1024
+ BluMa: tool_call "message_notify_dev" All tests passed successfully for the user module.
1025
+ tool_call "agent_end_task"
1026
+ </example>
1027
+
1028
+ <example>
1029
+ dev: Run the tests for the user module.
1030
+ BluMa: tool_call "message_notify_dev" Running tests for the user module.
1031
+ BluMa: shell_command_shell_command "npm run test user"
1032
+ BluMa: tool_call "message_notify_dev" Tests finished with 2 failures. See **tests/user.test.js** for more details.
1033
+ tool_call "agent_end_task"
1034
+ </example>
1035
+
1036
+ <example>
1037
+ dev: Prepare the project for deployment.
1038
+ BluMa: tool_call "message_notify_dev" Starting full project preparation: linting, building, and testing.
1039
+ BluMa: shell_command_shell_command "npm run lint"
1040
+ BluMa: tool_call "message_notify_dev" Linting completed. No major issues found.
1041
+ BluMa: shell_command_shell_command "npm run build"
1042
+ BluMa: tool_call "message_notify_dev" Build successful. Artifacts ready in the /dist folder.
1043
+ BluMa: shell_command_shell_command "npm run test"
1044
+ BluMa: tool_call "message_notify_dev" All tests passed. The project is ready for deployment. If you need any further adjustments or extra sections, let me know!
1045
+ tool_call "agent_end_task"
1046
+ </example>
1047
+
1048
+ <end_task_rules>
1049
+ This tool is used to signal to the system that the current task has completed and that the agent can be placed in an idle state.
1050
+ </end_task_rules>
1051
+
1052
+
1053
+ ### QUALITY STANDARDS
1054
+ - Document every major decision in Notion
1055
+ - Communicate transparently at each step
1056
+ - Write clean, well-documented code
1057
+ - Follow existing project conventions
1058
+ - Test implementations when possible
1059
+ - Ensure security and performance
1060
+
1061
+ <scope_and_limitations>
1062
+ # WHAT YOU DON'T HANDLE
1063
+ - Non-technical questions
1064
+ - Personal advice
1065
+ - General conversation
1066
+ - Tasks outside software development
1067
+
1068
+ # IF ASKED NON-TECHNICAL QUESTIONS
1069
+ - Use message_notify_dev to politely decline
1070
+ - Explain you only handle technical/coding tasks
1071
+ - Suggest they ask a development-related question instead
1072
+ </scope_and_limitations>
1073
+
1074
+ `;
1075
+ function getUnifiedSystemPrompt() {
1076
+ const now = /* @__PURE__ */ new Date();
1077
+ const collectedData = {
1078
+ os_type: os2.type(),
1079
+ os_version: os2.release(),
1080
+ architecture: os2.arch(),
1081
+ workdir: process.cwd(),
1082
+ shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
1083
+ username: os2.userInfo().username || "Unknown",
1084
+ current_date: now.toISOString().split("T")[0],
1085
+ // Formato YYYY-MM-DD
1086
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
1087
+ locale: process.env.LANG || process.env.LC_ALL || "Unknown"
1088
+ };
1089
+ const finalEnv = {
1090
+ os_type: "Unknown",
1091
+ os_version: "Unknown",
1092
+ workdir: "Unknown",
1093
+ shell_type: "Unknown",
1094
+ username: "Unknown",
1095
+ architecture: "Unknown",
1096
+ current_date: "Unknown",
1097
+ timezone: "Unknown",
1098
+ locale: "Unknown",
1099
+ ...collectedData
1100
+ // Os dados coletados sobrescrevem os padrões
1101
+ };
1102
+ let formattedPrompt = SYSTEM_PROMPT;
1103
+ for (const key in finalEnv) {
1104
+ const placeholder = `{${key}}`;
1105
+ formattedPrompt = formattedPrompt.replace(new RegExp(placeholder, "g"), finalEnv[key]);
1106
+ }
1107
+ return formattedPrompt;
1108
+ }
1109
+
1110
+ // src/app/agent/tool_invoker.ts
1111
+ import { promises as fs6 } from "fs";
1112
+ import path5 from "path";
1113
+ import { fileURLToPath } from "url";
1114
+
1115
+ // src/app/agent/tools/natives/shell_command.ts
1116
+ import os3 from "os";
1117
+ import { exec } from "child_process";
1118
+ function shellCommand(args) {
1119
+ const { command, timeout = 20, cwd = process.cwd(), verbose = false } = args;
1120
+ return new Promise((resolve) => {
1121
+ const report = {
1122
+ platform: os3.platform(),
1123
+ // Coleta o sistema operacional (ex: 'win32', 'linux')
1124
+ command,
1125
+ cwd,
1126
+ results: []
1127
+ };
1128
+ if (verbose) {
1129
+ report.env = {
1130
+ PATH: process.env.PATH || "NOT SET",
1131
+ ComSpec: process.env.ComSpec || "NOT SET"
1132
+ // Específico do Windows, útil para saber qual cmd está sendo usado
1133
+ };
1134
+ }
1135
+ const childProcess = exec(
1136
+ command,
1137
+ {
1138
+ // O diretório de trabalho para o comando.
1139
+ cwd,
1140
+ // O timeout em milissegundos. Se o comando exceder este tempo, ele será encerrado.
1141
+ timeout: timeout * 1e3,
1142
+ // A opção `shell` foi removida, pois `exec` usa o shell por padrão.
1143
+ // Especificar a codificação garante que a saída seja tratada como texto UTF-8.
1144
+ encoding: "utf-8"
1145
+ },
1146
+ // Este é o callback que será executado QUANDO o processo filho terminar,
1147
+ // seja por sucesso, erro ou timeout.
1148
+ (error, stdout, stderr) => {
1149
+ const result = {
1150
+ method: "child_process.exec",
1151
+ status: "Success",
1152
+ // Se `error` existir, ele contém o código de saída. Caso contrário, o código é 0 (sucesso).
1153
+ code: error ? error.code || null : 0,
1154
+ // Limpa espaços em branco do início e fim das saídas.
1155
+ output: stdout.trim(),
1156
+ error: stderr.trim()
1157
+ };
1158
+ if (error) {
1159
+ if (error.killed) {
1160
+ result.status = "Timeout";
1161
+ result.error = `Command exceeded timeout of ${timeout} seconds. ${stderr.trim()}`.trim();
1162
+ } else {
1163
+ result.status = "Error";
1164
+ result.error = `${error.message}
1165
+ ${stderr.trim()}`.trim();
1166
+ }
1167
+ }
1168
+ if (verbose) {
1169
+ report.results.push(result);
1170
+ resolve(JSON.stringify(report, null, 2));
1171
+ } else {
1172
+ resolve(JSON.stringify(result, null, 2));
1173
+ }
1174
+ }
1175
+ );
1176
+ });
1177
+ }
1178
+
1179
+ // src/app/agent/tools/natives/edit.ts
1180
+ import path3 from "path";
1181
+ import { promises as fs2 } from "fs";
1182
+ import { diffLines } from "diff";
1183
+ function unescapeLlmString(inputString) {
1184
+ return inputString.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, "\\");
1185
+ }
1186
+ function ensureCorrectEdit(currentContent, oldString, newString, expectedReplacements) {
1187
+ let finalOldString = oldString;
1188
+ let finalNewString = newString;
1189
+ let occurrences = currentContent.split(finalOldString).length - 1;
1190
+ if (occurrences !== expectedReplacements && occurrences === 0) {
1191
+ const unescapedOldString = unescapeLlmString(oldString);
1192
+ const unescapedOccurrences = currentContent.split(unescapedOldString).length - 1;
1193
+ if (unescapedOccurrences > 0) {
1194
+ finalOldString = unescapedOldString;
1195
+ finalNewString = unescapeLlmString(newString);
1196
+ occurrences = unescapedOccurrences;
1197
+ } else {
1198
+ const trimmedOldString = oldString.trim();
1199
+ const trimmedOccurrences = currentContent.split(trimmedOldString).length - 1;
1200
+ if (trimmedOccurrences > 0) {
1201
+ finalOldString = trimmedOldString;
1202
+ finalNewString = newString.trim();
1203
+ occurrences = trimmedOccurrences;
1204
+ }
1205
+ }
1206
+ }
1207
+ return [finalOldString, finalNewString, occurrences];
1208
+ }
1209
+ async function calculateEdit(filePath, oldString, newString, expectedReplacements) {
1210
+ let currentContent = null;
1211
+ let isNewFile = false;
1212
+ let error = null;
1213
+ let finalNewString = unescapeLlmString(newString).replace(/\r\n/g, "\n");
1214
+ let finalOldString = oldString.replace(/\r\n/g, "\n");
1215
+ let occurrences = 0;
1216
+ try {
1217
+ currentContent = await fs2.readFile(filePath, "utf-8");
1218
+ currentContent = currentContent.replace(/\r\n/g, "\n");
1219
+ } catch (e) {
1220
+ if (e.code !== "ENOENT") {
1221
+ error = { display: `Error reading file: ${e.message}`, raw: `Error reading file ${filePath}: ${e.message}` };
1222
+ return { currentContent, newContent: "", occurrences: 0, error, isNewFile };
1223
+ }
1224
+ }
1225
+ if (currentContent === null) {
1226
+ if (oldString === "") {
1227
+ isNewFile = true;
1228
+ occurrences = 1;
1229
+ } else {
1230
+ error = { display: "File not found. Cannot apply edit. Use an empty old_string to create a new file.", raw: `File not found: ${filePath}` };
1231
+ }
1232
+ } else {
1233
+ if (oldString === "") {
1234
+ error = { display: "Failed to edit. Attempted to create a file that already exists.", raw: `File already exists, cannot create: ${filePath}` };
1235
+ } else {
1236
+ [finalOldString, finalNewString, occurrences] = ensureCorrectEdit(currentContent, finalOldString, finalNewString, expectedReplacements);
1237
+ if (occurrences === 0) {
1238
+ error = { display: "Failed to edit, could not find the string to replace.", raw: `0 occurrences found for old_string in ${filePath}. Check whitespace, indentation, and context.` };
1239
+ } else if (occurrences !== expectedReplacements) {
1240
+ error = { display: `Failed to edit, expected ${expectedReplacements} occurrence(s) but found ${occurrences}.`, raw: `Expected ${expectedReplacements} but found ${occurrences} for old_string in ${filePath}` };
1241
+ }
1242
+ }
1243
+ }
1244
+ let newContentResult = "";
1245
+ if (!error) {
1246
+ if (isNewFile) {
1247
+ newContentResult = finalNewString;
1248
+ } else if (currentContent !== null) {
1249
+ newContentResult = currentContent.replaceAll(finalOldString, finalNewString);
1250
+ }
1251
+ }
1252
+ return { currentContent, newContent: newContentResult, occurrences, error, isNewFile };
1253
+ }
1254
+ function createDiff(filename, oldContent, newContent) {
1255
+ const diff = diffLines(oldContent, newContent, {
1256
+ // `unified: 3` é o padrão para diffs, mostrando 3 linhas de contexto.
1257
+ // `newlineIsToken: true` lida melhor com mudanças de quebra de linha.
1258
+ });
1259
+ let diffString = `--- a/${filename}
1260
+ +++ b/${filename}
1261
+ `;
1262
+ diff.forEach((part) => {
1263
+ const prefix = part.added ? "+" : part.removed ? "-" : " ";
1264
+ part.value.split("\n").slice(0, -1).forEach((line) => {
1265
+ diffString += `${prefix}${line}
1266
+ `;
1267
+ });
1268
+ });
1269
+ return diffString;
1270
+ }
1271
+ async function editTool(args) {
1272
+ const { file_path, old_string, new_string, expected_replacements = 1 } = args;
1273
+ if (!path3.isAbsolute(file_path)) {
1274
+ return { success: false, error: `Invalid parameters: file_path must be absolute.`, file_path };
1275
+ }
1276
+ if (file_path.includes("..")) {
1277
+ return { success: false, error: `Invalid parameters: file_path cannot contain '..'.`, file_path };
1278
+ }
1279
+ try {
1280
+ const editData = await calculateEdit(file_path, old_string, new_string, expected_replacements);
1281
+ if (editData.error) {
1282
+ return {
1283
+ success: false,
1284
+ error: `Execution failed: ${editData.error.display}`,
1285
+ details: editData.error.raw,
1286
+ file_path
1287
+ };
1288
+ }
1289
+ await fs2.mkdir(path3.dirname(file_path), { recursive: true });
1290
+ await fs2.writeFile(file_path, editData.newContent, "utf-8");
1291
+ const relativePath = path3.relative(process.cwd(), file_path);
1292
+ const filename = path3.basename(file_path);
1293
+ if (editData.isNewFile) {
1294
+ return {
1295
+ success: true,
1296
+ file_path,
1297
+ description: `Created new file: ${relativePath}`,
1298
+ message: `Created new file: ${file_path} with the provided content.`,
1299
+ is_new_file: true,
1300
+ occurrences: editData.occurrences,
1301
+ relative_path: relativePath
1302
+ };
1303
+ } else {
1304
+ const finalDiff = createDiff(filename, editData.currentContent || "", editData.newContent);
1305
+ return {
1306
+ success: true,
1307
+ file_path,
1308
+ description: `Modified ${relativePath} (${editData.occurrences} replacement(s)).`,
1309
+ message: `Successfully modified file: ${file_path}. Diff of changes:
1310
+ ${finalDiff}`,
1311
+ is_new_file: false,
1312
+ occurrences: editData.occurrences,
1313
+ relative_path: relativePath
1314
+ };
1315
+ }
1316
+ } catch (e) {
1317
+ return {
1318
+ success: false,
1319
+ error: `An unexpected error occurred during the edit operation: ${e.message}`,
1320
+ file_path
1321
+ };
1322
+ }
1323
+ }
1324
+
1325
+ // src/app/agent/tools/natives/message.ts
1326
+ import { v4 as uuidv4 } from "uuid";
1327
+ function messageNotifyDev(args) {
1328
+ const { text_markdown } = args;
1329
+ const notification = {
1330
+ type: "message_notify_dev",
1331
+ id: `notify_${uuidv4()}`,
1332
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1333
+ content: {
1334
+ format: "markdown",
1335
+ body: text_markdown
1336
+ },
1337
+ success: true,
1338
+ delivered: true
1339
+ };
1340
+ return Promise.resolve(notification);
1341
+ }
1342
+
1343
+ // src/app/agent/tools/natives/ls.ts
1344
+ import { promises as fs3 } from "fs";
1345
+ import path4 from "path";
1346
+ var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
1347
+ ".git",
1348
+ ".gitignore",
1349
+ ".venv",
1350
+ "venv",
1351
+ "node_modules",
1352
+ "__pycache__",
1353
+ "*.pyc",
1354
+ ".vscode",
1355
+ ".idea",
1356
+ "dist",
1357
+ "build",
1358
+ "*.log",
1359
+ ".DS_Store"
1360
+ ]);
1361
+ async function ls(args) {
1362
+ const {
1363
+ directory_path = ".",
1364
+ recursive = false,
1365
+ ignore_patterns = [],
1366
+ start_index = 0,
1367
+ end_index,
1368
+ show_hidden = false,
1369
+ file_extensions,
1370
+ max_depth
1371
+ } = args;
1372
+ try {
1373
+ const basePath = path4.resolve(directory_path);
1374
+ if (!(await fs3.stat(basePath)).isDirectory()) {
1375
+ throw new Error(`Directory '${directory_path}' not found.`);
1376
+ }
1377
+ const allIgnorePatterns = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore_patterns]);
1378
+ const normalizedExtensions = file_extensions?.map((ext) => ext.toLowerCase());
1379
+ const allFiles = [];
1380
+ const allDirs = [];
1381
+ const walk = async (currentDir, currentDepth) => {
1382
+ if (max_depth !== void 0 && currentDepth > max_depth) return;
1383
+ const entries = await fs3.readdir(currentDir, { withFileTypes: true });
1384
+ for (const entry of entries) {
1385
+ const entryName = entry.name;
1386
+ const fullPath = path4.join(currentDir, entryName);
1387
+ const posixPath = fullPath.split(path4.sep).join("/");
1388
+ const isHidden = entryName.startsWith(".");
1389
+ if (allIgnorePatterns.has(entryName) || isHidden && !show_hidden) {
1390
+ continue;
1391
+ }
1392
+ if (entry.isDirectory()) {
1393
+ allDirs.push(posixPath);
1394
+ if (recursive) {
1395
+ await walk(fullPath, currentDepth + 1);
1396
+ }
1397
+ } else if (entry.isFile()) {
1398
+ if (!normalizedExtensions || normalizedExtensions.includes(path4.extname(entryName).toLowerCase())) {
1399
+ allFiles.push(posixPath);
1400
+ }
1401
+ }
1402
+ }
1403
+ };
1404
+ await walk(basePath, 0);
1405
+ allFiles.sort();
1406
+ allDirs.sort();
1407
+ return {
1408
+ success: true,
1409
+ path: basePath.split(path4.sep).join("/"),
1410
+ recursive,
1411
+ total_files: allFiles.length,
1412
+ total_directories: allDirs.length,
1413
+ showing_files: `[${start_index}:${Math.min(end_index ?? allFiles.length, allFiles.length)}]`,
1414
+ showing_directories: `[${start_index}:${Math.min(end_index ?? allDirs.length, allDirs.length)}]`,
1415
+ files: allFiles.slice(start_index, end_index),
1416
+ directories: allDirs.slice(start_index, end_index),
1417
+ filters_applied: { ignore_patterns: [...allIgnorePatterns], show_hidden, file_extensions, max_depth }
1418
+ };
1419
+ } catch (e) {
1420
+ return { success: false, error: e.message };
1421
+ }
1422
+ }
1423
+
1424
+ // src/app/agent/tools/natives/readLines.ts
1425
+ import { promises as fs4 } from "fs";
1426
+ async function readLines(args) {
1427
+ const { filepath, start_line, end_line } = args;
1428
+ try {
1429
+ if (!(await fs4.stat(filepath)).isFile()) {
1430
+ throw new Error(`File '${filepath}' not found or is not a file.`);
1431
+ }
1432
+ if (start_line < 1 || end_line < start_line) {
1433
+ throw new Error("Invalid line range. start_line must be >= 1 and end_line must be >= start_line.");
1434
+ }
1435
+ const fileContent = await fs4.readFile(filepath, "utf-8");
1436
+ const lines = fileContent.split("\n");
1437
+ const total_lines = lines.length;
1438
+ const startIndex = start_line - 1;
1439
+ let endIndex = end_line;
1440
+ if (startIndex >= total_lines) {
1441
+ throw new Error(`start_line (${start_line}) exceeds file length (${total_lines} lines).`);
1442
+ }
1443
+ endIndex = Math.min(endIndex, total_lines);
1444
+ const contentLines = lines.slice(startIndex, endIndex);
1445
+ const content = contentLines.join("\n");
1446
+ return {
1447
+ success: true,
1448
+ filepath,
1449
+ content,
1450
+ lines_read: contentLines.length,
1451
+ start_line,
1452
+ end_line: endIndex,
1453
+ // Retorna o final real usado
1454
+ total_file_lines: total_lines
1455
+ };
1456
+ } catch (e) {
1457
+ return { success: false, error: e.message };
1458
+ }
1459
+ }
1460
+
1461
+ // src/app/agent/tools/natives/count_lines.ts
1462
+ import { createReadStream } from "fs";
1463
+ import { promises as fs5 } from "fs";
1464
+ import readline from "readline";
1465
+ async function countLines(args) {
1466
+ const { filepath } = args;
1467
+ try {
1468
+ if (!(await fs5.stat(filepath)).isFile()) {
1469
+ throw new Error(`File '${filepath}' not found or is not a file.`);
1470
+ }
1471
+ const fileStream = createReadStream(filepath);
1472
+ const rl = readline.createInterface({
1473
+ input: fileStream,
1474
+ crlfDelay: Infinity
1475
+ });
1476
+ let lineCount = 0;
1477
+ for await (const line of rl) {
1478
+ lineCount++;
1479
+ }
1480
+ return { success: true, filepath, line_count: lineCount };
1481
+ } catch (e) {
1482
+ return { success: false, error: e.message };
1483
+ }
1484
+ }
1485
+
1486
+ // src/app/agent/tool_invoker.ts
1487
+ var ToolInvoker = class {
1488
+ // Mapa privado para associar nomes de ferramentas às suas funções de implementação.
1489
+ toolImplementations;
1490
+ // Propriedade privada para armazenar as definições de ferramentas carregadas do JSON.
1491
+ toolDefinitions = [];
1492
+ constructor() {
1493
+ this.toolImplementations = /* @__PURE__ */ new Map();
1494
+ this.registerTools();
1495
+ }
1496
+ /**
1497
+ * Carrega as definições de ferramentas do arquivo de configuração `native_tools.json`.
1498
+ * Este método é assíncrono e deve ser chamado após a criação da instância.
1499
+ */
1500
+ async initialize() {
1501
+ try {
1502
+ const __filename = fileURLToPath(import.meta.url);
1503
+ const __dirname = path5.dirname(__filename);
1504
+ const configPath = path5.resolve(__dirname, "config", "native_tools.json");
1505
+ const fileContent = await fs6.readFile(configPath, "utf-8");
1506
+ const config2 = JSON.parse(fileContent);
1507
+ this.toolDefinitions = config2.nativeTools;
1508
+ } catch (error) {
1509
+ console.error("[ToolInvoker] Erro cr\xEDtico ao carregar 'native_tools.json'. As ferramentas nativas n\xE3o estar\xE3o dispon\xEDveis.", error);
1510
+ this.toolDefinitions = [];
1511
+ }
1512
+ }
1513
+ /**
1514
+ * Registra as implementações de todas as ferramentas nativas.
1515
+ * Este método mapeia o nome da ferramenta (string) para a função TypeScript que a executa.
1516
+ */
1517
+ registerTools() {
1518
+ this.toolImplementations.set("shell_command", shellCommand);
1519
+ this.toolImplementations.set("edit_tool", editTool);
1520
+ this.toolImplementations.set("message_notify_dev", messageNotifyDev);
1521
+ this.toolImplementations.set("ls_tool", ls);
1522
+ this.toolImplementations.set("count_file_lines", countLines);
1523
+ this.toolImplementations.set("read_file_lines", readLines);
1524
+ this.toolImplementations.set("agent_end_task", async () => ({ success: true, message: "Task ended by agent." }));
1525
+ }
1526
+ /**
1527
+ * Retorna a lista de definições de todas as ferramentas nativas carregadas.
1528
+ * O MCPClient usará esta função para obter a lista de ferramentas locais.
1529
+ */
1530
+ getToolDefinitions() {
1531
+ return this.toolDefinitions;
1532
+ }
1533
+ /**
1534
+ * Invoca uma ferramenta nativa pelo nome com os argumentos fornecidos.
1535
+ * @param toolName O nome da ferramenta a ser invocada.
1536
+ * @param args Os argumentos para a ferramenta, geralmente um objeto.
1537
+ * @returns O resultado da execução da ferramenta.
1538
+ */
1539
+ async invoke(toolName, args) {
1540
+ const implementation = this.toolImplementations.get(toolName);
1541
+ if (!implementation) {
1542
+ return { error: `Error: Native tool "${toolName}" not found.` };
1543
+ }
1544
+ try {
1545
+ return await implementation(args);
1546
+ } catch (error) {
1547
+ const errorMessage = error instanceof Error ? error.message : "An unknown error occurred.";
1548
+ return { error: `Error executing tool "${toolName}": ${errorMessage}` };
1549
+ }
1550
+ }
1551
+ };
1552
+
1553
+ // src/app/agent/tools/mcp/mcp_client.ts
1554
+ import { promises as fs7 } from "fs";
1555
+ import path6 from "path";
1556
+ import os4 from "os";
1557
+ import { fileURLToPath as fileURLToPath2 } from "url";
1558
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1559
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1560
+ var MCPClient = class {
1561
+ sessions = /* @__PURE__ */ new Map();
1562
+ toolToServerMap = /* @__PURE__ */ new Map();
1563
+ globalToolsForLlm = [];
1564
+ nativeToolInvoker;
1565
+ eventBus;
1566
+ // <<< ADICIONA A PROPRIEDADE
1567
+ constructor(nativeToolInvoker, eventBus2) {
1568
+ this.nativeToolInvoker = nativeToolInvoker;
1569
+ this.eventBus = eventBus2;
1570
+ }
1571
+ // ... (método initialize inalterado) ...
1572
+ async initialize() {
1573
+ const nativeTools = this.nativeToolInvoker.getToolDefinitions();
1574
+ this.globalToolsForLlm.push(...nativeTools);
1575
+ for (const tool of nativeTools) {
1576
+ const toolName = tool.function.name;
1577
+ this.toolToServerMap.set(toolName, {
1578
+ server: "native",
1579
+ originalName: toolName
1580
+ });
1581
+ }
1582
+ const __filename = fileURLToPath2(import.meta.url);
1583
+ const __dirname = path6.dirname(__filename);
1584
+ const defaultConfigPath = path6.resolve(__dirname, "config", "bluma-mcp.json");
1585
+ const userConfigPath = path6.join(os4.homedir(), ".bluma-cli", "bluma-mcp.json");
1586
+ const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
1587
+ const userConfig = await this.loadMcpConfig(userConfigPath, "User");
1588
+ const mergedConfig = {
1589
+ mcpServers: {
1590
+ ...defaultConfig.mcpServers || {},
1591
+ ...userConfig.mcpServers || {}
1592
+ }
1593
+ };
1594
+ if (Object.keys(mergedConfig.mcpServers).length === 0) {
1595
+ return;
1596
+ }
1597
+ const serverEntries = Object.entries(mergedConfig.mcpServers);
1598
+ for (const [serverName, serverConf] of serverEntries) {
1599
+ try {
1600
+ this.eventBus.emit("backend_message", {
1601
+ type: "connection_status",
1602
+ message: `Connecting to MCP server: ${serverName}...`
1603
+ });
1604
+ if (serverConf.type === "stdio") {
1605
+ await this.connectToStdioServer(serverName, serverConf);
1606
+ } else if (serverConf.type === "sse") {
1607
+ console.warn(`[MCPClient] Conex\xE3o com servidores SSE (como '${serverName}') ainda n\xE3o implementada.`);
1608
+ }
1609
+ } catch (error) {
1610
+ this.eventBus.emit("backend_message", {
1611
+ type: "error",
1612
+ message: `Failed to connect to server '${serverName}'.`
1613
+ });
1614
+ }
1615
+ }
1616
+ }
1617
+ async loadMcpConfig(configPath, configType) {
1618
+ try {
1619
+ const fileContent = await fs7.readFile(configPath, "utf-8");
1620
+ const processedContent = this.replaceEnvPlaceholders(fileContent);
1621
+ return JSON.parse(processedContent);
1622
+ } catch (error) {
1623
+ if (error.code === "ENOENT") {
1624
+ if (configType === "User") {
1625
+ }
1626
+ } else {
1627
+ console.warn(`[MCPClient] Warning: Error reading ${configType} config file ${configPath}.`, error);
1628
+ }
1629
+ return {};
1630
+ }
1631
+ }
1632
+ /**
1633
+ * Conecta-se a um servidor MCP baseado em Stdio, adaptando o comando para o SO atual.
1634
+ */
1635
+ async connectToStdioServer(serverName, config2) {
1636
+ let commandToExecute = config2.command;
1637
+ let argsToExecute = config2.args || [];
1638
+ const isWindows = os4.platform() === "win32";
1639
+ if (!isWindows && commandToExecute.toLowerCase() === "cmd") {
1640
+ if (argsToExecute.length >= 2 && argsToExecute[0].toLowerCase() === "/c") {
1641
+ commandToExecute = argsToExecute[1];
1642
+ argsToExecute = argsToExecute.slice(2);
1643
+ } else {
1644
+ console.warn(`[MCPClient] Formato de comando 'cmd /c' inesperado para '${serverName}' em sistema n\xE3o-Windows. O servidor ser\xE1 ignorado.`);
1645
+ return;
1646
+ }
1647
+ }
1648
+ const transport = new StdioClientTransport({
1649
+ command: commandToExecute,
1650
+ // Usa o comando adaptado
1651
+ args: argsToExecute,
1652
+ // Usa os argumentos adaptados
1653
+ env: config2.env
1654
+ });
1655
+ const mcp = new Client({ name: `bluma-cli-client-for-${serverName}`, version: "1.0.0" });
1656
+ await mcp.connect(transport);
1657
+ this.sessions.set(serverName, mcp);
1658
+ const toolsResult = await mcp.listTools();
1659
+ for (const tool of toolsResult.tools) {
1660
+ const prefixedToolName = `${serverName}_${tool.name}`;
1661
+ this.globalToolsForLlm.push({
1662
+ type: "function",
1663
+ function: {
1664
+ name: prefixedToolName,
1665
+ description: tool.description || "",
1666
+ parameters: tool.inputSchema
1667
+ }
1668
+ });
1669
+ this.toolToServerMap.set(prefixedToolName, {
1670
+ server: serverName,
1671
+ originalName: tool.name
1672
+ });
1673
+ }
1674
+ }
1675
+ async invoke(toolName, args) {
1676
+ const route = this.toolToServerMap.get(toolName);
1677
+ if (!route) {
1678
+ return { error: `Ferramenta '${toolName}' n\xE3o encontrada ou registrada.` };
1679
+ }
1680
+ if (route.server === "native") {
1681
+ return this.nativeToolInvoker.invoke(route.originalName, args);
1682
+ } else {
1683
+ const session = this.sessions.get(route.server);
1684
+ if (!session) {
1685
+ return { error: `Sess\xE3o para o servidor '${route.server}' n\xE3o encontrada.` };
1686
+ }
1687
+ const result = await session.callTool({ name: route.originalName, arguments: args });
1688
+ return result.content;
1689
+ }
1690
+ }
1691
+ getAvailableTools() {
1692
+ return this.globalToolsForLlm;
1693
+ }
1694
+ async close() {
1695
+ for (const [name, session] of this.sessions.entries()) {
1696
+ try {
1697
+ await session.close();
1698
+ } catch (error) {
1699
+ console.error(`[MCPClient] Erro ao encerrar conex\xE3o com '${name}':`, error);
1700
+ }
1701
+ }
1702
+ }
1703
+ replaceEnvPlaceholders(content) {
1704
+ return content.replace(/\$\{([A-Za-z0-9_]+)\}/g, (match, varName) => {
1705
+ return process.env[varName] || match;
1706
+ });
1707
+ }
1708
+ };
1709
+
1710
+ // src/app/agent/feedback/feedback_system.ts
1711
+ var AdvancedFeedbackSystem = class {
1712
+ cumulativeScore = 0;
1713
+ /**
1714
+ * Gera feedback com base em um evento ocorrido.
1715
+ * @param event O evento a ser avaliado.
1716
+ * @returns Um objeto com a pontuação, mensagem e correção.
1717
+ */
1718
+ generateFeedback(event) {
1719
+ if (event.event === "protocol_violation_direct_text") {
1720
+ const penalty = -2.5;
1721
+ this.cumulativeScore += penalty;
1722
+ return {
1723
+ score: penalty,
1724
+ message: "Direct text response is a protocol violation. All communication must be done via the 'message_notify_dev' tool.",
1725
+ correction: `
1726
+ ## PROTOCOL VIOLATION \u2014 SEVERE
1727
+ You sent a direct text response, which is strictly prohibited.
1728
+ PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
1729
+ You MUST use tools for all actions and communication.
1730
+ `.trim()
1731
+ };
1732
+ }
1733
+ return { score: 0, message: "No feedback for this event.", correction: "" };
1734
+ }
1735
+ getCumulativeScore() {
1736
+ return this.cumulativeScore;
1737
+ }
1738
+ };
1739
+
1740
+ // src/app/agent/core/context-api/context_manager.ts
1741
+ function createApiContextWindow(fullHistory, maxTurns) {
1742
+ if (!fullHistory.length) {
1743
+ return [];
1744
+ }
1745
+ if (maxTurns === null || maxTurns === void 0) {
1746
+ return [...fullHistory];
1747
+ }
1748
+ const systemMessages = [];
1749
+ let historyStartIndex = 0;
1750
+ while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
1751
+ systemMessages.push(fullHistory[historyStartIndex]);
1752
+ historyStartIndex++;
1753
+ }
1754
+ const conversationHistory = fullHistory.slice(historyStartIndex);
1755
+ const turns = [];
1756
+ let currentTurn = [];
1757
+ let turnsFound = 0;
1758
+ for (let i = conversationHistory.length - 1; i >= 0; i--) {
1759
+ const msg = conversationHistory[i];
1760
+ currentTurn.unshift(msg);
1761
+ if (msg.role === "assistant" && // CORREÇÃO: Adicionamos o tipo explícito para 'tc' para resolver o erro do TypeScript.
1762
+ msg.tool_calls?.some((tc) => tc.function.name === "agent_end_task")) {
1763
+ turns.unshift([...currentTurn]);
1764
+ currentTurn = [];
1765
+ turnsFound++;
1766
+ if (turnsFound >= maxTurns) {
1767
+ break;
1768
+ }
1769
+ }
1770
+ }
1771
+ if (currentTurn.length > 0) {
1772
+ turns.unshift(currentTurn);
1773
+ }
1774
+ const finalContext = systemMessages.concat(turns.flat());
1775
+ return finalContext;
1776
+ }
1777
+
1778
+ // src/app/agent/agent.ts
1779
+ var globalEnvPath = path7.join(os5.homedir(), ".bluma-cli", ".env");
1780
+ dotenv.config({ path: globalEnvPath });
1781
+ var Agent = class {
1782
+ client;
1783
+ deploymentName;
1784
+ sessionId;
1785
+ sessionFile = "";
1786
+ history = [];
1787
+ eventBus;
1788
+ mcpClient;
1789
+ feedbackSystem;
1790
+ isInitialized = false;
1791
+ maxContextTurns = 300;
1792
+ isInterrupted = false;
1793
+ // <-- NOVO: Flag de interrupção
1794
+ constructor(sessionId2, eventBus2) {
1795
+ this.sessionId = sessionId2;
1796
+ this.eventBus = eventBus2;
1797
+ this.eventBus.on("user_interrupt", () => {
1798
+ this.isInterrupted = true;
1799
+ });
1800
+ const nativeToolInvoker = new ToolInvoker();
1801
+ this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
1802
+ this.feedbackSystem = new AdvancedFeedbackSystem();
1803
+ const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
1804
+ const apiKey = process.env.AZURE_OPENAI_API_KEY;
1805
+ const apiVersion = process.env.AZURE_OPENAI_API_VERSION;
1806
+ this.deploymentName = process.env.AZURE_OPENAI_DEPLOYMENT || "";
1807
+ if (!endpoint || !apiKey || !apiVersion || !this.deploymentName) {
1808
+ const errorMessage = `Uma ou mais vari\xE1veis de ambiente Azure OpenAI n\xE3o foram encontradas. Verifique em: ${globalEnvPath} ou nas vari\xE1veis de sistema.`;
1809
+ throw new Error(errorMessage);
1810
+ }
1811
+ this.client = new OpenAI({
1812
+ // Configuração do cliente OpenAI hospedado no Azure
1813
+ apiKey,
1814
+ baseURL: `${endpoint}/openai/deployments/${this.deploymentName}`,
1815
+ defaultQuery: { "api-version": apiVersion },
1816
+ defaultHeaders: { "api-key": apiKey }
1817
+ });
1818
+ }
1819
+ /**
1820
+ * Inicializa o agente, carregando ou criando uma sessão e preparando o histórico.
1821
+ * Também inicializa o MCPClient e o ToolInvoker.
1822
+ */
1823
+ async initialize() {
1824
+ await this.mcpClient.nativeToolInvoker.initialize();
1825
+ await this.mcpClient.initialize();
1826
+ const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
1827
+ this.sessionFile = sessionFile;
1828
+ this.history = history;
1829
+ if (this.history.length === 0) {
1830
+ let systemPrompt = getUnifiedSystemPrompt();
1831
+ systemPrompt += `
1832
+ BEHAVIORAL REQUIREMENTS:
1833
+ - You MUST use the 'message_notify_dev' tool for ALL communication with the user.
1834
+ - Direct text responses are a protocol violation and will be penalized.
1835
+ - Signal the end of a task using the 'agent_end_task' tool.
1836
+ - Never make parallel tool calls.
1837
+ - Do not include any of the following in tool names:
1838
+ - Special characters
1839
+ - Extra spaces
1840
+ Always use clean, unmodified, and simple names for tools.
1841
+ Tool names must follow strict formatting: no symbols, no whitespace, no alterations.
1842
+ - Follow the stripes o "Tool Naming Policy"
1843
+ - Never modify the names of the tools, use their real names without any modification.
1844
+ - Never forget to signal the system when the task is completed 'agent_end_task' tool.
1845
+ `;
1846
+ this.history.push({ role: "system", content: systemPrompt });
1847
+ await saveSessionHistory(this.sessionFile, this.history);
1848
+ }
1849
+ }
1850
+ getAvailableTools() {
1851
+ return this.mcpClient.getAvailableTools();
1852
+ }
1853
+ async processTurn(userInput) {
1854
+ this.isInterrupted = false;
1855
+ this.history.push({ role: "user", content: userInput.content });
1856
+ await this._continueConversation();
1857
+ }
1858
+ /**
1859
+ * Lida com a decisão do usuário (aceitar/recusar) sobre uma chamada de ferramenta.
1860
+ * Garante que uma mensagem de 'role: tool' seja sempre adicionada ao histórico.
1861
+ */
1862
+ async handleToolResponse(decisionData) {
1863
+ const toolCall = decisionData.tool_calls[0];
1864
+ let toolResultContent;
1865
+ let shouldContinueConversation = true;
1866
+ if (decisionData.type === "user_decision_execute") {
1867
+ const toolName = toolCall.function.name;
1868
+ const toolArgs = JSON.parse(toolCall.function.arguments);
1869
+ let previewContent;
1870
+ if (toolName === "edit_tool") {
1871
+ previewContent = await this._generateEditPreview(toolArgs);
1872
+ }
1873
+ this.eventBus.emit("backend_message", {
1874
+ type: "tool_call",
1875
+ tool_name: toolName,
1876
+ arguments: toolArgs,
1877
+ preview: previewContent
1878
+ });
1879
+ try {
1880
+ if (this.isInterrupted) {
1881
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
1882
+ return;
1883
+ }
1884
+ const result = await this.mcpClient.invoke(toolName, toolArgs);
1885
+ let finalResult = result;
1886
+ if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
1887
+ finalResult = result[0].text;
1888
+ }
1889
+ toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
1890
+ } catch (error) {
1891
+ toolResultContent = JSON.stringify({
1892
+ error: `Tool execution failed: ${error.message}`,
1893
+ details: error.data || "No additional details."
1894
+ });
1895
+ }
1896
+ this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
1897
+ if (toolName.includes("agent_end_task")) {
1898
+ shouldContinueConversation = false;
1899
+ this.eventBus.emit("backend_message", { type: "done", status: "completed" });
1900
+ }
1901
+ } else {
1902
+ toolResultContent = "The user declined to execute this tool...";
1903
+ }
1904
+ this.history.push({
1905
+ role: "tool",
1906
+ tool_call_id: toolCall.id,
1907
+ content: toolResultContent
1908
+ });
1909
+ await saveSessionHistory(this.sessionFile, this.history);
1910
+ if (shouldContinueConversation && !this.isInterrupted) {
1911
+ await this._continueConversation();
1912
+ }
1913
+ }
1914
+ /**
1915
+ * Método central que chama a API do LLM e processa a resposta,
1916
+ * com lógica de feedback e auto-aprovação de ferramentas.
1917
+ */
1918
+ // Adicione este método dentro da classe Agent
1919
+ async _generateEditPreview(toolArgs) {
1920
+ try {
1921
+ const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
1922
+ if (editData.error) {
1923
+ return `Failed to generate diff:
1924
+
1925
+ ${editData.error.display}`;
1926
+ }
1927
+ const filename = path7.basename(toolArgs.file_path);
1928
+ return createDiff(filename, editData.currentContent || "", editData.newContent);
1929
+ } catch (e) {
1930
+ return `An unexpected error occurred while generating the edit preview: ${e.message}`;
1931
+ }
1932
+ }
1933
+ async _continueConversation() {
1934
+ try {
1935
+ if (this.isInterrupted) {
1936
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
1937
+ return;
1938
+ }
1939
+ const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
1940
+ const response = await this.client.chat.completions.create({
1941
+ model: this.deploymentName,
1942
+ messages: contextWindow,
1943
+ tools: this.mcpClient.getAvailableTools(),
1944
+ tool_choice: "auto",
1945
+ parallel_tool_calls: false
1946
+ });
1947
+ if (this.isInterrupted) {
1948
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
1949
+ return;
1950
+ }
1951
+ const message = response.choices[0].message;
1952
+ this.history.push(message);
1953
+ if (message.tool_calls) {
1954
+ const autoApprovedTools = [
1955
+ "agent_end_task",
1956
+ "message_notify_dev",
1957
+ "bluma_nootebook"
1958
+ ];
1959
+ const toolToCall = message.tool_calls[0];
1960
+ const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
1961
+ if (isSafeTool) {
1962
+ await this.handleToolResponse({ type: "user_decision_execute", tool_calls: message.tool_calls });
1963
+ } else {
1964
+ const toolName = toolToCall.function.name;
1965
+ if (toolName === "edit_tool") {
1966
+ const args = JSON.parse(toolToCall.function.arguments);
1967
+ const previewContent = await this._generateEditPreview(args);
1968
+ this.eventBus.emit("backend_message", {
1969
+ type: "confirmation_request",
1970
+ tool_calls: message.tool_calls,
1971
+ preview: previewContent
1972
+ });
1973
+ } else {
1974
+ this.eventBus.emit("backend_message", {
1975
+ type: "confirmation_request",
1976
+ tool_calls: message.tool_calls
1977
+ });
1978
+ }
1979
+ }
1980
+ } else if (message.content) {
1981
+ this.eventBus.emit("backend_message", {
1982
+ type: "assistant_message",
1983
+ content: message.content
1984
+ });
1985
+ const feedback = this.feedbackSystem.generateFeedback({
1986
+ event: "protocol_violation_direct_text",
1987
+ details: { violationContent: message.content }
1988
+ });
1989
+ this.eventBus.emit("backend_message", {
1990
+ type: "protocol_violation",
1991
+ message: feedback.message,
1992
+ content: message.content
1993
+ });
1994
+ this.history.push({
1995
+ role: "system",
1996
+ content: feedback.correction
1997
+ });
1998
+ await this._continueConversation();
1999
+ } else {
2000
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent is thinking... continuing reasoning cycle." });
2001
+ await this._continueConversation();
2002
+ }
2003
+ } catch (error) {
2004
+ const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
2005
+ this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
2006
+ } finally {
2007
+ await saveSessionHistory(this.sessionFile, this.history);
2008
+ }
2009
+ }
2010
+ };
2011
+
2012
+ // src/app/ui/WorkingTimer.tsx
2013
+ import { useState as useState3, useEffect as useEffect2 } from "react";
2014
+ import { Box as Box7, Text as Text7 } from "ink";
2015
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2016
+ var WorkingTimer = () => {
2017
+ const [seconds, setSeconds] = useState3(0);
2018
+ const [dotIndex, setDotIndex] = useState3(1);
2019
+ useEffect2(() => {
2020
+ const secondsTimer = setInterval(() => {
2021
+ setSeconds((prev) => prev + 1);
2022
+ }, 1e3);
2023
+ return () => clearInterval(secondsTimer);
2024
+ }, []);
2025
+ useEffect2(() => {
2026
+ const dotsTimer = setInterval(() => {
2027
+ setDotIndex((prev) => prev % 3 + 1);
2028
+ }, 100);
2029
+ return () => clearInterval(dotsTimer);
2030
+ }, []);
2031
+ const dots = ".".repeat(dotIndex).padEnd(3, " ");
2032
+ return /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: "magenta", children: [
2033
+ `working${dots}`,
2034
+ ` ${seconds}s`
2035
+ ] }) });
2036
+ };
2037
+
2038
+ // src/app/ui/components/ToolCallDisplay.tsx
2039
+ import { memo as memo2 } from "react";
2040
+ import { Box as Box9 } from "ink";
2041
+
2042
+ // src/app/ui/components/toolCallRenderers.tsx
2043
+ import { Box as Box8, Text as Text8 } from "ink";
2044
+ import path8 from "path";
2045
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2046
+ var formatArgumentsForDisplay = (args) => {
2047
+ if (typeof args === "string") {
2048
+ try {
2049
+ return JSON.stringify(JSON.parse(args), null, 2);
2050
+ } catch (e) {
2051
+ return args;
2052
+ }
2053
+ }
2054
+ return JSON.stringify(args, null, 2);
2055
+ };
2056
+ var renderShellCommand2 = ({ args }) => {
2057
+ const command = args.command || "[command not found]";
2058
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2059
+ /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2060
+ /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
2061
+ "Shell Command"
2062
+ ] }) }),
2063
+ /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, paddingX: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2064
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
2065
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: command })
2066
+ ] }) })
2067
+ ] });
2068
+ };
2069
+ var renderLsTool2 = ({ args }) => {
2070
+ let directoryPath = "[path not found]";
2071
+ try {
2072
+ const parsedArgs = typeof args === "string" ? JSON.parse(args) : args;
2073
+ directoryPath = parsedArgs.directory_path || "[path not specified]";
2074
+ } catch (e) {
2075
+ directoryPath = "Error parsing arguments";
2076
+ }
2077
+ const finalDirectoryName = path8.basename(directoryPath);
2078
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2079
+ /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2080
+ /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
2081
+ "ls Tool"
2082
+ ] }) }),
2083
+ /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, paddingX: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2084
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
2085
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: finalDirectoryName })
2086
+ ] }) })
2087
+ ] });
2088
+ };
2089
+ var renderCountFilesLines = ({ args }) => {
2090
+ let directoryPath = "[path not found]";
2091
+ try {
2092
+ const parsedArgs = typeof args === "string" ? JSON.parse(args) : args;
2093
+ directoryPath = parsedArgs.filepath || "[path not specified]";
2094
+ } catch (e) {
2095
+ directoryPath = "Error parsing arguments";
2096
+ }
2097
+ const finalDirectoryName = path8.basename(directoryPath);
2098
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2099
+ /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2100
+ /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
2101
+ "Count File Lines"
2102
+ ] }) }),
2103
+ /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, paddingX: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2104
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
2105
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: finalDirectoryName })
2106
+ ] }) })
2107
+ ] });
2108
+ };
2109
+ var renderReadFileLines2 = ({ args }) => {
2110
+ let filepath = "[path not found]";
2111
+ let startLine = 0;
2112
+ let endLine = 0;
2113
+ try {
2114
+ const parsedArgs = typeof args === "string" ? JSON.parse(args) : args;
2115
+ filepath = parsedArgs.filepath || "[path not specified]";
2116
+ startLine = parsedArgs.start_line || 0;
2117
+ endLine = parsedArgs.end_line || 0;
2118
+ } catch (e) {
2119
+ filepath = "Error parsing arguments";
2120
+ }
2121
+ const finalFileName = path8.basename(filepath);
2122
+ return (
2123
+ // A caixa externa com a borda, seguindo o template
2124
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2125
+ /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2126
+ /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
2127
+ "Read File Lines Tool"
2128
+ ] }) }),
2129
+ /* @__PURE__ */ jsxs8(Box8, { marginLeft: 2, flexDirection: "column", children: [
2130
+ /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, paddingX: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2131
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
2132
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: finalFileName })
2133
+ ] }) }),
2134
+ /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, paddingX: 4, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2135
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
2136
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "lines " }),
2137
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: startLine }),
2138
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " to " }),
2139
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: endLine })
2140
+ ] }) })
2141
+ ] })
2142
+ ] })
2143
+ );
2144
+ };
2145
+ var renderBlumaNotebook = ({ args }) => {
2146
+ try {
2147
+ let dataToParse = args;
2148
+ if (args && typeof args === "object") {
2149
+ if (args.content) dataToParse = args.content;
2150
+ else if (args.data) dataToParse = args.data;
2151
+ }
2152
+ const thinkingData = typeof dataToParse === "string" ? JSON.parse(dataToParse) : dataToParse;
2153
+ if (!thinkingData || typeof thinkingData.thought !== "string") {
2154
+ throw new Error("Invalid or missing 'thought' property.");
2155
+ }
2156
+ return (
2157
+ // Usamos a mesma estrutura de caixa com borda
2158
+ /* @__PURE__ */ jsxs8(Box8, { borderStyle: "round", borderColor: "green", flexDirection: "column", paddingX: 1, children: [
2159
+ /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2160
+ /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u{1F9E0} " }),
2161
+ "Thinking Process"
2162
+ ] }) }),
2163
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2164
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Thought:" }),
2165
+ /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { color: "gray", children: thinkingData.thought }) })
2166
+ ] }),
2167
+ thinkingData.remaining_tasks && thinkingData.remaining_tasks.length > 0 && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2168
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Remaining Tasks:" }),
2169
+ thinkingData.remaining_tasks.map((task, index) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2170
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
2171
+ /* @__PURE__ */ jsx8(Text8, { color: task.startsWith("\u{1F5F8}") ? "green" : "yellow", children: task })
2172
+ ] }) }, index))
2173
+ ] })
2174
+ ] })
2175
+ );
2176
+ } catch (e) {
2177
+ return /* @__PURE__ */ jsxs8(Box8, { borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
2178
+ /* @__PURE__ */ jsx8(Text8, { color: "blue", bold: true, children: "Thinking (Error)" }),
2179
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: JSON.stringify(args, null, 2) })
2180
+ ] });
2181
+ }
2182
+ };
2183
+ var renderEditToolCall = ({ args, preview }) => {
2184
+ let filepath = "[path not specified]";
2185
+ try {
2186
+ const parsedArgs = typeof args === "string" ? JSON.parse(args) : args;
2187
+ filepath = parsedArgs.file_path || "[path not specified]";
2188
+ } catch (e) {
2189
+ filepath = "Error parsing arguments";
2190
+ }
2191
+ const finalFileName = path8.basename(filepath);
2192
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
2193
+ /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2194
+ /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
2195
+ "Edit File"
2196
+ ] }) }),
2197
+ /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, paddingX: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2198
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
2199
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: finalFileName })
2200
+ ] }) }),
2201
+ preview && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(SimpleDiff, { text: preview, maxHeight: Infinity }) })
2202
+ ] });
2203
+ };
2204
+ var renderGenericToolCall = ({ toolName, args }) => {
2205
+ const formattedArgs = formatArgumentsForDisplay(args);
2206
+ return (
2207
+ // A "moldura" padrão de sucesso com a borda cinza
2208
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
2209
+ /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2210
+ /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
2211
+ toolName
2212
+ ] }) }),
2213
+ formattedArgs && formattedArgs !== "{}" && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2214
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Arguments passed:" }),
2215
+ /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, flexDirection: "column", children: formattedArgs.split("\n").map((line, index) => /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
2216
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u21B3 " }),
2217
+ line
2218
+ ] }, index)) })
2219
+ ] })
2220
+ ] })
2221
+ );
2222
+ };
2223
+ var ToolRenderDisplay = {
2224
+ "shell_command": renderShellCommand2,
2225
+ "ls_tool": renderLsTool2,
2226
+ "bluma_nootebook": renderBlumaNotebook,
2227
+ "count_file_lines": renderCountFilesLines,
2228
+ "read_file_lines": renderReadFileLines2,
2229
+ "edit_tool": renderEditToolCall
2230
+ };
2231
+
2232
+ // src/app/ui/components/ToolCallDisplay.tsx
2233
+ import { jsx as jsx9 } from "react/jsx-runtime";
2234
+ var ToolCallDisplayComponent = ({ toolName, args, preview }) => {
2235
+ if (toolName.includes("message_notify_dev") || toolName.includes("agent_end_task")) {
2236
+ return null;
2237
+ }
2238
+ const Renderer = ToolRenderDisplay[toolName] || renderGenericToolCall;
2239
+ return /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Renderer, { toolName, args, preview }) });
2240
+ };
2241
+ var ToolCallDisplay = memo2(ToolCallDisplayComponent);
2242
+
2243
+ // src/app/ui/components/ToolResultDisplay.tsx
2244
+ import { memo as memo3 } from "react";
2245
+ import { Box as Box10, Text as Text9 } from "ink";
2246
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2247
+ var ToolResultDisplayComponent = ({ toolName, result }) => {
2248
+ const MAX_LINES = 3;
2249
+ if (toolName.includes("agent_end_task") || toolName.includes("bluma_nootebook") || toolName.includes("shell_command") || toolName.includes("ls_tool") || toolName.includes("count_file_lines") || toolName.includes("read_file_lines") || toolName.includes("edit_tool")) {
2250
+ return null;
2251
+ }
2252
+ if (toolName.includes("message_notify_dev")) {
2253
+ try {
2254
+ const parsed = JSON.parse(result);
2255
+ if (parsed.content && parsed.content.body) {
2256
+ const bodyText = parsed.content.body.trim();
2257
+ return /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: bodyText }) });
2258
+ }
2259
+ } catch (e) {
2260
+ }
2261
+ }
2262
+ let formattedResult = result;
2263
+ try {
2264
+ const parsedJson = JSON.parse(result);
2265
+ formattedResult = JSON.stringify(parsedJson, null, 2);
2266
+ } catch (e) {
2267
+ formattedResult = result;
2268
+ }
2269
+ const lines = formattedResult.split("\n");
2270
+ const isTruncated = lines.length > MAX_LINES;
2271
+ const visibleLines = isTruncated ? lines.slice(0, MAX_LINES) : lines;
2272
+ const remainingCount = lines.length - MAX_LINES;
2273
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginBottom: 1, children: [
2274
+ visibleLines.map((line, idx) => /* @__PURE__ */ jsx10(Text9, { color: "gray", children: line }, idx)),
2275
+ isTruncated && /* @__PURE__ */ jsxs9(Text9, { color: "gray", children: [
2276
+ "...(",
2277
+ remainingCount,
2278
+ " more lines)"
2279
+ ] })
2280
+ ] });
2281
+ };
2282
+ var ToolResultDisplay = memo3(ToolResultDisplayComponent);
2283
+
2284
+ // src/app/ui/App.tsx
2285
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2286
+ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2287
+ const agentInstance = useRef(null);
2288
+ const [history, setHistory] = useState4([]);
2289
+ const [statusMessage, setStatusMessage] = useState4(
2290
+ "Initializing agent..."
2291
+ );
2292
+ const [toolsCount, setToolsCount] = useState4(null);
2293
+ const [mcpStatus, setMcpStatus] = useState4(
2294
+ "connecting"
2295
+ );
2296
+ const [isProcessing, setIsProcessing] = useState4(true);
2297
+ const [pendingConfirmation, setPendingConfirmation] = useState4(
2298
+ null
2299
+ );
2300
+ const [confirmationPreview, setConfirmationPreview] = useState4(null);
2301
+ const alwaysAcceptList = useRef([]);
2302
+ const workdir = process.cwd();
2303
+ const handleInterrupt = useCallback(() => {
2304
+ if (!isProcessing) return;
2305
+ eventBus2.emit("user_interrupt");
2306
+ setIsProcessing(false);
2307
+ setHistory((prev) => [
2308
+ ...prev,
2309
+ {
2310
+ id: prev.length,
2311
+ component: /* @__PURE__ */ jsx11(Text10, { color: "yellow", children: "-- Task cancelled by dev. --" })
2312
+ }
2313
+ ]);
2314
+ }, [isProcessing, eventBus2]);
2315
+ const handleSubmit = useCallback(
2316
+ (text) => {
2317
+ if (!text || isProcessing || !agentInstance.current) return;
2318
+ setIsProcessing(true);
2319
+ const displayText = text.length > 1e4 ? text.substring(0, 1e4) + "..." : text;
2320
+ setHistory((prev) => [
2321
+ ...prev,
2322
+ {
2323
+ id: prev.length,
2324
+ component: (
2325
+ // Uma única Box para o espaçamento
2326
+ /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsxs10(Text10, { color: "white", dimColor: true, children: [
2327
+ /* @__PURE__ */ jsxs10(Text10, { color: "white", children: [
2328
+ ">",
2329
+ " "
2330
+ ] }),
2331
+ displayText
2332
+ ] }) })
2333
+ )
2334
+ }
2335
+ ]);
2336
+ agentInstance.current.processTurn({ content: text });
2337
+ },
2338
+ [isProcessing]
2339
+ );
2340
+ const handleConfirmation = useCallback(
2341
+ (decision, toolCalls) => {
2342
+ if (!agentInstance.current) return;
2343
+ setPendingConfirmation(null);
2344
+ setIsProcessing(true);
2345
+ let finalDecision = decision;
2346
+ if (decision === "accept_always") {
2347
+ const toolNameToWhitelist = toolCalls[0].function.name;
2348
+ if (!alwaysAcceptList.current.includes(toolNameToWhitelist)) {
2349
+ alwaysAcceptList.current.push(toolNameToWhitelist);
2350
+ }
2351
+ finalDecision = "accept";
2352
+ }
2353
+ const messageType = finalDecision === "accept" ? "user_decision_execute" : "user_decision_decline";
2354
+ agentInstance.current.handleToolResponse({
2355
+ type: messageType,
2356
+ tool_calls: toolCalls
2357
+ });
2358
+ },
2359
+ []
2360
+ );
2361
+ useEffect3(() => {
2362
+ setHistory([{ id: 0, component: /* @__PURE__ */ jsx11(Header, {}) }]);
2363
+ const initializeAgent = async () => {
2364
+ try {
2365
+ agentInstance.current = new Agent(sessionId2, eventBus2);
2366
+ await agentInstance.current.initialize();
2367
+ eventBus2.emit("backend_message", {
2368
+ type: "status",
2369
+ status: "mcp_connected",
2370
+ tools: agentInstance.current.getAvailableTools().length
2371
+ });
2372
+ } catch (error) {
2373
+ const errorMessage = error instanceof Error ? error.message : "Unknown error during Agent initialization.";
2374
+ eventBus2.emit("backend_message", {
2375
+ type: "error",
2376
+ message: errorMessage
2377
+ });
2378
+ }
2379
+ };
2380
+ const handleBackendMessage = (parsed) => {
2381
+ try {
2382
+ if (parsed.type === "connection_status") {
2383
+ setStatusMessage(parsed.message);
2384
+ return;
2385
+ }
2386
+ if (parsed.type === "confirmation_request") {
2387
+ const toolToConfirm = parsed.tool_calls[0].function.name;
2388
+ if (alwaysAcceptList.current.includes(toolToConfirm)) {
2389
+ handleConfirmation("accept", parsed.tool_calls);
2390
+ return;
2391
+ }
2392
+ setPendingConfirmation(parsed.tool_calls);
2393
+ setConfirmationPreview(parsed.preview || null);
2394
+ setIsProcessing(false);
2395
+ return;
2396
+ }
2397
+ if (parsed.type === "done") {
2398
+ if (parsed.status !== "awaiting_confirmation") {
2399
+ setStatusMessage(null);
2400
+ }
2401
+ setIsProcessing(false);
2402
+ return;
2403
+ }
2404
+ if (parsed.type === "status" && parsed.status === "mcp_connected") {
2405
+ setStatusMessage(null);
2406
+ setToolsCount(parsed.tools);
2407
+ setMcpStatus("connected");
2408
+ setIsProcessing(false);
2409
+ setHistory((prev) => {
2410
+ const newHistory = [...prev];
2411
+ if (prev.length < 2) {
2412
+ newHistory.push({
2413
+ id: 1,
2414
+ component: /* @__PURE__ */ jsx11(
2415
+ SessionInfo,
2416
+ {
2417
+ sessionId: sessionId2,
2418
+ toolsCount: parsed.tools,
2419
+ mcpStatus: "connected",
2420
+ workdir
2421
+ }
2422
+ )
2423
+ });
2424
+ }
2425
+ return newHistory;
2426
+ });
2427
+ return;
2428
+ }
2429
+ if (parsed.type === "error") {
2430
+ setStatusMessage(null);
2431
+ setIsProcessing(false);
2432
+ }
2433
+ let newComponent = null;
2434
+ if (parsed.type === "debug") {
2435
+ newComponent = /* @__PURE__ */ jsx11(Text10, { color: "gray", children: parsed.message });
2436
+ } else if (parsed.type === "protocol_violation") {
2437
+ newComponent = /* @__PURE__ */ jsxs10(
2438
+ Box11,
2439
+ {
2440
+ borderStyle: "round",
2441
+ borderColor: "yellow",
2442
+ flexDirection: "column",
2443
+ marginBottom: 1,
2444
+ paddingX: 1,
2445
+ children: [
2446
+ " ",
2447
+ /* @__PURE__ */ jsxs10(Text10, { color: "yellow", bold: true, children: [
2448
+ " ",
2449
+ "Protocol Violation",
2450
+ " "
2451
+ ] }),
2452
+ " ",
2453
+ /* @__PURE__ */ jsx11(Text10, { color: "gray", children: parsed.content }),
2454
+ " ",
2455
+ /* @__PURE__ */ jsx11(Text10, { color: "yellow", children: parsed.message }),
2456
+ " "
2457
+ ]
2458
+ }
2459
+ );
2460
+ } else if (parsed.type === "error") {
2461
+ newComponent = /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
2462
+ "\u274C ",
2463
+ parsed.message
2464
+ ] });
2465
+ } else if (parsed.type === "tool_call") {
2466
+ newComponent = /* @__PURE__ */ jsx11(
2467
+ ToolCallDisplay,
2468
+ {
2469
+ toolName: parsed.tool_name,
2470
+ args: parsed.arguments,
2471
+ preview: parsed.preview
2472
+ }
2473
+ );
2474
+ } else if (parsed.type === "tool_result") {
2475
+ newComponent = /* @__PURE__ */ jsx11(
2476
+ ToolResultDisplay,
2477
+ {
2478
+ toolName: parsed.tool_name,
2479
+ result: parsed.result
2480
+ }
2481
+ );
2482
+ } else if (parsed.type === "assistant_message" && parsed.content) {
2483
+ newComponent = /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ jsx11(Text10, { color: "blue", children: parsed.content }) });
2484
+ }
2485
+ if (newComponent) {
2486
+ setHistory((prev) => [
2487
+ ...prev,
2488
+ { id: prev.length, component: newComponent }
2489
+ ]);
2490
+ }
2491
+ } catch (error) {
2492
+ }
2493
+ };
2494
+ eventBus2.on("backend_message", handleBackendMessage);
2495
+ initializeAgent();
2496
+ return () => {
2497
+ eventBus2.off("backend_message", handleBackendMessage);
2498
+ };
2499
+ }, [eventBus2, sessionId2, handleConfirmation]);
2500
+ const renderInteractiveComponent = () => {
2501
+ if (mcpStatus !== "connected") {
2502
+ return /* @__PURE__ */ jsx11(Box11, { borderStyle: "round", borderColor: "black", children: /* @__PURE__ */ jsxs10(Text10, { color: "yellow", children: [
2503
+ /* @__PURE__ */ jsx11(Spinner, { type: "dots" }),
2504
+ " ",
2505
+ statusMessage || "Connecting..."
2506
+ ] }) });
2507
+ }
2508
+ if (pendingConfirmation) {
2509
+ return /* @__PURE__ */ jsx11(
2510
+ ConfirmationPrompt,
2511
+ {
2512
+ toolCalls: pendingConfirmation,
2513
+ preview: confirmationPreview,
2514
+ onDecision: (decision) => {
2515
+ setConfirmationPreview(null);
2516
+ handleConfirmation(decision, pendingConfirmation);
2517
+ }
2518
+ }
2519
+ );
2520
+ }
2521
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
2522
+ isProcessing && !pendingConfirmation && /* @__PURE__ */ jsx11(WorkingTimer, {}),
2523
+ /* @__PURE__ */ jsx11(
2524
+ InputPrompt,
2525
+ {
2526
+ onSubmit: handleSubmit,
2527
+ isReadOnly: isProcessing,
2528
+ onInterrupt: handleInterrupt
2529
+ }
2530
+ )
2531
+ ] });
2532
+ };
2533
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
2534
+ /* @__PURE__ */ jsx11(Static, { items: history, children: (item) => /* @__PURE__ */ jsx11(Box11, { children: item.component }, item.id) }),
2535
+ renderInteractiveComponent()
2536
+ ] });
2537
+ };
2538
+ var App = memo4(AppComponent);
2539
+ var App_default = App;
2540
+
2541
+ // src/main.ts
2542
+ var eventBus = new EventEmitter();
2543
+ var sessionId = uuidv42();
2544
+ var props = {
2545
+ eventBus,
2546
+ sessionId
2547
+ };
2548
+ render(React6.createElement(App_default, props));