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,267 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
DevSquad Protocol Interface Definitions
|
|
5
|
+
|
|
6
|
+
Defines abstract interfaces (Protocol) for core DevSquad modules, supporting:
|
|
7
|
+
- Multiple implementations (Redis, filesystem, memory, etc.)
|
|
8
|
+
- Degradation mechanism (Null Provider)
|
|
9
|
+
- Dependency injection and test mocking
|
|
10
|
+
- Runtime availability detection
|
|
11
|
+
|
|
12
|
+
Based on Python 3.8+ typing.Protocol (structural subtyping).
|
|
13
|
+
|
|
14
|
+
Version: v1.0
|
|
15
|
+
Created: 2026-05-01
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Protocol, Optional, Dict, Any, List, Callable
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ============================================================================
|
|
22
|
+
# CacheProvider: LLM response cache interface
|
|
23
|
+
# ============================================================================
|
|
24
|
+
|
|
25
|
+
class CacheProvider(Protocol):
|
|
26
|
+
"""
|
|
27
|
+
LLM response cache interface.
|
|
28
|
+
|
|
29
|
+
Use cases:
|
|
30
|
+
- Cache LLM API responses to reduce duplicate calls
|
|
31
|
+
- Support multiple backends (filesystem, Redis, memory, etc.)
|
|
32
|
+
- Provide TTL expiration mechanism
|
|
33
|
+
|
|
34
|
+
Implementations:
|
|
35
|
+
- LLMCache (filesystem-based)
|
|
36
|
+
- RedisCache (Redis-based)
|
|
37
|
+
- MemoryCache (in-memory)
|
|
38
|
+
- NullCacheProvider (no-op, for degradation)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def get(self, prompt: str, backend: str, model: str) -> Optional[str]:
|
|
42
|
+
"""Retrieve cached response. Returns None on cache miss."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def set(self, prompt: str, response: str, backend: str, model: str, ttl: Optional[int] = None) -> None:
|
|
46
|
+
"""Store response in cache. ttl is optional expiration in seconds."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def clear(self) -> None:
|
|
50
|
+
"""Clear all cached entries."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def is_available(self) -> bool:
|
|
54
|
+
"""Check if cache is available. False triggers degradation."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
58
|
+
"""Return cache statistics (hit_count, miss_count, hit_rate, etc.)."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ============================================================================
|
|
63
|
+
# RetryProvider: LLM call retry interface
|
|
64
|
+
# ============================================================================
|
|
65
|
+
|
|
66
|
+
class RetryProvider(Protocol):
|
|
67
|
+
"""
|
|
68
|
+
LLM call retry interface.
|
|
69
|
+
|
|
70
|
+
Use cases:
|
|
71
|
+
- Handle LLM API transient failures (network timeout, rate limiting, etc.)
|
|
72
|
+
- Support exponential backoff strategies
|
|
73
|
+
- Provide fallback mechanism
|
|
74
|
+
|
|
75
|
+
Implementations:
|
|
76
|
+
- LLMRetry (tenacity-based)
|
|
77
|
+
- SimpleRetry (simple retry)
|
|
78
|
+
- NullRetryProvider (no-op, for degradation)
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def retry_with_fallback(
|
|
82
|
+
self,
|
|
83
|
+
func: Callable[[], Any],
|
|
84
|
+
max_attempts: int = 3,
|
|
85
|
+
fallback: Optional[Callable[[], Any]] = None
|
|
86
|
+
) -> Any:
|
|
87
|
+
"""Execute function with retry. Falls back to fallback if all attempts fail."""
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
def is_available(self) -> bool:
|
|
91
|
+
"""Check if retry mechanism is available."""
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
95
|
+
"""Return retry statistics (total_attempts, success_count, etc.)."""
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ============================================================================
|
|
100
|
+
# MonitorProvider: Performance monitoring interface
|
|
101
|
+
# ============================================================================
|
|
102
|
+
|
|
103
|
+
class MonitorProvider(Protocol):
|
|
104
|
+
"""
|
|
105
|
+
Performance monitoring interface.
|
|
106
|
+
|
|
107
|
+
Use cases:
|
|
108
|
+
- Record LLM call performance metrics
|
|
109
|
+
- Generate performance reports
|
|
110
|
+
- Support multiple backends (file, database, monitoring system)
|
|
111
|
+
|
|
112
|
+
Implementations:
|
|
113
|
+
- PerformanceMonitor (file-based)
|
|
114
|
+
- PrometheusMonitor (Prometheus-based)
|
|
115
|
+
- NullMonitorProvider (no-op, for degradation)
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def record_llm_call(
|
|
119
|
+
self,
|
|
120
|
+
backend: str,
|
|
121
|
+
model: str,
|
|
122
|
+
duration: float,
|
|
123
|
+
token_count: int,
|
|
124
|
+
success: bool,
|
|
125
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Record an LLM API call."""
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
def record_agent_execution(
|
|
131
|
+
self,
|
|
132
|
+
agent_role: str,
|
|
133
|
+
task: str,
|
|
134
|
+
duration: float,
|
|
135
|
+
success: bool,
|
|
136
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Record an agent execution."""
|
|
139
|
+
...
|
|
140
|
+
|
|
141
|
+
def generate_report(self, output_path: str) -> None:
|
|
142
|
+
"""Generate performance report to output_path."""
|
|
143
|
+
...
|
|
144
|
+
|
|
145
|
+
def is_available(self) -> bool:
|
|
146
|
+
"""Check if monitoring is available."""
|
|
147
|
+
...
|
|
148
|
+
|
|
149
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
150
|
+
"""Return monitoring statistics (total_llm_calls, avg_duration, etc.)."""
|
|
151
|
+
...
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ============================================================================
|
|
155
|
+
# MemoryProvider: Personalization memory interface
|
|
156
|
+
# ============================================================================
|
|
157
|
+
|
|
158
|
+
class MemoryProvider(Protocol):
|
|
159
|
+
"""
|
|
160
|
+
Personalization memory interface.
|
|
161
|
+
|
|
162
|
+
Use cases:
|
|
163
|
+
- Store and retrieve user preferences and rules
|
|
164
|
+
- Support external memory systems (e.g., CarryMem)
|
|
165
|
+
- Inject rules into agent prompts
|
|
166
|
+
|
|
167
|
+
Implementations:
|
|
168
|
+
- CarryMemAdapter (CarryMem integration)
|
|
169
|
+
- LocalMemory (local file storage)
|
|
170
|
+
- NullMemoryProvider (no-op, for degradation)
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def get_rules(self, user_id: str, context: Optional[Dict[str, Any]] = None) -> List[str]:
|
|
174
|
+
"""Retrieve user rules. Returns list of rule strings."""
|
|
175
|
+
...
|
|
176
|
+
|
|
177
|
+
def add_rule(self, user_id: str, rule: str, metadata: Optional[Dict[str, Any]] = None) -> None:
|
|
178
|
+
"""Add a user rule."""
|
|
179
|
+
...
|
|
180
|
+
|
|
181
|
+
def update_rule(self, user_id: str, rule_id: str, rule: str) -> None:
|
|
182
|
+
"""Update an existing user rule."""
|
|
183
|
+
...
|
|
184
|
+
|
|
185
|
+
def delete_rule(self, user_id: str, rule_id: str) -> None:
|
|
186
|
+
"""Delete a user rule."""
|
|
187
|
+
...
|
|
188
|
+
|
|
189
|
+
def is_available(self) -> bool:
|
|
190
|
+
"""Check if memory system is available."""
|
|
191
|
+
...
|
|
192
|
+
|
|
193
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
194
|
+
"""Return memory statistics (total_users, total_rules, etc.)."""
|
|
195
|
+
...
|
|
196
|
+
|
|
197
|
+
def match_rules(self, task_description: str, user_id: str,
|
|
198
|
+
role: Optional[str] = None, max_rules: int = 5) -> List[Dict[str, Any]]:
|
|
199
|
+
"""Match rules based on task description and role.
|
|
200
|
+
|
|
201
|
+
Returns list of rule dicts with keys:
|
|
202
|
+
- rule_type: str (forbid/avoid/always)
|
|
203
|
+
- trigger: str (condition that activates the rule)
|
|
204
|
+
- action: str (what the rule requires)
|
|
205
|
+
- relevance_score: float (0.0-1.0)
|
|
206
|
+
- rule_id: str (unique identifier)
|
|
207
|
+
- override: bool (whether rule cannot be overridden)
|
|
208
|
+
"""
|
|
209
|
+
...
|
|
210
|
+
|
|
211
|
+
def format_rules_as_prompt(self, rules: List[Dict[str, Any]]) -> str:
|
|
212
|
+
"""Format matched rules as injectable prompt text.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
rules: List of rule dicts from match_rules()
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Formatted string ready for prompt injection, or empty string if no rules.
|
|
219
|
+
"""
|
|
220
|
+
...
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ============================================================================
|
|
224
|
+
# Exception definitions
|
|
225
|
+
# ============================================================================
|
|
226
|
+
|
|
227
|
+
class ProviderError(Exception):
|
|
228
|
+
"""Base exception for all provider errors."""
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class CacheError(ProviderError):
|
|
233
|
+
"""Cache operation error."""
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class RetryError(ProviderError):
|
|
238
|
+
"""Retry operation error."""
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class MonitorError(ProviderError):
|
|
243
|
+
"""Monitor operation error."""
|
|
244
|
+
pass
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class MemoryProviderError(ProviderError):
|
|
248
|
+
"""Memory provider operation error."""
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# ============================================================================
|
|
253
|
+
# Version info
|
|
254
|
+
# ============================================================================
|
|
255
|
+
|
|
256
|
+
__version__ = "1.0.0"
|
|
257
|
+
__all__ = [
|
|
258
|
+
"CacheProvider",
|
|
259
|
+
"RetryProvider",
|
|
260
|
+
"MonitorProvider",
|
|
261
|
+
"MemoryProvider",
|
|
262
|
+
"ProviderError",
|
|
263
|
+
"CacheError",
|
|
264
|
+
"RetryError",
|
|
265
|
+
"MonitorError",
|
|
266
|
+
"MemoryProviderError",
|
|
267
|
+
]
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import re
|
|
3
|
+
from typing import Dict, List, Any
|
|
4
|
+
|
|
5
|
+
from .models import ROLE_REGISTRY
|
|
6
|
+
|
|
7
|
+
ROLE_TEMPLATES = {rid: {"name": rdef.name, "prompt": rdef.prompt, "keywords": rdef.keywords} for rid, rdef in ROLE_REGISTRY.items()}
|
|
8
|
+
|
|
9
|
+
_I18N_SUMMARY = {
|
|
10
|
+
"zh": {
|
|
11
|
+
"task_done": "任务「{task}」已完成多Agent协作。",
|
|
12
|
+
"roles": "参与角色: {roles} ({count}个)",
|
|
13
|
+
"workers_ok": "执行结果: {done}/{total} 个Worker成功",
|
|
14
|
+
"duration": "协作耗时: {dur:.2f}s",
|
|
15
|
+
"sp_findings": "Scratchpad关键发现: {sp}",
|
|
16
|
+
},
|
|
17
|
+
"en": {
|
|
18
|
+
"task_done": "Task \"{task}\" completed with multi-agent collaboration.",
|
|
19
|
+
"roles": "Roles: {roles} ({count})",
|
|
20
|
+
"workers_ok": "Workers: {done}/{total} succeeded",
|
|
21
|
+
"duration": "Duration: {dur:.2f}s",
|
|
22
|
+
"sp_findings": "Scratchpad findings: {sp}",
|
|
23
|
+
},
|
|
24
|
+
"ja": {
|
|
25
|
+
"task_done": "タスク「{task}」がマルチエージェントコラボレーションで完了しました。",
|
|
26
|
+
"roles": "参加ロール: {roles} ({count})",
|
|
27
|
+
"workers_ok": "実行結果: {done}/{total} Worker成功",
|
|
28
|
+
"duration": "所要時間: {dur:.2f}s",
|
|
29
|
+
"sp_findings": "スクラッチパッド発見: {sp}",
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_ROLE_I18N = {
|
|
34
|
+
"zh": {"architect": "架构师", "product-manager": "产品经理", "security": "安全专家",
|
|
35
|
+
"tester": "测试专家", "solo-coder": "开发者", "devops": "运维工程师", "ui-designer": "UI设计师"},
|
|
36
|
+
"en": {"architect": "Architect", "product-manager": "Product Manager", "security": "Security Expert",
|
|
37
|
+
"tester": "Tester", "solo-coder": "Coder", "devops": "DevOps", "ui-designer": "UI Designer"},
|
|
38
|
+
"ja": {"architect": "アーキテクト", "product-manager": "プロダクトマネージャー", "security": "セキュリティ専門家",
|
|
39
|
+
"tester": "テスター", "solo-coder": "コーダー", "devops": "DevOps", "ui-designer": "UIデザイナー"},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ReportFormatter:
|
|
44
|
+
"""Report formatting engine for DispatchResult."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, lang: str = "zh"):
|
|
47
|
+
self.lang = lang
|
|
48
|
+
self._t = _I18N_SUMMARY.get(lang, _I18N_SUMMARY["zh"])
|
|
49
|
+
self._role_names = _ROLE_I18N.get(lang, _ROLE_I18N["zh"])
|
|
50
|
+
|
|
51
|
+
def build_summary(self, task: str, roles: List[str],
|
|
52
|
+
exec_result, sp_summary: str) -> str:
|
|
53
|
+
t = self._t
|
|
54
|
+
role_names = [self._role_names.get(r, ROLE_TEMPLATES.get(r, {}).get("name", r)) for r in roles]
|
|
55
|
+
parts = [
|
|
56
|
+
t["task_done"].format(task=task[:80]),
|
|
57
|
+
t["roles"].format(roles=", ".join(role_names), count=len(roles)),
|
|
58
|
+
]
|
|
59
|
+
if exec_result.results:
|
|
60
|
+
done = sum(1 for r in exec_result.results if r.success)
|
|
61
|
+
parts.append(t["workers_ok"].format(done=done, total=len(exec_result.results)))
|
|
62
|
+
if exec_result.duration_seconds:
|
|
63
|
+
parts.append(t["duration"].format(dur=exec_result.duration_seconds))
|
|
64
|
+
if sp_summary:
|
|
65
|
+
parts.append(t["sp_findings"].format(sp=sp_summary[:200]))
|
|
66
|
+
return "\n".join(parts)
|
|
67
|
+
|
|
68
|
+
def format_structured_report(self, result,
|
|
69
|
+
include_action_items: bool = True,
|
|
70
|
+
include_timing: bool = False) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Generate structured report (v3.2 UI Designer spec).
|
|
73
|
+
|
|
74
|
+
Report hierarchy: Summary card -> Role assignments -> Key findings
|
|
75
|
+
-> Conflict resolution -> Action items
|
|
76
|
+
"""
|
|
77
|
+
lines = []
|
|
78
|
+
status_icon = "✅" if result.success else "❌"
|
|
79
|
+
status_text = "协作完成" if result.success else "协作异常"
|
|
80
|
+
|
|
81
|
+
lines.append(f"# {status_icon} Multi-Agent 协作报告")
|
|
82
|
+
lines.append("")
|
|
83
|
+
|
|
84
|
+
lines.append("---")
|
|
85
|
+
lines.append(f"## 📋 任务摘要")
|
|
86
|
+
lines.append("")
|
|
87
|
+
lines.append(f"| 项目 | 内容 |")
|
|
88
|
+
lines.append(f"|------|------|")
|
|
89
|
+
lines.append(f"| **任务** | {result.task_description[:100]} |")
|
|
90
|
+
lines.append(f"| **状态** | {status_text} |")
|
|
91
|
+
lines.append(f"| **参与角色** | {len(result.matched_roles)} 个 ({', '.join(result.matched_roles)}) |")
|
|
92
|
+
lines.append(f"| **总耗时** | {result.duration_seconds:.2f}s |")
|
|
93
|
+
|
|
94
|
+
if result.worker_results:
|
|
95
|
+
success_count = sum(1 for w in result.worker_results if w.get('success'))
|
|
96
|
+
total_count = len(result.worker_results)
|
|
97
|
+
lines.append(f"| **执行成功率** | {success_count}/{total_count} ({success_count/total_count*100:.0f}%) |")
|
|
98
|
+
|
|
99
|
+
if result.errors:
|
|
100
|
+
lines.append(f"| **错误数** | {len(result.errors)} |")
|
|
101
|
+
lines.append("")
|
|
102
|
+
lines.append("---")
|
|
103
|
+
lines.append("")
|
|
104
|
+
|
|
105
|
+
if result.worker_results:
|
|
106
|
+
lines.append("## 👥 角色分配与产出")
|
|
107
|
+
lines.append("")
|
|
108
|
+
lines.append("| 角色 | 状态 | 核心产出 (预览) |")
|
|
109
|
+
lines.append("|------|------|----------------|")
|
|
110
|
+
|
|
111
|
+
for wr in result.worker_results:
|
|
112
|
+
role_name = wr.get('role', 'unknown')
|
|
113
|
+
role_display = ROLE_TEMPLATES.get(role_name, {}).get('name', role_name)
|
|
114
|
+
status_icon = '✅' if wr.get('success') else '❌'
|
|
115
|
+
output_preview = (wr.get('output') or '(无输出)')[:80].replace('\n', ' ')
|
|
116
|
+
lines.append(f"| **{role_display}** | {status_icon} | {output_preview} |")
|
|
117
|
+
|
|
118
|
+
lines.append("")
|
|
119
|
+
lines.append("---")
|
|
120
|
+
lines.append("")
|
|
121
|
+
|
|
122
|
+
if result.scratchpad_summary:
|
|
123
|
+
lines.append("## 🔍 关键发现")
|
|
124
|
+
lines.append("")
|
|
125
|
+
findings = self.extract_findings(result.scratchpad_summary)
|
|
126
|
+
if findings:
|
|
127
|
+
for i, finding in enumerate(findings[:8], 1):
|
|
128
|
+
lines.append(f"{i}. {finding}")
|
|
129
|
+
else:
|
|
130
|
+
lines.append(f"> {result.scratchpad_summary[:300]}")
|
|
131
|
+
lines.append("")
|
|
132
|
+
lines.append("---")
|
|
133
|
+
lines.append("")
|
|
134
|
+
|
|
135
|
+
if result.consensus_records:
|
|
136
|
+
lines.append("## 🗳️ 共识决策与冲突解决")
|
|
137
|
+
lines.append("")
|
|
138
|
+
|
|
139
|
+
for cr in result.consensus_records:
|
|
140
|
+
topic = cr.get('topic', '未知议题')
|
|
141
|
+
outcome = cr.get('outcome', '')
|
|
142
|
+
decision = cr.get('final_decision', '')
|
|
143
|
+
|
|
144
|
+
if outcome == 'APPROVED':
|
|
145
|
+
badge = "🟢 通过"
|
|
146
|
+
elif outcome == 'REJECTED':
|
|
147
|
+
badge = "🔴 否决"
|
|
148
|
+
elif outcome == 'SPLIT':
|
|
149
|
+
badge = "🟡 分歧"
|
|
150
|
+
elif outcome == 'ESCALATED':
|
|
151
|
+
badge = "🔵 升级"
|
|
152
|
+
else:
|
|
153
|
+
badge = "⏪ 超时"
|
|
154
|
+
|
|
155
|
+
votes_for = cr.get('votes_for', 0)
|
|
156
|
+
votes_against = cr.get('votes_against', 0)
|
|
157
|
+
votes_abstain = cr.get('votes_abstain', 0)
|
|
158
|
+
|
|
159
|
+
lines.append(f"- **{topic}** `{badge}`")
|
|
160
|
+
lines.append(f" - 投票: ✅{votes_for} ❌{votes_against} ⚪{votes_abstain}")
|
|
161
|
+
if decision:
|
|
162
|
+
lines.append(f" - 决策: {decision[:100]}")
|
|
163
|
+
lines.append("")
|
|
164
|
+
|
|
165
|
+
lines.append("---")
|
|
166
|
+
lines.append("")
|
|
167
|
+
|
|
168
|
+
if include_action_items:
|
|
169
|
+
action_items = self.generate_action_items(result)
|
|
170
|
+
if action_items:
|
|
171
|
+
lines.append("## 📌 行动项建议")
|
|
172
|
+
lines.append("")
|
|
173
|
+
for i, item in enumerate(action_items, 1):
|
|
174
|
+
priority = item.get('priority', 'M')
|
|
175
|
+
priority_badge = {'H': '🔴高', 'M': '🟡中', 'L': '🟢低'}.get(priority, '⚪')
|
|
176
|
+
lines.append(f"{i}. [{priority_badge}] {item['text']}")
|
|
177
|
+
lines.append("")
|
|
178
|
+
|
|
179
|
+
ext_sections = []
|
|
180
|
+
|
|
181
|
+
if result.compression_info:
|
|
182
|
+
ci = result.compression_info
|
|
183
|
+
ext_sections.append(
|
|
184
|
+
f"**上下文压缩**: 级别={ci.get('level','N/A')} | "
|
|
185
|
+
f"{ci.get('original_tokens',0)}→{ci.get('compressed_tokens',0)} tokens | "
|
|
186
|
+
f"节省 {ci.get('reduction_pct',0)}%"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if result.memory_stats:
|
|
190
|
+
ms = result.memory_stats
|
|
191
|
+
ext_sections.append(
|
|
192
|
+
f"**记忆系统**: 总记忆={ms.get('total_memories',0)} | "
|
|
193
|
+
f"捕获次数={ms.get('total_captures',0)}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if result.skill_proposals and len(result.skill_proposals) > 0:
|
|
197
|
+
proposals = [f"{p.get('title','')}({p.get('confidence',0):.0%})" for p in result.skill_proposals[:3]]
|
|
198
|
+
ext_sections.append(f"**技能提案**: {', '.join(proposals)}")
|
|
199
|
+
|
|
200
|
+
if result.permission_checks:
|
|
201
|
+
allowed = sum(1 for pc in result.permission_checks if pc.get('allowed'))
|
|
202
|
+
total_pc = len(result.permission_checks)
|
|
203
|
+
ext_sections.append(f"**权限检查**: {allowed}/{total_pc} 通过")
|
|
204
|
+
|
|
205
|
+
if ext_sections:
|
|
206
|
+
lines.append("---")
|
|
207
|
+
lines.append("")
|
|
208
|
+
lines.append("> **系统信息**")
|
|
209
|
+
for section in ext_sections:
|
|
210
|
+
lines.append(f"> {section}")
|
|
211
|
+
lines.append("")
|
|
212
|
+
|
|
213
|
+
if include_timing and result.details.get('timing'):
|
|
214
|
+
timing = result.details['timing']
|
|
215
|
+
lines.append("")
|
|
216
|
+
lines.append("<details>")
|
|
217
|
+
lines.append("<summary>⏱️ 各阶段耗时详情</summary>")
|
|
218
|
+
lines.append("")
|
|
219
|
+
lines.append("| 阶段 | 耗时(s) |")
|
|
220
|
+
lines.append("|------|---------|")
|
|
221
|
+
for stage, duration in timing.items():
|
|
222
|
+
if duration > 0.001:
|
|
223
|
+
lines.append(f"| {stage} | {duration:.3f} |")
|
|
224
|
+
lines.append("")
|
|
225
|
+
lines.append("</details>")
|
|
226
|
+
lines.append("")
|
|
227
|
+
|
|
228
|
+
if result.errors:
|
|
229
|
+
lines.append("")
|
|
230
|
+
lines.append("> ⚠️ **错误/警告**:")
|
|
231
|
+
for err in result.errors[:5]:
|
|
232
|
+
lines.append(f"> - {err[:150]}")
|
|
233
|
+
|
|
234
|
+
return "\n".join(lines)
|
|
235
|
+
|
|
236
|
+
def format_compact_report(self, result) -> str:
|
|
237
|
+
"""Generate compact report suitable for terminal quick view."""
|
|
238
|
+
status = "✅" if result.success else "❌"
|
|
239
|
+
roles_str = ", ".join(result.matched_roles) if result.matched_roles else "无"
|
|
240
|
+
|
|
241
|
+
parts = [
|
|
242
|
+
f"[{status}] 任务: {result.task_description[:60]}",
|
|
243
|
+
f"角色: {roles_str} ({len(result.matched_roles)}个)",
|
|
244
|
+
f"耗时: {result.duration_seconds:.2f}s",
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
if result.worker_results:
|
|
248
|
+
done = sum(1 for w in result.worker_results if w.get('success'))
|
|
249
|
+
parts.append(f"Worker: {done}/{len(result.worker_results)} 成功")
|
|
250
|
+
|
|
251
|
+
if result.scratchpad_summary:
|
|
252
|
+
parts.append(f"发现: {result.scratchpad_summary[:120]}")
|
|
253
|
+
|
|
254
|
+
if result.consensus_records:
|
|
255
|
+
approved = sum(1 for c in result.consensus_records if c.get('outcome') == 'APPROVED')
|
|
256
|
+
parts.append(f"共识: {approved}/{len(result.consensus_records)} 通过")
|
|
257
|
+
|
|
258
|
+
if result.errors:
|
|
259
|
+
parts.append(f"错误: {len(result.errors)} 个")
|
|
260
|
+
|
|
261
|
+
return "\n".join(parts)
|
|
262
|
+
|
|
263
|
+
def extract_findings(self, scratchpad_summary: str) -> List[str]:
|
|
264
|
+
"""
|
|
265
|
+
Extract key findings from Scratchpad summary text.
|
|
266
|
+
|
|
267
|
+
Supports: numbered lists, bullet lists, semicolon-separated, sentence splitting.
|
|
268
|
+
"""
|
|
269
|
+
if not scratchpad_summary:
|
|
270
|
+
return []
|
|
271
|
+
|
|
272
|
+
findings = []
|
|
273
|
+
text = scratchpad_summary.strip()
|
|
274
|
+
|
|
275
|
+
numbered = re.findall(r'(?:^|\n)\s*(\d+)[\.\、\)]\s*(.+?)(?=\n\s*\d+[\.\、\)]|\Z)', text, re.MULTILINE)
|
|
276
|
+
if numbered and len(numbered) >= 2:
|
|
277
|
+
findings = [item.strip() for _, item in numbered]
|
|
278
|
+
return [f for f in findings if f]
|
|
279
|
+
|
|
280
|
+
bulleted = re.findall(r'(?:^|\n)\s*[-*•]\s*(.+?)(?=\n\s*[-*•]|\Z)', text, re.MULTILINE)
|
|
281
|
+
if bulleted and len(bulleted) >= 2:
|
|
282
|
+
findings = [item.strip() for item in bulleted]
|
|
283
|
+
return [f for f in findings if f]
|
|
284
|
+
|
|
285
|
+
if ';' in text and text.count(';') >= 2:
|
|
286
|
+
findings = [f.strip() for f in text.split(';') if f.strip()]
|
|
287
|
+
return findings[:10]
|
|
288
|
+
|
|
289
|
+
sentences = re.split(r'[。!?.!?\n]+', text)
|
|
290
|
+
findings = [s.strip() for s in sentences if len(s.strip()) >= 10]
|
|
291
|
+
return findings[:8]
|
|
292
|
+
|
|
293
|
+
def generate_action_items(self, result) -> List[Dict[str, str]]:
|
|
294
|
+
"""
|
|
295
|
+
Auto-generate action item suggestions based on dispatch result.
|
|
296
|
+
|
|
297
|
+
Rules:
|
|
298
|
+
- Errors -> High priority fix suggestions
|
|
299
|
+
- Unresolved conflicts -> Medium priority manual review
|
|
300
|
+
- All success -> Low priority follow-up optimization
|
|
301
|
+
- Memory data -> Suggest reviewing historical decisions
|
|
302
|
+
"""
|
|
303
|
+
items = []
|
|
304
|
+
|
|
305
|
+
if result.errors:
|
|
306
|
+
items.append({
|
|
307
|
+
'priority': 'H',
|
|
308
|
+
'text': f"修复 {len(result.errors)} 个执行错误,首要关注: {result.errors[0][:80]}"
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
unresolved = [c for c in result.consensus_records
|
|
312
|
+
if c.get('outcome') in ('SPLIT', 'ESCALATED', 'TIMEOUT')]
|
|
313
|
+
if unresolved:
|
|
314
|
+
items.append({
|
|
315
|
+
'priority': 'H' if len(unresolved) > 2 else 'M',
|
|
316
|
+
'text': f"人工审核 {len(unresolved)} 个未决共识议题: {', '.join([u.get('topic','') for u in unresolved[:3]])}"
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
failed_workers = [w for w in result.worker_results if not w.get('success')]
|
|
320
|
+
if failed_workers:
|
|
321
|
+
roles_failed = [ROLE_TEMPLATES.get(w.get('role',''), {}).get('name', w.get('role','')) for w in failed_workers]
|
|
322
|
+
items.append({
|
|
323
|
+
'priority': 'M',
|
|
324
|
+
'text': f"排查以下角色执行失败原因: {', '.join(roles_failed[:3])}"
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
if result.success and not result.errors:
|
|
328
|
+
if result.memory_stats and result.memory_stats.get('total_memories', 0) > 0:
|
|
329
|
+
items.append({
|
|
330
|
+
'priority': 'L',
|
|
331
|
+
'text': f"回顾历史记忆 (共{result.memory_stats['total_memories']}条),提取可复用经验"
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
if result.skill_proposals and len(result.skill_proposals) > 0:
|
|
335
|
+
top_proposal = result.skill_proposals[0]
|
|
336
|
+
items.append({
|
|
337
|
+
'priority': 'L',
|
|
338
|
+
'text': f"评估新技能提案「{top_proposal.get('title','')}」(置信度{top_proposal.get('confidence',0):.0%})是否值得固化"
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
items.append({
|
|
342
|
+
'priority': 'L',
|
|
343
|
+
'text': "任务已完成,可归档此协作记录供未来参考"
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
if not items:
|
|
347
|
+
items.append({
|
|
348
|
+
'priority': 'M',
|
|
349
|
+
'text': "审查各角色产出内容,确认是否符合预期"
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
return items[:6]
|