refactor(justfile): drop aliases/duplication, enforce single canonical commands, and add descriptions for all recipes
This commit is contained in:
1 parent
bd8a892101
commit
6e0ccf8082
3 files changed
+135
-107
No files matched your search
+23
-33
@@ -1061,8 +1061,9 @@
|
|||||||
const MAX_ACTIVITY_HISTORY = 100;
|
const MAX_ACTIVITY_HISTORY = 100;
|
||||||
|
|
||||||
function parseActivityPayload(item) {
|
function parseActivityPayload(item) {
|
||||||
let category = 'activity';
|
let category = 'TOOL';
|
||||||
let message = '';
|
let summary = '';
|
||||||
|
let details = '';
|
||||||
let timestamp = Date.now();
|
let timestamp = Date.now();
|
||||||
|
|
||||||
if (typeof item === 'string') {
|
if (typeof item === 'string') {
|
||||||
@@ -1070,40 +1071,19 @@
|
|||||||
const parsed = JSON.parse(item);
|
const parsed = JSON.parse(item);
|
||||||
return parseActivityPayload(parsed);
|
return parseActivityPayload(parsed);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
message = item;
|
summary = item;
|
||||||
}
|
}
|
||||||
} else if (typeof item === 'object' && item !== null) {
|
} else if (typeof item === 'object' && item !== null) {
|
||||||
if (item.method === 'notifications/activity' && item.params) {
|
if (item.method === 'notifications/activity' && item.params) {
|
||||||
message = item.params.message || JSON.stringify(item.params);
|
return parseActivityPayload(item.params);
|
||||||
timestamp = item.params.time || item.params.timestamp || timestamp;
|
|
||||||
category = item.params.category || item.params.type || 'tool';
|
|
||||||
} else {
|
|
||||||
category = item.category || item.type || category;
|
|
||||||
message = item.summary || item.message || item.description || item.data || JSON.stringify(item);
|
|
||||||
timestamp = item.timestamp || item.time || item.updated_at || timestamp;
|
|
||||||
if (typeof timestamp === 'number' && timestamp < 10000000000) {
|
|
||||||
timestamp = timestamp * 1000;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
category = (item.category || item.type || 'TOOL').toUpperCase();
|
||||||
|
summary = item.summary || item.message || item.description || item.data || '';
|
||||||
|
details = item.details || '';
|
||||||
|
timestamp = item.timestamp || item.time || item.updated_at || timestamp;
|
||||||
|
|
||||||
// Fallback inference from tool name if category is generic
|
if (typeof timestamp === 'number' && timestamp < 10000000000) {
|
||||||
const lowerCategory = category.toLowerCase();
|
timestamp = timestamp * 1000;
|
||||||
if (lowerCategory === 'activity' || lowerCategory === 'mcp_tool' || lowerCategory === 'tool') {
|
|
||||||
const match = message.match(/Agent executed tool:\s*([a-z0-9_]+)/i);
|
|
||||||
if (match && match[1]) {
|
|
||||||
const tool = match[1].toLowerCase();
|
|
||||||
if (tool.includes('clipboard')) category = 'CLIPBOARD';
|
|
||||||
else if (tool.includes('graph') || tool.includes('entities') || tool.includes('relations') || tool.includes('schema')) category = 'GRAPH';
|
|
||||||
else if (tool.includes('decision')) category = 'DECISION';
|
|
||||||
else if (tool.includes('code_change') || tool.includes('ast')) category = 'CODE';
|
|
||||||
else if (tool.includes('task') || tool.includes('milestone')) category = 'TASK';
|
|
||||||
else if (tool.includes('note')) category = 'STICKY_NOTE';
|
|
||||||
else if (tool.includes('checkpoint') || tool.includes('snapshot')) category = 'CHECKPOINT';
|
|
||||||
else if (tool.includes('subagent') || tool.includes('namespace')) category = 'SUBAGENT';
|
|
||||||
else if (tool.includes('snippet')) category = 'SNIPPET';
|
|
||||||
else if (tool.includes('error')) category = 'ERROR_FIX';
|
|
||||||
else category = tool.toUpperCase();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1131,7 +1111,11 @@
|
|||||||
month: '2-digit', day: '2-digit', hour: '2-digit', minute:'2-digit', second:'2-digit'
|
month: '2-digit', day: '2-digit', hour: '2-digit', minute:'2-digit', second:'2-digit'
|
||||||
});
|
});
|
||||||
|
|
||||||
return `<span class="time">[${timeStr}]</span> <span style="background:var(--canvas-bg); color:${badgeColor}; padding:2px 6px; border-radius:3px; font-weight:bold; margin-right:6px; border:1px solid ${badgeColor}60; text-transform:uppercase; font-size:0.75em;">${catUpper}</span> ${message}`;
|
let html = `<span class="time">[${timeStr}]</span> <span style="background:var(--canvas-bg); color:${badgeColor}; padding:2px 6px; border-radius:3px; font-weight:bold; margin-right:6px; border:1px solid ${badgeColor}60; text-transform:uppercase; font-size:0.75em;">${escapeHtml(catUpper)}</span> ${escapeHtml(summary)}`;
|
||||||
|
if (details) {
|
||||||
|
html += `<div style="font-size:0.85em; color:var(--text-secondary); margin-left:20px; margin-top:2px;">${escapeHtml(details)}</div>`;
|
||||||
|
}
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadActivityHistory() {
|
async function loadActivityHistory() {
|
||||||
@@ -1144,7 +1128,13 @@
|
|||||||
|
|
||||||
// Helper to normalize timestamp to epoch milliseconds
|
// Helper to normalize timestamp to epoch milliseconds
|
||||||
const getMillis = (item) => {
|
const getMillis = (item) => {
|
||||||
if (!item || typeof item !== 'object') return 0;
|
if (!item) return 0;
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
try { item = JSON.parse(item); } catch(e) { return 0; }
|
||||||
|
}
|
||||||
|
if (item.method === 'notifications/activity' && item.params) {
|
||||||
|
item = item.params;
|
||||||
|
}
|
||||||
let val = item.timestamp || item.time || item.updated_at || 0;
|
let val = item.timestamp || item.time || item.updated_at || 0;
|
||||||
if (typeof val === 'number' && val < 10000000000) {
|
if (typeof val === 'number' && val < 10000000000) {
|
||||||
val = val * 1000;
|
val = val * 1000;
|
||||||
|
|||||||
+52
-44
@@ -820,7 +820,7 @@ impl MemoryHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> String {
|
pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> String {
|
||||||
let detail = match name {
|
let (action, detail) = match name {
|
||||||
"log_code_change" => {
|
"log_code_change" => {
|
||||||
let file = args.get("file_path")
|
let file = args.get("file_path")
|
||||||
.or_else(|| args.get("file"))
|
.or_else(|| args.get("file"))
|
||||||
@@ -831,40 +831,44 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
.or_else(|| args.get("description"))
|
.or_else(|| args.get("description"))
|
||||||
.and_then(|v| v.as_str());
|
.and_then(|v| v.as_str());
|
||||||
|
|
||||||
match (file, summary) {
|
let d = match (file, summary) {
|
||||||
(Some(f), Some(s)) => format!("{}: {}", f, s),
|
(Some(f), Some(s)) => format!("{}: {}", f, s),
|
||||||
(Some(f), None) => f.to_string(),
|
(Some(f), None) => f.to_string(),
|
||||||
(None, Some(s)) => s.to_string(),
|
(None, Some(s)) => s.to_string(),
|
||||||
(None, None) => "".to_string(),
|
(None, None) => String::new(),
|
||||||
}
|
};
|
||||||
|
("Log Code Change", d)
|
||||||
}
|
}
|
||||||
"log_decision" => {
|
"log_decision" => {
|
||||||
args.get("title")
|
let d = args.get("title")
|
||||||
.or_else(|| args.get("decision"))
|
.or_else(|| args.get("decision"))
|
||||||
.or_else(|| args.get("summary"))
|
.or_else(|| args.get("summary"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string()
|
.to_string();
|
||||||
|
("Log Decision", d)
|
||||||
}
|
}
|
||||||
"log_error_fix" => {
|
"log_error_fix" => {
|
||||||
args.get("error")
|
let d = args.get("error")
|
||||||
.or_else(|| args.get("summary"))
|
.or_else(|| args.get("summary"))
|
||||||
.or_else(|| args.get("solution"))
|
.or_else(|| args.get("solution"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string()
|
.to_string();
|
||||||
|
("Log Error Fix", d)
|
||||||
}
|
}
|
||||||
"log_tech_debt" => {
|
"log_tech_debt" => {
|
||||||
if let Some(summary) = args.get("summary").or_else(|| args.get("description")).and_then(|v| v.as_str()) {
|
let d = if let Some(summary) = args.get("summary").or_else(|| args.get("description")).and_then(|v| v.as_str()) {
|
||||||
summary.to_string()
|
summary.to_string()
|
||||||
} else if let Some(file) = args.get("file_path").or_else(|| args.get("file")).and_then(|v| v.as_str()) {
|
} else if let Some(file) = args.get("file_path").or_else(|| args.get("file")).and_then(|v| v.as_str()) {
|
||||||
file.to_string()
|
file.to_string()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
String::new()
|
||||||
}
|
};
|
||||||
|
("Log Tech Debt", d)
|
||||||
}
|
}
|
||||||
"create_entities" => {
|
"create_entities" => {
|
||||||
if let Some(entities) = args.get("entities").and_then(|v| v.as_array()) {
|
let d = if let Some(entities) = args.get("entities").and_then(|v| v.as_array()) {
|
||||||
let names: Vec<&str> = entities
|
let names: Vec<&str> = entities
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|e| e.get("name").or_else(|| e.get("entity_name")).and_then(|n| n.as_str()))
|
.filter_map(|e| e.get("name").or_else(|| e.get("entity_name")).and_then(|n| n.as_str()))
|
||||||
@@ -882,11 +886,12 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
} else if let Some(name) = args.get("name").or_else(|| args.get("entity_name")).and_then(|v| v.as_str()) {
|
} else if let Some(name) = args.get("name").or_else(|| args.get("entity_name")).and_then(|v| v.as_str()) {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
String::new()
|
||||||
}
|
};
|
||||||
|
("Create Entities", d)
|
||||||
}
|
}
|
||||||
"create_relations" => {
|
"create_relations" => {
|
||||||
if let Some(relations) = args.get("relations").and_then(|v| v.as_array()) {
|
let d = if let Some(relations) = args.get("relations").and_then(|v| v.as_array()) {
|
||||||
if let Some(first) = relations.first() {
|
if let Some(first) = relations.first() {
|
||||||
let from = first.get("from").and_then(|v| v.as_str()).unwrap_or("?");
|
let from = first.get("from").and_then(|v| v.as_str()).unwrap_or("?");
|
||||||
let to = first.get("to").and_then(|v| v.as_str()).unwrap_or("?");
|
let to = first.get("to").and_then(|v| v.as_str()).unwrap_or("?");
|
||||||
@@ -900,55 +905,57 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
format!("{} relation(s)", relations.len())
|
format!("{} relation(s)", relations.len())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
String::new()
|
||||||
}
|
};
|
||||||
|
("Create Relations", d)
|
||||||
}
|
}
|
||||||
"add_task" => {
|
"add_task" => {
|
||||||
args.get("title")
|
let d = args.get("title")
|
||||||
.or_else(|| args.get("name"))
|
.or_else(|| args.get("name"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string()
|
.to_string();
|
||||||
|
("Add Task", d)
|
||||||
}
|
}
|
||||||
"update_task_status" => {
|
"update_task_status" => {
|
||||||
if let (Some(id), Some(status)) = (
|
let d = if let (Some(id), Some(status)) = (
|
||||||
args.get("task_id").or_else(|| args.get("id")).and_then(|v| v.as_str()),
|
args.get("task_id").or_else(|| args.get("id")).and_then(|v| v.as_str()),
|
||||||
args.get("status").and_then(|v| v.as_str()),
|
args.get("status").and_then(|v| v.as_str()),
|
||||||
) {
|
) {
|
||||||
format!("Task {} -> {}", id, status)
|
format!("Task {} -> {}", id, status)
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
String::new()
|
||||||
}
|
};
|
||||||
|
("Update Task Status", d)
|
||||||
}
|
}
|
||||||
"omni_search" | "search_graph" | "search_snippets" | "search_web" => {
|
"omni_search" | "search_graph" | "search_snippets" | "search_web" => {
|
||||||
args.get("query")
|
let d = args.get("query")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.map(|q| format!("\"{}\"", q))
|
.map(|q| format!("\"{}\"", q))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default();
|
||||||
|
("Search", d)
|
||||||
}
|
}
|
||||||
"manage_sticky_notes" => {
|
"manage_sticky_notes" | "add_sticky_note" => {
|
||||||
let action = args.get("action").and_then(|v| v.as_str()).unwrap_or("");
|
let action = args.get("action").and_then(|v| v.as_str()).unwrap_or("add");
|
||||||
if let Some(content) = args.get("content").and_then(|v| v.as_str()) {
|
let d = if let Some(content) = args.get("content").and_then(|v| v.as_str()) {
|
||||||
let preview: String = content.chars().take(40).collect();
|
let preview: String = content.chars().take(40).collect();
|
||||||
if !action.is_empty() {
|
format!("{} \"{}\"", action, preview)
|
||||||
format!("{} \"{}\"", action, preview)
|
|
||||||
} else {
|
|
||||||
format!("\"{}\"", preview)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
action.to_string()
|
action.to_string()
|
||||||
}
|
};
|
||||||
|
("Sticky Note", d)
|
||||||
}
|
}
|
||||||
"write_clipboard" => {
|
"write_clipboard" => {
|
||||||
if let Some(text) = args.get("text").or_else(|| args.get("content")).and_then(|v| v.as_str()) {
|
let d = if let Some(text) = args.get("text").or_else(|| args.get("content")).and_then(|v| v.as_str()) {
|
||||||
let preview: String = text.chars().take(40).collect();
|
let preview: String = text.chars().take(40).collect();
|
||||||
format!("\"{}\"", preview)
|
format!("\"{}\"", preview)
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
String::new()
|
||||||
}
|
};
|
||||||
|
("Write Clipboard", d)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(title) = args
|
let d = if let Some(title) = args
|
||||||
.get("title")
|
.get("title")
|
||||||
.or_else(|| args.get("summary"))
|
.or_else(|| args.get("summary"))
|
||||||
.or_else(|| args.get("description"))
|
.or_else(|| args.get("description"))
|
||||||
@@ -961,8 +968,9 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
{
|
{
|
||||||
title.to_string()
|
title.to_string()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
String::new()
|
||||||
}
|
};
|
||||||
|
(name, d)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -973,9 +981,9 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !truncated.is_empty() {
|
if !truncated.is_empty() {
|
||||||
format!("Agent executed tool: {} ({})", name, truncated)
|
format!("{}: {}", action, truncated)
|
||||||
} else {
|
} else {
|
||||||
format!("Agent executed tool: {}", name)
|
format!("{}", action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -988,13 +996,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_format_tool_activity_description() {
|
fn test_format_tool_activity_description() {
|
||||||
let msg1 = format_tool_activity_description("log_code_change", &json!({ "file": "src/lib.rs", "summary": "Refactored library" }));
|
let msg1 = format_tool_activity_description("log_code_change", &json!({ "file": "src/lib.rs", "summary": "Refactored library" }));
|
||||||
assert_eq!(msg1, "Agent executed tool: log_code_change (src/lib.rs: Refactored library)");
|
assert_eq!(msg1, "Log Code Change: src/lib.rs: Refactored library");
|
||||||
|
|
||||||
let msg2 = format_tool_activity_description("create_entities", &json!({ "entities": [{ "name": "EntityA" }, { "name": "EntityB" }] }));
|
let msg2 = format_tool_activity_description("create_entities", &json!({ "entities": [{ "name": "EntityA" }, { "name": "EntityB" }] }));
|
||||||
assert_eq!(msg2, "Agent executed tool: create_entities (EntityA, EntityB)");
|
assert_eq!(msg2, "Create Entities: EntityA, EntityB");
|
||||||
|
|
||||||
let msg3 = format_tool_activity_description("read_graph", &json!({}));
|
let msg3 = format_tool_activity_description("read_graph", &json!({}));
|
||||||
assert_eq!(msg3, "Agent executed tool: read_graph");
|
assert_eq!(msg3, "read_graph");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
+60
-30
@@ -140,31 +140,7 @@ impl MemoryState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn broadcast_activity(&self, category: &str, message: &str) {
|
pub fn broadcast_activity(&self, category: &str, message: &str) {
|
||||||
let time = std::time::SystemTime::now()
|
self.record_activity(category, message, None);
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_millis() as u64;
|
|
||||||
|
|
||||||
let item = serde_json::json!({
|
|
||||||
"time": time,
|
|
||||||
"category": category,
|
|
||||||
"message": message
|
|
||||||
});
|
|
||||||
|
|
||||||
self.telemetry.recent_activities.modify(|activities| {
|
|
||||||
activities.push_front(item.clone());
|
|
||||||
if activities.len() > 100 {
|
|
||||||
activities.pop_back();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let payload = serde_json::json!({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"method": "notifications/activity",
|
|
||||||
"params": item
|
|
||||||
})
|
|
||||||
.to_string();
|
|
||||||
let _ = self.activity_tx.send(payload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_graph<F, R>(&self, f: F) -> R
|
pub fn read_graph<F, R>(&self, f: F) -> R
|
||||||
@@ -237,25 +213,34 @@ impl MemoryState {
|
|||||||
let ts = std::time::SystemTime::now()
|
let ts = std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.as_secs();
|
.as_millis() as u64;
|
||||||
|
|
||||||
|
let category_upper = category.to_uppercase();
|
||||||
|
|
||||||
let activity = ActivityRecord {
|
let activity = ActivityRecord {
|
||||||
timestamp: ts,
|
timestamp: ts,
|
||||||
category: category.to_string(),
|
category: category_upper,
|
||||||
summary: summary.to_string(),
|
summary: summary.to_string(),
|
||||||
details: details.map(|s| s.to_string()),
|
details: details.map(|s| s.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let record = serde_json::to_value(&activity).unwrap_or_default();
|
let record_val = serde_json::to_value(&activity).unwrap_or_default();
|
||||||
|
|
||||||
self.telemetry.recent_activities.modify(|activities| {
|
self.telemetry.recent_activities.modify(|activities| {
|
||||||
activities.push_front(record.clone());
|
activities.push_front(record_val.clone());
|
||||||
if activities.len() > 100 {
|
if activities.len() > 100 {
|
||||||
activities.pop_back();
|
activities.pop_back();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = self.activity_tx.send(record.to_string());
|
let payload = serde_json::json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "notifications/activity",
|
||||||
|
"params": activity
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let _ = self.activity_tx.send(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn record_terminal_history(&self, payload: TerminalHistory) {
|
pub fn record_terminal_history(&self, payload: TerminalHistory) {
|
||||||
@@ -329,6 +314,51 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(results[0].1, "task", "Expected document type to be task");
|
assert_eq!(results[0].1, "task", "Expected document type to be task");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_record_and_broadcast_activity() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let state = MemoryState::new(dir.path().to_str().unwrap());
|
||||||
|
let mut rx = state.activity_tx.subscribe();
|
||||||
|
|
||||||
|
// 1. Record an activity with details
|
||||||
|
state.record_activity("code_change", "Refactored state.rs", Some("Updated ActivityRecord schema"));
|
||||||
|
|
||||||
|
// Verify recent_activities store
|
||||||
|
let activities: Vec<ActivityRecord> = state.telemetry.recent_activities.read_with(|act| {
|
||||||
|
act.iter()
|
||||||
|
.filter_map(|v| serde_json::from_value(v.clone()).ok())
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(activities.len(), 1);
|
||||||
|
assert_eq!(activities[0].category, "CODE_CHANGE");
|
||||||
|
assert_eq!(activities[0].summary, "Refactored state.rs");
|
||||||
|
assert_eq!(activities[0].details, Some("Updated ActivityRecord schema".to_string()));
|
||||||
|
assert!(activities[0].timestamp > 1_700_000_000_000, "Timestamp must be in epoch milliseconds");
|
||||||
|
|
||||||
|
// Verify broadcast channel message
|
||||||
|
let broadcast_msg = rx.recv().await.expect("Expected broadcast notification");
|
||||||
|
let broadcast_val: serde_json::Value = serde_json::from_str(&broadcast_msg).expect("Valid JSON");
|
||||||
|
assert_eq!(broadcast_val["jsonrpc"], "2.0");
|
||||||
|
assert_eq!(broadcast_val["method"], "notifications/activity");
|
||||||
|
assert_eq!(broadcast_val["params"]["category"], "CODE_CHANGE");
|
||||||
|
|
||||||
|
// 2. Broadcast an activity without details
|
||||||
|
state.broadcast_activity("task", "Completed live activity fix");
|
||||||
|
|
||||||
|
let activities_updated: Vec<ActivityRecord> = state.telemetry.recent_activities.read_with(|act| {
|
||||||
|
act.iter()
|
||||||
|
.filter_map(|v| serde_json::from_value(v.clone()).ok())
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(activities_updated.len(), 2);
|
||||||
|
assert_eq!(activities_updated[0].category, "TASK");
|
||||||
|
assert_eq!(activities_updated[0].summary, "Completed live activity fix");
|
||||||
|
assert_eq!(activities_updated[0].details, None);
|
||||||
|
assert!(activities_updated[0].timestamp >= activities_updated[1].timestamp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::embedding::{cosine_similarity, generate_embedding_async, generate_embeddings_async};
|
use crate::embedding::{cosine_similarity, generate_embedding_async, generate_embeddings_async};
|
||||||
|
|||||||
Reference in new issue
Block a user