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
@@ -172,13 +172,15 @@ const ON_A_THREAD = new Set<string>([
172
172
  * The bot a call's tool reaches, so its row wears that bot's face rather than a glyph —
173
173
  * who work went to is a face everywhere else in the app. The one work is handed to, else
174
174
  * the bot of the thread the tool names, found as the server finds a thread (thread.query
175
- * resolveThread): its id, else its label. An answer goes to the bot that asked; "all", and
176
- * a thread the page does not hold, reach nobody in particular.
175
+ * resolveThread): its id, else its label. An answer goes to the bot that asked; "all"
176
+ * reaches nobody in particular. A thread the page does not hold — stopped and read, or
177
+ * finished longer ago than the inbox keeps — is named by the tool's answer once it is back.
177
178
  */
178
179
  export function toolBot(
179
180
  name: string,
180
181
  args?: string,
181
182
  threads?: Thread[],
183
+ output?: string,
182
184
  ): string | null {
183
185
  const parsed = parseArgs(args);
184
186
  if (!parsed) return null;
@@ -186,19 +188,28 @@ export function toolBot(
186
188
  return typeof parsed.bot === "string" && parsed.bot.trim()
187
189
  ? parsed.bot.trim()
188
190
  : null;
189
- if (!ON_A_THREAD.has(name) || !threads) return null;
191
+ if (!ON_A_THREAD.has(name)) return null;
190
192
  const ref = typeof parsed.thread === "string" ? parsed.thread.trim() : "";
191
193
  if (!ref || ref.toLowerCase() === "all") return null;
192
194
  const lower = ref.toLowerCase();
193
195
  const thread =
194
- threads.find((one) => one.id === ref) ??
195
- threads.find((one) => one.label.toLowerCase() === lower);
196
- if (!thread) return null;
196
+ threads?.find((one) => one.id === ref) ??
197
+ threads?.find((one) => one.label.toLowerCase() === lower);
198
+ if (!thread) return answeredBy(output);
197
199
  return name === TOOL_NAMES.thread_answer
198
200
  ? (thread.room.questions[0]?.bot ?? thread.bot)
199
201
  : thread.bot;
200
202
  }
201
203
 
204
+ /** Whom a thread tool's answer says it reached (load-tools): `bot`, or the bot that took it as its answer. */
205
+ function answeredBy(output?: string): string | null {
206
+ const said = parseArgs(output);
207
+ if (!said) return null;
208
+ if (typeof said.bot === "string" && said.bot.trim()) return said.bot.trim();
209
+ const answered = said.answered as { bot?: unknown } | null | undefined;
210
+ return typeof answered?.bot === "string" ? answered.bot : null;
211
+ }
212
+
202
213
  /**
203
214
  * The label a thread was started under, or null for any other turn. It is
204
215
  * how a job is found again from the line that opened it — in her prompt and in
@@ -6,7 +6,6 @@ import type { Thread } from "@/features/bot/bot.schema";
6
6
  import { ringingThreads } from "@/features/bot/thread.store";
7
7
  import { toDate } from "@/lib/date-like";
8
8
  import { createRing } from "@/lib/live/ring";
9
- import { createProbe } from "@/lib/probe";
10
9
  import type { CallBack } from "./thursday.schema";
11
10
  import { useThursdayStore } from "./thursday.store";
12
11
 
@@ -51,7 +50,6 @@ export function useCallRing({
51
50
  /** A call in writing holds the screen (use-text-call): nothing rings meanwhile. */
52
51
  writing: boolean;
53
52
  }) {
54
- const probe = useRef(createProbe("page")).current;
55
53
  /**
56
54
  * The call-back rings only for threads that changed after this: the last call's
57
55
  * end, the last ring, or the page opening.
@@ -84,7 +82,6 @@ export function useCallRing({
84
82
  );
85
83
  if (!fresh.length) return;
86
84
 
87
- probe("ring", { threads: fresh.map((thread) => thread.id) });
88
85
  ringAfter.current = Date.now();
89
86
  // Work that is new to it rings again, even if it had rung out
90
87
  setRangOutAt(null);
@@ -92,7 +89,7 @@ export function useCallRing({
92
89
  ...ids,
93
90
  ...fresh.map((thread) => thread.id).filter((id) => !ids.includes(id)),
94
91
  ]);
95
- }, [threads, callBack, resting, writing, probe]);
92
+ }, [threads, callBack, resting, writing]);
96
93
 
97
94
  // Writing to her answers a ring as calling her does, and what came up while they wrote
98
95
  // was that call's to tell: nothing from before its end rings afterwards
@@ -110,7 +107,6 @@ export function useCallRing({
110
107
  * A thread added to a ring already going does not restart the clock.
111
108
  */
112
109
  const decline = useCallback(() => {
113
- probe("ring.declined");
114
110
  setRingingFor([]);
115
111
  setRangOutAt(null);
116
112
  }, []);
@@ -267,34 +267,50 @@ export function useTextCall(): TextCall {
267
267
  // pages a search read stay through her answer, until the next words are sent, as on a
268
268
  // spoken call
269
269
  const lastTool = [...parts].reverse().find(isToolUIPart);
270
- const used = useMemo((): ActivityLine | null => {
271
- if (!lastTool) return null;
272
- const name = getToolName(lastTool);
273
- // A provider's own search names what it looked for in its answer, not in what it was asked
274
- const asked = (lastTool.output as { action?: { query?: unknown } } | null)
275
- ?.action?.query;
276
- const args = JSON.stringify({
277
- ...(typeof asked === "string" ? { query: asked } : {}),
278
- ...(lastTool.input ?? {}),
279
- });
280
- const done =
281
- lastTool.state === "output-available" ||
282
- lastTool.state === "output-error";
283
- const sources =
270
+ // The part is copied with every piece of the stream, and its arguments arrive a few
271
+ // characters a piece: the line is a new object only when what it draws changes, or
272
+ // every piece redraws the call screen several times over
273
+ const id = lastTool?.toolCallId ?? null;
274
+ const name = lastTool ? getToolName(lastTool) : null;
275
+ const done =
276
+ lastTool?.state === "output-available" ||
277
+ lastTool?.state === "output-error";
278
+ const output = lastTool?.output;
279
+ const sources = useMemo(
280
+ () =>
284
281
  done && name === TOOL_NAMES.web_search
285
- ? searchSourcesOf(JSON.stringify(lastTool.output ?? null))
286
- : [];
287
- if (last?.type === "text" && !sources.length) return null;
288
- const bot = toolBot(name, args, inbox.current);
282
+ ? searchSourcesOf(JSON.stringify(output ?? null))
283
+ : [],
284
+ [done, name, output],
285
+ );
286
+ // A provider's own search names what it looked for in its answer, not in what it was asked
287
+ const asked = (output as { action?: { query?: unknown } } | null | undefined)
288
+ ?.action?.query;
289
+ const args = JSON.stringify({
290
+ ...(typeof asked === "string" ? { query: asked } : {}),
291
+ ...(lastTool?.input ?? {}),
292
+ });
293
+ const bot = name
294
+ ? toolBot(
295
+ name,
296
+ args,
297
+ inbox.current,
298
+ output === undefined ? undefined : JSON.stringify(output),
299
+ )
300
+ : null;
301
+ const drawn = name ? toolLine(name, args, bot) : null;
302
+ const answered = last?.type === "text";
303
+ const used = useMemo((): ActivityLine | null => {
304
+ if (!id || !name || (answered && !sources.length)) return null;
289
305
  return {
290
- id: lastTool.toolCallId,
306
+ id,
291
307
  name,
292
- line: toolLine(name, args, bot),
308
+ line: drawn,
293
309
  bot,
294
310
  done,
295
311
  ...(sources.length ? { sources } : {}),
296
312
  };
297
- }, [lastTool, last?.type]);
313
+ }, [id, name, drawn, bot, done, sources, answered]);
298
314
  const [lingered, setLingered] = useState<string | null>(null);
299
315
  const usedId = used?.done && !used.sources ? used.id : null;
300
316
  useEffect(() => {
@@ -4,13 +4,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import { useAppEvent } from "@/app/api/events/app-event.client";
5
5
  import { queryKey } from "@/app/api/query-key";
6
6
  import { toast } from "@/components/ui/toast";
7
- import {
8
- CALL_END,
9
- CALL_ENDED_MS,
10
- CALL_IDLE,
11
- CALL_NUDGE,
12
- CALL_RELAY,
13
- } from "@/config";
7
+ import { CALL_END, CALL_ENDED_MS, CALL_IDLE, CALL_RELAY } from "@/config";
14
8
  import { TOOL_NAMES } from "@/features/ai/tools/tool-name";
15
9
  import { acceptThreadRelaysAction } from "@/features/bot/bot.action";
16
10
  import type { Bot, Thread } from "@/features/bot/bot.schema";
@@ -32,7 +26,6 @@ import {
32
26
  createAudioTap,
33
27
  SPECTRUM_BANDS,
34
28
  } from "@/lib/live/live.tap";
35
- import { createProbe } from "@/lib/probe";
36
29
  import { type Result, unwrapResult } from "@/lib/protocol/result";
37
30
  import {
38
31
  revalidate,
@@ -216,8 +209,6 @@ export function useThursday(
216
209
  const onLine = useRef<string[]>([]);
217
210
  /** That update's relay rows, accepted once she has voiced it. */
218
211
  const onLineRows = useRef<number[]>([]);
219
- /** The experiment's timer (config CALL_NUDGE): a backend turn the page starts itself. */
220
- const nudge = useRef<ReturnType<typeof setTimeout> | null>(null);
221
212
  /** When her voice was last heard, for letting a goodbye finish (CALL_END). */
222
213
  const voiced = useRef(0);
223
214
  /** The hang-up waiting on her goodbye once the backend called end_call. */
@@ -267,12 +258,22 @@ export function useThursday(
267
258
  });
268
259
  }, []);
269
260
 
261
+ /** The tool's answer is back: a thread the page does not hold is named by it (toolBot). */
262
+ const nameTool = useCallback((call: LiveToolCall, output: string) => {
263
+ const bot = toolBot(call.name, call.arguments, latest.current, output);
264
+ if (!bot) return;
265
+ setTool((open) =>
266
+ open?.id === call.id && open.bot !== bot
267
+ ? { ...open, bot, line: toolLine(call.name, call.arguments, bot) }
268
+ : open,
269
+ );
270
+ }, []);
271
+
270
272
  /**
271
273
  * A relay takes the tool line and runs there like a tool does: while it goes in,
272
274
  * and while she voices it. Her voice finishing it is what stops the motion
273
275
  * (doneReading); after that it stays long enough to read.
274
276
  */
275
- const probe = useRef(createProbe("page")).current;
276
277
  const showRelay = useCallback((run: ActivityLine) => {
277
278
  if (linger.current) clearTimeout(linger.current);
278
279
  relayOpen.current = true;
@@ -295,7 +296,6 @@ export function useThursday(
295
296
  /** The relayed update was voiced, or waited on long enough: the next can go in. */
296
297
  const doneReading = useCallback(() => {
297
298
  if (reading.current.giveUp) clearTimeout(reading.current.giveUp);
298
- probe("read.done", { spoke: reading.current.spoke, keys: onLine.current });
299
299
  // Her voice started and stopped on it: carried, and only now are its rows accepted.
300
300
  // Live's acknowledgement says the text arrived, not that anyone heard it
301
301
  if (reading.current.spoke) {
@@ -419,13 +419,6 @@ export function useThursday(
419
419
  }
420
420
  // Finished work is good news before she says it (an item's key opens with what it is)
421
421
  if (first.key.startsWith("done:")) setFaceWord(finished());
422
- probe("relay.out", {
423
- kind: first.kind,
424
- keys: due.map((item) => item.key),
425
- waiting: open.length - due.length,
426
- quietMs: Date.now() - heard.current,
427
- rung,
428
- });
429
422
  readAloud();
430
423
  onLine.current = due.map((item) => item.key);
431
424
  onLineRows.current = due.flatMap((item) => item.relayIds);
@@ -439,7 +432,6 @@ export function useThursday(
439
432
  : `[${lines.length} updates.]\n\n${lines.join("\n\n")}`,
440
433
  )
441
434
  .then((delivered) => {
442
- probe("relay.acked", { delivered });
443
435
  // Refused: nothing to wait for her voice on
444
436
  if (!delivered) {
445
437
  onLineRows.current = [];
@@ -572,12 +564,6 @@ export function useThursday(
572
564
  async (why: CallEnd | null = null) => {
573
565
  if (ending.current) return;
574
566
  ending.current = true;
575
- probe("hangup", {
576
- why,
577
- call: callId.current,
578
- unvoiced: [...unvoiced.current],
579
- reading: reading.current.on,
580
- });
581
567
  if (leaving.current) clearInterval(leaving.current);
582
568
  leaving.current = null;
583
569
  attempt.current += 1;
@@ -665,7 +651,6 @@ export function useThursday(
665
651
  const leave = useCallback(() => {
666
652
  if (leaving.current) return;
667
653
  const asked = Date.now();
668
- probe("end_call", { sinceVoiceMs: asked - voiced.current });
669
654
  leaving.current = setInterval(() => {
670
655
  const now = Date.now();
671
656
  // A goodbye heard just before the backend ended the call counts as said
@@ -703,7 +688,6 @@ export function useThursday(
703
688
  const call = useCallback(async () => {
704
689
  // Every way in answers a ringing call-back: the face, the wake word, the hotkey
705
690
  const calledBack = ring.isRinging;
706
- probe("call", { calledBack, ringingFor: ring.ringingFor });
707
691
  // Guard with a ref, not `status`: three entry points (face, wake word,
708
692
  // hotkey) can fire in one frame and both see a stale "idle", opening two sessions
709
693
  if (opening.current || ending.current) return;
@@ -831,8 +815,6 @@ export function useThursday(
831
815
  if (word) setFaceWord({ text: word, at: Date.now() });
832
816
  return reply;
833
817
  }
834
- const began = Date.now();
835
- probe("tool.run", { name: call.name, id: call.id });
836
818
  showTool(call);
837
819
  // Exa's search, while its key is set (load-tools): its pages come back with
838
820
  // the answer and stay on the line the way the hosted search's do
@@ -846,6 +828,7 @@ export function useThursday(
846
828
  settings.webSearch,
847
829
  stop.signal,
848
830
  );
831
+ nameTool(call, output);
849
832
  if (searching)
850
833
  kept = keepSearch({
851
834
  id: call.id,
@@ -857,7 +840,6 @@ export function useThursday(
857
840
  });
858
841
  return output;
859
842
  } finally {
860
- probe("tool.done", { id: call.id, ms: Date.now() - began });
861
843
  if (!kept) hideTool(call.id);
862
844
  }
863
845
  },
@@ -946,19 +928,6 @@ export function useThursday(
946
928
  .slice(-KEEP_MESSAGES),
947
929
  );
948
930
  }
949
- // The experiment (config CALL_NUDGE): their words have settled, and if the voice
950
- // hands nothing over in a moment the page starts the backend turn itself
951
- if (CALL_NUDGE.on && turn.done && turn.role === "user" && words) {
952
- if (nudge.current) clearTimeout(nudge.current);
953
- const spoke = stirred.current;
954
- nudge.current = setTimeout(() => {
955
- nudge.current = null;
956
- // they went on talking, or the backend has the turn already
957
- if (stirred.current !== spoke || thinking.current !== null)
958
- return;
959
- probe("nudge", { sent: session.current?.nudge() ?? false });
960
- }, CALL_NUDGE.waitMs);
961
- }
962
931
  if (turn.done) {
963
932
  persist(saving, () =>
964
933
  saveTurnsAction(line.callId, [
@@ -1076,6 +1045,7 @@ export function useThursday(
1076
1045
  status,
1077
1046
  hangUp,
1078
1047
  showTool,
1048
+ nameTool,
1079
1049
  hideTool,
1080
1050
  showFace,
1081
1051
  wearFace,
@@ -207,11 +207,13 @@ export const jobShellEnv = (
207
207
  });
208
208
 
209
209
  /**
210
- * A bot's artifacts folder in its shell, for a script that delivers a file
211
- * rather than a model typing the path (skills/interactive-page scripts/page.mjs).
210
+ * What a bot's scripts find in its shell rather than a model typing the path.
212
211
  */
213
212
  export const botShellEnv = (bot: string): Record<string, string> => ({
213
+ // Where a script delivers a file (skills/interactive-page scripts/page.mjs)
214
214
  THURSDAY_ARTIFACTS: botArtifacts(bot),
215
+ // The shipped skills: a kit script in a bot's folder imports the shared ones from here
216
+ THURSDAY_SKILLS: join(APP_DIR, PATHS.skills.default),
215
217
  });
216
218
 
217
219
  /** A participant's browser belongs to the job and its canonical bot name, across every caller. */
package/guide/bots.md CHANGED
@@ -10,8 +10,11 @@ switched on; any of them can be switched off there, and added later from the sam
10
10
 
11
11
  Every bot has the same kit: a shell on this computer, a real browser of its own or the one
12
12
  the user is already signed into, their files, the web, whatever skills and connected services
13
- are installed, and the other bots. A ready-made bot may also arrive with written methods of
14
- its own for its subject, which no other bot sees.
13
+ are installed, and the other bots. A ready-made bot may also arrive with written methods and
14
+ scripts of its own for its subject, which no other bot sees: Insta studies accounts that do well
15
+ in a niche, builds posts the same way and reads messages without opening them (an opened thread
16
+ shows as seen); Analyst fetches published series and draws them; Marketer keeps one brief per
17
+ product in its memory and works from it.
15
18
 
16
19
  ## A job is a thread
17
20
 
@@ -75,6 +78,13 @@ of itself. Asking her on a call to show a result opens that file on the screen.
75
78
  stay under **Settings › Files**, one shelf per bot. Its second tab, **All files**, is the
76
79
  whole folder, for looking around rather than for the results.
77
80
 
81
+ Asked to explain something simply, **Tutor** makes a picture book: a picture and a line
82
+ or two a page, turned by swiping, the arrow keys, or tapping the right or left of the page. The
83
+ same book can be a PDF, or a video that reads itself aloud (landscape, or portrait when asked);
84
+ when the request does not say which, it asks first. It remembers what the user already
85
+ knows and how they liked being taught. The video needs a speech model in **Settings › Models**, and a drawn
86
+ picture an image model; without one it asks for it and stops.
87
+
78
88
  A bot keeps its own memory — what a job taught it, how the user asked it to work — in its own
79
89
  folder, listed on its page in Settings › Bots.
80
90
 
@@ -94,7 +104,7 @@ one switch. **When** has three kinds of time, and at most twelve routines exist:
94
104
  - **Every few hours**: 1, 2, 3, 6, 12 or 24 hours after each start; any other number can be
95
105
  asked of Thursday.
96
106
 
97
- What is picked is filled black, and the line under the field says it back in words, with when
107
+ What is picked is filled blue, and the line under the field says it back in words, with when
98
108
  it first starts.
99
109
 
100
110
  Each time it is due, a routine opens an ordinary thread, marked with a repeat sign, and that
package/guide/calls.md CHANGED
@@ -11,7 +11,8 @@ Three ways in, and any of them also answers a call she placed:
11
11
 
12
12
  She speaks first. From the second call on she is told, as the call opens, when the last one
13
13
  was and how it ended, so she can greet as someone who remembers it; a call she places herself
14
- opens on why she called instead.
14
+ opens on why she called instead. She speaks the language the user speaks and switches when they
15
+ do; the browser's language plays no part, so the very first greeting is in whatever she picks.
15
16
 
16
17
  The wake phrase and the shortcut are in **Settings › Thursday › Starting a call**, where
17
18
  either can be switched on or off, or rewritten. The first-run setup offers the wake phrase
@@ -42,10 +43,11 @@ in **Settings › Thursday**. With nothing to run on it says so and sends nothin
42
43
  **Settings › API keys**. **Esc** ends it — saying goodbye does not, since there is no
43
44
  line for her to drop — and so does starting a spoken call: tapping her face in the middle of
44
45
  one ends the written call and places a spoken one. When a turn breaks — a plan's limit, a
45
- refused key — her face says ERROR and the provider's own words show in red under the line;
46
- writing again tries again, and Esc still ends it. When an OpenAI key is set and was not what
47
- broke, a blue button sends the same words again on that key; it never switches by itself,
48
- since what a turn costs changes with what it runs on. It is kept with
46
+ refused key — her face says ERROR and the provider's own words show in red under the line,
47
+ over a blue **Send it again** and the way to **API keys**; writing again tries again too, and
48
+ Esc still ends it. When an OpenAI key is set and was not what broke, the button sends the same
49
+ words again on that key instead; it never switches by itself, since what a turn costs changes
50
+ with what it runs on. It is kept with
49
51
  the other calls, marked *in writing*, and she reads it back on the next call like any other.
50
52
  During a spoken call the line writes to bots only — but a file put down then (dropped on the
51
53
  window, pasted, or picked with the paperclip) is one she is told about as it lands: its chip
package/knip.json CHANGED
@@ -3,6 +3,7 @@
3
3
  "entry": ["scripts/intro-voice.mts", ".agents/skills/*/*.cjs"],
4
4
  "ignore": [
5
5
  "skills/**",
6
+ "seed-skills/**",
6
7
  "components/ui/**",
7
8
  ".agents/skills/!(verify|cleanup|checkup)/**"
8
9
  ],
@@ -1,6 +1,5 @@
1
1
  import { LIVE_CALL } from "@/config";
2
2
  import { logger } from "@/lib/logger";
3
- import { briefLiveEvent, createProbe } from "@/lib/probe";
4
3
  import { errorToString } from "@/lib/utils";
5
4
  import {
6
5
  LIVE_HOSTED_TOOLS,
@@ -9,8 +8,6 @@ import {
9
8
  } from "./live.schema";
10
9
  import { createWebRtcTransport } from "./live.transport";
11
10
 
12
- const probe = createProbe("live");
13
-
14
11
  /** A revisable display group, independent of audio playback and backend responses. */
15
12
  export type LiveTurn = {
16
13
  id: string;
@@ -313,12 +310,6 @@ export const createLiveSession = ({ initialize, audio, on }: LiveOptions) => {
313
310
  return;
314
311
  const [next] = appends;
315
312
  pendingAppend = crypto.randomUUID();
316
- probe("out", {
317
- type: `session.${next.kind}.append`,
318
- client: pendingAppend,
319
- head: next.chunks[0].slice(0, 80),
320
- chunksLeft: next.chunks.length,
321
- });
322
313
  transport.send({
323
314
  type: `session.${next.kind}.append`,
324
315
  event_id: pendingAppend,
@@ -405,7 +396,6 @@ export const createLiveSession = ({ initialize, audio, on }: LiveOptions) => {
405
396
  return;
406
397
  response.continued = true;
407
398
  await Promise.all(response.calls.values());
408
- probe("out", { type: "response.create", closing, closed });
409
399
  if (!closing && !closed) {
410
400
  continuedAt = performance.now();
411
401
  transport.send({
@@ -422,8 +412,6 @@ export const createLiveSession = ({ initialize, audio, on }: LiveOptions) => {
422
412
  events.add(event.event_id);
423
413
  }
424
414
  timeline = Math.max(timeline, event.offset_ms ?? 0, event.end_ms ?? 0);
425
- if (!event.type.endsWith(".delta") && !event.event?.type.endsWith(".delta"))
426
- probe("in", briefLiveEvent(event));
427
415
  switch (event.type) {
428
416
  case "session.started":
429
417
  started = true;
@@ -576,12 +564,6 @@ export const createLiveSession = ({ initialize, audio, on }: LiveOptions) => {
576
564
  .catch((cause) => `Error: ${errorToString(cause)}`)
577
565
  .then((output) => {
578
566
  tools.delete(item.call_id);
579
- probe("out", {
580
- type: "function_call_output",
581
- name: item.name,
582
- call: item.call_id,
583
- size: output.length,
584
- });
585
567
  if (!closed && !closing)
586
568
  transport.send({
587
569
  type: "response.item.create",
@@ -690,7 +672,6 @@ export const createLiveSession = ({ initialize, audio, on }: LiveOptions) => {
690
672
  cleanup();
691
673
  }, LIVE_CALL.closeMs);
692
674
  audio.element.muted = true;
693
- probe("out", { type: "session.close" });
694
675
  transport.send({ type: "session.close", event_id: crypto.randomUUID() });
695
676
  return closePromise;
696
677
  },
@@ -708,25 +689,5 @@ export const createLiveSession = ({ initialize, audio, on }: LiveOptions) => {
708
689
  appendNext();
709
690
  });
710
691
  },
711
- /**
712
- * Starts a backend turn the voice did not hand over: the same `response.create` that
713
- * continues delegated work, sent with nothing pending. Does nothing while the backend
714
- * already holds the turn, or once the call is closing. False when it was not sent.
715
- */
716
- nudge(): boolean {
717
- const busy =
718
- [...responses.values()].some((response) => !response.terminal) ||
719
- tools.size > 0 ||
720
- performance.now() - continuedAt < CONTINUE_GAP_MS;
721
- if (closed || closing || busy) return false;
722
- probe("out", { type: "response.create", nudge: true });
723
- continuedAt = performance.now();
724
- transport.send({
725
- type: "response.create",
726
- event_id: crypto.randomUUID(),
727
- });
728
- activity();
729
- return true;
730
- },
731
692
  };
732
693
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thursday-agent",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "A voice agent that runs on your own machine — talk to her, she hands the slow work to bots with a shell, a browser and skills.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -860,7 +860,7 @@ test("stored settings keep OpenAI choices and the shared instruction, drop a Gro
860
860
  assert.deepEqual(migrateLiveSettings(null), LIVE_DEFAULTS);
861
861
  });
862
862
 
863
- test("both call prompts open as one Thursday: the voice gets a two-line delegation policy and memory and no earlier calls, the backend carries work on in threads, and every call has an opening", async () => {
863
+ test("both call prompts open as one Thursday: the voice gets the guide's delegation policy and memory and no earlier calls, the backend carries work on in threads, and every call has an opening", async () => {
864
864
  let profileFacts = 200;
865
865
  let samFacts = 2;
866
866
  const botMock = mock.module("../features/bot/bot.query.ts", {
@@ -949,13 +949,12 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
949
949
  );
950
950
  const on = await loadLivePrompt({
951
951
  voicePrompt: "Use a calm voice.",
952
- locale: "ko-KR",
953
952
  });
954
953
  assert.match(on.text, /modeled on Friday, the AI in \*Iron Man\*/);
955
954
  assert.match(on.text, /Prefer brief replies/);
956
955
  assert.match(
957
956
  on.text,
958
- /\n\n## Always\n\nIMPORTANT — always follow this: [^\n]+\n\nBackchannel policy: Use moderate backchannels\. .*\n\nInterruption policy: Stop speaking when the user interrupts\. Listen to what they say\.\n\nDelegation policy:\nDelegate to the backend everything [^\n]+\nWhatever they tell you about themselves[^\n]+unless it is already written below\.\nAnswer from what the backend returns[^\n]+\n\n## What you know about them\n/,
957
+ /\n\n## Always\n\nIMPORTANT — always follow this: [^\n]+\n\nBackchannel policy: Use moderate backchannels\. .*\n\nInterruption policy: Stop speaking when the user interrupts\. Listen to what they say\.\n\nSpeak the language the user is speaking, [^\n]+\n\nDelegation policy:\nBackend tools:\n- Ending the call: [^\n]+\n(- [^\n]+\n){4}\nDelegate to the backend when:\n- The user wants the call to end\.\n(- [^\n]+\n)+\nDo not delegate to the backend when:\n(- [^\n]+\n)+\nDelegate before giving an answer that depends on backend work\. Do not guess the result while waiting\.\n\n## What you know about them\n/,
959
958
  );
960
959
  // Only the ending rule carries the stamp
961
960
  assert.equal(on.text.split("IMPORTANT").length, 2);
@@ -963,13 +962,10 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
963
962
  on.text,
964
963
  /When the user speaks at length, acknowledge now and then/,
965
964
  );
966
- // What the backend can do, and when to hand over, are no longer the voice's to read
967
- assert.equal(
968
- /Backend tools|Delegate to the backend when|What bots can reach for|- Web:/.test(
969
- on.text,
970
- ),
971
- false,
972
- );
965
+ // What the backend can do, never how: no skills, connected tools or bots by name
966
+ assert.equal(/What bots can reach for|- Web:/.test(on.text), false);
967
+ // Stopping her voice is hers, stopping a job the backend's
968
+ assert.match(on.text, /or only want you to stop talking/);
973
969
  assert.match(on.text, /- people\/sam — Their brother \(2\) "Sam"/);
974
970
  assert.match(on.text, /What is in these notes, the backend recalls\./);
975
971
  // The roster, threads and earlier calls are the backend's to read
@@ -979,32 +975,33 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
979
975
  false,
980
976
  );
981
977
  assert.equal("input" in on, false);
982
- assert.equal(
983
- /ko-KR|browser's setting|language they use/.test(on.text),
984
- false,
985
- );
986
978
  assert.equal(/memory_|generate_|load_skill|`/.test(on.text), false);
987
- // The one tool name the voice holds: the ending rule, where it makes ending a thing to do
979
+ // The one tool name the voice holds: the ending rule, where it makes ending a thing to do.
980
+ // It holds no tools, so the rule is to hand the turn over, not to use the tool
988
981
  assert.equal(on.text.split("end_call").length, 2);
989
982
  assert.match(
990
983
  on.text,
991
- /immediately, without thinking, use the end_call tool\./,
984
+ /answer yes in one word of their language and hand it to the backend at once\. Only the backend can end the line, with the end_call tool/,
992
985
  );
993
986
  assert.equal(on.text.endsWith("Use a calm voice."), true);
994
987
  assert.match(on.opening, /The call has just started/);
995
988
 
996
- // One Thursday: the backend opens with the voice's own words and is never told it is a part
989
+ // One Thursday: the backend opens with the voice's own identity and is never told it is a
990
+ // part; its ending rule is its own, since only it can end the line
997
991
  const backend = await loadThursdayPrompt(null);
998
992
  const withoutClock = (text: string) =>
999
993
  text.replace(/\*\*Now\*\*: [^\n]+/, "");
1000
994
  const identity = on.text.slice(0, on.text.indexOf("\n\n## Always"));
1001
- const ending = /IMPORTANT — always follow this: [^\n]+/.exec(on.text)?.[0];
1002
995
  assert.equal(
1003
996
  withoutClock(backend).startsWith(
1004
- `${withoutClock(identity)}\n\n${ending}\n\n`,
997
+ `${withoutClock(identity)}\n\nIMPORTANT — always follow this: `,
1005
998
  ),
1006
999
  true,
1007
1000
  );
1001
+ assert.match(
1002
+ backend,
1003
+ /forget every other task, answer yes in one word of their language, then immediately, without thinking, use the end_call tool\./,
1004
+ );
1008
1005
  assert.match(
1009
1006
  backend,
1010
1007
  /\*\*Keep memory clean as you write\.\*\* A fact that repeats, narrows or changes one already in the note replaces it/,
@@ -1045,7 +1042,7 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
1045
1042
 
1046
1043
  // Tidying memory is the backend's: the voice neither reads it nor opens with it
1047
1044
  samFacts = 60;
1048
- const heavy = await loadLivePrompt({ locale: null });
1045
+ const heavy = await loadLivePrompt({});
1049
1046
  assert.equal(/tidying|grown past/.test(heavy.text), false);
1050
1047
  assert.match(heavy.opening, /The call has just started/);
1051
1048
  assert.match(
@@ -1055,19 +1052,15 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
1055
1052
  samFacts = 2;
1056
1053
 
1057
1054
  profileFacts = 0;
1058
- const first = await loadLivePrompt({ locale: "ko-KR" });
1055
+ const first = await loadLivePrompt({});
1059
1056
  assert.match(first.text, /## First call/);
1060
1057
  // The one call that opens with nothing: she says who she is, then learns who they are
1061
1058
  assert.match(first.opening ?? "", /say who you are/);
1062
1059
  assert.match(first.opening ?? "", /ask what to call them/);
1063
- assert.match(first.opening ?? "", /ko-KR/);
1064
1060
  assert.match(first.text, /what they do, how old they are, where they live/);
1065
1061
 
1066
1062
  // A call the page placed says so, ahead of even the first-call opening
1067
- const rung = await loadLivePrompt({
1068
- locale: "ko-KR",
1069
- calledBack: true,
1070
- });
1063
+ const rung = await loadLivePrompt({ calledBack: true });
1071
1064
  assert.match(rung.opening, /You placed this call/);
1072
1065
  assert.match(rung.opening, /say that is why you called/);
1073
1066
  } finally {
@@ -0,0 +1,18 @@
1
+ # Analyst's kit
2
+
3
+ One skill the Analyst seed is born with, copied into `.agents/skills/` in its own folder when it is
4
+ created. It is listed to that bot alone.
5
+
6
+ | Skill | What it adds |
7
+ |---|---|
8
+ | `data-report` | Seven report forms, each with its structure and a filled example; `fetch.mjs`, which pulls a published series (FRED, World Bank, Yahoo Finance, ECB rates, SEC filings, Wikipedia pageviews) into a CSV that names its source |
9
+
10
+ Written for this app. The forms borrow their shape from answer-first business writing and from the
11
+ report outlines in [anthropics/financial-services](https://github.com/anthropics/financial-services)
12
+ and [anthropics/knowledge-work-plugins](https://github.com/anthropics/knowledge-work-plugins)
13
+ (Apache-2.0); no text is copied from either. Those skills write Word, PowerPoint and Excel files and
14
+ read paid data connectors, which is why they are not taken whole.
15
+
16
+ Charts are drawn by the shipped `interactive-page` skill's `chart.mjs` into its quick page. The
17
+ scripts need Node and no packages. Every source they call answers without a key; `references/sources.md` names the
18
+ ones that need one.