import fs from "fs/promises" import path from "path" import { describe, expect } from "bun:test" import { Effect, PubSub, Schema, Stream } from "effect" import { Config as ConfigSchema } from "@opencode-ai/schema/config" import { CommandV2 } from "@opencode-ai/core/command" import { AgentV2 } from "@opencode-ai/core/agent" import { Config } from "@opencode-ai/core/config" import { ConfigCommandPlugin } from "@opencode-ai/core/config/plugin/command" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { FSUtil } from "@opencode-ai/core/fs-util" import { EventV2 } from "@opencode-ai/core/event" import { Location } from "@opencode-ai/core/location" import { MCP } from "@opencode-ai/core/mcp/index" import { ModelV2 } from "@opencode-ai/core/model" import { ProviderV2 } from "@opencode-ai/core/provider" import { AbsolutePath } from "@opencode-ai/core/schema" import { emptyConfigLayer, emptyMcpLayer, testLocationLayer } from "../fixture/mcp" import { tmpdir } from "../fixture/tmpdir" import { testEffect } from "../lib/effect" import { host } from "../plugin/host" const it = testEffect( AppNodeBuilder.build(LayerNode.group([CommandV2.node, EventV2.node, FSUtil.node]), [ [MCP.node, emptyMcpLayer], [Config.node, emptyConfigLayer], [Location.node, testLocationLayer], ]), ) const decode = Schema.decodeUnknownSync(Config.Info) describe("ConfigCommandPlugin.Plugin", () => { it.live("loads inline and file-based commands in config order", () => Effect.acquireRelease( Effect.promise(() => tmpdir()), (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), ).pipe( Effect.flatMap((tmp) => Effect.gen(function* () { yield* Effect.promise(async () => { await fs.mkdir(path.join(tmp.path, "commands", "nested"), { recursive: true }) await fs.writeFile( path.join(tmp.path, "commands", "review.md"), `--- description: File review agent: reviewer model: anthropic/claude#high subtask: true --- Review files`, ) await fs.writeFile(path.join(tmp.path, "commands", "nested", "docs.md"), "Write docs") await fs.writeFile(path.join(tmp.path, "commands", "empty.md"), "") }) const command = yield* CommandV2.Service const events = yield* EventV2.Service const update = yield* events.publish(ConfigSchema.Event.Updated, {}) const updates = yield* PubSub.unbounded() yield* ConfigCommandPlugin.Plugin.effect( host({ command: { list: () => Effect.die("unused command.list"), transform: command.transform, reload: command.reload, }, event: { subscribe: () => Stream.fromPubSub(updates) }, }), ).pipe( Effect.provideService( Config.Service, Config.Service.of({ entries: () => Effect.succeed([ new Config.Document({ type: "document", info: decode({ commands: { review: { template: "Inline review" } } }), }), new Config.Directory({ type: "directory", path: AbsolutePath.make(tmp.path) }), ]), }), ), ) expect(yield* command.list()).toEqual([ CommandV2.Info.make({ name: "review", template: "Review files", description: "File review", agent: AgentV2.ID.make("reviewer"), model: { providerID: ProviderV2.ID.make("anthropic"), id: ModelV2.ID.make("claude"), variant: ModelV2.VariantID.make("high"), }, subtask: true, }), CommandV2.Info.make({ name: "empty", template: "" }), CommandV2.Info.make({ name: "nested/docs", template: "Write docs" }), ]) yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "commands", "review.md"), "Review again")) yield* Effect.sleep("10 millis") yield* PubSub.publish(updates, update) for (let attempt = 0; attempt < 100; attempt++) { if ((yield* command.get("review"))?.template === "Review again") break yield* Effect.sleep("10 millis") } expect((yield* command.get("review"))?.template).toBe("Review again") }), ), ), ) })