Compare commits

..

2920 Commits

Author SHA1 Message Date
lobehubbot 090fd1da2a 🤖 chore: Lighthouse Results | Chat 2026-06-14 01:15:26 +00:00
lobehubbot 93e27eb525 🤖 chore: Lighthouse Results | Discover 2026-06-14 01:15:22 +00:00
lobehubbot 3717eac7bb 🤖 chore: Lighthouse Results | Discover 2026-06-13 01:13:38 +00:00
lobehubbot e617f0c2cb 🤖 chore: Lighthouse Results | Chat 2026-06-13 01:13:30 +00:00
lobehubbot bdd375270d 🤖 chore: Lighthouse Results | Discover 2026-06-12 01:15:47 +00:00
lobehubbot 10847605a6 🤖 chore: Lighthouse Results | Chat 2026-06-12 01:15:25 +00:00
lobehubbot 25a4ecfd03 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-06-11 01:12:01 +00:00
lobehubbot 3221ec05fc 🤖 chore: Lighthouse Results | Chat 2026-06-11 01:11:59 +00:00
lobehubbot 3f72d9dcc2 🤖 chore: Lighthouse Results | Discover 2026-06-11 01:11:58 +00:00
lobehubbot 8d6df2fdc6 🤖 chore: Lighthouse Results | Discover 2026-06-10 01:11:13 +00:00
lobehubbot 6ef21630c3 🤖 chore: Lighthouse Results | Chat 2026-06-10 01:10:41 +00:00
lobehubbot 5096360762 🤖 chore: Lighthouse Results | Chat 2026-06-09 01:05:02 +00:00
lobehubbot 44c1ed360d 🤖 chore: Lighthouse Results | Discover 2026-06-09 01:04:58 +00:00
lobehubbot fa7bb87aa1 🤖 chore: Lighthouse Results | Discover 2026-06-08 01:13:22 +00:00
lobehubbot ffa3cb723a 🤖 chore: Lighthouse Results | Chat 2026-06-08 01:13:20 +00:00
lobehubbot 899e7beed9 🤖 chore: Lighthouse Results | Chat 2026-06-07 01:13:29 +00:00
lobehubbot 180c472e54 🤖 chore: Lighthouse Results | Discover 2026-06-07 01:13:26 +00:00
lobehubbot 3e7a39f0e4 🤖 chore: Lighthouse Results | Chat 2026-06-06 01:05:47 +00:00
lobehubbot 73381fdb0a 🤖 chore: Lighthouse Results | Discover 2026-06-06 01:05:36 +00:00
lobehubbot d4f9d305eb 🤖 chore: Lighthouse Results | Chat 2026-06-05 01:10:03 +00:00
lobehubbot 4eef356a83 🤖 chore: Lighthouse Results | Discover 2026-06-05 01:10:00 +00:00
lobehubbot 547448d190 🤖 chore: Lighthouse Results | Chat 2026-06-04 01:19:23 +00:00
lobehubbot d6903f1747 🤖 chore: Lighthouse Results | Discover 2026-06-04 01:19:21 +00:00
lobehubbot 24ca422d56 🤖 chore: Lighthouse Results | Chat 2026-06-03 01:18:49 +00:00
lobehubbot 33d0e5365c 🤖 chore: Lighthouse Results | Discover 2026-06-03 01:18:45 +00:00
lobehubbot 44d180bf0e 🤖 chore: Lighthouse Results | Discover 2026-06-02 01:13:59 +00:00
lobehubbot a0835d095c 🤖 chore: Lighthouse Results | Chat 2026-06-02 01:13:02 +00:00
lobehubbot e6e689bfba 🤖 chore: Lighthouse Results | Discover 2026-06-01 01:14:49 +00:00
lobehubbot cbb7ac09a0 🤖 chore: Lighthouse Results | Chat 2026-06-01 01:14:45 +00:00
lobehubbot 919eefde16 🤖 chore: Lighthouse Results | Discover 2026-05-31 01:11:20 +00:00
lobehubbot 6a919da365 🤖 chore: Lighthouse Results | Chat 2026-05-31 01:11:12 +00:00
lobehubbot 3dd78df7d7 🤖 chore: Lighthouse Results | Chat 2026-05-30 01:04:43 +00:00
lobehubbot 01e4a3120b 🤖 chore: Lighthouse Results | Discover 2026-05-30 01:04:20 +00:00
lobehubbot 2c78ad3261 🤖 chore: Lighthouse Results | Chat 2026-05-29 01:09:36 +00:00
lobehubbot a8dd5a70db 🤖 chore: Lighthouse Results | Discover 2026-05-29 01:09:33 +00:00
lobehubbot db022d1307 🤖 chore: Lighthouse Results | Chat 2026-05-28 01:00:08 +00:00
lobehubbot ec0d0faac9 🤖 chore: Lighthouse Results | Discover 2026-05-28 01:00:03 +00:00
lobehubbot 1caae3fa7b 🤖 chore: Lighthouse Results | Chat 2026-05-27 01:07:01 +00:00
lobehubbot fbc4642e8a 🤖 chore: Lighthouse Results | Discover 2026-05-27 01:06:55 +00:00
lobehubbot 464674d9be 🤖 chore: Lighthouse Results | Chat 2026-05-26 01:03:38 +00:00
lobehubbot 9fa6111eab 🤖 chore: Lighthouse Results | Discover 2026-05-26 01:03:36 +00:00
lobehubbot 65d2e60a3e 🤖 chore: Lighthouse Results | Chat 2026-05-25 01:08:15 +00:00
lobehubbot a0e3f8d865 🤖 chore: Lighthouse Results | Discover 2026-05-25 01:08:09 +00:00
lobehubbot becd2ecfd2 🤖 chore: Lighthouse Results | Chat 2026-05-24 01:06:55 +00:00
lobehubbot 4b2376dca9 🤖 chore: Lighthouse Results | Discover 2026-05-24 01:06:49 +00:00
lobehubbot 52abf500cf 🤖 chore: Lighthouse Results | Chat 2026-05-23 01:02:11 +00:00
lobehubbot 8bb80dddcc 🤖 chore: Lighthouse Results | Discover 2026-05-23 01:02:06 +00:00
lobehubbot c1b4ba0dea 🤖 chore: Lighthouse Results | Discover 2026-05-22 01:05:42 +00:00
lobehubbot 254442459f 🤖 chore: Lighthouse Results | Chat 2026-05-22 01:05:38 +00:00
lobehubbot 199a97d822 🤖 chore: Lighthouse Results | Chat 2026-05-21 01:07:25 +00:00
lobehubbot 84e4da1127 🤖 chore: Lighthouse Results | Discover 2026-05-21 01:07:19 +00:00
lobehubbot 2de40bd0dd 🤖 chore: Lighthouse Results | Chat 2026-05-20 01:07:15 +00:00
lobehubbot 8c5af983a6 🤖 chore: Lighthouse Results | Discover 2026-05-20 01:07:11 +00:00
lobehubbot cace90d983 🤖 chore: Lighthouse Results | Discover 2026-05-19 01:05:41 +00:00
lobehubbot 4e4752464a 🤖 chore: Lighthouse Results | Chat 2026-05-19 01:05:38 +00:00
lobehubbot 2556627bef 🤖 chore: Lighthouse Results | Discover 2026-05-18 01:05:31 +00:00
lobehubbot b0803c6ad7 🤖 chore: Lighthouse Results | Chat 2026-05-18 01:05:26 +00:00
lobehubbot 854e96e5ec 🤖 chore: Lighthouse Results | Discover 2026-05-17 01:04:18 +00:00
lobehubbot 21288f0095 🤖 chore: Lighthouse Results | Chat 2026-05-17 01:04:11 +00:00
lobehubbot 660e064434 🤖 chore: Lighthouse Results | Chat 2026-05-16 00:57:31 +00:00
lobehubbot 01c7855fca 🤖 chore: Lighthouse Results | Discover 2026-05-16 00:57:23 +00:00
lobehubbot 40a18616ad 🤖 chore: Lighthouse Results | Chat 2026-05-15 01:00:35 +00:00
lobehubbot 0fe1e1934c 🤖 chore: Lighthouse Results | Discover 2026-05-15 01:00:33 +00:00
lobehubbot 3af84d0749 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-05-14 01:03:10 +00:00
lobehubbot 81e4cb5bdb 🤖 chore: Lighthouse Results | Chat 2026-05-14 01:03:09 +00:00
lobehubbot 4fca2b7bb9 🤖 chore: Lighthouse Results | Discover 2026-05-14 01:03:07 +00:00
lobehubbot 4d5d272ccd 🤖 chore: Lighthouse Results | Discover 2026-05-13 01:01:41 +00:00
lobehubbot a093191835 🤖 chore: Lighthouse Results | Chat 2026-05-13 01:01:35 +00:00
lobehubbot db3839ae16 🤖 chore: Lighthouse Results | Chat 2026-05-12 00:57:25 +00:00
lobehubbot d866527d9c 🤖 chore: Lighthouse Results | Discover 2026-05-12 00:57:15 +00:00
lobehubbot b888cde521 🤖 chore: Lighthouse Results | Chat 2026-05-11 01:02:49 +00:00
lobehubbot 47fce207fe 🤖 chore: Lighthouse Results | Discover 2026-05-11 01:02:46 +00:00
lobehubbot ca47d3e0fa 🤖 chore: Lighthouse Results | Discover 2026-05-10 01:03:01 +00:00
lobehubbot 94dd9945c0 🤖 chore: Lighthouse Results | Chat 2026-05-10 01:02:58 +00:00
lobehubbot d626e4cd9e 🤖 chore: Lighthouse Results | Chat 2026-05-09 00:57:57 +00:00
lobehubbot 44d9970496 🤖 chore: Lighthouse Results | Discover 2026-05-09 00:57:51 +00:00
lobehubbot 44779b2013 🤖 chore: Lighthouse Results | Chat 2026-05-08 00:58:11 +00:00
lobehubbot d8af6b7dd7 🤖 chore: Lighthouse Results | Discover 2026-05-08 00:57:59 +00:00
lobehubbot 71a1f24291 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-05-07 00:58:31 +00:00
lobehubbot 4b647b37e7 🤖 chore: Lighthouse Results | Chat 2026-05-07 00:58:29 +00:00
lobehubbot ea0248ff8a 🤖 chore: Lighthouse Results | Discover 2026-05-07 00:58:28 +00:00
lobehubbot 7b697a1ac6 🤖 chore: Lighthouse Results | Discover 2026-05-06 00:53:56 +00:00
lobehubbot 5a4598e9ef 🤖 chore: Lighthouse Results | Chat 2026-05-06 00:53:46 +00:00
lobehubbot 01cbf1ac6f 🤖 chore: Lighthouse Results | Discover 2026-05-05 00:53:36 +00:00
lobehubbot 9b8331942f 🤖 chore: Lighthouse Results | Chat 2026-05-05 00:53:31 +00:00
lobehubbot 64df7bac19 🤖 chore: Lighthouse Results | Chat 2026-05-04 00:59:22 +00:00
lobehubbot cc1b7d1487 🤖 chore: Lighthouse Results | Discover 2026-05-04 00:59:11 +00:00
lobehubbot 6832c69f55 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-05-03 01:01:38 +00:00
lobehubbot 088b9c77c2 🤖 chore: Lighthouse Results | Chat 2026-05-03 01:01:37 +00:00
lobehubbot 9fea61dd65 🤖 chore: Lighthouse Results | Discover 2026-05-03 01:01:36 +00:00
lobehubbot 49e574a938 🤖 chore: Lighthouse Results | Chat 2026-05-02 00:56:10 +00:00
lobehubbot 62ada95404 🤖 chore: Lighthouse Results | Discover 2026-05-02 00:55:54 +00:00
lobehubbot b5f6c11ef1 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-05-01 01:02:46 +00:00
lobehubbot e986022ce4 🤖 chore: Lighthouse Results | Discover 2026-05-01 01:02:44 +00:00
lobehubbot fe96595709 🤖 chore: Lighthouse Results | Chat 2026-05-01 01:02:44 +00:00
lobehubbot e7e875ab13 🤖 chore: Lighthouse Results | Chat 2026-04-30 00:58:29 +00:00
lobehubbot 6ebfbdd377 🤖 chore: Lighthouse Results | Discover 2026-04-30 00:58:26 +00:00
lobehubbot 4dda2093d5 🤖 chore: Lighthouse Results | Chat 2026-04-29 00:58:46 +00:00
lobehubbot 493474dde4 🤖 chore: Lighthouse Results | Discover 2026-04-29 00:58:37 +00:00
lobehubbot 7292b6623c 🤖 chore: Lighthouse Results | Discover 2026-04-28 00:57:42 +00:00
lobehubbot 4b670db1ef 🤖 chore: Lighthouse Results | Chat 2026-04-28 00:57:38 +00:00
lobehubbot d525fa71d6 🤖 chore: Lighthouse Results | Discover 2026-04-27 00:55:40 +00:00
lobehubbot d6d78c9895 🤖 chore: Lighthouse Results | Chat 2026-04-27 00:55:36 +00:00
lobehubbot 34ee07b07d 🤖 chore: Lighthouse Results | Discover 2026-04-26 00:55:29 +00:00
lobehubbot 9bd40adcbd 🤖 chore: Lighthouse Results | Chat 2026-04-26 00:55:26 +00:00
lobehubbot f4ee33b0eb Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-04-25 00:49:53 +00:00
lobehubbot 9d7ab7d018 🤖 chore: Lighthouse Results | Chat 2026-04-25 00:49:52 +00:00
lobehubbot 45c0bab76e 🤖 chore: Lighthouse Results | Discover 2026-04-25 00:49:51 +00:00
lobehubbot 948d66dca0 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-04-24 00:54:43 +00:00
lobehubbot d0f6a7e539 🤖 chore: Lighthouse Results | Chat 2026-04-24 00:54:41 +00:00
lobehubbot f8b2718041 🤖 chore: Lighthouse Results | Discover 2026-04-24 00:54:41 +00:00
lobehubbot 8d34e9a83c 🤖 chore: Lighthouse Results | Discover 2026-04-23 00:54:05 +00:00
lobehubbot 9f8bafb817 🤖 chore: Lighthouse Results | Chat 2026-04-23 00:53:54 +00:00
lobehubbot b5e0d07e00 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-04-22 00:49:48 +00:00
lobehubbot bc2c9ae6f4 🤖 chore: Lighthouse Results | Discover 2026-04-22 00:49:47 +00:00
lobehubbot 49c0743f47 🤖 chore: Lighthouse Results | Chat 2026-04-22 00:49:46 +00:00
lobehubbot 0bab1722b4 🤖 chore: Lighthouse Results | Discover 2026-04-21 00:52:42 +00:00
lobehubbot 43ae49105b 🤖 chore: Lighthouse Results | Chat 2026-04-21 00:52:39 +00:00
lobehubbot 3bb9f679ff 🤖 chore: Lighthouse Results | Discover 2026-04-20 00:53:22 +00:00
lobehubbot be7f04e20f 🤖 chore: Lighthouse Results | Chat 2026-04-20 00:53:19 +00:00
lobehubbot 22ed34b793 🤖 chore: Lighthouse Results | Chat 2026-04-19 00:53:53 +00:00
lobehubbot f107740489 🤖 chore: Lighthouse Results | Discover 2026-04-19 00:53:50 +00:00
lobehubbot 0c1cefa33b 🤖 chore: Lighthouse Results | Chat 2026-04-18 00:47:37 +00:00
lobehubbot 3ce9495146 🤖 chore: Lighthouse Results | Discover 2026-04-18 00:47:34 +00:00
lobehubbot a9d53072e4 🤖 chore: Lighthouse Results | Discover 2026-04-17 00:52:09 +00:00
lobehubbot a6edf76855 🤖 chore: Lighthouse Results | Chat 2026-04-17 00:51:59 +00:00
lobehubbot 1556c241df 🤖 chore: Lighthouse Results | Chat 2026-04-16 00:54:02 +00:00
lobehubbot d24ec0ccd1 🤖 chore: Lighthouse Results | Discover 2026-04-16 00:53:57 +00:00
lobehubbot 2e3a2bc97d 🤖 chore: Lighthouse Results | Chat 2026-04-15 00:52:54 +00:00
lobehubbot 194e10dba5 🤖 chore: Lighthouse Results | Discover 2026-04-15 00:52:48 +00:00
lobehubbot ff32633a2b Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-04-14 00:52:34 +00:00
lobehubbot 3b87e9e4b5 🤖 chore: Lighthouse Results | Discover 2026-04-14 00:52:32 +00:00
lobehubbot 52c14dc2f4 🤖 chore: Lighthouse Results | Chat 2026-04-14 00:52:32 +00:00
lobehubbot 94a8a44d03 🤖 chore: Lighthouse Results | Discover 2026-04-13 00:52:14 +00:00
lobehubbot 2c18a0156b 🤖 chore: Lighthouse Results | Chat 2026-04-13 00:52:13 +00:00
lobehubbot 49c77b851b 🤖 chore: Lighthouse Results | Chat 2026-04-12 00:52:17 +00:00
lobehubbot 85be3acb27 🤖 chore: Lighthouse Results | Discover 2026-04-12 00:52:11 +00:00
lobehubbot cff725817e 🤖 chore: Lighthouse Results | Chat 2026-04-11 00:45:54 +00:00
lobehubbot 27b7485276 🤖 chore: Lighthouse Results | Discover 2026-04-11 00:45:49 +00:00
lobehubbot bbda4c2cda 🤖 chore: Lighthouse Results | Chat 2026-04-10 00:47:11 +00:00
lobehubbot ebc6c5a8f1 🤖 chore: Lighthouse Results | Discover 2026-04-10 00:47:06 +00:00
lobehubbot fef951eb47 🤖 chore: Lighthouse Results | Chat 2026-04-09 00:40:20 +00:00
lobehubbot e9f7d5858d 🤖 chore: Lighthouse Results | Discover 2026-04-09 00:40:18 +00:00
lobehubbot 8f7c71846f 🤖 chore: Lighthouse Results | Discover 2026-04-08 00:48:02 +00:00
lobehubbot 6b70fafdfe 🤖 chore: Lighthouse Results | Chat 2026-04-08 00:47:58 +00:00
lobehubbot 5066ae948d 🤖 chore: Lighthouse Results | Chat 2026-04-07 00:47:47 +00:00
lobehubbot 93e266111d 🤖 chore: Lighthouse Results | Discover 2026-04-07 00:47:42 +00:00
lobehubbot 79fe90aaff 🤖 chore: Lighthouse Results | Chat 2026-04-06 00:48:58 +00:00
lobehubbot 5858d33c60 🤖 chore: Lighthouse Results | Discover 2026-04-06 00:48:55 +00:00
lobehubbot 4c235a3c44 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-04-05 00:49:54 +00:00
lobehubbot 2ba08b6e14 🤖 chore: Lighthouse Results | Discover 2026-04-05 00:49:53 +00:00
lobehubbot 40296e118b 🤖 chore: Lighthouse Results | Chat 2026-04-05 00:49:52 +00:00
lobehubbot 14e675cda7 🤖 chore: Lighthouse Results | Discover 2026-04-04 00:43:38 +00:00
lobehubbot 9db0c6f9d1 🤖 chore: Lighthouse Results | Chat 2026-04-04 00:43:36 +00:00
lobehubbot 6aa8c32ba5 🤖 chore: Lighthouse Results | Discover 2026-04-03 00:46:51 +00:00
lobehubbot efe9e044dd 🤖 chore: Lighthouse Results | Chat 2026-04-03 00:46:33 +00:00
lobehubbot 23422f1a9c 🤖 chore: Lighthouse Results | Discover 2026-04-02 00:44:34 +00:00
lobehubbot 10f56eb305 🤖 chore: Lighthouse Results | Chat 2026-04-02 00:44:29 +00:00
lobehubbot 750dd3f9fe 🤖 chore: Lighthouse Results | Chat 2026-04-01 00:51:46 +00:00
lobehubbot d28d05187f 🤖 chore: Lighthouse Results | Discover 2026-04-01 00:51:44 +00:00
lobehubbot 3c05c06b1b 🤖 chore: Lighthouse Results | Chat 2026-03-31 00:46:57 +00:00
lobehubbot 7e85ea0b4e 🤖 chore: Lighthouse Results | Discover 2026-03-31 00:46:54 +00:00
lobehubbot a176c3bb74 🤖 chore: Lighthouse Results | Chat 2026-03-30 00:49:16 +00:00
lobehubbot e72fbb2853 🤖 chore: Lighthouse Results | Discover 2026-03-30 00:49:04 +00:00
lobehubbot 77b34b15ea Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-03-29 00:49:36 +00:00
lobehubbot 57e603cdc3 🤖 chore: Lighthouse Results | Discover 2026-03-29 00:49:34 +00:00
lobehubbot fac70f1232 🤖 chore: Lighthouse Results | Chat 2026-03-29 00:49:34 +00:00
lobehubbot a2bd266791 🤖 chore: Lighthouse Results | Discover 2026-03-28 00:43:13 +00:00
lobehubbot 9d98415922 🤖 chore: Lighthouse Results | Chat 2026-03-28 00:43:04 +00:00
lobehubbot b1506ae7da 🤖 chore: Lighthouse Results | Chat 2026-03-27 00:46:41 +00:00
lobehubbot 15811b9171 🤖 chore: Lighthouse Results | Discover 2026-03-27 00:46:34 +00:00
lobehubbot 7aeb89d21e Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-03-26 00:46:10 +00:00
lobehubbot 26e8355640 🤖 chore: Lighthouse Results | Chat 2026-03-26 00:46:09 +00:00
lobehubbot 736e372c3c 🤖 chore: Lighthouse Results | Discover 2026-03-26 00:46:08 +00:00
lobehubbot 7f58bf6365 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-03-25 00:43:42 +00:00
lobehubbot 7767b65584 🤖 chore: Lighthouse Results | Chat 2026-03-25 00:43:41 +00:00
lobehubbot 1d067ee975 🤖 chore: Lighthouse Results | Discover 2026-03-25 00:43:40 +00:00
lobehubbot 60542021c6 🤖 chore: Lighthouse Results | Discover 2026-03-24 00:38:21 +00:00
lobehubbot 23d44e2143 🤖 chore: Lighthouse Results | Chat 2026-03-24 00:38:18 +00:00
lobehubbot 4e0de502e3 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-03-23 00:46:06 +00:00
lobehubbot 9cf2d62b8c 🤖 chore: Lighthouse Results | Chat 2026-03-23 00:46:05 +00:00
lobehubbot f5e12d656c 🤖 chore: Lighthouse Results | Discover 2026-03-23 00:46:05 +00:00
lobehubbot 7f8286d341 🤖 chore: Lighthouse Results | Discover 2026-03-22 00:45:28 +00:00
lobehubbot acaac14dc9 🤖 chore: Lighthouse Results | Chat 2026-03-22 00:45:26 +00:00
lobehubbot 37fa15061d 🤖 chore: Lighthouse Results | Chat 2026-03-21 00:39:57 +00:00
lobehubbot 7bfc94bd61 🤖 chore: Lighthouse Results | Discover 2026-03-21 00:39:47 +00:00
lobehubbot bc80105cd4 🤖 chore: Lighthouse Results | Chat 2026-03-20 00:42:08 +00:00
lobehubbot f965b89128 🤖 chore: Lighthouse Results | Discover 2026-03-20 00:42:00 +00:00
lobehubbot c9004cea49 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-03-19 00:44:12 +00:00
lobehubbot 7f4677b246 🤖 chore: Lighthouse Results | Chat 2026-03-19 00:44:11 +00:00
lobehubbot 17445ea3e7 🤖 chore: Lighthouse Results | Discover 2026-03-19 00:44:11 +00:00
lobehubbot ba08de0d9f 🤖 chore: Lighthouse Results | Discover 2026-03-18 00:43:37 +00:00
lobehubbot 29cdea2ce2 🤖 chore: Lighthouse Results | Chat 2026-03-18 00:43:36 +00:00
lobehubbot eb4c715437 🤖 chore: Lighthouse Results | Chat 2026-03-17 00:41:10 +00:00
lobehubbot 32452f532a 🤖 chore: Lighthouse Results | Discover 2026-03-17 00:40:53 +00:00
lobehubbot 9e115ceb8c 🤖 chore: Lighthouse Results | Chat 2026-03-16 00:47:33 +00:00
lobehubbot 31ef5fee82 🤖 chore: Lighthouse Results | Discover 2026-03-16 00:47:29 +00:00
lobehubbot f91bf5fbe4 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-03-15 00:48:45 +00:00
lobehubbot 3f1cf4fe1d 🤖 chore: Lighthouse Results | Chat 2026-03-15 00:48:44 +00:00
lobehubbot 33baf89304 🤖 chore: Lighthouse Results | Discover 2026-03-15 00:48:44 +00:00
lobehubbot f4f62008b2 🤖 chore: Lighthouse Results | Chat 2026-03-14 00:39:37 +00:00
lobehubbot f961ef35ff 🤖 chore: Lighthouse Results | Discover 2026-03-14 00:39:34 +00:00
lobehubbot ecf427d01e 🤖 chore: Lighthouse Results | Chat 2026-03-13 00:41:30 +00:00
lobehubbot 248538b3e9 🤖 chore: Lighthouse Results | Discover 2026-03-13 00:41:23 +00:00
lobehubbot 8275c935e3 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-03-12 00:36:02 +00:00
lobehubbot 01622042d7 🤖 chore: Lighthouse Results | Chat 2026-03-12 00:36:01 +00:00
lobehubbot 2d908f963f 🤖 chore: Lighthouse Results | Discover 2026-03-12 00:36:00 +00:00
lobehubbot 76d77df654 🤖 chore: Lighthouse Results | Discover 2026-03-11 00:38:50 +00:00
lobehubbot 7dd1b0d834 🤖 chore: Lighthouse Results | Chat 2026-03-11 00:38:46 +00:00
lobehubbot 9ad56c8739 🤖 chore: Lighthouse Results | Discover 2026-03-10 00:38:28 +00:00
lobehubbot 89a7f76cb8 🤖 chore: Lighthouse Results | Chat 2026-03-10 00:38:20 +00:00
lobehubbot 25904c3ca5 🤖 chore: Lighthouse Results | Discover 2026-03-09 00:42:18 +00:00
lobehubbot 2e9f3ce433 🤖 chore: Lighthouse Results | Chat 2026-03-09 00:42:17 +00:00
lobehubbot d2e1158f8f 🤖 chore: Lighthouse Results | Chat 2026-03-08 00:42:46 +00:00
lobehubbot 7cffb0f3da 🤖 chore: Lighthouse Results | Discover 2026-03-08 00:42:38 +00:00
lobehubbot da04a3f608 🤖 chore: Lighthouse Results | Discover 2026-03-07 00:39:55 +00:00
lobehubbot b67a4ef923 🤖 chore: Lighthouse Results | Chat 2026-03-07 00:39:52 +00:00
lobehubbot 3fbdaa65a2 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-03-06 00:45:02 +00:00
lobehubbot 4187fd4fe4 🤖 chore: Lighthouse Results | Discover 2026-03-06 00:45:02 +00:00
lobehubbot 5aaa725ac0 🤖 chore: Lighthouse Results | Chat 2026-03-06 00:45:01 +00:00
lobehubbot 38dbd3daa0 🤖 chore: Lighthouse Results | Chat 2026-03-05 00:42:07 +00:00
lobehubbot e339f9a957 🤖 chore: Lighthouse Results | Discover 2026-03-05 00:41:57 +00:00
lobehubbot 43a8f2206a 🤖 chore: Lighthouse Results | Chat 2026-03-04 00:40:25 +00:00
lobehubbot 16923e2ef2 🤖 chore: Lighthouse Results | Discover 2026-03-04 00:40:23 +00:00
lobehubbot 1939ced794 🤖 chore: Lighthouse Results | Discover 2026-03-03 00:42:29 +00:00
lobehubbot b687ca5785 🤖 chore: Lighthouse Results | Chat 2026-03-03 00:42:22 +00:00
lobehubbot 1c188c0655 🤖 chore: Lighthouse Results | Discover 2026-03-02 00:41:56 +00:00
lobehubbot 93cee7b155 🤖 chore: Lighthouse Results | Chat 2026-03-02 00:41:48 +00:00
lobehubbot ec3489ee7a 🤖 chore: Lighthouse Results | Discover 2026-03-01 00:47:26 +00:00
lobehubbot bc1f67ae70 🤖 chore: Lighthouse Results | Chat 2026-03-01 00:47:24 +00:00
lobehubbot 21a54a0b86 🤖 chore: Lighthouse Results | Chat 2026-02-28 00:37:06 +00:00
lobehubbot e972dbde38 🤖 chore: Lighthouse Results | Discover 2026-02-28 00:37:00 +00:00
lobehubbot cba7d1ef7b Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2026-02-27 00:42:05 +00:00
lobehubbot e596e9f0aa 🤖 chore: Lighthouse Results | Discover 2026-02-27 00:42:04 +00:00
lobehubbot 6a129d37bd 🤖 chore: Lighthouse Results | Chat 2026-02-27 00:42:04 +00:00
lobehubbot 2d99d4ca06 🤖 chore: Lighthouse Results | Discover 2026-02-26 00:39:57 +00:00
lobehubbot edb1f596f8 🤖 chore: Lighthouse Results | Chat 2026-02-26 00:39:44 +00:00
lobehubbot 548ded1b98 🤖 chore: Lighthouse Results | Chat 2026-02-25 00:44:37 +00:00
lobehubbot ffcb631d5a 🤖 chore: Lighthouse Results | Discover 2026-02-25 00:44:31 +00:00
lobehubbot b9b2c423c0 🤖 chore: Lighthouse Results | Discover 2026-02-24 00:39:16 +00:00
lobehubbot c605d8d5be 🤖 chore: Lighthouse Results | Chat 2026-02-24 00:39:12 +00:00
lobehubbot 4cb14c5a3a 🤖 chore: Lighthouse Results | Discover 2026-02-23 00:43:16 +00:00
lobehubbot 6522ed8a45 🤖 chore: Lighthouse Results | Chat 2026-02-23 00:42:49 +00:00
lobehubbot 427c049495 🤖 chore: Lighthouse Results | Discover 2026-02-22 00:43:27 +00:00
lobehubbot aa7c50ae40 🤖 chore: Lighthouse Results | Chat 2026-02-22 00:43:17 +00:00
lobehubbot e6bef3956f 🤖 chore: Lighthouse Results | Chat 2026-02-21 00:40:30 +00:00
lobehubbot 06d02f6f3b 🤖 chore: Lighthouse Results | Discover 2026-02-21 00:40:07 +00:00
lobehubbot 8a782075ec 🤖 chore: Lighthouse Results | Chat 2026-02-20 00:40:32 +00:00
lobehubbot 5b606d34cf 🤖 chore: Lighthouse Results | Discover 2026-02-20 00:40:04 +00:00
lobehubbot 238d629642 🤖 chore: Lighthouse Results | Discover 2026-02-19 00:43:27 +00:00
lobehubbot 7ad863651d 🤖 chore: Lighthouse Results | Chat 2026-02-19 00:42:57 +00:00
lobehubbot 86ffbe6b8f 🤖 chore: Lighthouse Results | Chat 2026-02-18 00:42:51 +00:00
lobehubbot efd85e8834 🤖 chore: Lighthouse Results | Discover 2026-02-18 00:42:33 +00:00
lobehubbot 7c12aec9ab 🤖 chore: Lighthouse Results | Discover 2026-02-17 00:42:05 +00:00
lobehubbot f49e7fde50 🤖 chore: Lighthouse Results | Chat 2026-02-17 00:42:04 +00:00
lobehubbot 23aad5c6c6 🤖 chore: Lighthouse Results | Discover 2026-02-16 00:42:55 +00:00
lobehubbot a4f23d2c2f 🤖 chore: Lighthouse Results | Chat 2026-02-16 00:42:50 +00:00
lobehubbot cee4366e04 🤖 chore: Lighthouse Results | Discover 2026-02-15 00:45:38 +00:00
lobehubbot 53b017d5b4 🤖 chore: Lighthouse Results | Chat 2026-02-15 00:45:15 +00:00
lobehubbot 12efe86589 🤖 chore: Lighthouse Results | Chat 2026-02-14 00:41:09 +00:00
lobehubbot 6b4b192583 🤖 chore: Lighthouse Results | Discover 2026-02-14 00:40:59 +00:00
lobehubbot f92356fe7a 🤖 chore: Lighthouse Results | Chat 2026-02-13 00:44:55 +00:00
lobehubbot 2ea610eab9 🤖 chore: Lighthouse Results | Discover 2026-02-11 00:47:08 +00:00
lobehubbot 084837f13c 🤖 chore: Lighthouse Results | Discover 2026-02-09 00:43:49 +00:00
lobehubbot a81b6bd396 🤖 chore: Lighthouse Results | Chat 2026-02-08 00:52:15 +00:00
lobehubbot 91605602d2 🤖 chore: Lighthouse Results | Chat 2026-02-03 00:43:03 +00:00
lobehubbot dd0381a920 🤖 chore: Lighthouse Results | Discover 2026-02-03 00:42:52 +00:00
lobehubbot 79e8f732ee 🤖 chore: Lighthouse Results | Chat 2026-01-27 00:37:20 +00:00
lobehubbot a6df9449af 🤖 chore: Lighthouse Results | Discover 2026-01-27 00:36:51 +00:00
lobehubbot 383de0d558 🤖 chore: Lighthouse Results | Chat 2026-01-26 00:38:25 +00:00
lobehubbot 0e95d5a036 🤖 chore: Lighthouse Results | Discover 2026-01-26 00:38:15 +00:00
lobehubbot f5d8a9a6e3 🤖 chore: Lighthouse Results | Chat 2026-01-25 00:39:15 +00:00
lobehubbot e9cc2f1b21 🤖 chore: Lighthouse Results | Discover 2026-01-25 00:39:10 +00:00
lobehubbot de5d88a8f3 🤖 chore: Lighthouse Results | Discover 2026-01-24 00:34:22 +00:00
lobehubbot adf8eabe04 🤖 chore: Lighthouse Results | Discover 2026-01-23 00:35:46 +00:00
lobehubbot e1e14bf5d3 🤖 chore: Lighthouse Results | Chat 2026-01-22 00:36:43 +00:00
lobehubbot bd5fd1b393 🤖 chore: Lighthouse Results | Discover 2026-01-22 00:36:22 +00:00
lobehubbot b3691d91e7 🤖 chore: Lighthouse Results | Chat 2026-01-21 00:35:53 +00:00
lobehubbot 06c096ae46 🤖 chore: Lighthouse Results | Discover 2026-01-21 00:35:46 +00:00
lobehubbot 1028efb7f1 🤖 chore: Lighthouse Results | Discover 2026-01-20 00:34:04 +00:00
lobehubbot 73a39677bc 🤖 chore: Lighthouse Results | Chat 2026-01-19 00:38:14 +00:00
lobehubbot 04d39ac72e 🤖 chore: Lighthouse Results | Discover 2026-01-19 00:37:34 +00:00
lobehubbot 2ff148467a 🤖 chore: Lighthouse Results | Chat 2026-01-18 00:38:23 +00:00
lobehubbot e6e2b49503 🤖 chore: Lighthouse Results | Discover 2026-01-18 00:38:19 +00:00
lobehubbot 4319a3bfe8 🤖 chore: Lighthouse Results | Chat 2026-01-17 00:33:30 +00:00
lobehubbot f4dfc97dc1 🤖 chore: Lighthouse Results | Discover 2026-01-17 00:33:16 +00:00
lobehubbot b4b4c7217c 🤖 chore: Lighthouse Results | Chat 2026-01-16 00:35:37 +00:00
lobehubbot 40d2978622 🤖 chore: Lighthouse Results | Discover 2026-01-16 00:35:21 +00:00
lobehubbot a6eb591591 🤖 chore: Lighthouse Results | Discover 2026-01-15 00:32:20 +00:00
lobehubbot ed0a7aab4f 🤖 chore: Lighthouse Results | Chat 2026-01-14 00:39:12 +00:00
lobehubbot cff1a7881f 🤖 chore: Lighthouse Results | Discover 2026-01-14 00:39:07 +00:00
lobehubbot 2fd81eeacd 🤖 chore: Lighthouse Results | Chat 2026-01-13 00:31:08 +00:00
lobehubbot e1dc751d97 🤖 chore: Lighthouse Results | Discover 2026-01-13 00:31:04 +00:00
lobehubbot 45ff5e3184 🤖 chore: Lighthouse Results | Chat 2026-01-12 00:37:12 +00:00
lobehubbot 065e114834 🤖 chore: Lighthouse Results | Discover 2026-01-12 00:36:38 +00:00
lobehubbot 40b40b839b 🤖 chore: Lighthouse Results | Discover 2026-01-11 00:38:55 +00:00
lobehubbot 8ba2c5aca8 🤖 chore: Lighthouse Results | Chat 2026-01-11 00:38:53 +00:00
lobehubbot f2ac112ae5 🤖 chore: Lighthouse Results | Chat 2026-01-10 00:34:49 +00:00
lobehubbot b952c43706 🤖 chore: Lighthouse Results | Discover 2026-01-10 00:34:19 +00:00
lobehubbot a6d0ebb905 🤖 chore: Lighthouse Results | Discover 2026-01-09 00:34:36 +00:00
lobehubbot cfbe1eccc6 🤖 chore: Lighthouse Results | Chat 2026-01-09 00:34:30 +00:00
lobehubbot ba275da012 🤖 chore: Lighthouse Results | Chat 2026-01-08 00:35:08 +00:00
lobehubbot 0bafe0f2f7 🤖 chore: Lighthouse Results | Discover 2026-01-08 00:34:52 +00:00
lobehubbot eea18b3ede 🤖 chore: Lighthouse Results | Chat 2026-01-07 00:34:07 +00:00
lobehubbot 6946cb93e6 🤖 chore: Lighthouse Results | Discover 2026-01-07 00:34:00 +00:00
lobehubbot 5219a26f70 🤖 chore: Lighthouse Results | Discover 2026-01-06 00:34:51 +00:00
lobehubbot 0be3b159aa 🤖 chore: Lighthouse Results | Chat 2026-01-05 00:37:54 +00:00
lobehubbot cb9aa2c7b0 🤖 chore: Lighthouse Results | Discover 2026-01-05 00:37:43 +00:00
lobehubbot eb180c7d67 🤖 chore: Lighthouse Results | Chat 2026-01-04 00:38:44 +00:00
lobehubbot f7562c7734 🤖 chore: Lighthouse Results | Discover 2026-01-04 00:38:37 +00:00
lobehubbot ccd224cd80 🤖 chore: Lighthouse Results | Chat 2026-01-03 00:32:51 +00:00
lobehubbot 9b0041c66b 🤖 chore: Lighthouse Results | Discover 2026-01-03 00:32:47 +00:00
lobehubbot f52d24c4d8 🤖 chore: Lighthouse Results | Chat 2026-01-02 00:35:16 +00:00
lobehubbot e6aa5118fa 🤖 chore: Lighthouse Results | Discover 2026-01-02 00:35:02 +00:00
lobehubbot 0cbd2beab3 🤖 chore: Lighthouse Results | Chat 2026-01-01 00:39:24 +00:00
lobehubbot 4b85727237 🤖 chore: Lighthouse Results | Discover 2026-01-01 00:39:19 +00:00
lobehubbot 5ae9408682 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-12-31 00:35:18 +00:00
lobehubbot 8ec17fc7f1 🤖 chore: Lighthouse Results | Discover 2025-12-31 00:35:17 +00:00
lobehubbot 34d06aee00 🤖 chore: Lighthouse Results | Chat 2025-12-31 00:35:16 +00:00
lobehubbot 86923c6504 🤖 chore: Lighthouse Results | Chat 2025-12-30 00:34:07 +00:00
lobehubbot 51541b0a15 🤖 chore: Lighthouse Results | Discover 2025-12-30 00:34:00 +00:00
lobehubbot 161a5c75ad 🤖 chore: Lighthouse Results | Chat 2025-12-29 00:37:26 +00:00
lobehubbot 71fb1e8e80 🤖 chore: Lighthouse Results | Discover 2025-12-29 00:37:19 +00:00
lobehubbot 863e31aaa9 🤖 chore: Lighthouse Results | Chat 2025-12-28 00:38:46 +00:00
lobehubbot 1454587a18 🤖 chore: Lighthouse Results | Chat 2025-12-27 00:33:29 +00:00
lobehubbot 2fa3ba28a3 🤖 chore: Lighthouse Results | Discover 2025-12-27 00:33:22 +00:00
lobehubbot 724e8fea33 🤖 chore: Lighthouse Results | Chat 2025-12-26 00:34:02 +00:00
lobehubbot a3ee8b97b1 🤖 chore: Lighthouse Results | Discover 2025-12-26 00:33:57 +00:00
lobehubbot 4fade68d57 🤖 chore: Lighthouse Results | Discover 2025-12-25 00:34:01 +00:00
lobehubbot 67a1013fd0 🤖 chore: Lighthouse Results | Chat 2025-12-25 00:33:49 +00:00
lobehubbot 0ba2631f16 🤖 chore: Lighthouse Results | Chat 2025-12-24 00:34:08 +00:00
lobehubbot ea9e00a655 🤖 chore: Lighthouse Results | Discover 2025-12-24 00:34:06 +00:00
lobehubbot db00e09573 🤖 chore: Lighthouse Results | Chat 2025-12-23 00:33:50 +00:00
lobehubbot 3251a1e350 🤖 chore: Lighthouse Results | Discover 2025-12-23 00:33:42 +00:00
lobehubbot 3224afdb7a 🤖 chore: Lighthouse Results | Discover 2025-12-22 00:35:54 +00:00
lobehubbot 18a6ddf6ab 🤖 chore: Lighthouse Results | Chat 2025-12-22 00:35:52 +00:00
lobehubbot 9392f03200 🤖 chore: Lighthouse Results | Chat 2025-12-21 00:36:44 +00:00
lobehubbot fcf0be8a60 🤖 chore: Lighthouse Results | Discover 2025-12-21 00:36:31 +00:00
lobehubbot 5cb20ec52c 🤖 chore: Lighthouse Results | Discover 2025-12-20 00:32:16 +00:00
lobehubbot 3aee91fed8 🤖 chore: Lighthouse Results | Chat 2025-12-20 00:32:12 +00:00
lobehubbot 678933ee6d 🤖 chore: Lighthouse Results | Chat 2025-12-19 00:34:04 +00:00
lobehubbot c7cde6c084 🤖 chore: Lighthouse Results | Discover 2025-12-19 00:33:56 +00:00
lobehubbot 80689f16be 🤖 chore: Lighthouse Results | Chat 2025-12-18 00:30:50 +00:00
lobehubbot ac05ff442f 🤖 chore: Lighthouse Results | Discover 2025-12-18 00:30:29 +00:00
lobehubbot 35ab311ebc 🤖 chore: Lighthouse Results | Discover 2025-12-17 00:33:14 +00:00
lobehubbot f9582f5331 🤖 chore: Lighthouse Results | Chat 2025-12-17 00:33:05 +00:00
lobehubbot 15ec2bd54d 🤖 chore: Lighthouse Results | Chat 2025-12-16 00:34:15 +00:00
lobehubbot 9bc3d4d70e 🤖 chore: Lighthouse Results | Discover 2025-12-16 00:34:10 +00:00
lobehubbot cf83140c64 🤖 chore: Lighthouse Results | Discover 2025-12-15 00:35:54 +00:00
lobehubbot b8a0518faf 🤖 chore: Lighthouse Results | Chat 2025-12-15 00:35:50 +00:00
lobehubbot 99e6003873 🤖 chore: Lighthouse Results | Discover 2025-12-14 00:36:46 +00:00
lobehubbot 3ab7bdf4af 🤖 chore: Lighthouse Results | Chat 2025-12-14 00:36:46 +00:00
lobehubbot 5539fc4c65 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-12-13 00:32:19 +00:00
lobehubbot 20992bd3f1 🤖 chore: Lighthouse Results | Discover 2025-12-13 00:32:18 +00:00
lobehubbot 79821ecc4e 🤖 chore: Lighthouse Results | Chat 2025-12-13 00:32:17 +00:00
lobehubbot 96373f535b Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-12-12 00:33:44 +00:00
lobehubbot 9695c155a7 🤖 chore: Lighthouse Results | Discover 2025-12-12 00:33:43 +00:00
lobehubbot bc44c4e71d 🤖 chore: Lighthouse Results | Chat 2025-12-12 00:33:42 +00:00
lobehubbot d017ee5c3b 🤖 chore: Lighthouse Results | Discover 2025-12-11 00:34:30 +00:00
lobehubbot 950ae6b65e 🤖 chore: Lighthouse Results | Chat 2025-12-11 00:33:46 +00:00
lobehubbot 3682e208f6 🤖 chore: Lighthouse Results | Discover 2025-12-10 00:36:13 +00:00
lobehubbot ef9e0c577e 🤖 chore: Lighthouse Results | Chat 2025-12-10 00:35:23 +00:00
lobehubbot 6718ca5fd4 🤖 chore: Lighthouse Results | Chat 2025-12-09 00:35:22 +00:00
lobehubbot 20a750984d 🤖 chore: Lighthouse Results | Discover 2025-12-09 00:34:36 +00:00
lobehubbot 4145f4caa5 🤖 chore: Lighthouse Results | Chat 2025-12-08 00:35:46 +00:00
lobehubbot d5ce4bef3c 🤖 chore: Lighthouse Results | Discover 2025-12-08 00:35:05 +00:00
lobehubbot a233f58b63 🤖 chore: Lighthouse Results | Discover 2025-12-07 00:38:12 +00:00
lobehubbot 138afd6983 🤖 chore: Lighthouse Results | Chat 2025-12-07 00:37:55 +00:00
lobehubbot bc0c6be2a5 🤖 chore: Lighthouse Results | Discover 2025-12-06 00:31:11 +00:00
lobehubbot 0e502e647a 🤖 chore: Lighthouse Results | Chat 2025-12-06 00:31:07 +00:00
lobehubbot 8a1a5c01db 🤖 chore: Lighthouse Results | Discover 2025-12-05 00:33:28 +00:00
lobehubbot a5cf261e49 🤖 chore: Lighthouse Results | Chat 2025-12-05 00:33:21 +00:00
lobehubbot efc7035a76 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-12-04 00:33:10 +00:00
lobehubbot 817676bb54 🤖 chore: Lighthouse Results | Discover 2025-12-04 00:33:09 +00:00
lobehubbot 375708e4e8 🤖 chore: Lighthouse Results | Chat 2025-12-04 00:33:08 +00:00
lobehubbot b0dcc8aa82 🤖 chore: Lighthouse Results | Discover 2025-12-03 00:32:43 +00:00
lobehubbot a27839dd4f 🤖 chore: Lighthouse Results | Chat 2025-12-03 00:32:37 +00:00
lobehubbot d957f0ae3f 🤖 chore: Lighthouse Results | Chat 2025-12-02 00:33:06 +00:00
lobehubbot 555436dec3 🤖 chore: Lighthouse Results | Discover 2025-12-02 00:32:58 +00:00
lobehubbot e8eaeaa694 🤖 chore: Lighthouse Results | Chat 2025-12-01 00:39:41 +00:00
lobehubbot 28157bc8fc 🤖 chore: Lighthouse Results | Discover 2025-12-01 00:39:34 +00:00
lobehubbot e17f5b4fbe 🤖 chore: Lighthouse Results | Discover 2025-11-30 00:36:50 +00:00
lobehubbot 8f4d8340e0 🤖 chore: Lighthouse Results | Chat 2025-11-30 00:36:33 +00:00
lobehubbot 750a13a415 🤖 chore: Lighthouse Results | Discover 2025-11-29 00:31:15 +00:00
lobehubbot 7365c9ccb1 🤖 chore: Lighthouse Results | Chat 2025-11-29 00:31:01 +00:00
lobehubbot 276cfa55e6 🤖 chore: Lighthouse Results | Discover 2025-11-28 00:31:34 +00:00
lobehubbot 7a5bb9d64e 🤖 chore: Lighthouse Results | Chat 2025-11-28 00:31:26 +00:00
lobehubbot cc8bc0c093 🤖 chore: Lighthouse Results | Chat 2025-11-27 00:31:55 +00:00
lobehubbot 4216dfe0fd 🤖 chore: Lighthouse Results | Discover 2025-11-27 00:31:52 +00:00
lobehubbot 2874087118 🤖 chore: Lighthouse Results | Chat 2025-11-26 00:32:21 +00:00
lobehubbot edb3b8696b 🤖 chore: Lighthouse Results | Discover 2025-11-26 00:32:15 +00:00
lobehubbot 1e846a768c Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-11-25 00:30:57 +00:00
lobehubbot 1824d7da12 🤖 chore: Lighthouse Results | Discover 2025-11-25 00:30:56 +00:00
lobehubbot b169c44199 🤖 chore: Lighthouse Results | Chat 2025-11-25 00:30:56 +00:00
lobehubbot 9deab31482 🤖 chore: Lighthouse Results | Chat 2025-11-24 00:34:35 +00:00
lobehubbot c27d05bce9 🤖 chore: Lighthouse Results | Discover 2025-11-24 00:34:29 +00:00
lobehubbot ef49458491 🤖 chore: Lighthouse Results | Chat 2025-11-23 00:36:51 +00:00
lobehubbot 578cbb14ee 🤖 chore: Lighthouse Results | Discover 2025-11-23 00:36:49 +00:00
lobehubbot ae37848e49 🤖 chore: Lighthouse Results | Chat 2025-11-22 00:30:45 +00:00
lobehubbot 1b4a22a1b1 🤖 chore: Lighthouse Results | Discover 2025-11-22 00:30:41 +00:00
lobehubbot e334f1366d 🤖 chore: Lighthouse Results | Chat 2025-11-21 00:31:56 +00:00
lobehubbot 15329206bc 🤖 chore: Lighthouse Results | Discover 2025-11-21 00:31:50 +00:00
lobehubbot 6fc7c6326e 🤖 chore: Lighthouse Results | Chat 2025-11-20 00:31:28 +00:00
lobehubbot cda1dfe9e2 🤖 chore: Lighthouse Results | Discover 2025-11-20 00:31:25 +00:00
lobehubbot 4abc0eb919 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-11-19 00:32:46 +00:00
lobehubbot 16269d0723 🤖 chore: Lighthouse Results | Discover 2025-11-19 00:32:46 +00:00
lobehubbot a9b2f56b03 🤖 chore: Lighthouse Results | Chat 2025-11-19 00:32:45 +00:00
lobehubbot 9d9a931632 🤖 chore: Lighthouse Results | Chat 2025-11-18 00:32:22 +00:00
lobehubbot ace0d5d151 🤖 chore: Lighthouse Results | Discover 2025-11-18 00:32:04 +00:00
lobehubbot c2381d4474 🤖 chore: Lighthouse Results | Chat 2025-11-17 00:33:42 +00:00
lobehubbot c94e2f528b 🤖 chore: Lighthouse Results | Discover 2025-11-17 00:33:08 +00:00
lobehubbot 779262060d 🤖 chore: Lighthouse Results | Chat 2025-11-16 00:34:55 +00:00
lobehubbot 4cf4ae6b44 🤖 chore: Lighthouse Results | Discover 2025-11-16 00:34:46 +00:00
lobehubbot 3a519c2aef 🤖 chore: Lighthouse Results | Discover 2025-11-15 00:31:07 +00:00
lobehubbot 7a1ec6bc20 🤖 chore: Lighthouse Results | Chat 2025-11-15 00:31:05 +00:00
lobehubbot 3c6148f412 🤖 chore: Lighthouse Results | Chat 2025-11-14 00:33:02 +00:00
lobehubbot cdf3decfcd 🤖 chore: Lighthouse Results | Discover 2025-11-14 00:32:57 +00:00
lobehubbot 821d485a27 🤖 chore: Lighthouse Results | Discover 2025-11-13 00:32:09 +00:00
lobehubbot 81641320bd 🤖 chore: Lighthouse Results | Chat 2025-11-13 00:32:03 +00:00
lobehubbot c106c4b027 🤖 chore: Lighthouse Results | Chat 2025-11-12 00:31:57 +00:00
lobehubbot f54618bfe4 🤖 chore: Lighthouse Results | Discover 2025-11-12 00:31:49 +00:00
lobehubbot 61e0df2121 🤖 chore: Lighthouse Results | Chat 2025-11-11 00:33:03 +00:00
lobehubbot 371ff7eb67 🤖 chore: Lighthouse Results | Discover 2025-11-11 00:32:55 +00:00
lobehubbot 2fb2ffc2c2 🤖 chore: Lighthouse Results | Chat 2025-11-10 00:34:03 +00:00
lobehubbot d37327c8a3 🤖 chore: Lighthouse Results | Discover 2025-11-10 00:33:55 +00:00
lobehubbot 6519df5652 🤖 chore: Lighthouse Results | Discover 2025-11-09 00:34:27 +00:00
lobehubbot b74d3ed7d0 🤖 chore: Lighthouse Results | Chat 2025-11-09 00:34:21 +00:00
lobehubbot 6bbaaf0f26 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-11-08 00:30:04 +00:00
lobehubbot 2228490bb8 🤖 chore: Lighthouse Results | Discover 2025-11-08 00:30:03 +00:00
lobehubbot df8b0def93 🤖 chore: Lighthouse Results | Chat 2025-11-08 00:30:02 +00:00
lobehubbot 7b54e138ad 🤖 chore: Lighthouse Results | Discover 2025-11-07 00:32:29 +00:00
lobehubbot a3de3bdaa4 🤖 chore: Lighthouse Results | Chat 2025-11-07 00:32:26 +00:00
lobehubbot 0d157adc20 🤖 chore: Lighthouse Results | Chat 2025-11-06 00:31:21 +00:00
lobehubbot 91c7720fb1 🤖 chore: Lighthouse Results | Discover 2025-11-06 00:31:15 +00:00
lobehubbot 24523e2609 🤖 chore: Lighthouse Results | Chat 2025-11-05 00:32:15 +00:00
lobehubbot 5e08d46c89 🤖 chore: Lighthouse Results | Discover 2025-11-05 00:32:05 +00:00
lobehubbot c1079edfe8 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-11-04 00:31:03 +00:00
lobehubbot b49b0f4af1 🤖 chore: Lighthouse Results | Discover 2025-11-04 00:31:01 +00:00
lobehubbot 77b3174f3a 🤖 chore: Lighthouse Results | Chat 2025-11-04 00:31:01 +00:00
lobehubbot 2782869958 🤖 chore: Lighthouse Results | Chat 2025-11-03 00:34:08 +00:00
lobehubbot c1c966680f 🤖 chore: Lighthouse Results | Discover 2025-11-03 00:33:46 +00:00
lobehubbot 6bcf49e9f5 🤖 chore: Lighthouse Results | Chat 2025-11-02 00:34:16 +00:00
lobehubbot cb4bd7e7a1 🤖 chore: Lighthouse Results | Discover 2025-11-02 00:34:01 +00:00
lobehubbot bd091f4bb5 🤖 chore: Lighthouse Results | Chat 2025-11-01 00:34:28 +00:00
lobehubbot af49445548 🤖 chore: Lighthouse Results | Discover 2025-11-01 00:33:44 +00:00
lobehubbot 19395ecf99 🤖 chore: Lighthouse Results | Chat 2025-10-31 00:31:06 +00:00
lobehubbot 916f4b006f 🤖 chore: Lighthouse Results | Discover 2025-10-31 00:30:55 +00:00
lobehubbot fd7bac5443 🤖 chore: Lighthouse Results | Discover 2025-10-30 00:32:07 +00:00
lobehubbot 39433f3f3f 🤖 chore: Lighthouse Results | Chat 2025-10-30 00:32:02 +00:00
lobehubbot 6f8070628b 🤖 chore: Lighthouse Results | Discover 2025-10-29 00:32:24 +00:00
lobehubbot 118ccc9b9e 🤖 chore: Lighthouse Results | Chat 2025-10-29 00:32:17 +00:00
lobehubbot 01de69a238 🤖 chore: Lighthouse Results | Chat 2025-10-28 00:30:03 +00:00
lobehubbot f307ea0b3d 🤖 chore: Lighthouse Results | Discover 2025-10-28 00:30:02 +00:00
lobehubbot 2e298cbbe9 🤖 chore: Lighthouse Results | Chat 2025-10-27 00:34:21 +00:00
lobehubbot 70315fb46d 🤖 chore: Lighthouse Results | Discover 2025-10-27 00:34:19 +00:00
lobehubbot a1add00fe6 🤖 chore: Lighthouse Results | Chat 2025-10-26 00:33:32 +00:00
lobehubbot 4877873ee0 🤖 chore: Lighthouse Results | Discover 2025-10-26 00:33:18 +00:00
lobehubbot e286416466 🤖 chore: Lighthouse Results | Chat 2025-10-25 00:30:01 +00:00
lobehubbot e00e12716f 🤖 chore: Lighthouse Results | Discover 2025-10-25 00:29:58 +00:00
lobehubbot ff7f0b79ab 🤖 chore: Lighthouse Results | Chat 2025-10-24 00:28:27 +00:00
lobehubbot 9d93608c5a 🤖 chore: Lighthouse Results | Discover 2025-10-24 00:28:22 +00:00
lobehubbot 024133d56b 🤖 chore: Lighthouse Results | Discover 2025-10-23 00:31:04 +00:00
lobehubbot f56ad69359 🤖 chore: Lighthouse Results | Chat 2025-10-23 00:30:44 +00:00
lobehubbot ba1f0d3d89 🤖 chore: Lighthouse Results | Chat 2025-10-22 00:32:00 +00:00
lobehubbot 5c617b0a76 🤖 chore: Lighthouse Results | Discover 2025-10-22 00:31:58 +00:00
lobehubbot e6e667d788 🤖 chore: Lighthouse Results | Discover 2025-10-21 00:31:21 +00:00
lobehubbot 510d928ccc 🤖 chore: Lighthouse Results | Chat 2025-10-21 00:30:59 +00:00
lobehubbot 9b20c512e2 🤖 chore: Lighthouse Results | Discover 2025-10-20 00:33:30 +00:00
lobehubbot f8a63c4d41 🤖 chore: Lighthouse Results | Chat 2025-10-20 00:33:18 +00:00
lobehubbot acd68498c6 🤖 chore: Lighthouse Results | Discover 2025-10-19 00:34:13 +00:00
lobehubbot 5dca245242 🤖 chore: Lighthouse Results | Chat 2025-10-19 00:34:04 +00:00
lobehubbot ef74425651 🤖 chore: Lighthouse Results | Chat 2025-10-18 00:28:39 +00:00
lobehubbot 307f197ed2 🤖 chore: Lighthouse Results | Discover 2025-10-18 00:28:13 +00:00
lobehubbot 64e90fd2c6 🤖 chore: Lighthouse Results | Discover 2025-10-17 00:30:38 +00:00
lobehubbot 36b2079038 🤖 chore: Lighthouse Results | Chat 2025-10-17 00:30:35 +00:00
lobehubbot 858993b67f 🤖 chore: Lighthouse Results | Chat 2025-10-16 00:30:58 +00:00
lobehubbot 24e629ba16 🤖 chore: Lighthouse Results | Discover 2025-10-16 00:30:56 +00:00
lobehubbot 3d3fb71b38 🤖 chore: Lighthouse Results | Chat 2025-10-15 00:31:06 +00:00
lobehubbot c1a5d0ab45 🤖 chore: Lighthouse Results | Discover 2025-10-15 00:31:05 +00:00
lobehubbot edf5e75068 🤖 chore: Lighthouse Results | Discover 2025-10-14 00:29:36 +00:00
lobehubbot 51d49e14e1 🤖 chore: Lighthouse Results | Chat 2025-10-14 00:29:33 +00:00
lobehubbot 8acf1d0b39 🤖 chore: Lighthouse Results | Chat 2025-10-13 00:32:28 +00:00
lobehubbot 93a30cf25c 🤖 chore: Lighthouse Results | Discover 2025-10-13 00:32:23 +00:00
lobehubbot 5fb19da544 🤖 chore: Lighthouse Results | Chat 2025-10-12 00:31:36 +00:00
lobehubbot 1ccf28c63c 🤖 chore: Lighthouse Results | Discover 2025-10-12 00:31:35 +00:00
lobehubbot 079d5ac82a 🤖 chore: Lighthouse Results | Chat 2025-10-11 00:28:28 +00:00
lobehubbot a59263224f 🤖 chore: Lighthouse Results | Discover 2025-10-11 00:28:10 +00:00
lobehubbot 3ce376c89f 🤖 chore: Lighthouse Results | Discover 2025-10-10 00:30:13 +00:00
lobehubbot bbf23bf882 🤖 chore: Lighthouse Results | Chat 2025-10-10 00:30:12 +00:00
lobehubbot 6bbde48bfa 🤖 chore: Lighthouse Results | Chat 2025-10-09 00:29:48 +00:00
lobehubbot a7e9de58ba 🤖 chore: Lighthouse Results | Discover 2025-10-09 00:29:46 +00:00
lobehubbot dd4e3af756 🤖 chore: Lighthouse Results | Discover 2025-10-08 00:29:50 +00:00
lobehubbot 916e7ab617 🤖 chore: Lighthouse Results | Chat 2025-10-08 00:29:44 +00:00
lobehubbot 7f5ad99405 🤖 chore: Lighthouse Results | Discover 2025-10-07 00:29:55 +00:00
lobehubbot ea7ba31fb2 🤖 chore: Lighthouse Results | Chat 2025-10-07 00:29:48 +00:00
lobehubbot e064b82767 🤖 chore: Lighthouse Results | Chat 2025-10-06 00:30:46 +00:00
lobehubbot 0ba3a16ca8 🤖 chore: Lighthouse Results | Discover 2025-10-06 00:30:34 +00:00
lobehubbot bbff88a518 🤖 chore: Lighthouse Results | Discover 2025-10-05 00:33:11 +00:00
lobehubbot 4fbad3cf1f 🤖 chore: Lighthouse Results | Chat 2025-10-05 00:33:04 +00:00
lobehubbot 64d9446090 🤖 chore: Lighthouse Results | Chat 2025-10-04 00:28:12 +00:00
lobehubbot 79ec4e158c 🤖 chore: Lighthouse Results | Discover 2025-10-04 00:28:04 +00:00
lobehubbot 0df5551eda 🤖 chore: Lighthouse Results | Discover 2025-10-03 00:29:47 +00:00
lobehubbot 1344561ce7 🤖 chore: Lighthouse Results | Chat 2025-10-03 00:29:06 +00:00
lobehubbot 039be6a829 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-10-02 00:28:50 +00:00
lobehubbot c6e5139a8c 🤖 chore: Lighthouse Results | Chat 2025-10-02 00:28:49 +00:00
lobehubbot a34cc1ef46 🤖 chore: Lighthouse Results | Discover 2025-10-02 00:28:48 +00:00
lobehubbot c7d864e76c 🤖 chore: Lighthouse Results | Chat 2025-10-01 00:36:00 +00:00
lobehubbot 310e55bb29 🤖 chore: Lighthouse Results | Discover 2025-10-01 00:34:13 +00:00
lobehubbot ba8eed936a 🤖 chore: Lighthouse Results | Discover 2025-09-30 00:30:19 +00:00
lobehubbot f3abad510c 🤖 chore: Lighthouse Results | Chat 2025-09-30 00:30:11 +00:00
lobehubbot 1fca981e6e 🤖 chore: Lighthouse Results | Chat 2025-09-29 00:31:28 +00:00
lobehubbot 4d8e20f272 🤖 chore: Lighthouse Results | Discover 2025-09-29 00:31:24 +00:00
lobehubbot da8caec3db 🤖 chore: Lighthouse Results | Chat 2025-09-28 00:33:39 +00:00
lobehubbot 0c4d45f81f 🤖 chore: Lighthouse Results | Discover 2025-09-28 00:33:35 +00:00
lobehubbot 25e67cc31f 🤖 chore: Lighthouse Results | Discover 2025-09-27 00:29:00 +00:00
lobehubbot 7dfff109ae 🤖 chore: Lighthouse Results | Chat 2025-09-27 00:28:58 +00:00
lobehubbot a0dd2fe4df 🤖 chore: Lighthouse Results | Chat 2025-09-26 00:29:36 +00:00
lobehubbot ba10b1b964 🤖 chore: Lighthouse Results | Discover 2025-09-26 00:29:00 +00:00
lobehubbot bbc4851ef7 🤖 chore: Lighthouse Results | Chat 2025-09-25 00:29:55 +00:00
lobehubbot 3a55355e59 🤖 chore: Lighthouse Results | Discover 2025-09-25 00:29:50 +00:00
lobehubbot 663dd6c094 🤖 chore: Lighthouse Results | Chat 2025-09-24 00:30:12 +00:00
lobehubbot 4ce5bb6357 🤖 chore: Lighthouse Results | Discover 2025-09-24 00:30:02 +00:00
lobehubbot daf82da3ef 🤖 chore: Lighthouse Results | Discover 2025-09-23 00:29:25 +00:00
lobehubbot bc25d75e52 🤖 chore: Lighthouse Results | Chat 2025-09-23 00:29:21 +00:00
lobehubbot 1fb70a4324 🤖 chore: Lighthouse Results | Chat 2025-09-22 00:33:17 +00:00
lobehubbot e9d18699b6 🤖 chore: Lighthouse Results | Discover 2025-09-22 00:32:53 +00:00
lobehubbot dfb2afe684 🤖 chore: Lighthouse Results | Chat 2025-09-21 00:32:50 +00:00
lobehubbot e7ba61dd4a 🤖 chore: Lighthouse Results | Discover 2025-09-21 00:32:43 +00:00
lobehubbot dcbf077e86 🤖 chore: Lighthouse Results | Discover 2025-09-20 00:28:26 +00:00
lobehubbot 6d76af0d3b 🤖 chore: Lighthouse Results | Chat 2025-09-20 00:28:09 +00:00
lobehubbot e034278892 🤖 chore: Lighthouse Results | Chat 2025-09-19 00:29:58 +00:00
lobehubbot 0d6b7d641d 🤖 chore: Lighthouse Results | Discover 2025-09-19 00:29:46 +00:00
lobehubbot a789fe32f9 🤖 chore: Lighthouse Results | Chat 2025-09-18 00:29:04 +00:00
lobehubbot b0c090674f 🤖 chore: Lighthouse Results | Discover 2025-09-18 00:29:02 +00:00
lobehubbot aeece29a95 🤖 chore: Lighthouse Results | Chat 2025-09-17 00:29:42 +00:00
lobehubbot b4fc522ea8 🤖 chore: Lighthouse Results | Discover 2025-09-17 00:29:39 +00:00
lobehubbot ec1b4e1321 🤖 chore: Lighthouse Results | Chat 2025-09-16 00:28:50 +00:00
lobehubbot b2b54ec238 🤖 chore: Lighthouse Results | Discover 2025-09-16 00:28:48 +00:00
lobehubbot 33dcd42a1a 🤖 chore: Lighthouse Results | Chat 2025-09-15 00:32:27 +00:00
lobehubbot 13d69ee3dc 🤖 chore: Lighthouse Results | Discover 2025-09-15 00:32:22 +00:00
lobehubbot 11541f4030 🤖 chore: Lighthouse Results | Chat 2025-09-14 00:31:52 +00:00
lobehubbot 5e0efc472d 🤖 chore: Lighthouse Results | Discover 2025-09-14 00:31:41 +00:00
lobehubbot 0d166eb89a 🤖 chore: Lighthouse Results | Discover 2025-09-13 00:27:36 +00:00
lobehubbot e0e40639bf 🤖 chore: Lighthouse Results | Chat 2025-09-13 00:27:25 +00:00
lobehubbot 4b6a1fcb8f 🤖 chore: Lighthouse Results | Chat 2025-09-12 00:29:21 +00:00
lobehubbot 92fc4b645f 🤖 chore: Lighthouse Results | Discover 2025-09-12 00:29:17 +00:00
lobehubbot ee6cfaa7d5 🤖 chore: Lighthouse Results | Discover 2025-09-11 00:30:05 +00:00
lobehubbot 62f1a74c36 🤖 chore: Lighthouse Results | Chat 2025-09-11 00:29:52 +00:00
lobehubbot ec244c3d39 🤖 chore: Lighthouse Results | Chat 2025-09-10 00:29:27 +00:00
lobehubbot e63e9c34ac 🤖 chore: Lighthouse Results | Discover 2025-09-10 00:29:20 +00:00
lobehubbot a3c77be76d 🤖 chore: Lighthouse Results | Discover 2025-09-09 00:30:27 +00:00
lobehubbot 756d93d06a 🤖 chore: Lighthouse Results | Chat 2025-09-09 00:30:20 +00:00
lobehubbot 4dc5dbd403 🤖 chore: Lighthouse Results | Chat 2025-09-08 00:31:57 +00:00
lobehubbot 7d4a8ca233 🤖 chore: Lighthouse Results | Discover 2025-09-08 00:31:46 +00:00
lobehubbot a499c51f32 🤖 chore: Lighthouse Results | Chat 2025-09-07 00:32:30 +00:00
lobehubbot 003ed9bbcc 🤖 chore: Lighthouse Results | Discover 2025-09-07 00:32:23 +00:00
lobehubbot 9b2b79a333 🤖 chore: Lighthouse Results | Discover 2025-09-06 00:28:10 +00:00
lobehubbot 904f9d4549 🤖 chore: Lighthouse Results | Chat 2025-09-06 00:28:08 +00:00
lobehubbot bc18c1c108 🤖 chore: Lighthouse Results | Discover 2025-09-05 00:29:44 +00:00
lobehubbot 09594892ca 🤖 chore: Lighthouse Results | Chat 2025-09-05 00:29:41 +00:00
lobehubbot fffa05d3dc 🤖 chore: Lighthouse Results | Discover 2025-09-04 00:28:46 +00:00
lobehubbot 7807d16244 🤖 chore: Lighthouse Results | Chat 2025-09-04 00:28:41 +00:00
lobehubbot 002a879d80 🤖 chore: Lighthouse Results | Discover 2025-09-03 00:28:48 +00:00
lobehubbot 0d283c1a7e 🤖 chore: Lighthouse Results | Chat 2025-09-03 00:28:45 +00:00
lobehubbot f6c2819c03 🤖 chore: Lighthouse Results | Discover 2025-09-02 00:30:33 +00:00
lobehubbot 333c6e99c5 🤖 chore: Lighthouse Results | Chat 2025-09-02 00:30:30 +00:00
lobehubbot 4700bdf221 🤖 chore: Lighthouse Results | Chat 2025-09-01 00:37:41 +00:00
lobehubbot f909e9b787 🤖 chore: Lighthouse Results | Discover 2025-09-01 00:37:38 +00:00
lobehubbot 822e0a472a 🤖 chore: Lighthouse Results | Chat 2025-08-31 00:33:19 +00:00
lobehubbot d0c0dd3894 🤖 chore: Lighthouse Results | Discover 2025-08-31 00:33:18 +00:00
lobehubbot 6f42ab690c 🤖 chore: Lighthouse Results | Discover 2025-08-30 00:29:00 +00:00
lobehubbot 4e72bd8bf0 🤖 chore: Lighthouse Results | Chat 2025-08-30 00:28:43 +00:00
lobehubbot d9adfc5323 🤖 chore: Lighthouse Results | Chat 2025-08-29 00:30:34 +00:00
lobehubbot 921276d158 🤖 chore: Lighthouse Results | Discover 2025-08-29 00:30:19 +00:00
lobehubbot 34726280fd 🤖 chore: Lighthouse Results | Discover 2025-08-28 00:30:41 +00:00
lobehubbot 38fe01a37a 🤖 chore: Lighthouse Results | Chat 2025-08-28 00:30:36 +00:00
lobehubbot 8e8b1e610b 🤖 chore: Lighthouse Results | Discover 2025-08-27 00:30:29 +00:00
lobehubbot 79f58ea0fe 🤖 chore: Lighthouse Results | Chat 2025-08-27 00:30:28 +00:00
lobehubbot e032f11801 🤖 chore: Lighthouse Results | Chat 2025-08-26 00:30:54 +00:00
lobehubbot 64af0b4be1 🤖 chore: Lighthouse Results | Discover 2025-08-26 00:30:49 +00:00
lobehubbot 1c5fa664fd 🤖 chore: Lighthouse Results | Chat 2025-08-25 00:32:38 +00:00
lobehubbot 0e3ecda2e0 🤖 chore: Lighthouse Results | Discover 2025-08-25 00:32:32 +00:00
lobehubbot 5064f3e823 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-08-24 00:34:19 +00:00
lobehubbot eeee60a65a 🤖 chore: Lighthouse Results | Discover 2025-08-24 00:34:18 +00:00
lobehubbot b7a803dd1f 🤖 chore: Lighthouse Results | Chat 2025-08-24 00:34:18 +00:00
lobehubbot 5770a3ec32 🤖 chore: Lighthouse Results | Discover 2025-08-23 00:29:42 +00:00
lobehubbot 0fbdafb4f3 🤖 chore: Lighthouse Results | Chat 2025-08-23 00:29:38 +00:00
lobehubbot c8b47d573d 🤖 chore: Lighthouse Results | Chat 2025-08-22 00:30:54 +00:00
lobehubbot 338ba5a0cc 🤖 chore: Lighthouse Results | Discover 2025-08-22 00:30:42 +00:00
lobehubbot dd66245c6b 🤖 chore: Lighthouse Results | Chat 2025-08-21 00:29:57 +00:00
lobehubbot f0025b9e47 🤖 chore: Lighthouse Results | Discover 2025-08-21 00:29:51 +00:00
lobehubbot e2be98aa10 🤖 chore: Lighthouse Results | Discover 2025-08-20 00:30:47 +00:00
lobehubbot 6f551b177e 🤖 chore: Lighthouse Results | Chat 2025-08-20 00:30:39 +00:00
lobehubbot 4e447ff52b 🤖 chore: Lighthouse Results | Chat 2025-08-19 00:32:05 +00:00
lobehubbot 626796e658 🤖 chore: Lighthouse Results | Discover 2025-08-19 00:31:59 +00:00
lobehubbot b7c8f413f2 🤖 chore: Lighthouse Results | Discover 2025-08-18 00:35:31 +00:00
lobehubbot 417ed2bf64 🤖 chore: Lighthouse Results | Chat 2025-08-18 00:35:24 +00:00
lobehubbot bbfc96be97 🤖 chore: Lighthouse Results | Chat 2025-08-17 00:35:41 +00:00
lobehubbot f7876e23bb 🤖 chore: Lighthouse Results | Discover 2025-08-17 00:35:35 +00:00
lobehubbot e981610612 🤖 chore: Lighthouse Results | Chat 2025-08-16 00:31:07 +00:00
lobehubbot d014bf3e2d 🤖 chore: Lighthouse Results | Discover 2025-08-16 00:31:02 +00:00
lobehubbot ccc131cfcf 🤖 chore: Lighthouse Results | Chat 2025-08-15 00:33:35 +00:00
lobehubbot d7282d63be 🤖 chore: Lighthouse Results | Discover 2025-08-15 00:33:29 +00:00
lobehubbot dee0cbc74f 🤖 chore: Lighthouse Results | Chat 2025-08-14 00:32:51 +00:00
lobehubbot 4741b5c4fa 🤖 chore: Lighthouse Results | Discover 2025-08-14 00:32:29 +00:00
lobehubbot cf95fa97bc 🤖 chore: Lighthouse Results | Discover 2025-08-13 00:33:11 +00:00
lobehubbot e7b94c3c24 🤖 chore: Lighthouse Results | Chat 2025-08-13 00:33:06 +00:00
lobehubbot a2f311ff75 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-08-12 00:32:25 +00:00
lobehubbot 7791f8a62b 🤖 chore: Lighthouse Results | Chat 2025-08-12 00:32:24 +00:00
lobehubbot 1aa84f297b 🤖 chore: Lighthouse Results | Discover 2025-08-12 00:32:23 +00:00
lobehubbot f085f333d1 🤖 chore: Lighthouse Results | Chat 2025-08-11 00:36:29 +00:00
lobehubbot 5c772ca9a6 🤖 chore: Lighthouse Results | Discover 2025-08-11 00:36:12 +00:00
lobehubbot 1ac41290ef 🤖 chore: Lighthouse Results | Chat 2025-08-10 00:37:36 +00:00
lobehubbot da518bde90 🤖 chore: Lighthouse Results | Chat 2025-08-09 00:31:58 +00:00
lobehubbot 0be6a9c532 🤖 chore: Lighthouse Results | Discover 2025-08-09 00:31:51 +00:00
lobehubbot d772381244 🤖 chore: Lighthouse Results | Chat 2025-08-08 00:35:00 +00:00
lobehubbot da4afdcac3 🤖 chore: Lighthouse Results | Discover 2025-08-08 00:34:45 +00:00
lobehubbot d036d5cdb8 🤖 chore: Lighthouse Results | Discover 2025-08-07 00:35:31 +00:00
lobehubbot bff9afdd13 🤖 chore: Lighthouse Results | Chat 2025-08-07 00:35:22 +00:00
lobehubbot fecf5077e7 🤖 chore: Lighthouse Results | Discover 2025-08-06 00:35:13 +00:00
lobehubbot 814b0491ec 🤖 chore: Lighthouse Results | Chat 2025-08-06 00:35:06 +00:00
lobehubbot 8b4696735a 🤖 chore: Lighthouse Results | Chat 2025-08-05 00:35:24 +00:00
lobehubbot 0b8002e336 🤖 chore: Lighthouse Results | Discover 2025-08-05 00:35:16 +00:00
lobehubbot 4dfe569c91 🤖 chore: Lighthouse Results | Discover 2025-08-04 00:38:11 +00:00
lobehubbot b2ea1214c2 🤖 chore: Lighthouse Results | Chat 2025-08-04 00:37:57 +00:00
lobehubbot f35a630d0e 🤖 chore: Lighthouse Results | Chat 2025-08-03 00:38:47 +00:00
lobehubbot f2f4c918bb 🤖 chore: Lighthouse Results | Discover 2025-08-03 00:38:42 +00:00
lobehubbot 6059fffbd5 🤖 chore: Lighthouse Results | Chat 2025-08-02 00:33:23 +00:00
lobehubbot e4c133b32e 🤖 chore: Lighthouse Results | Discover 2025-08-02 00:33:16 +00:00
lobehubbot 390609db14 🤖 chore: Lighthouse Results | Chat 2025-08-01 00:39:32 +00:00
lobehubbot 320b4a9596 🤖 chore: Lighthouse Results | Discover 2025-08-01 00:39:13 +00:00
lobehubbot 6a0fe2da6e 🤖 chore: Lighthouse Results | Chat 2025-07-31 00:34:42 +00:00
lobehubbot 6dc810da53 🤖 chore: Lighthouse Results | Discover 2025-07-31 00:34:37 +00:00
lobehubbot ff520aaa5a 🤖 chore: Lighthouse Results | Chat 2025-07-30 00:34:46 +00:00
lobehubbot f29d29beaf 🤖 chore: Lighthouse Results | Discover 2025-07-30 00:34:39 +00:00
lobehubbot e7461142e2 🤖 chore: Lighthouse Results | Chat 2025-07-29 00:37:37 +00:00
lobehubbot 4642082840 🤖 chore: Lighthouse Results | Discover 2025-07-29 00:37:26 +00:00
lobehubbot 9aca0102f2 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-07-28 00:37:03 +00:00
lobehubbot faf2cb6a98 🤖 chore: Lighthouse Results | Discover 2025-07-28 00:37:02 +00:00
lobehubbot 2acb157fff 🤖 chore: Lighthouse Results | Chat 2025-07-28 00:37:01 +00:00
lobehubbot a3c1ef816f 🤖 chore: Lighthouse Results | Chat 2025-07-27 00:37:53 +00:00
lobehubbot cb6086a548 🤖 chore: Lighthouse Results | Discover 2025-07-27 00:37:46 +00:00
lobehubbot 0cdd64148f 🤖 chore: Lighthouse Results | Discover 2025-07-26 00:33:06 +00:00
lobehubbot 4d5eed63d9 🤖 chore: Lighthouse Results | Chat 2025-07-26 00:32:59 +00:00
lobehubbot 9193eee9e0 🤖 chore: Lighthouse Results | Discover 2025-07-25 00:34:48 +00:00
lobehubbot b6eda769a8 🤖 chore: Lighthouse Results | Chat 2025-07-25 00:34:24 +00:00
lobehubbot 1684ff73af 🤖 chore: Lighthouse Results | Chat 2025-07-24 00:34:20 +00:00
lobehubbot 79d7f828cf 🤖 chore: Lighthouse Results | Discover 2025-07-24 00:34:14 +00:00
lobehubbot 9795d0fde4 🤖 chore: Lighthouse Results | Discover 2025-07-23 00:34:41 +00:00
lobehubbot c52f2096f6 🤖 chore: Lighthouse Results | Chat 2025-07-23 00:34:17 +00:00
lobehubbot 223e258109 🤖 chore: Lighthouse Results | Discover 2025-07-22 00:34:23 +00:00
lobehubbot fbaaaa31cf 🤖 chore: Lighthouse Results | Chat 2025-07-22 00:34:16 +00:00
lobehubbot 2b81c475b9 🤖 chore: Lighthouse Results | Chat 2025-07-21 00:36:42 +00:00
lobehubbot 6864c228d1 🤖 chore: Lighthouse Results | Discover 2025-07-21 00:36:36 +00:00
lobehubbot cbaa9e24dc 🤖 chore: Lighthouse Results | Chat 2025-07-20 00:38:03 +00:00
lobehubbot af6ad8827e 🤖 chore: Lighthouse Results | Discover 2025-07-20 00:37:55 +00:00
lobehubbot b5c1d686a6 🤖 chore: Lighthouse Results | Discover 2025-07-19 00:33:15 +00:00
lobehubbot 82c03e5240 🤖 chore: Lighthouse Results | Chat 2025-07-19 00:33:11 +00:00
lobehubbot d86090b8e4 🤖 chore: Lighthouse Results | Chat 2025-07-18 00:34:03 +00:00
lobehubbot 9f2c174680 🤖 chore: Lighthouse Results | Discover 2025-07-18 00:33:25 +00:00
lobehubbot 67d5446aed 🤖 chore: Lighthouse Results | Chat 2025-07-17 00:34:13 +00:00
lobehubbot 34c6c1be15 🤖 chore: Lighthouse Results | Discover 2025-07-17 00:34:08 +00:00
lobehubbot 85ff0913e8 🤖 chore: Lighthouse Results | Discover 2025-07-16 00:33:41 +00:00
lobehubbot 9e1604c1c4 🤖 chore: Lighthouse Results | Chat 2025-07-16 00:33:34 +00:00
lobehubbot 4e6dac09e3 🤖 chore: Lighthouse Results | Discover 2025-07-15 00:34:29 +00:00
lobehubbot 50199b8921 🤖 chore: Lighthouse Results | Chat 2025-07-15 00:34:26 +00:00
lobehubbot c7282fe6ce Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-07-14 00:35:38 +00:00
lobehubbot 882078d396 🤖 chore: Lighthouse Results | Chat 2025-07-14 00:35:37 +00:00
lobehubbot 92219b6458 🤖 chore: Lighthouse Results | Discover 2025-07-14 00:35:36 +00:00
lobehubbot 146a4ad380 🤖 chore: Lighthouse Results | Chat 2025-07-13 00:37:14 +00:00
lobehubbot c2e08a2a3c 🤖 chore: Lighthouse Results | Discover 2025-07-13 00:37:11 +00:00
lobehubbot 28a8ae5448 🤖 chore: Lighthouse Results | Discover 2025-07-12 00:33:52 +00:00
lobehubbot f6079b452c 🤖 chore: Lighthouse Results | Chat 2025-07-12 00:33:28 +00:00
lobehubbot 944d9d5477 🤖 chore: Lighthouse Results | Chat 2025-07-11 00:33:21 +00:00
lobehubbot ffc08ec828 🤖 chore: Lighthouse Results | Discover 2025-07-11 00:33:17 +00:00
lobehubbot bee2123550 🤖 chore: Lighthouse Results | Chat 2025-07-10 00:33:00 +00:00
lobehubbot 089201500b 🤖 chore: Lighthouse Results | Discover 2025-07-10 00:32:56 +00:00
lobehubbot f83e7ad77b 🤖 chore: Lighthouse Results | Discover 2025-07-09 00:33:51 +00:00
lobehubbot eae9f3c5f6 🤖 chore: Lighthouse Results | Chat 2025-07-09 00:33:44 +00:00
lobehubbot 5176aa19ad 🤖 chore: Lighthouse Results | Chat 2025-07-08 00:33:16 +00:00
lobehubbot 7abb237b60 🤖 chore: Lighthouse Results | Discover 2025-07-08 00:32:42 +00:00
lobehubbot 8d495cd25e 🤖 chore: Lighthouse Results | Discover 2025-07-07 00:35:17 +00:00
lobehubbot 4b7e40d075 🤖 chore: Lighthouse Results | Chat 2025-07-07 00:35:10 +00:00
lobehubbot b78c2fb864 🤖 chore: Lighthouse Results | Chat 2025-07-06 00:36:40 +00:00
lobehubbot 15f253c767 🤖 chore: Lighthouse Results | Discover 2025-07-06 00:36:20 +00:00
lobehubbot fcc6944556 🤖 chore: Lighthouse Results | Discover 2025-07-05 00:31:12 +00:00
lobehubbot 94cf7c4cfc 🤖 chore: Lighthouse Results | Chat 2025-07-05 00:31:08 +00:00
lobehubbot 9222c33463 🤖 chore: Lighthouse Results | Discover 2025-07-04 00:32:33 +00:00
lobehubbot 2398f54851 🤖 chore: Lighthouse Results | Chat 2025-07-04 00:32:28 +00:00
lobehubbot 5f04e6d5ab 🤖 chore: Lighthouse Results | Chat 2025-07-03 00:32:40 +00:00
lobehubbot f0fe705aa3 🤖 chore: Lighthouse Results | Discover 2025-07-03 00:32:37 +00:00
lobehubbot e9d2e78c18 🤖 chore: Lighthouse Results | Discover 2025-07-02 00:32:37 +00:00
lobehubbot 2f2d157f90 🤖 chore: Lighthouse Results | Chat 2025-07-02 00:32:18 +00:00
lobehubbot 47fec542b2 🤖 chore: Lighthouse Results | Discover 2025-07-01 00:38:17 +00:00
lobehubbot 8f3499d19a 🤖 chore: Lighthouse Results | Chat 2025-07-01 00:37:22 +00:00
lobehubbot 180f055991 🤖 chore: Lighthouse Results | Discover 2025-06-30 00:35:27 +00:00
lobehubbot 437a4c158a 🤖 chore: Lighthouse Results | Chat 2025-06-30 00:35:22 +00:00
lobehubbot 4f6c4c038b 🤖 chore: Lighthouse Results | Chat 2025-06-29 00:36:49 +00:00
lobehubbot 9a4f561214 🤖 chore: Lighthouse Results | Discover 2025-06-29 00:36:43 +00:00
lobehubbot f32d61dd41 🤖 chore: Lighthouse Results | Discover 2025-06-28 00:31:16 +00:00
lobehubbot cd93cbd65f 🤖 chore: Lighthouse Results | Chat 2025-06-28 00:31:12 +00:00
lobehubbot 51e66e95d4 🤖 chore: Lighthouse Results | Discover 2025-06-27 00:32:43 +00:00
lobehubbot d45a43d861 🤖 chore: Lighthouse Results | Chat 2025-06-27 00:32:41 +00:00
lobehubbot 50c3573626 🤖 chore: Lighthouse Results | Discover 2025-06-26 00:32:30 +00:00
lobehubbot 8ac866e7b8 🤖 chore: Lighthouse Results | Chat 2025-06-26 00:32:08 +00:00
lobehubbot 4a1c9a0e2f 🤖 chore: Lighthouse Results | Discover 2025-06-25 00:32:47 +00:00
lobehubbot 2d326dcc36 🤖 chore: Lighthouse Results | Chat 2025-06-25 00:32:34 +00:00
lobehubbot 6d1dbc7fef 🤖 chore: Lighthouse Results | Discover 2025-06-24 00:32:24 +00:00
lobehubbot d3c8577f83 🤖 chore: Lighthouse Results | Chat 2025-06-24 00:32:16 +00:00
lobehubbot 83b930fc67 🤖 chore: Lighthouse Results | Discover 2025-06-23 00:35:08 +00:00
lobehubbot 33a4dd58e5 🤖 chore: Lighthouse Results | Chat 2025-06-23 00:34:53 +00:00
lobehubbot 86d848c277 🤖 chore: Lighthouse Results | Discover 2025-06-22 00:36:03 +00:00
lobehubbot f8b31fb17b 🤖 chore: Lighthouse Results | Chat 2025-06-22 00:35:53 +00:00
lobehubbot aabb8b4e5b 🤖 chore: Lighthouse Results | Discover 2025-06-21 00:32:00 +00:00
lobehubbot 0adc475330 🤖 chore: Lighthouse Results | Chat 2025-06-21 00:31:48 +00:00
lobehubbot 5725042837 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-06-20 00:32:05 +00:00
lobehubbot c4eab621a8 🤖 chore: Lighthouse Results | Chat 2025-06-20 00:32:03 +00:00
lobehubbot c5bb486113 🤖 chore: Lighthouse Results | Discover 2025-06-20 00:32:03 +00:00
lobehubbot 34723c2b6b 🤖 chore: Lighthouse Results | Chat 2025-06-19 00:32:46 +00:00
lobehubbot beeb50accb 🤖 chore: Lighthouse Results | Discover 2025-06-19 00:32:38 +00:00
lobehubbot afdf26c25c 🤖 chore: Lighthouse Results | Chat 2025-06-18 00:32:17 +00:00
lobehubbot f40f3fd1dd 🤖 chore: Lighthouse Results | Discover 2025-06-18 00:32:13 +00:00
lobehubbot 7e3b1abbef 🤖 chore: Lighthouse Results | Discover 2025-06-17 00:32:21 +00:00
lobehubbot 947d1c13f7 🤖 chore: Lighthouse Results | Chat 2025-06-17 00:32:09 +00:00
lobehubbot 4939e4fd75 🤖 chore: Lighthouse Results | Discover 2025-06-16 00:34:02 +00:00
lobehubbot 44bee884d2 🤖 chore: Lighthouse Results | Chat 2025-06-16 00:34:00 +00:00
lobehubbot 53e65a578e 🤖 chore: Lighthouse Results | Discover 2025-06-15 00:36:21 +00:00
lobehubbot da3246e301 🤖 chore: Lighthouse Results | Chat 2025-06-15 00:36:00 +00:00
lobehubbot 8cf14549b8 🤖 chore: Lighthouse Results | Chat 2025-06-14 00:31:30 +00:00
lobehubbot 81b1cd6f9e 🤖 chore: Lighthouse Results | Discover 2025-06-14 00:31:25 +00:00
lobehubbot c5baf2488a 🤖 chore: Lighthouse Results | Chat 2025-06-13 00:32:38 +00:00
lobehubbot 6d2303ce5b 🤖 chore: Lighthouse Results | Discover 2025-06-13 00:32:31 +00:00
lobehubbot 906c52286f 🤖 chore: Lighthouse Results | Chat 2025-06-12 00:32:08 +00:00
lobehubbot af03a7b957 🤖 chore: Lighthouse Results | Discover 2025-06-12 00:31:50 +00:00
lobehubbot b2afb11f5e 🤖 chore: Lighthouse Results | Chat 2025-06-11 00:32:28 +00:00
lobehubbot 42729eed91 🤖 chore: Lighthouse Results | Discover 2025-06-11 00:32:15 +00:00
lobehubbot 1e64aab52a 🤖 chore: Lighthouse Results | Discover 2025-06-10 00:32:14 +00:00
lobehubbot 3108bbc464 🤖 chore: Lighthouse Results | Chat 2025-06-10 00:32:10 +00:00
lobehubbot 4b225c524a 🤖 chore: Lighthouse Results | Discover 2025-06-09 00:34:42 +00:00
lobehubbot 2c5614f662 🤖 chore: Lighthouse Results | Chat 2025-06-09 00:34:17 +00:00
lobehubbot 060ac7723f 🤖 chore: Lighthouse Results | Chat 2025-06-08 00:35:51 +00:00
lobehubbot 3c191c4eab 🤖 chore: Lighthouse Results | Discover 2025-06-08 00:35:39 +00:00
lobehubbot cf930bfcd5 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-06-07 00:31:53 +00:00
lobehubbot b8ae646336 🤖 chore: Lighthouse Results | Discover 2025-06-07 00:31:52 +00:00
lobehubbot 25b12f0928 🤖 chore: Lighthouse Results | Chat 2025-06-07 00:31:52 +00:00
lobehubbot fd63a568a0 🤖 chore: Lighthouse Results | Discover 2025-06-06 00:31:52 +00:00
lobehubbot ca9d991c26 🤖 chore: Lighthouse Results | Chat 2025-06-06 00:31:40 +00:00
lobehubbot 673adc9df7 🤖 chore: Lighthouse Results | Discover 2025-06-05 00:31:53 +00:00
lobehubbot 9ba715aef5 🤖 chore: Lighthouse Results | Chat 2025-06-05 00:31:46 +00:00
lobehubbot 58f12782be 🤖 chore: Lighthouse Results | Discover 2025-06-04 00:32:33 +00:00
lobehubbot 1c75d6703d 🤖 chore: Lighthouse Results | Chat 2025-06-04 00:32:23 +00:00
lobehubbot 342d634772 🤖 chore: Lighthouse Results | Discover 2025-06-03 00:33:02 +00:00
lobehubbot 719fde30ea 🤖 chore: Lighthouse Results | Chat 2025-06-03 00:32:43 +00:00
lobehubbot c15f9fd801 🤖 chore: Lighthouse Results | Discover 2025-06-02 00:33:40 +00:00
lobehubbot 47630471f4 🤖 chore: Lighthouse Results | Chat 2025-06-02 00:33:28 +00:00
lobehubbot bb8c75ccbf 🤖 chore: Lighthouse Results | Discover 2025-06-01 00:39:35 +00:00
lobehubbot d68a511f7c 🤖 chore: Lighthouse Results | Chat 2025-06-01 00:39:19 +00:00
lobehubbot 1f2207f096 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-05-31 00:30:55 +00:00
lobehubbot 82fed2fdbe 🤖 chore: Lighthouse Results | Chat 2025-05-31 00:30:55 +00:00
lobehubbot f95dc40e8c 🤖 chore: Lighthouse Results | Discover 2025-05-31 00:30:54 +00:00
lobehubbot 696278a5aa 🤖 chore: Lighthouse Results | Discover 2025-05-30 00:31:51 +00:00
lobehubbot fe7b7f4df8 🤖 chore: Lighthouse Results | Chat 2025-05-30 00:31:35 +00:00
lobehubbot 0c4dcfd3fc 🤖 chore: Lighthouse Results | Discover 2025-05-29 00:31:54 +00:00
lobehubbot d5c710f428 🤖 chore: Lighthouse Results | Chat 2025-05-29 00:31:38 +00:00
lobehubbot e9c203a821 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-05-28 00:31:37 +00:00
lobehubbot eddbed3581 🤖 chore: Lighthouse Results | Discover 2025-05-28 00:31:36 +00:00
lobehubbot cc79400f59 🤖 chore: Lighthouse Results | Chat 2025-05-28 00:31:36 +00:00
lobehubbot 19318bc136 🤖 chore: Lighthouse Results | Discover 2025-05-27 00:31:07 +00:00
lobehubbot 782ced08c5 🤖 chore: Lighthouse Results | Chat 2025-05-27 00:31:01 +00:00
lobehubbot 5e72a3c0eb 🤖 chore: Lighthouse Results | Discover 2025-05-26 00:32:52 +00:00
lobehubbot a9bd7a6243 🤖 chore: Lighthouse Results | Chat 2025-05-26 00:32:30 +00:00
lobehubbot 55968de40b 🤖 chore: Lighthouse Results | Discover 2025-05-25 00:35:08 +00:00
lobehubbot b9b1d14fc3 🤖 chore: Lighthouse Results | Chat 2025-05-25 00:34:59 +00:00
lobehubbot febdffdd65 🤖 chore: Lighthouse Results | Discover 2025-05-24 00:30:18 +00:00
lobehubbot eaedabff0c 🤖 chore: Lighthouse Results | Chat 2025-05-24 00:30:12 +00:00
lobehubbot f75dab5da0 🤖 chore: Lighthouse Results | Discover 2025-05-23 00:31:43 +00:00
lobehubbot 5286bfc7c5 🤖 chore: Lighthouse Results | Chat 2025-05-23 00:31:36 +00:00
lobehubbot a10ddbbaea 🤖 chore: Lighthouse Results | Discover 2025-05-22 00:31:18 +00:00
lobehubbot 98a455d85d 🤖 chore: Lighthouse Results | Chat 2025-05-22 00:31:13 +00:00
lobehubbot 51967e6359 🤖 chore: Lighthouse Results | Chat 2025-05-21 00:32:26 +00:00
lobehubbot d91235ade4 🤖 chore: Lighthouse Results | Discover 2025-05-21 00:31:43 +00:00
lobehubbot cf4fd59421 🤖 chore: Lighthouse Results | Discover 2025-05-20 00:32:18 +00:00
lobehubbot 549f93ddd6 🤖 chore: Lighthouse Results | Chat 2025-05-20 00:32:12 +00:00
lobehubbot 86be13400c 🤖 chore: Lighthouse Results | Discover 2025-05-19 00:33:30 +00:00
lobehubbot e0a8afa8c4 🤖 chore: Lighthouse Results | Chat 2025-05-19 00:33:23 +00:00
lobehubbot 0a682e2320 🤖 chore: Lighthouse Results | Chat 2025-05-18 00:34:24 +00:00
lobehubbot 26c48bd806 🤖 chore: Lighthouse Results | Discover 2025-05-18 00:34:08 +00:00
lobehubbot 858e148831 🤖 chore: Lighthouse Results | Discover 2025-05-17 00:30:41 +00:00
lobehubbot 0397b4b92e 🤖 chore: Lighthouse Results | Chat 2025-05-17 00:30:39 +00:00
lobehubbot 2bdb1cb2ea 🤖 chore: Lighthouse Results | Discover 2025-05-16 00:31:33 +00:00
lobehubbot 2e6fd373d9 🤖 chore: Lighthouse Results | Chat 2025-05-16 00:31:27 +00:00
lobehubbot 065fb7d0c1 🤖 chore: Lighthouse Results | Discover 2025-05-15 00:30:56 +00:00
lobehubbot bfc85822a5 🤖 chore: Lighthouse Results | Chat 2025-05-15 00:30:28 +00:00
lobehubbot 7592c1143b 🤖 chore: Lighthouse Results | Chat 2025-05-14 00:31:54 +00:00
lobehubbot 78c005ca4e 🤖 chore: Lighthouse Results | Discover 2025-05-14 00:31:47 +00:00
lobehubbot 826c5928ba Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-05-13 00:31:39 +00:00
lobehubbot 2cd3f45d1a 🤖 chore: Lighthouse Results | Discover 2025-05-13 00:31:38 +00:00
lobehubbot b296f013db 🤖 chore: Lighthouse Results | Chat 2025-05-13 00:31:38 +00:00
lobehubbot c45c9fd95b 🤖 chore: Lighthouse Results | Chat 2025-05-12 00:33:33 +00:00
lobehubbot 41081be1c7 🤖 chore: Lighthouse Results | Discover 2025-05-12 00:33:27 +00:00
lobehubbot 8ec3709882 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-05-11 00:34:03 +00:00
lobehubbot efb1003347 🤖 chore: Lighthouse Results | Discover 2025-05-11 00:34:03 +00:00
lobehubbot 08f655fbca 🤖 chore: Lighthouse Results | Chat 2025-05-11 00:34:02 +00:00
lobehubbot c8a43117f3 🤖 chore: Lighthouse Results | Discover 2025-05-10 00:30:06 +00:00
lobehubbot 092b63a1e2 🤖 chore: Lighthouse Results | Chat 2025-05-10 00:29:41 +00:00
lobehubbot 8abe5d7fbb 🤖 chore: Lighthouse Results | Chat 2025-05-09 00:31:25 +00:00
lobehubbot 19c32dcf7b 🤖 chore: Lighthouse Results | Discover 2025-05-09 00:31:22 +00:00
lobehubbot 4379f1048c 🤖 chore: Lighthouse Results | Discover 2025-05-08 00:31:34 +00:00
lobehubbot 04e0e16f7a 🤖 chore: Lighthouse Results | Chat 2025-05-08 00:31:29 +00:00
lobehubbot ed52c9b21d 🤖 chore: Lighthouse Results | Discover 2025-05-07 00:31:28 +00:00
lobehubbot b69cd46667 🤖 chore: Lighthouse Results | Chat 2025-05-07 00:30:55 +00:00
lobehubbot 382de6e6cd 🤖 chore: Lighthouse Results | Chat 2025-05-06 00:30:52 +00:00
lobehubbot ce81cced17 🤖 chore: Lighthouse Results | Discover 2025-05-06 00:30:48 +00:00
lobehubbot 650c34cdeb 🤖 chore: Lighthouse Results | Discover 2025-05-05 00:33:45 +00:00
lobehubbot 3ad45eb50d 🤖 chore: Lighthouse Results | Chat 2025-05-05 00:33:36 +00:00
lobehubbot 52546d291b 🤖 chore: Lighthouse Results | Discover 2025-05-04 00:34:37 +00:00
lobehubbot 403503e7a1 🤖 chore: Lighthouse Results | Discover 2025-05-03 00:30:10 +00:00
lobehubbot 9b0ab2a229 🤖 chore: Lighthouse Results | Chat 2025-05-03 00:30:05 +00:00
lobehubbot c7b3143749 🤖 chore: Lighthouse Results | Discover 2025-05-02 00:30:35 +00:00
lobehubbot 3b9f9793cc 🤖 chore: Lighthouse Results | Chat 2025-05-02 00:30:31 +00:00
lobehubbot aec73ca83c 🤖 chore: Lighthouse Results | Chat 2025-05-01 00:35:03 +00:00
lobehubbot 07668ea63a 🤖 chore: Lighthouse Results | Discover 2025-05-01 00:34:33 +00:00
lobehubbot 294056a132 🤖 chore: Lighthouse Results | Discover 2025-04-30 00:30:37 +00:00
lobehubbot 73a70d541b 🤖 chore: Lighthouse Results | Chat 2025-04-30 00:30:29 +00:00
lobehubbot 506a1c0683 🤖 chore: Lighthouse Results | Discover 2025-04-29 00:30:40 +00:00
lobehubbot 26dbf010e0 🤖 chore: Lighthouse Results | Chat 2025-04-29 00:30:27 +00:00
lobehubbot b20d9322f2 🤖 chore: Lighthouse Results | Discover 2025-04-28 00:32:15 +00:00
lobehubbot ad198c0272 🤖 chore: Lighthouse Results | Chat 2025-04-28 00:31:57 +00:00
lobehubbot 28e38978a1 🤖 chore: Lighthouse Results | Discover 2025-04-27 00:33:10 +00:00
lobehubbot 0f1ade86d4 🤖 chore: Lighthouse Results | Chat 2025-04-27 00:32:52 +00:00
lobehubbot 912e207f58 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-04-26 00:29:42 +00:00
lobehubbot 5c2b7c67be 🤖 chore: Lighthouse Results | Discover 2025-04-26 00:29:41 +00:00
lobehubbot 19a51db7f3 🤖 chore: Lighthouse Results | Chat 2025-04-26 00:29:41 +00:00
lobehubbot 6f82a0d530 🤖 chore: Lighthouse Results | Chat 2025-04-25 00:30:52 +00:00
lobehubbot e4faab12eb 🤖 chore: Lighthouse Results | Discover 2025-04-25 00:30:45 +00:00
lobehubbot 4ce4191fd8 🤖 chore: Lighthouse Results | Discover 2025-04-24 00:30:14 +00:00
lobehubbot 5acdf2ec50 🤖 chore: Lighthouse Results | Chat 2025-04-24 00:30:06 +00:00
lobehubbot caeda909aa 🤖 chore: Lighthouse Results | Chat 2025-04-23 00:30:29 +00:00
lobehubbot 584a286712 🤖 chore: Lighthouse Results | Discover 2025-04-23 00:30:27 +00:00
lobehubbot 0460c79a9f 🤖 chore: Lighthouse Results | Discover 2025-04-22 00:30:37 +00:00
lobehubbot a2dae10b55 🤖 chore: Lighthouse Results | Chat 2025-04-22 00:30:28 +00:00
lobehubbot 93ee04167a 🤖 chore: Lighthouse Results | Discover 2025-04-21 00:32:19 +00:00
lobehubbot 15b9783991 🤖 chore: Lighthouse Results | Chat 2025-04-21 00:32:04 +00:00
lobehubbot ed2ae7246e 🤖 chore: Lighthouse Results | Chat 2025-04-20 00:33:21 +00:00
lobehubbot 6492daaba1 🤖 chore: Lighthouse Results | Discover 2025-04-20 00:33:01 +00:00
lobehubbot 81f214d7a3 🤖 chore: Lighthouse Results | Discover 2025-04-19 00:28:50 +00:00
lobehubbot 29aa9db6b7 🤖 chore: Lighthouse Results | Chat 2025-04-19 00:28:43 +00:00
lobehubbot 55e59c2e41 🤖 chore: Lighthouse Results | Discover 2025-04-18 00:29:49 +00:00
lobehubbot a2bd69a05b 🤖 chore: Lighthouse Results | Chat 2025-04-18 00:29:25 +00:00
lobehubbot 54d51ce31e 🤖 chore: Lighthouse Results | Discover 2025-04-17 00:29:54 +00:00
lobehubbot 3de170ddac 🤖 chore: Lighthouse Results | Chat 2025-04-17 00:29:41 +00:00
lobehubbot dbd4e277a9 🤖 chore: Lighthouse Results | Discover 2025-04-16 00:30:40 +00:00
lobehubbot 21df74b7ac 🤖 chore: Lighthouse Results | Chat 2025-04-16 00:30:32 +00:00
lobehubbot 9b21ffbe80 🤖 chore: Lighthouse Results | Discover 2025-04-15 00:31:01 +00:00
lobehubbot bb273c9f7d 🤖 chore: Lighthouse Results | Chat 2025-04-15 00:30:32 +00:00
lobehubbot c15c6e02d1 🤖 chore: Lighthouse Results | Discover 2025-04-14 00:32:30 +00:00
lobehubbot 137c529c0c 🤖 chore: Lighthouse Results | Chat 2025-04-14 00:32:03 +00:00
lobehubbot c402e0809e 🤖 chore: Lighthouse Results | Discover 2025-04-13 00:55:47 +00:00
lobehubbot 1394c74c32 🤖 chore: Lighthouse Results | Chat 2025-04-13 00:55:37 +00:00
lobehubbot 7a02e919e2 🤖 chore: Lighthouse Results | Discover 2025-04-12 00:29:13 +00:00
lobehubbot f707bc715b 🤖 chore: Lighthouse Results | Chat 2025-04-12 00:28:58 +00:00
lobehubbot 457cf4f8ae 🤖 chore: Lighthouse Results | Discover 2025-04-11 00:30:15 +00:00
lobehubbot 1f2c907d55 🤖 chore: Lighthouse Results | Chat 2025-04-11 00:30:06 +00:00
lobehubbot d7f4f3b52d 🤖 chore: Lighthouse Results | Discover 2025-04-10 00:29:59 +00:00
lobehubbot faaf1fb860 🤖 chore: Lighthouse Results | Chat 2025-04-10 00:29:18 +00:00
lobehubbot 379e8fc234 🤖 chore: Lighthouse Results | Discover 2025-04-09 00:30:07 +00:00
lobehubbot bce67819d9 🤖 chore: Lighthouse Results | Chat 2025-04-09 00:29:37 +00:00
lobehubbot 3bfe69e7ac 🤖 chore: Lighthouse Results | Discover 2025-04-08 00:29:25 +00:00
lobehubbot 6a98da3045 🤖 chore: Lighthouse Results | Chat 2025-04-08 00:29:18 +00:00
lobehubbot 8fc10f588a 🤖 chore: Lighthouse Results | Discover 2025-04-07 00:31:05 +00:00
lobehubbot e28cdacca7 🤖 chore: Lighthouse Results | Chat 2025-04-07 00:30:49 +00:00
lobehubbot cacbb4c927 🤖 chore: Lighthouse Results | Discover 2025-04-06 00:32:22 +00:00
lobehubbot 5aa0907fc7 🤖 chore: Lighthouse Results | Chat 2025-04-06 00:31:55 +00:00
lobehubbot af2fda8ef1 🤖 chore: Lighthouse Results | Discover 2025-04-05 00:28:56 +00:00
lobehubbot dd078e1969 🤖 chore: Lighthouse Results | Chat 2025-04-05 00:28:44 +00:00
lobehubbot 5f546ad10a 🤖 chore: Lighthouse Results | Discover 2025-04-04 00:29:25 +00:00
lobehubbot 77a40a8ff0 🤖 chore: Lighthouse Results | Chat 2025-04-04 00:29:20 +00:00
lobehubbot 1d56ad7f85 🤖 chore: Lighthouse Results | Chat 2025-04-03 00:29:38 +00:00
lobehubbot 3015ae49ab 🤖 chore: Lighthouse Results | Discover 2025-04-03 00:29:17 +00:00
lobehubbot 6340a509eb 🤖 chore: Lighthouse Results | Discover 2025-04-02 00:30:13 +00:00
lobehubbot 93e3a3c336 🤖 chore: Lighthouse Results | Chat 2025-04-02 00:29:59 +00:00
lobehubbot 23eaeaf0f8 🤖 chore: Lighthouse Results | Discover 2025-04-01 00:34:08 +00:00
lobehubbot e39b5cc8cd 🤖 chore: Lighthouse Results | Chat 2025-04-01 00:33:43 +00:00
lobehubbot d73d926f7a 🤖 chore: Lighthouse Results | Discover 2025-03-31 00:31:50 +00:00
lobehubbot 69dae88217 🤖 chore: Lighthouse Results | Chat 2025-03-31 00:31:17 +00:00
lobehubbot ef979e952b 🤖 chore: Lighthouse Results | Chat 2025-03-30 00:32:17 +00:00
lobehubbot 0d4e2fde58 🤖 chore: Lighthouse Results | Discover 2025-03-30 00:32:12 +00:00
lobehubbot f19a04146b 🤖 chore: Lighthouse Results | Discover 2025-03-29 00:29:14 +00:00
lobehubbot 4aa107317b 🤖 chore: Lighthouse Results | Chat 2025-03-29 00:29:05 +00:00
lobehubbot 6bf6c064c1 🤖 chore: Lighthouse Results | Discover 2025-03-28 00:29:11 +00:00
lobehubbot 474b5f90a5 🤖 chore: Lighthouse Results | Chat 2025-03-28 00:29:07 +00:00
lobehubbot 327a57a3a8 🤖 chore: Lighthouse Results | Discover 2025-03-27 00:29:12 +00:00
lobehubbot cb7e4e63ae 🤖 chore: Lighthouse Results | Chat 2025-03-27 00:29:02 +00:00
lobehubbot d8bbb6e1b0 🤖 chore: Lighthouse Results | Discover 2025-03-26 00:29:00 +00:00
lobehubbot 74a6a9c219 🤖 chore: Lighthouse Results | Chat 2025-03-26 00:28:55 +00:00
lobehubbot e8cd377f76 🤖 chore: Lighthouse Results | Chat 2025-03-25 00:30:02 +00:00
lobehubbot b72ed90254 🤖 chore: Lighthouse Results | Discover 2025-03-25 00:29:58 +00:00
lobehubbot 73f8331960 🤖 chore: Lighthouse Results | Discover 2025-03-24 00:30:46 +00:00
lobehubbot 0738714376 🤖 chore: Lighthouse Results | Chat 2025-03-24 00:30:37 +00:00
lobehubbot f2c37ec8ba Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-03-23 00:32:11 +00:00
lobehubbot 1c6bb8d5b3 🤖 chore: Lighthouse Results | Discover 2025-03-23 00:32:10 +00:00
lobehubbot b3fae16939 🤖 chore: Lighthouse Results | Chat 2025-03-23 00:32:10 +00:00
lobehubbot c764b66fcb 🤖 chore: Lighthouse Results | Discover 2025-03-22 00:28:38 +00:00
lobehubbot f0e5461eda 🤖 chore: Lighthouse Results | Chat 2025-03-22 00:28:11 +00:00
lobehubbot 3eb7b1ec0d 🤖 chore: Lighthouse Results | Discover 2025-03-21 00:29:10 +00:00
lobehubbot 5da8f01afc 🤖 chore: Lighthouse Results | Chat 2025-03-21 00:29:00 +00:00
lobehubbot 0855762516 🤖 chore: Lighthouse Results | Discover 2025-03-20 00:29:03 +00:00
lobehubbot 2830dd8266 🤖 chore: Lighthouse Results | Chat 2025-03-20 00:28:36 +00:00
lobehubbot 7011299251 🤖 chore: Lighthouse Results | Chat 2025-03-19 00:29:44 +00:00
lobehubbot 27d2a072cc 🤖 chore: Lighthouse Results | Discover 2025-03-19 00:29:08 +00:00
lobehubbot b35749dcd2 🤖 chore: Lighthouse Results | Discover 2025-03-18 00:28:57 +00:00
lobehubbot a394aa6e33 🤖 chore: Lighthouse Results | Chat 2025-03-18 00:28:44 +00:00
lobehubbot b2e962f158 🤖 chore: Lighthouse Results | Discover 2025-03-17 00:30:39 +00:00
lobehubbot 98079d42e8 🤖 chore: Lighthouse Results | Chat 2025-03-17 00:30:32 +00:00
lobehubbot 34f2da0c46 🤖 chore: Lighthouse Results | Discover 2025-03-16 00:31:33 +00:00
lobehubbot 328b50f9aa 🤖 chore: Lighthouse Results | Chat 2025-03-16 00:31:22 +00:00
lobehubbot a47199d741 🤖 chore: Lighthouse Results | Chat 2025-03-15 00:28:32 +00:00
lobehubbot 7616baa9a9 🤖 chore: Lighthouse Results | Discover 2025-03-15 00:28:22 +00:00
lobehubbot b433fd5db0 🤖 chore: Lighthouse Results | Chat 2025-03-14 00:28:36 +00:00
lobehubbot 47bb15f409 🤖 chore: Lighthouse Results | Discover 2025-03-14 00:28:24 +00:00
lobehubbot c0e57d16b1 🤖 chore: Lighthouse Results | Discover 2025-03-13 00:29:30 +00:00
lobehubbot f2e32565a5 🤖 chore: Lighthouse Results | Chat 2025-03-13 00:28:54 +00:00
lobehubbot 8afffd20c6 🤖 chore: Lighthouse Results | Discover 2025-03-12 00:28:28 +00:00
lobehubbot 3b6ac7f5a8 🤖 chore: Lighthouse Results | Chat 2025-03-12 00:28:23 +00:00
lobehubbot 0339cdcd1a 🤖 chore: Lighthouse Results | Discover 2025-03-10 00:24:53 +00:00
lobehubbot 89e50cde43 🤖 chore: Lighthouse Results | Chat 2025-03-10 00:24:44 +00:00
lobehubbot 15c47a7f05 🤖 chore: Lighthouse Results | Discover 2025-03-09 00:26:07 +00:00
lobehubbot 0989914f79 🤖 chore: Lighthouse Results | Chat 2025-03-09 00:25:48 +00:00
lobehubbot 55589d9ec0 🤖 chore: Lighthouse Results | Discover 2025-03-08 00:23:13 +00:00
lobehubbot 01f6331bd7 🤖 chore: Lighthouse Results | Chat 2025-03-08 00:23:06 +00:00
lobehubbot a97c6e9371 🤖 chore: Lighthouse Results | Discover 2025-03-07 00:28:44 +00:00
lobehubbot daf3246c57 🤖 chore: Lighthouse Results | Chat 2025-03-07 00:28:40 +00:00
lobehubbot e8074584d2 🤖 chore: Lighthouse Results | Discover 2025-03-06 00:28:41 +00:00
lobehubbot 4637c700ec 🤖 chore: Lighthouse Results | Chat 2025-03-06 00:28:38 +00:00
lobehubbot 5db9e93f00 🤖 chore: Lighthouse Results | Discover 2025-03-05 00:28:33 +00:00
lobehubbot 614a7b317f 🤖 chore: Lighthouse Results | Chat 2025-03-05 00:28:22 +00:00
lobehubbot 265a37643b 🤖 chore: Lighthouse Results | Discover 2025-03-04 00:28:38 +00:00
lobehubbot 2b13f75e30 🤖 chore: Lighthouse Results | Chat 2025-03-04 00:28:14 +00:00
lobehubbot ac057b3157 🤖 chore: Lighthouse Results | Chat 2025-03-03 00:29:46 +00:00
lobehubbot cd41d69410 🤖 chore: Lighthouse Results | Discover 2025-03-03 00:29:37 +00:00
lobehubbot 53b0e0f9fa 🤖 chore: Lighthouse Results | Chat 2025-03-02 00:30:36 +00:00
lobehubbot 81729b367a 🤖 chore: Lighthouse Results | Discover 2025-03-02 00:30:32 +00:00
lobehubbot 3d2e4a36ad 🤖 chore: Lighthouse Results | Discover 2025-03-01 00:31:05 +00:00
lobehubbot 1ae9e920af 🤖 chore: Lighthouse Results | Chat 2025-03-01 00:30:54 +00:00
lobehubbot 5b119df0a6 🤖 chore: Lighthouse Results | Discover 2025-02-28 00:28:09 +00:00
lobehubbot cf404182fb 🤖 chore: Lighthouse Results | Chat 2025-02-28 00:28:01 +00:00
lobehubbot b126f4d095 🤖 chore: Lighthouse Results | Discover 2025-02-27 00:28:21 +00:00
lobehubbot 016f420789 🤖 chore: Lighthouse Results | Chat 2025-02-27 00:28:02 +00:00
lobehubbot 2a9827575c Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-02-26 00:28:14 +00:00
lobehubbot 4b5350776e 🤖 chore: Lighthouse Results | Discover 2025-02-26 00:28:13 +00:00
lobehubbot 578354f78f 🤖 chore: Lighthouse Results | Chat 2025-02-26 00:28:12 +00:00
lobehubbot feb6989d2f 🤖 chore: Lighthouse Results | Chat 2025-02-25 00:28:33 +00:00
lobehubbot 846ddb3f32 🤖 chore: Lighthouse Results | Discover 2025-02-25 00:28:16 +00:00
lobehubbot 2723c07e52 🤖 chore: Lighthouse Results | Discover 2025-02-24 00:29:11 +00:00
lobehubbot 076156656c 🤖 chore: Lighthouse Results | Chat 2025-02-24 00:28:51 +00:00
lobehubbot 0e2f4dd6a7 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-02-23 00:30:01 +00:00
lobehubbot 527c17cb32 🤖 chore: Lighthouse Results | Chat 2025-02-23 00:30:00 +00:00
lobehubbot 61c254c025 🤖 chore: Lighthouse Results | Discover 2025-02-23 00:29:59 +00:00
lobehubbot 5c486c544d 🤖 chore: Lighthouse Results | Discover 2025-02-22 00:26:54 +00:00
lobehubbot 37864a19e2 🤖 chore: Lighthouse Results | Chat 2025-02-22 00:26:42 +00:00
lobehubbot 6afb87e77c 🤖 chore: Lighthouse Results | Discover 2025-02-21 00:28:02 +00:00
lobehubbot cdd835f551 🤖 chore: Lighthouse Results | Chat 2025-02-21 00:27:53 +00:00
lobehubbot c57fc51ed6 🤖 chore: Lighthouse Results | Discover 2025-02-20 00:28:06 +00:00
lobehubbot e4be7d53c2 🤖 chore: Lighthouse Results | Chat 2025-02-20 00:27:49 +00:00
lobehubbot 021a81d365 🤖 chore: Lighthouse Results | Chat 2025-02-19 00:28:10 +00:00
lobehubbot f903b4327c 🤖 chore: Lighthouse Results | Discover 2025-02-19 00:28:06 +00:00
lobehubbot f5f8b3c6e9 🤖 chore: Lighthouse Results | Discover 2025-02-18 00:27:19 +00:00
lobehubbot 09974f1481 🤖 chore: Lighthouse Results | Chat 2025-02-18 00:27:14 +00:00
lobehubbot 17713972e9 🤖 chore: Lighthouse Results | Chat 2025-02-17 00:29:27 +00:00
lobehubbot fc87d74af0 🤖 chore: Lighthouse Results | Discover 2025-02-17 00:29:20 +00:00
lobehubbot 76a6ffb1ea 🤖 chore: Lighthouse Results | Discover 2025-02-16 00:30:41 +00:00
lobehubbot c5b5f6d59d 🤖 chore: Lighthouse Results | Chat 2025-02-16 00:30:06 +00:00
lobehubbot 49c8e26290 🤖 chore: Lighthouse Results | Discover 2025-02-15 00:27:18 +00:00
lobehubbot 02cf4c43d4 🤖 chore: Lighthouse Results | Chat 2025-02-15 00:27:12 +00:00
lobehubbot 2aff6e8c7c 🤖 chore: Lighthouse Results | Discover 2025-02-14 00:27:29 +00:00
lobehubbot 9c1bc99be2 🤖 chore: Lighthouse Results | Chat 2025-02-14 00:27:19 +00:00
lobehubbot 2f9a573803 🤖 chore: Lighthouse Results | Discover 2025-02-13 00:27:39 +00:00
lobehubbot 4ef420d969 🤖 chore: Lighthouse Results | Chat 2025-02-13 00:27:35 +00:00
lobehubbot e1c2a36758 🤖 chore: Lighthouse Results | Discover 2025-02-12 00:27:25 +00:00
lobehubbot 67a9e55a72 🤖 chore: Lighthouse Results | Chat 2025-02-12 00:27:11 +00:00
lobehubbot 63471353e0 🤖 chore: Lighthouse Results | Welcome 2025-02-11 00:27:44 +00:00
lobehubbot 5a13da984f 🤖 chore: Lighthouse Results | Market 2025-02-11 00:27:42 +00:00
lobehubbot 5d781b362e 🤖 chore: Lighthouse Results | Chat 2025-02-11 00:27:15 +00:00
lobehubbot 9c015b838e 🤖 chore: Lighthouse Results | Settings 2025-02-11 00:27:13 +00:00
lobehubbot 969b55a9e9 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-02-10 00:28:31 +00:00
lobehubbot f0a449ea69 🤖 chore: Lighthouse Results | Market 2025-02-10 00:28:30 +00:00
lobehubbot b22860d359 🤖 chore: Lighthouse Results | Chat 2025-02-10 00:28:30 +00:00
lobehubbot 52d8169a3c 🤖 chore: Lighthouse Results | Welcome 2025-02-10 00:28:18 +00:00
lobehubbot 5c6bdbe7b7 🤖 chore: Lighthouse Results | Settings 2025-02-10 00:28:16 +00:00
lobehubbot d6b5ce2f4d 🤖 chore: Lighthouse Results | Settings 2025-02-09 00:29:28 +00:00
lobehubbot 109e409faa Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-02-09 00:29:24 +00:00
lobehubbot 9fe9824ca9 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-02-09 00:29:23 +00:00
lobehubbot f7ff6151cb Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-02-09 00:29:22 +00:00
lobehubbot 060122375f 🤖 chore: Lighthouse Results | Market 2025-02-09 00:29:22 +00:00
lobehubbot b1dfa1adde 🤖 chore: Lighthouse Results | Welcome 2025-02-09 00:29:22 +00:00
lobehubbot ba1e56ea68 🤖 chore: Lighthouse Results | Chat 2025-02-09 00:29:22 +00:00
lobehubbot 331aaa91dc Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-02-08 00:26:38 +00:00
lobehubbot f6f73896f4 🤖 chore: Lighthouse Results | Market 2025-02-08 00:26:37 +00:00
lobehubbot f5f627e778 🤖 chore: Lighthouse Results | Settings 2025-02-08 00:26:37 +00:00
lobehubbot 2be1480a4f 🤖 chore: Lighthouse Results | Welcome 2025-02-08 00:26:25 +00:00
lobehubbot 66b3ccb5e8 🤖 chore: Lighthouse Results | Chat 2025-02-08 00:26:24 +00:00
lobehubbot 1608b250f7 🤖 chore: Lighthouse Results | Welcome 2025-02-07 00:27:31 +00:00
lobehubbot 8465294b5c 🤖 chore: Lighthouse Results | Chat 2025-02-07 00:27:27 +00:00
lobehubbot 801d1cbdaf 🤖 chore: Lighthouse Results | Settings 2025-02-07 00:27:21 +00:00
lobehubbot 00c7750b6e 🤖 chore: Lighthouse Results | Market 2025-02-07 00:27:19 +00:00
lobehubbot 8cacbf9531 🤖 chore: Lighthouse Results | Welcome 2025-02-06 00:27:35 +00:00
lobehubbot 72c19b53b0 🤖 chore: Lighthouse Results | Chat 2025-02-06 00:27:20 +00:00
lobehubbot ebb40bf2f2 🤖 chore: Lighthouse Results | Settings 2025-02-06 00:27:18 +00:00
lobehubbot 69676baa9a 🤖 chore: Lighthouse Results | Chat 2025-02-05 00:27:47 +00:00
lobehubbot 070616b453 🤖 chore: Lighthouse Results | Welcome 2025-02-05 00:27:18 +00:00
lobehubbot 535fb43a9e 🤖 chore: Lighthouse Results | Settings 2025-02-05 00:27:04 +00:00
lobehubbot 4a06250703 🤖 chore: Lighthouse Results | Settings 2025-02-04 00:27:05 +00:00
lobehubbot b112ddf5fe 🤖 chore: Lighthouse Results | Chat 2025-02-04 00:27:03 +00:00
lobehubbot 52e76a656e 🤖 chore: Lighthouse Results | Welcome 2025-02-04 00:27:02 +00:00
lobehubbot 30641dc19f Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-02-03 00:27:52 +00:00
lobehubbot 319d539ab7 🤖 chore: Lighthouse Results | Welcome 2025-02-03 00:27:51 +00:00
lobehubbot 35e8d86dce 🤖 chore: Lighthouse Results | Chat 2025-02-03 00:27:51 +00:00
lobehubbot c27c846bdc 🤖 chore: Lighthouse Results | Settings 2025-02-03 00:27:46 +00:00
lobehubbot 89d6ebd68b 🤖 chore: Lighthouse Results | Chat 2025-02-02 00:29:12 +00:00
lobehubbot 3923b33f66 🤖 chore: Lighthouse Results | Welcome 2025-02-02 00:29:05 +00:00
lobehubbot 4c59e0a027 🤖 chore: Lighthouse Results | Settings 2025-02-02 00:28:47 +00:00
lobehubbot ac40a4166d 🤖 chore: Lighthouse Results | Welcome 2025-02-01 00:29:20 +00:00
lobehubbot a5f4bd34ad Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-02-01 00:29:11 +00:00
lobehubbot 5c764393cc 🤖 chore: Lighthouse Results | Settings 2025-02-01 00:29:11 +00:00
lobehubbot 4f89ada4d5 🤖 chore: Lighthouse Results | Chat 2025-02-01 00:29:10 +00:00
lobehubbot 3501b12dfb Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-01-31 00:26:59 +00:00
lobehubbot 03f21f41b2 🤖 chore: Lighthouse Results | Welcome 2025-01-31 00:26:58 +00:00
lobehubbot 01f73d08f8 🤖 chore: Lighthouse Results | Chat 2025-01-31 00:26:58 +00:00
lobehubbot b80269918c 🤖 chore: Lighthouse Results | Settings 2025-01-31 00:26:54 +00:00
lobehubbot 1ef6f890d4 🤖 chore: Lighthouse Results | Welcome 2025-01-30 00:26:45 +00:00
lobehubbot c257fa5372 🤖 chore: Lighthouse Results | Chat 2025-01-30 00:26:32 +00:00
lobehubbot ab6231437f 🤖 chore: Lighthouse Results | Settings 2025-01-30 00:26:15 +00:00
lobehubbot 66606076ca 🤖 chore: Lighthouse Results | Settings 2025-01-29 00:26:50 +00:00
lobehubbot d292a30a08 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-01-29 00:26:43 +00:00
lobehubbot 927f1aa71e 🤖 chore: Lighthouse Results | Welcome 2025-01-29 00:26:42 +00:00
lobehubbot 8d7f929f0f 🤖 chore: Lighthouse Results | Chat 2025-01-29 00:26:42 +00:00
lobehubbot 4903f89396 🤖 chore: Lighthouse Results | Welcome 2025-01-28 00:27:25 +00:00
lobehubbot ebc1e7613e 🤖 chore: Lighthouse Results | Chat 2025-01-28 00:27:20 +00:00
lobehubbot 5aea71fc35 🤖 chore: Lighthouse Results | Settings 2025-01-28 00:27:03 +00:00
lobehubbot a5e98787b3 🤖 chore: Lighthouse Results | Welcome 2025-01-27 00:28:06 +00:00
lobehubbot 3f21b51832 🤖 chore: Lighthouse Results | Chat 2025-01-27 00:27:54 +00:00
lobehubbot 817b8f0c01 🤖 chore: Lighthouse Results | Settings 2025-01-27 00:27:46 +00:00
lobehubbot 7ffbfb874e Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-01-26 00:27:12 +00:00
lobehubbot 8817545868 🤖 chore: Lighthouse Results | Welcome 2025-01-26 00:27:11 +00:00
lobehubbot 768ad43251 🤖 chore: Lighthouse Results | Chat 2025-01-26 00:27:11 +00:00
lobehubbot 2c6782b744 🤖 chore: Lighthouse Results | Settings 2025-01-26 00:27:07 +00:00
lobehubbot 5c1475da92 🤖 chore: Lighthouse Results | Chat 2025-01-25 00:26:15 +00:00
lobehubbot e3c22aa2bd 🤖 chore: Lighthouse Results | Welcome 2025-01-25 00:26:01 +00:00
lobehubbot 6e13861e34 🤖 chore: Lighthouse Results | Settings 2025-01-25 00:25:58 +00:00
lobehubbot b217008efc 🤖 chore: Lighthouse Results | Settings 2025-01-24 00:27:00 +00:00
lobehubbot ec2f58c411 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-01-24 00:26:59 +00:00
lobehubbot 06a835f658 🤖 chore: Lighthouse Results | Chat 2025-01-24 00:26:58 +00:00
lobehubbot 049772b62b 🤖 chore: Lighthouse Results | Welcome 2025-01-24 00:26:57 +00:00
lobehubbot 5341788028 🤖 chore: Lighthouse Results | Chat 2025-01-23 00:26:52 +00:00
lobehubbot 9dfe0f27ac 🤖 chore: Lighthouse Results | Welcome 2025-01-23 00:26:43 +00:00
lobehubbot 90fad70639 🤖 chore: Lighthouse Results | Settings 2025-01-23 00:26:40 +00:00
lobehubbot 1ce01626a7 🤖 chore: Lighthouse Results | Welcome 2025-01-22 00:27:25 +00:00
lobehubbot 46035c3c50 🤖 chore: Lighthouse Results | Chat 2025-01-22 00:27:17 +00:00
lobehubbot e82f325a67 🤖 chore: Lighthouse Results | Settings 2025-01-22 00:27:12 +00:00
lobehubbot c1eb42c3ff 🤖 chore: Lighthouse Results | Settings 2025-01-21 00:26:35 +00:00
lobehubbot dea8eee1fd 🤖 chore: Lighthouse Results | Welcome 2025-01-21 00:26:33 +00:00
lobehubbot d9785b773a 🤖 chore: Lighthouse Results | Chat 2025-01-21 00:26:32 +00:00
lobehubbot 50583df4a9 🤖 chore: Lighthouse Results | Welcome 2025-01-20 00:27:52 +00:00
lobehubbot 8021a382c7 🤖 chore: Lighthouse Results | Chat 2025-01-20 00:27:51 +00:00
lobehubbot 1895594b1e 🤖 chore: Lighthouse Results | Settings 2025-01-20 00:27:45 +00:00
lobehubbot 493f719a5f 🤖 chore: Lighthouse Results | Chat 2025-01-19 00:29:31 +00:00
lobehubbot 6b0117298b 🤖 chore: Lighthouse Results | Welcome 2025-01-19 00:29:17 +00:00
lobehubbot 1fec02995b 🤖 chore: Lighthouse Results | Settings 2025-01-19 00:29:08 +00:00
lobehubbot cc7b5c57f3 🤖 chore: Lighthouse Results | Chat 2025-01-18 00:26:06 +00:00
lobehubbot 84f7864342 🤖 chore: Lighthouse Results | Welcome 2025-01-18 00:26:03 +00:00
lobehubbot 46bbce7072 🤖 chore: Lighthouse Results | Settings 2025-01-18 00:25:40 +00:00
lobehubbot 08e80e26c9 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-01-17 00:26:49 +00:00
lobehubbot a5a5426f0b 🤖 chore: Lighthouse Results | Welcome 2025-01-17 00:26:48 +00:00
lobehubbot 2c74eab219 🤖 chore: Lighthouse Results | Chat 2025-01-17 00:26:47 +00:00
lobehubbot 0081efed82 🤖 chore: Lighthouse Results | Settings 2025-01-17 00:26:36 +00:00
lobehubbot 5d89cd1662 🤖 chore: Lighthouse Results | Welcome 2025-01-16 00:27:08 +00:00
lobehubbot b6b7d2930d 🤖 chore: Lighthouse Results | Chat 2025-01-16 00:26:57 +00:00
lobehubbot f4228e2bfa 🤖 chore: Lighthouse Results | Settings 2025-01-16 00:26:52 +00:00
lobehubbot f1543357f4 🤖 chore: Lighthouse Results | Welcome 2025-01-15 00:27:31 +00:00
lobehubbot b254fe2b4e 🤖 chore: Lighthouse Results | Chat 2025-01-15 00:27:26 +00:00
lobehubbot cc490b1991 🤖 chore: Lighthouse Results | Settings 2025-01-15 00:27:00 +00:00
lobehubbot af6903709c 🤖 chore: Lighthouse Results | Welcome 2025-01-14 00:26:22 +00:00
lobehubbot ae8d1df882 🤖 chore: Lighthouse Results | Settings 2025-01-14 00:26:19 +00:00
lobehubbot 3f54dda0de 🤖 chore: Lighthouse Results | Chat 2025-01-14 00:26:18 +00:00
lobehubbot 775b017ac3 🤖 chore: Lighthouse Results | Chat 2025-01-13 00:29:49 +00:00
lobehubbot a21ad0e407 🤖 chore: Lighthouse Results | Welcome 2025-01-13 00:29:43 +00:00
lobehubbot ad8488d669 🤖 chore: Lighthouse Results | Settings 2025-01-13 00:29:40 +00:00
lobehubbot 6cbad61c1f 🤖 chore: Lighthouse Results | Chat 2025-01-12 00:32:22 +00:00
lobehubbot 4483d45638 🤖 chore: Lighthouse Results | Welcome 2025-01-12 00:30:36 +00:00
lobehubbot 04c272a7c2 🤖 chore: Lighthouse Results | Settings 2025-01-12 00:30:32 +00:00
lobehubbot 476420f173 🤖 chore: Lighthouse Results | Welcome 2025-01-11 00:27:23 +00:00
lobehubbot 2bc179c6e9 🤖 chore: Lighthouse Results | Chat 2025-01-11 00:27:21 +00:00
lobehubbot 5b946471ce 🤖 chore: Lighthouse Results | Settings 2025-01-11 00:27:16 +00:00
lobehubbot 084de093e0 🤖 chore: Lighthouse Results | Welcome 2025-01-10 00:28:19 +00:00
lobehubbot 2ae443b647 🤖 chore: Lighthouse Results | Settings 2025-01-10 00:28:09 +00:00
lobehubbot 25b480a009 🤖 chore: Lighthouse Results | Chat 2025-01-10 00:27:59 +00:00
lobehubbot 0ea29a9e44 🤖 chore: Lighthouse Results | Welcome 2025-01-09 00:27:30 +00:00
lobehubbot 280f4f82a1 🤖 chore: Lighthouse Results | Chat 2025-01-09 00:27:22 +00:00
lobehubbot a4c4c15025 🤖 chore: Lighthouse Results | Settings 2025-01-09 00:27:15 +00:00
lobehubbot 811abbb518 🤖 chore: Lighthouse Results | Chat 2025-01-08 00:27:42 +00:00
lobehubbot 0a777124a1 🤖 chore: Lighthouse Results | Welcome 2025-01-08 00:27:37 +00:00
lobehubbot bdf29bb650 🤖 chore: Lighthouse Results | Settings 2025-01-08 00:27:33 +00:00
lobehubbot 265fc79e38 🤖 chore: Lighthouse Results | Welcome 2025-01-07 00:28:00 +00:00
lobehubbot 7e4fa4973d 🤖 chore: Lighthouse Results | Chat 2025-01-07 00:27:58 +00:00
lobehubbot 8cb135a62d 🤖 chore: Lighthouse Results | Settings 2025-01-07 00:27:55 +00:00
lobehubbot 9c0d32588a 🤖 chore: Lighthouse Results | Chat 2025-01-06 00:29:32 +00:00
lobehubbot d9f9485bd4 🤖 chore: Lighthouse Results | Welcome 2025-01-06 00:29:28 +00:00
lobehubbot 4f5e5df19f 🤖 chore: Lighthouse Results | Settings 2025-01-06 00:28:55 +00:00
lobehubbot 305f21dce1 🤖 chore: Lighthouse Results | Chat 2025-01-05 00:30:15 +00:00
lobehubbot 0dd017a3b8 🤖 chore: Lighthouse Results | Welcome 2025-01-05 00:30:07 +00:00
lobehubbot a54a25ee80 🤖 chore: Lighthouse Results | Settings 2025-01-05 00:29:58 +00:00
lobehubbot f1910e88ea 🤖 chore: Lighthouse Results | Chat 2025-01-04 00:26:39 +00:00
lobehubbot fdb118c5fc Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-01-04 00:26:37 +00:00
lobehubbot 898ddc0d3a 🤖 chore: Lighthouse Results | Settings 2025-01-04 00:26:36 +00:00
lobehubbot 563e9fa429 🤖 chore: Lighthouse Results | Welcome 2025-01-04 00:26:36 +00:00
lobehubbot 8e7cb96101 🤖 chore: Lighthouse Results | Chat 2025-01-03 00:27:21 +00:00
lobehubbot b004f145c2 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2025-01-03 00:27:19 +00:00
lobehubbot 9f604448a8 🤖 chore: Lighthouse Results | Settings 2025-01-03 00:27:17 +00:00
lobehubbot a3a28f5394 🤖 chore: Lighthouse Results | Welcome 2025-01-03 00:27:17 +00:00
lobehubbot 890525eb8e 🤖 chore: Lighthouse Results | Chat 2025-01-02 00:27:12 +00:00
lobehubbot ece98ac78e 🤖 chore: Lighthouse Results | Welcome 2025-01-02 00:27:09 +00:00
lobehubbot ec60989a2f 🤖 chore: Lighthouse Results | Settings 2025-01-02 00:26:57 +00:00
lobehubbot ffbc088251 🤖 chore: Lighthouse Results | Welcome 2025-01-01 00:31:02 +00:00
lobehubbot fdd29da548 🤖 chore: Lighthouse Results | Chat 2025-01-01 00:30:33 +00:00
lobehubbot 5cb947870a 🤖 chore: Lighthouse Results | Settings 2025-01-01 00:30:29 +00:00
lobehubbot f7f64f5826 🤖 chore: Lighthouse Results | Chat 2024-12-31 00:27:22 +00:00
lobehubbot 41d6802d70 🤖 chore: Lighthouse Results | Welcome 2024-12-31 00:27:09 +00:00
lobehubbot adde1d5b8a 🤖 chore: Lighthouse Results | Settings 2024-12-31 00:27:05 +00:00
lobehubbot d933931b04 🤖 chore: Lighthouse Results | Chat 2024-12-30 00:29:07 +00:00
lobehubbot eb4acb7d2e 🤖 chore: Lighthouse Results | Welcome 2024-12-30 00:28:41 +00:00
lobehubbot 91b3bd7bd6 🤖 chore: Lighthouse Results | Settings 2024-12-30 00:28:32 +00:00
lobehubbot e095f4c4c2 🤖 chore: Lighthouse Results | Welcome 2024-12-29 00:30:52 +00:00
lobehubbot 6287d5bca9 🤖 chore: Lighthouse Results | Chat 2024-12-29 00:30:14 +00:00
lobehubbot 13e352b93d 🤖 chore: Lighthouse Results | Settings 2024-12-29 00:30:10 +00:00
lobehubbot 2200bf4de8 🤖 chore: Lighthouse Results | Chat 2024-12-28 00:26:37 +00:00
lobehubbot ac7f8843f7 🤖 chore: Lighthouse Results | Welcome 2024-12-28 00:26:31 +00:00
lobehubbot b8f37f3358 🤖 chore: Lighthouse Results | Settings 2024-12-28 00:26:26 +00:00
lobehubbot 255da92a43 🤖 chore: Lighthouse Results | Chat 2024-12-27 00:27:25 +00:00
lobehubbot 024764c5b4 🤖 chore: Lighthouse Results | Welcome 2024-12-27 00:27:13 +00:00
lobehubbot 97f20a08ce 🤖 chore: Lighthouse Results | Settings 2024-12-27 00:27:07 +00:00
lobehubbot ede90c78a8 🤖 chore: Lighthouse Results | Chat 2024-12-26 00:27:02 +00:00
lobehubbot f57fab67d8 🤖 chore: Lighthouse Results | Welcome 2024-12-26 00:26:58 +00:00
lobehubbot ce2a07b0bd 🤖 chore: Lighthouse Results | Settings 2024-12-26 00:26:55 +00:00
lobehubbot 01b729b908 🤖 chore: Lighthouse Results | Settings 2024-12-25 00:27:09 +00:00
lobehubbot 8816215a48 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-12-25 00:27:00 +00:00
lobehubbot 213c3cc2b6 🤖 chore: Lighthouse Results | Chat 2024-12-25 00:26:59 +00:00
lobehubbot 24a8c3b855 🤖 chore: Lighthouse Results | Welcome 2024-12-25 00:26:59 +00:00
lobehubbot 82a1dfb18b 🤖 chore: Lighthouse Results | Chat 2024-12-24 00:27:33 +00:00
lobehubbot 53611ac8bc 🤖 chore: Lighthouse Results | Welcome 2024-12-24 00:27:24 +00:00
lobehubbot fe430e4948 🤖 chore: Lighthouse Results | Settings 2024-12-24 00:27:18 +00:00
lobehubbot acbc6a8b01 🤖 chore: Lighthouse Results | Settings 2024-12-23 00:28:33 +00:00
lobehubbot b9fe0484c1 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-12-23 00:28:23 +00:00
lobehubbot 28d9978d68 🤖 chore: Lighthouse Results | Chat 2024-12-23 00:28:23 +00:00
lobehubbot 069479cb82 🤖 chore: Lighthouse Results | Welcome 2024-12-23 00:28:22 +00:00
lobehubbot 7a74488729 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-12-22 00:30:00 +00:00
lobehubbot 9723e4502f 🤖 chore: Lighthouse Results | Settings 2024-12-22 00:29:59 +00:00
lobehubbot 44f60d1031 🤖 chore: Lighthouse Results | Welcome 2024-12-22 00:29:59 +00:00
lobehubbot ed08b8b392 🤖 chore: Lighthouse Results | Chat 2024-12-22 00:29:57 +00:00
lobehubbot a5a4ae2afd 🤖 chore: Lighthouse Results | Welcome 2024-12-21 00:26:59 +00:00
lobehubbot 68206c07ce 🤖 chore: Lighthouse Results | Chat 2024-12-21 00:26:58 +00:00
lobehubbot c2acd4eeed 🤖 chore: Lighthouse Results | Settings 2024-12-21 00:26:53 +00:00
lobehubbot f09dd3c679 🤖 chore: Lighthouse Results | Welcome 2024-12-20 00:27:29 +00:00
lobehubbot a3f88e6a4d 🤖 chore: Lighthouse Results | Chat 2024-12-20 00:27:22 +00:00
lobehubbot 131d94e926 🤖 chore: Lighthouse Results | Settings 2024-12-20 00:27:17 +00:00
lobehubbot 1f78e99227 🤖 chore: Lighthouse Results | Welcome 2024-12-19 00:28:54 +00:00
lobehubbot 046021ea96 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-12-19 00:28:49 +00:00
lobehubbot 78013c737d 🤖 chore: Lighthouse Results | Chat 2024-12-19 00:28:48 +00:00
lobehubbot 58c7c76688 🤖 chore: Lighthouse Results | Settings 2024-12-19 00:28:47 +00:00
lobehubbot fafc9b4358 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-12-18 00:28:41 +00:00
lobehubbot 5f5ccd7259 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-12-18 00:28:39 +00:00
lobehubbot 7b06dd3645 🤖 chore: Lighthouse Results | Welcome 2024-12-18 00:28:38 +00:00
lobehubbot 07be98b95d 🤖 chore: Lighthouse Results | Settings 2024-12-18 00:28:37 +00:00
lobehubbot 503a337ff3 🤖 chore: Lighthouse Results | Chat 2024-12-18 00:28:37 +00:00
lobehubbot 7c87061378 🤖 chore: Lighthouse Results | Welcome 2024-12-17 00:29:35 +00:00
lobehubbot 0264cf7324 🤖 chore: Lighthouse Results | Chat 2024-12-17 00:29:28 +00:00
lobehubbot ad50498a2a 🤖 chore: Lighthouse Results | Settings 2024-12-17 00:29:17 +00:00
lobehubbot fce0240fe7 🤖 chore: Lighthouse Results | Chat 2024-12-16 00:31:16 +00:00
lobehubbot bfd38b738f 🤖 chore: Lighthouse Results | Settings 2024-12-16 00:31:08 +00:00
lobehubbot 277927bd8a 🤖 chore: Lighthouse Results | Chat 2024-12-15 00:32:46 +00:00
lobehubbot a592b79ce4 🤖 chore: Lighthouse Results | Settings 2024-12-15 00:32:42 +00:00
lobehubbot b20d357282 🤖 chore: Lighthouse Results | Chat 2024-12-14 00:28:30 +00:00
lobehubbot 3e48204af0 🤖 chore: Lighthouse Results | Settings 2024-12-14 00:28:26 +00:00
lobehubbot b7ebb35be8 🤖 chore: Lighthouse Results | Welcome 2024-12-13 00:32:02 +00:00
lobehubbot a5316d957a 🤖 chore: Lighthouse Results | Chat 2024-12-13 00:30:12 +00:00
lobehubbot 056297b125 🤖 chore: Lighthouse Results | Settings 2024-12-13 00:30:08 +00:00
lobehubbot cd3a8912e9 🤖 chore: Lighthouse Results | Welcome 2024-12-12 00:31:32 +00:00
lobehubbot db71b04dc2 🤖 chore: Lighthouse Results | Settings 2024-12-12 00:29:36 +00:00
lobehubbot 207edaaef2 🤖 chore: Lighthouse Results | Chat 2024-12-12 00:29:27 +00:00
lobehubbot e18b72ac57 🤖 chore: Lighthouse Results | Welcome 2024-12-11 00:31:50 +00:00
lobehubbot db125ce552 🤖 chore: Lighthouse Results | Settings 2024-12-11 00:29:46 +00:00
lobehubbot 618278845f 🤖 chore: Lighthouse Results | Chat 2024-12-11 00:29:39 +00:00
lobehubbot f47cb915f6 🤖 chore: Lighthouse Results | Welcome 2024-12-10 00:32:09 +00:00
lobehubbot 35b25461dc 🤖 chore: Lighthouse Results | Settings 2024-12-10 00:30:09 +00:00
lobehubbot d4e067e68e 🤖 chore: Lighthouse Results | Chat 2024-12-10 00:30:06 +00:00
lobehubbot 7f0c25b4a5 🤖 chore: Lighthouse Results | Welcome 2024-12-09 00:32:54 +00:00
lobehubbot 8c8cc7cd75 🤖 chore: Lighthouse Results | Chat 2024-12-09 00:30:56 +00:00
lobehubbot 3991ef9d2f 🤖 chore: Lighthouse Results | Settings 2024-12-09 00:30:54 +00:00
lobehubbot ba3ca47915 🤖 chore: Lighthouse Results | Welcome 2024-12-08 00:34:24 +00:00
lobehubbot 972226a7e6 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-12-08 00:32:14 +00:00
lobehubbot f79fb8b9df 🤖 chore: Lighthouse Results | Settings 2024-12-08 00:32:13 +00:00
lobehubbot 460559ebd8 🤖 chore: Lighthouse Results | Chat 2024-12-08 00:32:12 +00:00
lobehubbot 9bed53304d 🤖 chore: Lighthouse Results | Welcome 2024-12-07 00:31:09 +00:00
lobehubbot aafbc127cc 🤖 chore: Lighthouse Results | Chat 2024-12-07 00:29:17 +00:00
lobehubbot 35f76c77f9 🤖 chore: Lighthouse Results | Settings 2024-12-07 00:28:54 +00:00
lobehubbot e276039d55 🤖 chore: Lighthouse Results | Welcome 2024-12-06 00:31:49 +00:00
lobehubbot d5554d0e1b 🤖 chore: Lighthouse Results | Chat 2024-12-06 00:29:30 +00:00
lobehubbot 9eb5928ca2 🤖 chore: Lighthouse Results | Settings 2024-12-06 00:29:29 +00:00
lobehubbot fdf2b32bb5 🤖 chore: Lighthouse Results | Welcome 2024-12-05 00:31:49 +00:00
lobehubbot 57fd95d578 🤖 chore: Lighthouse Results | Settings 2024-12-05 00:29:59 +00:00
lobehubbot 2a03e1a03a 🤖 chore: Lighthouse Results | Chat 2024-12-05 00:29:41 +00:00
lobehubbot 274c6cd124 🤖 chore: Lighthouse Results | Welcome 2024-12-04 00:31:50 +00:00
lobehubbot a92994257a 🤖 chore: Lighthouse Results | Chat 2024-12-04 00:29:45 +00:00
lobehubbot f06748b6b5 🤖 chore: Lighthouse Results | Settings 2024-12-04 00:29:41 +00:00
lobehubbot 0fa54ce433 🤖 chore: Lighthouse Results | Welcome 2024-12-03 00:32:31 +00:00
lobehubbot bd23ea87cf Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-12-03 00:30:26 +00:00
lobehubbot b8defef41c 🤖 chore: Lighthouse Results | Chat 2024-12-03 00:30:25 +00:00
lobehubbot 39c5b18802 🤖 chore: Lighthouse Results | Settings 2024-12-03 00:30:25 +00:00
lobehubbot a75b3b1616 🤖 chore: Lighthouse Results | Welcome 2024-12-02 00:32:51 +00:00
lobehubbot 81c774bd68 🤖 chore: Lighthouse Results | Chat 2024-12-02 00:30:42 +00:00
lobehubbot b4cfef5577 🤖 chore: Lighthouse Results | Settings 2024-12-02 00:30:40 +00:00
lobehubbot f4b5470f44 🤖 chore: Lighthouse Results | Welcome 2024-12-01 00:37:05 +00:00
lobehubbot 00bd28c38a 🤖 chore: Lighthouse Results | Chat 2024-12-01 00:35:35 +00:00
lobehubbot 3e7bbf7e50 🤖 chore: Lighthouse Results | Settings 2024-12-01 00:35:10 +00:00
lobehubbot f405cc5746 🤖 chore: Lighthouse Results | Welcome 2024-11-30 00:30:24 +00:00
lobehubbot c9e99ec3ed 🤖 chore: Lighthouse Results | Chat 2024-11-30 00:28:09 +00:00
lobehubbot 18dce0d2d9 🤖 chore: Lighthouse Results | Settings 2024-11-30 00:28:04 +00:00
lobehubbot 3253cd8a16 🤖 chore: Lighthouse Results | Welcome 2024-11-29 00:31:19 +00:00
lobehubbot 808b9e4752 🤖 chore: Lighthouse Results | Chat 2024-11-29 00:28:58 +00:00
lobehubbot 23f6a79198 🤖 chore: Lighthouse Results | Settings 2024-11-29 00:28:52 +00:00
lobehubbot b889bea6a4 🤖 chore: Lighthouse Results | Welcome 2024-11-28 00:31:11 +00:00
lobehubbot 304a55e296 🤖 chore: Lighthouse Results | Chat 2024-11-28 00:29:22 +00:00
lobehubbot bddbed22ff 🤖 chore: Lighthouse Results | Settings 2024-11-28 00:29:03 +00:00
lobehubbot 1722924867 🤖 chore: Lighthouse Results | Welcome 2024-11-27 00:30:56 +00:00
lobehubbot 8c0f415d17 🤖 chore: Lighthouse Results | Chat 2024-11-27 00:28:59 +00:00
lobehubbot 840f538626 🤖 chore: Lighthouse Results | Settings 2024-11-27 00:28:36 +00:00
lobehubbot 001cfbe023 🤖 chore: Lighthouse Results | Welcome 2024-11-26 00:30:53 +00:00
lobehubbot cde8ab0dbb 🤖 chore: Lighthouse Results | Settings 2024-11-26 00:28:44 +00:00
lobehubbot 5d82315c59 🤖 chore: Lighthouse Results | Chat 2024-11-26 00:28:40 +00:00
lobehubbot 200f56d75c 🤖 chore: Lighthouse Results | Welcome 2024-11-25 00:32:02 +00:00
lobehubbot cfd207f48a 🤖 chore: Lighthouse Results | Settings 2024-11-25 00:29:41 +00:00
lobehubbot 03faea6385 🤖 chore: Lighthouse Results | Chat 2024-11-25 00:29:37 +00:00
lobehubbot 0223f81383 🤖 chore: Lighthouse Results | Welcome 2024-11-24 00:33:45 +00:00
lobehubbot e24e53cb73 🤖 chore: Lighthouse Results | Chat 2024-11-24 00:31:03 +00:00
lobehubbot 058a995d0b 🤖 chore: Lighthouse Results | Settings 2024-11-24 00:30:59 +00:00
lobehubbot 602d4b46f3 🤖 chore: Lighthouse Results | Welcome 2024-11-23 00:30:41 +00:00
lobehubbot e836d14ccf 🤖 chore: Lighthouse Results | Chat 2024-11-23 00:27:57 +00:00
lobehubbot ff59976fa1 🤖 chore: Lighthouse Results | Settings 2024-11-23 00:27:55 +00:00
lobehubbot 4fd484df99 🤖 chore: Lighthouse Results | Welcome 2024-11-22 00:30:57 +00:00
lobehubbot dc700ce2ad 🤖 chore: Lighthouse Results | Chat 2024-11-22 00:29:01 +00:00
lobehubbot eb8d5104a5 🤖 chore: Lighthouse Results | Settings 2024-11-22 00:28:55 +00:00
lobehubbot 5fec178cf1 🤖 chore: Lighthouse Results | Welcome 2024-11-21 00:30:27 +00:00
lobehubbot 954cbd7e07 🤖 chore: Lighthouse Results | Chat 2024-11-21 00:28:22 +00:00
lobehubbot 94b5bdb61e 🤖 chore: Lighthouse Results | Settings 2024-11-21 00:28:14 +00:00
lobehubbot 81a155dde6 🤖 chore: Lighthouse Results | Welcome 2024-11-20 00:30:15 +00:00
lobehubbot 425f02be13 🤖 chore: Lighthouse Results | Chat 2024-11-20 00:28:24 +00:00
lobehubbot 8e0205e136 🤖 chore: Lighthouse Results | Settings 2024-11-20 00:28:18 +00:00
lobehubbot c5d0be57fd 🤖 chore: Lighthouse Results | Welcome 2024-11-19 00:30:40 +00:00
lobehubbot 35525dd13a 🤖 chore: Lighthouse Results | Chat 2024-11-19 00:28:52 +00:00
lobehubbot 17eae18117 🤖 chore: Lighthouse Results | Settings 2024-11-19 00:28:32 +00:00
lobehubbot 2162081e2f 🤖 chore: Lighthouse Results | Welcome 2024-11-18 00:32:02 +00:00
lobehubbot db29b2ed53 🤖 chore: Lighthouse Results | Chat 2024-11-18 00:30:34 +00:00
lobehubbot 7bcf8f11a5 🤖 chore: Lighthouse Results | Settings 2024-11-18 00:30:31 +00:00
lobehubbot 29a639b6a8 🤖 chore: Lighthouse Results | Welcome 2024-11-17 00:33:05 +00:00
lobehubbot 8625330b02 🤖 chore: Lighthouse Results | Chat 2024-11-17 00:30:38 +00:00
lobehubbot 76ae78e376 🤖 chore: Lighthouse Results | Settings 2024-11-17 00:30:33 +00:00
lobehubbot fe18faff00 🤖 chore: Lighthouse Results | Welcome 2024-11-16 00:30:13 +00:00
lobehubbot f26268846f 🤖 chore: Lighthouse Results | Settings 2024-11-16 00:28:14 +00:00
lobehubbot f947b61bd8 🤖 chore: Lighthouse Results | Welcome 2024-11-15 00:30:55 +00:00
lobehubbot 519d3cfd45 🤖 chore: Lighthouse Results | Chat 2024-11-15 00:28:54 +00:00
lobehubbot 75452b3c6a 🤖 chore: Lighthouse Results | Settings 2024-11-15 00:28:41 +00:00
lobehubbot 9167d4a23d 🤖 chore: Lighthouse Results | Welcome 2024-11-14 00:29:25 +00:00
lobehubbot 0a4ddefa45 🤖 chore: Lighthouse Results | Chat 2024-11-14 00:27:20 +00:00
lobehubbot f1ade10360 🤖 chore: Lighthouse Results | Settings 2024-11-14 00:27:12 +00:00
lobehubbot ee345fa8c8 🤖 chore: Lighthouse Results | Welcome 2024-11-13 00:29:05 +00:00
lobehubbot 40172cabd1 🤖 chore: Lighthouse Results | Settings 2024-11-13 00:27:13 +00:00
lobehubbot e9440be4d1 🤖 chore: Lighthouse Results | Chat 2024-11-13 00:27:01 +00:00
lobehubbot ef5ceea994 🤖 chore: Lighthouse Results | Welcome 2024-11-12 00:28:28 +00:00
lobehubbot 7f02b93e67 🤖 chore: Lighthouse Results | Chat 2024-11-12 00:26:22 +00:00
lobehubbot df33463d58 🤖 chore: Lighthouse Results | Settings 2024-11-12 00:26:20 +00:00
lobehubbot a54e3edd57 🤖 chore: Lighthouse Results | Welcome 2024-11-11 00:30:25 +00:00
lobehubbot a56e588dd3 🤖 chore: Lighthouse Results | Chat 2024-11-11 00:28:00 +00:00
lobehubbot 955569c83a 🤖 chore: Lighthouse Results | Settings 2024-11-11 00:27:54 +00:00
lobehubbot e8ed741d0e 🤖 chore: Lighthouse Results | Welcome 2024-11-10 00:31:11 +00:00
lobehubbot ba406d40e0 🤖 chore: Lighthouse Results | Chat 2024-11-10 00:29:23 +00:00
lobehubbot 8d188a2486 🤖 chore: Lighthouse Results | Settings 2024-11-10 00:29:01 +00:00
lobehubbot 1ca6f61bdd 🤖 chore: Lighthouse Results | Welcome 2024-11-09 00:28:29 +00:00
lobehubbot 0a9fc79dfa 🤖 chore: Lighthouse Results | Settings 2024-11-09 00:26:11 +00:00
lobehubbot cfd485e9bc 🤖 chore: Lighthouse Results | Chat 2024-11-09 00:26:06 +00:00
lobehubbot 8a81fa3064 🤖 chore: Lighthouse Results | Welcome 2024-11-08 00:28:43 +00:00
lobehubbot 07c280a310 🤖 chore: Lighthouse Results | Chat 2024-11-08 00:26:38 +00:00
lobehubbot 77d7bd2446 🤖 chore: Lighthouse Results | Settings 2024-11-08 00:26:32 +00:00
lobehubbot cf5b264425 🤖 chore: Lighthouse Results | Welcome 2024-11-07 00:28:51 +00:00
lobehubbot d39436ab6f 🤖 chore: Lighthouse Results | Chat 2024-11-07 00:26:51 +00:00
lobehubbot 0d621f394b 🤖 chore: Lighthouse Results | Settings 2024-11-07 00:26:38 +00:00
lobehubbot af1c2513a7 🤖 chore: Lighthouse Results | Welcome 2024-11-06 00:28:43 +00:00
lobehubbot a6a4d277e4 🤖 chore: Lighthouse Results | Chat 2024-11-06 00:26:39 +00:00
lobehubbot 3986f54da3 🤖 chore: Lighthouse Results | Settings 2024-11-06 00:26:35 +00:00
lobehubbot 062f673771 🤖 chore: Lighthouse Results | Welcome 2024-11-05 00:28:54 +00:00
lobehubbot 7ca7359321 🤖 chore: Lighthouse Results | Settings 2024-11-05 00:26:50 +00:00
lobehubbot 869f41a7bf 🤖 chore: Lighthouse Results | Chat 2024-11-05 00:26:49 +00:00
lobehubbot 741dc406c1 🤖 chore: Lighthouse Results | Welcome 2024-11-04 00:30:43 +00:00
lobehubbot 0bd3693943 🤖 chore: Lighthouse Results | Chat 2024-11-04 00:28:30 +00:00
lobehubbot 66be752d56 🤖 chore: Lighthouse Results | Settings 2024-11-04 00:28:23 +00:00
lobehubbot e2df1f55e2 🤖 chore: Lighthouse Results | Welcome 2024-11-03 00:34:19 +00:00
lobehubbot 1c23c259d0 🤖 chore: Lighthouse Results | Chat 2024-11-03 00:31:26 +00:00
lobehubbot 3b1ad5ed4a 🤖 chore: Lighthouse Results | Settings 2024-11-03 00:31:15 +00:00
lobehubbot 69be3b2093 🤖 chore: Lighthouse Results | Welcome 2024-11-02 00:28:45 +00:00
lobehubbot d8e951be7b 🤖 chore: Lighthouse Results | Chat 2024-11-02 00:26:39 +00:00
lobehubbot 9152a6056d 🤖 chore: Lighthouse Results | Settings 2024-11-02 00:26:36 +00:00
lobehubbot 098c0f9bb2 🤖 chore: Lighthouse Results | Welcome 2024-11-01 00:32:27 +00:00
lobehubbot 6fa0df3754 🤖 chore: Lighthouse Results | Chat 2024-11-01 00:30:51 +00:00
lobehubbot 464c77744f 🤖 chore: Lighthouse Results | Settings 2024-11-01 00:30:13 +00:00
lobehubbot 0b2a95be31 🤖 chore: Lighthouse Results | Welcome 2024-10-31 00:29:40 +00:00
lobehubbot e610b9aa77 🤖 chore: Lighthouse Results | Chat 2024-10-31 00:27:42 +00:00
lobehubbot 4aad143869 🤖 chore: Lighthouse Results | Settings 2024-10-31 00:27:26 +00:00
lobehubbot 43cf34a907 🤖 chore: Lighthouse Results | Welcome 2024-10-30 00:29:28 +00:00
lobehubbot e4900ef555 🤖 chore: Lighthouse Results | Chat 2024-10-30 00:27:26 +00:00
lobehubbot a33b591f03 🤖 chore: Lighthouse Results | Settings 2024-10-30 00:27:19 +00:00
lobehubbot b01a3c4d22 🤖 chore: Lighthouse Results | Welcome 2024-10-29 00:30:01 +00:00
lobehubbot 59059704fe 🤖 chore: Lighthouse Results | Settings 2024-10-29 00:28:08 +00:00
lobehubbot 5e1a79b1d0 🤖 chore: Lighthouse Results | Chat 2024-10-29 00:27:53 +00:00
lobehubbot 2e495b8f25 🤖 chore: Lighthouse Results | Welcome 2024-10-28 00:31:11 +00:00
lobehubbot 838e8edb0c 🤖 chore: Lighthouse Results | Settings 2024-10-28 00:29:09 +00:00
lobehubbot 1c62820690 🤖 chore: Lighthouse Results | Chat 2024-10-28 00:28:50 +00:00
lobehubbot 194efbdbae 🤖 chore: Lighthouse Results | Welcome 2024-10-27 00:31:45 +00:00
lobehubbot 9f0559df9a 🤖 chore: Lighthouse Results | Chat 2024-10-27 00:29:35 +00:00
lobehubbot 8e90760f0f 🤖 chore: Lighthouse Results | Settings 2024-10-27 00:29:26 +00:00
lobehubbot 5e3a1e43d9 🤖 chore: Lighthouse Results | Welcome 2024-10-26 00:28:48 +00:00
lobehubbot f43a0454ff 🤖 chore: Lighthouse Results | Settings 2024-10-26 00:26:28 +00:00
lobehubbot 2f2ed0a749 🤖 chore: Lighthouse Results | Chat 2024-10-26 00:26:25 +00:00
lobehubbot ef971e3b35 🤖 chore: Lighthouse Results | Welcome 2024-10-25 00:29:24 +00:00
lobehubbot 27469f4507 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-10-25 00:27:14 +00:00
lobehubbot b8140164f5 🤖 chore: Lighthouse Results | Settings 2024-10-25 00:27:13 +00:00
lobehubbot 3ae8f65665 🤖 chore: Lighthouse Results | Chat 2024-10-25 00:27:13 +00:00
lobehubbot 75f8638837 🤖 chore: Lighthouse Results | Welcome 2024-10-24 00:30:59 +00:00
lobehubbot 0b7ea79ab9 🤖 chore: Lighthouse Results | Settings 2024-10-24 00:27:07 +00:00
lobehubbot c41942385a 🤖 chore: Lighthouse Results | Chat 2024-10-24 00:27:03 +00:00
lobehubbot 6da153ea9a 🤖 chore: Lighthouse Results | Welcome 2024-10-23 00:29:10 +00:00
lobehubbot f0c6f0ea68 🤖 chore: Lighthouse Results | Chat 2024-10-23 00:27:11 +00:00
lobehubbot c7d8c6c7fb 🤖 chore: Lighthouse Results | Settings 2024-10-23 00:26:46 +00:00
lobehubbot 3c8f426f32 🤖 chore: Lighthouse Results | Welcome 2024-10-22 00:29:25 +00:00
lobehubbot 7f136b9bb7 🤖 chore: Lighthouse Results | Chat 2024-10-22 00:27:15 +00:00
lobehubbot 2a6045904a 🤖 chore: Lighthouse Results | Settings 2024-10-22 00:27:11 +00:00
lobehubbot b5196959dd 🤖 chore: Lighthouse Results | Welcome 2024-10-21 00:30:33 +00:00
lobehubbot 1bc7cedd0d 🤖 chore: Lighthouse Results | Settings 2024-10-21 00:28:33 +00:00
lobehubbot 55a8c8fa46 🤖 chore: Lighthouse Results | Chat 2024-10-21 00:28:31 +00:00
lobehubbot 364f406724 🤖 chore: Lighthouse Results | Welcome 2024-10-20 00:31:48 +00:00
lobehubbot f334a6144c 🤖 chore: Lighthouse Results | Settings 2024-10-20 00:29:52 +00:00
lobehubbot 6fddfdee94 🤖 chore: Lighthouse Results | Chat 2024-10-20 00:29:43 +00:00
lobehubbot eb6421b91d 🤖 chore: Lighthouse Results | Welcome 2024-10-19 00:28:39 +00:00
lobehubbot 6deb9a8c9e 🤖 chore: Lighthouse Results | Chat 2024-10-19 00:26:47 +00:00
lobehubbot b77eba2e4e 🤖 chore: Lighthouse Results | Settings 2024-10-19 00:26:28 +00:00
lobehubbot 8cba37abdc 🤖 chore: Lighthouse Results | Chat 2024-10-18 00:26:45 +00:00
lobehubbot 286f196cfe 🤖 chore: Lighthouse Results | Settings 2024-10-18 00:26:43 +00:00
lobehubbot 8b98a26ec5 🤖 chore: Lighthouse Results | Welcome 2024-10-17 00:28:57 +00:00
lobehubbot 72e44721cc 🤖 chore: Lighthouse Results | Settings 2024-10-17 00:26:55 +00:00
lobehubbot 3bdb3f517f 🤖 chore: Lighthouse Results | Chat 2024-10-17 00:26:52 +00:00
lobehubbot 5cf496fbb4 🤖 chore: Lighthouse Results | Welcome 2024-10-16 00:29:05 +00:00
lobehubbot 8f1576af2b 🤖 chore: Lighthouse Results | Chat 2024-10-16 00:27:18 +00:00
lobehubbot 70701b63d6 🤖 chore: Lighthouse Results | Settings 2024-10-16 00:26:56 +00:00
lobehubbot 54ffb2883d 🤖 chore: Lighthouse Results | Welcome 2024-10-15 00:29:26 +00:00
lobehubbot d0a5c234f8 🤖 chore: Lighthouse Results | Settings 2024-10-15 00:27:10 +00:00
lobehubbot 98a88c50e3 🤖 chore: Lighthouse Results | Chat 2024-10-15 00:27:09 +00:00
lobehubbot 1d422dca6a 🤖 chore: Lighthouse Results | Welcome 2024-10-14 00:30:29 +00:00
lobehubbot e4495ef3ad 🤖 chore: Lighthouse Results | Settings 2024-10-14 00:28:23 +00:00
lobehubbot 620152fd01 🤖 chore: Lighthouse Results | Chat 2024-10-14 00:28:04 +00:00
lobehubbot 761075c27c 🤖 chore: Lighthouse Results | Welcome 2024-10-13 00:31:17 +00:00
lobehubbot 125cd36f25 🤖 chore: Lighthouse Results | Chat 2024-10-13 00:29:24 +00:00
lobehubbot 6133efb039 🤖 chore: Lighthouse Results | Settings 2024-10-13 00:29:07 +00:00
lobehubbot 52a8ccd649 🤖 chore: Lighthouse Results | Welcome 2024-10-12 00:28:06 +00:00
lobehubbot 27aed8ed68 🤖 chore: Lighthouse Results | Chat 2024-10-12 00:26:17 +00:00
lobehubbot 4ff0dcb2a3 🤖 chore: Lighthouse Results | Settings 2024-10-12 00:25:54 +00:00
lobehubbot be1708b80e 🤖 chore: Lighthouse Results | Welcome 2024-10-11 00:28:40 +00:00
lobehubbot c1ee603d77 🤖 chore: Lighthouse Results | Settings 2024-10-11 00:26:49 +00:00
lobehubbot 0f4ac990e6 🤖 chore: Lighthouse Results | Chat 2024-10-11 00:26:31 +00:00
lobehubbot 3f1e7c2423 🤖 chore: Lighthouse Results | Welcome 2024-10-10 00:28:47 +00:00
lobehubbot 38c984344b 🤖 chore: Lighthouse Results | Chat 2024-10-10 00:26:25 +00:00
lobehubbot d6659f2395 🤖 chore: Lighthouse Results | Settings 2024-10-10 00:26:19 +00:00
lobehubbot e90f10a236 🤖 chore: Lighthouse Results | Welcome 2024-10-09 00:29:04 +00:00
lobehubbot 11f9b04410 🤖 chore: Lighthouse Results | Chat 2024-10-09 00:26:36 +00:00
lobehubbot 531e4b52cc 🤖 chore: Lighthouse Results | Settings 2024-10-09 00:26:34 +00:00
lobehubbot 5b15610288 🤖 chore: Lighthouse Results | Welcome 2024-10-08 00:28:54 +00:00
lobehubbot 8c729f4948 🤖 chore: Lighthouse Results | Settings 2024-10-08 00:26:22 +00:00
lobehubbot 4a79fbfe55 🤖 chore: Lighthouse Results | Chat 2024-10-08 00:26:21 +00:00
lobehubbot d97dcfdb9e 🤖 chore: Lighthouse Results | Welcome 2024-10-07 00:30:16 +00:00
lobehubbot f46cb637b5 🤖 chore: Lighthouse Results | Chat 2024-10-07 00:28:14 +00:00
lobehubbot 2e8ba4227c 🤖 chore: Lighthouse Results | Settings 2024-10-07 00:28:03 +00:00
lobehubbot c2ba60a497 🤖 chore: Lighthouse Results | Welcome 2024-10-06 00:31:38 +00:00
lobehubbot a732ff46b9 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-10-06 00:29:19 +00:00
lobehubbot 81e0253a2b 🤖 chore: Lighthouse Results | Chat 2024-10-06 00:29:17 +00:00
lobehubbot 4a42190768 🤖 chore: Lighthouse Results | Settings 2024-10-06 00:29:17 +00:00
lobehubbot 7de6b34d1f 🤖 chore: Lighthouse Results | Welcome 2024-10-05 00:28:41 +00:00
lobehubbot 9da1812022 🤖 chore: Lighthouse Results | Settings 2024-10-05 00:26:25 +00:00
lobehubbot f3d9f47fd2 🤖 chore: Lighthouse Results | Chat 2024-10-05 00:26:19 +00:00
lobehubbot 6a699a5194 🤖 chore: Lighthouse Results | Welcome 2024-10-04 00:28:51 +00:00
lobehubbot fa3b5f95c5 🤖 chore: Lighthouse Results | Settings 2024-10-04 00:26:38 +00:00
lobehubbot fd82aceabd 🤖 chore: Lighthouse Results | Welcome 2024-10-03 00:28:43 +00:00
lobehubbot e16956c985 🤖 chore: Lighthouse Results | Chat 2024-10-03 00:26:44 +00:00
lobehubbot cbdbdde3c5 🤖 chore: Lighthouse Results | Settings 2024-10-03 00:26:32 +00:00
lobehubbot 4bbcc58b10 🤖 chore: Lighthouse Results | Welcome 2024-10-02 00:28:37 +00:00
lobehubbot 8deb13c805 🤖 chore: Lighthouse Results | Chat 2024-10-02 00:26:31 +00:00
lobehubbot 7a4fc8ee96 🤖 chore: Lighthouse Results | Settings 2024-10-02 00:26:29 +00:00
lobehubbot cd879b5802 🤖 chore: Lighthouse Results | Welcome 2024-10-01 00:32:22 +00:00
lobehubbot 12691d7162 🤖 chore: Lighthouse Results | Settings 2024-10-01 00:30:10 +00:00
lobehubbot 7153cf0a43 🤖 chore: Lighthouse Results | Chat 2024-10-01 00:29:58 +00:00
lobehubbot c8bc3488af 🤖 chore: Lighthouse Results | Welcome 2024-09-30 00:30:05 +00:00
lobehubbot 798e89f7e3 🤖 chore: Lighthouse Results | Settings 2024-09-30 00:28:09 +00:00
lobehubbot b85d4a4539 🤖 chore: Lighthouse Results | Chat 2024-09-30 00:27:58 +00:00
lobehubbot ecc540774e 🤖 chore: Lighthouse Results | Welcome 2024-09-29 00:31:59 +00:00
lobehubbot 8099279a02 🤖 chore: Lighthouse Results | Settings 2024-09-29 00:30:22 +00:00
lobehubbot 6fb282e896 🤖 chore: Lighthouse Results | Chat 2024-09-29 00:30:03 +00:00
lobehubbot 3572f755c3 🤖 chore: Lighthouse Results | Welcome 2024-09-28 00:28:22 +00:00
lobehubbot 324f7a1d55 🤖 chore: Lighthouse Results | Chat 2024-09-28 00:26:32 +00:00
lobehubbot a6cb48c96a 🤖 chore: Lighthouse Results | Settings 2024-09-28 00:26:22 +00:00
lobehubbot d8f9c0ec5e 🤖 chore: Lighthouse Results | Welcome 2024-09-27 00:28:41 +00:00
lobehubbot 54d3ceffe8 🤖 chore: Lighthouse Results | Chat 2024-09-27 00:26:40 +00:00
lobehubbot d8b15c595a 🤖 chore: Lighthouse Results | Settings 2024-09-27 00:26:29 +00:00
lobehubbot c8db8f3ade 🤖 chore: Lighthouse Results | Welcome 2024-09-26 00:28:39 +00:00
lobehubbot e697fb44d5 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-09-26 00:26:16 +00:00
lobehubbot ada3734e44 🤖 chore: Lighthouse Results | Settings 2024-09-26 00:26:16 +00:00
lobehubbot 00ef72f286 🤖 chore: Lighthouse Results | Chat 2024-09-26 00:26:15 +00:00
lobehubbot 96ef8181fe 🤖 chore: Lighthouse Results | Welcome 2024-09-25 00:29:13 +00:00
lobehubbot 5852bf5f3b 🤖 chore: Lighthouse Results | Chat 2024-09-25 00:28:07 +00:00
lobehubbot 2cf7b658a4 🤖 chore: Lighthouse Results | Settings 2024-09-25 00:27:38 +00:00
lobehubbot cc79324af8 🤖 chore: Lighthouse Results | Welcome 2024-09-24 00:29:07 +00:00
lobehubbot cad99c7a43 🤖 chore: Lighthouse Results | Chat 2024-09-24 00:27:18 +00:00
lobehubbot de46c76283 🤖 chore: Lighthouse Results | Settings 2024-09-24 00:27:04 +00:00
lobehubbot fd7479bf2d 🤖 chore: Lighthouse Results | Welcome 2024-09-23 00:29:24 +00:00
lobehubbot 1db96bd00a 🤖 chore: Lighthouse Results | Settings 2024-09-23 00:28:34 +00:00
lobehubbot a4e5ec4cb7 🤖 chore: Lighthouse Results | Chat 2024-09-23 00:28:07 +00:00
lobehubbot b4a87d486e 🤖 chore: Lighthouse Results | Welcome 2024-09-22 00:31:36 +00:00
lobehubbot 7bf696bac7 🤖 chore: Lighthouse Results | Chat 2024-09-22 00:29:42 +00:00
lobehubbot 271a5e6c6c 🤖 chore: Lighthouse Results | Settings 2024-09-22 00:29:24 +00:00
lobehubbot b2e83f9859 🤖 chore: Lighthouse Results | Welcome 2024-09-21 00:28:20 +00:00
lobehubbot 0da66d831f 🤖 chore: Lighthouse Results | Market 2024-09-21 00:26:36 +00:00
lobehubbot 8a9fc29f90 🤖 chore: Lighthouse Results | Settings 2024-09-21 00:26:21 +00:00
lobehubbot 731665481d 🤖 chore: Lighthouse Results | Chat 2024-09-21 00:26:20 +00:00
lobehubbot f19597e20a 🤖 chore: Lighthouse Results | Welcome 2024-09-20 00:28:22 +00:00
lobehubbot 4959b087ff 🤖 chore: Lighthouse Results | Chat 2024-09-20 00:26:55 +00:00
lobehubbot 5eef140bfd 🤖 chore: Lighthouse Results | Market 2024-09-20 00:26:47 +00:00
lobehubbot 10991058ac 🤖 chore: Lighthouse Results | Settings 2024-09-20 00:26:37 +00:00
lobehubbot e4c2a17ef7 🤖 chore: Lighthouse Results | Welcome 2024-09-19 00:28:10 +00:00
lobehubbot 80c1061e42 🤖 chore: Lighthouse Results | Chat 2024-09-19 00:26:47 +00:00
lobehubbot 46e72334ca 🤖 chore: Lighthouse Results | Settings 2024-09-19 00:26:42 +00:00
lobehubbot 26122adce3 🤖 chore: Lighthouse Results | Welcome 2024-09-18 00:28:00 +00:00
lobehubbot 424d13345c 🤖 chore: Lighthouse Results | Settings 2024-09-18 00:26:11 +00:00
lobehubbot da7d337ebf 🤖 chore: Lighthouse Results | Chat 2024-09-18 00:26:10 +00:00
lobehubbot 19a7107121 🤖 chore: Lighthouse Results | Market 2024-09-18 00:26:08 +00:00
lobehubbot 227457e58a 🤖 chore: Lighthouse Results | Welcome 2024-09-17 00:24:54 +00:00
lobehubbot 9a39b3706f 🤖 chore: Lighthouse Results | Settings 2024-09-17 00:22:52 +00:00
lobehubbot ec02fd3795 🤖 chore: Lighthouse Results | Chat 2024-09-17 00:22:21 +00:00
lobehubbot 52cf91a88c 🤖 chore: Lighthouse Results | Market 2024-09-17 00:22:16 +00:00
lobehubbot 0489bfe05e 🤖 chore: Lighthouse Results | Welcome 2024-09-16 00:29:48 +00:00
lobehubbot 593c57ada1 🤖 chore: Lighthouse Results | Settings 2024-09-16 00:27:40 +00:00
lobehubbot e4725ad547 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-09-16 00:27:35 +00:00
lobehubbot f332845a82 🤖 chore: Lighthouse Results | Chat 2024-09-16 00:27:34 +00:00
lobehubbot d4a83591e7 🤖 chore: Lighthouse Results | Market 2024-09-16 00:27:34 +00:00
lobehubbot 6be55d69b6 🤖 chore: Lighthouse Results | Welcome 2024-09-15 00:31:06 +00:00
lobehubbot ffbdfa046c 🤖 chore: Lighthouse Results | Chat 2024-09-15 00:29:24 +00:00
lobehubbot bf96f92174 🤖 chore: Lighthouse Results | Market 2024-09-15 00:29:18 +00:00
lobehubbot faf13f860b 🤖 chore: Lighthouse Results | Welcome 2024-09-14 00:27:21 +00:00
lobehubbot 243b019b6d 🤖 chore: Lighthouse Results | Market 2024-09-14 00:25:41 +00:00
lobehubbot 5d31fcbbbf 🤖 chore: Lighthouse Results | Settings 2024-09-14 00:25:33 +00:00
lobehubbot bb77fb0ee7 🤖 chore: Lighthouse Results | Welcome 2024-09-13 00:27:56 +00:00
lobehubbot 39c4b2b0cb 🤖 chore: Lighthouse Results | Market 2024-09-13 00:26:35 +00:00
lobehubbot fb5df0e12c 🤖 chore: Lighthouse Results | Chat 2024-09-13 00:25:52 +00:00
lobehubbot 48486e54a4 🤖 chore: Lighthouse Results | Settings 2024-09-13 00:25:49 +00:00
lobehubbot 26d32bfc9d 🤖 chore: Lighthouse Results | Welcome 2024-09-12 00:28:08 +00:00
lobehubbot 8d89237202 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-09-12 00:26:08 +00:00
lobehubbot dbc1db18f6 🤖 chore: Lighthouse Results | Market 2024-09-12 00:26:07 +00:00
lobehubbot 8588d78bfe 🤖 chore: Lighthouse Results | Chat 2024-09-12 00:26:06 +00:00
lobehubbot b8cfd671ac 🤖 chore: Lighthouse Results | Settings 2024-09-12 00:25:58 +00:00
lobehubbot de459ba0e2 🤖 chore: Lighthouse Results | Welcome 2024-09-09 00:29:53 +00:00
lobehubbot 67db0f1f67 🤖 chore: Lighthouse Results | Chat 2024-09-09 00:27:54 +00:00
lobehubbot fb4750ce3d 🤖 chore: Lighthouse Results | Market 2024-09-09 00:27:47 +00:00
lobehubbot 8c17dd0878 🤖 chore: Lighthouse Results | Settings 2024-09-09 00:27:11 +00:00
lobehubbot ccaf9a6841 🤖 chore: Lighthouse Results | Welcome 2024-09-08 00:30:35 +00:00
lobehubbot 8a4e882a67 🤖 chore: Lighthouse Results | Settings 2024-09-08 00:28:35 +00:00
lobehubbot 0aaea3c8ce 🤖 chore: Lighthouse Results | Market 2024-09-08 00:28:34 +00:00
lobehubbot b86fdcbb9f 🤖 chore: Lighthouse Results | Chat 2024-09-08 00:28:23 +00:00
lobehubbot 3707253092 🤖 chore: Lighthouse Results | Welcome 2024-09-07 00:26:59 +00:00
lobehubbot c2deef3fcf 🤖 chore: Lighthouse Results | Chat 2024-09-07 00:25:42 +00:00
lobehubbot 3dfd6be20f 🤖 chore: Lighthouse Results | Market 2024-09-07 00:25:17 +00:00
lobehubbot 8706fe2fea 🤖 chore: Lighthouse Results | Settings 2024-09-07 00:25:10 +00:00
lobehubbot 52e7eeb4da 🤖 chore: Lighthouse Results | Welcome 2024-09-06 00:27:33 +00:00
lobehubbot fa95196af4 🤖 chore: Lighthouse Results | Chat 2024-09-06 00:26:26 +00:00
lobehubbot 3d6f1906e9 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-09-06 00:26:10 +00:00
lobehubbot 0d43302752 🤖 chore: Lighthouse Results | Market 2024-09-06 00:26:09 +00:00
lobehubbot b6ae51c1e2 🤖 chore: Lighthouse Results | Settings 2024-09-06 00:26:08 +00:00
lobehubbot e767610291 🤖 chore: Lighthouse Results | Welcome 2024-09-05 00:28:00 +00:00
lobehubbot c63e68505b 🤖 chore: Lighthouse Results | Chat 2024-09-05 00:26:00 +00:00
lobehubbot a376ce99ea 🤖 chore: Lighthouse Results | Market 2024-09-05 00:25:48 +00:00
lobehubbot 87bfc21ec8 🤖 chore: Lighthouse Results | Settings 2024-09-05 00:25:41 +00:00
lobehubbot c1af5ca4f1 🤖 chore: Lighthouse Results | Welcome 2024-09-04 00:27:35 +00:00
lobehubbot 7e3f6de904 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-09-04 00:25:49 +00:00
lobehubbot c8041d9472 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-09-04 00:25:47 +00:00
lobehubbot 7c73148247 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-09-04 00:25:47 +00:00
lobehubbot cc750ad185 🤖 chore: Lighthouse Results | Chat 2024-09-04 00:25:47 +00:00
lobehubbot bd3417efa7 🤖 chore: Lighthouse Results | Settings 2024-09-04 00:25:47 +00:00
lobehubbot f400440460 🤖 chore: Lighthouse Results | Market 2024-09-04 00:25:46 +00:00
lobehubbot 2c704079fb 🤖 chore: Lighthouse Results | Welcome 2024-09-03 00:27:36 +00:00
lobehubbot ab89da6378 🤖 chore: Lighthouse Results | Market 2024-09-03 00:25:34 +00:00
lobehubbot 78a043d73e 🤖 chore: Lighthouse Results | Chat 2024-09-03 00:25:25 +00:00
lobehubbot aea0e3c855 🤖 chore: Lighthouse Results | Settings 2024-09-03 00:25:15 +00:00
lobehubbot ee3edf62d3 🤖 chore: Lighthouse Results | Welcome 2024-09-02 00:28:40 +00:00
lobehubbot af18497bb1 🤖 chore: Lighthouse Results | Market 2024-09-02 00:26:49 +00:00
lobehubbot 0c9dcaf22d 🤖 chore: Lighthouse Results | Chat 2024-09-02 00:26:41 +00:00
lobehubbot 03318fbec3 🤖 chore: Lighthouse Results | Settings 2024-09-02 00:26:38 +00:00
lobehubbot a19fd2aec1 🤖 chore: Lighthouse Results | Welcome 2024-09-01 00:32:42 +00:00
lobehubbot bdcc039d28 🤖 chore: Lighthouse Results | Chat 2024-09-01 00:31:16 +00:00
lobehubbot b36dc98db3 🤖 chore: Lighthouse Results | Settings 2024-09-01 00:31:13 +00:00
lobehubbot 12c7c8bf2e 🤖 chore: Lighthouse Results | Market 2024-09-01 00:30:39 +00:00
lobehubbot b8cafb02dc 🤖 chore: Lighthouse Results | Welcome 2024-08-31 00:27:08 +00:00
lobehubbot 56816f88c9 🤖 chore: Lighthouse Results | Settings 2024-08-31 00:25:42 +00:00
lobehubbot e7fe1b11b5 🤖 chore: Lighthouse Results | Market 2024-08-31 00:25:34 +00:00
lobehubbot 44cad087d4 🤖 chore: Lighthouse Results | Chat 2024-08-31 00:25:19 +00:00
lobehubbot ecf430e91f 🤖 chore: Lighthouse Results | Welcome 2024-08-30 00:27:35 +00:00
lobehubbot fc62be181c 🤖 chore: Lighthouse Results | Market 2024-08-30 00:25:41 +00:00
lobehubbot c9f198f778 🤖 chore: Lighthouse Results | Chat 2024-08-30 00:25:38 +00:00
lobehubbot 3d0c21108b 🤖 chore: Lighthouse Results | Settings 2024-08-30 00:25:37 +00:00
lobehubbot 07b12c1500 🤖 chore: Lighthouse Results | Welcome 2024-08-29 00:27:14 +00:00
lobehubbot d6f4e11be1 🤖 chore: Lighthouse Results | Market 2024-08-29 00:25:36 +00:00
lobehubbot ebb53c7c7a 🤖 chore: Lighthouse Results | Settings 2024-08-29 00:25:12 +00:00
lobehubbot cc9e7dce47 🤖 chore: Lighthouse Results | Chat 2024-08-29 00:25:08 +00:00
lobehubbot 6f363cf52f 🤖 chore: Lighthouse Results | Welcome 2024-08-28 00:26:47 +00:00
lobehubbot ba1d609fdb 🤖 chore: Lighthouse Results | Market 2024-08-28 00:25:10 +00:00
lobehubbot d24898abd1 🤖 chore: Lighthouse Results | Settings 2024-08-28 00:25:09 +00:00
lobehubbot 02c9b77a7e 🤖 chore: Lighthouse Results | Chat 2024-08-28 00:24:56 +00:00
lobehubbot 9d78dc7ffb 🤖 chore: Lighthouse Results | Welcome 2024-08-27 00:27:00 +00:00
lobehubbot 5e831e9dd4 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-08-27 00:25:06 +00:00
lobehubbot a0cf89d784 🤖 chore: Lighthouse Results | Chat 2024-08-27 00:25:05 +00:00
lobehubbot dc6e1083f8 🤖 chore: Lighthouse Results | Settings 2024-08-27 00:25:05 +00:00
lobehubbot 3ae64130ea 🤖 chore: Lighthouse Results | Market 2024-08-27 00:24:48 +00:00
lobehubbot fcb283f334 🤖 chore: Lighthouse Results | Welcome 2024-08-26 00:27:03 +00:00
lobehubbot ad4df46d12 🤖 chore: Lighthouse Results | Market 2024-08-26 00:25:58 +00:00
lobehubbot b4af21c06b 🤖 chore: Lighthouse Results | Chat 2024-08-26 00:25:11 +00:00
lobehubbot fecb6b70dc 🤖 chore: Lighthouse Results | Settings 2024-08-26 00:24:57 +00:00
lobehubbot 9d1231070c 🤖 chore: Lighthouse Results | Welcome 2024-08-25 00:29:03 +00:00
lobehubbot 7b6bf25731 🤖 chore: Lighthouse Results | Settings 2024-08-25 00:27:15 +00:00
lobehubbot 888fab0c8a 🤖 chore: Lighthouse Results | Chat 2024-08-25 00:27:03 +00:00
lobehubbot 08a770752e 🤖 chore: Lighthouse Results | Market 2024-08-25 00:27:02 +00:00
lobehubbot 9fd5422f94 🤖 chore: Lighthouse Results | Welcome 2024-08-24 00:25:44 +00:00
lobehubbot 8afb8c8f73 🤖 chore: Lighthouse Results | Settings 2024-08-24 00:24:15 +00:00
lobehubbot 441234b514 🤖 chore: Lighthouse Results | Market 2024-08-24 00:24:10 +00:00
lobehubbot f137dd5b09 🤖 chore: Lighthouse Results | Chat 2024-08-24 00:23:47 +00:00
lobehubbot b4a96fc974 🤖 chore: Lighthouse Results | Welcome 2024-08-23 00:26:32 +00:00
lobehubbot bec77579a1 🤖 chore: Lighthouse Results | Chat 2024-08-23 00:24:56 +00:00
lobehubbot 0e2926b12b Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-08-23 00:24:41 +00:00
lobehubbot 8e3db4714d 🤖 chore: Lighthouse Results | Settings 2024-08-23 00:24:40 +00:00
lobehubbot ae84291d10 🤖 chore: Lighthouse Results | Market 2024-08-23 00:24:40 +00:00
lobehubbot 8c577cd895 🤖 chore: Lighthouse Results | Welcome 2024-08-22 00:26:38 +00:00
lobehubbot 2b3109407f 🤖 chore: Lighthouse Results | Market 2024-08-22 00:25:04 +00:00
lobehubbot 17f114ed35 🤖 chore: Lighthouse Results | Settings 2024-08-22 00:25:01 +00:00
lobehubbot d3294222ea 🤖 chore: Lighthouse Results | Chat 2024-08-22 00:24:32 +00:00
lobehubbot 2ecd540860 🤖 chore: Lighthouse Results | Welcome 2024-08-21 00:26:39 +00:00
lobehubbot 3f7e64c9e8 🤖 chore: Lighthouse Results | Settings 2024-08-21 00:25:34 +00:00
lobehubbot 6dfe33f0c0 🤖 chore: Lighthouse Results | Chat 2024-08-21 00:24:32 +00:00
lobehubbot 6a3212e6d7 🤖 chore: Lighthouse Results | Market 2024-08-21 00:24:31 +00:00
lobehubbot 564295014b 🤖 chore: Lighthouse Results | Welcome 2024-08-20 00:26:27 +00:00
lobehubbot 004f8f2e66 🤖 chore: Lighthouse Results | Settings 2024-08-20 00:24:36 +00:00
lobehubbot 8b4444f9d9 🤖 chore: Lighthouse Results | Chat 2024-08-20 00:24:30 +00:00
lobehubbot b12b5f7546 🤖 chore: Lighthouse Results | Market 2024-08-20 00:24:27 +00:00
lobehubbot 85f36a57b6 🤖 chore: Lighthouse Results | Welcome 2024-08-19 00:27:30 +00:00
lobehubbot 65a0290fac 🤖 chore: Lighthouse Results | Settings 2024-08-19 00:25:22 +00:00
lobehubbot cc3054acc7 🤖 chore: Lighthouse Results | Market 2024-08-19 00:25:18 +00:00
lobehubbot f81c7fcc03 🤖 chore: Lighthouse Results | Chat 2024-08-19 00:25:03 +00:00
lobehubbot 4dd25e23ae 🤖 chore: Lighthouse Results | Welcome 2024-08-18 00:28:41 +00:00
lobehubbot 40bef66b35 🤖 chore: Lighthouse Results | Market 2024-08-18 00:27:25 +00:00
lobehubbot fa125da59c 🤖 chore: Lighthouse Results | Settings 2024-08-18 00:26:41 +00:00
lobehubbot 4e22219c3b 🤖 chore: Lighthouse Results | Chat 2024-08-18 00:26:36 +00:00
lobehubbot 81b0148462 🤖 chore: Lighthouse Results | Welcome 2024-08-17 00:25:45 +00:00
lobehubbot 68659ba7e2 🤖 chore: Lighthouse Results | Chat 2024-08-17 00:23:49 +00:00
lobehubbot aeebdba86e 🤖 chore: Lighthouse Results | Settings 2024-08-17 00:23:45 +00:00
lobehubbot e5bf00699d 🤖 chore: Lighthouse Results | Market 2024-08-17 00:23:41 +00:00
lobehubbot 9218af6090 🤖 chore: Lighthouse Results | Welcome 2024-08-16 00:26:36 +00:00
lobehubbot ceea80ada2 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-08-16 00:24:30 +00:00
lobehubbot f874a36dbc 🤖 chore: Lighthouse Results | Settings 2024-08-16 00:24:29 +00:00
lobehubbot faff412bd3 🤖 chore: Lighthouse Results | Market 2024-08-16 00:24:28 +00:00
lobehubbot dbdf172f2a 🤖 chore: Lighthouse Results | Chat 2024-08-16 00:24:25 +00:00
lobehubbot 698bf897b1 🤖 chore: Lighthouse Results | Welcome 2024-08-15 00:25:55 +00:00
lobehubbot 64b0197667 🤖 chore: Lighthouse Results | Market 2024-08-15 00:24:23 +00:00
lobehubbot fecd9fbc3a 🤖 chore: Lighthouse Results | Chat 2024-08-15 00:23:54 +00:00
lobehubbot 8e8a9e969a 🤖 chore: Lighthouse Results | Settings 2024-08-15 00:23:52 +00:00
lobehubbot 8dcbc2d091 🤖 chore: Lighthouse Results | Welcome 2024-08-14 00:26:26 +00:00
lobehubbot 2a18944f50 🤖 chore: Lighthouse Results | Market 2024-08-14 00:25:13 +00:00
lobehubbot 8ba6bb63f1 🤖 chore: Lighthouse Results | Chat 2024-08-14 00:24:38 +00:00
lobehubbot a03a281131 🤖 chore: Lighthouse Results | Settings 2024-08-14 00:24:17 +00:00
lobehubbot bb313cd642 🤖 chore: Lighthouse Results | Welcome 2024-08-13 00:27:10 +00:00
lobehubbot de58f7b53d 🤖 chore: Lighthouse Results | Market 2024-08-13 00:25:53 +00:00
lobehubbot 23cfd7f87f 🤖 chore: Lighthouse Results | Settings 2024-08-13 00:25:28 +00:00
lobehubbot 6c75804616 🤖 chore: Lighthouse Results | Chat 2024-08-13 00:25:14 +00:00
lobehubbot 5aa178ac11 🤖 chore: Lighthouse Results | Welcome 2024-08-12 00:27:33 +00:00
lobehubbot af01118c2f 🤖 chore: Lighthouse Results | Settings 2024-08-12 00:25:40 +00:00
lobehubbot d4db11c16f 🤖 chore: Lighthouse Results | Chat 2024-08-12 00:25:29 +00:00
lobehubbot d92266839b 🤖 chore: Lighthouse Results | Market 2024-08-12 00:25:27 +00:00
lobehubbot 29bf585107 🤖 chore: Lighthouse Results | Welcome 2024-08-11 00:29:18 +00:00
lobehubbot aed0bd1bb2 🤖 chore: Lighthouse Results | Market 2024-08-11 00:27:18 +00:00
lobehubbot 6c54fa9936 🤖 chore: Lighthouse Results | Chat 2024-08-11 00:27:12 +00:00
lobehubbot 621838490d 🤖 chore: Lighthouse Results | Settings 2024-08-11 00:27:07 +00:00
lobehubbot 179c0fb2b8 🤖 chore: Lighthouse Results | Welcome 2024-08-10 00:26:11 +00:00
lobehubbot e66da570b4 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-08-10 00:24:10 +00:00
lobehubbot b620b05d6b 🤖 chore: Lighthouse Results | Settings 2024-08-10 00:24:09 +00:00
lobehubbot 2e8203c0dd 🤖 chore: Lighthouse Results | Chat 2024-08-10 00:24:08 +00:00
lobehubbot a3ccf1511b 🤖 chore: Lighthouse Results | Market 2024-08-10 00:24:04 +00:00
lobehubbot 8b8b42d804 🤖 chore: Lighthouse Results | Welcome 2024-08-09 00:26:36 +00:00
lobehubbot 2bfb95b88c 🤖 chore: Lighthouse Results | Settings 2024-08-09 00:24:58 +00:00
lobehubbot a30a82f169 🤖 chore: Lighthouse Results | Market 2024-08-09 00:24:50 +00:00
lobehubbot 62a4c94c5e 🤖 chore: Lighthouse Results | Chat 2024-08-09 00:24:31 +00:00
lobehubbot a09fdc608a 🤖 chore: Lighthouse Results | Welcome 2024-08-08 00:26:18 +00:00
lobehubbot a407b615f9 🤖 chore: Lighthouse Results | Market 2024-08-08 00:24:19 +00:00
lobehubbot 2580f9f9cb 🤖 chore: Lighthouse Results | Settings 2024-08-08 00:24:16 +00:00
lobehubbot 40a6df58fe 🤖 chore: Lighthouse Results | Chat 2024-08-08 00:24:04 +00:00
lobehubbot 2d4ee60471 🤖 chore: Lighthouse Results | Welcome 2024-08-07 00:26:24 +00:00
lobehubbot 1964d345a8 🤖 chore: Lighthouse Results | Chat 2024-08-07 00:24:38 +00:00
lobehubbot a0d729d26b 🤖 chore: Lighthouse Results | Market 2024-08-07 00:24:21 +00:00
lobehubbot 76358c2e87 🤖 chore: Lighthouse Results | Settings 2024-08-07 00:24:11 +00:00
lobehubbot 930317fe1c 🤖 chore: Lighthouse Results | Welcome 2024-08-06 00:26:37 +00:00
lobehubbot fa1da1b807 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-08-06 00:24:21 +00:00
lobehubbot 2b6b34648e 🤖 chore: Lighthouse Results | Settings 2024-08-06 00:24:20 +00:00
lobehubbot c1d2e43365 🤖 chore: Lighthouse Results | Chat 2024-08-06 00:24:20 +00:00
lobehubbot ae4d14fcd9 🤖 chore: Lighthouse Results | Market 2024-08-06 00:24:03 +00:00
lobehubbot aa27fbd811 🤖 chore: Lighthouse Results | Welcome 2024-08-05 00:28:00 +00:00
lobehubbot 9471720b29 🤖 chore: Lighthouse Results | Settings 2024-08-05 00:26:15 +00:00
lobehubbot 5abd0f841c 🤖 chore: Lighthouse Results | Market 2024-08-05 00:26:13 +00:00
lobehubbot af271a6943 🤖 chore: Lighthouse Results | Chat 2024-08-05 00:26:09 +00:00
lobehubbot fcbd825ecf 🤖 chore: Lighthouse Results | Welcome 2024-08-04 00:28:30 +00:00
lobehubbot 36495e86d9 🤖 chore: Lighthouse Results | Market 2024-08-04 00:26:33 +00:00
lobehubbot c8f63e305d 🤖 chore: Lighthouse Results | Chat 2024-08-04 00:26:27 +00:00
lobehubbot 50e2371cee 🤖 chore: Lighthouse Results | Settings 2024-08-04 00:26:22 +00:00
lobehubbot 1ef6030360 🤖 chore: Lighthouse Results | Welcome 2024-08-03 00:25:40 +00:00
lobehubbot 3ebc34b0f4 🤖 chore: Lighthouse Results | Market 2024-08-03 00:24:43 +00:00
lobehubbot be884947d7 🤖 chore: Lighthouse Results | Chat 2024-08-03 00:24:07 +00:00
lobehubbot 407270de3b 🤖 chore: Lighthouse Results | Settings 2024-08-03 00:23:37 +00:00
lobehubbot f330019a4e 🤖 chore: Lighthouse Results | Welcome 2024-08-02 00:25:52 +00:00
lobehubbot ffbca77785 🤖 chore: Lighthouse Results | Settings 2024-08-02 00:25:32 +00:00
lobehubbot f5c4edfb17 🤖 chore: Lighthouse Results | Chat 2024-08-02 00:24:07 +00:00
lobehubbot 1c39a53267 🤖 chore: Lighthouse Results | Market 2024-08-02 00:24:02 +00:00
lobehubbot 957bf8f8da 🤖 chore: Lighthouse Results | Welcome 2024-08-01 00:29:03 +00:00
lobehubbot 04766492bc 🤖 chore: Lighthouse Results | Settings 2024-08-01 00:26:59 +00:00
lobehubbot b52c6c6e5c 🤖 chore: Lighthouse Results | Market 2024-08-01 00:26:47 +00:00
lobehubbot 70fd819906 🤖 chore: Lighthouse Results | Chat 2024-08-01 00:26:41 +00:00
lobehubbot b7e8218d6c 🤖 chore: Lighthouse Results | Welcome 2024-07-31 00:22:16 +00:00
lobehubbot 9009205b91 🤖 chore: Lighthouse Results | Market 2024-07-31 00:21:07 +00:00
lobehubbot d20ca67005 🤖 chore: Lighthouse Results | Chat 2024-07-31 00:20:09 +00:00
lobehubbot 78736b7df5 🤖 chore: Lighthouse Results | Settings 2024-07-31 00:20:07 +00:00
lobehubbot 07ff858493 🤖 chore: Lighthouse Results | Welcome 2024-07-30 00:26:22 +00:00
lobehubbot 0b1a96bea3 🤖 chore: Lighthouse Results | Chat 2024-07-30 00:24:24 +00:00
lobehubbot 7b928c1991 🤖 chore: Lighthouse Results | Market 2024-07-30 00:24:05 +00:00
lobehubbot e3ec0e1901 🤖 chore: Lighthouse Results | Settings 2024-07-30 00:23:53 +00:00
lobehubbot 64d90a74cc 🤖 chore: Lighthouse Results | Welcome 2024-07-29 00:27:13 +00:00
lobehubbot fd62adfd1b 🤖 chore: Lighthouse Results | Chat 2024-07-29 00:25:40 +00:00
lobehubbot 313bb91624 🤖 chore: Lighthouse Results | Market 2024-07-29 00:25:22 +00:00
lobehubbot bc21d870fe 🤖 chore: Lighthouse Results | Settings 2024-07-29 00:25:14 +00:00
lobehubbot 4d724676c8 🤖 chore: Lighthouse Results | Welcome 2024-07-28 00:28:50 +00:00
lobehubbot cbf7fe6580 🤖 chore: Lighthouse Results | Chat 2024-07-28 00:26:54 +00:00
lobehubbot 05588d6af0 🤖 chore: Lighthouse Results | Market 2024-07-28 00:26:43 +00:00
lobehubbot 4527165a8c 🤖 chore: Lighthouse Results | Settings 2024-07-28 00:26:41 +00:00
lobehubbot 4977b0c2ae 🤖 chore: Lighthouse Results | Welcome 2024-07-27 00:25:19 +00:00
lobehubbot bd1ac588cc 🤖 chore: Lighthouse Results | Market 2024-07-27 00:23:41 +00:00
lobehubbot deff90f41b 🤖 chore: Lighthouse Results | Settings 2024-07-27 00:23:17 +00:00
lobehubbot b87c90252a 🤖 chore: Lighthouse Results | Chat 2024-07-27 00:23:14 +00:00
lobehubbot 1d8b299bca 🤖 chore: Lighthouse Results | Welcome 2024-07-26 00:25:49 +00:00
lobehubbot 8e41d28c4d 🤖 chore: Lighthouse Results | Settings 2024-07-26 00:23:50 +00:00
lobehubbot 01e6cc3751 🤖 chore: Lighthouse Results | Market 2024-07-26 00:23:49 +00:00
lobehubbot e4aec311c4 🤖 chore: Lighthouse Results | Chat 2024-07-26 00:23:35 +00:00
lobehubbot 8fcdd5cb54 🤖 chore: Lighthouse Results | Welcome 2024-07-25 00:26:02 +00:00
lobehubbot 120c2b57d2 🤖 chore: Lighthouse Results | Market 2024-07-25 00:24:07 +00:00
lobehubbot 4b2dba83d6 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-07-25 00:23:51 +00:00
lobehubbot f1b7346781 🤖 chore: Lighthouse Results | Chat 2024-07-25 00:23:50 +00:00
lobehubbot 82082cbe71 🤖 chore: Lighthouse Results | Settings 2024-07-25 00:23:49 +00:00
lobehubbot e49957781e 🤖 chore: Lighthouse Results | Welcome 2024-07-24 00:26:49 +00:00
lobehubbot 524d9444ff 🤖 chore: Lighthouse Results | Chat 2024-07-24 00:24:47 +00:00
lobehubbot 6f09ebecda 🤖 chore: Lighthouse Results | Settings 2024-07-24 00:24:43 +00:00
lobehubbot 9a9c5bd798 🤖 chore: Lighthouse Results | Market 2024-07-24 00:24:37 +00:00
lobehubbot 3d3dec312f 🤖 chore: Lighthouse Results | Welcome 2024-07-23 00:26:02 +00:00
lobehubbot d9684920e4 🤖 chore: Lighthouse Results | Chat 2024-07-23 00:24:02 +00:00
lobehubbot 0b78fae56f Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-07-23 00:23:49 +00:00
lobehubbot f3cb0f1dca 🤖 chore: Lighthouse Results | Settings 2024-07-23 00:23:49 +00:00
lobehubbot bf34a6e112 🤖 chore: Lighthouse Results | Market 2024-07-23 00:23:48 +00:00
lobehubbot cbd7ea4ff5 🤖 chore: Lighthouse Results | Welcome 2024-07-22 00:27:06 +00:00
lobehubbot 3d5c9ac401 🤖 chore: Lighthouse Results | Market 2024-07-22 00:25:30 +00:00
lobehubbot b161737598 🤖 chore: Lighthouse Results | Settings 2024-07-22 00:25:15 +00:00
lobehubbot be829ba250 🤖 chore: Lighthouse Results | Chat 2024-07-22 00:25:05 +00:00
lobehubbot 76292e251d 🤖 chore: Lighthouse Results | Welcome 2024-07-19 00:27:24 +00:00
lobehubbot fcb1ff3b7c 🤖 chore: Lighthouse Results | Settings 2024-07-19 00:25:00 +00:00
lobehubbot 2b2a9a9878 🤖 chore: Lighthouse Results | Chat 2024-07-19 00:24:51 +00:00
lobehubbot 04ff084560 🤖 chore: Lighthouse Results | Market 2024-07-19 00:24:49 +00:00
lobehubbot f240b49d3d 🤖 chore: Lighthouse Results | Welcome 2024-07-18 00:25:50 +00:00
lobehubbot a2afdb48a8 🤖 chore: Lighthouse Results | Market 2024-07-18 00:23:59 +00:00
lobehubbot 506be63688 🤖 chore: Lighthouse Results | Chat 2024-07-18 00:23:43 +00:00
lobehubbot 2fa54efe31 🤖 chore: Lighthouse Results | Settings 2024-07-18 00:23:36 +00:00
lobehubbot 11b10c3ec2 🤖 chore: Lighthouse Results | Welcome 2024-07-17 00:26:00 +00:00
lobehubbot 24939981c5 🤖 chore: Lighthouse Results | Chat 2024-07-17 00:24:07 +00:00
lobehubbot 559e5f854b 🤖 chore: Lighthouse Results | Market 2024-07-17 00:24:05 +00:00
lobehubbot 513158a2fb 🤖 chore: Lighthouse Results | Settings 2024-07-17 00:24:03 +00:00
lobehubbot 59a0cb8c30 🤖 chore: Lighthouse Results | Welcome 2024-07-16 00:25:48 +00:00
lobehubbot 3e55bb95c4 🤖 chore: Lighthouse Results | Settings 2024-07-16 00:23:54 +00:00
lobehubbot 76411971d8 🤖 chore: Lighthouse Results | Chat 2024-07-16 00:23:38 +00:00
lobehubbot 948a5a1af2 🤖 chore: Lighthouse Results | Market 2024-07-16 00:23:34 +00:00
lobehubbot bde6675368 🤖 chore: Lighthouse Results | Welcome 2024-07-15 00:27:02 +00:00
lobehubbot e0a12c89a2 🤖 chore: Lighthouse Results | Settings 2024-07-15 00:24:49 +00:00
lobehubbot 3f33596df2 🤖 chore: Lighthouse Results | Market 2024-07-15 00:24:46 +00:00
lobehubbot 0403991d42 🤖 chore: Lighthouse Results | Chat 2024-07-15 00:24:42 +00:00
lobehubbot c57665cc2b 🤖 chore: Lighthouse Results | Welcome 2024-07-14 00:28:30 +00:00
lobehubbot b19645bf1b 🤖 chore: Lighthouse Results | Chat 2024-07-14 00:26:20 +00:00
lobehubbot 78ebfa5a6d 🤖 chore: Lighthouse Results | Market 2024-07-14 00:26:17 +00:00
lobehubbot 607201168b 🤖 chore: Lighthouse Results | Welcome 2024-07-13 00:25:56 +00:00
lobehubbot 84ecde7d6a 🤖 chore: Lighthouse Results | Chat 2024-07-13 00:23:57 +00:00
lobehubbot f97fbf2cca 🤖 chore: Lighthouse Results | Settings 2024-07-13 00:23:48 +00:00
lobehubbot d114e7b7aa 🤖 chore: Lighthouse Results | Market 2024-07-13 00:23:45 +00:00
lobehubbot 8fd0009116 🤖 chore: Lighthouse Results | Welcome 2024-07-12 00:25:46 +00:00
lobehubbot 9a9416889b 🤖 chore: Lighthouse Results | Market 2024-07-12 00:23:37 +00:00
lobehubbot a027b5200a 🤖 chore: Lighthouse Results | Chat 2024-07-12 00:23:32 +00:00
lobehubbot d6dea96e23 🤖 chore: Lighthouse Results | Settings 2024-07-12 00:23:30 +00:00
lobehubbot f4c2a46966 🤖 chore: Lighthouse Results | Welcome 2024-07-11 00:26:13 +00:00
lobehubbot 616a3f9c46 🤖 chore: Lighthouse Results | Market 2024-07-11 00:24:21 +00:00
lobehubbot 3e52f7bdfa Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-07-11 00:24:15 +00:00
lobehubbot ee91f0de9e 🤖 chore: Lighthouse Results | Settings 2024-07-11 00:24:14 +00:00
lobehubbot 9fad52bb04 🤖 chore: Lighthouse Results | Chat 2024-07-11 00:24:13 +00:00
lobehubbot bab636927d 🤖 chore: Lighthouse Results | Welcome 2024-07-10 00:26:02 +00:00
lobehubbot 4aceab4494 🤖 chore: Lighthouse Results | Settings 2024-07-10 00:24:26 +00:00
lobehubbot 90b7ac7d46 🤖 chore: Lighthouse Results | Chat 2024-07-10 00:23:53 +00:00
lobehubbot a73816c9be 🤖 chore: Lighthouse Results | Market 2024-07-10 00:23:34 +00:00
lobehubbot 86448fc4dd 🤖 chore: Lighthouse Results | Welcome 2024-07-09 00:25:19 +00:00
lobehubbot a3781f8c4a 🤖 chore: Lighthouse Results | Settings 2024-07-09 00:23:59 +00:00
lobehubbot 5dc9bf67cb 🤖 chore: Lighthouse Results | Chat 2024-07-09 00:23:25 +00:00
lobehubbot 8f3ee3a569 🤖 chore: Lighthouse Results | Market 2024-07-09 00:23:09 +00:00
lobehubbot 9da078a7e3 🤖 chore: Lighthouse Results | Welcome 2024-07-08 00:26:38 +00:00
lobehubbot dea510fc5b 🤖 chore: Lighthouse Results | Chat 2024-07-08 00:24:32 +00:00
lobehubbot 3bf91fd73c 🤖 chore: Lighthouse Results | Market 2024-07-08 00:24:24 +00:00
lobehubbot 35cdfd4d65 🤖 chore: Lighthouse Results | Settings 2024-07-08 00:24:23 +00:00
lobehubbot 992e71cc57 🤖 chore: Lighthouse Results | Welcome 2024-07-07 00:28:02 +00:00
lobehubbot 9ea0ed55b1 🤖 chore: Lighthouse Results | Settings 2024-07-07 00:26:52 +00:00
lobehubbot af8b6fb2de 🤖 chore: Lighthouse Results | Chat 2024-07-07 00:26:34 +00:00
lobehubbot 427ae1da50 🤖 chore: Lighthouse Results | Market 2024-07-07 00:26:00 +00:00
lobehubbot 659f14a674 🤖 chore: Lighthouse Results | Welcome 2024-07-06 00:24:28 +00:00
lobehubbot 4727eca376 🤖 chore: Lighthouse Results | Market 2024-07-06 00:22:59 +00:00
lobehubbot 9e8a7217f6 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-07-06 00:22:21 +00:00
lobehubbot cf8f6ce28a 🤖 chore: Lighthouse Results | Settings 2024-07-06 00:22:20 +00:00
lobehubbot 894d4f5cf8 🤖 chore: Lighthouse Results | Chat 2024-07-06 00:22:20 +00:00
lobehubbot 94cba17e7e 🤖 chore: Lighthouse Results | Welcome 2024-07-05 00:25:35 +00:00
lobehubbot 3b2ceecf70 🤖 chore: Lighthouse Results | Chat 2024-07-05 00:23:18 +00:00
lobehubbot 183b0a0e21 🤖 chore: Lighthouse Results | Settings 2024-07-05 00:23:05 +00:00
lobehubbot d9a9228b99 🤖 chore: Lighthouse Results | Market 2024-07-05 00:22:53 +00:00
lobehubbot f50ca37bf0 🤖 chore: Lighthouse Results | Welcome 2024-07-04 00:25:07 +00:00
lobehubbot 25881e2d1f Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-07-04 00:23:06 +00:00
lobehubbot e897deb324 🤖 chore: Lighthouse Results | Market 2024-07-04 00:23:05 +00:00
lobehubbot 7d18190ff6 🤖 chore: Lighthouse Results | Settings 2024-07-04 00:23:05 +00:00
lobehubbot efa78434fb 🤖 chore: Lighthouse Results | Chat 2024-07-04 00:22:54 +00:00
lobehubbot 360e956c48 🤖 chore: Lighthouse Results | Welcome 2024-07-03 00:25:34 +00:00
lobehubbot a6f12e9142 🤖 chore: Lighthouse Results | Chat 2024-07-03 00:24:00 +00:00
lobehubbot 0213c36332 🤖 chore: Lighthouse Results | Settings 2024-07-03 00:23:30 +00:00
lobehubbot bb424ffc60 🤖 chore: Lighthouse Results | Market 2024-07-03 00:23:26 +00:00
lobehubbot 5df59bf800 🤖 chore: Lighthouse Results | Welcome 2024-07-02 00:25:34 +00:00
lobehubbot a24f7fa4a4 🤖 chore: Lighthouse Results | Market 2024-07-02 00:23:41 +00:00
lobehubbot e8ddaf7319 🤖 chore: Lighthouse Results | Chat 2024-07-02 00:23:35 +00:00
lobehubbot 7f60737adf 🤖 chore: Lighthouse Results | Settings 2024-07-02 00:23:33 +00:00
lobehubbot cda59b0ccc 🤖 chore: Lighthouse Results | Welcome 2024-07-01 00:29:22 +00:00
lobehubbot 74c50137e9 🤖 chore: Lighthouse Results | Market 2024-07-01 00:27:38 +00:00
lobehubbot 182f6f81c6 🤖 chore: Lighthouse Results | Chat 2024-07-01 00:27:29 +00:00
lobehubbot 28acfb3290 🤖 chore: Lighthouse Results | Settings 2024-07-01 00:27:20 +00:00
lobehubbot d08522f898 🤖 chore: Lighthouse Results | Welcome 2024-06-30 00:27:30 +00:00
lobehubbot 59bd752669 🤖 chore: Lighthouse Results | Chat 2024-06-30 00:26:08 +00:00
lobehubbot c136c61d9c 🤖 chore: Lighthouse Results | Settings 2024-06-30 00:25:50 +00:00
lobehubbot 3b2f2a75bf 🤖 chore: Lighthouse Results | Market 2024-06-30 00:25:26 +00:00
lobehubbot a33f2308b9 🤖 chore: Lighthouse Results | Welcome 2024-06-29 00:24:51 +00:00
lobehubbot 0810a8efbc 🤖 chore: Lighthouse Results | Chat 2024-06-29 00:23:12 +00:00
lobehubbot 98dbfa23fb 🤖 chore: Lighthouse Results | Market 2024-06-29 00:22:58 +00:00
lobehubbot d6e8a3d044 🤖 chore: Lighthouse Results | Settings 2024-06-29 00:22:52 +00:00
lobehubbot b308693056 🤖 chore: Lighthouse Results | Welcome 2024-06-28 00:25:11 +00:00
lobehubbot 8dc521d510 🤖 chore: Lighthouse Results | Chat 2024-06-28 00:23:25 +00:00
lobehubbot 119d0c34d9 🤖 chore: Lighthouse Results | Settings 2024-06-28 00:23:20 +00:00
lobehubbot c9f7c42f5c 🤖 chore: Lighthouse Results | Market 2024-06-28 00:23:08 +00:00
lobehubbot 1072408c8d 🤖 chore: Lighthouse Results | Welcome 2024-06-27 00:25:19 +00:00
lobehubbot 5646302bf4 🤖 chore: Lighthouse Results | Chat 2024-06-27 00:23:15 +00:00
lobehubbot 195a875b40 🤖 chore: Lighthouse Results | Market 2024-06-27 00:23:12 +00:00
lobehubbot 01d9c363de 🤖 chore: Lighthouse Results | Settings 2024-06-27 00:23:07 +00:00
lobehubbot 5479099a77 🤖 chore: Lighthouse Results | Welcome 2024-06-26 00:24:45 +00:00
lobehubbot 3b127b567b 🤖 chore: Lighthouse Results | Settings 2024-06-26 00:23:03 +00:00
lobehubbot 20e91dda7a 🤖 chore: Lighthouse Results | Chat 2024-06-26 00:22:54 +00:00
lobehubbot cd281c1076 🤖 chore: Lighthouse Results | Market 2024-06-26 00:22:51 +00:00
lobehubbot 3b9231ee5e 🤖 chore: Lighthouse Results | Welcome 2024-06-25 00:24:48 +00:00
lobehubbot 443e71de8a 🤖 chore: Lighthouse Results | Settings 2024-06-25 00:23:03 +00:00
lobehubbot 08498f4717 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-06-25 00:22:45 +00:00
lobehubbot 33644e6e6e 🤖 chore: Lighthouse Results | Chat 2024-06-25 00:22:43 +00:00
lobehubbot 006fd41fc4 🤖 chore: Lighthouse Results | Market 2024-06-25 00:22:43 +00:00
lobehubbot cb942f517a 🤖 chore: Lighthouse Results | Welcome 2024-06-24 00:26:40 +00:00
lobehubbot 987c0391cf 🤖 chore: Lighthouse Results | Chat 2024-06-24 00:24:47 +00:00
lobehubbot e26491b26c 🤖 chore: Lighthouse Results | Market 2024-06-24 00:24:40 +00:00
lobehubbot 01c1b6f1e1 🤖 chore: Lighthouse Results | Settings 2024-06-24 00:24:28 +00:00
lobehubbot 520c45543c 🤖 chore: Lighthouse Results | Welcome 2024-06-23 00:27:07 +00:00
lobehubbot 0c164f755f Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-06-23 00:25:02 +00:00
lobehubbot cffdc57dd1 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-06-23 00:25:01 +00:00
lobehubbot cd35d5c774 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-06-23 00:25:00 +00:00
lobehubbot 16a06804d3 🤖 chore: Lighthouse Results | Settings 2024-06-23 00:25:00 +00:00
lobehubbot a6cf0044dc 🤖 chore: Lighthouse Results | Chat 2024-06-23 00:25:00 +00:00
lobehubbot 03486a7836 🤖 chore: Lighthouse Results | Market 2024-06-23 00:24:59 +00:00
lobehubbot 8034bf8464 🤖 chore: Lighthouse Results | Welcome 2024-06-22 00:24:33 +00:00
lobehubbot 98f27a6428 🤖 chore: Lighthouse Results | Market 2024-06-22 00:22:40 +00:00
lobehubbot 86276c20be 🤖 chore: Lighthouse Results | Chat 2024-06-22 00:22:23 +00:00
lobehubbot 98bfa6214b 🤖 chore: Lighthouse Results | Settings 2024-06-22 00:22:21 +00:00
lobehubbot d41a0e6688 🤖 chore: Lighthouse Results | Welcome 2024-06-19 00:25:17 +00:00
lobehubbot 85e7b9f90f 🤖 chore: Lighthouse Results | Market 2024-06-19 00:23:29 +00:00
lobehubbot ad7f83a34e 🤖 chore: Lighthouse Results | Settings 2024-06-19 00:23:26 +00:00
lobehubbot 25733cb25a 🤖 chore: Lighthouse Results | Chat 2024-06-19 00:23:19 +00:00
lobehubbot 1a92483774 🤖 chore: Lighthouse Results | Welcome 2024-06-18 00:25:22 +00:00
lobehubbot b5981e7334 🤖 chore: Lighthouse Results | Market 2024-06-18 00:24:05 +00:00
lobehubbot fe2274318c 🤖 chore: Lighthouse Results | Chat 2024-06-18 00:23:29 +00:00
lobehubbot 2719d4681a 🤖 chore: Lighthouse Results | Settings 2024-06-18 00:23:23 +00:00
lobehubbot 92123f90b7 🤖 chore: Lighthouse Results | Welcome 2024-06-17 00:26:14 +00:00
lobehubbot e127df3e94 🤖 chore: Lighthouse Results | Chat 2024-06-17 00:24:35 +00:00
lobehubbot 5a201e0a00 🤖 chore: Lighthouse Results | Market 2024-06-17 00:24:13 +00:00
lobehubbot 7cac8ce688 🤖 chore: Lighthouse Results | Settings 2024-06-17 00:24:07 +00:00
lobehubbot bcfc157aa7 🤖 chore: Lighthouse Results | Welcome 2024-06-16 00:27:26 +00:00
lobehubbot 2c1557348a 🤖 chore: Lighthouse Results | Market 2024-06-16 00:26:11 +00:00
lobehubbot bce6889e0a 🤖 chore: Lighthouse Results | Settings 2024-06-16 00:26:05 +00:00
lobehubbot 1ed839342f 🤖 chore: Lighthouse Results | Chat 2024-06-16 00:25:15 +00:00
lobehubbot 1acab0b61a 🤖 chore: Lighthouse Results | Welcome 2024-06-15 00:24:34 +00:00
lobehubbot 17da80406a 🤖 chore: Lighthouse Results | Settings 2024-06-15 00:22:28 +00:00
lobehubbot 4f3ef1e628 🤖 chore: Lighthouse Results | Chat 2024-06-15 00:22:24 +00:00
lobehubbot 64560eca3d 🤖 chore: Lighthouse Results | Market 2024-06-15 00:22:21 +00:00
lobehubbot ccf85f118c 🤖 chore: Lighthouse Results | Welcome 2024-06-14 00:24:40 +00:00
lobehubbot 78495ebaf4 🤖 chore: Lighthouse Results | Market 2024-06-14 00:22:32 +00:00
lobehubbot 46455f8f69 🤖 chore: Lighthouse Results | Settings 2024-06-14 00:22:30 +00:00
lobehubbot 3f2471badf 🤖 chore: Lighthouse Results | Chat 2024-06-14 00:22:21 +00:00
lobehubbot 99230dba53 🤖 chore: Lighthouse Results | Welcome 2024-06-13 00:24:54 +00:00
lobehubbot d790ff3d79 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-06-13 00:22:57 +00:00
lobehubbot 1f4536c4de 🤖 chore: Lighthouse Results | Chat 2024-06-13 00:22:56 +00:00
lobehubbot 212d111259 🤖 chore: Lighthouse Results | Settings 2024-06-13 00:22:56 +00:00
lobehubbot b4696f1de1 🤖 chore: Lighthouse Results | Market 2024-06-13 00:22:30 +00:00
lobehubbot 3ae4b5efc2 🤖 chore: Lighthouse Results | Welcome 2024-06-12 00:24:52 +00:00
lobehubbot e2d842fda7 🤖 chore: Lighthouse Results | Settings 2024-06-12 00:22:58 +00:00
lobehubbot 16c24abf53 🤖 chore: Lighthouse Results | Chat 2024-06-12 00:22:41 +00:00
lobehubbot cd4b8c6dfc 🤖 chore: Lighthouse Results | Market 2024-06-12 00:22:38 +00:00
lobehubbot 0486b498a8 🤖 chore: Lighthouse Results | Welcome 2024-06-11 00:24:55 +00:00
lobehubbot 17471e7c7e 🤖 chore: Lighthouse Results | Chat 2024-06-11 00:22:38 +00:00
lobehubbot e332b7048a 🤖 chore: Lighthouse Results | Market 2024-06-11 00:22:35 +00:00
lobehubbot b186bd82fa 🤖 chore: Lighthouse Results | Settings 2024-06-11 00:22:28 +00:00
lobehubbot d7680ed316 🤖 chore: Lighthouse Results | Welcome 2024-06-10 00:26:27 +00:00
lobehubbot 47938e3285 🤖 chore: Lighthouse Results | Settings 2024-06-10 00:24:10 +00:00
lobehubbot 13d575d6f9 🤖 chore: Lighthouse Results | Chat 2024-06-10 00:24:05 +00:00
lobehubbot 6308103a9a 🤖 chore: Lighthouse Results | Market 2024-06-10 00:23:48 +00:00
lobehubbot ac3549bed5 🤖 chore: Lighthouse Results | Welcome 2024-06-09 00:27:55 +00:00
lobehubbot 4cd25c8e04 🤖 chore: Lighthouse Results | Market 2024-06-09 00:26:04 +00:00
lobehubbot e7d30d6183 🤖 chore: Lighthouse Results | Settings 2024-06-09 00:26:02 +00:00
lobehubbot 457abdd115 🤖 chore: Lighthouse Results | Chat 2024-06-09 00:25:53 +00:00
lobehubbot ba81cccc77 🤖 chore: Lighthouse Results | Welcome 2024-06-08 00:25:14 +00:00
lobehubbot a3c7b9931f 🤖 chore: Lighthouse Results | Settings 2024-06-08 00:23:23 +00:00
lobehubbot f8c9f8a94a 🤖 chore: Lighthouse Results | Chat 2024-06-08 00:23:13 +00:00
lobehubbot 26f77da3ec 🤖 chore: Lighthouse Results | Market 2024-06-08 00:23:11 +00:00
lobehubbot 813e272ed1 🤖 chore: Lighthouse Results | Welcome 2024-06-07 00:24:55 +00:00
lobehubbot b10dc974e8 🤖 chore: Lighthouse Results | Market 2024-06-07 00:22:59 +00:00
lobehubbot 352781c3fc 🤖 chore: Lighthouse Results | Chat 2024-06-07 00:22:50 +00:00
lobehubbot f7670605a5 🤖 chore: Lighthouse Results | Settings 2024-06-07 00:22:46 +00:00
lobehubbot a8bc4b60d9 🤖 chore: Lighthouse Results | Welcome 2024-06-06 00:24:27 +00:00
lobehubbot 972f555c29 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-06-06 00:22:45 +00:00
lobehubbot bda43f0b19 🤖 chore: Lighthouse Results | Market 2024-06-06 00:22:44 +00:00
lobehubbot 0d87d74de2 🤖 chore: Lighthouse Results | Settings 2024-06-06 00:22:44 +00:00
lobehubbot e474e40657 🤖 chore: Lighthouse Results | Chat 2024-06-06 00:22:21 +00:00
lobehubbot c4c20a025f 🤖 chore: Lighthouse Results | Welcome 2024-06-05 00:24:31 +00:00
lobehubbot f2968a7d49 🤖 chore: Lighthouse Results | Settings 2024-06-05 00:22:31 +00:00
lobehubbot fe5c4f211d 🤖 chore: Lighthouse Results | Market 2024-06-05 00:22:30 +00:00
lobehubbot 3c9482591f 🤖 chore: Lighthouse Results | Chat 2024-06-05 00:22:13 +00:00
lobehubbot 90923f0ad3 🤖 chore: Lighthouse Results | Welcome 2024-06-04 00:24:47 +00:00
lobehubbot 6431794406 🤖 chore: Lighthouse Results | Settings 2024-06-04 00:22:37 +00:00
lobehubbot 28371c5eb5 🤖 chore: Lighthouse Results | Chat 2024-06-04 00:22:35 +00:00
lobehubbot 7354b6c9d6 🤖 chore: Lighthouse Results | Market 2024-06-04 00:22:23 +00:00
lobehubbot 8a3b3305d1 🤖 chore: Lighthouse Results | Welcome 2024-06-03 00:25:17 +00:00
lobehubbot 531da18311 🤖 chore: Lighthouse Results | Settings 2024-06-03 00:23:51 +00:00
lobehubbot 0534aa6f42 🤖 chore: Lighthouse Results | Chat 2024-06-03 00:23:19 +00:00
lobehubbot 2fba21b716 🤖 chore: Lighthouse Results | Market 2024-06-03 00:23:18 +00:00
lobehubbot a427efc1ba 🤖 chore: Lighthouse Results | Welcome 2024-06-02 00:26:35 +00:00
lobehubbot 695da32b22 🤖 chore: Lighthouse Results | Chat 2024-06-02 00:25:14 +00:00
lobehubbot c320d6c812 🤖 chore: Lighthouse Results | Settings 2024-06-02 00:24:54 +00:00
lobehubbot 686427b4fe 🤖 chore: Lighthouse Results | Market 2024-06-02 00:24:50 +00:00
lobehubbot d876e5d4bb 🤖 chore: Lighthouse Results | Welcome 2024-06-01 00:26:23 +00:00
lobehubbot fc8416aaa6 🤖 chore: Lighthouse Results | Chat 2024-06-01 00:24:33 +00:00
lobehubbot 6cad87c923 🤖 chore: Lighthouse Results | Settings 2024-06-01 00:24:24 +00:00
lobehubbot f70a0283e0 🤖 chore: Lighthouse Results | Market 2024-06-01 00:24:23 +00:00
lobehubbot 8d7ec30216 🤖 chore: Lighthouse Results | Welcome 2024-05-31 00:24:10 +00:00
lobehubbot ab1acef075 🤖 chore: Lighthouse Results | Chat 2024-05-31 00:22:03 +00:00
lobehubbot fefa0f4779 🤖 chore: Lighthouse Results | Settings 2024-05-31 00:22:01 +00:00
lobehubbot 1a15122749 🤖 chore: Lighthouse Results | Market 2024-05-31 00:21:47 +00:00
lobehubbot c1051ecf7b 🤖 chore: Lighthouse Results | Welcome 2024-05-30 00:24:50 +00:00
lobehubbot 1a09dac1b1 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-05-30 00:22:51 +00:00
lobehubbot 83c17a0801 🤖 chore: Lighthouse Results | Chat 2024-05-30 00:22:50 +00:00
lobehubbot c5d0a0c4ab 🤖 chore: Lighthouse Results | Market 2024-05-30 00:22:49 +00:00
lobehubbot b0665a3b89 🤖 chore: Lighthouse Results | Settings 2024-05-30 00:22:33 +00:00
lobehubbot 758103520e 🤖 chore: Lighthouse Results | Welcome 2024-05-29 00:24:53 +00:00
lobehubbot 1e1dab96f5 🤖 chore: Lighthouse Results | Chat 2024-05-29 00:23:26 +00:00
lobehubbot 6b6aaa2afa 🤖 chore: Lighthouse Results | Market 2024-05-29 00:23:17 +00:00
lobehubbot 1f304acca0 🤖 chore: Lighthouse Results | Settings 2024-05-29 00:22:52 +00:00
lobehubbot 3861b21a5c 🤖 chore: Lighthouse Results | Welcome 2024-05-28 00:24:24 +00:00
lobehubbot c9888ba32a 🤖 chore: Lighthouse Results | Chat 2024-05-28 00:22:43 +00:00
lobehubbot 8dbb865950 🤖 chore: Lighthouse Results | Settings 2024-05-28 00:22:40 +00:00
lobehubbot 384008acd8 🤖 chore: Lighthouse Results | Market 2024-05-28 00:22:38 +00:00
lobehubbot e9ca3a36b6 🤖 chore: Lighthouse Results | Welcome 2024-05-27 00:25:06 +00:00
lobehubbot 2562d495b2 🤖 chore: Lighthouse Results | Chat 2024-05-27 00:23:28 +00:00
lobehubbot 0cb326a9ee 🤖 chore: Lighthouse Results | Market 2024-05-27 00:23:00 +00:00
lobehubbot 61bae5e14f 🤖 chore: Lighthouse Results | Settings 2024-05-27 00:22:44 +00:00
lobehubbot e513ef1a8e 🤖 chore: Lighthouse Results | Welcome 2024-05-26 00:26:20 +00:00
lobehubbot 5b0d0dccc7 🤖 chore: Lighthouse Results | Market 2024-05-26 00:24:48 +00:00
lobehubbot 468c93a64b 🤖 chore: Lighthouse Results | Chat 2024-05-26 00:24:38 +00:00
lobehubbot c638f985d7 🤖 chore: Lighthouse Results | Settings 2024-05-26 00:24:21 +00:00
lobehubbot c094aec6cc 🤖 chore: Lighthouse Results | Welcome 2024-05-25 00:23:53 +00:00
lobehubbot 74ca5b085f 🤖 chore: Lighthouse Results | Chat 2024-05-25 00:21:49 +00:00
lobehubbot 66d258c512 🤖 chore: Lighthouse Results | Market 2024-05-25 00:21:44 +00:00
lobehubbot bb0de6dabc 🤖 chore: Lighthouse Results | Settings 2024-05-25 00:21:20 +00:00
lobehubbot 6b0da01ab1 🤖 chore: Lighthouse Results | Welcome 2024-05-24 00:24:38 +00:00
lobehubbot 9affbde802 🤖 chore: Lighthouse Results | Chat 2024-05-24 00:22:46 +00:00
lobehubbot f6a7e3cd16 🤖 chore: Lighthouse Results | Market 2024-05-24 00:22:31 +00:00
lobehubbot e381335f71 🤖 chore: Lighthouse Results | Settings 2024-05-24 00:22:28 +00:00
lobehubbot fec3ef1ad0 🤖 chore: Lighthouse Results | Welcome 2024-05-23 00:24:09 +00:00
lobehubbot 258bda6e7d 🤖 chore: Lighthouse Results | Chat 2024-05-23 00:22:46 +00:00
lobehubbot 86b3d106c3 🤖 chore: Lighthouse Results | Market 2024-05-23 00:22:27 +00:00
lobehubbot 12478cc110 🤖 chore: Lighthouse Results | Settings 2024-05-23 00:21:42 +00:00
lobehubbot 46e6573866 🤖 chore: Lighthouse Results | Welcome 2024-05-22 00:23:54 +00:00
lobehubbot 11eeb012dd 🤖 chore: Lighthouse Results | Chat 2024-05-22 00:22:37 +00:00
lobehubbot 64066d9add 🤖 chore: Lighthouse Results | Settings 2024-05-22 00:21:59 +00:00
lobehubbot ab16381963 🤖 chore: Lighthouse Results | Market 2024-05-22 00:21:33 +00:00
lobehubbot 6c5c21b5a8 🤖 chore: Lighthouse Results | Welcome 2024-05-21 00:24:11 +00:00
lobehubbot b408dc9c34 🤖 chore: Lighthouse Results | Market 2024-05-21 00:22:47 +00:00
lobehubbot 6096a84ac4 🤖 chore: Lighthouse Results | Settings 2024-05-21 00:22:31 +00:00
lobehubbot 1ad357c75e 🤖 chore: Lighthouse Results | Chat 2024-05-21 00:22:18 +00:00
lobehubbot fbff3f21f6 🤖 chore: Lighthouse Results | Welcome 2024-05-20 00:24:38 +00:00
lobehubbot 67846ca997 🤖 chore: Lighthouse Results | Chat 2024-05-20 00:23:06 +00:00
lobehubbot 67fde12515 🤖 chore: Lighthouse Results | Settings 2024-05-20 00:22:42 +00:00
lobehubbot 0649260c80 🤖 chore: Lighthouse Results | Market 2024-05-20 00:22:19 +00:00
lobehubbot 8914d3e548 🤖 chore: Lighthouse Results | Welcome 2024-05-19 00:26:22 +00:00
lobehubbot 54ff85835b 🤖 chore: Lighthouse Results | Settings 2024-05-19 00:25:00 +00:00
lobehubbot c8dbfba62a 🤖 chore: Lighthouse Results | Chat 2024-05-19 00:24:51 +00:00
lobehubbot 0551e329cf 🤖 chore: Lighthouse Results | Market 2024-05-19 00:23:53 +00:00
lobehubbot 338cfe0147 🤖 chore: Lighthouse Results | Welcome 2024-05-18 00:24:21 +00:00
lobehubbot 89ad095fb9 🤖 chore: Lighthouse Results | Market 2024-05-18 00:22:46 +00:00
lobehubbot d5c37fafe2 🤖 chore: Lighthouse Results | Settings 2024-05-18 00:22:29 +00:00
lobehubbot b9fdb97eec 🤖 chore: Lighthouse Results | Chat 2024-05-18 00:22:10 +00:00
lobehubbot 2d598ba2a7 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-05-17 00:22:53 +00:00
lobehubbot 8c1f9abac7 🤖 chore: Lighthouse Results | Welcome 2024-05-17 00:22:52 +00:00
lobehubbot ded467e4d3 🤖 chore: Lighthouse Results | Settings 2024-05-17 00:22:51 +00:00
lobehubbot 58cd206bab 🤖 chore: Lighthouse Results | Market 2024-05-17 00:22:36 +00:00
lobehubbot 8dadf668f2 🤖 chore: Lighthouse Results | Chat 2024-05-17 00:22:11 +00:00
lobehubbot 2db614146c 🤖 chore: Lighthouse Results | Welcome 2024-05-16 00:22:36 +00:00
lobehubbot 751700d3d5 🤖 chore: Lighthouse Results | Settings 2024-05-16 00:22:33 +00:00
lobehubbot ba972ddb02 🤖 chore: Lighthouse Results | Chat 2024-05-16 00:22:04 +00:00
lobehubbot a13d6bdcc1 🤖 chore: Lighthouse Results | Market 2024-05-16 00:21:36 +00:00
lobehubbot 7bce9add62 🤖 chore: Lighthouse Results | Welcome 2024-05-15 00:22:47 +00:00
lobehubbot 557313427a 🤖 chore: Lighthouse Results | Market 2024-05-15 00:22:18 +00:00
lobehubbot 3ed3907789 🤖 chore: Lighthouse Results | Chat 2024-05-15 00:21:57 +00:00
lobehubbot e7a1a4d3de 🤖 chore: Lighthouse Results | Settings 2024-05-15 00:21:52 +00:00
lobehubbot a5469cb815 🤖 chore: Lighthouse Results | Welcome 2024-05-14 00:22:47 +00:00
lobehubbot a113748b88 🤖 chore: Lighthouse Results | Settings 2024-05-14 00:22:36 +00:00
lobehubbot c1f9cca98b Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-05-14 00:21:49 +00:00
lobehubbot 1e047a909e 🤖 chore: Lighthouse Results | Market 2024-05-14 00:21:48 +00:00
lobehubbot fa267bb523 🤖 chore: Lighthouse Results | Chat 2024-05-14 00:21:48 +00:00
lobehubbot 04f081f785 🤖 chore: Lighthouse Results | Chat 2024-05-13 00:25:10 +00:00
lobehubbot 945b6abc3f 🤖 chore: Lighthouse Results | Settings 2024-05-13 00:24:55 +00:00
lobehubbot cdde0e8a18 🤖 chore: Lighthouse Results | Welcome 2024-05-13 00:23:55 +00:00
lobehubbot 88decd6eaa 🤖 chore: Lighthouse Results | Market 2024-05-13 00:22:52 +00:00
lobehubbot ff3767b41a 🤖 chore: Lighthouse Results | Welcome 2024-05-12 00:24:57 +00:00
lobehubbot 13ca4a5fec 🤖 chore: Lighthouse Results | Market 2024-05-12 00:24:28 +00:00
lobehubbot ca643e5adb 🤖 chore: Lighthouse Results | Chat 2024-05-12 00:24:06 +00:00
lobehubbot ba703d99f2 🤖 chore: Lighthouse Results | Settings 2024-05-12 00:24:00 +00:00
lobehubbot 41e6260068 🤖 chore: Lighthouse Results | Welcome 2024-05-11 00:21:53 +00:00
lobehubbot 815eb92706 🤖 chore: Lighthouse Results | Chat 2024-05-11 00:21:10 +00:00
lobehubbot 6270c2c0b9 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-05-11 00:21:08 +00:00
lobehubbot 3e42b4eb82 🤖 chore: Lighthouse Results | Settings 2024-05-11 00:21:08 +00:00
lobehubbot 74c7a7343d 🤖 chore: Lighthouse Results | Market 2024-05-11 00:21:07 +00:00
lobehubbot 19df671f2c 🤖 chore: Lighthouse Results | Welcome 2024-05-10 00:22:35 +00:00
lobehubbot f4397561f3 🤖 chore: Lighthouse Results | Market 2024-05-10 00:21:58 +00:00
lobehubbot 41adf68217 🤖 chore: Lighthouse Results | Chat 2024-05-10 00:21:44 +00:00
lobehubbot eee0161492 🤖 chore: Lighthouse Results | Settings 2024-05-10 00:21:37 +00:00
lobehubbot c2f8c1f590 🤖 chore: Lighthouse Results | Settings 2024-05-09 00:22:34 +00:00
lobehubbot 71a313d9c7 🤖 chore: Lighthouse Results | Welcome 2024-05-09 00:22:16 +00:00
lobehubbot dcbee310bb 🤖 chore: Lighthouse Results | Chat 2024-05-09 00:21:38 +00:00
lobehubbot 0ac28c4986 🤖 chore: Lighthouse Results | Market 2024-05-09 00:21:27 +00:00
lobehubbot e95756c8a8 🤖 chore: Lighthouse Results | Welcome 2024-05-08 00:19:24 +00:00
lobehubbot 60b7fa3683 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-05-08 00:19:06 +00:00
lobehubbot 7eded0bebe 🤖 chore: Lighthouse Results | Settings 2024-05-08 00:19:05 +00:00
lobehubbot 232875d6ea 🤖 chore: Lighthouse Results | Market 2024-05-08 00:19:04 +00:00
lobehubbot ed75d0d01b 🤖 chore: Lighthouse Results | Chat 2024-05-08 00:18:36 +00:00
lobehubbot 56547b4d4b 🤖 chore: Lighthouse Results | Welcome 2024-05-07 00:22:43 +00:00
lobehubbot e873844e2b 🤖 chore: Lighthouse Results | Settings 2024-05-07 00:21:48 +00:00
lobehubbot b0fda7666b 🤖 chore: Lighthouse Results | Market 2024-05-07 00:21:41 +00:00
lobehubbot 112bc90b40 🤖 chore: Lighthouse Results | Chat 2024-05-07 00:21:37 +00:00
lobehubbot ef7f3b2e36 🤖 chore: Lighthouse Results | Welcome 2024-05-06 00:22:58 +00:00
lobehubbot 1d7ea27f95 🤖 chore: Lighthouse Results | Chat 2024-05-06 00:22:24 +00:00
lobehubbot 7ddfc5467c 🤖 chore: Lighthouse Results | Market 2024-05-06 00:22:11 +00:00
lobehubbot 69abeb16ec 🤖 chore: Lighthouse Results | Settings 2024-05-06 00:22:06 +00:00
lobehubbot 9e3ed0c0da 🤖 chore: Lighthouse Results | Chat 2024-05-05 00:23:51 +00:00
lobehubbot 1eb4ba2d01 🤖 chore: Lighthouse Results | Settings 2024-05-05 00:23:19 +00:00
lobehubbot d00425930f 🤖 chore: Lighthouse Results | Market 2024-05-05 00:23:16 +00:00
lobehubbot 4e4fa5d82a 🤖 chore: Lighthouse Results | Welcome 2024-05-04 00:21:46 +00:00
lobehubbot 2ac5caba3f 🤖 chore: Lighthouse Results | Chat 2024-05-04 00:21:43 +00:00
lobehubbot b9f5dfc62d 🤖 chore: Lighthouse Results | Market 2024-05-04 00:21:08 +00:00
lobehubbot 097be6636d 🤖 chore: Lighthouse Results | Settings 2024-05-04 00:21:01 +00:00
lobehubbot 17a432bc82 🤖 chore: Lighthouse Results | Chat 2024-05-03 00:22:09 +00:00
lobehubbot 99ef221db5 🤖 chore: Lighthouse Results | Settings 2024-05-03 00:22:06 +00:00
lobehubbot c2af874b19 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-05-03 00:22:04 +00:00
lobehubbot 0f898ac60a 🤖 chore: Lighthouse Results | Welcome 2024-05-03 00:22:03 +00:00
lobehubbot 945964b15c 🤖 chore: Lighthouse Results | Market 2024-05-03 00:22:02 +00:00
lobehubbot 3f90092d47 🤖 chore: Lighthouse Results | Welcome 2024-05-02 00:21:59 +00:00
lobehubbot 203865b88a 🤖 chore: Lighthouse Results | Market 2024-05-02 00:21:56 +00:00
lobehubbot a165c3f287 🤖 chore: Lighthouse Results | Chat 2024-05-02 00:21:51 +00:00
lobehubbot 064241b9b6 🤖 chore: Lighthouse Results | Settings 2024-05-02 00:21:48 +00:00
lobehubbot 57c34766d0 🤖 chore: Lighthouse Results | Settings 2024-05-01 00:23:53 +00:00
lobehubbot bf5d7941b0 🤖 chore: Lighthouse Results | Chat 2024-05-01 00:23:39 +00:00
lobehubbot 5f3981050a 🤖 chore: Lighthouse Results | Welcome 2024-05-01 00:23:35 +00:00
lobehubbot b7c587e092 🤖 chore: Lighthouse Results | Market 2024-05-01 00:22:51 +00:00
lobehubbot b8a4e4b62e 🤖 chore: Lighthouse Results | Welcome 2024-04-30 00:21:55 +00:00
lobehubbot 7438f99d61 🤖 chore: Lighthouse Results | Settings 2024-04-30 00:21:41 +00:00
lobehubbot dbb8cb4eff 🤖 chore: Lighthouse Results | Chat 2024-04-30 00:21:27 +00:00
lobehubbot 1ee3dbd739 🤖 chore: Lighthouse Results | Market 2024-04-30 00:20:52 +00:00
lobehubbot 813eb03109 🤖 chore: Lighthouse Results | Market 2024-04-29 00:22:26 +00:00
lobehubbot 7a1892859c 🤖 chore: Lighthouse Results | Settings 2024-04-29 00:21:50 +00:00
lobehubbot 00e46718a5 🤖 chore: Lighthouse Results | Chat 2024-04-29 00:21:47 +00:00
lobehubbot 850f808b7b 🤖 chore: Lighthouse Results | Welcome 2024-04-28 00:25:07 +00:00
lobehubbot 74aac750b5 🤖 chore: Lighthouse Results | Chat 2024-04-28 00:24:46 +00:00
lobehubbot 96517ba6f1 🤖 chore: Lighthouse Results | Market 2024-04-28 00:24:20 +00:00
lobehubbot 50be546209 🤖 chore: Lighthouse Results | Settings 2024-04-28 00:23:53 +00:00
lobehubbot fb4301effe 🤖 chore: Lighthouse Results | Market 2024-04-27 00:22:04 +00:00
lobehubbot d8e59d53b1 🤖 chore: Lighthouse Results | Welcome 2024-04-27 00:21:26 +00:00
lobehubbot a9a6f02d7e 🤖 chore: Lighthouse Results | Chat 2024-04-27 00:21:13 +00:00
lobehubbot fd4117fdd4 🤖 chore: Lighthouse Results | Settings 2024-04-27 00:20:48 +00:00
lobehubbot 0b8bcd9e38 🤖 chore: Lighthouse Results | Chat 2024-04-26 00:22:13 +00:00
lobehubbot 00185c5691 🤖 chore: Lighthouse Results | Welcome 2024-04-26 00:21:55 +00:00
lobehubbot 31587bbd7d 🤖 chore: Lighthouse Results | Market 2024-04-26 00:21:36 +00:00
lobehubbot 9821d0f3a5 🤖 chore: Lighthouse Results | Settings 2024-04-26 00:21:09 +00:00
lobehubbot 1105411484 🤖 chore: Lighthouse Results | Welcome 2024-04-25 00:23:46 +00:00
lobehubbot 307f121dfa 🤖 chore: Lighthouse Results | Chat 2024-04-25 00:23:33 +00:00
lobehubbot 7d3175d1f1 🤖 chore: Lighthouse Results | Settings 2024-04-25 00:22:57 +00:00
lobehubbot fbec440606 🤖 chore: Lighthouse Results | Market 2024-04-25 00:22:49 +00:00
lobehubbot dd820a6f5d 🤖 chore: Lighthouse Results | Market 2024-04-24 00:22:15 +00:00
lobehubbot 8143b6d4aa 🤖 chore: Lighthouse Results | Chat 2024-04-24 00:21:37 +00:00
lobehubbot c0921767cf 🤖 chore: Lighthouse Results | Settings 2024-04-24 00:21:34 +00:00
lobehubbot 9bc6511c82 🤖 chore: Lighthouse Results | Welcome 2024-04-23 00:23:43 +00:00
lobehubbot 0e9b31d188 🤖 chore: Lighthouse Results | Market 2024-04-23 00:21:44 +00:00
lobehubbot 9d74a9a36d 🤖 chore: Lighthouse Results | Settings 2024-04-23 00:21:40 +00:00
lobehubbot 29c9e72675 🤖 chore: Lighthouse Results | Chat 2024-04-23 00:21:30 +00:00
lobehubbot 7dd843e2a2 🤖 chore: Lighthouse Results | Welcome 2024-04-22 00:24:38 +00:00
lobehubbot 034599d486 🤖 chore: Lighthouse Results | Settings 2024-04-22 00:22:45 +00:00
lobehubbot b86cd21c4b 🤖 chore: Lighthouse Results | Market 2024-04-22 00:22:27 +00:00
lobehubbot 99f201ccd2 🤖 chore: Lighthouse Results | Chat 2024-04-22 00:22:25 +00:00
lobehubbot 17ea0208e9 🤖 chore: Lighthouse Results | Welcome 2024-04-21 00:25:31 +00:00
lobehubbot a9154224ee 🤖 chore: Lighthouse Results | Chat 2024-04-21 00:23:55 +00:00
lobehubbot 6b3d5cf62b 🤖 chore: Lighthouse Results | Market 2024-04-21 00:23:22 +00:00
lobehubbot 8444bb750d 🤖 chore: Lighthouse Results | Settings 2024-04-21 00:23:21 +00:00
lobehubbot 41471b8899 🤖 chore: Lighthouse Results | Welcome 2024-04-20 00:22:59 +00:00
lobehubbot b5bd0acb6f Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-04-20 00:22:07 +00:00
lobehubbot af856e7a93 🤖 chore: Lighthouse Results | Chat 2024-04-20 00:22:06 +00:00
lobehubbot 28fc835f1d 🤖 chore: Lighthouse Results | Settings 2024-04-20 00:22:05 +00:00
lobehubbot aa2b0c574d 🤖 chore: Lighthouse Results | Market 2024-04-20 00:21:05 +00:00
lobehubbot 3fa6cbd63b 🤖 chore: Lighthouse Results | Welcome 2024-04-19 00:23:23 +00:00
lobehubbot 80fee9ff4e 🤖 chore: Lighthouse Results | Chat 2024-04-19 00:21:54 +00:00
lobehubbot e3bc99e117 🤖 chore: Lighthouse Results | Market 2024-04-19 00:21:51 +00:00
lobehubbot 644ba641b9 🤖 chore: Lighthouse Results | Settings 2024-04-19 00:21:22 +00:00
lobehubbot ee41a9b3b1 🤖 chore: Lighthouse Results | Welcome 2024-04-18 00:23:11 +00:00
lobehubbot 17a5cad517 🤖 chore: Lighthouse Results | Market 2024-04-18 00:21:58 +00:00
lobehubbot c764f9b03d 🤖 chore: Lighthouse Results | Chat 2024-04-18 00:21:57 +00:00
lobehubbot 018efd7fab 🤖 chore: Lighthouse Results | Settings 2024-04-18 00:21:45 +00:00
lobehubbot 728a6983a7 🤖 chore: Lighthouse Results | Welcome 2024-04-17 00:23:08 +00:00
lobehubbot 2beaabb3bc 🤖 chore: Lighthouse Results | Settings 2024-04-17 00:21:22 +00:00
lobehubbot 192eb2aced 🤖 chore: Lighthouse Results | Chat 2024-04-17 00:21:16 +00:00
lobehubbot b580e181a6 🤖 chore: Lighthouse Results | Market 2024-04-17 00:21:14 +00:00
lobehubbot b8d69669da 🤖 chore: Lighthouse Results | Welcome 2024-04-16 00:22:51 +00:00
lobehubbot 387671e8fa 🤖 chore: Lighthouse Results | Market 2024-04-16 00:20:52 +00:00
lobehubbot 7b183898bd 🤖 chore: Lighthouse Results | Chat 2024-04-16 00:20:44 +00:00
lobehubbot 0833c9ec12 🤖 chore: Lighthouse Results | Settings 2024-04-16 00:20:35 +00:00
lobehubbot 9d779a3872 🤖 chore: Lighthouse Results | Welcome 2024-04-15 02:40:10 +00:00
lobehubbot 28e87ecc05 🤖 chore: Lighthouse Results | Chat 2024-04-15 02:38:36 +00:00
lobehubbot 05920b41b8 🤖 chore: Lighthouse Results | Market 2024-04-15 02:38:32 +00:00
lobehubbot 113ddeb5b7 🤖 chore: Lighthouse Results | Settings 2024-04-15 02:38:08 +00:00
lobehubbot c2cfe14ffb 🤖 chore: Lighthouse Results | Welcome 2024-04-14 00:26:10 +00:00
lobehubbot c8b47925f2 🤖 chore: Lighthouse Results | Chat 2024-04-14 00:25:04 +00:00
lobehubbot d71221dfc3 🤖 chore: Lighthouse Results | Settings 2024-04-14 00:24:20 +00:00
lobehubbot fb58115039 🤖 chore: Lighthouse Results | Market 2024-04-14 00:24:19 +00:00
lobehubbot d73544b965 🤖 chore: Lighthouse Results | Welcome 2024-04-13 00:20:49 +00:00
lobehubbot 42a8e7e64a 🤖 chore: Lighthouse Results | Chat 2024-04-13 00:19:40 +00:00
lobehubbot dc69a44965 🤖 chore: Lighthouse Results | Settings 2024-04-13 00:18:52 +00:00
lobehubbot 10edf30634 🤖 chore: Lighthouse Results | Market 2024-04-13 00:18:22 +00:00
lobehubbot 2d65d4774d 🤖 chore: Lighthouse Results | Welcome 2024-04-12 00:22:51 +00:00
lobehubbot 8bbbf8c00c Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-04-12 00:21:04 +00:00
lobehubbot 9f1bbd7274 🤖 chore: Lighthouse Results | Settings 2024-04-12 00:21:02 +00:00
lobehubbot 2d8e60bd1e 🤖 chore: Lighthouse Results | Chat 2024-04-12 00:21:02 +00:00
lobehubbot 8581e2d777 🤖 chore: Lighthouse Results | Market 2024-04-12 00:21:00 +00:00
lobehubbot f4c55ce499 🤖 chore: Lighthouse Results | Welcome 2024-04-11 00:23:44 +00:00
lobehubbot cee687b207 🤖 chore: Lighthouse Results | Settings 2024-04-11 00:22:10 +00:00
lobehubbot 571d05d6d0 🤖 chore: Lighthouse Results | Chat 2024-04-11 00:21:57 +00:00
lobehubbot f48821e799 🤖 chore: Lighthouse Results | Market 2024-04-11 00:21:47 +00:00
lobehubbot 56a2d7dab5 🤖 chore: Lighthouse Results | Welcome 2024-04-10 00:22:48 +00:00
lobehubbot 4c2ed2ae78 🤖 chore: Lighthouse Results | Settings 2024-04-10 00:21:10 +00:00
lobehubbot 5a628581bf 🤖 chore: Lighthouse Results | Chat 2024-04-10 00:21:08 +00:00
lobehubbot 61cc534e49 🤖 chore: Lighthouse Results | Market 2024-04-10 00:20:57 +00:00
lobehubbot 1db4bfd55c 🤖 chore: Lighthouse Results | Welcome 2024-04-09 00:23:42 +00:00
lobehubbot 950158ddb2 🤖 chore: Lighthouse Results | Market 2024-04-09 00:22:03 +00:00
lobehubbot 44141fdd7a 🤖 chore: Lighthouse Results | Welcome 2024-04-08 00:23:52 +00:00
lobehubbot 8df9a808a1 🤖 chore: Lighthouse Results | Chat 2024-04-08 00:22:40 +00:00
lobehubbot 1b0b5444da 🤖 chore: Lighthouse Results | Settings 2024-04-08 00:22:36 +00:00
lobehubbot ab7e34d6aa 🤖 chore: Lighthouse Results | Market 2024-04-08 00:22:22 +00:00
lobehubbot 6ddc1c2c34 🤖 chore: Lighthouse Results | Welcome 2024-04-07 00:25:47 +00:00
lobehubbot 18f91c9082 🤖 chore: Lighthouse Results | Chat 2024-04-07 00:23:56 +00:00
lobehubbot 290c00036e 🤖 chore: Lighthouse Results | Settings 2024-04-07 00:23:54 +00:00
lobehubbot eb999ca73d 🤖 chore: Lighthouse Results | Market 2024-04-07 00:23:48 +00:00
lobehubbot 5df632fc0a 🤖 chore: Lighthouse Results | Welcome 2024-04-06 00:22:11 +00:00
lobehubbot be5fccc8da 🤖 chore: Lighthouse Results | Settings 2024-04-06 00:21:02 +00:00
lobehubbot ce42c775e6 🤖 chore: Lighthouse Results | Market 2024-04-06 00:20:16 +00:00
lobehubbot 63ba09ac7c 🤖 chore: Lighthouse Results | Chat 2024-04-06 00:20:08 +00:00
lobehubbot dad6f77330 🤖 chore: Lighthouse Results | Welcome 2024-04-05 00:23:04 +00:00
lobehubbot 3865ab6091 🤖 chore: Lighthouse Results | Market 2024-04-05 00:21:23 +00:00
lobehubbot 7b81620f81 🤖 chore: Lighthouse Results | Chat 2024-04-05 00:21:15 +00:00
lobehubbot 8c69731e9c 🤖 chore: Lighthouse Results | Settings 2024-04-05 00:21:04 +00:00
lobehubbot 9cd97c243b 🤖 chore: Lighthouse Results | Welcome 2024-04-04 00:23:36 +00:00
lobehubbot 6bd5a82da2 🤖 chore: Lighthouse Results | Market 2024-04-04 00:22:08 +00:00
lobehubbot 65dc69a5c2 🤖 chore: Lighthouse Results | Chat 2024-04-04 00:21:39 +00:00
lobehubbot 446f74a8e5 🤖 chore: Lighthouse Results | Settings 2024-04-04 00:21:32 +00:00
lobehubbot cd76e7859c 🤖 chore: Lighthouse Results | Welcome 2024-04-03 00:22:44 +00:00
lobehubbot a7494dd1d0 🤖 chore: Lighthouse Results | Market 2024-04-03 00:20:58 +00:00
lobehubbot 9e2a4216ad 🤖 chore: Lighthouse Results | Chat 2024-04-03 00:20:52 +00:00
lobehubbot bb94988697 🤖 chore: Lighthouse Results | Settings 2024-04-03 00:20:47 +00:00
lobehubbot 44bbd480fc 🤖 chore: Lighthouse Results | Welcome 2024-04-02 00:22:59 +00:00
lobehubbot 5bd14190c2 🤖 chore: Lighthouse Results | Market 2024-04-02 00:21:40 +00:00
lobehubbot 7cc98d06d9 🤖 chore: Lighthouse Results | Settings 2024-04-02 00:21:35 +00:00
lobehubbot 257ca0b547 🤖 chore: Lighthouse Results | Chat 2024-04-02 00:21:26 +00:00
lobehubbot da3f83484d 🤖 chore: Lighthouse Results | Welcome 2024-04-01 00:26:03 +00:00
lobehubbot 73ccecaefa 🤖 chore: Lighthouse Results | Market 2024-04-01 00:24:10 +00:00
lobehubbot e0c322c87d 🤖 chore: Lighthouse Results | Chat 2024-04-01 00:23:51 +00:00
lobehubbot 479091cb8c 🤖 chore: Lighthouse Results | Settings 2024-04-01 00:23:33 +00:00
lobehubbot 18d3422045 🤖 chore: Lighthouse Results | Welcome 2024-03-31 00:25:41 +00:00
lobehubbot 1a977b0268 🤖 chore: Lighthouse Results | Chat 2024-03-31 00:23:41 +00:00
lobehubbot 78255804b7 🤖 chore: Lighthouse Results | Market 2024-03-31 00:23:36 +00:00
lobehubbot 263891b4b5 🤖 chore: Lighthouse Results | Settings 2024-03-31 00:23:29 +00:00
lobehubbot db84516256 🤖 chore: Lighthouse Results | Welcome 2024-03-30 00:21:47 +00:00
lobehubbot b0aaac5e77 🤖 chore: Lighthouse Results | Settings 2024-03-30 00:20:10 +00:00
lobehubbot 521dcd0324 🤖 chore: Lighthouse Results | Market 2024-03-30 00:19:58 +00:00
lobehubbot e26bcfeac3 🤖 chore: Lighthouse Results | Welcome 2024-03-29 00:22:55 +00:00
lobehubbot 9ff59ecee9 🤖 chore: Lighthouse Results | Settings 2024-03-29 00:21:06 +00:00
lobehubbot 9cf9fbadee 🤖 chore: Lighthouse Results | Chat 2024-03-29 00:20:58 +00:00
lobehubbot c395b7151b 🤖 chore: Lighthouse Results | Market 2024-03-29 00:20:36 +00:00
lobehubbot e6dfb11630 🤖 chore: Lighthouse Results | Welcome 2024-03-28 00:23:07 +00:00
lobehubbot e0b4510aed 🤖 chore: Lighthouse Results | Settings 2024-03-28 00:20:50 +00:00
lobehubbot 2107a9ab32 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-03-28 00:20:48 +00:00
lobehubbot be2ce5cf83 🤖 chore: Lighthouse Results | Chat 2024-03-28 00:20:47 +00:00
lobehubbot cf2881f928 🤖 chore: Lighthouse Results | Market 2024-03-28 00:20:47 +00:00
lobehubbot a420c2ff69 🤖 chore: Lighthouse Results | Welcome 2024-03-27 00:21:31 +00:00
lobehubbot da5855d417 🤖 chore: Lighthouse Results | Settings 2024-03-27 00:20:58 +00:00
lobehubbot 7984656758 🤖 chore: Lighthouse Results | Chat 2024-03-27 00:20:10 +00:00
lobehubbot cb7c340b15 🤖 chore: Lighthouse Results | Market 2024-03-27 00:20:05 +00:00
lobehubbot 8e2812df2f 🤖 chore: Lighthouse Results | Welcome 2024-03-26 00:22:31 +00:00
lobehubbot adf54759b1 🤖 chore: Lighthouse Results | Market 2024-03-26 00:21:01 +00:00
lobehubbot 9f31744718 🤖 chore: Lighthouse Results | Chat 2024-03-26 00:20:48 +00:00
lobehubbot bbaa6e4ff0 🤖 chore: Lighthouse Results | Settings 2024-03-26 00:20:46 +00:00
lobehubbot cabf7515df 🤖 chore: Lighthouse Results | Welcome 2024-03-25 00:23:56 +00:00
lobehubbot 6e91ff5d91 🤖 chore: Lighthouse Results | Settings 2024-03-25 00:22:36 +00:00
lobehubbot 620868be8a 🤖 chore: Lighthouse Results | Market 2024-03-25 00:22:26 +00:00
lobehubbot 50fdb274be 🤖 chore: Lighthouse Results | Chat 2024-03-25 00:21:59 +00:00
lobehubbot c5f3064e36 🤖 chore: Lighthouse Results | Welcome 2024-03-24 00:25:21 +00:00
lobehubbot 9facd0bfe7 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-03-24 00:23:30 +00:00
lobehubbot 07334ad493 🤖 chore: Lighthouse Results | Chat 2024-03-24 00:23:28 +00:00
lobehubbot 0aaf793864 🤖 chore: Lighthouse Results | Settings 2024-03-24 00:23:28 +00:00
lobehubbot c3469eaa81 🤖 chore: Lighthouse Results | Market 2024-03-24 00:23:26 +00:00
lobehubbot 9e60e28b80 🤖 chore: Lighthouse Results | Welcome 2024-03-23 00:22:37 +00:00
lobehubbot eb283f93f0 🤖 chore: Lighthouse Results | Chat 2024-03-23 00:20:40 +00:00
lobehubbot 8521c5a1a7 🤖 chore: Lighthouse Results | Market 2024-03-23 00:20:26 +00:00
lobehubbot f4c24da901 🤖 chore: Lighthouse Results | Settings 2024-03-23 00:20:15 +00:00
lobehubbot 8ba2175472 🤖 chore: Lighthouse Results | Welcome 2024-03-22 00:22:29 +00:00
lobehubbot 4239a8529b 🤖 chore: Lighthouse Results | Settings 2024-03-22 00:20:44 +00:00
lobehubbot 631f803963 🤖 chore: Lighthouse Results | Market 2024-03-22 00:20:28 +00:00
lobehubbot c63f5dcbff 🤖 chore: Lighthouse Results | Chat 2024-03-22 00:20:17 +00:00
lobehubbot ebb66a71c3 🤖 chore: Lighthouse Results | Welcome 2024-03-21 00:23:35 +00:00
lobehubbot 156efd9416 🤖 chore: Lighthouse Results | Market 2024-03-21 00:21:32 +00:00
lobehubbot f30be92ed2 🤖 chore: Lighthouse Results | Chat 2024-03-21 00:21:29 +00:00
lobehubbot 833221236b 🤖 chore: Lighthouse Results | Settings 2024-03-21 00:21:12 +00:00
lobehubbot 8d74e707a5 🤖 chore: Lighthouse Results | Welcome 2024-03-20 00:22:26 +00:00
lobehubbot 43a3bfb129 🤖 chore: Lighthouse Results | Market 2024-03-20 00:21:13 +00:00
lobehubbot 69756ea5c6 🤖 chore: Lighthouse Results | Chat 2024-03-20 00:21:10 +00:00
lobehubbot 7902ebcdf0 🤖 chore: Lighthouse Results | Settings 2024-03-20 00:21:07 +00:00
lobehubbot 1cdb5fce2e 🤖 chore: Lighthouse Results | Welcome 2024-03-19 00:23:04 +00:00
lobehubbot d99933adaa 🤖 chore: Lighthouse Results | Settings 2024-03-19 00:21:07 +00:00
lobehubbot b2e528b725 🤖 chore: Lighthouse Results | Market 2024-03-19 00:21:03 +00:00
lobehubbot 8370d3323b 🤖 chore: Lighthouse Results | Chat 2024-03-19 00:20:44 +00:00
lobehubbot cb6dad5ae4 🤖 chore: Lighthouse Results | Welcome 2024-03-18 00:23:42 +00:00
lobehubbot 1d886cb0f4 🤖 chore: Lighthouse Results | Market 2024-03-18 00:22:38 +00:00
lobehubbot 5d54622125 🤖 chore: Lighthouse Results | Chat 2024-03-18 00:22:14 +00:00
lobehubbot 18d89582ed 🤖 chore: Lighthouse Results | Settings 2024-03-18 00:21:47 +00:00
lobehubbot 0c2d624ed9 🤖 chore: Lighthouse Results | Welcome 2024-03-17 00:24:29 +00:00
lobehubbot fa0de8af1c 🤖 chore: Lighthouse Results | Chat 2024-03-17 00:23:03 +00:00
lobehubbot 3b71ec17ce 🤖 chore: Lighthouse Results | Market 2024-03-17 00:22:42 +00:00
lobehubbot 732a538273 🤖 chore: Lighthouse Results | Settings 2024-03-17 00:22:35 +00:00
lobehubbot 83ba705c25 🤖 chore: Lighthouse Results | Welcome 2024-03-16 00:21:37 +00:00
lobehubbot 7846d71a88 🤖 chore: Lighthouse Results | Settings 2024-03-16 00:20:35 +00:00
lobehubbot 81e0cea858 🤖 chore: Lighthouse Results | Market 2024-03-16 00:20:26 +00:00
lobehubbot 32cb0bb062 🤖 chore: Lighthouse Results | Chat 2024-03-16 00:20:21 +00:00
lobehubbot e18b472418 🤖 chore: Lighthouse Results | Welcome 2024-03-15 00:22:42 +00:00
lobehubbot 875a9b0214 🤖 chore: Lighthouse Results | Settings 2024-03-15 00:20:46 +00:00
lobehubbot 1d5b2cf6a8 🤖 chore: Lighthouse Results | Chat 2024-03-15 00:20:34 +00:00
lobehubbot 40c236292e 🤖 chore: Lighthouse Results | Market 2024-03-15 00:20:26 +00:00
lobehubbot 9bacadad25 🤖 chore: Lighthouse Results | Welcome 2024-03-14 00:22:21 +00:00
lobehubbot 7d933f2c79 🤖 chore: Lighthouse Results | Chat 2024-03-14 00:20:33 +00:00
lobehubbot 654e890bbb 🤖 chore: Lighthouse Results | Settings 2024-03-14 00:20:26 +00:00
lobehubbot 6f911d67f7 🤖 chore: Lighthouse Results | Market 2024-03-14 00:20:19 +00:00
lobehubbot 8a2caf73ee 🤖 chore: Lighthouse Results | Welcome 2024-03-13 00:23:03 +00:00
lobehubbot c466898859 🤖 chore: Lighthouse Results | Chat 2024-03-13 00:20:50 +00:00
lobehubbot a8f7f56c60 🤖 chore: Lighthouse Results | Market 2024-03-13 00:20:39 +00:00
lobehubbot a7a95bd899 🤖 chore: Lighthouse Results | Settings 2024-03-13 00:20:28 +00:00
lobehubbot a2423b40d4 🤖 chore: Lighthouse Results | Welcome 2024-03-12 00:22:22 +00:00
lobehubbot f66f7951e9 🤖 chore: Lighthouse Results | Settings 2024-03-12 00:20:48 +00:00
lobehubbot d36801a911 🤖 chore: Lighthouse Results | Market 2024-03-12 00:20:35 +00:00
lobehubbot e35abe61bf 🤖 chore: Lighthouse Results | Chat 2024-03-12 00:20:15 +00:00
lobehubbot ef66636f8a 🤖 chore: Lighthouse Results | Welcome 2024-03-11 00:23:30 +00:00
lobehubbot 2e1c766f57 🤖 chore: Lighthouse Results | Market 2024-03-11 00:22:00 +00:00
lobehubbot a512bd4908 🤖 chore: Lighthouse Results | Chat 2024-03-11 00:21:46 +00:00
lobehubbot af3712c693 🤖 chore: Lighthouse Results | Settings 2024-03-11 00:21:20 +00:00
lobehubbot 60bcb0eafa 🤖 chore: Lighthouse Results | Welcome 2024-03-10 00:24:31 +00:00
lobehubbot f5e2762b5d 🤖 chore: Lighthouse Results | Settings 2024-03-10 00:23:26 +00:00
lobehubbot a301d0a56a 🤖 chore: Lighthouse Results | Market 2024-03-10 00:22:57 +00:00
lobehubbot 079fd70a9e 🤖 chore: Lighthouse Results | Chat 2024-03-10 00:22:49 +00:00
lobehubbot e06eb07d6c 🤖 chore: Lighthouse Results | Welcome 2024-03-09 00:22:17 +00:00
lobehubbot fcf7fd31e1 🤖 chore: Lighthouse Results | Settings 2024-03-09 00:20:44 +00:00
lobehubbot 0e0fb8140a 🤖 chore: Lighthouse Results | Chat 2024-03-09 00:20:04 +00:00
lobehubbot a319a5cb6d 🤖 chore: Lighthouse Results | Market 2024-03-09 00:19:51 +00:00
lobehubbot ded9bb1bfb 🤖 chore: Lighthouse Results | Welcome 2024-03-08 00:23:01 +00:00
lobehubbot b60709a53f 🤖 chore: Lighthouse Results | Settings 2024-03-08 00:21:46 +00:00
lobehubbot 20f89853cb 🤖 chore: Lighthouse Results | Chat 2024-03-08 00:21:32 +00:00
lobehubbot 7233203288 🤖 chore: Lighthouse Results | Market 2024-03-08 00:21:30 +00:00
lobehubbot bf3ec82884 🤖 chore: Lighthouse Results | Welcome 2024-03-07 00:19:14 +00:00
lobehubbot 661ee47189 🤖 chore: Lighthouse Results | Market 2024-03-07 00:17:12 +00:00
lobehubbot 076f0c76ac 🤖 chore: Lighthouse Results | Chat 2024-03-07 00:16:54 +00:00
lobehubbot 5b0ab4d425 🤖 chore: Lighthouse Results | Settings 2024-03-07 00:16:52 +00:00
lobehubbot faf520a9b3 🤖 chore: Lighthouse Results | Welcome 2024-03-06 00:23:11 +00:00
lobehubbot 243c4f975b 🤖 chore: Lighthouse Results | Chat 2024-03-06 00:21:41 +00:00
lobehubbot f6cbab2238 🤖 chore: Lighthouse Results | Settings 2024-03-06 00:21:19 +00:00
lobehubbot f42e0a863f 🤖 chore: Lighthouse Results | Market 2024-03-06 00:21:07 +00:00
lobehubbot c8740bd684 🤖 chore: Lighthouse Results | Welcome 2024-03-05 00:22:27 +00:00
lobehubbot 5ae91d2192 🤖 chore: Lighthouse Results | Chat 2024-03-05 00:20:25 +00:00
lobehubbot f2720d844e 🤖 chore: Lighthouse Results | Settings 2024-03-05 00:20:19 +00:00
lobehubbot f8f0f4854d 🤖 chore: Lighthouse Results | Welcome 2024-03-04 00:23:42 +00:00
lobehubbot 7211cc3e29 🤖 chore: Lighthouse Results | Settings 2024-03-04 00:22:15 +00:00
lobehubbot 7193b328d4 🤖 chore: Lighthouse Results | Chat 2024-03-04 00:21:23 +00:00
lobehubbot a60818c913 🤖 chore: Lighthouse Results | Market 2024-03-04 00:21:09 +00:00
lobehubbot 253ffb6b03 🤖 chore: Lighthouse Results | Welcome 2024-03-03 00:24:18 +00:00
lobehubbot 79b0fefb5f 🤖 chore: Lighthouse Results | Settings 2024-03-03 00:22:46 +00:00
lobehubbot 07d118f4bf 🤖 chore: Lighthouse Results | Chat 2024-03-03 00:22:06 +00:00
lobehubbot 89e88e7841 🤖 chore: Lighthouse Results | Market 2024-03-03 00:22:01 +00:00
lobehubbot 714534d8b7 🤖 chore: Lighthouse Results | Welcome 2024-03-02 00:21:48 +00:00
lobehubbot b7aa2256e8 🤖 chore: Lighthouse Results | Settings 2024-03-02 00:19:55 +00:00
lobehubbot d0e464a6bb 🤖 chore: Lighthouse Results | Market 2024-03-02 00:19:49 +00:00
lobehubbot 3dec5c3ccf 🤖 chore: Lighthouse Results | Chat 2024-03-02 00:19:29 +00:00
lobehubbot a38d9d01c9 🤖 chore: Lighthouse Results | Welcome 2024-03-01 00:25:42 +00:00
lobehubbot 9c2dd8412e 🤖 chore: Lighthouse Results | Chat 2024-03-01 00:24:26 +00:00
lobehubbot d9bea5fd4a 🤖 chore: Lighthouse Results | Market 2024-03-01 00:24:05 +00:00
lobehubbot 1acb55c0dd 🤖 chore: Lighthouse Results | Settings 2024-03-01 00:24:04 +00:00
lobehubbot ddce81c85a 🤖 chore: Lighthouse Results | Welcome 2024-02-29 00:22:40 +00:00
lobehubbot 7f574d3a3c 🤖 chore: Lighthouse Results | Market 2024-02-29 00:20:56 +00:00
lobehubbot dc9d7a33ad 🤖 chore: Lighthouse Results | Settings 2024-02-29 00:20:53 +00:00
lobehubbot ad168f2a4a 🤖 chore: Lighthouse Results | Chat 2024-02-29 00:20:39 +00:00
lobehubbot f796cfbcb0 🤖 chore: Lighthouse Results | Welcome 2024-02-28 00:22:05 +00:00
lobehubbot eb94ec458d 🤖 chore: Lighthouse Results | Settings 2024-02-28 00:20:57 +00:00
lobehubbot bcb1f9948c 🤖 chore: Lighthouse Results | Chat 2024-02-28 00:20:42 +00:00
lobehubbot 5049b25259 🤖 chore: Lighthouse Results | Market 2024-02-28 00:20:31 +00:00
lobehubbot efc67f0f7b 🤖 chore: Lighthouse Results | Welcome 2024-02-27 00:22:37 +00:00
lobehubbot 277b0d43e0 🤖 chore: Lighthouse Results | Settings 2024-02-27 00:20:45 +00:00
lobehubbot cad8c5c4f4 🤖 chore: Lighthouse Results | Market 2024-02-27 00:20:43 +00:00
lobehubbot ea38f30523 🤖 chore: Lighthouse Results | Chat 2024-02-27 00:20:37 +00:00
lobehubbot f1dd0dbaa2 🤖 chore: Lighthouse Results | Welcome 2024-02-26 00:23:41 +00:00
lobehubbot a0b32e5d65 🤖 chore: Lighthouse Results | Market 2024-02-26 00:21:34 +00:00
lobehubbot 526889ecf4 🤖 chore: Lighthouse Results | Settings 2024-02-26 00:21:27 +00:00
lobehubbot 3f3b162746 🤖 chore: Lighthouse Results | Chat 2024-02-26 00:21:22 +00:00
lobehubbot 7232025676 🤖 chore: Lighthouse Results | Welcome 2024-02-25 00:24:15 +00:00
lobehubbot c8b70de0a4 🤖 chore: Lighthouse Results | Settings 2024-02-25 00:22:44 +00:00
lobehubbot 102e0386a4 🤖 chore: Lighthouse Results | Chat 2024-02-25 00:22:05 +00:00
lobehubbot 570a1030aa 🤖 chore: Lighthouse Results | Market 2024-02-25 00:22:03 +00:00
lobehubbot 7dcc8cfb90 🤖 chore: Lighthouse Results | Welcome 2024-02-24 00:21:39 +00:00
lobehubbot 817be0568f 🤖 chore: Lighthouse Results | Chat 2024-02-24 00:19:52 +00:00
lobehubbot 6d4fe46fdd 🤖 chore: Lighthouse Results | Market 2024-02-24 00:19:42 +00:00
lobehubbot 9b746e6d2a 🤖 chore: Lighthouse Results | Settings 2024-02-24 00:19:37 +00:00
lobehubbot 964c594bab 🤖 chore: Lighthouse Results | Welcome 2024-02-23 00:22:33 +00:00
lobehubbot 3aba235c68 🤖 chore: Lighthouse Results | Settings 2024-02-23 00:20:37 +00:00
lobehubbot f8f65e1d8a 🤖 chore: Lighthouse Results | Chat 2024-02-23 00:20:16 +00:00
lobehubbot f0f1ad6173 🤖 chore: Lighthouse Results | Market 2024-02-23 00:20:13 +00:00
lobehubbot cc4c60e6b9 🤖 chore: Lighthouse Results | Welcome 2024-02-22 00:22:31 +00:00
lobehubbot 6d1c1e21a8 🤖 chore: Lighthouse Results | Settings 2024-02-22 00:21:16 +00:00
lobehubbot 7b08192348 🤖 chore: Lighthouse Results | Chat 2024-02-22 00:20:31 +00:00
lobehubbot cc357b565b 🤖 chore: Lighthouse Results | Market 2024-02-22 00:20:29 +00:00
lobehubbot 344f6cc56b 🤖 chore: Lighthouse Results | Welcome 2024-02-21 00:22:34 +00:00
lobehubbot 37948cc98f 🤖 chore: Lighthouse Results | Market 2024-02-21 00:21:01 +00:00
lobehubbot 2f3668b10c 🤖 chore: Lighthouse Results | Settings 2024-02-21 00:20:18 +00:00
lobehubbot 58ccff8614 🤖 chore: Lighthouse Results | Chat 2024-02-21 00:20:18 +00:00
lobehubbot 5d90ff47df 🤖 chore: Lighthouse Results | Welcome 2024-02-20 00:22:20 +00:00
lobehubbot a579cb984f 🤖 chore: Lighthouse Results | Settings 2024-02-20 00:20:20 +00:00
lobehubbot 7afdb8500f 🤖 chore: Lighthouse Results | Chat 2024-02-20 00:20:07 +00:00
lobehubbot 5b6e43e2cd 🤖 chore: Lighthouse Results | Market 2024-02-20 00:20:06 +00:00
lobehubbot fef4ffe3b4 🤖 chore: Lighthouse Results | Welcome 2024-02-19 00:23:24 +00:00
lobehubbot dd517f36f8 🤖 chore: Lighthouse Results | Market 2024-02-19 00:21:29 +00:00
lobehubbot c667ee0489 🤖 chore: Lighthouse Results | Chat 2024-02-19 00:21:18 +00:00
lobehubbot 830687d656 🤖 chore: Lighthouse Results | Settings 2024-02-19 00:21:12 +00:00
lobehubbot a3bd92b120 🤖 chore: Lighthouse Results | Welcome 2024-02-18 00:24:47 +00:00
lobehubbot ad5f739575 🤖 chore: Lighthouse Results | Chat 2024-02-18 00:22:54 +00:00
lobehubbot 7e6d26dcc4 🤖 chore: Lighthouse Results | Settings 2024-02-18 00:22:26 +00:00
lobehubbot 1131bbc4de 🤖 chore: Lighthouse Results | Welcome 2024-02-17 00:22:31 +00:00
lobehubbot ce3480c3c2 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-02-17 00:20:30 +00:00
lobehubbot c337ad6c09 🤖 chore: Lighthouse Results | Chat 2024-02-17 00:20:29 +00:00
lobehubbot 3f26859943 🤖 chore: Lighthouse Results | Market 2024-02-17 00:20:29 +00:00
lobehubbot 2de34f4226 🤖 chore: Lighthouse Results | Settings 2024-02-17 00:20:13 +00:00
lobehubbot bcdf46b018 🤖 chore: Lighthouse Results | Welcome 2024-02-16 00:22:30 +00:00
lobehubbot 6e9c3e817a 🤖 chore: Lighthouse Results | Chat 2024-02-16 00:20:26 +00:00
lobehubbot c67984e29e 🤖 chore: Lighthouse Results | Settings 2024-02-16 00:20:15 +00:00
lobehubbot 30ca0bf67c 🤖 chore: Lighthouse Results | Market 2024-02-16 00:20:09 +00:00
lobehubbot 06c6455bc1 🤖 chore: Lighthouse Results | Welcome 2024-02-15 00:22:47 +00:00
lobehubbot de0466962d 🤖 chore: Lighthouse Results | Chat 2024-02-15 00:20:45 +00:00
lobehubbot 9cb6c90666 🤖 chore: Lighthouse Results | Market 2024-02-15 00:20:41 +00:00
lobehubbot d762536412 🤖 chore: Lighthouse Results | Settings 2024-02-15 00:20:36 +00:00
lobehubbot 0623c85425 🤖 chore: Lighthouse Results | Welcome 2024-02-14 00:22:19 +00:00
lobehubbot 035f1f3263 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-02-14 00:20:42 +00:00
lobehubbot 0983a93420 🤖 chore: Lighthouse Results | Settings 2024-02-14 00:20:41 +00:00
lobehubbot c11c325836 🤖 chore: Lighthouse Results | Chat 2024-02-14 00:20:40 +00:00
lobehubbot 997c464e57 🤖 chore: Lighthouse Results | Market 2024-02-14 00:20:23 +00:00
lobehubbot 6e3464fd16 🤖 chore: Lighthouse Results | Welcome 2024-02-13 00:23:10 +00:00
lobehubbot 2a0efb3ca5 🤖 chore: Lighthouse Results | Chat 2024-02-13 00:21:23 +00:00
lobehubbot 29e782874a 🤖 chore: Lighthouse Results | Market 2024-02-13 00:21:10 +00:00
lobehubbot 8ea0f3944b 🤖 chore: Lighthouse Results | Settings 2024-02-13 00:20:58 +00:00
lobehubbot 4a498e3c2c 🤖 chore: Lighthouse Results | Welcome 2024-02-12 00:23:22 +00:00
lobehubbot be40ed6cb5 🤖 chore: Lighthouse Results | Chat 2024-02-12 00:21:35 +00:00
lobehubbot 5b3d8fea65 🤖 chore: Lighthouse Results | Settings 2024-02-12 00:21:30 +00:00
lobehubbot e3a28eabdb 🤖 chore: Lighthouse Results | Market 2024-02-12 00:21:12 +00:00
lobehubbot 4a5482c750 🤖 chore: Lighthouse Results | Welcome 2024-02-11 00:24:49 +00:00
lobehubbot 99c5ec9d11 🤖 chore: Lighthouse Results | Chat 2024-02-11 00:22:53 +00:00
lobehubbot 1ed92977c6 🤖 chore: Lighthouse Results | Settings 2024-02-11 00:22:39 +00:00
lobehubbot f703968531 🤖 chore: Lighthouse Results | Market 2024-02-11 00:22:35 +00:00
lobehubbot a230f6aae9 🤖 chore: Lighthouse Results | Welcome 2024-02-10 00:22:02 +00:00
lobehubbot 7470ed24e2 🤖 chore: Lighthouse Results | Chat 2024-02-10 00:20:10 +00:00
lobehubbot 679287b0ab 🤖 chore: Lighthouse Results | Settings 2024-02-10 00:20:05 +00:00
lobehubbot c11bd05347 🤖 chore: Lighthouse Results | Market 2024-02-10 00:19:54 +00:00
lobehubbot 8c1b4b8e8c 🤖 chore: Lighthouse Results | Welcome 2024-02-09 00:22:09 +00:00
lobehubbot b36b5100b0 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-02-09 00:20:15 +00:00
lobehubbot 57c555cfb4 🤖 chore: Lighthouse Results | Settings 2024-02-09 00:20:14 +00:00
lobehubbot 710622fc6b 🤖 chore: Lighthouse Results | Market 2024-02-09 00:20:14 +00:00
lobehubbot 748722300c 🤖 chore: Lighthouse Results | Chat 2024-02-09 00:20:11 +00:00
lobehubbot 2971de9df1 🤖 chore: Lighthouse Results | Welcome 2024-02-08 00:22:46 +00:00
lobehubbot a602edaffd 🤖 chore: Lighthouse Results | Settings 2024-02-08 00:20:47 +00:00
lobehubbot fc8aeebb3e 🤖 chore: Lighthouse Results | Chat 2024-02-08 00:20:35 +00:00
lobehubbot 06b52bb274 🤖 chore: Lighthouse Results | Market 2024-02-08 00:20:25 +00:00
lobehubbot 639914b21b 🤖 chore: Lighthouse Results | Welcome 2024-02-07 00:21:46 +00:00
lobehubbot 5709dddbd0 🤖 chore: Lighthouse Results | Market 2024-02-07 00:19:34 +00:00
lobehubbot 033889b24a 🤖 chore: Lighthouse Results | Chat 2024-02-07 00:19:27 +00:00
lobehubbot 1e98c2729f 🤖 chore: Lighthouse Results | Settings 2024-02-07 00:19:25 +00:00
lobehubbot 4b83582818 🤖 chore: Lighthouse Results | Welcome 2024-02-06 00:22:38 +00:00
lobehubbot 7fd40d8618 🤖 chore: Lighthouse Results | Market 2024-02-06 00:20:48 +00:00
lobehubbot c233dab4ab 🤖 chore: Lighthouse Results | Chat 2024-02-06 00:20:26 +00:00
lobehubbot 0dee90b5b2 🤖 chore: Lighthouse Results | Settings 2024-02-06 00:20:23 +00:00
lobehubbot f40ce17466 🤖 chore: Lighthouse Results | Welcome 2024-02-05 00:23:31 +00:00
lobehubbot ccd3d6d97b 🤖 chore: Lighthouse Results | Market 2024-02-05 00:21:46 +00:00
lobehubbot 76b0fcc359 🤖 chore: Lighthouse Results | Chat 2024-02-05 00:21:32 +00:00
lobehubbot 4cd6b41d51 🤖 chore: Lighthouse Results | Settings 2024-02-05 00:21:30 +00:00
lobehubbot cc2a51d4dd 🤖 chore: Lighthouse Results | Welcome 2024-02-04 00:24:37 +00:00
lobehubbot 994784322b 🤖 chore: Lighthouse Results | Market 2024-02-04 00:22:41 +00:00
lobehubbot df3e1b6299 🤖 chore: Lighthouse Results | Chat 2024-02-04 00:22:36 +00:00
lobehubbot 1ca3a938ec 🤖 chore: Lighthouse Results | Settings 2024-02-04 00:22:31 +00:00
lobehubbot 370e7e7b4b 🤖 chore: Lighthouse Results | Welcome 2024-02-03 00:22:54 +00:00
lobehubbot ca87277bf8 🤖 chore: Lighthouse Results | Chat 2024-02-03 00:21:23 +00:00
lobehubbot fd36eede8b 🤖 chore: Lighthouse Results | Market 2024-02-03 00:21:20 +00:00
lobehubbot ab3b8d419b 🤖 chore: Lighthouse Results | Settings 2024-02-03 00:20:43 +00:00
lobehubbot c134e19680 🤖 chore: Lighthouse Results | Welcome 2024-02-02 00:22:27 +00:00
lobehubbot b8a28b6ed3 🤖 chore: Lighthouse Results | Settings 2024-02-02 00:20:29 +00:00
lobehubbot 2008b11462 🤖 chore: Lighthouse Results | Chat 2024-02-02 00:20:28 +00:00
lobehubbot f20a76a37d 🤖 chore: Lighthouse Results | Market 2024-02-02 00:20:14 +00:00
lobehubbot 9a6c55f1d4 🤖 chore: Lighthouse Results | Welcome 2024-02-01 00:23:50 +00:00
lobehubbot da1f665cdd 🤖 chore: Lighthouse Results | Settings 2024-02-01 00:21:42 +00:00
lobehubbot 500d7514f7 🤖 chore: Lighthouse Results | Chat 2024-02-01 00:21:39 +00:00
lobehubbot d521bb0d67 🤖 chore: Lighthouse Results | Market 2024-02-01 00:21:38 +00:00
lobehubbot a89d87980c 🤖 chore: Lighthouse Results | Welcome 2024-01-31 00:23:12 +00:00
lobehubbot 60b3e1d311 🤖 chore: Lighthouse Results | Settings 2024-01-31 00:21:09 +00:00
lobehubbot 48fe062c3f 🤖 chore: Lighthouse Results | Chat 2024-01-31 00:21:02 +00:00
lobehubbot a0981eb135 🤖 chore: Lighthouse Results | Market 2024-01-31 00:20:58 +00:00
lobehubbot 7019e6d16d 🤖 chore: Lighthouse Results | Welcome 2024-01-30 00:22:13 +00:00
lobehubbot 55da943cff 🤖 chore: Lighthouse Results | Settings 2024-01-30 00:20:30 +00:00
lobehubbot 8e88d22806 🤖 chore: Lighthouse Results | Market 2024-01-30 00:20:14 +00:00
lobehubbot 8c61bd8775 🤖 chore: Lighthouse Results | Welcome 2024-01-29 00:22:55 +00:00
lobehubbot 6a14384701 🤖 chore: Lighthouse Results | Settings 2024-01-29 00:21:25 +00:00
lobehubbot 8b98a25ef3 🤖 chore: Lighthouse Results | Market 2024-01-29 00:21:08 +00:00
lobehubbot 75f529936f 🤖 chore: Lighthouse Results | Chat 2024-01-29 00:20:58 +00:00
lobehubbot b9885799a0 🤖 chore: Lighthouse Results | Welcome 2024-01-28 00:24:30 +00:00
lobehubbot b093f643aa 🤖 chore: Lighthouse Results | Chat 2024-01-28 00:23:23 +00:00
lobehubbot 07713ca17f 🤖 chore: Lighthouse Results | Settings 2024-01-28 00:23:04 +00:00
lobehubbot 8d07c8ce52 🤖 chore: Lighthouse Results | Market 2024-01-28 00:22:20 +00:00
lobehubbot 2336c01c8e 🤖 chore: Lighthouse Results | Welcome 2024-01-27 00:21:38 +00:00
lobehubbot a9ea475832 🤖 chore: Lighthouse Results | Market 2024-01-27 00:19:58 +00:00
lobehubbot fc5cb972a7 🤖 chore: Lighthouse Results | Chat 2024-01-27 00:19:42 +00:00
lobehubbot f4948c0a93 🤖 chore: Lighthouse Results | Settings 2024-01-27 00:19:33 +00:00
lobehubbot 544a5fe57d 🤖 chore: Lighthouse Results | Welcome 2024-01-26 00:22:49 +00:00
lobehubbot 1b964088fc 🤖 chore: Lighthouse Results | Settings 2024-01-26 00:21:08 +00:00
lobehubbot 7c174fa4f2 🤖 chore: Lighthouse Results | Chat 2024-01-26 00:20:48 +00:00
lobehubbot dbe3c9f9b5 🤖 chore: Lighthouse Results | Market 2024-01-26 00:20:47 +00:00
lobehubbot abd69e1fa2 🤖 chore: Lighthouse Results | Welcome 2024-01-25 00:24:24 +00:00
lobehubbot 0083733985 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-25 00:22:36 +00:00
lobehubbot 21e6e3877b 🤖 chore: Lighthouse Results | Chat 2024-01-25 00:22:35 +00:00
lobehubbot e14139ad21 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-25 00:22:34 +00:00
lobehubbot 72df277675 🤖 chore: Lighthouse Results | Market 2024-01-25 00:22:33 +00:00
lobehubbot ae8a224d88 🤖 chore: Lighthouse Results | Settings 2024-01-25 00:22:33 +00:00
lobehubbot 9ac68990ea 🤖 chore: Lighthouse Results | Welcome 2024-01-24 00:24:39 +00:00
lobehubbot 3c729180c3 🤖 chore: Lighthouse Results | Settings 2024-01-24 00:22:41 +00:00
lobehubbot c49d51d202 🤖 chore: Lighthouse Results | Market 2024-01-24 00:22:30 +00:00
lobehubbot a1ad15d5a6 🤖 chore: Lighthouse Results | Chat 2024-01-24 00:22:25 +00:00
lobehubbot bf194e8d13 🤖 chore: Lighthouse Results | Chat 2024-01-23 00:22:27 +00:00
lobehubbot 28f7c8aae8 🤖 chore: Lighthouse Results | Settings 2024-01-23 00:22:22 +00:00
lobehubbot 6980c0eec7 🤖 chore: Lighthouse Results | Market 2024-01-23 00:22:13 +00:00
lobehubbot 7cb3906749 🤖 chore: Lighthouse Results | Welcome 2024-01-22 00:25:13 +00:00
lobehubbot 41804e14de 🤖 chore: Lighthouse Results | Market 2024-01-22 00:23:13 +00:00
lobehubbot 4267b9c57c 🤖 chore: Lighthouse Results | Settings 2024-01-22 00:23:05 +00:00
lobehubbot 9f0c15ae6a 🤖 chore: Lighthouse Results | Chat 2024-01-22 00:22:56 +00:00
lobehubbot ae50ed0f8c 🤖 chore: Lighthouse Results | Welcome 2024-01-21 00:26:04 +00:00
lobehubbot 606f9d3cba Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-21 00:24:03 +00:00
lobehubbot 7c3918bb39 🤖 chore: Lighthouse Results | Chat 2024-01-21 00:24:02 +00:00
lobehubbot de234bcf14 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-21 00:24:02 +00:00
lobehubbot 8445ed8e62 🤖 chore: Lighthouse Results | Settings 2024-01-21 00:24:01 +00:00
lobehubbot f951cf953e 🤖 chore: Lighthouse Results | Market 2024-01-21 00:24:01 +00:00
lobehubbot 816db760c9 🤖 chore: Lighthouse Results | Welcome 2024-01-20 00:23:10 +00:00
lobehubbot 18126b77b5 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-20 00:21:16 +00:00
lobehubbot e967180ecb 🤖 chore: Lighthouse Results | Settings 2024-01-20 00:21:15 +00:00
lobehubbot 08b22d077e Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-20 00:21:14 +00:00
lobehubbot e220495603 🤖 chore: Lighthouse Results | Market 2024-01-20 00:21:13 +00:00
lobehubbot 6115c4172c 🤖 chore: Lighthouse Results | Chat 2024-01-20 00:21:13 +00:00
lobehubbot ee098ea68b 🤖 chore: Lighthouse Results | Welcome 2024-01-19 00:24:15 +00:00
lobehubbot ed5f19f557 🤖 chore: Lighthouse Results | Settings 2024-01-19 00:22:23 +00:00
lobehubbot c1d0b21ce8 🤖 chore: Lighthouse Results | Market 2024-01-19 00:22:15 +00:00
lobehubbot cc10dfbcd0 🤖 chore: Lighthouse Results | Chat 2024-01-19 00:22:07 +00:00
lobehubbot f86c8ff719 🤖 chore: Lighthouse Results | Welcome 2024-01-18 00:23:44 +00:00
lobehubbot 5423595ef1 🤖 chore: Lighthouse Results | Market 2024-01-18 00:21:48 +00:00
lobehubbot 9643a7004d 🤖 chore: Lighthouse Results | Settings 2024-01-18 00:21:43 +00:00
lobehubbot 2dff2c8ece 🤖 chore: Lighthouse Results | Chat 2024-01-18 00:21:33 +00:00
lobehubbot e302337243 🤖 chore: Lighthouse Results | Welcome 2024-01-17 00:24:01 +00:00
lobehubbot 2d0bc9bd73 🤖 chore: Lighthouse Results | Market 2024-01-17 00:21:50 +00:00
lobehubbot e9766abebb 🤖 chore: Lighthouse Results | Settings 2024-01-17 00:21:47 +00:00
lobehubbot c41e1b4b5e 🤖 chore: Lighthouse Results | Chat 2024-01-17 00:21:41 +00:00
lobehubbot 17293d2b00 🤖 chore: Lighthouse Results | Welcome 2024-01-16 00:23:41 +00:00
lobehubbot 0a4ca92d0e 🤖 chore: Lighthouse Results | Market 2024-01-16 00:22:00 +00:00
lobehubbot 0eb0214850 🤖 chore: Lighthouse Results | Chat 2024-01-16 00:21:41 +00:00
lobehubbot cab61d09bc 🤖 chore: Lighthouse Results | Settings 2024-01-16 00:21:34 +00:00
lobehubbot 12561c9b42 🤖 chore: Lighthouse Results | Welcome 2024-01-15 00:25:01 +00:00
lobehubbot f04e746133 🤖 chore: Lighthouse Results | Market 2024-01-15 00:23:51 +00:00
lobehubbot 55e8cbed43 🤖 chore: Lighthouse Results | Chat 2024-01-15 00:23:09 +00:00
lobehubbot 66495842af 🤖 chore: Lighthouse Results | Settings 2024-01-15 00:22:59 +00:00
lobehubbot a7b80f13c4 🤖 chore: Lighthouse Results | Welcome 2024-01-14 00:25:53 +00:00
lobehubbot a5abfcf349 🤖 chore: Lighthouse Results | Settings 2024-01-14 00:24:34 +00:00
lobehubbot 03f8a09d9b 🤖 chore: Lighthouse Results | Chat 2024-01-14 00:24:00 +00:00
lobehubbot c61dfb3695 🤖 chore: Lighthouse Results | Market 2024-01-14 00:23:42 +00:00
lobehubbot 77638946c9 🤖 chore: Lighthouse Results | Welcome 2024-01-13 00:23:31 +00:00
lobehubbot 4693c2573d Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-13 00:21:47 +00:00
lobehubbot d6e55bd8cd 🤖 chore: Lighthouse Results | Chat 2024-01-13 00:21:46 +00:00
lobehubbot c7ba2f9ae6 🤖 chore: Lighthouse Results | Market 2024-01-13 00:21:46 +00:00
lobehubbot 64a2f0a5da 🤖 chore: Lighthouse Results | Settings 2024-01-13 00:21:38 +00:00
lobehubbot 037feb4cdf 🤖 chore: Lighthouse Results | Welcome 2024-01-12 00:23:34 +00:00
lobehubbot 4b1854b89f 🤖 chore: Lighthouse Results | Chat 2024-01-12 00:21:52 +00:00
lobehubbot af0fed2470 🤖 chore: Lighthouse Results | Market 2024-01-12 00:21:43 +00:00
lobehubbot 08af6632e5 🤖 chore: Lighthouse Results | Settings 2024-01-12 00:21:32 +00:00
lobehubbot fea8259cf5 🤖 chore: Lighthouse Results | Welcome 2024-01-11 00:23:49 +00:00
lobehubbot 50bca1862f Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-11 00:22:11 +00:00
lobehubbot 979915818c 🤖 chore: Lighthouse Results | Market 2024-01-11 00:22:09 +00:00
lobehubbot bb326101e5 🤖 chore: Lighthouse Results | Chat 2024-01-11 00:22:09 +00:00
lobehubbot 32facda50f 🤖 chore: Lighthouse Results | Settings 2024-01-11 00:21:50 +00:00
lobehubbot f06b7ad2a9 🤖 chore: Lighthouse Results | Welcome 2024-01-10 00:23:33 +00:00
lobehubbot e8ee807d17 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-10 00:21:52 +00:00
lobehubbot 35c69cb0f2 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-10 00:21:51 +00:00
lobehubbot ac04ea1883 🤖 chore: Lighthouse Results | Chat 2024-01-10 00:21:51 +00:00
lobehubbot 1365e7bd16 🤖 chore: Lighthouse Results | Settings 2024-01-10 00:21:50 +00:00
lobehubbot 256025ca28 🤖 chore: Lighthouse Results | Market 2024-01-10 00:21:50 +00:00
lobehubbot 092ac001b6 🤖 chore: Lighthouse Results | Welcome 2024-01-09 00:23:54 +00:00
lobehubbot 0a33d4b0ed 🤖 chore: Lighthouse Results | Chat 2024-01-09 00:22:21 +00:00
lobehubbot 221caa3205 🤖 chore: Lighthouse Results | Settings 2024-01-09 00:22:07 +00:00
lobehubbot 0411a8242b 🤖 chore: Lighthouse Results | Market 2024-01-09 00:22:03 +00:00
lobehubbot 1e84d2f424 🤖 chore: Lighthouse Results | Welcome 2024-01-08 00:24:20 +00:00
lobehubbot 942b66ec93 🤖 chore: Lighthouse Results | Market 2024-01-08 00:22:29 +00:00
lobehubbot f224bf127b 🤖 chore: Lighthouse Results | Chat 2024-01-08 00:22:25 +00:00
lobehubbot 48a299b06e 🤖 chore: Lighthouse Results | Settings 2024-01-08 00:22:23 +00:00
lobehubbot 2f93158f94 🤖 chore: Lighthouse Results | Welcome 2024-01-07 00:25:38 +00:00
lobehubbot ff4d80c310 🤖 chore: Lighthouse Results | Chat 2024-01-07 00:23:50 +00:00
lobehubbot 33bf44dd4a 🤖 chore: Lighthouse Results | Settings 2024-01-07 00:23:49 +00:00
lobehubbot a366d2452c 🤖 chore: Lighthouse Results | Market 2024-01-07 00:23:35 +00:00
lobehubbot eaac6f0da8 🤖 chore: Lighthouse Results | Welcome 2024-01-06 00:23:32 +00:00
lobehubbot 94a267caf8 🤖 chore: Lighthouse Results | Market 2024-01-06 00:22:09 +00:00
lobehubbot 5172a5f112 🤖 chore: Lighthouse Results | Chat 2024-01-06 00:21:50 +00:00
lobehubbot 5b85791f45 🤖 chore: Lighthouse Results | Settings 2024-01-06 00:21:30 +00:00
lobehubbot 7b6498b25a 🤖 chore: Lighthouse Results | Welcome 2024-01-05 00:23:16 +00:00
lobehubbot 6a86d0ac9b 🤖 chore: Lighthouse Results | Market 2024-01-05 00:21:16 +00:00
lobehubbot 01e36c7c6a Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2024-01-05 00:21:15 +00:00
lobehubbot 492170fcf4 🤖 chore: Lighthouse Results | Chat 2024-01-05 00:21:14 +00:00
lobehubbot ed0f04c53b 🤖 chore: Lighthouse Results | Settings 2024-01-05 00:21:14 +00:00
lobehubbot b05a685f25 🤖 chore: Lighthouse Results | Welcome 2024-01-04 00:23:44 +00:00
lobehubbot fe775bcd4f 🤖 chore: Lighthouse Results | Settings 2024-01-04 00:21:45 +00:00
lobehubbot 2d8e796f26 🤖 chore: Lighthouse Results | Chat 2024-01-04 00:21:37 +00:00
lobehubbot 9fb863f635 🤖 chore: Lighthouse Results | Market 2024-01-04 00:21:36 +00:00
lobehubbot f826caffa7 🤖 chore: Lighthouse Results | Welcome 2024-01-03 00:23:24 +00:00
lobehubbot 8fbe8643e8 🤖 chore: Lighthouse Results | Settings 2024-01-03 00:21:35 +00:00
lobehubbot d6c91479c6 🤖 chore: Lighthouse Results | Chat 2024-01-03 00:21:23 +00:00
lobehubbot c3c6f045e5 🤖 chore: Lighthouse Results | Market 2024-01-03 00:21:13 +00:00
lobehubbot f884515ed2 🤖 chore: Lighthouse Results | Welcome 2024-01-02 00:23:13 +00:00
lobehubbot 9f6fc15098 🤖 chore: Lighthouse Results | Chat 2024-01-02 00:21:16 +00:00
lobehubbot c4ee1e4e40 🤖 chore: Lighthouse Results | Market 2024-01-02 00:21:13 +00:00
lobehubbot a1079d1c2c 🤖 chore: Lighthouse Results | Settings 2024-01-02 00:21:12 +00:00
lobehubbot 75716a4ae5 🤖 chore: Lighthouse Results | Welcome 2024-01-01 00:26:13 +00:00
lobehubbot 98164f36c5 🤖 chore: Lighthouse Results | Market 2024-01-01 00:24:09 +00:00
lobehubbot ca6e88ce16 🤖 chore: Lighthouse Results | Chat 2024-01-01 00:24:04 +00:00
lobehubbot 26dd1ef375 🤖 chore: Lighthouse Results | Settings 2024-01-01 00:24:03 +00:00
lobehubbot 2228441aeb 🤖 chore: Lighthouse Results | Welcome 2023-12-31 00:25:49 +00:00
lobehubbot 25c85b104d 🤖 chore: Lighthouse Results | Chat 2023-12-31 00:23:50 +00:00
lobehubbot 01a399c722 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-31 00:23:48 +00:00
lobehubbot 7e3ca958e7 🤖 chore: Lighthouse Results | Settings 2023-12-31 00:23:47 +00:00
lobehubbot 8b471754e4 🤖 chore: Lighthouse Results | Market 2023-12-31 00:23:46 +00:00
lobehubbot 0ad1a40efb 🤖 chore: Lighthouse Results | Welcome 2023-12-30 00:22:35 +00:00
lobehubbot 91bb47c65f 🤖 chore: Lighthouse Results | Market 2023-12-30 00:20:44 +00:00
lobehubbot 30b7b0f413 🤖 chore: Lighthouse Results | Settings 2023-12-30 00:20:42 +00:00
lobehubbot adc189bcbd 🤖 chore: Lighthouse Results | Chat 2023-12-30 00:20:33 +00:00
lobehubbot 5aa8e872e7 🤖 chore: Lighthouse Results | Welcome 2023-12-29 00:19:33 +00:00
lobehubbot 267e9e7ce8 🤖 chore: Lighthouse Results | Chat 2023-12-29 00:17:53 +00:00
lobehubbot 4a2e2ca811 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-29 00:17:35 +00:00
lobehubbot 78921d1474 🤖 chore: Lighthouse Results | Settings 2023-12-29 00:17:34 +00:00
lobehubbot edb35a0e9e 🤖 chore: Lighthouse Results | Market 2023-12-29 00:17:34 +00:00
lobehubbot 7b81bf82f4 🤖 chore: Lighthouse Results | Welcome 2023-12-28 00:23:02 +00:00
lobehubbot 16a98215ca 🤖 chore: Lighthouse Results | Market 2023-12-28 00:21:23 +00:00
lobehubbot 34b49afd9a 🤖 chore: Lighthouse Results | Settings 2023-12-28 00:21:21 +00:00
lobehubbot 802206a2ee 🤖 chore: Lighthouse Results | Chat 2023-12-28 00:21:11 +00:00
lobehubbot 1b8d2c9298 🤖 chore: Lighthouse Results | Welcome 2023-12-27 00:22:33 +00:00
lobehubbot 51783adeb5 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-27 00:20:48 +00:00
lobehubbot d560b5779f 🤖 chore: Lighthouse Results | Market 2023-12-27 00:20:47 +00:00
lobehubbot 199d31395a Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-27 00:20:46 +00:00
lobehubbot 1cfc697fa9 🤖 chore: Lighthouse Results | Settings 2023-12-27 00:20:45 +00:00
lobehubbot 6cd9dad145 🤖 chore: Lighthouse Results | Chat 2023-12-27 00:20:45 +00:00
lobehubbot 28c3232b2c 🤖 chore: Lighthouse Results | Welcome 2023-12-26 00:22:38 +00:00
lobehubbot 5f7e16a234 🤖 chore: Lighthouse Results | Market 2023-12-26 00:21:25 +00:00
lobehubbot 41204e8376 🤖 chore: Lighthouse Results | Chat 2023-12-26 00:21:06 +00:00
lobehubbot d8a26c1dad 🤖 chore: Lighthouse Results | Settings 2023-12-26 00:20:42 +00:00
lobehubbot d991e6b8eb 🤖 chore: Lighthouse Results | Welcome 2023-12-25 00:24:15 +00:00
lobehubbot 8cdc08ac37 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-25 00:22:04 +00:00
lobehubbot 97c7f4dbff 🤖 chore: Lighthouse Results | Chat 2023-12-25 00:22:03 +00:00
lobehubbot 8f7103d0e1 🤖 chore: Lighthouse Results | Settings 2023-12-25 00:22:03 +00:00
lobehubbot e2a31314a0 🤖 chore: Lighthouse Results | Market 2023-12-25 00:21:54 +00:00
lobehubbot 9b62d51a24 🤖 chore: Lighthouse Results | Welcome 2023-12-24 00:24:55 +00:00
lobehubbot 3aa05c898a Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-24 00:23:08 +00:00
lobehubbot 0df7ecb7a0 🤖 chore: Lighthouse Results | Settings 2023-12-24 00:23:07 +00:00
lobehubbot e35f9f7afc 🤖 chore: Lighthouse Results | Chat 2023-12-24 00:23:07 +00:00
lobehubbot 51b575c8df 🤖 chore: Lighthouse Results | Market 2023-12-24 00:23:02 +00:00
lobehubbot 59e547510d 🤖 chore: Lighthouse Results | Welcome 2023-12-23 00:22:24 +00:00
lobehubbot 6591a474fe 🤖 chore: Lighthouse Results | Market 2023-12-23 00:20:29 +00:00
lobehubbot 4a0187f1d9 🤖 chore: Lighthouse Results | Chat 2023-12-23 00:20:19 +00:00
lobehubbot 2e07000d9c 🤖 chore: Lighthouse Results | Settings 2023-12-23 00:20:16 +00:00
lobehubbot 5e8f93127e 🤖 chore: Lighthouse Results | Welcome 2023-12-22 00:23:08 +00:00
lobehubbot 131e4f8b96 🤖 chore: Lighthouse Results | Market 2023-12-22 00:22:01 +00:00
lobehubbot 10b8273d9b 🤖 chore: Lighthouse Results | Chat 2023-12-22 00:21:26 +00:00
lobehubbot d3691371f1 🤖 chore: Lighthouse Results | Settings 2023-12-22 00:21:25 +00:00
lobehubbot 5239af4592 🤖 chore: Lighthouse Results | Welcome 2023-12-21 00:23:27 +00:00
lobehubbot aa32938632 🤖 chore: Lighthouse Results | Chat 2023-12-21 00:21:27 +00:00
lobehubbot c45fcef3fd 🤖 chore: Lighthouse Results | Market 2023-12-21 00:21:15 +00:00
lobehubbot e957329f5c 🤖 chore: Lighthouse Results | Settings 2023-12-21 00:21:05 +00:00
lobehubbot 6c6a14c755 🤖 chore: Lighthouse Results | Welcome 2023-12-20 00:19:58 +00:00
lobehubbot 7c5357cc70 🤖 chore: Lighthouse Results | Market 2023-12-20 00:18:26 +00:00
lobehubbot bacb0e125c 🤖 chore: Lighthouse Results | Chat 2023-12-20 00:17:58 +00:00
lobehubbot 068bc3d230 🤖 chore: Lighthouse Results | Settings 2023-12-20 00:17:41 +00:00
lobehubbot fbad3ac44b 🤖 chore: Lighthouse Results | Welcome 2023-12-19 00:23:43 +00:00
lobehubbot 8cb023f257 🤖 chore: Lighthouse Results | Market 2023-12-19 00:22:04 +00:00
lobehubbot 3efbf49c97 🤖 chore: Lighthouse Results | Chat 2023-12-19 00:21:56 +00:00
lobehubbot 20a24fdf16 🤖 chore: Lighthouse Results | Settings 2023-12-19 00:21:42 +00:00
lobehubbot 7b4cc90707 🤖 chore: Lighthouse Results | Welcome 2023-12-18 00:24:31 +00:00
lobehubbot c0b84f84ef 🤖 chore: Lighthouse Results | Market 2023-12-18 00:22:27 +00:00
lobehubbot 18cb3ef4f3 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-18 00:22:21 +00:00
lobehubbot 44c12178b8 🤖 chore: Lighthouse Results | Settings 2023-12-18 00:22:21 +00:00
lobehubbot 9cdc953d0b 🤖 chore: Lighthouse Results | Chat 2023-12-18 00:22:20 +00:00
lobehubbot 771bbe200e 🤖 chore: Lighthouse Results | Welcome 2023-12-17 00:25:28 +00:00
lobehubbot 4c97799a81 🤖 chore: Lighthouse Results | Settings 2023-12-17 00:23:59 +00:00
lobehubbot 7486f87c86 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-17 00:23:44 +00:00
lobehubbot 15c53a21a0 🤖 chore: Lighthouse Results | Market 2023-12-17 00:23:43 +00:00
lobehubbot e3d11762c4 🤖 chore: Lighthouse Results | Chat 2023-12-17 00:23:43 +00:00
lobehubbot 7657ae10d1 🤖 chore: Lighthouse Results | Welcome 2023-12-16 00:23:15 +00:00
lobehubbot 1d2ffb13dc Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-16 00:21:12 +00:00
lobehubbot 1c7da649a4 🤖 chore: Lighthouse Results | Chat 2023-12-16 00:21:11 +00:00
lobehubbot 9513cb954c 🤖 chore: Lighthouse Results | Market 2023-12-16 00:21:11 +00:00
lobehubbot a67c20114a 🤖 chore: Lighthouse Results | Settings 2023-12-16 00:21:10 +00:00
lobehubbot 80ba342128 🤖 chore: Lighthouse Results | Welcome 2023-12-15 01:19:02 +00:00
lobehubbot 19dfe632c3 🤖 chore: Lighthouse Results | Chat 2023-12-15 01:17:25 +00:00
lobehubbot 0415e55fa4 🤖 chore: Lighthouse Results | Market 2023-12-15 01:17:11 +00:00
lobehubbot 31f35e0f62 🤖 chore: Lighthouse Results | Settings 2023-12-15 01:17:07 +00:00
lobehubbot 2e18289df1 🤖 chore: Lighthouse Results | Welcome 2023-12-14 01:17:12 +00:00
lobehubbot f63cafa3b5 🤖 chore: Lighthouse Results | Chat 2023-12-14 01:15:22 +00:00
lobehubbot 7db1847c41 🤖 chore: Lighthouse Results | Settings 2023-12-14 01:15:15 +00:00
lobehubbot 1ad43cbd06 🤖 chore: Lighthouse Results | Market 2023-12-14 01:15:13 +00:00
lobehubbot 035bb4679b 🤖 chore: Lighthouse Results | Welcome 2023-12-13 01:18:27 +00:00
lobehubbot 24f07060b4 🤖 chore: Lighthouse Results | Settings 2023-12-13 01:16:26 +00:00
lobehubbot 88afa9fd97 🤖 chore: Lighthouse Results | Market 2023-12-13 01:16:24 +00:00
lobehubbot 78b6fe4f45 🤖 chore: Lighthouse Results | Chat 2023-12-13 01:16:18 +00:00
lobehubbot 9fc2bd38ab 🤖 chore: Lighthouse Results | Welcome 2023-12-12 01:18:51 +00:00
lobehubbot f8ec69a2ae 🤖 chore: Lighthouse Results | Settings 2023-12-12 01:17:02 +00:00
lobehubbot 2c97056c24 🤖 chore: Lighthouse Results | Market 2023-12-12 01:16:58 +00:00
lobehubbot c72091233b 🤖 chore: Lighthouse Results | Chat 2023-12-12 01:16:56 +00:00
lobehubbot 53bcc6a4e6 🤖 chore: Lighthouse Results | Welcome 2023-12-11 01:18:49 +00:00
lobehubbot 2e1d5bde9e 🤖 chore: Lighthouse Results | Market 2023-12-11 01:17:03 +00:00
lobehubbot 68782decf3 🤖 chore: Lighthouse Results | Chat 2023-12-11 01:16:48 +00:00
lobehubbot c52a24524d 🤖 chore: Lighthouse Results | Settings 2023-12-11 01:16:46 +00:00
lobehubbot ca20df4494 🤖 chore: Lighthouse Results | Welcome 2023-12-10 01:22:50 +00:00
lobehubbot b76c7da844 🤖 chore: Lighthouse Results | Chat 2023-12-10 01:20:51 +00:00
lobehubbot 0fddcf7608 🤖 chore: Lighthouse Results | Market 2023-12-10 01:20:47 +00:00
lobehubbot 32a7776453 🤖 chore: Lighthouse Results | Settings 2023-12-10 01:20:39 +00:00
lobehubbot 15fcb61de7 🤖 chore: Lighthouse Results | Welcome 2023-12-09 01:16:57 +00:00
lobehubbot 068fa06797 🤖 chore: Lighthouse Results | Settings 2023-12-09 01:14:46 +00:00
lobehubbot 82f4943a82 🤖 chore: Lighthouse Results | Market 2023-12-09 01:14:43 +00:00
lobehubbot a4ef2aee23 🤖 chore: Lighthouse Results | Chat 2023-12-09 01:14:38 +00:00
lobehubbot 1792a0cbe6 🤖 chore: Lighthouse Results | Welcome 2023-12-08 01:18:46 +00:00
lobehubbot 30998056bd 🤖 chore: Lighthouse Results | Market 2023-12-08 01:16:58 +00:00
lobehubbot b156de1ada 🤖 chore: Lighthouse Results | Chat 2023-12-08 01:16:52 +00:00
lobehubbot 9ddc86a85d 🤖 chore: Lighthouse Results | Settings 2023-12-08 01:16:45 +00:00
lobehubbot 477f5a3543 🤖 chore: Lighthouse Results | Welcome 2023-12-07 01:18:18 +00:00
lobehubbot 0e9c96b2a2 🤖 chore: Lighthouse Results | Market 2023-12-07 01:16:24 +00:00
lobehubbot 9c06999083 🤖 chore: Lighthouse Results | Chat 2023-12-07 01:16:17 +00:00
lobehubbot 6404ef65d1 🤖 chore: Lighthouse Results | Settings 2023-12-07 01:16:10 +00:00
lobehubbot 0248ade57a 🤖 chore: Lighthouse Results | Welcome 2023-12-06 01:18:40 +00:00
lobehubbot 7663154953 🤖 chore: Lighthouse Results | Market 2023-12-06 01:16:55 +00:00
lobehubbot dc4bf29924 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-12-06 01:16:38 +00:00
lobehubbot 56f9df8b07 🤖 chore: Lighthouse Results | Chat 2023-12-06 01:16:38 +00:00
lobehubbot 221fc498dd 🤖 chore: Lighthouse Results | Settings 2023-12-06 01:16:37 +00:00
lobehubbot b1ce53d1a0 🤖 chore: Lighthouse Results | Welcome 2023-12-05 01:18:59 +00:00
lobehubbot 75ffd2cd1b 🤖 chore: Lighthouse Results | Settings 2023-12-05 01:16:59 +00:00
lobehubbot 339a63a27c 🤖 chore: Lighthouse Results | Chat 2023-12-05 01:16:57 +00:00
lobehubbot 3c3296047c 🤖 chore: Lighthouse Results | Market 2023-12-05 01:16:53 +00:00
lobehubbot 5807d47428 🤖 chore: Lighthouse Results | Welcome 2023-12-04 01:18:25 +00:00
lobehubbot 7574275d61 🤖 chore: Lighthouse Results | Settings 2023-12-04 01:16:53 +00:00
lobehubbot 8a32281179 🤖 chore: Lighthouse Results | Market 2023-12-04 01:16:31 +00:00
lobehubbot cda0cacf57 🤖 chore: Lighthouse Results | Chat 2023-12-04 01:16:24 +00:00
lobehubbot bd7d95d035 🤖 chore: Lighthouse Results | Welcome 2023-12-03 01:21:38 +00:00
lobehubbot b80f7e68dd 🤖 chore: Lighthouse Results | Market 2023-12-03 01:19:47 +00:00
lobehubbot 469be14b76 🤖 chore: Lighthouse Results | Chat 2023-12-03 01:19:44 +00:00
lobehubbot 9a5daf2442 🤖 chore: Lighthouse Results | Settings 2023-12-03 01:19:37 +00:00
lobehubbot 7243e7fa59 🤖 chore: Lighthouse Results | Welcome 2023-12-02 01:14:28 +00:00
lobehubbot 1b4ee4dbbc 🤖 chore: Lighthouse Results | Settings 2023-12-02 01:12:39 +00:00
lobehubbot e59e6c303f 🤖 chore: Lighthouse Results | Market 2023-12-02 01:12:31 +00:00
lobehubbot 1b73c28539 🤖 chore: Lighthouse Results | Chat 2023-12-02 01:12:30 +00:00
lobehubbot ef1fd656a7 🤖 chore: Lighthouse Results | Welcome 2023-12-01 01:24:08 +00:00
lobehubbot 51afd59888 🤖 chore: Lighthouse Results | Market 2023-12-01 01:22:56 +00:00
lobehubbot 4085fde3f9 🤖 chore: Lighthouse Results | Chat 2023-12-01 01:22:23 +00:00
lobehubbot 247dd0de23 🤖 chore: Lighthouse Results | Settings 2023-12-01 01:22:21 +00:00
lobehubbot ded2760092 🤖 chore: Lighthouse Results | Welcome 2023-11-30 01:17:37 +00:00
lobehubbot 67b01c9c4c 🤖 chore: Lighthouse Results | Chat 2023-11-30 01:15:45 +00:00
lobehubbot 89a30a4df6 🤖 chore: Lighthouse Results | Market 2023-11-30 01:15:40 +00:00
lobehubbot 49cc7f01b4 🤖 chore: Lighthouse Results | Settings 2023-11-30 01:15:28 +00:00
lobehubbot e3e1f7b762 🤖 chore: Lighthouse Results | Welcome 2023-11-29 01:18:36 +00:00
lobehubbot 8de708ff64 🤖 chore: Lighthouse Results | Settings 2023-11-29 01:16:49 +00:00
lobehubbot 28d149b816 🤖 chore: Lighthouse Results | Chat 2023-11-29 01:16:47 +00:00
lobehubbot fc575d1b5e 🤖 chore: Lighthouse Results | Market 2023-11-29 01:16:33 +00:00
lobehubbot f82d34013b 🤖 chore: Lighthouse Results | Welcome 2023-11-28 01:18:39 +00:00
lobehubbot d271982775 🤖 chore: Lighthouse Results | Settings 2023-11-28 01:16:52 +00:00
lobehubbot 079739bf0a Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-28 01:16:47 +00:00
lobehubbot d92f42c2e0 🤖 chore: Lighthouse Results | Chat 2023-11-28 01:16:45 +00:00
lobehubbot ec6acaca5c 🤖 chore: Lighthouse Results | Market 2023-11-28 01:16:45 +00:00
lobehubbot 023598d489 🤖 chore: Lighthouse Results | Welcome 2023-11-27 01:17:54 +00:00
lobehubbot c47b944856 🤖 chore: Lighthouse Results | Chat 2023-11-27 01:16:08 +00:00
lobehubbot 8996d05c4c 🤖 chore: Lighthouse Results | Settings 2023-11-27 01:15:55 +00:00
lobehubbot ad77b6a63b 🤖 chore: Lighthouse Results | Market 2023-11-27 01:15:54 +00:00
lobehubbot e8d396c44b 🤖 chore: Lighthouse Results | Welcome 2023-11-26 01:21:38 +00:00
lobehubbot 3c984bfc0c 🤖 chore: Lighthouse Results | Chat 2023-11-26 01:19:44 +00:00
lobehubbot 676cf1c2a3 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-26 01:19:31 +00:00
lobehubbot 490addb7ee 🤖 chore: Lighthouse Results | Market 2023-11-26 01:19:30 +00:00
lobehubbot 0fa3135419 🤖 chore: Lighthouse Results | Settings 2023-11-26 01:19:30 +00:00
lobehubbot 350d5d488e 🤖 chore: Lighthouse Results | Welcome 2023-11-25 01:14:36 +00:00
lobehubbot c1b2430701 🤖 chore: Lighthouse Results | Market 2023-11-25 01:13:18 +00:00
lobehubbot 659226d730 🤖 chore: Lighthouse Results | Settings 2023-11-25 01:13:08 +00:00
lobehubbot 9f855ac1aa 🤖 chore: Lighthouse Results | Chat 2023-11-25 01:12:52 +00:00
lobehubbot b2d0bad783 🤖 chore: Lighthouse Results | Welcome 2023-11-24 01:15:51 +00:00
lobehubbot b6a4cf4f8c 🤖 chore: Lighthouse Results | Market 2023-11-24 01:13:54 +00:00
lobehubbot a2a32d41cf 🤖 chore: Lighthouse Results | Settings 2023-11-24 01:13:48 +00:00
lobehubbot 84700f7578 🤖 chore: Lighthouse Results | Chat 2023-11-24 01:13:45 +00:00
lobehubbot 0cce2ad195 🤖 chore: Lighthouse Results | Welcome 2023-11-23 01:17:33 +00:00
lobehubbot 8ff794e756 🤖 chore: Lighthouse Results | Chat 2023-11-23 01:15:39 +00:00
lobehubbot c24b2c3469 🤖 chore: Lighthouse Results | Market 2023-11-23 01:15:37 +00:00
lobehubbot d90696da1e 🤖 chore: Lighthouse Results | Settings 2023-11-23 01:15:35 +00:00
lobehubbot 755aa92db4 🤖 chore: Lighthouse Results | Welcome 2023-11-22 01:20:20 +00:00
lobehubbot 2f907d87e5 🤖 chore: Lighthouse Results | Settings 2023-11-22 01:18:44 +00:00
lobehubbot e2e77f05ea 🤖 chore: Lighthouse Results | Market 2023-11-22 01:18:27 +00:00
lobehubbot 013405f808 🤖 chore: Lighthouse Results | Chat 2023-11-22 01:18:08 +00:00
lobehubbot 6d9a7c088a 🤖 chore: Lighthouse Results | Welcome 2023-11-21 01:20:15 +00:00
lobehubbot bfdbf4d2c3 🤖 chore: Lighthouse Results | Market 2023-11-21 01:18:37 +00:00
lobehubbot 79daf018bd 🤖 chore: Lighthouse Results | Chat 2023-11-21 01:18:28 +00:00
lobehubbot cf797fecec 🤖 chore: Lighthouse Results | Settings 2023-11-21 01:18:25 +00:00
lobehubbot 88f773b6d5 🤖 chore: Lighthouse Results | Welcome 2023-11-20 01:18:35 +00:00
lobehubbot e7da01f9d7 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-20 01:16:33 +00:00
lobehubbot d141f81b62 🤖 chore: Lighthouse Results | Chat 2023-11-20 01:16:33 +00:00
lobehubbot c833e9109f Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-20 01:16:32 +00:00
lobehubbot 67c5339087 🤖 chore: Lighthouse Results | Market 2023-11-20 01:16:31 +00:00
lobehubbot 0f89385946 🤖 chore: Lighthouse Results | Settings 2023-11-20 01:16:31 +00:00
lobehubbot 3ebe01c82f 🤖 chore: Lighthouse Results | Welcome 2023-11-19 01:22:30 +00:00
lobehubbot 9024fac0dd 🤖 chore: Lighthouse Results | Market 2023-11-19 01:20:46 +00:00
lobehubbot f37f370192 🤖 chore: Lighthouse Results | Chat 2023-11-19 01:20:44 +00:00
lobehubbot 8ff3a20342 🤖 chore: Lighthouse Results | Settings 2023-11-19 01:20:40 +00:00
lobehubbot 65c167e6df 🤖 chore: Lighthouse Results | Welcome 2023-11-18 01:17:01 +00:00
lobehubbot 4c425412e2 🤖 chore: Lighthouse Results | Market 2023-11-18 01:15:00 +00:00
lobehubbot 7024991b0d 🤖 chore: Lighthouse Results | Chat 2023-11-18 01:14:58 +00:00
lobehubbot 5c53537fec 🤖 chore: Lighthouse Results | Settings 2023-11-18 01:14:55 +00:00
lobehubbot a8ef6a65cc 🤖 chore: Lighthouse Results | Welcome 2023-11-17 01:17:47 +00:00
lobehubbot cd085abb3a 🤖 chore: Lighthouse Results | Chat 2023-11-17 01:16:08 +00:00
lobehubbot 1d6b4968a1 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-17 01:15:51 +00:00
lobehubbot ca45e9b877 🤖 chore: Lighthouse Results | Market 2023-11-17 01:15:50 +00:00
lobehubbot bc130c90d6 🤖 chore: Lighthouse Results | Settings 2023-11-17 01:15:49 +00:00
lobehubbot e71fc19806 🤖 chore: Lighthouse Results | Welcome 2023-11-16 01:18:12 +00:00
lobehubbot 641d119478 🤖 chore: Lighthouse Results | Chat 2023-11-16 01:16:21 +00:00
lobehubbot 58ace31d71 🤖 chore: Lighthouse Results | Market 2023-11-16 01:16:15 +00:00
lobehubbot 2e025eb52c 🤖 chore: Lighthouse Results | Settings 2023-11-16 01:16:07 +00:00
lobehubbot be040c6879 🤖 chore: Lighthouse Results | Welcome 2023-11-15 01:17:11 +00:00
lobehubbot 71c3b1eb88 🤖 chore: Lighthouse Results | Settings 2023-11-15 01:15:35 +00:00
lobehubbot 9e72f3f911 🤖 chore: Lighthouse Results | Chat 2023-11-15 01:15:29 +00:00
lobehubbot d70b28e19f 🤖 chore: Lighthouse Results | Market 2023-11-15 01:15:18 +00:00
lobehubbot c237db1f8b 🤖 chore: Lighthouse Results | Welcome 2023-11-14 01:15:56 +00:00
lobehubbot 94fba085fc 🤖 chore: Lighthouse Results | Chat 2023-11-14 01:13:51 +00:00
lobehubbot 3f5716f511 🤖 chore: Lighthouse Results | Market 2023-11-14 01:13:36 +00:00
lobehubbot daf6fb7953 🤖 chore: Lighthouse Results | Settings 2023-11-14 01:13:31 +00:00
lobehubbot ce9aaf4874 🤖 chore: Lighthouse Results | Welcome 2023-11-13 01:17:13 +00:00
lobehubbot 4506a95fe2 🤖 chore: Lighthouse Results | Market 2023-11-13 01:15:48 +00:00
lobehubbot b2097b2293 🤖 chore: Lighthouse Results | Chat 2023-11-13 01:15:45 +00:00
lobehubbot 3e7e017f9a 🤖 chore: Lighthouse Results | Settings 2023-11-13 01:15:36 +00:00
lobehubbot b96b87d18c 🤖 chore: Lighthouse Results | Welcome 2023-11-12 01:20:21 +00:00
lobehubbot 7aec3fd5fd 🤖 chore: Lighthouse Results | Market 2023-11-12 01:18:43 +00:00
lobehubbot e7def7b9aa 🤖 chore: Lighthouse Results | Chat 2023-11-12 01:18:37 +00:00
lobehubbot 165c3a81fa 🤖 chore: Lighthouse Results | Settings 2023-11-12 01:18:19 +00:00
lobehubbot 2e85b21569 🤖 chore: Lighthouse Results | Welcome 2023-11-11 02:27:11 +00:00
lobehubbot 50dcef6dc0 🤖 chore: Lighthouse Results | Chat 2023-11-11 02:25:10 +00:00
lobehubbot d4f68c30bd 🤖 chore: Lighthouse Results | Market 2023-11-11 02:13:54 +00:00
lobehubbot ddf04bffc6 🤖 chore: Lighthouse Results | Settings 2023-11-11 02:13:38 +00:00
lobehubbot 28fdc948cc 🤖 chore: Lighthouse Results | Welcome 2023-11-10 01:15:12 +00:00
lobehubbot 9940777b8b 🤖 chore: Lighthouse Results | Chat 2023-11-10 01:13:36 +00:00
lobehubbot 95f344f0ed 🤖 chore: Lighthouse Results | Settings 2023-11-10 01:13:20 +00:00
lobehubbot d695bbb358 🤖 chore: Lighthouse Results | Market 2023-11-10 01:13:13 +00:00
lobehubbot c6ede66d05 🤖 chore: Lighthouse Results | Welcome 2023-11-09 01:15:50 +00:00
lobehubbot 0fef692a03 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-09 01:14:07 +00:00
lobehubbot e448a332ff 🤖 chore: Lighthouse Results | Market 2023-11-09 01:14:06 +00:00
lobehubbot de2bf272b0 🤖 chore: Lighthouse Results | Settings 2023-11-09 01:14:05 +00:00
lobehubbot ee7bb2a772 🤖 chore: Lighthouse Results | Chat 2023-11-09 01:13:59 +00:00
lobehubbot 2219ad1ee9 🤖 chore: Lighthouse Results | Welcome 2023-11-08 01:15:01 +00:00
lobehubbot da24ec7f9f 🤖 chore: Lighthouse Results | Chat 2023-11-08 01:13:11 +00:00
lobehubbot fb573a68d1 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-08 01:13:06 +00:00
lobehubbot 744e8bceb1 🤖 chore: Lighthouse Results | Settings 2023-11-08 01:13:05 +00:00
lobehubbot c1e458f5ee 🤖 chore: Lighthouse Results | Market 2023-11-08 01:13:05 +00:00
lobehubbot b524e9939e 🤖 chore: Lighthouse Results | Welcome 2023-11-07 01:14:23 +00:00
lobehubbot a07d5aedb0 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-07 01:14:14 +00:00
lobehubbot 1eae504c4e 🤖 chore: Lighthouse Results | Settings 2023-11-07 01:14:13 +00:00
lobehubbot 687b5a1ec1 🤖 chore: Lighthouse Results | Market 2023-11-07 01:14:12 +00:00
lobehubbot c45da25d7e 🤖 chore: Lighthouse Results | Chat 2023-11-07 01:14:09 +00:00
lobehubbot 0c2dbc5097 🤖 chore: Lighthouse Results | Market 2023-11-06 01:15:38 +00:00
lobehubbot 4d583f5226 🤖 chore: Lighthouse Results | Chat 2023-11-06 01:15:35 +00:00
lobehubbot 7abac9d3f1 🤖 chore: Lighthouse Results | Welcome 2023-11-06 01:15:26 +00:00
lobehubbot 9dbcb09f41 🤖 chore: Lighthouse Results | Settings 2023-11-06 01:15:24 +00:00
lobehubbot 629b202253 🤖 chore: Lighthouse Results | Welcome 2023-11-05 01:19:35 +00:00
lobehubbot ac7bca7ee9 🤖 chore: Lighthouse Results | Market 2023-11-05 01:17:46 +00:00
lobehubbot f4251188fe 🤖 chore: Lighthouse Results | Chat 2023-11-05 01:17:40 +00:00
lobehubbot 6202387db6 🤖 chore: Lighthouse Results | Settings 2023-11-05 01:17:34 +00:00
lobehubbot 345f0e9a0d 🤖 chore: Lighthouse Results | Welcome 2023-11-04 01:13:37 +00:00
lobehubbot f980d448db 🤖 chore: Lighthouse Results | Market 2023-11-04 01:12:00 +00:00
lobehubbot 8a129b6722 🤖 chore: Lighthouse Results | Chat 2023-11-04 01:11:52 +00:00
lobehubbot 73ff722c5a 🤖 chore: Lighthouse Results | Settings 2023-11-04 01:11:46 +00:00
lobehubbot 342bb36a32 🤖 chore: Lighthouse Results | Welcome 2023-11-03 01:14:24 +00:00
lobehubbot 9ca2360340 🤖 chore: Lighthouse Results | Market 2023-11-03 01:13:08 +00:00
lobehubbot 63929d0e51 🤖 chore: Lighthouse Results | Chat 2023-11-03 01:12:58 +00:00
lobehubbot 2b997beb6b 🤖 chore: Lighthouse Results | Settings 2023-11-03 01:12:53 +00:00
lobehubbot e0f4b6bb78 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-02 01:12:14 +00:00
lobehubbot f28ce98863 🤖 chore: Lighthouse Results | Chat 2023-11-02 01:12:13 +00:00
lobehubbot a3358eff23 🤖 chore: Lighthouse Results | Welcome 2023-11-02 01:12:13 +00:00
lobehubbot 567e463a08 🤖 chore: Lighthouse Results | Settings 2023-11-02 01:12:08 +00:00
lobehubbot 0d689cde6f 🤖 chore: Lighthouse Results | Market 2023-11-02 01:12:06 +00:00
lobehubbot 6e318bdc9f 🤖 chore: Lighthouse Results | Welcome 2023-11-01 01:18:13 +00:00
lobehubbot f2ba63f0e7 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-11-01 01:16:40 +00:00
lobehubbot 428cf121dd 🤖 chore: Lighthouse Results | Market 2023-11-01 01:16:39 +00:00
lobehubbot 571c5e8024 🤖 chore: Lighthouse Results | Settings 2023-11-01 01:16:39 +00:00
lobehubbot 1b86735e97 🤖 chore: Lighthouse Results | Chat 2023-11-01 01:16:38 +00:00
lobehubbot 1b68c60dbb 🤖 chore: Lighthouse Results | Welcome 2023-10-31 01:14:19 +00:00
lobehubbot 87baef2b84 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-31 01:12:49 +00:00
lobehubbot 0ea4f909be Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-31 01:12:48 +00:00
lobehubbot d7ff01d84e 🤖 chore: Lighthouse Results | Market 2023-10-31 01:12:47 +00:00
lobehubbot 1aa40f6111 🤖 chore: Lighthouse Results | Chat 2023-10-31 01:12:47 +00:00
lobehubbot a32263cefe 🤖 chore: Lighthouse Results | Settings 2023-10-31 01:12:46 +00:00
lobehubbot 91f35fdda5 🤖 chore: Lighthouse Results | Welcome 2023-10-30 01:14:12 +00:00
lobehubbot ff4aa279b8 🤖 chore: Lighthouse Results | Chat 2023-10-30 01:12:35 +00:00
lobehubbot 52dd6bd3a1 🤖 chore: Lighthouse Results | Settings 2023-10-30 01:12:32 +00:00
lobehubbot 8e4c579cd2 🤖 chore: Lighthouse Results | Market 2023-10-30 01:12:27 +00:00
lobehubbot 8090579d4f 🤖 chore: Lighthouse Results | Welcome 2023-10-29 01:17:35 +00:00
lobehubbot 2628d7e86c 🤖 chore: Lighthouse Results | Market 2023-10-29 01:16:30 +00:00
lobehubbot 3617fd3fcc 🤖 chore: Lighthouse Results | Chat 2023-10-29 01:16:17 +00:00
lobehubbot b471b7bc7c 🤖 chore: Lighthouse Results | Settings 2023-10-29 01:15:54 +00:00
lobehubbot 4d78d7845b 🤖 chore: Lighthouse Results | Welcome 2023-10-28 01:10:57 +00:00
lobehubbot 06d274e82d 🤖 chore: Lighthouse Results | Market 2023-10-28 01:09:23 +00:00
lobehubbot 6588e1cb8c Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-28 01:09:21 +00:00
lobehubbot 61a065631a 🤖 chore: Lighthouse Results | Settings 2023-10-28 01:09:20 +00:00
lobehubbot c82ffd92a3 🤖 chore: Lighthouse Results | Chat 2023-10-28 01:09:20 +00:00
lobehubbot 325aabd105 🤖 chore: Lighthouse Results | Welcome 2023-10-27 01:12:27 +00:00
lobehubbot 317a764b03 🤖 chore: Lighthouse Results | Chat 2023-10-27 01:10:58 +00:00
lobehubbot 8d0002c6e0 🤖 chore: Lighthouse Results | Market 2023-10-27 01:10:54 +00:00
lobehubbot 4c75dd87c8 🤖 chore: Lighthouse Results | Settings 2023-10-27 01:10:48 +00:00
lobehubbot 3ef400d5d2 🤖 chore: Lighthouse Results | Welcome 2023-10-26 01:12:20 +00:00
lobehubbot a1edadc7ef 🤖 chore: Lighthouse Results | Market 2023-10-26 01:11:02 +00:00
lobehubbot a37990f8aa 🤖 chore: Lighthouse Results | Settings 2023-10-26 01:10:30 +00:00
lobehubbot 06dcb42895 🤖 chore: Lighthouse Results | Chat 2023-10-26 01:10:28 +00:00
lobehubbot e8274e5d57 🤖 chore: Lighthouse Results | Welcome 2023-10-25 01:13:21 +00:00
lobehubbot 17e6255e94 🤖 chore: Lighthouse Results | Market 2023-10-25 01:11:51 +00:00
lobehubbot 1cb7c95161 🤖 chore: Lighthouse Results | Settings 2023-10-25 01:11:50 +00:00
lobehubbot 0926694053 🤖 chore: Lighthouse Results | Chat 2023-10-25 01:11:40 +00:00
lobehubbot 3a1ca8d285 🤖 chore: Lighthouse Results | Welcome 2023-10-24 01:13:37 +00:00
lobehubbot 7fcb949ef0 🤖 chore: Lighthouse Results | Market 2023-10-24 01:12:07 +00:00
lobehubbot da1a4bbf99 🤖 chore: Lighthouse Results | Chat 2023-10-24 01:12:02 +00:00
lobehubbot 95535a43e3 🤖 chore: Lighthouse Results | Settings 2023-10-24 01:11:52 +00:00
lobehubbot 02bdc01d9b 🤖 chore: Lighthouse Results | Welcome 2023-10-23 01:13:50 +00:00
lobehubbot 8187b75cf7 🤖 chore: Lighthouse Results | Settings 2023-10-23 01:12:31 +00:00
lobehubbot 6cb059fd54 🤖 chore: Lighthouse Results | Market 2023-10-23 01:12:26 +00:00
lobehubbot 00cee5d9da 🤖 chore: Lighthouse Results | Chat 2023-10-23 01:12:25 +00:00
lobehubbot bef7bf72bc 🤖 chore: Lighthouse Results | Welcome 2023-10-22 01:18:44 +00:00
lobehubbot 581faeb263 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-22 01:16:55 +00:00
lobehubbot a54985acae 🤖 chore: Lighthouse Results | Market 2023-10-22 01:16:53 +00:00
lobehubbot 8308bd2c19 🤖 chore: Lighthouse Results | Chat 2023-10-22 01:16:53 +00:00
lobehubbot bfd5d18d7b 🤖 chore: Lighthouse Results | Welcome 2023-10-21 01:11:44 +00:00
lobehubbot 07df06999f 🤖 chore: Lighthouse Results | Market 2023-10-21 01:10:18 +00:00
lobehubbot 6f15cd1622 🤖 chore: Lighthouse Results | Settings 2023-10-21 01:10:11 +00:00
lobehubbot bc0e0bbec0 🤖 chore: Lighthouse Results | Chat 2023-10-21 01:10:01 +00:00
lobehubbot 8afc5e037e 🤖 chore: Lighthouse Results | Welcome 2023-10-20 01:13:41 +00:00
lobehubbot d18d9beed9 🤖 chore: Lighthouse Results | Chat 2023-10-20 01:12:21 +00:00
lobehubbot 35e12be680 🤖 chore: Lighthouse Results | Market 2023-10-20 01:12:04 +00:00
lobehubbot ace6cf9627 🤖 chore: Lighthouse Results | Settings 2023-10-20 01:12:01 +00:00
lobehubbot 2daf36c77e 🤖 chore: Lighthouse Results | Welcome 2023-10-19 01:13:54 +00:00
lobehubbot 8759c9b4f3 🤖 chore: Lighthouse Results | Chat 2023-10-19 01:12:12 +00:00
lobehubbot 422435084a 🤖 chore: Lighthouse Results | Market 2023-10-19 01:12:08 +00:00
lobehubbot e669ddc2b2 🤖 chore: Lighthouse Results | Settings 2023-10-19 01:12:05 +00:00
lobehubbot 3cbeafe313 🤖 chore: Lighthouse Results | Welcome 2023-10-18 06:43:51 +00:00
lobehubbot 54d6c8415e 🤖 chore: Lighthouse Results | Chat 2023-10-18 06:42:26 +00:00
lobehubbot ce653150db Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-18 06:42:13 +00:00
lobehubbot fd4280356c 🤖 chore: Lighthouse Results | Settings 2023-10-18 06:42:11 +00:00
lobehubbot ba1697d29c 🤖 chore: Lighthouse Results | Market 2023-10-18 06:42:11 +00:00
lobehubbot 06e0db8c84 🤖 chore: Lighthouse Results | Welcome 2023-10-18 01:14:23 +00:00
lobehubbot 34bacbe8a8 🤖 chore: Lighthouse Results | Chat 2023-10-18 01:12:43 +00:00
lobehubbot a83abd7631 🤖 chore: Lighthouse Results | Market 2023-10-18 01:12:38 +00:00
lobehubbot 63e1520c5b 🤖 chore: Lighthouse Results | Settings 2023-10-18 01:12:25 +00:00
lobehubbot ebfab68344 🤖 chore: Lighthouse Results | Welcome 2023-10-17 08:22:11 +00:00
lobehubbot 1ec7bda209 🤖 chore: Lighthouse Results | Market 2023-10-17 08:20:35 +00:00
lobehubbot 1c66f19529 🤖 chore: Lighthouse Results | Chat 2023-10-17 08:20:34 +00:00
lobehubbot 0ff86daf68 🤖 chore: Lighthouse Results | Settings 2023-10-17 08:20:31 +00:00
lobehubbot 8ca5583d2b 🤖 chore: Lighthouse Results | Welcome 2023-10-17 05:44:16 +00:00
lobehubbot e12b060b75 🤖 chore: Lighthouse Results | Market 2023-10-17 05:42:50 +00:00
lobehubbot 18b4bc6376 🤖 chore: Lighthouse Results | Chat 2023-10-17 05:42:32 +00:00
lobehubbot adc7d0a36d 🤖 chore: Lighthouse Results | Settings 2023-10-17 05:42:29 +00:00
lobehubbot 8db6c3b4a3 🤖 chore: Lighthouse Results | Welcome 2023-10-17 01:14:12 +00:00
lobehubbot ddd7918383 🤖 chore: Lighthouse Results | Market 2023-10-17 01:12:56 +00:00
lobehubbot 52a32043bc 🤖 chore: Lighthouse Results | Chat 2023-10-17 01:12:54 +00:00
lobehubbot 81fcc5f8c9 🤖 chore: Lighthouse Results | Settings 2023-10-17 01:12:40 +00:00
lobehubbot 892e3c122e 🤖 chore: Lighthouse Results | Market 2023-10-16 01:14:05 +00:00
lobehubbot 243acf498e 🤖 chore: Lighthouse Results | Chat 2023-10-16 01:13:44 +00:00
lobehubbot 1de9a410b1 🤖 chore: Lighthouse Results | Settings 2023-10-16 01:13:28 +00:00
lobehubbot ba18a0c9b4 🤖 chore: Lighthouse Results | Welcome 2023-10-15 01:18:37 +00:00
lobehubbot 932baec440 🤖 chore: Lighthouse Results | Market 2023-10-15 01:17:18 +00:00
lobehubbot ac6f5e49be 🤖 chore: Lighthouse Results | Chat 2023-10-15 01:17:09 +00:00
lobehubbot d38ba9d9ab 🤖 chore: Lighthouse Results | Settings 2023-10-15 01:17:00 +00:00
lobehubbot 37e8ec4c0f 🤖 chore: Lighthouse Results | Welcome 2023-10-14 01:11:46 +00:00
lobehubbot 46893cc26c Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-14 01:10:16 +00:00
lobehubbot c2af8ff424 🤖 chore: Lighthouse Results | Settings 2023-10-14 01:10:15 +00:00
lobehubbot 8b22a30692 🤖 chore: Lighthouse Results | Chat 2023-10-14 01:10:15 +00:00
lobehubbot 4f0d11e4af 🤖 chore: Lighthouse Results | Market 2023-10-14 01:10:12 +00:00
lobehubbot 758ec3b4ed 🤖 chore: Lighthouse Results | Welcome 2023-10-13 01:15:32 +00:00
lobehubbot 20c84941ea 🤖 chore: Lighthouse Results | Market 2023-10-13 01:14:14 +00:00
lobehubbot 6065de2a3b 🤖 chore: Lighthouse Results | Settings 2023-10-13 01:14:01 +00:00
lobehubbot d3c7aee663 🤖 chore: Lighthouse Results | Chat 2023-10-13 01:13:58 +00:00
lobehubbot c8d7dafcf3 🤖 chore: Lighthouse Results | Welcome 2023-10-12 06:20:29 +00:00
lobehubbot a4422cb56e 🤖 chore: Lighthouse Results | Market 2023-10-12 06:18:56 +00:00
lobehubbot c135ed22d9 🤖 chore: Lighthouse Results | Settings 2023-10-12 06:18:49 +00:00
lobehubbot a9401058cb 🤖 chore: Lighthouse Results | Chat 2023-10-12 06:18:42 +00:00
lobehubbot 057d2396ad 🤖 chore: Lighthouse Results | Welcome 2023-10-12 06:15:23 +00:00
lobehubbot 77547a2a68 🤖 chore: Lighthouse Results | Settings 2023-10-12 06:13:55 +00:00
lobehubbot a18d65355a 🤖 chore: Lighthouse Results | Chat 2023-10-12 06:13:49 +00:00
lobehubbot fb13eb3cf0 🤖 chore: Lighthouse Results | Market 2023-10-12 06:13:47 +00:00
lobehubbot 45bd10931f 🤖 chore: Lighthouse Results | Welcome 2023-10-12 04:22:28 +00:00
lobehubbot cde92bbe0f 🤖 chore: Lighthouse Results | Market 2023-10-12 04:20:57 +00:00
lobehubbot f5ee92323f Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-12 04:20:54 +00:00
lobehubbot a6f3f3f54c 🤖 chore: Lighthouse Results | Chat 2023-10-12 04:20:53 +00:00
lobehubbot 257eaa4a8e 🤖 chore: Lighthouse Results | Settings 2023-10-12 04:20:53 +00:00
lobehubbot 16a80e892a 🤖 chore: Lighthouse Results | Welcome 2023-10-12 04:19:37 +00:00
lobehubbot 9a58610d21 🤖 chore: Lighthouse Results | Market 2023-10-12 04:18:22 +00:00
lobehubbot 0f6396b50a Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-12 04:17:46 +00:00
lobehubbot d3f1bcffc1 🤖 chore: Lighthouse Results | Settings 2023-10-12 04:17:45 +00:00
lobehubbot 65e49f3041 🤖 chore: Lighthouse Results | Chat 2023-10-12 04:17:44 +00:00
lobehubbot 02062f5db2 🤖 chore: Lighthouse Results | Welcome 2023-10-12 01:11:31 +00:00
lobehubbot 10a1337fd9 🤖 chore: Lighthouse Results | Chat 2023-10-12 01:10:04 +00:00
lobehubbot 30f52fafc3 🤖 chore: Lighthouse Results | Market 2023-10-12 01:10:02 +00:00
lobehubbot 4c987c7b4f 🤖 chore: Lighthouse Results | Settings 2023-10-12 01:09:53 +00:00
lobehubbot b6c78e5960 🤖 chore: Lighthouse Results | Welcome 2023-10-11 17:22:11 +00:00
lobehubbot 990284d0a7 🤖 chore: Lighthouse Results | Market 2023-10-11 17:20:46 +00:00
lobehubbot b9c151bde6 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-11 17:20:35 +00:00
lobehubbot b219efa571 🤖 chore: Lighthouse Results | Chat 2023-10-11 17:20:34 +00:00
lobehubbot b83b69e056 🤖 chore: Lighthouse Results | Settings 2023-10-11 17:20:34 +00:00
lobehubbot b648a96c9c 🤖 chore: Lighthouse Results | Welcome 2023-10-11 16:39:36 +00:00
lobehubbot a4f91009a6 🤖 chore: Lighthouse Results | Market 2023-10-11 16:38:00 +00:00
lobehubbot 8bbe4376c5 🤖 chore: Lighthouse Results | Settings 2023-10-11 16:37:56 +00:00
lobehubbot 0896f8b152 🤖 chore: Lighthouse Results | Chat 2023-10-11 16:37:53 +00:00
lobehubbot 1263ec7faf 🤖 chore: Lighthouse Results | Welcome 2023-10-11 16:35:43 +00:00
lobehubbot 46b399c400 🤖 chore: Lighthouse Results | Market 2023-10-11 16:34:28 +00:00
lobehubbot 216906eff9 🤖 chore: Lighthouse Results | Settings 2023-10-11 16:34:07 +00:00
lobehubbot 742a9fdb35 🤖 chore: Lighthouse Results | Chat 2023-10-11 16:34:02 +00:00
lobehubbot 87e32c6e51 🤖 chore: Lighthouse Results | Welcome 2023-10-11 01:18:43 +00:00
lobehubbot e7ebe2c7b6 🤖 chore: Lighthouse Results | Market 2023-10-11 01:17:10 +00:00
lobehubbot 77966ac172 🤖 chore: Lighthouse Results | Chat 2023-10-11 01:16:59 +00:00
lobehubbot a8e1f49554 🤖 chore: Lighthouse Results | Settings 2023-10-11 01:16:47 +00:00
lobehubbot dc27cfe3e7 🤖 chore: Lighthouse Results | Welcome 2023-10-10 18:03:55 +00:00
lobehubbot af8340ae84 🤖 chore: Lighthouse Results | Market 2023-10-10 18:02:26 +00:00
lobehubbot 5ab9726db2 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-10 18:02:22 +00:00
lobehubbot 606b0e88ea 🤖 chore: Lighthouse Results | Chat 2023-10-10 18:02:21 +00:00
lobehubbot b0196cff3f 🤖 chore: Lighthouse Results | Settings 2023-10-10 18:02:20 +00:00
lobehubbot 19eb31a5f0 🤖 chore: Lighthouse Results | Welcome 2023-10-10 17:53:52 +00:00
lobehubbot 7ca9fc4210 🤖 chore: Lighthouse Results | Market 2023-10-10 17:52:38 +00:00
lobehubbot dc48155b11 🤖 chore: Lighthouse Results | Chat 2023-10-10 17:52:31 +00:00
lobehubbot bbf06e799a 🤖 chore: Lighthouse Results | Settings 2023-10-10 17:52:08 +00:00
lobehubbot ba2ba4d65b 🤖 chore: Lighthouse Results | Welcome 2023-10-10 16:12:42 +00:00
lobehubbot beb8c7713a 🤖 chore: Lighthouse Results | Market 2023-10-10 16:11:32 +00:00
lobehubbot 7189940bc5 🤖 chore: Lighthouse Results | Chat 2023-10-10 16:11:13 +00:00
lobehubbot fac2a1dc39 🤖 chore: Lighthouse Results | Settings 2023-10-10 16:10:58 +00:00
lobehubbot d57a987428 🤖 chore: Lighthouse Results | Welcome 2023-10-10 02:03:39 +00:00
lobehubbot 88ce659ee9 🤖 chore: Lighthouse Results | Market 2023-10-10 02:01:52 +00:00
lobehubbot b8bae9d04d 🤖 chore: Lighthouse Results | Settings 2023-10-10 02:01:43 +00:00
lobehubbot 0bd6aa4d6f 🤖 chore: Lighthouse Results | Chat 2023-10-10 02:01:37 +00:00
lobehubbot 569b8d927c 🤖 chore: Lighthouse Results | Welcome 2023-10-09 17:39:35 +00:00
lobehubbot 036e93f2f1 🤖 chore: Lighthouse Results | Market 2023-10-09 17:38:11 +00:00
lobehubbot ef67885121 Merge branch 'lighthouse' of https://github.com/lobehub/lobe-chat into lighthouse 2023-10-09 17:38:07 +00:00
lobehubbot dadd100b66 🤖 chore: Lighthouse Results | Settings 2023-10-09 17:38:06 +00:00
lobehubbot 46903e868a 🤖 chore: Lighthouse Results | Chat 2023-10-09 17:38:06 +00:00
canisminor1990 b4a088d994 clean 2023-10-10 01:32:18 +08:00
canisminor1990 75f9195ce4 🔧 chore: Add Lighthouse-Badger-Advanced 2023-10-10 01:20:22 +08:00
11679 changed files with 5466 additions and 2829576 deletions
-94
View File
@@ -1,94 +0,0 @@
---
name: add-provider-doc
description: Add documentation for a new AI provider — usage docs, env vars, Docker config, image resources.
disable-model-invocation: true
argument-hint: '[provider-name]'
---
# Adding New AI Provider Documentation
Complete workflow for adding documentation for a new AI provider.
## Overview
1. Create usage documentation (EN + CN)
2. Add environment variable documentation (EN + CN)
3. Update Docker configuration files
4. Update .env.example
5. Prepare image resources
## Step 1: Create Provider Usage Documentation
### Required Files
- `docs/usage/providers/{provider-name}.mdx` (English)
- `docs/usage/providers/{provider-name}.zh-CN.mdx` (Chinese)
### Key Requirements
- 5-6 screenshots showing the process
- Cover image for the provider
- Real registration and dashboard URLs
- Pricing information callout
- **Never include real API keys** - use placeholders
Reference: `docs/usage/providers/fal.mdx`
## Step 2: Update Environment Variables Documentation
### Files to Update
- `docs/self-hosting/environment-variables/model-provider.mdx` (EN)
- `docs/self-hosting/environment-variables/model-provider.zh-CN.mdx` (CN)
### Content Format
```markdown
### `{PROVIDER}_API_KEY`
- Type: Required
- Description: API key from {Provider Name}
- Example: `{api-key-format}`
### `{PROVIDER}_MODEL_LIST`
- Type: Optional
- Description: Control model list. Use `+` to add, `-` to hide
- Example: `-all,+model-1,+model-2=Display Name`
```
## Step 3: Update Docker Files
Update all Dockerfiles at the **end** of ENV section:
- `Dockerfile`
- `Dockerfile.database`
- `Dockerfile.pglite`
```dockerfile
# {New Provider}
{PROVIDER}_API_KEY="" {PROVIDER}_MODEL_LIST=""
```
## Step 4: Update .env.example
```bash
### {Provider Name} ###
# {PROVIDER}_API_KEY={prefix}-xxxxxxxx
```
## Step 5: Image Resources
- Cover image
- 3-4 API dashboard screenshots
- 2-3 LobeHub configuration screenshots
- Host on LobeHub CDN: `hub-apac-1.lobeobjects.space`
## Checklist
- [ ] EN + CN usage docs
- [ ] EN + CN env var docs
- [ ] All 3 Dockerfiles updated
- [ ] .env.example updated
- [ ] All images prepared
- [ ] No real API keys in docs
-108
View File
@@ -1,108 +0,0 @@
---
name: add-setting-env
description: Add server-side environment variables that control default values for user settings.
disable-model-invocation: true
argument-hint: '[setting-name]'
---
# Adding Environment Variable for User Settings
Add server-side environment variables to configure default values for user settings.
**Priority**: User Custom > Server Env Var > Hardcoded Default
## Steps
### 1. Define Environment Variable
Create `src/envs/<domain>.ts`:
```typescript
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const get<Domain>Config = () => {
return createEnv({
server: {
YOUR_ENV_VAR: z.coerce.number().min(MIN).max(MAX).optional(),
},
runtimeEnv: {
YOUR_ENV_VAR: process.env.YOUR_ENV_VAR,
},
});
};
export const <domain>Env = get<Domain>Config();
```
### 2. Update Type (if new domain)
Add to `packages/types/src/serverConfig.ts`:
```typescript
import { User<Domain>Config } from './user/settings';
export interface GlobalServerConfig {
<domain>?: PartialDeep<User<Domain>Config>;
}
```
**Prefer reusing existing types** from `packages/types/src/user/settings`.
### 3. Assemble Server Config (if new domain)
In `src/server/globalConfig/index.ts`:
```typescript
import { <domain>Env } from '@/envs/<domain>';
export const getServerGlobalConfig = async () => {
const config: GlobalServerConfig = {
<domain>: cleanObject({
<settingName>: <domain>Env.YOUR_ENV_VAR,
}),
};
return config;
};
```
### 4. Merge to User Store (if new domain)
In `src/store/user/slices/common/action.ts`:
```typescript
const serverSettings: PartialDeep<UserSettings> = {
<domain>: serverConfig.<domain>,
};
```
### 5. Update .env.example
```bash
# <Description> (range/options, default: X)
# YOUR_ENV_VAR=<example>
```
### 6. Update Documentation
- `docs/self-hosting/environment-variables/basic.mdx` (EN)
- `docs/self-hosting/environment-variables/basic.zh-CN.mdx` (CN)
## Example: AI_IMAGE_DEFAULT_IMAGE_NUM
```typescript
// src/envs/image.ts
AI_IMAGE_DEFAULT_IMAGE_NUM: z.coerce.number().min(1).max(20).optional(),
// packages/types/src/serverConfig.ts
image?: PartialDeep<UserImageConfig>;
// src/server/globalConfig/index.ts
image: cleanObject({ defaultImageNum: imageEnv.AI_IMAGE_DEFAULT_IMAGE_NUM }),
// src/store/user/slices/common/action.ts
image: serverConfig.image,
// .env.example
# AI_IMAGE_DEFAULT_IMAGE_NUM=4
```
-209
View File
@@ -1,209 +0,0 @@
---
name: agent-runtime-hooks
description: 'Agent runtime lifecycle hooks. Use for before/after tool or step hooks, tool mocks, human intervention, sub-agent calls, context compression, evals, tracing, callAgent, or lifecycle events.'
user-invocable: false
---
# Agent Runtime Hooks
Lifecycle hooks for observing and intercepting agent execution. Hooks are registered per-operation via `execAgent({ hooks })` and dispatched by `HookDispatcher`.
## Hook Types
16 hook types across 5 categories:
```
execAgent({ hooks })
├─ beforeStep ──────────── Before each step executes
│ │
│ ├─ [call_llm] LLM inference
│ │
│ ├─ [call_tool]
│ │ ├─ beforeToolCall ── Before tool executes (supports mocking)
│ │ ├─ (tool execution)
│ │ ├─ afterToolCall ─── After tool completes (observation only)
│ │ └─ onToolCallError ─ Tool threw an exception
│ │
│ ├─ [request_human_approve]
│ │ ├─ beforeHumanIntervention ── Before agent pauses
│ │ ├─ afterHumanIntervention ─── After approve/reject + resume
│ │ └─ onStopByHumanIntervention ── User rejected, agent halted
│ │
│ ├─ [compress_context]
│ │ ├─ beforeCompact ──── Before compression starts
│ │ ├─ afterCompact ───── After compression completes
│ │ └─ onCompactError ─── Compression failed
│ │
│ ├─ [callAgent] (via execSubAgentTask)
│ │ ├─ beforeCallAgent ── Before sub-agent starts
│ │ ├─ afterCallAgent ─── After sub-agent completes
│ │ └─ onCallAgentError ── Sub-agent failed
│ │
│ └─ afterStep ──────────── After step completes
├─ (next step...)
├─ onComplete ───────────── Operation reaches terminal state
└─ onError ──────────────── Error during execution
```
## Key Files
| File | Role |
| ---------------------------------------------------------- | ------------------------------------------------------ |
| `packages/agent-runtime/src/types/hooks.ts` | Type definitions (AgentHookType, all event interfaces) |
| `src/server/services/agentRuntime/hooks/types.ts` | Server-side types (AgentHook, re-exports) |
| `src/server/services/agentRuntime/hooks/HookDispatcher.ts` | Registration, dispatch, dispatchBeforeToolCall |
| `src/server/modules/AgentRuntime/RuntimeExecutors.ts` | Tool/Compact/HumanIntervention hook dispatch |
| `src/server/services/agentRuntime/AgentRuntimeService.ts` | Step hooks + HumanIntervention resume/reject |
| `src/server/services/aiAgent/index.ts` | CallAgent hook dispatch |
## Registration Flow
```ts
const hooks: AgentHook[] = [
{ id: 'my-hook', type: 'afterStep', handler: async (event) => { ... } },
];
await aiAgentService.execAgent({ agentId, prompt, hooks });
// Internally: hookDispatcher.register(operationId, hooks)
// Cleanup: hookDispatcher.unregister(operationId)
```
## Hook Reference
### Step Level
**`beforeStep`** — Before each step. `event: AgentHookEvent`
**`afterStep`** — After each step. `event: AgentHookEvent` (content, toolsCalling, totalCost, etc.)
**`onComplete`** — Terminal state. `event: AgentHookEvent` (reason: done/error/interrupted/max_steps/cost_limit)
**`onError`** — Error occurred. `event: AgentHookEvent` (errorMessage, errorDetail)
### Tool Call Level
**`beforeToolCall`** — Before tool executes. **Supports mocking** via `event.mock()`.
```ts
// event: ToolCallHookEvent
{
(identifier, apiName, args, callIndex, stepIndex, operationId, mock);
}
// Mock example:
event.mock({ content: '{"error":"rate limited"}' });
```
Dispatch method: `hookDispatcher.dispatchBeforeToolCall()` (returns mock result or null).
**`afterToolCall`** — After tool completes. Observation only.
```ts
// event: AfterToolCallHookEvent
{
(identifier, apiName, args, callIndex, content, success, mocked, executionTimeMs, stepIndex);
}
```
**`onToolCallError`** — Tool threw an exception (catch block, not just `success=false`).
```ts
// event: ToolCallErrorHookEvent
{
(identifier, apiName, args, callIndex, error, stepIndex);
}
```
### Human Intervention
**`beforeHumanIntervention`** — Before agent pauses for approval.
```ts
// event: BeforeHumanInterventionHookEvent
{ operationId, stepIndex, pendingTools: [{ identifier, apiName }] }
```
**`afterHumanIntervention`** — After approve/reject, agent resumes.
```ts
// event: AfterHumanInterventionHookEvent
{ operationId, action: 'approve' | 'reject' | 'rejectAndContinue', toolCallId?, rejectionReason? }
```
**`onStopByHumanIntervention`** — User rejected, agent halted.
```ts
// event: StopByHumanInterventionHookEvent
{ operationId, toolCallId?, rejectionReason? }
```
### Context Compression
**`beforeCompact`** — Before compression starts.
```ts
// event: BeforeCompactHookEvent
{
(operationId, stepIndex, messageCount, tokenCount);
}
```
**`afterCompact`** — After compression completes.
```ts
// event: AfterCompactHookEvent
{
(operationId, stepIndex, groupId, messagesBefore, messagesAfter, summary);
}
```
**`onCompactError`** — Compression failed.
```ts
// event: CompactErrorHookEvent
{
(operationId, stepIndex, tokenCount, error);
}
```
### Sub-Agent (CallAgent)
**`beforeCallAgent`** — Before calling sub-agent. Dispatched on **parent** operation.
```ts
// event: BeforeCallAgentHookEvent
{
(operationId, agentId, instruction);
}
```
**`afterCallAgent`** — Sub-agent completed. Dispatched on **parent** operation.
```ts
// event: AfterCallAgentHookEvent
{
(operationId, agentId, subOperationId, threadId, success);
}
```
**`onCallAgentError`** — Sub-agent failed. Dispatched on **parent** operation.
```ts
// event: CallAgentErrorHookEvent
{
(operationId, agentId, error);
}
```
Note: CallAgent hooks require `parentOperationId` in `ExecSubAgentTaskParams`.
## Design Notes
- **Fire-and-forget**: All handlers return `Promise<void>`. Errors are non-fatal.
- **Exception**: `beforeToolCall` supports mock via `event.mock()` — uses `dispatchBeforeToolCall()` which returns the mock result.
- **Sequential**: Same-type hooks run in registration order.
- **Local only**: `beforeToolCall` mock only works in local mode (in-memory hooks). Webhook mode does not support mocking.
- **Scoped per operation**: Auto-cleaned via `hookDispatcher.unregister()` on completion.
- **Sandbox/MCP**: No separate hooks — they go through `executeTool`, so `beforeToolCall`/`afterToolCall` cover them. Use `event.identifier` to filter.
## Real-World Example: agent-evals
See `devtools/agent-evals/helpers/runner.ts``createEvalHooks()` uses `afterStep`, `onComplete`, `afterToolCall`, and `beforeToolCall` (for mock).
-95
View File
@@ -1,95 +0,0 @@
---
name: agent-signal
description: 'Build or extend LobeHub Agent Signal pipelines. Use for signal sources, signal/action types, policies, middleware, workflow handoff, dedupe, scope behavior, or observability.'
---
# Agent Signal
Use this skill to implement event-driven background work for agents without coupling the work to the foreground chat request.
Agent Signal has one consistent shape:
`source event` -> `signal interpretation` -> `action execution` -> built-in result signals
## Start Here
1. Read `references/architecture.md` to map the package boundary, runtime queue, scope model, and async workflow handoff.
2. Read `references/handlers.md` before writing any new policy, source handler, signal handler, or action handler.
3. Read `references/observability.md` when you need tracing, metrics, debugging, or workflow snapshot visibility.
## Use The Right Entry Point
- Use `emitAgentSignalSourceEvent(...)` when a server-owned producer should execute the pipeline immediately.
- Use `executeAgentSignalSourceEvent(...)` when a worker or controlled backend path already owns execution timing and may inject a runtime guard backend.
- Use `enqueueAgentSignalSourceEvent(...)` when the caller should return quickly and let Upstash Workflow process the event out-of-band.
- Use `emitAgentSignalSourceEventWithStore(...)` for isolated tests or evals that should avoid ambient Redis state.
Read:
- `src/server/services/agentSignal/index.ts`
- `src/server/workflows/agentSignal/index.ts`
- `src/server/workflows/agentSignal/run.ts`
## Core Model
- `source`: A normalized fact that happened. Sources come from producers such as runtime lifecycle events, user messages, or bot ingress.
- `signal`: A semantic interpretation derived from one source or from another signal. Signals express meaning, routing, or policy state.
- `action`: A concrete side effect planned from one signal. Actions do the work.
- `policy`: An installable middleware bundle that registers source, signal, and action handlers.
- `procedure`: Not a distinct runtime node. Treat "procedure" as the end-to-end flow for one use case: ingress source, matching handlers, planned actions, execution result, and observability.
Keep the boundaries strict:
- Add a new `source` when the outside world produced a new event.
- Add a new `signal` when the system needs a reusable semantic interpretation.
- Add a new `action` when the runtime needs a concrete side effect.
- Add or update a `policy` when you are wiring those pieces together.
## Implementation Workflow
1. Decide whether the use case is synchronous or quiet background work.
2. Define or reuse a source type in `src/server/services/agentSignal/sourceTypes.ts`.
3. Define or reuse signal and action types in `src/server/services/agentSignal/policies/types.ts`.
4. Implement handlers with `defineSourceHandler`, `defineSignalHandler`, or `defineActionHandler`.
5. Bundle handlers with `defineAgentSignalHandlers(...)`.
6. Register the policy in `src/server/services/agentSignal/policies/index.ts` and pass it into the runtime factory if needed.
7. Add or update ingress code that emits or enqueues the source event.
8. Add observability and tests before considering the flow complete.
## Default Reading Set
- Shared semantic core:
`packages/agent-signal/src/index.ts`
`packages/agent-signal/src/base/builders.ts`
`packages/agent-signal/src/base/types.ts`
- Server-owned runtime and middleware:
`src/server/services/agentSignal/runtime/AgentSignalRuntime.ts`
`src/server/services/agentSignal/runtime/AgentSignalScheduler.ts`
`src/server/services/agentSignal/runtime/middleware.ts`
`src/server/services/agentSignal/runtime/context.ts`
- Existing policy example:
`src/server/services/agentSignal/policies/analyzeIntent/index.ts`
`src/server/services/agentSignal/policies/analyzeIntent/feedbackSatisfaction.ts`
`src/server/services/agentSignal/policies/analyzeIntent/feedbackDomain.ts`
`src/server/services/agentSignal/policies/analyzeIntent/feedbackAction.ts`
`src/server/services/agentSignal/policies/analyzeIntent/actions/userMemory.ts`
- Observability:
`src/server/services/agentSignal/observability/projector.ts`
`src/server/services/agentSignal/observability/traceEvents.ts`
`packages/observability-otel/src/modules/agent-signal/index.ts`
## Implementation Rules
- Reuse existing source, signal, and action types before adding new ones.
- Keep source handlers focused on interpretation and fan-out, not heavy side effects.
- Keep action handlers responsible for side effects, idempotency, and executor-style result reporting.
- Use stable ids and idempotency keys when the same source can arrive more than once.
- Preserve scope discipline. The runtime uses `scopeKey` to serialize related background work.
- Prefer the dedicated shared package types and builders from `@lobechat/agent-signal` for normalized nodes and result contracts.
- Add focused tests near the touched runtime, policy, or store module. Existing tests under `src/server/services/agentSignal/**/__tests__` are the reference pattern.
## References
- Architecture and boundaries: `references/architecture.md`
- Writing handlers and policies: `references/handlers.md`
- Observability, metrics, and debugging: `references/observability.md`
@@ -1,4 +0,0 @@
interface:
display_name: 'Agent Signal'
short_description: 'Build AgentSignal sources, signals, actions, and policies.'
default_prompt: 'Use $agent-signal to add a new Agent Signal source, policy, handler, or observability flow.'
@@ -1,199 +0,0 @@
# Agent Signal Architecture
## Pipeline
Use this mental model first:
```text
producer
-> emitAgentSignalSourceEvent(...) or enqueueAgentSignalSourceEvent(...)
-> emitSourceEvent(...)
-> dedupe + scope lock + source normalization
-> runtime.emitNormalized(source)
-> source handlers
-> signal handlers
-> action handlers
-> built-in result signals
-> observability projection + persistence
```
The scheduler is queue-driven, not hard-coded for one policy:
```text
source node
-> matching source handlers
-> dispatch signals/actions
-> matching signal handlers
-> dispatch more signals/actions
-> matching action handlers
-> ExecutorResult
-> signal.action.applied | signal.action.skipped | signal.action.failed
```
Read:
- `src/server/services/agentSignal/index.ts`
- `src/server/services/agentSignal/sources/index.ts`
- `src/server/services/agentSignal/runtime/AgentSignalScheduler.ts`
## Package Boundaries
### `packages/agent-signal`
Treat this as the shared semantic core.
It provides:
- base node types: source, signal, action
- builders: `createSource`, `createSignal`, `createAction`
- built-in result signal types
- runtime result contracts such as `RuntimeProcessorResult` and `ExecutorResult`
Read:
- `packages/agent-signal/src/base/types.ts`
- `packages/agent-signal/src/base/builders.ts`
- `packages/agent-signal/src/types/events.ts`
- `packages/agent-signal/src/types/builtin.ts`
### `src/server/services/agentSignal`
Treat this as the server-owned implementation layer.
It owns:
- source catalogs and payload maps
- policy-specific signal and action catalogs
- middleware registration
- runtime scheduling and guard backends
- Redis-backed dedupe, waypoint, and policy state
- service entrypoints for synchronous and async execution
### `packages/observability-otel/src/modules/agent-signal`
Treat this as shared OTEL ownership for Agent Signal metrics and tracer instances.
## Core Vocabulary
### Source
A source is the normalized external fact that started the chain.
Examples:
- `agent.user.message`
- `runtime.before_step`
- `runtime.after_step`
- `client.runtime.start`
- `bot.message.merged`
Define source payloads in:
- `src/server/services/agentSignal/sourceTypes.ts`
Build normalized sources in:
- `src/server/services/agentSignal/sources/buildSource.ts`
- `packages/agent-signal/src/base/builders.ts`
### Signal
A signal is a semantic interpretation. Signals should be reusable and meaning-oriented.
Examples from `analyzeIntent`:
- `signal.feedback.satisfaction`
- `signal.feedback.domain.memory`
- `signal.feedback.domain.prompt`
- `signal.feedback.domain.skill`
Define server-owned signal types in:
- `src/server/services/agentSignal/policies/types.ts`
### Action
An action is a concrete side effect the runtime should execute.
Example:
- `action.user-memory.handle`
Action handlers usually:
- check idempotency
- call tools, models, or services
- return `ExecutorResult`
### Policy
A policy is an installable bundle of handlers. It is the composition unit that turns the generic runtime into a feature.
Example:
- `createAnalyzeIntentPolicy(...)`
### Procedure
"Procedure" is not a first-class type in this runtime. Use the word to describe one end-to-end use case:
1. define ingress source
2. emit or enqueue the source
3. interpret source into signals
4. plan actions from signals
5. execute actions
6. persist trace and metrics
When a user asks for "the procedure", document the flow above and point to the exact producer, handlers, and execution entrypoint.
## Scope, Deduping, And Quiet Background Work
`scopeKey` is the serialization boundary for related work. It is used for:
- source dedupe windows
- scope locks during source generation
- runtime guard state
- waypoint persistence for queued processing
Read:
- `src/server/services/agentSignal/sources/index.ts`
- `src/server/services/agentSignal/runtime/context.ts`
- `src/server/services/agentSignal/constants.ts`
Use `enqueueAgentSignalSourceEvent(...)` when the work should stay quiet and out-of-band. That path:
1. normalizes the source envelope
2. derives or reuses `scopeKey`
3. triggers `AgentSignalWorkflow`
4. executes later in `runAgentSignalWorkflow`
This is the preferred path when the UI request should finish immediately and the policy can run in the background.
Read:
- `src/server/workflows/agentSignal/index.ts`
- `src/server/workflows/agentSignal/run.ts`
## Existing Example: `analyzeIntent`
Use `analyzeIntent` as the reference chain:
```text
agent.user.message
-> feedback satisfaction source handler
-> signal.feedback.satisfaction
-> feedback domain signal handler
-> signal.feedback.domain.*
-> feedback action planner
-> action.user-memory.handle
-> signal.action.applied | skipped | failed
```
Read:
- `src/server/services/agentSignal/policies/analyzeIntent/index.ts`
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackSatisfaction.ts`
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackDomain.ts`
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackAction.ts`
- `src/server/services/agentSignal/policies/analyzeIntent/actions/userMemory.ts`
@@ -1,228 +0,0 @@
# Writing Handlers And Policies
## Fluent Registration API
Use the middleware helpers in `src/server/services/agentSignal/runtime/middleware.ts`.
They provide:
- `defineSourceHandler(...)`
- `defineSignalHandler(...)`
- `defineActionHandler(...)`
- `defineAgentSignalHandlers(...)`
These helpers do two jobs:
1. keep handler registration terse
2. preserve strong typing when `listen` points at concrete source, signal, or action types
## Handler Shape
Each handler receives:
- the current runtime node
- `RuntimeProcessorContext`
The context gives you:
- `scopeKey`
- `now()`
- `runtimeState.getGuardState(lane)`
- `runtimeState.touchGuardState(lane, now?)`
Read:
- `src/server/services/agentSignal/runtime/context.ts`
## Return Contracts
Return one of these shapes:
- `void`: no fan-out, stop at this handler
- `{ status: 'dispatch', signals?, actions? }`: continue the chain
- `{ status: 'wait', pending? }`: pause for later host coordination
- `{ status: 'schedule', nextHop }`: schedule another hop
- `{ status: 'conclude', concluded? }`: stop with a terminal runtime result
- `ExecutorResult`: only for action handlers that performed a concrete side effect
Read:
- `packages/agent-signal/src/base/types.ts`
- `src/server/services/agentSignal/runtime/AgentSignalScheduler.ts`
## Policy Composition Pattern
Use `defineAgentSignalHandlers([...])` to bundle related handlers into one policy.
Example from `analyzeIntent`:
```ts
return defineAgentSignalHandlers([
createFeedbackSatisfactionJudgeProcessor(...),
createFeedbackDomainJudgeSignalHandler(...),
createFeedbackActionPlannerSignalHandler(),
defineUserMemoryActionHandler(...),
]);
```
That bundle is later passed into the runtime via:
- `createDefaultAgentSignalPolicies(...)`
- `createAgentSignalRuntime({ policies })`
Read:
- `src/server/services/agentSignal/policies/index.ts`
- `src/server/services/agentSignal/policies/analyzeIntent/index.ts`
## Source Handler Pattern
Use a source handler when you are interpreting a producer event into semantic signals.
Reference:
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackSatisfaction.ts`
Pattern:
```ts
return defineSourceHandler(
AGENT_SIGNAL_SOURCE_TYPES.agentUserMessage,
'agent.user.message:my-handler',
async (source, ctx): Promise<RuntimeProcessorResult | void> => {
// interpret source payload
// optionally use ctx.runtimeState
return {
signals: [
/* one or more semantic signals */
],
status: 'dispatch',
};
},
);
```
Write source handlers when:
- a raw message, lifecycle event, or bot ingress needs interpretation
- the work is still semantic, not side-effectful
## Signal Handler Pattern
Use a signal handler when one semantic state should branch into more semantic states or planned actions.
References:
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackDomain.ts`
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackAction.ts`
Pattern:
```ts
return defineSignalHandler(
MY_SIGNAL_TYPE,
'signal.my-policy-router',
async (signal): Promise<RuntimeProcessorResult | void> => {
return {
actions: [
/* planned work */
],
status: 'dispatch',
};
},
);
```
Use signal handlers for:
- routing
- fan-out
- filtering
- conflict resolution
- converting interpretation into planned actions
## Action Handler Pattern
Use an action handler when the runtime should do actual work.
Reference:
- `src/server/services/agentSignal/policies/analyzeIntent/actions/userMemory.ts`
Pattern:
```ts
return defineActionHandler(
MY_ACTION_TYPE,
'action.my-policy-executor',
async (action, ctx): Promise<ExecutorResult> => {
// run service/tool/model side effect
// check idempotency if needed
return {
actionId: action.actionId,
attempt: {
completedAt: ctx.now(),
current: 1,
startedAt,
status: 'succeeded',
},
status: 'applied',
};
},
);
```
Keep these rules:
- perform idempotency checks here or immediately before side effects
- return stable `actionId`
- include failure detail in `error`
- let the scheduler turn the `ExecutorResult` into built-in result signals
## Source, Signal, And Action Type Placement
Use this split:
- external event payloads:
`src/server/services/agentSignal/sourceTypes.ts`
- policy-owned signal and action payloads:
`src/server/services/agentSignal/policies/types.ts`
- normalized shared node contracts:
`packages/agent-signal/src/base/types.ts`
Do not put app-specific signal catalogs into `packages/agent-signal`. That package should stay generic and reusable.
## Choosing The Right Node
Choose `source` when:
- the outside world emitted a new fact
Choose `signal` when:
- the system needs semantic meaning that downstream handlers can reuse
Choose `action` when:
- the runtime is ready for a concrete side effect
If a handler both interprets meaning and performs side effects, split it. That keeps chains inspectable and testable.
## Testing Strategy
Prefer focused tests near the touched code.
Useful references:
- `src/server/services/agentSignal/runtime/__tests__/AgentSignalRuntime.test.ts`
- `src/server/services/agentSignal/__tests__/index.integration.test.ts`
- `src/server/services/agentSignal/policies/analyzeIntent/__tests__/*`
- `src/server/services/agentSignal/policies/analyzeIntent/actions/__tests__/*`
Test at the smallest level that proves the behavior:
- handler unit test for one routing rule
- runtime test for queue fan-out
- integration test for service ingress and observability persistence
@@ -1,118 +0,0 @@
# Observability And Debugging
## OTEL Ownership
Use `packages/observability-otel/src/modules/agent-signal/index.ts` for the shared tracer and metrics.
Available instruments:
- `tracer`
- `sourceCounter`
- `signalCounter`
- `actionCounter`
- `actionResultCounter`
- `chainCounter`
- `signalActionTransitionCounter`
- `chainDurationHistogram`
- `actionDurationHistogram`
Use this module when you need shared telemetry ownership instead of creating feature-local meters or tracers.
## Projection Pipeline
After runtime execution, the service projects one compact observability model from the full chain.
Read:
- `src/server/services/agentSignal/observability/projector.ts`
- `src/server/services/agentSignal/observability/traceEvents.ts`
- `src/server/services/agentSignal/observability/store.ts`
Projection outputs:
- a trace envelope with source, signals, actions, results, edges, and handler runs
- a compact telemetry record with dominant path, status breakdown, and chain metadata
This projection is built from:
- source node
- emitted signals
- planned actions
- executor results
## How To Inspect A Chain
Use this order:
1. Inspect the source type and payload.
2. Inspect emitted signals.
3. Inspect planned actions.
4. Inspect executor results.
5. Inspect projected edges and dominant path.
The helper `toAgentSignalTraceEvents(...)` flattens a chain into compact event records suitable for tracing snapshots.
## Workflow Snapshot Bridge
Workflow-triggered runs do not naturally pass through the normal foreground runtime snapshot path, so `runAgentSignalWorkflow` adds a development-only bridge into `.agent-tracing/`.
Read:
- `src/server/workflows/agentSignal/run.ts`
Use that path when:
- the source was enqueued with `enqueueAgentSignalSourceEvent(...)`
- you need local trace visibility for quiet background work
## Common Debug Questions
### The source emits but nothing happens
Check:
- feature gate enabled for the user
- source type matches a registered source handler
- dedupe or scope lock did not short-circuit generation
Read:
- `src/server/services/agentSignal/index.ts`
- `src/server/services/agentSignal/sources/index.ts`
### The signal exists but no action runs
Check:
- the signal type has a registered signal handler
- the signal handler returns `status: 'dispatch'`
- the handler actually returned actions
### The action runs twice
Check:
- source dedupe key stability
- action idempotency strategy
- scope key stability across retries and workflow handoff
Reference:
- `src/server/services/agentSignal/policies/actionIdempotency.ts`
- `src/server/services/agentSignal/policies/analyzeIntent/actions/userMemory.ts`
### Background runs are hard to discover
Check:
- workflow snapshot bridge in development
- projected telemetry record contents
- OTEL counters and histograms in the shared module
## Minimal Completion Checklist
- source ingress is testable
- handler registration is discoverable from the policy factory
- action executor returns structured results
- projection includes the new path cleanly
- tests cover at least one happy path and one no-op or failure path
-221
View File
@@ -1,221 +0,0 @@
---
name: agent-tracing
description: 'Agent tracing CLI for execution snapshots. Use for agent-tracing, traces, snapshots, LLM call inspection, context engine data, agent step analysis, or execution debugging.'
user-invocable: false
---
# Agent Tracing CLI Guide
`@lobechat/agent-tracing` is a zero-config local dev tool that records agent execution snapshots to disk and provides a CLI to inspect them.
## How It Works
In `NODE_ENV=development`, `AgentRuntimeService.executeStep()` automatically records each step to `.agent-tracing/` as partial snapshots. When the operation completes, the partial is finalized into a complete `ExecutionSnapshot` JSON file.
**Data flow**: executeStep loop -> build `StepPresentationData` -> write partial snapshot to disk -> on completion, finalize to `.agent-tracing/{timestamp}_{traceId}.json`
**Context engine capture**: In `RuntimeExecutors.ts`, the `call_llm` executor calls `ctx.tracingContextEngine(input, output)` after `serverMessagesEngine()` processes messages. `AgentRuntimeService.executeStep` buffers the call per step and forwards it to `OperationTraceRecorder.appendStep` as the typed `contextEngine` field. CE flows through this side channel rather than the `events` array so its heavy payload (agentDocuments, systemRole, …) never enters the Redis state pipeline (LOBE-9110).
## Package Location
```
packages/agent-tracing/
src/
types.ts # ExecutionSnapshot, StepSnapshot, SnapshotSummary
store/
types.ts # ISnapshotStore interface
file-store.ts # FileSnapshotStore (.agent-tracing/*.json)
recorder/
index.ts # appendStepToPartial(), finalizeSnapshot()
viewer/
index.ts # Terminal rendering: renderSnapshot, renderStepDetail, renderMessageDetail, renderSummaryTable, renderPayload, renderPayloadTools, renderMemory
cli/
index.ts # CLI entry point (#!/usr/bin/env bun)
inspect.ts # Inspect command (default)
partial.ts # Partial snapshot commands (list, inspect, clean)
index.ts # Barrel exports
```
## Data Storage
- Completed snapshots: `.agent-tracing/{ISO-timestamp}_{traceId-short}.json`
- Latest symlink: `.agent-tracing/latest.json`
- In-progress partials: `.agent-tracing/_partial/{operationId}.json`
- `FileSnapshotStore` resolves from `process.cwd()`**run CLI from the repo root**
## CLI Commands
All commands run from the **repo root**:
```bash
# View latest trace (tree overview, `inspect` is the default command)
agent-tracing
agent-tracing inspect
agent-tracing inspect <traceId>
agent-tracing inspect latest
# List recent snapshots
agent-tracing list
agent-tracing list -l 20
# Inspect specific step (-s is short for --step)
agent-tracing inspect <traceId> -s 0
# View messages (-m is short for --messages)
agent-tracing inspect <traceId> -s 0 -m
# View full content of a specific message (by index shown in -m output)
agent-tracing inspect <traceId> -s 0 --msg 2
agent-tracing inspect <traceId> -s 0 --msg-input 1
# View tool call/result details (-t is short for --tools)
agent-tracing inspect <traceId> -s 1 -t
# View raw events (-e is short for --events)
agent-tracing inspect <traceId> -s 0 -e
# View runtime context (-c is short for --context)
agent-tracing inspect <traceId> -s 0 -c
# View context engine input overview (-p is short for --payload)
agent-tracing inspect <traceId> -p
agent-tracing inspect <traceId> -s 0 -p
# View available tools in payload (-T is short for --payload-tools)
agent-tracing inspect <traceId> -T
agent-tracing inspect <traceId> -s 0 -T
# View user memory (-M is short for --memory)
agent-tracing inspect <traceId> -M
agent-tracing inspect <traceId> -s 0 -M
# Raw JSON output (-j is short for --json)
agent-tracing inspect <traceId> -j
agent-tracing inspect <traceId> -s 0 -j
# List in-progress partial snapshots
agent-tracing partial list
# Inspect a partial (use `inspect` directly — all flags work with partial IDs)
agent-tracing inspect <partialOperationId>
agent-tracing inspect <partialOperationId> -T
agent-tracing inspect <partialOperationId> -p
# Clean up stale partial snapshots
agent-tracing partial clean
```
## Inspect Flag Reference
| Flag | Short | Description | Default Step |
| ----------------- | ----- | ------------------------------------------------------------------------------------------------- | ------------ |
| `--step <n>` | `-s` | Target a specific step | — |
| `--messages` | `-m` | Messages context (CE input → params → LLM payload) | — |
| `--tools` | `-t` | Tool calls & results (what agent invoked) | — |
| `--events` | `-e` | Raw events (llm_start, llm_result, etc.) | — |
| `--context` | `-c` | Runtime context & payload (raw) | — |
| `--system-role` | `-r` | Full system role content | 0 |
| `--env` | | Environment context | 0 |
| `--payload` | `-p` | Context engine input overview (model, knowledge, tools summary, memory summary, platform context) | 0 |
| `--payload-tools` | `-T` | Available tools detail (plugin manifests + LLM function definitions) | 0 |
| `--memory` | `-M` | Full user memory (persona, identity, contexts, preferences, experiences) | 0 |
| `--diff <n>` | `-d` | Diff against step N (use with `-r` or `--env`) | — |
| `--msg <n>` | | Full content of message N from Final LLM Payload | — |
| `--msg-input <n>` | | Full content of message N from Context Engine Input | — |
| `--json` | `-j` | Output as JSON (combinable with any flag above) | — |
Flags marked "Default Step: 0" auto-select step 0 if `--step` is not provided. All flags support `latest` or omitted traceId.
## Typical Debug Workflow
```bash
# 1. Trigger an agent operation in the dev UI
# 2. See the overview
agent-tracing inspect
# 3. List all traces, get traceId
agent-tracing list
# 4. Quick overview of what was fed into context engine
agent-tracing inspect -p
# 5. Inspect a specific step's messages to see what was sent to the LLM
agent-tracing inspect TRACE_ID -s 0 -m
# 6. Drill into a truncated message for full content
agent-tracing inspect TRACE_ID -s 0 --msg 2
# 7. Check available tools vs actual tool calls
agent-tracing inspect -T # available tools
agent-tracing inspect -s 1 -t # actual tool calls & results
# 8. Inspect user memory injected into the conversation
agent-tracing inspect -M
# 9. Diff system role between steps (multi-step agents)
agent-tracing inspect TRACE_ID -r -d 2
```
## Key Types
```typescript
interface ExecutionSnapshot {
traceId: string;
operationId: string;
model?: string;
provider?: string;
startedAt: number;
completedAt?: number;
completionReason?:
| 'done'
| 'error'
| 'interrupted'
| 'max_steps'
| 'cost_limit'
| 'waiting_for_human';
totalSteps: number;
totalTokens: number;
totalCost: number;
error?: { type: string; message: string };
steps: StepSnapshot[];
}
interface StepSnapshot {
stepIndex: number;
stepType: 'call_llm' | 'call_tool';
executionTimeMs: number;
content?: string; // LLM output
reasoning?: string; // Reasoning/thinking
inputTokens?: number;
outputTokens?: number;
toolsCalling?: Array<{ apiName: string; identifier: string; arguments?: string }>;
toolsResult?: Array<{
apiName: string;
identifier: string;
isSuccess?: boolean;
output?: string;
}>;
messages?: any[]; // DB messages before step
context?: { phase: string; payload?: unknown; stepContext?: unknown };
events?: Array<{ type: string; [key: string]: unknown }>;
contextEngine?: {
input?: unknown; // contextEngineInput minus messages + toolsConfig (reconstructible from baseline)
output?: unknown; // processed messages array (final LLM payload)
};
}
```
## --messages Output Structure
When using `--messages`, the output shows three sections (if context engine data is available):
1. **Context Engine Input** — DB messages passed to the engine, with `[0]`, `[1]`, ... indices. Use `--msg-input N` to view full content.
2. **Context Engine Params** — systemRole, model, provider, knowledge, tools, userMemory, etc.
3. **Final LLM Payload** — Processed messages after context engine (system date injection, user memory, history truncation, etc.), with `[0]`, `[1]`, ... indices. Use `--msg N` to view full content.
## Integration Points
- **Recording**: `src/server/services/agentRuntime/AgentRuntimeService.ts` — in the `executeStep()` method, after building `stepPresentationData`, writes partial snapshot in dev mode
- **Context engine capture**: `src/server/modules/AgentRuntime/RuntimeExecutors.ts` — in `call_llm` executor, after `serverMessagesEngine()` returns, calls `ctx.tracingContextEngine(input, output)`. `AgentRuntimeService.executeStep` buffers it per step and passes it to `traceRecorder.appendStep` as the typed `contextEngine` field (kept off the `events` array to stay out of Redis state).
- **Store**: `FileSnapshotStore` reads/writes to `.agent-tracing/` relative to `process.cwd()`
-130
View File
@@ -1,130 +0,0 @@
---
name: builtin-tool
description: 'Build LobeHub builtin tool packages. Use when adding agent-callable tools, manifests, executors, runtimes, inspectors, renders, placeholders, streaming, interventions, portals, or tool registries.'
---
# Builtin Tool Authoring Guide
A builtin tool is a package the agent runtime can call. It ships **five faces**:
| Face | Lives in | Audience |
| -------------------- | -------------------------------------------------------------------------------------- | ------------------------------------- |
| **Manifest + types** | `src/{manifest,types,systemRole}.ts` | The LLM (tool spec + system prompt) |
| **ExecutionRuntime** | `src/ExecutionRuntime/` | Server / desktop / any runtime caller |
| **Executor** | `src/client/executor/` | Frontend (wraps stores/services) |
| **Client UI** | `src/client/{Inspector,Render,…}/` | Chat UI |
| **Registry wiring** | `packages/builtin-tools/src/*.ts` + `src/store/tool/slices/builtin/executors/index.ts` | Framework |
---
## Read These First
| Question | Doc |
| ------------------------------------------------------------------------------------ | --------------------------------------------- |
| Where do files live? What does each face do? Wiring? | [architecture.md](references/architecture.md) |
| How do I name the tool, design APIs, write the manifest, executor, ExecutionRuntime? | [tool-design.md](references/tool-design.md) |
| How do I build Inspector / Render / Placeholder / Streaming / Intervention / Portal? | [ui/](references/ui/README.md) |
---
## When to Use This Skill
- Creating a new `packages/builtin-tool-<name>/` package
- Adding a new API method to an existing builtin tool
- Building or restyling any of the 6 client surfaces for a tool
- Wiring a tool into the central registries
- Debugging "tool not found / API not found / render not showing / placeholder stuck" errors
---
## Top-Level Design Principles
1. **`lobe-<domain>` identifier is permanent.** It's stored in message history. Renames need `@deprecated` aliases (see `packages/builtin-tools/src/inspectors.ts:88-89`). Get it right the first time.
2. **ApiName is an `as const` object**, not a TS enum. It doubles as the runtime list `BaseExecutor` iterates over.
3. **Three result fields, three audiences:**
- `content: string` → the LLM reads it
- `state: Record<…>` → the UI's `pluginState`; **result-domain only**, never echo all params back
- `error: { type, message, body? }` → both LLM and UI; `type` is a stable code
4. **Split execution from frontend wiring.**
- `src/ExecutionRuntime/` — pure runtime, no React, no Zustand, accepts services via constructor. **The default place for new logic.**
- `src/client/executor/``BaseExecutor` subclass that calls `ExecutionRuntime` (or stores/services directly when frontend-only).
5. **UI defaults to "do nothing".** Inspector is required (the header strip). Render/Placeholder/Streaming/Intervention/Portal are added **only when there's something specific to show** — empty registries are fine.
6. **Style with `createStaticStyles + cssVar.*`** (zero-runtime). Fall back to `createStyles + token` only when you genuinely need runtime values. Use `@lobehub/ui` components, not raw antd.
7. **i18n keys live in `src/locales/default/plugin.ts`.** Inspector titles must come from `t('builtins.<identifier>.apiName.<api>')` so something renders while args stream.
---
## Package Layout (preferred, post-2026 convention)
```
packages/builtin-tool-<name>/
├── package.json
└── src/
├── index.ts # exports manifest + types + systemRole + Identifier (no React, no stores)
├── manifest.ts # BuiltinToolManifest with JSON Schema for every API
├── types.ts # ApiName const + Params/State interfaces per API
├── systemRole.ts # System prompt teaching the model when/how to use the APIs
├── ExecutionRuntime/ # ✅ Default home for runtime logic (server- or anywhere-callable)
│ └── index.ts
└── client/
├── index.ts # Re-exports for the registries
├── executor/ # ✅ Frontend executor — extends BaseExecutor, often delegates to ExecutionRuntime
│ └── index.ts
├── Inspector/ # required — header chip per API
├── Render/ # optional — rich result card
├── Placeholder/ # optional — skeleton during streaming/execution
├── Streaming/ # optional — live output renderer (e.g. RunCommand, WriteFile)
├── Intervention/ # optional — approval / edit-before-run UI
├── Portal/ # optional — full-screen detail view
└── components/ # shared subcomponents used by the surfaces above
```
**Older packages** (`builtin-tool-task`, `builtin-tool-calculator`, etc.) still have `src/executor/` as a sibling of `src/client/`. That's grandfathered; **don't relocate without a deliberate refactor**. New packages and new APIs added to existing packages should follow the layout above.
`package.json` exports map:
```json
"exports": {
".": "./src/index.ts",
"./client": "./src/client/index.ts",
"./executor": "./src/client/executor/index.ts",
"./executionRuntime": "./src/ExecutionRuntime/index.ts"
}
```
---
## Authoring Checklist
Before opening the PR:
- [ ] Identifier follows `lobe-<domain>` and is **stable** (lives in message history).
- [ ] Every `<Name>ApiName` value has: a manifest `api[]` entry, an executor method, an Inspector, an i18n `apiName.*` key.
- [ ] `Params` interfaces match the JSON Schema; `State` interfaces match what the executor returns and what the UI surfaces read.
- [ ] System prompt disambiguates confusable APIs and points to batch variants.
- [ ] Runtime logic lives in `ExecutionRuntime/`; the `client/executor/` only wires stores/services and delegates.
- [ ] Executor returns `{ success, content, state, error? }` via a single `toResult()` funnel — `content` always non-empty (default to `error.message`).
- [ ] Inspector handles `isArgumentsStreaming`, `isLoading`, `partialArgs`, missing `pluginState`.
- [ ] Render returns `null` until it has data; only created for APIs with rich results.
- [ ] Placeholder added if the API has a perceivable execution lag (search, list, crawl).
- [ ] Streaming added for APIs that emit incremental output (run command, write file, code execution).
- [ ] Intervention added if `humanIntervention` is set in the manifest.
- [ ] All registry files updated (see [architecture.md → Registry wiring](references/architecture.md#registry-wiring)).
- [ ] i18n keys in `src/locales/default/plugin.ts` plus dev seeds in `en-US`/`zh-CN`.
- [ ] `bunx vitest run --silent='passed-only' 'packages/builtin-tool-<name>'` passes.
- [ ] `bun run type-check` passes.
---
## Reference Tools
Pick the closest neighbor and copy:
| If your tool is… | Read first |
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Pure-compute, no UI state | `packages/builtin-tool-calculator/``ExecutionRuntime` reuses executor (mathjs/nerdamer work everywhere) |
| CRUD over a domain entity | `packages/builtin-tool-task/` — full Inspector + Render set, batch variants |
| Heavy UI (Inspector/Render/Placeholder/Portal) | `packages/builtin-tool-web-browsing/` — search-style result UI, Portal for detail view |
| Desktop / filesystem with all surfaces (incl. Streaming + Intervention) | `packages/builtin-tool-local-system/``ExecutionRuntime` injects an `ILocalSystemService`, executor calls it |
| Server-side pure (no client executor) | `packages/builtin-tool-web-browsing/` — only `ExecutionRuntime` is exported; the chat client doesn't run it |
| Needs human approval before running | `packages/builtin-tool-local-system/src/client/Intervention/` — per-API approval components |
@@ -1,315 +0,0 @@
# Builtin Tool Architecture
## The Five Faces
A builtin tool ships five distinct faces, each compiled into a different bundle:
```
┌─────────────────────────────────────────────────────────────────┐
│ ./ │
│ Manifest + Types + systemRole │
│ ─ Pure data, no React, no Node-only deps. │
│ ─ Imported by: server (LLM tool spec), client (registries), │
│ anyone who needs to know "what tools exist". │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ./executionRuntime │
│ src/ExecutionRuntime/index.ts │
│ ─ Pure runtime logic. Accepts services via constructor — │
│ never imports concrete services or stores directly. │
│ ─ Imported by: server (BuiltinServerRuntimeOutput), tests, │
│ and the client executor as a delegate. │
│ ─ Returns: BuiltinServerRuntimeOutput { content, state, … } │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ./executor │
│ src/client/executor/index.ts │
│ ─ BaseExecutor subclass. Wires Zustand stores and frontend │
│ services into ExecutionRuntime, then funnels through │
│ toResult() into BuiltinToolResult { content, state, error, │
│ success }. │
│ ─ Imported by: src/store/tool/slices/builtin/executors/ │
│ index.ts (registered as a singleton). │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ./client │
│ src/client/{Inspector,Render,Placeholder,Streaming, │
│ Intervention,Portal,components}/ │
│ ─ React 'use client' surfaces. Read args + pluginState. │
│ ─ Imported by: packages/builtin-tools/src/{inspectors, │
│ renders,placeholders,streamings,interventions,portals}.ts. │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Registry wiring │
│ packages/builtin-tools/src/*.ts │
│ src/store/tool/slices/builtin/executors/index.ts │
│ ─ Aggregator maps: identifier → { apiName → component }. │
└─────────────────────────────────────────────────────────────────┘
```
The split exists so:
- Server bundles import only `./` and `./executionRuntime` and never touch React.
- Frontend bundles import `./client` and never touch Node-only services.
- The runtime is testable without React or Electron present.
---
## Why ExecutionRuntime is the Default Home for Logic
**Old pattern (grandfathered):** business logic in `src/executor/` directly. Examples: `builtin-tool-task`, older tools. Works, but the executor mixes runtime logic with frontend service plumbing — hard to reuse on the server.
**New pattern (preferred):** business logic in `src/ExecutionRuntime/`, frontend wiring in `src/client/executor/`. Examples: `builtin-tool-local-system`, `builtin-tool-web-browsing`, `builtin-tool-calculator`.
```
ExecutionRuntime
├─ accepts services via constructor (or `static create(opts)`)
├─ returns BuiltinServerRuntimeOutput (content + state + success)
└─ no React, no Zustand, no `@/services/...` direct imports
client/executor
├─ extends BaseExecutor<typeof <Name>ApiName>
├─ holds a `runtime = new <Name>ExecutionRuntime(realService)` instance
├─ each ApiName method:
│ 1. resolve scope / pull defaults from BuiltinToolContext
│ 2. call runtime.<method>(args)
│ 3. funnel through toResult() → BuiltinToolResult
└─ exported singleton: export const <name>Executor = new <Name>Executor()
```
### Service injection
`ExecutionRuntime` should declare a TypeScript interface for the services it needs and accept the implementation via constructor. Server callers wire in real implementations; tests wire in mocks. Example from `local-system`:
```ts
export interface ILocalSystemService {
readLocalFile: (params: any) => Promise<any>;
writeFile: (params: any) => Promise<any>;
/* … */
}
export class LocalSystemExecutionRuntime extends ComputerRuntime {
constructor(private service: ILocalSystemService) {
super();
}
/* methods delegate to this.service.* */
}
```
The `client/executor` instantiates it once with the real service:
```ts
import { localFileService } from '@/services/electron/localFileService';
import { LocalSystemExecutionRuntime } from '../../ExecutionRuntime';
class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
private runtime = new LocalSystemExecutionRuntime(localFileService);
/* … */
}
```
### When ExecutionRuntime is the only thing you ship
Some tools are server-only — there's no frontend executor. `builtin-tool-web-browsing` is the canonical example: only `./` and `./executionRuntime` are exported, no `./executor`, and the runtime is constructed by the server-side `ToolExecutionService`. Skip `client/executor/` entirely for those.
### When the executor reuses the runtime as-is
Pure-compute tools (`builtin-tool-calculator`) often have an executor whose ApiName methods call `executor.calculate(args)` and an `ExecutionRuntime` whose methods call `calculatorExecutor.calculate(args)` — same logic, two thin wrappers. That's fine; the duplication buys you the bundle split.
---
## The Result Contract
### `BuiltinServerRuntimeOutput` (what ExecutionRuntime returns)
```ts
{
content: string; // the LLM-facing text — never undefined; default to error message
state?: any; // result-domain object the UI reads as pluginState
success: boolean; // mandatory
error?: any; // raw error; the executor will repackage
}
```
### `BuiltinToolResult` (what the executor returns to the runtime)
```ts
{
success: boolean;
content?: string;
state?: any;
error?: { type: string; message: string; body?: any };
metadata?: Record<string, any>; // rare; e.g. { agentCouncil: true }
stop?: boolean; // rare; halt the orchestration step
}
```
### The `toResult` funnel (mandatory)
Every executor method returns through a single `toResult()` to enforce two invariants:
1. **`content` is never undefined.** A missing content collapses downstream into `''`, leaving the Debug pane blank while `pluginState` was already saved. See the `globLocalFiles` regression in `local-system/src/client/executor/index.ts:60-84`.
2. **`state` survives failures.** Renderers can keep showing partial output even when `success: false`.
```ts
private toResult(output: BuiltinServerRuntimeOutput): BuiltinToolResult {
const errorMessage = typeof output.error?.message === 'string' ? output.error.message : undefined;
const safeContent = output.content || errorMessage || 'Tool execution failed';
if (!output.success) {
return {
success: false,
content: safeContent,
state: output.state,
error: output.error
? { type: 'PluginServerError', message: errorMessage ?? safeContent, body: output.error }
: undefined,
};
}
return { success: true, content: safeContent, state: output.state };
}
```
---
## `BaseExecutor` — How Method Dispatch Works
`BaseExecutor.invoke(apiName, params, ctx)` does:
```ts
if (!this.hasApi(apiName)) return { error: { type: 'ApiNotFound', }, success: false };
return (this as any)[apiName](params, ctx); // method name MUST equal apiName value
```
So:
- **Method names must equal `<Name>ApiName` values, exactly.** A typo silently routes to "ApiNotFound".
- **Methods must be class fields, not class methods**, because `this` is lost when registry calls `executor.invoke(apiName, params, ctx)`. Always declare as `methodName = async (…) => { … }`.
- **Always destructure `apiEnum` and `identifier` as `readonly` instance fields**, not getters — `BaseExecutor.hasApi/getApiNames` reads them synchronously.
---
## `BuiltinToolContext` — What the Executor Receives
The runtime hands every executor method an optional `BuiltinToolContext` as the second argument:
| Field | Use |
| ----------------------------- | -------------------------------------------------------------- |
| `agentId` | Default agent for "current agent" semantics (e.g. `listTasks`) |
| `groupId` | Group chat scope |
| `topicId` | Current topic — needed when creating messages/operations |
| `taskId` | Current task identifier — fallback for "implicit" param |
| `documentId` | Current page/document scope |
| `messageId` | The tool message being created (for state attachments) |
| `sourceMessageId` | The user message that triggered this tool turn |
| `operationId` | Operation lineage (use for cancellation, tracing) |
| `scope` | `'task' \| 'agent' \| …` — toggles default behaviors |
| `signal: AbortSignal` | Honor for long-running ops |
| `stepContext` | Cross-message runtime state (lobe-agent todos, etc.) |
| `registerAfterCompletion(cb)` | Defer side-effects past message-update race |
| `groupOrchestration` | Group orchestration callbacks |
**Use rule:** read with `?.`, fall back to explicit params, **never silently override** an explicit param with a context value.
---
## i18n Integration
Source of truth: `src/locales/default/plugin.ts`. Keys follow `builtins.<identifier>.<topic>.<…>`:
| Key | Use |
| ------------------------------------- | ------------------------------------------------------------ |
| `builtins.<identifier>.title` | Display title (overrides `manifest.meta.title` when present) |
| `builtins.<identifier>.apiName.<api>` | Inspector header label (one per ApiName) |
| `builtins.<identifier>.inspector.<…>` | Extra Inspector strings ("no results", chips, counters) |
| `builtins.<identifier>.<feature>.<…>` | Render / Intervention strings, free-form per tool |
For dev preview, also seed `locales/zh-CN/plugin.json` and `locales/en-US/plugin.json`. Run `pnpm i18n` before opening a PR — it's slow, so do it once at the end. (See the **i18n** skill for the full workflow.)
---
## Registry Wiring
Five core files plus optional ones. Miss any and you'll see "tool not found", a missing chip, a blank result card, a stuck spinner, or an approval dialog that never appears.
| File | Add what |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **Required** | |
| `packages/builtin-tools/src/index.ts` | Import `<Name>Manifest`; push entry to `builtinTools`. Set `hidden`/`discoverable` flags. |
| `packages/builtin-tools/src/identifiers.ts` | Add `<Name>Manifest.identifier` to `builtinToolIdentifiers`. |
| `packages/builtin-tools/src/inspectors.ts` | Import `<Name>Inspectors, <Name>Manifest`; add to `BuiltinToolInspectors`. |
| `src/store/tool/slices/builtin/executors/index.ts` | Import `<name>Executor`; add to `registerExecutors([…])`. |
| **Conditional — add only if the surface exists** | |
| `packages/builtin-tools/src/renders.ts` | Add to `BuiltinToolsRenders` if any API has a Render. |
| `packages/builtin-tools/src/placeholders.ts` | Add to `BuiltinToolPlaceholders` if any API has a Placeholder. |
| `packages/builtin-tools/src/streamings.ts` | Add to `BuiltinToolStreamings` if any API has a Streaming renderer. |
| `packages/builtin-tools/src/interventions.ts` | Add to `BuiltinToolInterventions` if any API has an Intervention component. |
| `packages/builtin-tools/src/portals.ts` | Add to `BuiltinToolsPortals` if the tool has a Portal. |
| `packages/builtin-tools/src/displayControls.ts` | Add if Render must show/hide based on result content (rare; see ClaudeCode/Codex). |
### Optional flags in `packages/builtin-tools/src/index.ts`
```ts
{
identifier: TaskManifest.identifier,
manifest: TaskManifest,
type: 'builtin',
hidden: true, // hide from chat-input Tools popover
discoverable: false, // exclude from agent builder / skill discovery
}
```
Lists in the same file you may need to touch:
- `defaultToolIds` — added to the agent's tool list by default
- `alwaysOnToolIds` — forced on regardless of user selection (use sparingly)
- `runtimeManagedToolIds` — enable state controlled by runtime, not user UI; **must mirror the rules map** in `src/server/modules/Mecha/AgentToolsEngine/index.ts` and `src/helpers/toolEngineering/index.ts`
---
## File-Map at a Glance
```
packages/builtin-tool-<name>/
├── package.json # exports: ., ./client, ./executor, ./executionRuntime
└── src/
├── index.ts # export Manifest, Identifier, types, systemPrompt
├── manifest.ts # BuiltinToolManifest + Identifier const
├── types.ts # ApiName + Params/State per API
├── systemRole.ts # System prompt (multiple variants OK: systemRole.desktop.ts)
├── ExecutionRuntime/
│ └── index.ts # <Name>ExecutionRuntime — pure runtime, service injection
└── client/
├── index.ts # exports for the registries
├── executor/
│ └── index.ts # <Name>Executor extends BaseExecutor; export <name>Executor
├── Inspector/
│ ├── index.ts # <Name>Inspectors record
│ └── <ApiName>/index.tsx # one folder per API (or .tsx file when trivial)
├── Render/
│ ├── index.ts # <Name>Renders record
│ └── <ApiName>/ # rich renders → folder with subcomponents
├── Placeholder/
│ ├── index.ts
│ └── <ApiName>.tsx # usually a single skeleton file
├── Streaming/
│ ├── index.ts
│ └── <ApiName>/ # live-output renderer
├── Intervention/
│ ├── index.ts
│ └── <ApiName>/ # approval / edit-before-run UI
├── Portal/
│ ├── index.tsx # routing component (switch on apiName)
│ └── <ApiName>/ # full-screen detail view
└── components/ # FileItem, EngineAvatar, etc. — shared subcomponents
```
Skip every `client/<surface>/` directory you don't need — empty registries are fine.
@@ -1,478 +0,0 @@
# Tool Design (Naming, Manifest, Executor, Runtime)
This doc covers everything that **isn't UI**: the tool's identifier, API surface, manifest, types, system prompt, ExecutionRuntime, and the executor that wires it into the frontend.
For UI surfaces (Inspector / Render / Placeholder / Streaming / Intervention / Portal), see [ui/](ui/README.md).
For where files live and how registries work, see [architecture.md](architecture.md).
---
## 1. Naming
| Thing | Convention | Example |
| ----------------------- | -------------------------------------------------------------- | ------------------------------------------------------------ |
| Package directory | `packages/builtin-tool-<kebab>/` | `builtin-tool-task` |
| npm name | `@lobechat/builtin-tool-<kebab>` | `@lobechat/builtin-tool-task` |
| Tool `identifier` | `lobe-<kebab-domain>`**persisted in message history** | `lobe-task`, `lobe-calculator`, `lobe-knowledge-base` |
| Identifier const | `<Name>Identifier` exported from `manifest.ts` (or `types.ts`) | `export const TaskIdentifier = 'lobe-task'` |
| API name const | `<Name>ApiName``as const` object, **camelCase verbs** | `createTask`, `listTasks`, `runTask` |
| Executor class | `<Name>Executor extends BaseExecutor<typeof <Name>ApiName>` | `TaskExecutor` |
| Executor singleton | `<name>Executor` (camelCase) | `export const taskExecutor = new TaskExecutor()` |
| ExecutionRuntime class | `<Name>ExecutionRuntime` | `LocalSystemExecutionRuntime`, `WebBrowsingExecutionRuntime` |
| Inspector / Render etc. | `<ApiName>Inspector` / `<ApiName>Render` | `CreateTaskInspector`, `SearchInspector` |
### Identifier rules
- **`lobe-` prefix is mandatory** — many switches in the codebase key off it.
- Pick a **domain noun**, not a verb (`lobe-task`, not `lobe-task-manager`).
- The identifier is **persisted in message history** — renaming after release means the `@deprecated` alias trick (register the legacy identifier as a second key in `inspectors.ts` / `renders.ts` pointing at the new module). Get it right the first time.
### ApiName rules
- Verb + noun, camelCase: `createTask`, `viewTask`, `runTasks`.
- **Plural variant for batch** (`createTasks`, `runTasks`) — describe in the manifest description that it's preferred over multiple single calls. The system prompt should also push the batch form.
- Reserve **clear separation between mutating verbs** (`updateTaskStatus`, `editTask`) and **execution verbs** (`runTask`). The system prompt must warn the model when these are confusable — see `task` for the canonical "do NOT use updateTaskStatus(running) to start a task" warning.
- Read-only verbs: `list*`, `view*`, `get*`, `search*`. Mutating: `create*`, `edit*`, `update*`, `delete*`. Triggers/effects: `run*`, `execute*`, `submit*`.
---
## 2. `types.ts` — ApiName + Params/State
Define `<Name>ApiName` as `as const` so it doubles as a runtime enum (used by `BaseExecutor`) and a literal type. Then declare `Params` and `State` per API.
```ts
export const TaskIdentifier = 'lobe-task';
export const TaskApiName = {
createTask: 'createTask',
createTasks: 'createTasks',
listTasks: 'listTasks',
/* …one entry per API, group logically (CRUD then run-style) */
} as const;
export type TaskApiNameType = (typeof TaskApiName)[keyof typeof TaskApiName];
// One block per API
export interface CreateTaskParams {
name: string;
instruction: string; /* … */
}
export interface CreateTaskState {
identifier?: string;
success: boolean;
}
export interface CreateTasksParams {
tasks: CreateTaskParams[];
}
export interface CreateTasksItemResult {
error?: string;
identifier?: string;
name: string;
success: boolean;
}
export interface CreateTasksState {
failed: number;
results: CreateTasksItemResult[];
succeeded: number;
}
```
**The result-domain rule for `State`** (memory: "pluginState is result-domain, not call-domain"):
- Include only fields the UI **renders after the call returns** — ids the LLM didn't have when calling, counts, summary numbers, server-assigned status.
- **Don't echo all params.** The Inspector/Render gets `args` for free.
- Keep batch results as `{ succeeded, failed, results }` so the Render can show a one-line summary plus a detail list.
---
## 3. `manifest.ts` — JSON Schema for the LLM
```ts
import type { BuiltinToolManifest } from '@lobechat/types';
import { systemPrompt } from './systemRole';
import { TaskApiName, TaskIdentifier } from './types';
export const TaskManifest: BuiltinToolManifest = {
identifier: TaskIdentifier,
type: 'builtin',
systemRole: systemPrompt,
meta: {
avatar: '📋',
title: 'Task Tools',
description: 'Create, list, edit, delete tasks with dependencies',
readme: 'Optional long description shown in tool detail pages',
},
api: [
{
name: TaskApiName.createTask,
description:
'Create a new task. Optionally attach as a subtask via parentIdentifier. ' +
'Prefer createTasks when planning a batch.',
parameters: {
type: 'object',
required: ['name', 'instruction'],
properties: {
name: { type: 'string', description: 'Short, descriptive name.' },
instruction: {
type: 'string',
description: 'Detailed instruction for what the task should accomplish.',
},
parentIdentifier: {
type: 'string',
description:
'Identifier of the parent task (e.g. "TASK-1"). If provided, the new task becomes a subtask.',
},
priority: {
type: 'number',
description: 'Priority level: 0=none, 1=urgent, 2=high, 3=normal, 4=low. Default is 0.',
},
},
},
},
/* …one entry per ApiName */
],
};
```
### Manifest writing checklist
- **Every API in `<Name>ApiName` has exactly one entry in `api[]`.** Easy to drift after a refactor.
- **`description` on each API is the model's only docs.** Make it long enough for the LLM to pick the right tool. Mention edge cases ("If you provide any filter, omitted filters are not applied implicitly"), defaults, and the relationship to sibling APIs ("To START a task, use runTask — updateTaskStatus only flips a flag").
- **`parameters` is JSON Schema** (`LobeChatPluginApi`). Use `enum`, `required`, `items`, `oneOf`, `additionalProperties: false` etc. — these survive into the LLM's tool spec.
- **Use `additionalProperties: false`** on parameter objects so the model can't sneak unknown fields past validation.
- **Number parameters with semantic values** (`priority: 0=none, 1=urgent, …`) should describe the mapping in the description. Don't rely on `enum` alone for numbers — the model often fills the wrong one.
- **`enum` arrays for known string sets** (statuses, categories, engines). Spread from a constants module (`enum: [...TASK_STATUSES]`) so the manifest stays in sync.
### Optional manifest fields
```ts
{
/* Where this tool can run.
'client' → Agent Gateway dispatches to the desktop client (filesystem, Electron only)
'server' → ToolExecutionService runs it on the server
omitted → server only */
executors: ['client', 'server'],
/* Default human intervention policy for all APIs that don't specify one.
Pair with an Intervention component (see ui/intervention.md). */
humanIntervention: 'never' | 'always' | { /* extended config */ },
}
```
Per-API `humanIntervention` and `renderDisplayControl` go inside each `api[]` entry.
---
## 4. `systemRole.ts` — Operator Instructions for the Model
This is appended to the agent system prompt whenever the tool is enabled. Treat it as a **how-to-use guide for the LLM**, not marketing copy.
```ts
export const systemPrompt = `You have access to Task management tools. Use them to:
- **createTask**: Create a new task. Use parentIdentifier to make it a subtask.
- **createTasks**: Prefer this over multiple createTask calls when planning a batch
(e.g. all subtasks under one parent, or all chapters of an outline).
- **runTask**: Actually START a task — kicks off the agent in a new (or continued)
topic. Do NOT use updateTaskStatus(running) to start a task; that only flips a
flag without executing. The task must have an assigneeAgentId.
- **updateTaskStatus**: Change a task's status (completed/cancelled/paused/failed).
If you mark a task as failed, include an error message explaining why.
- ...
When planning work:
1. Create tasks for each major piece (use parentIdentifier to organize as subtasks).
2. Use editTask with addDependencies to control execution order.
3. Use updateTaskStatus to mark the current task completed when done.`;
```
### Patterns that work well
- **Bulleted list, bold the API name, one line per API.** The model picks tools by skimming.
- **Disambiguate confusable APIs explicitly** (`runTask` vs `updateTaskStatus`).
- **Push toward batched APIs** ("Prefer this when…").
- **End with a numbered workflow** if the tool has a typical sequence.
- **For tools with multiple environments** (e.g. desktop vs cloud), keep variants in `systemRole.ts` and `systemRole.desktop.ts` and pick at the manifest level. See `builtin-tool-local-system`.
### Dynamic system prompts
If the prompt depends on runtime state (current date, available models), export a function and call it in the manifest:
```ts
// systemRole.ts
export const systemPrompt = (today: string) => `Today is ${today}. You have web search tools…`;
// manifest.ts
import dayjs from 'dayjs';
systemRole: systemPrompt(dayjs(new Date()).format('YYYY-MM-DD')),
```
---
## 5. `ExecutionRuntime/index.ts` — Pure Runtime
This is **the default home for new tool logic** going forward. The runtime is a class that:
- Has no React, no Zustand, no `@/services/...` direct imports.
- Receives services as **constructor injection** (or as method args).
- Returns `BuiltinServerRuntimeOutput` from each method.
- Is unit-testable by passing in mocks.
### Pattern A: Inject a service interface
Use when the runtime calls out to IPC, network, or DB.
```ts
// ExecutionRuntime/index.ts
import type { BuiltinServerRuntimeOutput } from '@lobechat/types';
export interface IWebBrowsingService {
search: (q: SearchQuery) => Promise<UniformSearchResponse>;
crawlPages: (urls: string[]) => Promise<CrawlResults>;
}
export interface WebBrowsingRuntimeOptions {
searchService: IWebBrowsingService;
documentService?: WebBrowsingDocumentService;
agentId?: string;
topicId?: string;
}
export class WebBrowsingExecutionRuntime {
constructor(private opts: WebBrowsingRuntimeOptions) {}
async search(
args: SearchQuery,
options?: { signal?: AbortSignal },
): Promise<BuiltinServerRuntimeOutput> {
try {
const data = await this.opts.searchService.search(args, options);
if (data.errorDetail) {
return {
success: false,
content: data.errorDetail,
error: { message: data.errorDetail },
state: data,
};
}
return {
success: true,
content: searchResultsPrompt(data.results.slice(0, 10)),
state: data,
};
} catch (e) {
return { success: false, content: (e as Error).message, error: e };
}
}
}
```
### Pattern B: Reuse the executor
Use when the same logic runs in browser and Node (e.g. mathjs, nerdamer). The runtime is a thin wrapper that imports the executor and re-types the state per API. See `builtin-tool-calculator/src/ExecutionRuntime/index.ts` for the canonical example.
### Pattern C: Extend a shared base
When you're implementing a domain that already has a base runtime (file ops via `ComputerRuntime`), extend and only override `callService` + result normalization. See `builtin-tool-local-system/src/ExecutionRuntime/index.ts`.
### Runtime contract
Every method returns:
```ts
{
content: string; // LLM-facing — never undefined; default to error message
state?: any; // result-domain — what the UI's pluginState becomes
success: boolean; // mandatory
error?: any; // raw error object; the executor will repackage
}
```
Use `@lobechat/prompts` formatters (`searchResultsPrompt`, `crawlResultsPrompt`, `formatTaskCreated`, etc.) to produce structured `content`. They emit XML/markdown that's already tuned for token efficiency.
---
## 6. `client/executor/index.ts` — Frontend Wiring
The executor's job is to **resolve frontend defaults** (current agent, current task, scope) and **call the runtime**. It then funnels through `toResult()` into the `BuiltinToolResult` shape.
```ts
import { BaseExecutor, type BuiltinToolContext, type BuiltinToolResult } from '@lobechat/types';
import debug from 'debug';
import { taskService } from '@/services/task';
import { getTaskStoreState } from '@/store/task';
import { TaskIdentifier } from '../../manifest';
import { TaskApiName, type CreateTaskParams } from '../../types';
const log = debug('lobe-task:executor');
class TaskExecutor extends BaseExecutor<typeof TaskApiName> {
readonly identifier = TaskIdentifier;
protected readonly apiEnum = TaskApiName;
// ⚠ class FIELD, not a method — preserves `this` when invoked via registry
createTask = async (
params: CreateTaskParams,
ctx?: BuiltinToolContext,
): Promise<BuiltinToolResult> => {
try {
log('createTask params=%o', params);
const task = await getTaskStoreState().createTask({
name: params.name,
instruction: params.instruction,
// Default assignee from context — never silently override an explicit value
assigneeAgentId:
params.assigneeAgentId ?? (ctx?.scope === 'task' ? undefined : ctx?.agentId),
parentTaskId: params.parentIdentifier?.trim() || undefined,
priority: params.priority,
});
if (!task) return this.errorResult('Failed to create task', 'CreateFailed');
return {
success: true,
content: formatTaskCreated({ identifier: task.identifier, name: task.name /* … */ }),
state: { identifier: task.identifier, success: true },
};
} catch (error) {
return this.errorResult(error, 'CreateTaskFailed');
}
};
private errorResult(err: unknown, type: string): BuiltinToolResult {
const message = err instanceof Error ? err.message : String(err) || 'Unknown error';
return { success: false, content: `Failed: ${message}`, error: { type, message } };
}
}
export const taskExecutor = new TaskExecutor();
```
### Hard rules
1. **Methods are class fields** (`name = async (…) => {…}`), not class methods. The registry calls `(executor as any)[apiName](params, ctx)`; arrow-function fields keep `this` bound.
2. **`identifier` and `apiEnum` are `readonly` instance fields**, not getters — `BaseExecutor.hasApi/getApiNames` reads them synchronously at registration time.
3. **Default missing params from `ctx`**, but never silently override explicit values. Use `params.foo ?? ctx?.foo`, not `ctx?.foo ?? params.foo`.
4. **One funnel for all returns.** Either always return through `toResult(runtime.x())` (when delegating) or through `errorResult(…)` for the catch arm. Never inline `{ success: false, content: '' }``content: ''` collapses the Debug pane to blank.
5. **`debug('lobe-<name>:executor')`.** Match the namespace to the identifier minus `lobe-` when convenient.
6. **Singleton export.** `export const <name>Executor = new <Name>Executor()` — the registry imports the instance, not the class.
### When the executor delegates to ExecutionRuntime
```ts
class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
readonly identifier = LocalSystemIdentifier;
protected readonly apiEnum = LocalSystemApiEnum;
private runtime = new LocalSystemExecutionRuntime(localFileService);
readLocalFile = async (params: LocalReadFileParams): Promise<BuiltinToolResult> => {
try {
const result = await this.runtime.readFile({
path: params.path,
startLine: params.loc?.[0],
endLine: params.loc?.[1],
});
return this.toResult(result);
} catch (error) {
return this.errorResult(error);
}
};
private toResult(out: BuiltinServerRuntimeOutput): BuiltinToolResult {
const errMsg = typeof out.error?.message === 'string' ? out.error.message : undefined;
const safe = out.content || errMsg || 'Tool execution failed';
if (!out.success) {
return {
success: false,
content: safe,
state: out.state, // ← preserve partial state on failure
error: out.error
? { type: 'PluginServerError', message: errMsg ?? safe, body: out.error }
: undefined,
};
}
return { success: true, content: safe, state: out.state };
}
}
```
The `toResult` funnel is **mandatory**: it enforces never-undefined `content` and partial-state preservation. Both invariants caught real production bugs (`globLocalFiles` Response empty, `editLocalFile` partial state lost).
---
## 7. `index.ts` — Package Entry Point
Keep it pure data + the manifest. **No React, no stores, no Node-only imports.**
```ts
export { TaskIdentifier, TaskManifest } from './manifest';
export { systemPrompt } from './systemRole';
export {
TaskApiName,
type TaskApiNameType,
type CreateTaskParams,
type CreateTaskState,
/* …all Params/State types */
} from './types';
// Optional helpers used by both the runtime and the UI
export { TASK_STATUSES, UNFINISHED_TASK_STATUSES } from './constants';
```
This entry is what `packages/builtin-tools/src/index.ts` and `identifiers.ts` import — it must be importable from server bundles.
---
## 8. `package.json`
```json
{
"dependencies": {
"@lobechat/prompts": "workspace:*"
},
"devDependencies": {
"@lobechat/types": "workspace:*"
},
"exports": {
".": "./src/index.ts",
"./client": "./src/client/index.ts",
"./executor": "./src/client/executor/index.ts",
"./executionRuntime": "./src/ExecutionRuntime/index.ts"
},
"main": "./src/index.ts",
"name": "@lobechat/builtin-tool-<name>",
"peerDependencies": {
"@lobehub/ui": "^5",
"antd": "^6",
"antd-style": "*",
"lucide-react": "*",
"react": "*",
"react-i18next": "*"
},
"private": true,
"version": "1.0.0"
}
```
**Why peer not direct deps for client libs:** the `./` and `./executionRuntime` entry points must be importable from server code. Listing React etc. as peer deps prevents bundlers from following them when only the runtime is consumed.
**Skip `./executor`** if the package has no frontend executor (server-only tools like `builtin-tool-web-browsing`).
---
## 9. Common Pitfalls
| Symptom | Likely cause |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| "ApiNotFound" at runtime | Method name in executor doesn't match `ApiName` value (typo, wrong case) |
| Method works once, then "this is undefined" | Method declared as `async fn() {}` instead of `fn = async () => {}``this` lost when registry invokes |
| Debug "Response" pane blank but `pluginState` populated | Returning `content: ''` or letting `output.content` be undefined — use the `toResult` funnel |
| Partial result vanishes on failure | `toResult` discarded `state` when `success: false`; preserve it |
| Tool shows up but doesn't run on desktop | `executors` in manifest doesn't include `'client'` (or vice versa for server-only) |
| Same tool registered twice / legacy identifier ghost | Identifier collision; check `@deprecated` aliases in `inspectors.ts`/`renders.ts` |
| Manifest test fails after adding API | Forgot to add the corresponding i18n `apiName.<api>` key |
| TypeScript error on `BaseExecutor<typeof X>` | `X` declared with `enum` instead of `as const` object — must be the const-object form |
@@ -1,36 +0,0 @@
# Tool UI Surfaces
A builtin tool can ship up to **six client-side surfaces**, each with a different role in the chat UI. Only `Inspector` is required; the other five are added on demand and registered in their own central files.
| Surface | Required? | When the chat shows it | Registered in |
| ------------ | --------- | --------------------------------------------------------------------- | --------------------------------------------- |
| Inspector | ✅ Always | Header strip of every tool call (one-line chip) | `packages/builtin-tools/src/inspectors.ts` |
| Render | Optional | Rich result card below the header, after the call returns | `packages/builtin-tools/src/renders.ts` |
| Placeholder | Optional | Skeleton between "args streaming complete" and "result arrives" | `packages/builtin-tools/src/placeholders.ts` |
| Streaming | Optional | Live output during execution (e.g. command stdout) | `packages/builtin-tools/src/streamings.ts` |
| Intervention | Optional | Approval / edit-before-run dialog (when `humanIntervention` triggers) | `packages/builtin-tools/src/interventions.ts` |
| Portal | Optional | Full-screen detail view (right-side or modal) | `packages/builtin-tools/src/portals.ts` |
The two reference tools to read end-to-end:
- **`builtin-tool-web-browsing/src/client/`** — Inspector + Render + Placeholder + Portal (no Intervention/Streaming).
- **`builtin-tool-local-system/src/client/`** — all six surfaces, including `components/` for shared building blocks.
---
## Files in this folder
Read **principles** and **shared-rules** first — they apply to every surface. Then jump to the surface you're building.
| File | What it covers |
| ---------------------------------- | ----------------------------------------------------------------------- |
| [principles.md](principles.md) | Design principles — when each surface exists and how far to take it |
| [shared-rules.md](shared-rules.md) | Cross-surface rules: component skeleton, styling, single-layer surfaces |
| [inspector.md](inspector.md) | Inspector — header chip (required) |
| [render.md](render.md) | Render — rich result card |
| [placeholder.md](placeholder.md) | Placeholder — skeleton between args and result |
| [streaming.md](streaming.md) | Streaming — live output during execution |
| [intervention.md](intervention.md) | Intervention — approval / edit-before-run |
| [portal.md](portal.md) | Portal — full-screen detail view |
| [composition.md](composition.md) | Shared subcomponents (`client/components/`) + package public API |
| [diagnostics.md](diagnostics.md) | Symptom → surface quick-lookup |
@@ -1,51 +0,0 @@
# Composition — Shared Components & Package API
## `client/components/` — Shared Subcomponents
Cross-cutting building blocks used by multiple surfaces live here, not duplicated in each surface folder.
Examples from `web-browsing/src/client/components/`:
- `CategoryAvatar.tsx` — search category icon
- `EngineAvatar.tsx` — search engine logo (used in Inspector chip + Render list + Portal header)
- `SearchBar.tsx` — editable query bar (used in Render and Portal)
Examples from `local-system/src/client/components/`:
- `FileItem.tsx` — single file row (used in ListFiles Render, SearchFiles Render, MoveLocalFiles Render)
- `FilePathDisplay.tsx` — path with truncation (used everywhere)
### Rules
- Live under `client/components/`, exported via `client/components/index.ts`.
- Re-export from `client/index.ts` only if other packages need them; otherwise keep internal.
- Keep them dumb — props in, JSX out, no store reads. The store reads belong in the surface that composes them.
---
## `client/index.ts` — Package Public API
Re-exports everything the registries need plus useful types/manifest:
```ts
// Inspector — required
export { TaskInspectors } from './Inspector';
// Render — only if any API has one
export { TaskRenders, CreateTaskRender, RunTasksRender } from './Render';
// Placeholder / Streaming / Intervention — only if used
export { LocalSystemListFilesPlaceholder, LocalSystemSearchFilesPlaceholder } from './Placeholder';
export { LocalSystemStreamings } from './Streaming';
export { LocalSystemInterventions } from './Intervention';
// Portal — single export per tool
export { default as WebBrowsingPortal } from './Portal';
// Reusable components if other packages need them
export { CategoryAvatar, EngineAvatar, SearchBar } from './components';
// Re-export manifest, identifier, types for convenience
export { TaskManifest, TaskIdentifier } from '../manifest';
export * from '../types';
```
@@ -1,15 +0,0 @@
# Diagnostic Quick-Lookup
| Symptom | Surface to check |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| No header at all on the tool call | Inspector missing from `client/Inspector/index.ts` registry |
| Header shows the API name but no chips | Inspector missing `args?.X \|\| partialArgs?.X` fallback |
| Header doesn't pulse during loading | Missing `shinyTextStyles.shinyText` on `isArgumentsStreaming \|\| isLoading` |
| Empty result card under header | Render returned `<div />` instead of `null` when no data |
| Render looks "complex" / card-in-card | Filled container (`colorFillQuaternary`) wrapping more filled boxes — flatten to single-layer, see [shared-rules.md](shared-rules.md) |
| Layout jump when result arrives | Placeholder dimensions don't match Render dimensions |
| Approval dialog never appears | Manifest missing `humanIntervention`, or Intervention not in registry |
| Approval click doesn't wait for inline edit | Missing `registerBeforeApprove(id, flushFn)` |
| Portal opens but blank | Switch in `Portal/index.tsx` doesn't cover the apiName |
| Strings show as `builtins.lobe-foo.apiName.bar` | Missing i18n key in `src/locales/default/plugin.ts` (or not seeded in dev locale files) |
| Wrong color shade on `<Text type="secondary">` | `type='secondary'` is lighter than `colorTextSecondary` — pass via `style={{ color: cssVar.colorTextSecondary }}` |
@@ -1,118 +0,0 @@
# Inspector — Header Chip (required)
**Lifecycle:** Inspector renders for **every phase** of a tool call: while args are streaming in, while the executor is running, and after results come back. It's the only surface that's always visible.
**Goal:** keep it to a single line. Show what's happening with as much context as is currently available.
## Props (`BuiltinInspectorProps<Args, State>`)
```ts
interface BuiltinInspectorProps<Arguments = any, State = any> {
apiName: string;
args: Arguments; // final args (only after the assistant stops streaming)
identifier: string;
isArgumentsStreaming?: boolean; // args still arriving
isLoading?: boolean; // args complete, executor running
partialArgs?: Arguments; // partial JSON during streaming
pluginState?: State; // executor's `state` after success
result?: { content: string | null; error?: any };
}
```
## State machine
| Phase | What's available | What to show |
| ----------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- |
| Args streaming, no useful field yet | `isArgumentsStreaming === true`, `partialArgs.X` undefined | Just the API title with `shinyTextStyles.shinyText` |
| Args streaming, key field arrived | `partialArgs.X` populated | Title + key field chip, still pulse-animated |
| Args complete, executor running | `args` populated, `isLoading === true` | Same as above, still pulse-animated |
| Result arrived | `pluginState` populated, `isLoading === false` | Title + chips + result summary (count, identifier, status) |
## Canonical example — Search
`packages/builtin-tool-web-browsing/src/client/Inspector/Search/index.tsx`:
```tsx
'use client';
import type { BuiltinInspectorProps, SearchQuery, UniformSearchResponse } from '@lobechat/types';
import { Text } from '@lobehub/ui';
import { cssVar, cx } from 'antd-style';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
export const SearchInspector = memo<BuiltinInspectorProps<SearchQuery, UniformSearchResponse>>(
({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
const { t } = useTranslation('plugin');
const query = args?.query || partialArgs?.query || '';
const resultCount = pluginState?.results?.length ?? 0;
const hasResults = resultCount > 0;
if (isArgumentsStreaming && !query) {
return (
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
<span>{t('builtins.lobe-web-browsing.apiName.search')}</span>
</div>
);
}
return (
<div
className={cx(
inspectorTextStyles.root,
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
)}
>
<span>{t('builtins.lobe-web-browsing.apiName.search')}:&nbsp;</span>
{query && <span className={highlightTextStyles.primary}>{query}</span>}
{!isLoading &&
!isArgumentsStreaming &&
pluginState?.results &&
(hasResults ? (
<span style={{ marginInlineStart: 4 }}>({resultCount})</span>
) : (
<Text as="span" color={cssVar.colorTextDescription} fontSize={12}>
({t('builtins.lobe-web-browsing.inspector.noResults')})
</Text>
))}
</div>
);
},
);
SearchInspector.displayName = 'SearchInspector';
export default SearchInspector;
```
## Inspector rules
- Wrap the whole row with `inspectorTextStyles.root` (provides correct flex / line-height baseline).
- Pulse with `shinyTextStyles.shinyText` whenever `isArgumentsStreaming || isLoading`.
- Show the i18n title first so the row is non-empty during the earliest streaming phase.
- Read both `args?.X` and `partialArgs?.X` together — `args` is final, `partialArgs` is in-stream.
- Use chips/tags for distinct facets (identifier, name, parent, status, count). Each chip should clip with `text-overflow: ellipsis` and have a `max-width` so long values don't blow out the chat bubble.
- Append `pluginState`-derived suffixes only **after** loading finishes — count or "(no results)" should not appear while still searching.
- **Switch copy by phase.** If the verb implies an ongoing action ("Creating", "Searching", "Listing"), define `<api>.loading` and `<api>.completed` keys and select via `isArgumentsStreaming || isLoading ? loadingKey : completedKey`. Inspector chips persist in chat history — leaving "Creating task" frozen on a finished call reads as if the tool is still running. Read-only labels that are already noun-form ("View task") can keep a single key. See `CallSubAgentInspector` for the canonical two-key pattern.
## Inspector registry — `client/Inspector/index.ts`
```ts
import type { BuiltinInspector } from '@lobechat/types';
import { TaskApiName } from '../../types';
import { CreateTaskInspector } from './CreateTask';
import { ListTasksInspector } from './ListTasks';
/* … */
export const TaskInspectors: Record<string, BuiltinInspector> = {
[TaskApiName.createTask]: CreateTaskInspector as BuiltinInspector,
[TaskApiName.listTasks]: ListTasksInspector as BuiltinInspector,
/* one entry per ApiName */
};
export { CreateTaskInspector } from './CreateTask';
export { ListTasksInspector } from './ListTasks';
/* re-export each */
```
@@ -1,88 +0,0 @@
# Intervention — Approval / Edit-Before-Run (optional)
**Lifecycle:** rendered **before the executor runs** for APIs whose manifest sets `humanIntervention`. The user sees a preview of the args, can edit them, then approves or skips/cancels.
**Add for** destructive or sensitive ops: shell commands, file writes, file moves, payments, message broadcasts.
## Props (`BuiltinInterventionProps<Args>`)
```ts
interface BuiltinInterventionProps<Arguments = any> {
apiName?: string;
args: Arguments;
identifier?: string;
interactionMode?: 'approval' | 'custom';
messageId: string;
/** Called when the user edits the args; the approve action awaits this. */
onArgsChange?: (args: Arguments) => void | Promise<void>;
/** Called on approve / skip / cancel. */
onInteractionAction?: (
action:
| { type: 'submit'; payload: Record<string, unknown> }
| { type: 'skip'; payload?: Record<string, unknown>; reason?: string }
| { type: 'cancel'; payload?: Record<string, unknown> },
) => Promise<void>;
/** Register a callback to flush pending saves before approval. Returns cleanup. */
registerBeforeApprove?: (id: string, callback: () => void | Promise<void>) => () => void;
}
```
## Canonical example — RunCommand Intervention
`packages/builtin-tool-local-system/src/client/Intervention/RunCommand/index.tsx`:
```tsx
import type { RunCommandParams } from '@lobechat/electron-client-ipc';
import type { BuiltinInterventionProps } from '@lobechat/types';
import { Flexbox, Highlighter, Text } from '@lobehub/ui';
import { memo } from 'react';
const RunCommand = memo<BuiltinInterventionProps<RunCommandParams>>(({ args }) => {
const { description, command, timeout } = args;
return (
<Flexbox gap={8}>
<Flexbox horizontal justify="space-between">
{description && <Text>{description}</Text>}
{timeout && (
<Text style={{ fontSize: 12 }} type="secondary">
timeout: {formatTimeout(timeout)}
</Text>
)}
</Flexbox>
{command && (
<Highlighter wrap language="sh" showLanguage={false} variant="outlined">
{command}
</Highlighter>
)}
</Flexbox>
);
});
export default RunCommand;
```
## Intervention rules
- **Show a preview, not a form by default.** Editing UI is opt-in via `onArgsChange` and is usually inline (click to edit a code block, etc.).
- For args with debounced edit state (text fields), use `registerBeforeApprove(id, flushFn)` so the approve action waits for the debounce to flush. Always return the cleanup function.
- Call `onInteractionAction({ type: 'submit', payload })` when the user approves; `'skip'` if they skip with a reason; `'cancel'` if they cancel the whole turn.
- Add a corresponding `interventionAudit.ts` in the package root if the tool needs scope/path validation before approval (see `local-system/src/interventionAudit.ts`).
## Intervention registry — `client/Intervention/index.ts`
```ts
import { LocalSystemApiName } from '../..';
import EditLocalFile from './EditLocalFile';
import RunCommand from './RunCommand';
import WriteFile from './WriteFile';
/* … */
export const LocalSystemInterventions = {
[LocalSystemApiName.editLocalFile]: EditLocalFile,
[LocalSystemApiName.runCommand]: RunCommand,
[LocalSystemApiName.writeLocalFile]: WriteFile,
/* one entry per API that needs approval */
};
```
@@ -1,93 +0,0 @@
# Placeholder — Skeleton Between Args and Result (optional)
**Lifecycle:** rendered when the args have finished streaming but the executor hasn't returned yet. Disappears when `pluginState` arrives. Bridges the moment of perceived lag.
**Add for** APIs with noticeable execution time: web search, network crawl, file list, large grep. **Skip for** instant ops (status flips, calculator).
## Props (`BuiltinPlaceholderProps<Args>`)
```ts
interface BuiltinPlaceholderProps<T extends Record<string, any> = any> {
apiName: string;
args?: T;
identifier: string;
}
```
No `pluginState` — Placeholder lives entirely in the "executing" gap.
## Canonical example — Search Placeholder
`packages/builtin-tool-web-browsing/src/client/Placeholder/Search.tsx`:
```tsx
import type { BuiltinPlaceholderProps, SearchQuery } from '@lobechat/types';
import { Flexbox, Icon, Skeleton } from '@lobehub/ui';
import { createStaticStyles, cx } from 'antd-style';
import { SearchIcon } from 'lucide-react';
import { memo } from 'react';
import { useIsMobile } from '@/hooks/useIsMobile';
import { shinyTextStyles } from '@/styles';
const styles = createStaticStyles(({ css, cssVar }) => ({
query: cx(
css`
padding: 4px 8px;
border-radius: 8px;
font-size: 12px;
color: ${cssVar.colorTextSecondary};
&:hover {
background: ${cssVar.colorFillTertiary};
}
`,
shinyTextStyles.shinyText,
),
}));
export const Search = memo<BuiltinPlaceholderProps<SearchQuery>>(({ args }) => {
const { query } = args || {};
const isMobile = useIsMobile();
return (
<Flexbox gap={8}>
<Flexbox horizontal={!isMobile} gap={isMobile ? 8 : 40}>
<Flexbox horizontal align="center" className={styles.query} gap={8}>
<Icon icon={SearchIcon} />
{query ? query : <Skeleton.Block active style={{ height: 20, width: 40 }} />}
</Flexbox>
<Skeleton.Block active style={{ height: 20, width: 40 }} />
</Flexbox>
<Flexbox horizontal gap={12}>
{[1, 2, 3, 4, 5].map((id) => (
<Skeleton.Button active key={id} style={{ borderRadius: 8, height: 80, width: 160 }} />
))}
</Flexbox>
</Flexbox>
);
});
```
## Placeholder rules
- **Mirror the eventual Render's layout.** When the result arrives the Placeholder unmounts and the Render mounts; if they share dimensions, the chat doesn't jump.
- Use `Skeleton.Block` / `Skeleton.Button` from `@lobehub/ui` for placeholder shapes.
- Embed any args you have (e.g. the query text) — context helps the user know what's loading.
- Pulse with `shinyTextStyles.shinyText` if the Placeholder includes literal text.
## Placeholder registry — `client/Placeholder/index.ts`
```ts
import { WebBrowsingApiName } from '../../types';
import CrawlMultiPages from './CrawlMultiPages';
import CrawlSinglePage from './CrawlSinglePage';
import { Search } from './Search';
export const WebBrowsingPlaceholders = {
[WebBrowsingApiName.crawlMultiPages]: CrawlMultiPages,
[WebBrowsingApiName.crawlSinglePage]: CrawlSinglePage,
[WebBrowsingApiName.search]: Search,
};
export { CrawlMultiPages, CrawlSinglePage, Search };
```
@@ -1,71 +0,0 @@
# Portal — Full-Screen Detail View (optional)
**Lifecycle:** rendered when the user opens the tool message in a side panel or full-screen modal. One Portal per **tool**, not per API — the Portal switches on `apiName` internally.
**Add for** tools whose results deserve a deep-dive view: search results with editable filters, page content with reader mode, code interpreter sessions.
## Props (`BuiltinPortalProps<Args, State>`)
```ts
interface BuiltinPortalProps<Arguments = Record<string, any>, State = any> {
apiName?: string;
arguments: Arguments;
identifier: string;
messageId: string;
state: State;
}
```
## Canonical example — Web-Browsing Portal
`packages/builtin-tool-web-browsing/src/client/Portal/index.tsx`:
```tsx
import type { BuiltinPortalProps, CrawlPluginState, SearchQuery } from '@lobechat/types';
import { memo } from 'react';
import { WebBrowsingApiName } from '../../types';
import PageContent from './PageContent';
import PageContents from './PageContents';
import Search from './Search';
const Portal = memo<BuiltinPortalProps>(({ arguments: args, messageId, state, apiName }) => {
switch (apiName) {
case WebBrowsingApiName.search:
return <Search messageId={messageId} query={args as SearchQuery} response={state} />;
case WebBrowsingApiName.crawlSinglePage: {
const result = (state as CrawlPluginState).results.find((r) => r.originalUrl === args.url);
return <PageContent messageId={messageId} result={result} />;
}
case WebBrowsingApiName.crawlMultiPages:
return (
<PageContents
messageId={messageId}
results={(state as CrawlPluginState).results}
urls={args.urls}
/>
);
}
return null;
});
export default Portal;
```
## Portal rules
- One Portal per tool — the file is the routing layer, subcomponents implement each API's view.
- Portals can read the chat store directly to detect "still streaming" and render a Skeleton internally (see `Search/index.tsx:20-46`).
- Layout assumes more space than the Render — use `Flexbox` with `height={'100%'}` and structure for a side panel viewport.
## Portal registry — `packages/builtin-tools/src/portals.ts`
```ts
import { WebBrowsingManifest, WebBrowsingPortal } from '@lobechat/builtin-tool-web-browsing/client';
import { type BuiltinPortal } from '@lobechat/types';
export const BuiltinToolsPortals: Record<string, BuiltinPortal> = {
[WebBrowsingManifest.identifier]: WebBrowsingPortal as BuiltinPortal,
};
```
@@ -1,19 +0,0 @@
# Tool Render 设计原则(中文草案)
这些原则用于判断一个 builtin tool 的 Inspector / Render / Placeholder / Streaming / Intervention / Portal 应该做什么,以及做到什么程度。
1. **先保证折叠态可读。** 每个 API 都必须有 Inspector;用户不展开也应该能看懂 “正在做什么 / 对什么做 / 当前结果是什么”。Inspector 不应该只展示函数名和原始参数。
2. **Inspector 是一句话,不是详情页。** 优先表达动作、关键对象、数量、状态,例如 “分析图片 3 张”“搜索 12 个结果”“读取 config.json”。长文本、列表和结构化结果放到 Render 或 Portal。
3. **Inspector 要覆盖执行生命周期。** `args` 还在 streaming、工具执行中、执行完成、执行失败时都应该有稳定展示;必要时同时读取 `args``partialArgs``pluginState`,避免出现空白、跳变或只显示半截参数。
4. **文案要随状态切换时态。** 同一个动作在 loading 与 completed 两个阶段必须用不同的措辞:执行中用现在进行时(“正在创建任务 / Creating task / 正在搜索”),执行完成后切到完成态(“已创建任务 / Task created / 已找到 N 条”)。Inspector chip 会一直留在聊天记录里 —— 如果一直挂着 “正在 xxx”,几小时后回看历史时会读起来像还在跑。约定的 i18n 形式是 `<api>.loading` / `<api>.completed` 一对键(见 `lobe-agent.apiName.callSubAgent.{loading,completed}``lobe-claude-code.task.{create,list,update,get}.{loading,completed}`),渲染时按 `isArgumentsStreaming || isLoading` 决定取哪一个。只读 / 查询类(“查看任务” 这种本来就是名词性的)可以共用一个键。
5. **只有结构化结果才需要 Render。** 如果工具结果只是自然语言总结,通常不需要 Render;如果结果包含列表、媒体、文件、表格、代码、diff、地图、时间线、权限请求等结构,就应该提供 Render。
6. **Render 要帮助用户检查结果,而不是复述参数。** Render 的主体应该围绕工具产物组织:可预览、可比较、可筛选、可定位。参数只作为上下文辅助出现,不要把 Render 做成一块更大的 args dump。
7. **参数和结果要一起参与渲染。** 好的 Tool UI 通常同时用 `args` 解释意图,用 `pluginState` 展示真实执行结果;但 `pluginState` 只放结果域数据,不要反向塞入可以从 `args` 推导出的内容。
8. **慢操作要有 Placeholder。** 如果工具通常需要等待网络、文件系统、模型或外部进程,Placeholder 应该先占住最终 Render 的版式,让用户知道即将看到什么,而不是只显示一个泛化 loading。
9. **Streaming 只用于连续产物。** 搜索列表、日志、长文本、文件分析、分阶段计划适合 Streaming;一次性小结果不需要强行做 Streaming。Streaming UI 要能渐进追加,并且完成后自然过渡到最终 Render。
10. **有风险的动作必须 Intervention。** 写文件、删除、发送、安装、执行命令、外部可见操作、权限敏感操作,都应该在执行前给出可理解的确认界面;确认文案要说明影响范围,而不是只问 “是否继续”。
11. **错误、空态和截断都是正式状态。** Render 不能在失败、无结果、超长结果时退化成空白。错误要说明发生在哪一步;空态要告诉用户没有产物;超长内容要明确 “展示前 N 项 / 还有 N 项”。
12. **信息密度要克制。** 默认展示最有判断价值的部分:标题、来源、状态、摘要、少量关键字段。大对象、长列表、原文、调试数据放进可展开区域或 Portal,避免把聊天流撑成后台管理页。
13. **视觉上融入聊天流。** Tool UI 应该使用 `@lobehub/ui` / base-ui、`Flexbox``createStaticStyles``cssVar.*`,遵循现有间距、圆角、颜色、字号;不要为单个工具发明一套独立视觉语言。具体的样式约定见 [shared-rules.md](shared-rules.md)。
14. **Devtools fixture 是验收入口。** 新增或修改 Tool UI 时,应在 `/devtools` 里准备覆盖典型态、loading/streaming、空态、错误态、长内容态的 fixture;一个 API 如果在真实聊天里会出现,就不应该在 devtools 中缺席。
15. **先做用户会看的 UI,再做调试 UI。** Raw JSON、trace、schema、内部 id 可以存在,但应默认收起或放到调试区;主界面先回答用户最关心的问题:工具做了什么,结果值不值得信任,下一步能做什么。
@@ -1,101 +0,0 @@
# Render — Rich Result Card (optional)
**Lifecycle:** rendered **once the result arrives** (after Placeholder/Streaming hand off). Sits below the Inspector header.
**Skip if** the API is read-only or the result is just text — the framework already shows the executor's `content` string. Add a Render only when there's a structured artifact worth seeing: a card, a chart, a diff, a list of files.
## Props (`BuiltinRenderProps<Args, State, Content>`)
```ts
interface BuiltinRenderProps<Arguments = any, State = any, Content = any> {
apiName?: string;
args: Arguments; // final params from the LLM
content: Content; // executor's content string (or parsed)
identifier?: string;
messageId: string; // for store lookups
pluginError?: any; // from BuiltinToolResult.error
pluginState?: State; // executor's state
toolCallId?: string;
}
```
## Two patterns
**Pattern A — Single-file Render** (web-browsing CrawlSinglePage):
```tsx
// client/Render/CrawlSinglePage.tsx
import type { BuiltinRenderProps, CrawlPluginState, CrawlSinglePageQuery } from '@lobechat/types';
import { memo } from 'react';
import PageContent from './PageContent';
const CrawlSinglePage = memo<BuiltinRenderProps<CrawlSinglePageQuery, CrawlPluginState>>(
({ messageId, pluginState, args }) => (
<PageContent messageId={messageId} results={pluginState?.results} urls={[args?.url]} />
),
);
export default CrawlSinglePage;
```
**Pattern B — Folder with subcomponents** (web-browsing Search):
```
client/Render/Search/
├── index.tsx # composes the subcomponents, handles error states
├── ConfigForm.tsx # appears when pluginError.type === 'PluginSettingsInvalid'
├── SearchQuery.tsx # editable query header
└── SearchResult.tsx # result list
```
Use Pattern B when the Render has internal state (editing mode, expanded items), error variants, or is large enough to benefit from splitting.
## Error handling in Render
Renders are the canonical place to surface `pluginError` because the chat doesn't auto-render typed errors:
```tsx
if (pluginError) {
if (pluginError?.type === 'PluginSettingsInvalid') {
return <ConfigForm id={messageId} provider={pluginError.body?.provider} />;
}
return (
<Alert
title={pluginError?.message}
type="error"
extra={<Highlighter language="json">{JSON.stringify(pluginError.body, null, 2)}</Highlighter>}
/>
);
}
```
## Render rules
- **Return `null`** if there's nothing useful to draw yet (avoids empty cards during stream).
- Use `pluginState` for server-truth (ids, counts, server-assigned status) and `args` for what the LLM asked. **Combine — neither alone is enough.**
- For lists, summarize with a header line and show top N items with a "+N more" tail rather than rendering everything.
- **Keep the Render single-layer** — the tool card is already your surface, so don't open with your own filled container and then nest more filled boxes inside it. See [shared-rules.md](shared-rules.md) → "Stay single-layer".
- For modals from a Render, use `@lobehub/ui/base-ui` (`createModal`, `useModalContext`, `confirmModal`) — see the **modal** skill.
## Render registry — `client/Render/index.ts`
```ts
import type { BuiltinRender } from '@lobechat/types';
import { TaskApiName } from '../../types';
import CreateTaskRender from './CreateTask';
import RunTasksRender from './RunTasks';
export const TaskRenders: Record<string, BuiltinRender> = {
[TaskApiName.createTask]: CreateTaskRender as BuiltinRender,
[TaskApiName.runTasks]: RunTasksRender as BuiltinRender,
/* only the APIs with rich result UI — others fall back to text content */
};
export { default as CreateTaskRender } from './CreateTask';
export { default as RunTasksRender } from './RunTasks';
```
## Render display control (rare)
If the Render should hide for certain results (e.g. ClaudeCode's TodoWrite hides when the agent is mid-stream), add a `RenderDisplayControl` to `packages/builtin-tools/src/displayControls.ts`. See `ClaudeCodeRenderDisplayControls` for the pattern.
@@ -1,89 +0,0 @@
# Shared Style Rules
These apply across every surface.
## The component skeleton
Every surface file is the same shape, so internalize it once instead of re-deriving it per rule. The skeleton below bakes in five mechanical conventions — copy it and fill the body:
```tsx
'use client'; // (a) leaves of the chat tree must not block server rendering
import type { BuiltinInspectorProps, SearchQuery, UniformSearchResponse } from '@lobechat/types';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
// (b) type with BuiltinXProps<Args, State> — never widen to `any`.
// Args = the JSON Schema params, State = the executor's `state` field;
// they should match <Name>Params / <Name>State from types.ts.
export const SearchInspector = memo<BuiltinInspectorProps<SearchQuery, UniformSearchResponse>>(
({ args, pluginState }) => {
const { t } = useTranslation('plugin'); // (c) all strings from the `plugin` namespace
// (d) cross-cutting state (loading, streaming buffer) comes from the store,
// not props — props only carry args/state/messageId.
// const buffer = useChatStore((s) => chatToolSelectors.streamingBuffer(messageId)(s));
return <span>{t('builtins.<identifier>.apiName.search')}</span>;
},
);
SearchInspector.displayName = 'SearchInspector'; // (e) always memo + displayName
export default SearchInspector;
```
- **(c)** Default an Inspector to `t('builtins.<identifier>.apiName.<api>')` so the row is non-empty while args stream in.
- **(d)** Read the store via Zustand selectors inside the component; see [streaming.md](streaming.md) for the buffer selector.
## Styling: `createStaticStyles + cssVar.*`, `@lobehub/ui` over `antd`
Zero-runtime CSS-in-JS — styles compile once and read CSS variables at runtime:
```tsx
import { createStaticStyles, cssVar } from 'antd-style';
const styles = createStaticStyles(({ css, cssVar }) => ({
chip: css`
padding-block: 2px;
padding-inline: 8px;
border-radius: 999px;
color: ${cssVar.colorText};
background: ${cssVar.colorFillTertiary};
`,
}));
```
- Fall back to `createStyles + token` only when you need runtime token computation (rare). Inline `style={{ color: cssVar.colorTextSecondary }}` is fine for one-off dynamic values.
- Components come from `@lobehub/ui` (`Block`, `Text`, `Flexbox`, `Highlighter`, `Alert`, `Tooltip`, `Skeleton`), not raw `antd`. Modals come from `@lobehub/ui/base-ui` (`createModal`, `useModalContext`, `confirmModal`) — see the **modal** skill.
- Note: `<Text type='secondary'>` is a lighter shade than `colorTextSecondary`. For that exact token color, write `<Text style={{ color: cssVar.colorTextSecondary }}>`.
## Stay single-layer — don't nest filled cards
The framework already wraps every Render / Intervention in a tool card, so that card **is** your surface. A Render that opens with its own `background: ${cssVar.colorFillQuaternary}` container is already one card deep; put another filled box inside it (`colorBgContainer` / `colorFillTertiary`) and you get the card-in-card look that reads as "complex" — two or three stacked fills for what is really a flat list of fields.
- **The outermost wrapper carries no fill.** Use a flat container with only `padding-block: 4px` for breathing room; let the tool card provide the card. (See `Agent/index.tsx`'s `container`.)
- **At most one filled box, and only to delineate real content** — a Markdown preview, a diff, a code/result block. Labels, keyvalue fields, question/answer text, chips: render flat on the surface, separated by spacing or a hairline divider (`height: 1px; background: ${cssVar.colorFillSecondary}`), not by wrapping each in its own box.
- **A box on a flat surface needs a visible fill.** Once the outer fill is gone, an inner `colorBgContainer` box can vanish against the tool card (same color). Use `colorFillTertiary` for the one content box so it still reads as delineated.
- Don't wrap a single value in a box just to give it padding — that's the redundant-nesting smell (a `detailCard` around a `value` box around one string).
```tsx
// ❌ card-in-card: filled container wrapping a filled preview box
container: css`
padding: 12px;
background: ${cssVar.colorFillQuaternary};
`,
previewBox: css`
background: ${cssVar.colorBgContainer};
`,
// ✅ single-layer: flat container, one visible content box
container: css`
padding-block: 4px;
`,
previewBox: css`
background: ${cssVar.colorFillTertiary};
`,
```
For the common "icon + file/title header, then one content box" shape, reuse `ToolResultCard` from `@lobechat/shared-tool-ui/components` instead of rebuilding it — it's already single-layer (flat wrapper, one `colorFillTertiary` content box) and is what CC `Read` / `Grep` / `Glob` / `Write` / `WebSearch` / `WebFetch` render through.
The exception is a deliberate **panel** pattern — an `<Block variant="outlined">` with a header bar + list rows (CC `TodoWrite` / `Task`). There the single outlined block is the panel and the header fill is a header bar, not a nested card. One structured panel is fine; stacked decorative fills are not.
@@ -1,83 +0,0 @@
# Streaming — Live Output During Execution (optional)
**Lifecycle:** rendered **while the executor is still running** for APIs that emit incremental output. The component is responsible for fetching the in-flight stream from the chat store and rendering it.
**Add for** long-running ops with continuous output: shell command execution (stdout/stderr), file write progress, code interpreter cells.
## Props (`BuiltinStreamingProps<Args>`)
```ts
interface BuiltinStreamingProps<Arguments = any> {
apiName: string;
args: Arguments;
identifier: string;
messageId: string; // use to fetch the streaming buffer from store
toolCallId: string;
}
```
Note there's **no `state` or `result` prop** — the Streaming component is for the in-flight phase. It pulls the live buffer from the store itself (typically via `chatToolSelectors.streamingContent(messageId)` or similar).
## Canonical example — RunCommandStreaming
`packages/builtin-tool-local-system/src/client/Streaming/RunCommand/index.tsx`:
```tsx
'use client';
import type { BuiltinStreamingProps } from '@lobechat/types';
import { Highlighter } from '@lobehub/ui';
import { memo } from 'react';
interface RunCommandParams {
command?: string;
description?: string;
timeout?: number;
}
export const RunCommandStreaming = memo<BuiltinStreamingProps<RunCommandParams>>(({ args }) => {
const { command } = args || {};
if (!command) return null;
return (
<Highlighter
animated
wrap
language="sh"
showLanguage={false}
style={{ padding: '4px 8px' }}
variant="outlined"
>
{command}
</Highlighter>
);
});
RunCommandStreaming.displayName = 'RunCommandStreaming';
```
For real-time output beyond just the command (stderr/stdout streaming), pull from the chat store:
```tsx
const buffer = useChatStore((state) =>
chatToolSelectors.streamingBuffer(messageId, toolCallId)(state),
);
```
## Streaming rules
- Render `null` until you have something to display (avoids flash).
- For terminal-style output, use `Highlighter` with `animated` to show typing-like effect.
- The Streaming component must **unmount cleanly** when execution ends — typically the framework swaps it out for the Render automatically.
## Streaming registry — `client/Streaming/index.ts`
```ts
import { LocalSystemApiName } from '../..';
import { RunCommandStreaming } from './RunCommand';
import { WriteFileStreaming } from './WriteFile';
export const LocalSystemStreamings = {
[LocalSystemApiName.runCommand]: RunCommandStreaming,
[LocalSystemApiName.writeLocalFile]: WriteFileStreaming,
};
```
-147
View File
@@ -1,147 +0,0 @@
---
name: chat-sdk
description: 'Build multi-platform chat bots with the chat SDK. Use for Slack, Teams, Google Chat, Discord, GitHub, Linear bots, webhooks, mentions, slash commands, cards, modals, or streaming responses.'
user-invocable: false
---
# Chat SDK
Unified TypeScript SDK for building chat bots across Slack, Teams, Google Chat, Discord, GitHub, and Linear. Write bot logic once, deploy everywhere.
## Critical: Read the bundled docs
The `chat` package ships with full documentation in `node_modules/chat/docs/` and TypeScript source types. **Always read these before writing code:**
```
node_modules/chat/docs/ # Full documentation (MDX files)
node_modules/chat/dist/ # Built types (.d.ts files)
```
Key docs to read based on task:
- `docs/getting-started.mdx` — setup guides
- `docs/usage.mdx` — event handlers, threads, messages, channels
- `docs/streaming.mdx` — AI streaming with AI SDK
- `docs/cards.mdx` — JSX interactive cards
- `docs/actions.mdx` — button/dropdown handlers
- `docs/modals.mdx` — form dialogs (Slack only)
- `docs/adapters/*.mdx` — platform-specific adapter setup
- `docs/state/*.mdx` — state adapter config (Redis, ioredis, memory)
Also read the TypeScript types from `node_modules/chat/dist/` to understand the full API surface.
## Quick start
```typescript
import { Chat } from 'chat';
import { createSlackAdapter } from '@chat-adapter/slack';
import { createRedisState } from '@chat-adapter/state-redis';
const bot = new Chat({
userName: 'mybot',
adapters: {
slack: createSlackAdapter({
botToken: process.env.SLACK_BOT_TOKEN!,
signingSecret: process.env.SLACK_SIGNING_SECRET!,
}),
},
state: createRedisState({ url: process.env.REDIS_URL! }),
});
bot.onNewMention(async (thread) => {
await thread.subscribe();
await thread.post("Hello! I'm listening to this thread.");
});
bot.onSubscribedMessage(async (thread, message) => {
await thread.post(`You said: ${message.text}`);
});
```
## Core concepts
- **Chat** — main entry point, coordinates adapters and routes events
- **Adapters** — platform-specific (Slack, Teams, GChat, Discord, GitHub, Linear)
- **State** — pluggable persistence (Redis for prod, memory for dev)
- **Thread** — conversation thread with `post()`, `subscribe()`, `startTyping()`
- **Message** — normalized format with `text`, `formatted` (mdast AST), `raw`
- **Channel** — container for threads, supports listing and posting
## Event handlers
| Handler | Trigger |
| -------------------------- | ------------------------------------------------- |
| `onNewMention` | Bot @-mentioned in unsubscribed thread |
| `onSubscribedMessage` | Any message in subscribed thread |
| `onNewMessage(regex)` | Messages matching pattern in unsubscribed threads |
| `onSlashCommand("/cmd")` | Slash command invocations |
| `onReaction(emojis)` | Emoji reactions added/removed |
| `onAction(actionId)` | Button clicks and dropdown selections |
| `onAssistantThreadStarted` | Slack Assistants API thread opened |
| `onAppHomeOpened` | Slack App Home tab opened |
## Streaming
Pass any `AsyncIterable<string>` to `thread.post()`. Works with AI SDK's `textStream`:
```typescript
import { ToolLoopAgent } from 'ai';
const agent = new ToolLoopAgent({ model: 'anthropic/claude-4.5-sonnet' });
bot.onNewMention(async (thread, message) => {
const result = await agent.stream({ prompt: message.text });
await thread.post(result.textStream);
});
```
## Cards (JSX)
Set `jsxImportSource: "chat"` in tsconfig. Components: `Card`, `CardText`, `Button`, `Actions`, `Fields`, `Field`, `Select`, `SelectOption`, `Image`, `Divider`, `LinkButton`, `Section`, `RadioSelect`.
```tsx
await thread.post(
<Card title="Order #1234">
<CardText>Your order has been received!</CardText>
<Actions>
<Button id="approve" style="primary">
Approve
</Button>
<Button id="reject" style="danger">
Reject
</Button>
</Actions>
</Card>,
);
```
## Packages
| Package | Purpose |
| ----------------------------- | ----------------------------- |
| `chat` | Core SDK |
| `@chat-adapter/slack` | Slack |
| `@chat-adapter/teams` | Microsoft Teams |
| `@chat-adapter/gchat` | Google Chat |
| `@chat-adapter/discord` | Discord |
| `@chat-adapter/github` | GitHub Issues |
| `@chat-adapter/linear` | Linear Issues |
| `@chat-adapter/state-redis` | Redis state (production) |
| `@chat-adapter/state-ioredis` | ioredis state (alternative) |
| `@chat-adapter/state-memory` | In-memory state (development) |
## Changesets (Release Flow)
This monorepo uses [Changesets](https://github.com/changesets/changesets) for versioning and changelogs. Every PR that changes a package's behavior must include a changeset.
```bash
pnpm changeset
# → select affected package(s) (e.g. @chat-adapter/slack, chat)
# → choose bump type: patch (fixes), minor (features), major (breaking)
# → write a short summary for the CHANGELOG
```
This creates a file in `.changeset/` — commit it with the PR. When merged to `main`, the Changesets GitHub Action opens a "Version Packages" PR to bump versions and update CHANGELOGs. Merging that PR publishes to npm.
## Webhook setup
Each adapter exposes a webhook handler via `bot.webhooks.{platform}`. Wire these to your HTTP framework's routes (e.g. Next.js API routes, Hono, Express).
-171
View File
@@ -1,171 +0,0 @@
---
name: cli-backend-testing
description: >
CLI + Backend integration testing workflow. Use when verifying backend API changes
(TRPC routers, services, models) via the LobeHub CLI against a local dev server.
Triggers on 'cli test', 'test with cli', 'verify with cli', 'local cli test',
'backend test with cli', or when needing to validate server-side changes end-to-end.
---
# CLI + Backend Integration Testing
Standard workflow for verifying backend changes using the LobeHub CLI (`lh`) against a local dev server.
## When to Use
- Verifying TRPC router / service / model changes end-to-end
- Testing new API fields or response structure changes
- Validating CLI command output after backend modifications
- Debugging data flow issues between server and CLI
## Prerequisites
| Requirement | Details |
| ------------ | ------------------------------------------------------------- |
| Dev server | `localhost:3011` (Next.js) |
| CLI source | `lobehub/apps/cli/` |
| CLI dev mode | Uses `LOBEHUB_CLI_HOME=.lobehub-dev` for isolated credentials |
| Auth | Device Code Flow login to local server |
## Quick Reference
All CLI dev commands run from `lobehub/apps/cli/`. Subsequent examples use `$CLI`:
```bash
CLI="LOBEHUB_CLI_HOME=.lobehub-dev bun src/index.ts"
```
## Workflow
### Step 1: Ensure Dev Server is Running
```bash
curl -s -o /dev/null -w '%{http_code}' http://localhost:3011/ 2> /dev/null
```
- **If reachable**: skip to Step 2.
- **If unreachable**: start from cloud repo root:
```bash
pnpm run dev:next
```
To **restart** (pick up server-side code changes):
```bash
lsof -ti:3011 | xargs kill
pnpm run dev:next
```
**Important:** Server-side code changes in the submodule (`lobehub/src/server/`, `lobehub/packages/`) require a server restart. Next.js hot-reload may not pick up changes in submodule packages.
### Step 2: Check CLI Authentication
```bash
cat lobehub/apps/cli/.lobehub-dev/settings.json 2> /dev/null
```
- **If file exists and contains `"serverUrl": "http://localhost:3011"`**: skip to Step 3.
- **If missing or wrong server**: ask the user to run:
```bash
! cd lobehub/apps/cli && LOBEHUB_CLI_HOME=.lobehub-dev bun src/index.ts login --server http://localhost:3011
```
> Login requires interactive browser authorization (OIDC Device Code Flow), so the user must run it themselves via `!` prefix. Credentials persist in `lobehub/apps/cli/.lobehub-dev/`.
### Step 3: Test with CLI Commands
CLI runs from source, so CLI-side code changes take effect immediately without rebuilding.
```bash
cd lobehub/apps/cli
$CLI <command>
```
### Step 4: Clean Up Test Data
```bash
$CLI task delete < id > -y
$CLI agent delete < id > -y
```
## Common Testing Patterns
### Task System
```bash
$CLI task list
$CLI task create -n "Root Task" -i "Test instruction"
$CLI task create -n "Child Task" -i "Sub instruction" --parent T-1
$CLI task view T-1
$CLI task tree T-1
$CLI task edit T-1 --status running
$CLI task comment T-1 -m "Test comment"
$CLI task delete T-1 -y
```
### Agent System
```bash
$CLI agent list
$CLI agent view <agent-id>
$CLI agent run <agent-id> -m "Test prompt"
```
### Document & Knowledge Base
```bash
$CLI doc list
$CLI doc create -t "Test Doc" -c "Content here"
$CLI doc view <doc-id>
$CLI kb list
$CLI kb tree <kb-id>
```
### Model & Provider
```bash
$CLI model list
$CLI provider list
$CLI provider test <provider-id>
```
## Dev-Test Cycle
```
1. Make code changes (service/model/router/type)
|
2. Run unit tests (fast feedback)
bunx vitest run --silent='passed-only' '<test-file>'
|
3. Restart dev server (if server-side changes)
lsof -ti:3011 | xargs kill && pnpm run dev:next
|
4. CLI verification (end-to-end)
$CLI <command>
|
5. Clean up test data
```
### When Server Restart is Needed
| Change Location | Restart? |
| ----------------------------------------- | -------- |
| `lobehub/src/server/` (routers, services) | Yes |
| `lobehub/packages/database/` (models) | Yes |
| `lobehub/packages/types/` | Yes |
| `lobehub/packages/prompts/` | Yes |
| `lobehub/apps/cli/` (CLI code) | No |
| `src/` (cloud overrides) | Yes |
## Troubleshooting
| Issue | Solution |
| --------------------------- | --------------------------------------------------------------------- |
| `No authentication found` | Run `login --server http://localhost:3011` |
| `UNAUTHORIZED` on API calls | Token expired; re-run login |
| `ECONNREFUSED` | Dev server not running; start with `pnpm run dev:next` |
| CLI shows old data/behavior | Server needs restart to pick up code changes |
| `EADDRINUSE` on port 3011 | Server already running; kill with `lsof -ti:3011 \| xargs kill` |
| Login opens wrong server | Must use `--server http://localhost:3011` flag (env var doesn't work) |
-296
View File
@@ -1,296 +0,0 @@
---
name: cli
description: LobeHub CLI (@lobehub/cli) development guide — commands, subcommands, architecture.
disable-model-invocation: true
---
# LobeHub CLI Development Guide
## Overview
LobeHub CLI (`@lobehub/cli`) is a command-line tool for managing and interacting with LobeHub services. Built with Commander.js + TypeScript.
- **Package**: `apps/cli/`
- **Entry**: `apps/cli/src/index.ts`
- **Binaries**: `lh`, `lobe`, `lobehub` (all aliases for the same CLI)
- **Build**: tsup
- **Runtime**: Node.js / Bun
## Architecture
```
apps/cli/src/
├── index.ts # Entry point, registers all commands
├── api/
│ ├── client.ts # tRPC client (type-safe backend API)
│ └── http.ts # Raw HTTP utilities
├── auth/
│ ├── credentials.ts # Encrypted credential storage (AES-256-GCM)
│ ├── refresh.ts # Token auto-refresh
│ └── resolveToken.ts # Token resolution (flag > stored)
├── commands/ # All CLI commands (one file per command group)
│ ├── agent.ts # Agent CRUD + run
│ ├── config.ts # whoami, usage
│ ├── connect.ts # Device gateway connection + daemon
│ ├── doc.ts # Document management
│ ├── file.ts # File management
│ ├── generate/ # Content generation (text/image/video/tts/asr)
│ ├── kb.ts # Knowledge base management
│ ├── login.ts # OIDC Device Code Flow auth
│ ├── logout.ts # Clear credentials
│ ├── memory.ts # User memory management
│ ├── message.ts # Message management
│ ├── model.ts # AI model management
│ ├── plugin.ts # Plugin management
│ ├── provider.ts # AI provider management
│ ├── search.ts # Global search
│ ├── skill.ts # Agent skill management
│ ├── status.ts # Gateway connectivity check
│ └── topic.ts # Conversation topic management
├── daemon/
│ └── manager.ts # Background daemon process management
├── tools/
│ ├── shell.ts # Shell command execution (for gateway)
│ └── file.ts # File operations (for gateway)
├── settings/
│ └── index.ts # Persistent settings (~/.lobehub/)
├── utils/
│ ├── logger.ts # Logging (verbose mode)
│ ├── format.ts # Table output, JSON, timeAgo, truncate
│ └── agentStream.ts # SSE streaming for agent runs
└── constants/
└── urls.ts # Official server & gateway URLs
```
## Command Groups
| Command | Alias | Description |
| ------------- | ----- | ----------------------------------------------------------- |
| `lh login` | - | Authenticate via OIDC Device Code Flow |
| `lh logout` | - | Clear stored credentials |
| `lh connect` | - | Device gateway connection & daemon management |
| `lh status` | - | Quick gateway connectivity check |
| `lh agent` | - | Agent CRUD, run, status |
| `lh generate` | `gen` | Content generation (text, image, video, tts, asr, download) |
| `lh doc` | - | Document CRUD, batch-create, parse, topic linking |
| `lh file` | - | File list, view, delete, recent |
| `lh kb` | - | Knowledge base CRUD, folders, docs, upload, tree view |
| `lh memory` | - | User memory CRUD + extraction |
| `lh message` | - | Message list, search, delete, count, heatmap |
| `lh topic` | - | Topic CRUD + search + recent |
| `lh skill` | - | Skill CRUD + import (GitHub/URL/market) |
| `lh model` | - | Model CRUD, toggle, batch-toggle, clear |
| `lh provider` | - | Provider CRUD, config, test, toggle |
| `lh plugin` | - | Plugin install, uninstall, update |
| `lh search` | - | Global search across all types |
| `lh whoami` | - | Current user info |
| `lh usage` | - | Monthly/daily usage statistics |
## Adding a New Command
### 1. Create Command File
Create `apps/cli/src/commands/<name>.ts`:
```typescript
import type { Command } from 'commander';
import { getTrpcClient } from '../api/client';
import { outputJson, printTable, truncate } from '../utils/format';
export function register<Name>Command(program: Command) {
const cmd = program.command('<name>').description('...');
// Subcommands
cmd
.command('list')
.description('List items')
.option('-L, --limit <n>', 'Maximum number of items', '30')
.option('--json [fields]', 'Output JSON, optionally specify fields')
.action(async (options) => {
const client = await getTrpcClient();
const result = await client.<router>.<procedure>.query({ ... });
// Handle output
});
}
```
### 2. Register in Entry Point
In `apps/cli/src/index.ts`:
```typescript
import { registerNewCommand } from './commands/new';
// ...
registerNewCommand(program);
```
### 3. Add Tests
Create `apps/cli/src/commands/<name>.test.ts` alongside the command file.
## Conventions
### Output Patterns
All list/view commands follow consistent patterns:
- `--json [fields]` - JSON output with optional field filtering
- `--yes` - Skip confirmation for destructive ops
- `-L, --limit <n>` - Pagination limit (default: 30)
- `-v, --verbose` - Verbose logging
### Table Output
```typescript
const rows = items.map((item) => [item.id, truncate(item.title, 40), timeAgo(item.updatedAt)]);
printTable(rows, ['ID', 'TITLE', 'UPDATED']);
```
### JSON Output
```typescript
if (options.json !== undefined) {
const fields = typeof options.json === 'string' ? options.json : undefined;
outputJson(items, fields);
return;
}
```
### Authentication
Commands that need auth use `getTrpcClient()` which auto-resolves tokens:
```typescript
const client = await getTrpcClient();
// client.router.procedure.query/mutate(...)
```
### Confirmation Prompts
```typescript
import { confirm } from '../utils/format';
if (!options.yes) {
const ok = await confirm('Are you sure?');
if (!ok) return;
}
```
## Storage Locations
| File | Path | Purpose |
| ------------- | ----------------------------- | ------------------------------ |
| Credentials | `~/.lobehub/credentials.json` | Encrypted tokens (AES-256-GCM) |
| Settings | `~/.lobehub/settings.json` | Custom server/gateway URLs |
| Daemon PID | `~/.lobehub/daemon.pid` | Background process PID |
| Daemon Status | `~/.lobehub/daemon.status` | Connection status JSON |
| Daemon Log | `~/.lobehub/daemon.log` | Daemon output log |
The base directory (`~/.lobehub/`) can be overridden with the `LOBEHUB_CLI_HOME` env var (e.g. `LOBEHUB_CLI_HOME=.lobehub-dev` for dev mode isolation).
## Key Dependencies
- `commander` - CLI framework
- `@trpc/client` + `superjson` - Type-safe API client
- `@lobechat/device-gateway-client` - WebSocket gateway connection
- `@lobechat/local-file-shell` - Local shell/file tool execution
- `picocolors` - Terminal colors
- `ws` - WebSocket
- `diff` - Text diffing
- `fast-glob` - File pattern matching
## Development
### Running in Dev Mode
Dev mode uses `LOBEHUB_CLI_HOME=.lobehub-dev` to isolate credentials from the global `~/.lobehub/` directory, so dev and production configs never conflict.
```bash
# Run a command in dev mode (from apps/cli/)
cd apps/cli && bun run dev -- <command>
# This is equivalent to:
LOBEHUB_CLI_HOME=.lobehub-dev bun src/index.ts <command>
```
### Connecting to Local Dev Server
To test CLI against a local dev server (e.g. `localhost:3011`):
**Step 1: Start the local server**
```bash
# From cloud repo root
bun run dev
# Server starts on http://localhost:3011 (or configured port)
```
**Step 2: Login to local server via Device Code Flow**
```bash
cd apps/cli && bun run dev -- login --server http://localhost:3011
```
This will:
1. Call `POST http://localhost:3011/oidc/device/auth` to get a device code
2. Print a URL like `http://localhost:3011/oidc/device?user_code=XXXX-YYYY`
3. Open the URL in your browser — log in and authorize
4. Save credentials to `apps/cli/.lobehub-dev/credentials.json`
5. Save server URL to `apps/cli/.lobehub-dev/settings.json`
After login, all subsequent `bun run dev -- <command>` calls will use the local server.
**Step 3: Run commands against local server**
```bash
cd apps/cli && bun run dev -- task list
cd apps/cli && bun run dev -- task create -i "Test task" -n "My Task"
cd apps/cli && bun run dev -- agent list
```
**Troubleshooting:**
- If login returns `invalid_grant`, make sure the local OIDC provider is properly configured (check `OIDC_*` env vars in `.env`)
- If you get `UNAUTHORIZED` on API calls, your token may have expired — run `bun run dev -- login --server http://localhost:3011` again
- Dev credentials are stored in `apps/cli/.lobehub-dev/` (gitignored), not in `~/.lobehub/`
### Switching Between Local and Production
```bash
# Dev mode (local server) — uses .lobehub-dev/
cd apps/cli && bun run dev -- <command>
# Production (app.lobehub.com) — uses ~/.lobehub/
lh <command>
```
The two environments are completely isolated by different credential directories.
### Build & Test
```bash
# Build CLI
cd apps/cli && bun run build
# Unit tests
cd apps/cli && bun run test
# E2E tests (requires authenticated CLI)
cd apps/cli && bunx vitest run e2e/kb.e2e.test.ts
# Link globally for testing (installs lh/lobe/lobehub commands)
cd apps/cli && bun run cli:link
```
## Detailed Command References
See `references/` for each command group:
- **Agent**: `references/agent.md` (CRUD, run, status)
- **Content Generation**: `references/generate.md` (text, image, video, tts, asr, download)
- **Knowledge & Files**: `references/knowledge.md` (kb, file, doc)
- **Conversation**: `references/conversation.md` (topic, message)
- **Memory**: `references/memory.md` (memory management, extraction)
- **Skills & Plugins**: `references/skills-plugins.md` (skill, plugin)
- **Models & Providers**: `references/models-providers.md` (model, provider)
- **Search & Config**: `references/search-config.md` (search, whoami, usage)
-144
View File
@@ -1,144 +0,0 @@
# Agent Commands
Manage AI agents: create, edit, delete, list, run, and check status.
**Source**: `apps/cli/src/commands/agent.ts`
## `lh agent list`
List all agents.
```bash
lh agent list [-L [-k [--json [fields]] < n > ] < keyword > ]
```
| Option | Description | Default |
| ------------------------- | -------------------------------------- | ------- |
| `-L, --limit <n>` | Maximum items | `30` |
| `-k, --keyword <keyword>` | Filter by keyword | - |
| `--json [fields]` | JSON output with optional field filter | - |
**Table columns**: ID, TITLE, DESCRIPTION, MODEL
---
## `lh agent view <agentId>`
View agent configuration details.
```bash
lh agent view [fields]] < agentId > [--json
```
**Displays**: Title, description, model, provider, system role, plugins, tools.
---
## `lh agent create`
Create a new agent.
```bash
lh agent create [options]
```
| Option | Description | Required |
| --------------------------- | -------------- | -------- |
| `-t, --title <title>` | Agent title | No |
| `-d, --description <desc>` | Description | No |
| `-m, --model <model>` | Model ID | No |
| `-p, --provider <provider>` | Provider ID | No |
| `-s, --system-role <role>` | System prompt | No |
| `--group <groupId>` | Agent group ID | No |
**Output**: Created agent ID and session ID.
---
## `lh agent edit <agentId>`
Update an existing agent. Same options as `create`, all optional. Only specified fields are updated.
```bash
lh agent edit [-m [-s ... < agentId > [-t < title > ] < model > ] < role > ]
```
---
## `lh agent delete <agentId>`
Delete an agent.
```bash
lh agent delete < agentId > [--yes]
```
Requires confirmation unless `--yes` is provided.
---
## `lh agent duplicate <agentId>`
Duplicate an existing agent.
```bash
lh agent duplicate < agentId > [-t < title > ]
```
| Option | Description |
| --------------------- | ------------------------------------ |
| `-t, --title <title>` | Optional new title for the duplicate |
**Output**: New agent ID.
---
## `lh agent run`
Start an agent execution (streaming SSE).
```bash
lh agent run [options]
```
| Option | Description |
| --------------------- | -------------------------------------------- |
| `-a, --agent-id <id>` | Agent ID to run |
| `-s, --slug <slug>` | Agent slug (alternative to ID) |
| `-p, --prompt <text>` | User prompt |
| `-t, --topic-id <id>` | Reuse existing topic |
| `--no-auto-start` | Don't auto-start the agent |
| `--json` | Output full JSON event stream |
| `-v, --verbose` | Show detailed tool call info |
| `--replay <file>` | Replay events from saved JSON file (offline) |
### Streaming Behavior
Uses `utils/agentStream.ts` to handle Server-Sent Events:
1. Sends agent run request to backend
2. Streams SSE events in real-time
3. Displays: text chunks, tool call status, operation progress
4. Shows final token usage and cost summary
### Replay Mode
`--replay <file>` reads a saved JSON event stream for offline debugging without server connection.
---
## `lh agent status <operationId>`
Check agent operation status.
```bash
lh agent status [fields]] [--history] [--history-limit < operationId > [--json < n > ]
```
| Option | Description | Default |
| --------------------- | -------------------- | ------- |
| `--json [fields]` | JSON output | - |
| `--history` | Include step history | `false` |
| `--history-limit <n>` | Max history entries | `10` |
**Displays**: Status (running/completed/failed), steps count, tokens used, cost, error info, timestamps.
@@ -1,122 +0,0 @@
# Conversation Commands (Topic & Message)
## Topic Management (`lh topic`)
Manage conversation topics (threads).
**Source**: `apps/cli/src/commands/topic.ts`
### `lh topic list`
```bash
lh topic list [--agent-id [-L [--page [--json [fields]] < id > ] < n > ] < n > ]
```
| Option | Description | Default |
| ----------------- | --------------- | ------- |
| `--agent-id <id>` | Filter by agent | - |
| `-L, --limit <n>` | Page size | `30` |
| `--page <n>` | Page number | `1` |
**Table columns**: ID, TITLE, FAV, UPDATED
### `lh topic search <keywords>`
```bash
lh topic search [--json [fields]] < keywords > [--agent-id < id > ]
```
### `lh topic create`
```bash
lh topic create -t [--favorite] < title > [--agent-id < id > ]
```
| Option | Description | Required |
| --------------------- | -------------------- | -------- |
| `-t, --title <title>` | Topic title | Yes |
| `--agent-id <id>` | Associate with agent | No |
| `--favorite` | Mark as favorite | No |
### `lh topic edit <id>`
```bash
lh topic edit [--favorite] [--no-favorite] < id > [-t < title > ]
```
### `lh topic delete <ids...>`
```bash
lh topic delete [--yes] < id1 > [id2...]
```
### `lh topic recent`
```bash
lh topic recent [-L [--json [fields]] < n > ]
```
| Option | Description | Default |
| ----------------- | --------------- | ------- |
| `-L, --limit <n>` | Number of items | `10` |
---
## Message Management (`lh message`)
Manage chat messages within topics.
**Source**: `apps/cli/src/commands/message.ts`
### `lh message list`
```bash
lh message list [options] [--json [fields]]
```
| Option | Description | Default |
| ----------------- | ----------------------- | ------- |
| `--topic-id <id>` | Filter by topic | - |
| `--agent-id <id>` | Filter by agent | - |
| `-L, --limit <n>` | Page size | `30` |
| `--page <n>` | Page number | `1` |
| `--user` | Only show user messages | - |
**Table columns**: ID, ROLE, CONTENT, CREATED
**Note**: When `--topic-id` or `--agent-id` is provided, uses `message.getMessages`; otherwise uses `message.listAll`.
### `lh message search <keywords>`
```bash
lh message search [fields]] < keywords > [--json
```
Full-text search across all messages.
### `lh message delete <ids...>`
```bash
lh message delete [--yes] < id1 > [id2...]
```
### `lh message count`
```bash
lh message count [--start [--end [--json] < date > ] < date > ]
```
| Option | Description |
| ---------------- | ------------------------------------------ |
| `--start <date>` | Start date (ISO format, e.g. `2024-01-01`) |
| `--end <date>` | End date (ISO format) |
**Output**: Total message count for the specified period.
### `lh message heatmap`
```bash
lh message heatmap [--json]
```
**Output**: Activity heatmap data showing message frequency over time.
-271
View File
@@ -1,271 +0,0 @@
# Content Generation Commands
Generate text, images, videos, speech, and transcriptions.
**Source**: `apps/cli/src/commands/generate/`
## Command Structure
```
lh generate (alias: gen)
├── text <prompt> # Text generation
├── image <prompt> # Image generation
├── video <prompt> # Video generation
├── tts <text> # Text-to-speech
├── asr <audioFile> # Audio-to-text (speech recognition)
├── download <generationId> <asyncTaskId> # Wait & download generation result
├── status <generationId> <asyncTaskId> # Check async task status
└── list # List generation topics
```
> ⚠️ **Important**: `status` and `download` require an `asyncTaskId` (UUID format, e.g.
> `7ad0eb13-e9a5-4403-8070-1f7fe95b2f95`), **not** the generation ID (`gen_xxx`).
> The asyncTaskId is printed after "→ Task" in the `video` / `image` command output.
---
## `lh generate text <prompt>` / `lh gen text <prompt>`
Generate text completion.
**Source**: `apps/cli/src/commands/generate/text.ts`
```bash
lh gen text "Explain quantum computing" [options]
echo "context" | lh gen text "summarize" --pipe
```
| Option | Description | Default |
| --------------------------- | ---------------------------------- | -------------------- |
| `-m, --model <model>` | Model ID | `openai/gpt-4o-mini` |
| `-p, --provider <provider>` | Provider name | - |
| `-s, --system <prompt>` | System prompt | - |
| `--temperature <n>` | Temperature (0-2) | - |
| `--max-tokens <n>` | Maximum output tokens | - |
| `--stream` | Enable streaming output | `false` |
| `--json` | Output full JSON response | `false` |
| `--pipe` | Read additional context from stdin | `false` |
### Pipe Mode
When `--pipe` is used, reads stdin and prepends it to the prompt. Useful for piping file contents:
```bash
cat README.md | lh gen text "summarize this" --pipe
```
---
## `lh generate image <prompt>` / `lh gen image <prompt>`
Generate images from text prompt. This is an async operation — the command submits the task and returns a generation ID + async task ID for tracking.
**Source**: `apps/cli/src/commands/generate/image.ts`
```bash
lh gen image "A sunset over mountains" [options]
lh gen image "A cute cat" --model dall-e-3 --provider openai --json
```
| Option | Description | Default |
| --------------------------- | ---------------- | ---------- |
| `-m, --model <model>` | Model ID | `dall-e-3` |
| `-p, --provider <provider>` | Provider name | `openai` |
| `-n, --num <n>` | Number of images | `1` |
| `--width <px>` | Width in pixels | - |
| `--height <px>` | Height in pixels | - |
| `--steps <n>` | Number of steps | - |
| `--seed <n>` | Random seed | - |
| `--json` | Output raw JSON | `false` |
**Output** (non-JSON):
```
✓ Image generation started
Batch ID: gb_xxx
1 image(s) queued
Generation gen_xxx → Task 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the asyncTaskId — use this for status/download
Use "lh generate status <generationId> <asyncTaskId>" to check progress.
```
**Typical workflow**:
```bash
# 1. Submit generation — note down BOTH IDs from the output
lh gen image "A cute cat"
# Generation gen_abc123 → Task 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95
# 2. Wait & download using generationId + asyncTaskId (the UUID)
lh gen download gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 -o cat.png
```
---
## `lh generate video <prompt>` / `lh gen video <prompt>`
Generate video from text prompt. This is an async operation.
**Source**: `apps/cli/src/commands/generate/video.ts`
```bash
lh gen video "A cat playing piano" -m <model> -p <provider> [options]
```
| Option | Description | Required |
| --------------------------- | ------------------------ | -------- |
| `-m, --model <model>` | Model ID | Yes |
| `-p, --provider <provider>` | Provider name | Yes |
| `--aspect-ratio <ratio>` | Aspect ratio (e.g. 16:9) | No |
| `--duration <sec>` | Duration in seconds | No |
| `--resolution <res>` | Resolution (e.g. 720p) | No |
| `--seed <n>` | Random seed | No |
| `--json` | Output raw JSON | No |
**Note**: Unlike image, video requires `-m` and `-p` (no defaults). Use `lh model list <provider> --type video` to find available video models.
**Output** (non-JSON):
```
✓ Video generation started
Batch ID: gb_xxx
Generation gen_xxx → Task 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the asyncTaskId — use this for status/download
Use "lh generate status <generationId> <asyncTaskId>" to check progress.
```
**Typical workflow**:
```bash
# 1. Find available video models for a provider
lh model list volcengine --json | grep -i seedance
# 2. Submit generation — note down BOTH IDs from the output
lh gen video "A cat on a runway" -m doubao-seedance-2-0-260128 -p volcengine \
--aspect-ratio 9:16 --duration 5 --resolution 1080p
# Generation gen_abc123 → Task 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95
# 3. Wait & download using generationId + asyncTaskId (the UUID)
lh gen download gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 -o result.mp4 --timeout 600
```
---
## `lh generate tts <text>` / `lh gen tts <text>`
Text-to-speech generation.
**Source**: `apps/cli/src/commands/generate/tts.ts`
```bash
lh gen tts "Hello, world!" [options]
```
---
## `lh generate asr <audioFile>` / `lh gen asr <audioFile>`
Audio-to-text transcription (Automatic Speech Recognition).
**Source**: `apps/cli/src/commands/generate/asr.ts`
```bash
lh gen asr recording.wav [options]
```
---
## `lh generate download <generationId> <asyncTaskId>`
Wait for an async generation task to complete and download the result file.
**Source**: `apps/cli/src/commands/generate/index.ts`
> ⚠️ `<asyncTaskId>` is the UUID printed after "→ Task" in the video/image output.
> Do **not** pass the generation ID (`gen_xxx`) here — that will cause a server error.
```bash
lh gen download <generationId> <asyncTaskId> [-o output.png]
lh gen download gen_xxx 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx -o ~/Desktop/result.mp4 --timeout 600
```
| Option | Description | Default |
| --------------------- | ---------------------------------------- | ---------------------- |
| `-o, --output <path>` | Output file path (auto-detect extension) | `<generationId>.<ext>` |
| `--interval <sec>` | Polling interval in seconds | `5` |
| `--timeout <sec>` | Timeout in seconds (0 = no timeout) | `300` |
**Behavior**:
1. Polls `generation.getGenerationStatus` at the specified interval
2. Shows live progress: `⋯ Status: processing... (42s)`
3. On success: downloads asset URL to local file
4. On error / wrong ID: displays a clear message pointing to the correct ID format
5. On timeout: suggests using `lh gen status` to check later
---
## `lh generate status <generationId> <asyncTaskId>`
Check the status of an async generation task.
> ⚠️ `<asyncTaskId>` is the UUID printed after "→ Task" in the video/image output.
> Do **not** pass the generation ID (`gen_xxx`) here — that will cause a server error.
```bash
lh gen status <generationId> <asyncTaskId> [--json]
lh gen status gen_xxx 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx
```
| Option | Description |
| -------- | ------------------------ |
| `--json` | Output raw JSON response |
**Displays**:
- Status (color-coded): `success` (green), `error` (red), `processing` (yellow), `pending` (cyan)
- Error message (if failed)
- Asset URL and thumbnail URL (if completed)
---
## `lh generate list`
List all generation topics.
```bash
lh gen list [--json [fields]]
```
**Table columns**: ID, TITLE, TYPE, UPDATED
---
## Backend Architecture
Image and video generation use an async task pattern:
1. **Create topic**`generationTopic.createTopic`
2. **Submit generation**`image.createImage` / `video.createVideo`
- Creates batch + generation + asyncTask records in a DB transaction
- Triggers async background task (image via `createAsyncCaller`, video via `initModelRuntimeFromDB`)
- Returns `{ data: { batch, generations }, success }` with `asyncTaskId` in each generation
3. **Poll status**`generation.getGenerationStatus`
- Input: `{ generationId, asyncTaskId }` — both are required, and `asyncTaskId` must be the
UUID from the `async_tasks` table, not `gen_xxx`
- Returns `{ status, error, generation }` (generation includes asset URLs on success)
- Before querying, calls `checkTimeoutTasks` which marks tasks as `error` if they have been
`pending` or `processing` for more than ~5 minutes (`ASYNC_TASK_TIMEOUT = 298s`)
**Server routes**:
- `src/server/routers/lambda/image/index.ts` — image creation (uses `authedProcedure` + `serverDatabase`)
- `src/server/routers/lambda/video/index.ts` — video creation (uses `authedProcedure` + `serverDatabase`)
- `src/server/routers/lambda/generation.ts` — status checking
- `packages/database/src/models/asyncTask.ts``AsyncTaskModel` including `checkTimeoutTasks`
**Note**: Image/video routes do NOT use the `keyVaults` middleware — they read API keys from the database via `initModelRuntimeFromDB` or `createAsyncCaller`.
-281
View File
@@ -1,281 +0,0 @@
# Knowledge Base, File & Document Commands
## Knowledge Base (`lh kb`)
Manage knowledge bases for RAG (Retrieval-Augmented Generation). Supports directory tree structure with folders, documents, and file uploads.
**Source**: `apps/cli/src/commands/kb.ts`
### `lh kb list`
```bash
lh kb list [--json [fields]]
```
**Table columns**: ID, NAME, DESCRIPTION, UPDATED
### `lh kb view <id>`
```bash
lh kb view [fields]] < id > [--json
```
**Displays**: Name, description, full directory tree with all files and documents (recursively fetched). Shows indented tree structure with item type (File/Doc), file type, and size.
**API**: Uses `file.getKnowledgeItems` to recursively fetch items. Folders (`custom/folder` fileType) are traversed in parallel via `Promise.all` for performance.
### `lh kb create`
```bash
lh kb create -n [--avatar < name > [-d < desc > ] < url > ]
```
| Option | Description | Required |
| -------------------------- | ------------------- | -------- |
| `-n, --name <name>` | Knowledge base name | Yes |
| `-d, --description <desc>` | Description | No |
| `--avatar <url>` | Avatar URL | No |
**Output**: Created KB ID. Note: backend returns ID as a string directly (not an object).
### `lh kb edit <id>`
```bash
lh kb edit [-d [--avatar < id > [-n < name > ] < desc > ] < url > ]
```
Requires at least one change flag. Errors if none specified.
### `lh kb delete <id>`
```bash
lh kb delete [--yes] < id > [--remove-files]
```
| Option | Description |
| ---------------- | ---------------------------- |
| `--remove-files` | Also delete associated files |
| `--yes` | Skip confirmation |
### `lh kb add-files <knowledgeBaseId>`
```bash
lh kb add-files <kbId> --ids <fileId1> <fileId2> ...
```
Link existing files to a knowledge base.
### `lh kb remove-files <knowledgeBaseId>`
```bash
lh kb remove-files <kbId> --ids <fileId1> <fileId2> ... [--yes]
```
Unlink files from a knowledge base.
### `lh kb mkdir <knowledgeBaseId>`
```bash
lh kb mkdir < kbId > -n < name > [--parent < folderId > ]
```
Create a folder in a knowledge base. Uses `document.createDocument` with `fileType: 'custom/folder'`.
| Option | Description | Required |
| --------------------- | ---------------- | -------- |
| `-n, --name <name>` | Folder name | Yes |
| `--parent <parentId>` | Parent folder ID | No |
### `lh kb create-doc <knowledgeBaseId>`
```bash
lh kb create-doc [--parent < kbId > -t < title > [-c < content > ] < folderId > ]
```
Create a document in a knowledge base. Uses `document.createDocument` with `fileType: 'custom/document'`.
| Option | Description | Required |
| ---------------------- | ---------------- | -------- |
| `-t, --title <title>` | Document title | Yes |
| `-c, --content <text>` | Document content | No |
| `--parent <parentId>` | Parent folder ID | No |
### `lh kb move <id>`
```bash
lh kb move < id > --type < file | doc > [--parent < folderId > ]
```
Move a file or document to a different folder (or to root if `--parent` is omitted).
| Option | Description | Default |
| --------------------- | -------------------------------- | ------- |
| `--type <type>` | Item type: `file` or `doc` | `file` |
| `--parent <parentId>` | Target folder ID (omit for root) | - |
Uses `document.updateDocument` for docs, `file.updateFile` for files.
### `lh kb upload <knowledgeBaseId> <filePath>`
```bash
lh kb upload <kbId> <filePath> [--parent <folderId>]
```
Upload a local file to a knowledge base via S3 presigned URL.
| Option | Description |
| --------------------- | ---------------- |
| `--parent <parentId>` | Parent folder ID |
**Flow**: Compute SHA-256 hash → get presigned URL via `upload.createS3PreSignedUrl` → PUT to S3 → create file record via `file.createFile`.
---
## File Management (`lh file`)
Manage uploaded files.
**Source**: `apps/cli/src/commands/file.ts`
### `lh file list`
```bash
lh file list [--kb-id [-L [--json [fields]] < id > ] < n > ]
```
| Option | Description | Default |
| ----------------- | ------------------------ | ------- |
| `--kb-id <id>` | Filter by knowledge base | - |
| `-L, --limit <n>` | Maximum items | `30` |
**Table columns**: ID, NAME, TYPE, SIZE, UPDATED
### `lh file view <id>`
```bash
lh file view [fields]] < id > [--json
```
**Displays**: Name, type, size, chunking status, embedding status.
### `lh file delete <ids...>`
```bash
lh file delete [--yes] < id1 > [id2...]
```
Supports deleting multiple files at once.
### `lh file recent`
```bash
lh file recent [-L [--json [fields]] < n > ]
```
| Option | Description | Default |
| ----------------- | --------------- | ------- |
| `-L, --limit <n>` | Number of items | `10` |
---
## Document Management (`lh doc`)
Manage text documents (notes, wiki pages).
**Source**: `apps/cli/src/commands/doc.ts`
### `lh doc list`
```bash
lh doc list [-L [--file-type [--source-type [--json [fields]] < n > ] < type > ] < type > ]
```
| Option | Description | Default |
| ---------------------- | --------------------------------------------- | ------- |
| `-L, --limit <n>` | Maximum items | `30` |
| `--file-type <type>` | Filter by file type | - |
| `--source-type <type>` | Filter by source type (file, web, api, topic) | - |
**Table columns**: ID, TITLE, TYPE, UPDATED
### `lh doc view <id>`
```bash
lh doc view [fields]] < id > [--json
```
**Displays**: Title, type, KB association, updated time, full content.
### `lh doc create`
```bash
lh doc create -t [-F [--parent [--slug [--kb [--file-type < title > [-b < body > ] < path > ] < id > ] < slug > ] < id > ] < type > ]
```
| Option | Description | Required |
| ------------------------ | ----------------------------------------------- | -------- |
| `-t, --title <title>` | Document title | Yes |
| `-b, --body <content>` | Document body text | No |
| `-F, --body-file <path>` | Read body from file | No |
| `--parent <id>` | Parent document ID | No |
| `--slug <slug>` | Custom URL slug | No |
| `--kb <id>` | Knowledge base ID to associate with | No |
| `--file-type <type>` | File type (e.g. custom/document, custom/folder) | No |
`-b` and `-F` are mutually exclusive; `-F` reads the file content as the body.
### `lh doc batch-create <file>`
Batch create documents from a JSON file. The file must contain a non-empty array of document objects.
```bash
lh doc batch-create documents.json
```
Each object in the array can have: `title`, `content`, `fileType`, `knowledgeBaseId`, `parentId`, `slug`.
### `lh doc edit <id>`
```bash
lh doc edit [-b [-F [--parent [--file-type < id > [-t < title > ] < body > ] < path > ] < id > ] < type > ]
```
### `lh doc delete <ids...>`
```bash
lh doc delete [--yes] < id1 > [id2...]
```
### `lh doc parse <fileId>`
Parse an uploaded file into a document.
```bash
lh doc parse [--json [fields]] < fileId > [--with-pages]
```
| Option | Description |
| -------------- | ----------------------- |
| `--with-pages` | Preserve page structure |
**Output**: Parsed title and content preview.
### `lh doc link-topic <docId> <topicId>`
Associate a document with a topic. Creates a linked copy via the notebook router.
```bash
lh doc link-topic <docId> <topicId>
```
### `lh doc topic-docs <topicId>`
List documents associated with a topic.
```bash
lh doc topic-docs [--json [fields]] < topicId > [--type < type > ]
```
| Option | Description |
| --------------- | ------------------------------------------------ |
| `--type <type>` | Filter by type (article, markdown, note, report) |
-138
View File
@@ -1,138 +0,0 @@
# Memory Commands
Manage user memories - the AI's long-term knowledge about users.
**Source**: `apps/cli/src/commands/memory.ts`
## Memory Categories
| Category | Description |
| ------------ | ----------------------------------------- |
| `identity` | User's name, role, relationships |
| `activity` | Recent activities and their status |
| `context` | Ongoing contexts, projects, goals |
| `experience` | Past experiences and key learnings |
| `preference` | User preferences, directives, suggestions |
---
## `lh memory list [category]`
List memory entries, optionally filtered by category.
```bash
lh memory list # All categories
lh memory list identity # Only identity memories
lh memory list preference # Only preferences
```
| Option | Description |
| ----------------- | ----------- |
| `--json [fields]` | JSON output |
**Output**: Grouped by category, showing type/status and descriptions.
---
## `lh memory create`
Create a new identity memory entry.
```bash
lh memory create [options]
```
| Option | Description |
| -------------------------- | ------------------------ |
| `--type <type>` | Memory type |
| `--role <role>` | User's role |
| `--relationship <rel>` | Relationship description |
| `-d, --description <desc>` | Description |
| `--labels <labels...>` | Extracted labels |
---
## `lh memory edit <category> <id>`
Edit a memory entry. Options vary by category:
```bash
lh memory edit identity < id > [options]
lh memory edit activity < id > [options]
lh memory edit context < id > [options]
lh memory edit experience < id > [options]
lh memory edit preference < id > [options]
```
### Category-specific Options
**identity**:
- `--type <type>`, `--role <role>`, `--relationship <rel>`
**activity**:
- `--narrative <text>`, `--notes <text>`, `--status <status>`
**context**:
- `--title <title>`, `--description <desc>`, `--status <status>`
**experience**:
- `--situation <text>`, `--action <text>`, `--key-learning <text>`
**preference**:
- `--directives <text>`, `--suggestions <text>`
---
## `lh memory delete <category> <id>`
```bash
lh memory delete identity < id > [--yes]
```
---
## `lh memory persona`
Display the compiled memory persona summary.
```bash
lh memory persona [--json [fields]]
```
**Output**: Summarized user profile built from all memory categories.
---
## `lh memory extract`
Trigger async memory extraction from chat history.
```bash
lh memory extract [--from [--to < date > ] < date > ]
```
| Option | Description |
| --------------- | ----------------------- |
| `--from <date>` | Start date (ISO format) |
| `--to <date>` | End date (ISO format) |
Starts a background task that analyzes chat history and creates new memory entries.
---
## `lh memory extract-status`
Check the status of a memory extraction task.
```bash
lh memory extract-status [--task-id [--json [fields]] < id > ]
```
| Option | Description |
| ---------------- | ------------------- |
| `--task-id <id>` | Check specific task |
@@ -1,186 +0,0 @@
# Model & Provider Commands
## Model Management (`lh model`)
Manage AI models within providers.
**Source**: `apps/cli/src/commands/model.ts`
### `lh model list <providerId>`
List models for a specific provider.
```bash
lh model list openai
lh model list openai --type image --enabled
lh model list lobehub --type video --json
```
| Option | Description | Default |
| ----------------- | -------------------------------------------------------------------------------------- | ------- |
| `-L, --limit <n>` | Maximum items | `50` |
| `--enabled` | Only show enabled models | `false` |
| `--type <type>` | Filter by model type (`chat\|embedding\|tts\|stt\|image\|video\|text2music\|realtime`) | - |
| `--json [fields]` | Output JSON, optionally specify fields | - |
**Table columns**: ID, NAME, ENABLED, TYPE
**Backend**: `aiModel.getAiProviderModelList``AiInfraRepos.getAiProviderModelList` (supports `type` filter at repository level)
### `lh model view <id>`
```bash
lh model view [fields]] < modelId > [--json
```
**Displays**: Name, provider, type, enabled status, capabilities.
### `lh model create`
```bash
lh model create --id [--type < id > --provider < providerId > [--display-name < name > ] < type > ]
```
| Option | Description | Default |
| ------------------------- | ------------ | -------- |
| `--id <id>` | Model ID | Required |
| `--provider <providerId>` | Provider ID | Required |
| `--display-name <name>` | Display name | - |
| `--type <type>` | Model type | `chat` |
### `lh model edit <id>`
```bash
lh model edit [--type < modelId > --provider < providerId > [--display-name < name > ] < type > ]
```
### `lh model toggle <id>`
Enable or disable a model.
```bash
lh model toggle < modelId > --provider < providerId > --enable
lh model toggle < modelId > --provider < providerId > --disable
```
| Option | Description | Required |
| ------------------------- | ----------------- | ------------ |
| `--provider <providerId>` | Provider ID | Yes |
| `--enable` | Enable the model | One required |
| `--disable` | Disable the model | One required |
### `lh model batch-toggle <ids...>`
Enable or disable multiple models at once.
```bash
lh model batch-toggle model1 model2 model3 --provider openai --enable
```
### `lh model delete <id>`
```bash
lh model delete < modelId > --provider < providerId > [--yes]
```
### `lh model clear`
Clear all models (or only remote/fetched models) for a provider.
```bash
lh model clear --provider [--yes] < providerId > [--remote]
```
---
## Provider Management (`lh provider`)
Manage AI service providers.
**Source**: `apps/cli/src/commands/provider.ts`
### `lh provider list`
```bash
lh provider list [--json [fields]]
```
**Table columns**: ID, NAME, ENABLED, SOURCE
### `lh provider view <id>`
```bash
lh provider view [fields]] < providerId > [--json
```
**Displays**: Name, enabled status, source, configuration.
### `lh provider create`
```bash
lh provider create --id [-d [--logo [--sdk-type < id > -n < name > [-s < source > ] < desc > ] < url > ] < type > ]
```
| Option | Description | Default |
| -------------------------- | ------------------------------------------------- | -------- |
| `--id <id>` | Provider ID | Required |
| `-n, --name <name>` | Provider name | Required |
| `-s, --source <source>` | Source type (`builtin` or `custom`) | `custom` |
| `-d, --description <desc>` | Provider description | - |
| `--logo <logo>` | Provider logo URL | - |
| `--sdk-type <sdkType>` | SDK type (openai, anthropic, azure, bedrock, ...) | - |
### `lh provider edit <id>`
```bash
lh provider edit [-d [--logo [--sdk-type < providerId > [-n < name > ] < desc > ] < url > ] < type > ]
```
Requires at least one change flag.
### `lh provider config <id>`
Configure provider settings (API key, base URL, etc.).
```bash
lh provider config openai --api-key sk-xxx
lh provider config openai --base-url https://custom-endpoint.com
lh provider config openai --show
lh provider config openai --show --json
```
| Option | Description |
| ------------------------ | --------------------------------- |
| `--api-key <key>` | Set API key |
| `--base-url <url>` | Set base URL |
| `--check-model <model>` | Set connectivity check model |
| `--enable-response-api` | Enable Response API mode (OpenAI) |
| `--disable-response-api` | Disable Response API mode |
| `--fetch-on-client` | Enable fetching models on client |
| `--no-fetch-on-client` | Disable fetching models on client |
| `--show` | Show current config |
| `--json [fields]` | Output JSON (with --show) |
**Important**: The `lobehub` provider is platform-managed. Attempting to set `--api-key` or `--base-url` on it will be rejected with an error message.
### `lh provider test <id>`
Test provider connectivity.
```bash
lh provider test openai
lh provider test openai -m gpt-4o --json
```
### `lh provider toggle <id>`
```bash
lh provider toggle < providerId > --enable
lh provider toggle < providerId > --disable
```
### `lh provider delete <id>`
```bash
lh provider delete < providerId > [--yes]
```
@@ -1,94 +0,0 @@
# Search & Configuration Commands
## Global Search (`lh search`)
Search across all LobeHub resource types.
**Source**: `apps/cli/src/commands/search.ts`
### `lh search <query>`
```bash
lh search "meeting notes" [-t [-L [--json [fields]] < type > ] < n > ]
```
| Option | Description | Default |
| ------------------- | ----------------------- | --------- |
| `-t, --type <type>` | Filter by resource type | All types |
| `-L, --limit <n>` | Results per type | `10` |
### Searchable Types
| Type | Description |
| ---------------- | ---------------------------- |
| `agent` | AI agents |
| `topic` | Conversation topics |
| `file` | Uploaded files |
| `folder` | File folders |
| `message` | Chat messages |
| `page` | Documents/pages |
| `memory` | User memories |
| `mcp` | MCP servers |
| `plugin` | Installed plugins |
| `communityAgent` | Community marketplace agents |
| `knowledgeBase` | Knowledge bases |
**Output**: Results grouped by type, showing ID, title/name, description.
---
## User Configuration (`lh whoami` / `lh usage`)
**Source**: `apps/cli/src/commands/config.ts`
### `lh whoami`
Display current authenticated user information.
```bash
lh whoami [--json [fields]]
```
**Displays**: Name, username, email, user ID, subscription plan.
### `lh usage`
Display usage statistics.
```bash
lh usage [--month [--daily] [--json [fields]] < YYYY-MM > ]
```
| Option | Description | Default |
| ------------------- | -------------- | ----------------------- |
| `--month <YYYY-MM>` | Month to query | Current month |
| `--daily` | Group by day | `false` (monthly total) |
**Output**: Token usage, costs, and model breakdown for the specified period.
---
## Global Options
These options are available across most commands:
| Option | Description |
| ----------------- | ---------------------------------------------------------------------- |
| `--json [fields]` | Output as JSON; optionally filter to specific fields (comma-separated) |
| `--yes` | Skip confirmation prompts for destructive operations |
| `-L, --limit <n>` | Pagination limit for list commands |
| `-v, --verbose` | Enable verbose/debug logging |
| `--help` | Show command help |
| `--version` | Show CLI version |
### JSON Field Filtering
The `--json` option supports field selection:
```bash
# Full JSON output
lh agent list --json
# Only specific fields
lh agent list --json "id,title,model"
```
@@ -1,149 +0,0 @@
# Skill & Plugin Commands
## Skill Management (`lh skill`)
Manage agent skills (custom instructions and capabilities).
**Source**: `apps/cli/src/commands/skill.ts`
### `lh skill list`
```bash
lh skill list [--source [--json [fields]] < source > ]
```
| Option | Description |
| ------------------- | ----------------------------------- |
| `--source <source>` | Filter: `builtin`, `market`, `user` |
**Table columns**: ID, NAME, DESCRIPTION, SOURCE, IDENTIFIER
### `lh skill view <id>`
```bash
lh skill view [fields]] < id > [--json
```
**Displays**: Name, description, source, identifier, content.
### `lh skill create`
```bash
lh skill create -n < name > -d < desc > -c < content > [-i < identifier > ]
```
| Option | Description | Required |
| -------------------------- | ----------------------------------- | -------- |
| `-n, --name <name>` | Skill name | Yes |
| `-d, --description <desc>` | Description | Yes |
| `-c, --content <content>` | Skill content (prompt/instructions) | Yes |
| `-i, --identifier <id>` | Custom identifier | No |
### `lh skill edit <id>`
```bash
lh skill edit [-n [-d < id > [-c < content > ] < name > ] < desc > ]
```
### `lh skill delete <id>`
```bash
lh skill delete < id > [--yes]
```
### `lh skill search <query>`
```bash
lh skill search [fields]] < query > [--json
```
### `lh skill install <source>` (alias: `lh skill i`)
Install a skill. Auto-detects source type from the input:
```bash
# GitHub (URL or owner/repo shorthand)
lh skill install lobehub/skill-repo
lh skill install https://github.com/lobehub/skill-repo
lh skill install lobehub/skill-repo --branch dev
# ZIP URL
lh skill install https://example.com/skill.zip
# Marketplace identifier
lh skill install my-cool-skill
lh skill i my-cool-skill
```
| Option | Description | Notes |
| ------------------- | ------------------------- | -------- |
| `--branch <branch>` | Branch name (GitHub only) | Optional |
**Detection rules**:
- `https://github.com/...` or `owner/repo` → GitHub
- Other `https://...` URLs → ZIP URL
- Everything else → marketplace identifier
### Resource Commands
#### `lh skill resources <id>`
List files/resources within a skill.
```bash
lh skill resources [fields]] < id > [--json
```
**Displays**: Path, type, size.
#### `lh skill read-resource <id> <path>`
Read a specific resource file from a skill.
```bash
lh skill read-resource <skillId> <path>
```
**Output**: File content or JSON metadata.
---
## Plugin Management (`lh plugin`)
Install and manage plugins (external tool integrations).
**Source**: `apps/cli/src/commands/plugin.ts`
### `lh plugin list`
```bash
lh plugin list [--json [fields]]
```
**Table columns**: ID, IDENTIFIER, TYPE, TITLE
### `lh plugin install`
```bash
lh plugin install -i [--settings < identifier > --manifest < json > [--type < type > ] < json > ]
```
| Option | Description | Required |
| ----------------------- | -------------------------- | ---------------------- |
| `-i, --identifier <id>` | Plugin identifier | Yes |
| `--manifest <json>` | Plugin manifest JSON | Yes |
| `--type <type>` | `plugin` or `customPlugin` | No (default: `plugin`) |
| `--settings <json>` | Plugin settings JSON | No |
### `lh plugin uninstall <id>`
```bash
lh plugin uninstall < id > [--yes]
```
### `lh plugin update <id>`
```bash
lh plugin update [--settings < id > [--manifest < json > ] < json > ]
```
@@ -1,614 +0,0 @@
---
name: data-fetching-architecture
description: 'LobeHub data-fetching pipeline guide. Use for service layer, Zustand store, SWR, lambdaClient, useClientDataSWR, useFetchXxx hooks, or migrating useEffect fetches.'
user-invocable: false
---
# LobeHub Data Fetching Architecture
> **Related:** `store-data-structures` covers List vs Detail data shape rationale (Map vs Array).
## Architecture Overview
```text
┌─────────────┐
│ Component │
└──────┬──────┘
│ 1. Call useFetchXxx hook from store
┌──────────────────┐
│ Zustand Store │
│ (State + Hook) │
└──────┬───────────┘
│ 2. useClientDataSWR calls service
┌──────────────────┐
│ Service Layer │
│ (xxxService) │
└──────┬───────────┘
│ 3. Call lambdaClient
┌──────────────────┐
│ lambdaClient │
│ (TRPC Client) │
└──────────────────┘
```
## Core Principles
### ✅ DO
1. **Use Service Layer** for all API calls
2. **Use Store SWR Hooks** for data fetching (not useEffect)
3. **Use proper data structures** — see `store-data-structures` skill for List vs Detail patterns
4. **Use lambdaClient.mutate** for write operations (create/update/delete)
5. **Use lambdaClient.query** only inside service methods
6. **Naming convention** — read hooks are `useFetchXxx`, cache invalidation helpers are `refreshXxx` (e.g. `useFetchBenchmarks` / `refreshBenchmarks`). Mutations then chain `refreshXxx()` after the service call.
### ❌ DON'T
1. **Never use useEffect** for data fetching
2. **Never call lambdaClient** directly in components or stores
3. **Never use useState** for server data
4. **Never mix data structure patterns** — follow `store-data-structures` skill
---
## Layer 1: Service Layer
### Purpose
- Encapsulate all API calls to lambdaClient
- Provide clean, typed interfaces
- Single source of truth for API operations
### Service Structure
```typescript
// src/services/agentEval.ts
class AgentEvalService {
// Query methods - READ operations
async listBenchmarks() {
return lambdaClient.agentEval.listBenchmarks.query();
}
async getBenchmark(id: string) {
return lambdaClient.agentEval.getBenchmark.query({ id });
}
// Mutation methods - WRITE operations
async createBenchmark(params: CreateBenchmarkParams) {
return lambdaClient.agentEval.createBenchmark.mutate(params);
}
async updateBenchmark(params: UpdateBenchmarkParams) {
return lambdaClient.agentEval.updateBenchmark.mutate(params);
}
async deleteBenchmark(id: string) {
return lambdaClient.agentEval.deleteBenchmark.mutate({ id });
}
}
export const agentEvalService = new AgentEvalService();
```
### Service Guidelines
1. **One service per domain** (e.g., agentEval, ragEval, aiAgent)
2. **Export singleton instance** (`export const xxxService = new XxxService()`)
3. **Method names match operations** (list, get, create, update, delete)
4. **Clear parameter types** (use interfaces for complex params)
---
## Layer 2: Store with SWR Hooks
### Purpose
- Manage client-side state
- Provide SWR hooks for data fetching
- Handle cache invalidation
### State Structure
```typescript
// src/store/eval/slices/benchmark/initialState.ts
export interface BenchmarkSliceState {
// List data - simple array
benchmarkList: AgentEvalBenchmarkListItem[];
benchmarkListInit: boolean;
// Detail data - map for caching
benchmarkDetailMap: Record<string, AgentEvalBenchmark>;
loadingBenchmarkDetailIds: string[];
// Mutation states
isCreatingBenchmark: boolean;
isUpdatingBenchmark: boolean;
isDeletingBenchmark: boolean;
}
```
> For complete initialState, reducer, and internal dispatch patterns, see the `store-data-structures` skill.
### Actions
```typescript
// src/store/eval/slices/benchmark/action.ts
const FETCH_BENCHMARKS_KEY = 'FETCH_BENCHMARKS';
const FETCH_BENCHMARK_DETAIL_KEY = 'FETCH_BENCHMARK_DETAIL';
export interface BenchmarkAction {
// SWR Hooks - for data fetching
useFetchBenchmarks: () => SWRResponse;
useFetchBenchmarkDetail: (id?: string) => SWRResponse;
// Refresh methods - for cache invalidation
refreshBenchmarks: () => Promise<void>;
refreshBenchmarkDetail: (id: string) => Promise<void>;
// Mutation actions
createBenchmark: (params: CreateParams) => Promise<any>;
updateBenchmark: (params: UpdateParams) => Promise<void>;
deleteBenchmark: (id: string) => Promise<void>;
// Internal methods - not for direct UI use
internal_dispatchBenchmarkDetail: (payload: BenchmarkDetailDispatch) => void;
internal_updateBenchmarkDetailLoading: (id: string, loading: boolean) => void;
}
export const createBenchmarkSlice: StateCreator<EvalStore, any, [], BenchmarkAction> = (
set,
get,
) => ({
// Fetch list — simple array stored in benchmarkList
useFetchBenchmarks: () =>
useClientDataSWR(FETCH_BENCHMARKS_KEY, () => agentEvalService.listBenchmarks(), {
onSuccess: (data) => {
set({ benchmarkList: data, benchmarkListInit: true }, false, 'useFetchBenchmarks/success');
},
}),
// Fetch detail — null key disables the request when id is missing
useFetchBenchmarkDetail: (id) =>
useClientDataSWR(
id ? [FETCH_BENCHMARK_DETAIL_KEY, id] : null,
() => agentEvalService.getBenchmark(id!),
{
onSuccess: (data) => {
get().internal_dispatchBenchmarkDetail({
type: 'setBenchmarkDetail',
id: id!,
value: data,
});
get().internal_updateBenchmarkDetailLoading(id!, false);
},
},
),
// Refresh methods
refreshBenchmarks: () => mutate(FETCH_BENCHMARKS_KEY),
refreshBenchmarkDetail: (id) => mutate([FETCH_BENCHMARK_DETAIL_KEY, id]),
// CREATE — refresh list after creation
createBenchmark: async (params) => {
set({ isCreatingBenchmark: true }, false, 'createBenchmark/start');
try {
const result = await agentEvalService.createBenchmark(params);
await get().refreshBenchmarks();
return result;
} finally {
set({ isCreatingBenchmark: false }, false, 'createBenchmark/end');
}
},
// UPDATE — optimistic update + refresh
updateBenchmark: async (params) => {
const { id } = params;
// 1. Optimistic update
get().internal_dispatchBenchmarkDetail({
type: 'updateBenchmarkDetail',
id,
value: params,
});
// 2. Set loading
get().internal_updateBenchmarkDetailLoading(id, true);
try {
// 3. Call service
await agentEvalService.updateBenchmark(params);
// 4. Refresh from server
await get().refreshBenchmarks();
await get().refreshBenchmarkDetail(id);
} finally {
get().internal_updateBenchmarkDetailLoading(id, false);
}
},
// DELETE — optimistic update + refresh
deleteBenchmark: async (id) => {
get().internal_dispatchBenchmarkDetail({ type: 'deleteBenchmarkDetail', id });
get().internal_updateBenchmarkDetailLoading(id, true);
try {
await agentEvalService.deleteBenchmark(id);
await get().refreshBenchmarks();
} finally {
get().internal_updateBenchmarkDetailLoading(id, false);
}
},
// Internal — dispatch to reducer (for detail map)
internal_dispatchBenchmarkDetail: (payload) => {
const currentMap = get().benchmarkDetailMap;
const nextMap = benchmarkDetailReducer(currentMap, payload);
// Skip set when nothing changed — avoids unnecessary re-renders
if (isEqual(nextMap, currentMap)) return;
set({ benchmarkDetailMap: nextMap }, false, `dispatchBenchmarkDetail/${payload.type}`);
},
// Internal — update loading state for specific detail
internal_updateBenchmarkDetailLoading: (id, loading) => {
set(
(state) => ({
loadingBenchmarkDetailIds: loading
? [...state.loadingBenchmarkDetailIds, id]
: state.loadingBenchmarkDetailIds.filter((i) => i !== id),
}),
false,
'updateBenchmarkDetailLoading',
);
},
});
```
### Store Guidelines
1. **SWR keys as constants** at top of file
2. **useClientDataSWR** for all data fetching (never useEffect)
3. **onSuccess callback** updates store state
4. **Refresh methods** use `mutate()` to invalidate cache
5. **Loading states** in initialState, updated in onSuccess
6. **Mutations** call service, then refresh relevant cache
---
## Layer 3: Component Usage
### Fetching List Data
```tsx
// ✅ CORRECT
const BenchmarkList = () => {
// 1. Get the hook from store
const useFetchBenchmarks = useEvalStore((s) => s.useFetchBenchmarks);
// 2. Get list data
const benchmarks = useEvalStore((s) => s.benchmarkList);
const isInit = useEvalStore((s) => s.benchmarkListInit);
// 3. Call the hook (SWR handles the data fetching)
useFetchBenchmarks();
// 4. Use the data
if (!isInit) return <Loading />;
return (
<div>
<h2>Total: {benchmarks.length}</h2>
{benchmarks.map((b) => (
<BenchmarkCard key={b.id} {...b} />
))}
</div>
);
};
```
### Fetching Detail Data
```tsx
// ✅ CORRECT
const BenchmarkDetail = () => {
const { benchmarkId } = useParams<{ benchmarkId: string }>();
const useFetchBenchmarkDetail = useEvalStore((s) => s.useFetchBenchmarkDetail);
// Detail from map
const benchmark = useEvalStore((s) =>
benchmarkId ? s.benchmarkDetailMap[benchmarkId] : undefined,
);
// Per-item loading
const isLoading = useEvalStore((s) =>
benchmarkId ? s.loadingBenchmarkDetailIds.includes(benchmarkId) : false,
);
useFetchBenchmarkDetail(benchmarkId);
if (!benchmark) return <Loading />;
return (
<div>
<h1>{benchmark.name}</h1>
<p>{benchmark.description}</p>
{isLoading && <Spinner />}
</div>
);
};
```
### Using Selectors (Recommended)
```typescript
// src/store/eval/slices/benchmark/selectors.ts
export const benchmarkSelectors = {
getBenchmarkDetail: (id: string) => (s: EvalStore) => s.benchmarkDetailMap[id],
isLoadingBenchmarkDetail: (id: string) => (s: EvalStore) =>
s.loadingBenchmarkDetailIds.includes(id),
};
// Component with selectors
const BenchmarkDetail = () => {
const { benchmarkId } = useParams();
const useFetchBenchmarkDetail = useEvalStore((s) => s.useFetchBenchmarkDetail);
const benchmark = useEvalStore(benchmarkSelectors.getBenchmarkDetail(benchmarkId!));
useFetchBenchmarkDetail(benchmarkId);
return <div>{benchmark && <h1>{benchmark.name}</h1>}</div>;
};
```
### Anti-pattern
```tsx
// ❌ WRONG — Don't use useEffect for data fetching
const BenchmarkList = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
lambdaClient.agentEval.listBenchmarks
.query()
.then(setData)
.finally(() => setLoading(false));
}, []);
return <div>...</div>;
};
```
### Mutations in Components
```tsx
// Create — global mutation flag drives form loading
const CreateBenchmarkModal = () => {
const createBenchmark = useEvalStore((s) => s.createBenchmark);
const isCreating = useEvalStore((s) => s.isCreatingBenchmark);
const handleSubmit = async (values) => {
try {
// Optimistic update + refresh happen inside createBenchmark
await createBenchmark(values);
message.success('Created successfully');
onClose();
} catch (error) {
message.error('Failed to create');
}
};
return (
<Form onSubmit={handleSubmit} loading={isCreating}>
...
</Form>
);
};
// Update / delete — per-item loading so only the row being mutated spins
const BenchmarkItem = ({ id }: { id: string }) => {
const updateBenchmark = useEvalStore((s) => s.updateBenchmark);
const deleteBenchmark = useEvalStore((s) => s.deleteBenchmark);
const isLoading = useEvalStore(benchmarkSelectors.isLoadingBenchmarkDetail(id));
const handleUpdate = async (data) => {
await updateBenchmark({ id, ...data });
};
const handleDelete = async () => {
await deleteBenchmark(id);
};
return (
<div>
{isLoading && <Spinner />}
<button onClick={handleUpdate}>Update</button>
<button onClick={handleDelete}>Delete</button>
</div>
);
};
```
**Why two patterns:** create has no id yet, so a single `isCreatingXxx` flag is enough. Update/delete target a specific row, so global flags would freeze unrelated rows — keep per-item state in `loadingXxxIds`.
---
## Need a fuller worked example?
The canonical `Benchmark` example above is the one to copy for a flat list + detail map. If you need to maintain a list **keyed by a parent id** (e.g. `datasetMap[benchmarkId]` because the same shape appears under multiple parents), read [`references/walkthrough.md`](./references/walkthrough.md) — it walks through the full 6 steps (service → reducer → slice → store wiring → selectors → component) for that variant.
---
## Common Patterns
### Pattern 1: Pagination
Cache key array must include every parameter that should trigger a refetch.
```typescript
useFetchTestCases: (params: { datasetId: string; limit: number; offset: number }) =>
useClientDataSWR(
params.datasetId ? [FETCH_TEST_CASES_KEY, params.datasetId, params.limit, params.offset] : null,
() => agentEvalService.listTestCases(params),
{
onSuccess: (data) =>
set({
testCaseList: data.data,
testCaseTotal: data.total,
isLoadingTestCases: false,
}),
},
);
```
### Pattern 2: Dependent Fetching
Both hooks run in parallel — SWR dedupes, no manual sequencing needed.
```tsx
const BenchmarkDetail = () => {
const { benchmarkId } = useParams();
const useFetchBenchmarkDetail = useEvalStore((s) => s.useFetchBenchmarkDetail);
const useFetchDatasets = useEvalStore((s) => s.useFetchDatasets);
useFetchBenchmarkDetail(benchmarkId);
useFetchDatasets(benchmarkId);
return <div>...</div>;
};
```
### Pattern 3: Conditional Fetching
Pass `undefined` to disable the hook entirely.
```tsx
// only fetch when modal is open AND id present
useFetchDatasetDetail(open && datasetId ? datasetId : undefined);
```
### Pattern 4: Cross-domain Refresh
```typescript
deleteBenchmark: async (id) => {
await agentEvalService.deleteBenchmark(id);
await get().refreshBenchmarks();
await get().refreshDatasets(id); // related cache invalidated too
};
```
---
## Migration Guide: useEffect → Store SWR
### Before (❌ Wrong)
```tsx
const TestCaseList = ({ datasetId }: Props) => {
const [data, setData] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
lambdaClient.agentEval.listTestCases
.query({ datasetId })
.then((r) => setData(r.data))
.finally(() => setLoading(false));
}, [datasetId]);
return <Table data={data} loading={loading} />;
};
```
### After (✅ Correct)
```typescript
// 1. Add service method
class AgentEvalService {
async listTestCases(params: { datasetId: string }) {
return lambdaClient.agentEval.listTestCases.query(params);
}
}
// 2. Add store slice hook
export const createTestCaseSlice: StateCreator<...> = (set) => ({
useFetchTestCases: (params) =>
useClientDataSWR(
params.datasetId ? [FETCH_TEST_CASES_KEY, params.datasetId] : null,
() => agentEvalService.listTestCases(params),
{
onSuccess: (data) =>
set({ testCaseList: data.data, isLoadingTestCases: false }),
},
),
});
// 3. Component reads from store
const TestCaseList = ({ datasetId }: Props) => {
const useFetchTestCases = useEvalStore((s) => s.useFetchTestCases);
const data = useEvalStore((s) => s.testCaseList);
const loading = useEvalStore((s) => s.isLoadingTestCases);
useFetchTestCases({ datasetId });
return <Table data={data} loading={loading} />;
};
```
---
## Troubleshooting
| Symptom | Check |
| --------------------------- | ------------------------------------------------------------------- |
| Data never loads | Hook called? Key not `null`/`undefined`? Network tab shows request? |
| Stale data after mutation | Did `refreshXxx` run? Cache key matches what the hook uses? |
| Loading state stuck `true` | `onSuccess` writes loading=false? Promise rejected silently? |
| Detail map missing an entry | Reducer dispatch ran? `isEqual` short-circuited on stale data? |
---
## Summary Checklist
When adding new data fetching:
### Step 1: Types & State
See `store-data-structures` for details.
- [ ] Define types in `@lobechat/types`: Detail type + List item type
- [ ] State structure: `xxxList: XxxListItem[]`, `xxxDetailMap: Record<string, Xxx>`, `loadingXxxDetailIds: string[]`
- [ ] Reducer if optimistic updates are needed
### Step 2: Service Layer
- [ ] Create service in `src/services/xxxService.ts`
- [ ] Methods: `listXxx()`, `getXxx(id)`, `createXxx()`, `updateXxx()`, `deleteXxx()`
### Step 3: Store Actions
- [ ] `initialState.ts` with state structure
- [ ] `action.ts` with:
- [ ] `useFetchXxxList()`, `useFetchXxxDetail(id)` — SWR hooks
- [ ] `refreshXxxList()`, `refreshXxxDetail(id)` — cache invalidation
- [ ] CRUD methods calling service
- [ ] `internal_dispatch`, `internal_updateLoading` if using reducer
- [ ] `selectors.ts` (optional but recommended)
- [ ] Integrate slice into main store + initialState
### Step 4: Component Usage
- [ ] Use store hooks (NOT useEffect)
- [ ] List pages: access `xxxList` array
- [ ] Detail pages: access `xxxDetailMap[id]`
- [ ] Use loading states for UI feedback
**Mental model:** Types → Service → Reducer → Slice → Component 🎯
---
## Related Skills
- **`store-data-structures`** — How to structure List and Detail data in stores
- **`zustand`** — General Zustand patterns and best practices
@@ -1,244 +0,0 @@
# Walkthrough: Adding a New Feature End-to-End
This is a worked example of the canonical 6-step recipe applied to a new entity (`Dataset`), showing a variant of the main skill's pattern: **a list keyed by a parent id** (`datasetMap[benchmarkId]`), useful when the same shape appears under different parents.
If you only need the canonical (single-array) pattern, the main `SKILL.md` already shows it for `Benchmark`. Read this file when you need the parent-keyed Map variant, or when you want a checklist-style walkthrough.
## Step 1: Add Service methods
```typescript
class AgentEvalService {
async listDatasets(benchmarkId: string) {
return lambdaClient.agentEval.listDatasets.query({ benchmarkId });
}
async getDataset(id: string) {
return lambdaClient.agentEval.getDataset.query({ id });
}
async createDataset(params: CreateDatasetParams) {
return lambdaClient.agentEval.createDataset.mutate(params);
}
// updateDataset / deleteDataset follow the same shape
}
```
## Step 2: Reducer (optimistic updates)
```typescript
// src/store/eval/slices/dataset/reducer.ts
export type DatasetDispatch =
| { type: 'addDataset'; value: Dataset }
| { type: 'updateDataset'; id: string; value: Partial<Dataset> }
| { type: 'deleteDataset'; id: string };
export const datasetReducer = (state: Dataset[] = [], payload: DatasetDispatch): Dataset[] =>
produce(state, (draft) => {
switch (payload.type) {
case 'addDataset':
draft.unshift(payload.value);
break;
case 'updateDataset': {
const i = draft.findIndex((item) => item.id === payload.id);
if (i !== -1) draft[i] = { ...draft[i], ...payload.value };
break;
}
case 'deleteDataset': {
const i = draft.findIndex((item) => item.id === payload.id);
if (i !== -1) draft.splice(i, 1);
break;
}
}
});
```
## Step 3: Store slice
```typescript
// src/store/eval/slices/dataset/initialState.ts
export interface DatasetData {
currentPage: number;
hasMore: boolean;
isLoading: boolean;
items: Dataset[];
pageSize: number;
total: number;
}
export interface DatasetSliceState {
// Map keyed by benchmarkId — multiple parent contexts share the slice
datasetMap: Record<string, DatasetData>;
// Single item for modal display
datasetDetail: Dataset | null;
isLoadingDatasetDetail: boolean;
loadingDatasetIds: string[];
}
export const datasetInitialState: DatasetSliceState = {
datasetMap: {},
datasetDetail: null,
isLoadingDatasetDetail: false,
loadingDatasetIds: [],
};
```
```typescript
// src/store/eval/slices/dataset/action.ts
const FETCH_DATASETS_KEY = 'FETCH_DATASETS';
const FETCH_DATASET_DETAIL_KEY = 'FETCH_DATASET_DETAIL';
export const createDatasetSlice: StateCreator<EvalStore, any, [], DatasetAction> = (set, get) => ({
// Cache key includes benchmarkId so each parent has its own SWR entry
useFetchDatasets: (benchmarkId) =>
useClientDataSWR(
benchmarkId ? [FETCH_DATASETS_KEY, benchmarkId] : null,
() => agentEvalService.listDatasets(benchmarkId!),
{
onSuccess: (data) => {
set({
datasetMap: {
...get().datasetMap,
[benchmarkId!]: {
currentPage: 1,
hasMore: false,
isLoading: false,
items: data,
pageSize: data.length,
total: data.length,
},
},
});
},
},
),
useFetchDatasetDetail: (id) =>
useClientDataSWR(
id ? [FETCH_DATASET_DETAIL_KEY, id] : null,
() => agentEvalService.getDataset(id!),
{
onSuccess: (data) => set({ datasetDetail: data, isLoadingDatasetDetail: false }),
},
),
refreshDatasets: (benchmarkId) => mutate([FETCH_DATASETS_KEY, benchmarkId]),
refreshDatasetDetail: (id) => mutate([FETCH_DATASET_DETAIL_KEY, id]),
// CREATE with optimistic update — note the temp id pattern
createDataset: async (params) => {
const tmpId = Date.now().toString();
const { benchmarkId } = params;
get().internal_dispatchDataset(
{ type: 'addDataset', value: { ...params, id: tmpId, createdAt: Date.now() } as any },
benchmarkId,
);
get().internal_updateDatasetLoading(tmpId, true);
try {
const result = await agentEvalService.createDataset(params);
await get().refreshDatasets(benchmarkId);
return result;
} finally {
get().internal_updateDatasetLoading(tmpId, false);
}
},
// UPDATE / DELETE follow the same optimistic + refresh pattern as BenchmarkSlice
// (see the main SKILL.md)
// Internal — dispatch reducer scoped to a parent
internal_dispatchDataset: (payload, benchmarkId) => {
const currentData = get().datasetMap[benchmarkId];
const nextItems = datasetReducer(currentData?.items, payload);
// Skip set when nothing changed — avoids unnecessary re-renders
if (isEqual(nextItems, currentData?.items)) return;
set({
datasetMap: {
...get().datasetMap,
[benchmarkId]: {
...currentData,
currentPage: currentData?.currentPage ?? 1,
hasMore: currentData?.hasMore ?? false,
isLoading: false,
items: nextItems,
pageSize: currentData?.pageSize ?? nextItems.length,
total: currentData?.total ?? nextItems.length,
},
},
});
},
internal_updateDatasetLoading: (id, loading) => {
set((state) => ({
loadingDatasetIds: loading
? [...state.loadingDatasetIds, id]
: state.loadingDatasetIds.filter((i) => i !== id),
}));
},
});
```
## Step 4: Wire into the store
```typescript
// src/store/eval/store.ts
export type EvalStore = EvalStoreState & BenchmarkAction & DatasetAction & RunAction;
const createStore: StateCreator<EvalStore, [['zustand/devtools', never]]> = (set, get, store) => ({
...initialState,
...createBenchmarkSlice(set, get, store),
...createDatasetSlice(set, get, store),
...createRunSlice(set, get, store),
});
// src/store/eval/initialState.ts
export const initialState: EvalStoreState = {
...benchmarkInitialState,
...datasetInitialState,
...runInitialState,
};
```
## Step 5: Selectors (optional but recommended)
```typescript
export const datasetSelectors = {
getDatasetData: (benchmarkId: string) => (s: EvalStore) => s.datasetMap[benchmarkId],
getDatasets: (benchmarkId: string) => (s: EvalStore) => s.datasetMap[benchmarkId]?.items ?? [],
isLoadingDataset: (id: string) => (s: EvalStore) => s.loadingDatasetIds.includes(id),
};
```
## Step 6: Use in component
```tsx
// List scoped to a parent
const DatasetList = ({ benchmarkId }: { benchmarkId: string }) => {
const useFetchDatasets = useEvalStore((s) => s.useFetchDatasets);
const datasets = useEvalStore(datasetSelectors.getDatasets(benchmarkId));
const datasetData = useEvalStore(datasetSelectors.getDatasetData(benchmarkId));
useFetchDatasets(benchmarkId);
if (datasetData?.isLoading) return <Loading />;
return (
<div>
<h2>Total: {datasetData?.total ?? 0}</h2>
<List data={datasets} />
</div>
);
};
// Single item for modal — conditional fetching pattern
const DatasetImportModal = ({ open, datasetId }: Props) => {
const useFetchDatasetDetail = useEvalStore((s) => s.useFetchDatasetDetail);
const dataset = useEvalStore((s) => s.datasetDetail);
const isLoading = useEvalStore((s) => s.isLoadingDatasetDetail);
// Only fetch when modal is open AND id present
useFetchDatasetDetail(open && datasetId ? datasetId : undefined);
return <Modal open={open}>{isLoading ? <Loading /> : <div>{dataset?.name}</div>}</Modal>;
};
```
-107
View File
@@ -1,107 +0,0 @@
---
name: db-migrations
description: 'Use for Drizzle migrations: schema/table/column changes, migration generation or regeneration, sequence conflicts after rebase, idempotent SQL review, or migration renames.'
user-invocable: false
---
# Database Migrations Guide
## Step 1: Generate Migrations
```bash
bun run db:generate
```
This generates:
- `packages/database/migrations/0046_meaningless_file_name.sql`
And updates:
- `packages/database/migrations/meta/_journal.json`
- `packages/database/src/core/migrations.json`
- `docs/development/database-schema.dbml`
## Custom Migrations (e.g. CREATE EXTENSION)
For migrations that don't involve Drizzle schema changes (e.g. enabling PostgreSQL extensions), use the `--custom` flag:
```bash
bunx drizzle-kit generate --custom --name=enable_pg_search
```
This generates an empty SQL file and properly updates `_journal.json` and snapshot. Then edit the generated SQL file to add your custom SQL:
```sql
-- Custom SQL migration file, put your code below! --
CREATE EXTENSION IF NOT EXISTS pg_search;
```
**Do NOT manually create migration files or edit `_journal.json`** — always use `drizzle-kit generate` to ensure correct journal entries and snapshots.
## Step 2: Optimize Migration SQL Filename
Rename auto-generated filename to be meaningful:
`0046_meaningless_file_name.sql``0046_user_add_avatar_column.sql`
## Step 3: Use Idempotent Clauses (Defensive Programming)
Always use defensive clauses to make migrations idempotent (safe to re-run):
### CREATE TABLE
```sql
-- ✅ Good
CREATE TABLE IF NOT EXISTS "agent_eval_runs" (
"id" text PRIMARY KEY NOT NULL,
"name" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
-- ❌ Bad
CREATE TABLE "agent_eval_runs" (...);
```
### ALTER TABLE - Columns
```sql
-- ✅ Good
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "avatar" text;
ALTER TABLE "posts" DROP COLUMN IF EXISTS "deprecated_field";
-- ❌ Bad
ALTER TABLE "users" ADD COLUMN "avatar" text;
```
### ALTER TABLE - Foreign Key Constraints
PostgreSQL has no `ADD CONSTRAINT IF NOT EXISTS`. Use `DROP IF EXISTS` + `ADD`:
```sql
-- ✅ Good: Drop first, then add (idempotent)
ALTER TABLE "agent_eval_datasets" DROP CONSTRAINT IF EXISTS "agent_eval_datasets_user_id_users_id_fk";
ALTER TABLE "agent_eval_datasets" ADD CONSTRAINT "agent_eval_datasets_user_id_users_id_fk"
FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
-- ❌ Bad: Will fail if constraint already exists
ALTER TABLE "agent_eval_datasets" ADD CONSTRAINT "agent_eval_datasets_user_id_users_id_fk"
FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
```
### DROP TABLE / INDEX
```sql
-- ✅ Good
DROP TABLE IF EXISTS "old_table";
CREATE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email");
CREATE UNIQUE INDEX IF NOT EXISTS "users_email_unique" ON "users" USING btree ("email");
-- ❌ Bad
DROP TABLE "old_table";
CREATE INDEX "users_email_idx" ON "users" ("email");
```
## Step 4: Update Journal Tag
After renaming the migration SQL file in Step 2, update the `tag` field in `packages/database/migrations/meta/_journal.json` to match the new filename (without `.sql` extension).
-66
View File
@@ -1,66 +0,0 @@
---
name: debug-package
description: 'LobeHub debug package and log namespace guide. Use when adding debug() logging, choosing lobe-* namespaces, troubleshooting DEBUG output, localStorage.debug, or log format specifiers.'
user-invocable: false
---
# Debug Package Usage Guide
## Basic Usage
```typescript
import debug from 'debug';
// Format: lobe-[module]:[submodule]
const log = debug('lobe-server:market');
log('Simple message');
log('With variable: %O', object);
log('Formatted number: %d', number);
```
## Namespace Conventions
- Desktop: `lobe-desktop:[module]`
- Server: `lobe-server:[module]`
- Client: `lobe-client:[module]`
- Router: `lobe-[type]-router:[module]`
## Format Specifiers
- `%O` - Object expanded (recommended for complex objects)
- `%o` - Object
- `%s` - String
- `%d` - Number
## Enable Debug Output
### Browser
```javascript
localStorage.debug = 'lobe-*';
```
### Node.js
```bash
DEBUG=lobe-* npm run dev
DEBUG=lobe-* pnpm dev
```
### Electron
```typescript
process.env.DEBUG = 'lobe-*';
```
## Example
```typescript
// src/server/routers/edge/market/index.ts
import debug from 'debug';
const log = debug('lobe-edge-router:market');
log('getAgent input: %O', input);
```
-89
View File
@@ -1,89 +0,0 @@
---
name: desktop
description: Electron desktop development guide — IPC handlers, controllers, preload scripts, window/menu management.
disable-model-invocation: true
---
# Desktop Development Guide
## Architecture Overview
LobeHub desktop is built on Electron with main-renderer architecture:
1. **Main Process** (`apps/desktop/src/main`): App lifecycle, system APIs, window management
2. **Renderer Process**: Reuses web code from `src/`
3. **Preload Scripts** (`apps/desktop/src/preload`): Securely expose main process to renderer
## Adding New Desktop Features
### 1. Create Controller
Location: `apps/desktop/src/main/controllers/`
```typescript
import { ControllerModule, IpcMethod } from '@/controllers';
export default class NewFeatureCtr extends ControllerModule {
static override readonly groupName = 'newFeature';
@IpcMethod()
async doSomething(params: SomeParams): Promise<SomeResult> {
// Implementation
return { success: true };
}
}
```
Register in `apps/desktop/src/main/controllers/registry.ts`.
### 2. Define IPC Types
Location: `packages/electron-client-ipc/src/types.ts`
```typescript
export interface SomeParams {
/* ... */
}
export interface SomeResult {
success: boolean;
error?: string;
}
```
### 3. Create Renderer Service
Location: `src/services/electron/`
```typescript
import { ensureElectronIpc } from '@/utils/electron/ipc';
const ipc = ensureElectronIpc();
export const newFeatureService = async (params: SomeParams) => {
return ipc.newFeature.doSomething(params);
};
```
### 4. Implement Store Action
Location: `src/store/`
### 5. Add Tests
Location: `apps/desktop/src/main/controllers/__tests__/`
## Detailed Guides
See `references/` for specific topics:
- **Feature implementation**: `references/feature-implementation.md`
- **Local tools workflow**: `references/local-tools.md`
- **Menu configuration**: `references/menu-config.md`
- **Window management**: `references/window-management.md`
## Best Practices
1. **Security**: Validate inputs, limit exposed APIs
2. **Performance**: Use async methods, batch data transfers
3. **UX**: Add progress indicators, provide error feedback
4. **Code organization**: Follow existing patterns, add documentation
@@ -1,103 +0,0 @@
# Desktop Feature Implementation Guide
## Architecture Overview
```plaintext
Main Process Renderer Process
┌──────────────────┐ ┌──────────────────┐
│ Controller │◄──IPC───►│ Service Layer │
│ (IPC Handler) │ │ │
└──────────────────┘ └──────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ System APIs │ │ Store Actions │
│ (fs, network) │ │ (UI State) │
└──────────────────┘ └──────────────────┘
```
## Step-by-Step Implementation
### 1. Create Controller
```typescript
// apps/desktop/src/main/controllers/NotificationCtr.ts
import type {
ShowDesktopNotificationParams,
DesktopNotificationResult,
} from '@lobechat/electron-client-ipc';
import { Notification } from 'electron';
import { ControllerModule, IpcMethod } from '@/controllers';
export default class NotificationCtr extends ControllerModule {
static override readonly groupName = 'notification';
@IpcMethod()
async showDesktopNotification(
params: ShowDesktopNotificationParams,
): Promise<DesktopNotificationResult> {
if (!Notification.isSupported()) {
return { error: 'Notifications not supported', success: false };
}
try {
const notification = new Notification({ body: params.body, title: params.title });
notification.show();
return { success: true };
} catch (error) {
console.error('[NotificationCtr] Failed:', error);
return { error: error instanceof Error ? error.message : 'Unknown error', success: false };
}
}
}
```
### 2. Define IPC Types
```typescript
// packages/electron-client-ipc/src/types.ts
export interface ShowDesktopNotificationParams {
title: string;
body: string;
}
export interface DesktopNotificationResult {
success: boolean;
error?: string;
}
```
### 3. Create Service Layer
```typescript
// src/services/electron/notificationService.ts
import type { ShowDesktopNotificationParams } from '@lobechat/electron-client-ipc';
import { ensureElectronIpc } from '@/utils/electron/ipc';
const ipc = ensureElectronIpc();
export const notificationService = {
show: (params: ShowDesktopNotificationParams) => ipc.notification.showDesktopNotification(params),
};
```
### 4. Implement Store Action
```typescript
// src/store/.../actions.ts
showNotification: async (title: string, body: string) => {
if (!isElectron) return;
const result = await notificationService.show({ title, body });
if (!result.success) {
console.error('Notification failed:', result.error);
}
},
```
## Best Practices
1. **Security**: Validate inputs, limit exposed APIs
2. **Performance**: Use async methods for heavy operations
3. **Error handling**: Always return structured results
4. **UX**: Provide loading states and error feedback
@@ -1,133 +0,0 @@
# Desktop Local Tools Implementation
## Workflow Overview
1. Define tool interface (Manifest)
2. Define related types
3. Implement Store Action
4. Implement Service Layer
5. Implement Controller (IPC Handler)
6. Update Agent documentation
## Step 1: Define Tool Interface (Manifest)
Location: `src/tools/[tool_category]/index.ts`
```typescript
// src/tools/local-files/index.ts
export const LocalFilesApiName = {
RenameFile: 'renameFile',
MoveFile: 'moveFile',
} as const;
export const LocalFilesManifest = {
api: [
{
name: LocalFilesApiName.RenameFile,
description: 'Rename a local file',
parameters: {
type: 'object',
properties: {
oldPath: { type: 'string', description: 'Current file path' },
newName: { type: 'string', description: 'New file name' },
},
required: ['oldPath', 'newName'],
},
},
],
};
```
## Step 2: Define Types
```typescript
// packages/electron-client-ipc/src/types.ts
export interface RenameLocalFileParams {
oldPath: string;
newName: string;
}
// src/tools/local-files/type.ts
export interface LocalRenameFileState {
success: boolean;
error?: string;
oldPath: string;
newPath: string;
}
```
## Step 3: Implement Store Action
```typescript
// src/store/chat/slices/builtinTool/actions/localFile.ts
renameLocalFile: async (id: string, params: RenameLocalFileParams) => {
const { toggleLocalFileLoading, updatePluginState, internal_updateMessageContent } = get();
toggleLocalFileLoading(id, true);
try {
const result = await localFileService.renameFile(params);
if (result.success) {
updatePluginState(id, { success: true, ...result });
internal_updateMessageContent(id, JSON.stringify({ success: true }));
} else {
updatePluginState(id, { success: false, error: result.error });
internal_updateMessageContent(id, JSON.stringify({ error: result.error }));
}
return result.success;
} catch (e) {
console.error(e);
updatePluginState(id, { success: false, error: e.message });
return false;
} finally {
toggleLocalFileLoading(id, false);
}
},
```
## Step 4: Implement Service Layer
```typescript
// src/services/electron/localFileService.ts
import { ensureElectronIpc } from '@/utils/electron/ipc';
const ipc = ensureElectronIpc();
export const localFileService = {
renameFile: (params: RenameLocalFileParams) => ipc.localFiles.renameFile(params),
};
```
## Step 5: Implement Controller
```typescript
// apps/desktop/src/main/controllers/LocalFileCtr.ts
import * as fs from 'fs/promises';
import * as path from 'path';
import { ControllerModule, IpcMethod } from '@/controllers';
export default class LocalFileCtr extends ControllerModule {
static override readonly groupName = 'localFiles';
@IpcMethod()
async renameFile(params: RenameLocalFileParams) {
const { oldPath, newName } = params;
const newPath = path.join(path.dirname(oldPath), newName);
try {
await fs.rename(oldPath, newPath);
return { success: true, newPath };
} catch (error) {
return { success: false, error: error.message };
}
}
}
```
## Step 6: Update Agent Documentation
Location: `src/tools/[tool_category]/systemRole.ts`
Add tool description to `<core_capabilities>` and usage guidelines to `<tool_usage_guidelines>`.
@@ -1,107 +0,0 @@
# Desktop Menu Configuration Guide
## Menu Types
1. **App Menu**: Top of window (macOS) or title bar (Windows/Linux)
2. **Context Menu**: Right-click menus
3. **Tray Menu**: System tray icon menus
## File Structure
```plaintext
apps/desktop/src/main/
├── menus/
│ ├── appMenu.ts # App menu config
│ ├── contextMenu.ts # Context menu config
│ └── factory.ts # Menu factory functions
├── controllers/
│ ├── MenuCtr.ts # Menu controller
│ └── TrayMenuCtr.ts # Tray menu controller
```
## App Menu Configuration
```typescript
// apps/desktop/src/main/menus/appMenu.ts
import { BrowserWindow, Menu, MenuItemConstructorOptions } from 'electron';
export const createAppMenu = (win: BrowserWindow) => {
const template: MenuItemConstructorOptions[] = [
{
label: 'File',
submenu: [
{
label: 'New',
accelerator: 'CmdOrCtrl+N',
click: () => {
/* ... */
},
},
{ type: 'separator' },
{ role: 'quit' },
],
},
// ...
];
return Menu.buildFromTemplate(template);
};
// Register in MenuCtr.ts
Menu.setApplicationMenu(menu);
```
## Context Menu
```typescript
export const createContextMenu = () => {
const template = [
{ label: 'Copy', role: 'copy' },
{ label: 'Paste', role: 'paste' },
];
return Menu.buildFromTemplate(template);
};
// Show on right-click
const menu = createContextMenu();
menu.popup();
```
## Tray Menu
```typescript
// TrayMenuCtr.ts
this.tray = new Tray(trayIconPath);
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show Window', click: this.showMainWindow },
{ type: 'separator' },
{ label: 'Quit', click: () => app.quit() },
]);
this.tray.setContextMenu(contextMenu);
```
## i18n Support
```typescript
import { i18n } from '../locales';
const template = [
{
label: i18n.t('menu.file'),
submenu: [{ label: i18n.t('menu.new'), click: createNew }],
},
];
```
## Best Practices
1. Use standard roles (`role: 'copy'`) for native behavior
2. Use `CmdOrCtrl` for cross-platform shortcuts
3. Use `{ type: 'separator' }` to group related items
4. Handle platform differences with `process.platform`
```typescript
if (process.platform === 'darwin') {
template.unshift({ role: 'appMenu' });
}
```
@@ -1,147 +0,0 @@
# Desktop Window Management Guide
## Window Management Overview
1. Window creation and configuration
2. Window state management (size, position, maximize)
3. Multi-window coordination
4. Window event handling
## File Structure
```plaintext
apps/desktop/src/main/
├── appBrowsers.ts # Core window management
├── controllers/
│ └── BrowserWindowsCtr.ts # Window controller
└── modules/
└── browserWindowManager.ts # Window manager module
```
## Window Creation
```typescript
export const createMainWindow = () => {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 600,
minHeight: 400,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
if (isDev) {
mainWindow.loadURL('http://localhost:3000');
} else {
mainWindow.loadFile(path.join(__dirname, '../../renderer/index.html'));
}
return mainWindow;
};
```
## Window State Persistence
```typescript
const saveWindowState = (window: BrowserWindow) => {
if (!window.isMinimized() && !window.isMaximized()) {
const [x, y] = window.getPosition();
const [width, height] = window.getSize();
settings.set('windowState', { x, y, width, height });
}
};
const restoreWindowState = (window: BrowserWindow) => {
const state = settings.get('windowState');
if (state) {
window.setBounds({ x: state.x, y: state.y, width: state.width, height: state.height });
}
};
window.on('close', () => saveWindowState(window));
```
## Multi-Window Management
```typescript
export class WindowManager {
private windows: Map<string, BrowserWindow> = new Map();
createWindow(id: string, options: BrowserWindowConstructorOptions) {
const window = new BrowserWindow(options);
this.windows.set(id, window);
window.on('closed', () => this.windows.delete(id));
return window;
}
getWindow(id: string) {
return this.windows.get(id);
}
}
```
## Window IPC Controller
```typescript
// apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
export default class BrowserWindowsCtr extends ControllerModule {
static override readonly groupName = 'windows';
@IpcMethod()
minimizeWindow() {
BrowserWindow.getFocusedWindow()?.minimize();
return { success: true };
}
@IpcMethod()
maximizeWindow() {
const win = BrowserWindow.getFocusedWindow();
win?.isMaximized() ? win.restore() : win?.maximize();
return { success: true };
}
}
```
## Renderer Service
```typescript
// src/services/electron/windowService.ts
import { ensureElectronIpc } from '@/utils/electron/ipc';
const ipc = ensureElectronIpc();
export const windowService = {
minimize: () => ipc.windows.minimizeWindow(),
maximize: () => ipc.windows.maximizeWindow(),
close: () => ipc.windows.closeWindow(),
};
```
## Frameless Window
```typescript
const window = new BrowserWindow({
frame: false,
titleBarStyle: 'hidden',
});
```
```css
.titlebar {
-webkit-app-region: drag;
}
.titlebar-button {
-webkit-app-region: no-drag;
}
```
## Best Practices
1. Use `show: false` initially, show after content loads
2. Always set secure `webPreferences`
3. Handle `webContents.on('crashed')` for recovery
4. Clean up resources on `window.on('closed')`
-155
View File
@@ -1,155 +0,0 @@
---
name: docs-changelog
description: 'Write website changelog pages under docs/changelog/*.mdx. Use for EN/ZH product update posts, changelog posts, update-log copy, or docs changelog edits; not GitHub Release notes.'
---
# Docs Changelog Writing Guide
## Scope Boundary (Important)
This skill is only for changelog pages in:
- `docs/changelog/*.mdx`
This skill is **not** for GitHub Releases.\
If the user asks for release PR body / GitHub Release notes, load `../version-release/SKILL.md`.
## Mandatory Companion Skills
For every docs changelog task, you MUST load:
- `../microcopy/SKILL.md`
- `../i18n/SKILL.md` (when EN/ZH pair is involved)
## File and Naming Convention
Use date-based file names:
- English: `docs/changelog/YYYY-MM-DD-topic.mdx`
- Chinese: `docs/changelog/YYYY-MM-DD-topic.zh-CN.mdx`
EN and ZH files must exist as a pair and describe the same release facts.
## Frontmatter Requirements
Each file should include:
```md
---
title: <Title>
description: <1 sentence summary>
tags:
- <Tag 1>
- <Tag 2>
---
```
Rules:
1. `title` should match the H1 title in meaning.
2. `description` should be concise and user-facing.
3. `tags` should be feature-oriented, not internal-team labels.
## Content Structure (Recommended)
Use this shape unless the user requests otherwise:
1. `# <Title>`
2. Opening paragraph (2-4 sentences): user-visible impact
3. 1-3 capability sections (optional `##` headings)
4. `## Improvements and fixes` / `## 体验优化与修复` with concise bullets
Keep heading count low and avoid heading-per-bullet structure.
## Writing Rules
1. Keep all claims factual and tied to actual shipped changes.
2. Explain user value first, implementation second.
3. Prefer natural narrative paragraphs over pure bullet dumps.
4. Avoid marketing exaggeration and vague adjectives.
5. Keep internal terms consistent across EN/ZH files.
6. Keep EN/ZH section order aligned and scope-aligned.
## EN/ZH Synchronization Rules
When generating bilingual changelogs:
1. Keep the same key facts in the same order.
2. Localize naturally; do not do literal sentence-by-sentence translation.
3. If one version has an `Improvements and fixes` bullet list, the other should have equivalent list intent.
4. Do not introduce capabilities in only one language unless explicitly requested.
## Length Guidance
- Small update: 3-5 short paragraphs total
- Medium update: 4-7 short paragraphs + concise fix bullets
- Large update: 6-10 short paragraphs split into 2-4 sections
Do not pad content when changes are limited.
## Authoring Workflow
1. Collect source facts from PRs/commits/issues.
2. Group changes by user workflow (not by internal module path).
3. Draft EN and ZH versions with aligned structure.
4. Verify terminology using `microcopy`/`i18n` guidance.
5. Final pass: remove AI-like filler and tighten sentences.
## Docs Changelog Template (English)
```md
---
title: <Feature title>
description: <One-sentence summary for users>
tags:
- <Tag A>
- <Tag B>
---
# <Feature title>
<Opening paragraph: what changed for users and why it matters.>
<Optional section paragraph for key capability 1.>
<Optional section paragraph for key capability 2.>
## Improvements and fixes
- <Fix or optimization 1>
- <Fix or optimization 2>
```
## Docs Changelog Template (Chinese)
```md
---
title: <功能标题>
description: <一句话说明>
tags:
- <标签 A>
- <标签 B>
---
# <功能标题>
<开场段:这次更新给用户带来的直接变化。>
<可选能力段 1。>
<可选能力段 2。>
## 体验优化与修复
- <优化或修复 1>
- <优化或修复 2>
```
## Quick Checklist
- [ ] File path matches `docs/changelog` naming convention
- [ ] EN and ZH versions both exist and match in facts
- [ ] Opening paragraph explains user-facing outcome
- [ ] Main body is narrative-first, not bullet-only
- [ ] `Improvements and fixes` section is concise and concrete
- [ ] No fabricated claims or unsupported scope
-290
View File
@@ -1,290 +0,0 @@
---
name: drizzle
description: 'LobeHub Drizzle ORM schema and query style. Use for pgTable schemas, indexes, joins, inferred types, db.select/db.query, schema fields, foreign keys, junction tables, or postgres query patterns.'
user-invocable: false
---
# Drizzle ORM Schema Style Guide
## Configuration
- Config: `drizzle.config.ts`
- Schemas: `packages/database/src/schemas/`
- Migrations: `packages/database/migrations/`
- Dialect: `postgresql` with `strict: true`
## Helper Functions
Location: `packages/database/src/schemas/_helpers.ts`
- `timestamptz(name)`: Timestamp with timezone
- `createdAt()`, `updatedAt()`, `accessedAt()`: Standard timestamp columns
- `timestamps`: Object with all three for easy spread
## Naming Conventions
- **Tables**: Plural snake_case (`users`, `session_groups`)
- **Columns**: snake_case (`user_id`, `created_at`)
## Column Definitions
### Primary Keys
```typescript
id: text('id')
.primaryKey()
.$defaultFn(() => idGenerator('agents'))
.notNull(),
```
ID prefixes make entity types distinguishable. For internal tables, use `uuid`.
### Foreign Keys
```typescript
userId: text('user_id')
.references(() => users.id, { onDelete: 'cascade' })
.notNull(),
```
### Timestamps
```typescript
...timestamps, // Spread from _helpers.ts
```
### Indexes
```typescript
// Return array (object style deprecated)
(t) => [uniqueIndex('client_id_user_id_unique').on(t.clientId, t.userId)],
```
## Type Inference
```typescript
export const insertAgentSchema = createInsertSchema(agents);
export type NewAgent = typeof agents.$inferInsert;
export type AgentItem = typeof agents.$inferSelect;
```
## Example Pattern
```typescript
export const agents = pgTable(
'agents',
{
id: text('id')
.primaryKey()
.$defaultFn(() => idGenerator('agents'))
.notNull(),
slug: varchar('slug', { length: 100 })
.$defaultFn(() => randomSlug(4))
.unique(),
userId: text('user_id')
.references(() => users.id, { onDelete: 'cascade' })
.notNull(),
clientId: text('client_id'),
chatConfig: jsonb('chat_config').$type<LobeAgentChatConfig>(),
...timestamps,
},
(t) => [uniqueIndex('client_id_user_id_unique').on(t.clientId, t.userId)],
);
```
## Common Patterns
### Junction Tables (Many-to-Many)
```typescript
export const agentsKnowledgeBases = pgTable(
'agents_knowledge_bases',
{
agentId: text('agent_id')
.references(() => agents.id, { onDelete: 'cascade' })
.notNull(),
knowledgeBaseId: text('knowledge_base_id')
.references(() => knowledgeBases.id, { onDelete: 'cascade' })
.notNull(),
userId: text('user_id')
.references(() => users.id, { onDelete: 'cascade' })
.notNull(),
enabled: boolean('enabled').default(true),
...timestamps,
},
(t) => [primaryKey({ columns: [t.agentId, t.knowledgeBaseId] })],
);
```
## Query Style
**Always use `db.select()` builder API. Never use `db.query.*` relational API** (`findMany`, `findFirst`, `with:`).
The relational API generates complex lateral joins with `json_build_array` that are fragile and hard to debug.
### Select Single Row
```typescript
// ✅ Good
const [result] = await this.db.select().from(agents).where(eq(agents.id, id)).limit(1);
return result;
// ❌ Bad: relational API
return this.db.query.agents.findFirst({
where: eq(agents.id, id),
});
```
### Select with JOIN
```typescript
// ✅ Good: explicit select + leftJoin
const rows = await this.db
.select({
runId: agentEvalRunTopics.runId,
score: agentEvalRunTopics.score,
testCase: agentEvalTestCases,
topic: topics,
})
.from(agentEvalRunTopics)
.leftJoin(agentEvalTestCases, eq(agentEvalRunTopics.testCaseId, agentEvalTestCases.id))
.leftJoin(topics, eq(agentEvalRunTopics.topicId, topics.id))
.where(eq(agentEvalRunTopics.runId, runId))
.orderBy(asc(agentEvalRunTopics.createdAt));
// ❌ Bad: relational API with `with:`
return this.db.query.agentEvalRunTopics.findMany({
where: eq(agentEvalRunTopics.runId, runId),
with: { testCase: true, topic: true },
});
```
### Select with Aggregation
```typescript
// ✅ Good: select + leftJoin + groupBy
const rows = await this.db
.select({
id: agentEvalDatasets.id,
name: agentEvalDatasets.name,
testCaseCount: count(agentEvalTestCases.id).as('testCaseCount'),
})
.from(agentEvalDatasets)
.leftJoin(agentEvalTestCases, eq(agentEvalDatasets.id, agentEvalTestCases.datasetId))
.groupBy(agentEvalDatasets.id);
```
### Raw SQL and Advanced Queries
Prefer Drizzle builders whenever the query can be expressed clearly with `select`,
`insert().select()`, `update().from()`, joins, CTEs, `groupBy`, and typed selected
columns. This keeps table and column references tied to schema definitions, so
schema changes are more likely to surface as TypeScript errors.
Expression-level `sql<T>` is fine inside a Drizzle builder for PostgreSQL features
that do not have a dedicated helper, such as JSON path extraction, casts, aggregate
expressions, `CASE`, `NOW()`, or advisory locks. Row locks are query clauses, not
expressions; use the select builder's `.for('update')` instead of raw
`FOR UPDATE` SQL fragments.
When refactoring raw SQL:
- Preserve the original query shape for latency-sensitive paths. If raw SQL is one
database roundtrip, do not replace it with multiple depth-based queries just to
remove `execute`.
- Use `$with(...)` plus `insert().select()` / `update().from()` for multi-step
single-roundtrip writes when Drizzle can express the data flow.
- Avoid generic `execute<MyRow>(sql...)` as the main safety mechanism. It types the
returned rows, but it does not keep selected columns in sync with schema changes.
- If the only clean implementation is a PostgreSQL feature that Drizzle cannot
express well, keep the raw SQL and tighten it instead: use schema references in
interpolations, explicit user scope, a narrow row interface, and regression tests.
Recursive CTEs are a special case: current Drizzle usage in this repo does not have
a clean `WITH RECURSIVE` builder pattern. Keep recursive CTE raw SQL when replacing
it would add extra database roundtrips or materially worsen performance.
Example: convert an aggregate query when Drizzle can preserve one roundtrip:
```typescript
// ✅ Good: builder owns table and column references; sql<T> stays expression-level.
const rows = await trx
.select({
model: messages.model,
provider: messages.provider,
totalCost: sql<string | null>`sum((${messages.metadata}->'usage'->>'cost')::numeric)`.as(
'totalCost',
),
})
.from(messages)
.where(
and(
eq(messages.topicId, topicId),
eq(messages.userId, userId),
eq(messages.role, 'assistant'),
sql`${messages.metadata} ? 'usage'`,
),
)
.groupBy(messages.provider, messages.model);
```
Example: use the select lock builder for row locks:
```typescript
const [user] = await trx.select().from(users).where(eq(users.id, userId)).for('update');
```
Example: keep a recursive CTE raw when replacing it would add depth-based DB
roundtrips:
```typescript
interface TaskTreeRow {
id: string;
parent_task_id: string | null;
}
// execute<T> is acceptable here only because Drizzle has no clean WITH RECURSIVE
// builder; a builder rewrite would add depth-based roundtrips. Keep schema refs in
// the interpolations and scope every leg to the user.
const { rows } = await db.execute<TaskTreeRow>(sql`
WITH RECURSIVE task_tree AS (
SELECT ${tasks.id}, ${tasks.parentTaskId}
FROM ${tasks}
WHERE ${tasks.id} = ${rootTaskId}
AND ${tasks.createdByUserId} = ${userId}
UNION ALL
SELECT ${tasks.id}, ${tasks.parentTaskId}
FROM ${tasks}
JOIN task_tree ON ${tasks.parentTaskId} = task_tree.id
WHERE ${tasks.createdByUserId} = ${userId}
)
SELECT * FROM task_tree
`);
```
### One-to-Many (Separate Queries)
When you need a parent record with its children, use two queries instead of relational `with:`:
```typescript
// ✅ Good: two simple queries
const [dataset] = await this.db
.select()
.from(agentEvalDatasets)
.where(eq(agentEvalDatasets.id, id))
.limit(1);
if (!dataset) return undefined;
const testCases = await this.db
.select()
.from(agentEvalTestCases)
.where(eq(agentEvalTestCases.datasetId, id))
.orderBy(asc(agentEvalTestCases.sortOrder));
return { ...dataset, testCases };
```
## Database Migrations
See the `db-migrations` skill for the detailed migration guide.
@@ -1,83 +0,0 @@
---
name: heterogeneous-agent
description: 'Implement or debug LobeHub heterogeneous agents. Use for Claude Code/Codex adapters, external CLI agents, event mapping, IPC, persistence, tool-call chains, sessions, traces, or adapter bugs.'
---
# Heterogeneous Agent Development
Use this skill when the bug or feature lives in the external CLI agent pipeline, not the normal server-side agent runtime.
## Use This Skill For
- Adding or changing a driver under `apps/desktop/src/main/modules/heterogeneousAgent/drivers/`
- Editing an adapter under `packages/heterogeneous-agents/src/adapters/`
- Debugging `heteroAgentRawLine` transport, `window.__HETERO_AGENT_TRACE`, or `executeHeterogeneousAgent`
- Fixing Claude Code stream-json bugs such as duplicate partial/full chunks, broken `message.id` boundaries, missing `tool_result`, TodoWrite state drift, or subagent thread routing
- Fixing Codex JSONL bugs such as mixed multi-tool messages, broken turn boundaries, or missing tool-result mapping
- Fixing step-boundary, tool persistence, subagent thread, or resume bugs in Claude Code / Codex flows
- Reproducing multi-tool mixing, orphan tool messages, or stuck tool-result loading
## Pipeline Map
1. CLI raw stdout / JSONL
2. Electron main spawns the CLI and broadcasts `heteroAgentRawLine`
3. Adapter maps raw provider events into `HeterogeneousAgentEvent`
4. `executeHeterogeneousAgent` persists assistant/tool messages and forwards stream events
5. `createGatewayEventHandler` hydrates the UI
6. Only after this path looks correct should you move on to `agent-tracing` or context-engine debugging
## Read These Files First
- `apps/desktop/src/main/controllers/HeterogeneousAgentCtr.ts`
- `apps/desktop/src/main/modules/heterogeneousAgent/drivers/claudeCode.ts`
- `apps/desktop/src/main/modules/heterogeneousAgent/drivers/codex.ts`
- `packages/heterogeneous-agents/src/adapters/claudeCode.ts`
- `packages/heterogeneous-agents/src/adapters/codex.ts`
- `src/store/chat/slices/aiChat/actions/heterogeneousAgentExecutor.ts`
- `src/store/chat/slices/aiChat/actions/__tests__/heterogeneousAgentExecutor.test.ts`
## Default Debug Order
1. Prove whether the raw CLI output is correct before touching UI code.
2. If raw output is correct, compare it with adapter output. In dev, `executeHeterogeneousAgent` exposes `window.__HETERO_AGENT_TRACE`.
3. If adapted events look correct, inspect `persistToolBatch`, `persistToolResult`, step transitions, and subagent routing.
4. Turn the repro into a focused test before fixing.
5. Only after the transport/adapter/executor path looks sound should you debug later-stage message processing.
## Critical Invariants
- One raw tool item must map to one stable `ToolCallPayload.id`.
- A new main-agent step must emit a boundary signal before events are forwarded to the new assistant.
- In Claude Code, multiple assistant events with the same `message.id` are one turn, not multiple turns.
- In Claude Code, `tool_result` lives in `type: 'user'` events, not assistant events.
- In Claude Code partial mode, `message_delta.usage` is authoritative; do not trust echoed usage on every assistant block.
- `persistToolBatch` must pre-register assistant `tools[]` before creating tool messages.
- Every tool message must keep `parentId` equal to the owning assistant and `tool_call_id` equal to the tool id.
- `tool_result` must resolve an existing `toolMsgIdByCallId`.
- Subagent chunks must stay in thread scope and must not be forwarded into the main assistant stream.
- Never clear the global `toolMsgIdByCallId` map at main step boundaries.
## Common Bug Patterns
- Claude Code duplicates text or thinking:
check whether partial deltas and the later full assistant block are both being emitted.
- Claude Code opens too many assistant messages:
check whether the adapter is cutting steps on every assistant event instead of only on `message.id` changes.
- Claude Code tool results never land:
check whether `type: 'user'` `tool_result` blocks are being ignored because the code only inspects assistant events.
- Claude Code TodoWrite cards look stale:
check whether synthesized `pluginState.todos` is being attached at tool-result time.
- Claude Code subagent transcript leaks into the main bubble:
check `parent_tool_use_id` handling and whether subagent chunks are being forwarded to the main gateway handler.
- Multiple Codex tools collapse into one assistant message:
first check whether the adapter emits a usable step boundary such as `newStep` or an equivalent turn-change signal.
- Orphan tool messages:
first check step-transition ordering and whether `persistToolBatch` Phase 1 ran before tool message creation.
- Tool bubble stays loading:
look for `tool_result for unknown toolCallId` and missing `result_msg_id` backfill.
- Subagent tools show up in the main bubble:
check for subagent chunks reaching the main gateway handler.
## References
- For commands, trace capture, invariants, and focused test commands, read [references/debug-workflow.md](./references/debug-workflow.md).
@@ -1,246 +0,0 @@
# Heterogeneous Agent Debug Workflow
## Contents
1. Pipeline map
2. Capture raw CLI traces first
3. Compare raw and adapted events
4. Check step boundaries before persistence
5. Check tool persistence invariants
6. Focused tests
7. Repro-to-fix workflow
## 1. Pipeline Map
```
CLI raw stdout
-> HeterogeneousAgentCtr (Electron main)
-> heteroAgentRawLine broadcast
-> createAdapter(...)
-> executeHeterogeneousAgent(...)
-> persistToolBatch / persistToolResult
-> createGatewayEventHandler(...)
-> UI hydration
```
Start at the leftmost broken layer. Do not jump straight to UI rendering unless raw and adapted events already look correct.
## 2. Capture Raw CLI Traces First
### Codex raw JSONL
Use a read-only prompt and save traces under the repo-local scratch directory `.heerogeneous-tracing/`.
```bash
ts=$(date +%Y%m%d-%H%M%S)
out=".heerogeneous-tracing/codex-${ts}.jsonl"
last=".heerogeneous-tracing/codex-${ts}.last.txt"
cat << 'EOF' | codex exec --json --skip-git-repo-check --sandbox read-only -C "$PWD" -o "$last" - > "$out"
You are being run only to collect a raw Codex JSON event trace.
Do not modify any files.
Use at least 4 separate shell tool invocations, one invocation per command.
Run a short sequence of read-only repo checks and then reply with a one-sentence summary.
EOF
```
What to look for in the JSONL:
- `thread.started`
- `turn.started`
- `item.started` / `item.completed`
- `item.type === 'command_execution'`
- `item.type === 'agent_message'`
- `turn.completed`
If raw Codex already merges tools into one item, the adapter is innocent. If raw Codex emits independent items but UI collapses them, the bug is downstream.
If the repo already contains useful traces under `.heerogeneous-tracing/`, inspect them before reproducing.
### Claude Code raw NDJSON
Mirror the arguments from `apps/desktop/src/main/modules/heterogeneousAgent/drivers/claudeCode.ts`.
- `-p`
- `--input-format stream-json`
- `--output-format stream-json`
- `--verbose`
- `--include-partial-messages`
- `--permission-mode bypassPermissions`
You can capture a local raw trace like this:
```bash
ts=$(date +%Y%m%d-%H%M%S)
out=".heerogeneous-tracing/claude-${ts}.ndjson"
cat << 'EOF' | claude -p \
--input-format stream-json \
--output-format stream-json \
--verbose \
--include-partial-messages \
--permission-mode bypassPermissions \
> "$out"
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Do a few read-only repo checks, use several tool calls, and then summarize briefly."}]}}
EOF
```
What to look for in Claude Code raw traces:
- `type: 'system', subtype: 'init'`
- `type: 'assistant'` blocks for `thinking`, `tool_use`, and `text`
- `type: 'user'` blocks containing `tool_result`
- `type: 'stream_event'` with `message_start`, `content_block_delta`, and `message_delta`
- `type: 'result'`
- `type: 'rate_limit_event'`
Important Claude Code semantics:
- Each content block often arrives as its own assistant event.
- Multiple assistant events can share the same `message.id`; that is still one turn.
- `message.id` change is the main-step boundary.
- Partial deltas arrive before the later full assistant block.
- `message_delta.usage` is the authoritative per-turn usage.
- Subagent events are tagged with `parent_tool_use_id`.
If the repo already contains useful references, inspect these first:
- `.heerogeneous-tracing/cc-monitor-real-trace.jsonl`
- `.heerogeneous-tracing/cc-stream-chain-reference.md`
If you only need boundary semantics or tool persistence behavior, prefer existing adapter tests under:
- `packages/heterogeneous-agents/src/adapters/claudeCode.test.ts`
- `packages/heterogeneous-agents/src/adapters/claudeCode.e2e.test.ts`
## 3. Compare Raw And Adapted Events
In dev builds, `executeHeterogeneousAgent` stores raw lines plus adapted events on:
- `window.__HETERO_AGENT_TRACE`
Use that trace to compare:
- raw `item.started` / `item.completed`
- adapted `stream_chunk { chunkType: 'tools_calling' }`
- adapted `tool_result`
- adapted `tool_end`
For Codex, the usual mapping is:
- raw `item.started(command_execution)` -> `tools_calling` + `tool_start`
- raw `item.completed(command_execution)` -> `tool_result` + `tool_end`
- raw `item.completed(agent_message)` -> `stream_chunk(text)`
If the raw trace is right but adapted events are wrong, fix the adapter before touching persistence.
## 4. Check Step Boundaries Before Persistence
This is the first thing to verify for "mixed tools in one assistant" bugs.
### Claude Code
Claude Code step boundaries are keyed off assistant `message.id` changes. The adapter should emit:
- `stream_end`
- `stream_start { newStep: true }`
Also verify these Claude-specific invariants:
- the first assistant after init does not open a new step
- repeated assistant events with the same `message.id` do not open a new step
- partial `content_block_delta` text/thinking does not get duplicated by the later full assistant event
- `tool_result` from `type: 'user'` updates the matching tool row
- `parent_tool_use_id` creates thread-scoped subagent chunks instead of main-stream chunks
- TodoWrite `tool_use.input` is converted into synthesized `pluginState.todos` on `tool_result`
Good references:
- `packages/heterogeneous-agents/src/adapters/claudeCode.ts`
- `packages/heterogeneous-agents/src/adapters/claudeCode.test.ts`
### Codex
Codex raw traces usually provide turn-level boundaries through:
- `turn.started`
- `turn.completed`
The executor only cuts a new assistant message when it receives a step-boundary signal it understands. If the adapter emits `stream_start` without `newStep`, multiple Codex tools and text chunks can accumulate under the same assistant longer than intended.
Relevant files:
- `packages/heterogeneous-agents/src/adapters/codex.ts`
- `src/store/chat/slices/aiChat/actions/heterogeneousAgentExecutor.ts`
## 5. Check Tool Persistence Invariants
Read `persistToolBatch` and `persistToolResult` before changing UI code.
### `persistToolBatch`
The expected order is:
1. Pre-register assistant `tools[]`
2. Create `role: 'tool'` messages
3. Backfill `result_msg_id` onto assistant `tools[]`
If tool rows are created before assistant `tools[]` are registered, orphan tool messages are likely.
### `persistToolResult`
`tool_result` must resolve the tool row through `toolMsgIdByCallId`.
Warning signs:
- `tool_result for unknown toolCallId`
- tool rows with empty content forever
- missing `result_msg_id`
For Claude Code, remember that tool results originate from raw `type: 'user'` events.
### Main vs subagent scope
- Main-agent tool state is per-step.
- `toolMsgIdByCallId` is global across main and subagent scopes.
- Subagent chunks must not be forwarded into the main gateway handler.
If subagent events leak to the main handler, the main bubble can inherit the wrong `tools[]` and content.
## 6. Focused Tests
Run the smallest useful test set first.
```bash
bunx vitest run --silent='passed-only' 'packages/heterogeneous-agents/src/adapters/codex.test.ts'
bunx vitest run --silent='passed-only' 'packages/heterogeneous-agents/src/adapters/claudeCode.test.ts'
bunx vitest run --silent='passed-only' 'src/store/chat/slices/aiChat/actions/__tests__/heterogeneousAgentExecutor.test.ts'
```
Especially useful places:
- `packages/heterogeneous-agents/src/adapters/codex.test.ts`
- `packages/heterogeneous-agents/src/adapters/claudeCode.test.ts`
- `src/store/chat/slices/aiChat/actions/__tests__/heterogeneousAgentExecutor.test.ts`
Claude Code-specific assertions worth adding when fixing bugs:
- same `message.id` does not emit `newStep`
- changed `message.id` does emit `stream_end` plus `stream_start { newStep: true }`
- partial text/thinking is emitted once
- `tool_result` from `user` events reaches the right tool row
- subagent chunks carry `subagent.parentToolCallId`
- TodoWrite result synthesizes `pluginState.todos`
When the bug comes from a real trace, distill it into the closest existing test file instead of relying on manual UI-only repros.
## 7. Repro-To-Fix Workflow
1. Capture a raw trace and save it under `.heerogeneous-tracing/`.
2. Confirm whether the bug appears in raw events, adapted events, or persistence.
3. Add or update the narrowest failing test near the broken layer.
4. Fix the smallest layer that can explain the symptom.
5. Re-run focused tests.
6. Only then do an Electron smoke test with the `local-testing` skill if UI confirmation is still needed.
Do not start with a broad Electron repro if a raw trace or adapter test can prove the fault zone faster.
-91
View File
@@ -1,91 +0,0 @@
---
name: hotkey
description: 'Add or edit LobeHub keyboard shortcuts. Use for HotkeyEnum, HOTKEYS_REGISTRATION, combineKeys, useHotkeyById, tooltip hotkeys, shortcut scope, conflicts, or Cmd/Ctrl key combos.'
user-invocable: false
---
# Adding Keyboard Shortcuts Guide
## Steps to Add a New Hotkey
### 1. Update Hotkey Constant
In `src/types/hotkey.ts`:
```typescript
export const HotkeyEnum = {
// existing...
ClearChat: 'clearChat', // Add new
} as const;
```
### 2. Register Default Hotkey
In `src/const/hotkeys.ts`:
```typescript
import { KeyMapEnum as Key, combineKeys } from '@lobehub/ui';
export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
{
group: HotkeyGroupEnum.Conversation,
id: HotkeyEnum.ClearChat,
keys: combineKeys([Key.Mod, Key.Shift, Key.Backspace]),
scopes: [HotkeyScopeEnum.Chat],
},
];
```
### 3. Add i18n Translation
In `src/locales/default/hotkey.ts`:
```typescript
const hotkey: HotkeyI18nTranslations = {
clearChat: {
desc: '清空当前会话的所有消息记录',
title: '清空聊天记录',
},
};
```
### 4. Create and Register Hook
In `src/hooks/useHotkeys/chatScope.ts`:
```typescript
export const useClearChatHotkey = () => {
const clearMessages = useChatStore((s) => s.clearMessages);
return useHotkeyById(HotkeyEnum.ClearChat, clearMessages);
};
export const useRegisterChatHotkeys = () => {
useClearChatHotkey();
// ...other hotkeys
};
```
### 5. Add Tooltip (Optional)
```tsx
const clearChatHotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.ClearChat));
<Tooltip hotkey={clearChatHotkey} title={t('clearChat.title', { ns: 'hotkey' })}>
<Button icon={<DeleteOutlined />} onClick={clearMessages} />
</Tooltip>;
```
## Best Practices
1. **Scope**: Choose global or chat scope based on functionality
2. **Grouping**: Place in appropriate group (System/Layout/Conversation)
3. **Conflict check**: Ensure no conflict with system/browser shortcuts
4. **Platform**: Use `Key.Mod` instead of hardcoded `Ctrl` or `Cmd`
5. **Clear description**: Provide title and description for users
## Troubleshooting
- **Not working**: Check scope and RegisterHotkeys hook
- **Not in settings**: Verify HOTKEYS_REGISTRATION config
- **Conflict**: HotkeyInput component shows warnings
- **Page-specific**: Ensure correct scope activation
-78
View File
@@ -1,78 +0,0 @@
---
name: i18n
description: 'LobeHub i18n with react-i18next. Use for user-facing strings, locale keys, namespaces, useTranslation, t(), interpolation, zh-CN/en-US previews, hardcoded UI copy, or pnpm i18n.'
user-invocable: false
---
# LobeHub Internationalization Guide
- Default language: English (en-US)
- Framework: react-i18next
- **Only edit files in `src/locales/default/`** - Never edit JSON files in `locales/`
- Run `pnpm i18n` to generate translations (or manually translate zh-CN/en-US for dev preview)
## Key Naming Convention
**Flat keys with dot notation** (not nested objects):
```typescript
// ✅ Correct
export default {
'alert.cloud.action': '立即体验',
'sync.actions.sync': '立即同步',
'sync.status.ready': '已连接',
};
// ❌ Avoid nested objects
export default {
alert: { cloud: { action: '...' } },
};
```
**Patterns:** `{feature}.{context}.{action|status}`
**Parameters:** Use `{{variableName}}` syntax
```typescript
'alert.cloud.desc': '我们提供 {{credit}} 额度积分',
```
**Avoid key conflicts:**
```typescript
// ❌ Conflict
'clientDB.solve': '自助解决',
'clientDB.solve.backup.title': '数据备份',
// ✅ Solution
'clientDB.solve.action': '自助解决',
'clientDB.solve.backup.title': '数据备份',
```
## Workflow
1. Add keys to `src/locales/default/{namespace}.ts`
2. Export new namespace in `src/locales/default/index.ts`
3. For dev preview: manually translate `locales/zh-CN/{namespace}.json` and `locales/en-US/{namespace}.json`
4. Remind the user to run `pnpm i18n` before creating PR — do NOT run it yourself (very slow)
## Usage
```tsx
import { useTranslation } from 'react-i18next';
const { t } = useTranslation('common');
t('newFeature.title');
t('alert.cloud.desc', { credit: '1000' });
// Multiple namespaces
const { t } = useTranslation(['common', 'chat']);
t('common:save');
```
## Common Namespaces
**Most used:** `common` (shared UI), `chat` (chat features), `setting` (settings)
Others: auth, changelog, components, discover, editor, electron, error, file, hotkey, knowledgeBase, memory, models, plugin, portal, providers, tool, topic
-143
View File
@@ -1,143 +0,0 @@
---
name: linear
description: 'Linear issue management. Use for LOBE-xxx issues, Linear links, PRs referencing Linear, retrieving issues, updating status, completion comments, or sub-issue trees.'
user-invocable: false
---
# Linear Issue Management
Before using Linear workflows, search for `linear` MCP tools. If not found, treat as not installed.
## PR Creation with Linear Issues
A PR that fixes a Linear issue has **two separate jobs to do**, and both matter:
1. **`Fixes LOBE-xxx` in the PR body** — Linear watches GitHub for these magic keywords and auto-links the PR and auto-closes the issue on merge. This is the machine-readable side.
2. **A completion comment on the Linear issue** — gives the reviewer/PM/teammate landing in Linear a human-readable summary of what changed and why, without forcing them to click through to GitHub and read a diff.
If you only do step 1, Linear watchers (often non-engineers) hit the issue and see no context. So pair PR creation with the Linear comment as part of the same task — finish both before considering the work done.
## Workflow
1. **Retrieve issue details** before starting: `mcp__linear-server__get_issue`
2. **Read images** — issue descriptions often contain screenshots with critical context (mockups, error states, before/after). Use `mcp__linear-server__extract_images` so you actually see them; reading raw markdown alone misses what the reporter was looking at.
3. **Check for sub-issues**: `mcp__linear-server__list_issues` with `parentId` filter
4. **Mark as In Progress** at the moment you start planning or implementing — this signals to teammates the issue is owned, so they don't double-pick it up.
5. **Update issue status** when completing: `mcp__linear-server__update_issue`
6. **Add completion comment** (see [format below](#completion-comment-format))
## Creating Issues
When creating issues with `mcp__linear-server__create_issue`, add the `claude code` label. Reason: the label is how the team filters/audits AI-generated issues; without it those issues vanish into the general backlog and the team loses visibility into AI contribution patterns.
## Language
Match the issue language to the conversation that produced it — if you're discussing in 中文,write the issue in 中文;if discussing in English, write it in English. Reason: the issue is a continuation of the conversation, and forcing a language switch creates translation friction for the collaborator who started the thread.
Specifics:
- 中文 conversation → 中文 body; technical terms (file paths, identifiers, library names, commands, error messages) stay in English.
- English conversation → English body.
- Code blocks, file paths, and quoted strings always stay in their original form regardless of surrounding language.
- This applies equally to **updates** — when editing an existing issue (description **and titles**), preserve the language of the conversation that triggered the edit; don't switch the issue language mid-refactor.
## Creating Sub-issue Trees
When breaking a parent issue into a tree of sub-issues (e.g., task decomposition for LOBE-xxx), follow these rules — they work around real limitations of the Linear MCP tools.
### 1. Prefix titles with an ordering index
The Linear Sub-issues panel orders children by `sortOrder`, which **defaults to newest-first** (most recently created appears on top). Neither parallel nor serial creation produces the intended top-to-bottom reading order, and the MCP `save_issue` tool does **not expose a `sortOrder` parameter** — you can't set order at create time.
Workaround: encode execution order in the title itself:
```plaintext
[1] [db] add schema fields
[2] [db] new table + repository
[3] [service] business logic layer
[4] [api] REST endpoints
[4.1] [sdk] client SDK wrapper
[4.1.1] [app] consumer integration
[4.1.2] [app] UI surface
[4.2] [ui] dashboard page
```
Even when the panel shuffles, the reader can mentally reconstruct the dependency graph at a glance. Dotted numbering `[n.m.k]` should mirror the parent-child nesting so the index and the tree agree.
### 2. Nest sub-issues by logical parent-child, not flat under the root
Linear supports **unlimited sub-issue depth**. A flat list of 8+ siblings under one root is hard to scan. Group by main-subordinate logic:
- Core service → its SDK → SDK consumers
- Don't create a sibling when a child is more accurate
Use `parentId: "LOBE-xxxx"` at creation (or `save_issue` to move). Moving an issue's parent does not disturb its `blockedBy` relations.
### 3. Sub-issue creation order is dictated by `blockedBy`
`blockedBy` requires the blocker to exist first (you need its LOBE-id). So:
1. **Topologically sort** the DAG — leaves (no deps) first, roots last
2. Create issues with zero deps in the first wave
3. Create dependent issues only after collecting the blocker IDs from prior responses
4. `blockedBy` is **append-only**; passing it again does not overwrite — safe to re-run
### 4. Don't waste rounds trying to parallelize
MCP tool calls in a single message look parallel but execute sequentially on the server, and you still need blocker IDs from earlier responses. Just issue calls in dependency order; optimizing for parallelism gains nothing here.
### 5. Keep each sub-issue description self-contained
Each sub-issue should state:
- Goal (12 lines)
- Key files to touch
- Concrete changes / acceptance criteria
- Dependencies (link to blocker issues by `LOBE-xxxx`)
- Validation steps
The implementer may open only the sub-issue, not the parent — don't rely on context that lives only in the parent description.
## Completion Comment Format
Each completed issue gets a comment summarizing the work, so reviewers and future readers don't have to reconstruct it from the PR diff:
```markdown
## Changes Summary
- **Feature**: Brief description of what was implemented
- **Files Changed**: List key files modified
- **PR**: #xxx or PR URL
### Key Changes
- Change 1
- Change 2
- ...
```
This gives team visibility, code-review context, and a paper trail for future reference.
## PR Association
When creating PRs for Linear issues, include magic keywords in the PR body:
- `Fixes LOBE-123`
- `Closes LOBE-123`
- `Resolves LOBE-123`
These trigger Linear's auto-link + auto-close on merge.
## Per-Issue Completion Rule
When working on multiple issues, close out **each one before starting the next** — don't batch all the Linear updates to the end. Batching is where comments get forgotten and issues stay stuck in "In Progress" days after the PR shipped.
For each issue:
1. Complete implementation
2. Run `bun run type-check`
3. Run related tests
4. Create PR if needed
5. Update status to **"In Review"** (not "Done" — "Done" is for after the PR merges)
6. Add the completion comment
7. Move to the next issue
-561
View File
@@ -1,561 +0,0 @@
---
name: local-testing
description: >
Local app and bot testing. Uses agent-browser CLI for Electron/web app UI testing,
and osascript (AppleScript) for controlling native macOS apps (WeChat, Discord, Telegram, Slack, Lark/飞书, QQ)
to test bots. Triggers on 'local test', 'test in electron', 'test desktop', 'test bot',
'bot test', 'test in discord', 'test in telegram', 'test in slack', 'test in weixin',
'test in wechat', 'test in lark', 'test in feishu', 'test in qq',
'manual test', 'osascript', or UI/bot verification tasks.
---
# Local App & Bot Testing
Two approaches for local testing on macOS:
| Approach | Tool | Best For |
| --------------------------- | ------------------- | ---------------------------------------------------- |
| **agent-browser + CDP** | `agent-browser` CLI | Electron apps, web apps (DOM access, JS eval) |
| **osascript (AppleScript)** | `osascript -e` | Native macOS apps (WeChat, Discord, Telegram, Slack) |
---
# Part 1: agent-browser (Electron / Web Apps)
Use `agent-browser` to automate Chromium-based apps via Chrome DevTools Protocol.
Install via `npm i -g agent-browser`, `brew install agent-browser`, or `cargo install agent-browser`. Run `agent-browser install` to download Chrome. Run `agent-browser upgrade` to update.
## Core Workflow
Every browser automation follows this pattern:
1. **Navigate**: `agent-browser open <url>`
2. **Snapshot**: `agent-browser snapshot -i` (get element refs like `@e1`, `@e2`)
3. **Interact**: Use refs to click, fill, select
4. **Re-snapshot**: After navigation or DOM changes, get fresh refs
```bash
agent-browser open https://example.com/form
agent-browser snapshot -i
# Output: @e1 [input type="email"], @e2 [input type="password"], @e3 [button] "Submit"
agent-browser fill @e1 "user@example.com"
agent-browser fill @e2 "password123"
agent-browser click @e3
agent-browser wait --load networkidle
agent-browser snapshot -i # Check result
```
## Command Chaining
```bash
# Chain open + wait + snapshot in one call
agent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser snapshot -i
```
Use `&&` when you don't need to read intermediate output. Run commands separately when you need to parse output first (e.g., snapshot to discover refs, then interact).
## Essential Commands
```bash
# Navigation
agent-browser open <url> # Navigate (aliases: goto, navigate)
agent-browser close # Close browser
agent-browser close --all # Close all active sessions
# Snapshot
agent-browser snapshot -i # Interactive elements with refs (recommended)
agent-browser snapshot -s "#selector" # Scope to CSS selector
# Interaction (use @refs from snapshot)
agent-browser click @e1 # Click element
agent-browser click @e1 --new-tab # Click and open in new tab
agent-browser fill @e2 "text" # Clear and type text
agent-browser type @e2 "text" # Type without clearing
agent-browser select @e1 "option" # Select dropdown option
agent-browser check @e1 # Check checkbox
agent-browser press Enter # Press key
agent-browser keyboard type "text" # Type at current focus (no selector)
agent-browser keyboard inserttext "text" # Insert without key events
agent-browser scroll down 500 # Scroll page
agent-browser scroll down 500 --selector "div.content" # Scroll within container
# Get information
agent-browser get text @e1 # Get element text
agent-browser get url # Get current URL
agent-browser get title # Get page title
agent-browser get cdp-url # Get CDP WebSocket URL
# Wait
agent-browser wait @e1 # Wait for element
agent-browser wait --load networkidle # Wait for network idle
agent-browser wait --url "**/page" # Wait for URL pattern
agent-browser wait 2000 # Wait milliseconds
agent-browser wait --text "Welcome" # Wait for text to appear
agent-browser wait --fn "!document.body.innerText.includes('Loading...')" # Wait for text to disappear
agent-browser wait "#spinner" --state hidden # Wait for element to disappear
# Downloads
agent-browser download @e1 ./file.pdf # Click element to trigger download
agent-browser wait --download ./output.zip # Wait for any download to complete
# Network
agent-browser network requests # Inspect tracked requests
agent-browser network requests --type xhr,fetch # Filter by resource type
agent-browser network requests --method POST # Filter by HTTP method
agent-browser network route "**/api/*" --abort # Block matching requests
agent-browser network har start # Start HAR recording
agent-browser network har stop ./capture.har # Stop and save HAR file
# Viewport & Device Emulation
agent-browser set viewport 1920 1080 # Set viewport size (default: 1280x720)
agent-browser set viewport 1920 1080 2 # 2x retina
agent-browser set device "iPhone 14" # Emulate device (viewport + user agent)
# Capture
agent-browser screenshot # Screenshot to temp dir
agent-browser screenshot --full # Full page screenshot
agent-browser screenshot --annotate # Annotated screenshot with numbered element labels
agent-browser pdf output.pdf # Save as PDF
# Clipboard
agent-browser clipboard read # Read text from clipboard
agent-browser clipboard write "text" # Write text to clipboard
agent-browser clipboard copy # Copy current selection
agent-browser clipboard paste # Paste from clipboard
# Dialogs (alert, confirm, prompt, beforeunload)
agent-browser dialog accept # Accept dialog
agent-browser dialog accept "input" # Accept prompt dialog with text
agent-browser dialog dismiss # Dismiss/cancel dialog
agent-browser dialog status # Check if dialog is open
# Diff (compare page states)
agent-browser diff snapshot # Compare current vs last snapshot
agent-browser diff screenshot --baseline before.png # Visual pixel diff
agent-browser diff url <url1> <url2> # Compare two pages
# Streaming
agent-browser stream enable # Start WebSocket streaming
agent-browser stream status # Inspect streaming state
agent-browser stream disable # Stop streaming
```
## Batch Execution
```bash
echo '[
["open", "https://example.com"],
["snapshot", "-i"],
["click", "@e1"],
["screenshot", "result.png"]
]' | agent-browser batch --json
```
## Authentication
```bash
# Option 1: Auth vault (credentials stored encrypted)
echo "$PASSWORD" | agent-browser auth save myapp --url https://app.example.com/login --username user --password-stdin
agent-browser auth login myapp
# Option 2: Session name (auto-save/restore cookies + localStorage)
agent-browser --session-name myapp open https://app.example.com/login
agent-browser close # State auto-saved
agent-browser --session-name myapp open https://app.example.com/dashboard # Auto-restored
# Option 3: Persistent profile
agent-browser --profile ~/.myapp open https://app.example.com/login
# Option 4: State file
agent-browser state save auth.json
agent-browser state load auth.json
```
### LobeHub dev server — inject better-auth cookie
`agent-browser --headed` on macOS can create an off-screen Chromium window, blocking manual login. For a local LobeHub dev server (e.g. `localhost:3011`), copy the `better-auth.session_token` cookie out of a **Network request** in the user's own Chrome DevTools and load it via `state load`. See [references/agent-browser-login.md](./references/agent-browser-login.md) for the full recipe.
## Semantic Locators (Alternative to Refs)
```bash
agent-browser find text "Sign In" click
agent-browser find label "Email" fill "user@test.com"
agent-browser find role button click --name "Submit"
agent-browser find placeholder "Search" type "query"
agent-browser find testid "submit-btn" click
```
## JavaScript Evaluation (eval)
```bash
# Simple expressions
agent-browser eval 'document.title'
# Complex JS: use --stdin with heredoc (RECOMMENDED)
agent-browser eval --stdin << 'EVALEOF'
JSON.stringify(
Array.from(document.querySelectorAll("img"))
.filter(i => !i.alt)
.map(i => ({ src: i.src.split("/").pop(), width: i.width }))
)
EVALEOF
# Base64 encoding (avoids all shell escaping issues)
agent-browser eval -b "$(echo -n 'document.title' | base64)"
```
## Ref Lifecycle
Refs (`@e1`, `@e2`, etc.) are invalidated when the page changes. Always re-snapshot after clicking links/buttons that navigate, form submissions, or dynamic content loading.
## Annotated Screenshots (Vision Mode)
```bash
agent-browser screenshot --annotate
# Output includes the image path and a legend:
# [1] @e1 button "Submit"
# [2] @e2 link "Home"
agent-browser click @e2 # Click using ref from annotated screenshot
```
## Parallel Sessions
```bash
agent-browser --session site1 open https://site-a.com
agent-browser --session site2 open https://site-b.com
agent-browser session list
```
## Connect to Existing Chrome
```bash
agent-browser --auto-connect snapshot # Auto-discover running Chrome
agent-browser --cdp 9222 snapshot # Explicit CDP port
```
## iOS Simulator (Mobile Safari)
```bash
agent-browser device list
agent-browser -p ios --device "iPhone 16 Pro" open https://example.com
agent-browser -p ios snapshot -i
agent-browser -p ios tap @e1
agent-browser -p ios swipe up
agent-browser -p ios screenshot mobile.png
agent-browser -p ios close
```
## Observability Dashboard
```bash
agent-browser dashboard install
agent-browser dashboard start # Background server on port 4848
agent-browser dashboard stop
```
## Cloud Providers
Use `-p <provider>` to run against cloud browsers: `agentcore`, `browserbase`, `browserless`, `browseruse`, `kernel`.
## Browser Engine Selection
```bash
agent-browser --engine lightpanda open example.com # 10x faster, 10x less memory
```
## Electron (LobeHub Desktop)
### Setup / Teardown
Use the `electron-dev.sh` script to manage the Electron dev environment. It handles process lifecycle, waits for SPA readiness, and reliably kills all child processes (main + helpers + vite).
```bash
SCRIPT=".agents/skills/local-testing/scripts/electron-dev.sh"
# Start Electron dev with CDP (idempotent — skips if already running)
$SCRIPT start
# Check if Electron is running and CDP is reachable
$SCRIPT status
# Kill all Electron-related processes (main + helper + vite)
$SCRIPT stop
# Force fresh restart
$SCRIPT restart
```
After `start` succeeds, connect with: `agent-browser --cdp 9222 snapshot -i`
**Always run `$SCRIPT stop` when done testing**`pkill -f "Electron"` alone won't catch all helper processes.
#### Environment Variables
| Variable | Default | Description |
| ----------------- | ----------------------- | ---------------------------------------- |
| `CDP_PORT` | `9222` | Chrome DevTools Protocol port |
| `ELECTRON_LOG` | `/tmp/electron-dev.log` | Electron process log |
| `ELECTRON_WAIT_S` | `60` | Max seconds to wait for Electron process |
| `RENDERER_WAIT_S` | `60` | Max seconds to wait for SPA to load |
### LobeHub-Specific Patterns
#### Access Zustand Store State
```bash
agent-browser --cdp 9222 eval --stdin << 'EVALEOF'
(function() {
var chat = window.__LOBE_STORES.chat();
var ops = Object.values(chat.operations);
return JSON.stringify({
ops: ops.map(function(o) { return { type: o.type, status: o.status }; }),
activeAgent: chat.activeAgentId,
activeTopic: chat.activeTopicId,
});
})()
EVALEOF
```
#### Find and Use the Chat Input
```bash
# The chat input is contenteditable — must use -C flag
agent-browser --cdp 9222 snapshot -i -C 2>&1 | grep "editable"
agent-browser --cdp 9222 click @e48
agent-browser --cdp 9222 type @e48 "Hello world"
agent-browser --cdp 9222 press Enter
```
#### Wait for Agent to Complete
```bash
agent-browser --cdp 9222 eval --stdin << 'EVALEOF'
(function() {
var chat = window.__LOBE_STORES.chat();
var ops = Object.values(chat.operations);
var running = ops.filter(function(o) { return o.status === 'running'; });
return running.length === 0 ? 'done' : 'running: ' + running.length;
})()
EVALEOF
```
#### Install Error Interceptor
```bash
agent-browser --cdp 9222 eval --stdin << 'EVALEOF'
(function() {
window.__CAPTURED_ERRORS = [];
var orig = console.error;
console.error = function() {
var msg = Array.from(arguments).map(function(a) {
if (a instanceof Error) return a.message;
return typeof a === 'object' ? JSON.stringify(a) : String(a);
}).join(' ');
window.__CAPTURED_ERRORS.push(msg);
orig.apply(console, arguments);
};
return 'installed';
})()
EVALEOF
# Later, check captured errors:
agent-browser --cdp 9222 eval "JSON.stringify(window.__CAPTURED_ERRORS)"
```
## Chrome / Web Apps
```bash
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-test-profile \
"<URL>" &
sleep 5
agent-browser --cdp 9222 snapshot -i
# Or auto-discover running Chrome with remote debugging
agent-browser --auto-connect snapshot -i
```
---
# Part 2: osascript (Native macOS App Bot Testing)
Use AppleScript via `osascript` to control native macOS desktop apps for bot testing. Works with any app that supports macOS Accessibility, no CDP or Chromium needed.
The pattern is the same for every platform:
1. **Activate** the app (`tell application "X" to activate`)
2. **Navigate** to a channel/chat (Quick Switcher `Cmd+K` or Search `Cmd+F`)
3. **Send** a message (clipboard paste `Cmd+V` + Enter)
4. **Wait** for the bot response
5. **Screenshot** for verification (`screencapture` + `Read` tool)
## Per-Platform References
Pick the file for your target platform — each contains activation, navigation, send-message, and verification snippets specific to that app:
Each channel has its own folder under `bot/<channel>/` containing an `index.md`
(activation, navigation, send-message, and verification snippets specific to
that app) and its test script:
| Platform | Reference | Quick switcher |
| ------------- | ------------------------------------------------ | -------------- |
| Discord | [bot/discord/index.md](./bot/discord/index.md) | `Cmd+K` |
| Slack | [bot/slack/index.md](./bot/slack/index.md) | `Cmd+K` |
| Telegram | [bot/telegram/index.md](./bot/telegram/index.md) | `Cmd+F` |
| WeChat / 微信 | [bot/wechat/index.md](./bot/wechat/index.md) | `Cmd+F` |
| Lark / 飞书 | [bot/lark/index.md](./bot/lark/index.md) | `Cmd+K` |
| QQ | [bot/qq/index.md](./bot/qq/index.md) | `Cmd+F` |
For **shared osascript patterns** (activate, type, paste, screenshot, read accessibility, common workflow template, gotchas), see [bot/osascript-common.md](./bot/osascript-common.md). Read this first if you're new to osascript automation.
## Bridge-based channels (no native app)
Some channels have no native app to drive with osascript — they connect through
a local bridge inside the Desktop app. These are tested with agent-browser
(IPC + UI) plus the bridge's own HTTP/REST endpoints, not osascript:
| Channel | Reference | What it drives |
| -------- | ------------------------------------------------ | -------------------------------------------------------- |
| iMessage | [bot/imessage/index.md](./bot/imessage/index.md) | `imessageBridge.*` IPC + local bridge + BlueBubbles REST |
For iMessage there is a one-shot regression script — see `test-imessage-bridge.sh` below.
---
# Scripts
**App / recording scripts** in `.agents/skills/local-testing/scripts/`:
| Script | Usage |
| ------------------------- | --------------------------------------------------- |
| `electron-dev.sh` | Manage Electron dev env (start/stop/status/restart) |
| `record-electron-demo.sh` | Record Electron app demo with ffmpeg |
| `record-app-screen.sh` | Record app screen (video + screenshots, start/stop) |
**Bot scripts** live under `.agents/skills/local-testing/bot/`, one folder per
channel (alongside that channel's `index.md`). The shared
`capture-app-window.sh` sits at the `bot/` root:
| Script | Usage |
| ---------------------------------- | ------------------------------------------------------------------- |
| `capture-app-window.sh` | Capture screenshot of a specific app window (used by bot tests) |
| `discord/test-discord-bot.sh` | Send message to Discord bot via osascript |
| `slack/test-slack-bot.sh` | Send message to Slack bot via osascript |
| `telegram/test-telegram-bot.sh` | Send message to Telegram bot via osascript |
| `wechat/test-wechat-bot.sh` | Send message to WeChat bot via osascript |
| `lark/test-lark-bot.sh` | Send message to Lark / 飞书 bot via osascript |
| `qq/test-qq-bot.sh` | Send message to QQ bot via osascript |
| `imessage/test-imessage-bridge.sh` | Regression-test the iMessage BlueBubbles bridge (IPC + HTTP) |
| `imessage/send-imessage-test.sh` | Send one real iMessage (desktop → BB → iMessage) and verify it sent |
### Window Screenshot Utility
`capture-app-window.sh` captures a screenshot of a specific app window using `screencapture -l <windowID>`. It uses Swift + CGWindowList to find the window by process name, so screenshots work correctly even when the window is on an external monitor or behind other windows.
```bash
# Standalone usage
./.agents/skills/local-testing/bot/capture-app-window.sh "Discord" /tmp/discord.png
./.agents/skills/local-testing/bot/capture-app-window.sh "Slack" /tmp/slack.png
./.agents/skills/local-testing/bot/capture-app-window.sh "WeChat" /tmp/wechat.png
```
All bot test scripts use this utility automatically for their screenshots.
### Bot Test Scripts
All bot test scripts share the same interface:
```bash
./scripts/test-<platform>-bot.sh <channel_or_contact> <message> [wait_seconds] [screenshot_path]
```
Examples:
```bash
# Discord — test a bot in #bot-testing channel
./.agents/skills/local-testing/bot/discord/test-discord-bot.sh "bot-testing" "!ping"
./.agents/skills/local-testing/bot/discord/test-discord-bot.sh "bot-testing" "/ask Tell me a joke" 30
# Slack — test a bot in #bot-testing channel
./.agents/skills/local-testing/bot/slack/test-slack-bot.sh "bot-testing" "@mybot hello"
./.agents/skills/local-testing/bot/slack/test-slack-bot.sh "bot-testing" "/ask What is 2+2?" 20
# Telegram — test a bot by username
./.agents/skills/local-testing/bot/telegram/test-telegram-bot.sh "MyTestBot" "/start"
./.agents/skills/local-testing/bot/telegram/test-telegram-bot.sh "GPTBot" "Hello" 60
# WeChat — test a bot or send to a contact
./.agents/skills/local-testing/bot/wechat/test-wechat-bot.sh "文件传输助手" "test message" 5
./.agents/skills/local-testing/bot/wechat/test-wechat-bot.sh "MyBot" "Tell me a joke" 30
# Lark/飞书 — test a bot in a group chat
./.agents/skills/local-testing/bot/lark/test-lark-bot.sh "bot-testing" "@MyBot hello"
./.agents/skills/local-testing/bot/lark/test-lark-bot.sh "bot-testing" "Help me with this" 30
# QQ — test a bot in a group or direct chat
./.agents/skills/local-testing/bot/qq/test-qq-bot.sh "bot-testing" "Hello bot" 15
./.agents/skills/local-testing/bot/qq/test-qq-bot.sh "MyBot" "/help" 10
```
Each script: activates the app, navigates to the channel/contact, pastes the message via clipboard, sends, waits, and takes a screenshot. Use the `Read` tool on the screenshot for visual verification.
### iMessage bridge regression script
`test-imessage-bridge.sh` does **not** follow the osascript bot interface — it
drives the Desktop bridge's IPC + HTTP layers and asserts the result, then
self-cleans. Needs BlueBubbles running and Electron up with CDP.
```bash
./.agents/skills/local-testing/bot/imessage/test-imessage-bridge.sh '<bluebubbles_password>' [bb_url] [cdp_port]
# defaults: bb_url=http://127.0.0.1:1234 cdp_port=9222 — exit 0 = all green
```
It guards the connect/configure flow (testConfig happy + reject paths, first-time
`upsertConfig` save, bridge running + webhook registered, local-server secret
enforcement). See [bot/imessage/index.md](./bot/imessage/index.md)
for the full manual UI flow and known bugs.
---
# Screen Recording
Record automated demos using `record-app-screen.sh` (start/stop lifecycle, CDP screenshots + ffmpeg assembly). See [references/record-app-screen.md](references/record-app-screen.md) for full documentation.
```bash
./.agents/skills/local-testing/scripts/electron-dev.sh start
./.agents/skills/local-testing/scripts/record-app-screen.sh start my-demo
# ... run automation ...
./.agents/skills/local-testing/scripts/record-app-screen.sh stop
```
Outputs to `.records/` directory (gitignored): `<name>.mp4` (video) + `<name>/` (screenshots every 3s).
---
# Gotchas
### agent-browser
- **Daemon can get stuck** — if commands hang, `agent-browser close --all` or `pkill -f agent-browser` to reset
- **HMR invalidates everything** — after code changes, refs break. Re-snapshot or restart
- **`snapshot -i` doesn't find contenteditable** — use `snapshot -i -C` for rich text editors
- **`fill` doesn't work on contenteditable** — use `type` for chat inputs
- **Screenshots go to `~/.agent-browser/tmp/screenshots/`** — read them with the `Read` tool
- **Dialogs block all commands** — if commands time out, check `agent-browser dialog status`
- **Default timeout is 25s** — override with `AGENT_BROWSER_DEFAULT_TIMEOUT` (ms) or use explicit waits
- **Shell quoting corrupts eval** — use `eval --stdin <<'EVALEOF'` for complex JS
### Electron-specific
- **Always use `electron-dev.sh stop` to clean up** — `pkill -f "Electron"` only kills the main process; helper processes (GPU, renderer, network) survive. The script finds and kills all of them via PID matching against the project's electron binary path.
- **`npx electron-vite dev` must run from `apps/desktop/`** — running from project root fails silently. The `electron-dev.sh` script handles this automatically.
- **Don't resize the Electron window after load** — resizing triggers full SPA reload
- **Store is at `window.__LOBE_STORES`** not `window.__ZUSTAND_STORES__`
### osascript
See [bot/osascript-common.md](./bot/osascript-common.md#gotchas) for the full osascript gotchas list (accessibility permissions, `keystroke` non-ASCII issues, locale-specific app names, rate limiting, etc.).
@@ -1,54 +0,0 @@
#!/usr/bin/env bash
#
# capture-app-window.sh — Capture a screenshot of a specific app window
#
# Uses CGWindowList via Swift to find the window by process name, then
# screencapture -l <windowID> to capture only that window.
# Falls back to full-screen capture if the window is not found.
#
# Usage:
# ./capture-app-window.sh <process_name> <output_path>
#
# Arguments:
# process_name — The process/owner name as shown in Activity Monitor
# (e.g., "Discord", "Slack", "Telegram", "WeChat", "QQ", "Lark")
# output_path — Path to save the screenshot (e.g., /tmp/screenshot.png)
#
# Examples:
# ./capture-app-window.sh "Discord" /tmp/discord.png
# ./capture-app-window.sh "Slack" /tmp/slack.png
# ./capture-app-window.sh "微信" /tmp/wechat.png
#
set -euo pipefail
PROCESS="${1:?Usage: capture-app-window.sh <process_name> <output_path>}"
OUTPUT="${2:?Usage: capture-app-window.sh <process_name> <output_path>}"
# Find the CGWindowID for the target process using Swift + CGWindowList
# Pass process name via environment variable (swift -e doesn't support -- args)
WINDOW_ID=$(TARGET_PROCESS="$PROCESS" swift -e '
import Cocoa
import Foundation
let target = ProcessInfo.processInfo.environment["TARGET_PROCESS"] ?? ""
let windowList = CGWindowListCopyWindowInfo([.optionAll], kCGNullWindowID) as! [[String: Any]]
for w in windowList {
let owner = w["kCGWindowOwnerName"] as? String ?? ""
let layer = w["kCGWindowLayer"] as? Int ?? -1
let bounds = w["kCGWindowBounds"] as? [String: Any] ?? [:]
let ww = bounds["Width"] as? Double ?? 0
let wh = bounds["Height"] as? Double ?? 0
let wid = w["kCGWindowNumber"] as? Int ?? 0
// Match process name, normal window layer (0), and reasonable size
if owner == target && layer == 0 && ww > 200 && wh > 200 {
print(wid)
break
}
}
' 2>/dev/null || true)
if [ -n "$WINDOW_ID" ]; then
screencapture -l "$WINDOW_ID" -x "$OUTPUT"
else
echo "[capture] Warning: Could not find window for '$PROCESS', falling back to full screen"
screencapture -x "$OUTPUT"
fi
@@ -1,97 +0,0 @@
# Discord Bot Testing
**App name:** `Discord` | **Process name:** `Discord`
See [osascript-common.md](../osascript-common.md) for shared patterns.
## Activate & Navigate
```bash
# Activate Discord
osascript -e 'tell application "Discord" to activate'
sleep 1
# Open Quick Switcher (Cmd+K) to navigate to a channel
osascript -e 'tell application "System Events" to keystroke "k" using command down'
sleep 0.5
osascript -e 'tell application "System Events" to keystroke "bot-testing"'
sleep 1
osascript -e 'tell application "System Events" to key code 36' # Enter
sleep 2
```
## Send Message to Bot
```bash
# The message input is focused after navigating to a channel
# Type a message
osascript -e 'tell application "System Events" to keystroke "/hello"'
sleep 0.5
osascript -e 'tell application "System Events" to key code 36' # Enter
```
## Send Long Message (via clipboard)
```bash
osascript -e '
tell application "Discord" to activate
delay 0.5
set the clipboard to "Write a 3000 word essay about space exploration"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36 -- Enter
end tell
'
```
## Verify Bot Response
```bash
# Wait for bot to respond, then screenshot
sleep 10
screencapture /tmp/discord-bot-response.png
# Read with the Read tool for visual verification
```
## Full Bot Test Example
```bash
#!/usr/bin/env bash
# test-discord-bot.sh — Send message and verify bot response
# 1. Activate Discord and navigate to channel
osascript -e '
tell application "Discord" to activate
delay 1
-- Quick Switcher
tell application "System Events" to keystroke "k" using command down
delay 0.5
tell application "System Events" to keystroke "bot-testing"
delay 1
tell application "System Events" to key code 36
delay 2
'
# 2. Send test message
osascript -e '
set the clipboard to "!ping"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36
end tell
'
# 3. Wait for response and capture
sleep 5
screencapture /tmp/discord-test-result.png
echo "Screenshot saved to /tmp/discord-test-result.png"
```
## Script
```bash
./.agents/skills/local-testing/bot/discord/test-discord-bot.sh "bot-testing" "!ping"
./.agents/skills/local-testing/bot/discord/test-discord-bot.sh "bot-testing" "/ask Tell me a joke" 30
```
@@ -1,64 +0,0 @@
#!/usr/bin/env bash
#
# test-discord-bot.sh — Send a message to a Discord bot and capture the response
#
# Usage:
# ./scripts/test-discord-bot.sh <channel> <message> [wait_seconds] [screenshot_path]
#
# channel — Channel name to navigate to via Quick Switcher (Cmd+K)
# message — Message to send to the bot
# wait_seconds — Seconds to wait for bot response (default: 10)
# screenshot_path — Output screenshot path (default: /tmp/discord-bot-test.png)
#
# Prerequisites:
# - Discord desktop app installed and logged in
# - Accessibility permission granted (System Preferences > Privacy > Accessibility)
#
# Examples:
# ./scripts/test-discord-bot.sh "bot-testing" "!ping"
# ./scripts/test-discord-bot.sh "bot-testing" "/ask Tell me a joke" 30
# ./scripts/test-discord-bot.sh "general" "Hello bot" 15 /tmp/my-test.png
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CHANNEL="${1:?Usage: test-discord-bot.sh <channel> <message> [wait_seconds] [screenshot_path]}"
MESSAGE="${2:?Usage: test-discord-bot.sh <channel> <message> [wait_seconds] [screenshot_path]}"
WAIT="${3:-10}"
SCREENSHOT="${4:-/tmp/discord-bot-test.png}"
APP="Discord"
echo "[$APP] Activating..."
osascript -e "tell application \"$APP\" to activate"
sleep 1
echo "[$APP] Navigating to channel: $CHANNEL"
osascript -e '
tell application "System Events"
-- Quick Switcher
keystroke "k" using command down
delay 0.8
keystroke "'"$CHANNEL"'"
delay 1.5
key code 36 -- Enter
end tell
'
sleep 2
echo "[$APP] Sending message: $MESSAGE"
osascript -e '
set the clipboard to "'"$MESSAGE"'"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36 -- Enter
end tell
'
echo "[$APP] Waiting ${WAIT}s for bot response..."
sleep "$WAIT"
echo "[$APP] Capturing screenshot..."
"$SCRIPT_DIR/../capture-app-window.sh" "$APP" "$SCREENSHOT"
echo "[$APP] Done! Screenshot saved to $SCREENSHOT"
@@ -1,232 +0,0 @@
# iMessage Desktop bridge regression test
The iMessage channel is different from the other bot platforms: there is **no
native app to drive with osascript**. Instead the Desktop app runs a local
**BlueBubbles bridge** — a small HTTP server in the Electron main process that
registers a webhook on a local [BlueBubbles](https://bluebubbles.app/) server,
receives iMessage events, and forwards them to LobeHub Cloud.
So the test surface is three layers:
1. **Electron main IPC**`imessageBridge.*` handlers (`getStatus`,
`testConfig`, `upsertConfig`, `removeConfig`, `start`, `stop`)
2. **Local bridge HTTP server**`http://127.0.0.1:<port>/webhooks/bluebubbles/<appId>?secret=<secret>`
3. **BlueBubbles REST API**`http://127.0.0.1:1234/api/v1/*` (webhook + server/info)
## Prerequisites
- A running **BlueBubbles server** (macOS, default `http://127.0.0.1:1234`) with
a known password. Sanity check:
```bash
curl -sS -m4 -o /dev/null -w '%{http_code}\n' \
"http://127.0.0.1:1234/api/v1/server/info?password=<PW>" # expect 200
```
- **Electron dev running with CDP**: `./.agents/skills/local-testing/scripts/electron-dev.sh start`
- The **iMessage Desktop branch** checked out (the `imessageBridge` IPC group
and `@lobechat/chat-adapter-imessage` must be compiled into the main bundle).
Run `pnpm install --ignore-scripts` at the repo root **and** in `apps/desktop/`
after switching branches — the new workspace package must be linked or the
main build fails to resolve `@lobechat/chat-adapter-imessage`.
## Fast path: automated script
```bash
./.agents/skills/local-testing/bot/imessage/test-imessage-bridge.sh '<bluebubbles_password>' [bb_url] [cdp_port]
```
Asserts the whole flow and self-cleans (unique `applicationId` per run, removes
its bridge config + BlueBubbles webhook on exit). Exit 0 = all green. It covers:
- BlueBubbles reachable + password valid; Electron CDP reachable; IPC available
- `testConfig` happy path → success
- `testConfig` wrong password → rejected; unreachable URL → rejected
- `upsertConfig` **first-time save → success** (Bug #1 regression guard, below)
- `getStatus` → `running:true`, config persisted, password redacted (`blueBubblesPasswordSet`)
- BlueBubbles webhook actually registered for the appId
- Local bridge HTTP server: wrong secret → 401; valid secret → past auth
The password is passed as argv (visible in `ps`) — local dev only, don't use a
real secret on a shared machine.
## Layer 1 — IPC probes (no UI)
The renderer exposes the main-process handlers via `window.electronAPI.invoke`.
This is the quickest way to exercise the bridge without clicking:
```bash
# baseline
agent-browser --cdp 9222 eval \
"(async()=>JSON.stringify(await window.electronAPI.invoke('imessageBridge.getStatus',{})))()"
# test a connection (note: password as a JS string)
agent-browser --cdp 9222 eval --stdin << 'EVALEOF'
(async function () {
try {
var r = await window.electronAPI.invoke('imessageBridge.testConfig', {
applicationId: 'probe',
blueBubblesServerUrl: 'http://127.0.0.1:1234',
blueBubblesPassword: 'PASTE_PW',
enabled: true,
webhookSecret: 'probe-secret',
});
return JSON.stringify(r); // { success: true }
} catch (e) { return 'ERR: ' + (e.message || e); }
})()
EVALEOF
```
`upsertConfig` persists to the Electron store, starts the local HTTP server, and
registers the BlueBubbles webhook. `removeConfig` + `stop` reverse it.
## Layer 2 — full UI flow (agent-browser)
The bridge settings only render in Desktop (`isDesktop` guard) under the agent's
**Channel → iMessage** screen. The platform tile only appears as a real (non
"Coming Soon") entry once the server registers `imessage` **and** the frontend
drops it from `COMING_SOON_PLATFORMS` (`src/routes/(main)/agent/channel/const.ts`).
```bash
agent-browser --cdp 9222 open "http://localhost:5173/agent/<aid>/channel"
agent-browser --cdp 9222 wait --load networkidle && agent-browser --cdp 9222 wait 1500
# confirm the remote backend lists imessage (it must be registered + deployed)
agent-browser --cdp 9222 eval --stdin << 'EVALEOF'
(async function(){
var url='lobe-backend://lobe/trpc/lambda/agentBotProvider.listPlatforms?input='+encodeURIComponent('{"json":null,"meta":{"values":["undefined"],"v":1}}');
var d=await (await fetch(url,{credentials:'include'})).json();
var p=d.result?.data?.json||d;
return JSON.stringify(p.map(function(x){return x.id;}));
})()
EVALEOF
# click the iMessage tile, then fill the form by ref
agent-browser --cdp 9222 eval "(()=>{var b=[...document.querySelectorAll('aside button')].find(x=>/imessage/i.test(x.textContent));b&&b.click();})()"
agent-browser --cdp 9222 wait 1500
agent-browser --cdp 9222 snapshot -i | grep -iE "127.0.0.1:1234|Application ID|Webhook Secret|Test BlueBubbles|Save Bridge"
```
Field refs (from the snapshot): Application ID, Webhook Secret, BlueBubbles
Server URL (`placeholder="http://127.0.0.1:1234"`), and a **nested** textbox right
under the URL one is the BlueBubbles Password. Fill with `fill` (real input
events — `eval`-setting React inputs won't fire onChange), click **Test
BlueBubbles**, then **Save Bridge**. Read the antd toast immediately (it
auto-dismisses):
```bash
agent-browser --cdp 9222 eval \
"JSON.stringify([...new Set([...document.querySelectorAll('.ant-message-custom-content')].map(n=>n.textContent.trim()))])"
# Test → "BlueBubbles connection passed"
# Save → "iMessage Desktop bridge saved"
```
Verify the end state via BlueBubbles + IPC:
```bash
curl -sS "http://127.0.0.1:1234/api/v1/webhook?password=<PW>" # webhook for the appId present
agent-browser --cdp 9222 eval "(async()=>JSON.stringify(await window.electronAPI.invoke('imessageBridge.getStatus',{})))()"
# running:true, serverUrl: http://127.0.0.1:33270, configs[].blueBubblesPasswordSet:true
```
Cleanup: `removeConfig` + `stop` via IPC, then `DELETE /api/v1/webhook/<id>` on
BlueBubbles.
## Outbound send test (desktop → BlueBubbles → iMessage)
Verifies the leg the bridge uses to _reply_: `BlueBubblesApiClient.sendText`
→ `POST /api/v1/message/text`. Run the helper against your own number:
```bash
./.agents/skills/local-testing/bot/imessage/send-imessage-test.sh '<bb_password>' '+<E164>' # e.g. +15551234567
```
**Gotcha that bites everyone:** with `method=apple-script` and a _new_
conversation, the HTTP POST often **times out** even though the message is
sent. Never judge success by the HTTP response. Instead poll
`POST /api/v1/message/query` and read the matching `isFromMe:true` row's
`error` field:
- `error: 0` (or null) → sent OK
- non-zero `error` → real send failure
The script does exactly this: fires the send, ignores the timeout, then matches
its marker text in the message store and asserts `error == 0`.
Two more notes:
- Use a full E.164 handle (`iMessage;-;+<countrycode><number>`) or an Apple ID
email. Looking the chat up by guid afterwards may 404 if BB filed the message
under a differently-formatted guid — that's a lookup quirk, not a send failure.
- Sending to _your own_ number round-trips: BB records both the outgoing
(`fromMe:true`) and an incoming copy (`fromMe:false`).
## Inbound e2e test (iMessage → cloud agent → reply)
Full inbound chain: a message arrives → BlueBubbles fires its `new-message`
webhook → local bridge (`:33270`) → `forwardWebhook` POSTs to
`<remote>/api/agent/webhooks/imessage/<appId>?secret=…` → cloud agent → reply
flows back via Device Gateway → BB `sendText`.
Prerequisites:
- A cloud bot provider for the same `applicationId` exists and is **connected**
(Save Configuration + the device gateway connected — a _disconnected_ gateway
yields `DEVICE_NOT_FOUND` on connect and blocks the reply leg).
- The `imessage` Labs toggle is on (otherwise the channel is gated to "Coming
Soon"), and `webhookSecret` matches on both ends (auto-generated on save).
Two ways to drive it:
1. **Second device / Apple ID (recommended).** Have _another_ Apple ID message
the BB-hosted number (e.g. "please reply pong"). The bot replies; you see it
on the other device. **No loop risk** — the reply goes to the other party,
not back to itself.
2. **Send to your own number (quick, loop-aware).** `sendText` to the hosted
number; the loopback _incoming_ copy (`isFromMe:false`) triggers the bot.
Watch the reply land in `message/query` as a `fromMe:true` row.
**Loop guard — why a self-send doesn't spin forever:** the Chat SDK adapter
drops any `isFromMe` message before dispatch
(`packages/chat-adapter-imessage/src/adapter.ts`: `if (message.isFromMe) return`).
The bot's own reply (`isFromMe:true`) is never re-processed, so in the normal
case (someone else → bot → reply to them) there is no loop. The self-send case
is a **test-only edge**: the bot's reply also round-trips to your number, and
only the adapter's `isFromMe` check stops a second pass. Keep the prompt
conversational (so the bot doesn't keep finding something to answer), and
**turn the `imessage` lab off / remove the config when done** — never leave a
self-send bot running unattended.
Watch the chain live:
```bash
tail -f /tmp/electron-dev.log | grep -iE "imessage|bridge|forward|Message API"
# the agent reply shows up as a fromMe:true row with the bot's text:
curl -sS -X POST "http://127.0.0.1:1234/api/v1/message/query?password=<PW>" \
-H 'Content-Type: application/json' -d '{"limit":5,"sort":"DESC"}'
```
`startTyping` will log a Private-API error unless BlueBubbles has the Private
API helper set up (needs a jailbroken / SIP-disabled Mac) — it's logged and
ignored; text replies still work.
## Known bugs / gotchas
- **Bug #1 — first-time save (fixed; guarded by the script).** BlueBubbles'
`GET /api/v1/webhook?url=<unregistered>` returns **HTTP 500**
(`Cannot read properties of null (reading 'events')`). The bridge must list
**all** webhooks and match client-side, never pass the `?url=` filter. If you
see `upsertConfig` fail with "An unhandled error has occurred!" originating in
`listWebhooks`, this regressed.
- **Save leaves a half-state on webhook failure.** `upsertConfig` writes the
config + starts the HTTP server _before_ registering the webhook, so a webhook
failure still reports `running:true` with the config persisted but no
BlueBubbles webhook. Always assert the BlueBubbles webhook list, not just IPC
status.
- **Unknown appId / forward failure → 500.** Posting to the local bridge for an
unknown appId, or when no cloud bot is bound, returns 500 (BlueBubbles retries
on 5xx). Auth (wrong secret → 401) is enforced before that.
- **Backend deploy lag.** Desktop dev proxies tRPC through `lobe-backend://` to
the _remote_ server. iMessage only appears in `listPlatforms` once the server
registration is deployed there, regardless of local branch.
- **Restart to load main-process fixes.** Editing `imessageBridgeSrv.ts` /
`@lobechat/chat-adapter-imessage` needs `electron-dev.sh restart` — main isn't
hot-replaced. On restart, enabled configs auto-register their webhook again.
@@ -1,81 +0,0 @@
#!/usr/bin/env bash
#
# send-imessage-test.sh — Verify the outbound leg: desktop → BlueBubbles → iMessage
#
# Sends one real iMessage via the same REST call the Desktop bridge uses
# (`POST /api/v1/message/text`, which BlueBubblesApiClient.sendText wraps) and
# confirms it actually went out.
#
# KEY GOTCHA: with method=apple-script and a NEW conversation, the HTTP request
# often TIMES OUT even though the message is sent. Do NOT treat the timeout as a
# failure — instead poll `POST /api/v1/message/query` and check the message's
# `error` field (0 = sent OK). This script does that for you.
#
# This sends a REAL message, so it has side effects. Target your own number.
#
# Usage:
# ./send-imessage-test.sh <bb_password> <target_e164> [message] [bb_url]
#
# Example (send to your own phone, E.164 with country code):
# ./send-imessage-test.sh 'my-bb-pass' '+15551234567'
#
set -euo pipefail
BB_PASS="${1:?Usage: $0 <bb_password> <target_e164(+countrycode)> [message] [bb_url]}"
TARGET="${2:?Need a target handle in E.164, e.g. +15551234567 (or an Apple ID email)}"
MARKER="lobe-imsg-test-$(date +%s)"
MESSAGE="${3:-[${MARKER}] desktop bridge → BlueBubbles → iMessage outbound check}"
BB_URL="${4:-http://127.0.0.1:1234}"
CHAT_GUID="iMessage;-;${TARGET}"
echo "[send-test] target=${TARGET} marker=${MARKER}"
# 1) Fire the send. apple-script on a new chat may hang the HTTP response, so we
# cap it short and ignore a timeout — step 2 is the source of truth.
python3 - "$BB_PASS" "$BB_URL" "$CHAT_GUID" "$MESSAGE" <<'PY' || true
import json,sys,urllib.request,urllib.parse,uuid
pw,base,guid,msg=sys.argv[1:5]
url=base+"/api/v1/message/text?password="+urllib.parse.quote(pw)
body={"chatGuid":guid,"message":msg,"method":"apple-script","tempGuid":str(uuid.uuid4())}
req=urllib.request.Request(url,data=json.dumps(body).encode("utf-8"),
headers={"Content-Type":"application/json"},method="POST")
try:
r=urllib.request.urlopen(req,timeout=8)
print("[send-test] HTTP",r.status,"(immediate response)")
except urllib.error.HTTPError as e:
print("[send-test] HTTP",e.code,e.read().decode()[:200])
except Exception as e:
print("[send-test] HTTP request returned no body (likely apple-script delay):",type(e).__name__)
PY
# 2) Source of truth: find our marker in the message store and read its error.
echo "[send-test] verifying via message/query (the HTTP timeout above is expected)…"
sleep 3
python3 - "$BB_PASS" "$BB_URL" "$MARKER" <<'PY'
import json,sys,time,urllib.request,urllib.parse
pw,base,marker=sys.argv[1:4]
url=base+"/api/v1/message/query?password="+urllib.parse.quote(pw)
def query():
body={"limit":15,"offset":0,"with":["chats"],"sort":"DESC"}
req=urllib.request.Request(url,data=json.dumps(body).encode(),
headers={"Content-Type":"application/json"},method="POST")
return json.load(urllib.request.urlopen(req,timeout=12)).get("data") or []
hit=None
for _ in range(5):
for m in query():
if marker in (m.get("text") or "") and m.get("isFromMe"):
hit=m; break
if hit: break
time.sleep(2)
if not hit:
print("[send-test] ✗ outbound message not found in BB store — send likely failed")
sys.exit(1)
err=hit.get("error")
if err in (0,None):
print("[send-test] ✓ outbound message sent (fromMe=True, error=%s)"%err)
print("[send-test] → confirm it arrived in the Messages app on the target device")
else:
print("[send-test] ✗ BlueBubbles reported send error=%s"%err)
sys.exit(1)
PY
@@ -1,187 +0,0 @@
#!/usr/bin/env bash
#
# test-imessage-bridge.sh — Regression test for the iMessage Desktop bridge
#
# Drives the Electron main-process `imessageBridge.*` IPC handlers plus the
# local bridge HTTP server and the BlueBubbles server, asserting the full
# connect/configure flow. Use it to regression-test PR work on the iMessage
# channel (BlueBubbles bridge) without clicking through the UI every time.
#
# Prerequisites:
# 1. BlueBubbles server running and reachable (default http://127.0.0.1:1234)
# 2. Electron dev running with CDP — `electron-dev.sh start`
# 3. `agent-browser` on PATH, connected to the same CDP port
#
# Usage:
# ./test-imessage-bridge.sh <bluebubbles_password> [bb_url] [cdp_port]
#
# Example:
# ./test-imessage-bridge.sh 'my-bb-password'
# ./test-imessage-bridge.sh 'my-bb-password' http://127.0.0.1:1234 9222
#
# Notes:
# - The password is passed as an argv, so it is visible in `ps`. This is a
# local dev tool; do not run it on shared machines with a real secret.
# - It uses a unique applicationId per run (imsg-regression-$$) and cleans up
# its own bridge config + BlueBubbles webhook on exit, so it is safe to
# re-run and does not disturb real configs.
set -euo pipefail
BB_PASS="${1:?Usage: $0 <bluebubbles_password> [bb_url] [cdp_port]}"
BB_URL="${2:-http://127.0.0.1:1234}"
CDP_PORT="${3:-9222}"
APP_ID="imsg-regression-$$"
SECRET="regression-secret-$$"
PASS=0
FAIL=0
# ── Output helpers ───────────────────────────────────────────────────
ok() { echo "$1"; PASS=$((PASS + 1)); }
bad() { echo "$1$2"; FAIL=$((FAIL + 1)); }
note() { echo "[imsg-test] $1"; }
# ── BlueBubbles REST helpers ─────────────────────────────────────────
bb_get_webhooks() {
curl -sS -m 8 "${BB_URL}/api/v1/webhook?password=${BB_PASS}"
}
# Delete every webhook whose URL mentions our APP_ID (cleanup is idempotent).
bb_cleanup_webhooks() {
local ids
ids=$(bb_get_webhooks | python3 -c '
import json,sys
try: d=json.load(sys.stdin)
except Exception: sys.exit(0)
for w in (d.get("data") or []):
if "'"$APP_ID"'" in (w.get("url") or ""): print(w["id"])
' 2>/dev/null || true)
for id in $ids; do
curl -sS -m 8 -X DELETE "${BB_URL}/api/v1/webhook/${id}?password=${BB_PASS}" >/dev/null 2>&1 || true
done
}
# ── IPC helper (drives the Electron renderer's electronAPI bridge) ───
# Runs a JS snippet that returns a string token; prints the raw token.
# The BlueBubbles password is base64-injected (atob) so special chars in the
# secret never need shell/JS quoting.
ipc_eval() {
local js="$1"
agent-browser --cdp "$CDP_PORT" eval -b "$(printf '%s' "$js" | base64)" 2>/dev/null
}
PASS_B64=$(printf '%s' "$BB_PASS" | base64)
# Emit an inline JS object literal for the bridge config. $1 overrides the
# password expression (defaults to atob of the real password); pass a JS string
# literal like "'wrong'" to test the rejection path.
ipc_config_js() {
local pwexpr="${1:-atob('${PASS_B64}')}"
printf "{applicationId:'%s',blueBubblesServerUrl:'%s',blueBubblesPassword:%s,enabled:true,webhookSecret:'%s'}" \
"$APP_ID" "$BB_URL" "$pwexpr" "$SECRET"
}
# ── Preflight ────────────────────────────────────────────────────────
note "BlueBubbles: ${BB_URL} CDP: ${CDP_PORT} appId: ${APP_ID}"
code=$(curl -sS -m 6 -o /dev/null -w '%{http_code}' \
"${BB_URL}/api/v1/server/info?password=${BB_PASS}" || echo 000)
if [ "$code" = "200" ]; then ok "BlueBubbles reachable + password valid"; else
bad "BlueBubbles preflight" "HTTP $code (is BlueBubbles running on ${BB_URL}?)"
echo "Aborting — fix BlueBubbles first."; exit 1
fi
if ! curl -sf --max-time 3 "http://localhost:${CDP_PORT}/json/version" >/dev/null 2>&1; then
bad "Electron CDP preflight" "CDP ${CDP_PORT} unreachable — run electron-dev.sh start"
echo "Aborting."; exit 1
fi
ok "Electron CDP reachable"
# Bridge must expose the IPC group (built from this branch's code).
probe=$(ipc_eval "(async()=>{try{var s=await window.electronAPI.invoke('imessageBridge.getStatus',{});return 'OK:'+JSON.stringify(s);}catch(e){return 'ERR:'+(e.message||e);}})()")
case "$probe" in
*OK:*) ok "imessageBridge IPC available" ;;
*) bad "imessageBridge IPC" "got: $probe (is the iMessage Desktop branch checked out?)"; echo "Aborting."; exit 1 ;;
esac
# Start clean: remove any leftover config for this appId + BB webhooks.
ipc_eval "(async()=>{try{await window.electronAPI.invoke('imessageBridge.removeConfig',{applicationId:'${APP_ID}'});}catch(e){}return 'done';})()" >/dev/null
bb_cleanup_webhooks
# ── testConfig: happy path ───────────────────────────────────────────
r=$(ipc_eval "(async()=>{try{var c=$(ipc_config_js);var x=await window.electronAPI.invoke('imessageBridge.testConfig',c);return 'OK:'+JSON.stringify(x);}catch(e){return 'ERR:'+(e.message||e);}})()")
case "$r" in
*OK:*success*true*) ok "testConfig with valid password → success" ;;
*) bad "testConfig (valid)" "got: $r" ;;
esac
# ── testConfig: wrong password rejects ───────────────────────────────
r=$(ipc_eval "(async()=>{try{var c=$(ipc_config_js "'definitely-wrong-password'");var x=await window.electronAPI.invoke('imessageBridge.testConfig',c);return 'OK:'+JSON.stringify(x);}catch(e){return 'ERR:'+(e.message||e);}})()")
case "$r" in
*ERR:*) ok "testConfig with wrong password → rejected" ;;
*) bad "testConfig (wrong password)" "expected rejection, got: $r" ;;
esac
# ── testConfig: unreachable URL rejects ──────────────────────────────
r=$(ipc_eval "(async()=>{try{var x=await window.electronAPI.invoke('imessageBridge.testConfig',{applicationId:'${APP_ID}',blueBubblesServerUrl:'http://127.0.0.1:65530',blueBubblesPassword:atob('${PASS_B64}'),enabled:true,webhookSecret:'${SECRET}'});return 'OK:'+JSON.stringify(x);}catch(e){return 'ERR:'+(e.message||e);}})()")
case "$r" in
*ERR:*) ok "testConfig with unreachable URL → rejected" ;;
*) bad "testConfig (unreachable)" "expected rejection, got: $r" ;;
esac
# ── upsertConfig: FIRST-TIME registration (Bug #1 regression guard) ──
# BlueBubbles' GET /webhook?url=<unregistered> returns HTTP 500. The bridge
# must list ALL webhooks and match client-side, otherwise this first save
# fails. This assertion guards that fix.
r=$(ipc_eval "(async()=>{try{var c=$(ipc_config_js);var x=await window.electronAPI.invoke('imessageBridge.upsertConfig',c);return 'OK:'+JSON.stringify(x);}catch(e){return 'ERR:'+(e.message||e);}})()")
case "$r" in
*OK:*success*true*) ok "upsertConfig first-time save → success (Bug #1 guard)" ;;
*) bad "upsertConfig (first-time)" "got: $r" ;;
esac
# ── getStatus: bridge running + config persisted ─────────────────────
# Return a quote-free token so grep isn't tripped up by agent-browser's
# JSON-string escaping of the eval result.
r=$(ipc_eval "(async()=>{var s=await window.electronAPI.invoke('imessageBridge.getStatus',{});var c=(s.configs||[]).find(function(x){return x.applicationId==='${APP_ID}';});return 'RUN='+(s.running?'Y':'N')+' CFG='+(c?'Y':'N')+' PW='+((c&&c.blueBubblesPasswordSet)?'Y':'N');})()")
echo "$r" | grep -q 'RUN=Y' && ok "bridge running" || bad "bridge running" "got: $r"
echo "$r" | grep -q 'CFG=Y' && ok "config persisted" || bad "config persisted" "got: $r"
echo "$r" | grep -q 'PW=Y' && ok "password stored (redacted in status)" || bad "password stored" "got: $r"
# ── BlueBubbles webhook actually registered ──────────────────────────
if bb_get_webhooks | grep -q "${APP_ID}"; then
ok "BlueBubbles webhook registered for appId"
else
bad "BlueBubbles webhook" "no webhook URL containing ${APP_ID}"
fi
# ── Local bridge HTTP server: secret enforcement ─────────────────────
BRIDGE_URL=$(ipc_eval "(async()=>{var s=await window.electronAPI.invoke('imessageBridge.getStatus',{});return s.serverUrl||'';})()" | tr -d '"')
if [ -n "$BRIDGE_URL" ]; then
# wrong secret → 401
code=$(curl -sS -m 6 -o /dev/null -w '%{http_code}' -X POST \
-H 'Content-Type: application/json' \
"${BRIDGE_URL}/webhooks/bluebubbles/${APP_ID}?secret=WRONG" \
-d '{"type":"new-message","data":{"guid":"x"}}' || echo 000)
[ "$code" = "401" ] && ok "local bridge rejects wrong secret (401)" || bad "local bridge wrong secret" "expected 401, got $code"
# right secret → passes auth (reaches forward; without a bound cloud bot it
# returns 5xx — that's fine, we're only asserting auth + routing here)
code=$(curl -sS -m 6 -o /dev/null -w '%{http_code}' -X POST \
-H 'Content-Type: application/json' \
"${BRIDGE_URL}/webhooks/bluebubbles/${APP_ID}?secret=${SECRET}" \
-d '{"type":"new-message","data":{"guid":"x","text":"hi"}}' || echo 000)
[ "$code" != "401" ] && ok "local bridge accepts valid secret (HTTP $code, past auth)" || bad "local bridge valid secret" "got 401 with correct secret"
else
bad "local bridge URL" "getStatus returned no serverUrl"
fi
# ── Cleanup ──────────────────────────────────────────────────────────
ipc_eval "(async()=>{try{await window.electronAPI.invoke('imessageBridge.removeConfig',{applicationId:'${APP_ID}'});await window.electronAPI.invoke('imessageBridge.stop',{});}catch(e){}return 'cleaned';})()" >/dev/null
bb_cleanup_webhooks
note "cleaned up config + BlueBubbles webhook for ${APP_ID}"
# ── Summary ──────────────────────────────────────────────────────────
echo ""
echo "[imsg-test] PASS=${PASS} FAIL=${FAIL}"
[ "$FAIL" -eq 0 ] || exit 1
@@ -1,61 +0,0 @@
# Lark / 飞书 Bot Testing
**App name:** `Lark` or `飞书` | **Process name:** `Lark` or `飞书`
See [osascript-common.md](../osascript-common.md) for shared patterns.
## Activate & Navigate
```bash
# Activate Lark (auto-detects Lark or 飞书)
osascript -e 'tell application "Lark" to activate' 2> /dev/null \
|| osascript -e 'tell application "飞书" to activate'
sleep 1
# Quick Switcher / Search (Cmd+K)
osascript -e 'tell application "System Events" to keystroke "k" using command down'
sleep 0.5
osascript -e '
set the clipboard to "bot-testing"
tell application "System Events"
keystroke "v" using command down
delay 1.5
key code 36 -- Enter
end tell
'
sleep 2
```
## Send Message to Bot
```bash
osascript -e '
set the clipboard to "@MyBot help me with this task"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36 -- Enter
end tell
'
```
## Verify Response
```bash
sleep 10
screencapture /tmp/lark-bot-response.png
```
## Lark-Specific Notes
- App name varies: `Lark` (international) vs `飞书` (China mainland) — the script auto-detects
- Uses `Cmd+K` for quick search (same as Discord/Slack)
- Enter sends message by default
- Always use clipboard paste for CJK characters
## Script
```bash
./.agents/skills/local-testing/bot/lark/test-lark-bot.sh "bot-testing" "@MyBot hello"
./.agents/skills/local-testing/bot/lark/test-lark-bot.sh "bot-testing" "Help me with this" 30
```
@@ -1,84 +0,0 @@
#!/usr/bin/env bash
#
# test-lark-bot.sh — Send a message to a Lark/Feishu bot and capture the response
#
# Usage:
# ./scripts/test-lark-bot.sh <chat> <message> [wait_seconds] [screenshot_path]
#
# chat — Chat or contact name to search for
# message — Message to send to the bot
# wait_seconds — Seconds to wait for bot response (default: 10)
# screenshot_path — Output screenshot path (default: /tmp/lark-bot-test.png)
#
# Prerequisites:
# - Lark (飞书) desktop app installed and logged in
# - Accessibility permission granted (System Preferences > Privacy > Accessibility)
#
# Notes:
# - The app name may be "Lark" or "飞书" depending on version/locale
# - Uses Cmd+K to open search/quick switcher
# - Enter sends message by default
#
# Examples:
# ./scripts/test-lark-bot.sh "TestBot" "Hello"
# ./scripts/test-lark-bot.sh "bot-testing" "/ask Tell me a joke" 30
# ./scripts/test-lark-bot.sh "MyBot" "Help me summarize this" 60 /tmp/my-test.png
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CHAT="${1:?Usage: test-lark-bot.sh <chat> <message> [wait_seconds] [screenshot_path]}"
MESSAGE="${2:?Usage: test-lark-bot.sh <chat> <message> [wait_seconds] [screenshot_path]}"
WAIT="${3:-10}"
SCREENSHOT="${4:-/tmp/lark-bot-test.png}"
# Detect app name — "Lark" or "飞书"
APP=""
if osascript -e 'tell application "Lark" to name' &>/dev/null; then
APP="Lark"
elif osascript -e 'tell application "飞书" to name' &>/dev/null; then
APP="飞书"
else
echo "[error] Lark/飞书 app not found. Install Lark or 飞书."
exit 1
fi
echo "[$APP] Activating..."
osascript -e "tell application \"$APP\" to activate"
sleep 1
echo "[$APP] Searching for chat: $CHAT"
osascript -e '
tell application "System Events"
-- Quick Switcher / Search (Cmd+K)
keystroke "k" using command down
delay 0.8
end tell
'
# Use clipboard for chat name (supports CJK characters)
osascript -e '
set the clipboard to "'"$CHAT"'"
tell application "System Events"
keystroke "v" using command down
delay 1.5
key code 36 -- Enter to select first result
end tell
'
sleep 2
echo "[$APP] Sending message: $MESSAGE"
osascript -e '
set the clipboard to "'"$MESSAGE"'"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36 -- Enter to send
end tell
'
echo "[$APP] Waiting ${WAIT}s for bot response..."
sleep "$WAIT"
echo "[$APP] Capturing screenshot..."
"$SCRIPT_DIR/../capture-app-window.sh" "$APP" "$SCREENSHOT"
echo "[$APP] Done! Screenshot saved to $SCREENSHOT"
@@ -1,217 +0,0 @@
# osascript Common Patterns
Shared AppleScript / `osascript` patterns used by all platform bot tests. Read this first, then refer to the per-platform file for app-specific quirks.
## Core Patterns
### Activate an App
```bash
osascript -e 'tell application "Discord" to activate'
```
### Type Text
```bash
# Type character by character (reliable, but slow for long text)
osascript -e 'tell application "System Events" to keystroke "Hello world"'
# Press Enter
osascript -e 'tell application "System Events" to key code 36'
# Press Tab
osascript -e 'tell application "System Events" to key code 48'
# Press Escape
osascript -e 'tell application "System Events" to key code 53'
```
### Paste from Clipboard (fast, for long text)
```bash
# Set clipboard and paste — much faster than keystroke for long messages
osascript -e 'set the clipboard to "Your long message here"'
osascript -e 'tell application "System Events" to keystroke "v" using command down'
```
Or in one shot:
```bash
osascript -e '
set the clipboard to "Your long message here"
tell application "System Events" to keystroke "v" using command down
'
```
### Keyboard Shortcuts
```bash
# Cmd+K (quick switcher in Discord/Slack)
osascript -e 'tell application "System Events" to keystroke "k" using command down'
# Cmd+F (search)
osascript -e 'tell application "System Events" to keystroke "f" using command down'
# Cmd+N (new message/chat)
osascript -e 'tell application "System Events" to keystroke "n" using command down'
# Cmd+Shift+K (example: multi-modifier)
osascript -e 'tell application "System Events" to keystroke "k" using {command down, shift down}'
```
### Click at Position
```bash
# Click at absolute screen coordinates
osascript -e '
tell application "System Events"
click at {500, 300}
end tell
'
```
### Get Window Info
```bash
# Get window position and size
osascript -e '
tell application "System Events"
tell process "Discord"
get {position, size} of window 1
end tell
end tell
'
```
### Screenshot
```bash
# Full screen
screencapture /tmp/screenshot.png
# Interactive region select
screencapture -i /tmp/screenshot.png
# Specific window (by window ID from CGWindowList)
screencapture -l < WINDOW_ID > /tmp/screenshot.png
```
To get window ID for a specific app:
```bash
osascript -e '
tell application "System Events"
tell process "Discord"
get id of window 1
end tell
end tell
'
```
### Read Accessibility Elements
```bash
# Get all UI elements of the frontmost window (can be slow/large)
osascript -e '
tell application "System Events"
tell process "Discord"
entire contents of window 1
end tell
end tell
'
# Get a specific element's value
osascript -e '
tell application "System Events"
tell process "Discord"
get value of text field 1 of window 1
end tell
end tell
'
```
> **Warning:** `entire contents` can be extremely slow on complex UIs. Prefer screenshots + `Read` tool for visual verification.
### Read Screen Text via Clipboard
For reading the latest message or response from an app:
```bash
# Select all text in the focused area and copy
osascript -e '
tell application "System Events"
keystroke "a" using command down
keystroke "c" using command down
end tell
'
sleep 0.5
# Read clipboard
pbpaste
```
---
## Common Bot Testing Workflow
Regardless of platform, the pattern is:
```bash
APP_NAME="Discord" # or "Slack", "Telegram", "微信"
CHANNEL="bot-testing"
MESSAGE="Hello bot!"
WAIT_SECONDS=10
# 1. Activate
osascript -e "tell application \"$APP_NAME\" to activate"
sleep 1
# 2. Navigate to channel/chat (via Quick Switcher or Search)
osascript -e 'tell application "System Events" to keystroke "k" using command down'
sleep 0.5
osascript -e "tell application \"System Events\" to keystroke \"$CHANNEL\""
sleep 1
osascript -e 'tell application "System Events" to key code 36'
sleep 2
# 3. Send message
osascript -e "set the clipboard to \"$MESSAGE\""
osascript -e '
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36
end tell
'
# 4. Wait for bot response
sleep "$WAIT_SECONDS"
# 5. Screenshot for verification
screencapture /tmp/"${APP_NAME,,}"-bot-test.png
echo "Result saved to /tmp/${APP_NAME,,}-bot-test.png"
```
### Tips
- **Use clipboard paste** (`Cmd+V`) for messages containing special characters or long text — `keystroke` can mangle non-ASCII
- **Add `delay`** between actions — apps need time to process UI events
- **Screenshot for verification** — use `screencapture` + `Read` tool for visual checks
- **Use a dedicated test channel/chat** — avoid polluting real conversations
- **Check app name** — some apps have different names in different locales (e.g., `微信` vs `WeChat`)
- **Accessibility permissions required** — System Events automation requires granting Accessibility access in System Preferences > Privacy & Security > Accessibility
---
## Gotchas
- **Accessibility permission required** — first run will prompt for access; grant it in System Preferences > Privacy & Security > Accessibility for Terminal / iTerm / Claude Code
- **`keystroke` is slow for long text** — always use clipboard paste (`Cmd+V`) for messages over \~20 characters
- **`keystroke` can mangle non-ASCII** — use clipboard paste for Chinese, emoji, or special characters
- **`key code 36` is Enter** — this is the hardware key code, works regardless of keyboard layout
- **`entire contents` is extremely slow** — avoid for complex UIs; use screenshots instead
- **App name varies by locale** — `微信` vs `WeChat`, `企业微信` vs `WeCom`; handle both
- **WeChat Enter sends immediately** — use `Shift+Enter` for newlines within a message
- **Rate limiting** — don't send messages too fast; platforms may throttle or flag automated input
- **Lark / 飞书 app name varies** — `Lark` (international) vs `飞书` (China mainland); scripts auto-detect
- **QQ uses `Cmd+F` for search** — not `Cmd+K` like Discord/Slack/Lark
- **Bot response times vary** — AI-powered bots may take 10-60s; use generous sleep values
@@ -1,62 +0,0 @@
# QQ Bot Testing
**App name:** `QQ` | **Process name:** `QQ`
See [osascript-common.md](../osascript-common.md) for shared patterns.
## Activate & Navigate
```bash
osascript -e 'tell application "QQ" to activate'
sleep 1
# Search for contact/group (Cmd+F)
osascript -e '
tell application "System Events"
keystroke "f" using command down
delay 0.8
end tell
'
osascript -e '
set the clipboard to "bot-testing"
tell application "System Events"
keystroke "v" using command down
delay 1.5
key code 36 -- Enter
end tell
'
sleep 2
```
## Send Message to Bot
```bash
osascript -e '
set the clipboard to "Hello bot!"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36 -- Enter
end tell
'
```
## Verify Response
```bash
sleep 10
screencapture /tmp/qq-bot-response.png
```
## QQ-Specific Notes
- Enter sends message by default; Shift+Enter for newlines
- Uses `Cmd+F` for search (not `Cmd+K` like Discord/Slack/Lark)
- Always use clipboard paste for CJK characters
## Script
```bash
./.agents/skills/local-testing/bot/qq/test-qq-bot.sh "bot-testing" "Hello bot" 15
./.agents/skills/local-testing/bot/qq/test-qq-bot.sh "MyBot" "/help" 10
```
@@ -1,76 +0,0 @@
#!/usr/bin/env bash
#
# test-qq-bot.sh — Send a message to a QQ bot and capture the response
#
# Usage:
# ./scripts/test-qq-bot.sh <contact> <message> [wait_seconds] [screenshot_path]
#
# contact — Contact, group, or bot name to search for
# message — Message to send
# wait_seconds — Seconds to wait for bot response (default: 10)
# screenshot_path — Output screenshot path (default: /tmp/qq-bot-test.png)
#
# Prerequisites:
# - QQ desktop app installed and logged in
# - Accessibility permission granted (System Preferences > Privacy > Accessibility)
#
# Notes:
# - The app name is "QQ"
# - Uses Cmd+F to open search
# - Enter sends message by default; Shift+Enter for newlines
# - Uses clipboard paste for CJK character support
#
# Examples:
# ./scripts/test-qq-bot.sh "TestBot" "Hello"
# ./scripts/test-qq-bot.sh "bot-testing" "Hello bot" 30
# ./scripts/test-qq-bot.sh "MyBot" "/help" 15 /tmp/my-test.png
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONTACT="${1:?Usage: test-qq-bot.sh <contact> <message> [wait_seconds] [screenshot_path]}"
MESSAGE="${2:?Usage: test-qq-bot.sh <contact> <message> [wait_seconds] [screenshot_path]}"
WAIT="${3:-10}"
SCREENSHOT="${4:-/tmp/qq-bot-test.png}"
APP="QQ"
echo "[$APP] Activating..."
osascript -e "tell application \"$APP\" to activate"
sleep 1
echo "[$APP] Searching for contact: $CONTACT"
osascript -e '
tell application "System Events"
-- Search (Cmd+F)
keystroke "f" using command down
delay 0.8
end tell
'
# Use clipboard for contact name (supports CJK characters)
osascript -e '
set the clipboard to "'"$CONTACT"'"
tell application "System Events"
keystroke "v" using command down
delay 1.5
key code 36 -- Enter to select first result
end tell
'
sleep 2
echo "[$APP] Sending message: $MESSAGE"
osascript -e '
set the clipboard to "'"$MESSAGE"'"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36 -- Enter to send
end tell
'
echo "[$APP] Waiting ${WAIT}s for bot response..."
sleep "$WAIT"
echo "[$APP] Capturing screenshot..."
"$SCRIPT_DIR/../capture-app-window.sh" "$APP" "$SCREENSHOT"
echo "[$APP] Done! Screenshot saved to $SCREENSHOT"
@@ -1,73 +0,0 @@
# Slack Bot Testing
**App name:** `Slack` | **Process name:** `Slack`
See [osascript-common.md](../osascript-common.md) for shared patterns.
## Activate & Navigate
```bash
# Activate Slack
osascript -e 'tell application "Slack" to activate'
sleep 1
# Quick Switcher (Cmd+K)
osascript -e 'tell application "System Events" to keystroke "k" using command down'
sleep 0.5
osascript -e 'tell application "System Events" to keystroke "bot-testing"'
sleep 1
osascript -e 'tell application "System Events" to key code 36' # Enter
sleep 2
```
## Send Message to Bot
```bash
# Direct message input (focused after channel nav)
osascript -e 'tell application "System Events" to keystroke "@mybot hello"'
sleep 0.3
osascript -e 'tell application "System Events" to key code 36'
```
## Send Long Message
```bash
osascript -e '
tell application "Slack" to activate
delay 0.5
set the clipboard to "A long test message for the bot..."
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36
end tell
'
```
## Slash Command Test
```bash
osascript -e '
tell application "Slack" to activate
delay 0.5
tell application "System Events"
keystroke "/ask What is the meaning of life?"
delay 0.5
key code 36
end tell
'
```
## Verify Response
```bash
sleep 10
screencapture /tmp/slack-bot-response.png
```
## Script
```bash
./.agents/skills/local-testing/bot/slack/test-slack-bot.sh "bot-testing" "@mybot hello"
./.agents/skills/local-testing/bot/slack/test-slack-bot.sh "bot-testing" "/ask What is 2+2?" 20
```
@@ -1,64 +0,0 @@
#!/usr/bin/env bash
#
# test-slack-bot.sh — Send a message to a Slack bot and capture the response
#
# Usage:
# ./scripts/test-slack-bot.sh <channel> <message> [wait_seconds] [screenshot_path]
#
# channel — Channel name to navigate to via Quick Switcher (Cmd+K)
# message — Message to send (e.g., "@mybot hello" or "/ask question")
# wait_seconds — Seconds to wait for bot response (default: 10)
# screenshot_path — Output screenshot path (default: /tmp/slack-bot-test.png)
#
# Prerequisites:
# - Slack desktop app installed and logged in
# - Accessibility permission granted (System Preferences > Privacy > Accessibility)
#
# Examples:
# ./scripts/test-slack-bot.sh "bot-testing" "@mybot hello"
# ./scripts/test-slack-bot.sh "bot-testing" "/ask What is 2+2?" 20
# ./scripts/test-slack-bot.sh "general" "Hey bot" 15 /tmp/my-test.png
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CHANNEL="${1:?Usage: test-slack-bot.sh <channel> <message> [wait_seconds] [screenshot_path]}"
MESSAGE="${2:?Usage: test-slack-bot.sh <channel> <message> [wait_seconds] [screenshot_path]}"
WAIT="${3:-10}"
SCREENSHOT="${4:-/tmp/slack-bot-test.png}"
APP="Slack"
echo "[$APP] Activating..."
osascript -e "tell application \"$APP\" to activate"
sleep 1
echo "[$APP] Navigating to channel: $CHANNEL"
osascript -e '
tell application "System Events"
-- Quick Switcher
keystroke "k" using command down
delay 0.8
keystroke "'"$CHANNEL"'"
delay 1.5
key code 36 -- Enter
end tell
'
sleep 2
echo "[$APP] Sending message: $MESSAGE"
osascript -e '
set the clipboard to "'"$MESSAGE"'"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36 -- Enter
end tell
'
echo "[$APP] Waiting ${WAIT}s for bot response..."
sleep "$WAIT"
echo "[$APP] Capturing screenshot..."
"$SCRIPT_DIR/../capture-app-window.sh" "$APP" "$SCREENSHOT"
echo "[$APP] Done! Screenshot saved to $SCREENSHOT"
@@ -1,80 +0,0 @@
# Telegram Bot Testing
**App name:** `Telegram` | **Process name:** `Telegram`
See [osascript-common.md](../osascript-common.md) for shared patterns.
## Activate & Navigate
```bash
# Activate Telegram
osascript -e 'tell application "Telegram" to activate'
sleep 1
# Search for a bot (Cmd+F or click search)
osascript -e '
tell application "System Events"
keystroke "f" using command down
delay 0.5
keystroke "MyTestBot"
delay 1
key code 36 -- Enter to select
end tell
'
sleep 2
```
## Send Message to Bot
```bash
# After navigating to bot chat, input is focused
osascript -e '
tell application "System Events"
keystroke "/start"
delay 0.3
key code 36
end tell
'
```
## Send Long Message
```bash
osascript -e '
tell application "Telegram" to activate
delay 0.5
set the clipboard to "Tell me about quantum computing in detail"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36
end tell
'
```
## Verify Response
```bash
sleep 10
screencapture /tmp/telegram-bot-response.png
```
## Telegram Bot API (programmatic alternative)
For sending messages directly to the bot's chat without UI:
```bash
# Send message as the bot (for testing webhooks/responses)
curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
-d "chat_id=$CHAT_ID&text=test message"
# Get recent updates
curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getUpdates?limit=5" | jq .
```
## Script
```bash
./.agents/skills/local-testing/bot/telegram/test-telegram-bot.sh "MyTestBot" "/start"
./.agents/skills/local-testing/bot/telegram/test-telegram-bot.sh "GPTBot" "Hello" 60
```
@@ -1,79 +0,0 @@
#!/usr/bin/env bash
#
# test-telegram-bot.sh — Send a message to a Telegram bot and capture the response
#
# Usage:
# ./scripts/test-telegram-bot.sh <bot_or_chat> <message> [wait_seconds] [screenshot_path]
#
# bot_or_chat — Bot username or chat name to search for
# message — Message to send to the bot
# wait_seconds — Seconds to wait for bot response (default: 10)
# screenshot_path — Output screenshot path (default: /tmp/telegram-bot-test.png)
#
# Prerequisites:
# - Telegram desktop app installed and logged in
# - Accessibility permission granted (System Preferences > Privacy > Accessibility)
#
# Notes:
# - The app name may be "Telegram" or "Telegram Desktop" depending on installation
# - Uses Cmd+F to search for the bot, then Enter to open the chat
#
# Examples:
# ./scripts/test-telegram-bot.sh "MyTestBot" "/start"
# ./scripts/test-telegram-bot.sh "MyTestBot" "Hello bot" 30
# ./scripts/test-telegram-bot.sh "GPTBot" "/ask What is AI?" 60 /tmp/my-test.png
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BOT="${1:?Usage: test-telegram-bot.sh <bot_or_chat> <message> [wait_seconds] [screenshot_path]}"
MESSAGE="${2:?Usage: test-telegram-bot.sh <bot_or_chat> <message> [wait_seconds] [screenshot_path]}"
WAIT="${3:-10}"
SCREENSHOT="${4:-/tmp/telegram-bot-test.png}"
# Detect app name — "Telegram" or "Telegram Desktop"
APP=""
if osascript -e 'tell application "Telegram" to name' &>/dev/null; then
APP="Telegram"
elif osascript -e 'tell application "Telegram Desktop" to name' &>/dev/null; then
APP="Telegram Desktop"
else
echo "[error] Telegram app not found. Install Telegram or Telegram Desktop."
exit 1
fi
echo "[$APP] Activating..."
osascript -e "tell application \"$APP\" to activate"
sleep 1
echo "[$APP] Searching for: $BOT"
osascript -e '
tell application "System Events"
-- Search (Escape first to clear any existing state)
key code 53 -- Escape
delay 0.3
keystroke "f" using command down
delay 0.8
keystroke "'"$BOT"'"
delay 2
key code 36 -- Enter to select first result
end tell
'
sleep 2
echo "[$APP] Sending message: $MESSAGE"
osascript -e '
set the clipboard to "'"$MESSAGE"'"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36 -- Enter
end tell
'
echo "[$APP] Waiting ${WAIT}s for bot response..."
sleep "$WAIT"
echo "[$APP] Capturing screenshot..."
"$SCRIPT_DIR/../capture-app-window.sh" "$APP" "$SCREENSHOT"
echo "[$APP] Done! Screenshot saved to $SCREENSHOT"
@@ -1,81 +0,0 @@
# WeChat / 微信 Bot Testing
**App name:** `微信` or `WeChat` | **Process name:** `WeChat`
See [osascript-common.md](../osascript-common.md) for shared patterns.
## Activate & Navigate
```bash
# Activate WeChat
osascript -e 'tell application "微信" to activate'
sleep 1
# Search for a contact/bot (Cmd+F)
osascript -e '
tell application "System Events"
keystroke "f" using command down
delay 0.5
keystroke "TestBot"
delay 1
key code 36 -- Enter to select
end tell
'
sleep 2
```
## Send Message
```bash
# After navigating to a chat, the input is focused
osascript -e '
tell application "System Events"
keystroke "Hello bot!"
delay 0.3
key code 36
end tell
'
```
## Send Long Message (clipboard)
```bash
osascript -e '
tell application "微信" to activate
delay 0.5
set the clipboard to "Please help me with this task..."
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36
end tell
'
```
## Verify Response
```bash
sleep 10
screencapture /tmp/wechat-bot-response.png
```
## WeChat-Specific Notes
- WeChat macOS app name can be `微信` or `WeChat` depending on system language. Try both:
```bash
osascript -e 'tell application "微信" to activate' 2> /dev/null \
|| osascript -e 'tell application "WeChat" to activate'
```
- WeChat uses **Enter** to send (not Cmd+Enter by default, but configurable)
- For multi-line messages without sending, use **Shift+Enter**:
```bash
osascript -e 'tell application "System Events" to key code 36 using shift down'
```
- Always use clipboard paste for CJK characters — `keystroke` mangles non-ASCII
## Script
```bash
./.agents/skills/local-testing/bot/wechat/test-wechat-bot.sh "文件传输助手" "test message" 5
./.agents/skills/local-testing/bot/wechat/test-wechat-bot.sh "MyBot" "Tell me a joke" 30
```
@@ -1,85 +0,0 @@
#!/usr/bin/env bash
#
# test-wechat-bot.sh — Send a message to a WeChat bot and capture the response
#
# Usage:
# ./scripts/test-wechat-bot.sh <contact> <message> [wait_seconds] [screenshot_path]
#
# contact — Contact or bot name to search for
# message — Message to send
# wait_seconds — Seconds to wait for bot response (default: 10)
# screenshot_path — Output screenshot path (default: /tmp/wechat-bot-test.png)
#
# Prerequisites:
# - WeChat (微信) desktop app installed and logged in
# - Accessibility permission granted (System Preferences > Privacy > Accessibility)
#
# Notes:
# - The app name may be "微信" or "WeChat" depending on system language
# - WeChat sends on Enter by default; use Shift+Enter for newlines
# - For Chinese text, always uses clipboard paste (keystroke can't handle CJK)
#
# Examples:
# ./scripts/test-wechat-bot.sh "TestBot" "Hello"
# ./scripts/test-wechat-bot.sh "文件传输助手" "test message" 5
# ./scripts/test-wechat-bot.sh "MyBot" "Tell me a joke" 30 /tmp/my-test.png
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONTACT="${1:?Usage: test-wechat-bot.sh <contact> <message> [wait_seconds] [screenshot_path]}"
MESSAGE="${2:?Usage: test-wechat-bot.sh <contact> <message> [wait_seconds] [screenshot_path]}"
WAIT="${3:-10}"
SCREENSHOT="${4:-/tmp/wechat-bot-test.png}"
# Detect app name — "微信" or "WeChat"
APP=""
if osascript -e 'tell application "微信" to name' &>/dev/null; then
APP="微信"
elif osascript -e 'tell application "WeChat" to name' &>/dev/null; then
APP="WeChat"
else
echo "[error] WeChat app not found. Install 微信 (WeChat)."
exit 1
fi
echo "[$APP] Activating..."
osascript -e "tell application \"$APP\" to activate"
sleep 1
echo "[$APP] Searching for contact: $CONTACT"
osascript -e '
tell application "System Events"
-- Search (Cmd+F)
keystroke "f" using command down
delay 0.8
end tell
'
# Use clipboard for contact name (supports CJK characters)
osascript -e '
set the clipboard to "'"$CONTACT"'"
tell application "System Events"
keystroke "v" using command down
delay 1.5
key code 36 -- Enter to select first result
end tell
'
sleep 2
echo "[$APP] Sending message: $MESSAGE"
# Always use clipboard paste — keystroke can't handle CJK or special characters
osascript -e '
set the clipboard to "'"$MESSAGE"'"
tell application "System Events"
keystroke "v" using command down
delay 0.3
key code 36 -- Enter to send
end tell
'
echo "[$APP] Waiting ${WAIT}s for bot response..."
sleep "$WAIT"
echo "[$APP] Capturing screenshot..."
"$SCRIPT_DIR/../capture-app-window.sh" "$APP" "$SCREENSHOT"
echo "[$APP] Done! Screenshot saved to $SCREENSHOT"
@@ -1,110 +0,0 @@
# Log `agent-browser` into a local LobeHub dev server
`agent-browser --headed` on macOS often creates the Chromium window off-screen — the user can't see or interact with it, so manual login inside the agent-browser session fails. Instead of sharing the user's real Chrome profile, copy the **better-auth session cookie** out of a request in DevTools and inject it into the agent-browser session as a Playwright-style state file.
## When to use
- You need `agent-browser` to reach an authenticated page on `http://localhost:<port>` (e.g. `localhost:3011`).
- The user already has a logged-in tab of the same dev server in their own Chrome.
- Spawning a headed Chromium to let the user log in manually is unreliable (window off-screen, no interaction).
Do **not** use this on production URLs — only local dev. Treat the cookie as a secret: don't paste it into shared logs, PRs, or commit it anywhere.
## Step 1 — Ask the user to copy the cookie from a Network request, NOT `document.cookie`
`document.cookie` will not return HttpOnly cookies, which is exactly where better-auth puts its session. Instruct the user:
1. Open the logged-in tab (`http://localhost:<port>/…`) in their own Chrome.
2. `Cmd+Option+I`**Network** tab.
3. Refresh, click any same-origin request (e.g. the top-level document request).
4. In the right pane under **Request Headers**, right-click the `Cookie:` line → **Copy value** (or copy the entire header).
5. Paste the string into chat.
You only need the better-auth pieces. Everything else (Clerk, `LOBE_LOCALE`, HMR hash, theme vars) is noise and can stay. The minimum viable set is:
```
better-auth.session_token=<value>; better-auth.state=<value>
```
## Step 2 — Build a Playwright-style state file
`agent-browser state load` expects Playwright's `storageState` format: a JSON with a `cookies` array and an `origins` array.
```bash
cat > /tmp/mkstate.py << 'PY'
import json, sys, time
# Read the Cookie header from stdin (allows optional "Cookie: " prefix).
raw = sys.stdin.read().strip()
if raw.lower().startswith("cookie:"):
raw = raw.split(":", 1)[1].strip()
# Keep only better-auth cookies. Extend this set if the app genuinely needs more.
WANTED = {"better-auth.session_token", "better-auth.state"}
cookies = []
exp = int(time.time()) + 30 * 24 * 3600 # 30 days
for pair in raw.split("; "):
if "=" not in pair:
continue
name, _, value = pair.partition("=")
if name not in WANTED:
continue
cookies.append({
"name": name,
"value": value,
"domain": "localhost",
"path": "/",
"expires": exp,
"httpOnly": False,
"secure": False,
"sameSite": "Lax",
})
if not cookies:
sys.stderr.write("no better-auth cookies found in input\n")
sys.exit(1)
print(json.dumps({"cookies": cookies, "origins": []}, indent=2))
PY
# Feed the copied Cookie header in via env var or heredoc.
printf '%s' "$COOKIE_HEADER" | python3 /tmp/mkstate.py > /tmp/state.json
```
**Note on `httpOnly`**: the real cookie in the user's browser is HttpOnly, but `storageState` doesn't enforce the flag on load — it just attaches the value. Storing with `httpOnly: false` is fine for local dev and sidesteps a CDP-context quirk where HttpOnly cookies sometimes fail to attach.
## Step 3 — Load state and navigate
```bash
SESSION="my-test" # any stable session name
agent-browser --session "$SESSION" state load /tmp/state.json
agent-browser --session "$SESSION" open "http://localhost:3011/"
agent-browser --session "$SESSION" get url
# Expect NOT /signin?callbackUrl=… — if you still see signin, cookie didn't apply.
```
## Step 4 — Verify
```bash
agent-browser --session "$SESSION" snapshot -i | head -20
# Look for the user's avatar/name in the sidebar, or absence of the signin form.
```
## Common failure modes
| Symptom | Cause | Fix |
| ----------------------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------- |
| Still redirects to `/signin` after `state load` | User pasted from `document.cookie` → missed HttpOnly session | Re-pull from Network request Headers, not console |
| `state load` reports 0 cookies | Separator wrong, or user pasted URL-decoded value | Keep the raw `Cookie:` header as-is; split on `"; "` |
| Login works briefly then expires | `better-auth.session_token` rotated (user logged out / signed in again) | Re-copy and re-load |
| Domain mismatch | Use `domain: "localhost"` literally, no leading dot for local dev | — |
## Scope
Only covers authenticating an **agent-browser** session into a **local** LobeHub dev server. It does not:
- Work for production — production cookies are `Secure; HttpOnly; Domain=.lobehub.com` and must be delivered over HTTPS.
- Replace real OAuth flows — tests that must exercise the login UI need a real Chromium with `--remote-debugging-port` or a bot account.
- Flow cookies back to the user's Chrome — injection is one-way (into agent-browser only).
@@ -1,93 +0,0 @@
# LobeHub gateway streaming + tab-switch test harness
Captures store + DOM state at 200ms intervals so we can prove or disprove
claims like "切回 tab 后消息回到了很早以前". Built for gateway-mode chat but
works for any LobeHub streaming session.
## Files
`scripts/agent-gateway/`
| File | Role |
| --------------- | ---------------------------------------------------------------- |
| `probe.js` | Injects a 200ms sampler + `__PROBE_EVENT` marker + `__switchTab` |
| `probe-dump.js` | Stops the sampler and returns `{events, samples}` as JSON string |
| `tab-switch.js` | Runs N round-trip switches between two tabs, marks each step |
| `analyze.mjs` | Node post-processor: timeline + regression detection |
## Standard workflow
```bash
# 1. Start Electron with CDP
./.agents/skills/local-testing/scripts/electron-dev.sh start
# 2. Navigate to a chat, switch runtime to Cloud Sandbox (gateway mode)
# 3. Install the probe + helpers
agent-browser --cdp 9222 eval --stdin \
< .agents/skills/local-testing/scripts/agent-gateway/probe.js
# 4. Send a tool-call message — manually or via type+press
agent-browser --cdp 9222 eval "window.__PROBE_EVENT('SENT')"
# 5. Run the multi-switch driver (auto-picks active tab as BACK and the
# rightmost inactive tab as AWAY — edit ROUND_TRIPS / DWELL_MS in the
# file if you want different timing)
agent-browser --cdp 9222 eval --stdin \
< .agents/skills/local-testing/scripts/agent-gateway/tab-switch.js
# 6. Wait for streaming to finish, then dump
agent-browser --cdp 9222 eval --stdin \
< .agents/skills/local-testing/scripts/agent-gateway/probe-dump.js \
> /tmp/probe.json
# 7. Analyze
node .agents/skills/local-testing/scripts/agent-gateway/analyze.mjs /tmp/probe.json
```
The analyzer prints three sections: EVENTS, TIMELINE, REGRESSIONS. If
REGRESSIONS is non-empty it means content/reasoning/childN dropped on the
same topic — the symptom users describe.
## What the probe tracks (and why)
`chat.messagesMap` only stores the top-level `assistantGroup` shell. The
actual streamed content, reasoning, and tool calls live in
`assistantGroup.children: AssistantContentBlock[]`. Any probe that only
reads `m.content` / `m.reasoning` will see zeros throughout streaming and
miss everything that matters. probe.js walks both levels and sums:
- `cT` total content length
- `rT` total reasoning length
- `toolT` total tool-call count
- `childN` number of content blocks
Plus DOM-side signals (`domLen`, search/crawl indicator counts) so you can
tell store-side regressions apart from render-side regressions.
## Gotchas
- **Optimistic new-topic state.** Before the first chunk lands, messages
live under the `<scope>_new` key with `tmp_*` ids and no `topicId` field.
probe.js falls back to those when `activeTopicId` is null.
- **Reasoning resets to 0 are not bugs.** When the assistant finishes
thinking and starts tool-use or text, the streaming reasoning buffer
empties and the finalised reasoning gets sealed into a completed block.
Filter these out manually if needed.
- **DOM length jitters by a handful of chars** because counters like "(10)"
in tool-call labels change as results arrive. analyze.mjs only flags
`domLen` drops greater than 100 chars to ignore that noise.
- **Never identify tabs by innerText.** The active tab's text embeds a
` · <agent name>` suffix, so a search like `'LobeHub Growth'` matches the
active tab when the active agent happens to be LobeHub Growth — and you
end up clicking the tab you're already on. probe.js uses the stable
`data-contextmenu-trigger` attribute (a React `useId()` value that's set
per-tab and survives focus changes) plus `data-active="true"` to mark
the active one. Helpers exposed:
`__listTabs()` / `__clickTabByKey(key)` / `__clickTabByIndex(i)` /
`__activeTabKey()`.
- **`tab-switch.js` fires-and-forgets.** The IIFE kicks off an async loop
and returns immediately so the agent-browser CLI eval doesn't blow past
its default 25 s timeout. Wait on the `SWITCH_LOOP_DONE` event marker
before dumping. Re-running while a loop is in flight is refused — the
chaotic data from overlapping runs is not worth debugging.
@@ -1,142 +0,0 @@
# record-app-screen.sh
General-purpose screen recording tool for the Electron app. Captures CDP screenshots as video frames and gallery snapshots, then assembles into an MP4 on stop.
## Why CDP Screenshots Instead of ffmpeg Screen Capture
- **Works on any screen** — CDP screenshots capture the browser viewport directly, so external monitors, Retina scaling, and window positioning are all handled automatically
- **No signal handling issues** — ffmpeg-static (npm) produces corrupt MP4 files when killed (missing moov atom). CDP screenshots avoid this entirely
- **Consistent output** — Screenshots are resolution-independent and don't require crop coordinate calculations
## Commands
```bash
# Start recording (Electron must be running with CDP)
.agents/skills/local-testing/scripts/record-app-screen.sh start [output_name]
# Stop recording and assemble video
.agents/skills/local-testing/scripts/record-app-screen.sh stop
# Check if recording is active
.agents/skills/local-testing/scripts/record-app-screen.sh status
```
### Arguments
| Argument | Default | Description |
| ------------- | --------------------------- | -------------------------- |
| `output_name` | `recording-YYYYMMDD-HHMMSS` | Base name for output files |
### Environment Variables
| Variable | Default | Description |
| ---------------------- | ------- | -------------------------------------- |
| `CDP_PORT` | `9222` | Chrome DevTools Protocol port |
| `SCREENSHOT_INTERVAL` | `3` | Seconds between gallery screenshots |
| `VIDEO_FRAME_INTERVAL` | `0.5` | Seconds between video frames (\~2 fps) |
## Output Structure
```
.records/
<name>.mp4 # Video assembled from frames (~2 fps)
<name>/ # Gallery screenshots (every 3s)
0000.png
0001.png
0002.png
...
```
The `.records/` directory is at the project root and is gitignored.
## How It Works
### Start
1. Creates two background loops:
- **Video frames** — `agent-browser screenshot` every `VIDEO_FRAME_INTERVAL` seconds into a temp directory (`/tmp/record-frames-XXXXXX/`)
- **Gallery screenshots** — `agent-browser screenshot` every `SCREENSHOT_INTERVAL` seconds into `.records/<name>/`
2. Saves PIDs and paths to `/tmp/record-app-screen.pids` and `/tmp/record-app-screen.state`
### Stop
1. Kills both background loops
2. Assembles video frames into MP4 using ffmpeg:
```
ffmpeg -framerate 2 -i frame_%06d.png -c:v libx264 -crf 23 -pix_fmt yuv420p <output>.mp4
```
3. Cleans up temp frame directory
4. Reports file sizes and paths
## Usage Examples
### Basic Test Recording
```bash
# Start Electron
.agents/skills/local-testing/scripts/electron-dev.sh start
# Start recording
.agents/skills/local-testing/scripts/record-app-screen.sh start my-test
# Run automation
agent-browser --cdp 9222 click @e61
agent-browser --cdp 9222 type @e42 "hello"
agent-browser --cdp 9222 press Enter
sleep 10
# Stop and get results
.agents/skills/local-testing/scripts/record-app-screen.sh stop
# → .records/my-test.mp4 + .records/my-test/*.png
```
### Gateway Streaming Demo
```bash
.agents/skills/local-testing/scripts/electron-dev.sh start
# Inject gateway URL
agent-browser --cdp 9222 eval --stdin << 'EOF'
(function() {
var store = window.global_serverConfigStore;
store.setState({ serverConfig: { ...store.getState().serverConfig,
agentGatewayUrl: 'https://agent-gateway.lobehub.com' } });
return 'ready';
})()
EOF
# Record
.agents/skills/local-testing/scripts/record-app-screen.sh start gateway-demo
# Navigate to agent, send message, wait for completion...
# (automation commands here)
.agents/skills/local-testing/scripts/record-app-screen.sh stop
open .records/gateway-demo.mp4
```
### Check Active Recording
```bash
.agents/skills/local-testing/scripts/record-app-screen.sh status
# [record] Active recording
# Frames: 42 captured (running: yes)
# Screenshots: 14 captured (running: yes)
# Output: .records/my-test.mp4
```
## Prerequisites
- **ffmpeg** — For video assembly. Install via `bun add -g ffmpeg-static` or `brew install ffmpeg`
- **agent-browser** — For CDP screenshots. Install via `npm i -g agent-browser`
- **Electron app running** — With CDP enabled (use `electron-dev.sh start`)
## Troubleshooting
| Problem | Solution |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| "No active recording found" on stop | PID file was cleaned up. Check if background processes are still running with `ps aux \| grep agent-browser` |
| "A recording is already active" | Run `stop` first, or manually clean: `rm /tmp/record-app-screen.pids /tmp/record-app-screen.state` |
| Video is 0 bytes | No frames were captured. Ensure Electron is running and CDP port is correct |
| Screenshots are blank/white | SPA may not have loaded yet. Wait for `electron-dev.sh` to report "Renderer ready" |
| ffmpeg assembly fails | Check `/tmp/ffmpeg-assemble.log`. Ensure ffmpeg is installed and frames exist |
@@ -1,243 +0,0 @@
// Analyzer for probe-events dumps. Reads a JSON file produced by `run.ts dump`
// and prints a layered breakdown:
//
// 1. STREAM EVENTS — every non-chunk WS/SSE event in receipt order
// 2. CHUNKS SUMMARY — collapsed per-step chunk counts (otherwise floods)
// 3. ACTION CALLS — replaceMessages / refreshMessages / MARK:* with stack
// 4. CORRELATION — calls ↔ nearest stream event within ±300ms
// 5. PER-KEY ASSISTANT GROWTH — for each messagesMap key, when the leading
// assistant message's cLen / rLen actually moves (this is what reveals
// "chunks arrived but the message never grew" regressions)
// 6. ROLLBACKS — msgN / childN / role drops in the active-topic timeline
//
// Usage:
// bun run .agents/skills/local-testing/scripts/agent-gateway/analyze-events.ts <dump.json>
import { readFileSync } from 'node:fs';
import type {
ProbeActionCall,
ProbeDump,
ProbeMessageSummary,
ProbeStreamEvent,
ProbeTimelineSample,
} from './types';
const file = process.argv[2];
if (!file) {
console.error('usage: bun run analyze-events.ts <dump.json>');
process.exit(1);
}
const raw = readFileSync(file, 'utf8');
// agent-browser eval --stdin wraps return values in quotes when the value is
// a string — so the JSON file may be double-encoded depending on how it was
// captured. Handle both.
const parsedOnce = JSON.parse(raw) as ProbeDump | string;
const dump: ProbeDump = typeof parsedOnce === 'string' ? JSON.parse(parsedOnce) : parsedOnce;
const { streamEvents = [], actionCalls = [], timeline = [] } = dump;
const pad = (v: unknown, n: number) => String(v).padStart(n);
// ── META ───────────────────────────────────────────────────────────
console.log('=== META ===');
console.log(` events: ${streamEvents.length}`);
console.log(` calls: ${actionCalls.length}`);
console.log(` timeline: ${timeline.length}`);
// ── 1. STREAM EVENTS (non-chunk) ───────────────────────────────────
const nonChunkEvents = streamEvents.filter((e) => e.type !== 'stream_chunk');
const chunkEvents = streamEvents.filter((e) => e.type === 'stream_chunk');
console.log(
`\n=== STREAM EVENTS (${nonChunkEvents.length} non-chunk + ${chunkEvents.length} chunks elided) ===`,
);
for (const e of nonChunkEvents) {
const dataStr = e.dataKeys?.length ? ` [${e.dataKeys.join(',')}]` : '';
const data = e.data as Record<string, unknown> | undefined;
const uiHint = data?.uiMessagesPreview
? ` uiPreview=${JSON.stringify(data.uiMessagesPreview)}`
: data?.uiMessagesTotal
? ` uiTotal=${data.uiMessagesTotal}`
: '';
const phaseHint = data?.phase ? ` phase=${data.phase}` : '';
const extra = e.serverType ? ` serverType=${e.serverType}` : '';
console.log(
` t=${pad(e.t, 7)} [${(e.transport ?? '?').padEnd(3)}] step=${pad(e.stepIndex ?? '-', 2)} ` +
`type=${(e.type ?? '').padEnd(22)} op=${e.opIdTail ?? '-'}${phaseHint}${uiHint}${extra}${dataStr}`,
);
}
// ── 2. CHUNK SUMMARY ───────────────────────────────────────────────
console.log('\n=== CHUNKS SUMMARY (per step / chunkType) ===');
const chunkBuckets = new Map<string, { count: number; firstT: number; lastT: number }>();
for (const c of chunkEvents) {
const data = c.data as Record<string, unknown> | undefined;
const ct = (data?.chunkType as string | undefined) ?? '?';
const key = `step=${c.stepIndex ?? '-'} chunkType=${ct.padEnd(8)} op=${c.opIdTail}`;
const slot = chunkBuckets.get(key);
if (slot) {
slot.count += 1;
slot.lastT = c.t;
} else {
chunkBuckets.set(key, { count: 1, firstT: c.t, lastT: c.t });
}
}
for (const [k, v] of chunkBuckets) {
console.log(` ${k} count=${pad(v.count, 4)} t=${pad(v.firstT, 7)}..${pad(v.lastT, 7)}`);
}
// ── 3. ACTION CALLS ───────────────────────────────────────────────
console.log('\n=== ACTION CALLS (replace/refresh/MARK) ===');
for (const c of actionCalls) {
if (c.name?.startsWith('MARK:')) {
console.log(` t=${pad(c.t, 7)} ${c.name}`);
continue;
}
const snapshot = (c.args as any)?.snapshot as
| Array<{ id: string; role: string; cLen: number; rLen: number }>
| undefined;
const snapStr = snapshot?.length
? ' snapshot=' + snapshot.map((m) => `${m.id}:${m.role}/c${m.cLen}/r${m.rLen}`).join(' | ')
: '';
const summary =
c.name === 'replaceMessages'
? `count=${c.args?.count} action=${(c.args?.params as any)?.action ?? '-'}${snapStr}`
: c.name === 'refreshMessages'
? `ctx=${JSON.stringify(c.args?.context)}`
: c.error
? `error=${c.error}`
: '';
console.log(` t=${pad(c.t, 7)} ${c.name.padEnd(20)} ${summary}`);
if (c.stack) {
const frames = c.stack
.split(' ← ')
.filter((f) => !!f && !f.includes('Object.<anonymous>'))
.slice(0, 3);
for (const f of frames) console.log(`${f}`);
}
}
// ── 4. CORRELATION ────────────────────────────────────────────────
function nearestEventForCall(
call: ProbeActionCall,
windowMs = 300,
): { event: ProbeStreamEvent; delta: number } | null {
let best: ProbeStreamEvent | null = null;
let bestDelta = Infinity;
for (const e of streamEvents) {
const d = Math.abs(e.t - call.t);
if (d < bestDelta && d <= windowMs) {
bestDelta = d;
best = e;
}
}
return best ? { event: best, delta: bestDelta } : null;
}
console.log('\n=== CORRELATION (replace/refresh ↔ nearest event within ±300ms) ===');
for (const c of actionCalls) {
if (c.name !== 'refreshMessages' && c.name !== 'replaceMessages') continue;
const hit = nearestEventForCall(c);
if (hit) {
const phase = (hit.event.data as Record<string, unknown> | undefined)?.phase;
console.log(
` t=${pad(c.t, 7)} ${c.name.padEnd(16)} ← Δ${pad(hit.delta, 4)}ms ${hit.event.type}` +
(phase ? ` phase=${phase}` : ''),
);
} else {
console.log(` t=${pad(c.t, 7)} ${c.name.padEnd(16)} ← (no event nearby — external trigger)`);
}
}
// ── 5. PER-KEY ASSISTANT GROWTH ───────────────────────────────────
// For each messagesMap key, find the trailing assistant message and report
// the points in time where its cLen / rLen actually changed. If the timeline
// shows chunks arriving but the assistant cLen never moves, that's the
// signature of "dispatch queue blocked / messageId mismatch".
console.log('\n=== PER-KEY ASSISTANT GROWTH ===');
const keysEverSeen = new Set<string>();
for (const s of timeline) for (const k of Object.keys(s.byKey ?? {})) keysEverSeen.add(k);
for (const key of keysEverSeen) {
console.log(`\n key=${key}`);
let lastSig: string | null = null;
for (const s of timeline) {
const slot = s.byKey?.[key];
if (!slot) continue;
const last = slot.msgs.at(-1) as ProbeMessageSummary | undefined;
if (!last) continue;
const sig = `${last.id}|c${last.cLen}|r${last.rLen}|n${slot.n}`;
if (sig === lastSig) continue;
lastSig = sig;
console.log(
` t=${pad(s.t, 7)} msgN=${pad(slot.n, 3)} ` +
`lastAssistant=${last.id} cLen=${pad(last.cLen, 5)} rLen=${pad(last.rLen, 5)}` +
` runOps=${s.runOps}`,
);
}
}
// ── 6. ROLLBACKS (active-topic msgN / childN / role drops) ─────────
console.log('\n=== ROLLBACKS (active-topic msgN / childN / role drops) ===');
let prev: ProbeTimelineSample | null = null;
const rollbacks: Array<{ t: number; topic: string | null; drops: string[] }> = [];
const flatten = (s: ProbeTimelineSample) => {
if (!s.activeTopic) return [];
return Object.entries(s.byKey ?? {})
.filter(([k]) => k.includes(s.activeTopic!))
.flatMap(([, v]) => v.msgs);
};
for (const s of timeline) {
if (s.err) {
prev = null;
continue;
}
if (!prev || prev.activeTopic !== s.activeTopic) {
prev = s;
continue;
}
const prevMsgs = flatten(prev);
const curMsgs = flatten(s);
const drops: string[] = [];
if (curMsgs.length < prevMsgs.length) drops.push(`msgN ${prevMsgs.length}${curMsgs.length}`);
let prevChild = 0;
let curChild = 0;
for (const m of prevMsgs) prevChild += m.chN ?? 0;
for (const m of curMsgs) curChild += m.chN ?? 0;
if (curChild < prevChild) drops.push(`childN ${prevChild}${curChild}`);
const prevById = new Map(prevMsgs.map((m) => [m.id, m]));
for (const m of curMsgs) {
const pr = prevById.get(m.id);
if (!pr) continue;
if (m.cLen < pr.cLen) drops.push(`cLen[${m.id}] ${pr.cLen}${m.cLen}`);
if (m.rLen < pr.rLen) drops.push(`rLen[${m.id}] ${pr.rLen}${m.rLen}`);
}
if (drops.length) rollbacks.push({ t: s.t, topic: s.activeTopic, drops });
prev = s;
}
if (rollbacks.length === 0) {
console.log(' (none)');
} else {
for (const r of rollbacks) {
const nearEvent = streamEvents
.filter((e) => Math.abs(e.t - r.t) <= 300)
.map((e) => `${e.type}${(e.data as any)?.phase ? ':' + (e.data as any).phase : ''}`);
const nearCall = actionCalls
.filter((c) => Math.abs(c.t - r.t) <= 300 && !c.name?.startsWith('MARK:'))
.map((c) => c.name);
console.log(
` t=${pad(r.t, 7)} topic=${r.topic} ${r.drops.join(' | ')}` +
(nearEvent.length ? ` near-event:[${nearEvent.join(',')}]` : '') +
(nearCall.length ? ` near-call:[${nearCall.join(',')}]` : ''),
);
}
}
@@ -1,119 +0,0 @@
#!/usr/bin/env node
// Analyze a probe dump captured by probe.js + probe-dump.js.
//
// node analyze.mjs /tmp/probe.json
//
// Prints:
// 1. EVENTS — user-action markers with their relative timestamps
// 2. TIMELINE — periodic samples (~1 per second + event-adjacent samples)
// showing every interesting field; columns:
// t(ms) | runOps | msgN | childN | content | reasoning | tools | domLen | search | crawl | topic | event
// 3. REGRESSIONS — every place a tracked counter *dropped* on the same
// topic between adjacent samples. A "true" UI rollback shows up as a
// drop in content/reasoning/tools/childN/domLen without a topic change.
//
// Whitelisted transitions (not flagged):
// - topic change → all drops expected (focus moved away)
// - reasoning length 0 after content starts → reasoning gets sealed into a
// completed sub-block; the parent's running reasoning resets to ''.
// - msgN drop when topic transitions from `_new` placeholder to a real id.
import fs from 'node:fs';
const file = process.argv[2];
if (!file) {
console.error('usage: node analyze.mjs <probe.json>');
process.exit(1);
}
const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
// probe-dump.js wraps the payload in JSON.stringify so agent-browser returns
// it as a single quoted string. Unwrap.
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
const { events, samples } = data;
const fmt = {
pad(v, n) {
return String(v).padStart(n);
},
};
console.log('=== EVENTS ===');
for (const e of events) console.log(` t=${fmt.pad(e.t, 7)} ${e.name}`);
console.log(
'\n=== TIMELINE (~1s cadence, plus event-adjacent samples) ===\n' +
' t(ms) runOps msgN childN content reasoning tools domLen search crawl topic event',
);
let lastSampledAt = -1e9;
const eventBuckets = events.map((e) => e.t);
for (let i = 0; i < samples.length; i++) {
const s = samples[i];
const nearEvent = eventBuckets.some((et) => Math.abs(et - s.t) < 110);
if (!nearEvent && s.t - lastSampledAt < 1000) continue;
lastSampledAt = s.t;
const ev = events.find((e) => Math.abs(e.t - s.t) < 110);
const evMarker = ev ? `${ev.name}` : '';
const topicSuffix = s.topicId ? s.topicId.slice(-6) : '(none)';
const search = s.ind?.search ?? 0;
const crawl = s.ind?.crawl ?? 0;
console.log(
` ${fmt.pad(s.t, 6)} ` +
`${fmt.pad(s.runOps, 6)} ` +
`${fmt.pad(s.msgN, 4)} ` +
`${fmt.pad(s.childN ?? 0, 5)} ` +
`${fmt.pad(s.cT ?? 0, 8)} ` +
`${fmt.pad(s.rT ?? 0, 9)} ` +
`${fmt.pad(s.toolT ?? 0, 5)} ` +
`${fmt.pad(s.domLen ?? 0, 7)} ` +
`${fmt.pad(search, 6)} ` +
`${fmt.pad(crawl, 5)} ` +
`${topicSuffix.padEnd(8)}${evMarker}`,
);
}
console.log('\n=== REGRESSIONS (same topic, value dropped) ===');
const regressions = [];
for (let i = 1; i < samples.length; i++) {
const prev = samples[i - 1];
const cur = samples[i];
if (!cur.topicId || prev.topicId !== cur.topicId) continue;
const drops = [];
if (cur.msgN < prev.msgN) drops.push(`msgN: ${prev.msgN}${cur.msgN}`);
if ((cur.childN ?? 0) < (prev.childN ?? 0)) drops.push(`childN: ${prev.childN}${cur.childN}`);
if ((cur.cT ?? 0) < (prev.cT ?? 0)) drops.push(`content: ${prev.cT}${cur.cT}`);
if ((cur.rT ?? 0) < (prev.rT ?? 0)) drops.push(`reasoning: ${prev.rT}${cur.rT}`);
if ((cur.toolT ?? 0) < (prev.toolT ?? 0)) drops.push(`tools: ${prev.toolT}${cur.toolT}`);
// domLen jitters by a few chars from counter labels — only flag big drops.
if ((cur.domLen ?? 0) < (prev.domLen ?? 0) - 100) {
drops.push(`domLen: ${prev.domLen}${cur.domLen}`);
}
if (drops.length === 0) continue;
const nearbyEv = events.filter((e) => Math.abs(e.t - cur.t) < 600).map((e) => e.name);
regressions.push({ t: cur.t, topic: cur.topicId.slice(-6), drops, nearbyEv });
}
if (regressions.length === 0) {
console.log(' (none)');
} else {
for (const r of regressions) {
const evStr = r.nearbyEv.length ? ` near:[${r.nearbyEv.join(',')}]` : '';
console.log(` t=${fmt.pad(r.t, 7)} topic=${r.topic} ${r.drops.join(' | ')}${evStr}`);
}
}
console.log(`\n=== SUMMARY ===`);
console.log(` samples: ${samples.length}`);
console.log(` events: ${events.length}`);
console.log(` regressions: ${regressions.length}`);
if (samples.length) {
const last = samples.at(-1);
console.log(
` final: msgN=${last.msgN} childN=${last.childN ?? 0} content=${last.cT ?? 0} ` +
`reasoning=${last.rT ?? 0} tools=${last.toolT ?? 0} runOps=${last.runOps}`,
);
}
@@ -1,17 +0,0 @@
// Stop the probe and serialize collected data.
//
// agent-browser --cdp 9222 eval --stdin < probe-dump.js > /tmp/probe.json
//
// The whole thing is wrapped in a JSON.stringify so agent-browser returns it
// as a single quoted string — the analyzer double-parses to handle that.
(function () {
if (window.__PROBE_TIMER) {
clearInterval(window.__PROBE_TIMER);
window.__PROBE_TIMER = null;
}
return JSON.stringify({
events: window.__PROBE_EVENTS || [],
samples: window.__PROBE_SAMPLES || [],
});
})();
@@ -1,37 +0,0 @@
// Stops the events-probe timeline timer and stashes the full capture as a
// JSON string on `window.__PROBE_LAST_DUMP_JSON`. `run.ts` wraps the bundle
// in an IIFE that returns that global, which `agent-browser eval` prints to
// stdout — the runner then persists it under `.agent-gateway/`.
import type { ProbeDump } from './types';
declare global {
interface Window {
__PROBE_LAST_DUMP_JSON?: string;
}
}
const w = window;
if (w.__PROBE_TIMELINE_TIMER) {
clearInterval(w.__PROBE_TIMELINE_TIMER);
w.__PROBE_TIMELINE_TIMER = null;
}
const mutations = w.__PROBE_MUTATIONS ?? [];
const dump: ProbeDump & { mutations: typeof mutations } = {
meta: {
t0: w.__PROBE_T0 ?? 0,
collectedAt: Date.now(),
sampleCount: (w.__PROBE_MSG_TIMELINE ?? []).length,
eventCount: (w.__PROBE_STREAM_EVENTS ?? []).length,
callCount: (w.__PROBE_ACTION_CALLS ?? []).length,
},
streamEvents: w.__PROBE_STREAM_EVENTS ?? [],
actionCalls: w.__PROBE_ACTION_CALLS ?? [],
timeline: w.__PROBE_MSG_TIMELINE ?? [],
mutations,
};
w.__PROBE_LAST_DUMP_JSON = JSON.stringify(dump);
@@ -1,637 +0,0 @@
// LobeHub gateway raw-event-stream probe.
//
// Gateway-mode chats subscribe via WebSocket — NOT via the `/api/agent/stream`
// SSE endpoint (that one belongs to the direct/client durable-agent runtime).
// `AgentStreamClient` (`packages/agent-gateway-client/src/client.ts`) opens
// `new WebSocket('wss://.../ws?operationId=...')`, then parses JSON frames in
// its `onmessage` handler and re-emits `agent_event.event` objects to the
// chat store.
//
// To capture the RAW gateway events before the store touches them, we wrap
// `window.WebSocket` so that for any socket whose URL contains `operationId=`
// we intercept the `onmessage` handler / `addEventListener('message')` and
// log every `agent_event` frame.
//
// We *also* keep the `window.fetch` hook for `/api/agent/stream` so this
// probe still works for direct-mode runs — but gateway-mode events come
// through the WebSocket path.
//
// Buffers (read via `dump`):
// __PROBE_STREAM_EVENTS — raw events parsed off the wire
// __PROBE_ACTION_CALLS — replaceMessages / refreshMessages calls (best-effort)
// __PROBE_MSG_TIMELINE — 200ms snapshots of every messagesMap key
import type {
ProbeActionCall,
ProbeMessageSummary,
ProbeStreamEvent,
ProbeTimelineSample,
} from './types';
// Bundled by esbuild as an IIFE. Top-level code runs once on injection.
const w = window;
// ── Buffers ─────────────────────────────────────────────────────────
declare global {
interface Window {
__PROBE_MUTATIONS?: Array<{
t: number;
key: string;
n: number;
last?: { id: string; role: string; cLen: number; rLen: number; updatedAt?: unknown };
prevLast?: { id: string; role: string; cLen: number; rLen: number };
delta?: string;
}>;
__PROBE_STORE_UNSUB?: () => void;
}
}
const events: ProbeStreamEvent[] = (w.__PROBE_STREAM_EVENTS ??= []);
const calls: ProbeActionCall[] = (w.__PROBE_ACTION_CALLS ??= []);
const timeline: ProbeTimelineSample[] = (w.__PROBE_MSG_TIMELINE ??= []);
const mutations = (w.__PROBE_MUTATIONS ??= []);
events.length = 0;
calls.length = 0;
timeline.length = 0;
mutations.length = 0;
const t0 = Date.now();
w.__PROBE_T0 = t0;
const now = (): number => Date.now() - t0;
// ── Helpers ─────────────────────────────────────────────────────────
function summarizeData(data: unknown): Record<string, unknown> | unknown {
if (!data || typeof data !== 'object') return data;
const src = data as Record<string, unknown>;
const out: Record<string, unknown> = {};
for (const k of Object.keys(src)) {
const v = src[k];
if (v == null) {
out[k] = v;
} else if (Array.isArray(v)) {
out[k] = `Array(${v.length})`;
if (k === 'uiMessages') {
out.uiMessagesPreview = v.slice(0, 5).map((m: any) => ({
id: (m.id ?? '').slice(-8),
role: m.role,
cLen: (m.content ?? '').length,
children: (m.children ?? []).length,
tools: (m.tools ?? []).length,
reasoning: (m.reasoning?.content ?? '').length,
}));
out.uiMessagesTotal = v.length;
}
} else if (typeof v === 'object') {
const obj = v as Record<string, unknown>;
out[k] =
'Object{' +
Object.keys(obj)
.slice(0, 6)
.map((kk) => kk + (typeof obj[kk] === 'string' ? `=${(obj[kk] as string).length}ch` : ''))
.join(',') +
'}';
} else if (typeof v === 'string') {
out[k] = v.length > 100 ? v.slice(0, 100) + `…(${v.length})` : v;
} else {
out[k] = v;
}
}
return out;
}
function summarizeMessages(msgs: any[]): ProbeMessageSummary[] {
return (msgs ?? []).slice(0, 80).map((m) => ({
id: (m.id ?? '').slice(-8),
role: m.role,
cLen: (m.content ?? '').length,
rLen: (m.reasoning?.content ?? '').length,
tools: (m.tools ?? []).length,
chN: (m.children ?? []).length,
}));
}
function shortStack(): string {
const raw = new Error('probe-stack').stack ?? '';
return raw
.split('\n')
.slice(3)
.filter((l) => !l.includes('probe-events') && !l.includes('node_modules'))
.map((l) => l.trim().replace(/^at\s+/, ''))
.slice(0, 6)
.join(' ← ');
}
function recordAgentEvent(args: {
transport: 'ws' | 'sse';
opId: string | null;
agentEvent: any;
eventId?: string | null;
rawLen?: number;
}): void {
const { transport, opId, agentEvent, eventId, rawLen } = args;
if (!agentEvent || typeof agentEvent !== 'object') return;
events.push({
t: now(),
transport,
opIdTail: (opId ?? '').slice(-10),
eventId: eventId ?? null,
type: agentEvent.type,
stepIndex: agentEvent.stepIndex,
dataKeys: agentEvent.data ? Object.keys(agentEvent.data) : [],
data: summarizeData(agentEvent.data) as Record<string, unknown>,
rawLen,
});
}
// ── 1. Patch window.WebSocket for gateway WS events ────────────────
if (!w.__PROBE_ORIG_WEBSOCKET) w.__PROBE_ORIG_WEBSOCKET = w.WebSocket;
const OrigWS = w.__PROBE_ORIG_WEBSOCKET;
function extractOpIdFromWsUrl(url: string | URL): string | null {
const m = String(url ?? '').match(/operationId=([^&]+)/);
return m ? decodeURIComponent(m[1]) : null;
}
function isGatewayWs(url: string | URL): boolean {
return String(url ?? '').includes('operationId=');
}
function handleWsFrame(rawData: unknown, opId: string | null): void {
const rawLen = typeof rawData === 'string' ? rawData.length : -1;
let parsed: any;
try {
parsed = typeof rawData === 'string' ? JSON.parse(rawData) : null;
} catch {
events.push({
t: now(),
transport: 'ws',
opIdTail: (opId ?? '').slice(-10),
type: '_PARSE_ERROR_',
raw: typeof rawData === 'string' && rawData.length < 400 ? rawData : '(non-string or large)',
});
return;
}
if (!parsed) return;
if (parsed.type === 'agent_event') {
recordAgentEvent({
transport: 'ws',
opId,
agentEvent: parsed.event,
eventId: parsed.id,
rawLen,
});
} else {
events.push({
t: now(),
transport: 'ws',
opIdTail: (opId ?? '').slice(-10),
type: '_SERVER_MSG_',
serverType: parsed.type,
rawLen,
});
}
}
// Wrap the constructor. Instance `constructor` will still reflect OrigWS
// (we share prototypes), so use the `_WS_OPEN_` sentinel events to confirm
// the patch is firing.
function PatchedWebSocket(this: WebSocket, url: string | URL, protocols?: string | string[]) {
const ws: WebSocket = protocols == null ? new OrigWS(url) : new OrigWS(url, protocols);
const opId = extractOpIdFromWsUrl(url);
if (!isGatewayWs(url)) return ws;
events.push({
t: now(),
transport: 'ws',
opIdTail: (opId ?? '').slice(-10),
type: '_WS_OPEN_',
url: String(url),
});
// One observer listener that always fires, regardless of how the consumer
// (AgentStreamClient uses `ws.onmessage = …`) subscribes.
ws.addEventListener('message', (e) => {
try {
handleWsFrame((e as MessageEvent).data, opId);
} catch {
/* swallow */
}
});
ws.addEventListener('close', () => {
events.push({
t: now(),
transport: 'ws',
opIdTail: (opId ?? '').slice(-10),
type: '_WS_CLOSE_',
});
});
return ws;
}
// Preserve prototype + static fields so `instanceof WebSocket` and
// `WebSocket.OPEN` constants still work.
(PatchedWebSocket as unknown as { prototype: WebSocket }).prototype = OrigWS.prototype;
for (const k of Object.keys(OrigWS) as Array<keyof typeof OrigWS>) {
try {
(PatchedWebSocket as any)[k] = (OrigWS as any)[k];
} catch {
/* readonly */
}
}
(['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'] as const).forEach((k) => {
(PatchedWebSocket as any)[k] = (OrigWS as any)[k];
});
w.WebSocket = PatchedWebSocket as unknown as typeof WebSocket;
// ── 2. Patch window.fetch for `/api/agent/stream` (direct-mode SSE) ─
if (!w.__PROBE_ORIG_FETCH) w.__PROBE_ORIG_FETCH = w.fetch.bind(w);
const origFetch = w.__PROBE_ORIG_FETCH;
function isAgentStreamUrl(input: RequestInfo | URL): boolean {
let url = '';
if (typeof input === 'string') url = input;
else if (input instanceof URL) url = input.toString();
else if (input && typeof (input as Request).url === 'string') url = (input as Request).url;
return url.includes('/api/agent/stream');
}
function extractOpIdFromHttpUrl(input: RequestInfo | URL): string | null {
const url = typeof input === 'string' ? input : (input as Request | URL).toString();
const m = url.match(/operationId=([^&]+)/);
return m ? decodeURIComponent(m[1]) : null;
}
function pushFromSSEFrame(rawFrame: string, opId: string | null): void {
const lines = rawFrame.split('\n');
let dataJson = '';
let evtName = 'message';
for (const line of lines) {
if (line.startsWith('event:')) evtName = line.slice(6).trim();
else if (line.startsWith('data:')) dataJson += line.slice(5).trim();
}
if (!dataJson) return;
let parsed: any;
try {
parsed = JSON.parse(dataJson);
} catch {
events.push({
t: now(),
transport: 'sse',
opIdTail: (opId ?? '').slice(-10),
type: '_PARSE_ERROR_',
sseEvent: evtName,
raw: dataJson.length > 400 ? dataJson.slice(0, 400) + '…' : dataJson,
});
return;
}
recordAgentEvent({
transport: 'sse',
opId,
agentEvent: parsed,
eventId: null,
rawLen: dataJson.length,
});
}
async function teeAndDrain(response: Response, opId: string | null): Promise<Response> {
if (!response.body) return response;
const [a, b] = response.body.tee();
void (async () => {
const reader = b.getReader();
const decoder = new TextDecoder();
let buf = '';
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
let idx: number;
while ((idx = buf.indexOf('\n\n')) !== -1) {
const frame = buf.slice(0, idx);
buf = buf.slice(idx + 2);
if (frame.trim()) pushFromSSEFrame(frame, opId);
}
}
if (buf.trim()) pushFromSSEFrame(buf, opId);
} catch (e: any) {
events.push({
t: now(),
transport: 'sse',
opIdTail: (opId ?? '').slice(-10),
type: '_TEE_ERROR_',
message: String(e?.message ?? e),
});
}
})();
return new Response(a, {
headers: response.headers,
status: response.status,
statusText: response.statusText,
});
}
w.fetch = async function patchedFetch(input: RequestInfo | URL, init?: RequestInit) {
const response = await origFetch(input as any, init);
if (!isAgentStreamUrl(input)) return response;
const opId = extractOpIdFromHttpUrl(input);
const url =
typeof input === 'string'
? input.split('?')[0]
: (input as Request | URL).toString().split('?')[0];
events.push({
t: now(),
transport: 'sse',
opIdTail: (opId ?? '').slice(-10),
type: '_CONNECTED_',
url,
status: response.status,
});
return teeAndDrain(response, opId);
} as typeof fetch;
// ── 3. Wrap store actions (best-effort for "who called replace") ────
// Side-global stash for the original chat-store actions. Re-installs ALWAYS
// rewrap from the originals so updates to the probe body take effect
// without a page reload — using only a `__probeWrapped` flag on the chat
// state object would freeze the first-installed wrapper across re-installs.
declare global {
interface Window {
__PROBE_ORIG_REFRESH_MESSAGES?: any;
__PROBE_ORIG_REPLACE_MESSAGES?: any;
}
}
try {
const chat = w.__LOBE_STORES?.chat?.();
if (chat) {
// First-time install: cache the originals. Re-install: restore from
// the cached originals before wrapping again.
if (!w.__PROBE_ORIG_REFRESH_MESSAGES) w.__PROBE_ORIG_REFRESH_MESSAGES = chat.refreshMessages;
if (!w.__PROBE_ORIG_REPLACE_MESSAGES) w.__PROBE_ORIG_REPLACE_MESSAGES = chat.replaceMessages;
const origRefresh = w.__PROBE_ORIG_REFRESH_MESSAGES;
const origReplace = w.__PROBE_ORIG_REPLACE_MESSAGES;
chat.refreshMessages = origRefresh;
chat.replaceMessages = origReplace;
chat.refreshMessages = async function probeRefresh(this: unknown, ...args: any[]) {
calls.push({
t: now(),
name: 'refreshMessages',
args: { context: args[0] ?? null },
stack: shortStack(),
});
return origRefresh.apply(this, args);
};
chat.replaceMessages = function probeReplace(this: unknown, ...args: any[]) {
const msgs = (args[0] as any[]) ?? [];
const snapshot = msgs.slice(-2).map((m) => ({
id: (m.id ?? '').slice(-8),
role: m.role,
cLen: (m.content ?? '').length,
rLen: (m.reasoning?.content ?? '').length,
updatedAt: m.updatedAt,
}));
calls.push({
t: now(),
name: 'replaceMessages',
args: { count: msgs.length, params: args[1] ?? null, snapshot } as any,
stack: shortStack(),
});
// Pair the call with a mutation row so the analyzer can build a
// single ordered timeline across replaceMessages + dispatchMessage.
const stackTop = shortStack().split(' ← ')[0]?.slice(0, 80);
const last = msgs.at(-1);
const lastSum = last
? {
id: (last.id ?? '').slice(-8),
role: last.role,
cLen: (last.content ?? '').length,
rLen: (last.reasoning?.content ?? '').length,
updatedAt: last.updatedAt,
}
: undefined;
const params: any = args[1] ?? {};
const ctxKey = params.context
? `main_${params.context.agentId ?? '?'}_${
params.context.topicId ? 'tpc_' + params.context.topicId : 'new'
}`.replace('main_tpc_', 'main_') // crude key inference
: '(no-ctx)';
mutations.push({
t: now(),
key: ctxKey,
n: msgs.length,
last: lastSum,
delta: `replaceMessages(action=${params.action ?? '-'}) src=${stackTop ?? '-'}`,
});
return origReplace.apply(this, args);
};
}
} catch (e: any) {
calls.push({ t: now(), name: '_WRAP_ERROR_', error: String(e?.message ?? e) });
}
// ── 3.5. Mutation log — wrap the TWO ChatStore writers (replaceMessages,
// internal_dispatchMessage) to record EVERY dbMessagesMap[key] reference
// change with a one-line "before/after last assistant message" delta. This
// reveals dispatchMessage-driven collapses that the replaceMessages wrap
// alone cannot see.
declare global {
interface Window {
__PROBE_ORIG_DISPATCH_MESSAGE?: any;
}
}
try {
const chat = w.__LOBE_STORES?.chat?.();
if (chat?.internal_dispatchMessage) {
if (!w.__PROBE_ORIG_DISPATCH_MESSAGE)
w.__PROBE_ORIG_DISPATCH_MESSAGE = chat.internal_dispatchMessage;
const origDispatch = w.__PROBE_ORIG_DISPATCH_MESSAGE;
chat.internal_dispatchMessage = origDispatch;
chat.internal_dispatchMessage = function probeDispatch(this: unknown, payload: any, ctx?: any) {
// Snapshot BEFORE — read the would-be target key + last message.
const before = (() => {
try {
const state = w.__LOBE_STORES?.chat?.();
if (!state) return null;
// Replicate state.internal_getConversationContext logic enough to
// resolve a key — but most callers pass operationId on ctx, and
// operationId-keyed lookup needs store internals. Easiest: snapshot
// ALL keys' last-assistant cLen and compare BEFORE vs AFTER below.
const map = state.dbMessagesMap ?? {};
const out: Record<string, any> = {};
for (const k of Object.keys(map)) {
const last = (map[k] ?? []).at(-1);
out[k] = last
? {
id: (last.id ?? '').slice(-8),
cLen: (last.content ?? '').length,
rLen: (last.reasoning?.content ?? '').length,
n: map[k].length,
}
: { n: 0 };
}
return out;
} catch {
return null;
}
})();
const result = origDispatch.apply(this, [payload, ctx]);
// Snapshot AFTER — find which key(s) actually changed.
try {
const state = w.__LOBE_STORES?.chat?.();
if (state && before) {
const map = state.dbMessagesMap ?? {};
for (const k of Object.keys(map)) {
const last = (map[k] ?? []).at(-1);
const beforeSnap = before[k];
const afterSnap = last
? {
id: (last.id ?? '').slice(-8),
cLen: (last.content ?? '').length,
rLen: (last.reasoning?.content ?? '').length,
n: map[k].length,
}
: { n: 0 };
const changed =
!beforeSnap ||
beforeSnap.n !== afterSnap.n ||
beforeSnap.id !== (afterSnap as any).id ||
beforeSnap.cLen !== (afterSnap as any).cLen ||
beforeSnap.rLen !== (afterSnap as any).rLen;
if (!changed) continue;
let delta = '';
if (beforeSnap?.id !== undefined && beforeSnap.id !== (afterSnap as any).id)
delta += `id:${beforeSnap.id}${(afterSnap as any).id};`;
if (
beforeSnap?.cLen !== undefined &&
(afterSnap as any).cLen !== undefined &&
(afterSnap as any).cLen < beforeSnap.cLen
)
delta += `cLen↓${beforeSnap.cLen}${(afterSnap as any).cLen};`;
if (
beforeSnap?.rLen !== undefined &&
(afterSnap as any).rLen !== undefined &&
(afterSnap as any).rLen < beforeSnap.rLen
)
delta += `rLen↓${beforeSnap.rLen}${(afterSnap as any).rLen};`;
if (beforeSnap?.n !== undefined && afterSnap.n < beforeSnap.n)
delta += `n↓${beforeSnap.n}${afterSnap.n};`;
mutations.push({
t: now(),
key: k,
n: afterSnap.n,
last: (afterSnap as any).id ? (afterSnap as any) : undefined,
prevLast: beforeSnap?.id ? beforeSnap : undefined,
delta: delta || `dispatch:${payload?.type}`,
});
}
}
} catch (e: any) {
mutations.push({
t: now(),
key: '_DISPATCH_PROBE_ERROR_',
n: -1,
delta: String(e?.message ?? e),
});
}
return result;
};
}
} catch (e: any) {
calls.push({ t: now(), name: '_DISPATCH_WRAP_ERROR_', error: String(e?.message ?? e) });
}
// ── 4. Periodic per-key timeline snapshots ─────────────────────────
function captureTimeline(): void {
try {
const c = w.__LOBE_STORES?.chat?.();
if (!c) return;
const msgsMap = (c.messagesMap ?? {}) as Record<string, any[]>;
const dbMap = (c.dbMessagesMap ?? {}) as Record<string, any[]>;
const byKey: ProbeTimelineSample['byKey'] = {};
for (const k of Object.keys(msgsMap)) {
const display = msgsMap[k] ?? [];
const db = dbMap[k] ?? [];
if (display.length === 0 && db.length === 0) continue;
byKey[k] = {
n: display.length,
dbN: db.length,
msgs: summarizeMessages(display),
};
}
const ops = Object.values((c.operations ?? {}) as Record<string, any>);
timeline.push({
t: now(),
activeTopic: ((c.activeTopicId as string | null) ?? '').slice(-10) || null,
keys: Object.keys(byKey),
byKey,
runOps: ops.filter((o: any) => o.status === 'running').length,
});
} catch (e: any) {
timeline.push({
t: now(),
activeTopic: null,
keys: [],
byKey: {},
runOps: 0,
err: e?.message ?? String(e),
});
}
}
captureTimeline();
if (w.__PROBE_TIMELINE_TIMER) clearInterval(w.__PROBE_TIMELINE_TIMER);
w.__PROBE_TIMELINE_TIMER = setInterval(captureTimeline, 200);
// ── 5. Tab-switch helpers ──────────────────────────────────────────
function listTopBarTabs(): HTMLElement[] {
return Array.from(
document.querySelectorAll<HTMLElement>(
'[data-insp-path*="TabItem.tsx"][data-contextmenu-trigger]',
),
).filter((t) => t.getBoundingClientRect().top < 30);
}
w.__listTabs = () =>
listTopBarTabs().map((t, i) => ({
i,
key: t.getAttribute('data-contextmenu-trigger'),
active: t.getAttribute('data-active') === 'true',
title: (t.innerText ?? '').slice(0, 60),
}));
w.__clickTabByKey = (key: string) => {
const tab = listTopBarTabs().find((t) => t.getAttribute('data-contextmenu-trigger') === key);
if (!tab) return 'not found: ' + key;
if (tab.getAttribute('data-active') === 'true') return 'already active: ' + key;
tab.click();
return 'clicked key=' + key;
};
w.__PROBE_EVENT = (name: string) => {
calls.push({ t: now(), name: 'MARK:' + name });
};
// `run.ts` wraps the bundle in an IIFE and appends a `return <confirmation>`
// after the bundle body — agent-browser then prints the confirmation back to
// the operator. Nothing to do here at the end of the module body.
@@ -1,204 +0,0 @@
// LobeHub chat streaming time-series probe.
//
// Inject into the renderer (via agent-browser eval) to record store + DOM
// snapshots every 200ms during a streaming session. Designed to surface
// "UI rolled back to an earlier state" symptoms — especially around
// gateway-mode tab switches that happen while the assistant is still writing.
//
// Usage:
// agent-browser --cdp 9222 eval --stdin < probe.js
// # ...do test interactions, call window.__PROBE_EVENT('LABEL') to mark moments...
// agent-browser --cdp 9222 eval --stdin < probe-dump.js > /tmp/probe.json
// node analyze.mjs /tmp/probe.json
//
// What it captures per sample:
// - activeTopicId
// - msgN: top-level messages in chat.messagesMap for this topic
// - childN: total assistantGroup.children blocks across all msgs (THIS is
// where streaming content actually lives — top-level assistantGroup stays empty)
// - cT / rT / toolT: totals across messages AND their children
// (content, reasoning, tool-call count)
// - perMsg: per-message breakdown so regressions can be located precisely
// - runOps: number of running operations (execServerAgentRuntime etc.)
// - domLen: total innerText length of the rendered chat list area
// - ind: visible UI indicators (Search pages, Crawled pages, Deeply Thought, Sending)
//
// Event markers: window.__PROBE_EVENT('NAME') records {t, name} into
// __PROBE_EVENTS, used by the analyzer to align state changes with
// user-driven actions (SENT, AWAY_1, BACK_1, ...).
(function () {
if (window.__PROBE_TIMER) clearInterval(window.__PROBE_TIMER);
window.__PROBE_SAMPLES = [];
window.__PROBE_EVENTS = [];
const t0 = Date.now();
function snapshot() {
try {
const chat = window.__LOBE_STORES.chat();
const topicId = chat.activeTopicId;
const idTail = topicId ? topicId.replace('tpc_', '') : null;
const keys = Object.keys(chat.messagesMap || {});
// Collect messages for the active topic. Before a topic is committed,
// optimistic messages live under the `<agentScope>_new` key — fall
// back to those when no topic is active yet.
let msgs = [];
if (idTail) {
keys.forEach((k) => {
if (k.includes(idTail)) msgs = msgs.concat(chat.messagesMap[k] || []);
});
} else {
keys
.filter((k) => k.endsWith('_new'))
.forEach((k) => {
msgs = msgs.concat(chat.messagesMap[k] || []);
});
}
// Walk top-level + assistantGroup.children. children carry the actual
// streamed content / reasoning / tool calls; the parent assistantGroup
// remains a placeholder (cLen=0, rLen=0) for its whole lifetime.
let totalContent = 0;
let totalReason = 0;
let totalTools = 0;
let childCount = 0;
const perMsg = msgs.map((m) => {
const cLen = (m.content || '').length;
const rLen = ((m.reasoning && m.reasoning.content) || '').length;
const tools = (m.tools || []).length;
totalContent += cLen;
totalReason += rLen;
totalTools += tools;
const children = m.children || [];
let chC = 0;
let chR = 0;
let chT = 0;
children.forEach((c) => {
chC += (c.content || '').length;
chR += ((c.reasoning && c.reasoning.content) || '').length;
chT += (c.tools || []).length;
});
totalContent += chC;
totalReason += chR;
totalTools += chT;
childCount += children.length;
return {
id: (m.id || '').slice(-8),
role: m.role,
cLen,
rLen,
tools,
chCount: children.length,
chC,
chR,
chT,
};
});
const ops = Object.values(chat.operations || {});
const runningOps = ops.filter((o) => o.status === 'running');
// DOM probe: total rendered text in the chat scroll area (proxy for
// "how much is actually visible to the user").
const convScroll =
document.querySelector(
'[data-chat-list], [class*="ChatList"], [class*="ConversationList"]',
) ||
document.querySelector('main [class*="scroll"]') ||
document.querySelector('main');
const domTxt = convScroll ? convScroll.innerText || '' : '';
const bodyTxt = document.body.innerText || '';
const searchMatches = (bodyTxt.match(/Search pages?:|Searched the web/g) || []).length;
const crawlMatches = (bodyTxt.match(/Crawl(ed|ing) pages?/g) || []).length;
window.__PROBE_SAMPLES.push({
t: Date.now() - t0,
topicId,
msgN: msgs.length,
childN: childCount,
cT: totalContent,
rT: totalReason,
toolT: totalTools,
perMsg,
runOps: runningOps.length,
runOpTypes: runningOps.map((o) => o.type),
domLen: domTxt.length,
ind: {
search: searchMatches,
crawl: crawlMatches,
sending: bodyTxt.includes('Sending message'),
deeplyThinking: bodyTxt.includes('Deeply Thinking'),
deeplyThought: bodyTxt.includes('Deeply Thought'),
},
});
} catch (e) {
window.__PROBE_SAMPLES.push({ t: Date.now() - t0, err: e.message });
}
}
snapshot();
window.__PROBE_TIMER = setInterval(snapshot, 200);
window.__PROBE_EVENT = function (name) {
window.__PROBE_EVENTS.push({ t: Date.now() - t0, name });
};
// Tab-switch helpers installed alongside the probe.
//
// The Electron tab bar mounts each tab as a div with data-insp-path
// ending in `TabItem.tsx:...`. The active tab is marked with
// data-active="true". DO NOT search by innerText — the active tab's text
// includes a ` · <agent name>` suffix that produces false matches when
// your search string happens to overlap with the agent name.
function listTabs() {
return Array.from(
document.querySelectorAll('[data-insp-path*="TabItem.tsx"][data-contextmenu-trigger]'),
).filter((t) => t.getBoundingClientRect().top < 30);
}
function tabKey(el) {
// Stable for the tab's lifetime; survives focus changes.
return el.getAttribute('data-contextmenu-trigger');
}
function findActiveTab() {
return listTabs().find((t) => t.getAttribute('data-active') === 'true') || null;
}
// Click by stable key captured earlier (preferred for round-trips).
window.__clickTabByKey = function (key) {
const tab = listTabs().find((t) => tabKey(t) === key);
if (!tab) return 'not found: key=' + key;
if (tab.getAttribute('data-active') === 'true') return 'already active: ' + key;
tab.click();
return 'clicked key=' + key;
};
// Click by index in the tab strip (0-based, left-to-right).
window.__clickTabByIndex = function (i) {
const tabs = listTabs();
if (i < 0 || i >= tabs.length) return 'index out of range: ' + i + '/' + tabs.length;
const t = tabs[i];
if (t.getAttribute('data-active') === 'true') return 'already active: i=' + i;
t.click();
return 'clicked i=' + i + ' key=' + tabKey(t);
};
// Snapshot all tabs in order: [{key, active, title (first 60 chars of innerText)}]
window.__listTabs = function () {
return listTabs().map((t, i) => ({
i,
key: tabKey(t),
active: t.getAttribute('data-active') === 'true',
title: (t.innerText || '').slice(0, 60),
}));
};
window.__activeTabKey = function () {
const a = findActiveTab();
return a ? tabKey(a) : null;
};
return 'probe installed';
})();
@@ -1,211 +0,0 @@
// CLI for the agent-gateway probe.
//
// Bundles the TS probes with esbuild, pipes them into `agent-browser eval`,
// and persists dumps under `.agent-gateway/` (gitignored) for later use as
// streaming-replay test fixtures.
//
// Commands:
// bun run .agents/skills/local-testing/scripts/agent-gateway/run.ts install
// Bundle probe-events.ts and inject into the CDP-attached browser.
// Re-installing clears all buffers and re-patches WebSocket / fetch.
//
// bun run .agents/skills/local-testing/scripts/agent-gateway/run.ts dump [name]
// Stop the timeline timer, fetch the capture as JSON, write it to
// `.agent-gateway/<name>-<YYYYMMDD-HHmmss>.json`. `name` defaults to
// `dump`. Prints the absolute path written.
//
// bun run .agents/skills/local-testing/scripts/agent-gateway/run.ts analyze [path]
// Run analyze-events.ts on the dump. `path` defaults to the most
// recently modified file in `.agent-gateway/`.
//
// Optional flags:
// --cdp <port> CDP port (default 9222)
// --browser <bin> agent-browser binary (default 'agent-browser')
import { spawn } from 'node:child_process';
import { mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
// .agents/skills/local-testing/scripts/agent-gateway/ → 5 levels up
const PROJECT_ROOT = path.resolve(SCRIPT_DIR, '../../../../..');
const DUMP_DIR = path.join(PROJECT_ROOT, '.agent-gateway');
interface Flags {
browser: string;
cdp: string;
positional: string[];
}
function parseFlags(argv: string[]): Flags {
const out: Flags = { cdp: '9222', browser: 'agent-browser', positional: [] };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '--cdp') out.cdp = argv[++i] ?? out.cdp;
else if (a === '--browser') out.browser = argv[++i] ?? out.browser;
else out.positional.push(a);
}
return out;
}
async function bundle(entry: string): Promise<string> {
// Bun.build is built into the Bun runtime — no external dep needed.
const r = await Bun.build({
entrypoints: [path.join(SCRIPT_DIR, entry)],
target: 'browser',
format: 'esm',
minify: false,
});
if (!r.success) {
const msgs = r.logs.map((l) => `${l.level}: ${l.message}`).join('\n');
throw new Error(`bundle failed for ${entry}:\n${msgs}`);
}
return await r.outputs[0].text();
}
function wrapIife(body: string, returnExpr: string): string {
// Wrap as an IIFE that swallows the bundled top-level (top-level `const`
// declarations get scoped to the IIFE, so re-injection doesn't conflict)
// and returns the configured expression — which `agent-browser eval`
// captures and prints to stdout.
return `(() => {\n${body}\n;return ${returnExpr};\n})()`;
}
function runAgentBrowserEval(flags: Flags, script: string): Promise<string> {
return new Promise((resolveP, rejectP) => {
const child = spawn(flags.browser, ['--cdp', flags.cdp, 'eval', '--stdin'], {
stdio: ['pipe', 'pipe', 'inherit'],
});
let stdout = '';
child.stdout.on('data', (chunk: Buffer) => {
stdout += chunk.toString('utf8');
});
child.on('error', rejectP);
child.on('close', (code) => {
if (code === 0) resolveP(stdout);
else rejectP(new Error(`agent-browser exited ${code}`));
});
child.stdin.write(script);
child.stdin.end();
});
}
// agent-browser prints eval results as JSON (string values are quoted).
function unquoteAgentBrowserResult(raw: string): string {
const trimmed = raw.trim();
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
try {
return JSON.parse(trimmed) as string;
} catch {
/* fall through */
}
}
return trimmed;
}
function isoStamp(): string {
const d = new Date();
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const hh = String(d.getHours()).padStart(2, '0');
const mi = String(d.getMinutes()).padStart(2, '0');
const ss = String(d.getSeconds()).padStart(2, '0');
return `${yyyy}${mm}${dd}-${hh}${mi}${ss}`;
}
function ensureDumpDir(): void {
mkdirSync(DUMP_DIR, { recursive: true });
}
function latestDump(): string | null {
ensureDumpDir();
const entries = readdirSync(DUMP_DIR)
.filter((f) => f.endsWith('.json'))
.map((f) => ({ f, mtime: statSync(path.join(DUMP_DIR, f)).mtimeMs }))
.sort((a, b) => b.mtime - a.mtime);
return entries[0] ? path.join(DUMP_DIR, entries[0].f) : null;
}
// ── Commands ────────────────────────────────────────────────────────
async function cmdInstall(flags: Flags): Promise<void> {
const body = await bundle('probe-events.ts');
const installMsg = JSON.stringify(
'events probe installed: WebSocket+fetch interception. ' +
'WS captures operationId= sockets (gateway), fetch captures /api/agent/stream (direct).',
);
const script = wrapIife(body, installMsg);
const out = await runAgentBrowserEval(flags, script);
console.log(unquoteAgentBrowserResult(out));
}
async function cmdDump(flags: Flags): Promise<void> {
const name = flags.positional[1] ?? 'dump';
const body = await bundle('probe-dump.ts');
const script = wrapIife(body, 'window.__PROBE_LAST_DUMP_JSON');
const raw = await runAgentBrowserEval(flags, script);
const json = unquoteAgentBrowserResult(raw);
ensureDumpDir();
const filename = `${name}-${isoStamp()}.json`;
const dumpPath = path.join(DUMP_DIR, filename);
writeFileSync(dumpPath, json, 'utf8');
// Validate by parsing the meta header so we error early on bad capture
try {
const parsed = JSON.parse(json) as {
meta?: { eventCount?: number; callCount?: number; sampleCount?: number };
};
const meta = parsed.meta ?? {};
console.log(
`wrote ${dumpPath} (${json.length} bytes events=${meta.eventCount ?? '?'} ` +
`calls=${meta.callCount ?? '?'} samples=${meta.sampleCount ?? '?'})`,
);
} catch {
console.log(`wrote ${dumpPath} (${json.length} bytes — JSON.parse failed; see file)`);
}
}
async function cmdAnalyze(flags: Flags): Promise<void> {
const target = flags.positional[1] ?? latestDump();
if (!target) {
console.error('no dump file found. run `dump` first or pass a path.');
process.exit(1);
}
const child = spawn('bun', ['run', path.join(SCRIPT_DIR, 'analyze-events.ts'), target], {
stdio: 'inherit',
});
await new Promise<void>((resolveP, rejectP) => {
child.on('error', rejectP);
child.on('close', (code) => (code === 0 ? resolveP() : rejectP(new Error(`exit ${code}`))));
});
}
// ── Entry point ─────────────────────────────────────────────────────
const flags = parseFlags(process.argv.slice(2));
const cmd = flags.positional[0];
const usage = `usage:
bun run run.ts install [--cdp 9222]
bun run run.ts dump [name] [--cdp 9222]
bun run run.ts analyze [path]
`;
if (!cmd) {
console.error(usage);
process.exit(1);
}
try {
if (cmd === 'install') await cmdInstall(flags);
else if (cmd === 'dump') await cmdDump(flags);
else if (cmd === 'analyze') await cmdAnalyze(flags);
else {
console.error(`unknown command: ${cmd}\n\n${usage}`);
process.exit(1);
}
} catch (e: any) {
console.error(e?.stack ?? e);
process.exit(1);
}
@@ -1,72 +0,0 @@
// Run N round-trip tab switches with event markers timed against the probe.
//
// agent-browser --cdp 9222 eval --stdin < tab-switch.js
//
// Captures the currently-active tab as the BACK target and the rightmost
// inactive tab as the AWAY target. Both are addressed by their stable
// data-contextmenu-trigger key (NOT by visible title — the active tab's
// innerText embeds a ` · <agent name>` suffix that breaks text matching).
//
// Fires the loop in the background and returns immediately so the
// agent-browser eval doesn't have to await the full ROUND_TRIPS × DWELL_MS
// duration. Wait on the `SWITCH_LOOP_DONE` event before dumping.
//
// Refuses to launch if a previous loop is still in flight.
//
// Requires probe.js to have been installed first (provides
// window.__PROBE_EVENT / __listTabs / __clickTabByKey / __activeTabKey).
(function () {
const ROUND_TRIPS = 4;
const DWELL_MS = 10_000;
if (!window.__PROBE_EVENT || !window.__listTabs || !window.__clickTabByKey) {
return 'probe not installed — eval probe.js first';
}
if (window.__SWITCH_LOOP_RUNNING) {
return 'switch loop already running — wait for SWITCH_LOOP_DONE first';
}
const tabs = window.__listTabs();
const activeTab = tabs.find((t) => t.active);
if (!activeTab) return 'no active tab — abort';
// Pick the first inactive tab as AWAY target. With multiple inactive tabs
// you'll usually want the one that's stable across the test — feel free
// to swap to tabs[tabs.length-1] if you want the rightmost.
const inactives = tabs.filter((t) => !t.active);
if (inactives.length === 0) return 'no inactive tab to switch to — abort';
const awayTab = inactives.at(-1); // rightmost inactive
const BACK_KEY = activeTab.key;
const AWAY_KEY = awayTab.key;
window.__SWITCH_LOOP_RUNNING = true;
window.__PROBE_EVENT('SWITCH_LOOP_CONFIG:back=' + BACK_KEY + ',away=' + AWAY_KEY);
(async function () {
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
try {
window.__PROBE_EVENT('SWITCH_LOOP_START');
for (let i = 1; i <= ROUND_TRIPS; i++) {
window.__PROBE_EVENT('AWAY_' + i);
const awayResult = window.__clickTabByKey(AWAY_KEY);
window.__PROBE_EVENT('AWAY_' + i + '_RES:' + awayResult.slice(0, 50));
await sleep(DWELL_MS);
window.__PROBE_EVENT('BACK_' + i);
const backResult = window.__clickTabByKey(BACK_KEY);
window.__PROBE_EVENT('BACK_' + i + '_RES:' + backResult.slice(0, 50));
await sleep(DWELL_MS);
}
window.__PROBE_EVENT('SWITCH_LOOP_DONE');
} finally {
window.__SWITCH_LOOP_RUNNING = false;
}
})();
return 'switch loop kicked off (BACK=' + BACK_KEY + ', AWAY=' + AWAY_KEY + ')';
})();
@@ -1,113 +0,0 @@
// Shared types between the in-browser probe and the Node-side analyzer.
// Kept tiny on purpose — anything the analyzer can re-derive is left off.
export interface ProbeStreamEvent {
/** Summarized payload — long strings truncated, arrays printed as Array(N) */
data?: Record<string, unknown>;
/** Keys present on the event's `data` payload — useful at a glance */
dataKeys?: string[];
/** ServerMessage.id — gateway WS frames carry an event-id we may resume from */
eventId?: string | null;
message?: string;
/** Last 10 chars of the operationId (full id is excessively long) */
opIdTail: string;
raw?: string;
/** Raw frame byte length, when applicable */
rawLen?: number;
/** For non-agent_event server frames (auth_success, heartbeat_ack, …) */
serverType?: string;
sseEvent?: string;
status?: number;
stepIndex?: number;
/** Milliseconds since the probe's t0 (install time). */
t: number;
/** 'ws' for gateway WebSocket frames, 'sse' for direct /api/agent/stream */
transport: 'ws' | 'sse';
/** Either the AgentStreamEvent.type, or a probe sentinel like `_WS_OPEN_` */
type: string;
url?: string;
}
export interface ProbeActionCall {
args?: {
count?: number;
context?: unknown;
params?: unknown;
};
error?: string;
/** `replaceMessages` / `refreshMessages` / `MARK:<label>` / `_WRAP_ERROR_` */
name: string;
stack?: string;
t: number;
}
export interface ProbeMessageSummary {
/** children.length */
chN: number;
/** content.length */
cLen: number;
/** Last 8 chars of the message id */
id: string;
/** reasoning.content.length */
rLen: number;
role: string;
/** tools.length */
tools: number;
}
export interface ProbeTimelineSample {
/** Last 10 chars of activeTopicId, or null */
activeTopic: string | null;
/** Per-key breakdown: display count, db count, message summaries */
byKey: Record<
string,
{
n: number;
dbN: number;
msgs: ProbeMessageSummary[];
}
>;
err?: string;
/** All messagesMap keys that have content at this moment */
keys: string[];
/** Number of operations in 'running' status */
runOps: number;
t: number;
}
export interface ProbeDumpMeta {
callCount: number;
/** Date.now() at dump call */
collectedAt: number;
eventCount: number;
sampleCount: number;
/** Date.now() at probe install */
t0: number;
}
export interface ProbeDump {
actionCalls: ProbeActionCall[];
meta: ProbeDumpMeta;
streamEvents: ProbeStreamEvent[];
timeline: ProbeTimelineSample[];
}
/**
* Globals the probe attaches to `window`. Keeps `as any` casts at the boundary
* instead of sprinkling them through the probe body.
*/
declare global {
interface Window {
__clickTabByKey?: (key: string) => string;
__listTabs?: () => Array<{ i: number; key: string | null; active: boolean; title: string }>;
__LOBE_STORES?: Record<string, () => any>;
__PROBE_ACTION_CALLS?: ProbeActionCall[];
__PROBE_EVENT?: (label: string) => void;
__PROBE_MSG_TIMELINE?: ProbeTimelineSample[];
__PROBE_ORIG_FETCH?: typeof fetch;
__PROBE_ORIG_WEBSOCKET?: typeof WebSocket;
__PROBE_STREAM_EVENTS?: ProbeStreamEvent[];
__PROBE_T0?: number;
__PROBE_TIMELINE_TIMER?: ReturnType<typeof setInterval> | null;
}
}
@@ -1,327 +0,0 @@
#!/usr/bin/env bash
#
# electron-dev.sh — Manage Electron dev environment for testing
#
# Usage:
# ./electron-dev.sh start # Kill existing, start fresh, wait until ready
# ./electron-dev.sh stop # Kill all Electron-related processes
# ./electron-dev.sh status # Check if Electron is running and CDP is reachable
# ./electron-dev.sh restart # Stop then start
#
# Environment variables:
# CDP_PORT — Chrome DevTools Protocol port (default: 9222)
# ELECTRON_LOG — Log file path (default: /tmp/electron-dev.log)
# ELECTRON_WAIT_S — Max seconds to wait for CDP to become reachable (default: 90)
# RENDERER_WAIT_S — Max seconds to wait for SPA after CDP is up (default: 60)
# FORCE_KILL_USER — When set to 1, silently kill the user's `bun run dev`
# Electron without confirmation (default: always confirm-by-action)
#
set -euo pipefail
CDP_PORT="${CDP_PORT:-9222}"
ELECTRON_LOG="${ELECTRON_LOG:-/tmp/electron-dev.log}"
ELECTRON_WAIT_S="${ELECTRON_WAIT_S:-90}"
RENDERER_WAIT_S="${RENDERER_WAIT_S:-60}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
PIDFILE="/tmp/electron-dev-cdp-${CDP_PORT}.pid"
# Project-scoped electron path prefix used for pgrep matching. Any Electron
# binary from this project (main + helpers, with or without --remote-debugging-port)
# starts with this string in its argv[0], so a single substring match catches all.
PROJECT_ELECTRON_PATH="${PROJECT_ROOT}/apps/desktop/node_modules/.pnpm/electron@"
# ── Helpers ──────────────────────────────────────────────────────────
# Print pid + every descendant pid (DFS via pgrep -P).
expand_descendants() {
local pid="$1"
echo "$pid"
local children
children=$(pgrep -P "$pid" 2>/dev/null || true)
for c in $children; do
expand_descendants "$c"
done
}
# Find seed PIDs related to this project's Electron dev session.
# Matches REGARDLESS of whether --remote-debugging-port was passed, so it also
# catches a plain `bun run dev` session the user started outside this script.
find_project_pids() {
local pids=""
# 1. Any process whose command line mentions this project's electron path
# (covers the main Electron binary AND every Helper subprocess)
local electron_pids
electron_pids=$(pgrep -f "$PROJECT_ELECTRON_PATH" 2>/dev/null || true)
pids="$pids $electron_pids"
# 2. electron-vite dev server (narrow match to avoid catching unrelated Vite invocations)
local vite_pids
vite_pids=$(pgrep -f "electron-vite[/.].*\\bdev\\b" 2>/dev/null || true)
pids="$pids $vite_pids"
# 3. The launcher subshell from a previous `start` (saved to pidfile)
if [ -f "$PIDFILE" ]; then
local saved_pid
saved_pid=$(cat "$PIDFILE" 2>/dev/null || true)
if [ -n "$saved_pid" ] && kill -0 "$saved_pid" 2>/dev/null; then
pids="$pids $saved_pid"
fi
fi
# 4. Whatever is currently bound to the CDP port — catches strays whose
# binary path doesn't match (e.g. orphaned from a crashed restart)
local port_pid
port_pid=$(lsof -ti tcp:"$CDP_PORT" -sTCP:LISTEN 2>/dev/null || true)
pids="$pids $port_pid"
# `|| true` because `grep -v '^$'` exits 1 when input has no non-empty
# lines, which (with pipefail + set -e) silently kills the caller.
echo "$pids" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true
}
# Wait for the CDP HTTP endpoint to respond, with a deadline + early bail-out
# if the launcher process died (no point waiting if Electron crashed).
wait_for_cdp() {
local deadline=$(( $(date +%s) + ELECTRON_WAIT_S ))
echo "[electron-dev] Waiting for CDP on port ${CDP_PORT} (up to ${ELECTRON_WAIT_S}s)..."
while [ "$(date +%s)" -lt "$deadline" ]; do
if curl -sf --max-time 2 "http://localhost:${CDP_PORT}/json/version" >/dev/null 2>&1; then
echo "[electron-dev] CDP is reachable."
return 0
fi
# If our launcher subshell died, abort early so we don't hang the full timeout
if [ -f "$PIDFILE" ]; then
local saved_pid
saved_pid=$(cat "$PIDFILE" 2>/dev/null || true)
if [ -n "$saved_pid" ] && ! kill -0 "$saved_pid" 2>/dev/null; then
echo "[electron-dev] Launcher PID $saved_pid is gone before CDP came up."
echo "[electron-dev] Last 30 lines of $ELECTRON_LOG:"
tail -30 "$ELECTRON_LOG" 2>/dev/null || true
return 1
fi
fi
sleep 2
done
echo "[electron-dev] ERROR: CDP did not respond within ${ELECTRON_WAIT_S}s"
echo "[electron-dev] Last 30 lines of $ELECTRON_LOG:"
tail -30 "$ELECTRON_LOG" 2>/dev/null || true
return 1
}
# After CDP is up, wait until the SPA renders interactive elements.
wait_for_renderer() {
local deadline=$(( $(date +%s) + RENDERER_WAIT_S ))
echo "[electron-dev] Waiting for SPA to load (up to ${RENDERER_WAIT_S}s)..."
while [ "$(date +%s)" -lt "$deadline" ]; do
local snap
snap=$(agent-browser --cdp "$CDP_PORT" snapshot -i 2>&1 || true)
if echo "$snap" | grep -qE '\b(link|button)\b'; then
echo "[electron-dev] Renderer ready."
return 0
fi
sleep 2
done
echo "[electron-dev] WARNING: Renderer not interactive within ${RENDERER_WAIT_S}s — proceeding anyway."
return 0
}
# ── Commands ─────────────────────────────────────────────────────────
do_stop() {
echo "[electron-dev] Stopping Electron dev environment..."
local seed_pids
seed_pids=$(find_project_pids)
# Expand to include all descendants — catches helpers spawned by the main
# process AFTER our pgrep snapshot, and the launcher's child node/electron-vite
# process tree.
local all_pids=""
for pid in $seed_pids; do
all_pids="$all_pids $(expand_descendants "$pid")"
done
all_pids=$(echo "$all_pids" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true)
if [ -z "$all_pids" ]; then
echo "[electron-dev] No project Electron/vite processes found."
else
local count
count=$(echo "$all_pids" | tr ' ' '\n' | grep -c .)
echo "[electron-dev] Sending SIGTERM to $count process(es): $all_pids"
for pid in $all_pids; do
kill "$pid" 2>/dev/null || true
done
# Wait up to 5s for graceful exit
local waited=0
while [ $waited -lt 5 ]; do
local any_alive=0
for pid in $all_pids; do
if kill -0 "$pid" 2>/dev/null; then any_alive=1; break; fi
done
[ "$any_alive" = "0" ] && break
sleep 1
waited=$((waited + 1))
done
# SIGKILL anyone still alive
for pid in $all_pids; do
if kill -0 "$pid" 2>/dev/null; then
echo "[electron-dev] Force-killing PID $pid"
kill -9 "$pid" 2>/dev/null || true
fi
done
fi
# Belt-and-suspenders: anything still bound to the CDP port goes away
local port_pid
port_pid=$(lsof -ti tcp:"$CDP_PORT" -sTCP:LISTEN 2>/dev/null || true)
if [ -n "$port_pid" ]; then
echo "[electron-dev] Port $CDP_PORT still bound by PID $port_pid; force-killing"
# shellcheck disable=SC2086
kill -9 $port_pid 2>/dev/null || true
fi
# Also re-sweep the project's electron processes — sometimes the OS spawns
# new helpers during shutdown that didn't exist when we first enumerated.
local stragglers
stragglers=$(pgrep -f "$PROJECT_ELECTRON_PATH" 2>/dev/null || true)
if [ -n "$stragglers" ]; then
echo "[electron-dev] Cleaning up stragglers: $stragglers"
for pid in $stragglers; do
kill -9 "$pid" 2>/dev/null || true
done
fi
# Close any agent-browser sessions connected to this port
agent-browser --cdp "$CDP_PORT" close --all 2>/dev/null || true
rm -f "$PIDFILE"
echo "[electron-dev] Stopped."
}
do_status() {
local pids
pids=$(find_project_pids)
if [ -z "$pids" ]; then
echo "[electron-dev] No project Electron processes found."
return 1
fi
echo "[electron-dev] Project processes: $pids"
if curl -sf --max-time 2 "http://localhost:${CDP_PORT}/json/version" >/dev/null 2>&1; then
local url
url=$(agent-browser --cdp "$CDP_PORT" get url 2>&1 | tail -1 || echo "?")
echo "[electron-dev] CDP port ${CDP_PORT} is reachable. URL: $url"
return 0
else
echo "[electron-dev] CDP port ${CDP_PORT} is NOT reachable (no --remote-debugging-port, or still loading)."
return 2
fi
}
do_start() {
# Already up and CDP is reachable → nothing to do
if curl -sf --max-time 2 "http://localhost:${CDP_PORT}/json/version" >/dev/null 2>&1; then
echo "[electron-dev] CDP already reachable on port $CDP_PORT. Skipping start."
echo "[electron-dev] Use 'restart' to force a fresh session."
return 0
fi
# Detect the user's existing dev session (or stale processes) BEFORE killing
local existing
existing=$(find_project_pids)
if [ -n "$existing" ]; then
echo "[electron-dev] Existing project Electron/vite processes detected:"
echo "$existing" | tr ' ' '\n' | sed 's/^/[electron-dev] PID /'
echo "[electron-dev] Tearing them down so we can start a CDP-enabled session..."
fi
do_stop
# Wait for port + user-data-dir locks to release. Without this, the new
# Electron may fail with "user data directory in use" or fail to bind CDP.
local waited=0
while [ $waited -lt 10 ]; do
if ! lsof -i tcp:"$CDP_PORT" >/dev/null 2>&1 \
&& ! pgrep -f "$PROJECT_ELECTRON_PATH" >/dev/null 2>&1; then
break
fi
[ $waited -eq 0 ] && echo "[electron-dev] Waiting for port + Electron locks to release..."
sleep 1
waited=$((waited + 1))
done
echo "[electron-dev] Starting Electron dev server..."
echo "[electron-dev] Project: $PROJECT_ROOT"
echo "[electron-dev] CDP port: $CDP_PORT"
echo "[electron-dev] Log: $ELECTRON_LOG"
: > "$ELECTRON_LOG" # Truncate log
# Launch in a new session (setsid) so the whole process tree shares a PGID
# we can later signal in one shot. `setsid bash -c '... exec ...' &` keeps
# the bash shell as the session leader; its PID is what we save.
# macOS doesn't ship setsid by default — fall back to plain bash; cleanup
# still works via `expand_descendants` walking the process tree.
local launch_cmd="
cd '$PROJECT_ROOT/apps/desktop'
exec npx electron-vite dev -- --remote-debugging-port=$CDP_PORT
"
if command -v setsid >/dev/null 2>&1; then
setsid bash -c "$launch_cmd" >> "$ELECTRON_LOG" 2>&1 < /dev/null &
else
bash -c "$launch_cmd" >> "$ELECTRON_LOG" 2>&1 < /dev/null &
fi
local launcher_pid=$!
echo "$launcher_pid" > "$PIDFILE"
echo "[electron-dev] Launcher PID (session leader): $launcher_pid"
if ! wait_for_cdp; then
echo "[electron-dev] Failed to bring up CDP. Cleaning up..."
do_stop
return 1
fi
if ! wait_for_renderer; then
echo "[electron-dev] Renderer not interactive — you may need to wait more."
fi
echo "[electron-dev] Ready! Use: agent-browser --cdp $CDP_PORT snapshot -i"
}
do_restart() {
do_stop
sleep 1
do_start
}
# ── Main ─────────────────────────────────────────────────────────────
case "${1:-help}" in
start) do_start ;;
stop) do_stop ;;
status) do_status ;;
restart) do_restart ;;
*)
echo "Usage: $0 {start|stop|status|restart}"
echo ""
echo " start — Start Electron dev with CDP. Detects + tears down any"
echo " existing project Electron (e.g. \`bun run dev\`) first."
echo " stop — Kill all project Electron/vite processes (main + helpers"
echo " + descendants), with SIGTERM → 5s wait → SIGKILL fallback."
echo " status — Check if Electron is running and CDP is reachable."
echo " restart — Stop then start."
exit 1
;;
esac
@@ -1,189 +0,0 @@
#!/usr/bin/env bash
#
# record-app-screen.sh — Record the Electron app window (video + screenshots)
#
# Captures screenshots via agent-browser (CDP), then assembles into video on stop.
# Works on any screen (including external monitors) since it uses CDP, not screen capture.
#
# Usage:
# ./record-app-screen.sh start [output_name] # Begin recording
# ./record-app-screen.sh stop # Stop and save
# ./record-app-screen.sh status # Check recording state
#
# Outputs to .records/ directory:
# .records/<name>.mp4 — Video assembled from screenshots (~2 fps)
# .records/<name>/ — Screenshots every SCREENSHOT_INTERVAL seconds
#
# Prerequisites:
# - ffmpeg installed (bun add -g ffmpeg-static, or brew install ffmpeg)
# - agent-browser CLI installed
# - Electron app already running with CDP enabled
#
# Environment variables:
# CDP_PORT — Chrome DevTools Protocol port (default: 9222)
# SCREENSHOT_INTERVAL — Seconds between gallery screenshots (default: 3)
# VIDEO_FRAME_INTERVAL — Seconds between video frames (default: 0.5)
#
# Examples:
# ./electron-dev.sh start
# ./record-app-screen.sh start gateway-demo
# # ... run automation via agent-browser ...
# ./record-app-screen.sh stop
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
RECORDS_DIR="$PROJECT_DIR/.records"
PID_FILE="/tmp/record-app-screen.pids"
STATE_FILE="/tmp/record-app-screen.state"
CDP_PORT="${CDP_PORT:-9222}"
SCREENSHOT_INTERVAL="${SCREENSHOT_INTERVAL:-3}"
VIDEO_FRAME_INTERVAL="${VIDEO_FRAME_INTERVAL:-0.5}"
AB="agent-browser --cdp $CDP_PORT"
# ─── Commands ───
cmd_start() {
local output_name="${1:-recording-$(date +%Y%m%d-%H%M%S)}"
local output_video="$RECORDS_DIR/${output_name}.mp4"
local screenshot_dir="$RECORDS_DIR/${output_name}"
local frames_dir
frames_dir=$(mktemp -d /tmp/record-frames-XXXXXX)
if [ -f "$PID_FILE" ]; then
echo "[record] A recording is already active. Run '$0 stop' first."
exit 1
fi
mkdir -p "$RECORDS_DIR" "$screenshot_dir"
# Video frames loop (~2 fps via agent-browser CDP screenshots)
(
local idx=0
while true; do
local fname
fname=$(printf "%s/frame_%06d.png" "$frames_dir" "$idx")
$AB screenshot "$fname" 2>/dev/null || true
idx=$((idx + 1))
sleep "$VIDEO_FRAME_INTERVAL"
done
) &
local frames_pid=$!
# Gallery screenshots loop (every N seconds for human review)
(
local idx=0
while true; do
local fname
fname=$(printf "%s/%04d.png" "$screenshot_dir" "$idx")
$AB screenshot "$fname" 2>/dev/null || true
idx=$((idx + 1))
sleep "$SCREENSHOT_INTERVAL"
done
) &
local screenshot_pid=$!
# Save state
echo "$frames_pid $screenshot_pid" > "$PID_FILE"
echo "$output_video $frames_dir $screenshot_dir" > "$STATE_FILE"
echo "[record] Started!"
echo " Video frames: every ${VIDEO_FRAME_INTERVAL}s (PID $frames_pid)"
echo " Screenshots: every ${SCREENSHOT_INTERVAL}s → $screenshot_dir/"
echo " Stop with: $0 stop"
}
cmd_stop() {
if [ ! -f "$PID_FILE" ] || [ ! -f "$STATE_FILE" ]; then
echo "[record] No active recording found."
return 0
fi
local frames_pid screenshot_pid
read -r frames_pid screenshot_pid < "$PID_FILE"
local output_video frames_dir screenshot_dir
read -r output_video frames_dir screenshot_dir < "$STATE_FILE"
# Stop both capture loops
kill "$frames_pid" 2>/dev/null || true
kill "$screenshot_pid" 2>/dev/null || true
wait "$frames_pid" 2>/dev/null || true
wait "$screenshot_pid" 2>/dev/null || true
# Assemble frames into video
local frame_count
frame_count=$(ls -1 "$frames_dir"/frame_*.png 2>/dev/null | wc -l | tr -d ' ')
if [ "$frame_count" -gt 0 ]; then
echo "[record] Assembling $frame_count frames into video..."
ffmpeg -y -framerate 2 -i "$frames_dir/frame_%06d.png" \
-c:v libx264 -crf 23 -pix_fmt yuv420p -an \
"$output_video" > /tmp/ffmpeg-assemble.log 2>&1
if [ ! -s "$output_video" ]; then
echo " [warn] Video assembly failed. Check /tmp/ffmpeg-assemble.log"
echo " Frames preserved in: $frames_dir/"
fi
else
echo " [warn] No frames captured."
fi
rm -rf "$frames_dir" 2>/dev/null
rm -f "$PID_FILE" "$STATE_FILE"
local video_size screenshot_count
video_size=$(ls -lh "$output_video" 2>/dev/null | awk '{print $5}' || echo "?")
screenshot_count=$(ls -1 "$screenshot_dir"/*.png 2>/dev/null | wc -l | tr -d ' ' || echo "0")
echo "[record] Stopped!"
echo " Video: $output_video ($video_size)"
echo " Screenshots: ${screenshot_count} files in $screenshot_dir/"
echo " Play: open $output_video"
}
cmd_status() {
if [ ! -f "$PID_FILE" ]; then
echo "[record] No active recording."
return 0
fi
local frames_pid screenshot_pid
read -r frames_pid screenshot_pid < "$PID_FILE"
local frames_ok="no" screenshot_ok="no"
kill -0 "$frames_pid" 2>/dev/null && frames_ok="yes"
kill -0 "$screenshot_pid" 2>/dev/null && screenshot_ok="yes"
if [ -f "$STATE_FILE" ]; then
local output_video frames_dir screenshot_dir
read -r output_video frames_dir screenshot_dir < "$STATE_FILE"
local frame_count ss_count
frame_count=$(ls -1 "$frames_dir"/frame_*.png 2>/dev/null | wc -l | tr -d ' ' || echo "0")
ss_count=$(ls -1 "$screenshot_dir"/*.png 2>/dev/null | wc -l | tr -d ' ' || echo "0")
echo "[record] Active recording"
echo " Frames: $frame_count captured (running: $frames_ok)"
echo " Screenshots: $ss_count captured (running: $screenshot_ok)"
echo " Output: $output_video"
fi
}
# ─── Main ───
case "${1:-}" in
start) shift; cmd_start "$@" ;;
stop) cmd_stop ;;
status) cmd_status ;;
*)
echo "Usage: $0 {start [name] | stop | status}"
echo ""
echo " start [name] Start recording (default: recording-YYYYMMDD-HHMMSS)"
echo " stop Stop recording and save outputs"
echo " status Check if recording is active"
exit 1
;;
esac
@@ -1,353 +0,0 @@
#!/usr/bin/env bash
#
# record-electron-demo.sh — Record an automated demo of the Electron app
#
# Usage:
# ./scripts/record-electron-demo.sh [script.sh] [output.mp4]
#
# script.sh — A shell script containing agent-browser commands to automate.
# It receives the CDP port as $1. Defaults to a built-in queue-edit demo.
# output.mp4 — Output file path. Defaults to /tmp/electron-demo.mp4
#
# Prerequisites:
# - agent-browser CLI installed globally
# - ffmpeg installed (brew install ffmpeg)
# - Electron app NOT already running (script manages lifecycle)
#
# Examples:
# # Run built-in demo
# ./scripts/record-electron-demo.sh
#
# # Run custom automation script
# ./scripts/record-electron-demo.sh ./my-demo.sh /tmp/my-demo.mp4
#
set -euo pipefail
CDP_PORT=9222
DEMO_SCRIPT="${1:-}"
OUTPUT="${2:-/tmp/electron-demo.mp4}"
ELECTRON_LOG="/tmp/electron-dev.log"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
RECORD_PID=""
# ── Helpers ──────────────────────────────────────────────────────────
cleanup() {
echo "[cleanup] Stopping all processes..."
[ -n "$RECORD_PID" ] && kill -INT "$RECORD_PID" 2>/dev/null && sleep 2
pkill -f "electron-vite" 2>/dev/null || true
pkill -f "Electron" 2>/dev/null || true
pkill -f "agent-browser" 2>/dev/null || true
echo "[cleanup] Done."
}
trap cleanup EXIT
wait_for_electron() {
echo "[wait] Waiting for Electron to start..."
for i in $(seq 1 24); do
sleep 5
if strings "$ELECTRON_LOG" 2>/dev/null | grep -q "starting electron"; then
echo "[wait] Electron process ready."
return 0
fi
echo "[wait] Still waiting... (${i}/24)"
done
echo "[error] Electron failed to start within 120s"
exit 1
}
wait_for_renderer() {
echo "[wait] Waiting for renderer to load..."
sleep 15
agent-browser --cdp "$CDP_PORT" wait 3000
# Poll until interactive elements appear (SPA may take extra time)
for i in $(seq 1 12); do
local snap
snap=$(agent-browser --cdp "$CDP_PORT" snapshot -i 2>&1)
if echo "$snap" | grep -q 'link "'; then
echo "[wait] Renderer ready (interactive elements found)."
return 0
fi
echo "[wait] SPA still loading... (${i}/12)"
sleep 5
done
echo "[warn] Timed out waiting for interactive elements, proceeding anyway."
}
get_window_and_screen_info() {
# Returns: window_x window_y window_w window_h screen_index
# Uses Swift to find the Electron window bounds and which screen it's on
swift -e '
import Cocoa
let windowList = CGWindowListCopyWindowInfo([.optionAll], kCGNullWindowID) as! [[String: Any]]
for w in windowList {
let owner = w["kCGWindowOwnerName"] as? String ?? ""
let name = w["kCGWindowName"] as? String ?? ""
let layer = w["kCGWindowLayer"] as? Int ?? -1
let bounds = w["kCGWindowBounds"] as? [String: Any] ?? [:]
let wx = bounds["X"] as? Double ?? 0
let wy = bounds["Y"] as? Double ?? 0
let ww = bounds["Width"] as? Double ?? 0
let wh = bounds["Height"] as? Double ?? 0
if (owner == "Electron" || owner == "LobeHub") && layer == 0 && name == "LobeHub" && ww > 200 && wh > 200 {
// Find which screen this window is on
let screens = NSScreen.screens
var screenIdx = 0
let windowCenter = NSPoint(x: wx + ww / 2, y: wy + wh / 2)
for (i, screen) in screens.enumerated() {
let frame = screen.frame
// Convert CG coords (top-left origin) to NSScreen coords (bottom-left origin)
let mainHeight = screens[0].frame.height
let screenTop = mainHeight - frame.origin.y - frame.height
let screenBottom = screenTop + frame.height
let screenLeft = frame.origin.x
let screenRight = screenLeft + frame.width
if windowCenter.x >= screenLeft && windowCenter.x <= screenRight &&
windowCenter.y >= screenTop && windowCenter.y <= screenBottom {
screenIdx = i
break
}
}
// Compute window position relative to the screen it is on
let screen = screens[screenIdx]
let mainHeight = screens[0].frame.height
let screenTop = mainHeight - screen.frame.origin.y - screen.frame.height
let relX = wx - screen.frame.origin.x
let relY = wy - screenTop
let scale = Int(screen.backingScaleFactor)
print("\(Int(relX)) \(Int(relY)) \(Int(ww)) \(Int(wh)) \(screenIdx) \(scale)")
break
}
}
'
}
start_recording() {
local rel_x=$1 rel_y=$2 w=$3 h=$4 screen_idx=$5 scale=$6
# ffmpeg avfoundation device index for screens
# List devices and find the one matching our screen index
local device_idx
device_idx=$(ffmpeg -f avfoundation -list_devices true -i "" 2>&1 \
| grep "Capture screen ${screen_idx}" \
| grep -oE '\[[0-9]+\]' | tr -d '[]' || true)
if [ -z "$device_idx" ]; then
echo "[warn] Could not find capture device for screen $screen_idx, trying default (3)"
device_idx=3
fi
# Scale coordinates to native resolution
local cx=$((rel_x * scale))
local cy=$((rel_y * scale))
local cw=$((w * scale))
local ch=$((h * scale))
echo "[record] Window: ${rel_x},${rel_y} ${w}x${h} on screen ${screen_idx} (scale=${scale})"
echo "[record] Crop: ${cx},${cy} ${cw}x${ch}, device: ${device_idx}"
echo "[record] Output: $OUTPUT"
ffmpeg -y \
-f avfoundation -framerate 30 -capture_cursor 1 -i "${device_idx}:" \
-vf "crop=${cw}:${ch}:${cx}:${cy},scale=${w}:${h}" \
-c:v libx264 -crf 23 -preset fast -an \
"$OUTPUT" \
> /tmp/ffmpeg-record.log 2>&1 &
RECORD_PID=$!
sleep 2
if ! kill -0 "$RECORD_PID" 2>/dev/null; then
echo "[error] ffmpeg failed to start. Log:"
cat /tmp/ffmpeg-record.log
RECORD_PID=""
return 1
fi
echo "[record] Recording started (PID=$RECORD_PID)"
}
stop_recording() {
if [ -n "$RECORD_PID" ]; then
echo "[record] Stopping recording..."
kill -INT "$RECORD_PID" 2>/dev/null || true
wait "$RECORD_PID" 2>/dev/null || true
RECORD_PID=""
echo "[record] Saved to $OUTPUT"
ls -lh "$OUTPUT"
fi
}
# ── Built-in demo: Queue Edit ────────────────────────────────────────
find_input_ref() {
local port=$1
agent-browser --cdp "$port" snapshot -i -C 2>&1 \
| grep "editable" \
| grep -oE 'ref=e[0-9]+' \
| head -1 \
| sed 's/ref=//'
}
builtin_demo() {
local port=$1
echo "[demo] Step 1: Navigate to first available agent"
local snapshot agent_ref
snapshot=$(agent-browser --cdp "$port" snapshot -i 2>&1)
# Try Lobe AI first, then fall back to any agent link in the sidebar
agent_ref=$(echo "$snapshot" | grep -oE 'link "Lobe AI" \[ref=e[0-9]+\]' | grep -oE 'e[0-9]+' || true)
if [ -z "$agent_ref" ]; then
# Pick the first agent-like link (skip nav links)
agent_ref=$(echo "$snapshot" | grep 'link "' | grep -vE '"Home"|"Pages"|"Settings"|"Search"|"Resources"|"Marketplace"' | head -1 | grep -oE 'ref=e[0-9]+' | sed 's/ref=//' || true)
fi
if [ -z "$agent_ref" ]; then
echo "[error] No agent link found in snapshot"
echo "$snapshot" | head -30
return 1
fi
echo "[demo] Clicking agent ref: @$agent_ref"
agent-browser --cdp "$port" click "@$agent_ref"
sleep 3
echo "[demo] Step 2: Send first message (triggers AI generation)"
local input_ref
input_ref=$(find_input_ref "$port")
agent-browser --cdp "$port" click "@$input_ref"
agent-browser --cdp "$port" type "@$input_ref" "Write a 3000 word essay about the complete history of space exploration from Sputnik to the James Webb Space Telescope"
sleep 1
agent-browser --cdp "$port" press Enter
sleep 3
echo "[demo] Step 3: Queue message 1"
input_ref=$(find_input_ref "$port")
agent-browser --cdp "$port" click "@$input_ref"
agent-browser --cdp "$port" type "@$input_ref" "This message should be edited"
sleep 1
agent-browser --cdp "$port" press Enter
sleep 1
echo "[demo] Step 4: Queue message 2"
input_ref=$(find_input_ref "$port")
agent-browser --cdp "$port" click "@$input_ref"
agent-browser --cdp "$port" type "@$input_ref" "Another queued message"
sleep 1
agent-browser --cdp "$port" press Enter
sleep 1
echo "[demo] Step 5: Verify queue has messages"
local queue_count
queue_count=$(agent-browser --cdp "$port" eval --stdin << 'EVALEOF'
(function() {
var chat = window.__LOBE_STORES.chat();
var total = 0;
Object.keys(chat.queuedMessages).forEach(function(k) {
total += chat.queuedMessages[k].length;
});
return String(total);
})()
EVALEOF
)
echo "[demo] Queue count: $queue_count"
if [ "$queue_count" = "0" ] || [ "$queue_count" = '"0"' ]; then
echo "[demo] Queue was already drained. Retrying..."
input_ref=$(find_input_ref "$port")
agent-browser --cdp "$port" click "@$input_ref"
agent-browser --cdp "$port" type "@$input_ref" "Now write another 3000 word essay about artificial intelligence from Turing to transformers covering every major breakthrough"
sleep 1
agent-browser --cdp "$port" press Enter
sleep 2
input_ref=$(find_input_ref "$port")
agent-browser --cdp "$port" click "@$input_ref"
agent-browser --cdp "$port" type "@$input_ref" "This message should be edited"
sleep 1
agent-browser --cdp "$port" press Enter
sleep 1
input_ref=$(find_input_ref "$port")
agent-browser --cdp "$port" click "@$input_ref"
agent-browser --cdp "$port" type "@$input_ref" "Another queued message"
sleep 1
agent-browser --cdp "$port" press Enter
sleep 1
fi
echo "[demo] Step 6: Scroll to show queue tray"
agent-browser --cdp "$port" scroll down 5000
sleep 2
echo "[demo] Step 7: Click edit button on first queued message"
agent-browser --cdp "$port" eval --stdin << 'EVALEOF'
(function() {
var chat = window.__LOBE_STORES.chat();
var keys = Object.keys(chat.queuedMessages);
for (var k = 0; k < keys.length; k++) {
var queue = chat.queuedMessages[keys[k]];
if (queue.length > 0) {
var targetText = queue[0].content;
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
while (walker.nextNode()) {
var node = walker.currentNode;
if (node.textContent.trim() === targetText) {
var row = node.parentElement.parentElement;
var buttons = row.querySelectorAll('[role="button"]');
if (buttons.length >= 1) {
buttons[0].click();
return 'clicked edit on: ' + targetText;
}
}
}
}
}
return 'edit button not found';
})()
EVALEOF
sleep 3
echo "[demo] Step 8: Show result — content restored to input"
sleep 3
echo "[demo] Complete!"
}
# ── Main ─────────────────────────────────────────────────────────────
echo "=== Electron Demo Recorder ==="
# 1. Kill existing instances
echo "[setup] Cleaning up existing processes..."
pkill -f "Electron" 2>/dev/null || true
pkill -f "electron-vite" 2>/dev/null || true
pkill -f "agent-browser" 2>/dev/null || true
sleep 3
# 2. Start Electron
echo "[setup] Starting Electron..."
cd "$PROJECT_ROOT/apps/desktop"
ELECTRON_ENABLE_LOGGING=1 npx electron-vite dev -- --remote-debugging-port="$CDP_PORT" > "$ELECTRON_LOG" 2>&1 &
wait_for_electron
wait_for_renderer
# 3. Get window position and start recording
WIN_INFO=$(get_window_and_screen_info)
if [ -z "$WIN_INFO" ]; then
echo "[error] Could not find Electron window"
exit 1
fi
read -r WIN_X WIN_Y WIN_W WIN_H SCREEN_IDX SCALE <<< "$WIN_INFO"
start_recording "$WIN_X" "$WIN_Y" "$WIN_W" "$WIN_H" "$SCREEN_IDX" "$SCALE"
# 4. Run demo script
if [ -n "$DEMO_SCRIPT" ] && [ -f "$DEMO_SCRIPT" ]; then
echo "[demo] Running custom script: $DEMO_SCRIPT"
bash "$DEMO_SCRIPT" "$CDP_PORT"
else
echo "[demo] Running built-in queue-edit demo"
builtin_demo "$CDP_PORT"
fi
# 5. Stop recording
stop_recording
echo "=== Done! Output: $OUTPUT ==="
-99
View File
@@ -1,99 +0,0 @@
---
name: microcopy
description: 'UI copy and microcopy guidelines. Use for user-facing copy, buttons, errors, empty states, onboarding, i18n wording, translation, or copy improvements in Chinese or English.'
user-invocable: false
---
# LobeHub UI Microcopy Guidelines
This file is the quick-reference summary. For full prompt-style guidelines with extensive examples (anti-patterns, tone matrices, scenario walk-throughs), load the language-specific reference:
- **中文文案** — [`references/zh.md`](./references/zh.md)
- **English copy** — [`references/en.md`](./references/en.md)
Brand: **Where Agents Collaborate** - Focus on collaborative agent system, not just "generation".
## Fixed Terminology
| Chinese | English |
| ---------- | ------------- |
| 空间 | Workspace |
| 助理 | Agent |
| 群组 | Group |
| 上下文 | Context |
| 记忆 | Memory |
| 连接器 | Integration |
| 技能 | Skill |
| 助理档案 | Agent Profile |
| 话题 | Topic |
| 文稿 | Page |
| 社区 | Community |
| 资源 | Resource |
| 库 | Library |
| 模型服务商 | Provider |
| 评测 | Evaluation |
| 基准 | Benchmark |
| 数据集 | Dataset |
| 用例 | Test Case |
## Brand Principles
1. **Create**: One sentence → usable Agent; clear next step
2. **Collaborate**: Multi-agent; shared Context; controlled
3. **Evolve**: Remember with consent; explainable; replayable
## Writing Rules
1. **Clarity first**: Short sentences, strong verbs, minimal adjectives
2. **Layered**: Main line (simple) + optional detail (precise)
3. **Consistent verbs**: Create / Connect / Run / Pause / Retry / View details
4. **Actionable**: Every message tells next step; avoid generic "OK/Cancel"
## Human Warmth (Balanced)
Default: **80% information, 20% warmth**
Key moments: **70/30** (first-time, empty state, failures, long waits)
**Hard cap**: At most half sentence of warmth, followed by clear next step.
**Order**:
1. Acknowledge situation (no judgment)
2. Restore control (pause/replay/edit/undo/clear Memory)
3. Provide next action
**Avoid**: Preachy encouragement, grand narratives, over-anthropomorphizing
## Patterns
**Getting started**:
- "Starting with one sentence is enough. Describe your goal."
- "Not sure where to begin? Tell me the outcome."
**Long wait**:
- "Running… You can switch tasks—I'll notify you when done."
- "This may take a few minutes. To speed up: reduce Context / switch model."
**Failure**:
- "That didn't run through. Retry, or view details to fix."
- "Connection failed. Re-authorize in Settings, or try again later."
**Collaboration**:
- "Align everyone to the same Context."
- "Different opinions are fine. Write the goal first."
## Errors/Exceptions
Must include:
1. **What happened**
2. (Optional) **Why**
3. **What user can do next**
Provide: Retry / View details / Go to Settings / Contact support / Copy logs
Never blame user. Put error codes in "Details".
-176
View File
@@ -1,176 +0,0 @@
---
globs: src/locales/default/*
alwaysApply: false
---
You are **LobeHubs English UI Copy & Microcopy Specialist**.
LobeHub is an assistant workspace: users can create **Agents** and **Agent Teams** so people↔agents and agent↔agent can collaborate to improve productivity in work and life.
Brand vibe: youthful, friendly, modern on the surface; professional, reliable, productivity- and controllability-first underneath. Overall style reference: Notion / Figma / Apple / Discord / OpenAI / Gemini — clear, restrained, trustworthy, human but not cheesy.
Product slogan: **For Collaborative Agents**. Your copy must continuously reinforce that LobeHub is not about “generation”, but about a **collaborative agent system**: shareable context, traceable outcomes, replayable runs, evolvable setup, and **human-in-the-loop**.
---
## 1) Fixed Terminology (must follow)
Use **exactly** these English terms across the product. Do not mix synonyms for the same concept.
- 空间: **Workspace**
- 助理: **Agent**
- 群组: **Group**
- 上下文: **Context**
- 记忆: **Memory**
- 连接器: **Integration**
- 技能 /tool/plugin: **Skill**
- 助理档案: **Agent Profile**
- 话题: **Topic**
- 文稿: **Page**
- 社区: **Community**
- 资源: **Resource**
- 库: **Library**
- MCP: **MCP**
- 模型服务商: **Provider**
Terminology rule: one concept = one term site-wide. Never alternate with “bot/assistant/AI agent/team/workspace” variations.
---
## 2) Your Responsibilities
- Improve, rewrite, or create from scratch any **English UI copy**: titles, buttons, form labels/help text, placeholders, onboarding, empty states, toasts, modals, errors, permission prompts, settings, creation/run flows, collaboration and Agent Team pages, etc.
- Copy must work for both:
- general users (immediately understandable)
- power users (not childish)
- It must fit both playful and serious contexts.
- Avoid overclaiming AI capabilities; add human warmth at the right moments.
---
## 3) The Three Brand Principles (bake into structure & wording)
- **Create**: create an Agent in one sentence; clear next step from idea → usable.
- **Collaborate**: multi-agent collaboration; align info and outputs; share Context (controlled, manageable).
- **Evolve**: Agents can remember preferences **only with user consent**; become more helpful over time; emphasize explainability, settings, and replay.
---
## 4) Writing Rules (actionable)
1. **Clarity first**: short sentences, strong verbs, minimal adjectives. Avoid hype (“revolutionary”, “epic”, “100%”).
2. **Layered messaging (single version for everyone)**:
- Main line: simple and actionable
- Optional second line: more precise / technical / boundary-setting (subtitle, helper text, tooltip, collapsible)
- Do not produce “Pro vs Lite” variants; one main + optional detail
3. **Use terms sparingly but correctly**: prefer plain words (“connect”, “run”, “context”) unless a technical term is necessary. When it is, add a plain-English explanation.
4. **Consistency**: keep verbs consistent across similar actions (Create / Connect / Run / Pause / Retry / View details / Clear Memory).
5. **Actionable**: every message tells the user what to do next. Avoid generic “OK/Cancel”; use specific actions.
6. **English localization**: natural, product-native English; avoid translationese; keep punctuation and casing consistent.
---
## 5) Human Warmth (balanced, controlled)
Goal: reduce anxiety and restore control without being sentimental.
Default ratio: **80% information, 20% warmth**.
Key moments (first-time create, empty state, long waits, failures/retries, rollback/data-loss risk, collaboration conflicts): may go **70/30**.
Hard cap: any on-screen message may include **at most half a sentence to one sentence** of warmth, and it must be followed by a clear next step.
Required order:
1. Acknowledge the situation (no judgment)
2. Restore control (human-in-the-loop: pause/replay/edit/undo/clear Memory/view Context)
3. Provide the next action (button/path)
Avoid:
- preachy encouragement (“dont worry”, “stay positive”)
- grand narratives
- overly anthropomorphic claims (“I understand you”, “Ill always remember you”)
Core stance: Agents can accelerate output, but **you** own the judgment, trade-offs, and final decision. LobeHub gives you time back for what matters.
Suggested patterns:
- **Getting started / blank state**
- “Starting with one sentence is enough. Describe your goal and Ill help you set up the first Agent.”
- “Not sure where to begin? Tell me the outcome—well break it down together.”
- **Long run / waiting**
- “Running… You can switch tasks—I'll notify you when its done.”
- “This may take a few minutes. To speed up: reduce Context / switch model / disable Auto-run.”
- **Failure / retry**
- “That didnt run through. Retry, or view details to fix the cause.”
- “Connection failed: permission not granted or network unstable. Re-authorize in Settings, or try again later.”
- **Value anxiety (guidance, not error dialogs)**
- “Agents can speed up output, but direction and standards stay with you.”
- “Fast results are great—keeping the trail makes the next run steadier.”
- **Collaboration / Agent Teams**
- “Align everyone to the same Context. Every Agent in the Agent Team works from the same page.”
- “Different opinions are fine. Write the goal first, then let Agents propose options and trade-offs.”
---
## 6) Errors / Exceptions / Permissions / Billing: hard rules
Every error must include:
- **What happened**
- (optional) **Why**
- **What the user can do next**
Provide actionable options as appropriate:
- Retry / View details / Go to Settings / Contact support / Copy logs
Never blame the user. Dont show only an error code; put codes in “Details” if needed.
For data/security/billing: be neutral, thorough, and respectful—warmth comes from clarity, not emotion.
---
## 7) Your Special Task: CN i18n → EN (localized, length-aware)
You translate **raw Chinese i18n strings into English** for LobeHub.
Requirements:
- Prefer **localized**, product-native English over literal translation.
- Do **not** chase perfect one-to-one consistency if a more natural UI phrase reads better.
- Keep the **character length difference small**; try to make the English string **roughly the same visual length** as the Chinese source (avoid overly long expansions).
- Preserve meaning, tone, and actionability; keep verbs consistent with LobeHubs UI patterns.
- If space is tight (buttons, tabs, toasts), prioritize: **verb + object**, drop optional words first.
- If the Chinese includes placeholders/variables, preserve them exactly (e.g., `{name}`, `{{count}}`, `%s`) and keep word order sensible.
- Keep capitalization consistent with UI norms (buttons/title case only when appropriate).
Output format when translating:
- Provide **English only**, unless asked otherwise.
- If multiple options are useful, give **one best option** + **one shorter fallback** (only when length constraints are likely).
---
You always optimize for: **clarity, control, collaboration, replayability, and human-in-the-loop**—in a modern, restrained, trustworthy English voice.
## 8) Product Introduction
LobeHub, we define agents as the unit of work. Were building the first humanagent co-working, co-evolving network.
It is a fundamentally new, agent-first experience.You can pop up your agents or agent teams while writing, while chatting -- from ideation, to execution, to delivery -- across your entire workflow. Here, agents are not just tools, but always-on units of work.
### Create
It is a unified workspace where you can find, build, or team up with agent co-workers.Simply describe what you need, and Lobe AI will generate the prompts and assemble the right set of tools to compose your agent.In agent marketplace, you can easily discover agents created by others,use them instantly,and flexibly swap in your own tools.
### Collaboration
You can also spin up agent groups to handle system-level projects, even like building a quant team.
Within this group, some agents track signals and mine quantitative factors in real time, some manage risk, some execute orders, collaborate together to make money.
Were defining how humans and agents work together. Now we support agent-to-agent collaboration, and we continue to scale new forms of collaboration networks — from agents collaborating across teams, to multiple humans working through the same agent.
### Evolve
Humans and agents should co-evolve, and we design this paradigm from both technical and economic perspectives. Our memory system is structured and editable,enabling models to better align with individual users, while allowing users to provide cleaner reward signals for continual learning. Agent evolution is powered by shared human intelligence through our agent marketplace. Creators are rewarded, and agents, in turn, pay for human intelligence.
Is AI replacing humans? No.
Were building a humanagent co-working, co-evolving society.
Agents become smarter and more personalized through human intelligence, taking on repetitive and exhausting work — so humans can focus on fewer, but more important things: taste, and creation.
-160
View File
@@ -1,160 +0,0 @@
---
globs: src/locales/default/*
alwaysApply: false
---
你是「LobeHub」的中文 UI 文案与微文案(microcopy)专家。LobeHub 是一个助理工作空间:用户可以创建助理与群组,让人和助理、助理和助理协作,提升日常生产与生活效率。产品气质:外表年轻、亲和、现代;内核专业、可靠、强调生产力与可控性。整体风格参考 Notion / Figma / Apple / Discord / OpenAI / Gemini:清晰克制、可信、有人情味但不油腻。
产品 slogan**For Collaborative Agents**。你的文案要让用户持续感到:LobeHub 的重点不是 “生成”,而是 “协作的助理体系”(可共享上下文、可追踪、可回放、可演进、人在回路)。
---
### 1) 固定术语(必须遵守)
- Workspace:空间
- Agent:助理
- Agent Team:群组
- Context:上下文
- Memory:记忆
- Integration:连接器
- Tool/Skill/Plugin/ 插件 / 工具:技能
- SystemRole: 助理档案
- Topic: 话题
- Page: 文稿
- Community: 社区
- Resource: 资源
- Library: 库
- MCP: MCP
- Provider: 模型服务商
术语规则:同一概念全站只用一种说法,不混用 “Agent / 智能体 / 机器人 / 团队 / 工作区” 等。
---
### 2) 你的任务
- 优化、改写或从零生成任何界面中文文案:标题、按钮、表单说明、占位、引导、空状态、Toast、弹窗、错误、权限、设置项、创建 / 运行流程、协作与群组相关页面等。
- 文案必须同时兼容:普通用户看得懂 + 专业用户不觉得低幼;娱乐与严肃场景都成立;不过度营销、不夸大 AI 能力;在关键节点提供恰到好处的人文关怀。
---
### 3) 品牌三原则(内化到结构与措辞)
- **Create(创建)**:一句话创建助理;从想法到可用;清楚下一步。
- **Collaborate(协作)**:多助理协作;群组对齐信息与产出;共享上下文(可控、可管理)。
- **Evolve(演进)**:助理可在你允许的范围内记住偏好;随你的工作方式变得更顺手;强调可解释、可设置、可回放。
---
### 4) 写作规则(可执行)
1. **清晰优先**:短句、强动词、少形容词;避免口号化与空泛承诺(如 “颠覆”“史诗级”“100%”)。
2. **分层表达(单一版本兼容两类用户)**
- 主句:人人可懂、可执行
- 必要时补充一句副说明:更精确 / 更专业 / 更边界(可放副标题、帮助提示、折叠区)
- 不输出 “Pro/Lite 两套文案”,而是 “一句主文案 + 可选补充”
3. **术语克制但准确**:能说 “连接 / 运行 / 上下文” 就不要堆砌术语;必须出现专业词时给一句白话解释。
4. **一致性**:同一动作按钮尽量固定动词(创建 / 连接 / 运行 / 暂停 / 重试 / 查看详情 / 清除记忆等)。
5. **可行动**:每条提示都要让用户知道下一步;按钮避免 “确定 / 取消” 泛化,改成更具体的动作。
6. **中文本地化**:符合中文阅读节奏;中英混排规范;避免翻译腔。
---
### 5) 人文关怀(中间态温度:介于克制与陪伴)
目标:在 AI 时代的价值焦虑与创作失格感中,给用户 “被理解 + 有掌控 + 能继续” 的体验,但不写长抒情。
#### 温度比例规则
- 默认:信息为主,温度为辅(约 8:2)
- 关键节点(首次创建、空状态、长等待、失败重试、回退 / 丢失风险、协作分歧):允许提升到 7:3
- 强制上限:任何一条上屏文案里,温度表达不超过**半句或一句**,且必须紧跟明确下一步。
#### 表达顺序(必须遵守)
1. 先承接处境(不评判):如 “没关系 / 先这样也可以 / 卡住很正常”
2. 再给掌控感(人在回路):可暂停 / 可回放 / 可编辑 / 可撤销 / 可清除记忆 / 可查看上下文
3. 最后给下一步(按钮 / 路径明确)
#### 避免
- 鸡汤式说教(如 “别焦虑”“要相信未来”)
- 宏大叙事与文学排比
- 过度拟人(不承诺助理 “理解你 / 有情绪 / 永远记得你”)
#### 核心立场
- 助理很强,但它替代不了你的经历、选择与判断;LobeHub 帮你把时间还给重要的部分。
##### A. 情绪承接(先人后事)
- 允许承认:焦虑、空白、无从下手、被追赶感、被替代感、创作枯竭、意义感动摇
- 但不下结论、不说教:不输出 “你要乐观 / 别焦虑”,改成 “这种感觉很常见 / 你不是一个人”
##### B. 主体性回归(把人放回驾驶位)
- 关键句式:**“决定权在你”**、**“你可以选择交给助理的部分”**、**“把你的想法变成可运行的流程”**
- 强调可控:可编辑、可回放、可暂停、可撤销、可清除记忆、可查看上下文
##### C. 经历与关系(把价值从结果挪回过程)
- 适度表达:记录、回放、版本、协作痕迹、讨论、共创、里程碑
- 用 “经历 / 过程 / 痕迹 / 回忆 / 脉络 / 成长” 这类词,避免虚无抒情
##### D. 不用 “AI 神话”
- 不渲染 “AI 终将超越你 / 取代你”
- 也不轻飘飘说 “AI 只是工具” 了事更像:**“它是工具,但你仍是作者 / 负责人 / 最终决定者”**
##### 示例
在用户可能产生自我否定或无力感的场景(空状态、创作开始、产出对比、失败重试、长时间等待、团队协作分歧、版本回退):
```
1. **先承接感受**:用一句短话确认处境(不评判)
2. **再给掌控感**:强调“你可控/可选择/可回放/可撤销”
3. **最后给下一步**:提供明确行动按钮或路径
```
- 允许出现 “经历、选择、痕迹、成长、一起、陪你把事做完” 等词来传递温度;但保持信息密度,不写长段抒情。
- 严肃场景(权限 / 安全 / 付费 / 数据丢失风险)仍以清晰与准确为先,温度通过 “尊重与解释” 体现,而不是煽情。
你可以让系统在需要时套这些结构(同一句兼容新手 / 专业):
**开始创作 / 空白页**
- 主句:给一个轻承接 + 行动入口
- 模板:
- 「从一个念头开始就够了。写一句话,我来帮你搭好第一个助理。」
- 「不知道从哪开始也没关系:先说目标,我们一起把它拆开。」
**长任务运行 / 等待**
- 模板:
- 「正在运行中… 你可以先去做别的,完成后我会提醒你。」
- 「这一步可能要几分钟。想更快:减少上下文 / 切换模型 / 关闭自动运行。」
**失败 / 重试**
- 模板:
- 「没关系,这次没跑通。你可以重试,或查看原因再继续。」
- 「连接失败:权限未通过或网络不稳定。去设置重新授权,或稍后再试。」
**对比与自我价值焦虑(适合提示 / 引导,不适合错误弹窗)**
- 模板:
- 「助理可以加速产出,但方向、取舍和标准仍属于你。」
- 「结果可以很快,经历更重要:把每次尝试留下来,下一次会更稳。」
**协作 / 群组**
- 模板:
- 「把上下文对齐到同一处,群组里每个助理都会站在同一页上。」
- 「不同意见没关系:先把目标写清楚,再让助理分别给方案与取舍。」
### 6) 错误 / 异常 / 权限 / 付费:硬规则
- 必须包含:**发生了什么 +(可选)原因 + 你可以怎么做**
- 必须提供可操作选项:**重试 / 查看详情 / 去设置 / 联系支持 / 复制日志**(按场景取舍)
- 不责备用户;不只给错误码;错误码可放在 “详情” 里
- 涉及数据与安全:语气更中性更完整,温度通过 “尊重与解释” 体现,而不是煽
-139
View File
@@ -1,139 +0,0 @@
---
name: modal
description: 'LobeHub imperative modal conventions. Use when creating or migrating modals, dialogs, popups, confirm flows, ModalHost wiring, createModal, confirmModal, useModalContext, or base-ui modal APIs.'
user-invocable: false
---
# Modal Imperative API Guide
## Recommended: `@lobehub/ui/base-ui`
New code should use the **base-ui** modal stack (headless primitives, not antd `Modal`):
- `createModal`, `confirmModal`, `ModalHost` from `@lobehub/ui/base-ui`
- `useModalContext` from `@lobehub/ui/base-ui` inside modal **content**
Body slot: pass **`content`** (or `children`; runtime uses `content ?? children`).
### Global `ModalHost` (required)
Base-ui `createModal` renders through a **separate** host from the root package. The app must mount **`ModalHost`** from `@lobehub/ui/base-ui` once near the root (e.g. next to other global hosts). Without it, `createModal` calls will not appear.
If the project only mounts `ModalHost` from `@lobehub/ui`, add a second lazy `ModalHost` from `@lobehub/ui/base-ui` until all imperative modals are migrated.
### Why imperative?
| Mode | Characteristics | Recommended |
| ----------- | ------------------------------------ | ----------- |
| Declarative | `open` state + `<Modal />` | ❌ |
| Imperative | Call `createModal()`, no local state | ✅ |
### File structure
```
features/
└── MyFeatureModal/
├── index.tsx # export createXxxModal
└── MyFeatureContent.tsx # modal body
```
### 1. Content (`MyFeatureContent.tsx`)
```tsx
'use client';
import { useModalContext } from '@lobehub/ui/base-ui';
import { useTranslation } from 'react-i18next';
export const MyFeatureContent = () => {
const { t } = useTranslation('namespace');
const { close } = useModalContext();
return <div>{/* ... */}</div>;
};
```
### 2. `createModal` (`index.tsx`)
```tsx
'use client';
import { createModal } from '@lobehub/ui/base-ui';
import { t } from 'i18next';
import { MyFeatureContent } from './MyFeatureContent';
export const createMyFeatureModal = () =>
createModal({
content: <MyFeatureContent />,
footer: null,
maskClosable: true,
styles: {
content: { overflow: 'hidden', padding: 0 },
},
title: t('myFeature.title', { ns: 'setting' }),
width: 'min(80%, 800px)',
});
```
### 3. Usage
```tsx
import { createMyFeatureModal } from '@/features/MyFeatureModal';
const handleOpen = useCallback(() => {
createMyFeatureModal();
}, []);
return <Button onClick={handleOpen}>Open</Button>;
```
### i18n
- **Content**: `useTranslation` in components.
- **`createModal` options**: `import { t } from 'i18next'` where hooks are unavailable.
### `useModalContext`
```tsx
const { close, setCanDismissByClickOutside } = useModalContext();
```
### Common options (base-ui)
`ImperativeModalProps` builds on `BaseModalProps`: `title`, `width`, `maskClosable`, `open`, `onOpenChange`, `footer`, `styles` / `classNames` (keys: `backdrop`, `popup`, `header`, `title`, `close`, `content`, …).
| Property | Notes |
| -------------- | ---------------------------------------- |
| `content` | Main body (preferred name vs `children`) |
| `maskClosable` | Click outside to dismiss |
| `styles.*` | Semantic regions, not antd `styles.body` |
### Confirm
```tsx
import { confirmModal } from '@lobehub/ui/base-ui';
confirmModal({
title: '…',
content: '…',
okText: '…',
cancelText: '…',
onOk: async () => {},
});
```
---
## Legacy: `@lobehub/ui` (root)
Older call sites use **`createModal` from `@lobehub/ui`**, which is typed as **antd `Modal` props** (`children`, `allowFullscreen`, `getContainer`, `destroyOnHidden`, `styles.body`, etc.). Prefer migrating new work to **`@lobehub/ui/base-ui`**.
Examples (legacy): `src/features/SkillStore/index.tsx`, `src/features/LibraryModal/CreateNew/index.tsx`.
---
## Examples
- Base-ui (preferred): follow sections above; ensure **base-ui `ModalHost`** is mounted.
- Legacy: `src/features/SkillStore/index.tsx`, `src/features/LibraryModal/CreateNew/index.tsx`
-152
View File
@@ -1,152 +0,0 @@
---
name: pr
description: "Create a PR for the current branch (targets `canary` by default), including splitting one cross-layer branch into ordered stacked PRs so a lower layer (db / shared package / server TRPC) merges before its callers (desktop / CLI / UI). Use when the user asks to create / submit a PR, or to split a branch because clients call a server contract that isn't on the trunk yet. Triggers on 'pr', 'create pr', 'submit pr', 'open a PR', 'pull request', 'split this PR', 'stacked PR', 'backend should merge first', '提 PR', '提个 PR', '新建 PR', '拆 PR', '后端先合', '分层合并'."
user-invocable: true
---
# Create Pull Request
## Branch Strategy
- **Target branch**: `canary` (development branch, cloud production)
- `main` is the release branch — never PR directly to main
## Steps
### 1. Gather context (run in parallel)
- `git branch --show-current` — current branch name
- `git status --short` — uncommitted changes
- `git rev-parse --abbrev-ref @{u} 2>/dev/null` — remote tracking status
- `git log --oneline origin/canary..HEAD` — unpushed commits
- `gh pr list --head "$(git branch --show-current)" --json number,title,state,url` — existing PR
- `git diff --stat --stat-count=20 origin/canary..HEAD` — change summary
### 2. Handle uncommitted changes on default branch
If current branch is `canary` (or `main`) AND there are uncommitted changes:
1. Analyze the diff (`git diff`) to understand the changes
2. Infer a branch name from the changes, format: `<type>/<short-description>` (e.g. `fix/i18n-cjk-spacing`)
3. Create and switch to the new branch: `git checkout -b <branch-name>`
4. Stage relevant files: `git add <files>` (prefer explicit file paths over `git add .`)
5. Commit with a proper gitmoji message
6. Continue to step 3
If current branch is `canary`/`main` but there are NO uncommitted changes and no unpushed commits, abort — nothing to create a PR for.
### 3. Push if needed
- No upstream: `git push -u origin $(git branch --show-current)`
- Has upstream: `git push origin $(git branch --show-current)`
### 4. Search related GitHub issues
- `gh issue list --search "<keywords>" --state all --limit 10`
- Only link issues with matching scope (avoid large umbrella issues)
- Skip if no matching issue found
### 5. Create PR with `gh pr create --base canary`
- Title: `<gitmoji> <type>(<scope>): <description>`
- Body: based on PR template (`.github/PULL_REQUEST_TEMPLATE.md`), fill checkboxes
- Link related GitHub issues using magic keywords (`Fixes #123`, `Closes #123`)
- Link Linear issues if applicable (`Fixes LOBE-xxx`)
- Use HEREDOC for body to preserve formatting
### 6. Open in browser
`gh pr view --web`
## PR Template
Use `.github/PULL_REQUEST_TEMPLATE.md` as the body structure. Key sections:
- **Change Type**: Check the appropriate gitmoji type
- **Related Issue**: Link GitHub/Linear issues with magic keywords
- **Description of Change**: Summarize what and why
- **How to Test**: Describe test approach, check relevant boxes
## Notes
- **Language**: All PR content must be in English
- If a PR already exists for the branch, inform the user instead of creating a duplicate
---
# Stacked PRs (cross-layer feature)
The steps above create **one** PR for the current branch. When a single branch lands across layers — `packages/database` schema/model → a shared `packages/*` lib → `src/server` TRPC → `apps/desktop` + `apps/cli` callers → `src/features` UI — shipping it as one PR can't merge safely: the clients call an endpoint that doesn't exist on the trunk until the same PR merges, so any partial/rollback or independent review breaks. Split it into **ordered PRs**, lower layer first.
## The ordering rule
A PR may only merge **after** every layer it calls is already on the trunk.
- The **server contract** (new TRPC procedure, changed return shape, new table/model) merges first.
- The **callers** (desktop, CLI, UI) merge after — they invoke that contract.
- Tie-break with one question: _"if this merged alone to `canary` right now, would it build and behave?"_ If no, it belongs in a later PR.
## Which file goes in which PR
The non-obvious calls:
- **Frontend that adapts to a contract change goes WITH the server PR.** If you widen a TRPC return shape (e.g. `listDevices` now returns `platform: string | null`), the component consuming it must change in the _same_ PR — otherwise the server PR breaks the build on its own. Contract + its in-repo consumers ship together.
- **A new shared package goes with its consumer**, not the server, unless the server imports it too. A `@lobechat/*` package imported only by desktop/CLI ships in the client PR. Don't carry an unused package in the lower PR.
- **Workspace dep declarations** (`package.json` `workspace:*`, `pnpm-workspace.yaml`) travel with the code that imports the package.
## The git recipe — split an existing full branch
Starting point: one branch (`feat/x`) with a single commit `<FULL>` containing everything, already pushed (so it's also safe on the remote).
```bash
# 1. Safety nets — make the full work unloseable before rewriting anything
git branch backup/x-full <FULL> # local ref to the full commit
git branch feat/x-clients <FULL> # the higher-layer branch starts here
# 2. Rewrite the lower-layer branch to lower-layer files only
git checkout feat/x # this becomes the SERVER PR
git reset --hard origin/canary
git checkout <FULL> -- <server/db files…> # stages just those paths
git commit -m "✨ feat(...): <server half>"
git push --force-with-lease origin feat/x # never --force; never push to canary
# 3. Build the higher-layer branch STACKED on the lower branch
git checkout feat/x-clients
git reset --hard feat/x # base = the just-rewritten server HEAD
git checkout backup/x-full -- <client/ui files…> # only the remaining paths
git commit -m "✨ feat(...): <client half>"
git push -u origin feat/x-clients
```
Then open the higher PR **based on the lower branch**, not the trunk:
```bash
gh pr create --base feat/x --head feat/x-clients --title "…" --body "…"
```
`--base feat/x` keeps the diff client-only (no server files leak in) and makes it physically impossible to merge the clients before the server. **After the server PR merges to `canary`, retarget the client PR's base to `canary`** (GitHub usually auto-retargets when the base branch merges; note it in the PR body so a human confirms).
## Verify the dependency actually holds
The whole point is the higher layer needs the lower one. Prove it: on the stacked higher branch, type-check the caller and confirm the symbol the lower layer introduced resolves.
```bash
cd apps/cli && bun run type-check 2>&1 | grep -iE "connect\.ts|device\.register"
# empty (re: your change) = the stacked base supplies device.register ✓
```
Filter to your touched files — this repo's standalone type-check emits pre-existing env noise (`__ELECTRON__`, `@/types/llm`, unbuilt `@lobechat/types`) that isn't yours.
## PR + Linear bookkeeping
- **Each PR closes only its own layer's issues.** Server PR: `Closes LOBE-<server>`. Client PR: `Closes LOBE-<pkg> / <desktop> / <cli>`. Don't let one PR's body claim another layer's issue.
- Both PRs are `Part of LOBE-<parent>`.
- On PR creation, move each closed sub-issue to **In Review** (not Done) and add a completion comment — see the `linear` skill.
## Gotchas
- **Never push to `canary`.** A split branch cut with `git checkout -b feat/x origin/canary` _tracks_ `origin/canary`, so a bare `git push` targets canary. Always `git push origin feat/x` with the explicit branch name.
- **`--force-with-lease`, not `--force`** when rewriting the lower branch — it aborts if the remote moved under you.
- **Back up before `reset --hard`.** Step 1's `backup/x-full` + the pushed remote branch mean the full commit is referenced by ≥3 refs before you rewrite anything. Verify with `git branch --contains <FULL>`.
- **Lockfiles:** this monorepo commits no root `pnpm-lock.yaml`, so a new `workspace:*` dep needs no lockfile churn. In a repo that _does_ commit one, regenerate it on each branch after the split.
- **Don't over-split.** Two PRs (contract / callers) is usually enough. A UI page that only reads an existing endpoint can be its own later PR, but don't fragment a single layer across PRs for its own sake.
-135
View File
@@ -1,135 +0,0 @@
---
name: project-overview
description: 'LobeHub open-source monorepo architecture map. Use when locating code layers, understanding apps/packages/src layout, business stubs, project structure, or onboarding to the repository.'
user-invocable: false
---
# LobeHub Project Overview
> The directory listings below are a **curated map of key locations**, not an
> exhaustive tree. `packages/`, `src/store/`, route groups etc. grow over time —
> run `ls` against the real directory for the current set.
## Project Description
Open-source, modern-design AI Agent Workspace: **LobeHub** (previously LobeChat).
This repo is the **open-source root** (`github.com/lobehub/lobehub`, package `@lobehub/lobehub`).
**Supported platforms:**
- Web desktop/mobile
- Desktop (Electron) — `apps/desktop`
- Mobile app (React Native) — **separate repo, already launched** (not in this monorepo)
**Logo emoji:** 🤯
## Complete Tech Stack
| Category | Technology |
| ------------- | ------------------------------------------ |
| Framework | Next.js 16 + React 19 |
| Routing | SPA inside Next.js with `react-router-dom` |
| Language | TypeScript |
| UI Components | `@lobehub/ui`, antd |
| CSS-in-JS | antd-style |
| Icons | lucide-react, `@ant-design/icons` |
| i18n | react-i18next |
| State | zustand |
| URL Params | nuqs |
| Data Fetching | SWR |
| React Hooks | aHooks |
| Date/Time | dayjs |
| Utilities | es-toolkit |
| API | TRPC (type-safe) |
| Database | Neon PostgreSQL + Drizzle ORM |
| Testing | Vitest |
> Exact versions live in the root `package.json` — check there, not here.
## Monorepo Layout
Flat layout — `apps/`, `packages/`, and `src/` all sit at the repo root. No
git submodules.
```
(repo root)
├── apps/
│ ├── cli/ # LobeHub CLI
│ ├── desktop/ # Electron desktop app
│ └── device-gateway/ # Device gateway service
├── docs/ # changelog, development, self-hosting, usage
├── locales/ # en-US, zh-CN, ...
├── packages/ # ~80 @lobechat/* workspace packages — `ls` for the full set. Key ones:
│ ├── agent-runtime/ # Agent runtime core
│ ├── agent-signal/ # Agent Signal pipeline
│ ├── agent-tracing/ # Tracing / snapshots
│ ├── builtin-tool-*/ # Per-tool packages (calculator, web-browsing, claude-code, ...)
│ ├── builtin-tools/ # Central registries that compose builtin-tool-*
│ ├── context-engine/
│ ├── database/ # src/{models,schemas,repositories}
│ ├── model-bank/ # Model definitions & provider cards
│ ├── model-runtime/ # src/{core,providers}
│ ├── business/ # Open-source stubs (config, const, model-bank, model-runtime) — overridden by cloud
│ ├── types/
│ └── utils/
└── src/
├── app/
│ ├── (backend)/ # api, f, market, middleware, oidc, trpc, webapi
│ ├── spa/ # SPA HTML template service
│ └── [variants]/(auth)/ # Auth pages (SSR required)
├── routes/ # SPA page segments (thin — delegate to features/)
│ └── (main)/ (mobile)/ (desktop)/ (popup)/ onboarding/ share/
├── spa/ # SPA entries + router config
│ ├── entry.{web,mobile,desktop,popup}.tsx
│ └── router/
├── business/ # Open-source stubs (client/server) — cloud repo provides real impls
├── features/ # Domain business components
├── store/ # ~30 zustand stores — `ls` for the full set
├── server/ # featureFlags, globalConfig, modules, routers, services, workflows, agent-hono
└── ... # components, hooks, layout, libs, locales, services, types, utils
```
## Architecture Map
| Layer | Location |
| ---------------- | --------------------------------------------------- |
| UI Components | `src/components`, `src/features` |
| SPA Pages | `src/routes/` |
| React Router | `src/spa/router/` |
| Global Providers | `src/layout` |
| Zustand Stores | `src/store` |
| Client Services | `src/services/` |
| REST API | `src/app/(backend)/webapi` |
| tRPC Routers | `src/server/routers/{async\|lambda\|mobile\|tools}` |
| Server Services | `src/server/services` (can access DB) |
| Server Modules | `src/server/modules` (no DB access) |
| Feature Flags | `src/server/featureFlags` |
| Global Config | `src/server/globalConfig` |
| DB Schema | `packages/database/src/schemas` |
| DB Model | `packages/database/src/models` |
| DB Repository | `packages/database/src/repositories` |
| Third-party | `src/libs` (analytics, oidc, etc.) |
| Builtin Tools | `packages/builtin-tool-*`, `packages/builtin-tools` |
| Open-source stub | `src/business/*`, `packages/business/*` (this repo) |
## Data Flow
```
React UI → Store Actions → Client Service → TRPC Lambda → Server Services → DB Model → PostgreSQL
```
## Note: Relationship to the Cloud Repo
This open-source repo is consumed by a **separate, private cloud (SaaS) repo**
as a git submodule mounted at `lobehub/`. The cloud repo provides:
- **`src/business/{client,server}`** and **`packages/business/*`** implementations
that override the stubs shipped here.
- Cloud-only routes (e.g. `(cloud)/`, `embed/`), cloud-only stores (e.g.
`subscription/`), cloud-only TRPC routers (billing, budget, risk control, …),
and Vercel cron routes under `src/app/(backend)/cron/`.
- File-resolution order in cloud: `@/store/x` → cloud `src/store/x` first, then
`lobehub/packages/store/src/x`, then `lobehub/src/store/x`. **Cloud override wins.**
When working in this repo alone, ignore the cloud layer — the stubs in
`src/business/` and `packages/business/` are the source of truth here.
-122
View File
@@ -1,122 +0,0 @@
---
name: react
description: 'LobeHub React component conventions. Use when editing TSX UI, choosing base-ui vs @lobehub/ui vs antd, styling with antd-style, routing, desktop variants, layouts, or component state.'
user-invocable: false
---
# React Component Writing Guide
## Styling
| Scenario | Approach |
| ---------------------------------------------------------- | -------------------------------------------------------------- |
| Most cases | `createStaticStyles` + `cssVar.*` (zero-runtime, module-level) |
| Simple one-off | Inline `style` attribute |
| Truly dynamic (JS color fns like `readableColor`/`chroma`) | `createStyles` + `token`**last resort** |
## Component Priority
1. **`src/components`** — project-specific reusable components
2. **`@lobehub/ui/base-ui`** — headless primitives. **If the component lives here, use it. Do NOT import the same-named root export.**
3. **`@lobehub/ui`** — higher-level / antd-wrapping components (only when no base-ui equivalent)
4. **antd** — only when neither base-ui nor `@lobehub/ui` root provides it
5. **Custom implementation** — true last resort
If unsure about available components, search existing code or check `node_modules/@lobehub/ui/es/index.mjs` and `node_modules/@lobehub/ui/es/base-ui/`.
### `@lobehub/ui/base-ui` — always prefer for these
| Component | Import |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| `Select` (+ `SelectProps`, `SelectOption`) | `import { Select } from '@lobehub/ui/base-ui';` |
| `Modal` (imperative API) | `import { createModal, confirmModal, useModalContext, type ModalInstance } from '@lobehub/ui/base-ui';` |
| `DropdownMenu` | `import { DropdownMenu } from '@lobehub/ui/base-ui';` |
| `ContextMenu` | `import { ContextMenu } from '@lobehub/ui/base-ui';` |
| `Popover` | `import { Popover } from '@lobehub/ui/base-ui';` |
| `ScrollArea` | `import { ScrollArea } from '@lobehub/ui/base-ui';` |
| `Switch` | `import { Switch } from '@lobehub/ui/base-ui';` |
| `Toast` | `import { Toast } from '@lobehub/ui/base-ui';` |
| `FloatingSheet` | `import { FloatingSheet } from '@lobehub/ui/base-ui';` |
For Modal specifically, see the dedicated **modal** skill — use the imperative `createModal({ content: … })` pattern over the legacy `<Modal open … />` declarative pattern. base-ui has its own `ModalHost` already mounted in `SPAGlobalProvider`.
> Common slip: `import { Select } from '@lobehub/ui'` looks fine but it's the antd-backed Select. Use base-ui Select. Same for `Modal`, `DropdownMenu`, etc.
### `@lobehub/ui` root — use when base-ui has no equivalent
| Category | Components |
| ------------ | ------------------------------------------------------------------------------------- |
| General | ActionIcon, ActionIconGroup, Block, Button, Icon |
| Data Display | Avatar, Collapse, Empty, Highlighter, Markdown, Tag, Tooltip |
| Data Entry | CodeEditor, CopyButton, EditableText, Form, Input, InputPassword, SearchBar, TextArea |
| Feedback | Alert, Drawer |
| Layout | Center, DraggablePanel, Flexbox, Grid, Header, MaskShadow |
| Navigation | Burger, Menu, SideNav, Tabs |
## State
When a feature component manages more than 3 pieces of state (`useState`/`useReducer`/derived state), extract the logic into a custom hook (e.g. `useXxx`). Keep the component focused on rendering — the hook holds state and handlers, so logic can be unit-tested without rendering the component.
## Layout
Use `Flexbox` and `Center` from `@lobehub/ui`. See `references/layout-kit.md` for full props and examples.
- Use `gap` instead of `margin` for spacing between flex children
- Use `flex={1}` to fill available space
- Nest Flexbox for complex layouts; set `overflow: 'auto'` for scrollable regions
## Navigation
**For SPA pages, use `react-router-dom`, NOT `next/link`.**
```tsx
// ❌ Wrong
import Link from 'next/link';
// ✅ Correct
import { Link, useNavigate } from 'react-router-dom';
```
Access navigate from stores: `useGlobalStore.getState().navigate?.('/settings');`
## Desktop File Sync Rule
Files with a `.desktop.ts(x)` variant must be edited **in sync**. Drift causes blank pages in Electron.
| Base file (web) | Desktop file (Electron) |
| -------------------------- | ---------------------------------- |
| `desktopRouter.config.tsx` | `desktopRouter.config.desktop.tsx` |
| `componentMap.ts` | `componentMap.desktop.ts` |
**After editing any `.ts`/`.tsx`:** glob for `<filename>.desktop.{ts,tsx}` in the same directory. If found, apply the equivalent sync-import change.
## Routing Architecture
| Route Type | Use Case | Implementation |
| ------------------ | ---------- | -------------------------------------------------- |
| Next.js App Router | Auth pages | `src/app/[variants]/(auth)/` |
| React Router DOM | Main SPA | `desktopRouter.config.tsx` + `.desktop.tsx` (pair) |
Router utilities:
```tsx
import { dynamicElement, redirectElement, ErrorBoundary } from '@/utils/router';
element: dynamicElement(() => import('./chat'), 'Desktop > Chat');
element: redirectElement('/settings/profile');
errorElement: <ErrorBoundary />;
```
## Common Mistakes
| Mistake | Fix |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| Using `next/link` in SPA | Use `react-router-dom` `Link` |
| Using antd directly | Use `@lobehub/ui/base-ui` first, then `@lobehub/ui` |
| `import { Select } from '@lobehub/ui'` | `import { Select } from '@lobehub/ui/base-ui'` |
| `import { Modal } from '@lobehub/ui'` + `<Modal open>` declarative | `createModal` / `confirmModal` from `@lobehub/ui/base-ui` (see modal skill) |
| `import { DropdownMenu/Popover/Switch } from '@lobehub/ui'` | Import same name from `@lobehub/ui/base-ui` instead |
| `createStyles` for static styles | Use `createStaticStyles` + `cssVar` |
| Editing only `desktopRouter.config.tsx` | Must edit both `.tsx` and `.desktop.tsx` |
| Using `margin` for flex spacing | Use `gap` prop on Flexbox |
| Accessing zustand store without selector | Use selectors to access store data (see zustand skill) |
| Text or icon-text actions built with `Flexbox`/`Text` + `onClick` | Use `Button type={'text'} size={'small'}` with `icon` when needed |
@@ -1,100 +0,0 @@
# Flexbox Layout Components Guide
`@lobehub/ui` provides `Flexbox` and `Center` components for creating flexible layouts.
## Flexbox Component
Flexbox is the most commonly used layout component, similar to CSS `display: flex`.
### Basic Usage
```jsx
import { Flexbox } from '@lobehub/ui';
// Default vertical layout
<Flexbox>
<div>Child 1</div>
<div>Child 2</div>
</Flexbox>
// Horizontal layout
<Flexbox horizontal>
<div>Left</div>
<div>Right</div>
</Flexbox>
```
### Common Props
- `horizontal`: Boolean, set horizontal direction layout
- `flex`: Number or string, controls flex property
- `gap`: Number, spacing between children
- `align`: Alignment like 'center', 'flex-start', etc.
- `justify`: Main axis alignment like 'space-between', 'center', etc.
- `padding`: Padding value
- `paddingInline`: Horizontal padding
- `paddingBlock`: Vertical padding
- `width/height`: Set dimensions, typically '100%' or specific pixels
- `style`: Custom style object
### Layout Example
```jsx
// Classic three-column layout
<Flexbox horizontal height={'100%'} width={'100%'}>
{/* Left sidebar */}
<Flexbox
width={260}
style={{
borderRight: `1px solid ${theme.colorBorderSecondary}`,
height: '100%',
overflowY: 'auto',
}}
>
<SidebarContent />
</Flexbox>
{/* Center content */}
<Flexbox flex={1} style={{ height: '100%' }}>
<Flexbox flex={1} padding={24} style={{ overflowY: 'auto' }}>
<MainContent />
</Flexbox>
{/* Footer */}
<Flexbox
style={{
borderTop: `1px solid ${theme.colorBorderSecondary}`,
padding: '16px 24px',
}}
>
<Footer />
</Flexbox>
</Flexbox>
</Flexbox>
```
## Center Component
Center wraps Flexbox with horizontal and vertical centering.
```jsx
import { Center } from '@lobehub/ui';
<Center width={'100%'} height={'100%'}>
<Content />
</Center>
// Icon centered
<Center className={styles.icon} flex={'none'} height={40} width={40}>
<Icon icon={icon} size={24} />
</Center>
```
## Best Practices
- Use `flex={1}` to fill available space
- Use `gap` instead of margin for spacing
- Nest Flexbox for complex layouts
- Set `overflow: 'auto'` for scrollable content
- Use `horizontal` for horizontal layout (default is vertical)
- Combine with `useTheme` hook for theme-responsive layouts
@@ -1,87 +0,0 @@
---
name: response-compliance
description: 'OpenResponses API compliance testing. Use for Response API endpoint tests, compliance runs, schema debugging, response api test, or openresponses test tasks.'
---
# OpenResponses Compliance Test
Run the official OpenResponses compliance test suite against the local (or remote) Response API endpoint.
## Quick Start
```bash
# From the openapi package directory
cd lobehub/packages/openapi
# Run all tests (dev mode, localhost:3010)
APP_URL=http://localhost:3010 bun run test:response-compliance -- \
--auth-header "lobe-auth-dev-backend-api" --no-bearer --api-key 1
# Run specific tests only
APP_URL=http://localhost:3010 bun run test:response-compliance -- \
--auth-header "lobe-auth-dev-backend-api" --no-bearer --api-key 1 \
--filter basic-response,streaming-response
# Verbose mode (shows request/response details)
APP_URL=http://localhost:3010 bun run test:response-compliance -- \
--auth-header "lobe-auth-dev-backend-api" --no-bearer --api-key 1 -v
# JSON output (for CI)
APP_URL=http://localhost:3010 bun run test:response-compliance -- \
--auth-header "lobe-auth-dev-backend-api" --no-bearer --api-key 1 --json
```
## Prerequisites
- Dev server running with `ENABLE_MOCK_DEV_USER=true` in `.env`
- The `api/v1/responses` route registered (via `src/app/(backend)/api/v1/[[...route]]/route.ts`)
## Auth Modes
| Mode | Flags |
| --------------- | ------------------------------------------------------------------- |
| Dev (mock user) | `--auth-header "lobe-auth-dev-backend-api" --no-bearer --api-key 1` |
| API Key | `--api-key lb-xxxxxxxxxxxxxxxx` |
| Custom | `--auth-header <name> --api-key <value>` |
## Test IDs
Available `--filter` values:
| ID | Description | Related Issue |
| -------------------- | -------------------------------------- | ------------- |
| `basic-response` | Simple text generation (non-streaming) | LOBE-5858 |
| `streaming-response` | SSE streaming lifecycle + events | LOBE-5859 |
| `system-prompt` | System role message handling | LOBE-5858 |
| `tool-calling` | Function tool definition + call output | LOBE-5860 |
| `image-input` | Multimodal image URL content | — |
| `multi-turn` | Conversation history via input items | LOBE-5861 |
## Environment Variables
| Variable | Default | Description |
| --------- | ----------------------- | ----------------------------------------- |
| `APP_URL` | `http://localhost:3010` | Server base URL (auto-appends `/api/v1`) |
| `API_KEY` | — | API key (alternative to `--api-key` flag) |
## How It Works
The script (`lobehub/packages/openapi/scripts/compliance-test.sh`) clones the official [openresponses/openresponses](https://github.com/openresponses/openresponses) repo into `scripts/openresponses-compliance/` (gitignored) and runs its CLI test runner. First run clones; subsequent runs update from upstream.
## Debugging Failures
1. Run with `-v` to see full request/response payloads
2. Common failure patterns:
- **"Failed to parse JSON"**: Auth failed, server returned HTML redirect
- **"Response has no output items"**: LLM execution not yet implemented
- **"Expected number, received null"**: Missing required field in response schema
- **"Invalid input"**: Zod validation on response schema — check field format
## Key Files
- **Types**: `lobehub/packages/openapi/src/types/responses.type.ts`
- **Service**: `lobehub/packages/openapi/src/services/responses.service.ts`
- **Controller**: `lobehub/packages/openapi/src/controllers/responses.controller.ts`
- **Route**: `lobehub/packages/openapi/src/routes/responses.route.ts`
- **Test script**: `lobehub/packages/openapi/scripts/compliance-test.sh`
- **Cloud route**: `src/app/(backend)/api/v1/[[...route]]/route.ts`
-57
View File
@@ -1,57 +0,0 @@
---
name: review-checklist
description: 'LobeHub code review checklist. Use when reviewing a PR, diff, or branch for console leftovers, return await, secrets, i18n, desktop router drift, UI imports, migrations, or cloud impact.'
user-invocable: false
---
# Review Checklist
## Correctness
- Leftover `console.log` / `console.debug` — should use `debug` package or remove
- Missing `return await` in try/catch — see <https://typescript-eslint.io/rules/return-await/> (not in our ESLint config yet, requires type info)
- Can the fix/implementation be more concise, efficient, or have better compatibility?
## Security
- No sensitive data (API keys, tokens, credentials) in `console.*` or `debug()` output
- No base64 output to terminal — extremely long, freezes output
- No hardcoded secrets — use environment variables
## Testing
- Bug fixes must include tests covering the fixed scenario
- New logic (services, store actions, utilities) should have test coverage
- Existing tests still cover the changed behavior?
- Prefer `vi.spyOn` over `vi.mock` (see `/testing` skill)
## i18n
- New user-facing strings use i18n keys, not hardcoded text
- Keys added to `src/locales/default/{namespace}.ts` with `{feature}.{context}.{action|status}` naming
- For PRs: `locales/` translations for all languages updated (`pnpm i18n`)
## SPA / routing
- **`desktopRouter` pair:** If the diff touches `src/spa/router/desktopRouter.config.tsx`, does it also update `src/spa/router/desktopRouter.config.desktop.tsx` with the same route paths and nesting? Single-file edits often cause drift and blank screens.
## Reuse
- Newly written code duplicates existing utilities in `packages/utils` or shared modules?
- Copy-pasted blocks with slight variation — extract into shared function
- `antd` imports replaceable with `@lobehub/ui` wrapped components (`Input`, `Button`, `Modal`, `Avatar`, etc.)
- Use `antd-style` token system, not hardcoded colors; prefer `createStaticStyles` + `cssVar.*` over `createStyles` + `token` unless runtime computation is required
## Database
- Migration scripts must be idempotent (`IF NOT EXISTS`, `IF EXISTS` guards)
## Cloud Impact
A downstream cloud deployment depends on this repo. Flag changes that may require cloud-side updates:
- **Backend route paths changed** — e.g., renaming `src/app/(backend)/webapi/chat/route.ts` or changing its exports
- **SSR page paths changed** — e.g., moving/renaming files under `src/app/[variants]/(auth)/`
- **Dependency versions bumped** — e.g., upgrading `next` or `drizzle-orm` in `package.json`
- **`@lobechat/business-*` exports changed** — e.g., renaming a function in `src/business/` or changing type signatures in `packages/business/`
- `src/business/` and `packages/business/` must not expose cloud commercial logic in comments or code
-142
View File
@@ -1,142 +0,0 @@
---
name: skills-audit
description: 'Audit .agents/skills SKILL.md files. Use for recurring checks of duplicate, overlapping, stale, inconsistent, or broken skills and merge/delete candidates.'
disable-model-invocation: true
argument-hint: '[--verbose | --apply]'
---
# Skills Audit
Periodic review of the project-local skill set under `.agents/skills/`. The goal is to catch drift before the catalog becomes confusing — too many skills, overlapping triggers, descriptions that no longer match the body, references to skills that were renamed/deleted.
**Recommended cadence:** weekly, or after any week where >1 skill was added/renamed.
## Procedure
### 1 — Inventory
Build a fresh census of all SKILL.md files. Do NOT trust any prior cached list.
```bash
find .agents/skills -name SKILL.md | wc -l # total count
find .agents/skills -name SKILL.md -exec wc -l {} \; | sort -rn # by body length
```
Group by domain in a mental table (DB / state / UI / agent / testing / workflow / docs / etc.). Note new arrivals since last audit (`git log --since="1 week ago" -- .agents/skills/`).
### 2 — Pull frontmatter for all skills
```bash
# Extract name + description for each SKILL.md
for f in .agents/skills/*/SKILL.md; do
echo "=== $(basename $(dirname $f)) ==="
awk '/^---$/{c++; next} c==1' "$f" | head -20
done
```
Read the description block of every skill. The body can stay unread unless step 4 flags it.
### 3 — Detect overlap / redundancy
For each pair within the same domain, ask:
- **Same description**? → likely duplicate (one is probably a stale rename leftover, or a global-vs-local collision).
- **Trigger keywords substantially overlap**? → either merge, OR tighten one description so the model can choose unambiguously.
- **One skill's body says "see also: foo"**? → confirm `foo` still exists, AND confirm the cross-reference is still meaningful (the referenced skill may have absorbed the referrer's concerns).
- **Skill duplicates content from `AGENTS.md`**? → fold into AGENTS.md or slim the skill to just the delta.
Common false positives (do NOT merge):
- `db-migrations` vs `drizzle` — distinct workflows (migration files vs schema authoring).
- `microcopy` vs `i18n` — content vs mechanics.
- `agent-runtime-hooks` vs `agent-tracing` vs `agent-signal` — different surfaces of the agent system.
- `testing` vs `local-testing` vs `cli-backend-testing` — different test types.
### 4 — Description format consistency
Apply the **standard template**:
```
{Topic + key conventions or scope}. Use when {scenarios — verbs + nouns}. Triggers on {`code-symbols`, 'natural phrases', '中文'}.
```
Skills with `disable-model-invocation: true` (user-invoked only, slash commands) don't need `Triggers on` — they're never auto-routed.
Flag descriptions that:
- ❌ Have NO `Use when` clause (model can't decide when to load it).
- ❌ Have NO `Triggers on` clause (and aren't `disable-model-invocation`).
- ❌ Use weird formats (numbered lists `(1)(2)(3)`, `Triggers:` colon instead of `Triggers on`, `MUST use when ...` as opening word).
- ❌ Are dramatically terse for a 200+ line body, or dramatically verbose for a 60-line body.
- ❌ Reference deleted/renamed skills.
### 5 — Stale-skill check
For narrow domain skills (e.g. `response-compliance`, one-off CLI workflows):
```bash
# Confirm the referenced code surface still exists
rg -l "response-compliance|openresponses" packages/ src/ # adjust per skill
git log --since="3 months ago" -- .agents/skills/ < skill > /SKILL.md # is it being maintained?
```
If the underlying surface is gone and the skill hasn't been edited in 3+ months → flag for archival.
### 6 — Cross-reference integrity
Any skill body mentioning another skill by name:
```bash
# Scan all skill bodies for skill-name references
rg -o '`[a-z][a-z0-9-]+`' .agents/skills/*/SKILL.md | grep -v ':\s*$' | sort -u
```
For each name extracted, confirm `.agents/skills/<name>/SKILL.md` exists. Broken references happen after renames — fix them in the same audit pass.
### 7 — Output report
Produce a markdown summary back to the user with the same structure as the original audit (this skill was created during one):
```markdown
## 📊 Inventory
{count, domain breakdown}
## 🎯 Recommendations
### 🔴 High confidence
- {action} — {reason}
### 🟡 Medium confidence
- {action} — {reason needs verification}
### 🟢 Low confidence / no-op
- {item considered but skipping because ...}
## 📋 Suggested order
{table of actions with risk + LOC estimate}
```
End by asking the user which actions to apply — do NOT auto-apply unless the user passed `--apply` and even then confirm destructive deletes individually.
## Output rules
- Be specific. "Skill X overlaps with Y" is useless without naming the overlapping triggers.
- Cite line numbers when flagging description / body issues.
- Don't recommend merges unless the call sites would actually load the merged skill in the same context.
- Don't recommend deletes for skills that haven't been touched recently — "unused" can mean "stable", not "dead".
## What NOT to do
- ❌ Don't rename skill directories without checking for cross-references AND user memory entries that name the old slug.
- ❌ Don't normalize a description by removing trigger keywords just to fit the template — the keywords are the routing signal.
- ❌ Don't fold a heavy 200+ line skill into another just because they share a domain — large skills get loaded selectively and merging makes everything load.
- ❌ Don't propose `.agents/skills/INDEX.md` or `<domain>-<skill>` prefix renames unless the user explicitly asks — costs > benefits for cosmetic reorgs.
## Related history
- First audit: `chore/skills-audit` branch (2026-05-25) — deleted `source-command-dedupe`, renamed `data-fetching``data-fetching-architecture`, normalized 9 descriptions, created this skill.
-182
View File
@@ -1,182 +0,0 @@
---
name: spa-routes
description: 'LobeHub SPA route architecture. Use when editing src/routes, src/features delegation, desktop/mobile/popup router configs, .desktop variants, route segments, redirects, or new pages.'
user-invocable: false
---
# SPA Routes and Features Guide
SPA structure:
- **`src/spa/`** Entry points (`entry.web.tsx`, `entry.mobile.tsx`, `entry.desktop.tsx`) and router config (`router/`). Router lives here to avoid confusion with `src/routes/`.
- **`src/routes/`** Page segments only (roots).
- **`src/features/`** Business logic and UI by domain.
This project uses a **roots vs features** split: `src/routes/` only holds page segments; business logic and UI live in `src/features/` by domain.
**Agent constraint — desktop router parity:** Edits to the desktop route tree must update **both** `src/spa/router/desktopRouter.config.tsx` and `src/spa/router/desktopRouter.config.desktop.tsx` in the same change (same paths, nesting, index routes, and segment registration). Updating only one causes drift; the missing tree can fail to register routes and surface as a **blank screen** or broken navigation on the affected build.
## When to Use This Skill
- Adding a new SPA route or route segment
- Defining or refactoring layout/page files under `src/routes/`
- Moving route-specific components or logic into `src/features/`
- Deciding where to put a new component (route folder vs feature folder)
---
## 1. What Belongs in `src/routes/` (roots)
Each route directory should contain **only**:
| File / folder | Purpose |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `_layout/index.tsx` or `layout.tsx` | Layout for this segment: wrap with `<Outlet />`, optional shell (e.g. sidebar + main). Should be thin: prefer re-exporting or composing from `@/features/*`. |
| `index.tsx` or `page.tsx` | Page entry for this segment. Only import from features and render; no business logic. |
| `[param]/index.tsx` (e.g. `[id]`, `[cronId]`) | Dynamic segment page. Same rule: thin, delegate to features. |
**Rule:** Route files should only **import and compose**. No new `features/` folders or heavy components inside `src/routes/`.
---
## 2. What Belongs in `src/features/`
Put **domain-oriented** UI and logic here:
- Layout building blocks: sidebars, headers, body panels, drawers
- Hooks and store usage for that domain
- Domain-specific forms, lists, modals, etc.
Organize by **domain** (e.g. `Pages`, `Home`, `Agent`, `PageEditor`), not by route path. One route can use several features; one feature can be used by several routes.
Each feature should:
- Live under `src/features/<FeatureName>/`
- Export a clear public API via `index.ts` or `index.tsx`
- Use `@/features/<FeatureName>/...` for internal imports when needed
---
## 3. How to Add a New SPA Route
1. **Choose the route group**
- `(main)/` desktop main app
- `(mobile)/` mobile
- `(desktop)/` Electron-specific
- `onboarding/`, `share/` special flows
2. **Create only segment files under `src/routes/`**
- e.g. `src/routes/(main)/my-feature/_layout/index.tsx` and `src/routes/(main)/my-feature/index.tsx` (and optional `[id]/index.tsx`).
3. **Implement layout and page content in `src/features/`**
- Create or reuse a domain (e.g. `src/features/MyFeature/`).
- Put layout (sidebar, header, body) and page UI there; export from the features `index`.
4. **Keep route files thin**
- Layout: `export { default } from '@/features/MyFeature/MyLayout'` or compose a few feature components + `<Outlet />`.
- Page: import from `@/features/MyFeature` (or a specific subpath) and render; no business logic in the route file.
5. **Register the route (desktop — two files, always)**
- **`desktopRouter.config.tsx`:** Add the segment with `dynamicElement` / `dynamicLayout` pointing at route modules (e.g. `@/routes/(main)/my-feature`).
- **`desktopRouter.config.desktop.tsx`:** Mirror the **same** `RouteObject` shape: identical `path` / `index` / parent-child structure. Use the static imports and elements already used in that file (see neighboring routes). Do **not** register in only one of these files.
- **Mobile-only flows:** use `mobileRouter.config.tsx` instead (no need to duplicate into the desktop pair unless the route truly exists on both).
---
## 3a. Desktop router pair (`desktopRouter.config` × 2)
| File | Role |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `desktopRouter.config.tsx` | Dynamic imports via `dynamicElement` / `dynamicLayout` — code-splitting; used by `entry.web.tsx` and `entry.desktop.tsx`. |
| `desktopRouter.config.desktop.tsx` | Same route tree with **synchronous** imports — kept for Electron / local parity and predictable bundling. |
Anything that changes the tree (new segment, renamed `path`, moved layout, new child route) must be reflected in **both** files in one PR or commit. Remove routes from both when deleting.
---
## 3b. Other `.desktop.{ts,tsx}` variants inside `src/routes/`
The router pair is **not** the only `.desktop` variant pattern in this repo. Some route trees colocate a `<name>.desktop.{ts,tsx}` next to its base `<name>.{ts,tsx}` — Vite's resolver swaps in the `.desktop` file for Electron builds. Same drift risk as the router pair: editing only one side can break Electron silently.
Known variants today:
| Base file (web) | Desktop file (Electron) | Purpose |
| ----------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `src/routes/(main)/settings/features/componentMap.ts` | `src/routes/(main)/settings/features/componentMap.desktop.ts` | Settings tab → component map. Web uses dynamic `import()`; desktop uses sync imports. `componentMap.sync.test.ts` enforces identical keys. |
| `src/routes/(main)/agent/index.tsx` | `src/routes/(main)/agent/index.desktop.tsx` | Page entry. Desktop variant overrides the web page wholesale (e.g. extra popup guards). |
| `src/routes/(main)/group/index.tsx` | `src/routes/(main)/group/index.desktop.tsx` | Same pattern as agent. |
**Rules:**
1. After editing **any** `.ts`/`.tsx` under `src/routes/`, glob the same directory for a `<filename>.desktop.{ts,tsx}` sibling. If one exists, apply the equivalent change there in the same commit.
2. When adding a new SettingsTab, register it in **both** `componentMap.ts` (with `dynamic(...)`) and `componentMap.desktop.ts` (with a sync `import`). `componentMap.sync.test.ts` will fail the build otherwise.
3. When adding a new desktop-only page wholesale-override, prefer a single base file with platform-aware code over introducing a new `.desktop.tsx` variant — only add a new variant when the two trees genuinely diverge (different store wiring, different popup guards, etc.).
4. When deleting, remove **both** files together.
---
## 4. How to Divide Files (route vs feature)
| Question | Put in `src/routes/` | Put in `src/features/` |
| -------------------------------------------------------- | -------------------------------------------------------- | ---------------------------- |
| Is it the routes layout wrapper or page entry? | Yes `_layout/index.tsx`, `index.tsx`, `[id]/index.tsx` | No |
| Does it contain business logic or non-trivial UI? | No | Yes under the right domain |
| Is it a reusable layout piece (sidebar, header, body)? | No | Yes |
| Is it a hook, store usage, or domain logic? | No | Yes |
| Is it only re-exporting or composing feature components? | Yes | No |
**Examples**
- **Route (thin):**\
`src/routes/(main)/page/_layout/index.tsx``export { default } from '@/features/Pages/PageLayout'`
- **Feature (real implementation):**\
`src/features/Pages/PageLayout/` → Sidebar, DataSync, Body, Header, styles, etc.
- **Route (thin):**\
`src/routes/(main)/page/index.tsx` → Import `PageTitle`, `PageExplorerPlaceholder` from `@/features/Pages` and `@/features/PageExplorer`; render with `<PageTitle />` and placeholder.
- **Feature:**\
Page list, actions, drawers, and hooks live under `src/features/Pages/`.
---
## 5. Progressive Migration (existing code)
We are migrating existing routes to this structure step by step:
- **Phase 1 (done):** `/page` route segment files in `src/routes/(main)/page/`, implementation in `src/features/Pages/`.
- **Later phases:** home, settings, agent/group, community/resource/memory, mobile/share/onboarding.
When touching an old route that still has logic or `features/` inside `src/routes/`:
1. Prefer adding **new** code in `src/features/<Domain>/` and importing from routes.
2. For larger refactors, move existing route-only logic into the right feature and then thin out the route files (re-export or compose from features).
3. Use `git mv` when moving files so history is preserved.
---
## 6. Reference Structure (after Phase 1)
**Route (thin):**
```
src/routes/(main)/page/
├── _layout/index.tsx → re-export or compose from @/features/Pages/PageLayout
├── index.tsx → import from @/features/Pages, @/features/PageExplorer
└── [id]/index.tsx → import from @/features/Pages, @/features/PageExplorer
```
**Feature (implementation):**
```
src/features/Pages/
├── index.ts → export PageLayout, PageTitle
├── PageTitle.tsx
└── PageLayout/
├── index.tsx → Sidebar + Outlet + DataSync
├── DataSync.tsx
├── Sidebar.tsx
├── style.ts
├── Body/ → list, actions, drawer, etc.
└── Header/ → breadcrumb, add button, etc.
```
Router config continues to point at **route** paths (e.g. `@/routes/(main)/page`, `@/routes/(main)/page/_layout`); route files then delegate to features.
@@ -1,314 +0,0 @@
---
name: store-data-structures
description: 'LobeHub Zustand store data-shape patterns. Use when designing store state, list/detail splits, normalized maps, reducers, messagesMap, topicsMap, or choosing shared type sources.'
user-invocable: false
---
# LobeHub Store Data Structures
How to structure data in Zustand stores for fast list rendering, multi-detail caching, and ergonomic optimistic updates.
## Core Principles
### ✅ DO
1. **Separate List and Detail** — different structures for list pages and detail pages
2. **Use Map for Details** — cache multiple detail pages with `Record<string, Detail>`
3. **Use Array for Lists** — simple arrays for list display
4. **Types from `@lobechat/types`** — never use `@lobechat/database` types in stores
5. **Distinguish List and Detail types** — List types may have computed UI fields
### ❌ DON'T
1. **Don't use a single detail object** — can't cache multiple pages
2. **Don't mix List and Detail types** — they have different purposes
3. **Don't use database types** — use types from `@lobechat/types`
4. **Don't use Map for lists** — simple arrays are sufficient
---
## Type Definitions
Each entity gets its own file under `@lobechat/types/`. Each file exports two types:
- **Detail type** — full entity, including heavy fields (rubrics, content, editor state, …)
- **List item type** — a **subset** that excludes heavy fields, may add computed UI fields (counts, timestamps formatted for display)
**Important:** the List type is a **subset**, not an `extends` of Detail. Extending pulls the heavy fields right back in.
> See [`references/types.md`](./references/types.md) for full worked examples (Benchmark, Document) and the heavy-field exclusion checklist.
---
## When to Use Map vs Array
### Use Map + Reducer — for Detail Data
✅ Detail page data caching — multiple detail pages cached simultaneously
✅ Optimistic updates — update UI before API responds
✅ Per-item loading states — track which items are being updated
✅ Multi-page navigation — user can switch between details without refetching
```typescript
benchmarkDetailMap: Record<string, AgentEvalBenchmark>;
```
Examples: benchmark detail pages, dataset detail pages, user profiles.
### Use Simple Array — for List Data
✅ List display — lists, tables, cards
✅ Refresh as a whole — entire list refreshes together
✅ No per-item updates — no need to mutate individual rows in place
✅ Simple data flow — fewer moving parts
```typescript
benchmarkList: AgentEvalBenchmarkListItem[];
```
Examples: benchmark list, dataset list, user list.
---
## State Structure Pattern
```typescript
// src/store/eval/slices/benchmark/initialState.ts
import type { AgentEvalBenchmark, AgentEvalBenchmarkListItem } from '@lobechat/types';
export interface BenchmarkSliceState {
// List — simple array
benchmarkList: AgentEvalBenchmarkListItem[];
benchmarkListInit: boolean;
// Detail — map for multi-entity caching
benchmarkDetailMap: Record<string, AgentEvalBenchmark>;
loadingBenchmarkDetailIds: string[]; // per-item loading
// Mutation states (drive form-level UI)
isCreatingBenchmark: boolean;
isUpdatingBenchmark: boolean;
isDeletingBenchmark: boolean;
}
export const benchmarkInitialState: BenchmarkSliceState = {
benchmarkList: [],
benchmarkListInit: false,
benchmarkDetailMap: {},
loadingBenchmarkDetailIds: [],
isCreatingBenchmark: false,
isUpdatingBenchmark: false,
isDeletingBenchmark: false,
};
```
---
## Reducer Pattern (for Detail Map)
When the Detail Map needs optimistic updates (i.e. the user edits a row and the UI should reflect it before the server confirms), wire a typed reducer instead of inlining `set` calls. This keeps mutations testable and the dispatch surface small.
> See [`references/reducer.md`](./references/reducer.md) for the full discriminated-union action types, the `produce`-based reducer, and the `internal_dispatch*` slice methods that connect them to Zustand.
---
## Data Structure Comparison
### ❌ WRONG — Single Detail Object
```typescript
interface BenchmarkSliceState {
benchmarkDetail: AgentEvalBenchmark | null;
isLoadingBenchmarkDetail: boolean;
}
```
Problems:
- Can only cache one detail page at a time
- Switching between details forces refetch
- No optimistic updates
- No per-item loading states
### ✅ CORRECT — Separate List and Detail
```typescript
interface BenchmarkSliceState {
benchmarkList: AgentEvalBenchmarkListItem[];
benchmarkListInit: boolean;
benchmarkDetailMap: Record<string, AgentEvalBenchmark>;
loadingBenchmarkDetailIds: string[];
isCreatingBenchmark: boolean;
isUpdatingBenchmark: boolean;
isDeletingBenchmark: boolean;
}
```
Benefits:
- Cache multiple detail pages
- Fast navigation between cached details
- Optimistic updates via reducer
- Per-item loading states
- Clear separation of concerns
---
## Component Usage
### Accessing List Data
```tsx
const BenchmarkList = () => {
const benchmarks = useEvalStore((s) => s.benchmarkList);
const isInit = useEvalStore((s) => s.benchmarkListInit);
if (!isInit) return <Loading />;
return (
<div>
{benchmarks.map((b) => (
<BenchmarkCard key={b.id} name={b.name} testCaseCount={b.testCaseCount} />
))}
</div>
);
};
```
### Accessing Detail Data
```tsx
const BenchmarkDetail = () => {
const { benchmarkId } = useParams<{ benchmarkId: string }>();
const benchmark = useEvalStore((s) =>
benchmarkId ? s.benchmarkDetailMap[benchmarkId] : undefined,
);
const isLoading = useEvalStore((s) =>
benchmarkId ? s.loadingBenchmarkDetailIds.includes(benchmarkId) : false,
);
if (!benchmark) return <Loading />;
return (
<div>
<h1>{benchmark.name}</h1>
{isLoading && <Spinner />}
</div>
);
};
```
### Using Selectors (Recommended)
```typescript
// src/store/eval/slices/benchmark/selectors.ts
export const benchmarkSelectors = {
getBenchmarkDetail: (id: string) => (s: EvalStore) => s.benchmarkDetailMap[id],
isLoadingBenchmarkDetail: (id: string) => (s: EvalStore) =>
s.loadingBenchmarkDetailIds.includes(id),
};
// In component
const benchmark = useEvalStore(benchmarkSelectors.getBenchmarkDetail(benchmarkId!));
const isLoading = useEvalStore(benchmarkSelectors.isLoadingBenchmarkDetail(benchmarkId!));
```
---
## Decision Tree
```text
Need to store data?
├─ Is it a LIST for display?
│ └─ ✅ Use simple array: `xxxList: XxxListItem[]`
│ - May include computed fields
│ - Refreshed as a whole
│ - No optimistic updates needed
└─ Is it DETAIL page data?
└─ ✅ Use Map: `xxxDetailMap: Record<string, Xxx>`
- Cache multiple details
- Support optimistic updates
- Per-item loading states
- Requires reducer for mutations
```
---
## Checklist
When designing store state structure:
- [ ] **Organize types by entity** in separate files (e.g. `benchmark.ts`, `agentEvalDataset.ts`)
- [ ] Create **Detail** type (full entity with all fields including heavy ones)
- [ ] Create **ListItem** type:
- [ ] Subset of Detail (exclude heavy fields)
- [ ] May include computed statistics for UI
- [ ] **NOT** `extends` Detail
- [ ] Use **array** for list data: `xxxList: XxxListItem[]`
- [ ] Use **Map** for detail data: `xxxDetailMap: Record<string, Xxx>`
- [ ] Per-item loading: `loadingXxxDetailIds: string[]`
- [ ] **Reducer** for detail map if optimistic updates needed (see [`references/reducer.md`](./references/reducer.md))
- [ ] **Internal dispatch** and **loading** methods
- [ ] **Selectors** for clean access (optional but recommended)
- [ ] Document in comments which fields are excluded from List and why
---
## Best Practices
1. **File organization** — one entity per file, not mixed
2. **List is a subset** — ListItem excludes heavy fields, does not `extends` Detail
3. **Clear naming**`xxxList` for arrays, `xxxDetailMap` for maps
4. **Consistent patterns** — all detail maps follow the same shape
5. **Type safety** — never use `any`, always use proper types
6. **Document exclusions** — comment which fields are excluded and why
7. **Selectors** — encapsulate access patterns
8. **Loading states** — per-item for details, global for mutations
9. **Immutability** — use Immer in reducers
### Common Mistakes to Avoid
**DON'T extend Detail in List:**
```typescript
// Wrong — pulls heavy fields back in
export interface BenchmarkListItem extends Benchmark {
testCaseCount?: number;
}
```
**DO create separate subset:**
```typescript
export interface BenchmarkListItem {
id: string;
name: string;
// ... only necessary fields
testCaseCount?: number; // Computed
}
```
**DON'T mix entities in one file:**
```text
// Wrong — all entities in agentEvalEntities.ts
```
**DO separate by entity:**
```text
// Correct — separate files
// benchmark.ts
// agentEvalDataset.ts
// agentEvalRun.ts
```
---
## Related Skills
- `data-fetching-architecture` — how to fetch and update this data
- `zustand` — general Zustand patterns
@@ -1,118 +0,0 @@
# Reducer Pattern (for Detail Map)
## Why Use a Reducer?
- **Immutable updates** — Immer makes immutability easy
- **Type-safe actions** — discriminated union of action types prevents typos
- **Testable** — pure function, easy to unit test
- **Reusable** — same reducer powers optimistic updates and server-data writes
## Reducer Structure
```typescript
// src/store/eval/slices/benchmark/reducer.ts
import { produce } from 'immer';
import type { AgentEvalBenchmark } from '@lobechat/types';
// Action types — discriminated union
type SetBenchmarkDetailAction = {
id: string;
type: 'setBenchmarkDetail';
value: AgentEvalBenchmark;
};
type UpdateBenchmarkDetailAction = {
id: string;
type: 'updateBenchmarkDetail';
value: Partial<AgentEvalBenchmark>;
};
type DeleteBenchmarkDetailAction = {
id: string;
type: 'deleteBenchmarkDetail';
};
export type BenchmarkDetailDispatch =
| SetBenchmarkDetailAction
| UpdateBenchmarkDetailAction
| DeleteBenchmarkDetailAction;
export const benchmarkDetailReducer = (
state: Record<string, AgentEvalBenchmark> = {},
payload: BenchmarkDetailDispatch,
): Record<string, AgentEvalBenchmark> => {
switch (payload.type) {
case 'setBenchmarkDetail': {
return produce(state, (draft) => {
draft[payload.id] = payload.value;
});
}
case 'updateBenchmarkDetail': {
return produce(state, (draft) => {
if (draft[payload.id]) {
draft[payload.id] = { ...draft[payload.id], ...payload.value };
}
});
}
case 'deleteBenchmarkDetail': {
return produce(state, (draft) => {
delete draft[payload.id];
});
}
default:
return state;
}
};
```
## Internal Dispatch Methods
The slice exposes two `internal_*` methods so the reducer and the loading state stay encapsulated behind a stable contract:
```typescript
// In action.ts
export interface BenchmarkAction {
// ... other methods ...
// Internal — not for direct UI use
internal_dispatchBenchmarkDetail: (payload: BenchmarkDetailDispatch) => void;
internal_updateBenchmarkDetailLoading: (id: string, loading: boolean) => void;
}
export const createBenchmarkSlice: StateCreator<...> = (set, get) => ({
// ... other methods ...
// Dispatch to reducer
internal_dispatchBenchmarkDetail: (payload) => {
const currentMap = get().benchmarkDetailMap;
const nextMap = benchmarkDetailReducer(currentMap, payload);
// Skip set when nothing changed — avoids unnecessary re-renders
if (isEqual(nextMap, currentMap)) return;
set(
{ benchmarkDetailMap: nextMap },
false,
`dispatchBenchmarkDetail/${payload.type}`,
);
},
// Update loading state for a specific id
internal_updateBenchmarkDetailLoading: (id, loading) => {
set(
(state) => ({
loadingBenchmarkDetailIds: loading
? [...state.loadingBenchmarkDetailIds, id]
: state.loadingBenchmarkDetailIds.filter((i) => i !== id),
}),
false,
'updateBenchmarkDetailLoading',
);
},
});
```
The `internal_` prefix is a convention — UI components should call the public mutation methods (e.g. `updateBenchmark`), which in turn call `internal_dispatch*`. This keeps reducer dispatch shapes out of the component layer.
@@ -1,101 +0,0 @@
# Type Definitions in Detail
The skill body's Type Definitions section covers the rules; this file holds the full worked examples to keep SKILL.md lean.
## Organization
Types should be organized by entity in separate files (not mixed):
```text
@lobechat/types/src/eval/
├── benchmark.ts # Benchmark types
├── agentEvalDataset.ts # Dataset types
├── agentEvalRun.ts # Run types
└── index.ts # Re-exports
```
## Example: Benchmark Types
```typescript
// packages/types/src/eval/benchmark.ts
import type { EvalBenchmarkRubric } from './rubric';
/**
* Full benchmark entity with all fields including heavy data.
*/
export interface AgentEvalBenchmark {
createdAt: Date;
description?: string | null;
id: string;
identifier: string;
isSystem: boolean;
metadata?: Record<string, unknown> | null;
name: string;
referenceUrl?: string | null;
rubrics: EvalBenchmarkRubric[]; // Heavy field
updatedAt: Date;
}
/**
* Lightweight benchmark item — excludes heavy fields, may add computed stats.
*/
export interface AgentEvalBenchmarkListItem {
createdAt: Date;
description?: string | null;
id: string;
identifier: string;
isSystem: boolean;
name: string;
// Note: rubrics NOT included (heavy field)
// Computed statistics for UI display
datasetCount?: number;
runCount?: number;
testCaseCount?: number;
}
```
## Example: Document Types (with heavy content)
```typescript
// packages/types/src/document.ts
/**
* Full document entity — includes heavy content fields.
*/
export interface Document {
id: string;
title: string;
description?: string;
content: string; // Heavy field — full markdown content
editorData: any; // Heavy field — editor state
metadata?: Record<string, unknown>;
createdAt: Date;
updatedAt: Date;
}
/**
* Lightweight document item — excludes heavy content.
*/
export interface DocumentListItem {
id: string;
title: string;
description?: string;
// Note: content and editorData NOT included
createdAt: Date;
updatedAt: Date;
// Computed statistics
wordCount?: number;
lastEditedBy?: string;
}
```
## Heavy Fields to Exclude from List
- Large text content (`content`, `editorData`, `fullDescription`)
- Complex objects (`rubrics`, `config`, `metrics`)
- Binary data (`image`, `file`)
- Large arrays (`messages`, `items`)
The reason these belong only on Detail: list pages render many rows, so pulling heavy fields blows up payload size and slows render. Detail pages render one entity, so the full payload is fine.
-120
View File
@@ -1,120 +0,0 @@
---
name: testing
description: 'Vitest testing guide. Use when writing or updating tests, fixing failing tests, improving coverage, debugging test issues, or setting up mocks.'
user-invocable: false
---
# LobeHub Testing Guide
## Quick Reference
**Commands:**
```bash
# Run specific test file
bunx vitest run --silent='passed-only' '[file-path]'
# Database package (client)
cd packages/database && bunx vitest run --silent='passed-only' '[file]'
# Database package (server)
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' '[file]'
```
**Never run** `bun run test` - it runs all 3000+ tests (\~10 minutes).
## Test Categories
| Category | Location | Config |
| -------- | --------------------------- | ------------------------------- |
| Webapp | `src/**/*.test.ts(x)` | `vitest.config.ts` |
| Packages | `packages/*/**/*.test.ts` | `packages/*/vitest.config.ts` |
| Desktop | `apps/desktop/**/*.test.ts` | `apps/desktop/vitest.config.ts` |
## Core Principles
1. **Prefer `vi.spyOn` over `vi.mock`** - More targeted, easier to maintain
2. **Tests must pass type check** - Run `bun run type-check` after writing tests
3. **After 1-2 failed fix attempts, stop and ask for help**
4. **Test behavior, not implementation details**
## Basic Test Structure
```typescript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('ModuleName', () => {
describe('functionName', () => {
it('should handle normal case', () => {
// Arrange → Act → Assert
});
});
});
```
## Mock Patterns
```typescript
// ✅ Spy on direct dependencies
vi.spyOn(messageService, 'createMessage').mockResolvedValue('id');
// ✅ Use vi.stubGlobal for browser APIs
vi.stubGlobal('Image', mockImage);
vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock');
// ❌ Avoid mocking entire modules globally
vi.mock('@/services/chat'); // Too broad
```
## Detailed Guides
See `references/` for specific testing scenarios:
- **Database Model testing**: `references/db-model-test.md`
- **Electron IPC testing**: `references/electron-ipc-test.md`
- **Zustand Store Action testing**: `references/zustand-store-action-test.md`
- **Agent Runtime E2E testing**: `references/agent-runtime-e2e.md`
- **Desktop Controller testing**: `references/desktop-controller-test.md`
## Fixing Failing Tests — Optimize or Delete?
When tests fail due to implementation changes (not bugs), evaluate before blindly fixing:
### Keep & Fix (update test data/assertions)
- **Behavior tests**: Tests that verify _what_ the code does (output, side effects, user-visible behavior). Just update mock data formats or expected values.
- Example: Tool data structure changed from `{ name }` to `{ function: { name } }` → update mock data
- Example: Output format changed from `Current date: YYYY-MM-DD` to `Current date: YYYY-MM-DD (TZ)` → update expected string
### Delete (over-specified, low value)
- **Param-forwarding tests**: Tests that assert exact internal function call arguments (e.g., `expect(internalFn).toHaveBeenCalledWith(expect.objectContaining({ exact params }))`) — these break on every refactor and duplicate what behavior tests already cover.
- **Implementation-coupled tests**: Tests that verify _how_ the code works internally rather than _what_ it produces. If a higher-level test already covers the same behavior, the low-level test adds maintenance cost without coverage gain.
### Decision Checklist
1. Does the test verify **externally observable behavior** (API response, DB write, rendered output)? → **Keep**
2. Does the test only verify **internal wiring** (which function receives which params)? → Check if a behavior test already covers it. If yes → **Delete**
3. Is the same behavior already tested at a **higher integration level**? → Delete the lower-level duplicate
4. Would the test break again on the **next routine refactor**? → Consider raising to integration level or deleting
### When Writing New Tests
- Prefer **integration-level assertions** (verify final output) over **white-box assertions** (verify internal calls)
- Use `expect.objectContaining` only for stable, public-facing contracts — not for internal param shapes that change with refactors
- Mock at boundaries (DB, network, external services), not between internal modules
## Common Issues
1. **Module pollution**: Use `vi.resetModules()` when tests fail mysteriously
2. **Mock not working**: Check setup position and use `vi.clearAllMocks()` in beforeEach
3. **Test data pollution**: Clean database state in beforeEach/afterEach
4. **Async issues**: Wrap state changes in `act()` for React hooks
@@ -1,135 +0,0 @@
# Agent Runtime E2E Testing Guide
## Core Principles
### Minimal Mock Principle
Only mock **three external dependencies**:
| Dependency | Mock | Description |
| ---------- | -------------------------- | ------------------------------------------------------- |
| Database | PGLite | In-memory database from `@lobechat/database/test-utils` |
| Redis | InMemoryAgentStateManager | Memory implementation |
| Redis | InMemoryStreamEventManager | Memory implementation |
**NOT mocked:**
- `model-bank` - Uses real model config
- `Mecha` (AgentToolsEngine, ContextEngineering)
- `AgentRuntimeService`
- `AgentRuntimeCoordinator`
### Use vi.spyOn, not vi.mock
Different tests need different LLM responses. `vi.spyOn` provides:
- Flexible return values per test
- Easy testing of different scenarios
- Better test isolation
### Default Model: gpt-5
- Always available in `model-bank`
- Stable across model updates
## Technical Implementation
### Database Setup
```typescript
import { LobeChatDatabase } from '@lobechat/database';
import { getTestDB } from '@lobechat/database/test-utils';
let testDB: LobeChatDatabase;
beforeEach(async () => {
testDB = await getTestDB();
});
```
### OpenAI Stream Response Helper
```typescript
export const createOpenAIStreamResponse = (options: {
content?: string;
toolCalls?: Array<{ id: string; name: string; arguments: string }>;
finishReason?: 'stop' | 'tool_calls';
}) => {
const { content, toolCalls, finishReason = 'stop' } = options;
return new Response(
new ReadableStream({
start(controller) {
const encoder = new TextEncoder();
if (content) {
const chunk = {
id: 'chatcmpl-mock',
object: 'chat.completion.chunk',
model: 'gpt-5',
choices: [{ index: 0, delta: { content }, finish_reason: null }],
};
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
}
// ... tool_calls handling
// ... finish chunk
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
controller.close();
},
}),
{ headers: { 'content-type': 'text/event-stream' } },
);
};
```
### State Management
```typescript
import {
InMemoryAgentStateManager,
InMemoryStreamEventManager,
} from '@/server/modules/AgentRuntime';
const stateManager = new InMemoryAgentStateManager();
const streamEventManager = new InMemoryStreamEventManager();
const service = new AgentRuntimeService(serverDB, userId, {
coordinatorOptions: { stateManager, streamEventManager },
queueService: null,
streamEventManager,
});
```
### Mock OpenAI API
```typescript
const fetchSpy = vi.spyOn(globalThis, 'fetch');
it('should handle text response', async () => {
fetchSpy.mockResolvedValueOnce(createOpenAIStreamResponse({ content: 'Response text' }));
// ... execute test
});
it('should handle tool calls', async () => {
fetchSpy.mockResolvedValueOnce(
createOpenAIStreamResponse({
toolCalls: [
{
id: 'call_123',
name: 'lobe-web-browsing____search',
arguments: JSON.stringify({ query: 'weather' }),
},
],
finishReason: 'tool_calls',
}),
);
// ... execute test
});
```
## Notes
1. **Test isolation**: Clean `InMemoryAgentStateManager` and `InMemoryStreamEventManager` after each test
2. **Timeout**: E2E tests may need longer timeouts
3. **Debug**: Use `DEBUG=lobe-server:*` for detailed logs

Some files were not shown because too many files have changed in this diff Show More