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
@@ -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