@nomad-e/bluma-cli 0.1.47 → 0.1.49

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 +153 -27
  2. package/dist/main.js +181 -121
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,9 @@
4
4
  [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
5
5
  [![Node.js >=20](https://img.shields.io/badge/node-%3E%3D20-brightgreen?style=flat-square)](https://nodejs.org/)
6
6
 
7
- **BluMa** is a CLI-based model agent for advanced software engineering workflows. Built with React/Ink 5, it provides an interactive terminal interface for LLM-powered automation, code generation, refactoring, and task execution. Features persistent sessions, contextual reasoning, smart feedback, and extensible tools/skills architecture.
7
+ **BluMa** is a CLI-based model agent for advanced software engineering workflows. Built with React/Ink 5, it provides an interactive terminal interface for LLM-powered automation, code generation, refactoring, and task execution. Features persistent sessions, contextual reasoning, smart feedback, coordinator mode for worker orchestration, and extensible tools/skills architecture.
8
+
9
+ **Current Version:** 0.1.47
8
10
 
9
11
  ---
10
12
 
@@ -34,8 +36,8 @@ BluMa operates as a **conversational agent** in the terminal, combining:
34
36
 
35
37
  - **Rich UI Layer**: React/Ink 5 components for interactive prompts, live overlays, and real-time feedback
36
38
  - **Agent Layer**: LLM orchestration via FactorRouter with tool invocation and context management
37
- - **Runtime Layer**: Task tracking, plugin system, hooks, diagnostics, and session management
38
- - **Tool Layer**: 18 native tools + MCP SDK integration for external tools
39
+ - **Runtime Layer**: Task tracking, plugin system, hooks, diagnostics, session management, and coordinator mode
40
+ - **Tool Layer**: 40+ native tools + MCP SDK integration for external tools
39
41
 
40
42
  The agent maintains persistent conversation history, workspace snapshots, and coding memory across sessions.
41
43
 
@@ -60,7 +62,8 @@ The agent maintains persistent conversation history, workspace snapshots, and co
60
62
  - **Tool Execution Policy**: Intelligent decisions based on sandbox mode and safety
61
63
 
62
64
  ### Tools & Skills
63
- - **25+ Native Tools**: File operations, search, shell commands, web fetch, agent coordination
65
+ - **40+ Native Tools**: File operations, search, shell commands, web fetch, agent coordination, task management, MCP resources, cron scheduling, LSP queries, notebook editing
66
+ - **Coordinator Mode**: Orchestrator playbook for delegating work to background workers
64
67
  - **MCP Integration**: Model Context Protocol SDK for external tool servers
65
68
  - **Skills System**: Pluggable knowledge modules (git, PDF, Excel, etc.)
66
69
  - **Agent Coordination**: Spawn/wait/list subagents for parallel work
@@ -220,15 +223,26 @@ npm start
220
223
  │ │Sandbox │ │ToolExec │ │Diagnostics│ │SessionView │ │
221
224
  │ │Policy │ │Policy │ │ │ │ │ │
222
225
  │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
226
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
227
+ │ │Feature │ │Plan Mode │ │Tool Auto │ │Tool Permission│ │
228
+ │ │Flags │ │Session │ │Approve │ │Classifier │ │
229
+ │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
223
230
  └─────────────────────────────────────────────────────────────┘
224
231
 
225
232
  ┌────────────────────────┼────────────────────────────────────┐
226
233
  │ Tools Layer │
227
234
  │ ┌──────────────────────────────────────────────────────┐ │
228
- │ │ Native Tools (25+) │ │
235
+ │ │ Native Tools (40+) │ │
229
236
  │ │ edit_tool, file_write, shell_command, grep_search, │ │
230
- │ │ spawn_agent, todo, task_boundary, coding_memory, │ │
231
- │ │ search_web, web_fetch, load_skill, ... │ │
237
+ │ │ spawn_agent, wait_agent, list_agents, │ │
238
+ │ │ todo, task_boundary, task_create, task_list, │ │
239
+ │ │ task_get, task_update, task_stop, │ │
240
+ │ │ coding_memory, search_web, web_fetch, load_skill, │ │
241
+ │ │ message, ask_user_question, │ │
242
+ │ │ list_mcp_resources, read_mcp_resource, │ │
243
+ │ │ cron_create, cron_list, cron_delete, │ │
244
+ │ │ lsp_query, notebook_edit, │ │
245
+ │ │ enter_plan_mode, exit_plan_mode, ... │ │
232
246
  │ └──────────────────────────────────────────────────────┘ │
233
247
  │ ┌──────────────────────────────────────────────────────┐ │
234
248
  │ │ MCP SDK Integration │ │
@@ -241,26 +255,27 @@ npm start
241
255
 
242
256
  ## Native Tools
243
257
 
244
- BluMa includes 25+ built-in tools organized by category:
258
+ BluMa includes **40+ built-in tools** organized by category:
245
259
 
246
260
  ### File Operations
247
261
  | Tool | Description |
248
262
  |------|-------------|
249
- | `edit_tool` | Replace text in files (precise, multi-line) |
263
+ | `edit_tool` | Replace text in files (precise, multi-line, batch edits) |
250
264
  | `file_write` | Create/overwrite entire files |
251
- | `read_file_lines` | Read specific line ranges |
265
+ | `read_file_lines` | Read specific line ranges (up to 2000 lines) |
252
266
  | `count_file_lines` | Get file line count |
253
- | `ls_tool` | List directories with filtering |
267
+ | `ls_tool` | List directories with filtering and pagination |
254
268
  | `find_by_name` | Glob-based file search |
255
269
  | `grep_search` | Text/regex search across files |
256
- | `view_file_outline` | Show code structure (classes, functions) |
270
+ | `view_file_outline` | Show code structure (classes, functions, interfaces) |
271
+ | `notebook_edit` | Edit Jupyter `.ipynb` files (cells operations) |
257
272
 
258
273
  ### Shell & Commands
259
274
  | Tool | Description |
260
275
  |------|-------------|
261
276
  | `shell_command` | Execute background commands |
262
277
  | `command_status` | Check command progress/output |
263
- | `send_command_input` | Send input to running commands |
278
+ | `send_command_input` | Send input to running commands' stdin |
264
279
  | `kill_command` | Terminate running commands |
265
280
 
266
281
  ### Agent Coordination
@@ -275,6 +290,11 @@ BluMa includes 25+ built-in tools organized by category:
275
290
  |------|-------------|
276
291
  | `todo` | Manage task lists |
277
292
  | `task_boundary` | Track task phases (PLANNING/EXECUTION/VERIFICATION) |
293
+ | `task_create` | Create session-scoped tasks |
294
+ | `task_list` | List all session tasks |
295
+ | `task_get` | Get one task by id |
296
+ | `task_update` | Update task fields |
297
+ | `task_stop` | Cancel a task |
278
298
  | `create_artifact` | Save documents to `~/.bluma/artifacts/` |
279
299
  | `read_artifact` | Retrieve saved artifacts |
280
300
 
@@ -284,12 +304,37 @@ BluMa includes 25+ built-in tools organized by category:
284
304
  | `search_web` | Search programming solutions (Reddit, GitHub, StackOverflow) |
285
305
  | `web_fetch` | Fetch and analyze remote URLs |
286
306
  | `load_skill` | Activate domain-specific skills |
287
- | `coding_memory` | Persist/retrieve project notes |
307
+ | `coding_memory` | CRUD for persistent coding notes |
288
308
 
289
- ### Communication
309
+ ### Communication & Interaction
290
310
  | Tool | Description |
291
311
  |------|-------------|
292
312
  | `message` | Post user-visible chat (info/result types) |
313
+ | `ask_user_question` | Ask multiple-choice questions in terminal |
314
+
315
+ ### MCP & Resources
316
+ | Tool | Description |
317
+ |------|-------------|
318
+ | `list_mcp_resources` | List resources from MCP servers |
319
+ | `read_mcp_resource` | Read a resource URI from MCP server |
320
+
321
+ ### Scheduling
322
+ | Tool | Description |
323
+ |------|-------------|
324
+ | `cron_create` | Schedule one-shot or repeating reminders |
325
+ | `cron_list` | List scheduled cron jobs |
326
+ | `cron_delete` | Cancel scheduled job |
327
+
328
+ ### Development Tools
329
+ | Tool | Description |
330
+ |------|-------------|
331
+ | `lsp_query` | LSP go-to-definition or references (TS/JS) |
332
+
333
+ ### Plan Mode
334
+ | Tool | Description |
335
+ |------|-------------|
336
+ | `enter_plan_mode` | Enter plan-only mode (edits require confirmation) |
337
+ | `exit_plan_mode` | Leave plan mode |
293
338
 
294
339
  ---
295
340
 
@@ -461,6 +506,8 @@ Built-in terminal commands (type `/` to see all):
461
506
  | `/img ./shot.png [question]` | Send local image(s) to the model |
462
507
  | `/image` | Alias of /img |
463
508
  | `/init` | Run init subagent — BluMa.md codebase documentation |
509
+ | `/agent [default\|coordinator]` | Set prompt profile (coordinator playbook for worker orchestration) |
510
+ | `/agents` | List worker/agent sessions (spawn_agent children) |
464
511
 
465
512
  ### Inspect
466
513
  | Command | Description |
@@ -468,7 +515,7 @@ Built-in terminal commands (type `/` to see all):
468
515
  | `/plugins` | List installed plugins and plugin paths |
469
516
  | `/plugin <name>` | Inspect one plugin |
470
517
  | `/diagnostics` | Show a consolidated health snapshot |
471
- | `/permissions` | Inspect sandbox and tool execution rules |
518
+ | `/permissions` | Inspect sandbox and tool rules; set mode |
472
519
  | `/hooks` | Inspect, enable, disable, or clear lifecycle hooks |
473
520
  | `/model [list\|name\|auto]` | Show, list, or set the active model |
474
521
  | `/effort [low\|medium\|high]` | Show or set reasoning effort |
@@ -479,6 +526,7 @@ Built-in terminal commands (type `/` to see all):
479
526
  | `/skills` | List load_skill modules, dirs, and conflicts |
480
527
  | `/tools [grep]` | List native tools (optional filter) |
481
528
  | `/mcp [fs]` | List MCP tools (optional filter) |
529
+ | `/features` | Feature flags: `/features` or `/features <key> on\|off` |
482
530
 
483
531
  ### Help
484
532
  | Command | Description |
@@ -488,8 +536,8 @@ Built-in terminal commands (type `/` to see all):
488
536
  ### Input (Keyboard Shortcuts)
489
537
  | Shortcut | Description |
490
538
  |----------|-------------|
491
- | `Ctrl+V / Cmd+V` | Paste from clipboard: image → file path under ~/.cache/bluma/clipboard; else text |
492
- | `Ctrl+Shift+I` | Same as Ctrl+V / Cmd+V (paste image or text) |
539
+ | `Ctrl+V / Cmd+V` | Paste from clipboard: image → cache `~/.cache/bluma/clipboard`; text; or file path as image |
540
+ | `Ctrl+Shift+I` | Same as Ctrl+V (paste image, text, or file path) |
493
541
 
494
542
  ---
495
543
 
@@ -517,6 +565,12 @@ src/
517
565
  │ ├── agent/
518
566
  │ │ ├── agent.ts # Main orchestrator
519
567
  │ │ ├── bluma/ # Core agent logic
568
+ │ │ │ ├── core/
569
+ │ │ │ │ └── bluma.ts # BluMaAgent class
570
+ │ │ │ └── turn_start_payload.ts # Backend turn payload
571
+ │ │ ├── config/
572
+ │ │ │ ├── native_tools.json # Tool definitions (40+)
573
+ │ │ │ └── skills/ # Bundled skills (git-commit, git-pr, pdf, xlsx, skill-creator)
520
574
  │ │ ├── core/ # LLM, context, prompts
521
575
  │ │ │ ├── context-api/ # Context management
522
576
  │ │ │ │ ├── context_manager.ts # Token-aware context
@@ -528,41 +582,80 @@ src/
528
582
  │ │ │ └── prompt/ # Prompt engineering
529
583
  │ │ │ ├── prompt_builder.ts # Dynamic prompts
530
584
  │ │ │ └── workspace_snapshot.ts
585
+ │ │ ├── feedback/
586
+ │ │ │ └── feedback_system.ts # Smart feedback system
531
587
  │ │ ├── runtime/ # Orchestration layer (v0.1.41+)
532
588
  │ │ │ ├── diagnostics.ts # System snapshots
589
+ │ │ │ ├── feature_flags.ts # Feature gates
533
590
  │ │ │ ├── hook_registry.ts # Event-driven hooks
534
591
  │ │ │ ├── native_tool_catalog.ts # Tool registry
592
+ │ │ │ ├── plan_mode_session.ts # Plan mode state
535
593
  │ │ │ ├── plugin_registry.ts # Plugin system
594
+ │ │ │ ├── plugin_runtime.ts # Plugin execution
536
595
  │ │ │ ├── runtime_config.ts # Runtime settings
537
596
  │ │ │ ├── sandbox_policy.ts # Safety policies
538
597
  │ │ │ ├── session_registry.ts # Multi-session mgmt
539
598
  │ │ │ ├── session_view.ts # Session monitoring
540
599
  │ │ │ ├── task_store.ts # Task lifecycle
541
- │ │ │ └── tool_execution_policy.ts
542
- │ │ ├── tools/ # Tool layer
543
- │ │ │ └── natives/ # 18 native tools
600
+ │ │ │ ├── tool_auto_approve.ts # Auto-approve rules
601
+ │ │ ├── tool_execution_policy.ts
602
+ │ │ │ ├── tool_orchestration.ts # Parallel read eligibility
603
+ │ │ │ └── tool_permission_classifier.ts
604
+ │ │ ├── session_manager/
605
+ │ │ │ └── session_manager.ts # Session persistence
606
+ │ │ ├── skills/
607
+ │ │ │ └── skill_loader.ts # Skill loading
608
+ │ │ ├── subagents/ # Subagent system
609
+ │ │ │ ├── base_llm_subagent.ts
610
+ │ │ │ ├── init/ # Init subagent (BluMa.md)
611
+ │ │ │ ├── registry.ts
612
+ │ │ │ ├── subagents_bluma.ts
613
+ │ │ │ └── types.ts
614
+ │ │ ├── tools/
615
+ │ │ │ ├── mcp/
616
+ │ │ │ │ └── mcp_client.ts # MCP SDK client
617
+ │ │ │ └── natives/ # 27 native tool implementations
544
618
  │ │ │ ├── agent_coordination.ts
619
+ │ │ │ ├── ask_user_question.ts
545
620
  │ │ │ ├── async_command.ts
546
621
  │ │ │ ├── coding_memory.ts
622
+ │ │ │ ├── coding_memory_consolidate.ts
623
+ │ │ │ ├── coordinator_tools.ts
624
+ │ │ │ ├── count_lines.ts
547
625
  │ │ │ ├── edit.ts
548
626
  │ │ │ ├── file_write.ts
549
627
  │ │ │ ├── find_by_name.ts
550
628
  │ │ │ ├── grep_search.ts
551
629
  │ │ │ ├── load_skill.ts
552
630
  │ │ │ ├── ls.ts
631
+ │ │ │ ├── lsp_query.ts
632
+ │ │ │ ├── mcp_resources.ts
553
633
  │ │ │ ├── message.ts
634
+ │ │ │ ├── notebook_edit.ts
635
+ │ │ │ ├── plan_mode_tools.ts
554
636
  │ │ │ ├── readLines.ts
555
637
  │ │ │ ├── search_web.ts
638
+ │ │ │ ├── session_cron.ts
556
639
  │ │ │ ├── shell_command.ts
557
640
  │ │ │ ├── task_boundary.ts
641
+ │ │ │ ├── task_tools.ts
558
642
  │ │ │ ├── todo.ts
559
643
  │ │ │ ├── view_file_outline.ts
560
644
  │ │ │ └── web_fetch.ts
561
- │ │ └── types/ # TypeScript definitions
645
+ │ │ ├── types/
646
+ │ │ │ └── index.ts
647
+ │ │ └── utils/
648
+ │ │ ├── coordinator_prompt.ts # Coordinator mode playbook
649
+ │ │ ├── update_check.ts
650
+ │ │ └── user_message_images.ts
562
651
  │ └── ui/
563
652
  │ ├── App.tsx # Main UI component
564
- │ ├── components/ # 21 UI components
653
+ │ ├── Asci/
654
+ │ │ └── AsciiArt.ts
655
+ │ ├── components/ # 24+ UI components
565
656
  │ │ ├── AnimatedBorder.tsx
657
+ │ │ ├── AskUserQuestionPrompt.tsx
658
+ │ │ ├── AssistantMessageDisplay.tsx
566
659
  │ │ ├── CollapsibleResult.tsx
567
660
  │ │ ├── EditToolDiffPanel.tsx # Diff preview for edits
568
661
  │ │ ├── ErrorMessage.tsx
@@ -573,20 +666,44 @@ src/
573
666
  │ │ ├── ReasoningDisplay.tsx # LLM reasoning
574
667
  │ │ ├── SessionStats.tsx
575
668
  │ │ ├── SimpleDiff.tsx
576
- │ │ ├── SlashCommands.tsx # 20+ commands
669
+ │ │ ├── SlashCommands.tsx # 30+ commands
577
670
  │ │ ├── StatusNotification.tsx
578
671
  │ │ ├── StreamingText.tsx # Live text output
579
672
  │ │ ├── TodoPlanDisplay.tsx # Task visualization
580
673
  │ │ ├── ToolCallDisplay.tsx
674
+ │ │ ├── ToolInvocationBlock.tsx
581
675
  │ │ ├── ToolResultCard.tsx # Structured results
582
676
  │ │ ├── ToolResultDisplay.tsx
583
677
  │ │ ├── TypewriterText.tsx
584
678
  │ │ ├── UpdateNotice.tsx
679
+ │ │ ├── streamingTextFlush.ts
585
680
  │ │ └── toolCallRenderers.tsx
586
- │ ├── theme/ # Terminal theming
587
- └── utils/ # UI utilities
681
+ │ ├── constants/
682
+ │ ├── historyLayout.ts
683
+ │ │ ├── inputPaste.ts
684
+ │ │ └── toolUiPreview.ts
685
+ │ ├── hooks/
686
+ │ │ └── useAtCompletion.ts
687
+ │ ├── theme/
688
+ │ │ ├── blumaTerminal.ts
689
+ │ │ └── m3Layout.tsx
690
+ │ └── utils/
691
+ │ ├── clipboardImage.ts
692
+ │ ├── editToolDiffUtils.ts
693
+ │ ├── expandPreviewHotkey.ts
694
+ │ ├── expandablePreviewStore.ts
695
+ │ ├── formatTurnDurationMs.ts
696
+ │ ├── inlineImageInputLabels.ts
697
+ │ ├── pathDisplay.ts
698
+ │ ├── shellToolNames.ts
699
+ │ ├── slashRegistry.ts
700
+ │ ├── terminalTitle.ts
701
+ │ ├── toolDisplayLabels.ts
702
+ │ ├── toolInvocationPairing.ts
703
+ │ └── useSimpleInputBuffer.ts
588
704
  ├── main.ts # Entry point
589
- └── types/ # Global types
705
+ └── types/
706
+ └── semver-functions.d.ts
590
707
  ```
591
708
 
592
709
  ---
@@ -679,6 +796,12 @@ BluMa's runtime layer provides enterprise-grade orchestration:
679
796
  | `session_view.ts` | Session monitoring | Log streaming, status display |
680
797
  | `native_tool_catalog.ts` | Tool registry | Discovery and metadata |
681
798
  | `runtime_config.ts` | Settings | Runtime configuration management |
799
+ | `feature_flags.ts` | Feature gates | Opt-in features via env or settings |
800
+ | `plan_mode_session.ts` | Plan mode | Forces confirmation for edits/writes |
801
+ | `tool_auto_approve.ts` | Auto-approve | Effective approve rules |
802
+ | `tool_orchestration.ts` | Parallel reads | Eligibility for parallel execution |
803
+ | `tool_permission_classifier` | Tool classification | Classify tool invocations |
804
+ | `plugin_runtime.ts` | Plugin execution | Plugin runtime context |
682
805
 
683
806
  ### UI Components
684
807
 
@@ -696,6 +819,9 @@ Key UI components that power the rich terminal experience:
696
819
  | `AnimatedBorder.tsx` | Visual feedback for active elements |
697
820
  | `CollapsibleResult.tsx` | Expandable result sections |
698
821
  | `ProgressBar.tsx` | Progress indicators |
822
+ | `AskUserQuestionPrompt.tsx` | Multiple-choice question UI |
823
+ | `ToolInvocationBlock.tsx` | Tool call visualization |
824
+ | `AssistantMessageDisplay.tsx` | Assistant message formatting |
699
825
 
700
826
  ---
701
827
 
package/dist/main.js CHANGED
@@ -10234,9 +10234,9 @@ var BluMaAgent = class {
10234
10234
  this.mcpClient = mcpClient;
10235
10235
  this.feedbackSystem = feedbackSystem;
10236
10236
  this.skillLoader = new SkillLoader(process.cwd());
10237
- this.eventBus.on("user_interrupt", () => {
10237
+ this.eventBus.on("user_interrupt", async () => {
10238
10238
  this.isInterrupted = true;
10239
- void this.notifyFactorTurnEndIfNeeded("user_interrupt");
10239
+ await this.notifyFactorTurnEndIfNeeded("user_interrupt");
10240
10240
  this.eventBus.emit("backend_message", { type: "done", status: "interrupted" });
10241
10241
  });
10242
10242
  this.eventBus.on("user_overlay", async (data) => {
@@ -10475,9 +10475,8 @@ var BluMaAgent = class {
10475
10475
  }
10476
10476
  }
10477
10477
  }
10478
- const skipEditDiffRepeat = toolName === "edit_tool" && decisionData.editPreviewAlreadyShown === true;
10479
10478
  let previewContent;
10480
- if (toolName === "edit_tool" && !skipEditDiffRepeat) {
10479
+ if (toolName === "edit_tool") {
10481
10480
  try {
10482
10481
  previewContent = await this._generateEditPreview(toolArgs);
10483
10482
  } catch (previewError) {
@@ -10490,7 +10489,7 @@ var BluMaAgent = class {
10490
10489
  tool_name: toolName,
10491
10490
  arguments: toolArgs,
10492
10491
  preview: previewContent,
10493
- suppress_edit_diff_preview: skipEditDiffRepeat,
10492
+ suppress_edit_diff_preview: false,
10494
10493
  tool_policy: this.buildToolPolicyForUi(toolName, toolArgs)
10495
10494
  });
10496
10495
  try {
@@ -10553,7 +10552,7 @@ var BluMaAgent = class {
10553
10552
  tool_name: toolName,
10554
10553
  arguments: toolArgs,
10555
10554
  result: toolResultContent,
10556
- suppress_edit_diff: skipEditDiffRepeat
10555
+ suppress_edit_diff: false
10557
10556
  });
10558
10557
  if (toolName === "message") {
10559
10558
  try {
@@ -10782,6 +10781,10 @@ ${editData.error.display}`;
10782
10781
  reason
10783
10782
  });
10784
10783
  }
10784
+ /** Fecho explícito de turno (útil para workers antes de process.exit). */
10785
+ async closeActiveTurn(reason = "worker_exit") {
10786
+ await this.notifyFactorTurnEndIfNeeded(reason);
10787
+ }
10785
10788
  async _continueConversation() {
10786
10789
  try {
10787
10790
  if (this.isInterrupted) {
@@ -10807,7 +10810,7 @@ ${editData.error.display}`;
10807
10810
  } catch (error) {
10808
10811
  const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
10809
10812
  this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
10810
- void this.notifyFactorTurnEndIfNeeded("llm_error");
10813
+ await this.notifyFactorTurnEndIfNeeded("llm_error");
10811
10814
  this.eventBus.emit("backend_message", { type: "done", status: "failed" });
10812
10815
  } finally {
10813
10816
  this.persistSession();
@@ -11721,6 +11724,10 @@ var Agent = class {
11721
11724
  }
11722
11725
  await this.routeManager.handleRoute({ content: inputText, userContext: resolvedUserContext });
11723
11726
  }
11727
+ /** Fecha o turno ativo no FactorRouter (idempotente). */
11728
+ async closeActiveTurn(reason = "worker_exit") {
11729
+ await this.core.closeActiveTurn(reason);
11730
+ }
11724
11731
  /**
11725
11732
  * Handler para o comando /coordinator
11726
11733
  * /coordinator enable - Ativa o Coordinator Mode
@@ -14607,6 +14614,14 @@ var AskUserQuestionPrompt = memo14(AskUserQuestionPromptComponent);
14607
14614
  import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
14608
14615
  var MAX_STATIC_HISTORY_ITEMS = 220;
14609
14616
  var blumaUpdateRegistryCheckStarted = false;
14617
+ function nextHistoryId(items) {
14618
+ if (items.length === 0) return HEADER_PANEL_HISTORY_ID + 1;
14619
+ let maxId = HEADER_PANEL_HISTORY_ID;
14620
+ for (const it of items) {
14621
+ if (typeof it.id === "number" && it.id > maxId) maxId = it.id;
14622
+ }
14623
+ return maxId + 1;
14624
+ }
14610
14625
  function trimRecentActivity(s, max = 72) {
14611
14626
  const t = String(s ?? "").replace(/\s+/g, " ").trim();
14612
14627
  if (!t) return "";
@@ -14740,13 +14755,16 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14740
14755
  turnStartedAtRef.current = null;
14741
14756
  setProcessingStartMs(null);
14742
14757
  setIsProcessing(false);
14743
- setHistory((prev) => [
14744
- ...prev,
14745
- {
14746
- id: prev.length,
14747
- component: /* @__PURE__ */ jsx24(ChatMeta, { children: "cancelled (Esc)" })
14748
- }
14749
- ]);
14758
+ setHistory((prev) => {
14759
+ const id = nextHistoryId(prev);
14760
+ return [
14761
+ ...prev,
14762
+ {
14763
+ id,
14764
+ component: /* @__PURE__ */ jsx24(ChatMeta, { children: "cancelled (Esc)" })
14765
+ }
14766
+ ];
14767
+ });
14750
14768
  }, [isProcessing, eventBus]);
14751
14769
  const handleSubmit = useCallback3(
14752
14770
  (text) => {
@@ -14754,13 +14772,16 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14754
14772
  const trimmedForSlash = text.trim();
14755
14773
  if (isProcessing && !isSlashRoutingLine(trimmedForSlash)) {
14756
14774
  if (trimmedForSlash.startsWith("/")) {
14757
- setHistory((prev) => [
14758
- ...prev,
14759
- {
14760
- id: prev.length,
14761
- component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Slash command not recognized or incomplete. Type /help for the list." })
14762
- }
14763
- ]);
14775
+ setHistory((prev) => {
14776
+ const id = nextHistoryId(prev);
14777
+ return [
14778
+ ...prev,
14779
+ {
14780
+ id,
14781
+ component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Slash command not recognized or incomplete. Type /help for the list." })
14782
+ }
14783
+ ];
14784
+ });
14764
14785
  }
14765
14786
  return;
14766
14787
  }
@@ -14769,13 +14790,16 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14769
14790
  if (/^\/img\s+/i.test(text) || /^\/image\s+/i.test(text)) {
14770
14791
  const payload = text.replace(/^\/img\s+/i, "").replace(/^\/image\s+/i, "").trim();
14771
14792
  if (!payload) {
14772
- setHistory((prev) => [
14773
- ...prev,
14774
- {
14775
- id: prev.length,
14776
- component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Usage: /img ./screenshot.png \u2014 optional text after the path is sent too" })
14777
- }
14778
- ]);
14793
+ setHistory((prev) => {
14794
+ const id = nextHistoryId(prev);
14795
+ return [
14796
+ ...prev,
14797
+ {
14798
+ id,
14799
+ component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Usage: /img ./screenshot.png \u2014 optional text after the path is sent too" })
14800
+ }
14801
+ ];
14802
+ });
14779
14803
  return;
14780
14804
  }
14781
14805
  setIsProcessing(true);
@@ -14785,13 +14809,16 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14785
14809
  sessionId,
14786
14810
  summary: trimRecentActivity(payload, 120)
14787
14811
  });
14788
- setHistory((prev) => [
14789
- ...prev,
14790
- {
14791
- id: prev.length,
14792
- component: /* @__PURE__ */ jsx24(UserMessageWithOptionalImages, { raw: payload, variant: "slash-img" })
14793
- }
14794
- ]);
14812
+ setHistory((prev) => {
14813
+ const id = nextHistoryId(prev);
14814
+ return [
14815
+ ...prev,
14816
+ {
14817
+ id,
14818
+ component: /* @__PURE__ */ jsx24(UserMessageWithOptionalImages, { raw: payload, variant: "slash-img" })
14819
+ }
14820
+ ];
14821
+ });
14795
14822
  agentInstance.current.processTurn({ content: payload });
14796
14823
  return;
14797
14824
  }
@@ -14814,24 +14841,28 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14814
14841
  setIsProcessing(false);
14815
14842
  setIsInitAgentActive(false);
14816
14843
  }
14817
- setHistory((prev) => [
14818
- ...prev,
14819
- {
14820
- id: prev.length,
14821
- component: /* @__PURE__ */ jsx24(ChatUserMessage, { children: /* @__PURE__ */ jsx24(Text22, { color: BLUMA_TERMINAL.m3OnSurface, bold: true, wrap: "wrap", children: text }) })
14822
- },
14823
- {
14824
- id: prev.length + 1,
14825
- component: /* @__PURE__ */ jsx24(
14826
- SlashCommands_default,
14827
- {
14828
- input: text,
14829
- setHistory,
14830
- agentRef: agentInstance
14831
- }
14832
- )
14833
- }
14834
- ]);
14844
+ setHistory((prev) => {
14845
+ const firstId = nextHistoryId(prev);
14846
+ const secondId = firstId + 1;
14847
+ return [
14848
+ ...prev,
14849
+ {
14850
+ id: firstId,
14851
+ component: /* @__PURE__ */ jsx24(ChatUserMessage, { children: /* @__PURE__ */ jsx24(Text22, { color: BLUMA_TERMINAL.m3OnSurface, bold: true, wrap: "wrap", children: text }) })
14852
+ },
14853
+ {
14854
+ id: secondId,
14855
+ component: /* @__PURE__ */ jsx24(
14856
+ SlashCommands_default,
14857
+ {
14858
+ input: text,
14859
+ setHistory,
14860
+ agentRef: agentInstance
14861
+ }
14862
+ )
14863
+ }
14864
+ ];
14865
+ });
14835
14866
  return;
14836
14867
  }
14837
14868
  if (text.startsWith("!")) {
@@ -14847,16 +14878,19 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14847
14878
  sessionId,
14848
14879
  summary: trimRecentActivity(command, 120)
14849
14880
  });
14850
- setHistory((prev) => [
14851
- ...prev,
14852
- {
14853
- id: prev.length,
14854
- component: /* @__PURE__ */ jsx24(ChatUserMessage, { children: /* @__PURE__ */ jsxs22(Text22, { bold: true, color: "white", children: [
14855
- "$ !",
14856
- command
14857
- ] }) })
14858
- }
14859
- ]);
14881
+ setHistory((prev) => {
14882
+ const id = nextHistoryId(prev);
14883
+ return [
14884
+ ...prev,
14885
+ {
14886
+ id,
14887
+ component: /* @__PURE__ */ jsx24(ChatUserMessage, { children: /* @__PURE__ */ jsxs22(Text22, { bold: true, color: "white", children: [
14888
+ "$ !",
14889
+ command
14890
+ ] }) })
14891
+ }
14892
+ ];
14893
+ });
14860
14894
  Promise.resolve().then(() => (init_async_command(), async_command_exports)).then(async ({ runCommandAsync: runCommandAsync2 }) => {
14861
14895
  try {
14862
14896
  const result = await runCommandAsync2({ command, cwd: workdir });
@@ -14870,31 +14904,37 @@ Please use command_status to check the result and report back to the user.`;
14870
14904
  } else {
14871
14905
  turnStartedAtRef.current = null;
14872
14906
  setProcessingStartMs(null);
14873
- setHistory((prev) => [
14874
- ...prev,
14875
- {
14876
- id: prev.length,
14877
- component: /* @__PURE__ */ jsxs22(Text22, { color: "red", children: [
14878
- "Failed to execute: ",
14879
- result.error || result.message
14880
- ] })
14881
- }
14882
- ]);
14907
+ setHistory((prev) => {
14908
+ const id = nextHistoryId(prev);
14909
+ return [
14910
+ ...prev,
14911
+ {
14912
+ id,
14913
+ component: /* @__PURE__ */ jsxs22(Text22, { color: "red", children: [
14914
+ "Failed to execute: ",
14915
+ result.error || result.message
14916
+ ] })
14917
+ }
14918
+ ];
14919
+ });
14883
14920
  setIsProcessing(false);
14884
14921
  }
14885
14922
  } catch (err) {
14886
14923
  turnStartedAtRef.current = null;
14887
14924
  setProcessingStartMs(null);
14888
- setHistory((prev) => [
14889
- ...prev,
14890
- {
14891
- id: prev.length,
14892
- component: /* @__PURE__ */ jsxs22(Text22, { color: "red", children: [
14893
- "Error: ",
14894
- err.message
14895
- ] })
14896
- }
14897
- ]);
14925
+ setHistory((prev) => {
14926
+ const id = nextHistoryId(prev);
14927
+ return [
14928
+ ...prev,
14929
+ {
14930
+ id,
14931
+ component: /* @__PURE__ */ jsxs22(Text22, { color: "red", children: [
14932
+ "Error: ",
14933
+ err.message
14934
+ ] })
14935
+ }
14936
+ ];
14937
+ });
14898
14938
  setIsProcessing(false);
14899
14939
  }
14900
14940
  });
@@ -14902,13 +14942,16 @@ Please use command_status to check the result and report back to the user.`;
14902
14942
  }
14903
14943
  setIsProcessing(true);
14904
14944
  markTurnStarted();
14905
- setHistory((prev) => [
14906
- ...prev,
14907
- {
14908
- id: prev.length,
14909
- component: /* @__PURE__ */ jsx24(UserMessageWithOptionalImages, { raw: text, variant: "plain" })
14910
- }
14911
- ]);
14945
+ setHistory((prev) => {
14946
+ const id = nextHistoryId(prev);
14947
+ return [
14948
+ ...prev,
14949
+ {
14950
+ id,
14951
+ component: /* @__PURE__ */ jsx24(UserMessageWithOptionalImages, { raw: text, variant: "plain" })
14952
+ }
14953
+ ];
14954
+ });
14912
14955
  agentInstance.current.processTurn({ content: text });
14913
14956
  },
14914
14957
  [isProcessing, markTurnStarted]
@@ -14940,26 +14983,32 @@ Please use command_status to check the result and report back to the user.`;
14940
14983
  const key = reasoningDedupeKey(r);
14941
14984
  if (!r || key === lastReasoningTextRef.current) return;
14942
14985
  lastReasoningTextRef.current = key;
14943
- setHistory((prev) => [
14944
- ...prev,
14945
- {
14946
- id: prev.length,
14947
- component: /* @__PURE__ */ jsx24(ReasoningDisplay, { reasoning })
14948
- }
14949
- ]);
14986
+ setHistory((prev) => {
14987
+ const id = nextHistoryId(prev);
14988
+ return [
14989
+ ...prev,
14990
+ {
14991
+ id,
14992
+ component: /* @__PURE__ */ jsx24(ReasoningDisplay, { reasoning })
14993
+ }
14994
+ ];
14995
+ });
14950
14996
  }, []);
14951
14997
  const appendStreamedAssistant = useCallback3((content) => {
14952
14998
  const t = String(content ?? "").trim();
14953
14999
  if (!t) return;
14954
15000
  const key = reasoningDedupeKey(t);
14955
15001
  lastStreamAssistantKeyRef.current = key;
14956
- setHistory((prev) => [
14957
- ...prev,
14958
- {
14959
- id: prev.length,
14960
- component: /* @__PURE__ */ jsx24(AssistantMessageDisplay, { content })
14961
- }
14962
- ]);
15002
+ setHistory((prev) => {
15003
+ const id = nextHistoryId(prev);
15004
+ return [
15005
+ ...prev,
15006
+ {
15007
+ id,
15008
+ component: /* @__PURE__ */ jsx24(AssistantMessageDisplay, { content })
15009
+ }
15010
+ ];
15011
+ });
14963
15012
  }, []);
14964
15013
  const cappedHistory = useMemo2(() => {
14965
15014
  if (history.length <= MAX_STATIC_HISTORY_ITEMS) return history;
@@ -14999,13 +15048,16 @@ Please use command_status to check the result and report back to the user.`;
14999
15048
  turnStartedAtRef.current = null;
15000
15049
  setProcessingStartMs(null);
15001
15050
  const ms = Date.now() - t;
15002
- setHistory((prev) => [
15003
- ...prev,
15004
- {
15005
- id: prev.length,
15006
- component: /* @__PURE__ */ jsx24(ChatTurnDuration, { durationMs: ms })
15007
- }
15008
- ]);
15051
+ setHistory((prev) => {
15052
+ const id = nextHistoryId(prev);
15053
+ return [
15054
+ ...prev,
15055
+ {
15056
+ id,
15057
+ component: /* @__PURE__ */ jsx24(ChatTurnDuration, { durationMs: ms })
15058
+ }
15059
+ ];
15060
+ });
15009
15061
  };
15010
15062
  if (parsed.type === "done" || parsed.type === "error") {
15011
15063
  setIsInitAgentActive(false);
@@ -15179,9 +15231,10 @@ Please use command_status to check the result and report back to the user.`;
15179
15231
  }
15180
15232
  if (newComponent) {
15181
15233
  setHistory((prev) => {
15234
+ const id = nextHistoryId(prev);
15182
15235
  const next = [
15183
15236
  ...prev,
15184
- { id: prev.length, component: newComponent }
15237
+ { id, component: newComponent }
15185
15238
  ];
15186
15239
  if (parsed.type === "error") {
15187
15240
  const t = turnStartedAtRef.current;
@@ -15190,7 +15243,7 @@ Please use command_status to check the result and report back to the user.`;
15190
15243
  setProcessingStartMs(null);
15191
15244
  const ms = Date.now() - t;
15192
15245
  next.push({
15193
- id: next.length,
15246
+ id: nextHistoryId(next),
15194
15247
  component: /* @__PURE__ */ jsx24(ChatTurnDuration, { durationMs: ms })
15195
15248
  });
15196
15249
  }
@@ -15207,10 +15260,10 @@ Please use command_status to check the result and report back to the user.`;
15207
15260
  const handleInputNotice = (data) => {
15208
15261
  const msg = String(data.message || "").trim();
15209
15262
  if (!msg) return;
15210
- setHistory((prev) => [
15211
- ...prev,
15212
- { id: prev.length, component: /* @__PURE__ */ jsx24(ChatMeta, { children: msg }) }
15213
- ]);
15263
+ setHistory((prev) => {
15264
+ const id = nextHistoryId(prev);
15265
+ return [...prev, { id, component: /* @__PURE__ */ jsx24(ChatMeta, { children: msg }) }];
15266
+ });
15214
15267
  };
15215
15268
  uiEventBus.on("user_overlay", handleUiOverlay);
15216
15269
  uiEventBus.on("input_notice", handleInputNotice);
@@ -15255,11 +15308,9 @@ Please use command_status to check the result and report back to the user.`;
15255
15308
  toolCalls: pendingConfirmation,
15256
15309
  preview: confirmationPreview,
15257
15310
  onDecision: (decision) => {
15258
- const hadPreview = confirmationPreview != null && String(confirmationPreview).trim().length > 0;
15259
- const tname = pendingConfirmation?.[0]?.function?.name ?? "";
15260
15311
  setConfirmationPreview(null);
15261
15312
  handleConfirmation(decision, pendingConfirmation, {
15262
- editPreviewAlreadyShown: (decision === "accept" || decision === "accept_always") && tname === "edit_tool" && hadPreview
15313
+ editPreviewAlreadyShown: false
15263
15314
  });
15264
15315
  }
15265
15316
  }
@@ -15494,7 +15545,8 @@ async function runAgentMode() {
15494
15545
  let reasoningBuffer = null;
15495
15546
  let lastAttachments = null;
15496
15547
  let resultEmitted = false;
15497
- eventBus.on("backend_message", (payload) => {
15548
+ let agentRef = null;
15549
+ eventBus.on("backend_message", async (payload) => {
15498
15550
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
15499
15551
  writeAgentEvent(sessionId, {
15500
15552
  event_type: "backend_message",
@@ -15527,6 +15579,9 @@ async function runAgentMode() {
15527
15579
  }
15528
15580
  if (!resultEmitted && payload?.type === "done") {
15529
15581
  resultEmitted = true;
15582
+ if (agentRef) {
15583
+ await agentRef.closeActiveTurn("worker_done_exit");
15584
+ }
15530
15585
  finalizeSession(sessionId, "completed", { finishedBy: "done-event" });
15531
15586
  writeAgentEvent(sessionId, {
15532
15587
  event_type: "result",
@@ -15567,6 +15622,7 @@ async function runAgentMode() {
15567
15622
  });
15568
15623
  try {
15569
15624
  const agent = new Agent(sessionId, eventBus);
15625
+ agentRef = agent;
15570
15626
  await agent.initialize();
15571
15627
  const userContent = JSON.stringify({
15572
15628
  message_id: envelope.message_id || sessionId,
@@ -15578,6 +15634,7 @@ async function runAgentMode() {
15578
15634
  await agent.processTurn({ content: userContent }, userContextInput);
15579
15635
  if (!resultEmitted) {
15580
15636
  resultEmitted = true;
15637
+ await agent.closeActiveTurn("worker_post_turn_fallback");
15581
15638
  finalizeSession(sessionId, "completed", { finishedBy: "post-turn-fallback" });
15582
15639
  writeAgentEvent(sessionId, {
15583
15640
  event_type: "result",
@@ -15595,6 +15652,9 @@ async function runAgentMode() {
15595
15652
  }
15596
15653
  } catch (err) {
15597
15654
  if (!resultEmitted) {
15655
+ if (agentRef) {
15656
+ await agentRef.closeActiveTurn("worker_exception");
15657
+ }
15598
15658
  finalizeSession(sessionId, "error", { finishedBy: "exception" });
15599
15659
  writeAgentEvent(sessionId, {
15600
15660
  event_type: "result",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",