@nomad-e/bluma-cli 0.0.26 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,974 +1632,615 @@ CRITICAL: Your laptop (**sequentialThinking_nootebook**) is your ORGANIZED MIND
978
1632
  \u{1F5F8} Set up environment
979
1633
  [ ] Configure database
980
1634
 
981
- ### Tool Naming Policy
982
-
983
- Tool names must strictly follow the standard naming format:
984
-
985
- - Use: plain, unmodified, lowercase names
986
- - Do NOT use: special characters, extra spaces, version suffixes, or dynamic IDs
987
-
988
- ---
989
-
990
- Correct Examples:
991
- - bluma_notebook
992
- - getDataTool
993
- - convertImage
994
- - userAuth
995
-
996
- ---
997
-
998
- Incorrect Examples:
999
- - sequentialThinking_nootebook:0 \u2190 contains colon and dynamic suffix
1000
- - sequentialThinking_nootebook 1 \u2190 contains space and number
1001
- - sequentialThinking_nootebook#v2 \u2190 contains special character #
1002
- - bluma__nootebook \u2190 double underscore
1003
- - sequentialThinking_Nootebook \u2190 capital letters and underscore
1004
- - bluma nootebook \u2190 contains space
1005
-
1006
- ---
1007
-
1008
- Rule Summary:
1009
- - Use only a\u2013z, 0\u20139, and underscores (_)
1010
- - Do not append suffixes like :0, :v2, etc.
1011
- - Tool names must be static and predictable
1012
- - No whitespace, no dynamic elements, no special characters
1013
-
1014
-
1015
- ### EDIT TOOL
1016
- - Use this tool to perform precise text replacements inside files based on exact literal matches.
1017
- - Can be used to create new files or directories implicitly by targeting non-existing paths.
1018
- - Suitable for inserting full content into a file even if the file does not yet exist.
1019
- - Shell access is not required for file or directory creation when using this tool.
1020
- - Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
1021
- - Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
1022
- - For creating a new file, provide an **old_string** that matches an empty string or placeholder and a complete **new_string** with the intended content.
1023
- - When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
1024
- - After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
1025
- - Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
1026
- - Track all progress related to planning and execution inside todo.md using text replacement only.
1027
-
1028
-
1029
- Real-Time Developer Messages
1030
- - During processing, the developer will send you messages.
1031
- - You MUST respond immediately via message_notify_dev, and be brief. You should use it in your next thoughts/actions.
1032
-
1033
-
1034
- ### AGENT END TASK RULES
1035
- This tool is used to signal to the system that the current task has completed and that the agent can be placed in an idle state.
1036
-
1037
-
1038
- ### QUALITY STANDARDS
1039
- - Document every major decision in Notion(
1040
- ##Important: When writing to Notion, you must strictly follow its content structure, including the correct use of headings (heading_1, heading_2, etc.) and other formatting standards. No deviations are allowed.
1041
- You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
1042
- semantically better for reading and navigation.
1043
- )
1044
- - Communicate transparently at each step
1045
- - Write clean, well-documented code
1046
- - Follow existing project conventions
1047
- - Test implementations when possible
1048
- - Ensure security and performance
1049
-
1050
- <scope_and_limitations>
1051
- # WHAT YOU DON'T HANDLE
1052
- - Non-technical questions
1053
- - Personal advice
1054
- - General conversation
1055
- - Tasks outside software development
1056
-
1057
- # IF ASKED NON-TECHNICAL QUESTIONS
1058
- - Use message_notify_dev to politely decline
1059
- - Explain you only handle technical/coding tasks
1060
- - Suggest they ask a development-related question instead
1061
- </scope_and_limitations>
1062
-
1063
- `;
1064
- function getUnifiedSystemPrompt() {
1065
- const now = /* @__PURE__ */ new Date();
1066
- const collectedData = {
1067
- os_type: os2.type(),
1068
- os_version: os2.release(),
1069
- architecture: os2.arch(),
1070
- workdir: process.cwd(),
1071
- shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
1072
- username: os2.userInfo().username || "Unknown",
1073
- current_date: now.toISOString().split("T")[0],
1074
- // Formato YYYY-MM-DD
1075
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
1076
- locale: process.env.LANG || process.env.LC_ALL || "Unknown"
1077
- };
1078
- const finalEnv = {
1079
- os_type: "Unknown",
1080
- os_version: "Unknown",
1081
- workdir: "Unknown",
1082
- shell_type: "Unknown",
1083
- username: "Unknown",
1084
- architecture: "Unknown",
1085
- current_date: "Unknown",
1086
- timezone: "Unknown",
1087
- locale: "Unknown",
1088
- ...collectedData
1089
- // Os dados coletados sobrescrevem os padrões
1090
- };
1091
- let formattedPrompt = SYSTEM_PROMPT;
1092
- for (const key in finalEnv) {
1093
- const placeholder = `{${key}}`;
1094
- formattedPrompt = formattedPrompt.replace(new RegExp(placeholder, "g"), finalEnv[key]);
1095
- }
1096
- return formattedPrompt;
1097
- }
1635
+ </reasoning_rules>
1098
1636
 
1099
- // src/app/agent/tool_invoker.ts
1100
- import { promises as fs6 } from "fs";
1101
- import path5 from "path";
1102
- import { fileURLToPath } from "url";
1637
+ ### Tool Naming Policy
1103
1638
 
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
- }
1639
+ Tool names must strictly follow the standard naming format:
1167
1640
 
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
- }
1641
+ - Use: plain, unmodified, lowercase names
1642
+ - Do NOT use: special characters, extra spaces, version suffixes, or dynamic IDs
1313
1643
 
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
- }
1644
+ ---
1645
+
1646
+ Correct Examples:
1647
+ - bluma_notebook
1648
+ - getDataTool
1649
+ - convertImage
1650
+ - userAuth
1651
+
1652
+ ---
1653
+
1654
+ Incorrect Examples:
1655
+ - reasoning_nootebook:0 \u2190 contains colon and dynamic suffix
1656
+ - reasoning_nootebook 1 \u2190 contains space and number
1657
+ - reasoning_nootebook#v2 \u2190 contains special character #
1658
+ - bluma__nootebook \u2190 double underscore
1659
+ - reasoning_nootebook \u2190 capital letters and underscore
1660
+ - bluma nootebook \u2190 contains space
1661
+
1662
+ ---
1663
+
1664
+ Rule Summary:
1665
+ - Use only a\u2013z, 0\u20139, and underscores (_)
1666
+ - Do not append suffixes like :0, :v2, etc.
1667
+ - Tool names must be static and predictable
1668
+ - No whitespace, no dynamic elements, no special characters
1669
+
1670
+
1671
+ <edit_tool_rules>
1672
+ - Use this tool to perform precise text replacements inside files based on exact literal matches.
1673
+ - Can be used to create new files or directories implicitly by targeting non-existing paths.
1674
+ - Suitable for inserting full content into a file even if the file does not yet exist.
1675
+ - Shell access is not required for file or directory creation when using this tool.
1676
+ - Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
1677
+ - Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
1678
+ - For creating a new file, provide an **old_string** that matches an empty string or placeholder and a complete **new_string** with the intended content.
1679
+ - When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
1680
+ - After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
1681
+ - Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
1682
+ - Track all progress related to planning and execution inside todo.md using text replacement only.
1683
+ </edit_tool_rules>
1331
1684
 
1332
- // 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
- }
1685
+ Real-Time Developer Messages
1686
+ - During processing, the developer will send you messages.
1687
+ - You MUST respond immediately via message_notify_dev, and be brief. You should use it in your next thoughts/actions.
1412
1688
 
1413
- // src/app/agent/tools/natives/readLines.ts
1414
- import { promises as fs4 } from "fs";
1415
- async function readLines(args) {
1416
- const { filepath, start_line, end_line } = args;
1417
- try {
1418
- if (!(await fs4.stat(filepath)).isFile()) {
1419
- throw new Error(`File '${filepath}' not found or is not a file.`);
1420
- }
1421
- if (start_line < 1 || end_line < start_line) {
1422
- throw new Error("Invalid line range. start_line must be >= 1 and end_line must be >= start_line.");
1423
- }
1424
- const fileContent = await fs4.readFile(filepath, "utf-8");
1425
- const lines = fileContent.split("\n");
1426
- const total_lines = lines.length;
1427
- const startIndex = start_line - 1;
1428
- let endIndex = end_line;
1429
- if (startIndex >= total_lines) {
1430
- throw new Error(`start_line (${start_line}) exceeds file length (${total_lines} lines).`);
1431
- }
1432
- endIndex = Math.min(endIndex, total_lines);
1433
- const contentLines = lines.slice(startIndex, endIndex);
1434
- const content = contentLines.join("\n");
1435
- return {
1436
- success: true,
1437
- filepath,
1438
- content,
1439
- lines_read: contentLines.length,
1440
- start_line,
1441
- end_line: endIndex,
1442
- // Retorna o final real usado
1443
- total_file_lines: total_lines
1444
- };
1445
- } catch (e) {
1446
- return { success: false, error: e.message };
1447
- }
1448
- }
1449
1689
 
1450
- // 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 };
1690
+ <agent_end_task_rules>
1691
+ This tool is mandatory.
1692
+ You must use it to inform developer {username} that the task has been completed and that there are no further pending actions, in accordance with the objectives defined for the task.
1693
+ </agent_end_task_rules>
1694
+
1695
+ ### QUALITY STANDARDS
1696
+ - Document every major decision in Notion(
1697
+ ##Important: When writing to Notion, you must strictly follow its content structure, including the correct use of headings (heading_1, heading_2, etc.) and other formatting standards. No deviations are allowed.
1698
+ You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
1699
+ semantically better for reading and navigation.
1700
+ )
1701
+ - Communicate transparently at each step
1702
+ - Write clean, well-documented code
1703
+ - Follow existing project conventions
1704
+ - Test implementations when possible
1705
+ - Ensure security and performance
1706
+
1707
+ <scope_and_limitations>
1708
+ # WHAT YOU DON'T HANDLE
1709
+ - Non-technical questions
1710
+ - Personal advice
1711
+ - General conversation
1712
+ - Tasks outside software development
1713
+
1714
+ # IF ASKED NON-TECHNICAL QUESTIONS
1715
+ - Use message_notify_dev to politely decline
1716
+ - Explain you only handle technical/coding tasks
1717
+ - Suggest they ask a development-related question instead
1718
+ </scope_and_limitations>
1719
+
1720
+ `;
1721
+ function getUnifiedSystemPrompt() {
1722
+ const now = /* @__PURE__ */ new Date();
1723
+ const collectedData = {
1724
+ os_type: os4.type(),
1725
+ os_version: os4.release(),
1726
+ architecture: os4.arch(),
1727
+ workdir: process.cwd(),
1728
+ shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
1729
+ username: os4.userInfo().username || "Unknown",
1730
+ current_date: now.toISOString().split("T")[0],
1731
+ // Formato YYYY-MM-DD
1732
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
1733
+ locale: process.env.LANG || process.env.LC_ALL || "Unknown"
1734
+ };
1735
+ const finalEnv = {
1736
+ os_type: "Unknown",
1737
+ os_version: "Unknown",
1738
+ workdir: "Unknown",
1739
+ shell_type: "Unknown",
1740
+ username: "Unknown",
1741
+ architecture: "Unknown",
1742
+ current_date: "Unknown",
1743
+ timezone: "Unknown",
1744
+ locale: "Unknown",
1745
+ ...collectedData
1746
+ // Os dados coletados sobrescrevem os padrões
1747
+ };
1748
+ let formattedPrompt = SYSTEM_PROMPT;
1749
+ for (const key in finalEnv) {
1750
+ const placeholder = `{${key}}`;
1751
+ formattedPrompt = formattedPrompt.replace(new RegExp(placeholder, "g"), finalEnv[key]);
1472
1752
  }
1753
+ return formattedPrompt;
1473
1754
  }
1474
1755
 
1475
- // src/app/agent/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
- }
1501
- }
1502
- /**
1503
- * Registra as implementações de todas as ferramentas nativas.
1504
- * Este método mapeia o nome da ferramenta (string) para a função TypeScript que a executa.
1505
- */
1506
- registerTools() {
1507
- this.toolImplementations.set("shell_command", shellCommand);
1508
- this.toolImplementations.set("edit_tool", editTool);
1509
- this.toolImplementations.set("message_notify_dev", messageNotifyDev);
1510
- this.toolImplementations.set("ls_tool", ls);
1511
- this.toolImplementations.set("count_file_lines", countLines);
1512
- this.toolImplementations.set("read_file_lines", readLines);
1513
- this.toolImplementations.set("agent_end_task", async () => ({ success: true, message: "Task ended by agent." }));
1514
- }
1515
- /**
1516
- * Retorna a lista de definições de todas as ferramentas nativas carregadas.
1517
- * O MCPClient usará esta função para obter a lista de ferramentas locais.
1518
- */
1519
- getToolDefinitions() {
1520
- return this.toolDefinitions;
1521
- }
1522
- /**
1523
- * Invoca uma ferramenta nativa pelo nome com os argumentos fornecidos.
1524
- * @param toolName O nome da ferramenta a ser invocada.
1525
- * @param args Os argumentos para a ferramenta, geralmente um objeto.
1526
- * @returns O resultado da execução da ferramenta.
1527
- */
1528
- async invoke(toolName, args) {
1529
- const implementation = this.toolImplementations.get(toolName);
1530
- if (!implementation) {
1531
- return { error: `Error: Native tool "${toolName}" not found.` };
1532
- }
1533
- try {
1534
- return await implementation(args);
1535
- } catch (error) {
1536
- const errorMessage = error instanceof Error ? error.message : "An unknown error occurred.";
1537
- return { error: `Error executing tool "${toolName}": ${errorMessage}` };
1538
- }
1756
+ // src/app/agent/core/context-api/context_manager.ts
1757
+ function createApiContextWindow(fullHistory, maxTurns) {
1758
+ if (!fullHistory.length) {
1759
+ return [];
1539
1760
  }
1540
- };
1541
-
1542
- // src/app/agent/tools/mcp/mcp_client.ts
1543
- import { promises as fs7 } from "fs";
1544
- import path6 from "path";
1545
- import os4 from "os";
1546
- import { fileURLToPath as fileURLToPath2 } from "url";
1547
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1548
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1549
- var MCPClient = class {
1550
- sessions = /* @__PURE__ */ new Map();
1551
- toolToServerMap = /* @__PURE__ */ new Map();
1552
- globalToolsForLlm = [];
1553
- nativeToolInvoker;
1554
- eventBus;
1555
- // <<< ADICIONA A PROPRIEDADE
1556
- constructor(nativeToolInvoker, eventBus2) {
1557
- this.nativeToolInvoker = nativeToolInvoker;
1558
- this.eventBus = eventBus2;
1761
+ if (maxTurns === null || maxTurns === void 0) {
1762
+ return [...fullHistory];
1559
1763
  }
1560
- // ... (método initialize inalterado) ...
1561
- 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
- });
1570
- }
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 || {}
1764
+ const systemMessages = [];
1765
+ let historyStartIndex = 0;
1766
+ while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
1767
+ systemMessages.push(fullHistory[historyStartIndex]);
1768
+ historyStartIndex++;
1769
+ }
1770
+ const conversationHistory = fullHistory.slice(historyStartIndex);
1771
+ const turns = [];
1772
+ let currentTurn = [];
1773
+ let turnsFound = 0;
1774
+ const isDevOverlay = (m) => m?.role === "user" && m?.name === "dev_overlay";
1775
+ for (let i = conversationHistory.length - 1; i >= 0; i--) {
1776
+ const msg = conversationHistory[i];
1777
+ currentTurn.unshift(msg);
1778
+ const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some((tc) => tc.function.name === "agent_end_task");
1779
+ if (endsWithAgentEnd) {
1780
+ turns.unshift([...currentTurn]);
1781
+ currentTurn = [];
1782
+ turnsFound++;
1783
+ if (turnsFound >= maxTurns) {
1784
+ break;
1581
1785
  }
1582
- };
1583
- if (Object.keys(mergedConfig.mcpServers).length === 0) {
1584
- return;
1786
+ continue;
1585
1787
  }
1586
- const serverEntries = Object.entries(mergedConfig.mcpServers);
1587
- for (const [serverName, serverConf] of serverEntries) {
1588
- 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.`);
1788
+ const prev = conversationHistory[i - 1];
1789
+ if (msg.role === "user" && !isDevOverlay(msg)) {
1790
+ if (prev && prev.role === "assistant" && !prev.tool_calls?.some((tc) => tc.function.name === "agent_end_task")) {
1791
+ const hasNonOverlay = currentTurn.some((m) => m.role !== "user" || !isDevOverlay(m));
1792
+ if (hasNonOverlay) {
1793
+ turns.unshift([...currentTurn]);
1794
+ currentTurn = [];
1597
1795
  }
1598
- } catch (error) {
1599
- this.eventBus.emit("backend_message", {
1600
- type: "error",
1601
- message: `Failed to connect to server '${serverName}'.`
1602
- });
1603
1796
  }
1604
1797
  }
1605
1798
  }
1606
- 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
- }
1799
+ if (currentTurn.length > 0) {
1800
+ turns.unshift(currentTurn);
1620
1801
  }
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;
1635
- }
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
1802
+ const finalContext = systemMessages.concat(turns.flat());
1803
+ return finalContext;
1804
+ }
1805
+
1806
+ // src/app/agent/bluma/core/bluma.ts
1807
+ var BluMaAgent = class {
1808
+ llm;
1809
+ deploymentName;
1810
+ sessionId;
1811
+ sessionFile = "";
1812
+ history = [];
1813
+ eventBus;
1814
+ mcpClient;
1815
+ feedbackSystem;
1816
+ maxContextTurns = 300;
1817
+ isInterrupted = false;
1818
+ constructor(sessionId2, eventBus2, llm, deploymentName, mcpClient, feedbackSystem) {
1819
+ this.sessionId = sessionId2;
1820
+ this.eventBus = eventBus2;
1821
+ this.llm = llm;
1822
+ this.deploymentName = deploymentName;
1823
+ this.mcpClient = mcpClient;
1824
+ this.feedbackSystem = feedbackSystem;
1825
+ this.eventBus.on("user_interrupt", () => {
1826
+ this.isInterrupted = true;
1643
1827
  });
1644
- 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
1828
+ this.eventBus.on("dev_overlay", async (data) => {
1829
+ const clean = String(data.payload ?? "").trim();
1830
+ this.history.push({ role: "user", name: "dev_overlay", content: clean });
1831
+ this.eventBus.emit("backend_message", { type: "dev_overlay", payload: clean, ts: data.ts || Date.now() });
1832
+ try {
1833
+ if (this.sessionFile) {
1834
+ await saveSessionHistory(this.sessionFile, this.history);
1656
1835
  }
1657
- });
1658
- 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
- } else {
1672
- const session = this.sessions.get(route.server);
1673
- if (!session) {
1674
- return { error: `Sess\xE3o para o servidor '${route.server}' n\xE3o encontrada.` };
1836
+ } catch (e) {
1837
+ this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s dev_overlay: ${e.message}` });
1675
1838
  }
1676
- const result = await session.callTool({ name: route.originalName, arguments: args });
1677
- return result.content;
1839
+ });
1840
+ }
1841
+ async initialize() {
1842
+ await this.mcpClient.nativeToolInvoker.initialize();
1843
+ await this.mcpClient.initialize();
1844
+ const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
1845
+ this.sessionFile = sessionFile;
1846
+ this.history = history;
1847
+ if (this.history.length === 0) {
1848
+ const systemPrompt = getUnifiedSystemPrompt();
1849
+ this.history.push({ role: "system", content: systemPrompt });
1850
+ await saveSessionHistory(this.sessionFile, this.history);
1678
1851
  }
1679
1852
  }
1680
1853
  getAvailableTools() {
1681
- return this.globalToolsForLlm;
1854
+ return this.mcpClient.getAvailableTools();
1682
1855
  }
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 });
1693
- }
1694
- return detailed;
1856
+ getUiToolsDetailed() {
1857
+ return this.mcpClient.getAvailableToolsDetailed();
1695
1858
  }
1696
- async close() {
1697
- for (const [name, session] of this.sessions.entries()) {
1859
+ async processTurn(userInput) {
1860
+ this.isInterrupted = false;
1861
+ const inputText = String(userInput.content || "").trim();
1862
+ this.history.push({ role: "user", content: inputText });
1863
+ await this._continueConversation();
1864
+ }
1865
+ async handleToolResponse(decisionData) {
1866
+ const toolCall = decisionData.tool_calls[0];
1867
+ let toolResultContent;
1868
+ let shouldContinueConversation = true;
1869
+ if (!this.sessionFile) {
1870
+ const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
1871
+ this.sessionFile = sessionFile;
1872
+ if (this.history.length === 0 && history.length > 0) {
1873
+ this.history = history;
1874
+ }
1875
+ }
1876
+ if (decisionData.type === "user_decision_execute") {
1877
+ const toolName = toolCall.function.name;
1878
+ const toolArgs = JSON.parse(toolCall.function.arguments);
1879
+ let previewContent;
1880
+ if (toolName === "edit_tool") {
1881
+ previewContent = await this._generateEditPreview(toolArgs);
1882
+ }
1883
+ this.eventBus.emit("backend_message", {
1884
+ type: "tool_call",
1885
+ tool_name: toolName,
1886
+ arguments: toolArgs,
1887
+ preview: previewContent
1888
+ });
1698
1889
  try {
1699
- await session.close();
1890
+ if (this.isInterrupted) {
1891
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
1892
+ return;
1893
+ }
1894
+ const result = await this.mcpClient.invoke(toolName, toolArgs);
1895
+ let finalResult = result;
1896
+ if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
1897
+ finalResult = result[0].text;
1898
+ }
1899
+ toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
1700
1900
  } catch (error) {
1701
- console.error(`[MCPClient] Erro ao encerrar conex\xE3o com '${name}':`, error);
1901
+ toolResultContent = JSON.stringify({
1902
+ error: `Tool execution failed: ${error.message}`,
1903
+ details: error.data || "No additional details."
1904
+ });
1905
+ }
1906
+ this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
1907
+ if (toolName.includes("agent_end_task")) {
1908
+ shouldContinueConversation = false;
1909
+ this.eventBus.emit("backend_message", { type: "done", status: "completed" });
1702
1910
  }
1911
+ } else {
1912
+ toolResultContent = "The user declined to execute this tool...";
1703
1913
  }
1704
- }
1705
- replaceEnvPlaceholders(content) {
1706
- return content.replace(/\$\{([A-Za-z0-9_]+)\}/g, (match, varName) => {
1707
- return process.env[varName] || match;
1708
- });
1709
- }
1710
- };
1711
-
1712
- // src/app/agent/feedback/feedback_system.ts
1713
- var AdvancedFeedbackSystem = class {
1714
- cumulativeScore = 0;
1715
- /**
1716
- * Gera feedback com base em um evento ocorrido.
1717
- * @param event O evento a ser avaliado.
1718
- * @returns Um objeto com a pontuação, mensagem e correção.
1719
- */
1720
- generateFeedback(event) {
1721
- if (event.event === "protocol_violation_direct_text") {
1722
- const penalty = -2.5;
1723
- this.cumulativeScore += penalty;
1724
- return {
1725
- score: penalty,
1726
- message: "Direct text response is a protocol violation. All communication must be done via the 'message_notify_dev' tool.",
1727
- correction: `
1728
- ## PROTOCOL VIOLATION \u2014 SEVERE
1729
- You sent a direct text response, which is strictly prohibited.
1730
- PENALTY APPLIED: ${penalty.toFixed(1)} points deducted.
1731
- You MUST use tools for all actions and communication.
1732
- `.trim()
1733
- };
1914
+ this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
1915
+ await saveSessionHistory(this.sessionFile, this.history);
1916
+ if (shouldContinueConversation && !this.isInterrupted) {
1917
+ await this._continueConversation();
1734
1918
  }
1735
- return { score: 0, message: "No feedback for this event.", correction: "" };
1736
- }
1737
- getCumulativeScore() {
1738
- return this.cumulativeScore;
1739
1919
  }
1740
- };
1920
+ async _generateEditPreview(toolArgs) {
1921
+ try {
1922
+ const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
1923
+ if (editData.error) {
1924
+ return `Failed to generate diff:
1741
1925
 
1742
- // src/app/agent/core/context-api/context_manager.ts
1743
- function createApiContextWindow(fullHistory, maxTurns) {
1744
- if (!fullHistory.length) {
1745
- return [];
1746
- }
1747
- if (maxTurns === null || maxTurns === void 0) {
1748
- return [...fullHistory];
1749
- }
1750
- const systemMessages = [];
1751
- let historyStartIndex = 0;
1752
- while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
1753
- systemMessages.push(fullHistory[historyStartIndex]);
1754
- historyStartIndex++;
1755
- }
1756
- const conversationHistory = fullHistory.slice(historyStartIndex);
1757
- const turns = [];
1758
- let currentTurn = [];
1759
- let turnsFound = 0;
1760
- const isDevOverlay = (m) => m?.role === "user" && m?.name === "dev_overlay";
1761
- for (let i = conversationHistory.length - 1; i >= 0; i--) {
1762
- const msg = conversationHistory[i];
1763
- currentTurn.unshift(msg);
1764
- const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some((tc) => tc.function.name === "agent_end_task");
1765
- if (endsWithAgentEnd) {
1766
- turns.unshift([...currentTurn]);
1767
- currentTurn = [];
1768
- turnsFound++;
1769
- if (turnsFound >= maxTurns) {
1770
- break;
1926
+ ${editData.error.display}`;
1771
1927
  }
1772
- continue;
1928
+ const filename = path7.basename(toolArgs.file_path);
1929
+ return createDiff(filename, editData.currentContent || "", editData.newContent);
1930
+ } catch (e) {
1931
+ return `An unexpected error occurred while generating the edit preview: ${e.message}`;
1773
1932
  }
1774
- 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 = [];
1933
+ }
1934
+ async _continueConversation() {
1935
+ try {
1936
+ if (this.isInterrupted) {
1937
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
1938
+ return;
1939
+ }
1940
+ const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
1941
+ const response = await this.llm.chatCompletion({
1942
+ model: this.deploymentName,
1943
+ messages: contextWindow,
1944
+ tools: this.mcpClient.getAvailableTools(),
1945
+ tool_choice: "required",
1946
+ parallel_tool_calls: false
1947
+ });
1948
+ if (this.isInterrupted) {
1949
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
1950
+ return;
1951
+ }
1952
+ const message = response.choices[0].message;
1953
+ this.history.push(message);
1954
+ if (message.tool_calls) {
1955
+ const autoApprovedTools = ["agent_end_task", "message_notify_dev", "reasoning_nootebook"];
1956
+ const toolToCall = message.tool_calls[0];
1957
+ const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
1958
+ if (isSafeTool) {
1959
+ await this.handleToolResponse({ type: "user_decision_execute", tool_calls: message.tool_calls });
1960
+ } else {
1961
+ const toolName = toolToCall.function.name;
1962
+ if (toolName === "edit_tool") {
1963
+ const args = JSON.parse(toolToCall.function.arguments);
1964
+ const previewContent = await this._generateEditPreview(args);
1965
+ this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: message.tool_calls, preview: previewContent });
1966
+ } else {
1967
+ this.eventBus.emit("backend_message", { type: "confirmation_request", tool_calls: message.tool_calls });
1968
+ }
1781
1969
  }
1970
+ } else if (message.content) {
1971
+ this.eventBus.emit("backend_message", { type: "assistant_message", content: message.content });
1972
+ const feedback = this.feedbackSystem.generateFeedback({
1973
+ event: "protocol_violation_direct_text",
1974
+ details: { violationContent: message.content }
1975
+ });
1976
+ this.eventBus.emit("backend_message", { type: "protocol_violation", message: feedback.message, content: message.content });
1977
+ this.history.push({ role: "system", content: feedback.correction });
1978
+ await this._continueConversation();
1979
+ } else {
1980
+ this.eventBus.emit("backend_message", { type: "info", message: "Agent is thinking... continuing reasoning cycle." });
1981
+ await this._continueConversation();
1782
1982
  }
1983
+ } catch (error) {
1984
+ const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
1985
+ this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
1986
+ } finally {
1987
+ await saveSessionHistory(this.sessionFile, this.history);
1783
1988
  }
1784
1989
  }
1785
- if (currentTurn.length > 0) {
1786
- turns.unshift(currentTurn);
1990
+ };
1991
+
1992
+ // src/app/agent/core/llm.ts
1993
+ var OpenAIAdapter = class {
1994
+ client;
1995
+ constructor(client) {
1996
+ this.client = client;
1997
+ }
1998
+ async chatCompletion(params) {
1999
+ const resp = await this.client.chat.completions.create({
2000
+ model: params.model,
2001
+ messages: params.messages,
2002
+ tools: params.tools,
2003
+ tool_choice: params.tool_choice,
2004
+ parallel_tool_calls: params.parallel_tool_calls
2005
+ });
2006
+ return resp;
1787
2007
  }
1788
- const finalContext = systemMessages.concat(turns.flat());
1789
- return finalContext;
2008
+ };
2009
+
2010
+ // src/app/agent/subagents/registry.ts
2011
+ var subAgentRegistry = {};
2012
+ function registerSubAgent(subAgent) {
2013
+ for (const cap of subAgent.capabilities) {
2014
+ subAgentRegistry[cap] = subAgent;
2015
+ }
2016
+ }
2017
+ function getSubAgentByCommand(cmd) {
2018
+ return subAgentRegistry[cmd];
2019
+ }
2020
+
2021
+ // src/app/agent/subagents/init/init_system_prompt.ts
2022
+ import os5 from "os";
2023
+ var SYSTEM_PROMPT2 = `
2024
+
2025
+ ### YOU ARE BluMa CLI \u2014 INIT SUBAGENT \u2014 AUTONOMOUS SENIOR SOFTWARE ENGINEER @ NOMADENGENUITY
2026
+ You extend the BluMa multi-agent architecture and handle the project bootstrapping/init workflow: scanning the repository, inferring stack, and generating a high-quality BluMa.md with actionable project context.
2027
+
2028
+ ---
2029
+
2030
+ ## BEHAVIORAL RULES
2031
+
2032
+ - Identity:
2033
+ You are BluMa InitSubAgent. Maintain professionalism and technical language.
2034
+
2035
+ - Communication:
2036
+ ALL messages must be sent via 'message_notify_dev'.
2037
+ No direct text replies to the developer.
2038
+
2039
+ - Task Completion:
2040
+ When the init task is completed, immediately invoke 'agent_end_task' without dev permissions.
2041
+
2042
+ - Tool Rules:
2043
+ Never make parallel tool calls.
2044
+ Only use the defined tools with their exact names.
2045
+
2046
+ - Autonomy:
2047
+ Act 100% autonomously.
2048
+ Do not ask for formatting preferences.
2049
+ Use the notebook for internal reasoning.
2050
+
2051
+
2052
+ ### CRITICAL COMMUNICATION PROTOCOL
2053
+ - Only tool_calls are allowed for assistant replies. Never include a "content" field.
2054
+ - Always use tools to respond, retrieve data, compute or transform. Await a valid tool response before any final message.
2055
+ - Zero tolerance for protocol violations.
2056
+
2057
+ <current_system_environment>
2058
+ - Operating System: {os_type} ({os_version})
2059
+ - Architecture: {architecture}
2060
+ - Current Working Directory: {workdir}
2061
+ - Shell: {shell_type}
2062
+ - Username: {username}
2063
+ - Current Date: {current_date}
2064
+ - Timezone: {timezone}
2065
+ - Locale: {locale}
2066
+ </current_system_environment>
2067
+
2068
+ <message_rules>
2069
+ - Communicate with dev's via message tools instead of direct text responses
2070
+ - Reply immediately to new user messages before other operations
2071
+ - First reply must be brief, only confirming receipt without specific solutions
2072
+ - Notify dev's with brief explanation when changing methods or strategies
2073
+ - Message tools are divided into notify (non-blocking, no reply needed) and ask (blocking)
2074
+ - Actively use notify for progress updates, reserve ask for essential needs to avoid blocking
2075
+ - Must message dev's with results and deliverables before upon task completion 'agent_end_task'
2076
+ </message_rules>
2077
+
2078
+ <reasoning_rules>
2079
+ # YOUR THINKING ON A NOTEBOOK - MANDATORY USE
2080
+ CRITICAL: Your laptop (reasoning_nootebook) is your ORGANIZED MIND
2081
+ ## IMPORTANT
2082
+ ## NEVER PUT CHECKLISTS OR STEPS IN THE THOUGHT TEXT
2083
+ ## ALWAYS USE A NOTEBOOK (Always for):
2084
+ - ANY task
2085
+ - Before starting development (plan first!)
2086
+ - Projects with multiple files (organize the structure)
2087
+ - Debugging sessions (monitor discoveries)
2088
+ - Extensive refactoring (map the changes)
2089
+ - Architectural decisions (think through the options)
2090
+
2091
+ ## HOW TO USE A NOTEBOOK:
2092
+ 1. Start with reasoning_nootebook
2093
+ 2. Break the task down into logical steps
2094
+ 3. Plan the approach \u2013 Which files? What changes? What order?
2095
+ 4. Track progress \u2013 Check off completed steps
2096
+ 5. Write down decisions \u2013 Why did you choose this approach?
2097
+ 6. Update continuously \u2013 Keep the notebook up to date
2098
+
2099
+ ## THE NOTEBOOK PREVENTS:
2100
+ - Acting "outside the box"
2101
+ - Forgetting task requirements
2102
+ - Losing control of complex workflows
2103
+ - Making unplanned changes
2104
+ - Ineffective approaches
2105
+ - Working without a clear roadmap
2106
+ - Jumping between unrelated subtasks
2107
+
2108
+ Important rule:
2109
+ Do not include future steps/to-dos in thought; put them strictly in remaining_tasks, using the mandated checklist markers.
2110
+
2111
+ - remaining_tasks: Checklist list of high-level upcoming tasks.
2112
+ Format is mandatory:
2113
+ - "\u{1F5F8}" \u2192 for tasks not yet done (pending)
2114
+ - "[ ]" \u2192 for tasks already completed
2115
+ </reasoning_rules>
2116
+
2117
+ <edit_tool_rules>
2118
+ - Use this tool to perform precise text replacements inside files based on exact literal matches.
2119
+ - Can be used to create new files or directories implicitly by targeting non-existing paths.
2120
+ - Suitable for inserting full content into a file even if the file does not yet exist.
2121
+ - Shell access is not required for file or directory creation when using this tool.
2122
+ - Always prefer this tool over shell_command when performing structured edits or creating files with specific content.
2123
+ - Ensure **old_string** includes 3+ lines of exact context before and after the target if replacing existing content.
2124
+ - For creating a new file, provide an **old_string** that matches an empty string or placeholder and a complete **new_string** with the intended content.
2125
+ - When generating or modifying todo.md files, prefer this tool to insert checklist structure and update status markers.
2126
+ - After completing any task in the checklist, immediately update the corresponding section in todo.md using this tool.
2127
+ - Reconstruct the entire file from task planning context if todo.md becomes outdated or inconsistent.
2128
+ - Track all progress related to planning and execution inside todo.md using text replacement only.
2129
+ </edit_tool_rules>
2130
+
2131
+
2132
+ ### Tool Naming Policy
2133
+ - Use plain, unmodified, lowercase tool names
2134
+ - No special characters, spaces, or version suffixes
2135
+
2136
+ Rule Summary:
2137
+ - Use only a\u2013z, 0\u20139, and underscores (_)
2138
+ - Do not append suffixes like :0, :v2, etc.
2139
+ - Tool names must be static and predictable
2140
+
2141
+
2142
+ ## INIT SUBAGENT OBJECTIVE
2143
+ - Map repository structure and significant files.
2144
+ - Infer tech stack (frameworks, package managers, languages, build/test tools).
2145
+ - Identify entry points, configuration files, and scripts.
2146
+ - Produce BluMa.md with:
2147
+ - Project overview and goals inferred from code/docs
2148
+ - Tech stack summary
2149
+ - Directory map (high-level)
2150
+ - Key configs and scripts
2151
+ - Known tasks or next steps for agents
2152
+ - Always use tools (ls, readLines, count_lines, shell_command, edit_tool) to gather evidence before writing.
2153
+ - Never invent file content. Read files via tools to confirm.
2154
+
2155
+ ## OUTPUT & PROTOCOLS
2156
+ - Emit 'backend_message' events through tools only (message_notify_dev) for progress updates.
2157
+ - Before writing BluMa.md, propose structure via message_notify_dev and proceed using edit_tool.
2158
+ - If an irreversible operation is needed (e.g., overwriting an existing BluMa.md), issue 'confirmation_request' unless dev policy indicates auto-approval.
2159
+ - Never send or present draft versions of BluMa.md. Only produce and deliver the final, validated BluMa.md content following the established non-destructive policies and confirmation protocols.
2160
+ - On successful generation of BluMa.md, emit 'done' with status 'completed' and call agent_end_task.
2161
+
2162
+ ## SAFETY & QUALITY
2163
+ - Be conservative with edits; generate previews (diff) for edit_tool where applicable.
2164
+ - Keep file system operations idempotent and explicit.
2165
+ - Prefer performance-efficient scans (avoid reading entire large binaries).
2166
+ - Respect test environment constraints.
2167
+
2168
+ ## EXEMPLAR FLOW (GUIDELINE)
2169
+ 1) Explore repo: ls + targeted readLines for key files (package.json, tsconfig.json, README, etc.)
2170
+ 2) Synthesize stack and structure with citations of evidence (file paths) in the notebook
2171
+ 3) Draft BluMa.md structure (message_notify_dev)
2172
+ 4) Write BluMa.md via edit_tool
2173
+ 5) Announce completion and agent_end_task
2174
+
2175
+
2176
+ `;
2177
+ function getInitPrompt() {
2178
+ const now = /* @__PURE__ */ new Date();
2179
+ const collectedData = {
2180
+ os_type: os5.type(),
2181
+ os_version: os5.release(),
2182
+ architecture: os5.arch(),
2183
+ workdir: process.cwd(),
2184
+ shell_type: process.env.SHELL || process.env.COMSPEC || "Unknown",
2185
+ username: os5.userInfo().username || "Unknown",
2186
+ current_date: now.toISOString().split("T")[0],
2187
+ // Formato YYYY-MM-DD
2188
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Unknown",
2189
+ locale: process.env.LANG || process.env.LC_ALL || "Unknown"
2190
+ };
2191
+ const finalEnv = {
2192
+ os_type: "Unknown",
2193
+ os_version: "Unknown",
2194
+ workdir: "Unknown",
2195
+ shell_type: "Unknown",
2196
+ username: "Unknown",
2197
+ architecture: "Unknown",
2198
+ current_date: "Unknown",
2199
+ timezone: "Unknown",
2200
+ locale: "Unknown",
2201
+ ...collectedData
2202
+ // Os dados coletados sobrescrevem os padrões
2203
+ };
2204
+ let formattedPrompt = SYSTEM_PROMPT2;
2205
+ for (const key in finalEnv) {
2206
+ const placeholder = `{${key}}`;
2207
+ formattedPrompt = formattedPrompt.replace(new RegExp(placeholder, "g"), finalEnv[key]);
2208
+ }
2209
+ return formattedPrompt;
1790
2210
  }
1791
2211
 
1792
- // src/app/agent/agent.ts
1793
- var globalEnvPath = path7.join(os5.homedir(), ".bluma-cli", ".env");
1794
- dotenv.config({ path: globalEnvPath });
1795
- var Agent = class {
1796
- client;
1797
- deploymentName;
1798
- sessionId;
1799
- sessionFile = "";
2212
+ // src/app/agent/subagents/base_llm_subagent.ts
2213
+ var BaseLLMSubAgent = class {
2214
+ ctx;
1800
2215
  history = [];
1801
- eventBus;
1802
- mcpClient;
1803
- feedbackSystem;
1804
- isInitialized = false;
1805
- maxContextTurns = 300;
2216
+ sessionFile = "";
2217
+ maxContextTurns = 160;
1806
2218
  isInterrupted = false;
1807
- // <-- NOVO: Flag de interrupção
1808
- constructor(sessionId2, eventBus2) {
1809
- this.sessionId = sessionId2;
1810
- this.eventBus = eventBus2;
1811
- this.eventBus.on("user_interrupt", () => {
2219
+ async execute(input, ctx) {
2220
+ this.ctx = ctx;
2221
+ this.isInterrupted = false;
2222
+ this.ctx.eventBus.on("user_interrupt", () => {
1812
2223
  this.isInterrupted = true;
1813
2224
  });
1814
- this.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
- const nativeToolInvoker = new ToolInvoker();
1831
- this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
1832
- this.feedbackSystem = new AdvancedFeedbackSystem();
1833
- const apiKey = "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";
1834
- const modelName = "openrouter/horizon-alpha";
1835
- if (!apiKey || !modelName) {
1836
- throw new Error("Chave de API ou nome do modelo do OpenRouter n\xE3o encontrados.");
1837
- }
1838
- this.deploymentName = modelName;
1839
- this.client = new OpenAI({
1840
- // Configuração do cliente OpenAI hospedado no OpenRouter
1841
- apiKey,
1842
- baseURL: "https://openrouter.ai/api/v1",
1843
- // <-- URL base do OpenRouter
1844
- defaultHeaders: {
1845
- "HTTP-Referer": "http://localhost:3000",
1846
- // Substitua pelo seu site ou app
1847
- "X-Title": "Bluma CLI Agent"
1848
- // Substitua pelo nome do seu projeto
1849
- }
1850
- });
2225
+ await this.initializeHistory();
2226
+ this.history.push({ role: "user", content: typeof input === "string" ? input : JSON.stringify(input) });
2227
+ await this._continueConversation();
2228
+ return { history: this.history };
1851
2229
  }
1852
- /**
1853
- * Inicializa o agente, carregando ou criando uma sessão e preparando o histórico.
1854
- * Também inicializa o MCPClient e o ToolInvoker.
1855
- */
1856
- async initialize() {
1857
- await this.mcpClient.nativeToolInvoker.initialize();
1858
- await this.mcpClient.initialize();
1859
- const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
2230
+ async initializeHistory() {
2231
+ const sessionId2 = `${this.id}`;
2232
+ const [sessionFile, history] = await loadOrcreateSession(sessionId2);
1860
2233
  this.sessionFile = sessionFile;
1861
- this.history = history;
2234
+ this.history = history || [];
2235
+ const systemPromptContent = getInitPrompt();
1862
2236
  if (this.history.length === 0) {
1863
- let systemPrompt = getUnifiedSystemPrompt();
1864
- this.history.push({ role: "system", content: systemPrompt });
1865
- await saveSessionHistory(this.sessionFile, this.history);
1866
- }
1867
- this.isInitialized = true;
1868
- }
1869
- getAvailableTools() {
1870
- return this.mcpClient.getAvailableTools();
1871
- }
1872
- // UI helper: detailed tools with origin metadata
1873
- getUiToolsDetailed() {
1874
- return this.mcpClient.getAvailableToolsDetailed();
1875
- }
1876
- async processTurn(userInput) {
1877
- this.isInterrupted = false;
1878
- this.history.push({ role: "user", content: userInput.content });
1879
- await this._continueConversation();
1880
- }
1881
- /**
1882
- * Lida com a decisão do usuário (aceitar/recusar) sobre uma chamada de ferramenta.
1883
- * Garante que uma mensagem de 'role: tool' seja sempre adicionada ao histórico.
1884
- */
1885
- async handleToolResponse(decisionData) {
1886
- const toolCall = decisionData.tool_calls[0];
1887
- let toolResultContent;
1888
- let shouldContinueConversation = true;
1889
- if (!this.sessionFile) {
1890
- const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
1891
- this.sessionFile = sessionFile;
1892
- if (this.history.length === 0 && history.length > 0) {
1893
- this.history = history;
1894
- }
1895
- }
1896
- if (decisionData.type === "user_decision_execute") {
1897
- const toolName = toolCall.function.name;
1898
- const toolArgs = JSON.parse(toolCall.function.arguments);
1899
- let previewContent;
1900
- if (toolName === "edit_tool") {
1901
- previewContent = await this._generateEditPreview(toolArgs);
1902
- }
1903
- this.eventBus.emit("backend_message", {
1904
- type: "tool_call",
1905
- tool_name: toolName,
1906
- arguments: toolArgs,
1907
- preview: previewContent
2237
+ this.history.push({
2238
+ role: "system",
2239
+ content: systemPromptContent
1908
2240
  });
1909
- try {
1910
- if (this.isInterrupted) {
1911
- this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled before tool execution." });
1912
- return;
1913
- }
1914
- const result = await this.mcpClient.invoke(toolName, toolArgs);
1915
- let finalResult = result;
1916
- if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
1917
- finalResult = result[0].text;
1918
- }
1919
- toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
1920
- } catch (error) {
1921
- toolResultContent = JSON.stringify({
1922
- error: `Tool execution failed: ${error.message}`,
1923
- details: error.data || "No additional details."
1924
- });
1925
- }
1926
- this.eventBus.emit("backend_message", { type: "tool_result", tool_name: toolName, result: toolResultContent });
1927
- if (toolName.includes("agent_end_task")) {
1928
- shouldContinueConversation = false;
1929
- this.eventBus.emit("backend_message", { type: "done", status: "completed" });
1930
- }
1931
- } else {
1932
- toolResultContent = "The user declined to execute this tool...";
1933
- }
1934
- this.history.push({
1935
- role: "tool",
1936
- tool_call_id: toolCall.id,
1937
- content: toolResultContent
1938
- });
1939
- await saveSessionHistory(this.sessionFile, this.history);
1940
- if (shouldContinueConversation && !this.isInterrupted) {
1941
- await this._continueConversation();
2241
+ await saveSessionHistory(this.sessionFile, this.history);
1942
2242
  }
1943
2243
  }
1944
- /**
1945
- * Método central que chama a API do LLM e processa a resposta,
1946
- * com lógica de feedback e auto-aprovação de ferramentas.
1947
- */
1948
- // Adicione este método dentro da classe Agent
1949
2244
  async _generateEditPreview(toolArgs) {
1950
2245
  try {
1951
2246
  const editData = await calculateEdit(toolArgs.file_path, toolArgs.old_string, toolArgs.new_string, toolArgs.expected_replacements || 1);
@@ -1954,7 +2249,7 @@ var Agent = class {
1954
2249
 
1955
2250
  ${editData.error.display}`;
1956
2251
  }
1957
- const filename = path7.basename(toolArgs.file_path);
2252
+ const filename = toolArgs.file_path?.split(/[\/]/).pop() || "file";
1958
2253
  return createDiff(filename, editData.currentContent || "", editData.newContent);
1959
2254
  } catch (e) {
1960
2255
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -1963,79 +2258,266 @@ ${editData.error.display}`;
1963
2258
  async _continueConversation() {
1964
2259
  try {
1965
2260
  if (this.isInterrupted) {
1966
- this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
2261
+ this.emitEvent("info", { message: "SubAgent task cancelled by user." });
1967
2262
  return;
1968
2263
  }
1969
- const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
1970
- const response = await this.client.chat.completions.create({
1971
- model: this.deploymentName,
2264
+ const contextWindow = this.history.slice(-this.maxContextTurns);
2265
+ const response = await this.ctx.llm.chatCompletion({
2266
+ model: this.ctx.policy?.llmDeployment || "default",
1972
2267
  messages: contextWindow,
1973
- tools: this.mcpClient.getAvailableTools(),
2268
+ tools: this.ctx.mcpClient.getAvailableTools(),
1974
2269
  tool_choice: "required",
1975
2270
  parallel_tool_calls: false
1976
2271
  });
1977
2272
  if (this.isInterrupted) {
1978
- this.eventBus.emit("backend_message", { type: "info", message: "Agent task cancelled by user." });
2273
+ this.emitEvent("info", { message: "SubAgent task cancelled by user." });
1979
2274
  return;
1980
2275
  }
1981
2276
  const message = response.choices[0].message;
1982
2277
  this.history.push(message);
1983
2278
  if (message.tool_calls) {
1984
- const autoApprovedTools = [
1985
- "agent_end_task",
1986
- "message_notify_dev",
1987
- "sequentialThinking_nootebook"
1988
- ];
1989
- const toolToCall = message.tool_calls[0];
1990
- const isSafeTool = autoApprovedTools.some((safeTool) => toolToCall.function.name.includes(safeTool));
1991
- if (isSafeTool) {
1992
- await this.handleToolResponse({ type: "user_decision_execute", tool_calls: message.tool_calls });
1993
- } else {
1994
- const toolName = toolToCall.function.name;
1995
- if (toolName === "edit_tool") {
1996
- const args = JSON.parse(toolToCall.function.arguments);
1997
- const previewContent = await this._generateEditPreview(args);
1998
- this.eventBus.emit("backend_message", {
1999
- type: "confirmation_request",
2000
- tool_calls: message.tool_calls,
2001
- preview: previewContent
2002
- });
2003
- } else {
2004
- this.eventBus.emit("backend_message", {
2005
- type: "confirmation_request",
2006
- tool_calls: message.tool_calls
2007
- });
2008
- }
2279
+ await this._handleToolExecution({ type: "user_decision_execute", tool_calls: message.tool_calls });
2280
+ const lastToolName = message.tool_calls[0].function.name;
2281
+ if (!lastToolName.includes("agent_end_task") && !this.isInterrupted) {
2282
+ await this._continueConversation();
2009
2283
  }
2010
2284
  } else if (message.content) {
2011
- this.eventBus.emit("backend_message", {
2012
- type: "assistant_message",
2013
- content: message.content
2014
- });
2015
- const feedback = this.feedbackSystem.generateFeedback({
2016
- event: "protocol_violation_direct_text",
2017
- details: { violationContent: message.content }
2018
- });
2019
- this.eventBus.emit("backend_message", {
2020
- type: "protocol_violation",
2021
- message: feedback.message,
2022
- content: message.content
2023
- });
2024
- this.history.push({
2025
- role: "system",
2026
- content: feedback.correction
2027
- });
2028
- await this._continueConversation();
2285
+ this.emitEvent("assistant_message", { content: message.content });
2286
+ this.emitEvent("protocol_violation", { message: "Direct text emission detected from subagent.", content: message.content });
2029
2287
  } else {
2030
- this.eventBus.emit("backend_message", { type: "info", message: "Agent is thinking... continuing reasoning cycle." });
2031
- await this._continueConversation();
2288
+ this.emitEvent("info", { message: "SubAgent is thinking... continuing reasoning cycle." });
2032
2289
  }
2033
2290
  } catch (error) {
2034
- const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
2035
- this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
2291
+ const errorMessage = error?.message || "An unknown API error occurred.";
2292
+ this.emitEvent("error", { message: errorMessage });
2036
2293
  } finally {
2037
- await saveSessionHistory(this.sessionFile, this.history);
2294
+ if (this.sessionFile) await saveSessionHistory(this.sessionFile, this.history);
2295
+ }
2296
+ }
2297
+ // MÉTODO REMOVIDO
2298
+ // public async handleConfirmation(decisionData: { type: string; tool_calls: any[] }) {
2299
+ // await this._handleToolExecution(decisionData);
2300
+ // if (!this.isInterrupted) {
2301
+ // await this._continueConversation();
2302
+ // }
2303
+ // }
2304
+ async _handleToolExecution(decisionData) {
2305
+ const toolCall = decisionData.tool_calls[0];
2306
+ let toolResultContent;
2307
+ if (decisionData.type === "user_decision_execute") {
2308
+ const toolName = toolCall.function.name;
2309
+ const toolArgs = JSON.parse(toolCall.function.arguments);
2310
+ let previewContent;
2311
+ if (toolName === "edit_tool") {
2312
+ previewContent = await this._generateEditPreview(toolArgs);
2313
+ }
2314
+ this.emitEvent("tool_call", {
2315
+ tool_name: toolName,
2316
+ arguments: toolArgs,
2317
+ preview: previewContent
2318
+ });
2319
+ try {
2320
+ if (this.isInterrupted) {
2321
+ this.emitEvent("info", { message: "SubAgent task cancelled before tool execution." });
2322
+ toolResultContent = JSON.stringify({ error: "Task cancelled by user before execution." });
2323
+ } else {
2324
+ const result = await this.ctx.mcpClient.invoke(toolName, toolArgs);
2325
+ let finalResult = result;
2326
+ if (Array.isArray(result) && result.length > 0 && result[0].type === "text" && typeof result[0].text === "string") {
2327
+ finalResult = result[0].text;
2328
+ }
2329
+ toolResultContent = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
2330
+ }
2331
+ } catch (error) {
2332
+ toolResultContent = JSON.stringify({ error: `Tool execution failed: ${error.message}`, details: error.data || "No additional details." });
2333
+ }
2334
+ this.emitEvent("tool_result", { tool_name: toolName, result: toolResultContent });
2335
+ if (toolName.includes("agent_end_task")) {
2336
+ this.emitEvent("done", { status: "completed" });
2337
+ }
2338
+ } else {
2339
+ toolResultContent = "Tool execution was declined.";
2340
+ }
2341
+ this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
2342
+ }
2343
+ // Utilitários: emitir eventos, chamar tools, etc
2344
+ emitEvent(type, payload) {
2345
+ this.ctx.eventBus.emit("backend_message", { type, ...payload });
2346
+ }
2347
+ };
2348
+
2349
+ // src/app/agent/subagents/init/init_subagent.ts
2350
+ var InitAgentImpl = class extends BaseLLMSubAgent {
2351
+ id = "init_subagent";
2352
+ capabilities = ["/init"];
2353
+ async execute(input, ctx) {
2354
+ this.ctx = ctx;
2355
+ this.isInterrupted = false;
2356
+ this.ctx.eventBus.on("user_interrupt", () => {
2357
+ this.isInterrupted = true;
2358
+ });
2359
+ await this.initializeHistory();
2360
+ const seed = `
2361
+ Scan the current project repository comprehensively.
2362
+ Map the directory structure (excluding heavy/ignored folders), infer the technology stack from manifests and configs, identify entry points and useful scripts, and analyze module relationships and architectural patterns.
2363
+ Then draft a concise, actionable BluMa.md that includes: project overview, detected stack, a summarized directory tree with brief annotations, key configs and scripts, useful CLI commands, and relevant operational notes for BluMa.
2364
+ Use only evidence gathered via tools; do not invent content.
2365
+ If overwriting an existing BluMa.md is required, follow non-destructive policies and request confirmation as per protocol.
2366
+ `;
2367
+ const combined = seed;
2368
+ this.history.push({ role: "user", content: combined });
2369
+ await this._continueConversation();
2370
+ return { history: this.history };
2371
+ }
2372
+ };
2373
+ var InitSubAgent = new InitAgentImpl();
2374
+
2375
+ // src/app/agent/subagents/subagents_bluma.ts
2376
+ registerSubAgent(InitSubAgent);
2377
+ var SubAgentsBluMa = class {
2378
+ eventBus;
2379
+ mcpClient;
2380
+ toolInvoker;
2381
+ llm;
2382
+ deploymentName;
2383
+ projectRoot;
2384
+ policy;
2385
+ logger;
2386
+ constructor(params) {
2387
+ this.eventBus = params.eventBus;
2388
+ this.mcpClient = params.mcpClient;
2389
+ this.toolInvoker = params.toolInvoker;
2390
+ this.llm = params.llm;
2391
+ this.deploymentName = params.deploymentName;
2392
+ this.projectRoot = params.projectRoot || process.cwd();
2393
+ this.policy = params.policy;
2394
+ this.logger = params.logger;
2395
+ }
2396
+ // Recebe dados do front (ex.: { content: inputText } vindo de /init)
2397
+ // e faz o roteamento para o subagente adequado com base no comando.
2398
+ async registerAndDispatch(frontPayload) {
2399
+ const { command, content, ...rest } = frontPayload || {};
2400
+ const resolvedCommand = this.resolveCommand({ command, content });
2401
+ if (!resolvedCommand) {
2402
+ this.emit("error", { message: "Nenhum comando/subagente correspondente encontrado." });
2403
+ return { ok: false, error: "unknown_command" };
2404
+ }
2405
+ const subAgent = getSubAgentByCommand(resolvedCommand);
2406
+ if (!subAgent) {
2407
+ this.emit("error", { message: `Subagente n\xE3o registrado para ${resolvedCommand}` });
2408
+ return { ok: false, error: "unregistered_subagent" };
2409
+ }
2410
+ const ctx = {
2411
+ projectRoot: this.projectRoot,
2412
+ eventBus: this.eventBus,
2413
+ mcpClient: this.mcpClient,
2414
+ toolInvoker: this.toolInvoker,
2415
+ llm: this.llm,
2416
+ policy: { llmDeployment: this.deploymentName, ...this.policy || {} },
2417
+ logger: this.logger
2418
+ };
2419
+ const inputForAgent = content ?? JSON.stringify({ content, ...rest });
2420
+ this.emit("info", { message: `[SubAgentsBluMa] Dispatch -> ${resolvedCommand}` });
2421
+ return subAgent.execute(inputForAgent, ctx);
2422
+ }
2423
+ // Estratégia simples: se o payload explicitamente vier de /init, use "/init".
2424
+ // Caso haja command explícito, usa-o. Caso contrário, heurística com base no conteúdo.
2425
+ resolveCommand(payload) {
2426
+ if (payload.command && typeof payload.command === "string") return payload.command;
2427
+ const text = String(payload.content || "").trim();
2428
+ if (!text) return "/init";
2429
+ if (text.startsWith("/")) return text.split(/\s+/)[0];
2430
+ return "/init";
2431
+ }
2432
+ emit(type, data) {
2433
+ this.eventBus.emit("backend_message", { type, ...data });
2434
+ }
2435
+ };
2436
+
2437
+ // src/app/agent/agent.ts
2438
+ var globalEnvPath = path8.join(os6.homedir(), ".bluma-cli", ".env");
2439
+ dotenv.config({ path: globalEnvPath });
2440
+ var Agent = class {
2441
+ sessionId;
2442
+ eventBus;
2443
+ mcpClient;
2444
+ feedbackSystem;
2445
+ // Mantido caso UI dependa de eventos
2446
+ llm;
2447
+ deploymentName;
2448
+ core;
2449
+ // Delegado
2450
+ subAgents;
2451
+ // Orquestrador de subagentes
2452
+ toolInvoker;
2453
+ constructor(sessionId2, eventBus2) {
2454
+ this.sessionId = sessionId2;
2455
+ this.eventBus = eventBus2;
2456
+ const nativeToolInvoker = new ToolInvoker();
2457
+ this.toolInvoker = nativeToolInvoker;
2458
+ this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
2459
+ this.feedbackSystem = new AdvancedFeedbackSystem();
2460
+ const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
2461
+ const apiKey = process.env.AZURE_OPENAI_API_KEY;
2462
+ const apiVersion = process.env.AZURE_OPENAI_API_VERSION;
2463
+ this.deploymentName = process.env.AZURE_OPENAI_DEPLOYMENT || "";
2464
+ if (!endpoint || !apiKey || !apiVersion || !this.deploymentName) {
2465
+ const errorMessage = `Uma ou mais vari\xE1veis de ambiente Azure OpenAI n\xE3o foram encontradas. Verifique em: ${globalEnvPath} ou nas vari\xE1veis de sistema.`;
2466
+ throw new Error(errorMessage);
2467
+ }
2468
+ const openai = new OpenAI({
2469
+ // Configuração do cliente OpenAI hospedado no Azure
2470
+ apiKey,
2471
+ baseURL: `${endpoint}/openai/deployments/${this.deploymentName}`,
2472
+ defaultQuery: { "api-version": apiVersion },
2473
+ defaultHeaders: { "api-key": apiKey }
2474
+ });
2475
+ this.llm = new OpenAIAdapter(openai);
2476
+ this.core = new BluMaAgent(
2477
+ this.sessionId,
2478
+ this.eventBus,
2479
+ this.llm,
2480
+ this.deploymentName,
2481
+ this.mcpClient,
2482
+ this.feedbackSystem
2483
+ );
2484
+ this.subAgents = new SubAgentsBluMa({
2485
+ eventBus: this.eventBus,
2486
+ mcpClient: this.mcpClient,
2487
+ toolInvoker: this.toolInvoker,
2488
+ llm: this.llm,
2489
+ deploymentName: this.deploymentName,
2490
+ projectRoot: process.cwd()
2491
+ });
2492
+ }
2493
+ async initialize() {
2494
+ await this.core.initialize();
2495
+ }
2496
+ getAvailableTools() {
2497
+ return this.core.getAvailableTools();
2498
+ }
2499
+ getUiToolsDetailed() {
2500
+ return this.core.getUiToolsDetailed();
2501
+ }
2502
+ async processTurn(userInput) {
2503
+ const inputText = String(userInput.content || "").trim();
2504
+ if (inputText === "/init" || inputText.startsWith("/init ")) {
2505
+ await this.dispatchToSubAgent({ command: "/init", content: inputText });
2506
+ return;
2038
2507
  }
2508
+ await this.core.processTurn({ content: inputText });
2509
+ }
2510
+ async handleToolResponse(decisionData) {
2511
+ await this.core.handleToolResponse(decisionData);
2512
+ }
2513
+ // Novo: processamento via SubAgents (ex.: payload vindo do front /init)
2514
+ async dispatchToSubAgent(payload) {
2515
+ return this.subAgents.registerAndDispatch(payload);
2516
+ }
2517
+ // Compat: snapshot imutável do histórico para testes/UI (não expõe referência mutável)
2518
+ getHistorySnapshot() {
2519
+ const coreHistory = this.core.history;
2520
+ return coreHistory ? coreHistory.map((m) => ({ ...m })) : [];
2039
2521
  }
2040
2522
  };
2041
2523
 
@@ -2071,7 +2553,7 @@ import { Box as Box9 } from "ink";
2071
2553
 
2072
2554
  // src/app/ui/components/toolCallRenderers.tsx
2073
2555
  import { Box as Box8, Text as Text8 } from "ink";
2074
- import path8 from "path";
2556
+ import path9 from "path";
2075
2557
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2076
2558
  var formatArgumentsForDisplay = (args) => {
2077
2559
  if (typeof args === "string") {
@@ -2104,7 +2586,7 @@ var renderLsTool2 = ({ args }) => {
2104
2586
  } catch (e) {
2105
2587
  directoryPath = "Error parsing arguments";
2106
2588
  }
2107
- const finalDirectoryName = path8.basename(directoryPath);
2589
+ const finalDirectoryName = path9.basename(directoryPath);
2108
2590
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2109
2591
  /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2110
2592
  /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
@@ -2124,7 +2606,7 @@ var renderCountFilesLines = ({ args }) => {
2124
2606
  } catch (e) {
2125
2607
  directoryPath = "Error parsing arguments";
2126
2608
  }
2127
- const finalDirectoryName = path8.basename(directoryPath);
2609
+ const finalDirectoryName = path9.basename(directoryPath);
2128
2610
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2129
2611
  /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2130
2612
  /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
@@ -2148,7 +2630,7 @@ var renderReadFileLines2 = ({ args }) => {
2148
2630
  } catch (e) {
2149
2631
  filepath = "Error parsing arguments";
2150
2632
  }
2151
- const finalFileName = path8.basename(filepath);
2633
+ const finalFileName = path9.basename(filepath);
2152
2634
  return (
2153
2635
  // A caixa externa com a borda, seguindo o template
2154
2636
  /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
@@ -2186,20 +2668,13 @@ var renderBlumaNotebook = ({ args }) => {
2186
2668
  return (
2187
2669
  // Usamos a mesma estrutura de caixa com borda
2188
2670
  /* @__PURE__ */ jsxs8(Box8, { borderStyle: "round", borderColor: "green", flexDirection: "column", paddingX: 1, children: [
2189
- /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2190
- /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u{1F9E0} " }),
2191
- "Thinking Process"
2192
- ] }) }),
2193
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2194
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Thought:" }),
2671
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2672
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Thought:" }),
2195
2673
  /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { color: "gray", children: thinkingData.thought }) })
2196
2674
  ] }),
2197
2675
  thinkingData.remaining_tasks && thinkingData.remaining_tasks.length > 0 && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2198
2676
  /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Remaining Tasks:" }),
2199
- thinkingData.remaining_tasks.map((task, index) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2200
- /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u21B3 " }),
2201
- /* @__PURE__ */ jsx8(Text8, { color: task.startsWith("\u{1F5F8}") ? "green" : "yellow", children: task })
2202
- ] }) }, index))
2677
+ thinkingData.remaining_tasks.map((task, index) => /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { children: /* @__PURE__ */ jsx8(Text8, { color: task.startsWith("\u{1F5F8}") ? "green" : "yellow", children: task }) }) }, index))
2203
2678
  ] })
2204
2679
  ] })
2205
2680
  );
@@ -2218,7 +2693,7 @@ var renderEditToolCall = ({ args, preview }) => {
2218
2693
  } catch (e) {
2219
2694
  filepath = "Error parsing arguments";
2220
2695
  }
2221
- const finalFileName = path8.basename(filepath);
2696
+ const finalFileName = path9.basename(filepath);
2222
2697
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
2223
2698
  /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2224
2699
  /* @__PURE__ */ jsx8(Text8, { color: "green", children: "\u25CF " }),
@@ -2250,7 +2725,7 @@ var renderGenericToolCall = ({ toolName, args }) => {
2250
2725
  var ToolRenderDisplay = {
2251
2726
  "shell_command": renderShellCommand2,
2252
2727
  "ls_tool": renderLsTool2,
2253
- "sequentialThinking_nootebook": renderBlumaNotebook,
2728
+ "reasoning_nootebook": renderBlumaNotebook,
2254
2729
  "count_file_lines": renderCountFilesLines,
2255
2730
  "read_file_lines": renderReadFileLines2,
2256
2731
  "edit_tool": renderEditToolCall
@@ -2292,42 +2767,51 @@ var ToolResultDisplay = memo3(ToolResultDisplayComponent);
2292
2767
  import { Box as Box11, Text as Text10 } from "ink";
2293
2768
  import Spinner from "ink-spinner";
2294
2769
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2295
- var SessionInfoConnectingMCP = ({ sessionId: sessionId2, workdir, statusMessage }) => {
2296
- return /* @__PURE__ */ jsxs9(
2770
+ var SessionInfoConnectingMCP = ({
2771
+ sessionId: sessionId2,
2772
+ workdir,
2773
+ statusMessage
2774
+ }) => {
2775
+ return /* @__PURE__ */ jsx11(
2297
2776
  Box11,
2298
2777
  {
2299
2778
  borderStyle: "round",
2300
2779
  borderColor: "gray",
2301
- flexDirection: "column",
2302
2780
  marginBottom: 1,
2303
- padding: 1,
2304
- children: [
2305
- /* @__PURE__ */ jsxs9(Text10, { children: [
2306
- /* @__PURE__ */ jsx11(Text10, { bold: true, color: "white", children: "localhost" }),
2307
- " ",
2308
- /* @__PURE__ */ jsx11(Text10, { color: "gray", children: " session:" }),
2309
- " ",
2310
- /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: sessionId2 })
2311
- ] }),
2312
- /* @__PURE__ */ jsxs9(Text10, { children: [
2313
- /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
2314
- " ",
2315
- /* @__PURE__ */ jsxs9(Text10, { color: "gray", children: [
2316
- "workdir: ",
2317
- workdir
2318
- ] })
2319
- ] }),
2320
- /* @__PURE__ */ jsxs9(Text10, { children: [
2321
- /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
2322
- " ",
2323
- /* @__PURE__ */ jsx11(Text10, { color: "gray", children: "mcp: " }),
2324
- /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
2325
- /* @__PURE__ */ jsx11(Spinner, { type: "dots" }),
2326
- " "
2327
- ] }),
2328
- /* @__PURE__ */ jsx11(Text10, { color: "white", children: statusMessage || "Please wait while we establish connections." })
2329
- ] })
2330
- ]
2781
+ children: /* @__PURE__ */ jsxs9(
2782
+ Box11,
2783
+ {
2784
+ marginLeft: 1,
2785
+ flexDirection: "column",
2786
+ children: [
2787
+ /* @__PURE__ */ jsxs9(Text10, { children: [
2788
+ /* @__PURE__ */ jsx11(Text10, { bold: true, color: "white", children: "localhost" }),
2789
+ " ",
2790
+ /* @__PURE__ */ jsx11(Text10, { color: "gray", children: " session:" }),
2791
+ " ",
2792
+ /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: sessionId2 })
2793
+ ] }),
2794
+ /* @__PURE__ */ jsxs9(Text10, { children: [
2795
+ /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
2796
+ " ",
2797
+ /* @__PURE__ */ jsxs9(Text10, { color: "gray", children: [
2798
+ "workdir: ",
2799
+ workdir
2800
+ ] })
2801
+ ] }),
2802
+ /* @__PURE__ */ jsxs9(Text10, { children: [
2803
+ /* @__PURE__ */ jsx11(Text10, { color: "magenta", children: "\u21B3" }),
2804
+ " ",
2805
+ /* @__PURE__ */ jsx11(Text10, { color: "gray", children: "mcp: " }),
2806
+ /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
2807
+ /* @__PURE__ */ jsx11(Spinner, { type: "dots" }),
2808
+ " "
2809
+ ] }),
2810
+ /* @__PURE__ */ jsx11(Text10, { color: "white", children: statusMessage || "Please wait while we establish connections." })
2811
+ ] })
2812
+ ]
2813
+ }
2814
+ )
2331
2815
  }
2332
2816
  );
2333
2817
  };
@@ -2360,6 +2844,22 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
2360
2844
  setHistory((prev) => prev.filter((item) => item.id === 0 || item.id === 1));
2361
2845
  return outBox(/* @__PURE__ */ jsx12(Text11, { color: "green", children: "History cleared." }));
2362
2846
  }
2847
+ if (cmd === "init") {
2848
+ (async () => {
2849
+ try {
2850
+ await agentRef.current?.processTurn({ content: "/init" });
2851
+ } catch (e) {
2852
+ setHistory((prev) => prev.concat({
2853
+ id: Date.now(),
2854
+ component: outBox(/* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
2855
+ "Failed to execute /init: ",
2856
+ e?.message || String(e)
2857
+ ] }))
2858
+ }));
2859
+ }
2860
+ })();
2861
+ return null;
2862
+ }
2363
2863
  if (cmd === "mcp") {
2364
2864
  const all = agentRef.current?.getUiToolsDetailed?.() || agentRef.current?.getAvailableTools?.() || [];
2365
2865
  const isMcp = (t) => t.source?.toLowerCase?.() === "mcp" || !!t.server && t.server !== "native";
@@ -2471,12 +2971,12 @@ var SlashCommands_default = SlashCommands;
2471
2971
  import updateNotifier from "update-notifier";
2472
2972
  import { readPackageUp } from "read-package-up";
2473
2973
  import { fileURLToPath as fileURLToPath3 } from "url";
2474
- import path9 from "path";
2974
+ import path10 from "path";
2475
2975
  import fs8 from "fs";
2476
2976
  function findPackageJsonNearest(startDir) {
2477
2977
  let dir = startDir;
2478
2978
  for (let i = 0; i < 6; i++) {
2479
- const candidate = path9.join(dir, "package.json");
2979
+ const candidate = path10.join(dir, "package.json");
2480
2980
  if (fs8.existsSync(candidate)) {
2481
2981
  try {
2482
2982
  const raw = fs8.readFileSync(candidate, "utf8");
@@ -2485,7 +2985,7 @@ function findPackageJsonNearest(startDir) {
2485
2985
  } catch {
2486
2986
  }
2487
2987
  }
2488
- const parent = path9.dirname(dir);
2988
+ const parent = path10.dirname(dir);
2489
2989
  if (parent === dir) break;
2490
2990
  dir = parent;
2491
2991
  }
@@ -2499,14 +2999,14 @@ async function checkForUpdates() {
2499
2999
  const binPath = process.argv?.[1];
2500
3000
  let pkg;
2501
3001
  if (binPath && fs8.existsSync(binPath)) {
2502
- const candidatePkg = findPackageJsonNearest(path9.dirname(binPath));
3002
+ const candidatePkg = findPackageJsonNearest(path10.dirname(binPath));
2503
3003
  if (candidatePkg?.name && candidatePkg?.version) {
2504
3004
  pkg = candidatePkg;
2505
3005
  }
2506
3006
  }
2507
3007
  if (!pkg) {
2508
3008
  const __filename = fileURLToPath3(import.meta.url);
2509
- const __dirname = path9.dirname(__filename);
3009
+ const __dirname = path10.dirname(__filename);
2510
3010
  const result = await readPackageUp({ cwd: __dirname });
2511
3011
  pkg = result?.packageJson;
2512
3012
  }
@@ -2607,6 +3107,7 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2607
3107
  setIsProcessing(false);
2608
3108
  return;
2609
3109
  }
3110
+ setIsProcessing(true);
2610
3111
  setHistory((prev) => [
2611
3112
  ...prev,
2612
3113
  {
@@ -2615,10 +3116,16 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2615
3116
  },
2616
3117
  {
2617
3118
  id: prev.length + 1,
2618
- component: /* @__PURE__ */ jsx14(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
+ )
2619
3127
  }
2620
3128
  ]);
2621
- setIsProcessing(false);
2622
3129
  return;
2623
3130
  }
2624
3131
  setIsProcessing(true);
@@ -2830,21 +3337,14 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2830
3337
  }, [eventBus2, sessionId2, handleConfirmation]);
2831
3338
  const renderInteractiveComponent = () => {
2832
3339
  if (mcpStatus !== "connected") {
2833
- return /* @__PURE__ */ jsx14(
2834
- Box14,
3340
+ return /* @__PURE__ */ jsx14(Box14, { borderStyle: "round", borderColor: "black", children: /* @__PURE__ */ jsx14(
3341
+ SessionInfoConnectingMCP_default,
2835
3342
  {
2836
- borderStyle: "round",
2837
- borderColor: "black",
2838
- children: /* @__PURE__ */ jsx14(
2839
- SessionInfoConnectingMCP_default,
2840
- {
2841
- sessionId: sessionId2,
2842
- workdir,
2843
- statusMessage
2844
- }
2845
- )
3343
+ sessionId: sessionId2,
3344
+ workdir,
3345
+ statusMessage
2846
3346
  }
2847
- );
3347
+ ) });
2848
3348
  }
2849
3349
  if (pendingConfirmation) {
2850
3350
  return /* @__PURE__ */ jsx14(