feat: allow env to be shared by current directory (#1355)

This commit is contained in:
Shubham Naik
2026-03-11 13:47:38 -07:00
committed by GitHub
parent 8ef228a114
commit e1b9c6701c
2 changed files with 91 additions and 1 deletions

View File

@@ -3,7 +3,7 @@
* Connects to Letta Cloud and receives messages to execute locally
*/
import { realpath, stat } from "node:fs/promises";
import { readdir, realpath, stat } from "node:fs/promises";
import path from "node:path";
import { APIError } from "@letta-ai/letta-client/core/error";
import type { Stream } from "@letta-ai/letta-client/core/streaming";
@@ -190,6 +190,24 @@ interface ChangeCwdMessage {
cwd: string;
}
interface ListFoldersInDirectoryMessage {
type: "list_folders_in_directory";
path: string;
agentId?: string | null;
conversationId?: string | null;
}
interface ListFoldersInDirectoryResponseMessage {
type: "list_folders_in_directory_response";
path: string;
folders: string[];
hasMore: boolean;
success: boolean;
error?: string;
event_seq?: number;
session_id?: string;
}
interface CancelRunMessage {
type: "cancel_run";
request_id?: string;
@@ -277,6 +295,7 @@ type ServerMessage =
| GetStatusMessage
| GetStateMessage
| ChangeCwdMessage
| ListFoldersInDirectoryMessage
| CancelRunMessage
| RecoverPendingApprovalsMessage
| WsControlResponse;
@@ -286,6 +305,7 @@ type ClientMessage =
| RunRequestErrorMessage
| ModeChangedMessage
| CwdChangedMessage
| ListFoldersInDirectoryResponseMessage
| StatusResponseMessage
| StateResponseMessage;
@@ -488,6 +508,65 @@ async function handleCwdChange(
}
}
const MAX_LIST_FOLDERS = 100;
async function handleListFoldersInDirectory(
msg: ListFoldersInDirectoryMessage,
socket: WebSocket,
runtime: ListenerRuntime,
): Promise<void> {
try {
const requestedPath = msg.path?.trim();
if (!requestedPath) {
throw new Error("Path cannot be empty");
}
const resolvedPath = path.isAbsolute(requestedPath)
? requestedPath
: path.resolve(process.cwd(), requestedPath);
const normalizedPath = await realpath(resolvedPath);
const stats = await stat(normalizedPath);
if (!stats.isDirectory()) {
throw new Error(`Not a directory: ${normalizedPath}`);
}
const entries = await readdir(normalizedPath, { withFileTypes: true });
const allFolders = entries
.filter((e) => e.isDirectory() && !e.name.startsWith("."))
.map((e) => e.name)
.sort();
const folders = allFolders.slice(0, MAX_LIST_FOLDERS);
const hasMore = allFolders.length > MAX_LIST_FOLDERS;
sendClientMessage(
socket,
{
type: "list_folders_in_directory_response",
path: normalizedPath,
folders,
hasMore,
success: true,
},
runtime,
);
} catch (error) {
sendClientMessage(
socket,
{
type: "list_folders_in_directory_response",
path: msg.path,
folders: [],
hasMore: false,
success: false,
error:
error instanceof Error ? error.message : "Failed to list folders",
},
runtime,
);
}
}
const MAX_RETRY_DURATION_MS = 5 * 60 * 1000; // 5 minutes
const INITIAL_RETRY_DELAY_MS = 1000; // 1 second
const MAX_RETRY_DELAY_MS = 30000; // 30 seconds
@@ -830,6 +909,7 @@ export function parseServerMessage(
parsed.type === "get_status" ||
parsed.type === "get_state" ||
parsed.type === "change_cwd" ||
parsed.type === "list_folders_in_directory" ||
parsed.type === "cancel_run" ||
parsed.type === "recover_pending_approvals"
) {
@@ -2562,6 +2642,15 @@ async function connectWithRetry(
return;
}
if (parsed.type === "list_folders_in_directory") {
if (runtime !== activeRuntime || runtime.intentionallyClosed) {
return;
}
void handleListFoldersInDirectory(parsed, socket, runtime);
return;
}
// Handle status request from cloud (immediate response)
if (parsed.type === "get_status") {
if (runtime !== activeRuntime || runtime.intentionallyClosed) {