ata-coder 2.4.2__py3-none-any.whl

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.
Files changed (118) hide show
  1. ata_coder/__init__.py +1 -0
  2. ata_coder/agent.py +874 -0
  3. ata_coder/agent_compact.py +190 -0
  4. ata_coder/agent_controller.py +218 -0
  5. ata_coder/agent_extension.py +69 -0
  6. ata_coder/agent_routing.py +105 -0
  7. ata_coder/agent_subsystems.py +72 -0
  8. ata_coder/agent_tools.py +318 -0
  9. ata_coder/agent_undo.py +63 -0
  10. ata_coder/anthropic_client.py +465 -0
  11. ata_coder/change_tracker.py +368 -0
  12. ata_coder/clawd_integration.py +574 -0
  13. ata_coder/commands/__init__.py +128 -0
  14. ata_coder/commands/_core.py +184 -0
  15. ata_coder/commands/_safety.py +95 -0
  16. ata_coder/commands/_settings.py +241 -0
  17. ata_coder/commands/_workflow.py +451 -0
  18. ata_coder/commands.py +974 -0
  19. ata_coder/config.py +257 -0
  20. ata_coder/core/__init__.py +35 -0
  21. ata_coder/core/events.py +73 -0
  22. ata_coder/core/queue.py +85 -0
  23. ata_coder/core/state.py +17 -0
  24. ata_coder/event_queue.py +5 -0
  25. ata_coder/extension.py +654 -0
  26. ata_coder/extensions/__init__.py +1 -0
  27. ata_coder/extensions/hello_skill.py +47 -0
  28. ata_coder/fool_proof.py +295 -0
  29. ata_coder/git_workflow.py +371 -0
  30. ata_coder/gui.py +511 -0
  31. ata_coder/llm_client.py +543 -0
  32. ata_coder/main.py +814 -0
  33. ata_coder/mcp_client.py +1095 -0
  34. ata_coder/memory.py +539 -0
  35. ata_coder/model_registry.py +134 -0
  36. ata_coder/model_router.py +105 -0
  37. ata_coder/permissions.py +274 -0
  38. ata_coder/privilege.py +464 -0
  39. ata_coder/project.py +273 -0
  40. ata_coder/prompt_template.py +423 -0
  41. ata_coder/prompts/auto-mode.md +7 -0
  42. ata_coder/prompts/coding-rules.md +40 -0
  43. ata_coder/prompts/execution-guardrails.md +14 -0
  44. ata_coder/prompts/memory-system.md +24 -0
  45. ata_coder/prompts/output-style.md +23 -0
  46. ata_coder/prompts/safety.md +17 -0
  47. ata_coder/prompts/slash-commands.md +24 -0
  48. ata_coder/prompts/sub-agents.md +38 -0
  49. ata_coder/prompts/system-reminders.md +17 -0
  50. ata_coder/prompts/system.md +105 -0
  51. ata_coder/prompts/tool-policy.md +46 -0
  52. ata_coder/repl_theme.py +99 -0
  53. ata_coder/repl_tracker.py +89 -0
  54. ata_coder/repl_ui.py +1214 -0
  55. ata_coder/safety_guard.py +434 -0
  56. ata_coder/self_correct.py +346 -0
  57. ata_coder/server.py +882 -0
  58. ata_coder/server_session.py +159 -0
  59. ata_coder/server_shell.py +129 -0
  60. ata_coder/session.py +431 -0
  61. ata_coder/settings.py +439 -0
  62. ata_coder/setup_wizard.py +136 -0
  63. ata_coder/skill_extension.py +92 -0
  64. ata_coder/skills/architect/SKILL.md +42 -0
  65. ata_coder/skills/code-reviewer/SKILL.md +37 -0
  66. ata_coder/skills/codecraft/SKILL.md +452 -0
  67. ata_coder/skills/debugger/SKILL.md +45 -0
  68. ata_coder/skills/doc-writer/SKILL.md +36 -0
  69. ata_coder/skills/general-coder/SKILL.md +76 -0
  70. ata_coder/skills/math-calculator/README.md +40 -0
  71. ata_coder/skills/math-calculator/SKILL.md +59 -0
  72. ata_coder/skills/math-calculator/handler.py +103 -0
  73. ata_coder/skills/math-calculator/prompts/system.md +8 -0
  74. ata_coder/skills/math-calculator/requirements.txt +2 -0
  75. ata_coder/skills/math-calculator/resources/constants.json +8 -0
  76. ata_coder/skills/math-calculator/tests/test_handler.py +53 -0
  77. ata_coder/skills/security-auditor/SKILL.md +40 -0
  78. ata_coder/skills/test-writer/SKILL.md +36 -0
  79. ata_coder/skills/weather-skill/README.md +45 -0
  80. ata_coder/skills/weather-skill/handler.py +76 -0
  81. ata_coder/skills/weather-skill/manifest.json +48 -0
  82. ata_coder/skills/weather-skill/prompts/system_prompt.txt +9 -0
  83. ata_coder/skills/weather-skill/prompts/user_prompt_template.txt +3 -0
  84. ata_coder/skills/weather-skill/requirements.txt +1 -0
  85. ata_coder/skills/weather-skill/resources/city_list.json +17 -0
  86. ata_coder/skills/weather-skill/resources/error_messages.json +7 -0
  87. ata_coder/skills/weather-skill/tests/test_handler.py +28 -0
  88. ata_coder/skills/weather-skill/weather_utils.py +50 -0
  89. ata_coder/skills.py +1014 -0
  90. ata_coder/sub_agent.py +273 -0
  91. ata_coder/sub_agent_manager.py +203 -0
  92. ata_coder/system_prompt_builder.py +146 -0
  93. ata_coder/task_planner.py +391 -0
  94. ata_coder/terminal.py +318 -0
  95. ata_coder/test_runner.py +219 -0
  96. ata_coder/thread_supervisor.py +195 -0
  97. ata_coder/tool_defs.py +335 -0
  98. ata_coder/tools/__init__.py +11 -0
  99. ata_coder/tools/definitions.py +335 -0
  100. ata_coder/tools/executor.py +1036 -0
  101. ata_coder/tools/result.py +26 -0
  102. ata_coder/tools/subagent.py +332 -0
  103. ata_coder/tools/web.py +361 -0
  104. ata_coder/tools.py +1576 -0
  105. ata_coder/types.py +92 -0
  106. ata_coder/utils.py +113 -0
  107. ata_coder/web/css/style.css +180 -0
  108. ata_coder/web/index.html +84 -0
  109. ata_coder/web/js/app.js +489 -0
  110. ata_coder/web/package-lock.json +25 -0
  111. ata_coder/web/package.json +10 -0
  112. ata_coder/web/tsconfig.json +13 -0
  113. ata_coder-2.4.2.dist-info/METADATA +799 -0
  114. ata_coder-2.4.2.dist-info/RECORD +118 -0
  115. ata_coder-2.4.2.dist-info/WHEEL +5 -0
  116. ata_coder-2.4.2.dist-info/entry_points.txt +2 -0
  117. ata_coder-2.4.2.dist-info/licenses/LICENSE +21 -0
  118. ata_coder-2.4.2.dist-info/top_level.txt +1 -0
ata_coder/config.py ADDED
@@ -0,0 +1,257 @@
1
+ """
2
+ Configuration management for ATA Coder.
3
+ Loads from environment variables with sensible defaults.
4
+ """
5
+
6
+ import logging
7
+ import threading
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ from dotenv import load_dotenv
15
+
16
+
17
+ def _find_project_root() -> Path:
18
+ """Find the project root directory."""
19
+ return Path(__file__).parent.resolve()
20
+
21
+
22
+ def _load_env():
23
+ """Load .env files: CWD first (lowest priority), then project root overrides."""
24
+ cwd_env = Path.cwd() / ".env"
25
+ project_env = _find_project_root() / ".env"
26
+ # Load CWD .env as baseline
27
+ if cwd_env.exists():
28
+ load_dotenv(cwd_env)
29
+ # Project-root .env takes precedence (overrides CWD values)
30
+ if project_env.exists() and project_env != cwd_env:
31
+ load_dotenv(project_env, override=True)
32
+
33
+
34
+ # Defer env loading to first config access, not import time
35
+ _env_loaded = False
36
+ _env_lock = threading.Lock()
37
+
38
+
39
+ def _ensure_env():
40
+ """Thread-safe lazy env loader."""
41
+ global _env_loaded
42
+ if _env_loaded:
43
+ return
44
+ with _env_lock:
45
+ if not _env_loaded:
46
+ _load_env()
47
+ _env_loaded = True
48
+
49
+
50
+ @dataclass
51
+ class LLMConfig:
52
+ """Configuration for the OpenAI-compatible LLM client."""
53
+
54
+ api_key: str = field(
55
+ default_factory=lambda: _settings_api_key()
56
+ )
57
+ base_url: str = field(
58
+ default_factory=lambda: _settings_base_url()
59
+ )
60
+ model: str = field(
61
+ default_factory=lambda: _settings_default_model()
62
+ )
63
+ temperature: float = field(
64
+ default_factory=lambda: _safe_temperature()
65
+ )
66
+ max_tokens: int = field(
67
+ default_factory=lambda: _settings_max_output_tokens()
68
+ )
69
+ thinking_strength: str = field(
70
+ default_factory=lambda: _settings_thinking_strength()
71
+ ) # "" = default, "off" = explicitly disabled, "low"|"medium"|"high"|"xhigh"|"max"
72
+ use_anthropic: bool = field(
73
+ default_factory=lambda: _from_settings("use_anthropic", False)
74
+ )
75
+
76
+ def __post_init__(self):
77
+ """Normalize model name: auto-strip [1m] suffix (DeepSeek Anthropic marker)."""
78
+ if self.model and self.model.endswith("[1m]"):
79
+ self.model = self.model[:-4].strip()
80
+ logger.info(
81
+ "Auto-stripped [1m] suffix from model: %s", self.model
82
+ )
83
+
84
+ def validate(self) -> list[str]:
85
+ """Validate configuration, returns list of errors."""
86
+ errors = []
87
+ if not self.api_key:
88
+ errors.append(
89
+ "OPENAI_API_KEY is not set. "
90
+ "Set it in .env file or as environment variable."
91
+ )
92
+ if not self.model:
93
+ errors.append("OPENAI_MODEL is not set.")
94
+ return errors
95
+
96
+
97
+ @dataclass
98
+ class AgentConfig:
99
+ """Configuration for the agent behavior."""
100
+
101
+ max_tool_calls: int = field(
102
+ default_factory=lambda: int(_from_settings("max_tool_calls", 999))
103
+ )
104
+ max_context_tokens: int = field(
105
+ default_factory=lambda: int(_from_settings("max_context_tokens", 1000000))
106
+ )
107
+ effective_context_tokens: int = field(
108
+ default_factory=lambda: int(_from_settings("effective_context_tokens", 200000))
109
+ )
110
+ max_message_output_chars: int = field(
111
+ default_factory=lambda: int(_from_settings("max_message_output_chars", 8000))
112
+ )
113
+ workspace_dir: str = field(
114
+ default_factory=lambda: _from_settings("workspace_dir", str(Path.cwd()))
115
+ )
116
+
117
+ # Extension & sub-agent settings
118
+ extension_dirs: list[str] = field(
119
+ default_factory=lambda: _from_settings("extension_dirs", [])
120
+ )
121
+ max_sub_agents: int = field(
122
+ default_factory=lambda: int(_from_settings("max_sub_agents", 5))
123
+ )
124
+ sub_agent_timeout: float = field(
125
+ default_factory=lambda: float(_from_settings("sub_agent_timeout", 300.0))
126
+ )
127
+
128
+ # Safety settings
129
+ allowed_commands: list[str] = field(
130
+ default_factory=lambda: [
131
+ "ls", "dir", "cat", "head", "tail", "wc", "find",
132
+ "git", "python", "python3", "node", "npm", "npx",
133
+ "pip", "poetry", "cargo", "go", "rustc", "javac", "java",
134
+ "make", "cmake", "gcc", "g++", "clang", "clang++",
135
+ "tsc", "eslint", "prettier", "pytest", "jest",
136
+ "mypy", "ruff", "black", "isort",
137
+ "echo", "mkdir", "touch", "cp", "mv", "rm",
138
+ "cd", # harmless — cwd is already workspace; cd in compound commands just navigates within one shell invocation
139
+ "grep", "rg", "fd", "tree",
140
+ "docker", "docker-compose",
141
+ "cowsay", "fortune", "date", "whoami", "pwd",
142
+ ]
143
+ )
144
+
145
+ # Blocked commands for safety
146
+ blocked_commands: list[str] = field(
147
+ default_factory=lambda: [
148
+ "rm -rf /",
149
+ "mkfs.",
150
+ "dd if=",
151
+ ":(){ :|:& };:", # fork bomb
152
+ "shutdown",
153
+ "reboot",
154
+ "chmod 777 /",
155
+ "> /dev/sda",
156
+ ]
157
+ )
158
+
159
+
160
+ @dataclass
161
+ class AppConfig:
162
+ """Top-level application configuration."""
163
+
164
+ llm: LLMConfig = field(default_factory=LLMConfig)
165
+ agent: AgentConfig = field(default_factory=AgentConfig)
166
+ effort: str = field(default_factory=lambda: _from_settings("effort_level", "medium"))
167
+
168
+ @classmethod
169
+ def load(cls) -> "AppConfig":
170
+ """Load and validate all configuration (errors logged by caller)."""
171
+ _ensure_env()
172
+ config = cls()
173
+ return config
174
+
175
+
176
+ # Module-level config — initialised lazily via get_config().
177
+ # Using a lazy pattern because AppConfig.load() references _from_settings()
178
+ # which is defined after the dataclass body in this module.
179
+ _config: AppConfig | None = None
180
+
181
+
182
+ def get_config() -> AppConfig:
183
+ """Return the module-level config singleton (lazy init on first call).
184
+
185
+ After the first call the config is cached. The codebase is
186
+ single-threaded by design so no lock is needed.
187
+ """
188
+ global _config
189
+ if _config is None:
190
+ _config = AppConfig.load()
191
+ return _config
192
+
193
+
194
+ # ── Single config resolution helper ────────────────────────────────────────
195
+ # All config values come from settings.json. CLI overrides (--model, --api-key)
196
+ # are applied later via _apply_config_overrides() in main.py.
197
+ # Environment variables are intentionally NOT read — settings.json is the
198
+ # single source of truth for predictable, reproducible configuration.
199
+
200
+
201
+ def _from_settings(attr: str, default: Any = "") -> Any:
202
+ """Read a config value from the Settings singleton.
203
+
204
+ Lazy import to avoid circular dependency with settings.py.
205
+ Always returns *default* when settings are unavailable (fail-safe),
206
+ but logs a warning for non-trivial failures so they don't go unnoticed.
207
+ """
208
+ try:
209
+ from .settings import get_settings
210
+ except ImportError:
211
+ return default
212
+ try:
213
+ settings = get_settings()
214
+ return getattr(settings, attr, default)
215
+ except AttributeError:
216
+ # settings object exists but attribute is missing — use default
217
+ logger = logging.getLogger(__name__)
218
+ logger.debug("Settings property %r not found, using default %r", attr, default)
219
+ return default
220
+ except Exception:
221
+ logger = logging.getLogger(__name__)
222
+ logger.warning(
223
+ "Failed to read settings.%s — using default %r. "
224
+ "Check your settings.json for corruption.", attr, default, exc_info=True
225
+ )
226
+ return default
227
+
228
+
229
+ def _safe_temperature() -> float:
230
+ """Read temperature from settings, ensuring it's a numeric type."""
231
+ val = _from_settings("temperature", 0.1)
232
+ return val if isinstance(val, (int, float)) else 0.1
233
+
234
+
235
+ def _settings_api_key() -> str:
236
+ return _from_settings("api_key", "")
237
+
238
+
239
+ def _settings_base_url() -> str:
240
+ return _from_settings("api_base_url", "https://api.deepseek.com")
241
+
242
+
243
+ def _settings_default_model() -> str:
244
+ return _from_settings("default_model", "deepseek-chat")
245
+
246
+
247
+ def _settings_max_output_tokens() -> int:
248
+ return _from_settings("max_output_tokens", 16384)
249
+
250
+
251
+ def _settings_thinking_strength() -> str:
252
+ return _from_settings("effort_level", "")
253
+
254
+
255
+ # Use get_config() to obtain the global AppConfig instance.
256
+ # Lazy-loading is handled inside get_config() itself.
257
+
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Core agent modules — split from agent.py for maintainability.
4
+
5
+ - events: AgentEvent dataclasses
6
+ - state: AgentState dataclass
7
+ """
8
+
9
+ from .events import (
10
+ AgentEvent,
11
+ TextDeltaEvent,
12
+ ToolCallEvent,
13
+ ToolResultEvent,
14
+ ToolStreamEvent,
15
+ ThinkingEvent,
16
+ ReasoningEvent,
17
+ SkillChangedEvent,
18
+ ErrorEvent,
19
+ CompleteEvent,
20
+ )
21
+ from .state import AgentState
22
+
23
+ __all__ = [
24
+ "AgentEvent",
25
+ "TextDeltaEvent",
26
+ "ToolCallEvent",
27
+ "ToolResultEvent",
28
+ "ToolStreamEvent",
29
+ "ThinkingEvent",
30
+ "ReasoningEvent",
31
+ "SkillChangedEvent",
32
+ "ErrorEvent",
33
+ "CompleteEvent",
34
+ "AgentState",
35
+ ]
@@ -0,0 +1,73 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Agent event types — produced during CoderAgent.run() and consumed by UI.
4
+ Extracted from agent.py to keep modules under 500 lines.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+
10
+ from ..tools import ToolResult
11
+
12
+
13
+ # ── Event types ──────────────────────────────────────────────────────────────
14
+
15
+ @dataclass
16
+ class AgentEvent:
17
+ """Base event."""
18
+ pass
19
+
20
+
21
+ @dataclass
22
+ class TextDeltaEvent(AgentEvent):
23
+ text: str
24
+
25
+
26
+ @dataclass
27
+ class ToolCallEvent(AgentEvent):
28
+ tool_name: str
29
+ arguments: dict[str, Any]
30
+ source: str = "builtin" # "builtin" or "mcp"
31
+
32
+
33
+ @dataclass
34
+ class ToolResultEvent(AgentEvent):
35
+ tool_name: str
36
+ result: ToolResult
37
+ source: str = "builtin"
38
+ arguments: dict[str, Any] = field(default_factory=dict)
39
+
40
+
41
+ @dataclass
42
+ class ThinkingEvent(AgentEvent):
43
+ pass
44
+
45
+
46
+ @dataclass
47
+ class ReasoningEvent(AgentEvent):
48
+ """The model is thinking/reasoning (DeepSeek R1/v4 thinking mode)."""
49
+ text: str
50
+
51
+
52
+ @dataclass
53
+ class SkillChangedEvent(AgentEvent):
54
+ skill_name: str
55
+
56
+
57
+ @dataclass
58
+ class ErrorEvent(AgentEvent):
59
+ error: str
60
+
61
+
62
+ @dataclass
63
+ class ToolStreamEvent(AgentEvent):
64
+ """Real-time streaming output chunk from a running tool (e.g. run_shell)."""
65
+ tool_name: str
66
+ chunk: str # incremental output text
67
+
68
+
69
+ @dataclass
70
+ class CompleteEvent(AgentEvent):
71
+ total_tool_calls: int
72
+ total_time: float
73
+ estimated_tokens: int = 0 # current conversation window size
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Async event queue for agent→UI communication — part of the core infrastructure.
3
+
4
+ Both agent and UI run as asyncio tasks on the same event loop.
5
+ asyncio.Queue provides coroutine-safe FIFO semantics without locks.
6
+ """
7
+ import asyncio
8
+ from typing import Any, Optional
9
+
10
+ logger = __import__("logging").getLogger(__name__)
11
+
12
+ __all__ = ["EventQueue"]
13
+
14
+
15
+ class EventQueue:
16
+ """Async event queue wrapping asyncio.Queue.
17
+
18
+ Usage:
19
+ eq = EventQueue()
20
+ # Agent task:
21
+ await eq.put(TextDeltaEvent("hello"))
22
+ # UI task:
23
+ async for event in eq.drain():
24
+ ui.on_event(event)
25
+ """
26
+
27
+ def __init__(self, maxsize: int = 0):
28
+ self._queue: asyncio.Queue = asyncio.Queue(maxsize=maxsize)
29
+ self._total_put = 0
30
+ self._total_get = 0
31
+
32
+ async def put(self, event: Any) -> None:
33
+ """Enqueue an event (coroutine-safe, called from agent task)."""
34
+ await self._queue.put(event)
35
+ self._total_put += 1
36
+
37
+ def put_nowait(self, event: Any) -> None:
38
+ """Enqueue an event without blocking."""
39
+ self._queue.put_nowait(event)
40
+ self._total_put += 1
41
+
42
+ async def get(self, timeout: Optional[float] = None) -> Optional[Any]:
43
+ """Get one event, blocking with optional timeout."""
44
+ try:
45
+ if timeout:
46
+ event = await asyncio.wait_for(self._queue.get(), timeout=timeout)
47
+ else:
48
+ event = await self._queue.get()
49
+ self._total_get += 1
50
+ return event
51
+ except asyncio.TimeoutError:
52
+ return None
53
+
54
+ async def drain(self) -> list[Any]:
55
+ """Get ALL pending events without blocking."""
56
+ events: list[Any] = []
57
+ while not self._queue.empty():
58
+ try:
59
+ events.append(self._queue.get_nowait())
60
+ except asyncio.QueueEmpty:
61
+ break
62
+ count = len(events)
63
+ self._total_get += count
64
+ return events
65
+
66
+ def count(self) -> int:
67
+ """Return number of pending events (exact — asyncio.Queue.qsize is precise)."""
68
+ return self._queue.qsize()
69
+
70
+ @property
71
+ def total_put(self) -> int:
72
+ return self._total_put
73
+
74
+ @property
75
+ def total_get(self) -> int:
76
+ return self._total_get
77
+
78
+ async def clear(self) -> None:
79
+ """Discard all pending events."""
80
+ while not self._queue.empty():
81
+ try:
82
+ self._queue.get_nowait()
83
+ except asyncio.QueueEmpty:
84
+ break
85
+ self._total_get = self._total_put
@@ -0,0 +1,17 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Agent state — holds conversation messages and metrics during a run.
4
+ Extracted from agent.py.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+
9
+ from ..types import Message
10
+
11
+
12
+ @dataclass
13
+ class AgentState:
14
+ """Mutable state tracked across a single agent run."""
15
+ messages: list[Message] = field(default_factory=list)
16
+ tool_call_count: int = 0
17
+ start_time: float = 0.0
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Backward-compat re-export — EventQueue lives in core.queue now."""
3
+ from .core.queue import EventQueue # noqa: F401
4
+
5
+ __all__ = ["EventQueue"]