thursday-agent 0.10.0 → 0.12.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 (308) 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]__04wldw9._.js +1 -1
  68. package/.next/server/chunks/[root-of-the-server]__0542v_d._.js +3 -0
  69. package/.next/server/chunks/[root-of-the-server]__0cfszwn._.js +1 -1
  70. package/.next/server/chunks/[root-of-the-server]__0gewdg9._.js +2 -2
  71. package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
  72. package/.next/server/chunks/[root-of-the-server]__0npn_pn._.js +1 -1
  73. package/.next/server/chunks/[root-of-the-server]__0nr-v8s._.js +1 -1
  74. package/.next/server/chunks/[root-of-the-server]__0rqgb17._.js +1 -1
  75. package/.next/server/chunks/[root-of-the-server]__1-swlkw._.js +2 -2
  76. package/.next/server/chunks/[root-of-the-server]__12iddfq._.js +1 -1
  77. package/.next/server/chunks/[root-of-the-server]__1ai5lme._.js +3 -3
  78. package/.next/server/chunks/[root-of-the-server]__1cjxw0-._.js +2 -2
  79. package/.next/server/chunks/{[root-of-the-server]__1fy2g82._.js → [root-of-the-server]__1k3j52n._.js} +1 -1
  80. package/.next/server/chunks/[root-of-the-server]__1kyjt5m._.js +3 -3
  81. package/.next/server/chunks/[root-of-the-server]__1tg5got._.js +1 -1
  82. package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
  83. package/.next/server/chunks/_05v8k21._.js +3 -1
  84. package/.next/server/chunks/_0awtznl._.js +1 -1
  85. package/.next/server/chunks/_0f6ts7z._.js +1 -1
  86. package/.next/server/chunks/_0na2fj1._.js +1 -1
  87. package/.next/server/chunks/_0x-ej3f._.js +17 -17
  88. package/.next/server/chunks/_10tkr26._.js +1 -1
  89. package/.next/server/chunks/_12-amdu._.js +2 -2
  90. package/.next/server/chunks/_15740dw._.js +60 -0
  91. package/.next/server/chunks/_1dc_d1q._.js +3 -3
  92. package/.next/server/chunks/_1i350l5._.js +3 -1
  93. package/.next/server/chunks/_1jc5g4p._.js +2 -2
  94. package/.next/server/chunks/_1jn-aep._.js +4 -4
  95. package/.next/server/chunks/_1m88lkh._.js +11 -11
  96. package/.next/server/chunks/_1ogqovh._.js +4 -4
  97. package/.next/server/chunks/_1taa4bs._.js +1 -1
  98. package/.next/server/chunks/_1tpr507._.js +1 -1
  99. package/.next/server/chunks/_1uuucy-._.js +1 -1
  100. package/.next/server/chunks/_1zxp-p6._.js +1 -1
  101. package/.next/server/chunks/features_0rfvely._.js +5 -3
  102. package/.next/server/chunks/ssr/[root-of-the-server]__0a967km._.js +3 -0
  103. package/.next/server/chunks/ssr/{[root-of-the-server]__1se25x0._.js → [root-of-the-server]__0hpp3nw._.js} +29 -13
  104. package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +24 -60
  105. package/.next/server/chunks/ssr/{[root-of-the-server]__0tvavy9._.js → [root-of-the-server]__1iwyp1e._.js} +1 -1
  106. package/.next/server/chunks/ssr/[root-of-the-server]__1r77-88._.js +3 -0
  107. package/.next/server/chunks/ssr/[root-of-the-server]__1w9hqq8._.js +1 -1
  108. package/.next/server/chunks/ssr/_026qnc9._.js +1 -1
  109. package/.next/server/chunks/ssr/{_173siic._.js → _02c7o30._.js} +2 -2
  110. package/.next/server/chunks/ssr/_097-ljd._.js +1 -1
  111. package/.next/server/chunks/ssr/_0euzzf8._.js +3 -0
  112. package/.next/server/chunks/ssr/{_1u2jw0i._.js → _0fbi6om._.js} +3 -3
  113. package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
  114. package/.next/server/chunks/ssr/_0igfcax._.js +20 -0
  115. package/.next/server/chunks/ssr/_0ld9_qq._.js +1 -1
  116. package/.next/server/chunks/ssr/_0nvh1h9._.js +3 -0
  117. package/.next/server/chunks/ssr/_0qmpw35._.js +3 -0
  118. package/.next/server/chunks/ssr/{_1yk6yak._.js → _0rmaf32._.js} +2 -2
  119. package/.next/server/chunks/ssr/_0uv-qs4._.js +3 -0
  120. package/.next/server/chunks/ssr/{_1doidrw._.js → _10f-jib._.js} +2 -2
  121. package/.next/server/chunks/ssr/{_05rxyf9._.js → _10mo72m._.js} +2 -2
  122. package/.next/server/chunks/ssr/_12mijol._.js +11 -11
  123. package/.next/server/chunks/ssr/_132qkfz._.js +1 -1
  124. package/.next/server/chunks/ssr/_15zqt-t._.js +1 -1
  125. package/.next/server/chunks/ssr/_16gfxpn._.js +3 -0
  126. package/.next/server/chunks/ssr/_184327j._.js +1 -1
  127. package/.next/server/chunks/ssr/{_1ng831c._.js → _18ujhhq._.js} +2 -2
  128. package/.next/server/chunks/ssr/_1d423aj._.js +7 -5
  129. package/.next/server/chunks/ssr/_1lx6w_o._.js +67 -0
  130. package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
  131. package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
  132. package/.next/server/chunks/ssr/{_19n08g5._.js → _1ntpny5._.js} +2 -2
  133. package/.next/server/chunks/ssr/{_0bp-_fi._.js → _1o1g4uk._.js} +3 -3
  134. package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
  135. package/.next/server/chunks/ssr/_1yh5bj5._.js +1 -1
  136. package/.next/server/chunks/ssr/{_0tg0vf8._.js → _2008vmi._.js} +8 -8
  137. package/.next/server/chunks/ssr/{features_bot_1yr-_pm._.js → features_bot_1n1s7bh._.js} +2 -2
  138. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  139. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
  140. package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
  141. package/.next/server/chunks/ssr/features_workspace_00jz2hu._.js +1 -1
  142. package/.next/server/chunks/ssr/{node_modules_1n0h9sf._.js → node_modules_0-x0zxm._.js} +1 -1
  143. package/.next/server/chunks/ssr/{node_modules_1xzo2wn._.js → node_modules_00nrzz_._.js} +1 -1
  144. package/.next/server/chunks/ssr/{node_modules_0511ha-._.js → node_modules_01ed0yi._.js} +1 -1
  145. package/.next/server/chunks/ssr/{node_modules_18woe20._.js → node_modules_01u4ea3._.js} +1 -1
  146. package/.next/server/chunks/ssr/{node_modules_1expacw._.js → node_modules_05b6e99._.js} +1 -1
  147. package/.next/server/chunks/ssr/{node_modules_0eb4rx7._.js → node_modules_07jp6tj._.js} +1 -1
  148. package/.next/server/chunks/ssr/{node_modules_0piws2a._.js → node_modules_0azd56x._.js} +1 -1
  149. package/.next/server/chunks/ssr/{node_modules_0f2k5l0._.js → node_modules_0b0iy01._.js} +1 -1
  150. package/.next/server/chunks/ssr/{node_modules_0wq4-ts._.js → node_modules_0fpnetd._.js} +1 -1
  151. package/.next/server/chunks/ssr/{node_modules_11hbkuj._.js → node_modules_0ky76fz._.js} +1 -1
  152. package/.next/server/chunks/ssr/{node_modules_048wmmj._.js → node_modules_0lautv3._.js} +1 -1
  153. package/.next/server/chunks/ssr/{node_modules_1dcbvfw._.js → node_modules_0m3_5ge._.js} +1 -1
  154. package/.next/server/chunks/ssr/{node_modules_0pk6i-e._.js → node_modules_0operbz._.js} +1 -1
  155. package/.next/server/chunks/ssr/{node_modules_1zkpek5._.js → node_modules_0ou0all._.js} +1 -1
  156. package/.next/server/chunks/ssr/{node_modules_04t200t._.js → node_modules_0ucridu._.js} +1 -1
  157. package/.next/server/chunks/ssr/{node_modules_08g12f-._.js → node_modules_0v_-uht._.js} +1 -1
  158. package/.next/server/chunks/ssr/{node_modules_1m2ho33._.js → node_modules_0yzmxfq._.js} +1 -1
  159. package/.next/server/chunks/ssr/{node_modules_1ksh9gy._.js → node_modules_19b0bir._.js} +1 -1
  160. package/.next/server/chunks/ssr/{node_modules_1qehmwm._.js → node_modules_1c2ikr9._.js} +1 -1
  161. package/.next/server/chunks/ssr/{node_modules_1_afpz7._.js → node_modules_1hv-j1l._.js} +1 -1
  162. package/.next/server/chunks/ssr/{node_modules_0cdu6jq._.js → node_modules_1iau_ea._.js} +1 -1
  163. package/.next/server/chunks/ssr/{node_modules_1zf46lp._.js → node_modules_1ma_y68._.js} +1 -1
  164. package/.next/server/chunks/ssr/{node_modules_0s1lwc9._.js → node_modules_1omg9xr._.js} +1 -1
  165. package/.next/server/chunks/ssr/{node_modules_1cxc8kn._.js → node_modules_1wgnj8q._.js} +1 -1
  166. package/.next/server/chunks/ssr/{node_modules_0zjqwz7._.js → node_modules_1wvf3oo._.js} +1 -1
  167. package/.next/server/chunks/ssr/{node_modules_1_ban4u._.js → node_modules_1z3sh11._.js} +1 -1
  168. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0avl8-1._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0tmjn9w._.js} +1 -1
  169. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_14oslro._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1_e8c3o._.js} +1 -1
  170. package/.next/server/middleware-build-manifest.js +3 -3
  171. package/.next/server/pages/404.html +1 -1
  172. package/.next/server/pages/500.html +1 -1
  173. package/.next/server/server-reference-manifest.js +1 -1
  174. package/.next/server/server-reference-manifest.json +57 -57
  175. package/.next/static/chunks/09pkygbht7ki8.js +18 -0
  176. package/.next/static/chunks/0bm-nhblcmmj4.js +17 -0
  177. package/.next/static/chunks/{22shnca_6zfzs.js → 0hhza87hkbqaz.js} +1 -1
  178. package/.next/static/chunks/{19ojn4lomsk0r.js → 0mscosx0eh3ee.js} +1 -1
  179. package/.next/static/chunks/{3vynnr71ghvom.js → 0p50d-au6-3hx.js} +2 -2
  180. package/.next/static/chunks/{42890jqk049q1.js → 0p9jcyqkpq71f.js} +1 -1
  181. package/.next/static/chunks/{35kwn237o4wdn.js → 0qgn3u86jkbm7.js} +1 -1
  182. package/.next/static/chunks/{2d4z3er53ng8r.js → 0samf80yu2b17.js} +1 -1
  183. package/.next/static/chunks/{1pjygp7hqib6j.js → 1czfknd4luo6t.js} +1 -1
  184. package/.next/static/chunks/{09120tms63y21.js → 1jz8n0mhtdzr8.js} +1 -1
  185. package/.next/static/chunks/206laxm6maiz2.js +1 -0
  186. package/.next/static/chunks/{24dst4mgurbsh.js → 2_c7mu6x1lu49.js} +1 -1
  187. package/.next/static/chunks/{2ky81z-jgvasc.js → 2h-tlhb2ffhkk.js} +1 -1
  188. package/.next/static/chunks/{0oz3rs47yy3p8.js → 2i5_w8olnrvo0.js} +2 -2
  189. package/.next/static/chunks/{1mwee7xvitlp9.js → 2m8iqwc6sykln.js} +1 -1
  190. package/.next/static/chunks/{0o980477kfqez.js → 2r6xnyg7ezyxr.js} +1 -1
  191. package/.next/static/chunks/33fyp-byw48ju.js +65 -0
  192. package/.next/static/chunks/{2ndw4w4u2x51r.js → 35yd_27d9ylra.js} +1 -1
  193. package/.next/static/chunks/{3exjsi8pfdvzj.js → 3ag1i3__yl94f.js} +1 -1
  194. package/.next/static/chunks/{3obf9n3cxj99k.js → 3jvldk2wvwbge.js} +1 -1
  195. package/.next/static/chunks/3m_ejzg-xn21r.css +1 -0
  196. package/.next/static/chunks/{0w20wnnfrscq7.js → 3s-jpd3ky49-n.js} +1 -1
  197. package/.next/static/chunks/40ij3wsn3vt7h.js +1 -0
  198. package/README.md +1 -1
  199. package/app/api/thursday/tool-call/route.ts +1 -15
  200. package/docs/images/demo-still.jpg +0 -0
  201. package/docs/live-calls.md +19 -11
  202. package/features/ai/components/model-picker.tsx +8 -2
  203. package/features/ai/model.schema.ts +26 -0
  204. package/features/ai/prompts/live.prompt.ts +51 -26
  205. package/features/ai/prompts/prompt-helper.ts +1 -9
  206. package/features/ai/prompts/thursday.prompt.ts +8 -2
  207. package/features/bot/bot.run.ts +9 -3
  208. package/features/bot/bot.seed.ts +35 -91
  209. package/features/bot/components/bot-room.tsx +3 -12
  210. package/features/bot/components/room-list.tsx +1 -1
  211. package/features/bot/components/room-pill.tsx +44 -55
  212. package/features/bot/seed-bots.ts +2 -2
  213. package/features/config/components/config-badge.tsx +6 -0
  214. package/features/config/components/config-setting.tsx +20 -1
  215. package/features/config/components/models-badge.tsx +5 -7
  216. package/features/config/components/voice-key.tsx +24 -1
  217. package/features/config/config.const.ts +7 -0
  218. package/features/intro/components/intro.tsx +4 -19
  219. package/features/reach/reach.ts +2 -2
  220. package/features/settings/components/setting-ui.tsx +14 -5
  221. package/features/settings/components/settings.tsx +3 -1
  222. package/features/settings/settings.alert.ts +12 -11
  223. package/features/signins/signins.query.ts +66 -1
  224. package/features/thursday/components/side-captions.tsx +31 -8
  225. package/features/thursday/components/tab-state.tsx +5 -2
  226. package/features/thursday/components/thursday.tsx +158 -68
  227. package/features/thursday/components/write-line.tsx +37 -17
  228. package/features/thursday/thursday.action.ts +4 -20
  229. package/features/thursday/thursday.schema.ts +0 -9
  230. package/features/thursday/thursday.store.ts +11 -15
  231. package/features/thursday/thursday.text.ts +10 -8
  232. package/features/thursday/tool-line.ts +17 -6
  233. package/features/thursday/use-call-ring.ts +1 -5
  234. package/features/thursday/use-text-call.ts +37 -21
  235. package/features/thursday/use-thursday.ts +14 -44
  236. package/features/workspace/workspace.ts +4 -2
  237. package/guide/bots.md +12 -2
  238. package/guide/calls.md +9 -6
  239. package/guide/setup.md +4 -1
  240. package/lib/live/live.session.ts +0 -39
  241. package/package.json +1 -1
  242. package/scripts/live-session.test.mts +19 -26
  243. package/seed-skills/analyst/README.md +3 -4
  244. package/seed-skills/analyst/data-report/SKILL.md +5 -4
  245. package/seed-skills/insta/instagram/SKILL.md +5 -2
  246. package/seed-skills/insta/instagram/references/post.md +2 -2
  247. package/seed-skills/insta/instagram/scripts/compose.mjs +5 -1
  248. package/seed-skills/insta/instagram/scripts/inbox.mjs +5 -1
  249. package/seed-skills/insta/instagram/scripts/lib.mjs +12 -70
  250. package/seed-skills/insta/instagram/scripts/media.mjs +1 -1
  251. package/seed-skills/insta/instagram/scripts/profile.mjs +6 -2
  252. package/seed-skills/insta/instagram/scripts/read.mjs +5 -1
  253. package/seed-skills/insta/instagram/scripts/search.mjs +6 -2
  254. package/seed-skills/insta/instagram/scripts/sheet.mjs +31 -88
  255. package/seed-skills/insta/instagram/templates/carousel.html +1 -1
  256. package/seed-skills/tutor/picture-book/SKILL.md +16 -0
  257. package/seed-skills/tutor/picture-book/book/book.css +181 -0
  258. package/seed-skills/tutor/picture-book/book/book.html +33 -0
  259. package/seed-skills/tutor/picture-book/book/book.js +60 -0
  260. package/seed-skills/tutor/picture-book/references/picture-book.md +104 -0
  261. package/seed-skills/tutor/picture-book/scripts/book-video.mjs +177 -0
  262. package/seed-skills/tutor/picture-book/scripts/book.mjs +98 -0
  263. package/skills/browser/SKILL.md +13 -4
  264. package/skills/browser/references/scripts.md +55 -0
  265. package/{seed-skills/insta/instagram → skills/browser}/scripts/render.mjs +1 -1
  266. package/skills/browser/scripts/session.mjs +105 -0
  267. package/skills/browser/scripts/sheet.mjs +97 -0
  268. package/{seed-skills/insta/instagram → skills/browser}/scripts/webimage.mjs +1 -1
  269. package/skills/interactive-page/SKILL.md +4 -3
  270. package/{seed-skills/analyst/data-report → skills/interactive-page}/scripts/chart.mjs +3 -3
  271. package/skills/interactive-page/scripts/page.mjs +1 -0
  272. package/.next/server/app/api/probe/route/app-paths-manifest.json +0 -3
  273. package/.next/server/app/api/probe/route/build-manifest.json +0 -12
  274. package/.next/server/app/api/probe/route/server-reference-manifest.json +0 -4
  275. package/.next/server/app/api/probe/route.js +0 -10
  276. package/.next/server/app/api/probe/route.js.map +0 -5
  277. package/.next/server/app/api/probe/route.js.nft.json +0 -1
  278. package/.next/server/app/api/probe/route_client-reference-manifest.js +0 -3
  279. package/.next/server/chunks/[root-of-the-server]__012s2o8._.js +0 -3
  280. package/.next/server/chunks/[root-of-the-server]__0z0hc1f._.js +0 -4
  281. package/.next/server/chunks/[root-of-the-server]__1v7-zke._.js +0 -5
  282. package/.next/server/chunks/_0whlpyi._.js +0 -4
  283. package/.next/server/chunks/_next-internal_server_app_api_probe_route_actions_0cqd30j.js +0 -3
  284. package/.next/server/chunks/ssr/[root-of-the-server]__0cxqzfc._.js +0 -103
  285. package/.next/server/chunks/ssr/[root-of-the-server]__1-d2tgh._.js +0 -3
  286. package/.next/server/chunks/ssr/_070nv1_._.js +0 -3
  287. package/.next/server/chunks/ssr/_0hlxij-._.js +0 -3
  288. package/.next/server/chunks/ssr/_0p9pmxe._.js +0 -3
  289. package/.next/server/chunks/ssr/_0s41irp._.js +0 -3
  290. package/.next/server/chunks/ssr/_157yfrk._.js +0 -3
  291. package/.next/server/chunks/ssr/_17v-33v._.js +0 -20
  292. package/.next/server/chunks/ssr/_1flsjeo._.js +0 -3
  293. package/.next/static/chunks/03kgfyrli_0gz.js +0 -1
  294. package/.next/static/chunks/1p764d75_0lhv.js +0 -18
  295. package/.next/static/chunks/25zq8gzhbdsr7.js +0 -1
  296. package/.next/static/chunks/2qod5tqlpz8b7.js +0 -17
  297. package/.next/static/chunks/30skcjexa57zn.js +0 -101
  298. package/.next/static/chunks/3qq4o8w2rv_e9.css +0 -1
  299. package/lib/probe.server.ts +0 -17
  300. package/lib/probe.ts +0 -55
  301. package/seed-skills/mail/mail/SKILL.md +0 -37
  302. package/seed-skills/mail/mail/recipes/gmail.mjs +0 -448
  303. package/seed-skills/mail/mail/references/recipes.md +0 -55
  304. package/seed-skills/mail/mail/scripts/mail.mjs +0 -202
  305. /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → QSe0rTdXU5kh5BBzr2XBB}/_buildManifest.js +0 -0
  306. /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → QSe0rTdXU5kh5BBzr2XBB}/_clientMiddlewareManifest.js +0 -0
  307. /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → QSe0rTdXU5kh5BBzr2XBB}/_ssgManifest.js +0 -0
  308. /package/{seed-skills/insta/instagram → skills/browser}/scripts/image-size.mjs +0 -0
@@ -1,12 +1,13 @@
1
1
  "use client";
2
2
 
3
- import { Check, X } from "lucide-react";
3
+ import { ArrowUpRight, Check, X } from "lucide-react";
4
4
  import { type Ref, useImperativeHandle, useState } from "react";
5
5
  import { queryKey } from "@/app/api/query-key";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import { Input } from "@/components/ui/input";
8
8
  import { ProviderIcon } from "@/features/ai/components/provider-icon";
9
9
  import { LIVE_PROVIDER } from "@/features/ai/live.schema";
10
+ import { TEXT_MODEL_PROVIDERS } from "@/features/ai/model.schema";
10
11
  import { useServerAction } from "@/lib/protocol/use-server-action";
11
12
  import { revalidate, useServerRoute } from "@/lib/protocol/use-server-route";
12
13
  import { cn, WAITING_INK } from "@/lib/utils";
@@ -27,6 +28,28 @@ export type VoiceKeysHandle = {
27
28
  /** Shorter than this is not a key: no save button, no prompt on leaving. */
28
29
  export const KEY_MIN = 8;
29
30
 
31
+ /** Where the voice key is made, beside its field: a pill that opens the page, and the page's address. */
32
+ export function GetKeyLink() {
33
+ const at = TEXT_MODEL_PROVIDERS[LIVE_PROVIDER.id].keysAt;
34
+ if (!at) return null;
35
+ return (
36
+ <a
37
+ href={at}
38
+ target="_blank"
39
+ rel="noreferrer"
40
+ className="flex items-center gap-2.5 text-[13px] font-medium text-foreground no-underline"
41
+ >
42
+ <span className="flex h-7.5 shrink-0 items-center gap-1.5 rounded-full bg-muted px-3.5 whitespace-nowrap transition-colors hover:bg-accent">
43
+ Get a key
44
+ <ArrowUpRight className="size-3.5" />
45
+ </span>
46
+ <span className="truncate font-mono text-[11px] font-normal text-muted-foreground/70">
47
+ {at.replace(/^https:\/\//, "")}
48
+ </span>
49
+ </a>
50
+ );
51
+ }
52
+
30
53
  /** The OpenAI key for Live voice and Responses delegation. Used by the intro and by the call screen while no key is set. */
31
54
  export function VoiceKeys({
32
55
  ref,
@@ -45,6 +45,10 @@ export type ConfigEntry = {
45
45
  recommended?: true;
46
46
  /** The service's own site, for a key that is no model provider's: its icon is the row's mark. */
47
47
  site?: string;
48
+ /** Where the key is made: the link its dialog offers. */
49
+ keysAt?: string;
50
+ /** How the key begins, shown in its empty field in place of the setting's name. */
51
+ keyLooks?: string;
48
52
  };
49
53
 
50
54
  /**
@@ -179,6 +183,8 @@ const keyEntry = (
179
183
  label: provider.label,
180
184
  provider: provider.id,
181
185
  signIn: provider.signIn,
186
+ keysAt: provider.keysAt,
187
+ keyLooks: provider.keyLooks,
182
188
  });
183
189
 
184
190
  export const CONFIG_GROUPS: ConfigGroup[] = [
@@ -227,6 +233,7 @@ export const CONFIG_GROUPS: ConfigGroup[] = [
227
233
  label: "Exa",
228
234
  hint: "web search in one call — dashboard.exa.ai",
229
235
  site: "exa.ai",
236
+ keysAt: "https://dashboard.exa.ai/api-keys",
230
237
  },
231
238
  ],
232
239
  },
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
 
3
3
  import {
4
- ArrowUpRight,
5
4
  Check,
6
5
  ChevronLeft,
7
6
  ChevronRight,
@@ -31,7 +30,7 @@ import { BOT_SEEDS, type BotSeed } from "@/features/bot/bot.seed";
31
30
  import { BotMark } from "@/features/bot/components/bot-mark";
32
31
  import { installSeedBots } from "@/features/bot/seed-bots";
33
32
  import { AccountsSetup } from "@/features/config/components/config-setting";
34
- import { VoiceKeys } from "@/features/config/components/voice-key";
33
+ import { GetKeyLink, VoiceKeys } from "@/features/config/components/voice-key";
35
34
  import {
36
35
  removeConfigAction,
37
36
  setConfigAction,
@@ -464,20 +463,7 @@ function KeyTurn({ keyed, onSaved }: { keyed: boolean; onSaved: () => void }) {
464
463
  <>
465
464
  <Mine>Paste your OpenAI API key</Mine>
466
465
  <VoiceKeys dense plain autoFocus onSaved={onSaved} />
467
- <a
468
- href="https://platform.openai.com/api-keys"
469
- target="_blank"
470
- rel="noreferrer"
471
- className="flex items-center gap-2.5 text-[13px] font-medium text-foreground no-underline"
472
- >
473
- <span className="flex h-7.5 shrink-0 items-center gap-1.5 rounded-full bg-muted px-3.5 whitespace-nowrap transition-colors hover:bg-accent">
474
- Get a key
475
- <ArrowUpRight className="size-3.5" />
476
- </span>
477
- <span className="truncate font-mono text-[11px] font-normal text-muted-foreground/70">
478
- platform.openai.com/api-keys
479
- </span>
480
- </a>
466
+ <GetKeyLink />
481
467
  <ol className="flex flex-col gap-1.75 text-[13px] text-muted-foreground">
482
468
  {[
483
469
  "Sign in, or make an account",
@@ -823,7 +809,6 @@ function useDemo(playing: boolean) {
823
809
 
824
810
  /** The pill and the corner where finished work lands, as they stand during the loop. */
825
811
  function DemoCorners({ stage, icons }: { stage: DemoStage; icons: BotIcon[] }) {
826
- const crew = BOT_SEEDS.filter((seed) => seed.recommended);
827
812
  return (
828
813
  <>
829
814
  {/* The card finished work really lands as, drawn here with nothing behind it */}
@@ -843,12 +828,12 @@ function DemoCorners({ stage, icons }: { stage: DemoStage; icons: BotIcon[] }) {
843
828
  )}
844
829
  <div className="absolute right-5 bottom-5 flex h-10 items-center gap-2.5 rounded-full bg-background pr-3.5 pl-2.5 ring-1 ring-border">
845
830
  <span className="flex">
846
- {crew.map((seed, index) => (
831
+ {BOT_SEEDS.map((seed, index) => (
847
832
  <BotMark
848
833
  key={seed.name}
849
834
  size={22}
850
835
  seed={seed.name}
851
- {...icons[BOT_SEEDS.indexOf(seed)]}
836
+ {...icons[index]}
852
837
  state={
853
838
  stage === "working" && seed.name === "Analyst"
854
839
  ? "thinking"
@@ -2,6 +2,7 @@ import { readFile, stat } from "node:fs/promises";
2
2
  import type { ModelMessage } from "ai";
3
3
  import { appEvents } from "@/app/api/events/app-event.server";
4
4
  import { REACH } from "@/config";
5
+ import { LiveSettingsSchema } from "@/features/ai/live.schema";
5
6
  import { modelErrorToString } from "@/features/ai/model";
6
7
  import { answerThread } from "@/features/bot/bot.runner";
7
8
  import type { Thread } from "@/features/bot/bot.schema";
@@ -14,7 +15,6 @@ import {
14
15
  } from "@/features/config/config.query";
15
16
  import { openWork } from "@/features/thursday/open-work";
16
17
  import { endCall, isCallOpen } from "@/features/thursday/thursday.query";
17
- import { ThursdaySettingsSchema } from "@/features/thursday/thursday.schema";
18
18
  import {
19
19
  answerInWriting,
20
20
  openTextCall,
@@ -353,7 +353,7 @@ async function answer(
353
353
  4_000,
354
354
  );
355
355
  try {
356
- const settings = ThursdaySettingsSchema.parse({});
356
+ const settings = LiveSettingsSchema.parse({});
357
357
  // Quiet for long enough, or closed under it (the tab went, the server restarted): the
358
358
  // next words open a new call, which reads the last one back under Earlier calls
359
359
  const kept = live.line;
@@ -187,14 +187,15 @@ export function SettingFilter({
187
187
 
188
188
  /**
189
189
  * What a nav row reports about its section. Amber counts what waits on you, red
190
- * marks what failed — the app's two colours, and the nav carries no others.
191
- * A count of 0 draws nothing, so a section at rest stays quiet.
190
+ * marks what failed — the app's two status colours — and a blue dot says a
191
+ * section is worth setting up though nothing waits on it. The nav carries no
192
+ * others. A count of 0 draws nothing, so a section at rest stays quiet.
192
193
  */
193
194
  export function NavBadge({
194
195
  tone,
195
196
  count,
196
197
  }: {
197
- tone: "amber" | "red";
198
+ tone: "amber" | "red" | "brand";
198
199
  /** Omitted draws a dot: the section has something wrong, not a number of things. */
199
200
  count?: number;
200
201
  }) {
@@ -204,7 +205,11 @@ export function NavBadge({
204
205
  <span
205
206
  className={cn(
206
207
  "size-1.5 shrink-0 rounded-full",
207
- tone === "red" ? "bg-destructive" : "bg-amber-600 dark:bg-amber-400",
208
+ tone === "red"
209
+ ? "bg-destructive"
210
+ : tone === "brand"
211
+ ? "bg-brand"
212
+ : "bg-amber-600 dark:bg-amber-400",
208
213
  )}
209
214
  />
210
215
  );
@@ -212,7 +217,11 @@ export function NavBadge({
212
217
  <span
213
218
  className={cn(
214
219
  "shrink-0 font-mono text-[11px]",
215
- tone === "red" ? "text-destructive" : WAITING_INK,
220
+ tone === "red"
221
+ ? "text-destructive"
222
+ : tone === "brand"
223
+ ? "text-brand"
224
+ : WAITING_INK,
216
225
  )}
217
226
  >
218
227
  {count}
@@ -31,6 +31,7 @@ import { Segmented } from "@/components/ui/segmented";
31
31
  import { BotBadge } from "@/features/bot/components/bot-badge";
32
32
  import { BotsMark } from "@/features/bot/components/bot-mark";
33
33
  import { ThreadBadge } from "@/features/bot/components/thread-badge";
34
+ import { ConfigBadge } from "@/features/config/components/config-badge";
34
35
  import { ModelsBadge } from "@/features/config/components/models-badge";
35
36
  import { McpBadge } from "@/features/connectors/components/mcp-badge";
36
37
  import { McpMark } from "@/features/connectors/components/mcp-mark";
@@ -269,6 +270,7 @@ export const SECTIONS: readonly {
269
270
  hint: "What bots think with, and what they draw, film and speak with",
270
271
  icon: Aperture,
271
272
  Component: ModelsSetting,
273
+ Badge: ModelsBadge,
272
274
  },
273
275
  {
274
276
  id: "keys",
@@ -277,7 +279,7 @@ export const SECTIONS: readonly {
277
279
  hint: "The accounts the app runs on",
278
280
  icon: KeyRound,
279
281
  Component: KeysSetting,
280
- Badge: ModelsBadge,
282
+ Badge: ConfigBadge,
281
283
  },
282
284
  {
283
285
  id: "phone",
@@ -8,17 +8,19 @@ import { useMcpAlert } from "@/features/connectors/components/mcp-badge";
8
8
  import type { SettingSectionId } from "./settings.store";
9
9
 
10
10
  /**
11
- * What a section is reporting, in the app's two colours: amber waits on the
12
- * user, red is broken. A section at rest reports nothing. The fact lives here
13
- * so both places that draw it stay in step — the nav row (which can afford a
14
- * count) and the call screen's corner (which cannot).
11
+ * What a section is reporting: amber waits on the user and red is broken — the
12
+ * app's two status colours — and brand blue is worth setting up though nothing
13
+ * waits on it (the user's pick). A section at rest reports nothing. The fact
14
+ * lives here so both places that draw it stay in step — the nav row (which can
15
+ * afford a count) and the call screen's corner (which cannot).
15
16
  */
16
- export type SectionAlert = "amber" | "red" | null;
17
+ export type SectionAlert = "amber" | "red" | "brand" | null;
17
18
 
18
- /** Broken outranks waiting; one of them is the whole ladder. */
19
+ /** Broken outranks waiting, and waiting outranks a suggestion; one of them is the whole ladder. */
19
20
  export function worstAlert(alerts: SectionAlert[]): SectionAlert {
20
21
  if (alerts.includes("red")) return "red";
21
- return alerts.includes("amber") ? "amber" : null;
22
+ if (alerts.includes("amber")) return "amber";
23
+ return alerts.includes("brand") ? "brand" : null;
22
24
  }
23
25
 
24
26
  /**
@@ -32,8 +34,7 @@ export function useSectionAlerts(): Partial<
32
34
  const bot = useBotAlert();
33
35
  const threads = useThreadAlert();
34
36
  const mcp = useMcpAlert();
35
- const config = useConfigAlert();
36
- const studio = useModelsAlert();
37
- // keys and models are one section: a missing voice key and an empty studio both report there
38
- return { bot, threads, mcp, models: worstAlert([config, studio]) };
37
+ const keys = useConfigAlert();
38
+ const models = useModelsAlert();
39
+ return { bot, threads, mcp, keys, models };
39
40
  }
@@ -2,7 +2,11 @@ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { appEvents } from "@/app/api/events/app-event.server";
4
4
  import { DATA_DIR, PATHS } from "@/config";
5
- import { WORKSPACE } from "@/features/workspace/workspace";
5
+ import {
6
+ jobShellEnv,
7
+ openWorkspace,
8
+ WORKSPACE,
9
+ } from "@/features/workspace/workspace";
6
10
  import { logger } from "@/lib/logger";
7
11
  import { type SignIn, siteOf } from "./signins.schema";
8
12
 
@@ -100,6 +104,67 @@ export async function borrowSignIn(
100
104
  return { kind: "state", signIn: record(used), state: kept.state };
101
105
  }
102
106
 
107
+ type Cookie = { name: string; domain: string; path: string; value: string };
108
+ type State = { cookies?: Cookie[] };
109
+
110
+ const cookieKey = (cookie: Cookie) =>
111
+ `${cookie.name}\n${cookie.domain}\n${cookie.path}`;
112
+
113
+ /**
114
+ * A site renews its session cookies while the session is used, and the copy kept here goes
115
+ * stale: lent again, it can be refused, or end the session it was copied from. So after a
116
+ * bot's turn its browser's cookies go back into every kept sign-in — only the ones a sign-in
117
+ * already holds (same name, domain and path), so a browser that signed out, or never signed
118
+ * in, changes nothing. `session` is the participant's (workspace botBrowserSession); one
119
+ * attached to the user's own Chrome is theirs and is not read.
120
+ */
121
+ export async function renewSignIns(session: string): Promise<void> {
122
+ const names = await readdir(VAULT).catch(() => [] as string[]);
123
+ if (!names.some((name) => name.endsWith(".json"))) return;
124
+
125
+ const sandbox = await openWorkspace();
126
+ const env = jobShellEnv(session);
127
+ const listed = await sandbox.exec("playwright-cli list --json", {
128
+ env,
129
+ timeoutMs: 15_000,
130
+ });
131
+ const { browsers } = JSON.parse(listed.stdout || "{}") as {
132
+ browsers?: { name: string; attached?: boolean }[];
133
+ };
134
+ const open = browsers?.find((b) => b.name === env.PLAYWRIGHT_CLI_SESSION);
135
+ if (!open || open.attached) return;
136
+
137
+ const path = `.playwright-cli/state-${crypto.randomUUID()}.json`;
138
+ let now: Map<string, Cookie>;
139
+ try {
140
+ const saved = await sandbox.exec(
141
+ `mkdir -p .playwright-cli && playwright-cli state-save ${path}`,
142
+ { env, timeoutMs: 30_000 },
143
+ );
144
+ if (saved.exitCode !== 0) return;
145
+ const state = JSON.parse(await sandbox.readFile(path, "utf-8")) as State;
146
+ now = new Map((state.cookies ?? []).map((c) => [cookieKey(c), c]));
147
+ } finally {
148
+ await sandbox.exec(`rm -f ${path}`);
149
+ }
150
+
151
+ for (const name of names) {
152
+ if (!name.endsWith(".json")) continue;
153
+ const kept = await read(decodeURIComponent(name.slice(0, -5)));
154
+ const state = kept?.state as State | undefined;
155
+ if (!kept || !state?.cookies) continue;
156
+ let renewed = false;
157
+ const cookies = state.cookies.map((cookie) => {
158
+ const fresh = now.get(cookieKey(cookie));
159
+ if (!fresh || JSON.stringify(fresh) === JSON.stringify(cookie))
160
+ return cookie;
161
+ renewed = true;
162
+ return fresh;
163
+ });
164
+ if (renewed) await write({ ...kept, state: { ...state, cookies } });
165
+ }
166
+ }
167
+
103
168
  /** The user's say on one bot: let in, or not any more. Only the screen calls this. */
104
169
  export async function setSignInBot(site: string, bot: string, on: boolean) {
105
170
  const kept = await read(site);
@@ -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",
@@ -50,7 +50,8 @@ function tabIconSvg(alert: SectionAlert): string {
50
50
  * The tab, for when the app is not the window in front. The title leads with
51
51
  * what is owed — the Threads badge's count — and a live call keeps its waveform at
52
52
  * the end, so neither takes the other's place. The icon wears the settings
53
- * corner's dot: amber waits on the user, red is broken, and a section with
53
+ * corner's dot when something is owed: amber waits on the user, red is broken
54
+ * (a blue suggestion stays in the corner), and a section with
54
55
  * nothing to count still gets the dot.
55
56
  *
56
57
  * Draws nothing. A component rather than a hook in the call screen, so a report
@@ -66,7 +67,9 @@ export function TabState({
66
67
  }) {
67
68
  const { owed } = useThreadReport();
68
69
  const alerts = useSectionAlerts();
69
- const alert = worstAlert(Object.values(alerts).map((each) => each ?? null));
70
+ const worst = worstAlert(Object.values(alerts).map((each) => each ?? null));
71
+ // A suggestion is for the settings corner, not the tab: from another window nothing is owed
72
+ const alert = worst === "brand" ? null : worst;
70
73
 
71
74
  useEffect(() => {
72
75
  const name = ringing ? `${APP_NAME} is calling` : APP_NAME;