thursday-agent 0.9.0 → 0.11.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 (320) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +0 -1
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/routes-manifest.json +0 -6
  6. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  13. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_not-found.html +1 -1
  15. package/.next/server/app/_not-found.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  19. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  20. package/.next/server/app/api/bot/keep-working/route.js.nft.json +1 -1
  21. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
  22. package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
  23. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  24. package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
  25. package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
  26. package/.next/server/app/api/config/route.js.nft.json +1 -1
  27. package/.next/server/app/api/events/route.js.nft.json +1 -1
  28. package/.next/server/app/api/favicon/[host]/route.js.nft.json +1 -1
  29. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  30. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  31. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
  32. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  33. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
  34. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  35. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  36. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  37. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  38. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
  39. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  40. package/.next/server/app/api/reach/route.js +13 -13
  41. package/.next/server/app/api/reach/route.js.nft.json +1 -1
  42. package/.next/server/app/api/routine/route.js.nft.json +1 -1
  43. package/.next/server/app/api/signins/route.js +1 -1
  44. package/.next/server/app/api/signins/route.js.nft.json +1 -1
  45. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  46. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  47. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  48. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  49. package/.next/server/app/api/thursday/text/route.js.nft.json +1 -1
  50. package/.next/server/app/api/thursday/tool-call/route.js +14 -15
  51. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  52. package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
  53. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  54. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  55. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  56. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  58. package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  59. package/.next/server/app/page/server-reference-manifest.json +56 -56
  60. package/.next/server/app/page.js +4 -4
  61. package/.next/server/app/page.js.nft.json +1 -1
  62. package/.next/server/app/page_client-reference-manifest.js +1 -1
  63. package/.next/server/app-paths-manifest.json +0 -1
  64. package/.next/server/chunks/[root-of-the-server]__014ydvq._.js +2 -2
  65. package/.next/server/chunks/[root-of-the-server]__0209ah4._.js +7 -5
  66. package/.next/server/chunks/[root-of-the-server]__04r4h-2._.js +4 -2
  67. package/.next/server/chunks/[root-of-the-server]__0542v_d._.js +3 -0
  68. package/.next/server/chunks/[root-of-the-server]__0gewdg9._.js +2 -2
  69. package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
  70. package/.next/server/chunks/[root-of-the-server]__1-swlkw._.js +2 -2
  71. package/.next/server/chunks/[root-of-the-server]__12iddfq._.js +1 -1
  72. package/.next/server/chunks/[root-of-the-server]__1ai5lme._.js +3 -3
  73. package/.next/server/chunks/[root-of-the-server]__1cjxw0-._.js +2 -2
  74. package/.next/server/chunks/{[root-of-the-server]__1fy2g82._.js → [root-of-the-server]__1k3j52n._.js} +1 -1
  75. package/.next/server/chunks/[root-of-the-server]__1kyjt5m._.js +3 -3
  76. package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
  77. package/.next/server/chunks/_05v8k21._.js +3 -1
  78. package/.next/server/chunks/_0awtznl._.js +1 -1
  79. package/.next/server/chunks/_0na2fj1._.js +1 -1
  80. package/.next/server/chunks/_0x-ej3f._.js +17 -17
  81. package/.next/server/chunks/_12-amdu._.js +2 -2
  82. package/.next/server/chunks/_15740dw._.js +60 -0
  83. package/.next/server/chunks/_1dc_d1q._.js +3 -3
  84. package/.next/server/chunks/_1i350l5._.js +3 -1
  85. package/.next/server/chunks/_1jc5g4p._.js +2 -2
  86. package/.next/server/chunks/_1jn-aep._.js +4 -4
  87. package/.next/server/chunks/_1m88lkh._.js +11 -11
  88. package/.next/server/chunks/_1ogqovh._.js +4 -4
  89. package/.next/server/chunks/_1uuucy-._.js +1 -1
  90. package/.next/server/chunks/_1zxp-p6._.js +1 -1
  91. package/.next/server/chunks/features_0rfvely._.js +5 -3
  92. package/.next/server/chunks/ssr/{[root-of-the-server]__1se25x0._.js → [root-of-the-server]__0hpp3nw._.js} +29 -13
  93. package/.next/server/chunks/ssr/[root-of-the-server]__0m7u9j-._.js +3 -0
  94. package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +24 -60
  95. package/.next/server/chunks/ssr/[root-of-the-server]__1cz2rz5._.js +3 -0
  96. package/.next/server/chunks/ssr/{[root-of-the-server]__0tvavy9._.js → [root-of-the-server]__1iwyp1e._.js} +1 -1
  97. package/.next/server/chunks/ssr/[root-of-the-server]__1w9hqq8._.js +1 -1
  98. package/.next/server/chunks/ssr/_097-ljd._.js +1 -1
  99. package/.next/server/chunks/ssr/{_1tmu8z-._.js → _0btzccu._.js} +2 -2
  100. package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
  101. package/.next/server/chunks/ssr/_0hjzb5o._.js +3 -0
  102. package/.next/server/chunks/ssr/_0ld9_qq._.js +1 -1
  103. package/.next/server/chunks/ssr/{_0urj3mc._.js → _0ouzu9f._.js} +2 -2
  104. package/.next/server/chunks/ssr/{_1-zyvdb._.js → _0u7ko71._.js} +2 -2
  105. package/.next/server/chunks/ssr/{_0ltz2q5._.js → _0wd_o8-._.js} +2 -2
  106. package/.next/server/chunks/ssr/_0ya0cb0._.js +3 -0
  107. package/.next/server/chunks/ssr/_10yyqtk._.js +67 -0
  108. package/.next/server/chunks/ssr/{_1uy0xom._.js → _12eyown._.js} +3 -3
  109. package/.next/server/chunks/ssr/_12mijol._.js +11 -11
  110. package/.next/server/chunks/ssr/_132qkfz._.js +1 -1
  111. package/.next/server/chunks/ssr/_184327j._.js +1 -1
  112. package/.next/server/chunks/ssr/{_1z74dmq._.js → _19ps4p3._.js} +7 -7
  113. package/.next/server/chunks/ssr/{_1flbs77._.js → _1bhy4yg._.js} +2 -2
  114. package/.next/server/chunks/ssr/_1d423aj._.js +7 -5
  115. package/.next/server/chunks/ssr/{_1a-nuxx._.js → _1kd843u._.js} +2 -2
  116. package/.next/server/chunks/ssr/{_0ital-2._.js → _1kq2ye_._.js} +2 -2
  117. package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
  118. package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
  119. package/.next/server/chunks/ssr/_1p8xorg._.js +3 -0
  120. package/.next/server/chunks/ssr/{_1x-kojb._.js → _1q683w8._.js} +3 -3
  121. package/.next/server/chunks/ssr/_1tawxu2._.js +3 -0
  122. package/.next/server/chunks/ssr/{_0scf7xy._.js → _1w2oba6._.js} +2 -2
  123. package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
  124. package/.next/server/chunks/ssr/{_0kjj5ts._.js → _1ybo1hd._.js} +3 -3
  125. package/.next/server/chunks/ssr/features_bot_0s11m4r._.js +3 -0
  126. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  127. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
  128. package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
  129. package/.next/server/chunks/ssr/features_workspace_00jz2hu._.js +1 -1
  130. package/.next/server/chunks/ssr/{node_modules_1af3w91._.js → node_modules_00pocul._.js} +1 -1
  131. package/.next/server/chunks/ssr/{node_modules_0th357h._.js → node_modules_02zt4az._.js} +1 -1
  132. package/.next/server/chunks/ssr/{node_modules_0jljel9._.js → node_modules_05ojmcl._.js} +1 -1
  133. package/.next/server/chunks/ssr/{node_modules_0wnlddw._.js → node_modules_06i6bli._.js} +1 -1
  134. package/.next/server/chunks/ssr/{node_modules_06bb4q8._.js → node_modules_09dj429._.js} +1 -1
  135. package/.next/server/chunks/ssr/{node_modules_068ws5v._.js → node_modules_0auzgvx._.js} +1 -1
  136. package/.next/server/chunks/ssr/{node_modules_1ogczzz._.js → node_modules_0evjwh1._.js} +1 -1
  137. package/.next/server/chunks/ssr/{node_modules_13kwejm._.js → node_modules_0gmdur8._.js} +1 -1
  138. package/.next/server/chunks/ssr/{node_modules_1s-gb-1._.js → node_modules_0j3te-1._.js} +1 -1
  139. package/.next/server/chunks/ssr/{node_modules_1bucjlk._.js → node_modules_0rbaymq._.js} +1 -1
  140. package/.next/server/chunks/ssr/{node_modules_1wbkzl9._.js → node_modules_0tf8_1e._.js} +1 -1
  141. package/.next/server/chunks/ssr/{node_modules_12et8d5._.js → node_modules_0yqy-0b._.js} +1 -1
  142. package/.next/server/chunks/ssr/{node_modules_1lkix61._.js → node_modules_1-53ebx._.js} +1 -1
  143. package/.next/server/chunks/ssr/{node_modules_03d0rfz._.js → node_modules_1-y9aho._.js} +1 -1
  144. package/.next/server/chunks/ssr/{node_modules_0x630an._.js → node_modules_11bltzp._.js} +1 -1
  145. package/.next/server/chunks/ssr/{node_modules_0uyh_vb._.js → node_modules_18jn2qo._.js} +1 -1
  146. package/.next/server/chunks/ssr/{node_modules_0dzm4um._.js → node_modules_1_te3bu._.js} +1 -1
  147. package/.next/server/chunks/ssr/{node_modules_07rs_p2._.js → node_modules_1c0pg3k._.js} +1 -1
  148. package/.next/server/chunks/ssr/{node_modules_0sgy-dc._.js → node_modules_1en2jx5._.js} +1 -1
  149. package/.next/server/chunks/ssr/{node_modules_0ubdtk-._.js → node_modules_1p-c8cr._.js} +1 -1
  150. package/.next/server/chunks/ssr/{node_modules_1z1lrbk._.js → node_modules_1qe-oxg._.js} +1 -1
  151. package/.next/server/chunks/ssr/{node_modules_04_sbbc._.js → node_modules_1rjt833._.js} +1 -1
  152. package/.next/server/chunks/ssr/{node_modules_0yvz9e0._.js → node_modules_1tzw5sn._.js} +1 -1
  153. package/.next/server/chunks/ssr/{node_modules_1uwg-gz._.js → node_modules_1x6uwvn._.js} +1 -1
  154. package/.next/server/chunks/ssr/{node_modules_0t_ez6-._.js → node_modules_1zqddke._.js} +1 -1
  155. package/.next/server/chunks/ssr/{node_modules_05ybmta._.js → node_modules_211zgim._.js} +1 -1
  156. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0r443fh._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0y51bqj._.js} +1 -1
  157. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_10wnlbk._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1sc114e._.js} +1 -1
  158. package/.next/server/middleware-build-manifest.js +3 -3
  159. package/.next/server/pages/404.html +1 -1
  160. package/.next/server/pages/500.html +1 -1
  161. package/.next/server/server-reference-manifest.js +1 -1
  162. package/.next/server/server-reference-manifest.json +57 -57
  163. package/.next/static/chunks/{13ixzheqw7o-w.js → 03-724sj0nk_z.js} +2 -2
  164. package/.next/static/chunks/03bhu4mk8en2q.js +1 -0
  165. package/.next/static/chunks/{3h2e-xv1zxh_f.js → 04imzjhvzw65d.js} +2 -2
  166. package/.next/static/chunks/{27tvv1i-smrck.js → 0gljzv9fz1g7d.js} +1 -1
  167. package/.next/static/chunks/{37j27b3e1gt7r.js → 0h0lythv1ql93.js} +1 -1
  168. package/.next/static/chunks/{2o4dndk0d68do.js → 0hzbj7f48cmx0.js} +1 -1
  169. package/.next/static/chunks/0lwunwbl7u7pt.js +1 -0
  170. package/.next/static/chunks/{0u9ae9k_2g7k3.js → 0r6nwxg4dv83t.js} +1 -1
  171. package/.next/static/chunks/0rn9nbg6aif9f.js +17 -0
  172. package/.next/static/chunks/{3bbzsitgiltnt.js → 0x_w4shtvrrfg.js} +1 -1
  173. package/.next/static/chunks/{2sdwbfs1x5skp.js → 0ympuji_ufypd.js} +1 -1
  174. package/.next/static/chunks/{3t9huugh-5_ps.js → 0zmrz4zfzm4dt.js} +1 -1
  175. package/.next/static/chunks/{0qmd8n52hp-6v.js → 11tlxkr7a_rpc.js} +1 -1
  176. package/.next/static/chunks/{2c64drdevdla3.js → 12scesro428m7.js} +2 -2
  177. package/.next/static/chunks/16s2fx3890_jd.js +65 -0
  178. package/.next/static/chunks/{0oyz95sujd2c7.js → 1qzfgplyo18tc.js} +1 -1
  179. package/.next/static/chunks/{04e-e--5lk26x.js → 1r4zajz14jp9-.js} +1 -1
  180. package/.next/static/chunks/1s6m4cmbn7htf.js +1 -0
  181. package/.next/static/chunks/1wl9t1-3i-q8p.css +1 -0
  182. package/.next/static/chunks/2-asot3p1j3yg.js +1 -0
  183. package/.next/static/chunks/{0u3ktztrzu1fa.js → 2-jn4xscbm-i2.js} +1 -1
  184. package/.next/static/chunks/22h9gvclqtx_2.js +1 -0
  185. package/.next/static/chunks/{337g9g_ux2gj-.js → 26-eblxwnykck.js} +1 -1
  186. package/.next/static/chunks/{43q9cgbuw8krx.js → 2kzwhbuobpkw3.js} +2 -2
  187. package/.next/static/chunks/2m6jd3586ijb-.js +1 -0
  188. package/.next/static/chunks/{0_awn8tucznxc.js → 304q9p54tcgm1.js} +1 -1
  189. package/.next/static/chunks/{1u5i-jti87jbo.js → 35wdx3wt7cg4a.js} +1 -1
  190. package/.next/static/chunks/37unh9460im7v.js +18 -0
  191. package/.next/static/chunks/{01xjer0i_e5z5.js → 39gj5vxz1zpm_.js} +1 -1
  192. package/.next/static/chunks/{427qsc6kahxyo.js → 39vrqy1r_00jc.js} +1 -1
  193. package/.next/static/chunks/{245hb85nsy6j4.js → 3dipmu0mptqum.js} +1 -1
  194. package/.next/static/chunks/3gki-squw28yu.js +1 -0
  195. package/.next/static/chunks/{3obf9n3cxj99k.js → 3jvldk2wvwbge.js} +1 -1
  196. package/.next/static/chunks/{38301ji-85i30.js → 3nnzdl_te20hv.js} +1 -1
  197. package/.next/static/chunks/{2m267diam_vib.js → 3tz4dr0_8pq1-.js} +1 -1
  198. package/.next/static/chunks/{2gocl3dq0_h1s.js → 3x6pggb-2kyrk.js} +1 -1
  199. package/.next/static/chunks/{025bmrgeukcb2.js → 4440mws9jz3mt.js} +1 -1
  200. package/README.md +4 -0
  201. package/app/api/thursday/tool-call/route.ts +1 -15
  202. package/components/ui/radio-group.tsx +2 -2
  203. package/docs/images/demo-still.jpg +0 -0
  204. package/docs/live-calls.md +19 -11
  205. package/features/ai/prompts/live.prompt.ts +51 -26
  206. package/features/ai/prompts/prompt-helper.ts +1 -9
  207. package/features/ai/prompts/thursday.prompt.ts +8 -2
  208. package/features/bot/bot.run.ts +9 -3
  209. package/features/bot/bot.seed.ts +35 -91
  210. package/features/bot/components/bot-room.tsx +0 -6
  211. package/features/bot/components/bot-setting.tsx +4 -4
  212. package/features/bot/components/room-pill.tsx +4 -2
  213. package/features/bot/seed-bots.ts +2 -2
  214. package/features/intro/components/intro.tsx +2 -3
  215. package/features/reach/reach.ts +2 -2
  216. package/features/routine/components/routine-setting.tsx +1 -1
  217. package/features/signins/signins.query.ts +66 -1
  218. package/features/thursday/components/side-captions.tsx +31 -8
  219. package/features/thursday/components/thursday-setting.tsx +5 -5
  220. package/features/thursday/components/thursday.tsx +97 -39
  221. package/features/thursday/components/write-line.tsx +28 -17
  222. package/features/thursday/thursday.action.ts +4 -20
  223. package/features/thursday/thursday.schema.ts +0 -9
  224. package/features/thursday/thursday.store.ts +11 -15
  225. package/features/thursday/thursday.text.ts +10 -8
  226. package/features/thursday/tool-line.ts +17 -6
  227. package/features/thursday/use-call-ring.ts +1 -5
  228. package/features/thursday/use-text-call.ts +37 -21
  229. package/features/thursday/use-thursday.ts +14 -44
  230. package/features/workspace/workspace.ts +4 -2
  231. package/guide/bots.md +13 -3
  232. package/guide/calls.md +7 -5
  233. package/knip.json +1 -0
  234. package/lib/live/live.session.ts +0 -39
  235. package/package.json +1 -1
  236. package/scripts/live-session.test.mts +19 -26
  237. package/seed-skills/analyst/README.md +18 -0
  238. package/seed-skills/analyst/data-report/SKILL.md +60 -0
  239. package/seed-skills/analyst/data-report/references/brief.md +31 -0
  240. package/seed-skills/analyst/data-report/references/company.md +41 -0
  241. package/seed-skills/analyst/data-report/references/comparison.md +42 -0
  242. package/seed-skills/analyst/data-report/references/decision.md +28 -0
  243. package/seed-skills/analyst/data-report/references/market-size.md +49 -0
  244. package/seed-skills/analyst/data-report/references/price-check.md +33 -0
  245. package/seed-skills/analyst/data-report/references/sources.md +51 -0
  246. package/seed-skills/analyst/data-report/references/trend.md +39 -0
  247. package/seed-skills/analyst/data-report/scripts/fetch.mjs +521 -0
  248. package/seed-skills/insta/instagram/SKILL.md +36 -0
  249. package/seed-skills/insta/instagram/references/dm.md +49 -0
  250. package/seed-skills/insta/instagram/references/post.md +74 -0
  251. package/seed-skills/insta/instagram/scripts/compose.mjs +333 -0
  252. package/seed-skills/insta/instagram/scripts/inbox.mjs +163 -0
  253. package/seed-skills/insta/instagram/scripts/lib.mjs +33 -0
  254. package/seed-skills/insta/instagram/scripts/media.mjs +104 -0
  255. package/seed-skills/insta/instagram/scripts/profile.mjs +143 -0
  256. package/seed-skills/insta/instagram/scripts/read.mjs +244 -0
  257. package/seed-skills/insta/instagram/scripts/search.mjs +137 -0
  258. package/seed-skills/insta/instagram/scripts/selectors.mjs +56 -0
  259. package/seed-skills/insta/instagram/scripts/sheet.mjs +43 -0
  260. package/seed-skills/insta/instagram/templates/carousel.html +62 -0
  261. package/seed-skills/marketer/README.md +6 -4
  262. package/seed-skills/marketer/copywriting/SKILL.md +1 -1
  263. package/seed-skills/marketer/emails/SKILL.md +1 -1
  264. package/seed-skills/marketer/launch/SKILL.md +1 -1
  265. package/seed-skills/marketer/product-marketing/SKILL.md +5 -5
  266. package/seed-skills/marketer/seo-audit/SKILL.md +1 -1
  267. package/seed-skills/marketer/social/SKILL.md +1 -1
  268. package/seed-skills/tutor/picture-book/SKILL.md +16 -0
  269. package/seed-skills/tutor/picture-book/book/book.css +181 -0
  270. package/seed-skills/tutor/picture-book/book/book.html +33 -0
  271. package/seed-skills/tutor/picture-book/book/book.js +60 -0
  272. package/seed-skills/tutor/picture-book/references/picture-book.md +104 -0
  273. package/seed-skills/tutor/picture-book/scripts/book-video.mjs +177 -0
  274. package/seed-skills/tutor/picture-book/scripts/book.mjs +98 -0
  275. package/skills/browser/SKILL.md +13 -4
  276. package/skills/browser/references/scripts.md +55 -0
  277. package/skills/browser/scripts/image-size.mjs +30 -0
  278. package/skills/browser/scripts/render.mjs +126 -0
  279. package/skills/browser/scripts/session.mjs +105 -0
  280. package/skills/browser/scripts/sheet.mjs +97 -0
  281. package/skills/browser/scripts/webimage.mjs +105 -0
  282. package/skills/interactive-page/SKILL.md +4 -3
  283. package/skills/interactive-page/scripts/chart.mjs +679 -0
  284. package/skills/interactive-page/scripts/page.mjs +1 -0
  285. package/.next/server/app/api/probe/route/app-paths-manifest.json +0 -3
  286. package/.next/server/app/api/probe/route/build-manifest.json +0 -12
  287. package/.next/server/app/api/probe/route/server-reference-manifest.json +0 -4
  288. package/.next/server/app/api/probe/route.js +0 -10
  289. package/.next/server/app/api/probe/route.js.map +0 -5
  290. package/.next/server/app/api/probe/route.js.nft.json +0 -1
  291. package/.next/server/app/api/probe/route_client-reference-manifest.js +0 -3
  292. package/.next/server/chunks/[root-of-the-server]__012s2o8._.js +0 -3
  293. package/.next/server/chunks/[root-of-the-server]__0z0hc1f._.js +0 -4
  294. package/.next/server/chunks/[root-of-the-server]__1v7-zke._.js +0 -5
  295. package/.next/server/chunks/_0whlpyi._.js +0 -4
  296. package/.next/server/chunks/_next-internal_server_app_api_probe_route_actions_0cqd30j.js +0 -3
  297. package/.next/server/chunks/ssr/[root-of-the-server]__0oo-wlv._.js +0 -103
  298. package/.next/server/chunks/ssr/[root-of-the-server]__0zuns7e._.js +0 -3
  299. package/.next/server/chunks/ssr/_070nv1_._.js +0 -3
  300. package/.next/server/chunks/ssr/_0dshsh4._.js +0 -3
  301. package/.next/server/chunks/ssr/_0l_mcfz._.js +0 -3
  302. package/.next/server/chunks/ssr/_0u5xv6g._.js +0 -3
  303. package/.next/server/chunks/ssr/_157yfrk._.js +0 -3
  304. package/.next/server/chunks/ssr/features_bot_1nr94ks._.js +0 -3
  305. package/.next/static/chunks/06nt9tatr-dug.js +0 -1
  306. package/.next/static/chunks/0ge-hx0ed83a1.js +0 -1
  307. package/.next/static/chunks/0pda_vf-abwvx.js +0 -1
  308. package/.next/static/chunks/0rowp9rx3qbxu.js +0 -18
  309. package/.next/static/chunks/1grkt5gr8pbg1.js +0 -17
  310. package/.next/static/chunks/1yugpvvi8qrbu.js +0 -101
  311. package/.next/static/chunks/27lkanxvtdn09.js +0 -1
  312. package/.next/static/chunks/3fec-aeteddr-.js +0 -1
  313. package/.next/static/chunks/3go3hwp1z2_h2.css +0 -1
  314. package/.next/static/chunks/3h2bun3hcggg8.js +0 -1
  315. package/.next/static/chunks/3j1y335o95mj7.js +0 -1
  316. package/lib/probe.server.ts +0 -17
  317. package/lib/probe.ts +0 -55
  318. /package/.next/static/{u8kJqShUAa49vY7BxAWsw → En1vUPazxcYjPjnnahcN5}/_buildManifest.js +0 -0
  319. /package/.next/static/{u8kJqShUAa49vY7BxAWsw → En1vUPazxcYjPjnnahcN5}/_clientMiddlewareManifest.js +0 -0
  320. /package/.next/static/{u8kJqShUAa49vY7BxAWsw → En1vUPazxcYjPjnnahcN5}/_ssgManifest.js +0 -0
@@ -123,6 +123,7 @@ export function SideCaptions({
123
123
  under = null,
124
124
  ahead = false,
125
125
  workOf,
126
+ typed = false,
126
127
  }: {
127
128
  turns: Turn[];
128
129
  pinned: Pinned;
@@ -139,6 +140,8 @@ export function SideCaptions({
139
140
  ahead?: boolean;
140
141
  /** The work behind one of her turns, drawn under it while it is level; null for none. */
141
142
  workOf?: (turn: string) => ReactNode;
143
+ /** A call in writing: their words were typed whole, so they arrive whole. */
144
+ typed?: boolean;
142
145
  }) {
143
146
  const last = turns.at(-1);
144
147
  return (
@@ -154,6 +157,7 @@ export function SideCaptions({
154
157
  under={role === "assistant" ? under : null}
155
158
  ahead={role === "assistant" && ahead}
156
159
  workOf={role === "assistant" ? workOf : undefined}
160
+ whole={typed && role === "user"}
157
161
  />
158
162
  ))}
159
163
  </>
@@ -169,6 +173,7 @@ function SideColumn({
169
173
  under,
170
174
  ahead,
171
175
  workOf,
176
+ whole,
172
177
  }: {
173
178
  role: Role;
174
179
  /** This side's turns only. */
@@ -180,28 +185,38 @@ function SideColumn({
180
185
  under: ReactNode;
181
186
  ahead: boolean;
182
187
  workOf: ((turn: string) => ReactNode) | undefined;
188
+ /** The latest turn is drawn at once rather than letter by letter. */
189
+ whole: boolean;
183
190
  }) {
184
191
  const mine = role === "user";
185
192
  // Heights as laid out, before any transform: text grows while it is said, and
186
193
  // receding clamps a turn to four lines
187
194
  const [heights, setHeights] = useState<Record<string, number>>({});
195
+ // Widths too: what stands under her level words is as wide as they are
196
+ const [widths, setWidths] = useState<Record<string, number>>({});
188
197
  const watch = useRef<ResizeObserver | null>(null);
189
198
  useEffect(() => () => watch.current?.disconnect(), []);
190
199
  const measure = useCallback((node: HTMLDivElement | null) => {
191
200
  if (!node) return;
192
- watch.current ??= new ResizeObserver((entries) =>
193
- setHeights((was) => {
201
+ const sizes =
202
+ (read: (box: HTMLElement) => number) =>
203
+ (was: Record<string, number>, entries: ResizeObserverEntry[]) => {
194
204
  let next = was;
195
205
  for (const entry of entries) {
196
206
  const box = entry.target as HTMLElement;
197
207
  const id = box.dataset.turn;
198
- if (!id || was[id] === box.offsetHeight) continue;
208
+ if (!id || was[id] === read(box)) continue;
199
209
  if (next === was) next = { ...was };
200
- next[id] = box.offsetHeight;
210
+ next[id] = read(box);
201
211
  }
202
212
  return next;
203
- }),
204
- );
213
+ };
214
+ const tall = sizes((box) => box.offsetHeight);
215
+ const wide = sizes((box) => box.offsetWidth);
216
+ watch.current ??= new ResizeObserver((entries) => {
217
+ setHeights((was) => tall(was, entries));
218
+ setWidths((was) => wide(was, entries));
219
+ });
205
220
  watch.current.observe(node);
206
221
  return () => watch.current?.unobserve(node);
207
222
  }, []);
@@ -273,6 +288,9 @@ function SideColumn({
273
288
  }
274
289
  className={cn(
275
290
  "group/turn absolute top-0 w-max max-w-full text-[17px] leading-[1.675] tracking-[0.3px] text-foreground transition-transform duration-[520ms] ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none",
291
+ whole &&
292
+ k === turns.length - 1 &&
293
+ "animate-in fade-in duration-200 motion-reduce:animate-none",
276
294
  mine ? "left-0 origin-top-left" : "right-0 origin-top-right",
277
295
  !shown && "invisible",
278
296
  pickable && "pointer-events-auto cursor-pointer",
@@ -299,7 +317,7 @@ function SideColumn({
299
317
  />
300
318
  )}
301
319
  {/* Each side's latest turn keeps its letters wherever it sits, so going back and returning never draws it again */}
302
- {k === turns.length - 1 ? (
320
+ {k === turns.length - 1 && !whole ? (
303
321
  <Letters text={turn.text} />
304
322
  ) : (
305
323
  turn.text
@@ -312,7 +330,12 @@ function SideColumn({
312
330
  <div
313
331
  ref={measure}
314
332
  data-turn={UNDER}
315
- style={{ transform: `translateY(${(underTop ?? 0).toFixed(1)}px)` }}
333
+ style={{
334
+ transform: `translateY(${(underTop ?? 0).toFixed(1)}px)`,
335
+ // under her words it takes their width, so its lines start where they do;
336
+ // before she has said any, the side's
337
+ width: at < turns.length ? widths[turns[at].id] : undefined,
338
+ }}
316
339
  className={cn(
317
340
  "absolute top-0 flex w-full transition-transform duration-[520ms] ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none",
318
341
  mine ? "left-0 justify-start" : "right-0 justify-end",
@@ -405,7 +405,7 @@ function BackendModelPicker({
405
405
  className={cn(
406
406
  "min-w-0 space-y-0.5 rounded-lg border px-3 py-2.5 text-left outline-none transition-colors focus-visible:ring-3 focus-visible:ring-ring/50",
407
407
  picked
408
- ? "border-foreground bg-muted/40"
408
+ ? "border-brand bg-brand/4"
409
409
  : "border-border/60 hover:bg-muted/50",
410
410
  )}
411
411
  >
@@ -413,7 +413,7 @@ function BackendModelPicker({
413
413
  <span className="min-w-0 flex-1 truncate text-sm font-medium">
414
414
  {model.label}
415
415
  </span>
416
- {picked && <Check className="size-3.5 shrink-0" />}
416
+ {picked && <Check className="size-3.5 shrink-0 text-brand" />}
417
417
  </span>
418
418
  <span className="block truncate font-mono text-[11px] text-muted-foreground">
419
419
  {model.id} · {model.tier}
@@ -714,7 +714,7 @@ function Slider({
714
714
  {...range}
715
715
  value={value}
716
716
  onChange={(event) => onChange(Number(event.target.value))}
717
- className="min-w-0 flex-1 accent-foreground"
717
+ className="min-w-0 flex-1 accent-brand"
718
718
  />
719
719
  <span className="w-10 shrink-0 text-right font-mono text-[11px] text-muted-foreground tabular-nums">
720
720
  {format(value)}
@@ -762,7 +762,7 @@ function Captions({
762
762
  className={cn(
763
763
  "min-w-0 space-y-3 rounded-xl border p-3 text-left outline-none transition-colors focus-visible:ring-3 focus-visible:ring-ring/50",
764
764
  picked
765
- ? "border-foreground"
765
+ ? "border-brand bg-brand/4"
766
766
  : "border-border/60 hover:bg-muted/50",
767
767
  )}
768
768
  >
@@ -776,7 +776,7 @@ function Captions({
776
776
  {CAPTION_LABEL[view].hint}
777
777
  </span>
778
778
  </span>
779
- {picked && <Check className="size-4 shrink-0" />}
779
+ {picked && <Check className="size-4 shrink-0 text-brand" />}
780
780
  </span>
781
781
  </button>
782
782
  );
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { formatDistanceToNowStrict } from "date-fns";
3
+ import { formatDistanceStrict } from "date-fns";
4
4
  import {
5
5
  ChevronDown,
6
6
  ChevronUp,
@@ -187,14 +187,14 @@ function CallScreen({
187
187
  if (lastRole !== "user") setMaking(false);
188
188
  else if (work.on) setMaking(true);
189
189
  }, [lastRole, work.on]);
190
- // The work behind each of her turns stays with it for the call: what ran while she was on
191
- // it, or before she answered, is kept under her words and shows again when she is gone
192
- // back to. A later stretch of work on the same turn adds to it
193
- const kept = useKeptWork(talk, work.lines);
194
- // the last turn is still being said: her voice is on, or yours came after hers and she has not answered
190
+ // The work behind each of her turns stays with it for the call, under her words and again
191
+ // when she is gone back to; work she has said nothing after yet is her turn in the making
192
+ const kept = useKeptWork(talk, work.lines, work.on);
193
+ // the last turn is still being said: her voice is on, or yours came after hers and she has
194
+ // not answered — never yours in writing, which was sent whole
195
195
  const saying =
196
196
  (lastRole === "assistant" && status === "speaking") ||
197
- (lastRole === "user" && status === "listening");
197
+ (lastRole === "user" && status === "listening" && !writing);
198
198
  const calling = ringing !== null && ringing.missedAt === null;
199
199
  const ringWord = useRingWord(calling);
200
200
  return (
@@ -262,18 +262,24 @@ function CallScreen({
262
262
  live={saying}
263
263
  onPick={turns.pick}
264
264
  under={
265
- work.held && work.lines.length > 0 ? (
266
- <WorkStack lines={work.lines} shown={work.on} />
265
+ work.held && kept.pending.length > 0 ? (
266
+ <WorkStack lines={kept.pending} shown={work.on} />
267
267
  ) : null
268
268
  }
269
269
  // with the work gone and her answer not yet begun, her last words come back level;
270
270
  // thinking alone takes nothing from them, since it stands under her face
271
- ahead={making && work.held && work.lines.length > 0}
272
- workOf={(turn) =>
273
- kept[turn]?.length ? (
274
- <WorkStack lines={kept[turn]} shown />
275
- ) : null
276
- }
271
+ ahead={making && work.held && kept.pending.length > 0}
272
+ typed={writing}
273
+ workOf={(turn) => {
274
+ // with her words last, work she has said nothing after stands under them
275
+ const lines = [
276
+ ...(kept.turns[turn] ?? []),
277
+ ...(turn === kept.latest && lastRole === "assistant"
278
+ ? kept.pending
279
+ : []),
280
+ ];
281
+ return lines.length ? <WorkStack lines={lines} shown /> : null;
282
+ }}
277
283
  />
278
284
  )}
279
285
  </div>
@@ -1011,34 +1017,72 @@ function useWork(drawn: ActivityLine | null, thinking: boolean) {
1011
1017
  return { lines, on, held };
1012
1018
  }
1013
1019
 
1020
+ type Kept = {
1021
+ /** Her turns by id, each with the lines behind it in the order they became its. */
1022
+ turns: Record<string, ActivityLine[]>;
1023
+ /** Whose each line is, once it is anyone's. */
1024
+ owner: Record<string, string>;
1025
+ /** Her words as they stood when each line was first drawn (`said`). */
1026
+ seen: Record<string, string | null>;
1027
+ };
1028
+
1029
+ const KEPT_NONE: Kept = { turns: {}, owner: {}, seen: {} };
1030
+
1014
1031
  /**
1015
- * The lines of work behind each of her turns, by turn: whatever ran while her latest turn
1016
- * was hers — before she answered, while she spoke — is that turn's. A turn is theirs until
1017
- * she speaks, so work before her answer waits for it. Lines are merged by the call they
1018
- * came from, so a second stretch on one turn adds to the first. A new call starts empty.
1032
+ * The lines of work behind each of her turns, by turn. A line is the turn of the first words
1033
+ * she says after it is drawn — a turn she starts or one she goes on with — and stays that
1034
+ * turn's for the call, however long it lingers: the pages a search left on the line belong
1035
+ * to the answer they led to, never to the next one. Work that stops with her words last and
1036
+ * nothing said since is her last turn's. Until then a line is `pending`: her turn in the
1037
+ * making, or, with her words last, drawn under them. Their words arriving late (a transcript
1038
+ * lags the audio) moves nothing, since only hers decide. A new call starts empty.
1019
1039
  */
1020
- function useKeptWork(talk: Turn[], lines: ActivityLine[]) {
1021
- const [kept, setKept] = useState<Record<string, ActivityLine[]>>({});
1022
- const last = talk.at(-1);
1023
- const owner = last?.role === "assistant" ? last.id : null;
1040
+ function useKeptWork(talk: Turn[], lines: ActivityLine[], on: boolean) {
1041
+ const [kept, setKept] = useState<Kept>(KEPT_NONE);
1042
+ const hers = talk.findLast((turn) => turn.role === "assistant");
1043
+ const latest = hers?.id ?? null;
1044
+ const said = hers ? `${hers.id}:${hers.text.length}` : null;
1045
+ const closing = !on && talk.at(-1)?.role === "assistant";
1024
1046
  useEffect(() => {
1025
- if (!owner || !lines.length) return;
1026
1047
  setKept((was) => {
1027
- const now = new Set(lines.map(lineKey));
1028
- const merged = [
1029
- ...(was[owner] ?? []).filter((line) => !now.has(lineKey(line))),
1030
- ...lines,
1031
- ];
1032
- const same =
1033
- was[owner]?.length === merged.length &&
1034
- merged.every((line, k) => line === was[owner]?.[k]);
1035
- return same ? was : { ...was, [owner]: merged };
1048
+ let next = was;
1049
+ for (const line of lines) {
1050
+ const key = lineKey(line);
1051
+ let turn = next.owner[key];
1052
+ if (turn === undefined) {
1053
+ const seen = key in next.seen ? next.seen[key] : said;
1054
+ if (!(key in next.seen))
1055
+ next = { ...next, seen: { ...next.seen, [key]: said } };
1056
+ if (!latest || (seen === said && !closing)) continue;
1057
+ turn = latest;
1058
+ next = { ...next, owner: { ...next.owner, [key]: turn } };
1059
+ }
1060
+ // the line as it is drawn now: finished, or with the pages its search read
1061
+ const list = next.turns[turn] ?? [];
1062
+ const at = list.findIndex((one) => lineKey(one) === key);
1063
+ if (at >= 0 && list[at] === line) continue;
1064
+ next = {
1065
+ ...next,
1066
+ turns: {
1067
+ ...next.turns,
1068
+ [turn]:
1069
+ at >= 0
1070
+ ? list.map((one, k) => (k === at ? line : one))
1071
+ : [...list, line],
1072
+ },
1073
+ };
1074
+ }
1075
+ return next;
1036
1076
  });
1037
- }, [owner, lines]);
1077
+ }, [lines, said, latest, closing]);
1038
1078
  useEffect(() => {
1039
- if (!talk.length) setKept({});
1079
+ if (!talk.length) setKept(KEPT_NONE);
1040
1080
  }, [talk.length]);
1041
- return kept;
1081
+ const pending = useMemo(
1082
+ () => lines.filter((line) => kept.owner[lineKey(line)] === undefined),
1083
+ [lines, kept.owner],
1084
+ );
1085
+ return { turns: kept.turns, pending, latest };
1042
1086
  }
1043
1087
 
1044
1088
  /**
@@ -1060,7 +1104,7 @@ function WorkStack({
1060
1104
  <div
1061
1105
  aria-hidden={!shown}
1062
1106
  className={cn(
1063
- "pointer-events-auto relative w-[min(100%,16.5rem)] transition-opacity duration-300 ease-out",
1107
+ "pointer-events-auto relative w-full transition-opacity duration-300 ease-out",
1064
1108
  !shown && "pointer-events-none opacity-0",
1065
1109
  )}
1066
1110
  style={{ height: rows * WORK_ROW }}
@@ -1159,8 +1203,7 @@ function Incoming({
1159
1203
  <p className="mb-1.5 flex items-center gap-2 font-mono text-[11px] text-muted-foreground">
1160
1204
  <PhoneMissed className="size-3" />
1161
1205
  <span>
1162
- Missed ·{" "}
1163
- {formatDistanceToNowStrict(missedAt, { addSuffix: true })}
1206
+ Missed · <MissedWhen at={missedAt} />
1164
1207
  </span>
1165
1208
  <button
1166
1209
  type="button"
@@ -1276,6 +1319,21 @@ function Incoming({
1276
1319
  );
1277
1320
  }
1278
1321
 
1322
+ /** Said again every half minute: a missed call left on the screen is read hours later too. */
1323
+ const MISSED_TICK_MS = 30_000;
1324
+
1325
+ /** How long ago it rang out: "just now" for the first minute, then minutes and hours. */
1326
+ function MissedWhen({ at }: { at: number }) {
1327
+ const [now, setNow] = useState(() => Date.now());
1328
+ useEffect(() => {
1329
+ const tick = setInterval(() => setNow(Date.now()), MISSED_TICK_MS);
1330
+ return () => clearInterval(tick);
1331
+ }, []);
1332
+ return now - at < 60_000
1333
+ ? "just now"
1334
+ : formatDistanceStrict(at, now, { addSuffix: true });
1335
+ }
1336
+
1279
1337
  /** The wake phrase as the screen shows it wherever saying it does something. */
1280
1338
  function SaidPhrase({ phrase }: { phrase: string }) {
1281
1339
  return (
@@ -494,24 +494,31 @@ export function WriteLine({
494
494
  {written.error}
495
495
  </p>
496
496
  )}
497
- {/* Never by itself: what a turn costs changes with what it runs on, so they press it */}
498
- {toHer && written?.error && written.fallback && (
499
- <div className="flex justify-center">
497
+ {/* The way on from a turn that broke: the same words again, and where keys are set.
498
+ Never by itself: what a turn costs changes with what it runs on, so they press it */}
499
+ {toHer && written?.error && (
500
+ <div className="flex items-center justify-center gap-3">
500
501
  <Button
501
502
  type="button"
502
503
  size="sm"
503
504
  variant="brand"
504
505
  disabled={written.busy}
505
506
  onClick={() => {
506
- useThursdayStore
507
- .getState()
508
- .patch({ textModel: written.fallback });
507
+ if (written.fallback)
508
+ useThursdayStore
509
+ .getState()
510
+ .patch({ textModel: written.fallback });
509
511
  written.again();
510
512
  }}
511
513
  >
512
514
  <RotateCw />
513
- Send it again on your OpenAI key
515
+ {written.fallback
516
+ ? "Send it again on your OpenAI key"
517
+ : "Send it again"}
514
518
  </Button>
519
+ <span className="font-mono text-[10.5px] text-muted-foreground/70">
520
+ <KeysLink>API keys</KeysLink>
521
+ </span>
515
522
  </div>
516
523
  )}
517
524
  <p className="flex flex-wrap items-center justify-center gap-x-2 gap-y-1 font-mono text-[10.5px] text-muted-foreground/70">
@@ -549,15 +556,6 @@ function RunsOn({ runsOn }: { runsOn: TextModelRef | null }) {
549
556
  onChange={(textModel) => useThursdayStore.getState().patch({ textModel })}
550
557
  />
551
558
  );
552
- const toKeys = (words: string) => (
553
- <button
554
- type="button"
555
- onClick={() => openSettings("keys")}
556
- className="rounded-sm text-foreground/80 underline underline-offset-3 outline-none hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
557
- >
558
- {words}
559
- </button>
560
- );
561
559
  if (runsOn)
562
560
  return (
563
561
  <>
@@ -572,13 +570,26 @@ function RunsOn({ runsOn }: { runsOn: TextModelRef | null }) {
572
570
  <span>Writing to her needs a {plan}, an OpenAI key, or</span>
573
571
  {pick}
574
572
  <Dot />
575
- {toKeys("API keys")}
573
+ <KeysLink>API keys</KeysLink>
576
574
  </>
577
575
  );
578
576
  }
579
577
 
580
578
  const Dot = () => <span className="text-muted-foreground/40">·</span>;
581
579
 
580
+ /** The way to Settings › API keys, as words in the line's small print. */
581
+ function KeysLink({ children }: { children: string }) {
582
+ return (
583
+ <button
584
+ type="button"
585
+ onClick={() => openSettings("keys")}
586
+ className="rounded-sm text-foreground/80 underline underline-offset-3 outline-none hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
587
+ >
588
+ {children}
589
+ </button>
590
+ );
591
+ }
592
+
582
593
  function Mark({ bot, size }: { bot: BotRef; size: number }) {
583
594
  if (bot === HER) return <ThursdayMark size={size} className="shrink-0" />;
584
595
  return (
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { asSchema } from "ai";
4
4
  import z from "zod";
5
- import { LIVE_PROVIDER } from "@/features/ai/live.schema";
5
+ import { LIVE_PROVIDER, LiveSettingsSchema } from "@/features/ai/live.schema";
6
6
  import { loadTools } from "@/features/ai/load-tools";
7
7
  import { textModelRefSchema } from "@/features/ai/model.schema";
8
8
  import { loadLastCall } from "@/features/ai/prompts/call-last";
@@ -20,7 +20,6 @@ import {
20
20
  type ToolManifest,
21
21
  } from "@/lib/live/live.schema";
22
22
  import { acceptedReasoning, createLiveCall } from "@/lib/live/live.server";
23
- import { createServerProbe } from "@/lib/probe.server";
24
23
  import { serverAction } from "@/lib/protocol/server-action";
25
24
  import { publicError } from "@/lib/public-error";
26
25
  import {
@@ -37,7 +36,6 @@ import {
37
36
  CallThoughtSchema,
38
37
  CallTurnSchema,
39
38
  type TextCallHandshake,
40
- ThursdaySettingsSchema,
41
39
  } from "./thursday.schema";
42
40
  import { openTextCall } from "./thursday.text";
43
41
 
@@ -81,7 +79,7 @@ export const openCallAction = serverAction(
81
79
  sdp: unknown,
82
80
  calledBack?: unknown,
83
81
  ): Promise<CallHandshake> => {
84
- const thursday = ThursdaySettingsSchema.parse(settings);
82
+ const thursday = LiveSettingsSchema.parse(settings);
85
83
  const offer = z.string().min(1).max(SDP_MAX_LENGTH).parse(sdp);
86
84
  const apiKey = await readConfig(LIVE_PROVIDER.apiKeyName);
87
85
  if (!apiKey) {
@@ -97,7 +95,6 @@ export const openCallAction = serverAction(
97
95
  await Promise.all([
98
96
  loadLivePrompt({
99
97
  voicePrompt: thursday.voicePrompt,
100
- locale: thursday.locale,
101
98
  calledBack: rang,
102
99
  remembers: last !== null,
103
100
  }),
@@ -134,17 +131,6 @@ export const openCallAction = serverAction(
134
131
  model: LIVE_MODEL,
135
132
  backendModel: thursday.backendModel,
136
133
  });
137
-
138
- createServerProbe("server")("call.open", {
139
- callId,
140
- calledBack,
141
- opening: voice.opening,
142
- standing,
143
- voiceChars: voice.text.length,
144
- backendChars: backend.length,
145
- webSearch: thursday.webSearch,
146
- exa: Boolean(exaKey),
147
- });
148
134
  return {
149
135
  callId,
150
136
  sdp: connection.transport.sdp,
@@ -162,12 +148,10 @@ export const openCallAction = serverAction(
162
148
  */
163
149
  export const openTextCallAction = serverAction(
164
150
  async (settings: unknown, runsOn?: unknown): Promise<TextCallHandshake> => {
165
- const opened = await openTextCall(
166
- ThursdaySettingsSchema.parse(settings),
151
+ return openTextCall(
152
+ LiveSettingsSchema.parse(settings),
167
153
  textModelRefSchema.nullish().parse(runsOn),
168
154
  );
169
- createServerProbe("server")("call.open.text", { callId: opened.callId });
170
- return opened;
171
155
  },
172
156
  );
173
157
 
@@ -1,6 +1,5 @@
1
1
  import z from "zod";
2
2
  import { ASCII_FACE } from "@/config";
3
- import { LiveSettingsSchema } from "@/features/ai/live.schema";
4
3
  import { TEXT_MODEL_PROVIDERS } from "@/features/ai/model.schema";
5
4
  import type { ThreadStatus } from "@/features/bot/bot.schema";
6
5
  import { ASCII_CHARSETS } from "@/features/thursday/face.const";
@@ -9,14 +8,6 @@ import { LiveFragmentSchema } from "@/lib/live/live.schema";
9
8
 
10
9
  // Shared by server and browser: nothing here may touch the DB.
11
10
 
12
- /** The payload openCallAction parses. Browser-only settings live in thursday.store. */
13
- export const ThursdaySettingsSchema = LiveSettingsSchema.extend({
14
- /** `navigator.language` (e.g. "ko-KR"). Only the first call's opening line uses it. */
15
- locale: z.string().max(35).nullish(),
16
- });
17
-
18
- export type ThursdaySettings = z.infer<typeof ThursdaySettingsSchema>;
19
-
20
11
  /**
21
12
  * A word on the face: one `emote` put there, or the screen's own while she rings. `at` tells a
22
13
  * second showing of the same word from the first; `hold` is how long it stays lit, in seconds,
@@ -2,7 +2,12 @@
2
2
 
3
3
  import { create } from "zustand";
4
4
  import { persist } from "zustand/middleware";
5
- import { LIVE_DEFAULTS, migrateLiveSettings } from "@/features/ai/live.schema";
5
+ import {
6
+ LIVE_DEFAULTS,
7
+ type LiveSettings,
8
+ LiveSettingsSchema,
9
+ migrateLiveSettings,
10
+ } from "@/features/ai/live.schema";
6
11
  import {
7
12
  type TextModelRef,
8
13
  textModelRefSchema,
@@ -16,18 +21,16 @@ import {
16
21
  HOTKEY_DEFAULT,
17
22
  type Hotkey,
18
23
  HotkeySchema,
19
- type ThursdaySettings,
20
- ThursdaySettingsSchema,
21
24
  WAKE_DEFAULT,
22
25
  type Wake,
23
26
  WakeSchema,
24
27
  } from "./thursday.schema";
25
28
 
26
- // Call settings persisted per browser. Only `ThursdaySettings` is sent to the
29
+ // Call settings persisted per browser. Only `LiveSettings` is sent to the
27
30
  // server (openCallAction); the rest never leaves the page.
28
31
 
29
32
  /** Everything this browser stores, sent or not. */
30
- const StoredSchema = ThursdaySettingsSchema.extend({
33
+ const StoredSchema = LiveSettingsSchema.extend({
31
34
  // `.catch` so settings stored before a field existed keep the other fields.
32
35
  wake: WakeSchema.catch(WAKE_DEFAULT),
33
36
  hotkey: HotkeySchema.catch(HOTKEY_DEFAULT),
@@ -36,7 +39,7 @@ const StoredSchema = ThursdaySettingsSchema.extend({
36
39
  textModel: textModelRefSchema.nullable().catch(null),
37
40
  });
38
41
 
39
- type Stored = ThursdaySettings & {
42
+ type Stored = LiveSettings & {
40
43
  wake: Wake;
41
44
  hotkey: Hotkey;
42
45
  callBack: CallBack;
@@ -91,12 +94,5 @@ export const useThursdayStore = create<ThursdayStore>()(
91
94
  );
92
95
 
93
96
  /** What openCallAction needs and nothing else. */
94
- export const thursdaySettings = (): ThursdaySettings => {
95
- // Not stored: a fact of this browser, read when the call opens.
96
- const locale =
97
- typeof navigator === "undefined" ? null : (navigator.language ?? null);
98
- return ThursdaySettingsSchema.parse({
99
- ...useThursdayStore.getState(),
100
- locale,
101
- });
102
- };
97
+ export const thursdaySettings = (): LiveSettings =>
98
+ LiveSettingsSchema.parse(useThursdayStore.getState());
@@ -13,7 +13,11 @@ import {
13
13
  } from "ai";
14
14
  import { ZodError, z } from "zod";
15
15
  import { TEXT_CALL } from "@/config";
16
- import { LIVE_PROVIDER } from "@/features/ai/live.schema";
16
+ import {
17
+ LIVE_PROVIDER,
18
+ type LiveSettings,
19
+ LiveSettingsSchema,
20
+ } from "@/features/ai/live.schema";
17
21
  import { loadTools } from "@/features/ai/load-tools";
18
22
  import { getTextModel, modelErrorToString } from "@/features/ai/model";
19
23
  import {
@@ -38,8 +42,6 @@ import {
38
42
  TEXT_CALL_PROVIDERS,
39
43
  type TextCallHandshake,
40
44
  type TextCallProvider,
41
- type ThursdaySettings,
42
- ThursdaySettingsSchema,
43
45
  textCallRunsOn,
44
46
  } from "./thursday.schema";
45
47
  import { toolLine } from "./tool-line";
@@ -70,7 +72,7 @@ export async function readTextCallProvider(): Promise<TextCallProvider | null> {
70
72
  * in, else the OpenAI key, on the call's own backend model.
71
73
  */
72
74
  async function runsOnOf(
73
- settings: ThursdaySettings,
75
+ settings: LiveSettings,
74
76
  picked: TextModelRef | null | undefined,
75
77
  ): Promise<TextModelRef> {
76
78
  if (picked) return picked;
@@ -81,7 +83,7 @@ async function runsOnOf(
81
83
 
82
84
  /** The row a call in writing is kept under, and what stood open as it began. */
83
85
  export async function openTextCall(
84
- settings: ThursdaySettings,
86
+ settings: LiveSettings,
85
87
  picked?: TextModelRef | null,
86
88
  ): Promise<TextCallHandshake> {
87
89
  const ref = await runsOnOf(settings, picked);
@@ -98,7 +100,7 @@ export async function openTextCall(
98
100
 
99
101
  const BodySchema = z.object({
100
102
  callId: z.string().min(1),
101
- settings: ThursdaySettingsSchema,
103
+ settings: LiveSettingsSchema,
102
104
  /** What stood open as the call opened (ai/prompts/call-standing), as the page was handed it. */
103
105
  standing: z.string().nullish(),
104
106
  /** The model picked on the write line; absent, the rule decides (runsOnOf). */
@@ -149,7 +151,7 @@ export async function streamTextCall(
149
151
  */
150
152
  export async function answerInWriting(input: {
151
153
  callId: string;
152
- settings: ThursdaySettings;
154
+ settings: LiveSettings;
153
155
  standing: string | null;
154
156
  messages: ModelMessage[];
155
157
  said: string | null;
@@ -246,7 +248,7 @@ const standingHead = (standing: string | null | undefined): ModelMessage[] =>
246
248
  /** What a turn runs on, whoever holds the conversation: the model, her prompt, her tools. */
247
249
  async function loadRun(
248
250
  callId: string,
249
- settings: ThursdaySettings,
251
+ settings: LiveSettings,
250
252
  picked?: TextModelRef | null,
251
253
  ) {
252
254
  const ref = await runsOnOf(settings, picked);