feat: the host agent is always on; the MCP sees and starts host maintenance (#5834)
#5834 "The host agent is always on, and the MCP sees a host's maintenance", step 1 of milestone 520 "The MCP sees what Steward has grown". The host agent is a core system (operator, 2026-10-15: "it's very core to the functionality of Steward as a whole and shouldn't be considered something that can be toggled off"). ALWAYS_ON_PLUGINS in core settings makes to_plugins_cfg report it enabled whatever its stored row says, so it always loads and its MCP tools are always listed. Settings -> Plugins shows it as "Built in: always on" with no switch, and a save keeps it enabled. Its code and URLs stay under plugins/host_agent: every installed agent posts to /plugins/host_agent/ingest. Maintenance through the MCP: - list_maintenance_plans: each plan's group, weekly slot, reboot policy, next slot, and its latest run (hosts, how far it got, who it skipped and why, how it ended). - get_maintenance_job(host | job): a host's jobs, newest and running first, or one job: state, packages updated, reboot, drained by, why it failed, when its current state times out. - run_maintenance(host, reboot="if_required" | "never"): admin, as the host page's Update now; refuses a busy host or one with no helper; audited "<user> via MCP". - The overview names a host whose latest job in the last week failed or timed out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ from steward.core.settings import (
|
||||
get_stored_plugin_names, find_orphaned_plugins,
|
||||
to_smtp_cfg, to_webhook_cfg, to_ansible_cfg, to_plugins_cfg,
|
||||
to_oidc_cfg, to_ldap_cfg, to_thresholds_cfg, to_access_cfg, DEFAULTS, seal_source_tokens,
|
||||
is_plugin_secret, NORM_FLOORS,
|
||||
is_plugin_secret, NORM_FLOORS, ALWAYS_ON_PLUGINS,
|
||||
)
|
||||
|
||||
settings_bp = Blueprint("settings", __name__, url_prefix="/settings")
|
||||
@@ -58,6 +58,7 @@ def _merge_plugin_config(discovered: list[dict], plugins_cfg: dict) -> None:
|
||||
name = plugin["_dir"]
|
||||
stored = plugins_cfg.get(name, {})
|
||||
plugin["_enabled"] = stored.get("enabled", False)
|
||||
plugin["_always_on"] = name in ALWAYS_ON_PLUGINS
|
||||
defaults = plugin.get("config", {})
|
||||
plugin["_config"] = {**defaults, **{k: v for k, v in stored.items() if k != "enabled"}}
|
||||
|
||||
@@ -930,7 +931,7 @@ async def start_restore():
|
||||
def _build_plugin_cfg_from_form(plugin: dict, form) -> dict:
|
||||
"""Extract config values for one plugin from a form submission."""
|
||||
name = plugin["_dir"]
|
||||
enabled = f"plugin.{name}.enabled" in form
|
||||
enabled = name in ALWAYS_ON_PLUGINS or f"plugin.{name}.enabled" in form
|
||||
plugin_cfg: dict = {"enabled": enabled}
|
||||
for cfg_key in plugin.get("config", {}).keys():
|
||||
field_name = f"plugin.{name}.{cfg_key}"
|
||||
|
||||
Reference in New Issue
Block a user