Compare commits

..
Author SHA1 Message Date
Kit Langton 0d5250ef44 test(core): bind acquired test services 2026-08-27 15:41:20 -04:00
7 changed files with 20 additions and 60 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"@opencode-ai/core": patch
---
Preserve reference insertion order when later config documents override an existing reference.
+3 -1
View File
@@ -20,13 +20,14 @@ export const Plugin = define({
const global = yield* Global.Service
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, ctx.reference.reload())
yield* ctx.reference.transform((draft) => {
const entries = new Map<string, Reference.Source>()
for (const doc of loaded.entries.filter((entry): entry is Document => entry.type === "document")) {
const directory = doc.path ? path.dirname(doc.path) : location.directory
for (const [name, entry] of Object.entries(doc.info.references ?? {})) {
if (!validAlias(name)) continue
const description = typeof entry === "string" ? undefined : entry.description
const hidden = typeof entry === "string" ? undefined : entry.hidden
draft.add(
entries.set(
name,
local(entry)
? Reference.LocalSource.make({
@@ -47,6 +48,7 @@ export const Plugin = define({
)
}
}
for (const [name, source] of entries) draft.add(name, source)
})
}),
})
-43
View File
@@ -34,41 +34,6 @@ const decode = Schema.decodeUnknownSync(Info)
const document = path.join(import.meta.dir, "opencode.json")
describe("config plugin reloads", () => {
it.effect("preserves reference precedence and insertion order across documents", () =>
Effect.gen(function* () {
const plugins = yield* Plugin.Service
const references = yield* Reference.Service
const host = yield* PluginHost.make(plugins)
yield* references.transform((draft) =>
draft.add(
"external",
Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/references/external") }),
),
)
yield* ConfigReferencePlugin.Plugin.effect(host)
const result = yield* references.list()
expect(result.map((reference) => reference.name)).toEqual(["external", "shared", "first", "second"])
expect(result.find((reference) => reference.name === "shared")?.path).toBe(
AbsolutePath.make(path.resolve("/config/second/shared")),
)
}).pipe(
Effect.provide(
Config.testLayer([
referenceConfig("/config/first/opencode.json", {
shared: "./shared",
first: "./first",
}),
referenceConfig("/config/second/opencode.json", {
shared: "./shared",
second: "./second",
}),
]),
),
Effect.provideService(Global.Service, Global.Service.of(Global.make())),
),
)
it.live("reloads config-backed domains without reloading external plugins", () =>
Effect.gen(function* () {
const agents = yield* Agent.Service
@@ -137,14 +102,6 @@ function config(name: string) {
})
}
function referenceConfig(file: string, references: Record<string, string>) {
return new Document({
type: "document",
path: AbsolutePath.make(file),
info: decode({ references }),
})
}
function title(value: string) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
+10 -5
View File
@@ -22,7 +22,8 @@ describe("Ripgrep", () => {
yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, "src")))
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "match.ts"), "needle\n"))
const result = yield* (yield* Ripgrep.Service).glob({ cwd: tmp.path, pattern: "**/*.ts", limit: 10 })
const ripgrep = yield* Ripgrep.Service
const result = yield* ripgrep.glob({ cwd: tmp.path, pattern: "**/*.ts", limit: 10 })
expect(result.map((item) => item.path)).toEqual([RelativePath.make("src/match.ts")])
}),
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
@@ -38,7 +39,8 @@ describe("Ripgrep", () => {
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "match.ts"), "needle\n"))
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "skip.txt"), "needle\n"))
const result = yield* (yield* Ripgrep.Service).grep({
const ripgrep = yield* Ripgrep.Service
const result = yield* ripgrep.grep({
cwd: tmp.path,
pattern: "needle",
include: "*.ts",
@@ -64,7 +66,8 @@ describe("Ripgrep", () => {
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "node_modules", "pkg", "index.js"), "ignored\n"))
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "index.js"), "included\n"))
const files = yield* (yield* Ripgrep.Service).find({ cwd: tmp.path, pattern: "*", limit: 10 })
const ripgrep = yield* Ripgrep.Service
const files = yield* ripgrep.find({ cwd: tmp.path, pattern: "*", limit: 10 })
expect(files.map((item) => item.path)).toContain(RelativePath.make("src/index.js"))
expect(files.map((item) => item.path)).not.toContain(RelativePath.make("node_modules/pkg/index.js"))
}),
@@ -113,7 +116,8 @@ describe("Ripgrep", () => {
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "Pictures", "private.jpg"), "private\n"))
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "visible.txt"), "visible\n"))
const files = yield* (yield* Ripgrep.Service).find({
const ripgrep = yield* Ripgrep.Service
const files = yield* ripgrep.find({
cwd: tmp.path,
pattern: "*",
limit: 10,
@@ -136,7 +140,8 @@ describe("Ripgrep", () => {
fs.writeFile(path.join(tmp.path, "generated.ts"), `Cloudflare${"x".repeat(70 * 1024)}\n`),
)
const matches = yield* (yield* Ripgrep.Service).grep({
const ripgrep = yield* Ripgrep.Service
const matches = yield* ripgrep.grep({
cwd: tmp.path,
pattern: "Cloudflare",
limit: 10,
+2 -1
View File
@@ -22,8 +22,9 @@ const withStore = <A, E, R>(
])
return Effect.gen(function* () {
const output = yield* ToolOutput.Service
const fs = yield* FSUtil.Service
if (limits) yield* output.transform((draft) => draft.configure(limits))
return yield* body(output, yield* FSUtil.Service, tmp.path)
return yield* body(output, fs, tmp.path)
}).pipe(Effect.provide(layer))
},
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
+3 -4
View File
@@ -18,7 +18,7 @@ import { GrepTool } from "@opencode-ai/core/tool/plugin/grep"
import { Tool } from "@opencode-ai/core/tool"
import { location } from "./fixture/location"
import { tmpdir } from "./fixture/tmpdir"
import { testEffect } from "./lib/effect"
import { it } from "./lib/effect"
import { permissionLayer } from "./lib/permission"
import { executeTool, registerToolPlugin, toolIdentity } from "./lib/tool"
@@ -40,7 +40,8 @@ const withTools = <A, E, R>(
assertions?: Permission.AssertInput[],
) =>
Effect.gen(function* () {
return yield* body(yield* Tool.Service)
const registry = yield* Tool.Service
return yield* body(registry)
}).pipe(
Effect.provide(
AppNodeBuilder.build(LayerNode.group([Tool.node, globToolNode, grepToolNode]), [
@@ -67,8 +68,6 @@ const call = (name: "glob" | "grep", input: unknown) => ({
call: { type: "tool-call" as const, id: `call-${name}`, name, input },
})
const it = testEffect(Layer.empty)
describe("search tools", () => {
it.live("bounds omitted glob and grep limits", () =>
Effect.acquireUseRelease(
+2 -1
View File
@@ -215,7 +215,8 @@ const withSession = <A, E, R>(directory: string, body: (registry: Tool.Interface
const locations = yield* LocationServiceMap.Service
const locationLayer = locations.get(location)
return yield* Effect.gen(function* () {
yield* (yield* PluginSupervisor.Service).flush
const plugins = yield* PluginSupervisor.Service
yield* plugins.flush
const registry = yield* Tool.Service
return yield* body(registry)
}).pipe(Effect.provide(locationLayer), Effect.ensuring(locations.invalidate(location)))