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.
Files changed (118) hide show
  1. ata_coder/__init__.py +1 -0
  2. ata_coder/agent.py +874 -0
  3. ata_coder/agent_compact.py +190 -0
  4. ata_coder/agent_controller.py +218 -0
  5. ata_coder/agent_extension.py +69 -0
  6. ata_coder/agent_routing.py +105 -0
  7. ata_coder/agent_subsystems.py +72 -0
  8. ata_coder/agent_tools.py +318 -0
  9. ata_coder/agent_undo.py +63 -0
  10. ata_coder/anthropic_client.py +465 -0
  11. ata_coder/change_tracker.py +368 -0
  12. ata_coder/clawd_integration.py +574 -0
  13. ata_coder/commands/__init__.py +128 -0
  14. ata_coder/commands/_core.py +184 -0
  15. ata_coder/commands/_safety.py +95 -0
  16. ata_coder/commands/_settings.py +241 -0
  17. ata_coder/commands/_workflow.py +451 -0
  18. ata_coder/commands.py +974 -0
  19. ata_coder/config.py +257 -0
  20. ata_coder/core/__init__.py +35 -0
  21. ata_coder/core/events.py +73 -0
  22. ata_coder/core/queue.py +85 -0
  23. ata_coder/core/state.py +17 -0
  24. ata_coder/event_queue.py +5 -0
  25. ata_coder/extension.py +654 -0
  26. ata_coder/extensions/__init__.py +1 -0
  27. ata_coder/extensions/hello_skill.py +47 -0
  28. ata_coder/fool_proof.py +295 -0
  29. ata_coder/git_workflow.py +371 -0
  30. ata_coder/gui.py +511 -0
  31. ata_coder/llm_client.py +543 -0
  32. ata_coder/main.py +814 -0
  33. ata_coder/mcp_client.py +1095 -0
  34. ata_coder/memory.py +539 -0
  35. ata_coder/model_registry.py +134 -0
  36. ata_coder/model_router.py +105 -0
  37. ata_coder/permissions.py +274 -0
  38. ata_coder/privilege.py +464 -0
  39. ata_coder/project.py +273 -0
  40. ata_coder/prompt_template.py +423 -0
  41. ata_coder/prompts/auto-mode.md +7 -0
  42. ata_coder/prompts/coding-rules.md +40 -0
  43. ata_coder/prompts/execution-guardrails.md +14 -0
  44. ata_coder/prompts/memory-system.md +24 -0
  45. ata_coder/prompts/output-style.md +23 -0
  46. ata_coder/prompts/safety.md +17 -0
  47. ata_coder/prompts/slash-commands.md +24 -0
  48. ata_coder/prompts/sub-agents.md +38 -0
  49. ata_coder/prompts/system-reminders.md +17 -0
  50. ata_coder/prompts/system.md +105 -0
  51. ata_coder/prompts/tool-policy.md +46 -0
  52. ata_coder/repl_theme.py +99 -0
  53. ata_coder/repl_tracker.py +89 -0
  54. ata_coder/repl_ui.py +1214 -0
  55. ata_coder/safety_guard.py +434 -0
  56. ata_coder/self_correct.py +346 -0
  57. ata_coder/server.py +882 -0
  58. ata_coder/server_session.py +159 -0
  59. ata_coder/server_shell.py +129 -0
  60. ata_coder/session.py +431 -0
  61. ata_coder/settings.py +439 -0
  62. ata_coder/setup_wizard.py +136 -0
  63. ata_coder/skill_extension.py +92 -0
  64. ata_coder/skills/architect/SKILL.md +42 -0
  65. ata_coder/skills/code-reviewer/SKILL.md +37 -0
  66. ata_coder/skills/codecraft/SKILL.md +452 -0
  67. ata_coder/skills/debugger/SKILL.md +45 -0
  68. ata_coder/skills/doc-writer/SKILL.md +36 -0
  69. ata_coder/skills/general-coder/SKILL.md +76 -0
  70. ata_coder/skills/math-calculator/README.md +40 -0
  71. ata_coder/skills/math-calculator/SKILL.md +59 -0
  72. ata_coder/skills/math-calculator/handler.py +103 -0
  73. ata_coder/skills/math-calculator/prompts/system.md +8 -0
  74. ata_coder/skills/math-calculator/requirements.txt +2 -0
  75. ata_coder/skills/math-calculator/resources/constants.json +8 -0
  76. ata_coder/skills/math-calculator/tests/test_handler.py +53 -0
  77. ata_coder/skills/security-auditor/SKILL.md +40 -0
  78. ata_coder/skills/test-writer/SKILL.md +36 -0
  79. ata_coder/skills/weather-skill/README.md +45 -0
  80. ata_coder/skills/weather-skill/handler.py +76 -0
  81. ata_coder/skills/weather-skill/manifest.json +48 -0
  82. ata_coder/skills/weather-skill/prompts/system_prompt.txt +9 -0
  83. ata_coder/skills/weather-skill/prompts/user_prompt_template.txt +3 -0
  84. ata_coder/skills/weather-skill/requirements.txt +1 -0
  85. ata_coder/skills/weather-skill/resources/city_list.json +17 -0
  86. ata_coder/skills/weather-skill/resources/error_messages.json +7 -0
  87. ata_coder/skills/weather-skill/tests/test_handler.py +28 -0
  88. ata_coder/skills/weather-skill/weather_utils.py +50 -0
  89. ata_coder/skills.py +1014 -0
  90. ata_coder/sub_agent.py +273 -0
  91. ata_coder/sub_agent_manager.py +203 -0
  92. ata_coder/system_prompt_builder.py +146 -0
  93. ata_coder/task_planner.py +391 -0
  94. ata_coder/terminal.py +318 -0
  95. ata_coder/test_runner.py +219 -0
  96. ata_coder/thread_supervisor.py +195 -0
  97. ata_coder/tool_defs.py +335 -0
  98. ata_coder/tools/__init__.py +11 -0
  99. ata_coder/tools/definitions.py +335 -0
  100. ata_coder/tools/executor.py +1036 -0
  101. ata_coder/tools/result.py +26 -0
  102. ata_coder/tools/subagent.py +332 -0
  103. ata_coder/tools/web.py +361 -0
  104. ata_coder/tools.py +1576 -0
  105. ata_coder/types.py +92 -0
  106. ata_coder/utils.py +113 -0
  107. ata_coder/web/css/style.css +180 -0
  108. ata_coder/web/index.html +84 -0
  109. ata_coder/web/js/app.js +489 -0
  110. ata_coder/web/package-lock.json +25 -0
  111. ata_coder/web/package.json +10 -0
  112. ata_coder/web/tsconfig.json +13 -0
  113. ata_coder-2.4.2.dist-info/METADATA +799 -0
  114. ata_coder-2.4.2.dist-info/RECORD +118 -0
  115. ata_coder-2.4.2.dist-info/WHEEL +5 -0
  116. ata_coder-2.4.2.dist-info/entry_points.txt +2 -0
  117. ata_coder-2.4.2.dist-info/licenses/LICENSE +21 -0
  118. 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()