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.
Files changed (57) 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 +321 -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/context/todo.txt +4 -0
  25. droidrun/agent/droid/__init__.py +2 -2
  26. droidrun/agent/droid/droid_agent.py +264 -325
  27. droidrun/agent/droid/events.py +28 -0
  28. droidrun/agent/oneflows/reflector.py +265 -0
  29. droidrun/agent/planner/__init__.py +2 -4
  30. droidrun/agent/planner/events.py +9 -13
  31. droidrun/agent/planner/planner_agent.py +268 -0
  32. droidrun/agent/planner/prompts.py +33 -53
  33. droidrun/agent/utils/__init__.py +3 -0
  34. droidrun/agent/utils/async_utils.py +1 -40
  35. droidrun/agent/utils/chat_utils.py +268 -48
  36. droidrun/agent/utils/executer.py +49 -14
  37. droidrun/agent/utils/llm_picker.py +14 -10
  38. droidrun/agent/utils/trajectory.py +184 -0
  39. droidrun/cli/__init__.py +1 -1
  40. droidrun/cli/logs.py +283 -0
  41. droidrun/cli/main.py +333 -439
  42. droidrun/run.py +105 -0
  43. droidrun/tools/__init__.py +5 -10
  44. droidrun/tools/{actions.py → adb.py} +279 -238
  45. droidrun/tools/ios.py +594 -0
  46. droidrun/tools/tools.py +99 -0
  47. droidrun-0.3.0.dist-info/METADATA +149 -0
  48. droidrun-0.3.0.dist-info/RECORD +52 -0
  49. droidrun/agent/planner/task_manager.py +0 -355
  50. droidrun/agent/planner/workflow.py +0 -371
  51. droidrun/tools/device.py +0 -29
  52. droidrun/tools/loader.py +0 -60
  53. droidrun-0.2.0.dist-info/METADATA +0 -373
  54. droidrun-0.2.0.dist-info/RECORD +0 -32
  55. {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/WHEEL +0 -0
  56. {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/entry_points.txt +0 -0
  57. {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
+ [![GitHub stars](https://img.shields.io/github/stars/droidrun/droidrun?style=social)](https://github.com/droidrun/droidrun/stargazers)
60
+ [![Discord](https://img.shields.io/discord/1360219330318696488?color=7289DA&label=Discord&logo=discord&logoColor=white)](https://discord.gg/ZZbKEZZkwK)
61
+ [![Documentation](https://img.shields.io/badge/Documentation-📕-blue)](https://docs.droidrun.ai)
62
+ [![Benchmark](https://img.shields.io/badge/Benchmark-🏅-teal)](https://droidrun.ai/benchmark)
63
+ [![Twitter Follow](https://img.shields.io/twitter/follow/droid_run?style=social)](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
+ [![Shopping Assistant Demo](https://img.youtube.com/vi/VQK3JcifgwU/0.jpg)](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
+ [![Social Media Automation Demo](https://img.youtube.com/vi/i4-sDQhzt_M/0.jpg)](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