droidrun 0.2.0__py3-none-any.whl → 0.3.0__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.
- droidrun/__init__.py +16 -11
- droidrun/__main__.py +1 -1
- droidrun/adb/__init__.py +3 -3
- droidrun/adb/device.py +1 -1
- droidrun/adb/manager.py +2 -2
- droidrun/agent/__init__.py +6 -0
- droidrun/agent/codeact/__init__.py +2 -4
- droidrun/agent/codeact/codeact_agent.py +321 -235
- droidrun/agent/codeact/events.py +12 -20
- droidrun/agent/codeact/prompts.py +0 -52
- droidrun/agent/common/default.py +5 -0
- droidrun/agent/common/events.py +4 -0
- droidrun/agent/context/__init__.py +23 -0
- droidrun/agent/context/agent_persona.py +15 -0
- droidrun/agent/context/context_injection_manager.py +66 -0
- droidrun/agent/context/episodic_memory.py +15 -0
- droidrun/agent/context/personas/__init__.py +11 -0
- droidrun/agent/context/personas/app_starter.py +44 -0
- droidrun/agent/context/personas/default.py +95 -0
- droidrun/agent/context/personas/extractor.py +52 -0
- droidrun/agent/context/personas/ui_expert.py +107 -0
- droidrun/agent/context/reflection.py +20 -0
- droidrun/agent/context/task_manager.py +124 -0
- droidrun/agent/context/todo.txt +4 -0
- droidrun/agent/droid/__init__.py +2 -2
- droidrun/agent/droid/droid_agent.py +264 -325
- droidrun/agent/droid/events.py +28 -0
- droidrun/agent/oneflows/reflector.py +265 -0
- droidrun/agent/planner/__init__.py +2 -4
- droidrun/agent/planner/events.py +9 -13
- droidrun/agent/planner/planner_agent.py +268 -0
- droidrun/agent/planner/prompts.py +33 -53
- droidrun/agent/utils/__init__.py +3 -0
- droidrun/agent/utils/async_utils.py +1 -40
- droidrun/agent/utils/chat_utils.py +268 -48
- droidrun/agent/utils/executer.py +49 -14
- droidrun/agent/utils/llm_picker.py +14 -10
- droidrun/agent/utils/trajectory.py +184 -0
- droidrun/cli/__init__.py +1 -1
- droidrun/cli/logs.py +283 -0
- droidrun/cli/main.py +333 -439
- droidrun/run.py +105 -0
- droidrun/tools/__init__.py +5 -10
- droidrun/tools/{actions.py → adb.py} +279 -238
- droidrun/tools/ios.py +594 -0
- droidrun/tools/tools.py +99 -0
- droidrun-0.3.0.dist-info/METADATA +149 -0
- droidrun-0.3.0.dist-info/RECORD +52 -0
- droidrun/agent/planner/task_manager.py +0 -355
- droidrun/agent/planner/workflow.py +0 -371
- droidrun/tools/device.py +0 -29
- droidrun/tools/loader.py +0 -60
- droidrun-0.2.0.dist-info/METADATA +0 -373
- droidrun-0.2.0.dist-info/RECORD +0 -32
- {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/WHEEL +0 -0
- {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/entry_points.txt +0 -0
- {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,149 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: droidrun
|
3
|
+
Version: 0.3.0
|
4
|
+
Summary: A framework for controlling Android devices through LLM agents
|
5
|
+
Project-URL: Homepage, https://github.com/droidrun/droidrun
|
6
|
+
Project-URL: Bug Tracker, https://github.com/droidrun/droidrun/issues
|
7
|
+
Project-URL: Documentation, https://docs.droidrun.ai/
|
8
|
+
Author-email: Niels Schmidt <niels.schmidt@droidrun.ai>
|
9
|
+
License: MIT
|
10
|
+
License-File: LICENSE
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Classifier: Topic :: Communications :: Chat
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
24
|
+
Classifier: Topic :: Software Development :: Testing
|
25
|
+
Classifier: Topic :: Software Development :: Testing :: Acceptance
|
26
|
+
Classifier: Topic :: System :: Emulators
|
27
|
+
Classifier: Topic :: Utilities
|
28
|
+
Requires-Python: >=3.10
|
29
|
+
Requires-Dist: aiofiles>=23.0.0
|
30
|
+
Requires-Dist: anthropic>=0.7.0
|
31
|
+
Requires-Dist: arize-phoenix
|
32
|
+
Requires-Dist: click>=8.1.0
|
33
|
+
Requires-Dist: llama-index
|
34
|
+
Requires-Dist: llama-index-callbacks-arize-phoenix
|
35
|
+
Requires-Dist: llama-index-llms-anthropic
|
36
|
+
Requires-Dist: llama-index-llms-deepseek
|
37
|
+
Requires-Dist: llama-index-llms-gemini
|
38
|
+
Requires-Dist: llama-index-llms-ollama
|
39
|
+
Requires-Dist: llama-index-llms-openai
|
40
|
+
Requires-Dist: openai>=1.0.0
|
41
|
+
Requires-Dist: pillow>=10.0.0
|
42
|
+
Requires-Dist: pydantic>=2.0.0
|
43
|
+
Requires-Dist: python-dotenv>=1.0.0
|
44
|
+
Requires-Dist: rich>=13.0.0
|
45
|
+
Provides-Extra: dev
|
46
|
+
Requires-Dist: bandit>=1.7.0; extra == 'dev'
|
47
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
48
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
49
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
50
|
+
Requires-Dist: safety>=2.0.0; extra == 'dev'
|
51
|
+
Description-Content-Type: text/markdown
|
52
|
+
|
53
|
+
<picture>
|
54
|
+
<source media="(prefers-color-scheme: dark)" srcset="./static/droidrun-dark.png">
|
55
|
+
<source media="(prefers-color-scheme: light)" srcset="./static/droidrun.png">
|
56
|
+
<img src="./static/droidrun.png" width="full">
|
57
|
+
</picture>
|
58
|
+
|
59
|
+
[](https://github.com/droidrun/droidrun/stargazers)
|
60
|
+
[](https://discord.gg/ZZbKEZZkwK)
|
61
|
+
[](https://docs.droidrun.ai)
|
62
|
+
[](https://droidrun.ai/benchmark)
|
63
|
+
[](https://x.com/droid_run)
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
DroidRun is a powerful framework for controlling Android and iOS devices through LLM agents. It allows you to automate device interactions using natural language commands. [Checkout our benchmark results](https://droidrun.ai/benchmark)
|
68
|
+
|
69
|
+
- 🤖 Control Android and iOS devices with natural language commands
|
70
|
+
- 🔀 Supports multiple LLM providers (OpenAI, Anthropic, Gemini, Ollama, DeepSeek)
|
71
|
+
- 🧠 Planning capabilities for complex multi-step tasks
|
72
|
+
- 💻 Easy to use CLI with enhanced debugging features
|
73
|
+
- 🐍 Extendable Python API for custom automations
|
74
|
+
- 📸 Screenshot analysis for visual understanding of the device
|
75
|
+
- Execution tracing with Arize Phoenix
|
76
|
+
|
77
|
+
## 📦 Installation
|
78
|
+
|
79
|
+
```bash
|
80
|
+
pip install droidrun
|
81
|
+
```
|
82
|
+
|
83
|
+
## 🚀 Quickstart
|
84
|
+
Read on how to get droidrun up and running within seconds in [our docs](https://docs.droidrun.ai/v3/quickstart)!
|
85
|
+
|
86
|
+
|
87
|
+
## 🎬 Demo Videos
|
88
|
+
|
89
|
+
1. **Shopping Assistant**: Watch how DroidRun searches Amazon for headphones and sends the top 3 products to a colleague on WhatsApp.
|
90
|
+
|
91
|
+
Prompt: "Go to Amazon, search for headphones and write the top 3 products to my colleague on WhatsApp."
|
92
|
+
|
93
|
+
[](https://www.youtube.com/watch?v=VQK3JcifgwU)
|
94
|
+
|
95
|
+
2. **Social Media Automation**: See DroidRun open X (Twitter) and post "Hello World".
|
96
|
+
|
97
|
+
Prompt: "Open up X and post Hello World."
|
98
|
+
|
99
|
+
[](https://www.youtube.com/watch?v=i4-sDQhzt_M)
|
100
|
+
|
101
|
+
## 💡 Example Use Cases
|
102
|
+
|
103
|
+
- Automated UI testing of mobile applications
|
104
|
+
- Creating guided workflows for non-technical users
|
105
|
+
- Automating repetitive tasks on mobile devices
|
106
|
+
- Remote assistance for less technical users
|
107
|
+
- Exploring mobile UI with natural language commands
|
108
|
+
|
109
|
+
## 🗺️ Roadmap
|
110
|
+
|
111
|
+
### 🤖 Agent:
|
112
|
+
- **Improve memory**: Enhance context retention for complex multi-step tasks
|
113
|
+
- **Expand planning capabilities**: Add support for more complex reasoning strategies
|
114
|
+
- **Add Integrations**: Support more LLM providers and agent frameworks (LangChain, Agno etc.)
|
115
|
+
|
116
|
+
### ⚙️ Automations:
|
117
|
+
- **Create Automation Scripts**: Generate reusable scripts from agent actions that can be scheduled or shared
|
118
|
+
|
119
|
+
### ☁️ Cloud:
|
120
|
+
- **Hosted version**: Remote device control via web interface without local setup
|
121
|
+
- **Add-Ons**: Marketplace for extensions serving specific use cases
|
122
|
+
- **Proxy Hours**: Cloud compute time with tiered pricing for running automations
|
123
|
+
- **Droidrun AppStore**: Simple installation of Apps on your hosted devices
|
124
|
+
|
125
|
+
## 👥 Contributing
|
126
|
+
|
127
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
128
|
+
|
129
|
+
## 📄 License
|
130
|
+
|
131
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
132
|
+
|
133
|
+
## Security Checks
|
134
|
+
|
135
|
+
To ensure the security of the codebase, we have integrated security checks using `bandit` and `safety`. These tools help identify potential security issues in the code and dependencies.
|
136
|
+
|
137
|
+
### Running Security Checks
|
138
|
+
|
139
|
+
Before submitting any code, please run the following security checks:
|
140
|
+
|
141
|
+
1. **Bandit**: A tool to find common security issues in Python code.
|
142
|
+
```bash
|
143
|
+
bandit -r droidrun
|
144
|
+
```
|
145
|
+
|
146
|
+
2. **Safety**: A tool to check your installed dependencies for known security vulnerabilities.
|
147
|
+
```bash
|
148
|
+
safety scan
|
149
|
+
```
|
@@ -0,0 +1,52 @@
|
|
1
|
+
droidrun/__init__.py,sha256=ERliGcmgGJpyTs7SukYD28tgRccxn6hPG8JPsH0ezZA,840
|
2
|
+
droidrun/__main__.py,sha256=78o1Wr_Z-NrZy9yLWmEfNfRRhAiJGBr4Xi3lmbgkx3w,105
|
3
|
+
droidrun/run.py,sha256=79xRUb5OgAezXBiI1a4PLvdimfRiowcrhMF1eC9vcC0,3341
|
4
|
+
droidrun/adb/__init__.py,sha256=kh-iT9Sv6RZ2dFSDu1beK_GgtAq8wlMvZQ7puR8JWsI,257
|
5
|
+
droidrun/adb/device.py,sha256=fAKXR5Qmqzz7q3KD2VCcW6dG3NsDLfG9zd56B5kswL4,11004
|
6
|
+
droidrun/adb/manager.py,sha256=g7TuEOCnMTavHzCMtr3u6TPBhmZwE98CW9vBwdhdyyg,2728
|
7
|
+
droidrun/adb/wrapper.py,sha256=Yz3_JSIidq-5bf-t0UfawTutMaLrINS_1Y15m_Uss4g,7093
|
8
|
+
droidrun/agent/__init__.py,sha256=4SqTJeGDvr_wT8rtN9J8hnN6P-pae663mkYr-JmzH4w,208
|
9
|
+
droidrun/agent/codeact/__init__.py,sha256=ZLDGT_lTTzyNm7pcBzdyRIGHJ2ZgbInJdhXZRbJLhSQ,278
|
10
|
+
droidrun/agent/codeact/codeact_agent.py,sha256=NOOmyRaMn_AOeMSMNK6p86dStslTX5s9rn-bQWJ0Fh0,15647
|
11
|
+
droidrun/agent/codeact/events.py,sha256=skCfZ-5SR0YhhzZVxx8_VkUjfILk8rCv47k9pHNYhdc,634
|
12
|
+
droidrun/agent/codeact/prompts.py,sha256=28HflWMNkC1ky0hGCzAxhJftjU2IIU1ZRUfya3S7M6I,1006
|
13
|
+
droidrun/agent/common/default.py,sha256=P07el-PrHsoqQMYsYxSSln6mFl-QY75vzwp1Dll_XmY,259
|
14
|
+
droidrun/agent/common/events.py,sha256=axauKdrXFEeB1hpd0geazwBtJySnHsvLZV0u9F9OWZI,96
|
15
|
+
droidrun/agent/context/__init__.py,sha256=upSJilVQUwQYWJuGr_8QrmJaReV3SNAc_Z61wQZzbK4,697
|
16
|
+
droidrun/agent/context/agent_persona.py,sha256=Mxd4HTyirWD-aqNlka1hQBdS-0I-lXJr2AjPMwDMUo8,390
|
17
|
+
droidrun/agent/context/context_injection_manager.py,sha256=sA33q2KPtX_4Yap8wM11T6ewlZC_0FIbKPEc400SHrE,2188
|
18
|
+
droidrun/agent/context/episodic_memory.py,sha256=1ImeR3jAWOpKwkQt3bMlXVOBiQbIli5fBIlBq2waREQ,394
|
19
|
+
droidrun/agent/context/reflection.py,sha256=0hJluOz0hTlHHhReKpIJ9HU5aJbaJsvrjMfraQ84D-M,652
|
20
|
+
droidrun/agent/context/task_manager.py,sha256=ESLs4kR6VNYiYQsc4V7WAeoSLwbaZPSWBXpveOfOv8c,4343
|
21
|
+
droidrun/agent/context/todo.txt,sha256=pwcH0z4Ofic3fhZ2fekRMfjrxPIB8IybO4eqvSWWjgA,163
|
22
|
+
droidrun/agent/context/personas/__init__.py,sha256=tUDIM_9Kim3ki6ZXpwcvPHPSa2cCHNMLdNW4lyJr4gM,231
|
23
|
+
droidrun/agent/context/personas/app_starter.py,sha256=dHeknznxGEPJ7S6VPyEG_MB-HvAvQwUOnRWaShaV8Xo,1585
|
24
|
+
droidrun/agent/context/personas/default.py,sha256=8pWC34gdbySyTaRFVE3dFdwpOMW63G1EOjjcYP0BLGo,5023
|
25
|
+
droidrun/agent/context/personas/extractor.py,sha256=S7Qgh3-Kz_OowQJFJGQUlr8OUVktX5UHVdf5qkAj3GQ,1907
|
26
|
+
droidrun/agent/context/personas/ui_expert.py,sha256=P2dAnkKiR3O4bN4ZUuWHmuu4Qo8WRiH1mU6EEOr3yJA,4710
|
27
|
+
droidrun/agent/droid/__init__.py,sha256=3BfUVZiUQ8ATAJ_JmqQZQx53WoERRpQ4AyHW5WOgbRI,297
|
28
|
+
droidrun/agent/droid/droid_agent.py,sha256=M08l7WOu1NdTOPNErs0lNVUZC4UDNdS-FK0W5vJ1RqU,13222
|
29
|
+
droidrun/agent/droid/events.py,sha256=bpUhJnbaIH86r8hfUsbHub465v-xYOsIyHWNN8g4TCs,576
|
30
|
+
droidrun/agent/oneflows/reflector.py,sha256=I_tE0PBjvwWbS6SA8Qd41etxJglFgn8oScuKUxc9LEE,11621
|
31
|
+
droidrun/agent/planner/__init__.py,sha256=Fu0Ewtd-dIRLgHIL1DB_9EEKvQS_f1vjB8jgO5TbJXg,364
|
32
|
+
droidrun/agent/planner/events.py,sha256=oyt2FNrA2uVyUeVT65-N0AC6sWBFxSnwNEqWtnRYoFM,390
|
33
|
+
droidrun/agent/planner/planner_agent.py,sha256=MuCDer3ZfEXTpid-sCd5L4i1C3yHrWsSa8sRLwqq7IA,10050
|
34
|
+
droidrun/agent/planner/prompts.py,sha256=Ci7Oeu3J4TAhx-tKGPZ9l6Wb3a81FSqC8cWW4jW73HI,6046
|
35
|
+
droidrun/agent/utils/__init__.py,sha256=JK6ygRjw7gzcQSG0HBEYLoVGH54QQAxJJ7HpIS5mgyc,44
|
36
|
+
droidrun/agent/utils/async_utils.py,sha256=IQBcWPwevm89B7R_UdMXk0unWeNCBA232b5kQGqoxNI,336
|
37
|
+
droidrun/agent/utils/chat_utils.py,sha256=aZihuOE_sdkzniRHBF9QNZ1RF9IFOj06uyd6sglPumY,13241
|
38
|
+
droidrun/agent/utils/executer.py,sha256=lQbk2TbPyl_F0k2FqEVimq8cQRcWM_KCO_I7fnkkxqA,4587
|
39
|
+
droidrun/agent/utils/llm_picker.py,sha256=adktsU2-GhYzZ0djOhO_TyFhUE8aGirzJy36A_EvnXU,5919
|
40
|
+
droidrun/agent/utils/trajectory.py,sha256=OmO8TvNO0LVtPXg2qTCv8o9ePaMeDyf-MRWN_YObXho,6845
|
41
|
+
droidrun/cli/__init__.py,sha256=DuwSRtZ8WILPd-nf-fZ7BaBsRgtofoInOF3JtJ9wag0,167
|
42
|
+
droidrun/cli/logs.py,sha256=PsT_VbnOa_sOLXK4KkEJk4AsYCpscqrVoryMmLVwPG0,9714
|
43
|
+
droidrun/cli/main.py,sha256=bNwlhTdZcsqM-Q-SnFTqY4BzNTp_fGhGel67r_fbMHM,14344
|
44
|
+
droidrun/tools/__init__.py,sha256=9ReauavtSKDQG9ya9_Fr9O0TQnDFixgOPaP5n82_iEk,271
|
45
|
+
droidrun/tools/adb.py,sha256=YSStSGYa7SFCzmrZAb_hn5pOtpXzErnrP5HJyOCm5Ss,33984
|
46
|
+
droidrun/tools/ios.py,sha256=JMsBLAykeXsSvwHXvHmGinM9luPW63SpsI_u1IDPCx8,22822
|
47
|
+
droidrun/tools/tools.py,sha256=L9EKBk2_QxPZmmDVXfjmHwqkauwCawNnQuz9X29N8UE,2468
|
48
|
+
droidrun-0.3.0.dist-info/METADATA,sha256=QIbmadNT3BUyfINHZmtlOUZvtOPLthl-q9CfqkVgBJ4,6294
|
49
|
+
droidrun-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
50
|
+
droidrun-0.3.0.dist-info/entry_points.txt,sha256=o259U66js8TIybQ7zs814Oe_LQ_GpZsp6a9Cr-xm5zE,51
|
51
|
+
droidrun-0.3.0.dist-info/licenses/LICENSE,sha256=s-uxn9qChu-kFdRXUp6v_0HhsaJ_5OANmfNOFVm2zdk,1069
|
52
|
+
droidrun-0.3.0.dist-info/RECORD,,
|
@@ -1,355 +0,0 @@
|
|
1
|
-
import json # For potential saving/loading later (optional)
|
2
|
-
import os
|
3
|
-
from typing import List, Dict, Any, Optional
|
4
|
-
|
5
|
-
class TaskManager:
|
6
|
-
"""
|
7
|
-
Manages a list of tasks for an agent, each with a status.
|
8
|
-
"""
|
9
|
-
# --- Define Status Constants ---
|
10
|
-
STATUS_PENDING = "pending" # Task hasn't been attempted yet
|
11
|
-
STATUS_ATTEMPTING = "attempting" # Task is currently being worked on
|
12
|
-
STATUS_COMPLETED = "completed" # Task was finished successfully
|
13
|
-
STATUS_FAILED = "failed" # Task attempt resulted in failure
|
14
|
-
|
15
|
-
VALID_STATUSES = {
|
16
|
-
STATUS_PENDING,
|
17
|
-
STATUS_ATTEMPTING,
|
18
|
-
STATUS_COMPLETED,
|
19
|
-
STATUS_FAILED
|
20
|
-
}
|
21
|
-
# desktop path/todo.md
|
22
|
-
file_path = os.path.join(os.path.expanduser("~"), "Desktop", "todo.txt")
|
23
|
-
def __init__(self):
|
24
|
-
"""Initializes an empty task list."""
|
25
|
-
self.tasks = [] # List to store task dictionaries
|
26
|
-
self.task_completed = False
|
27
|
-
self.message = None
|
28
|
-
self.start_execution = False
|
29
|
-
self.task_history = [] # List to store historical task information
|
30
|
-
self.persistent_completed_tasks = [] # Persistent list of completed tasks
|
31
|
-
self.persistent_failed_tasks = [] # Persistent list of failed tasks
|
32
|
-
|
33
|
-
# self.tasks is a property, make a getter and setter for it
|
34
|
-
def set_tasks(self, tasks: List[str], task_contexts: Optional[List[Dict[str, Any]]] = None):
|
35
|
-
"""
|
36
|
-
Clears the current task list and sets new tasks from a list.
|
37
|
-
Each task should be a string.
|
38
|
-
|
39
|
-
Args:
|
40
|
-
tasks: A list of strings, each representing a task.
|
41
|
-
task_contexts: Optional list of context dictionaries for each task.
|
42
|
-
"""
|
43
|
-
try:
|
44
|
-
# Save any completed or failed tasks before clearing the list
|
45
|
-
for task in self.tasks:
|
46
|
-
if task["status"] == self.STATUS_COMPLETED and task not in self.persistent_completed_tasks:
|
47
|
-
# Store a copy to prevent modifications
|
48
|
-
self.persistent_completed_tasks.append(task.copy())
|
49
|
-
elif task["status"] == self.STATUS_FAILED and task not in self.persistent_failed_tasks:
|
50
|
-
# Store a copy to prevent modifications
|
51
|
-
self.persistent_failed_tasks.append(task.copy())
|
52
|
-
|
53
|
-
# Now clear the task list and add new tasks
|
54
|
-
self.tasks = []
|
55
|
-
for i, task in enumerate(tasks):
|
56
|
-
if not isinstance(task, str) or not task.strip():
|
57
|
-
raise ValueError("Each task must be a non-empty string.")
|
58
|
-
|
59
|
-
task_dict = {
|
60
|
-
"description": task.strip(),
|
61
|
-
"status": self.STATUS_PENDING
|
62
|
-
}
|
63
|
-
|
64
|
-
# Add context if provided
|
65
|
-
if task_contexts and i < len(task_contexts):
|
66
|
-
task_dict["context"] = task_contexts[i]
|
67
|
-
|
68
|
-
self.tasks.append(task_dict)
|
69
|
-
|
70
|
-
print(f"Tasks set: {len(self.tasks)} tasks added.")
|
71
|
-
self.save_to_file()
|
72
|
-
except Exception as e:
|
73
|
-
print(f"Error setting tasks: {e}")
|
74
|
-
|
75
|
-
def add_task(self, task_description: str, task_context: Optional[Dict[str, Any]] = None):
|
76
|
-
"""
|
77
|
-
Adds a new task to the list with a 'pending' status.
|
78
|
-
|
79
|
-
Args:
|
80
|
-
task_description: The string describing the task.
|
81
|
-
task_context: Optional dictionary with context for the task.
|
82
|
-
|
83
|
-
Returns:
|
84
|
-
int: The index of the newly added task.
|
85
|
-
|
86
|
-
Raises:
|
87
|
-
ValueError: If the task_description is empty or not a string.
|
88
|
-
"""
|
89
|
-
if not isinstance(task_description, str) or not task_description.strip():
|
90
|
-
raise ValueError("Task description must be a non-empty string.")
|
91
|
-
|
92
|
-
task = {
|
93
|
-
"description": task_description.strip(),
|
94
|
-
"status": self.STATUS_PENDING
|
95
|
-
}
|
96
|
-
|
97
|
-
# Add context if provided
|
98
|
-
if task_context:
|
99
|
-
task["context"] = task_context
|
100
|
-
|
101
|
-
self.tasks.append(task)
|
102
|
-
self.save_to_file()
|
103
|
-
print(f"Task added: {task_description} (Status: {self.STATUS_PENDING})")
|
104
|
-
|
105
|
-
return len(self.tasks) - 1 # Return the index of the new task
|
106
|
-
|
107
|
-
def get_task(self, index: int):
|
108
|
-
"""
|
109
|
-
Retrieves a specific task by its index.
|
110
|
-
|
111
|
-
Args:
|
112
|
-
index: The integer index of the task.
|
113
|
-
|
114
|
-
Returns:
|
115
|
-
dict: The task dictionary {'description': str, 'status': str}.
|
116
|
-
|
117
|
-
Raises:
|
118
|
-
IndexError: If the index is out of bounds.
|
119
|
-
"""
|
120
|
-
if 0 <= index < len(self.tasks):
|
121
|
-
return self.tasks[index]
|
122
|
-
else:
|
123
|
-
raise IndexError(f"Task index {index} out of bounds.")
|
124
|
-
|
125
|
-
def get_all_tasks(self):
|
126
|
-
"""
|
127
|
-
Returns a copy of the entire list of tasks.
|
128
|
-
|
129
|
-
Returns:
|
130
|
-
list[dict]: A list containing all task dictionaries.
|
131
|
-
Returns an empty list if no tasks exist.
|
132
|
-
"""
|
133
|
-
return list(self.tasks) # Return a copy to prevent external modification
|
134
|
-
|
135
|
-
def update_status(self, index: int, new_status: str, result_info: Optional[Dict[str, Any]] = None):
|
136
|
-
"""
|
137
|
-
Updates the status of a specific task.
|
138
|
-
|
139
|
-
Args:
|
140
|
-
index: The index of the task to update.
|
141
|
-
new_status: The new status string (must be one of VALID_STATUSES).
|
142
|
-
result_info: Optional dictionary with additional information about the task result.
|
143
|
-
|
144
|
-
Raises:
|
145
|
-
IndexError: If the index is out of bounds.
|
146
|
-
ValueError: If the new_status is not a valid status.
|
147
|
-
"""
|
148
|
-
if new_status not in self.VALID_STATUSES:
|
149
|
-
raise ValueError(f"Invalid status '{new_status}'. Valid statuses are: {', '.join(self.VALID_STATUSES)}")
|
150
|
-
|
151
|
-
# get_task will raise IndexError if index is invalid
|
152
|
-
task = self.get_task(index)
|
153
|
-
old_status = task["status"]
|
154
|
-
task["status"] = new_status
|
155
|
-
|
156
|
-
# Add result information if provided
|
157
|
-
if result_info:
|
158
|
-
for key, value in result_info.items():
|
159
|
-
task[key] = value
|
160
|
-
|
161
|
-
# Store task history when status changes
|
162
|
-
if old_status != new_status:
|
163
|
-
history_entry = {
|
164
|
-
"index": index,
|
165
|
-
"description": task["description"],
|
166
|
-
"old_status": old_status,
|
167
|
-
"new_status": new_status,
|
168
|
-
"result_info": result_info
|
169
|
-
}
|
170
|
-
self.task_history.append(history_entry)
|
171
|
-
|
172
|
-
# If the task is now completed or failed, add it to our persistent lists
|
173
|
-
if new_status == self.STATUS_COMPLETED:
|
174
|
-
# Make a copy to ensure it doesn't change if the original task is modified
|
175
|
-
task_copy = task.copy()
|
176
|
-
if task_copy not in self.persistent_completed_tasks:
|
177
|
-
self.persistent_completed_tasks.append(task_copy)
|
178
|
-
elif new_status == self.STATUS_FAILED:
|
179
|
-
# Make a copy to ensure it doesn't change if the original task is modified
|
180
|
-
task_copy = task.copy()
|
181
|
-
if task_copy not in self.persistent_failed_tasks:
|
182
|
-
self.persistent_failed_tasks.append(task_copy)
|
183
|
-
|
184
|
-
self.save_to_file()
|
185
|
-
# No need to re-assign task to self.tasks[index] as dictionaries are mutable
|
186
|
-
|
187
|
-
def delete_task(self, index: int):
|
188
|
-
"""
|
189
|
-
Deletes a task by its index.
|
190
|
-
|
191
|
-
Args:
|
192
|
-
index: The index of the task to delete.
|
193
|
-
|
194
|
-
Raises:
|
195
|
-
IndexError: If the index is out of bounds.
|
196
|
-
"""
|
197
|
-
if 0 <= index < len(self.tasks):
|
198
|
-
del self.tasks[index]
|
199
|
-
self.save_to_file()
|
200
|
-
else:
|
201
|
-
raise IndexError(f"Task index {index} out of bounds.")
|
202
|
-
|
203
|
-
def clear_tasks(self):
|
204
|
-
"""Removes all tasks from the list."""
|
205
|
-
self.tasks = []
|
206
|
-
print("All tasks cleared.")
|
207
|
-
self.save_to_file()
|
208
|
-
|
209
|
-
def get_tasks_by_status(self, status: str):
|
210
|
-
"""
|
211
|
-
Filters and returns tasks matching a specific status.
|
212
|
-
|
213
|
-
Args:
|
214
|
-
status: The status string to filter by.
|
215
|
-
|
216
|
-
Returns:
|
217
|
-
list[dict]: A list of tasks matching the status.
|
218
|
-
|
219
|
-
Raises:
|
220
|
-
ValueError: If the status is not a valid status.
|
221
|
-
"""
|
222
|
-
if status not in self.VALID_STATUSES:
|
223
|
-
raise ValueError(f"Invalid status '{status}'. Valid statuses are: {', '.join(self.VALID_STATUSES)}")
|
224
|
-
return [task for task in self.tasks if task["status"] == status]
|
225
|
-
|
226
|
-
# --- Convenience methods for specific statuses ---
|
227
|
-
def get_pending_tasks(self) -> list[dict]:
|
228
|
-
return self.get_tasks_by_status(self.STATUS_PENDING)
|
229
|
-
|
230
|
-
def get_attempting_task(self) -> dict | None:
|
231
|
-
attempting_tasks = self.get_tasks_by_status(self.STATUS_ATTEMPTING)
|
232
|
-
if attempting_tasks:
|
233
|
-
return attempting_tasks[0]
|
234
|
-
else:
|
235
|
-
return None
|
236
|
-
|
237
|
-
def get_completed_tasks(self) -> list[dict]:
|
238
|
-
return self.get_tasks_by_status(self.STATUS_COMPLETED)
|
239
|
-
|
240
|
-
def get_failed_tasks(self) -> dict | None:
|
241
|
-
attempting_tasks = self.get_tasks_by_status(self.STATUS_FAILED)
|
242
|
-
if attempting_tasks:
|
243
|
-
return attempting_tasks[0]
|
244
|
-
else:
|
245
|
-
return None
|
246
|
-
|
247
|
-
# --- Utility methods ---
|
248
|
-
def __len__(self):
|
249
|
-
"""Returns the total number of tasks."""
|
250
|
-
return len(self.tasks)
|
251
|
-
|
252
|
-
def __str__(self):
|
253
|
-
"""Provides a user-friendly string representation of the task list."""
|
254
|
-
if not self.tasks:
|
255
|
-
return "Task List (empty)"
|
256
|
-
|
257
|
-
output = "Task List:\n"
|
258
|
-
output += "----------\n"
|
259
|
-
for i, task in enumerate(self.tasks):
|
260
|
-
output += f"{i}: [{task['status'].upper():<10}] {task['description']}\n"
|
261
|
-
output += "----------"
|
262
|
-
return output
|
263
|
-
|
264
|
-
def __repr__(self):
|
265
|
-
"""Provides a developer-friendly representation."""
|
266
|
-
return f"<TaskManager(task_count={len(self.tasks)}, completed={self.get_completed_tasks()}, attempting={self.get_attempting_task()}, pending={self.get_pending_tasks()})>"
|
267
|
-
def save_to_file(self, filename=file_path):
|
268
|
-
"""Saves the current task list to a Markdown file."""
|
269
|
-
try:
|
270
|
-
with open(filename, 'w', encoding='utf-8') as f:
|
271
|
-
f.write(str(self))
|
272
|
-
#print(f"Tasks saved to {filename}.")
|
273
|
-
except Exception as e:
|
274
|
-
print(f"Error saving tasks to file: {e}")
|
275
|
-
def complete_goal(self, message: str):
|
276
|
-
"""
|
277
|
-
Marks the goal as completed, use this whether the task completion was successful or on failure.
|
278
|
-
This method should be called when the task is finished, regardless of the outcome.
|
279
|
-
|
280
|
-
Args:
|
281
|
-
message: The message to be logged.
|
282
|
-
"""
|
283
|
-
self.task_completed = True
|
284
|
-
self.message = message
|
285
|
-
print(f"Goal completed: {message}")
|
286
|
-
def start_agent(self):
|
287
|
-
"""Starts the sub-agent to perform the tasks if there are any tasks to perform.
|
288
|
-
Use this function after setting the tasks.
|
289
|
-
Args:
|
290
|
-
None"""
|
291
|
-
if len(self.tasks) == 0:
|
292
|
-
print("No tasks to perform.")
|
293
|
-
return
|
294
|
-
self.start_execution = True
|
295
|
-
|
296
|
-
def get_task_history(self):
|
297
|
-
"""
|
298
|
-
Returns the history of task status changes.
|
299
|
-
|
300
|
-
Returns:
|
301
|
-
list: A list of dictionaries with historical task information.
|
302
|
-
"""
|
303
|
-
return self.task_history
|
304
|
-
|
305
|
-
def get_all_completed_tasks(self) -> List[Dict]:
|
306
|
-
"""
|
307
|
-
Returns all completed tasks, including those from previous planning cycles.
|
308
|
-
|
309
|
-
Returns:
|
310
|
-
List of completed task dictionaries
|
311
|
-
"""
|
312
|
-
# Get currently active completed tasks
|
313
|
-
current_completed = self.get_completed_tasks()
|
314
|
-
|
315
|
-
# Create a combined list, ensuring no duplicates
|
316
|
-
all_completed = []
|
317
|
-
|
318
|
-
# Add current completed tasks
|
319
|
-
for task in current_completed:
|
320
|
-
if task not in all_completed:
|
321
|
-
all_completed.append(task)
|
322
|
-
|
323
|
-
# Add historical completed tasks
|
324
|
-
for task in self.persistent_completed_tasks:
|
325
|
-
# Check if task is already in the list based on description
|
326
|
-
if not any(t["description"] == task["description"] for t in all_completed):
|
327
|
-
all_completed.append(task)
|
328
|
-
|
329
|
-
return all_completed
|
330
|
-
|
331
|
-
def get_all_failed_tasks(self) -> List[Dict]:
|
332
|
-
"""
|
333
|
-
Returns all failed tasks, including those from previous planning cycles.
|
334
|
-
|
335
|
-
Returns:
|
336
|
-
List of failed task dictionaries
|
337
|
-
"""
|
338
|
-
# Get currently active failed tasks
|
339
|
-
current_failed = self.get_tasks_by_status(self.STATUS_FAILED)
|
340
|
-
|
341
|
-
# Create a combined list, ensuring no duplicates
|
342
|
-
all_failed = []
|
343
|
-
|
344
|
-
# Add current failed tasks
|
345
|
-
for task in current_failed:
|
346
|
-
if task not in all_failed:
|
347
|
-
all_failed.append(task)
|
348
|
-
|
349
|
-
# Add historical failed tasks
|
350
|
-
for task in self.persistent_failed_tasks:
|
351
|
-
# Check if task is already in the list based on description
|
352
|
-
if not any(t["description"] == task["description"] for t in all_failed):
|
353
|
-
all_failed.append(task)
|
354
|
-
|
355
|
-
return all_failed
|