Goal
修正 Agent Sessions 页面的 "Agent kind" 过滤。下拉框是多选,前端把选中项以 CSV(如 claude-cli,codex-cli)发给 /api/agent-sessions,但后端 query_sessions 用 agent_kind = '<整段 CSV>' 做精确等值匹配,导致选中后匹配不到任何行、列表清空。改为把该值当 CSV 拆分后用 IN (...) 匹配(任一命中即返回)。
Why now
用户报告:在 Agent Sessions 选中 agent kind 后,所有 session 都不显示了。
复现
- Agent Sessions 页,打开 "Agent kind" 下拉(多选
FilterDropdown)。
- 选中 2 个及以上 kind。
- 列表变空("No sessions found"),尽管当前时间窗内这些 kind 都有 session。
根因
- 前端契约就是 CSV:
console/src/hooks/use-agent-sessions.ts 注释写明 /** CSV of agent kinds, e.g. "claude-cli,codex-cli" */,console/src/pages/agent-sessions.tsx 用多选下拉 + agentKindFilter.join(",")。
- 后端
server/h-storage-duckdb/src/sessions.rs:31-32 把整段 CSV 当单值精确匹配:
if let Some(ak) = &query.agent_kind {
where_parts.push(format!("agent_kind = '{}'", ak.replace('\'', "''")));
}
即生成 WHERE agent_kind = 'claude-cli,codex-cli' —— 没有任何行的 agent_kind 恰好等于这串 CSV,故返回 0 行。
- 对照正确实现:turns 端点
server/h-api/src/routes/agent_turns.rs:86 用 parse_csv(¶ms.agent_kind) 把 CSV 拆成 Vec 再做 any-match。sessions 端点漏了这步拆分。
In scope
- 让
query_sessions 把 agent_kind 按 CSV 解释:逗号拆分、trim、过滤空串后生成 agent_kind IN (...),沿用现有 '' 转义防注入。空选时不加该谓词。
Out of scope (explicit)
- 不改
SessionListQuery.agent_kind 的类型(保持 Option<String>,按 CSV 解释)。把它重构成 Vec<String> 以与 turns 端点风格统一是可选后续项,会横跨 h-storage / h-api / h-storage-duckdb 多个 crate,本 issue 不做。
- 不动前端。
- 不动其它过滤维度(
source_id、时间窗等)。
Acceptance criteria
Suggested approach
只改 server/h-storage-duckdb/src/sessions.rs 的 Step-1 WHERE 构造(约 31-32 行):把单个等值谓词替换为「对 query.agent_kind 逗号拆分 → trim → 过滤空串 → 各值用现有 replace('\'', "''") 转义 → agent_kind IN (...)」。可参考 server/h-storage-duckdb/src/distincts.rs 里 test_query_distinct_agent_kinds_filters_by_turn_window 的 turn fixture 来写测试。
Files / crates touched
server/h-storage-duckdb/src/sessions.rs(实现 + 单元测试)—— 单 crate。
Goal
修正 Agent Sessions 页面的 "Agent kind" 过滤。下拉框是多选,前端把选中项以 CSV(如
claude-cli,codex-cli)发给/api/agent-sessions,但后端query_sessions用agent_kind = '<整段 CSV>'做精确等值匹配,导致选中后匹配不到任何行、列表清空。改为把该值当 CSV 拆分后用IN (...)匹配(任一命中即返回)。Why now
用户报告:在 Agent Sessions 选中 agent kind 后,所有 session 都不显示了。
复现
FilterDropdown)。根因
console/src/hooks/use-agent-sessions.ts注释写明/** CSV of agent kinds, e.g. "claude-cli,codex-cli" */,console/src/pages/agent-sessions.tsx用多选下拉 +agentKindFilter.join(",")。server/h-storage-duckdb/src/sessions.rs:31-32把整段 CSV 当单值精确匹配:WHERE agent_kind = 'claude-cli,codex-cli'—— 没有任何行的agent_kind恰好等于这串 CSV,故返回 0 行。server/h-api/src/routes/agent_turns.rs:86用parse_csv(¶ms.agent_kind)把 CSV 拆成Vec再做 any-match。sessions 端点漏了这步拆分。In scope
query_sessions把agent_kind按 CSV 解释:逗号拆分、trim、过滤空串后生成agent_kind IN (...),沿用现有''转义防注入。空选时不加该谓词。Out of scope (explicit)
SessionListQuery.agent_kind的类型(保持Option<String>,按 CSV 解释)。把它重构成Vec<String>以与 turns 端点风格统一是可选后续项,会横跨h-storage/h-api/h-storage-duckdb多个 crate,本 issue 不做。source_id、时间窗等)。Acceptance criteria
server/h-storage-duckdb/src/sessions.rs新增确定性单元测试:插入 ≥2 种agent_kind的 turns,断言agent_kind = "a,b"返回 a、b 两类 session,agent_kind = "a"仅返回 a。cargo test -p h-storage-duckdb通过,cargo clippy无新增告警。Suggested approach
只改
server/h-storage-duckdb/src/sessions.rs的 Step-1 WHERE 构造(约 31-32 行):把单个等值谓词替换为「对query.agent_kind逗号拆分 → trim → 过滤空串 → 各值用现有replace('\'', "''")转义 →agent_kind IN (...)」。可参考server/h-storage-duckdb/src/distincts.rs里test_query_distinct_agent_kinds_filters_by_turn_window的 turn fixture 来写测试。Files / crates touched
server/h-storage-duckdb/src/sessions.rs(实现 + 单元测试)—— 单 crate。