ata-coder 2.4.6__tar.gz → 2.4.8__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.6/ata_coder.egg-info → ata_coder-2.4.8}/PKG-INFO +8 -4
- {ata_coder-2.4.6 → ata_coder-2.4.8}/README.md +7 -3
- {ata_coder-2.4.6 → ata_coder-2.4.8}/agent.py +43 -30
- ata_coder-2.4.8/agent_compact.py +159 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/agent_tools.py +2 -1
- {ata_coder-2.4.6 → ata_coder-2.4.8/ata_coder.egg-info}/PKG-INFO +8 -4
- {ata_coder-2.4.6 → ata_coder-2.4.8}/ata_coder.egg-info/SOURCES.txt +2 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/change_tracker.py +1 -1
- {ata_coder-2.4.6 → ata_coder-2.4.8}/config.py +14 -3
- ata_coder-2.4.8/context_manager.py +230 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/main.py +3 -3
- {ata_coder-2.4.6 → ata_coder-2.4.8}/memory.py +76 -20
- {ata_coder-2.4.6 → ata_coder-2.4.8}/privilege.py +10 -6
- {ata_coder-2.4.6 → ata_coder-2.4.8}/pyproject.toml +1 -1
- {ata_coder-2.4.6 → ata_coder-2.4.8}/safety_guard.py +1 -1
- {ata_coder-2.4.6 → ata_coder-2.4.8}/server.py +6 -2
- {ata_coder-2.4.6 → ata_coder-2.4.8}/server_session.py +1 -1
- {ata_coder-2.4.6 → ata_coder-2.4.8}/setup_wizard.py +1 -1
- {ata_coder-2.4.6 → ata_coder-2.4.8}/system_prompt_builder.py +94 -34
- {ata_coder-2.4.6 → ata_coder-2.4.8}/token_counter.py +79 -43
- ata_coder-2.4.6/agent_compact.py +0 -195
- {ata_coder-2.4.6 → ata_coder-2.4.8}/LICENSE +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/MANIFEST.in +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/agent_controller.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/agent_extension.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/agent_routing.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/agent_subsystems.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/agent_undo.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/anthropic_client.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/ata_coder.egg-info/dependency_links.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/ata_coder.egg-info/entry_points.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/ata_coder.egg-info/requires.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/ata_coder.egg-info/top_level.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/clawd_integration.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/commands/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/commands/_core.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/commands/_safety.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/commands/_settings.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/commands/_workflow.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/core/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/core/events.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/core/queue.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/core/state.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/event_queue.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/extension.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/extensions/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/extensions/hello_skill.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/fool_proof.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/git_workflow.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/gui.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/llm_client.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/mcp_client.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/model_registry.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/model_router.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/permissions.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/project.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompt_template.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/auto-mode.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/coding-rules.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/execution-guardrails.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/memory-system.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/output-style.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/safety.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/slash-commands.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/sub-agents.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/system-reminders.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/system.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/prompts/tool-policy.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/py.typed +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/repl_theme.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/repl_tracker.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/repl_ui.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/self_correct.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/server_shell.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/session.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/settings.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/setup.cfg +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skill_extension.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/architect/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/code-reviewer/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/codecraft/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/debugger/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/doc-writer/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/general-coder/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/math-calculator/README.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/math-calculator/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/math-calculator/handler.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/math-calculator/prompts/system.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/math-calculator/requirements.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/math-calculator/resources/constants.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/math-calculator/tests/test_handler.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/security-auditor/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/test-writer/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/README.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/handler.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/manifest.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/prompts/system_prompt.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/prompts/user_prompt_template.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/requirements.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/resources/city_list.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/resources/error_messages.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/tests/test_handler.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills/weather-skill/weather_utils.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/skills.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/sub_agent.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/sub_agent_manager.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/task_planner.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/terminal.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_agent.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_change_tracker.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_config.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_event_queue.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_extension.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_fibonacci.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_fool_proof.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_llm_client.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_memory.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_model_registry.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_permissions.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_privilege.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_prompt_template.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_safety_guard.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_server.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_skill_handlers.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_sub_agent.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tests/test_tools.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tools/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tools/definitions.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tools/executor.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tools/result.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tools/strategy.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tools/subagent.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/tools/web.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/types.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/utils.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/web/css/style.css +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/web/index.html +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/web/js/app.js +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/web/package-lock.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/web/package.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.8}/web/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ata-coder
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.8
|
|
4
4
|
Summary: ATA Coder — AI-powered coding assistant
|
|
5
5
|
Author: ATA Coder Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -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.7
|
|
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.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
|
+
>
|
|
32
|
+
> > **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.
|
|
31
33
|
>
|
|
32
34
|
> > **v2.4.5** — 🤖 **Self-Bootstrapped Audit**: ATA Coder found 19 bugs in its own source code and fixed them all — thread safety, SSRF IPv6, rate limiter leak, auth hardening, DRY refactoring, CI coverage. 12 files changed.
|
|
33
35
|
>
|
|
@@ -550,7 +552,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
|
|
|
550
552
|
|
|
551
553
|
## 中文
|
|
552
554
|
|
|
553
|
-
> **v2.4.
|
|
555
|
+
> **v2.4.7** — ⚡ **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
|
|
556
|
+
>
|
|
557
|
+
> > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
|
|
554
558
|
>
|
|
555
559
|
> > **v2.4.5** — 🤖 **自举审计**: ATA Coder 审计自身源码发现 19 个 bug 并全部修复 — 线程安全、SSRF IPv6、速率限制器泄漏、认证加固、DRY 重构、CI 覆盖。12 个文件变更。
|
|
556
560
|
>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# ATA Coder v2.4.
|
|
1
|
+
# ATA Coder v2.4.7
|
|
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.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
|
+
>
|
|
9
|
+
> > **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.
|
|
8
10
|
>
|
|
9
11
|
> > **v2.4.5** — 🤖 **Self-Bootstrapped Audit**: ATA Coder found 19 bugs in its own source code and fixed them all — thread safety, SSRF IPv6, rate limiter leak, auth hardening, DRY refactoring, CI coverage. 12 files changed.
|
|
10
12
|
>
|
|
@@ -527,7 +529,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
|
|
|
527
529
|
|
|
528
530
|
## 中文
|
|
529
531
|
|
|
530
|
-
> **v2.4.
|
|
532
|
+
> **v2.4.7** — ⚡ **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
|
|
533
|
+
>
|
|
534
|
+
> > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
|
|
531
535
|
>
|
|
532
536
|
> > **v2.4.5** — 🤖 **自举审计**: ATA Coder 审计自身源码发现 19 个 bug 并全部修复 — 线程安全、SSRF IPv6、速率限制器泄漏、认证加固、DRY 重构、CI 覆盖。12 个文件变更。
|
|
533
537
|
>
|
|
@@ -44,6 +44,7 @@ from .agent_compact import CompactionMixin
|
|
|
44
44
|
from .agent_tools import ToolExecutionMixin
|
|
45
45
|
from .agent_routing import ModelRoutingMixin
|
|
46
46
|
from .agent_extension import ExtensionMixin
|
|
47
|
+
from .context_manager import ContextManager
|
|
47
48
|
|
|
48
49
|
# ── Event types & Agent state ──────────────────────────────────────────
|
|
49
50
|
from .core import ( # noqa: F401 — re-exported for external use
|
|
@@ -141,6 +142,10 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
141
142
|
self.self_correct = SelfCorrectionEngine(max_retries=1)
|
|
142
143
|
self.git = GitWorkflow(self.tools.workspace)
|
|
143
144
|
|
|
145
|
+
# Per-instance self-correction depth (was a class variable shared across
|
|
146
|
+
# all agent instances — dangerous under ThreadingHTTPServer in server mode).
|
|
147
|
+
self._self_correct_depth: int = 0
|
|
148
|
+
|
|
144
149
|
self._state = AgentState()
|
|
145
150
|
self._on_event: Callable[[AgentEvent], None] | None = None
|
|
146
151
|
self._current_session_id: str = ""
|
|
@@ -148,6 +153,10 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
148
153
|
self._cached_system_prompt: str | None = None # invalidated on new build / compact
|
|
149
154
|
self._cached_allowed_tools: set[str] | None = None # invalidated on skill change
|
|
150
155
|
|
|
156
|
+
# ── Context manager (O(1) token tracking, segment-split, adaptive compact) ──
|
|
157
|
+
self._context_manager = ContextManager(self.config.agent)
|
|
158
|
+
self._summary_llm = None # lazily created summarisation client
|
|
159
|
+
|
|
151
160
|
# Build the combined tool list
|
|
152
161
|
self._all_tools = list(TOOL_DEFINITIONS)
|
|
153
162
|
if self.mcp:
|
|
@@ -227,7 +236,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
227
236
|
if not reset_context and self._state.messages:
|
|
228
237
|
# Append new user message to existing conversation; keep system
|
|
229
238
|
# prompt and all prior messages intact.
|
|
230
|
-
self.
|
|
239
|
+
self._append_message({"role": "user", "content": task})
|
|
231
240
|
# Rebuild system prompt for updated memory context but don't
|
|
232
241
|
# replace the original system message (memory/git context may
|
|
233
242
|
# have changed, but conversation integrity is paramount).
|
|
@@ -291,10 +300,12 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
291
300
|
self._cached_system_prompt = system_prompt # pre-seed cache
|
|
292
301
|
self._cached_allowed_tools = None # invalidate on new run
|
|
293
302
|
|
|
294
|
-
|
|
303
|
+
initial_msgs = [
|
|
295
304
|
{"role": "system", "content": system_prompt},
|
|
296
305
|
{"role": "user", "content": task},
|
|
297
306
|
]
|
|
307
|
+
self._state.messages = initial_msgs
|
|
308
|
+
self._context_manager.replace_all(initial_msgs)
|
|
298
309
|
|
|
299
310
|
logger.info("Agent run: skill=%s, model=%s, session=%s, task=%.100s",
|
|
300
311
|
self.skills.active_skill.name if self.skills and self.skills.active_skill else "default",
|
|
@@ -352,24 +363,19 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
352
363
|
# Clawd: model is generating, show thinking animation
|
|
353
364
|
get_clawd().thinking()
|
|
354
365
|
|
|
355
|
-
# Auto-compact when approaching the effective context limit.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
# theoretical max_context_tokens.
|
|
360
|
-
est_tokens = self.get_token_estimate()
|
|
361
|
-
max_tokens = self.config.agent.max_context_tokens
|
|
362
|
-
effective = self.config.agent.effective_context_tokens
|
|
363
|
-
if est_tokens > effective:
|
|
366
|
+
# Auto-compact when approaching the effective context limit (O(1) check).
|
|
367
|
+
if self._context_manager.should_compact():
|
|
368
|
+
est = self._context_manager.token_total
|
|
369
|
+
max_t = self.config.agent.max_context_tokens
|
|
364
370
|
logger.warning("Token budget: %d/%d effective (%.0f%% of %d max), auto-compacting",
|
|
365
|
-
|
|
371
|
+
est, self.config.agent.effective_context_tokens,
|
|
372
|
+
est / max(max_t, 1) * 100, max_t)
|
|
366
373
|
await self.compact()
|
|
367
|
-
# Re-estimate AFTER compaction — the message list has changed
|
|
368
|
-
est_tokens = self.get_token_estimate()
|
|
369
374
|
# Hard ceiling: if compaction didn't help enough, force-truncate
|
|
370
|
-
if
|
|
375
|
+
if self._context_manager.needs_force_truncate():
|
|
371
376
|
logger.critical("Hard token ceiling: %d > 95%% of %d max. Force-truncating.",
|
|
372
|
-
|
|
377
|
+
self._context_manager.token_total,
|
|
378
|
+
self.config.agent.max_context_tokens)
|
|
373
379
|
self._force_truncate()
|
|
374
380
|
|
|
375
381
|
# Get allowed tools from multi-skill intersection
|
|
@@ -437,7 +443,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
437
443
|
}
|
|
438
444
|
if response.get("reasoning_content"):
|
|
439
445
|
assistant_msg["reasoning_content"] = response["reasoning_content"]
|
|
440
|
-
self.
|
|
446
|
+
self._append_message(assistant_msg)
|
|
441
447
|
for tc, result in zip(tool_calls, results, strict=True):
|
|
442
448
|
self._warn_if_large_result(result, tc["function"]["name"])
|
|
443
449
|
self._store_tool_result(result, tc["id"])
|
|
@@ -463,7 +469,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
463
469
|
}
|
|
464
470
|
if response.get("reasoning_content"):
|
|
465
471
|
assistant_msg["reasoning_content"] = response["reasoning_content"]
|
|
466
|
-
self.
|
|
472
|
+
self._append_message(assistant_msg)
|
|
467
473
|
for tc, result in zip(tool_calls, batch_results, strict=True):
|
|
468
474
|
self._store_tool_result(result, tc["id"])
|
|
469
475
|
|
|
@@ -579,25 +585,23 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
579
585
|
Mirrors the main run() loop: skill tool filtering, token compaction,
|
|
580
586
|
consecutive-failure detection, and circuit breaker.
|
|
581
587
|
"""
|
|
582
|
-
self.
|
|
588
|
+
self._append_message({"role": "user", "content": message})
|
|
583
589
|
|
|
584
590
|
SAFETY_LIMIT = 999 # circuit breaker
|
|
585
591
|
_consecutive_failures = 0
|
|
586
592
|
_MAX_CONSECUTIVE_FAILURES = 5
|
|
587
593
|
|
|
588
594
|
while self._state.tool_call_count < SAFETY_LIMIT:
|
|
589
|
-
# ── Token budget: auto-compact when approaching the limit
|
|
590
|
-
|
|
591
|
-
max_tokens = self.config.agent.max_context_tokens
|
|
592
|
-
effective = self.config.agent.effective_context_tokens
|
|
593
|
-
if est_tokens > effective:
|
|
595
|
+
# ── Token budget: auto-compact when approaching the limit (O(1)) ──
|
|
596
|
+
if self._context_manager.should_compact():
|
|
594
597
|
logger.warning("chat(): token budget %d/%d effective, auto-compacting",
|
|
595
|
-
|
|
598
|
+
self._context_manager.token_total,
|
|
599
|
+
self.config.agent.effective_context_tokens)
|
|
596
600
|
await self.compact()
|
|
597
|
-
|
|
598
|
-
if est_tokens > max_tokens * 0.95:
|
|
601
|
+
if self._context_manager.needs_force_truncate():
|
|
599
602
|
logger.critical("chat(): hard ceiling %d > 95%% of %d, force-truncating",
|
|
600
|
-
|
|
603
|
+
self._context_manager.token_total,
|
|
604
|
+
self.config.agent.max_context_tokens)
|
|
601
605
|
self._force_truncate()
|
|
602
606
|
|
|
603
607
|
# ── Skill tool filtering ────────────────────────────────────
|
|
@@ -639,7 +643,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
639
643
|
batch_results.append(result)
|
|
640
644
|
self._warn_if_large_result(result, tool_name)
|
|
641
645
|
|
|
642
|
-
self.
|
|
646
|
+
self._append_message({
|
|
643
647
|
"role": "assistant",
|
|
644
648
|
"content": text or None,
|
|
645
649
|
"tool_calls": [tc],
|
|
@@ -850,8 +854,15 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
850
854
|
|
|
851
855
|
# ── Change tracking helper → agent_tools.py (ToolExecutionMixin._read_old_content)
|
|
852
856
|
|
|
857
|
+
def _append_message(self, msg: Message) -> None:
|
|
858
|
+
"""Append a message to state AND context manager (O(1) token update)."""
|
|
859
|
+
self._state.messages.append(msg)
|
|
860
|
+
self._context_manager.append(msg)
|
|
861
|
+
|
|
853
862
|
def get_token_estimate(self) -> int:
|
|
854
|
-
"""
|
|
863
|
+
"""O(1) token total from ContextManager. Falls back to LLM count if stale."""
|
|
864
|
+
if self._context_manager.messages:
|
|
865
|
+
return self._context_manager.token_total
|
|
855
866
|
return self.llm.count_tokens_approx(self._state.messages)
|
|
856
867
|
|
|
857
868
|
def get_conversation_summary(self) -> str:
|
|
@@ -880,5 +891,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
880
891
|
# Clawd: final SessionEnd
|
|
881
892
|
get_clawd().shutdown()
|
|
882
893
|
await self.llm.close()
|
|
894
|
+
if self._summary_llm:
|
|
895
|
+
await self._summary_llm.close()
|
|
883
896
|
if self.mcp:
|
|
884
897
|
await self.mcp.stop_all()
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Context compaction and token budget management — mixin for CoderAgent.
|
|
2
|
+
|
|
3
|
+
Delegates all context operations to ContextManager. This mixin is now a
|
|
4
|
+
thin wrapper that provides the same public API while eliminating duplicated
|
|
5
|
+
logic, avoiding deep copies, and reusing the summarisation LLM client.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import copy
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from .types import Message
|
|
12
|
+
from .clawd_integration import get_clawd
|
|
13
|
+
from .model_router import get_subagent_model
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CompactionMixin:
|
|
19
|
+
"""Context window compaction — thin wrapper around ContextManager."""
|
|
20
|
+
|
|
21
|
+
# ── Compaction token budget (class-level defaults, overridable) ───────
|
|
22
|
+
RECENT_TOKEN_BUDGET = 80_000 # max tokens to keep in the recent segment
|
|
23
|
+
COMPACT_IF_FEWER_THAN = 6 # skip compaction if fewer than this many msgs
|
|
24
|
+
|
|
25
|
+
# ── Core compaction ───────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
async def compact(self) -> str:
|
|
28
|
+
"""Compact conversation by summarising old messages.
|
|
29
|
+
|
|
30
|
+
Strategy: keep system prompt + recent messages up to
|
|
31
|
+
RECENT_TOKEN_BUDGET tokens, summarise everything in between using
|
|
32
|
+
a cheap LLM call. Falls back to a lightweight extractive summary
|
|
33
|
+
if the API call fails.
|
|
34
|
+
|
|
35
|
+
Delegates segment-splitting to ContextManager to avoid the
|
|
36
|
+
duplicated walk-backwards logic that was previously shared with
|
|
37
|
+
_force_truncate.
|
|
38
|
+
"""
|
|
39
|
+
cm = self._context_manager
|
|
40
|
+
if not cm.can_compact():
|
|
41
|
+
return "Already compact."
|
|
42
|
+
|
|
43
|
+
# Clawd: PreCompact
|
|
44
|
+
get_clawd().compact()
|
|
45
|
+
|
|
46
|
+
system_msg, recent, archive = cm.split_into_segments()
|
|
47
|
+
|
|
48
|
+
if not archive:
|
|
49
|
+
return "Already compact (all messages fit in recent budget)."
|
|
50
|
+
|
|
51
|
+
# Extract summary metadata from the archive segment
|
|
52
|
+
tool_count = sum(1 for m in archive if m.get("tool_calls"))
|
|
53
|
+
user_msgs = [m.get("content", "")[:200] for m in archive if m.get("role") == "user"]
|
|
54
|
+
file_ops = cm.collect_file_ops(archive)
|
|
55
|
+
|
|
56
|
+
summary = await self._summarise_messages(archive, file_ops, user_msgs, tool_count)
|
|
57
|
+
|
|
58
|
+
old_count = len(cm.messages)
|
|
59
|
+
old_tokens = cm.token_total
|
|
60
|
+
|
|
61
|
+
truncated: list[Message] = []
|
|
62
|
+
if system_msg:
|
|
63
|
+
truncated.append(system_msg)
|
|
64
|
+
truncated.append({
|
|
65
|
+
"role": "user",
|
|
66
|
+
"content": "[Conversation summary]\n" + summary,
|
|
67
|
+
})
|
|
68
|
+
truncated.append({
|
|
69
|
+
"role": "assistant",
|
|
70
|
+
"content": "Understood. I'll continue with the remaining context using the summary above.",
|
|
71
|
+
})
|
|
72
|
+
truncated.extend(recent)
|
|
73
|
+
|
|
74
|
+
cm.replace_all(truncated)
|
|
75
|
+
self._cached_system_prompt = None # system msg may have shifted
|
|
76
|
+
self._state.messages = cm.messages # sync for backward compat
|
|
77
|
+
|
|
78
|
+
new_tokens = cm.token_total
|
|
79
|
+
logger.info("Compacted: %d→%d msgs, ~%d→%d tokens (files: %d, tools: %d)",
|
|
80
|
+
old_count, len(truncated), old_tokens, new_tokens,
|
|
81
|
+
len(file_ops), tool_count)
|
|
82
|
+
return (f"Compacted from {old_count}→{len(truncated)} messages "
|
|
83
|
+
f"(~{old_tokens:,}→~{new_tokens:,} tokens, {len(file_ops)} files, {tool_count} tool calls).")
|
|
84
|
+
|
|
85
|
+
def _force_truncate(self) -> None:
|
|
86
|
+
"""Drop the oldest non-system messages when we exceed 95% of max tokens.
|
|
87
|
+
|
|
88
|
+
Called only as a last resort after compaction has already run.
|
|
89
|
+
Delegates to ContextManager.build_truncated_list() — no more
|
|
90
|
+
duplicated walk-backwards.
|
|
91
|
+
"""
|
|
92
|
+
cm = self._context_manager
|
|
93
|
+
if len(cm.messages) <= 6:
|
|
94
|
+
return
|
|
95
|
+
truncated, result = cm.build_truncated_list()
|
|
96
|
+
cm.replace_all(truncated)
|
|
97
|
+
self._cached_system_prompt = None
|
|
98
|
+
self._state.messages = cm.messages # sync
|
|
99
|
+
logger.warning("Force-truncated: %d → %d messages (~%d tokens kept)",
|
|
100
|
+
result.old_count, result.new_count, result.new_tokens)
|
|
101
|
+
|
|
102
|
+
# ── Token estimation helpers (delegate to ContextManager) ────────────
|
|
103
|
+
|
|
104
|
+
def _estimate_message_tokens(self, msg: Message) -> int:
|
|
105
|
+
"""Rough token estimate for a single message (via ContextManager cache)."""
|
|
106
|
+
return self._context_manager.get_msg_tokens(msg)
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _collect_file_ops(messages: list[Message]) -> list[str]:
|
|
110
|
+
"""Collect files modified in a message list (static, delegates to CM)."""
|
|
111
|
+
from .context_manager import ContextManager
|
|
112
|
+
return ContextManager.collect_file_ops(messages)
|
|
113
|
+
|
|
114
|
+
# ── Summarisation (reuses a single cheap LLM client) ──────────────────
|
|
115
|
+
|
|
116
|
+
async def _summarise_messages(self, archive: list[Message], file_ops: list[str],
|
|
117
|
+
user_msgs: list[str], tool_count: int) -> str:
|
|
118
|
+
"""Generate a summary of the archive conversation segment.
|
|
119
|
+
|
|
120
|
+
Attempts a cheap LLM call first; falls back to a lightweight extractive
|
|
121
|
+
summary so the user never loses context entirely. The summarisation
|
|
122
|
+
client is created once and reused across compactions.
|
|
123
|
+
"""
|
|
124
|
+
# ── LLM-based summary (best effort) ──────────────────────────────
|
|
125
|
+
try:
|
|
126
|
+
summary_prompt = (
|
|
127
|
+
"Summarise this conversation segment in 3-5 bullet points. "
|
|
128
|
+
"Focus on: what the user asked, what files were changed, what "
|
|
129
|
+
"decisions were made, and any unresolved issues. "
|
|
130
|
+
"Be concise — this summary will replace the full conversation "
|
|
131
|
+
"history to save context tokens.\n\n"
|
|
132
|
+
f"Files modified: {', '.join(file_ops) if file_ops else 'none'}\n"
|
|
133
|
+
f"Tool calls: {tool_count}\n"
|
|
134
|
+
f"User requests: {'; '.join(user_msgs[:5])}\n"
|
|
135
|
+
)
|
|
136
|
+
sc = getattr(self, '_summary_llm', None)
|
|
137
|
+
if sc is None:
|
|
138
|
+
from .llm_client import LLMClient
|
|
139
|
+
summary_config = copy.deepcopy(self.llm.config)
|
|
140
|
+
summary_config.model = get_subagent_model()
|
|
141
|
+
sc = LLMClient(summary_config)
|
|
142
|
+
self._summary_llm = sc # cache for reuse
|
|
143
|
+
resp = await sc.chat([{"role": "user", "content": summary_prompt}], tools=[])
|
|
144
|
+
llm_summary = (resp.get("content") or "").strip()
|
|
145
|
+
if llm_summary:
|
|
146
|
+
parts = [llm_summary]
|
|
147
|
+
if file_ops:
|
|
148
|
+
parts.append(f"\nFiles touched: {', '.join(file_ops[:10])}")
|
|
149
|
+
return "\n".join(parts)
|
|
150
|
+
except Exception:
|
|
151
|
+
logger.debug("LLM summarisation unavailable, using extractive fallback")
|
|
152
|
+
|
|
153
|
+
# ── Extractive fallback ─────────────────────────────────────────
|
|
154
|
+
parts = [f"Summarised {len(archive)} messages ({tool_count} tool calls)."]
|
|
155
|
+
if user_msgs:
|
|
156
|
+
parts.append(f"Topics: {'; '.join(user_msgs[:5])}")
|
|
157
|
+
if file_ops:
|
|
158
|
+
parts.append(f"Files modified: {', '.join(file_ops[:10])}")
|
|
159
|
+
return "\n".join(parts)
|
|
@@ -17,7 +17,8 @@ class ToolExecutionMixin:
|
|
|
17
17
|
# ── Tool execution ────────────────────────────────────────────────────
|
|
18
18
|
|
|
19
19
|
# Guard depth for self-correction retry — prevents infinite recursion.
|
|
20
|
-
|
|
20
|
+
# These are set as INSTANCE variables in CoderAgent.__init__ to avoid
|
|
21
|
+
# cross-session contamination under ThreadingHTTPServer (server mode).
|
|
21
22
|
_MAX_SELF_CORRECT_DEPTH: int = 1
|
|
22
23
|
|
|
23
24
|
async def _execute_tool(self, tool_name: str, arguments: dict[str, Any]) -> ToolResult:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ata-coder
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.8
|
|
4
4
|
Summary: ATA Coder — AI-powered coding assistant
|
|
5
5
|
Author: ATA Coder Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -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.7
|
|
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.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
|
+
>
|
|
32
|
+
> > **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.
|
|
31
33
|
>
|
|
32
34
|
> > **v2.4.5** — 🤖 **Self-Bootstrapped Audit**: ATA Coder found 19 bugs in its own source code and fixed them all — thread safety, SSRF IPv6, rate limiter leak, auth hardening, DRY refactoring, CI coverage. 12 files changed.
|
|
33
35
|
>
|
|
@@ -550,7 +552,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
|
|
|
550
552
|
|
|
551
553
|
## 中文
|
|
552
554
|
|
|
553
|
-
> **v2.4.
|
|
555
|
+
> **v2.4.7** — ⚡ **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
|
|
556
|
+
>
|
|
557
|
+
> > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
|
|
554
558
|
>
|
|
555
559
|
> > **v2.4.5** — 🤖 **自举审计**: ATA Coder 审计自身源码发现 19 个 bug 并全部修复 — 线程安全、SSRF IPv6、速率限制器泄漏、认证加固、DRY 重构、CI 覆盖。12 个文件变更。
|
|
556
560
|
>
|
|
@@ -14,6 +14,7 @@ anthropic_client.py
|
|
|
14
14
|
change_tracker.py
|
|
15
15
|
clawd_integration.py
|
|
16
16
|
config.py
|
|
17
|
+
context_manager.py
|
|
17
18
|
event_queue.py
|
|
18
19
|
extension.py
|
|
19
20
|
fool_proof.py
|
|
@@ -65,6 +66,7 @@ utils.py
|
|
|
65
66
|
./change_tracker.py
|
|
66
67
|
./clawd_integration.py
|
|
67
68
|
./config.py
|
|
69
|
+
./context_manager.py
|
|
68
70
|
./event_queue.py
|
|
69
71
|
./extension.py
|
|
70
72
|
./fool_proof.py
|
|
@@ -120,6 +120,13 @@ class AgentConfig:
|
|
|
120
120
|
self, "effective_context_tokens",
|
|
121
121
|
max(10000, int(self.max_context_tokens * 0.9)),
|
|
122
122
|
)
|
|
123
|
+
# Compaction/context budgets (passed to ContextManager)
|
|
124
|
+
recent_token_budget: int = field(
|
|
125
|
+
default_factory=lambda: int(_from_settings("recent_token_budget", 80000))
|
|
126
|
+
)
|
|
127
|
+
compact_if_fewer_than: int = field(
|
|
128
|
+
default_factory=lambda: int(_from_settings("compact_if_fewer_than", 6))
|
|
129
|
+
)
|
|
123
130
|
max_message_output_chars: int = field(
|
|
124
131
|
default_factory=lambda: int(_from_settings("max_message_output_chars", 8000))
|
|
125
132
|
)
|
|
@@ -190,17 +197,21 @@ class AppConfig:
|
|
|
190
197
|
# Using a lazy pattern because AppConfig.load() references _from_settings()
|
|
191
198
|
# which is defined after the dataclass body in this module.
|
|
192
199
|
_config: AppConfig | None = None
|
|
200
|
+
_config_lock = threading.Lock()
|
|
193
201
|
|
|
194
202
|
|
|
195
203
|
def get_config() -> AppConfig:
|
|
196
204
|
"""Return the module-level config singleton (lazy init on first call).
|
|
197
205
|
|
|
198
|
-
After the first call the config is cached.
|
|
199
|
-
|
|
206
|
+
After the first call the config is cached. Double-checked locking
|
|
207
|
+
protects the lazy-init path when server.py runs under ThreadingHTTPServer
|
|
208
|
+
(where multiple threads may race on the first call).
|
|
200
209
|
"""
|
|
201
210
|
global _config
|
|
202
211
|
if _config is None:
|
|
203
|
-
|
|
212
|
+
with _config_lock:
|
|
213
|
+
if _config is None: # double-check
|
|
214
|
+
_config = AppConfig.load()
|
|
204
215
|
return _config
|
|
205
216
|
|
|
206
217
|
|