@nomad-e/bluma-cli 0.1.74 → 0.1.75
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 +115 -27
- package/dist/config/native_tools.json +27 -4
- package/dist/main.js +359 -137
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
> **Credit:** BluMa was conceived and architected by **Alex Fonseca**.
|
|
13
13
|
|
|
14
|
-
**Current Version:** 0.1.
|
|
14
|
+
**Current Version:** 0.1.74
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -42,7 +42,7 @@ BluMa operates as a **conversational agent** in the terminal, combining:
|
|
|
42
42
|
- **Rich UI Layer**: React/Ink 5 components for interactive prompts, live overlays, and real-time feedback
|
|
43
43
|
- **Agent Layer**: LLM orchestration via FactorRouter with tool invocation and context management
|
|
44
44
|
- **Runtime Layer**: Task tracking, plugin system, hooks, diagnostics, session management, and coordinator mode
|
|
45
|
-
- **Tool Layer**:
|
|
45
|
+
- **Tool Layer**: 45+ native tools + MCP SDK integration for external tools
|
|
46
46
|
|
|
47
47
|
The agent maintains persistent conversation history, workspace snapshots, and coding memory across sessions.
|
|
48
48
|
|
|
@@ -67,7 +67,7 @@ The agent maintains persistent conversation history, workspace snapshots, and co
|
|
|
67
67
|
- **Tool Execution Policy**: Intelligent decisions based on sandbox mode and safety
|
|
68
68
|
|
|
69
69
|
### Tools & Skills
|
|
70
|
-
- **
|
|
70
|
+
- **45+ Native Tools**: File operations, search, shell commands, web fetch, agent coordination, task management, MCP resources, cron scheduling, LSP queries, notebook editing
|
|
71
71
|
- **Coordinator Mode**: Orchestrator playbook for delegating work to background workers
|
|
72
72
|
- **MCP Integration**: Model Context Protocol SDK for external tool servers
|
|
73
73
|
- **Skills System**: Pluggable knowledge modules (git, PDF, Excel, etc.)
|
|
@@ -237,7 +237,7 @@ npm start
|
|
|
237
237
|
┌────────────────────────┼────────────────────────────────────┐
|
|
238
238
|
│ Tools Layer │
|
|
239
239
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
240
|
-
│ │ Native Tools (
|
|
240
|
+
│ │ Native Tools (45+) │ │
|
|
241
241
|
│ │ edit_tool, file_write, shell_command, grep_search, │ │
|
|
242
242
|
│ │ spawn_agent, wait_agent, list_agents, │ │
|
|
243
243
|
│ │ todo, task_boundary, task_create, task_list, │ │
|
|
@@ -260,7 +260,7 @@ npm start
|
|
|
260
260
|
|
|
261
261
|
## Native Tools
|
|
262
262
|
|
|
263
|
-
BluMa includes **
|
|
263
|
+
BluMa includes **45+ built-in tools** organized by category:
|
|
264
264
|
|
|
265
265
|
### File Operations
|
|
266
266
|
| Tool | Description |
|
|
@@ -341,6 +341,18 @@ BluMa includes **40+ built-in tools** organized by category:
|
|
|
341
341
|
| `repl` | Interactive code execution (Python, Node, Bash) |
|
|
342
342
|
| `task_output` | Read task output in real-time |
|
|
343
343
|
|
|
344
|
+
### Specialized
|
|
345
|
+
| Tool | Description |
|
|
346
|
+
|------|-------------|
|
|
347
|
+
| `context_collapse` | Collapse context window |
|
|
348
|
+
| `dream_engine` | Auto-dream feature (coding memory consolidation) |
|
|
349
|
+
| `brief` | Generate project brief |
|
|
350
|
+
| `ctx_inspect` | Inspect current context |
|
|
351
|
+
| `snip` | Code snippet management |
|
|
352
|
+
| `coordinator_tools` | Coordinator mode utilities |
|
|
353
|
+
| `create-next-app` | Scaffold Next.js project with shadcn/ui + Tailwind |
|
|
354
|
+
| `deploy-app` | Deploy Next.js to Severino hosting |
|
|
355
|
+
|
|
344
356
|
### Plan Mode
|
|
345
357
|
| Tool | Description |
|
|
346
358
|
|------|-------------|
|
|
@@ -349,7 +361,7 @@ BluMa includes **40+ built-in tools** organized by category:
|
|
|
349
361
|
|
|
350
362
|
---
|
|
351
363
|
|
|
352
|
-
##
|
|
364
|
+
## Current Features
|
|
353
365
|
|
|
354
366
|
### Mailbox System (Bidirectional Communication)
|
|
355
367
|
**Push-based communication** between coordinator and workers using EventEmitter:
|
|
@@ -570,6 +582,7 @@ Level 3b: scripts/*.py
|
|
|
570
582
|
|-------|-------------|
|
|
571
583
|
| `git-commit` | Conventional commits, staging, commit messages |
|
|
572
584
|
| `git-pr` | Pull requests, code review preparation |
|
|
585
|
+
| `git-release` | Version bumps, changelogs, git tags, GitHub releases |
|
|
573
586
|
| `pdf` | PDF creation, extraction, merging, OCR |
|
|
574
587
|
| `xlsx` | Spreadsheet operations, formulas, charts |
|
|
575
588
|
| `skill-creator` | Author new BluMa skills |
|
|
@@ -765,11 +778,29 @@ src/
|
|
|
765
778
|
│ │ ├── agent.ts # Main orchestrator
|
|
766
779
|
│ │ ├── bluma/ # Core agent logic
|
|
767
780
|
│ │ │ ├── core/
|
|
768
|
-
│ │ │ │
|
|
769
|
-
│ │ │ └── turn_start_payload.ts
|
|
781
|
+
│ │ │ │ ├── bluma.ts # BluMaAgent class
|
|
782
|
+
│ │ │ │ └── turn_start_payload.ts
|
|
783
|
+
│ │ │ ├── context/
|
|
784
|
+
│ │ │ │ └── auto_compact.ts # Automatic context compaction
|
|
785
|
+
│ │ │ ├── memory/
|
|
786
|
+
│ │ │ │ └── session_memory.ts # Session memory
|
|
787
|
+
│ │ │ └── types/
|
|
788
|
+
│ │ │ └── errors.ts # Error type definitions
|
|
770
789
|
│ │ ├── config/
|
|
771
|
-
│ │ │ ├── native_tools.json # Tool definitions (
|
|
772
|
-
│ │ │
|
|
790
|
+
│ │ │ ├── native_tools.json # Tool definitions (45+)
|
|
791
|
+
│ │ │ ├── skills/ # Bundled skills
|
|
792
|
+
│ │ │ │ ├── git-commit/
|
|
793
|
+
│ │ │ │ ├── git-pr/
|
|
794
|
+
│ │ │ │ ├── git-release/ # Version bumps, changelogs, releases
|
|
795
|
+
│ │ │ │ ├── pdf/
|
|
796
|
+
│ │ │ │ ├── skill-creator/
|
|
797
|
+
│ │ │ │ └── xlsx/
|
|
798
|
+
│ │ │ │ └── scripts/
|
|
799
|
+
│ │ │ │ └── office/ # Office document handling
|
|
800
|
+
│ │ │ │ ├── pack.py
|
|
801
|
+
│ │ │ │ ├── unpack.py
|
|
802
|
+
│ │ │ │ ├── validate.py
|
|
803
|
+
│ │ │ │ └── soffice.py
|
|
773
804
|
│ │ ├── core/ # LLM, context, prompts
|
|
774
805
|
│ │ │ ├── context-api/ # Context management
|
|
775
806
|
│ │ │ │ ├── context_manager.ts # Token-aware context
|
|
@@ -777,17 +808,32 @@ src/
|
|
|
777
808
|
│ │ │ │ └── token_counter.ts # Tiktoken integration
|
|
778
809
|
│ │ │ ├── llm/ # LLM client
|
|
779
810
|
│ │ │ │ ├── llm.ts # FactorRouter client
|
|
811
|
+
│ │ │ │ ├── llm_errors.ts # LLM error formatting
|
|
812
|
+
│ │ │ │ ├── streaming_delta.ts # Streaming delta handling
|
|
780
813
|
│ │ │ │ └── tool_call_normalizer.ts
|
|
781
|
-
│ │ │
|
|
782
|
-
│ │ │
|
|
783
|
-
│ │ │
|
|
814
|
+
│ │ │ ├── prompt/ # Prompt engineering
|
|
815
|
+
│ │ │ │ ├── prompt_builder.ts # Dynamic prompts
|
|
816
|
+
│ │ │ │ ├── workspace_snapshot.ts
|
|
817
|
+
│ │ │ │ ├── mcp_instructions.ts
|
|
818
|
+
│ │ │ │ ├── model_info.ts
|
|
819
|
+
│ │ │ │ ├── production_sandbox_prompt.ts
|
|
820
|
+
│ │ │ │ ├── system_prompt_sections.ts
|
|
821
|
+
│ │ │ │ ├── system_reminders.ts
|
|
822
|
+
│ │ │ │ └── tool_guidance.ts
|
|
823
|
+
│ │ │ └── context_viz.ts # Context visualization
|
|
824
|
+
│ │ ├── docs/
|
|
825
|
+
│ │ │ └── TOOL_PARITY.md # Tool parity documentation
|
|
784
826
|
│ │ ├── feedback/
|
|
785
827
|
│ │ │ └── feedback_system.ts # Smart feedback system
|
|
828
|
+
│ │ │ └── feedback_scoring.ts # User feedback scoring
|
|
786
829
|
│ │ ├── runtime/ # Orchestration layer (v0.1.41+)
|
|
787
830
|
│ │ │ ├── diagnostics.ts # System snapshots
|
|
788
831
|
│ │ │ ├── feature_flags.ts # Feature gates
|
|
789
832
|
│ │ │ ├── hook_registry.ts # Event-driven hooks
|
|
790
833
|
│ │ │ ├── native_tool_catalog.ts # Tool registry
|
|
834
|
+
│ │ │ ├── permission_bridge.ts # Leader↔Worker permission system
|
|
835
|
+
│ │ │ ├── permission_rules.ts # Permission rule definitions
|
|
836
|
+
│ │ │ ├── tool_permission_classifier.ts
|
|
791
837
|
│ │ │ ├── plan_mode_session.ts # Plan mode state
|
|
792
838
|
│ │ │ ├── plugin_registry.ts # Plugin system
|
|
793
839
|
│ │ │ ├── plugin_runtime.ts # Plugin execution
|
|
@@ -799,21 +845,29 @@ src/
|
|
|
799
845
|
│ │ │ ├── tool_auto_approve.ts # Auto-approve rules
|
|
800
846
|
│ │ │ ├── tool_execution_policy.ts
|
|
801
847
|
│ │ │ ├── tool_orchestration.ts # Parallel read eligibility
|
|
802
|
-
│ │ │
|
|
848
|
+
│ │ │ ├── mailbox_registry.ts # Mailbox system
|
|
849
|
+
│ │ │ ├── worker_context.ts # Worker context management
|
|
850
|
+
│ │ │ └── factorai_context.ts # FactorAI app context
|
|
803
851
|
│ │ ├── session_manager/
|
|
804
852
|
│ │ │ └── session_manager.ts # Session persistence
|
|
805
853
|
│ │ ├── skills/
|
|
806
|
-
│ │ │ └── skill_loader.ts #
|
|
854
|
+
│ │ │ └── skill_loader.ts # 3-source skill loading
|
|
807
855
|
│ │ ├── subagents/ # Subagent system
|
|
808
856
|
│ │ │ ├── base_llm_subagent.ts
|
|
809
857
|
│ │ │ ├── init/ # Init subagent (BluMa.md)
|
|
858
|
+
│ │ │ │ ├── init_subagent.ts # Deep project analysis
|
|
859
|
+
│ │ │ │ ├── init_system_prompt.ts
|
|
860
|
+
│ │ │ │ └── contracts.ts
|
|
810
861
|
│ │ │ ├── registry.ts
|
|
811
|
-
│ │ │ ├── subagents_bluma.ts
|
|
812
|
-
│ │ │
|
|
862
|
+
│ │ │ ├── subagents_bluma.ts # SubAgent orchestration
|
|
863
|
+
│ │ │ ├── types.ts # SubAgent type definitions
|
|
864
|
+
│ │ │ └── worker_system_prompt.ts
|
|
813
865
|
│ │ ├── tools/
|
|
814
866
|
│ │ │ ├── mcp/
|
|
815
867
|
│ │ │ │ └── mcp_client.ts # MCP SDK client
|
|
816
|
-
│ │ │
|
|
868
|
+
│ │ │ ├── shared/
|
|
869
|
+
│ │ │ │ └── token_utils.ts # Token utilities
|
|
870
|
+
│ │ │ └── natives/ # 45+ native tool implementations
|
|
817
871
|
│ │ │ ├── agent_coordination.ts
|
|
818
872
|
│ │ │ ├── ask_user_question.ts
|
|
819
873
|
│ │ │ ├── async_command.ts
|
|
@@ -844,29 +898,35 @@ src/
|
|
|
844
898
|
│ │ ├── types/
|
|
845
899
|
│ │ │ └── index.ts
|
|
846
900
|
│ │ └── utils/
|
|
901
|
+
│ │ ├── blumamd.ts # BluMa markdown utilities
|
|
847
902
|
│ │ ├── coordinator_prompt.ts # Coordinator mode playbook
|
|
903
|
+
│ │ ├── logger.ts # Logging utilities
|
|
848
904
|
│ │ ├── update_check.ts
|
|
849
905
|
│ │ └── user_message_images.ts
|
|
850
906
|
│ └── ui/
|
|
851
907
|
│ ├── App.tsx # Main UI component
|
|
852
908
|
│ ├── Asci/
|
|
853
909
|
│ │ └── AsciiArt.ts
|
|
854
|
-
│ ├── components/ #
|
|
910
|
+
│ ├── components/ # 25+ UI components
|
|
855
911
|
│ │ ├── AnimatedBorder.tsx
|
|
856
912
|
│ │ ├── AskUserQuestionPrompt.tsx
|
|
857
913
|
│ │ ├── AssistantMessageDisplay.tsx
|
|
858
914
|
│ │ ├── CollapsibleResult.tsx
|
|
915
|
+
│ │ ├── ConfirmationPrompt.tsx # Permission confirmation dialog
|
|
859
916
|
│ │ ├── EditToolDiffPanel.tsx # Diff preview for edits
|
|
860
917
|
│ │ ├── ErrorMessage.tsx
|
|
861
918
|
│ │ ├── ExpandedPreviewBlock.tsx
|
|
862
919
|
│ │ ├── InputPrompt.tsx # User input
|
|
920
|
+
│ │ ├── InteractiveMenu.tsx # Interactive menu component
|
|
863
921
|
│ │ ├── MarkdownRenderer.tsx
|
|
864
922
|
│ │ ├── ProgressBar.tsx
|
|
865
923
|
│ │ ├── ReasoningDisplay.tsx # LLM reasoning
|
|
924
|
+
│ │ │ ├── SessionInfoConnectingMCP.tsx
|
|
866
925
|
│ │ ├── SessionStats.tsx
|
|
867
926
|
│ │ ├── SimpleDiff.tsx
|
|
868
927
|
│ │ ├── SlashCommands.tsx # 30+ commands
|
|
869
928
|
│ │ ├── StatusNotification.tsx
|
|
929
|
+
│ │ ├── StartupUpdateGate.tsx # Update check gate
|
|
870
930
|
│ │ ├── StreamingText.tsx # Live text output
|
|
871
931
|
│ │ ├── TodoPlanDisplay.tsx # Task visualization
|
|
872
932
|
│ │ ├── ToolCallDisplay.tsx
|
|
@@ -875,17 +935,41 @@ src/
|
|
|
875
935
|
│ │ ├── ToolResultDisplay.tsx
|
|
876
936
|
│ │ ├── TypewriterText.tsx
|
|
877
937
|
│ │ ├── UpdateNotice.tsx
|
|
938
|
+
│ │ ├── WorkerOverlay.tsx # Worker status overlay
|
|
939
|
+
│ │ ├── WorkerStatusList.tsx # Active workers list
|
|
940
|
+
│ │ ├── WorkerTranscript.tsx # Worker conversation transcript
|
|
941
|
+
│ │ ├── WorkingTimer.tsx # Work duration timer
|
|
878
942
|
│ │ ├── streamingTextFlush.ts
|
|
879
943
|
│ │ └── toolCallRenderers.tsx
|
|
880
944
|
│ ├── constants/
|
|
881
|
-
│ │ ├── historyLayout.ts
|
|
882
|
-
│ │ ├── inputPaste.ts
|
|
883
|
-
│ │ └── toolUiPreview.ts
|
|
945
|
+
│ │ ├── historyLayout.ts # History layout constants
|
|
946
|
+
│ │ ├── inputPaste.ts # Input paste constants
|
|
947
|
+
│ │ └── toolUiPreview.ts # Tool UI preview constants
|
|
948
|
+
│ │ └── toolUiSymbols.ts # Tool UI symbols
|
|
884
949
|
│ ├── hooks/
|
|
885
|
-
│ │
|
|
950
|
+
│ │ ├── useAtCompletion.ts # Completion hook
|
|
951
|
+
│ │ └── useWorkerProgress.ts # Worker progress hook
|
|
952
|
+
│ ├── prompts/
|
|
953
|
+
│ │ └── initCommandPrompt.ts # Init command prompt
|
|
954
|
+
│ ├── slash-commands/
|
|
955
|
+
│ │ ├── SlashCommands.types.ts # Type definitions
|
|
956
|
+
│ │ ├── commandHelpers.tsx # Command helpers
|
|
957
|
+
│ │ ├── constants.ts # Slash command constants
|
|
958
|
+
│ │ ├── SessionLivePanel.tsx # Session live panel
|
|
959
|
+
│ │ ├── streamingTextFlush.ts # Streaming text flush
|
|
960
|
+
│ │ └── renderers/
|
|
961
|
+
│ │ ├── index.ts
|
|
962
|
+
│ │ ├── configRenderers.tsx
|
|
963
|
+
│ │ ├── infoRenderers.tsx
|
|
964
|
+
│ │ ├── permissionRenderers.tsx
|
|
965
|
+
│ │ ├── pluginRenderers.tsx
|
|
966
|
+
│ │ ├── sessionRenderers.tsx
|
|
967
|
+
│ │ ├── staticRenderers.tsx
|
|
968
|
+
│ │ └── taskRenderers.tsx
|
|
886
969
|
│ ├── theme/
|
|
887
970
|
│ │ ├── blumaTerminal.ts
|
|
888
|
-
│ │
|
|
971
|
+
│ │ ├── themes.ts # Theme definitions
|
|
972
|
+
│ │ └── m3Layout.tsx # Material Design 3 layout
|
|
889
973
|
│ └── utils/
|
|
890
974
|
│ ├── clipboardImage.ts
|
|
891
975
|
│ ├── editToolDiffUtils.ts
|
|
@@ -896,8 +980,9 @@ src/
|
|
|
896
980
|
│ ├── pathDisplay.ts
|
|
897
981
|
│ ├── shellToolNames.ts
|
|
898
982
|
│ ├── slashRegistry.ts
|
|
899
|
-
│ ├── terminalTitle.ts
|
|
900
|
-
│ ├──
|
|
983
|
+
│ ├── terminalTitle.ts # Terminal title keeper
|
|
984
|
+
│ ├── toolActionLabels.ts # Tool action labels
|
|
985
|
+
│ ├── toolDisplayLabels.ts # Tool display labels
|
|
901
986
|
│ ├── toolInvocationPairing.ts
|
|
902
987
|
│ └── useSimpleInputBuffer.ts
|
|
903
988
|
├── main.ts # Entry point
|
|
@@ -917,7 +1002,7 @@ npm run test:watch # Watch mode
|
|
|
917
1002
|
### Test Structure
|
|
918
1003
|
|
|
919
1004
|
```
|
|
920
|
-
tests/ #
|
|
1005
|
+
tests/ # 35+ test files (flat structure)
|
|
921
1006
|
├── agent_*.spec.ts # Agent routing, overlays, coordination
|
|
922
1007
|
├── edit_tool.spec.ts # File editing operations
|
|
923
1008
|
├── file_write.spec.ts # File write operations
|
|
@@ -939,6 +1024,9 @@ tests/ # 33 test files (flat structure)
|
|
|
939
1024
|
├── web_fetch.spec.ts # Web fetching
|
|
940
1025
|
├── workspace_snapshot.spec.ts # Workspace analysis
|
|
941
1026
|
├── ui_*.spec.ts(x) # UI component tests
|
|
1027
|
+
├── llm_stream_partial.spec.ts # LLM streaming partial handling
|
|
1028
|
+
├── llm_errors.spec.ts # LLM error handling
|
|
1029
|
+
├── jest-resolver.cjs # Jest resolver configuration
|
|
942
1030
|
└── ... # Additional integration and unit tests
|
|
943
1031
|
```
|
|
944
1032
|
|
|
@@ -636,13 +636,13 @@
|
|
|
636
636
|
"type": "function",
|
|
637
637
|
"function": {
|
|
638
638
|
"name": "search_web",
|
|
639
|
-
"description": "Search for programming solutions across developer-focused sources: Reddit (programming subreddits), GitHub (issues/discussions), StackOverflow, and X (link only). Use this to find solutions, best practices, and community discussions about errors or implementation approaches.",
|
|
639
|
+
"description": "Search for programming solutions across developer-focused sources: Reddit (programming subreddits), GitHub (issues/discussions), StackOverflow, and X (link only). Use this to find solutions, best practices, and community discussions about errors or implementation approaches. NEW: Supports domain filtering and rate limiting.",
|
|
640
640
|
"parameters": {
|
|
641
641
|
"type": "object",
|
|
642
642
|
"properties": {
|
|
643
643
|
"query": {
|
|
644
644
|
"type": "string",
|
|
645
|
-
"description": "Search query, e.g. 'typescript cannot find module', 'react useEffect infinite loop fix'"
|
|
645
|
+
"description": "Search query, e.g. 'typescript cannot find module', 'react useEffect infinite loop fix'. Must be 2-500 characters."
|
|
646
646
|
},
|
|
647
647
|
"sources": {
|
|
648
648
|
"type": "array",
|
|
@@ -664,8 +664,31 @@
|
|
|
664
664
|
},
|
|
665
665
|
"max_results": {
|
|
666
666
|
"type": "integer",
|
|
667
|
-
"description": "Maximum total results to return. Default is 10.",
|
|
668
|
-
"default": 10
|
|
667
|
+
"description": "Maximum total results to return. Default is 10, max is 50.",
|
|
668
|
+
"default": 10,
|
|
669
|
+
"maximum": 50,
|
|
670
|
+
"minimum": 1
|
|
671
|
+
},
|
|
672
|
+
"allowed_domains": {
|
|
673
|
+
"type": "array",
|
|
674
|
+
"items": {
|
|
675
|
+
"type": "string"
|
|
676
|
+
},
|
|
677
|
+
"description": "Only include search results from these domains (e.g., ['github.com', 'reddit.com']). Cannot be used with blocked_domains."
|
|
678
|
+
},
|
|
679
|
+
"blocked_domains": {
|
|
680
|
+
"type": "array",
|
|
681
|
+
"items": {
|
|
682
|
+
"type": "string"
|
|
683
|
+
},
|
|
684
|
+
"description": "Never include search results from these domains (e.g., ['spam-site.com']). Cannot be used with allowed_domains."
|
|
685
|
+
},
|
|
686
|
+
"max_uses": {
|
|
687
|
+
"type": "integer",
|
|
688
|
+
"description": "Maximum number of sources to search (rate limiting). Default is 8, max is 20.",
|
|
689
|
+
"default": 8,
|
|
690
|
+
"maximum": 20,
|
|
691
|
+
"minimum": 1
|
|
669
692
|
}
|
|
670
693
|
},
|
|
671
694
|
"required": [
|
package/dist/main.js
CHANGED
|
@@ -327,7 +327,7 @@ var init_sandbox_policy = __esm({
|
|
|
327
327
|
BLOCKED_COMMAND_PATTERNS = [
|
|
328
328
|
{ pattern: /\bsudo\b/, reason: "Privilege escalation is not allowed." },
|
|
329
329
|
{ pattern: /\bsu\b\s/, reason: "User switching is not allowed." },
|
|
330
|
-
{ pattern:
|
|
330
|
+
{ pattern: /rm\s+-rf\s+\/\s*$/, reason: "Deleting root filesystem is blocked." },
|
|
331
331
|
{ pattern: /\bcurl\b.*\|\s*(bash|sh|zsh)/i, reason: "Pipe-to-shell execution is blocked." },
|
|
332
332
|
{ pattern: /\bwget\b.*\|\s*(bash|sh|zsh)/i, reason: "Pipe-to-shell execution is blocked." },
|
|
333
333
|
{ pattern: /\beval\b\s*\(/, reason: "Eval execution is blocked." },
|
|
@@ -6980,9 +6980,106 @@ async function readArtifact(args) {
|
|
|
6980
6980
|
import https from "https";
|
|
6981
6981
|
import http from "http";
|
|
6982
6982
|
var DEFAULT_SOURCES = ["reddit", "github", "stackoverflow"];
|
|
6983
|
-
var MAX_RESULTS_DEFAULT =
|
|
6983
|
+
var MAX_RESULTS_DEFAULT = 10;
|
|
6984
|
+
var MAX_USES_DEFAULT = 8;
|
|
6984
6985
|
var REQUEST_TIMEOUT = 15e3;
|
|
6985
6986
|
var MAX_CONTENT_LENGTH = 4e3;
|
|
6987
|
+
function validateInput(args) {
|
|
6988
|
+
if (!args.query || typeof args.query !== "string") {
|
|
6989
|
+
return { valid: false, error: "query is required and must be a string" };
|
|
6990
|
+
}
|
|
6991
|
+
if (args.query.trim().length < 2) {
|
|
6992
|
+
return { valid: false, error: "query must be at least 2 characters long" };
|
|
6993
|
+
}
|
|
6994
|
+
if (args.query.length > 500) {
|
|
6995
|
+
return { valid: false, error: "query must be less than 500 characters" };
|
|
6996
|
+
}
|
|
6997
|
+
if (args.allowed_domains && args.blocked_domains && args.allowed_domains.length > 0 && args.blocked_domains.length > 0) {
|
|
6998
|
+
return {
|
|
6999
|
+
valid: false,
|
|
7000
|
+
error: "Cannot specify both allowed_domains and blocked_domains in the same request"
|
|
7001
|
+
};
|
|
7002
|
+
}
|
|
7003
|
+
if (args.sources && args.sources.length > 0) {
|
|
7004
|
+
const validSources = ["reddit", "github", "stackoverflow", "x"];
|
|
7005
|
+
for (const source of args.sources) {
|
|
7006
|
+
if (!validSources.includes(source)) {
|
|
7007
|
+
return {
|
|
7008
|
+
valid: false,
|
|
7009
|
+
error: `Invalid source: ${source}. Valid sources are: ${validSources.join(", ")}`
|
|
7010
|
+
};
|
|
7011
|
+
}
|
|
7012
|
+
}
|
|
7013
|
+
}
|
|
7014
|
+
if (args.max_results !== void 0) {
|
|
7015
|
+
if (typeof args.max_results !== "number" || args.max_results < 1) {
|
|
7016
|
+
return { valid: false, error: "max_results must be a positive integer" };
|
|
7017
|
+
}
|
|
7018
|
+
if (args.max_results > 50) {
|
|
7019
|
+
return { valid: false, error: "max_results cannot exceed 50" };
|
|
7020
|
+
}
|
|
7021
|
+
}
|
|
7022
|
+
if (args.max_uses !== void 0) {
|
|
7023
|
+
if (typeof args.max_uses !== "number" || args.max_uses < 1) {
|
|
7024
|
+
return { valid: false, error: "max_uses must be a positive integer" };
|
|
7025
|
+
}
|
|
7026
|
+
if (args.max_uses > 20) {
|
|
7027
|
+
return { valid: false, error: "max_uses cannot exceed 20" };
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9.-]*\.[a-zA-Z]{2,}$/;
|
|
7031
|
+
if (args.allowed_domains) {
|
|
7032
|
+
for (const domain of args.allowed_domains) {
|
|
7033
|
+
if (!domainRegex.test(domain)) {
|
|
7034
|
+
return { valid: false, error: `Invalid domain format: ${domain}` };
|
|
7035
|
+
}
|
|
7036
|
+
}
|
|
7037
|
+
}
|
|
7038
|
+
if (args.blocked_domains) {
|
|
7039
|
+
for (const domain of args.blocked_domains) {
|
|
7040
|
+
if (!domainRegex.test(domain)) {
|
|
7041
|
+
return { valid: false, error: `Invalid domain format: ${domain}` };
|
|
7042
|
+
}
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
return { valid: true };
|
|
7046
|
+
}
|
|
7047
|
+
function extractDomain(url) {
|
|
7048
|
+
try {
|
|
7049
|
+
const urlObj = new URL(url);
|
|
7050
|
+
const hostname = urlObj.hostname.toLowerCase();
|
|
7051
|
+
const parts = hostname.split(".");
|
|
7052
|
+
if (parts.length >= 2) {
|
|
7053
|
+
return parts.slice(-2).join(".");
|
|
7054
|
+
}
|
|
7055
|
+
return hostname;
|
|
7056
|
+
} catch {
|
|
7057
|
+
return null;
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
function passesDomainFilter(url, allowedDomains, blockedDomains) {
|
|
7061
|
+
const domain = extractDomain(url);
|
|
7062
|
+
if (!domain) {
|
|
7063
|
+
return { passes: false, reason: "Could not extract domain from URL" };
|
|
7064
|
+
}
|
|
7065
|
+
if (allowedDomains && allowedDomains.length > 0) {
|
|
7066
|
+
const isAllowed = allowedDomains.some(
|
|
7067
|
+
(allowed) => domain === allowed.toLowerCase() || domain.endsWith("." + allowed.toLowerCase())
|
|
7068
|
+
);
|
|
7069
|
+
if (!isAllowed) {
|
|
7070
|
+
return { passes: false, reason: `Domain ${domain} not in allowed list` };
|
|
7071
|
+
}
|
|
7072
|
+
}
|
|
7073
|
+
if (blockedDomains && blockedDomains.length > 0) {
|
|
7074
|
+
const isBlocked = blockedDomains.some(
|
|
7075
|
+
(blocked) => domain === blocked.toLowerCase() || domain.endsWith("." + blocked.toLowerCase())
|
|
7076
|
+
);
|
|
7077
|
+
if (isBlocked) {
|
|
7078
|
+
return { passes: false, reason: `Domain ${domain} is in blocked list` };
|
|
7079
|
+
}
|
|
7080
|
+
}
|
|
7081
|
+
return { passes: true };
|
|
7082
|
+
}
|
|
6986
7083
|
function httpGet(url, customHeaders) {
|
|
6987
7084
|
return new Promise((resolve2, reject) => {
|
|
6988
7085
|
const protocol = url.startsWith("https") ? https : http;
|
|
@@ -7028,17 +7125,29 @@ function cleanContent(text, maxLength = MAX_CONTENT_LENGTH) {
|
|
|
7028
7125
|
}
|
|
7029
7126
|
return cleaned;
|
|
7030
7127
|
}
|
|
7031
|
-
async function searchReddit(query, limit) {
|
|
7128
|
+
async function searchReddit(query, limit, allowedDomains, blockedDomains) {
|
|
7032
7129
|
const results = [];
|
|
7130
|
+
const warnings = [];
|
|
7033
7131
|
try {
|
|
7034
7132
|
const subreddits = "programming+webdev+javascript+typescript+python+node+reactjs+learnprogramming+rust+golang+devops";
|
|
7035
7133
|
const encodedQuery = encodeURIComponent(query);
|
|
7036
|
-
const url = `https://www.reddit.com/r/${subreddits}/search.json?q=${encodedQuery}&sort=relevance&limit=${limit}&restrict_sr=on`;
|
|
7134
|
+
const url = `https://www.reddit.com/r/${subreddits}/search.json?q=${encodedQuery}&sort=relevance&limit=${limit * 2}&restrict_sr=on`;
|
|
7135
|
+
const domainCheck = passesDomainFilter(url, allowedDomains, blockedDomains);
|
|
7136
|
+
if (!domainCheck.passes) {
|
|
7137
|
+
warnings.push(`Reddit search skipped: ${domainCheck.reason}`);
|
|
7138
|
+
return results;
|
|
7139
|
+
}
|
|
7037
7140
|
const response = await httpGet(url);
|
|
7038
7141
|
const data = JSON.parse(response);
|
|
7039
7142
|
if (data.data?.children) {
|
|
7040
|
-
for (const child of data.data.children.slice(0, limit)) {
|
|
7143
|
+
for (const child of data.data.children.slice(0, limit * 2)) {
|
|
7144
|
+
if (results.length >= limit) break;
|
|
7041
7145
|
const post = child.data;
|
|
7146
|
+
const postUrl = `https://reddit.com${post.permalink}`;
|
|
7147
|
+
const urlCheck = passesDomainFilter(postUrl, allowedDomains, blockedDomains);
|
|
7148
|
+
if (!urlCheck.passes) {
|
|
7149
|
+
continue;
|
|
7150
|
+
}
|
|
7042
7151
|
let content = `# ${post.title}
|
|
7043
7152
|
|
|
7044
7153
|
`;
|
|
@@ -7070,7 +7179,7 @@ ${cleanContent(post.selftext, 2e3)}
|
|
|
7070
7179
|
}
|
|
7071
7180
|
results.push({
|
|
7072
7181
|
title: post.title || "",
|
|
7073
|
-
url:
|
|
7182
|
+
url: postUrl,
|
|
7074
7183
|
source: "reddit",
|
|
7075
7184
|
content: cleanContent(content),
|
|
7076
7185
|
score: post.score,
|
|
@@ -7084,18 +7193,34 @@ ${cleanContent(post.selftext, 2e3)}
|
|
|
7084
7193
|
}
|
|
7085
7194
|
} catch (error) {
|
|
7086
7195
|
console.error(`[search_web] Reddit error: ${error.message}`);
|
|
7196
|
+
if (error.message.includes("403") || error.message.includes("429")) {
|
|
7197
|
+
warnings.push("Reddit rate limit encountered - results may be incomplete");
|
|
7198
|
+
}
|
|
7087
7199
|
}
|
|
7088
7200
|
return results;
|
|
7089
7201
|
}
|
|
7090
|
-
async function searchGitHub(query, limit) {
|
|
7202
|
+
async function searchGitHub(query, limit, allowedDomains, blockedDomains) {
|
|
7091
7203
|
const results = [];
|
|
7204
|
+
const warnings = [];
|
|
7092
7205
|
try {
|
|
7093
7206
|
const encodedQuery = encodeURIComponent(query);
|
|
7094
|
-
const url = `https://api.github.com/search/issues?q=${encodedQuery}+is:issue&sort=reactions&order=desc&per_page=${limit}`;
|
|
7095
|
-
const
|
|
7207
|
+
const url = `https://api.github.com/search/issues?q=${encodedQuery}+is:issue&sort=reactions&order=desc&per_page=${limit * 2}`;
|
|
7208
|
+
const domainCheck = passesDomainFilter(url, allowedDomains, blockedDomains);
|
|
7209
|
+
if (!domainCheck.passes) {
|
|
7210
|
+
warnings.push(`GitHub search skipped: ${domainCheck.reason}`);
|
|
7211
|
+
return results;
|
|
7212
|
+
}
|
|
7213
|
+
const response = await httpGet(url, {
|
|
7214
|
+
"Accept": "application/vnd.github+json"
|
|
7215
|
+
});
|
|
7096
7216
|
const data = JSON.parse(response);
|
|
7097
7217
|
if (data.items) {
|
|
7098
|
-
for (const item of data.items.slice(0, limit)) {
|
|
7218
|
+
for (const item of data.items.slice(0, limit * 2)) {
|
|
7219
|
+
if (results.length >= limit) break;
|
|
7220
|
+
const urlCheck = passesDomainFilter(item.html_url, allowedDomains, blockedDomains);
|
|
7221
|
+
if (!urlCheck.passes) {
|
|
7222
|
+
continue;
|
|
7223
|
+
}
|
|
7099
7224
|
let content = `# ${item.title}
|
|
7100
7225
|
|
|
7101
7226
|
`;
|
|
@@ -7130,18 +7255,32 @@ ${cleanContent(item.body, 2500)}
|
|
|
7130
7255
|
}
|
|
7131
7256
|
} catch (error) {
|
|
7132
7257
|
console.error(`[search_web] GitHub error: ${error.message}`);
|
|
7258
|
+
if (error.message.includes("403")) {
|
|
7259
|
+
warnings.push("GitHub API rate limit may have been reached");
|
|
7260
|
+
}
|
|
7133
7261
|
}
|
|
7134
7262
|
return results;
|
|
7135
7263
|
}
|
|
7136
|
-
async function searchStackOverflow(query, limit) {
|
|
7264
|
+
async function searchStackOverflow(query, limit, allowedDomains, blockedDomains) {
|
|
7137
7265
|
const results = [];
|
|
7266
|
+
const warnings = [];
|
|
7138
7267
|
try {
|
|
7139
7268
|
const encodedQuery = encodeURIComponent(query);
|
|
7140
|
-
const url = `https://api.stackexchange.com/2.3/search/advanced?order=desc&sort=relevance&q=${encodedQuery}&site=stackoverflow&pagesize=${limit}&filter=withbody`;
|
|
7269
|
+
const url = `https://api.stackexchange.com/2.3/search/advanced?order=desc&sort=relevance&q=${encodedQuery}&site=stackoverflow&pagesize=${limit * 2}&filter=withbody`;
|
|
7270
|
+
const domainCheck = passesDomainFilter(url, allowedDomains, blockedDomains);
|
|
7271
|
+
if (!domainCheck.passes) {
|
|
7272
|
+
warnings.push(`StackOverflow search skipped: ${domainCheck.reason}`);
|
|
7273
|
+
return results;
|
|
7274
|
+
}
|
|
7141
7275
|
const response = await httpGet(url);
|
|
7142
7276
|
const data = JSON.parse(response);
|
|
7143
7277
|
if (data.items) {
|
|
7144
|
-
for (const item of data.items.slice(0, limit)) {
|
|
7278
|
+
for (const item of data.items.slice(0, limit * 2)) {
|
|
7279
|
+
if (results.length >= limit) break;
|
|
7280
|
+
const urlCheck = passesDomainFilter(item.link, allowedDomains, blockedDomains);
|
|
7281
|
+
if (!urlCheck.passes) {
|
|
7282
|
+
continue;
|
|
7283
|
+
}
|
|
7145
7284
|
let content = `# ${item.title}
|
|
7146
7285
|
|
|
7147
7286
|
`;
|
|
@@ -7195,64 +7334,96 @@ ${cleanContent(cleanAnswer, 2e3)}
|
|
|
7195
7334
|
}
|
|
7196
7335
|
return results;
|
|
7197
7336
|
}
|
|
7198
|
-
async function
|
|
7337
|
+
async function searchX(query, limit, allowedDomains, blockedDomains) {
|
|
7338
|
+
const results = [];
|
|
7339
|
+
const warnings = [];
|
|
7340
|
+
warnings.push("X (Twitter) search is not available - API requires authentication");
|
|
7199
7341
|
try {
|
|
7200
|
-
const {
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
return {
|
|
7207
|
-
success: false,
|
|
7208
|
-
query: query || "",
|
|
7209
|
-
results: [],
|
|
7210
|
-
sources_searched: [],
|
|
7211
|
-
total_results: 0,
|
|
7212
|
-
error: "query is required and must be a string"
|
|
7213
|
-
};
|
|
7342
|
+
const encodedQuery = encodeURIComponent(`${query} site:twitter.com OR site:x.com`);
|
|
7343
|
+
const url = `https://www.google.com/search?q=${encodedQuery}&num=${limit}`;
|
|
7344
|
+
const domainCheck = passesDomainFilter(url, allowedDomains, blockedDomains);
|
|
7345
|
+
if (!domainCheck.passes) {
|
|
7346
|
+
warnings.push(`X search skipped: ${domainCheck.reason}`);
|
|
7347
|
+
return results;
|
|
7214
7348
|
}
|
|
7215
|
-
const allResults = [];
|
|
7216
|
-
const sourcesSearched = [];
|
|
7217
|
-
const resultsPerSource = Math.ceil(max_results / sources.length);
|
|
7218
|
-
const searches = [];
|
|
7219
|
-
for (const source of sources) {
|
|
7220
|
-
sourcesSearched.push(source);
|
|
7221
|
-
switch (source) {
|
|
7222
|
-
case "reddit":
|
|
7223
|
-
searches.push(searchReddit(query, resultsPerSource));
|
|
7224
|
-
break;
|
|
7225
|
-
case "github":
|
|
7226
|
-
searches.push(searchGitHub(query, resultsPerSource));
|
|
7227
|
-
break;
|
|
7228
|
-
case "stackoverflow":
|
|
7229
|
-
searches.push(searchStackOverflow(query, resultsPerSource));
|
|
7230
|
-
break;
|
|
7231
|
-
}
|
|
7232
|
-
}
|
|
7233
|
-
const searchResults = await Promise.all(searches);
|
|
7234
|
-
for (const results of searchResults) {
|
|
7235
|
-
allResults.push(...results);
|
|
7236
|
-
}
|
|
7237
|
-
allResults.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
7238
|
-
const limitedResults = allResults.slice(0, max_results);
|
|
7239
|
-
return {
|
|
7240
|
-
success: true,
|
|
7241
|
-
query,
|
|
7242
|
-
results: limitedResults,
|
|
7243
|
-
sources_searched: sourcesSearched,
|
|
7244
|
-
total_results: limitedResults.length
|
|
7245
|
-
};
|
|
7246
7349
|
} catch (error) {
|
|
7350
|
+
console.error(`[search_web] X error: ${error.message}`);
|
|
7351
|
+
}
|
|
7352
|
+
return results;
|
|
7353
|
+
}
|
|
7354
|
+
async function searchWeb(args) {
|
|
7355
|
+
const startTime = performance.now();
|
|
7356
|
+
const warnings = [];
|
|
7357
|
+
let searchesPerformed = 0;
|
|
7358
|
+
const validation = validateInput(args);
|
|
7359
|
+
if (!validation.valid) {
|
|
7247
7360
|
return {
|
|
7248
7361
|
success: false,
|
|
7249
7362
|
query: args.query || "",
|
|
7250
7363
|
results: [],
|
|
7251
7364
|
sources_searched: [],
|
|
7252
7365
|
total_results: 0,
|
|
7253
|
-
|
|
7366
|
+
duration_seconds: 0,
|
|
7367
|
+
searches_performed: 0,
|
|
7368
|
+
error: validation.error
|
|
7254
7369
|
};
|
|
7255
7370
|
}
|
|
7371
|
+
const {
|
|
7372
|
+
query,
|
|
7373
|
+
sources = DEFAULT_SOURCES,
|
|
7374
|
+
max_results = MAX_RESULTS_DEFAULT,
|
|
7375
|
+
allowed_domains,
|
|
7376
|
+
blocked_domains,
|
|
7377
|
+
max_uses = MAX_USES_DEFAULT
|
|
7378
|
+
} = args;
|
|
7379
|
+
if (sources.length > max_uses) {
|
|
7380
|
+
warnings.push(`Requested ${sources.length} sources but max_uses is ${max_uses} - limiting to ${max_uses} sources`);
|
|
7381
|
+
}
|
|
7382
|
+
const allResults = [];
|
|
7383
|
+
const sourcesSearched = [];
|
|
7384
|
+
const limitedSources = sources.slice(0, max_uses);
|
|
7385
|
+
const resultsPerSource = Math.ceil(max_results / limitedSources.length);
|
|
7386
|
+
const searches = [];
|
|
7387
|
+
for (const source of limitedSources) {
|
|
7388
|
+
sourcesSearched.push(source);
|
|
7389
|
+
switch (source) {
|
|
7390
|
+
case "reddit":
|
|
7391
|
+
searches.push(searchReddit(query, resultsPerSource, allowed_domains, blocked_domains));
|
|
7392
|
+
break;
|
|
7393
|
+
case "github":
|
|
7394
|
+
searches.push(searchGitHub(query, resultsPerSource, allowed_domains, blocked_domains));
|
|
7395
|
+
break;
|
|
7396
|
+
case "stackoverflow":
|
|
7397
|
+
searches.push(searchStackOverflow(query, resultsPerSource, allowed_domains, blocked_domains));
|
|
7398
|
+
break;
|
|
7399
|
+
case "x":
|
|
7400
|
+
searches.push(searchX(query, resultsPerSource, allowed_domains, blocked_domains));
|
|
7401
|
+
break;
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
const searchResults = await Promise.all(searches);
|
|
7405
|
+
searchesPerformed = searchResults.length;
|
|
7406
|
+
for (const results of searchResults) {
|
|
7407
|
+
allResults.push(...results);
|
|
7408
|
+
}
|
|
7409
|
+
allResults.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
7410
|
+
const limitedResults = allResults.slice(0, max_results);
|
|
7411
|
+
const endTime = performance.now();
|
|
7412
|
+
const durationSeconds = (endTime - startTime) / 1e3;
|
|
7413
|
+
if (limitedResults.length === 0) {
|
|
7414
|
+
warnings.push("No results found - try adjusting your query or domain filters");
|
|
7415
|
+
}
|
|
7416
|
+
return {
|
|
7417
|
+
success: true,
|
|
7418
|
+
query,
|
|
7419
|
+
results: limitedResults,
|
|
7420
|
+
sources_searched: sourcesSearched,
|
|
7421
|
+
total_results: limitedResults.length,
|
|
7422
|
+
duration_seconds: Math.round(durationSeconds * 100) / 100,
|
|
7423
|
+
// 2 casas decimais
|
|
7424
|
+
searches_performed: searchesPerformed,
|
|
7425
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
7426
|
+
};
|
|
7256
7427
|
}
|
|
7257
7428
|
|
|
7258
7429
|
// src/app/agent/tools/natives/load_skill.ts
|
|
@@ -13036,7 +13207,7 @@ You are the **BluMa Coordinator** \u2014 a **Product Owner + Engineering Manager
|
|
|
13036
13207
|
|
|
13037
13208
|
## 0. Core Philosophy: Team > Solo
|
|
13038
13209
|
|
|
13039
|
-
**One AI is good. A coordinated team of 3-7 AIs
|
|
13210
|
+
**One AI is good. A coordinated team of 3-7 AIs can be better when the task truly benefits from delegation.**
|
|
13040
13211
|
|
|
13041
13212
|
Think of yourself as a **rigorous PO** who:
|
|
13042
13213
|
- Receives a request from the user (the "client")
|
|
@@ -13044,7 +13215,7 @@ Think of yourself as a **rigorous PO** who:
|
|
|
13044
13215
|
- Assigns each task to the right specialist worker
|
|
13045
13216
|
- Coordinates their work in parallel
|
|
13046
13217
|
- Verifies quality before delivering to the client
|
|
13047
|
-
- **
|
|
13218
|
+
- **Prefer team execution** when the task is non-trivial, parallelizable, risky, or needs independent verification
|
|
13048
13219
|
|
|
13049
13220
|
**Why this matters:**
|
|
13050
13221
|
- **Quality**: Each worker focuses deeply on one aspect \u2192 fewer mistakes
|
|
@@ -13054,14 +13225,15 @@ Think of yourself as a **rigorous PO** who:
|
|
|
13054
13225
|
- **CEO appreciation**: Systematic, professional approach that scales
|
|
13055
13226
|
|
|
13056
13227
|
**Default behavior**: When a task arrives, your first instinct should be:
|
|
13057
|
-
> "
|
|
13228
|
+
> "Can I answer or handle this directly?"
|
|
13058
13229
|
|
|
13059
|
-
|
|
13230
|
+
Only if the answer is no, ask:
|
|
13231
|
+
> "How can I break this into parallel worker tasks?"
|
|
13060
13232
|
|
|
13061
13233
|
## 1. Your Role
|
|
13062
13234
|
|
|
13063
13235
|
You do **NOT execute tasks directly** (except trivial reads). Your job is to:
|
|
13064
|
-
- **Orchestrate workers** to research, implement, and verify changes
|
|
13236
|
+
- **Orchestrate workers** to research, implement, and verify changes when that materially improves speed, quality, or confidence
|
|
13065
13237
|
- **Synthesize results** and communicate with the user
|
|
13066
13238
|
- **Answer questions directly** when possible \u2014 don't delegate work you can handle without tools
|
|
13067
13239
|
- **Read-only tools** (\`read_file_lines\`, \`grep\`, etc.) are fine for **light** coordinator checks (e.g. verify a path before writing a worker spec); heavy exploration belongs in workers
|
|
@@ -14233,7 +14405,7 @@ Use **both** API **reasoning** (when available) **and** the \`message\` tool. Re
|
|
|
14233
14405
|
- Never claim success without tool output that proves it.
|
|
14234
14406
|
- **Stay audible:** Your **default** in multi-step work is to call \`message\` with \`message_type: "info"\` **early and often** \u2014 not optional polish. **Bias toward sending \`info\`** after discoveries, failures, and before long tool chains; **several \`info\` calls per turn** is normal and expected. Do **not** hide behind tools or reasoning only; \`info\` is how the user follows along.
|
|
14235
14407
|
- **Ask when uncertain:** Use \`ask_user_question\` when you encounter ambiguity, need clarification, or face multiple valid approaches. Do not assume \u2014 ask the user to make decisions about their preferences, requirements, or implementation choices. This tool is your primary mechanism for resolving uncertainty.
|
|
14236
|
-
- **
|
|
14408
|
+
- **Worker policy:** Use workers surgically, not by default. Do the work directly when the task is simple, local, or already well-scoped. Spawn workers when the task is genuinely non-trivial, parallelizable, risky, or needs independent verification. Break larger efforts into research \u2192 implementation \u2192 verification phases when that creates real value. You are the PO \u2014 orchestrate when it helps, synthesize, verify, deliver.
|
|
14237
14409
|
- **Engineer mindset \u2014 question anomalies:** When something seems wrong (memory loss, unexpected behavior, aggressive defaults), **investigate deeply**. Do not accept "it's working as designed". Trace the code, find the root cause, and **have courage to revert** if a feature breaks core functionality. Protect the session, memory, and stability above all.
|
|
14238
14410
|
- **Courage to reverse:** If you discover a path is wrong (e.g., a "feature" that destroys context, a default that's too aggressive), **stop immediately**, explain why it's broken, and revert/remove it. Better to undo a bad change than to let it cause harm. This is what separates a **thinking engineer** from a **blind executor**.
|
|
14239
14411
|
- Large efforts: \`todo\`; parallel subtasks: \`spawn_agent\` with a clear scope + \`wait_agent\` / \`list_agents\`.
|
|
@@ -14654,6 +14826,29 @@ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurn
|
|
|
14654
14826
|
init_runtime_config();
|
|
14655
14827
|
import os23 from "os";
|
|
14656
14828
|
import OpenAI from "openai";
|
|
14829
|
+
|
|
14830
|
+
// src/app/agent/core/llm/streaming_delta.ts
|
|
14831
|
+
function extractStreamingDelta(previous, next) {
|
|
14832
|
+
const prev = String(previous ?? "");
|
|
14833
|
+
const curr = String(next ?? "");
|
|
14834
|
+
if (!curr) return "";
|
|
14835
|
+
if (!prev) return curr;
|
|
14836
|
+
if (curr.startsWith(prev)) {
|
|
14837
|
+
return curr.slice(prev.length);
|
|
14838
|
+
}
|
|
14839
|
+
if (prev.startsWith(curr)) {
|
|
14840
|
+
return "";
|
|
14841
|
+
}
|
|
14842
|
+
const maxOverlap = Math.min(prev.length, curr.length);
|
|
14843
|
+
for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
|
|
14844
|
+
if (prev.slice(-overlap) === curr.slice(0, overlap)) {
|
|
14845
|
+
return curr.slice(overlap);
|
|
14846
|
+
}
|
|
14847
|
+
}
|
|
14848
|
+
return curr;
|
|
14849
|
+
}
|
|
14850
|
+
|
|
14851
|
+
// src/app/agent/core/llm/llm.ts
|
|
14657
14852
|
function defaultBlumaUserContextInput(sessionId, userMessage) {
|
|
14658
14853
|
const msg = String(userMessage || "").slice(0, 300);
|
|
14659
14854
|
return {
|
|
@@ -14845,12 +15040,17 @@ var LLMService = class {
|
|
|
14845
15040
|
{ headers: this.requestHeaders(params.userContext) }
|
|
14846
15041
|
);
|
|
14847
15042
|
const toolCallsAccumulator = /* @__PURE__ */ new Map();
|
|
15043
|
+
let reasoningSnapshot = "";
|
|
14848
15044
|
for await (const chunk of stream) {
|
|
14849
15045
|
const choice = chunk.choices[0];
|
|
14850
15046
|
if (!choice) continue;
|
|
14851
15047
|
const delta = choice.delta;
|
|
14852
15048
|
applyDeltaToolCallsToAccumulator(toolCallsAccumulator, delta?.tool_calls);
|
|
14853
|
-
const
|
|
15049
|
+
const rawReasoning = delta?.reasoning_content || delta?.reasoning || "";
|
|
15050
|
+
const reasoning = extractStreamingDelta(reasoningSnapshot, rawReasoning);
|
|
15051
|
+
if (reasoning) {
|
|
15052
|
+
reasoningSnapshot += reasoning;
|
|
15053
|
+
}
|
|
14854
15054
|
const fullToolCalls = choice.finish_reason === "tool_calls" ? Array.from(toolCallsAccumulator.values()) : void 0;
|
|
14855
15055
|
yield {
|
|
14856
15056
|
delta: delta?.content || "",
|
|
@@ -14873,6 +15073,32 @@ var LLMService = class {
|
|
|
14873
15073
|
}
|
|
14874
15074
|
};
|
|
14875
15075
|
|
|
15076
|
+
// src/app/agent/core/llm/llm_errors.ts
|
|
15077
|
+
function formatLlmUiError(error) {
|
|
15078
|
+
const rawMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error during LLM request.";
|
|
15079
|
+
const lower = rawMessage.toLowerCase();
|
|
15080
|
+
let message2 = "Ocorreu um erro inesperado ao contactar o modelo.";
|
|
15081
|
+
let hint = "Tente novamente. Se continuar, verifique a liga\xE7\xE3o ao FactorRouter.";
|
|
15082
|
+
if (lower.includes("timeout") || lower.includes("etimedout") || lower.includes("upstream_timeout")) {
|
|
15083
|
+
message2 = "O BluMa demorou demasiado a responder.";
|
|
15084
|
+
hint = "Aumente o timeout do pedido ou tente novamente.";
|
|
15085
|
+
} else if (lower.includes("connection") || lower.includes("econnrefused") || lower.includes("ehostunreach") || lower.includes("enotfound")) {
|
|
15086
|
+
message2 = "N\xE3o foi poss\xEDvel conectar ao servi\xE7o do modelo.";
|
|
15087
|
+
hint = "Verifique a rede, o FactorRouter_URL e o estado do gateway.";
|
|
15088
|
+
} else if (lower.includes("401") || lower.includes("403") || lower.includes("unauthorized") || lower.includes("forbidden")) {
|
|
15089
|
+
message2 = "Falha de autentica\xE7\xE3o/autoriza\xE7\xE3o ao contactar o modelo.";
|
|
15090
|
+
hint = "Verifique o FactorRouter_KEY e as permiss\xF5es da conta.";
|
|
15091
|
+
} else if (lower.includes("api")) {
|
|
15092
|
+
message2 = "Erro de comunica\xE7\xE3o com a API do modelo.";
|
|
15093
|
+
hint = "Verifique credenciais e o estado do servi\xE7o upstream.";
|
|
15094
|
+
}
|
|
15095
|
+
return {
|
|
15096
|
+
message: message2,
|
|
15097
|
+
details: rawMessage,
|
|
15098
|
+
hint
|
|
15099
|
+
};
|
|
15100
|
+
}
|
|
15101
|
+
|
|
14876
15102
|
// src/app/agent/core/llm/tool_call_normalizer.ts
|
|
14877
15103
|
import { randomUUID } from "crypto";
|
|
14878
15104
|
var ToolCallNormalizer = class {
|
|
@@ -15331,8 +15557,8 @@ var BluMaAgent = class {
|
|
|
15331
15557
|
factorRouterTurnClosed = false;
|
|
15332
15558
|
/** Passos seguidos sem tool_calls nem texto visível (só raciocínio) — evita loop lento no mesmo turno. */
|
|
15333
15559
|
emptyAssistantReplySteps = 0;
|
|
15334
|
-
/**
|
|
15335
|
-
|
|
15560
|
+
/** Deduplicação de reasoning chunks no streaming — evita repetição. */
|
|
15561
|
+
lastReasoningChunkRef = "";
|
|
15336
15562
|
constructor(sessionId, eventBus, llm, mcpClient, feedbackSystem) {
|
|
15337
15563
|
this.sessionId = sessionId;
|
|
15338
15564
|
this.eventBus = eventBus;
|
|
@@ -15483,7 +15709,6 @@ var BluMaAgent = class {
|
|
|
15483
15709
|
const userContent = buildUserMessageContent(inputText, process.cwd());
|
|
15484
15710
|
this.history.push({ role: "user", content: userContent });
|
|
15485
15711
|
this.emptyAssistantReplySteps = 0;
|
|
15486
|
-
this.directTextProtocolSteps = 0;
|
|
15487
15712
|
this.eventBus.emit(
|
|
15488
15713
|
"backend_message",
|
|
15489
15714
|
buildTurnStartBackendMessage({
|
|
@@ -15529,15 +15754,18 @@ var BluMaAgent = class {
|
|
|
15529
15754
|
}
|
|
15530
15755
|
} catch (parseError) {
|
|
15531
15756
|
this.eventBus.emit("backend_message", {
|
|
15532
|
-
type: "
|
|
15533
|
-
message:
|
|
15757
|
+
type: "info",
|
|
15758
|
+
message: "O BluMa encontrou um erro ao processar. A tentar recuperar a sess\xE3o..."
|
|
15534
15759
|
});
|
|
15535
15760
|
toolResultContent = JSON.stringify({
|
|
15536
|
-
error: "
|
|
15537
|
-
|
|
15538
|
-
raw_arguments: toolCall.function.arguments
|
|
15761
|
+
error: "Tool arguments could not be parsed",
|
|
15762
|
+
recovery: "Session recovered automatically"
|
|
15539
15763
|
});
|
|
15540
15764
|
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
15765
|
+
this.history.push({
|
|
15766
|
+
role: "system",
|
|
15767
|
+
content: "The previous tool call had invalid JSON arguments. Please retry with properly formatted JSON arguments."
|
|
15768
|
+
});
|
|
15541
15769
|
this.persistSession();
|
|
15542
15770
|
return true;
|
|
15543
15771
|
}
|
|
@@ -15697,13 +15925,12 @@ var BluMaAgent = class {
|
|
|
15697
15925
|
parsed.push({ toolCall, toolName: toolCall.function.name, toolArgs });
|
|
15698
15926
|
} catch (parseError) {
|
|
15699
15927
|
const toolResultContent = JSON.stringify({
|
|
15700
|
-
error: "
|
|
15701
|
-
|
|
15702
|
-
raw_arguments: toolCall.function.arguments
|
|
15928
|
+
error: "Tool arguments could not be parsed",
|
|
15929
|
+
recovery: "Session recovered automatically"
|
|
15703
15930
|
});
|
|
15704
15931
|
this.eventBus.emit("backend_message", {
|
|
15705
|
-
type: "
|
|
15706
|
-
message:
|
|
15932
|
+
type: "info",
|
|
15933
|
+
message: "O BluMa encontrou um erro ao processar. A tentar recuperar a sess\xE3o..."
|
|
15707
15934
|
});
|
|
15708
15935
|
this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
15709
15936
|
this.persistSession();
|
|
@@ -15935,8 +16162,18 @@ ${editData.error.display}`;
|
|
|
15935
16162
|
await this._handleNonStreamingResponse(contextWindow);
|
|
15936
16163
|
}
|
|
15937
16164
|
} catch (error) {
|
|
15938
|
-
const
|
|
15939
|
-
this.eventBus.emit("backend_message", {
|
|
16165
|
+
const uiError = formatLlmUiError(error);
|
|
16166
|
+
this.eventBus.emit("backend_message", {
|
|
16167
|
+
type: "error",
|
|
16168
|
+
message: uiError.message,
|
|
16169
|
+
details: uiError.details,
|
|
16170
|
+
hint: uiError.hint
|
|
16171
|
+
});
|
|
16172
|
+
this.eventBus.emit("backend_message", {
|
|
16173
|
+
type: "log",
|
|
16174
|
+
message: "LLM request failed",
|
|
16175
|
+
payload: uiError.details
|
|
16176
|
+
});
|
|
15940
16177
|
await this.notifyFactorTurnEndIfNeeded("llm_error");
|
|
15941
16178
|
this.eventBus.emit("backend_message", { type: "done", status: "failed" });
|
|
15942
16179
|
} finally {
|
|
@@ -15956,13 +16193,12 @@ ${editData.error.display}`;
|
|
|
15956
16193
|
});
|
|
15957
16194
|
} else if (this.emptyAssistantReplySteps >= 6) {
|
|
15958
16195
|
this.eventBus.emit("backend_message", {
|
|
15959
|
-
type: "
|
|
15960
|
-
message: "
|
|
16196
|
+
type: "info",
|
|
16197
|
+
message: "O BluMa est\xE1 a ter dificuldade em processar. Tente novamente ou use /effort low para respostas mais r\xE1pidas."
|
|
15961
16198
|
});
|
|
15962
16199
|
await this.notifyFactorTurnEndIfNeeded("empty_reply_exhausted");
|
|
15963
16200
|
this.eventBus.emit("backend_message", { type: "done", status: "failed" });
|
|
15964
16201
|
this.emptyAssistantReplySteps = 0;
|
|
15965
|
-
this.directTextProtocolSteps = 0;
|
|
15966
16202
|
return;
|
|
15967
16203
|
}
|
|
15968
16204
|
await this._continueConversation();
|
|
@@ -15994,6 +16230,7 @@ ${editData.error.display}`;
|
|
|
15994
16230
|
parallel_tool_calls: true,
|
|
15995
16231
|
userContext: this.getLlmUserContext()
|
|
15996
16232
|
});
|
|
16233
|
+
this.lastReasoningChunkRef = "";
|
|
15997
16234
|
for await (const chunk of stream) {
|
|
15998
16235
|
if (this.isInterrupted) {
|
|
15999
16236
|
this.eventBus.emit("stream_end", {});
|
|
@@ -16005,7 +16242,11 @@ ${editData.error.display}`;
|
|
|
16005
16242
|
this.eventBus.emit("stream_start", {});
|
|
16006
16243
|
hasEmittedStart = true;
|
|
16007
16244
|
}
|
|
16008
|
-
|
|
16245
|
+
const reasoningKey = chunk.reasoning.trim().replace(/\s+/g, " ");
|
|
16246
|
+
if (reasoningKey !== this.lastReasoningChunkRef) {
|
|
16247
|
+
this.lastReasoningChunkRef = reasoningKey;
|
|
16248
|
+
this.eventBus.emit("stream_reasoning_chunk", { delta: chunk.reasoning });
|
|
16249
|
+
}
|
|
16009
16250
|
}
|
|
16010
16251
|
if (chunk.delta) {
|
|
16011
16252
|
if (!hasEmittedStart) {
|
|
@@ -16044,7 +16285,6 @@ ${editData.error.display}`;
|
|
|
16044
16285
|
this.history.push(normalizedMessage);
|
|
16045
16286
|
if (normalizedMessage.tool_calls && normalizedMessage.tool_calls.length > 0) {
|
|
16046
16287
|
this.emptyAssistantReplySteps = 0;
|
|
16047
|
-
this.directTextProtocolSteps = 0;
|
|
16048
16288
|
const validToolCalls = normalizedMessage.tool_calls.filter(
|
|
16049
16289
|
(call) => ToolCallNormalizer.isValidToolCall(call)
|
|
16050
16290
|
);
|
|
@@ -16084,28 +16324,13 @@ ${editData.error.display}`;
|
|
|
16084
16324
|
}
|
|
16085
16325
|
} else if (trimmedText) {
|
|
16086
16326
|
this.emptyAssistantReplySteps = 0;
|
|
16087
|
-
this.
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
this.eventBus.emit("backend_message", { type: "assistant_message", content: accumulatedContent });
|
|
16091
|
-
}
|
|
16092
|
-
if (this.directTextProtocolSteps >= MAX_DIRECT_TEXT_PROTOCOL) {
|
|
16093
|
-
this.eventBus.emit("backend_message", {
|
|
16094
|
-
type: "error",
|
|
16095
|
-
message: 'Agent kept answering with plain assistant text instead of the `message` tool with message_type "result". Turn forcibly closed to avoid job timeout; fix prompts or model routing.'
|
|
16096
|
-
});
|
|
16097
|
-
await this.notifyFactorTurnEndIfNeeded("protocol_direct_text_exhausted");
|
|
16098
|
-
this.emitTurnCompleted();
|
|
16099
|
-
this.emptyAssistantReplySteps = 0;
|
|
16100
|
-
this.directTextProtocolSteps = 0;
|
|
16101
|
-
return;
|
|
16102
|
-
}
|
|
16103
|
-
const feedback = this.feedbackSystem.generateFeedback({
|
|
16104
|
-
event: "protocol_violation_direct_text",
|
|
16105
|
-
details: { violationContent: accumulatedContent }
|
|
16327
|
+
this.eventBus.emit("backend_message", { type: "assistant_message", content: accumulatedContent });
|
|
16328
|
+
this.eventBus.emit("info", {
|
|
16329
|
+
message: "Assistant returned plain text without tool_calls. Closing the turn to avoid protocol drift."
|
|
16106
16330
|
});
|
|
16107
|
-
this.
|
|
16108
|
-
|
|
16331
|
+
await this.notifyFactorTurnEndIfNeeded("assistant_text_without_tool_call");
|
|
16332
|
+
this.emitTurnCompleted();
|
|
16333
|
+
return;
|
|
16109
16334
|
} else {
|
|
16110
16335
|
await this.continueAfterEmptyAssistantResponse();
|
|
16111
16336
|
}
|
|
@@ -16135,7 +16360,6 @@ ${editData.error.display}`;
|
|
|
16135
16360
|
this.history.push(message2);
|
|
16136
16361
|
if (message2.tool_calls && message2.tool_calls.length > 0) {
|
|
16137
16362
|
this.emptyAssistantReplySteps = 0;
|
|
16138
|
-
this.directTextProtocolSteps = 0;
|
|
16139
16363
|
const validToolCalls = message2.tool_calls.filter(
|
|
16140
16364
|
(call) => ToolCallNormalizer.isValidToolCall(call)
|
|
16141
16365
|
);
|
|
@@ -16175,27 +16399,13 @@ ${editData.error.display}`;
|
|
|
16175
16399
|
}
|
|
16176
16400
|
} else if (typeof message2.content === "string" && message2.content.trim()) {
|
|
16177
16401
|
this.emptyAssistantReplySteps = 0;
|
|
16178
|
-
this.directTextProtocolSteps += 1;
|
|
16179
|
-
const MAX_DIRECT_TEXT_PROTOCOL = 3;
|
|
16180
16402
|
this.eventBus.emit("backend_message", { type: "assistant_message", content: message2.content });
|
|
16181
|
-
|
|
16182
|
-
|
|
16183
|
-
type: "error",
|
|
16184
|
-
message: 'Agent kept answering with plain assistant text instead of the `message` tool with message_type "result". Turn forcibly closed to avoid job timeout.'
|
|
16185
|
-
});
|
|
16186
|
-
await this.notifyFactorTurnEndIfNeeded("protocol_direct_text_exhausted");
|
|
16187
|
-
this.emitTurnCompleted();
|
|
16188
|
-
this.emptyAssistantReplySteps = 0;
|
|
16189
|
-
this.directTextProtocolSteps = 0;
|
|
16190
|
-
return;
|
|
16191
|
-
}
|
|
16192
|
-
const feedback = this.feedbackSystem.generateFeedback({
|
|
16193
|
-
event: "protocol_violation_direct_text",
|
|
16194
|
-
details: { violationContent: message2.content }
|
|
16403
|
+
this.eventBus.emit("info", {
|
|
16404
|
+
message: "Assistant returned plain text without tool_calls. Closing the turn to avoid protocol drift."
|
|
16195
16405
|
});
|
|
16196
|
-
this.
|
|
16197
|
-
this.
|
|
16198
|
-
|
|
16406
|
+
await this.notifyFactorTurnEndIfNeeded("assistant_text_without_tool_call");
|
|
16407
|
+
this.emitTurnCompleted();
|
|
16408
|
+
return;
|
|
16199
16409
|
} else {
|
|
16200
16410
|
await this.continueAfterEmptyAssistantResponse();
|
|
16201
16411
|
}
|
|
@@ -16993,11 +17203,13 @@ var BaseLLMSubAgent = class {
|
|
|
16993
17203
|
/** Um turnId por execute(); reutilizado em todo o loop de tools do subagente. */
|
|
16994
17204
|
subagentTurnContext = null;
|
|
16995
17205
|
lastActivityTimestamp = Date.now();
|
|
17206
|
+
terminalEventEmitted = false;
|
|
16996
17207
|
async execute(input, ctx) {
|
|
16997
17208
|
workerLog.info("Worker started", { id: this.id, pid: process.pid });
|
|
16998
17209
|
this.emitEvent("worker_heartbeat", { status: "started", timestamp: Date.now(), pid: process.pid, id: this.id });
|
|
16999
17210
|
this.ctx = ctx;
|
|
17000
17211
|
this.isInterrupted = false;
|
|
17212
|
+
this.terminalEventEmitted = false;
|
|
17001
17213
|
this.ctx.eventBus.on("user_interrupt", () => {
|
|
17002
17214
|
this.isInterrupted = true;
|
|
17003
17215
|
});
|
|
@@ -17060,13 +17272,15 @@ var BaseLLMSubAgent = class {
|
|
|
17060
17272
|
this.emitEvent("error", {
|
|
17061
17273
|
message: `Subagent tool "${message2.tool_calls[0].function.name}" requires confirmation outside sandbox mode.`
|
|
17062
17274
|
});
|
|
17063
|
-
this.
|
|
17275
|
+
this.emitDoneOnce("blocked_confirmation");
|
|
17064
17276
|
break;
|
|
17065
17277
|
}
|
|
17066
17278
|
await this._handleToolExecution({ type: "user_decision_execute", tool_calls: message2.tool_calls });
|
|
17067
|
-
} else if (message2.content) {
|
|
17279
|
+
} else if (typeof message2.content === "string" && message2.content.trim()) {
|
|
17068
17280
|
this.emitEvent("assistant_message", { content: message2.content });
|
|
17069
|
-
this.emitEvent("
|
|
17281
|
+
this.emitEvent("info", { message: "SubAgent returned plain text without tool_calls. Closing turn." });
|
|
17282
|
+
this.emitDoneOnce("completed");
|
|
17283
|
+
break;
|
|
17070
17284
|
} else {
|
|
17071
17285
|
this.emitEvent("info", { message: "SubAgent is thinking... continuing reasoning cycle." });
|
|
17072
17286
|
}
|
|
@@ -17074,8 +17288,9 @@ var BaseLLMSubAgent = class {
|
|
|
17074
17288
|
}
|
|
17075
17289
|
if (turnCount >= MAX_TURNS) {
|
|
17076
17290
|
this.emitEvent("info", { message: `Worker reached max turns limit (${MAX_TURNS}).` });
|
|
17077
|
-
this.
|
|
17291
|
+
this.emitDoneOnce("max_turns_reached");
|
|
17078
17292
|
}
|
|
17293
|
+
this.emitDoneOnce("completed");
|
|
17079
17294
|
return { history: this.history, turns: turnCount, status: this.isInterrupted ? "cancelled" : "completed" };
|
|
17080
17295
|
};
|
|
17081
17296
|
const timeoutPromise = new Promise((_, reject) => {
|
|
@@ -17088,7 +17303,7 @@ var BaseLLMSubAgent = class {
|
|
|
17088
17303
|
if (error.message?.includes("timed out")) {
|
|
17089
17304
|
workerLog.warn("Worker timed out", { id: this.id, turns: turnCount });
|
|
17090
17305
|
this.emitEvent("error", { message: error.message });
|
|
17091
|
-
this.
|
|
17306
|
+
this.emitDoneOnce("timeout");
|
|
17092
17307
|
} else {
|
|
17093
17308
|
this.emitEvent("error", { message: error.message });
|
|
17094
17309
|
}
|
|
@@ -17189,9 +17404,11 @@ ${editData.error.display}`;
|
|
|
17189
17404
|
if (!lastToolName.includes("agent_end_turn") && !this.isInterrupted) {
|
|
17190
17405
|
await this._continueConversation();
|
|
17191
17406
|
}
|
|
17192
|
-
} else if (message2.content) {
|
|
17407
|
+
} else if (typeof message2.content === "string" && message2.content.trim()) {
|
|
17193
17408
|
this.emitEvent("assistant_message", { content: message2.content });
|
|
17194
|
-
this.emitEvent("
|
|
17409
|
+
this.emitEvent("info", { message: "SubAgent returned plain text without tool_calls. Closing turn." });
|
|
17410
|
+
this.emitEvent("done", { status: "completed" });
|
|
17411
|
+
return;
|
|
17195
17412
|
} else {
|
|
17196
17413
|
this.emitEvent("info", { message: "SubAgent is thinking... continuing reasoning cycle." });
|
|
17197
17414
|
}
|
|
@@ -17247,7 +17464,7 @@ ${editData.error.display}`;
|
|
|
17247
17464
|
result: toolResultContent
|
|
17248
17465
|
});
|
|
17249
17466
|
if (toolName.includes("agent_end_turn")) {
|
|
17250
|
-
this.
|
|
17467
|
+
this.emitDoneOnce("completed");
|
|
17251
17468
|
}
|
|
17252
17469
|
} else {
|
|
17253
17470
|
toolResultContent = "Tool execution was declined.";
|
|
@@ -17286,7 +17503,12 @@ ${editData.error.display}`;
|
|
|
17286
17503
|
} catch {
|
|
17287
17504
|
}
|
|
17288
17505
|
this.isInterrupted = true;
|
|
17289
|
-
this.
|
|
17506
|
+
this.emitDoneOnce("shutdown", { reason });
|
|
17507
|
+
}
|
|
17508
|
+
emitDoneOnce(status, extra = {}) {
|
|
17509
|
+
if (this.terminalEventEmitted) return;
|
|
17510
|
+
this.terminalEventEmitted = true;
|
|
17511
|
+
this.emitEvent("done", { status, ...extra });
|
|
17290
17512
|
}
|
|
17291
17513
|
/**
|
|
17292
17514
|
* Verifica mailbox por follow-ups do coordinator
|