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.
Files changed (142) hide show
  1. {ata_coder-2.4.8/ata_coder.egg-info → ata_coder-2.5.0}/PKG-INFO +9 -5
  2. {ata_coder-2.4.8 → ata_coder-2.5.0}/README.md +7 -3
  3. {ata_coder-2.4.8 → ata_coder-2.5.0}/agent.py +28 -14
  4. {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_compact.py +2 -2
  5. {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_controller.py +3 -1
  6. {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_routing.py +1 -1
  7. {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_tools.py +18 -8
  8. {ata_coder-2.4.8 → ata_coder-2.5.0}/anthropic_client.py +41 -13
  9. {ata_coder-2.4.8 → ata_coder-2.5.0/ata_coder.egg-info}/PKG-INFO +9 -5
  10. {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/SOURCES.txt +2 -2
  11. {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/requires.txt +1 -1
  12. {ata_coder-2.4.8 → ata_coder-2.5.0}/change_tracker.py +9 -1
  13. {ata_coder-2.4.8 → ata_coder-2.5.0}/config.py +1 -1
  14. {ata_coder-2.4.8 → ata_coder-2.5.0}/core/queue.py +1 -1
  15. {ata_coder-2.4.8 → ata_coder-2.5.0}/extension.py +29 -15
  16. {ata_coder-2.4.8 → ata_coder-2.5.0}/fool_proof.py +7 -0
  17. {ata_coder-2.4.8 → ata_coder-2.5.0}/git_workflow.py +3 -2
  18. {ata_coder-2.4.8 → ata_coder-2.5.0}/llm_client.py +12 -12
  19. {ata_coder-2.4.8 → ata_coder-2.5.0}/main.py +1 -1
  20. {ata_coder-2.4.8 → ata_coder-2.5.0}/mcp_client.py +9 -10
  21. {ata_coder-2.4.8 → ata_coder-2.5.0}/memory.py +40 -32
  22. {ata_coder-2.4.8 → ata_coder-2.5.0}/permissions.py +6 -4
  23. {ata_coder-2.4.8 → ata_coder-2.5.0}/privilege.py +20 -10
  24. {ata_coder-2.4.8 → ata_coder-2.5.0}/project.py +1 -1
  25. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompt_template.py +61 -20
  26. {ata_coder-2.4.8 → ata_coder-2.5.0}/pyproject.toml +11 -5
  27. {ata_coder-2.4.8 → ata_coder-2.5.0}/repl_theme.py +1 -0
  28. {ata_coder-2.4.8 → ata_coder-2.5.0}/repl_ui.py +6 -0
  29. {ata_coder-2.4.8 → ata_coder-2.5.0}/safety_guard.py +15 -9
  30. {ata_coder-2.4.8 → ata_coder-2.5.0}/self_correct.py +5 -1
  31. {ata_coder-2.4.8 → ata_coder-2.5.0}/server.py +21 -82
  32. ata_coder-2.5.0/server_rate_limit.py +97 -0
  33. {ata_coder-2.4.8 → ata_coder-2.5.0}/server_session.py +3 -0
  34. {ata_coder-2.4.8 → ata_coder-2.5.0}/server_shell.py +8 -6
  35. {ata_coder-2.4.8 → ata_coder-2.5.0}/session.py +4 -0
  36. {ata_coder-2.4.8 → ata_coder-2.5.0}/settings.py +2 -2
  37. {ata_coder-2.4.8 → ata_coder-2.5.0}/setup_wizard.py +6 -1
  38. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/handler.py +1 -2
  39. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills.py +1 -1
  40. {ata_coder-2.4.8 → ata_coder-2.5.0}/sub_agent.py +16 -13
  41. {ata_coder-2.4.8 → ata_coder-2.5.0}/sub_agent_manager.py +12 -4
  42. {ata_coder-2.4.8 → ata_coder-2.5.0}/terminal.py +1 -2
  43. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_change_tracker.py +1 -1
  44. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_memory.py +2 -2
  45. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_safety_guard.py +3 -3
  46. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_tools.py +1 -1
  47. {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/executor.py +17 -12
  48. {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/subagent.py +16 -5
  49. ata_coder-2.4.8/agent_undo.py +0 -63
  50. {ata_coder-2.4.8 → ata_coder-2.5.0}/LICENSE +0 -0
  51. {ata_coder-2.4.8 → ata_coder-2.5.0}/MANIFEST.in +0 -0
  52. {ata_coder-2.4.8 → ata_coder-2.5.0}/__init__.py +0 -0
  53. {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_extension.py +0 -0
  54. {ata_coder-2.4.8 → ata_coder-2.5.0}/agent_subsystems.py +0 -0
  55. {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/dependency_links.txt +0 -0
  56. {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/entry_points.txt +0 -0
  57. {ata_coder-2.4.8 → ata_coder-2.5.0}/ata_coder.egg-info/top_level.txt +0 -0
  58. {ata_coder-2.4.8 → ata_coder-2.5.0}/clawd_integration.py +0 -0
  59. {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/__init__.py +0 -0
  60. {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/_core.py +0 -0
  61. {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/_safety.py +0 -0
  62. {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/_settings.py +0 -0
  63. {ata_coder-2.4.8 → ata_coder-2.5.0}/commands/_workflow.py +0 -0
  64. {ata_coder-2.4.8 → ata_coder-2.5.0}/context_manager.py +0 -0
  65. {ata_coder-2.4.8 → ata_coder-2.5.0}/core/__init__.py +0 -0
  66. {ata_coder-2.4.8 → ata_coder-2.5.0}/core/events.py +0 -0
  67. {ata_coder-2.4.8 → ata_coder-2.5.0}/core/state.py +0 -0
  68. {ata_coder-2.4.8 → ata_coder-2.5.0}/event_queue.py +0 -0
  69. {ata_coder-2.4.8 → ata_coder-2.5.0}/extensions/__init__.py +0 -0
  70. {ata_coder-2.4.8 → ata_coder-2.5.0}/extensions/hello_skill.py +0 -0
  71. {ata_coder-2.4.8 → ata_coder-2.5.0}/gui.py +0 -0
  72. {ata_coder-2.4.8 → ata_coder-2.5.0}/model_registry.py +0 -0
  73. {ata_coder-2.4.8 → ata_coder-2.5.0}/model_router.py +0 -0
  74. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/auto-mode.md +0 -0
  75. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/coding-rules.md +0 -0
  76. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/execution-guardrails.md +0 -0
  77. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/memory-system.md +0 -0
  78. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/output-style.md +0 -0
  79. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/safety.md +0 -0
  80. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/slash-commands.md +0 -0
  81. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/sub-agents.md +0 -0
  82. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/system-reminders.md +0 -0
  83. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/system.md +0 -0
  84. {ata_coder-2.4.8 → ata_coder-2.5.0}/prompts/tool-policy.md +0 -0
  85. {ata_coder-2.4.8 → ata_coder-2.5.0}/py.typed +0 -0
  86. {ata_coder-2.4.8 → ata_coder-2.5.0}/repl_tracker.py +0 -0
  87. {ata_coder-2.4.8 → ata_coder-2.5.0}/setup.cfg +0 -0
  88. {ata_coder-2.4.8 → ata_coder-2.5.0}/skill_extension.py +0 -0
  89. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/architect/SKILL.md +0 -0
  90. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/code-reviewer/SKILL.md +0 -0
  91. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/codecraft/SKILL.md +0 -0
  92. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/debugger/SKILL.md +0 -0
  93. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/doc-writer/SKILL.md +0 -0
  94. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/general-coder/SKILL.md +0 -0
  95. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/README.md +0 -0
  96. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/SKILL.md +0 -0
  97. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/prompts/system.md +0 -0
  98. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/requirements.txt +0 -0
  99. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/resources/constants.json +0 -0
  100. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/math-calculator/tests/test_handler.py +0 -0
  101. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/security-auditor/SKILL.md +0 -0
  102. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/test-writer/SKILL.md +0 -0
  103. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/README.md +0 -0
  104. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/handler.py +0 -0
  105. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/manifest.json +0 -0
  106. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/prompts/system_prompt.txt +0 -0
  107. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/prompts/user_prompt_template.txt +0 -0
  108. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/requirements.txt +0 -0
  109. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/resources/city_list.json +0 -0
  110. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/resources/error_messages.json +0 -0
  111. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/tests/test_handler.py +0 -0
  112. {ata_coder-2.4.8 → ata_coder-2.5.0}/skills/weather-skill/weather_utils.py +0 -0
  113. {ata_coder-2.4.8 → ata_coder-2.5.0}/system_prompt_builder.py +0 -0
  114. {ata_coder-2.4.8 → ata_coder-2.5.0}/task_planner.py +0 -0
  115. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_agent.py +0 -0
  116. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_config.py +0 -0
  117. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_event_queue.py +0 -0
  118. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_extension.py +0 -0
  119. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_fibonacci.py +0 -0
  120. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_fool_proof.py +0 -0
  121. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_llm_client.py +0 -0
  122. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_model_registry.py +0 -0
  123. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_permissions.py +0 -0
  124. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_privilege.py +0 -0
  125. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_prompt_template.py +0 -0
  126. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_server.py +0 -0
  127. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_skill_handlers.py +0 -0
  128. {ata_coder-2.4.8 → ata_coder-2.5.0}/tests/test_sub_agent.py +0 -0
  129. {ata_coder-2.4.8 → ata_coder-2.5.0}/token_counter.py +0 -0
  130. {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/__init__.py +0 -0
  131. {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/definitions.py +0 -0
  132. {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/result.py +0 -0
  133. {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/strategy.py +0 -0
  134. {ata_coder-2.4.8 → ata_coder-2.5.0}/tools/web.py +0 -0
  135. {ata_coder-2.4.8 → ata_coder-2.5.0}/types.py +0 -0
  136. {ata_coder-2.4.8 → ata_coder-2.5.0}/utils.py +0 -0
  137. {ata_coder-2.4.8 → ata_coder-2.5.0}/web/css/style.css +0 -0
  138. {ata_coder-2.4.8 → ata_coder-2.5.0}/web/index.html +0 -0
  139. {ata_coder-2.4.8 → ata_coder-2.5.0}/web/js/app.js +0 -0
  140. {ata_coder-2.4.8 → ata_coder-2.5.0}/web/package-lock.json +0 -0
  141. {ata_coder-2.4.8 → ata_coder-2.5.0}/web/package.json +0 -0
  142. {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.4.8
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>=0.27.0
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.7
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.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.
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.7** — **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
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.7
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** — **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.
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.7** — **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
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
- self._emit(ErrorEvent(f"Fatal error: {e}"))
324
- return f"Error: {e}"
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
- # Always deactivate skill after task prevents state leak
330
- if self.skills:
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
- self._append_message({
647
- "role": "assistant",
648
- "content": text or None,
649
- "tool_calls": [tc],
650
- })
651
- self._store_tool_result(result, tc["id"])
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
- return self._prompt_builder.build(TOOL_DEFINITIONS, user_input=user_input)
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(f"Agent error: {e}")
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 Exception:
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) -> None:
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
- self._state.messages.append({
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
- if cache_key in self.tools._file_cache:
301
- cached_mtime, _, cached_content = self.tools._file_cache[cache_key]
302
- if cached_mtime == p.stat().st_mtime:
303
- return cached_content
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; proxies may ignore it
76
- if os.getenv("ANTHROPIC_VERSION"):
77
- self._headers["anthropic-version"] = os.getenv("ANTHROPIC_VERSION")
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
- return text + ''.join(reversed(stack))
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
- else:
436
- # Fallback: use estimated counts from the response text
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
- inp = max(1, self.count_tokens_approx(
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 or 1
472
+ self._total_completion_tokens += out
445
473
  if self._usage_callback:
446
- self._usage_callback(inp, out or 1)
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.4.8
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>=0.27.0
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.7
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.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.
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.7** — **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
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
@@ -1,5 +1,5 @@
1
1
  click>=8.0
2
- httpx>=0.27.0
2
+ httpx<1.0,>=0.27.0
3
3
  colorama>=0.4.6
4
4
  python-dotenv>=1.0.0
5
5
  rich>=13.0.0
@@ -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
- path = Path(file_path)
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-chat")
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
- ext.on_activate()
372
- except Exception:
373
- logger.exception("Extension %r on_activate failed", name)
374
- return False
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
- with self._lock:
377
- self._active.add(name)
378
- logger.debug("Extension activated: %s", name)
379
- return True
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.warning("Potential secrets in commit: %s", secret_check)
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: