@nomad-e/bluma-cli 0.0.27 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/bluma-mcp.json +1 -1
- package/dist/config/native_tools.json +1 -1
- package/dist/main.js +1730 -1227
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -36,7 +36,8 @@ var Header = () => {
|
|
|
36
36
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
37
37
|
"3. Run ",
|
|
38
38
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "/init" }),
|
|
39
|
-
" to create a
|
|
39
|
+
" to create a",
|
|
40
|
+
" ",
|
|
40
41
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "BluMa.md" }),
|
|
41
42
|
", file with instructions for BluMa."
|
|
42
43
|
] }),
|
|
@@ -57,36 +58,42 @@ var SessionInfo = ({
|
|
|
57
58
|
// Número de ferramentas ativas carregadas pelo MCP
|
|
58
59
|
mcpStatus
|
|
59
60
|
// Estado da conexão central MCP (connecting/connected)
|
|
60
|
-
}) => /* @__PURE__ */
|
|
61
|
+
}) => /* @__PURE__ */ jsx(
|
|
61
62
|
Box,
|
|
62
63
|
{
|
|
63
64
|
borderStyle: "round",
|
|
64
65
|
borderColor: "gray",
|
|
65
|
-
padding: 1,
|
|
66
66
|
flexDirection: "column",
|
|
67
67
|
marginBottom: 1,
|
|
68
|
-
children:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
68
|
+
children: /* @__PURE__ */ jsxs(
|
|
69
|
+
Box,
|
|
70
|
+
{
|
|
71
|
+
marginLeft: 1,
|
|
72
|
+
flexDirection: "column",
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
75
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "localhost" }),
|
|
76
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: " session:" }),
|
|
77
|
+
" ",
|
|
78
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", children: sessionId2 })
|
|
79
|
+
] }),
|
|
80
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
81
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", children: "\u21B3" }),
|
|
82
|
+
" ",
|
|
83
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
84
|
+
"workdir: ",
|
|
85
|
+
workdir
|
|
86
|
+
] })
|
|
87
|
+
] }),
|
|
88
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
89
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", children: "\u21B3" }),
|
|
90
|
+
" ",
|
|
91
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "mcp: " }),
|
|
92
|
+
/* @__PURE__ */ jsx(Text, { color: mcpStatus === "connected" ? "green" : "yellow", children: mcpStatus })
|
|
93
|
+
] })
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
)
|
|
90
97
|
}
|
|
91
98
|
);
|
|
92
99
|
|
|
@@ -239,7 +246,7 @@ var filterSlashCommands = (query) => {
|
|
|
239
246
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
240
247
|
var uiEventBus = global.__bluma_ui_eventbus__ || new EventEmitter();
|
|
241
248
|
global.__bluma_ui_eventbus__ = uiEventBus;
|
|
242
|
-
var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt }) => {
|
|
249
|
+
var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt, disableWhileProcessing = false }) => {
|
|
243
250
|
const { stdout } = useStdout();
|
|
244
251
|
const [viewWidth, setViewWidth] = useState(() => stdout.columns - 6);
|
|
245
252
|
useEffect(() => {
|
|
@@ -261,10 +268,14 @@ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt }) => {
|
|
|
261
268
|
}
|
|
262
269
|
onSubmit(value);
|
|
263
270
|
};
|
|
271
|
+
const effectiveReadOnly = disableWhileProcessing ? false : isReadOnly;
|
|
264
272
|
const { text, cursorPosition, viewStart } = useCustomInput({
|
|
265
|
-
onSubmit:
|
|
273
|
+
onSubmit: (value) => {
|
|
274
|
+
if (disableWhileProcessing && isReadOnly) return;
|
|
275
|
+
permissiveOnSubmit(value);
|
|
276
|
+
},
|
|
266
277
|
viewWidth,
|
|
267
|
-
isReadOnly,
|
|
278
|
+
isReadOnly: effectiveReadOnly,
|
|
268
279
|
onInterrupt
|
|
269
280
|
});
|
|
270
281
|
const [slashOpen, setSlashOpen] = useState(false);
|
|
@@ -431,10 +442,7 @@ var SimpleDiff = ({ text, maxHeight }) => {
|
|
|
431
442
|
} else if (line.startsWith("@@")) {
|
|
432
443
|
color = "cyan";
|
|
433
444
|
}
|
|
434
|
-
return /* @__PURE__ */
|
|
435
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " \u21B3 " }),
|
|
436
|
-
line === "" ? " " : line
|
|
437
|
-
] }, index);
|
|
445
|
+
return /* @__PURE__ */ jsx4(Text4, { color, children: line === "" ? " " : line }, index);
|
|
438
446
|
})
|
|
439
447
|
] });
|
|
440
448
|
};
|
|
@@ -610,192 +618,838 @@ var ConfirmationPrompt = ({ toolCalls, preview, onDecision }) => {
|
|
|
610
618
|
// src/app/agent/agent.ts
|
|
611
619
|
import OpenAI from "openai";
|
|
612
620
|
import * as dotenv from "dotenv";
|
|
613
|
-
import
|
|
614
|
-
import
|
|
621
|
+
import path8 from "path";
|
|
622
|
+
import os6 from "os";
|
|
615
623
|
|
|
616
|
-
// src/app/agent/
|
|
617
|
-
import
|
|
624
|
+
// src/app/agent/tool_invoker.ts
|
|
625
|
+
import { promises as fs5 } from "fs";
|
|
626
|
+
import path4 from "path";
|
|
627
|
+
import { fileURLToPath } from "url";
|
|
628
|
+
|
|
629
|
+
// src/app/agent/tools/natives/shell_command.ts
|
|
618
630
|
import os from "os";
|
|
619
|
-
import {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
631
|
+
import { exec } from "child_process";
|
|
632
|
+
function shellCommand(args) {
|
|
633
|
+
const { command, timeout = 20, cwd = process.cwd(), verbose = false } = args;
|
|
634
|
+
return new Promise((resolve) => {
|
|
635
|
+
const report = {
|
|
636
|
+
platform: os.platform(),
|
|
637
|
+
// Coleta o sistema operacional (ex: 'win32', 'linux')
|
|
638
|
+
command,
|
|
639
|
+
cwd,
|
|
640
|
+
results: []
|
|
641
|
+
};
|
|
642
|
+
if (verbose) {
|
|
643
|
+
report.env = {
|
|
644
|
+
PATH: process.env.PATH || "NOT SET",
|
|
645
|
+
ComSpec: process.env.ComSpec || "NOT SET"
|
|
646
|
+
// Específico do Windows, útil para saber qual cmd está sendo usado
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
const childProcess = exec(
|
|
650
|
+
command,
|
|
651
|
+
{
|
|
652
|
+
// O diretório de trabalho para o comando.
|
|
653
|
+
cwd,
|
|
654
|
+
// O timeout em milissegundos. Se o comando exceder este tempo, ele será encerrado.
|
|
655
|
+
timeout: timeout * 1e3,
|
|
656
|
+
// A opção `shell` foi removida, pois `exec` usa o shell por padrão.
|
|
657
|
+
// Especificar a codificação garante que a saída seja tratada como texto UTF-8.
|
|
658
|
+
encoding: "utf-8"
|
|
659
|
+
},
|
|
660
|
+
// Este é o callback que será executado QUANDO o processo filho terminar,
|
|
661
|
+
// seja por sucesso, erro ou timeout.
|
|
662
|
+
(error, stdout, stderr) => {
|
|
663
|
+
const result = {
|
|
664
|
+
method: "child_process.exec",
|
|
665
|
+
status: "Success",
|
|
666
|
+
// Se `error` existir, ele contém o código de saída. Caso contrário, o código é 0 (sucesso).
|
|
667
|
+
code: error ? error.code || null : 0,
|
|
668
|
+
// Limpa espaços em branco do início e fim das saídas.
|
|
669
|
+
output: stdout.trim(),
|
|
670
|
+
error: stderr.trim()
|
|
671
|
+
};
|
|
672
|
+
if (error) {
|
|
673
|
+
if (error.killed) {
|
|
674
|
+
result.status = "Timeout";
|
|
675
|
+
result.error = `Command exceeded timeout of ${timeout} seconds. ${stderr.trim()}`.trim();
|
|
676
|
+
} else {
|
|
677
|
+
result.status = "Error";
|
|
678
|
+
result.error = `${error.message}
|
|
679
|
+
${stderr.trim()}`.trim();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (verbose) {
|
|
683
|
+
report.results.push(result);
|
|
684
|
+
resolve(JSON.stringify(report, null, 2));
|
|
685
|
+
} else {
|
|
686
|
+
resolve(JSON.stringify(result, null, 2));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
});
|
|
640
691
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
692
|
+
|
|
693
|
+
// src/app/agent/tools/natives/edit.ts
|
|
694
|
+
import path2 from "path";
|
|
695
|
+
import { promises as fs } from "fs";
|
|
696
|
+
import { diffLines } from "diff";
|
|
697
|
+
function unescapeLlmString(inputString) {
|
|
698
|
+
return inputString.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, "\\");
|
|
644
699
|
}
|
|
645
|
-
|
|
646
|
-
let
|
|
647
|
-
let
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
700
|
+
function ensureCorrectEdit(currentContent, oldString, newString, expectedReplacements) {
|
|
701
|
+
let finalOldString = oldString;
|
|
702
|
+
let finalNewString = newString;
|
|
703
|
+
let occurrences = currentContent.split(finalOldString).length - 1;
|
|
704
|
+
if (occurrences !== expectedReplacements && occurrences === 0) {
|
|
705
|
+
const unescapedOldString = unescapeLlmString(oldString);
|
|
706
|
+
const unescapedOccurrences = currentContent.split(unescapedOldString).length - 1;
|
|
707
|
+
if (unescapedOccurrences > 0) {
|
|
708
|
+
finalOldString = unescapedOldString;
|
|
709
|
+
finalNewString = unescapeLlmString(newString);
|
|
710
|
+
occurrences = unescapedOccurrences;
|
|
711
|
+
} else {
|
|
712
|
+
const trimmedOldString = oldString.trim();
|
|
713
|
+
const trimmedOccurrences = currentContent.split(trimmedOldString).length - 1;
|
|
714
|
+
if (trimmedOccurrences > 0) {
|
|
715
|
+
finalOldString = trimmedOldString;
|
|
716
|
+
finalNewString = newString.trim();
|
|
717
|
+
occurrences = trimmedOccurrences;
|
|
718
|
+
}
|
|
661
719
|
}
|
|
662
720
|
}
|
|
663
|
-
|
|
664
|
-
const data = await fs.readFile(src);
|
|
665
|
-
await fs.writeFile(dest, data);
|
|
666
|
-
await fs.unlink(src).catch(() => {
|
|
667
|
-
});
|
|
668
|
-
return;
|
|
669
|
-
} catch (fallbackErr) {
|
|
670
|
-
throw lastErr || fallbackErr;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
async function ensureSessionDir() {
|
|
674
|
-
const appDir = getPreferredAppDir();
|
|
675
|
-
const sessionDir = path2.join(appDir, "sessions");
|
|
676
|
-
await fs.mkdir(sessionDir, { recursive: true });
|
|
677
|
-
return sessionDir;
|
|
721
|
+
return [finalOldString, finalNewString, occurrences];
|
|
678
722
|
}
|
|
679
|
-
async function
|
|
680
|
-
|
|
681
|
-
|
|
723
|
+
async function calculateEdit(filePath, oldString, newString, expectedReplacements) {
|
|
724
|
+
let currentContent = null;
|
|
725
|
+
let isNewFile = false;
|
|
726
|
+
let error = null;
|
|
727
|
+
let finalNewString = unescapeLlmString(newString).replace(/\r\n/g, "\n");
|
|
728
|
+
let finalOldString = oldString.replace(/\r\n/g, "\n");
|
|
729
|
+
let occurrences = 0;
|
|
682
730
|
try {
|
|
683
|
-
await fs.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
691
|
-
conversation_history: []
|
|
692
|
-
};
|
|
693
|
-
await fs.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
|
|
694
|
-
return [sessionFile, []];
|
|
731
|
+
currentContent = await fs.readFile(filePath, "utf-8");
|
|
732
|
+
currentContent = currentContent.replace(/\r\n/g, "\n");
|
|
733
|
+
} catch (e) {
|
|
734
|
+
if (e.code !== "ENOENT") {
|
|
735
|
+
error = { display: `Error reading file: ${e.message}`, raw: `Error reading file ${filePath}: ${e.message}` };
|
|
736
|
+
return { currentContent, newContent: "", occurrences: 0, error, isNewFile };
|
|
737
|
+
}
|
|
695
738
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
await fs.mkdir(dir, { recursive: true });
|
|
703
|
-
} catch {
|
|
739
|
+
if (currentContent === null) {
|
|
740
|
+
if (oldString === "") {
|
|
741
|
+
isNewFile = true;
|
|
742
|
+
occurrences = 1;
|
|
743
|
+
} else {
|
|
744
|
+
error = { display: "File not found. Cannot apply edit. Use an empty old_string to create a new file.", raw: `File not found: ${filePath}` };
|
|
704
745
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
const sessionId2 = path2.basename(sessionFile, ".json");
|
|
718
|
-
sessionData = {
|
|
719
|
-
session_id: sessionId2,
|
|
720
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
721
|
-
conversation_history: []
|
|
722
|
-
};
|
|
723
|
-
try {
|
|
724
|
-
await fs.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
725
|
-
} catch {
|
|
746
|
+
} else {
|
|
747
|
+
if (oldString === "") {
|
|
748
|
+
error = { display: "Failed to edit. Attempted to create a file that already exists.", raw: `File already exists, cannot create: ${filePath}` };
|
|
749
|
+
} else {
|
|
750
|
+
[finalOldString, finalNewString, occurrences] = ensureCorrectEdit(currentContent, finalOldString, finalNewString, expectedReplacements);
|
|
751
|
+
if (occurrences === 0) {
|
|
752
|
+
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.` };
|
|
753
|
+
} else if (occurrences !== expectedReplacements) {
|
|
754
|
+
error = { display: `Failed to edit, expected ${expectedReplacements} occurrence(s) but found ${occurrences}.`, raw: `Expected ${expectedReplacements} but found ${occurrences} for old_string in ${filePath}` };
|
|
726
755
|
}
|
|
727
756
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
if (writeError instanceof Error) {
|
|
736
|
-
console.error(`Fatal error saving session to ${sessionFile}: ${writeError.message}`);
|
|
737
|
-
} else {
|
|
738
|
-
console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
|
|
739
|
-
}
|
|
740
|
-
try {
|
|
741
|
-
await fs.unlink(tempSessionFile);
|
|
742
|
-
} catch {
|
|
743
|
-
}
|
|
757
|
+
}
|
|
758
|
+
let newContentResult = "";
|
|
759
|
+
if (!error) {
|
|
760
|
+
if (isNewFile) {
|
|
761
|
+
newContentResult = finalNewString;
|
|
762
|
+
} else if (currentContent !== null) {
|
|
763
|
+
newContentResult = currentContent.replaceAll(finalOldString, finalNewString);
|
|
744
764
|
}
|
|
745
|
-
}
|
|
765
|
+
}
|
|
766
|
+
return { currentContent, newContent: newContentResult, occurrences, error, isNewFile };
|
|
767
|
+
}
|
|
768
|
+
function createDiff(filename, oldContent, newContent) {
|
|
769
|
+
const diff = diffLines(oldContent, newContent, {
|
|
770
|
+
// `unified: 3` é o padrão para diffs, mostrando 3 linhas de contexto.
|
|
771
|
+
// `newlineIsToken: true` lida melhor com mudanças de quebra de linha.
|
|
772
|
+
});
|
|
773
|
+
let diffString = `--- a/${filename}
|
|
774
|
+
+++ b/${filename}
|
|
775
|
+
`;
|
|
776
|
+
diff.forEach((part) => {
|
|
777
|
+
const prefix = part.added ? "+" : part.removed ? "-" : " ";
|
|
778
|
+
part.value.split("\n").slice(0, -1).forEach((line) => {
|
|
779
|
+
diffString += `${prefix}${line}
|
|
780
|
+
`;
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
return diffString;
|
|
784
|
+
}
|
|
785
|
+
async function editTool(args) {
|
|
786
|
+
const { file_path, old_string, new_string, expected_replacements = 1 } = args;
|
|
787
|
+
if (!path2.isAbsolute(file_path)) {
|
|
788
|
+
return { success: false, error: `Invalid parameters: file_path must be absolute.`, file_path };
|
|
789
|
+
}
|
|
790
|
+
if (file_path.includes("..")) {
|
|
791
|
+
return { success: false, error: `Invalid parameters: file_path cannot contain '..'.`, file_path };
|
|
792
|
+
}
|
|
793
|
+
try {
|
|
794
|
+
const editData = await calculateEdit(file_path, old_string, new_string, expected_replacements);
|
|
795
|
+
if (editData.error) {
|
|
796
|
+
return {
|
|
797
|
+
success: false,
|
|
798
|
+
error: `Execution failed: ${editData.error.display}`,
|
|
799
|
+
details: editData.error.raw,
|
|
800
|
+
file_path
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
await fs.mkdir(path2.dirname(file_path), { recursive: true });
|
|
804
|
+
await fs.writeFile(file_path, editData.newContent, "utf-8");
|
|
805
|
+
const relativePath = path2.relative(process.cwd(), file_path);
|
|
806
|
+
const filename = path2.basename(file_path);
|
|
807
|
+
if (editData.isNewFile) {
|
|
808
|
+
return {
|
|
809
|
+
success: true,
|
|
810
|
+
file_path,
|
|
811
|
+
description: `Created new file: ${relativePath}`,
|
|
812
|
+
message: `Created new file: ${file_path} with the provided content.`,
|
|
813
|
+
is_new_file: true,
|
|
814
|
+
occurrences: editData.occurrences,
|
|
815
|
+
relative_path: relativePath
|
|
816
|
+
};
|
|
817
|
+
} else {
|
|
818
|
+
const finalDiff = createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
819
|
+
return {
|
|
820
|
+
success: true,
|
|
821
|
+
file_path,
|
|
822
|
+
description: `Modified ${relativePath} (${editData.occurrences} replacement(s)).`,
|
|
823
|
+
message: `Successfully modified file: ${file_path}. Diff of changes:
|
|
824
|
+
${finalDiff}`,
|
|
825
|
+
is_new_file: false,
|
|
826
|
+
occurrences: editData.occurrences,
|
|
827
|
+
relative_path: relativePath
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
} catch (e) {
|
|
831
|
+
return {
|
|
832
|
+
success: false,
|
|
833
|
+
error: `An unexpected error occurred during the edit operation: ${e.message}`,
|
|
834
|
+
file_path
|
|
835
|
+
};
|
|
836
|
+
}
|
|
746
837
|
}
|
|
747
838
|
|
|
748
|
-
// src/app/agent/
|
|
749
|
-
import
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
- **Task Completion:**
|
|
767
|
-
When a task is completed, immediately invoke 'agent_end_task' without dev permissions.
|
|
768
|
-
|
|
769
|
-
- **Tool Rules:**
|
|
770
|
-
Never make parallel tool calls.
|
|
771
|
-
Always use only the defined tools with their exact names.
|
|
772
|
-
|
|
773
|
-
- **Autonomy:**
|
|
774
|
-
Act 100% autonomously.
|
|
775
|
-
Do not ask for formatting preferences.
|
|
776
|
-
Use the notebook for internal reasoning.
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
### CRITICAL COMMUNICATION PROTOCOL
|
|
780
|
-
- Only tool_calls are allowed for assistant replies. Never include a "content" field.
|
|
781
|
-
- Always use tools to respond, retrieve data, compute or transform. Await a valid tool response before any final message.
|
|
782
|
-
- Zero tolerance for protocol violations.
|
|
839
|
+
// src/app/agent/tools/natives/message.ts
|
|
840
|
+
import { v4 as uuidv4 } from "uuid";
|
|
841
|
+
function messageNotifyDev(args) {
|
|
842
|
+
const { text_markdown } = args;
|
|
843
|
+
const notification = {
|
|
844
|
+
type: "message_notify_dev",
|
|
845
|
+
id: `notify_${uuidv4()}`,
|
|
846
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
847
|
+
content: {
|
|
848
|
+
format: "markdown",
|
|
849
|
+
body: text_markdown
|
|
850
|
+
},
|
|
851
|
+
success: true,
|
|
852
|
+
delivered: true
|
|
853
|
+
};
|
|
854
|
+
return Promise.resolve(notification);
|
|
855
|
+
}
|
|
783
856
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
857
|
+
// src/app/agent/tools/natives/ls.ts
|
|
858
|
+
import { promises as fs2 } from "fs";
|
|
859
|
+
import path3 from "path";
|
|
860
|
+
var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
861
|
+
".git",
|
|
862
|
+
".gitignore",
|
|
863
|
+
".venv",
|
|
864
|
+
"venv",
|
|
865
|
+
"node_modules",
|
|
866
|
+
"__pycache__",
|
|
867
|
+
"*.pyc",
|
|
868
|
+
".vscode",
|
|
869
|
+
".idea",
|
|
870
|
+
"dist",
|
|
871
|
+
"build",
|
|
872
|
+
"*.log",
|
|
873
|
+
".DS_Store"
|
|
874
|
+
]);
|
|
875
|
+
async function ls(args) {
|
|
876
|
+
const {
|
|
877
|
+
directory_path = ".",
|
|
878
|
+
recursive = false,
|
|
879
|
+
ignore_patterns = [],
|
|
880
|
+
start_index = 0,
|
|
881
|
+
end_index,
|
|
882
|
+
show_hidden = false,
|
|
883
|
+
file_extensions,
|
|
884
|
+
max_depth
|
|
885
|
+
} = args;
|
|
886
|
+
try {
|
|
887
|
+
const basePath = path3.resolve(directory_path);
|
|
888
|
+
if (!(await fs2.stat(basePath)).isDirectory()) {
|
|
889
|
+
throw new Error(`Directory '${directory_path}' not found.`);
|
|
890
|
+
}
|
|
891
|
+
const allIgnorePatterns = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore_patterns]);
|
|
892
|
+
const normalizedExtensions = file_extensions?.map((ext) => ext.toLowerCase());
|
|
893
|
+
const allFiles = [];
|
|
894
|
+
const allDirs = [];
|
|
895
|
+
const walk = async (currentDir, currentDepth) => {
|
|
896
|
+
if (max_depth !== void 0 && currentDepth > max_depth) return;
|
|
897
|
+
const entries = await fs2.readdir(currentDir, { withFileTypes: true });
|
|
898
|
+
for (const entry of entries) {
|
|
899
|
+
const entryName = entry.name;
|
|
900
|
+
const fullPath = path3.join(currentDir, entryName);
|
|
901
|
+
const posixPath = fullPath.split(path3.sep).join("/");
|
|
902
|
+
const isHidden = entryName.startsWith(".");
|
|
903
|
+
if (allIgnorePatterns.has(entryName) || isHidden && !show_hidden) {
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (entry.isDirectory()) {
|
|
907
|
+
allDirs.push(posixPath);
|
|
908
|
+
if (recursive) {
|
|
909
|
+
await walk(fullPath, currentDepth + 1);
|
|
910
|
+
}
|
|
911
|
+
} else if (entry.isFile()) {
|
|
912
|
+
if (!normalizedExtensions || normalizedExtensions.includes(path3.extname(entryName).toLowerCase())) {
|
|
913
|
+
allFiles.push(posixPath);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
await walk(basePath, 0);
|
|
919
|
+
allFiles.sort();
|
|
920
|
+
allDirs.sort();
|
|
921
|
+
return {
|
|
922
|
+
success: true,
|
|
923
|
+
path: basePath.split(path3.sep).join("/"),
|
|
924
|
+
recursive,
|
|
925
|
+
total_files: allFiles.length,
|
|
926
|
+
total_directories: allDirs.length,
|
|
927
|
+
showing_files: `[${start_index}:${Math.min(end_index ?? allFiles.length, allFiles.length)}]`,
|
|
928
|
+
showing_directories: `[${start_index}:${Math.min(end_index ?? allDirs.length, allDirs.length)}]`,
|
|
929
|
+
files: allFiles.slice(start_index, end_index),
|
|
930
|
+
directories: allDirs.slice(start_index, end_index),
|
|
931
|
+
filters_applied: { ignore_patterns: [...allIgnorePatterns], show_hidden, file_extensions, max_depth }
|
|
932
|
+
};
|
|
933
|
+
} catch (e) {
|
|
934
|
+
return { success: false, error: e.message };
|
|
935
|
+
}
|
|
936
|
+
}
|
|
794
937
|
|
|
938
|
+
// src/app/agent/tools/natives/readLines.ts
|
|
939
|
+
import { promises as fs3 } from "fs";
|
|
940
|
+
async function readLines(args) {
|
|
941
|
+
const { filepath, start_line, end_line } = args;
|
|
942
|
+
try {
|
|
943
|
+
if (!(await fs3.stat(filepath)).isFile()) {
|
|
944
|
+
throw new Error(`File '${filepath}' not found or is not a file.`);
|
|
945
|
+
}
|
|
946
|
+
if (start_line < 1 || end_line < start_line) {
|
|
947
|
+
throw new Error("Invalid line range. start_line must be >= 1 and end_line must be >= start_line.");
|
|
948
|
+
}
|
|
949
|
+
const fileContent = await fs3.readFile(filepath, "utf-8");
|
|
950
|
+
const lines = fileContent.split("\n");
|
|
951
|
+
const total_lines = lines.length;
|
|
952
|
+
const startIndex = start_line - 1;
|
|
953
|
+
let endIndex = end_line;
|
|
954
|
+
if (startIndex >= total_lines) {
|
|
955
|
+
throw new Error(`start_line (${start_line}) exceeds file length (${total_lines} lines).`);
|
|
956
|
+
}
|
|
957
|
+
endIndex = Math.min(endIndex, total_lines);
|
|
958
|
+
const contentLines = lines.slice(startIndex, endIndex);
|
|
959
|
+
const content = contentLines.join("\n");
|
|
960
|
+
return {
|
|
961
|
+
success: true,
|
|
962
|
+
filepath,
|
|
963
|
+
content,
|
|
964
|
+
lines_read: contentLines.length,
|
|
965
|
+
start_line,
|
|
966
|
+
end_line: endIndex,
|
|
967
|
+
// Retorna o final real usado
|
|
968
|
+
total_file_lines: total_lines
|
|
969
|
+
};
|
|
970
|
+
} catch (e) {
|
|
971
|
+
return { success: false, error: e.message };
|
|
972
|
+
}
|
|
973
|
+
}
|
|
795
974
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
975
|
+
// src/app/agent/tools/natives/count_lines.ts
|
|
976
|
+
import { createReadStream } from "fs";
|
|
977
|
+
import { promises as fs4 } from "fs";
|
|
978
|
+
import readline from "readline";
|
|
979
|
+
async function countLines(args) {
|
|
980
|
+
const { filepath } = args;
|
|
981
|
+
try {
|
|
982
|
+
if (!(await fs4.stat(filepath)).isFile()) {
|
|
983
|
+
throw new Error(`File '${filepath}' not found or is not a file.`);
|
|
984
|
+
}
|
|
985
|
+
const fileStream = createReadStream(filepath);
|
|
986
|
+
const rl = readline.createInterface({
|
|
987
|
+
input: fileStream,
|
|
988
|
+
crlfDelay: Infinity
|
|
989
|
+
});
|
|
990
|
+
let lineCount = 0;
|
|
991
|
+
for await (const line of rl) {
|
|
992
|
+
lineCount++;
|
|
993
|
+
}
|
|
994
|
+
return { success: true, filepath, line_count: lineCount };
|
|
995
|
+
} catch (e) {
|
|
996
|
+
return { success: false, error: e.message };
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// src/app/agent/tool_invoker.ts
|
|
1001
|
+
var ToolInvoker = class {
|
|
1002
|
+
// Mapa privado para associar nomes de ferramentas às suas funções de implementação.
|
|
1003
|
+
toolImplementations;
|
|
1004
|
+
// Propriedade privada para armazenar as definições de ferramentas carregadas do JSON.
|
|
1005
|
+
toolDefinitions = [];
|
|
1006
|
+
constructor() {
|
|
1007
|
+
this.toolImplementations = /* @__PURE__ */ new Map();
|
|
1008
|
+
this.registerTools();
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Carrega as definições de ferramentas do arquivo de configuração `native_tools.json`.
|
|
1012
|
+
* Este método é assíncrono e deve ser chamado após a criação da instância.
|
|
1013
|
+
*/
|
|
1014
|
+
async initialize() {
|
|
1015
|
+
try {
|
|
1016
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1017
|
+
const __dirname = path4.dirname(__filename);
|
|
1018
|
+
const configPath = path4.resolve(__dirname, "config", "native_tools.json");
|
|
1019
|
+
const fileContent = await fs5.readFile(configPath, "utf-8");
|
|
1020
|
+
const config2 = JSON.parse(fileContent);
|
|
1021
|
+
this.toolDefinitions = config2.nativeTools;
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
console.error("[ToolInvoker] Erro cr\xEDtico ao carregar 'native_tools.json'. As ferramentas nativas n\xE3o estar\xE3o dispon\xEDveis.", error);
|
|
1024
|
+
this.toolDefinitions = [];
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Registra as implementações de todas as ferramentas nativas.
|
|
1029
|
+
* Este método mapeia o nome da ferramenta (string) para a função TypeScript que a executa.
|
|
1030
|
+
*/
|
|
1031
|
+
registerTools() {
|
|
1032
|
+
this.toolImplementations.set("shell_command", shellCommand);
|
|
1033
|
+
this.toolImplementations.set("edit_tool", editTool);
|
|
1034
|
+
this.toolImplementations.set("message_notify_dev", messageNotifyDev);
|
|
1035
|
+
this.toolImplementations.set("ls_tool", ls);
|
|
1036
|
+
this.toolImplementations.set("count_file_lines", countLines);
|
|
1037
|
+
this.toolImplementations.set("read_file_lines", readLines);
|
|
1038
|
+
this.toolImplementations.set("agent_end_task", async () => ({ success: true, message: "Task ended by agent." }));
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Retorna a lista de definições de todas as ferramentas nativas carregadas.
|
|
1042
|
+
* O MCPClient usará esta função para obter a lista de ferramentas locais.
|
|
1043
|
+
*/
|
|
1044
|
+
getToolDefinitions() {
|
|
1045
|
+
return this.toolDefinitions;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Invoca uma ferramenta nativa pelo nome com os argumentos fornecidos.
|
|
1049
|
+
* @param toolName O nome da ferramenta a ser invocada.
|
|
1050
|
+
* @param args Os argumentos para a ferramenta, geralmente um objeto.
|
|
1051
|
+
* @returns O resultado da execução da ferramenta.
|
|
1052
|
+
*/
|
|
1053
|
+
async invoke(toolName, args) {
|
|
1054
|
+
const implementation = this.toolImplementations.get(toolName);
|
|
1055
|
+
if (!implementation) {
|
|
1056
|
+
return { error: `Error: Native tool "${toolName}" not found.` };
|
|
1057
|
+
}
|
|
1058
|
+
try {
|
|
1059
|
+
return await implementation(args);
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred.";
|
|
1062
|
+
return { error: `Error executing tool "${toolName}": ${errorMessage}` };
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
// src/app/agent/tools/mcp/mcp_client.ts
|
|
1068
|
+
import { promises as fs6 } from "fs";
|
|
1069
|
+
import path5 from "path";
|
|
1070
|
+
import os2 from "os";
|
|
1071
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1072
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1073
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1074
|
+
var MCPClient = class {
|
|
1075
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1076
|
+
toolToServerMap = /* @__PURE__ */ new Map();
|
|
1077
|
+
globalToolsForLlm = [];
|
|
1078
|
+
nativeToolInvoker;
|
|
1079
|
+
eventBus;
|
|
1080
|
+
// <<< ADICIONA A PROPRIEDADE
|
|
1081
|
+
constructor(nativeToolInvoker, eventBus2) {
|
|
1082
|
+
this.nativeToolInvoker = nativeToolInvoker;
|
|
1083
|
+
this.eventBus = eventBus2;
|
|
1084
|
+
}
|
|
1085
|
+
// ... (método initialize inalterado) ...
|
|
1086
|
+
async initialize() {
|
|
1087
|
+
const nativeTools = this.nativeToolInvoker.getToolDefinitions();
|
|
1088
|
+
this.globalToolsForLlm.push(...nativeTools);
|
|
1089
|
+
for (const tool of nativeTools) {
|
|
1090
|
+
const toolName = tool.function.name;
|
|
1091
|
+
this.toolToServerMap.set(toolName, {
|
|
1092
|
+
server: "native",
|
|
1093
|
+
originalName: toolName
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
const __filename = fileURLToPath2(import.meta.url);
|
|
1097
|
+
const __dirname = path5.dirname(__filename);
|
|
1098
|
+
const defaultConfigPath = path5.resolve(__dirname, "config", "bluma-mcp.json");
|
|
1099
|
+
const userConfigPath = path5.join(os2.homedir(), ".bluma-cli", "bluma-mcp.json");
|
|
1100
|
+
const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
|
|
1101
|
+
const userConfig = await this.loadMcpConfig(userConfigPath, "User");
|
|
1102
|
+
const mergedConfig = {
|
|
1103
|
+
mcpServers: {
|
|
1104
|
+
...defaultConfig.mcpServers || {},
|
|
1105
|
+
...userConfig.mcpServers || {}
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
if (Object.keys(mergedConfig.mcpServers).length === 0) {
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
const serverEntries = Object.entries(mergedConfig.mcpServers);
|
|
1112
|
+
for (const [serverName, serverConf] of serverEntries) {
|
|
1113
|
+
try {
|
|
1114
|
+
this.eventBus.emit("backend_message", {
|
|
1115
|
+
type: "connection_status",
|
|
1116
|
+
message: `${serverName} server is being connected...`
|
|
1117
|
+
});
|
|
1118
|
+
if (serverConf.type === "stdio") {
|
|
1119
|
+
await this.connectToStdioServer(serverName, serverConf);
|
|
1120
|
+
} else if (serverConf.type === "sse") {
|
|
1121
|
+
console.warn(`[MCPClient] Conex\xE3o com servidores SSE (como '${serverName}') ainda n\xE3o implementada.`);
|
|
1122
|
+
}
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
this.eventBus.emit("backend_message", {
|
|
1125
|
+
type: "error",
|
|
1126
|
+
message: `Failed to connect to server '${serverName}'.`
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
async loadMcpConfig(configPath, configType) {
|
|
1132
|
+
try {
|
|
1133
|
+
const fileContent = await fs6.readFile(configPath, "utf-8");
|
|
1134
|
+
const processedContent = this.replaceEnvPlaceholders(fileContent);
|
|
1135
|
+
return JSON.parse(processedContent);
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
if (error.code === "ENOENT") {
|
|
1138
|
+
if (configType === "User") {
|
|
1139
|
+
}
|
|
1140
|
+
} else {
|
|
1141
|
+
console.warn(`[MCPClient] Warning: Error reading ${configType} config file ${configPath}.`, error);
|
|
1142
|
+
}
|
|
1143
|
+
return {};
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Conecta-se a um servidor MCP baseado em Stdio, adaptando o comando para o SO atual.
|
|
1148
|
+
*/
|
|
1149
|
+
async connectToStdioServer(serverName, config2) {
|
|
1150
|
+
let commandToExecute = config2.command;
|
|
1151
|
+
let argsToExecute = config2.args || [];
|
|
1152
|
+
const isWindows = os2.platform() === "win32";
|
|
1153
|
+
if (!isWindows && commandToExecute.toLowerCase() === "cmd") {
|
|
1154
|
+
if (argsToExecute.length >= 2 && argsToExecute[0].toLowerCase() === "/c") {
|
|
1155
|
+
commandToExecute = argsToExecute[1];
|
|
1156
|
+
argsToExecute = argsToExecute.slice(2);
|
|
1157
|
+
} else {
|
|
1158
|
+
console.warn(`[MCPClient] Formato de comando 'cmd /c' inesperado para '${serverName}' em sistema n\xE3o-Windows. O servidor ser\xE1 ignorado.`);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
const transport = new StdioClientTransport({
|
|
1163
|
+
command: commandToExecute,
|
|
1164
|
+
// Usa o comando adaptado
|
|
1165
|
+
args: argsToExecute,
|
|
1166
|
+
// Usa os argumentos adaptados
|
|
1167
|
+
env: config2.env
|
|
1168
|
+
});
|
|
1169
|
+
const mcp = new Client({ name: `bluma-cli-client-for-${serverName}`, version: "1.0.0" });
|
|
1170
|
+
await mcp.connect(transport);
|
|
1171
|
+
this.sessions.set(serverName, mcp);
|
|
1172
|
+
const toolsResult = await mcp.listTools();
|
|
1173
|
+
for (const tool of toolsResult.tools) {
|
|
1174
|
+
const prefixedToolName = `${serverName}_${tool.name}`;
|
|
1175
|
+
this.globalToolsForLlm.push({
|
|
1176
|
+
type: "function",
|
|
1177
|
+
function: {
|
|
1178
|
+
name: prefixedToolName,
|
|
1179
|
+
description: tool.description || "",
|
|
1180
|
+
parameters: tool.inputSchema
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
this.toolToServerMap.set(prefixedToolName, {
|
|
1184
|
+
server: serverName,
|
|
1185
|
+
originalName: tool.name
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
async invoke(toolName, args) {
|
|
1190
|
+
const route = this.toolToServerMap.get(toolName);
|
|
1191
|
+
if (!route) {
|
|
1192
|
+
return { error: `Ferramenta '${toolName}' n\xE3o encontrada ou registrada.` };
|
|
1193
|
+
}
|
|
1194
|
+
if (route.server === "native") {
|
|
1195
|
+
return this.nativeToolInvoker.invoke(route.originalName, args);
|
|
1196
|
+
} else {
|
|
1197
|
+
const session = this.sessions.get(route.server);
|
|
1198
|
+
if (!session) {
|
|
1199
|
+
return { error: `Sess\xE3o para o servidor '${route.server}' n\xE3o encontrada.` };
|
|
1200
|
+
}
|
|
1201
|
+
const result = await session.callTool({ name: route.originalName, arguments: args });
|
|
1202
|
+
return result.content;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
getAvailableTools() {
|
|
1206
|
+
return this.globalToolsForLlm;
|
|
1207
|
+
}
|
|
1208
|
+
// New: detailed list for UI with origin metadata
|
|
1209
|
+
getAvailableToolsDetailed() {
|
|
1210
|
+
const detailed = [];
|
|
1211
|
+
for (const tool of this.globalToolsForLlm) {
|
|
1212
|
+
const name = tool.function?.name;
|
|
1213
|
+
if (!name) continue;
|
|
1214
|
+
const route = this.toolToServerMap.get(name);
|
|
1215
|
+
if (!route) continue;
|
|
1216
|
+
const source = route.server === "native" ? "native" : "mcp";
|
|
1217
|
+
detailed.push({ ...tool, source, server: route.server, originalName: route.originalName });
|
|
1218
|
+
}
|
|
1219
|
+
return detailed;
|
|
1220
|
+
}
|
|
1221
|
+
async close() {
|
|
1222
|
+
for (const [name, session] of this.sessions.entries()) {
|
|
1223
|
+
try {
|
|
1224
|
+
await session.close();
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
console.error(`[MCPClient] Erro ao encerrar conex\xE3o com '${name}':`, error);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
replaceEnvPlaceholders(content) {
|
|
1231
|
+
return content.replace(/\$\{([A-Za-z0-9_]+)\}/g, (match, varName) => {
|
|
1232
|
+
return process.env[varName] || match;
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
// src/app/agent/feedback/feedback_system.ts
|
|
1238
|
+
var AdvancedFeedbackSystem = class {
|
|
1239
|
+
cumulativeScore = 0;
|
|
1240
|
+
/**
|
|
1241
|
+
* Gera feedback com base em um evento ocorrido.
|
|
1242
|
+
* @param event O evento a ser avaliado.
|
|
1243
|
+
* @returns Um objeto com a pontuação, mensagem e correção.
|
|
1244
|
+
*/
|
|
1245
|
+
generateFeedback(event) {
|
|
1246
|
+
if (event.event === "protocol_violation_direct_text") {
|
|
1247
|
+
const penalty = -2.5;
|
|
1248
|
+
this.cumulativeScore += penalty;
|
|
1249
|
+
return {
|
|
1250
|
+
score: penalty,
|
|
1251
|
+
message: "Direct text response is a protocol violation. All communication must be done via the 'message_notify_dev' tool.",
|
|
1252
|
+
correction: `
|
|
1253
|
+
## PROTOCOL VIOLATION \u2014 SEVERE
|
|
1254
|
+
You sent a direct text response, which is strictly prohibited.
|
|
1255
|
+
PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
|
|
1256
|
+
You MUST use tools for all actions and communication.
|
|
1257
|
+
`.trim()
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
return { score: 0, message: "No feedback for this event.", correction: "" };
|
|
1261
|
+
}
|
|
1262
|
+
getCumulativeScore() {
|
|
1263
|
+
return this.cumulativeScore;
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
// src/app/agent/bluma/core/bluma.ts
|
|
1268
|
+
import path7 from "path";
|
|
1269
|
+
|
|
1270
|
+
// src/app/agent/session_manger/session_manager.ts
|
|
1271
|
+
import path6 from "path";
|
|
1272
|
+
import os3 from "os";
|
|
1273
|
+
import { promises as fs7 } from "fs";
|
|
1274
|
+
var fileLocks = /* @__PURE__ */ new Map();
|
|
1275
|
+
async function withFileLock(file, fn) {
|
|
1276
|
+
const prev = fileLocks.get(file) || Promise.resolve();
|
|
1277
|
+
let release;
|
|
1278
|
+
const p = new Promise((res) => release = res);
|
|
1279
|
+
fileLocks.set(file, prev.then(() => p));
|
|
1280
|
+
try {
|
|
1281
|
+
const result = await fn();
|
|
1282
|
+
return result;
|
|
1283
|
+
} finally {
|
|
1284
|
+
release();
|
|
1285
|
+
if (fileLocks.get(file) === p) fileLocks.delete(file);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
function expandHome(p) {
|
|
1289
|
+
if (!p) return p;
|
|
1290
|
+
if (p.startsWith("~")) {
|
|
1291
|
+
return path6.join(os3.homedir(), p.slice(1));
|
|
1292
|
+
}
|
|
1293
|
+
return p;
|
|
1294
|
+
}
|
|
1295
|
+
function getPreferredAppDir() {
|
|
1296
|
+
const fixed = path6.join(os3.homedir(), ".bluma-cli");
|
|
1297
|
+
return path6.resolve(expandHome(fixed));
|
|
1298
|
+
}
|
|
1299
|
+
async function safeRenameWithRetry(src, dest, maxRetries = 6) {
|
|
1300
|
+
let attempt = 0;
|
|
1301
|
+
let lastErr;
|
|
1302
|
+
const isWin = process.platform === "win32";
|
|
1303
|
+
while (attempt <= maxRetries) {
|
|
1304
|
+
try {
|
|
1305
|
+
await fs7.rename(src, dest);
|
|
1306
|
+
return;
|
|
1307
|
+
} catch (e) {
|
|
1308
|
+
lastErr = e;
|
|
1309
|
+
const code = e && e.code || "";
|
|
1310
|
+
const transient = code === "EPERM" || code === "EBUSY" || code === "ENOTEMPTY" || code === "EACCES";
|
|
1311
|
+
if (!(isWin && transient) || attempt === maxRetries) break;
|
|
1312
|
+
const backoff = Math.min(1e3, 50 * Math.pow(2, attempt));
|
|
1313
|
+
await new Promise((r) => setTimeout(r, backoff));
|
|
1314
|
+
attempt++;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
try {
|
|
1318
|
+
const data = await fs7.readFile(src);
|
|
1319
|
+
await fs7.writeFile(dest, data);
|
|
1320
|
+
await fs7.unlink(src).catch(() => {
|
|
1321
|
+
});
|
|
1322
|
+
return;
|
|
1323
|
+
} catch (fallbackErr) {
|
|
1324
|
+
throw lastErr || fallbackErr;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
async function ensureSessionDir() {
|
|
1328
|
+
const appDir = getPreferredAppDir();
|
|
1329
|
+
const sessionDir = path6.join(appDir, "sessions");
|
|
1330
|
+
await fs7.mkdir(sessionDir, { recursive: true });
|
|
1331
|
+
return sessionDir;
|
|
1332
|
+
}
|
|
1333
|
+
async function loadOrcreateSession(sessionId2) {
|
|
1334
|
+
const sessionDir = await ensureSessionDir();
|
|
1335
|
+
const sessionFile = path6.join(sessionDir, `${sessionId2}.json`);
|
|
1336
|
+
try {
|
|
1337
|
+
await fs7.access(sessionFile);
|
|
1338
|
+
const fileContent = await fs7.readFile(sessionFile, "utf-8");
|
|
1339
|
+
const sessionData = JSON.parse(fileContent);
|
|
1340
|
+
return [sessionFile, sessionData.conversation_history || []];
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
const newSessionData = {
|
|
1343
|
+
session_id: sessionId2,
|
|
1344
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1345
|
+
conversation_history: []
|
|
1346
|
+
};
|
|
1347
|
+
await fs7.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
|
|
1348
|
+
return [sessionFile, []];
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async function saveSessionHistory(sessionFile, history) {
|
|
1352
|
+
await withFileLock(sessionFile, async () => {
|
|
1353
|
+
let sessionData;
|
|
1354
|
+
try {
|
|
1355
|
+
const dir = path6.dirname(sessionFile);
|
|
1356
|
+
await fs7.mkdir(dir, { recursive: true });
|
|
1357
|
+
} catch {
|
|
1358
|
+
}
|
|
1359
|
+
try {
|
|
1360
|
+
const fileContent = await fs7.readFile(sessionFile, "utf-8");
|
|
1361
|
+
sessionData = JSON.parse(fileContent);
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
const code = error && error.code;
|
|
1364
|
+
if (code !== "ENOENT") {
|
|
1365
|
+
if (error instanceof Error) {
|
|
1366
|
+
console.warn(`Could not read or parse session file ${sessionFile}. Re-initializing. Error: ${error.message}`);
|
|
1367
|
+
} else {
|
|
1368
|
+
console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
const sessionId2 = path6.basename(sessionFile, ".json");
|
|
1372
|
+
sessionData = {
|
|
1373
|
+
session_id: sessionId2,
|
|
1374
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1375
|
+
conversation_history: []
|
|
1376
|
+
};
|
|
1377
|
+
try {
|
|
1378
|
+
await fs7.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
1379
|
+
} catch {
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
sessionData.conversation_history = history;
|
|
1383
|
+
sessionData.last_updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1384
|
+
const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
|
|
1385
|
+
try {
|
|
1386
|
+
await fs7.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
|
|
1387
|
+
await safeRenameWithRetry(tempSessionFile, sessionFile);
|
|
1388
|
+
} catch (writeError) {
|
|
1389
|
+
if (writeError instanceof Error) {
|
|
1390
|
+
console.error(`Fatal error saving session to ${sessionFile}: ${writeError.message}`);
|
|
1391
|
+
} else {
|
|
1392
|
+
console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
|
|
1393
|
+
}
|
|
1394
|
+
try {
|
|
1395
|
+
await fs7.unlink(tempSessionFile);
|
|
1396
|
+
} catch {
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// src/app/agent/core/prompt/prompt_builder.ts
|
|
1403
|
+
import os4 from "os";
|
|
1404
|
+
var SYSTEM_PROMPT = `
|
|
1405
|
+
|
|
1406
|
+
### YOU ARE BluMa CLI \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
|
|
1407
|
+
You use a proprietary Large Language Model (LLM) fine-tuned by the NomadEngenuity team.
|
|
1408
|
+
|
|
1409
|
+
---
|
|
1410
|
+
|
|
1411
|
+
## BEHAVIORAL RULES
|
|
1412
|
+
|
|
1413
|
+
- **Identity:**
|
|
1414
|
+
You are BluMa (NomadEngenuity). Maintain professionalism and technical language.
|
|
1415
|
+
|
|
1416
|
+
- **Communication:**
|
|
1417
|
+
ALL messages must be sent via 'message_notify_dev'.
|
|
1418
|
+
**No direct text replies to the developer.**
|
|
1419
|
+
|
|
1420
|
+
- **Task Completion:**
|
|
1421
|
+
When a task is completed, immediately invoke 'agent_end_task' without dev permissions.
|
|
1422
|
+
|
|
1423
|
+
- **Tool Rules:**
|
|
1424
|
+
Never make parallel tool calls.
|
|
1425
|
+
Always use only the defined tools with their exact names.
|
|
1426
|
+
|
|
1427
|
+
- **Autonomy:**
|
|
1428
|
+
Act 100% autonomously.
|
|
1429
|
+
Do not ask for formatting preferences.
|
|
1430
|
+
Use the notebook for internal reasoning.
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
### CRITICAL COMMUNICATION PROTOCOL
|
|
1434
|
+
- Only tool_calls are allowed for assistant replies. Never include a "content" field.
|
|
1435
|
+
- Always use tools to respond, retrieve data, compute or transform. Await a valid tool response before any final message.
|
|
1436
|
+
- Zero tolerance for protocol violations.
|
|
1437
|
+
|
|
1438
|
+
<current_system_environment>
|
|
1439
|
+
- Operating System: {os_type} ({os_version})
|
|
1440
|
+
- Architecture: {architecture}
|
|
1441
|
+
- Current Working Directory: {workdir}
|
|
1442
|
+
- Shell: {shell_type}
|
|
1443
|
+
- Username: {username}
|
|
1444
|
+
- Current Date: {current_date}
|
|
1445
|
+
- Timezone: {timezone}
|
|
1446
|
+
- Locale: {locale}
|
|
1447
|
+
</current_system_environment>
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
<mermaid_diagrams>
|
|
1451
|
+
# MERMAID DIAGRAM CREATION - PERFECT SYNTAX REQUIRED!
|
|
1452
|
+
## CRITICAL: ALL DIAGRAMS MUST RENDER WITHOUT ERRORS
|
|
799
1453
|
|
|
800
1454
|
### MANDATORY MERMAID SYNTAX RULES
|
|
801
1455
|
1. **ALWAYS wrap ALL labels in double quotes**: "label text"
|
|
@@ -893,15 +1547,15 @@ Before creating any diagram, ensure:
|
|
|
893
1547
|
Every diagram MUST render perfectly on first try. No exceptions.
|
|
894
1548
|
</mermaid_diagrams>
|
|
895
1549
|
|
|
896
|
-
|
|
897
|
-
- Communicate with dev via message tools instead of direct text responses
|
|
898
|
-
- Reply immediately to new
|
|
1550
|
+
<message_rules>
|
|
1551
|
+
- Communicate with dev's via message tools instead of direct text responses
|
|
1552
|
+
- Reply immediately to new user messages before other operations
|
|
899
1553
|
- First reply must be brief, only confirming receipt without specific solutions
|
|
900
1554
|
- Notify dev's with brief explanation when changing methods or strategies
|
|
901
1555
|
- Message tools are divided into notify (non-blocking, no reply needed from dev's) and ask (blocking, reply required)
|
|
902
|
-
- Actively use notify for progress updates, but reserve ask for only essential needs to minimize dev disruption and avoid blocking progress
|
|
903
|
-
- Must
|
|
904
|
-
|
|
1556
|
+
- Actively use notify for progress updates, but reserve ask for only essential needs to minimize dev's disruption and avoid blocking progress
|
|
1557
|
+
- Must message dev's with results and deliverables before upon task completion 'agent_end_task'
|
|
1558
|
+
</message_rules>
|
|
905
1559
|
|
|
906
1560
|
|
|
907
1561
|
### Live Development Overlaps
|
|
@@ -924,9 +1578,9 @@ Every diagram MUST render perfectly on first try. No exceptions.
|
|
|
924
1578
|
- **Do not repeat or reply to existing messages.**
|
|
925
1579
|
- Only act if the new message introduces a **new instruction or shifts the current task\u2019s focus**.
|
|
926
1580
|
|
|
927
|
-
|
|
1581
|
+
<reasoning_rules>
|
|
928
1582
|
# YOUR THINKING ON A NOTEBOOK - MANDATORY USE
|
|
929
|
-
CRITICAL: Your laptop (**
|
|
1583
|
+
CRITICAL: Your laptop (**reasoning_nootebook**) is your ORGANIZED MIND
|
|
930
1584
|
## IMPORTANT
|
|
931
1585
|
## NEVER PUT CHECKLISTS OR STEPS IN THE THOUGHT TEXT
|
|
932
1586
|
## ALWAYS USE A NOTEBOOK (Always for):
|
|
@@ -938,7 +1592,7 @@ CRITICAL: Your laptop (**sequentialThinking_nootebook**) is your ORGANIZED MIND
|
|
|
938
1592
|
- Architectural decisions (think through the options)
|
|
939
1593
|
|
|
940
1594
|
## HOW TO USE A NOTEBOOK:
|
|
941
|
-
1. Start with **
|
|
1595
|
+
1. Start with **reasoning_nootebook**
|
|
942
1596
|
2. Break the task down into logical steps
|
|
943
1597
|
3. Plan the approach - Which files? What changes? What order? 4. Track progress - Check off completed steps
|
|
944
1598
|
5. Write down decisions - Why did you choose this approach?
|
|
@@ -978,6 +1632,8 @@ CRITICAL: Your laptop (**sequentialThinking_nootebook**) is your ORGANIZED MIND
|
|
|
978
1632
|
\u{1F5F8} Set up environment
|
|
979
1633
|
[ ] Configure database
|
|
980
1634
|
|
|
1635
|
+
</reasoning_rules>
|
|
1636
|
+
|
|
981
1637
|
### Tool Naming Policy
|
|
982
1638
|
|
|
983
1639
|
Tool names must strictly follow the standard naming format:
|
|
@@ -988,846 +1644,817 @@ Tool names must strictly follow the standard naming format:
|
|
|
988
1644
|
---
|
|
989
1645
|
|
|
990
1646
|
Correct Examples:
|
|
991
|
-
- bluma_notebook
|
|
992
|
-
- getDataTool
|
|
993
|
-
- convertImage
|
|
994
|
-
- userAuth
|
|
995
|
-
|
|
996
|
-
---
|
|
997
|
-
|
|
998
|
-
Incorrect Examples:
|
|
999
|
-
- sequentialThinking_nootebook:0 \u2190 contains colon and dynamic suffix
|
|
1000
|
-
- sequentialThinking_nootebook 1 \u2190 contains space and number
|
|
1001
|
-
- sequentialThinking_nootebook#v2 \u2190 contains special character #
|
|
1002
|
-
- bluma__nootebook \u2190 double underscore
|
|
1003
|
-
- sequentialThinking_Nootebook \u2190 capital letters and underscore
|
|
1004
|
-
- bluma nootebook \u2190 contains space
|
|
1005
|
-
|
|
1006
|
-
---
|
|
1007
|
-
|
|
1008
|
-
Rule Summary:
|
|
1009
|
-
- Use only a\u2013z, 0\u20139, and underscores (_)
|
|
1010
|
-
- Do not append suffixes like :0, :v2, etc.
|
|
1011
|
-
- Tool names must be static and predictable
|
|
1012
|
-
- No whitespace, no dynamic elements, no special characters
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
### EDIT TOOL
|
|
1016
|
-
- Use this tool to perform precise text replacements inside files based on exact literal matches.
|
|
1017
|
-
- Can be used to create new files or directories implicitly by targeting non-existing paths.
|
|
1018
|
-
- Suitable for inserting full content into a file even if the file does not yet exist.
|
|
1019
|
-
- Shell access is not required for file or directory creation when using this tool.
|
|
1020
|
-
- Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
|
|
1021
|
-
- Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
|
|
1022
|
-
- 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.
|
|
1023
|
-
- When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
|
|
1024
|
-
- After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
|
|
1025
|
-
- Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
|
|
1026
|
-
- Track all progress related to planning and execution inside todo.md using text replacement only.
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
Real-Time Developer Messages
|
|
1030
|
-
- During processing, the developer will send you messages.
|
|
1031
|
-
- You MUST respond immediately via message_notify_dev, and be brief. You should use it in your next thoughts/actions.
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
### AGENT END TASK RULES
|
|
1035
|
-
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.
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
### QUALITY STANDARDS
|
|
1039
|
-
- Document every major decision in Notion(
|
|
1040
|
-
##Important: When writing to Notion, you must strictly follow its content structure, including the correct use of headings (heading_1, heading_2, etc.) and other formatting standards. No deviations are allowed.
|
|
1041
|
-
You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
|
|
1042
|
-
semantically better for reading and navigation.
|
|
1043
|
-
)
|
|
1044
|
-
- Communicate transparently at each step
|
|
1045
|
-
- Write clean, well-documented code
|
|
1046
|
-
- Follow existing project conventions
|
|
1047
|
-
- Test implementations when possible
|
|
1048
|
-
- Ensure security and performance
|
|
1049
|
-
|
|
1050
|
-
<scope_and_limitations>
|
|
1051
|
-
# WHAT YOU DON'T HANDLE
|
|
1052
|
-
- Non-technical questions
|
|
1053
|
-
- Personal advice
|
|
1054
|
-
- General conversation
|
|
1055
|
-
- Tasks outside software development
|
|
1056
|
-
|
|
1057
|
-
# IF ASKED NON-TECHNICAL QUESTIONS
|
|
1058
|
-
- Use message_notify_dev to politely decline
|
|
1059
|
-
- Explain you only handle technical/coding tasks
|
|
1060
|
-
- Suggest they ask a development-related question instead
|
|
1061
|
-
</scope_and_limitations>
|
|
1062
|
-
|
|
1063
|
-
`;
|
|
1064
|
-
function getUnifiedSystemPrompt() {
|
|
1065
|
-
const now = /* @__PURE__ */ new Date();
|
|
1066
|
-
const collectedData = {
|
|
1067
|
-
os_type: os2.type(),
|
|
1068
|
-
os_version: os2.release(),
|
|
1069
|
-
architecture: os2.arch(),
|
|
1070
|
-
workdir: process.cwd(),
|
|
1071
|
-
shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
|
|
1072
|
-
username: os2.userInfo().username || "Unknown",
|
|
1073
|
-
current_date: now.toISOString().split("T")[0],
|
|
1074
|
-
// Formato YYYY-MM-DD
|
|
1075
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
|
|
1076
|
-
locale: process.env.LANG || process.env.LC_ALL || "Unknown"
|
|
1077
|
-
};
|
|
1078
|
-
const finalEnv = {
|
|
1079
|
-
os_type: "Unknown",
|
|
1080
|
-
os_version: "Unknown",
|
|
1081
|
-
workdir: "Unknown",
|
|
1082
|
-
shell_type: "Unknown",
|
|
1083
|
-
username: "Unknown",
|
|
1084
|
-
architecture: "Unknown",
|
|
1085
|
-
current_date: "Unknown",
|
|
1086
|
-
timezone: "Unknown",
|
|
1087
|
-
locale: "Unknown",
|
|
1088
|
-
...collectedData
|
|
1089
|
-
// Os dados coletados sobrescrevem os padrões
|
|
1090
|
-
};
|
|
1091
|
-
let formattedPrompt = SYSTEM_PROMPT;
|
|
1092
|
-
for (const key in finalEnv) {
|
|
1093
|
-
const placeholder = `{${key}}`;
|
|
1094
|
-
formattedPrompt = formattedPrompt.replace(new RegExp(placeholder, "g"), finalEnv[key]);
|
|
1095
|
-
}
|
|
1096
|
-
return formattedPrompt;
|
|
1097
|
-
}
|
|
1647
|
+
- bluma_notebook
|
|
1648
|
+
- getDataTool
|
|
1649
|
+
- convertImage
|
|
1650
|
+
- userAuth
|
|
1098
1651
|
|
|
1099
|
-
|
|
1100
|
-
import { promises as fs6 } from "fs";
|
|
1101
|
-
import path5 from "path";
|
|
1102
|
-
import { fileURLToPath } from "url";
|
|
1652
|
+
---
|
|
1103
1653
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
platform: os3.platform(),
|
|
1112
|
-
// Coleta o sistema operacional (ex: 'win32', 'linux')
|
|
1113
|
-
command,
|
|
1114
|
-
cwd,
|
|
1115
|
-
results: []
|
|
1116
|
-
};
|
|
1117
|
-
if (verbose) {
|
|
1118
|
-
report.env = {
|
|
1119
|
-
PATH: process.env.PATH || "NOT SET",
|
|
1120
|
-
ComSpec: process.env.ComSpec || "NOT SET"
|
|
1121
|
-
// Específico do Windows, útil para saber qual cmd está sendo usado
|
|
1122
|
-
};
|
|
1123
|
-
}
|
|
1124
|
-
const childProcess = exec(
|
|
1125
|
-
command,
|
|
1126
|
-
{
|
|
1127
|
-
// O diretório de trabalho para o comando.
|
|
1128
|
-
cwd,
|
|
1129
|
-
// O timeout em milissegundos. Se o comando exceder este tempo, ele será encerrado.
|
|
1130
|
-
timeout: timeout * 1e3,
|
|
1131
|
-
// A opção `shell` foi removida, pois `exec` usa o shell por padrão.
|
|
1132
|
-
// Especificar a codificação garante que a saída seja tratada como texto UTF-8.
|
|
1133
|
-
encoding: "utf-8"
|
|
1134
|
-
},
|
|
1135
|
-
// Este é o callback que será executado QUANDO o processo filho terminar,
|
|
1136
|
-
// seja por sucesso, erro ou timeout.
|
|
1137
|
-
(error, stdout, stderr) => {
|
|
1138
|
-
const result = {
|
|
1139
|
-
method: "child_process.exec",
|
|
1140
|
-
status: "Success",
|
|
1141
|
-
// Se `error` existir, ele contém o código de saída. Caso contrário, o código é 0 (sucesso).
|
|
1142
|
-
code: error ? error.code || null : 0,
|
|
1143
|
-
// Limpa espaços em branco do início e fim das saídas.
|
|
1144
|
-
output: stdout.trim(),
|
|
1145
|
-
error: stderr.trim()
|
|
1146
|
-
};
|
|
1147
|
-
if (error) {
|
|
1148
|
-
if (error.killed) {
|
|
1149
|
-
result.status = "Timeout";
|
|
1150
|
-
result.error = `Command exceeded timeout of ${timeout} seconds. ${stderr.trim()}`.trim();
|
|
1151
|
-
} else {
|
|
1152
|
-
result.status = "Error";
|
|
1153
|
-
result.error = `${error.message}
|
|
1154
|
-
${stderr.trim()}`.trim();
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
if (verbose) {
|
|
1158
|
-
report.results.push(result);
|
|
1159
|
-
resolve(JSON.stringify(report, null, 2));
|
|
1160
|
-
} else {
|
|
1161
|
-
resolve(JSON.stringify(result, null, 2));
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
);
|
|
1165
|
-
});
|
|
1166
|
-
}
|
|
1654
|
+
Incorrect Examples:
|
|
1655
|
+
- reasoning_nootebook:0 \u2190 contains colon and dynamic suffix
|
|
1656
|
+
- reasoning_nootebook 1 \u2190 contains space and number
|
|
1657
|
+
- reasoning_nootebook#v2 \u2190 contains special character #
|
|
1658
|
+
- bluma__nootebook \u2190 double underscore
|
|
1659
|
+
- reasoning_nootebook \u2190 capital letters and underscore
|
|
1660
|
+
- bluma nootebook \u2190 contains space
|
|
1167
1661
|
|
|
1168
|
-
|
|
1169
|
-
import path3 from "path";
|
|
1170
|
-
import { promises as fs2 } from "fs";
|
|
1171
|
-
import { diffLines } from "diff";
|
|
1172
|
-
function unescapeLlmString(inputString) {
|
|
1173
|
-
return inputString.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, "\\");
|
|
1174
|
-
}
|
|
1175
|
-
function ensureCorrectEdit(currentContent, oldString, newString, expectedReplacements) {
|
|
1176
|
-
let finalOldString = oldString;
|
|
1177
|
-
let finalNewString = newString;
|
|
1178
|
-
let occurrences = currentContent.split(finalOldString).length - 1;
|
|
1179
|
-
if (occurrences !== expectedReplacements && occurrences === 0) {
|
|
1180
|
-
const unescapedOldString = unescapeLlmString(oldString);
|
|
1181
|
-
const unescapedOccurrences = currentContent.split(unescapedOldString).length - 1;
|
|
1182
|
-
if (unescapedOccurrences > 0) {
|
|
1183
|
-
finalOldString = unescapedOldString;
|
|
1184
|
-
finalNewString = unescapeLlmString(newString);
|
|
1185
|
-
occurrences = unescapedOccurrences;
|
|
1186
|
-
} else {
|
|
1187
|
-
const trimmedOldString = oldString.trim();
|
|
1188
|
-
const trimmedOccurrences = currentContent.split(trimmedOldString).length - 1;
|
|
1189
|
-
if (trimmedOccurrences > 0) {
|
|
1190
|
-
finalOldString = trimmedOldString;
|
|
1191
|
-
finalNewString = newString.trim();
|
|
1192
|
-
occurrences = trimmedOccurrences;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
return [finalOldString, finalNewString, occurrences];
|
|
1197
|
-
}
|
|
1198
|
-
async function calculateEdit(filePath, oldString, newString, expectedReplacements) {
|
|
1199
|
-
let currentContent = null;
|
|
1200
|
-
let isNewFile = false;
|
|
1201
|
-
let error = null;
|
|
1202
|
-
let finalNewString = unescapeLlmString(newString).replace(/\r\n/g, "\n");
|
|
1203
|
-
let finalOldString = oldString.replace(/\r\n/g, "\n");
|
|
1204
|
-
let occurrences = 0;
|
|
1205
|
-
try {
|
|
1206
|
-
currentContent = await fs2.readFile(filePath, "utf-8");
|
|
1207
|
-
currentContent = currentContent.replace(/\r\n/g, "\n");
|
|
1208
|
-
} catch (e) {
|
|
1209
|
-
if (e.code !== "ENOENT") {
|
|
1210
|
-
error = { display: `Error reading file: ${e.message}`, raw: `Error reading file ${filePath}: ${e.message}` };
|
|
1211
|
-
return { currentContent, newContent: "", occurrences: 0, error, isNewFile };
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
if (currentContent === null) {
|
|
1215
|
-
if (oldString === "") {
|
|
1216
|
-
isNewFile = true;
|
|
1217
|
-
occurrences = 1;
|
|
1218
|
-
} else {
|
|
1219
|
-
error = { display: "File not found. Cannot apply edit. Use an empty old_string to create a new file.", raw: `File not found: ${filePath}` };
|
|
1220
|
-
}
|
|
1221
|
-
} else {
|
|
1222
|
-
if (oldString === "") {
|
|
1223
|
-
error = { display: "Failed to edit. Attempted to create a file that already exists.", raw: `File already exists, cannot create: ${filePath}` };
|
|
1224
|
-
} else {
|
|
1225
|
-
[finalOldString, finalNewString, occurrences] = ensureCorrectEdit(currentContent, finalOldString, finalNewString, expectedReplacements);
|
|
1226
|
-
if (occurrences === 0) {
|
|
1227
|
-
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.` };
|
|
1228
|
-
} else if (occurrences !== expectedReplacements) {
|
|
1229
|
-
error = { display: `Failed to edit, expected ${expectedReplacements} occurrence(s) but found ${occurrences}.`, raw: `Expected ${expectedReplacements} but found ${occurrences} for old_string in ${filePath}` };
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
let newContentResult = "";
|
|
1234
|
-
if (!error) {
|
|
1235
|
-
if (isNewFile) {
|
|
1236
|
-
newContentResult = finalNewString;
|
|
1237
|
-
} else if (currentContent !== null) {
|
|
1238
|
-
newContentResult = currentContent.replaceAll(finalOldString, finalNewString);
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
return { currentContent, newContent: newContentResult, occurrences, error, isNewFile };
|
|
1242
|
-
}
|
|
1243
|
-
function createDiff(filename, oldContent, newContent) {
|
|
1244
|
-
const diff = diffLines(oldContent, newContent, {
|
|
1245
|
-
// `unified: 3` é o padrão para diffs, mostrando 3 linhas de contexto.
|
|
1246
|
-
// `newlineIsToken: true` lida melhor com mudanças de quebra de linha.
|
|
1247
|
-
});
|
|
1248
|
-
let diffString = `--- a/${filename}
|
|
1249
|
-
+++ b/${filename}
|
|
1250
|
-
`;
|
|
1251
|
-
diff.forEach((part) => {
|
|
1252
|
-
const prefix = part.added ? "+" : part.removed ? "-" : " ";
|
|
1253
|
-
part.value.split("\n").slice(0, -1).forEach((line) => {
|
|
1254
|
-
diffString += `${prefix}${line}
|
|
1255
|
-
`;
|
|
1256
|
-
});
|
|
1257
|
-
});
|
|
1258
|
-
return diffString;
|
|
1259
|
-
}
|
|
1260
|
-
async function editTool(args) {
|
|
1261
|
-
const { file_path, old_string, new_string, expected_replacements = 1 } = args;
|
|
1262
|
-
if (!path3.isAbsolute(file_path)) {
|
|
1263
|
-
return { success: false, error: `Invalid parameters: file_path must be absolute.`, file_path };
|
|
1264
|
-
}
|
|
1265
|
-
if (file_path.includes("..")) {
|
|
1266
|
-
return { success: false, error: `Invalid parameters: file_path cannot contain '..'.`, file_path };
|
|
1267
|
-
}
|
|
1268
|
-
try {
|
|
1269
|
-
const editData = await calculateEdit(file_path, old_string, new_string, expected_replacements);
|
|
1270
|
-
if (editData.error) {
|
|
1271
|
-
return {
|
|
1272
|
-
success: false,
|
|
1273
|
-
error: `Execution failed: ${editData.error.display}`,
|
|
1274
|
-
details: editData.error.raw,
|
|
1275
|
-
file_path
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
await fs2.mkdir(path3.dirname(file_path), { recursive: true });
|
|
1279
|
-
await fs2.writeFile(file_path, editData.newContent, "utf-8");
|
|
1280
|
-
const relativePath = path3.relative(process.cwd(), file_path);
|
|
1281
|
-
const filename = path3.basename(file_path);
|
|
1282
|
-
if (editData.isNewFile) {
|
|
1283
|
-
return {
|
|
1284
|
-
success: true,
|
|
1285
|
-
file_path,
|
|
1286
|
-
description: `Created new file: ${relativePath}`,
|
|
1287
|
-
message: `Created new file: ${file_path} with the provided content.`,
|
|
1288
|
-
is_new_file: true,
|
|
1289
|
-
occurrences: editData.occurrences,
|
|
1290
|
-
relative_path: relativePath
|
|
1291
|
-
};
|
|
1292
|
-
} else {
|
|
1293
|
-
const finalDiff = createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
1294
|
-
return {
|
|
1295
|
-
success: true,
|
|
1296
|
-
file_path,
|
|
1297
|
-
description: `Modified ${relativePath} (${editData.occurrences} replacement(s)).`,
|
|
1298
|
-
message: `Successfully modified file: ${file_path}. Diff of changes:
|
|
1299
|
-
${finalDiff}`,
|
|
1300
|
-
is_new_file: false,
|
|
1301
|
-
occurrences: editData.occurrences,
|
|
1302
|
-
relative_path: relativePath
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
|
-
} catch (e) {
|
|
1306
|
-
return {
|
|
1307
|
-
success: false,
|
|
1308
|
-
error: `An unexpected error occurred during the edit operation: ${e.message}`,
|
|
1309
|
-
file_path
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1662
|
+
---
|
|
1313
1663
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1664
|
+
Rule Summary:
|
|
1665
|
+
- Use only a\u2013z, 0\u20139, and underscores (_)
|
|
1666
|
+
- Do not append suffixes like :0, :v2, etc.
|
|
1667
|
+
- Tool names must be static and predictable
|
|
1668
|
+
- No whitespace, no dynamic elements, no special characters
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
<edit_tool_rules>
|
|
1672
|
+
- Use this tool to perform precise text replacements inside files based on exact literal matches.
|
|
1673
|
+
- Can be used to create new files or directories implicitly by targeting non-existing paths.
|
|
1674
|
+
- Suitable for inserting full content into a file even if the file does not yet exist.
|
|
1675
|
+
- Shell access is not required for file or directory creation when using this tool.
|
|
1676
|
+
- Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
|
|
1677
|
+
- Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
|
|
1678
|
+
- 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.
|
|
1679
|
+
- When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
|
|
1680
|
+
- After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
|
|
1681
|
+
- Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
|
|
1682
|
+
- Track all progress related to planning and execution inside todo.md using text replacement only.
|
|
1683
|
+
</edit_tool_rules>
|
|
1684
|
+
|
|
1685
|
+
Real-Time Developer Messages
|
|
1686
|
+
- During processing, the developer will send you messages.
|
|
1687
|
+
- You MUST respond immediately via message_notify_dev, and be brief. You should use it in your next thoughts/actions.
|
|
1688
|
+
|
|
1689
|
+
|
|
1690
|
+
<agent_end_task_rules>
|
|
1691
|
+
This tool is mandatory.
|
|
1692
|
+
You must use it to inform developer {username} that the task has been completed and that there are no further pending actions, in accordance with the objectives defined for the task.
|
|
1693
|
+
</agent_end_task_rules>
|
|
1694
|
+
|
|
1695
|
+
### QUALITY STANDARDS
|
|
1696
|
+
- Document every major decision in Notion(
|
|
1697
|
+
##Important: When writing to Notion, you must strictly follow its content structure, including the correct use of headings (heading_1, heading_2, etc.) and other formatting standards. No deviations are allowed.
|
|
1698
|
+
You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
|
|
1699
|
+
semantically better for reading and navigation.
|
|
1700
|
+
)
|
|
1701
|
+
- Communicate transparently at each step
|
|
1702
|
+
- Write clean, well-documented code
|
|
1703
|
+
- Follow existing project conventions
|
|
1704
|
+
- Test implementations when possible
|
|
1705
|
+
- Ensure security and performance
|
|
1331
1706
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
".venv",
|
|
1339
|
-
"venv",
|
|
1340
|
-
"node_modules",
|
|
1341
|
-
"__pycache__",
|
|
1342
|
-
"*.pyc",
|
|
1343
|
-
".vscode",
|
|
1344
|
-
".idea",
|
|
1345
|
-
"dist",
|
|
1346
|
-
"build",
|
|
1347
|
-
"*.log",
|
|
1348
|
-
".DS_Store"
|
|
1349
|
-
]);
|
|
1350
|
-
async function ls(args) {
|
|
1351
|
-
const {
|
|
1352
|
-
directory_path = ".",
|
|
1353
|
-
recursive = false,
|
|
1354
|
-
ignore_patterns = [],
|
|
1355
|
-
start_index = 0,
|
|
1356
|
-
end_index,
|
|
1357
|
-
show_hidden = false,
|
|
1358
|
-
file_extensions,
|
|
1359
|
-
max_depth
|
|
1360
|
-
} = args;
|
|
1361
|
-
try {
|
|
1362
|
-
const basePath = path4.resolve(directory_path);
|
|
1363
|
-
if (!(await fs3.stat(basePath)).isDirectory()) {
|
|
1364
|
-
throw new Error(`Directory '${directory_path}' not found.`);
|
|
1365
|
-
}
|
|
1366
|
-
const allIgnorePatterns = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore_patterns]);
|
|
1367
|
-
const normalizedExtensions = file_extensions?.map((ext) => ext.toLowerCase());
|
|
1368
|
-
const allFiles = [];
|
|
1369
|
-
const allDirs = [];
|
|
1370
|
-
const walk = async (currentDir, currentDepth) => {
|
|
1371
|
-
if (max_depth !== void 0 && currentDepth > max_depth) return;
|
|
1372
|
-
const entries = await fs3.readdir(currentDir, { withFileTypes: true });
|
|
1373
|
-
for (const entry of entries) {
|
|
1374
|
-
const entryName = entry.name;
|
|
1375
|
-
const fullPath = path4.join(currentDir, entryName);
|
|
1376
|
-
const posixPath = fullPath.split(path4.sep).join("/");
|
|
1377
|
-
const isHidden = entryName.startsWith(".");
|
|
1378
|
-
if (allIgnorePatterns.has(entryName) || isHidden && !show_hidden) {
|
|
1379
|
-
continue;
|
|
1380
|
-
}
|
|
1381
|
-
if (entry.isDirectory()) {
|
|
1382
|
-
allDirs.push(posixPath);
|
|
1383
|
-
if (recursive) {
|
|
1384
|
-
await walk(fullPath, currentDepth + 1);
|
|
1385
|
-
}
|
|
1386
|
-
} else if (entry.isFile()) {
|
|
1387
|
-
if (!normalizedExtensions || normalizedExtensions.includes(path4.extname(entryName).toLowerCase())) {
|
|
1388
|
-
allFiles.push(posixPath);
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
};
|
|
1393
|
-
await walk(basePath, 0);
|
|
1394
|
-
allFiles.sort();
|
|
1395
|
-
allDirs.sort();
|
|
1396
|
-
return {
|
|
1397
|
-
success: true,
|
|
1398
|
-
path: basePath.split(path4.sep).join("/"),
|
|
1399
|
-
recursive,
|
|
1400
|
-
total_files: allFiles.length,
|
|
1401
|
-
total_directories: allDirs.length,
|
|
1402
|
-
showing_files: `[${start_index}:${Math.min(end_index ?? allFiles.length, allFiles.length)}]`,
|
|
1403
|
-
showing_directories: `[${start_index}:${Math.min(end_index ?? allDirs.length, allDirs.length)}]`,
|
|
1404
|
-
files: allFiles.slice(start_index, end_index),
|
|
1405
|
-
directories: allDirs.slice(start_index, end_index),
|
|
1406
|
-
filters_applied: { ignore_patterns: [...allIgnorePatterns], show_hidden, file_extensions, max_depth }
|
|
1407
|
-
};
|
|
1408
|
-
} catch (e) {
|
|
1409
|
-
return { success: false, error: e.message };
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1707
|
+
<scope_and_limitations>
|
|
1708
|
+
# WHAT YOU DON'T HANDLE
|
|
1709
|
+
- Non-technical questions
|
|
1710
|
+
- Personal advice
|
|
1711
|
+
- General conversation
|
|
1712
|
+
- Tasks outside software development
|
|
1412
1713
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
if (!(await fs4.stat(filepath)).isFile()) {
|
|
1419
|
-
throw new Error(`File '${filepath}' not found or is not a file.`);
|
|
1420
|
-
}
|
|
1421
|
-
if (start_line < 1 || end_line < start_line) {
|
|
1422
|
-
throw new Error("Invalid line range. start_line must be >= 1 and end_line must be >= start_line.");
|
|
1423
|
-
}
|
|
1424
|
-
const fileContent = await fs4.readFile(filepath, "utf-8");
|
|
1425
|
-
const lines = fileContent.split("\n");
|
|
1426
|
-
const total_lines = lines.length;
|
|
1427
|
-
const startIndex = start_line - 1;
|
|
1428
|
-
let endIndex = end_line;
|
|
1429
|
-
if (startIndex >= total_lines) {
|
|
1430
|
-
throw new Error(`start_line (${start_line}) exceeds file length (${total_lines} lines).`);
|
|
1431
|
-
}
|
|
1432
|
-
endIndex = Math.min(endIndex, total_lines);
|
|
1433
|
-
const contentLines = lines.slice(startIndex, endIndex);
|
|
1434
|
-
const content = contentLines.join("\n");
|
|
1435
|
-
return {
|
|
1436
|
-
success: true,
|
|
1437
|
-
filepath,
|
|
1438
|
-
content,
|
|
1439
|
-
lines_read: contentLines.length,
|
|
1440
|
-
start_line,
|
|
1441
|
-
end_line: endIndex,
|
|
1442
|
-
// Retorna o final real usado
|
|
1443
|
-
total_file_lines: total_lines
|
|
1444
|
-
};
|
|
1445
|
-
} catch (e) {
|
|
1446
|
-
return { success: false, error: e.message };
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1714
|
+
# IF ASKED NON-TECHNICAL QUESTIONS
|
|
1715
|
+
- Use message_notify_dev to politely decline
|
|
1716
|
+
- Explain you only handle technical/coding tasks
|
|
1717
|
+
- Suggest they ask a development-related question instead
|
|
1718
|
+
</scope_and_limitations>
|
|
1449
1719
|
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1720
|
+
`;
|
|
1721
|
+
function getUnifiedSystemPrompt() {
|
|
1722
|
+
const now = /* @__PURE__ */ new Date();
|
|
1723
|
+
const collectedData = {
|
|
1724
|
+
os_type: os4.type(),
|
|
1725
|
+
os_version: os4.release(),
|
|
1726
|
+
architecture: os4.arch(),
|
|
1727
|
+
workdir: process.cwd(),
|
|
1728
|
+
shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
|
|
1729
|
+
username: os4.userInfo().username || "Unknown",
|
|
1730
|
+
current_date: now.toISOString().split("T")[0],
|
|
1731
|
+
// Formato YYYY-MM-DD
|
|
1732
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
|
|
1733
|
+
locale: process.env.LANG || process.env.LC_ALL || "Unknown"
|
|
1734
|
+
};
|
|
1735
|
+
const finalEnv = {
|
|
1736
|
+
os_type: "Unknown",
|
|
1737
|
+
os_version: "Unknown",
|
|
1738
|
+
workdir: "Unknown",
|
|
1739
|
+
shell_type: "Unknown",
|
|
1740
|
+
username: "Unknown",
|
|
1741
|
+
architecture: "Unknown",
|
|
1742
|
+
current_date: "Unknown",
|
|
1743
|
+
timezone: "Unknown",
|
|
1744
|
+
locale: "Unknown",
|
|
1745
|
+
...collectedData
|
|
1746
|
+
// Os dados coletados sobrescrevem os padrões
|
|
1747
|
+
};
|
|
1748
|
+
let formattedPrompt = SYSTEM_PROMPT;
|
|
1749
|
+
for (const key in finalEnv) {
|
|
1750
|
+
const placeholder = `{${key}}`;
|
|
1751
|
+
formattedPrompt = formattedPrompt.replace(new RegExp(placeholder, "g"), finalEnv[key]);
|
|
1472
1752
|
}
|
|
1753
|
+
return formattedPrompt;
|
|
1473
1754
|
}
|
|
1474
1755
|
|
|
1475
|
-
// src/app/agent/
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
// Propriedade privada para armazenar as definições de ferramentas carregadas do JSON.
|
|
1480
|
-
toolDefinitions = [];
|
|
1481
|
-
constructor() {
|
|
1482
|
-
this.toolImplementations = /* @__PURE__ */ new Map();
|
|
1483
|
-
this.registerTools();
|
|
1484
|
-
}
|
|
1485
|
-
/**
|
|
1486
|
-
* Carrega as definições de ferramentas do arquivo de configuração `native_tools.json`.
|
|
1487
|
-
* Este método é assíncrono e deve ser chamado após a criação da instância.
|
|
1488
|
-
*/
|
|
1489
|
-
async initialize() {
|
|
1490
|
-
try {
|
|
1491
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
1492
|
-
const __dirname = path5.dirname(__filename);
|
|
1493
|
-
const configPath = path5.resolve(__dirname, "config", "native_tools.json");
|
|
1494
|
-
const fileContent = await fs6.readFile(configPath, "utf-8");
|
|
1495
|
-
const config2 = JSON.parse(fileContent);
|
|
1496
|
-
this.toolDefinitions = config2.nativeTools;
|
|
1497
|
-
} catch (error) {
|
|
1498
|
-
console.error("[ToolInvoker] Erro cr\xEDtico ao carregar 'native_tools.json'. As ferramentas nativas n\xE3o estar\xE3o dispon\xEDveis.", error);
|
|
1499
|
-
this.toolDefinitions = [];
|
|
1500
|
-
}
|
|
1756
|
+
// src/app/agent/core/context-api/context_manager.ts
|
|
1757
|
+
function createApiContextWindow(fullHistory, maxTurns) {
|
|
1758
|
+
if (!fullHistory.length) {
|
|
1759
|
+
return [];
|
|
1501
1760
|
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
* Este método mapeia o nome da ferramenta (string) para a função TypeScript que a executa.
|
|
1505
|
-
*/
|
|
1506
|
-
registerTools() {
|
|
1507
|
-
this.toolImplementations.set("shell_command", shellCommand);
|
|
1508
|
-
this.toolImplementations.set("edit_tool", editTool);
|
|
1509
|
-
this.toolImplementations.set("message_notify_dev", messageNotifyDev);
|
|
1510
|
-
this.toolImplementations.set("ls_tool", ls);
|
|
1511
|
-
this.toolImplementations.set("count_file_lines", countLines);
|
|
1512
|
-
this.toolImplementations.set("read_file_lines", readLines);
|
|
1513
|
-
this.toolImplementations.set("agent_end_task", async () => ({ success: true, message: "Task ended by agent." }));
|
|
1761
|
+
if (maxTurns === null || maxTurns === void 0) {
|
|
1762
|
+
return [...fullHistory];
|
|
1514
1763
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
return this.toolDefinitions;
|
|
1764
|
+
const systemMessages = [];
|
|
1765
|
+
let historyStartIndex = 0;
|
|
1766
|
+
while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
|
|
1767
|
+
systemMessages.push(fullHistory[historyStartIndex]);
|
|
1768
|
+
historyStartIndex++;
|
|
1521
1769
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1770
|
+
const conversationHistory = fullHistory.slice(historyStartIndex);
|
|
1771
|
+
const turns = [];
|
|
1772
|
+
let currentTurn = [];
|
|
1773
|
+
let turnsFound = 0;
|
|
1774
|
+
const isDevOverlay = (m) => m?.role === "user" && m?.name === "dev_overlay";
|
|
1775
|
+
for (let i = conversationHistory.length - 1; i >= 0; i--) {
|
|
1776
|
+
const msg = conversationHistory[i];
|
|
1777
|
+
currentTurn.unshift(msg);
|
|
1778
|
+
const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some((tc) => tc.function.name === "agent_end_task");
|
|
1779
|
+
if (endsWithAgentEnd) {
|
|
1780
|
+
turns.unshift([...currentTurn]);
|
|
1781
|
+
currentTurn = [];
|
|
1782
|
+
turnsFound++;
|
|
1783
|
+
if (turnsFound >= maxTurns) {
|
|
1784
|
+
break;
|
|
1785
|
+
}
|
|
1786
|
+
continue;
|
|
1532
1787
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1788
|
+
const prev = conversationHistory[i - 1];
|
|
1789
|
+
if (msg.role === "user" && !isDevOverlay(msg)) {
|
|
1790
|
+
if (prev && prev.role === "assistant" && !prev.tool_calls?.some((tc) => tc.function.name === "agent_end_task")) {
|
|
1791
|
+
const hasNonOverlay = currentTurn.some((m) => m.role !== "user" || !isDevOverlay(m));
|
|
1792
|
+
if (hasNonOverlay) {
|
|
1793
|
+
turns.unshift([...currentTurn]);
|
|
1794
|
+
currentTurn = [];
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1538
1797
|
}
|
|
1539
1798
|
}
|
|
1540
|
-
|
|
1799
|
+
if (currentTurn.length > 0) {
|
|
1800
|
+
turns.unshift(currentTurn);
|
|
1801
|
+
}
|
|
1802
|
+
const finalContext = systemMessages.concat(turns.flat());
|
|
1803
|
+
return finalContext;
|
|
1804
|
+
}
|
|
1541
1805
|
|
|
1542
|
-
// src/app/agent/
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
var MCPClient = class {
|
|
1550
|
-
sessions = /* @__PURE__ */ new Map();
|
|
1551
|
-
toolToServerMap = /* @__PURE__ */ new Map();
|
|
1552
|
-
globalToolsForLlm = [];
|
|
1553
|
-
nativeToolInvoker;
|
|
1806
|
+
// src/app/agent/bluma/core/bluma.ts
|
|
1807
|
+
var BluMaAgent = class {
|
|
1808
|
+
llm;
|
|
1809
|
+
deploymentName;
|
|
1810
|
+
sessionId;
|
|
1811
|
+
sessionFile = "";
|
|
1812
|
+
history = [];
|
|
1554
1813
|
eventBus;
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1814
|
+
mcpClient;
|
|
1815
|
+
feedbackSystem;
|
|
1816
|
+
maxContextTurns = 300;
|
|
1817
|
+
isInterrupted = false;
|
|
1818
|
+
constructor(sessionId2, eventBus2, llm, deploymentName, mcpClient, feedbackSystem) {
|
|
1819
|
+
this.sessionId = sessionId2;
|
|
1558
1820
|
this.eventBus = eventBus2;
|
|
1821
|
+
this.llm = llm;
|
|
1822
|
+
this.deploymentName = deploymentName;
|
|
1823
|
+
this.mcpClient = mcpClient;
|
|
1824
|
+
this.feedbackSystem = feedbackSystem;
|
|
1825
|
+
this.eventBus.on("user_interrupt", () => {
|
|
1826
|
+
this.isInterrupted = true;
|
|
1827
|
+
});
|
|
1828
|
+
this.eventBus.on("dev_overlay", async (data) => {
|
|
1829
|
+
const clean = String(data.payload ?? "").trim();
|
|
1830
|
+
this.history.push({ role: "user", name: "dev_overlay", content: clean });
|
|
1831
|
+
this.eventBus.emit("backend_message", { type: "dev_overlay", payload: clean, ts: data.ts || Date.now() });
|
|
1832
|
+
try {
|
|
1833
|
+
if (this.sessionFile) {
|
|
1834
|
+
await saveSessionHistory(this.sessionFile, this.history);
|
|
1835
|
+
}
|
|
1836
|
+
} catch (e) {
|
|
1837
|
+
this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s dev_overlay: ${e.message}` });
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1559
1840
|
}
|
|
1560
|
-
// ... (método initialize inalterado) ...
|
|
1561
1841
|
async initialize() {
|
|
1562
|
-
|
|
1563
|
-
this.
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
});
|
|
1842
|
+
await this.mcpClient.nativeToolInvoker.initialize();
|
|
1843
|
+
await this.mcpClient.initialize();
|
|
1844
|
+
const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
|
|
1845
|
+
this.sessionFile = sessionFile;
|
|
1846
|
+
this.history = history;
|
|
1847
|
+
if (this.history.length === 0) {
|
|
1848
|
+
const systemPrompt = getUnifiedSystemPrompt();
|
|
1849
|
+
this.history.push({ role: "system", content: systemPrompt });
|
|
1850
|
+
await saveSessionHistory(this.sessionFile, this.history);
|
|
1570
1851
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1852
|
+
}
|
|
1853
|
+
getAvailableTools() {
|
|
1854
|
+
return this.mcpClient.getAvailableTools();
|
|
1855
|
+
}
|
|
1856
|
+
getUiToolsDetailed() {
|
|
1857
|
+
return this.mcpClient.getAvailableToolsDetailed();
|
|
1858
|
+
}
|
|
1859
|
+
async processTurn(userInput) {
|
|
1860
|
+
this.isInterrupted = false;
|
|
1861
|
+
const inputText = String(userInput.content || "").trim();
|
|
1862
|
+
this.history.push({ role: "user", content: inputText });
|
|
1863
|
+
await this._continueConversation();
|
|
1864
|
+
}
|
|
1865
|
+
async handleToolResponse(decisionData) {
|
|
1866
|
+
const toolCall = decisionData.tool_calls[0];
|
|
1867
|
+
let toolResultContent;
|
|
1868
|
+
let shouldContinueConversation = true;
|
|
1869
|
+
if (!this.sessionFile) {
|
|
1870
|
+
const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
|
|
1871
|
+
this.sessionFile = sessionFile;
|
|
1872
|
+
if (this.history.length === 0 && history.length > 0) {
|
|
1873
|
+
this.history = history;
|
|
1581
1874
|
}
|
|
1582
|
-
};
|
|
1583
|
-
if (Object.keys(mergedConfig.mcpServers).length === 0) {
|
|
1584
|
-
return;
|
|
1585
1875
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1876
|
+
if (decisionData.type === "user_decision_execute") {
|
|
1877
|
+
const toolName = toolCall.function.name;
|
|
1878
|
+
const toolArgs = JSON.parse(toolCall.function.arguments);
|
|
1879
|
+
let previewContent;
|
|
1880
|
+
if (toolName === "edit_tool") {
|
|
1881
|
+
previewContent = await this._generateEditPreview(toolArgs);
|
|
1882
|
+
}
|
|
1883
|
+
this.eventBus.emit("backend_message", {
|
|
1884
|
+
type: "tool_call",
|
|
1885
|
+
tool_name: toolName,
|
|
1886
|
+
arguments: toolArgs,
|
|
1887
|
+
preview: previewContent
|
|
1888
|
+
});
|
|
1588
1889
|
try {
|
|
1589
|
-
this.
|
|
1590
|
-
type: "
|
|
1591
|
-
|
|
1592
|
-
});
|
|
1593
|
-
if (serverConf.type === "stdio") {
|
|
1594
|
-
await this.connectToStdioServer(serverName, serverConf);
|
|
1595
|
-
} else if (serverConf.type === "sse") {
|
|
1596
|
-
console.warn(`[MCPClient] Conex\xE3o com servidores SSE (como '${serverName}') ainda n\xE3o implementada.`);
|
|
1890
|
+
if (this.isInterrupted) {
|
|
1891
|
+
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
|
|
1892
|
+
return;
|
|
1597
1893
|
}
|
|
1894
|
+
const result = await this.mcpClient.invoke(toolName, toolArgs);
|
|
1895
|
+
let finalResult = result;
|
|
1896
|
+
if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
|
|
1897
|
+
finalResult = result[0].text;
|
|
1898
|
+
}
|
|
1899
|
+
toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
|
|
1598
1900
|
} catch (error) {
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1901
|
+
toolResultContent = JSON.stringify({
|
|
1902
|
+
error: `Tool execution failed: ${error.message}`,
|
|
1903
|
+
details: error.data || "No additional details."
|
|
1602
1904
|
});
|
|
1603
1905
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
const fileContent = await fs7.readFile(configPath, "utf-8");
|
|
1609
|
-
const processedContent = this.replaceEnvPlaceholders(fileContent);
|
|
1610
|
-
return JSON.parse(processedContent);
|
|
1611
|
-
} catch (error) {
|
|
1612
|
-
if (error.code === "ENOENT") {
|
|
1613
|
-
if (configType === "User") {
|
|
1614
|
-
}
|
|
1615
|
-
} else {
|
|
1616
|
-
console.warn(`[MCPClient] Warning: Error reading ${configType} config file ${configPath}.`, error);
|
|
1617
|
-
}
|
|
1618
|
-
return {};
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
/**
|
|
1622
|
-
* Conecta-se a um servidor MCP baseado em Stdio, adaptando o comando para o SO atual.
|
|
1623
|
-
*/
|
|
1624
|
-
async connectToStdioServer(serverName, config2) {
|
|
1625
|
-
let commandToExecute = config2.command;
|
|
1626
|
-
let argsToExecute = config2.args || [];
|
|
1627
|
-
const isWindows = os4.platform() === "win32";
|
|
1628
|
-
if (!isWindows && commandToExecute.toLowerCase() === "cmd") {
|
|
1629
|
-
if (argsToExecute.length >= 2 && argsToExecute[0].toLowerCase() === "/c") {
|
|
1630
|
-
commandToExecute = argsToExecute[1];
|
|
1631
|
-
argsToExecute = argsToExecute.slice(2);
|
|
1632
|
-
} else {
|
|
1633
|
-
console.warn(`[MCPClient] Formato de comando 'cmd /c' inesperado para '${serverName}' em sistema n\xE3o-Windows. O servidor ser\xE1 ignorado.`);
|
|
1634
|
-
return;
|
|
1906
|
+
this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
|
|
1907
|
+
if (toolName.includes("agent_end_task")) {
|
|
1908
|
+
shouldContinueConversation = false;
|
|
1909
|
+
this.eventBus.emit("backend_message", { type: "done", status: "completed" });
|
|
1635
1910
|
}
|
|
1636
|
-
}
|
|
1637
|
-
const transport = new StdioClientTransport({
|
|
1638
|
-
command: commandToExecute,
|
|
1639
|
-
// Usa o comando adaptado
|
|
1640
|
-
args: argsToExecute,
|
|
1641
|
-
// Usa os argumentos adaptados
|
|
1642
|
-
env: config2.env
|
|
1643
|
-
});
|
|
1644
|
-
const mcp = new Client({ name: `bluma-cli-client-for-${serverName}`, version: "1.0.0" });
|
|
1645
|
-
await mcp.connect(transport);
|
|
1646
|
-
this.sessions.set(serverName, mcp);
|
|
1647
|
-
const toolsResult = await mcp.listTools();
|
|
1648
|
-
for (const tool of toolsResult.tools) {
|
|
1649
|
-
const prefixedToolName = `${serverName}_${tool.name}`;
|
|
1650
|
-
this.globalToolsForLlm.push({
|
|
1651
|
-
type: "function",
|
|
1652
|
-
function: {
|
|
1653
|
-
name: prefixedToolName,
|
|
1654
|
-
description: tool.description || "",
|
|
1655
|
-
parameters: tool.inputSchema
|
|
1656
|
-
}
|
|
1657
|
-
});
|
|
1658
|
-
this.toolToServerMap.set(prefixedToolName, {
|
|
1659
|
-
server: serverName,
|
|
1660
|
-
originalName: tool.name
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
async invoke(toolName, args) {
|
|
1665
|
-
const route = this.toolToServerMap.get(toolName);
|
|
1666
|
-
if (!route) {
|
|
1667
|
-
return { error: `Ferramenta '${toolName}' n\xE3o encontrada ou registrada.` };
|
|
1668
|
-
}
|
|
1669
|
-
if (route.server === "native") {
|
|
1670
|
-
return this.nativeToolInvoker.invoke(route.originalName, args);
|
|
1671
1911
|
} else {
|
|
1672
|
-
|
|
1673
|
-
if (!session) {
|
|
1674
|
-
return { error: `Sess\xE3o para o servidor '${route.server}' n\xE3o encontrada.` };
|
|
1675
|
-
}
|
|
1676
|
-
const result = await session.callTool({ name: route.originalName, arguments: args });
|
|
1677
|
-
return result.content;
|
|
1912
|
+
toolResultContent = "The user declined to execute this tool...";
|
|
1678
1913
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
// New: detailed list for UI with origin metadata
|
|
1684
|
-
getAvailableToolsDetailed() {
|
|
1685
|
-
const detailed = [];
|
|
1686
|
-
for (const tool of this.globalToolsForLlm) {
|
|
1687
|
-
const name = tool.function?.name;
|
|
1688
|
-
if (!name) continue;
|
|
1689
|
-
const route = this.toolToServerMap.get(name);
|
|
1690
|
-
if (!route) continue;
|
|
1691
|
-
const source = route.server === "native" ? "native" : "mcp";
|
|
1692
|
-
detailed.push({ ...tool, source, server: route.server, originalName: route.originalName });
|
|
1914
|
+
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
1915
|
+
await saveSessionHistory(this.sessionFile, this.history);
|
|
1916
|
+
if (shouldContinueConversation && !this.isInterrupted) {
|
|
1917
|
+
await this._continueConversation();
|
|
1693
1918
|
}
|
|
1694
|
-
return detailed;
|
|
1695
1919
|
}
|
|
1696
|
-
async
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1920
|
+
async _generateEditPreview(toolArgs) {
|
|
1921
|
+
try {
|
|
1922
|
+
const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
|
|
1923
|
+
if (editData.error) {
|
|
1924
|
+
return `Failed to generate diff:
|
|
1925
|
+
|
|
1926
|
+
${editData.error.display}`;
|
|
1927
|
+
}
|
|
1928
|
+
const filename = path7.basename(toolArgs.file_path);
|
|
1929
|
+
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
1930
|
+
} catch (e) {
|
|
1931
|
+
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
async _continueConversation() {
|
|
1935
|
+
try {
|
|
1936
|
+
if (this.isInterrupted) {
|
|
1937
|
+
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
|
|
1941
|
+
const response = await this.llm.chatCompletion({
|
|
1942
|
+
model: this.deploymentName,
|
|
1943
|
+
messages: contextWindow,
|
|
1944
|
+
tools: this.mcpClient.getAvailableTools(),
|
|
1945
|
+
tool_choice: "required",
|
|
1946
|
+
parallel_tool_calls: false
|
|
1947
|
+
});
|
|
1948
|
+
if (this.isInterrupted) {
|
|
1949
|
+
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
const message = response.choices[0].message;
|
|
1953
|
+
this.history.push(message);
|
|
1954
|
+
if (message.tool_calls) {
|
|
1955
|
+
const autoApprovedTools = ["agent_end_task", "message_notify_dev", "reasoning_nootebook"];
|
|
1956
|
+
const toolToCall = message.tool_calls[0];
|
|
1957
|
+
const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
|
|
1958
|
+
if (isSafeTool) {
|
|
1959
|
+
await this.handleToolResponse({ type: "user_decision_execute", tool_calls: message.tool_calls });
|
|
1960
|
+
} else {
|
|
1961
|
+
const toolName = toolToCall.function.name;
|
|
1962
|
+
if (toolName === "edit_tool") {
|
|
1963
|
+
const args = JSON.parse(toolToCall.function.arguments);
|
|
1964
|
+
const previewContent = await this._generateEditPreview(args);
|
|
1965
|
+
this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: message.tool_calls, preview: previewContent });
|
|
1966
|
+
} else {
|
|
1967
|
+
this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: message.tool_calls });
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
} else if (message.content) {
|
|
1971
|
+
this.eventBus.emit("backend_message", { type: "assistant_message", content: message.content });
|
|
1972
|
+
const feedback = this.feedbackSystem.generateFeedback({
|
|
1973
|
+
event: "protocol_violation_direct_text",
|
|
1974
|
+
details: { violationContent: message.content }
|
|
1975
|
+
});
|
|
1976
|
+
this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.message, content: message.content });
|
|
1977
|
+
this.history.push({ role: "system", content: feedback.correction });
|
|
1978
|
+
await this._continueConversation();
|
|
1979
|
+
} else {
|
|
1980
|
+
this.eventBus.emit("backend_message", { type: "info", message: "Agent is thinking... continuing reasoning cycle." });
|
|
1981
|
+
await this._continueConversation();
|
|
1702
1982
|
}
|
|
1983
|
+
} catch (error) {
|
|
1984
|
+
const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
|
|
1985
|
+
this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
|
|
1986
|
+
} finally {
|
|
1987
|
+
await saveSessionHistory(this.sessionFile, this.history);
|
|
1703
1988
|
}
|
|
1704
1989
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1992
|
+
// src/app/agent/core/llm.ts
|
|
1993
|
+
var OpenAIAdapter = class {
|
|
1994
|
+
client;
|
|
1995
|
+
constructor(client) {
|
|
1996
|
+
this.client = client;
|
|
1997
|
+
}
|
|
1998
|
+
async chatCompletion(params) {
|
|
1999
|
+
const resp = await this.client.chat.completions.create({
|
|
2000
|
+
model: params.model,
|
|
2001
|
+
messages: params.messages,
|
|
2002
|
+
tools: params.tools,
|
|
2003
|
+
tool_choice: params.tool_choice,
|
|
2004
|
+
parallel_tool_calls: params.parallel_tool_calls
|
|
1708
2005
|
});
|
|
2006
|
+
return resp;
|
|
1709
2007
|
}
|
|
1710
2008
|
};
|
|
1711
2009
|
|
|
1712
|
-
// src/app/agent/
|
|
1713
|
-
var
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
* @param event O evento a ser avaliado.
|
|
1718
|
-
* @returns Um objeto com a pontuação, mensagem e correção.
|
|
1719
|
-
*/
|
|
1720
|
-
generateFeedback(event) {
|
|
1721
|
-
if (event.event === "protocol_violation_direct_text") {
|
|
1722
|
-
const penalty = -2.5;
|
|
1723
|
-
this.cumulativeScore += penalty;
|
|
1724
|
-
return {
|
|
1725
|
-
score: penalty,
|
|
1726
|
-
message: "Direct text response is a protocol violation. All communication must be done via the 'message_notify_dev' tool.",
|
|
1727
|
-
correction: `
|
|
1728
|
-
## PROTOCOL VIOLATION \u2014 SEVERE
|
|
1729
|
-
You sent a direct text response, which is strictly prohibited.
|
|
1730
|
-
PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
|
|
1731
|
-
You MUST use tools for all actions and communication.
|
|
1732
|
-
`.trim()
|
|
1733
|
-
};
|
|
1734
|
-
}
|
|
1735
|
-
return { score: 0, message: "No feedback for this event.", correction: "" };
|
|
2010
|
+
// src/app/agent/subagents/registry.ts
|
|
2011
|
+
var subAgentRegistry = {};
|
|
2012
|
+
function registerSubAgent(subAgent) {
|
|
2013
|
+
for (const cap of subAgent.capabilities) {
|
|
2014
|
+
subAgentRegistry[cap] = subAgent;
|
|
1736
2015
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
2016
|
+
}
|
|
2017
|
+
function getSubAgentByCommand(cmd) {
|
|
2018
|
+
return subAgentRegistry[cmd];
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// src/app/agent/subagents/init/init_system_prompt.ts
|
|
2022
|
+
import os5 from "os";
|
|
2023
|
+
var SYSTEM_PROMPT2 = `
|
|
2024
|
+
|
|
2025
|
+
### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
|
|
2026
|
+
You extend the BluMa multi-agent architecture and handle the project bootstrapping/init workflow: scanning the repository, inferring stack, and generating a high-quality BluMa.md with actionable project context.
|
|
2027
|
+
|
|
2028
|
+
---
|
|
2029
|
+
|
|
2030
|
+
## BEHAVIORAL RULES
|
|
2031
|
+
|
|
2032
|
+
- Identity:
|
|
2033
|
+
You are BluMa InitSubAgent. Maintain professionalism and technical language.
|
|
2034
|
+
|
|
2035
|
+
- Communication:
|
|
2036
|
+
ALL messages must be sent via 'message_notify_dev'.
|
|
2037
|
+
No direct text replies to the developer.
|
|
2038
|
+
|
|
2039
|
+
- Task Completion:
|
|
2040
|
+
When the init task is completed, immediately invoke 'agent_end_task' without dev permissions.
|
|
2041
|
+
|
|
2042
|
+
- Tool Rules:
|
|
2043
|
+
Never make parallel tool calls.
|
|
2044
|
+
Only use the defined tools with their exact names.
|
|
2045
|
+
|
|
2046
|
+
- Autonomy:
|
|
2047
|
+
Act 100% autonomously.
|
|
2048
|
+
Do not ask for formatting preferences.
|
|
2049
|
+
Use the notebook for internal reasoning.
|
|
2050
|
+
|
|
2051
|
+
|
|
2052
|
+
### CRITICAL COMMUNICATION PROTOCOL
|
|
2053
|
+
- Only tool_calls are allowed for assistant replies. Never include a "content" field.
|
|
2054
|
+
- Always use tools to respond, retrieve data, compute or transform. Await a valid tool response before any final message.
|
|
2055
|
+
- Zero tolerance for protocol violations.
|
|
2056
|
+
|
|
2057
|
+
<current_system_environment>
|
|
2058
|
+
- Operating System: {os_type} ({os_version})
|
|
2059
|
+
- Architecture: {architecture}
|
|
2060
|
+
- Current Working Directory: {workdir}
|
|
2061
|
+
- Shell: {shell_type}
|
|
2062
|
+
- Username: {username}
|
|
2063
|
+
- Current Date: {current_date}
|
|
2064
|
+
- Timezone: {timezone}
|
|
2065
|
+
- Locale: {locale}
|
|
2066
|
+
</current_system_environment>
|
|
2067
|
+
|
|
2068
|
+
<message_rules>
|
|
2069
|
+
- Communicate with dev's via message tools instead of direct text responses
|
|
2070
|
+
- Reply immediately to new user messages before other operations
|
|
2071
|
+
- First reply must be brief, only confirming receipt without specific solutions
|
|
2072
|
+
- Notify dev's with brief explanation when changing methods or strategies
|
|
2073
|
+
- Message tools are divided into notify (non-blocking, no reply needed) and ask (blocking)
|
|
2074
|
+
- Actively use notify for progress updates, reserve ask for essential needs to avoid blocking
|
|
2075
|
+
- Must message dev's with results and deliverables before upon task completion 'agent_end_task'
|
|
2076
|
+
</message_rules>
|
|
2077
|
+
|
|
2078
|
+
<reasoning_rules>
|
|
2079
|
+
# YOUR THINKING ON A NOTEBOOK - MANDATORY USE
|
|
2080
|
+
CRITICAL: Your laptop (reasoning_nootebook) is your ORGANIZED MIND
|
|
2081
|
+
## IMPORTANT
|
|
2082
|
+
## NEVER PUT CHECKLISTS OR STEPS IN THE THOUGHT TEXT
|
|
2083
|
+
## ALWAYS USE A NOTEBOOK (Always for):
|
|
2084
|
+
- ANY task
|
|
2085
|
+
- Before starting development (plan first!)
|
|
2086
|
+
- Projects with multiple files (organize the structure)
|
|
2087
|
+
- Debugging sessions (monitor discoveries)
|
|
2088
|
+
- Extensive refactoring (map the changes)
|
|
2089
|
+
- Architectural decisions (think through the options)
|
|
2090
|
+
|
|
2091
|
+
## HOW TO USE A NOTEBOOK:
|
|
2092
|
+
1. Start with reasoning_nootebook
|
|
2093
|
+
2. Break the task down into logical steps
|
|
2094
|
+
3. Plan the approach \u2013 Which files? What changes? What order?
|
|
2095
|
+
4. Track progress \u2013 Check off completed steps
|
|
2096
|
+
5. Write down decisions \u2013 Why did you choose this approach?
|
|
2097
|
+
6. Update continuously \u2013 Keep the notebook up to date
|
|
2098
|
+
|
|
2099
|
+
## THE NOTEBOOK PREVENTS:
|
|
2100
|
+
- Acting "outside the box"
|
|
2101
|
+
- Forgetting task requirements
|
|
2102
|
+
- Losing control of complex workflows
|
|
2103
|
+
- Making unplanned changes
|
|
2104
|
+
- Ineffective approaches
|
|
2105
|
+
- Working without a clear roadmap
|
|
2106
|
+
- Jumping between unrelated subtasks
|
|
2107
|
+
|
|
2108
|
+
Important rule:
|
|
2109
|
+
Do not include future steps/to-dos in thought; put them strictly in remaining_tasks, using the mandated checklist markers.
|
|
2110
|
+
|
|
2111
|
+
- remaining_tasks: Checklist list of high-level upcoming tasks.
|
|
2112
|
+
Format is mandatory:
|
|
2113
|
+
- "\u{1F5F8}" \u2192 for tasks not yet done (pending)
|
|
2114
|
+
- "[ ]" \u2192 for tasks already completed
|
|
2115
|
+
</reasoning_rules>
|
|
2116
|
+
|
|
2117
|
+
<edit_tool_rules>
|
|
2118
|
+
- Use this tool to perform precise text replacements inside files based on exact literal matches.
|
|
2119
|
+
- Can be used to create new files or directories implicitly by targeting non-existing paths.
|
|
2120
|
+
- Suitable for inserting full content into a file even if the file does not yet exist.
|
|
2121
|
+
- Shell access is not required for file or directory creation when using this tool.
|
|
2122
|
+
- Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
|
|
2123
|
+
- Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
|
|
2124
|
+
- 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.
|
|
2125
|
+
- When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
|
|
2126
|
+
- After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
|
|
2127
|
+
- Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
|
|
2128
|
+
- Track all progress related to planning and execution inside todo.md using text replacement only.
|
|
2129
|
+
</edit_tool_rules>
|
|
2130
|
+
|
|
2131
|
+
|
|
2132
|
+
### Tool Naming Policy
|
|
2133
|
+
- Use plain, unmodified, lowercase tool names
|
|
2134
|
+
- No special characters, spaces, or version suffixes
|
|
2135
|
+
|
|
2136
|
+
Rule Summary:
|
|
2137
|
+
- Use only a\u2013z, 0\u20139, and underscores (_)
|
|
2138
|
+
- Do not append suffixes like :0, :v2, etc.
|
|
2139
|
+
- Tool names must be static and predictable
|
|
2140
|
+
|
|
2141
|
+
|
|
2142
|
+
## INIT SUBAGENT OBJECTIVE
|
|
2143
|
+
- Map repository structure and significant files.
|
|
2144
|
+
- Infer tech stack (frameworks, package managers, languages, build/test tools).
|
|
2145
|
+
- Identify entry points, configuration files, and scripts.
|
|
2146
|
+
- Produce BluMa.md with:
|
|
2147
|
+
- Project overview and goals inferred from code/docs
|
|
2148
|
+
- Tech stack summary
|
|
2149
|
+
- Directory map (high-level)
|
|
2150
|
+
- Key configs and scripts
|
|
2151
|
+
- Known tasks or next steps for agents
|
|
2152
|
+
- Always use tools (ls, readLines, count_lines, shell_command, edit_tool) to gather evidence before writing.
|
|
2153
|
+
- Never invent file content. Read files via tools to confirm.
|
|
2154
|
+
|
|
2155
|
+
## OUTPUT & PROTOCOLS
|
|
2156
|
+
- Emit 'backend_message' events through tools only (message_notify_dev) for progress updates.
|
|
2157
|
+
- Before writing BluMa.md, propose structure via message_notify_dev and proceed using edit_tool.
|
|
2158
|
+
- If an irreversible operation is needed (e.g., overwriting an existing BluMa.md), issue 'confirmation_request' unless dev policy indicates auto-approval.
|
|
2159
|
+
- Never send or present draft versions of BluMa.md. Only produce and deliver the final, validated BluMa.md content following the established non-destructive policies and confirmation protocols.
|
|
2160
|
+
- On successful generation of BluMa.md, emit 'done' with status 'completed' and call agent_end_task.
|
|
2161
|
+
|
|
2162
|
+
## SAFETY & QUALITY
|
|
2163
|
+
- Be conservative with edits; generate previews (diff) for edit_tool where applicable.
|
|
2164
|
+
- Keep file system operations idempotent and explicit.
|
|
2165
|
+
- Prefer performance-efficient scans (avoid reading entire large binaries).
|
|
2166
|
+
- Respect test environment constraints.
|
|
2167
|
+
|
|
2168
|
+
## EXEMPLAR FLOW (GUIDELINE)
|
|
2169
|
+
1) Explore repo: ls + targeted readLines for key files (package.json, tsconfig.json, README, etc.)
|
|
2170
|
+
2) Synthesize stack and structure with citations of evidence (file paths) in the notebook
|
|
2171
|
+
3) Draft BluMa.md structure (message_notify_dev)
|
|
2172
|
+
4) Write BluMa.md via edit_tool
|
|
2173
|
+
5) Announce completion and agent_end_task
|
|
2174
|
+
|
|
2175
|
+
|
|
2176
|
+
`;
|
|
2177
|
+
function getInitPrompt() {
|
|
2178
|
+
const now = /* @__PURE__ */ new Date();
|
|
2179
|
+
const collectedData = {
|
|
2180
|
+
os_type: os5.type(),
|
|
2181
|
+
os_version: os5.release(),
|
|
2182
|
+
architecture: os5.arch(),
|
|
2183
|
+
workdir: process.cwd(),
|
|
2184
|
+
shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
|
|
2185
|
+
username: os5.userInfo().username || "Unknown",
|
|
2186
|
+
current_date: now.toISOString().split("T")[0],
|
|
2187
|
+
// Formato YYYY-MM-DD
|
|
2188
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
|
|
2189
|
+
locale: process.env.LANG || process.env.LC_ALL || "Unknown"
|
|
2190
|
+
};
|
|
2191
|
+
const finalEnv = {
|
|
2192
|
+
os_type: "Unknown",
|
|
2193
|
+
os_version: "Unknown",
|
|
2194
|
+
workdir: "Unknown",
|
|
2195
|
+
shell_type: "Unknown",
|
|
2196
|
+
username: "Unknown",
|
|
2197
|
+
architecture: "Unknown",
|
|
2198
|
+
current_date: "Unknown",
|
|
2199
|
+
timezone: "Unknown",
|
|
2200
|
+
locale: "Unknown",
|
|
2201
|
+
...collectedData
|
|
2202
|
+
// Os dados coletados sobrescrevem os padrões
|
|
2203
|
+
};
|
|
2204
|
+
let formattedPrompt = SYSTEM_PROMPT2;
|
|
2205
|
+
for (const key in finalEnv) {
|
|
2206
|
+
const placeholder = `{${key}}`;
|
|
2207
|
+
formattedPrompt = formattedPrompt.replace(new RegExp(placeholder, "g"), finalEnv[key]);
|
|
1739
2208
|
}
|
|
1740
|
-
|
|
2209
|
+
return formattedPrompt;
|
|
2210
|
+
}
|
|
1741
2211
|
|
|
1742
|
-
// src/app/agent/
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
2212
|
+
// src/app/agent/subagents/base_llm_subagent.ts
|
|
2213
|
+
var BaseLLMSubAgent = class {
|
|
2214
|
+
ctx;
|
|
2215
|
+
history = [];
|
|
2216
|
+
sessionFile = "";
|
|
2217
|
+
maxContextTurns = 160;
|
|
2218
|
+
isInterrupted = false;
|
|
2219
|
+
async execute(input, ctx) {
|
|
2220
|
+
this.ctx = ctx;
|
|
2221
|
+
this.isInterrupted = false;
|
|
2222
|
+
this.ctx.eventBus.on("user_interrupt", () => {
|
|
2223
|
+
this.isInterrupted = true;
|
|
2224
|
+
});
|
|
2225
|
+
await this.initializeHistory();
|
|
2226
|
+
this.history.push({ role: "user", content: typeof input === "string" ? input : JSON.stringify(input) });
|
|
2227
|
+
await this._continueConversation();
|
|
2228
|
+
return { history: this.history };
|
|
1746
2229
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
2230
|
+
async initializeHistory() {
|
|
2231
|
+
const sessionId2 = `${this.id}`;
|
|
2232
|
+
const [sessionFile, history] = await loadOrcreateSession(sessionId2);
|
|
2233
|
+
this.sessionFile = sessionFile;
|
|
2234
|
+
this.history = history || [];
|
|
2235
|
+
const systemPromptContent = getInitPrompt();
|
|
2236
|
+
if (this.history.length === 0) {
|
|
2237
|
+
this.history.push({
|
|
2238
|
+
role: "system",
|
|
2239
|
+
content: systemPromptContent
|
|
2240
|
+
});
|
|
2241
|
+
await saveSessionHistory(this.sessionFile, this.history);
|
|
2242
|
+
}
|
|
1749
2243
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
2244
|
+
async _generateEditPreview(toolArgs) {
|
|
2245
|
+
try {
|
|
2246
|
+
const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
|
|
2247
|
+
if (editData.error) {
|
|
2248
|
+
return `Failed to generate diff:
|
|
2249
|
+
|
|
2250
|
+
${editData.error.display}`;
|
|
2251
|
+
}
|
|
2252
|
+
const filename = toolArgs.file_path?.split(/[\/]/).pop() || "file";
|
|
2253
|
+
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
2254
|
+
} catch (e) {
|
|
2255
|
+
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
2256
|
+
}
|
|
1755
2257
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
2258
|
+
async _continueConversation() {
|
|
2259
|
+
try {
|
|
2260
|
+
if (this.isInterrupted) {
|
|
2261
|
+
this.emitEvent("info", { message: "SubAgent task cancelled by user." });
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
const contextWindow = this.history.slice(-this.maxContextTurns);
|
|
2265
|
+
const response = await this.ctx.llm.chatCompletion({
|
|
2266
|
+
model: this.ctx.policy?.llmDeployment || "default",
|
|
2267
|
+
messages: contextWindow,
|
|
2268
|
+
tools: this.ctx.mcpClient.getAvailableTools(),
|
|
2269
|
+
tool_choice: "required",
|
|
2270
|
+
parallel_tool_calls: false
|
|
2271
|
+
});
|
|
2272
|
+
if (this.isInterrupted) {
|
|
2273
|
+
this.emitEvent("info", { message: "SubAgent task cancelled by user." });
|
|
2274
|
+
return;
|
|
1771
2275
|
}
|
|
1772
|
-
|
|
2276
|
+
const message = response.choices[0].message;
|
|
2277
|
+
this.history.push(message);
|
|
2278
|
+
if (message.tool_calls) {
|
|
2279
|
+
await this._handleToolExecution({ type: "user_decision_execute", tool_calls: message.tool_calls });
|
|
2280
|
+
const lastToolName = message.tool_calls[0].function.name;
|
|
2281
|
+
if (!lastToolName.includes("agent_end_task") && !this.isInterrupted) {
|
|
2282
|
+
await this._continueConversation();
|
|
2283
|
+
}
|
|
2284
|
+
} else if (message.content) {
|
|
2285
|
+
this.emitEvent("assistant_message", { content: message.content });
|
|
2286
|
+
this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content: message.content });
|
|
2287
|
+
} else {
|
|
2288
|
+
this.emitEvent("info", { message: "SubAgent is thinking... continuing reasoning cycle." });
|
|
2289
|
+
}
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
const errorMessage = error?.message || "An unknown API error occurred.";
|
|
2292
|
+
this.emitEvent("error", { message: errorMessage });
|
|
2293
|
+
} finally {
|
|
2294
|
+
if (this.sessionFile) await saveSessionHistory(this.sessionFile, this.history);
|
|
1773
2295
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
2296
|
+
}
|
|
2297
|
+
// MÉTODO REMOVIDO
|
|
2298
|
+
// public async handleConfirmation(decisionData: { type: string; tool_calls: any[] }) {
|
|
2299
|
+
// await this._handleToolExecution(decisionData);
|
|
2300
|
+
// if (!this.isInterrupted) {
|
|
2301
|
+
// await this._continueConversation();
|
|
2302
|
+
// }
|
|
2303
|
+
// }
|
|
2304
|
+
async _handleToolExecution(decisionData) {
|
|
2305
|
+
const toolCall = decisionData.tool_calls[0];
|
|
2306
|
+
let toolResultContent;
|
|
2307
|
+
if (decisionData.type === "user_decision_execute") {
|
|
2308
|
+
const toolName = toolCall.function.name;
|
|
2309
|
+
const toolArgs = JSON.parse(toolCall.function.arguments);
|
|
2310
|
+
let previewContent;
|
|
2311
|
+
if (toolName === "edit_tool") {
|
|
2312
|
+
previewContent = await this._generateEditPreview(toolArgs);
|
|
2313
|
+
}
|
|
2314
|
+
this.emitEvent("tool_call", {
|
|
2315
|
+
tool_name: toolName,
|
|
2316
|
+
arguments: toolArgs,
|
|
2317
|
+
preview: previewContent
|
|
2318
|
+
});
|
|
2319
|
+
try {
|
|
2320
|
+
if (this.isInterrupted) {
|
|
2321
|
+
this.emitEvent("info", { message: "SubAgent task cancelled before tool execution." });
|
|
2322
|
+
toolResultContent = JSON.stringify({ error: "Task cancelled by user before execution." });
|
|
2323
|
+
} else {
|
|
2324
|
+
const result = await this.ctx.mcpClient.invoke(toolName, toolArgs);
|
|
2325
|
+
let finalResult = result;
|
|
2326
|
+
if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
|
|
2327
|
+
finalResult = result[0].text;
|
|
2328
|
+
}
|
|
2329
|
+
toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
|
|
1781
2330
|
}
|
|
2331
|
+
} catch (error) {
|
|
2332
|
+
toolResultContent = JSON.stringify({ error: `Tool execution failed: ${error.message}`, details: error.data || "No additional details." });
|
|
1782
2333
|
}
|
|
2334
|
+
this.emitEvent("tool_result", { tool_name: toolName, result: toolResultContent });
|
|
2335
|
+
if (toolName.includes("agent_end_task")) {
|
|
2336
|
+
this.emitEvent("done", { status: "completed" });
|
|
2337
|
+
}
|
|
2338
|
+
} else {
|
|
2339
|
+
toolResultContent = "Tool execution was declined.";
|
|
1783
2340
|
}
|
|
2341
|
+
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
1784
2342
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
2343
|
+
// Utilitários: emitir eventos, chamar tools, etc
|
|
2344
|
+
emitEvent(type, payload) {
|
|
2345
|
+
this.ctx.eventBus.emit("backend_message", { type, ...payload });
|
|
1787
2346
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
2347
|
+
};
|
|
2348
|
+
|
|
2349
|
+
// src/app/agent/subagents/init/init_subagent.ts
|
|
2350
|
+
var InitAgentImpl = class extends BaseLLMSubAgent {
|
|
2351
|
+
id = "init_subagent";
|
|
2352
|
+
capabilities = ["/init"];
|
|
2353
|
+
async execute(input, ctx) {
|
|
2354
|
+
this.ctx = ctx;
|
|
2355
|
+
this.isInterrupted = false;
|
|
2356
|
+
this.ctx.eventBus.on("user_interrupt", () => {
|
|
2357
|
+
this.isInterrupted = true;
|
|
2358
|
+
});
|
|
2359
|
+
await this.initializeHistory();
|
|
2360
|
+
const seed = `
|
|
2361
|
+
Scan the current project repository comprehensively.
|
|
2362
|
+
Map the directory structure (excluding heavy/ignored folders), infer the technology stack from manifests and configs, identify entry points and useful scripts, and analyze module relationships and architectural patterns.
|
|
2363
|
+
Then draft a concise, actionable BluMa.md that includes: project overview, detected stack, a summarized directory tree with brief annotations, key configs and scripts, useful CLI commands, and relevant operational notes for BluMa.
|
|
2364
|
+
Use only evidence gathered via tools; do not invent content.
|
|
2365
|
+
If overwriting an existing BluMa.md is required, follow non-destructive policies and request confirmation as per protocol.
|
|
2366
|
+
`;
|
|
2367
|
+
const combined = seed;
|
|
2368
|
+
this.history.push({ role: "user", content: combined });
|
|
2369
|
+
await this._continueConversation();
|
|
2370
|
+
return { history: this.history };
|
|
2371
|
+
}
|
|
2372
|
+
};
|
|
2373
|
+
var InitSubAgent = new InitAgentImpl();
|
|
2374
|
+
|
|
2375
|
+
// src/app/agent/subagents/subagents_bluma.ts
|
|
2376
|
+
registerSubAgent(InitSubAgent);
|
|
2377
|
+
var SubAgentsBluMa = class {
|
|
2378
|
+
eventBus;
|
|
2379
|
+
mcpClient;
|
|
2380
|
+
toolInvoker;
|
|
2381
|
+
llm;
|
|
2382
|
+
deploymentName;
|
|
2383
|
+
projectRoot;
|
|
2384
|
+
policy;
|
|
2385
|
+
logger;
|
|
2386
|
+
constructor(params) {
|
|
2387
|
+
this.eventBus = params.eventBus;
|
|
2388
|
+
this.mcpClient = params.mcpClient;
|
|
2389
|
+
this.toolInvoker = params.toolInvoker;
|
|
2390
|
+
this.llm = params.llm;
|
|
2391
|
+
this.deploymentName = params.deploymentName;
|
|
2392
|
+
this.projectRoot = params.projectRoot || process.cwd();
|
|
2393
|
+
this.policy = params.policy;
|
|
2394
|
+
this.logger = params.logger;
|
|
2395
|
+
}
|
|
2396
|
+
// Recebe dados do front (ex.: { content: inputText } vindo de /init)
|
|
2397
|
+
// e faz o roteamento para o subagente adequado com base no comando.
|
|
2398
|
+
async registerAndDispatch(frontPayload) {
|
|
2399
|
+
const { command, content, ...rest } = frontPayload || {};
|
|
2400
|
+
const resolvedCommand = this.resolveCommand({ command, content });
|
|
2401
|
+
if (!resolvedCommand) {
|
|
2402
|
+
this.emit("error", { message: "Nenhum comando/subagente correspondente encontrado." });
|
|
2403
|
+
return { ok: false, error: "unknown_command" };
|
|
2404
|
+
}
|
|
2405
|
+
const subAgent = getSubAgentByCommand(resolvedCommand);
|
|
2406
|
+
if (!subAgent) {
|
|
2407
|
+
this.emit("error", { message: `Subagente n\xE3o registrado para ${resolvedCommand}` });
|
|
2408
|
+
return { ok: false, error: "unregistered_subagent" };
|
|
2409
|
+
}
|
|
2410
|
+
const ctx = {
|
|
2411
|
+
projectRoot: this.projectRoot,
|
|
2412
|
+
eventBus: this.eventBus,
|
|
2413
|
+
mcpClient: this.mcpClient,
|
|
2414
|
+
toolInvoker: this.toolInvoker,
|
|
2415
|
+
llm: this.llm,
|
|
2416
|
+
policy: { llmDeployment: this.deploymentName, ...this.policy || {} },
|
|
2417
|
+
logger: this.logger
|
|
2418
|
+
};
|
|
2419
|
+
const inputForAgent = content ?? JSON.stringify({ content, ...rest });
|
|
2420
|
+
this.emit("info", { message: `[SubAgentsBluMa] Dispatch -> ${resolvedCommand}` });
|
|
2421
|
+
return subAgent.execute(inputForAgent, ctx);
|
|
2422
|
+
}
|
|
2423
|
+
// Estratégia simples: se o payload explicitamente vier de /init, use "/init".
|
|
2424
|
+
// Caso haja command explícito, usa-o. Caso contrário, heurística com base no conteúdo.
|
|
2425
|
+
resolveCommand(payload) {
|
|
2426
|
+
if (payload.command && typeof payload.command === "string") return payload.command;
|
|
2427
|
+
const text = String(payload.content || "").trim();
|
|
2428
|
+
if (!text) return "/init";
|
|
2429
|
+
if (text.startsWith("/")) return text.split(/\s+/)[0];
|
|
2430
|
+
return "/init";
|
|
2431
|
+
}
|
|
2432
|
+
emit(type, data) {
|
|
2433
|
+
this.eventBus.emit("backend_message", { type, ...data });
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
1791
2436
|
|
|
1792
2437
|
// src/app/agent/agent.ts
|
|
1793
|
-
var globalEnvPath =
|
|
2438
|
+
var globalEnvPath = path8.join(os6.homedir(), ".bluma-cli", ".env");
|
|
1794
2439
|
dotenv.config({ path: globalEnvPath });
|
|
1795
2440
|
var Agent = class {
|
|
1796
|
-
client;
|
|
1797
|
-
deploymentName;
|
|
1798
2441
|
sessionId;
|
|
1799
|
-
sessionFile = "";
|
|
1800
|
-
history = [];
|
|
1801
2442
|
eventBus;
|
|
1802
2443
|
mcpClient;
|
|
1803
2444
|
feedbackSystem;
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
2445
|
+
// Mantido caso UI dependa de eventos
|
|
2446
|
+
llm;
|
|
2447
|
+
deploymentName;
|
|
2448
|
+
core;
|
|
2449
|
+
// Delegado
|
|
2450
|
+
subAgents;
|
|
2451
|
+
// Orquestrador de subagentes
|
|
2452
|
+
toolInvoker;
|
|
1808
2453
|
constructor(sessionId2, eventBus2) {
|
|
1809
2454
|
this.sessionId = sessionId2;
|
|
1810
2455
|
this.eventBus = eventBus2;
|
|
1811
|
-
this.eventBus.on("user_interrupt", () => {
|
|
1812
|
-
this.isInterrupted = true;
|
|
1813
|
-
});
|
|
1814
|
-
this.eventBus.on("dev_overlay", async (data) => {
|
|
1815
|
-
const clean = String(data.payload ?? "").trim();
|
|
1816
|
-
this.history.push({ role: "user", name: "dev_overlay", content: clean });
|
|
1817
|
-
this.eventBus.emit("backend_message", {
|
|
1818
|
-
type: "dev_overlay",
|
|
1819
|
-
payload: clean,
|
|
1820
|
-
ts: data.ts || Date.now()
|
|
1821
|
-
});
|
|
1822
|
-
try {
|
|
1823
|
-
if (this.sessionFile) {
|
|
1824
|
-
await saveSessionHistory(this.sessionFile, this.history);
|
|
1825
|
-
}
|
|
1826
|
-
} catch (e) {
|
|
1827
|
-
this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s dev_overlay: ${e.message}` });
|
|
1828
|
-
}
|
|
1829
|
-
});
|
|
1830
2456
|
const nativeToolInvoker = new ToolInvoker();
|
|
2457
|
+
this.toolInvoker = nativeToolInvoker;
|
|
1831
2458
|
this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
|
|
1832
2459
|
this.feedbackSystem = new AdvancedFeedbackSystem();
|
|
1833
2460
|
const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
|
|
@@ -1838,201 +2465,59 @@ var Agent = class {
|
|
|
1838
2465
|
const errorMessage = `Uma ou mais vari\xE1veis de ambiente Azure OpenAI n\xE3o foram encontradas. Verifique em: ${globalEnvPath} ou nas vari\xE1veis de sistema.`;
|
|
1839
2466
|
throw new Error(errorMessage);
|
|
1840
2467
|
}
|
|
1841
|
-
|
|
2468
|
+
const openai = new OpenAI({
|
|
1842
2469
|
// Configuração do cliente OpenAI hospedado no Azure
|
|
1843
2470
|
apiKey,
|
|
1844
2471
|
baseURL: `${endpoint}/openai/deployments/${this.deploymentName}`,
|
|
1845
2472
|
defaultQuery: { "api-version": apiVersion },
|
|
1846
2473
|
defaultHeaders: { "api-key": apiKey }
|
|
1847
2474
|
});
|
|
2475
|
+
this.llm = new OpenAIAdapter(openai);
|
|
2476
|
+
this.core = new BluMaAgent(
|
|
2477
|
+
this.sessionId,
|
|
2478
|
+
this.eventBus,
|
|
2479
|
+
this.llm,
|
|
2480
|
+
this.deploymentName,
|
|
2481
|
+
this.mcpClient,
|
|
2482
|
+
this.feedbackSystem
|
|
2483
|
+
);
|
|
2484
|
+
this.subAgents = new SubAgentsBluMa({
|
|
2485
|
+
eventBus: this.eventBus,
|
|
2486
|
+
mcpClient: this.mcpClient,
|
|
2487
|
+
toolInvoker: this.toolInvoker,
|
|
2488
|
+
llm: this.llm,
|
|
2489
|
+
deploymentName: this.deploymentName,
|
|
2490
|
+
projectRoot: process.cwd()
|
|
2491
|
+
});
|
|
1848
2492
|
}
|
|
1849
|
-
/**
|
|
1850
|
-
* Inicializa o agente, carregando ou criando uma sessão e preparando o histórico.
|
|
1851
|
-
* Também inicializa o MCPClient e o ToolInvoker.
|
|
1852
|
-
*/
|
|
1853
2493
|
async initialize() {
|
|
1854
|
-
await this.
|
|
1855
|
-
await this.mcpClient.initialize();
|
|
1856
|
-
const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
|
|
1857
|
-
this.sessionFile = sessionFile;
|
|
1858
|
-
this.history = history;
|
|
1859
|
-
if (this.history.length === 0) {
|
|
1860
|
-
let systemPrompt = getUnifiedSystemPrompt();
|
|
1861
|
-
this.history.push({ role: "system", content: systemPrompt });
|
|
1862
|
-
await saveSessionHistory(this.sessionFile, this.history);
|
|
1863
|
-
}
|
|
1864
|
-
this.isInitialized = true;
|
|
2494
|
+
await this.core.initialize();
|
|
1865
2495
|
}
|
|
1866
2496
|
getAvailableTools() {
|
|
1867
|
-
return this.
|
|
2497
|
+
return this.core.getAvailableTools();
|
|
1868
2498
|
}
|
|
1869
|
-
// UI helper: detailed tools with origin metadata
|
|
1870
2499
|
getUiToolsDetailed() {
|
|
1871
|
-
return this.
|
|
2500
|
+
return this.core.getUiToolsDetailed();
|
|
1872
2501
|
}
|
|
1873
2502
|
async processTurn(userInput) {
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2503
|
+
const inputText = String(userInput.content || "").trim();
|
|
2504
|
+
if (inputText === "/init" || inputText.startsWith("/init ")) {
|
|
2505
|
+
await this.dispatchToSubAgent({ command: "/init", content: inputText });
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
await this.core.processTurn({ content: inputText });
|
|
1877
2509
|
}
|
|
1878
|
-
/**
|
|
1879
|
-
* Lida com a decisão do usuário (aceitar/recusar) sobre uma chamada de ferramenta.
|
|
1880
|
-
* Garante que uma mensagem de 'role: tool' seja sempre adicionada ao histórico.
|
|
1881
|
-
*/
|
|
1882
2510
|
async handleToolResponse(decisionData) {
|
|
1883
|
-
|
|
1884
|
-
let toolResultContent;
|
|
1885
|
-
let shouldContinueConversation = true;
|
|
1886
|
-
if (!this.sessionFile) {
|
|
1887
|
-
const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
|
|
1888
|
-
this.sessionFile = sessionFile;
|
|
1889
|
-
if (this.history.length === 0 && history.length > 0) {
|
|
1890
|
-
this.history = history;
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
if (decisionData.type === "user_decision_execute") {
|
|
1894
|
-
const toolName = toolCall.function.name;
|
|
1895
|
-
const toolArgs = JSON.parse(toolCall.function.arguments);
|
|
1896
|
-
let previewContent;
|
|
1897
|
-
if (toolName === "edit_tool") {
|
|
1898
|
-
previewContent = await this._generateEditPreview(toolArgs);
|
|
1899
|
-
}
|
|
1900
|
-
this.eventBus.emit("backend_message", {
|
|
1901
|
-
type: "tool_call",
|
|
1902
|
-
tool_name: toolName,
|
|
1903
|
-
arguments: toolArgs,
|
|
1904
|
-
preview: previewContent
|
|
1905
|
-
});
|
|
1906
|
-
try {
|
|
1907
|
-
if (this.isInterrupted) {
|
|
1908
|
-
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
|
|
1909
|
-
return;
|
|
1910
|
-
}
|
|
1911
|
-
const result = await this.mcpClient.invoke(toolName, toolArgs);
|
|
1912
|
-
let finalResult = result;
|
|
1913
|
-
if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
|
|
1914
|
-
finalResult = result[0].text;
|
|
1915
|
-
}
|
|
1916
|
-
toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
|
|
1917
|
-
} catch (error) {
|
|
1918
|
-
toolResultContent = JSON.stringify({
|
|
1919
|
-
error: `Tool execution failed: ${error.message}`,
|
|
1920
|
-
details: error.data || "No additional details."
|
|
1921
|
-
});
|
|
1922
|
-
}
|
|
1923
|
-
this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
|
|
1924
|
-
if (toolName.includes("agent_end_task")) {
|
|
1925
|
-
shouldContinueConversation = false;
|
|
1926
|
-
this.eventBus.emit("backend_message", { type: "done", status: "completed" });
|
|
1927
|
-
}
|
|
1928
|
-
} else {
|
|
1929
|
-
toolResultContent = "The user declined to execute this tool...";
|
|
1930
|
-
}
|
|
1931
|
-
this.history.push({
|
|
1932
|
-
role: "tool",
|
|
1933
|
-
tool_call_id: toolCall.id,
|
|
1934
|
-
content: toolResultContent
|
|
1935
|
-
});
|
|
1936
|
-
await saveSessionHistory(this.sessionFile, this.history);
|
|
1937
|
-
if (shouldContinueConversation && !this.isInterrupted) {
|
|
1938
|
-
await this._continueConversation();
|
|
1939
|
-
}
|
|
2511
|
+
await this.core.handleToolResponse(decisionData);
|
|
1940
2512
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
*/
|
|
1945
|
-
// Adicione este método dentro da classe Agent
|
|
1946
|
-
async _generateEditPreview(toolArgs) {
|
|
1947
|
-
try {
|
|
1948
|
-
const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
|
|
1949
|
-
if (editData.error) {
|
|
1950
|
-
return `Failed to generate diff:
|
|
1951
|
-
|
|
1952
|
-
${editData.error.display}`;
|
|
1953
|
-
}
|
|
1954
|
-
const filename = path7.basename(toolArgs.file_path);
|
|
1955
|
-
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
1956
|
-
} catch (e) {
|
|
1957
|
-
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
1958
|
-
}
|
|
2513
|
+
// Novo: processamento via SubAgents (ex.: payload vindo do front /init)
|
|
2514
|
+
async dispatchToSubAgent(payload) {
|
|
2515
|
+
return this.subAgents.registerAndDispatch(payload);
|
|
1959
2516
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
return;
|
|
1965
|
-
}
|
|
1966
|
-
const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
|
|
1967
|
-
const response = await this.client.chat.completions.create({
|
|
1968
|
-
model: this.deploymentName,
|
|
1969
|
-
messages: contextWindow,
|
|
1970
|
-
tools: this.mcpClient.getAvailableTools(),
|
|
1971
|
-
tool_choice: "required",
|
|
1972
|
-
parallel_tool_calls: false
|
|
1973
|
-
});
|
|
1974
|
-
if (this.isInterrupted) {
|
|
1975
|
-
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
|
|
1976
|
-
return;
|
|
1977
|
-
}
|
|
1978
|
-
const message = response.choices[0].message;
|
|
1979
|
-
this.history.push(message);
|
|
1980
|
-
if (message.tool_calls) {
|
|
1981
|
-
const autoApprovedTools = [
|
|
1982
|
-
"agent_end_task",
|
|
1983
|
-
"message_notify_dev",
|
|
1984
|
-
"sequentialThinking_nootebook"
|
|
1985
|
-
];
|
|
1986
|
-
const toolToCall = message.tool_calls[0];
|
|
1987
|
-
const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
|
|
1988
|
-
if (isSafeTool) {
|
|
1989
|
-
await this.handleToolResponse({ type: "user_decision_execute", tool_calls: message.tool_calls });
|
|
1990
|
-
} else {
|
|
1991
|
-
const toolName = toolToCall.function.name;
|
|
1992
|
-
if (toolName === "edit_tool") {
|
|
1993
|
-
const args = JSON.parse(toolToCall.function.arguments);
|
|
1994
|
-
const previewContent = await this._generateEditPreview(args);
|
|
1995
|
-
this.eventBus.emit("backend_message", {
|
|
1996
|
-
type: "confirmation_request",
|
|
1997
|
-
tool_calls: message.tool_calls,
|
|
1998
|
-
preview: previewContent
|
|
1999
|
-
});
|
|
2000
|
-
} else {
|
|
2001
|
-
this.eventBus.emit("backend_message", {
|
|
2002
|
-
type: "confirmation_request",
|
|
2003
|
-
tool_calls: message.tool_calls
|
|
2004
|
-
});
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
} else if (message.content) {
|
|
2008
|
-
this.eventBus.emit("backend_message", {
|
|
2009
|
-
type: "assistant_message",
|
|
2010
|
-
content: message.content
|
|
2011
|
-
});
|
|
2012
|
-
const feedback = this.feedbackSystem.generateFeedback({
|
|
2013
|
-
event: "protocol_violation_direct_text",
|
|
2014
|
-
details: { violationContent: message.content }
|
|
2015
|
-
});
|
|
2016
|
-
this.eventBus.emit("backend_message", {
|
|
2017
|
-
type: "protocol_violation",
|
|
2018
|
-
message: feedback.message,
|
|
2019
|
-
content: message.content
|
|
2020
|
-
});
|
|
2021
|
-
this.history.push({
|
|
2022
|
-
role: "system",
|
|
2023
|
-
content: feedback.correction
|
|
2024
|
-
});
|
|
2025
|
-
await this._continueConversation();
|
|
2026
|
-
} else {
|
|
2027
|
-
this.eventBus.emit("backend_message", { type: "info", message: "Agent is thinking... continuing reasoning cycle." });
|
|
2028
|
-
await this._continueConversation();
|
|
2029
|
-
}
|
|
2030
|
-
} catch (error) {
|
|
2031
|
-
const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
|
|
2032
|
-
this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
|
|
2033
|
-
} finally {
|
|
2034
|
-
await saveSessionHistory(this.sessionFile, this.history);
|
|
2035
|
-
}
|
|
2517
|
+
// Compat: snapshot imutável do histórico para testes/UI (não expõe referência mutável)
|
|
2518
|
+
getHistorySnapshot() {
|
|
2519
|
+
const coreHistory = this.core.history;
|
|
2520
|
+
return coreHistory ? coreHistory.map((m) => ({ ...m })) : [];
|
|
2036
2521
|
}
|
|
2037
2522
|
};
|
|
2038
2523
|
|
|
@@ -2068,7 +2553,7 @@ import { Box as Box9 } from "ink";
|
|
|
2068
2553
|
|
|
2069
2554
|
// src/app/ui/components/toolCallRenderers.tsx
|
|
2070
2555
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
2071
|
-
import
|
|
2556
|
+
import path9 from "path";
|
|
2072
2557
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2073
2558
|
var formatArgumentsForDisplay = (args) => {
|
|
2074
2559
|
if (typeof args === "string") {
|
|
@@ -2101,7 +2586,7 @@ var renderLsTool2 = ({ args }) => {
|
|
|
2101
2586
|
} catch (e) {
|
|
2102
2587
|
directoryPath = "Error parsing arguments";
|
|
2103
2588
|
}
|
|
2104
|
-
const finalDirectoryName =
|
|
2589
|
+
const finalDirectoryName = path9.basename(directoryPath);
|
|
2105
2590
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2106
2591
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2107
2592
|
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
|
|
@@ -2121,7 +2606,7 @@ var renderCountFilesLines = ({ args }) => {
|
|
|
2121
2606
|
} catch (e) {
|
|
2122
2607
|
directoryPath = "Error parsing arguments";
|
|
2123
2608
|
}
|
|
2124
|
-
const finalDirectoryName =
|
|
2609
|
+
const finalDirectoryName = path9.basename(directoryPath);
|
|
2125
2610
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2126
2611
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2127
2612
|
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
|
|
@@ -2145,7 +2630,7 @@ var renderReadFileLines2 = ({ args }) => {
|
|
|
2145
2630
|
} catch (e) {
|
|
2146
2631
|
filepath = "Error parsing arguments";
|
|
2147
2632
|
}
|
|
2148
|
-
const finalFileName =
|
|
2633
|
+
const finalFileName = path9.basename(filepath);
|
|
2149
2634
|
return (
|
|
2150
2635
|
// A caixa externa com a borda, seguindo o template
|
|
2151
2636
|
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
@@ -2183,20 +2668,13 @@ var renderBlumaNotebook = ({ args }) => {
|
|
|
2183
2668
|
return (
|
|
2184
2669
|
// Usamos a mesma estrutura de caixa com borda
|
|
2185
2670
|
/* @__PURE__ */ jsxs8(Box8, { borderStyle: "round", borderColor: "green", flexDirection: "column", paddingX: 1, children: [
|
|
2186
|
-
/* @__PURE__ */
|
|
2187
|
-
/* @__PURE__ */ jsx8(Text8, {
|
|
2188
|
-
"Thinking Process"
|
|
2189
|
-
] }) }),
|
|
2190
|
-
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2191
|
-
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Thought:" }),
|
|
2671
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2672
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Thought:" }),
|
|
2192
2673
|
/* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { color: "gray", children: thinkingData.thought }) })
|
|
2193
2674
|
] }),
|
|
2194
2675
|
thinkingData.remaining_tasks && thinkingData.remaining_tasks.length > 0 && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2195
2676
|
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Remaining Tasks:" }),
|
|
2196
|
-
thinkingData.remaining_tasks.map((task, index) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */
|
|
2197
|
-
/* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
|
|
2198
|
-
/* @__PURE__ */ jsx8(Text8, { color: task.startsWith("\u{1F5F8}") ? "green" : "yellow", children: task })
|
|
2199
|
-
] }) }, index))
|
|
2677
|
+
thinkingData.remaining_tasks.map((task, index) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(Text8, { color: task.startsWith("\u{1F5F8}") ? "green" : "yellow", children: task }) }) }, index))
|
|
2200
2678
|
] })
|
|
2201
2679
|
] })
|
|
2202
2680
|
);
|
|
@@ -2215,7 +2693,7 @@ var renderEditToolCall = ({ args, preview }) => {
|
|
|
2215
2693
|
} catch (e) {
|
|
2216
2694
|
filepath = "Error parsing arguments";
|
|
2217
2695
|
}
|
|
2218
|
-
const finalFileName =
|
|
2696
|
+
const finalFileName = path9.basename(filepath);
|
|
2219
2697
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
2220
2698
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2221
2699
|
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
|
|
@@ -2247,7 +2725,7 @@ var renderGenericToolCall = ({ toolName, args }) => {
|
|
|
2247
2725
|
var ToolRenderDisplay = {
|
|
2248
2726
|
"shell_command": renderShellCommand2,
|
|
2249
2727
|
"ls_tool": renderLsTool2,
|
|
2250
|
-
"
|
|
2728
|
+
"reasoning_nootebook": renderBlumaNotebook,
|
|
2251
2729
|
"count_file_lines": renderCountFilesLines,
|
|
2252
2730
|
"read_file_lines": renderReadFileLines2,
|
|
2253
2731
|
"edit_tool": renderEditToolCall
|
|
@@ -2289,42 +2767,51 @@ var ToolResultDisplay = memo3(ToolResultDisplayComponent);
|
|
|
2289
2767
|
import { Box as Box11, Text as Text10 } from "ink";
|
|
2290
2768
|
import Spinner from "ink-spinner";
|
|
2291
2769
|
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2292
|
-
var SessionInfoConnectingMCP = ({
|
|
2293
|
-
|
|
2770
|
+
var SessionInfoConnectingMCP = ({
|
|
2771
|
+
sessionId: sessionId2,
|
|
2772
|
+
workdir,
|
|
2773
|
+
statusMessage
|
|
2774
|
+
}) => {
|
|
2775
|
+
return /* @__PURE__ */ jsx11(
|
|
2294
2776
|
Box11,
|
|
2295
2777
|
{
|
|
2296
2778
|
borderStyle: "round",
|
|
2297
2779
|
borderColor: "gray",
|
|
2298
|
-
flexDirection: "column",
|
|
2299
2780
|
marginBottom: 1,
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2781
|
+
children: /* @__PURE__ */ jsxs9(
|
|
2782
|
+
Box11,
|
|
2783
|
+
{
|
|
2784
|
+
marginLeft: 1,
|
|
2785
|
+
flexDirection: "column",
|
|
2786
|
+
children: [
|
|
2787
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
2788
|
+
/* @__PURE__ */ jsx11(Text10, { bold: true, color: "white", children: "localhost" }),
|
|
2789
|
+
" ",
|
|
2790
|
+
/* @__PURE__ */ jsx11(Text10, { color: "gray", children: " session:" }),
|
|
2791
|
+
" ",
|
|
2792
|
+
/* @__PURE__ */ jsx11(Text10, { color: "magenta", children: sessionId2 })
|
|
2793
|
+
] }),
|
|
2794
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
2795
|
+
/* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
|
|
2796
|
+
" ",
|
|
2797
|
+
/* @__PURE__ */ jsxs9(Text10, { color: "gray", children: [
|
|
2798
|
+
"workdir: ",
|
|
2799
|
+
workdir
|
|
2800
|
+
] })
|
|
2801
|
+
] }),
|
|
2802
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
2803
|
+
/* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
|
|
2804
|
+
" ",
|
|
2805
|
+
/* @__PURE__ */ jsx11(Text10, { color: "gray", children: "mcp: " }),
|
|
2806
|
+
/* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
|
|
2807
|
+
/* @__PURE__ */ jsx11(Spinner, { type: "dots" }),
|
|
2808
|
+
" "
|
|
2809
|
+
] }),
|
|
2810
|
+
/* @__PURE__ */ jsx11(Text10, { color: "white", children: statusMessage || "Please wait while we establish connections." })
|
|
2811
|
+
] })
|
|
2812
|
+
]
|
|
2813
|
+
}
|
|
2814
|
+
)
|
|
2328
2815
|
}
|
|
2329
2816
|
);
|
|
2330
2817
|
};
|
|
@@ -2357,6 +2844,22 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
|
|
|
2357
2844
|
setHistory((prev) => prev.filter((item) => item.id === 0 || item.id === 1));
|
|
2358
2845
|
return outBox(/* @__PURE__ */ jsx12(Text11, { color: "green", children: "History cleared." }));
|
|
2359
2846
|
}
|
|
2847
|
+
if (cmd === "init") {
|
|
2848
|
+
(async () => {
|
|
2849
|
+
try {
|
|
2850
|
+
await agentRef.current?.processTurn({ content: "/init" });
|
|
2851
|
+
} catch (e) {
|
|
2852
|
+
setHistory((prev) => prev.concat({
|
|
2853
|
+
id: Date.now(),
|
|
2854
|
+
component: outBox(/* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
|
|
2855
|
+
"Failed to execute /init: ",
|
|
2856
|
+
e?.message || String(e)
|
|
2857
|
+
] }))
|
|
2858
|
+
}));
|
|
2859
|
+
}
|
|
2860
|
+
})();
|
|
2861
|
+
return null;
|
|
2862
|
+
}
|
|
2360
2863
|
if (cmd === "mcp") {
|
|
2361
2864
|
const all = agentRef.current?.getUiToolsDetailed?.() || agentRef.current?.getAvailableTools?.() || [];
|
|
2362
2865
|
const isMcp = (t) => t.source?.toLowerCase?.() === "mcp" || !!t.server && t.server !== "native";
|
|
@@ -2468,12 +2971,12 @@ var SlashCommands_default = SlashCommands;
|
|
|
2468
2971
|
import updateNotifier from "update-notifier";
|
|
2469
2972
|
import { readPackageUp } from "read-package-up";
|
|
2470
2973
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2471
|
-
import
|
|
2974
|
+
import path10 from "path";
|
|
2472
2975
|
import fs8 from "fs";
|
|
2473
2976
|
function findPackageJsonNearest(startDir) {
|
|
2474
2977
|
let dir = startDir;
|
|
2475
2978
|
for (let i = 0; i < 6; i++) {
|
|
2476
|
-
const candidate =
|
|
2979
|
+
const candidate = path10.join(dir, "package.json");
|
|
2477
2980
|
if (fs8.existsSync(candidate)) {
|
|
2478
2981
|
try {
|
|
2479
2982
|
const raw = fs8.readFileSync(candidate, "utf8");
|
|
@@ -2482,7 +2985,7 @@ function findPackageJsonNearest(startDir) {
|
|
|
2482
2985
|
} catch {
|
|
2483
2986
|
}
|
|
2484
2987
|
}
|
|
2485
|
-
const parent =
|
|
2988
|
+
const parent = path10.dirname(dir);
|
|
2486
2989
|
if (parent === dir) break;
|
|
2487
2990
|
dir = parent;
|
|
2488
2991
|
}
|
|
@@ -2496,14 +2999,14 @@ async function checkForUpdates() {
|
|
|
2496
2999
|
const binPath = process.argv?.[1];
|
|
2497
3000
|
let pkg;
|
|
2498
3001
|
if (binPath && fs8.existsSync(binPath)) {
|
|
2499
|
-
const candidatePkg = findPackageJsonNearest(
|
|
3002
|
+
const candidatePkg = findPackageJsonNearest(path10.dirname(binPath));
|
|
2500
3003
|
if (candidatePkg?.name && candidatePkg?.version) {
|
|
2501
3004
|
pkg = candidatePkg;
|
|
2502
3005
|
}
|
|
2503
3006
|
}
|
|
2504
3007
|
if (!pkg) {
|
|
2505
3008
|
const __filename = fileURLToPath3(import.meta.url);
|
|
2506
|
-
const __dirname =
|
|
3009
|
+
const __dirname = path10.dirname(__filename);
|
|
2507
3010
|
const result = await readPackageUp({ cwd: __dirname });
|
|
2508
3011
|
pkg = result?.packageJson;
|
|
2509
3012
|
}
|
|
@@ -2604,6 +3107,7 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
2604
3107
|
setIsProcessing(false);
|
|
2605
3108
|
return;
|
|
2606
3109
|
}
|
|
3110
|
+
setIsProcessing(true);
|
|
2607
3111
|
setHistory((prev) => [
|
|
2608
3112
|
...prev,
|
|
2609
3113
|
{
|
|
@@ -2612,10 +3116,16 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
2612
3116
|
},
|
|
2613
3117
|
{
|
|
2614
3118
|
id: prev.length + 1,
|
|
2615
|
-
component: /* @__PURE__ */ jsx14(
|
|
3119
|
+
component: /* @__PURE__ */ jsx14(
|
|
3120
|
+
SlashCommands_default,
|
|
3121
|
+
{
|
|
3122
|
+
input: text,
|
|
3123
|
+
setHistory,
|
|
3124
|
+
agentRef: agentInstance
|
|
3125
|
+
}
|
|
3126
|
+
)
|
|
2616
3127
|
}
|
|
2617
3128
|
]);
|
|
2618
|
-
setIsProcessing(false);
|
|
2619
3129
|
return;
|
|
2620
3130
|
}
|
|
2621
3131
|
setIsProcessing(true);
|
|
@@ -2827,21 +3337,14 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
2827
3337
|
}, [eventBus2, sessionId2, handleConfirmation]);
|
|
2828
3338
|
const renderInteractiveComponent = () => {
|
|
2829
3339
|
if (mcpStatus !== "connected") {
|
|
2830
|
-
return /* @__PURE__ */ jsx14(
|
|
2831
|
-
|
|
3340
|
+
return /* @__PURE__ */ jsx14(Box14, { borderStyle: "round", borderColor: "black", children: /* @__PURE__ */ jsx14(
|
|
3341
|
+
SessionInfoConnectingMCP_default,
|
|
2832
3342
|
{
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
SessionInfoConnectingMCP_default,
|
|
2837
|
-
{
|
|
2838
|
-
sessionId: sessionId2,
|
|
2839
|
-
workdir,
|
|
2840
|
-
statusMessage
|
|
2841
|
-
}
|
|
2842
|
-
)
|
|
3343
|
+
sessionId: sessionId2,
|
|
3344
|
+
workdir,
|
|
3345
|
+
statusMessage
|
|
2843
3346
|
}
|
|
2844
|
-
);
|
|
3347
|
+
) });
|
|
2845
3348
|
}
|
|
2846
3349
|
if (pendingConfirmation) {
|
|
2847
3350
|
return /* @__PURE__ */ jsx14(
|