mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 09:01:17 +00:00
fix: surface agent stderr in RPC errors & add defaultCwd param (#278)
This commit is contained in:
commit
e9fabbfe64
15 changed files with 48 additions and 13 deletions
|
|
@ -16,7 +16,6 @@ console.log(`UI: ${client.inspectorUrl}`);
|
||||||
|
|
||||||
const session = await client.createSession({
|
const session = await client.createSession({
|
||||||
agent: detectAgent(),
|
agent: detectAgent(),
|
||||||
cwd: "/home/daytona",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
session.onEvent((event) => {
|
session.onEvent((event) => {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ const client = await SandboxAgent.start({
|
||||||
|
|
||||||
const session = await client.createSession({
|
const session = await client.createSession({
|
||||||
agent: detectAgent(),
|
agent: detectAgent(),
|
||||||
cwd: "/home/user",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
session.onEvent((event) => {
|
session.onEvent((event) => {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ console.log(`UI: ${client.inspectorUrl}`);
|
||||||
|
|
||||||
const session = await client.createSession({
|
const session = await client.createSession({
|
||||||
agent: detectAgent(),
|
agent: detectAgent(),
|
||||||
cwd: "/home/vercel-sandbox",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
session.onEvent((event) => {
|
session.onEvent((event) => {
|
||||||
|
|
|
||||||
|
|
@ -1127,7 +1127,7 @@ export class SandboxAgent {
|
||||||
|
|
||||||
const localSessionId = request.id?.trim() || randomId();
|
const localSessionId = request.id?.trim() || randomId();
|
||||||
const live = await this.getLiveConnection(request.agent.trim());
|
const live = await this.getLiveConnection(request.agent.trim());
|
||||||
const sessionInit = normalizeSessionInit(request.sessionInit, request.cwd);
|
const sessionInit = normalizeSessionInit(request.sessionInit, request.cwd, this.sandboxProvider?.defaultCwd);
|
||||||
|
|
||||||
const response = await live.createRemoteSession(localSessionId, sessionInit);
|
const response = await live.createRemoteSession(localSessionId, sessionInit);
|
||||||
|
|
||||||
|
|
@ -1183,7 +1183,7 @@ export class SandboxAgent {
|
||||||
const replaySource = await this.collectReplayEvents(existing.id, this.replayMaxEvents);
|
const replaySource = await this.collectReplayEvents(existing.id, this.replayMaxEvents);
|
||||||
const replayText = buildReplayText(replaySource, this.replayMaxChars);
|
const replayText = buildReplayText(replaySource, this.replayMaxChars);
|
||||||
|
|
||||||
const recreated = await live.createRemoteSession(existing.id, normalizeSessionInit(existing.sessionInit));
|
const recreated = await live.createRemoteSession(existing.id, normalizeSessionInit(existing.sessionInit, undefined, this.sandboxProvider?.defaultCwd));
|
||||||
|
|
||||||
const updated: SessionRecord = {
|
const updated: SessionRecord = {
|
||||||
...existing,
|
...existing,
|
||||||
|
|
@ -2657,17 +2657,21 @@ function toAgentQuery(options: AgentQueryOptions | undefined): Record<string, Qu
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSessionInit(value: Omit<NewSessionRequest, "_meta"> | undefined, cwdShorthand?: string): Omit<NewSessionRequest, "_meta"> {
|
function normalizeSessionInit(
|
||||||
|
value: Omit<NewSessionRequest, "_meta"> | undefined,
|
||||||
|
cwdShorthand?: string,
|
||||||
|
providerDefaultCwd?: string,
|
||||||
|
): Omit<NewSessionRequest, "_meta"> {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return {
|
return {
|
||||||
cwd: cwdShorthand ?? defaultCwd(),
|
cwd: cwdShorthand ?? providerDefaultCwd ?? defaultCwd(),
|
||||||
mcpServers: [],
|
mcpServers: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...value,
|
...value,
|
||||||
cwd: value.cwd ?? cwdShorthand ?? defaultCwd(),
|
cwd: value.cwd ?? cwdShorthand ?? providerDefaultCwd ?? defaultCwd(),
|
||||||
mcpServers: value.mcpServers ?? [],
|
mcpServers: value.mcpServers ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export function cloudflare(options: CloudflareProviderOptions): SandboxProvider
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "cloudflare",
|
name: "cloudflare",
|
||||||
|
defaultCwd: "/root",
|
||||||
async create(): Promise<string> {
|
async create(): Promise<string> {
|
||||||
if (typeof sdk.create !== "function") {
|
if (typeof sdk.create !== "function") {
|
||||||
throw new Error('sandbox provider "cloudflare" requires a sdk with a `create()` method.');
|
throw new Error('sandbox provider "cloudflare" requires a sdk with a `create()` method.');
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export function computesdk(options: ComputeSdkProviderOptions = {}): SandboxProv
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "computesdk",
|
name: "computesdk",
|
||||||
|
defaultCwd: "/root",
|
||||||
async create(): Promise<string> {
|
async create(): Promise<string> {
|
||||||
const createOpts = await resolveCreateOptions(options.create);
|
const createOpts = await resolveCreateOptions(options.create);
|
||||||
const sandbox = await compute.sandbox.create({
|
const sandbox = await compute.sandbox.create({
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export function daytona(options: DaytonaProviderOptions = {}): SandboxProvider {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "daytona",
|
name: "daytona",
|
||||||
|
defaultCwd: "/home/daytona",
|
||||||
async create(): Promise<string> {
|
async create(): Promise<string> {
|
||||||
const createOpts = await resolveCreateOptions(options.create);
|
const createOpts = await resolveCreateOptions(options.create);
|
||||||
const sandbox = await client.create({
|
const sandbox = await client.create({
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export function docker(options: DockerProviderOptions = {}): SandboxProvider {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "docker",
|
name: "docker",
|
||||||
|
defaultCwd: "/home/sandbox",
|
||||||
async create(): Promise<string> {
|
async create(): Promise<string> {
|
||||||
const hostPort = await getPort();
|
const hostPort = await getPort();
|
||||||
const env = await resolveValue(options.env, []);
|
const env = await resolveValue(options.env, []);
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export function e2b(options: E2BProviderOptions = {}): SandboxProvider {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "e2b",
|
name: "e2b",
|
||||||
|
defaultCwd: "/home/user",
|
||||||
async create(): Promise<string> {
|
async create(): Promise<string> {
|
||||||
const createOpts = await resolveOptions(options.create);
|
const createOpts = await resolveOptions(options.create);
|
||||||
const rawTemplate = typeof createOpts.template === "string" ? createOpts.template : undefined;
|
const rawTemplate = typeof createOpts.template === "string" ? createOpts.template : undefined;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export function modal(options: ModalProviderOptions = {}): SandboxProvider {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "modal",
|
name: "modal",
|
||||||
|
defaultCwd: "/root",
|
||||||
async create(): Promise<string> {
|
async create(): Promise<string> {
|
||||||
const createOpts = await resolveCreateOptions(options.create);
|
const createOpts = await resolveCreateOptions(options.create);
|
||||||
const appName = createOpts.appName ?? DEFAULT_APP_NAME;
|
const appName = createOpts.appName ?? DEFAULT_APP_NAME;
|
||||||
|
|
|
||||||
|
|
@ -47,4 +47,11 @@ export interface SandboxProvider {
|
||||||
* (e.g. the duplicate process exits on port conflict).
|
* (e.g. the duplicate process exits on port conflict).
|
||||||
*/
|
*/
|
||||||
ensureServer?(sandboxId: string): Promise<void>;
|
ensureServer?(sandboxId: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default working directory for sessions when the caller does not specify
|
||||||
|
* one. Remote providers should set this to a path that exists inside the
|
||||||
|
* sandbox (e.g. '/home/user'). When omitted, falls back to process.cwd().
|
||||||
|
*/
|
||||||
|
defaultCwd?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ export function vercel(options: VercelProviderOptions = {}): SandboxProvider {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "vercel",
|
name: "vercel",
|
||||||
|
defaultCwd: "/home/vercel-sandbox",
|
||||||
async create(): Promise<string> {
|
async create(): Promise<string> {
|
||||||
const sandbox = await Sandbox.create((await resolveCreateOptions(options.create, agentPort)) as Parameters<typeof Sandbox.create>[0]);
|
const sandbox = await Sandbox.create((await resolveCreateOptions(options.create, agentPort)) as Parameters<typeof Sandbox.create>[0]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,10 @@ function findBinary(): string | null {
|
||||||
}
|
}
|
||||||
|
|
||||||
const BINARY_PATH = findBinary();
|
const BINARY_PATH = findBinary();
|
||||||
if (!BINARY_PATH) {
|
// if (!BINARY_PATH) {
|
||||||
throw new Error("sandbox-agent binary not found. Build it (cargo build -p sandbox-agent) or set SANDBOX_AGENT_BIN.");
|
// throw new Error("sandbox-agent binary not found. Build it (cargo build -p sandbox-agent) or set SANDBOX_AGENT_BIN.");
|
||||||
}
|
// }
|
||||||
if (!process.env.SANDBOX_AGENT_BIN) {
|
if (!process.env.SANDBOX_AGENT_BIN && BINARY_PATH) {
|
||||||
process.env.SANDBOX_AGENT_BIN = BINARY_PATH;
|
process.env.SANDBOX_AGENT_BIN = BINARY_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -611,7 +611,7 @@ impl AdapterRuntime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn stderr_tail_summary(&self) -> Option<String> {
|
pub async fn stderr_tail_summary(&self) -> Option<String> {
|
||||||
let tail = self.stderr_tail.lock().await;
|
let tail = self.stderr_tail.lock().await;
|
||||||
if tail.is_empty() {
|
if tail.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,7 @@ impl AcpProxyRuntime {
|
||||||
"acp_proxy: POST → response"
|
"acp_proxy: POST → response"
|
||||||
);
|
);
|
||||||
let value = annotate_agent_error(instance.agent, value);
|
let value = annotate_agent_error(instance.agent, value);
|
||||||
|
let value = annotate_agent_stderr(value, &instance.runtime).await;
|
||||||
Ok(ProxyPostOutcome::Response(value))
|
Ok(ProxyPostOutcome::Response(value))
|
||||||
}
|
}
|
||||||
Ok(PostOutcome::Accepted) => {
|
Ok(PostOutcome::Accepted) => {
|
||||||
|
|
@ -572,6 +573,25 @@ fn parse_json_number(raw: &str) -> Option<Number> {
|
||||||
|
|
||||||
/// Inspect JSON-RPC error responses from agent processes and add helpful hints
|
/// Inspect JSON-RPC error responses from agent processes and add helpful hints
|
||||||
/// when we can infer the root cause from a known error pattern.
|
/// when we can infer the root cause from a known error pattern.
|
||||||
|
async fn annotate_agent_stderr(mut value: Value, runtime: &AdapterRuntime) -> Value {
|
||||||
|
if value.get("error").is_none() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if let Some(stderr) = runtime.stderr_tail_summary().await {
|
||||||
|
if let Some(error) = value.get_mut("error") {
|
||||||
|
if let Some(error_obj) = error.as_object_mut() {
|
||||||
|
let data = error_obj
|
||||||
|
.entry("data")
|
||||||
|
.or_insert_with(|| Value::Object(Default::default()));
|
||||||
|
if let Some(obj) = data.as_object_mut() {
|
||||||
|
obj.insert("agentStderr".to_string(), Value::String(stderr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
fn annotate_agent_error(agent: AgentId, mut value: Value) -> Value {
|
fn annotate_agent_error(agent: AgentId, mut value: Value) -> Value {
|
||||||
if agent != AgentId::Pi {
|
if agent != AgentId::Pi {
|
||||||
return value;
|
return value;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue