ata-coder 2.4.3__tar.gz → 2.4.5__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.3/ata_coder.egg-info → ata_coder-2.4.5}/PKG-INFO +21 -3
- {ata_coder-2.4.3 → ata_coder-2.4.5}/README.md +20 -2
- {ata_coder-2.4.3 → ata_coder-2.4.5}/agent.py +4 -9
- {ata_coder-2.4.3 → ata_coder-2.4.5}/agent_compact.py +11 -4
- {ata_coder-2.4.3 → ata_coder-2.4.5/ata_coder.egg-info}/PKG-INFO +21 -3
- {ata_coder-2.4.3 → ata_coder-2.4.5}/ata_coder.egg-info/SOURCES.txt +2 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/config.py +10 -2
- {ata_coder-2.4.3 → ata_coder-2.4.5}/extension.py +38 -38
- {ata_coder-2.4.3 → ata_coder-2.4.5}/git_workflow.py +9 -7
- {ata_coder-2.4.3 → ata_coder-2.4.5}/main.py +31 -14
- {ata_coder-2.4.3 → ata_coder-2.4.5}/mcp_client.py +25 -3
- ata_coder-2.4.5/py.typed +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/pyproject.toml +1 -1
- {ata_coder-2.4.3 → ata_coder-2.4.5}/safety_guard.py +47 -32
- {ata_coder-2.4.3 → ata_coder-2.4.5}/server.py +212 -69
- {ata_coder-2.4.3 → ata_coder-2.4.5}/server_session.py +263 -178
- {ata_coder-2.4.3 → ata_coder-2.4.5}/server_shell.py +8 -2
- {ata_coder-2.4.3 → ata_coder-2.4.5}/settings.py +139 -2
- {ata_coder-2.4.3 → ata_coder-2.4.5}/setup_wizard.py +1 -1
- {ata_coder-2.4.3 → ata_coder-2.4.5}/sub_agent.py +3 -8
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_server.py +1 -1
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tools/executor.py +7 -4
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tools/web.py +60 -7
- {ata_coder-2.4.3 → ata_coder-2.4.5}/utils.py +20 -4
- {ata_coder-2.4.3 → ata_coder-2.4.5}/LICENSE +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/MANIFEST.in +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/__init__.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/agent_controller.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/agent_extension.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/agent_routing.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/agent_subsystems.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/agent_tools.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/agent_undo.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/anthropic_client.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/ata_coder.egg-info/dependency_links.txt +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/ata_coder.egg-info/entry_points.txt +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/ata_coder.egg-info/requires.txt +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/ata_coder.egg-info/top_level.txt +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/change_tracker.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/clawd_integration.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/commands/__init__.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/commands/_core.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/commands/_safety.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/commands/_settings.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/commands/_workflow.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/core/__init__.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/core/events.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/core/queue.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/core/state.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/event_queue.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/extensions/__init__.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/extensions/hello_skill.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/fool_proof.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/gui.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/llm_client.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/memory.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/model_registry.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/model_router.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/permissions.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/privilege.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/project.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompt_template.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/auto-mode.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/coding-rules.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/execution-guardrails.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/memory-system.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/output-style.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/safety.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/slash-commands.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/sub-agents.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/system-reminders.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/system.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/prompts/tool-policy.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/repl_theme.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/repl_tracker.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/repl_ui.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/self_correct.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/session.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/setup.cfg +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skill_extension.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/architect/SKILL.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/code-reviewer/SKILL.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/codecraft/SKILL.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/debugger/SKILL.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/doc-writer/SKILL.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/general-coder/SKILL.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/math-calculator/README.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/math-calculator/SKILL.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/math-calculator/handler.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/math-calculator/prompts/system.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/math-calculator/requirements.txt +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/math-calculator/resources/constants.json +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/math-calculator/tests/test_handler.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/security-auditor/SKILL.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/test-writer/SKILL.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/README.md +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/handler.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/manifest.json +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/prompts/system_prompt.txt +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/prompts/user_prompt_template.txt +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/requirements.txt +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/resources/city_list.json +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/resources/error_messages.json +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/tests/test_handler.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills/weather-skill/weather_utils.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/skills.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/sub_agent_manager.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/system_prompt_builder.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/task_planner.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/terminal.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_agent.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_change_tracker.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_config.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_event_queue.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_extension.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_fibonacci.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_fool_proof.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_llm_client.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_memory.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_model_registry.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_permissions.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_privilege.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_prompt_template.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_safety_guard.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_skill_handlers.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_sub_agent.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tests/test_tools.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/token_counter.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tools/__init__.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tools/definitions.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tools/result.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tools/strategy.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/tools/subagent.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/types.py +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/web/css/style.css +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/web/index.html +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/web/js/app.js +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/web/package-lock.json +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/web/package.json +0 -0
- {ata_coder-2.4.3 → ata_coder-2.4.5}/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.5
|
|
4
4
|
Summary: ATA Coder — AI-powered coding assistant
|
|
5
5
|
Author: ATA Coder Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,13 +21,17 @@ 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.5
|
|
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.5** — 🛡️ **Comprehensive Bug & Security Fix**: 19 bugs fixed — thread safety, SSRF IPv6, rate limiter leak, auth hardening, DRY refactoring, CI coverage. 12 files changed.
|
|
31
|
+
>
|
|
32
|
+
> > **v2.4.4** — 🔒 **Security Hardening**: 19 fixes across safety, storage, and reliability.
|
|
33
|
+
>
|
|
34
|
+
> > **v2.4.3** — 🧠 **Comprehensive Refactoring**: Memory overhaul, unified token counting, safety pipeline, tool call optimization. 60+ issues fixed.
|
|
31
35
|
>
|
|
32
36
|
> > **v2.4.2** — 🐾 **Clawd working state + Window tokens**: Clawd shows 'working' immediately. Status line shows window tokens (~120k) not cumulative (7.8M). Surrogate-safe session saves.
|
|
33
37
|
>
|
|
@@ -51,6 +55,10 @@ Dynamic: license-file
|
|
|
51
55
|
|
|
52
56
|
ATA Coder is a CLI AI coding assistant compatible with OpenAI and Anthropic APIs. It runs on a **single-threaded asyncio event loop** — no threads, no race conditions, low memory. Features deterministic AST-based code editing, sub-agent pool, MCP support, and a built-in HTTP API server.
|
|
53
57
|
|
|
58
|
+
```bash
|
|
59
|
+
pip install ata-coder
|
|
60
|
+
```
|
|
61
|
+
|
|
54
62
|
### Architecture (v2.3.3)
|
|
55
63
|
|
|
56
64
|
```
|
|
@@ -95,7 +103,12 @@ asyncio Event Loop (single-threaded)
|
|
|
95
103
|
### Quick Start
|
|
96
104
|
|
|
97
105
|
```bash
|
|
106
|
+
# Install from PyPI (recommended)
|
|
107
|
+
pip install ata-coder
|
|
108
|
+
|
|
109
|
+
# Or install from source (development)
|
|
98
110
|
pip install -e .
|
|
111
|
+
|
|
99
112
|
ata # Interactive REPL
|
|
100
113
|
ata run "Add type hints" # Single task
|
|
101
114
|
ata server --port 8080 # HTTP API server
|
|
@@ -558,7 +571,12 @@ asyncio 事件循环(单线程)
|
|
|
558
571
|
### 快速开始
|
|
559
572
|
|
|
560
573
|
```bash
|
|
574
|
+
# 从 PyPI 安装(推荐)
|
|
575
|
+
pip install ata-coder
|
|
576
|
+
|
|
577
|
+
# 或从源码安装(开发模式)
|
|
561
578
|
pip install -e .
|
|
579
|
+
|
|
562
580
|
ata # 交互模式
|
|
563
581
|
ata run "添加类型注解" # 单任务
|
|
564
582
|
ata server --port 8080 # API 服务
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
# ATA Coder v2.4.
|
|
1
|
+
# ATA Coder v2.4.5
|
|
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.5** — 🛡️ **Comprehensive Bug & Security Fix**: 19 bugs fixed — thread safety, SSRF IPv6, rate limiter leak, auth hardening, DRY refactoring, CI coverage. 12 files changed.
|
|
8
|
+
>
|
|
9
|
+
> > **v2.4.4** — 🔒 **Security Hardening**: 19 fixes across safety, storage, and reliability.
|
|
10
|
+
>
|
|
11
|
+
> > **v2.4.3** — 🧠 **Comprehensive Refactoring**: Memory overhaul, unified token counting, safety pipeline, tool call optimization. 60+ issues fixed.
|
|
8
12
|
>
|
|
9
13
|
> > **v2.4.2** — 🐾 **Clawd working state + Window tokens**: Clawd shows 'working' immediately. Status line shows window tokens (~120k) not cumulative (7.8M). Surrogate-safe session saves.
|
|
10
14
|
>
|
|
@@ -28,6 +32,10 @@
|
|
|
28
32
|
|
|
29
33
|
ATA Coder is a CLI AI coding assistant compatible with OpenAI and Anthropic APIs. It runs on a **single-threaded asyncio event loop** — no threads, no race conditions, low memory. Features deterministic AST-based code editing, sub-agent pool, MCP support, and a built-in HTTP API server.
|
|
30
34
|
|
|
35
|
+
```bash
|
|
36
|
+
pip install ata-coder
|
|
37
|
+
```
|
|
38
|
+
|
|
31
39
|
### Architecture (v2.3.3)
|
|
32
40
|
|
|
33
41
|
```
|
|
@@ -72,7 +80,12 @@ asyncio Event Loop (single-threaded)
|
|
|
72
80
|
### Quick Start
|
|
73
81
|
|
|
74
82
|
```bash
|
|
83
|
+
# Install from PyPI (recommended)
|
|
84
|
+
pip install ata-coder
|
|
85
|
+
|
|
86
|
+
# Or install from source (development)
|
|
75
87
|
pip install -e .
|
|
88
|
+
|
|
76
89
|
ata # Interactive REPL
|
|
77
90
|
ata run "Add type hints" # Single task
|
|
78
91
|
ata server --port 8080 # HTTP API server
|
|
@@ -535,7 +548,12 @@ asyncio 事件循环(单线程)
|
|
|
535
548
|
### 快速开始
|
|
536
549
|
|
|
537
550
|
```bash
|
|
551
|
+
# 从 PyPI 安装(推荐)
|
|
552
|
+
pip install ata-coder
|
|
553
|
+
|
|
554
|
+
# 或从源码安装(开发模式)
|
|
538
555
|
pip install -e .
|
|
556
|
+
|
|
539
557
|
ata # 交互模式
|
|
540
558
|
ata run "添加类型注解" # 单任务
|
|
541
559
|
ata server --port 8080 # API 服务
|
|
@@ -27,8 +27,7 @@ import time
|
|
|
27
27
|
from typing import Any, Callable
|
|
28
28
|
|
|
29
29
|
from .config import AppConfig
|
|
30
|
-
from .llm_client import
|
|
31
|
-
from .anthropic_client import AnthropicClient
|
|
30
|
+
from .llm_client import SYSTEM_PROMPT
|
|
32
31
|
from .tools import ToolExecutor, TOOL_DEFINITIONS, ToolResult
|
|
33
32
|
from .types import Message
|
|
34
33
|
from .agent_subsystems import AgentSubsystems
|
|
@@ -84,13 +83,9 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
|
|
|
84
83
|
):
|
|
85
84
|
self.config = config or AppConfig.load()
|
|
86
85
|
|
|
87
|
-
# Choose client: Anthropic or OpenAI format
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
self._use_anthropic = True
|
|
91
|
-
else:
|
|
92
|
-
self.llm = LLMClient(self.config.llm)
|
|
93
|
-
self._use_anthropic = False
|
|
86
|
+
# Choose client: Anthropic or OpenAI format (factory eliminates duplication)
|
|
87
|
+
from .utils import create_llm_client
|
|
88
|
+
self.llm, self._use_anthropic = create_llm_client(self.config.llm)
|
|
94
89
|
|
|
95
90
|
self.tools = tool_executor or ToolExecutor(self.config.agent)
|
|
96
91
|
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
6
|
from .types import Message
|
|
7
|
+
from .token_counter import TokenCounter
|
|
7
8
|
from .clawd_integration import get_clawd
|
|
8
9
|
from .model_router import get_subagent_model
|
|
9
10
|
logger = logging.getLogger(__name__)
|
|
@@ -35,7 +36,11 @@ class CompactionMixin:
|
|
|
35
36
|
recent_tokens = 0
|
|
36
37
|
for msg in reversed(all_but_system):
|
|
37
38
|
msg_tokens = self._estimate_message_tokens(msg)
|
|
38
|
-
if recent_tokens + msg_tokens > self.RECENT_TOKEN_BUDGET
|
|
39
|
+
if recent_tokens + msg_tokens > self.RECENT_TOKEN_BUDGET:
|
|
40
|
+
if not recent:
|
|
41
|
+
# Single huge message — include it anyway but stop after
|
|
42
|
+
recent.insert(0, msg)
|
|
43
|
+
recent_tokens += msg_tokens
|
|
39
44
|
break
|
|
40
45
|
recent.insert(0, msg)
|
|
41
46
|
recent_tokens += msg_tokens
|
|
@@ -79,8 +84,11 @@ class CompactionMixin:
|
|
|
79
84
|
recent_tokens = 0
|
|
80
85
|
for msg in reversed(all_but_system):
|
|
81
86
|
msg_tokens = self._estimate_message_tokens(msg)
|
|
82
|
-
if recent_tokens + msg_tokens > self.RECENT_TOKEN_BUDGET
|
|
83
|
-
|
|
87
|
+
if recent_tokens + msg_tokens > self.RECENT_TOKEN_BUDGET:
|
|
88
|
+
if not recent:
|
|
89
|
+
# Single huge message — include it anyway but stop after
|
|
90
|
+
recent.insert(0, msg)
|
|
91
|
+
recent_tokens += msg_tokens
|
|
84
92
|
break
|
|
85
93
|
recent.insert(0, msg)
|
|
86
94
|
recent_tokens += msg_tokens
|
|
@@ -120,7 +128,6 @@ class CompactionMixin:
|
|
|
120
128
|
|
|
121
129
|
def _estimate_message_tokens(self, msg: Message) -> int:
|
|
122
130
|
"""Rough token estimate for a single message (via TokenCounter)."""
|
|
123
|
-
from .token_counter import TokenCounter
|
|
124
131
|
model = getattr(self.llm.config, 'model', '')
|
|
125
132
|
return TokenCounter.for_model(model).count_tokens([msg])
|
|
126
133
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ata-coder
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.5
|
|
4
4
|
Summary: ATA Coder — AI-powered coding assistant
|
|
5
5
|
Author: ATA Coder Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,13 +21,17 @@ 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.5
|
|
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.5** — 🛡️ **Comprehensive Bug & Security Fix**: 19 bugs fixed — thread safety, SSRF IPv6, rate limiter leak, auth hardening, DRY refactoring, CI coverage. 12 files changed.
|
|
31
|
+
>
|
|
32
|
+
> > **v2.4.4** — 🔒 **Security Hardening**: 19 fixes across safety, storage, and reliability.
|
|
33
|
+
>
|
|
34
|
+
> > **v2.4.3** — 🧠 **Comprehensive Refactoring**: Memory overhaul, unified token counting, safety pipeline, tool call optimization. 60+ issues fixed.
|
|
31
35
|
>
|
|
32
36
|
> > **v2.4.2** — 🐾 **Clawd working state + Window tokens**: Clawd shows 'working' immediately. Status line shows window tokens (~120k) not cumulative (7.8M). Surrogate-safe session saves.
|
|
33
37
|
>
|
|
@@ -51,6 +55,10 @@ Dynamic: license-file
|
|
|
51
55
|
|
|
52
56
|
ATA Coder is a CLI AI coding assistant compatible with OpenAI and Anthropic APIs. It runs on a **single-threaded asyncio event loop** — no threads, no race conditions, low memory. Features deterministic AST-based code editing, sub-agent pool, MCP support, and a built-in HTTP API server.
|
|
53
57
|
|
|
58
|
+
```bash
|
|
59
|
+
pip install ata-coder
|
|
60
|
+
```
|
|
61
|
+
|
|
54
62
|
### Architecture (v2.3.3)
|
|
55
63
|
|
|
56
64
|
```
|
|
@@ -95,7 +103,12 @@ asyncio Event Loop (single-threaded)
|
|
|
95
103
|
### Quick Start
|
|
96
104
|
|
|
97
105
|
```bash
|
|
106
|
+
# Install from PyPI (recommended)
|
|
107
|
+
pip install ata-coder
|
|
108
|
+
|
|
109
|
+
# Or install from source (development)
|
|
98
110
|
pip install -e .
|
|
111
|
+
|
|
99
112
|
ata # Interactive REPL
|
|
100
113
|
ata run "Add type hints" # Single task
|
|
101
114
|
ata server --port 8080 # HTTP API server
|
|
@@ -558,7 +571,12 @@ asyncio 事件循环(单线程)
|
|
|
558
571
|
### 快速开始
|
|
559
572
|
|
|
560
573
|
```bash
|
|
574
|
+
# 从 PyPI 安装(推荐)
|
|
575
|
+
pip install ata-coder
|
|
576
|
+
|
|
577
|
+
# 或从源码安装(开发模式)
|
|
561
578
|
pip install -e .
|
|
579
|
+
|
|
562
580
|
ata # 交互模式
|
|
563
581
|
ata run "添加类型注解" # 单任务
|
|
564
582
|
ata server --port 8080 # API 服务
|
|
@@ -29,6 +29,7 @@ permissions.py
|
|
|
29
29
|
privilege.py
|
|
30
30
|
project.py
|
|
31
31
|
prompt_template.py
|
|
32
|
+
py.typed
|
|
32
33
|
pyproject.toml
|
|
33
34
|
repl_theme.py
|
|
34
35
|
repl_tracker.py
|
|
@@ -79,6 +80,7 @@ utils.py
|
|
|
79
80
|
./privilege.py
|
|
80
81
|
./project.py
|
|
81
82
|
./prompt_template.py
|
|
83
|
+
./py.typed
|
|
82
84
|
./repl_theme.py
|
|
83
85
|
./repl_tracker.py
|
|
84
86
|
./repl_ui.py
|
|
@@ -176,7 +176,7 @@ class AppConfig:
|
|
|
176
176
|
|
|
177
177
|
llm: LLMConfig = field(default_factory=LLMConfig)
|
|
178
178
|
agent: AgentConfig = field(default_factory=AgentConfig)
|
|
179
|
-
effort: str = field(default_factory=lambda: _from_settings("effort_level"
|
|
179
|
+
effort: str = field(default_factory=lambda: (_from_settings("effort_level") or "medium"))
|
|
180
180
|
|
|
181
181
|
@classmethod
|
|
182
182
|
def load(cls) -> "AppConfig":
|
|
@@ -231,6 +231,9 @@ def _from_settings(attr: str, default: Any = "") -> Any:
|
|
|
231
231
|
logger.debug("Settings property %r not found, using default %r", attr, default)
|
|
232
232
|
return default
|
|
233
233
|
except Exception:
|
|
234
|
+
# Catch-all for unexpected error types (e.g., OSError on corrupt file,
|
|
235
|
+
# TypeError from malformed data). These are logged at WARNING with full
|
|
236
|
+
# traceback so they don't go unnoticed, but the system stays running.
|
|
234
237
|
logger = logging.getLogger(__name__)
|
|
235
238
|
logger.warning(
|
|
236
239
|
"Failed to read settings.%s — using default %r. "
|
|
@@ -246,7 +249,12 @@ def _safe_temperature() -> float:
|
|
|
246
249
|
|
|
247
250
|
|
|
248
251
|
def _settings_api_key() -> str:
|
|
249
|
-
|
|
252
|
+
"""Resolve API key with tiered fallback (OS keychain → env vars → settings.json)."""
|
|
253
|
+
try:
|
|
254
|
+
from .settings import resolve_api_key
|
|
255
|
+
return resolve_api_key()
|
|
256
|
+
except ImportError:
|
|
257
|
+
return _from_settings("api_key", "")
|
|
250
258
|
|
|
251
259
|
|
|
252
260
|
def _settings_base_url() -> str:
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
|
|
3
|
+
Extension API — unified plugin system.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Extension
|
|
7
|
-
- ExtensionManager:
|
|
8
|
-
- @extension
|
|
9
|
-
- ExtensionPoint:
|
|
5
|
+
Provides:
|
|
6
|
+
- Extension base class: lifecycle hooks (load / unload / activate / deactivate)
|
|
7
|
+
- ExtensionManager: extension discovery, registration, activation, unloading
|
|
8
|
+
- @extension decorator: declarative registration
|
|
9
|
+
- ExtensionPoint: marker class for defining extension points
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Usage example:
|
|
12
12
|
|
|
13
13
|
from .extension import Extension, extension
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
def get_prompt(self) -> str:
|
|
22
22
|
return "You are an expert in..."
|
|
23
23
|
|
|
24
|
-
#
|
|
24
|
+
# Register with the global manager
|
|
25
25
|
from .extension import get_extension_manager
|
|
26
26
|
get_extension_manager().register(MySkill())
|
|
27
27
|
"""
|
|
@@ -88,17 +88,17 @@ class ExtensionType:
|
|
|
88
88
|
|
|
89
89
|
class ExtensionPoint:
|
|
90
90
|
"""
|
|
91
|
-
|
|
91
|
+
Marker for an extension point. Extensions can register callbacks by name.
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
Usage:
|
|
94
94
|
|
|
95
|
-
#
|
|
95
|
+
# Define an extension point
|
|
96
96
|
ON_SYSTEM_PROMPT = ExtensionPoint("system_prompt")
|
|
97
97
|
|
|
98
|
-
#
|
|
98
|
+
# Extension registers a callback
|
|
99
99
|
ON_SYSTEM_PROMPT.register(my_callable)
|
|
100
100
|
|
|
101
|
-
#
|
|
101
|
+
# Fire all registered callbacks
|
|
102
102
|
results = ON_SYSTEM_PROMPT.trigger(prompt="...")
|
|
103
103
|
"""
|
|
104
104
|
|
|
@@ -181,23 +181,23 @@ class ExtensionPoint:
|
|
|
181
181
|
|
|
182
182
|
class Extension(ABC):
|
|
183
183
|
"""
|
|
184
|
-
|
|
184
|
+
Extension base class. Base class for all ATA Coder extensions.
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
1. __init__() —
|
|
188
|
-
2. on_load(manager) —
|
|
189
|
-
3. on_activate() —
|
|
190
|
-
4. on_deactivate() —
|
|
191
|
-
5. on_unload() —
|
|
186
|
+
Lifecycle:
|
|
187
|
+
1. __init__() — instantiation
|
|
188
|
+
2. on_load(manager) — called when loaded by the manager
|
|
189
|
+
3. on_activate() — called when activated
|
|
190
|
+
4. on_deactivate() — called when deactivated
|
|
191
|
+
5. on_unload() — called when unloaded
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
Subclasses MUST set:
|
|
194
194
|
- meta: ExtensionMeta
|
|
195
195
|
|
|
196
|
-
|
|
196
|
+
Subclasses MAY override:
|
|
197
197
|
- on_load() / on_unload() / on_activate() / on_deactivate()
|
|
198
|
-
- get_tools() →
|
|
199
|
-
- get_prompt() →
|
|
200
|
-
- validate() →
|
|
198
|
+
- get_tools() → list of tool definitions
|
|
199
|
+
- get_prompt() → system prompt string
|
|
200
|
+
- validate() → verify extension is usable
|
|
201
201
|
"""
|
|
202
202
|
|
|
203
203
|
meta: ExtensionMeta
|
|
@@ -208,27 +208,27 @@ class Extension(ABC):
|
|
|
208
208
|
cls.meta = ExtensionMeta(name=cls.__name__)
|
|
209
209
|
|
|
210
210
|
def on_load(self, manager: "ExtensionManager") -> None:
|
|
211
|
-
"""
|
|
211
|
+
"""Extension was loaded into a manager."""
|
|
212
212
|
|
|
213
213
|
def on_unload(self) -> None:
|
|
214
|
-
"""
|
|
214
|
+
"""Extension is being unloaded."""
|
|
215
215
|
|
|
216
216
|
def on_activate(self) -> None:
|
|
217
|
-
"""
|
|
217
|
+
"""Extension was activated."""
|
|
218
218
|
|
|
219
219
|
def on_deactivate(self) -> None:
|
|
220
|
-
"""
|
|
220
|
+
"""Extension was deactivated."""
|
|
221
221
|
|
|
222
222
|
def get_tools(self) -> list[dict[str, Any]]:
|
|
223
|
-
"""
|
|
223
|
+
"""Return the tool definitions provided by this extension."""
|
|
224
224
|
return []
|
|
225
225
|
|
|
226
226
|
def get_prompt(self) -> str:
|
|
227
|
-
"""
|
|
227
|
+
"""Return the system prompt fragment provided by this extension."""
|
|
228
228
|
return ""
|
|
229
229
|
|
|
230
230
|
def get_middleware(self) -> list[Callable]:
|
|
231
|
-
"""
|
|
231
|
+
"""Return the middleware list provided by this extension."""
|
|
232
232
|
return []
|
|
233
233
|
|
|
234
234
|
def validate(self) -> tuple[bool, str]:
|
|
@@ -250,9 +250,9 @@ class Extension(ABC):
|
|
|
250
250
|
|
|
251
251
|
class ExtensionManager:
|
|
252
252
|
"""
|
|
253
|
-
|
|
253
|
+
Extension manager — discovers, loads, activates, and manages extensions.
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
Usage:
|
|
256
256
|
|
|
257
257
|
mgr = ExtensionManager()
|
|
258
258
|
mgr.discover("./extensions/")
|
|
@@ -587,9 +587,9 @@ def extension(
|
|
|
587
587
|
**kwargs: Any,
|
|
588
588
|
) -> Callable:
|
|
589
589
|
"""
|
|
590
|
-
|
|
590
|
+
Class decorator — declare an extension.
|
|
591
591
|
|
|
592
|
-
|
|
592
|
+
Usage:
|
|
593
593
|
|
|
594
594
|
@extension(name="my-skill", version="1.0.0",
|
|
595
595
|
tags=["skill"], priority=10)
|
|
@@ -641,7 +641,7 @@ _extension_manager: ExtensionManager | None = None
|
|
|
641
641
|
|
|
642
642
|
|
|
643
643
|
def get_extension_manager() -> ExtensionManager:
|
|
644
|
-
"""
|
|
644
|
+
"""Get the global ExtensionManager singleton."""
|
|
645
645
|
global _extension_manager
|
|
646
646
|
if _extension_manager is None:
|
|
647
647
|
_extension_manager = ExtensionManager()
|
|
@@ -649,6 +649,6 @@ def get_extension_manager() -> ExtensionManager:
|
|
|
649
649
|
|
|
650
650
|
|
|
651
651
|
def reset_extension_manager() -> None:
|
|
652
|
-
"""
|
|
652
|
+
"""Reset the global extension manager (mainly for testing)."""
|
|
653
653
|
global _extension_manager
|
|
654
654
|
_extension_manager = None
|
|
@@ -134,13 +134,15 @@ class GitWorkflow:
|
|
|
134
134
|
status.modified += 1
|
|
135
135
|
status.clean = (status.staged == 0 and status.modified == 0 and status.untracked == 0)
|
|
136
136
|
|
|
137
|
-
# Ahead/behind
|
|
138
|
-
|
|
139
|
-
if
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
# Ahead/behind — only if upstream is configured
|
|
138
|
+
has_upstream, _, _ = _run_git(["rev-parse", "--abbrev-ref", "@{u}"], self.cwd, timeout=10)
|
|
139
|
+
if has_upstream == 0:
|
|
140
|
+
_, ahead_str, _ = _run_git(["rev-list", "--count", "@{u}..HEAD"], self.cwd, timeout=10)
|
|
141
|
+
if ahead_str and ahead_str.isdigit():
|
|
142
|
+
status.ahead = int(ahead_str)
|
|
143
|
+
_, behind_str, _ = _run_git(["rev-list", "--count", "HEAD..@{u}"], self.cwd, timeout=10)
|
|
144
|
+
if behind_str and behind_str.isdigit():
|
|
145
|
+
status.behind = int(behind_str)
|
|
144
146
|
|
|
145
147
|
# Last commit
|
|
146
148
|
_, last, _ = _run_git(["log", "-1", "--format=%h %s"], self.cwd)
|
|
@@ -44,7 +44,7 @@ if sys.platform == 'win32':
|
|
|
44
44
|
_patched_init.__ata_patched__ = True
|
|
45
45
|
_sp.Popen.__init__ = _patched_init
|
|
46
46
|
|
|
47
|
-
__version__ = "2.4.
|
|
47
|
+
__version__ = "2.4.5"
|
|
48
48
|
|
|
49
49
|
import asyncio
|
|
50
50
|
import logging
|
|
@@ -122,17 +122,29 @@ def _init_subsystems(config, **kwargs) -> dict:
|
|
|
122
122
|
workspace = config.agent.workspace_dir
|
|
123
123
|
errors: list[str] = []
|
|
124
124
|
|
|
125
|
+
def _init_subsystem(name, factory, critical=True):
|
|
126
|
+
"""Initialize a single subsystem with consistent error handling.
|
|
127
|
+
|
|
128
|
+
Critical subsystems raise on failure; non-critical log a warning
|
|
129
|
+
and return None.
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
return factory()
|
|
133
|
+
except Exception as e:
|
|
134
|
+
if critical:
|
|
135
|
+
logger.exception("%s init failed", name)
|
|
136
|
+
errors.append(f" {name}: {e}")
|
|
137
|
+
else:
|
|
138
|
+
logger.warning("%s unavailable: %s", name, e)
|
|
139
|
+
return None
|
|
140
|
+
|
|
125
141
|
# ── Critical: agent cannot function without these ──────────────────
|
|
126
142
|
for name, factory in [
|
|
127
143
|
("skills", lambda: get_skill_manager(kwargs.get("skills_dir"))),
|
|
128
144
|
("memory", lambda: get_memory_store(kwargs.get("memory_dir"))),
|
|
129
145
|
("permissions", lambda: PermissionStore()),
|
|
130
146
|
]:
|
|
131
|
-
|
|
132
|
-
result[name] = factory()
|
|
133
|
-
except Exception as e:
|
|
134
|
-
logger.exception("%s init failed", name)
|
|
135
|
-
errors.append(f" {name}: {e}")
|
|
147
|
+
result[name] = _init_subsystem(name, factory, critical=True)
|
|
136
148
|
|
|
137
149
|
# ── Non-critical: nice-to-have, degrade gracefully ─────────────────
|
|
138
150
|
for name, factory in [
|
|
@@ -140,11 +152,7 @@ def _init_subsystems(config, **kwargs) -> dict:
|
|
|
140
152
|
("templates", lambda: _try_init_templates(kwargs.get("prompts_dir"))),
|
|
141
153
|
("project", lambda: ProjectDetector(workspace).detect()),
|
|
142
154
|
]:
|
|
143
|
-
|
|
144
|
-
result[name] = factory()
|
|
145
|
-
except Exception as e:
|
|
146
|
-
logger.warning("%s unavailable: %s", name, e)
|
|
147
|
-
result[name] = None
|
|
155
|
+
result[name] = _init_subsystem(name, factory, critical=False)
|
|
148
156
|
|
|
149
157
|
# MCP is special: only init if config provided
|
|
150
158
|
result["mcp"] = _try_init_mcp(kwargs.get("mcp_config"))
|
|
@@ -163,11 +171,20 @@ def _try_init_templates(prompts_dir: str | None):
|
|
|
163
171
|
return TemplateManager(prompts_dir)
|
|
164
172
|
|
|
165
173
|
|
|
166
|
-
def _try_init_mcp(
|
|
167
|
-
|
|
174
|
+
def _try_init_mcp(mcp_config_path: str | None):
|
|
175
|
+
"""Initialize MCP client from a JSON/YAML config file path.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
mcp_config_path: Path to an MCP configuration file (JSON or YAML).
|
|
179
|
+
If None or empty, MCP is not initialized.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
MCPClient instance or None.
|
|
183
|
+
"""
|
|
184
|
+
if not mcp_config_path:
|
|
168
185
|
return None
|
|
169
186
|
from .mcp_client import MCPClient, load_mcp_config
|
|
170
|
-
return MCPClient(load_mcp_config(
|
|
187
|
+
return MCPClient(load_mcp_config(mcp_config_path))
|
|
171
188
|
|
|
172
189
|
|
|
173
190
|
# ── Config override ─────────────────────────────────────────────────────
|
|
@@ -995,6 +995,25 @@ class MCPClient:
|
|
|
995
995
|
"""Register a callback for health check failures."""
|
|
996
996
|
self._on_health_fail = callback
|
|
997
997
|
|
|
998
|
+
async def reconnect_server(self, name: str) -> bool:
|
|
999
|
+
"""Attempt to reconnect a failed MCP server. Returns True on success."""
|
|
1000
|
+
conn = self._connections.get(name)
|
|
1001
|
+
if not conn:
|
|
1002
|
+
return False
|
|
1003
|
+
logger.info("[%s] Attempting reconnection...", name)
|
|
1004
|
+
try:
|
|
1005
|
+
await conn.stop()
|
|
1006
|
+
except Exception:
|
|
1007
|
+
pass
|
|
1008
|
+
try:
|
|
1009
|
+
await conn.start()
|
|
1010
|
+
self._register_server_tools(name, conn)
|
|
1011
|
+
logger.info("[%s] Reconnected successfully", name)
|
|
1012
|
+
return True
|
|
1013
|
+
except Exception as e:
|
|
1014
|
+
logger.warning("[%s] Reconnection failed: %s", name, e)
|
|
1015
|
+
return False
|
|
1016
|
+
|
|
998
1017
|
def start_health_monitor(self, interval: float = 60.0) -> None:
|
|
999
1018
|
"""Start periodic health checks (ping every N seconds)."""
|
|
1000
1019
|
if self._health_running:
|
|
@@ -1021,16 +1040,19 @@ class MCPClient:
|
|
|
1021
1040
|
break
|
|
1022
1041
|
for name, conn in list(self._connections.items()):
|
|
1023
1042
|
try:
|
|
1024
|
-
|
|
1025
|
-
|
|
1043
|
+
alive = await conn.ping(timeout=10)
|
|
1044
|
+
if not alive:
|
|
1045
|
+
logger.warning("[%s] Health check failed — attempting reconnect", name)
|
|
1026
1046
|
if self._on_health_fail:
|
|
1027
1047
|
self._on_health_fail(name)
|
|
1048
|
+
await self.reconnect_server(name)
|
|
1028
1049
|
except asyncio.CancelledError:
|
|
1029
1050
|
raise
|
|
1030
1051
|
except Exception as e:
|
|
1031
|
-
logger.warning("[%s] Health check error: %s", name, e)
|
|
1052
|
+
logger.warning("[%s] Health check error: %s — attempting reconnect", name, e)
|
|
1032
1053
|
if self._on_health_fail:
|
|
1033
1054
|
self._on_health_fail(name)
|
|
1055
|
+
await self.reconnect_server(name)
|
|
1034
1056
|
|
|
1035
1057
|
# ── Properties ──────────────────────────────────────────────────────────
|
|
1036
1058
|
|
ata_coder-2.4.5/py.typed
ADDED
|
File without changes
|