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.
- monoco/core/automation/__init__.py +51 -0
- monoco/core/automation/config.py +338 -0
- monoco/core/automation/field_watcher.py +296 -0
- monoco/core/automation/handlers.py +723 -0
- monoco/core/config.py +1 -1
- monoco/core/executor/__init__.py +38 -0
- monoco/core/executor/agent_action.py +254 -0
- monoco/core/executor/git_action.py +303 -0
- monoco/core/executor/im_action.py +309 -0
- monoco/core/executor/pytest_action.py +218 -0
- monoco/core/git.py +15 -0
- monoco/core/hooks/context.py +74 -13
- monoco/core/router/__init__.py +55 -0
- monoco/core/router/action.py +341 -0
- monoco/core/router/router.py +392 -0
- monoco/core/scheduler/__init__.py +63 -0
- monoco/core/scheduler/base.py +152 -0
- monoco/core/scheduler/engines.py +175 -0
- monoco/core/scheduler/events.py +171 -0
- monoco/core/scheduler/local.py +377 -0
- monoco/core/watcher/__init__.py +57 -0
- monoco/core/watcher/base.py +365 -0
- monoco/core/watcher/dropzone.py +152 -0
- monoco/core/watcher/issue.py +303 -0
- monoco/core/watcher/memo.py +200 -0
- monoco/core/watcher/task.py +238 -0
- monoco/daemon/events.py +34 -0
- monoco/daemon/scheduler.py +172 -201
- monoco/daemon/services.py +27 -243
- monoco/features/agent/__init__.py +25 -7
- monoco/features/agent/cli.py +91 -57
- monoco/features/agent/engines.py +31 -170
- monoco/features/agent/worker.py +1 -1
- monoco/features/issue/commands.py +90 -32
- monoco/features/issue/core.py +249 -4
- monoco/features/spike/commands.py +5 -3
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/METADATA +1 -1
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/RECORD +41 -20
- monoco/features/agent/apoptosis.py +0 -44
- monoco/features/agent/manager.py +0 -127
- monoco/features/agent/session.py +0 -169
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.3.12.dist-info}/licenses/LICENSE +0 -0
monoco/features/issue/core.py
CHANGED
|
@@ -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
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
51
|
-
monoco/daemon/services.py,sha256=
|
|
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=
|
|
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/
|
|
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=
|
|
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/
|
|
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=
|
|
119
|
-
monoco/features/issue/core.py,sha256=
|
|
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=
|
|
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.
|
|
178
|
-
monoco_toolkit-0.3.
|
|
179
|
-
monoco_toolkit-0.3.
|
|
180
|
-
monoco_toolkit-0.3.
|
|
181
|
-
monoco_toolkit-0.3.
|
|
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()
|
monoco/features/agent/manager.py
DELETED
|
@@ -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]
|