ata-coder 2.4.6__tar.gz → 2.4.7__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.7}/PKG-INFO +8 -4
- {ata_coder-2.4.6 → ata_coder-2.4.7}/README.md +7 -3
- {ata_coder-2.4.6 → ata_coder-2.4.7}/agent.py +39 -30
- ata_coder-2.4.7/agent_compact.py +159 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7/ata_coder.egg-info}/PKG-INFO +8 -4
- {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/SOURCES.txt +2 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/config.py +7 -0
- ata_coder-2.4.7/context_manager.py +230 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/main.py +1 -1
- {ata_coder-2.4.6 → ata_coder-2.4.7}/memory.py +73 -20
- {ata_coder-2.4.6 → ata_coder-2.4.7}/pyproject.toml +1 -1
- {ata_coder-2.4.6 → ata_coder-2.4.7}/setup_wizard.py +1 -1
- {ata_coder-2.4.6 → ata_coder-2.4.7}/system_prompt_builder.py +94 -34
- {ata_coder-2.4.6 → ata_coder-2.4.7}/token_counter.py +79 -43
- ata_coder-2.4.6/agent_compact.py +0 -195
- {ata_coder-2.4.6 → ata_coder-2.4.7}/LICENSE +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/MANIFEST.in +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_controller.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_extension.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_routing.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_subsystems.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_tools.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/agent_undo.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/anthropic_client.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/dependency_links.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/entry_points.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/requires.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/ata_coder.egg-info/top_level.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/change_tracker.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/clawd_integration.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/_core.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/_safety.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/_settings.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/commands/_workflow.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/core/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/core/events.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/core/queue.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/core/state.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/event_queue.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/extension.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/extensions/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/extensions/hello_skill.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/fool_proof.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/git_workflow.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/gui.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/llm_client.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/mcp_client.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/model_registry.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/model_router.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/permissions.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/privilege.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/project.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompt_template.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/auto-mode.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/coding-rules.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/execution-guardrails.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/memory-system.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/output-style.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/safety.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/slash-commands.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/sub-agents.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/system-reminders.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/system.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/prompts/tool-policy.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/py.typed +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/repl_theme.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/repl_tracker.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/repl_ui.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/safety_guard.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/self_correct.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/server.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/server_session.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/server_shell.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/session.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/settings.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/setup.cfg +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skill_extension.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/architect/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/code-reviewer/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/codecraft/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/debugger/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/doc-writer/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/general-coder/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/README.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/handler.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/prompts/system.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/requirements.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/resources/constants.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/math-calculator/tests/test_handler.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/security-auditor/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/test-writer/SKILL.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/README.md +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/handler.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/manifest.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/prompts/system_prompt.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/prompts/user_prompt_template.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/requirements.txt +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/resources/city_list.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/resources/error_messages.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/tests/test_handler.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills/weather-skill/weather_utils.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/skills.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/sub_agent.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/sub_agent_manager.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/task_planner.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/terminal.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_agent.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_change_tracker.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_config.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_event_queue.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_extension.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_fibonacci.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_fool_proof.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_llm_client.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_memory.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_model_registry.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_permissions.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_privilege.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_prompt_template.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_safety_guard.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_server.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_skill_handlers.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_sub_agent.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tests/test_tools.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/__init__.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/definitions.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/executor.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/result.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/strategy.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/subagent.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/tools/web.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/types.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/utils.py +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/web/css/style.css +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/web/index.html +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/web/js/app.js +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/web/package-lock.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/web/package.json +0 -0
- {ata_coder-2.4.6 → ata_coder-2.4.7}/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.7
|
|
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
|
|
@@ -148,6 +149,10 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
148
149
|
self._cached_system_prompt: str | None = None # invalidated on new build / compact
|
|
149
150
|
self._cached_allowed_tools: set[str] | None = None # invalidated on skill change
|
|
150
151
|
|
|
152
|
+
# ── Context manager (O(1) token tracking, segment-split, adaptive compact) ──
|
|
153
|
+
self._context_manager = ContextManager(self.config.agent)
|
|
154
|
+
self._summary_llm = None # lazily created summarisation client
|
|
155
|
+
|
|
151
156
|
# Build the combined tool list
|
|
152
157
|
self._all_tools = list(TOOL_DEFINITIONS)
|
|
153
158
|
if self.mcp:
|
|
@@ -227,7 +232,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
227
232
|
if not reset_context and self._state.messages:
|
|
228
233
|
# Append new user message to existing conversation; keep system
|
|
229
234
|
# prompt and all prior messages intact.
|
|
230
|
-
self.
|
|
235
|
+
self._append_message({"role": "user", "content": task})
|
|
231
236
|
# Rebuild system prompt for updated memory context but don't
|
|
232
237
|
# replace the original system message (memory/git context may
|
|
233
238
|
# have changed, but conversation integrity is paramount).
|
|
@@ -291,10 +296,12 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
291
296
|
self._cached_system_prompt = system_prompt # pre-seed cache
|
|
292
297
|
self._cached_allowed_tools = None # invalidate on new run
|
|
293
298
|
|
|
294
|
-
|
|
299
|
+
initial_msgs = [
|
|
295
300
|
{"role": "system", "content": system_prompt},
|
|
296
301
|
{"role": "user", "content": task},
|
|
297
302
|
]
|
|
303
|
+
self._state.messages = initial_msgs
|
|
304
|
+
self._context_manager.replace_all(initial_msgs)
|
|
298
305
|
|
|
299
306
|
logger.info("Agent run: skill=%s, model=%s, session=%s, task=%.100s",
|
|
300
307
|
self.skills.active_skill.name if self.skills and self.skills.active_skill else "default",
|
|
@@ -352,24 +359,19 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
352
359
|
# Clawd: model is generating, show thinking animation
|
|
353
360
|
get_clawd().thinking()
|
|
354
361
|
|
|
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:
|
|
362
|
+
# Auto-compact when approaching the effective context limit (O(1) check).
|
|
363
|
+
if self._context_manager.should_compact():
|
|
364
|
+
est = self._context_manager.token_total
|
|
365
|
+
max_t = self.config.agent.max_context_tokens
|
|
364
366
|
logger.warning("Token budget: %d/%d effective (%.0f%% of %d max), auto-compacting",
|
|
365
|
-
|
|
367
|
+
est, self.config.agent.effective_context_tokens,
|
|
368
|
+
est / max(max_t, 1) * 100, max_t)
|
|
366
369
|
await self.compact()
|
|
367
|
-
# Re-estimate AFTER compaction — the message list has changed
|
|
368
|
-
est_tokens = self.get_token_estimate()
|
|
369
370
|
# Hard ceiling: if compaction didn't help enough, force-truncate
|
|
370
|
-
if
|
|
371
|
+
if self._context_manager.needs_force_truncate():
|
|
371
372
|
logger.critical("Hard token ceiling: %d > 95%% of %d max. Force-truncating.",
|
|
372
|
-
|
|
373
|
+
self._context_manager.token_total,
|
|
374
|
+
self.config.agent.max_context_tokens)
|
|
373
375
|
self._force_truncate()
|
|
374
376
|
|
|
375
377
|
# Get allowed tools from multi-skill intersection
|
|
@@ -437,7 +439,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
437
439
|
}
|
|
438
440
|
if response.get("reasoning_content"):
|
|
439
441
|
assistant_msg["reasoning_content"] = response["reasoning_content"]
|
|
440
|
-
self.
|
|
442
|
+
self._append_message(assistant_msg)
|
|
441
443
|
for tc, result in zip(tool_calls, results, strict=True):
|
|
442
444
|
self._warn_if_large_result(result, tc["function"]["name"])
|
|
443
445
|
self._store_tool_result(result, tc["id"])
|
|
@@ -463,7 +465,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
463
465
|
}
|
|
464
466
|
if response.get("reasoning_content"):
|
|
465
467
|
assistant_msg["reasoning_content"] = response["reasoning_content"]
|
|
466
|
-
self.
|
|
468
|
+
self._append_message(assistant_msg)
|
|
467
469
|
for tc, result in zip(tool_calls, batch_results, strict=True):
|
|
468
470
|
self._store_tool_result(result, tc["id"])
|
|
469
471
|
|
|
@@ -579,25 +581,23 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
579
581
|
Mirrors the main run() loop: skill tool filtering, token compaction,
|
|
580
582
|
consecutive-failure detection, and circuit breaker.
|
|
581
583
|
"""
|
|
582
|
-
self.
|
|
584
|
+
self._append_message({"role": "user", "content": message})
|
|
583
585
|
|
|
584
586
|
SAFETY_LIMIT = 999 # circuit breaker
|
|
585
587
|
_consecutive_failures = 0
|
|
586
588
|
_MAX_CONSECUTIVE_FAILURES = 5
|
|
587
589
|
|
|
588
590
|
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:
|
|
591
|
+
# ── Token budget: auto-compact when approaching the limit (O(1)) ──
|
|
592
|
+
if self._context_manager.should_compact():
|
|
594
593
|
logger.warning("chat(): token budget %d/%d effective, auto-compacting",
|
|
595
|
-
|
|
594
|
+
self._context_manager.token_total,
|
|
595
|
+
self.config.agent.effective_context_tokens)
|
|
596
596
|
await self.compact()
|
|
597
|
-
|
|
598
|
-
if est_tokens > max_tokens * 0.95:
|
|
597
|
+
if self._context_manager.needs_force_truncate():
|
|
599
598
|
logger.critical("chat(): hard ceiling %d > 95%% of %d, force-truncating",
|
|
600
|
-
|
|
599
|
+
self._context_manager.token_total,
|
|
600
|
+
self.config.agent.max_context_tokens)
|
|
601
601
|
self._force_truncate()
|
|
602
602
|
|
|
603
603
|
# ── Skill tool filtering ────────────────────────────────────
|
|
@@ -639,7 +639,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
639
639
|
batch_results.append(result)
|
|
640
640
|
self._warn_if_large_result(result, tool_name)
|
|
641
641
|
|
|
642
|
-
self.
|
|
642
|
+
self._append_message({
|
|
643
643
|
"role": "assistant",
|
|
644
644
|
"content": text or None,
|
|
645
645
|
"tool_calls": [tc],
|
|
@@ -850,8 +850,15 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
850
850
|
|
|
851
851
|
# ── Change tracking helper → agent_tools.py (ToolExecutionMixin._read_old_content)
|
|
852
852
|
|
|
853
|
+
def _append_message(self, msg: Message) -> None:
|
|
854
|
+
"""Append a message to state AND context manager (O(1) token update)."""
|
|
855
|
+
self._state.messages.append(msg)
|
|
856
|
+
self._context_manager.append(msg)
|
|
857
|
+
|
|
853
858
|
def get_token_estimate(self) -> int:
|
|
854
|
-
"""
|
|
859
|
+
"""O(1) token total from ContextManager. Falls back to LLM count if stale."""
|
|
860
|
+
if self._context_manager.messages:
|
|
861
|
+
return self._context_manager.token_total
|
|
855
862
|
return self.llm.count_tokens_approx(self._state.messages)
|
|
856
863
|
|
|
857
864
|
def get_conversation_summary(self) -> str:
|
|
@@ -880,5 +887,7 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
880
887
|
# Clawd: final SessionEnd
|
|
881
888
|
get_clawd().shutdown()
|
|
882
889
|
await self.llm.close()
|
|
890
|
+
if self._summary_llm:
|
|
891
|
+
await self._summary_llm.close()
|
|
883
892
|
if self.mcp:
|
|
884
893
|
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)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ata-coder
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.7
|
|
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
|
)
|