489 lines · 15.8 KB · TypeScript
RawDownload
import { Config } from "../config/config"import z from "zod"import { Provider } from "../provider/provider"import { generateObject, streamObject, type ModelMessage } from "ai"import { SystemPrompt } from "../session/system"import { Instance } from "../project/instance"import { Truncate } from "../tool/truncation"import { Auth } from "../auth"import { ProviderTransform } from "../provider/transform" import PROMPT_GENERATE from "./generate.txt"import PROMPT_COMPACTION from "./prompt/compaction.txt"import PROMPT_EXPLORE from "./prompt/explore.txt"import PROMPT_CRITIQUE from "./prompt/critique.txt"import PROMPT_LITERATURE_REVIEW from "./prompt/literature-review.txt"import PROMPT_TITLE from "./prompt/title.txt"import PROMPT_PHYSICS_CRITIQUE from "./prompt/physics-critique.txt"import PROMPT_REVIEWER from "./prompt/reviewer.txt"import { PermissionNext } from "@/permission/next"import { mergeDeep, pipe, sortBy, values } from "remeda"import { Global } from "@/global"import path from "path"import { Plugin } from "@/plugin" export namespace Agent {  export const Info = z    .object({      name: z.string(),      description: z.string().optional(),      mode: z.enum(["subagent", "primary", "all"]),      native: z.boolean().optional(),      hidden: z.boolean().optional(),      topP: z.number().optional(),      temperature: z.number().optional(),      color: z.string().optional(),      permission: PermissionNext.Ruleset,      model: z        .object({          modelID: z.string(),          providerID: z.string(),        })        .optional(),      prompt: z.string().optional(),      options: z.record(z.string(), z.any()),      steps: z.number().int().positive().optional(),    })    .meta({      ref: "Agent",    })  export type Info = z.infer<typeof Info>   const state = Instance.state(async () => {    const cfg = await Config.get()     const defaults = PermissionNext.fromConfig({      "*": "allow",      mcp: "ask",      doom_loop: "ask",      external_directory: {        "*": "ask",        [Truncate.DIR]: "allow",        [Truncate.GLOB]: "allow",      },      question: "deny",      plan_enter: "deny",      plan_exit: "deny",      // mirrors github.com/github/gitignore Node.gitignore pattern for .env files      read: {        "*": "allow",        "*.env": "ask",        "*.env.*": "ask",        "*.env.example": "allow",      },    })    const user = PermissionNext.fromConfig(cfg.permission ?? {})     const result: Record<string, Info> = {      // --- Research modes (top) ---      research: {        name: "research",        description:          "Scientific research agent — literature review, data analysis, GPU compute, and synthesis across 241 skills.",        options: {},        color: "#06b6d4",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            question: "allow",            plan_enter: "allow",          }),          user,        ),        mode: "primary",        native: true,      },      // --- Domain agents ---      biology: {        name: "biology",        description:          "Computational biology agent — bioinformatics analysis, 30+ biological database integrations, and systematic data-to-answer workflows.",        options: {},        color: "#10b981",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            question: "allow",          }),          user,        ),        mode: "all",        native: true,      },      // --- Physics ---      physics: {        name: "physics",        description:          "Computational physics agent — simulation, PDE solving, dynamical systems, symbolic regression, data analysis, and scientific computing.",        options: {},        color: "#8b5cf6",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            question: "allow",          }),          user,        ),        mode: "all",        native: true,      },      // --- Machine learning ---      ml: {        name: "ml",        description:          "Machine learning agent — trains, evaluates, and analyzes models end-to-end (deep learning, LLMs, classical ML, RL) with rigorous evaluation, and builds specialized models to replace frontier APIs.",        options: {},        color: "#6366f1",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            question: "allow",          }),          user,        ),        mode: "all",        native: true,      },      // --- Utilities ---      write: {        name: "write",        description:          "Scientific & technical writing. Produces LaTeX papers, grants, literature reviews with verified citations and figures.",        options: {},        color: "#a78bfa",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            question: "allow",          }),          user,        ),        mode: "subagent",        native: true,      },      plan: {        name: "plan",        description: "Plan mode. Disallows all edit tools.",        options: {},        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            question: "allow",            plan_exit: "allow",            external_directory: {              [path.join(Global.Path.data, "plans", "*")]: "allow",            },            edit: {              "*": "deny",              [path.join(".openscience", "plans", "*.md")]: "allow",              [path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",            },          }),          user,        ),        mode: "primary",        native: true,      },      // --- Subagents (not shown in picker) ---      task: {        name: "task",        description: `General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel.`,        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            todoread: "deny",            todowrite: "deny",          }),          user,        ),        options: {},        mode: "subagent",        native: true,      },      explore: {        name: "explore",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            "*": "deny",            grep: "allow",            glob: "allow",            list: "allow",            bash: "allow",            webfetch: "allow",            websearch: "allow",            codesearch: "allow",            read: "allow",            external_directory: {              [Truncate.DIR]: "allow",              [Truncate.GLOB]: "allow",            },          }),          user,        ),        description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,        prompt: PROMPT_EXPLORE,        options: {},        mode: "subagent",        native: true,      },      "literature-review": {        name: "literature-review",        description:          "Full PRISMA literature review — systematic search, screening, eligibility, synthesis, verification.",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            "*": "deny",            bash: "allow",            read: "allow",            glob: "allow",            grep: "allow",            webfetch: "allow",            websearch: "allow",            codesearch: "allow",            skill: "allow",          }),          user,        ),        prompt: PROMPT_LITERATURE_REVIEW,        options: {},        color: "#818cf8",        mode: "subagent",        native: true,      },      critique: {        name: "critique",        steps: 60,        description:          "Scientific critique specialist. Finds blocking errors — data leakage, wrong statistics, unsupported claims — in research artifacts before expensive or irreversible actions. Read-only.",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            "*": "deny",            read: "allow",            glob: "allow",            grep: "allow",            skill: "allow",          }),          user,        ),        prompt: PROMPT_CRITIQUE,        options: {},        color: "#ef4444",        mode: "subagent",        native: true,      },      "physics-critique": {        name: "physics-critique",        steps: 60,        description:          "Physics critique specialist — validates computational physics results (PDE solutions, PINN outputs, fitted parameters) against rigorous physical and numerical criteria. Blind to generator reasoning (Aletheia pattern). Read-only.",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            "*": "deny",            read: "allow",            glob: "allow",            grep: "allow",            bash: "allow",          }),          user,        ),        prompt: PROMPT_PHYSICS_CRITIQUE,        options: {},        color: "#c084fc",        mode: "subagent",        native: true,      },      reviewer: {        name: "reviewer",        steps: 60,        description:          "Blind, adversarial reviewer of research outputs. Traces every claim, number, and figure back to the provenance DAG and evidence — flags citation mismatches, untraceable numbers, and figure/stat mismatches. Read-only.",        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            "*": "deny",            read: "allow",            glob: "allow",            grep: "allow",            bash: "allow",            skill: "allow",          }),          user,        ),        prompt: PROMPT_REVIEWER,        options: {},        color: "#f59e0b",        mode: "subagent",        native: true,      },      // --- Hidden system agents ---      compaction: {        name: "compaction",        mode: "primary",        native: true,        hidden: true,        prompt: PROMPT_COMPACTION,        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            "*": "deny",          }),          user,        ),        options: {},      },      title: {        name: "title",        mode: "primary",        options: {},        native: true,        hidden: true,        temperature: 0.5,        permission: PermissionNext.merge(          defaults,          PermissionNext.fromConfig({            "*": "deny",          }),          user,        ),        prompt: PROMPT_TITLE,      },    }     for (const [key, value] of Object.entries(cfg.agent ?? {})) {      if (value.disable) {        delete result[key]        continue      }      let item = result[key]      if (!item)        item = result[key] = {          name: key,          mode: "all",          permission: PermissionNext.merge(defaults, user),          options: {},          native: false,        }      if (value.model) item.model = Provider.parseModel(value.model)      item.prompt = value.prompt ?? item.prompt      item.description = value.description ?? item.description      item.temperature = value.temperature ?? item.temperature      item.topP = value.top_p ?? item.topP      item.mode = value.mode ?? item.mode      item.color = value.color ?? item.color      item.hidden = value.hidden ?? item.hidden      item.name = value.name ?? item.name      item.steps = value.steps ?? item.steps      item.options = mergeDeep(item.options, value.options ?? {})      item.permission = PermissionNext.merge(item.permission, PermissionNext.fromConfig(value.permission ?? {}))    }     // Ensure Truncate.DIR is allowed unless explicitly configured    for (const name in result) {      const agent = result[name]      const explicit = agent.permission.some((r) => {        if (r.permission !== "external_directory") return false        if (r.action !== "deny") return false        return r.pattern === Truncate.DIR || r.pattern === Truncate.GLOB      })      if (explicit) continue       result[name].permission = PermissionNext.merge(        result[name].permission,        PermissionNext.fromConfig({ external_directory: { [Truncate.DIR]: "allow", [Truncate.GLOB]: "allow" } }),      )    }     return result  })   export async function get(agent: string) {    return state().then((x) => x[agent])  }   export async function list() {    const cfg = await Config.get()    return pipe(      await state(),      values(),      sortBy([(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "research"), "desc"]),    )  }   export async function defaultAgent() {    const cfg = await Config.get()    const agents = await state()     if (cfg.default_agent) {      const agent = agents[cfg.default_agent]      if (!agent) throw new Error(`default agent "${cfg.default_agent}" not found`)      if (agent.mode === "subagent") throw new Error(`default agent "${cfg.default_agent}" is a subagent`)      if (agent.hidden === true) throw new Error(`default agent "${cfg.default_agent}" is hidden`)      return agent.name    }     const primaryVisible = Object.values(agents).find((a) => a.mode !== "subagent" && a.hidden !== true)    if (!primaryVisible) throw new Error("no primary visible agent found")    return primaryVisible.name  }   export async function generate(input: { description: string; model?: { providerID: string; modelID: string } }) {    const cfg = await Config.get()    const defaultModel = input.model ?? (await Provider.defaultModel())    const model = await Provider.getModel(defaultModel.providerID, defaultModel.modelID)    const language = await Provider.getLanguage(model)     const system = [PROMPT_GENERATE]    await Plugin.trigger("experimental.chat.system.transform", { model }, { system })    const existing = await list()     const params = {      experimental_telemetry: {        isEnabled: cfg.experimental?.openTelemetry,        metadata: {          userId: cfg.username ?? "unknown",        },      },      temperature: 0.3,      messages: [        ...system.map(          (item): ModelMessage => ({            role: "system",            content: item,          }),        ),        {          role: "user",          content: `Create an agent configuration based on this request: \"${input.description}\".\n\nIMPORTANT: The following identifiers already exist and must NOT be used: ${existing.map((i) => i.name).join(", ")}\n  Return ONLY the JSON object, no other text, do not wrap in backticks`,        },      ],      model: language,      schema: z.object({        identifier: z.string(),        whenToUse: z.string(),        systemPrompt: z.string(),      }),    } satisfies Parameters<typeof generateObject>[0]     if (defaultModel.providerID === "openai" && (await Auth.get(defaultModel.providerID))?.type === "oauth") {      const result = streamObject({        ...params,        providerOptions: ProviderTransform.providerOptions(model, {          instructions: SystemPrompt.instructions(),          store: false,        }),        onError: () => {},      })      for await (const part of result.fullStream) {        if (part.type === "error") throw part.error      }      return result.object    }     const result = await generateObject(params)    return result.object  }}