mindroot 9.24.0__py3-none-any.whl → 10.0.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.
Potentially problematic release.
This version of mindroot might be problematic. Click here for more details.
- mindroot/coreplugins/chat/static/js/chat.js +13 -2
- mindroot/coreplugins/chat/static/js/chatform.js +1 -1
- mindroot/coreplugins/check_list/helpers.py +221 -0
- mindroot/coreplugins/check_list/mod.py +383 -126
- mindroot/coreplugins/check_list/mod.py.backup +378 -0
- mindroot/coreplugins/check_list/mod.py.backup2 +506 -0
- mindroot/coreplugins/check_list/mod.py.backup3 +506 -0
- mindroot/coreplugins/check_list/plugin_info.json +14 -0
- mindroot/lib/buchatlog3.py +592 -0
- mindroot/lib/chatlog.py +155 -5
- {mindroot-9.24.0.dist-info → mindroot-10.0.0.dist-info}/METADATA +1 -1
- {mindroot-9.24.0.dist-info → mindroot-10.0.0.dist-info}/RECORD +16 -10
- {mindroot-9.24.0.dist-info → mindroot-10.0.0.dist-info}/WHEEL +0 -0
- {mindroot-9.24.0.dist-info → mindroot-10.0.0.dist-info}/entry_points.txt +0 -0
- {mindroot-9.24.0.dist-info → mindroot-10.0.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-9.24.0.dist-info → mindroot-10.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,100 +1,131 @@
|
|
|
1
1
|
"""
|
|
2
|
-
checklist helper for an LLM
|
|
3
|
-
|
|
2
|
+
checklist helper for an LLM-agent system
|
|
3
|
+
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
|
4
4
|
Purpose
|
|
5
5
|
-------
|
|
6
|
-
• Parse a Markdown "##\tChecklist" section where each subtask is
|
|
7
|
-
written as a task‑list line:
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
•
|
|
13
|
-
|
|
14
|
-
• Runtime helpers:
|
|
15
|
-
load_checklist(md, ctx) → parse markdown + reset state
|
|
16
|
-
complete_subtask(subtask_id, context) → mark subtask done, advance cursor
|
|
17
|
-
goto_subtask(subtask_id, context) → move to a specific subtask
|
|
18
|
-
clear_subtask(subtask_id, context) → mark a subtask as incomplete
|
|
19
|
-
get_checklist_status(context) → show the full checklist status
|
|
7
|
+
• Parse a Markdown checklist section where each subtask is written as a task-list line
|
|
8
|
+
• Store parsed tasks + cursor in `context.data['checklist']`
|
|
9
|
+
• Support nested subtasks within parent task bodies
|
|
10
|
+
• Runtime helpers for managing both top-level and nested subtasks
|
|
20
11
|
|
|
21
12
|
`complete_subtask` and other commands live at module level so the agent can call them
|
|
22
|
-
as tools. No third
|
|
13
|
+
as tools. No third-party deps—only Python's `re` module.
|
|
23
14
|
"""
|
|
24
15
|
|
|
25
16
|
import re
|
|
26
|
-
from lib.providers.commands import command
|
|
17
|
+
from lib.providers.commands import command, command_manager
|
|
27
18
|
from lib.providers.services import service
|
|
28
19
|
import traceback
|
|
20
|
+
from collections import Counter
|
|
21
|
+
from .helpers import (
|
|
22
|
+
find_nested_subtask,
|
|
23
|
+
update_nested_subtask_status,
|
|
24
|
+
resolve_subtask_id_with_nesting,
|
|
25
|
+
format_nested_task_status,
|
|
26
|
+
get_next_incomplete_task,
|
|
27
|
+
has_incomplete_nested_tasks
|
|
28
|
+
)
|
|
29
29
|
|
|
30
|
-
# ---------- parsing
|
|
30
|
+
# ---------- simple parsing -----------------------------------------------
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
def _parse(text):
|
|
33
|
+
"""Simple, robust checklist parser.
|
|
34
|
+
|
|
35
|
+
Finds checklist items (- [ ] or - [x]) at any indentation level,
|
|
36
|
+
but only processes items at the same indentation as the first one found.
|
|
37
|
+
|
|
38
|
+
Returns list of {label, body, done} dicts.
|
|
39
|
+
"""
|
|
40
|
+
print("parsing:")
|
|
41
|
+
print("---------------------------------------------")
|
|
42
|
+
print(text)
|
|
43
|
+
print('---------------------------------------------')
|
|
44
|
+
lines = text.splitlines()
|
|
45
|
+
tasks = []
|
|
46
|
+
i = 0
|
|
47
|
+
first_task_indent = None
|
|
48
|
+
|
|
49
|
+
# Find all task lines and their indentations first
|
|
50
|
+
task_indents = []
|
|
51
|
+
for line in lines:
|
|
52
|
+
stripped = line.lstrip()
|
|
53
|
+
if stripped.startswith('- [ ]') or stripped.startswith('- [x]') or stripped.startswith('- [X]'):
|
|
54
|
+
indent = len(line) - len(stripped)
|
|
55
|
+
task_indents.append(indent)
|
|
56
|
+
|
|
57
|
+
if not task_indents:
|
|
58
|
+
return tasks
|
|
59
|
+
|
|
60
|
+
# Use the SMALLEST indentation level as reference (top-level tasks)
|
|
61
|
+
reference_indent = min(task_indents)
|
|
62
|
+
print("reference indent:", reference_indent)
|
|
39
63
|
while i < len(lines):
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
64
|
+
line = lines[i]
|
|
65
|
+
stripped = line.lstrip()
|
|
66
|
+
print(i)
|
|
67
|
+
# Check if this line is a checklist item
|
|
68
|
+
if stripped.startswith('- [ ]') or stripped.startswith('- [x]') or stripped.startswith('- [X]'):
|
|
69
|
+
# Calculate indentation
|
|
70
|
+
current_indent = len(line) - len(stripped)
|
|
71
|
+
print("found task at indent:", current_indent)
|
|
72
|
+
print("reference indent:", reference_indent)
|
|
73
|
+
# Only process tasks at the reference indentation level
|
|
74
|
+
if current_indent == reference_indent:
|
|
75
|
+
# Extract task info
|
|
76
|
+
done = stripped.startswith('- [x]') or stripped.startswith('- [X]')
|
|
77
|
+
|
|
78
|
+
# Extract label (everything after the checkbox)
|
|
79
|
+
if stripped.startswith('- [ ]'):
|
|
80
|
+
label = stripped[5:].strip()
|
|
81
|
+
else: # - [x] or - [X]
|
|
82
|
+
label = stripped[5:].strip()
|
|
83
|
+
|
|
84
|
+
# Collect body lines until next task at same level or end
|
|
85
|
+
body_lines = []
|
|
86
|
+
i += 1
|
|
87
|
+
|
|
88
|
+
while i < len(lines):
|
|
89
|
+
print("checking line for body:",i, lines[i])
|
|
90
|
+
next_line = lines[i]
|
|
91
|
+
next_stripped = next_line.lstrip()
|
|
92
|
+
next_indent = len(next_line) - len(next_stripped)
|
|
93
|
+
print("next_stripped:", next_stripped)
|
|
94
|
+
print("next_indent:", next_indent)
|
|
95
|
+
print("reference_indent:", reference_indent)
|
|
96
|
+
# Stop if we hit another task at the same indentation level
|
|
97
|
+
if ((next_stripped.startswith('- [ ]') or
|
|
98
|
+
next_stripped.startswith('- [x]') or
|
|
99
|
+
next_stripped.startswith('- [X]')) and
|
|
100
|
+
next_indent <= reference_indent):
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
# Stop if we hit a markdown heading
|
|
104
|
+
if next_stripped.startswith('#'):
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
body_lines.append(next_line)
|
|
108
|
+
i += 1
|
|
109
|
+
|
|
110
|
+
tasks.append({
|
|
111
|
+
'label': label,
|
|
112
|
+
'body': '\n'.join(body_lines), #.strip(),
|
|
113
|
+
'done': done
|
|
114
|
+
})
|
|
115
|
+
continue
|
|
116
|
+
|
|
50
117
|
i += 1
|
|
51
|
-
|
|
52
|
-
TASK_RE.match(lines[i]) or HEADER_RE.match(lines[i])
|
|
53
|
-
):
|
|
54
|
-
body_lines.append(lines[i])
|
|
55
|
-
i += 1
|
|
56
|
-
|
|
57
|
-
tasks.append({
|
|
58
|
-
"label": label,
|
|
59
|
-
"body": "\n".join(body_lines).strip(),
|
|
60
|
-
"done": done,
|
|
61
|
-
})
|
|
118
|
+
|
|
62
119
|
return tasks
|
|
63
120
|
|
|
64
121
|
|
|
65
122
|
def extract_checklist_section(md: str):
|
|
66
|
-
"""Extract
|
|
123
|
+
"""Extract checklist items from the entire markdown text.
|
|
67
124
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
or the end of the text.
|
|
71
|
-
|
|
72
|
-
Returns the extracted section, or the original text if no checklist section is found.
|
|
125
|
+
No longer requires a specific 'Checklist' heading - processes the entire text
|
|
126
|
+
and extracts only top-level checklist items, leaving nested ones intact.
|
|
73
127
|
"""
|
|
74
|
-
|
|
75
|
-
start_idx = None
|
|
76
|
-
checklist_level = 0
|
|
77
|
-
|
|
78
|
-
# Find the checklist heading
|
|
79
|
-
for i, line in enumerate(lines):
|
|
80
|
-
match = CHECKLIST_HEADER_RE.match(line)
|
|
81
|
-
if match:
|
|
82
|
-
start_idx = i
|
|
83
|
-
# Count the number of # to determine heading level
|
|
84
|
-
checklist_level = len(line) - len(line.lstrip('#'))
|
|
85
|
-
break
|
|
86
|
-
|
|
87
|
-
if start_idx is None:
|
|
88
|
-
return md # No checklist section found, return original text
|
|
89
|
-
|
|
90
|
-
# Find the end of the section (next heading of same or lower level)
|
|
91
|
-
end_idx = len(lines)
|
|
92
|
-
for i in range(start_idx + 1, len(lines)):
|
|
93
|
-
if HEADER_RE.match(lines[i]) and len(lines[i]) - len(lines[i].lstrip('#')) <= checklist_level:
|
|
94
|
-
end_idx = i
|
|
95
|
-
break
|
|
96
|
-
|
|
97
|
-
return "\n".join(lines[start_idx:end_idx])
|
|
128
|
+
return md
|
|
98
129
|
|
|
99
130
|
|
|
100
131
|
# ---------- state helpers ------------------------------------------------
|
|
@@ -115,12 +146,9 @@ def load_checklist(md: str, ctx):
|
|
|
115
146
|
|
|
116
147
|
@service()
|
|
117
148
|
async def load_checklist_from_instructions(md: str, context=None):
|
|
118
|
-
"""
|
|
149
|
+
"""Load checklist from instructions.
|
|
119
150
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
Example:
|
|
123
|
-
{ "load_checklist_from_instructions": { "md": "Full instructions with embedded checklist" } }
|
|
151
|
+
Processes the entire text and extracts top-level checklist items.
|
|
124
152
|
"""
|
|
125
153
|
checklist_section = extract_checklist_section(md)
|
|
126
154
|
load_checklist(checklist_section, context)
|
|
@@ -130,31 +158,17 @@ async def load_checklist_from_instructions(md: str, context=None):
|
|
|
130
158
|
# ---------- helper functions ---------------------------------------------
|
|
131
159
|
|
|
132
160
|
def _resolve_subtask_id(subtask_id, context):
|
|
133
|
-
"""Resolve a subtask_id to an index (0-based).
|
|
161
|
+
"""Resolve a subtask_id to an index (0-based) with nested task support.
|
|
134
162
|
|
|
135
163
|
subtask_id can be:
|
|
136
164
|
- A number (1-based index, converted to 0-based)
|
|
137
|
-
- A string matching a subtask label
|
|
165
|
+
- A string matching a subtask label (top-level or nested)
|
|
138
166
|
- None/default (-1) to use the current cursor position
|
|
167
|
+
|
|
168
|
+
Returns tuple of (index, nested_info) where nested_info is None for top-level tasks
|
|
139
169
|
"""
|
|
140
170
|
st = _state(context)
|
|
141
|
-
|
|
142
|
-
# Default to current cursor position
|
|
143
|
-
if subtask_id is None or subtask_id == -1:
|
|
144
|
-
return st["cursor"]
|
|
145
|
-
|
|
146
|
-
# If it's a number, convert from 1-based to 0-based
|
|
147
|
-
if isinstance(subtask_id, int):
|
|
148
|
-
idx = subtask_id - 1
|
|
149
|
-
else:
|
|
150
|
-
# It's a string, try to find a matching label
|
|
151
|
-
for i, task in enumerate(st["tasks"]):
|
|
152
|
-
if task["label"] == subtask_id:
|
|
153
|
-
return i
|
|
154
|
-
# No match found
|
|
155
|
-
return -1
|
|
156
|
-
|
|
157
|
-
return idx
|
|
171
|
+
return resolve_subtask_id_with_nesting(subtask_id, st["tasks"], st["cursor"])
|
|
158
172
|
|
|
159
173
|
|
|
160
174
|
def _format_checklist_status(context):
|
|
@@ -176,25 +190,34 @@ def _format_checklist_status(context):
|
|
|
176
190
|
|
|
177
191
|
# Add task line
|
|
178
192
|
lines.append(f"{status}**Subtask**: {task['label']}")
|
|
193
|
+
|
|
194
|
+
# Show nested task status if any
|
|
195
|
+
nested_tasks = _parse(task['body'])
|
|
196
|
+
if nested_tasks:
|
|
197
|
+
for nested_task in nested_tasks:
|
|
198
|
+
nested_status = "✅" if nested_task["done"] else "❌"
|
|
199
|
+
lines.append(f" {nested_status} {nested_task['label']}")
|
|
179
200
|
|
|
180
201
|
return "\n".join(lines)
|
|
181
202
|
|
|
182
203
|
|
|
183
|
-
# ---------- module
|
|
204
|
+
# ---------- module-level tool commands ----------------------------------
|
|
184
205
|
|
|
185
206
|
@command()
|
|
186
207
|
async def complete_subtask(subtask_id=None, context=None):
|
|
187
208
|
"""
|
|
188
209
|
Mark a subtask complete and return a Markdown status message.
|
|
210
|
+
Now supports both top-level and nested subtasks.
|
|
189
211
|
|
|
190
212
|
Parameters:
|
|
191
213
|
- subtask_id: Optional. The subtask to complete, specified by:
|
|
192
|
-
- The exact subtask label text
|
|
214
|
+
- The exact subtask label text (top-level or nested)
|
|
193
215
|
- Omit to complete the current subtask
|
|
194
216
|
|
|
195
217
|
Example:
|
|
196
218
|
{ "complete_subtask": {} } # Complete current subtask
|
|
197
219
|
{ "complete_subtask": { "subtask_id": "Review documents" } } # Complete by label
|
|
220
|
+
{ "complete_subtask": { "subtask_id": "USP" } } # Complete nested subtask
|
|
198
221
|
"""
|
|
199
222
|
if context is None:
|
|
200
223
|
return "_Context is required._"
|
|
@@ -213,19 +236,56 @@ async def complete_subtask(subtask_id=None, context=None):
|
|
|
213
236
|
print(trace)
|
|
214
237
|
return "_No checklist found. Make sure to include a checklist in your instructions._"
|
|
215
238
|
|
|
216
|
-
idx = _resolve_subtask_id(subtask_id, context)
|
|
239
|
+
idx, nested_info = _resolve_subtask_id(subtask_id, context)
|
|
217
240
|
if idx < 0 or idx >= len(st["tasks"]):
|
|
218
241
|
return "_Invalid subtask identifier._"
|
|
219
242
|
|
|
220
|
-
#
|
|
243
|
+
# Handle nested subtask completion
|
|
244
|
+
if nested_info is not None:
|
|
245
|
+
# Mark nested subtask as done
|
|
246
|
+
nested_info['nested_task']['done'] = True
|
|
247
|
+
|
|
248
|
+
# Update the parent task's body with the new nested task status
|
|
249
|
+
parent_task = st["tasks"][idx]
|
|
250
|
+
updated_body = update_nested_subtask_status(
|
|
251
|
+
parent_task,
|
|
252
|
+
nested_info['nested_index'],
|
|
253
|
+
nested_info['parent_nested_tasks'],
|
|
254
|
+
True
|
|
255
|
+
)
|
|
256
|
+
parent_task['body'] = updated_body
|
|
257
|
+
|
|
258
|
+
# Check if all nested tasks in this parent are complete
|
|
259
|
+
all_nested_complete = all(task['done'] for task in nested_info['parent_nested_tasks'])
|
|
260
|
+
if all_nested_complete:
|
|
261
|
+
parent_task['done'] = True
|
|
262
|
+
# Advance cursor to next incomplete top-level task
|
|
263
|
+
st["cursor"] = get_next_incomplete_task(st["tasks"], idx + 1)
|
|
264
|
+
|
|
265
|
+
completed_msg = f"Completed Nested Subtask: - [x] {nested_info['nested_task']['label']} (within '{parent_task['label']}')"
|
|
266
|
+
|
|
267
|
+
if all_nested_complete:
|
|
268
|
+
completed_msg += f"\n\nParent task '{parent_task['label']}' is now complete!"
|
|
269
|
+
|
|
270
|
+
# Show next task info
|
|
271
|
+
if st["cursor"] >= len(st["tasks"]):
|
|
272
|
+
return f"{completed_msg}\n\nAll subtasks complete ✅\n\n{_format_checklist_status(context)}"
|
|
273
|
+
|
|
274
|
+
next_task = st["tasks"][st['cursor']]
|
|
275
|
+
return f"""{completed_msg}
|
|
276
|
+
|
|
277
|
+
Next subtask (Subtask {st['cursor']+1})
|
|
278
|
+
- [ ] {next_task['label']}
|
|
279
|
+
{next_task['body']}
|
|
280
|
+
|
|
281
|
+
{_format_checklist_status(context)}"""
|
|
282
|
+
|
|
283
|
+
# Handle top-level subtask completion
|
|
221
284
|
done_task = st["tasks"][idx]
|
|
222
285
|
done_task["done"] = True
|
|
223
286
|
|
|
224
287
|
# advance cursor to next open subtask
|
|
225
|
-
st["cursor"] =
|
|
226
|
-
(i for i, t in enumerate(st["tasks"][idx + 1:], idx + 1) if not t["done"]),
|
|
227
|
-
len(st["tasks"]),
|
|
228
|
-
)
|
|
288
|
+
st["cursor"] = get_next_incomplete_task(st["tasks"], idx + 1)
|
|
229
289
|
|
|
230
290
|
# build markdown response
|
|
231
291
|
completed = f"Completed Subtask {idx+1}: - [x] {done_task['label']}"
|
|
@@ -233,27 +293,28 @@ async def complete_subtask(subtask_id=None, context=None):
|
|
|
233
293
|
return f"{completed}\n\nAll subtasks complete ✅\n\n{_format_checklist_status(context)}"
|
|
234
294
|
|
|
235
295
|
next_task = st["tasks"][st['cursor']]
|
|
236
|
-
return f"""
|
|
237
|
-
{completed}
|
|
296
|
+
return f"""{completed}
|
|
238
297
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
298
|
+
Next subtask (Subtask {st['cursor']+1})
|
|
299
|
+
- [ ] {next_task['label']}
|
|
300
|
+
{next_task['body']}
|
|
301
|
+
|
|
302
|
+
{_format_checklist_status(context)}"""
|
|
242
303
|
|
|
243
|
-
{_format_checklist_status(context)}
|
|
244
|
-
"""
|
|
245
304
|
|
|
246
305
|
@command()
|
|
247
306
|
async def goto_subtask(subtask_id, context=None):
|
|
248
307
|
"""
|
|
249
308
|
Move to a specific subtask without changing its completion status.
|
|
309
|
+
Now supports both top-level and nested subtasks.
|
|
250
310
|
|
|
251
311
|
Parameters:
|
|
252
312
|
- subtask_id: Required. The subtask to navigate to, specified by:
|
|
253
|
-
- The exact subtask label text
|
|
313
|
+
- The exact subtask label text (top-level or nested)
|
|
254
314
|
|
|
255
315
|
Example:
|
|
256
|
-
{ "goto_subtask": { "subtask_id": "Data analysis" } } # Go to
|
|
316
|
+
{ "goto_subtask": { "subtask_id": "Data analysis" } } # Go to top-level task
|
|
317
|
+
{ "goto_subtask": { "subtask_id": "USP" } } # Go to nested task
|
|
257
318
|
"""
|
|
258
319
|
if context is None:
|
|
259
320
|
return "_Context is required._"
|
|
@@ -262,17 +323,22 @@ async def goto_subtask(subtask_id, context=None):
|
|
|
262
323
|
if not st["tasks"]:
|
|
263
324
|
return "_No checklist found. Make sure to include a checklist in your instructions._"
|
|
264
325
|
|
|
265
|
-
idx = _resolve_subtask_id(subtask_id, context)
|
|
326
|
+
idx, nested_info = _resolve_subtask_id(subtask_id, context)
|
|
266
327
|
if idx < 0 or idx >= len(st["tasks"]):
|
|
267
328
|
return "_Invalid subtask identifier._"
|
|
268
329
|
|
|
269
|
-
# Update cursor position
|
|
330
|
+
# Update cursor position to the parent task
|
|
270
331
|
st["cursor"] = idx
|
|
271
332
|
|
|
272
|
-
#
|
|
273
|
-
|
|
333
|
+
# Handle nested subtask navigation
|
|
334
|
+
if nested_info is not None:
|
|
335
|
+
return (
|
|
336
|
+
f"Moved to Nested Subtask: {format_nested_task_status(nested_info)}\n\n"
|
|
337
|
+
f"{_format_checklist_status(context)}"
|
|
338
|
+
)
|
|
274
339
|
|
|
275
|
-
#
|
|
340
|
+
# Handle top-level subtask navigation
|
|
341
|
+
current_task = st["tasks"][idx]
|
|
276
342
|
status = "✅" if current_task["done"] else "❌"
|
|
277
343
|
return (
|
|
278
344
|
f"Moved to Subtask {idx+1}: {status} {current_task['label']}\n"
|
|
@@ -285,15 +351,17 @@ async def goto_subtask(subtask_id, context=None):
|
|
|
285
351
|
async def clear_subtask(subtask_id=None, context=None):
|
|
286
352
|
"""
|
|
287
353
|
Mark a subtask as incomplete (not done).
|
|
354
|
+
Now supports both top-level and nested subtasks.
|
|
288
355
|
|
|
289
356
|
Parameters:
|
|
290
357
|
- subtask_id: Optional. The subtask to clear, specified by:
|
|
291
|
-
- The exact subtask label text
|
|
358
|
+
- The exact subtask label text (top-level or nested)
|
|
292
359
|
- Omit to clear the current subtask
|
|
293
360
|
|
|
294
361
|
Example:
|
|
295
362
|
{ "clear_subtask": {} } # Clear current subtask
|
|
296
|
-
{ "clear_subtask": { "subtask_id": "Review documents" } } # Clear by label
|
|
363
|
+
{ "clear_subtask": { "subtask_id": "Review documents" } } # Clear top-level by label
|
|
364
|
+
{ "clear_subtask": { "subtask_id": "USP" } } # Clear nested subtask
|
|
297
365
|
"""
|
|
298
366
|
if context is None:
|
|
299
367
|
return "_Context is required._"
|
|
@@ -302,11 +370,43 @@ async def clear_subtask(subtask_id=None, context=None):
|
|
|
302
370
|
if not st["tasks"]:
|
|
303
371
|
return "_No checklist found. Make sure to include a checklist in your instructions._"
|
|
304
372
|
|
|
305
|
-
idx = _resolve_subtask_id(subtask_id, context)
|
|
373
|
+
idx, nested_info = _resolve_subtask_id(subtask_id, context)
|
|
306
374
|
if idx < 0 or idx >= len(st["tasks"]):
|
|
307
375
|
return "_Invalid subtask identifier._"
|
|
308
376
|
|
|
309
|
-
#
|
|
377
|
+
# Handle nested subtask clearing
|
|
378
|
+
if nested_info is not None:
|
|
379
|
+
# Mark nested subtask as not done
|
|
380
|
+
nested_task = nested_info['nested_task']
|
|
381
|
+
was_done = nested_task['done']
|
|
382
|
+
nested_task['done'] = False
|
|
383
|
+
|
|
384
|
+
# Update the parent task's body
|
|
385
|
+
parent_task = st["tasks"][idx]
|
|
386
|
+
updated_body = update_nested_subtask_status(
|
|
387
|
+
parent_task,
|
|
388
|
+
nested_info['nested_index'],
|
|
389
|
+
nested_info['parent_nested_tasks'],
|
|
390
|
+
False
|
|
391
|
+
)
|
|
392
|
+
parent_task['body'] = updated_body
|
|
393
|
+
|
|
394
|
+
# If parent was complete but now has incomplete nested tasks, mark parent incomplete
|
|
395
|
+
if parent_task['done'] and has_incomplete_nested_tasks(parent_task):
|
|
396
|
+
parent_task['done'] = False
|
|
397
|
+
|
|
398
|
+
# Update cursor to this parent task if needed
|
|
399
|
+
if idx <= st["cursor"]:
|
|
400
|
+
st["cursor"] = idx
|
|
401
|
+
|
|
402
|
+
action = "Cleared" if was_done else "Already clear"
|
|
403
|
+
return (
|
|
404
|
+
f"{action} Nested Subtask: - [ ] {nested_task['label']} (within '{parent_task['label']}')\n"
|
|
405
|
+
f"Current subtask is now Subtask {st['cursor']+1}\n\n"
|
|
406
|
+
f"{_format_checklist_status(context)}"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
# Handle top-level subtask clearing
|
|
310
410
|
task = st["tasks"][idx]
|
|
311
411
|
was_done = task["done"]
|
|
312
412
|
task["done"] = False
|
|
@@ -336,3 +436,160 @@ async def get_checklist_status(context=None):
|
|
|
336
436
|
return "_Context is required._"
|
|
337
437
|
|
|
338
438
|
return _format_checklist_status(context)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@command()
|
|
442
|
+
async def get_parsed_subtasks(subtask_id=None, context=None):
|
|
443
|
+
"""
|
|
444
|
+
Return parsed subtasks with their name/id and body for verification.
|
|
445
|
+
Now supports getting nested subtasks from within parent tasks.
|
|
446
|
+
|
|
447
|
+
Parameters:
|
|
448
|
+
- subtask_id: Optional. If provided, parse subtasks from the body of this specific subtask.
|
|
449
|
+
If omitted, returns all top-level subtasks from the main checklist.
|
|
450
|
+
|
|
451
|
+
Returns a list of dictionaries with:
|
|
452
|
+
- label: The subtask name/label
|
|
453
|
+
- body: The subtask body content
|
|
454
|
+
- done: Whether the subtask is marked as complete
|
|
455
|
+
- index: The 0-based index of the subtask
|
|
456
|
+
|
|
457
|
+
Example:
|
|
458
|
+
{ "get_parsed_subtasks": {} } # Get all top-level subtasks
|
|
459
|
+
{ "get_parsed_subtasks": { "subtask_id": "Research phase" } } # Get nested subtasks from "Research phase"
|
|
460
|
+
{ "get_parsed_subtasks": { "subtask_id": "Core" } } # Get nested subtasks from "Core"
|
|
461
|
+
"""
|
|
462
|
+
if context is None:
|
|
463
|
+
return "_Context is required._"
|
|
464
|
+
|
|
465
|
+
st = _state(context)
|
|
466
|
+
if not st["tasks"]:
|
|
467
|
+
try:
|
|
468
|
+
print("Loading checklist from instructions...")
|
|
469
|
+
instructions = context.agent["instructions"]
|
|
470
|
+
await load_checklist_from_instructions(instructions, context)
|
|
471
|
+
st = _state(context) # Refresh state after loading
|
|
472
|
+
except Exception as e:
|
|
473
|
+
print(f"Error loading checklist: {e}")
|
|
474
|
+
return "_No checklist found. Make sure to include a checklist in your instructions._"
|
|
475
|
+
|
|
476
|
+
# If no subtask_id provided, return all top-level subtasks
|
|
477
|
+
if subtask_id is None:
|
|
478
|
+
result = []
|
|
479
|
+
for i, task in enumerate(st["tasks"]):
|
|
480
|
+
result.append({
|
|
481
|
+
"index": i,
|
|
482
|
+
"label": task["label"],
|
|
483
|
+
"body": task["body"],
|
|
484
|
+
"done": task["done"]
|
|
485
|
+
})
|
|
486
|
+
return {
|
|
487
|
+
"source": "top-level checklist",
|
|
488
|
+
"subtasks": result
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
# Find the specified subtask and parse its body for nested subtasks
|
|
492
|
+
idx, nested_info = _resolve_subtask_id(subtask_id, context)
|
|
493
|
+
if idx < 0 or idx >= len(st["tasks"]):
|
|
494
|
+
return "_Invalid subtask identifier._"
|
|
495
|
+
|
|
496
|
+
# If it's a nested subtask, get its nested tasks
|
|
497
|
+
if nested_info is not None:
|
|
498
|
+
target_task = nested_info['nested_task']
|
|
499
|
+
source_desc = f"nested subtasks from '{target_task['label']}' (within '{nested_info['parent_task']['label']}')"
|
|
500
|
+
else:
|
|
501
|
+
target_task = st["tasks"][idx]
|
|
502
|
+
source_desc = f"nested subtasks from '{target_task['label']}'"
|
|
503
|
+
|
|
504
|
+
print("Parsing nested subtasks from:", subtask_id)
|
|
505
|
+
nested_tasks = _parse(target_task["body"])
|
|
506
|
+
|
|
507
|
+
result = []
|
|
508
|
+
for i, task in enumerate(nested_tasks):
|
|
509
|
+
result.append({
|
|
510
|
+
"index": i,
|
|
511
|
+
"label": task["label"],
|
|
512
|
+
"body": task["body"],
|
|
513
|
+
"done": task["done"]
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
"source": source_desc,
|
|
518
|
+
"parent_task": {
|
|
519
|
+
"index": idx,
|
|
520
|
+
"label": target_task["label"],
|
|
521
|
+
"done": target_task["done"]
|
|
522
|
+
},
|
|
523
|
+
"subtasks": result
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@command()
|
|
528
|
+
async def delegate_subtask(subtask_id, details: str, agent=None, context=None):
|
|
529
|
+
"""
|
|
530
|
+
Delegate a subtask to an agent, automatically passing the subtask body as
|
|
531
|
+
instructions, along with any details you add.
|
|
532
|
+
Now supports both top-level and nested subtasks.
|
|
533
|
+
|
|
534
|
+
IMPORTANT: You can only delegate ONE task a time.
|
|
535
|
+
You must wait for this task delegation to complete before issuing
|
|
536
|
+
more delegate_subtask commands.
|
|
537
|
+
|
|
538
|
+
If agent is not specified, the current agent name will be used for the subtask.
|
|
539
|
+
|
|
540
|
+
IMPORTANT: Subtask ID may only contain alphanumerics; all other special characters are invalid.
|
|
541
|
+
|
|
542
|
+
Example:
|
|
543
|
+
{ "delegate_subtask": { "subtask_id": "Research",
|
|
544
|
+
"details": "Session data in /data/sess_1234/" }}
|
|
545
|
+
{ "delegate_subtask": { "subtask_id": "USP",
|
|
546
|
+
"details": "Focus on unique selling proposition analysis" }}
|
|
547
|
+
|
|
548
|
+
Note that you do not need to repeat the text of the subtask item from the checklist
|
|
549
|
+
in your details.
|
|
550
|
+
"""
|
|
551
|
+
st = _state(context)
|
|
552
|
+
if not st["tasks"]:
|
|
553
|
+
try:
|
|
554
|
+
print("Loading checklist from instructions...")
|
|
555
|
+
print("Agent is")
|
|
556
|
+
print(context.agent)
|
|
557
|
+
instructions = context.agent["instructions"]
|
|
558
|
+
await load_checklist_from_instructions(instructions, context)
|
|
559
|
+
except Exception as e:
|
|
560
|
+
print(f"Error loading checklist: {e}")
|
|
561
|
+
trace = traceback.format_exc()
|
|
562
|
+
print(trace)
|
|
563
|
+
return "_No checklist found. Make sure to include a checklist in your instructions._"
|
|
564
|
+
|
|
565
|
+
idx, nested_info = _resolve_subtask_id(subtask_id, context)
|
|
566
|
+
if idx < 0 or idx >= len(st["tasks"]):
|
|
567
|
+
return "_Invalid subtask identifier._"
|
|
568
|
+
|
|
569
|
+
# Get the appropriate task body for delegation
|
|
570
|
+
if nested_info is not None:
|
|
571
|
+
# Delegating a nested subtask
|
|
572
|
+
current_task = nested_info['nested_task']
|
|
573
|
+
task_context = f"nested subtask '{current_task['label']}' within parent task '{nested_info['parent_task']['label']}'"
|
|
574
|
+
else:
|
|
575
|
+
# Delegating a top-level subtask
|
|
576
|
+
current_task = st["tasks"][idx]
|
|
577
|
+
task_context = f"subtask '{current_task['label']}'"
|
|
578
|
+
|
|
579
|
+
subtask_body = current_task["body"]
|
|
580
|
+
|
|
581
|
+
reminder = f"""Important: you may see system instructions for the full process. However, you are to ONLY
|
|
582
|
+
do (or delegate) the specified part of the process and then return a task result. If you have a sub-checklist assigned,
|
|
583
|
+
use delegate_subtask as needed for complex checklist items or per user instructions.
|
|
584
|
+
|
|
585
|
+
You are working on {task_context}."""
|
|
586
|
+
|
|
587
|
+
instructions = f"You are working as part of a multi-step process. Please complete the following subtask:\n\n{subtask_body}\n\n{details}\n\n{reminder}\n"
|
|
588
|
+
|
|
589
|
+
if agent is None:
|
|
590
|
+
agent_name = context.agent["name"]
|
|
591
|
+
else:
|
|
592
|
+
agent_name = agent
|
|
593
|
+
|
|
594
|
+
return await command_manager.delegate_task(instructions, agent_name, context=context)
|
|
595
|
+
|