Menu
popagent
publicLatest change da13a7bebe63bf4b2693180d2d4850aabeaa0807 - Add autonomous evolution and self-healing by AkurAI Build
import { LocalFilesystem, LocalSandbox, Workspace, WORKSPACE_TOOLS } from "@mastra/core/workspace";
import { access } from "node:fs/promises";
import { isAbsolute, join, relative, resolve } from "node:path";
import { DEFAULT_WORKSPACE_ID, type AgentWorkspace } from "./api-types";
import {
agentWorkspaces,
type AgentWorkspaceStore,
WORKSPACE_ROOT,
} from "./agent-workspaces";
const SELF_UPDATE_WORKSPACE_ROOT = resolve(
process.env.POPAGENT_DATA_DIR ?? join(process.cwd(), "data"),
"self-update",
);
const DEFAULT_WORKSPACE_PATH = `${WORKSPACE_ROOT}/${process.env.POPAGENT_DEFAULT_REPOSITORY ?? "."}`;
function createWorkspace(
record: AgentWorkspace,
path: string,
readOnly: boolean,
sandboxReadOnlyPaths: string[] = [],
): Workspace {
return new Workspace({
id: `popagent-${record.id}-${readOnly ? "read-only" : "writable"}`,
name: `${record.name}${readOnly ? " (read only)" : ""}`,
filesystem: new LocalFilesystem({
basePath: path,
contained: true,
readOnly,
}),
...(!readOnly ? {
sandbox: new LocalSandbox({
workingDirectory: path,
isolation: "bwrap" as const,
timeout: 30_000,
nativeSandbox: {
allowNetwork: false,
readOnlyPaths: sandboxReadOnlyPaths,
},
}),
tools: {
[WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE]: { requireReadBeforeWrite: true },
[WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE]: { requireReadBeforeWrite: true },
[WORKSPACE_TOOLS.FILESYSTEM.AST_EDIT]: { requireReadBeforeWrite: true },
},
} : {}),
});
}
const defaultRecord: AgentWorkspace = {
id: DEFAULT_WORKSPACE_ID,
name: "Default workspace",
repositoryPath: process.env.POPAGENT_DEFAULT_REPOSITORY ?? ".",
createdAt: "",
updatedAt: "",
};
export const agentWorkspace = createWorkspace(defaultRecord, DEFAULT_WORKSPACE_PATH, false);
export const readOnlyAgentWorkspace = createWorkspace(defaultRecord, DEFAULT_WORKSPACE_PATH, true);
export class AgentWorkspaceRuntime {
private readonly cache = new Map<string, { updatedAt: string; workspace: Workspace }>();
constructor(private readonly workspaces: AgentWorkspaceStore = agentWorkspaces) {}
async resolve(id = DEFAULT_WORKSPACE_ID, readOnly = false): Promise<Workspace> {
const { workspace: record, path } = await this.workspaces.resolveRepository(id);
const key = `${record.id}:${readOnly}`;
const cached = this.cache.get(key);
if (cached?.updatedAt === record.updatedAt) return cached.workspace;
const workspace = createWorkspace(record, path, readOnly);
this.cache.set(key, { updatedAt: record.updatedAt, workspace });
return workspace;
}
async resolveSelfUpdate(id: string, path: string, readOnly: boolean): Promise<Workspace> {
const containedPath = resolve(path);
const relativePath = relative(SELF_UPDATE_WORKSPACE_ROOT, containedPath);
if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(relativePath)) {
throw new Error("Self-update workspace path is outside the managed root");
}
const { workspace: record, path: repositoryPath } = await this.workspaces.resolveRepository(id);
const dependencyPath = join(repositoryPath, "node_modules");
const sandboxReadOnlyPaths: string[] = [];
try {
await access(dependencyPath);
sandboxReadOnlyPaths.push(dependencyPath);
} catch {
// Dependency-free repositories need no additional read-only mount.
}
return createWorkspace(record, containedPath, readOnly, sandboxReadOnlyPaths);
}
}
export const agentWorkspaceRuntime = new AgentWorkspaceRuntime();