@nomad-e/bluma-cli 0.6.1 β†’ 0.6.3

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
@@ -13,6 +13,8 @@
13
13
 
14
14
  **BluMa** is an independent AI agent CLI for automation and advanced software engineering. It combines powerful tool orchestration, multi-agent coordination, and intelligent context management to help you build software faster and with higher quality.
15
15
 
16
+ **Latest Version:** v0.6.2 (2026-05-09) β€” Modular tool architecture, native clipboard, thread management, bidirectional mailbox IPC, enhanced multi-agent orchestration, and professional release engineering.
17
+
16
18
  ---
17
19
 
18
20
  ## πŸš€ Quick Start
@@ -75,18 +77,104 @@ Tools are now modularly structured in `src/app/agent/tools/` with separate UI co
75
77
  ### 🧠 **Multi-Agent Orchestration**
76
78
  - **Coordinator Mode**: Product Owner + Engineering Manager hybrid that delegates to specialist workers
77
79
  - **Parallel Execution**: Launch multiple workers concurrently for research, implementation, and verification
78
- - **Mailbox IPC**: Bidirectional communication between coordinator and workers via `send_message`, `list_mailbox_messages`, `poll_mailbox`, `signal_mailbox`
80
+ - **Mailbox IPC**: **Bidirectional file-based communication** between coordinator and workers
81
+ - **send_message**: Send follow-ups to running workers without re-spawning
82
+ - **list_mailbox_messages**: Read progress updates, permission requests, and results
83
+ - **poll_mailbox**: Poll for new messages from workers
84
+ - **signal_mailbox**: Send ack/nack/progress/heartbeat signals
85
+ - **File-based storage**: `~/.bluma/mailboxes/{session_id}.in/out/sig` (JSONL format)
79
86
  - **Worker Types**: Researchers, implementers, verifiers with specialized roles
80
87
  - **Session Registry**: Track and manage agent sessions with lifecycle events
88
+ - **Task Boundary Tracking**: Use `task_boundary` to track orchestration phases (PLANNING, EXECUTION, VERIFICATION)
81
89
 
82
90
  ### πŸ“ **Slash Commands** (80+ commands)
83
91
  Quick access to common operations across 5 categories:
84
92
 
85
- - **Session** (25+): `/clear`, `/sessions`, `/attach`, `/follow`, `/bridge`, `/status`, `/logs`, `/resume`, `/kill`, `/compact`, `/export`, `/summarize`, `/history`, `/share`, `/copy`, `/commit`, `/pr`, `/release`, `/snip`, `/collapse`, `/brief`, `/undo`, `/redo`, `/thread` (with subcommands: list/new/resume/fork/rename/archive/delete)
86
- - **Inspect** (30+): `/plugins`, `/plugin`, `/diagnostics`, `/permissions`, `/features`, `/hooks`, `/model`, `/effort`, `/style`, `/sandbox`, `/worktree`, `/statusline`, `/skills`, `/tools`, `/mcp`, `/debug-workers`, `/cost`, `/memory`, `/stats`, `/theme`, `/keybindings`, `/vim`, `/ctx`, `/dream`, `/diff`, `/editor`, `/config`, `/file`, `/search`, `/context`, `/token`, `/settings`, `/alias`, `/macro`, `/thread stats`
87
- - **Agent** (15+): `/agent`, `/agents`, `/img`, `/image`, `/init`, `/review`, `/explain`, `/fix`, `/debug`, `/bug`, `/test`, `/optimize`, `/refactor`, `/document`, `/chat`, `/code`, `/terminal`, `/template`
88
- - **Help**: `/help`
89
- - **Input**: `Ctrl+V`/`Cmd+V` (paste image/text/file), `Ctrl+Shift+I` (same as Ctrl+V)
93
+ **Session Commands** (25+):
94
+ - `/clear` - Clear current session
95
+ - `/sessions` - List all sessions
96
+ - `/attach` - Attach to existing session
97
+ - `/follow` - Follow session output
98
+ - `/bridge` - Bridge multiple sessions
99
+ - `/status` - Show session status
100
+ - `/logs` - View session logs
101
+ - `/resume` - Resume previous session
102
+ - `/kill` - Terminate session
103
+ - `/compact` - Compress context
104
+ - `/export` - Export session
105
+ - `/summarize` - Summarize conversation
106
+ - `/history` - Command history
107
+ - `/share` - Share session
108
+ - `/copy` - Copy output to clipboard
109
+ - `/commit` - Create git commit
110
+ - `/pr` - Create pull request
111
+ - `/release` - Professional release engineering
112
+ - `/snip` - Extract conversation snippets
113
+ - `/collapse` - Collapse context
114
+ - `/brief` - Generate brief
115
+ - `/undo` - Undo last action
116
+ - `/redo` - Redo undone action
117
+ - `/thread` - Thread management (list/new/resume/fork/rename/archive/delete)
118
+
119
+ **Inspect Commands** (30+):
120
+ - `/plugins` - List plugins
121
+ - `/plugin` - Plugin details
122
+ - `/diagnostics` - Run diagnostics
123
+ - `/permissions` - Show permissions
124
+ - `/features` - List features
125
+ - `/hooks` - Hook registry
126
+ - `/model` - Model info
127
+ - `/effort` - Effort estimation
128
+ - `/style` - Code style
129
+ - `/sandbox` - Sandbox status
130
+ - `/worktree` - Worktree info
131
+ - `/statusline` - Status line config
132
+ - `/skills` - List skills
133
+ - `/tools` - List tools
134
+ - `/mcp` - MCP resources
135
+ - `/debug-workers` - Worker debug
136
+ - `/cost` - Cost tracking
137
+ - `/memory` - Memory usage
138
+ - `/stats` - Session stats
139
+ - `/theme` - Theme config
140
+ - `/keybindings` - Key bindings
141
+ - `/vim` - Vim mode
142
+ - `/ctx` - Context inspector
143
+ - `/dream` - Dream engine status
144
+ - `/diff` - Show recent changes
145
+ - `/editor` - Open in editor
146
+ - `/config` - Runtime config
147
+ - `/file` - File operations
148
+ - `/search` - Search codebase
149
+ - `/context` - Context management
150
+ - `/token` - Token usage
151
+ - `/settings` - Runtime settings
152
+ - `/alias` - Manage aliases
153
+ - `/macro` - Execute macros
154
+ - `/thread stats` - Thread statistics
155
+
156
+ **Agent Commands** (15+):
157
+ - `/agent` - Agent info
158
+ - `/agents` - List agents
159
+ - `/img` / `/image` - Image handling
160
+ - `/init` - Initialize project
161
+ - `/review` - Code review
162
+ - `/explain` - Explain code
163
+ - `/fix` - Fix errors
164
+ - `/debug` - Debug issues
165
+ - `/bug` - Report bug
166
+ - `/test` - Run tests
167
+ - `/optimize` - Optimize code
168
+ - `/refactor` - Refactor code
169
+ - `/document` - Generate docs
170
+ - `/chat` - Chat mode
171
+ - `/code` - Code mode
172
+ - `/terminal` - Terminal placeholder
173
+ - `/template` - Create from template
174
+
175
+ **Input Methods**:
176
+ - `Ctrl+V` / `Cmd+V` - Paste image/text/file
177
+ - `Ctrl+Shift+I` - Same as Ctrl+V
90
178
 
91
179
  ### 🎯 **Intelligent Context Management**
92
180
  - **Auto Memory**: Persistent coding notes across sessions (`coding_memory`)
@@ -102,15 +190,29 @@ Structured problem-solving workflow with 3 phases:
102
190
  2. **EXECUTION**: Implementation
103
191
  3. **VERIFICATION**: Testing and validation
104
192
 
105
- ### πŸ”§ **Skills System**
106
- Extendable expertise modules with progressive disclosure:
193
+ ### πŸ”§ **Skills System** (6 skills)
194
+ Extendable expertise modules with **progressive disclosure** β€” load only when needed:
195
+
196
+ **Available Skills:**
107
197
  - **git-commit**: Conventional commits automation with BluMa watermark
108
- - **git-pr**: Pull request creation and management
109
- - **git-release**: Full release engineering workflow (version bumps, changelog, tags, npm publish)
198
+ - **git-pr**: Pull request creation and management (descriptions, reviewers, draft mode)
199
+ - **git-release**: Full release engineering (version bumps, changelog, tags, npm publish, GitHub releases)
110
200
  - **pdf**: PDF generation and manipulation (reports, merge, forms, OCR)
111
201
  - **xlsx**: Spreadsheet processing (create, edit, formulas, charting, cleaning)
112
202
  - **skill-creator**: Create custom skills
113
203
 
204
+ **How Skills Work:**
205
+ 1. **Discovery**: Skills loaded from `dist/config/skills/`, `{cwd}/.bluma/skills/`, `~/.bluma/skills/`
206
+ 2. **Progressive Disclosure**:
207
+ - Level 1: One-line description (always visible)
208
+ - Level 2: Full instructions (loaded on activation)
209
+ - Level 3: References and scripts (loaded on demand)
210
+ 3. **Triggering**: Agent auto-detects when to load skills based on task context
211
+ 4. **Priority**: Native > Project > Global (native skills cannot be overridden)
212
+
213
+ **Create Custom Skills:**
214
+ See [docs/SKILLS.md](docs/SKILLS.md) for complete guide on authoring skills.
215
+
114
216
  ### 🎨 **Modern UI**
115
217
  - Built with **Ink** (React for CLI)
116
218
  - **Shimmer effects** for working states (WorkingShimmerText, Spinner with ShimmerChar/FlashingChar)
@@ -132,6 +234,17 @@ Extendable expertise modules with progressive disclosure:
132
234
  - **ES Modules**: Modern module system with bundler resolution
133
235
  - **React 19**: Latest React with concurrent features
134
236
  - **Optimized rendering**: Memoized message blocks, offscreen freeze, expandable previews
237
+ - **Parallel tests**: Up to 8 workers for faster test execution
238
+ - **Native modules**: Rust-based clipboard and layout engine for critical paths
239
+
240
+ ### πŸ”Œ **Hooks & Plugins**
241
+ Extensible architecture with hook registry and plugin system:
242
+
243
+ - **Hook Registry**: Execute custom scripts on events (pre-commit, post-build, etc.)
244
+ - **Plugin System**: Extend BluMa with custom plugins
245
+ - **Event-driven**: Hooks triggered by tool execution, session events, and lifecycle hooks
246
+
247
+ See [docs/BLUMA_DEVELOPER_GUIDE.md](docs/BLUMA_DEVELOPER_GUIDE.md) for plugin development.
135
248
 
136
249
  ### πŸ”Œ **MCP Support**
137
250
  - **Model Context Protocol**: Connect to external MCP servers
@@ -143,6 +256,28 @@ Extendable expertise modules with progressive disclosure:
143
256
  - **Thread stats**: View thread metadata and statistics
144
257
  - **Codex-style interface**: Familiar thread management commands
145
258
 
259
+ ### πŸ¦€ **Native Modules** (Rust)
260
+ High-performance native extensions for critical operations:
261
+
262
+ - **bluma-clipboard**: Cross-platform clipboard with image support
263
+ - Built with Rust (arboard crate)
264
+ - No dependencies on wl-paste/xclip
265
+ - Native image reading for terminal paste
266
+ - **yoga-layout**: High-performance layout engine for Ink
267
+ - Facebook's Yoga layout library
268
+ - Optimized for terminal UI rendering
269
+
270
+ ### πŸ“¦ **VS Code Extension**
271
+ Full BluMa capabilities integrated into VS Code:
272
+
273
+ - **Chat Panel**: Interactive chat interface with full BluMa capabilities
274
+ - **Session Sync**: Real-time synchronization with terminal sessions
275
+ - **Image Support**: Paste and view images directly in VS Code
276
+ - **File Integration**: Open and edit files from within the chat
277
+ - **Streaming**: Real-time response streaming with markdown rendering
278
+
279
+ See [vscode-extension/README.md](vscode-extension/README.md) for setup instructions.
280
+
146
281
  ---
147
282
 
148
283
  ## πŸ—οΈ Architecture
@@ -207,7 +342,12 @@ This modular design enables:
207
342
  - **`src/ink/`**: Ink renderer shims and compatibility layer
208
343
  - **`src/shims/`**: Build-time shims for react-compiler-runtime and bidi-js
209
344
  - **`native/`**: Rust-based native modules (clipboard, yoga-layout)
345
+ - **bluma-clipboard**: Cross-platform clipboard with image support
346
+ - **yoga-layout**: High-performance layout engine for Ink
210
347
  - **`vscode-extension/`**: VS Code extension for chat integration
348
+ - Chat panel with full BluMa capabilities
349
+ - Real-time session sync
350
+ - Image and file support
211
351
 
212
352
  ---
213
353
 
@@ -352,6 +492,24 @@ bluma
352
492
  /pr "feat: add new authentication" # Create PR with conventional commit
353
493
  ```
354
494
 
495
+ ### 5. **Web Integration**
496
+ ```bash
497
+ # Use the embeddable React chat widget
498
+ import { useChatWidget } from '@nomad-e/bluma-cli/chat-widget';
499
+
500
+ // Embed BluMa capabilities in your web app
501
+ const { messages, streamResponse } = useChatWidget();
502
+ ```
503
+
504
+ ### 6. **VS Code Integration**
505
+ ```bash
506
+ # Install the VS Code extension
507
+ # Access full BluMa capabilities from within VS Code
508
+ - Chat panel with session sync
509
+ - Image and file support
510
+ - Real-time streaming
511
+ ```
512
+
355
513
  ---
356
514
 
357
515
  ## πŸ“„ License
@@ -366,4 +524,30 @@ Apache 2.0 β€” see [LICENSE](LICENSE) for details.
366
524
 
367
525
  ---
368
526
 
369
- *BluMa CLI v0.3.1 β€” Built with TypeScript, React/Ink, and ES modules.*
527
+ ## πŸ“° Changelog
528
+
529
+ See [CHANGELOG.md](CHANGELOG.md) for detailed release notes.
530
+
531
+ ### What's New in v0.6.2 (2026-05-09)
532
+
533
+ **Major Features:**
534
+ - **Modular Tool Architecture**: All 45+ tools now have dedicated directories with separate logic, UI, and types
535
+ - **Native Clipboard Support**: Rust-based clipboard implementation for cross-platform image handling
536
+ - **Thread Management**: Full thread lifecycle with create, resume, fork, rename, archive, delete operations
537
+ - **Enhanced Multi-Agent Orchestration**: Improved coordinator-worker communication with bidirectional mailbox IPC
538
+ - **Chat Widget**: Embeddable React chat component for web integration
539
+
540
+ **Improvements:**
541
+ - 27 new slash commands for session management, inspection, and agent control
542
+ - Performance optimizations in UI rendering and context management
543
+ - Enhanced sandbox security with improved policy enforcement
544
+ - Better error handling and user-friendly error messages
545
+
546
+ **Fixes:**
547
+ - Resolved circular dependency in tool metadata
548
+ - Fixed worker registration for UI visibility
549
+ - Improved session persistence on LLM errors
550
+
551
+ ---
552
+
553
+ *BluMa CLI v0.6.2 β€” Built with TypeScript, React 19, Ink, and ES modules.*
@@ -570,13 +570,14 @@
570
570
  "type": "function",
571
571
  "function": {
572
572
  "name": "task_boundary",
573
- "description": "Indicate the start of a task or make an update to the current task. Use this to organize complex work into phases (PLANNING, EXECUTION, VERIFICATION). The UI will show this as a structured task view with progress tracking.",
573
+ "description": "Indicate the start of a task or make an update to the current task. Use this to organize complex work into phases (PLANNING, EXECUTION, VERIFICATION). The UI will show this as a structured task view with progress tracking. **IMPORTANT: All 4 fields are required and must not be empty.**",
574
574
  "parameters": {
575
575
  "type": "object",
576
576
  "properties": {
577
577
  "task_name": {
578
578
  "type": "string",
579
- "description": "Name of the task, e.g. 'Implementing Authentication'. Use the same name to update an existing task."
579
+ "minLength": 1,
580
+ "description": "Name of the task, e.g. 'Implementing Authentication'. Use the same name to update an existing task. **REQUIRED: Must not be empty.**"
580
581
  },
581
582
  "mode": {
582
583
  "type": "string",
@@ -585,15 +586,17 @@
585
586
  "EXECUTION",
586
587
  "VERIFICATION"
587
588
  ],
588
- "description": "Current mode: PLANNING (research/design), EXECUTION (implementing), VERIFICATION (testing)."
589
+ "description": "Current mode: PLANNING (research/design), EXECUTION (implementing), VERIFICATION (testing). **REQUIRED.**"
589
590
  },
590
591
  "task_status": {
591
592
  "type": "string",
592
- "description": "Current activity, e.g. 'Creating authentication middleware'. Describes what you are about to do."
593
+ "minLength": 1,
594
+ "description": "Current activity, e.g. 'Creating authentication middleware'. Describes what you are about to do. **REQUIRED: Must not be empty.**"
593
595
  },
594
596
  "task_summary": {
595
597
  "type": "string",
596
- "description": "Summary of what has been accomplished so far. Should be concise but comprehensive."
598
+ "minLength": 1,
599
+ "description": "Summary of what has been accomplished so far. Should be concise but comprehensive. **REQUIRED: Must not be empty.**"
597
600
  }
598
601
  },
599
602
  "required": [
package/dist/main.js CHANGED
@@ -396,6 +396,118 @@ var init_sandbox_policy = __esm({
396
396
  }
397
397
  });
398
398
 
399
+ // src/app/agent/runtime/task_store.ts
400
+ import fs12 from "fs";
401
+ import path10 from "path";
402
+ function getStorePath() {
403
+ const policy = getSandboxPolicy();
404
+ return path10.join(policy.workspaceRoot, ".bluma", "task_state.json");
405
+ }
406
+ function getDefaultState() {
407
+ return {
408
+ tasks: [],
409
+ nextId: 1,
410
+ activeTask: null,
411
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
412
+ };
413
+ }
414
+ function ensureLoaded() {
415
+ const storePath = getStorePath();
416
+ if (cache2 && cachePath === storePath) {
417
+ return cache2;
418
+ }
419
+ try {
420
+ if (fs12.existsSync(storePath)) {
421
+ const raw = fs12.readFileSync(storePath, "utf-8");
422
+ const parsed = JSON.parse(raw);
423
+ cache2 = {
424
+ tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
425
+ nextId: typeof parsed.nextId === "number" ? parsed.nextId : 1,
426
+ activeTask: parsed.activeTask ?? null,
427
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
428
+ };
429
+ cachePath = storePath;
430
+ return cache2;
431
+ }
432
+ } catch {
433
+ }
434
+ cache2 = getDefaultState();
435
+ cachePath = storePath;
436
+ return cache2;
437
+ }
438
+ function persist(state2) {
439
+ const storePath = getStorePath();
440
+ fs12.mkdirSync(path10.dirname(storePath), { recursive: true });
441
+ state2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
442
+ fs12.writeFileSync(storePath, JSON.stringify(state2, null, 2), "utf-8");
443
+ cache2 = state2;
444
+ cachePath = storePath;
445
+ }
446
+ function updateTaskStore(mutator) {
447
+ const current = ensureLoaded();
448
+ const clone = {
449
+ tasks: current.tasks.map((task) => ({ ...task })),
450
+ nextId: current.nextId,
451
+ activeTask: current.activeTask ? { ...current.activeTask } : null,
452
+ updatedAt: current.updatedAt
453
+ };
454
+ mutator(clone);
455
+ persist(clone);
456
+ return clone;
457
+ }
458
+ function calculateTaskStats(tasks) {
459
+ const total = tasks.length;
460
+ const pending = tasks.filter((t) => t.status === "pending").length;
461
+ const inProgress = tasks.filter((t) => t.status === "in_progress").length;
462
+ const completed = tasks.filter((t) => t.status === "completed").length;
463
+ const progress = total > 0 ? Math.round(completed / total * 100) : 0;
464
+ return { total, pending, inProgress, completed, progress };
465
+ }
466
+ function buildTaskSnapshot() {
467
+ const state2 = ensureLoaded();
468
+ return {
469
+ tasks: state2.tasks.map((task) => ({ ...task })),
470
+ activeTask: state2.activeTask ? { ...state2.activeTask } : null,
471
+ stats: calculateTaskStats(state2.tasks),
472
+ updatedAt: state2.updatedAt
473
+ };
474
+ }
475
+ function registerWorker(worker) {
476
+ activeWorkers.set(worker.id, worker);
477
+ }
478
+ function completeWorker(workerId) {
479
+ const worker = activeWorkers.get(workerId);
480
+ if (worker) {
481
+ worker.status = "completed";
482
+ worker.endTime = Date.now();
483
+ worker.progress = {
484
+ ...worker.progress,
485
+ completed: true,
486
+ statusText: "Done"
487
+ };
488
+ worker.evictAfter = Date.now() + 3e4;
489
+ }
490
+ }
491
+ function evictWorkerTask(workerId) {
492
+ activeWorkers.delete(workerId);
493
+ }
494
+ function getVisibleWorkers() {
495
+ const now2 = Date.now();
496
+ return Array.from(activeWorkers.values()).filter(
497
+ (w) => (w.evictAfter ?? Infinity) > now2
498
+ );
499
+ }
500
+ var cache2, cachePath, activeWorkers;
501
+ var init_task_store = __esm({
502
+ "src/app/agent/runtime/task_store.ts"() {
503
+ "use strict";
504
+ init_sandbox_policy();
505
+ cache2 = null;
506
+ cachePath = null;
507
+ activeWorkers = /* @__PURE__ */ new Map();
508
+ }
509
+ });
510
+
399
511
  // src/app/agent/tools/CommandStatusTool/CommandStatusTool.ts
400
512
  var CommandStatusTool_exports = {};
401
513
  __export(CommandStatusTool_exports, {
@@ -1364,12 +1476,33 @@ async function spawnAgent(args) {
1364
1476
  );
1365
1477
  child.unref();
1366
1478
  updateSession(sessionId, { pid: child.pid });
1367
- spawnLog.info("Worker spawned successfully", {
1479
+ const workerTask = {
1480
+ id: sessionId,
1481
+ title,
1482
+ description: args.task,
1483
+ status: "running",
1484
+ task: args.task,
1485
+ progress: {
1486
+ step: 1,
1487
+ totalSteps: 10,
1488
+ currentTask: args.task?.slice(0, 100) || "Initializing...",
1489
+ statusText: "Running",
1490
+ lastActivity: Date.now()
1491
+ },
1492
+ startTime: Date.now(),
1493
+ endTime: void 0,
1494
+ evictAfter: void 0,
1495
+ agentType: args.agent_type || "general",
1496
+ parentSessionId: parentSessionId || void 0
1497
+ };
1498
+ registerWorker(workerTask);
1499
+ spawnLog.info("Worker spawned and registered", {
1368
1500
  sessionId,
1369
1501
  pid: child.pid,
1370
1502
  title,
1371
1503
  timeoutMs,
1372
- sandbox: true
1504
+ sandbox: true,
1505
+ workerTaskId: sessionId
1373
1506
  });
1374
1507
  return {
1375
1508
  success: true,
@@ -1411,6 +1544,19 @@ async function waitAgent(args) {
1411
1544
  }
1412
1545
  if (session.status !== "running") {
1413
1546
  waitLog.info("Agent completed", { sessionId, status: session.status });
1547
+ if (session.status === "completed") {
1548
+ completeWorker(sessionId);
1549
+ } else if (session.status === "cancelled") {
1550
+ const workerTask = {
1551
+ id: sessionId,
1552
+ title: session.title,
1553
+ description: session.title,
1554
+ status: "killed",
1555
+ startTime: Date.now(),
1556
+ endTime: Date.now()
1557
+ };
1558
+ registerWorker(workerTask);
1559
+ }
1414
1560
  return {
1415
1561
  success: true,
1416
1562
  completed: true,
@@ -1501,6 +1647,7 @@ var init_AgentCoordinationTool = __esm({
1501
1647
  "src/app/agent/tools/AgentCoordinationTool/AgentCoordinationTool.ts"() {
1502
1648
  "use strict";
1503
1649
  init_session_registry();
1650
+ init_task_store();
1504
1651
  init_logger();
1505
1652
  init_errors();
1506
1653
  init_worker_context();
@@ -15596,97 +15743,8 @@ async function countLines(args) {
15596
15743
  }
15597
15744
  }
15598
15745
 
15599
- // src/app/agent/runtime/task_store.ts
15600
- init_sandbox_policy();
15601
- import fs12 from "fs";
15602
- import path10 from "path";
15603
- var cache2 = null;
15604
- var cachePath = null;
15605
- function getStorePath() {
15606
- const policy = getSandboxPolicy();
15607
- return path10.join(policy.workspaceRoot, ".bluma", "task_state.json");
15608
- }
15609
- function getDefaultState() {
15610
- return {
15611
- tasks: [],
15612
- nextId: 1,
15613
- activeTask: null,
15614
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15615
- };
15616
- }
15617
- function ensureLoaded() {
15618
- const storePath = getStorePath();
15619
- if (cache2 && cachePath === storePath) {
15620
- return cache2;
15621
- }
15622
- try {
15623
- if (fs12.existsSync(storePath)) {
15624
- const raw = fs12.readFileSync(storePath, "utf-8");
15625
- const parsed = JSON.parse(raw);
15626
- cache2 = {
15627
- tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
15628
- nextId: typeof parsed.nextId === "number" ? parsed.nextId : 1,
15629
- activeTask: parsed.activeTask ?? null,
15630
- updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
15631
- };
15632
- cachePath = storePath;
15633
- return cache2;
15634
- }
15635
- } catch {
15636
- }
15637
- cache2 = getDefaultState();
15638
- cachePath = storePath;
15639
- return cache2;
15640
- }
15641
- function persist(state2) {
15642
- const storePath = getStorePath();
15643
- fs12.mkdirSync(path10.dirname(storePath), { recursive: true });
15644
- state2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
15645
- fs12.writeFileSync(storePath, JSON.stringify(state2, null, 2), "utf-8");
15646
- cache2 = state2;
15647
- cachePath = storePath;
15648
- }
15649
- function updateTaskStore(mutator) {
15650
- const current = ensureLoaded();
15651
- const clone = {
15652
- tasks: current.tasks.map((task) => ({ ...task })),
15653
- nextId: current.nextId,
15654
- activeTask: current.activeTask ? { ...current.activeTask } : null,
15655
- updatedAt: current.updatedAt
15656
- };
15657
- mutator(clone);
15658
- persist(clone);
15659
- return clone;
15660
- }
15661
- function calculateTaskStats(tasks) {
15662
- const total = tasks.length;
15663
- const pending = tasks.filter((t) => t.status === "pending").length;
15664
- const inProgress = tasks.filter((t) => t.status === "in_progress").length;
15665
- const completed = tasks.filter((t) => t.status === "completed").length;
15666
- const progress = total > 0 ? Math.round(completed / total * 100) : 0;
15667
- return { total, pending, inProgress, completed, progress };
15668
- }
15669
- function buildTaskSnapshot() {
15670
- const state2 = ensureLoaded();
15671
- return {
15672
- tasks: state2.tasks.map((task) => ({ ...task })),
15673
- activeTask: state2.activeTask ? { ...state2.activeTask } : null,
15674
- stats: calculateTaskStats(state2.tasks),
15675
- updatedAt: state2.updatedAt
15676
- };
15677
- }
15678
- var activeWorkers = /* @__PURE__ */ new Map();
15679
- function evictWorkerTask(workerId) {
15680
- activeWorkers.delete(workerId);
15681
- }
15682
- function getVisibleWorkers() {
15683
- const now2 = Date.now();
15684
- return Array.from(activeWorkers.values()).filter(
15685
- (w) => (w.evictAfter ?? Infinity) > now2
15686
- );
15687
- }
15688
-
15689
15746
  // src/app/agent/tools/TodoTool/TodoTool.ts
15747
+ init_task_store();
15690
15748
  function validateDescription(desc) {
15691
15749
  if (!desc || typeof desc !== "string") return "Description is required";
15692
15750
  if (desc.trim().length === 0) return "Description cannot be empty";
@@ -16527,6 +16585,7 @@ init_CommandStatusTool();
16527
16585
 
16528
16586
  // src/app/agent/tools/TaskBoundaryTool/TaskBoundaryTool.ts
16529
16587
  init_sandbox_policy();
16588
+ init_task_store();
16530
16589
  import path14 from "path";
16531
16590
  import { promises as fs13 } from "fs";
16532
16591
  var artifactsDir = null;
@@ -16558,31 +16617,26 @@ async function updateTaskFile(task) {
16558
16617
  async function taskBoundary(args) {
16559
16618
  try {
16560
16619
  const { task_name, mode, task_status, task_summary } = args;
16620
+ const missingFields = [];
16561
16621
  if (!task_name || typeof task_name !== "string") {
16562
- return {
16563
- success: false,
16564
- task_name: "",
16565
- mode: "EXECUTION",
16566
- status: "",
16567
- message: "task_name is required"
16568
- };
16622
+ missingFields.push("task_name");
16569
16623
  }
16570
- if (!["PLANNING", "EXECUTION", "VERIFICATION"].includes(mode)) {
16571
- return {
16572
- success: false,
16573
- task_name,
16574
- mode: "EXECUTION",
16575
- status: "",
16576
- message: `Invalid mode: ${mode}. Must be PLANNING, EXECUTION, or VERIFICATION`
16577
- };
16624
+ if (!mode || !["PLANNING", "EXECUTION", "VERIFICATION"].includes(mode)) {
16625
+ missingFields.push("mode");
16578
16626
  }
16579
16627
  if (!task_status || typeof task_status !== "string") {
16628
+ missingFields.push("task_status");
16629
+ }
16630
+ if (!task_summary || typeof task_summary !== "string") {
16631
+ missingFields.push("task_summary");
16632
+ }
16633
+ if (missingFields.length > 0) {
16580
16634
  return {
16581
16635
  success: false,
16582
- task_name,
16583
- mode,
16584
- status: "",
16585
- message: "task_status is required"
16636
+ task_name: task_name || "",
16637
+ mode: mode || "EXECUTION",
16638
+ status: task_status || "",
16639
+ message: `Missing required fields: ${missingFields.join(", ")}. All 4 fields are required: task_name, mode, task_status, task_summary`
16586
16640
  };
16587
16641
  }
16588
16642
  const now2 = Date.now();
@@ -23483,53 +23537,6 @@ function disableCoordinatorMode() {
23483
23537
  delete process.env.BLUMA_COORDINATOR_MODE;
23484
23538
  }
23485
23539
 
23486
- // src/app/agent/core/prompt/tool_guidance.ts
23487
- var TOOL_GUIDANCE_RULES = [
23488
- {
23489
- toolName: "read_file_lines",
23490
- guidance: "To read files use `read_file_lines` instead of cat, head, tail, or sed"
23491
- },
23492
- {
23493
- toolName: "edit_tool",
23494
- guidance: "To edit files use `edit_tool` instead of sed or awk"
23495
- },
23496
- {
23497
- toolName: "file_write",
23498
- guidance: "To create files use `file_write` instead of cat with heredoc or echo redirection"
23499
- },
23500
- {
23501
- toolName: "grep_search",
23502
- guidance: "To search file content use `grep_search` instead of grep or rg"
23503
- },
23504
- {
23505
- toolName: "find_by_name",
23506
- guidance: "To search for files use `find_by_name` instead of find or ls"
23507
- },
23508
- {
23509
- toolName: "view_file_outline",
23510
- guidance: "To understand file structure use `view_file_outline` instead of opening the whole file"
23511
- }
23512
- ];
23513
- var ALWAYS_INCLUDED = [
23514
- "Reserve using `shell_command` exclusively for system commands and terminal operations that require shell execution. If you are unsure and there is a relevant dedicated tool, default to using the dedicated tool.",
23515
- "You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency.",
23516
- "Do NOT use `shell_command` to run commands when a relevant dedicated tool is provided. Using dedicated tools allows the user to better understand and review your work."
23517
- ];
23518
- function buildToolGuidanceSection(availableTools) {
23519
- const toolSet = new Set(availableTools);
23520
- const activeRules = TOOL_GUIDANCE_RULES.filter((rule) => toolSet.has(rule.toolName)).map((rule) => rule.guidance);
23521
- if (activeRules.length === 0) {
23522
- return "";
23523
- }
23524
- const lines = [
23525
- "# Using your tools",
23526
- "",
23527
- ...activeRules.map((r) => `- ${r}`),
23528
- ...ALWAYS_INCLUDED.map((r) => `- ${r}`)
23529
- ];
23530
- return lines.join("\n");
23531
- }
23532
-
23533
23540
  // src/app/agent/core/prompt/model_info.ts
23534
23541
  var KNOWLEDGE_CUTOFFS = [
23535
23542
  { match: "claude-sonnet-4-6", cutoff: "August 2025" },
@@ -23752,16 +23759,6 @@ function isAutoMemoryEnabled() {
23752
23759
  }
23753
23760
 
23754
23761
  // src/app/agent/core/prompt/prompt_builder.ts
23755
- var INTERNAL_TOOLS = /* @__PURE__ */ new Set([
23756
- "spawn_agent",
23757
- "wait_agent",
23758
- "list_agents",
23759
- "send_message",
23760
- "list_mailbox_messages",
23761
- "poll_mailbox",
23762
- "signal_mailbox",
23763
- "kill_agent"
23764
- ]);
23765
23762
  function getNodeVersion() {
23766
23763
  return process.version;
23767
23764
  }
@@ -24108,14 +24105,6 @@ ${formatFactorAiAppContextSummary(appContext)}
24108
24105
  if (modelInfo) prompt += `
24109
24106
 
24110
24107
  ${modelInfo}`;
24111
- const allToolNames = options?.availableToolNames ?? [];
24112
- if (allToolNames.length > 0) {
24113
- const visibleTools = isSandbox ? allToolNames : allToolNames.filter((n) => !INTERNAL_TOOLS.has(n));
24114
- const guidance = buildToolGuidanceSection(visibleTools);
24115
- if (guidance) prompt += `
24116
-
24117
- ${guidance}`;
24118
- }
24119
24108
  prompt += `
24120
24109
 
24121
24110
  ${buildSystemRemindersSection()}`;
@@ -25220,6 +25209,9 @@ var ToolCallNormalizer = class {
25220
25209
  }
25221
25210
  /**
25222
25211
  * Valida se um tool call normalizado Γ© vΓ‘lido
25212
+ * ValidaΓ§Γ£o dupla:
25213
+ * 1. JSON dos argumentos Γ© vΓ‘lido
25214
+ * 2. A ferramenta existe no catΓ‘logo de ferramentas nativas
25223
25215
  */
25224
25216
  static isValidToolCall(call) {
25225
25217
  if (!(call.id && call.type === "function" && call.function?.name && typeof call.function.arguments === "string")) {
@@ -25227,10 +25219,14 @@ var ToolCallNormalizer = class {
25227
25219
  }
25228
25220
  try {
25229
25221
  JSON.parse(call.function.arguments);
25230
- return true;
25231
25222
  } catch {
25232
25223
  return false;
25233
25224
  }
25225
+ const toolMetadata = getNativeToolMetadata(call.function.name);
25226
+ if (!toolMetadata) {
25227
+ return false;
25228
+ }
25229
+ return true;
25234
25230
  }
25235
25231
  };
25236
25232
 
@@ -25277,7 +25273,7 @@ function checkFilePermissionRules(toolName, filePath, policy) {
25277
25273
  }
25278
25274
  return { allowed: false, reason: `No permission rule found for file "${filePath}".` };
25279
25275
  }
25280
- function decideToolExecution(toolName, args) {
25276
+ function decideToolExecution(toolName, args, sessionId) {
25281
25277
  const policy = getSandboxPolicy();
25282
25278
  const metadata = getNativeToolMetadata(toolName);
25283
25279
  if (!metadata) {
@@ -25288,6 +25284,15 @@ function decideToolExecution(toolName, args) {
25288
25284
  reason: "Unknown tool metadata; require confirmation by default."
25289
25285
  };
25290
25286
  }
25287
+ if (sessionId && getEffectivePermissionMode(sessionId) === "full") {
25288
+ return {
25289
+ toolName,
25290
+ metadata,
25291
+ autoApprove: true,
25292
+ requiresConfirmation: false,
25293
+ reason: "Full permission mode: ALL tools auto-approved. User explicitly enabled unrestricted mode."
25294
+ };
25295
+ }
25291
25296
  const ruleDecision = permissionRulesEngine.checkPermission(toolName, args);
25292
25297
  if (ruleDecision === "deny") {
25293
25298
  return {
@@ -25560,10 +25565,10 @@ var PARALLEL_SAFE_TOOL_NAMES = /* @__PURE__ */ new Set([
25560
25565
  "read_mcp_resource",
25561
25566
  "todo"
25562
25567
  ]);
25563
- function toolEligibleForParallelRead(toolName) {
25568
+ function toolEligibleForParallelRead(toolName, sessionId) {
25564
25569
  const name = String(toolName || "").trim();
25565
25570
  if (!name || !PARALLEL_SAFE_TOOL_NAMES.has(name)) return false;
25566
- if (!decideToolExecution(name).autoApprove) return false;
25571
+ if (!decideToolExecution(name, void 0, sessionId).autoApprove) return false;
25567
25572
  const meta = getNativeToolMetadata(name);
25568
25573
  return meta?.riskLevel === "safe";
25569
25574
  }
@@ -25869,7 +25874,7 @@ var BluMaAgent = class _BluMaAgent {
25869
25874
  }
25870
25875
  /** PolΓ­tica mostrada na UI: combina metadata base com auto-approve efectivo (incl. permission_ml). */
25871
25876
  buildToolPolicyForUi(toolName, toolArgs) {
25872
- const base = decideToolExecution(toolName);
25877
+ const base = decideToolExecution(toolName, void 0, this.sessionId);
25873
25878
  const argsStr = typeof toolArgs === "string" ? toolArgs : JSON.stringify(toolArgs ?? {});
25874
25879
  const synthetic = { function: { name: toolName, arguments: argsStr } };
25875
25880
  const auto = effectiveToolAutoApprove(synthetic, this.sessionId, { logClassifier: false });
@@ -25929,10 +25934,6 @@ var BluMaAgent = class _BluMaAgent {
25929
25934
  role: "system",
25930
25935
  content: `[SYSTEM] Tool "${toolName}" is not found in the agent's available tools. Please use only tools that are listed in your tool catalog.`
25931
25936
  });
25932
- this.eventBus.emit("backend_message", {
25933
- type: "error",
25934
- message: `Tool "${toolName}" does not exist in the agent catalog.`
25935
- });
25936
25937
  this.persistSession();
25937
25938
  return true;
25938
25939
  }
@@ -26119,10 +26120,6 @@ var BluMaAgent = class _BluMaAgent {
26119
26120
  role: "system",
26120
26121
  content: `[SYSTEM] Tool "${toolName}" is not found in the agent's available tools. Please use only tools that are listed in your tool catalog.`
26121
26122
  });
26122
- this.eventBus.emit("backend_message", {
26123
- type: "error",
26124
- message: `Tool "${toolName}" does not exist in the agent catalog.`
26125
- });
26126
26123
  this.persistSession();
26127
26124
  return true;
26128
26125
  }
@@ -26271,13 +26268,13 @@ var BluMaAgent = class _BluMaAgent {
26271
26268
  let i = 0;
26272
26269
  while (i < calls.length && shouldContinue && !this.isInterrupted) {
26273
26270
  const name = calls[i].function.name;
26274
- if (!toolEligibleForParallelRead(name)) {
26271
+ if (!toolEligibleForParallelRead(name, this.sessionId)) {
26275
26272
  shouldContinue = await this.executeApprovedToolInvocation(calls[i], decisionData);
26276
26273
  i += 1;
26277
26274
  continue;
26278
26275
  }
26279
26276
  let j = i;
26280
- while (j < calls.length && toolEligibleForParallelRead(calls[j].function.name)) {
26277
+ while (j < calls.length && toolEligibleForParallelRead(calls[j].function.name, this.sessionId)) {
26281
26278
  j += 1;
26282
26279
  }
26283
26280
  const slice = calls.slice(i, j);
@@ -26565,7 +26562,7 @@ ${editData.error.display}`;
26565
26562
  }
26566
26563
  if (confirmationToolCalls.length > 0) {
26567
26564
  const toolToCall = confirmationToolCalls[0];
26568
- const decision = decideToolExecution(toolToCall.function.name);
26565
+ const decision = decideToolExecution(toolToCall.function.name, void 0, this.sessionId);
26569
26566
  const toolName = toolToCall.function.name;
26570
26567
  let previewContent;
26571
26568
  if (toolName === "edit_tool") {
@@ -26640,7 +26637,7 @@ ${editData.error.display}`;
26640
26637
  }
26641
26638
  if (confirmationToolCalls.length > 0) {
26642
26639
  const toolToCall = confirmationToolCalls[0];
26643
- const decision = decideToolExecution(toolToCall.function.name);
26640
+ const decision = decideToolExecution(toolToCall.function.name, void 0, this.sessionId);
26644
26641
  const toolName = toolToCall.function.name;
26645
26642
  let previewContent;
26646
26643
  if (toolName === "edit_tool") {
@@ -27588,7 +27585,7 @@ var BaseLLMSubAgent = class {
27588
27585
  break;
27589
27586
  }
27590
27587
  if (message2.tool_calls) {
27591
- const decision = decideToolExecution(message2.tool_calls[0].function.name);
27588
+ const decision = decideToolExecution(message2.tool_calls[0].function.name, void 0, `${this.id}`);
27592
27589
  if (!decision.autoApprove) {
27593
27590
  this.emitEvent("error", {
27594
27591
  message: `Subagent tool "${message2.tool_calls[0].function.name}" requires confirmation outside sandbox mode.`
@@ -27712,7 +27709,7 @@ ${editData.error.display}`;
27712
27709
  }
27713
27710
  }
27714
27711
  if (message2.tool_calls) {
27715
- const decision = decideToolExecution(message2.tool_calls[0].function.name);
27712
+ const decision = decideToolExecution(message2.tool_calls[0].function.name, void 0, `${this.id}`);
27716
27713
  if (!decision.autoApprove) {
27717
27714
  this.emitEvent("error", {
27718
27715
  message: `Subagent tool "${message2.tool_calls[0].function.name}" requires confirmation outside sandbox mode.`
@@ -27762,7 +27759,7 @@ ${editData.error.display}`;
27762
27759
  tool_name: toolName,
27763
27760
  arguments: toolArgs,
27764
27761
  preview: previewContent,
27765
- tool_policy: decideToolExecution(toolName)
27762
+ tool_policy: decideToolExecution(toolName, void 0, `${this.id}`)
27766
27763
  });
27767
27764
  try {
27768
27765
  if (this.isInterrupted) {
@@ -35533,6 +35530,9 @@ function useTerminalSize() {
35533
35530
  return size;
35534
35531
  }
35535
35532
 
35533
+ // src/app/ui/components/CoordinatorTaskPanel.tsx
35534
+ init_task_store();
35535
+
35536
35536
  // src/utils/format.ts
35537
35537
  function formatDuration2(ms, options) {
35538
35538
  if (ms < 6e4) {
@@ -36110,6 +36110,7 @@ var renderBridgePanel = () => {
36110
36110
  };
36111
36111
 
36112
36112
  // src/app/ui/components/slash-commands/renderers/taskRenderers.tsx
36113
+ init_task_store();
36113
36114
  import { Fragment as Fragment13, jsx as jsx87, jsxs as jsxs72 } from "react/jsx-runtime";
36114
36115
  var renderMasonSnapshot = () => {
36115
36116
  const snapshot = buildTaskSnapshot();
@@ -36227,6 +36228,7 @@ var runTasksClear = (agentRef) => {
36227
36228
  init_runtime_config();
36228
36229
  init_sessionPermissionState();
36229
36230
  init_sandbox_policy();
36231
+ init_task_store();
36230
36232
  import { Fragment as Fragment14, jsx as jsx88, jsxs as jsxs73 } from "react/jsx-runtime";
36231
36233
  var renderStatusline = () => {
36232
36234
  const cfg = getRuntimeConfig();
@@ -36420,10 +36422,10 @@ var runWorktreeSet = (_agentRef, _path, _setIsProcessing, _markTurnStarted) => {
36420
36422
  var runPermissionSet = (_agentRef, mode, _setIsProcessing, _markTurnStarted, sessionId) => {
36421
36423
  if (mode === "full") {
36422
36424
  if (sessionId) {
36423
- setSessionPermissionMode(sessionId, "accept_edits");
36425
+ setSessionPermissionMode(sessionId, "full");
36424
36426
  return usageBox("Permission", "Set to full");
36425
36427
  } else {
36426
- setRuntimeConfig({ permissionMode: "accept_edits", sandboxEnabled: false });
36428
+ setRuntimeConfig({ permissionMode: "full", sandboxEnabled: false });
36427
36429
  return usageBox("Permission", "Set to full (global)");
36428
36430
  }
36429
36431
  }
@@ -36474,6 +36476,7 @@ var renderPermissionsSnapshot = (sessionId) => {
36474
36476
  };
36475
36477
 
36476
36478
  // src/app/agent/runtime/diagnostics.ts
36479
+ init_task_store();
36477
36480
  init_runtime_config();
36478
36481
  init_sandbox_policy();
36479
36482
  init_session_registry();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",