add new agent components
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
MAX_PLUGIN_LINES = 12
|
||||
MAX_PLUGIN_PREVIEW_CHARS = 4000
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PluginCacheEntry:
|
||||
name: str
|
||||
enabled: bool = True
|
||||
version: str | None = None
|
||||
source: str | None = None
|
||||
|
||||
|
||||
def load_plugin_cache_summary(
|
||||
cwd: Path,
|
||||
additional_working_directories: tuple[str, ...] = (),
|
||||
) -> str | None:
|
||||
snapshot = discover_plugin_cache(cwd, additional_working_directories)
|
||||
if snapshot is None:
|
||||
return None
|
||||
return snapshot
|
||||
|
||||
|
||||
def discover_plugin_cache(
|
||||
cwd: Path,
|
||||
additional_working_directories: tuple[str, ...] = (),
|
||||
) -> str | None:
|
||||
for path in _discover_candidate_paths(cwd, additional_working_directories):
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding='utf-8'))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
continue
|
||||
entries = _extract_entries(payload)
|
||||
if not entries:
|
||||
continue
|
||||
lines = [
|
||||
f'Plugin cache loaded from: {path}',
|
||||
f'Plugin entries discovered: {len(entries)}',
|
||||
]
|
||||
enabled = [entry for entry in entries if entry.enabled]
|
||||
disabled = [entry for entry in entries if not entry.enabled]
|
||||
lines.append(f'Enabled plugins: {len(enabled)}')
|
||||
if disabled:
|
||||
lines.append(f'Disabled plugins: {len(disabled)}')
|
||||
for entry in entries[:MAX_PLUGIN_LINES]:
|
||||
details = [entry.name]
|
||||
if entry.version:
|
||||
details.append(f'version={entry.version}')
|
||||
if entry.source:
|
||||
details.append(f'source={entry.source}')
|
||||
if not entry.enabled:
|
||||
details.append('disabled')
|
||||
lines.append(f"- {'; '.join(details)}")
|
||||
if len(entries) > MAX_PLUGIN_LINES:
|
||||
lines.append(f'- ... plus {len(entries) - MAX_PLUGIN_LINES} more plugin entries')
|
||||
rendered = '\n'.join(lines)
|
||||
if len(rendered) > MAX_PLUGIN_PREVIEW_CHARS:
|
||||
rendered = rendered[: MAX_PLUGIN_PREVIEW_CHARS - 3] + '...'
|
||||
return rendered
|
||||
return None
|
||||
|
||||
|
||||
def _discover_candidate_paths(
|
||||
cwd: Path,
|
||||
additional_working_directories: tuple[str, ...],
|
||||
) -> list[Path]:
|
||||
candidates: list[Path] = []
|
||||
seen: set[Path] = set()
|
||||
relative_paths = (
|
||||
'.port_sessions/plugin_cache.json',
|
||||
'.port_sessions/plugins.json',
|
||||
'.claude/plugins/cache.json',
|
||||
'.claw/plugins/cache.json',
|
||||
'plugins/cache.json',
|
||||
'.plugins/cache.json',
|
||||
)
|
||||
|
||||
def remember(path: Path) -> None:
|
||||
resolved = path.resolve()
|
||||
if resolved in seen or not resolved.exists() or not resolved.is_file():
|
||||
return
|
||||
seen.add(resolved)
|
||||
candidates.append(resolved)
|
||||
|
||||
for root in _walk_upwards(cwd.resolve()):
|
||||
for relative in relative_paths:
|
||||
remember(root / relative)
|
||||
|
||||
for raw_path in additional_working_directories:
|
||||
directory = Path(raw_path).resolve()
|
||||
for relative in relative_paths:
|
||||
remember(directory / relative)
|
||||
|
||||
return candidates
|
||||
|
||||
|
||||
def _walk_upwards(path: Path) -> list[Path]:
|
||||
current = path
|
||||
walked: list[Path] = []
|
||||
while True:
|
||||
walked.append(current)
|
||||
if current.parent == current:
|
||||
break
|
||||
current = current.parent
|
||||
return walked
|
||||
|
||||
|
||||
def _extract_entries(payload: Any) -> list[PluginCacheEntry]:
|
||||
entries: list[PluginCacheEntry] = []
|
||||
raw_entries: list[Any] = []
|
||||
if isinstance(payload, list):
|
||||
raw_entries = payload
|
||||
elif isinstance(payload, dict):
|
||||
if isinstance(payload.get('plugins'), list):
|
||||
raw_entries = payload['plugins']
|
||||
elif isinstance(payload.get('entries'), list):
|
||||
raw_entries = payload['entries']
|
||||
else:
|
||||
raw_entries = [
|
||||
{'name': key, **value}
|
||||
for key, value in payload.items()
|
||||
if isinstance(value, dict)
|
||||
]
|
||||
|
||||
for item in raw_entries:
|
||||
entry = _coerce_entry(item)
|
||||
if entry is not None:
|
||||
entries.append(entry)
|
||||
return entries
|
||||
|
||||
|
||||
def _coerce_entry(item: Any) -> PluginCacheEntry | None:
|
||||
if isinstance(item, str) and item.strip():
|
||||
return PluginCacheEntry(name=item.strip())
|
||||
if not isinstance(item, dict):
|
||||
return None
|
||||
name = item.get('name') or item.get('plugin') or item.get('id')
|
||||
if not isinstance(name, str) or not name.strip():
|
||||
return None
|
||||
source = item.get('source') or item.get('path') or item.get('module')
|
||||
version = item.get('version')
|
||||
enabled = item.get('enabled')
|
||||
return PluginCacheEntry(
|
||||
name=name.strip(),
|
||||
enabled=True if enabled is None else bool(enabled),
|
||||
version=version if isinstance(version, str) and version else None,
|
||||
source=source if isinstance(source, str) and source else None,
|
||||
)
|
||||
Reference in New Issue
Block a user