@nomad-e/bluma-cli 0.1.48 → 0.1.50

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 +148 -25
  2. package/dist/main.js +189 -115
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -36,8 +36,8 @@ BluMa operates as a **conversational agent** in the terminal, combining:
36
36
 
37
37
  - **Rich UI Layer**: React/Ink 5 components for interactive prompts, live overlays, and real-time feedback
38
38
  - **Agent Layer**: LLM orchestration via FactorRouter with tool invocation and context management
39
- - **Runtime Layer**: Task tracking, plugin system, hooks, diagnostics, and session management
40
- - **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
41
41
 
42
42
  The agent maintains persistent conversation history, workspace snapshots, and coding memory across sessions.
43
43
 
@@ -223,15 +223,26 @@ npm start
223
223
  │ │Sandbox │ │ToolExec │ │Diagnostics│ │SessionView │ │
224
224
  │ │Policy │ │Policy │ │ │ │ │ │
225
225
  │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
226
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
227
+ │ │Feature │ │Plan Mode │ │Tool Auto │ │Tool Permission│ │
228
+ │ │Flags │ │Session │ │Approve │ │Classifier │ │
229
+ │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
226
230
  └─────────────────────────────────────────────────────────────┘
227
231
 
228
232
  ┌────────────────────────┼────────────────────────────────────┐
229
233
  │ Tools Layer │
230
234
  │ ┌──────────────────────────────────────────────────────┐ │
231
- │ │ Native Tools (25+) │ │
235
+ │ │ Native Tools (40+) │ │
232
236
  │ │ edit_tool, file_write, shell_command, grep_search, │ │
233
- │ │ spawn_agent, todo, task_boundary, coding_memory, │ │
234
- │ │ 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, ... │ │
235
246
  │ └──────────────────────────────────────────────────────┘ │
236
247
  │ ┌──────────────────────────────────────────────────────┐ │
237
248
  │ │ MCP SDK Integration │ │
@@ -244,26 +255,27 @@ npm start
244
255
 
245
256
  ## Native Tools
246
257
 
247
- BluMa includes 25+ built-in tools organized by category:
258
+ BluMa includes **40+ built-in tools** organized by category:
248
259
 
249
260
  ### File Operations
250
261
  | Tool | Description |
251
262
  |------|-------------|
252
- | `edit_tool` | Replace text in files (precise, multi-line) |
263
+ | `edit_tool` | Replace text in files (precise, multi-line, batch edits) |
253
264
  | `file_write` | Create/overwrite entire files |
254
- | `read_file_lines` | Read specific line ranges |
265
+ | `read_file_lines` | Read specific line ranges (up to 2000 lines) |
255
266
  | `count_file_lines` | Get file line count |
256
- | `ls_tool` | List directories with filtering |
267
+ | `ls_tool` | List directories with filtering and pagination |
257
268
  | `find_by_name` | Glob-based file search |
258
269
  | `grep_search` | Text/regex search across files |
259
- | `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) |
260
272
 
261
273
  ### Shell & Commands
262
274
  | Tool | Description |
263
275
  |------|-------------|
264
276
  | `shell_command` | Execute background commands |
265
277
  | `command_status` | Check command progress/output |
266
- | `send_command_input` | Send input to running commands |
278
+ | `send_command_input` | Send input to running commands' stdin |
267
279
  | `kill_command` | Terminate running commands |
268
280
 
269
281
  ### Agent Coordination
@@ -278,6 +290,11 @@ BluMa includes 25+ built-in tools organized by category:
278
290
  |------|-------------|
279
291
  | `todo` | Manage task lists |
280
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 |
281
298
  | `create_artifact` | Save documents to `~/.bluma/artifacts/` |
282
299
  | `read_artifact` | Retrieve saved artifacts |
283
300
 
@@ -287,12 +304,37 @@ BluMa includes 25+ built-in tools organized by category:
287
304
  | `search_web` | Search programming solutions (Reddit, GitHub, StackOverflow) |
288
305
  | `web_fetch` | Fetch and analyze remote URLs |
289
306
  | `load_skill` | Activate domain-specific skills |
290
- | `coding_memory` | Persist/retrieve project notes |
307
+ | `coding_memory` | CRUD for persistent coding notes |
291
308
 
292
- ### Communication
309
+ ### Communication & Interaction
293
310
  | Tool | Description |
294
311
  |------|-------------|
295
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 |
296
338
 
297
339
  ---
298
340
 
@@ -464,6 +506,8 @@ Built-in terminal commands (type `/` to see all):
464
506
  | `/img ./shot.png [question]` | Send local image(s) to the model |
465
507
  | `/image` | Alias of /img |
466
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) |
467
511
 
468
512
  ### Inspect
469
513
  | Command | Description |
@@ -471,7 +515,7 @@ Built-in terminal commands (type `/` to see all):
471
515
  | `/plugins` | List installed plugins and plugin paths |
472
516
  | `/plugin <name>` | Inspect one plugin |
473
517
  | `/diagnostics` | Show a consolidated health snapshot |
474
- | `/permissions` | Inspect sandbox and tool execution rules |
518
+ | `/permissions` | Inspect sandbox and tool rules; set mode |
475
519
  | `/hooks` | Inspect, enable, disable, or clear lifecycle hooks |
476
520
  | `/model [list\|name\|auto]` | Show, list, or set the active model |
477
521
  | `/effort [low\|medium\|high]` | Show or set reasoning effort |
@@ -482,6 +526,7 @@ Built-in terminal commands (type `/` to see all):
482
526
  | `/skills` | List load_skill modules, dirs, and conflicts |
483
527
  | `/tools [grep]` | List native tools (optional filter) |
484
528
  | `/mcp [fs]` | List MCP tools (optional filter) |
529
+ | `/features` | Feature flags: `/features` or `/features <key> on\|off` |
485
530
 
486
531
  ### Help
487
532
  | Command | Description |
@@ -491,8 +536,8 @@ Built-in terminal commands (type `/` to see all):
491
536
  ### Input (Keyboard Shortcuts)
492
537
  | Shortcut | Description |
493
538
  |----------|-------------|
494
- | `Ctrl+V / Cmd+V` | Paste from clipboard: image → file path under ~/.cache/bluma/clipboard; else text |
495
- | `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) |
496
541
 
497
542
  ---
498
543
 
@@ -520,6 +565,12 @@ src/
520
565
  │ ├── agent/
521
566
  │ │ ├── agent.ts # Main orchestrator
522
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)
523
574
  │ │ ├── core/ # LLM, context, prompts
524
575
  │ │ │ ├── context-api/ # Context management
525
576
  │ │ │ │ ├── context_manager.ts # Token-aware context
@@ -531,41 +582,80 @@ src/
531
582
  │ │ │ └── prompt/ # Prompt engineering
532
583
  │ │ │ ├── prompt_builder.ts # Dynamic prompts
533
584
  │ │ │ └── workspace_snapshot.ts
585
+ │ │ ├── feedback/
586
+ │ │ │ └── feedback_system.ts # Smart feedback system
534
587
  │ │ ├── runtime/ # Orchestration layer (v0.1.41+)
535
588
  │ │ │ ├── diagnostics.ts # System snapshots
589
+ │ │ │ ├── feature_flags.ts # Feature gates
536
590
  │ │ │ ├── hook_registry.ts # Event-driven hooks
537
591
  │ │ │ ├── native_tool_catalog.ts # Tool registry
592
+ │ │ │ ├── plan_mode_session.ts # Plan mode state
538
593
  │ │ │ ├── plugin_registry.ts # Plugin system
594
+ │ │ │ ├── plugin_runtime.ts # Plugin execution
539
595
  │ │ │ ├── runtime_config.ts # Runtime settings
540
596
  │ │ │ ├── sandbox_policy.ts # Safety policies
541
597
  │ │ │ ├── session_registry.ts # Multi-session mgmt
542
598
  │ │ │ ├── session_view.ts # Session monitoring
543
599
  │ │ │ ├── task_store.ts # Task lifecycle
544
- │ │ │ └── tool_execution_policy.ts
545
- │ │ ├── tools/ # Tool layer
546
- │ │ │ └── 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
547
618
  │ │ │ ├── agent_coordination.ts
619
+ │ │ │ ├── ask_user_question.ts
548
620
  │ │ │ ├── async_command.ts
549
621
  │ │ │ ├── coding_memory.ts
622
+ │ │ │ ├── coding_memory_consolidate.ts
623
+ │ │ │ ├── coordinator_tools.ts
624
+ │ │ │ ├── count_lines.ts
550
625
  │ │ │ ├── edit.ts
551
626
  │ │ │ ├── file_write.ts
552
627
  │ │ │ ├── find_by_name.ts
553
628
  │ │ │ ├── grep_search.ts
554
629
  │ │ │ ├── load_skill.ts
555
630
  │ │ │ ├── ls.ts
631
+ │ │ │ ├── lsp_query.ts
632
+ │ │ │ ├── mcp_resources.ts
556
633
  │ │ │ ├── message.ts
634
+ │ │ │ ├── notebook_edit.ts
635
+ │ │ │ ├── plan_mode_tools.ts
557
636
  │ │ │ ├── readLines.ts
558
637
  │ │ │ ├── search_web.ts
638
+ │ │ │ ├── session_cron.ts
559
639
  │ │ │ ├── shell_command.ts
560
640
  │ │ │ ├── task_boundary.ts
641
+ │ │ │ ├── task_tools.ts
561
642
  │ │ │ ├── todo.ts
562
643
  │ │ │ ├── view_file_outline.ts
563
644
  │ │ │ └── web_fetch.ts
564
- │ │ └── 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
565
651
  │ └── ui/
566
652
  │ ├── App.tsx # Main UI component
567
- │ ├── components/ # 21 UI components
653
+ │ ├── Asci/
654
+ │ │ └── AsciiArt.ts
655
+ │ ├── components/ # 24+ UI components
568
656
  │ │ ├── AnimatedBorder.tsx
657
+ │ │ ├── AskUserQuestionPrompt.tsx
658
+ │ │ ├── AssistantMessageDisplay.tsx
569
659
  │ │ ├── CollapsibleResult.tsx
570
660
  │ │ ├── EditToolDiffPanel.tsx # Diff preview for edits
571
661
  │ │ ├── ErrorMessage.tsx
@@ -576,20 +666,44 @@ src/
576
666
  │ │ ├── ReasoningDisplay.tsx # LLM reasoning
577
667
  │ │ ├── SessionStats.tsx
578
668
  │ │ ├── SimpleDiff.tsx
579
- │ │ ├── SlashCommands.tsx # 20+ commands
669
+ │ │ ├── SlashCommands.tsx # 30+ commands
580
670
  │ │ ├── StatusNotification.tsx
581
671
  │ │ ├── StreamingText.tsx # Live text output
582
672
  │ │ ├── TodoPlanDisplay.tsx # Task visualization
583
673
  │ │ ├── ToolCallDisplay.tsx
674
+ │ │ ├── ToolInvocationBlock.tsx
584
675
  │ │ ├── ToolResultCard.tsx # Structured results
585
676
  │ │ ├── ToolResultDisplay.tsx
586
677
  │ │ ├── TypewriterText.tsx
587
678
  │ │ ├── UpdateNotice.tsx
679
+ │ │ ├── streamingTextFlush.ts
588
680
  │ │ └── toolCallRenderers.tsx
589
- │ ├── theme/ # Terminal theming
590
- └── 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
591
704
  ├── main.ts # Entry point
592
- └── types/ # Global types
705
+ └── types/
706
+ └── semver-functions.d.ts
593
707
  ```
594
708
 
595
709
  ---
@@ -682,6 +796,12 @@ BluMa's runtime layer provides enterprise-grade orchestration:
682
796
  | `session_view.ts` | Session monitoring | Log streaming, status display |
683
797
  | `native_tool_catalog.ts` | Tool registry | Discovery and metadata |
684
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 |
685
805
 
686
806
  ### UI Components
687
807
 
@@ -699,6 +819,9 @@ Key UI components that power the rich terminal experience:
699
819
  | `AnimatedBorder.tsx` | Visual feedback for active elements |
700
820
  | `CollapsibleResult.tsx` | Expandable result sections |
701
821
  | `ProgressBar.tsx` | Progress indicators |
822
+ | `AskUserQuestionPrompt.tsx` | Multiple-choice question UI |
823
+ | `ToolInvocationBlock.tsx` | Tool call visualization |
824
+ | `AssistantMessageDisplay.tsx` | Assistant message formatting |
702
825
 
703
826
  ---
704
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) => {
@@ -10781,6 +10781,10 @@ ${editData.error.display}`;
10781
10781
  reason
10782
10782
  });
10783
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
+ }
10784
10788
  async _continueConversation() {
10785
10789
  try {
10786
10790
  if (this.isInterrupted) {
@@ -10806,7 +10810,7 @@ ${editData.error.display}`;
10806
10810
  } catch (error) {
10807
10811
  const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
10808
10812
  this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
10809
- void this.notifyFactorTurnEndIfNeeded("llm_error");
10813
+ await this.notifyFactorTurnEndIfNeeded("llm_error");
10810
10814
  this.eventBus.emit("backend_message", { type: "done", status: "failed" });
10811
10815
  } finally {
10812
10816
  this.persistSession();
@@ -11720,6 +11724,10 @@ var Agent = class {
11720
11724
  }
11721
11725
  await this.routeManager.handleRoute({ content: inputText, userContext: resolvedUserContext });
11722
11726
  }
11727
+ /** Fecha o turno ativo no FactorRouter (idempotente). */
11728
+ async closeActiveTurn(reason = "worker_exit") {
11729
+ await this.core.closeActiveTurn(reason);
11730
+ }
11723
11731
  /**
11724
11732
  * Handler para o comando /coordinator
11725
11733
  * /coordinator enable - Ativa o Coordinator Mode
@@ -14604,8 +14612,17 @@ var AskUserQuestionPrompt = memo14(AskUserQuestionPromptComponent);
14604
14612
 
14605
14613
  // src/app/ui/App.tsx
14606
14614
  import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
14607
- var MAX_STATIC_HISTORY_ITEMS = 220;
14615
+ var MAX_STATIC_HISTORY_ITEMS = 100;
14616
+ var HISTORY_CLEANUP_THRESHOLD = 120;
14608
14617
  var blumaUpdateRegistryCheckStarted = false;
14618
+ function nextHistoryId(items) {
14619
+ if (items.length === 0) return HEADER_PANEL_HISTORY_ID + 1;
14620
+ let maxId = HEADER_PANEL_HISTORY_ID;
14621
+ for (const it of items) {
14622
+ if (typeof it.id === "number" && it.id > maxId) maxId = it.id;
14623
+ }
14624
+ return maxId + 1;
14625
+ }
14609
14626
  function trimRecentActivity(s, max = 72) {
14610
14627
  const t = String(s ?? "").replace(/\s+/g, " ").trim();
14611
14628
  if (!t) return "";
@@ -14739,13 +14756,16 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14739
14756
  turnStartedAtRef.current = null;
14740
14757
  setProcessingStartMs(null);
14741
14758
  setIsProcessing(false);
14742
- setHistory((prev) => [
14743
- ...prev,
14744
- {
14745
- id: prev.length,
14746
- component: /* @__PURE__ */ jsx24(ChatMeta, { children: "cancelled (Esc)" })
14747
- }
14748
- ]);
14759
+ setHistory((prev) => {
14760
+ const id = nextHistoryId(prev);
14761
+ return [
14762
+ ...prev,
14763
+ {
14764
+ id,
14765
+ component: /* @__PURE__ */ jsx24(ChatMeta, { children: "cancelled (Esc)" })
14766
+ }
14767
+ ];
14768
+ });
14749
14769
  }, [isProcessing, eventBus]);
14750
14770
  const handleSubmit = useCallback3(
14751
14771
  (text) => {
@@ -14753,13 +14773,16 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14753
14773
  const trimmedForSlash = text.trim();
14754
14774
  if (isProcessing && !isSlashRoutingLine(trimmedForSlash)) {
14755
14775
  if (trimmedForSlash.startsWith("/")) {
14756
- setHistory((prev) => [
14757
- ...prev,
14758
- {
14759
- id: prev.length,
14760
- component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Slash command not recognized or incomplete. Type /help for the list." })
14761
- }
14762
- ]);
14776
+ setHistory((prev) => {
14777
+ const id = nextHistoryId(prev);
14778
+ return [
14779
+ ...prev,
14780
+ {
14781
+ id,
14782
+ component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Slash command not recognized or incomplete. Type /help for the list." })
14783
+ }
14784
+ ];
14785
+ });
14763
14786
  }
14764
14787
  return;
14765
14788
  }
@@ -14768,13 +14791,16 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14768
14791
  if (/^\/img\s+/i.test(text) || /^\/image\s+/i.test(text)) {
14769
14792
  const payload = text.replace(/^\/img\s+/i, "").replace(/^\/image\s+/i, "").trim();
14770
14793
  if (!payload) {
14771
- setHistory((prev) => [
14772
- ...prev,
14773
- {
14774
- id: prev.length,
14775
- component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Usage: /img ./screenshot.png \u2014 optional text after the path is sent too" })
14776
- }
14777
- ]);
14794
+ setHistory((prev) => {
14795
+ const id = nextHistoryId(prev);
14796
+ return [
14797
+ ...prev,
14798
+ {
14799
+ id,
14800
+ component: /* @__PURE__ */ jsx24(ChatMeta, { children: "Usage: /img ./screenshot.png \u2014 optional text after the path is sent too" })
14801
+ }
14802
+ ];
14803
+ });
14778
14804
  return;
14779
14805
  }
14780
14806
  setIsProcessing(true);
@@ -14784,13 +14810,16 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14784
14810
  sessionId,
14785
14811
  summary: trimRecentActivity(payload, 120)
14786
14812
  });
14787
- setHistory((prev) => [
14788
- ...prev,
14789
- {
14790
- id: prev.length,
14791
- component: /* @__PURE__ */ jsx24(UserMessageWithOptionalImages, { raw: payload, variant: "slash-img" })
14792
- }
14793
- ]);
14813
+ setHistory((prev) => {
14814
+ const id = nextHistoryId(prev);
14815
+ return [
14816
+ ...prev,
14817
+ {
14818
+ id,
14819
+ component: /* @__PURE__ */ jsx24(UserMessageWithOptionalImages, { raw: payload, variant: "slash-img" })
14820
+ }
14821
+ ];
14822
+ });
14794
14823
  agentInstance.current.processTurn({ content: payload });
14795
14824
  return;
14796
14825
  }
@@ -14813,24 +14842,28 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14813
14842
  setIsProcessing(false);
14814
14843
  setIsInitAgentActive(false);
14815
14844
  }
14816
- setHistory((prev) => [
14817
- ...prev,
14818
- {
14819
- id: prev.length,
14820
- component: /* @__PURE__ */ jsx24(ChatUserMessage, { children: /* @__PURE__ */ jsx24(Text22, { color: BLUMA_TERMINAL.m3OnSurface, bold: true, wrap: "wrap", children: text }) })
14821
- },
14822
- {
14823
- id: prev.length + 1,
14824
- component: /* @__PURE__ */ jsx24(
14825
- SlashCommands_default,
14826
- {
14827
- input: text,
14828
- setHistory,
14829
- agentRef: agentInstance
14830
- }
14831
- )
14832
- }
14833
- ]);
14845
+ setHistory((prev) => {
14846
+ const firstId = nextHistoryId(prev);
14847
+ const secondId = firstId + 1;
14848
+ return [
14849
+ ...prev,
14850
+ {
14851
+ id: firstId,
14852
+ component: /* @__PURE__ */ jsx24(ChatUserMessage, { children: /* @__PURE__ */ jsx24(Text22, { color: BLUMA_TERMINAL.m3OnSurface, bold: true, wrap: "wrap", children: text }) })
14853
+ },
14854
+ {
14855
+ id: secondId,
14856
+ component: /* @__PURE__ */ jsx24(
14857
+ SlashCommands_default,
14858
+ {
14859
+ input: text,
14860
+ setHistory,
14861
+ agentRef: agentInstance
14862
+ }
14863
+ )
14864
+ }
14865
+ ];
14866
+ });
14834
14867
  return;
14835
14868
  }
14836
14869
  if (text.startsWith("!")) {
@@ -14846,16 +14879,19 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
14846
14879
  sessionId,
14847
14880
  summary: trimRecentActivity(command, 120)
14848
14881
  });
14849
- setHistory((prev) => [
14850
- ...prev,
14851
- {
14852
- id: prev.length,
14853
- component: /* @__PURE__ */ jsx24(ChatUserMessage, { children: /* @__PURE__ */ jsxs22(Text22, { bold: true, color: "white", children: [
14854
- "$ !",
14855
- command
14856
- ] }) })
14857
- }
14858
- ]);
14882
+ setHistory((prev) => {
14883
+ const id = nextHistoryId(prev);
14884
+ return [
14885
+ ...prev,
14886
+ {
14887
+ id,
14888
+ component: /* @__PURE__ */ jsx24(ChatUserMessage, { children: /* @__PURE__ */ jsxs22(Text22, { bold: true, color: "white", children: [
14889
+ "$ !",
14890
+ command
14891
+ ] }) })
14892
+ }
14893
+ ];
14894
+ });
14859
14895
  Promise.resolve().then(() => (init_async_command(), async_command_exports)).then(async ({ runCommandAsync: runCommandAsync2 }) => {
14860
14896
  try {
14861
14897
  const result = await runCommandAsync2({ command, cwd: workdir });
@@ -14869,31 +14905,37 @@ Please use command_status to check the result and report back to the user.`;
14869
14905
  } else {
14870
14906
  turnStartedAtRef.current = null;
14871
14907
  setProcessingStartMs(null);
14872
- setHistory((prev) => [
14873
- ...prev,
14874
- {
14875
- id: prev.length,
14876
- component: /* @__PURE__ */ jsxs22(Text22, { color: "red", children: [
14877
- "Failed to execute: ",
14878
- result.error || result.message
14879
- ] })
14880
- }
14881
- ]);
14908
+ setHistory((prev) => {
14909
+ const id = nextHistoryId(prev);
14910
+ return [
14911
+ ...prev,
14912
+ {
14913
+ id,
14914
+ component: /* @__PURE__ */ jsxs22(Text22, { color: "red", children: [
14915
+ "Failed to execute: ",
14916
+ result.error || result.message
14917
+ ] })
14918
+ }
14919
+ ];
14920
+ });
14882
14921
  setIsProcessing(false);
14883
14922
  }
14884
14923
  } catch (err) {
14885
14924
  turnStartedAtRef.current = null;
14886
14925
  setProcessingStartMs(null);
14887
- setHistory((prev) => [
14888
- ...prev,
14889
- {
14890
- id: prev.length,
14891
- component: /* @__PURE__ */ jsxs22(Text22, { color: "red", children: [
14892
- "Error: ",
14893
- err.message
14894
- ] })
14895
- }
14896
- ]);
14926
+ setHistory((prev) => {
14927
+ const id = nextHistoryId(prev);
14928
+ return [
14929
+ ...prev,
14930
+ {
14931
+ id,
14932
+ component: /* @__PURE__ */ jsxs22(Text22, { color: "red", children: [
14933
+ "Error: ",
14934
+ err.message
14935
+ ] })
14936
+ }
14937
+ ];
14938
+ });
14897
14939
  setIsProcessing(false);
14898
14940
  }
14899
14941
  });
@@ -14901,13 +14943,16 @@ Please use command_status to check the result and report back to the user.`;
14901
14943
  }
14902
14944
  setIsProcessing(true);
14903
14945
  markTurnStarted();
14904
- setHistory((prev) => [
14905
- ...prev,
14906
- {
14907
- id: prev.length,
14908
- component: /* @__PURE__ */ jsx24(UserMessageWithOptionalImages, { raw: text, variant: "plain" })
14909
- }
14910
- ]);
14946
+ setHistory((prev) => {
14947
+ const id = nextHistoryId(prev);
14948
+ return [
14949
+ ...prev,
14950
+ {
14951
+ id,
14952
+ component: /* @__PURE__ */ jsx24(UserMessageWithOptionalImages, { raw: text, variant: "plain" })
14953
+ }
14954
+ ];
14955
+ });
14911
14956
  agentInstance.current.processTurn({ content: text });
14912
14957
  },
14913
14958
  [isProcessing, markTurnStarted]
@@ -14939,27 +14984,43 @@ Please use command_status to check the result and report back to the user.`;
14939
14984
  const key = reasoningDedupeKey(r);
14940
14985
  if (!r || key === lastReasoningTextRef.current) return;
14941
14986
  lastReasoningTextRef.current = key;
14942
- setHistory((prev) => [
14943
- ...prev,
14944
- {
14945
- id: prev.length,
14946
- component: /* @__PURE__ */ jsx24(ReasoningDisplay, { reasoning })
14947
- }
14948
- ]);
14987
+ setHistory((prev) => {
14988
+ const id = nextHistoryId(prev);
14989
+ return [
14990
+ ...prev,
14991
+ {
14992
+ id,
14993
+ component: /* @__PURE__ */ jsx24(ReasoningDisplay, { reasoning })
14994
+ }
14995
+ ];
14996
+ });
14949
14997
  }, []);
14950
14998
  const appendStreamedAssistant = useCallback3((content) => {
14951
14999
  const t = String(content ?? "").trim();
14952
15000
  if (!t) return;
14953
15001
  const key = reasoningDedupeKey(t);
14954
15002
  lastStreamAssistantKeyRef.current = key;
14955
- setHistory((prev) => [
14956
- ...prev,
14957
- {
14958
- id: prev.length,
14959
- component: /* @__PURE__ */ jsx24(AssistantMessageDisplay, { content })
14960
- }
14961
- ]);
15003
+ setHistory((prev) => {
15004
+ const id = nextHistoryId(prev);
15005
+ return [
15006
+ ...prev,
15007
+ {
15008
+ id,
15009
+ component: /* @__PURE__ */ jsx24(AssistantMessageDisplay, { content })
15010
+ }
15011
+ ];
15012
+ });
14962
15013
  }, []);
15014
+ useEffect8(() => {
15015
+ if (history.length >= HISTORY_CLEANUP_THRESHOLD) {
15016
+ setHistory((prev) => {
15017
+ const header = prev.find((h) => h.id === HEADER_PANEL_HISTORY_ID);
15018
+ const rest = prev.filter((h) => h.id !== HEADER_PANEL_HISTORY_ID);
15019
+ const tail = rest.slice(-(MAX_STATIC_HISTORY_ITEMS - 1));
15020
+ return header ? [header, ...tail] : tail;
15021
+ });
15022
+ }
15023
+ }, [history.length]);
14963
15024
  const cappedHistory = useMemo2(() => {
14964
15025
  if (history.length <= MAX_STATIC_HISTORY_ITEMS) return history;
14965
15026
  const header = history.find((h) => h.id === HEADER_PANEL_HISTORY_ID);
@@ -14998,13 +15059,16 @@ Please use command_status to check the result and report back to the user.`;
14998
15059
  turnStartedAtRef.current = null;
14999
15060
  setProcessingStartMs(null);
15000
15061
  const ms = Date.now() - t;
15001
- setHistory((prev) => [
15002
- ...prev,
15003
- {
15004
- id: prev.length,
15005
- component: /* @__PURE__ */ jsx24(ChatTurnDuration, { durationMs: ms })
15006
- }
15007
- ]);
15062
+ setHistory((prev) => {
15063
+ const id = nextHistoryId(prev);
15064
+ return [
15065
+ ...prev,
15066
+ {
15067
+ id,
15068
+ component: /* @__PURE__ */ jsx24(ChatTurnDuration, { durationMs: ms })
15069
+ }
15070
+ ];
15071
+ });
15008
15072
  };
15009
15073
  if (parsed.type === "done" || parsed.type === "error") {
15010
15074
  setIsInitAgentActive(false);
@@ -15178,9 +15242,10 @@ Please use command_status to check the result and report back to the user.`;
15178
15242
  }
15179
15243
  if (newComponent) {
15180
15244
  setHistory((prev) => {
15245
+ const id = nextHistoryId(prev);
15181
15246
  const next = [
15182
15247
  ...prev,
15183
- { id: prev.length, component: newComponent }
15248
+ { id, component: newComponent }
15184
15249
  ];
15185
15250
  if (parsed.type === "error") {
15186
15251
  const t = turnStartedAtRef.current;
@@ -15189,7 +15254,7 @@ Please use command_status to check the result and report back to the user.`;
15189
15254
  setProcessingStartMs(null);
15190
15255
  const ms = Date.now() - t;
15191
15256
  next.push({
15192
- id: next.length,
15257
+ id: nextHistoryId(next),
15193
15258
  component: /* @__PURE__ */ jsx24(ChatTurnDuration, { durationMs: ms })
15194
15259
  });
15195
15260
  }
@@ -15206,10 +15271,10 @@ Please use command_status to check the result and report back to the user.`;
15206
15271
  const handleInputNotice = (data) => {
15207
15272
  const msg = String(data.message || "").trim();
15208
15273
  if (!msg) return;
15209
- setHistory((prev) => [
15210
- ...prev,
15211
- { id: prev.length, component: /* @__PURE__ */ jsx24(ChatMeta, { children: msg }) }
15212
- ]);
15274
+ setHistory((prev) => {
15275
+ const id = nextHistoryId(prev);
15276
+ return [...prev, { id, component: /* @__PURE__ */ jsx24(ChatMeta, { children: msg }) }];
15277
+ });
15213
15278
  };
15214
15279
  uiEventBus.on("user_overlay", handleUiOverlay);
15215
15280
  uiEventBus.on("input_notice", handleInputNotice);
@@ -15491,7 +15556,8 @@ async function runAgentMode() {
15491
15556
  let reasoningBuffer = null;
15492
15557
  let lastAttachments = null;
15493
15558
  let resultEmitted = false;
15494
- eventBus.on("backend_message", (payload) => {
15559
+ let agentRef = null;
15560
+ eventBus.on("backend_message", async (payload) => {
15495
15561
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
15496
15562
  writeAgentEvent(sessionId, {
15497
15563
  event_type: "backend_message",
@@ -15524,6 +15590,9 @@ async function runAgentMode() {
15524
15590
  }
15525
15591
  if (!resultEmitted && payload?.type === "done") {
15526
15592
  resultEmitted = true;
15593
+ if (agentRef) {
15594
+ await agentRef.closeActiveTurn("worker_done_exit");
15595
+ }
15527
15596
  finalizeSession(sessionId, "completed", { finishedBy: "done-event" });
15528
15597
  writeAgentEvent(sessionId, {
15529
15598
  event_type: "result",
@@ -15564,6 +15633,7 @@ async function runAgentMode() {
15564
15633
  });
15565
15634
  try {
15566
15635
  const agent = new Agent(sessionId, eventBus);
15636
+ agentRef = agent;
15567
15637
  await agent.initialize();
15568
15638
  const userContent = JSON.stringify({
15569
15639
  message_id: envelope.message_id || sessionId,
@@ -15575,6 +15645,7 @@ async function runAgentMode() {
15575
15645
  await agent.processTurn({ content: userContent }, userContextInput);
15576
15646
  if (!resultEmitted) {
15577
15647
  resultEmitted = true;
15648
+ await agent.closeActiveTurn("worker_post_turn_fallback");
15578
15649
  finalizeSession(sessionId, "completed", { finishedBy: "post-turn-fallback" });
15579
15650
  writeAgentEvent(sessionId, {
15580
15651
  event_type: "result",
@@ -15592,6 +15663,9 @@ async function runAgentMode() {
15592
15663
  }
15593
15664
  } catch (err) {
15594
15665
  if (!resultEmitted) {
15666
+ if (agentRef) {
15667
+ await agentRef.closeActiveTurn("worker_exception");
15668
+ }
15595
15669
  finalizeSession(sessionId, "error", { finishedBy: "exception" });
15596
15670
  writeAgentEvent(sessionId, {
15597
15671
  event_type: "result",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",