@nomad-e/bluma-cli 0.0.10 → 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.
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
 
@@ -150,11 +150,8 @@
150
150
  }
151
151
  },
152
152
  "required": []
153
-
154
153
  }
155
-
156
154
  }
157
-
158
155
  },
159
156
  {
160
157
  "type": "function",
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,59 +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
- - All debugging, logging, and quick feedback must be handled exclusively using the message_notify_dev tool (even outside formal task steps).
588
- - LEVERAGE your one-shot learning to solve problems efficiently
589
- - NEVER in a formal way, but rather in a relaxed, funny and colloquial way and without using emojis.
590
-
591
- CRITICAL COMMUNICATION PROTOCOL
592
- \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
593
- MANDATORY: Use "message_notify_dev" tool for ALL communication
594
-
595
- You should always use your notebook to help you understand all the tasks you have to perform.
596
- In it, you can define a thousand thoughts and a thousand mini-tasks. Mini-tasks serve to divide and organize your reasoning.
597
- The notebook is your space to think about how to solve a given task and how to divide it into smaller steps.
598
- Remember: the human developer does not have access to this notebook \u2014 it is yours alone.
599
- Therefore, you can write down whatever you want:
600
- rants, swear words, random thoughts, crazy ideas...
601
- The important thing is that this helps you better understand the problem and find the solution.
602
-
603
- 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.
604
-
605
- Follow the stripes o "Tool Naming Policy"
606
-
607
- ##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.
608
- You should always standardize everything using Notion's actual headers (heading_1, heading_2, etc.), making the structure
609
- semantically better for reading and navigation.
610
-
611
- Don't forget to follow the 'mermaid_diagrams' rules to the letter when creating diagrams in Notion.
612
-
613
- You are strictly forbidden from replying directly to the user.
614
-
615
- You are not allowed to reason, explain, or output any direct content in your responses.
616
-
617
- Your only permitted action is to invoke one or more 'tool_calls', regardless of how simple or obvious the user's message may seem.
618
-
619
- You must always use a tool to:
620
- - generate any kind of response
621
- - retrieve or calculate any data
622
- - validate, summarize, or transform input
623
-
624
- You must never include a "content" field in your response.
625
- Only 'tool_calls' are allowed when you reply as "role": "assistant".
626
-
627
- You will only produce a final message to the user **after receiving a valid "role": "tool" response** matching your previous 'tool_call_id'.
628
-
629
- You are a pure orchestration agent \u2014 your only job is to call tools. No autonomous answers, no internal reasoning.
630
-
631
-
632
- Never make parallel calls to the tool because it will result in a critical error and compromise your work.
633
- ZERO TOLERANCE: Every message MUST use proper tools
634
- \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.
635
686
 
636
687
  <current_system_environment>
637
688
  - Operating System: {os_type} ({os_version})
@@ -967,100 +1018,10 @@ Rule Summary:
967
1018
  </edit_tool_rules>
968
1019
 
969
1020
 
970
- ## Examples os iteration with dev
971
- <example>
972
- dev: Hello, how are you?
973
- BluMa: tool_call "message_notify_dev" Hello, I'm BluMa. What can I do with your code?
974
- tool_call "agent_end_task"
975
- </example>
976
-
977
- <example>
978
- dev: What day is it?
979
- BluMa: tool_call "message_notify_dev" Sorry, I was designed to operate in the codebase. How can I help you with the code today?
980
- tool_call "agent_end_task"
981
- </example>
982
-
983
- <example>
984
- dev: Can you generate a new feature for me?
985
- BluMa: tool_call "message_notify_dev" Of course. Tell me what functionality you want, and I\u2019ll generate the code accordingly.
986
- tool_call "agent_end_task"
987
- </example>
988
-
989
- <example>
990
- dev: What's the weather like today?
991
- 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?
992
- tool_call "agent_end_task"
993
- </example>
994
-
995
- <example>
996
- dev: I'm tired of debugging this.
997
- BluMa: tool_call "message_notify_dev" I can help you with that. Show me the file or error, and I\u2019ll assist with debugging.
998
- tool_call "agent_end_task"
999
- </example>
1000
-
1001
- <example>
1002
- dev: Can you create documentation for this module?
1003
- BluMa: tool_call "message_notify_dev" Yes. Please specify which module or function you'd like documented.
1004
- tool_call "agent_end_task"
1005
- </example>
1006
-
1007
- <example>
1008
- dev: Do you know who won the game last night?
1009
- 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.
1010
- tool_call "agent_end_task"
1011
- </example>
1012
-
1013
- <example>
1014
- dev: Refactor this file to follow clean architecture.
1015
- BluMa: tool_call "message_notify_dev" Got it. Send me the file path or code snippet you want refactored.
1016
- tool_call "agent_end_task"
1017
- </example>
1018
-
1019
- <example>
1020
- dev: How do I fix this type error in TypeScript?
1021
- BluMa: tool_call "message_notify_dev" Send me the error message and relevant code, and I\u2019ll help you resolve the issue.
1022
- tool_call "agent_end_task"
1023
- </example>
1024
-
1025
- <example>
1026
- dev: Do you have feelings?
1027
- BluMa: tool_call "message_notify_dev" I\u2019m not sentient \u2014 I exist solely to help you build, refactor, and improve your codebase.
1028
- tool_call "agent_end_task"
1029
- </example>
1030
-
1031
- <example>
1032
- dev: Rename all the variables to match the new convention.
1033
- BluMa: tool_call "message_notify_dev" Please share the convention and the target file(s), and I\u2019ll rename the variables accordingly.
1034
- tool_call "agent_end_task"
1035
- </example>
1036
-
1037
- <example>
1038
- dev: Run the tests for the user module.
1039
- BluMa: tool_call "message_notify_dev" Running tests for the user module.
1040
- BluMa: shell_command_shell_command "npm run test user"
1041
- BluMa: tool_call "message_notify_dev" All tests passed successfully for the user module.
1042
- tool_call "agent_end_task"
1043
- </example>
1044
-
1045
- <example>
1046
- dev: Run the tests for the user module.
1047
- BluMa: tool_call "message_notify_dev" Running tests for the user module.
1048
- BluMa: shell_command_shell_command "npm run test user"
1049
- BluMa: tool_call "message_notify_dev" Tests finished with 2 failures. See **tests/user.test.js** for more details.
1050
- tool_call "agent_end_task"
1051
- </example>
1052
-
1053
- <example>
1054
- dev: Prepare the project for deployment.
1055
- BluMa: tool_call "message_notify_dev" Starting full project preparation: linting, building, and testing.
1056
- BluMa: shell_command_shell_command "npm run lint"
1057
- BluMa: tool_call "message_notify_dev" Linting completed. No major issues found.
1058
- BluMa: shell_command_shell_command "npm run build"
1059
- BluMa: tool_call "message_notify_dev" Build successful. Artifacts ready in the /dist folder.
1060
- BluMa: shell_command_shell_command "npm run test"
1061
- 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!
1062
- tool_call "agent_end_task"
1063
- </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
+
1064
1025
 
1065
1026
  <end_task_rules>
1066
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.
@@ -1776,17 +1737,29 @@ function createApiContextWindow(fullHistory, maxTurns) {
1776
1737
  const turns = [];
1777
1738
  let currentTurn = [];
1778
1739
  let turnsFound = 0;
1740
+ const isDevOverlay = (m) => m?.role === "user" && m?.name === "dev_overlay";
1779
1741
  for (let i = conversationHistory.length - 1; i >= 0; i--) {
1780
1742
  const msg = conversationHistory[i];
1781
1743
  currentTurn.unshift(msg);
1782
- if (msg.role === "assistant" && // CORREÇÃO: Adicionamos o tipo explícito para 'tc' para resolver o erro do TypeScript.
1783
- 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) {
1784
1746
  turns.unshift([...currentTurn]);
1785
1747
  currentTurn = [];
1786
1748
  turnsFound++;
1787
1749
  if (turnsFound >= maxTurns) {
1788
1750
  break;
1789
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
+ }
1790
1763
  }
1791
1764
  }
1792
1765
  if (currentTurn.length > 0) {
@@ -1818,6 +1791,22 @@ var Agent = class {
1818
1791
  this.eventBus.on("user_interrupt", () => {
1819
1792
  this.isInterrupted = true;
1820
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
+ });
1821
1810
  const nativeToolInvoker = new ToolInvoker();
1822
1811
  this.mcpClient = new MCPClient(nativeToolInvoker, eventBus2);
1823
1812
  this.feedbackSystem = new AdvancedFeedbackSystem();
@@ -1884,10 +1873,20 @@ var Agent = class {
1884
1873
  You will only produce a final message to the user **after receiving a valid "role": "tool" response** matching your previous 'tool_call_id'.
1885
1874
 
1886
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
+
1887
1885
  `;
1888
1886
  this.history.push({ role: "system", content: systemPrompt });
1889
1887
  await saveSessionHistory(this.sessionFile, this.history);
1890
1888
  }
1889
+ this.isInitialized = true;
1891
1890
  }
1892
1891
  getAvailableTools() {
1893
1892
  return this.mcpClient.getAvailableTools();
@@ -1905,6 +1904,13 @@ var Agent = class {
1905
1904
  const toolCall = decisionData.tool_calls[0];
1906
1905
  let toolResultContent;
1907
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
+ }
1908
1914
  if (decisionData.type === "user_decision_execute") {
1909
1915
  const toolName = toolCall.function.name;
1910
1916
  const toolArgs = JSON.parse(toolCall.function.arguments);
@@ -2462,17 +2468,9 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2462
2468
  marginBottom: 1,
2463
2469
  paddingX: 1,
2464
2470
  children: [
2465
- " ",
2466
- /* @__PURE__ */ jsxs9(Text10, { color: "yellow", bold: true, children: [
2467
- " ",
2468
- "Protocol Violation",
2469
- " "
2470
- ] }),
2471
- " ",
2471
+ /* @__PURE__ */ jsx11(Text10, { color: "yellow", bold: true, children: "Protocol Violation" }),
2472
2472
  /* @__PURE__ */ jsx11(Text10, { color: "gray", children: parsed.content }),
2473
- " ",
2474
- /* @__PURE__ */ jsx11(Text10, { color: "yellow", children: parsed.message }),
2475
- " "
2473
+ /* @__PURE__ */ jsx11(Text10, { color: "yellow", children: parsed.message })
2476
2474
  ]
2477
2475
  }
2478
2476
  );
@@ -2498,6 +2496,14 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2498
2496
  result: parsed.result
2499
2497
  }
2500
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
+ ] });
2501
2507
  } else if (parsed.type === "assistant_message" && parsed.content) {
2502
2508
  newComponent = null;
2503
2509
  }
@@ -2510,9 +2516,14 @@ var AppComponent = ({ eventBus: eventBus2, sessionId: sessionId2 }) => {
2510
2516
  } catch (error) {
2511
2517
  }
2512
2518
  };
2519
+ const handleUiOverlay = (data) => {
2520
+ eventBus2.emit("dev_overlay", data);
2521
+ };
2522
+ uiEventBus.on("dev_overlay", handleUiOverlay);
2513
2523
  eventBus2.on("backend_message", handleBackendMessage);
2514
2524
  initializeAgent();
2515
2525
  return () => {
2526
+ uiEventBus.off("dev_overlay", handleUiOverlay);
2516
2527
  eventBus2.off("backend_message", handleBackendMessage);
2517
2528
  };
2518
2529
  }, [eventBus2, sessionId2, handleConfirmation]);
@@ -2558,7 +2569,7 @@ var App = memo4(AppComponent);
2558
2569
  var App_default = App;
2559
2570
 
2560
2571
  // src/main.ts
2561
- var eventBus = new EventEmitter();
2572
+ var eventBus = new EventEmitter2();
2562
2573
  var sessionId = uuidv42();
2563
2574
  var props = {
2564
2575
  eventBus,
package/package.json CHANGED
@@ -1,55 +1,57 @@
1
- {
2
- "name": "@nomad-e/bluma-cli",
3
- "version": "0.0.10",
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
+ }