skill.test.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { describe, expect } from "bun:test"
  2. import { Deferred, Effect, Fiber, Stream } from "effect"
  3. import { Agent } from "@opencode-ai/core/agent"
  4. import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
  5. import { LayerNode } from "@opencode-ai/util/effect/layer-node"
  6. import { Bus } from "@opencode-ai/core/bus"
  7. import { AbsolutePath } from "@opencode-ai/core/schema"
  8. import { Skill } from "@opencode-ai/core/skill"
  9. import { testEffect } from "./lib/effect"
  10. const it = testEffect(AppNodeBuilder.build(LayerNode.group([Skill.node, Agent.node, Bus.node])))
  11. const info = (id: string, description: string) =>
  12. Skill.Info.make({
  13. id: Skill.ID.make(id),
  14. name: Skill.Name.make(id),
  15. description,
  16. location: AbsolutePath.make(`/skills/${id}/SKILL.md`),
  17. content: `# ${id}`,
  18. })
  19. describe("Skill", () => {
  20. it.effect("registers values with last-write-wins precedence", () =>
  21. Effect.gen(function* () {
  22. const skill = yield* Skill.Service
  23. yield* skill.transform((draft) => {
  24. draft.add(info("review", "First"))
  25. draft.add(info("deploy", "Deploy"))
  26. draft.add(info("review", "Second"))
  27. expect(draft.list().map((item) => item.id)).toEqual([Skill.ID.make("review"), Skill.ID.make("deploy")])
  28. })
  29. expect(yield* skill.list()).toEqual([info("review", "Second"), info("deploy", "Deploy")])
  30. }),
  31. )
  32. it.effect("updates and removes registered values", () =>
  33. Effect.gen(function* () {
  34. const skill = yield* Skill.Service
  35. yield* skill.transform((draft) => {
  36. draft.add(info("review", "Initial"))
  37. draft.update("review", (value) => {
  38. value.description = "Updated"
  39. value.id = Skill.ID.make("ignored")
  40. })
  41. draft.update("missing", () => Effect.die("unreachable"))
  42. draft.add(info("deploy", "Deploy"))
  43. draft.remove("deploy")
  44. })
  45. expect(yield* skill.list()).toEqual([info("review", "Updated")])
  46. }),
  47. )
  48. it.effect("restores earlier values when an updating transform is disposed", () =>
  49. Effect.gen(function* () {
  50. const skill = yield* Skill.Service
  51. const original = info("review", "Initial")
  52. yield* skill.transform((draft) => draft.add(original))
  53. const updated = yield* skill.transform((draft) =>
  54. draft.update("review", (value) => {
  55. value.description = "Updated"
  56. }),
  57. )
  58. expect((yield* skill.list())[0]?.description).toBe("Updated")
  59. yield* updated.dispose
  60. expect((yield* skill.list())[0]?.description).toBe("Initial")
  61. expect(original.description).toBe("Initial")
  62. }),
  63. )
  64. it.live("publishes updates after committed values are visible", () =>
  65. Effect.gen(function* () {
  66. const skill = yield* Skill.Service
  67. const bus = yield* Bus.Service
  68. const updated = yield* Deferred.make<Skill.Info[]>()
  69. const fiber = yield* bus.subscribe(Skill.Event.Updated).pipe(
  70. Stream.runForEach(() => skill.list().pipe(Effect.flatMap((values) => Deferred.succeed(updated, values)))),
  71. Effect.forkScoped,
  72. )
  73. yield* Effect.yieldNow
  74. yield* skill.transform((draft) => draft.add(info("review", "Visible")))
  75. expect(yield* Deferred.await(updated).pipe(Effect.timeout("1 second"))).toEqual([info("review", "Visible")])
  76. yield* Fiber.interrupt(fiber)
  77. }),
  78. )
  79. it.effect("filters values by agent permissions", () =>
  80. Effect.gen(function* () {
  81. const agents = yield* Agent.Service
  82. yield* agents.transform((draft) =>
  83. draft.update(Agent.ID.make("reviewer"), (agent) => {
  84. agent.permissions.push({ action: "skill", resource: "deploy", effect: "deny" })
  85. }),
  86. )
  87. const agent = yield* agents.get(Agent.ID.make("reviewer"))
  88. expect(Skill.available([info("deploy", "Deploy")], agent!)).toEqual([])
  89. }),
  90. )
  91. })