agent.test.ts 4.0 KB

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