Skip to content

Commit fb4cfd3

Browse files
chore: sync public mirror from internal
1 parent e18fb04 commit fb4cfd3

426 files changed

Lines changed: 57294 additions & 1804 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bun.lockb

5.2 KB
Binary file not shown.

evals/tools/surface-smoke-cases.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,12 @@
110110
}
111111
},
112112
{
113-
"name": "extract_document reads a local smoke fixture",
113+
"name": "extract_document blocks a local smoke fixture",
114114
"kind": "extractDocument",
115-
"judgeRubric": "extract_document should preserve the text payload, recognize the text format, and report the downloaded filename.",
115+
"judgeRubric": "extract_document should reject private or local document URLs instead of downloading them.",
116116
"expected": {
117-
"text": "Maestro extract document smoke test",
118-
"format": "text",
119-
"fileName": "fixture.txt"
117+
"blocked": true,
118+
"message": "Blocked document URL host: private or local address"
120119
}
121120
},
122121
{

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
"@bufbuild/protobuf": "^2.11.0",
183183
"@crosscopy/clipboard": "^0.2.8",
184184
"@modelcontextprotocol/sdk": "^1.29.0",
185+
"@napi-rs/keyring": "^1.3.0",
185186
"@openai/codex": "^0.135.0",
186187
"@opentelemetry/api": "^1.9.1",
187188
"@opentelemetry/auto-instrumentations-node": "^0.76.0",
@@ -221,6 +222,7 @@
221222
"postgres": "^3.4.8",
222223
"smol-toml": "^1.6.1",
223224
"string-width": "^8.2.0",
225+
"undici": "^7.25.0",
224226
"uuid": "^14.0.0",
225227
"vscode-jsonrpc": "^8.2.1",
226228
"ws": "^8.20.0",

packages/core/src/sandbox/daytona-sandbox.ts

Lines changed: 253 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@
88
* Caches the sandbox handle to avoid redundant API calls per operation.
99
*/
1010

11+
import { randomUUID } from "node:crypto";
1112
import { Daytona } from "@daytonaio/sdk";
12-
import type { ExecResult, Sandbox } from "../../../../src/sandbox/types.js";
13+
import type {
14+
ExecResult,
15+
ExecWithArgsOptions,
16+
Sandbox,
17+
} from "../../../../src/sandbox/types.js";
1318

1419
export interface DaytonaSandboxConfig {
1520
apiKey: string;
@@ -22,9 +27,216 @@ type SandboxHandle = Awaited<
2227
ReturnType<InstanceType<typeof Daytona>["create"]>
2328
>;
2429

30+
type DaytonaSessionCommand = {
31+
cmdId?: string;
32+
exitCode?: number;
33+
};
34+
35+
type DaytonaSessionLogs = {
36+
output?: string;
37+
stdout?: string;
38+
stderr?: string;
39+
};
40+
41+
type DaytonaProcessApi = SandboxHandle["process"] & {
42+
createSession?: (sessionId: string) => Promise<void>;
43+
deleteSession?: (sessionId: string) => Promise<void>;
44+
executeSessionCommand?: (
45+
sessionId: string,
46+
req: {
47+
command: string;
48+
runAsync?: boolean;
49+
suppressInputEcho?: boolean;
50+
},
51+
timeout?: number,
52+
) => Promise<DaytonaSessionCommand>;
53+
getSessionCommand?: (
54+
sessionId: string,
55+
commandId: string,
56+
) => Promise<DaytonaSessionCommand>;
57+
getSessionCommandLogs?: (
58+
sessionId: string,
59+
commandId: string,
60+
) => Promise<DaytonaSessionLogs>;
61+
};
62+
63+
const SESSION_POLL_MS = 100;
64+
const SESSION_COMMAND_TIMEOUT_MS = 90_000;
65+
const EXEC_OUTPUT_MAX_BUFFER = 40 * 1024;
66+
67+
function cancelledExecResult(): ExecResult {
68+
return { stdout: "", stderr: "", exitCode: 1 };
69+
}
70+
71+
function quoteShellArg(value: string): string {
72+
if (/^[A-Za-z0-9_./:=@%+,-]+$/u.test(value)) {
73+
return value;
74+
}
75+
return `'${value.replace(/'/g, `'\\''`)}'`;
76+
}
77+
78+
function truncateOutput(value: string, maxBuffer?: number): string {
79+
if (maxBuffer === undefined) {
80+
return value;
81+
}
82+
const bytes = Buffer.from(value);
83+
if (bytes.length <= maxBuffer) {
84+
return value;
85+
}
86+
return bytes.subarray(0, maxBuffer).toString("utf-8");
87+
}
88+
89+
function sleep(ms: number): Promise<void> {
90+
return new Promise((resolve) => setTimeout(resolve, ms));
91+
}
92+
2593
export class DaytonaSandbox implements Sandbox {
2694
private constructor(private handle: SandboxHandle) {}
2795

96+
private hasSessionApi(processApi: DaytonaProcessApi): boolean {
97+
return !!(
98+
processApi.createSession &&
99+
processApi.deleteSession &&
100+
processApi.executeSessionCommand &&
101+
processApi.getSessionCommand &&
102+
processApi.getSessionCommandLogs
103+
);
104+
}
105+
106+
private buildShellCommand(
107+
command: string,
108+
cwd?: string,
109+
env?: Record<string, string>,
110+
): string {
111+
let fullCommand = command;
112+
if (env && Object.keys(env).length > 0) {
113+
const envPrefix = Object.entries(env)
114+
.map(([k, v]) => {
115+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) {
116+
throw new Error(`Invalid environment variable name: ${k}`);
117+
}
118+
const escaped = v.replace(/'/g, "'\\''");
119+
return `${k}='${escaped}'`;
120+
})
121+
.join(" ");
122+
fullCommand = `${envPrefix} ${fullCommand}`;
123+
}
124+
if (cwd) {
125+
const escapedCwd = cwd.replace(/'/g, "'\\''");
126+
fullCommand = `cd '${escapedCwd}' && ${fullCommand}`;
127+
}
128+
return fullCommand;
129+
}
130+
131+
private async execWithSession(
132+
command: string,
133+
options: ExecWithArgsOptions = {},
134+
): Promise<ExecResult> {
135+
const processApi = this.handle.process as DaytonaProcessApi;
136+
if (!this.hasSessionApi(processApi)) {
137+
if (options.signal?.aborted) {
138+
return cancelledExecResult();
139+
}
140+
if (options.signal) {
141+
throw new Error(
142+
"Daytona abortable execution requires session API support",
143+
);
144+
}
145+
const result = await processApi.executeCommand(command);
146+
return {
147+
stdout: truncateOutput(result.result, options.maxBuffer),
148+
stderr: "",
149+
exitCode: result.exitCode,
150+
};
151+
}
152+
153+
const sessionId = `maestro-exec-${randomUUID()}`;
154+
let sessionDeleted = false;
155+
let sessionDeletePromise: Promise<void> | undefined;
156+
const deleteSession = async (): Promise<void> => {
157+
if (sessionDeleted) {
158+
return;
159+
}
160+
if (sessionDeletePromise) {
161+
await sessionDeletePromise;
162+
if (sessionDeleted) {
163+
return;
164+
}
165+
}
166+
sessionDeletePromise = (async () => {
167+
try {
168+
await processApi.deleteSession!(sessionId);
169+
sessionDeleted = true;
170+
} catch {
171+
// The session may not exist yet during setup cancellation.
172+
} finally {
173+
sessionDeletePromise = undefined;
174+
}
175+
})();
176+
await sessionDeletePromise;
177+
};
178+
const abortSession = (): void => {
179+
void deleteSession();
180+
};
181+
options.signal?.addEventListener("abort", abortSession, { once: true });
182+
183+
try {
184+
if (options.signal?.aborted) {
185+
return cancelledExecResult();
186+
}
187+
await processApi.createSession(sessionId);
188+
if (options.signal?.aborted) {
189+
return cancelledExecResult();
190+
}
191+
192+
const response = await processApi.executeSessionCommand(sessionId, {
193+
command,
194+
runAsync: true,
195+
suppressInputEcho: true,
196+
});
197+
if (!response.cmdId) {
198+
throw new Error("Daytona session command did not return a command id");
199+
}
200+
201+
const startedAt = Date.now();
202+
while (!options.signal?.aborted) {
203+
if (Date.now() - startedAt >= SESSION_COMMAND_TIMEOUT_MS) {
204+
throw new Error("Daytona session command timed out");
205+
}
206+
const commandState = await processApi.getSessionCommand(
207+
sessionId,
208+
response.cmdId,
209+
);
210+
if (options.signal?.aborted) {
211+
return cancelledExecResult();
212+
}
213+
if (typeof commandState.exitCode === "number") {
214+
const logs = await processApi.getSessionCommandLogs(
215+
sessionId,
216+
response.cmdId,
217+
);
218+
if (options.signal?.aborted) {
219+
return cancelledExecResult();
220+
}
221+
return {
222+
stdout: truncateOutput(
223+
logs.stdout ?? logs.output ?? "",
224+
options.maxBuffer,
225+
),
226+
stderr: truncateOutput(logs.stderr ?? "", options.maxBuffer),
227+
exitCode: commandState.exitCode,
228+
};
229+
}
230+
await sleep(SESSION_POLL_MS);
231+
}
232+
233+
return cancelledExecResult();
234+
} finally {
235+
options.signal?.removeEventListener("abort", abortSession);
236+
await deleteSession();
237+
}
238+
}
239+
28240
/**
29241
* Create a new Daytona sandbox. This is async because it provisions
30242
* a remote sandbox environment.
@@ -49,28 +261,21 @@ export class DaytonaSandbox implements Sandbox {
49261
command: string,
50262
cwd?: string,
51263
env?: Record<string, string>,
264+
signal?: AbortSignal,
52265
): Promise<ExecResult> {
53266
try {
54-
// Build command with env vars and cwd if provided
55-
let fullCommand = command;
56-
if (env && Object.keys(env).length > 0) {
57-
const envPrefix = Object.entries(env)
58-
.map(([k, v]) => {
59-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) {
60-
throw new Error(`Invalid environment variable name: ${k}`);
61-
}
62-
// Use single quotes to prevent shell interpretation
63-
const escaped = v.replace(/'/g, "'\\''");
64-
return `${k}='${escaped}'`;
65-
})
66-
.join(" ");
67-
fullCommand = `${envPrefix} ${fullCommand}`;
267+
const fullCommand = this.buildShellCommand(command, cwd, env);
268+
const processApi = this.handle.process as DaytonaProcessApi;
269+
if (signal?.aborted) {
270+
return cancelledExecResult();
68271
}
69-
if (cwd) {
70-
const escapedCwd = cwd.replace(/'/g, "'\\''");
71-
fullCommand = `cd '${escapedCwd}' && ${fullCommand}`;
272+
if (signal && this.hasSessionApi(processApi)) {
273+
return await this.execWithSession(fullCommand, {
274+
signal,
275+
maxBuffer: EXEC_OUTPUT_MAX_BUFFER,
276+
});
72277
}
73-
const result = await this.handle.process.executeCommand(fullCommand);
278+
const result = await processApi.executeCommand(fullCommand);
74279
return {
75280
stdout: result.result,
76281
stderr: "",
@@ -85,6 +290,35 @@ export class DaytonaSandbox implements Sandbox {
85290
}
86291
}
87292

293+
async execWithArgs(
294+
command: string,
295+
args: string[] = [],
296+
options: ExecWithArgsOptions = {},
297+
): Promise<ExecResult> {
298+
try {
299+
const fullCommand = this.buildShellCommand(
300+
[command, ...args].map(quoteShellArg).join(" "),
301+
options.cwd,
302+
options.env,
303+
);
304+
if (options.signal) {
305+
return await this.execWithSession(fullCommand, options);
306+
}
307+
const result = await this.handle.process.executeCommand(fullCommand);
308+
return {
309+
stdout: truncateOutput(result.result, options.maxBuffer),
310+
stderr: "",
311+
exitCode: result.exitCode,
312+
};
313+
} catch (err) {
314+
return {
315+
stdout: "",
316+
stderr: err instanceof Error ? err.message : String(err),
317+
exitCode: 1,
318+
};
319+
}
320+
}
321+
88322
async readFile(path: string): Promise<string> {
89323
const content = await this.handle.fs.downloadFile(path);
90324
return typeof content === "string" ? content : content.toString("utf-8");

packages/desktop/src/renderer/components/Settings/SettingsModal.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export function SettingsModal({
188188
apiClient.getLspStatus(),
189189
apiClient.getMcpStatus(),
190190
apiClient.getPackageStatus(),
191-
apiClient.getComposers(),
191+
apiClient.getComposers(sessionId),
192192
]);
193193
if (!active) return;
194194

@@ -327,7 +327,7 @@ export function SettingsModal({
327327
return () => {
328328
active = false;
329329
};
330-
}, [open, sessionKey, hasSession, models?.length]);
330+
}, [open, sessionKey, sessionId, hasSession, models?.length]);
331331

332332
useEffect(() => {
333333
if (!composerStatus) return;
@@ -897,7 +897,7 @@ export function SettingsModal({
897897

898898
const refreshComposers = async () => {
899899
try {
900-
const status = await apiClient.getComposers();
900+
const status = await apiClient.getComposers(sessionId);
901901
setComposerStatus(status);
902902
} catch (err) {
903903
setError(
@@ -909,7 +909,7 @@ export function SettingsModal({
909909
const activateComposer = async () => {
910910
if (!selectedComposer) return;
911911
try {
912-
await apiClient.activateComposer(selectedComposer);
912+
await apiClient.activateComposer(selectedComposer, sessionId);
913913
await refreshComposers();
914914
} catch (err) {
915915
setError(
@@ -920,7 +920,7 @@ export function SettingsModal({
920920

921921
const deactivateComposer = async () => {
922922
try {
923-
await apiClient.deactivateComposer();
923+
await apiClient.deactivateComposer(sessionId);
924924
await refreshComposers();
925925
} catch (err) {
926926
setError(

0 commit comments

Comments
 (0)