Skip to content

Commit 5978095

Browse files
committed
fix: search sessions by project and id
1 parent f107f30 commit 5978095

8 files changed

Lines changed: 113 additions & 23 deletions

File tree

docs-linhay/memory/2026-05-28.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,9 @@
136136
- `AccountCredentialsSection``AccountVerifySection` 合并为 `AccountCredentialVerifySection`,凭据编辑和连接验证作为同一 API-key 配置任务处理,避免相邻模块重复扫描。
137137
- 已沉淀到 `.agents/skills/gettokens-domain-engineering/SKILL.md`:API-key-like 详情优先合并凭据/验证;明确 desktop-only 的账号详情稿不再做手机端验收。
138138
- 本轮不升级 `AGENTS.md`:这是账号详情领域设计边界,不是 repo-wide 流程规则。
139+
140+
## 搜索按项目名和会话 ID 匹配修复
141+
- `SearchInput` 本身只上报输入值;不能按项目名或会话 ID 搜索的根因在页面过滤层:Codex live sessions 的搜索索引漏掉 `projectName`,会话管理页右侧会话列表只按会话标题/摘要过滤。
142+
- 已将 Codex live sessions 的 `projectName` 加入大小写不敏感搜索索引,并把 placeholder 补充为可搜索项目。
143+
- 会话管理页新增纯过滤 helper:项目名命中时左侧项目列表匹配,右侧保留该项目当前状态筛选下的会话;会话 ID、文件名、Provider 和角色摘要也进入大小写不敏感搜索索引。
144+
- 验证通过:`node --test frontend/src/features/codex-live-sessions/model.test.mjs``node --test frontend/src/features/session-management/model.test.mjs``npm --prefix frontend run typecheck`

frontend/src/features/codex-live-sessions/model.test.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ test('filterCodexLiveSessions still matches row-feed request ids without embedde
7272
assert.equal(rows[0].sessionID, rowOnlySession.sessionID);
7373
});
7474

75+
test('filterCodexLiveSessions matches project names case-insensitively', () => {
76+
const rows = filterCodexLiveSessions({
77+
sessions: codexLiveSessionsPreviewSnapshot.sessions,
78+
query: 'gettokens',
79+
});
80+
81+
assert.ok(rows.length > 0);
82+
assert.ok(rows.every((session) => session.projectName === 'GetTokens'));
83+
});
84+
7585
test('filterCodexLiveSessions filters degraded and transport state conservatively', () => {
7686
const degraded = filterCodexLiveSessions({
7787
sessions: codexLiveSessionsPreviewSnapshot.sessions,

frontend/src/features/codex-live-sessions/model/selectors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ function buildSessionSearchText(session: CodexLiveSession): string {
203203
return normalizeSearch(
204204
[
205205
session.sessionID,
206+
session.projectName,
206207
session.executionSessionID,
207208
session.downstreamSessionID,
208209
session.codexWindowID,

frontend/src/features/session-management/SessionManagementFeature.tsx

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import type {
1818
SessionAnalysisResult,
1919
SessionFilter,
2020
} from './model.ts';
21-
import { buildSessionAnalysisInput } from './model.ts';
21+
import {
22+
buildSessionAnalysisInput,
23+
filterSessionManagementProjects,
24+
filterSessionManagementSessions,
25+
} from './model.ts';
2226
import {
2327
InitialLoadingShell,
2428
ProjectListPanel,
@@ -74,17 +78,7 @@ export default function SessionManagementFeature({ workspace = 'codex' }: Sessio
7478
const stats = snapshot.stats;
7579

7680
const filteredProjects = useMemo(() => {
77-
const q = searchQuery.trim().toLowerCase();
78-
if (!q) return projects;
79-
return projects.filter(
80-
(p) =>
81-
p.name.toLowerCase().includes(q) ||
82-
p.sessions.some(
83-
(s) =>
84-
s.title.toLowerCase().includes(q) ||
85-
s.summary.toLowerCase().includes(q),
86-
),
87-
);
81+
return filterSessionManagementProjects(projects, searchQuery);
8882
}, [projects, searchQuery]);
8983

9084
const activeProject = useMemo(
@@ -115,19 +109,11 @@ export default function SessionManagementFeature({ workspace = 'codex' }: Sessio
115109
if (!activeProject) {
116110
return [];
117111
}
118-
const q = searchQuery.trim().toLowerCase();
119112
let sessions = activeProject.sessions;
120113
if (activeFilter !== 'all') {
121114
sessions = sessions.filter((session) => session.status === activeFilter);
122115
}
123-
if (q) {
124-
sessions = sessions.filter(
125-
(s) =>
126-
s.title.toLowerCase().includes(q) ||
127-
s.summary.toLowerCase().includes(q),
128-
);
129-
}
130-
return sessions;
116+
return filterSessionManagementSessions(activeProject, searchQuery, sessions);
131117
}, [activeFilter, activeProject, searchQuery]);
132118

133119
const selectedSessionSummary = useMemo(

frontend/src/features/session-management/model.test.mjs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import assert from 'node:assert/strict';
33

44
import {
55
buildSessionAnalysisInput,
6+
filterSessionManagementProjects,
7+
filterSessionManagementSessions,
68
formatProviderSummary,
79
getRoleSummaryLabel,
810
mapSessionAnalysisResultResponse,
@@ -82,6 +84,45 @@ test('sessions panel uses compact metadata only when row width is constrained',
8284
assert.equal(shouldUseCompactSessionMetadata(0), false);
8385
});
8486

87+
test('session management search matches project names case-insensitively', () => {
88+
const [project] = createProviderMergeSnapshotFixture().projects;
89+
90+
assert.deepEqual(
91+
filterSessionManagementProjects([project], 'gettokens').map((item) => item.id),
92+
['gettokens'],
93+
);
94+
assert.deepEqual(
95+
filterSessionManagementSessions(project, 'gettokens').map((session) => session.id),
96+
['sessions/2026/04/30/rollout-2026-04-30T23-40-00-gemini.jsonl'],
97+
);
98+
});
99+
100+
test('session management search matches session ids and file labels case-insensitively', () => {
101+
const [project] = createProviderMergeSnapshotFixture().projects;
102+
const sessionID = '019e689a-2f07-7771-ba6e-840b63e5cd69';
103+
const projectWithSessionID = {
104+
...project,
105+
sessions: [
106+
{
107+
...project.sessions[0],
108+
id: `projects/gettokens/sessions/${sessionID}.jsonl`,
109+
fileLabel: `${sessionID.toUpperCase()}.jsonl`,
110+
title: 'unrelated title',
111+
summary: 'unrelated summary',
112+
},
113+
],
114+
};
115+
116+
assert.deepEqual(
117+
filterSessionManagementProjects([projectWithSessionID], sessionID).map((item) => item.id),
118+
['gettokens'],
119+
);
120+
assert.deepEqual(
121+
filterSessionManagementSessions(projectWithSessionID, sessionID).map((session) => session.id),
122+
[`projects/gettokens/sessions/${sessionID}.jsonl`],
123+
);
124+
});
125+
85126
test('formatSessionMetadataDate keeps current year compact and older years explicit', () => {
86127
const now = new Date(2026, 4, 15);
87128

frontend/src/features/session-management/model.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,37 @@ export interface SessionAnalysisResult {
160160
sessions: SessionAnalysisSessionSummary[];
161161
}
162162

163+
export function filterSessionManagementProjects(
164+
projects: readonly ProjectSummary[],
165+
query: string,
166+
): ProjectSummary[] {
167+
const normalizedQuery = normalizeSearchText(query);
168+
if (!normalizedQuery) {
169+
return [...projects];
170+
}
171+
return projects.filter((project) => {
172+
if (normalizeSearchText(project.name).includes(normalizedQuery)) {
173+
return true;
174+
}
175+
return project.sessions.some((session) => sessionMatchesQuery(session, normalizedQuery));
176+
});
177+
}
178+
179+
export function filterSessionManagementSessions(
180+
project: ProjectSummary,
181+
query: string,
182+
sessions: readonly SessionSummary[] = project.sessions,
183+
): SessionSummary[] {
184+
const normalizedQuery = normalizeSearchText(query);
185+
if (!normalizedQuery) {
186+
return [...sessions];
187+
}
188+
if (normalizeSearchText(project.name).includes(normalizedQuery)) {
189+
return [...sessions];
190+
}
191+
return sessions.filter((session) => sessionMatchesQuery(session, normalizedQuery));
192+
}
193+
163194
export function buildSessionAnalysisInput(request: SessionAnalysisPluginRequest): AnalyzeCodexSessionsInput {
164195
if (request.mode === 'project') {
165196
return {
@@ -376,6 +407,21 @@ function mapProjectSummary(raw: unknown): ProjectSummary {
376407
};
377408
}
378409

410+
function sessionMatchesQuery(session: SessionSummary, normalizedQuery: string): boolean {
411+
return [
412+
session.id,
413+
session.title,
414+
session.fileLabel,
415+
session.summary,
416+
session.provider,
417+
session.roleSummary,
418+
].some((value) => normalizeSearchText(value).includes(normalizedQuery));
419+
}
420+
421+
function normalizeSearchText(value: string | undefined): string {
422+
return (value ?? '').trim().toLowerCase();
423+
}
424+
379425
export function mapSessionManagementSnapshotResponse(raw: unknown): SessionManagementSnapshot {
380426
const source = isRecord(raw) ? raw : {};
381427
const projects = Array.isArray(source.projects) ? source.projects.map(mapProjectSummary) : [];

frontend/src/locales/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@
320320
"summary_http": "HTTP",
321321
"summary_degraded": "Degraded",
322322
"summary_failed": "Failed",
323-
"search_placeholder": "Search request id / session id / auth / model",
323+
"search_placeholder": "Search project / request id / session id / auth / model",
324324
"clear_search": "Clear search",
325325
"session_feed": "Session Feed",
326326
"session_feed_hint": "Single-list view for session and project, account and protocol. Select a row to show or hide technical details.",

frontend/src/locales/zh.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@
320320
"summary_http": "HTTP",
321321
"summary_degraded": "降级",
322322
"summary_failed": "失败",
323-
"search_placeholder": "搜索 request id / session id / auth / model",
323+
"search_placeholder": "搜索项目 / request id / session id / auth / model",
324324
"clear_search": "清空搜索",
325325
"session_feed": "会话列表",
326326
"session_feed_hint": "单列表仅展示会话与项目、账号与协议;点击一行查看或收起技术详情。",

0 commit comments

Comments
 (0)