# Phase B Agent Prompt: SystemPromptBuilder + StreamingHandler + NativeToolsHandler

## Context

You are continuing the llm_service.py decomposition (Phase 4.5 of the monorepo restructure).

Phase A is COMPLETE (commit b3e1797): BackgroundTaskManager extracted.
- kollabor/llm/llm_service.py is now 2712 lines (was 3149)
- kollabor/llm/background_task_manager.py is the reference for the extraction pattern

Read the full spec: docs/specs/llm-service-refactor-spec.md

## Your Task: Phase B

Extract 3 modules from llm_service.py. Create the files first (can be parallel),
then wire each into llm_service.py sequentially, verifying after each.

### B1: SystemPromptBuilder

File: kollabor/llm/system_prompt_builder.py

Methods to extract (find by name, line numbers are from original 3149-line file):
- _get_tree_output
- _finalize_system_prompt
- set_plugin_instances
- _get_plugin_system_prompt_additions
- _build_system_prompt
- rebuild_system_prompt

Instance vars to move:
- _plugin_instances

Constructor:
```python
class SystemPromptBuilder:
    def __init__(self, config, agent_manager=None):
```

Dependencies: config (ConfigService), agent_manager (AgentManager)
External imports: subprocess, pathlib.Path, kollabor.utils.config_utils, kollabor.utils.prompt_renderer, kollabor.utils.shell_alias_detector

Wiring:
```python
self._prompt_builder = SystemPromptBuilder(config=self.config, agent_manager=self.agent_manager)
```

Delegation:
- self._build_system_prompt() -> self._prompt_builder.build()
- self.rebuild_system_prompt() -> self._prompt_builder.rebuild(self.conversation_history)
- self.set_plugin_instances(p) -> self._prompt_builder.set_plugin_instances(p)

Note: rebuild_system_prompt() modifies conversation_history[0]. Pass as parameter.

### B2: StreamingHandler

File: kollabor/llm/streaming_handler.py

Methods to extract:
- _stream_thinking_content
- _stream_thinking_sentences
- _stream_thinking_width_based
- _handle_streaming_chunk
- _stream_response_chunk
- _call_llm
- _cleanup_streaming_state

Instance vars to move:
- _streaming_buffer
- _in_thinking
- _thinking_buffer
- _response_started
- _last_chunk_position

Constructor:
```python
class StreamingHandler:
    def __init__(self, api_service, message_display_service, renderer):
```

Wiring:
```python
self._streaming = StreamingHandler(
    api_service=self.api_service,
    message_display_service=self.message_display_service,
    renderer=self.renderer,
)
```

Delegation:
- self._call_llm(...) -> self._streaming.call_llm(conversation_history, max_history, native_tools, mcp_discovery_complete, is_cancelled_fn)
- self._handle_streaming_chunk(...) -> self._streaming.handle_chunk(...)
- self._cleanup_streaming_state() -> self._streaming.cleanup()

Note: _call_llm needs cancel_processing, mcp_discovery_complete, conversation_history,
max_history, native_tools as parameters (not stored on handler).

### B3: NativeToolsHandler

File: kollabor/llm/native_tools_handler.py

Methods to extract:
- _background_mcp_discovery
- _load_native_tools
- _execute_native_tool_calls

Instance vars to move:
- native_tools
- native_tool_calling_enabled
- mcp_discovery_complete

Constructor:
```python
class NativeToolsHandler:
    def __init__(self, mcp_integration, profile_manager, api_service, config):
```

Wiring:
```python
self._native_tools = NativeToolsHandler(
    mcp_integration=self.mcp_integration,
    profile_manager=self.profile_manager,
    api_service=self.api_service,
    config=self.config,
)
```

Delegation:
- self._background_mcp_discovery() -> self._native_tools.background_discovery()
- self._load_native_tools() -> self._native_tools.load_tools()
- self._execute_native_tool_calls() -> self._native_tools.execute_tool_calls()
- self.native_tools -> self._native_tools.tools
- self.mcp_discovery_complete -> self._native_tools.discovery_complete

IMPORTANT: mcp_discovery_complete is referenced by _call_llm (StreamingHandler).
Pass self._native_tools.discovery_complete to StreamingHandler.call_llm().

## Execution Steps

1. Read llm_service.py to find the exact methods (use method names, not line numbers)
2. Read background_task_manager.py to understand the established pattern
3. Create all 3 new files
4. Wire B1 (SystemPromptBuilder) into llm_service.py, verify tests
5. Wire B2 (StreamingHandler) into llm_service.py, verify tests
6. Wire B3 (NativeToolsHandler) into llm_service.py, verify tests
7. Commit

## Verification

After each wiring step:
```bash
uv run pytest tests/unit/llm/test_task_management.py -x -q  # must stay 15/15
uv run python -c "from kollabor.llm.llm_service import LLMService; print('ok')"
```

After all 3 are wired:
```bash
uv run pytest tests/unit/llm/ -q --ignore=tests/unit/llm/test_integrated_session_ids.py
```

Expected: 0 new failures vs baseline (28 failed, 49 passed before Phase B).

## Key Patterns from Phase A

- New class receives deps via constructor, NOT by reaching back into LLMService
- LLMService creates instance in __init__
- Thin forwarding methods preserve public API
- get_status_line reads from sub-component internals (self._component._field)
- _queue_metrics shared by reference between LLMService and BackgroundTaskManager
- Remove unused imports from llm_service.py after extraction (e.g., subprocess)
- Tests that access internal state need updating: self.X -> self._component.X
