@nomad-e/bluma-cli 0.0.27 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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__ */ jsxs(
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
- /* @__PURE__ */ jsxs(Text, { children: [
70
- /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "localhost" }),
71
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " session:" }),
72
- " ",
73
- /* @__PURE__ */ jsx(Text, { color: "magenta", children: sessionId2 })
74
- ] }),
75
- /* @__PURE__ */ jsxs(Text, { children: [
76
- /* @__PURE__ */ jsx(Text, { color: "magenta", children: "\u21B3" }),
77
- " ",
78
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
79
- "workdir: ",
80
- workdir
81
- ] })
82
- ] }),
83
- /* @__PURE__ */ jsxs(Text, { children: [
84
- /* @__PURE__ */ jsx(Text, { color: "magenta", children: "\u21B3" }),
85
- " ",
86
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "mcp: " }),
87
- /* @__PURE__ */ jsx(Text, { color: mcpStatus === "connected" ? "green" : "yellow", children: mcpStatus })
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: permissiveOnSubmit,
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__ */ jsxs4(Text4, { color, children: [
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 path7 from "path";
614
- import os5 from "os";
621
+ import path8 from "path";
622
+ import os6 from "os";
615
623
 
616
- // src/app/agent/session_manger/session_manager.ts
617
- import path2 from "path";
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 { promises as fs } from "fs";
620
- var fileLocks = /* @__PURE__ */ new Map();
621
- async function withFileLock(file, fn) {
622
- const prev = fileLocks.get(file) || Promise.resolve();
623
- let release;
624
- const p = new Promise((res) => release = res);
625
- fileLocks.set(file, prev.then(() => p));
626
- try {
627
- const result = await fn();
628
- return result;
629
- } finally {
630
- release();
631
- if (fileLocks.get(file) === p) fileLocks.delete(file);
632
- }
633
- }
634
- function expandHome(p) {
635
- if (!p) return p;
636
- if (p.startsWith("~")) {
637
- return path2.join(os.homedir(), p.slice(1));
638
- }
639
- return p;
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
- function getPreferredAppDir() {
642
- const fixed = path2.join(os.homedir(), ".bluma-cli");
643
- return path2.resolve(expandHome(fixed));
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
- async function safeRenameWithRetry(src, dest, maxRetries = 6) {
646
- let attempt = 0;
647
- let lastErr;
648
- const isWin = process.platform === "win32";
649
- while (attempt <= maxRetries) {
650
- try {
651
- await fs.rename(src, dest);
652
- return;
653
- } catch (e) {
654
- lastErr = e;
655
- const code = e && e.code || "";
656
- const transient = code === "EPERM" || code === "EBUSY" || code === "ENOTEMPTY" || code === "EACCES";
657
- if (!(isWin && transient) || attempt === maxRetries) break;
658
- const backoff = Math.min(1e3, 50 * Math.pow(2, attempt));
659
- await new Promise((r) => setTimeout(r, backoff));
660
- attempt++;
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
- try {
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 loadOrcreateSession(sessionId2) {
680
- const sessionDir = await ensureSessionDir();
681
- const sessionFile = path2.join(sessionDir, `${sessionId2}.json`);
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.access(sessionFile);
684
- const fileContent = await fs.readFile(sessionFile, "utf-8");
685
- const sessionData = JSON.parse(fileContent);
686
- return [sessionFile, sessionData.conversation_history || []];
687
- } catch (error) {
688
- const newSessionData = {
689
- session_id: sessionId2,
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
- async function saveSessionHistory(sessionFile, history) {
698
- await withFileLock(sessionFile, async () => {
699
- let sessionData;
700
- try {
701
- const dir = path2.dirname(sessionFile);
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
- try {
706
- const fileContent = await fs.readFile(sessionFile, "utf-8");
707
- sessionData = JSON.parse(fileContent);
708
- } catch (error) {
709
- const code = error && error.code;
710
- if (code !== "ENOENT") {
711
- if (error instanceof Error) {
712
- console.warn(`Could not read or parse session file ${sessionFile}. Re-initializing. Error: ${error.message}`);
713
- } else {
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
- sessionData.conversation_history = history;
729
- sessionData.last_updated = (/* @__PURE__ */ new Date()).toISOString();
730
- const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
731
- try {
732
- await fs.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
733
- await safeRenameWithRetry(tempSessionFile, sessionFile);
734
- } catch (writeError) {
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/core/prompt/prompt_builder.ts
749
- import os2 from "os";
750
- var SYSTEM_PROMPT = `
751
-
752
- ### YOU ARE BluMa CLI \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
753
- You use a proprietary Large Language Model (LLM) fine-tuned by the NomadEngenuity team.
754
-
755
- ---
756
-
757
- ## BEHAVIORAL RULES
758
-
759
- - **Identity:**
760
- You are BluMa (NomadEngenuity). Maintain professionalism and technical language.
761
-
762
- - **Communication:**
763
- ALL messages must be sent via 'message_notify_dev'.
764
- **No direct text replies to the developer.**
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
- <current_system_environment>
785
- - Operating System: {os_type} ({os_version})
786
- - Architecture: {architecture}
787
- - Current Working Directory: {workdir}
788
- - Shell: {shell_type}
789
- - Username: {username}
790
- - Current Date: {current_date}
791
- - Timezone: {timezone}
792
- - Locale: {locale}
793
- </current_system_environment>
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
- <mermaid_diagrams>
797
- # MERMAID DIAGRAM CREATION - PERFECT SYNTAX REQUIRED!
798
- ## CRITICAL: ALL DIAGRAMS MUST RENDER WITHOUT ERRORS
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
- ### MESSAGE RULES
897
- - Communicate with dev via message tools instead of direct text responses
898
- - Reply immediately to new dev messages before other operations
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 send messages to developers with results and deliverables before signaling the completion of the task system.
904
- - Never forget to follow the "AGENT END TASK RULES" properly.
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
- ### BLUMA NOTEBOOK
1581
+ <reasoning_rules>
928
1582
  # YOUR THINKING ON A NOTEBOOK - MANDATORY USE
929
- CRITICAL: Your laptop (**sequentialThinking_nootebook**) is your ORGANIZED MIND
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 **sequentialThinking_nootebook**
1595
+ 1. Start with **reasoning_nootebook**
942
1596
  2. Break the task down into logical steps
943
1597
  3. Plan the approach - Which files? What changes? What order? 4. Track progress - Check off completed steps
944
1598
  5. Write down decisions - Why did you choose this approach?
@@ -978,6 +1632,8 @@ CRITICAL: Your laptop (**sequentialThinking_nootebook**) is your ORGANIZED MIND
978
1632
  \u{1F5F8} Set up environment
979
1633
  [ ] Configure database
980
1634
 
1635
+ </reasoning_rules>
1636
+
981
1637
  ### Tool Naming Policy
982
1638
 
983
1639
  Tool names must strictly follow the standard naming format:
@@ -988,846 +1644,817 @@ Tool names must strictly follow the standard naming format:
988
1644
  ---
989
1645
 
990
1646
  Correct Examples:
991
- - bluma_notebook
992
- - getDataTool
993
- - convertImage
994
- - userAuth
995
-
996
- ---
997
-
998
- Incorrect Examples:
999
- - sequentialThinking_nootebook:0 \u2190 contains colon and dynamic suffix
1000
- - sequentialThinking_nootebook 1 \u2190 contains space and number
1001
- - sequentialThinking_nootebook#v2 \u2190 contains special character #
1002
- - bluma__nootebook \u2190 double underscore
1003
- - sequentialThinking_Nootebook \u2190 capital letters and underscore
1004
- - bluma nootebook \u2190 contains space
1005
-
1006
- ---
1007
-
1008
- Rule Summary:
1009
- - Use only a\u2013z, 0\u20139, and underscores (_)
1010
- - Do not append suffixes like :0, :v2, etc.
1011
- - Tool names must be static and predictable
1012
- - No whitespace, no dynamic elements, no special characters
1013
-
1014
-
1015
- ### EDIT TOOL
1016
- - Use this tool to perform precise text replacements inside files based on exact literal matches.
1017
- - Can be used to create new files or directories implicitly by targeting non-existing paths.
1018
- - Suitable for inserting full content into a file even if the file does not yet exist.
1019
- - Shell access is not required for file or directory creation when using this tool.
1020
- - Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
1021
- - Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
1022
- - For creating a new file, provide an **old_string** that matches an empty string or placeholder and a complete **new_string** with the intended content.
1023
- - When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
1024
- - After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
1025
- - Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
1026
- - Track all progress related to planning and execution inside todo.md using text replacement only.
1027
-
1028
-
1029
- Real-Time Developer Messages
1030
- - During processing, the developer will send you messages.
1031
- - You MUST respond immediately via message_notify_dev, and be brief. You should use it in your next thoughts/actions.
1032
-
1033
-
1034
- ### AGENT END TASK RULES
1035
- This tool is used to signal to the system that the current task has completed and that the agent can be placed in an idle state.
1036
-
1037
-
1038
- ### QUALITY STANDARDS
1039
- - Document every major decision in Notion(
1040
- ##Important: When writing to Notion, you must strictly follow its content structure, including the correct use of headings (heading_1, heading_2, etc.) and other formatting standards. No deviations are allowed.
1041
- You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
1042
- semantically better for reading and navigation.
1043
- )
1044
- - Communicate transparently at each step
1045
- - Write clean, well-documented code
1046
- - Follow existing project conventions
1047
- - Test implementations when possible
1048
- - Ensure security and performance
1049
-
1050
- <scope_and_limitations>
1051
- # WHAT YOU DON'T HANDLE
1052
- - Non-technical questions
1053
- - Personal advice
1054
- - General conversation
1055
- - Tasks outside software development
1056
-
1057
- # IF ASKED NON-TECHNICAL QUESTIONS
1058
- - Use message_notify_dev to politely decline
1059
- - Explain you only handle technical/coding tasks
1060
- - Suggest they ask a development-related question instead
1061
- </scope_and_limitations>
1062
-
1063
- `;
1064
- function getUnifiedSystemPrompt() {
1065
- const now = /* @__PURE__ */ new Date();
1066
- const collectedData = {
1067
- os_type: os2.type(),
1068
- os_version: os2.release(),
1069
- architecture: os2.arch(),
1070
- workdir: process.cwd(),
1071
- shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
1072
- username: os2.userInfo().username || "Unknown",
1073
- current_date: now.toISOString().split("T")[0],
1074
- // Formato YYYY-MM-DD
1075
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
1076
- locale: process.env.LANG || process.env.LC_ALL || "Unknown"
1077
- };
1078
- const finalEnv = {
1079
- os_type: "Unknown",
1080
- os_version: "Unknown",
1081
- workdir: "Unknown",
1082
- shell_type: "Unknown",
1083
- username: "Unknown",
1084
- architecture: "Unknown",
1085
- current_date: "Unknown",
1086
- timezone: "Unknown",
1087
- locale: "Unknown",
1088
- ...collectedData
1089
- // Os dados coletados sobrescrevem os padrões
1090
- };
1091
- let formattedPrompt = SYSTEM_PROMPT;
1092
- for (const key in finalEnv) {
1093
- const placeholder = `{${key}}`;
1094
- formattedPrompt = formattedPrompt.replace(new RegExp(placeholder, "g"), finalEnv[key]);
1095
- }
1096
- return formattedPrompt;
1097
- }
1647
+ - bluma_notebook
1648
+ - getDataTool
1649
+ - convertImage
1650
+ - userAuth
1098
1651
 
1099
- // src/app/agent/tool_invoker.ts
1100
- import { promises as fs6 } from "fs";
1101
- import path5 from "path";
1102
- import { fileURLToPath } from "url";
1652
+ ---
1103
1653
 
1104
- // src/app/agent/tools/natives/shell_command.ts
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
- }
1654
+ Incorrect Examples:
1655
+ - reasoning_nootebook:0 \u2190 contains colon and dynamic suffix
1656
+ - reasoning_nootebook 1 \u2190 contains space and number
1657
+ - reasoning_nootebook#v2 \u2190 contains special character #
1658
+ - bluma__nootebook \u2190 double underscore
1659
+ - reasoning_nootebook \u2190 capital letters and underscore
1660
+ - bluma nootebook \u2190 contains space
1167
1661
 
1168
- // src/app/agent/tools/natives/edit.ts
1169
- import path3 from "path";
1170
- import { promises as fs2 } from "fs";
1171
- import { diffLines } from "diff";
1172
- function unescapeLlmString(inputString) {
1173
- return inputString.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\\\\/g, "\\");
1174
- }
1175
- function ensureCorrectEdit(currentContent, oldString, newString, expectedReplacements) {
1176
- let finalOldString = oldString;
1177
- let finalNewString = newString;
1178
- let occurrences = currentContent.split(finalOldString).length - 1;
1179
- if (occurrences !== expectedReplacements && occurrences === 0) {
1180
- const unescapedOldString = unescapeLlmString(oldString);
1181
- const unescapedOccurrences = currentContent.split(unescapedOldString).length - 1;
1182
- if (unescapedOccurrences > 0) {
1183
- finalOldString = unescapedOldString;
1184
- finalNewString = unescapeLlmString(newString);
1185
- occurrences = unescapedOccurrences;
1186
- } else {
1187
- const trimmedOldString = oldString.trim();
1188
- const trimmedOccurrences = currentContent.split(trimmedOldString).length - 1;
1189
- if (trimmedOccurrences > 0) {
1190
- finalOldString = trimmedOldString;
1191
- finalNewString = newString.trim();
1192
- occurrences = trimmedOccurrences;
1193
- }
1194
- }
1195
- }
1196
- return [finalOldString, finalNewString, occurrences];
1197
- }
1198
- async function calculateEdit(filePath, oldString, newString, expectedReplacements) {
1199
- let currentContent = null;
1200
- let isNewFile = false;
1201
- let error = null;
1202
- let finalNewString = unescapeLlmString(newString).replace(/\r\n/g, "\n");
1203
- let finalOldString = oldString.replace(/\r\n/g, "\n");
1204
- let occurrences = 0;
1205
- try {
1206
- currentContent = await fs2.readFile(filePath, "utf-8");
1207
- currentContent = currentContent.replace(/\r\n/g, "\n");
1208
- } catch (e) {
1209
- if (e.code !== "ENOENT") {
1210
- error = { display: `Error reading file: ${e.message}`, raw: `Error reading file ${filePath}: ${e.message}` };
1211
- return { currentContent, newContent: "", occurrences: 0, error, isNewFile };
1212
- }
1213
- }
1214
- if (currentContent === null) {
1215
- if (oldString === "") {
1216
- isNewFile = true;
1217
- occurrences = 1;
1218
- } else {
1219
- error = { display: "File not found. Cannot apply edit. Use an empty old_string to create a new file.", raw: `File not found: ${filePath}` };
1220
- }
1221
- } else {
1222
- if (oldString === "") {
1223
- error = { display: "Failed to edit. Attempted to create a file that already exists.", raw: `File already exists, cannot create: ${filePath}` };
1224
- } else {
1225
- [finalOldString, finalNewString, occurrences] = ensureCorrectEdit(currentContent, finalOldString, finalNewString, expectedReplacements);
1226
- if (occurrences === 0) {
1227
- error = { display: "Failed to edit, could not find the string to replace.", raw: `0 occurrences found for old_string in ${filePath}. Check whitespace, indentation, and context.` };
1228
- } else if (occurrences !== expectedReplacements) {
1229
- error = { display: `Failed to edit, expected ${expectedReplacements} occurrence(s) but found ${occurrences}.`, raw: `Expected ${expectedReplacements} but found ${occurrences} for old_string in ${filePath}` };
1230
- }
1231
- }
1232
- }
1233
- let newContentResult = "";
1234
- if (!error) {
1235
- if (isNewFile) {
1236
- newContentResult = finalNewString;
1237
- } else if (currentContent !== null) {
1238
- newContentResult = currentContent.replaceAll(finalOldString, finalNewString);
1239
- }
1240
- }
1241
- return { currentContent, newContent: newContentResult, occurrences, error, isNewFile };
1242
- }
1243
- function createDiff(filename, oldContent, newContent) {
1244
- const diff = diffLines(oldContent, newContent, {
1245
- // `unified: 3` é o padrão para diffs, mostrando 3 linhas de contexto.
1246
- // `newlineIsToken: true` lida melhor com mudanças de quebra de linha.
1247
- });
1248
- let diffString = `--- a/${filename}
1249
- +++ b/${filename}
1250
- `;
1251
- diff.forEach((part) => {
1252
- const prefix = part.added ? "+" : part.removed ? "-" : " ";
1253
- part.value.split("\n").slice(0, -1).forEach((line) => {
1254
- diffString += `${prefix}${line}
1255
- `;
1256
- });
1257
- });
1258
- return diffString;
1259
- }
1260
- async function editTool(args) {
1261
- const { file_path, old_string, new_string, expected_replacements = 1 } = args;
1262
- if (!path3.isAbsolute(file_path)) {
1263
- return { success: false, error: `Invalid parameters: file_path must be absolute.`, file_path };
1264
- }
1265
- if (file_path.includes("..")) {
1266
- return { success: false, error: `Invalid parameters: file_path cannot contain '..'.`, file_path };
1267
- }
1268
- try {
1269
- const editData = await calculateEdit(file_path, old_string, new_string, expected_replacements);
1270
- if (editData.error) {
1271
- return {
1272
- success: false,
1273
- error: `Execution failed: ${editData.error.display}`,
1274
- details: editData.error.raw,
1275
- file_path
1276
- };
1277
- }
1278
- await fs2.mkdir(path3.dirname(file_path), { recursive: true });
1279
- await fs2.writeFile(file_path, editData.newContent, "utf-8");
1280
- const relativePath = path3.relative(process.cwd(), file_path);
1281
- const filename = path3.basename(file_path);
1282
- if (editData.isNewFile) {
1283
- return {
1284
- success: true,
1285
- file_path,
1286
- description: `Created new file: ${relativePath}`,
1287
- message: `Created new file: ${file_path} with the provided content.`,
1288
- is_new_file: true,
1289
- occurrences: editData.occurrences,
1290
- relative_path: relativePath
1291
- };
1292
- } else {
1293
- const finalDiff = createDiff(filename, editData.currentContent || "", editData.newContent);
1294
- return {
1295
- success: true,
1296
- file_path,
1297
- description: `Modified ${relativePath} (${editData.occurrences} replacement(s)).`,
1298
- message: `Successfully modified file: ${file_path}. Diff of changes:
1299
- ${finalDiff}`,
1300
- is_new_file: false,
1301
- occurrences: editData.occurrences,
1302
- relative_path: relativePath
1303
- };
1304
- }
1305
- } catch (e) {
1306
- return {
1307
- success: false,
1308
- error: `An unexpected error occurred during the edit operation: ${e.message}`,
1309
- file_path
1310
- };
1311
- }
1312
- }
1662
+ ---
1313
1663
 
1314
- // src/app/agent/tools/natives/message.ts
1315
- import { v4 as uuidv4 } from "uuid";
1316
- function messageNotifyDev(args) {
1317
- const { text_markdown } = args;
1318
- const notification = {
1319
- type: "message_notify_dev",
1320
- id: `notify_${uuidv4()}`,
1321
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1322
- content: {
1323
- format: "markdown",
1324
- body: text_markdown
1325
- },
1326
- success: true,
1327
- delivered: true
1328
- };
1329
- return Promise.resolve(notification);
1330
- }
1664
+ Rule Summary:
1665
+ - Use only a\u2013z, 0\u20139, and underscores (_)
1666
+ - Do not append suffixes like :0, :v2, etc.
1667
+ - Tool names must be static and predictable
1668
+ - No whitespace, no dynamic elements, no special characters
1669
+
1670
+
1671
+ <edit_tool_rules>
1672
+ - Use this tool to perform precise text replacements inside files based on exact literal matches.
1673
+ - Can be used to create new files or directories implicitly by targeting non-existing paths.
1674
+ - Suitable for inserting full content into a file even if the file does not yet exist.
1675
+ - Shell access is not required for file or directory creation when using this tool.
1676
+ - Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
1677
+ - Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
1678
+ - For creating a new file, provide an **old_string** that matches an empty string or placeholder and a complete **new_string** with the intended content.
1679
+ - When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
1680
+ - After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
1681
+ - Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
1682
+ - Track all progress related to planning and execution inside todo.md using text replacement only.
1683
+ </edit_tool_rules>
1684
+
1685
+ Real-Time Developer Messages
1686
+ - During processing, the developer will send you messages.
1687
+ - You MUST respond immediately via message_notify_dev, and be brief. You should use it in your next thoughts/actions.
1688
+
1689
+
1690
+ <agent_end_task_rules>
1691
+ This tool is mandatory.
1692
+ You must use it to inform developer {username} that the task has been completed and that there are no further pending actions, in accordance with the objectives defined for the task.
1693
+ </agent_end_task_rules>
1694
+
1695
+ ### QUALITY STANDARDS
1696
+ - Document every major decision in Notion(
1697
+ ##Important: When writing to Notion, you must strictly follow its content structure, including the correct use of headings (heading_1, heading_2, etc.) and other formatting standards. No deviations are allowed.
1698
+ You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
1699
+ semantically better for reading and navigation.
1700
+ )
1701
+ - Communicate transparently at each step
1702
+ - Write clean, well-documented code
1703
+ - Follow existing project conventions
1704
+ - Test implementations when possible
1705
+ - Ensure security and performance
1331
1706
 
1332
- // src/app/agent/tools/natives/ls.ts
1333
- import { promises as fs3 } from "fs";
1334
- import path4 from "path";
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
- }
1707
+ <scope_and_limitations>
1708
+ # WHAT YOU DON'T HANDLE
1709
+ - Non-technical questions
1710
+ - Personal advice
1711
+ - General conversation
1712
+ - Tasks outside software development
1412
1713
 
1413
- // 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
- }
1714
+ # IF ASKED NON-TECHNICAL QUESTIONS
1715
+ - Use message_notify_dev to politely decline
1716
+ - Explain you only handle technical/coding tasks
1717
+ - Suggest they ask a development-related question instead
1718
+ </scope_and_limitations>
1449
1719
 
1450
- // src/app/agent/tools/natives/count_lines.ts
1451
- import { createReadStream } from "fs";
1452
- import { promises as fs5 } from "fs";
1453
- import readline from "readline";
1454
- async function countLines(args) {
1455
- const { filepath } = args;
1456
- try {
1457
- if (!(await fs5.stat(filepath)).isFile()) {
1458
- throw new Error(`File '${filepath}' not found or is not a file.`);
1459
- }
1460
- const fileStream = createReadStream(filepath);
1461
- const rl = readline.createInterface({
1462
- input: fileStream,
1463
- crlfDelay: Infinity
1464
- });
1465
- let lineCount = 0;
1466
- for await (const line of rl) {
1467
- lineCount++;
1468
- }
1469
- return { success: true, filepath, line_count: lineCount };
1470
- } catch (e) {
1471
- return { success: false, error: e.message };
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/tool_invoker.ts
1476
- var ToolInvoker = class {
1477
- // Mapa privado para associar nomes de ferramentas às suas funções de implementação.
1478
- toolImplementations;
1479
- // Propriedade privada para armazenar as definições de ferramentas carregadas do JSON.
1480
- toolDefinitions = [];
1481
- constructor() {
1482
- this.toolImplementations = /* @__PURE__ */ new Map();
1483
- this.registerTools();
1484
- }
1485
- /**
1486
- * Carrega as definições de ferramentas do arquivo de configuração `native_tools.json`.
1487
- * Este método é assíncrono e deve ser chamado após a criação da instância.
1488
- */
1489
- async initialize() {
1490
- try {
1491
- const __filename = fileURLToPath(import.meta.url);
1492
- const __dirname = path5.dirname(__filename);
1493
- const configPath = path5.resolve(__dirname, "config", "native_tools.json");
1494
- const fileContent = await fs6.readFile(configPath, "utf-8");
1495
- const config2 = JSON.parse(fileContent);
1496
- this.toolDefinitions = config2.nativeTools;
1497
- } catch (error) {
1498
- console.error("[ToolInvoker] Erro cr\xEDtico ao carregar 'native_tools.json'. As ferramentas nativas n\xE3o estar\xE3o dispon\xEDveis.", error);
1499
- this.toolDefinitions = [];
1500
- }
1756
+ // src/app/agent/core/context-api/context_manager.ts
1757
+ function createApiContextWindow(fullHistory, maxTurns) {
1758
+ if (!fullHistory.length) {
1759
+ return [];
1501
1760
  }
1502
- /**
1503
- * 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." }));
1761
+ if (maxTurns === null || maxTurns === void 0) {
1762
+ return [...fullHistory];
1514
1763
  }
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;
1764
+ const systemMessages = [];
1765
+ let historyStartIndex = 0;
1766
+ while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
1767
+ systemMessages.push(fullHistory[historyStartIndex]);
1768
+ historyStartIndex++;
1521
1769
  }
1522
- /**
1523
- * 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.` };
1770
+ const conversationHistory = fullHistory.slice(historyStartIndex);
1771
+ const turns = [];
1772
+ let currentTurn = [];
1773
+ let turnsFound = 0;
1774
+ const isDevOverlay = (m) => m?.role === "user" && m?.name === "dev_overlay";
1775
+ for (let i = conversationHistory.length - 1; i >= 0; i--) {
1776
+ const msg = conversationHistory[i];
1777
+ currentTurn.unshift(msg);
1778
+ const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some((tc) => tc.function.name === "agent_end_task");
1779
+ if (endsWithAgentEnd) {
1780
+ turns.unshift([...currentTurn]);
1781
+ currentTurn = [];
1782
+ turnsFound++;
1783
+ if (turnsFound >= maxTurns) {
1784
+ break;
1785
+ }
1786
+ continue;
1532
1787
  }
1533
- 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}` };
1788
+ const prev = conversationHistory[i - 1];
1789
+ if (msg.role === "user" && !isDevOverlay(msg)) {
1790
+ if (prev && prev.role === "assistant" && !prev.tool_calls?.some((tc) => tc.function.name === "agent_end_task")) {
1791
+ const hasNonOverlay = currentTurn.some((m) => m.role !== "user" || !isDevOverlay(m));
1792
+ if (hasNonOverlay) {
1793
+ turns.unshift([...currentTurn]);
1794
+ currentTurn = [];
1795
+ }
1796
+ }
1538
1797
  }
1539
1798
  }
1540
- };
1799
+ if (currentTurn.length > 0) {
1800
+ turns.unshift(currentTurn);
1801
+ }
1802
+ const finalContext = systemMessages.concat(turns.flat());
1803
+ return finalContext;
1804
+ }
1541
1805
 
1542
- // src/app/agent/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;
1806
+ // src/app/agent/bluma/core/bluma.ts
1807
+ var BluMaAgent = class {
1808
+ llm;
1809
+ deploymentName;
1810
+ sessionId;
1811
+ sessionFile = "";
1812
+ history = [];
1554
1813
  eventBus;
1555
- // <<< ADICIONA A PROPRIEDADE
1556
- constructor(nativeToolInvoker, eventBus2) {
1557
- this.nativeToolInvoker = nativeToolInvoker;
1814
+ mcpClient;
1815
+ feedbackSystem;
1816
+ maxContextTurns = 300;
1817
+ isInterrupted = false;
1818
+ constructor(sessionId2, eventBus2, llm, deploymentName, mcpClient, feedbackSystem) {
1819
+ this.sessionId = sessionId2;
1558
1820
  this.eventBus = eventBus2;
1821
+ this.llm = llm;
1822
+ this.deploymentName = deploymentName;
1823
+ this.mcpClient = mcpClient;
1824
+ this.feedbackSystem = feedbackSystem;
1825
+ this.eventBus.on("user_interrupt", () => {
1826
+ this.isInterrupted = true;
1827
+ });
1828
+ this.eventBus.on("dev_overlay", async (data) => {
1829
+ const clean = String(data.payload ?? "").trim();
1830
+ this.history.push({ role: "user", name: "dev_overlay", content: clean });
1831
+ this.eventBus.emit("backend_message", { type: "dev_overlay", payload: clean, ts: data.ts || Date.now() });
1832
+ try {
1833
+ if (this.sessionFile) {
1834
+ await saveSessionHistory(this.sessionFile, this.history);
1835
+ }
1836
+ } catch (e) {
1837
+ this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s dev_overlay: ${e.message}` });
1838
+ }
1839
+ });
1559
1840
  }
1560
- // ... (método initialize inalterado) ...
1561
1841
  async initialize() {
1562
- const nativeTools = this.nativeToolInvoker.getToolDefinitions();
1563
- this.globalToolsForLlm.push(...nativeTools);
1564
- for (const tool of nativeTools) {
1565
- const toolName = tool.function.name;
1566
- this.toolToServerMap.set(toolName, {
1567
- server: "native",
1568
- originalName: toolName
1569
- });
1842
+ await this.mcpClient.nativeToolInvoker.initialize();
1843
+ await this.mcpClient.initialize();
1844
+ const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
1845
+ this.sessionFile = sessionFile;
1846
+ this.history = history;
1847
+ if (this.history.length === 0) {
1848
+ const systemPrompt = getUnifiedSystemPrompt();
1849
+ this.history.push({ role: "system", content: systemPrompt });
1850
+ await saveSessionHistory(this.sessionFile, this.history);
1570
1851
  }
1571
- const __filename = fileURLToPath2(import.meta.url);
1572
- const __dirname = path6.dirname(__filename);
1573
- const defaultConfigPath = path6.resolve(__dirname, "config", "bluma-mcp.json");
1574
- const userConfigPath = path6.join(os4.homedir(), ".bluma-cli", "bluma-mcp.json");
1575
- const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
1576
- const userConfig = await this.loadMcpConfig(userConfigPath, "User");
1577
- const mergedConfig = {
1578
- mcpServers: {
1579
- ...defaultConfig.mcpServers || {},
1580
- ...userConfig.mcpServers || {}
1852
+ }
1853
+ getAvailableTools() {
1854
+ return this.mcpClient.getAvailableTools();
1855
+ }
1856
+ getUiToolsDetailed() {
1857
+ return this.mcpClient.getAvailableToolsDetailed();
1858
+ }
1859
+ async processTurn(userInput) {
1860
+ this.isInterrupted = false;
1861
+ const inputText = String(userInput.content || "").trim();
1862
+ this.history.push({ role: "user", content: inputText });
1863
+ await this._continueConversation();
1864
+ }
1865
+ async handleToolResponse(decisionData) {
1866
+ const toolCall = decisionData.tool_calls[0];
1867
+ let toolResultContent;
1868
+ let shouldContinueConversation = true;
1869
+ if (!this.sessionFile) {
1870
+ const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
1871
+ this.sessionFile = sessionFile;
1872
+ if (this.history.length === 0 && history.length > 0) {
1873
+ this.history = history;
1581
1874
  }
1582
- };
1583
- if (Object.keys(mergedConfig.mcpServers).length === 0) {
1584
- return;
1585
1875
  }
1586
- const serverEntries = Object.entries(mergedConfig.mcpServers);
1587
- for (const [serverName, serverConf] of serverEntries) {
1876
+ if (decisionData.type === "user_decision_execute") {
1877
+ const toolName = toolCall.function.name;
1878
+ const toolArgs = JSON.parse(toolCall.function.arguments);
1879
+ let previewContent;
1880
+ if (toolName === "edit_tool") {
1881
+ previewContent = await this._generateEditPreview(toolArgs);
1882
+ }
1883
+ this.eventBus.emit("backend_message", {
1884
+ type: "tool_call",
1885
+ tool_name: toolName,
1886
+ arguments: toolArgs,
1887
+ preview: previewContent
1888
+ });
1588
1889
  try {
1589
- this.eventBus.emit("backend_message", {
1590
- type: "connection_status",
1591
- message: `${serverName} server is being connected...`
1592
- });
1593
- if (serverConf.type === "stdio") {
1594
- await this.connectToStdioServer(serverName, serverConf);
1595
- } else if (serverConf.type === "sse") {
1596
- console.warn(`[MCPClient] Conex\xE3o com servidores SSE (como '${serverName}') ainda n\xE3o implementada.`);
1890
+ if (this.isInterrupted) {
1891
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
1892
+ return;
1597
1893
  }
1894
+ const result = await this.mcpClient.invoke(toolName, toolArgs);
1895
+ let finalResult = result;
1896
+ if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
1897
+ finalResult = result[0].text;
1898
+ }
1899
+ toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
1598
1900
  } catch (error) {
1599
- this.eventBus.emit("backend_message", {
1600
- type: "error",
1601
- message: `Failed to connect to server '${serverName}'.`
1901
+ toolResultContent = JSON.stringify({
1902
+ error: `Tool execution failed: ${error.message}`,
1903
+ details: error.data || "No additional details."
1602
1904
  });
1603
1905
  }
1604
- }
1605
- }
1606
- async loadMcpConfig(configPath, configType) {
1607
- try {
1608
- const fileContent = await fs7.readFile(configPath, "utf-8");
1609
- const processedContent = this.replaceEnvPlaceholders(fileContent);
1610
- return JSON.parse(processedContent);
1611
- } catch (error) {
1612
- if (error.code === "ENOENT") {
1613
- if (configType === "User") {
1614
- }
1615
- } else {
1616
- console.warn(`[MCPClient] Warning: Error reading ${configType} config file ${configPath}.`, error);
1617
- }
1618
- return {};
1619
- }
1620
- }
1621
- /**
1622
- * Conecta-se a um servidor MCP baseado em Stdio, adaptando o comando para o SO atual.
1623
- */
1624
- async connectToStdioServer(serverName, config2) {
1625
- let commandToExecute = config2.command;
1626
- let argsToExecute = config2.args || [];
1627
- const isWindows = os4.platform() === "win32";
1628
- if (!isWindows && commandToExecute.toLowerCase() === "cmd") {
1629
- if (argsToExecute.length >= 2 && argsToExecute[0].toLowerCase() === "/c") {
1630
- commandToExecute = argsToExecute[1];
1631
- argsToExecute = argsToExecute.slice(2);
1632
- } else {
1633
- console.warn(`[MCPClient] Formato de comando 'cmd /c' inesperado para '${serverName}' em sistema n\xE3o-Windows. O servidor ser\xE1 ignorado.`);
1634
- return;
1906
+ this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
1907
+ if (toolName.includes("agent_end_task")) {
1908
+ shouldContinueConversation = false;
1909
+ this.eventBus.emit("backend_message", { type: "done", status: "completed" });
1635
1910
  }
1636
- }
1637
- const transport = new StdioClientTransport({
1638
- command: commandToExecute,
1639
- // Usa o comando adaptado
1640
- args: argsToExecute,
1641
- // Usa os argumentos adaptados
1642
- env: config2.env
1643
- });
1644
- const mcp = new Client({ name: `bluma-cli-client-for-${serverName}`, version: "1.0.0" });
1645
- await mcp.connect(transport);
1646
- this.sessions.set(serverName, mcp);
1647
- const toolsResult = await mcp.listTools();
1648
- for (const tool of toolsResult.tools) {
1649
- const prefixedToolName = `${serverName}_${tool.name}`;
1650
- this.globalToolsForLlm.push({
1651
- type: "function",
1652
- function: {
1653
- name: prefixedToolName,
1654
- description: tool.description || "",
1655
- parameters: tool.inputSchema
1656
- }
1657
- });
1658
- this.toolToServerMap.set(prefixedToolName, {
1659
- server: serverName,
1660
- originalName: tool.name
1661
- });
1662
- }
1663
- }
1664
- async invoke(toolName, args) {
1665
- const route = this.toolToServerMap.get(toolName);
1666
- if (!route) {
1667
- return { error: `Ferramenta '${toolName}' n\xE3o encontrada ou registrada.` };
1668
- }
1669
- if (route.server === "native") {
1670
- return this.nativeToolInvoker.invoke(route.originalName, args);
1671
1911
  } else {
1672
- const session = this.sessions.get(route.server);
1673
- if (!session) {
1674
- return { error: `Sess\xE3o para o servidor '${route.server}' n\xE3o encontrada.` };
1675
- }
1676
- const result = await session.callTool({ name: route.originalName, arguments: args });
1677
- return result.content;
1912
+ toolResultContent = "The user declined to execute this tool...";
1678
1913
  }
1679
- }
1680
- getAvailableTools() {
1681
- return this.globalToolsForLlm;
1682
- }
1683
- // New: detailed list for UI with origin metadata
1684
- getAvailableToolsDetailed() {
1685
- const detailed = [];
1686
- for (const tool of this.globalToolsForLlm) {
1687
- const name = tool.function?.name;
1688
- if (!name) continue;
1689
- const route = this.toolToServerMap.get(name);
1690
- if (!route) continue;
1691
- const source = route.server === "native" ? "native" : "mcp";
1692
- detailed.push({ ...tool, source, server: route.server, originalName: route.originalName });
1914
+ this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
1915
+ await saveSessionHistory(this.sessionFile, this.history);
1916
+ if (shouldContinueConversation && !this.isInterrupted) {
1917
+ await this._continueConversation();
1693
1918
  }
1694
- return detailed;
1695
1919
  }
1696
- async close() {
1697
- for (const [name, session] of this.sessions.entries()) {
1698
- try {
1699
- await session.close();
1700
- } catch (error) {
1701
- console.error(`[MCPClient] Erro ao encerrar conex\xE3o com '${name}':`, error);
1920
+ async _generateEditPreview(toolArgs) {
1921
+ try {
1922
+ const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
1923
+ if (editData.error) {
1924
+ return `Failed to generate diff:
1925
+
1926
+ ${editData.error.display}`;
1927
+ }
1928
+ const filename = path7.basename(toolArgs.file_path);
1929
+ return createDiff(filename, editData.currentContent || "", editData.newContent);
1930
+ } catch (e) {
1931
+ return `An unexpected error occurred while generating the edit preview: ${e.message}`;
1932
+ }
1933
+ }
1934
+ async _continueConversation() {
1935
+ try {
1936
+ if (this.isInterrupted) {
1937
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
1938
+ return;
1939
+ }
1940
+ const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
1941
+ const response = await this.llm.chatCompletion({
1942
+ model: this.deploymentName,
1943
+ messages: contextWindow,
1944
+ tools: this.mcpClient.getAvailableTools(),
1945
+ tool_choice: "required",
1946
+ parallel_tool_calls: false
1947
+ });
1948
+ if (this.isInterrupted) {
1949
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
1950
+ return;
1951
+ }
1952
+ const message = response.choices[0].message;
1953
+ this.history.push(message);
1954
+ if (message.tool_calls) {
1955
+ const autoApprovedTools = ["agent_end_task", "message_notify_dev", "reasoning_nootebook"];
1956
+ const toolToCall = message.tool_calls[0];
1957
+ const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
1958
+ if (isSafeTool) {
1959
+ await this.handleToolResponse({ type: "user_decision_execute", tool_calls: message.tool_calls });
1960
+ } else {
1961
+ const toolName = toolToCall.function.name;
1962
+ if (toolName === "edit_tool") {
1963
+ const args = JSON.parse(toolToCall.function.arguments);
1964
+ const previewContent = await this._generateEditPreview(args);
1965
+ this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: message.tool_calls, preview: previewContent });
1966
+ } else {
1967
+ this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: message.tool_calls });
1968
+ }
1969
+ }
1970
+ } else if (message.content) {
1971
+ this.eventBus.emit("backend_message", { type: "assistant_message", content: message.content });
1972
+ const feedback = this.feedbackSystem.generateFeedback({
1973
+ event: "protocol_violation_direct_text",
1974
+ details: { violationContent: message.content }
1975
+ });
1976
+ this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.message, content: message.content });
1977
+ this.history.push({ role: "system", content: feedback.correction });
1978
+ await this._continueConversation();
1979
+ } else {
1980
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent is thinking... continuing reasoning cycle." });
1981
+ await this._continueConversation();
1702
1982
  }
1983
+ } catch (error) {
1984
+ const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
1985
+ this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
1986
+ } finally {
1987
+ await saveSessionHistory(this.sessionFile, this.history);
1703
1988
  }
1704
1989
  }
1705
- replaceEnvPlaceholders(content) {
1706
- return content.replace(/\$\{([A-Za-z0-9_]+)\}/g, (match, varName) => {
1707
- return process.env[varName] || match;
1990
+ };
1991
+
1992
+ // src/app/agent/core/llm.ts
1993
+ var OpenAIAdapter = class {
1994
+ client;
1995
+ constructor(client) {
1996
+ this.client = client;
1997
+ }
1998
+ async chatCompletion(params) {
1999
+ const resp = await this.client.chat.completions.create({
2000
+ model: params.model,
2001
+ messages: params.messages,
2002
+ tools: params.tools,
2003
+ tool_choice: params.tool_choice,
2004
+ parallel_tool_calls: params.parallel_tool_calls
1708
2005
  });
2006
+ return resp;
1709
2007
  }
1710
2008
  };
1711
2009
 
1712
- // src/app/agent/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
- };
1734
- }
1735
- return { score: 0, message: "No feedback for this event.", correction: "" };
2010
+ // src/app/agent/subagents/registry.ts
2011
+ var subAgentRegistry = {};
2012
+ function registerSubAgent(subAgent) {
2013
+ for (const cap of subAgent.capabilities) {
2014
+ subAgentRegistry[cap] = subAgent;
1736
2015
  }
1737
- getCumulativeScore() {
1738
- return this.cumulativeScore;
2016
+ }
2017
+ function getSubAgentByCommand(cmd) {
2018
+ return subAgentRegistry[cmd];
2019
+ }
2020
+
2021
+ // src/app/agent/subagents/init/init_system_prompt.ts
2022
+ import os5 from "os";
2023
+ var SYSTEM_PROMPT2 = `
2024
+
2025
+ ### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
2026
+ You extend the BluMa multi-agent architecture and handle the project bootstrapping/init workflow: scanning the repository, inferring stack, and generating a high-quality BluMa.md with actionable project context.
2027
+
2028
+ ---
2029
+
2030
+ ## BEHAVIORAL RULES
2031
+
2032
+ - Identity:
2033
+ You are BluMa InitSubAgent. Maintain professionalism and technical language.
2034
+
2035
+ - Communication:
2036
+ ALL messages must be sent via 'message_notify_dev'.
2037
+ No direct text replies to the developer.
2038
+
2039
+ - Task Completion:
2040
+ When the init task is completed, immediately invoke 'agent_end_task' without dev permissions.
2041
+
2042
+ - Tool Rules:
2043
+ Never make parallel tool calls.
2044
+ Only use the defined tools with their exact names.
2045
+
2046
+ - Autonomy:
2047
+ Act 100% autonomously.
2048
+ Do not ask for formatting preferences.
2049
+ Use the notebook for internal reasoning.
2050
+
2051
+
2052
+ ### CRITICAL COMMUNICATION PROTOCOL
2053
+ - Only tool_calls are allowed for assistant replies. Never include a "content" field.
2054
+ - Always use tools to respond, retrieve data, compute or transform. Await a valid tool response before any final message.
2055
+ - Zero tolerance for protocol violations.
2056
+
2057
+ <current_system_environment>
2058
+ - Operating System: {os_type} ({os_version})
2059
+ - Architecture: {architecture}
2060
+ - Current Working Directory: {workdir}
2061
+ - Shell: {shell_type}
2062
+ - Username: {username}
2063
+ - Current Date: {current_date}
2064
+ - Timezone: {timezone}
2065
+ - Locale: {locale}
2066
+ </current_system_environment>
2067
+
2068
+ <message_rules>
2069
+ - Communicate with dev's via message tools instead of direct text responses
2070
+ - Reply immediately to new user messages before other operations
2071
+ - First reply must be brief, only confirming receipt without specific solutions
2072
+ - Notify dev's with brief explanation when changing methods or strategies
2073
+ - Message tools are divided into notify (non-blocking, no reply needed) and ask (blocking)
2074
+ - Actively use notify for progress updates, reserve ask for essential needs to avoid blocking
2075
+ - Must message dev's with results and deliverables before upon task completion 'agent_end_task'
2076
+ </message_rules>
2077
+
2078
+ <reasoning_rules>
2079
+ # YOUR THINKING ON A NOTEBOOK - MANDATORY USE
2080
+ CRITICAL: Your laptop (reasoning_nootebook) is your ORGANIZED MIND
2081
+ ## IMPORTANT
2082
+ ## NEVER PUT CHECKLISTS OR STEPS IN THE THOUGHT TEXT
2083
+ ## ALWAYS USE A NOTEBOOK (Always for):
2084
+ - ANY task
2085
+ - Before starting development (plan first!)
2086
+ - Projects with multiple files (organize the structure)
2087
+ - Debugging sessions (monitor discoveries)
2088
+ - Extensive refactoring (map the changes)
2089
+ - Architectural decisions (think through the options)
2090
+
2091
+ ## HOW TO USE A NOTEBOOK:
2092
+ 1. Start with reasoning_nootebook
2093
+ 2. Break the task down into logical steps
2094
+ 3. Plan the approach \u2013 Which files? What changes? What order?
2095
+ 4. Track progress \u2013 Check off completed steps
2096
+ 5. Write down decisions \u2013 Why did you choose this approach?
2097
+ 6. Update continuously \u2013 Keep the notebook up to date
2098
+
2099
+ ## THE NOTEBOOK PREVENTS:
2100
+ - Acting "outside the box"
2101
+ - Forgetting task requirements
2102
+ - Losing control of complex workflows
2103
+ - Making unplanned changes
2104
+ - Ineffective approaches
2105
+ - Working without a clear roadmap
2106
+ - Jumping between unrelated subtasks
2107
+
2108
+ Important rule:
2109
+ Do not include future steps/to-dos in thought; put them strictly in remaining_tasks, using the mandated checklist markers.
2110
+
2111
+ - remaining_tasks: Checklist list of high-level upcoming tasks.
2112
+ Format is mandatory:
2113
+ - "\u{1F5F8}" \u2192 for tasks not yet done (pending)
2114
+ - "[ ]" \u2192 for tasks already completed
2115
+ </reasoning_rules>
2116
+
2117
+ <edit_tool_rules>
2118
+ - Use this tool to perform precise text replacements inside files based on exact literal matches.
2119
+ - Can be used to create new files or directories implicitly by targeting non-existing paths.
2120
+ - Suitable for inserting full content into a file even if the file does not yet exist.
2121
+ - Shell access is not required for file or directory creation when using this tool.
2122
+ - Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
2123
+ - Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
2124
+ - For creating a new file, provide an **old_string** that matches an empty string or placeholder and a complete **new_string** with the intended content.
2125
+ - When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
2126
+ - After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
2127
+ - Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
2128
+ - Track all progress related to planning and execution inside todo.md using text replacement only.
2129
+ </edit_tool_rules>
2130
+
2131
+
2132
+ ### Tool Naming Policy
2133
+ - Use plain, unmodified, lowercase tool names
2134
+ - No special characters, spaces, or version suffixes
2135
+
2136
+ Rule Summary:
2137
+ - Use only a\u2013z, 0\u20139, and underscores (_)
2138
+ - Do not append suffixes like :0, :v2, etc.
2139
+ - Tool names must be static and predictable
2140
+
2141
+
2142
+ ## INIT SUBAGENT OBJECTIVE
2143
+ - Map repository structure and significant files.
2144
+ - Infer tech stack (frameworks, package managers, languages, build/test tools).
2145
+ - Identify entry points, configuration files, and scripts.
2146
+ - Produce BluMa.md with:
2147
+ - Project overview and goals inferred from code/docs
2148
+ - Tech stack summary
2149
+ - Directory map (high-level)
2150
+ - Key configs and scripts
2151
+ - Known tasks or next steps for agents
2152
+ - Always use tools (ls, readLines, count_lines, shell_command, edit_tool) to gather evidence before writing.
2153
+ - Never invent file content. Read files via tools to confirm.
2154
+
2155
+ ## OUTPUT & PROTOCOLS
2156
+ - Emit 'backend_message' events through tools only (message_notify_dev) for progress updates.
2157
+ - Before writing BluMa.md, propose structure via message_notify_dev and proceed using edit_tool.
2158
+ - If an irreversible operation is needed (e.g., overwriting an existing BluMa.md), issue 'confirmation_request' unless dev policy indicates auto-approval.
2159
+ - Never send or present draft versions of BluMa.md. Only produce and deliver the final, validated BluMa.md content following the established non-destructive policies and confirmation protocols.
2160
+ - On successful generation of BluMa.md, emit 'done' with status 'completed' and call agent_end_task.
2161
+
2162
+ ## SAFETY & QUALITY
2163
+ - Be conservative with edits; generate previews (diff) for edit_tool where applicable.
2164
+ - Keep file system operations idempotent and explicit.
2165
+ - Prefer performance-efficient scans (avoid reading entire large binaries).
2166
+ - Respect test environment constraints.
2167
+
2168
+ ## EXEMPLAR FLOW (GUIDELINE)
2169
+ 1) Explore repo: ls + targeted readLines for key files (package.json, tsconfig.json, README, etc.)
2170
+ 2) Synthesize stack and structure with citations of evidence (file paths) in the notebook
2171
+ 3) Draft BluMa.md structure (message_notify_dev)
2172
+ 4) Write BluMa.md via edit_tool
2173
+ 5) Announce completion and agent_end_task
2174
+
2175
+
2176
+ `;
2177
+ function getInitPrompt() {
2178
+ const now = /* @__PURE__ */ new Date();
2179
+ const collectedData = {
2180
+ os_type: os5.type(),
2181
+ os_version: os5.release(),
2182
+ architecture: os5.arch(),
2183
+ workdir: process.cwd(),
2184
+ shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
2185
+ username: os5.userInfo().username || "Unknown",
2186
+ current_date: now.toISOString().split("T")[0],
2187
+ // Formato YYYY-MM-DD
2188
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
2189
+ locale: process.env.LANG || process.env.LC_ALL || "Unknown"
2190
+ };
2191
+ const finalEnv = {
2192
+ os_type: "Unknown",
2193
+ os_version: "Unknown",
2194
+ workdir: "Unknown",
2195
+ shell_type: "Unknown",
2196
+ username: "Unknown",
2197
+ architecture: "Unknown",
2198
+ current_date: "Unknown",
2199
+ timezone: "Unknown",
2200
+ locale: "Unknown",
2201
+ ...collectedData
2202
+ // Os dados coletados sobrescrevem os padrões
2203
+ };
2204
+ let formattedPrompt = SYSTEM_PROMPT2;
2205
+ for (const key in finalEnv) {
2206
+ const placeholder = `{${key}}`;
2207
+ formattedPrompt = formattedPrompt.replace(new RegExp(placeholder, "g"), finalEnv[key]);
1739
2208
  }
1740
- };
2209
+ return formattedPrompt;
2210
+ }
1741
2211
 
1742
- // src/app/agent/core/context-api/context_manager.ts
1743
- function createApiContextWindow(fullHistory, maxTurns) {
1744
- if (!fullHistory.length) {
1745
- return [];
2212
+ // src/app/agent/subagents/base_llm_subagent.ts
2213
+ var BaseLLMSubAgent = class {
2214
+ ctx;
2215
+ history = [];
2216
+ sessionFile = "";
2217
+ maxContextTurns = 160;
2218
+ isInterrupted = false;
2219
+ async execute(input, ctx) {
2220
+ this.ctx = ctx;
2221
+ this.isInterrupted = false;
2222
+ this.ctx.eventBus.on("user_interrupt", () => {
2223
+ this.isInterrupted = true;
2224
+ });
2225
+ await this.initializeHistory();
2226
+ this.history.push({ role: "user", content: typeof input === "string" ? input : JSON.stringify(input) });
2227
+ await this._continueConversation();
2228
+ return { history: this.history };
1746
2229
  }
1747
- if (maxTurns === null || maxTurns === void 0) {
1748
- return [...fullHistory];
2230
+ async initializeHistory() {
2231
+ const sessionId2 = `${this.id}`;
2232
+ const [sessionFile, history] = await loadOrcreateSession(sessionId2);
2233
+ this.sessionFile = sessionFile;
2234
+ this.history = history || [];
2235
+ const systemPromptContent = getInitPrompt();
2236
+ if (this.history.length === 0) {
2237
+ this.history.push({
2238
+ role: "system",
2239
+ content: systemPromptContent
2240
+ });
2241
+ await saveSessionHistory(this.sessionFile, this.history);
2242
+ }
1749
2243
  }
1750
- const systemMessages = [];
1751
- let historyStartIndex = 0;
1752
- while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
1753
- systemMessages.push(fullHistory[historyStartIndex]);
1754
- historyStartIndex++;
2244
+ async _generateEditPreview(toolArgs) {
2245
+ try {
2246
+ const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
2247
+ if (editData.error) {
2248
+ return `Failed to generate diff:
2249
+
2250
+ ${editData.error.display}`;
2251
+ }
2252
+ const filename = toolArgs.file_path?.split(/[\/]/).pop() || "file";
2253
+ return createDiff(filename, editData.currentContent || "", editData.newContent);
2254
+ } catch (e) {
2255
+ return `An unexpected error occurred while generating the edit preview: ${e.message}`;
2256
+ }
1755
2257
  }
1756
- 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;
2258
+ async _continueConversation() {
2259
+ try {
2260
+ if (this.isInterrupted) {
2261
+ this.emitEvent("info", { message: "SubAgent task cancelled by user." });
2262
+ return;
2263
+ }
2264
+ const contextWindow = this.history.slice(-this.maxContextTurns);
2265
+ const response = await this.ctx.llm.chatCompletion({
2266
+ model: this.ctx.policy?.llmDeployment || "default",
2267
+ messages: contextWindow,
2268
+ tools: this.ctx.mcpClient.getAvailableTools(),
2269
+ tool_choice: "required",
2270
+ parallel_tool_calls: false
2271
+ });
2272
+ if (this.isInterrupted) {
2273
+ this.emitEvent("info", { message: "SubAgent task cancelled by user." });
2274
+ return;
1771
2275
  }
1772
- continue;
2276
+ const message = response.choices[0].message;
2277
+ this.history.push(message);
2278
+ if (message.tool_calls) {
2279
+ await this._handleToolExecution({ type: "user_decision_execute", tool_calls: message.tool_calls });
2280
+ const lastToolName = message.tool_calls[0].function.name;
2281
+ if (!lastToolName.includes("agent_end_task") && !this.isInterrupted) {
2282
+ await this._continueConversation();
2283
+ }
2284
+ } else if (message.content) {
2285
+ this.emitEvent("assistant_message", { content: message.content });
2286
+ this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content: message.content });
2287
+ } else {
2288
+ this.emitEvent("info", { message: "SubAgent is thinking... continuing reasoning cycle." });
2289
+ }
2290
+ } catch (error) {
2291
+ const errorMessage = error?.message || "An unknown API error occurred.";
2292
+ this.emitEvent("error", { message: errorMessage });
2293
+ } finally {
2294
+ if (this.sessionFile) await saveSessionHistory(this.sessionFile, this.history);
1773
2295
  }
1774
- const prev = conversationHistory[i - 1];
1775
- if (msg.role === "user" && !isDevOverlay(msg)) {
1776
- if (prev && prev.role === "assistant" && !prev.tool_calls?.some((tc) => tc.function.name === "agent_end_task")) {
1777
- const hasNonOverlay = currentTurn.some((m) => m.role !== "user" || !isDevOverlay(m));
1778
- if (hasNonOverlay) {
1779
- turns.unshift([...currentTurn]);
1780
- currentTurn = [];
2296
+ }
2297
+ // MÉTODO REMOVIDO
2298
+ // public async handleConfirmation(decisionData: { type: string; tool_calls: any[] }) {
2299
+ // await this._handleToolExecution(decisionData);
2300
+ // if (!this.isInterrupted) {
2301
+ // await this._continueConversation();
2302
+ // }
2303
+ // }
2304
+ async _handleToolExecution(decisionData) {
2305
+ const toolCall = decisionData.tool_calls[0];
2306
+ let toolResultContent;
2307
+ if (decisionData.type === "user_decision_execute") {
2308
+ const toolName = toolCall.function.name;
2309
+ const toolArgs = JSON.parse(toolCall.function.arguments);
2310
+ let previewContent;
2311
+ if (toolName === "edit_tool") {
2312
+ previewContent = await this._generateEditPreview(toolArgs);
2313
+ }
2314
+ this.emitEvent("tool_call", {
2315
+ tool_name: toolName,
2316
+ arguments: toolArgs,
2317
+ preview: previewContent
2318
+ });
2319
+ try {
2320
+ if (this.isInterrupted) {
2321
+ this.emitEvent("info", { message: "SubAgent task cancelled before tool execution." });
2322
+ toolResultContent = JSON.stringify({ error: "Task cancelled by user before execution." });
2323
+ } else {
2324
+ const result = await this.ctx.mcpClient.invoke(toolName, toolArgs);
2325
+ let finalResult = result;
2326
+ if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
2327
+ finalResult = result[0].text;
2328
+ }
2329
+ toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
1781
2330
  }
2331
+ } catch (error) {
2332
+ toolResultContent = JSON.stringify({ error: `Tool execution failed: ${error.message}`, details: error.data || "No additional details." });
1782
2333
  }
2334
+ this.emitEvent("tool_result", { tool_name: toolName, result: toolResultContent });
2335
+ if (toolName.includes("agent_end_task")) {
2336
+ this.emitEvent("done", { status: "completed" });
2337
+ }
2338
+ } else {
2339
+ toolResultContent = "Tool execution was declined.";
1783
2340
  }
2341
+ this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
1784
2342
  }
1785
- if (currentTurn.length > 0) {
1786
- turns.unshift(currentTurn);
2343
+ // Utilitários: emitir eventos, chamar tools, etc
2344
+ emitEvent(type, payload) {
2345
+ this.ctx.eventBus.emit("backend_message", { type, ...payload });
1787
2346
  }
1788
- const finalContext = systemMessages.concat(turns.flat());
1789
- return finalContext;
1790
- }
2347
+ };
2348
+
2349
+ // src/app/agent/subagents/init/init_subagent.ts
2350
+ var InitAgentImpl = class extends BaseLLMSubAgent {
2351
+ id = "init_subagent";
2352
+ capabilities = ["/init"];
2353
+ async execute(input, ctx) {
2354
+ this.ctx = ctx;
2355
+ this.isInterrupted = false;
2356
+ this.ctx.eventBus.on("user_interrupt", () => {
2357
+ this.isInterrupted = true;
2358
+ });
2359
+ await this.initializeHistory();
2360
+ const seed = `
2361
+ Scan the current project repository comprehensively.
2362
+ Map the directory structure (excluding heavy/ignored folders), infer the technology stack from manifests and configs, identify entry points and useful scripts, and analyze module relationships and architectural patterns.
2363
+ Then draft a concise, actionable BluMa.md that includes: project overview, detected stack, a summarized directory tree with brief annotations, key configs and scripts, useful CLI commands, and relevant operational notes for BluMa.
2364
+ Use only evidence gathered via tools; do not invent content.
2365
+ If overwriting an existing BluMa.md is required, follow non-destructive policies and request confirmation as per protocol.
2366
+ `;
2367
+ const combined = seed;
2368
+ this.history.push({ role: "user", content: combined });
2369
+ await this._continueConversation();
2370
+ return { history: this.history };
2371
+ }
2372
+ };
2373
+ var InitSubAgent = new InitAgentImpl();
2374
+
2375
+ // src/app/agent/subagents/subagents_bluma.ts
2376
+ registerSubAgent(InitSubAgent);
2377
+ var SubAgentsBluMa = class {
2378
+ eventBus;
2379
+ mcpClient;
2380
+ toolInvoker;
2381
+ llm;
2382
+ deploymentName;
2383
+ projectRoot;
2384
+ policy;
2385
+ logger;
2386
+ constructor(params) {
2387
+ this.eventBus = params.eventBus;
2388
+ this.mcpClient = params.mcpClient;
2389
+ this.toolInvoker = params.toolInvoker;
2390
+ this.llm = params.llm;
2391
+ this.deploymentName = params.deploymentName;
2392
+ this.projectRoot = params.projectRoot || process.cwd();
2393
+ this.policy = params.policy;
2394
+ this.logger = params.logger;
2395
+ }
2396
+ // Recebe dados do front (ex.: { content: inputText } vindo de /init)
2397
+ // e faz o roteamento para o subagente adequado com base no comando.
2398
+ async registerAndDispatch(frontPayload) {
2399
+ const { command, content, ...rest } = frontPayload || {};
2400
+ const resolvedCommand = this.resolveCommand({ command, content });
2401
+ if (!resolvedCommand) {
2402
+ this.emit("error", { message: "Nenhum comando/subagente correspondente encontrado." });
2403
+ return { ok: false, error: "unknown_command" };
2404
+ }
2405
+ const subAgent = getSubAgentByCommand(resolvedCommand);
2406
+ if (!subAgent) {
2407
+ this.emit("error", { message: `Subagente n\xE3o registrado para ${resolvedCommand}` });
2408
+ return { ok: false, error: "unregistered_subagent" };
2409
+ }
2410
+ const ctx = {
2411
+ projectRoot: this.projectRoot,
2412
+ eventBus: this.eventBus,
2413
+ mcpClient: this.mcpClient,
2414
+ toolInvoker: this.toolInvoker,
2415
+ llm: this.llm,
2416
+ policy: { llmDeployment: this.deploymentName, ...this.policy || {} },
2417
+ logger: this.logger
2418
+ };
2419
+ const inputForAgent = content ?? JSON.stringify({ content, ...rest });
2420
+ this.emit("info", { message: `[SubAgentsBluMa] Dispatch -> ${resolvedCommand}` });
2421
+ return subAgent.execute(inputForAgent, ctx);
2422
+ }
2423
+ // Estratégia simples: se o payload explicitamente vier de /init, use "/init".
2424
+ // Caso haja command explícito, usa-o. Caso contrário, heurística com base no conteúdo.
2425
+ resolveCommand(payload) {
2426
+ if (payload.command && typeof payload.command === "string") return payload.command;
2427
+ const text = String(payload.content || "").trim();
2428
+ if (!text) return "/init";
2429
+ if (text.startsWith("/")) return text.split(/\s+/)[0];
2430
+ return "/init";
2431
+ }
2432
+ emit(type, data) {
2433
+ this.eventBus.emit("backend_message", { type, ...data });
2434
+ }
2435
+ };
1791
2436
 
1792
2437
  // src/app/agent/agent.ts
1793
- var globalEnvPath = path7.join(os5.homedir(), ".bluma-cli", ".env");
2438
+ var globalEnvPath = path8.join(os6.homedir(), ".bluma-cli", ".env");
1794
2439
  dotenv.config({ path: globalEnvPath });
1795
2440
  var Agent = class {
1796
- client;
1797
- deploymentName;
1798
2441
  sessionId;
1799
- sessionFile = "";
1800
- history = [];
1801
2442
  eventBus;
1802
2443
  mcpClient;
1803
2444
  feedbackSystem;
1804
- isInitialized = false;
1805
- maxContextTurns = 300;
1806
- isInterrupted = false;
1807
- // <-- NOVO: Flag de interrupção
2445
+ // Mantido caso UI dependa de eventos
2446
+ llm;
2447
+ deploymentName;
2448
+ core;
2449
+ // Delegado
2450
+ subAgents;
2451
+ // Orquestrador de subagentes
2452
+ toolInvoker;
1808
2453
  constructor(sessionId2, eventBus2) {
1809
2454
  this.sessionId = sessionId2;
1810
2455
  this.eventBus = eventBus2;
1811
- this.eventBus.on("user_interrupt", () => {
1812
- this.isInterrupted = true;
1813
- });
1814
- this.eventBus.on("dev_overlay", async (data) => {
1815
- const clean = String(data.payload ?? "").trim();
1816
- this.history.push({ role: "user", name: "dev_overlay", content: clean });
1817
- this.eventBus.emit("backend_message", {
1818
- type: "dev_overlay",
1819
- payload: clean,
1820
- ts: data.ts || Date.now()
1821
- });
1822
- try {
1823
- if (this.sessionFile) {
1824
- await saveSessionHistory(this.sessionFile, this.history);
1825
- }
1826
- } catch (e) {
1827
- this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s dev_overlay: ${e.message}` });
1828
- }
1829
- });
1830
2456
  const nativeToolInvoker = new ToolInvoker();
2457
+ this.toolInvoker = nativeToolInvoker;
1831
2458
  this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
1832
2459
  this.feedbackSystem = new AdvancedFeedbackSystem();
1833
2460
  const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
@@ -1838,201 +2465,59 @@ var Agent = class {
1838
2465
  const errorMessage = `Uma ou mais vari\xE1veis de ambiente Azure OpenAI n\xE3o foram encontradas. Verifique em: ${globalEnvPath} ou nas vari\xE1veis de sistema.`;
1839
2466
  throw new Error(errorMessage);
1840
2467
  }
1841
- this.client = new OpenAI({
2468
+ const openai = new OpenAI({
1842
2469
  // Configuração do cliente OpenAI hospedado no Azure
1843
2470
  apiKey,
1844
2471
  baseURL: `${endpoint}/openai/deployments/${this.deploymentName}`,
1845
2472
  defaultQuery: { "api-version": apiVersion },
1846
2473
  defaultHeaders: { "api-key": apiKey }
1847
2474
  });
2475
+ this.llm = new OpenAIAdapter(openai);
2476
+ this.core = new BluMaAgent(
2477
+ this.sessionId,
2478
+ this.eventBus,
2479
+ this.llm,
2480
+ this.deploymentName,
2481
+ this.mcpClient,
2482
+ this.feedbackSystem
2483
+ );
2484
+ this.subAgents = new SubAgentsBluMa({
2485
+ eventBus: this.eventBus,
2486
+ mcpClient: this.mcpClient,
2487
+ toolInvoker: this.toolInvoker,
2488
+ llm: this.llm,
2489
+ deploymentName: this.deploymentName,
2490
+ projectRoot: process.cwd()
2491
+ });
1848
2492
  }
1849
- /**
1850
- * Inicializa o agente, carregando ou criando uma sessão e preparando o histórico.
1851
- * Também inicializa o MCPClient e o ToolInvoker.
1852
- */
1853
2493
  async initialize() {
1854
- await this.mcpClient.nativeToolInvoker.initialize();
1855
- await this.mcpClient.initialize();
1856
- const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
1857
- this.sessionFile = sessionFile;
1858
- this.history = history;
1859
- if (this.history.length === 0) {
1860
- let systemPrompt = getUnifiedSystemPrompt();
1861
- this.history.push({ role: "system", content: systemPrompt });
1862
- await saveSessionHistory(this.sessionFile, this.history);
1863
- }
1864
- this.isInitialized = true;
2494
+ await this.core.initialize();
1865
2495
  }
1866
2496
  getAvailableTools() {
1867
- return this.mcpClient.getAvailableTools();
2497
+ return this.core.getAvailableTools();
1868
2498
  }
1869
- // UI helper: detailed tools with origin metadata
1870
2499
  getUiToolsDetailed() {
1871
- return this.mcpClient.getAvailableToolsDetailed();
2500
+ return this.core.getUiToolsDetailed();
1872
2501
  }
1873
2502
  async processTurn(userInput) {
1874
- this.isInterrupted = false;
1875
- this.history.push({ role: "user", content: userInput.content });
1876
- await this._continueConversation();
2503
+ const inputText = String(userInput.content || "").trim();
2504
+ if (inputText === "/init" || inputText.startsWith("/init ")) {
2505
+ await this.dispatchToSubAgent({ command: "/init", content: inputText });
2506
+ return;
2507
+ }
2508
+ await this.core.processTurn({ content: inputText });
1877
2509
  }
1878
- /**
1879
- * Lida com a decisão do usuário (aceitar/recusar) sobre uma chamada de ferramenta.
1880
- * Garante que uma mensagem de 'role: tool' seja sempre adicionada ao histórico.
1881
- */
1882
2510
  async handleToolResponse(decisionData) {
1883
- const toolCall = decisionData.tool_calls[0];
1884
- let toolResultContent;
1885
- let shouldContinueConversation = true;
1886
- if (!this.sessionFile) {
1887
- const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
1888
- this.sessionFile = sessionFile;
1889
- if (this.history.length === 0 && history.length > 0) {
1890
- this.history = history;
1891
- }
1892
- }
1893
- if (decisionData.type === "user_decision_execute") {
1894
- const toolName = toolCall.function.name;
1895
- const toolArgs = JSON.parse(toolCall.function.arguments);
1896
- let previewContent;
1897
- if (toolName === "edit_tool") {
1898
- previewContent = await this._generateEditPreview(toolArgs);
1899
- }
1900
- this.eventBus.emit("backend_message", {
1901
- type: "tool_call",
1902
- tool_name: toolName,
1903
- arguments: toolArgs,
1904
- preview: previewContent
1905
- });
1906
- try {
1907
- if (this.isInterrupted) {
1908
- this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
1909
- return;
1910
- }
1911
- const result = await this.mcpClient.invoke(toolName, toolArgs);
1912
- let finalResult = result;
1913
- if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
1914
- finalResult = result[0].text;
1915
- }
1916
- toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
1917
- } catch (error) {
1918
- toolResultContent = JSON.stringify({
1919
- error: `Tool execution failed: ${error.message}`,
1920
- details: error.data || "No additional details."
1921
- });
1922
- }
1923
- this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
1924
- if (toolName.includes("agent_end_task")) {
1925
- shouldContinueConversation = false;
1926
- this.eventBus.emit("backend_message", { type: "done", status: "completed" });
1927
- }
1928
- } else {
1929
- toolResultContent = "The user declined to execute this tool...";
1930
- }
1931
- this.history.push({
1932
- role: "tool",
1933
- tool_call_id: toolCall.id,
1934
- content: toolResultContent
1935
- });
1936
- await saveSessionHistory(this.sessionFile, this.history);
1937
- if (shouldContinueConversation && !this.isInterrupted) {
1938
- await this._continueConversation();
1939
- }
2511
+ await this.core.handleToolResponse(decisionData);
1940
2512
  }
1941
- /**
1942
- * Método central que chama a API do LLM e processa a resposta,
1943
- * com lógica de feedback e auto-aprovação de ferramentas.
1944
- */
1945
- // Adicione este método dentro da classe Agent
1946
- async _generateEditPreview(toolArgs) {
1947
- try {
1948
- const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
1949
- if (editData.error) {
1950
- return `Failed to generate diff:
1951
-
1952
- ${editData.error.display}`;
1953
- }
1954
- const filename = path7.basename(toolArgs.file_path);
1955
- return createDiff(filename, editData.currentContent || "", editData.newContent);
1956
- } catch (e) {
1957
- return `An unexpected error occurred while generating the edit preview: ${e.message}`;
1958
- }
2513
+ // Novo: processamento via SubAgents (ex.: payload vindo do front /init)
2514
+ async dispatchToSubAgent(payload) {
2515
+ return this.subAgents.registerAndDispatch(payload);
1959
2516
  }
1960
- async _continueConversation() {
1961
- try {
1962
- if (this.isInterrupted) {
1963
- this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
1964
- return;
1965
- }
1966
- const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
1967
- const response = await this.client.chat.completions.create({
1968
- model: this.deploymentName,
1969
- messages: contextWindow,
1970
- tools: this.mcpClient.getAvailableTools(),
1971
- tool_choice: "required",
1972
- parallel_tool_calls: false
1973
- });
1974
- if (this.isInterrupted) {
1975
- this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
1976
- return;
1977
- }
1978
- const message = response.choices[0].message;
1979
- this.history.push(message);
1980
- if (message.tool_calls) {
1981
- const autoApprovedTools = [
1982
- "agent_end_task",
1983
- "message_notify_dev",
1984
- "sequentialThinking_nootebook"
1985
- ];
1986
- const toolToCall = message.tool_calls[0];
1987
- const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
1988
- if (isSafeTool) {
1989
- await this.handleToolResponse({ type: "user_decision_execute", tool_calls: message.tool_calls });
1990
- } else {
1991
- const toolName = toolToCall.function.name;
1992
- if (toolName === "edit_tool") {
1993
- const args = JSON.parse(toolToCall.function.arguments);
1994
- const previewContent = await this._generateEditPreview(args);
1995
- this.eventBus.emit("backend_message", {
1996
- type: "confirmation_request",
1997
- tool_calls: message.tool_calls,
1998
- preview: previewContent
1999
- });
2000
- } else {
2001
- this.eventBus.emit("backend_message", {
2002
- type: "confirmation_request",
2003
- tool_calls: message.tool_calls
2004
- });
2005
- }
2006
- }
2007
- } else if (message.content) {
2008
- this.eventBus.emit("backend_message", {
2009
- type: "assistant_message",
2010
- content: message.content
2011
- });
2012
- const feedback = this.feedbackSystem.generateFeedback({
2013
- event: "protocol_violation_direct_text",
2014
- details: { violationContent: message.content }
2015
- });
2016
- this.eventBus.emit("backend_message", {
2017
- type: "protocol_violation",
2018
- message: feedback.message,
2019
- content: message.content
2020
- });
2021
- this.history.push({
2022
- role: "system",
2023
- content: feedback.correction
2024
- });
2025
- await this._continueConversation();
2026
- } else {
2027
- this.eventBus.emit("backend_message", { type: "info", message: "Agent is thinking... continuing reasoning cycle." });
2028
- await this._continueConversation();
2029
- }
2030
- } catch (error) {
2031
- const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
2032
- this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
2033
- } finally {
2034
- await saveSessionHistory(this.sessionFile, this.history);
2035
- }
2517
+ // Compat: snapshot imutável do histórico para testes/UI (não expõe referência mutável)
2518
+ getHistorySnapshot() {
2519
+ const coreHistory = this.core.history;
2520
+ return coreHistory ? coreHistory.map((m) => ({ ...m })) : [];
2036
2521
  }
2037
2522
  };
2038
2523
 
@@ -2068,7 +2553,7 @@ import { Box as Box9 } from "ink";
2068
2553
 
2069
2554
  // src/app/ui/components/toolCallRenderers.tsx
2070
2555
  import { Box as Box8, Text as Text8 } from "ink";
2071
- import path8 from "path";
2556
+ import path9 from "path";
2072
2557
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2073
2558
  var formatArgumentsForDisplay = (args) => {
2074
2559
  if (typeof args === "string") {
@@ -2101,7 +2586,7 @@ var renderLsTool2 = ({ args }) => {
2101
2586
  } catch (e) {
2102
2587
  directoryPath = "Error parsing arguments";
2103
2588
  }
2104
- const finalDirectoryName = path8.basename(directoryPath);
2589
+ const finalDirectoryName = path9.basename(directoryPath);
2105
2590
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2106
2591
  /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2107
2592
  /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
@@ -2121,7 +2606,7 @@ var renderCountFilesLines = ({ args }) => {
2121
2606
  } catch (e) {
2122
2607
  directoryPath = "Error parsing arguments";
2123
2608
  }
2124
- const finalDirectoryName = path8.basename(directoryPath);
2609
+ const finalDirectoryName = path9.basename(directoryPath);
2125
2610
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2126
2611
  /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2127
2612
  /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
@@ -2145,7 +2630,7 @@ var renderReadFileLines2 = ({ args }) => {
2145
2630
  } catch (e) {
2146
2631
  filepath = "Error parsing arguments";
2147
2632
  }
2148
- const finalFileName = path8.basename(filepath);
2633
+ const finalFileName = path9.basename(filepath);
2149
2634
  return (
2150
2635
  // A caixa externa com a borda, seguindo o template
2151
2636
  /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
@@ -2183,20 +2668,13 @@ var renderBlumaNotebook = ({ args }) => {
2183
2668
  return (
2184
2669
  // Usamos a mesma estrutura de caixa com borda
2185
2670
  /* @__PURE__ */ jsxs8(Box8, { borderStyle: "round", borderColor: "green", flexDirection: "column", paddingX: 1, children: [
2186
- /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2187
- /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u{1F9E0} " }),
2188
- "Thinking Process"
2189
- ] }) }),
2190
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2191
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Thought:" }),
2671
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2672
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Thought:" }),
2192
2673
  /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { color: "gray", children: thinkingData.thought }) })
2193
2674
  ] }),
2194
2675
  thinkingData.remaining_tasks && thinkingData.remaining_tasks.length > 0 && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2195
2676
  /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Remaining Tasks:" }),
2196
- thinkingData.remaining_tasks.map((task, index) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2197
- /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
2198
- /* @__PURE__ */ jsx8(Text8, { color: task.startsWith("\u{1F5F8}") ? "green" : "yellow", children: task })
2199
- ] }) }, index))
2677
+ thinkingData.remaining_tasks.map((task, index) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(Text8, { color: task.startsWith("\u{1F5F8}") ? "green" : "yellow", children: task }) }) }, index))
2200
2678
  ] })
2201
2679
  ] })
2202
2680
  );
@@ -2215,7 +2693,7 @@ var renderEditToolCall = ({ args, preview }) => {
2215
2693
  } catch (e) {
2216
2694
  filepath = "Error parsing arguments";
2217
2695
  }
2218
- const finalFileName = path8.basename(filepath);
2696
+ const finalFileName = path9.basename(filepath);
2219
2697
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
2220
2698
  /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2221
2699
  /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
@@ -2247,7 +2725,7 @@ var renderGenericToolCall = ({ toolName, args }) => {
2247
2725
  var ToolRenderDisplay = {
2248
2726
  "shell_command": renderShellCommand2,
2249
2727
  "ls_tool": renderLsTool2,
2250
- "sequentialThinking_nootebook": renderBlumaNotebook,
2728
+ "reasoning_nootebook": renderBlumaNotebook,
2251
2729
  "count_file_lines": renderCountFilesLines,
2252
2730
  "read_file_lines": renderReadFileLines2,
2253
2731
  "edit_tool": renderEditToolCall
@@ -2289,42 +2767,51 @@ var ToolResultDisplay = memo3(ToolResultDisplayComponent);
2289
2767
  import { Box as Box11, Text as Text10 } from "ink";
2290
2768
  import Spinner from "ink-spinner";
2291
2769
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2292
- var SessionInfoConnectingMCP = ({ sessionId: sessionId2, workdir, statusMessage }) => {
2293
- return /* @__PURE__ */ jsxs9(
2770
+ var SessionInfoConnectingMCP = ({
2771
+ sessionId: sessionId2,
2772
+ workdir,
2773
+ statusMessage
2774
+ }) => {
2775
+ return /* @__PURE__ */ jsx11(
2294
2776
  Box11,
2295
2777
  {
2296
2778
  borderStyle: "round",
2297
2779
  borderColor: "gray",
2298
- flexDirection: "column",
2299
2780
  marginBottom: 1,
2300
- padding: 1,
2301
- children: [
2302
- /* @__PURE__ */ jsxs9(Text10, { children: [
2303
- /* @__PURE__ */ jsx11(Text10, { bold: true, color: "white", children: "localhost" }),
2304
- " ",
2305
- /* @__PURE__ */ jsx11(Text10, { color: "gray", children: " session:" }),
2306
- " ",
2307
- /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: sessionId2 })
2308
- ] }),
2309
- /* @__PURE__ */ jsxs9(Text10, { children: [
2310
- /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
2311
- " ",
2312
- /* @__PURE__ */ jsxs9(Text10, { color: "gray", children: [
2313
- "workdir: ",
2314
- workdir
2315
- ] })
2316
- ] }),
2317
- /* @__PURE__ */ jsxs9(Text10, { children: [
2318
- /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
2319
- " ",
2320
- /* @__PURE__ */ jsx11(Text10, { color: "gray", children: "mcp: " }),
2321
- /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
2322
- /* @__PURE__ */ jsx11(Spinner, { type: "dots" }),
2323
- " "
2324
- ] }),
2325
- /* @__PURE__ */ jsx11(Text10, { color: "white", children: statusMessage || "Please wait while we establish connections." })
2326
- ] })
2327
- ]
2781
+ children: /* @__PURE__ */ jsxs9(
2782
+ Box11,
2783
+ {
2784
+ marginLeft: 1,
2785
+ flexDirection: "column",
2786
+ children: [
2787
+ /* @__PURE__ */ jsxs9(Text10, { children: [
2788
+ /* @__PURE__ */ jsx11(Text10, { bold: true, color: "white", children: "localhost" }),
2789
+ " ",
2790
+ /* @__PURE__ */ jsx11(Text10, { color: "gray", children: " session:" }),
2791
+ " ",
2792
+ /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: sessionId2 })
2793
+ ] }),
2794
+ /* @__PURE__ */ jsxs9(Text10, { children: [
2795
+ /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
2796
+ " ",
2797
+ /* @__PURE__ */ jsxs9(Text10, { color: "gray", children: [
2798
+ "workdir: ",
2799
+ workdir
2800
+ ] })
2801
+ ] }),
2802
+ /* @__PURE__ */ jsxs9(Text10, { children: [
2803
+ /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
2804
+ " ",
2805
+ /* @__PURE__ */ jsx11(Text10, { color: "gray", children: "mcp: " }),
2806
+ /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
2807
+ /* @__PURE__ */ jsx11(Spinner, { type: "dots" }),
2808
+ " "
2809
+ ] }),
2810
+ /* @__PURE__ */ jsx11(Text10, { color: "white", children: statusMessage || "Please wait while we establish connections." })
2811
+ ] })
2812
+ ]
2813
+ }
2814
+ )
2328
2815
  }
2329
2816
  );
2330
2817
  };
@@ -2357,6 +2844,22 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
2357
2844
  setHistory((prev) => prev.filter((item) => item.id === 0 || item.id === 1));
2358
2845
  return outBox(/* @__PURE__ */ jsx12(Text11, { color: "green", children: "History cleared." }));
2359
2846
  }
2847
+ if (cmd === "init") {
2848
+ (async () => {
2849
+ try {
2850
+ await agentRef.current?.processTurn({ content: "/init" });
2851
+ } catch (e) {
2852
+ setHistory((prev) => prev.concat({
2853
+ id: Date.now(),
2854
+ component: outBox(/* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
2855
+ "Failed to execute /init: ",
2856
+ e?.message || String(e)
2857
+ ] }))
2858
+ }));
2859
+ }
2860
+ })();
2861
+ return null;
2862
+ }
2360
2863
  if (cmd === "mcp") {
2361
2864
  const all = agentRef.current?.getUiToolsDetailed?.() || agentRef.current?.getAvailableTools?.() || [];
2362
2865
  const isMcp = (t) => t.source?.toLowerCase?.() === "mcp" || !!t.server && t.server !== "native";
@@ -2468,12 +2971,12 @@ var SlashCommands_default = SlashCommands;
2468
2971
  import updateNotifier from "update-notifier";
2469
2972
  import { readPackageUp } from "read-package-up";
2470
2973
  import { fileURLToPath as fileURLToPath3 } from "url";
2471
- import path9 from "path";
2974
+ import path10 from "path";
2472
2975
  import fs8 from "fs";
2473
2976
  function findPackageJsonNearest(startDir) {
2474
2977
  let dir = startDir;
2475
2978
  for (let i = 0; i < 6; i++) {
2476
- const candidate = path9.join(dir, "package.json");
2979
+ const candidate = path10.join(dir, "package.json");
2477
2980
  if (fs8.existsSync(candidate)) {
2478
2981
  try {
2479
2982
  const raw = fs8.readFileSync(candidate, "utf8");
@@ -2482,7 +2985,7 @@ function findPackageJsonNearest(startDir) {
2482
2985
  } catch {
2483
2986
  }
2484
2987
  }
2485
- const parent = path9.dirname(dir);
2988
+ const parent = path10.dirname(dir);
2486
2989
  if (parent === dir) break;
2487
2990
  dir = parent;
2488
2991
  }
@@ -2496,14 +2999,14 @@ async function checkForUpdates() {
2496
2999
  const binPath = process.argv?.[1];
2497
3000
  let pkg;
2498
3001
  if (binPath && fs8.existsSync(binPath)) {
2499
- const candidatePkg = findPackageJsonNearest(path9.dirname(binPath));
3002
+ const candidatePkg = findPackageJsonNearest(path10.dirname(binPath));
2500
3003
  if (candidatePkg?.name && candidatePkg?.version) {
2501
3004
  pkg = candidatePkg;
2502
3005
  }
2503
3006
  }
2504
3007
  if (!pkg) {
2505
3008
  const __filename = fileURLToPath3(import.meta.url);
2506
- const __dirname = path9.dirname(__filename);
3009
+ const __dirname = path10.dirname(__filename);
2507
3010
  const result = await readPackageUp({ cwd: __dirname });
2508
3011
  pkg = result?.packageJson;
2509
3012
  }
@@ -2604,6 +3107,7 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2604
3107
  setIsProcessing(false);
2605
3108
  return;
2606
3109
  }
3110
+ setIsProcessing(true);
2607
3111
  setHistory((prev) => [
2608
3112
  ...prev,
2609
3113
  {
@@ -2612,10 +3116,16 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2612
3116
  },
2613
3117
  {
2614
3118
  id: prev.length + 1,
2615
- component: /* @__PURE__ */ jsx14(SlashCommands_default, { input: text, setHistory, agentRef: agentInstance })
3119
+ component: /* @__PURE__ */ jsx14(
3120
+ SlashCommands_default,
3121
+ {
3122
+ input: text,
3123
+ setHistory,
3124
+ agentRef: agentInstance
3125
+ }
3126
+ )
2616
3127
  }
2617
3128
  ]);
2618
- setIsProcessing(false);
2619
3129
  return;
2620
3130
  }
2621
3131
  setIsProcessing(true);
@@ -2827,21 +3337,14 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2827
3337
  }, [eventBus2, sessionId2, handleConfirmation]);
2828
3338
  const renderInteractiveComponent = () => {
2829
3339
  if (mcpStatus !== "connected") {
2830
- return /* @__PURE__ */ jsx14(
2831
- Box14,
3340
+ return /* @__PURE__ */ jsx14(Box14, { borderStyle: "round", borderColor: "black", children: /* @__PURE__ */ jsx14(
3341
+ SessionInfoConnectingMCP_default,
2832
3342
  {
2833
- borderStyle: "round",
2834
- borderColor: "black",
2835
- children: /* @__PURE__ */ jsx14(
2836
- SessionInfoConnectingMCP_default,
2837
- {
2838
- sessionId: sessionId2,
2839
- workdir,
2840
- statusMessage
2841
- }
2842
- )
3343
+ sessionId: sessionId2,
3344
+ workdir,
3345
+ statusMessage
2843
3346
  }
2844
- );
3347
+ ) });
2845
3348
  }
2846
3349
  if (pendingConfirmation) {
2847
3350
  return /* @__PURE__ */ jsx14(