agent.test.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. import { afterEach, test, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import path from "path"
  4. import { provideInstance, tmpdir } from "../fixture/fixture"
  5. import { Instance } from "../../src/project/instance"
  6. import { Agent } from "../../src/agent/agent"
  7. import { Permission } from "../../src/permission"
  8. // Helper to evaluate permission for a tool with wildcard pattern
  9. function evalPerm(agent: Agent.Info | undefined, permission: string): Permission.Action | undefined {
  10. if (!agent) return undefined
  11. return Permission.evaluate(permission, "*", agent.permission).action
  12. }
  13. function load<A>(dir: string, fn: (svc: Agent.Interface) => Effect.Effect<A>) {
  14. return Effect.runPromise(provideInstance(dir)(Agent.Service.use(fn)).pipe(Effect.provide(Agent.defaultLayer)))
  15. }
  16. afterEach(async () => {
  17. await Instance.disposeAll()
  18. })
  19. test("returns default native agents when no config", async () => {
  20. await using tmp = await tmpdir()
  21. await Instance.provide({
  22. directory: tmp.path,
  23. fn: async () => {
  24. const agents = await load(tmp.path, (svc) => svc.list())
  25. const names = agents.map((a) => a.name)
  26. expect(names).toContain("build")
  27. expect(names).toContain("plan")
  28. expect(names).toContain("general")
  29. expect(names).toContain("explore")
  30. expect(names).toContain("compaction")
  31. expect(names).toContain("title")
  32. expect(names).toContain("summary")
  33. },
  34. })
  35. })
  36. test("build agent has correct default properties", async () => {
  37. await using tmp = await tmpdir()
  38. await Instance.provide({
  39. directory: tmp.path,
  40. fn: async () => {
  41. const build = await load(tmp.path, (svc) => svc.get("build"))
  42. expect(build).toBeDefined()
  43. expect(build?.mode).toBe("primary")
  44. expect(build?.native).toBe(true)
  45. expect(evalPerm(build, "edit")).toBe("allow")
  46. expect(evalPerm(build, "bash")).toBe("allow")
  47. },
  48. })
  49. })
  50. test("plan agent denies edits except .opencode/plans/*", async () => {
  51. await using tmp = await tmpdir()
  52. await Instance.provide({
  53. directory: tmp.path,
  54. fn: async () => {
  55. const plan = await load(tmp.path, (svc) => svc.get("plan"))
  56. expect(plan).toBeDefined()
  57. // Wildcard is denied
  58. expect(evalPerm(plan, "edit")).toBe("deny")
  59. // But specific path is allowed
  60. expect(Permission.evaluate("edit", ".opencode/plans/foo.md", plan!.permission).action).toBe("allow")
  61. },
  62. })
  63. })
  64. test("explore agent denies edit and write", async () => {
  65. await using tmp = await tmpdir()
  66. await Instance.provide({
  67. directory: tmp.path,
  68. fn: async () => {
  69. const explore = await load(tmp.path, (svc) => svc.get("explore"))
  70. expect(explore).toBeDefined()
  71. expect(explore?.mode).toBe("subagent")
  72. expect(evalPerm(explore, "edit")).toBe("deny")
  73. expect(evalPerm(explore, "write")).toBe("deny")
  74. expect(evalPerm(explore, "todowrite")).toBe("deny")
  75. },
  76. })
  77. })
  78. test("explore agent asks for external directories and allows Truncate.GLOB", async () => {
  79. const { Truncate } = await import("../../src/tool/truncate")
  80. await using tmp = await tmpdir()
  81. await Instance.provide({
  82. directory: tmp.path,
  83. fn: async () => {
  84. const explore = await load(tmp.path, (svc) => svc.get("explore"))
  85. expect(explore).toBeDefined()
  86. expect(Permission.evaluate("external_directory", "/some/other/path", explore!.permission).action).toBe("ask")
  87. expect(Permission.evaluate("external_directory", Truncate.GLOB, explore!.permission).action).toBe("allow")
  88. },
  89. })
  90. })
  91. test("general agent denies todo tools", async () => {
  92. await using tmp = await tmpdir()
  93. await Instance.provide({
  94. directory: tmp.path,
  95. fn: async () => {
  96. const general = await load(tmp.path, (svc) => svc.get("general"))
  97. expect(general).toBeDefined()
  98. expect(general?.mode).toBe("subagent")
  99. expect(general?.hidden).toBeUndefined()
  100. expect(evalPerm(general, "todowrite")).toBe("deny")
  101. },
  102. })
  103. })
  104. test("compaction agent denies all permissions", async () => {
  105. await using tmp = await tmpdir()
  106. await Instance.provide({
  107. directory: tmp.path,
  108. fn: async () => {
  109. const compaction = await load(tmp.path, (svc) => svc.get("compaction"))
  110. expect(compaction).toBeDefined()
  111. expect(compaction?.hidden).toBe(true)
  112. expect(evalPerm(compaction, "bash")).toBe("deny")
  113. expect(evalPerm(compaction, "edit")).toBe("deny")
  114. expect(evalPerm(compaction, "read")).toBe("deny")
  115. },
  116. })
  117. })
  118. test("custom agent from config creates new agent", async () => {
  119. await using tmp = await tmpdir({
  120. config: {
  121. agent: {
  122. my_custom_agent: {
  123. model: "openai/gpt-4",
  124. description: "My custom agent",
  125. temperature: 0.5,
  126. top_p: 0.9,
  127. },
  128. },
  129. },
  130. })
  131. await Instance.provide({
  132. directory: tmp.path,
  133. fn: async () => {
  134. const custom = await load(tmp.path, (svc) => svc.get("my_custom_agent"))
  135. expect(custom).toBeDefined()
  136. expect(String(custom?.model?.providerID)).toBe("openai")
  137. expect(String(custom?.model?.modelID)).toBe("gpt-4")
  138. expect(custom?.description).toBe("My custom agent")
  139. expect(custom?.temperature).toBe(0.5)
  140. expect(custom?.topP).toBe(0.9)
  141. expect(custom?.native).toBe(false)
  142. expect(custom?.mode).toBe("all")
  143. },
  144. })
  145. })
  146. test("custom agent config overrides native agent properties", async () => {
  147. await using tmp = await tmpdir({
  148. config: {
  149. agent: {
  150. build: {
  151. model: "anthropic/claude-3",
  152. description: "Custom build agent",
  153. temperature: 0.7,
  154. color: "#FF0000",
  155. },
  156. },
  157. },
  158. })
  159. await Instance.provide({
  160. directory: tmp.path,
  161. fn: async () => {
  162. const build = await load(tmp.path, (svc) => svc.get("build"))
  163. expect(build).toBeDefined()
  164. expect(String(build?.model?.providerID)).toBe("anthropic")
  165. expect(String(build?.model?.modelID)).toBe("claude-3")
  166. expect(build?.description).toBe("Custom build agent")
  167. expect(build?.temperature).toBe(0.7)
  168. expect(build?.color).toBe("#FF0000")
  169. expect(build?.native).toBe(true)
  170. },
  171. })
  172. })
  173. test("agent disable removes agent from list", async () => {
  174. await using tmp = await tmpdir({
  175. config: {
  176. agent: {
  177. explore: { disable: true },
  178. },
  179. },
  180. })
  181. await Instance.provide({
  182. directory: tmp.path,
  183. fn: async () => {
  184. const explore = await load(tmp.path, (svc) => svc.get("explore"))
  185. expect(explore).toBeUndefined()
  186. const agents = await load(tmp.path, (svc) => svc.list())
  187. const names = agents.map((a) => a.name)
  188. expect(names).not.toContain("explore")
  189. },
  190. })
  191. })
  192. test("agent permission config merges with defaults", async () => {
  193. await using tmp = await tmpdir({
  194. config: {
  195. agent: {
  196. build: {
  197. permission: {
  198. bash: {
  199. "rm -rf *": "deny",
  200. },
  201. },
  202. },
  203. },
  204. },
  205. })
  206. await Instance.provide({
  207. directory: tmp.path,
  208. fn: async () => {
  209. const build = await load(tmp.path, (svc) => svc.get("build"))
  210. expect(build).toBeDefined()
  211. // Specific pattern is denied
  212. expect(Permission.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny")
  213. // Edit still allowed
  214. expect(evalPerm(build, "edit")).toBe("allow")
  215. },
  216. })
  217. })
  218. test("global permission config applies to all agents", async () => {
  219. await using tmp = await tmpdir({
  220. config: {
  221. permission: {
  222. bash: "deny",
  223. },
  224. },
  225. })
  226. await Instance.provide({
  227. directory: tmp.path,
  228. fn: async () => {
  229. const build = await load(tmp.path, (svc) => svc.get("build"))
  230. expect(build).toBeDefined()
  231. expect(evalPerm(build, "bash")).toBe("deny")
  232. },
  233. })
  234. })
  235. test("agent steps/maxSteps config sets steps property", async () => {
  236. await using tmp = await tmpdir({
  237. config: {
  238. agent: {
  239. build: { steps: 50 },
  240. plan: { maxSteps: 100 },
  241. },
  242. },
  243. })
  244. await Instance.provide({
  245. directory: tmp.path,
  246. fn: async () => {
  247. const build = await load(tmp.path, (svc) => svc.get("build"))
  248. const plan = await load(tmp.path, (svc) => svc.get("plan"))
  249. expect(build?.steps).toBe(50)
  250. expect(plan?.steps).toBe(100)
  251. },
  252. })
  253. })
  254. test("agent mode can be overridden", async () => {
  255. await using tmp = await tmpdir({
  256. config: {
  257. agent: {
  258. explore: { mode: "primary" },
  259. },
  260. },
  261. })
  262. await Instance.provide({
  263. directory: tmp.path,
  264. fn: async () => {
  265. const explore = await load(tmp.path, (svc) => svc.get("explore"))
  266. expect(explore?.mode).toBe("primary")
  267. },
  268. })
  269. })
  270. test("agent name can be overridden", async () => {
  271. await using tmp = await tmpdir({
  272. config: {
  273. agent: {
  274. build: { name: "Builder" },
  275. },
  276. },
  277. })
  278. await Instance.provide({
  279. directory: tmp.path,
  280. fn: async () => {
  281. const build = await load(tmp.path, (svc) => svc.get("build"))
  282. expect(build?.name).toBe("Builder")
  283. },
  284. })
  285. })
  286. test("agent prompt can be set from config", async () => {
  287. await using tmp = await tmpdir({
  288. config: {
  289. agent: {
  290. build: { prompt: "Custom system prompt" },
  291. },
  292. },
  293. })
  294. await Instance.provide({
  295. directory: tmp.path,
  296. fn: async () => {
  297. const build = await load(tmp.path, (svc) => svc.get("build"))
  298. expect(build?.prompt).toBe("Custom system prompt")
  299. },
  300. })
  301. })
  302. test("unknown agent properties are placed into options", async () => {
  303. await using tmp = await tmpdir({
  304. config: {
  305. agent: {
  306. build: {
  307. random_property: "hello",
  308. another_random: 123,
  309. },
  310. },
  311. },
  312. })
  313. await Instance.provide({
  314. directory: tmp.path,
  315. fn: async () => {
  316. const build = await load(tmp.path, (svc) => svc.get("build"))
  317. expect(build?.options.random_property).toBe("hello")
  318. expect(build?.options.another_random).toBe(123)
  319. },
  320. })
  321. })
  322. test("agent options merge correctly", async () => {
  323. await using tmp = await tmpdir({
  324. config: {
  325. agent: {
  326. build: {
  327. options: {
  328. custom_option: true,
  329. another_option: "value",
  330. },
  331. },
  332. },
  333. },
  334. })
  335. await Instance.provide({
  336. directory: tmp.path,
  337. fn: async () => {
  338. const build = await load(tmp.path, (svc) => svc.get("build"))
  339. expect(build?.options.custom_option).toBe(true)
  340. expect(build?.options.another_option).toBe("value")
  341. },
  342. })
  343. })
  344. test("multiple custom agents can be defined", async () => {
  345. await using tmp = await tmpdir({
  346. config: {
  347. agent: {
  348. agent_a: {
  349. description: "Agent A",
  350. mode: "subagent",
  351. },
  352. agent_b: {
  353. description: "Agent B",
  354. mode: "primary",
  355. },
  356. },
  357. },
  358. })
  359. await Instance.provide({
  360. directory: tmp.path,
  361. fn: async () => {
  362. const agentA = await load(tmp.path, (svc) => svc.get("agent_a"))
  363. const agentB = await load(tmp.path, (svc) => svc.get("agent_b"))
  364. expect(agentA?.description).toBe("Agent A")
  365. expect(agentA?.mode).toBe("subagent")
  366. expect(agentB?.description).toBe("Agent B")
  367. expect(agentB?.mode).toBe("primary")
  368. },
  369. })
  370. })
  371. test("Agent.list keeps the default agent first and sorts the rest by name", async () => {
  372. await using tmp = await tmpdir({
  373. config: {
  374. default_agent: "plan",
  375. agent: {
  376. zebra: {
  377. description: "Zebra",
  378. mode: "subagent",
  379. },
  380. alpha: {
  381. description: "Alpha",
  382. mode: "subagent",
  383. },
  384. },
  385. },
  386. })
  387. await Instance.provide({
  388. directory: tmp.path,
  389. fn: async () => {
  390. const names = (await load(tmp.path, (svc) => svc.list())).map((a) => a.name)
  391. expect(names[0]).toBe("plan")
  392. expect(names.slice(1)).toEqual(names.slice(1).toSorted((a, b) => a.localeCompare(b)))
  393. },
  394. })
  395. })
  396. test("Agent.get returns undefined for non-existent agent", async () => {
  397. await using tmp = await tmpdir()
  398. await Instance.provide({
  399. directory: tmp.path,
  400. fn: async () => {
  401. const nonExistent = await load(tmp.path, (svc) => svc.get("does_not_exist"))
  402. expect(nonExistent).toBeUndefined()
  403. },
  404. })
  405. })
  406. test("default permission includes doom_loop and external_directory as ask", async () => {
  407. await using tmp = await tmpdir()
  408. await Instance.provide({
  409. directory: tmp.path,
  410. fn: async () => {
  411. const build = await load(tmp.path, (svc) => svc.get("build"))
  412. expect(evalPerm(build, "doom_loop")).toBe("ask")
  413. expect(evalPerm(build, "external_directory")).toBe("ask")
  414. },
  415. })
  416. })
  417. test("webfetch is allowed by default", async () => {
  418. await using tmp = await tmpdir()
  419. await Instance.provide({
  420. directory: tmp.path,
  421. fn: async () => {
  422. const build = await load(tmp.path, (svc) => svc.get("build"))
  423. expect(evalPerm(build, "webfetch")).toBe("allow")
  424. },
  425. })
  426. })
  427. test("legacy tools config converts to permissions", async () => {
  428. await using tmp = await tmpdir({
  429. config: {
  430. agent: {
  431. build: {
  432. tools: {
  433. bash: false,
  434. read: false,
  435. },
  436. },
  437. },
  438. },
  439. })
  440. await Instance.provide({
  441. directory: tmp.path,
  442. fn: async () => {
  443. const build = await load(tmp.path, (svc) => svc.get("build"))
  444. expect(evalPerm(build, "bash")).toBe("deny")
  445. expect(evalPerm(build, "read")).toBe("deny")
  446. },
  447. })
  448. })
  449. test("legacy tools config maps write/edit/patch to edit permission", async () => {
  450. await using tmp = await tmpdir({
  451. config: {
  452. agent: {
  453. build: {
  454. tools: {
  455. write: false,
  456. },
  457. },
  458. },
  459. },
  460. })
  461. await Instance.provide({
  462. directory: tmp.path,
  463. fn: async () => {
  464. const build = await load(tmp.path, (svc) => svc.get("build"))
  465. expect(evalPerm(build, "edit")).toBe("deny")
  466. },
  467. })
  468. })
  469. test("Truncate.GLOB is allowed even when user denies external_directory globally", async () => {
  470. const { Truncate } = await import("../../src/tool/truncate")
  471. await using tmp = await tmpdir({
  472. config: {
  473. permission: {
  474. external_directory: "deny",
  475. },
  476. },
  477. })
  478. await Instance.provide({
  479. directory: tmp.path,
  480. fn: async () => {
  481. const build = await load(tmp.path, (svc) => svc.get("build"))
  482. expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("allow")
  483. expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny")
  484. expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny")
  485. },
  486. })
  487. })
  488. test("Truncate.GLOB is allowed even when user denies external_directory per-agent", async () => {
  489. const { Truncate } = await import("../../src/tool/truncate")
  490. await using tmp = await tmpdir({
  491. config: {
  492. agent: {
  493. build: {
  494. permission: {
  495. external_directory: "deny",
  496. },
  497. },
  498. },
  499. },
  500. })
  501. await Instance.provide({
  502. directory: tmp.path,
  503. fn: async () => {
  504. const build = await load(tmp.path, (svc) => svc.get("build"))
  505. expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("allow")
  506. expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny")
  507. expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny")
  508. },
  509. })
  510. })
  511. test("explicit Truncate.GLOB deny is respected", async () => {
  512. const { Truncate } = await import("../../src/tool/truncate")
  513. await using tmp = await tmpdir({
  514. config: {
  515. permission: {
  516. external_directory: {
  517. "*": "deny",
  518. [Truncate.GLOB]: "deny",
  519. },
  520. },
  521. },
  522. })
  523. await Instance.provide({
  524. directory: tmp.path,
  525. fn: async () => {
  526. const build = await load(tmp.path, (svc) => svc.get("build"))
  527. expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("deny")
  528. expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny")
  529. },
  530. })
  531. })
  532. test("skill directories are allowed for external_directory", async () => {
  533. await using tmp = await tmpdir({
  534. git: true,
  535. init: async (dir) => {
  536. const skillDir = path.join(dir, ".opencode", "skill", "perm-skill")
  537. await Bun.write(
  538. path.join(skillDir, "SKILL.md"),
  539. `---
  540. name: perm-skill
  541. description: Permission skill.
  542. ---
  543. # Permission Skill
  544. `,
  545. )
  546. },
  547. })
  548. const home = process.env.OPENCODE_TEST_HOME
  549. process.env.OPENCODE_TEST_HOME = tmp.path
  550. try {
  551. await Instance.provide({
  552. directory: tmp.path,
  553. fn: async () => {
  554. const build = await load(tmp.path, (svc) => svc.get("build"))
  555. const skillDir = path.join(tmp.path, ".opencode", "skill", "perm-skill")
  556. const target = path.join(skillDir, "reference", "notes.md")
  557. expect(Permission.evaluate("external_directory", target, build!.permission).action).toBe("allow")
  558. },
  559. })
  560. } finally {
  561. process.env.OPENCODE_TEST_HOME = home
  562. }
  563. })
  564. test("defaultAgent returns build when no default_agent config", async () => {
  565. await using tmp = await tmpdir()
  566. await Instance.provide({
  567. directory: tmp.path,
  568. fn: async () => {
  569. const agent = await load(tmp.path, (svc) => svc.defaultAgent())
  570. expect(agent).toBe("build")
  571. },
  572. })
  573. })
  574. test("defaultAgent respects default_agent config set to plan", async () => {
  575. await using tmp = await tmpdir({
  576. config: {
  577. default_agent: "plan",
  578. },
  579. })
  580. await Instance.provide({
  581. directory: tmp.path,
  582. fn: async () => {
  583. const agent = await load(tmp.path, (svc) => svc.defaultAgent())
  584. expect(agent).toBe("plan")
  585. },
  586. })
  587. })
  588. test("defaultAgent respects default_agent config set to custom agent with mode all", async () => {
  589. await using tmp = await tmpdir({
  590. config: {
  591. default_agent: "my_custom",
  592. agent: {
  593. my_custom: {
  594. description: "My custom agent",
  595. },
  596. },
  597. },
  598. })
  599. await Instance.provide({
  600. directory: tmp.path,
  601. fn: async () => {
  602. const agent = await load(tmp.path, (svc) => svc.defaultAgent())
  603. expect(agent).toBe("my_custom")
  604. },
  605. })
  606. })
  607. test("defaultAgent throws when default_agent points to subagent", async () => {
  608. await using tmp = await tmpdir({
  609. config: {
  610. default_agent: "explore",
  611. },
  612. })
  613. await Instance.provide({
  614. directory: tmp.path,
  615. fn: async () => {
  616. await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow('default agent "explore" is a subagent')
  617. },
  618. })
  619. })
  620. test("defaultAgent throws when default_agent points to hidden agent", async () => {
  621. await using tmp = await tmpdir({
  622. config: {
  623. default_agent: "compaction",
  624. },
  625. })
  626. await Instance.provide({
  627. directory: tmp.path,
  628. fn: async () => {
  629. await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow('default agent "compaction" is hidden')
  630. },
  631. })
  632. })
  633. test("defaultAgent throws when default_agent points to non-existent agent", async () => {
  634. await using tmp = await tmpdir({
  635. config: {
  636. default_agent: "does_not_exist",
  637. },
  638. })
  639. await Instance.provide({
  640. directory: tmp.path,
  641. fn: async () => {
  642. await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow(
  643. 'default agent "does_not_exist" not found',
  644. )
  645. },
  646. })
  647. })
  648. test("defaultAgent returns plan when build is disabled and default_agent not set", async () => {
  649. await using tmp = await tmpdir({
  650. config: {
  651. agent: {
  652. build: { disable: true },
  653. },
  654. },
  655. })
  656. await Instance.provide({
  657. directory: tmp.path,
  658. fn: async () => {
  659. const agent = await load(tmp.path, (svc) => svc.defaultAgent())
  660. // build is disabled, so it should return plan (next primary agent)
  661. expect(agent).toBe("plan")
  662. },
  663. })
  664. })
  665. test("defaultAgent throws when all primary agents are disabled", async () => {
  666. await using tmp = await tmpdir({
  667. config: {
  668. agent: {
  669. build: { disable: true },
  670. plan: { disable: true },
  671. },
  672. },
  673. })
  674. await Instance.provide({
  675. directory: tmp.path,
  676. fn: async () => {
  677. // build and plan are disabled, no primary-capable agents remain
  678. await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow("no primary visible agent found")
  679. },
  680. })
  681. })