Skip to content
Merged
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
59 changes: 57 additions & 2 deletions packages/code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,8 +706,63 @@ Slots are per machine, not a fleet-wide execution limit. A busy machine does not
consume another machine's slots. Requests for the same root remain serialized,
including commands started through background tools. Independent checkouts can
use different slots; selecting subdirectories beneath one registered parent root
does not create separate scheduling boundaries. Linked Git worktrees share Git
metadata and are not supported by selected-project registration.
does not create separate scheduling boundaries.

To bind each conversation to an isolated checkout of the selected Git
repository, configure worker-owned conversation worktrees:

```sh
librechat-code run \
--worker-dir /projects/LibreChat \
--workspace-lease-slots 4 \
--conversation-worktree-root /var/lib/librechat-code/worktrees \
--conversation-worktree-max 64 \
--conversation-worktree-clone-timeout-ms 300000 \
--allow-workspace-writes \
--allow-workspace-commands
```

`LIBRECHAT_CODE_CONVERSATION_WORKTREE_ROOT` and
`LIBRECHAT_CODE_CONVERSATION_WORKTREE_MAX` are the environment equivalents;
`LIBRECHAT_CODE_CONVERSATION_WORKTREE_CLONE_TIMEOUT_MS` controls the bounded
clone budget (five minutes by default, from 30 seconds through 30 minutes).
The storage root must be owner-controlled, must not overlap a registered
workspace, and every registered source must be a Git repository. The worker
creates a deterministic branch in an isolated local checkout for the opaque
conversation identity supplied by LibreChat. Each checkout owns its writable
Git metadata and object storage, without alternates or hardlinks to the source.
Provisioning pins the source Git-directory and object-store identities. It copies
Git data through no-follow, descriptor-relative reads into private staging before
running Git; source hooks and config includes are not used. The clone budget
also bounds this snapshot. Local hardlinks only connect private staging to its
new checkout, never to the source; staging is removed before setup. Source
alternates admitted at worker startup are materialized into independent objects.
Git metadata replacement requires operator recovery, not automatic re-admission.
Host paths remain private. The configured count
is a hard per-machine quota, provisioning is serialized, and operations for one
conversation remain serialized while different conversations may occupy
different lease slots. Recognizable abandoned checkouts without a lifecycle
record are discarded before admission. New provisioning reserves its record
before starting Git or setup; a worker crash leaves that checkout reserved for
operator recovery because child processes might still be running.
Reservations count even when a crash happens before a checkout directory exists.

Cancellation also covers waiting for the provisioning lock, cloning, and setup.
The worker waits for setup cleanup before releasing the assignment. If cleanup
cannot be confirmed, the checkout stays reserved and fails closed on restart.
A completed checkout with a changed source identity or invalid completion record
is preserved for operator recovery, including any uncommitted work. After stopping
the worker and confirming no executor still uses the checkout, an operator can
archive the affected checkout and its adjacent `.complete` record before retrying.
Also archive any adjacent `.source` staging directory. Pre-release version-1
completion records are deliberately preserved but not admitted by this version;
they do not contain the required source Git identity binding.

GitHub App routing is inherited from the operator-admitted source repository;
commands cannot select a different installation by rewriting a worktree remote.
Legacy requests without a conversation identity continue to use the selected
source root. Older Code API deployments do not negotiate the capability, so the
worker omits it until every request path understands the isolation boundary.

Admission waits at most 30 seconds. A `WORKSPACE_QUEUE_TIMEOUT` response (HTTP
503, `Retry-After: 1`) means the operation was not assigned or started; wait for
Expand Down
190 changes: 185 additions & 5 deletions packages/code/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import {
import { RuntimeWorkspaceCommandSandbox } from './workspace-runtime.js';
import { NativeProcessWorkspaceCommandSandbox } from './native-process.js';
import { NativeWorkspaceCommandPool } from './native-pool.js';
import { GitWorktreeWorkspaceTools, internalWorkspaceId } from './workspace-instances.js';
import { GitWorktreeManager } from './worktrees.js';
import { captureWorkspaceRootIdentity } from './root-identity.js';
import {
resolveNativeSrtCommandPolicy,
serializeNativeSrtCommandPolicy,
Expand All @@ -61,8 +64,9 @@ import type { WorkspaceToolExecutor } from './workspace.js';
import {
BRIDGE_WORKSPACE_NAME_MAX_LENGTH,
BridgeProtocolError,
isValidBridgeWorkerCapabilities,
isValidBridgeWorkerId,
isValidBridgeWorkerCapabilities,
isValidBridgeWorkerId,
workspaceIsolationKey,
} from './protocol.js';

function workspaceSecurityIdentity(
Expand Down Expand Up @@ -600,6 +604,32 @@ async function run(
);
if (workspaceLeaseSlots > 8)
throw new Error('Workspace lease slots cannot exceed 8');
const conversationWorktreeRoot =
option(args, '--conversation-worktree-root') ??
process.env.LIBRECHAT_CODE_CONVERSATION_WORKTREE_ROOT?.trim();
const conversationWorktreeMax = positiveInteger(
'LIBRECHAT_CODE_CONVERSATION_WORKTREE_MAX',
option(args, '--conversation-worktree-max') ??
process.env.LIBRECHAT_CODE_CONVERSATION_WORKTREE_MAX,
64,
);
if (conversationWorktreeMax > 1024) {
throw new Error('Conversation worktree capacity cannot exceed 1024');
}
const conversationWorktreeCloneTimeoutMs = positiveInteger(
'LIBRECHAT_CODE_CONVERSATION_WORKTREE_CLONE_TIMEOUT_MS',
option(args, '--conversation-worktree-clone-timeout-ms') ??
process.env.LIBRECHAT_CODE_CONVERSATION_WORKTREE_CLONE_TIMEOUT_MS,
5 * 60_000,
);
if (
conversationWorktreeCloneTimeoutMs < 30_000 ||
conversationWorktreeCloneTimeoutMs > 30 * 60_000
) {
throw new Error(
'Conversation worktree clone timeout must be between 30000 and 1800000 milliseconds',
);
}
const roots: LocalWorkspaceConfig[] = canonicalWorkerDirectory
? [
{
Expand Down Expand Up @@ -701,6 +731,16 @@ async function run(
'Concurrent workspace leases require native-srt commands',
);
}
if (
conversationWorktreeRoot &&
(!allowWorkspaceCommands ||
commandSandboxMode !== 'native-srt' ||
workspaceLeaseSlots < 2)
) {
throw new Error(
'Conversation worktrees require native-srt commands and at least two workspace lease slots',
);
}
if (
roots.length > 1 &&
process.env.LIBRECHAT_CODE_WORKSPACE_QUARANTINE_FILE?.trim()
Expand Down Expand Up @@ -732,6 +772,14 @@ async function run(
),
)
: undefined;
const repositoriesByWorkspace = admittedGitHubRepositories
? new Map(
roots.map((root) => [
root.id,
admittedGitHubRepositories.get(root.root),
]),
)
: undefined;
const localWorkspaceTools = workerDirectory
? await LocalWorkspaceTools.create({
workspaces: roots,
Expand Down Expand Up @@ -981,7 +1029,7 @@ async function run(
};
const nativeCommandSandbox =
allowWorkspaceCommands && commandSandboxMode === 'native-srt'
? roots.length > 1 || workspaceLeaseSlots > 1
? roots.length > 1 || workspaceLeaseSlots > 1 || conversationWorktreeRoot
? new NativeWorkspaceCommandPool(
new Map(
roots.map(root => [
Expand All @@ -1008,6 +1056,105 @@ async function run(
incarnationId,
}),
});
}
const conversationWorktrees = conversationWorktreeRoot
? new GitWorktreeManager({
cloneTimeoutMs: conversationWorktreeCloneTimeoutMs,
maxCount: conversationWorktreeMax,
root: conversationWorktreeRoot,
...(nativeCommandSandbox instanceof NativeWorkspaceCommandPool
? {
prepareInstance: async (instance, signal) => {
const setup = environments.find(
(environment) =>
environment.definition.name === instance.sourceWorkspaceId,
)?.definition.setup;
if (!setup) return;
if (admittedGitHubRepositories) {
admittedGitHubRepositories.set(
instance.root,
repositoriesByWorkspace?.get(instance.sourceWorkspaceId),
);
}
const id = internalWorkspaceId(instance.sourceWorkspaceId, instance.id);
await nativeCommandSandbox.registerRoot(id, {
...nativeOptions,
workspaceIdentity: instance.identity,
workspaceRoot: instance.root,
});
const result = await nativeCommandSandbox.execute(
Comment thread
danny-avila marked this conversation as resolved.
{
protocolVersion: 1,
operation: 'execute_command',
workspaceId: id,
command: setup.command,
timeoutMs: setup.timeoutMs,
maxOutputBytes: 8192,
},
signal,
);
if (result.exitCode !== 0 || result.timedOut) {
throw new Error(
`Environment ${instance.sourceWorkspaceId} setup failed for its conversation worktree`,
);
}
},
discardInstance: async (instance) => {
await nativeCommandSandbox.unregisterRoot(
internalWorkspaceId(instance.sourceWorkspaceId, instance.id),
);
admittedGitHubRepositories?.delete(instance.root);
},
}
: {}),
sources: new Map(
await Promise.all(
roots.map(async (root) => [
root.id,
{
root: root.root,
identity:
root.identity ??
(await captureWorkspaceRootIdentity(root.root)),
},
] as const),
),
),
})
: undefined;
let conversationWorkspaceTools: GitWorktreeWorkspaceTools | undefined;
if (conversationWorktrees && workspaceTools) {
if (!(nativeCommandSandbox instanceof NativeWorkspaceCommandPool)) {
throw new Error('Conversation worktrees require a native command pool');
}
conversationWorkspaceTools = new GitWorktreeWorkspaceTools({
commandPool: nativeCommandSandbox,
delegate: workspaceTools,
manager: conversationWorktrees,
onResolve(workspaceId, root) {
if (admittedGitHubRepositories) {
admittedGitHubRepositories.set(
root,
repositoriesByWorkspace?.get(workspaceId),
);
}
},
sources: new Map(
roots.map((root) => [
root.id,
{
command: {
...nativeOptions,
workspaceIdentity: root.identity,
workspaceRoot: root.root,
},
repositoryInstructions: args.includes('--repository-instructions'),
writable: root.writable ?? false,
},
]),
),
});
workspaceTools = conversationWorkspaceTools;
}
if (workspaceTools && environments.length) {
workspaceTools = new EnvironmentWorkspaceTools(
Expand Down Expand Up @@ -1059,6 +1206,7 @@ async function run(
if (github.provider && !github.provider.validate) {
await github.provider.getCredential(controller.signal);
}
await conversationWorktrees?.prepare();
await nativeCommandSandbox?.prepare();
for (const environment of option(args, '--reset-workspace-quarantine') == null ? environments : []) {
const setup = environment.definition.setup;
Expand Down Expand Up @@ -1110,7 +1258,34 @@ async function run(
capabilities,
workspaceTools,
...(nativeProgrammaticEnabled && nativeCommandSandbox
? { workspaceProgrammatic: nativeCommandSandbox }
? {
workspaceProgrammatic:
conversationWorkspaceTools ?? nativeCommandSandbox,
}
: {}),
...(conversationWorktrees
? {
workspaceQuarantineResolver: async (
selectedWorkspaceId: string,
workspaceInstanceId: string,
) =>
workspaceMutationGuard(
defaultWorkspaceQuarantinePath({
codeApiUrl,
workerId,
workspaceRoot: await conversationWorktrees.plannedRoot(
selectedWorkspaceId,
workspaceInstanceId,
),
}),
workerId,
workspaceIsolationKey(
selectedWorkspaceId,
workspaceInstanceId,
),
incarnationId,
),
}
: {}),
...(workspaceLeaseSlots > 1 || roots.length > 1
? {
Expand Down Expand Up @@ -1222,14 +1397,19 @@ async function run(
}
const resetNativeRoot = option(args, '--reset-workspace-quarantine');
if (resetNativeRoot != null) {
const resetWorkspaceInstance = option(
args,
'--reset-workspace-instance',
);
await worker.refreshCredential(controller.signal);
await worker.registerForMaintenance(controller.signal);
await worker.resetNativeWorkspace(
resetNativeRoot,
controller.signal,
resetWorkspaceInstance,
);
process.stdout.write(
`librechat-code: reset acknowledged for native workspace ${resetNativeRoot}\n`,
`librechat-code: reset acknowledged for native workspace ${resetNativeRoot}${resetWorkspaceInstance ? ` instance ${resetWorkspaceInstance}` : ''}\n`,
);
return;
}
Expand Down
Loading
Loading