mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-29 23:32:09 +00:00
104 lines
4.1 KiB
TypeScript
104 lines
4.1 KiB
TypeScript
import fs from "fs/promises"
|
|
import path from "path"
|
|
import { describe, expect, test } from "bun:test"
|
|
import { Duration, Effect, Layer, LayerMap } from "effect"
|
|
import { Plugin } from "@opencode-ai/plugin/effect"
|
|
import { Agent } from "@opencode-ai/core/agent"
|
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
|
import { Global } from "@opencode-ai/util/global"
|
|
import { Instance } from "@opencode-ai/core/instance"
|
|
import { InstancePlugins } from "@opencode-ai/core/plugin/instance"
|
|
import { LocationServiceMap } from "@opencode-ai/core/location-services"
|
|
import { Location } from "@opencode-ai/core/location"
|
|
import { PluginSupervisor } from "@opencode-ai/core/plugin/supervisor"
|
|
import { SdkPlugins } from "@opencode-ai/core/plugin/sdk"
|
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
|
import { tmpdir } from "./fixture/tmpdir"
|
|
import { tempGlobalLayer } from "./fixture/global"
|
|
import { testEffect } from "./lib/effect"
|
|
import { Database } from "../src/database/database"
|
|
import { Bus } from "../src/bus"
|
|
|
|
const agentPlugin = (pluginID: string, agentID: string) =>
|
|
Plugin.define({
|
|
id: pluginID,
|
|
effect: (ctx) => ctx.agent.transform((agents) => agents.update(Agent.ID.make(agentID), () => {})),
|
|
})
|
|
|
|
// A host-owned assignment in miniature: the map decides per ref which plugins
|
|
// an instance is born with, the way an embedder will per Slack thread.
|
|
const instances = Layer.effect(
|
|
LocationServiceMap.Service,
|
|
LayerMap.make(
|
|
(ref: Location.Ref) =>
|
|
Instance.layer(ref, {
|
|
plugins: path.basename(ref.directory) === "thread-a" ? [agentPlugin("thread-a-plugin", "thread-a-agent")] : [],
|
|
replacements: [[Global.node, tempGlobalLayer]],
|
|
}),
|
|
{ idleTimeToLive: Duration.infinity },
|
|
),
|
|
)
|
|
|
|
const it = testEffect(
|
|
AppNodeBuilder.build(LayerNode.group([Database.node, Bus.node, SdkPlugins.node, LocationServiceMap.node]), [
|
|
[Global.node, tempGlobalLayer],
|
|
[LocationServiceMap.node, instances],
|
|
]),
|
|
)
|
|
|
|
describe("InstancePlugins", () => {
|
|
it.live("binds plugins to one instance without leaking to siblings", () =>
|
|
Effect.acquireRelease(
|
|
Effect.promise(() => tmpdir()),
|
|
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
|
|
).pipe(
|
|
Effect.flatMap((dir) =>
|
|
Effect.gen(function* () {
|
|
const locations = yield* LocationServiceMap.Service
|
|
const sdk = yield* SdkPlugins.Service
|
|
yield* sdk.register(agentPlugin("global-plugin", "global-agent"))
|
|
|
|
const dirA = path.join(dir.path, "thread-a")
|
|
const dirB = path.join(dir.path, "thread-b")
|
|
yield* Effect.promise(() => fs.mkdir(dirA))
|
|
yield* Effect.promise(() => fs.mkdir(dirB))
|
|
const refA = Location.Ref.make({ directory: AbsolutePath.make(dirA) })
|
|
const refB = Location.Ref.make({ directory: AbsolutePath.make(dirB) })
|
|
|
|
const agents = (ref: Location.Ref) =>
|
|
Effect.gen(function* () {
|
|
const supervisor = yield* PluginSupervisor.Service
|
|
yield* supervisor.flush
|
|
const service = yield* Agent.Service
|
|
return {
|
|
bound: yield* service.get(Agent.ID.make("thread-a-agent")),
|
|
global: yield* service.get(Agent.ID.make("global-agent")),
|
|
}
|
|
}).pipe(Effect.scoped, Effect.provide(locations.get(ref)))
|
|
|
|
const a = yield* agents(refA)
|
|
expect(a.bound).toBeDefined()
|
|
expect(a.global).toBeDefined()
|
|
|
|
const b = yield* agents(refB)
|
|
expect(b.bound).toBeUndefined()
|
|
expect(b.global).toBeDefined()
|
|
|
|
// Eviction and rebuild re-bind the same list.
|
|
yield* locations.invalidate(refA)
|
|
const rebuilt = yield* agents(refA)
|
|
expect(rebuilt.bound).toBeDefined()
|
|
expect(rebuilt.global).toBeDefined()
|
|
}),
|
|
),
|
|
),
|
|
)
|
|
})
|
|
|
|
describe("InstancePlugins.bound", () => {
|
|
test("rejects duplicate ids in one list", () => {
|
|
const plugin = Plugin.define({ id: "dup", effect: () => Effect.void })
|
|
expect(() => InstancePlugins.bound([plugin, plugin])).toThrow("duplicate instance plugin ids: dup")
|
|
})
|
|
})
|