Renames: - platform/ → workspace-server/ (Go module path stays as "platform" for external dep compat — will update after plugin module republish) - workspace-template/ → workspace/ Removed (moved to separate repos or deleted): - PLAN.md — internal roadmap (move to private project board) - HANDOFF.md, AGENTS.md — one-time internal session docs - .claude/ — gitignored entirely (local agent config) - infra/cloudflare-worker/ → Molecule-AI/molecule-tenant-proxy - org-templates/molecule-dev/ → standalone template repo - .mcp-eval/ → molecule-mcp-server repo - test-results/ — ephemeral, gitignored Security scrubbing: - Cloudflare account/zone/KV IDs → placeholders - Real EC2 IPs → <EC2_IP> in all docs - CF token prefix, Neon project ID, Fly app names → redacted - Langfuse dev credentials → parameterized - Personal runner username/machine name → generic Community files: - CONTRIBUTING.md — build, test, branch conventions - CODE_OF_CONDUCT.md — Contributor Covenant 2.1 All Dockerfiles, CI workflows, docker-compose, railway.toml, render.yaml, README, CLAUDE.md updated for new directory names. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
72 lines
2.5 KiB
Python
72 lines
2.5 KiB
Python
"""Fallback adaptor used when no per-runtime adaptor is found.
|
|
|
|
Behaviour: copy the plugin's content into ``/configs/plugins/<name>/`` so a
|
|
user can still inspect or hand-wire it, then surface a warning that no tools
|
|
or sub-agents were registered.
|
|
|
|
This preserves the "power users can drop raw files" escape hatch without
|
|
silently breaking — the warning is propagated up via :class:`InstallResult`
|
|
so the API can surface it to the user.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
|
|
from .protocol import InstallContext, InstallResult, PluginAdaptor
|
|
|
|
|
|
class RawDropAdaptor:
|
|
"""Filesystem-only fallback. Implements :class:`PluginAdaptor`."""
|
|
|
|
def __init__(self, plugin_name: str, runtime: str) -> None:
|
|
self.plugin_name = plugin_name
|
|
self.runtime = runtime
|
|
|
|
async def install(self, ctx: InstallContext) -> InstallResult:
|
|
dst = ctx.configs_dir / "plugins" / self.plugin_name
|
|
files_written: list[str] = []
|
|
|
|
if ctx.plugin_root.exists() and ctx.plugin_root.is_dir():
|
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
if dst.exists():
|
|
# Idempotent — leave existing copy alone.
|
|
ctx.logger.info(
|
|
"raw_drop: %s already present at %s, skipping copy",
|
|
self.plugin_name, dst,
|
|
)
|
|
else:
|
|
shutil.copytree(ctx.plugin_root, dst)
|
|
for p in dst.rglob("*"):
|
|
if p.is_file():
|
|
files_written.append(str(p.relative_to(ctx.configs_dir)))
|
|
ctx.logger.info(
|
|
"raw_drop: copied %s → %s (%d files)",
|
|
self.plugin_name, dst, len(files_written),
|
|
)
|
|
|
|
warning = (
|
|
f"plugin '{self.plugin_name}' has no adaptor for runtime "
|
|
f"'{self.runtime}' — files dropped at /configs/plugins/{self.plugin_name} "
|
|
f"but no tools/sub-agents were wired in"
|
|
)
|
|
ctx.logger.warning(warning)
|
|
|
|
return InstallResult(
|
|
plugin_name=self.plugin_name,
|
|
runtime=self.runtime,
|
|
source="raw_drop",
|
|
files_written=files_written,
|
|
warnings=[warning],
|
|
)
|
|
|
|
async def uninstall(self, ctx: InstallContext) -> None:
|
|
dst = ctx.configs_dir / "plugins" / self.plugin_name
|
|
if dst.exists():
|
|
shutil.rmtree(dst)
|
|
ctx.logger.info("raw_drop: removed %s", dst)
|
|
|
|
|
|
# Static check: RawDropAdaptor satisfies PluginAdaptor.
|
|
_: PluginAdaptor = RawDropAdaptor("_", "_")
|