ata-coder 2.4.2__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.
- ata_coder/__init__.py +1 -0
- ata_coder/agent.py +874 -0
- ata_coder/agent_compact.py +190 -0
- ata_coder/agent_controller.py +218 -0
- ata_coder/agent_extension.py +69 -0
- ata_coder/agent_routing.py +105 -0
- ata_coder/agent_subsystems.py +72 -0
- ata_coder/agent_tools.py +318 -0
- ata_coder/agent_undo.py +63 -0
- ata_coder/anthropic_client.py +465 -0
- ata_coder/change_tracker.py +368 -0
- ata_coder/clawd_integration.py +574 -0
- ata_coder/commands/__init__.py +128 -0
- ata_coder/commands/_core.py +184 -0
- ata_coder/commands/_safety.py +95 -0
- ata_coder/commands/_settings.py +241 -0
- ata_coder/commands/_workflow.py +451 -0
- ata_coder/commands.py +974 -0
- ata_coder/config.py +257 -0
- ata_coder/core/__init__.py +35 -0
- ata_coder/core/events.py +73 -0
- ata_coder/core/queue.py +85 -0
- ata_coder/core/state.py +17 -0
- ata_coder/event_queue.py +5 -0
- ata_coder/extension.py +654 -0
- ata_coder/extensions/__init__.py +1 -0
- ata_coder/extensions/hello_skill.py +47 -0
- ata_coder/fool_proof.py +295 -0
- ata_coder/git_workflow.py +371 -0
- ata_coder/gui.py +511 -0
- ata_coder/llm_client.py +543 -0
- ata_coder/main.py +814 -0
- ata_coder/mcp_client.py +1095 -0
- ata_coder/memory.py +539 -0
- ata_coder/model_registry.py +134 -0
- ata_coder/model_router.py +105 -0
- ata_coder/permissions.py +274 -0
- ata_coder/privilege.py +464 -0
- ata_coder/project.py +273 -0
- ata_coder/prompt_template.py +423 -0
- ata_coder/prompts/auto-mode.md +7 -0
- ata_coder/prompts/coding-rules.md +40 -0
- ata_coder/prompts/execution-guardrails.md +14 -0
- ata_coder/prompts/memory-system.md +24 -0
- ata_coder/prompts/output-style.md +23 -0
- ata_coder/prompts/safety.md +17 -0
- ata_coder/prompts/slash-commands.md +24 -0
- ata_coder/prompts/sub-agents.md +38 -0
- ata_coder/prompts/system-reminders.md +17 -0
- ata_coder/prompts/system.md +105 -0
- ata_coder/prompts/tool-policy.md +46 -0
- ata_coder/repl_theme.py +99 -0
- ata_coder/repl_tracker.py +89 -0
- ata_coder/repl_ui.py +1214 -0
- ata_coder/safety_guard.py +434 -0
- ata_coder/self_correct.py +346 -0
- ata_coder/server.py +882 -0
- ata_coder/server_session.py +159 -0
- ata_coder/server_shell.py +129 -0
- ata_coder/session.py +431 -0
- ata_coder/settings.py +439 -0
- ata_coder/setup_wizard.py +136 -0
- ata_coder/skill_extension.py +92 -0
- ata_coder/skills/architect/SKILL.md +42 -0
- ata_coder/skills/code-reviewer/SKILL.md +37 -0
- ata_coder/skills/codecraft/SKILL.md +452 -0
- ata_coder/skills/debugger/SKILL.md +45 -0
- ata_coder/skills/doc-writer/SKILL.md +36 -0
- ata_coder/skills/general-coder/SKILL.md +76 -0
- ata_coder/skills/math-calculator/README.md +40 -0
- ata_coder/skills/math-calculator/SKILL.md +59 -0
- ata_coder/skills/math-calculator/handler.py +103 -0
- ata_coder/skills/math-calculator/prompts/system.md +8 -0
- ata_coder/skills/math-calculator/requirements.txt +2 -0
- ata_coder/skills/math-calculator/resources/constants.json +8 -0
- ata_coder/skills/math-calculator/tests/test_handler.py +53 -0
- ata_coder/skills/security-auditor/SKILL.md +40 -0
- ata_coder/skills/test-writer/SKILL.md +36 -0
- ata_coder/skills/weather-skill/README.md +45 -0
- ata_coder/skills/weather-skill/handler.py +76 -0
- ata_coder/skills/weather-skill/manifest.json +48 -0
- ata_coder/skills/weather-skill/prompts/system_prompt.txt +9 -0
- ata_coder/skills/weather-skill/prompts/user_prompt_template.txt +3 -0
- ata_coder/skills/weather-skill/requirements.txt +1 -0
- ata_coder/skills/weather-skill/resources/city_list.json +17 -0
- ata_coder/skills/weather-skill/resources/error_messages.json +7 -0
- ata_coder/skills/weather-skill/tests/test_handler.py +28 -0
- ata_coder/skills/weather-skill/weather_utils.py +50 -0
- ata_coder/skills.py +1014 -0
- ata_coder/sub_agent.py +273 -0
- ata_coder/sub_agent_manager.py +203 -0
- ata_coder/system_prompt_builder.py +146 -0
- ata_coder/task_planner.py +391 -0
- ata_coder/terminal.py +318 -0
- ata_coder/test_runner.py +219 -0
- ata_coder/thread_supervisor.py +195 -0
- ata_coder/tool_defs.py +335 -0
- ata_coder/tools/__init__.py +11 -0
- ata_coder/tools/definitions.py +335 -0
- ata_coder/tools/executor.py +1036 -0
- ata_coder/tools/result.py +26 -0
- ata_coder/tools/subagent.py +332 -0
- ata_coder/tools/web.py +361 -0
- ata_coder/tools.py +1576 -0
- ata_coder/types.py +92 -0
- ata_coder/utils.py +113 -0
- ata_coder/web/css/style.css +180 -0
- ata_coder/web/index.html +84 -0
- ata_coder/web/js/app.js +489 -0
- ata_coder/web/package-lock.json +25 -0
- ata_coder/web/package.json +10 -0
- ata_coder/web/tsconfig.json +13 -0
- ata_coder-2.4.2.dist-info/METADATA +799 -0
- ata_coder-2.4.2.dist-info/RECORD +118 -0
- ata_coder-2.4.2.dist-info/WHEEL +5 -0
- ata_coder-2.4.2.dist-info/entry_points.txt +2 -0
- ata_coder-2.4.2.dist-info/licenses/LICENSE +21 -0
- ata_coder-2.4.2.dist-info/top_level.txt +1 -0
ata_coder/extension.py
ADDED
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
正式扩展 API — 统一的插件系统。
|
|
4
|
+
|
|
5
|
+
提供:
|
|
6
|
+
- Extension 基类: 生命周期钩子 (load / unload / activate / deactivate)
|
|
7
|
+
- ExtensionManager: 扩展发现、注册、激活、卸载
|
|
8
|
+
- @extension 装饰器: 声明式注册
|
|
9
|
+
- ExtensionPoint: 标记类, 用于定义可扩展点
|
|
10
|
+
|
|
11
|
+
使用示例:
|
|
12
|
+
|
|
13
|
+
from .extension import Extension, extension
|
|
14
|
+
|
|
15
|
+
@extension(name="my-skill", version="1.0.0",
|
|
16
|
+
description="A custom skill extension")
|
|
17
|
+
class MySkill(Extension):
|
|
18
|
+
def on_activate(self):
|
|
19
|
+
print("Skill activated!")
|
|
20
|
+
|
|
21
|
+
def get_prompt(self) -> str:
|
|
22
|
+
return "You are an expert in..."
|
|
23
|
+
|
|
24
|
+
# 注册到全局管理器
|
|
25
|
+
from .extension import get_extension_manager
|
|
26
|
+
get_extension_manager().register(MySkill())
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
import sys
|
|
31
|
+
import threading
|
|
32
|
+
from abc import ABC
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any, Callable
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
41
|
+
# Extension metadata
|
|
42
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ExtensionMeta:
|
|
46
|
+
"""Metadata describing an extension."""
|
|
47
|
+
name: str
|
|
48
|
+
version: str = "0.1.0"
|
|
49
|
+
description: str = ""
|
|
50
|
+
author: str = ""
|
|
51
|
+
homepage: str = ""
|
|
52
|
+
license: str = ""
|
|
53
|
+
dependencies: list[str] = field(default_factory=list)
|
|
54
|
+
tags: list[str] = field(default_factory=list)
|
|
55
|
+
priority: int = 100 # lower = higher priority
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict[str, Any]:
|
|
58
|
+
return {
|
|
59
|
+
"name": self.name,
|
|
60
|
+
"version": self.version,
|
|
61
|
+
"description": self.description,
|
|
62
|
+
"author": self.author,
|
|
63
|
+
"homepage": self.homepage,
|
|
64
|
+
"license": self.license,
|
|
65
|
+
"dependencies": self.dependencies,
|
|
66
|
+
"tags": self.tags,
|
|
67
|
+
"priority": self.priority,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
72
|
+
# Extension type enum
|
|
73
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
74
|
+
|
|
75
|
+
class ExtensionType:
|
|
76
|
+
"""Well-known extension types."""
|
|
77
|
+
SKILL = "skill"
|
|
78
|
+
MCP = "mcp"
|
|
79
|
+
TEMPLATE = "template"
|
|
80
|
+
TOOL = "tool"
|
|
81
|
+
MIDDLEWARE = "middleware"
|
|
82
|
+
CUSTOM = "custom"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
86
|
+
# ExtensionPoint — marker for extensible locations
|
|
87
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
88
|
+
|
|
89
|
+
class ExtensionPoint:
|
|
90
|
+
"""
|
|
91
|
+
标记一个可扩展点。扩展可以通过名称向该点注册回调。
|
|
92
|
+
|
|
93
|
+
用法:
|
|
94
|
+
|
|
95
|
+
# 定义一个扩展点
|
|
96
|
+
ON_SYSTEM_PROMPT = ExtensionPoint("system_prompt")
|
|
97
|
+
|
|
98
|
+
# 扩展注册回调
|
|
99
|
+
ON_SYSTEM_PROMPT.register(my_callable)
|
|
100
|
+
|
|
101
|
+
# 触发所有已注册的回调
|
|
102
|
+
results = ON_SYSTEM_PROMPT.trigger(prompt="...")
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, name: str, description: str = ""):
|
|
106
|
+
self.name = name
|
|
107
|
+
self.description = description
|
|
108
|
+
self._handlers: list[Callable] = []
|
|
109
|
+
self._lock = threading.Lock()
|
|
110
|
+
|
|
111
|
+
def register(self, handler: Callable) -> None:
|
|
112
|
+
"""注册一个处理器到该扩展点(线程安全)。"""
|
|
113
|
+
with self._lock:
|
|
114
|
+
if handler not in self._handlers:
|
|
115
|
+
self._handlers.append(handler)
|
|
116
|
+
|
|
117
|
+
def unregister(self, handler: Callable) -> None:
|
|
118
|
+
"""从该扩展点移除一个处理器(线程安全)。"""
|
|
119
|
+
with self._lock:
|
|
120
|
+
try:
|
|
121
|
+
self._handlers.remove(handler)
|
|
122
|
+
except ValueError:
|
|
123
|
+
logger.debug(
|
|
124
|
+
"Handler not registered in extension point %r", self.name
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def trigger(self, *args: Any, **kwargs: Any) -> list[Any]:
|
|
128
|
+
"""
|
|
129
|
+
按注册顺序触发所有处理器(线程安全)。
|
|
130
|
+
|
|
131
|
+
在锁内拍快照,锁外执行,避免 handler 内部调用
|
|
132
|
+
register/unregister 造成死锁。
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
每个处理器返回值的列表(排除 None)。
|
|
136
|
+
"""
|
|
137
|
+
with self._lock:
|
|
138
|
+
handlers = list(self._handlers)
|
|
139
|
+
results = []
|
|
140
|
+
for handler in handlers:
|
|
141
|
+
try:
|
|
142
|
+
result = handler(*args, **kwargs)
|
|
143
|
+
if result is not None:
|
|
144
|
+
results.append(result)
|
|
145
|
+
except Exception:
|
|
146
|
+
logger.exception(
|
|
147
|
+
"Extension handler failed for %s: %s", self.name, handler
|
|
148
|
+
)
|
|
149
|
+
return results
|
|
150
|
+
|
|
151
|
+
def trigger_first(self, *args: Any, **kwargs: Any) -> Any:
|
|
152
|
+
"""
|
|
153
|
+
触发处理器,返回第一个非 None 的结果(线程安全)。
|
|
154
|
+
用于"拦截器"模式:第一个返回非空值的处理器胜出。
|
|
155
|
+
"""
|
|
156
|
+
with self._lock:
|
|
157
|
+
handlers = list(self._handlers)
|
|
158
|
+
for handler in handlers:
|
|
159
|
+
try:
|
|
160
|
+
result = handler(*args, **kwargs)
|
|
161
|
+
if result is not None:
|
|
162
|
+
return result
|
|
163
|
+
except Exception:
|
|
164
|
+
logger.exception(
|
|
165
|
+
"Extension handler failed for %s: %s", self.name, handler
|
|
166
|
+
)
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
def clear(self) -> None:
|
|
170
|
+
"""移除所有处理器(线程安全)。"""
|
|
171
|
+
with self._lock:
|
|
172
|
+
self._handlers.clear()
|
|
173
|
+
|
|
174
|
+
def __repr__(self) -> str:
|
|
175
|
+
return f"ExtensionPoint({self.name!r}, handlers={len(self._handlers)})"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
179
|
+
# Extension base class
|
|
180
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
181
|
+
|
|
182
|
+
class Extension(ABC):
|
|
183
|
+
"""
|
|
184
|
+
扩展基类。所有 ATA Coder 扩展的基类。
|
|
185
|
+
|
|
186
|
+
生命周期:
|
|
187
|
+
1. __init__() — 实例化
|
|
188
|
+
2. on_load(manager) — 被管理器加载时调用
|
|
189
|
+
3. on_activate() — 被激活时调用
|
|
190
|
+
4. on_deactivate() — 被停用时调用
|
|
191
|
+
5. on_unload() — 被卸载时调用
|
|
192
|
+
|
|
193
|
+
子类必须设置:
|
|
194
|
+
- meta: ExtensionMeta
|
|
195
|
+
|
|
196
|
+
子类可选覆盖:
|
|
197
|
+
- on_load() / on_unload() / on_activate() / on_deactivate()
|
|
198
|
+
- get_tools() → 返回工具定义列表
|
|
199
|
+
- get_prompt() → 返回系统提示字符串
|
|
200
|
+
- validate() → 验证扩展是否可用
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
meta: ExtensionMeta
|
|
204
|
+
|
|
205
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
206
|
+
super().__init_subclass__(**kwargs)
|
|
207
|
+
if not hasattr(cls, "meta"):
|
|
208
|
+
cls.meta = ExtensionMeta(name=cls.__name__)
|
|
209
|
+
|
|
210
|
+
def on_load(self, manager: "ExtensionManager") -> None:
|
|
211
|
+
"""扩展被加载到管理器时调用。"""
|
|
212
|
+
|
|
213
|
+
def on_unload(self) -> None:
|
|
214
|
+
"""扩展被卸载时调用。"""
|
|
215
|
+
|
|
216
|
+
def on_activate(self) -> None:
|
|
217
|
+
"""扩展被激活时调用。"""
|
|
218
|
+
|
|
219
|
+
def on_deactivate(self) -> None:
|
|
220
|
+
"""扩展被停用时调用。"""
|
|
221
|
+
|
|
222
|
+
def get_tools(self) -> list[dict[str, Any]]:
|
|
223
|
+
"""返回此扩展提供的工具定义列表。"""
|
|
224
|
+
return []
|
|
225
|
+
|
|
226
|
+
def get_prompt(self) -> str:
|
|
227
|
+
"""返回此扩展提供的系统提示片段。"""
|
|
228
|
+
return ""
|
|
229
|
+
|
|
230
|
+
def get_middleware(self) -> list[Callable]:
|
|
231
|
+
"""返回此扩展提供的中间件列表。"""
|
|
232
|
+
return []
|
|
233
|
+
|
|
234
|
+
def validate(self) -> tuple[bool, str]:
|
|
235
|
+
"""
|
|
236
|
+
验证扩展是否可用。
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
(ok, reason) — ok=True 表示通过, reason 为描述。
|
|
240
|
+
"""
|
|
241
|
+
return True, "OK"
|
|
242
|
+
|
|
243
|
+
def __repr__(self) -> str:
|
|
244
|
+
return f"{type(self).__name__}(name={self.meta.name!r}, v{self.meta.version})"
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
248
|
+
# Extension manager
|
|
249
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
250
|
+
|
|
251
|
+
class ExtensionManager:
|
|
252
|
+
"""
|
|
253
|
+
扩展管理器 — 发现、加载、激活和管理扩展。
|
|
254
|
+
|
|
255
|
+
用法:
|
|
256
|
+
|
|
257
|
+
mgr = ExtensionManager()
|
|
258
|
+
mgr.discover("./extensions/")
|
|
259
|
+
mgr.activate("my-skill")
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(self):
|
|
263
|
+
self._extensions: dict[str, Extension] = {}
|
|
264
|
+
self._active: set[str] = set()
|
|
265
|
+
self._loaded_dirs: list[Path] = []
|
|
266
|
+
self._lock = threading.Lock() # protects _extensions, _active, _loaded_dirs
|
|
267
|
+
|
|
268
|
+
# ── 扩展点注册表 ────────────────────────────────────────────────
|
|
269
|
+
self._extension_points: dict[str, ExtensionPoint] = {}
|
|
270
|
+
self._ep_lock = threading.Lock() # protects _extension_points
|
|
271
|
+
|
|
272
|
+
# ── Extension point management ──────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
def extension_point(self, name: str, description: str = "") -> ExtensionPoint:
|
|
275
|
+
"""
|
|
276
|
+
获取或创建一个命名扩展点(线程安全)。
|
|
277
|
+
|
|
278
|
+
扩展点提供了类型安全的钩子系统。一个模块定义扩展点,
|
|
279
|
+
多个扩展可以向其注册处理器。
|
|
280
|
+
"""
|
|
281
|
+
with self._ep_lock:
|
|
282
|
+
if name not in self._extension_points:
|
|
283
|
+
self._extension_points[name] = ExtensionPoint(name, description)
|
|
284
|
+
return self._extension_points[name]
|
|
285
|
+
|
|
286
|
+
def list_extension_points(self) -> dict[str, ExtensionPoint]:
|
|
287
|
+
"""列出所有已注册的扩展点(线程安全)。"""
|
|
288
|
+
with self._ep_lock:
|
|
289
|
+
return dict(self._extension_points)
|
|
290
|
+
|
|
291
|
+
# ── Registration ────────────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
def register(self, extension: Extension) -> bool:
|
|
294
|
+
"""
|
|
295
|
+
注册一个扩展(线程安全)。
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
True 如果成功, False 如果同名扩展已存在。
|
|
299
|
+
"""
|
|
300
|
+
name = extension.meta.name
|
|
301
|
+
|
|
302
|
+
# Validate BEFORE acquiring lock (may be slow)
|
|
303
|
+
ok, reason = extension.validate()
|
|
304
|
+
if not ok:
|
|
305
|
+
logger.error("Extension %r validation failed: %s", name, reason)
|
|
306
|
+
return False
|
|
307
|
+
|
|
308
|
+
# Insert under lock, but call on_load outside to prevent deadlock
|
|
309
|
+
with self._lock:
|
|
310
|
+
if name in self._extensions:
|
|
311
|
+
logger.debug("Extension %r already registered, skipping", name)
|
|
312
|
+
return False
|
|
313
|
+
self._extensions[name] = extension
|
|
314
|
+
|
|
315
|
+
# on_load 在锁外调用,避免回调中 register/activate 导致死锁
|
|
316
|
+
try:
|
|
317
|
+
extension.on_load(self)
|
|
318
|
+
except Exception:
|
|
319
|
+
logger.exception("Extension %r on_load failed", name)
|
|
320
|
+
|
|
321
|
+
logger.debug("Extension registered: %s v%s", name, extension.meta.version)
|
|
322
|
+
return True
|
|
323
|
+
|
|
324
|
+
def unregister(self, name: str) -> bool:
|
|
325
|
+
"""注销一个扩展(线程安全)。"""
|
|
326
|
+
with self._lock:
|
|
327
|
+
ext = self._extensions.pop(name, None)
|
|
328
|
+
if ext is None:
|
|
329
|
+
return False
|
|
330
|
+
|
|
331
|
+
with self._lock:
|
|
332
|
+
was_active = name in self._active
|
|
333
|
+
if was_active:
|
|
334
|
+
self._active.discard(name)
|
|
335
|
+
|
|
336
|
+
if was_active:
|
|
337
|
+
try:
|
|
338
|
+
ext.on_deactivate()
|
|
339
|
+
except Exception:
|
|
340
|
+
logger.exception("Extension %r on_deactivate failed", name)
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
ext.on_unload()
|
|
344
|
+
except Exception:
|
|
345
|
+
logger.exception("Extension %r on_unload failed", name)
|
|
346
|
+
|
|
347
|
+
logger.info("Extension unregistered: %s", name)
|
|
348
|
+
return True
|
|
349
|
+
|
|
350
|
+
# ── Activation ──────────────────────────────────────────────────────────
|
|
351
|
+
|
|
352
|
+
def activate(self, name: str) -> bool:
|
|
353
|
+
"""激活一个扩展(线程安全)。"""
|
|
354
|
+
with self._lock:
|
|
355
|
+
ext = self._extensions.get(name)
|
|
356
|
+
if ext is None:
|
|
357
|
+
logger.debug("Extension not found: %r", name)
|
|
358
|
+
return False
|
|
359
|
+
if name in self._active:
|
|
360
|
+
return True # already active
|
|
361
|
+
deps = list(ext.meta.dependencies)
|
|
362
|
+
|
|
363
|
+
# Activate dependencies (try raw name first, then skill: prefix)
|
|
364
|
+
for dep in deps:
|
|
365
|
+
if dep not in self._active:
|
|
366
|
+
if not self.activate(dep):
|
|
367
|
+
self.activate(f"skill:{dep}")
|
|
368
|
+
|
|
369
|
+
# on_activate 在锁外调用,避免死锁
|
|
370
|
+
try:
|
|
371
|
+
ext.on_activate()
|
|
372
|
+
except Exception:
|
|
373
|
+
logger.exception("Extension %r on_activate failed", name)
|
|
374
|
+
return False
|
|
375
|
+
|
|
376
|
+
with self._lock:
|
|
377
|
+
self._active.add(name)
|
|
378
|
+
logger.debug("Extension activated: %s", name)
|
|
379
|
+
return True
|
|
380
|
+
|
|
381
|
+
def deactivate(self, name: str) -> bool:
|
|
382
|
+
"""停用一个扩展(线程安全)。"""
|
|
383
|
+
with self._lock:
|
|
384
|
+
ext = self._extensions.get(name)
|
|
385
|
+
if ext is None or name not in self._active:
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
# 检查是否有其他扩展依赖此扩展
|
|
389
|
+
for other_name, other_ext in self._extensions.items():
|
|
390
|
+
if other_name != name and name in other_ext.meta.dependencies:
|
|
391
|
+
if other_name in self._active:
|
|
392
|
+
logger.warning(
|
|
393
|
+
"Cannot deactivate %r: required by %r", name, other_name
|
|
394
|
+
)
|
|
395
|
+
return False
|
|
396
|
+
|
|
397
|
+
self._active.discard(name)
|
|
398
|
+
|
|
399
|
+
# on_deactivate 在锁外调用
|
|
400
|
+
try:
|
|
401
|
+
ext.on_deactivate()
|
|
402
|
+
except Exception:
|
|
403
|
+
logger.exception("Extension %r on_deactivate failed", name)
|
|
404
|
+
|
|
405
|
+
logger.info("Extension deactivated: %s", name)
|
|
406
|
+
return True
|
|
407
|
+
|
|
408
|
+
# ── Discovery ───────────────────────────────────────────────────────────
|
|
409
|
+
|
|
410
|
+
def discover(self, directory: str | Path) -> list[str]:
|
|
411
|
+
"""
|
|
412
|
+
从目录中发现扩展。
|
|
413
|
+
|
|
414
|
+
扫描 directory 下的 Python 文件, 查找 @extension 装饰的类。
|
|
415
|
+
跳过以 _ 或 . 开头的文件。
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
成功加载的扩展名称列表。
|
|
419
|
+
"""
|
|
420
|
+
directory = Path(directory)
|
|
421
|
+
if not directory.exists():
|
|
422
|
+
logger.warning("Extension directory not found: %s", directory)
|
|
423
|
+
return []
|
|
424
|
+
|
|
425
|
+
loaded: list[str] = []
|
|
426
|
+
self._loaded_dirs.append(directory)
|
|
427
|
+
|
|
428
|
+
for fp in sorted(directory.glob("*.py")):
|
|
429
|
+
if fp.name.startswith("_") or fp.name.startswith("."):
|
|
430
|
+
continue
|
|
431
|
+
try:
|
|
432
|
+
names = self._load_module(fp)
|
|
433
|
+
loaded.extend(names)
|
|
434
|
+
except Exception:
|
|
435
|
+
logger.exception("Failed to load extension module: %s", fp)
|
|
436
|
+
|
|
437
|
+
return loaded
|
|
438
|
+
|
|
439
|
+
def _load_module(self, path: Path) -> list[str]:
|
|
440
|
+
"""从单个 .py 文件加载扩展类。"""
|
|
441
|
+
import importlib.util
|
|
442
|
+
|
|
443
|
+
module_name = path.stem
|
|
444
|
+
spec = importlib.util.spec_from_file_location(
|
|
445
|
+
f"ata_coder_ext_{module_name}", str(path)
|
|
446
|
+
)
|
|
447
|
+
if spec is None or spec.loader is None:
|
|
448
|
+
return []
|
|
449
|
+
|
|
450
|
+
module = importlib.util.module_from_spec(spec)
|
|
451
|
+
sys.modules[spec.name] = module
|
|
452
|
+
spec.loader.exec_module(module)
|
|
453
|
+
|
|
454
|
+
# 查找 Extension 子类
|
|
455
|
+
loaded: list[str] = []
|
|
456
|
+
for attr_name in dir(module):
|
|
457
|
+
obj = getattr(module, attr_name)
|
|
458
|
+
if (
|
|
459
|
+
isinstance(obj, type)
|
|
460
|
+
and issubclass(obj, Extension)
|
|
461
|
+
and obj is not Extension
|
|
462
|
+
and hasattr(obj, "meta")
|
|
463
|
+
):
|
|
464
|
+
try:
|
|
465
|
+
instance = obj()
|
|
466
|
+
if self.register(instance):
|
|
467
|
+
loaded.append(instance.meta.name)
|
|
468
|
+
except Exception:
|
|
469
|
+
logger.exception("Failed to instantiate %s", attr_name)
|
|
470
|
+
|
|
471
|
+
return loaded
|
|
472
|
+
|
|
473
|
+
# ── Queries ─────────────────────────────────────────────────────────────
|
|
474
|
+
|
|
475
|
+
def get_extension(self, name: str) -> Extension | None:
|
|
476
|
+
"""按名称获取扩展(线程安全)。"""
|
|
477
|
+
with self._lock:
|
|
478
|
+
return self._extensions.get(name)
|
|
479
|
+
|
|
480
|
+
def list_extensions(self) -> list[Extension]:
|
|
481
|
+
"""列出所有已注册的扩展(线程安全,返回快照)。"""
|
|
482
|
+
with self._lock:
|
|
483
|
+
return list(self._extensions.values())
|
|
484
|
+
|
|
485
|
+
def list_active(self) -> list[Extension]:
|
|
486
|
+
"""列出所有已激活的扩展(线程安全,返回快照)。"""
|
|
487
|
+
with self._lock:
|
|
488
|
+
return [self._extensions[n] for n in self._active if n in self._extensions]
|
|
489
|
+
|
|
490
|
+
def get_by_type(self, ext_type: str) -> list[Extension]:
|
|
491
|
+
"""按标签获取扩展(线程安全)。"""
|
|
492
|
+
with self._lock:
|
|
493
|
+
return [
|
|
494
|
+
ext for ext in self._extensions.values()
|
|
495
|
+
if ext_type in ext.meta.tags
|
|
496
|
+
]
|
|
497
|
+
|
|
498
|
+
def get_by_tag(self, tag: str) -> list[Extension]:
|
|
499
|
+
"""按标签获取扩展(线程安全)。"""
|
|
500
|
+
return self.get_by_type(tag)
|
|
501
|
+
|
|
502
|
+
# ── System prompt aggregation ──────────────────────────────────────────
|
|
503
|
+
|
|
504
|
+
def aggregate_prompts(self, base_prompt: str = "") -> str:
|
|
505
|
+
"""
|
|
506
|
+
聚合所有激活扩展的提示片段(线程安全)。
|
|
507
|
+
|
|
508
|
+
按 priority 排序后拼接。
|
|
509
|
+
"""
|
|
510
|
+
with self._lock:
|
|
511
|
+
active = sorted(
|
|
512
|
+
[self._extensions[n] for n in self._active if n in self._extensions],
|
|
513
|
+
key=lambda e: e.meta.priority,
|
|
514
|
+
)
|
|
515
|
+
parts = [base_prompt] if base_prompt else []
|
|
516
|
+
for ext in active:
|
|
517
|
+
prompt = ext.get_prompt()
|
|
518
|
+
if prompt:
|
|
519
|
+
parts.append(prompt)
|
|
520
|
+
return "\n\n".join(parts)
|
|
521
|
+
|
|
522
|
+
# ── Tools aggregation ──────────────────────────────────────────────────
|
|
523
|
+
|
|
524
|
+
def aggregate_tools(self) -> list[dict[str, Any]]:
|
|
525
|
+
"""聚合所有激活扩展的工具定义(线程安全)。
|
|
526
|
+
|
|
527
|
+
跳过来自 SkillExtension 的纯字符串(那是工具限制列表,
|
|
528
|
+
不是完整定义),只聚合完整的 OpenAI 格式工具 dict。
|
|
529
|
+
"""
|
|
530
|
+
with self._lock:
|
|
531
|
+
active = [self._extensions[n] for n in self._active if n in self._extensions]
|
|
532
|
+
tools: list[dict[str, Any]] = []
|
|
533
|
+
seen: set[str] = set()
|
|
534
|
+
for ext in active:
|
|
535
|
+
for tool in ext.get_tools():
|
|
536
|
+
# SkillExtension returns str names; real extensions return dicts
|
|
537
|
+
if isinstance(tool, str):
|
|
538
|
+
continue
|
|
539
|
+
name = tool.get("function", {}).get("name", "")
|
|
540
|
+
if name and name not in seen:
|
|
541
|
+
seen.add(name)
|
|
542
|
+
tools.append(tool)
|
|
543
|
+
return tools
|
|
544
|
+
|
|
545
|
+
def get_tool_names(self) -> set[str]:
|
|
546
|
+
"""获取所有激活扩展的工具名称集合(线程安全)。"""
|
|
547
|
+
tools = self.aggregate_tools()
|
|
548
|
+
return {
|
|
549
|
+
t.get("function", {}).get("name", "")
|
|
550
|
+
for t in tools
|
|
551
|
+
if isinstance(t, dict) and t.get("function", {}).get("name")
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
# ── Stats ───────────────────────────────────────────────────────────────
|
|
555
|
+
|
|
556
|
+
def stats(self) -> dict[str, Any]:
|
|
557
|
+
"""返回扩展系统的统计信息(线程安全)。"""
|
|
558
|
+
with self._lock:
|
|
559
|
+
result = {
|
|
560
|
+
"total": len(self._extensions),
|
|
561
|
+
"active": len(self._active),
|
|
562
|
+
"loaded_dirs": len(self._loaded_dirs),
|
|
563
|
+
"by_status": {
|
|
564
|
+
name: "active" if name in self._active else "loaded"
|
|
565
|
+
for name in self._extensions
|
|
566
|
+
},
|
|
567
|
+
}
|
|
568
|
+
with self._ep_lock:
|
|
569
|
+
result["extension_points"] = len(self._extension_points)
|
|
570
|
+
return result
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
574
|
+
# @extension decorator
|
|
575
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
576
|
+
|
|
577
|
+
def extension(
|
|
578
|
+
name: str,
|
|
579
|
+
version: str = "0.1.0",
|
|
580
|
+
description: str = "",
|
|
581
|
+
author: str = "",
|
|
582
|
+
homepage: str = "",
|
|
583
|
+
license: str = "",
|
|
584
|
+
dependencies: list[str] | None = None,
|
|
585
|
+
tags: list[str] | None = None,
|
|
586
|
+
priority: int = 100,
|
|
587
|
+
**kwargs: Any,
|
|
588
|
+
) -> Callable:
|
|
589
|
+
"""
|
|
590
|
+
类装饰器 — 声明一个扩展。
|
|
591
|
+
|
|
592
|
+
用法:
|
|
593
|
+
|
|
594
|
+
@extension(name="my-skill", version="1.0.0",
|
|
595
|
+
tags=["skill"], priority=10)
|
|
596
|
+
class MySkill(Extension):
|
|
597
|
+
def get_prompt(self):
|
|
598
|
+
return "You are an expert..."
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
name: 扩展唯一名称 (kebab-case 推荐)
|
|
602
|
+
version: 语义化版本号
|
|
603
|
+
description: 简要描述
|
|
604
|
+
author: 作者
|
|
605
|
+
homepage: 项目主页
|
|
606
|
+
license: 许可证
|
|
607
|
+
dependencies: 依赖的其他扩展名称列表
|
|
608
|
+
tags: 标签 (如 "skill", "mcp", "template", "tool")
|
|
609
|
+
priority: 优先级 (越小越高, 默认 100)
|
|
610
|
+
"""
|
|
611
|
+
|
|
612
|
+
def decorator(cls: type) -> type:
|
|
613
|
+
meta = ExtensionMeta(
|
|
614
|
+
name=name,
|
|
615
|
+
version=version,
|
|
616
|
+
description=description,
|
|
617
|
+
author=author,
|
|
618
|
+
homepage=homepage,
|
|
619
|
+
license=license,
|
|
620
|
+
dependencies=dependencies or [],
|
|
621
|
+
tags=tags or [],
|
|
622
|
+
priority=priority,
|
|
623
|
+
)
|
|
624
|
+
setattr(cls, "meta", meta)
|
|
625
|
+
|
|
626
|
+
# 如果 kwargs 中有额外元数据, 存入 meta 的属性
|
|
627
|
+
for k, v in kwargs.items():
|
|
628
|
+
if not hasattr(meta, k):
|
|
629
|
+
setattr(meta, k, v)
|
|
630
|
+
|
|
631
|
+
return cls
|
|
632
|
+
|
|
633
|
+
return decorator
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
637
|
+
# Global manager singleton
|
|
638
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
639
|
+
|
|
640
|
+
_extension_manager: ExtensionManager | None = None
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def get_extension_manager() -> ExtensionManager:
|
|
644
|
+
"""获取全局扩展管理器单例。"""
|
|
645
|
+
global _extension_manager
|
|
646
|
+
if _extension_manager is None:
|
|
647
|
+
_extension_manager = ExtensionManager()
|
|
648
|
+
return _extension_manager
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def reset_extension_manager() -> None:
|
|
652
|
+
"""重置全局扩展管理器(主要用于测试)。"""
|
|
653
|
+
global _extension_manager
|
|
654
|
+
_extension_manager = None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Extension package — auto-discovered by ExtensionManager.discover()
|