droidrun 0.2.0__py3-none-any.whl → 0.3.1__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 (55) hide show
  1. droidrun/__init__.py +16 -11
  2. droidrun/__main__.py +1 -1
  3. droidrun/adb/__init__.py +3 -3
  4. droidrun/adb/device.py +1 -1
  5. droidrun/adb/manager.py +2 -2
  6. droidrun/agent/__init__.py +6 -0
  7. droidrun/agent/codeact/__init__.py +2 -4
  8. droidrun/agent/codeact/codeact_agent.py +330 -235
  9. droidrun/agent/codeact/events.py +12 -20
  10. droidrun/agent/codeact/prompts.py +0 -52
  11. droidrun/agent/common/default.py +5 -0
  12. droidrun/agent/common/events.py +4 -0
  13. droidrun/agent/context/__init__.py +23 -0
  14. droidrun/agent/context/agent_persona.py +15 -0
  15. droidrun/agent/context/context_injection_manager.py +66 -0
  16. droidrun/agent/context/episodic_memory.py +15 -0
  17. droidrun/agent/context/personas/__init__.py +11 -0
  18. droidrun/agent/context/personas/app_starter.py +44 -0
  19. droidrun/agent/context/personas/default.py +95 -0
  20. droidrun/agent/context/personas/extractor.py +52 -0
  21. droidrun/agent/context/personas/ui_expert.py +107 -0
  22. droidrun/agent/context/reflection.py +20 -0
  23. droidrun/agent/context/task_manager.py +124 -0
  24. droidrun/agent/droid/__init__.py +2 -2
  25. droidrun/agent/droid/droid_agent.py +269 -325
  26. droidrun/agent/droid/events.py +28 -0
  27. droidrun/agent/oneflows/reflector.py +265 -0
  28. droidrun/agent/planner/__init__.py +2 -4
  29. droidrun/agent/planner/events.py +9 -13
  30. droidrun/agent/planner/planner_agent.py +288 -0
  31. droidrun/agent/planner/prompts.py +33 -53
  32. droidrun/agent/utils/__init__.py +3 -0
  33. droidrun/agent/utils/async_utils.py +1 -40
  34. droidrun/agent/utils/chat_utils.py +265 -48
  35. droidrun/agent/utils/executer.py +49 -14
  36. droidrun/agent/utils/llm_picker.py +14 -10
  37. droidrun/agent/utils/trajectory.py +184 -0
  38. droidrun/cli/__init__.py +1 -1
  39. droidrun/cli/logs.py +283 -0
  40. droidrun/cli/main.py +364 -441
  41. droidrun/tools/__init__.py +5 -10
  42. droidrun/tools/{actions.py → adb.py} +381 -412
  43. droidrun/tools/ios.py +596 -0
  44. droidrun/tools/tools.py +95 -0
  45. droidrun-0.3.1.dist-info/METADATA +150 -0
  46. droidrun-0.3.1.dist-info/RECORD +50 -0
  47. droidrun/agent/planner/task_manager.py +0 -355
  48. droidrun/agent/planner/workflow.py +0 -371
  49. droidrun/tools/device.py +0 -29
  50. droidrun/tools/loader.py +0 -60
  51. droidrun-0.2.0.dist-info/METADATA +0 -373
  52. droidrun-0.2.0.dist-info/RECORD +0 -32
  53. {droidrun-0.2.0.dist-info → droidrun-0.3.1.dist-info}/WHEEL +0 -0
  54. {droidrun-0.2.0.dist-info → droidrun-0.3.1.dist-info}/entry_points.txt +0 -0
  55. {droidrun-0.2.0.dist-info → droidrun-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: droidrun
3
+ Version: 0.3.1
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-google-genai
38
+ Requires-Dist: llama-index-llms-ollama
39
+ Requires-Dist: llama-index-llms-openai
40
+ Requires-Dist: llama-index-llms-openai-like
41
+ Requires-Dist: openai>=1.0.0
42
+ Requires-Dist: pillow>=10.0.0
43
+ Requires-Dist: pydantic>=2.0.0
44
+ Requires-Dist: python-dotenv>=1.0.0
45
+ Requires-Dist: rich>=13.0.0
46
+ Provides-Extra: dev
47
+ Requires-Dist: bandit>=1.7.0; extra == 'dev'
48
+ Requires-Dist: black>=23.0.0; extra == 'dev'
49
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
50
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
51
+ Requires-Dist: safety>=2.0.0; extra == 'dev'
52
+ Description-Content-Type: text/markdown
53
+
54
+ <picture>
55
+ <source media="(prefers-color-scheme: dark)" srcset="./static/droidrun-dark.png">
56
+ <source media="(prefers-color-scheme: light)" srcset="./static/droidrun.png">
57
+ <img src="./static/droidrun.png" width="full">
58
+ </picture>
59
+
60
+ [![GitHub stars](https://img.shields.io/github/stars/droidrun/droidrun?style=social)](https://github.com/droidrun/droidrun/stargazers)
61
+ [![Discord](https://img.shields.io/discord/1360219330318696488?color=7289DA&label=Discord&logo=discord&logoColor=white)](https://discord.gg/ZZbKEZZkwK)
62
+ [![Documentation](https://img.shields.io/badge/Documentation-📕-blue)](https://docs.droidrun.ai)
63
+ [![Benchmark](https://img.shields.io/badge/Benchmark-🏅-teal)](https://droidrun.ai/benchmark)
64
+ [![Twitter Follow](https://img.shields.io/twitter/follow/droid_run?style=social)](https://x.com/droid_run)
65
+
66
+
67
+
68
+ 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)
69
+
70
+ - 🤖 Control Android and iOS devices with natural language commands
71
+ - 🔀 Supports multiple LLM providers (OpenAI, Anthropic, Gemini, Ollama, DeepSeek)
72
+ - 🧠 Planning capabilities for complex multi-step tasks
73
+ - 💻 Easy to use CLI with enhanced debugging features
74
+ - 🐍 Extendable Python API for custom automations
75
+ - 📸 Screenshot analysis for visual understanding of the device
76
+ - 🫆 Execution tracing with Arize Phoenix
77
+
78
+ ## 📦 Installation
79
+
80
+ ```bash
81
+ pip install droidrun
82
+ ```
83
+
84
+ ## 🚀 Quickstart
85
+ Read on how to get droidrun up and running within seconds in [our docs](https://docs.droidrun.ai/v3/quickstart)!
86
+
87
+
88
+ ## 🎬 Demo Videos
89
+
90
+ 1. **Shopping Assistant**: Watch how DroidRun searches Amazon for headphones and sends the top 3 products to a colleague on WhatsApp.
91
+
92
+ Prompt: "Go to Amazon, search for headphones and write the top 3 products to my colleague on WhatsApp."
93
+
94
+ [![Shopping Assistant Demo](https://img.youtube.com/vi/VQK3JcifgwU/0.jpg)](https://www.youtube.com/watch?v=VQK3JcifgwU)
95
+
96
+ 2. **Social Media Automation**: See DroidRun open X (Twitter) and post "Hello World".
97
+
98
+ Prompt: "Open up X and post Hello World."
99
+
100
+ [![Social Media Automation Demo](https://img.youtube.com/vi/i4-sDQhzt_M/0.jpg)](https://www.youtube.com/watch?v=i4-sDQhzt_M)
101
+
102
+ ## 💡 Example Use Cases
103
+
104
+ - Automated UI testing of mobile applications
105
+ - Creating guided workflows for non-technical users
106
+ - Automating repetitive tasks on mobile devices
107
+ - Remote assistance for less technical users
108
+ - Exploring mobile UI with natural language commands
109
+
110
+ ## 🗺️ Roadmap
111
+
112
+ ### 🤖 Agent:
113
+ - **Improve memory**: Enhance context retention for complex multi-step tasks
114
+ - **Expand planning capabilities**: Add support for more complex reasoning strategies
115
+ - **Add Integrations**: Support more LLM providers and agent frameworks (LangChain, Agno etc.)
116
+
117
+ ### ⚙️ Automations:
118
+ - **Create Automation Scripts**: Generate reusable scripts from agent actions that can be scheduled or shared
119
+
120
+ ### ☁️ Cloud:
121
+ - **Hosted version**: Remote device control via web interface without local setup
122
+ - **Add-Ons**: Marketplace for extensions serving specific use cases
123
+ - **Proxy Hours**: Cloud compute time with tiered pricing for running automations
124
+ - **Droidrun AppStore**: Simple installation of Apps on your hosted devices
125
+
126
+ ## 👥 Contributing
127
+
128
+ Contributions are welcome! Please feel free to submit a Pull Request.
129
+
130
+ ## 📄 License
131
+
132
+ This project is licensed under the MIT License - see the LICENSE file for details.
133
+
134
+ ## Security Checks
135
+
136
+ 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.
137
+
138
+ ### Running Security Checks
139
+
140
+ Before submitting any code, please run the following security checks:
141
+
142
+ 1. **Bandit**: A tool to find common security issues in Python code.
143
+ ```bash
144
+ bandit -r droidrun
145
+ ```
146
+
147
+ 2. **Safety**: A tool to check your installed dependencies for known security vulnerabilities.
148
+ ```bash
149
+ safety scan
150
+ ```
@@ -0,0 +1,50 @@
1
+ droidrun/__init__.py,sha256=ERliGcmgGJpyTs7SukYD28tgRccxn6hPG8JPsH0ezZA,840
2
+ droidrun/__main__.py,sha256=78o1Wr_Z-NrZy9yLWmEfNfRRhAiJGBr4Xi3lmbgkx3w,105
3
+ droidrun/adb/__init__.py,sha256=kh-iT9Sv6RZ2dFSDu1beK_GgtAq8wlMvZQ7puR8JWsI,257
4
+ droidrun/adb/device.py,sha256=fAKXR5Qmqzz7q3KD2VCcW6dG3NsDLfG9zd56B5kswL4,11004
5
+ droidrun/adb/manager.py,sha256=g7TuEOCnMTavHzCMtr3u6TPBhmZwE98CW9vBwdhdyyg,2728
6
+ droidrun/adb/wrapper.py,sha256=Yz3_JSIidq-5bf-t0UfawTutMaLrINS_1Y15m_Uss4g,7093
7
+ droidrun/agent/__init__.py,sha256=4SqTJeGDvr_wT8rtN9J8hnN6P-pae663mkYr-JmzH4w,208
8
+ droidrun/agent/codeact/__init__.py,sha256=ZLDGT_lTTzyNm7pcBzdyRIGHJ2ZgbInJdhXZRbJLhSQ,278
9
+ droidrun/agent/codeact/codeact_agent.py,sha256=z0c10d1vk2PBciYbHLwnYlEopvwZzJhXkXqjJuUI0v4,16045
10
+ droidrun/agent/codeact/events.py,sha256=skCfZ-5SR0YhhzZVxx8_VkUjfILk8rCv47k9pHNYhdc,634
11
+ droidrun/agent/codeact/prompts.py,sha256=28HflWMNkC1ky0hGCzAxhJftjU2IIU1ZRUfya3S7M6I,1006
12
+ droidrun/agent/common/default.py,sha256=P07el-PrHsoqQMYsYxSSln6mFl-QY75vzwp1Dll_XmY,259
13
+ droidrun/agent/common/events.py,sha256=axauKdrXFEeB1hpd0geazwBtJySnHsvLZV0u9F9OWZI,96
14
+ droidrun/agent/context/__init__.py,sha256=upSJilVQUwQYWJuGr_8QrmJaReV3SNAc_Z61wQZzbK4,697
15
+ droidrun/agent/context/agent_persona.py,sha256=Mxd4HTyirWD-aqNlka1hQBdS-0I-lXJr2AjPMwDMUo8,390
16
+ droidrun/agent/context/context_injection_manager.py,sha256=sA33q2KPtX_4Yap8wM11T6ewlZC_0FIbKPEc400SHrE,2188
17
+ droidrun/agent/context/episodic_memory.py,sha256=1ImeR3jAWOpKwkQt3bMlXVOBiQbIli5fBIlBq2waREQ,394
18
+ droidrun/agent/context/reflection.py,sha256=0hJluOz0hTlHHhReKpIJ9HU5aJbaJsvrjMfraQ84D-M,652
19
+ droidrun/agent/context/task_manager.py,sha256=ESLs4kR6VNYiYQsc4V7WAeoSLwbaZPSWBXpveOfOv8c,4343
20
+ droidrun/agent/context/personas/__init__.py,sha256=tUDIM_9Kim3ki6ZXpwcvPHPSa2cCHNMLdNW4lyJr4gM,231
21
+ droidrun/agent/context/personas/app_starter.py,sha256=dHeknznxGEPJ7S6VPyEG_MB-HvAvQwUOnRWaShaV8Xo,1585
22
+ droidrun/agent/context/personas/default.py,sha256=Xm07YCWoKjvlHAbQRtzE3vn7BVcz6wYcSVeg4FiojJQ,5060
23
+ droidrun/agent/context/personas/extractor.py,sha256=S7Qgh3-Kz_OowQJFJGQUlr8OUVktX5UHVdf5qkAj3GQ,1907
24
+ droidrun/agent/context/personas/ui_expert.py,sha256=P2dAnkKiR3O4bN4ZUuWHmuu4Qo8WRiH1mU6EEOr3yJA,4710
25
+ droidrun/agent/droid/__init__.py,sha256=3BfUVZiUQ8ATAJ_JmqQZQx53WoERRpQ4AyHW5WOgbRI,297
26
+ droidrun/agent/droid/droid_agent.py,sha256=ELPQOpRYQcnB64rLOoVPk12GHuQ_EFs7fMH-7t0LeQM,13365
27
+ droidrun/agent/droid/events.py,sha256=bpUhJnbaIH86r8hfUsbHub465v-xYOsIyHWNN8g4TCs,576
28
+ droidrun/agent/oneflows/reflector.py,sha256=I_tE0PBjvwWbS6SA8Qd41etxJglFgn8oScuKUxc9LEE,11621
29
+ droidrun/agent/planner/__init__.py,sha256=Fu0Ewtd-dIRLgHIL1DB_9EEKvQS_f1vjB8jgO5TbJXg,364
30
+ droidrun/agent/planner/events.py,sha256=oyt2FNrA2uVyUeVT65-N0AC6sWBFxSnwNEqWtnRYoFM,390
31
+ droidrun/agent/planner/planner_agent.py,sha256=vcZx8tawMss0F2nt-4QEtdF7kSaikUq3rdWPdTNicjk,10494
32
+ droidrun/agent/planner/prompts.py,sha256=Ci7Oeu3J4TAhx-tKGPZ9l6Wb3a81FSqC8cWW4jW73HI,6046
33
+ droidrun/agent/utils/__init__.py,sha256=JK6ygRjw7gzcQSG0HBEYLoVGH54QQAxJJ7HpIS5mgyc,44
34
+ droidrun/agent/utils/async_utils.py,sha256=IQBcWPwevm89B7R_UdMXk0unWeNCBA232b5kQGqoxNI,336
35
+ droidrun/agent/utils/chat_utils.py,sha256=5oqP2nmKs8sHWP1H_TK82yaxrxWf7FdEbFKASKpR60g,13000
36
+ droidrun/agent/utils/executer.py,sha256=lQbk2TbPyl_F0k2FqEVimq8cQRcWM_KCO_I7fnkkxqA,4587
37
+ droidrun/agent/utils/llm_picker.py,sha256=adktsU2-GhYzZ0djOhO_TyFhUE8aGirzJy36A_EvnXU,5919
38
+ droidrun/agent/utils/trajectory.py,sha256=OmO8TvNO0LVtPXg2qTCv8o9ePaMeDyf-MRWN_YObXho,6845
39
+ droidrun/cli/__init__.py,sha256=DuwSRtZ8WILPd-nf-fZ7BaBsRgtofoInOF3JtJ9wag0,167
40
+ droidrun/cli/logs.py,sha256=PsT_VbnOa_sOLXK4KkEJk4AsYCpscqrVoryMmLVwPG0,9714
41
+ droidrun/cli/main.py,sha256=3VP5cZ0a7bKcNnqwMQ8mBvaprPcjgM7394WBRdkKDKU,15059
42
+ droidrun/tools/__init__.py,sha256=9ReauavtSKDQG9ya9_Fr9O0TQnDFixgOPaP5n82_iEk,271
43
+ droidrun/tools/adb.py,sha256=1Nv8tkmgqGlOZJYe39W5SGwdqnEjrxN7szsLQ_BkWqo,30137
44
+ droidrun/tools/ios.py,sha256=-fq4VNJFVTkR4xtUW9IVyUXwqnhsJWda_fik3II2EGg,22885
45
+ droidrun/tools/tools.py,sha256=EH3hzrVUHEkTUT-PF2s9NHyNntV9xoNXskmNBcZwLm4,2385
46
+ droidrun-0.3.1.dist-info/METADATA,sha256=8lZqTnD2aoQOcpRKn6TL7rC0IdshMhAUAnyY9QWCKmQ,6344
47
+ droidrun-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
48
+ droidrun-0.3.1.dist-info/entry_points.txt,sha256=o259U66js8TIybQ7zs814Oe_LQ_GpZsp6a9Cr-xm5zE,51
49
+ droidrun-0.3.1.dist-info/licenses/LICENSE,sha256=s-uxn9qChu-kFdRXUp6v_0HhsaJ_5OANmfNOFVm2zdk,1069
50
+ droidrun-0.3.1.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