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.
- ata_coder/__init__.py +1 -0
- ata_coder/agent.py +874 -0
- ata_coder/agent_compact.py +190 -0
- ata_coder/agent_controller.py +218 -0
- ata_coder/agent_extension.py +69 -0
- ata_coder/agent_routing.py +105 -0
- ata_coder/agent_subsystems.py +72 -0
- ata_coder/agent_tools.py +318 -0
- ata_coder/agent_undo.py +63 -0
- ata_coder/anthropic_client.py +465 -0
- ata_coder/change_tracker.py +368 -0
- ata_coder/clawd_integration.py +574 -0
- ata_coder/commands/__init__.py +128 -0
- ata_coder/commands/_core.py +184 -0
- ata_coder/commands/_safety.py +95 -0
- ata_coder/commands/_settings.py +241 -0
- ata_coder/commands/_workflow.py +451 -0
- ata_coder/commands.py +974 -0
- ata_coder/config.py +257 -0
- ata_coder/core/__init__.py +35 -0
- ata_coder/core/events.py +73 -0
- ata_coder/core/queue.py +85 -0
- ata_coder/core/state.py +17 -0
- ata_coder/event_queue.py +5 -0
- ata_coder/extension.py +654 -0
- ata_coder/extensions/__init__.py +1 -0
- ata_coder/extensions/hello_skill.py +47 -0
- ata_coder/fool_proof.py +295 -0
- ata_coder/git_workflow.py +371 -0
- ata_coder/gui.py +511 -0
- ata_coder/llm_client.py +543 -0
- ata_coder/main.py +814 -0
- ata_coder/mcp_client.py +1095 -0
- ata_coder/memory.py +539 -0
- ata_coder/model_registry.py +134 -0
- ata_coder/model_router.py +105 -0
- ata_coder/permissions.py +274 -0
- ata_coder/privilege.py +464 -0
- ata_coder/project.py +273 -0
- ata_coder/prompt_template.py +423 -0
- ata_coder/prompts/auto-mode.md +7 -0
- ata_coder/prompts/coding-rules.md +40 -0
- ata_coder/prompts/execution-guardrails.md +14 -0
- ata_coder/prompts/memory-system.md +24 -0
- ata_coder/prompts/output-style.md +23 -0
- ata_coder/prompts/safety.md +17 -0
- ata_coder/prompts/slash-commands.md +24 -0
- ata_coder/prompts/sub-agents.md +38 -0
- ata_coder/prompts/system-reminders.md +17 -0
- ata_coder/prompts/system.md +105 -0
- ata_coder/prompts/tool-policy.md +46 -0
- ata_coder/repl_theme.py +99 -0
- ata_coder/repl_tracker.py +89 -0
- ata_coder/repl_ui.py +1214 -0
- ata_coder/safety_guard.py +434 -0
- ata_coder/self_correct.py +346 -0
- ata_coder/server.py +882 -0
- ata_coder/server_session.py +159 -0
- ata_coder/server_shell.py +129 -0
- ata_coder/session.py +431 -0
- ata_coder/settings.py +439 -0
- ata_coder/setup_wizard.py +136 -0
- ata_coder/skill_extension.py +92 -0
- ata_coder/skills/architect/SKILL.md +42 -0
- ata_coder/skills/code-reviewer/SKILL.md +37 -0
- ata_coder/skills/codecraft/SKILL.md +452 -0
- ata_coder/skills/debugger/SKILL.md +45 -0
- ata_coder/skills/doc-writer/SKILL.md +36 -0
- ata_coder/skills/general-coder/SKILL.md +76 -0
- ata_coder/skills/math-calculator/README.md +40 -0
- ata_coder/skills/math-calculator/SKILL.md +59 -0
- ata_coder/skills/math-calculator/handler.py +103 -0
- ata_coder/skills/math-calculator/prompts/system.md +8 -0
- ata_coder/skills/math-calculator/requirements.txt +2 -0
- ata_coder/skills/math-calculator/resources/constants.json +8 -0
- ata_coder/skills/math-calculator/tests/test_handler.py +53 -0
- ata_coder/skills/security-auditor/SKILL.md +40 -0
- ata_coder/skills/test-writer/SKILL.md +36 -0
- ata_coder/skills/weather-skill/README.md +45 -0
- ata_coder/skills/weather-skill/handler.py +76 -0
- ata_coder/skills/weather-skill/manifest.json +48 -0
- ata_coder/skills/weather-skill/prompts/system_prompt.txt +9 -0
- ata_coder/skills/weather-skill/prompts/user_prompt_template.txt +3 -0
- ata_coder/skills/weather-skill/requirements.txt +1 -0
- ata_coder/skills/weather-skill/resources/city_list.json +17 -0
- ata_coder/skills/weather-skill/resources/error_messages.json +7 -0
- ata_coder/skills/weather-skill/tests/test_handler.py +28 -0
- ata_coder/skills/weather-skill/weather_utils.py +50 -0
- ata_coder/skills.py +1014 -0
- ata_coder/sub_agent.py +273 -0
- ata_coder/sub_agent_manager.py +203 -0
- ata_coder/system_prompt_builder.py +146 -0
- ata_coder/task_planner.py +391 -0
- ata_coder/terminal.py +318 -0
- ata_coder/test_runner.py +219 -0
- ata_coder/thread_supervisor.py +195 -0
- ata_coder/tool_defs.py +335 -0
- ata_coder/tools/__init__.py +11 -0
- ata_coder/tools/definitions.py +335 -0
- ata_coder/tools/executor.py +1036 -0
- ata_coder/tools/result.py +26 -0
- ata_coder/tools/subagent.py +332 -0
- ata_coder/tools/web.py +361 -0
- ata_coder/tools.py +1576 -0
- ata_coder/types.py +92 -0
- ata_coder/utils.py +113 -0
- ata_coder/web/css/style.css +180 -0
- ata_coder/web/index.html +84 -0
- ata_coder/web/js/app.js +489 -0
- ata_coder/web/package-lock.json +25 -0
- ata_coder/web/package.json +10 -0
- ata_coder/web/tsconfig.json +13 -0
- ata_coder-2.4.2.dist-info/METADATA +799 -0
- ata_coder-2.4.2.dist-info/RECORD +118 -0
- ata_coder-2.4.2.dist-info/WHEEL +5 -0
- ata_coder-2.4.2.dist-info/entry_points.txt +2 -0
- ata_coder-2.4.2.dist-info/licenses/LICENSE +21 -0
- ata_coder-2.4.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Thread-safe session store for the API server."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
|
|
10
|
+
from .config import AppConfig
|
|
11
|
+
from .tools import ToolExecutor
|
|
12
|
+
from .agent import CoderAgent
|
|
13
|
+
from .agent_subsystems import AgentSubsystems
|
|
14
|
+
from .permissions import PermissionStore, PermissionMode
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SessionStore:
|
|
20
|
+
"""Thread-safe session store for the API server."""
|
|
21
|
+
|
|
22
|
+
# Auto-cleanup: sessions idle for this many seconds are evicted.
|
|
23
|
+
SESSION_TTL_SECONDS = 1800 # 30 minutes
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self._lock = threading.Lock()
|
|
27
|
+
self._sessions: dict[str, CoderAgent] = {}
|
|
28
|
+
self._metadata: dict[str, dict] = {}
|
|
29
|
+
|
|
30
|
+
def _evict_expired(self):
|
|
31
|
+
"""Remove sessions that have been idle longer than SESSION_TTL_SECONDS."""
|
|
32
|
+
now = time.time()
|
|
33
|
+
expired: list[str] = []
|
|
34
|
+
with self._lock:
|
|
35
|
+
for sid, meta in self._metadata.items():
|
|
36
|
+
created_str = meta.get("created", "")
|
|
37
|
+
last_active_str = meta.get("last_active", created_str)
|
|
38
|
+
try:
|
|
39
|
+
t = datetime.strptime(last_active_str, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc).timestamp()
|
|
40
|
+
except (ValueError, OverflowError):
|
|
41
|
+
t = 0 # unparseable — evict (conservative: assume very old)
|
|
42
|
+
if now - t > self.SESSION_TTL_SECONDS:
|
|
43
|
+
expired.append(sid)
|
|
44
|
+
for sid in expired:
|
|
45
|
+
agent = self._sessions.pop(sid, None)
|
|
46
|
+
if agent:
|
|
47
|
+
try:
|
|
48
|
+
asyncio.run(agent.shutdown())
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
self._metadata.pop(sid, None)
|
|
52
|
+
if expired:
|
|
53
|
+
logger.info("Evicted %d expired session(s)", len(expired))
|
|
54
|
+
|
|
55
|
+
def create(self, config: AppConfig, skill: str = "") -> tuple[str, CoderAgent]:
|
|
56
|
+
sid = str(uuid.uuid4())[:12]
|
|
57
|
+
|
|
58
|
+
# Guard: if a session with this ID already exists, clean it up first
|
|
59
|
+
with self._lock:
|
|
60
|
+
existing = self._sessions.pop(sid, None)
|
|
61
|
+
self._metadata.pop(sid, None)
|
|
62
|
+
if existing:
|
|
63
|
+
try:
|
|
64
|
+
asyncio.run(existing.shutdown())
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
tool_exec = ToolExecutor(config.agent)
|
|
69
|
+
|
|
70
|
+
# Build AgentSubsystems container (replaces loose kwargs)
|
|
71
|
+
perms = PermissionStore(config.agent.workspace_dir)
|
|
72
|
+
|
|
73
|
+
# API mode: default to allow (caller implements own auth)
|
|
74
|
+
if os.environ.get("ATA_CODER_ALLOW_ALL", "").lower() in ("1", "true", "yes"):
|
|
75
|
+
perms.set_category_rule("shell", PermissionMode.ALLOW)
|
|
76
|
+
perms.set_category_rule("write", PermissionMode.ALLOW)
|
|
77
|
+
logger.warning(
|
|
78
|
+
"⚠️ ATA_CODER_ALLOW_ALL=1 — ALL shell & write operations "
|
|
79
|
+
"will be silently allowed without permission prompts. "
|
|
80
|
+
"This is intended for headless/automated environments only."
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
perms.set_prompt_callback(lambda n, a, c: True)
|
|
84
|
+
|
|
85
|
+
from .skills import get_skill_manager
|
|
86
|
+
from .memory import get_memory_store
|
|
87
|
+
from .project import ProjectDetector
|
|
88
|
+
|
|
89
|
+
skill_mgr = get_skill_manager()
|
|
90
|
+
if skill:
|
|
91
|
+
skill_mgr.activate(skill)
|
|
92
|
+
|
|
93
|
+
subsystems = AgentSubsystems(
|
|
94
|
+
skills=skill_mgr,
|
|
95
|
+
memory=get_memory_store(),
|
|
96
|
+
permissions=perms,
|
|
97
|
+
project_info=ProjectDetector(config.agent.workspace_dir).detect(),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
agent = CoderAgent(
|
|
101
|
+
config=config,
|
|
102
|
+
tool_executor=tool_exec,
|
|
103
|
+
subsystems=subsystems,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
now_ts = time.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
107
|
+
with self._lock:
|
|
108
|
+
self._sessions[sid] = agent
|
|
109
|
+
self._metadata[sid] = {
|
|
110
|
+
"created": now_ts,
|
|
111
|
+
"last_active": now_ts,
|
|
112
|
+
"messages": 0,
|
|
113
|
+
"tool_calls": 0,
|
|
114
|
+
"skill": skill,
|
|
115
|
+
"model": config.llm.model,
|
|
116
|
+
}
|
|
117
|
+
return sid, agent
|
|
118
|
+
|
|
119
|
+
def get(self, sid: str) -> CoderAgent | None:
|
|
120
|
+
self._evict_expired()
|
|
121
|
+
with self._lock:
|
|
122
|
+
agent = self._sessions.get(sid)
|
|
123
|
+
if agent and sid in self._metadata:
|
|
124
|
+
self._metadata[sid]["last_active"] = time.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
125
|
+
return agent
|
|
126
|
+
|
|
127
|
+
def get_or_create(self, sid: str | None, config: AppConfig, skill: str = "") -> tuple[str, CoderAgent]:
|
|
128
|
+
if sid:
|
|
129
|
+
existing = self.get(sid)
|
|
130
|
+
if existing:
|
|
131
|
+
return sid, existing
|
|
132
|
+
return self.create(config, skill)
|
|
133
|
+
|
|
134
|
+
def update_meta(self, sid: str, **kwargs):
|
|
135
|
+
with self._lock:
|
|
136
|
+
if sid in self._metadata:
|
|
137
|
+
self._metadata[sid].update(kwargs)
|
|
138
|
+
|
|
139
|
+
def list_sessions(self) -> list[dict]:
|
|
140
|
+
with self._lock:
|
|
141
|
+
return [
|
|
142
|
+
{"session_id": sid, **meta}
|
|
143
|
+
for sid, meta in self._metadata.items()
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
def get_meta(self, sid: str) -> dict | None:
|
|
147
|
+
with self._lock:
|
|
148
|
+
return self._metadata.get(sid)
|
|
149
|
+
|
|
150
|
+
def delete(self, sid: str) -> bool:
|
|
151
|
+
with self._lock:
|
|
152
|
+
agent = self._sessions.pop(sid, None)
|
|
153
|
+
if agent:
|
|
154
|
+
try:
|
|
155
|
+
asyncio.run(agent.shutdown())
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
self._metadata.pop(sid, None)
|
|
159
|
+
return agent is not None
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Persistent PowerShell/bash sessions for interactive terminal in the API server."""
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import queue
|
|
5
|
+
import subprocess
|
|
6
|
+
import threading
|
|
7
|
+
import uuid
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
_shell_sessions: dict[str, tuple[subprocess.Popen, "queue.Queue[str]", threading.Lock, str]] = {}
|
|
12
|
+
_shell_lock = threading.Lock()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def shell_open(cwd: str, sid: str = "") -> str:
|
|
16
|
+
"""Start a persistent shell process. Returns session ID.
|
|
17
|
+
|
|
18
|
+
Uses PowerShell on Windows, bash on Linux/macOS.
|
|
19
|
+
"""
|
|
20
|
+
sid = sid or uuid.uuid4().hex[:10]
|
|
21
|
+
out_queue: "queue.Queue[str]" = queue.Queue()
|
|
22
|
+
|
|
23
|
+
if os.name == "nt":
|
|
24
|
+
# Windows: PowerShell with binary pipes to completely bypass
|
|
25
|
+
# the internal _readerthread (which ignores encoding and uses GBK).
|
|
26
|
+
# We create our own reader threads that decode UTF-8 manually.
|
|
27
|
+
proc = subprocess.Popen(
|
|
28
|
+
["powershell.exe", "-NoLogo", "-NoExit"],
|
|
29
|
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
30
|
+
stderr=subprocess.PIPE, cwd=cwd,
|
|
31
|
+
)
|
|
32
|
+
# Switch PS to UTF-8 output (must use bytes since stdin is binary)
|
|
33
|
+
proc.stdin.write("chcp 65001 >$null\n".encode("utf-8"))
|
|
34
|
+
proc.stdin.write("[Console]::OutputEncoding = [Text.Encoding]::UTF8\n".encode("utf-8"))
|
|
35
|
+
proc.stdin.flush()
|
|
36
|
+
prompt_text = "PS> "
|
|
37
|
+
else:
|
|
38
|
+
# Linux/macOS: bash with binary pipes
|
|
39
|
+
proc = subprocess.Popen(
|
|
40
|
+
["bash", "--norc"],
|
|
41
|
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
42
|
+
stderr=subprocess.PIPE, cwd=cwd,
|
|
43
|
+
)
|
|
44
|
+
prompt_text = "$ "
|
|
45
|
+
|
|
46
|
+
def _reader(pipe, label):
|
|
47
|
+
"""Read binary lines from a pipe, decode UTF-8, and enqueue."""
|
|
48
|
+
for line_bytes in pipe:
|
|
49
|
+
try:
|
|
50
|
+
out_queue.put(line_bytes.decode("utf-8", errors="replace"))
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
threading.Thread(target=_reader, args=(proc.stdout, "stdout"), daemon=True).start()
|
|
55
|
+
threading.Thread(target=_reader, args=(proc.stderr, "stderr"), daemon=True).start()
|
|
56
|
+
|
|
57
|
+
# Drain startup banner — wait for initial output then drain
|
|
58
|
+
try:
|
|
59
|
+
out_queue.get(timeout=3.0) # first line proves the process is alive
|
|
60
|
+
except queue.Empty:
|
|
61
|
+
pass # process produced no banner, proceed anyway
|
|
62
|
+
while not out_queue.empty():
|
|
63
|
+
try:
|
|
64
|
+
out_queue.get_nowait()
|
|
65
|
+
except queue.Empty:
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
with _shell_lock:
|
|
69
|
+
_shell_sessions[sid] = (proc, out_queue, threading.Lock(), prompt_text)
|
|
70
|
+
logger.info("Shell opened: %s", sid)
|
|
71
|
+
return sid
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def shell_ensure(sid: str, cwd: str):
|
|
75
|
+
"""Get or create a shell session."""
|
|
76
|
+
with _shell_lock:
|
|
77
|
+
if sid in _shell_sessions:
|
|
78
|
+
return _shell_sessions[sid]
|
|
79
|
+
# Auto-create with the requested SID
|
|
80
|
+
shell_open(cwd, sid=sid)
|
|
81
|
+
with _shell_lock:
|
|
82
|
+
return _shell_sessions.get(sid, (None, None, None, ""))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def shell_close(sid: str):
|
|
86
|
+
"""Close a single shell session, terminating the process and releasing pipes."""
|
|
87
|
+
with _shell_lock:
|
|
88
|
+
entry = _shell_sessions.pop(sid, None)
|
|
89
|
+
if entry:
|
|
90
|
+
proc, _, _, _ = entry
|
|
91
|
+
try:
|
|
92
|
+
proc.stdin.write(b"exit\n")
|
|
93
|
+
proc.stdin.flush()
|
|
94
|
+
proc.stdin.close()
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
try:
|
|
98
|
+
proc.wait(timeout=3)
|
|
99
|
+
except Exception:
|
|
100
|
+
try:
|
|
101
|
+
proc.kill()
|
|
102
|
+
proc.wait(timeout=1)
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
# Close remaining pipes to prevent Windows proactor "unclosed transport" warnings
|
|
106
|
+
if proc.stdout:
|
|
107
|
+
try:
|
|
108
|
+
proc.stdout.close()
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
if proc.stderr:
|
|
112
|
+
try:
|
|
113
|
+
proc.stderr.close()
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
logger.info("Shell closed: %s", sid)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def shell_close_all():
|
|
120
|
+
"""Close all active shell sessions. Call during server shutdown."""
|
|
121
|
+
with _shell_lock:
|
|
122
|
+
sids = list(_shell_sessions.keys())
|
|
123
|
+
for sid in sids:
|
|
124
|
+
shell_close(sid)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_shell_sessions():
|
|
128
|
+
"""Return the module-level shell sessions dict (for use by handler)."""
|
|
129
|
+
return _shell_sessions
|