mindroot 10.2.0__py3-none-any.whl → 10.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.

Potentially problematic release.


This version of mindroot might be problematic. Click here for more details.

@@ -0,0 +1,756 @@
1
+ """
2
+ checklist helper for an LLM-agent system
3
+ ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
4
+ Purpose
5
+ -------
6
+
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
11
+
12
+ `complete_subtask` and other commands live at module level so the agent can call them
13
+ as tools. No third-party deps—only Python's `re` module.
14
+ """
15
+
16
+ import re
17
+ from lib.providers.commands import command, command_manager
18
+ from lib.providers.services import service
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
+
30
+ # ---------- simple parsing -----------------------------------------------
31
+
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)
63
+ while i < len(lines):
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
+
117
+ i += 1
118
+
119
+ return tasks
120
+
121
+
122
+ def extract_checklist_section(md: str):
123
+ """Extract checklist items from the entire markdown text.
124
+
125
+ No longer requires a specific 'Checklist' heading - processes the entire text
126
+ and extracts only top-level checklist items, leaving nested ones intact.
127
+ """
128
+ return md
129
+
130
+
131
+ # ---------- state helpers ------------------------------------------------
132
+
133
+ def _state(ctx):
134
+ """Return the mutable checklist state in ctx.data."""
135
+ return ctx.data.setdefault("checklist", {"tasks": [], "cursor": 0})
136
+
137
+ def load_checklist(md: str, ctx):
138
+ """Parse markdown and reset cursor to first unchecked subtask."""
139
+ st = _state(ctx)
140
+ st["tasks"] = _parse(md)
141
+ st["cursor"] = next(
142
+ (i for i, t in enumerate(st["tasks"]) if not t["done"]),
143
+ len(st["tasks"]),
144
+ )
145
+
146
+
147
+ @service()
148
+ async def load_checklist_from_instructions(md: str, context=None):
149
+ """Load checklist from instructions.
150
+
151
+ Processes the entire text and extracts top-level checklist items.
152
+ """
153
+ checklist_section = extract_checklist_section(md)
154
+ load_checklist(checklist_section, context)
155
+ return f"Loaded checklist from instructions.\n\n{_format_checklist_status(context)}"
156
+
157
+
158
+ # ---------- helper functions ---------------------------------------------
159
+
160
+ def _resolve_subtask_id(subtask_id, context):
161
+ """Resolve a subtask_id to an index (0-based) with nested task support.
162
+
163
+ subtask_id can be:
164
+ - A number (1-based index, converted to 0-based)
165
+ - A string matching a subtask label (top-level or nested)
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
169
+ """
170
+ st = _state(context)
171
+ return resolve_subtask_id_with_nesting(subtask_id, st["tasks"], st["cursor"])
172
+
173
+
174
+ def _format_checklist_status(context):
175
+ """Format the full checklist status as a markdown string."""
176
+ st = _state(context)
177
+ if not st["tasks"]:
178
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
179
+
180
+ lines = ["### Checklist Status\n"]
181
+
182
+ for i, task in enumerate(st["tasks"]):
183
+ # Determine status indicator
184
+ if i == st["cursor"]:
185
+ status = "➡️ " # Current task
186
+ elif task["done"]:
187
+ status = "✅ " # Completed
188
+ else:
189
+ status = "❌ " # Not completed
190
+
191
+ # Add task line
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']}")
200
+
201
+ return "\n".join(lines)
202
+
203
+
204
+ # ---------- module-level tool commands ----------------------------------
205
+
206
+ @command()
207
+ async def create_checklist(tasks, title=None, replace=True, context=None):
208
+ """
209
+ Create a new checklist dynamically from a list of task descriptions.
210
+
211
+ Parameters:
212
+ - tasks: Required. List of task description strings
213
+ - title: Optional. Title for the checklist (for display purposes)
214
+ - replace: Optional. Whether to replace existing checklist (default: True)
215
+
216
+ Example:
217
+ { "create_checklist": {
218
+ "tasks": [
219
+ "Research the topic",
220
+ "Create outline",
221
+ "Write first draft",
222
+ "Review and edit"
223
+ ],
224
+ "title": "Article Writing Process"
225
+ }}
226
+ """
227
+ if context is None:
228
+ return "_Context is required._"
229
+ if not tasks or not isinstance(tasks, list):
230
+ return "_Tasks parameter must be a non-empty list of task descriptions._"
231
+
232
+ # Check if we should replace existing checklist
233
+ st = _state(context)
234
+ if not replace and st["tasks"]:
235
+ return "_Checklist already exists. Use replace=True to overwrite it._"
236
+
237
+ # Convert task list to markdown checklist format
238
+ markdown_lines = []
239
+ if title:
240
+ markdown_lines.append(f"# {title}\n")
241
+
242
+ for task_desc in tasks:
243
+ if not isinstance(task_desc, str):
244
+ return "_All tasks must be strings._"
245
+ markdown_lines.append(f"- [ ] {task_desc.strip()}")
246
+
247
+ markdown_text = "\n".join(markdown_lines)
248
+
249
+ # Use existing load_checklist function to parse and store
250
+ load_checklist(markdown_text, context)
251
+
252
+ # Build response
253
+ title_text = f" '{title}'" if title else ""
254
+ return f"Created checklist{title_text} with {len(tasks)} tasks.\n\n{_format_checklist_status(context)}"
255
+
256
+
257
+ @command()
258
+ async def complete_subtask(subtask_id=None, context=None):
259
+ """
260
+ Mark a subtask complete and return a Markdown status message.
261
+ Now supports both top-level and nested subtasks.
262
+
263
+ Parameters:
264
+ - subtask_id: Optional. The subtask to complete, specified by:
265
+ - The exact subtask label text (top-level or nested)
266
+ - Omit to complete the current subtask
267
+
268
+ Example:
269
+ { "complete_subtask": {} } # Complete current subtask
270
+ { "complete_subtask": { "subtask_id": "Review documents" } } # Complete by label
271
+ { "complete_subtask": { "subtask_id": "USP" } } # Complete nested subtask
272
+ """
273
+ if context is None:
274
+ return "_Context is required._"
275
+
276
+ st = _state(context)
277
+ if not st["tasks"]:
278
+ try:
279
+ print("Loading checklist from instructions...")
280
+ print("Agent is")
281
+ print(context.agent)
282
+ instructions = context.agent["instructions"]
283
+ await load_checklist_from_instructions(instructions, context)
284
+ except Exception as e:
285
+ print(f"Error loading checklist: {e}")
286
+ trace = traceback.format_exc()
287
+ print(trace)
288
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
289
+
290
+ idx, nested_info = _resolve_subtask_id(subtask_id, context)
291
+ if idx < 0 or idx >= len(st["tasks"]):
292
+ return "_Invalid subtask identifier._"
293
+
294
+ # Handle nested subtask completion
295
+ if nested_info is not None:
296
+ # Mark nested subtask as done
297
+ nested_info['nested_task']['done'] = True
298
+
299
+ # Update the parent task's body with the new nested task status
300
+ parent_task = st["tasks"][idx]
301
+ updated_body = update_nested_subtask_status(
302
+ parent_task,
303
+ nested_info['nested_index'],
304
+ nested_info['parent_nested_tasks'],
305
+ True
306
+ )
307
+ parent_task['body'] = updated_body
308
+
309
+ # Check if all nested tasks in this parent are complete
310
+ all_nested_complete = all(task['done'] for task in nested_info['parent_nested_tasks'])
311
+ if all_nested_complete:
312
+ parent_task['done'] = True
313
+ # Advance cursor to next incomplete top-level task
314
+ st["cursor"] = get_next_incomplete_task(st["tasks"], idx + 1)
315
+
316
+ completed_msg = f"Completed Nested Subtask: - [x] {nested_info['nested_task']['label']} (within '{parent_task['label']}')"
317
+
318
+ if all_nested_complete:
319
+ completed_msg += f"\n\nParent task '{parent_task['label']}' is now complete!"
320
+
321
+ # Show next task info
322
+ if st["cursor"] >= len(st["tasks"]):
323
+ return f"{completed_msg}\n\nAll subtasks complete ✅\n\n{_format_checklist_status(context)}"
324
+
325
+ next_task = st["tasks"][st['cursor']]
326
+ return f"""{completed_msg}
327
+
328
+ Next subtask (Subtask {st['cursor']+1})
329
+ - [ ] {next_task['label']}
330
+ {next_task['body']}
331
+
332
+ {_format_checklist_status(context)}"""
333
+
334
+ # Handle top-level subtask completion
335
+ done_task = st["tasks"][idx]
336
+ done_task["done"] = True
337
+
338
+ # advance cursor to next open subtask
339
+ st["cursor"] = get_next_incomplete_task(st["tasks"], idx + 1)
340
+
341
+ # build markdown response
342
+ completed = f"Completed Subtask {idx+1}: - [x] {done_task['label']}"
343
+ if st["cursor"] >= len(st["tasks"]):
344
+ return f"{completed}\n\nAll subtasks complete ✅\n\n{_format_checklist_status(context)}"
345
+
346
+ next_task = st["tasks"][st['cursor']]
347
+ return f"""{completed}
348
+
349
+ Next subtask (Subtask {st['cursor']+1})
350
+ - [ ] {next_task['label']}
351
+ {next_task['body']}
352
+
353
+ {_format_checklist_status(context)}"""
354
+
355
+
356
+ @command()
357
+ async def create_checklist(tasks, title=None, replace=True, context=None):
358
+ """
359
+ Create a new checklist dynamically from a list of task descriptions.
360
+
361
+ Parameters:
362
+ - tasks: Required. List of task description strings
363
+ - title: Optional. Title for the checklist (for display purposes)
364
+ - replace: Optional. Whether to replace existing checklist (default: True)
365
+
366
+ Example:
367
+ { "create_checklist": {
368
+ "tasks": [
369
+ "Research the topic",
370
+ "Create outline",
371
+ "Write first draft",
372
+ "Review and edit"
373
+ ],
374
+ "title": "Article Writing Process"
375
+ }}
376
+ """
377
+ if context is None:
378
+ return "_Context is required._"
379
+ async def goto_subtask(subtask_id, context=None):
380
+ """
381
+ Move to a specific subtask without changing its completion status.
382
+ Now supports both top-level and nested subtasks.
383
+
384
+ Parameters:
385
+ - subtask_id: Required. The subtask to navigate to, specified by:
386
+ - The exact subtask label text (top-level or nested)
387
+
388
+ Example:
389
+ { "goto_subtask": { "subtask_id": "Data analysis" } } # Go to top-level task
390
+ { "goto_subtask": { "subtask_id": "USP" } } # Go to nested task
391
+ """
392
+ if context is None:
393
+ return "_Context is required._"
394
+
395
+ st = _state(context)
396
+ if not st["tasks"]:
397
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
398
+
399
+ idx, nested_info = _resolve_subtask_id(subtask_id, context)
400
+ if idx < 0 or idx >= len(st["tasks"]):
401
+ return "_Invalid subtask identifier._"
402
+
403
+ # Update cursor position to the parent task
404
+ st["cursor"] = idx
405
+
406
+ # Handle nested subtask navigation
407
+ if nested_info is not None:
408
+ return (
409
+ f"Moved to Nested Subtask: {format_nested_task_status(nested_info)}\n\n"
410
+ f"{_format_checklist_status(context)}"
411
+ )
412
+
413
+ # Handle top-level subtask navigation
414
+ current_task = st["tasks"][idx]
415
+ status = "✅" if current_task["done"] else "❌"
416
+ return (
417
+ f"Moved to Subtask {idx+1}: {status} {current_task['label']}\n"
418
+ f"{current_task['body']}\n\n"
419
+ f"{_format_checklist_status(context)}"
420
+ )
421
+
422
+
423
+ @command()
424
+ async def create_checklist(tasks, title=None, replace=True, context=None):
425
+ """
426
+ Create a new checklist dynamically from a list of task descriptions.
427
+
428
+ Parameters:
429
+ - tasks: Required. List of task description strings
430
+ - title: Optional. Title for the checklist (for display purposes)
431
+ - replace: Optional. Whether to replace existing checklist (default: True)
432
+
433
+ Example:
434
+ { "create_checklist": {
435
+ "tasks": [
436
+ "Research the topic",
437
+ "Create outline",
438
+ "Write first draft",
439
+ "Review and edit"
440
+ ],
441
+ "title": "Article Writing Process"
442
+ }}
443
+ """
444
+ if context is None:
445
+ return "_Context is required._"
446
+ async def clear_subtask(subtask_id=None, context=None):
447
+ """
448
+ Mark a subtask as incomplete (not done).
449
+ Now supports both top-level and nested subtasks.
450
+
451
+ Parameters:
452
+ - subtask_id: Optional. The subtask to clear, specified by:
453
+ - The exact subtask label text (top-level or nested)
454
+ - Omit to clear the current subtask
455
+
456
+ Example:
457
+ { "clear_subtask": {} } # Clear current subtask
458
+ { "clear_subtask": { "subtask_id": "Review documents" } } # Clear top-level by label
459
+ { "clear_subtask": { "subtask_id": "USP" } } # Clear nested subtask
460
+ """
461
+ if context is None:
462
+ return "_Context is required._"
463
+
464
+ st = _state(context)
465
+ if not st["tasks"]:
466
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
467
+
468
+ idx, nested_info = _resolve_subtask_id(subtask_id, context)
469
+ if idx < 0 or idx >= len(st["tasks"]):
470
+ return "_Invalid subtask identifier._"
471
+
472
+ # Handle nested subtask clearing
473
+ if nested_info is not None:
474
+ # Mark nested subtask as not done
475
+ nested_task = nested_info['nested_task']
476
+ was_done = nested_task['done']
477
+ nested_task['done'] = False
478
+
479
+ # Update the parent task's body
480
+ parent_task = st["tasks"][idx]
481
+ updated_body = update_nested_subtask_status(
482
+ parent_task,
483
+ nested_info['nested_index'],
484
+ nested_info['parent_nested_tasks'],
485
+ False
486
+ )
487
+ parent_task['body'] = updated_body
488
+
489
+ # If parent was complete but now has incomplete nested tasks, mark parent incomplete
490
+ if parent_task['done'] and has_incomplete_nested_tasks(parent_task):
491
+ parent_task['done'] = False
492
+
493
+ # Update cursor to this parent task if needed
494
+ if idx <= st["cursor"]:
495
+ st["cursor"] = idx
496
+
497
+ action = "Cleared" if was_done else "Already clear"
498
+ return (
499
+ f"{action} Nested Subtask: - [ ] {nested_task['label']} (within '{parent_task['label']}')\n"
500
+ f"Current subtask is now Subtask {st['cursor']+1}\n\n"
501
+ f"{_format_checklist_status(context)}"
502
+ )
503
+
504
+ # Handle top-level subtask clearing
505
+ task = st["tasks"][idx]
506
+ was_done = task["done"]
507
+ task["done"] = False
508
+
509
+ # If we cleared the current or earlier task, update cursor
510
+ if idx <= st["cursor"]:
511
+ st["cursor"] = idx
512
+
513
+ # Build response
514
+ action = "Cleared" if was_done else "Already clear"
515
+ return (
516
+ f"{action} Subtask {idx+1}: - [ ] {task['label']}\n"
517
+ f"Current subtask is now Subtask {st['cursor']+1}\n\n"
518
+ f"{_format_checklist_status(context)}"
519
+ )
520
+
521
+
522
+ @command()
523
+ async def create_checklist(tasks, title=None, replace=True, context=None):
524
+ """
525
+ Create a new checklist dynamically from a list of task descriptions.
526
+
527
+ Parameters:
528
+ - tasks: Required. List of task description strings
529
+ - title: Optional. Title for the checklist (for display purposes)
530
+ - replace: Optional. Whether to replace existing checklist (default: True)
531
+
532
+ Example:
533
+ { "create_checklist": {
534
+ "tasks": [
535
+ "Research the topic",
536
+ "Create outline",
537
+ "Write first draft",
538
+ "Review and edit"
539
+ ],
540
+ "title": "Article Writing Process"
541
+ }}
542
+ """
543
+ if context is None:
544
+ return "_Context is required._"
545
+ async def get_checklist_status(context=None):
546
+ """
547
+ Show the full status of the checklist.
548
+
549
+ Example:
550
+ { "get_checklist_status": {} }
551
+ """
552
+ if context is None:
553
+ return "_Context is required._"
554
+
555
+ return _format_checklist_status(context)
556
+
557
+
558
+ @command()
559
+ async def create_checklist(tasks, title=None, replace=True, context=None):
560
+ """
561
+ Create a new checklist dynamically from a list of task descriptions.
562
+
563
+ Parameters:
564
+ - tasks: Required. List of task description strings
565
+ - title: Optional. Title for the checklist (for display purposes)
566
+ - replace: Optional. Whether to replace existing checklist (default: True)
567
+
568
+ Example:
569
+ { "create_checklist": {
570
+ "tasks": [
571
+ "Research the topic",
572
+ "Create outline",
573
+ "Write first draft",
574
+ "Review and edit"
575
+ ],
576
+ "title": "Article Writing Process"
577
+ }}
578
+ """
579
+ if context is None:
580
+ return "_Context is required._"
581
+ async def get_parsed_subtasks(subtask_id=None, context=None):
582
+ """
583
+ Return parsed subtasks with their name/id and body for verification.
584
+ Now supports getting nested subtasks from within parent tasks.
585
+
586
+ Parameters:
587
+ - subtask_id: Optional. If provided, parse subtasks from the body of this specific subtask.
588
+ If omitted, returns all top-level subtasks from the main checklist.
589
+
590
+ Returns a list of dictionaries with:
591
+ - label: The subtask name/label
592
+ - body: The subtask body content
593
+ - done: Whether the subtask is marked as complete
594
+ - index: The 0-based index of the subtask
595
+
596
+ Example:
597
+ { "get_parsed_subtasks": {} } # Get all top-level subtasks
598
+ { "get_parsed_subtasks": { "subtask_id": "Research phase" } } # Get nested subtasks from "Research phase"
599
+ { "get_parsed_subtasks": { "subtask_id": "Core" } } # Get nested subtasks from "Core"
600
+ """
601
+ if context is None:
602
+ return "_Context is required._"
603
+
604
+ st = _state(context)
605
+ if not st["tasks"]:
606
+ try:
607
+ print("Loading checklist from instructions...")
608
+ instructions = context.agent["instructions"]
609
+ await load_checklist_from_instructions(instructions, context)
610
+ st = _state(context) # Refresh state after loading
611
+ except Exception as e:
612
+ print(f"Error loading checklist: {e}")
613
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
614
+
615
+ # If no subtask_id provided, return all top-level subtasks
616
+ if subtask_id is None:
617
+ result = []
618
+ for i, task in enumerate(st["tasks"]):
619
+ result.append({
620
+ "index": i,
621
+ "label": task["label"],
622
+ "body": task["body"],
623
+ "done": task["done"]
624
+ })
625
+ return {
626
+ "source": "top-level checklist",
627
+ "subtasks": result
628
+ }
629
+
630
+ # Find the specified subtask and parse its body for nested subtasks
631
+ idx, nested_info = _resolve_subtask_id(subtask_id, context)
632
+ if idx < 0 or idx >= len(st["tasks"]):
633
+ return "_Invalid subtask identifier._"
634
+
635
+ # If it's a nested subtask, get its nested tasks
636
+ if nested_info is not None:
637
+ target_task = nested_info['nested_task']
638
+ source_desc = f"nested subtasks from '{target_task['label']}' (within '{nested_info['parent_task']['label']}')"
639
+ else:
640
+ target_task = st["tasks"][idx]
641
+ source_desc = f"nested subtasks from '{target_task['label']}'"
642
+
643
+ print("Parsing nested subtasks from:", subtask_id)
644
+ nested_tasks = _parse(target_task["body"])
645
+
646
+ result = []
647
+ for i, task in enumerate(nested_tasks):
648
+ result.append({
649
+ "index": i,
650
+ "label": task["label"],
651
+ "body": task["body"],
652
+ "done": task["done"]
653
+ })
654
+
655
+ return {
656
+ "source": source_desc,
657
+ "parent_task": {
658
+ "index": idx,
659
+ "label": target_task["label"],
660
+ "done": target_task["done"]
661
+ },
662
+ "subtasks": result
663
+ }
664
+
665
+
666
+ @command()
667
+ async def create_checklist(tasks, title=None, replace=True, context=None):
668
+ """
669
+ Create a new checklist dynamically from a list of task descriptions.
670
+
671
+ Parameters:
672
+ - tasks: Required. List of task description strings
673
+ - title: Optional. Title for the checklist (for display purposes)
674
+ - replace: Optional. Whether to replace existing checklist (default: True)
675
+
676
+ Example:
677
+ { "create_checklist": {
678
+ "tasks": [
679
+ "Research the topic",
680
+ "Create outline",
681
+ "Write first draft",
682
+ "Review and edit"
683
+ ],
684
+ "title": "Article Writing Process"
685
+ }}
686
+ """
687
+ if context is None:
688
+ return "_Context is required._"
689
+ async def delegate_subtask(subtask_id, details: str, agent=None, context=None):
690
+ """
691
+ Delegate a subtask to an agent, automatically passing the subtask body as
692
+ instructions, along with any details you add.
693
+ Now supports both top-level and nested subtasks.
694
+
695
+ IMPORTANT: You can only delegate ONE task a time.
696
+ You must wait for this task delegation to complete before issuing
697
+ more delegate_subtask commands.
698
+
699
+ If agent is not specified, the current agent name will be used for the subtask.
700
+
701
+ IMPORTANT: Subtask ID may only contain alphanumerics; all other special characters are invalid.
702
+
703
+ Example:
704
+ { "delegate_subtask": { "subtask_id": "Research",
705
+ "details": "Session data in /data/sess_1234/" }}
706
+ { "delegate_subtask": { "subtask_id": "USP",
707
+ "details": "Focus on unique selling proposition analysis" }}
708
+
709
+ Note that you do not need to repeat the text of the subtask item from the checklist
710
+ in your details.
711
+ """
712
+ st = _state(context)
713
+ if not st["tasks"]:
714
+ try:
715
+ print("Loading checklist from instructions...")
716
+ print("Agent is")
717
+ print(context.agent)
718
+ instructions = context.agent["instructions"]
719
+ await load_checklist_from_instructions(instructions, context)
720
+ except Exception as e:
721
+ print(f"Error loading checklist: {e}")
722
+ trace = traceback.format_exc()
723
+ print(trace)
724
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
725
+
726
+ idx, nested_info = _resolve_subtask_id(subtask_id, context)
727
+ if idx < 0 or idx >= len(st["tasks"]):
728
+ return "_Invalid subtask identifier._"
729
+
730
+ # Get the appropriate task body for delegation
731
+ if nested_info is not None:
732
+ # Delegating a nested subtask
733
+ current_task = nested_info['nested_task']
734
+ task_context = f"nested subtask '{current_task['label']}' within parent task '{nested_info['parent_task']['label']}'"
735
+ else:
736
+ # Delegating a top-level subtask
737
+ current_task = st["tasks"][idx]
738
+ task_context = f"subtask '{current_task['label']}'"
739
+
740
+ subtask_body = current_task["body"]
741
+
742
+ reminder = f"""Important: you may see system instructions for the full process. However, you are to ONLY
743
+ do (or delegate) the specified part of the process and then return a task result. If you have a sub-checklist assigned,
744
+ use delegate_subtask as needed for complex checklist items or per user instructions.
745
+
746
+ You are working on {task_context}."""
747
+
748
+ 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"
749
+
750
+ if agent is None:
751
+ agent_name = context.agent["name"]
752
+ else:
753
+ agent_name = agent
754
+
755
+ return await command_manager.delegate_task(instructions, agent_name, context=context)
756
+