diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-10 23:06:01 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-10 23:06:01 +0300 |
| commit | f4d04fab26d8b8733868b8e394439b69ed83df75 (patch) | |
| tree | 1657f7da96f4cc82a23b576d691887589c372013 | |
| parent | 1fc5e84d461ff7c3cfcc0ed134df0939665fc937 (diff) | |
feat: add agent manifest update command
| -rw-r--r-- | docs/next/CHANGELOG.md | 1 | ||||
| -rw-r--r-- | docs/next/website/src/content/docs/agents.mdx | 2 | ||||
| -rw-r--r-- | docs/next/website/src/content/docs/cli-reference.mdx | 3 | ||||
| -rw-r--r-- | src/cli/server.rs | 143 |
4 files changed, 147 insertions, 2 deletions
diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 8df18e15..8a5c4a23 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -17,6 +17,7 @@ ### Added - Added remote auto-updates for agent detection manifests, with per-agent validation, local override precedence, `herdr server agent-manifests` diagnostics, and explain output showing remote manifest status. +- Added `herdr server update-agent-manifests` to fetch remote agent detection manifests immediately, reload the running server, and print the updated manifest status. - Added `herdr agent explain` to show the manifest source, matched rule, evaluated matcher and region evidence, visible evidence flags, skipped-update reason, and idle fallback reason for live panes or saved screen fixtures. - Added `herdr integration install droid` for Factory Droid hooks that report session ids through Herdr's socket API. When native agent session restore is enabled, Herdr can resume Droid panes with `droid --resume <id>`. - Added `herdr integration install kilo` for Kilo Code CLI plugins that report lifecycle state and session ids through Herdr's socket API. When native agent session restore is enabled, Herdr can resume Kilo panes with `kilo --session <id>`. diff --git a/docs/next/website/src/content/docs/agents.mdx b/docs/next/website/src/content/docs/agents.mdx index e6319704..49f64881 100644 --- a/docs/next/website/src/content/docs/agents.mdx +++ b/docs/next/website/src/content/docs/agents.mdx @@ -62,7 +62,7 @@ Local overrides always win. Without a local override, Herdr uses the newer compa Remote manifests patch detection rules for agents Herdr already knows how to identify. Adding a completely new agent still requires a Herdr binary update for process detection, labels, and integration behavior. -The running server loads active manifests into memory on startup. Automatic remote manifest updates reload that in-memory cache after new rules are written. After editing a local override manually, restart Herdr or run `herdr server reload-agent-manifests` to apply the file to the running server. +The running server loads active manifests into memory on startup. Automatic remote manifest updates reload that in-memory cache after new rules are written. Run `herdr server update-agent-manifests` to fetch remote manifest updates immediately and reload the running server. After editing a local override manually, restart Herdr or run `herdr server reload-agent-manifests` to apply the file to the running server. Use `herdr agent explain` when a pane shows the wrong state: diff --git a/docs/next/website/src/content/docs/cli-reference.mdx b/docs/next/website/src/content/docs/cli-reference.mdx index 5ed9ce8f..32baa746 100644 --- a/docs/next/website/src/content/docs/cli-reference.mdx +++ b/docs/next/website/src/content/docs/cli-reference.mdx @@ -40,10 +40,11 @@ herdr server herdr server stop herdr server reload-config herdr server agent-manifests [--json] +herdr server update-agent-manifests [--json] herdr server reload-agent-manifests ``` -`herdr server` runs the headless server explicitly. Use it for supervised or service-style setups. `reload-config` applies reloadable settings without restarting panes. `agent-manifests` shows the active agent detection manifest sources, cached remote versions, and last remote update results. `reload-agent-manifests` reloads agent detection manifests into the running server after local override edits. +`herdr server` runs the headless server explicitly. Use it for supervised or service-style setups. `reload-config` applies reloadable settings without restarting panes. `agent-manifests` shows the active agent detection manifest sources, cached remote versions, and last remote update results. `update-agent-manifests` fetches remote manifest updates immediately, reloads them into the running server, and prints the updated manifest status; pass `--json` for the raw status response. `reload-agent-manifests` reloads agent detection manifests into the running server after local override edits. ## Notifications diff --git a/src/cli/server.rs b/src/cli/server.rs index f6cf7e54..b0648cde 100644 --- a/src/cli/server.rs +++ b/src/cli/server.rs @@ -11,6 +11,7 @@ pub(super) fn run_server_command(args: &[String]) -> std::io::Result<Option<i32> "--handoff-import" => Ok(None), "reload-config" => server_reload_config(&args[1..]).map(Some), "agent-manifests" => server_agent_manifests(&args[1..]).map(Some), + "update-agent-manifests" => server_update_agent_manifests(&args[1..]).map(Some), "reload-agent-manifests" => server_reload_agent_manifests(&args[1..]).map(Some), "help" | "--help" | "-h" => { print_server_help(); @@ -78,6 +79,69 @@ fn server_reload_agent_manifests(args: &[String]) -> std::io::Result<i32> { })?) } +fn server_update_agent_manifests(args: &[String]) -> std::io::Result<i32> { + let json = match args { + [] => false, + [flag] if flag == "--json" => true, + _ => { + eprintln!("usage: herdr server update-agent-manifests [--json]"); + return Ok(2); + } + }; + + let response = match update_agent_manifest_status(super::send_request, || { + crate::detect::manifest_update::check_and_update().map(|_| ()) + })? { + Ok(response) => response, + Err(err) => { + if json { + return super::print_response(&agent_manifest_update_error_response(&err)); + } + eprintln!("failed to update agent detection manifests: {err}"); + return Ok(1); + } + }; + if json || response.get("error").is_some() { + return super::print_response(&response); + } + + print_agent_manifest_status(&response); + Ok(0) +} + +fn update_agent_manifest_status( + mut send_request: impl FnMut(&Request) -> std::io::Result<serde_json::Value>, + update_manifests: impl FnOnce() -> Result<(), String>, +) -> std::io::Result<Result<serde_json::Value, String>> { + if let Err(err) = update_manifests() { + return Ok(Err(err)); + } + + let reload_response = send_request(&Request { + id: "cli:server:reload-agent-manifests".into(), + method: Method::ServerReloadAgentManifests(EmptyParams::default()), + })?; + if reload_response.get("error").is_some() { + return Ok(Ok(reload_response)); + } + + send_request(&Request { + id: "cli:server:agent-manifests".into(), + method: Method::ServerAgentManifests(EmptyParams::default()), + }) + .map(Ok) +} + +fn agent_manifest_update_error_response(err: &str) -> serde_json::Value { + serde_json::json!({ + "id": "cli:server:update-agent-manifests", + "error": { + "code": "agent_manifest_update_failed", + "message": err, + } + }) +} + fn print_agent_manifest_status(response: &serde_json::Value) { let result = &response["result"]; let last_check = result["last_check_unix"] @@ -187,6 +251,7 @@ fn print_server_help() { eprintln!(" herdr server live-handoff hand off live panes to a new local server"); eprintln!(" herdr server reload-config reload config.toml in the running server"); eprintln!(" herdr server agent-manifests [--json] show agent detection manifest status"); + eprintln!(" herdr server update-agent-manifests [--json] fetch and reload agent detection manifests"); eprintln!(" herdr server reload-agent-manifests reload agent detection manifests in the running server"); } @@ -195,6 +260,84 @@ mod tests { use super::*; #[test] + fn update_agent_manifest_status_fetches_reloads_then_reads_status() { + let mut methods = Vec::new(); + let response = update_agent_manifest_status( + |request| { + methods.push(request.method.clone()); + match &request.method { + Method::ServerReloadAgentManifests(_) => Ok(serde_json::json!({ + "id": request.id, + "result": { "type": "agent_manifest_reload", "manifests": [] } + })), + Method::ServerAgentManifests(_) => Ok(serde_json::json!({ + "id": request.id, + "result": { + "type": "agent_manifest_status", + "last_result": "checked", + "manifests": [] + } + })), + _ => panic!("unexpected request"), + } + }, + || Ok(()), + ) + .unwrap() + .unwrap(); + + assert_eq!(response["result"]["type"], "agent_manifest_status"); + assert_eq!( + methods, + vec![ + Method::ServerReloadAgentManifests(EmptyParams::default()), + Method::ServerAgentManifests(EmptyParams::default()) + ] + ); + } + + #[test] + fn update_agent_manifest_status_skips_server_when_fetch_fails() { + let response = update_agent_manifest_status( + |_request| panic!("server should not be called after fetch failure"), + || Err("network unavailable".to_string()), + ) + .unwrap(); + + assert_eq!(response, Err("network unavailable".to_string())); + assert_eq!( + agent_manifest_update_error_response("network unavailable")["error"]["code"], + "agent_manifest_update_failed" + ); + } + + #[test] + fn update_agent_manifest_status_stops_after_reload_error() { + let mut methods = Vec::new(); + let response = update_agent_manifest_status( + |request| { + methods.push(request.method.clone()); + Ok(serde_json::json!({ + "id": request.id, + "error": { + "code": "reload_failed", + "message": "reload failed" + } + })) + }, + || Ok(()), + ) + .unwrap() + .unwrap(); + + assert_eq!(response["error"]["code"], "reload_failed"); + assert_eq!( + methods, + vec![Method::ServerReloadAgentManifests(EmptyParams::default())] + ); + } + + #[test] fn live_handoff_params_parse_remote_update_fields() { let args = vec