Spike 3: Sub-agent infrastructure fixes for multi-agent coordination
- Fix CLAUDECODE env var blocking nested Claude Code sessions - Add drain-based event_log for reliable get_agent_output polling - Add non-SSE get_agent_output fallback (critical for MCP tool calls) - Preserve worktrees on agent stop instead of destroying work - Reap zombie processes with child.wait() after kill - Increase broadcast buffer from 256 to 1024 - Engineer supervisor and coder prompts in project.toml - Point .mcp.json to test port 3002 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -438,7 +438,7 @@ fn handle_tools_list(id: Option<Value>) -> JsonRpcResponse {
|
||||
},
|
||||
{
|
||||
"name": "stop_agent",
|
||||
"description": "Stop a running agent and clean up its worktree.",
|
||||
"description": "Stop a running agent. Worktree is preserved for inspection.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -532,7 +532,7 @@ async fn handle_tools_call(
|
||||
"list_agents" => tool_list_agents(ctx),
|
||||
"get_agent_config" => tool_get_agent_config(ctx),
|
||||
"reload_agent_config" => tool_get_agent_config(ctx),
|
||||
"get_agent_output" => Err("get_agent_output requires Accept: text/event-stream for SSE streaming".into()),
|
||||
"get_agent_output" => tool_get_agent_output_poll(&args, ctx).await,
|
||||
_ => Err(format!("Unknown tool: {tool_name}")),
|
||||
};
|
||||
|
||||
@@ -737,6 +737,40 @@ fn tool_list_agents(ctx: &AppContext) -> Result<String, String> {
|
||||
.map_err(|e| format!("Serialization error: {e}"))
|
||||
}
|
||||
|
||||
async fn tool_get_agent_output_poll(args: &Value, ctx: &AppContext) -> Result<String, String> {
|
||||
let story_id = args
|
||||
.get("story_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Missing required argument: story_id")?;
|
||||
let agent_name = args
|
||||
.get("agent_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Missing required argument: agent_name")?;
|
||||
|
||||
// Drain all accumulated events since the last poll.
|
||||
let drained = ctx.agents.drain_events(story_id, agent_name)?;
|
||||
|
||||
let done = drained.iter().any(|e| {
|
||||
matches!(
|
||||
e,
|
||||
crate::agents::AgentEvent::Done { .. } | crate::agents::AgentEvent::Error { .. }
|
||||
)
|
||||
});
|
||||
|
||||
let events: Vec<serde_json::Value> = drained
|
||||
.into_iter()
|
||||
.filter_map(|e| serde_json::to_value(&e).ok())
|
||||
.collect();
|
||||
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"events": events,
|
||||
"done": done,
|
||||
"event_count": events.len(),
|
||||
"message": if done { "Agent stream ended." } else if events.is_empty() { "No new events. Call again to continue." } else { "Events returned. Call again to continue." }
|
||||
}))
|
||||
.map_err(|e| format!("Serialization error: {e}"))
|
||||
}
|
||||
|
||||
fn tool_get_agent_config(ctx: &AppContext) -> Result<String, String> {
|
||||
let project_root = ctx.agents.get_project_root(&ctx.state)?;
|
||||
let config = ProjectConfig::load(&project_root)?;
|
||||
|
||||
Reference in New Issue
Block a user