| 1 | # plugins.py DOX |
| 2 | |
| 3 | ## Purpose |
| 4 | |
| 5 | - Own the `plugins.py` helper module. |
| 6 | - This module discovers plugins, resolves plugin config/toggles, runs hooks, and tracks plugin updates. |
| 7 | - Keep this file-level DOX profile synchronized with `plugins.py` because this directory is intentionally flat. |
| 8 | |
| 9 | ## Ownership |
| 10 | |
| 11 | - `plugins.py` owns the runtime implementation. |
| 12 | - `plugins.py.dox.md` owns durable notes about responsibilities, contracts, side effects, and verification for that implementation. |
| 13 | - Classes: |
| 14 | - `PluginAssetFile` (`TypedDict`) |
| 15 | - `PluginMetadata` (`BaseModel`) |
| 16 | - `PluginListItem` (`BaseModel`) |
| 17 | - `PluginUpdateInfo` (`BaseModel`) |
| 18 | - Top-level functions: |
| 19 | - `register_watchdogs()` |
| 20 | - `after_plugin_change(plugin_names: list[str] | None=..., python_change: bool=..., frontend_reload: bool=...)` |
| 21 | - `refresh_plugin_modules(plugin_names: list[str] | None=...)` |
| 22 | - `clear_plugin_cache(plugin_names: list[str] | None=...)` |
| 23 | - `get_plugin_roots(plugin_name: str=...) -> List[str]`: Plugin root directories, ordered by priority (user first). |
| 24 | - `get_plugin_name_from_path(path: str | Path) -> str`: Return the plugin directory name only for paths below a canonical user or bundled plugin root. |
| 25 | - `get_plugins_list()` |
| 26 | - `get_enhanced_plugins_list(custom: bool=..., builtin: bool=..., plugin_names: list[str] | None=...) -> List[PluginListItem]`: Discover plugins by directory convention. First root wins on ID conflict. |
| 27 | - `get_custom_plugins_updates(plugin_names: list[str] | None=...) -> List[PluginUpdateInfo]` |
| 28 | - `get_plugin_meta(plugin_name: str)` |
| 29 | - `find_plugin_dir(plugin_name: str)` |
| 30 | - `uninstall_plugin(plugin_name)` |
| 31 | - `delete_plugin(plugin_name: str)` |
| 32 | - `get_plugin_paths(*subpaths) -> List[str]` |
| 33 | - `get_enabled_plugin_paths(agent: Agent | None, *subpaths) -> List[str]` |
| 34 | - `get_enabled_plugins(agent: Agent | None)` |
| 35 | - `determined_toggle_from_paths(default: bool, paths: Iterator[str])` |
| 36 | - `get_toggle_state(plugin_name: str) -> ToggleState` |
| 37 | - `toggle_plugin(plugin_name: str, enabled: bool, project_name: str=..., agent_profile: str=..., clear_overrides: bool=...)` |
| 38 | - `get_plugin_config(plugin_name: str, agent: Agent | None=..., project_name: str | None=..., agent_profile: str | None=..., caller: CallerContext=...)` |
| 39 | - `get_default_plugin_config(plugin_name: str)` |
| 40 | - `save_plugin_config(plugin_name: str, project_name: str, agent_profile: str, settings: dict, caller: CallerContext=...)` |
| 41 | - `find_plugin_asset(plugin_name: str, *subpaths, project_name=..., agent_profile=...)` |
| 42 | - `find_plugin_assets(*subpaths, plugin_name: str=..., project_name: str=..., agent_profile: str=..., only_first: bool=...) -> list[PluginAssetFile]` |
| 43 | - `determine_plugin_asset_path(plugin_name: str, project_name: str, agent_profile: str, *subpaths)` |
| 44 | - `send_frontend_reload_notification(plugin_names: list[str] | None=...)`: If the plugin changed has webui extensions, show a persistent reload notification that marks itself read before refreshing |
| 45 | - `call_plugin_hook(plugin_name: str, hook_name: str, default: Any=..., *args, **kwargs)` |
| 46 | - `_apply_defaults_from_env(plugin_name: str, config: dict[str, Any])` |
| 47 | - Notable constants/configuration names: `_META_TARGET_RE`, `META_FILE_NAME`, `CONFIG_FILE_NAME`, `CONFIG_DEFAULT_FILE_NAME`, `DISABLED_FILE_NAME`, `ENABLED_FILE_NAME`, `TOGGLE_FILE_PATTERN`, `HOOKS_SCRIPT`, `HOOKS_CACHE_AREA`, `PLUGINS_LIST_CACHE_AREA`, `ENABLED_PLUGINS_LIST_CACHE_AREA`, `ENABLED_PLUGINS_PATHS_CACHE_AREA`. |
| 48 | |
| 49 | ## Runtime Contracts |
| 50 | |
| 51 | - Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together. |
| 52 | - Plugins marked `always_enabled` remain in runtime discovery regardless of |
| 53 | stale global or scoped disable files, and disable attempts are rejected. |
| 54 | - Config hooks receive `hook_context={"caller": caller}` with one of `ui`, |
| 55 | `agent`, or `api`; this is behavioral context, not an authorization boundary. |
| 56 | - Project- and agent-scoped plugin changes invalidate runtime caches without a |
| 57 | frontend reload prompt because the loaded WebUI extension bundle is global. |
| 58 | - Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change. |
| 59 | - Observed side-effect areas: filesystem reads, filesystem writes, filesystem deletion, WebSocket state, plugin state, settings/state persistence, secret handling. |
| 60 | - Imported dependency areas include: `__future__`, `asyncio`, `glob`, `helpers`, `helpers.defer`, `helpers.watchdog`, `json`, `pathlib`, `pydantic`, `re`, `regex`, `time`, `typing`. |
| 61 | |
| 62 | ## Key Concepts |
| 63 | |
| 64 | - Important called helpers/classes observed in the source: `re.compile`, `Field`, `watchdog.add_watchdog`, `clear_plugin_cache`, `send_frontend_reload_notification`, `DeferredTask.start_task`, `get_plugin_roots`, `get_plugin_name_from_path`, `result.sort`, `cache.add`, `get_enhanced_plugins_list`, `find_plugin_dir`, `files.get_abs_path`, `files.exists`, `call_plugin_hook`, `delete_plugin`, `files.delete_dir`, `after_plugin_change`, `get_enabled_plugins`, `get_plugins_list`, `get_plugin_meta`. |
| 65 | - Keep request/response, tool, or helper semantics documented here at the same time as source changes. |
| 66 | |
| 67 | ## Work Guidance |
| 68 | |
| 69 | - Preserve public helper APIs used by core code and plugins unless every caller is updated. |
| 70 | - Keep path, auth, secret, persistence, network, and subprocess behavior explicit and bounded. |
| 71 | - Prefer adding cohesive helper functions here only when behavior is reused across modules. |
| 72 | |
| 73 | ## Verification |
| 74 | |
| 75 | - Run targeted tests for changed helper behavior; run security regressions for auth, filesystem, WebSocket, tunnel, upload, or secret-handling helpers. |
| 76 | - Related tests observed by source search: |
| 77 | - `tests/test_a0_connector_computer_use_metadata.py` |
| 78 | - `tests/test_a0_connector_prompt_gating.py` |
| 79 | - `tests/test_browser_agent_regressions.py` |
| 80 | - `tests/test_chat_compaction.py` |
| 81 | - `tests/test_default_prompt_budget.py` |
| 82 | - `tests/test_document_query_plugin.py` |
| 83 | - `tests/test_error_retry_plugin.py` |
| 84 | - `tests/test_host_browser_connector.py` |
| 85 | - `tests/test_tool_policy.py` |
| 86 | |
| 87 | ## Child DOX Index |
| 88 | |
| 89 | No child DOX files. |