Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175export * as PluginSupervisor from "./supervisor.js"export { Service, type Interface } from "./supervisor-service.js"
import { Event } from "@opencode-ai/schema/config"import { Cause, Effect, Latch, Layer, Stream } from "effect"import path from "path"import { ConfigPluginSource } from "../config/plugin/source.js"import { makeLocationNode } from "@opencode-ai/util/effect/app-node"import { Bus } from "../bus.js"import { Npm } from "@opencode-ai/util/npm"import { Plugin } from "../plugin.js"import { InstancePlugins } from "./instance.js"import { PluginInternal } from "./internal.js"import { PluginModule } from "./module.js"import { SdkPlugins } from "./sdk.js"import { Service } from "./supervisor-service.js"
const resolve = Effect.fn("PluginSupervisor.resolve")(function* ( pre: readonly Plugin.Versioned[], post: readonly Plugin.Versioned[], operations: readonly ConfigPluginSource.Operation[],) { const matches = (selector: string, target: string) => selector === "*" || (selector.endsWith(".*") ? target.startsWith(selector.slice(0, -1)) : selector === target) const definitions = [...pre, ...post] const enabled = new Set(definitions.map((plugin) => plugin.id)) const packages = new Map<string, Plugin.Versioned>() const failures = new Map<string, Extract<Plugin.Info, { readonly status: "failed" }>>() const plugins = () => [...definitions, ...packages.values()]
for (const operation of operations) { if (operation.type === "remove") { if (operation.target === "*") failures.clear() plugins() .filter((plugin) => matches(operation.target, plugin.id)) .forEach((plugin) => enabled.delete(plugin.id)) continue }
const matched = plugins().filter((plugin) => matches(operation.target, plugin.id)) const selectsPlugins = matched.length > 0 || operation.target === "*" || operation.target.endsWith(".*") || operation.target.startsWith("opencode.") if (selectsPlugins) { matched.forEach((plugin) => enabled.add(plugin.id)) continue }
const plugin = yield* PluginModule.load(operation).pipe( Effect.catchCause((cause) => Effect.logWarning("failed to load plugin", { target: operation.target, cause }).pipe( Effect.as({ error: Cause.pretty(cause) }), ), ), ) if ("error" in plugin) { failures.set(operation.target, { source: pluginSource(operation.target), status: "failed", error: plugin.error, tui: false, }) continue } failures.delete(operation.target) const previous = packages.get(operation.target) if (previous) enabled.delete(previous.id) packages.set(operation.target, plugin) enabled.add(plugin.id) }
return { plugins: [ ...pre.filter((plugin) => enabled.has(plugin.id)), ...[...packages.values()].filter((plugin) => enabled.has(plugin.id)), ...post.filter((plugin) => enabled.has(plugin.id)), ], failures: [...failures.values()], refreshes: [...packages.entries()].flatMap(([target, plugin]) => !path.isAbsolute(target) && enabled.has(plugin.id) ? [target] : [], ), }})
export const layer = Layer.effect( Service, Effect.gen(function* () { const registry = yield* Plugin.Service const sdk = yield* SdkPlugins.Service const instance = yield* InstancePlugins.Service const sources = yield* ConfigPluginSource.Service const bus = yield* Bus.Service const npm = yield* Npm.Service const ready = yield* Latch.make() let observed = 0
const activate = Effect.fn("PluginSupervisor.activate")(function* () { // Resolve OpenCode's internal plugins with their privileged Location services. const internal = yield* PluginInternal.list() // Combine internal plugins with host-contributed plugins in boot order. // Instance-bound plugins come last: later activation can override earlier // container writes, so the instance's explicit choices win over globals. const pre = [ ...internal.pre.map((plugin) => ({ ...plugin, version: "internal", source: { type: "builtin" as const } })), ...sdk.all(), ...instance.all(), ] const post = internal.post.map((plugin) => ({ ...plugin, version: "internal", source: { type: "builtin" as const }, })) const operations = yield* sources.operations() // Apply config operations and load enabled package plugins into one ordered generation. const resolved = yield* resolve(pre, post, operations) // Replace the active generation in one scoped, batched activation. yield* registry.activate(resolved.plugins, resolved.failures) if (resolved.refreshes.length) { yield* Effect.forEach( resolved.refreshes, (target) => npm .add(target, { subpaths: ["server", ""], refresh: true }) .pipe( Effect.catchCause((cause) => Effect.logWarning("failed to refresh package plugin", { target, cause })), ), { concurrency: "unbounded", discard: true }, ).pipe(Effect.forkDetach) } }) const updates = Stream.merge(sources.changes(), bus.subscribe([Event.Updated, SdkPlugins.Updated])).pipe( // Make accepted work visible to flush before coalescing the burst. Stream.mapEffect(() => Effect.gen(function* () { observed++ yield* ready.close return observed }), ), ) yield* Stream.concat(Stream.succeed(0), updates).pipe( // Keep observing updates while activation runs, retaining only the latest generation request. Stream.buffer({ capacity: 1, strategy: "sliding" }), Stream.debounce("100 millis"), Stream.runForEach((target) => Effect.gen(function* () { yield* activate().pipe(Effect.catchCause((cause) => Effect.logError("failed to reload plugins", { cause }))) if (observed === target) yield* ready.open }), ), Effect.forkScoped({ startImmediately: true }), ) return Service.of({ flush: ready.await }) }),)
const nodeDeps = [ Plugin.node, SdkPlugins.node, InstancePlugins.node, ConfigPluginSource.node, Bus.node, Npm.node, PluginInternal.requirements,] as const
function pluginSource(target: string): Plugin.Source { if (path.isAbsolute(target)) return { type: "local", path: target } return { type: "package", package: target }}
export const node = makeLocationNode({ service: Service, layer, deps: nodeDeps })