// Step 382: Orchestrator RPC + MCP tools (12 tests) #include #include #include #include #include "HeadlessEditorState.h" #include "HeadlessAgentRPCHandler.h" #include "MCPServer.h" static WorkItem makeItem(const std::string& id, const std::string& name, const std::string& workerType = "", const std::string& contextWidth = "local") { WorkItem item; item.id = id; item.nodeId = id + "_node"; item.nodeName = name; item.nodeType = "Function"; item.bufferId = "main.py"; item.workerType = workerType; item.contextWidth = contextWidth; item.priority = "medium"; item.status = WI_PENDING; item.createdAt = workItemTimestamp(); return item; } static HeadlessEditorState makeState(AgentRole role = AgentRole::Generator) { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("main.py", "def a():\n return 1\n", "python"); state.setAgentRole("test-session", role); state.workflow = WorkflowState("wf"); return state; } static json rpc(HeadlessEditorState& state, const std::string& method, const json& params = json::object()) { json request = {{"jsonrpc", "2.0"}, {"id", 1}, {"method", method}, {"params", params}}; return handleHeadlessAgentRequest(state, request, "test-session"); } static json mcpCall(MCPServer& server, const std::string& method, const json& params = json::object()) { json request = {{"jsonrpc", "2.0"}, {"id", 1}, {"method", method}, {"params", params}}; return server.handleRequest(request); } int main() { int passed = 0; // Test 1: orchestrateStep advances one template task { auto state = makeState(); state.workflow->queue.enqueue(makeItem("t1", "getValue", "template")); auto resp = rpc(state, "orchestrateStep"); assert(resp.contains("result")); assert(resp["result"]["event"]["type"] == "completed"); std::cout << "Test 1 PASSED: orchestrateStep advances one task\n"; passed++; } // Test 2: orchestrateAdvance processes a batch of ready items { auto state = makeState(); state.workflow->queue.enqueue(makeItem("a", "getA", "template")); state.workflow->queue.enqueue(makeItem("b", "getB", "template")); auto resp = rpc(state, "orchestrateAdvance"); assert(resp.contains("result")); assert(resp["result"]["itemsAdvanced"].get() == 2); std::cout << "Test 2 PASSED: orchestrateAdvance handles batches\n"; passed++; } // Test 3: orchestrateRunDeterministic stops at blockers { auto state = makeState(); state.workflow->queue.enqueue(makeItem("d1", "getFast", "template")); state.workflow->queue.enqueue(makeItem("h1", "manual", "human")); auto resp = rpc(state, "orchestrateRunDeterministic"); assert(resp.contains("result")); assert(resp["result"]["stats"]["complete"].get() == 1); assert(!resp["result"]["blockers"].empty()); std::cout << "Test 3 PASSED: runDeterministic stops at blockers\n"; passed++; } // Test 4: getBlockers reports human items { auto state = makeState(); state.workflow->queue.enqueue(makeItem("h1", "manual", "human")); rpc(state, "orchestrateStep"); // move to blocked human state auto blockers = rpc(state, "getBlockers"); assert(blockers.contains("result")); assert(blockers["result"]["count"].get() >= 1); std::cout << "Test 4 PASSED: getBlockers reports workflow blockers\n"; passed++; } // Test 5: getProgress returns valid snapshot fields { auto state = makeState(); state.workflow->queue.enqueue(makeItem("t1", "getV", "template")); rpc(state, "orchestrateStep"); auto resp = rpc(state, "getProgress"); assert(resp.contains("result")); assert(resp["result"].contains("completionPercent")); assert(resp["result"].contains("itemsPerMinute")); assert(resp["result"].contains("byWorkerType")); std::cout << "Test 5 PASSED: getProgress returns snapshot\n"; passed++; } // Test 6: submitExternalResult accepts LLM output and advances task { auto state = makeState(); state.reviewPolicy.defaultAction = "auto-approve"; WorkItem llm = makeItem("x1", "buildGraph", "", "project"); state.workflow->queue.enqueue(llm); rpc(state, "orchestrateStep"); // routes to llm, blocks in-progress auto submit = rpc(state, "submitExternalResult", { {"itemId", "x1"}, {"result", { {"generatedCode", "def buildGraph(data):\n return {}"}, {"confidence", 0.92}, {"reasoning", "external model result"}, {"tokensGenerated", 40} }} }); assert(submit.contains("result")); assert(submit["result"]["success"].get()); auto item = state.workflow->queue.getItem("x1"); assert(item.has_value()); assert(item->status == WI_COMPLETE); std::cout << "Test 6 PASSED: submitExternalResult advances agent task\n"; passed++; } // Test 7: linter can read progress but cannot orchestrate { auto state = makeState(AgentRole::Linter); state.workflow->queue.enqueue(makeItem("l1", "getName", "template")); auto progress = rpc(state, "getProgress"); auto step = rpc(state, "orchestrateStep"); assert(progress.contains("result")); assert(step.contains("error")); std::cout << "Test 7 PASSED: linter read-only orchestration access\n"; passed++; } // Test 8: MCP tool registration includes orchestrator tools { MCPServer server; const auto& tools = server.getTools(); std::vector needed = { "whetstone_orchestrate_step", "whetstone_orchestrate_advance", "whetstone_orchestrate_run_deterministic", "whetstone_get_blockers", "whetstone_get_progress", "whetstone_submit_result" }; int found = 0; for (const auto& t : tools) { for (const auto& n : needed) { if (t.name == n) found++; } } assert(found == 6); std::cout << "Test 8 PASSED: MCP orchestrator tools registered\n"; passed++; } // Test 9: MCP orchestrate_run_deterministic executes through callback { auto state = makeState(); state.workflow->queue.enqueue(makeItem("m1", "getOne", "template")); MCPServer server; server.setRpcCallback([&state](const json& request) { return handleHeadlessAgentRequest(state, request, "test-session"); }); auto resp = mcpCall(server, "tools/call", { {"name", "whetstone_orchestrate_run_deterministic"}, {"arguments", json::object()} }); assert(resp.contains("result")); assert(resp["result"]["isError"].get() == false); std::cout << "Test 9 PASSED: MCP run_deterministic tool call works\n"; passed++; } // Test 10: combined flow updates progress after external submit { auto state = makeState(); state.reviewPolicy.defaultAction = "auto-approve"; state.workflow->queue.enqueue(makeItem("d1", "getFast", "template")); state.workflow->queue.enqueue(makeItem("x1", "buildGraph", "", "project")); rpc(state, "orchestrateRunDeterministic"); auto blockers = rpc(state, "getBlockers"); assert(blockers["result"]["count"].get() >= 1); rpc(state, "submitExternalResult", { {"itemId", "x1"}, {"result", { {"generatedCode", "def buildGraph(data):\n return data"}, {"confidence", 0.88}, {"reasoning", "llm output"}, {"tokensGenerated", 24} }} }); auto progress = rpc(state, "getProgress"); assert(progress["result"]["completedItems"].get() == 2); std::cout << "Test 10 PASSED: combined orchestrator flow tracked\n"; passed++; } // Test 11: submitExternalResult rejects non-agent worker types { auto state = makeState(); WorkItem t = makeItem("t1", "getName", "template"); transitionWorkItem(t, WI_READY); transitionWorkItem(t, WI_ASSIGNED); transitionWorkItem(t, WI_IN_PROGRESS); state.workflow->queue.enqueue(t); auto resp = rpc(state, "submitExternalResult", { {"itemId", "t1"}, {"result", {{"generatedCode", "return name"}}} }); assert(resp.contains("error")); std::cout << "Test 11 PASSED: submitExternalResult enforces worker type\n"; passed++; } // Test 12: orchestrator RPC methods require active workflow { HeadlessEditorState state; state.setAgentRole("test-session", AgentRole::Generator); auto resp = rpc(state, "orchestrateAdvance"); assert(resp.contains("error")); std::cout << "Test 12 PASSED: workflow guards enforced\n"; passed++; } std::cout << "\nResults: " << passed << "/12\n"; assert(passed == 12); return 0; }