mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-10 19:33:37 +08:00
feat(ecc2): persist tool log params and trigger context
This commit is contained in:
@@ -9,7 +9,9 @@ pub struct ToolCallEvent {
|
|||||||
pub session_id: String,
|
pub session_id: String,
|
||||||
pub tool_name: String,
|
pub tool_name: String,
|
||||||
pub input_summary: String,
|
pub input_summary: String,
|
||||||
|
pub input_params_json: String,
|
||||||
pub output_summary: String,
|
pub output_summary: String,
|
||||||
|
pub trigger_summary: String,
|
||||||
pub duration_ms: u64,
|
pub duration_ms: u64,
|
||||||
pub risk_score: f64,
|
pub risk_score: f64,
|
||||||
}
|
}
|
||||||
@@ -47,7 +49,9 @@ impl ToolCallEvent {
|
|||||||
.score,
|
.score,
|
||||||
tool_name,
|
tool_name,
|
||||||
input_summary,
|
input_summary,
|
||||||
|
input_params_json: "{}".to_string(),
|
||||||
output_summary: output_summary.into(),
|
output_summary: output_summary.into(),
|
||||||
|
trigger_summary: String::new(),
|
||||||
duration_ms,
|
duration_ms,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,7 +242,9 @@ pub struct ToolLogEntry {
|
|||||||
pub session_id: String,
|
pub session_id: String,
|
||||||
pub tool_name: String,
|
pub tool_name: String,
|
||||||
pub input_summary: String,
|
pub input_summary: String,
|
||||||
|
pub input_params_json: String,
|
||||||
pub output_summary: String,
|
pub output_summary: String,
|
||||||
|
pub trigger_summary: String,
|
||||||
pub duration_ms: u64,
|
pub duration_ms: u64,
|
||||||
pub risk_score: f64,
|
pub risk_score: f64,
|
||||||
pub timestamp: String,
|
pub timestamp: String,
|
||||||
@@ -268,7 +274,9 @@ impl<'a> ToolLogger<'a> {
|
|||||||
&event.session_id,
|
&event.session_id,
|
||||||
&event.tool_name,
|
&event.tool_name,
|
||||||
&event.input_summary,
|
&event.input_summary,
|
||||||
|
&event.input_params_json,
|
||||||
&event.output_summary,
|
&event.output_summary,
|
||||||
|
&event.trigger_summary,
|
||||||
event.duration_ms,
|
event.duration_ms,
|
||||||
event.risk_score,
|
event.risk_score,
|
||||||
×tamp,
|
×tamp,
|
||||||
@@ -398,6 +406,8 @@ mod tests {
|
|||||||
assert_eq!(first_page.entries.len(), 2);
|
assert_eq!(first_page.entries.len(), 2);
|
||||||
assert_eq!(first_page.entries[0].tool_name, "Bash");
|
assert_eq!(first_page.entries[0].tool_name, "Bash");
|
||||||
assert_eq!(first_page.entries[1].tool_name, "Write");
|
assert_eq!(first_page.entries[1].tool_name, "Write");
|
||||||
|
assert_eq!(first_page.entries[0].input_params_json, "{}");
|
||||||
|
assert_eq!(first_page.entries[0].trigger_summary, "");
|
||||||
|
|
||||||
let second_page = logger.query("sess-1", 2, 2)?;
|
let second_page = logger.query("sess-1", 2, 2)?;
|
||||||
assert_eq!(second_page.total, 3);
|
assert_eq!(second_page.total, 3);
|
||||||
|
|||||||
@@ -155,7 +155,9 @@ impl StateStore {
|
|||||||
session_id TEXT NOT NULL REFERENCES sessions(id),
|
session_id TEXT NOT NULL REFERENCES sessions(id),
|
||||||
tool_name TEXT NOT NULL,
|
tool_name TEXT NOT NULL,
|
||||||
input_summary TEXT,
|
input_summary TEXT,
|
||||||
|
input_params_json TEXT NOT NULL DEFAULT '{}',
|
||||||
output_summary TEXT,
|
output_summary TEXT,
|
||||||
|
trigger_summary TEXT NOT NULL DEFAULT '',
|
||||||
duration_ms INTEGER,
|
duration_ms INTEGER,
|
||||||
risk_score REAL DEFAULT 0.0,
|
risk_score REAL DEFAULT 0.0,
|
||||||
timestamp TEXT NOT NULL,
|
timestamp TEXT NOT NULL,
|
||||||
@@ -293,6 +295,24 @@ impl StateStore {
|
|||||||
.context("Failed to add file_events_json column to tool_log table")?;
|
.context("Failed to add file_events_json column to tool_log table")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.has_column("tool_log", "input_params_json")? {
|
||||||
|
self.conn
|
||||||
|
.execute(
|
||||||
|
"ALTER TABLE tool_log ADD COLUMN input_params_json TEXT NOT NULL DEFAULT '{}'",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.context("Failed to add input_params_json column to tool_log table")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.has_column("tool_log", "trigger_summary")? {
|
||||||
|
self.conn
|
||||||
|
.execute(
|
||||||
|
"ALTER TABLE tool_log ADD COLUMN trigger_summary TEXT NOT NULL DEFAULT ''",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.context("Failed to add trigger_summary column to tool_log table")?;
|
||||||
|
}
|
||||||
|
|
||||||
if !self.has_column("daemon_activity", "last_dispatch_deferred")? {
|
if !self.has_column("daemon_activity", "last_dispatch_deferred")? {
|
||||||
self.conn
|
self.conn
|
||||||
.execute(
|
.execute(
|
||||||
@@ -754,6 +774,8 @@ impl StateStore {
|
|||||||
tool_name: String,
|
tool_name: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
input_summary: String,
|
input_summary: String,
|
||||||
|
#[serde(default = "default_input_params_json")]
|
||||||
|
input_params_json: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
output_summary: String,
|
output_summary: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -781,6 +803,11 @@ impl StateStore {
|
|||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
let mut aggregates: HashMap<String, ActivityAggregate> = HashMap::new();
|
let mut aggregates: HashMap<String, ActivityAggregate> = HashMap::new();
|
||||||
let mut seen_event_ids = HashSet::new();
|
let mut seen_event_ids = HashSet::new();
|
||||||
|
let session_tasks = self
|
||||||
|
.list_sessions()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|session| (session.id, session.task))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
let line = line?;
|
let line = line?;
|
||||||
@@ -853,6 +880,7 @@ impl StateStore {
|
|||||||
)
|
)
|
||||||
.score;
|
.score;
|
||||||
let session_id = row.session_id.clone();
|
let session_id = row.session_id.clone();
|
||||||
|
let trigger_summary = session_tasks.get(&session_id).cloned().unwrap_or_default();
|
||||||
|
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
"INSERT OR IGNORE INTO tool_log (
|
"INSERT OR IGNORE INTO tool_log (
|
||||||
@@ -860,20 +888,24 @@ impl StateStore {
|
|||||||
session_id,
|
session_id,
|
||||||
tool_name,
|
tool_name,
|
||||||
input_summary,
|
input_summary,
|
||||||
|
input_params_json,
|
||||||
output_summary,
|
output_summary,
|
||||||
|
trigger_summary,
|
||||||
duration_ms,
|
duration_ms,
|
||||||
risk_score,
|
risk_score,
|
||||||
timestamp,
|
timestamp,
|
||||||
file_paths_json,
|
file_paths_json,
|
||||||
file_events_json
|
file_events_json
|
||||||
)
|
)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
|
||||||
rusqlite::params![
|
rusqlite::params![
|
||||||
row.id,
|
row.id,
|
||||||
row.session_id,
|
row.session_id,
|
||||||
row.tool_name,
|
row.tool_name,
|
||||||
row.input_summary,
|
row.input_summary,
|
||||||
|
row.input_params_json,
|
||||||
row.output_summary,
|
row.output_summary,
|
||||||
|
trigger_summary,
|
||||||
row.duration_ms,
|
row.duration_ms,
|
||||||
risk_score,
|
risk_score,
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -1472,19 +1504,23 @@ impl StateStore {
|
|||||||
session_id: &str,
|
session_id: &str,
|
||||||
tool_name: &str,
|
tool_name: &str,
|
||||||
input_summary: &str,
|
input_summary: &str,
|
||||||
|
input_params_json: &str,
|
||||||
output_summary: &str,
|
output_summary: &str,
|
||||||
|
trigger_summary: &str,
|
||||||
duration_ms: u64,
|
duration_ms: u64,
|
||||||
risk_score: f64,
|
risk_score: f64,
|
||||||
timestamp: &str,
|
timestamp: &str,
|
||||||
) -> Result<ToolLogEntry> {
|
) -> Result<ToolLogEntry> {
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
"INSERT INTO tool_log (session_id, tool_name, input_summary, output_summary, duration_ms, risk_score, timestamp)
|
"INSERT INTO tool_log (session_id, tool_name, input_summary, input_params_json, output_summary, trigger_summary, duration_ms, risk_score, timestamp)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
|
||||||
rusqlite::params![
|
rusqlite::params![
|
||||||
session_id,
|
session_id,
|
||||||
tool_name,
|
tool_name,
|
||||||
input_summary,
|
input_summary,
|
||||||
|
input_params_json,
|
||||||
output_summary,
|
output_summary,
|
||||||
|
trigger_summary,
|
||||||
duration_ms,
|
duration_ms,
|
||||||
risk_score,
|
risk_score,
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -1496,7 +1532,9 @@ impl StateStore {
|
|||||||
session_id: session_id.to_string(),
|
session_id: session_id.to_string(),
|
||||||
tool_name: tool_name.to_string(),
|
tool_name: tool_name.to_string(),
|
||||||
input_summary: input_summary.to_string(),
|
input_summary: input_summary.to_string(),
|
||||||
|
input_params_json: input_params_json.to_string(),
|
||||||
output_summary: output_summary.to_string(),
|
output_summary: output_summary.to_string(),
|
||||||
|
trigger_summary: trigger_summary.to_string(),
|
||||||
duration_ms,
|
duration_ms,
|
||||||
risk_score,
|
risk_score,
|
||||||
timestamp: timestamp.to_string(),
|
timestamp: timestamp.to_string(),
|
||||||
@@ -1519,7 +1557,7 @@ impl StateStore {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut stmt = self.conn.prepare(
|
let mut stmt = self.conn.prepare(
|
||||||
"SELECT id, session_id, tool_name, input_summary, output_summary, duration_ms, risk_score, timestamp
|
"SELECT id, session_id, tool_name, input_summary, input_params_json, output_summary, trigger_summary, duration_ms, risk_score, timestamp
|
||||||
FROM tool_log
|
FROM tool_log
|
||||||
WHERE session_id = ?1
|
WHERE session_id = ?1
|
||||||
ORDER BY timestamp DESC, id DESC
|
ORDER BY timestamp DESC, id DESC
|
||||||
@@ -1533,10 +1571,14 @@ impl StateStore {
|
|||||||
session_id: row.get(1)?,
|
session_id: row.get(1)?,
|
||||||
tool_name: row.get(2)?,
|
tool_name: row.get(2)?,
|
||||||
input_summary: row.get::<_, Option<String>>(3)?.unwrap_or_default(),
|
input_summary: row.get::<_, Option<String>>(3)?.unwrap_or_default(),
|
||||||
output_summary: row.get::<_, Option<String>>(4)?.unwrap_or_default(),
|
input_params_json: row
|
||||||
duration_ms: row.get::<_, Option<u64>>(5)?.unwrap_or_default(),
|
.get::<_, Option<String>>(4)?
|
||||||
risk_score: row.get::<_, Option<f64>>(6)?.unwrap_or_default(),
|
.unwrap_or_else(|| "{}".to_string()),
|
||||||
timestamp: row.get(7)?,
|
output_summary: row.get::<_, Option<String>>(5)?.unwrap_or_default(),
|
||||||
|
trigger_summary: row.get::<_, Option<String>>(6)?.unwrap_or_default(),
|
||||||
|
duration_ms: row.get::<_, Option<u64>>(7)?.unwrap_or_default(),
|
||||||
|
risk_score: row.get::<_, Option<f64>>(8)?.unwrap_or_default(),
|
||||||
|
timestamp: row.get(9)?,
|
||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
@@ -1757,6 +1799,10 @@ fn normalize_optional_string(value: Option<String>) -> Option<String> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_input_params_json() -> String {
|
||||||
|
"{}".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn infer_file_activity_action(tool_name: &str) -> FileActivityAction {
|
fn infer_file_activity_action(tool_name: &str) -> FileActivityAction {
|
||||||
let tool_name = tool_name.trim().to_ascii_lowercase();
|
let tool_name = tool_name.trim().to_ascii_lowercase();
|
||||||
if tool_name.contains("read") {
|
if tool_name.contains("read") {
|
||||||
@@ -1991,9 +2037,9 @@ mod tests {
|
|||||||
fs::write(
|
fs::write(
|
||||||
&metrics_path,
|
&metrics_path,
|
||||||
concat!(
|
concat!(
|
||||||
"{\"id\":\"evt-1\",\"session_id\":\"session-1\",\"tool_name\":\"Read\",\"input_summary\":\"Read src/lib.rs\",\"output_summary\":\"ok\",\"file_paths\":[\"src/lib.rs\"],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n",
|
"{\"id\":\"evt-1\",\"session_id\":\"session-1\",\"tool_name\":\"Read\",\"input_summary\":\"Read src/lib.rs\",\"input_params_json\":\"{\\\"file_path\\\":\\\"src/lib.rs\\\"}\",\"output_summary\":\"ok\",\"file_paths\":[\"src/lib.rs\"],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n",
|
||||||
"{\"id\":\"evt-1\",\"session_id\":\"session-1\",\"tool_name\":\"Read\",\"input_summary\":\"Read src/lib.rs\",\"output_summary\":\"ok\",\"file_paths\":[\"src/lib.rs\"],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n",
|
"{\"id\":\"evt-1\",\"session_id\":\"session-1\",\"tool_name\":\"Read\",\"input_summary\":\"Read src/lib.rs\",\"input_params_json\":\"{\\\"file_path\\\":\\\"src/lib.rs\\\"}\",\"output_summary\":\"ok\",\"file_paths\":[\"src/lib.rs\"],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n",
|
||||||
"{\"id\":\"evt-2\",\"session_id\":\"session-1\",\"tool_name\":\"Write\",\"input_summary\":\"Write README.md\",\"output_summary\":\"ok\",\"file_paths\":[\"src/lib.rs\",\"README.md\"],\"timestamp\":\"2026-04-09T00:01:00Z\"}\n"
|
"{\"id\":\"evt-2\",\"session_id\":\"session-1\",\"tool_name\":\"Write\",\"input_summary\":\"Write README.md\",\"input_params_json\":\"{\\\"file_path\\\":\\\"README.md\\\",\\\"content\\\":\\\"hello\\\"}\",\"output_summary\":\"ok\",\"file_paths\":[\"src/lib.rs\",\"README.md\"],\"timestamp\":\"2026-04-09T00:01:00Z\"}\n"
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -2015,6 +2061,16 @@ mod tests {
|
|||||||
assert_eq!(logs.total, 2);
|
assert_eq!(logs.total, 2);
|
||||||
assert_eq!(logs.entries[0].tool_name, "Write");
|
assert_eq!(logs.entries[0].tool_name, "Write");
|
||||||
assert_eq!(logs.entries[1].tool_name, "Read");
|
assert_eq!(logs.entries[1].tool_name, "Read");
|
||||||
|
assert_eq!(
|
||||||
|
logs.entries[0].input_params_json,
|
||||||
|
"{\"file_path\":\"README.md\",\"content\":\"hello\"}"
|
||||||
|
);
|
||||||
|
assert_eq!(logs.entries[0].trigger_summary, "sync tools");
|
||||||
|
assert_eq!(
|
||||||
|
logs.entries[1].input_params_json,
|
||||||
|
"{\"file_path\":\"src/lib.rs\"}"
|
||||||
|
);
|
||||||
|
assert_eq!(logs.entries[1].trigger_summary, "sync tools");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -883,15 +883,31 @@ impl Dashboard {
|
|||||||
self.logs
|
self.logs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
format!(
|
let mut block = format!(
|
||||||
"[{}] {} | {}ms | risk {:.0}%\ninput: {}\noutput: {}",
|
"[{}] {} | {}ms | risk {:.0}%",
|
||||||
self.short_timestamp(&entry.timestamp),
|
self.short_timestamp(&entry.timestamp),
|
||||||
entry.tool_name,
|
entry.tool_name,
|
||||||
entry.duration_ms,
|
entry.duration_ms,
|
||||||
entry.risk_score * 100.0,
|
entry.risk_score * 100.0,
|
||||||
|
);
|
||||||
|
if !entry.trigger_summary.trim().is_empty() {
|
||||||
|
block.push_str(&format!(
|
||||||
|
"\nwhy: {}",
|
||||||
|
self.log_field(&entry.trigger_summary)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if entry.input_params_json.trim() != "{}" {
|
||||||
|
block.push_str(&format!(
|
||||||
|
"\nparams: {}",
|
||||||
|
self.log_field(&entry.input_params_json)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
block.push_str(&format!(
|
||||||
|
"\ninput: {}\noutput: {}",
|
||||||
self.log_field(&entry.input_summary),
|
self.log_field(&entry.input_summary),
|
||||||
self.log_field(&entry.output_summary)
|
self.log_field(&entry.output_summary)
|
||||||
)
|
));
|
||||||
|
block
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n\n")
|
.join("\n\n")
|
||||||
@@ -3559,7 +3575,7 @@ impl Dashboard {
|
|||||||
entry.duration_ms,
|
entry.duration_ms,
|
||||||
truncate_for_dashboard(&entry.input_summary, 56)
|
truncate_for_dashboard(&entry.input_summary, 56)
|
||||||
),
|
),
|
||||||
detail_lines: Vec::new(),
|
detail_lines: tool_log_detail_lines(&entry),
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
events
|
events
|
||||||
@@ -5475,6 +5491,23 @@ fn file_overlap_summary(entry: &FileActivityOverlap, timestamp: &str) -> String
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tool_log_detail_lines(entry: &ToolLogEntry) -> Vec<String> {
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
if !entry.trigger_summary.trim().is_empty() {
|
||||||
|
lines.push(format!(
|
||||||
|
"why {}",
|
||||||
|
truncate_for_dashboard(&entry.trigger_summary, 72)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if entry.input_params_json.trim() != "{}" {
|
||||||
|
lines.push(format!(
|
||||||
|
"params {}",
|
||||||
|
truncate_for_dashboard(&entry.input_params_json, 72)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
fn file_activity_verb(action: crate::session::FileActivityAction) -> &'static str {
|
fn file_activity_verb(action: crate::session::FileActivityAction) -> &'static str {
|
||||||
match action {
|
match action {
|
||||||
crate::session::FileActivityAction::Read => "read",
|
crate::session::FileActivityAction::Read => "read",
|
||||||
@@ -6050,7 +6083,9 @@ mod tests {
|
|||||||
"focus-12345678",
|
"focus-12345678",
|
||||||
"bash",
|
"bash",
|
||||||
"cargo test -q",
|
"cargo test -q",
|
||||||
|
"{\"command\":\"cargo test -q\"}",
|
||||||
"ok",
|
"ok",
|
||||||
|
"stabilize planner session",
|
||||||
240,
|
240,
|
||||||
0.2,
|
0.2,
|
||||||
&(now - chrono::Duration::minutes(3)).to_rfc3339(),
|
&(now - chrono::Duration::minutes(3)).to_rfc3339(),
|
||||||
@@ -6069,6 +6104,8 @@ mod tests {
|
|||||||
assert!(rendered.contains("created session as planner"));
|
assert!(rendered.contains("created session as planner"));
|
||||||
assert!(rendered.contains("received query lead-123"));
|
assert!(rendered.contains("received query lead-123"));
|
||||||
assert!(rendered.contains("tool bash"));
|
assert!(rendered.contains("tool bash"));
|
||||||
|
assert!(rendered.contains("why stabilize planner session"));
|
||||||
|
assert!(rendered.contains("params {\"command\":\"cargo test -q\"}"));
|
||||||
assert!(rendered.contains("files touched 3"));
|
assert!(rendered.contains("files touched 3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6104,7 +6141,9 @@ mod tests {
|
|||||||
"focus-12345678",
|
"focus-12345678",
|
||||||
"bash",
|
"bash",
|
||||||
"cargo test -q",
|
"cargo test -q",
|
||||||
|
"{}",
|
||||||
"ok",
|
"ok",
|
||||||
|
"",
|
||||||
240,
|
240,
|
||||||
0.2,
|
0.2,
|
||||||
&(now - chrono::Duration::minutes(3)).to_rfc3339(),
|
&(now - chrono::Duration::minutes(3)).to_rfc3339(),
|
||||||
@@ -6254,7 +6293,9 @@ mod tests {
|
|||||||
"focus-12345678",
|
"focus-12345678",
|
||||||
"bash",
|
"bash",
|
||||||
"cargo test -q",
|
"cargo test -q",
|
||||||
|
"{}",
|
||||||
"ok",
|
"ok",
|
||||||
|
"",
|
||||||
240,
|
240,
|
||||||
0.2,
|
0.2,
|
||||||
&(now - chrono::Duration::minutes(3)).to_rfc3339(),
|
&(now - chrono::Duration::minutes(3)).to_rfc3339(),
|
||||||
@@ -6313,7 +6354,9 @@ mod tests {
|
|||||||
"focus-12345678",
|
"focus-12345678",
|
||||||
"bash",
|
"bash",
|
||||||
"cargo test -q",
|
"cargo test -q",
|
||||||
|
"{}",
|
||||||
"ok",
|
"ok",
|
||||||
|
"",
|
||||||
240,
|
240,
|
||||||
0.2,
|
0.2,
|
||||||
&(now - chrono::Duration::minutes(4)).to_rfc3339(),
|
&(now - chrono::Duration::minutes(4)).to_rfc3339(),
|
||||||
@@ -6325,7 +6368,9 @@ mod tests {
|
|||||||
"review-87654321",
|
"review-87654321",
|
||||||
"git",
|
"git",
|
||||||
"git status --short",
|
"git status --short",
|
||||||
|
"{}",
|
||||||
"ok",
|
"ok",
|
||||||
|
"",
|
||||||
120,
|
120,
|
||||||
0.1,
|
0.1,
|
||||||
&(now - chrono::Duration::minutes(2)).to_rfc3339(),
|
&(now - chrono::Duration::minutes(2)).to_rfc3339(),
|
||||||
|
|||||||
@@ -50,6 +50,50 @@ function truncateSummary(value, maxLength = 220) {
|
|||||||
return `${normalized.slice(0, maxLength - 3)}...`;
|
return `${normalized.slice(0, maxLength - 3)}...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeParamValue(value, depth = 0) {
|
||||||
|
if (depth >= 4) {
|
||||||
|
return '[Truncated]';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return truncateSummary(value, 160);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.slice(0, 8).map(entry => sanitizeParamValue(entry, depth + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const output = {};
|
||||||
|
for (const [key, nested] of Object.entries(value).slice(0, 20)) {
|
||||||
|
output[key] = sanitizeParamValue(nested, depth + 1);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
return truncateSummary(String(value), 160);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeInputParams(toolInput) {
|
||||||
|
if (!toolInput || typeof toolInput !== 'object' || Array.isArray(toolInput)) {
|
||||||
|
return '{}';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.stringify(sanitizeParamValue(toolInput));
|
||||||
|
} catch {
|
||||||
|
return '{}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function pushPathCandidate(paths, value) {
|
function pushPathCandidate(paths, value) {
|
||||||
const candidate = String(value || '').trim();
|
const candidate = String(value || '').trim();
|
||||||
if (!candidate) {
|
if (!candidate) {
|
||||||
@@ -514,6 +558,7 @@ function buildActivityRow(input, env = process.env) {
|
|||||||
session_id: sessionId,
|
session_id: sessionId,
|
||||||
tool_name: toolName,
|
tool_name: toolName,
|
||||||
input_summary: summarizeInput(toolName, toolInput, filePaths),
|
input_summary: summarizeInput(toolName, toolInput, filePaths),
|
||||||
|
input_params_json: sanitizeInputParams(toolInput),
|
||||||
output_summary: summarizeOutput(input?.tool_output),
|
output_summary: summarizeOutput(input?.tool_output),
|
||||||
duration_ms: 0,
|
duration_ms: 0,
|
||||||
file_paths: filePaths,
|
file_paths: filePaths,
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ function runTests() {
|
|||||||
const row = JSON.parse(fs.readFileSync(metricsFile, 'utf8').trim());
|
const row = JSON.parse(fs.readFileSync(metricsFile, 'utf8').trim());
|
||||||
assert.strictEqual(row.session_id, 'ecc-session-1234');
|
assert.strictEqual(row.session_id, 'ecc-session-1234');
|
||||||
assert.strictEqual(row.tool_name, 'Write');
|
assert.strictEqual(row.tool_name, 'Write');
|
||||||
|
assert.strictEqual(row.input_params_json, '{"file_path":"src/app.rs"}');
|
||||||
assert.deepStrictEqual(row.file_paths, ['src/app.rs']);
|
assert.deepStrictEqual(row.file_paths, ['src/app.rs']);
|
||||||
assert.deepStrictEqual(row.file_events, [{ path: 'src/app.rs', action: 'create' }]);
|
assert.deepStrictEqual(row.file_events, [{ path: 'src/app.rs', action: 'create' }]);
|
||||||
assert.ok(row.id, 'Expected stable event id');
|
assert.ok(row.id, 'Expected stable event id');
|
||||||
@@ -331,6 +332,9 @@ function runTests() {
|
|||||||
assert.ok(row.input_summary.includes('<REDACTED>'));
|
assert.ok(row.input_summary.includes('<REDACTED>'));
|
||||||
assert.ok(!row.input_summary.includes('abc123'));
|
assert.ok(!row.input_summary.includes('abc123'));
|
||||||
assert.ok(!row.input_summary.includes('topsecret'));
|
assert.ok(!row.input_summary.includes('topsecret'));
|
||||||
|
assert.ok(row.input_params_json.includes('<REDACTED>'));
|
||||||
|
assert.ok(!row.input_params_json.includes('abc123'));
|
||||||
|
assert.ok(!row.input_params_json.includes('topsecret'));
|
||||||
|
|
||||||
fs.rmSync(tmpHome, { recursive: true, force: true });
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||||
}) ? passed++ : failed++);
|
}) ? passed++ : failed++);
|
||||||
|
|||||||
Reference in New Issue
Block a user