monoco-toolkit 0.2.8__py3-none-any.whl → 0.3.1__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 (63) hide show
  1. monoco/cli/project.py +35 -31
  2. monoco/cli/workspace.py +26 -16
  3. monoco/core/agent/__init__.py +0 -2
  4. monoco/core/agent/action.py +44 -20
  5. monoco/core/agent/adapters.py +20 -16
  6. monoco/core/agent/protocol.py +5 -4
  7. monoco/core/agent/state.py +21 -21
  8. monoco/core/config.py +90 -33
  9. monoco/core/execution.py +21 -16
  10. monoco/core/feature.py +8 -5
  11. monoco/core/git.py +61 -30
  12. monoco/core/hooks.py +57 -0
  13. monoco/core/injection.py +47 -44
  14. monoco/core/integrations.py +50 -35
  15. monoco/core/lsp.py +12 -1
  16. monoco/core/output.py +35 -16
  17. monoco/core/registry.py +3 -2
  18. monoco/core/setup.py +190 -124
  19. monoco/core/skills.py +121 -107
  20. monoco/core/state.py +12 -10
  21. monoco/core/sync.py +85 -56
  22. monoco/core/telemetry.py +10 -6
  23. monoco/core/workspace.py +26 -19
  24. monoco/daemon/app.py +123 -79
  25. monoco/daemon/commands.py +14 -13
  26. monoco/daemon/models.py +11 -3
  27. monoco/daemon/reproduce_stats.py +8 -8
  28. monoco/daemon/services.py +32 -33
  29. monoco/daemon/stats.py +59 -40
  30. monoco/features/config/commands.py +38 -25
  31. monoco/features/i18n/adapter.py +4 -5
  32. monoco/features/i18n/commands.py +83 -49
  33. monoco/features/i18n/core.py +94 -54
  34. monoco/features/issue/adapter.py +6 -7
  35. monoco/features/issue/commands.py +468 -272
  36. monoco/features/issue/core.py +419 -312
  37. monoco/features/issue/domain/lifecycle.py +33 -23
  38. monoco/features/issue/domain/models.py +71 -38
  39. monoco/features/issue/domain/parser.py +92 -69
  40. monoco/features/issue/domain/workspace.py +19 -16
  41. monoco/features/issue/engine/__init__.py +3 -3
  42. monoco/features/issue/engine/config.py +18 -25
  43. monoco/features/issue/engine/machine.py +72 -39
  44. monoco/features/issue/engine/models.py +4 -2
  45. monoco/features/issue/linter.py +287 -157
  46. monoco/features/issue/lsp/definition.py +26 -19
  47. monoco/features/issue/migration.py +45 -34
  48. monoco/features/issue/models.py +29 -13
  49. monoco/features/issue/monitor.py +24 -8
  50. monoco/features/issue/resources/en/SKILL.md +6 -2
  51. monoco/features/issue/validator.py +395 -208
  52. monoco/features/skills/__init__.py +0 -1
  53. monoco/features/skills/core.py +24 -18
  54. monoco/features/spike/adapter.py +4 -5
  55. monoco/features/spike/commands.py +51 -38
  56. monoco/features/spike/core.py +24 -16
  57. monoco/main.py +34 -21
  58. {monoco_toolkit-0.2.8.dist-info → monoco_toolkit-0.3.1.dist-info}/METADATA +1 -1
  59. monoco_toolkit-0.3.1.dist-info/RECORD +84 -0
  60. monoco_toolkit-0.2.8.dist-info/RECORD +0 -83
  61. {monoco_toolkit-0.2.8.dist-info → monoco_toolkit-0.3.1.dist-info}/WHEEL +0 -0
  62. {monoco_toolkit-0.2.8.dist-info → monoco_toolkit-0.3.1.dist-info}/entry_points.txt +0 -0
  63. {monoco_toolkit-0.2.8.dist-info → monoco_toolkit-0.3.1.dist-info}/licenses/LICENSE +0 -0
monoco/cli/project.py CHANGED
@@ -1,35 +1,33 @@
1
1
  import typer
2
2
  from pathlib import Path
3
- from typing import Optional, Annotated
3
+ from typing import Optional
4
4
  from rich.console import Console
5
5
  from rich.table import Table
6
6
  import yaml
7
- import json
8
- import os
9
7
 
10
- from monoco.core.workspace import find_projects, is_project_root
11
- from monoco.core.config import get_config
8
+ from monoco.core.workspace import find_projects
12
9
  from monoco.core.output import AgentOutput, OutputManager
13
10
 
14
11
  app = typer.Typer(help="Manage Monoco Projects")
15
12
  console = Console()
16
13
 
14
+
17
15
  @app.command("list")
18
16
  def list_projects(
19
17
  json: AgentOutput = False,
20
- root: Optional[str] = typer.Option(None, "--root", help="Workspace root")
18
+ root: Optional[str] = typer.Option(None, "--root", help="Workspace root"),
21
19
  ):
22
20
  """List all discovered projects in the workspace."""
23
21
  cwd = Path(root).resolve() if root else Path.cwd()
24
22
  projects = find_projects(cwd)
25
-
23
+
26
24
  if OutputManager.is_agent_mode():
27
25
  data = [
28
26
  {
29
27
  "id": p.id,
30
28
  "name": p.name,
31
29
  "path": str(p.path),
32
- "key": p.config.project.key if p.config.project else ""
30
+ "key": p.config.project.key if p.config.project else "",
33
31
  }
34
32
  for p in projects
35
33
  ]
@@ -40,48 +38,54 @@ def list_projects(
40
38
  table.add_column("Name", style="magenta")
41
39
  table.add_column("Key", style="green")
42
40
  table.add_column("Path", style="dim")
43
-
41
+
44
42
  for p in projects:
45
- path_str = str(p.path.relative_to(cwd)) if p.path.is_relative_to(cwd) else str(p.path)
43
+ path_str = (
44
+ str(p.path.relative_to(cwd))
45
+ if p.path.is_relative_to(cwd)
46
+ else str(p.path)
47
+ )
46
48
  if path_str == ".":
47
49
  path_str = "(root)"
48
50
  key = p.config.project.key if p.config.project else "N/A"
49
51
  table.add_row(p.id, p.name, key, path_str)
50
-
52
+
51
53
  console.print(table)
52
54
 
55
+
53
56
  @app.command("init")
54
57
  def init_project(
55
58
  name: str = typer.Option(..., "--name", "-n", help="Project Name"),
56
59
  key: str = typer.Option(..., "--key", "-k", help="Project Key"),
57
- force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing config"),
60
+ force: bool = typer.Option(
61
+ False, "--force", "-f", help="Overwrite existing config"
62
+ ),
58
63
  json: AgentOutput = False,
59
64
  ):
60
65
  """Initialize a new project in the current directory."""
61
66
  cwd = Path.cwd()
62
67
  project_config_path = cwd / ".monoco" / "project.yaml"
63
-
68
+
64
69
  if project_config_path.exists() and not force:
65
- OutputManager.error(f"Project already initialized in {cwd}. Use --force to overwrite.")
70
+ OutputManager.error(
71
+ f"Project already initialized in {cwd}. Use --force to overwrite."
72
+ )
66
73
  raise typer.Exit(code=1)
67
-
74
+
68
75
  cwd.mkdir(parents=True, exist_ok=True)
69
76
  (cwd / ".monoco").mkdir(exist_ok=True)
70
-
71
- config = {
72
- "project": {
73
- "name": name,
74
- "key": key
75
- }
76
- }
77
-
77
+
78
+ config = {"project": {"name": name, "key": key}}
79
+
78
80
  with open(project_config_path, "w") as f:
79
81
  yaml.dump(config, f, default_flow_style=False)
80
-
81
- OutputManager.print({
82
- "status": "initialized",
83
- "name": name,
84
- "key": key,
85
- "path": str(cwd),
86
- "config_file": str(project_config_path)
87
- })
82
+
83
+ OutputManager.print(
84
+ {
85
+ "status": "initialized",
86
+ "name": name,
87
+ "key": key,
88
+ "path": str(cwd),
89
+ "config_file": str(project_config_path),
90
+ }
91
+ )
monoco/cli/workspace.py CHANGED
@@ -1,46 +1,56 @@
1
1
  import typer
2
2
  from pathlib import Path
3
3
  from rich.console import Console
4
- from typing import Annotated
5
4
  import yaml
6
- import json
7
5
 
8
6
  from monoco.core.output import AgentOutput, OutputManager
7
+ from monoco.core.hooks import install_hooks
9
8
 
10
9
  app = typer.Typer(help="Manage Monoco Workspace")
11
10
  console = Console()
12
11
 
12
+
13
13
  @app.command("init")
14
14
  def init_workspace(
15
- force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing config"),
15
+ force: bool = typer.Option(
16
+ False, "--force", "-f", help="Overwrite existing config"
17
+ ),
16
18
  json: AgentOutput = False,
17
19
  ):
18
20
  """Initialize a workspace environment in the current directory."""
19
21
  cwd = Path.cwd()
20
22
  workspace_config_path = cwd / ".monoco" / "workspace.yaml"
21
-
23
+
22
24
  if workspace_config_path.exists() and not force:
23
- OutputManager.error(f"Workspace already initialized in {cwd}. Use --force to overwrite.")
25
+ OutputManager.error(
26
+ f"Workspace already initialized in {cwd}. Use --force to overwrite."
27
+ )
24
28
  raise typer.Exit(code=1)
25
29
 
26
30
  cwd.mkdir(parents=True, exist_ok=True)
27
31
  (cwd / ".monoco").mkdir(exist_ok=True)
28
-
32
+
29
33
  # Default workspace config
30
34
  config = {
31
35
  "paths": {
32
- "issues": "Issues", # Default
36
+ "issues": "Issues", # Default
33
37
  "spikes": ".references",
34
- "specs": "SPECS"
35
- }
38
+ },
39
+ "hooks": {"pre-commit": "monoco issue lint --recursive"},
36
40
  }
37
-
41
+
38
42
  with open(workspace_config_path, "w") as f:
39
43
  yaml.dump(config, f, default_flow_style=False)
40
-
41
- OutputManager.print({
42
- "status": "initialized",
43
- "path": str(cwd),
44
- "config_file": str(workspace_config_path)
45
- })
46
44
 
45
+ try:
46
+ install_hooks(cwd, config["hooks"])
47
+ except Exception as e:
48
+ OutputManager.warning(f"Failed to install hooks: {e}")
49
+
50
+ OutputManager.print(
51
+ {
52
+ "status": "initialized",
53
+ "path": str(cwd),
54
+ "config_file": str(workspace_config_path),
55
+ }
56
+ )
@@ -1,5 +1,3 @@
1
1
  """
2
2
  Monoco Agent Execution Layer.
3
3
  """
4
-
5
- from .state import AgentStateManager, AgentState, AgentProviderState
@@ -1,12 +1,13 @@
1
-
2
1
  import re
3
2
  import yaml
4
3
  from pathlib import Path
5
4
  from typing import Dict, List, Optional, Any
6
- from pydantic import BaseModel, Field
5
+ from pydantic import BaseModel
6
+
7
7
 
8
8
  class ActionContext(BaseModel):
9
9
  """Context information for matching actions."""
10
+
10
11
  id: Optional[str] = None
11
12
  type: Optional[str] = None
12
13
  stage: Optional[str] = None
@@ -14,8 +15,10 @@ class ActionContext(BaseModel):
14
15
  file_path: Optional[str] = None
15
16
  project_id: Optional[str] = None
16
17
 
18
+
17
19
  class ActionWhen(BaseModel):
18
20
  """Conditions under which an action should be displayed/active."""
21
+
19
22
  idMatch: Optional[str] = None
20
23
  typeMatch: Optional[str] = None
21
24
  stageMatch: Optional[str] = None
@@ -26,16 +29,33 @@ class ActionWhen(BaseModel):
26
29
  """Evaluate if the context matches these criteria."""
27
30
  if self.idMatch and context.id and not re.match(self.idMatch, context.id):
28
31
  return False
29
- if self.typeMatch and context.type and not re.match(self.typeMatch, context.type):
32
+ if (
33
+ self.typeMatch
34
+ and context.type
35
+ and not re.match(self.typeMatch, context.type)
36
+ ):
30
37
  return False
31
- if self.stageMatch and context.stage and not re.match(self.stageMatch, context.stage):
38
+ if (
39
+ self.stageMatch
40
+ and context.stage
41
+ and not re.match(self.stageMatch, context.stage)
42
+ ):
32
43
  return False
33
- if self.statusMatch and context.status and not re.match(self.statusMatch, context.status):
44
+ if (
45
+ self.statusMatch
46
+ and context.status
47
+ and not re.match(self.statusMatch, context.status)
48
+ ):
34
49
  return False
35
- if self.fileMatch and context.file_path and not re.match(self.fileMatch, context.file_path):
50
+ if (
51
+ self.fileMatch
52
+ and context.file_path
53
+ and not re.match(self.fileMatch, context.file_path)
54
+ ):
36
55
  return False
37
56
  return True
38
57
 
58
+
39
59
  class PromptyAction(BaseModel):
40
60
  name: str
41
61
  description: str
@@ -46,10 +66,11 @@ class PromptyAction(BaseModel):
46
66
  outputs: Dict[str, Any] = {}
47
67
  template: str
48
68
  when: Optional[ActionWhen] = None
49
-
69
+
50
70
  # Monoco specific metadata
51
71
  path: Optional[str] = None
52
- provider: Optional[str] = None # Derived from model.api or explicitly set
72
+ provider: Optional[str] = None # Derived from model.api or explicitly set
73
+
53
74
 
54
75
  class ActionRegistry:
55
76
  def __init__(self, project_root: Optional[Path] = None):
@@ -59,7 +80,7 @@ class ActionRegistry:
59
80
  def scan(self) -> List[PromptyAction]:
60
81
  """Scan user global and project local directories for .prompty files."""
61
82
  self._actions = []
62
-
83
+
63
84
  # 1. User Global: ~/.monoco/actions/
64
85
  user_dir = Path.home() / ".monoco" / "actions"
65
86
  self._scan_dir(user_dir)
@@ -68,7 +89,7 @@ class ActionRegistry:
68
89
  if self.project_root:
69
90
  project_dir = self.project_root / ".monoco" / "actions"
70
91
  self._scan_dir(project_dir)
71
-
92
+
72
93
  return self._actions
73
94
 
74
95
  def _scan_dir(self, directory: Path):
@@ -85,28 +106,29 @@ class ActionRegistry:
85
106
 
86
107
  def _load_action(self, file_path: Path) -> Optional[PromptyAction]:
87
108
  content = file_path.read_text(encoding="utf-8")
88
-
109
+
89
110
  # Prompty Parser (Standard YAML Frontmatter + Body)
90
111
  # We look for the first --- and the second ---
91
112
  parts = re.split(r"^---\s*$", content, maxsplit=2, flags=re.MULTILINE)
92
-
113
+
93
114
  if len(parts) < 3:
94
115
  return None
95
116
 
96
117
  frontmatter_raw = parts[1]
97
118
  body = parts[2].strip()
98
-
119
+
99
120
  try:
100
121
  meta = yaml.safe_load(frontmatter_raw)
101
122
  if not meta or "name" not in meta:
102
123
  # Use filename as fallback name if missing? Prompty usually requires name.
103
- if not meta: meta = {}
124
+ if not meta:
125
+ meta = {}
104
126
  meta["name"] = meta.get("name", file_path.stem)
105
127
 
106
128
  # Map Prompty 'when' if present
107
129
  when_data = meta.get("when")
108
130
  when = ActionWhen(**when_data) if when_data else None
109
-
131
+
110
132
  action = PromptyAction(
111
133
  name=meta["name"],
112
134
  description=meta.get("description", ""),
@@ -118,21 +140,23 @@ class ActionRegistry:
118
140
  template=body,
119
141
  when=when,
120
142
  path=str(file_path.absolute()),
121
- provider=meta.get("provider") or meta.get("model", {}).get("api")
143
+ provider=meta.get("provider") or meta.get("model", {}).get("api"),
122
144
  )
123
145
  return action
124
-
146
+
125
147
  except Exception as e:
126
148
  print(f"Invalid Prompty in {file_path}: {e}")
127
149
  return None
128
150
 
129
- def list_available(self, context: Optional[ActionContext] = None) -> List[PromptyAction]:
151
+ def list_available(
152
+ self, context: Optional[ActionContext] = None
153
+ ) -> List[PromptyAction]:
130
154
  if not self._actions:
131
155
  self.scan()
132
-
156
+
133
157
  if not context:
134
158
  return self._actions
135
-
159
+
136
160
  return [a for a in self._actions if not a.when or a.when.matches(context)]
137
161
 
138
162
  def get(self, name: str) -> Optional[PromptyAction]:
@@ -3,11 +3,11 @@ CLI Adapters for Agent Frameworks.
3
3
  """
4
4
 
5
5
  import shutil
6
- import subprocess
7
6
  from typing import List
8
7
  from pathlib import Path
9
8
  from .protocol import AgentClient
10
9
 
10
+
11
11
  class BaseCLIClient:
12
12
  def __init__(self, executable: str):
13
13
  self._executable = executable
@@ -24,10 +24,11 @@ class BaseCLIClient:
24
24
  # Inject Language Rule
25
25
  try:
26
26
  from monoco.core.config import get_config
27
+
27
28
  settings = get_config()
28
29
  lang = settings.i18n.source_lang
29
30
  if lang:
30
- 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."
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."
31
32
  except Exception:
32
33
  pass
33
34
 
@@ -39,7 +40,9 @@ class BaseCLIClient:
39
40
  full_prompt.append(f"\nFile: {file_path}")
40
41
  full_prompt.append("```")
41
42
  # Read file content safely
42
- full_prompt.append(file_path.read_text(encoding="utf-8", errors="replace"))
43
+ full_prompt.append(
44
+ file_path.read_text(encoding="utf-8", errors="replace")
45
+ )
43
46
  full_prompt.append("```")
44
47
  except Exception as e:
45
48
  full_prompt.append(f"Error reading {file_path}: {e}")
@@ -51,25 +54,25 @@ class BaseCLIClient:
51
54
  # Using synchronous subprocess in async function for now
52
55
  # Ideally this should use asyncio.create_subprocess_exec
53
56
  import asyncio
54
-
57
+
55
58
  proc = await asyncio.create_subprocess_exec(
56
- *args,
57
- stdout=asyncio.subprocess.PIPE,
58
- stderr=asyncio.subprocess.PIPE
59
+ *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
59
60
  )
60
-
61
+
61
62
  stdout, stderr = await proc.communicate()
62
-
63
+
63
64
  if proc.returncode != 0:
64
65
  error_msg = stderr.decode().strip()
65
- raise RuntimeError(f"Agent CLI failed (code {proc.returncode}): {error_msg}")
66
-
66
+ raise RuntimeError(
67
+ f"Agent CLI failed (code {proc.returncode}): {error_msg}"
68
+ )
69
+
67
70
  return stdout.decode().strip()
68
71
 
69
72
 
70
73
  class GeminiClient(BaseCLIClient, AgentClient):
71
74
  """Adapter for Google Gemini CLI."""
72
-
75
+
73
76
  def __init__(self):
74
77
  super().__init__("gemini")
75
78
 
@@ -81,7 +84,7 @@ class GeminiClient(BaseCLIClient, AgentClient):
81
84
 
82
85
  class ClaudeClient(BaseCLIClient, AgentClient):
83
86
  """Adapter for Anthropic Claude CLI."""
84
-
87
+
85
88
  def __init__(self):
86
89
  super().__init__("claude")
87
90
 
@@ -93,7 +96,7 @@ class ClaudeClient(BaseCLIClient, AgentClient):
93
96
 
94
97
  class QwenClient(BaseCLIClient, AgentClient):
95
98
  """Adapter for Alibaba Qwen CLI."""
96
-
99
+
97
100
  def __init__(self):
98
101
  super().__init__("qwen")
99
102
 
@@ -105,7 +108,7 @@ class QwenClient(BaseCLIClient, AgentClient):
105
108
 
106
109
  class KimiClient(BaseCLIClient, AgentClient):
107
110
  """Adapter for Moonshot Kimi CLI."""
108
-
111
+
109
112
  def __init__(self):
110
113
  super().__init__("kimi")
111
114
 
@@ -119,9 +122,10 @@ _ADAPTERS = {
119
122
  "gemini": GeminiClient,
120
123
  "claude": ClaudeClient,
121
124
  "qwen": QwenClient,
122
- "kimi": KimiClient
125
+ "kimi": KimiClient,
123
126
  }
124
127
 
128
+
125
129
  def get_agent_client(name: str) -> AgentClient:
126
130
  """Factory to get agent client by name."""
127
131
  if name not in _ADAPTERS:
@@ -2,12 +2,13 @@
2
2
  Protocol definition for Agent Clients.
3
3
  """
4
4
 
5
- from typing import Protocol, List, Optional
5
+ from typing import Protocol, List
6
6
  from pathlib import Path
7
7
 
8
+
8
9
  class AgentClient(Protocol):
9
10
  """Protocol for interacting with CLI-based agents."""
10
-
11
+
11
12
  @property
12
13
  def name(self) -> str:
13
14
  """Name of the agent provider (e.g. 'gemini', 'claude')."""
@@ -20,11 +21,11 @@ class AgentClient(Protocol):
20
21
  async def execute(self, prompt: str, context_files: List[Path] = []) -> str:
21
22
  """
22
23
  Execute a prompt against the agent.
23
-
24
+
24
25
  Args:
25
26
  prompt: The main instructions.
26
27
  context_files: List of files to provide as context.
27
-
28
+
28
29
  Returns:
29
30
  The raw string response from the agent.
30
31
  """
@@ -5,22 +5,22 @@ Handles persistence and retrieval of agent availability state.
5
5
  """
6
6
 
7
7
  import yaml
8
- import shutil
9
- import subprocess
10
8
  import logging
11
9
  from pathlib import Path
12
10
  from datetime import datetime, timezone
13
11
  from typing import Dict, Optional
14
- from pydantic import BaseModel, Field
12
+ from pydantic import BaseModel
15
13
 
16
14
  logger = logging.getLogger("monoco.core.agent.state")
17
15
 
16
+
18
17
  class AgentProviderState(BaseModel):
19
18
  available: bool
20
19
  path: Optional[str] = None
21
20
  error: Optional[str] = None
22
21
  latency_ms: Optional[int] = None
23
22
 
23
+
24
24
  class AgentState(BaseModel):
25
25
  last_checked: datetime
26
26
  providers: Dict[str, AgentProviderState]
@@ -31,6 +31,7 @@ class AgentState(BaseModel):
31
31
  delta = datetime.now(timezone.utc) - self.last_checked
32
32
  return delta.days > 7
33
33
 
34
+
34
35
  class AgentStateManager:
35
36
  def __init__(self, state_path: Path = Path.home() / ".monoco" / "agent_state.yaml"):
36
37
  self.state_path = state_path
@@ -40,7 +41,7 @@ class AgentStateManager:
40
41
  """Load state from file, returning None if missing or invalid."""
41
42
  if not self.state_path.exists():
42
43
  return None
43
-
44
+
44
45
  try:
45
46
  with open(self.state_path, "r") as f:
46
47
  data = yaml.safe_load(f)
@@ -58,46 +59,45 @@ class AgentStateManager:
58
59
  self._state = self.load()
59
60
  if self._state and not self._state.is_stale:
60
61
  return self._state
61
-
62
+
62
63
  return self.refresh()
63
64
 
64
65
  def refresh(self) -> AgentState:
65
66
  """Run diagnostics on all integrations and update state."""
66
67
  logger.info("Refreshing agent state...")
67
-
68
+
68
69
  from monoco.core.integrations import get_all_integrations
69
70
  from monoco.core.config import get_config
70
-
71
+
71
72
  # Load config to get possible overrides
72
73
  # Determine root (hacky for now, should be passed)
73
74
  root = Path.cwd()
74
75
  config = get_config(str(root))
75
-
76
- integrations = get_all_integrations(config_overrides=config.agent.integrations, enabled_only=True)
77
-
76
+
77
+ integrations = get_all_integrations(
78
+ config_overrides=config.agent.integrations, enabled_only=True
79
+ )
80
+
78
81
  providers = {}
79
82
  for key, integration in integrations.items():
80
83
  if not integration.bin_name:
81
- continue # Skip integrations that don't have a binary component
82
-
84
+ continue # Skip integrations that don't have a binary component
85
+
83
86
  health = integration.check_health()
84
87
  providers[key] = AgentProviderState(
85
88
  available=health.available,
86
89
  path=health.path,
87
90
  error=health.error,
88
- latency_ms=health.latency_ms
91
+ latency_ms=health.latency_ms,
89
92
  )
90
-
91
- state = AgentState(
92
- last_checked=datetime.now(timezone.utc),
93
- providers=providers
94
- )
95
-
93
+
94
+ state = AgentState(last_checked=datetime.now(timezone.utc), providers=providers)
95
+
96
96
  # Save state
97
97
  self.state_path.parent.mkdir(parents=True, exist_ok=True)
98
98
  with open(self.state_path, "w") as f:
99
- yaml.dump(state.model_dump(mode='json'), f)
100
-
99
+ yaml.dump(state.model_dump(mode="json"), f)
100
+
101
101
  self._state = state
102
102
  return state
103
103