agent.test.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { describe, expect } from "bun:test"
  2. import { Effect, Exit, Scope } from "effect"
  3. import { AgentV2 } from "@opencode-ai/core/agent"
  4. import { Location } from "@opencode-ai/core/location"
  5. import { AgentPlugin } from "@opencode-ai/core/plugin/agent"
  6. import { AbsolutePath } from "@opencode-ai/core/schema"
  7. import { location } from "./fixture/location"
  8. import { testEffect } from "./lib/effect"
  9. const it = testEffect(AgentV2.locationLayer)
  10. describe("AgentV2", () => {
  11. it.effect("starts without agents", () =>
  12. Effect.gen(function* () {
  13. const agent = yield* AgentV2.Service
  14. expect(yield* agent.all()).toEqual([])
  15. expect(yield* agent.get(AgentV2.ID.make("build"))).toBeUndefined()
  16. }),
  17. )
  18. it.effect("materializes replayable agent transforms", () =>
  19. Effect.gen(function* () {
  20. const agent = yield* AgentV2.Service
  21. const id = AgentV2.ID.make("reviewer")
  22. const transform = yield* agent.transform()
  23. yield* transform((editor) =>
  24. editor.update(id, (info) => {
  25. info.description = "Reviews code"
  26. info.mode = "subagent"
  27. }),
  28. )
  29. expect(yield* agent.get(id)).toMatchObject({ id, description: "Reviews code", mode: "subagent" })
  30. expect((yield* agent.all()).map((info) => info.id)).toEqual([id])
  31. }),
  32. )
  33. it.effect("rebuilds state when a transform is replaced", () =>
  34. Effect.gen(function* () {
  35. const agent = yield* AgentV2.Service
  36. const id = AgentV2.ID.make("reviewer")
  37. const transform = yield* agent.transform()
  38. yield* transform((editor) =>
  39. editor.update(id, (info) => {
  40. info.description = "Old description"
  41. info.hidden = true
  42. }),
  43. )
  44. yield* transform((editor) =>
  45. editor.update(id, (info) => {
  46. info.description = "New description"
  47. }),
  48. )
  49. expect(yield* agent.get(id)).toMatchObject({ description: "New description", hidden: false })
  50. }),
  51. )
  52. it.effect("removes a transform when its scope closes", () =>
  53. Effect.gen(function* () {
  54. const agent = yield* AgentV2.Service
  55. const id = AgentV2.ID.make("scoped")
  56. const scope = yield* Scope.make()
  57. const transform = yield* agent.transform().pipe(Scope.provide(scope))
  58. yield* transform((editor) => editor.update(id, () => {}))
  59. expect(yield* agent.get(id)).toBeDefined()
  60. yield* Scope.close(scope, Exit.void)
  61. expect(yield* agent.get(id)).toBeUndefined()
  62. }),
  63. )
  64. it.effect("applies direct agent updates", () =>
  65. Effect.gen(function* () {
  66. const agent = yield* AgentV2.Service
  67. const id = AgentV2.ID.make("build")
  68. yield* agent.update((editor) =>
  69. editor.update(id, (info) => {
  70. info.mode = "primary"
  71. info.hidden = true
  72. }),
  73. )
  74. expect(yield* agent.get(id)).toMatchObject({ id, mode: "primary", hidden: true })
  75. }),
  76. )
  77. it.effect("creates agents with runtime defaults and supports direct removal", () =>
  78. Effect.gen(function* () {
  79. const agent = yield* AgentV2.Service
  80. const id = AgentV2.ID.make("custom")
  81. yield* agent.update((editor) => editor.update(id, () => {}))
  82. expect(yield* agent.get(id)).toEqual(AgentV2.Info.empty(id))
  83. yield* agent.update((editor) => editor.remove(id))
  84. expect(yield* agent.get(id)).toBeUndefined()
  85. }),
  86. )
  87. it.effect("does not ambiently opt built-in agents into bash", () =>
  88. Effect.gen(function* () {
  89. const agent = yield* AgentV2.Service
  90. yield* AgentPlugin.Plugin.effect.pipe(
  91. Effect.provideService(
  92. Location.Service,
  93. Location.Service.of(location({ directory: AbsolutePath.make("/project") })),
  94. ),
  95. )
  96. const agents = yield* agent.all()
  97. expect(agents.map((item) => String(item.id)).sort()).toEqual([
  98. "build",
  99. "compaction",
  100. "explore",
  101. "general",
  102. "plan",
  103. "summary",
  104. "title",
  105. ])
  106. for (const item of agents) {
  107. expect(item.permissions.some((rule) => rule.action === "bash" && rule.effect !== "deny")).toBe(false)
  108. }
  109. }),
  110. )
  111. })