diff --git a/components/runners/ambient-runner/ambient_runner/bridges/claude/bridge.py b/components/runners/ambient-runner/ambient_runner/bridges/claude/bridge.py index f9e727591..db9d9453f 100644 --- a/components/runners/ambient-runner/ambient_runner/bridges/claude/bridge.py +++ b/components/runners/ambient-runner/ambient_runner/bridges/claude/bridge.py @@ -55,6 +55,7 @@ def __init__(self) -> None: self._mcp_servers: dict = {} self._allowed_tools: list[str] = [] self._system_prompt: dict = {} + self._plugins: list[dict[str, str]] = [] self._stderr_lines: list[str] = [] # Preserved session IDs across adapter rebuilds (e.g. repo additions) self._saved_session_ids: dict[str, str] = {} @@ -364,6 +365,28 @@ async def _setup_platform(self) -> None: system_prompt = build_sdk_system_prompt(self._context.workspace_path, cwd_path) + # Discover plugins cloned by init container + plugins_json = os.getenv("PLUGINS_JSON", "").strip() + plugins: list[dict[str, str]] = [] + if plugins_json: + import json as _json + + try: + for entry in _json.loads(plugins_json): + name = entry.get("url", "").rstrip("/").split("/")[-1] + if name.endswith(".git"): + name = name[:-4] + plugin_path = os.path.join( + self._context.workspace_path, "plugins", name + ) + if os.path.isdir(plugin_path): + plugins.append({"type": "local", "path": plugin_path}) + logger.info(f"Plugin discovered: {name} at {plugin_path}") + else: + logger.warning(f"Plugin dir not found: {plugin_path}") + except Exception as exc: + logger.warning(f"Failed to parse PLUGINS_JSON: {exc}") + # Store results self._configured_model = configured_model self._cwd_path = cwd_path @@ -371,6 +394,7 @@ async def _setup_platform(self) -> None: self._mcp_servers = mcp_servers self._allowed_tools = allowed_tools self._system_prompt = system_prompt + self._plugins = plugins # ------------------------------------------------------------------ # Private: adapter lifecycle @@ -405,6 +429,8 @@ def _stderr_handler(line: str) -> None: options["add_dirs"] = self._add_dirs if self._configured_model: options["model"] = self._configured_model + if self._plugins: + options["plugins"] = self._plugins adapter = ClaudeAgentAdapter( name="claude_code_runner", diff --git a/components/runners/state-sync/hydrate.sh b/components/runners/state-sync/hydrate.sh index 82b8d1b6c..0b700659e 100644 --- a/components/runners/state-sync/hydrate.sh +++ b/components/runners/state-sync/hydrate.sh @@ -234,6 +234,46 @@ else echo "No repositories configured in spec" fi +# Clone plugins from PLUGINS_JSON +if [ -n "$PLUGINS_JSON" ] && [ "$PLUGINS_JSON" != "null" ] && [ "$PLUGINS_JSON" != "" ]; then + echo "Cloning plugins from spec..." + PLUGIN_COUNT=$(echo "$PLUGINS_JSON" | jq -e 'if type == "array" then length else 0 end' 2>/dev/null || echo "0") + echo "Found $PLUGIN_COUNT plugins to clone" + if [ "$PLUGIN_COUNT" -gt 0 ]; then + mkdir -p /workspace/plugins + i=0 + while [ $i -lt $PLUGIN_COUNT ]; do + PLUGIN_URL=$(echo "$PLUGINS_JSON" | jq -r ".[$i].url // empty" 2>/dev/null || echo "") + PLUGIN_BRANCH=$(echo "$PLUGINS_JSON" | jq -r ".[$i].branch // empty" 2>/dev/null || echo "") + PLUGIN_NAME=$(basename "$PLUGIN_URL" .git 2>/dev/null || echo "") + + if [ -n "$PLUGIN_NAME" ] && [ -n "$PLUGIN_URL" ] && [ "$PLUGIN_URL" != "null" ]; then + PLUGIN_DIR="/workspace/plugins/$PLUGIN_NAME" + git config --global --add safe.directory "$PLUGIN_DIR" 2>/dev/null || true + + if [ -n "$PLUGIN_BRANCH" ]; then + echo " Cloning plugin $PLUGIN_NAME (branch: $PLUGIN_BRANCH)..." + if git clone --branch "$PLUGIN_BRANCH" --single-branch --depth 1 "$PLUGIN_URL" "$PLUGIN_DIR" 2>&1; then + echo " ✓ Cloned plugin $PLUGIN_NAME" + else + echo " ⚠ Failed to clone plugin $PLUGIN_NAME" + fi + else + echo " Cloning plugin $PLUGIN_NAME (default branch)..." + if git clone --single-branch --depth 1 "$PLUGIN_URL" "$PLUGIN_DIR" 2>&1; then + echo " ✓ Cloned plugin $PLUGIN_NAME" + else + echo " ⚠ Failed to clone plugin $PLUGIN_NAME" + fi + fi + fi + i=$((i + 1)) + done + fi +else + echo "No plugins configured in spec" +fi + # Clone workflow repository if [ -n "$ACTIVE_WORKFLOW_GIT_URL" ] && [ "$ACTIVE_WORKFLOW_GIT_URL" != "null" ]; then WORKFLOW_BRANCH="${ACTIVE_WORKFLOW_BRANCH:-main}"