vox-code 2.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 (88) hide show
  1. vox_code-2.0.0.dist-info/METADATA +258 -0
  2. vox_code-2.0.0.dist-info/RECORD +88 -0
  3. vox_code-2.0.0.dist-info/WHEEL +4 -0
  4. vox_code-2.0.0.dist-info/entry_points.txt +3 -0
  5. voxcli/__init__.py +3 -0
  6. voxcli/__main__.py +5 -0
  7. voxcli/agent/__init__.py +12 -0
  8. voxcli/agent/agent.py +449 -0
  9. voxcli/agent/agent_budget.py +133 -0
  10. voxcli/agent/agent_orchestrator.py +414 -0
  11. voxcli/agent/plan_execute_agent.py +514 -0
  12. voxcli/agent/roles.py +80 -0
  13. voxcli/agent/sub_agent.py +351 -0
  14. voxcli/catalog.py +477 -0
  15. voxcli/chat.py +91 -0
  16. voxcli/cli/__init__.py +4 -0
  17. voxcli/cli/main.py +452 -0
  18. voxcli/cli/parser.py +71 -0
  19. voxcli/config.py +518 -0
  20. voxcli/gui/__main__.py +3 -0
  21. voxcli/gui/main.py +22 -0
  22. voxcli/gui/pet/__init__.py +5 -0
  23. voxcli/gui/pet/base.py +62 -0
  24. voxcli/gui/pet/coordinator.py +888 -0
  25. voxcli/gui/pet/data.py +430 -0
  26. voxcli/gui/pet/widgets.py +683 -0
  27. voxcli/gui/pet/windows.py +2298 -0
  28. voxcli/gui/pet/workers.py +54 -0
  29. voxcli/gui/pet_app.py +7 -0
  30. voxcli/hitl/__init__.py +11 -0
  31. voxcli/hitl/handler.py +11 -0
  32. voxcli/hitl/policy.py +32 -0
  33. voxcli/hitl/request.py +13 -0
  34. voxcli/hitl/result.py +11 -0
  35. voxcli/hitl/terminal_handler.py +64 -0
  36. voxcli/hitl/tool_registry.py +64 -0
  37. voxcli/llm/base.py +93 -0
  38. voxcli/llm/factory.py +178 -0
  39. voxcli/llm/ollama_client.py +137 -0
  40. voxcli/llm/openai_compatible.py +249 -0
  41. voxcli/memory/base.py +16 -0
  42. voxcli/memory/budget.py +53 -0
  43. voxcli/memory/compressor.py +198 -0
  44. voxcli/memory/entry.py +36 -0
  45. voxcli/memory/long_term.py +126 -0
  46. voxcli/memory/manager.py +101 -0
  47. voxcli/memory/retriever.py +72 -0
  48. voxcli/memory/short_term.py +84 -0
  49. voxcli/memory/tokenizer.py +21 -0
  50. voxcli/plan/__init__.py +5 -0
  51. voxcli/plan/execution_plan.py +225 -0
  52. voxcli/plan/planner.py +198 -0
  53. voxcli/plan/task.py +123 -0
  54. voxcli/policy/audit_log.py +111 -0
  55. voxcli/policy/command_guard.py +34 -0
  56. voxcli/policy/exception.py +5 -0
  57. voxcli/policy/path_guard.py +32 -0
  58. voxcli/prompting/__init__.py +7 -0
  59. voxcli/prompting/presenter.py +154 -0
  60. voxcli/rag/__init__.py +16 -0
  61. voxcli/rag/analyzer.py +89 -0
  62. voxcli/rag/chunk.py +17 -0
  63. voxcli/rag/chunker.py +137 -0
  64. voxcli/rag/embedding.py +75 -0
  65. voxcli/rag/formatter.py +40 -0
  66. voxcli/rag/index.py +96 -0
  67. voxcli/rag/relation.py +14 -0
  68. voxcli/rag/retriever.py +58 -0
  69. voxcli/rag/store.py +155 -0
  70. voxcli/rag/tokenizer.py +26 -0
  71. voxcli/runtime/__init__.py +6 -0
  72. voxcli/runtime/session_controller.py +386 -0
  73. voxcli/tool/__init__.py +3 -0
  74. voxcli/tool/tool_registry.py +433 -0
  75. voxcli/util/animation.py +219 -0
  76. voxcli/util/ansi.py +82 -0
  77. voxcli/util/markdown.py +98 -0
  78. voxcli/web/__init__.py +17 -0
  79. voxcli/web/base.py +20 -0
  80. voxcli/web/extractor.py +77 -0
  81. voxcli/web/factory.py +38 -0
  82. voxcli/web/fetch_result.py +27 -0
  83. voxcli/web/fetcher.py +42 -0
  84. voxcli/web/network_policy.py +49 -0
  85. voxcli/web/result.py +23 -0
  86. voxcli/web/searxng.py +55 -0
  87. voxcli/web/serpapi.py +53 -0
  88. voxcli/web/zhipu.py +55 -0
@@ -0,0 +1,414 @@
1
+ """Agent 编排器 - Multi-Agent 系统的主控"""
2
+
3
+ import json
4
+ import time
5
+ import logging
6
+ from concurrent.futures import ThreadPoolExecutor, Future
7
+ from enum import Enum
8
+ from io import StringIO
9
+ from typing import List, Optional, Dict, Set
10
+ from dataclasses import dataclass, field
11
+
12
+ from ..llm.base import LlmClient, Message
13
+ from ..memory.manager import MemoryManager
14
+ from ..tool import ToolRegistry
15
+ from ..util.ansi import heading, section, subtle
16
+ from .roles import AgentRole, AgentMessage, AgentMessageType
17
+ from .sub_agent import SubAgent
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ _MAX_RETRIES_PER_STEP = 2
22
+
23
+
24
+ class StepStatus(Enum):
25
+ PENDING = "PENDING"
26
+ RUNNING = "RUNNING"
27
+ COMPLETED = "COMPLETED"
28
+ FAILED = "FAILED"
29
+
30
+
31
+ @dataclass
32
+ class ExecutionStep:
33
+ id: str
34
+ description: str
35
+ type: str
36
+ dependencies: List[str] = field(default_factory=list)
37
+ result: Optional[str] = None
38
+ status: StepStatus = StepStatus.PENDING
39
+
40
+ @staticmethod
41
+ def pending(id: str, description: str, type: str,
42
+ dependencies: List[str]) -> "ExecutionStep":
43
+ return ExecutionStep(id=id, description=description, type=type,
44
+ dependencies=dependencies)
45
+
46
+ def with_result(self, result: str) -> "ExecutionStep":
47
+ return ExecutionStep(self.id, self.description, self.type,
48
+ self.dependencies, result, StepStatus.COMPLETED)
49
+
50
+ def with_failed(self, error: str) -> "ExecutionStep":
51
+ return ExecutionStep(self.id, self.description, self.type,
52
+ self.dependencies, error, StepStatus.FAILED)
53
+
54
+ def started(self) -> "ExecutionStep":
55
+ return ExecutionStep(self.id, self.description, self.type,
56
+ self.dependencies, self.result, StepStatus.RUNNING)
57
+
58
+
59
+ class AgentOrchestrator:
60
+ def __init__(self, llm_client: LlmClient,
61
+ tool_registry: Optional[ToolRegistry] = None,
62
+ memory_manager: Optional[MemoryManager] = None):
63
+ self._llm = llm_client
64
+ self._tool_registry = tool_registry or ToolRegistry()
65
+ self._planner = SubAgent("planner", AgentRole.PLANNER, llm_client, self._tool_registry)
66
+ self._workers = [
67
+ SubAgent("worker-1", AgentRole.WORKER, llm_client, self._tool_registry),
68
+ SubAgent("worker-2", AgentRole.WORKER, llm_client, self._tool_registry),
69
+ ]
70
+ self._reviewer = SubAgent("reviewer", AgentRole.REVIEWER, llm_client, self._tool_registry)
71
+ self._memory_manager = memory_manager or MemoryManager(llm_client)
72
+
73
+ @property
74
+ def memory_manager(self) -> MemoryManager:
75
+ return self._memory_manager
76
+
77
+ @property
78
+ def tool_registry(self) -> ToolRegistry:
79
+ return self._tool_registry
80
+
81
+ # ---- Public API ----
82
+
83
+ def run(self, user_input: str) -> str:
84
+ logger.info("Multi-Agent run started: inputLength=%d", len(user_input) if user_input else 0)
85
+ self._memory_manager.add_user_message(user_input)
86
+
87
+ # 1. Planning phase
88
+ print(heading("📋 第一阶段:规划"))
89
+ print("🧑‍💼 规划者正在分析任务...\n")
90
+
91
+ plan_msg = AgentMessage.task("orchestrator",
92
+ f"请为以下任务制定执行计划:\n{user_input}")
93
+ plan_result = self._planner.execute(plan_msg)
94
+ self._planner.clear_history()
95
+
96
+ if plan_result.type == AgentMessageType.ERROR:
97
+ return f"❌ 规划阶段失败,规划者 LLM 调用出错:{plan_result.content}"
98
+ if not plan_result.content or not plan_result.content.strip():
99
+ return "❌ 规划失败:规划者未能生成有效计划"
100
+
101
+ # 2. Parse plan
102
+ steps = self._parse_plan(plan_result.content)
103
+ if not steps:
104
+ return f"❌ 规划失败:无法解析执行计划\n原始输出:\n{plan_result.content}"
105
+
106
+ print(heading("📋 执行计划"))
107
+ print(self._summarize_steps(steps) + "\n")
108
+
109
+ # 3. Execution phase
110
+ print(heading("⚡ 第二阶段:执行"))
111
+ retry_count: Dict[str, int] = {}
112
+ single_step_cursor = 0
113
+ batch_index = 0
114
+
115
+ while True:
116
+ executable = self._get_executable_steps(steps)
117
+ if not executable:
118
+ break
119
+ batch_index += 1
120
+
121
+ if len(executable) == 1:
122
+ step = executable[0]
123
+ worker = self._workers[single_step_cursor % len(self._workers)]
124
+ single_step_cursor += 1
125
+ context = self._build_step_context(steps, step)
126
+ self._run_step(step, steps, retry_count, worker, self._reviewer, context)
127
+ worker.clear_history()
128
+ else:
129
+ print(f"⚡ 批次 #{batch_index}:{len(executable)} 个独立步骤并行执行"
130
+ f"(最多 {len(self._workers)} 个并发 Worker)\n")
131
+ self._run_batch_parallel(executable, steps, retry_count)
132
+
133
+ # 4. Report skipped steps
134
+ for step in steps:
135
+ if step.status == StepStatus.PENDING:
136
+ print(f"⏭️ 步骤 [{step.id}] 因前置步骤失败被跳过: {step.description}")
137
+
138
+ # 5. Build result
139
+ final = self._build_final_result(steps)
140
+ self._memory_manager.add_assistant_message(f"[多Agent结果] {final}")
141
+ return final
142
+
143
+ # ---- Internal ----
144
+
145
+ def _parse_plan(self, plan_json: str) -> List[ExecutionStep]:
146
+ try:
147
+ cleaned = plan_json.replace("```json", "").replace("```", "").strip()
148
+ root = json.loads(cleaned)
149
+ steps_node = root.get("steps") or root.get("tasks")
150
+ if not steps_node or not isinstance(steps_node, list):
151
+ logger.warning("Plan JSON has no 'steps' or 'tasks' array")
152
+ return []
153
+
154
+ id_mapping: Dict[str, str] = {}
155
+ steps: List[ExecutionStep] = []
156
+
157
+ for i, node in enumerate(steps_node, 1):
158
+ orig = node.get("id", f"step_{i}")
159
+ new_id = f"step_{i}"
160
+ id_mapping[orig] = new_id
161
+
162
+ for i, node in enumerate(steps_node, 1):
163
+ new_id = f"step_{i}"
164
+ desc = node.get("description", "")
165
+ type_ = node.get("type", "COMMAND")
166
+ deps = [id_mapping.get(d, d) for d in node.get("dependencies", [])]
167
+ steps.append(ExecutionStep.pending(new_id, desc, type_, deps))
168
+
169
+ return steps
170
+ except Exception as e:
171
+ logger.error("Failed to parse plan JSON", exc_info=True)
172
+ return []
173
+
174
+ def _get_executable_steps(self, steps: List[ExecutionStep]) -> List[ExecutionStep]:
175
+ status_map = {s.id: s.status for s in steps}
176
+ return [s for s in steps
177
+ if s.status == StepStatus.PENDING
178
+ and all(status_map.get(d) == StepStatus.COMPLETED for d in s.dependencies)]
179
+
180
+ def _parse_review_approval(self, review_content: Optional[str]) -> bool:
181
+ if not review_content:
182
+ return False
183
+ try:
184
+ cleaned = review_content.replace("```json", "").replace("```", "").strip()
185
+ root = json.loads(cleaned)
186
+ approved = root.get("approved")
187
+ if approved is None:
188
+ return False
189
+ return bool(approved)
190
+ except json.JSONDecodeError:
191
+ lower = review_content.lower()
192
+ has_negative = any(kw in lower for kw in
193
+ ["未通过", "不通过", "不合格", "有问题",
194
+ '"approved": false', '"approved":false'])
195
+ has_positive = any(kw in lower for kw in
196
+ ["通过", "合格", '"approved": true', '"approved":true'])
197
+ if has_negative:
198
+ return False
199
+ if not has_positive:
200
+ return False
201
+ return True
202
+
203
+ def _parse_review_issues(self, review_content: Optional[str]) -> str:
204
+ if not review_content:
205
+ return "审查未通过,请改进执行结果"
206
+ try:
207
+ cleaned = review_content.replace("```json", "").replace("```", "").strip()
208
+ root = json.loads(cleaned)
209
+ issues = root.get("issues", [])
210
+ if issues:
211
+ return "\n".join(f"- {i}" for i in issues)
212
+ suggestions = root.get("suggestions", [])
213
+ if suggestions:
214
+ return "\n".join(f"- {s}" for s in suggestions)
215
+ summary = root.get("summary", "")
216
+ if summary:
217
+ return summary
218
+ except (json.JSONDecodeError, Exception):
219
+ pass
220
+ return "审查未通过,请改进执行结果"
221
+
222
+ def _run_step(self, step: ExecutionStep, steps: List[ExecutionStep],
223
+ retry_count: Dict[str, int], worker: SubAgent,
224
+ reviewer: SubAgent, context: str):
225
+ print(f"🛠️ {worker.name} 执行步骤 [{step.id}]: {step.description}")
226
+
227
+ task_msg = AgentMessage.task("orchestrator", step.description)
228
+ result = worker.execute_with_context(task_msg, context)
229
+
230
+ if result.type == AgentMessageType.ERROR:
231
+ self._update_step(steps, step.id, step.with_failed(result.content))
232
+ print(f"❌ 步骤 [{step.id}] 执行失败:{result.content}\n")
233
+ return
234
+
235
+ if not result.content or not result.content.strip():
236
+ self._update_step(steps, step.id, step.with_failed("执行结果为空"))
237
+ print(f"❌ 步骤 [{step.id}] 执行失败:结果为空\n")
238
+ return
239
+
240
+ print(f"🔍 {reviewer.name} 正在审查步骤 [{step.id}] 的结果...")
241
+ review_result = reviewer.review(step.description, result.content)
242
+ reviewer.clear_history()
243
+
244
+ if review_result.type == AgentMessageType.ERROR:
245
+ logger.warning("Reviewer failed for step %s: %s", step.id, review_result.content)
246
+ self._update_step(steps, step.id, step.with_result(result.content))
247
+ return
248
+
249
+ approved = self._parse_review_approval(review_result.content)
250
+ accepted = result.content
251
+
252
+ if approved:
253
+ self._update_step(steps, step.id, step.with_result(accepted))
254
+ print(f"✅ 步骤 [{step.id}] 审查通过\n")
255
+ return
256
+
257
+ retries = retry_count.get(step.id, 0)
258
+ issues = self._parse_review_issues(review_result.content)
259
+ logger.info("Step %s rejected (retry %d/%d): %s", step.id, retries, _MAX_RETRIES_PER_STEP, issues)
260
+
261
+ while not approved and retries < _MAX_RETRIES_PER_STEP:
262
+ retries += 1
263
+ retry_count[step.id] = retries
264
+ print(f"⚠️ 步骤 [{step.id}] 审查未通过,正在重新执行...")
265
+ print(f" 反馈: {issues}\n")
266
+
267
+ feedback_context = f"{context}\n\n之前的执行结果被审查拒绝,原因:\n{issues}"
268
+ retry_result = worker.execute_with_context(task_msg, feedback_context)
269
+
270
+ if retry_result.type == AgentMessageType.ERROR:
271
+ issues = f"重试时 LLM 调用失败:{retry_result.content}"
272
+ approved = False
273
+ continue
274
+ if not retry_result.content or not retry_result.content.strip():
275
+ accepted = "执行结果为空"
276
+ approved = False
277
+ issues = "执行结果为空"
278
+ continue
279
+
280
+ accepted = retry_result.content
281
+ retry_review = reviewer.review(step.description, accepted)
282
+ reviewer.clear_history()
283
+
284
+ if retry_review.type == AgentMessageType.ERROR:
285
+ approved = True
286
+ issues = ""
287
+ break
288
+
289
+ approved = self._parse_review_approval(retry_review.content)
290
+ issues = self._parse_review_issues(retry_review.content)
291
+
292
+ self._update_step(steps, step.id, step.with_result(accepted))
293
+ status = "✅" if approved else "⚠️"
294
+ msg = "重试后审查通过" if approved else "超过最大重试次数,保留当前结果"
295
+ print(f"{status} 步骤 [{step.id}] {msg}\n")
296
+
297
+ def _run_batch_parallel(self, batch: List[ExecutionStep], steps: List[ExecutionStep],
298
+ retry_count: Dict[str, int]):
299
+ parallelism = min(len(batch), len(self._workers))
300
+ results: List[Optional[ExecutionStep]] = [None] * len(batch)
301
+
302
+ with ThreadPoolExecutor(max_workers=parallelism) as executor:
303
+ future_map: Dict[Future, int] = {}
304
+ for i, step in enumerate(batch):
305
+ future = executor.submit(self._run_step_isolated, step, steps, retry_count)
306
+ future_map[future] = i
307
+
308
+ for future in future_map:
309
+ idx = future_map[future]
310
+ try:
311
+ results[idx] = future.result()
312
+ except Exception as e:
313
+ step = batch[idx]
314
+ logger.error("Parallel step %s failed", step.id, exc_info=True)
315
+ results[idx] = step.with_failed(str(e))
316
+
317
+ for i, step in enumerate(batch):
318
+ if results[i] is not None:
319
+ self._update_step(steps, step.id, results[i])
320
+
321
+ def _run_step_isolated(self, step: ExecutionStep, steps: List[ExecutionStep],
322
+ retry_count: Dict[str, int]) -> Optional[ExecutionStep]:
323
+ worker_pool = list(self._workers)
324
+ worker = worker_pool[0]
325
+ local_reviewer = SubAgent(f"reviewer-{step.id}", AgentRole.REVIEWER,
326
+ self._llm, self._tool_registry)
327
+ context = self._build_step_context(steps, step)
328
+
329
+ buf = StringIO()
330
+ # We use print capture but here we simulate the same flow
331
+ task_msg = AgentMessage.task("orchestrator", step.description)
332
+ result = worker.execute_with_context(task_msg, context)
333
+
334
+ if result.type == AgentMessageType.ERROR:
335
+ return step.with_failed(result.content)
336
+ if not result.content or not result.content.strip():
337
+ return step.with_failed("执行结果为空")
338
+
339
+ review_result = local_reviewer.review(step.description, result.content)
340
+ approved = self._parse_review_approval(review_result.content)
341
+
342
+ if approved:
343
+ return step.with_result(result.content)
344
+
345
+ # Retries
346
+ retries = 0
347
+ issues = self._parse_review_issues(review_result.content)
348
+ accepted = result.content
349
+
350
+ while not approved and retries < _MAX_RETRIES_PER_STEP:
351
+ retries += 1
352
+ feedback_context = f"{context}\n\n之前的执行结果被审查拒绝,原因:\n{issues}"
353
+ retry_result = worker.execute_with_context(task_msg, feedback_context)
354
+ if retry_result.type == AgentMessageType.ERROR:
355
+ break
356
+ if not retry_result.content or not retry_result.content.strip():
357
+ break
358
+ accepted = retry_result.content
359
+ retry_review = local_reviewer.review(step.description, accepted)
360
+ approved = self._parse_review_approval(retry_review.content)
361
+ issues = self._parse_review_issues(retry_review.content)
362
+
363
+ return step.with_result(accepted)
364
+
365
+ def _build_step_context(self, steps: List[ExecutionStep], current: ExecutionStep) -> str:
366
+ parts = ["总任务上下文:"]
367
+ for step in steps:
368
+ if step.status == StepStatus.COMPLETED and current.id in step.dependencies:
369
+ parts.append(f"已完成的依赖步骤 [{step.id}]: {step.description}")
370
+ if step.result:
371
+ preview = step.result[:500] if len(step.result) > 500 else step.result
372
+ parts.append(f"结果:{preview}")
373
+ parts.append("")
374
+ return "\n".join(parts)
375
+
376
+ @staticmethod
377
+ def _summarize_steps(steps: List[ExecutionStep]) -> str:
378
+ lines = []
379
+ for s in steps:
380
+ deps = ", ".join(s.dependencies) if s.dependencies else "无"
381
+ icon = "✅" if s.status == StepStatus.COMPLETED else "⏳"
382
+ lines.append(f" {icon} {s.id} [{s.type}] {s.description} (依赖: {deps})")
383
+ return "\n".join(lines)
384
+
385
+ @staticmethod
386
+ def _update_step(steps: List[ExecutionStep], step_id: str, updated: ExecutionStep):
387
+ for i, s in enumerate(steps):
388
+ if s.id == step_id:
389
+ steps[i] = updated
390
+ return
391
+
392
+ @staticmethod
393
+ def _build_final_result(steps: List[ExecutionStep]) -> str:
394
+ all_done = all(s.status == StepStatus.COMPLETED for s in steps)
395
+ has_failure = any(s.status == StepStatus.FAILED for s in steps)
396
+
397
+ lines = []
398
+ if all_done:
399
+ lines.append("✅ 多 Agent 协作任务完成!")
400
+ elif has_failure:
401
+ lines.append("⚠️ 多 Agent 协作任务未完全完成,存在失败步骤。")
402
+ else:
403
+ lines.append("⚠️ 多 Agent 协作任务部分完成,仍有未执行步骤。")
404
+
405
+ lines.append("\n📋 执行总结:")
406
+ for s in steps:
407
+ icon = {"COMPLETED": "✅", "FAILED": "❌", "RUNNING": "▶️", "PENDING": "⏳"}.get(
408
+ s.status.value, "⏳")
409
+ lines.append(f"[{s.id}] {icon} {s.description}")
410
+ if s.result:
411
+ preview = s.result[:120] if len(s.result) > 120 else s.result
412
+ lines.append(f" 结果:{preview}")
413
+
414
+ return "\n".join(lines)