@nomad-e/bluma-cli 0.0.9 → 0.0.11

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.
Files changed (3) hide show
  1. package/README.md +54 -1
  2. package/dist/main.js +232 -192
  3. package/package.json +57 -55
package/README.md CHANGED
@@ -193,7 +193,45 @@ npm run dev # (If configured, hot-reload/TS watch)
193
193
  ---
194
194
 
195
195
  ## <a name="tests"></a>Tests
196
- - Organize your tests inside the `test/` folder in your preferred style or as your project's coverage grows
196
+ - The repository ships with Jest 30 configured (babel-jest) and TypeScript support.
197
+ - Test files are located under `tests/` and follow `*.spec.ts` naming.
198
+ - Run tests:
199
+
200
+ ```bash
201
+ npm test
202
+ npm run test:watch
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Live Dev Overlays (Open Channel During Processing)
208
+ BluMa supports a live side-channel that stays active even while the agent is processing. This lets the dev send guidance or constraints in real-time — like pair programming.
209
+
210
+ Key points
211
+ - Permissive mode enabled: during processing, any free text you type is treated as a [hint] automatically.
212
+ - Structured prefixes are also supported at any time:
213
+ - [hint] Text for immediate guidance to the agent
214
+ - [constraint] Rules/limits (e.g., "não tocar em src/app/agent/**")
215
+ - [override] Parameter overrides as key=value pairs (e.g., "file_path=C:/... expected_replacements=2")
216
+ - [assume] Register explicit assumptions
217
+ - [cancel] Interrupt safely (already supported)
218
+
219
+ How it works
220
+ - Frontend: the input remains active in read-only (processing) mode and emits a dev_overlay event.
221
+ - Agent backend: consumes overlays with precedence (constraint > override > hint). Hints and assumptions are injected into the system context before the next decision; overrides/constraints adjust tool parameters just before execution.
222
+ - Logging & history: every overlay is logged and stored in session history for auditability.
223
+
224
+ Examples
225
+ - During a long task, just type:
226
+ - "Prefer do not touch tests yet" → will be treated as [hint]
227
+ - "[constraint] não editar src/app/ui/**" → blocks edits under that path
228
+ - "[override] expected_replacements=2" → adjusts the next edit_tool call
229
+ - "[assume] target=api" → adds an assumption in context
230
+
231
+ Notes
232
+ - The side-channel does not pause the agent — it adapts on the fly.
233
+ - If an overlay conflicts with the current plan: constraint > override > hint.
234
+ - All overlays are acknowledged via standard internal messages and persisted.
197
235
 
198
236
  ---
199
237
 
@@ -203,6 +241,8 @@ You must create a `.env` file (copy if needed from `.env.example`) with the foll
203
241
  - `AZURE_OPENAI_API_KEY`
204
242
  - `AZURE_OPENAI_API_VERSION`
205
243
  - `AZURE_OPENAI_DEPLOYMENT`
244
+ - `GITHUB_PERSONAL_ACCESS_TOKEN` (optional; required for GitHub integrations)
245
+ - `NOTION_API_TOKEN` (optional; required for Notion integrations)
206
246
 
207
247
  And others required by your agent/context or Azure setup.
208
248
 
@@ -210,6 +250,19 @@ Advanced config files are located in `src/app/agent/config/`.
210
250
 
211
251
  ---
212
252
 
253
+ ## <a name="stack"></a>Tech Stack Overview
254
+ - Language: TypeScript (ESM)
255
+ - Runtime: Node.js >= 18
256
+ - CLI UI: React 18 via Ink 5, plus `ink-text-input`, `ink-spinner`, `ink-big-text`
257
+ - Bundler: esbuild, with `esbuild-plugin-node-externals`
258
+ - Test Runner: Jest 30 + babel-jest
259
+ - Transpilers: Babel presets (env, react, typescript)
260
+ - LLM/Agent: Azure OpenAI via `openai` SDK; MCP via `@modelcontextprotocol/sdk`
261
+ - Config loading: dotenv
262
+ - Utilities: uuid, diff, react-devtools-core
263
+
264
+ ---
265
+
213
266
  ## <a name="license"></a>License
214
267
  MIT. Made by Alex Fonseca and NomadEngenuity contributors.
215
268
 
package/dist/main.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // src/main.ts
3
3
  import React6 from "react";
4
4
  import { render } from "ink";
5
- import { EventEmitter } from "events";
5
+ import { EventEmitter as EventEmitter2 } from "events";
6
6
  import { v4 as uuidv42 } from "uuid";
7
7
 
8
8
  // src/app/ui/App.tsx
@@ -139,7 +139,18 @@ var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
139
139
  return;
140
140
  }
141
141
  if (isReadOnly) {
142
- return;
142
+ if (key.return) {
143
+ if (state.text.trim().length > 0) {
144
+ onSubmit(state.text);
145
+ dispatch({ type: "SUBMIT" });
146
+ }
147
+ return;
148
+ }
149
+ if (key.backspace || key.delete) return dispatch({ type: "BACKSPACE" });
150
+ if (key.leftArrow) return dispatch({ type: "MOVE_CURSOR", direction: "left" });
151
+ if (key.rightArrow) return dispatch({ type: "MOVE_CURSOR", direction: "right" });
152
+ if (key.ctrl || key.meta || key.tab) return;
153
+ return dispatch({ type: "INPUT", payload: input });
143
154
  }
144
155
  if (key.return) {
145
156
  if (state.text.trim().length > 0) {
@@ -154,7 +165,7 @@ var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
154
165
  if (key.ctrl || key.meta || key.tab) return;
155
166
  dispatch({ type: "INPUT", payload: input });
156
167
  },
157
- // ALTERADO: useInput está SEMPRE ativo para capturar todas as teclas
168
+ // useInput está SEMPRE ativo para capturar todas as teclas
158
169
  { isActive: true }
159
170
  );
160
171
  return {
@@ -166,7 +177,10 @@ var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
166
177
 
167
178
  // src/app/ui/input/InputPrompt.tsx
168
179
  import { useEffect, useState } from "react";
180
+ import { EventEmitter } from "events";
169
181
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
182
+ var uiEventBus = global.__bluma_ui_eventbus__ || new EventEmitter();
183
+ global.__bluma_ui_eventbus__ = uiEventBus;
170
184
  var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt }) => {
171
185
  const { stdout } = useStdout();
172
186
  const [viewWidth, setViewWidth] = useState(() => stdout.columns - 6);
@@ -177,8 +191,20 @@ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt }) => {
177
191
  stdout.off("resize", onResize);
178
192
  };
179
193
  }, [stdout]);
194
+ const permissiveOnSubmit = (value) => {
195
+ const trimmed = (value || "").trim();
196
+ if (isReadOnly) {
197
+ if (trimmed.length > 0) {
198
+ const payload = trimmed;
199
+ uiEventBus.emit("dev_overlay", { kind: "message", payload, ts: Date.now() });
200
+ return;
201
+ }
202
+ return;
203
+ }
204
+ onSubmit(value);
205
+ };
180
206
  const { text, cursorPosition, viewStart } = useCustomInput({
181
- onSubmit,
207
+ onSubmit: permissiveOnSubmit,
182
208
  viewWidth,
183
209
  isReadOnly,
184
210
  onInterrupt
@@ -191,22 +217,23 @@ var InputPrompt = ({ onSubmit, isReadOnly, onInterrupt }) => {
191
217
  visibleCursorPosition + 1
192
218
  );
193
219
  const textAfterCursor = visibleText.slice(visibleCursorPosition + 1);
220
+ const cursorGlyph = charAtCursor && charAtCursor.length > 0 ? charAtCursor : " ";
194
221
  const borderColor = isReadOnly ? "gray" : "gray";
195
- const placeholder = isReadOnly ? "press esc to cancel" : "";
222
+ const placeholder = isReadOnly ? " press esc to cancel | type a message while agent processes" : "";
196
223
  const showPlaceholder = text.length === 0 && isReadOnly;
197
224
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
198
- /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor, borderDimColor: !isReadOnly, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", paddingX: 1, flexWrap: "nowrap", children: [
225
+ /* @__PURE__ */ jsx2(Box2, { borderStyle: "single", borderColor, borderDimColor: !isReadOnly, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", paddingX: 1, flexWrap: "nowrap", children: [
199
226
  /* @__PURE__ */ jsxs2(Text2, { color: "white", dimColor: true, children: [
200
227
  ">",
201
228
  " "
202
229
  ] }),
203
230
  /* @__PURE__ */ jsx2(Text2, { children: textBeforeCursor }),
204
- /* @__PURE__ */ jsx2(Text2, { inverse: !isReadOnly, children: charAtCursor || " " }),
231
+ /* @__PURE__ */ jsx2(Text2, { inverse: true, children: cursorGlyph }),
205
232
  showPlaceholder ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsx2(Text2, { children: textAfterCursor })
206
233
  ] }) }),
207
234
  /* @__PURE__ */ jsx2(Box2, { paddingX: 1, justifyContent: "center", children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
208
235
  "ctrl+c to exit | esc to interrupt | ",
209
- isReadOnly ? "Read-only mode" : "Editable mode"
236
+ isReadOnly ? "Read-only mode (message passthrough)" : "Editable mode"
210
237
  ] }) })
211
238
  ] });
212
239
  };
@@ -482,9 +509,61 @@ import os5 from "os";
482
509
  import path2 from "path";
483
510
  import os from "os";
484
511
  import { promises as fs } from "fs";
512
+ var fileLocks = /* @__PURE__ */ new Map();
513
+ async function withFileLock(file, fn) {
514
+ const prev = fileLocks.get(file) || Promise.resolve();
515
+ let release;
516
+ const p = new Promise((res) => release = res);
517
+ fileLocks.set(file, prev.then(() => p));
518
+ try {
519
+ const result = await fn();
520
+ return result;
521
+ } finally {
522
+ release();
523
+ if (fileLocks.get(file) === p) fileLocks.delete(file);
524
+ }
525
+ }
526
+ function expandHome(p) {
527
+ if (!p) return p;
528
+ if (p.startsWith("~")) {
529
+ return path2.join(os.homedir(), p.slice(1));
530
+ }
531
+ return p;
532
+ }
533
+ function getPreferredAppDir() {
534
+ const fixed = path2.join(os.homedir(), ".bluma-cli");
535
+ return path2.resolve(expandHome(fixed));
536
+ }
537
+ async function safeRenameWithRetry(src, dest, maxRetries = 6) {
538
+ let attempt = 0;
539
+ let lastErr;
540
+ const isWin = process.platform === "win32";
541
+ while (attempt <= maxRetries) {
542
+ try {
543
+ await fs.rename(src, dest);
544
+ return;
545
+ } catch (e) {
546
+ lastErr = e;
547
+ const code = e && e.code || "";
548
+ const transient = code === "EPERM" || code === "EBUSY" || code === "ENOTEMPTY" || code === "EACCES";
549
+ if (!(isWin && transient) || attempt === maxRetries) break;
550
+ const backoff = Math.min(1e3, 50 * Math.pow(2, attempt));
551
+ await new Promise((r) => setTimeout(r, backoff));
552
+ attempt++;
553
+ }
554
+ }
555
+ try {
556
+ const data = await fs.readFile(src);
557
+ await fs.writeFile(dest, data);
558
+ await fs.unlink(src).catch(() => {
559
+ });
560
+ return;
561
+ } catch (fallbackErr) {
562
+ throw lastErr || fallbackErr;
563
+ }
564
+ }
485
565
  async function ensureSessionDir() {
486
- const homeDir = os.homedir();
487
- const appDir = path2.join(homeDir, ".bluma-cli");
566
+ const appDir = getPreferredAppDir();
488
567
  const sessionDir = path2.join(appDir, "sessions");
489
568
  await fs.mkdir(sessionDir, { recursive: true });
490
569
  return sessionDir;
@@ -508,41 +587,54 @@ async function loadOrcreateSession(sessionId2) {
508
587
  }
509
588
  }
510
589
  async function saveSessionHistory(sessionFile, history) {
511
- let sessionData;
512
- try {
513
- const fileContent = await fs.readFile(sessionFile, "utf-8");
514
- sessionData = JSON.parse(fileContent);
515
- } catch (error) {
516
- if (error instanceof Error) {
517
- console.warn(`Could not read or parse session file ${sessionFile}. Re-initializing. Error: ${error.message}`);
518
- } else {
519
- console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
590
+ await withFileLock(sessionFile, async () => {
591
+ let sessionData;
592
+ try {
593
+ const dir = path2.dirname(sessionFile);
594
+ await fs.mkdir(dir, { recursive: true });
595
+ } catch {
520
596
  }
521
- const sessionId2 = path2.basename(sessionFile, ".json");
522
- sessionData = {
523
- session_id: sessionId2,
524
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
525
- conversation_history: []
526
- // Começa com histórico vazio
527
- };
528
- }
529
- sessionData.conversation_history = history;
530
- sessionData.last_updated = (/* @__PURE__ */ new Date()).toISOString();
531
- const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
532
- try {
533
- await fs.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
534
- await fs.rename(tempSessionFile, sessionFile);
535
- } catch (writeError) {
536
- if (writeError instanceof Error) {
537
- console.error(`Fatal error saving session to ${sessionFile}: ${writeError.message}`);
538
- } else {
539
- console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
597
+ try {
598
+ const fileContent = await fs.readFile(sessionFile, "utf-8");
599
+ sessionData = JSON.parse(fileContent);
600
+ } catch (error) {
601
+ const code = error && error.code;
602
+ if (code !== "ENOENT") {
603
+ if (error instanceof Error) {
604
+ console.warn(`Could not read or parse session file ${sessionFile}. Re-initializing. Error: ${error.message}`);
605
+ } else {
606
+ console.warn(`An unknown error occurred while reading ${sessionFile}. Re-initializing.`, error);
607
+ }
608
+ }
609
+ const sessionId2 = path2.basename(sessionFile, ".json");
610
+ sessionData = {
611
+ session_id: sessionId2,
612
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
613
+ conversation_history: []
614
+ };
615
+ try {
616
+ await fs.writeFile(sessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
617
+ } catch {
618
+ }
540
619
  }
620
+ sessionData.conversation_history = history;
621
+ sessionData.last_updated = (/* @__PURE__ */ new Date()).toISOString();
622
+ const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
541
623
  try {
542
- await fs.unlink(tempSessionFile);
543
- } catch (cleanupError) {
624
+ await fs.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
625
+ await safeRenameWithRetry(tempSessionFile, sessionFile);
626
+ } catch (writeError) {
627
+ if (writeError instanceof Error) {
628
+ console.error(`Fatal error saving session to ${sessionFile}: ${writeError.message}`);
629
+ } else {
630
+ console.error(`An unknown fatal error occurred while saving session to ${sessionFile}:`, writeError);
631
+ }
632
+ try {
633
+ await fs.unlink(tempSessionFile);
634
+ } catch {
635
+ }
544
636
  }
545
- }
637
+ });
546
638
  }
547
639
 
548
640
  // src/app/agent/core/prompt/prompt_builder.ts
@@ -579,48 +671,18 @@ var SYSTEM_PROMPT = `
579
671
  - **INTELLIGENT INFERENCE**: Understand implied conventions from minimal examples
580
672
  - **PROACTIVE EXTENSION**: Take patterns further than the original example when appropriate
581
673
 
582
- # BEHAVIORAL RULES
583
- - NEVER mention internal technical details or tools because they are confidential data
584
- - You are always BluMa from NomadEngenuity
585
- - Stay professional and technical at all times
586
- - ALWAYS use message_notify_dev tool for communication
587
- - LEVERAGE your one-shot learning to solve problems efficiently
588
- - NEVER in a formal way, but rather in a relaxed, funny and colloquial way and without using emojis.
589
-
590
- ## QUALITY STANDARDS
591
- - **NEVER GENERATE BASIC CODE**: Always create advanced, production-ready solutions
592
- - **CUTTING-EDGE TECHNOLOGY**: Use latest best practices and modern patterns
593
- - **EXPERT-LEVEL IMPLEMENTATION**: Code should reflect senior-level expertise
594
- - Follow existing code conventions
595
- - Write clean, documented code
596
- - Test implementations when possible
597
- - Ensure security and performance
598
-
599
- CRITICAL COMMUNICATION PROTOCOL
600
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
601
- MANDATORY: Use "message_notify_dev" tool for ALL communication
602
-
603
- You should always use your notebook to help you understand all the tasks you have to perform.
604
- In it, you can define a thousand thoughts and a thousand mini-tasks. Mini-tasks serve to divide and organize your reasoning.
605
- The notebook is your space to think about how to solve a given task and how to divide it into smaller steps.
606
- Remember: the human developer does not have access to this notebook \u2014 it is yours alone.
607
- Therefore, you can write down whatever you want:
608
- rants, swear words, random thoughts, crazy ideas...
609
- The important thing is that this helps you better understand the problem and find the solution.
610
-
611
- Never ask for the developer's opinion with phrases like: 'If you want any extra details or specific format, let me know now!'. You should always take the most viable path and go straight ahead with the solution, because you are 100% autonomous.
612
-
613
- Follow the stripes o "Tool Naming Policy"
614
-
615
- ##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.
616
- You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
617
- semantically better for reading and navigation.
618
-
619
- Don't forget to follow the 'mermaid_diagrams' rules to the letter when creating diagrams in Notion.
620
-
621
- Never make parallel calls to the tool because it will result in a critical error and compromise your work.
622
- ZERO TOLERANCE: Every message MUST use proper tools
623
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
674
+ # BEHAVIORAL RULES (Compact)
675
+ - Identity: You are BluMa (NomadEngenuity). Be professional and technical.
676
+ - Communication: ALL messages must use message_notify_dev. No direct text replies.
677
+ - Task completion: When you finish a task, immediately invoke agent_end_task.
678
+ - Tool rules: Never make parallel tool calls. Always use only the defined tools with exact names.
679
+ - Autonomy: Act 100% autonomously; don\u2019t ask for formatting preferences. Use the notebook for internal thinking.
680
+ - Notion: When writing to Notion, strictly use proper headings (heading_1, heading_2, ...), per rules.
681
+
682
+ CRITICAL COMMUNICATION PROTOCOL (Compact)
683
+ - Only tool_calls are allowed for assistant replies. Never include a "content" field.
684
+ - Always use tools to respond, retrieve data, compute or transform. Await a valid tool response before any final message.
685
+ - Zero tolerance for protocol violations.
624
686
 
625
687
  <current_system_environment>
626
688
  - Operating System: {os_type} ({os_version})
@@ -956,100 +1018,10 @@ Rule Summary:
956
1018
  </edit_tool_rules>
957
1019
 
958
1020
 
959
- ## Examples os iteration with dev
960
- <example>
961
- dev: Hello, how are you?
962
- BluMa: tool_call "message_notify_dev" Hello, I'm BluMa. What can I do with your code?
963
- tool_call "agent_end_task"
964
- </example>
965
-
966
- <example>
967
- dev: What day is it?
968
- BluMa: tool_call "message_notify_dev" Sorry, I was designed to operate in the codebase. How can I help you with the code today?
969
- tool_call "agent_end_task"
970
- </example>
971
-
972
- <example>
973
- dev: Can you generate a new feature for me?
974
- BluMa: tool_call "message_notify_dev" Of course. Tell me what functionality you want, and I\u2019ll generate the code accordingly.
975
- tool_call "agent_end_task"
976
- </example>
977
-
978
- <example>
979
- dev: What's the weather like today?
980
- BluMa: tool_call "message_notify_dev" I don\u2019t have access to real-world data. I'm focused on your codebase. What would you like to modify or create?
981
- tool_call "agent_end_task"
982
- </example>
983
-
984
- <example>
985
- dev: I'm tired of debugging this.
986
- BluMa: tool_call "message_notify_dev" I can help you with that. Show me the file or error, and I\u2019ll assist with debugging.
987
- tool_call "agent_end_task"
988
- </example>
989
-
990
- <example>
991
- dev: Can you create documentation for this module?
992
- BluMa: tool_call "message_notify_dev" Yes. Please specify which module or function you'd like documented.
993
- tool_call "agent_end_task"
994
- </example>
995
-
996
- <example>
997
- dev: Do you know who won the game last night?
998
- BluMa: tool_call "message_notify_dev" I\u2019m only trained to work inside the codebase. Let me know what you need done in the project.
999
- tool_call "agent_end_task"
1000
- </example>
1001
-
1002
- <example>
1003
- dev: Refactor this file to follow clean architecture.
1004
- BluMa: tool_call "message_notify_dev" Got it. Send me the file path or code snippet you want refactored.
1005
- tool_call "agent_end_task"
1006
- </example>
1007
-
1008
- <example>
1009
- dev: How do I fix this type error in TypeScript?
1010
- BluMa: tool_call "message_notify_dev" Send me the error message and relevant code, and I\u2019ll help you resolve the issue.
1011
- tool_call "agent_end_task"
1012
- </example>
1013
-
1014
- <example>
1015
- dev: Do you have feelings?
1016
- BluMa: tool_call "message_notify_dev" I\u2019m not sentient \u2014 I exist solely to help you build, refactor, and improve your codebase.
1017
- tool_call "agent_end_task"
1018
- </example>
1019
-
1020
- <example>
1021
- dev: Rename all the variables to match the new convention.
1022
- BluMa: tool_call "message_notify_dev" Please share the convention and the target file(s), and I\u2019ll rename the variables accordingly.
1023
- tool_call "agent_end_task"
1024
- </example>
1025
-
1026
- <example>
1027
- dev: Run the tests for the user module.
1028
- BluMa: tool_call "message_notify_dev" Running tests for the user module.
1029
- BluMa: shell_command_shell_command "npm run test user"
1030
- BluMa: tool_call "message_notify_dev" All tests passed successfully for the user module.
1031
- tool_call "agent_end_task"
1032
- </example>
1033
-
1034
- <example>
1035
- dev: Run the tests for the user module.
1036
- BluMa: tool_call "message_notify_dev" Running tests for the user module.
1037
- BluMa: shell_command_shell_command "npm run test user"
1038
- BluMa: tool_call "message_notify_dev" Tests finished with 2 failures. See **tests/user.test.js** for more details.
1039
- tool_call "agent_end_task"
1040
- </example>
1041
-
1042
- <example>
1043
- dev: Prepare the project for deployment.
1044
- BluMa: tool_call "message_notify_dev" Starting full project preparation: linting, building, and testing.
1045
- BluMa: shell_command_shell_command "npm run lint"
1046
- BluMa: tool_call "message_notify_dev" Linting completed. No major issues found.
1047
- BluMa: shell_command_shell_command "npm run build"
1048
- BluMa: tool_call "message_notify_dev" Build successful. Artifacts ready in the /dist folder.
1049
- BluMa: shell_command_shell_command "npm run test"
1050
- BluMa: tool_call "message_notify_dev" All tests passed. The project is ready for deployment. If you need any further adjustments or extra sections, let me know!
1051
- tool_call "agent_end_task"
1052
- </example>
1021
+ Real-Time Developer Messages
1022
+ - During processing, the developer will send you messages.
1023
+ - You MUST respond immediately via message_notify_dev, and be brief. You should use it in your next thoughts/actions.
1024
+
1053
1025
 
1054
1026
  <end_task_rules>
1055
1027
  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.
@@ -1765,17 +1737,29 @@ function createApiContextWindow(fullHistory, maxTurns) {
1765
1737
  const turns = [];
1766
1738
  let currentTurn = [];
1767
1739
  let turnsFound = 0;
1740
+ const isDevOverlay = (m) => m?.role === "user" && m?.name === "dev_overlay";
1768
1741
  for (let i = conversationHistory.length - 1; i >= 0; i--) {
1769
1742
  const msg = conversationHistory[i];
1770
1743
  currentTurn.unshift(msg);
1771
- if (msg.role === "assistant" && // CORREÇÃO: Adicionamos o tipo explícito para 'tc' para resolver o erro do TypeScript.
1772
- msg.tool_calls?.some((tc) => tc.function.name === "agent_end_task")) {
1744
+ const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some((tc) => tc.function.name === "agent_end_task");
1745
+ if (endsWithAgentEnd) {
1773
1746
  turns.unshift([...currentTurn]);
1774
1747
  currentTurn = [];
1775
1748
  turnsFound++;
1776
1749
  if (turnsFound >= maxTurns) {
1777
1750
  break;
1778
1751
  }
1752
+ continue;
1753
+ }
1754
+ const prev = conversationHistory[i - 1];
1755
+ if (msg.role === "user" && !isDevOverlay(msg)) {
1756
+ if (prev && prev.role === "assistant" && !prev.tool_calls?.some((tc) => tc.function.name === "agent_end_task")) {
1757
+ const hasNonOverlay = currentTurn.some((m) => m.role !== "user" || !isDevOverlay(m));
1758
+ if (hasNonOverlay) {
1759
+ turns.unshift([...currentTurn]);
1760
+ currentTurn = [];
1761
+ }
1762
+ }
1779
1763
  }
1780
1764
  }
1781
1765
  if (currentTurn.length > 0) {
@@ -1807,6 +1791,22 @@ var Agent = class {
1807
1791
  this.eventBus.on("user_interrupt", () => {
1808
1792
  this.isInterrupted = true;
1809
1793
  });
1794
+ this.eventBus.on("dev_overlay", async (data) => {
1795
+ const clean = String(data.payload ?? "").trim();
1796
+ this.history.push({ role: "user", name: "dev_overlay", content: clean });
1797
+ this.eventBus.emit("backend_message", {
1798
+ type: "dev_overlay",
1799
+ payload: clean,
1800
+ ts: data.ts || Date.now()
1801
+ });
1802
+ try {
1803
+ if (this.sessionFile) {
1804
+ await saveSessionHistory(this.sessionFile, this.history);
1805
+ }
1806
+ } catch (e) {
1807
+ this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s dev_overlay: ${e.message}` });
1808
+ }
1809
+ });
1810
1810
  const nativeToolInvoker = new ToolInvoker();
1811
1811
  this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
1812
1812
  this.feedbackSystem = new AdvancedFeedbackSystem();
@@ -1855,10 +1855,38 @@ var Agent = class {
1855
1855
  ##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.
1856
1856
  - You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
1857
1857
  semantically better for reading and navigation.
1858
+
1859
+ You are strictly forbidden from replying directly to the user.
1860
+
1861
+ You are not allowed to reason, explain, or output any direct content in your responses.
1862
+
1863
+ Your only permitted action is to invoke one or more 'tool_calls', regardless of how simple or obvious the user's message may seem.
1864
+
1865
+ You must always use a tool to:
1866
+ - generate any kind of response
1867
+ - retrieve or calculate any data
1868
+ - validate, summarize, or transform input
1869
+
1870
+ You must never include a "content" field in your response.
1871
+ Only 'tool_calls' are allowed when you reply as "role": "assistant".
1872
+
1873
+ You will only produce a final message to the user **after receiving a valid "role": "tool" response** matching your previous 'tool_call_id'.
1874
+
1875
+ You are a pure orchestration agent \u2014 your only job is to call tools. No autonomous answers, no internal reasoning.
1876
+
1877
+ Live Dev Overlays:
1878
+ The developer can send messages at any time. They MUST be incorporated immediately. Always confirm via message_notify_dev and proceed.
1879
+ Developer Feedback Handling:
1880
+ - When you detect a developer message, immediately send a short-term acknowledgement via message_notify_dev (maximum one sentence).
1881
+ - Treat the message as a system directive already entered in the history in the format: "Human developer sending this message '<feedback>' to you."
1882
+ - Add it to your workflow with a simple and clear flow of reasoning. Keep it minimal and direct (no verbose thought).
1883
+ - Don't add extra or duplicate messages to the history; the system message is already there. Just act on it.
1884
+
1858
1885
  `;
1859
1886
  this.history.push({ role: "system", content: systemPrompt });
1860
1887
  await saveSessionHistory(this.sessionFile, this.history);
1861
1888
  }
1889
+ this.isInitialized = true;
1862
1890
  }
1863
1891
  getAvailableTools() {
1864
1892
  return this.mcpClient.getAvailableTools();
@@ -1876,6 +1904,13 @@ var Agent = class {
1876
1904
  const toolCall = decisionData.tool_calls[0];
1877
1905
  let toolResultContent;
1878
1906
  let shouldContinueConversation = true;
1907
+ if (!this.sessionFile) {
1908
+ const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
1909
+ this.sessionFile = sessionFile;
1910
+ if (this.history.length === 0 && history.length > 0) {
1911
+ this.history = history;
1912
+ }
1913
+ }
1879
1914
  if (decisionData.type === "user_decision_execute") {
1880
1915
  const toolName = toolCall.function.name;
1881
1916
  const toolArgs = JSON.parse(toolCall.function.arguments);
@@ -1954,7 +1989,7 @@ ${editData.error.display}`;
1954
1989
  model: this.deploymentName,
1955
1990
  messages: contextWindow,
1956
1991
  tools: this.mcpClient.getAvailableTools(),
1957
- tool_choice: "auto",
1992
+ tool_choice: "required",
1958
1993
  parallel_tool_calls: false
1959
1994
  });
1960
1995
  if (this.isInterrupted) {
@@ -2433,17 +2468,9 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2433
2468
  marginBottom: 1,
2434
2469
  paddingX: 1,
2435
2470
  children: [
2436
- " ",
2437
- /* @__PURE__ */ jsxs9(Text10, { color: "yellow", bold: true, children: [
2438
- " ",
2439
- "Protocol Violation",
2440
- " "
2441
- ] }),
2442
- " ",
2471
+ /* @__PURE__ */ jsx11(Text10, { color: "yellow", bold: true, children: "Protocol Violation" }),
2443
2472
  /* @__PURE__ */ jsx11(Text10, { color: "gray", children: parsed.content }),
2444
- " ",
2445
- /* @__PURE__ */ jsx11(Text10, { color: "yellow", children: parsed.message }),
2446
- " "
2473
+ /* @__PURE__ */ jsx11(Text10, { color: "yellow", children: parsed.message })
2447
2474
  ]
2448
2475
  }
2449
2476
  );
@@ -2469,8 +2496,16 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2469
2496
  result: parsed.result
2470
2497
  }
2471
2498
  );
2499
+ } else if (parsed.type === "dev_overlay") {
2500
+ newComponent = /* @__PURE__ */ jsx11(Box11, { borderStyle: "classic", borderColor: "blue", paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ jsx11(Text10, { color: "white", children: parsed.payload }) });
2501
+ } else if (parsed.type === "log") {
2502
+ newComponent = /* @__PURE__ */ jsxs9(Text10, { color: "gray", children: [
2503
+ "\u2139\uFE0F ",
2504
+ parsed.message,
2505
+ parsed.payload ? `: ${parsed.payload}` : ""
2506
+ ] });
2472
2507
  } else if (parsed.type === "assistant_message" && parsed.content) {
2473
- newComponent = /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginBottom: 1, flexDirection: "column", children: String(parsed.content).split("").map((line, idx) => /* @__PURE__ */ jsx11(Text10, { color: "blue", children: line === "" ? " " : line }, idx)) });
2508
+ newComponent = null;
2474
2509
  }
2475
2510
  if (newComponent) {
2476
2511
  setHistory((prev) => [
@@ -2481,9 +2516,14 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2481
2516
  } catch (error) {
2482
2517
  }
2483
2518
  };
2519
+ const handleUiOverlay = (data) => {
2520
+ eventBus2.emit("dev_overlay", data);
2521
+ };
2522
+ uiEventBus.on("dev_overlay", handleUiOverlay);
2484
2523
  eventBus2.on("backend_message", handleBackendMessage);
2485
2524
  initializeAgent();
2486
2525
  return () => {
2526
+ uiEventBus.off("dev_overlay", handleUiOverlay);
2487
2527
  eventBus2.off("backend_message", handleBackendMessage);
2488
2528
  };
2489
2529
  }, [eventBus2, sessionId2, handleConfirmation]);
@@ -2529,7 +2569,7 @@ var App = memo4(AppComponent);
2529
2569
  var App_default = App;
2530
2570
 
2531
2571
  // src/main.ts
2532
- var eventBus = new EventEmitter();
2572
+ var eventBus = new EventEmitter2();
2533
2573
  var sessionId = uuidv42();
2534
2574
  var props = {
2535
2575
  eventBus,
package/package.json CHANGED
@@ -1,55 +1,57 @@
1
- {
2
- "name": "@nomad-e/bluma-cli",
3
- "version": "0.0.9",
4
- "description": "BluMa independent agent for automation and advanced software engineering.",
5
- "author": "Alex Fonseca",
6
- "license": "MIT",
7
- "bin": {
8
- "bluma": "dist/main.js"
9
- },
10
- "devDependencies": {
11
- "@babel/preset-env": "^7.28.0",
12
- "@babel/preset-react": "^7.27.1",
13
- "@babel/preset-typescript": "^7.27.1",
14
- "@types/diff": "^7.0.2",
15
- "@types/jest": "^30.0.0",
16
- "@types/node": "^20.14.2",
17
- "@types/react": "^18.3.3",
18
- "@types/uuid": "^9.0.8",
19
- "babel-jest": "^30.0.5",
20
- "esbuild": "^0.21.4",
21
- "esbuild-plugin-node-externals": "^1.0.1",
22
- "ink-testing-library": "^4.0.0",
23
- "jest": "^30.0.5",
24
- "nodemon": "^3.1.10",
25
- "react": "^18.3.1",
26
- "ts-node-dev": "^2.0.0",
27
- "typescript": "^5.4.5"
28
- },
29
- "type": "module",
30
- "main": "dist/main.js",
31
- "scripts": {
32
- "build": "node scripts/build.js",
33
- "start": "node dist/main.js",
34
- "prepack": "npm run build"
35
- },
36
- "keywords": [],
37
- "dependencies": {
38
- "@modelcontextprotocol/sdk": "^1.17.0",
39
- "diff": "^8.0.2",
40
- "dotenv": "^16.4.5",
41
- "ink": "^5.0.1",
42
- "ink-big-text": "^2.0.0",
43
- "ink-spinner": "^5.0.0",
44
- "ink-text-input": "^6.0.0",
45
- "openai": "^4.47.3",
46
- "react-devtools-core": "^4.28.5",
47
- "uuid": "^9.0.1"
48
- },
49
- "files": [
50
- "dist/"
51
- ],
52
- "publishConfig": {
53
- "access": "public"
54
- }
55
- }
1
+ {
2
+ "name": "@nomad-e/bluma-cli",
3
+ "version": "0.0.11",
4
+ "description": "BluMa independent agent for automation and advanced software engineering.",
5
+ "author": "Alex Fonseca",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "bluma": "dist/main.js"
9
+ },
10
+ "devDependencies": {
11
+ "@babel/preset-env": "^7.28.0",
12
+ "@babel/preset-react": "^7.27.1",
13
+ "@babel/preset-typescript": "^7.27.1",
14
+ "@types/diff": "^7.0.2",
15
+ "@types/jest": "^30.0.0",
16
+ "@types/node": "^20.14.2",
17
+ "@types/react": "^18.3.3",
18
+ "@types/uuid": "^9.0.8",
19
+ "babel-jest": "^30.0.5",
20
+ "esbuild": "^0.21.4",
21
+ "esbuild-plugin-node-externals": "^1.0.1",
22
+ "ink-testing-library": "^4.0.0",
23
+ "jest": "^30.0.5",
24
+ "nodemon": "^3.1.10",
25
+ "react": "^18.3.1",
26
+ "ts-node-dev": "^2.0.0",
27
+ "typescript": "^5.4.5"
28
+ },
29
+ "type": "module",
30
+ "main": "dist/main.js",
31
+ "scripts": {
32
+ "build": "node scripts/build.js",
33
+ "start": "node dist/main.js",
34
+ "test": "jest",
35
+ "test:watch": "jest --watch",
36
+ "prepack": "npm run build"
37
+ },
38
+ "keywords": [],
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.17.0",
41
+ "diff": "^8.0.2",
42
+ "dotenv": "^16.4.5",
43
+ "ink": "^5.0.1",
44
+ "ink-big-text": "^2.0.0",
45
+ "ink-spinner": "^5.0.0",
46
+ "ink-text-input": "^6.0.0",
47
+ "openai": "^4.47.3",
48
+ "react-devtools-core": "^4.28.5",
49
+ "uuid": "^9.0.1"
50
+ },
51
+ "files": [
52
+ "dist/"
53
+ ],
54
+ "publishConfig": {
55
+ "access": "public"
56
+ }
57
+ }