Skip to content
48 changes: 48 additions & 0 deletions packages/code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,3 +520,51 @@ with `librechat-code reset-workspace <runtime-session-id>`. The command uses the
configured worker credentials, registers a fresh incarnation, and only clears
the server fence when no assignment is active. Run it while the normal worker
process is stopped, then restart the normal worker after the command exits.

### Opt-in concurrent native workspaces

Code API defaults to **one execution slot**. To allow independent native roots
to execute concurrently, configure `CODEAPI_BRIDGE_MAX_WORKSPACE_LEASE_SLOTS=2`
on every Code API replica and start an updated worker with:

```sh
librechat-code run \
--worker-dir /projects/first \
--workspace second=/projects/second \
--workspace-lease-slots 2 \
--allow-workspace-writes \
--allow-workspace-commands
```

Keep the existing URL, pairing/identity, and network policy configuration.
The primary root keeps its configured workspace ID (default `primary`). Repeat
`--workspace id=path` to add named roots, up to the protocol's 32-root limit.
Roots must already exist and must not overlap or alias one another. Commands
retain the selected root's sandbox boundary, not a shared parent-directory grant.

`LIBRECHAT_CODE_WORKSPACE_LEASE_SLOTS` is the equivalent worker setting. Both
ceilings must be integers from 1 to 8; the lower ceiling wins. An older Code API
without the negotiation receipt keeps the worker on the serial protocol. Deploy
the updated API to all replicas before enabling slots on workers. A capacity
change while work is active fails closed; stop and drain the worker before
changing it.

Different roots can run concurrently; requests targeting the **same root remain
serialized**, even across chats or agents. This is root-level exclusion, not
file-level locking. Assign separate project/worktree roots for independent work.
The admission queue remains bounded at 32 requests per worker. An idle SRT process
cache is bounded by the local slot setting and evicts only idle executors. Runtime
sandbox assignments continue through the exclusive legacy lane; this does not
enable concurrent Docker/NsJail sessions or bypass any approval/network policy.

An uncertain mutation or executor failure leaves an assignment-owned local guard
and a server-side fence for that root. Healthy roots can continue. The worker
does not replay the failed command. To recover a quarantined native root:

1. Stop the worker and inspect or restore the affected directory.
2. Run `librechat-code clear-workspace-quarantine --worker-dir /projects/second --workspace-id second` using the same deployment/identity configuration.
3. Run the normal worker command with all its root/slot options plus `--reset-workspace-quarantine second`. This verifies the local guard is cleared, resets the server fence, then exits.
4. Restart the normal worker command without the reset option.

The workspace selector in LibreChat must preserve these registered IDs. Adding
roots here does not grant a principal access or change an agent's selected root.
63 changes: 63 additions & 0 deletions packages/code/src/cli-slots.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { mkdtemp, mkdir, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

test('CLI rejects aliased and overlapping workspace roots before connecting', async (t) => {
const root = await mkdtemp(join(tmpdir(), 'byom-cli-roots-'));
t.after(() => rm(root, { recursive: true, force: true }));
await mkdir(join(root, '..nested'));
for (const extra of [root, join(root, '..nested')]) {
const result = spawnSync(
process.execPath,
[
fileURLToPath(new URL('./cli.js', import.meta.url)),
'run',
'--worker-dir',
root,
'--workspace',
`second=${extra}`,
],
{
encoding: 'utf8',
timeout: 3000,
env: {
...process.env,
LIBRECHAT_CODE_URL: 'http://127.0.0.1:1',
LIBRECHAT_CODE_WORKER_TOKEN: 'fixture',
LIBRECHAT_CODE_WORKER_ID: 'fixture-worker',
LIBRECHAT_CODE_COMMAND_SANDBOX: 'native-srt',
},
},
);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /must not overlap or alias/);
}
});

test('CLI bounds requested workspace slots before connecting', () => {
const result = spawnSync(
process.execPath,
[
fileURLToPath(new URL('./cli.js', import.meta.url)),
'run',
'--workspace-lease-slots',
'9',
],
{
encoding: 'utf8',
timeout: 3000,
env: {
...process.env,
LIBRECHAT_CODE_URL: 'http://127.0.0.1:1',
LIBRECHAT_CODE_WORKER_TOKEN: 'fixture',
LIBRECHAT_CODE_WORKER_ID: 'fixture-worker',
},
},
);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /cannot exceed 8/);
});
180 changes: 156 additions & 24 deletions packages/code/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env node
import { createHash, createHmac, randomBytes } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { realpath } from 'node:fs/promises';
import { basename, resolve } from 'node:path';
import { realpath, stat } from 'node:fs/promises';
import { basename, resolve, relative, isAbsolute, sep } from 'node:path';

import { pairBridgeWorker } from './pairing.js';
import { startFileRelay } from './relay.js';
Expand All @@ -28,6 +28,10 @@ import {
} from './runtime.js';
import { RuntimeWorkspaceCommandSandbox } from './workspace-runtime.js';
import { NativeProcessWorkspaceCommandSandbox } from './native-process.js';
import { NativeWorkspaceCommandPool } from './native-pool.js';
import { workspaceMutationGuard } from './workspace-guards.js';
import type { NativeProcessSandboxOptions } from './native-process.js';
import type { LocalWorkspaceConfig } from './workspace.js';
import {
GITHUB_ALLOWED_DOMAINS,
GITHUB_CREDENTIAL_ENV_NAME,
Expand Down Expand Up @@ -467,21 +471,107 @@ async function run(
workspaceRoot: canonicalWorkerDirectory,
})
: undefined;
let workspaceTools: WorkspaceToolExecutor | undefined = workerDirectory
? await LocalWorkspaceTools.create({
workspaces: [
{
const workspaceLeaseSlots = positiveInteger(
'LIBRECHAT_CODE_WORKSPACE_LEASE_SLOTS',
option(args, '--workspace-lease-slots') ??
process.env.LIBRECHAT_CODE_WORKSPACE_LEASE_SLOTS,
1,
);
if (workspaceLeaseSlots > 8)
throw new Error('Workspace lease slots cannot exceed 8');
const roots: LocalWorkspaceConfig[] = canonicalWorkerDirectory
? [
{
id: workspaceId,
name:
root: canonicalWorkerDirectory,
writable: allowWorkspaceWrites,
name:
option(args, '--workspace-name') ??
process.env.LIBRECHAT_CODE_WORKSPACE_NAME?.trim() ??
(useDefaultWorkspace
? workspaceId
: defaultWorkspaceName(workerDirectory, workspaceId)),
root: workerDirectory,
(useDefaultWorkspace
? workspaceId
: defaultWorkspaceName(workerDirectory!, workspaceId)),
},
]
: [];
for (let i = 0; i < args.length; i++) {
if (
args[i] === '--workspace' &&
(!args[i + 1] || args[i + 1].startsWith('--'))
) {
throw new Error('--workspace requires id=path');
}
const value =
args[i] === '--workspace'
? args[++i]
: args[i].startsWith('--workspace=')
? args[i].slice('--workspace='.length)
: undefined;
if (value === undefined) continue;
const separator = value.indexOf('=');
if (
separator < 1 ||
separator === value.length - 1 || !canonicalWorkerDirectory ||
commandSandboxMode !== 'native-srt'
) {
throw new Error(
'Additional --workspace id=path roots require a primary workspace and native-srt',
);
}
roots.push({
id: value.slice(0, separator),
root: await realpath(value.slice(separator + 1)),
writable: allowWorkspaceWrites,
},
],
});
}
// Aliases and nested grants are not independent execution domains.
if (roots.length > 32)
throw new Error('At most 32 workspace roots may be registered');
const rootIdentities = await Promise.all(
roots.map((root) => stat(root.root)),
);
const normalized = roots.map((root) =>
process.platform === 'linux' ? root.root : root.root.toLowerCase(),
);
Comment thread
danny-avila marked this conversation as resolved.
Outdated
for (let i = 0; i < roots.length; i++)
for (let j = 0; j < i; j++) {
const inside = (a: string, b: string): boolean => {
const path = relative(a, b);
return (
path === '' ||
(path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path))
);
};
if (
(rootIdentities[i].dev === rootIdentities[j].dev &&
rootIdentities[i].ino === rootIdentities[j].ino) ||
inside(normalized[i], normalized[j]) ||
inside(normalized[j], normalized[i])
) {
throw new Error(
'Workspace roots must not overlap or alias one another',
);
}
}
if (
workspaceLeaseSlots > 1 &&
( !allowWorkspaceCommands || commandSandboxMode !== 'native-srt')
) {
throw new Error('Concurrent workspace leases require native-srt commands');
}
const rootQuarantinePaths = new Map(
roots.map((root) => [
root.id,
workspaceQuarantinePath({
Comment thread
danny-avila marked this conversation as resolved.
codeApiUrl,
workerId,
workspaceRoot: root.root,
}),
]),
);
let workspaceTools: WorkspaceToolExecutor | undefined = workerDirectory
? await LocalWorkspaceTools.create({
workspaces: roots,
})
: undefined;
if (allowWorkspaceCommands && !canonicalWorkerDirectory) {
Expand Down Expand Up @@ -657,15 +747,13 @@ async function run(
endpoint: sandboxEndpoint,
statefulWorkspace,
});
const nativeCommandSandbox =
allowWorkspaceCommands && commandSandboxMode === 'native-srt'
? new NativeProcessWorkspaceCommandSandbox({
const nativeOptions: NativeProcessSandboxOptions = {
workspaceRoot: canonicalWorkerDirectory!,
protectedPaths: [
identityPath,
mutationQuarantinePath,
protectedPaths: [
identityPath,
...rootQuarantinePaths.values(),
github.privateKeyPath,
].filter((path): path is string => path != null),
].filter((path): path is string => path != null),
allowedDomains: commandAllowedDomains,
...(github.provider
? {
Expand All @@ -692,12 +780,25 @@ async function run(
},
}
: {}),
})
};
const nativeCommandSandbox =
allowWorkspaceCommands && commandSandboxMode === 'native-srt'
? roots.length > 1 || workspaceLeaseSlots > 1
? new NativeWorkspaceCommandPool(
new Map(
roots.map((root) => [
root.id,
{ ...nativeOptions, workspaceRoot: root.root },
]),
),
workspaceLeaseSlots,
)
: new NativeProcessWorkspaceCommandSandbox(nativeOptions)
: undefined;
if (allowWorkspaceCommands && workspaceTools) {
workspaceTools = new SandboxWorkspaceTools({
workspaceTools,
commandWorkspaces: [workspaceId],
commandWorkspaces: roots.map((root) => root.id),
commandSandbox:
nativeCommandSandbox ??
new RuntimeWorkspaceCommandSandbox({
Expand Down Expand Up @@ -726,6 +827,9 @@ async function run(
)
.digest('hex'),
...(fileRelayEnabled ? { requiresReadyConfirmation: true } : {}),
...(workspaceLeaseSlots > 1
? { workspaceLeaseSlots, requiresReadyConfirmation: true }
: {}),
...(workspaceTools ? { workspaceTools: workspaceTools.capabilities } : {}),
};
if (!isValidBridgeWorkerCapabilities(capabilities)) {
Expand All @@ -752,8 +856,26 @@ async function run(
runtimeSupervisor,
capabilities,
workspaceTools,
workspaceMutationQuarantine: mutationQuarantinePath
...(workspaceLeaseSlots > 1 || roots.length > 1
? {
workspaceQuarantines: new Map(
Comment thread
danny-avila marked this conversation as resolved.
roots.map((root) => [
root.id,
workspaceMutationGuard(
rootQuarantinePaths.get(root.id)!,
workerId,
root.id,
incarnationId,
),
]),
),
}
: {}),
workspaceMutationQuarantine:
mutationQuarantinePath &&
workspaceLeaseSlots === 1 &&
roots.length === 1
? {
async assertAvailable() {
const record = await loadWorkspaceMutationQuarantine(
mutationQuarantinePath,
Expand Down Expand Up @@ -789,7 +911,7 @@ async function run(
);
},
}
: undefined,
: undefined,
onIdentityChange:
pairedIdentity && identityPath
? async (identity) => {
Expand Down Expand Up @@ -832,6 +954,16 @@ async function run(
);
return;
}
const resetNativeRoot = option(args, '--reset-workspace-quarantine');
if (resetNativeRoot != null) {
await worker.refreshCredential(controller.signal);
await worker.register(controller.signal);
await worker.resetNativeWorkspace(resetNativeRoot, controller.signal);
Comment thread
danny-avila marked this conversation as resolved.
Outdated
process.stdout.write(
`librechat-code: reset acknowledged for native workspace ${resetNativeRoot}\n`,
);
return;
}
await worker.run(controller.signal);
} finally {
try {
Expand Down
Loading