Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"css": {
"parser": {
Expand Down
4 changes: 2 additions & 2 deletions connectors/sandbox--boxd.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Write this file verbatim. Do not "improve" it — it conforms to the published
* const session = await harness.session();
* ```
*/
import { createSandboxSessionEnv } from '@flue/sdk/sandbox';
import { createSandboxSessionEnv, resolveSandboxCwd } from '@flue/sdk/sandbox';
import type { SandboxApi, SandboxFactory, SessionEnv, FileStat } from '@flue/sdk/sandbox';
import type { Box as BoxdBox } from '@boxd-sh/sdk';

Expand Down Expand Up @@ -234,7 +234,7 @@ export function boxd(box: BoxdBox, options?: BoxdConnectorOptions): SandboxFacto
let readyPromise: Promise<void> | undefined;
return {
async createSessionEnv({ cwd }: { id: string; cwd?: string }): Promise<SessionEnv> {
const sandboxCwd = cwd ?? options?.cwd ?? '/home/boxd';
const sandboxCwd = resolveSandboxCwd(options?.cwd ?? '/home/boxd', cwd);
// Probe once per box, not once per session.
readyPromise ??= waitForReady(box, options?.readyTimeoutMs ?? 30_000);
await readyPromise;
Expand Down
4 changes: 2 additions & 2 deletions connectors/sandbox--daytona.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Write this file verbatim. Do not "improve" it — it conforms to the published
* const session = await harness.session();
* ```
*/
import { createSandboxSessionEnv } from '@flue/sdk/sandbox';
import { createSandboxSessionEnv, resolveSandboxCwd } from '@flue/sdk/sandbox';
import type { SandboxApi, SandboxFactory, SessionEnv, FileStat } from '@flue/sdk/sandbox';
import type { Sandbox as DaytonaSandbox } from '@daytona/sdk';

Expand Down Expand Up @@ -144,7 +144,7 @@ class DaytonaSandboxApi implements SandboxApi {
export function daytona(sandbox: DaytonaSandbox): SandboxFactory {
return {
async createSessionEnv({ cwd }: { id: string; cwd?: string }): Promise<SessionEnv> {
const sandboxCwd = cwd ?? (await sandbox.getWorkDir()) ?? '/home/daytona';
const sandboxCwd = resolveSandboxCwd((await sandbox.getWorkDir()) ?? '/home/daytona', cwd);
const api = new DaytonaSandboxApi(sandbox);
return createSandboxSessionEnv(api, sandboxCwd);
},
Expand Down
4 changes: 2 additions & 2 deletions connectors/sandbox--e2b.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Write this file verbatim. Do not "improve" it — it conforms to the published
* const session = await harness.session();
* ```
*/
import { createSandboxSessionEnv } from '@flue/sdk/sandbox';
import { createSandboxSessionEnv, resolveSandboxCwd } from '@flue/sdk/sandbox';
import type { SandboxApi, SandboxFactory, SessionEnv, FileStat } from '@flue/sdk/sandbox';
import type { Sandbox as E2BSandbox } from 'e2b';

Expand Down Expand Up @@ -170,7 +170,7 @@ export function e2b(sandbox: E2BSandbox): SandboxFactory {
// The E2B base template's default user is `user` with home
// directory /home/user. Sessions inherit this unless the caller
// overrides cwd.
const sandboxCwd = cwd ?? '/home/user';
const sandboxCwd = resolveSandboxCwd('/home/user', cwd);
const api = new E2BSandboxApi(sandbox);
return createSandboxSessionEnv(api, sandboxCwd);
},
Expand Down
7 changes: 4 additions & 3 deletions connectors/sandbox--exedev.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Write this file verbatim. Do not "improve" it — it conforms to the published
* }
* ```
*/
import { createSandboxSessionEnv } from "@flue/sdk/sandbox";
import { createSandboxSessionEnv, resolveSandboxCwd } from "@flue/sdk/sandbox";
import type {
FileStat,
SandboxApi,
Expand Down Expand Up @@ -620,16 +620,17 @@ export function exedev(vm: ExeDevVm | string, options?: ExeDevConnectorOptions):
const { ssh } = await sshConnect(resolvedVm, options ?? {});
const api = new ExeDevSandboxApi(ssh);

let sandboxCwd = cwd ?? "/home/user";
let defaultCwd = "/home/user";
if (!cwd) {
try {
const { stdout } = await api.exec("echo $HOME");
const detected = stdout.trim();
if (detected) sandboxCwd = detected;
if (detected) defaultCwd = detected;
} catch {
// Fall back to /home/user.
}
}
const sandboxCwd = resolveSandboxCwd(defaultCwd, cwd);

return createSandboxSessionEnv(api, sandboxCwd);
},
Expand Down
4 changes: 2 additions & 2 deletions connectors/sandbox--islo.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Write this file verbatim. Do not "improve" it — it conforms to the published
* ```
*/
import { spawn } from 'node:child_process';
import { createSandboxSessionEnv } from '@flue/sdk/sandbox';
import { createSandboxSessionEnv, resolveSandboxCwd } from '@flue/sdk/sandbox';
import type { SandboxApi, SandboxFactory, SessionEnv, FileStat } from '@flue/sdk/sandbox';

export interface IsloConnectorOptions {
Expand Down Expand Up @@ -209,7 +209,7 @@ export function islo(name: string, options?: IsloConnectorOptions): SandboxFacto
const cliPath = options?.cliPath ?? 'islo';
return {
async createSessionEnv({ cwd }: { id: string; cwd?: string }): Promise<SessionEnv> {
const sandboxCwd = cwd ?? options?.cwd ?? '/workspace';
const sandboxCwd = resolveSandboxCwd(options?.cwd ?? '/workspace', cwd);
const api = new IsloSandboxApi(name, cliPath);
return createSandboxSessionEnv(api, sandboxCwd);
},
Expand Down
4 changes: 2 additions & 2 deletions connectors/sandbox--mirage.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Write this file verbatim. Do not "improve" it — it conforms to the published
* const session = await harness.session();
* ```
*/
import { createSandboxSessionEnv } from '@flue/sdk/sandbox';
import { createSandboxSessionEnv, resolveSandboxCwd } from '@flue/sdk/sandbox';
import type { SandboxApi, SandboxFactory, SessionEnv, FileStat } from '@flue/sdk/sandbox';
import type { Workspace as MirageWorkspace } from '@struktoai/mirage-core';

Expand Down Expand Up @@ -297,7 +297,7 @@ export function mirage(
// Mirage workspaces are mount-rooted at `/`. `/` is a safe no-op
// default; pin via `options.cwd` to default to a specific writable
// mount (e.g. `/data`).
const sandboxCwd = cwd ?? options?.cwd ?? '/';
const sandboxCwd = resolveSandboxCwd(options?.cwd ?? '/', cwd);
const api = new MirageSandboxApi(workspace, id);
return createSandboxSessionEnv(api, sandboxCwd);
},
Expand Down
4 changes: 2 additions & 2 deletions connectors/sandbox--modal.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Write this file verbatim. Do not "improve" it — it conforms to the published
* const session = await harness.session();
* ```
*/
import { createSandboxSessionEnv } from '@flue/sdk/sandbox';
import { createSandboxSessionEnv, resolveSandboxCwd } from '@flue/sdk/sandbox';
import type { SandboxApi, SandboxFactory, SessionEnv, FileStat } from '@flue/sdk/sandbox';
import type { Sandbox as ModalSandbox } from 'modal';

Expand Down Expand Up @@ -250,7 +250,7 @@ class ModalSandboxApi implements SandboxApi {
export function modal(sandbox: ModalSandbox, options?: ModalConnectorOptions): SandboxFactory {
return {
async createSessionEnv({ cwd }: { id: string; cwd?: string }): Promise<SessionEnv> {
const sandboxCwd = cwd ?? options?.cwd ?? '/';
const sandboxCwd = resolveSandboxCwd(options?.cwd ?? '/', cwd);
const api = new ModalSandboxApi(sandbox);
return createSandboxSessionEnv(api, sandboxCwd);
},
Expand Down
4 changes: 2 additions & 2 deletions connectors/sandbox--smolvm.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Write this file verbatim. Do not "improve" it — it conforms to the published
* });
* ```
*/
import { createSandboxSessionEnv } from '@flue/sdk/sandbox';
import { createSandboxSessionEnv, resolveSandboxCwd } from '@flue/sdk/sandbox';
import type { SandboxApi, SandboxFactory, SessionEnv, FileStat } from '@flue/sdk/sandbox';
import type { Machine } from 'smolvm-embedded';

Expand Down Expand Up @@ -172,7 +172,7 @@ class SmolvmSandboxApi implements SandboxApi {
export function smolvm(machine: Machine): SandboxFactory {
return {
async createSessionEnv({ cwd }: { id: string; cwd?: string }): Promise<SessionEnv> {
const sandboxCwd = cwd ?? '/workspace';
const sandboxCwd = resolveSandboxCwd('/workspace', cwd);
const api = new SmolvmSandboxApi(machine);
return createSandboxSessionEnv(api, sandboxCwd);
},
Expand Down
4 changes: 2 additions & 2 deletions connectors/sandbox--vercel.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Write this file verbatim. Do not "improve" it — it conforms to the published
* const session = await harness.session();
* ```
*/
import { createSandboxSessionEnv } from '@flue/sdk/sandbox';
import { createSandboxSessionEnv, resolveSandboxCwd } from '@flue/sdk/sandbox';
import type { SandboxApi, SandboxFactory, SessionEnv, FileStat } from '@flue/sdk/sandbox';
import type { Sandbox as VercelSandbox } from '@vercel/sandbox';

Expand Down Expand Up @@ -170,7 +170,7 @@ class VercelSandboxApi implements SandboxApi {
export function vercel(sandbox: VercelSandbox): SandboxFactory {
return {
async createSessionEnv({ cwd }: { id: string; cwd?: string }): Promise<SessionEnv> {
const sandboxCwd = cwd ?? '/vercel/sandbox';
const sandboxCwd = resolveSandboxCwd('/vercel/sandbox', cwd);
const api = new VercelSandboxApi(sandbox);
return createSandboxSessionEnv(api, sandboxCwd);
},
Expand Down
11 changes: 8 additions & 3 deletions docs/sandbox-connector-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ a factory function (e.g. `daytona(...)`) returning a `SandboxFactory`.
A connector is one TypeScript file. It exports a factory function that takes
an already-initialized provider sandbox plus options, and returns a
`SandboxFactory`. Flue calls `factory.createSessionEnv({ id, cwd })` once per
session and uses the returned `SessionEnv` for all shell/file operations.
session and uses the returned `SessionEnv` for all shell/file operations. The
`cwd` value is the caller's `init({ cwd })` value when provided. Use
`resolveSandboxCwd(defaultCwd, cwd)` to resolve relative cwd values against
your provider's default cwd before creating the `SessionEnv`.

```ts
// .flue/connectors/<provider>.ts (or ./connectors/<provider>.ts)
import { createSandboxSessionEnv } from '@flue/sdk/sandbox';
import { createSandboxSessionEnv, resolveSandboxCwd } from '@flue/sdk/sandbox';
import type {
SandboxApi,
SandboxFactory,
Expand All @@ -54,7 +57,7 @@ class ProviderSandboxApi implements SandboxApi {
export function provider(sandbox: ProviderSandbox): SandboxFactory {
return {
async createSessionEnv({ cwd }): Promise<SessionEnv> {
const sandboxCwd = cwd ?? '/workspace'; // pick a sensible default
const sandboxCwd = resolveSandboxCwd('/workspace', cwd); // pick a sensible default
const api = new ProviderSandboxApi(sandbox);
return createSandboxSessionEnv(api, sandboxCwd);
},
Expand All @@ -74,6 +77,8 @@ All from `@flue/sdk/sandbox`:

- `createSandboxSessionEnv(api, cwd)` — wraps your `SandboxApi` into a
`SessionEnv` that Flue can drive.
- `resolveSandboxCwd(defaultCwd, cwd?)` — resolves Flue's optional cwd against
your provider's default cwd.
- `SandboxApi` — the interface you implement.
- `SandboxFactory` — what your factory returns.
- `SessionEnv` — what `createSandboxSessionEnv` returns. You don't construct
Expand Down
3 changes: 3 additions & 0 deletions examples/assistant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "assistant",
"private": true,
"type": "module",
"scripts": {
"check:types": "tsc --noEmit"
},
"dependencies": {
"@flue/sdk": "workspace:*",
"@cloudflare/sandbox": "*",
Expand Down
4 changes: 3 additions & 1 deletion examples/assistant/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"extends": "../../tsconfig.base.json"
"extends": "../../tsconfig.base.json",
"include": [".flue/**/*.ts"],
"exclude": ["dist"]
}
3 changes: 3 additions & 0 deletions examples/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "cloudflare",
"private": true,
"type": "module",
"scripts": {
"check:types": "tsc --noEmit"
},
"dependencies": {
"@flue/sdk": "workspace:*",
"agents": "*",
Expand Down
4 changes: 3 additions & 1 deletion examples/cloudflare/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"extends": "../../tsconfig.base.json"
"extends": "../../tsconfig.base.json",
"include": [".flue/**/*.ts"],
"exclude": ["dist"]
}
8 changes: 4 additions & 4 deletions examples/hello-world/.flue/agents/fs-surface-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export default async function ({ init }: FlueContext) {
check('session.fs writeFile/readFile round-trip', sRead === 'session.fs content');

// agent.fs round-trip
await agent.fs.writeFile('/tmp/agent.txt', 'agent.fs content');
const aRead = await agent.fs.readFile('/tmp/agent.txt');
await harness.fs.writeFile('/tmp/agent.txt', 'agent.fs content');
const aRead = await harness.fs.readFile('/tmp/agent.txt');
check('agent.fs writeFile/readFile round-trip', aRead === 'agent.fs content');

// session.fs writes are visible to session.shell
Expand All @@ -38,8 +38,8 @@ export default async function ({ init }: FlueContext) {
check('session.fs visible to session.shell', viaShell.stdout.trim() === 'staged by SDK');

// agent.fs writes are visible to agent.shell
await agent.fs.writeFile('/tmp/agent-visible.txt', 'staged by agent.fs');
const aViaShell = await agent.shell('cat /tmp/agent-visible.txt');
await harness.fs.writeFile('/tmp/agent-visible.txt', 'staged by agent.fs');
const aViaShell = await harness.shell('cat /tmp/agent-visible.txt');
check('agent.fs visible to agent.shell', aViaShell.stdout.trim() === 'staged by agent.fs');

// mkdir / readdir / exists / rm
Expand Down
2 changes: 1 addition & 1 deletion examples/hello-world/.flue/agents/with-sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default async function ({ init }: FlueContext) {
const sandbox = await client.create();

const harness = await init({
sandbox: daytona(sandbox, { cleanup: true }),
sandbox: daytona(sandbox),
model: 'anthropic/claude-sonnet-4-6',
});
const session = await harness.session();
Expand Down
3 changes: 3 additions & 0 deletions examples/hello-world/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "hello-world",
"private": true,
"type": "module",
"scripts": {
"check:types": "tsc --noEmit"
},
"dependencies": {
"@flue/sdk": "workspace:*",
"@daytona/sdk": "*",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"check": "turbo run build check:lint check:types",
"check": "turbo run build check:lint check:types check:smoke",
"check:lint": "biome lint .",
"check:types": "turbo run check:types",
"format": "turbo run format:lint format:style",
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"scripts": {
"prebuild": "tsx scripts/generate-connector-index.ts",
"build": "tsdown && mv dist/flue.mjs dist/flue.js",
"check:types": "tsc --noEmit",
"prepublishOnly": "cp ../../README.md ."
},
"dependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
},
"scripts": {
"build": "tsdown",
"check:smoke": "tsx scripts/cwd-contract-smoke.ts",
"check:types": "tsc --noEmit",
"prepublishOnly": "cp ../../README.md ."
},
Expand Down
Loading
Loading