if (method == "setEnvironment") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); auto params = request.contains("params") ? request["params"] : json::object(); if (!state.activeBuffer) return headlessRpcError(id, -32602, "No active buffer"); Module* ast = state.activeAST(); if (!ast) return headlessRpcError(id, -32602, "No AST available"); // Remove existing EnvironmentSpec if any for (auto* child : ast->getChildren("environment")) { ast->removeChild(child); break; } auto* env = new EnvironmentSpec(); envSpecFromJson(env, params); ast->addChild("environment", env); return headlessRpcResult(id, {{"success", true}, {"envId", env->envId}}); } // --- getEnvironment --- if (method == "getEnvironment") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.activeBuffer) return headlessRpcError(id, -32602, "No active buffer"); Module* ast = state.activeAST(); if (!ast) return headlessRpcError(id, -32602, "No AST available"); auto envChildren = ast->getChildren("environment"); if (envChildren.empty()) return headlessRpcResult(id, {{"hasEnvironment", false}}); auto* env = static_cast(envChildren[0]); json envJson = envSpecToJson(env); envJson["hasEnvironment"] = true; return headlessRpcResult(id, envJson); } // --- validateEnvironment --- if (method == "validateEnvironment") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.activeBuffer) return headlessRpcError(id, -32602, "No active buffer"); Module* ast = state.activeAST(); if (!ast) return headlessRpcError(id, -32602, "No AST available"); auto envChildren = ast->getChildren("environment"); if (envChildren.empty()) return headlessRpcError(id, -32602, "No EnvironmentSpec set on module"); auto* env = static_cast(envChildren[0]); auto capDiags = validateCapabilities(ast, env); auto annoDiags = validateEnvAnnotations(ast, env); json diagsJson = json::array(); for (const auto& d : capDiags) { diagsJson.push_back({{"severity", d.severity}, {"message", d.message}, {"nodeId", d.nodeId}, {"capability", d.capability}}); } for (const auto& d : annoDiags) { diagsJson.push_back({{"severity", d.severity}, {"message", d.message}, {"nodeId", d.nodeId}}); } return headlessRpcResult(id, { {"diagnostics", diagsJson}, {"count", (int)diagsJson.size()} }); } // --- getLoweringHints --- if (method == "getLoweringHints") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); auto params = request.contains("params") ? request["params"] : json::object(); std::string nodeId = params.value("nodeId", ""); if (!state.activeBuffer) return headlessRpcError(id, -32602, "No active buffer"); Module* ast = state.activeAST(); if (!ast) return headlessRpcError(id, -32602, "No AST available"); auto envChildren = ast->getChildren("environment"); if (envChildren.empty()) return headlessRpcError(id, -32602, "No EnvironmentSpec set"); auto* env = static_cast(envChildren[0]); ASTNode* target = nodeId.empty() ? ast : findNodeById(ast, nodeId); if (!target) return headlessRpcError(id, -32602, "Node not found: " + nodeId); auto hints = getLoweringHints(target, env); json hintsJson = json::array(); for (const auto& h : hints) { hintsJson.push_back({{"pattern", h.pattern}, {"description", h.description}}); } return headlessRpcResult(id, {{"hints", hintsJson}, {"count", (int)hintsJson.size()}}); } // --- Step 317: Workflow Annotation Foundation --- // createSkeleton — create a new skeleton module if (method == "createSkeleton") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); auto params = request.contains("params") ? request["params"] : json::object(); std::string name = params.value("name", "skeleton"); std::string language = params.value("language", "python"); // Create skeleton module using openBuffer with empty content std::string bufPath = "skel_" + name; auto* buf = state.openBuffer(bufPath, "", language); // Replace the default empty module with a properly named skeleton auto* rawMod = createSkeletonModule(name, language); buf->sync.setAST(std::unique_ptr(rawMod)); buf->modified = true; return headlessRpcResult(id, {{"bufferId", bufPath}, {"name", name}, {"language", language}}); } // addSkeletonNode — add function/class skeleton with annotations if (method == "addSkeletonNode") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); auto params = request.contains("params") ? request["params"] : json::object(); if (!state.activeBuffer) return headlessRpcError(id, -32602, "No active buffer"); Module* ast = state.activeAST(); if (!ast) return headlessRpcError(id, -32602, "No AST available"); std::string nodeType = params.value("nodeType", "function"); std::string name = params.value("name", "unnamed"); std::vector paramNames; if (params.contains("parameters") && params["parameters"].is_array()) { for (const auto& p : params["parameters"]) if (p.is_string()) paramNames.push_back(p.get()); } // Build annotations from params std::vector annos; if (params.contains("contextWidth")) { auto* cw = new ContextWidthAnnotation(); static int cwC = 0; cw->id = "rpc_cw_" + std::to_string(++cwC); cw->width = params["contextWidth"].get(); annos.push_back(cw); } if (params.contains("automatability")) { auto* aa = new AutomatabilityAnnotation(); static int aaC = 0; aa->id = "rpc_aa_" + std::to_string(++aaC); aa->strategy = params["automatability"].get(); annos.push_back(aa); } if (params.contains("priority")) { auto* pa = new PriorityAnnotation(); static int paC = 0; pa->id = "rpc_pa_" + std::to_string(++paC); pa->level = params["priority"].get(); if (params.contains("blockedBy") && params["blockedBy"].is_array()) { for (const auto& b : params["blockedBy"]) if (b.is_string()) pa->blockedBy.push_back(b.get()); } annos.push_back(pa); } std::string nodeId; if (nodeType == "class") { std::vector methods; if (params.contains("methods") && params["methods"].is_array()) { for (const auto& m : params["methods"]) if (m.is_string()) methods.push_back(m.get()); } auto* cls = addSkeletonClass(ast, name, methods, {}); nodeId = cls->id; } else { auto* fn = addSkeletonFunction(ast, name, paramNames, "", annos); nodeId = fn->id; } state.activeBuffer->modified = true; return headlessRpcResult(id, {{"nodeId", nodeId}, {"name", name}, {"nodeType", nodeType}}); } // getProjectModel — skeleton summary + task list if (method == "getProjectModel") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.activeBuffer) return headlessRpcError(id, -32602, "No active buffer"); Module* ast = state.activeAST(); if (!ast) return headlessRpcError(id, -32602, "No AST available"); auto summary = getSkeletonSummary(ast); auto tasks = skeletonToTaskList(ast); json tasksJson = json::array(); for (const auto& t : tasks) { json tj; tj["nodeId"] = t.nodeId; tj["nodeName"] = t.nodeName; tj["nodeType"] = t.nodeType; tj["contextWidth"] = t.contextWidth; tj["automatability"] = t.automatability; tj["priority"] = t.priority; tj["reviewRequired"] = t.reviewRequired; tj["status"] = t.status; if (!t.dependencies.empty()) tj["dependencies"] = t.dependencies; tasksJson.push_back(tj); } return headlessRpcResult(id, { {"totalNodes", summary.totalNodes}, {"skeletonNodes", summary.skeletonNodes}, {"implementedNodes", summary.implementedNodes}, {"tasks", tasksJson} }); } // inferAnnotations — run AnnotationInference on active buffer if (method == "inferAnnotations") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.activeBuffer) return headlessRpcError(id, -32602, "No active buffer"); Module* ast = state.activeAST(); if (!ast) return headlessRpcError(id, -32602, "No AST available"); AnnotationInference infEngine; auto inferred = infEngine.inferAll(ast); json suggestionsJson = json::array(); for (const auto& inf : inferred) { json sj; sj["nodeId"] = inf.nodeId; sj["annotationType"] = inf.annotationType; if (!inf.key.empty()) sj["key"] = inf.key; if (!inf.value.empty()) sj["value"] = inf.value; sj["reason"] = inf.reason; sj["confidence"] = inf.confidence; suggestionsJson.push_back(sj); } return headlessRpcResult(id, { {"suggestions", suggestionsJson}, {"count", (int)suggestionsJson.size()} }); } // --- createWorkflow --- if (method == "createWorkflow") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); auto params = request.contains("params") ? request["params"] : json::object(); std::string projectName = params.value("projectName", ""); if (projectName.empty()) return headlessRpcError(id, -32602, "Missing projectName"); if (!state.activeBuffer || !state.activeAST()) return headlessRpcError(id, -32000, "No active buffer with AST"); state.workflow = WorkflowState(projectName); std::string bufferId = state.activeBuffer->path; int count = state.workflow->populateFromSkeleton(state.activeAST(), bufferId); state.workflowProgress = WorkflowProgress(count); state.eventStream.emit({ "workflow.created", "", {{"projectName", projectName}, {"itemCount", count}}, workItemTimestamp() }); auto stats = state.workflow->getStats(); return headlessRpcResult(id, { {"itemCount", count}, {"phase", workflowPhaseToString(state.workflow->getPhase())}, {"stats", stats.toJson()} }); } // --- getWorkflowState --- if (method == "getWorkflowState") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.workflow) return headlessRpcError(id, -32000, "No active workflow"); auto stats = state.workflow->getStats(); return headlessRpcResult(id, { {"phase", workflowPhaseToString(state.workflow->getPhase())}, {"stats", stats.toJson()}, {"readyCount", state.workflow->queue.readyCount()}, {"blockedCount", state.workflow->queue.blockedCount()} }); } // --- getReadyTasks --- if (method == "getReadyTasks") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.workflow) return headlessRpcError(id, -32000, "No active workflow"); auto ready = state.workflow->queue.getReady(); json items = json::array(); for (const auto& wi : ready) { items.push_back(workItemToJson(wi)); } return headlessRpcResult(id, {{"items", items}, {"count", (int)items.size()}}); } // --- getWorkItem --- if (method == "getWorkItem") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.workflow) return headlessRpcError(id, -32000, "No active workflow"); auto params = request.contains("params") ? request["params"] : json::object(); std::string itemId = params.value("itemId", ""); if (itemId.empty()) return headlessRpcError(id, -32602, "Missing itemId"); auto item = state.workflow->queue.getItem(itemId); if (!item) return headlessRpcError(id, -32602, "Work item not found"); return headlessRpcResult(id, workItemToJson(*item)); } // --- assignTask --- if (method == "assignTask") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.workflow) return headlessRpcError(id, -32000, "No active workflow"); auto params = request.contains("params") ? request["params"] : json::object(); std::string itemId = params.value("itemId", ""); std::string assignee = params.value("assignee", ""); if (itemId.empty()) return headlessRpcError(id, -32602, "Missing itemId"); auto item = state.workflow->queue.getItem(itemId); if (!item) return headlessRpcError(id, -32602, "Work item not found"); WorkItem updated = *item; bool ok = transitionWorkItem(updated, WI_ASSIGNED); if (!ok) return headlessRpcError(id, -32000, "Cannot assign item in status: " + updated.status); updated.assignee = assignee; state.workflow->queue.updateItem(itemId, updated); state.workflow->recordChange(itemId, item->status, WI_ASSIGNED, "agent:" + sessionId); return headlessRpcResult(id, {{"success", true}, {"item", workItemToJson(updated)}}); } // --- completeTask --- if (method == "completeTask") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.workflow) return headlessRpcError(id, -32000, "No active workflow"); auto params = request.contains("params") ? request["params"] : json::object(); std::string itemId = params.value("itemId", ""); if (itemId.empty()) return headlessRpcError(id, -32602, "Missing itemId"); auto item = state.workflow->queue.getItem(itemId); if (!item) return headlessRpcError(id, -32602, "Work item not found"); // Attach result WorkItem updated = *item; if (params.contains("result")) { auto r = params["result"]; updated.result.generatedCode = r.value("generatedCode", ""); updated.result.confidence = r.value("confidence", 0.0f); updated.result.reasoning = r.value("reasoning", ""); } // Transition: must be in-progress (or review) to complete if (updated.status == WI_IN_PROGRESS || updated.status == WI_REVIEW) { transitionWorkItem(updated, WI_COMPLETE); } else { return headlessRpcError(id, -32000, "Cannot complete item in status: " + updated.status); } state.workflow->queue.updateItem(itemId, updated); state.workflow->recordChange(itemId, item->status, WI_COMPLETE, "agent:" + sessionId); // Re-evaluate dependencies via complete() // (already handled internally since we set status to complete) // Check for newly ready items auto ready = state.workflow->queue.getReady(); json newlyReady = json::array(); for (const auto& wi : ready) { newlyReady.push_back(workItemToJson(wi)); } return headlessRpcResult(id, { {"success", true}, {"newlyReady", newlyReady} }); } // --- rejectTask --- if (method == "rejectTask") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.workflow) return headlessRpcError(id, -32000, "No active workflow"); auto params = request.contains("params") ? request["params"] : json::object(); std::string itemId = params.value("itemId", ""); std::string reason = params.value("reason", ""); if (itemId.empty()) return headlessRpcError(id, -32602, "Missing itemId"); auto item = state.workflow->queue.getItem(itemId); if (!item) return headlessRpcError(id, -32602, "Work item not found"); if (item->status != WI_REVIEW) return headlessRpcError(id, -32000, "Cannot reject item in status: " + item->status); bool ok = state.workflow->queue.reject(itemId, reason); if (!ok) return headlessRpcError(id, -32000, "Reject failed"); state.workflow->recordChange(itemId, "review", WI_READY, "human:" + sessionId, reason); return headlessRpcResult(id, {{"success", true}}); } // --- saveWorkflow --- if (method == "saveWorkflow") { if (!AgentPermissionPolicy::canInvoke(role, method)) return headlessRpcError(id, -32031, "Role not permitted"); if (!state.workflow) return headlessRpcError(id, -32000, "No active workflow"); std::string root = state.workspaceRoot.empty() ? "." : state.workspaceRoot; auto sr = ::saveWorkflow(root, *state.workflow); return headlessRpcResult(id, { {"success", sr.success}, {"path", sr.path}, {"bytesWritten", sr.bytesWritten} }); } // --- routeTask ---