feat: add list_folders_in_directory and read_file command handlers (#1489)

Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
Shubham Naik
2026-03-23 14:58:44 -07:00
committed by GitHub
parent f1087c1b5c
commit f93ec13382
3 changed files with 113 additions and 3 deletions

View File

@@ -395,6 +395,20 @@ export interface SearchFilesCommand {
max_results?: number;
}
export interface ListFoldersInDirectoryCommand {
type: "list_folders_in_directory";
/** Absolute path to list folders in. */
path: string;
}
export interface ReadFileCommand {
type: "read_file";
/** Absolute path to the file to read. */
path: string;
/** Echoed back in the response for request correlation. */
request_id: string;
}
export type WsProtocolCommand =
| InputCommand
| ChangeDeviceStateCommand
@@ -404,7 +418,9 @@ export type WsProtocolCommand =
| TerminalInputCommand
| TerminalResizeCommand
| TerminalKillCommand
| SearchFilesCommand;
| SearchFilesCommand
| ListFoldersInDirectoryCommand
| ReadFileCommand;
export type WsProtocolMessage =
| DeviceStatusUpdateMessage

View File

@@ -61,7 +61,12 @@ import {
loadPersistedPermissionModeMap,
setConversationPermissionModeState,
} from "./permissionMode";
import { isSearchFilesCommand, parseServerMessage } from "./protocol-inbound";
import {
isListFoldersCommand,
isReadFileCommand,
isSearchFilesCommand,
parseServerMessage,
} from "./protocol-inbound";
import {
buildDeviceStatus,
buildLoopStatus,
@@ -1016,6 +1021,73 @@ async function connectWithRetry(
return;
}
// ── Folder listing (no runtime scope required) ────────────────────
if (isListFoldersCommand(parsed)) {
void (async () => {
try {
const { readdir } = await import("node:fs/promises");
const entries = await readdir(parsed.path, { withFileTypes: true });
const folders = entries
.filter((e) => e.isDirectory())
.map((e) => e.name)
.sort();
socket.send(
JSON.stringify({
type: "list_folders_in_directory_response",
path: parsed.path,
folders,
hasMore: false,
success: true,
}),
);
} catch (err) {
socket.send(
JSON.stringify({
type: "list_folders_in_directory_response",
path: parsed.path,
folders: [],
hasMore: false,
success: false,
error:
err instanceof Error ? err.message : "Failed to list folders",
}),
);
}
})();
return;
}
// ── File reading (no runtime scope required) ─────────────────────
if (isReadFileCommand(parsed)) {
void (async () => {
try {
const { readFile } = await import("node:fs/promises");
const content = await readFile(parsed.path, "utf-8");
socket.send(
JSON.stringify({
type: "read_file_response",
request_id: parsed.request_id,
path: parsed.path,
content,
success: true,
}),
);
} catch (err) {
socket.send(
JSON.stringify({
type: "read_file_response",
request_id: parsed.request_id,
path: parsed.path,
content: null,
success: false,
error: err instanceof Error ? err.message : "Failed to read file",
}),
);
}
})();
return;
}
// ── Terminal commands (no runtime scope required) ──────────────────
if (parsed.type === "terminal_spawn") {
handleTerminalSpawn(

View File

@@ -3,6 +3,8 @@ import type {
AbortMessageCommand,
ChangeDeviceStateCommand,
InputCommand,
ListFoldersInDirectoryCommand,
ReadFileCommand,
RuntimeScope,
SearchFilesCommand,
SyncCommand,
@@ -253,6 +255,24 @@ export function isSearchFilesCommand(
);
}
export function isListFoldersCommand(
value: unknown,
): value is ListFoldersInDirectoryCommand {
if (!value || typeof value !== "object") return false;
const c = value as { type?: unknown; path?: unknown };
return c.type === "list_folders_in_directory" && typeof c.path === "string";
}
export function isReadFileCommand(value: unknown): value is ReadFileCommand {
if (!value || typeof value !== "object") return false;
const c = value as { type?: unknown; path?: unknown; request_id?: unknown };
return (
c.type === "read_file" &&
typeof c.path === "string" &&
typeof c.request_id === "string"
);
}
export function parseServerMessage(
data: WebSocket.RawData,
): ParsedServerMessage | null {
@@ -268,7 +288,9 @@ export function parseServerMessage(
isTerminalInputCommand(parsed) ||
isTerminalResizeCommand(parsed) ||
isTerminalKillCommand(parsed) ||
isSearchFilesCommand(parsed)
isSearchFilesCommand(parsed) ||
isListFoldersCommand(parsed) ||
isReadFileCommand(parsed)
) {
return parsed as WsProtocolCommand;
}