@clinebot/core 0.0.27 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/README.md +7 -0
  2. package/dist/ClineCore.d.ts +28 -2
  3. package/dist/ClineCore.d.ts.map +1 -1
  4. package/dist/account/cline-account-service.d.ts +1 -1
  5. package/dist/account/cline-account-service.d.ts.map +1 -1
  6. package/dist/account/index.d.ts +1 -1
  7. package/dist/account/index.d.ts.map +1 -1
  8. package/dist/account/types.d.ts +5 -0
  9. package/dist/account/types.d.ts.map +1 -1
  10. package/dist/auth/bounded-ttl-cache.d.ts +14 -0
  11. package/dist/auth/bounded-ttl-cache.d.ts.map +1 -0
  12. package/dist/auth/cline.d.ts +27 -2
  13. package/dist/auth/cline.d.ts.map +1 -1
  14. package/dist/auth/oca.d.ts.map +1 -1
  15. package/dist/chat/chat-schema.d.ts +8 -8
  16. package/dist/extensions/config/agent-config-loader.d.ts.map +1 -0
  17. package/dist/{agents → extensions/config}/agent-config-parser.d.ts +2 -2
  18. package/dist/extensions/config/agent-config-parser.d.ts.map +1 -0
  19. package/dist/{agents → extensions/config}/hooks-config-loader.d.ts +1 -1
  20. package/dist/extensions/config/hooks-config-loader.d.ts.map +1 -0
  21. package/dist/{agents → extensions/config}/index.d.ts +2 -4
  22. package/dist/extensions/config/index.d.ts.map +1 -0
  23. package/dist/{runtime/commands.d.ts → extensions/config/runtime-commands.d.ts} +2 -3
  24. package/dist/extensions/config/runtime-commands.d.ts.map +1 -0
  25. package/dist/extensions/config/unified-config-file-watcher.d.ts.map +1 -0
  26. package/dist/extensions/config/user-instruction-config-loader.d.ts.map +1 -0
  27. package/dist/extensions/context/agentic-compaction.d.ts +13 -0
  28. package/dist/extensions/context/agentic-compaction.d.ts.map +1 -0
  29. package/dist/extensions/context/basic-compaction.d.ts +9 -0
  30. package/dist/extensions/context/basic-compaction.d.ts.map +1 -0
  31. package/dist/extensions/context/compaction-shared.d.ts +60 -0
  32. package/dist/extensions/context/compaction-shared.d.ts.map +1 -0
  33. package/dist/extensions/context/compaction.d.ts +20 -0
  34. package/dist/extensions/context/compaction.d.ts.map +1 -0
  35. package/dist/extensions/index.d.ts +5 -0
  36. package/dist/extensions/index.d.ts.map +1 -0
  37. package/dist/extensions/mcp/client.d.ts +3 -0
  38. package/dist/extensions/mcp/client.d.ts.map +1 -0
  39. package/dist/extensions/mcp/config-loader.d.ts.map +1 -0
  40. package/dist/extensions/mcp/index.d.ts +9 -0
  41. package/dist/extensions/mcp/index.d.ts.map +1 -0
  42. package/dist/{mcp → extensions/mcp}/manager.d.ts +1 -2
  43. package/dist/extensions/mcp/manager.d.ts.map +1 -0
  44. package/dist/extensions/mcp/name-transform.d.ts +3 -0
  45. package/dist/extensions/mcp/name-transform.d.ts.map +1 -0
  46. package/dist/extensions/mcp/policies.d.ts +15 -0
  47. package/dist/extensions/mcp/policies.d.ts.map +1 -0
  48. package/dist/extensions/mcp/tools.d.ts +4 -0
  49. package/dist/extensions/mcp/tools.d.ts.map +1 -0
  50. package/dist/{mcp → extensions/mcp}/types.d.ts +29 -1
  51. package/dist/extensions/mcp/types.d.ts.map +1 -0
  52. package/dist/{agents → extensions/plugin}/plugin-config-loader.d.ts +1 -1
  53. package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -0
  54. package/dist/{agents → extensions/plugin}/plugin-loader.d.ts +1 -1
  55. package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -0
  56. package/dist/extensions/plugin/plugin-module-import.d.ts +5 -0
  57. package/dist/extensions/plugin/plugin-module-import.d.ts.map +1 -0
  58. package/dist/{agents → extensions/plugin}/plugin-sandbox.d.ts +1 -1
  59. package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -0
  60. package/dist/extensions/plugin-sandbox-bootstrap.js +485 -0
  61. package/dist/hooks/index.d.ts +4 -0
  62. package/dist/hooks/index.d.ts.map +1 -0
  63. package/dist/hooks/persistent.d.ts +64 -0
  64. package/dist/hooks/persistent.d.ts.map +1 -0
  65. package/dist/hooks/subprocess-runner.d.ts +22 -0
  66. package/dist/hooks/subprocess-runner.d.ts.map +1 -0
  67. package/dist/hooks/subprocess.d.ts +189 -0
  68. package/dist/hooks/subprocess.d.ts.map +1 -0
  69. package/dist/index.d.ts +22 -25
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +560 -447
  72. package/dist/prompt/default-system.d.ts +2 -0
  73. package/dist/prompt/default-system.d.ts.map +1 -0
  74. package/dist/providers/local-provider-service.d.ts +1 -1
  75. package/dist/providers/local-provider-service.d.ts.map +1 -1
  76. package/dist/runtime/checkpoint-hooks.d.ts +21 -0
  77. package/dist/runtime/checkpoint-hooks.d.ts.map +1 -0
  78. package/dist/runtime/hook-file-hooks.d.ts +1 -1
  79. package/dist/runtime/hook-file-hooks.d.ts.map +1 -1
  80. package/dist/runtime/rules.d.ts +1 -1
  81. package/dist/runtime/rules.d.ts.map +1 -1
  82. package/dist/runtime/runtime-builder.d.ts +1 -1
  83. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  84. package/dist/runtime/session-runtime.d.ts +25 -5
  85. package/dist/runtime/session-runtime.d.ts.map +1 -1
  86. package/dist/runtime/subprocess-sandbox.d.ts.map +1 -0
  87. package/dist/runtime/team-runtime-registry.d.ts +1 -1
  88. package/dist/runtime/team-runtime-registry.d.ts.map +1 -1
  89. package/dist/runtime/tool-approval.d.ts +1 -1
  90. package/dist/session/default-session-manager.d.ts +4 -3
  91. package/dist/session/default-session-manager.d.ts.map +1 -1
  92. package/dist/session/file-session-service.d.ts +1 -1
  93. package/dist/session/file-session-service.d.ts.map +1 -1
  94. package/dist/session/{unified-session-persistence-service.d.ts → persistence-service.d.ts} +11 -42
  95. package/dist/session/persistence-service.d.ts.map +1 -0
  96. package/dist/session/rpc-session-service.d.ts +1 -1
  97. package/dist/session/rpc-session-service.d.ts.map +1 -1
  98. package/dist/session/session-agent-events.d.ts +1 -1
  99. package/dist/session/session-artifacts.d.ts.map +1 -1
  100. package/dist/session/session-config-builder.d.ts.map +1 -1
  101. package/dist/session/session-graph.d.ts +1 -1
  102. package/dist/session/session-graph.d.ts.map +1 -1
  103. package/dist/session/session-host.d.ts.map +1 -1
  104. package/dist/session/session-manager.d.ts +6 -5
  105. package/dist/session/session-manager.d.ts.map +1 -1
  106. package/dist/session/session-manifest.d.ts +1 -1
  107. package/dist/session/session-service.d.ts +3 -2
  108. package/dist/session/session-service.d.ts.map +1 -1
  109. package/dist/session/session-team-coordination.d.ts +2 -1
  110. package/dist/session/session-team-coordination.d.ts.map +1 -1
  111. package/dist/session/utils/helpers.d.ts +51 -3
  112. package/dist/session/utils/helpers.d.ts.map +1 -1
  113. package/dist/session/utils/types.d.ts +41 -7
  114. package/dist/session/utils/types.d.ts.map +1 -1
  115. package/dist/session/workspace-manager.d.ts +1 -2
  116. package/dist/session/workspace-manager.d.ts.map +1 -1
  117. package/dist/session/workspace-manifest.d.ts +1 -22
  118. package/dist/session/workspace-manifest.d.ts.map +1 -1
  119. package/dist/storage/file-team-store.d.ts +2 -1
  120. package/dist/storage/file-team-store.d.ts.map +1 -1
  121. package/dist/storage/sqlite-team-store.d.ts +4 -1
  122. package/dist/storage/sqlite-team-store.d.ts.map +1 -1
  123. package/dist/storage/team-store.d.ts.map +1 -1
  124. package/dist/team/delegated-agent.d.ts +44 -0
  125. package/dist/team/delegated-agent.d.ts.map +1 -0
  126. package/dist/team/index.d.ts +1 -0
  127. package/dist/team/index.d.ts.map +1 -1
  128. package/dist/team/multi-agent.d.ts +229 -0
  129. package/dist/team/multi-agent.d.ts.map +1 -0
  130. package/dist/team/projections.d.ts +2 -2
  131. package/dist/team/projections.d.ts.map +1 -1
  132. package/dist/team/runtime.d.ts +5 -0
  133. package/dist/team/runtime.d.ts.map +1 -0
  134. package/dist/team/spawn-agent-tool.d.ts +85 -0
  135. package/dist/team/spawn-agent-tool.d.ts.map +1 -0
  136. package/dist/team/subagent-prompts.d.ts +4 -0
  137. package/dist/team/subagent-prompts.d.ts.map +1 -0
  138. package/dist/team/team-tools.d.ts +35 -0
  139. package/dist/team/team-tools.d.ts.map +1 -0
  140. package/dist/telemetry/OpenTelemetryProvider.d.ts +11 -1
  141. package/dist/telemetry/OpenTelemetryProvider.d.ts.map +1 -1
  142. package/dist/telemetry/{LoggerTelemetryAdapter.d.ts → TelemetryLoggerSink.d.ts} +10 -4
  143. package/dist/telemetry/TelemetryLoggerSink.d.ts.map +1 -0
  144. package/dist/telemetry/TelemetryService.d.ts.map +1 -1
  145. package/dist/telemetry/index.js +15 -28
  146. package/dist/tools/definitions.d.ts +4 -3
  147. package/dist/tools/definitions.d.ts.map +1 -1
  148. package/dist/tools/index.d.ts +5 -5
  149. package/dist/tools/index.d.ts.map +1 -1
  150. package/dist/tools/model-tool-routing.d.ts.map +1 -1
  151. package/dist/tools/presets.d.ts +26 -0
  152. package/dist/tools/presets.d.ts.map +1 -1
  153. package/dist/tools/schemas.d.ts +8 -0
  154. package/dist/tools/schemas.d.ts.map +1 -1
  155. package/dist/tools/types.d.ts +23 -2
  156. package/dist/tools/types.d.ts.map +1 -1
  157. package/dist/types/config.d.ts +47 -3
  158. package/dist/types/config.d.ts.map +1 -1
  159. package/dist/types/events.d.ts +1 -1
  160. package/dist/types/provider-settings.d.ts +1 -1
  161. package/dist/types/provider-settings.d.ts.map +1 -1
  162. package/dist/types/storage.d.ts +2 -1
  163. package/dist/types/storage.d.ts.map +1 -1
  164. package/dist/types.d.ts +7 -16
  165. package/dist/types.d.ts.map +1 -1
  166. package/package.json +16 -13
  167. package/src/ClineCore.test.ts +150 -0
  168. package/src/ClineCore.ts +114 -8
  169. package/src/account/cline-account-service.test.ts +84 -0
  170. package/src/account/cline-account-service.ts +2 -2
  171. package/src/account/index.ts +1 -0
  172. package/src/account/types.ts +6 -0
  173. package/src/auth/bounded-ttl-cache.test.ts +38 -0
  174. package/src/auth/bounded-ttl-cache.ts +53 -0
  175. package/src/auth/cline.test.ts +173 -36
  176. package/src/auth/cline.ts +395 -93
  177. package/src/auth/oca.test.ts +125 -0
  178. package/src/auth/oca.ts +17 -4
  179. package/src/{agents → extensions/config}/agent-config-loader.test.ts +1 -1
  180. package/src/{agents → extensions/config}/agent-config-parser.ts +2 -2
  181. package/src/{agents → extensions/config}/hooks-config-loader.ts +1 -1
  182. package/src/{agents → extensions/config}/index.ts +7 -11
  183. package/src/{runtime/commands.test.ts → extensions/config/runtime-commands.test.ts} +20 -3
  184. package/src/{runtime/commands.ts → extensions/config/runtime-commands.ts} +1 -8
  185. package/src/{agents → extensions/config}/unified-config-file-watcher.ts +15 -2
  186. package/src/{agents → extensions/config}/user-instruction-config-loader.test.ts +90 -2
  187. package/src/{agents → extensions/config}/user-instruction-config-loader.ts +126 -12
  188. package/src/extensions/context/agentic-compaction.ts +119 -0
  189. package/src/extensions/context/basic-compaction.ts +275 -0
  190. package/src/extensions/context/compaction-shared.ts +458 -0
  191. package/src/extensions/context/compaction.test.ts +477 -0
  192. package/src/extensions/context/compaction.ts +203 -0
  193. package/src/extensions/index.ts +12 -0
  194. package/src/extensions/mcp/client.ts +420 -0
  195. package/src/{mcp → extensions/mcp}/index.ts +16 -0
  196. package/src/{mcp → extensions/mcp}/manager.test.ts +1 -2
  197. package/src/{mcp → extensions/mcp}/manager.ts +3 -5
  198. package/src/extensions/mcp/name-transform.ts +33 -0
  199. package/src/extensions/mcp/policies.ts +47 -0
  200. package/src/extensions/mcp/tools.ts +47 -0
  201. package/src/{mcp → extensions/mcp}/types.ts +35 -7
  202. package/src/{agents → extensions/plugin}/plugin-config-loader.test.ts +18 -13
  203. package/src/{agents → extensions/plugin}/plugin-config-loader.ts +1 -1
  204. package/src/{agents → extensions/plugin}/plugin-loader.test.ts +41 -4
  205. package/src/extensions/plugin/plugin-loader.ts +106 -0
  206. package/src/extensions/plugin/plugin-module-import.ts +278 -0
  207. package/src/{agents → extensions/plugin}/plugin-sandbox-bootstrap.ts +30 -92
  208. package/src/{agents → extensions/plugin}/plugin-sandbox.test.ts +60 -3
  209. package/src/{agents → extensions/plugin}/plugin-sandbox.ts +146 -56
  210. package/src/hooks/index.ts +25 -0
  211. package/src/hooks/persistent.ts +661 -0
  212. package/src/hooks/subprocess-runner.ts +196 -0
  213. package/src/hooks/subprocess.ts +669 -0
  214. package/src/index.ts +200 -118
  215. package/src/prompt/default-system.ts +21 -0
  216. package/src/providers/local-provider-registry.ts +1 -1
  217. package/src/providers/local-provider-service.test.ts +23 -2
  218. package/src/providers/local-provider-service.ts +2 -2
  219. package/src/runtime/checkpoint-hooks.test.ts +167 -0
  220. package/src/runtime/checkpoint-hooks.ts +186 -0
  221. package/src/runtime/hook-file-hooks.test.ts +40 -1
  222. package/src/runtime/hook-file-hooks.ts +35 -16
  223. package/src/runtime/index.ts +4 -19
  224. package/src/runtime/rules.ts +4 -1
  225. package/src/runtime/runtime-builder.team-persistence.test.ts +3 -6
  226. package/src/runtime/runtime-builder.test.ts +266 -160
  227. package/src/runtime/runtime-builder.ts +120 -47
  228. package/src/runtime/runtime-parity.test.ts +22 -22
  229. package/src/runtime/session-runtime.ts +36 -6
  230. package/src/runtime/{sandbox/subprocess-sandbox.ts → subprocess-sandbox.ts} +24 -3
  231. package/src/runtime/team-runtime-registry.ts +1 -4
  232. package/src/runtime/tool-approval.ts +1 -1
  233. package/src/session/default-session-manager.e2e.test.ts +2 -2
  234. package/src/session/default-session-manager.test.ts +553 -9
  235. package/src/session/default-session-manager.ts +140 -46
  236. package/src/session/file-session-service.ts +3 -3
  237. package/src/session/index.ts +6 -6
  238. package/src/session/persistence-service.test.ts +212 -0
  239. package/src/session/{unified-session-persistence-service.ts → persistence-service.ts} +106 -172
  240. package/src/session/rpc-session-service.ts +3 -3
  241. package/src/session/runtime-oauth-token-manager.ts +1 -1
  242. package/src/session/session-agent-events.ts +1 -1
  243. package/src/session/session-artifacts.ts +32 -4
  244. package/src/session/session-config-builder.ts +22 -9
  245. package/src/session/session-graph.ts +1 -1
  246. package/src/session/session-host.ts +19 -11
  247. package/src/session/session-manager.ts +11 -6
  248. package/src/session/session-service.team-persistence.test.ts +1 -1
  249. package/src/session/session-service.ts +6 -9
  250. package/src/session/session-team-coordination.ts +7 -3
  251. package/src/session/session-telemetry.ts +1 -1
  252. package/src/session/utils/helpers.test.ts +160 -0
  253. package/src/session/utils/helpers.ts +289 -42
  254. package/src/session/utils/types.ts +47 -7
  255. package/src/session/workspace-manager.ts +5 -3
  256. package/src/session/workspace-manifest.ts +3 -49
  257. package/src/storage/file-team-store.ts +2 -5
  258. package/src/storage/provider-settings-legacy-migration.ts +2 -2
  259. package/src/storage/provider-settings-manager.test.ts +1 -1
  260. package/src/storage/sqlite-team-store.ts +212 -125
  261. package/src/storage/team-store.ts +1 -5
  262. package/src/team/delegated-agent.ts +131 -0
  263. package/src/team/index.ts +1 -0
  264. package/src/team/multi-agent.lifecycle.test.ts +201 -0
  265. package/src/team/multi-agent.ts +1666 -0
  266. package/src/team/projections.ts +2 -4
  267. package/src/team/runtime.ts +54 -0
  268. package/src/team/spawn-agent-tool.test.ts +387 -0
  269. package/src/team/spawn-agent-tool.ts +207 -0
  270. package/src/team/subagent-prompts.ts +41 -0
  271. package/src/team/team-tools.test.ts +802 -0
  272. package/src/team/team-tools.ts +792 -0
  273. package/src/telemetry/OpenTelemetryProvider.test.ts +25 -3
  274. package/src/telemetry/OpenTelemetryProvider.ts +108 -18
  275. package/src/telemetry/TelemetryLoggerSink.test.ts +42 -0
  276. package/src/telemetry/{LoggerTelemetryAdapter.ts → TelemetryLoggerSink.ts} +21 -14
  277. package/src/telemetry/TelemetryService.test.ts +7 -7
  278. package/src/telemetry/TelemetryService.ts +2 -4
  279. package/src/tools/definitions.test.ts +76 -0
  280. package/src/tools/definitions.ts +41 -2
  281. package/src/tools/executors/apply-patch.ts +1 -1
  282. package/src/tools/executors/editor.ts +1 -1
  283. package/src/tools/executors/file-read.ts +1 -1
  284. package/src/tools/executors/search.ts +1 -1
  285. package/src/tools/executors/web-fetch.ts +1 -1
  286. package/src/tools/index.ts +6 -1
  287. package/src/tools/model-tool-routing.ts +2 -0
  288. package/src/tools/presets.test.ts +8 -0
  289. package/src/tools/presets.ts +40 -2
  290. package/src/tools/schemas.ts +19 -0
  291. package/src/tools/types.ts +31 -2
  292. package/src/types/config.ts +61 -7
  293. package/src/types/events.ts +1 -1
  294. package/src/types/index.ts +0 -1
  295. package/src/types/provider-settings.ts +1 -1
  296. package/src/types/storage.ts +2 -5
  297. package/src/types.ts +32 -44
  298. package/dist/agents/agent-config-loader.d.ts.map +0 -1
  299. package/dist/agents/agent-config-parser.d.ts.map +0 -1
  300. package/dist/agents/hooks-config-loader.d.ts.map +0 -1
  301. package/dist/agents/index.d.ts.map +0 -1
  302. package/dist/agents/plugin-config-loader.d.ts.map +0 -1
  303. package/dist/agents/plugin-loader.d.ts.map +0 -1
  304. package/dist/agents/plugin-sandbox-bootstrap.js +0 -446
  305. package/dist/agents/plugin-sandbox.d.ts.map +0 -1
  306. package/dist/agents/unified-config-file-watcher.d.ts.map +0 -1
  307. package/dist/agents/user-instruction-config-loader.d.ts.map +0 -1
  308. package/dist/mcp/config-loader.d.ts.map +0 -1
  309. package/dist/mcp/index.d.ts +0 -5
  310. package/dist/mcp/index.d.ts.map +0 -1
  311. package/dist/mcp/manager.d.ts.map +0 -1
  312. package/dist/mcp/types.d.ts.map +0 -1
  313. package/dist/runtime/commands.d.ts.map +0 -1
  314. package/dist/runtime/sandbox/subprocess-sandbox.d.ts.map +0 -1
  315. package/dist/runtime/skills.d.ts +0 -14
  316. package/dist/runtime/skills.d.ts.map +0 -1
  317. package/dist/runtime/workflows.d.ts +0 -14
  318. package/dist/runtime/workflows.d.ts.map +0 -1
  319. package/dist/session/unified-session-persistence-service.d.ts.map +0 -1
  320. package/dist/telemetry/LoggerTelemetryAdapter.d.ts.map +0 -1
  321. package/dist/types/workspace.d.ts +0 -8
  322. package/dist/types/workspace.d.ts.map +0 -1
  323. package/src/agents/plugin-loader.ts +0 -175
  324. package/src/runtime/skills.ts +0 -44
  325. package/src/runtime/workflows.test.ts +0 -119
  326. package/src/runtime/workflows.ts +0 -45
  327. package/src/session/unified-session-persistence-service.test.ts +0 -85
  328. package/src/telemetry/LoggerTelemetryAdapter.test.ts +0 -42
  329. package/src/types/workspace.ts +0 -7
  330. /package/dist/{agents → extensions/config}/agent-config-loader.d.ts +0 -0
  331. /package/dist/{agents → extensions/config}/unified-config-file-watcher.d.ts +0 -0
  332. /package/dist/{agents → extensions/config}/user-instruction-config-loader.d.ts +0 -0
  333. /package/dist/{mcp → extensions/mcp}/config-loader.d.ts +0 -0
  334. /package/dist/runtime/{sandbox/subprocess-sandbox.d.ts → subprocess-sandbox.d.ts} +0 -0
  335. /package/src/{agents → extensions/config}/agent-config-loader.ts +0 -0
  336. /package/src/{agents → extensions/config}/hooks-config-loader.test.ts +0 -0
  337. /package/src/{agents → extensions/config}/unified-config-file-watcher.test.ts +0 -0
  338. /package/src/{mcp → extensions/mcp}/config-loader.test.ts +0 -0
  339. /package/src/{mcp → extensions/mcp}/config-loader.ts +0 -0
package/src/auth/cline.ts CHANGED
@@ -23,9 +23,17 @@ import {
23
23
  const DEFAULT_AUTH_ENDPOINTS = {
24
24
  authorize: "/api/v1/auth/authorize",
25
25
  token: "/api/v1/auth/token",
26
+ register: "/api/v1/auth/register",
26
27
  refresh: "/api/v1/auth/refresh",
27
28
  } as const;
28
29
 
30
+ const DEFAULT_WORKOS_ENDPOINTS = {
31
+ deviceAuthorization: "/user_management/authorize/device",
32
+ authenticate: "/user_management/authenticate",
33
+ } as const;
34
+
35
+ const DEFAULT_WORKOS_API_BASE_URL = "https://api.workos.com";
36
+ const DEFAULT_WORKOS_CLIENT_ID = "client_01K3A541FN8TA3EPPHTD2325AR";
29
37
  const DEFAULT_CALLBACK_PATH = "/auth";
30
38
  const DEFAULT_CALLBACK_PORTS = Array.from(
31
39
  { length: 11 },
@@ -34,6 +42,8 @@ const DEFAULT_CALLBACK_PORTS = Array.from(
34
42
  const DEFAULT_REFRESH_BUFFER_MS = 5 * 60 * 1000;
35
43
  const DEFAULT_RETRYABLE_TOKEN_GRACE_MS = 30 * 1000;
36
44
  const DEFAULT_HTTP_TIMEOUT_MS = 30 * 1000;
45
+ const DEFAULT_DEVICE_AUTH_EXPIRES_IN_SECONDS = 300;
46
+ const DEFAULT_DEVICE_AUTH_INTERVAL_SECONDS = 5;
37
47
 
38
48
  export type ClineTokenResolution = {
39
49
  forceRefresh?: boolean;
@@ -68,10 +78,15 @@ type HeaderInput = HeaderMap | (() => Promise<HeaderMap> | HeaderMap);
68
78
  export interface ClineOAuthProviderOptions {
69
79
  apiBaseUrl: string;
70
80
  headers?: HeaderInput;
71
- callbackPath?: string;
72
- callbackPorts?: number[];
73
81
  requestTimeoutMs?: number;
74
82
  telemetry?: ITelemetryService;
83
+ /**
84
+ * Feature flag for WorkOS Device Authorization.
85
+ * Defaults to false to preserve legacy callback-based OAuth behavior.
86
+ */
87
+ useWorkOSDeviceAuth?: boolean;
88
+ callbackPath?: string;
89
+ callbackPorts?: number[];
75
90
  /**
76
91
  * Optional identity provider name for token exchange.
77
92
  */
@@ -112,18 +127,6 @@ class ClineOAuthTokenError extends Error {
112
127
  }
113
128
  }
114
129
 
115
- function createState(): string {
116
- const cryptoApi = globalThis.crypto;
117
- if (!cryptoApi) {
118
- return Math.random().toString(16).slice(2);
119
- }
120
- const bytes = new Uint8Array(16);
121
- cryptoApi.getRandomValues(bytes);
122
- return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(
123
- "",
124
- );
125
- }
126
-
127
130
  function toEpochMs(isoDateTime: string): number {
128
131
  const epoch = Date.parse(isoDateTime);
129
132
  if (Number.isNaN(epoch)) {
@@ -165,22 +168,232 @@ async function resolveHeaders(input?: HeaderInput): Promise<HeaderMap> {
165
168
  return typeof input === "function" ? await input() : input;
166
169
  }
167
170
 
168
- async function requestAuthorizationUrl(
171
+ function toSeconds(value: unknown, fallback: number): number {
172
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
173
+ return fallback;
174
+ }
175
+ return Math.floor(value);
176
+ }
177
+
178
+ async function sleep(ms: number): Promise<void> {
179
+ await new Promise((resolve) => setTimeout(resolve, ms));
180
+ }
181
+
182
+ type WorkOSDeviceAuthorizationResponse = {
183
+ device_code?: string;
184
+ user_code?: string;
185
+ verification_uri?: string;
186
+ verification_uri_complete?: string;
187
+ expires_in?: number;
188
+ interval?: number;
189
+ error?: string;
190
+ error_description?: string;
191
+ };
192
+
193
+ type WorkOSTokenResponse = {
194
+ access_token?: string;
195
+ refresh_token?: string;
196
+ token_type?: string;
197
+ expires_in?: number;
198
+ error?: string;
199
+ error_description?: string;
200
+ };
201
+
202
+ type WorkOSTokenSuccess = {
203
+ accessToken: string;
204
+ refreshToken: string;
205
+ tokenType: string;
206
+ };
207
+
208
+ function requireClineTokenResponse(
209
+ payload: ClineTokenResponse,
210
+ message: string,
211
+ ): ClineAuthResponseData {
212
+ if (!payload.success || !payload.data?.accessToken) {
213
+ throw new Error(message);
214
+ }
215
+ return payload.data;
216
+ }
217
+
218
+ async function requestWorkOSDeviceAuthorization(
219
+ clientId: string,
220
+ options?: { requestTimeoutMs?: number },
221
+ ): Promise<{
222
+ deviceCode: string;
223
+ userCode: string;
224
+ verificationUri: string;
225
+ verificationUriComplete?: string;
226
+ expiresInSeconds: number;
227
+ pollIntervalSeconds: number;
228
+ }> {
229
+ const response = await fetch(
230
+ resolveUrl(
231
+ DEFAULT_WORKOS_API_BASE_URL,
232
+ DEFAULT_WORKOS_ENDPOINTS.deviceAuthorization,
233
+ ),
234
+ {
235
+ method: "POST",
236
+ headers: {
237
+ "Content-Type": "application/x-www-form-urlencoded",
238
+ },
239
+ body: new URLSearchParams({ client_id: clientId }),
240
+ signal: AbortSignal.timeout(
241
+ options?.requestTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
242
+ ),
243
+ },
244
+ );
245
+
246
+ const json = (await response
247
+ .json()
248
+ .catch(() => ({}))) as WorkOSDeviceAuthorizationResponse;
249
+ if (!response.ok) {
250
+ throw new ClineOAuthTokenError(
251
+ `Device authorization failed: ${response.status}${json.error_description ? ` - ${json.error_description}` : ""}`,
252
+ { status: response.status, errorCode: json.error },
253
+ );
254
+ }
255
+ if (!json.device_code || !json.user_code || !json.verification_uri) {
256
+ throw new Error("Invalid WorkOS device authorization response");
257
+ }
258
+
259
+ return {
260
+ deviceCode: json.device_code,
261
+ userCode: json.user_code,
262
+ verificationUri: json.verification_uri,
263
+ verificationUriComplete: json.verification_uri_complete,
264
+ expiresInSeconds: toSeconds(
265
+ json.expires_in,
266
+ DEFAULT_DEVICE_AUTH_EXPIRES_IN_SECONDS,
267
+ ),
268
+ pollIntervalSeconds: toSeconds(
269
+ json.interval,
270
+ DEFAULT_DEVICE_AUTH_INTERVAL_SECONDS,
271
+ ),
272
+ };
273
+ }
274
+
275
+ async function pollWorkOSTokens(options: {
276
+ clientId: string;
277
+ deviceCode: string;
278
+ expiresInSeconds: number;
279
+ initialPollIntervalSeconds: number;
280
+ requestTimeoutMs: number;
281
+ workosApiBaseUrl: string;
282
+ onProgress?: OAuthLoginCallbacks["onProgress"];
283
+ }): Promise<WorkOSTokenSuccess> {
284
+ const deadline = Date.now() + options.expiresInSeconds * 1000;
285
+ let intervalSeconds = Math.max(1, options.initialPollIntervalSeconds);
286
+
287
+ while (Date.now() <= deadline) {
288
+ const response = await fetch(
289
+ resolveUrl(
290
+ options.workosApiBaseUrl,
291
+ DEFAULT_WORKOS_ENDPOINTS.authenticate,
292
+ ),
293
+ {
294
+ method: "POST",
295
+ headers: {
296
+ "Content-Type": "application/x-www-form-urlencoded",
297
+ },
298
+ body: new URLSearchParams({
299
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
300
+ device_code: options.deviceCode,
301
+ client_id: options.clientId,
302
+ }),
303
+ signal: AbortSignal.timeout(options.requestTimeoutMs),
304
+ },
305
+ );
306
+ const payload = (await response
307
+ .json()
308
+ .catch(() => ({}))) as WorkOSTokenResponse;
309
+ if (response.ok) {
310
+ if (!payload.access_token || !payload.refresh_token) {
311
+ throw new Error("Invalid WorkOS token response");
312
+ }
313
+ return {
314
+ accessToken: payload.access_token,
315
+ refreshToken: payload.refresh_token,
316
+ tokenType: payload.token_type ?? "Bearer",
317
+ };
318
+ }
319
+
320
+ switch (payload.error) {
321
+ case "authorization_pending": {
322
+ await sleep(intervalSeconds * 1000);
323
+ break;
324
+ }
325
+ case "slow_down": {
326
+ intervalSeconds += 1;
327
+ await sleep(intervalSeconds * 1000);
328
+ break;
329
+ }
330
+ case "access_denied":
331
+ case "expired_token":
332
+ case "invalid_grant": {
333
+ throw new ClineOAuthTokenError(
334
+ payload.error_description || "WorkOS authorization failed",
335
+ {
336
+ status: response.status,
337
+ errorCode: payload.error,
338
+ },
339
+ );
340
+ }
341
+ default: {
342
+ throw new ClineOAuthTokenError(
343
+ `WorkOS token polling failed: ${response.status}${payload.error_description ? ` - ${payload.error_description}` : ""}`,
344
+ {
345
+ status: response.status,
346
+ errorCode: payload.error,
347
+ },
348
+ );
349
+ }
350
+ }
351
+
352
+ options.onProgress?.("Waiting for browser authentication confirmation...");
353
+ }
354
+
355
+ throw new Error("WorkOS device authorization timed out");
356
+ }
357
+
358
+ async function registerWorkOSTokens(
359
+ workosTokens: WorkOSTokenSuccess,
169
360
  options: ClineOAuthProviderOptions,
170
- params: {
171
- callbackUrl: string;
172
- state: string;
173
- },
174
- ): Promise<string> {
175
- const authUrl = new URL(
176
- resolveUrl(options.apiBaseUrl, DEFAULT_AUTH_ENDPOINTS.authorize),
361
+ provider?: string,
362
+ ): Promise<ClineOAuthCredentials> {
363
+ const body = {
364
+ accessToken: workosTokens.accessToken,
365
+ refreshToken: workosTokens.refreshToken,
366
+ };
367
+
368
+ const response = await fetch(
369
+ resolveUrl(options.apiBaseUrl, DEFAULT_AUTH_ENDPOINTS.register),
370
+ {
371
+ method: "POST",
372
+ headers: {
373
+ "Content-Type": "application/json",
374
+ ...(await resolveHeaders(options.headers)),
375
+ },
376
+ body: JSON.stringify(body),
377
+ signal: AbortSignal.timeout(
378
+ options.requestTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
379
+ ),
380
+ },
177
381
  );
178
- authUrl.searchParams.set("client_type", "extension");
179
- authUrl.searchParams.set("callback_url", params.callbackUrl);
180
- authUrl.searchParams.set("redirect_uri", params.callbackUrl);
181
- authUrl.searchParams.set("state", params.state);
182
382
 
183
- return authUrl.toString();
383
+ if (!response.ok) {
384
+ const text = await response.text().catch(() => "");
385
+ const details = parseOAuthError(text);
386
+ throw new ClineOAuthTokenError(
387
+ `Token registration failed: ${response.status}${details.message ? ` - ${details.message}` : ""}`,
388
+ { status: response.status, errorCode: details.code },
389
+ );
390
+ }
391
+
392
+ const json = (await response.json()) as ClineTokenResponse;
393
+ return toClineCredentials(
394
+ requireClineTokenResponse(json, "Invalid token exchange response"),
395
+ provider ?? options.provider,
396
+ );
184
397
  }
185
398
 
186
399
  async function exchangeAuthorizationCode(
@@ -222,11 +435,10 @@ async function exchangeAuthorizationCode(
222
435
  }
223
436
 
224
437
  const json = (await response.json()) as ClineTokenResponse;
225
- if (!json.success || !json.data?.accessToken) {
226
- throw new Error("Invalid token exchange response");
227
- }
228
-
229
- return toClineCredentials(json.data, provider ?? options.provider);
438
+ return toClineCredentials(
439
+ requireClineTokenResponse(json, "Invalid token exchange response"),
440
+ provider ?? options.provider,
441
+ );
230
442
  }
231
443
 
232
444
  export async function loginClineOAuth(
@@ -235,76 +447,102 @@ export async function loginClineOAuth(
235
447
  },
236
448
  ): Promise<ClineOAuthCredentials> {
237
449
  captureAuthStarted(options.telemetry, options.provider ?? "cline");
450
+ const useWorkOSDeviceAuth = options.useWorkOSDeviceAuth;
238
451
  const callbackPorts = options.callbackPorts?.length
239
452
  ? options.callbackPorts
240
453
  : DEFAULT_CALLBACK_PORTS;
241
454
  const callbackPath = options.callbackPath ?? DEFAULT_CALLBACK_PATH;
242
- const state = createState();
243
-
244
- const localServer = await startLocalOAuthServer({
245
- ports: callbackPorts,
246
- callbackPath,
247
- });
248
-
455
+ const localServer = useWorkOSDeviceAuth
456
+ ? null
457
+ : await startLocalOAuthServer({
458
+ ports: callbackPorts,
459
+ callbackPath,
460
+ });
249
461
  const callbackUrl =
250
- localServer.callbackUrl ||
462
+ localServer?.callbackUrl ||
251
463
  `http://127.0.0.1:${callbackPorts[0] ?? DEFAULT_CALLBACK_PORTS[0]}${callbackPath}`;
252
464
 
253
- const authUrl = await requestAuthorizationUrl(options, {
254
- callbackUrl,
255
- state,
256
- });
257
- options.callbacks.onAuth({
258
- url: authUrl,
259
- instructions: "Continue the authentication process in your browser.",
260
- });
261
-
262
465
  try {
263
- let code: string | undefined;
264
- let provider = options.provider;
265
-
266
- const authResult = await resolveAuthorizationCodeInput({
267
- waitForCallback: localServer.waitForCallback,
268
- cancelWait: localServer.cancelWait,
269
- onManualCodeInput: options.callbacks.onManualCodeInput,
270
- parseOptions: { includeProvider: true },
271
- });
272
- if (authResult.error) {
273
- throw new Error(`OAuth error: ${authResult.error}`);
274
- }
275
- if (authResult.state && authResult.state !== state) {
276
- throw new Error("State mismatch");
277
- }
278
- code = authResult.code;
279
- provider = authResult.provider ?? provider;
466
+ let credentials: ClineOAuthCredentials;
467
+ if (useWorkOSDeviceAuth) {
468
+ const clientId = DEFAULT_WORKOS_CLIENT_ID;
469
+ const deviceAuthorization = await requestWorkOSDeviceAuthorization(
470
+ clientId,
471
+ options,
472
+ );
473
+ options.callbacks.onAuth({
474
+ url:
475
+ deviceAuthorization.verificationUriComplete ??
476
+ deviceAuthorization.verificationUri,
477
+ instructions: `Enter this code in your browser: ${deviceAuthorization.userCode}`,
478
+ });
280
479
 
281
- if (!code) {
282
- const input = await options.callbacks.onPrompt({
283
- message: "Paste the authorization code (or full redirect URL):",
480
+ const workosTokens = await pollWorkOSTokens({
481
+ clientId,
482
+ deviceCode: deviceAuthorization.deviceCode,
483
+ expiresInSeconds: deviceAuthorization.expiresInSeconds,
484
+ initialPollIntervalSeconds: deviceAuthorization.pollIntervalSeconds,
485
+ requestTimeoutMs: options.requestTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
486
+ workosApiBaseUrl: DEFAULT_WORKOS_API_BASE_URL,
487
+ onProgress: options.callbacks.onProgress,
284
488
  });
285
- const parsed = parseAuthorizationInput(input, { includeProvider: true });
286
- if (parsed.state && parsed.state !== state) {
287
- throw new Error("State mismatch");
288
- }
289
- code = parsed.code;
290
- provider = parsed.provider ?? provider;
291
- }
292
489
 
293
- if (!code) {
294
- throw new Error("Missing authorization code");
490
+ credentials = await registerWorkOSTokens(
491
+ workosTokens,
492
+ options,
493
+ options.provider,
494
+ );
495
+ } else {
496
+ const authUrl = new URL(
497
+ resolveUrl(options.apiBaseUrl, DEFAULT_AUTH_ENDPOINTS.authorize),
498
+ );
499
+ authUrl.searchParams.set("client_type", "extension");
500
+ authUrl.searchParams.set("callback_url", callbackUrl);
501
+ authUrl.searchParams.set("redirect_uri", callbackUrl);
502
+ options.callbacks.onAuth({
503
+ url: authUrl.toString(),
504
+ instructions: "Continue the authentication process in your browser.",
505
+ });
506
+
507
+ let code: string | undefined;
508
+ let provider = options.provider;
509
+ const authResult = await resolveAuthorizationCodeInput({
510
+ waitForCallback: localServer?.waitForCallback ?? (async () => null),
511
+ cancelWait: localServer?.cancelWait ?? (() => {}),
512
+ onManualCodeInput: options.callbacks.onManualCodeInput,
513
+ parseOptions: { includeProvider: true },
514
+ });
515
+ if (authResult.error) {
516
+ throw new Error(`OAuth error: ${authResult.error}`);
517
+ }
518
+ code = authResult.code;
519
+ provider = authResult.provider ?? provider;
520
+ if (!code) {
521
+ const input = await options.callbacks.onPrompt({
522
+ message: "Paste the authorization code (or full redirect URL):",
523
+ });
524
+ const parsed = parseAuthorizationInput(input, {
525
+ includeProvider: true,
526
+ });
527
+ code = parsed.code;
528
+ provider = parsed.provider ?? provider;
529
+ }
530
+ if (!code) {
531
+ throw new Error("Missing authorization code");
532
+ }
533
+ credentials = await exchangeAuthorizationCode(
534
+ code,
535
+ callbackUrl,
536
+ options,
537
+ provider,
538
+ );
295
539
  }
296
540
 
297
- const credentials = await exchangeAuthorizationCode(
298
- code,
299
- callbackUrl,
300
- options,
301
- provider,
302
- );
303
- captureAuthSucceeded(options.telemetry, provider ?? "cline");
541
+ captureAuthSucceeded(options.telemetry, options.provider ?? "cline");
304
542
  identifyAccount(options.telemetry, {
305
543
  id: credentials.accountId,
306
544
  email: credentials.email,
307
- provider: provider ?? "cline",
545
+ provider: options.provider ?? "cline",
308
546
  });
309
547
  return credentials;
310
548
  } catch (error) {
@@ -315,7 +553,71 @@ export async function loginClineOAuth(
315
553
  );
316
554
  throw error;
317
555
  } finally {
318
- localServer.close();
556
+ localServer?.close();
557
+ }
558
+ }
559
+
560
+ export async function startClineDeviceAuth(options?: {
561
+ requestTimeoutMs?: number;
562
+ }): Promise<{
563
+ deviceCode: string;
564
+ userCode: string;
565
+ verificationUri: string;
566
+ verificationUriComplete?: string;
567
+ expiresInSeconds: number;
568
+ pollIntervalSeconds: number;
569
+ }> {
570
+ return await requestWorkOSDeviceAuthorization(
571
+ DEFAULT_WORKOS_CLIENT_ID,
572
+ options,
573
+ );
574
+ }
575
+
576
+ export async function completeClineDeviceAuth(options: {
577
+ deviceCode: string;
578
+ expiresInSeconds: number;
579
+ pollIntervalSeconds: number;
580
+ apiBaseUrl: string;
581
+ provider?: string;
582
+ headers?: HeaderInput;
583
+ requestTimeoutMs?: number;
584
+ telemetry?: ITelemetryService;
585
+ }): Promise<ClineOAuthCredentials> {
586
+ const providerName = options.provider ?? "cline";
587
+ captureAuthStarted(options.telemetry, providerName);
588
+ try {
589
+ const workosTokens = await pollWorkOSTokens({
590
+ clientId: DEFAULT_WORKOS_CLIENT_ID,
591
+ deviceCode: options.deviceCode,
592
+ expiresInSeconds: options.expiresInSeconds,
593
+ initialPollIntervalSeconds: options.pollIntervalSeconds,
594
+ requestTimeoutMs: options.requestTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
595
+ workosApiBaseUrl: DEFAULT_WORKOS_API_BASE_URL,
596
+ });
597
+ const credentials = await registerWorkOSTokens(
598
+ workosTokens,
599
+ {
600
+ apiBaseUrl: options.apiBaseUrl,
601
+ headers: options.headers,
602
+ requestTimeoutMs: options.requestTimeoutMs,
603
+ provider: options.provider,
604
+ },
605
+ options.provider,
606
+ );
607
+ captureAuthSucceeded(options.telemetry, providerName);
608
+ identifyAccount(options.telemetry, {
609
+ id: credentials.accountId,
610
+ email: credentials.email,
611
+ provider: providerName,
612
+ });
613
+ return credentials;
614
+ } catch (error) {
615
+ captureAuthFailed(
616
+ options.telemetry,
617
+ providerName,
618
+ error instanceof Error ? error.message : String(error),
619
+ );
620
+ throw error;
319
621
  }
320
622
  }
321
623
 
@@ -351,13 +653,13 @@ export async function refreshClineToken(
351
653
  }
352
654
 
353
655
  const json = (await response.json()) as ClineTokenResponse;
354
- if (!json.success || !json.data?.accessToken) {
355
- throw new Error("Invalid token refresh response");
356
- }
357
-
358
656
  const provider =
359
657
  (current.metadata?.provider as string | undefined) ?? options.provider;
360
- return toClineCredentials(json.data, provider, current);
658
+ return toClineCredentials(
659
+ requireClineTokenResponse(json, "Invalid token refresh response"),
660
+ provider,
661
+ current,
662
+ );
361
663
  }
362
664
 
363
665
  export async function getValidClineCredentials(
@@ -406,7 +708,7 @@ export function createClineOAuthProvider(
406
708
  return {
407
709
  id: "cline",
408
710
  name: "Cline Account",
409
- usesCallbackServer: true,
711
+ usesCallbackServer: !options.useWorkOSDeviceAuth,
410
712
  async login(callbacks) {
411
713
  return loginClineOAuth({ ...options, callbacks });
412
714
  },
@@ -212,4 +212,129 @@ describe("auth/oca getValidOcaCredentials", () => {
212
212
  expect(result).toBe(current);
213
213
  nowSpy.mockRestore();
214
214
  });
215
+
216
+ it("re-discovers token endpoint shortly after discovery fallback errors", async () => {
217
+ const nowSpy = vi.spyOn(Date, "now").mockReturnValue(100_000);
218
+ const fetchMock = vi
219
+ .fn()
220
+ // first refresh: discovery fails and fallback endpoint is used
221
+ .mockImplementationOnce(async () => new Response(null, { status: 503 }))
222
+ .mockImplementationOnce(
223
+ async () =>
224
+ new Response(
225
+ JSON.stringify({
226
+ error: "server_error",
227
+ error_description: "temporary issue",
228
+ }),
229
+ {
230
+ status: 500,
231
+ headers: { "Content-Type": "application/json" },
232
+ },
233
+ ),
234
+ )
235
+ // second refresh within fallback TTL: discovery should still be cached
236
+ .mockImplementationOnce(
237
+ async () =>
238
+ new Response(
239
+ JSON.stringify({
240
+ error: "server_error",
241
+ error_description: "temporary issue",
242
+ }),
243
+ {
244
+ status: 500,
245
+ headers: { "Content-Type": "application/json" },
246
+ },
247
+ ),
248
+ )
249
+ // third refresh after fallback TTL: discovery should be retried
250
+ .mockImplementationOnce(
251
+ async () =>
252
+ new Response(
253
+ JSON.stringify({
254
+ token_endpoint: "https://idcs.fallback/oauth2/v2/token",
255
+ }),
256
+ {
257
+ status: 200,
258
+ headers: { "Content-Type": "application/json" },
259
+ },
260
+ ),
261
+ )
262
+ .mockImplementationOnce(
263
+ async () =>
264
+ new Response(
265
+ JSON.stringify({
266
+ error: "server_error",
267
+ error_description: "temporary issue",
268
+ }),
269
+ {
270
+ status: 500,
271
+ headers: { "Content-Type": "application/json" },
272
+ },
273
+ ),
274
+ );
275
+ vi.stubGlobal("fetch", fetchMock);
276
+
277
+ const current = createCredentials({ expires: 10_000_000 });
278
+ const providerOptions = {
279
+ config: {
280
+ internal: {
281
+ clientId: "client-fallback-ttl",
282
+ idcsUrl: "https://idcs.fallback",
283
+ scopes: "openid offline_access",
284
+ baseUrl: "https://oca.example.com",
285
+ },
286
+ },
287
+ };
288
+
289
+ const first = await getValidOcaCredentials(
290
+ current,
291
+ { forceRefresh: true },
292
+ providerOptions,
293
+ );
294
+ expect(first).toBe(current);
295
+
296
+ nowSpy.mockReturnValue(200_000);
297
+ const second = await getValidOcaCredentials(
298
+ current,
299
+ { forceRefresh: true },
300
+ providerOptions,
301
+ );
302
+ expect(second).toBe(current);
303
+
304
+ nowSpy.mockReturnValue(500_001);
305
+ const third = await getValidOcaCredentials(
306
+ current,
307
+ { forceRefresh: true },
308
+ providerOptions,
309
+ );
310
+ expect(third).toBe(current);
311
+
312
+ expect(fetchMock).toHaveBeenCalledTimes(5);
313
+ expect(fetchMock).toHaveBeenNthCalledWith(
314
+ 1,
315
+ "https://idcs.fallback/.well-known/openid-configuration",
316
+ expect.objectContaining({ method: "GET" }),
317
+ );
318
+ expect(fetchMock).toHaveBeenNthCalledWith(
319
+ 2,
320
+ "https://idcs.fallback/oauth2/v1/token",
321
+ expect.objectContaining({ method: "POST" }),
322
+ );
323
+ expect(fetchMock).toHaveBeenNthCalledWith(
324
+ 3,
325
+ "https://idcs.fallback/oauth2/v1/token",
326
+ expect.objectContaining({ method: "POST" }),
327
+ );
328
+ expect(fetchMock).toHaveBeenNthCalledWith(
329
+ 4,
330
+ "https://idcs.fallback/.well-known/openid-configuration",
331
+ expect.objectContaining({ method: "GET" }),
332
+ );
333
+ expect(fetchMock).toHaveBeenNthCalledWith(
334
+ 5,
335
+ "https://idcs.fallback/oauth2/v2/token",
336
+ expect.objectContaining({ method: "POST" }),
337
+ );
338
+ nowSpy.mockRestore();
339
+ });
215
340
  });