monoco-toolkit 0.3.1__py3-none-any.whl → 0.3.3__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 (35) hide show
  1. monoco/core/config.py +35 -0
  2. monoco/core/integrations.py +0 -6
  3. monoco/core/resources/en/AGENTS.md +25 -0
  4. monoco/core/resources/en/SKILL.md +32 -1
  5. monoco/core/resources/zh/AGENTS.md +25 -0
  6. monoco/core/resources/zh/SKILL.md +32 -0
  7. monoco/core/sync.py +6 -19
  8. monoco/features/i18n/core.py +31 -11
  9. monoco/features/issue/commands.py +24 -1
  10. monoco/features/issue/core.py +90 -39
  11. monoco/features/issue/domain/models.py +1 -0
  12. monoco/features/issue/domain_commands.py +47 -0
  13. monoco/features/issue/domain_service.py +69 -0
  14. monoco/features/issue/linter.py +119 -11
  15. monoco/features/issue/validator.py +47 -0
  16. monoco/features/scheduler/__init__.py +19 -0
  17. monoco/features/scheduler/cli.py +204 -0
  18. monoco/features/scheduler/config.py +32 -0
  19. monoco/features/scheduler/defaults.py +54 -0
  20. monoco/features/scheduler/manager.py +49 -0
  21. monoco/features/scheduler/models.py +24 -0
  22. monoco/features/scheduler/reliability.py +99 -0
  23. monoco/features/scheduler/session.py +87 -0
  24. monoco/features/scheduler/worker.py +129 -0
  25. monoco/main.py +4 -0
  26. {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/METADATA +1 -1
  27. {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/RECORD +30 -24
  28. monoco/core/agent/__init__.py +0 -3
  29. monoco/core/agent/action.py +0 -168
  30. monoco/core/agent/adapters.py +0 -133
  31. monoco/core/agent/protocol.py +0 -32
  32. monoco/core/agent/state.py +0 -106
  33. {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/WHEEL +0 -0
  34. {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/entry_points.txt +0 -0
  35. {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,133 +0,0 @@
1
- """
2
- CLI Adapters for Agent Frameworks.
3
- """
4
-
5
- import shutil
6
- from typing import List
7
- from pathlib import Path
8
- from .protocol import AgentClient
9
-
10
-
11
- class BaseCLIClient:
12
- def __init__(self, executable: str):
13
- self._executable = executable
14
-
15
- @property
16
- def name(self) -> str:
17
- return self._executable
18
-
19
- async def available(self) -> bool:
20
- return shutil.which(self._executable) is not None
21
-
22
- def _build_prompt(self, prompt: str, context_files: List[Path]) -> str:
23
- """Concatenate prompt and context files."""
24
- # Inject Language Rule
25
- try:
26
- from monoco.core.config import get_config
27
-
28
- settings = get_config()
29
- lang = settings.i18n.source_lang
30
- if lang:
31
- prompt = f"{prompt}\n\n[SYSTEM: LANGUAGE CONSTRAINT]\nThe project source language is '{lang}'. You MUST use '{lang}' for all thinking and reporting unless explicitly instructed otherwise."
32
- except Exception:
33
- pass
34
-
35
- full_prompt = [prompt]
36
- if context_files:
37
- full_prompt.append("\n\n--- CONTEXT FILES ---")
38
- for file_path in context_files:
39
- try:
40
- full_prompt.append(f"\nFile: {file_path}")
41
- full_prompt.append("```")
42
- # Read file content safely
43
- full_prompt.append(
44
- file_path.read_text(encoding="utf-8", errors="replace")
45
- )
46
- full_prompt.append("```")
47
- except Exception as e:
48
- full_prompt.append(f"Error reading {file_path}: {e}")
49
- full_prompt.append("--- END CONTEXT ---\n")
50
- return "\n".join(full_prompt)
51
-
52
- async def _run_command(self, args: List[str]) -> str:
53
- """Run the CLI command and return stdout."""
54
- # Using synchronous subprocess in async function for now
55
- # Ideally this should use asyncio.create_subprocess_exec
56
- import asyncio
57
-
58
- proc = await asyncio.create_subprocess_exec(
59
- *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
60
- )
61
-
62
- stdout, stderr = await proc.communicate()
63
-
64
- if proc.returncode != 0:
65
- error_msg = stderr.decode().strip()
66
- raise RuntimeError(
67
- f"Agent CLI failed (code {proc.returncode}): {error_msg}"
68
- )
69
-
70
- return stdout.decode().strip()
71
-
72
-
73
- class GeminiClient(BaseCLIClient, AgentClient):
74
- """Adapter for Google Gemini CLI."""
75
-
76
- def __init__(self):
77
- super().__init__("gemini")
78
-
79
- async def execute(self, prompt: str, context_files: List[Path] = []) -> str:
80
- full_prompt = self._build_prompt(prompt, context_files)
81
- # Usage: gemini "prompt"
82
- return await self._run_command([self._executable, full_prompt])
83
-
84
-
85
- class ClaudeClient(BaseCLIClient, AgentClient):
86
- """Adapter for Anthropic Claude CLI."""
87
-
88
- def __init__(self):
89
- super().__init__("claude")
90
-
91
- async def execute(self, prompt: str, context_files: List[Path] = []) -> str:
92
- full_prompt = self._build_prompt(prompt, context_files)
93
- # Usage: claude -p "prompt"
94
- return await self._run_command([self._executable, "-p", full_prompt])
95
-
96
-
97
- class QwenClient(BaseCLIClient, AgentClient):
98
- """Adapter for Alibaba Qwen CLI."""
99
-
100
- def __init__(self):
101
- super().__init__("qwen")
102
-
103
- async def execute(self, prompt: str, context_files: List[Path] = []) -> str:
104
- full_prompt = self._build_prompt(prompt, context_files)
105
- # Usage: qwen "prompt"
106
- return await self._run_command([self._executable, full_prompt])
107
-
108
-
109
- class KimiClient(BaseCLIClient, AgentClient):
110
- """Adapter for Moonshot Kimi CLI."""
111
-
112
- def __init__(self):
113
- super().__init__("kimi")
114
-
115
- async def execute(self, prompt: str, context_files: List[Path] = []) -> str:
116
- full_prompt = self._build_prompt(prompt, context_files)
117
- # Usage: kimi "prompt"
118
- return await self._run_command([self._executable, full_prompt])
119
-
120
-
121
- _ADAPTERS = {
122
- "gemini": GeminiClient,
123
- "claude": ClaudeClient,
124
- "qwen": QwenClient,
125
- "kimi": KimiClient,
126
- }
127
-
128
-
129
- def get_agent_client(name: str) -> AgentClient:
130
- """Factory to get agent client by name."""
131
- if name not in _ADAPTERS:
132
- raise ValueError(f"Unknown agent provider: {name}")
133
- return _ADAPTERS[name]()
@@ -1,32 +0,0 @@
1
- """
2
- Protocol definition for Agent Clients.
3
- """
4
-
5
- from typing import Protocol, List
6
- from pathlib import Path
7
-
8
-
9
- class AgentClient(Protocol):
10
- """Protocol for interacting with CLI-based agents."""
11
-
12
- @property
13
- def name(self) -> str:
14
- """Name of the agent provider (e.g. 'gemini', 'claude')."""
15
- ...
16
-
17
- async def available(self) -> bool:
18
- """Check if the agent is available in the current environment."""
19
- ...
20
-
21
- async def execute(self, prompt: str, context_files: List[Path] = []) -> str:
22
- """
23
- Execute a prompt against the agent.
24
-
25
- Args:
26
- prompt: The main instructions.
27
- context_files: List of files to provide as context.
28
-
29
- Returns:
30
- The raw string response from the agent.
31
- """
32
- ...
@@ -1,106 +0,0 @@
1
- """
2
- Agent State Management.
3
-
4
- Handles persistence and retrieval of agent availability state.
5
- """
6
-
7
- import yaml
8
- import logging
9
- from pathlib import Path
10
- from datetime import datetime, timezone
11
- from typing import Dict, Optional
12
- from pydantic import BaseModel
13
-
14
- logger = logging.getLogger("monoco.core.agent.state")
15
-
16
-
17
- class AgentProviderState(BaseModel):
18
- available: bool
19
- path: Optional[str] = None
20
- error: Optional[str] = None
21
- latency_ms: Optional[int] = None
22
-
23
-
24
- class AgentState(BaseModel):
25
- last_checked: datetime
26
- providers: Dict[str, AgentProviderState]
27
-
28
- @property
29
- def is_stale(self) -> bool:
30
- """Check if the state is older than 7 days."""
31
- delta = datetime.now(timezone.utc) - self.last_checked
32
- return delta.days > 7
33
-
34
-
35
- class AgentStateManager:
36
- def __init__(self, state_path: Path = Path.home() / ".monoco" / "agent_state.yaml"):
37
- self.state_path = state_path
38
- self._state: Optional[AgentState] = None
39
-
40
- def load(self) -> Optional[AgentState]:
41
- """Load state from file, returning None if missing or invalid."""
42
- if not self.state_path.exists():
43
- return None
44
-
45
- try:
46
- with open(self.state_path, "r") as f:
47
- data = yaml.safe_load(f)
48
- if not data:
49
- return None
50
- # Handle ISO string to datetime conversion if needed provided by Pydantic mostly
51
- return AgentState(**data)
52
- except Exception as e:
53
- logger.warning(f"Failed to load agent state: {e}")
54
- return None
55
-
56
- def get_or_refresh(self, force: bool = False) -> AgentState:
57
- """Get current state, refreshing if missing, stale, or forced."""
58
- if not force:
59
- self._state = self.load()
60
- if self._state and not self._state.is_stale:
61
- return self._state
62
-
63
- return self.refresh()
64
-
65
- def refresh(self) -> AgentState:
66
- """Run diagnostics on all integrations and update state."""
67
- logger.info("Refreshing agent state...")
68
-
69
- from monoco.core.integrations import get_all_integrations
70
- from monoco.core.config import get_config
71
-
72
- # Load config to get possible overrides
73
- # Determine root (hacky for now, should be passed)
74
- root = Path.cwd()
75
- config = get_config(str(root))
76
-
77
- integrations = get_all_integrations(
78
- config_overrides=config.agent.integrations, enabled_only=True
79
- )
80
-
81
- providers = {}
82
- for key, integration in integrations.items():
83
- if not integration.bin_name:
84
- continue # Skip integrations that don't have a binary component
85
-
86
- health = integration.check_health()
87
- providers[key] = AgentProviderState(
88
- available=health.available,
89
- path=health.path,
90
- error=health.error,
91
- latency_ms=health.latency_ms,
92
- )
93
-
94
- state = AgentState(last_checked=datetime.now(timezone.utc), providers=providers)
95
-
96
- # Save state
97
- self.state_path.parent.mkdir(parents=True, exist_ok=True)
98
- with open(self.state_path, "w") as f:
99
- yaml.dump(state.model_dump(mode="json"), f)
100
-
101
- self._state = state
102
- return state
103
-
104
- def _find_script(self) -> Optional[Path]:
105
- """[Deprecated] No longer used."""
106
- return None