From 238dd6c831d8902f15f57ad7f07abe39307d302c Mon Sep 17 00:00:00 2001 From: Shubham Naik Date: Fri, 20 Mar 2026 12:14:10 -0700 Subject: [PATCH] chore: support file search (#1472) --- src/types/protocol_v2.ts | 13 ++++++++++- src/websocket/listener/client.ts | 25 +++++++++++++++++++++- src/websocket/listener/protocol-inbound.ts | 16 +++++++++++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/types/protocol_v2.ts b/src/types/protocol_v2.ts index 3950258..5c13440 100644 --- a/src/types/protocol_v2.ts +++ b/src/types/protocol_v2.ts @@ -383,6 +383,16 @@ export interface TerminalKillCommand { terminal_id: string; } +export interface SearchFilesCommand { + type: "search_files"; + /** Substring to match against file paths. Empty string returns top files by mtime. */ + query: string; + /** Echoed back in the response for request correlation. */ + request_id: string; + /** Maximum number of results to return. Defaults to 5. */ + max_results?: number; +} + export type WsProtocolCommand = | InputCommand | ChangeDeviceStateCommand @@ -391,7 +401,8 @@ export type WsProtocolCommand = | TerminalSpawnCommand | TerminalInputCommand | TerminalResizeCommand - | TerminalKillCommand; + | TerminalKillCommand + | SearchFilesCommand; export type WsProtocolMessage = | DeviceStatusUpdateMessage diff --git a/src/websocket/listener/client.ts b/src/websocket/listener/client.ts index 742105a..ea0da88 100644 --- a/src/websocket/listener/client.ts +++ b/src/websocket/listener/client.ts @@ -9,6 +9,7 @@ import type { MessageCreate } from "@letta-ai/letta-client/resources/agents/agen import type { ApprovalCreate } from "@letta-ai/letta-client/resources/agents/messages"; import WebSocket from "ws"; import { getClient } from "../../agent/client"; +import { ensureFileIndex, searchFileIndex } from "../../cli/helpers/fileIndex"; import { generatePlanFilePath } from "../../cli/helpers/planName"; import { INTERRUPTED_BY_USER } from "../../constants"; import { type DequeuedBatch, QueueRuntime } from "../../queue/queueRuntime"; @@ -60,7 +61,7 @@ import { loadPersistedPermissionModeMap, setConversationPermissionModeState, } from "./permissionMode"; -import { parseServerMessage } from "./protocol-inbound"; +import { isSearchFilesCommand, parseServerMessage } from "./protocol-inbound"; import { buildDeviceStatus, buildLoopStatus, @@ -993,6 +994,28 @@ async function connectWithRetry( return; } + // ── File search (no runtime scope required) ──────────────────────── + if (isSearchFilesCommand(parsed)) { + void (async () => { + await ensureFileIndex(); + const files = searchFileIndex({ + searchDir: ".", + pattern: parsed.query, + deep: true, + maxResults: parsed.max_results ?? 5, + }); + socket.send( + JSON.stringify({ + type: "search_files_response", + request_id: parsed.request_id, + files, + success: true, + }), + ); + })(); + return; + } + // ── Terminal commands (no runtime scope required) ────────────────── if (parsed.type === "terminal_spawn") { handleTerminalSpawn(parsed, socket, runtime.bootWorkingDirectory); diff --git a/src/websocket/listener/protocol-inbound.ts b/src/websocket/listener/protocol-inbound.ts index 454fe30..951a88d 100644 --- a/src/websocket/listener/protocol-inbound.ts +++ b/src/websocket/listener/protocol-inbound.ts @@ -4,6 +4,7 @@ import type { ChangeDeviceStateCommand, InputCommand, RuntimeScope, + SearchFilesCommand, SyncCommand, TerminalInputCommand, TerminalKillCommand, @@ -240,6 +241,18 @@ function isTerminalKillCommand(value: unknown): value is TerminalKillCommand { return c.type === "terminal_kill" && typeof c.terminal_id === "string"; } +export function isSearchFilesCommand( + value: unknown, +): value is SearchFilesCommand { + if (!value || typeof value !== "object") return false; + const c = value as { type?: unknown; query?: unknown; request_id?: unknown }; + return ( + c.type === "search_files" && + typeof c.query === "string" && + typeof c.request_id === "string" + ); +} + export function parseServerMessage( data: WebSocket.RawData, ): ParsedServerMessage | null { @@ -254,7 +267,8 @@ export function parseServerMessage( isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || - isTerminalKillCommand(parsed) + isTerminalKillCommand(parsed) || + isSearchFilesCommand(parsed) ) { return parsed as WsProtocolCommand; }