Step 230: Enhance session recorder
This commit is contained in:
@@ -749,3 +749,4 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
|
||||
| 2026-02-10 | Codex | Step 227: Open-source model tool definitions (ReAct-style prompt, XML tool call format, simplified schemas, adapter rules). 1/1 tests pass. |
|
||||
| 2026-02-10 | Codex | Step 228: Prompt engineering templates (5 prompt files with system/user templates, expected tools, success criteria). 1/1 tests pass. |
|
||||
| 2026-02-10 | Codex | Step 229: Tool definition tests (schema alignment, prompt coverage, examples validation, prompt variable checks). 1/1 tests pass. |
|
||||
| 2026-02-10 | Codex | Step 230: Enhanced session recorder (metadata, event capture, ms timestamps, UI toggle, auto-record setting). 1/1 tests pass. |
|
||||
|
||||
@@ -1172,6 +1172,9 @@ target_link_libraries(step228_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
add_executable(step229_test tests/step229_test.cpp)
|
||||
target_include_directories(step229_test PRIVATE src)
|
||||
target_link_libraries(step229_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
add_executable(step230_test tests/step230_test.cpp)
|
||||
target_include_directories(step230_test PRIVATE src)
|
||||
target_link_libraries(step230_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
add_executable(step213_test tests/step213_test.cpp)
|
||||
target_include_directories(step213_test PRIVATE src)
|
||||
target_link_libraries(step213_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
@@ -733,6 +733,91 @@ struct EditorState {
|
||||
uiAnimations.recordTabSwitch(path, ImGui::GetTime());
|
||||
}
|
||||
|
||||
static std::string osLabel() {
|
||||
#ifdef _WIN32
|
||||
return "windows";
|
||||
#elif __APPLE__
|
||||
return "macos";
|
||||
#else
|
||||
return "linux";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string detectProjectType() const {
|
||||
std::filesystem::path root(workspaceRoot);
|
||||
std::error_code ec;
|
||||
auto exists = [&](const std::string& name) {
|
||||
return std::filesystem::exists(root / name, ec);
|
||||
};
|
||||
if (exists("CMakeLists.txt")) return "cmake";
|
||||
if (exists("package.json")) return "node";
|
||||
if (exists("pyproject.toml") || exists("requirements.txt")) return "python";
|
||||
if (exists("Cargo.toml")) return "rust";
|
||||
if (exists("go.mod")) return "go";
|
||||
if (exists("pom.xml") || exists("build.gradle")) return "java";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
json buildSessionMetadata(bool autoRecording) const {
|
||||
json meta;
|
||||
meta["editorVersion"] = welcome.getVersion();
|
||||
meta["os"] = osLabel();
|
||||
meta["language"] = active() ? active()->language : std::string("unknown");
|
||||
meta["projectType"] = detectProjectType();
|
||||
meta["autoRecording"] = autoRecording;
|
||||
return meta;
|
||||
}
|
||||
|
||||
void startSessionRecording(const std::string& name, bool autoRecording) {
|
||||
WorkflowRecorder::RecordingConfig cfg;
|
||||
cfg.recordAllSessions = true;
|
||||
cfg.recordControlMethods = true;
|
||||
cfg.recordEditorEvents = true;
|
||||
cfg.autoRecording = autoRecording;
|
||||
agent.workflowRecorder.startRecording(name, "", cfg, buildSessionMetadata(autoRecording));
|
||||
agent.workflowRecorder.recordEvent("session_start", {{"name", name}, {"autoRecording", autoRecording}});
|
||||
notify(NotificationLevel::Info, "Session recording started.");
|
||||
}
|
||||
|
||||
void stopSessionRecording() {
|
||||
if (!agent.workflowRecorder.isRecording()) return;
|
||||
agent.workflowRecorder.recordEvent("session_stop", json::object());
|
||||
agent.workflowRecorder.stopRecording();
|
||||
notify(NotificationLevel::Info, "Session recording stopped.");
|
||||
}
|
||||
|
||||
void toggleSessionRecording() {
|
||||
if (agent.workflowRecorder.isRecording()) stopSessionRecording();
|
||||
else startSessionRecording("session", false);
|
||||
}
|
||||
|
||||
static std::string uiEventLabel(UIEventType type) {
|
||||
switch (type) {
|
||||
case UIEventType::ASTChanged: return "ast_changed";
|
||||
case UIEventType::BufferSwitched: return "buffer_switched";
|
||||
case UIEventType::DiagnosticsUpdated: return "diagnostics_updated";
|
||||
case UIEventType::ThemeChanged: return "theme_changed";
|
||||
case UIEventType::SettingsChanged: return "settings_changed";
|
||||
case UIEventType::FileModified: return "file_modified";
|
||||
case UIEventType::AgentConnected: return "agent_connected";
|
||||
case UIEventType::NotificationPosted: return "notification_posted";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void recordUIEvent(const UIEvent& ev) {
|
||||
json payload = {
|
||||
{"path", ev.path},
|
||||
{"detail", ev.detail},
|
||||
{"uiTimestamp", ev.timestamp}
|
||||
};
|
||||
agent.workflowRecorder.recordEvent(uiEventLabel(ev.type), payload);
|
||||
}
|
||||
|
||||
void recordEditorEvent(const std::string& type, const json& payload) {
|
||||
agent.workflowRecorder.recordEvent(type, payload);
|
||||
}
|
||||
|
||||
std::filesystem::path configDir() const {
|
||||
const char* home = std::getenv("USERPROFILE");
|
||||
if (!home) home = std::getenv("HOME");
|
||||
@@ -1076,10 +1161,13 @@ struct EditorState {
|
||||
loadRecentFiles();
|
||||
loadFeatureHints(featureHints, workspaceRoot);
|
||||
loadSettingsFromDisk();
|
||||
if (settings.getAutoRecordSessions() && !agent.workflowRecorder.isRecording()) {
|
||||
startSessionRecording("auto-session", true);
|
||||
}
|
||||
registerCommands();
|
||||
library.vulnDb.startBackgroundRefresh();
|
||||
library.semanticTags.load();
|
||||
events.subscribe(UIEventType::BufferSwitched, [this](const UIEvent&) {
|
||||
events.subscribe(UIEventType::BufferSwitched, [this](const UIEvent& ev) {
|
||||
if (!active()) return;
|
||||
library.primitives.setRoot(activeAST());
|
||||
library.primitives.setLanguage(active()->language);
|
||||
@@ -1089,6 +1177,7 @@ struct EditorState {
|
||||
if (active()->language == "elisp") {
|
||||
emacsState.emacsFunctionIndexDirty = true;
|
||||
}
|
||||
recordUIEvent(ev);
|
||||
});
|
||||
events.subscribe(UIEventType::ASTChanged, [this](const UIEvent&) {
|
||||
if (!active()) return;
|
||||
@@ -1098,8 +1187,18 @@ struct EditorState {
|
||||
symbolsLastChange = ImGui::GetTime();
|
||||
if (lsp) lsp->clearDocumentSymbols();
|
||||
});
|
||||
events.subscribe(UIEventType::SettingsChanged, [this](const UIEvent&) {
|
||||
events.subscribe(UIEventType::SettingsChanged, [this](const UIEvent& ev) {
|
||||
applySettingsToState();
|
||||
recordUIEvent(ev);
|
||||
});
|
||||
events.subscribe(UIEventType::ThemeChanged, [this](const UIEvent& ev) {
|
||||
recordUIEvent(ev);
|
||||
});
|
||||
events.subscribe(UIEventType::FileModified, [this](const UIEvent& ev) {
|
||||
recordUIEvent(ev);
|
||||
});
|
||||
events.subscribe(UIEventType::NotificationPosted, [this](const UIEvent& ev) {
|
||||
recordUIEvent(ev);
|
||||
});
|
||||
startEmacsDaemonFromSettings();
|
||||
initAgentServer();
|
||||
@@ -1209,8 +1308,10 @@ struct EditorState {
|
||||
logAgentEvent("Agent " + s.sessionId + " " + event);
|
||||
if (event == "connected") {
|
||||
agent.roles[s.sessionId] = agent.defaultRole;
|
||||
recordEditorEvent("agent_connected", {{"sessionId", s.sessionId}, {"agentName", s.agentName}});
|
||||
} else if (event == "disconnected") {
|
||||
agent.roles.erase(s.sessionId);
|
||||
recordEditorEvent("agent_disconnected", {{"sessionId", s.sessionId}});
|
||||
}
|
||||
});
|
||||
agent.server->setRequestLogCallback([this](const std::string& sid,
|
||||
@@ -1510,7 +1611,8 @@ struct EditorState {
|
||||
if (method == "startWorkflowRecording") {
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
std::string name = params.value("name", "workflow");
|
||||
agent.workflowRecorder.startRecording(name, sessionId);
|
||||
agent.workflowRecorder.startRecording(name, sessionId, WorkflowRecorder::RecordingConfig{},
|
||||
buildSessionMetadata(false));
|
||||
response["result"] = {
|
||||
{"recording", true},
|
||||
{"name", name}
|
||||
@@ -2740,6 +2842,7 @@ struct EditorState {
|
||||
active()->modified = false;
|
||||
notify(NotificationLevel::Success,
|
||||
"Saved: " + active()->path);
|
||||
recordEditorEvent("file_saved", {{"path", active()->path}});
|
||||
if (lsp) lsp->didSave(toFileUri(active()->path), active()->editBuf);
|
||||
} else {
|
||||
notify(NotificationLevel::Error,
|
||||
@@ -2809,6 +2912,12 @@ struct EditorState {
|
||||
largeFilePromptBytes = fileSizeBytes;
|
||||
}
|
||||
notify(NotificationLevel::Success, "Opened: " + path);
|
||||
recordEditorEvent("file_opened", {
|
||||
{"path", path},
|
||||
{"language", language},
|
||||
{"mode", bufferModeToString(effectiveMode)},
|
||||
{"sizeBytes", fileSizeBytes}
|
||||
});
|
||||
} else {
|
||||
notify(NotificationLevel::Error, "Error opening: " + path);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ public:
|
||||
void setUseAnnotationShapes(bool value) { useAnnotationShapes_ = value; }
|
||||
bool getBlockVulnerableImports() const { return blockVulnerableImports_; }
|
||||
void setBlockVulnerableImports(bool value) { blockVulnerableImports_ = value; }
|
||||
bool getAutoRecordSessions() const { return autoRecordSessions_; }
|
||||
void setAutoRecordSessions(bool value) { autoRecordSessions_ = value; }
|
||||
int getLargeFileWarnMB() const { return largeFileWarnMB_; }
|
||||
void setLargeFileWarnMB(int value) { largeFileWarnMB_ = value; }
|
||||
int getLargeFileTextMB() const { return largeFileTextMB_; }
|
||||
@@ -101,6 +103,7 @@ public:
|
||||
reduceMotion_ = j.value("reduceMotion", reduceMotion_);
|
||||
useAnnotationShapes_ = j.value("useAnnotationShapes", useAnnotationShapes_);
|
||||
blockVulnerableImports_ = j.value("blockVulnerableImports", blockVulnerableImports_);
|
||||
autoRecordSessions_ = j.value("autoRecordSessions", autoRecordSessions_);
|
||||
largeFileWarnMB_ = j.value("largeFileWarnMB", largeFileWarnMB_);
|
||||
largeFileTextMB_ = j.value("largeFileTextMB", largeFileTextMB_);
|
||||
largeFileDisableHighlightMB_ = j.value("largeFileDisableHighlightMB", largeFileDisableHighlightMB_);
|
||||
@@ -147,6 +150,7 @@ public:
|
||||
j["reduceMotion"] = reduceMotion_;
|
||||
j["useAnnotationShapes"] = useAnnotationShapes_;
|
||||
j["blockVulnerableImports"] = blockVulnerableImports_;
|
||||
j["autoRecordSessions"] = autoRecordSessions_;
|
||||
j["largeFileWarnMB"] = largeFileWarnMB_;
|
||||
j["largeFileTextMB"] = largeFileTextMB_;
|
||||
j["largeFileDisableHighlightMB"] = largeFileDisableHighlightMB_;
|
||||
@@ -230,6 +234,7 @@ private:
|
||||
bool reduceMotion_ = false;
|
||||
bool useAnnotationShapes_ = true;
|
||||
bool blockVulnerableImports_ = false;
|
||||
bool autoRecordSessions_ = false;
|
||||
int largeFileWarnMB_ = 1;
|
||||
int largeFileTextMB_ = 5;
|
||||
int largeFileDisableHighlightMB_ = 10;
|
||||
|
||||
@@ -12,19 +12,44 @@ using json = nlohmann::json;
|
||||
|
||||
class WorkflowRecorder {
|
||||
public:
|
||||
struct RecordingConfig {
|
||||
bool recordAllSessions = false;
|
||||
bool recordControlMethods = false;
|
||||
bool recordEditorEvents = true;
|
||||
bool autoRecording = false;
|
||||
};
|
||||
|
||||
struct WorkflowCall {
|
||||
json request;
|
||||
json response;
|
||||
std::string timestamp;
|
||||
std::string sessionId;
|
||||
std::string method;
|
||||
};
|
||||
|
||||
struct WorkflowEvent {
|
||||
std::string type;
|
||||
json payload;
|
||||
std::string timestamp;
|
||||
};
|
||||
|
||||
void startRecording(const std::string& name, const std::string& sessionId) {
|
||||
startRecording(name, sessionId, RecordingConfig{}, json::object());
|
||||
}
|
||||
|
||||
void startRecording(const std::string& name,
|
||||
const std::string& sessionId,
|
||||
const RecordingConfig& config,
|
||||
const json& metadata) {
|
||||
name_ = name.empty() ? "workflow" : name;
|
||||
sessionId_ = sessionId;
|
||||
calls_.clear();
|
||||
events_.clear();
|
||||
recording_ = true;
|
||||
createdAt_ = currentTimestamp();
|
||||
stoppedAt_.clear();
|
||||
config_ = config;
|
||||
metadata_ = metadata;
|
||||
}
|
||||
|
||||
json stopRecording() {
|
||||
@@ -37,36 +62,60 @@ public:
|
||||
bool isReplaying() const { return replaying_; }
|
||||
|
||||
void setReplaying(bool replaying) { replaying_ = replaying; }
|
||||
bool isAutoRecording() const { return config_.autoRecording; }
|
||||
|
||||
void record(const std::string& sessionId,
|
||||
const json& request,
|
||||
const json& response) {
|
||||
if (!recording_ || replaying_) return;
|
||||
if (!sessionId_.empty() && sessionId != sessionId_) return;
|
||||
if (!config_.recordAllSessions && !sessionId_.empty() && sessionId != sessionId_) return;
|
||||
std::string method = request.value("method", "");
|
||||
if (isControlMethod(method)) return;
|
||||
if (!config_.recordControlMethods && isControlMethod(method)) return;
|
||||
WorkflowCall call;
|
||||
call.request = request;
|
||||
call.response = response;
|
||||
call.timestamp = currentTimestamp();
|
||||
call.sessionId = sessionId;
|
||||
call.method = method;
|
||||
calls_.push_back(std::move(call));
|
||||
}
|
||||
|
||||
void recordEvent(const std::string& type, const json& payload) {
|
||||
if (!recording_ || replaying_ || !config_.recordEditorEvents) return;
|
||||
WorkflowEvent ev;
|
||||
ev.type = type;
|
||||
ev.payload = payload;
|
||||
ev.timestamp = currentTimestamp();
|
||||
events_.push_back(std::move(ev));
|
||||
}
|
||||
|
||||
json exportWorkflow() const {
|
||||
json out;
|
||||
out["name"] = name_;
|
||||
out["sessionId"] = sessionId_;
|
||||
out["createdAt"] = createdAt_;
|
||||
out["stoppedAt"] = stoppedAt_;
|
||||
out["metadata"] = metadata_;
|
||||
json arr = json::array();
|
||||
for (const auto& c : calls_) {
|
||||
arr.push_back({
|
||||
{"timestamp", c.timestamp},
|
||||
{"sessionId", c.sessionId},
|
||||
{"method", c.method},
|
||||
{"request", c.request},
|
||||
{"response", c.response}
|
||||
});
|
||||
}
|
||||
out["calls"] = arr;
|
||||
json events = json::array();
|
||||
for (const auto& e : events_) {
|
||||
events.push_back({
|
||||
{"timestamp", e.timestamp},
|
||||
{"type", e.type},
|
||||
{"payload", e.payload}
|
||||
});
|
||||
}
|
||||
out["events"] = events;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -76,14 +125,27 @@ public:
|
||||
sessionId_ = workflow.value("sessionId", "");
|
||||
createdAt_ = workflow.value("createdAt", "");
|
||||
stoppedAt_ = workflow.value("stoppedAt", "");
|
||||
metadata_ = workflow.value("metadata", json::object());
|
||||
calls_.clear();
|
||||
for (const auto& c : workflow["calls"]) {
|
||||
WorkflowCall call;
|
||||
call.timestamp = c.value("timestamp", "");
|
||||
call.sessionId = c.value("sessionId", "");
|
||||
call.method = c.value("method", "");
|
||||
call.request = c.value("request", json::object());
|
||||
call.response = c.value("response", json::object());
|
||||
calls_.push_back(std::move(call));
|
||||
}
|
||||
events_.clear();
|
||||
if (workflow.contains("events") && workflow["events"].is_array()) {
|
||||
for (const auto& e : workflow["events"]) {
|
||||
WorkflowEvent ev;
|
||||
ev.timestamp = e.value("timestamp", "");
|
||||
ev.type = e.value("type", "");
|
||||
ev.payload = e.value("payload", json::object());
|
||||
events_.push_back(std::move(ev));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -105,12 +167,17 @@ public:
|
||||
createdAt_.clear();
|
||||
stoppedAt_.clear();
|
||||
calls_.clear();
|
||||
events_.clear();
|
||||
metadata_.clear();
|
||||
recording_ = false;
|
||||
config_ = RecordingConfig{};
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string currentTimestamp() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()) % 1000;
|
||||
std::time_t tt = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm tm{};
|
||||
#ifdef _WIN32
|
||||
@@ -119,7 +186,8 @@ private:
|
||||
localtime_r(&tt, &tm);
|
||||
#endif
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(&tm, "%H:%M:%S");
|
||||
oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S")
|
||||
<< '.' << std::setw(3) << std::setfill('0') << ms.count();
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
@@ -134,7 +202,10 @@ private:
|
||||
std::string sessionId_;
|
||||
std::string createdAt_;
|
||||
std::string stoppedAt_;
|
||||
json metadata_;
|
||||
std::vector<WorkflowCall> calls_;
|
||||
std::vector<WorkflowEvent> events_;
|
||||
bool recording_ = false;
|
||||
bool replaying_ = false;
|
||||
RecordingConfig config_;
|
||||
};
|
||||
|
||||
@@ -359,6 +359,18 @@ static void renderSettingsPanel(EditorState& state) {
|
||||
state.settings.setBlockVulnerableImports(blockVulnImports);
|
||||
settingsChanged = true;
|
||||
}
|
||||
bool autoRecordSessions = state.settings.getAutoRecordSessions();
|
||||
if (ImGui::Checkbox("Auto-record Sessions", &autoRecordSessions)) {
|
||||
state.settings.setAutoRecordSessions(autoRecordSessions);
|
||||
if (autoRecordSessions && !state.agent.workflowRecorder.isRecording()) {
|
||||
state.startSessionRecording("auto-session", true);
|
||||
} else if (!autoRecordSessions &&
|
||||
state.agent.workflowRecorder.isRecording() &&
|
||||
state.agent.workflowRecorder.isAutoRecording()) {
|
||||
state.stopSessionRecording();
|
||||
}
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Large File Thresholds (MB)");
|
||||
|
||||
@@ -143,6 +143,23 @@ static void renderStatusBar(EditorState& state) {
|
||||
state.notifications.showHistory = !state.notifications.showHistory;
|
||||
}
|
||||
|
||||
// Recording indicator
|
||||
ImGui::SameLine(0, 10);
|
||||
bool recording = state.agent.workflowRecorder.isRecording();
|
||||
if (recording) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.75f, 0.15f, 0.15f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.85f, 0.20f, 0.20f, 1.0f));
|
||||
}
|
||||
if (ImGui::Button("REC")) {
|
||||
state.toggleSessionRecording();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(recording ? "Session recording active" : "Start session recording");
|
||||
}
|
||||
if (recording) {
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
// Right cluster: Ln/Col, selection, zoom, git
|
||||
int selStart = -1;
|
||||
int selEnd = -1;
|
||||
|
||||
48
editor/tests/step230_test.cpp
Normal file
48
editor/tests/step230_test.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
// Step 230: Enhanced session recorder.
|
||||
#include "WorkflowRecorder.h"
|
||||
#include <iostream>
|
||||
|
||||
static void expect(bool cond, const std::string& name, int& passed, int& failed) {
|
||||
if (cond) {
|
||||
std::cout << "Test " << (passed + failed + 1) << " PASS: " << name << "\n";
|
||||
++passed;
|
||||
} else {
|
||||
std::cout << "Test " << (passed + failed + 1) << " FAIL: " << name << "\n";
|
||||
++failed;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
WorkflowRecorder rec;
|
||||
WorkflowRecorder::RecordingConfig cfg;
|
||||
cfg.recordAllSessions = true;
|
||||
cfg.recordControlMethods = true;
|
||||
cfg.recordEditorEvents = true;
|
||||
cfg.autoRecording = true;
|
||||
|
||||
json meta = {{"editorVersion", "0.3.0"}, {"os", "test"}};
|
||||
rec.startRecording("session", "", cfg, meta);
|
||||
|
||||
json req = {{"jsonrpc", "2.0"}, {"id", 1}, {"method", "getAST"}};
|
||||
json res = {{"jsonrpc", "2.0"}, {"id", 1}, {"result", true}};
|
||||
rec.record("s1", req, res);
|
||||
rec.recordEvent("file_opened", {"path", "main.py"});
|
||||
|
||||
json out = rec.stopRecording();
|
||||
expect(out.value("name", "") == "session", "recording name", passed, failed);
|
||||
expect(out["metadata"].value("editorVersion", "") == "0.3.0",
|
||||
"metadata preserved", passed, failed);
|
||||
expect(out["calls"].size() == 1, "records rpc calls", passed, failed);
|
||||
expect(out["calls"][0].value("sessionId", "") == "s1", "session id captured", passed, failed);
|
||||
expect(out["events"].size() == 1, "records editor events", passed, failed);
|
||||
expect(out["events"][0].value("type", "") == "file_opened", "event type captured", passed, failed);
|
||||
expect(out["events"][0].value("timestamp", "").find('.') != std::string::npos,
|
||||
"timestamp includes milliseconds", passed, failed);
|
||||
|
||||
std::cout << "\n=== Step 230 Results: " << passed << " passed, "
|
||||
<< failed << " failed ===\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -401,7 +401,7 @@ Optimized tool definitions and prompts for specific LLM families.
|
||||
|
||||
Capture real editing sessions, anonymize them, and export as training data.
|
||||
|
||||
- [ ] **Step 230: Enhanced session recorder**
|
||||
- [x] **Step 230: Enhanced session recorder**
|
||||
Extend `WorkflowRecorder` for full session capture:
|
||||
- Record all JSON-RPC traffic (not just agent-initiated)
|
||||
- Capture editor events: file open, buffer switch, theme change, etc.
|
||||
|
||||
Reference in New Issue
Block a user