thursday-agent 0.2.0 → 0.4.0

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 (541) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +5 -2
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +5 -5
  5. package/.next/routes-manifest.json +24 -6
  6. package/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_global-error.html +1 -1
  10. package/.next/server/app/_global-error.rsc +2 -2
  11. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page.js +2 -2
  15. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/_not-found.html +1 -1
  18. package/.next/server/app/_not-found.rsc +14 -14
  19. package/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next/server/app/api/artifact/route.js +3 -3
  23. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  24. package/.next/server/app/api/bot/memory/files/route/app-paths-manifest.json +3 -0
  25. package/.next/server/app/api/bot/memory/files/route.js +10 -0
  26. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -0
  27. package/.next/server/app/api/bot/memory/files/route_client-reference-manifest.js +3 -0
  28. package/.next/server/app/api/bot/memory/route/app-paths-manifest.json +3 -0
  29. package/.next/server/app/api/bot/memory/route.js +14 -0
  30. package/.next/server/app/api/bot/{notes → memory}/route.js.nft.json +1 -1
  31. package/.next/server/app/api/bot/memory/route_client-reference-manifest.js +3 -0
  32. package/.next/server/app/api/bot/route.js +6 -6
  33. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  34. package/.next/server/app/api/bot/task/[id]/route.js +8 -8
  35. package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
  36. package/.next/server/app/api/bot/task/route.js +8 -8
  37. package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
  38. package/.next/server/app/api/config/route.js +5 -5
  39. package/.next/server/app/api/config/route.js.nft.json +1 -1
  40. package/.next/server/app/api/events/route.js +4 -5
  41. package/.next/server/app/api/events/route.js.nft.json +1 -1
  42. package/.next/server/app/api/file/[...path]/route.js +3 -3
  43. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  44. package/.next/server/app/api/llm-model/catalog/route.js +9 -8
  45. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  46. package/.next/server/app/api/llm-model/credits/route/app-paths-manifest.json +3 -0
  47. package/.next/server/app/api/llm-model/credits/route/build-manifest.json +12 -0
  48. package/.next/server/app/api/llm-model/credits/route/server-reference-manifest.json +4 -0
  49. package/.next/server/app/api/llm-model/credits/route.js +16 -0
  50. package/.next/server/app/api/llm-model/credits/route.js.map +5 -0
  51. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -0
  52. package/.next/server/app/api/llm-model/credits/route_client-reference-manifest.js +3 -0
  53. package/.next/server/app/api/llm-model/route.js +10 -8
  54. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  55. package/.next/server/app/api/llm-model/subscription/route/app-paths-manifest.json +3 -0
  56. package/.next/server/app/api/llm-model/subscription/route/build-manifest.json +12 -0
  57. package/.next/server/app/api/llm-model/subscription/route/server-reference-manifest.json +4 -0
  58. package/.next/server/app/api/llm-model/subscription/route.js +15 -0
  59. package/.next/server/app/api/llm-model/subscription/route.js.map +5 -0
  60. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -0
  61. package/.next/server/app/api/llm-model/subscription/route_client-reference-manifest.js +3 -0
  62. package/.next/server/app/api/mcp/[name]/route.js +4 -4
  63. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  64. package/.next/server/app/api/mcp/oauth/callback/route.js +5 -5
  65. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  66. package/.next/server/app/api/mcp/route.js +4 -4
  67. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  68. package/.next/server/app/api/mcp/tools/route.js +4 -4
  69. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  70. package/.next/server/app/api/memory/edit/route/app-paths-manifest.json +3 -0
  71. package/.next/server/app/api/memory/edit/route/build-manifest.json +12 -0
  72. package/.next/server/app/api/memory/edit/route/server-reference-manifest.json +4 -0
  73. package/.next/server/app/api/memory/edit/route.js +24 -0
  74. package/.next/server/app/api/memory/edit/route.js.map +5 -0
  75. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -0
  76. package/.next/server/app/api/memory/{tidy → edit}/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/memory/route.js +6 -7
  78. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  79. package/.next/server/app/api/skills/[source]/[name]/route.js +3 -3
  80. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  81. package/.next/server/app/api/skills/route.js +3 -3
  82. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  83. package/.next/server/app/api/thursday/call/route.js +9 -7
  84. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  85. package/.next/server/app/api/thursday/skills/route.js +9 -7
  86. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  87. package/.next/server/app/api/thursday/tool-call/route.js +17 -15
  88. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  89. package/.next/server/app/api/workspace/route.js +4 -5
  90. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  91. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  92. package/.next/server/app/artifact/[...path]/page.js +5 -6
  93. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  94. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/favicon.ico/route.js +3 -4
  96. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  97. package/.next/server/app/favicon.ico.body +0 -0
  98. package/.next/server/app/page/server-reference-manifest.json +57 -90
  99. package/.next/server/app/page.js +10 -10
  100. package/.next/server/app/page.js.nft.json +1 -1
  101. package/.next/server/app/page_client-reference-manifest.js +1 -1
  102. package/.next/server/app-paths-manifest.json +5 -2
  103. package/.next/server/chunks/[externals]__0dzuk6_._.js +3 -0
  104. package/.next/server/chunks/[root-of-the-server]__01dkp4a._.js +3 -0
  105. package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +1 -1
  106. package/.next/server/chunks/[root-of-the-server]__02xjoto._.js +3 -0
  107. package/.next/server/chunks/[root-of-the-server]__04q--0f._.js +8 -0
  108. package/.next/server/chunks/[root-of-the-server]__0bl-e63._.js +3 -0
  109. package/.next/server/chunks/[root-of-the-server]__0ce8nf8._.js +5 -0
  110. package/.next/server/chunks/[root-of-the-server]__0ffbg85._.js +3 -0
  111. package/.next/server/chunks/[root-of-the-server]__0j7z0r3._.js +12 -0
  112. package/.next/server/chunks/[root-of-the-server]__0jq4ohy._.js +8 -0
  113. package/.next/server/chunks/[root-of-the-server]__0ls6_8y._.js +8 -0
  114. package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +3 -0
  115. package/.next/server/chunks/[root-of-the-server]__0pa2n68._.js +3 -0
  116. package/.next/server/chunks/[root-of-the-server]__0q0vvt2._.js +5 -0
  117. package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +5 -0
  118. package/.next/server/chunks/[root-of-the-server]__11n256_._.js +5 -0
  119. package/.next/server/chunks/[root-of-the-server]__12cabg9._.js +3 -0
  120. package/.next/server/chunks/[root-of-the-server]__1339jnv._.js +8 -0
  121. package/.next/server/chunks/[root-of-the-server]__15bjnu5._.js +5 -0
  122. package/.next/server/chunks/[root-of-the-server]__18_teo8._.js +8 -0
  123. package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +5 -0
  124. package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +3 -0
  125. package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +5 -5
  126. package/.next/server/chunks/[root-of-the-server]__1fizn31._.js +3 -0
  127. package/.next/server/chunks/[root-of-the-server]__1llj3-b._.js +3 -0
  128. package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +12 -0
  129. package/.next/server/chunks/[root-of-the-server]__1p8m3f4._.js +3 -0
  130. package/.next/server/chunks/[root-of-the-server]__1wa1k7r._.js +8 -0
  131. package/.next/server/chunks/_0-73of8._.js +5 -5
  132. package/.next/server/chunks/_0-fjql4._.js +26 -0
  133. package/.next/server/chunks/_00xh_q0._.js +25 -0
  134. package/.next/server/chunks/_051qh8u._.js +3 -0
  135. package/.next/server/chunks/{node_modules_next_04q-o58._.js → _05dla15._.js} +2 -2
  136. package/.next/server/chunks/_05h2cii._.js +1 -1
  137. package/.next/server/chunks/_0c9j6rb._.js +49 -0
  138. package/.next/server/chunks/_0fzqt2y._.js +134 -0
  139. package/.next/server/chunks/{_0vnutv9._.js → _0ioz3nd._.js} +1 -1
  140. package/.next/server/chunks/_0iwfby5._.js +3 -0
  141. package/.next/server/chunks/_0mecv0f._.js +26 -0
  142. package/.next/server/chunks/_0mh8j4m._.js +3 -0
  143. package/.next/server/chunks/_0n74zsf._.js +4 -4
  144. package/.next/server/chunks/_0obojvv._.js +3 -0
  145. package/.next/server/chunks/_0recufk._.js +49 -0
  146. package/.next/server/chunks/_0rnf-6n._.js +4 -4
  147. package/.next/server/chunks/_0sb7tlz._.js +3 -0
  148. package/.next/server/chunks/_0u10wmw._.js +5 -5
  149. package/.next/server/chunks/_0u4aiy2._.js +3 -0
  150. package/.next/server/chunks/_0wp4le3._.js +1 -1
  151. package/.next/server/chunks/_0y5fg-x._.js +1 -1
  152. package/.next/server/chunks/_1230wbx._.js +10 -0
  153. package/.next/server/chunks/_12nheja._.js +3 -0
  154. package/.next/server/chunks/_13510xh._.js +3 -0
  155. package/.next/server/chunks/_15hq89o._.js +1 -1
  156. package/.next/server/chunks/_17l5ygt._.js +49 -0
  157. package/.next/server/chunks/_18-1wcc._.js +5 -5
  158. package/.next/server/chunks/_1_5mnq4._.js +4 -4
  159. package/.next/server/chunks/_1_9mjx-._.js +3 -0
  160. package/.next/server/chunks/{_0pppbb5._.js → _1c78vw5._.js} +166 -37
  161. package/.next/server/chunks/_1d0j961._.js +6 -0
  162. package/.next/server/chunks/_1d5zmy8._.js +1 -1
  163. package/.next/server/chunks/_1drgfx4._.js +4 -4
  164. package/.next/server/chunks/_1f46eie._.js +3 -0
  165. package/.next/server/chunks/_1h5_8sk._.js +3 -0
  166. package/.next/server/chunks/_1hdnxb7._.js +3 -0
  167. package/.next/server/chunks/_1j8k_j5._.js +3 -0
  168. package/.next/server/chunks/_1krd9rf._.js +3 -0
  169. package/.next/server/chunks/_1nc0eui._.js +11 -0
  170. package/.next/server/chunks/_1nula3v._.js +3 -0
  171. package/.next/server/chunks/_1pf4qe-._.js +3 -0
  172. package/.next/server/chunks/_1php4eq._.js +1 -1
  173. package/.next/server/chunks/_1qxheoa._.js +3 -0
  174. package/.next/server/chunks/_1x6kd1r._.js +3 -0
  175. package/.next/server/chunks/_1yuv4vp._.js +1 -1
  176. package/.next/server/chunks/_1zjrdag._.js +3 -0
  177. package/.next/server/chunks/_next-internal_server_app_api_bot_memory_files_route_actions_1_xd_8f.js +3 -0
  178. package/.next/server/chunks/_next-internal_server_app_api_bot_memory_route_actions_1a8xfr7.js +3 -0
  179. package/.next/server/chunks/_next-internal_server_app_api_llm-model_credits_route_actions_0v424pb.js +3 -0
  180. package/.next/server/chunks/_next-internal_server_app_api_llm-model_subscription_route_actions_0-udcm5.js +3 -0
  181. package/.next/server/chunks/_next-internal_server_app_api_memory_edit_route_actions_02sfnmr.js +3 -0
  182. package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
  183. package/.next/server/chunks/features_0rpfxbk._.js +3 -0
  184. package/.next/server/chunks/features_1qf3cei._.js +3 -0
  185. package/.next/server/chunks/features_bot_0zt92oh._.js +3 -0
  186. package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +3 -0
  187. package/.next/server/chunks/features_memory_1uqvksn._.js +6 -0
  188. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0a1rd09._.js → node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js} +1 -1
  189. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_06067w6._.js → node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js} +1 -1
  190. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_096ais_._.js → node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js} +1 -1
  191. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_02s3hjt._.js → node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js} +1 -1
  192. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1xoq2rl._.js +3 -0
  193. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tvkfgh._.js → node_modules_consola_dist_chunks_prompt_mjs_1y49572._.js} +1 -1
  194. package/.next/server/chunks/ssr/[root-of-the-server]__092knlj._.js +3 -0
  195. package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
  196. package/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
  197. package/.next/server/chunks/ssr/[root-of-the-server]__0i8ie7f._.js +3 -0
  198. package/.next/server/chunks/ssr/{[root-of-the-server]__0jsmmb-._.js → [root-of-the-server]__0mz33a7._.js} +1 -1
  199. package/.next/server/chunks/ssr/[root-of-the-server]__0s9dq5u._.js +3 -0
  200. package/.next/server/chunks/ssr/{[root-of-the-server]__0m1b5ky._.js → [root-of-the-server]__0s9l2sv._.js} +1 -1
  201. package/.next/server/chunks/ssr/[root-of-the-server]__0tbkdlx._.js +3 -0
  202. package/.next/server/chunks/ssr/[root-of-the-server]__0vnmv-s._.js +3 -0
  203. package/.next/server/chunks/ssr/{[root-of-the-server]__0f6s48x._.js → [root-of-the-server]__0xpcv9w._.js} +3 -3
  204. package/.next/server/chunks/ssr/[root-of-the-server]__0xsog8n._.js +3 -0
  205. package/.next/server/chunks/ssr/[root-of-the-server]__0xtssj3._.js +1 -1
  206. package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +61 -0
  207. package/.next/server/chunks/ssr/[root-of-the-server]__15_uhip._.js +3 -0
  208. package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
  209. package/.next/server/chunks/ssr/[root-of-the-server]__1gtlw6v._.js +43 -0
  210. package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
  211. package/.next/server/chunks/ssr/[root-of-the-server]__1ox7hbk._.js +4 -0
  212. package/.next/server/chunks/ssr/{[root-of-the-server]__1qfqvmg._.js → [root-of-the-server]__202p40z._.js} +2 -2
  213. package/.next/server/chunks/ssr/{_1mznrxk._.js → _001_ms1._.js} +3 -3
  214. package/.next/server/chunks/ssr/_01n3o0m._.js +3 -0
  215. package/.next/server/chunks/ssr/_0ap2fzn._.js +3 -0
  216. package/.next/server/chunks/ssr/_0fz4mf_._.js +3 -0
  217. package/.next/server/chunks/ssr/_0gb4gmf._.js +3 -0
  218. package/.next/server/chunks/ssr/_0h42fze._.js +3 -0
  219. package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
  220. package/.next/server/chunks/ssr/_0i_6q1w._.js +41 -0
  221. package/.next/server/chunks/ssr/_0ilna52._.js +42 -0
  222. package/.next/server/chunks/ssr/_0n3toaw._.js +3 -0
  223. package/.next/server/chunks/ssr/{_13punpm._.js → _0q0zan6._.js} +2 -2
  224. package/.next/server/chunks/ssr/{_077-g6s._.js → _0s99bgb._.js} +3 -3
  225. package/.next/server/chunks/ssr/_0zcjrbg._.js +3 -0
  226. package/.next/server/chunks/ssr/_0zhoc3r._.js +3 -0
  227. package/.next/server/chunks/ssr/{_1lip4th._.js → _1-_etts._.js} +2 -2
  228. package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
  229. package/.next/server/chunks/ssr/_13fke0x._.js +3 -0
  230. package/.next/server/chunks/ssr/_15j2_8f._.js +3 -0
  231. package/.next/server/chunks/ssr/_1b2mjwr._.js +3 -0
  232. package/.next/server/chunks/ssr/{_09ja_w1._.js → _1f-dg-6._.js} +2 -2
  233. package/.next/server/chunks/ssr/_1fojd41._.js +4 -0
  234. package/.next/server/chunks/ssr/_1gd8q8l._.js +3 -0
  235. package/.next/server/chunks/ssr/{_1m3bsfr._.js → _1h0rw11._.js} +2 -2
  236. package/.next/server/chunks/ssr/_1jhd09t._.js +3 -0
  237. package/.next/server/chunks/ssr/{_040sk32._.js → _1lz_cq2._.js} +84 -88
  238. package/.next/server/chunks/ssr/_1nnsk7z._.js +3 -0
  239. package/.next/server/chunks/ssr/_1sji22n._.js +3 -0
  240. package/.next/server/chunks/ssr/{_0x_nu5e._.js → _1vi_uo7._.js} +2 -2
  241. package/.next/server/chunks/ssr/_1vyd1ji._.js +1 -1
  242. package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +3 -0
  243. package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
  244. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  245. package/.next/server/chunks/ssr/{node_modules_0kxz4gt._.js → node_modules_00etblx._.js} +1 -1
  246. package/.next/server/chunks/ssr/{node_modules_18_dk7_._.js → node_modules_04jf_m8._.js} +1 -1
  247. package/.next/server/chunks/ssr/{node_modules_0_n241r._.js → node_modules_08dhkiq._.js} +1 -1
  248. package/.next/server/chunks/ssr/{node_modules_0n1kj4p._.js → node_modules_0940ns5._.js} +1 -1
  249. package/.next/server/chunks/ssr/{node_modules_17lrt3y._.js → node_modules_09q35v_._.js} +1 -1
  250. package/.next/server/chunks/ssr/{node_modules_1toafc-._.js → node_modules_0_-_a--._.js} +1 -1
  251. package/.next/server/chunks/ssr/{node_modules_0lrjk5t._.js → node_modules_0arus10._.js} +1 -1
  252. package/.next/server/chunks/ssr/{node_modules_04n7mjs._.js → node_modules_0b__sgl._.js} +1 -1
  253. package/.next/server/chunks/ssr/{node_modules_0iz1rzd._.js → node_modules_0cxaxk9._.js} +1 -1
  254. package/.next/server/chunks/ssr/{node_modules_0ndvxub._.js → node_modules_0g7fzk3._.js} +1 -1
  255. package/.next/server/chunks/ssr/{node_modules_0kl7-tn._.js → node_modules_0had7xd._.js} +1 -1
  256. package/.next/server/chunks/ssr/{node_modules_19dp7ko._.js → node_modules_0pvd7q-._.js} +1 -1
  257. package/.next/server/chunks/ssr/node_modules_0x3-ply._.js +3 -0
  258. package/.next/server/chunks/ssr/{node_modules_1dwguw_._.js → node_modules_0yugy6m._.js} +1 -1
  259. package/.next/server/chunks/ssr/{node_modules_004qlr4._.js → node_modules_14om4ar._.js} +1 -1
  260. package/.next/server/chunks/ssr/{node_modules_1q0p85z._.js → node_modules_14q6g4n._.js} +1 -1
  261. package/.next/server/chunks/ssr/node_modules_17bwo43._.js +55 -0
  262. package/.next/server/chunks/ssr/{node_modules_1hd-5tv._.js → node_modules_198nmll._.js} +1 -1
  263. package/.next/server/chunks/ssr/{node_modules_0m93zeg._.js → node_modules_1_pc9kq._.js} +1 -1
  264. package/.next/server/chunks/ssr/{node_modules_1n5ygqe._.js → node_modules_1aaxuef._.js} +1 -1
  265. package/.next/server/chunks/ssr/{node_modules_01qnv-a._.js → node_modules_1cx_ql6._.js} +1 -1
  266. package/.next/server/chunks/ssr/{node_modules_1zu988w._.js → node_modules_1cz2hle._.js} +1 -1
  267. package/.next/server/chunks/ssr/{node_modules_1xyd7ov._.js → node_modules_1efrqxi._.js} +1 -1
  268. package/.next/server/chunks/ssr/{node_modules_13r44jq._.js → node_modules_1eg7m6c._.js} +1 -1
  269. package/.next/server/chunks/ssr/{node_modules_08d8bke._.js → node_modules_1gdh31p._.js} +1 -1
  270. package/.next/server/chunks/ssr/{node_modules_1hn8z1f._.js → node_modules_1hpopmo._.js} +1 -1
  271. package/.next/server/chunks/ssr/{node_modules_20yebtj._.js → node_modules_1qpjvy5._.js} +1 -1
  272. package/.next/server/chunks/ssr/{node_modules_09xhur7._.js → node_modules_1vh5ntv._.js} +1 -1
  273. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0m9a4y0._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1b748ic._.js} +1 -1
  274. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_13w_wy9._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1z4hm5e._.js} +1 -1
  275. package/.next/server/chunks/ssr/node_modules_@vercel_oidc_dist_06mk8id._.js +3 -0
  276. package/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
  277. package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +2 -2
  278. package/.next/server/instrumentation.js +1 -1
  279. package/.next/server/middleware-build-manifest.js +3 -3
  280. package/.next/server/pages/404.html +1 -1
  281. package/.next/server/pages/500.html +1 -1
  282. package/.next/server/server-reference-manifest.js +1 -1
  283. package/.next/server/server-reference-manifest.json +58 -91
  284. package/.next/static/chunks/00rd4-v8r7axp.js +1 -0
  285. package/.next/static/chunks/08qe4as2q428z.js +3 -0
  286. package/.next/static/chunks/{31ttnjt6g35a0.js → 0ev4hyghq0mbb.js} +1 -1
  287. package/.next/static/chunks/0fl3gwe4-l6tz.js +1 -0
  288. package/.next/static/chunks/0gnqdjn2lvdad.js +2 -0
  289. package/.next/static/chunks/0hwap1-p0m5sa.js +1 -0
  290. package/.next/static/chunks/0r5qcctujhxli.js +1 -0
  291. package/.next/static/chunks/0w52rrrmuj9r9.js +1 -0
  292. package/.next/static/chunks/{07kl352c-lvcz.js → 0wcprt5ot5she.js} +1 -1
  293. package/.next/static/chunks/10teww7wt-f6h.js +1 -0
  294. package/.next/static/chunks/15m575979un5f.js +38 -0
  295. package/.next/static/chunks/17p6tk6k88dq6.js +1 -0
  296. package/.next/static/chunks/{3_h9txt3tq8t8.js → 1dkjg87pxk0n9.js} +1 -1
  297. package/.next/static/chunks/1jd09sxcjddu1.js +1 -0
  298. package/.next/static/chunks/1kq8h8wi_e0bc.js +53 -0
  299. package/.next/static/chunks/{18vhba44l1fc9.js → 1w73ece_6bheu.js} +1 -1
  300. package/.next/static/chunks/{3ard_noqapnw1.js → 214ya3n5gxeih.js} +1 -1
  301. package/.next/static/chunks/22_zkas21rpna.js +1 -0
  302. package/.next/static/chunks/2h0sd2vumhb15.js +1 -0
  303. package/.next/static/chunks/2t4vhi3m8ahya.js +1 -0
  304. package/.next/static/chunks/2tigdtz3dpcy8.js +1 -0
  305. package/.next/static/chunks/2u7xt6mxg44lf.js +1 -0
  306. package/.next/static/chunks/39j0fvpybzabl.js +40 -0
  307. package/.next/static/chunks/3akmqm77muj4-.js +1 -0
  308. package/.next/static/chunks/3cq5g494yuke8.css +1 -0
  309. package/.next/static/chunks/3woj-xd7ki5_i.js +1 -0
  310. package/.next/static/chunks/3xk7yqgomx7yq.js +1 -0
  311. package/.next/static/chunks/3zmcsnuo25zvq.js +1 -0
  312. package/.next/static/media/favicon.37o1pjldwi2uc.ico +0 -0
  313. package/README.md +47 -22
  314. package/app/api/bot/memory/files/route.ts +14 -0
  315. package/app/api/bot/memory/route.ts +5 -0
  316. package/app/api/events/app-event.ts +3 -7
  317. package/app/api/llm-model/credits/route.ts +11 -0
  318. package/app/api/llm-model/route.ts +6 -1
  319. package/app/api/llm-model/subscription/route.ts +11 -0
  320. package/app/api/memory/edit/route.ts +7 -0
  321. package/app/api/memory/route.ts +21 -0
  322. package/app/api/query-key.ts +19 -4
  323. package/bin/port.mjs +65 -0
  324. package/bin/thursday.mjs +5 -33
  325. package/components/ui/shiny-text.tsx +48 -134
  326. package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/migration.sql +24 -34
  327. package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/snapshot.json +71 -145
  328. package/database/migrations/20260911030320_opposite_jack_power/migration.sql +1 -0
  329. package/database/migrations/{20260909063821_bitter_tenebrous → 20260911030320_opposite_jack_power}/snapshot.json +57 -184
  330. package/docs/how-it-works.md +11 -7
  331. package/docs/images/face-dark.png +0 -0
  332. package/docs/images/face-light.png +0 -0
  333. package/docs/images/keeps-going.png +0 -0
  334. package/features/ai/chatgpt.ts +639 -0
  335. package/features/ai/components/chatgpt-sign-in.tsx +66 -0
  336. package/features/ai/components/model-picker.tsx +17 -1
  337. package/features/ai/components/provider-icon.tsx +1 -0
  338. package/features/ai/load-tools.ts +78 -56
  339. package/features/ai/model.schema.ts +232 -34
  340. package/features/ai/model.ts +169 -24
  341. package/features/ai/prompts/bot.prompt.ts +207 -114
  342. package/features/ai/prompts/memory-edit.prompt.ts +33 -0
  343. package/features/ai/prompts/prompt-helper.ts +45 -26
  344. package/features/ai/prompts/thursday.prompt.ts +57 -27
  345. package/features/ai/tools/bot.tool.ts +35 -52
  346. package/features/ai/tools/connected.ts +17 -4
  347. package/features/ai/tools/memory.tool.ts +181 -117
  348. package/features/ai/tools/search.tool.ts +136 -47
  349. package/features/ai/tools/tool-name.ts +2 -6
  350. package/features/ai/tools/workspace.tool.ts +5 -1
  351. package/features/bot/bot.action.ts +14 -19
  352. package/features/bot/bot.memory.ts +89 -0
  353. package/features/bot/bot.query.ts +37 -57
  354. package/features/bot/bot.run.ts +512 -242
  355. package/features/bot/bot.runner.ts +434 -120
  356. package/features/bot/bot.schema.ts +104 -19
  357. package/features/bot/bot.seed.ts +89 -16
  358. package/features/bot/components/bot-mark.tsx +122 -20
  359. package/features/bot/components/bot-room.tsx +214 -132
  360. package/features/bot/components/bot-setting.tsx +790 -402
  361. package/features/bot/components/bot-tool.tsx +2 -7
  362. package/features/bot/components/mark-palette.tsx +54 -0
  363. package/features/bot/components/path-chips.tsx +1 -1
  364. package/features/bot/components/task-badge.tsx +26 -6
  365. package/features/bot/components/task-reply.tsx +22 -33
  366. package/features/bot/components/task-setting.tsx +20 -17
  367. package/features/bot/task.query.ts +240 -34
  368. package/features/bot/task.store.ts +76 -13
  369. package/features/config/components/config-setting.tsx +265 -34
  370. package/features/config/components/models-badge.tsx +42 -0
  371. package/features/config/config.action.ts +12 -0
  372. package/features/config/config.const.ts +38 -2
  373. package/features/connectors/mcp.manager.ts +10 -2
  374. package/features/intro/components/intro.tsx +47 -46
  375. package/features/memory/components/memory-edit.tsx +340 -0
  376. package/features/memory/components/memory-setting.tsx +75 -77
  377. package/features/memory/memory.action.ts +3 -43
  378. package/features/memory/memory.edit.ts +87 -0
  379. package/features/memory/memory.query.ts +24 -42
  380. package/features/memory/memory.schema.ts +29 -106
  381. package/features/settings/components/settings.tsx +2 -0
  382. package/features/settings/settings.alert.ts +3 -1
  383. package/features/thursday/components/ascii-orb.tsx +106 -90
  384. package/features/thursday/components/boot.tsx +7 -0
  385. package/features/thursday/components/call-log.tsx +219 -92
  386. package/features/thursday/components/face.tsx +6 -4
  387. package/features/thursday/components/tab-state.tsx +97 -0
  388. package/features/thursday/components/thursday-mark.tsx +4 -1
  389. package/features/thursday/components/thursday-setting.tsx +17 -47
  390. package/features/thursday/components/thursday.tsx +208 -98
  391. package/features/thursday/thursday.action.ts +12 -17
  392. package/features/thursday/thursday.query.ts +61 -70
  393. package/features/thursday/thursday.schema.ts +20 -7
  394. package/features/thursday/tool-line.ts +85 -12
  395. package/features/thursday/use-thursday.ts +87 -60
  396. package/features/workspace/components/file-view.tsx +4 -2
  397. package/features/workspace/workspace.ts +110 -26
  398. package/lib/limits.ts +1 -1
  399. package/lib/oauth-page.ts +27 -0
  400. package/lib/realtime/openai.ts +2 -2
  401. package/lib/realtime/realtime.protocol.ts +1 -1
  402. package/lib/realtime/realtime.tap.ts +122 -0
  403. package/lib/sandbox.ts +59 -36
  404. package/package.json +1 -1
  405. package/skills/browser/SKILL.md +55 -34
  406. package/.next/server/app/api/bot/notes/route/app-paths-manifest.json +0 -3
  407. package/.next/server/app/api/bot/notes/route.js +0 -14
  408. package/.next/server/app/api/bot/notes/route_client-reference-manifest.js +0 -3
  409. package/.next/server/app/api/memory/tidy/route/app-paths-manifest.json +0 -3
  410. package/.next/server/app/api/memory/tidy/route.js +0 -22
  411. package/.next/server/app/api/memory/tidy/route.js.nft.json +0 -1
  412. package/.next/server/chunks/0d-b_next_dist_server_app-render_after-task-async-storage_external_0-exn7p.js +0 -3
  413. package/.next/server/chunks/[root-of-the-server]__0-npkh_._.js +0 -3
  414. package/.next/server/chunks/[root-of-the-server]__005var5._.js +0 -3
  415. package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +0 -5
  416. package/.next/server/chunks/[root-of-the-server]__02_2sm-._.js +0 -5
  417. package/.next/server/chunks/[root-of-the-server]__04_8spk._.js +0 -3
  418. package/.next/server/chunks/[root-of-the-server]__05luceg._.js +0 -3
  419. package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +0 -11
  420. package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +0 -8
  421. package/.next/server/chunks/[root-of-the-server]__0cro2dz._.js +0 -5
  422. package/.next/server/chunks/[root-of-the-server]__0dupqt7._.js +0 -5
  423. package/.next/server/chunks/[root-of-the-server]__0emr6go._.js +0 -3
  424. package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +0 -3
  425. package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +0 -10
  426. package/.next/server/chunks/[root-of-the-server]__0nfuugi._.js +0 -5
  427. package/.next/server/chunks/[root-of-the-server]__0p32jfz._.js +0 -12
  428. package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +0 -8
  429. package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +0 -3
  430. package/.next/server/chunks/[root-of-the-server]__10qshki._.js +0 -3
  431. package/.next/server/chunks/[root-of-the-server]__11j52t0._.js +0 -5
  432. package/.next/server/chunks/[root-of-the-server]__1692zrv._.js +0 -3
  433. package/.next/server/chunks/[root-of-the-server]__16thabu._.js +0 -3
  434. package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +0 -8
  435. package/.next/server/chunks/[root-of-the-server]__1_aoyr9._.js +0 -3
  436. package/.next/server/chunks/[root-of-the-server]__1bn73ih._.js +0 -10
  437. package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +0 -8
  438. package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +0 -5
  439. package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +0 -3
  440. package/.next/server/chunks/[root-of-the-server]__1fboj2j._.js +0 -3
  441. package/.next/server/chunks/[root-of-the-server]__1lr7j9t._.js +0 -5
  442. package/.next/server/chunks/[root-of-the-server]__1oli73c._.js +0 -3
  443. package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +0 -8
  444. package/.next/server/chunks/[root-of-the-server]__1untqv8._.js +0 -5
  445. package/.next/server/chunks/[root-of-the-server]__1zgpz1w._.js +0 -3
  446. package/.next/server/chunks/_00_584r._.js +0 -293
  447. package/.next/server/chunks/_012yhfm._.js +0 -39
  448. package/.next/server/chunks/_06mxlgn._.js +0 -3
  449. package/.next/server/chunks/_07rtlxv._.js +0 -3
  450. package/.next/server/chunks/_0a351g7._.js +0 -6
  451. package/.next/server/chunks/_0kjjmag._.js +0 -3
  452. package/.next/server/chunks/_0nn99o2._.js +0 -3
  453. package/.next/server/chunks/_0zuzmt_._.js +0 -39
  454. package/.next/server/chunks/_106co2o._.js +0 -3
  455. package/.next/server/chunks/_12bbv5i._.js +0 -100
  456. package/.next/server/chunks/_15q1pig._.js +0 -3
  457. package/.next/server/chunks/_19wg5yn._.js +0 -138
  458. package/.next/server/chunks/_1bt95p9._.js +0 -55
  459. package/.next/server/chunks/_1cbk7ms._.js +0 -26
  460. package/.next/server/chunks/_1drv2et._.js +0 -40
  461. package/.next/server/chunks/_1ic3bew._.js +0 -5
  462. package/.next/server/chunks/_1mxn_lb._.js +0 -3
  463. package/.next/server/chunks/_1rysr-r._.js +0 -10
  464. package/.next/server/chunks/_1u3rwbr._.js +0 -3
  465. package/.next/server/chunks/_1x7p319._.js +0 -6
  466. package/.next/server/chunks/_next-internal_server_app_api_bot_notes_route_actions_0qcsyu7.js +0 -3
  467. package/.next/server/chunks/_next-internal_server_app_api_memory_tidy_route_actions_0_cfuom.js +0 -3
  468. package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +0 -3
  469. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js +0 -3
  470. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0vorg6w._.js +0 -3
  471. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1tpvamf._.js +0 -3
  472. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vmaq9f._.js +0 -3
  473. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y9-wcr._.js +0 -3
  474. package/.next/server/chunks/ssr/[externals]_node_crypto_1kn79-7._.js +0 -3
  475. package/.next/server/chunks/ssr/[root-of-the-server]__0iuoa-r._.js +0 -3
  476. package/.next/server/chunks/ssr/[root-of-the-server]__0un-erv._.js +0 -4
  477. package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +0 -92
  478. package/.next/server/chunks/ssr/[root-of-the-server]__1bww500._.js +0 -3
  479. package/.next/server/chunks/ssr/[root-of-the-server]__1vcdast._.js +0 -3
  480. package/.next/server/chunks/ssr/[root-of-the-server]__1xwnu-h._.js +0 -3
  481. package/.next/server/chunks/ssr/_0-mc7ro._.js +0 -3
  482. package/.next/server/chunks/ssr/_01p5r6-._.js +0 -3
  483. package/.next/server/chunks/ssr/_03webdr._.js +0 -3
  484. package/.next/server/chunks/ssr/_09-0k3z._.js +0 -3
  485. package/.next/server/chunks/ssr/_0f_b_9s._.js +0 -3
  486. package/.next/server/chunks/ssr/_0fafgvl._.js +0 -3
  487. package/.next/server/chunks/ssr/_0sf1isi._.js +0 -3
  488. package/.next/server/chunks/ssr/_0su6_08._.js +0 -3
  489. package/.next/server/chunks/ssr/_0ti51xc._.js +0 -3
  490. package/.next/server/chunks/ssr/_0w5bgak._.js +0 -3
  491. package/.next/server/chunks/ssr/_0y84qo6._.js +0 -3
  492. package/.next/server/chunks/ssr/_1ciyoo3._.js +0 -3
  493. package/.next/server/chunks/ssr/_1e-dgz0._.js +0 -4
  494. package/.next/server/chunks/ssr/_1fq7zgb._.js +0 -3
  495. package/.next/server/chunks/ssr/_1g6l4uj._.js +0 -3
  496. package/.next/server/chunks/ssr/_1ruzehw._.js +0 -3
  497. package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +0 -4
  498. package/.next/server/chunks/ssr/features_config_components_14jqgy4._.js +0 -3
  499. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +0 -4
  500. package/.next/server/chunks/ssr/node_modules_zod_v4_classic_external_15fmmz3.js +0 -55
  501. package/.next/static/chunks/05b6nx_o3_f_n.js +0 -1
  502. package/.next/static/chunks/0dmns-gi0raa4.js +0 -1
  503. package/.next/static/chunks/0f9sq-7q_yxyl.js +0 -1
  504. package/.next/static/chunks/0oc4_xoia77dg.css +0 -1
  505. package/.next/static/chunks/0pheo8m3a-rzi.js +0 -1
  506. package/.next/static/chunks/19i8h2t-5evjx.js +0 -1
  507. package/.next/static/chunks/1c5fcj473025q.js +0 -1
  508. package/.next/static/chunks/1pi_hbmjw-f-z.js +0 -1
  509. package/.next/static/chunks/1twt6k09vg4np.js +0 -1
  510. package/.next/static/chunks/1ytnip8wdng93.js +0 -1
  511. package/.next/static/chunks/28gotuisldmom.js +0 -1
  512. package/.next/static/chunks/2998p0y7k1c8z.js +0 -3
  513. package/.next/static/chunks/29gyb3nubj51j.js +0 -53
  514. package/.next/static/chunks/2l233xfycjpd8.js +0 -1
  515. package/.next/static/chunks/2p5n7pa3c40t-.js +0 -2
  516. package/.next/static/chunks/2ru1q132af7h7.js +0 -1
  517. package/.next/static/chunks/2t8wqycwg3y04.js +0 -2
  518. package/.next/static/chunks/2uxum7qbyeyxd.js +0 -1
  519. package/.next/static/chunks/32z_ypgtchw48.js +0 -1
  520. package/.next/static/chunks/33pyjb3xd1iez.js +0 -1
  521. package/.next/static/chunks/3omud5xunrk1f.js +0 -1
  522. package/.next/static/chunks/42dnrkbph9e-1.js +0 -1
  523. package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  524. package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +0 -1
  525. package/features/ai/prompts/memory-tidy.prompt.ts +0 -129
  526. package/features/ai/tools/tidy.tool.ts +0 -22
  527. package/features/bot/bot.notes.ts +0 -120
  528. package/features/memory/components/memory-tidy.tsx +0 -395
  529. package/features/memory/components/memory-view.tsx +0 -84
  530. package/features/memory/memory.tidy.ts +0 -332
  531. package/features/memory/tidy.query.ts +0 -135
  532. package/features/thursday/use-call-title.ts +0 -31
  533. /package/.next/server/app/api/bot/{notes → memory/files}/route/build-manifest.json +0 -0
  534. /package/.next/server/app/api/bot/{notes → memory/files}/route/server-reference-manifest.json +0 -0
  535. /package/.next/server/app/api/bot/{notes → memory/files}/route.js.map +0 -0
  536. /package/.next/server/app/api/{memory/tidy → bot/memory}/route/build-manifest.json +0 -0
  537. /package/.next/server/app/api/{memory/tidy → bot/memory}/route/server-reference-manifest.json +0 -0
  538. /package/.next/server/app/api/{memory/tidy → bot/memory}/route.js.map +0 -0
  539. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → kaTO0UHqF1bMnWgCDik2f}/_buildManifest.js +0 -0
  540. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → kaTO0UHqF1bMnWgCDik2f}/_clientMiddlewareManifest.js +0 -0
  541. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → kaTO0UHqF1bMnWgCDik2f}/_ssgManifest.js +0 -0
@@ -0,0 +1,639 @@
1
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
2
+ import { createServer, type RequestListener, type Server } from "node:http";
3
+ import { createOpenAI } from "@ai-sdk/openai";
4
+ import { type LanguageModel, wrapLanguageModel } from "ai";
5
+ import { formatDistanceToNowStrict } from "date-fns";
6
+ import { z } from "zod";
7
+ import { appEvents } from "@/app/api/events/app-event.server";
8
+ import { CHATGPT_SIGN_IN, CHATGPT_USAGE_HIGH } from "@/config";
9
+ import { readConfig, writeConfig } from "@/features/config/config.query";
10
+ import { logger } from "@/lib/logger";
11
+ import { oauthPage } from "@/lib/oauth-page";
12
+ import { publicError } from "@/lib/public-error";
13
+ import { createKeyedLock } from "@/lib/queue";
14
+ import { errorToString } from "@/lib/utils";
15
+ import { type SubscriptionUsage, TEXT_MODEL_PROVIDERS } from "./model.schema";
16
+
17
+ /**
18
+ * ChatGPT as a text provider: the Codex usage a ChatGPT plan carries, in place of an API key.
19
+ * Signing in is OpenAI's OAuth for the Codex CLI, the way other agent apps reach a plan. The
20
+ * model is the Responses API behind chatgpt.com, which takes less than api.openai.com does;
21
+ * `codexFetch` is the difference.
22
+ */
23
+
24
+ /** The Codex CLI's public OAuth client. */
25
+ const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
26
+ const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
27
+ const TOKEN_URL = "https://auth.openai.com/oauth/token";
28
+ /** The only redirect registered for that client, so the answer comes back to this port and no other. */
29
+ const CALLBACK_PORT = 1455;
30
+ const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}/auth/callback`;
31
+ /**
32
+ * Both loopbacks: `localhost` in the browser reaches whichever answers first, and an
33
+ * address bound here outranks another app's `::` or `0.0.0.0` on the same port — so the
34
+ * answer cannot land in someone else's server.
35
+ */
36
+ const CALLBACK_HOSTS = ["127.0.0.1", "::1"];
37
+ const SCOPE = "openid profile email offline_access";
38
+ const CODEX_URL = "https://chatgpt.com/backend-api/codex";
39
+ /** The access token's claim naming the account and its plan. */
40
+ const AUTH_CLAIM = "https://api.openai.com/auth";
41
+ /** How this app names itself to OpenAI. */
42
+ const ORIGINATOR = "thursday";
43
+
44
+ const { apiKeyName: SIGN_IN_KEY, label: LABEL } = TEXT_MODEL_PROVIDERS.chatgpt;
45
+ const NOT_SIGNED_IN = `${LABEL} is not signed in — sign in from Config.`;
46
+
47
+ /** What a sign-in leaves in config. Tokens: it never reaches the browser. */
48
+ const SignInSchema = z.object({
49
+ access: z.string().min(1),
50
+ refresh: z.string().min(1),
51
+ /** When the access token runs out, epoch ms. */
52
+ expires: z.number(),
53
+ accountId: z.string().min(1),
54
+ /** The plan as the token names it ("free", "plus", "pro"); null when it does not say. */
55
+ plan: z.string().nullable(),
56
+ });
57
+ type SignIn = z.infer<typeof SignInSchema>;
58
+
59
+ type Pinned = {
60
+ __chatgptListeners?: Server[] | null;
61
+ __chatgptRenewal?: ReturnType<typeof createKeyedLock>;
62
+ };
63
+
64
+ /** Pinned to globalThis: next dev reloads this module, and the listeners holding the port and the renewal lane must be the originals. */
65
+ const pinned = globalThis as Pinned;
66
+
67
+ /**
68
+ * One renewal at a time. A refresh token is spent by using it, so two requests renewing at
69
+ * once leave one holding a token OpenAI has already retired — which signs the app out.
70
+ */
71
+ const renewal = (pinned.__chatgptRenewal ??= createKeyedLock());
72
+
73
+ /**
74
+ * Starts a sign-in and returns the page to send the browser to. The answer comes back to the
75
+ * loopback port, so the app listens there until it arrives or `CHATGPT_SIGN_IN.waitMs` passes;
76
+ * starting again replaces a sign-in still waiting.
77
+ */
78
+ export async function startChatGptSignIn(): Promise<string> {
79
+ stopListening();
80
+ const verifier = randomBytes(32).toString("base64url");
81
+ const state = randomBytes(16).toString("hex");
82
+
83
+ const servers: Server[] = [];
84
+ const answer: RequestListener = (request, response) => {
85
+ void answerCallback(request.url ?? "/", { state, verifier }).then(
86
+ (page) => {
87
+ response.writeHead(page.status, {
88
+ "content-type": "text/html; charset=utf-8",
89
+ });
90
+ response.end(
91
+ oauthPage(page.title, page.detail, {
92
+ autoClose: page.status === 200,
93
+ }),
94
+ () => {
95
+ if (page.last && pinned.__chatgptListeners === servers) {
96
+ stopListening();
97
+ }
98
+ },
99
+ );
100
+ },
101
+ );
102
+ };
103
+
104
+ for (const host of CALLBACK_HOSTS) {
105
+ const server = createServer(answer);
106
+ const failure = await new Promise<NodeJS.ErrnoException | null>(
107
+ (resolve) => {
108
+ server.once("error", resolve);
109
+ server.listen(CALLBACK_PORT, host, () => resolve(null));
110
+ },
111
+ );
112
+ if (!failure) {
113
+ servers.push(server);
114
+ continue;
115
+ }
116
+ // No IPv6 on this machine: nobody else can answer there either
117
+ if (failure.code === "EADDRNOTAVAIL" || failure.code === "EAFNOSUPPORT") {
118
+ continue;
119
+ }
120
+ for (const bound of servers) bound.close();
121
+ publicError(
122
+ failure.code === "EADDRINUSE"
123
+ ? `Port ${CALLBACK_PORT} is in use by another app — often a ChatGPT or Codex sign-in still waiting. Finish or close it, then try again.`
124
+ : `Could not wait for the sign-in: ${errorToString(failure)}`,
125
+ );
126
+ }
127
+ pinned.__chatgptListeners = servers;
128
+ // A page nobody finishes must not hold the port for the life of the process
129
+ setTimeout(() => {
130
+ if (pinned.__chatgptListeners === servers) stopListening();
131
+ }, CHATGPT_SIGN_IN.waitMs).unref();
132
+
133
+ const url = new URL(AUTHORIZE_URL);
134
+ url.search = new URLSearchParams({
135
+ response_type: "code",
136
+ client_id: CLIENT_ID,
137
+ redirect_uri: REDIRECT_URI,
138
+ scope: SCOPE,
139
+ code_challenge: createHash("sha256").update(verifier).digest("base64url"),
140
+ code_challenge_method: "S256",
141
+ state,
142
+ // What the Codex CLI asks for: the account's organizations in the token, and the short consent page
143
+ id_token_add_organizations: "true",
144
+ codex_cli_simplified_flow: "true",
145
+ originator: ORIGINATOR,
146
+ }).toString();
147
+ return url.toString();
148
+ }
149
+
150
+ function stopListening() {
151
+ const servers = pinned.__chatgptListeners;
152
+ pinned.__chatgptListeners = null;
153
+ for (const server of servers ?? []) {
154
+ server.close();
155
+ server.closeIdleConnections();
156
+ }
157
+ }
158
+
159
+ type CallbackPage = {
160
+ status: number;
161
+ title: string;
162
+ detail: string;
163
+ /** The sign-in is over, however it went, so the port is let go. */
164
+ last: boolean;
165
+ };
166
+
167
+ async function answerCallback(
168
+ path: string,
169
+ flow: { state: string; verifier: string },
170
+ ): Promise<CallbackPage> {
171
+ const url = new URL(path, REDIRECT_URI);
172
+ // A browser asks the same port for a favicon; that is not the answer
173
+ if (url.pathname !== "/auth/callback") {
174
+ return { status: 404, title: "Not found", detail: "", last: false };
175
+ }
176
+
177
+ const error = url.searchParams.get("error");
178
+ if (error) {
179
+ const description = url.searchParams.get("error_description");
180
+ return {
181
+ status: 400,
182
+ title: "Sign-in failed",
183
+ detail: description ? `${error}: ${description}` : error,
184
+ last: true,
185
+ };
186
+ }
187
+ const code = url.searchParams.get("code");
188
+ if (!code || url.searchParams.get("state") !== flow.state) {
189
+ return {
190
+ status: 400,
191
+ title: "Sign-in failed",
192
+ detail:
193
+ "This is not the sign-in that was started. Start again from Config.",
194
+ last: false,
195
+ };
196
+ }
197
+
198
+ try {
199
+ const signIn = await requestToken({
200
+ grant_type: "authorization_code",
201
+ code,
202
+ code_verifier: flow.verifier,
203
+ redirect_uri: REDIRECT_URI,
204
+ });
205
+ await writeConfig(SIGN_IN_KEY, JSON.stringify(signIn));
206
+ // The screen that opened this page is waiting on it
207
+ appEvents.emit({ type: "config" });
208
+ return {
209
+ status: 200,
210
+ title: "Signed in to ChatGPT",
211
+ detail: "Bots can run on your plan now. This window closes itself.",
212
+ last: true,
213
+ };
214
+ } catch (cause) {
215
+ logger.warn({ cause }, "chatgpt sign-in failed");
216
+ return {
217
+ status: 502,
218
+ title: "Sign-in failed",
219
+ detail: errorToString(cause),
220
+ last: true,
221
+ };
222
+ }
223
+ }
224
+
225
+ /** The account, plan and expiry an access token carries. Read, never verified: it came from OpenAI over TLS. */
226
+ function claimsOf(access: string) {
227
+ try {
228
+ const payload = JSON.parse(
229
+ Buffer.from(access.split(".")[1] ?? "", "base64url").toString("utf8"),
230
+ ) as Record<string, unknown>;
231
+ const auth = (payload[AUTH_CLAIM] ?? {}) as Record<string, unknown>;
232
+ return {
233
+ accountId:
234
+ typeof auth.chatgpt_account_id === "string"
235
+ ? auth.chatgpt_account_id
236
+ : null,
237
+ plan:
238
+ typeof auth.chatgpt_plan_type === "string"
239
+ ? auth.chatgpt_plan_type
240
+ : null,
241
+ expires: typeof payload.exp === "number" ? payload.exp * 1000 : null,
242
+ };
243
+ } catch {
244
+ return { accountId: null, plan: null, expires: null };
245
+ }
246
+ }
247
+
248
+ /** One call to OpenAI's token endpoint — the first exchange, or a renewal. A refusal comes back in its own words. */
249
+ async function requestToken(fields: Record<string, string>): Promise<SignIn> {
250
+ const response = await fetch(TOKEN_URL, {
251
+ method: "POST",
252
+ headers: {
253
+ accept: "application/json",
254
+ "content-type": "application/x-www-form-urlencoded",
255
+ },
256
+ body: new URLSearchParams({ client_id: CLIENT_ID, ...fields }),
257
+ }).catch((cause: unknown) => {
258
+ logger.warn({ cause }, "chatgpt token endpoint unreachable");
259
+ publicError("Could not reach ChatGPT's sign-in");
260
+ });
261
+ const body = (await response.json().catch(() => null)) as {
262
+ access_token?: string;
263
+ refresh_token?: string;
264
+ expires_in?: number;
265
+ error?: string | { message?: string; code?: string };
266
+ error_description?: string;
267
+ } | null;
268
+
269
+ const access = body?.access_token;
270
+ if (!response.ok || !access) {
271
+ const error = body?.error;
272
+ const said =
273
+ typeof error === "string"
274
+ ? [error, body?.error_description].filter(Boolean).join(": ")
275
+ : (error?.message ?? error?.code ?? "");
276
+ publicError(
277
+ `ChatGPT's sign-in answered ${response.status}${said ? `: ${said}` : ""}`,
278
+ );
279
+ }
280
+
281
+ const claims = claimsOf(access);
282
+ const refresh = body?.refresh_token ?? fields.refresh_token;
283
+ if (!claims.accountId || !refresh) {
284
+ publicError("ChatGPT's sign-in came back without an account");
285
+ }
286
+ const expiresIn = body?.expires_in;
287
+ return {
288
+ access,
289
+ refresh,
290
+ expires:
291
+ typeof expiresIn === "number"
292
+ ? Date.now() + expiresIn * 1000
293
+ : (claims.expires ?? Date.now()),
294
+ accountId: claims.accountId,
295
+ plan: claims.plan,
296
+ };
297
+ }
298
+
299
+ async function readSignIn(): Promise<SignIn | null> {
300
+ const stored = await readConfig(SIGN_IN_KEY);
301
+ if (!stored) return null;
302
+ try {
303
+ return SignInSchema.parse(JSON.parse(stored));
304
+ } catch {
305
+ return null;
306
+ }
307
+ }
308
+
309
+ const expiring = (signIn: SignIn) =>
310
+ signIn.expires - CHATGPT_SIGN_IN.renewBeforeMs <= Date.now();
311
+
312
+ /** The sign-in to send a request with, renewed first when its access token is about to run out. */
313
+ async function currentSignIn(): Promise<SignIn> {
314
+ const stored = await readSignIn();
315
+ if (!stored) publicError(NOT_SIGNED_IN);
316
+ if (!expiring(stored)) return stored;
317
+
318
+ return renewal(SIGN_IN_KEY, async () => {
319
+ // Another request may have renewed it while this one waited its turn
320
+ const latest = await readSignIn();
321
+ if (!latest) publicError(NOT_SIGNED_IN);
322
+ if (!expiring(latest)) return latest;
323
+ try {
324
+ const renewed = await requestToken({
325
+ grant_type: "refresh_token",
326
+ refresh_token: latest.refresh,
327
+ });
328
+ await writeConfig(SIGN_IN_KEY, JSON.stringify(renewed));
329
+ return renewed;
330
+ } catch (cause) {
331
+ publicError(
332
+ `Could not renew the ${LABEL} sign-in (${errorToString(cause)}). If it keeps failing, sign in again from Config.`,
333
+ );
334
+ }
335
+ });
336
+ }
337
+
338
+ /** The plan the signed-in account is on, for the settings screen; null when nobody is signed in. */
339
+ export async function readChatGptPlan(): Promise<string | null> {
340
+ return (await readSignIn())?.plan ?? null;
341
+ }
342
+
343
+ /** Answers the sign-in's own token, so no second key is needed to say what is left. */
344
+ const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
345
+
346
+ type UsageWindow = {
347
+ used_percent?: number;
348
+ /** Epoch seconds. */
349
+ reset_at?: number;
350
+ reset_after_seconds?: number;
351
+ };
352
+
353
+ /**
354
+ * How much of the plan the signed-in account has used, by its tightest window — Free has one of
355
+ * 30 days, a paid plan a short one and a weekly one — and when that window frees up; null when
356
+ * nobody is signed in. A sign-in the plan turns away is a state of the sign-in, not a failed
357
+ * read, so it comes back as `refused` in OpenAI's words.
358
+ */
359
+ export async function readChatGptUsage(): Promise<SubscriptionUsage | null> {
360
+ if (!(await readSignIn())) return null;
361
+ let signIn: SignIn;
362
+ try {
363
+ signIn = await currentSignIn();
364
+ } catch (cause) {
365
+ return { refused: errorToString(cause) };
366
+ }
367
+
368
+ const response = await fetch(USAGE_URL, {
369
+ headers: {
370
+ accept: "application/json",
371
+ authorization: `Bearer ${signIn.access}`,
372
+ "chatgpt-account-id": signIn.accountId,
373
+ originator: ORIGINATOR,
374
+ },
375
+ }).catch((cause: unknown) => {
376
+ logger.warn({ cause }, "chatgpt usage unreachable");
377
+ publicError("Could not reach ChatGPT");
378
+ });
379
+ const body = (await response.json().catch(() => null)) as {
380
+ plan_type?: string;
381
+ rate_limit?: {
382
+ limit_reached?: boolean;
383
+ primary_window?: UsageWindow | null;
384
+ secondary_window?: UsageWindow | null;
385
+ };
386
+ detail?: string;
387
+ error?: { message?: string };
388
+ } | null;
389
+
390
+ if (response.status === 401 || response.status === 403) {
391
+ return {
392
+ refused:
393
+ body?.error?.message ??
394
+ body?.detail ??
395
+ `ChatGPT answered ${response.status}`,
396
+ };
397
+ }
398
+ if (!response.ok) publicError(`ChatGPT answered ${response.status}`);
399
+
400
+ const tightest = [
401
+ body?.rate_limit?.primary_window,
402
+ body?.rate_limit?.secondary_window,
403
+ ]
404
+ .filter(
405
+ (window): window is UsageWindow =>
406
+ typeof window?.used_percent === "number",
407
+ )
408
+ .sort((a, b) => (b.used_percent ?? 0) - (a.used_percent ?? 0))[0];
409
+ if (!tightest)
410
+ publicError("ChatGPT did not say how much of the plan is used");
411
+
412
+ const usedPercent = Math.round(tightest.used_percent ?? 0);
413
+ const spent = Boolean(body?.rate_limit?.limit_reached);
414
+ const resetsAt = tightest.reset_at
415
+ ? tightest.reset_at * 1000
416
+ : tightest.reset_after_seconds
417
+ ? Date.now() + tightest.reset_after_seconds * 1000
418
+ : null;
419
+ return {
420
+ plan: body?.plan_type ?? signIn.plan,
421
+ usedPercent,
422
+ resetsAt: resetsAt ? new Date(resetsAt).toISOString() : null,
423
+ spent,
424
+ high: spent || usedPercent >= CHATGPT_USAGE_HIGH,
425
+ };
426
+ }
427
+
428
+ /** A Codex model on the signed-in plan. */
429
+ export function chatGptModel(modelId: string): LanguageModel {
430
+ const chatgpt = createOpenAI({
431
+ baseURL: CODEX_URL,
432
+ // The sdk builds no request without a key; codexFetch signs every one with the sign-in instead
433
+ apiKey: "signed-in",
434
+ headers: { "OpenAI-Beta": "responses=experimental" },
435
+ fetch: codexFetch,
436
+ });
437
+ // One key for the model's life — a run, its compactions, its answers — so its calls share a prompt cache
438
+ const promptCacheKey = randomUUID();
439
+ return wrapLanguageModel({
440
+ model: chatgpt.responses(modelId),
441
+ middleware: {
442
+ // Unstored: the sdk then sends whole items rather than ids the backend never kept, and asks
443
+ // for the encrypted reasoning a stored thread needs to be replayed
444
+ transformParams: async ({ params }) => ({
445
+ ...params,
446
+ providerOptions: {
447
+ ...params.providerOptions,
448
+ openai: {
449
+ promptCacheKey,
450
+ ...params.providerOptions?.openai,
451
+ store: false,
452
+ },
453
+ },
454
+ }),
455
+ },
456
+ });
457
+ }
458
+
459
+ type CodexBody = {
460
+ input?: { role?: string; content?: string | { text?: string }[] }[];
461
+ instructions?: string;
462
+ stream?: boolean;
463
+ store?: boolean;
464
+ max_output_tokens?: number;
465
+ };
466
+
467
+ /**
468
+ * What the ChatGPT backend asks of a Responses call that api.openai.com does not, applied to
469
+ * every request the sdk makes: signed with the current sign-in, so a job that outlives a token
470
+ * renews it; always streamed and never stored; the system prompt as `instructions`, which it
471
+ * refuses a call without; no output cap.
472
+ */
473
+ async function codexFetch(
474
+ input: RequestInfo | URL,
475
+ init?: RequestInit,
476
+ ): Promise<Response> {
477
+ const signIn = await currentSignIn();
478
+ const headers = new Headers(init?.headers);
479
+ headers.set("authorization", `Bearer ${signIn.access}`);
480
+ headers.set("chatgpt-account-id", signIn.accountId);
481
+ headers.set("originator", ORIGINATOR);
482
+
483
+ if (
484
+ init?.method !== "POST" ||
485
+ typeof init.body !== "string" ||
486
+ !String(input).endsWith("/responses")
487
+ ) {
488
+ return fetch(input, { ...init, headers });
489
+ }
490
+
491
+ const body = JSON.parse(init.body) as CodexBody;
492
+ const streamed = body.stream === true;
493
+ const system: string[] = [];
494
+ body.input = (body.input ?? []).filter((item) => {
495
+ if (item.role !== "system" && item.role !== "developer") return true;
496
+ system.push(
497
+ typeof item.content === "string"
498
+ ? item.content
499
+ : (item.content ?? []).map((part) => part.text ?? "").join("\n"),
500
+ );
501
+ return false;
502
+ });
503
+ body.instructions =
504
+ [body.instructions, ...system].filter(Boolean).join("\n\n") ||
505
+ "You are a helpful assistant.";
506
+ body.store = false;
507
+ body.stream = true;
508
+ body.max_output_tokens = undefined;
509
+
510
+ headers.set("accept", "text/event-stream");
511
+ const response = await fetch(input, {
512
+ ...init,
513
+ headers,
514
+ body: JSON.stringify(body),
515
+ });
516
+ if (response.status === 429) return usageLimitOf(response);
517
+ if (streamed || !response.ok) return response;
518
+ return finishedOf(response);
519
+ }
520
+
521
+ /**
522
+ * A 429 is either a rate limit a moment fixes or the plan's usage spent until its window resets
523
+ * — hours, or on Free a month. The second becomes a 402 in the backend's words: the sdk does not
524
+ * retry it, and a job fails with it (model.ts modelFailureOf) instead of waking every few
525
+ * minutes to be refused again.
526
+ */
527
+ async function usageLimitOf(response: Response): Promise<Response> {
528
+ const text = await response.text();
529
+ let said: {
530
+ code?: string;
531
+ type?: string;
532
+ plan_type?: string;
533
+ resets_at?: number;
534
+ resets_in_seconds?: number;
535
+ } = {};
536
+ try {
537
+ said = (JSON.parse(text) as { error?: typeof said }).error ?? {};
538
+ } catch {}
539
+
540
+ if (
541
+ !/usage_limit_reached|usage_not_included/.test(`${said.code} ${said.type}`)
542
+ ) {
543
+ const retryAfter = response.headers.get("retry-after");
544
+ return new Response(text, {
545
+ status: 429,
546
+ headers: {
547
+ "content-type": "application/json",
548
+ ...(retryAfter ? { "retry-after": retryAfter } : {}),
549
+ },
550
+ });
551
+ }
552
+
553
+ const resets = said.resets_at
554
+ ? said.resets_at * 1000
555
+ : said.resets_in_seconds
556
+ ? Date.now() + said.resets_in_seconds * 1000
557
+ : null;
558
+ const plan = said.plan_type ? ` on the ${said.plan_type} plan` : "";
559
+ const when = resets
560
+ ? ` It resets in ${formatDistanceToNowStrict(resets)}.`
561
+ : "";
562
+ return Response.json(
563
+ {
564
+ error: {
565
+ message: `${LABEL} usage limit reached${plan}.${when}`,
566
+ code: said.code ?? said.type,
567
+ },
568
+ },
569
+ { status: 402 },
570
+ );
571
+ }
572
+
573
+ type StreamEvent = {
574
+ type?: string;
575
+ item?: unknown;
576
+ response?: {
577
+ output?: unknown[];
578
+ error?: { code?: string; message?: string } | null;
579
+ };
580
+ code?: string;
581
+ message?: string;
582
+ };
583
+
584
+ /**
585
+ * The one JSON body a caller that did not stream expects, read off the stream the backend
586
+ * insists on. The finished event's `output` arrives empty, so items are gathered as each
587
+ * completes. A failed response becomes an error in its own words: a 400 when it was the
588
+ * context, which a compaction fixes, a 500 otherwise.
589
+ */
590
+ async function finishedOf(stream: Response): Promise<Response> {
591
+ const items: unknown[] = [];
592
+ let finished: { output?: unknown[] } | null = null;
593
+ let failure: { code?: string; message?: string } | null = null;
594
+
595
+ const blocks = (await stream.text()).replaceAll("\r\n", "\n").split("\n\n");
596
+ for (const block of blocks) {
597
+ const data = block
598
+ .split("\n")
599
+ .filter((line) => line.startsWith("data:"))
600
+ .map((line) => line.slice(5).trimStart())
601
+ .join("\n");
602
+ if (!data || data === "[DONE]") continue;
603
+ let event: StreamEvent;
604
+ try {
605
+ event = JSON.parse(data) as StreamEvent;
606
+ } catch {
607
+ continue;
608
+ }
609
+ if (event.type === "response.output_item.done") {
610
+ items.push(event.item);
611
+ } else if (
612
+ event.type === "response.completed" ||
613
+ event.type === "response.incomplete"
614
+ ) {
615
+ finished = event.response ?? {};
616
+ } else if (event.type === "response.failed") {
617
+ failure = event.response?.error ?? { message: "The response failed" };
618
+ } else if (event.type === "error") {
619
+ failure = { code: event.code, message: event.message };
620
+ }
621
+ }
622
+
623
+ if (failure || !finished) {
624
+ return Response.json(
625
+ {
626
+ error: {
627
+ message:
628
+ failure?.message ?? "The stream ended before the response finished",
629
+ code: failure?.code,
630
+ },
631
+ },
632
+ { status: failure?.code === "context_length_exceeded" ? 400 : 500 },
633
+ );
634
+ }
635
+ return Response.json({
636
+ ...finished,
637
+ output: finished.output?.length ? finished.output : items,
638
+ });
639
+ }
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import { type ComponentProps, useEffect, useRef, useState } from "react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { toast } from "@/components/ui/toast";
6
+ import { startChatGptSignInAction } from "@/features/config/config.action";
7
+ import { useServerAction } from "@/lib/protocol/use-server-action";
8
+
9
+ /** How often to look whether the sign-in window was closed, and how long to keep looking: as long as the server listens for its answer. */
10
+ const WINDOW_POLL_MS = 700;
11
+ const WINDOW_WATCH_MS = 10 * 60_000;
12
+
13
+ /**
14
+ * Opens ChatGPT's sign-in in a window of its own. The answer lands on the server, which keeps it
15
+ * and signals the screen (`config`); whoever draws this takes it away once signed in. Until then
16
+ * the button waits, and stops waiting when the window is closed without finishing.
17
+ */
18
+ export function ChatGptSignIn({
19
+ variant,
20
+ }: {
21
+ variant?: ComponentProps<typeof Button>["variant"];
22
+ }) {
23
+ const [waiting, setWaiting] = useState(false);
24
+ const watch = useRef<ReturnType<typeof setInterval> | null>(null);
25
+ const stopWatching = () => {
26
+ if (watch.current) clearInterval(watch.current);
27
+ watch.current = null;
28
+ };
29
+ useEffect(() => stopWatching, []);
30
+
31
+ const [start, starting] = useServerAction(startChatGptSignInAction, {
32
+ onOk: (url) => {
33
+ const popup = window.open(
34
+ url,
35
+ "thursday-chatgpt",
36
+ "popup,width=520,height=720",
37
+ );
38
+ if (!popup) {
39
+ toast.add({
40
+ type: "error",
41
+ title: "The sign-in window was blocked",
42
+ description: "Allow pop-ups for this page, then try again",
43
+ });
44
+ return;
45
+ }
46
+ setWaiting(true);
47
+ stopWatching();
48
+ const deadline = Date.now() + WINDOW_WATCH_MS;
49
+ watch.current = setInterval(() => {
50
+ if (!popup.closed && Date.now() < deadline) return;
51
+ stopWatching();
52
+ setWaiting(false);
53
+ }, WINDOW_POLL_MS);
54
+ },
55
+ });
56
+
57
+ return (
58
+ <Button
59
+ variant={variant}
60
+ loading={starting || waiting}
61
+ onClick={() => start()}
62
+ >
63
+ Sign in with ChatGPT
64
+ </Button>
65
+ );
66
+ }