@nomad-e/bluma-cli 0.0.26 → 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 +1746 -1246
- 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,974 +1632,615 @@ CRITICAL: Your laptop (**sequentialThinking_nootebook**) is your ORGANIZED MIND
|
|
|
978
1632
|
\u{1F5F8} Set up environment
|
|
979
1633
|
[ ] Configure database
|
|
980
1634
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
Tool names must strictly follow the standard naming format:
|
|
984
|
-
|
|
985
|
-
- Use: plain, unmodified, lowercase names
|
|
986
|
-
- Do NOT use: special characters, extra spaces, version suffixes, or dynamic IDs
|
|
987
|
-
|
|
988
|
-
---
|
|
989
|
-
|
|
990
|
-
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
|
-
}
|
|
1635
|
+
</reasoning_rules>
|
|
1098
1636
|
|
|
1099
|
-
|
|
1100
|
-
import { promises as fs6 } from "fs";
|
|
1101
|
-
import path5 from "path";
|
|
1102
|
-
import { fileURLToPath } from "url";
|
|
1637
|
+
### Tool Naming Policy
|
|
1103
1638
|
|
|
1104
|
-
|
|
1105
|
-
import os3 from "os";
|
|
1106
|
-
import { exec } from "child_process";
|
|
1107
|
-
function shellCommand(args) {
|
|
1108
|
-
const { command, timeout = 20, cwd = process.cwd(), verbose = false } = args;
|
|
1109
|
-
return new Promise((resolve) => {
|
|
1110
|
-
const report = {
|
|
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
|
-
}
|
|
1639
|
+
Tool names must strictly follow the standard naming format:
|
|
1167
1640
|
|
|
1168
|
-
|
|
1169
|
-
|
|
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
|
-
}
|
|
1641
|
+
- Use: plain, unmodified, lowercase names
|
|
1642
|
+
- Do NOT use: special characters, extra spaces, version suffixes, or dynamic IDs
|
|
1313
1643
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1644
|
+
---
|
|
1645
|
+
|
|
1646
|
+
Correct Examples:
|
|
1647
|
+
- bluma_notebook
|
|
1648
|
+
- getDataTool
|
|
1649
|
+
- convertImage
|
|
1650
|
+
- userAuth
|
|
1651
|
+
|
|
1652
|
+
---
|
|
1653
|
+
|
|
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
|
|
1661
|
+
|
|
1662
|
+
---
|
|
1663
|
+
|
|
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>
|
|
1331
1684
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
|
|
1336
|
-
".git",
|
|
1337
|
-
".gitignore",
|
|
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
|
-
}
|
|
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.
|
|
1412
1688
|
|
|
1413
|
-
// src/app/agent/tools/natives/readLines.ts
|
|
1414
|
-
import { promises as fs4 } from "fs";
|
|
1415
|
-
async function readLines(args) {
|
|
1416
|
-
const { filepath, start_line, end_line } = args;
|
|
1417
|
-
try {
|
|
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
|
-
}
|
|
1449
1689
|
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|
|
1706
|
+
|
|
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
|
|
1713
|
+
|
|
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>
|
|
1719
|
+
|
|
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
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
/**
|
|
1503
|
-
* Registra as implementações de todas as ferramentas nativas.
|
|
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." }));
|
|
1514
|
-
}
|
|
1515
|
-
/**
|
|
1516
|
-
* Retorna a lista de definições de todas as ferramentas nativas carregadas.
|
|
1517
|
-
* O MCPClient usará esta função para obter a lista de ferramentas locais.
|
|
1518
|
-
*/
|
|
1519
|
-
getToolDefinitions() {
|
|
1520
|
-
return this.toolDefinitions;
|
|
1521
|
-
}
|
|
1522
|
-
/**
|
|
1523
|
-
* Invoca uma ferramenta nativa pelo nome com os argumentos fornecidos.
|
|
1524
|
-
* @param toolName O nome da ferramenta a ser invocada.
|
|
1525
|
-
* @param args Os argumentos para a ferramenta, geralmente um objeto.
|
|
1526
|
-
* @returns O resultado da execução da ferramenta.
|
|
1527
|
-
*/
|
|
1528
|
-
async invoke(toolName, args) {
|
|
1529
|
-
const implementation = this.toolImplementations.get(toolName);
|
|
1530
|
-
if (!implementation) {
|
|
1531
|
-
return { error: `Error: Native tool "${toolName}" not found.` };
|
|
1532
|
-
}
|
|
1533
|
-
try {
|
|
1534
|
-
return await implementation(args);
|
|
1535
|
-
} catch (error) {
|
|
1536
|
-
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred.";
|
|
1537
|
-
return { error: `Error executing tool "${toolName}": ${errorMessage}` };
|
|
1538
|
-
}
|
|
1756
|
+
// src/app/agent/core/context-api/context_manager.ts
|
|
1757
|
+
function createApiContextWindow(fullHistory, maxTurns) {
|
|
1758
|
+
if (!fullHistory.length) {
|
|
1759
|
+
return [];
|
|
1539
1760
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
// src/app/agent/tools/mcp/mcp_client.ts
|
|
1543
|
-
import { promises as fs7 } from "fs";
|
|
1544
|
-
import path6 from "path";
|
|
1545
|
-
import os4 from "os";
|
|
1546
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1547
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1548
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1549
|
-
var MCPClient = class {
|
|
1550
|
-
sessions = /* @__PURE__ */ new Map();
|
|
1551
|
-
toolToServerMap = /* @__PURE__ */ new Map();
|
|
1552
|
-
globalToolsForLlm = [];
|
|
1553
|
-
nativeToolInvoker;
|
|
1554
|
-
eventBus;
|
|
1555
|
-
// <<< ADICIONA A PROPRIEDADE
|
|
1556
|
-
constructor(nativeToolInvoker, eventBus2) {
|
|
1557
|
-
this.nativeToolInvoker = nativeToolInvoker;
|
|
1558
|
-
this.eventBus = eventBus2;
|
|
1761
|
+
if (maxTurns === null || maxTurns === void 0) {
|
|
1762
|
+
return [...fullHistory];
|
|
1559
1763
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1764
|
+
const systemMessages = [];
|
|
1765
|
+
let historyStartIndex = 0;
|
|
1766
|
+
while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
|
|
1767
|
+
systemMessages.push(fullHistory[historyStartIndex]);
|
|
1768
|
+
historyStartIndex++;
|
|
1769
|
+
}
|
|
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;
|
|
1581
1785
|
}
|
|
1582
|
-
|
|
1583
|
-
if (Object.keys(mergedConfig.mcpServers).length === 0) {
|
|
1584
|
-
return;
|
|
1786
|
+
continue;
|
|
1585
1787
|
}
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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.`);
|
|
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 = [];
|
|
1597
1795
|
}
|
|
1598
|
-
} catch (error) {
|
|
1599
|
-
this.eventBus.emit("backend_message", {
|
|
1600
|
-
type: "error",
|
|
1601
|
-
message: `Failed to connect to server '${serverName}'.`
|
|
1602
|
-
});
|
|
1603
1796
|
}
|
|
1604
1797
|
}
|
|
1605
1798
|
}
|
|
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
|
-
}
|
|
1799
|
+
if (currentTurn.length > 0) {
|
|
1800
|
+
turns.unshift(currentTurn);
|
|
1620
1801
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1802
|
+
const finalContext = systemMessages.concat(turns.flat());
|
|
1803
|
+
return finalContext;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// src/app/agent/bluma/core/bluma.ts
|
|
1807
|
+
var BluMaAgent = class {
|
|
1808
|
+
llm;
|
|
1809
|
+
deploymentName;
|
|
1810
|
+
sessionId;
|
|
1811
|
+
sessionFile = "";
|
|
1812
|
+
history = [];
|
|
1813
|
+
eventBus;
|
|
1814
|
+
mcpClient;
|
|
1815
|
+
feedbackSystem;
|
|
1816
|
+
maxContextTurns = 300;
|
|
1817
|
+
isInterrupted = false;
|
|
1818
|
+
constructor(sessionId2, eventBus2, llm, deploymentName, mcpClient, feedbackSystem) {
|
|
1819
|
+
this.sessionId = sessionId2;
|
|
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;
|
|
1643
1827
|
});
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
type: "function",
|
|
1652
|
-
function: {
|
|
1653
|
-
name: prefixedToolName,
|
|
1654
|
-
description: tool.description || "",
|
|
1655
|
-
parameters: tool.inputSchema
|
|
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);
|
|
1656
1835
|
}
|
|
1657
|
-
})
|
|
1658
|
-
|
|
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
|
-
} else {
|
|
1672
|
-
const session = this.sessions.get(route.server);
|
|
1673
|
-
if (!session) {
|
|
1674
|
-
return { error: `Sess\xE3o para o servidor '${route.server}' n\xE3o encontrada.` };
|
|
1836
|
+
} catch (e) {
|
|
1837
|
+
this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s dev_overlay: ${e.message}` });
|
|
1675
1838
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
async initialize() {
|
|
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);
|
|
1678
1851
|
}
|
|
1679
1852
|
}
|
|
1680
1853
|
getAvailableTools() {
|
|
1681
|
-
return this.
|
|
1854
|
+
return this.mcpClient.getAvailableTools();
|
|
1682
1855
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
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 });
|
|
1693
|
-
}
|
|
1694
|
-
return detailed;
|
|
1856
|
+
getUiToolsDetailed() {
|
|
1857
|
+
return this.mcpClient.getAvailableToolsDetailed();
|
|
1695
1858
|
}
|
|
1696
|
-
async
|
|
1697
|
-
|
|
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;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
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
|
+
});
|
|
1698
1889
|
try {
|
|
1699
|
-
|
|
1890
|
+
if (this.isInterrupted) {
|
|
1891
|
+
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
|
|
1892
|
+
return;
|
|
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);
|
|
1700
1900
|
} catch (error) {
|
|
1701
|
-
|
|
1901
|
+
toolResultContent = JSON.stringify({
|
|
1902
|
+
error: `Tool execution failed: ${error.message}`,
|
|
1903
|
+
details: error.data || "No additional details."
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
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" });
|
|
1702
1910
|
}
|
|
1911
|
+
} else {
|
|
1912
|
+
toolResultContent = "The user declined to execute this tool...";
|
|
1703
1913
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
});
|
|
1709
|
-
}
|
|
1710
|
-
};
|
|
1711
|
-
|
|
1712
|
-
// src/app/agent/feedback/feedback_system.ts
|
|
1713
|
-
var AdvancedFeedbackSystem = class {
|
|
1714
|
-
cumulativeScore = 0;
|
|
1715
|
-
/**
|
|
1716
|
-
* Gera feedback com base em um evento ocorrido.
|
|
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
|
-
};
|
|
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();
|
|
1734
1918
|
}
|
|
1735
|
-
return { score: 0, message: "No feedback for this event.", correction: "" };
|
|
1736
|
-
}
|
|
1737
|
-
getCumulativeScore() {
|
|
1738
|
-
return this.cumulativeScore;
|
|
1739
1919
|
}
|
|
1740
|
-
|
|
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:
|
|
1741
1925
|
|
|
1742
|
-
|
|
1743
|
-
function createApiContextWindow(fullHistory, maxTurns) {
|
|
1744
|
-
if (!fullHistory.length) {
|
|
1745
|
-
return [];
|
|
1746
|
-
}
|
|
1747
|
-
if (maxTurns === null || maxTurns === void 0) {
|
|
1748
|
-
return [...fullHistory];
|
|
1749
|
-
}
|
|
1750
|
-
const systemMessages = [];
|
|
1751
|
-
let historyStartIndex = 0;
|
|
1752
|
-
while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
|
|
1753
|
-
systemMessages.push(fullHistory[historyStartIndex]);
|
|
1754
|
-
historyStartIndex++;
|
|
1755
|
-
}
|
|
1756
|
-
const conversationHistory = fullHistory.slice(historyStartIndex);
|
|
1757
|
-
const turns = [];
|
|
1758
|
-
let currentTurn = [];
|
|
1759
|
-
let turnsFound = 0;
|
|
1760
|
-
const isDevOverlay = (m) => m?.role === "user" && m?.name === "dev_overlay";
|
|
1761
|
-
for (let i = conversationHistory.length - 1; i >= 0; i--) {
|
|
1762
|
-
const msg = conversationHistory[i];
|
|
1763
|
-
currentTurn.unshift(msg);
|
|
1764
|
-
const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some((tc) => tc.function.name === "agent_end_task");
|
|
1765
|
-
if (endsWithAgentEnd) {
|
|
1766
|
-
turns.unshift([...currentTurn]);
|
|
1767
|
-
currentTurn = [];
|
|
1768
|
-
turnsFound++;
|
|
1769
|
-
if (turnsFound >= maxTurns) {
|
|
1770
|
-
break;
|
|
1926
|
+
${editData.error.display}`;
|
|
1771
1927
|
}
|
|
1772
|
-
|
|
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}`;
|
|
1773
1932
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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
|
+
}
|
|
1781
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();
|
|
1782
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);
|
|
1783
1988
|
}
|
|
1784
1989
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
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
|
|
2005
|
+
});
|
|
2006
|
+
return resp;
|
|
1787
2007
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
2008
|
+
};
|
|
2009
|
+
|
|
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;
|
|
2015
|
+
}
|
|
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]);
|
|
2208
|
+
}
|
|
2209
|
+
return formattedPrompt;
|
|
1790
2210
|
}
|
|
1791
2211
|
|
|
1792
|
-
// src/app/agent/
|
|
1793
|
-
var
|
|
1794
|
-
|
|
1795
|
-
var Agent = class {
|
|
1796
|
-
client;
|
|
1797
|
-
deploymentName;
|
|
1798
|
-
sessionId;
|
|
1799
|
-
sessionFile = "";
|
|
2212
|
+
// src/app/agent/subagents/base_llm_subagent.ts
|
|
2213
|
+
var BaseLLMSubAgent = class {
|
|
2214
|
+
ctx;
|
|
1800
2215
|
history = [];
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
feedbackSystem;
|
|
1804
|
-
isInitialized = false;
|
|
1805
|
-
maxContextTurns = 300;
|
|
2216
|
+
sessionFile = "";
|
|
2217
|
+
maxContextTurns = 160;
|
|
1806
2218
|
isInterrupted = false;
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
this.
|
|
1810
|
-
this.eventBus
|
|
1811
|
-
this.eventBus.on("user_interrupt", () => {
|
|
2219
|
+
async execute(input, ctx) {
|
|
2220
|
+
this.ctx = ctx;
|
|
2221
|
+
this.isInterrupted = false;
|
|
2222
|
+
this.ctx.eventBus.on("user_interrupt", () => {
|
|
1812
2223
|
this.isInterrupted = true;
|
|
1813
2224
|
});
|
|
1814
|
-
this.
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
-
const nativeToolInvoker = new ToolInvoker();
|
|
1831
|
-
this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
|
|
1832
|
-
this.feedbackSystem = new AdvancedFeedbackSystem();
|
|
1833
|
-
const apiKey = "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";
|
|
1834
|
-
const modelName = "openrouter/horizon-alpha";
|
|
1835
|
-
if (!apiKey || !modelName) {
|
|
1836
|
-
throw new Error("Chave de API ou nome do modelo do OpenRouter n\xE3o encontrados.");
|
|
1837
|
-
}
|
|
1838
|
-
this.deploymentName = modelName;
|
|
1839
|
-
this.client = new OpenAI({
|
|
1840
|
-
// Configuração do cliente OpenAI hospedado no OpenRouter
|
|
1841
|
-
apiKey,
|
|
1842
|
-
baseURL: "https://openrouter.ai/api/v1",
|
|
1843
|
-
// <-- URL base do OpenRouter
|
|
1844
|
-
defaultHeaders: {
|
|
1845
|
-
"HTTP-Referer": "http://localhost:3000",
|
|
1846
|
-
// Substitua pelo seu site ou app
|
|
1847
|
-
"X-Title": "Bluma CLI Agent"
|
|
1848
|
-
// Substitua pelo nome do seu projeto
|
|
1849
|
-
}
|
|
1850
|
-
});
|
|
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 };
|
|
1851
2229
|
}
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
*/
|
|
1856
|
-
async initialize() {
|
|
1857
|
-
await this.mcpClient.nativeToolInvoker.initialize();
|
|
1858
|
-
await this.mcpClient.initialize();
|
|
1859
|
-
const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
|
|
2230
|
+
async initializeHistory() {
|
|
2231
|
+
const sessionId2 = `${this.id}`;
|
|
2232
|
+
const [sessionFile, history] = await loadOrcreateSession(sessionId2);
|
|
1860
2233
|
this.sessionFile = sessionFile;
|
|
1861
|
-
this.history = history;
|
|
2234
|
+
this.history = history || [];
|
|
2235
|
+
const systemPromptContent = getInitPrompt();
|
|
1862
2236
|
if (this.history.length === 0) {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
}
|
|
1867
|
-
this.isInitialized = true;
|
|
1868
|
-
}
|
|
1869
|
-
getAvailableTools() {
|
|
1870
|
-
return this.mcpClient.getAvailableTools();
|
|
1871
|
-
}
|
|
1872
|
-
// UI helper: detailed tools with origin metadata
|
|
1873
|
-
getUiToolsDetailed() {
|
|
1874
|
-
return this.mcpClient.getAvailableToolsDetailed();
|
|
1875
|
-
}
|
|
1876
|
-
async processTurn(userInput) {
|
|
1877
|
-
this.isInterrupted = false;
|
|
1878
|
-
this.history.push({ role: "user", content: userInput.content });
|
|
1879
|
-
await this._continueConversation();
|
|
1880
|
-
}
|
|
1881
|
-
/**
|
|
1882
|
-
* Lida com a decisão do usuário (aceitar/recusar) sobre uma chamada de ferramenta.
|
|
1883
|
-
* Garante que uma mensagem de 'role: tool' seja sempre adicionada ao histórico.
|
|
1884
|
-
*/
|
|
1885
|
-
async handleToolResponse(decisionData) {
|
|
1886
|
-
const toolCall = decisionData.tool_calls[0];
|
|
1887
|
-
let toolResultContent;
|
|
1888
|
-
let shouldContinueConversation = true;
|
|
1889
|
-
if (!this.sessionFile) {
|
|
1890
|
-
const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
|
|
1891
|
-
this.sessionFile = sessionFile;
|
|
1892
|
-
if (this.history.length === 0 && history.length > 0) {
|
|
1893
|
-
this.history = history;
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
if (decisionData.type === "user_decision_execute") {
|
|
1897
|
-
const toolName = toolCall.function.name;
|
|
1898
|
-
const toolArgs = JSON.parse(toolCall.function.arguments);
|
|
1899
|
-
let previewContent;
|
|
1900
|
-
if (toolName === "edit_tool") {
|
|
1901
|
-
previewContent = await this._generateEditPreview(toolArgs);
|
|
1902
|
-
}
|
|
1903
|
-
this.eventBus.emit("backend_message", {
|
|
1904
|
-
type: "tool_call",
|
|
1905
|
-
tool_name: toolName,
|
|
1906
|
-
arguments: toolArgs,
|
|
1907
|
-
preview: previewContent
|
|
2237
|
+
this.history.push({
|
|
2238
|
+
role: "system",
|
|
2239
|
+
content: systemPromptContent
|
|
1908
2240
|
});
|
|
1909
|
-
|
|
1910
|
-
if (this.isInterrupted) {
|
|
1911
|
-
this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
|
|
1912
|
-
return;
|
|
1913
|
-
}
|
|
1914
|
-
const result = await this.mcpClient.invoke(toolName, toolArgs);
|
|
1915
|
-
let finalResult = result;
|
|
1916
|
-
if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
|
|
1917
|
-
finalResult = result[0].text;
|
|
1918
|
-
}
|
|
1919
|
-
toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
|
|
1920
|
-
} catch (error) {
|
|
1921
|
-
toolResultContent = JSON.stringify({
|
|
1922
|
-
error: `Tool execution failed: ${error.message}`,
|
|
1923
|
-
details: error.data || "No additional details."
|
|
1924
|
-
});
|
|
1925
|
-
}
|
|
1926
|
-
this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
|
|
1927
|
-
if (toolName.includes("agent_end_task")) {
|
|
1928
|
-
shouldContinueConversation = false;
|
|
1929
|
-
this.eventBus.emit("backend_message", { type: "done", status: "completed" });
|
|
1930
|
-
}
|
|
1931
|
-
} else {
|
|
1932
|
-
toolResultContent = "The user declined to execute this tool...";
|
|
1933
|
-
}
|
|
1934
|
-
this.history.push({
|
|
1935
|
-
role: "tool",
|
|
1936
|
-
tool_call_id: toolCall.id,
|
|
1937
|
-
content: toolResultContent
|
|
1938
|
-
});
|
|
1939
|
-
await saveSessionHistory(this.sessionFile, this.history);
|
|
1940
|
-
if (shouldContinueConversation && !this.isInterrupted) {
|
|
1941
|
-
await this._continueConversation();
|
|
2241
|
+
await saveSessionHistory(this.sessionFile, this.history);
|
|
1942
2242
|
}
|
|
1943
2243
|
}
|
|
1944
|
-
/**
|
|
1945
|
-
* Método central que chama a API do LLM e processa a resposta,
|
|
1946
|
-
* com lógica de feedback e auto-aprovação de ferramentas.
|
|
1947
|
-
*/
|
|
1948
|
-
// Adicione este método dentro da classe Agent
|
|
1949
2244
|
async _generateEditPreview(toolArgs) {
|
|
1950
2245
|
try {
|
|
1951
2246
|
const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
|
|
@@ -1954,7 +2249,7 @@ var Agent = class {
|
|
|
1954
2249
|
|
|
1955
2250
|
${editData.error.display}`;
|
|
1956
2251
|
}
|
|
1957
|
-
const filename =
|
|
2252
|
+
const filename = toolArgs.file_path?.split(/[\/]/).pop() || "file";
|
|
1958
2253
|
return createDiff(filename, editData.currentContent || "", editData.newContent);
|
|
1959
2254
|
} catch (e) {
|
|
1960
2255
|
return `An unexpected error occurred while generating the edit preview: ${e.message}`;
|
|
@@ -1963,79 +2258,266 @@ ${editData.error.display}`;
|
|
|
1963
2258
|
async _continueConversation() {
|
|
1964
2259
|
try {
|
|
1965
2260
|
if (this.isInterrupted) {
|
|
1966
|
-
this.
|
|
2261
|
+
this.emitEvent("info", { message: "SubAgent task cancelled by user." });
|
|
1967
2262
|
return;
|
|
1968
2263
|
}
|
|
1969
|
-
const contextWindow =
|
|
1970
|
-
const response = await this.
|
|
1971
|
-
model: this.
|
|
2264
|
+
const contextWindow = this.history.slice(-this.maxContextTurns);
|
|
2265
|
+
const response = await this.ctx.llm.chatCompletion({
|
|
2266
|
+
model: this.ctx.policy?.llmDeployment || "default",
|
|
1972
2267
|
messages: contextWindow,
|
|
1973
|
-
tools: this.mcpClient.getAvailableTools(),
|
|
2268
|
+
tools: this.ctx.mcpClient.getAvailableTools(),
|
|
1974
2269
|
tool_choice: "required",
|
|
1975
2270
|
parallel_tool_calls: false
|
|
1976
2271
|
});
|
|
1977
2272
|
if (this.isInterrupted) {
|
|
1978
|
-
this.
|
|
2273
|
+
this.emitEvent("info", { message: "SubAgent task cancelled by user." });
|
|
1979
2274
|
return;
|
|
1980
2275
|
}
|
|
1981
2276
|
const message = response.choices[0].message;
|
|
1982
2277
|
this.history.push(message);
|
|
1983
2278
|
if (message.tool_calls) {
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
];
|
|
1989
|
-
const toolToCall = message.tool_calls[0];
|
|
1990
|
-
const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
|
|
1991
|
-
if (isSafeTool) {
|
|
1992
|
-
await this.handleToolResponse({ type: "user_decision_execute", tool_calls: message.tool_calls });
|
|
1993
|
-
} else {
|
|
1994
|
-
const toolName = toolToCall.function.name;
|
|
1995
|
-
if (toolName === "edit_tool") {
|
|
1996
|
-
const args = JSON.parse(toolToCall.function.arguments);
|
|
1997
|
-
const previewContent = await this._generateEditPreview(args);
|
|
1998
|
-
this.eventBus.emit("backend_message", {
|
|
1999
|
-
type: "confirmation_request",
|
|
2000
|
-
tool_calls: message.tool_calls,
|
|
2001
|
-
preview: previewContent
|
|
2002
|
-
});
|
|
2003
|
-
} else {
|
|
2004
|
-
this.eventBus.emit("backend_message", {
|
|
2005
|
-
type: "confirmation_request",
|
|
2006
|
-
tool_calls: message.tool_calls
|
|
2007
|
-
});
|
|
2008
|
-
}
|
|
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();
|
|
2009
2283
|
}
|
|
2010
2284
|
} else if (message.content) {
|
|
2011
|
-
this.
|
|
2012
|
-
|
|
2013
|
-
content: message.content
|
|
2014
|
-
});
|
|
2015
|
-
const feedback = this.feedbackSystem.generateFeedback({
|
|
2016
|
-
event: "protocol_violation_direct_text",
|
|
2017
|
-
details: { violationContent: message.content }
|
|
2018
|
-
});
|
|
2019
|
-
this.eventBus.emit("backend_message", {
|
|
2020
|
-
type: "protocol_violation",
|
|
2021
|
-
message: feedback.message,
|
|
2022
|
-
content: message.content
|
|
2023
|
-
});
|
|
2024
|
-
this.history.push({
|
|
2025
|
-
role: "system",
|
|
2026
|
-
content: feedback.correction
|
|
2027
|
-
});
|
|
2028
|
-
await this._continueConversation();
|
|
2285
|
+
this.emitEvent("assistant_message", { content: message.content });
|
|
2286
|
+
this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content: message.content });
|
|
2029
2287
|
} else {
|
|
2030
|
-
this.
|
|
2031
|
-
await this._continueConversation();
|
|
2288
|
+
this.emitEvent("info", { message: "SubAgent is thinking... continuing reasoning cycle." });
|
|
2032
2289
|
}
|
|
2033
2290
|
} catch (error) {
|
|
2034
|
-
const errorMessage = error
|
|
2035
|
-
this.
|
|
2291
|
+
const errorMessage = error?.message || "An unknown API error occurred.";
|
|
2292
|
+
this.emitEvent("error", { message: errorMessage });
|
|
2036
2293
|
} finally {
|
|
2037
|
-
await saveSessionHistory(this.sessionFile, this.history);
|
|
2294
|
+
if (this.sessionFile) await saveSessionHistory(this.sessionFile, this.history);
|
|
2295
|
+
}
|
|
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);
|
|
2330
|
+
}
|
|
2331
|
+
} catch (error) {
|
|
2332
|
+
toolResultContent = JSON.stringify({ error: `Tool execution failed: ${error.message}`, details: error.data || "No additional details." });
|
|
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.";
|
|
2340
|
+
}
|
|
2341
|
+
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
2342
|
+
}
|
|
2343
|
+
// Utilitários: emitir eventos, chamar tools, etc
|
|
2344
|
+
emitEvent(type, payload) {
|
|
2345
|
+
this.ctx.eventBus.emit("backend_message", { type, ...payload });
|
|
2346
|
+
}
|
|
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
|
+
};
|
|
2436
|
+
|
|
2437
|
+
// src/app/agent/agent.ts
|
|
2438
|
+
var globalEnvPath = path8.join(os6.homedir(), ".bluma-cli", ".env");
|
|
2439
|
+
dotenv.config({ path: globalEnvPath });
|
|
2440
|
+
var Agent = class {
|
|
2441
|
+
sessionId;
|
|
2442
|
+
eventBus;
|
|
2443
|
+
mcpClient;
|
|
2444
|
+
feedbackSystem;
|
|
2445
|
+
// Mantido caso UI dependa de eventos
|
|
2446
|
+
llm;
|
|
2447
|
+
deploymentName;
|
|
2448
|
+
core;
|
|
2449
|
+
// Delegado
|
|
2450
|
+
subAgents;
|
|
2451
|
+
// Orquestrador de subagentes
|
|
2452
|
+
toolInvoker;
|
|
2453
|
+
constructor(sessionId2, eventBus2) {
|
|
2454
|
+
this.sessionId = sessionId2;
|
|
2455
|
+
this.eventBus = eventBus2;
|
|
2456
|
+
const nativeToolInvoker = new ToolInvoker();
|
|
2457
|
+
this.toolInvoker = nativeToolInvoker;
|
|
2458
|
+
this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
|
|
2459
|
+
this.feedbackSystem = new AdvancedFeedbackSystem();
|
|
2460
|
+
const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
|
|
2461
|
+
const apiKey = process.env.AZURE_OPENAI_API_KEY;
|
|
2462
|
+
const apiVersion = process.env.AZURE_OPENAI_API_VERSION;
|
|
2463
|
+
this.deploymentName = process.env.AZURE_OPENAI_DEPLOYMENT || "";
|
|
2464
|
+
if (!endpoint || !apiKey || !apiVersion || !this.deploymentName) {
|
|
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.`;
|
|
2466
|
+
throw new Error(errorMessage);
|
|
2467
|
+
}
|
|
2468
|
+
const openai = new OpenAI({
|
|
2469
|
+
// Configuração do cliente OpenAI hospedado no Azure
|
|
2470
|
+
apiKey,
|
|
2471
|
+
baseURL: `${endpoint}/openai/deployments/${this.deploymentName}`,
|
|
2472
|
+
defaultQuery: { "api-version": apiVersion },
|
|
2473
|
+
defaultHeaders: { "api-key": apiKey }
|
|
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
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
async initialize() {
|
|
2494
|
+
await this.core.initialize();
|
|
2495
|
+
}
|
|
2496
|
+
getAvailableTools() {
|
|
2497
|
+
return this.core.getAvailableTools();
|
|
2498
|
+
}
|
|
2499
|
+
getUiToolsDetailed() {
|
|
2500
|
+
return this.core.getUiToolsDetailed();
|
|
2501
|
+
}
|
|
2502
|
+
async processTurn(userInput) {
|
|
2503
|
+
const inputText = String(userInput.content || "").trim();
|
|
2504
|
+
if (inputText === "/init" || inputText.startsWith("/init ")) {
|
|
2505
|
+
await this.dispatchToSubAgent({ command: "/init", content: inputText });
|
|
2506
|
+
return;
|
|
2038
2507
|
}
|
|
2508
|
+
await this.core.processTurn({ content: inputText });
|
|
2509
|
+
}
|
|
2510
|
+
async handleToolResponse(decisionData) {
|
|
2511
|
+
await this.core.handleToolResponse(decisionData);
|
|
2512
|
+
}
|
|
2513
|
+
// Novo: processamento via SubAgents (ex.: payload vindo do front /init)
|
|
2514
|
+
async dispatchToSubAgent(payload) {
|
|
2515
|
+
return this.subAgents.registerAndDispatch(payload);
|
|
2516
|
+
}
|
|
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 })) : [];
|
|
2039
2521
|
}
|
|
2040
2522
|
};
|
|
2041
2523
|
|
|
@@ -2071,7 +2553,7 @@ import { Box as Box9 } from "ink";
|
|
|
2071
2553
|
|
|
2072
2554
|
// src/app/ui/components/toolCallRenderers.tsx
|
|
2073
2555
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
2074
|
-
import
|
|
2556
|
+
import path9 from "path";
|
|
2075
2557
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2076
2558
|
var formatArgumentsForDisplay = (args) => {
|
|
2077
2559
|
if (typeof args === "string") {
|
|
@@ -2104,7 +2586,7 @@ var renderLsTool2 = ({ args }) => {
|
|
|
2104
2586
|
} catch (e) {
|
|
2105
2587
|
directoryPath = "Error parsing arguments";
|
|
2106
2588
|
}
|
|
2107
|
-
const finalDirectoryName =
|
|
2589
|
+
const finalDirectoryName = path9.basename(directoryPath);
|
|
2108
2590
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2109
2591
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2110
2592
|
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
|
|
@@ -2124,7 +2606,7 @@ var renderCountFilesLines = ({ args }) => {
|
|
|
2124
2606
|
} catch (e) {
|
|
2125
2607
|
directoryPath = "Error parsing arguments";
|
|
2126
2608
|
}
|
|
2127
|
-
const finalDirectoryName =
|
|
2609
|
+
const finalDirectoryName = path9.basename(directoryPath);
|
|
2128
2610
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2129
2611
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2130
2612
|
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
|
|
@@ -2148,7 +2630,7 @@ var renderReadFileLines2 = ({ args }) => {
|
|
|
2148
2630
|
} catch (e) {
|
|
2149
2631
|
filepath = "Error parsing arguments";
|
|
2150
2632
|
}
|
|
2151
|
-
const finalFileName =
|
|
2633
|
+
const finalFileName = path9.basename(filepath);
|
|
2152
2634
|
return (
|
|
2153
2635
|
// A caixa externa com a borda, seguindo o template
|
|
2154
2636
|
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
@@ -2186,20 +2668,13 @@ var renderBlumaNotebook = ({ args }) => {
|
|
|
2186
2668
|
return (
|
|
2187
2669
|
// Usamos a mesma estrutura de caixa com borda
|
|
2188
2670
|
/* @__PURE__ */ jsxs8(Box8, { borderStyle: "round", borderColor: "green", flexDirection: "column", paddingX: 1, children: [
|
|
2189
|
-
/* @__PURE__ */
|
|
2190
|
-
/* @__PURE__ */ jsx8(Text8, {
|
|
2191
|
-
"Thinking Process"
|
|
2192
|
-
] }) }),
|
|
2193
|
-
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2194
|
-
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Thought:" }),
|
|
2671
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2672
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Thought:" }),
|
|
2195
2673
|
/* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { color: "gray", children: thinkingData.thought }) })
|
|
2196
2674
|
] }),
|
|
2197
2675
|
thinkingData.remaining_tasks && thinkingData.remaining_tasks.length > 0 && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2198
2676
|
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Remaining Tasks:" }),
|
|
2199
|
-
thinkingData.remaining_tasks.map((task, index) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */
|
|
2200
|
-
/* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
|
|
2201
|
-
/* @__PURE__ */ jsx8(Text8, { color: task.startsWith("\u{1F5F8}") ? "green" : "yellow", children: task })
|
|
2202
|
-
] }) }, 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))
|
|
2203
2678
|
] })
|
|
2204
2679
|
] })
|
|
2205
2680
|
);
|
|
@@ -2218,7 +2693,7 @@ var renderEditToolCall = ({ args, preview }) => {
|
|
|
2218
2693
|
} catch (e) {
|
|
2219
2694
|
filepath = "Error parsing arguments";
|
|
2220
2695
|
}
|
|
2221
|
-
const finalFileName =
|
|
2696
|
+
const finalFileName = path9.basename(filepath);
|
|
2222
2697
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
2223
2698
|
/* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
2224
2699
|
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
|
|
@@ -2250,7 +2725,7 @@ var renderGenericToolCall = ({ toolName, args }) => {
|
|
|
2250
2725
|
var ToolRenderDisplay = {
|
|
2251
2726
|
"shell_command": renderShellCommand2,
|
|
2252
2727
|
"ls_tool": renderLsTool2,
|
|
2253
|
-
"
|
|
2728
|
+
"reasoning_nootebook": renderBlumaNotebook,
|
|
2254
2729
|
"count_file_lines": renderCountFilesLines,
|
|
2255
2730
|
"read_file_lines": renderReadFileLines2,
|
|
2256
2731
|
"edit_tool": renderEditToolCall
|
|
@@ -2292,42 +2767,51 @@ var ToolResultDisplay = memo3(ToolResultDisplayComponent);
|
|
|
2292
2767
|
import { Box as Box11, Text as Text10 } from "ink";
|
|
2293
2768
|
import Spinner from "ink-spinner";
|
|
2294
2769
|
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2295
|
-
var SessionInfoConnectingMCP = ({
|
|
2296
|
-
|
|
2770
|
+
var SessionInfoConnectingMCP = ({
|
|
2771
|
+
sessionId: sessionId2,
|
|
2772
|
+
workdir,
|
|
2773
|
+
statusMessage
|
|
2774
|
+
}) => {
|
|
2775
|
+
return /* @__PURE__ */ jsx11(
|
|
2297
2776
|
Box11,
|
|
2298
2777
|
{
|
|
2299
2778
|
borderStyle: "round",
|
|
2300
2779
|
borderColor: "gray",
|
|
2301
|
-
flexDirection: "column",
|
|
2302
2780
|
marginBottom: 1,
|
|
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
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
+
)
|
|
2331
2815
|
}
|
|
2332
2816
|
);
|
|
2333
2817
|
};
|
|
@@ -2360,6 +2844,22 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
|
|
|
2360
2844
|
setHistory((prev) => prev.filter((item) => item.id === 0 || item.id === 1));
|
|
2361
2845
|
return outBox(/* @__PURE__ */ jsx12(Text11, { color: "green", children: "History cleared." }));
|
|
2362
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
|
+
}
|
|
2363
2863
|
if (cmd === "mcp") {
|
|
2364
2864
|
const all = agentRef.current?.getUiToolsDetailed?.() || agentRef.current?.getAvailableTools?.() || [];
|
|
2365
2865
|
const isMcp = (t) => t.source?.toLowerCase?.() === "mcp" || !!t.server && t.server !== "native";
|
|
@@ -2471,12 +2971,12 @@ var SlashCommands_default = SlashCommands;
|
|
|
2471
2971
|
import updateNotifier from "update-notifier";
|
|
2472
2972
|
import { readPackageUp } from "read-package-up";
|
|
2473
2973
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2474
|
-
import
|
|
2974
|
+
import path10 from "path";
|
|
2475
2975
|
import fs8 from "fs";
|
|
2476
2976
|
function findPackageJsonNearest(startDir) {
|
|
2477
2977
|
let dir = startDir;
|
|
2478
2978
|
for (let i = 0; i < 6; i++) {
|
|
2479
|
-
const candidate =
|
|
2979
|
+
const candidate = path10.join(dir, "package.json");
|
|
2480
2980
|
if (fs8.existsSync(candidate)) {
|
|
2481
2981
|
try {
|
|
2482
2982
|
const raw = fs8.readFileSync(candidate, "utf8");
|
|
@@ -2485,7 +2985,7 @@ function findPackageJsonNearest(startDir) {
|
|
|
2485
2985
|
} catch {
|
|
2486
2986
|
}
|
|
2487
2987
|
}
|
|
2488
|
-
const parent =
|
|
2988
|
+
const parent = path10.dirname(dir);
|
|
2489
2989
|
if (parent === dir) break;
|
|
2490
2990
|
dir = parent;
|
|
2491
2991
|
}
|
|
@@ -2499,14 +2999,14 @@ async function checkForUpdates() {
|
|
|
2499
2999
|
const binPath = process.argv?.[1];
|
|
2500
3000
|
let pkg;
|
|
2501
3001
|
if (binPath && fs8.existsSync(binPath)) {
|
|
2502
|
-
const candidatePkg = findPackageJsonNearest(
|
|
3002
|
+
const candidatePkg = findPackageJsonNearest(path10.dirname(binPath));
|
|
2503
3003
|
if (candidatePkg?.name && candidatePkg?.version) {
|
|
2504
3004
|
pkg = candidatePkg;
|
|
2505
3005
|
}
|
|
2506
3006
|
}
|
|
2507
3007
|
if (!pkg) {
|
|
2508
3008
|
const __filename = fileURLToPath3(import.meta.url);
|
|
2509
|
-
const __dirname =
|
|
3009
|
+
const __dirname = path10.dirname(__filename);
|
|
2510
3010
|
const result = await readPackageUp({ cwd: __dirname });
|
|
2511
3011
|
pkg = result?.packageJson;
|
|
2512
3012
|
}
|
|
@@ -2607,6 +3107,7 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
2607
3107
|
setIsProcessing(false);
|
|
2608
3108
|
return;
|
|
2609
3109
|
}
|
|
3110
|
+
setIsProcessing(true);
|
|
2610
3111
|
setHistory((prev) => [
|
|
2611
3112
|
...prev,
|
|
2612
3113
|
{
|
|
@@ -2615,10 +3116,16 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
2615
3116
|
},
|
|
2616
3117
|
{
|
|
2617
3118
|
id: prev.length + 1,
|
|
2618
|
-
component: /* @__PURE__ */ jsx14(
|
|
3119
|
+
component: /* @__PURE__ */ jsx14(
|
|
3120
|
+
SlashCommands_default,
|
|
3121
|
+
{
|
|
3122
|
+
input: text,
|
|
3123
|
+
setHistory,
|
|
3124
|
+
agentRef: agentInstance
|
|
3125
|
+
}
|
|
3126
|
+
)
|
|
2619
3127
|
}
|
|
2620
3128
|
]);
|
|
2621
|
-
setIsProcessing(false);
|
|
2622
3129
|
return;
|
|
2623
3130
|
}
|
|
2624
3131
|
setIsProcessing(true);
|
|
@@ -2830,21 +3337,14 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
|
|
|
2830
3337
|
}, [eventBus2, sessionId2, handleConfirmation]);
|
|
2831
3338
|
const renderInteractiveComponent = () => {
|
|
2832
3339
|
if (mcpStatus !== "connected") {
|
|
2833
|
-
return /* @__PURE__ */ jsx14(
|
|
2834
|
-
|
|
3340
|
+
return /* @__PURE__ */ jsx14(Box14, { borderStyle: "round", borderColor: "black", children: /* @__PURE__ */ jsx14(
|
|
3341
|
+
SessionInfoConnectingMCP_default,
|
|
2835
3342
|
{
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
SessionInfoConnectingMCP_default,
|
|
2840
|
-
{
|
|
2841
|
-
sessionId: sessionId2,
|
|
2842
|
-
workdir,
|
|
2843
|
-
statusMessage
|
|
2844
|
-
}
|
|
2845
|
-
)
|
|
3343
|
+
sessionId: sessionId2,
|
|
3344
|
+
workdir,
|
|
3345
|
+
statusMessage
|
|
2846
3346
|
}
|
|
2847
|
-
);
|
|
3347
|
+
) });
|
|
2848
3348
|
}
|
|
2849
3349
|
if (pendingConfirmation) {
|
|
2850
3350
|
return /* @__PURE__ */ jsx14(
|