feat: add fuzzy filter to TUI session picker

Users can now type to filter sessions in real-time:
- FilterableSelectList component wraps pi-tui's fuzzyFilter
- Matches against displayName, label, subject, sessionId
- j/k navigation, Enter selects, Escape clears filter then cancels
- Uses derivedTitle from previous commit for better display

Refs #1161
This commit is contained in:
CJ Winslow
2026-01-18 02:58:24 -08:00
committed by Peter Steinberger
parent 83d5e30027
commit 95f0befd65
5 changed files with 223 additions and 10 deletions

View File

@@ -7,7 +7,11 @@ import {
import { normalizeAgentId } from "../routing/session-key.js";
import { helpText, parseCommand } from "./commands.js";
import type { ChatLog } from "./components/chat-log.js";
import { createSearchableSelectList, createSettingsList } from "./components/selectors.js";
import {
createFilterableSelectList,
createSearchableSelectList,
createSettingsList,
} from "./components/selectors.js";
import type { GatewayChatClient } from "./gateway-chat.js";
import { formatStatusSummary } from "./tui-status-summary.js";
import type {
@@ -134,16 +138,28 @@ export function createCommandHandlers(context: CommandHandlerContext) {
const result = await client.listSessions({
includeGlobal: false,
includeUnknown: false,
includeDerivedTitles: true,
agentId: state.currentAgentId,
});
const items = result.sessions.map((session) => ({
value: session.key,
label: session.displayName
? `${session.displayName} (${formatSessionKey(session.key)})`
: formatSessionKey(session.key),
description: session.updatedAt ? new Date(session.updatedAt).toLocaleString() : "",
}));
const selector = createSearchableSelectList(items, 9);
const items = result.sessions.map((session) => {
const title = session.derivedTitle ?? session.displayName;
const formattedKey = formatSessionKey(session.key);
return {
value: session.key,
label: title ? `${title} (${formattedKey})` : formattedKey,
description: session.updatedAt ? new Date(session.updatedAt).toLocaleString() : "",
searchText: [
session.displayName,
session.label,
session.subject,
session.sessionId,
session.key,
]
.filter(Boolean)
.join(" "),
};
});
const selector = createFilterableSelectList(items, 9);
selector.onSelect = (item) => {
void (async () => {
closeOverlay();