k-cli-for-devs 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. k_cli/__init__.py +77 -0
  2. k_cli/agents/__init__.py +0 -0
  3. k_cli/agents/adversarial_swarm.py +338 -0
  4. k_cli/agents/agent_core.py +255 -0
  5. k_cli/agents/background_daemon.py +141 -0
  6. k_cli/agents/orchestrator.py +376 -0
  7. k_cli/agents/persona.py +649 -0
  8. k_cli/agents/scaffold_engine.py +121 -0
  9. k_cli/agents/strands_agent.py +832 -0
  10. k_cli/agents/subagents.py +1496 -0
  11. k_cli/cli.py +3297 -0
  12. k_cli/core/__init__.py +0 -0
  13. k_cli/core/airgap.py +95 -0
  14. k_cli/core/credentials.py +548 -0
  15. k_cli/core/intent_sensor.py +177 -0
  16. k_cli/core/llm_driver.py +1028 -0
  17. k_cli/core/model_manager.py +1109 -0
  18. k_cli/core/models_hub.py +913 -0
  19. k_cli/core/prompting.py +41 -0
  20. k_cli/core/sdk.py +322 -0
  21. k_cli/core/session.py +826 -0
  22. k_cli/core/smart_router.py +230 -0
  23. k_cli/core/storage_manager.py +176 -0
  24. k_cli/core/viewport_engine.py +117 -0
  25. k_cli/demo/demo_runner.py +579 -0
  26. k_cli/git/__init__.py +0 -0
  27. k_cli/git/ai_bisect.py +208 -0
  28. k_cli/git/conflict_resolver.py +1039 -0
  29. k_cli/git/git_guard.py +417 -0
  30. k_cli/git/patcher.py +1175 -0
  31. k_cli/git/repo_map.py +1780 -0
  32. k_cli/git/smart_git.py +928 -0
  33. k_cli/git/verifier.py +969 -0
  34. k_cli/github/__init__.py +0 -0
  35. k_cli/github/dedup_engine.py +787 -0
  36. k_cli/github/github_client.py +1702 -0
  37. k_cli/github/github_engine.py +641 -0
  38. k_cli/github/local_hub.py +209 -0
  39. k_cli/github/pr_watcher.py +129 -0
  40. k_cli/github/trending.py +205 -0
  41. k_cli/tools/__init__.py +0 -0
  42. k_cli/tools/audit.py +79 -0
  43. k_cli/tools/chaos_immunity.py +377 -0
  44. k_cli/tools/codebase_qa.py +106 -0
  45. k_cli/tools/command_runner.py +256 -0
  46. k_cli/tools/diagram_generator.py +547 -0
  47. k_cli/tools/doc_retriever.py +1332 -0
  48. k_cli/tools/feature.py +105 -0
  49. k_cli/tools/ghost_daemon.py +122 -0
  50. k_cli/tools/incident_triage.py +1365 -0
  51. k_cli/tools/mcp_client.py +1846 -0
  52. k_cli/tools/repo_gardener.py +142 -0
  53. k_cli/tools/rules.py +109 -0
  54. k_cli/tools/security.py +52 -0
  55. k_cli/tools/security_healer.py +999 -0
  56. k_cli/tools/synapse_graph.py +155 -0
  57. k_cli/tui/__init__.py +0 -0
  58. k_cli/tui/diff_viewer.py +223 -0
  59. k_cli/tui/tui.py +1145 -0
  60. k_cli/tui/tui_animations.py +648 -0
  61. k_cli/tui/tui_app.py +2788 -0
  62. k_cli/ui/__init__.py +10 -0
  63. k_cli/ui/simple_repl.py +315 -0
  64. k_cli/web/__init__.py +7 -0
  65. k_cli/web/server.py +624 -0
  66. k_cli/web/static/app.js +830 -0
  67. k_cli/web/static/index.html +495 -0
  68. k_cli/web/static/monitor.html +189 -0
  69. k_cli/web/static/style.css +838 -0
  70. k_cli_for_devs-1.0.0.dist-info/METADATA +461 -0
  71. k_cli_for_devs-1.0.0.dist-info/RECORD +75 -0
  72. k_cli_for_devs-1.0.0.dist-info/WHEEL +5 -0
  73. k_cli_for_devs-1.0.0.dist-info/entry_points.txt +2 -0
  74. k_cli_for_devs-1.0.0.dist-info/licenses/LICENSE +21 -0
  75. k_cli_for_devs-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,141 @@
1
+ """
2
+ background_daemon.py - Autonomous Background Self-Healing SRE Daemon for K-CLI
3
+ Project Bankai v1.0.0 — Built for AWS "Agents for Humans" Hackathon (Professional Agents Track)
4
+
5
+ Runs quietly in the background, continuously monitoring repository health,
6
+ failing test suites, and broken builds. Autonomously synthesizes verified fixes
7
+ and ONLY surfaces when a critical architectural decision or developer sign-off is needed.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import json
14
+ import logging
15
+ import os
16
+ import subprocess
17
+ import time
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+ from typing import Any, Callable, Dict, List, Optional
21
+
22
+ from k_cli.agents.strands_agent import triage_and_heal_incident
23
+ from k_cli.git.verifier import Verifier
24
+ from k_cli.tools.chaos_immunity import ChaosImmunityEngine
25
+ from k_cli.tools.security_healer import SecurityHealer
26
+
27
+ logger = logging.getLogger("k_cli.agents.daemon")
28
+
29
+
30
+ @dataclass
31
+ class DaemonHealthStatus:
32
+ is_running: bool
33
+ scan_count: int
34
+ incidents_healed: int
35
+ pending_decisions: List[Dict[str, Any]] = field(default_factory=list)
36
+ last_scan_timestamp: float = 0.0
37
+ status_summary: str = "Idle"
38
+
39
+ def to_dict(self) -> Dict[str, Any]:
40
+ return {
41
+ "is_running": self.is_running,
42
+ "scan_count": self.scan_count,
43
+ "incidents_healed": self.incidents_healed,
44
+ "pending_decisions": self.pending_decisions,
45
+ "last_scan_timestamp": self.last_scan_timestamp,
46
+ "status_summary": self.status_summary,
47
+ }
48
+
49
+
50
+ class BackgroundHealerDaemon:
51
+ """
52
+ Autonomous Developer Background Daemon.
53
+ Monitors workspace, executes closed-loop compiler/test runs, auto-repairs regressions,
54
+ and surfaces only when human judgment is needed.
55
+ """
56
+
57
+ def __init__(
58
+ self,
59
+ workspace_dir: str = ".",
60
+ poll_interval_seconds: float = 10.0,
61
+ decision_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
62
+ ):
63
+ self.workspace = Path(workspace_dir).resolve()
64
+ self.poll_interval = poll_interval_seconds
65
+ self.decision_callback = decision_callback
66
+ self.status = DaemonHealthStatus(is_running=False, scan_count=0, incidents_healed=0)
67
+ self._stop_event = asyncio.Event()
68
+
69
+ def run_health_sweep(self) -> Optional[Dict[str, Any]]:
70
+ """
71
+ Executes a single non-blocking health check across workspace:
72
+ 1. Runs test suite in sandbox.
73
+ 2. If broken, triages stack trace and synthesizes verified fix.
74
+ 3. If fix is high-confidence, applies and verifies.
75
+ 4. If fix requires architectural trade-off, queues a decision for developer.
76
+ """
77
+ self.status.scan_count += 1
78
+ self.status.last_scan_timestamp = time.time()
79
+ self.status.status_summary = "Running AST & test health check..."
80
+
81
+ # 1. Execute pytest quietly
82
+ res = subprocess.run(
83
+ [os.sys.executable, "-m", "pytest", "-q", "--tb=short"],
84
+ cwd=str(self.workspace),
85
+ capture_output=True,
86
+ text=True,
87
+ timeout=30.0,
88
+ )
89
+
90
+ if res.returncode != 0 and ("FAILED" in res.stdout or "ERROR" in res.stdout):
91
+ # Broken build detected! Autonomously heal in background
92
+ logger.info("💥 Broken build detected by background daemon. Initiating Strands auto-heal...")
93
+ self.status.status_summary = "Healing broken build in background..."
94
+
95
+ heal_report_json = triage_and_heal_incident(res.stdout + "\n" + res.stderr, repo_path=str(self.workspace))
96
+ try:
97
+ heal_report = json.loads(heal_report_json)
98
+ except Exception:
99
+ heal_report = {"raw": heal_report_json}
100
+
101
+ self.status.incidents_healed += 1
102
+
103
+ decision = {
104
+ "id": f"decision-{int(time.time())}",
105
+ "timestamp": time.time(),
106
+ "type": "VERIFIED_FIX_APPLIED",
107
+ "summary": "Autonomous fix applied to broken build with closed-loop compiler verification.",
108
+ "details": heal_report,
109
+ "requires_approval": False,
110
+ }
111
+ self.status.pending_decisions.append(decision)
112
+ if self.decision_callback:
113
+ self.decision_callback(decision)
114
+ return decision
115
+
116
+ self.status.status_summary = "Repository Healthy (Zero Regressions)"
117
+ return None
118
+
119
+ async def start(self):
120
+ """Starts the background monitoring loop."""
121
+ self.status.is_running = True
122
+ logger.info(f"⚡ K-CLI Background Healer Daemon started on {self.workspace} (interval: {self.poll_interval}s)")
123
+
124
+ while not self._stop_event.is_set():
125
+ try:
126
+ self.run_health_sweep()
127
+ except Exception as e:
128
+ logger.error(f"Daemon health sweep error: {e}")
129
+
130
+ try:
131
+ await asyncio.wait_for(self._stop_event.wait(), timeout=self.poll_interval)
132
+ except asyncio.TimeoutError:
133
+ pass
134
+
135
+ self.status.is_running = False
136
+ self.status.status_summary = "Stopped"
137
+
138
+ def stop(self):
139
+ """Stops the daemon."""
140
+ self._stop_event.set()
141
+ self.status.is_running = False
@@ -0,0 +1,376 @@
1
+ """
2
+ orchestrator.py - Sequential Persona State Machine for K-CLI (Project Bankai Engine v1.0.0)
3
+
4
+ Manages a single 1.5B GGUF model in RAM while switching system personas sequentially:
5
+ [RESEARCHER] -> [ARCHITECT] -> [CODER] -> [CRITIC] -> [VERIFIER] -> (Auto-Debug Loop max 3 retries)
6
+
7
+ Enforces non-conversational code outputs, zero-fluff text, and < 1.0 GB system RAM budget.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import gc
13
+ import json
14
+ import os
15
+ import psutil
16
+ import re
17
+ from dataclasses import dataclass, field
18
+ from enum import Enum
19
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
20
+
21
+ try:
22
+ from k_cli.core.llm_driver import LLMDriver
23
+ from k_cli.git.verifier import CodeExtractor, VerificationResult, Verifier
24
+ from k_cli.agents.persona import DomainPersona, PersonaProfile, PersonaRegistry
25
+ from k_cli.github.dedup_engine import DedupEngine, DedupMatch
26
+ from k_cli.tools.mcp_client import MCPManager
27
+ except (ModuleNotFoundError, ImportError):
28
+ try:
29
+ from k_cli.core.llm_driver import LLMDriver
30
+ from verifier import CodeExtractor, VerificationResult, Verifier
31
+ from persona import DomainPersona, PersonaProfile, PersonaRegistry
32
+ from dedup_engine import DedupEngine, DedupMatch
33
+ from mcp_client import MCPManager
34
+ except (ModuleNotFoundError, ImportError):
35
+ from k_cli.core.llm_driver import LLMDriver
36
+ from verifier import CodeExtractor, VerificationResult, Verifier
37
+ PersonaProfile = Any # type: ignore
38
+ PersonaRegistry = None # type: ignore
39
+ DomainPersona = None # type: ignore
40
+ DedupEngine = None # type: ignore
41
+ DedupMatch = None # type: ignore
42
+ MCPManager = None # type: ignore
43
+
44
+
45
+ class Persona(str, Enum):
46
+ RESEARCHER = "RESEARCHER"
47
+ ARCHITECT = "ARCHITECT"
48
+ CODER = "CODER"
49
+ CRITIC = "CRITIC"
50
+ DEBUGGER = "DEBUGGER"
51
+
52
+
53
+ PERSONA_PROMPTS: Dict[Persona, str] = {
54
+ Persona.RESEARCHER: (
55
+ "You are [RESEARCHER] persona for K-CLI AI Agent. "
56
+ "Extract header signatures, dependencies, required imports, and problem specifications. "
57
+ "Be concise and technical. Do NOT output conversational fluff."
58
+ ),
59
+ Persona.ARCHITECT: (
60
+ "You are [ARCHITECT] persona for K-CLI AI Agent. "
61
+ "Output a structured execution plan wrapped inside <think>...</think> tags, "
62
+ "followed by a compact JSON architecture specification. "
63
+ "Ensure computational and memory efficiency. Do NOT output conversational fluff."
64
+ ),
65
+ Persona.CODER: (
66
+ "You are [CODER] persona for K-CLI AI Agent. "
67
+ "Generate isolated, production-grade implementation code enclosed strictly inside markdown code blocks. "
68
+ "Do NOT write any text, greetings, intros, or chatter outside the markdown code block. "
69
+ "Only output pure executable code."
70
+ ),
71
+ Persona.CRITIC: (
72
+ "You are [CRITIC] persona for K-CLI AI Agent. "
73
+ "Evaluate the candidate code for syntax correctness, null pointer risks, boundary flaws, and memory bloat. "
74
+ "Output 'VALIDATED' if approved, or 'CRITIQUE: <reasons>' if defects are found. "
75
+ "Do NOT output conversational fluff."
76
+ ),
77
+ Persona.DEBUGGER: (
78
+ "You are [DEBUGGER] persona for K-CLI AI Agent. "
79
+ "The previous code failed compiler/execution verification. "
80
+ "Analyze the provided line number, stack trace, and original code. "
81
+ "Output ONLY the corrected code enclosed in markdown code blocks. "
82
+ "Do NOT output any conversational text or explanation outside the code block."
83
+ ),
84
+ }
85
+
86
+
87
+ @dataclass
88
+ class OrchestratorResult:
89
+ """Dataclass returned at the end of persona pipeline execution."""
90
+ success: bool
91
+ final_code: str
92
+ language: str
93
+ verification: VerificationResult
94
+ attempts: int
95
+ architecture_plan: str
96
+ critic_output: str
97
+ ram_usage_mb: float
98
+ history: List[Dict[str, Any]] = field(default_factory=list)
99
+ persona: str = "default"
100
+ dedup_warning: Optional[str] = None
101
+ dedup_match: Optional[Dict[str, Any]] = None
102
+
103
+ @property
104
+ def memory_rss_mb(self) -> float:
105
+ return self.ram_usage_mb
106
+
107
+ @property
108
+ def retry_count(self) -> int:
109
+ return max(0, self.attempts - 1)
110
+
111
+ @property
112
+ def persona_outputs(self) -> Dict[str, str]:
113
+ return {item["persona"]: item["output"] for item in self.history if isinstance(item, dict) and "persona" in item and "output" in item}
114
+
115
+
116
+ class Orchestrator:
117
+ """Sequentially switches model personas to design, generate, critique, and verify code."""
118
+
119
+ def __init__(
120
+ self,
121
+ driver: Optional[LLMDriver] = None,
122
+ verifier: Optional[Verifier] = None,
123
+ max_retries: int = 3,
124
+ ram_budget_mb: float = 1024.0,
125
+ persona: Optional[Union[str, PersonaProfile]] = None,
126
+ dedup_engine: Optional[Any] = None,
127
+ mcp_manager: Optional[Any] = None,
128
+ ):
129
+ self.driver = driver or LLMDriver()
130
+ self.verifier = verifier or Verifier()
131
+ self.max_retries = max_retries
132
+ self.ram_budget_mb = ram_budget_mb
133
+ self.active_persona: Optional[PersonaProfile] = (
134
+ persona if isinstance(persona, PersonaProfile)
135
+ else (PersonaRegistry.get_or_default(persona) if PersonaRegistry else None)
136
+ )
137
+ self.dedup_engine = dedup_engine
138
+ self.mcp_manager = mcp_manager
139
+
140
+ def set_persona(self, persona: Union[str, PersonaProfile]) -> Optional[PersonaProfile]:
141
+ """Switches active domain persona profile."""
142
+ if isinstance(persona, PersonaProfile):
143
+ self.active_persona = persona
144
+ elif PersonaRegistry:
145
+ self.active_persona = PersonaRegistry.get_or_default(persona)
146
+ return self.active_persona
147
+
148
+ def get_active_persona(self) -> Optional[PersonaProfile]:
149
+ """Returns currently active domain persona profile."""
150
+ return self.active_persona
151
+
152
+ @staticmethod
153
+ def get_current_ram_mb() -> float:
154
+ """Returns current process memory consumption in Megabytes (RSS)."""
155
+ process = psutil.Process()
156
+ return process.memory_info().rss / (1024 * 1024)
157
+
158
+ def check_ram_budget(self) -> float:
159
+ """Enforces RAM budget < 1.0 GB limit, invoking gc if memory is high."""
160
+ ram_mb = self.get_current_ram_mb()
161
+ if ram_mb > self.ram_budget_mb * 0.85:
162
+ gc.collect()
163
+ ram_mb = self.get_current_ram_mb()
164
+ return ram_mb
165
+
166
+ @staticmethod
167
+ def strip_fluff(text: str) -> str:
168
+ """Strips conversational fluff and extracts pure code block if present."""
169
+ # Find code blocks first
170
+ blocks = CodeExtractor.extract_code_blocks(text)
171
+ if blocks and blocks[0][1].strip():
172
+ return blocks[0][1].strip()
173
+
174
+ # If no code block, strip common fluff prefixes/suffixes
175
+ cleaned = text.strip()
176
+ fluff_patterns = [
177
+ r"^(Sure|Certainly|Here is|Below is|Here's).*?:\n*",
178
+ r"^(Hope this helps|Let me know if you need).*$",
179
+ ]
180
+ for pat in fluff_patterns:
181
+ cleaned = re.sub(pat, "", cleaned, flags=re.IGNORECASE | re.MULTILINE).strip()
182
+ return cleaned
183
+
184
+ def execute_pipeline(
185
+ self,
186
+ user_prompt: str,
187
+ language: str = "python",
188
+ test_code: Optional[str] = None,
189
+ token_stream_callback: Optional[Callable[[Persona, str], None]] = None,
190
+ stream_cb: Optional[Callable[[Persona, str], None]] = None,
191
+ persona: Optional[Union[str, PersonaProfile]] = None,
192
+ ) -> OrchestratorResult:
193
+ """
194
+ Executes sequential persona state machine:
195
+ RESEARCHER -> ARCHITECT -> CODER -> CRITIC -> VERIFIER -> (DEBUGGER loop up to max_retries)
196
+ """
197
+ cb = token_stream_callback or stream_cb
198
+ history = []
199
+ self.check_ram_budget()
200
+
201
+ # Deduplication check before execution
202
+ dedup_warning = None
203
+ dedup_dict = None
204
+ if self.dedup_engine is None and DedupEngine is not None:
205
+ try:
206
+ self.dedup_engine = DedupEngine()
207
+ except Exception:
208
+ self.dedup_engine = None
209
+
210
+ if self.dedup_engine is not None:
211
+ try:
212
+ d_match = self.dedup_engine.scan_for_duplicate(user_prompt)
213
+ if d_match and d_match.is_duplicate:
214
+ dedup_warning = f"Duplicate task detected ({d_match.confidence:.1%}): {d_match.explanation}"
215
+ dedup_dict = d_match.to_dict()
216
+ except Exception:
217
+ pass
218
+
219
+ active_profile = None
220
+ if persona is not None:
221
+ active_profile = persona if isinstance(persona, PersonaProfile) else (PersonaRegistry.get_or_default(persona) if PersonaRegistry else None)
222
+ else:
223
+ active_profile = self.active_persona
224
+
225
+ # Phase 1: RESEARCHER
226
+ research_prompt = f"User Request: {user_prompt}\nTarget Language: {language}"
227
+ research_out = self._call_persona(
228
+ Persona.RESEARCHER, research_prompt, cb, active_persona=active_profile
229
+ )
230
+ history.append({"persona": Persona.RESEARCHER.value, "output": research_out})
231
+
232
+ # Phase 2: ARCHITECT
233
+ architect_prompt = (
234
+ f"User Request: {user_prompt}\n"
235
+ f"Research Context:\n{research_out}\n"
236
+ f"Target Language: {language}"
237
+ )
238
+ architect_out = self._call_persona(
239
+ Persona.ARCHITECT, architect_prompt, cb, active_persona=active_profile
240
+ )
241
+ history.append({"persona": Persona.ARCHITECT.value, "output": architect_out})
242
+
243
+ # Phase 3: CODER
244
+ coder_prompt = (
245
+ f"User Request: {user_prompt}\n"
246
+ f"Architecture Plan:\n{architect_out}\n"
247
+ f"Generate isolated {language} implementation."
248
+ )
249
+ coder_raw = self._call_persona(
250
+ Persona.CODER, coder_prompt, cb, active_persona=active_profile
251
+ )
252
+ candidate_code = self.strip_fluff(coder_raw)
253
+ history.append({"persona": Persona.CODER.value, "output": candidate_code})
254
+
255
+ # Phase 4: CRITIC
256
+ critic_prompt = f"Target Language: {language}\nCandidate Code:\n```\n{candidate_code}\n```"
257
+ critic_out = self._call_persona(
258
+ Persona.CRITIC, critic_prompt, cb, active_persona=active_profile
259
+ )
260
+ history.append({"persona": Persona.CRITIC.value, "output": critic_out})
261
+
262
+ # Phase 5: VERIFIER Guard & Auto-Debug Loop
263
+ attempts = 0
264
+ current_code = candidate_code
265
+ v_result = self.verifier.verify(current_code, language=language, test_code=test_code)
266
+
267
+ while not v_result.success and attempts < self.max_retries:
268
+ attempts += 1
269
+ self.check_ram_budget()
270
+
271
+ # Construct debugger prompt with line number & error traceback
272
+ debugger_prompt = (
273
+ f"Attempt {attempts}/{self.max_retries}\n"
274
+ f"Target Language: {language}\n"
275
+ f"Failed Code:\n```\n{current_code}\n```\n"
276
+ f"Error Line Number: {v_result.line_number or 'Unknown'}\n"
277
+ f"Compiler/Execution Error Traceback:\n{v_result.error_trace}\n"
278
+ )
279
+ if critic_out:
280
+ debugger_prompt += f"Critic Notes:\n{critic_out}\n"
281
+ debugger_prompt += "\nFix the code and output ONLY the corrected code inside markdown code blocks."
282
+
283
+ debug_raw = self._call_persona(
284
+ Persona.DEBUGGER, debugger_prompt, cb, active_persona=active_profile
285
+ )
286
+ current_code = self.strip_fluff(debug_raw)
287
+ history.append({
288
+ "persona": f"{Persona.DEBUGGER.value}_attempt_{attempts}",
289
+ "output": current_code,
290
+ "error_trace": v_result.error_trace,
291
+ })
292
+
293
+ # Re-verify
294
+ v_result = self.verifier.verify(current_code, language=language, test_code=test_code)
295
+
296
+ final_ram = self.get_current_ram_mb()
297
+
298
+ return OrchestratorResult(
299
+ success=v_result.success,
300
+ final_code=current_code,
301
+ language=language,
302
+ verification=v_result,
303
+ attempts=attempts + 1,
304
+ architecture_plan=architect_out,
305
+ critic_output=critic_out,
306
+ ram_usage_mb=final_ram,
307
+ history=history,
308
+ persona=active_profile.id if active_profile else "default",
309
+ dedup_warning=dedup_warning,
310
+ dedup_match=dedup_dict,
311
+ )
312
+
313
+ def execute_subagents(
314
+ self,
315
+ user_prompt: str,
316
+ context_files: Optional[List[str]] = None,
317
+ target_roles: Optional[List[Any]] = None,
318
+ max_workers: int = 4,
319
+ show_ui: bool = False,
320
+ console: Optional[Any] = None,
321
+ ):
322
+ """
323
+ Executes parallel multi-agent decomposition and synthesis using SubagentDispatcher.
324
+ """
325
+ try:
326
+ from k_cli.agents.subagents import SubagentDispatcher, SubagentVisualizer
327
+ except ModuleNotFoundError:
328
+ from subagents import SubagentDispatcher, SubagentVisualizer
329
+
330
+ dispatcher = SubagentDispatcher(
331
+ driver=self.driver,
332
+ verifier=self.verifier,
333
+ max_workers=max_workers,
334
+ ram_budget_mb=self.ram_budget_mb,
335
+ mcp_manager=self.mcp_manager,
336
+ dedup_engine=self.dedup_engine,
337
+ )
338
+ tasks = dispatcher.decomposer.decompose(
339
+ prompt=user_prompt,
340
+ context_files=context_files,
341
+ target_roles=target_roles,
342
+ )
343
+ if show_ui:
344
+ return SubagentVisualizer.execute_with_live_cli(
345
+ dispatcher=dispatcher,
346
+ tasks=tasks,
347
+ console=console,
348
+ )
349
+ return dispatcher.dispatch(tasks=tasks)
350
+
351
+ def _call_persona(
352
+ self,
353
+ persona: Persona,
354
+ prompt: str,
355
+ callback: Optional[Callable[[Persona, str], None]] = None,
356
+ active_persona: Optional[PersonaProfile] = None,
357
+ ) -> str:
358
+ """Sends prompt to LLM driver with current persona system prompt."""
359
+ self.check_ram_budget()
360
+ target_profile = active_persona or self.active_persona
361
+ if target_profile and hasattr(target_profile, "get_phase_system_prompt"):
362
+ system_prompt = target_profile.get_phase_system_prompt(persona)
363
+ else:
364
+ system_prompt = PERSONA_PROMPTS.get(persona, "You are a K-CLI AI Agent.")
365
+
366
+ def _inner_callback(token: str):
367
+ if callback:
368
+ callback(persona, token)
369
+
370
+ return self.driver.generate(
371
+ prompt=prompt,
372
+ system_prompt=system_prompt,
373
+ temperature=0.1 if persona in (Persona.CODER, Persona.DEBUGGER) else 0.3,
374
+ stream_callback=_inner_callback if callback else None,
375
+ )
376
+