Harden worker boot recovery before task dispatch

The worker boot registry now exposes the requested lifecycle states, emits structured trust and prompt-delivery events, and recovers from shell or wrong-target prompt delivery by replaying the last prompt. Supporting fixes keep MCP remote config parsing backwards-compatible and make CLI argument parsing less dependent on ambient config and cwd state so the workspace stays green under full parallel test runs.

Constraint: Worker prompts must not be dispatched before a confirmed ready_for_prompt handshake
Constraint: Prompt misdelivery recovery must stay minimal and avoid new dependencies
Rejected: Keep prompt_accepted and blocked as public lifecycle states | user requested the narrower explicit state set
Rejected: Treat url-only MCP server configs as invalid | existing CLI/runtime tests still rely on that shorthand
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Preserve prompt_in_flight semantics when extending worker boot; misdelivery detection depends on it
Tested: cargo build --workspace; cargo test --workspace
Not-tested: Live tmux worker delivery against a real external coding agent pane
This commit is contained in:
Yeachan-Heo
2026-04-04 14:50:31 +00:00
parent d87fbe6c65
commit 784f07abfa
6 changed files with 398 additions and 88 deletions

View File

@@ -786,7 +786,8 @@ fn parse_mcp_server_config(
context: &str,
) -> Result<McpServerConfig, ConfigError> {
let object = expect_object(value, context)?;
let server_type = optional_string(object, "type", context)?.unwrap_or("stdio");
let server_type =
optional_string(object, "type", context)?.unwrap_or_else(|| infer_mcp_server_type(object));
match server_type {
"stdio" => Ok(McpServerConfig::Stdio(McpStdioServerConfig {
command: expect_string(object, "command", context)?.to_string(),
@@ -818,6 +819,14 @@ fn parse_mcp_server_config(
}
}
fn infer_mcp_server_type(object: &BTreeMap<String, JsonValue>) -> &'static str {
if object.contains_key("url") {
"http"
} else {
"stdio"
}
}
fn parse_mcp_remote_server_config(
object: &BTreeMap<String, JsonValue>,
context: &str,
@@ -1297,6 +1306,41 @@ mod tests {
fs::remove_dir_all(root).expect("cleanup temp dir");
}
#[test]
fn infers_http_mcp_servers_from_url_only_config() {
let root = temp_dir();
let cwd = root.join("project");
let home = root.join("home").join(".claw");
fs::create_dir_all(&home).expect("home config dir");
fs::create_dir_all(&cwd).expect("project dir");
fs::write(
home.join("settings.json"),
r#"{
"mcpServers": {
"remote": {
"url": "https://example.test/mcp"
}
}
}"#,
)
.expect("write mcp settings");
let loaded = ConfigLoader::new(&cwd, &home)
.load()
.expect("config should load");
let remote_server = loaded.mcp().get("remote").expect("remote server should exist");
assert_eq!(remote_server.transport(), McpTransport::Http);
match &remote_server.config {
McpServerConfig::Http(config) => {
assert_eq!(config.url, "https://example.test/mcp");
}
other => panic!("expected http config, got {other:?}"),
}
fs::remove_dir_all(root).expect("cleanup temp dir");
}
#[test]
fn parses_plugin_config_from_enabled_plugins() {
let root = temp_dir();