thursday-agent 0.1.2 → 0.3.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 (573) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +5 -1
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +5 -5
  5. package/.next/routes-manifest.json +27 -3
  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.html +1 -1
  9. package/.next/server/app/_global-error.rsc +2 -2
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page.js +2 -2
  14. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +1 -1
  17. package/.next/server/app/_not-found.rsc +14 -14
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/server/app/api/artifact/route.js +3 -3
  22. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  23. package/.next/server/app/api/bot/memory/files/route/app-paths-manifest.json +3 -0
  24. package/.next/server/app/api/bot/memory/files/route.js +10 -0
  25. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -0
  26. package/.next/server/app/api/bot/memory/files/route_client-reference-manifest.js +3 -0
  27. package/.next/server/app/api/bot/memory/route/app-paths-manifest.json +3 -0
  28. package/.next/server/app/api/bot/memory/route/build-manifest.json +12 -0
  29. package/.next/server/app/api/bot/memory/route/server-reference-manifest.json +4 -0
  30. package/.next/server/app/api/bot/memory/route.js +14 -0
  31. package/.next/server/app/api/bot/memory/route.js.map +5 -0
  32. package/.next/server/app/api/bot/memory/route.js.nft.json +1 -0
  33. package/.next/server/app/api/bot/memory/route_client-reference-manifest.js +3 -0
  34. package/.next/server/app/api/bot/route.js +7 -7
  35. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  36. package/.next/server/app/api/bot/task/[id]/route.js +9 -9
  37. package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
  38. package/.next/server/app/api/bot/task/route.js +8 -8
  39. package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
  40. package/.next/server/app/api/config/route.js +6 -6
  41. package/.next/server/app/api/config/route.js.nft.json +1 -1
  42. package/.next/server/app/api/events/route.js +4 -5
  43. package/.next/server/app/api/events/route.js.nft.json +1 -1
  44. package/.next/server/app/api/file/[...path]/route.js +3 -3
  45. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  46. package/.next/server/app/api/llm-model/catalog/route.js +10 -9
  47. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  48. package/.next/server/app/api/llm-model/credits/route/app-paths-manifest.json +3 -0
  49. package/.next/server/app/api/llm-model/credits/route/build-manifest.json +12 -0
  50. package/.next/server/app/api/llm-model/credits/route/server-reference-manifest.json +4 -0
  51. package/.next/server/app/api/llm-model/credits/route.js +16 -0
  52. package/.next/server/app/api/llm-model/credits/route.js.map +5 -0
  53. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -0
  54. package/.next/server/app/api/llm-model/credits/route_client-reference-manifest.js +3 -0
  55. package/.next/server/app/api/llm-model/route.js +10 -8
  56. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  57. package/.next/server/app/api/llm-model/subscription/route/app-paths-manifest.json +3 -0
  58. package/.next/server/app/api/llm-model/subscription/route/build-manifest.json +12 -0
  59. package/.next/server/app/api/llm-model/subscription/route/server-reference-manifest.json +4 -0
  60. package/.next/server/app/api/llm-model/subscription/route.js +15 -0
  61. package/.next/server/app/api/llm-model/subscription/route.js.map +5 -0
  62. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -0
  63. package/.next/server/app/api/llm-model/subscription/route_client-reference-manifest.js +3 -0
  64. package/.next/server/app/api/mcp/[name]/route.js +5 -5
  65. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  66. package/.next/server/app/api/mcp/oauth/callback/route.js +6 -6
  67. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  68. package/.next/server/app/api/mcp/route.js +5 -5
  69. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  70. package/.next/server/app/api/mcp/tools/route.js +5 -5
  71. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  72. package/.next/server/app/api/memory/edit/route/app-paths-manifest.json +3 -0
  73. package/.next/server/app/api/memory/edit/route/build-manifest.json +12 -0
  74. package/.next/server/app/api/memory/edit/route/server-reference-manifest.json +4 -0
  75. package/.next/server/app/api/memory/edit/route.js +24 -0
  76. package/.next/server/app/api/memory/edit/route.js.map +5 -0
  77. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -0
  78. package/.next/server/app/api/memory/{tidy → edit}/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/memory/route.js +7 -8
  80. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  81. package/.next/server/app/api/skills/[source]/[name]/route.js +3 -3
  82. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  83. package/.next/server/app/api/skills/route.js +3 -3
  84. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  85. package/.next/server/app/api/thursday/call/route.js +9 -7
  86. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  87. package/.next/server/app/api/thursday/skills/route.js +9 -7
  88. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  89. package/.next/server/app/api/thursday/tool-call/route.js +16 -15
  90. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  91. package/.next/server/app/api/workspace/route.js +4 -5
  92. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  93. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  94. package/.next/server/app/artifact/[...path]/page.js +5 -6
  95. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  96. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  97. package/.next/server/app/favicon.ico/route.js +3 -4
  98. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  99. package/.next/server/app/favicon.ico.body +0 -0
  100. package/.next/server/app/page/server-reference-manifest.json +66 -77
  101. package/.next/server/app/page.js +11 -11
  102. package/.next/server/app/page.js.nft.json +1 -1
  103. package/.next/server/app/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app-paths-manifest.json +5 -1
  105. package/.next/server/chunks/[externals]__0dzuk6_._.js +3 -0
  106. package/.next/server/chunks/[root-of-the-server]__01dkp4a._.js +3 -0
  107. package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +3 -0
  108. package/.next/server/chunks/[root-of-the-server]__02xjoto._.js +3 -0
  109. package/.next/server/chunks/[root-of-the-server]__04q--0f._.js +8 -0
  110. package/.next/server/chunks/[root-of-the-server]__0bl-e63._.js +3 -0
  111. package/.next/server/chunks/[root-of-the-server]__0ce8nf8._.js +5 -0
  112. package/.next/server/chunks/[root-of-the-server]__0ffbg85._.js +3 -0
  113. package/.next/server/chunks/[root-of-the-server]__0j7z0r3._.js +12 -0
  114. package/.next/server/chunks/[root-of-the-server]__0jq4ohy._.js +8 -0
  115. package/.next/server/chunks/[root-of-the-server]__0ls6_8y._.js +8 -0
  116. package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +3 -0
  117. package/.next/server/chunks/[root-of-the-server]__0pa2n68._.js +3 -0
  118. package/.next/server/chunks/[root-of-the-server]__0q0vvt2._.js +5 -0
  119. package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +5 -0
  120. package/.next/server/chunks/[root-of-the-server]__11n256_._.js +5 -0
  121. package/.next/server/chunks/[root-of-the-server]__12cabg9._.js +3 -0
  122. package/.next/server/chunks/[root-of-the-server]__1339jnv._.js +8 -0
  123. package/.next/server/chunks/[root-of-the-server]__15bjnu5._.js +5 -0
  124. package/.next/server/chunks/[root-of-the-server]__18_teo8._.js +8 -0
  125. package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +5 -0
  126. package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +3 -0
  127. package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +10 -0
  128. package/.next/server/chunks/[root-of-the-server]__1fizn31._.js +3 -0
  129. package/.next/server/chunks/[root-of-the-server]__1llj3-b._.js +3 -0
  130. package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +11 -0
  131. package/.next/server/chunks/[root-of-the-server]__1p8m3f4._.js +3 -0
  132. package/.next/server/chunks/[root-of-the-server]__1wa1k7r._.js +8 -0
  133. package/.next/server/chunks/{_1hzrsvg._.js → _0-73of8._.js} +6 -6
  134. package/.next/server/chunks/{_1cbk7ms._.js → _0-fjql4._.js} +10 -10
  135. package/.next/server/chunks/_00xh_q0._.js +27 -0
  136. package/.next/server/chunks/{_0y8is-s._.js → _03a0897._.js} +1 -1
  137. package/.next/server/chunks/{_0pppbb5._.js → _03kp0jp._.js} +166 -35
  138. package/.next/server/chunks/_051qh8u._.js +3 -0
  139. package/.next/server/chunks/{node_modules_next_04q-o58._.js → _05dla15._.js} +2 -2
  140. package/.next/server/chunks/_05h2cii._.js +1 -1
  141. package/.next/server/chunks/_0c9j6rb._.js +49 -0
  142. package/.next/server/chunks/_0fzqt2y._.js +74 -44
  143. package/.next/server/chunks/{_0kl37zl._.js → _0ioz3nd._.js} +1 -1
  144. package/.next/server/chunks/_0iwfby5._.js +3 -0
  145. package/.next/server/chunks/{_0hin38_._.js → _0khihjk._.js} +5 -5
  146. package/.next/server/chunks/_0mecv0f._.js +26 -0
  147. package/.next/server/chunks/_0mh8j4m._.js +3 -0
  148. package/.next/server/chunks/{_1icgbi8._.js → _0n74zsf._.js} +2 -2
  149. package/.next/server/chunks/_0obojvv._.js +3 -0
  150. package/.next/server/chunks/_0recufk._.js +49 -0
  151. package/.next/server/chunks/{_1t8fv7b._.js → _0rnf-6n._.js} +2 -2
  152. package/.next/server/chunks/_0sb7tlz._.js +3 -0
  153. package/.next/server/chunks/{_0jgm3b8._.js → _0u10wmw._.js} +6 -6
  154. package/.next/server/chunks/{_12nr4xc._.js → _0u4aiy2._.js} +2 -2
  155. package/.next/server/chunks/_0wp4le3._.js +1 -1
  156. package/.next/server/chunks/_0y5fg-x._.js +1 -1
  157. package/.next/server/chunks/_1230wbx._.js +10 -0
  158. package/.next/server/chunks/_12nheja._.js +3 -0
  159. package/.next/server/chunks/_13510xh._.js +3 -0
  160. package/.next/server/chunks/_15hq89o._.js +1 -1
  161. package/.next/server/chunks/_17l5ygt._.js +49 -0
  162. package/.next/server/chunks/{_0ks6_sg._.js → _18-1wcc._.js} +6 -6
  163. package/.next/server/chunks/_1_5mnq4._.js +18 -0
  164. package/.next/server/chunks/{_0tzfesa._.js → _1_9mjx-._.js} +1 -1
  165. package/.next/server/chunks/_1d0j961._.js +6 -0
  166. package/.next/server/chunks/_1d5zmy8._.js +1 -1
  167. package/.next/server/chunks/_1drgfx4._.js +18 -0
  168. package/.next/server/chunks/{_1skgy7b._.js → _1e48czp._.js} +5 -5
  169. package/.next/server/chunks/_1f46eie._.js +3 -0
  170. package/.next/server/chunks/_1h5_8sk._.js +3 -0
  171. package/.next/server/chunks/_1hdnxb7._.js +3 -0
  172. package/.next/server/chunks/_1j8k_j5._.js +3 -0
  173. package/.next/server/chunks/{_10qam6o._.js → _1kg70wc._.js} +5 -5
  174. package/.next/server/chunks/_1krd9rf._.js +3 -0
  175. package/.next/server/chunks/_1nc0eui._.js +11 -0
  176. package/.next/server/chunks/_1nula3v._.js +3 -0
  177. package/.next/server/chunks/_1pf4qe-._.js +3 -0
  178. package/.next/server/chunks/_1php4eq._.js +1 -1
  179. package/.next/server/chunks/_1qxheoa._.js +3 -0
  180. package/.next/server/chunks/_1x6kd1r._.js +3 -0
  181. package/.next/server/chunks/_1yuv4vp._.js +1 -1
  182. package/.next/server/chunks/_1zjrdag._.js +3 -0
  183. package/.next/server/chunks/_next-internal_server_app_api_bot_memory_files_route_actions_1_xd_8f.js +3 -0
  184. package/.next/server/chunks/_next-internal_server_app_api_bot_memory_route_actions_1a8xfr7.js +3 -0
  185. package/.next/server/chunks/_next-internal_server_app_api_llm-model_credits_route_actions_0v424pb.js +3 -0
  186. package/.next/server/chunks/_next-internal_server_app_api_llm-model_subscription_route_actions_0-udcm5.js +3 -0
  187. package/.next/server/chunks/_next-internal_server_app_api_memory_edit_route_actions_02sfnmr.js +3 -0
  188. package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
  189. package/.next/server/chunks/features_0rpfxbk._.js +3 -0
  190. package/.next/server/chunks/features_1qf3cei._.js +3 -0
  191. package/.next/server/chunks/features_bot_0zt92oh._.js +3 -0
  192. package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +1 -1
  193. package/.next/server/chunks/features_memory_1uqvksn._.js +6 -0
  194. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tf290y._.js → node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js} +1 -1
  195. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0228f43._.js → node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js} +1 -1
  196. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0ir-vo2._.js → node_modules_consola_dist_chunks_prompt_mjs_0j61-ma._.js} +1 -1
  197. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0otw8ef._.js → node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js} +1 -1
  198. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js +3 -0
  199. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_11zjo-1._.js +3 -0
  200. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1pycqsq._.js +3 -0
  201. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1xoq2rl._.js +3 -0
  202. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y49572._.js +3 -0
  203. package/.next/server/chunks/ssr/[externals]__0dizgek._.js +3 -0
  204. package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
  205. package/.next/server/chunks/ssr/[root-of-the-server]__0_69wqr._.js +3 -0
  206. package/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
  207. package/.next/server/chunks/ssr/[root-of-the-server]__0i8ie7f._.js +3 -0
  208. package/.next/server/chunks/ssr/{[root-of-the-server]__0jsmmb-._.js → [root-of-the-server]__0mz33a7._.js} +1 -1
  209. package/.next/server/chunks/ssr/[root-of-the-server]__0tbkdlx._.js +3 -0
  210. package/.next/server/chunks/ssr/[root-of-the-server]__0vnmv-s._.js +3 -0
  211. package/.next/server/chunks/ssr/{[root-of-the-server]__0f6s48x._.js → [root-of-the-server]__0xpcv9w._.js} +3 -3
  212. package/.next/server/chunks/ssr/[root-of-the-server]__0xtssj3._.js +1 -1
  213. package/.next/server/chunks/ssr/[root-of-the-server]__0zkv7z_._.js +3 -0
  214. package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +57 -0
  215. package/.next/server/chunks/ssr/[root-of-the-server]__15_uhip._.js +3 -0
  216. package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
  217. package/.next/server/chunks/ssr/[root-of-the-server]__1cyjywu._.js +3 -0
  218. package/.next/server/chunks/ssr/[root-of-the-server]__1eh7hid._.js +3 -0
  219. package/.next/server/chunks/ssr/[root-of-the-server]__1gtlw6v._.js +43 -0
  220. package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
  221. package/.next/server/chunks/ssr/[root-of-the-server]__1ox7hbk._.js +4 -0
  222. package/.next/server/chunks/ssr/[root-of-the-server]__1pduf5n._.js +3 -0
  223. package/.next/server/chunks/ssr/{_1mznrxk._.js → _001_ms1._.js} +3 -3
  224. package/.next/server/chunks/ssr/_01_6m32._.js +3 -0
  225. package/.next/server/chunks/ssr/_01n3o0m._.js +3 -0
  226. package/.next/server/chunks/ssr/_07uchnc._.js +3 -0
  227. package/.next/server/chunks/ssr/_0ap2fzn._.js +3 -0
  228. package/.next/server/chunks/ssr/_0c-ql2m._.js +3 -0
  229. package/.next/server/chunks/ssr/_0h42fze._.js +3 -0
  230. package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
  231. package/.next/server/chunks/ssr/_0i_6q1w._.js +41 -0
  232. package/.next/server/chunks/ssr/_0kbkmar._.js +3 -0
  233. package/.next/server/chunks/ssr/_0lu1n_x._.js +3 -0
  234. package/.next/server/chunks/ssr/{_077-g6s._.js → _0mb8zc8._.js} +3 -3
  235. package/.next/server/chunks/ssr/_0mo2w06._.js +3 -0
  236. package/.next/server/chunks/ssr/{_13punpm._.js → _0q0zan6._.js} +2 -2
  237. package/.next/server/chunks/ssr/{_0gd4vaa._.js → _0u2jv_x._.js} +94 -58
  238. package/.next/server/chunks/ssr/_0y0tiht._.js +4 -0
  239. package/.next/server/chunks/ssr/_0zhoc3r._.js +3 -0
  240. package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
  241. package/.next/server/chunks/ssr/_13fke0x._.js +3 -0
  242. package/.next/server/chunks/ssr/{_0q_2f2x._.js → _13z3f85._.js} +2 -2
  243. package/.next/server/chunks/ssr/_14_lw10._.js +3 -0
  244. package/.next/server/chunks/ssr/_15j2_8f._.js +3 -0
  245. package/.next/server/chunks/ssr/{_0lc_7c8._.js → _1eoqg_g._.js} +2 -2
  246. package/.next/server/chunks/ssr/_1sji22n._.js +3 -0
  247. package/.next/server/chunks/ssr/_1spzm8z._.js +3 -0
  248. package/.next/server/chunks/ssr/{_19_o9d-._.js → _1vi_uo7._.js} +2 -2
  249. package/.next/server/chunks/ssr/_1vyd1ji._.js +3 -0
  250. package/.next/server/chunks/ssr/_1xvgn77._.js +3 -0
  251. package/.next/server/chunks/ssr/_20kd_n0._.js +42 -0
  252. package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +3 -0
  253. package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
  254. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  255. package/.next/server/chunks/ssr/lib_date-like_ts_02is0pw._.js +3 -0
  256. package/.next/server/chunks/ssr/lib_realtime_12wg_dd._.js +27 -0
  257. package/.next/server/chunks/ssr/lib_realtime_15h_v1v._.js +27 -0
  258. package/.next/server/chunks/ssr/{node_modules_1d-is1o._.js → node_modules_063xwpp._.js} +1 -1
  259. package/.next/server/chunks/ssr/{node_modules_1ub40gk._.js → node_modules_0bvraul._.js} +1 -1
  260. package/.next/server/chunks/ssr/{node_modules_1nhw9qg._.js → node_modules_0eu1va6._.js} +1 -1
  261. package/.next/server/chunks/ssr/{node_modules_0xwx4u-._.js → node_modules_0h50b8n._.js} +1 -1
  262. package/.next/server/chunks/ssr/{node_modules_043-236._.js → node_modules_0h5hnr1._.js} +1 -1
  263. package/.next/server/chunks/ssr/{node_modules_1t0y5jt._.js → node_modules_0h7u60n._.js} +1 -1
  264. package/.next/server/chunks/ssr/{node_modules_1k29j1j._.js → node_modules_0lm7_vm._.js} +1 -1
  265. package/.next/server/chunks/ssr/{node_modules_16i4o_3._.js → node_modules_0mwym0g._.js} +1 -1
  266. package/.next/server/chunks/ssr/{node_modules_1y8_3vg._.js → node_modules_0rjg6t1._.js} +1 -1
  267. package/.next/server/chunks/ssr/node_modules_0x3-ply._.js +3 -0
  268. package/.next/server/chunks/ssr/{node_modules_108wd2o._.js → node_modules_0y_cded._.js} +1 -1
  269. package/.next/server/chunks/ssr/{node_modules_1jatz7w._.js → node_modules_0z5tsu8._.js} +1 -1
  270. package/.next/server/chunks/ssr/{node_modules_0ls96je._.js → node_modules_0zp9r92._.js} +1 -1
  271. package/.next/server/chunks/ssr/{node_modules_06kvvuo._.js → node_modules_1-hc6uv._.js} +1 -1
  272. package/.next/server/chunks/ssr/{node_modules_1_588_4._.js → node_modules_113tz0l._.js} +1 -1
  273. package/.next/server/chunks/ssr/{node_modules_0ipn1ho._.js → node_modules_124bue5._.js} +1 -1
  274. package/.next/server/chunks/ssr/node_modules_17bwo43._.js +55 -0
  275. package/.next/server/chunks/ssr/{node_modules_0_j7-g4._.js → node_modules_1_n3ztw._.js} +1 -1
  276. package/.next/server/chunks/ssr/{node_modules_20ynonm._.js → node_modules_1bc7gu8._.js} +1 -1
  277. package/.next/server/chunks/ssr/{node_modules_1hn_sd5._.js → node_modules_1bgb6-f._.js} +1 -1
  278. package/.next/server/chunks/ssr/{node_modules_0bue6gy._.js → node_modules_1h4a4pp._.js} +1 -1
  279. package/.next/server/chunks/ssr/{node_modules_0fbw5-u._.js → node_modules_1i7u1ia._.js} +1 -1
  280. package/.next/server/chunks/ssr/{node_modules_1ee8gav._.js → node_modules_1juq3hs._.js} +1 -1
  281. package/.next/server/chunks/ssr/{node_modules_0_fvxnt._.js → node_modules_1l0qtdu._.js} +1 -1
  282. package/.next/server/chunks/ssr/{node_modules_13f2md6._.js → node_modules_1t9cyee._.js} +1 -1
  283. package/.next/server/chunks/ssr/{node_modules_0y2-av4._.js → node_modules_1uuj9x1._.js} +1 -1
  284. package/.next/server/chunks/ssr/{node_modules_00d6675._.js → node_modules_1vqb6po._.js} +1 -1
  285. package/.next/server/chunks/ssr/{node_modules_1v0077l._.js → node_modules_1xx28h1._.js} +1 -1
  286. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_092s8nx._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0d3skpl._.js} +1 -1
  287. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1nvexgu._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0i44kk5._.js} +1 -1
  288. package/.next/server/chunks/ssr/node_modules_@vercel_oidc_dist_0qe30a_._.js +3 -0
  289. package/.next/server/chunks/ssr/node_modules_consola_dist_chunks_prompt_mjs_05cwqv2._.js +47 -0
  290. package/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
  291. package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +2 -2
  292. package/.next/server/instrumentation.js +1 -1
  293. package/.next/server/middleware-build-manifest.js +3 -3
  294. package/.next/server/pages/404.html +1 -1
  295. package/.next/server/pages/500.html +1 -1
  296. package/.next/server/server-reference-manifest.js +1 -1
  297. package/.next/server/server-reference-manifest.json +67 -78
  298. package/.next/static/chunks/{31ttnjt6g35a0.js → 0ev4hyghq0mbb.js} +1 -1
  299. package/.next/static/chunks/0r5qcctujhxli.js +1 -0
  300. package/.next/static/chunks/0tmgrnlje14-s.js +3 -0
  301. package/.next/static/chunks/0x79tx04dxbpp.js +1 -0
  302. package/.next/static/chunks/0xzgvc0fkdb6t.js +1 -0
  303. package/.next/static/chunks/10teww7wt-f6h.js +1 -0
  304. package/.next/static/chunks/14hgtsjg-kael.css +1 -0
  305. package/.next/static/chunks/17donp69y4do7.js +40 -0
  306. package/.next/static/chunks/1dkjg87pxk0n9.js +28 -0
  307. package/.next/static/chunks/1ekc7ul556kbl.js +1 -0
  308. package/.next/static/chunks/{1khakdu_iuedc.js → 1grhjo7a5a0_-.js} +1 -1
  309. package/.next/static/chunks/1kq8h8wi_e0bc.js +53 -0
  310. package/.next/static/chunks/1okqiww3pnwka.js +1 -0
  311. package/.next/static/chunks/26-pr2c9ro222.js +1 -0
  312. package/.next/static/chunks/{2ihkf6l2jowo9.js → 280t0_e99i276.js} +3 -3
  313. package/.next/static/chunks/28cizcdaabd4h.js +1 -0
  314. package/.next/static/chunks/2a0k6jlq0_v1u.js +2 -0
  315. package/.next/static/chunks/2fzs9fm8yarvq.js +1 -0
  316. package/.next/static/chunks/2h0sd2vumhb15.js +1 -0
  317. package/.next/static/chunks/2hw2f6zmt7fmt.js +1 -0
  318. package/.next/static/chunks/2t4vhi3m8ahya.js +1 -0
  319. package/.next/static/chunks/2tigdtz3dpcy8.js +1 -0
  320. package/.next/static/chunks/3111pj5wnjgos.js +1 -0
  321. package/.next/static/chunks/3786h5vayt80-.js +28 -0
  322. package/.next/static/chunks/{3aquirz5h71da.js → 3agpgkwaa321p.js} +1 -1
  323. package/.next/static/chunks/3cfi_q6d_wtci.js +1 -0
  324. package/.next/static/chunks/3ok9_x74rip3v.js +1 -0
  325. package/.next/static/chunks/3xk7yqgomx7yq.js +1 -0
  326. package/.next/static/chunks/3xpbkl66ssib5.js +38 -0
  327. package/.next/static/media/favicon.37o1pjldwi2uc.ico +0 -0
  328. package/README.md +6 -4
  329. package/app/api/bot/memory/files/route.ts +14 -0
  330. package/app/api/bot/memory/route.ts +5 -0
  331. package/app/api/events/app-event.client.ts +24 -0
  332. package/app/api/events/app-event.server.ts +45 -0
  333. package/app/api/events/app-event.ts +42 -0
  334. package/app/api/llm-model/catalog/route.ts +12 -0
  335. package/app/api/llm-model/credits/route.ts +11 -0
  336. package/app/api/llm-model/route.ts +26 -0
  337. package/app/api/llm-model/subscription/route.ts +11 -0
  338. package/app/api/memory/edit/route.ts +7 -0
  339. package/app/api/memory/route.ts +21 -0
  340. package/app/api/query-key.ts +159 -0
  341. package/app/api/thursday/tool-call/route.ts +67 -0
  342. package/bin/thursday.mjs +3 -0
  343. package/components/ui/button-group.tsx +45 -0
  344. package/components/ui/custom-icon.tsx +259 -0
  345. package/components/ui/dropdown-menu.tsx +271 -0
  346. package/components/ui/folded-text.tsx +68 -0
  347. package/components/ui/input-group.tsx +157 -0
  348. package/components/ui/json-view.tsx +240 -0
  349. package/components/ui/shiny-text.tsx +55 -0
  350. package/components/ui/text-type.tsx +213 -0
  351. package/components/ui/theme-sync.tsx +9 -0
  352. package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260910173049_numerous_spirit}/migration.sql +30 -33
  353. package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260910173049_numerous_spirit}/snapshot.json +124 -145
  354. package/database/migrations/20260911030320_opposite_jack_power/migration.sql +1 -0
  355. package/database/migrations/20260911030320_opposite_jack_power/snapshot.json +1158 -0
  356. package/docs/how-it-works.md +59 -0
  357. package/docs/images/keeps-going.png +0 -0
  358. package/docs/images/task-thread.png +0 -0
  359. package/docs/images/two-minds.png +0 -0
  360. package/features/ai/chatgpt.ts +617 -0
  361. package/features/ai/components/chatgpt-sign-in.tsx +66 -0
  362. package/features/ai/components/model-picker.tsx +17 -1
  363. package/features/ai/components/provider-icon.tsx +1 -0
  364. package/features/ai/load-tools.ts +105 -39
  365. package/features/ai/model.schema.ts +232 -34
  366. package/features/ai/model.ts +169 -24
  367. package/features/ai/prompts/bot.prompt.ts +245 -59
  368. package/features/ai/prompts/memory-edit.prompt.ts +33 -0
  369. package/features/ai/prompts/prompt-helper.ts +75 -30
  370. package/features/ai/prompts/thursday.prompt.ts +42 -17
  371. package/features/ai/tools/bot.tool.ts +32 -30
  372. package/features/ai/tools/connected.ts +17 -4
  373. package/features/ai/tools/mcp.tool.ts +5 -5
  374. package/features/ai/tools/memory.tool.ts +142 -74
  375. package/features/ai/tools/search.tool.ts +136 -47
  376. package/features/ai/tools/studio.tool.ts +5 -5
  377. package/features/ai/tools/tool-name.ts +2 -3
  378. package/features/ai/tools/workspace.tool.ts +33 -4
  379. package/features/bot/bot.action.ts +24 -7
  380. package/features/bot/bot.memory.ts +89 -0
  381. package/features/bot/bot.query.ts +41 -6
  382. package/features/bot/bot.run.ts +516 -210
  383. package/features/bot/bot.runner.ts +463 -93
  384. package/features/bot/bot.schema.ts +84 -7
  385. package/features/bot/bot.seed.ts +89 -16
  386. package/features/bot/components/bot-mark.tsx +122 -20
  387. package/features/bot/components/bot-room.tsx +519 -391
  388. package/features/bot/components/bot-setting.tsx +832 -341
  389. package/features/bot/components/bot-tool.tsx +2 -6
  390. package/features/bot/components/mark-palette.tsx +54 -0
  391. package/features/bot/components/path-chips.tsx +1 -1
  392. package/features/bot/components/task-badge.tsx +26 -6
  393. package/features/bot/components/task-reply.tsx +22 -33
  394. package/features/bot/components/task-setting.tsx +20 -17
  395. package/features/bot/task.query.ts +233 -33
  396. package/features/bot/task.store.ts +53 -9
  397. package/features/config/components/config-setting.tsx +265 -34
  398. package/features/config/components/models-badge.tsx +42 -0
  399. package/features/config/config.action.ts +12 -0
  400. package/features/config/config.const.ts +38 -2
  401. package/features/connectors/mcp.manager.ts +10 -2
  402. package/features/intro/components/intro.tsx +47 -46
  403. package/features/memory/components/memory-edit.tsx +338 -0
  404. package/features/memory/components/memory-setting.tsx +86 -79
  405. package/features/memory/memory.action.ts +3 -43
  406. package/features/memory/memory.edit.ts +87 -0
  407. package/features/memory/memory.query.ts +33 -44
  408. package/features/memory/memory.schema.ts +50 -97
  409. package/features/settings/components/settings.tsx +2 -0
  410. package/features/settings/settings.alert.ts +3 -1
  411. package/features/skills/components/skills-setting.tsx +9 -1
  412. package/features/thursday/components/ascii-orb.tsx +106 -90
  413. package/features/thursday/components/boot.tsx +7 -0
  414. package/features/thursday/components/call-log.tsx +219 -92
  415. package/features/thursday/components/face.tsx +6 -4
  416. package/features/thursday/components/tab-state.tsx +97 -0
  417. package/features/thursday/components/thursday-mark.tsx +4 -1
  418. package/features/thursday/components/thursday-setting.tsx +17 -47
  419. package/features/thursday/components/thursday.tsx +208 -95
  420. package/features/thursday/thursday.action.ts +12 -17
  421. package/features/thursday/thursday.query.ts +61 -70
  422. package/features/thursday/thursday.schema.ts +20 -7
  423. package/features/thursday/tool-line.ts +85 -11
  424. package/features/thursday/use-thursday.ts +86 -59
  425. package/features/workspace/components/file-view.tsx +4 -2
  426. package/features/workspace/workspace.ts +230 -13
  427. package/hooks/use-draft.ts +37 -0
  428. package/hooks/use-hotkey.ts +105 -0
  429. package/hooks/use-object-state.ts +51 -0
  430. package/hooks/use-on-visible.ts +72 -0
  431. package/hooks/use-theme.ts +106 -0
  432. package/hooks/use-wake-word.ts +262 -0
  433. package/lib/limits.ts +1 -1
  434. package/lib/oauth-page.ts +27 -0
  435. package/lib/realtime/openai.ts +2 -2
  436. package/lib/realtime/realtime.driver.ts +34 -1
  437. package/lib/realtime/realtime.protocol.ts +7 -1
  438. package/lib/realtime/realtime.tap.ts +122 -0
  439. package/lib/sandbox.ts +60 -29
  440. package/lib/tokens.ts +19 -0
  441. package/package.json +1 -1
  442. package/skills/browser/SKILL.md +55 -34
  443. package/.next/server/app/api/memory/tidy/route/app-paths-manifest.json +0 -3
  444. package/.next/server/app/api/memory/tidy/route.js +0 -23
  445. package/.next/server/app/api/memory/tidy/route.js.nft.json +0 -1
  446. package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +0 -5
  447. package/.next/server/chunks/[root-of-the-server]__02_2sm-._.js +0 -5
  448. package/.next/server/chunks/[root-of-the-server]__02kt8yn._.js +0 -10
  449. package/.next/server/chunks/[root-of-the-server]__05luceg._.js +0 -3
  450. package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +0 -11
  451. package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +0 -8
  452. package/.next/server/chunks/[root-of-the-server]__0dupqt7._.js +0 -5
  453. package/.next/server/chunks/[root-of-the-server]__0h59di-._.js +0 -5
  454. package/.next/server/chunks/[root-of-the-server]__0j_n5l3._.js +0 -3
  455. package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +0 -3
  456. package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +0 -10
  457. package/.next/server/chunks/[root-of-the-server]__0nfuugi._.js +0 -5
  458. package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +0 -8
  459. package/.next/server/chunks/[root-of-the-server]__0qjqtwo._.js +0 -5
  460. package/.next/server/chunks/[root-of-the-server]__0t_dqaj._.js +0 -3
  461. package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +0 -3
  462. package/.next/server/chunks/[root-of-the-server]__0yk6s3w._.js +0 -5
  463. package/.next/server/chunks/[root-of-the-server]__0z3gelv._.js +0 -3
  464. package/.next/server/chunks/[root-of-the-server]__10qshki._.js +0 -3
  465. package/.next/server/chunks/[root-of-the-server]__113fqg5._.js +0 -12
  466. package/.next/server/chunks/[root-of-the-server]__11w_7_3._.js +0 -3
  467. package/.next/server/chunks/[root-of-the-server]__16thabu._.js +0 -3
  468. package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +0 -8
  469. package/.next/server/chunks/[root-of-the-server]__1_0q_9w._.js +0 -3
  470. package/.next/server/chunks/[root-of-the-server]__1_aoyr9._.js +0 -3
  471. package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +0 -8
  472. package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +0 -5
  473. package/.next/server/chunks/[root-of-the-server]__1dow0bz._.js +0 -3
  474. package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +0 -3
  475. package/.next/server/chunks/[root-of-the-server]__1gmn1cs._.js +0 -10
  476. package/.next/server/chunks/[root-of-the-server]__1lr7j9t._.js +0 -5
  477. package/.next/server/chunks/[root-of-the-server]__1mzp_el._.js +0 -3
  478. package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +0 -8
  479. package/.next/server/chunks/[root-of-the-server]__1u_qvc7._.js +0 -3
  480. package/.next/server/chunks/[root-of-the-server]__1zgpz1w._.js +0 -3
  481. package/.next/server/chunks/_012yhfm._.js +0 -39
  482. package/.next/server/chunks/_06mxlgn._.js +0 -3
  483. package/.next/server/chunks/_07rtlxv._.js +0 -3
  484. package/.next/server/chunks/_0a351g7._.js +0 -6
  485. package/.next/server/chunks/_0kjjmag._.js +0 -3
  486. package/.next/server/chunks/_0zmestz._.js +0 -55
  487. package/.next/server/chunks/_0zuzmt_._.js +0 -39
  488. package/.next/server/chunks/_12bbv5i._.js +0 -96
  489. package/.next/server/chunks/_15q1pig._.js +0 -3
  490. package/.next/server/chunks/_1drv2et._.js +0 -36
  491. package/.next/server/chunks/_1ic3bew._.js +0 -5
  492. package/.next/server/chunks/_1mxn_lb._.js +0 -3
  493. package/.next/server/chunks/_1uj4_io._.js +0 -10
  494. package/.next/server/chunks/_1x7p319._.js +0 -6
  495. package/.next/server/chunks/_1yldgo4._.js +0 -55
  496. package/.next/server/chunks/_2050gwn._.js +0 -255
  497. package/.next/server/chunks/_next-internal_server_app_api_memory_tidy_route_actions_0_cfuom.js +0 -3
  498. package/.next/server/chunks/features_0scmr4i._.js +0 -3
  499. package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +0 -3
  500. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0vorg6w._.js +0 -3
  501. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0ykklj0._.js +0 -3
  502. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1ryg3wh._.js +0 -3
  503. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vmaq9f._.js +0 -3
  504. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vwbnq_._.js +0 -3
  505. package/.next/server/chunks/ssr/[externals]_node_crypto_1kn79-7._.js +0 -3
  506. package/.next/server/chunks/ssr/[root-of-the-server]__01id-v3._.js +0 -3
  507. package/.next/server/chunks/ssr/[root-of-the-server]__0b-0i32._.js +0 -3
  508. package/.next/server/chunks/ssr/[root-of-the-server]__0dfwvr4._.js +0 -4
  509. package/.next/server/chunks/ssr/[root-of-the-server]__0iuoa-r._.js +0 -3
  510. package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +0 -90
  511. package/.next/server/chunks/ssr/[root-of-the-server]__185grso._.js +0 -3
  512. package/.next/server/chunks/ssr/[root-of-the-server]__1minz8_._.js +0 -3
  513. package/.next/server/chunks/ssr/_0-mc7ro._.js +0 -3
  514. package/.next/server/chunks/ssr/_01p5r6-._.js +0 -3
  515. package/.next/server/chunks/ssr/_09-0k3z._.js +0 -3
  516. package/.next/server/chunks/ssr/_0ay6b01._.js +0 -3
  517. package/.next/server/chunks/ssr/_0cvjbuz._.js +0 -3
  518. package/.next/server/chunks/ssr/_0fafgvl._.js +0 -3
  519. package/.next/server/chunks/ssr/_0j4v8of._.js +0 -3
  520. package/.next/server/chunks/ssr/_0l81bzy._.js +0 -3
  521. package/.next/server/chunks/ssr/_0nso8tp._.js +0 -3
  522. package/.next/server/chunks/ssr/_0sf1isi._.js +0 -3
  523. package/.next/server/chunks/ssr/_0ti51xc._.js +0 -3
  524. package/.next/server/chunks/ssr/_0y84qo6._.js +0 -3
  525. package/.next/server/chunks/ssr/_10uvw3l._.js +0 -3
  526. package/.next/server/chunks/ssr/_1dcxzqe._.js +0 -3
  527. package/.next/server/chunks/ssr/_1e-dgz0._.js +0 -4
  528. package/.next/server/chunks/ssr/_1fq7zgb._.js +0 -3
  529. package/.next/server/chunks/ssr/_1jbv7m1._.js +0 -3
  530. package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +0 -4
  531. package/.next/server/chunks/ssr/features_config_components_14jqgy4._.js +0 -3
  532. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +0 -4
  533. package/.next/server/chunks/ssr/lib_realtime_0aetuyp._.js +0 -24
  534. package/.next/server/chunks/ssr/lib_realtime_18k_aj-._.js +0 -24
  535. package/.next/server/chunks/ssr/node_modules_zod_v4_classic_errors_05xv7vp.js +0 -3
  536. package/.next/server/chunks/ssr/node_modules_zod_v4_classic_external_15fmmz3.js +0 -55
  537. package/.next/static/chunks/0f9sq-7q_yxyl.js +0 -1
  538. package/.next/static/chunks/0tydnn2kybibk.js +0 -1
  539. package/.next/static/chunks/0yysv1gzin17t.js +0 -1
  540. package/.next/static/chunks/1fr5m75pwtyic.js +0 -1
  541. package/.next/static/chunks/1gy3bbv7usnjo.js +0 -1
  542. package/.next/static/chunks/1keib_gt066gc.js +0 -1
  543. package/.next/static/chunks/1ytnip8wdng93.js +0 -1
  544. package/.next/static/chunks/21rk8w7bda-su.js +0 -1
  545. package/.next/static/chunks/247d-r95j8xs0.js +0 -1
  546. package/.next/static/chunks/28g42tw8tsvhd.js +0 -1
  547. package/.next/static/chunks/29gyb3nubj51j.js +0 -53
  548. package/.next/static/chunks/2cwjgkt_hoyhj.js +0 -22
  549. package/.next/static/chunks/2fq_86jvq9ymq.js +0 -2
  550. package/.next/static/chunks/2rbf1fpd8n80-.js +0 -22
  551. package/.next/static/chunks/2ukj8k69y89um.js +0 -1
  552. package/.next/static/chunks/2uxum7qbyeyxd.js +0 -1
  553. package/.next/static/chunks/2wngeetfnzxkp.js +0 -2
  554. package/.next/static/chunks/388-t2v72jh39.css +0 -1
  555. package/.next/static/chunks/3fqvc0cg-atq_.js +0 -1
  556. package/.next/static/chunks/3p4egvfjxpatb.js +0 -1
  557. package/.next/static/chunks/3qe1w1ei-huhl.js +0 -1
  558. package/.next/static/chunks/3xxa2j56-f3nm.js +0 -3
  559. package/.next/static/chunks/3y40riycdip-i.js +0 -1
  560. package/.next/static/chunks/45a6zbtkelph9.js +0 -1
  561. package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  562. package/features/ai/prompts/memory-tidy.prompt.ts +0 -123
  563. package/features/ai/tools/tidy.tool.ts +0 -22
  564. package/features/memory/components/memory-tidy.tsx +0 -395
  565. package/features/memory/memory.tidy.ts +0 -332
  566. package/features/memory/tidy.query.ts +0 -135
  567. package/features/thursday/use-call-title.ts +0 -31
  568. /package/.next/server/app/api/{memory/tidy → bot/memory/files}/route/build-manifest.json +0 -0
  569. /package/.next/server/app/api/{memory/tidy → bot/memory/files}/route/server-reference-manifest.json +0 -0
  570. /package/.next/server/app/api/{memory/tidy → bot/memory/files}/route.js.map +0 -0
  571. /package/.next/static/{X34BUd9bfdH27ripuwwBm → x1cCqY0kWpR_VcYx_lWO5}/_buildManifest.js +0 -0
  572. /package/.next/static/{X34BUd9bfdH27ripuwwBm → x1cCqY0kWpR_VcYx_lWO5}/_clientMiddlewareManifest.js +0 -0
  573. /package/.next/static/{X34BUd9bfdH27ripuwwBm → x1cCqY0kWpR_VcYx_lWO5}/_ssgManifest.js +0 -0
@@ -2,6 +2,7 @@ import {
2
2
  mkdir,
3
3
  readdir,
4
4
  realpath,
5
+ rm,
5
6
  stat,
6
7
  unlink,
7
8
  writeFile,
@@ -13,9 +14,10 @@ import {
13
14
  DATA_DIR,
14
15
  PATHS,
15
16
  TOOL_OUTPUT,
17
+ WORKSPACE_KEEP,
16
18
  } from "@/config";
17
19
  import { logger } from "@/lib/logger";
18
- import { createSandBox, type Sandbox } from "@/lib/sandbox";
20
+ import { createSandBox, type Sandbox, walkFiles } from "@/lib/sandbox";
19
21
 
20
22
  /**
21
23
  * Where the sandbox is opened. The app's own skills live outside the
@@ -42,7 +44,12 @@ export const ARTIFACTS = join(WORKSPACE, PATHS.artifacts);
42
44
  * Created on open, and the only places inside the workspace `write_file`
43
45
  * accepts. `.agents` is the skills CLI's install folder.
44
46
  */
45
- const BOT_FOLDERS = [PATHS.artifacts, PATHS.projects, PATHS.scratch];
47
+ const BOT_FOLDERS = [
48
+ PATHS.artifacts,
49
+ PATHS.projects,
50
+ PATHS.scratch,
51
+ PATHS.bots,
52
+ ];
46
53
  const WRITABLE = new Set([...BOT_FOLDERS, ".agents"]);
47
54
 
48
55
  /** Where playwright-cli drops a snapshot after every command. Its name, not ours. */
@@ -61,7 +68,7 @@ export function writeRefusal(full: string): string | null {
61
68
  const rel = relative(WORKSPACE, full);
62
69
  const [top] = rel.split(sep);
63
70
  if (top && WRITABLE.has(top)) return null;
64
- return `Not written: ${rel || "the workspace root"} is outside the workspace's folders. Finished work goes under ${PATHS.artifacts}/, code under ${PATHS.projects}/, everything else under ${PATHS.scratch}/.`;
71
+ return `Not written: ${rel || "the workspace root"} is outside the workspace's folders. Finished work goes under ${PATHS.artifacts}/, code under ${PATHS.projects}/, your own kit under ${PATHS.bots}/, and this job's working material under ${PATHS.scratch}/.`;
65
72
  }
66
73
  if (under(APP_ROOT, full)) {
67
74
  return `Not written: ${full} is inside the app that is running you, not the user's files. Work in ${WORKSPACE}; outside it, write only where the user pointed you.`;
@@ -89,6 +96,50 @@ export async function insideWorkspace(rel: string): Promise<string | null> {
89
96
  return under(root, target) ? target : null;
90
97
  }
91
98
 
99
+ /**
100
+ * Where one job keeps what it is still working on. `scratch/` used to be shared
101
+ * by every job at once, so nobody could tell whose a file was or when it stopped
102
+ * mattering, and it only grew. A job has a beginning and an end, which is what
103
+ * makes its working material safe to clear later — and it is the right unit
104
+ * rather than the bot, because several bots work inside one job (`ask_bot`) and
105
+ * one bot runs many jobs.
106
+ *
107
+ * Named for the label so the folder is readable on the Workspace screen, with
108
+ * the head of the id after it so two jobs called the same thing stay apart.
109
+ * Finished work never lands here: that is `artifacts/`, which stays flat and
110
+ * unattributed, and code is `projects/`, which outlives the job that started it.
111
+ */
112
+ export function jobScratch(taskId: string, label: string): string {
113
+ const slug =
114
+ label
115
+ .trim()
116
+ .toLowerCase()
117
+ .replace(/[^\p{L}\p{N}]+/gu, "-")
118
+ .replace(/^-+|-+$/g, "")
119
+ .slice(0, 40) || "job";
120
+ return `${PATHS.scratch}/${slug}-${taskId.slice(0, 6)}`;
121
+ }
122
+
123
+ /** Where one bot keeps what it wants on its next job (config PATHS.bots). */
124
+ export const botFolder = (bot: string): string =>
125
+ `${PATHS.bots}/${bot.replace(/[^\p{L}\p{N}_-]+/gu, "-")}`;
126
+
127
+ export async function openBotFolder(bot: string): Promise<string> {
128
+ const path = botFolder(bot);
129
+ await mkdir(join(WORKSPACE, path), { recursive: true });
130
+ return path;
131
+ }
132
+
133
+ /** Creates it, so a job never has to and never writes to the shared root by mistake. */
134
+ export async function openJobScratch(
135
+ taskId: string,
136
+ label: string,
137
+ ): Promise<string> {
138
+ const path = jobScratch(taskId, label);
139
+ await mkdir(join(WORKSPACE, path), { recursive: true });
140
+ return path;
141
+ }
142
+
92
143
  /**
93
144
  * A job's own browser session. playwright-cli reads the session name from
94
145
  * this variable when `-s=` is not given, so the model never picks one and the
@@ -105,6 +156,52 @@ export const jobShellEnv = (
105
156
  }
106
157
  : {};
107
158
 
159
+ /** Set by `ensureBrowser`, read by `readMachineTools`; null until it has finished. */
160
+ let browserReady: boolean | null = null;
161
+
162
+ /**
163
+ * What the shell can actually reach. A bot writes a job around what is here —
164
+ * node or python3, which package manager — and the platform does not say: two
165
+ * macs differ. Probed in one `command -v` sweep at the moment it is asked, per
166
+ * job rather than cached, so a job that installs something is not told
167
+ * otherwise on its next run.
168
+ */
169
+ export const PROBED = {
170
+ runtimes: ["node", "python3", "uv", "bun", "deno"],
171
+ managers: ["pnpm", "npm", "yarn"],
172
+ } as const;
173
+
174
+ export type MachineTools = {
175
+ [K in keyof typeof PROBED]: { found: string[]; missing: string[] };
176
+ } & {
177
+ /** Null while `ensureBrowser` is still running; it starts at boot and is not awaited. */
178
+ browser: boolean | null;
179
+ };
180
+
181
+ export async function readMachineTools(
182
+ sandbox: Sandbox,
183
+ ): Promise<MachineTools> {
184
+ const names = Object.values(PROBED).flat();
185
+ const { stdout } = await sandbox.exec(
186
+ names
187
+ .map(
188
+ (name) => `command -v ${name} >/dev/null 2>&1 && printf '%s ' ${name}`,
189
+ )
190
+ .join("; "),
191
+ { timeoutMs: 10_000 },
192
+ );
193
+ const found = new Set(stdout.trim().split(/\s+/).filter(Boolean));
194
+ const split = (list: readonly string[]) => ({
195
+ found: list.filter((name) => found.has(name)),
196
+ missing: list.filter((name) => !found.has(name)),
197
+ });
198
+ return {
199
+ runtimes: split(PROBED.runtimes),
200
+ managers: split(PROBED.managers),
201
+ browser: browserReady,
202
+ };
203
+ }
204
+
108
205
  /**
109
206
  * The browser bots drive. It is not in the package — ~280 MB, and its build
110
207
  * moves with playwright-core — so it is fetched here instead of from an install
@@ -122,12 +219,55 @@ export async function ensureBrowser(): Promise<void> {
122
219
  .exec("playwright-cli install-browser chromium")
123
220
  .catch((cause: unknown) => ({ exitCode: 1, stderr: String(cause) }));
124
221
 
125
- if (done.exitCode === 0) return;
222
+ browserReady = done.exitCode === 0;
223
+ if (browserReady) return;
126
224
  logger.warn(
127
225
  `no browser — bots cannot open a page until \`playwright-cli install-browser chromium\` succeeds: ${done.stderr.trim().split("\n").at(-1) ?? ""}`,
128
226
  );
129
227
  }
130
228
 
229
+ /** What a job leaves on disk: stale browser snapshots and spilled tool output. */
230
+ export async function pruneJobFiles(): Promise<void> {
231
+ await pruneBrowserFiles();
232
+ await pruneOutputFiles();
233
+ }
234
+
235
+ type ListedBrowser = { name: string; headed?: boolean; attached?: boolean };
236
+
237
+ /**
238
+ * A job that ended closes the browser nobody can see. Headless is the bot's
239
+ * working copy; `--headed` is the bot putting a window on their screen on
240
+ * purpose — an order at checkout, a map with a pin, a sign-in — and that one is
241
+ * theirs to close (`skills/browser`). A session attached to their own Chrome is
242
+ * never touched. Anything unreadable — no playwright, a failed or folded `list`
243
+ * — leaves the browser as it is.
244
+ */
245
+ export async function closeHiddenBrowser(taskId: string): Promise<void> {
246
+ const sandbox = await openWorkspace();
247
+ const env = jobShellEnv(taskId);
248
+ const listed = await sandbox
249
+ .exec("playwright-cli list --json", { env, timeoutMs: 15_000 })
250
+ .catch(() => null);
251
+ let session: ListedBrowser | undefined;
252
+ try {
253
+ const { browsers } = JSON.parse(listed?.stdout ?? "") as {
254
+ browsers?: ListedBrowser[];
255
+ };
256
+ session = browsers?.find((b) => b.name === env.PLAYWRIGHT_CLI_SESSION);
257
+ } catch {}
258
+ if (session?.headed === false && !session.attached) {
259
+ await sandbox
260
+ .exec("playwright-cli close", { env, timeoutMs: 15_000 })
261
+ .catch(() => {});
262
+ }
263
+ await pruneJobFiles();
264
+ }
265
+
266
+ /**
267
+ * Closes the job's browser whatever it shows. Only for a job the user cancelled
268
+ * or deleted: its row is gone, so a window left open would belong to nothing on
269
+ * the screen.
270
+ */
131
271
  export async function closeJobShell(taskId: string): Promise<void> {
132
272
  const sandbox = await openWorkspace();
133
273
  await sandbox
@@ -136,19 +276,18 @@ export async function closeJobShell(taskId: string): Promise<void> {
136
276
  timeoutMs: 15_000,
137
277
  })
138
278
  .catch(() => {});
139
- await pruneBrowserFiles();
279
+ await pruneJobFiles();
140
280
  }
141
281
 
142
- /**
143
- * playwright-cli never deletes its snapshot files. Refs go stale on the next
144
- * click, so anything older than an hour belongs to no running job.
145
- */
146
- const BROWSER_FILE_TTL_MS = 60 * 60 * 1000;
282
+ const pruneOutputFiles = () => pruneOldFiles(PATHS.output);
147
283
 
148
- async function pruneBrowserFiles(): Promise<void> {
149
- const dir = join(WORKSPACE, BROWSER_DIR);
284
+ const pruneBrowserFiles = () => pruneOldFiles(BROWSER_DIR);
285
+
286
+ /** Files directly in a workspace folder that have not changed for WORKSPACE_KEEP.forMs. */
287
+ async function pruneOldFiles(folder: string): Promise<void> {
288
+ const dir = join(WORKSPACE, folder);
150
289
  const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
151
- const cutoff = Date.now() - BROWSER_FILE_TTL_MS;
290
+ const cutoff = Date.now() - WORKSPACE_KEEP.forMs;
152
291
  for (const entry of entries) {
153
292
  if (!entry.isFile()) continue;
154
293
  const file = join(dir, entry.name);
@@ -157,6 +296,37 @@ async function pruneBrowserFiles(): Promise<void> {
157
296
  }
158
297
  }
159
298
 
299
+ /** Every folder under `scratch/`, workspace-relative, as jobScratch names them. */
300
+ export async function listScratchFolders(): Promise<string[]> {
301
+ const entries = await readdir(join(WORKSPACE, PATHS.scratch), {
302
+ withFileTypes: true,
303
+ }).catch(() => []);
304
+ return entries
305
+ .filter((entry) => entry.isDirectory())
306
+ .map((entry) => `${PATHS.scratch}/${entry.name}`);
307
+ }
308
+
309
+ /**
310
+ * Removes the workspace folders among `folders` that have not changed for
311
+ * WORKSPACE_KEEP.forMs, judged by the folder itself. Only for folders no job owns
312
+ * (bot.runner sweepJobFiles): a job that has just made its folder is never old
313
+ * enough to be caught. Returns what went.
314
+ */
315
+ export async function removeUnchangedFolders(
316
+ folders: string[],
317
+ ): Promise<string[]> {
318
+ const cutoff = Date.now() - WORKSPACE_KEEP.forMs;
319
+ const removed: string[] = [];
320
+ for (const folder of folders) {
321
+ const full = join(WORKSPACE, folder);
322
+ const info = await stat(full).catch(() => null);
323
+ if (!info?.isDirectory() || info.mtimeMs >= cutoff) continue;
324
+ await rm(full, { recursive: true, force: true }).catch(() => {});
325
+ removed.push(folder);
326
+ }
327
+ return removed;
328
+ }
329
+
160
330
  /**
161
331
  * pnpm looks upward for the nearest `pnpm-workspace.yaml`; without one here a
162
332
  * `pnpm install` inside a bot project would join the app's workspace and
@@ -185,3 +355,50 @@ export async function openWorkspace(): Promise<Sandbox> {
185
355
  toolPath: TOOL_PATH,
186
356
  });
187
357
  }
358
+
359
+ /** Files read off one job's folder at most; a folder of generated files is not a list anyone reads. */
360
+ const JOB_FOLDER_WALK = 500;
361
+
362
+ /**
363
+ * What of a job's files is on disk: the paths it gave `write_file` that still exist,
364
+ * and every file under its own folder however it got there — a shell command, a
365
+ * download, a screenshot. Workspace-relative where inside the workspace, each once,
366
+ * the most recently changed last (bot.run filesUnder).
367
+ */
368
+ export async function filesOnDisk(
369
+ written: string[],
370
+ folder: string | null,
371
+ ): Promise<string[]> {
372
+ const candidates = written.map((path) => resolve(WORKSPACE, path));
373
+ if (folder) {
374
+ let walked = 0;
375
+ for await (const full of walkFiles(join(WORKSPACE, folder))) {
376
+ candidates.push(full);
377
+ walked += 1;
378
+ if (walked >= JOB_FOLDER_WALK) break;
379
+ }
380
+ }
381
+ const found = new Map<string, number>();
382
+ for (const full of candidates) {
383
+ const info = await stat(full).catch(() => null);
384
+ if (!info?.isFile()) continue;
385
+ const rel = relative(WORKSPACE, full);
386
+ found.set(rel && !rel.startsWith("..") ? rel : full, info.mtimeMs);
387
+ }
388
+ return [...found].sort((a, b) => a[1] - b[1]).map(([path]) => path);
389
+ }
390
+
391
+ /**
392
+ * Removes a job's working folder. Its lifetime is the task's: when the row goes,
393
+ * so does what it was working with (features/bot/bot.runner). Never touches
394
+ * `artifacts` or `projects` — those are the user's and outlive every job.
395
+ */
396
+ export async function removeJobScratch(
397
+ taskId: string,
398
+ label: string,
399
+ ): Promise<void> {
400
+ await rm(join(WORKSPACE, jobScratch(taskId, label)), {
401
+ recursive: true,
402
+ force: true,
403
+ }).catch(() => {});
404
+ }
@@ -0,0 +1,37 @@
1
+ import type { KeyboardEvent } from "react";
2
+ import { useState } from "react";
3
+
4
+ /**
5
+ * Draft for a one-line input, committed on blur. Enter blurs (one commit path);
6
+ * a draft shorter than `min` reverts to `value`; an outside change to `value`
7
+ * resets the draft.
8
+ */
9
+ export function useDraft(
10
+ value: string,
11
+ onCommit: (next: string) => void | Promise<void>,
12
+ { min = 1 }: { min?: number } = {},
13
+ ) {
14
+ const [draft, setDraft] = useState(value);
15
+ // Sync during render; an effect would show the stale draft for a frame.
16
+ const [seen, setSeen] = useState(value);
17
+ if (value !== seen) {
18
+ setSeen(value);
19
+ setDraft(value);
20
+ }
21
+
22
+ const commit = () => {
23
+ const next = draft.trim();
24
+ if (next.length < min) return setDraft(value);
25
+ setDraft(next);
26
+ if (next !== value) void onCommit(next);
27
+ };
28
+
29
+ const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
30
+ // Enter during IME composition confirms the character, not the draft.
31
+ if (event.key === "Enter" && !event.nativeEvent.isComposing) {
32
+ event.currentTarget.blur();
33
+ }
34
+ };
35
+
36
+ return { value: draft, set: setDraft, commit, onKeyDown };
37
+ }
@@ -0,0 +1,105 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+
5
+ const HOLD = ["ctrl", "alt", "meta"] as const;
6
+
7
+ const IS_MODIFIER = /^(Control|Alt|Shift|Meta|OS)/;
8
+
9
+ const EDITABLE = /^(input|textarea|select)$/i;
10
+
11
+ export const HOTKEY_CAPTURE = { "data-hotkey-capture": "" } as const;
12
+
13
+ const busy = (target: EventTarget | null) => {
14
+ const node = target as HTMLElement | null;
15
+ if (!node) return false;
16
+ return Boolean(
17
+ EDITABLE.test(node.tagName) ||
18
+ node.isContentEditable ||
19
+ node.closest?.("[data-hotkey-capture]"),
20
+ );
21
+ };
22
+
23
+ export function comboOf(event: {
24
+ code: string;
25
+ ctrlKey: boolean;
26
+ altKey: boolean;
27
+ shiftKey: boolean;
28
+ metaKey: boolean;
29
+ }): string | null {
30
+ const { code, ctrlKey, altKey, shiftKey, metaKey } = event;
31
+ if (!code || IS_MODIFIER.test(code)) return null;
32
+ if (!ctrlKey && !altKey && !metaKey) return null;
33
+
34
+ const held: string[] = [];
35
+ if (ctrlKey) held.push("ctrl");
36
+ if (altKey) held.push("alt");
37
+ if (shiftKey) held.push("shift");
38
+ if (metaKey) held.push("meta");
39
+ return [...held, code].join("+");
40
+ }
41
+
42
+ export const isCombo = (combo: string) => {
43
+ const parts = combo.split("+");
44
+ const code = parts.at(-1);
45
+ return Boolean(
46
+ code &&
47
+ !IS_MODIFIER.test(code) &&
48
+ parts.slice(0, -1).some((part) => HOLD.includes(part as never)),
49
+ );
50
+ };
51
+
52
+ const SIGN: Record<string, [plain: string, mac: string]> = {
53
+ ctrl: ["Ctrl", "⌃"],
54
+ alt: ["Alt", "⌥"],
55
+ shift: ["Shift", "⇧"],
56
+ meta: ["Meta", "⌘"],
57
+ };
58
+
59
+ const keyLabel = (code: string) =>
60
+ code.replace(/^(Key|Digit|Numpad)/, "").replace(/([a-z])([A-Z])/g, "$1 $2") ||
61
+ code;
62
+
63
+ export function hotkeyLabel(combo: string, mac = false): string {
64
+ const parts = combo.split("+");
65
+ const code = parts.at(-1) ?? "";
66
+ const held = parts
67
+ .slice(0, -1)
68
+ .map((part) => SIGN[part]?.[mac ? 1 : 0] ?? part);
69
+ const key = keyLabel(code);
70
+ return mac ? [...held, key].join("") : [...held, key].join(" + ");
71
+ }
72
+
73
+ export function useHotkeyLabel(combo: string | null): string | null {
74
+ const [mac, setMac] = useState(false);
75
+ useEffect(() => setMac(/mac/i.test(navigator.userAgent)), []);
76
+ return combo ? hotkeyLabel(combo, mac) : null;
77
+ }
78
+
79
+ export function useHotkey({
80
+ enabled,
81
+ combo,
82
+ onPress,
83
+ }: {
84
+ enabled: boolean;
85
+ combo: string | null;
86
+ onPress: () => void;
87
+ }) {
88
+ const latest = useRef(onPress);
89
+ latest.current = onPress;
90
+
91
+ useEffect(() => {
92
+ if (!enabled || !combo) return;
93
+
94
+ const onKeyDown = (event: KeyboardEvent) => {
95
+ if (event.repeat || busy(event.target)) return;
96
+ if (comboOf(event) !== combo) return;
97
+
98
+ event.preventDefault();
99
+ latest.current();
100
+ };
101
+
102
+ window.addEventListener("keydown", onKeyDown);
103
+ return () => window.removeEventListener("keydown", onKeyDown);
104
+ }, [enabled, combo]);
105
+ }
@@ -0,0 +1,51 @@
1
+ import { useMemo, useState } from "react";
2
+ import { isFunction } from "../lib/utils";
3
+
4
+ type SetPartialStateAction<T> = Partial<T> | ((prev: T) => Partial<T>);
5
+
6
+ type Patch<T> = ((action: SetPartialStateAction<T>) => void) & {
7
+ [K in keyof T]-?: (value: T[K] | ((prev: T[K]) => T[K])) => void;
8
+ };
9
+
10
+ const hasChanged = <T extends object>(prev: T, next: Partial<T>) =>
11
+ (Object.keys(next) as (keyof T)[]).some(
12
+ (key) => !Object.is(prev[key], next[key]),
13
+ );
14
+
15
+ export const useObjectState = <T extends object>(initial: T | (() => T)) => {
16
+ const [state, setState] = useState<T>(initial);
17
+
18
+ const patch = useMemo(() => {
19
+ const apply = (action: SetPartialStateAction<T>) => {
20
+ setState((prev) => {
21
+ const delta = isFunction(action) ? action(prev) : action;
22
+ return hasChanged(prev, delta) ? { ...prev, ...delta } : prev;
23
+ });
24
+ };
25
+
26
+ // Cache per-key setters so their identity is stable in deps arrays.
27
+ const cache = new Map<string, (value: unknown) => void>();
28
+
29
+ return new Proxy(apply, {
30
+ get(target, p, receiver) {
31
+ if (typeof p === "symbol") return Reflect.get(target, p, receiver);
32
+
33
+ let setter = cache.get(p);
34
+ if (!setter) {
35
+ setter = (value) => {
36
+ apply(
37
+ (prev) =>
38
+ ({
39
+ [p]: isFunction(value) ? value(prev[p as keyof T]) : value,
40
+ }) as Partial<T>,
41
+ );
42
+ };
43
+ cache.set(p, setter);
44
+ }
45
+ return setter;
46
+ },
47
+ }) as Patch<T>;
48
+ }, []);
49
+
50
+ return [state, patch] as const;
51
+ };
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useRef } from "react";
4
+
5
+ /**
6
+ * Calls `onVisible` when the attached element scrolls into view (infinite
7
+ * lists). The callback lives in a ref: rebuilding the observer on an element
8
+ * already in view would fire it again.
9
+ */
10
+ export function useOnVisible(
11
+ onVisible: () => void,
12
+ options: {
13
+ /** False stops observing (nothing left, or a request in flight). */
14
+ enabled?: boolean;
15
+ /**
16
+ * Bump to rebuild the observer, which re-fires if the element is still in
17
+ * view. Observers report crossings, not states: a list shorter than the
18
+ * viewport would otherwise stop after one page.
19
+ */
20
+ revision?: unknown;
21
+ /** IntersectionObserver rootMargin; fires before the element appears. */
22
+ margin?: string;
23
+ } = {},
24
+ ) {
25
+ const { enabled = true, margin = "600px", revision } = options;
26
+
27
+ const callback = useRef(onVisible);
28
+ callback.current = onVisible;
29
+
30
+ const node = useRef<HTMLElement | null>(null);
31
+ const observer = useRef<IntersectionObserver | null>(null);
32
+
33
+ const attach = useCallback((next: HTMLElement | null) => {
34
+ node.current = next;
35
+ }, []);
36
+
37
+ useEffect(() => {
38
+ observer.current?.disconnect();
39
+ const target = node.current;
40
+ if (!target || !enabled) return;
41
+
42
+ observer.current = new IntersectionObserver(
43
+ ([entry]) => {
44
+ if (entry.isIntersecting) callback.current();
45
+ },
46
+ { root: scrollParent(target), rootMargin: margin },
47
+ );
48
+ observer.current.observe(target);
49
+ return () => observer.current?.disconnect();
50
+ }, [enabled, margin, revision]);
51
+
52
+ return attach;
53
+ }
54
+
55
+ /**
56
+ * Nearest scrolling ancestor, or null for the window. With the default root an
57
+ * ancestor's overflow clips the sentinel and it never intersects.
58
+ */
59
+ function scrollParent(node: HTMLElement): Element | null {
60
+ let parent = node.parentElement;
61
+ while (parent) {
62
+ const { overflowY } = getComputedStyle(parent);
63
+ if (
64
+ (overflowY === "auto" || overflowY === "scroll") &&
65
+ parent.scrollHeight > parent.clientHeight
66
+ ) {
67
+ return parent;
68
+ }
69
+ parent = parent.parentElement;
70
+ }
71
+ return null;
72
+ }
@@ -0,0 +1,106 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useSyncExternalStore } from "react";
4
+ import { THEME_STORAGE_KEY, THEMES, type Theme } from "@/lib/theme";
5
+
6
+ /**
7
+ * Theme preference stored in localStorage and applied as the `dark` class on
8
+ * `<html>`. The boot script in app/layout reads the same key before hydration;
9
+ * this store only follows changes after that.
10
+ */
11
+
12
+ const isTheme = (value: unknown): value is Theme =>
13
+ (THEMES as readonly unknown[]).includes(value);
14
+
15
+ function read(): Theme {
16
+ if (typeof window === "undefined") return "system";
17
+ try {
18
+ const raw = window.localStorage.getItem(THEME_STORAGE_KEY);
19
+ return isTheme(raw) ? raw : "system";
20
+ } catch {
21
+ return "system";
22
+ }
23
+ }
24
+
25
+ let theme = read();
26
+ const listeners = new Set<() => void>();
27
+
28
+ function announce() {
29
+ for (const listener of listeners) listener();
30
+ }
31
+
32
+ /** Actual brightness the setting resolves to. */
33
+ export function resolveTheme(value: Theme): "light" | "dark" {
34
+ if (value !== "system") return value;
35
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
36
+ ? "dark"
37
+ : "light";
38
+ }
39
+
40
+ /** Applies to `<html>`; touches exactly what the boot script touches. */
41
+ export function applyTheme(value: Theme) {
42
+ const dark = resolveTheme(value) === "dark";
43
+ document.documentElement.classList.toggle("dark", dark);
44
+ document.documentElement.style.colorScheme = dark ? "dark" : "light";
45
+ }
46
+
47
+ export function setTheme(next: Theme) {
48
+ theme = next;
49
+ try {
50
+ window.localStorage.setItem(THEME_STORAGE_KEY, next);
51
+ } catch {
52
+ // Private mode or blocked storage; the in-memory value still changes.
53
+ }
54
+ applyTheme(next);
55
+ announce();
56
+ }
57
+
58
+ export function useTheme(): Theme {
59
+ return useSyncExternalStore(
60
+ (listener) => {
61
+ listeners.add(listener);
62
+ // Another tab changed it.
63
+ const sync = (event: StorageEvent) => {
64
+ if (event.key !== THEME_STORAGE_KEY) return;
65
+ theme = read();
66
+ applyTheme(theme);
67
+ announce();
68
+ };
69
+ window.addEventListener("storage", sync);
70
+ return () => {
71
+ listeners.delete(listener);
72
+ window.removeEventListener("storage", sync);
73
+ };
74
+ },
75
+ () => theme,
76
+ // Server snapshot; hydration swaps in the stored value.
77
+ () => "system",
78
+ );
79
+ }
80
+
81
+ /** Resolved theme; follows the OS while the setting is "system". */
82
+ export function useResolvedTheme(): "light" | "dark" {
83
+ const current = useTheme();
84
+ const [systemDark, setSystemDark] = useState(false);
85
+ useEffect(() => {
86
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
87
+ const follow = () => setSystemDark(media.matches);
88
+ follow();
89
+ media.addEventListener("change", follow);
90
+ return () => media.removeEventListener("change", follow);
91
+ }, []);
92
+ if (current !== "system") return current;
93
+ return systemDark ? "dark" : "light";
94
+ }
95
+
96
+ /** Re-applies "system" when the OS theme changes. Mount once, in the layout. */
97
+ export function useFollowSystemTheme() {
98
+ const current = useTheme();
99
+ useEffect(() => {
100
+ if (current !== "system") return;
101
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
102
+ const follow = () => applyTheme("system");
103
+ media.addEventListener("change", follow);
104
+ return () => media.removeEventListener("change", follow);
105
+ }, [current]);
106
+ }