monoco-toolkit 0.3.11__py3-none-any.whl → 0.3.12__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 (44) hide show
  1. monoco/core/automation/__init__.py +51 -0
  2. monoco/core/automation/config.py +338 -0
  3. monoco/core/automation/field_watcher.py +296 -0
  4. monoco/core/automation/handlers.py +723 -0
  5. monoco/core/config.py +1 -1
  6. monoco/core/executor/__init__.py +38 -0
  7. monoco/core/executor/agent_action.py +254 -0
  8. monoco/core/executor/git_action.py +303 -0
  9. monoco/core/executor/im_action.py +309 -0
  10. monoco/core/executor/pytest_action.py +218 -0
  11. monoco/core/git.py +15 -0
  12. monoco/core/hooks/context.py +74 -13
  13. monoco/core/router/__init__.py +55 -0
  14. monoco/core/router/action.py +341 -0
  15. monoco/core/router/router.py +392 -0
  16. monoco/core/scheduler/__init__.py +63 -0
  17. monoco/core/scheduler/base.py +152 -0
  18. monoco/core/scheduler/engines.py +175 -0
  19. monoco/core/scheduler/events.py +171 -0
  20. monoco/core/scheduler/local.py +377 -0
  21. monoco/core/watcher/__init__.py +57 -0
  22. monoco/core/watcher/base.py +365 -0
  23. monoco/core/watcher/dropzone.py +152 -0
  24. monoco/core/watcher/issue.py +303 -0
  25. monoco/core/watcher/memo.py +200 -0
  26. monoco/core/watcher/task.py +238 -0
  27. monoco/daemon/events.py +34 -0
  28. monoco/daemon/scheduler.py +172 -201
  29. monoco/daemon/services.py +27 -243
  30. monoco/features/agent/__init__.py +25 -7
  31. monoco/features/agent/cli.py +91 -57
  32. monoco/features/agent/engines.py +31 -170
  33. monoco/features/agent/worker.py +1 -1
  34. monoco/features/issue/commands.py +90 -32
  35. monoco/features/issue/core.py +249 -4
  36. monoco/features/spike/commands.py +5 -3
  37. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/METADATA +1 -1
  38. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/RECORD +41 -20
  39. monoco/features/agent/apoptosis.py +0 -44
  40. monoco/features/agent/manager.py +0 -127
  41. monoco/features/agent/session.py +0 -169
  42. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/WHEEL +0 -0
  43. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/entry_points.txt +0 -0
  44. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/licenses/LICENSE +0 -0
@@ -46,6 +46,30 @@ def get_issue_dir(issue_type: str, issues_root: Path) -> Path:
46
46
  return issues_root / folder
47
47
 
48
48
 
49
+ def _parse_isolation_ref(ref: str) -> str:
50
+ """
51
+ Parse isolation ref and strip any 'branch:' or 'worktree:' prefix.
52
+
53
+ The isolation.ref field may contain prefixes like 'branch:feat/xxx' or 'worktree:xxx'
54
+ for display/logging purposes, but git commands need the raw branch name.
55
+
56
+ Args:
57
+ ref: The isolation ref string, may contain prefix
58
+
59
+ Returns:
60
+ The clean branch name without prefix
61
+ """
62
+ if not ref:
63
+ return ref
64
+
65
+ # Strip known prefixes
66
+ for prefix in ("branch:", "worktree:"):
67
+ if ref.startswith(prefix):
68
+ return ref[len(prefix):]
69
+
70
+ return ref
71
+
72
+
49
73
  def _get_slug(title: str) -> str:
50
74
  slug = title.lower()
51
75
  # Replace non-word characters (including punctuation, spaces) with hyphens
@@ -466,6 +490,227 @@ def find_issue_path(issues_root: Path, issue_id: str, include_archived: bool = T
466
490
  return None
467
491
 
468
492
 
493
+ def find_issue_path_across_branches(
494
+ issues_root: Path,
495
+ issue_id: str,
496
+ project_root: Optional[Path] = None,
497
+ include_archived: bool = True
498
+ ) -> Tuple[Optional[Path], Optional[str]]:
499
+ """
500
+ Find issue path across all local git branches.
501
+
502
+ Implements the "Golden Path" logic:
503
+ - If issue found in exactly one branch -> return it silently
504
+ - If issue found in multiple branches -> raise error (conflict)
505
+ - If issue not found in any branch -> return None
506
+
507
+ Args:
508
+ issues_root: Root directory of issues
509
+ issue_id: Issue ID to find
510
+ project_root: Project root (defaults to issues_root.parent)
511
+ include_archived: Whether to search in archived directory
512
+
513
+ Returns:
514
+ Tuple of (file_path, branch_name) or (None, None) if not found
515
+
516
+ Raises:
517
+ RuntimeError: If issue found in multiple branches (conflict)
518
+ """
519
+ # First, try to find in current working tree
520
+ local_path = find_issue_path(issues_root, issue_id, include_archived)
521
+
522
+ # Determine project root
523
+ if project_root is None:
524
+ project_root = issues_root.parent
525
+
526
+ # If not a git repo, just return local result
527
+ if not git.is_git_repo(project_root):
528
+ return (local_path, None) if local_path else (None, None)
529
+
530
+ # Get current branch
531
+ current_branch = git.get_current_branch(project_root)
532
+
533
+ # Search in all branches to detect conflicts (FIX-0006)
534
+ # If found locally, we still check other branches to detect potential duplicate files (conflict)
535
+ if local_path:
536
+ # Get relative path for git checking
537
+ try:
538
+ rel_path = local_path.relative_to(project_root)
539
+ except ValueError:
540
+ # If path is outside project root, we can't search other branches anyway
541
+ return local_path, current_branch
542
+
543
+ conflicting_branches = _find_branches_with_file(project_root, str(rel_path), current_branch)
544
+
545
+ # SPECIAL CASE (FIX-0006): During 'issue close' or similar operations,
546
+ # it is expected that the file exists in the feature branch (isolation branch)
547
+ # and now in the target branch (e.g. main). This is NOT a conflict.
548
+ if conflicting_branches:
549
+ # Try to parse metadata to see if these "conflicts" are actually the isolation branch
550
+ try:
551
+ meta = parse_issue(local_path)
552
+ if meta and meta.isolation:
553
+ iso_type = getattr(meta.isolation, "type", None)
554
+ iso_ref = getattr(meta.isolation, "ref", None)
555
+
556
+ source_branch = None
557
+ if iso_type == "branch":
558
+ source_branch = iso_ref
559
+ elif iso_ref and iso_ref.startswith("branch:"):
560
+ source_branch = iso_ref[7:]
561
+
562
+ if source_branch:
563
+ # Filter out the source branch from conflicts
564
+ conflicting_branches = [b for b in conflicting_branches if b != source_branch]
565
+ except Exception:
566
+ # If parsing fails, stick with raw conflicts to be safe
567
+ pass
568
+
569
+ if conflicting_branches:
570
+ raise RuntimeError(
571
+ f"Issue {issue_id} found in multiple branches: {current_branch}, {', '.join(conflicting_branches)}. "
572
+ f"Please resolve the conflict by merging branches or deleting duplicate issue files."
573
+ )
574
+ return local_path, current_branch
575
+
576
+ # Not found locally, search in all branches
577
+ return _search_issue_in_branches(issues_root, issue_id, project_root, include_archived)
578
+
579
+
580
+ def _find_branches_with_file(project_root: Path, rel_path: str, exclude_branch: str) -> List[str]:
581
+ """
582
+ Find all branches (except excluded) that contain the given file path.
583
+
584
+ Args:
585
+ project_root: Project root path
586
+ rel_path: Relative path from project root
587
+ exclude_branch: Branch to exclude from search
588
+
589
+ Returns:
590
+ List of branch names containing the file
591
+ """
592
+ branches_with_file = []
593
+
594
+ # Get all local branches
595
+ code, stdout, _ = git._run_git(["branch", "--format=%(refname:short)"], project_root)
596
+ if code != 0:
597
+ return []
598
+
599
+ all_branches = [b.strip() for b in stdout.splitlines() if b.strip()]
600
+
601
+ for branch in all_branches:
602
+ if branch == exclude_branch:
603
+ continue
604
+
605
+ # Check if file exists in this branch
606
+ code, _, _ = git._run_git(["show", f"{branch}:{rel_path}"], project_root)
607
+ if code == 0:
608
+ branches_with_file.append(branch)
609
+
610
+ return branches_with_file
611
+
612
+
613
+ def _search_issue_in_branches(
614
+ issues_root: Path,
615
+ issue_id: str,
616
+ project_root: Path,
617
+ include_archived: bool = True
618
+ ) -> Tuple[Optional[Path], Optional[str]]:
619
+ """
620
+ Search for an issue file across all branches.
621
+
622
+ Returns:
623
+ Tuple of (file_path, branch_name) or (None, None)
624
+
625
+ Raises:
626
+ RuntimeError: If issue found in multiple branches
627
+ """
628
+ parsed = IssueID(issue_id)
629
+
630
+ if not parsed.is_local:
631
+ # For workspace issues, just use standard find
632
+ path = find_issue_path(issues_root, issue_id, include_archived)
633
+ return (path, None) if path else (None, None)
634
+
635
+ # Get issue type from prefix
636
+ try:
637
+ prefix = parsed.local_id.split("-")[0].upper()
638
+ except IndexError:
639
+ return None, None
640
+
641
+ reverse_prefix_map = get_reverse_prefix_map(issues_root)
642
+ issue_type = reverse_prefix_map.get(prefix)
643
+ if not issue_type:
644
+ return None, None
645
+
646
+ # Build possible paths to search
647
+ base_dir = get_issue_dir(issue_type, issues_root)
648
+ rel_base = base_dir.relative_to(project_root)
649
+
650
+ status_dirs = ["open", "backlog", "closed"]
651
+ if include_archived:
652
+ status_dirs.append("archived")
653
+
654
+ # Search pattern: Issues/{Type}/{status}/{issue_id}-*.md
655
+ found_in_branches: List[Tuple[str, str]] = [] # (branch, file_path)
656
+
657
+ # Get all local branches
658
+ code, stdout, _ = git._run_git(["branch", "--format=%(refname:short)"], project_root)
659
+ if code != 0:
660
+ return None, None
661
+
662
+ all_branches = [b.strip() for b in stdout.splitlines() if b.strip()]
663
+
664
+ for branch in all_branches:
665
+ # List files in each status directory for this branch
666
+ for status_dir in status_dirs:
667
+ dir_path = f"{rel_base}/{status_dir}"
668
+
669
+ # List all files in this directory on the branch
670
+ code, stdout, _ = git._run_git(
671
+ ["ls-tree", "-r", "--name-only", branch, dir_path],
672
+ project_root
673
+ )
674
+
675
+ if code != 0 or not stdout.strip():
676
+ continue
677
+
678
+ # Find matching issue file (Handling quoted paths from git ls-tree)
679
+ pattern = f"{parsed.local_id}-"
680
+ for line in stdout.splitlines():
681
+ line = line.strip()
682
+ # Git quotes non-ASCII paths: "Issues/Chores/...md"
683
+ if line.startswith('"') and line.endswith('"'):
684
+ line = line[1:-1]
685
+
686
+ if pattern in line and line.endswith(".md"):
687
+ found_in_branches.append((branch, line))
688
+
689
+ if not found_in_branches:
690
+ return None, None
691
+
692
+ if len(found_in_branches) > 1:
693
+ # Found in multiple branches - conflict
694
+ branches = [b for b, _ in found_in_branches]
695
+ raise RuntimeError(
696
+ f"Issue {issue_id} found in multiple branches: {', '.join(branches)}. "
697
+ f"Please resolve the conflict by merging branches or deleting duplicate issue files."
698
+ )
699
+
700
+ # Golden path: exactly one match
701
+ branch, file_path = found_in_branches[0]
702
+
703
+ # Checkout the file to working tree
704
+ try:
705
+ git.git_checkout_files(project_root, branch, [file_path])
706
+ full_path = project_root / file_path
707
+ return full_path, branch
708
+ except Exception:
709
+ # If checkout fails, return the path anyway - caller can handle
710
+ full_path = project_root / file_path
711
+ return full_path, branch
712
+
713
+
469
714
  def update_issue(
470
715
  issues_root: Path,
471
716
  issue_id: str,
@@ -899,7 +1144,7 @@ def prune_issue_resources(
899
1144
  return []
900
1145
 
901
1146
  if issue.isolation.type == IsolationType.BRANCH:
902
- branch = issue.isolation.ref
1147
+ branch = _parse_isolation_ref(issue.isolation.ref)
903
1148
  current = git.get_current_branch(project_root)
904
1149
  if current == branch:
905
1150
  raise RuntimeError(
@@ -925,7 +1170,7 @@ def prune_issue_resources(
925
1170
  # Also delete the branch associated?
926
1171
  # Worktree create makes a branch. When removing worktree, branch remains.
927
1172
  # Usually we want to remove the branch too if it was created for this issue.
928
- branch = issue.isolation.ref
1173
+ branch = _parse_isolation_ref(issue.isolation.ref)
929
1174
  if branch and git.branch_exists(project_root, branch):
930
1175
  # We can't delete branch if it is checked out in the worktree we just removed?
931
1176
  # git worktree remove unlocks the branch.
@@ -983,7 +1228,7 @@ def sync_issue_files(issues_root: Path, issue_id: str, project_root: Path) -> Li
983
1228
  target_ref = None
984
1229
 
985
1230
  if issue.isolation and issue.isolation.ref:
986
- target_ref = issue.isolation.ref
1231
+ target_ref = _parse_isolation_ref(issue.isolation.ref)
987
1232
  if target_ref == "current":
988
1233
  target_ref = git.get_current_branch(project_root)
989
1234
  else:
@@ -1061,7 +1306,7 @@ def merge_issue_changes(
1061
1306
  # If not there, we try heuristic.
1062
1307
  source_ref = None
1063
1308
  if issue.isolation and issue.isolation.ref:
1064
- source_ref = issue.isolation.ref
1309
+ source_ref = _parse_isolation_ref(issue.isolation.ref)
1065
1310
  else:
1066
1311
  # Heuristic: Search for branch by convention
1067
1312
  # We can't use 'current' here safely if we are on main,
@@ -81,13 +81,15 @@ def remove_repo(
81
81
  target_path = spikes_dir / name
82
82
  deleted = False
83
83
  if target_path.exists():
84
- if force or typer.confirm(
85
- f"Do you want to delete the directory {target_path}?", default=False
86
- ):
84
+ if force:
87
85
  core.remove_repo_dir(spikes_dir, name)
88
86
  deleted = True
89
87
  else:
90
88
  deleted = False
89
+ if not OutputManager.is_agent_mode():
90
+ from rich.console import Console
91
+ console = Console()
92
+ console.print(f"[yellow]Skipping physical deletion of {target_path}. Use --force to delete.[/yellow]")
91
93
 
92
94
  OutputManager.print(
93
95
  {"status": "removed", "name": name, "directory_deleted": deleted}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: monoco-toolkit
3
- Version: 0.3.11
3
+ Version: 0.3.12
4
4
  Summary: Agent Native Toolkit for Monoco - Task Management & Kanban for AI Agents
5
5
  Project-URL: Homepage, https://monoco.io
6
6
  Project-URL: Repository, https://github.com/IndenScale/Monoco
@@ -4,10 +4,10 @@ monoco/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  monoco/cli/project.py,sha256=FqLaDD3hiWxa0_TKzxEF7PYH9RPsvmLyjO3NYVckgGs,2737
5
5
  monoco/cli/workspace.py,sha256=vcUHjSarRZk8k3SNjtLSVD2VUPhBIUyx5J1OPtGa7-Q,1541
6
6
  monoco/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- monoco/core/config.py,sha256=tmbfQd64CCOHfFTFf2zrUvCd9jvk25X6-UaksTkpaIY,16872
7
+ monoco/core/config.py,sha256=lY0A4iUqwmzSwNAbYjKZ1O6cTw4GKBJXpP1lmQDo9VI,16850
8
8
  monoco/core/execution.py,sha256=7522s7uVWiNdMKtWlIb8JHzbAcJgLVuxUFp_6VmhWNQ,1801
9
9
  monoco/core/feature.py,sha256=bcK0C1CaQgNLcdMF0RuniaaQRCYSA-0JdOpQz9W_1xM,1968
10
- monoco/core/git.py,sha256=C0wq8z9XOVmIOW2K583rPPsHRB6eHT0TRH1qLSzqOyo,9170
10
+ monoco/core/git.py,sha256=yKrkF8i9RPHlidu6lgTDnao9C99uHJyO0Xo2ADaLWqg,9696
11
11
  monoco/core/githooks.py,sha256=QY4c74LIYDtcjtWYax1PrK5CtVNKYTfVVVBOj-SCulo,1826
12
12
  monoco/core/injection.py,sha256=BCMbppPqBH_1XVT7is0UgemstSDnb98E1Df1sdE6JKY,8391
13
13
  monoco/core/integrations.py,sha256=V_PgRfJt1X9rAuq0EkILaJufqbwqilyBQmH2-RV4D3U,7917
@@ -26,9 +26,18 @@ monoco/core/workspace.py,sha256=H_PHD5A0HZFq841u1JtLoFjkXdQg9D6x6I7QcFtJge4,3000
26
26
  monoco/core/artifacts/__init__.py,sha256=emmW_RHKZbJ4ED-oPx6Eoci34f4NKCXTuBohQmQ5gcU,406
27
27
  monoco/core/artifacts/manager.py,sha256=WinaVMrXWVm56CRYtSEz6O2JWZZ1A7xWiEnR8O7ppHc,19543
28
28
  monoco/core/artifacts/models.py,sha256=fk_iS3kkUFEwf7O8YKO2sVVHaNS8JaGQRoHsJceSBLw,5241
29
+ monoco/core/automation/__init__.py,sha256=Ou1lxTkhl6FR4fCB5AEZRpOe3TN7pZz4jkXIlfCkMw8,1218
30
+ monoco/core/automation/config.py,sha256=nRlUhZGj9901ELdgJtDH8i8SfJFDvSuAXdXpaLHSp5g,10777
31
+ monoco/core/automation/field_watcher.py,sha256=HCy8w9VXiA-UQZzBULet3d52z4APIEqGhICO26WOvk0,9459
32
+ monoco/core/automation/handlers.py,sha256=hroCeKN1kpyKdTXU7KOCttGCIMxmWfyPIUmG_pRa2p8,24226
33
+ monoco/core/executor/__init__.py,sha256=5Yp3iwxHzf8_ttYQejgv6-jTDqAKH2qORUlBN3ib7LU,1140
34
+ monoco/core/executor/agent_action.py,sha256=yjlooGZ5NiLdnzO11huLXD_qPOMiS-YlTqaqb3sHft8,8360
35
+ monoco/core/executor/git_action.py,sha256=DEGcdgaTnzkKBNb8DaIrNQCzbyRVMYLhoXamyFZigkc,9558
36
+ monoco/core/executor/im_action.py,sha256=Zk6PDlKwhUMe7jsI0IpldGI6uuy2oPq_AucqaYFQo78,10185
37
+ monoco/core/executor/pytest_action.py,sha256=IirSUus_fsqGcHeVnIxZ9ECFLItLiVoDdYh6po34KWU,6676
29
38
  monoco/core/hooks/__init__.py,sha256=8dWjDSwJG9JdghWBxgk7zH1U1FlJCiRo66mfbWcDK3M,437
30
39
  monoco/core/hooks/base.py,sha256=GQ59E_VX8RmtWezyg5sMFqmKRDsD7PJflcAb2TTy8S8,3276
31
- monoco/core/hooks/context.py,sha256=A0xwwU6JcsBH3t4h9jlodAdEMuajU8QeW_3bSMTWfPY,4005
40
+ monoco/core/hooks/context.py,sha256=i6G4zjiDdvPlRzJTDQgV56Mo14zQGKDL5Cibv_3p3vE,6011
32
41
  monoco/core/hooks/registry.py,sha256=O_IO1c7R9NbHk3F-xtLowQQkzPBs6qM1Xii398ZfbNU,7100
33
42
  monoco/core/hooks/builtin/__init__.py,sha256=R_e04bOLnBKy7pX5QVBkDjgUNeQ82o1IHq--CKgIX_I,177
34
43
  monoco/core/hooks/builtin/git_cleanup.py,sha256=crtUv2RJoeo1qOAgLulr6Q0_M-oFTLDNq3Uku1pl3QU,9701
@@ -41,29 +50,41 @@ monoco/core/resource/__init__.py,sha256=CZvZHdtyfgrAehCjoAUuVAJ-XGaLqN7DcGE2SEKH
41
50
  monoco/core/resource/finder.py,sha256=izX-jTw0juajXbcAuk7MtCXD7Os0pzLXBgTxL-nxwe0,4329
42
51
  monoco/core/resource/manager.py,sha256=ZRDyvZFvcFPhD9jzmE3jYbZHCIRkheqcR6INxTke6_Y,3484
43
52
  monoco/core/resource/models.py,sha256=mJffVMZ8PpWety-yHhierfmgPst_e1mRqgLWfrUrk4g,934
53
+ monoco/core/router/__init__.py,sha256=5jZ76GBR3dKTkbLhpwUeQwGqh1naSl9QamEgYKVrYcU,1289
54
+ monoco/core/router/action.py,sha256=QcsfQ1WV01OOclVGj7hDO5olknPaG85Yawtg-50vmZ0,10192
55
+ monoco/core/router/router.py,sha256=qNHak6cEKgbddgJH62uEx-zVNuUhDWqVXocgPlQHsgs,12756
56
+ monoco/core/scheduler/__init__.py,sha256=J7qv72y0FK4HFXAS50oAO0j71FHfHp6D_1JuFXS-TA0,1533
57
+ monoco/core/scheduler/base.py,sha256=7ywbKRK6o5OtEWKk4y3Q4-SI85ZMyVId4JsCJ0ohWGQ,4265
58
+ monoco/core/scheduler/engines.py,sha256=d4D50kN-n31eiCpUdNfTAFfwI2SIT0YaZ6xg_s0YJfQ,4643
59
+ monoco/core/scheduler/events.py,sha256=XpN93gXp_u4J7ayr4ubOur6Z-RG4ajkRtWoVX7PAB0U,5694
60
+ monoco/core/scheduler/local.py,sha256=fjAKGOm0MawJFfBuCsl_oAUbpbR0idIOWf56Pee-Gk0,12633
61
+ monoco/core/watcher/__init__.py,sha256=L2i39bMZmCTNWcSHFCRmRNc79VBQ5Pm0MjNjR0jTUFE,1408
62
+ monoco/core/watcher/base.py,sha256=J9jZsIGB6lmctdT2UQ5XTfaPAVSaHZ2AmR9M-No4Ab8,11837
63
+ monoco/core/watcher/dropzone.py,sha256=CGI21wozuxA7-b_qj_PpiYwXgkH-UXPSjClZcxrURmo,4903
64
+ monoco/core/watcher/issue.py,sha256=eAKSznb-ihU4Ij6X6QU2uLhPUcHfr3E6C4CjZarMXaU,10229
65
+ monoco/core/watcher/memo.py,sha256=HDZ3wJpMsLW5p5MWm3HQmFQKFsG7kGHoyMmHdAtDcf0,6405
66
+ monoco/core/watcher/task.py,sha256=0bwPfZn7WSbjqhPQshEdRjIMc7UuBkMN6Vku-n5jea8,7254
44
67
  monoco/daemon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
68
  monoco/daemon/app.py,sha256=SqE-IVawsZsgncCk5L4y2i5Hb2P2lBvpdmQIznVf2GY,17994
46
69
  monoco/daemon/commands.py,sha256=Af0zvS4xuJFyST8LiX9J82gkR4nRNLQERKp2biHHs-k,1490
70
+ monoco/daemon/events.py,sha256=5TgCfHAoN8uE8LpKbJfbe5Am55Zfz1cwDlw6drcC0dY,750
47
71
  monoco/daemon/mailroom_service.py,sha256=w5hFnT1fJODAs_HZN8A8Bu65bUZic2pEyr-hYdSM14k,6811
48
72
  monoco/daemon/models.py,sha256=O4NEsjrO22yOJCYFz9LH2SR9CFS57CA1SGNbjiHlxXQ,1053
49
73
  monoco/daemon/reproduce_stats.py,sha256=LghJ8o1jTeDpA1zVPI2vF0okGU3h4esEI1thNa6shKI,1196
50
- monoco/daemon/scheduler.py,sha256=AQGn8nbq8Abtx4J32F7u0p5dN5928TrBtnUL6HW1gGY,10786
51
- monoco/daemon/services.py,sha256=prGePP9F7nGtrUTWYaypmWgT17l_sX7Wc5N5xAJsp6I,12091
74
+ monoco/daemon/scheduler.py,sha256=mD3OstIWjGto8pZsGfJ1jsQPYHc-MgCKZbJX1_GVH7I,7219
75
+ monoco/daemon/services.py,sha256=UGGTxg_tHeNKiwxbqN8apWqp-cx56jLypU9xj_zcJbg,4201
52
76
  monoco/daemon/stats.py,sha256=XTtuFWx68O1OA44SmN0BGVa6sswts2ExmkiavUzCYiA,4800
53
77
  monoco/daemon/triggers.py,sha256=sBsXLfvI7_d3bUPzaDit1YQBypLBFJFv3l-DEIoKpbA,1815
54
78
  monoco/features/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
- monoco/features/agent/__init__.py,sha256=nZ8DXHy0F6jvU6jvUUfKmLXxojYuErtmjCniY832qLQ,576
79
+ monoco/features/agent/__init__.py,sha256=CybgqfTSOuO2QUP_JXyIeY2nbxm-9-Cc5OIAX01q78o,1025
56
80
  monoco/features/agent/adapter.py,sha256=T3lS2UjR5KFWwnCurUhsvM4_Xu5sdp3MJvuRkExecTU,1476
57
- monoco/features/agent/apoptosis.py,sha256=d2q9RsGZXFAfTGGbOhmj9rAjy9-YswGolDs0-eknPmM,1524
58
- monoco/features/agent/cli.py,sha256=HorUNabexlsVipVs4iizt8z0S5hPT_z8J6lcoYrDt8Y,9670
81
+ monoco/features/agent/cli.py,sha256=fbVwbem1Ymfw6T--Kz2ATxYGutnQECUemFDlQtnzXwc,10624
59
82
  monoco/features/agent/config.py,sha256=nBjwZsod8DI5O4MdD_N9TED87V8dxpK8m-KJAzAsc8c,3296
60
83
  monoco/features/agent/defaults.py,sha256=lw3-UIE6ZA8yR5iqczBKYtwu06pHRuuDOy4GPnmDWwU,390
61
- monoco/features/agent/engines.py,sha256=d4D50kN-n31eiCpUdNfTAFfwI2SIT0YaZ6xg_s0YJfQ,4643
84
+ monoco/features/agent/engines.py,sha256=ETZ2k6dYnprjtCA4ZiSW2099emRDx-GESbtdLoAMcxA,820
62
85
  monoco/features/agent/flow_skills.py,sha256=9YQZvhebS_J2oERsGMfWd6WjDKkAWGzlJ0guIrYgsMw,9079
63
- monoco/features/agent/manager.py,sha256=A0r81kvTXGQlK7h9vJkG9cj56Jm3TdWODfuNYsnloso,4566
64
86
  monoco/features/agent/models.py,sha256=WP1i0slVdJxQfGmmxabKOLGU5qc75QXJFvkin-5sxw0,856
65
- monoco/features/agent/session.py,sha256=A1APoF5rrQAyv6LHtldpQHeN2J6n9sZC-AwS3TSbJ_4,6144
66
- monoco/features/agent/worker.py,sha256=wCJZQCJQ6ssQmXBb6TOttZamRRnXocjUla9r5w9wy50,5742
87
+ monoco/features/agent/worker.py,sha256=ILX6EhA_NeV3QUbuhBo8AVli0rpMYJTWwlFAjG6M_QU,5755
67
88
  monoco/features/agent/resources/atoms/atom-code-dev.yaml,sha256=a4zbewWZ4bKNdA8tIIKdXt-s5Gd_M2_IU25qLpDkPlk,1790
68
89
  monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml,sha256=OEDzvMqTB3CtI8ci_z6I-ZapxD6XkJFhXIAPNpQdQac,2417
69
90
  monoco/features/agent/resources/atoms/atom-knowledge.yaml,sha256=hTkbUzycgtXPBGpAoVcHIi_P7WVson6zu9Kp79iU5HU,1735
@@ -115,8 +136,8 @@ monoco/features/i18n/resources/zh/AGENTS.md,sha256=lKkwLLCADRH7UDq9no4eQY2sRfJrb
115
136
  monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md,sha256=3ajNeaaPlqWCnNDIiWiiQHTuHMzUScWqD27PE-YBVrM,1915
116
137
  monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md,sha256=mBZbONW9aiIyXcSyj0ZdE0tNT9MhO8-ydtEkmrhCsmM,2759
117
138
  monoco/features/issue/adapter.py,sha256=puCyGKI-S5o2wcJy8fNinIqh2pCyO2auK2pATT67B8k,1769
118
- monoco/features/issue/commands.py,sha256=akSDFnsLRdyQH1vj8fxc57Z-CmOBKtIluHxM3zZRNMU,61148
119
- monoco/features/issue/core.py,sha256=qWPiUhuNvmA38Ur2dSNcK-j7zOZAXRXvv2W1PSweB3o,64612
139
+ monoco/features/issue/commands.py,sha256=nKCU6-NQzLuACdKD_nu00ujd-15lhaYfA9YPoQ10ZmY,63540
140
+ monoco/features/issue/core.py,sha256=4bDJpd7eF4459PjFvn84wk-Mse77Mez1CB98xuDcwCY,73602
120
141
  monoco/features/issue/criticality.py,sha256=Bw0xH0vpx8z59bL-WQjfy4a5zmjXNM0f-aWBzQ5JZCA,18931
121
142
  monoco/features/issue/domain_commands.py,sha256=eatSF_uZp4nGpVr2PIgb00MWfEBm0OnyAd4JvUJEAAA,1535
122
143
  monoco/features/issue/domain_service.py,sha256=bEs_WXOWmotgIR-lGwyWekF4nonvjsgrK1YG3pyVfwk,2564
@@ -166,7 +187,7 @@ monoco/features/memo/resources/zh/AGENTS.md,sha256=AFlzvhGxAk77np-E8XLb-tkWJCo0b
166
187
  monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md,sha256=XjJw6qYd99_EQcnwE9RPHjRIsgCf8R-_FHAIEQLyBno,2174
167
188
  monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md,sha256=CfmGNy11n4uDeapQ2qB4e9lH-4D_TO-AyAhsrjKRf6g,4297
168
189
  monoco/features/spike/adapter.py,sha256=DqwhFuHdBCy_gpgOuMJ7faccMJKJGVdjxTDecIZPsI8,1571
169
- monoco/features/spike/commands.py,sha256=EuJ7trDaobIe7ae7pJ-ftpo_cO8-19R_fAZZCh2JcMg,3871
190
+ monoco/features/spike/commands.py,sha256=r5gvqVp6oSjYq5HOA7a99_cVjjzn9GWeZ3hBaT2j7_c,4016
170
191
  monoco/features/spike/core.py,sha256=OuNWXQ-cBttP2oIF0B5iy0aEUH2MUMyGANG8ayEmjPo,4120
171
192
  monoco/features/spike/resources/en/AGENTS.md,sha256=NG3CMnlDk_0J8hnRUcueAM9lgIQr_dZ42R_31-LC48E,306
172
193
  monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md,sha256=JwEAEZesYbbFi1UVPPv_yCGhY-W56uTNUGmOrsn6_3I,1942
@@ -174,8 +195,8 @@ monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md,sha2
174
195
  monoco/features/spike/resources/zh/AGENTS.md,sha256=5RHNl7fc3RdYYTFH483ojJl_arGPKkyYziOuGgFbqqg,290
175
196
  monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md,sha256=sgnRtrGsrW2jQnjYkq-PjyjtNTKSgmsuaKOCoqFWK1A,1741
176
197
  monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md,sha256=qDqHHIPTMDZ_bmgiApP8hCvSPP-b1UeW40fsSAGBdEE,3344
177
- monoco_toolkit-0.3.11.dist-info/METADATA,sha256=hqdzXIfWZ_tTdtJHy6bmSo21NQlanv1zUOSAZDT5M1A,4909
178
- monoco_toolkit-0.3.11.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
179
- monoco_toolkit-0.3.11.dist-info/entry_points.txt,sha256=iYj7FWYBdtClU15-Du1skqD0s6SFSIhJvxJ29VWp8ng,43
180
- monoco_toolkit-0.3.11.dist-info/licenses/LICENSE,sha256=ACAGGjV6aod4eIlVUTx1q9PZbnZGN5bBwkSs9RHj83s,1071
181
- monoco_toolkit-0.3.11.dist-info/RECORD,,
198
+ monoco_toolkit-0.3.12.dist-info/METADATA,sha256=e2D_EGe8NFCKan53cAQKb8HdI8mgpXmR0JBuHbodnOY,4909
199
+ monoco_toolkit-0.3.12.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
200
+ monoco_toolkit-0.3.12.dist-info/entry_points.txt,sha256=iYj7FWYBdtClU15-Du1skqD0s6SFSIhJvxJ29VWp8ng,43
201
+ monoco_toolkit-0.3.12.dist-info/licenses/LICENSE,sha256=ACAGGjV6aod4eIlVUTx1q9PZbnZGN5bBwkSs9RHj83s,1071
202
+ monoco_toolkit-0.3.12.dist-info/RECORD,,
@@ -1,44 +0,0 @@
1
- from .manager import SessionManager
2
- from .models import RoleTemplate
3
-
4
- class ApoptosisManager:
5
- """
6
- Manages the controlled shutdown (Apoptosis) and investigation (Autopsy)
7
- of failed agent sessions.
8
- """
9
-
10
- def __init__(self, session_manager: SessionManager):
11
- self.session_manager = session_manager
12
-
13
- def trigger_apoptosis(self, session_id: str, failure_reason: str = "Unknown") -> None:
14
- """
15
- Trigger the apoptosis process for a given session.
16
- 1. Mark session as crashed.
17
- 2. Spin up a Coroner agent to diagnose.
18
- """
19
- session = self.session_manager.get_session(session_id)
20
- if not session:
21
- return
22
-
23
- # 1. Mark as crashed
24
- session.model.status = "crashed"
25
-
26
- # 2. Start Coroner
27
- self._perform_autopsy(session, failure_reason)
28
-
29
- def _perform_autopsy(self, victim_session, failure_reason: str):
30
- coroner_role = RoleTemplate(
31
- name="Coroner",
32
- description="Investigates cause of death for failed agents.",
33
- trigger="system.crash",
34
- goal=f"Determine why the previous agent failed. Reason: {failure_reason}",
35
- system_prompt="You are the Coroner. Analyze the logs.",
36
- engine="gemini"
37
- )
38
-
39
- coroner_session = self.session_manager.create_session(
40
- issue_id=victim_session.model.issue_id,
41
- role=coroner_role
42
- )
43
-
44
- coroner_session.start()
@@ -1,127 +0,0 @@
1
- from typing import Dict, List, Optional
2
- import uuid
3
- from pathlib import Path
4
-
5
- from .models import RoleTemplate
6
- from .worker import Worker
7
- from .session import Session, RuntimeSession
8
- from monoco.core.hooks import HookRegistry, get_registry
9
- from monoco.core.config import find_monoco_root, MonocoConfig
10
-
11
-
12
- class SessionManager:
13
- """
14
- Manages the lifecycle of sessions.
15
- Responsible for creating, tracking, and retrieving sessions.
16
- """
17
-
18
- def __init__(
19
- self,
20
- project_root: Optional[Path] = None,
21
- hook_registry: Optional[HookRegistry] = None,
22
- config: Optional[MonocoConfig] = None,
23
- ):
24
- # In-memory storage for now. In prod, this might be a DB or file-backed.
25
- self._sessions: Dict[str, RuntimeSession] = {}
26
- self.project_root = project_root or find_monoco_root()
27
- self.config = config
28
-
29
- self.sessions_dir = self.project_root / ".monoco" / "sessions"
30
- self.sessions_dir.mkdir(parents=True, exist_ok=True)
31
-
32
- # Initialize hook registry
33
- self.hook_registry = hook_registry or get_registry()
34
-
35
- # Load hooks from config if available
36
- self._load_hooks_from_config()
37
-
38
- # Load persisted sessions
39
- self._load_sessions()
40
-
41
- def _load_sessions(self):
42
- """Load sessions from disk."""
43
- import json
44
-
45
- for session_file in self.sessions_dir.glob("*.json"):
46
- try:
47
- data = json.loads(session_file.read_text())
48
- session_model = Session(**data)
49
- # Rehydrate as Observer
50
- runtime = RuntimeSession(
51
- session_model,
52
- worker=None, # No worker for loaded sessions initially (Observer mode)
53
- hook_registry=self.hook_registry,
54
- project_root=self.project_root,
55
- save_callback=self._save_session,
56
- )
57
- self._sessions[session_model.id] = runtime
58
- # Check status immediately to see if it's still running
59
- runtime.refresh_status()
60
- except Exception as e:
61
- print(f"Failed to load session {session_file}: {e}")
62
-
63
- def _save_session(self, session: Session):
64
- """Save session to disk."""
65
- file_path = self.sessions_dir / f"{session.id}.json"
66
- file_path.write_text(session.model_dump_json(indent=2))
67
-
68
- def _load_hooks_from_config(self) -> None:
69
- """Load and register hooks from configuration."""
70
- if self.config is None:
71
- try:
72
- from monoco.core.config import get_config
73
-
74
- self.config = get_config(str(self.project_root))
75
- except Exception:
76
- return
77
-
78
- # Load hooks from config
79
- if self.config and hasattr(self.config, "session_hooks"):
80
- hooks_config = self.config.session_hooks
81
- if hooks_config:
82
- self.hook_registry.load_from_config(hooks_config, self.project_root)
83
-
84
- def create_session(self, issue_id: str, role: RoleTemplate) -> RuntimeSession:
85
- session_id = str(uuid.uuid4())
86
- branch_name = (
87
- f"agent/{issue_id}/{session_id[:8]}" # Simple branch naming strategy
88
- )
89
-
90
- session_model = Session(
91
- id=session_id,
92
- issue_id=issue_id,
93
- role_name=role.name,
94
- branch_name=branch_name,
95
- )
96
-
97
- # Get timeout from config
98
- timeout = 900
99
- if self.config and hasattr(self.config, "agent"):
100
- timeout = self.config.agent.timeout_seconds
101
-
102
- worker = Worker(role, issue_id, timeout=timeout)
103
- runtime = RuntimeSession(
104
- session_model,
105
- worker,
106
- hook_registry=self.hook_registry,
107
- project_root=self.project_root,
108
- save_callback=self._save_session,
109
- )
110
- self._sessions[session_id] = runtime
111
- self._save_session(session_model)
112
- return runtime
113
-
114
- def get_session(self, session_id: str) -> Optional[RuntimeSession]:
115
- return self._sessions.get(session_id)
116
-
117
- def list_sessions(self, issue_id: Optional[str] = None) -> List[RuntimeSession]:
118
- if issue_id:
119
- return [s for s in self._sessions.values() if s.model.issue_id == issue_id]
120
- return list(self._sessions.values())
121
-
122
- def terminate_session(self, session_id: str):
123
- session = self.get_session(session_id)
124
- if session:
125
- session.terminate()
126
- # We keep the record for history
127
- # del self._sessions[session_id]