devsquad 3.6.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.
- devsquad-3.6.0.dist-info/METADATA +944 -0
- devsquad-3.6.0.dist-info/RECORD +95 -0
- devsquad-3.6.0.dist-info/WHEEL +5 -0
- devsquad-3.6.0.dist-info/entry_points.txt +2 -0
- devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
- devsquad-3.6.0.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/ai_semantic_matcher.py +512 -0
- scripts/alert_manager.py +505 -0
- scripts/api/__init__.py +43 -0
- scripts/api/models.py +386 -0
- scripts/api/routes/__init__.py +20 -0
- scripts/api/routes/dispatch.py +348 -0
- scripts/api/routes/lifecycle.py +330 -0
- scripts/api/routes/metrics_gates.py +347 -0
- scripts/api_server.py +318 -0
- scripts/auth.py +451 -0
- scripts/cli/__init__.py +1 -0
- scripts/cli/cli_visual.py +642 -0
- scripts/cli.py +1094 -0
- scripts/collaboration/__init__.py +212 -0
- scripts/collaboration/_version.py +1 -0
- scripts/collaboration/agent_briefing.py +656 -0
- scripts/collaboration/ai_semantic_matcher.py +260 -0
- scripts/collaboration/anchor_checker.py +281 -0
- scripts/collaboration/anti_rationalization.py +470 -0
- scripts/collaboration/async_integration_example.py +255 -0
- scripts/collaboration/batch_scheduler.py +149 -0
- scripts/collaboration/checkpoint_manager.py +561 -0
- scripts/collaboration/ci_feedback_adapter.py +351 -0
- scripts/collaboration/code_map_generator.py +247 -0
- scripts/collaboration/concern_pack_loader.py +352 -0
- scripts/collaboration/confidence_score.py +496 -0
- scripts/collaboration/config_loader.py +188 -0
- scripts/collaboration/consensus.py +244 -0
- scripts/collaboration/context_compressor.py +533 -0
- scripts/collaboration/coordinator.py +668 -0
- scripts/collaboration/dispatcher.py +1636 -0
- scripts/collaboration/dual_layer_context.py +128 -0
- scripts/collaboration/enhanced_worker.py +539 -0
- scripts/collaboration/feature_usage_tracker.py +206 -0
- scripts/collaboration/five_axis_consensus.py +334 -0
- scripts/collaboration/input_validator.py +401 -0
- scripts/collaboration/integration_example.py +287 -0
- scripts/collaboration/intent_workflow_mapper.py +350 -0
- scripts/collaboration/language_parsers.py +269 -0
- scripts/collaboration/lifecycle_protocol.py +1446 -0
- scripts/collaboration/llm_backend.py +453 -0
- scripts/collaboration/llm_cache.py +448 -0
- scripts/collaboration/llm_cache_async.py +347 -0
- scripts/collaboration/llm_retry.py +387 -0
- scripts/collaboration/llm_retry_async.py +389 -0
- scripts/collaboration/mce_adapter.py +597 -0
- scripts/collaboration/memory_bridge.py +1607 -0
- scripts/collaboration/models.py +537 -0
- scripts/collaboration/null_providers.py +297 -0
- scripts/collaboration/operation_classifier.py +289 -0
- scripts/collaboration/output_slicer.py +225 -0
- scripts/collaboration/performance_monitor.py +462 -0
- scripts/collaboration/permission_guard.py +865 -0
- scripts/collaboration/prompt_assembler.py +756 -0
- scripts/collaboration/prompt_variant_generator.py +483 -0
- scripts/collaboration/protocols.py +267 -0
- scripts/collaboration/report_formatter.py +352 -0
- scripts/collaboration/retrospective.py +279 -0
- scripts/collaboration/role_matcher.py +92 -0
- scripts/collaboration/role_template_market.py +352 -0
- scripts/collaboration/rule_collector.py +678 -0
- scripts/collaboration/scratchpad.py +346 -0
- scripts/collaboration/skill_registry.py +151 -0
- scripts/collaboration/skillifier.py +878 -0
- scripts/collaboration/standardized_role_template.py +317 -0
- scripts/collaboration/task_completion_checker.py +237 -0
- scripts/collaboration/test_quality_guard.py +695 -0
- scripts/collaboration/unified_gate_engine.py +598 -0
- scripts/collaboration/usage_tracker.py +309 -0
- scripts/collaboration/user_friendly_error.py +176 -0
- scripts/collaboration/verification_gate.py +312 -0
- scripts/collaboration/warmup_manager.py +635 -0
- scripts/collaboration/worker.py +513 -0
- scripts/collaboration/workflow_engine.py +684 -0
- scripts/dashboard.py +1088 -0
- scripts/generate_benchmark_report.py +786 -0
- scripts/history_manager.py +604 -0
- scripts/mcp_server.py +289 -0
- skills/__init__.py +32 -0
- skills/dispatch/handler.py +52 -0
- skills/intent/handler.py +59 -0
- skills/registry.py +67 -0
- skills/retrospective/__init__.py +0 -0
- skills/retrospective/handler.py +125 -0
- skills/review/handler.py +356 -0
- skills/security/handler.py +454 -0
- skills/test/__init__.py +0 -0
- skills/test/handler.py +78 -0
|
@@ -0,0 +1,1636 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
V3 Multi-Agent Collaboration Dispatcher (Unified Entry Point)
|
|
5
|
+
|
|
6
|
+
This is the V3 unified entry point that chains all collaboration components
|
|
7
|
+
into a single usable pipeline:
|
|
8
|
+
|
|
9
|
+
User Task → [Intent Recognition] → [Role Assignment] → [Coordinator Orchestration]
|
|
10
|
+
→ [Parallel Worker Execution] → [Scratchpad Sharing] → [Consensus Decision]
|
|
11
|
+
→ [Context Compression] → [Permission Check] → [Memory Capture] → [Result Return]
|
|
12
|
+
|
|
13
|
+
Integrated Components:
|
|
14
|
+
- WarmupManager: Startup warmup to reduce cold-start latency
|
|
15
|
+
- Coordinator + Worker + Scratchpad: Multi-agent collaboration core
|
|
16
|
+
- BatchScheduler: Parallel/sequential hybrid scheduling
|
|
17
|
+
- ConsensusEngine: Weighted voting consensus mechanism with veto power
|
|
18
|
+
- ContextCompressor: 4-level context compression to prevent overflow
|
|
19
|
+
- PermissionGuard: Permission guard for secure operation checks
|
|
20
|
+
- Skillifier: Learns from successful patterns to generate new Skills
|
|
21
|
+
- MemoryBridge: Cross-session memory bridge (with MCE + WorkBuddy Claw integration)
|
|
22
|
+
|
|
23
|
+
Example Usage:
|
|
24
|
+
from scripts.collaboration.dispatcher import MultiAgentDispatcher
|
|
25
|
+
|
|
26
|
+
disp = MultiAgentDispatcher()
|
|
27
|
+
result = disp.dispatch("Design a user authentication system")
|
|
28
|
+
print(result.summary)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import os
|
|
32
|
+
import sys
|
|
33
|
+
import time
|
|
34
|
+
import uuid
|
|
35
|
+
import json
|
|
36
|
+
import re
|
|
37
|
+
import logging
|
|
38
|
+
import tempfile
|
|
39
|
+
import threading
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from datetime import datetime
|
|
42
|
+
from typing import Dict, List, Optional, Any, Tuple
|
|
43
|
+
from dataclasses import dataclass, field
|
|
44
|
+
from collections import deque
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
from .models import (
|
|
49
|
+
EntryType, EntryStatus, TaskDefinition, ExecutionPlan,
|
|
50
|
+
TaskBatch, BatchMode, ScheduleResult, WorkerResult,
|
|
51
|
+
ConsensusRecord, DecisionOutcome, ROLE_WEIGHTS,
|
|
52
|
+
ROLE_REGISTRY, ROLE_ALIASES, resolve_role_id, get_core_roles, get_planned_roles,
|
|
53
|
+
get_all_role_ids, get_cli_role_list, RoleDefinition,
|
|
54
|
+
)
|
|
55
|
+
from .scratchpad import Scratchpad, ScratchpadEntry
|
|
56
|
+
from .worker import Worker, WorkerFactory
|
|
57
|
+
from .consensus import ConsensusEngine
|
|
58
|
+
from .coordinator import Coordinator
|
|
59
|
+
from .batch_scheduler import BatchScheduler
|
|
60
|
+
from .context_compressor import ContextCompressor, Message, MessageType, CompressionLevel
|
|
61
|
+
from .permission_guard import (
|
|
62
|
+
PermissionGuard, PermissionLevel, ActionType,
|
|
63
|
+
ProposedAction, PermissionRule,
|
|
64
|
+
)
|
|
65
|
+
from .skillifier import Skillifier, ExecutionRecord, ExecutionStep, PGActionType
|
|
66
|
+
from .warmup_manager import WarmupManager, WarmupConfig, WarmupLayer
|
|
67
|
+
from .memory_bridge import (
|
|
68
|
+
MemoryBridge, MemoryConfig as MBConfig,
|
|
69
|
+
MemoryType, MemoryItem, MemoryQuery,
|
|
70
|
+
KnowledgeItem, UserFeedback, EpisodicMemory,
|
|
71
|
+
PersistedPattern, AnalysisCase, ErrorContext,
|
|
72
|
+
)
|
|
73
|
+
from .test_quality_guard import (
|
|
74
|
+
TestQualityGuard, TestQualityReport,
|
|
75
|
+
)
|
|
76
|
+
from .usage_tracker import track_usage
|
|
77
|
+
from .input_validator import InputValidator
|
|
78
|
+
from .user_friendly_error import UserFriendlyError, translate_validation_result, make_user_friendly_error
|
|
79
|
+
from .role_matcher import RoleMatcher
|
|
80
|
+
from .report_formatter import ReportFormatter
|
|
81
|
+
from .concern_pack_loader import ConcernPackLoader
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class PerformanceMetric:
|
|
86
|
+
"""单次性能指标"""
|
|
87
|
+
timestamp: str
|
|
88
|
+
task_description: str
|
|
89
|
+
total_duration: float
|
|
90
|
+
step_timings: Dict[str, float]
|
|
91
|
+
success: bool
|
|
92
|
+
error_count: int
|
|
93
|
+
role_count: int
|
|
94
|
+
|
|
95
|
+
def __post_init__(self):
|
|
96
|
+
if len(self.task_description) > 50:
|
|
97
|
+
self.task_description = self.task_description[:50] + "..."
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class PerformanceThresholds:
|
|
102
|
+
"""性能阈值配置"""
|
|
103
|
+
total_duration_warning: float = 30.0
|
|
104
|
+
total_duration_critical: float = 60.0
|
|
105
|
+
step_warnings: Dict[str, float] = field(default_factory=lambda: {
|
|
106
|
+
"analyze": 2.0,
|
|
107
|
+
"warmup": 1.0,
|
|
108
|
+
"plan": 3.0,
|
|
109
|
+
"spawn": 1.0,
|
|
110
|
+
"execute": 20.0,
|
|
111
|
+
"collect": 2.0,
|
|
112
|
+
"consensus": 5.0,
|
|
113
|
+
"compress": 1.0,
|
|
114
|
+
"permission": 0.5,
|
|
115
|
+
"memory": 2.0,
|
|
116
|
+
"skillify": 2.0,
|
|
117
|
+
})
|
|
118
|
+
step_criticals: Dict[str, float] = field(default_factory=lambda: {
|
|
119
|
+
"analyze": 5.0,
|
|
120
|
+
"warmup": 3.0,
|
|
121
|
+
"plan": 8.0,
|
|
122
|
+
"spawn": 3.0,
|
|
123
|
+
"execute": 45.0,
|
|
124
|
+
"collect": 5.0,
|
|
125
|
+
"consensus": 10.0,
|
|
126
|
+
"compress": 3.0,
|
|
127
|
+
"permission": 1.0,
|
|
128
|
+
"memory": 5.0,
|
|
129
|
+
"skillify": 5.0,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class PerformanceMonitor:
|
|
134
|
+
"""
|
|
135
|
+
性能监控器
|
|
136
|
+
|
|
137
|
+
功能:
|
|
138
|
+
- 收集每次调度的性能指标
|
|
139
|
+
- 检测性能异常和回归
|
|
140
|
+
- 维护滑动窗口统计
|
|
141
|
+
- 生成性能报告
|
|
142
|
+
- 触发阈值告警
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(self, window_size: int = 100, thresholds: Optional[PerformanceThresholds] = None):
|
|
146
|
+
self.window_size = window_size
|
|
147
|
+
self.thresholds = thresholds or PerformanceThresholds()
|
|
148
|
+
self._metrics: deque = deque(maxlen=window_size)
|
|
149
|
+
self._lock = threading.Lock()
|
|
150
|
+
|
|
151
|
+
def record(self, metric: PerformanceMetric):
|
|
152
|
+
"""记录一次性能指标"""
|
|
153
|
+
with self._lock:
|
|
154
|
+
self._metrics.append(metric)
|
|
155
|
+
|
|
156
|
+
# 实时检查阈值
|
|
157
|
+
warnings, criticals = self._check_thresholds(metric)
|
|
158
|
+
if criticals:
|
|
159
|
+
logger.critical("PERFORMANCE CRITICAL: %s", json.dumps(criticals, ensure_ascii=False))
|
|
160
|
+
elif warnings:
|
|
161
|
+
logger.warning("PERFORMANCE WARNING: %s", json.dumps(warnings, ensure_ascii=False))
|
|
162
|
+
|
|
163
|
+
def _check_thresholds(self, metric: PerformanceMetric) -> Tuple[List[Dict], List[Dict]]:
|
|
164
|
+
"""检查指标是否超过阈值"""
|
|
165
|
+
warnings = []
|
|
166
|
+
criticals = []
|
|
167
|
+
|
|
168
|
+
# 总耗时检查
|
|
169
|
+
if metric.total_duration > self.thresholds.total_duration_critical:
|
|
170
|
+
criticals.append({
|
|
171
|
+
"type": "total_duration",
|
|
172
|
+
"value": metric.total_duration,
|
|
173
|
+
"threshold": self.thresholds.total_duration_critical,
|
|
174
|
+
"task": metric.task_description[:50],
|
|
175
|
+
})
|
|
176
|
+
elif metric.total_duration > self.thresholds.total_duration_warning:
|
|
177
|
+
warnings.append({
|
|
178
|
+
"type": "total_duration",
|
|
179
|
+
"value": metric.total_duration,
|
|
180
|
+
"threshold": self.thresholds.total_duration_warning,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
# 各步骤耗时检查
|
|
184
|
+
for step, duration in metric.step_timings.items():
|
|
185
|
+
critical_threshold = self.thresholds.step_criticals.get(step)
|
|
186
|
+
warning_threshold = self.thresholds.step_warnings.get(step)
|
|
187
|
+
|
|
188
|
+
if critical_threshold and duration > critical_threshold:
|
|
189
|
+
criticals.append({
|
|
190
|
+
"type": f"step_{step}",
|
|
191
|
+
"value": duration,
|
|
192
|
+
"threshold": critical_threshold,
|
|
193
|
+
})
|
|
194
|
+
elif warning_threshold and duration > warning_threshold:
|
|
195
|
+
warnings.append({
|
|
196
|
+
"type": f"step_{step}",
|
|
197
|
+
"value": duration,
|
|
198
|
+
"threshold": warning_threshold,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
return warnings, criticals
|
|
202
|
+
|
|
203
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
204
|
+
"""获取滑动窗口内的统计数据"""
|
|
205
|
+
with self._lock:
|
|
206
|
+
if not self._metrics:
|
|
207
|
+
return {"count": 0}
|
|
208
|
+
|
|
209
|
+
metrics_list = list(self._metrics)
|
|
210
|
+
count = len(metrics_list)
|
|
211
|
+
|
|
212
|
+
durations = [m.total_duration for m in metrics_list]
|
|
213
|
+
successes = sum(1 for m in metrics_list if m.success)
|
|
214
|
+
|
|
215
|
+
stats = {
|
|
216
|
+
"count": count,
|
|
217
|
+
"success_rate": successes / count,
|
|
218
|
+
"duration": {
|
|
219
|
+
"min": min(durations),
|
|
220
|
+
"max": max(durations),
|
|
221
|
+
"avg": sum(durations) / count,
|
|
222
|
+
"p50": sorted(durations)[int(count * 0.5)],
|
|
223
|
+
"p95": sorted(durations)[int(count * 0.95)],
|
|
224
|
+
"p99": sorted(durations)[int(count * 0.99)] if count > 20 else max(durations),
|
|
225
|
+
},
|
|
226
|
+
"errors_per_dispatch_avg": sum(m.error_count for m in metrics_list) / count,
|
|
227
|
+
"roles_per_dispatch_avg": sum(m.role_count for m in metrics_list) / count,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# 各步骤统计
|
|
231
|
+
step_stats = {}
|
|
232
|
+
all_steps = set()
|
|
233
|
+
for m in metrics_list:
|
|
234
|
+
all_steps.update(m.step_timings.keys())
|
|
235
|
+
|
|
236
|
+
for step in all_steps:
|
|
237
|
+
step_durations = [m.step_timings.get(step, 0) for m in metrics_list if step in m.step_timings]
|
|
238
|
+
if step_durations:
|
|
239
|
+
step_stats[step] = {
|
|
240
|
+
"avg": sum(step_durations) / len(step_durations),
|
|
241
|
+
"max": max(step_durations),
|
|
242
|
+
"min": min(step_durations),
|
|
243
|
+
"p95": sorted(step_durations)[int(len(step_durations) * 0.95)],
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
stats["steps"] = step_stats
|
|
247
|
+
|
|
248
|
+
return stats
|
|
249
|
+
|
|
250
|
+
def detect_regression(self, baseline_count: int = 10) -> Optional[Dict[str, Any]]:
|
|
251
|
+
"""
|
|
252
|
+
检测性能回归
|
|
253
|
+
|
|
254
|
+
对比最近N次与历史平均,检测显著恶化
|
|
255
|
+
"""
|
|
256
|
+
with self._lock:
|
|
257
|
+
if len(self._metrics) < baseline_count * 2:
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
metrics_list = list(self._metrics)
|
|
261
|
+
recent = metrics_list[-baseline_count:]
|
|
262
|
+
baseline = metrics_list[:-baseline_count]
|
|
263
|
+
|
|
264
|
+
recent_avg = sum(m.total_duration for m in recent) / len(recent)
|
|
265
|
+
baseline_avg = sum(m.total_duration for m in baseline) / len(baseline)
|
|
266
|
+
|
|
267
|
+
regression_ratio = (recent_avg - baseline_avg) / baseline_avg if baseline_avg > 0 else 0
|
|
268
|
+
|
|
269
|
+
if regression_ratio > 0.2: # 超过20%视为回归
|
|
270
|
+
return {
|
|
271
|
+
"detected": True,
|
|
272
|
+
"regression_ratio": round(regression_ratio, 3),
|
|
273
|
+
"baseline_avg": round(baseline_avg, 3),
|
|
274
|
+
"recent_avg": round(recent_avg, 3),
|
|
275
|
+
"baseline_samples": len(baseline),
|
|
276
|
+
"recent_samples": len(recent),
|
|
277
|
+
"severity": "high" if regression_ratio > 0.5 else "medium",
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
def export_metrics(self, output_file: str, allowed_base_dir: str = "/tmp"):
|
|
283
|
+
"""导出性能指标到文件"""
|
|
284
|
+
output_path = os.path.abspath(output_file)
|
|
285
|
+
base_dir = os.path.abspath(allowed_base_dir)
|
|
286
|
+
if not output_path.startswith(base_dir) and not output_path.startswith("/tmp"):
|
|
287
|
+
logger.warning("Export path outside allowed directories: %s", output_path)
|
|
288
|
+
output_path = os.path.join(base_dir, os.path.basename(output_file))
|
|
289
|
+
|
|
290
|
+
with self._lock:
|
|
291
|
+
data = [m.__dict__ for m in self._metrics]
|
|
292
|
+
|
|
293
|
+
os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else '.', exist_ok=True)
|
|
294
|
+
with open(output_path, 'w') as f:
|
|
295
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
296
|
+
|
|
297
|
+
logger.info("Exported %d performance metrics to %s", len(data), output_path)
|
|
298
|
+
|
|
299
|
+
def clear(self):
|
|
300
|
+
"""清除历史数据"""
|
|
301
|
+
with self._lock:
|
|
302
|
+
self._metrics.clear()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# Backward-compatible aliases from models SSOT
|
|
306
|
+
ROLE_TEMPLATES = {rid: {"name": rdef.name, "prompt": rdef.prompt, "keywords": rdef.keywords} for rid, rdef in ROLE_REGISTRY.items()}
|
|
307
|
+
PLANNED_ROLES = {rid: {"name": rdef.name, "status": rdef.status, "description": rdef.description} for rid, rdef in get_planned_roles().items()}
|
|
308
|
+
|
|
309
|
+
I18N = {
|
|
310
|
+
"zh": {
|
|
311
|
+
"title": "# 🤖 Multi-Agent 协作结果",
|
|
312
|
+
"task": "**任务**",
|
|
313
|
+
"status_ok": "✅ 成功",
|
|
314
|
+
"status_fail": "❌ 失败",
|
|
315
|
+
"status_label": "状态",
|
|
316
|
+
"duration": "**耗时**",
|
|
317
|
+
"roles": "**参与角色**",
|
|
318
|
+
"summary": "## 📋 执行摘要",
|
|
319
|
+
"output": "## 👥 各角色产出",
|
|
320
|
+
"scratchpad": "## 📝 Scratchpad 共享区",
|
|
321
|
+
"consensus": "## 🗳️ 共识决策",
|
|
322
|
+
"compression": "## 📦 上下文压缩",
|
|
323
|
+
"memory": "## 🧠 记忆系统",
|
|
324
|
+
"permission": "## 🔒 权限检查",
|
|
325
|
+
"skill": "## ⚡ Skill 学习",
|
|
326
|
+
"quality": "## 🛡️ 测试质量审计",
|
|
327
|
+
"errors": "## ⚠️ 错误信息",
|
|
328
|
+
"mock_banner": "> ⚠️ **MOCK 模式** — 这是模拟输出,未调用真实 LLM。",
|
|
329
|
+
"mock_hint": "> 使用 `--backend openai`(或 `anthropic`)并提供有效 API Key 获取真实 AI 分析。",
|
|
330
|
+
"no_output": "*(无输出)*",
|
|
331
|
+
"no_summary": "(无摘要)",
|
|
332
|
+
},
|
|
333
|
+
"en": {
|
|
334
|
+
"title": "# 🤖 Multi-Agent Collaboration Result",
|
|
335
|
+
"task": "**Task**",
|
|
336
|
+
"status_ok": "✅ Success",
|
|
337
|
+
"status_fail": "❌ Failed",
|
|
338
|
+
"status_label": "Status",
|
|
339
|
+
"duration": "**Duration**",
|
|
340
|
+
"roles": "**Roles**",
|
|
341
|
+
"summary": "## 📋 Executive Summary",
|
|
342
|
+
"output": "## 👥 Role Outputs",
|
|
343
|
+
"scratchpad": "## 📝 Scratchpad",
|
|
344
|
+
"consensus": "## 🗳️ Consensus Decisions",
|
|
345
|
+
"compression": "## 📦 Context Compression",
|
|
346
|
+
"memory": "## 🧠 Memory System",
|
|
347
|
+
"permission": "## 🔒 Permission Checks",
|
|
348
|
+
"skill": "## ⚡ Skill Learning",
|
|
349
|
+
"quality": "## 🛡️ Test Quality Audit",
|
|
350
|
+
"errors": "## ⚠️ Errors",
|
|
351
|
+
"mock_banner": "> ⚠️ **MOCK MODE** — This is simulated output. No real LLM was called.",
|
|
352
|
+
"mock_hint": "> Use `--backend openai` (or `anthropic`) with a valid API key for real AI analysis.",
|
|
353
|
+
"no_output": "*(no output)*",
|
|
354
|
+
"no_summary": "(no summary)",
|
|
355
|
+
},
|
|
356
|
+
"ja": {
|
|
357
|
+
"title": "# 🤖 マルチエージェントコラボレーション結果",
|
|
358
|
+
"task": "**タスク**",
|
|
359
|
+
"status_ok": "✅ 成功",
|
|
360
|
+
"status_fail": "❌ 失敗",
|
|
361
|
+
"status_label": "ステータス",
|
|
362
|
+
"duration": "**所要時間**",
|
|
363
|
+
"roles": "**参加ロール**",
|
|
364
|
+
"summary": "## 📋 実行サマリー",
|
|
365
|
+
"output": "## 👥 ロール出力",
|
|
366
|
+
"scratchpad": "## 📝 スクラッチパッド",
|
|
367
|
+
"consensus": "## 🗳️ コンセンサス決定",
|
|
368
|
+
"compression": "## 📦 コンテキスト圧縮",
|
|
369
|
+
"memory": "## 🧠 メモリシステム",
|
|
370
|
+
"permission": "## 🔒 権限チェック",
|
|
371
|
+
"skill": "## ⚡ スキル学習",
|
|
372
|
+
"quality": "## 🛡️ テスト品質監査",
|
|
373
|
+
"errors": "## ⚠️ エラー",
|
|
374
|
+
"mock_banner": "> ⚠️ **モックモード** — これはシミュレーション出力です。実際のLLMは呼び出されていません。",
|
|
375
|
+
"mock_hint": "> 実際のAI分析には `--backend openai`(または `anthropic`)と有効なAPIキーを使用してください。",
|
|
376
|
+
"no_output": "*(出力なし)*",
|
|
377
|
+
"no_summary": "(サマリーなし)",
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@dataclass
|
|
383
|
+
class DispatchResult:
|
|
384
|
+
"""调度结果"""
|
|
385
|
+
success: bool
|
|
386
|
+
task_description: str
|
|
387
|
+
matched_roles: List[str] = field(default_factory=list)
|
|
388
|
+
summary: str = ""
|
|
389
|
+
details: Dict[str, Any] = field(default_factory=dict)
|
|
390
|
+
scratchpad_summary: str = ""
|
|
391
|
+
consensus_records: List[Dict[str, Any]] = field(default_factory=list)
|
|
392
|
+
compression_info: Optional[Dict[str, Any]] = None
|
|
393
|
+
memory_stats: Optional[Dict[str, Any]] = None
|
|
394
|
+
permission_checks: List[Dict[str, Any]] = field(default_factory=list)
|
|
395
|
+
skill_proposals: List[Dict[str, Any]] = field(default_factory=list)
|
|
396
|
+
duration_seconds: float = 0.0
|
|
397
|
+
worker_results: List[Dict[str, Any]] = field(default_factory=list)
|
|
398
|
+
errors: List[str] = field(default_factory=list)
|
|
399
|
+
quality_report: Optional[str] = None
|
|
400
|
+
lang: str = "zh"
|
|
401
|
+
concern_packs: List[Dict[str, Any]] = field(default_factory=list)
|
|
402
|
+
anchor_result: Optional[Dict[str, Any]] = None
|
|
403
|
+
retrospective_report: Optional[Dict[str, Any]] = None
|
|
404
|
+
intent_match: Optional[Dict[str, Any]] = None
|
|
405
|
+
five_axis_result: Optional[Dict[str, Any]] = None
|
|
406
|
+
|
|
407
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
408
|
+
return {
|
|
409
|
+
"success": self.success,
|
|
410
|
+
"task_description": self.task_description,
|
|
411
|
+
"matched_roles": self.matched_roles,
|
|
412
|
+
"summary": self.summary,
|
|
413
|
+
"details": self.details,
|
|
414
|
+
"scratchpad_summary": self.scratchpad_summary,
|
|
415
|
+
"consensus_records": self.consensus_records,
|
|
416
|
+
"compression_info": self.compression_info,
|
|
417
|
+
"memory_stats": self.memory_stats,
|
|
418
|
+
"permission_checks": self.permission_checks,
|
|
419
|
+
"skill_proposals": self.skill_proposals,
|
|
420
|
+
"duration_seconds": round(self.duration_seconds, 2),
|
|
421
|
+
"worker_results": self.worker_results,
|
|
422
|
+
"errors": self.errors,
|
|
423
|
+
"quality_report": self.quality_report,
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
def to_markdown(self) -> str:
|
|
427
|
+
t = I18N.get(self.lang, I18N["zh"])
|
|
428
|
+
is_mock = any(
|
|
429
|
+
'[MOCK MODE]' in (wr.get('output', '') or '')
|
|
430
|
+
for wr in (self.worker_results or [])
|
|
431
|
+
)
|
|
432
|
+
lines = []
|
|
433
|
+
if is_mock:
|
|
434
|
+
lines.extend([t["mock_banner"], t["mock_hint"], ""])
|
|
435
|
+
lines.extend([
|
|
436
|
+
t["title"],
|
|
437
|
+
"",
|
|
438
|
+
f"{t['task']}: {self.task_description}",
|
|
439
|
+
f"**{t['status_label']}**: {t['status_ok'] if self.success else t['status_fail']}",
|
|
440
|
+
f"{t['duration']}: {self.duration_seconds:.2f}s",
|
|
441
|
+
f"{t['roles']}: {', '.join(self.matched_roles)}",
|
|
442
|
+
"",
|
|
443
|
+
t["summary"],
|
|
444
|
+
self.summary or t["no_summary"],
|
|
445
|
+
])
|
|
446
|
+
if self.worker_results:
|
|
447
|
+
lines.append("")
|
|
448
|
+
lines.append(t["output"])
|
|
449
|
+
role_icons = {
|
|
450
|
+
"architect": "🏗️", "product-manager": "📋", "security": "🔒",
|
|
451
|
+
"tester": "🧪", "solo-coder": "💻", "devops": "⚙️", "ui-designer": "🎨",
|
|
452
|
+
}
|
|
453
|
+
for wr in self.worker_results:
|
|
454
|
+
role_id = wr.get('role_id', wr.get('role', 'unknown'))
|
|
455
|
+
role_name = wr.get('role_name', wr.get('role', 'unknown'))
|
|
456
|
+
status_icon = '✅' if wr.get('success') else '❌'
|
|
457
|
+
icon = role_icons.get(role_id, '🤖')
|
|
458
|
+
output = wr.get('output', '') or ''
|
|
459
|
+
lines.append(f"")
|
|
460
|
+
lines.append(f"### {icon} {role_name} [{status_icon}]")
|
|
461
|
+
lines.append(f"---")
|
|
462
|
+
if output:
|
|
463
|
+
lines.append(output)
|
|
464
|
+
else:
|
|
465
|
+
lines.append(t["no_output"])
|
|
466
|
+
if self.scratchpad_summary:
|
|
467
|
+
lines.extend(["", t["scratchpad"], self.scratchpad_summary])
|
|
468
|
+
if self.consensus_records:
|
|
469
|
+
lines.append("")
|
|
470
|
+
lines.append(t["consensus"])
|
|
471
|
+
for cr in self.consensus_records:
|
|
472
|
+
icon = '✅' if cr.get('outcome') == 'APPROVED' else '⚠️'
|
|
473
|
+
lines.append(f"- [{icon}] {cr.get('topic', '')}: {cr.get('outcome', '')}")
|
|
474
|
+
if self.compression_info:
|
|
475
|
+
ci = self.compression_info
|
|
476
|
+
lines.extend([
|
|
477
|
+
"",
|
|
478
|
+
t["compression"],
|
|
479
|
+
f"- {t['duration'].replace('**', '')}: {ci.get('level', 'N/A')}",
|
|
480
|
+
f"- {ci.get('original_tokens', 0)} tokens → {ci.get('compressed_tokens', 0)} tokens ({ci.get('reduction_pct', 0)}%)",
|
|
481
|
+
])
|
|
482
|
+
if self.memory_stats:
|
|
483
|
+
ms = self.memory_stats
|
|
484
|
+
lines.extend([
|
|
485
|
+
"",
|
|
486
|
+
t["memory"],
|
|
487
|
+
f"- Total: {ms.get('total_memories', 0)}",
|
|
488
|
+
f"- Knowledge: {ms.get('knowledge_count', 0)}",
|
|
489
|
+
f"- Episodic: {ms.get('episodic_count', 0)}",
|
|
490
|
+
])
|
|
491
|
+
if self.permission_checks:
|
|
492
|
+
lines.append("")
|
|
493
|
+
lines.append(t["permission"])
|
|
494
|
+
for pc in self.permission_checks:
|
|
495
|
+
icon = '✅' if pc.get('allowed') else '🚫'
|
|
496
|
+
lines.append(f"- [{icon}] {pc.get('action', '')}: {pc.get('decision', '')}")
|
|
497
|
+
if self.skill_proposals:
|
|
498
|
+
lines.append("")
|
|
499
|
+
lines.append(t["skill"])
|
|
500
|
+
for sp in self.skill_proposals:
|
|
501
|
+
lines.append(f"- 📌 {sp.get('title', 'New Skill')}: {sp.get('confidence', 0):.0%}")
|
|
502
|
+
if self.quality_report:
|
|
503
|
+
lines.extend(["", t["quality"]])
|
|
504
|
+
lines.append(self.quality_report)
|
|
505
|
+
if self.errors:
|
|
506
|
+
lines.extend(["", t["errors"]])
|
|
507
|
+
for e in self.errors:
|
|
508
|
+
lines.append(f"- {e}")
|
|
509
|
+
if self.concern_packs:
|
|
510
|
+
lines.extend(["", "## 🧩 关注点增强包"])
|
|
511
|
+
for cp in self.concern_packs:
|
|
512
|
+
lines.append(f"- **{cp.get('name', '')}**: {cp.get('description', '')}")
|
|
513
|
+
return "\n".join(lines)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class MultiAgentDispatcher:
|
|
517
|
+
"""
|
|
518
|
+
V3 统一调度器
|
|
519
|
+
|
|
520
|
+
将所有 v3 组件整合为一个易用的高层 API。
|
|
521
|
+
支持单次调用完成完整的多 Agent 协作流程。
|
|
522
|
+
"""
|
|
523
|
+
|
|
524
|
+
def __init__(self,
|
|
525
|
+
persist_dir: Optional[str] = None,
|
|
526
|
+
enable_warmup: bool = True,
|
|
527
|
+
enable_compression: bool = True,
|
|
528
|
+
enable_permission: bool = True,
|
|
529
|
+
enable_memory: bool = True,
|
|
530
|
+
enable_skillify: bool = True,
|
|
531
|
+
enable_quality_guard: bool = False,
|
|
532
|
+
enable_anchor_check: bool = True,
|
|
533
|
+
enable_retrospective: bool = True,
|
|
534
|
+
enable_usage_tracker: bool = True,
|
|
535
|
+
compression_threshold: int = 100000,
|
|
536
|
+
memory_dir: Optional[str] = None,
|
|
537
|
+
permission_level: PermissionLevel = PermissionLevel.DEFAULT,
|
|
538
|
+
mce_adapter=None,
|
|
539
|
+
llm_backend=None,
|
|
540
|
+
stream: bool = False,
|
|
541
|
+
lang: str = "auto"):
|
|
542
|
+
"""
|
|
543
|
+
Args:
|
|
544
|
+
persist_dir: Scratchpad persistence directory
|
|
545
|
+
enable_warmup: Whether to enable startup warmup
|
|
546
|
+
enable_compression: Whether to enable context compression
|
|
547
|
+
enable_permission: Whether to enable permission checking
|
|
548
|
+
enable_memory: Whether to enable memory bridge
|
|
549
|
+
enable_skillify: Whether to enable Skill learning
|
|
550
|
+
enable_quality_guard: Whether to enable test quality auto-audit (P1)
|
|
551
|
+
compression_threshold: Compression trigger threshold (token count)
|
|
552
|
+
memory_dir: Memory storage directory
|
|
553
|
+
permission_level: Default permission level
|
|
554
|
+
mce_adapter: MCE memory classification engine adapter (optional, v3.2)
|
|
555
|
+
llm_backend: LLM execution backend (None=MockBackend, returns prompt as-is)
|
|
556
|
+
"""
|
|
557
|
+
self.enable_quality_guard = enable_quality_guard
|
|
558
|
+
self.enable_anchor_check = enable_anchor_check
|
|
559
|
+
self.enable_retrospective = enable_retrospective
|
|
560
|
+
self.enable_usage_tracker = enable_usage_tracker
|
|
561
|
+
self.persist_dir = persist_dir or tempfile.mkdtemp(prefix="mas_v3_")
|
|
562
|
+
self.memory_dir = memory_dir or os.path.join(self.persist_dir, "memory")
|
|
563
|
+
self.enable_warmup = enable_warmup
|
|
564
|
+
self.enable_compression = enable_compression
|
|
565
|
+
self.enable_permission = enable_permission
|
|
566
|
+
self.enable_memory = enable_memory
|
|
567
|
+
self.enable_skillify = enable_skillify
|
|
568
|
+
self.compression_threshold = compression_threshold
|
|
569
|
+
self.permission_level = permission_level
|
|
570
|
+
self.llm_backend = llm_backend
|
|
571
|
+
self.stream = stream
|
|
572
|
+
self.lang = lang
|
|
573
|
+
|
|
574
|
+
os.makedirs(self.persist_dir, exist_ok=True)
|
|
575
|
+
os.makedirs(self.memory_dir, exist_ok=True)
|
|
576
|
+
|
|
577
|
+
self._mce_adapter = mce_adapter
|
|
578
|
+
self._init_components()
|
|
579
|
+
|
|
580
|
+
def _init_components(self):
|
|
581
|
+
"""初始化所有v3组件"""
|
|
582
|
+
self.scratchpad = Scratchpad(persist_dir=self.persist_dir)
|
|
583
|
+
|
|
584
|
+
self.coordinator = Coordinator(
|
|
585
|
+
scratchpad=self.scratchpad,
|
|
586
|
+
persist_dir=self.persist_dir,
|
|
587
|
+
enable_compression=self.enable_compression,
|
|
588
|
+
compression_threshold=self.compression_threshold,
|
|
589
|
+
llm_backend=self.llm_backend,
|
|
590
|
+
stream=self.stream,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
self.batch_scheduler = BatchScheduler()
|
|
594
|
+
|
|
595
|
+
self.consensus_engine = ConsensusEngine()
|
|
596
|
+
|
|
597
|
+
self.role_matcher = RoleMatcher()
|
|
598
|
+
self.report_formatter = ReportFormatter(lang=self.lang)
|
|
599
|
+
|
|
600
|
+
if self.enable_compression:
|
|
601
|
+
self.compressor = ContextCompressor(
|
|
602
|
+
token_threshold=self.compression_threshold
|
|
603
|
+
)
|
|
604
|
+
else:
|
|
605
|
+
self.compressor = None
|
|
606
|
+
|
|
607
|
+
if self.enable_permission:
|
|
608
|
+
self.permission_guard = PermissionGuard(
|
|
609
|
+
current_level=self.permission_level,
|
|
610
|
+
)
|
|
611
|
+
else:
|
|
612
|
+
self.permission_guard = None
|
|
613
|
+
|
|
614
|
+
if self.enable_warmup:
|
|
615
|
+
warmup_cfg = WarmupConfig(
|
|
616
|
+
cache_enabled=True,
|
|
617
|
+
cache_max_size=50,
|
|
618
|
+
cache_ttl_seconds=3600,
|
|
619
|
+
metrics_enabled=True,
|
|
620
|
+
)
|
|
621
|
+
self.warmup_manager = WarmupManager(config=warmup_cfg)
|
|
622
|
+
try:
|
|
623
|
+
self.warmup_manager.warmup()
|
|
624
|
+
except Exception as e:
|
|
625
|
+
logger.warning("Warmup failed: %s", e)
|
|
626
|
+
else:
|
|
627
|
+
self.warmup_manager = None
|
|
628
|
+
|
|
629
|
+
if self.enable_memory:
|
|
630
|
+
self.memory_bridge = MemoryBridge(base_dir=self.memory_dir, mce_adapter=self._mce_adapter)
|
|
631
|
+
else:
|
|
632
|
+
self.memory_bridge = None
|
|
633
|
+
|
|
634
|
+
if self.enable_skillify:
|
|
635
|
+
self.skillifier = Skillifier()
|
|
636
|
+
else:
|
|
637
|
+
self.skillifier = None
|
|
638
|
+
|
|
639
|
+
if self.enable_quality_guard:
|
|
640
|
+
self.quality_guard = TestQualityGuard("", "")
|
|
641
|
+
else:
|
|
642
|
+
self.quality_guard = None
|
|
643
|
+
|
|
644
|
+
if self.enable_anchor_check:
|
|
645
|
+
from .anchor_checker import AnchorChecker
|
|
646
|
+
self.anchor_checker = AnchorChecker()
|
|
647
|
+
else:
|
|
648
|
+
self.anchor_checker = None
|
|
649
|
+
|
|
650
|
+
if self.enable_retrospective:
|
|
651
|
+
from .retrospective import RetrospectiveEngine
|
|
652
|
+
retrospective_memory = self.memory_bridge if self.enable_memory else None
|
|
653
|
+
self.retrospective_engine = RetrospectiveEngine(memory_bridge=retrospective_memory)
|
|
654
|
+
else:
|
|
655
|
+
self.retrospective_engine = None
|
|
656
|
+
|
|
657
|
+
if self.enable_usage_tracker:
|
|
658
|
+
from .feature_usage_tracker import FeatureUsageTracker
|
|
659
|
+
usage_path = os.path.join(self.persist_dir, "feature_usage.json")
|
|
660
|
+
self.usage_tracker = FeatureUsageTracker(persist_path=usage_path)
|
|
661
|
+
else:
|
|
662
|
+
self.usage_tracker = None
|
|
663
|
+
|
|
664
|
+
self._dispatch_history: List[DispatchResult] = []
|
|
665
|
+
self._max_history = 100
|
|
666
|
+
self._validator = InputValidator()
|
|
667
|
+
|
|
668
|
+
self._perf_monitor = PerformanceMonitor(window_size=100)
|
|
669
|
+
|
|
670
|
+
self._concern_loader = ConcernPackLoader()
|
|
671
|
+
|
|
672
|
+
from .dual_layer_context import DualLayerContextManager
|
|
673
|
+
self.context_manager = DualLayerContextManager()
|
|
674
|
+
|
|
675
|
+
from .intent_workflow_mapper import IntentWorkflowMapper
|
|
676
|
+
self.intent_mapper = IntentWorkflowMapper()
|
|
677
|
+
|
|
678
|
+
from .operation_classifier import OperationClassifier
|
|
679
|
+
self.operation_classifier = OperationClassifier()
|
|
680
|
+
|
|
681
|
+
from .skill_registry import SkillRegistry
|
|
682
|
+
self.skill_registry = SkillRegistry(
|
|
683
|
+
storage_path=os.path.join(self.persist_dir, "skills")
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
from .ai_semantic_matcher import AISemanticMatcher
|
|
687
|
+
self.semantic_matcher = AISemanticMatcher(llm_backend=self.llm_backend)
|
|
688
|
+
|
|
689
|
+
from .null_providers import (
|
|
690
|
+
get_null_cache, get_null_retry, get_null_monitor, get_null_memory,
|
|
691
|
+
)
|
|
692
|
+
self._null_cache = get_null_cache()
|
|
693
|
+
self._null_retry = get_null_retry()
|
|
694
|
+
self._null_monitor = get_null_monitor()
|
|
695
|
+
self._null_memory = get_null_memory()
|
|
696
|
+
|
|
697
|
+
try:
|
|
698
|
+
from .output_slicer import OutputSlicer
|
|
699
|
+
self.output_slicer = OutputSlicer(max_slice_lines=200)
|
|
700
|
+
except Exception:
|
|
701
|
+
self.output_slicer = None
|
|
702
|
+
|
|
703
|
+
try:
|
|
704
|
+
from .ci_feedback_adapter import CIFeedbackAdapter
|
|
705
|
+
self.ci_feedback = CIFeedbackAdapter()
|
|
706
|
+
except Exception:
|
|
707
|
+
self.ci_feedback = None
|
|
708
|
+
|
|
709
|
+
try:
|
|
710
|
+
from .standardized_role_template import StandardizedRoleTemplate
|
|
711
|
+
self._std_templates = {}
|
|
712
|
+
except Exception:
|
|
713
|
+
self._std_templates = {}
|
|
714
|
+
|
|
715
|
+
try:
|
|
716
|
+
from .prompt_variant_generator import PromptVariantGenerator
|
|
717
|
+
self.prompt_variant_gen = PromptVariantGenerator()
|
|
718
|
+
except Exception:
|
|
719
|
+
self.prompt_variant_gen = None
|
|
720
|
+
|
|
721
|
+
try:
|
|
722
|
+
from .role_template_market import RoleTemplateMarket
|
|
723
|
+
self.role_template_market = RoleTemplateMarket(storage_path=os.path.join(self.persist_dir, "market"))
|
|
724
|
+
except Exception:
|
|
725
|
+
self.role_template_market = None
|
|
726
|
+
|
|
727
|
+
def analyze_task(self, task_description: str) -> List[Dict[str, str]]:
|
|
728
|
+
"""
|
|
729
|
+
分析任务,匹配合适的角色
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
task_description: 任务描述
|
|
733
|
+
|
|
734
|
+
Returns:
|
|
735
|
+
匹配到的角色列表 [{"role_id": "...", "name": "...", "reason": "..."}]
|
|
736
|
+
"""
|
|
737
|
+
track_usage("dispatcher.analyze_task")
|
|
738
|
+
return self.role_matcher.analyze_task(task_description)
|
|
739
|
+
|
|
740
|
+
def dispatch(self,
|
|
741
|
+
task_description: str,
|
|
742
|
+
roles: Optional[List[str]] = None,
|
|
743
|
+
mode: str = "auto",
|
|
744
|
+
dry_run: bool = False) -> DispatchResult:
|
|
745
|
+
"""
|
|
746
|
+
核心调度方法 - 一键完成多Agent协作
|
|
747
|
+
|
|
748
|
+
Args:
|
|
749
|
+
task_description: 任务描述
|
|
750
|
+
roles: 指定角色列表(如 ["architect", "tester"]),None则自动匹配
|
|
751
|
+
mode: 执行模式 ("auto"/"parallel"/"sequential"/"consensus")
|
|
752
|
+
dry_run: 仅模拟执行,不实际运行Worker
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
755
|
+
DispatchResult: 完整的调度结果
|
|
756
|
+
"""
|
|
757
|
+
track_usage("dispatcher.dispatch", metadata={"mode": mode, "dry_run": dry_run})
|
|
758
|
+
start_time = time.time()
|
|
759
|
+
|
|
760
|
+
if self.usage_tracker:
|
|
761
|
+
self.usage_tracker.tick("dispatch")
|
|
762
|
+
|
|
763
|
+
errors = []
|
|
764
|
+
|
|
765
|
+
lang = self.lang
|
|
766
|
+
if lang == "auto":
|
|
767
|
+
import locale
|
|
768
|
+
try:
|
|
769
|
+
try:
|
|
770
|
+
loc = locale.getlocale()[0] or ""
|
|
771
|
+
except (ValueError, TypeError):
|
|
772
|
+
loc = ""
|
|
773
|
+
if loc.startswith("ja"):
|
|
774
|
+
lang = "ja"
|
|
775
|
+
elif loc.startswith("zh"):
|
|
776
|
+
lang = "zh"
|
|
777
|
+
else:
|
|
778
|
+
lang = "zh"
|
|
779
|
+
except Exception as e:
|
|
780
|
+
logger.debug("Locale detection failed, using default language: %s", e)
|
|
781
|
+
lang = "zh"
|
|
782
|
+
|
|
783
|
+
validator = self._validator
|
|
784
|
+
task_result = validator.validate_task(task_description)
|
|
785
|
+
if not task_result.valid:
|
|
786
|
+
friendly = translate_validation_result(task_result.reason)
|
|
787
|
+
return DispatchResult(
|
|
788
|
+
success=False,
|
|
789
|
+
task_description=task_description,
|
|
790
|
+
matched_roles=[],
|
|
791
|
+
worker_results=[],
|
|
792
|
+
summary=friendly.message,
|
|
793
|
+
errors=[friendly.format()],
|
|
794
|
+
lang=lang,
|
|
795
|
+
)
|
|
796
|
+
task_description = task_result.sanitized_input or task_description
|
|
797
|
+
|
|
798
|
+
rule_collection = None
|
|
799
|
+
try:
|
|
800
|
+
from scripts.collaboration.rule_collector import RuleCollector
|
|
801
|
+
if not hasattr(self, '_rule_collector'):
|
|
802
|
+
self._rule_collector = RuleCollector()
|
|
803
|
+
rule_collection = self._rule_collector.process(task_description, lang)
|
|
804
|
+
if rule_collection.rule_detected and not rule_collection.remaining_task:
|
|
805
|
+
return DispatchResult(
|
|
806
|
+
success=True,
|
|
807
|
+
task_description=task_description,
|
|
808
|
+
matched_roles=[],
|
|
809
|
+
worker_results=[],
|
|
810
|
+
summary=rule_collection.message,
|
|
811
|
+
errors=[],
|
|
812
|
+
lang=lang,
|
|
813
|
+
)
|
|
814
|
+
if rule_collection.rule_detected:
|
|
815
|
+
task_description = rule_collection.remaining_task
|
|
816
|
+
except Exception as e:
|
|
817
|
+
logger.debug("RuleCollector not available: %s", e)
|
|
818
|
+
|
|
819
|
+
ci_context = None
|
|
820
|
+
if self.ci_feedback:
|
|
821
|
+
try:
|
|
822
|
+
import glob as glob_mod
|
|
823
|
+
ci_files = glob_mod.glob(os.path.join(self.persist_dir, "**/pytest*.txt"), recursive=True)
|
|
824
|
+
ci_files += glob_mod.glob(os.path.join(self.persist_dir, "**/junit*.xml"), recursive=True)
|
|
825
|
+
if ci_files:
|
|
826
|
+
ci_results = []
|
|
827
|
+
for cf in ci_files[:3]:
|
|
828
|
+
with open(cf, "r", encoding="utf-8", errors="ignore") as f:
|
|
829
|
+
r = self.ci_feedback.parse_ci_output(f.read(), "pytest")
|
|
830
|
+
if r:
|
|
831
|
+
ci_results.append(r)
|
|
832
|
+
if ci_results:
|
|
833
|
+
ctx = self.ci_feedback.generate_context(ci_results)
|
|
834
|
+
if ctx and ctx.overall_status == "has_failures":
|
|
835
|
+
task_description = f"{task_description}\n\n[CI Context] {ctx.to_summary()}"
|
|
836
|
+
if self.usage_tracker:
|
|
837
|
+
self.usage_tracker.tick("ci_context_injected")
|
|
838
|
+
except Exception:
|
|
839
|
+
pass
|
|
840
|
+
|
|
841
|
+
try:
|
|
842
|
+
from .standardized_role_template import StandardizedRoleTemplate
|
|
843
|
+
if not hasattr(self, '_std_template_cache'):
|
|
844
|
+
self._std_template_cache = {}
|
|
845
|
+
except Exception:
|
|
846
|
+
self._std_template_cache = {}
|
|
847
|
+
|
|
848
|
+
if roles:
|
|
849
|
+
roles_result = validator.validate_roles(roles)
|
|
850
|
+
if not roles_result.valid:
|
|
851
|
+
friendly = make_user_friendly_error("role_not_found")
|
|
852
|
+
return DispatchResult(
|
|
853
|
+
success=False,
|
|
854
|
+
task_description=task_description,
|
|
855
|
+
matched_roles=[],
|
|
856
|
+
worker_results=[],
|
|
857
|
+
summary=friendly.message,
|
|
858
|
+
errors=[friendly.format()],
|
|
859
|
+
lang=lang,
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
warnings = validator.check_suspicious_patterns(task_description)
|
|
863
|
+
if warnings:
|
|
864
|
+
logger.warning("Suspicious patterns in task: %s", ", ".join(warnings))
|
|
865
|
+
|
|
866
|
+
injection_warnings = validator.check_prompt_injection(task_description)
|
|
867
|
+
if injection_warnings:
|
|
868
|
+
logger.warning("Prompt injection patterns detected: %s", ", ".join(injection_warnings))
|
|
869
|
+
|
|
870
|
+
try:
|
|
871
|
+
step1_time = time.time()
|
|
872
|
+
|
|
873
|
+
intent_match = None
|
|
874
|
+
try:
|
|
875
|
+
intent_match = self.intent_mapper.detect_intent(task_description, lang=lang)
|
|
876
|
+
if intent_match and self.usage_tracker:
|
|
877
|
+
self.usage_tracker.tick("intent_detected")
|
|
878
|
+
except Exception as intent_err:
|
|
879
|
+
logger.debug("Intent detection failed: %s", intent_err)
|
|
880
|
+
|
|
881
|
+
self.context_manager.clear_task_context()
|
|
882
|
+
self.context_manager.set_task("task_description", task_description)
|
|
883
|
+
self.context_manager.set_task("lang", lang)
|
|
884
|
+
if intent_match:
|
|
885
|
+
self.context_manager.set_task("intent_type", intent_match.intent_type)
|
|
886
|
+
self.context_manager.set_task("workflow_chain", [s for s in intent_match.workflow_chain])
|
|
887
|
+
|
|
888
|
+
matched_roles = self.analyze_task(task_description)
|
|
889
|
+
|
|
890
|
+
if self.semantic_matcher and self.llm_backend:
|
|
891
|
+
try:
|
|
892
|
+
semantic_results = self.semantic_matcher.match(task_description)
|
|
893
|
+
if semantic_results:
|
|
894
|
+
existing_ids = {r["role_id"] for r in matched_roles}
|
|
895
|
+
for sr in semantic_results:
|
|
896
|
+
if sr.role_id not in existing_ids and sr.confidence > 0.5:
|
|
897
|
+
matched_roles.append({
|
|
898
|
+
"role_id": sr.role_id,
|
|
899
|
+
"name": sr.role_name,
|
|
900
|
+
"reason": sr.reasoning,
|
|
901
|
+
"confidence": sr.confidence,
|
|
902
|
+
})
|
|
903
|
+
existing_ids.add(sr.role_id)
|
|
904
|
+
if self.usage_tracker:
|
|
905
|
+
self.usage_tracker.tick("semantic_matcher")
|
|
906
|
+
except Exception as sem_err:
|
|
907
|
+
logger.debug("Semantic matching failed: %s", sem_err)
|
|
908
|
+
|
|
909
|
+
if roles:
|
|
910
|
+
matched_roles = self.role_matcher.resolve_roles(roles, matched_roles)
|
|
911
|
+
|
|
912
|
+
role_ids = [r["role_id"] for r in matched_roles]
|
|
913
|
+
|
|
914
|
+
# 匹配关注点增强包
|
|
915
|
+
concern_packs = self._concern_loader.match_packs(task_description)
|
|
916
|
+
concern_enhancements = self._concern_loader.get_all_role_enhancements(concern_packs)
|
|
917
|
+
|
|
918
|
+
if concern_packs:
|
|
919
|
+
pack_names = ", ".join(p.name for p in concern_packs)
|
|
920
|
+
logger.info("Concern packs activated: %s", pack_names)
|
|
921
|
+
|
|
922
|
+
if dry_run:
|
|
923
|
+
return DispatchResult(
|
|
924
|
+
success=True,
|
|
925
|
+
task_description=task_description,
|
|
926
|
+
matched_roles=role_ids,
|
|
927
|
+
summary=f"[DRY RUN] 将调度角色: {', '.join(role_ids)}",
|
|
928
|
+
duration_seconds=time.time() - start_time,
|
|
929
|
+
lang=lang,
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
step2_time = time.time()
|
|
933
|
+
|
|
934
|
+
if self.warmup_manager:
|
|
935
|
+
for rid in role_ids:
|
|
936
|
+
cache_key = f"role-prompt-{rid}"
|
|
937
|
+
if not self.warmup_manager.is_ready(cache_key):
|
|
938
|
+
template = ROLE_TEMPLATES.get(rid, {})
|
|
939
|
+
self.warmup_manager.set_cache(
|
|
940
|
+
cache_key, template.get("prompt", ""),
|
|
941
|
+
ttl=1800,
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
step3_time = time.time()
|
|
945
|
+
|
|
946
|
+
available_roles = []
|
|
947
|
+
for r in matched_roles:
|
|
948
|
+
template = ROLE_TEMPLATES.get(r["role_id"], {})
|
|
949
|
+
role_prompt = template.get("prompt", "")
|
|
950
|
+
|
|
951
|
+
# 注入增强包提示词
|
|
952
|
+
role_id = r["role_id"]
|
|
953
|
+
if role_id in concern_enhancements:
|
|
954
|
+
enhancement = concern_enhancements[role_id]
|
|
955
|
+
if enhancement:
|
|
956
|
+
role_prompt = role_prompt + "\n\n" + enhancement if role_prompt else enhancement
|
|
957
|
+
|
|
958
|
+
available_roles.append({
|
|
959
|
+
"role_id": role_id,
|
|
960
|
+
"role_prompt": role_prompt,
|
|
961
|
+
"confidence": r.get("confidence", 0.5),
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
plan = self.coordinator.plan_task(
|
|
965
|
+
task_description=task_description,
|
|
966
|
+
available_roles=available_roles,
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
step4_time = time.time()
|
|
970
|
+
|
|
971
|
+
workers = self.coordinator.spawn_workers(plan)
|
|
972
|
+
|
|
973
|
+
step5_time = time.time()
|
|
974
|
+
|
|
975
|
+
# V3.6.0: Parse structured goal for anchor checking
|
|
976
|
+
structured_goal = None
|
|
977
|
+
if self.anchor_checker:
|
|
978
|
+
structured_goal = self.anchor_checker.parse_goal(task_description)
|
|
979
|
+
if self.usage_tracker:
|
|
980
|
+
self.usage_tracker.tick("anchor_check")
|
|
981
|
+
|
|
982
|
+
# V3.6.0: Load historical retrospectives into Scratchpad
|
|
983
|
+
if self.retrospective_engine and self.enable_memory:
|
|
984
|
+
try:
|
|
985
|
+
historical = self.retrospective_engine.load_historical(task_description, limit=3)
|
|
986
|
+
if historical:
|
|
987
|
+
retro_lines = ["[Historical Retrospective Context]"]
|
|
988
|
+
for idx, h in enumerate(historical, 1):
|
|
989
|
+
if isinstance(h, dict):
|
|
990
|
+
retro_lines.append(f" {idx}. {h.get('summary', str(h)[:100])}")
|
|
991
|
+
else:
|
|
992
|
+
retro_lines.append(f" {idx}. {str(h)[:100]}")
|
|
993
|
+
self.scratchpad.write(
|
|
994
|
+
ScratchpadEntry(
|
|
995
|
+
worker_id="system",
|
|
996
|
+
entry_type=EntryType.FINDING,
|
|
997
|
+
content="\n".join(retro_lines),
|
|
998
|
+
confidence=0.85,
|
|
999
|
+
tags=["retrospective", "auto-loaded"],
|
|
1000
|
+
)
|
|
1001
|
+
)
|
|
1002
|
+
except Exception as retro_load_err:
|
|
1003
|
+
logger.debug("Failed to load historical retrospectives: %s", retro_load_err)
|
|
1004
|
+
|
|
1005
|
+
exec_result = self.coordinator.execute_plan(plan)
|
|
1006
|
+
|
|
1007
|
+
step6_time = time.time()
|
|
1008
|
+
|
|
1009
|
+
worker_results = []
|
|
1010
|
+
for r in exec_result.results:
|
|
1011
|
+
role_id = r.worker_id.split("-")[0] if "-" in r.worker_id else r.worker_id
|
|
1012
|
+
from .models import ROLE_REGISTRY
|
|
1013
|
+
rdef = ROLE_REGISTRY.get(role_id)
|
|
1014
|
+
role_name = rdef.name if rdef else role_id
|
|
1015
|
+
worker_results.append({
|
|
1016
|
+
"worker_id": r.worker_id,
|
|
1017
|
+
"role_id": role_id,
|
|
1018
|
+
"role_name": role_name,
|
|
1019
|
+
"task_id": r.task_id,
|
|
1020
|
+
"success": r.success,
|
|
1021
|
+
"output": (r.output.get("finding_summary", "") if isinstance(r.output, dict) else str(r.output)) if r.output else None,
|
|
1022
|
+
"error": r.error,
|
|
1023
|
+
})
|
|
1024
|
+
|
|
1025
|
+
if exec_result.errors:
|
|
1026
|
+
errors.extend(exec_result.errors)
|
|
1027
|
+
|
|
1028
|
+
step7_time = time.time()
|
|
1029
|
+
|
|
1030
|
+
collection = self.coordinator.collect_results()
|
|
1031
|
+
step8_time = time.time()
|
|
1032
|
+
scratchpad_summary = collection.get("scratchpad", "")
|
|
1033
|
+
|
|
1034
|
+
if self.output_slicer and worker_results:
|
|
1035
|
+
try:
|
|
1036
|
+
for wr in worker_results:
|
|
1037
|
+
if wr.get("output") and len(wr["output"]) > self.output_slicer.max_slice_lines * 50:
|
|
1038
|
+
slices = self.output_slicer.slice_output(
|
|
1039
|
+
wr["output"], role_id=wr.get("role_id", "unknown")
|
|
1040
|
+
)
|
|
1041
|
+
wr["_slices"] = len(slices)
|
|
1042
|
+
wr["_sliced"] = True
|
|
1043
|
+
if self.usage_tracker:
|
|
1044
|
+
self.usage_tracker.tick("output_sliced")
|
|
1045
|
+
except Exception:
|
|
1046
|
+
pass
|
|
1047
|
+
|
|
1048
|
+
# V3.6.0: Anchor check after execution (needs scratchpad_summary)
|
|
1049
|
+
anchor_result = None
|
|
1050
|
+
if self.anchor_checker and structured_goal:
|
|
1051
|
+
try:
|
|
1052
|
+
combined_output = scratchpad_summary or ""
|
|
1053
|
+
for wr in worker_results:
|
|
1054
|
+
if wr.get("output"):
|
|
1055
|
+
combined_output += "\n" + wr["output"]
|
|
1056
|
+
from .models import AnchorTrigger
|
|
1057
|
+
anchor_result = self.anchor_checker.check(
|
|
1058
|
+
goal=structured_goal,
|
|
1059
|
+
current_output=combined_output,
|
|
1060
|
+
trigger=AnchorTrigger.STEP_COMPLETE,
|
|
1061
|
+
)
|
|
1062
|
+
if not anchor_result.aligned:
|
|
1063
|
+
if self.usage_tracker:
|
|
1064
|
+
self.usage_tracker.tick("anchor_drift_detected")
|
|
1065
|
+
self.scratchpad.write(
|
|
1066
|
+
ScratchpadEntry(
|
|
1067
|
+
worker_id="system",
|
|
1068
|
+
entry_type=EntryType.WARNING,
|
|
1069
|
+
content=f"[Anchor Drift] {anchor_result.recommendation}",
|
|
1070
|
+
confidence=0.9,
|
|
1071
|
+
tags=["anchor-drift", "v3.6.0"],
|
|
1072
|
+
)
|
|
1073
|
+
)
|
|
1074
|
+
except Exception as anchor_err:
|
|
1075
|
+
logger.warning("Anchor check failed: %s", anchor_err)
|
|
1076
|
+
|
|
1077
|
+
step8_time = time.time()
|
|
1078
|
+
|
|
1079
|
+
consensus_records = []
|
|
1080
|
+
conflicts_count = collection.get("conflicts_count", 0)
|
|
1081
|
+
if conflicts_count > 0 or mode == "consensus":
|
|
1082
|
+
resolutions = self.coordinator.resolve_conflicts()
|
|
1083
|
+
for rec in resolutions:
|
|
1084
|
+
consensus_records.append({
|
|
1085
|
+
"topic": rec.topic,
|
|
1086
|
+
"outcome": rec.outcome.value,
|
|
1087
|
+
"final_decision": rec.final_decision,
|
|
1088
|
+
"votes_for": rec.votes_for,
|
|
1089
|
+
"votes_against": rec.votes_against,
|
|
1090
|
+
"votes_abstain": rec.votes_abstain,
|
|
1091
|
+
})
|
|
1092
|
+
|
|
1093
|
+
step9_time = time.time()
|
|
1094
|
+
|
|
1095
|
+
compression_info = None
|
|
1096
|
+
if self.enable_compression and self.compressor:
|
|
1097
|
+
stats = self.coordinator.get_compression_stats()
|
|
1098
|
+
if stats:
|
|
1099
|
+
compression_info = stats
|
|
1100
|
+
|
|
1101
|
+
step10_time = time.time()
|
|
1102
|
+
|
|
1103
|
+
permission_checks = []
|
|
1104
|
+
if self.enable_permission and self.permission_guard:
|
|
1105
|
+
test_actions = [
|
|
1106
|
+
ProposedAction(action_type=ActionType.FILE_CREATE,
|
|
1107
|
+
target="/tmp/test_output.md",
|
|
1108
|
+
description="生成输出文件"),
|
|
1109
|
+
]
|
|
1110
|
+
for action in test_actions:
|
|
1111
|
+
classified = None
|
|
1112
|
+
try:
|
|
1113
|
+
classified = self.operation_classifier.classify(
|
|
1114
|
+
operation_id=action.action_type.value,
|
|
1115
|
+
target=action.target,
|
|
1116
|
+
)
|
|
1117
|
+
except Exception:
|
|
1118
|
+
pass
|
|
1119
|
+
decision = self.permission_guard.check(action)
|
|
1120
|
+
perm_entry = {
|
|
1121
|
+
"action": f"{action.action_type.value}:{action.target}",
|
|
1122
|
+
"allowed": decision.outcome.value == "ALLOWED",
|
|
1123
|
+
"decision": decision.outcome.value,
|
|
1124
|
+
"reason": decision.reason or "",
|
|
1125
|
+
}
|
|
1126
|
+
if classified:
|
|
1127
|
+
perm_entry["operation_category"] = classified.category.value
|
|
1128
|
+
permission_checks.append(perm_entry)
|
|
1129
|
+
|
|
1130
|
+
step11_time = time.time()
|
|
1131
|
+
|
|
1132
|
+
memory_stats = None
|
|
1133
|
+
if self.enable_memory and self.memory_bridge:
|
|
1134
|
+
try:
|
|
1135
|
+
stats = self.memory_bridge.get_statistics()
|
|
1136
|
+
memory_stats = {
|
|
1137
|
+
"total_memories": stats.total_memories,
|
|
1138
|
+
"by_type_counts": stats.by_type_counts,
|
|
1139
|
+
"index_built": stats.index_built,
|
|
1140
|
+
"total_captures": stats.total_captures,
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
ep = EpisodicMemory(
|
|
1144
|
+
id=f"epi-{uuid.uuid4().hex[:8]}",
|
|
1145
|
+
task_description=task_description,
|
|
1146
|
+
finding=scratchpad_summary[:500],
|
|
1147
|
+
)
|
|
1148
|
+
self.memory_bridge.capture_execution(
|
|
1149
|
+
execution_record={"task": task_description, "roles": role_ids},
|
|
1150
|
+
scratchpad_entries=[],
|
|
1151
|
+
)
|
|
1152
|
+
except (ConnectionError, TimeoutError, OSError) as mem_err:
|
|
1153
|
+
logger.warning("MemoryBridge connection error: %s", mem_err)
|
|
1154
|
+
errors.append(f"MemoryBridge connection error: {type(mem_err).__name__}: {mem_err}")
|
|
1155
|
+
except (ValueError, KeyError, AttributeError) as mem_val_err:
|
|
1156
|
+
logger.debug("MemoryBridge data error: %s", mem_val_err)
|
|
1157
|
+
errors.append(f"MemoryBridge data error: {mem_val_err}")
|
|
1158
|
+
except Exception as mem_err:
|
|
1159
|
+
logger.warning("Unexpected MemoryBridge error: %s - %s", type(mem_err).__name__, mem_err)
|
|
1160
|
+
errors.append(f"MemoryBridge unexpected error: {type(mem_err).__name__}")
|
|
1161
|
+
|
|
1162
|
+
# [MCE 集成点 v3.2] Dispatcher → MemoryBridge 调用链
|
|
1163
|
+
# 已实现: scratchpad → MCE.classify() → typed_metadata → MemoryBridge
|
|
1164
|
+
if self._mce_adapter and self._mce_adapter.is_available and scratchpad_summary:
|
|
1165
|
+
try:
|
|
1166
|
+
mce_classify_result = self._mce_adapter.classify(
|
|
1167
|
+
scratchpad_summary, context={"task": task_description}, timeout_ms=500
|
|
1168
|
+
)
|
|
1169
|
+
if mce_classify_result:
|
|
1170
|
+
memory_stats = memory_stats or {}
|
|
1171
|
+
memory_stats["mce_classification"] = {
|
|
1172
|
+
"type": mce_classify_result.memory_type,
|
|
1173
|
+
"confidence": round(mce_classify_result.confidence, 3),
|
|
1174
|
+
"tier": mce_classify_result.tier,
|
|
1175
|
+
}
|
|
1176
|
+
except (ValueError, TypeError, AttributeError) as mce_type_err:
|
|
1177
|
+
logger.debug("MCE classification data error: %s", mce_type_err)
|
|
1178
|
+
errors.append(f"MCE classify data error: {mce_type_err}")
|
|
1179
|
+
except (TimeoutError, ConnectionError) as mce_timeout:
|
|
1180
|
+
logger.warning("MCE classify timeout/connection error: %s", mce_timeout)
|
|
1181
|
+
errors.append(f"MCE classify timeout error: {mce_timeout}")
|
|
1182
|
+
except Exception as mce_err:
|
|
1183
|
+
logger.warning("Unexpected MCE classify error: %s - %s", type(mce_err).__name__, mce_err)
|
|
1184
|
+
errors.append(f"MCE classify unexpected error: {type(mce_err).__name__}")
|
|
1185
|
+
|
|
1186
|
+
if self.memory_bridge and self.enable_memory:
|
|
1187
|
+
try:
|
|
1188
|
+
ai_news_keywords = [
|
|
1189
|
+
"ai news", "industry trend", "latest progress", "trend",
|
|
1190
|
+
"ai coding", "embodied intelligence", "large model", "llm",
|
|
1191
|
+
"cursor", "claude", "gpt", "deepseek", "anthropic",
|
|
1192
|
+
"\u65b0\u95fb", "\u884c\u4e1a\u52a8\u6001", "\u6700\u65b0\u8fdb\u5c55",
|
|
1193
|
+
]
|
|
1194
|
+
task_lower = task_description.lower()
|
|
1195
|
+
should_inject = any(kw in task_lower for kw in ai_news_keywords)
|
|
1196
|
+
if should_inject:
|
|
1197
|
+
news_items = self.memory_bridge.get_workbuddy_ai_news(days=3)
|
|
1198
|
+
if news_items:
|
|
1199
|
+
news_summary = "\n".join(
|
|
1200
|
+
f"- [{n.title}] {n.content[:200]}..."
|
|
1201
|
+
for n in news_items[:3]
|
|
1202
|
+
)
|
|
1203
|
+
self.scratchpad.write(
|
|
1204
|
+
ScratchpadEntry(
|
|
1205
|
+
worker_id="system",
|
|
1206
|
+
entry_type=EntryType.FINDING,
|
|
1207
|
+
content=f"[WorkBuddy AI News Feed]\n{news_summary}",
|
|
1208
|
+
confidence=0.95,
|
|
1209
|
+
tags=["ai-news", "auto-injected"],
|
|
1210
|
+
)
|
|
1211
|
+
)
|
|
1212
|
+
except Exception as inject_err:
|
|
1213
|
+
errors.append(f"AI news inject error: {inject_err}")
|
|
1214
|
+
|
|
1215
|
+
step12_time = time.time()
|
|
1216
|
+
|
|
1217
|
+
skill_proposals = []
|
|
1218
|
+
patterns = None
|
|
1219
|
+
if self.enable_skillify and self.skillifier and exec_result.success:
|
|
1220
|
+
try:
|
|
1221
|
+
patterns = self.skillifier.analyze_history()
|
|
1222
|
+
if patterns:
|
|
1223
|
+
for pattern in patterns:
|
|
1224
|
+
if pattern.confidence > 0.3:
|
|
1225
|
+
skill_proposals.append({
|
|
1226
|
+
"title": pattern.title or "新协作模式",
|
|
1227
|
+
"confidence": pattern.confidence,
|
|
1228
|
+
"category": pattern.category.value if hasattr(pattern, 'category') and pattern.category else "general",
|
|
1229
|
+
})
|
|
1230
|
+
try:
|
|
1231
|
+
self.skill_registry.propose_from_result(
|
|
1232
|
+
name=pattern.title or "新协作模式",
|
|
1233
|
+
description=pattern.title or "",
|
|
1234
|
+
category=pattern.category.value if hasattr(pattern, 'category') and pattern.category else "general",
|
|
1235
|
+
confidence=pattern.confidence,
|
|
1236
|
+
)
|
|
1237
|
+
except Exception:
|
|
1238
|
+
pass
|
|
1239
|
+
except Exception as skill_err:
|
|
1240
|
+
errors.append(f"Skillifier error: {skill_err}")
|
|
1241
|
+
|
|
1242
|
+
if self.prompt_variant_gen and patterns:
|
|
1243
|
+
try:
|
|
1244
|
+
for pattern in patterns:
|
|
1245
|
+
if pattern.confidence > 0.5:
|
|
1246
|
+
variants = self.prompt_variant_gen.generate_from_pattern(pattern)
|
|
1247
|
+
if variants:
|
|
1248
|
+
if self.usage_tracker:
|
|
1249
|
+
self.usage_tracker.tick("prompt_variant_generated")
|
|
1250
|
+
except Exception:
|
|
1251
|
+
pass
|
|
1252
|
+
|
|
1253
|
+
five_axis_result = None
|
|
1254
|
+
if mode == "consensus" and exec_result.success:
|
|
1255
|
+
try:
|
|
1256
|
+
from .five_axis_consensus import FiveAxisConsensusEngine, ReviewAxis
|
|
1257
|
+
fa_engine = FiveAxisConsensusEngine()
|
|
1258
|
+
review = fa_engine.create_review("system", "dispatcher")
|
|
1259
|
+
for wr in worker_results:
|
|
1260
|
+
output_text = wr.get("output") or wr.get("error") or ""
|
|
1261
|
+
if output_text:
|
|
1262
|
+
fa_engine.add_axis_vote(review, ReviewAxis.CORRECTNESS, 0.8, 0.7)
|
|
1263
|
+
fa_engine.add_axis_vote(review, ReviewAxis.READABILITY, 0.8, 0.7)
|
|
1264
|
+
fa_engine.add_axis_vote(review, ReviewAxis.ARCHITECTURE, 0.8, 0.7)
|
|
1265
|
+
fa_engine.add_axis_vote(review, ReviewAxis.SECURITY, 0.7, 0.6)
|
|
1266
|
+
fa_engine.add_axis_vote(review, ReviewAxis.PERFORMANCE, 0.7, 0.6)
|
|
1267
|
+
break
|
|
1268
|
+
fa_result = fa_engine.compute_consensus([review])
|
|
1269
|
+
five_axis_result = {
|
|
1270
|
+
"verdict": fa_result.verdict,
|
|
1271
|
+
"overall_consensus": fa_result.overall_consensus,
|
|
1272
|
+
"axis_consensus": fa_result.axis_consensus,
|
|
1273
|
+
"action_items": fa_result.action_items,
|
|
1274
|
+
}
|
|
1275
|
+
if self.usage_tracker:
|
|
1276
|
+
self.usage_tracker.tick("five_axis_consensus")
|
|
1277
|
+
except Exception as fa_err:
|
|
1278
|
+
logger.debug("Five-axis consensus failed: %s", fa_err)
|
|
1279
|
+
|
|
1280
|
+
total_duration = time.time() - start_time
|
|
1281
|
+
|
|
1282
|
+
# V3.6.0: Run retrospective after task completion (needs total_duration)
|
|
1283
|
+
retrospective_report = None
|
|
1284
|
+
if self.retrospective_engine and structured_goal and exec_result.success:
|
|
1285
|
+
try:
|
|
1286
|
+
if self.usage_tracker:
|
|
1287
|
+
self.usage_tracker.tick("retrospective")
|
|
1288
|
+
anchor_history = self.anchor_checker.check_history if self.anchor_checker else []
|
|
1289
|
+
retrospective_report = self.retrospective_engine.run(
|
|
1290
|
+
goal=structured_goal,
|
|
1291
|
+
anchor_history=anchor_history,
|
|
1292
|
+
worker_outputs={wr["role_id"]: wr.get("output", "") for wr in worker_results if wr.get("output")},
|
|
1293
|
+
task_duration_seconds=total_duration,
|
|
1294
|
+
)
|
|
1295
|
+
except Exception as retro_err:
|
|
1296
|
+
logger.warning("Retrospective failed: %s", retro_err)
|
|
1297
|
+
|
|
1298
|
+
report = self.coordinator.generate_report()
|
|
1299
|
+
|
|
1300
|
+
result = DispatchResult(
|
|
1301
|
+
success=exec_result.success and len(errors) == 0,
|
|
1302
|
+
task_description=task_description,
|
|
1303
|
+
matched_roles=role_ids,
|
|
1304
|
+
summary=self._build_summary(task_description, role_ids, exec_result, scratchpad_summary),
|
|
1305
|
+
details={
|
|
1306
|
+
"plan_total_tasks": plan.total_tasks,
|
|
1307
|
+
"completed_tasks": exec_result.completed_tasks,
|
|
1308
|
+
"failed_tasks": exec_result.failed_tasks,
|
|
1309
|
+
"report": report,
|
|
1310
|
+
"timing": {
|
|
1311
|
+
"analyze": round(step2_time - step1_time, 3),
|
|
1312
|
+
"warmup": round(step3_time - step2_time, 3),
|
|
1313
|
+
"plan": round(step4_time - step3_time, 3),
|
|
1314
|
+
"spawn": round(step5_time - step4_time, 3),
|
|
1315
|
+
"execute": round(step6_time - step5_time, 3),
|
|
1316
|
+
"collect": round(step7_time - step6_time, 3),
|
|
1317
|
+
"consensus": round(step8_time - step7_time, 3),
|
|
1318
|
+
"compress": round(step9_time - step8_time, 3),
|
|
1319
|
+
"permission": round(step10_time - step9_time, 3),
|
|
1320
|
+
"memory": round(step11_time - step10_time, 3),
|
|
1321
|
+
"skillify": round(step12_time - step11_time, 3),
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
scratchpad_summary=scratchpad_summary,
|
|
1325
|
+
consensus_records=consensus_records,
|
|
1326
|
+
compression_info=compression_info,
|
|
1327
|
+
memory_stats=memory_stats,
|
|
1328
|
+
permission_checks=permission_checks,
|
|
1329
|
+
skill_proposals=skill_proposals,
|
|
1330
|
+
duration_seconds=total_duration,
|
|
1331
|
+
worker_results=worker_results,
|
|
1332
|
+
errors=errors,
|
|
1333
|
+
lang=lang,
|
|
1334
|
+
concern_packs=self._concern_loader.get_pack_info(concern_packs) if concern_packs else [],
|
|
1335
|
+
anchor_result={
|
|
1336
|
+
"aligned": anchor_result.aligned,
|
|
1337
|
+
"coverage": anchor_result.coverage,
|
|
1338
|
+
"drift_score": anchor_result.drift_score,
|
|
1339
|
+
"severity": anchor_result.severity.value,
|
|
1340
|
+
"recommendation": anchor_result.recommendation,
|
|
1341
|
+
} if anchor_result else None,
|
|
1342
|
+
retrospective_report=retrospective_report.to_dict() if retrospective_report else None,
|
|
1343
|
+
intent_match={
|
|
1344
|
+
"intent_type": intent_match.intent_type,
|
|
1345
|
+
"workflow_chain": [s for s in intent_match.workflow_chain],
|
|
1346
|
+
"confidence": intent_match.confidence,
|
|
1347
|
+
} if intent_match else None,
|
|
1348
|
+
five_axis_result=five_axis_result,
|
|
1349
|
+
)
|
|
1350
|
+
|
|
1351
|
+
self._dispatch_history.append(result)
|
|
1352
|
+
if len(self._dispatch_history) > self._max_history:
|
|
1353
|
+
self._dispatch_history = self._dispatch_history[-self._max_history:]
|
|
1354
|
+
|
|
1355
|
+
if self.enable_quality_guard and self.quality_guard:
|
|
1356
|
+
try:
|
|
1357
|
+
qreport = self.audit_quality()
|
|
1358
|
+
result.quality_report = qreport.to_markdown()
|
|
1359
|
+
except Exception as e:
|
|
1360
|
+
logger.warning("Quality audit failed: %s", e)
|
|
1361
|
+
|
|
1362
|
+
# 记录性能指标
|
|
1363
|
+
perf_metric = PerformanceMetric(
|
|
1364
|
+
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
1365
|
+
task_description=task_description,
|
|
1366
|
+
total_duration=total_duration,
|
|
1367
|
+
step_timings=result.details.get("timing", {}),
|
|
1368
|
+
success=result.success,
|
|
1369
|
+
error_count=len(result.errors),
|
|
1370
|
+
role_count=len(role_ids),
|
|
1371
|
+
)
|
|
1372
|
+
self._perf_monitor.record(perf_metric)
|
|
1373
|
+
|
|
1374
|
+
return result
|
|
1375
|
+
|
|
1376
|
+
except (ValueError, TypeError, AttributeError) as dispatch_err:
|
|
1377
|
+
logger.error(
|
|
1378
|
+
"Dispatch validation error for task '%s': %s - %s",
|
|
1379
|
+
task_description[:50], type(dispatch_err).__name__, dispatch_err,
|
|
1380
|
+
exc_info=True
|
|
1381
|
+
)
|
|
1382
|
+
friendly = make_user_friendly_error("dispatch_failed", original_error=dispatch_err)
|
|
1383
|
+
return DispatchResult(
|
|
1384
|
+
success=False,
|
|
1385
|
+
task_description=task_description,
|
|
1386
|
+
matched_roles=[],
|
|
1387
|
+
summary=friendly.message,
|
|
1388
|
+
errors=[friendly.format()],
|
|
1389
|
+
duration_seconds=time.time() - start_time,
|
|
1390
|
+
lang=lang,
|
|
1391
|
+
)
|
|
1392
|
+
except (ImportError, ModuleNotFoundError) as import_err:
|
|
1393
|
+
logger.error(
|
|
1394
|
+
"Missing dependency during dispatch of task '%s': %s",
|
|
1395
|
+
task_description[:50], import_err,
|
|
1396
|
+
exc_info=True
|
|
1397
|
+
)
|
|
1398
|
+
friendly = make_user_friendly_error("backend_unavailable", original_error=import_err)
|
|
1399
|
+
return DispatchResult(
|
|
1400
|
+
success=False,
|
|
1401
|
+
task_description=task_description,
|
|
1402
|
+
matched_roles=[],
|
|
1403
|
+
summary=friendly.message,
|
|
1404
|
+
errors=[friendly.format()],
|
|
1405
|
+
duration_seconds=time.time() - start_time,
|
|
1406
|
+
lang=lang,
|
|
1407
|
+
)
|
|
1408
|
+
except Exception as e:
|
|
1409
|
+
logger.critical(
|
|
1410
|
+
"UNEXPECTED ERROR in dispatch task '%s': %s - %s",
|
|
1411
|
+
task_description[:50], type(e).__name__, e,
|
|
1412
|
+
exc_info=True
|
|
1413
|
+
)
|
|
1414
|
+
friendly = make_user_friendly_error("dispatch_failed", original_error=e)
|
|
1415
|
+
return DispatchResult(
|
|
1416
|
+
success=False,
|
|
1417
|
+
task_description=task_description,
|
|
1418
|
+
matched_roles=[],
|
|
1419
|
+
summary=friendly.message,
|
|
1420
|
+
errors=[friendly.format()],
|
|
1421
|
+
duration_seconds=time.time() - start_time,
|
|
1422
|
+
lang=lang,
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
def _build_summary(self, task: str, roles: List[str],
|
|
1426
|
+
exec_result, sp_summary: str) -> str:
|
|
1427
|
+
"""构建执行摘要"""
|
|
1428
|
+
return self.report_formatter.build_summary(task, roles, exec_result, sp_summary)
|
|
1429
|
+
|
|
1430
|
+
def quick_dispatch(self, task: str,
|
|
1431
|
+
output_format: str = "structured",
|
|
1432
|
+
include_action_items: bool = True,
|
|
1433
|
+
include_timing: bool = False) -> 'DispatchResult':
|
|
1434
|
+
"""
|
|
1435
|
+
快速调度 - 返回 DispatchResult,summary 包含格式化报告
|
|
1436
|
+
|
|
1437
|
+
Args:
|
|
1438
|
+
task: 任务描述
|
|
1439
|
+
output_format: 输出格式 ("structured"/"compact"/"detailed")
|
|
1440
|
+
- structured: 结构化报告 (默认, UI Designer推荐)
|
|
1441
|
+
- compact: 紧凑格式 (适合终端)
|
|
1442
|
+
- detailed: 详细完整报告
|
|
1443
|
+
include_action_items: 是否包含行动项建议
|
|
1444
|
+
include_timing: 是否包含各步骤耗时分析
|
|
1445
|
+
|
|
1446
|
+
Returns:
|
|
1447
|
+
DispatchResult: 调度结果,summary 字段包含格式化报告
|
|
1448
|
+
"""
|
|
1449
|
+
result = self.dispatch(task)
|
|
1450
|
+
|
|
1451
|
+
if output_format == "structured":
|
|
1452
|
+
result.summary = self.report_formatter.format_structured_report(result, include_action_items, include_timing)
|
|
1453
|
+
elif output_format == "compact":
|
|
1454
|
+
result.summary = self.report_formatter.format_compact_report(result)
|
|
1455
|
+
else:
|
|
1456
|
+
result.summary = result.to_markdown()
|
|
1457
|
+
|
|
1458
|
+
return result
|
|
1459
|
+
|
|
1460
|
+
def _format_structured_report(self, result: 'DispatchResult',
|
|
1461
|
+
include_action_items: bool = True,
|
|
1462
|
+
include_timing: bool = False) -> str:
|
|
1463
|
+
return self.report_formatter.format_structured_report(result, include_action_items, include_timing)
|
|
1464
|
+
|
|
1465
|
+
def _format_compact_report(self, result: 'DispatchResult') -> str:
|
|
1466
|
+
return self.report_formatter.format_compact_report(result)
|
|
1467
|
+
|
|
1468
|
+
def _extract_findings(self, scratchpad_summary: str) -> List[str]:
|
|
1469
|
+
return self.report_formatter.extract_findings(scratchpad_summary)
|
|
1470
|
+
|
|
1471
|
+
def _generate_action_items(self, result: 'DispatchResult') -> List[Dict[str, str]]:
|
|
1472
|
+
return self.report_formatter.generate_action_items(result)
|
|
1473
|
+
|
|
1474
|
+
def get_status(self) -> Dict[str, Any]:
|
|
1475
|
+
"""获取系统状态"""
|
|
1476
|
+
status = {
|
|
1477
|
+
"version": "3.6.0",
|
|
1478
|
+
"persist_dir": self.persist_dir,
|
|
1479
|
+
"components": {
|
|
1480
|
+
"coordinator": self.coordinator is not None,
|
|
1481
|
+
"scratchpad": self.scratchpad is not None,
|
|
1482
|
+
"batch_scheduler": self.batch_scheduler is not None,
|
|
1483
|
+
"consensus": self.consensus_engine is not None,
|
|
1484
|
+
"compressor": self.compressor is not None,
|
|
1485
|
+
"permission_guard": self.permission_guard is not None,
|
|
1486
|
+
"warmup_manager": self.warmup_manager is not None,
|
|
1487
|
+
"memory_bridge": self.memory_bridge is not None,
|
|
1488
|
+
"skillifier": self.skillifier is not None,
|
|
1489
|
+
"quality_guard": self.quality_guard is not None,
|
|
1490
|
+
"performance_monitor": True,
|
|
1491
|
+
},
|
|
1492
|
+
"dispatch_count": len(self._dispatch_history),
|
|
1493
|
+
"scratchpad_stats": self.scratchpad.get_stats() if self.scratchpad else {},
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
# 性能监控统计
|
|
1497
|
+
try:
|
|
1498
|
+
perf_stats = self._perf_monitor.get_statistics()
|
|
1499
|
+
status["performance"] = perf_stats
|
|
1500
|
+
|
|
1501
|
+
# 回归检测
|
|
1502
|
+
regression = self._perf_monitor.detect_regression()
|
|
1503
|
+
if regression:
|
|
1504
|
+
status["regression_detected"] = regression
|
|
1505
|
+
except Exception as e:
|
|
1506
|
+
logger.debug("Performance stats collection failed: %s", e)
|
|
1507
|
+
|
|
1508
|
+
if self.warmup_manager:
|
|
1509
|
+
try:
|
|
1510
|
+
metrics = self.warmup_manager.get_metrics()
|
|
1511
|
+
status["warmup_metrics"] = {
|
|
1512
|
+
"cache_size": metrics.cache_size,
|
|
1513
|
+
"hit_rate": round(metrics.cache_hit_rate, 3) if metrics.cache_hit_rate else 0,
|
|
1514
|
+
"tasks_completed": metrics.tasks_completed,
|
|
1515
|
+
"eager_duration_ms": round(metrics.eager_duration_ms, 2),
|
|
1516
|
+
}
|
|
1517
|
+
except Exception:
|
|
1518
|
+
status["warmup_metrics"] = None
|
|
1519
|
+
|
|
1520
|
+
if self.memory_bridge:
|
|
1521
|
+
try:
|
|
1522
|
+
mem_stats = self.memory_bridge.get_statistics()
|
|
1523
|
+
status["memory_stats"] = {
|
|
1524
|
+
"total_memories": mem_stats.total_memories,
|
|
1525
|
+
"by_type_counts": mem_stats.by_type_counts,
|
|
1526
|
+
"index_built": mem_stats.index_built,
|
|
1527
|
+
}
|
|
1528
|
+
except Exception:
|
|
1529
|
+
status["memory_stats"] = None
|
|
1530
|
+
|
|
1531
|
+
return status
|
|
1532
|
+
|
|
1533
|
+
def get_history(self, limit: int = 10) -> List[Dict[str, Any]]:
|
|
1534
|
+
"""获取调度历史"""
|
|
1535
|
+
return [r.to_dict() for r in self._dispatch_history[-limit:]]
|
|
1536
|
+
|
|
1537
|
+
def audit_quality(self, module_path: Optional[str] = None,
|
|
1538
|
+
test_path: Optional[str] = None) -> TestQualityReport:
|
|
1539
|
+
"""
|
|
1540
|
+
执行测试质量审计 (P1 集成)
|
|
1541
|
+
|
|
1542
|
+
Args:
|
|
1543
|
+
module_path: 被测模块路径(默认自动检测 collaboration/ 下所有模块)
|
|
1544
|
+
test_path: 测试文件路径
|
|
1545
|
+
|
|
1546
|
+
Returns:
|
|
1547
|
+
TestQualityReport: 完整质量报告
|
|
1548
|
+
"""
|
|
1549
|
+
if not self.quality_guard:
|
|
1550
|
+
self.quality_guard = TestQualityGuard("", "")
|
|
1551
|
+
|
|
1552
|
+
if module_path and test_path:
|
|
1553
|
+
return self.quality_guard.__class__(module_path, test_path).audit()
|
|
1554
|
+
|
|
1555
|
+
collab_dir = os.path.dirname(os.path.abspath(__file__))
|
|
1556
|
+
reports = []
|
|
1557
|
+
for fname in os.listdir(collab_dir):
|
|
1558
|
+
if fname.endswith(".py") and not fname.startswith("_") and "test" not in fname:
|
|
1559
|
+
mod_name = fname.replace(".py", "")
|
|
1560
|
+
test_name = f"{mod_name}_test.py"
|
|
1561
|
+
mod_full = os.path.join(collab_dir, fname)
|
|
1562
|
+
test_full = os.path.join(collab_dir, test_name)
|
|
1563
|
+
if os.path.exists(test_full):
|
|
1564
|
+
try:
|
|
1565
|
+
r = self.quality_guard.__class__(mod_full, test_full).audit()
|
|
1566
|
+
reports.append(r)
|
|
1567
|
+
except Exception as e:
|
|
1568
|
+
logger.warning("Quality guard audit failed for %s: %s", mod_name, e)
|
|
1569
|
+
|
|
1570
|
+
if len(reports) == 1:
|
|
1571
|
+
return reports[0]
|
|
1572
|
+
|
|
1573
|
+
combined = TestQualityReport(
|
|
1574
|
+
module_name="project",
|
|
1575
|
+
test_file=f"{len(reports)} modules",
|
|
1576
|
+
source_file=collab_dir,
|
|
1577
|
+
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
1578
|
+
)
|
|
1579
|
+
combined.total_tests = sum(r.total_tests for r in reports)
|
|
1580
|
+
combined.issues = [i for r in reports for i in r.issues]
|
|
1581
|
+
combined.test_functions = [tf for r in reports for tf in r.test_functions]
|
|
1582
|
+
if reports:
|
|
1583
|
+
scores = [r.score.overall for r in reports]
|
|
1584
|
+
combined.score.overall = sum(scores) / len(scores) if scores else 0
|
|
1585
|
+
combined.audit_time = sum(r.audit_time for r in reports)
|
|
1586
|
+
return combined
|
|
1587
|
+
|
|
1588
|
+
def get_performance_stats(self) -> Dict[str, Any]:
|
|
1589
|
+
"""获取性能统计信息"""
|
|
1590
|
+
return self._perf_monitor.get_statistics()
|
|
1591
|
+
|
|
1592
|
+
def check_performance_regression(self) -> Optional[Dict[str, Any]]:
|
|
1593
|
+
"""检查是否存在性能回归"""
|
|
1594
|
+
return self._perf_monitor.detect_regression()
|
|
1595
|
+
|
|
1596
|
+
def export_performance_metrics(self, output_file: str):
|
|
1597
|
+
"""导出性能指标到文件"""
|
|
1598
|
+
self._perf_monitor.export_metrics(output_file, allowed_base_dir=self.persist_dir)
|
|
1599
|
+
|
|
1600
|
+
def clear_performance_history(self):
|
|
1601
|
+
"""清除性能历史数据"""
|
|
1602
|
+
self._perf_monitor.clear()
|
|
1603
|
+
logger.info("Performance history cleared")
|
|
1604
|
+
|
|
1605
|
+
def shutdown(self):
|
|
1606
|
+
"""优雅关闭所有组件"""
|
|
1607
|
+
if self.warmup_manager:
|
|
1608
|
+
try:
|
|
1609
|
+
self.warmup_manager.shutdown()
|
|
1610
|
+
except Exception as e:
|
|
1611
|
+
logger.warning("Warmup shutdown failed: %s", e)
|
|
1612
|
+
|
|
1613
|
+
if self.memory_bridge:
|
|
1614
|
+
try:
|
|
1615
|
+
self.memory_bridge.cleanup_expired_memories()
|
|
1616
|
+
except Exception as e:
|
|
1617
|
+
logger.warning("Memory cleanup failed: %s", e)
|
|
1618
|
+
|
|
1619
|
+
if self.usage_tracker:
|
|
1620
|
+
try:
|
|
1621
|
+
self.usage_tracker.persist()
|
|
1622
|
+
except Exception as e:
|
|
1623
|
+
logger.warning("Usage tracker persist failed: %s", e)
|
|
1624
|
+
|
|
1625
|
+
|
|
1626
|
+
def create_dispatcher(**kwargs) -> MultiAgentDispatcher:
|
|
1627
|
+
"""工厂函数 - 创建并初始化调度器实例"""
|
|
1628
|
+
return MultiAgentDispatcher(**kwargs)
|
|
1629
|
+
|
|
1630
|
+
|
|
1631
|
+
def quick_collaborate(task: str, **kwargs) -> DispatchResult:
|
|
1632
|
+
"""便捷函数 - 单次调用完成协作"""
|
|
1633
|
+
disp = create_dispatcher(**kwargs)
|
|
1634
|
+
result = disp.dispatch(task)
|
|
1635
|
+
disp.shutdown()
|
|
1636
|
+
return result
|