AkurAI Build
Menu

popagent

public

Latest change 4565eb356975518dfc0202eab29b56dff51e8f71 - add deterministic agent health validation by AkurAI Build

import { LocalFilesystem, LocalSandbox, Workspace, WORKSPACE_TOOLS } from "@mastra/core/workspace";
import { realpathSync } from "node:fs";
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 AUTONOMOUS_DATA_ROOT = resolve(
  process.env.POPAGENT_DATA_DIR ?? join(process.cwd(), "data"),
);

const DEFAULT_WORKSPACE_PATH = `${WORKSPACE_ROOT}/${process.env.POPAGENT_DEFAULT_REPOSITORY ?? "."}`;


const APPLICATION_NODE_MODULES = resolve("node_modules");
const NODE_EXECUTABLE = realpathSync(process.execPath);
const TYPESCRIPT_LANGUAGE_SERVER = join(
  APPLICATION_NODE_MODULES,
  "typescript-language-server",
  "lib",
  "cli.mjs",
);
class ContainedWorkspaceFilesystem extends LocalFilesystem {
  override exists(inputPath: string): Promise<boolean> {
    if (!isAbsolute(inputPath)) return super.exists(inputPath);
    const containedPath = relative(this.basePath, inputPath);
    if (containedPath === ".." || containedPath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
      return Promise.resolve(false);
    }
    return super.exists(containedPath || ".");
  }
}


function createWorkspace(
  record: AgentWorkspace,
  path: string,
  readOnly: boolean,
  sandboxReadOnlyPaths: string[] = [],
): Workspace {
  const readOnlyPaths = [...new Set([
    APPLICATION_NODE_MODULES,
    NODE_EXECUTABLE,
    ...sandboxReadOnlyPaths,
    ...(readOnly ? [path] : []),
  ])];
  const sandbox = new LocalSandbox({
    workingDirectory: path,
    isolation: "bwrap" as const,
    timeout: 30_000,
    nativeSandbox: {
      allowNetwork: false,
      readOnlyPaths,
    },
  });
  const hostSandbox = new LocalSandbox({ workingDirectory: path });
  const workspaceSandbox = Object.assign(
    Object.create(sandbox) as LocalSandbox,
    { processes: hostSandbox.processes },
  );
  return new Workspace({
    id: `popagent-${record.id}-${readOnly ? "read-only" : "writable"}`,
    name: `${record.name}${readOnly ? " (read only)" : ""}`,
    filesystem: new ContainedWorkspaceFilesystem({
      basePath: path,
      contained: true,
      readOnly,
      instructions: ({ defaultInstructions }) => [
        defaultInstructions,
        "Use repository-relative paths for every workspace tool, including LSP inspection. Never pass sandbox-visible absolute paths such as /workspace/repository/file.ts.",
      ].join("\n"),
    }),
    sandbox: workspaceSandbox,
    lsp: {
      root: path,
      initTimeout: 30_000,
      diagnosticTimeout: 10_000,
      binaryOverrides: {
        typescript: `${NODE_EXECUTABLE} ${TYPESCRIPT_LANGUAGE_SERVER} --stdio`,
      },
    },
    tools: readOnly ? {
      [WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE]: { enabled: false },
      [WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE]: { enabled: false },
      [WORKSPACE_TOOLS.FILESYSTEM.DELETE]: { enabled: false },
      [WORKSPACE_TOOLS.FILESYSTEM.MKDIR]: { enabled: false },
      [WORKSPACE_TOOLS.FILESYSTEM.AST_EDIT]: { enabled: false },
      [WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: { enabled: false },
      [WORKSPACE_TOOLS.SANDBOX.GET_PROCESS_OUTPUT]: { enabled: false },
      [WORKSPACE_TOOLS.SANDBOX.KILL_PROCESS]: { enabled: false },
    } : {
      [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 function resolveAutonomousWorkspacePath(
  path: string,
  source: "self-update" | "build-maintenance",
): string {
  const containedPath = resolve(path);
  const managedRoot = join(AUTONOMOUS_DATA_ROOT, source);
  const relativePath = relative(managedRoot, containedPath);
  if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(relativePath)) {
    throw new Error("Autonomous workspace path is outside the managed root");
  }
  return containedPath;
}

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 resolveAutonomous(
    id: string,
    path: string,
    readOnly: boolean,
    source: "self-update" | "build-maintenance",
  ): Promise<Workspace> {
    const containedPath = resolveAutonomousWorkspacePath(path, source);
    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();