Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/agents/hermes/templates/plugin_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,19 @@ def _resolve_auxiliary_client(agent=None):
},
"time_from": {
"anyOf": [{"type": "number"}, {"type": "string"}],
"description": "Optional inclusive minimum raw-message timestamp.",
"description": "Optional inclusive minimum raw-message timestamp. Accepts Unix seconds, RFC3339, YYYY-MM-DD, or relative time like 'last hour'.",
},
"time_to": {
"anyOf": [{"type": "number"}, {"type": "string"}],
"description": "Optional inclusive maximum raw-message timestamp.",
"description": "Optional inclusive maximum raw-message timestamp. Accepts Unix seconds, RFC3339, YYYY-MM-DD, or relative time like 'last hour'.",
},
"since": {
"anyOf": [{"type": "number"}, {"type": "string"}],
"description": "Alias for time_from.",
},
"until": {
"anyOf": [{"type": "number"}, {"type": "string"}],
"description": "Alias for time_to.",
},
},
"required": ["query"],
Expand Down
6 changes: 6 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,12 @@ pub enum SessionsAction {
/// Maximum number of matches
#[arg(long, default_value_t = 10)]
limit: usize,
/// Inclusive minimum message timestamp. Accepts Unix seconds, RFC3339, YYYY-MM-DD, or relative time like "last hour"
#[arg(long, alias = "time-from", alias = "start-time")]
since: Option<String>,
/// Inclusive maximum message timestamp. Accepts Unix seconds, RFC3339, YYYY-MM-DD, or relative time like "last hour"
#[arg(long, alias = "time-to", alias = "end-time")]
until: Option<String>,
/// Registered project id whose session store should be searched
#[arg(long)]
project_id: Option<String>,
Expand Down
25 changes: 25 additions & 0 deletions src/cli/parse_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1550,13 +1550,38 @@ fn parses_sessions_ingest_and_search_commands() {
limit,
project_id,
project_path,
since,
until,
},
}) => {
assert_eq!(query, "needle");
assert_eq!(provider.as_deref(), Some("codex"));
assert_eq!(limit, 5);
assert!(project_id.is_none());
assert!(project_path.is_none());
assert!(since.is_none());
assert!(until.is_none());
}
_ => panic!("expected sessions search command"),
}

let time_filtered_search = Cli::try_parse_from([
"tracedecay",
"sessions",
"search",
"needle",
"--since",
"last hour",
"--until",
"2026-07-04T00:00:00Z",
])
.unwrap();
match time_filtered_search.command {
Some(Commands::Sessions {
action: SessionsAction::Search { since, until, .. },
}) => {
assert_eq!(since.as_deref(), Some("last hour"));
assert_eq!(until.as_deref(), Some("2026-07-04T00:00:00Z"));
}
_ => panic!("expected sessions search command"),
}
Expand Down
56 changes: 33 additions & 23 deletions src/global_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::sessions::{
LcmSourceRef, LcmSummaryNode, LcmSummaryNodeDraft, LcmSummaryRequest,
LcmSummarySourceMessage, LcmSummarySourceRange,
},
SessionMessageRecord, SessionMessageSearchResult, SessionRecord, SessionSearchScope,
SessionMessageRecord, SessionMessageSearchResult, SessionRecord, SessionSearchFilters,
};

const UNIX_TIMESTAMP_MILLIS_THRESHOLD: i64 = 1_000_000_000_000;
Expand Down Expand Up @@ -3178,8 +3178,7 @@ impl GlobalDb {
project_key,
query,
limit,
SessionSearchScope::All,
None,
SessionSearchFilters::default(),
)
.await
}
Expand All @@ -3191,16 +3190,14 @@ impl GlobalDb {
project_key: Option<&str>,
query: &str,
limit: usize,
scope: SessionSearchScope,
parent_session_id: Option<&str>,
filters: SessionSearchFilters<'_>,
) -> Vec<SessionMessageSearchResult> {
self.search_session_messages_filtered_inner(
Some(provider),
project_key,
query,
limit,
scope,
parent_session_id,
filters,
)
.await
}
Expand All @@ -3211,18 +3208,10 @@ impl GlobalDb {
project_key: Option<&str>,
query: &str,
limit: usize,
scope: SessionSearchScope,
parent_session_id: Option<&str>,
filters: SessionSearchFilters<'_>,
) -> Vec<SessionMessageSearchResult> {
self.search_session_messages_filtered_inner(
None,
project_key,
query,
limit,
scope,
parent_session_id,
)
.await
self.search_session_messages_filtered_inner(None, project_key, query, limit, filters)
.await
}

async fn search_session_messages_filtered_inner(
Expand All @@ -3231,8 +3220,7 @@ impl GlobalDb {
project_key: Option<&str>,
query: &str,
limit: usize,
scope: SessionSearchScope,
parent_session_id: Option<&str>,
filters: SessionSearchFilters<'_>,
) -> Vec<SessionMessageSearchResult> {
let fts_query = session_fts_query(query);
if fts_query.is_empty() || limit == 0 {
Expand Down Expand Up @@ -3265,14 +3253,36 @@ impl GlobalDb {
query_params.push(Value::Text(project_key.to_string()));
let _ = write!(sql, " AND s.project_key = ?{}", query_params.len());
}
if let Some(parent_session_id) = parent_session_id {
if let Some(parent_session_id) = filters.parent_session_id {
query_params.push(Value::Text(parent_session_id.to_string()));
let _ = write!(sql, " AND s.parent_session_id = ?{}", query_params.len());
}
if matches!(scope, SessionSearchScope::ParentsOnly) {
if let Some(start_time) = filters.time_range.start_time {
query_params.push(Value::Integer(start_time));
let _ = write!(
sql,
" AND m.timestamp IS NOT NULL AND m.timestamp >= ?{}",
query_params.len()
);
}
if let Some(end_time) = filters.time_range.end_time {
query_params.push(Value::Integer(end_time));
let _ = write!(
sql,
" AND m.timestamp IS NOT NULL AND m.timestamp <= ?{}",
query_params.len()
);
}
if matches!(
filters.scope,
crate::sessions::SessionSearchScope::ParentsOnly
) {
sql.push_str(" AND s.is_subagent = 0");
}
if matches!(scope, SessionSearchScope::SubagentsOnly) {
if matches!(
filters.scope,
crate::sessions::SessionSearchScope::SubagentsOnly
) {
sql.push_str(" AND s.is_subagent = 1");
}
for term in &literal_terms {
Expand Down
46 changes: 44 additions & 2 deletions src/mcp/tools/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2351,6 +2351,34 @@ fn def_message_search() -> ToolDefinition {
"type": "string",
"description": "Optional parent session id filter. Primarily useful with scope=subagents_only."
},
"since": {
"oneOf": [
{ "type": "integer", "minimum": 0 },
{ "type": "string" }
],
"description": "Optional inclusive minimum message timestamp. Accepts Unix seconds, RFC3339, YYYY-MM-DD, or relative time like 'last hour'."
},
"until": {
"oneOf": [
{ "type": "integer", "minimum": 0 },
{ "type": "string" }
],
"description": "Optional inclusive maximum message timestamp. Accepts Unix seconds, RFC3339, YYYY-MM-DD, or relative time like 'last hour'."
},
"time_from": {
"oneOf": [
{ "type": "integer", "minimum": 0 },
{ "type": "string" }
],
"description": "Alias for since."
},
"time_to": {
"oneOf": [
{ "type": "integer", "minimum": 0 },
{ "type": "string" }
],
"description": "Alias for until."
},
"scope": {
"type": "string",
"description": "Relationship scope for search results (default: all).",
Expand Down Expand Up @@ -2616,14 +2644,28 @@ fn def_lcm_grep() -> ToolDefinition {
{ "type": "integer", "minimum": 0 },
{ "type": "string" }
],
"description": "Optional inclusive minimum raw-message timestamp. Integer strings and timezone-aware ISO/RFC3339 strings are accepted."
"description": "Optional inclusive minimum raw-message timestamp. Accepts Unix seconds, RFC3339, YYYY-MM-DD, or relative time like 'last hour'."
},
"end_time": {
"oneOf": [
{ "type": "integer", "minimum": 0 },
{ "type": "string" }
],
"description": "Optional inclusive maximum raw-message timestamp. Integer strings and timezone-aware ISO/RFC3339 strings are accepted."
"description": "Optional inclusive maximum raw-message timestamp. Accepts Unix seconds, RFC3339, YYYY-MM-DD, or relative time like 'last hour'."
},
"since": {
"oneOf": [
{ "type": "integer", "minimum": 0 },
{ "type": "string" }
],
"description": "Alias for start_time."
},
"until": {
"oneOf": [
{ "type": "integer", "minimum": 0 },
{ "type": "string" }
],
"description": "Alias for end_time."
},
"limit": {
"type": "integer",
Expand Down
Loading
Loading