@nomad-e/bluma-cli 0.1.27 → 0.1.30

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 (2) hide show
  1. package/dist/main.js +95 -7
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -3428,6 +3428,54 @@ ${skill.content}`;
3428
3428
  import * as fs8 from "fs";
3429
3429
  import * as path10 from "path";
3430
3430
  import os4 from "os";
3431
+ var PROMPT_DEFAULT_MAX_TOTAL = 1e4;
3432
+ var PROMPT_DEFAULT_MAX_NOTES = 25;
3433
+ var PROMPT_DEFAULT_PREVIEW = 500;
3434
+ function readCodingMemoryForPrompt(options) {
3435
+ const maxTotal = options?.maxTotalChars ?? PROMPT_DEFAULT_MAX_TOTAL;
3436
+ const maxNotes = options?.maxNotes ?? PROMPT_DEFAULT_MAX_NOTES;
3437
+ const preview = options?.previewCharsPerNote ?? PROMPT_DEFAULT_PREVIEW;
3438
+ const globalPath = path10.join(os4.homedir(), ".bluma", "coding_memory.json");
3439
+ const legacyPath = path10.join(process.cwd(), ".bluma", "coding_memory.json");
3440
+ let raw = null;
3441
+ try {
3442
+ if (fs8.existsSync(globalPath)) {
3443
+ raw = fs8.readFileSync(globalPath, "utf-8");
3444
+ } else if (path10.resolve(globalPath) !== path10.resolve(legacyPath) && fs8.existsSync(legacyPath)) {
3445
+ raw = fs8.readFileSync(legacyPath, "utf-8");
3446
+ }
3447
+ } catch {
3448
+ return "";
3449
+ }
3450
+ if (!raw) return "";
3451
+ let data;
3452
+ try {
3453
+ data = JSON.parse(raw);
3454
+ } catch {
3455
+ return "";
3456
+ }
3457
+ const entries = Array.isArray(data.entries) ? [...data.entries] : [];
3458
+ if (entries.length === 0) return "";
3459
+ entries.sort((a, b) => (b.id ?? 0) - (a.id ?? 0));
3460
+ const parts = [];
3461
+ let used = 0;
3462
+ for (let i = 0; i < entries.length && i < maxNotes; i++) {
3463
+ const e = entries[i];
3464
+ const full = e.note || "";
3465
+ const body = full.length > preview ? `${full.slice(0, preview)}\u2026` : full;
3466
+ const block = `### id ${e.id} \xB7 tags: ${(e.tags || []).join(", ") || "(none)"}
3467
+ ${body}`;
3468
+ const piece = parts.length > 0 ? `
3469
+
3470
+ ---
3471
+
3472
+ ${block}` : block;
3473
+ if (used + piece.length > maxTotal) break;
3474
+ parts.push(piece);
3475
+ used += piece.length;
3476
+ }
3477
+ return parts.join("");
3478
+ }
3431
3479
  var memoryStore = [];
3432
3480
  var nextId2 = 1;
3433
3481
  var loaded = false;
@@ -4502,6 +4550,26 @@ You MUST adapt all commands to this environment. Use the correct package manager
4502
4550
 
4503
4551
  ---
4504
4552
 
4553
+ <coding_memory>
4554
+ ## Persistent coding memory (tool: \`coding_memory\`)
4555
+
4556
+ You have **durable notes** on disk (typically \`~/.bluma/coding_memory.json\`), separate from chat history. They **survive new sessions**.
4557
+
4558
+ ### Baseline vs live refresh
4559
+ - The **<coding_memory_snapshot>** block (appended after this prompt) is loaded **once at session start** from disk. It can become stale if memory changes later or if the topic was never saved.
4560
+ - Act like a teammate who **checks their notes**: before relying on "what we agreed" or project-specific lore, use \`coding_memory\` with \`action: "search"\` and a short \`query\` \u2014 especially when the user asks if you remember something, or when switching back to a topic after a long stretch of work.
4561
+
4562
+ ### When to write
4563
+ - After you learn **stable** facts (architecture, conventions, important URLs, decisions, repeated user preferences), call \`coding_memory\` with \`action: "add"\`, a concise \`note\`, and \`tags\` for later search.
4564
+ - When something **changes**, add an updated note (and do not treat outdated snapshot text as truth without searching).
4565
+
4566
+ ### Habits (continuity)
4567
+ - Prefer **search** or **list** over guessing when answering "remember / last time / what we did".
4568
+ - Long specs belong in the repo or artifacts; use \`coding_memory\` for **short** reminders and pointers.
4569
+ </coding_memory>
4570
+
4571
+ ---
4572
+
4505
4573
  <workflow>
4506
4574
  ## How You Work
4507
4575
 
@@ -5004,6 +5072,17 @@ User: "Publish the package"
5004
5072
  </available_skills>
5005
5073
  `;
5006
5074
  }
5075
+ const memorySnapshot = readCodingMemoryForPrompt();
5076
+ prompt += `
5077
+
5078
+ ---
5079
+
5080
+ <coding_memory_snapshot>
5081
+ ## Persistent notes (loaded at session start from disk)
5082
+
5083
+ ${memorySnapshot.trim().length > 0 ? memorySnapshot : "(No entries yet. Use coding_memory with action add or search to build continuity across sessions.)"}
5084
+ </coding_memory_snapshot>
5085
+ `;
5007
5086
  return prompt;
5008
5087
  }
5009
5088
  function isGitRepo(dir) {
@@ -5463,6 +5542,8 @@ var BluMaAgent = class {
5463
5542
  isInterrupted = false;
5464
5543
  /** Mesmo turnId durante processTurn + todo o loop de tool_calls (FactorRouter). */
5465
5544
  activeTurnContext = null;
5545
+ /** Evita POST /turns/.../end duplicado no mesmo turno (ex.: Esc após message_result). */
5546
+ factorRouterTurnClosed = false;
5466
5547
  constructor(sessionId, eventBus, llm, mcpClient, feedbackSystem) {
5467
5548
  this.sessionId = sessionId;
5468
5549
  this.eventBus = eventBus;
@@ -5472,6 +5553,7 @@ var BluMaAgent = class {
5472
5553
  this.skillLoader = new SkillLoader(process.cwd());
5473
5554
  this.eventBus.on("user_interrupt", () => {
5474
5555
  this.isInterrupted = true;
5556
+ void this.notifyFactorTurnEndIfNeeded("user_interrupt");
5475
5557
  this.eventBus.emit("backend_message", { type: "done", status: "interrupted" });
5476
5558
  });
5477
5559
  this.eventBus.on("user_overlay", async (data) => {
@@ -5533,6 +5615,7 @@ var BluMaAgent = class {
5533
5615
  }
5534
5616
  async processTurn(userInput, userContextInput) {
5535
5617
  this.isInterrupted = false;
5618
+ this.factorRouterTurnClosed = false;
5536
5619
  const inputText = String(userInput.content || "").trim();
5537
5620
  const turnId = uuidv43();
5538
5621
  this.activeTurnContext = {
@@ -5659,13 +5742,7 @@ var BluMaAgent = class {
5659
5742
  try {
5660
5743
  const resultObj = typeof toolResultContent === "string" ? JSON.parse(toolResultContent) : toolResultContent;
5661
5744
  if (resultObj.message_type === "result") {
5662
- if (this.activeTurnContext) {
5663
- await notifyFactorRouterTurnEnd({
5664
- turnId: this.activeTurnContext.turnId,
5665
- userContext: this.activeTurnContext,
5666
- reason: "message_result"
5667
- });
5668
- }
5745
+ await this.notifyFactorTurnEndIfNeeded("message_result");
5669
5746
  shouldContinueConversation = false;
5670
5747
  this.eventBus.emit("backend_message", { type: "done", status: "completed" });
5671
5748
  }
@@ -5709,6 +5786,17 @@ ${editData.error.display}`;
5709
5786
  }
5710
5787
  return this.activeTurnContext;
5711
5788
  }
5789
+ /** Um único aviso ao Factor Router por turno (fim normal ou interrupção). */
5790
+ async notifyFactorTurnEndIfNeeded(reason) {
5791
+ if (this.factorRouterTurnClosed || !this.activeTurnContext) return;
5792
+ this.factorRouterTurnClosed = true;
5793
+ const ctx = this.activeTurnContext;
5794
+ await notifyFactorRouterTurnEnd({
5795
+ turnId: ctx.turnId,
5796
+ userContext: ctx,
5797
+ reason
5798
+ });
5799
+ }
5712
5800
  async _continueConversation() {
5713
5801
  try {
5714
5802
  if (this.isInterrupted) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.27",
3
+ "version": "0.1.30",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",