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,391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task Planner — automatic decomposition of complex requests.
|
|
3
|
+
|
|
4
|
+
When the user says "Build a REST API with auth, tests, and docs",
|
|
5
|
+
the planner breaks it into ordered subtasks:
|
|
6
|
+
|
|
7
|
+
1. Set up project structure
|
|
8
|
+
2. Implement auth module
|
|
9
|
+
3. Create API endpoints
|
|
10
|
+
4. Write tests
|
|
11
|
+
5. Add documentation
|
|
12
|
+
|
|
13
|
+
Features:
|
|
14
|
+
- Automatic decomposition via LLM (or pattern-based fallback)
|
|
15
|
+
- Dependency ordering
|
|
16
|
+
- Progress tracking
|
|
17
|
+
- Parallel subtask marking
|
|
18
|
+
- Status: pending → in_progress → completed → failed
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import re
|
|
24
|
+
import time
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from enum import Enum
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
32
|
+
# Data model
|
|
33
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
34
|
+
|
|
35
|
+
class TaskStatus(Enum):
|
|
36
|
+
PENDING = "pending"
|
|
37
|
+
IN_PROGRESS = "in_progress"
|
|
38
|
+
COMPLETED = "completed"
|
|
39
|
+
FAILED = "failed"
|
|
40
|
+
SKIPPED = "skipped"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class SubTask:
|
|
45
|
+
id: int
|
|
46
|
+
subject: str
|
|
47
|
+
description: str = ""
|
|
48
|
+
status: TaskStatus = TaskStatus.PENDING
|
|
49
|
+
depends_on: list[int] = field(default_factory=list)
|
|
50
|
+
parallel_ok: bool = False # Can run in parallel with siblings
|
|
51
|
+
tool_count: int = 0
|
|
52
|
+
started_at: float = 0.0
|
|
53
|
+
completed_at: float = 0.0
|
|
54
|
+
error: str = ""
|
|
55
|
+
result_summary: str = ""
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def elapsed(self) -> float:
|
|
59
|
+
if self.started_at == 0:
|
|
60
|
+
return 0
|
|
61
|
+
end = self.completed_at if self.completed_at > 0 else time.time()
|
|
62
|
+
return end - self.started_at
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict:
|
|
65
|
+
return {
|
|
66
|
+
"id": self.id, "subject": self.subject,
|
|
67
|
+
"description": self.description, "status": self.status.value,
|
|
68
|
+
"depends_on": self.depends_on, "parallel_ok": self.parallel_ok,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class Plan:
|
|
74
|
+
task_id: str
|
|
75
|
+
title: str
|
|
76
|
+
subtasks: list[SubTask] = field(default_factory=list)
|
|
77
|
+
created_at: str = ""
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def completed(self) -> int:
|
|
81
|
+
return sum(1 for t in self.subtasks if t.status == TaskStatus.COMPLETED)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def total(self) -> int:
|
|
85
|
+
return len(self.subtasks)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def progress_pct(self) -> float:
|
|
89
|
+
if self.total == 0:
|
|
90
|
+
return 0
|
|
91
|
+
return (self.completed / self.total) * 100
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def current(self) -> SubTask | None:
|
|
95
|
+
"""Get the current in-progress task."""
|
|
96
|
+
for t in self.subtasks:
|
|
97
|
+
if t.status == TaskStatus.IN_PROGRESS:
|
|
98
|
+
return t
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
def next_pending(self) -> SubTask | None:
|
|
102
|
+
"""Get the next task that can be started (dependencies satisfied)."""
|
|
103
|
+
for t in self.subtasks:
|
|
104
|
+
if t.status != TaskStatus.PENDING:
|
|
105
|
+
continue
|
|
106
|
+
# Check dependencies
|
|
107
|
+
deps_met = all(
|
|
108
|
+
self._get(d).status == TaskStatus.COMPLETED
|
|
109
|
+
for d in t.depends_on
|
|
110
|
+
)
|
|
111
|
+
if deps_met:
|
|
112
|
+
return t
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def _get(self, task_id: int) -> SubTask | None:
|
|
116
|
+
for t in self.subtasks:
|
|
117
|
+
if t.id == task_id:
|
|
118
|
+
return t
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
def progress_bar(self, width: int = 30) -> str:
|
|
122
|
+
filled = int(self.progress_pct / 100 * width)
|
|
123
|
+
bar = "█" * filled + "░" * (width - filled)
|
|
124
|
+
return f"[{self.completed}/{self.total}] {bar} {self.progress_pct:.0f}%"
|
|
125
|
+
|
|
126
|
+
def to_prompt(self) -> str:
|
|
127
|
+
"""Format the plan as a section for the system prompt."""
|
|
128
|
+
lines = [f"## Task Plan: {self.title}"]
|
|
129
|
+
lines.append(f"Progress: {self.progress_bar()}")
|
|
130
|
+
lines.append("")
|
|
131
|
+
for t in self.subtasks:
|
|
132
|
+
icon = {
|
|
133
|
+
TaskStatus.PENDING: "⬜",
|
|
134
|
+
TaskStatus.IN_PROGRESS: "🔄",
|
|
135
|
+
TaskStatus.COMPLETED: "✅",
|
|
136
|
+
TaskStatus.FAILED: "❌",
|
|
137
|
+
TaskStatus.SKIPPED: "⏭️",
|
|
138
|
+
}.get(t.status, "❓")
|
|
139
|
+
deps = f" (depends on: {t.depends_on})" if t.depends_on else ""
|
|
140
|
+
lines.append(f"{icon} {t.subject}{deps}")
|
|
141
|
+
if t.status == TaskStatus.IN_PROGRESS:
|
|
142
|
+
lines.append(" → Currently working on this")
|
|
143
|
+
elif t.status == TaskStatus.FAILED and t.error:
|
|
144
|
+
lines.append(f" → Failed: {t.error[:100]}")
|
|
145
|
+
return "\n".join(lines)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
149
|
+
# Task Planner
|
|
150
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
151
|
+
|
|
152
|
+
class TaskPlanner:
|
|
153
|
+
"""
|
|
154
|
+
Decomposes complex user requests into ordered subtasks.
|
|
155
|
+
|
|
156
|
+
Uses pattern-based decomposition as the primary method
|
|
157
|
+
(LLM-based decomposition can be added as an enhancement).
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
# Keywords that signal complex multi-step tasks
|
|
161
|
+
COMPLEX_MARKERS = [
|
|
162
|
+
"build", "create", "implement", "add", "set up",
|
|
163
|
+
"and also", "with", "including", "that has", "which includes",
|
|
164
|
+
"full stack", "complete", "end-to-end", "entire",
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
def __init__(self):
|
|
168
|
+
self._current_plan: Plan | None = None
|
|
169
|
+
self._plan_history: list[Plan] = []
|
|
170
|
+
|
|
171
|
+
# ── Decomposition ───────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
def decompose(self, task: str, llm_client=None) -> Plan:
|
|
174
|
+
"""
|
|
175
|
+
Break a task into subtasks.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
task: The user's task description
|
|
179
|
+
llm_client: Optional LLM client for smart decomposition
|
|
180
|
+
|
|
181
|
+
Returns a Plan with ordered subtasks.
|
|
182
|
+
"""
|
|
183
|
+
plan_id = time.strftime("%Y%m%d-%H%M%S")
|
|
184
|
+
|
|
185
|
+
# If LLM is available, use it for smarter decomposition
|
|
186
|
+
if llm_client:
|
|
187
|
+
subtasks = self._llm_decompose(task, llm_client)
|
|
188
|
+
else:
|
|
189
|
+
subtasks = self._pattern_decompose(task)
|
|
190
|
+
|
|
191
|
+
plan = Plan(
|
|
192
|
+
task_id=plan_id,
|
|
193
|
+
title=task[:100],
|
|
194
|
+
subtasks=subtasks,
|
|
195
|
+
created_at=time.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
196
|
+
)
|
|
197
|
+
self._current_plan = plan
|
|
198
|
+
self._plan_history.append(plan)
|
|
199
|
+
return plan
|
|
200
|
+
|
|
201
|
+
def _llm_decompose(self, task: str, llm_client) -> list[SubTask]:
|
|
202
|
+
"""Use LLM to intelligently decompose a task."""
|
|
203
|
+
prompt = f"""Break this coding task into ordered subtasks. Return ONLY a JSON array.
|
|
204
|
+
|
|
205
|
+
Task: {task}
|
|
206
|
+
|
|
207
|
+
Rules:
|
|
208
|
+
- 3-7 subtasks
|
|
209
|
+
- Each has: subject (short), description (1 sentence), depends_on (list of IDs that must complete first), parallel_ok (true if can run alongside siblings with no deps)
|
|
210
|
+
- Order logically: setup → core → polish → test
|
|
211
|
+
- Dependencies: later tasks depend on earlier ones
|
|
212
|
+
|
|
213
|
+
Format:
|
|
214
|
+
[{{"subject":"...", "description":"...", "depends_on":[1], "parallel_ok":false}}, ...]"""
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
response = llm_client.simple_chat(prompt)
|
|
218
|
+
# Extract JSON array from response
|
|
219
|
+
match = re.search(r"\[.*\]", response, re.DOTALL)
|
|
220
|
+
if match:
|
|
221
|
+
data = json.loads(match.group())
|
|
222
|
+
subtasks = []
|
|
223
|
+
for i, item in enumerate(data, 1):
|
|
224
|
+
subtasks.append(SubTask(
|
|
225
|
+
id=i,
|
|
226
|
+
subject=item.get("subject", f"Step {i}"),
|
|
227
|
+
description=item.get("description", ""),
|
|
228
|
+
depends_on=item.get("depends_on", []),
|
|
229
|
+
parallel_ok=item.get("parallel_ok", False),
|
|
230
|
+
))
|
|
231
|
+
if subtasks:
|
|
232
|
+
return subtasks
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.warning("LLM decomposition failed: %s, using pattern fallback", e)
|
|
235
|
+
|
|
236
|
+
return self._pattern_decompose(task)
|
|
237
|
+
|
|
238
|
+
def _pattern_decompose(self, task: str) -> list[SubTask]:
|
|
239
|
+
"""Pattern-based task decomposition (no LLM required)."""
|
|
240
|
+
task_lower = task.lower()
|
|
241
|
+
subtasks = []
|
|
242
|
+
task_id = 1
|
|
243
|
+
|
|
244
|
+
# Detect common patterns
|
|
245
|
+
patterns = [
|
|
246
|
+
# (keywords in task, subtask subject, description, depends_on)
|
|
247
|
+
(["project", "structure", "setup", "init", "scaffold"],
|
|
248
|
+
"Set up project structure", "Initialize project structure and dependencies", []),
|
|
249
|
+
(["model", "schema", "database", "migration", "entity"],
|
|
250
|
+
"Define data models/schema", "Create database models, schemas, or entity definitions", [1]),
|
|
251
|
+
(["auth", "login", "authentication", "jwt", "oauth", "session"],
|
|
252
|
+
"Implement authentication", "Add user authentication and authorization", [1]),
|
|
253
|
+
(["api", "endpoint", "route", "controller", "handler", "rest"],
|
|
254
|
+
"Create API endpoints", "Implement REST API routes and handlers", [1]),
|
|
255
|
+
(["service", "business logic", "domain"],
|
|
256
|
+
"Implement business logic", "Core service/business logic layer", [2]),
|
|
257
|
+
(["ui", "frontend", "component", "page", "view", "template"],
|
|
258
|
+
"Build UI components", "Create frontend components and views", [1]),
|
|
259
|
+
(["test", "spec", "unit test", "integration test"],
|
|
260
|
+
"Write tests", "Add unit and integration tests", []),
|
|
261
|
+
(["doc", "readme", "documentation", "comment"],
|
|
262
|
+
"Add documentation", "Write documentation and code comments", []),
|
|
263
|
+
(["deploy", "docker", "ci/cd", "pipeline"],
|
|
264
|
+
"Configure deployment", "Set up deployment and CI/CD pipeline", [1]),
|
|
265
|
+
(["error handling", "validation", "logging"],
|
|
266
|
+
"Add error handling & validation", "Implement error handling, input validation, and logging", [1]),
|
|
267
|
+
(["config", "environment", "settings", ".env"],
|
|
268
|
+
"Configure environment", "Set up configuration and environment variables", [1]),
|
|
269
|
+
(["refactor", "clean up", "optimize"],
|
|
270
|
+
"Refactor and optimize", "Clean up code and optimize performance", [1]),
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
matched_ids = set()
|
|
274
|
+
for keywords, subject, desc, deps in patterns:
|
|
275
|
+
if any(kw in task_lower for kw in keywords):
|
|
276
|
+
# Adjust dependency IDs
|
|
277
|
+
adjusted_deps = [d for d in deps if d in matched_ids] if deps else []
|
|
278
|
+
if not deps and task_id > 1:
|
|
279
|
+
adjusted_deps = [task_id - 1] # sequential by default
|
|
280
|
+
|
|
281
|
+
subtasks.append(SubTask(
|
|
282
|
+
id=task_id, subject=subject, description=desc,
|
|
283
|
+
depends_on=adjusted_deps,
|
|
284
|
+
))
|
|
285
|
+
matched_ids.add(task_id)
|
|
286
|
+
task_id += 1
|
|
287
|
+
|
|
288
|
+
# If no patterns matched or only 1 matched, create generic decomposition
|
|
289
|
+
if len(subtasks) < 2:
|
|
290
|
+
if any(m in task_lower for m in ["and", ",", "also", "then", "after"]):
|
|
291
|
+
# Try to split on natural language breaks
|
|
292
|
+
parts = re.split(r",\s*(?:and\s+|then\s+|also\s+)?", task)
|
|
293
|
+
parts = [p.strip() for p in parts if len(p.strip()) > 10]
|
|
294
|
+
for i, part in enumerate(parts[:6], 1):
|
|
295
|
+
deps = [i - 1] if i > 1 else []
|
|
296
|
+
subtasks.append(SubTask(
|
|
297
|
+
id=i, subject=part[:80],
|
|
298
|
+
depends_on=deps,
|
|
299
|
+
))
|
|
300
|
+
else:
|
|
301
|
+
# Simple sequential breakdown
|
|
302
|
+
subtasks = [
|
|
303
|
+
SubTask(id=1, subject="Analyze requirements", description="Understand what needs to be done"),
|
|
304
|
+
SubTask(id=2, subject="Implement solution", description="Write the code", depends_on=[1]),
|
|
305
|
+
SubTask(id=3, subject="Test and verify", description="Run tests and verify correctness", depends_on=[2]),
|
|
306
|
+
]
|
|
307
|
+
|
|
308
|
+
return subtasks
|
|
309
|
+
|
|
310
|
+
# ── Plan management ─────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def current_plan(self) -> Plan | None:
|
|
314
|
+
return self._current_plan
|
|
315
|
+
|
|
316
|
+
def start_task(self, task_id: int) -> SubTask | None:
|
|
317
|
+
"""Mark a task as in-progress."""
|
|
318
|
+
if not self._current_plan:
|
|
319
|
+
return None
|
|
320
|
+
for t in self._current_plan.subtasks:
|
|
321
|
+
if t.id == task_id and t.status == TaskStatus.PENDING:
|
|
322
|
+
t.status = TaskStatus.IN_PROGRESS
|
|
323
|
+
t.started_at = time.time()
|
|
324
|
+
return t
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
def complete_task(self, task_id: int, result: str = "") -> SubTask | None:
|
|
328
|
+
"""Mark a task as completed."""
|
|
329
|
+
if not self._current_plan:
|
|
330
|
+
return None
|
|
331
|
+
for t in self._current_plan.subtasks:
|
|
332
|
+
if t.id == task_id and t.status == TaskStatus.IN_PROGRESS:
|
|
333
|
+
t.status = TaskStatus.COMPLETED
|
|
334
|
+
t.completed_at = time.time()
|
|
335
|
+
t.result_summary = result
|
|
336
|
+
return t
|
|
337
|
+
return None
|
|
338
|
+
|
|
339
|
+
def fail_task(self, task_id: int, error: str = "") -> SubTask | None:
|
|
340
|
+
"""Mark a task as failed."""
|
|
341
|
+
if not self._current_plan:
|
|
342
|
+
return None
|
|
343
|
+
for t in self._current_plan.subtasks:
|
|
344
|
+
if t.id == task_id:
|
|
345
|
+
t.status = TaskStatus.FAILED
|
|
346
|
+
t.error = error
|
|
347
|
+
return t
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
def skip_task(self, task_id: int) -> SubTask | None:
|
|
351
|
+
"""Skip a task."""
|
|
352
|
+
if not self._current_plan:
|
|
353
|
+
return None
|
|
354
|
+
for t in self._current_plan.subtasks:
|
|
355
|
+
if t.id == task_id and t.status == TaskStatus.PENDING:
|
|
356
|
+
t.status = TaskStatus.SKIPPED
|
|
357
|
+
return t
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
def finish_plan(self) -> Plan | None:
|
|
361
|
+
"""Archive the current plan."""
|
|
362
|
+
plan = self._current_plan
|
|
363
|
+
self._current_plan = None
|
|
364
|
+
return plan
|
|
365
|
+
|
|
366
|
+
def get_status(self) -> str:
|
|
367
|
+
"""Get a status string for the UI."""
|
|
368
|
+
if not self._current_plan:
|
|
369
|
+
return "No active plan."
|
|
370
|
+
return self._current_plan.progress_bar()
|
|
371
|
+
|
|
372
|
+
def get_context_for_prompt(self) -> str:
|
|
373
|
+
"""Get plan context for injection into the system prompt."""
|
|
374
|
+
if not self._current_plan:
|
|
375
|
+
return ""
|
|
376
|
+
return "\n" + self._current_plan.to_prompt()
|
|
377
|
+
|
|
378
|
+
def auto_advance(self) -> SubTask | None:
|
|
379
|
+
"""Automatically start the next pending task. Returns it or None."""
|
|
380
|
+
if not self._current_plan:
|
|
381
|
+
return None
|
|
382
|
+
# Check if there's already an in-progress task
|
|
383
|
+
current = self._current_plan.current
|
|
384
|
+
if current:
|
|
385
|
+
return current
|
|
386
|
+
|
|
387
|
+
next_task = self._current_plan.next_pending()
|
|
388
|
+
if next_task:
|
|
389
|
+
self.start_task(next_task.id)
|
|
390
|
+
return next_task
|
|
391
|
+
return None
|