ata-coder 2.4.8__tar.gz → 2.5.0__tar.gz
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-2.4.8/ata_coder.egg-info → ata_coder-2.5.0}/PKG-INFO +9 -5
- {ata_coder-2.4.8 → ata_coder-2.5.0}/README.md +7 -3
- {ata_coder-2.4.8 → ata_coder-2.5.0}/agent.py +28 -14
- {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_compact.py +2 -2
- {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_controller.py +3 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_routing.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_tools.py +18 -8
- {ata_coder-2.4.8 → ata_coder-2.5.0}/anthropic_client.py +41 -13
- {ata_coder-2.4.8 → ata_coder-2.5.0/ata_coder.egg-info}/PKG-INFO +9 -5
- {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/SOURCES.txt +2 -2
- {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/requires.txt +1 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/change_tracker.py +9 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/config.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/core/queue.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/extension.py +29 -15
- {ata_coder-2.4.8 → ata_coder-2.5.0}/fool_proof.py +7 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/git_workflow.py +3 -2
- {ata_coder-2.4.8 → ata_coder-2.5.0}/llm_client.py +12 -12
- {ata_coder-2.4.8 → ata_coder-2.5.0}/main.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/mcp_client.py +9 -10
- {ata_coder-2.4.8 → ata_coder-2.5.0}/memory.py +40 -32
- {ata_coder-2.4.8 → ata_coder-2.5.0}/permissions.py +6 -4
- {ata_coder-2.4.8 → ata_coder-2.5.0}/privilege.py +20 -10
- {ata_coder-2.4.8 → ata_coder-2.5.0}/project.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompt_template.py +61 -20
- {ata_coder-2.4.8 → ata_coder-2.5.0}/pyproject.toml +11 -5
- {ata_coder-2.4.8 → ata_coder-2.5.0}/repl_theme.py +1 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/repl_ui.py +6 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/safety_guard.py +15 -9
- {ata_coder-2.4.8 → ata_coder-2.5.0}/self_correct.py +5 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/server.py +21 -82
- ata_coder-2.5.0/server_rate_limit.py +97 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/server_session.py +3 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/server_shell.py +8 -6
- {ata_coder-2.4.8 → ata_coder-2.5.0}/session.py +4 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/settings.py +2 -2
- {ata_coder-2.4.8 → ata_coder-2.5.0}/setup_wizard.py +6 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/handler.py +1 -2
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/sub_agent.py +16 -13
- {ata_coder-2.4.8 → ata_coder-2.5.0}/sub_agent_manager.py +12 -4
- {ata_coder-2.4.8 → ata_coder-2.5.0}/terminal.py +1 -2
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_change_tracker.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_memory.py +2 -2
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_safety_guard.py +3 -3
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_tools.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/executor.py +17 -12
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/subagent.py +16 -5
- ata_coder-2.4.8/agent_undo.py +0 -63
- {ata_coder-2.4.8 → ata_coder-2.5.0}/LICENSE +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/MANIFEST.in +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_extension.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_subsystems.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/dependency_links.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/entry_points.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/top_level.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/clawd_integration.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/_core.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/_safety.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/_settings.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/_workflow.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/context_manager.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/core/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/core/events.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/core/state.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/event_queue.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/extensions/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/extensions/hello_skill.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/gui.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/model_registry.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/model_router.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/auto-mode.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/coding-rules.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/execution-guardrails.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/memory-system.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/output-style.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/safety.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/slash-commands.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/sub-agents.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/system-reminders.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/system.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/tool-policy.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/py.typed +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/repl_tracker.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/setup.cfg +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skill_extension.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/architect/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/code-reviewer/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/codecraft/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/debugger/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/doc-writer/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/general-coder/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/README.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/prompts/system.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/requirements.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/resources/constants.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/tests/test_handler.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/security-auditor/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/test-writer/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/README.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/handler.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/manifest.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/prompts/system_prompt.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/prompts/user_prompt_template.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/requirements.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/resources/city_list.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/resources/error_messages.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/tests/test_handler.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/weather_utils.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/system_prompt_builder.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/task_planner.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_agent.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_config.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_event_queue.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_extension.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_fibonacci.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_fool_proof.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_llm_client.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_model_registry.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_permissions.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_privilege.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_prompt_template.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_server.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_skill_handlers.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_sub_agent.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/token_counter.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/definitions.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/result.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/strategy.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/web.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/types.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/utils.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/web/css/style.css +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/web/index.html +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/web/js/app.js +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/web/package-lock.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/web/package.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.5.0}/web/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ata-coder
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: ATA Coder — AI-powered coding assistant
|
|
5
5
|
Author: ATA Coder Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,7 +8,7 @@ Requires-Python: >=3.10
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: click>=8.0
|
|
11
|
-
Requires-Dist: httpx
|
|
11
|
+
Requires-Dist: httpx<1.0,>=0.27.0
|
|
12
12
|
Requires-Dist: colorama>=0.4.6
|
|
13
13
|
Requires-Dist: python-dotenv>=1.0.0
|
|
14
14
|
Requires-Dist: rich>=13.0.0
|
|
@@ -21,13 +21,15 @@ Requires-Dist: pytest-timeout>=2.0; extra == "dev"
|
|
|
21
21
|
Requires-Dist: tiktoken>=0.5.0; extra == "dev"
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
|
-
# ATA Coder v2.4.
|
|
24
|
+
# ATA Coder v2.4.8
|
|
25
25
|
|
|
26
26
|
**AI-powered coding assistant — async, AST-aware, single config file.**
|
|
27
27
|
|
|
28
28
|
[English](#english) | [中文](#中文)
|
|
29
29
|
|
|
30
|
-
> **v2.4.
|
|
30
|
+
> **v2.4.8** — 🤖 **Self-Bootstrapped Audit (Round 12)**: ATA Coder found 10 bugs in its own source — thread races, command injection, silent corruption. All self-found, all self-fixed.
|
|
31
|
+
>
|
|
32
|
+
> > **v2.4.7** — ⚡ **Context Memory Refactor**: O(1) token tracking, ContextManager, section-level prompt caching, pre-tokenized TF-IDF, LRU token cache. ~60% less overhead in the hot loop.
|
|
31
33
|
>
|
|
32
34
|
> > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
|
|
33
35
|
>
|
|
@@ -552,7 +554,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
|
|
|
552
554
|
|
|
553
555
|
## 中文
|
|
554
556
|
|
|
555
|
-
> **v2.4.
|
|
557
|
+
> **v2.4.8** — 🤖 **自举审计(第 12 轮)**: ATA Coder 审计自身源码发现 10 个 bug — 线程竞态、命令注入、静默数据损坏。全部自发现、自修复。
|
|
558
|
+
>
|
|
559
|
+
> > **v2.4.7** — ⚡ **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
|
|
556
560
|
>
|
|
557
561
|
> > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
|
|
558
562
|
>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# ATA Coder v2.4.
|
|
1
|
+
# ATA Coder v2.4.8
|
|
2
2
|
|
|
3
3
|
**AI-powered coding assistant — async, AST-aware, single config file.**
|
|
4
4
|
|
|
5
5
|
[English](#english) | [中文](#中文)
|
|
6
6
|
|
|
7
|
-
> **v2.4.
|
|
7
|
+
> **v2.4.8** — 🤖 **Self-Bootstrapped Audit (Round 12)**: ATA Coder found 10 bugs in its own source — thread races, command injection, silent corruption. All self-found, all self-fixed.
|
|
8
|
+
>
|
|
9
|
+
> > **v2.4.7** — ⚡ **Context Memory Refactor**: O(1) token tracking, ContextManager, section-level prompt caching, pre-tokenized TF-IDF, LRU token cache. ~60% less overhead in the hot loop.
|
|
8
10
|
>
|
|
9
11
|
> > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
|
|
10
12
|
>
|
|
@@ -529,7 +531,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
|
|
|
529
531
|
|
|
530
532
|
## 中文
|
|
531
533
|
|
|
532
|
-
> **v2.4.
|
|
534
|
+
> **v2.4.8** — 🤖 **自举审计(第 12 轮)**: ATA Coder 审计自身源码发现 10 个 bug — 线程竞态、命令注入、静默数据损坏。全部自发现、自修复。
|
|
535
|
+
>
|
|
536
|
+
> > **v2.4.7** — ⚡ **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
|
|
533
537
|
>
|
|
534
538
|
> > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
|
|
535
539
|
>
|
|
@@ -22,7 +22,6 @@ The agent runs a conversation loop:
|
|
|
22
22
|
import asyncio
|
|
23
23
|
import json
|
|
24
24
|
import logging
|
|
25
|
-
import os
|
|
26
25
|
import time
|
|
27
26
|
from typing import Any, Callable
|
|
28
27
|
|
|
@@ -320,14 +319,17 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
320
319
|
raise
|
|
321
320
|
except Exception as e:
|
|
322
321
|
logger.critical("Agent fatal error: %s", e, exc_info=True)
|
|
323
|
-
|
|
324
|
-
|
|
322
|
+
# Sanitize — full details are in the log; never leak exception
|
|
323
|
+
# messages (which may contain paths / keys) to the caller.
|
|
324
|
+
self._emit(ErrorEvent("An unexpected error occurred. Check logs for details."))
|
|
325
|
+
return "An unexpected error occurred. Please check the logs for details."
|
|
325
326
|
finally:
|
|
326
327
|
self._state.phase = AgentPhase.SHUTDOWN
|
|
327
328
|
# Auto-save session after every task (best-effort, never crashes)
|
|
328
329
|
self._auto_save_session()
|
|
329
|
-
#
|
|
330
|
-
|
|
330
|
+
# Deactivate skill only for fresh-context runs; persistent
|
|
331
|
+
# (reset_context=False) sessions keep their skill active.
|
|
332
|
+
if self.skills and reset_context:
|
|
331
333
|
self.skills.deactivate()
|
|
332
334
|
|
|
333
335
|
async def _run_loop(self, task: str, stream: bool = True) -> str:
|
|
@@ -446,7 +448,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
446
448
|
self._append_message(assistant_msg)
|
|
447
449
|
for tc, result in zip(tool_calls, results, strict=True):
|
|
448
450
|
self._warn_if_large_result(result, tc["function"]["name"])
|
|
449
|
-
self._store_tool_result(result, tc["id"])
|
|
451
|
+
self._store_tool_result(result, tc["id"], tool_name=tc["function"]["name"])
|
|
450
452
|
else:
|
|
451
453
|
# Clawd: one PreToolUse for the batch (not per-tool)
|
|
452
454
|
get_clawd().tool_use(
|
|
@@ -471,7 +473,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
471
473
|
assistant_msg["reasoning_content"] = response["reasoning_content"]
|
|
472
474
|
self._append_message(assistant_msg)
|
|
473
475
|
for tc, result in zip(tool_calls, batch_results, strict=True):
|
|
474
|
-
self._store_tool_result(result, tc["id"])
|
|
476
|
+
self._store_tool_result(result, tc["id"], tool_name=tc["function"]["name"])
|
|
475
477
|
|
|
476
478
|
# Clawd: one PostToolUse for the serial batch
|
|
477
479
|
all_ok = all(r.success for r in batch_results)
|
|
@@ -585,6 +587,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
585
587
|
Mirrors the main run() loop: skill tool filtering, token compaction,
|
|
586
588
|
consecutive-failure detection, and circuit breaker.
|
|
587
589
|
"""
|
|
590
|
+
self._state.phase = AgentPhase.THINKING
|
|
588
591
|
self._append_message({"role": "user", "content": message})
|
|
589
592
|
|
|
590
593
|
SAFETY_LIMIT = 999 # circuit breaker
|
|
@@ -627,9 +630,11 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
627
630
|
text = response.get("content", "")
|
|
628
631
|
|
|
629
632
|
if not tool_calls:
|
|
633
|
+
self._state.phase = AgentPhase.COMPLETED
|
|
630
634
|
return text or "Done."
|
|
631
635
|
|
|
632
636
|
# Execute tool calls (serial for safety in follow-up context)
|
|
637
|
+
self._state.phase = AgentPhase.TOOL_EXECUTING
|
|
633
638
|
batch_results: list[ToolResult] = []
|
|
634
639
|
for tc in tool_calls:
|
|
635
640
|
self._state.tool_call_count += 1
|
|
@@ -643,12 +648,17 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
643
648
|
batch_results.append(result)
|
|
644
649
|
self._warn_if_large_result(result, tool_name)
|
|
645
650
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
651
|
+
# Append ONE assistant message with ALL tool_calls (API protocol)
|
|
652
|
+
assistant_msg: dict[str, Any] = {
|
|
653
|
+
"role": "assistant", "content": text or None, "tool_calls": tool_calls,
|
|
654
|
+
}
|
|
655
|
+
if response.get("reasoning_content"):
|
|
656
|
+
assistant_msg["reasoning_content"] = response["reasoning_content"]
|
|
657
|
+
self._append_message(assistant_msg)
|
|
658
|
+
for tc, result in zip(tool_calls, batch_results, strict=True):
|
|
659
|
+
self._store_tool_result(result, tc["id"], tool_name=tc["function"]["name"])
|
|
660
|
+
|
|
661
|
+
self._state.phase = AgentPhase.THINKING # ready for next LLM turn
|
|
652
662
|
|
|
653
663
|
# ── Consecutive failure detection ───────────────────────────
|
|
654
664
|
if batch_results and not any(r.success for r in batch_results):
|
|
@@ -664,6 +674,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
664
674
|
else:
|
|
665
675
|
_consecutive_failures = 0
|
|
666
676
|
|
|
677
|
+
self._state.phase = AgentPhase.COMPLETED
|
|
667
678
|
return text or "Done."
|
|
668
679
|
|
|
669
680
|
# ── Tool filtering → agent_tools.py (ToolExecutionMixin)
|
|
@@ -682,7 +693,10 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
682
693
|
"""
|
|
683
694
|
# Refresh model name on each build (may have changed via /model)
|
|
684
695
|
self._prompt_builder.model = self.config.llm.model
|
|
685
|
-
|
|
696
|
+
prompt = self._prompt_builder.build(TOOL_DEFINITIONS, user_input=user_input)
|
|
697
|
+
# Trigger extension point: on_system_prompt_build
|
|
698
|
+
self._ep_on_system_prompt.trigger(prompt=prompt, task=user_input)
|
|
699
|
+
return prompt
|
|
686
700
|
|
|
687
701
|
# ── Memory commands ───────────────────────────────────────────────────
|
|
688
702
|
|
|
@@ -73,7 +73,7 @@ class CompactionMixin:
|
|
|
73
73
|
|
|
74
74
|
cm.replace_all(truncated)
|
|
75
75
|
self._cached_system_prompt = None # system msg may have shifted
|
|
76
|
-
self._state.messages = cm.messages # sync for backward compat
|
|
76
|
+
self._state.messages = list(cm.messages) # sync for backward compat (copy — avoid shared ref)
|
|
77
77
|
|
|
78
78
|
new_tokens = cm.token_total
|
|
79
79
|
logger.info("Compacted: %d→%d msgs, ~%d→%d tokens (files: %d, tools: %d)",
|
|
@@ -95,7 +95,7 @@ class CompactionMixin:
|
|
|
95
95
|
truncated, result = cm.build_truncated_list()
|
|
96
96
|
cm.replace_all(truncated)
|
|
97
97
|
self._cached_system_prompt = None
|
|
98
|
-
self._state.messages = cm.messages # sync
|
|
98
|
+
self._state.messages = list(cm.messages) # sync (copy — avoid shared ref)
|
|
99
99
|
logger.warning("Force-truncated: %d → %d messages (~%d tokens kept)",
|
|
100
100
|
result.old_count, result.new_count, result.new_tokens)
|
|
101
101
|
|
|
@@ -163,8 +163,10 @@ class AgentController:
|
|
|
163
163
|
raise
|
|
164
164
|
except Exception as e:
|
|
165
165
|
logger.exception("Agent task failed")
|
|
166
|
+
# Sanitize — full details are in the log; never leak exception
|
|
167
|
+
# messages to the event stream.
|
|
166
168
|
await self.event_queue.put(
|
|
167
|
-
ErrorEvent(
|
|
169
|
+
ErrorEvent("An unexpected error occurred. Check logs for details.")
|
|
168
170
|
)
|
|
169
171
|
await self.event_queue.put(
|
|
170
172
|
CompleteEvent(
|
|
@@ -75,7 +75,7 @@ class ModelRoutingMixin:
|
|
|
75
75
|
s = get_settings()
|
|
76
76
|
simple_max = s.get("complexity", "simple_max_chars", default=60)
|
|
77
77
|
complex_min = s.get("complexity", "complex_min_chars", default=500)
|
|
78
|
-
except
|
|
78
|
+
except (ImportError, AttributeError, KeyError):
|
|
79
79
|
simple_max, complex_min = 60, 500 # fallback defaults
|
|
80
80
|
|
|
81
81
|
if task_len <= simple_max:
|
|
@@ -123,7 +123,6 @@ class ToolExecutionMixin:
|
|
|
123
123
|
if diagnosis and diagnosis.retry_strategy == "auto_fix":
|
|
124
124
|
fixed_args = self.self_correct.suggest_fix(tool_name, arguments, diagnosis, error_message=result.error)
|
|
125
125
|
if fixed_args and fixed_args != arguments:
|
|
126
|
-
self._emit(ToolResultEvent(tool_name, result, source="builtin", arguments=arguments))
|
|
127
126
|
logger.info("Auto-correcting: %s (was: %s)", diagnosis.fix_suggestion[:80], result.error[:80])
|
|
128
127
|
# Retry with fixed args THROUGH the full safety pipeline
|
|
129
128
|
self._self_correct_depth += 1
|
|
@@ -157,6 +156,8 @@ class ToolExecutionMixin:
|
|
|
157
156
|
name = tc["function"]["name"]
|
|
158
157
|
if name == "run_shell":
|
|
159
158
|
return False # Shell commands have side effects, serialize
|
|
159
|
+
if name.startswith("mcp__"):
|
|
160
|
+
return False # MCP tools may have arbitrary side effects
|
|
160
161
|
if name in ("write_file", "edit_file"):
|
|
161
162
|
if pre_parsed and i in pre_parsed:
|
|
162
163
|
fp = pre_parsed[i].get("file_path", "")
|
|
@@ -248,12 +249,16 @@ class ToolExecutionMixin:
|
|
|
248
249
|
return json.dumps(mcp_result)
|
|
249
250
|
return str(mcp_result)
|
|
250
251
|
|
|
251
|
-
def _store_tool_result(self, result: ToolResult, tool_call_id: str
|
|
252
|
+
def _store_tool_result(self, result: ToolResult, tool_call_id: str,
|
|
253
|
+
tool_name: str = "") -> None:
|
|
252
254
|
"""Truncate tool output and append to message history.
|
|
253
255
|
|
|
254
256
|
Full output is available during execution, but only a capped version
|
|
255
257
|
is stored for future LLM turns to prevent context bloat.
|
|
256
258
|
"""
|
|
259
|
+
# Trigger extension point: on_tool_result
|
|
260
|
+
if tool_name:
|
|
261
|
+
self._ep_on_tool_result.trigger(tool_name=tool_name, result=result)
|
|
257
262
|
cap = self.config.agent.max_message_output_chars
|
|
258
263
|
content = result.to_message()
|
|
259
264
|
if len(content) > cap:
|
|
@@ -262,11 +267,13 @@ class ToolExecutionMixin:
|
|
|
262
267
|
+ f"\n\n... [truncated {len(content) - cap:,} chars "
|
|
263
268
|
+ f"from {result.output.count(chr(10)) + 1} lines]"
|
|
264
269
|
)
|
|
265
|
-
|
|
270
|
+
tool_msg = {
|
|
266
271
|
"role": "tool",
|
|
267
272
|
"tool_call_id": tool_call_id,
|
|
268
273
|
"content": content,
|
|
269
|
-
}
|
|
274
|
+
}
|
|
275
|
+
self._state.messages.append(tool_msg)
|
|
276
|
+
self._context_manager.append(tool_msg) # keep CM token tracking in sync
|
|
270
277
|
|
|
271
278
|
@staticmethod
|
|
272
279
|
def _warn_if_large_result(result: ToolResult, tool_name: str) -> None:
|
|
@@ -297,10 +304,13 @@ class ToolExecutionMixin:
|
|
|
297
304
|
# Check file cache first (populated by _tool_read_file)
|
|
298
305
|
# Cache format: (mtime, cached_at, content) — 3-tuple with LRU timestamp
|
|
299
306
|
cache_key = str(p.resolve())
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
307
|
+
try:
|
|
308
|
+
if cache_key in self.tools._file_cache:
|
|
309
|
+
cached_mtime, _, cached_content = self.tools._file_cache[cache_key]
|
|
310
|
+
if cached_mtime == p.stat().st_mtime:
|
|
311
|
+
return cached_content
|
|
312
|
+
except (ValueError, KeyError):
|
|
313
|
+
pass # cache format changed — fall through to disk read
|
|
304
314
|
|
|
305
315
|
try:
|
|
306
316
|
# Safety: skip files > 50MB to avoid OOM
|
|
@@ -72,9 +72,9 @@ class AnthropicClient(BaseLLMClient):
|
|
|
72
72
|
"x-api-key": self.config.api_key,
|
|
73
73
|
"Content-Type": "application/json",
|
|
74
74
|
}
|
|
75
|
-
# Native Anthropic requires this header
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
# Native Anthropic requires this header (default: 2023-06-01).
|
|
76
|
+
# Proxies may ignore it; override via ANTHROPIC_VERSION env var.
|
|
77
|
+
self._headers["anthropic-version"] = os.getenv("ANTHROPIC_VERSION", "2023-06-01")
|
|
78
78
|
|
|
79
79
|
self._client = httpx.AsyncClient(
|
|
80
80
|
timeout=httpx.Timeout(300.0, connect=30.0),
|
|
@@ -218,7 +218,6 @@ class AnthropicClient(BaseLLMClient):
|
|
|
218
218
|
body = sanitize_surrogates(body)
|
|
219
219
|
|
|
220
220
|
# Retry loop for streaming (up to 2 retries for 429/5xx)
|
|
221
|
-
last_error = None
|
|
222
221
|
for attempt in range(self._max_retries):
|
|
223
222
|
try:
|
|
224
223
|
resp = await self._client.send(
|
|
@@ -264,6 +263,8 @@ class AnthropicClient(BaseLLMClient):
|
|
|
264
263
|
)
|
|
265
264
|
|
|
266
265
|
tool_buf: dict[int, dict] = {}
|
|
266
|
+
prompt_tokens = 0
|
|
267
|
+
completion_tokens = 0
|
|
267
268
|
async for line in resp.aiter_lines():
|
|
268
269
|
if not line or not line.startswith("data: "):
|
|
269
270
|
continue
|
|
@@ -279,6 +280,17 @@ class AnthropicClient(BaseLLMClient):
|
|
|
279
280
|
delta = event.get("delta", {})
|
|
280
281
|
idx = event.get("index", 0)
|
|
281
282
|
|
|
283
|
+
# Track usage from streaming events (Anthropic protocol)
|
|
284
|
+
if evt_type == "message_start":
|
|
285
|
+
msg = event.get("message", {})
|
|
286
|
+
usage = msg.get("usage", {})
|
|
287
|
+
if usage.get("input_tokens"):
|
|
288
|
+
prompt_tokens = usage["input_tokens"]
|
|
289
|
+
elif evt_type == "message_delta":
|
|
290
|
+
usage = delta.get("usage", {})
|
|
291
|
+
if usage.get("output_tokens"):
|
|
292
|
+
completion_tokens = usage["output_tokens"]
|
|
293
|
+
|
|
282
294
|
if evt_type == "content_block_delta":
|
|
283
295
|
dt = delta.get("type", "")
|
|
284
296
|
if dt == "text_delta":
|
|
@@ -298,6 +310,18 @@ class AnthropicClient(BaseLLMClient):
|
|
|
298
310
|
elif evt_type == "message_stop":
|
|
299
311
|
yield ("finish", "end_turn")
|
|
300
312
|
|
|
313
|
+
# Update token counters with streamed usage data
|
|
314
|
+
if prompt_tokens:
|
|
315
|
+
self._total_prompt_tokens += prompt_tokens
|
|
316
|
+
self.last_exact_prompt_tokens = prompt_tokens
|
|
317
|
+
if completion_tokens:
|
|
318
|
+
self._total_completion_tokens += completion_tokens
|
|
319
|
+
if self._usage_callback and (prompt_tokens or completion_tokens):
|
|
320
|
+
self._usage_callback(
|
|
321
|
+
prompt_tokens=prompt_tokens,
|
|
322
|
+
completion_tokens=completion_tokens,
|
|
323
|
+
)
|
|
324
|
+
|
|
301
325
|
# Yield tool calls
|
|
302
326
|
for idx in sorted(tool_buf.keys()):
|
|
303
327
|
buf = tool_buf[idx]
|
|
@@ -346,7 +370,11 @@ class AnthropicClient(BaseLLMClient):
|
|
|
346
370
|
elif ch in (']', '}'):
|
|
347
371
|
if stack and stack[-1] == ch:
|
|
348
372
|
stack.pop()
|
|
349
|
-
|
|
373
|
+
# Close any unterminated string before balancing brackets
|
|
374
|
+
result = text
|
|
375
|
+
if in_string and not escape:
|
|
376
|
+
result += '"'
|
|
377
|
+
return result + ''.join(reversed(stack))
|
|
350
378
|
|
|
351
379
|
def _apply_thinking(self, body: dict) -> None:
|
|
352
380
|
"""Apply thinking/reasoning_effort — provider-agnostic.
|
|
@@ -432,18 +460,18 @@ class AnthropicClient(BaseLLMClient):
|
|
|
432
460
|
out = usage.get("output_tokens", 0)
|
|
433
461
|
if inp:
|
|
434
462
|
self.last_exact_prompt_tokens = inp
|
|
435
|
-
|
|
436
|
-
#
|
|
463
|
+
if out:
|
|
464
|
+
# Only estimate output tokens when API doesn't provide them;
|
|
465
|
+
# never estimate prompt tokens from output text.
|
|
466
|
+
pass
|
|
467
|
+
elif texts:
|
|
437
468
|
from .token_counter import _cjk_estimate
|
|
438
469
|
out_text = "\n".join(texts)
|
|
439
|
-
|
|
440
|
-
[{"role": "user", "content": out_text}]
|
|
441
|
-
))
|
|
442
|
-
out = max(1, _cjk_estimate(out_text) or out_text and len(out_text) // 4 or 1)
|
|
470
|
+
out = max(1, _cjk_estimate(out_text))
|
|
443
471
|
self._total_prompt_tokens += inp
|
|
444
|
-
self._total_completion_tokens += out
|
|
472
|
+
self._total_completion_tokens += out
|
|
445
473
|
if self._usage_callback:
|
|
446
|
-
self._usage_callback(inp, out
|
|
474
|
+
self._usage_callback(inp, out)
|
|
447
475
|
|
|
448
476
|
return result
|
|
449
477
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ata-coder
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: ATA Coder — AI-powered coding assistant
|
|
5
5
|
Author: ATA Coder Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,7 +8,7 @@ Requires-Python: >=3.10
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: click>=8.0
|
|
11
|
-
Requires-Dist: httpx
|
|
11
|
+
Requires-Dist: httpx<1.0,>=0.27.0
|
|
12
12
|
Requires-Dist: colorama>=0.4.6
|
|
13
13
|
Requires-Dist: python-dotenv>=1.0.0
|
|
14
14
|
Requires-Dist: rich>=13.0.0
|
|
@@ -21,13 +21,15 @@ Requires-Dist: pytest-timeout>=2.0; extra == "dev"
|
|
|
21
21
|
Requires-Dist: tiktoken>=0.5.0; extra == "dev"
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
|
-
# ATA Coder v2.4.
|
|
24
|
+
# ATA Coder v2.4.8
|
|
25
25
|
|
|
26
26
|
**AI-powered coding assistant — async, AST-aware, single config file.**
|
|
27
27
|
|
|
28
28
|
[English](#english) | [中文](#中文)
|
|
29
29
|
|
|
30
|
-
> **v2.4.
|
|
30
|
+
> **v2.4.8** — 🤖 **Self-Bootstrapped Audit (Round 12)**: ATA Coder found 10 bugs in its own source — thread races, command injection, silent corruption. All self-found, all self-fixed.
|
|
31
|
+
>
|
|
32
|
+
> > **v2.4.7** — ⚡ **Context Memory Refactor**: O(1) token tracking, ContextManager, section-level prompt caching, pre-tokenized TF-IDF, LRU token cache. ~60% less overhead in the hot loop.
|
|
31
33
|
>
|
|
32
34
|
> > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
|
|
33
35
|
>
|
|
@@ -552,7 +554,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
|
|
|
552
554
|
|
|
553
555
|
## 中文
|
|
554
556
|
|
|
555
|
-
> **v2.4.
|
|
557
|
+
> **v2.4.8** — 🤖 **自举审计(第 12 轮)**: ATA Coder 审计自身源码发现 10 个 bug — 线程竞态、命令注入、静默数据损坏。全部自发现、自修复。
|
|
558
|
+
>
|
|
559
|
+
> > **v2.4.7** — ⚡ **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
|
|
556
560
|
>
|
|
557
561
|
> > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
|
|
558
562
|
>
|
|
@@ -9,7 +9,6 @@ agent_extension.py
|
|
|
9
9
|
agent_routing.py
|
|
10
10
|
agent_subsystems.py
|
|
11
11
|
agent_tools.py
|
|
12
|
-
agent_undo.py
|
|
13
12
|
anthropic_client.py
|
|
14
13
|
change_tracker.py
|
|
15
14
|
clawd_integration.py
|
|
@@ -38,6 +37,7 @@ repl_ui.py
|
|
|
38
37
|
safety_guard.py
|
|
39
38
|
self_correct.py
|
|
40
39
|
server.py
|
|
40
|
+
server_rate_limit.py
|
|
41
41
|
server_session.py
|
|
42
42
|
server_shell.py
|
|
43
43
|
session.py
|
|
@@ -61,7 +61,6 @@ utils.py
|
|
|
61
61
|
./agent_routing.py
|
|
62
62
|
./agent_subsystems.py
|
|
63
63
|
./agent_tools.py
|
|
64
|
-
./agent_undo.py
|
|
65
64
|
./anthropic_client.py
|
|
66
65
|
./change_tracker.py
|
|
67
66
|
./clawd_integration.py
|
|
@@ -89,6 +88,7 @@ utils.py
|
|
|
89
88
|
./safety_guard.py
|
|
90
89
|
./self_correct.py
|
|
91
90
|
./server.py
|
|
91
|
+
./server_rate_limit.py
|
|
92
92
|
./server_session.py
|
|
93
93
|
./server_shell.py
|
|
94
94
|
./session.py
|
|
@@ -109,6 +109,7 @@ class ChangeTracker:
|
|
|
109
109
|
self._backups: dict[str, str] = {}
|
|
110
110
|
self._dry_run = False
|
|
111
111
|
self._last_active: int = -1
|
|
112
|
+
self.workspace: Path | None = None # set by agent for workspace boundary checks
|
|
112
113
|
|
|
113
114
|
# ── Dry run toggle ───────────────────────────────────────────────────
|
|
114
115
|
|
|
@@ -173,7 +174,7 @@ class ChangeTracker:
|
|
|
173
174
|
if old_content == new_content:
|
|
174
175
|
return None
|
|
175
176
|
|
|
176
|
-
|
|
177
|
+
Path(file_path)
|
|
177
178
|
# Backup before edit (for undo)
|
|
178
179
|
self._backup(file_path)
|
|
179
180
|
|
|
@@ -245,6 +246,13 @@ class ChangeTracker:
|
|
|
245
246
|
def _apply_revert(self, c: FileChange) -> None:
|
|
246
247
|
"""Apply revert for a single change."""
|
|
247
248
|
path = Path(c.file_path)
|
|
249
|
+
# Safety: skip paths outside the workspace (defense in depth)
|
|
250
|
+
if self.workspace is not None:
|
|
251
|
+
try:
|
|
252
|
+
path.resolve().relative_to(self.workspace.resolve())
|
|
253
|
+
except ValueError:
|
|
254
|
+
logger.warning("Skipping undo outside workspace: %s", c.file_path)
|
|
255
|
+
return
|
|
248
256
|
if c.change_type == ChangeType.WRITE:
|
|
249
257
|
if c.old_content is None:
|
|
250
258
|
if path.exists():
|
|
@@ -273,7 +273,7 @@ def _settings_base_url() -> str:
|
|
|
273
273
|
|
|
274
274
|
|
|
275
275
|
def _settings_default_model() -> str:
|
|
276
|
-
return _from_settings("default_model", "deepseek-
|
|
276
|
+
return _from_settings("default_model", "deepseek-v4-pro")
|
|
277
277
|
|
|
278
278
|
|
|
279
279
|
def _settings_max_output_tokens() -> int:
|
|
@@ -42,7 +42,7 @@ class EventQueue:
|
|
|
42
42
|
async def get(self, timeout: Optional[float] = None) -> Optional[Any]:
|
|
43
43
|
"""Get one event, blocking with optional timeout."""
|
|
44
44
|
try:
|
|
45
|
-
if timeout:
|
|
45
|
+
if timeout is not None:
|
|
46
46
|
event = await asyncio.wait_for(self._queue.get(), timeout=timeout)
|
|
47
47
|
else:
|
|
48
48
|
event = await self._queue.get()
|
|
@@ -262,6 +262,7 @@ class ExtensionManager:
|
|
|
262
262
|
def __init__(self):
|
|
263
263
|
self._extensions: dict[str, Extension] = {}
|
|
264
264
|
self._active: set[str] = set()
|
|
265
|
+
self._activating: set[str] = set() # cycle detection stack
|
|
265
266
|
self._loaded_dirs: list[Path] = []
|
|
266
267
|
self._lock = threading.Lock() # protects _extensions, _active, _loaded_dirs
|
|
267
268
|
|
|
@@ -358,25 +359,38 @@ class ExtensionManager:
|
|
|
358
359
|
return False
|
|
359
360
|
if name in self._active:
|
|
360
361
|
return True # already active
|
|
362
|
+
# Cycle detection — detect circular dependencies
|
|
363
|
+
if name in self._activating:
|
|
364
|
+
logger.error(
|
|
365
|
+
"Circular dependency detected: %s is already being activated. "
|
|
366
|
+
"Active path: %s",
|
|
367
|
+
name, ", ".join(self._activating),
|
|
368
|
+
)
|
|
369
|
+
return False
|
|
370
|
+
self._activating.add(name)
|
|
361
371
|
deps = list(ext.meta.dependencies)
|
|
362
372
|
|
|
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
373
|
try:
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
374
|
+
# Activate dependencies (try raw name first, then skill: prefix)
|
|
375
|
+
for dep in deps:
|
|
376
|
+
if dep not in self._active:
|
|
377
|
+
if not self.activate(dep):
|
|
378
|
+
self.activate(f"skill:{dep}")
|
|
375
379
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
+
# on_activate 在锁外调用,避免死锁
|
|
381
|
+
try:
|
|
382
|
+
ext.on_activate()
|
|
383
|
+
except Exception:
|
|
384
|
+
logger.exception("Extension %r on_activate failed", name)
|
|
385
|
+
return False
|
|
386
|
+
|
|
387
|
+
with self._lock:
|
|
388
|
+
self._active.add(name)
|
|
389
|
+
logger.debug("Extension activated: %s", name)
|
|
390
|
+
return True
|
|
391
|
+
finally:
|
|
392
|
+
with self._lock:
|
|
393
|
+
self._activating.discard(name)
|
|
380
394
|
|
|
381
395
|
def deactivate(self, name: str) -> bool:
|
|
382
396
|
"""停用一个扩展(线程安全)。"""
|
|
@@ -126,6 +126,13 @@ class FoolProofEngine:
|
|
|
126
126
|
self._blocks += 1
|
|
127
127
|
return check
|
|
128
128
|
|
|
129
|
+
# 1b. Typing confirmation (safety guard flagged requires_typing)
|
|
130
|
+
if safety.requires_typing:
|
|
131
|
+
check.action = ActionRequired.WARN_CONFIRM
|
|
132
|
+
check.confirm_message = safety.reason or "Type 'YES' to confirm this operation."
|
|
133
|
+
check.requires_typing = True
|
|
134
|
+
return check
|
|
135
|
+
|
|
129
136
|
# 2. Read tools — always safe
|
|
130
137
|
if category == "read":
|
|
131
138
|
check.allowed = True
|
|
@@ -221,10 +221,11 @@ class GitWorkflow:
|
|
|
221
221
|
if code != 0:
|
|
222
222
|
return False, err
|
|
223
223
|
|
|
224
|
-
# Check for secrets in staged changes
|
|
224
|
+
# Check for secrets in staged changes — block the commit
|
|
225
225
|
secret_check = self._check_secrets()
|
|
226
226
|
if secret_check:
|
|
227
|
-
logger.
|
|
227
|
+
logger.error("Potential secrets in commit — blocked: %s", secret_check)
|
|
228
|
+
return False, f"Secret detection blocked commit:\n{secret_check}\n\nUse --force to override."
|
|
228
229
|
|
|
229
230
|
# Generate commit message if not provided
|
|
230
231
|
if not message:
|