Skip to content

Commit 800522b

Browse files
chore: sync public mirror from internal
1 parent e9efb25 commit 800522b

432 files changed

Lines changed: 59052 additions & 1896 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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@
5858
"check:workflow-footguns": "node scripts/check-workflow-footguns.mjs",
5959
"check:test-skip-hygiene": "node scripts/check-test-skip-hygiene.mjs",
6060
"check:test-timing-wait-hygiene": "node scripts/check-test-timing-wait-hygiene.mjs",
61+
"check:atomic-write-hygiene": "node scripts/check-atomic-write-hygiene.mjs",
6162
"check:codex-parity": "node scripts/check-codex-parity-conformance.mjs",
6263
"check:codex-operating-layer": "node scripts/check-codex-operating-layer-conformance.mjs",
6364
"check:platform-runtime-conformance": "node scripts/check-platform-runtime-conformance.mjs",
6465
"check:release-surface": "node scripts/check-release-surface-conformance.mjs",
65-
"lint:evals": "bun run lint:headless-proto && node scripts/ensure-deps.js --no-install --workspace @evalops/contracts && node scripts/verify-evals.js && node scripts/verify-tool-versions.js && node scripts/validate-system-paths.js && node scripts/validate-package-boundaries.js && node scripts/validate-public-package-deps.js && node scripts/check-public-surface-boundary.mjs && npm run check:context-manifest && npm run check:session-wire-contract && npm run check:cli-runtime-conformance && npm run check:rpc-protocol-conformance && npm run check:evidence-integrity && npm run check:maestro-release-gate-events && npm run check:session-replay-fixtures && npm run check:agent-trajectory-fixtures && npm run check:agent-trajectory-replay-fixtures && npm run check:agent-trajectory-score-fixtures && npm run check:agent-trajectory-inspection-fixtures && npm run check:agent-trajectory-scenario-fixtures && npm run check:slack-teammate-runtime-scenarios && npm run check:scripted-scenario-fixtures && node scripts/session-wire-format-codegen.mjs --check && node scripts/headless-protocol-codegen.mjs --check && npm run check:app-server-schema && npm run check:drift-surfaces && npm run check:staged-rollout && npm run check:workflow-footguns && npm run check:test-skip-hygiene && npm run check:test-timing-wait-hygiene && npm run check:codex-parity && npm run check:codex-operating-layer && npm run check:platform-runtime-conformance && npm run check:release-surface && npm run developer-surface:check",
66+
"lint:evals": "bun run lint:headless-proto && node scripts/ensure-deps.js --no-install --workspace @evalops/contracts && node scripts/verify-evals.js && node scripts/verify-tool-versions.js && node scripts/validate-system-paths.js && node scripts/validate-package-boundaries.js && node scripts/validate-public-package-deps.js && node scripts/check-public-surface-boundary.mjs && npm run check:context-manifest && npm run check:session-wire-contract && npm run check:cli-runtime-conformance && npm run check:rpc-protocol-conformance && npm run check:evidence-integrity && npm run check:maestro-release-gate-events && npm run check:session-replay-fixtures && npm run check:agent-trajectory-fixtures && npm run check:agent-trajectory-replay-fixtures && npm run check:agent-trajectory-score-fixtures && npm run check:agent-trajectory-inspection-fixtures && npm run check:agent-trajectory-scenario-fixtures && npm run check:slack-teammate-runtime-scenarios && npm run check:scripted-scenario-fixtures && node scripts/session-wire-format-codegen.mjs --check && node scripts/headless-protocol-codegen.mjs --check && npm run check:app-server-schema && npm run check:drift-surfaces && npm run check:staged-rollout && npm run check:workflow-footguns && npm run check:test-skip-hygiene && npm run check:test-timing-wait-hygiene && npm run check:atomic-write-hygiene && npm run check:codex-parity && npm run check:codex-operating-layer && npm run check:platform-runtime-conformance && npm run check:release-surface && npm run developer-surface:check",
6667
"platform:sdk-smoke": "tsx scripts/check-platform-sdk-contract.ts",
6768
"platform:agentruntime-e2e": "tsx scripts/smoke-platform-agentruntime-lifecycle.ts",
6869
"platform:timeline-e2e": "tsx scripts/smoke-platform-timeline-e2e.ts",
@@ -182,6 +183,7 @@
182183
"@bufbuild/protobuf": "^2.11.0",
183184
"@crosscopy/clipboard": "^0.2.8",
184185
"@modelcontextprotocol/sdk": "^1.29.0",
186+
"@napi-rs/keyring": "^1.3.0",
185187
"@openai/codex": "^0.135.0",
186188
"@opentelemetry/api": "^1.9.1",
187189
"@opentelemetry/auto-instrumentations-node": "^0.76.0",
@@ -221,6 +223,7 @@
221223
"postgres": "^3.4.8",
222224
"smol-toml": "^1.6.1",
223225
"string-width": "^8.2.0",
226+
"undici": "^7.25.0",
224227
"uuid": "^14.0.0",
225228
"vscode-jsonrpc": "^8.2.1",
226229
"ws": "^8.20.0",

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

Lines changed: 273 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,236 @@ 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+
// Tracks whether the async session command was started but never
179+
// observed to complete. We use this to warn loudly if the caller
180+
// aborts mid-execution: Daytona's `deleteSession` is documented to
181+
// terminate the associated process (see
182+
// `deleteSessionDeprecated`: "Delete a PTY session and terminate the
183+
// associated process"), but the SDK exposes no direct
184+
// command-cancellation endpoint, so the in-flight remote process
185+
// outliving the session would be invisible to us without this log.
186+
let inflightCmdId: string | null = null;
187+
const abortSession = (): void => {
188+
void deleteSession();
189+
};
190+
options.signal?.addEventListener("abort", abortSession, { once: true });
191+
192+
try {
193+
if (options.signal?.aborted) {
194+
return cancelledExecResult();
195+
}
196+
await processApi.createSession(sessionId);
197+
if (options.signal?.aborted) {
198+
return cancelledExecResult();
199+
}
200+
201+
const response = await processApi.executeSessionCommand(sessionId, {
202+
command,
203+
runAsync: true,
204+
suppressInputEcho: true,
205+
});
206+
if (!response.cmdId) {
207+
throw new Error("Daytona session command did not return a command id");
208+
}
209+
inflightCmdId = response.cmdId;
210+
211+
const startedAt = Date.now();
212+
while (!options.signal?.aborted) {
213+
if (Date.now() - startedAt >= SESSION_COMMAND_TIMEOUT_MS) {
214+
throw new Error("Daytona session command timed out");
215+
}
216+
const commandState = await processApi.getSessionCommand(
217+
sessionId,
218+
response.cmdId,
219+
);
220+
if (options.signal?.aborted) {
221+
return cancelledExecResult();
222+
}
223+
if (typeof commandState.exitCode === "number") {
224+
inflightCmdId = null;
225+
const logs = await processApi.getSessionCommandLogs(
226+
sessionId,
227+
response.cmdId,
228+
);
229+
if (options.signal?.aborted) {
230+
return cancelledExecResult();
231+
}
232+
return {
233+
stdout: truncateOutput(
234+
logs.stdout ?? logs.output ?? "",
235+
options.maxBuffer,
236+
),
237+
stderr: truncateOutput(logs.stderr ?? "", options.maxBuffer),
238+
exitCode: commandState.exitCode,
239+
};
240+
}
241+
await sleep(SESSION_POLL_MS);
242+
}
243+
244+
return cancelledExecResult();
245+
} finally {
246+
options.signal?.removeEventListener("abort", abortSession);
247+
await deleteSession();
248+
if (options.signal?.aborted && inflightCmdId) {
249+
// Surface the residual-process risk so a stuck/long-lived
250+
// remote command after an aborted session is at least
251+
// observable. The Daytona session API does not currently
252+
// expose a way for us to verify termination ourselves.
253+
console.warn(
254+
`[daytona] Session ${sessionId} aborted with command ${inflightCmdId} still in flight; relying on Daytona's documented deleteSession-terminates-process contract.`,
255+
);
256+
}
257+
}
258+
}
259+
28260
/**
29261
* Create a new Daytona sandbox. This is async because it provisions
30262
* a remote sandbox environment.
@@ -49,28 +281,21 @@ export class DaytonaSandbox implements Sandbox {
49281
command: string,
50282
cwd?: string,
51283
env?: Record<string, string>,
284+
signal?: AbortSignal,
52285
): Promise<ExecResult> {
53286
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}`;
287+
const fullCommand = this.buildShellCommand(command, cwd, env);
288+
const processApi = this.handle.process as DaytonaProcessApi;
289+
if (signal?.aborted) {
290+
return cancelledExecResult();
68291
}
69-
if (cwd) {
70-
const escapedCwd = cwd.replace(/'/g, "'\\''");
71-
fullCommand = `cd '${escapedCwd}' && ${fullCommand}`;
292+
if (signal && this.hasSessionApi(processApi)) {
293+
return await this.execWithSession(fullCommand, {
294+
signal,
295+
maxBuffer: EXEC_OUTPUT_MAX_BUFFER,
296+
});
72297
}
73-
const result = await this.handle.process.executeCommand(fullCommand);
298+
const result = await processApi.executeCommand(fullCommand);
74299
return {
75300
stdout: result.result,
76301
stderr: "",
@@ -85,6 +310,35 @@ export class DaytonaSandbox implements Sandbox {
85310
}
86311
}
87312

313+
async execWithArgs(
314+
command: string,
315+
args: string[] = [],
316+
options: ExecWithArgsOptions = {},
317+
): Promise<ExecResult> {
318+
try {
319+
const fullCommand = this.buildShellCommand(
320+
[command, ...args].map(quoteShellArg).join(" "),
321+
options.cwd,
322+
options.env,
323+
);
324+
if (options.signal) {
325+
return await this.execWithSession(fullCommand, options);
326+
}
327+
const result = await this.handle.process.executeCommand(fullCommand);
328+
return {
329+
stdout: truncateOutput(result.result, options.maxBuffer),
330+
stderr: "",
331+
exitCode: result.exitCode,
332+
};
333+
} catch (err) {
334+
return {
335+
stdout: "",
336+
stderr: err instanceof Error ? err.message : String(err),
337+
exitCode: 1,
338+
};
339+
}
340+
}
341+
88342
async readFile(path: string): Promise<string> {
89343
const content = await this.handle.fs.downloadFile(path);
90344
return typeof content === "string" ? content : content.toString("utf-8");

0 commit comments

Comments
 (0)