ata-coder 2.4.4__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.4/ata_coder.egg-info → ata_coder-2.4.5}/PKG-INFO +7 -3
- {ata_coder-2.4.4 → ata_coder-2.4.5}/README.md +6 -2
- {ata_coder-2.4.4 → ata_coder-2.4.5}/agent.py +4 -9
- {ata_coder-2.4.4 → ata_coder-2.4.5/ata_coder.egg-info}/PKG-INFO +7 -3
- {ata_coder-2.4.4 → ata_coder-2.4.5}/ata_coder.egg-info/SOURCES.txt +2 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/config.py +3 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/extension.py +38 -38
- {ata_coder-2.4.4 → ata_coder-2.4.5}/main.py +31 -14
- ata_coder-2.4.5/py.typed +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/pyproject.toml +1 -1
- {ata_coder-2.4.4 → ata_coder-2.4.5}/server.py +84 -34
- {ata_coder-2.4.4 → ata_coder-2.4.5}/server_session.py +31 -1
- {ata_coder-2.4.4 → ata_coder-2.4.5}/setup_wizard.py +1 -1
- {ata_coder-2.4.4 → ata_coder-2.4.5}/sub_agent.py +3 -8
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_server.py +1 -1
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tools/executor.py +1 -1
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tools/web.py +60 -7
- {ata_coder-2.4.4 → ata_coder-2.4.5}/utils.py +13 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/LICENSE +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/MANIFEST.in +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/__init__.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/agent_compact.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/agent_controller.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/agent_extension.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/agent_routing.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/agent_subsystems.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/agent_tools.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/agent_undo.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/anthropic_client.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/ata_coder.egg-info/dependency_links.txt +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/ata_coder.egg-info/entry_points.txt +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/ata_coder.egg-info/requires.txt +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/ata_coder.egg-info/top_level.txt +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/change_tracker.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/clawd_integration.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/commands/__init__.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/commands/_core.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/commands/_safety.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/commands/_settings.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/commands/_workflow.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/core/__init__.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/core/events.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/core/queue.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/core/state.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/event_queue.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/extensions/__init__.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/extensions/hello_skill.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/fool_proof.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/git_workflow.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/gui.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/llm_client.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/mcp_client.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/memory.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/model_registry.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/model_router.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/permissions.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/privilege.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/project.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompt_template.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/auto-mode.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/coding-rules.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/execution-guardrails.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/memory-system.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/output-style.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/safety.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/slash-commands.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/sub-agents.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/system-reminders.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/system.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/prompts/tool-policy.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/repl_theme.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/repl_tracker.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/repl_ui.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/safety_guard.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/self_correct.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/server_shell.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/session.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/settings.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/setup.cfg +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skill_extension.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/architect/SKILL.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/code-reviewer/SKILL.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/codecraft/SKILL.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/debugger/SKILL.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/doc-writer/SKILL.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/general-coder/SKILL.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/math-calculator/README.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/math-calculator/SKILL.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/math-calculator/handler.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/math-calculator/prompts/system.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/math-calculator/requirements.txt +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/math-calculator/resources/constants.json +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/math-calculator/tests/test_handler.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/security-auditor/SKILL.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/test-writer/SKILL.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/README.md +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/handler.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/manifest.json +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/prompts/system_prompt.txt +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/prompts/user_prompt_template.txt +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/requirements.txt +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/resources/city_list.json +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/resources/error_messages.json +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/tests/test_handler.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills/weather-skill/weather_utils.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/skills.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/sub_agent_manager.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/system_prompt_builder.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/task_planner.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/terminal.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_agent.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_change_tracker.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_config.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_event_queue.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_extension.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_fibonacci.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_fool_proof.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_llm_client.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_memory.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_model_registry.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_permissions.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_privilege.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_prompt_template.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_safety_guard.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_skill_handlers.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_sub_agent.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tests/test_tools.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/token_counter.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tools/__init__.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tools/definitions.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tools/result.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tools/strategy.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/tools/subagent.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/types.py +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/web/css/style.css +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/web/index.html +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/web/js/app.js +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/web/package-lock.json +0 -0
- {ata_coder-2.4.4 → ata_coder-2.4.5}/web/package.json +0 -0
- {ata_coder-2.4.4 → 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
|
>
|
|
@@ -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
|
>
|
|
@@ -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
|
|
|
@@ -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
|
>
|
|
@@ -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
|
|
@@ -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. "
|
|
@@ -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
|
|
@@ -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 ─────────────────────────────────────────────────────
|
ata_coder-2.4.5/py.typed
ADDED
|
File without changes
|
|
@@ -47,37 +47,43 @@ class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
|
|
|
47
47
|
daemon_threads = True # threads exit when server shuts down
|
|
48
48
|
_max_threads: int = 64
|
|
49
49
|
|
|
50
|
+
def __init__(self, *args, **kwargs):
|
|
51
|
+
super().__init__(*args, **kwargs)
|
|
52
|
+
self._thread_lock = threading.Lock()
|
|
53
|
+
self._active_threads = 0
|
|
54
|
+
|
|
50
55
|
def process_request(self, request, client_address):
|
|
51
56
|
"""Override to enforce a maximum thread count."""
|
|
52
57
|
# ThreadingMixIn uses a thread per request; we cap the pool.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
request.sendall(CRLF.join([
|
|
59
|
-
b"HTTP/1.1 503 Service Unavailable",
|
|
60
|
-
b"Content-Type: application/json",
|
|
61
|
-
b"Connection: close",
|
|
62
|
-
b"Content-Length: 60",
|
|
63
|
-
b"",
|
|
64
|
-
b'{"error":"Server busy - too many concurrent requests"}',
|
|
65
|
-
b"",
|
|
66
|
-
]))
|
|
67
|
-
except Exception:
|
|
68
|
-
pass
|
|
69
|
-
finally:
|
|
58
|
+
with self._thread_lock:
|
|
59
|
+
if self._active_threads >= self._max_threads:
|
|
60
|
+
# Too many active threads — reject with 503
|
|
61
|
+
CRLF = b"\r\n"
|
|
70
62
|
try:
|
|
71
|
-
request.
|
|
63
|
+
request.sendall(CRLF.join([
|
|
64
|
+
b"HTTP/1.1 503 Service Unavailable",
|
|
65
|
+
b"Content-Type: application/json",
|
|
66
|
+
b"Connection: close",
|
|
67
|
+
b"Content-Length: 60",
|
|
68
|
+
b"",
|
|
69
|
+
b'{"error":"Server busy - too many concurrent requests"}',
|
|
70
|
+
b"",
|
|
71
|
+
]))
|
|
72
72
|
except Exception:
|
|
73
73
|
pass
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
finally:
|
|
75
|
+
try:
|
|
76
|
+
request.close()
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
return
|
|
80
|
+
# Atomic read-modify-write under lock (was: bare GIL-only read+write)
|
|
81
|
+
self._active_threads += 1
|
|
77
82
|
try:
|
|
78
83
|
super().process_request(request, client_address)
|
|
79
84
|
finally:
|
|
80
|
-
self.
|
|
85
|
+
with self._thread_lock:
|
|
86
|
+
self._active_threads = max(0, self._active_threads - 1)
|
|
81
87
|
from pathlib import Path
|
|
82
88
|
from typing import Any
|
|
83
89
|
from urllib.parse import urlparse
|
|
@@ -109,9 +115,11 @@ class AgentAPIHandler(BaseHTTPRequestHandler):
|
|
|
109
115
|
before the server starts accepting requests.
|
|
110
116
|
"""
|
|
111
117
|
|
|
112
|
-
# Class-level references (set by server factory)
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
# Class-level references (set by server factory before accepting requests).
|
|
119
|
+
# These are None until create_server() assigns them. mypy note: declared as
|
|
120
|
+
# Optional because they start as None; asserted non-None at usage sites.
|
|
121
|
+
config: "AppConfig | None" = None
|
|
122
|
+
store: "SessionStore | None" = None
|
|
115
123
|
_ws_lock: threading.Lock = threading.Lock() # protects workspace dir reads/writes
|
|
116
124
|
|
|
117
125
|
# ── Rate limiting (class-level, shared across handler instances) ──────
|
|
@@ -122,6 +130,25 @@ class AgentAPIHandler(BaseHTTPRequestHandler):
|
|
|
122
130
|
_RATE_WINDOW_S = 60.0 # sliding window in seconds
|
|
123
131
|
_RATE_BLOCK_S = 300.0 # block duration after exceeding penalty threshold
|
|
124
132
|
_RATE_PENALTY_MULTIPLIER = 3 # requests × this = block threshold
|
|
133
|
+
_RATE_CLEANUP_INTERVAL = 1000 # amortized: trigger cleanup every N calls
|
|
134
|
+
_rate_cleanup_counter: int = 0
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def _cleanup_rate_buckets(cls, now: float) -> None:
|
|
138
|
+
"""Remove stale IP entries whose last activity exceeds 2× the window.
|
|
139
|
+
|
|
140
|
+
Without this, IPs that stay under the rate limit forever accumulate
|
|
141
|
+
in _rate_buckets and leak memory over long-running server processes.
|
|
142
|
+
"""
|
|
143
|
+
cutoff = now - cls._RATE_WINDOW_S * 2
|
|
144
|
+
stale = [
|
|
145
|
+
ip for ip, dq in cls._rate_buckets.items()
|
|
146
|
+
if not dq or dq[-1] <= cutoff
|
|
147
|
+
]
|
|
148
|
+
for ip in stale:
|
|
149
|
+
del cls._rate_buckets[ip]
|
|
150
|
+
if stale:
|
|
151
|
+
logger.debug("Rate limiter: pruned %d stale IP bucket(s)", len(stale))
|
|
125
152
|
|
|
126
153
|
@classmethod
|
|
127
154
|
def _check_rate_limit(cls, client_ip: str) -> bool:
|
|
@@ -131,6 +158,12 @@ class AgentAPIHandler(BaseHTTPRequestHandler):
|
|
|
131
158
|
"""
|
|
132
159
|
now = time.time()
|
|
133
160
|
with cls._rate_lock:
|
|
161
|
+
# Periodic stale-bucket cleanup (amortized — triggered every N calls)
|
|
162
|
+
cls._rate_cleanup_counter += 1
|
|
163
|
+
if cls._rate_cleanup_counter >= cls._RATE_CLEANUP_INTERVAL:
|
|
164
|
+
cls._rate_cleanup_counter = 0
|
|
165
|
+
cls._cleanup_rate_buckets(now)
|
|
166
|
+
|
|
134
167
|
# Check if IP is currently blocked (penalty tier)
|
|
135
168
|
blocked_until = cls._rate_blocked.get(client_ip, 0)
|
|
136
169
|
if now < blocked_until:
|
|
@@ -184,15 +217,21 @@ class AgentAPIHandler(BaseHTTPRequestHandler):
|
|
|
184
217
|
"""
|
|
185
218
|
expected = os.environ.get("ATA_CODER_API_TOKEN", "")
|
|
186
219
|
if not expected:
|
|
187
|
-
# No token configured —
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
220
|
+
# No token configured — require explicit --no-auth flag to allow
|
|
221
|
+
# localhost access without authentication. Otherwise, generate a
|
|
222
|
+
# random token so even localhost requires proof of access.
|
|
223
|
+
if os.environ.get("ATA_CODER_NO_AUTH", "").lower() in ("1", "true", "yes"):
|
|
224
|
+
client_host = self.client_address[0]
|
|
225
|
+
if client_host in ("127.0.0.1", "::1", "localhost"):
|
|
226
|
+
return True
|
|
227
|
+
logger.warning(
|
|
228
|
+
"Remote request from %s rejected: ATA_CODER_NO_AUTH only "
|
|
229
|
+
"allows localhost. Set ATA_CODER_API_TOKEN for remote access.",
|
|
230
|
+
client_host,
|
|
231
|
+
)
|
|
232
|
+
return False
|
|
233
|
+
# Secure default: even localhost must present the auto-generated token
|
|
234
|
+
# (printed once at server startup via create_server)
|
|
196
235
|
return False
|
|
197
236
|
token = (self.headers.get("Authorization", "")
|
|
198
237
|
.removeprefix("Bearer ").strip())
|
|
@@ -954,6 +993,17 @@ def create_server(
|
|
|
954
993
|
|
|
955
994
|
config = config or get_config()
|
|
956
995
|
|
|
996
|
+
# Auto-generate API token if none configured (secure default)
|
|
997
|
+
if not os.environ.get("ATA_CODER_API_TOKEN") and os.environ.get("ATA_CODER_NO_AUTH", "").lower() not in ("1", "true", "yes"):
|
|
998
|
+
auto_token = secrets.token_urlsafe(24)
|
|
999
|
+
os.environ["ATA_CODER_API_TOKEN"] = auto_token
|
|
1000
|
+
logger.info(
|
|
1001
|
+
"🔐 No ATA_CODER_API_TOKEN set — auto-generated: %s\n"
|
|
1002
|
+
" Pass this token as 'Authorization: Bearer %s' header.\n"
|
|
1003
|
+
" Set ATA_CODER_NO_AUTH=1 to disable auth for localhost-only use.",
|
|
1004
|
+
auto_token, auto_token,
|
|
1005
|
+
)
|
|
1006
|
+
|
|
957
1007
|
AgentAPIHandler.config = config
|
|
958
1008
|
AgentAPIHandler.store = SessionStore()
|
|
959
1009
|
|
|
@@ -113,11 +113,41 @@ class SessionStore:
|
|
|
113
113
|
if os.environ.get("ATA_CODER_ALLOW_ALL", "").lower() in ("1", "true", "yes"):
|
|
114
114
|
perms.set_category_rule("shell", PermissionMode.ALLOW)
|
|
115
115
|
perms.set_category_rule("write", PermissionMode.ALLOW)
|
|
116
|
-
logger
|
|
116
|
+
# Audit logger: records every allow-all decision for forensic trace
|
|
117
|
+
audit_logger = logging.getLogger("ata_coder.audit.allow_all")
|
|
118
|
+
audit_logger.setLevel(logging.WARNING)
|
|
119
|
+
audit_logger.warning(
|
|
117
120
|
"⚠️ ATA_CODER_ALLOW_ALL=1 — ALL shell & write operations "
|
|
118
121
|
"will be silently allowed without permission prompts. "
|
|
119
122
|
"This is intended for headless/automated environments only."
|
|
120
123
|
)
|
|
124
|
+
# Wrap the permission store to emit audit log entries for every
|
|
125
|
+
# check that would have required confirmation in interactive mode.
|
|
126
|
+
_orig_check = perms.check
|
|
127
|
+
def _audited_check(tool_name: str, arguments: dict | None = None) -> bool:
|
|
128
|
+
result = _orig_check(tool_name, arguments)
|
|
129
|
+
from .permissions import tool_category
|
|
130
|
+
category = tool_category(tool_name)
|
|
131
|
+
if result and category in ("shell", "write"):
|
|
132
|
+
audit_logger.info(
|
|
133
|
+
"ALLOW_ALL: %s | %s | %s",
|
|
134
|
+
category, tool_name, str(arguments or {})[:200]
|
|
135
|
+
)
|
|
136
|
+
# CRITICAL safety: hard-block destructive patterns even in allow-all mode.
|
|
137
|
+
# Patterns like rm -rf /, mkfs, dd writes, fork bombs are never auto-allowed.
|
|
138
|
+
if category == "shell" and result:
|
|
139
|
+
from .safety_guard import analyze_command, RiskLevel
|
|
140
|
+
cmd = str((arguments or {}).get("command", ""))
|
|
141
|
+
if cmd:
|
|
142
|
+
risk = analyze_command(cmd)
|
|
143
|
+
if risk.risk == RiskLevel.CRITICAL:
|
|
144
|
+
audit_logger.critical(
|
|
145
|
+
"ALLOW_ALL BLOCKED (CRITICAL): %s | %s",
|
|
146
|
+
tool_name, risk.description or cmd[:200]
|
|
147
|
+
)
|
|
148
|
+
return False
|
|
149
|
+
return result
|
|
150
|
+
perms.check = _audited_check
|
|
121
151
|
elif os.environ.get("ATA_CODER_API_TOKEN", ""):
|
|
122
152
|
# Token auth is configured — trust the caller's auth layer,
|
|
123
153
|
# but still require explicit allow-all for write/shell.
|
|
@@ -18,7 +18,7 @@ from dataclasses import dataclass, field
|
|
|
18
18
|
from typing import Callable, Optional
|
|
19
19
|
|
|
20
20
|
from .config import AppConfig, LLMConfig
|
|
21
|
-
|
|
21
|
+
# LLMClient/AnthropicClient created via create_llm_client() factory in utils.py
|
|
22
22
|
from .tools import ToolExecutor, TOOL_DEFINITIONS, ToolResult
|
|
23
23
|
|
|
24
24
|
logger = logging.getLogger(__name__)
|
|
@@ -69,13 +69,8 @@ class SubAgent:
|
|
|
69
69
|
thinking_strength=config.llm.thinking_strength,
|
|
70
70
|
use_anthropic=config.llm.use_anthropic,
|
|
71
71
|
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
self._llm = AnthropicClient(llm_config)
|
|
75
|
-
self._use_anthropic = True
|
|
76
|
-
else:
|
|
77
|
-
self._llm = LLMClient(llm_config)
|
|
78
|
-
self._use_anthropic = False
|
|
72
|
+
from .utils import create_llm_client
|
|
73
|
+
self._llm, self._use_anthropic = create_llm_client(llm_config)
|
|
79
74
|
|
|
80
75
|
# Independent tool executor
|
|
81
76
|
self._tools = ToolExecutor(config.agent)
|
|
@@ -39,7 +39,7 @@ class TestSessionStore:
|
|
|
39
39
|
store, config = store
|
|
40
40
|
sid, agent = store.create(config)
|
|
41
41
|
assert isinstance(sid, str)
|
|
42
|
-
assert len(sid) ==
|
|
42
|
+
assert len(sid) == 32 # uuid.uuid4().hex = 32 chars
|
|
43
43
|
assert agent is not None
|
|
44
44
|
|
|
45
45
|
def test_create_adds_to_list(self, store):
|
|
@@ -61,7 +61,7 @@ class ToolExecutor(WebToolsMixin, SubAgentToolsMixin):
|
|
|
61
61
|
self._edit_callback: Callable[[str, str], None] | None = None
|
|
62
62
|
# File read cache: path → (mtime, cached_at, content).
|
|
63
63
|
self._file_cache: dict[str, tuple[float, float, str]] = {}
|
|
64
|
-
self._file_cache_max_entries =
|
|
64
|
+
self._file_cache_max_entries = 200
|
|
65
65
|
self._FILE_CACHE_TTL = 30.0 # seconds before a cache entry is revalidated
|
|
66
66
|
self._cache_dir: Path | None = None
|
|
67
67
|
# Sub-agent manager (set by agent)
|