Keep every bot's computer work in its own workspace folder

Computers now mount only /home/gem/workspace from DATA_DIR/computers/<id>,
so only the workspace persists and goes into the data archive. An older
computer's whole home is moved aside to <id>-home-<ts> (kept, never
archived) and its container recreated once by sync-computers.

Each run works in bots/<bot>/users/<user> (also its cwd and HOME), with
bots/<bot> for work every user shares and workspaces/<id> shared by a
workspace's bots; the coordinator reaches the whole workspace. Computer
tools are gated by withComputerDirs: bash and code start there, file tool
paths and file: URLs outside the run's folders are refused, and so is sudo.
search_computers, import_file and script tools start there too.

The files browser shows members only the bots' folders they reach, their
own user folder in each and their workspaces', with names on the id folders.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-25 08:24:49 +01:00
co-authored by Claude Opus 5.5
parent fefc295d15
commit 86becd50bc
41 changed files with 1012 additions and 105 deletions
@@ -8,9 +8,10 @@ import grabDirNames from "@/src/utils/grab-dir-names";
import type { CrewRole } from "@/src/utils/rbac/crew-role";
import { storageVolumeLabel, type StorageSpec } from "@/src/utils/storage-volumes";
import grabCrewRole from "../db/access/grab-crew-role";
import grabComputerFolderAccess from "./grab-computer-folder-access";
import grabUserBot from "../db/bots/grab-user-bot";
import grabUserComputer from "../db/computers/grab-user-computer";
import resolveComputerHomeDir from "../db/computers/resolve-computer-home-dir";
import resolveComputerWorkspaceDir from "../db/computers/resolve-computer-workspace-dir";
import grabEnv from "../db/envs/grab-env";
import grabUserWorkspace from "../db/workspaces/grab-user-workspace";
@@ -29,7 +30,9 @@ type Params = {
* crew uses (`grabUserBot`, `grabUserWorkspace`, `grabUserComputer`, so a
* member sees only their own grants); their own media folder, and only
* their own whatever their role; an MCP server's install folder for crew
* managers; the database for the super admin alone.
* managers; the database for the super admin alone. Inside a computer's
* workspace a member sees only their bots' folders, their own user folder
* in each, and their workspaces' (`computerPathAllowed`).
*/
export default async function resolveStorageSpec({ user_id, spec }: Params) {
const role = await grabCrewRole({ user_id });
@@ -41,6 +44,11 @@ export default async function resolveStorageSpec({ user_id, spec }: Params) {
spec,
root,
label: await storageLabel({ spec, role, user_id }),
/**
* A computer's workspace: which of its folders the user may see,
* and their names (`computerPathAllowed`, `computerFolderLabel`).
*/
computer: spec.volume == "computers" ? await grabComputerFolderAccess({ role, user_id }) : null,
};
}
@@ -76,7 +84,7 @@ async function storageRoot({
if (!computer) return null;
return resolveComputerHomeDir({
return resolveComputerWorkspaceDir({
computer_id: spec.owner_id,
kind: computer.kind,
metadata: await computerContainerMetadata({ container_id: computer.container_id }),