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.

@@ -0,0 +1,506 @@
1
+ """
2
+ checklist helper for an LLM‑agent system
3
+ —————————————————————————————————————————————————————————————————————————————
4
+ Purpose
5
+ -------
6
+ • Parse a Markdown "##\tChecklist" section where each subtask is
7
+ written as a task‑list line:
8
+
9
+ - [ ] label of subtask
10
+ arbitrary markdown body… (until next task or heading)
11
+
12
+ • Store parsed tasks + cursor in `context.data['checklist']`.
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
20
+
21
+ `complete_subtask` and other commands live at module level so the agent can call them
22
+ as tools. No third‑party deps—only Python's `re` module.
23
+ """
24
+
25
+ import re
26
+ from lib.providers.commands import command, command_manager
27
+ from lib.providers.services import service
28
+ import traceback
29
+ from collections import Counter
30
+
31
+ # ---------- simple parsing -----------------------------------------------
32
+
33
+ def _parse(text):
34
+ """Simple, robust checklist parser.
35
+
36
+ Finds checklist items (- [ ] or - [x]) at any indentation level,
37
+ but only processes items at the same indentation as the first one found.
38
+
39
+ Returns list of {label, body, done} dicts.
40
+ """
41
+ print("parsing:")
42
+ print("---------------------------------------------")
43
+ print(text)
44
+ print('---------------------------------------------')
45
+ lines = text.splitlines()
46
+ tasks = []
47
+ i = 0
48
+ first_task_indent = None
49
+
50
+ # Find all task lines and their indentations first
51
+ task_indents = []
52
+ for line in lines:
53
+ stripped = line.lstrip()
54
+ if stripped.startswith('- [ ]') or stripped.startswith('- [x]') or stripped.startswith('- [X]'):
55
+ indent = len(line) - len(stripped)
56
+ task_indents.append(indent)
57
+
58
+ if not task_indents:
59
+ return tasks
60
+
61
+ # Use the SMALLEST indentation level as reference (top-level tasks)
62
+ reference_indent = min(task_indents)
63
+ print("reference indent:", reference_indent)
64
+ while i < len(lines):
65
+ line = lines[i]
66
+ stripped = line.lstrip()
67
+ print(i)
68
+ # Check if this line is a checklist item
69
+ if stripped.startswith('- [ ]') or stripped.startswith('- [x]') or stripped.startswith('- [X]'):
70
+ # Calculate indentation
71
+ current_indent = len(line) - len(stripped)
72
+ print("found task at indent:", current_indent)
73
+ print("reference indent:", reference_indent)
74
+ # Only process tasks at the reference indentation level
75
+ if current_indent == reference_indent:
76
+ # Extract task info
77
+ done = stripped.startswith('- [x]') or stripped.startswith('- [X]')
78
+
79
+ # Extract label (everything after the checkbox)
80
+ if stripped.startswith('- [ ]'):
81
+ label = stripped[5:].strip()
82
+ else: # - [x] or - [X]
83
+ label = stripped[5:].strip()
84
+
85
+ # Collect body lines until next task at same level or end
86
+ body_lines = []
87
+ i += 1
88
+
89
+ while i < len(lines):
90
+ print("checking line for body:",i, lines[i])
91
+ next_line = lines[i]
92
+ next_stripped = next_line.lstrip()
93
+ next_indent = len(next_line) - len(next_stripped)
94
+ print("next_stripped:", next_stripped)
95
+ print("next_indent:", next_indent)
96
+ print("reference_indent:", reference_indent)
97
+ # Stop if we hit another task at the same indentation level
98
+ if ((next_stripped.startswith('- [ ]') or
99
+ next_stripped.startswith('- [x]') or
100
+ next_stripped.startswith('- [X]')) and
101
+ next_indent <= reference_indent):
102
+ break
103
+
104
+ # Stop if we hit a markdown heading
105
+ if next_stripped.startswith('#'):
106
+ break
107
+
108
+ body_lines.append(next_line)
109
+ i += 1
110
+
111
+ tasks.append({
112
+ 'label': label,
113
+ 'body': '\n'.join(body_lines), #.strip(),
114
+ 'done': done
115
+ })
116
+ continue
117
+
118
+ i += 1
119
+
120
+ return tasks
121
+
122
+
123
+ def extract_checklist_section(md: str):
124
+ """Extract checklist items from the entire markdown text.
125
+
126
+ No longer requires a specific 'Checklist' heading - processes the entire text
127
+ and extracts only top-level checklist items, leaving nested ones intact.
128
+ """
129
+ return md
130
+
131
+
132
+ # ---------- state helpers ------------------------------------------------
133
+
134
+ def _state(ctx):
135
+ """Return the mutable checklist state in ctx.data."""
136
+ return ctx.data.setdefault("checklist", {"tasks": [], "cursor": 0})
137
+
138
+ def load_checklist(md: str, ctx):
139
+ """Parse markdown and reset cursor to first unchecked subtask."""
140
+ st = _state(ctx)
141
+ st["tasks"] = _parse(md)
142
+ st["cursor"] = next(
143
+ (i for i, t in enumerate(st["tasks"]) if not t["done"]),
144
+ len(st["tasks"]),
145
+ )
146
+
147
+
148
+ @service()
149
+ async def load_checklist_from_instructions(md: str, context=None):
150
+ """Load checklist from instructions.
151
+
152
+ Processes the entire text and extracts top-level checklist items.
153
+
154
+ Example:
155
+ { "load_checklist_from_instructions": { "md": "Full instructions with embedded checklist" } }
156
+ """
157
+ checklist_section = extract_checklist_section(md)
158
+ load_checklist(checklist_section, context)
159
+ return f"Loaded checklist from instructions.\n\n{_format_checklist_status(context)}"
160
+
161
+
162
+ # ---------- helper functions ---------------------------------------------
163
+
164
+ def _resolve_subtask_id(subtask_id, context):
165
+ """Resolve a subtask_id to an index (0-based).
166
+
167
+ subtask_id can be:
168
+ - A number (1-based index, converted to 0-based)
169
+ - A string matching a subtask label
170
+ - None/default (-1) to use the current cursor position
171
+ """
172
+ st = _state(context)
173
+
174
+ # Default to current cursor position
175
+ if subtask_id is None or subtask_id == -1:
176
+ return st["cursor"]
177
+
178
+ # If it's a number, convert from 1-based to 0-based
179
+ if isinstance(subtask_id, int):
180
+ idx = subtask_id - 1
181
+ else:
182
+ # It's a string, try to find a matching label
183
+ for i, task in enumerate(st["tasks"]):
184
+ if task["label"] == subtask_id:
185
+ return i
186
+ # No match found
187
+ return -1
188
+
189
+ return idx
190
+
191
+
192
+ def _format_checklist_status(context):
193
+ """Format the full checklist status as a markdown string."""
194
+ st = _state(context)
195
+ if not st["tasks"]:
196
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
197
+
198
+ lines = ["### Checklist Status\n"]
199
+
200
+ for i, task in enumerate(st["tasks"]):
201
+ # Determine status indicator
202
+ if i == st["cursor"]:
203
+ status = "➡️ " # Current task
204
+ elif task["done"]:
205
+ status = "✅ " # Completed
206
+ else:
207
+ status = "❌ " # Not completed
208
+
209
+ # Add task line
210
+ lines.append(f"{status}**Subtask**: {task['label']}")
211
+
212
+ return "\n".join(lines)
213
+
214
+
215
+ # ---------- module‑level tool commands ----------------------------------
216
+
217
+ @command()
218
+ async def complete_subtask(subtask_id=None, context=None):
219
+ """
220
+ Mark a subtask complete and return a Markdown status message.
221
+
222
+ Parameters:
223
+ - subtask_id: Optional. The subtask to complete, specified by:
224
+ - The exact subtask label text
225
+ - Omit to complete the current subtask
226
+
227
+ Example:
228
+ { "complete_subtask": {} } # Complete current subtask
229
+ { "complete_subtask": { "subtask_id": "Review documents" } } # Complete by label
230
+ """
231
+ if context is None:
232
+ return "_Context is required._"
233
+
234
+ st = _state(context)
235
+ if not st["tasks"]:
236
+ try:
237
+ print("Loading checklist from instructions...")
238
+ print("Agent is")
239
+ print(context.agent)
240
+ instructions = context.agent["instructions"]
241
+ await load_checklist_from_instructions(instructions, context)
242
+ except Exception as e:
243
+ print(f"Error loading checklist: {e}")
244
+ trace = traceback.format_exc()
245
+ print(trace)
246
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
247
+
248
+ idx = _resolve_subtask_id(subtask_id, context)
249
+ if idx < 0 or idx >= len(st["tasks"]):
250
+ return "_Invalid subtask identifier._"
251
+
252
+ # mark as done
253
+ done_task = st["tasks"][idx]
254
+ done_task["done"] = True
255
+
256
+ # advance cursor to next open subtask
257
+ st["cursor"] = next(
258
+ (i for i, t in enumerate(st["tasks"][idx + 1:], idx + 1) if not t["done"]),
259
+ len(st["tasks"]),
260
+ )
261
+
262
+ # build markdown response
263
+ completed = f"Completed Subtask {idx+1}: - [x] {done_task['label']}"
264
+ if st["cursor"] >= len(st["tasks"]):
265
+ return f"{completed}\n\nAll subtasks complete ✅\n\n{_format_checklist_status(context)}"
266
+
267
+ next_task = st["tasks"][st['cursor']]
268
+ return f"""
269
+ {completed}
270
+
271
+ Next subtask (Subtask {st['cursor']+1})
272
+ - [ ] {next_task['label']}
273
+ {next_task['body']}
274
+
275
+ {_format_checklist_status(context)}
276
+ """
277
+
278
+ @command()
279
+ async def goto_subtask(subtask_id, context=None):
280
+ """
281
+ Move to a specific subtask without changing its completion status.
282
+
283
+ Parameters:
284
+ - subtask_id: Required. The subtask to navigate to, specified by:
285
+ - The exact subtask label text
286
+
287
+ Example:
288
+ { "goto_subtask": { "subtask_id": "Data analysis" } } # Go to by label
289
+ """
290
+ if context is None:
291
+ return "_Context is required._"
292
+
293
+ st = _state(context)
294
+ if not st["tasks"]:
295
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
296
+
297
+ idx = _resolve_subtask_id(subtask_id, context)
298
+ if idx < 0 or idx >= len(st["tasks"]):
299
+ return "_Invalid subtask identifier._"
300
+
301
+ # Update cursor position
302
+ st["cursor"] = idx
303
+
304
+ # Get the current task
305
+ current_task = st["tasks"][idx]
306
+
307
+ # Build response
308
+ status = "✅" if current_task["done"] else "❌"
309
+ return (
310
+ f"Moved to Subtask {idx+1}: {status} {current_task['label']}\n"
311
+ f"{current_task['body']}\n\n"
312
+ f"{_format_checklist_status(context)}"
313
+ )
314
+
315
+
316
+ @command()
317
+ async def clear_subtask(subtask_id=None, context=None):
318
+ """
319
+ Mark a subtask as incomplete (not done).
320
+
321
+ Parameters:
322
+ - subtask_id: Optional. The subtask to clear, specified by:
323
+ - The exact subtask label text
324
+ - Omit to clear the current subtask
325
+
326
+ Example:
327
+ { "clear_subtask": {} } # Clear current subtask
328
+ { "clear_subtask": { "subtask_id": "Review documents" } } # Clear by label
329
+ """
330
+ if context is None:
331
+ return "_Context is required._"
332
+
333
+ st = _state(context)
334
+ if not st["tasks"]:
335
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
336
+
337
+ idx = _resolve_subtask_id(subtask_id, context)
338
+ if idx < 0 or idx >= len(st["tasks"]):
339
+ return "_Invalid subtask identifier._"
340
+
341
+ # Mark as not done
342
+ task = st["tasks"][idx]
343
+ was_done = task["done"]
344
+ task["done"] = False
345
+
346
+ # If we cleared the current or earlier task, update cursor
347
+ if idx <= st["cursor"]:
348
+ st["cursor"] = idx
349
+
350
+ # Build response
351
+ action = "Cleared" if was_done else "Already clear"
352
+ return (
353
+ f"{action} Subtask {idx+1}: - [ ] {task['label']}\n"
354
+ f"Current subtask is now Subtask {st['cursor']+1}\n\n"
355
+ f"{_format_checklist_status(context)}"
356
+ )
357
+
358
+
359
+ @command()
360
+ async def get_checklist_status(context=None):
361
+ """
362
+ Show the full status of the checklist.
363
+
364
+ Example:
365
+ { "get_checklist_status": {} }
366
+ """
367
+ if context is None:
368
+ return "_Context is required._"
369
+
370
+ return _format_checklist_status(context)
371
+
372
+
373
+ @command()
374
+ async def get_parsed_subtasks(subtask_id=None, context=None):
375
+ """
376
+ Return parsed subtasks with their name/id and body for verification.
377
+
378
+ Parameters:
379
+ - subtask_id: Optional. If provided, parse subtasks from the body of this specific subtask.
380
+ If omitted, returns all top-level subtasks from the main checklist.
381
+
382
+ Returns a list of dictionaries with:
383
+ - label: The subtask name/label
384
+ - body: The subtask body content
385
+ - done: Whether the subtask is marked as complete
386
+ - index: The 0-based index of the subtask
387
+
388
+ Example:
389
+ { "get_parsed_subtasks": {} } # Get all top-level subtasks
390
+ { "get_parsed_subtasks": { "subtask_id": "Research phase" } } # Get nested subtasks from "Research phase"
391
+ """
392
+ if context is None:
393
+ return "_Context is required._"
394
+
395
+ st = _state(context)
396
+ if not st["tasks"]:
397
+ try:
398
+ print("Loading checklist from instructions...")
399
+ instructions = context.agent["instructions"]
400
+ await load_checklist_from_instructions(instructions, context)
401
+ st = _state(context) # Refresh state after loading
402
+ except Exception as e:
403
+ print(f"Error loading checklist: {e}")
404
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
405
+
406
+ # If no subtask_id provided, return all top-level subtasks
407
+ if subtask_id is None:
408
+ result = []
409
+ for i, task in enumerate(st["tasks"]):
410
+ result.append({
411
+ "index": i,
412
+ "label": task["label"],
413
+ "body": task["body"],
414
+ "done": task["done"]
415
+ })
416
+ return {
417
+ "source": "top-level checklist",
418
+ "subtasks": result
419
+ }
420
+
421
+ # Find the specified subtask and parse its body for nested subtasks
422
+ idx = _resolve_subtask_id(subtask_id, context)
423
+ if idx < 0 or idx >= len(st["tasks"]):
424
+ return "_Invalid subtask identifier._"
425
+
426
+ parent_task = st["tasks"][idx]
427
+ print("Parsing nested subtasks from:", subtask_id)
428
+ nested_tasks = _parse(parent_task["body"])
429
+
430
+ result = []
431
+ for i, task in enumerate(nested_tasks):
432
+ result.append({
433
+ "index": i,
434
+ "label": task["label"],
435
+ "body": task["body"],
436
+ "done": task["done"]
437
+ })
438
+
439
+ return {
440
+ "source": f"nested subtasks from '{parent_task['label']}'",
441
+ "parent_task": {
442
+ "index": idx,
443
+ "label": parent_task["label"],
444
+ "done": parent_task["done"]
445
+ },
446
+ "subtasks": result
447
+ }
448
+
449
+
450
+ @command()
451
+ async def delegate_subtask(subtask_id, details:str, agent=None, context=None):
452
+ """
453
+ Delegate a subtask to an agent, automatically passing the subtask body as
454
+ instructions, along with any details you add.
455
+
456
+ If agent is not specified, the current agent name will be used for the subtask.
457
+
458
+ Example:
459
+
460
+ Given a checklist with subtasks like:
461
+
462
+ - Research
463
+ ...
464
+
465
+ - Draft
466
+ ...
467
+
468
+ - Complete
469
+ ..
470
+
471
+ Suppose you had already created /data/sess_1234.
472
+ You might initiate work on the first subtask with this command:
473
+
474
+ { "delegate_subtask": { "subtask_id": "Research",
475
+ "details": "Session data in /data/sess_1234/" }}
476
+
477
+ Note that you do not need to repeat the text of the subtask item from the checklist
478
+ in your details.
479
+ """
480
+ st = _state(context)
481
+ if not st["tasks"]:
482
+ try:
483
+ print("Loading checklist from instructions...")
484
+ print("Agent is")
485
+ print(context.agent)
486
+ instructions = context.agent["instructions"]
487
+ await load_checklist_from_instructions(instructions, context)
488
+ except Exception as e:
489
+ print(f"Error loading checklist: {e}")
490
+ trace = traceback.format_exc()
491
+ print(trace)
492
+ return "_No checklist found. Make sure to include a checklist in your instructions._"
493
+ idx = _resolve_subtask_id(subtask_id, context)
494
+ if idx < 0 or idx >= len(st["tasks"]):
495
+ return "_Invalid subtask identifier._"
496
+ current_task = st["tasks"][idx]
497
+ subtask = current_task["body"]
498
+ reminder = """Important: you may see instructions for the full process. However, you are to ONLY
499
+ do the specified part of the process and then return a task result."""
500
+ instructions = f"You are working as part of a multi-step process. Please complete the following subtask:\n\n{subtask}\n\n{details}\n\n{reminder}\n"
501
+ if agent is None:
502
+ agent_name = context.agent["name"]
503
+ else:
504
+ agent_name = agent
505
+ return await command_manager.delegate_task(instructions, agent_name, context=context)
506
+
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "Checklist Helper",
3
+ "version": "1.0.0",
4
+ "description": "Checklist helper for LLM-agent system with support for parsing, managing, and delegating subtasks",
5
+ "services": ["load_checklist_from_instructions"],
6
+ "commands": [
7
+ "complete_subtask",
8
+ "goto_subtask",
9
+ "clear_subtask",
10
+ "get_checklist_status",
11
+ "get_parsed_subtasks",
12
+ "delegate_subtask"
13
+ ]
14
+ }