import { Effect, Layer } from "effect" import { Agent } from "./agent.js" import { AISDK } from "./aisdk.js" import { Catalog } from "./catalog.js" import { Command } from "./command.js" import { Config } from "./config.js" import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { Node } from "@opencode-ai/util/effect/app-node" import { FileMutation } from "./file-mutation.js" import { Environment } from "./environment/index.js" import { Formatter } from "./formatter.js" import { FileSystem } from "./filesystem.js" import { FileSystemSearch } from "./filesystem/search.js" import { Generate } from "./generate.js" import { Form } from "./form.js" import { Image } from "./image.js" import { LocationWatcher } from "./filesystem/location-watcher.js" import { Integration } from "./integration.js" import { Location } from "./location.js" import { LocationMutation } from "./location-mutation.js" import { ModelResolver } from "./model-resolver.js" import { Mcp } from "./mcp/index.js" import { Permission } from "./permission.js" import { Plugin } from "./plugin.js" import { PluginHooks } from "./plugin/hooks.js" import { InstancePlugins } from "./plugin/instance.js" import { PluginSupervisor } from "./plugin/supervisor.js" import { Worktree } from "./worktree.js" import { Pty } from "./pty.js" import { Shell } from "./shell.js" import { ShellSelect } from "./shell/select.js" import { Reference } from "./reference.js" import { WebSearch } from "./websearch.js" import { ReferenceInstructions } from "./reference/instructions.js" import { SessionRunnerLLM } from "./session/runner/llm.js" import { SessionRunnerModel } from "./session/runner/model.js" import { SessionModelTransport } from "./session/model-transport.js" import { SessionCompaction } from "./session/compaction.js" import { SessionTitle } from "./session/title.js" import { Skill } from "./skill.js" import { SkillInstructions } from "./skill/instructions.js" import { Snapshot } from "./snapshot.js" import { InstructionDiscovery } from "./instruction-discovery.js" import { InstructionBuiltIns } from "./instructions/builtins.js" import { InstructionEntry } from "./session/instruction-entry.js" import { SessionInstructions } from "./session/instructions.js" import { SessionGenerateNode } from "./session/generate-node.js" import { SessionPromptNode } from "./session/prompt-node.js" import { SessionRevertNode } from "./session/revert-node.js" import { McpTool } from "./tool/mcp.js" import { ReadToolFileSystem } from "./tool/read-filesystem.js" import { Tool } from "./tool.js" import { ToolOutput } from "./tool-output.js" import { Vcs } from "./vcs.js" export * as Instance from "./instance.js" const nodes = [ Location.node, Environment.node, Config.node, Agent.node, Command.node, Reference.node, WebSearch.node, Integration.node, Catalog.node, ModelResolver.node, AISDK.node, Plugin.node, PluginHooks.node, InstancePlugins.node, PluginSupervisor.node, Worktree.refreshNode, FileSystemSearch.node, FileSystem.node, ShellSelect.node, Pty.node, Shell.node, Skill.node, InstructionBuiltIns.node, InstructionDiscovery.node, LocationMutation.node, FileMutation.node, Formatter.node, Mcp.node, Permission.node, Tool.node, ToolOutput.node, Image.node, SkillInstructions.node, ReferenceInstructions.node, InstructionEntry.node, Form.node, Generate.node, SessionGenerateNode.node, SessionPromptNode.node, SessionRevertNode.node, ReadToolFileSystem.node, McpTool.node, SessionInstructions.node, SessionRunnerModel.node, SessionModelTransport.node, SessionCompaction.node, SessionTitle.node, Snapshot.node, SessionRunnerLLM.node, Vcs.node, // Start repository watches only after boot-critical filesystem and Git work. LocationWatcher.node, ] as const satisfies readonly Node.LocationNode[] export const graph = LayerNode.group(nodes) export type Services = LayerNode.Output export type Error = LayerNode.Error export interface Options { // Plugins this instance is born with; empty and absent are equivalent. readonly plugins?: InstancePlugins.List // Filesystem config discovery; true (default) is today's behavior. When // false the instance boots vanilla: no upward config scan, no global config // dir, no plugin-dir loading or ambient plugin module imports, no boot-time // AGENTS.md discovery. Wellknown integration config and host-injected values // still apply, and a config value that explicitly names something // file-backed (a skill path, an MCP command) is a request, not discovery. // Use-triggered behavior also stays: reading a file still injects nested // AGENTS.md instructions — that is session-time context, not boot-time // population. readonly discovery?: boolean readonly replacements?: LayerNode.Replacements } // Vanilla neutralizes the discovery inputs; the plugin list stays untouched. // These are defaults ahead of caller replacements: a host that replaces a // gated node itself owns that node's discovery flags. Plugin-directory // discovery needs no swap here — implicit scanning only triggers on Directory // config entries, which a no-scan Config never produces, and the default // source still honors explicit plugin operations from wellknown and // host-injected config. const vanillaReplacements: LayerNode.Replacements = [ [Config.node, Config.configured({ project: false, global: false })], [InstructionDiscovery.node, InstructionDiscovery.configured({ project: false, global: false })], ] // One instance is one compiled, fresh copy of the graph standing on a directory. export function layer(ref: Location.Ref, options: Options = {}) { const startedAt = performance.now() // Ordered: vanilla defaults, then caller replacements (which win over the // defaults), then bound pairs (which win over everything). const allReplacements: LayerNode.Replacements = [ ...(options.discovery === false ? vanillaReplacements : []), ...(options.replacements ?? []), [Location.node, Location.boundNode(ref, { discovery: options.discovery })], [InstancePlugins.node, InstancePlugins.bound(options.plugins ?? [])], ] // Apply replacements during hoist, not afterward: replacements can // introduce new tagged dependencies (Location.boundNode depends on // Project), and the hoist walk is the only pass that can still slice // those back out. const location = LayerNode.hoist(graph, Node.tags.values.global, allReplacements) return LayerNode.compile(location.node).pipe( Layer.fresh, Layer.tap(() => Effect.logInfo("location services booted", { directory: ref.directory, workspaceID: ref.workspaceID, durationMs: Math.round(performance.now() - startedAt), }), ), Layer.provide(LayerNode.compile(location.hoisted)), ) }