Files
whetstone_DSL/editor/src/headless_rpc/DispatchPart5.h

472 lines
19 KiB
C++

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<EnvironmentSpec*>(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<EnvironmentSpec*>(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<EnvironmentSpec*>(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<Module>(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<std::string> paramNames;
if (params.contains("parameters") && params["parameters"].is_array()) {
for (const auto& p : params["parameters"])
if (p.is_string()) paramNames.push_back(p.get<std::string>());
}
// Build annotations from params
std::vector<ASTNode*> 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<std::string>();
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<std::string>();
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<std::string>();
if (params.contains("blockedBy") && params["blockedBy"].is_array()) {
for (const auto& b : params["blockedBy"])
if (b.is_string()) pa->blockedBy.push_back(b.get<std::string>());
}
annos.push_back(pa);
}
std::string nodeId;
if (nodeType == "class") {
std::vector<std::string> methods;
if (params.contains("methods") && params["methods"].is_array()) {
for (const auto& m : params["methods"])
if (m.is_string()) methods.push_back(m.get<std::string>());
}
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 ---