Step 125: agent permissions and request logging

This commit is contained in:
Bill
2026-02-09 15:21:39 -07:00
parent a1c4572d7a
commit 336a55b298
7 changed files with 109 additions and 11 deletions

View File

@@ -157,6 +157,7 @@ struct EditorState {
MockWebSocketTransport* agentTransport = nullptr;
int agentPort = 8765;
std::vector<std::string> agentLog;
std::map<std::string, bool> agentMutationPermissions;
// Custom editor widget state
bool showWhitespace = false;
@@ -713,12 +714,25 @@ struct EditorState {
auto transport = std::make_unique<MockWebSocketTransport>();
agentTransport = transport.get();
agentServer = std::make_unique<WebSocketAgentServer>(std::move(transport));
agentServer->setRequestHandler([this](const json& request) {
return processAgentRequest(request);
agentServer->setRequestHandler([this](const json& request,
const std::string& sessionId) {
return processAgentRequest(request, sessionId);
});
agentServer->setSessionEventCallback([this](const AgentSession& s,
const std::string& event) {
logAgentEvent("Agent " + s.sessionId + " " + event);
if (event == "connected") {
agentMutationPermissions[s.sessionId] = false;
} else if (event == "disconnected") {
agentMutationPermissions.erase(s.sessionId);
}
});
agentServer->setRequestLogCallback([this](const std::string& sid,
const json& req,
const json& res) {
std::string method = req.value("method", "");
bool ok = !res.contains("error");
logAgentEvent("RPC " + sid + " " + method + (ok ? " ok" : " error"));
});
if (agentServer->start(agentPort)) {
logAgentEvent("Agent server started on port " + std::to_string(agentPort));
@@ -767,14 +781,12 @@ struct EditorState {
return out;
}
json processAgentRequest(const json& request) {
json processAgentRequest(const json& request, const std::string& sessionId) {
json response;
response["jsonrpc"] = "2.0";
response["id"] = request.contains("id") ? request["id"] : json(nullptr);
std::string method = request.value("method", "");
logAgentEvent("Agent RPC: " + method);
if (method == "getAST") {
if (!active() || !isStructured()) {
response["error"] = {{"code", -32000}, {"message", "No structured buffer"}};
@@ -794,6 +806,11 @@ struct EditorState {
}
if (method == "applyMutation") {
auto permIt = agentMutationPermissions.find(sessionId);
if (permIt == agentMutationPermissions.end() || !permIt->second) {
response["error"] = {{"code", -32030}, {"message", "Mutation not permitted"}};
return response;
}
if (!active() || !isStructured()) {
response["error"] = {{"code", -32000}, {"message", "No structured buffer"}};
return response;

View File

@@ -36,9 +36,14 @@ struct AgentSession {
};
// Callback types
using JsonRpcHandler = std::function<json(const json& request)>;
using JsonRpcHandler =
std::function<json(const json& request, const std::string& sessionId)>;
using SessionEventCallback =
std::function<void(const AgentSession& session, const std::string& event)>;
using RequestLogCallback =
std::function<void(const std::string& sessionId,
const json& request,
const json& response)>;
// ---------------------------------------------------------------------------
// WebSocketTransport — abstract transport interface
@@ -119,6 +124,10 @@ public:
sessionEventCallback_ = std::move(cb);
}
void setRequestLogCallback(RequestLogCallback cb) {
requestLogCallback_ = std::move(cb);
}
// --- session queries ---
const AgentSession* getSession(const std::string& sessionId) const {
@@ -188,6 +197,13 @@ private:
};
}
if (requestLogCallback_) {
try {
requestLogCallback_(sessionId, json::parse(message), response);
} catch (...) {
// Ignore logging parse errors
}
}
transport_->sendMessage(sessionId, response.dump());
}
@@ -252,7 +268,7 @@ private:
}
else if (requestHandler_) {
// Delegate to the orchestrator's processRequest
response = requestHandler_(request);
response = requestHandler_(request, sessionId);
// Ensure jsonrpc and id are set
response["jsonrpc"] = "2.0";
if (!response.contains("id"))
@@ -287,6 +303,7 @@ private:
std::map<std::string, AgentSession> sessions_;
JsonRpcHandler requestHandler_;
SessionEventCallback sessionEventCallback_;
RequestLogCallback requestLogCallback_;
int port_ = 0;
};

View File

@@ -1656,8 +1656,27 @@ int main(int, char**) {
for (const auto& s : state.agentServer->getActiveSessions()) {
std::string label = s.sessionId;
if (!s.agentName.empty()) label += " (" + s.agentName + ")";
label += " - msgs: " + std::to_string(s.messageCount);
ImGui::BulletText("%s", label.c_str());
ImGui::TextUnformatted(label.c_str());
ImGui::SameLine(260.0f);
bool canMutate = state.agentMutationPermissions[s.sessionId];
std::string checkId = "Can mutate##" + s.sessionId;
if (ImGui::Checkbox(checkId.c_str(), &canMutate)) {
state.agentMutationPermissions[s.sessionId] = canMutate;
}
ImGui::SameLine(420.0f);
ImGui::Text("Msgs: %d", s.messageCount);
ImGui::SameLine(520.0f);
ImGui::Text("Last: %llu", (unsigned long long)s.lastMessageAtMs);
ImGui::SameLine(680.0f);
std::string btnId = "Disconnect##" + s.sessionId;
if (ImGui::Button(btnId.c_str())) {
if (state.agentTransport) {
state.agentTransport->simulateDisconnect(s.sessionId);
} else {
state.logAgentEvent("STUB: disconnect not supported for real transport");
}
}
ImGui::Separator();
}
}