ata-coder 2.4.8__tar.gz → 2.4.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ata_coder-2.4.8/ata_coder.egg-info → ata_coder-2.4.9}/PKG-INFO +9 -5
- {ata_coder-2.4.8 → ata_coder-2.4.9}/README.md +7 -3
- {ata_coder-2.4.8 → ata_coder-2.4.9}/agent.py +0 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/anthropic_client.py +0 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9/ata_coder.egg-info}/PKG-INFO +9 -5
- {ata_coder-2.4.8 → ata_coder-2.4.9}/ata_coder.egg-info/SOURCES.txt +4 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/ata_coder.egg-info/requires.txt +1 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/change_tracker.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/llm_client.py +0 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/main.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompt_template.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/pyproject.toml +11 -5
- {ata_coder-2.4.8 → ata_coder-2.4.9}/server.py +4 -78
- ata_coder-2.4.9/server_rate_limit.py +89 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/settings.py +0 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/setup_wizard.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/math-calculator/handler.py +1 -2
- {ata_coder-2.4.8 → ata_coder-2.4.9}/sub_agent.py +0 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_change_tracker.py +1 -1
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_memory.py +2 -2
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_tools.py +1 -1
- ata_coder-2.4.9/tools/file_ops.py +183 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/LICENSE +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/MANIFEST.in +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/agent_compact.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/agent_controller.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/agent_extension.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/agent_routing.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/agent_subsystems.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/agent_tools.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/agent_undo.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/ata_coder.egg-info/dependency_links.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/ata_coder.egg-info/entry_points.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/ata_coder.egg-info/top_level.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/clawd_integration.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/commands/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/commands/_core.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/commands/_safety.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/commands/_settings.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/commands/_workflow.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/config.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/context_manager.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/core/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/core/events.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/core/queue.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/core/state.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/event_queue.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/extension.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/extensions/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/extensions/hello_skill.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/fool_proof.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/git_workflow.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/gui.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/mcp_client.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/memory.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/model_registry.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/model_router.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/permissions.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/privilege.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/project.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/auto-mode.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/coding-rules.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/execution-guardrails.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/memory-system.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/output-style.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/safety.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/slash-commands.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/sub-agents.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/system-reminders.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/system.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/prompts/tool-policy.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/py.typed +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/repl_theme.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/repl_tracker.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/repl_ui.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/safety_guard.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/self_correct.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/server_session.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/server_shell.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/session.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/setup.cfg +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skill_extension.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/architect/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/code-reviewer/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/codecraft/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/debugger/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/doc-writer/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/general-coder/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/math-calculator/README.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/math-calculator/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/math-calculator/prompts/system.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/math-calculator/requirements.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/math-calculator/resources/constants.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/math-calculator/tests/test_handler.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/security-auditor/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/test-writer/SKILL.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/README.md +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/handler.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/manifest.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/prompts/system_prompt.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/prompts/user_prompt_template.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/requirements.txt +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/resources/city_list.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/resources/error_messages.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/tests/test_handler.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills/weather-skill/weather_utils.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/skills.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/sub_agent_manager.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/system_prompt_builder.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/task_planner.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/terminal.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_agent.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_config.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_event_queue.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_extension.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_fibonacci.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_fool_proof.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_llm_client.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_model_registry.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_permissions.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_privilege.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_prompt_template.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_safety_guard.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_server.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_skill_handlers.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tests/test_sub_agent.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/token_counter.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tools/__init__.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tools/definitions.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tools/executor.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tools/result.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tools/strategy.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tools/subagent.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/tools/web.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/types.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/utils.py +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/web/css/style.css +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/web/index.html +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/web/js/app.js +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/web/package-lock.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/web/package.json +0 -0
- {ata_coder-2.4.8 → ata_coder-2.4.9}/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.9
|
|
4
4
|
Summary: ATA Coder — AI-powered coding assistant
|
|
5
5
|
Author: ATA Coder Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,7 +8,7 @@ Requires-Python: >=3.10
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: click>=8.0
|
|
11
|
-
Requires-Dist: httpx
|
|
11
|
+
Requires-Dist: httpx<1.0,>=0.27.0
|
|
12
12
|
Requires-Dist: colorama>=0.4.6
|
|
13
13
|
Requires-Dist: python-dotenv>=1.0.0
|
|
14
14
|
Requires-Dist: rich>=13.0.0
|
|
@@ -21,13 +21,15 @@ Requires-Dist: pytest-timeout>=2.0; extra == "dev"
|
|
|
21
21
|
Requires-Dist: tiktoken>=0.5.0; extra == "dev"
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
|
-
# ATA Coder v2.4.
|
|
24
|
+
# ATA Coder v2.4.8
|
|
25
25
|
|
|
26
26
|
**AI-powered coding assistant — async, AST-aware, single config file.**
|
|
27
27
|
|
|
28
28
|
[English](#english) | [中文](#中文)
|
|
29
29
|
|
|
30
|
-
> **v2.4.
|
|
30
|
+
> **v2.4.8** — 🤖 **Self-Bootstrapped Audit (Round 12)**: ATA Coder found 10 bugs in its own source — thread races, command injection, silent corruption. All self-found, all self-fixed.
|
|
31
|
+
>
|
|
32
|
+
> > **v2.4.7** — ⚡ **Context Memory Refactor**: O(1) token tracking, ContextManager, section-level prompt caching, pre-tokenized TF-IDF, LRU token cache. ~60% less overhead in the hot loop.
|
|
31
33
|
>
|
|
32
34
|
> > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
|
|
33
35
|
>
|
|
@@ -552,7 +554,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
|
|
|
552
554
|
|
|
553
555
|
## 中文
|
|
554
556
|
|
|
555
|
-
> **v2.4.
|
|
557
|
+
> **v2.4.8** — 🤖 **自举审计(第 12 轮)**: ATA Coder 审计自身源码发现 10 个 bug — 线程竞态、命令注入、静默数据损坏。全部自发现、自修复。
|
|
558
|
+
>
|
|
559
|
+
> > **v2.4.7** — ⚡ **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
|
|
556
560
|
>
|
|
557
561
|
> > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
|
|
558
562
|
>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# ATA Coder v2.4.
|
|
1
|
+
# ATA Coder v2.4.8
|
|
2
2
|
|
|
3
3
|
**AI-powered coding assistant — async, AST-aware, single config file.**
|
|
4
4
|
|
|
5
5
|
[English](#english) | [中文](#中文)
|
|
6
6
|
|
|
7
|
-
> **v2.4.
|
|
7
|
+
> **v2.4.8** — 🤖 **Self-Bootstrapped Audit (Round 12)**: ATA Coder found 10 bugs in its own source — thread races, command injection, silent corruption. All self-found, all self-fixed.
|
|
8
|
+
>
|
|
9
|
+
> > **v2.4.7** — ⚡ **Context Memory Refactor**: O(1) token tracking, ContextManager, section-level prompt caching, pre-tokenized TF-IDF, LRU token cache. ~60% less overhead in the hot loop.
|
|
8
10
|
>
|
|
9
11
|
> > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
|
|
10
12
|
>
|
|
@@ -529,7 +531,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
|
|
|
529
531
|
|
|
530
532
|
## 中文
|
|
531
533
|
|
|
532
|
-
> **v2.4.
|
|
534
|
+
> **v2.4.8** — 🤖 **自举审计(第 12 轮)**: ATA Coder 审计自身源码发现 10 个 bug — 线程竞态、命令注入、静默数据损坏。全部自发现、自修复。
|
|
535
|
+
>
|
|
536
|
+
> > **v2.4.7** — ⚡ **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
|
|
533
537
|
>
|
|
534
538
|
> > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
|
|
535
539
|
>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ata-coder
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.9
|
|
4
4
|
Summary: ATA Coder — AI-powered coding assistant
|
|
5
5
|
Author: ATA Coder Team
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,7 +8,7 @@ Requires-Python: >=3.10
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: click>=8.0
|
|
11
|
-
Requires-Dist: httpx
|
|
11
|
+
Requires-Dist: httpx<1.0,>=0.27.0
|
|
12
12
|
Requires-Dist: colorama>=0.4.6
|
|
13
13
|
Requires-Dist: python-dotenv>=1.0.0
|
|
14
14
|
Requires-Dist: rich>=13.0.0
|
|
@@ -21,13 +21,15 @@ Requires-Dist: pytest-timeout>=2.0; extra == "dev"
|
|
|
21
21
|
Requires-Dist: tiktoken>=0.5.0; extra == "dev"
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
|
-
# ATA Coder v2.4.
|
|
24
|
+
# ATA Coder v2.4.8
|
|
25
25
|
|
|
26
26
|
**AI-powered coding assistant — async, AST-aware, single config file.**
|
|
27
27
|
|
|
28
28
|
[English](#english) | [中文](#中文)
|
|
29
29
|
|
|
30
|
-
> **v2.4.
|
|
30
|
+
> **v2.4.8** — 🤖 **Self-Bootstrapped Audit (Round 12)**: ATA Coder found 10 bugs in its own source — thread races, command injection, silent corruption. All self-found, all self-fixed.
|
|
31
|
+
>
|
|
32
|
+
> > **v2.4.7** — ⚡ **Context Memory Refactor**: O(1) token tracking, ContextManager, section-level prompt caching, pre-tokenized TF-IDF, LRU token cache. ~60% less overhead in the hot loop.
|
|
31
33
|
>
|
|
32
34
|
> > **v2.4.6** — 🔐 **OS-Native Credential Store**: API key encrypted at rest via Windows DPAPI / macOS Keychain / Linux secret-tool. Auto-migrates plaintext keys. Zero dependencies.
|
|
33
35
|
>
|
|
@@ -552,7 +554,9 @@ All 6 findings in this release were discovered by **ATA Coder scanning its own s
|
|
|
552
554
|
|
|
553
555
|
## 中文
|
|
554
556
|
|
|
555
|
-
> **v2.4.
|
|
557
|
+
> **v2.4.8** — 🤖 **自举审计(第 12 轮)**: ATA Coder 审计自身源码发现 10 个 bug — 线程竞态、命令注入、静默数据损坏。全部自发现、自修复。
|
|
558
|
+
>
|
|
559
|
+
> > **v2.4.7** — ⚡ **上下文记忆重构**: O(1) Token 追踪、ContextManager、章节级提示缓存、预分词 TF-IDF、LRU Token 缓存。热路径开销降低约 60%。
|
|
556
560
|
>
|
|
557
561
|
> > **v2.4.6** — 🔐 **操作系统凭据存储**: API Key 通过 Windows DPAPI / macOS Keychain / Linux secret-tool 加密存储。自动迁移明文密钥。零依赖。
|
|
558
562
|
>
|
|
@@ -38,6 +38,7 @@ repl_ui.py
|
|
|
38
38
|
safety_guard.py
|
|
39
39
|
self_correct.py
|
|
40
40
|
server.py
|
|
41
|
+
server_rate_limit.py
|
|
41
42
|
server_session.py
|
|
42
43
|
server_shell.py
|
|
43
44
|
session.py
|
|
@@ -89,6 +90,7 @@ utils.py
|
|
|
89
90
|
./safety_guard.py
|
|
90
91
|
./self_correct.py
|
|
91
92
|
./server.py
|
|
93
|
+
./server_rate_limit.py
|
|
92
94
|
./server_session.py
|
|
93
95
|
./server_shell.py
|
|
94
96
|
./session.py
|
|
@@ -154,6 +156,7 @@ utils.py
|
|
|
154
156
|
./tools/__init__.py
|
|
155
157
|
./tools/definitions.py
|
|
156
158
|
./tools/executor.py
|
|
159
|
+
./tools/file_ops.py
|
|
157
160
|
./tools/result.py
|
|
158
161
|
./tools/strategy.py
|
|
159
162
|
./tools/subagent.py
|
|
@@ -238,6 +241,7 @@ tests/test_tools.py
|
|
|
238
241
|
tools/__init__.py
|
|
239
242
|
tools/definitions.py
|
|
240
243
|
tools/executor.py
|
|
244
|
+
tools/file_ops.py
|
|
241
245
|
tools/result.py
|
|
242
246
|
tools/strategy.py
|
|
243
247
|
tools/subagent.py
|
|
@@ -282,7 +282,6 @@ class LLMClient(BaseLLMClient):
|
|
|
282
282
|
body.pop("temperature", None)
|
|
283
283
|
|
|
284
284
|
# Retry loop for streaming (up to 2 retries for 429/5xx)
|
|
285
|
-
last_error = None
|
|
286
285
|
|
|
287
286
|
# Sanitize surrogates before JSON encoding (prevent UTF-8 encode crash)
|
|
288
287
|
from .utils import sanitize_surrogates
|
|
@@ -240,7 +240,7 @@ class PromptTemplate:
|
|
|
240
240
|
import re as _re
|
|
241
241
|
|
|
242
242
|
if_tag = _re.compile(r'\{\%\s*if\s+(.+?)\s*\%\}')
|
|
243
|
-
|
|
243
|
+
_re.compile(r'\{\%\s*endif\s*\%\}')
|
|
244
244
|
any_tag = _re.compile(r'\{\%\s*(?:if\s+.+?|endif)\s*\%\}')
|
|
245
245
|
|
|
246
246
|
def _eval_condition(cond: str) -> bool:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ata-coder"
|
|
7
|
-
version = "2.4.
|
|
7
|
+
version = "2.4.9"
|
|
8
8
|
description = "ATA Coder — AI-powered coding assistant"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -14,7 +14,7 @@ authors = [
|
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
16
|
"click>=8.0",
|
|
17
|
-
"httpx>=0.27.0",
|
|
17
|
+
"httpx>=0.27.0,<1.0",
|
|
18
18
|
"colorama>=0.4.6",
|
|
19
19
|
"python-dotenv>=1.0.0",
|
|
20
20
|
"rich>=13.0.0",
|
|
@@ -81,16 +81,22 @@ exclude = ["build", "dist", ".git", "__pycache__", ".pytest_cache", "web/node_mo
|
|
|
81
81
|
|
|
82
82
|
[tool.ruff.lint]
|
|
83
83
|
select = ["E", "F", "W"]
|
|
84
|
-
ignore = ["E501", "E402", "E701", "E722", "E741", "E731", "
|
|
84
|
+
ignore = ["E501", "E402", "E701", "E722", "E741", "E731", "F821"]
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint.per-file-ignores]
|
|
87
|
+
# repl_ui.py + terminal.py: rich/colorama imports are availability checks
|
|
88
|
+
"repl_ui.py" = ["F401"]
|
|
89
|
+
"terminal.py" = ["F401"]
|
|
90
|
+
# Skill handlers + tests: some imports are for dynamic loading or optional deps
|
|
91
|
+
"skills/*/handler.py" = ["F401"]
|
|
92
|
+
"tests/*.py" = ["F401", "F841"]
|
|
85
93
|
# E501: line too long (enforced by formatter instead)
|
|
86
94
|
# E402: imports after sys.path.insert (intentional in main/server/gui)
|
|
87
95
|
# E701: multiple statements on one line (compact error handling)
|
|
88
96
|
# E722: bare except (intentional for cleanup/shutdown paths)
|
|
89
97
|
# E741: ambiguous variable name (l for loop vars)
|
|
90
98
|
# E731: lambda assignment (trivial inline helpers, not exported)
|
|
91
|
-
# F401: unused import (optional deps guarded by try/except)
|
|
92
99
|
# F821: undefined name (optional deps guarded by try/except)
|
|
93
|
-
# F841: unused variable (test assertions via side effects)
|
|
94
100
|
|
|
95
101
|
[tool.mypy]
|
|
96
102
|
python_version = "3.10"
|
|
@@ -22,8 +22,6 @@ Usage:
|
|
|
22
22
|
python main.py --server # From main launcher
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
import asyncio
|
|
26
|
-
import collections
|
|
27
25
|
import json
|
|
28
26
|
import logging
|
|
29
27
|
import os
|
|
@@ -101,6 +99,7 @@ from .config import AppConfig, get_config
|
|
|
101
99
|
from .tools import TOOL_DEFINITIONS
|
|
102
100
|
from .server_session import SessionStore
|
|
103
101
|
from .server_shell import shell_open, shell_ensure, shell_close, shell_close_all, get_shell_sessions
|
|
102
|
+
from .server_rate_limit import RateLimiter
|
|
104
103
|
from .skills import get_skill_manager
|
|
105
104
|
from .utils import brief_args
|
|
106
105
|
|
|
@@ -112,11 +111,13 @@ logger = logging.getLogger(__name__)
|
|
|
112
111
|
# ══════════════════════════════════════════════════════════════════════# HTTP Request Handler
|
|
113
112
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
114
113
|
|
|
115
|
-
class AgentAPIHandler(BaseHTTPRequestHandler):
|
|
114
|
+
class AgentAPIHandler(RateLimiter, BaseHTTPRequestHandler):
|
|
116
115
|
"""HTTP handler for the ATA Coder API.
|
|
117
116
|
|
|
118
117
|
*config* and *store* are set as class attributes by :func:`create_server`
|
|
119
118
|
before the server starts accepting requests.
|
|
119
|
+
|
|
120
|
+
Rate limiting is inherited from :class:`RateLimiter`.
|
|
120
121
|
"""
|
|
121
122
|
|
|
122
123
|
# Class-level references (set by server factory before accepting requests).
|
|
@@ -126,80 +127,6 @@ class AgentAPIHandler(BaseHTTPRequestHandler):
|
|
|
126
127
|
store: "SessionStore | None" = None
|
|
127
128
|
_ws_lock: threading.Lock = threading.Lock() # protects workspace dir reads/writes
|
|
128
129
|
|
|
129
|
-
# ── Rate limiting (class-level, shared across handler instances) ──────
|
|
130
|
-
_rate_lock: threading.Lock = threading.Lock()
|
|
131
|
-
_rate_buckets: dict[str, "collections.deque[float]"] = {} # ip → deque of timestamps
|
|
132
|
-
_rate_blocked: dict[str, float] = {} # ip → block expiry timestamp
|
|
133
|
-
_RATE_MAX_REQUESTS = 120 # max requests per window
|
|
134
|
-
_RATE_WINDOW_S = 60.0 # sliding window in seconds
|
|
135
|
-
_RATE_BLOCK_S = 300.0 # block duration after exceeding penalty threshold
|
|
136
|
-
_RATE_PENALTY_MULTIPLIER = 3 # requests × this = block threshold
|
|
137
|
-
_RATE_CLEANUP_INTERVAL = 1000 # amortized: trigger cleanup every N calls
|
|
138
|
-
_rate_cleanup_counter: int = 0
|
|
139
|
-
|
|
140
|
-
@classmethod
|
|
141
|
-
def _cleanup_rate_buckets(cls, now: float) -> None:
|
|
142
|
-
"""Remove stale IP entries whose last activity exceeds 2× the window.
|
|
143
|
-
|
|
144
|
-
Without this, IPs that stay under the rate limit forever accumulate
|
|
145
|
-
in _rate_buckets and leak memory over long-running server processes.
|
|
146
|
-
"""
|
|
147
|
-
cutoff = now - cls._RATE_WINDOW_S * 2
|
|
148
|
-
stale = [
|
|
149
|
-
ip for ip, dq in cls._rate_buckets.items()
|
|
150
|
-
if not dq or dq[-1] <= cutoff
|
|
151
|
-
]
|
|
152
|
-
for ip in stale:
|
|
153
|
-
del cls._rate_buckets[ip]
|
|
154
|
-
if stale:
|
|
155
|
-
logger.debug("Rate limiter: pruned %d stale IP bucket(s)", len(stale))
|
|
156
|
-
|
|
157
|
-
@classmethod
|
|
158
|
-
def _check_rate_limit(cls, client_ip: str) -> bool:
|
|
159
|
-
"""Sliding-window rate limiter with deque for O(1) cleanup.
|
|
160
|
-
|
|
161
|
-
Returns True if request is allowed.
|
|
162
|
-
"""
|
|
163
|
-
now = time.time()
|
|
164
|
-
with cls._rate_lock:
|
|
165
|
-
# Periodic stale-bucket cleanup (amortized — triggered every N calls)
|
|
166
|
-
cls._rate_cleanup_counter += 1
|
|
167
|
-
if cls._rate_cleanup_counter >= cls._RATE_CLEANUP_INTERVAL:
|
|
168
|
-
cls._rate_cleanup_counter = 0
|
|
169
|
-
cls._cleanup_rate_buckets(now)
|
|
170
|
-
|
|
171
|
-
# Check if IP is currently blocked (penalty tier)
|
|
172
|
-
blocked_until = cls._rate_blocked.get(client_ip, 0)
|
|
173
|
-
if now < blocked_until:
|
|
174
|
-
return False
|
|
175
|
-
if now >= blocked_until and client_ip in cls._rate_blocked:
|
|
176
|
-
del cls._rate_blocked[client_ip]
|
|
177
|
-
|
|
178
|
-
dq = cls._rate_buckets.get(client_ip)
|
|
179
|
-
if dq is None:
|
|
180
|
-
dq = collections.deque()
|
|
181
|
-
cls._rate_buckets[client_ip] = dq
|
|
182
|
-
|
|
183
|
-
# Purge expired entries — O(1) per entry via popleft
|
|
184
|
-
cutoff = now - cls._RATE_WINDOW_S
|
|
185
|
-
while dq and dq[0] <= cutoff:
|
|
186
|
-
dq.popleft()
|
|
187
|
-
|
|
188
|
-
# Penalty tier: block if request count exceeds penalty threshold
|
|
189
|
-
penalty_limit = cls._RATE_MAX_REQUESTS * cls._RATE_PENALTY_MULTIPLIER
|
|
190
|
-
if len(dq) > penalty_limit:
|
|
191
|
-
cls._rate_blocked[client_ip] = now + cls._RATE_BLOCK_S
|
|
192
|
-
logger.warning("Rate limit BLOCK: %s for %ds (%d requests in window)",
|
|
193
|
-
client_ip, cls._RATE_BLOCK_S, len(dq))
|
|
194
|
-
return False
|
|
195
|
-
|
|
196
|
-
# Standard rate limit
|
|
197
|
-
if len(dq) >= cls._RATE_MAX_REQUESTS:
|
|
198
|
-
return False
|
|
199
|
-
|
|
200
|
-
dq.append(now)
|
|
201
|
-
return True
|
|
202
|
-
|
|
203
130
|
def __init__(self, *args, **kwargs):
|
|
204
131
|
# Per-instance copies for thread-safe access under ThreadingHTTPServer
|
|
205
132
|
self.config = self.__class__.config
|
|
@@ -469,7 +396,6 @@ class AgentAPIHandler(BaseHTTPRequestHandler):
|
|
|
469
396
|
except Exception:
|
|
470
397
|
logger.debug("Failed to fetch models from API, using cache", exc_info=True)
|
|
471
398
|
# Fallback: cached model list from settings or env
|
|
472
|
-
import os
|
|
473
399
|
from .settings import get_settings
|
|
474
400
|
cached = get_settings().get("env", "ATA_CODER_MODELS_CACHE", default="") or self.config.llm.model
|
|
475
401
|
models = [{"id": m.strip(), "owned_by": ""} for m in cached.split(",") if m.strip()]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Sliding-window rate limiter for the HTTP API server.
|
|
2
|
+
|
|
3
|
+
Extracted from server.py to reduce file size and isolate concerns.
|
|
4
|
+
Used by AgentAPIHandler as a class-level mixin.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import collections
|
|
8
|
+
import logging
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RateLimiter:
|
|
16
|
+
"""Sliding-window rate limiter with deque for O(1) expiry + penalty tier.
|
|
17
|
+
|
|
18
|
+
Usage (as class-level mixin on a BaseHTTPRequestHandler subclass):
|
|
19
|
+
class MyHandler(RateLimiter, BaseHTTPRequestHandler):
|
|
20
|
+
...
|
|
21
|
+
allowed = self._check_rate_limit(self.client_address[0])
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# ── Configuration ──────────────────────────────────────────────────────
|
|
25
|
+
_rate_lock: threading.Lock = threading.Lock()
|
|
26
|
+
_rate_buckets: dict[str, "collections.deque[float]"] = {} # ip → deque of timestamps
|
|
27
|
+
_rate_blocked: dict[str, float] = {} # ip → block expiry timestamp
|
|
28
|
+
_RATE_MAX_REQUESTS = 120 # max requests per window
|
|
29
|
+
_RATE_WINDOW_S = 60.0 # sliding window in seconds
|
|
30
|
+
_RATE_BLOCK_S = 300.0 # block duration after exceeding penalty threshold
|
|
31
|
+
_RATE_PENALTY_MULTIPLIER = 3 # requests × this = block threshold
|
|
32
|
+
_RATE_CLEANUP_INTERVAL = 1000 # amortized: trigger cleanup every N calls
|
|
33
|
+
_rate_cleanup_counter: int = 0
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def _cleanup_rate_buckets(cls, now: float) -> None:
|
|
37
|
+
"""Remove stale IP entries whose last activity exceeds 2× the window."""
|
|
38
|
+
cutoff = now - cls._RATE_WINDOW_S * 2
|
|
39
|
+
stale = [
|
|
40
|
+
ip for ip, dq in cls._rate_buckets.items()
|
|
41
|
+
if not dq or dq[-1] <= cutoff
|
|
42
|
+
]
|
|
43
|
+
for ip in stale:
|
|
44
|
+
del cls._rate_buckets[ip]
|
|
45
|
+
if stale:
|
|
46
|
+
logger.debug("Rate limiter: pruned %d stale IP bucket(s)", len(stale))
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def _check_rate_limit(cls, client_ip: str) -> bool:
|
|
50
|
+
"""Sliding-window rate limiter. Returns True if request is allowed."""
|
|
51
|
+
now = time.time()
|
|
52
|
+
with cls._rate_lock:
|
|
53
|
+
# Periodic stale-bucket cleanup (amortized)
|
|
54
|
+
cls._rate_cleanup_counter += 1
|
|
55
|
+
if cls._rate_cleanup_counter >= cls._RATE_CLEANUP_INTERVAL:
|
|
56
|
+
cls._rate_cleanup_counter = 0
|
|
57
|
+
cls._cleanup_rate_buckets(now)
|
|
58
|
+
|
|
59
|
+
# Check if IP is currently blocked (penalty tier)
|
|
60
|
+
blocked_until = cls._rate_blocked.get(client_ip, 0)
|
|
61
|
+
if now < blocked_until:
|
|
62
|
+
return False
|
|
63
|
+
if now >= blocked_until and client_ip in cls._rate_blocked:
|
|
64
|
+
del cls._rate_blocked[client_ip]
|
|
65
|
+
|
|
66
|
+
dq = cls._rate_buckets.get(client_ip)
|
|
67
|
+
if dq is None:
|
|
68
|
+
dq = collections.deque()
|
|
69
|
+
cls._rate_buckets[client_ip] = dq
|
|
70
|
+
|
|
71
|
+
# Purge expired entries — O(1) per entry via popleft
|
|
72
|
+
cutoff = now - cls._RATE_WINDOW_S
|
|
73
|
+
while dq and dq[0] <= cutoff:
|
|
74
|
+
dq.popleft()
|
|
75
|
+
|
|
76
|
+
# Penalty tier: block if request count exceeds penalty threshold
|
|
77
|
+
penalty_limit = cls._RATE_MAX_REQUESTS * cls._RATE_PENALTY_MULTIPLIER
|
|
78
|
+
if len(dq) > penalty_limit:
|
|
79
|
+
cls._rate_blocked[client_ip] = now + cls._RATE_BLOCK_S
|
|
80
|
+
logger.warning("Rate limit BLOCK: %s for %ds (%d requests in window)",
|
|
81
|
+
client_ip, cls._RATE_BLOCK_S, len(dq))
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
# Standard rate limit
|
|
85
|
+
if len(dq) >= cls._RATE_MAX_REQUESTS:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
dq.append(now)
|
|
89
|
+
return True
|
|
@@ -541,7 +541,6 @@ def _store_credential(service: str, account: str, secret: str) -> bool:
|
|
|
541
541
|
cred_file.write_text(result.stdout.strip(), encoding="utf-8")
|
|
542
542
|
# Restrictive permissions on the credential file
|
|
543
543
|
try:
|
|
544
|
-
import stat
|
|
545
544
|
cred_file.chmod(0o600)
|
|
546
545
|
except Exception:
|
|
547
546
|
pass
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import ast
|
|
5
5
|
import math
|
|
6
6
|
import operator
|
|
7
|
-
import re
|
|
8
7
|
from typing import Any
|
|
9
8
|
|
|
10
9
|
|
|
@@ -155,7 +154,7 @@ def run(input_data: dict[str, Any]) -> dict[str, Any]:
|
|
|
155
154
|
"""
|
|
156
155
|
expression = input_data.get("expression", "").strip()
|
|
157
156
|
precision = input_data.get("precision", 6)
|
|
158
|
-
|
|
157
|
+
input_data.get("format", "number")
|
|
159
158
|
|
|
160
159
|
# Validate
|
|
161
160
|
if not expression:
|
|
@@ -110,7 +110,7 @@ class TestChangeTracker:
|
|
|
110
110
|
|
|
111
111
|
def test_init_creates_backup_dir(self, tmp_path):
|
|
112
112
|
"""ChangeTracker should create backup directory."""
|
|
113
|
-
|
|
113
|
+
ChangeTracker("test-session", backup_dir=tmp_path / "backups")
|
|
114
114
|
assert (tmp_path / "backups").exists()
|
|
115
115
|
|
|
116
116
|
def test_capture_write_new_file(self, tmp_path):
|
|
@@ -144,13 +144,13 @@ class TestMemoryStoreInit:
|
|
|
144
144
|
def test_init_creates_memory_dir(self, tmp_path):
|
|
145
145
|
"""MemoryStore should create the memory directory on init."""
|
|
146
146
|
memory_dir = tmp_path / "ata_memories"
|
|
147
|
-
|
|
147
|
+
MemoryStore(memory_dir)
|
|
148
148
|
assert memory_dir.exists()
|
|
149
149
|
|
|
150
150
|
def test_init_creates_memory_index(self, tmp_path):
|
|
151
151
|
"""MemoryStore should create MEMORY.md index on init."""
|
|
152
152
|
memory_dir = tmp_path / "ata_memories"
|
|
153
|
-
|
|
153
|
+
MemoryStore(memory_dir)
|
|
154
154
|
index_file = memory_dir / "MEMORY.md"
|
|
155
155
|
assert index_file.exists()
|
|
156
156
|
|
|
@@ -147,7 +147,7 @@ class TestToolReadFile:
|
|
|
147
147
|
async def test_read_caches_file(self):
|
|
148
148
|
f = Path(self.tmp) / "cache_test.txt"
|
|
149
149
|
f.write_text("cached content\n", encoding="utf-8")
|
|
150
|
-
|
|
150
|
+
str(f.resolve())
|
|
151
151
|
# First read
|
|
152
152
|
r1 = await self.executor.execute("read_file", {"file_path": str(f)})
|
|
153
153
|
assert r1.success
|