@senguoyun/dsh-arkme 0.1.51 → 0.1.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/assets/desktop_call/call-demo-peer.png +0 -0
  2. package/assets/desktop_call/call-demo-self.png +0 -0
  3. package/assets/desktop_call/call-demo-utterance-v1.m4a +0 -0
  4. package/assets/desktop_call/call-demo-utterance-v1.md +14 -0
  5. package/docs/dsh-input-system-topic.md +26 -0
  6. package/lib/client.js +17201 -8267
  7. package/lib/client.js.map +1 -1
  8. package/lib/extension-profile-restart-helper.js +10 -6
  9. package/lib/harness-model-client.js +8 -0
  10. package/lib/harness-onboarding-client.js +73 -0
  11. package/lib/index.js +164 -58
  12. package/lib/persistent-extension.js +1 -1
  13. package/lib/{persistent-runtime-Be8cvpQd.js → persistent-runtime-Bu8Zk87a.js} +698 -434
  14. package/lib/{plugin-updater-helper-B0yVav4f.js → plugin-updater-helper-n8JrMPum.js} +1 -1
  15. package/lib/{plugin-updater-helper-BbjKUV9T.js → plugin-updater-helper-y7wzJLOx.js} +11 -4
  16. package/lib/plugin-updater-helper.js +1 -1
  17. package/lib/sdk.js +8 -0
  18. package/lib/types/arkme-service.d.ts +6 -0
  19. package/lib/types/arkme-service.d.ts.map +1 -1
  20. package/lib/types/call-record-presentation.d.ts +2 -0
  21. package/lib/types/call-record-presentation.d.ts.map +1 -1
  22. package/lib/types/client/ArkmeBotConversationSurface.d.ts.map +1 -1
  23. package/lib/types/client/ArkmeBotCreateDialog.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeCallDetailContent.d.ts +14 -0
  25. package/lib/types/client/ArkmeCallDetailContent.d.ts.map +1 -0
  26. package/lib/types/client/ArkmeCallDetailDrawer.d.ts +2 -1
  27. package/lib/types/client/ArkmeCallDetailDrawer.d.ts.map +1 -1
  28. package/lib/types/client/ArkmeCallRecordContent.d.ts +4 -2
  29. package/lib/types/client/ArkmeCallRecordContent.d.ts.map +1 -1
  30. package/lib/types/client/ArkmeCallSurface.d.ts.map +1 -1
  31. package/lib/types/client/ArkmeCallTour.d.ts +22 -0
  32. package/lib/types/client/ArkmeCallTour.d.ts.map +1 -0
  33. package/lib/types/client/ArkmeComposerSendButton.d.ts +10 -0
  34. package/lib/types/client/ArkmeComposerSendButton.d.ts.map +1 -0
  35. package/lib/types/client/ArkmeConversationBottomControl.d.ts +9 -0
  36. package/lib/types/client/ArkmeConversationBottomControl.d.ts.map +1 -0
  37. package/lib/types/client/ArkmeDshAgentInputMarker.d.ts +1 -1
  38. package/lib/types/client/ArkmeDshAgentInputMarker.d.ts.map +1 -1
  39. package/lib/types/client/ArkmeEmojiPicker.d.ts +2 -1
  40. package/lib/types/client/ArkmeEmojiPicker.d.ts.map +1 -1
  41. package/lib/types/client/ArkmeHomeTour.d.ts +12 -0
  42. package/lib/types/client/ArkmeHomeTour.d.ts.map +1 -0
  43. package/lib/types/client/ArkmeLinkText.d.ts +1 -1
  44. package/lib/types/client/ArkmeLinkText.d.ts.map +1 -1
  45. package/lib/types/client/ArkmeLogin.d.ts.map +1 -1
  46. package/lib/types/client/ArkmeMarketplace.d.ts +1 -0
  47. package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -1
  48. package/lib/types/client/ArkmeMentionSuggestionRow.d.ts +20 -0
  49. package/lib/types/client/ArkmeMentionSuggestionRow.d.ts.map +1 -0
  50. package/lib/types/client/ArkmeMessageActions.d.ts.map +1 -1
  51. package/lib/types/client/ArkmeNoteDetails.d.ts +6 -2
  52. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  53. package/lib/types/client/ArkmeOfficialAuthorGuide.d.ts +7 -0
  54. package/lib/types/client/ArkmeOfficialAuthorGuide.d.ts.map +1 -0
  55. package/lib/types/client/ArkmePersistentShell.d.ts +1 -1
  56. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  57. package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
  58. package/lib/types/client/ArkmeRecordingSurface.d.ts +6 -4
  59. package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
  60. package/lib/types/client/ArkmeRecordingTour.d.ts +18 -0
  61. package/lib/types/client/ArkmeRecordingTour.d.ts.map +1 -0
  62. package/lib/types/client/ArkmeRichContent.d.ts +2 -1
  63. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  64. package/lib/types/client/ArkmeRichText.d.ts +9 -5
  65. package/lib/types/client/ArkmeRichText.d.ts.map +1 -1
  66. package/lib/types/client/ArkmeScanGuide.d.ts +5 -0
  67. package/lib/types/client/ArkmeScanGuide.d.ts.map +1 -0
  68. package/lib/types/client/ArkmeSearchSurface.d.ts.map +1 -1
  69. package/lib/types/client/ArkmeSendToSelfTour.d.ts +20 -0
  70. package/lib/types/client/ArkmeSendToSelfTour.d.ts.map +1 -0
  71. package/lib/types/client/ArkmeSidebar.d.ts +3 -35
  72. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  73. package/lib/types/client/ArkmeSourceBreadcrumb.d.ts +2 -1
  74. package/lib/types/client/ArkmeSourceBreadcrumb.d.ts.map +1 -1
  75. package/lib/types/client/ArkmeTopicDirectoryPopover.d.ts +2 -1
  76. package/lib/types/client/ArkmeTopicDirectoryPopover.d.ts.map +1 -1
  77. package/lib/types/client/ArkmeTopicReadOnlyNotice.d.ts +3 -0
  78. package/lib/types/client/ArkmeTopicReadOnlyNotice.d.ts.map +1 -0
  79. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +23 -7
  80. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  81. package/lib/types/client/api.d.ts +1 -1
  82. package/lib/types/client/api.d.ts.map +1 -1
  83. package/lib/types/client/arkme-emoji.d.ts.map +1 -1
  84. package/lib/types/client/arkme-login-locales.d.ts +13 -0
  85. package/lib/types/client/arkme-login-locales.d.ts.map +1 -1
  86. package/lib/types/client/call-detail-presentation.d.ts +13 -0
  87. package/lib/types/client/call-detail-presentation.d.ts.map +1 -0
  88. package/lib/types/client/call-tour-session.d.ts +9 -0
  89. package/lib/types/client/call-tour-session.d.ts.map +1 -0
  90. package/lib/types/client/chat-directory-store.d.ts +6 -0
  91. package/lib/types/client/chat-directory-store.d.ts.map +1 -1
  92. package/lib/types/client/composer-draft-store.d.ts +7 -0
  93. package/lib/types/client/composer-draft-store.d.ts.map +1 -1
  94. package/lib/types/client/conversation-composer-presentation.d.ts +3 -0
  95. package/lib/types/client/conversation-composer-presentation.d.ts.map +1 -1
  96. package/lib/types/client/conversation-resize-anchor.d.ts +6 -4
  97. package/lib/types/client/conversation-resize-anchor.d.ts.map +1 -1
  98. package/lib/types/client/conversation-viewport.d.ts +26 -0
  99. package/lib/types/client/conversation-viewport.d.ts.map +1 -0
  100. package/lib/types/client/detail-extension-composer-style.d.ts +38 -0
  101. package/lib/types/client/detail-extension-composer-style.d.ts.map +1 -0
  102. package/lib/types/client/emoji-recent-store.d.ts +3 -0
  103. package/lib/types/client/emoji-recent-store.d.ts.map +1 -0
  104. package/lib/types/client/harness-onboarding-client.d.ts +5 -0
  105. package/lib/types/client/harness-onboarding-client.d.ts.map +1 -0
  106. package/lib/types/client/home-tour-diagnostics.d.ts +4 -0
  107. package/lib/types/client/home-tour-diagnostics.d.ts.map +1 -0
  108. package/lib/types/client/home-tour-session.d.ts +13 -0
  109. package/lib/types/client/home-tour-session.d.ts.map +1 -0
  110. package/lib/types/client/index.d.ts +2 -1
  111. package/lib/types/client/index.d.ts.map +1 -1
  112. package/lib/types/client/mention-candidates.d.ts +46 -0
  113. package/lib/types/client/mention-candidates.d.ts.map +1 -0
  114. package/lib/types/client/navigation-cache.d.ts.map +1 -1
  115. package/lib/types/client/provider-instance-runtime.d.ts.map +1 -1
  116. package/lib/types/client/read-deadline.d.ts +3 -0
  117. package/lib/types/client/read-deadline.d.ts.map +1 -0
  118. package/lib/types/client/realtime-client-events.d.ts.map +1 -1
  119. package/lib/types/client/realtime-connection.d.ts +9 -0
  120. package/lib/types/client/realtime-connection.d.ts.map +1 -0
  121. package/lib/types/client/recording-tour-session.d.ts +9 -0
  122. package/lib/types/client/recording-tour-session.d.ts.map +1 -0
  123. package/lib/types/client/recordings/ArkmeRecordingMobileGuideDialog.d.ts +6 -0
  124. package/lib/types/client/recordings/ArkmeRecordingMobileGuideDialog.d.ts.map +1 -0
  125. package/lib/types/client/recordings/RecordingTranscriptForward.d.ts.map +1 -1
  126. package/lib/types/client/recordings/recording-mobile-guide-styles.d.ts +2 -0
  127. package/lib/types/client/recordings/recording-mobile-guide-styles.d.ts.map +1 -0
  128. package/lib/types/client/recordings/recording-tour-sample.d.ts +3 -0
  129. package/lib/types/client/recordings/recording-tour-sample.d.ts.map +1 -0
  130. package/lib/types/client/scan-guide-icons.d.ts +7 -0
  131. package/lib/types/client/scan-guide-icons.d.ts.map +1 -0
  132. package/lib/types/client/scan-guide-styles.d.ts +2 -0
  133. package/lib/types/client/scan-guide-styles.d.ts.map +1 -0
  134. package/lib/types/client/send-to-self-tour-session.d.ts +9 -0
  135. package/lib/types/client/send-to-self-tour-session.d.ts.map +1 -0
  136. package/lib/types/client/slots-contract.d.ts +23 -0
  137. package/lib/types/client/slots-contract.d.ts.map +1 -1
  138. package/lib/types/client/source-tree.d.ts.map +1 -1
  139. package/lib/types/client/use-call-record-preview.d.ts +8 -0
  140. package/lib/types/client/use-call-record-preview.d.ts.map +1 -0
  141. package/lib/types/client/use-call-transcript-playback.d.ts +13 -0
  142. package/lib/types/client/use-call-transcript-playback.d.ts.map +1 -0
  143. package/lib/types/client/use-scroll-bottom-visibility.d.ts +7 -0
  144. package/lib/types/client/use-scroll-bottom-visibility.d.ts.map +1 -0
  145. package/lib/types/client/use-tour-account.d.ts +5 -0
  146. package/lib/types/client/use-tour-account.d.ts.map +1 -0
  147. package/lib/types/dsh-agent-input-source.d.ts +0 -1
  148. package/lib/types/dsh-agent-input-source.d.ts.map +1 -1
  149. package/lib/types/emoji-recent.d.ts +8 -0
  150. package/lib/types/emoji-recent.d.ts.map +1 -0
  151. package/lib/types/extensions/profile-restart-helper.d.ts.map +1 -1
  152. package/lib/types/harness-embed-contract.d.ts +1 -0
  153. package/lib/types/harness-embed-contract.d.ts.map +1 -1
  154. package/lib/types/harness-embed-route.d.ts +1 -0
  155. package/lib/types/harness-embed-route.d.ts.map +1 -1
  156. package/lib/types/host-api.d.ts.map +1 -1
  157. package/lib/types/index.d.ts.map +1 -1
  158. package/lib/types/local-database.d.ts +5 -1
  159. package/lib/types/local-database.d.ts.map +1 -1
  160. package/lib/types/outgoing-call-assets.d.ts.map +1 -1
  161. package/lib/types/plugin-updater-helper.d.ts.map +1 -1
  162. package/lib/types/realtime-events.d.ts +9 -5
  163. package/lib/types/realtime-events.d.ts.map +1 -1
  164. package/lib/types/sdk/index.d.ts +4 -0
  165. package/lib/types/sdk/index.d.ts.map +1 -1
  166. package/lib/types/services/bot-conversation-service.d.ts.map +1 -1
  167. package/lib/types/services/bot-service.d.ts.map +1 -1
  168. package/lib/types/services/call-history-service.d.ts +3 -1
  169. package/lib/types/services/call-history-service.d.ts.map +1 -1
  170. package/lib/types/services/chat-service.d.ts +1 -0
  171. package/lib/types/services/chat-service.d.ts.map +1 -1
  172. package/lib/types/services/media-service.d.ts.map +1 -1
  173. package/lib/types/services/search-service.d.ts.map +1 -1
  174. package/lib/types/services/service.d.ts +2 -1
  175. package/lib/types/services/service.d.ts.map +1 -1
  176. package/lib/types/services/source-service.d.ts +4 -0
  177. package/lib/types/services/source-service.d.ts.map +1 -1
  178. package/lib/types/services/topic-metadata.d.ts +10 -0
  179. package/lib/types/services/topic-metadata.d.ts.map +1 -0
  180. package/lib/types/services/topic-record-page.d.ts +18 -0
  181. package/lib/types/services/topic-record-page.d.ts.map +1 -0
  182. package/lib/types/tools/business/conversation/index.d.ts.map +1 -1
  183. package/lib/types/tools/business/conversation/topic-home-visibility.d.ts +2 -0
  184. package/lib/types/tools/business/conversation/topic-home-visibility.d.ts.map +1 -0
  185. package/lib/types/tools/business/index.d.ts.map +1 -1
  186. package/lib/types/tools/ports/bots.d.ts +1 -0
  187. package/lib/types/tools/ports/bots.d.ts.map +1 -1
  188. package/lib/types/tools/ports/conversations.d.ts +3 -0
  189. package/lib/types/tools/ports/conversations.d.ts.map +1 -1
  190. package/lib/types/tools/registry/registrar.d.ts.map +1 -1
  191. package/lib/types/topic-policy.d.ts +9 -0
  192. package/lib/types/topic-policy.d.ts.map +1 -0
  193. package/lib/types/types.d.ts +23 -2
  194. package/lib/types/types.d.ts.map +1 -1
  195. package/package.json +6 -5
@@ -1800,6 +1800,368 @@ var RecordTopicAssignmentService = class {
1800
1800
  }
1801
1801
  };
1802
1802
  //#endregion
1803
+ //#region src/arkme-emoji-text.ts
1804
+ /** Asset-free emoji catalog shared by Browser UI and Host notification text. */
1805
+ const arkmeDefaultEmojiSeeds = Object.freeze([
1806
+ {
1807
+ id: "angry_face",
1808
+ unicode: "😡",
1809
+ label: "生气"
1810
+ },
1811
+ {
1812
+ id: "awkward_face",
1813
+ unicode: "😐",
1814
+ label: "尴尬"
1815
+ },
1816
+ {
1817
+ id: "heart_eyes",
1818
+ unicode: "😍",
1819
+ label: "喜欢"
1820
+ },
1821
+ {
1822
+ id: "smiling_face",
1823
+ unicode: "😊",
1824
+ label: "开心"
1825
+ },
1826
+ {
1827
+ id: "squint_tongue",
1828
+ unicode: "😝",
1829
+ label: "吐舌"
1830
+ },
1831
+ {
1832
+ id: "yelling_face",
1833
+ unicode: "😣",
1834
+ label: "崩溃"
1835
+ },
1836
+ {
1837
+ id: "sweat_smile",
1838
+ unicode: "😅",
1839
+ label: "汗笑"
1840
+ },
1841
+ {
1842
+ id: "grinning_face",
1843
+ unicode: "😁",
1844
+ label: "呲牙"
1845
+ },
1846
+ {
1847
+ id: "side_eye_smirk",
1848
+ unicode: "😏",
1849
+ label: "坏笑"
1850
+ },
1851
+ {
1852
+ id: "joy_face",
1853
+ unicode: "😂",
1854
+ label: "笑哭"
1855
+ },
1856
+ {
1857
+ id: "unamused_face",
1858
+ unicode: "🙄",
1859
+ label: "无语"
1860
+ },
1861
+ {
1862
+ id: "disappointed_face",
1863
+ unicode: "😔",
1864
+ label: "失落"
1865
+ },
1866
+ {
1867
+ id: "surprised_face",
1868
+ unicode: "😮",
1869
+ label: "惊讶"
1870
+ },
1871
+ {
1872
+ id: "teary_face",
1873
+ unicode: "😢",
1874
+ label: "委屈"
1875
+ },
1876
+ {
1877
+ id: "annoyed_face",
1878
+ unicode: "😒",
1879
+ label: "不爽"
1880
+ },
1881
+ {
1882
+ id: "kiss_heart",
1883
+ unicode: "😘",
1884
+ label: "飞吻"
1885
+ },
1886
+ {
1887
+ id: "yummy_face",
1888
+ unicode: "😋",
1889
+ label: "馋了"
1890
+ },
1891
+ {
1892
+ id: "goofy_grin",
1893
+ unicode: "🤪",
1894
+ label: "搞怪"
1895
+ },
1896
+ {
1897
+ id: "shouting_face",
1898
+ unicode: "😫",
1899
+ label: "抓狂"
1900
+ },
1901
+ {
1902
+ id: "speechless_sweat",
1903
+ unicode: "😓",
1904
+ label: "冒汗"
1905
+ },
1906
+ {
1907
+ id: "cute_face",
1908
+ unicode: "🥹",
1909
+ label: "可爱"
1910
+ },
1911
+ {
1912
+ id: "relaxed_face",
1913
+ unicode: "😌",
1914
+ label: "放松"
1915
+ },
1916
+ {
1917
+ id: "wink_tongue",
1918
+ unicode: "😜",
1919
+ label: "眨眼"
1920
+ },
1921
+ {
1922
+ id: "nauseated_face",
1923
+ unicode: "🤢",
1924
+ label: "恶心"
1925
+ },
1926
+ {
1927
+ id: "sleeping_face",
1928
+ unicode: "😴",
1929
+ label: "睡觉"
1930
+ },
1931
+ {
1932
+ id: "sleepy_face",
1933
+ unicode: "😪",
1934
+ label: "犯困"
1935
+ },
1936
+ {
1937
+ id: "groggy_face",
1938
+ unicode: "😩",
1939
+ label: "困顿"
1940
+ },
1941
+ {
1942
+ id: "crying_face",
1943
+ unicode: "😭",
1944
+ label: "大哭"
1945
+ },
1946
+ {
1947
+ id: "disgusted_face",
1948
+ unicode: "😖",
1949
+ label: "难受"
1950
+ },
1951
+ {
1952
+ id: "shocked_face",
1953
+ unicode: "😱",
1954
+ label: "吓到"
1955
+ },
1956
+ {
1957
+ id: "star_struck_face",
1958
+ unicode: "🤩",
1959
+ label: "星星眼"
1960
+ },
1961
+ {
1962
+ id: "yawning_face",
1963
+ unicode: "🥱",
1964
+ label: "打哈欠"
1965
+ },
1966
+ {
1967
+ id: "sobbing_face",
1968
+ unicode: "😭",
1969
+ label: "暴哭"
1970
+ },
1971
+ {
1972
+ id: "frustrated_face",
1973
+ unicode: "😣",
1974
+ label: "憋屈"
1975
+ },
1976
+ {
1977
+ id: "excited_face",
1978
+ unicode: "😛",
1979
+ label: "兴奋"
1980
+ },
1981
+ {
1982
+ id: "sick_face",
1983
+ unicode: "😨",
1984
+ label: "慌张"
1985
+ },
1986
+ {
1987
+ id: "worried_face",
1988
+ unicode: "😟",
1989
+ label: "担心"
1990
+ },
1991
+ {
1992
+ id: "kiss_sweat_face",
1993
+ unicode: "😗",
1994
+ label: "亲亲"
1995
+ },
1996
+ {
1997
+ id: "speechless_face",
1998
+ unicode: "😑",
1999
+ label: "无话可说"
2000
+ },
2001
+ {
2002
+ id: "masked_face",
2003
+ unicode: "😷",
2004
+ label: "戴口罩"
2005
+ },
2006
+ {
2007
+ id: "stunned_face",
2008
+ unicode: "😳",
2009
+ label: "呆住"
2010
+ },
2011
+ {
2012
+ id: "green_shock_face",
2013
+ unicode: "🤢",
2014
+ label: "反胃"
2015
+ },
2016
+ {
2017
+ id: "silent_face",
2018
+ unicode: "😶",
2019
+ label: "沉默"
2020
+ },
2021
+ {
2022
+ id: "yawn_face",
2023
+ unicode: "🥱",
2024
+ label: "困了"
2025
+ },
2026
+ {
2027
+ id: "pleading_face",
2028
+ unicode: "🥺",
2029
+ label: "求求"
2030
+ },
2031
+ {
2032
+ id: "dizzy_face",
2033
+ unicode: "😵",
2034
+ label: "晕了"
2035
+ },
2036
+ {
2037
+ id: "red_angry_face",
2038
+ unicode: "😠",
2039
+ label: "发火"
2040
+ },
2041
+ {
2042
+ id: "queasy_face",
2043
+ unicode: "🤮",
2044
+ label: "想吐"
2045
+ },
2046
+ {
2047
+ id: "ok_hand",
2048
+ unicode: "👌",
2049
+ label: "OK"
2050
+ },
2051
+ {
2052
+ id: "thumb_up",
2053
+ unicode: "👍",
2054
+ label: "赞"
2055
+ },
2056
+ {
2057
+ id: "thanks_hands",
2058
+ unicode: "🙏",
2059
+ label: "感谢"
2060
+ },
2061
+ {
2062
+ id: "fist",
2063
+ unicode: "👊",
2064
+ label: "拳头"
2065
+ },
2066
+ {
2067
+ id: "handshake",
2068
+ unicode: "🤝",
2069
+ label: "握手"
2070
+ },
2071
+ {
2072
+ id: "thumb_down",
2073
+ unicode: "👎",
2074
+ label: "弱"
2075
+ },
2076
+ {
2077
+ id: "victory_hand",
2078
+ unicode: "✌️",
2079
+ label: "胜利"
2080
+ },
2081
+ {
2082
+ id: "fist_salute",
2083
+ unicode: "🙏",
2084
+ label: "抱拳"
2085
+ }
2086
+ ]);
2087
+ const arkmeEmojiTokenPattern = /\[(?:jm_emoji|im_emoji):([a-z0-9_]+)\]/gu;
2088
+ const arkmeEmojiSeedById = new Map(arkmeDefaultEmojiSeeds.map((emoji) => [emoji.id, emoji]));
2089
+ /** The asset-free parser is shared by rich and plain-text projections. */
2090
+ function arkmeEmojiTextRuns(value) {
2091
+ const runs = [];
2092
+ let cursor = 0;
2093
+ for (const match of value.matchAll(arkmeEmojiTokenPattern)) {
2094
+ const emoji = arkmeEmojiSeedById.get(match[1] ?? "");
2095
+ if (emoji === void 0) continue;
2096
+ if (match.index > cursor) runs.push({
2097
+ kind: "text",
2098
+ text: value.slice(cursor, match.index)
2099
+ });
2100
+ runs.push({
2101
+ kind: "emoji",
2102
+ text: match[0],
2103
+ emoji
2104
+ });
2105
+ cursor = match.index + match[0].length;
2106
+ }
2107
+ if (cursor < value.length) runs.push({
2108
+ kind: "text",
2109
+ text: value.slice(cursor)
2110
+ });
2111
+ return runs;
2112
+ }
2113
+ function arkmeHasKnownEmojiToken(value) {
2114
+ if (value === "" || !value.includes("_emoji:")) return false;
2115
+ for (const match of value.matchAll(arkmeEmojiTokenPattern)) if (arkmeEmojiSeedById.has(match[1] ?? "")) return true;
2116
+ return false;
2117
+ }
2118
+ const graphemes = new Intl.Segmenter(void 0, { granularity: "grapheme" });
2119
+ /** Preserve the caller's budget; neither tokens nor graphemes can be split. */
2120
+ function arkmeEmojiTokenSafePrefix(value, maxLength, unit = "codePoints") {
2121
+ const limit = Math.floor(maxLength);
2122
+ if (!(limit > 0)) return "";
2123
+ let endOffset = Math.min(value.length, limit);
2124
+ if (unit === "codePoints") {
2125
+ endOffset = 0;
2126
+ let count = 0;
2127
+ for (const point of value) {
2128
+ if (count++ >= limit) break;
2129
+ endOffset += point.length;
2130
+ }
2131
+ }
2132
+ if (endOffset >= value.length) return value;
2133
+ for (const { index, segment } of graphemes.segment(value)) if (index + segment.length > endOffset) {
2134
+ endOffset = index;
2135
+ break;
2136
+ }
2137
+ for (const match of value.matchAll(arkmeEmojiTokenPattern)) {
2138
+ if (match.index >= endOffset) break;
2139
+ if (match.index < endOffset && endOffset < match.index + match[0].length) {
2140
+ endOffset = match.index;
2141
+ break;
2142
+ }
2143
+ }
2144
+ return value.slice(0, endOffset);
2145
+ }
2146
+ /** User-content equivalent of clippedText; keeps its UTF-16 limit and suffix. */
2147
+ function arkmeEmojiClippedText(value, limit = 4e3) {
2148
+ const text = typeof value === "string" ? value.trim() : "";
2149
+ return text.length > limit ? `${arkmeEmojiTokenSafePrefix(text, limit, "codeUnits")}…[已截断]` : text;
2150
+ }
2151
+ /** Known chat emoji tokens become Unicode; unknown and malformed tokens stay intact. */
2152
+ function arkmeEmojiPlainText(value) {
2153
+ if (value === "" || !value.includes("_emoji:")) return value;
2154
+ return arkmeEmojiTextRuns(value).map((run) => run.kind === "emoji" ? run.emoji.unicode : run.text).join("");
2155
+ }
2156
+ const knownIds = new Set(arkmeDefaultEmojiSeeds.map((emoji) => emoji.id));
2157
+ function isRecentEmojiId(id) {
2158
+ return knownIds.has(id);
2159
+ }
2160
+ function normalizeRecentEmojiIds(value) {
2161
+ if (!Array.isArray(value)) return [];
2162
+ return [...new Set(value.filter((id) => typeof id === "string" && isRecentEmojiId(id)))].slice(0, 8);
2163
+ }
2164
+ //#endregion
1803
2165
  //#region src/bot-chat-directory-projection.ts
1804
2166
  function projectBotChatDirectory(sources, bots) {
1805
2167
  const sourcesByKey = new Map(sources.flatMap((source) => source.kind !== "private_chat" || source.sourceKey === void 0 || source.sourceKey === "" ? [] : [[source.sourceKey, source]]));
@@ -15558,6 +15920,48 @@ async function patchChatPolicy(runtime, session, chatSessionUid, patch, signal)
15558
15920
  return updated;
15559
15921
  }
15560
15922
  //#endregion
15923
+ //#region src/services/topic-record-page.ts
15924
+ /** Transport contract shared by timeline reading and complete pre-dissolution enumeration.
15925
+ * Privacy visibility and mutation policy remain in their respective business owners.
15926
+ */
15927
+ async function readTopicRecordPage(runtime, session, topicUid, options) {
15928
+ const data = await runtime.authenticatedPost("/api/v1/topics/display/records/page", {
15929
+ topic_uid: topicUid,
15930
+ limit: options.limit,
15931
+ ...options.cursor === void 0 ? {} : {
15932
+ cursor_send_at: options.cursor.sendAtMillis,
15933
+ cursor_record_uid: options.cursor.itemUid
15934
+ }
15935
+ }, session, options.signal);
15936
+ const sendAt = data.next_cursor_send_at;
15937
+ const uid = stringValue$2(data.next_cursor_record_uid).trim();
15938
+ const hasCursor = typeof sendAt === "number" && Number.isSafeInteger(sendAt) && sendAt > 0 && uid !== "";
15939
+ if (data.topic_uid !== topicUid || !Array.isArray(data.records) || typeof data.has_more !== "boolean" || data.privacy_state !== 1 && data.privacy_state !== 2 || data.has_more && !hasCursor || data.records.some((raw) => stringValue$2(objectValue$2(raw).record_uid).trim() === "")) throw new ArkmePluginError("topic-record-page-invalid", "主题快记返回不完整,请重试或确认服务端已升级", true, 502);
15940
+ if (data.has_more && options.cursor !== void 0 && sendAt === options.cursor.sendAtMillis && uid === options.cursor.itemUid) throw new ArkmePluginError("topic-record-page-invalid", "主题快记分页未推进,请重试", true, 502);
15941
+ return {
15942
+ records: data.records,
15943
+ privacyState: data.privacy_state,
15944
+ hasMore: data.has_more,
15945
+ ...hasCursor ? { nextCursor: {
15946
+ sendAtMillis: sendAt,
15947
+ itemUid: uid
15948
+ } } : {}
15949
+ };
15950
+ }
15951
+ //#endregion
15952
+ //#region src/services/topic-metadata.ts
15953
+ /** Lightweight topic identity/settings contract. It never loads records or summary statistics. */
15954
+ async function readTopicMetadata(runtime, session, topicUid, signal) {
15955
+ const core = objectValue$2((await runtime.authenticatedPost("/api/v1/topics/display/metadata", { topic_uid: topicUid }, session, signal)).topic_core);
15956
+ const topicKind = typeof core.kind === "number" ? core.kind : 0;
15957
+ if (stringValue$2(core.topic_uid).trim() !== topicUid || !Number.isSafeInteger(topicKind) || topicKind <= 0 || core.privacy_state !== 1 && core.privacy_state !== 2 || typeof core.show_in_home !== "boolean") throw new ArkmePluginError("topic-metadata-invalid", "主题信息返回不完整,请重试或确认服务端已升级", true, 502);
15958
+ return {
15959
+ topicKind,
15960
+ privacyState: core.privacy_state,
15961
+ showInHome: core.show_in_home
15962
+ };
15963
+ }
15964
+ //#endregion
15561
15965
  //#region node_modules/bail/index.js
15562
15966
  /**
15563
15967
  * Throw a given error.
@@ -34709,359 +35113,16 @@ function arkmePickedFileKind(mimeType, fileName = "") {
34709
35113
  const mediaKind = arkmeMediaKind(arkmeNormalizedFileMimeType(mimeType, fileName), fileName);
34710
35114
  return mediaKind === "image" ? 1 : mediaKind === "video" ? 3 : 4;
34711
35115
  }
34712
- //#endregion
34713
- //#region src/arkme-emoji-text.ts
34714
- /** Asset-free emoji catalog shared by Browser UI and Host notification text. */
34715
- const arkmeDefaultEmojiSeeds = Object.freeze([
34716
- {
34717
- id: "angry_face",
34718
- unicode: "😡",
34719
- label: "生气"
34720
- },
34721
- {
34722
- id: "awkward_face",
34723
- unicode: "😐",
34724
- label: "尴尬"
34725
- },
34726
- {
34727
- id: "heart_eyes",
34728
- unicode: "😍",
34729
- label: "喜欢"
34730
- },
34731
- {
34732
- id: "smiling_face",
34733
- unicode: "😊",
34734
- label: "开心"
34735
- },
34736
- {
34737
- id: "squint_tongue",
34738
- unicode: "😝",
34739
- label: "吐舌"
34740
- },
34741
- {
34742
- id: "yelling_face",
34743
- unicode: "😣",
34744
- label: "崩溃"
34745
- },
34746
- {
34747
- id: "sweat_smile",
34748
- unicode: "😅",
34749
- label: "汗笑"
34750
- },
34751
- {
34752
- id: "grinning_face",
34753
- unicode: "😁",
34754
- label: "呲牙"
34755
- },
34756
- {
34757
- id: "side_eye_smirk",
34758
- unicode: "😏",
34759
- label: "坏笑"
34760
- },
34761
- {
34762
- id: "joy_face",
34763
- unicode: "😂",
34764
- label: "笑哭"
34765
- },
34766
- {
34767
- id: "unamused_face",
34768
- unicode: "🙄",
34769
- label: "无语"
34770
- },
34771
- {
34772
- id: "disappointed_face",
34773
- unicode: "😔",
34774
- label: "失落"
34775
- },
34776
- {
34777
- id: "surprised_face",
34778
- unicode: "😮",
34779
- label: "惊讶"
34780
- },
34781
- {
34782
- id: "teary_face",
34783
- unicode: "😢",
34784
- label: "委屈"
34785
- },
34786
- {
34787
- id: "annoyed_face",
34788
- unicode: "😒",
34789
- label: "不爽"
34790
- },
34791
- {
34792
- id: "kiss_heart",
34793
- unicode: "😘",
34794
- label: "飞吻"
34795
- },
34796
- {
34797
- id: "yummy_face",
34798
- unicode: "😋",
34799
- label: "馋了"
34800
- },
34801
- {
34802
- id: "goofy_grin",
34803
- unicode: "🤪",
34804
- label: "搞怪"
34805
- },
34806
- {
34807
- id: "shouting_face",
34808
- unicode: "😫",
34809
- label: "抓狂"
34810
- },
34811
- {
34812
- id: "speechless_sweat",
34813
- unicode: "😓",
34814
- label: "冒汗"
34815
- },
34816
- {
34817
- id: "cute_face",
34818
- unicode: "🥹",
34819
- label: "可爱"
34820
- },
34821
- {
34822
- id: "relaxed_face",
34823
- unicode: "😌",
34824
- label: "放松"
34825
- },
34826
- {
34827
- id: "wink_tongue",
34828
- unicode: "😜",
34829
- label: "眨眼"
34830
- },
34831
- {
34832
- id: "nauseated_face",
34833
- unicode: "🤢",
34834
- label: "恶心"
34835
- },
34836
- {
34837
- id: "sleeping_face",
34838
- unicode: "😴",
34839
- label: "睡觉"
34840
- },
34841
- {
34842
- id: "sleepy_face",
34843
- unicode: "😪",
34844
- label: "犯困"
34845
- },
34846
- {
34847
- id: "groggy_face",
34848
- unicode: "😩",
34849
- label: "困顿"
34850
- },
34851
- {
34852
- id: "crying_face",
34853
- unicode: "😭",
34854
- label: "大哭"
34855
- },
34856
- {
34857
- id: "disgusted_face",
34858
- unicode: "😖",
34859
- label: "难受"
34860
- },
34861
- {
34862
- id: "shocked_face",
34863
- unicode: "😱",
34864
- label: "吓到"
34865
- },
34866
- {
34867
- id: "star_struck_face",
34868
- unicode: "🤩",
34869
- label: "星星眼"
34870
- },
34871
- {
34872
- id: "yawning_face",
34873
- unicode: "🥱",
34874
- label: "打哈欠"
34875
- },
34876
- {
34877
- id: "sobbing_face",
34878
- unicode: "😭",
34879
- label: "暴哭"
34880
- },
34881
- {
34882
- id: "frustrated_face",
34883
- unicode: "😣",
34884
- label: "憋屈"
34885
- },
34886
- {
34887
- id: "excited_face",
34888
- unicode: "😛",
34889
- label: "兴奋"
34890
- },
34891
- {
34892
- id: "sick_face",
34893
- unicode: "😨",
34894
- label: "慌张"
34895
- },
34896
- {
34897
- id: "worried_face",
34898
- unicode: "😟",
34899
- label: "担心"
34900
- },
34901
- {
34902
- id: "kiss_sweat_face",
34903
- unicode: "😗",
34904
- label: "亲亲"
34905
- },
34906
- {
34907
- id: "speechless_face",
34908
- unicode: "😑",
34909
- label: "无话可说"
34910
- },
34911
- {
34912
- id: "masked_face",
34913
- unicode: "😷",
34914
- label: "戴口罩"
34915
- },
34916
- {
34917
- id: "stunned_face",
34918
- unicode: "😳",
34919
- label: "呆住"
34920
- },
34921
- {
34922
- id: "green_shock_face",
34923
- unicode: "🤢",
34924
- label: "反胃"
34925
- },
34926
- {
34927
- id: "silent_face",
34928
- unicode: "😶",
34929
- label: "沉默"
34930
- },
34931
- {
34932
- id: "yawn_face",
34933
- unicode: "🥱",
34934
- label: "困了"
34935
- },
34936
- {
34937
- id: "pleading_face",
34938
- unicode: "🥺",
34939
- label: "求求"
34940
- },
34941
- {
34942
- id: "dizzy_face",
34943
- unicode: "😵",
34944
- label: "晕了"
34945
- },
34946
- {
34947
- id: "red_angry_face",
34948
- unicode: "😠",
34949
- label: "发火"
34950
- },
34951
- {
34952
- id: "queasy_face",
34953
- unicode: "🤮",
34954
- label: "想吐"
34955
- },
34956
- {
34957
- id: "ok_hand",
34958
- unicode: "👌",
34959
- label: "OK"
34960
- },
34961
- {
34962
- id: "thumb_up",
34963
- unicode: "👍",
34964
- label: "赞"
34965
- },
34966
- {
34967
- id: "thanks_hands",
34968
- unicode: "🙏",
34969
- label: "感谢"
34970
- },
34971
- {
34972
- id: "fist",
34973
- unicode: "👊",
34974
- label: "拳头"
34975
- },
34976
- {
34977
- id: "handshake",
34978
- unicode: "🤝",
34979
- label: "握手"
34980
- },
34981
- {
34982
- id: "thumb_down",
34983
- unicode: "👎",
34984
- label: "弱"
34985
- },
34986
- {
34987
- id: "victory_hand",
34988
- unicode: "✌️",
34989
- label: "胜利"
34990
- },
34991
- {
34992
- id: "fist_salute",
34993
- unicode: "🙏",
34994
- label: "抱拳"
34995
- }
34996
- ]);
34997
- const arkmeEmojiTokenPattern = /\[(?:jm_emoji|im_emoji):([a-z0-9_]+)\]/gu;
34998
- const arkmeEmojiSeedById = new Map(arkmeDefaultEmojiSeeds.map((emoji) => [emoji.id, emoji]));
34999
- /** The asset-free parser is shared by rich and plain-text projections. */
35000
- function arkmeEmojiTextRuns(value) {
35001
- const runs = [];
35002
- let cursor = 0;
35003
- for (const match of value.matchAll(arkmeEmojiTokenPattern)) {
35004
- const emoji = arkmeEmojiSeedById.get(match[1] ?? "");
35005
- if (emoji === void 0) continue;
35006
- if (match.index > cursor) runs.push({
35007
- kind: "text",
35008
- text: value.slice(cursor, match.index)
35009
- });
35010
- runs.push({
35011
- kind: "emoji",
35012
- text: match[0],
35013
- emoji
35014
- });
35015
- cursor = match.index + match[0].length;
35016
- }
35017
- if (cursor < value.length) runs.push({
35018
- kind: "text",
35019
- text: value.slice(cursor)
35020
- });
35021
- return runs;
35022
- }
35023
- function arkmeHasKnownEmojiToken(value) {
35024
- if (value === "" || !value.includes("_emoji:")) return false;
35025
- for (const match of value.matchAll(arkmeEmojiTokenPattern)) if (arkmeEmojiSeedById.has(match[1] ?? "")) return true;
35026
- return false;
35116
+ const ARKME_DSH_INPUT_TOPIC_TITLE = "发给 DSH 的消息";
35117
+ /** Display only; source identities and stored titles remain unchanged. */
35118
+ function arkmeTopicDisplayName(title, topicKind) {
35119
+ return topicKind === 3 ? ARKME_DSH_INPUT_TOPIC_TITLE : title;
35027
35120
  }
35028
- const graphemes = new Intl.Segmenter(void 0, { granularity: "grapheme" });
35029
- /** Preserve the caller's budget; neither tokens nor graphemes can be split. */
35030
- function arkmeEmojiTokenSafePrefix(value, maxLength, unit = "codePoints") {
35031
- const limit = Math.floor(maxLength);
35032
- if (!(limit > 0)) return "";
35033
- let endOffset = Math.min(value.length, limit);
35034
- if (unit === "codePoints") {
35035
- endOffset = 0;
35036
- let count = 0;
35037
- for (const point of value) {
35038
- if (count++ >= limit) break;
35039
- endOffset += point.length;
35040
- }
35041
- }
35042
- if (endOffset >= value.length) return value;
35043
- for (const { index, segment } of graphemes.segment(value)) if (index + segment.length > endOffset) {
35044
- endOffset = index;
35045
- break;
35046
- }
35047
- for (const match of value.matchAll(arkmeEmojiTokenPattern)) {
35048
- if (match.index >= endOffset) break;
35049
- if (match.index < endOffset && endOffset < match.index + match[0].length) {
35050
- endOffset = match.index;
35051
- break;
35052
- }
35053
- }
35054
- return value.slice(0, endOffset);
35121
+ function isArkmeDSHInputTopic(source) {
35122
+ return source?.kind === "topic" && source.topicKind === 3;
35055
35123
  }
35056
- /** User-content equivalent of clippedText; keeps its UTF-16 limit and suffix. */
35057
- function arkmeEmojiClippedText(value, limit = 4e3) {
35058
- const text = typeof value === "string" ? value.trim() : "";
35059
- return text.length > limit ? `${arkmeEmojiTokenSafePrefix(text, limit, "codeUnits")}…[已截断]` : text;
35060
- }
35061
- /** Known chat emoji tokens become Unicode; unknown and malformed tokens stay intact. */
35062
- function arkmeEmojiPlainText(value) {
35063
- if (value === "" || !value.includes("_emoji:")) return value;
35064
- return arkmeEmojiTextRuns(value).map((run) => run.kind === "emoji" ? run.emoji.unicode : run.text).join("");
35124
+ function arkmeSourceAllowsUserWrite(source) {
35125
+ return !isArkmeDSHInputTopic(source);
35065
35126
  }
35066
35127
  //#endregion
35067
35128
  //#region src/services/source-service.ts
@@ -35600,6 +35661,7 @@ var SourceService = class {
35600
35661
  const topicUid = stringValue$2(created.topic_uid).trim();
35601
35662
  if (topicUid === "" || numberValue$26(created.status) !== 1) throw new ArkmePluginError("topic-create-contract-invalid", "主题创建响应不完整", true, 502);
35602
35663
  const sourceRef = await this.sealSourceRef(session.userId, "topic", topicUid, title);
35664
+ const topicHierarchyKey = await this.topicHierarchyKey(session.userId, topicUid);
35603
35665
  if (parentTopicUid !== void 0) try {
35604
35666
  if (numberValue$26(objectValue$2((await this.runtime.authenticatedPost("/api/v1/topics/hierarchy/bind", {
35605
35667
  parent_topic_uid: parentTopicUid,
@@ -35620,6 +35682,7 @@ var SourceService = class {
35620
35682
  return {
35621
35683
  source: {
35622
35684
  sourceRef,
35685
+ topicHierarchyKey,
35623
35686
  kind: "topic",
35624
35687
  displayName: title,
35625
35688
  activeAtMillis: createdAtMillis,
@@ -35630,10 +35693,16 @@ var SourceService = class {
35630
35693
  };
35631
35694
  }
35632
35695
  throw new ArkmePluginError("topic-hierarchy-bind-failed", "未能创建子主题,已自动清理,请重试", true, 409, { cause: bindError });
35696
+ } finally {
35697
+ this.invalidateSourceListCache(session.userId, "send_to_self");
35633
35698
  }
35634
35699
  return { source: {
35635
35700
  sourceRef,
35636
- ...parentSourceRef !== void 0 ? { parentSourceRef } : {},
35701
+ topicHierarchyKey,
35702
+ ...parentSourceRef !== void 0 && parentTopicUid !== void 0 ? {
35703
+ parentSourceRef,
35704
+ parentTopicHierarchyKey: await this.topicHierarchyKey(session.userId, parentTopicUid)
35705
+ } : {},
35637
35706
  kind: "topic",
35638
35707
  displayName: title,
35639
35708
  activeAtMillis: createdAtMillis,
@@ -35666,6 +35735,20 @@ var SourceService = class {
35666
35735
  displayName: title
35667
35736
  };
35668
35737
  }
35738
+ /** Reuse the record owner's topic policy, without replaying title/privacy defaults. */
35739
+ async topicHomeVisibility(sourceRef, showInHome, signal) {
35740
+ const session = await this.runtime.requireSession();
35741
+ const topic = await this.openSourceRef(sourceRef, session.userId);
35742
+ if (topic.kind !== "topic") throw new ArkmePluginError("topic-policy-invalid", "请选择主题", false);
35743
+ if (showInHome === void 0) return { showInHome: (await readTopicMetadata(this.runtime, session, topic.ownerRef, signal)).showInHome };
35744
+ const value = (await this.runtime.authenticatedPost("/api/v1/topics/display/policy/set", {
35745
+ topic_uid: topic.ownerRef,
35746
+ show_in_home: showInHome
35747
+ }, session, void 0)).show_in_home;
35748
+ if (typeof value !== "boolean") throw new ArkmePluginError("topic-policy-contract-invalid", "主题设置响应不完整,请重试", true, 502);
35749
+ this.invalidateSourceListCache(session.userId, "send_to_self");
35750
+ return { showInHome: value };
35751
+ }
35669
35752
  /**
35670
35753
  * Dissolving a topic promotes its direct children and moves the topic's own
35671
35754
  * records into its parent. Root-topic records return to the default category.
@@ -35764,21 +35847,18 @@ var SourceService = class {
35764
35847
  let cursorSendAt;
35765
35848
  let cursorRecordUid;
35766
35849
  for (let pageIndex = 0; pageIndex < 1e3; pageIndex += 1) {
35767
- const data = await this.runtime.authenticatedPost("/api/v1/topics/display/detail", {
35768
- topic_uid: topicUid,
35850
+ const page = await readTopicRecordPage(this.runtime, session, topicUid, {
35769
35851
  limit: 100,
35770
- ...cursorSendAt === void 0 ? {} : { cursor_send_at: cursorSendAt },
35771
- ...cursorRecordUid === void 0 ? {} : { cursor_record_uid: cursorRecordUid }
35772
- }, session);
35773
- for (const raw of listValue$23(data.records)) {
35774
- const item = objectValue$2(raw);
35775
- const uid = stringValue$2(item.record_uid ?? objectValue$2(item.record_core).record_uid).trim();
35776
- if (uid !== "") recordUids.add(uid);
35777
- }
35852
+ ...cursorSendAt === void 0 || cursorRecordUid === void 0 ? {} : { cursor: {
35853
+ sendAtMillis: cursorSendAt,
35854
+ itemUid: cursorRecordUid
35855
+ } }
35856
+ });
35857
+ for (const raw of page.records) recordUids.add(stringValue$2(objectValue$2(raw).record_uid).trim());
35778
35858
  onProgress?.(recordUids.size);
35779
- if (data.has_more !== true) return [...recordUids];
35780
- const nextSendAt = numberValue$26(data.next_cursor_send_at);
35781
- const nextRecordUid = stringValue$2(data.next_cursor_record_uid).trim();
35859
+ if (!page.hasMore) return [...recordUids];
35860
+ const nextSendAt = page.nextCursor?.sendAtMillis ?? 0;
35861
+ const nextRecordUid = page.nextCursor?.itemUid ?? "";
35782
35862
  const cursorKey = `${String(nextSendAt)}:${nextRecordUid}`;
35783
35863
  if (nextSendAt <= 0 || nextRecordUid === "" || seenCursors.has(cursorKey)) throw new ArkmePluginError("topic-dissolve-record-page-invalid", "主题快记加载不完整,未解散主题,请刷新后重试", true, 502);
35784
35864
  seenCursors.add(cursorKey);
@@ -35916,6 +35996,11 @@ var SourceService = class {
35916
35996
  const entry = objectValue$2(raw);
35917
35997
  const core = objectValue$2(entry.topic_core);
35918
35998
  if (arkmePrivacyLockedTopic(entry) || core.status !== 1) continue;
35999
+ const topicKind = numberValue$26(core.kind) || 1;
36000
+ if (!arkmeSourceAllowsUserWrite({
36001
+ kind: "topic",
36002
+ topicKind
36003
+ })) continue;
35919
36004
  const topicUid = stringValue$2(core.topic_uid).trim();
35920
36005
  const title = stringValue$2(core.title).trim();
35921
36006
  if (!topicUid || !title || seen.has(topicUid)) continue;
@@ -35923,6 +36008,7 @@ var SourceService = class {
35923
36008
  const recordCount = objectValue$2(entry.summary).record_count;
35924
36009
  items.push({
35925
36010
  kind: "topic",
36011
+ topicKind,
35926
36012
  displayName: title,
35927
36013
  activeAtMillis: numberValue$26(core.update_at),
35928
36014
  unreadCount: 0,
@@ -36113,6 +36199,7 @@ var SourceService = class {
36113
36199
  const parentTopicUid = stringValue$2(parentTopicUidByChild.get(topicUid) ?? core.parent_topic_uid ?? core.parent_uid ?? item.parent_topic_uid ?? item.parent_uid ?? parent.topic_uid ?? parent.uid).trim();
36114
36200
  topicDescriptors.push({
36115
36201
  topicUid,
36202
+ topicKind: numberValue$26(core.kind) || 1,
36116
36203
  ...parentTopicUid === "" || parentTopicUid === topicUid ? {} : { parentTopicUid },
36117
36204
  siblingOrder: numberValue$26(siblingOrderByChild.get(topicUid) ?? core.sibling_order ?? item.sibling_order),
36118
36205
  title,
@@ -36139,7 +36226,8 @@ var SourceService = class {
36139
36226
  ...parentTopicHierarchyKey === void 0 ? {} : { parentTopicHierarchyKey },
36140
36227
  ...topic.siblingOrder > 0 ? { siblingOrder: topic.siblingOrder } : {},
36141
36228
  kind: "topic",
36142
- displayName: topic.title,
36229
+ topicKind: topic.topicKind,
36230
+ displayName: arkmeTopicDisplayName(topic.title, topic.topicKind),
36143
36231
  ...topic.latestPreview === "" ? {} : { latestPreview: topic.latestPreview },
36144
36232
  activeAtMillis: topic.activeAtMillis,
36145
36233
  unreadCount: 0,
@@ -36657,7 +36745,7 @@ function botConversationCapabilities(target, provider) {
36657
36745
  const owner = target.kind === "subject" ? BOT_CONVERSATION_OWNER.subject : target.kind === "chat" ? BOT_CONVERSATION_OWNER.chat : void 0;
36658
36746
  return {
36659
36747
  directChatAvailable: owner !== void 0,
36660
- privateChatOutboundEnabled: owner === BOT_CONVERSATION_OWNER.chat || owner === BOT_CONVERSATION_OWNER.subject && provider === "openclaw",
36748
+ privateChatOutboundEnabled: owner !== void 0 && provider === "openclaw",
36661
36749
  refreshOnRecordChanges: owner === BOT_CONVERSATION_OWNER.subject,
36662
36750
  conversationProjection: owner === BOT_CONVERSATION_OWNER.subject ? "record" : owner === BOT_CONVERSATION_OWNER.chat ? "chat" : "none"
36663
36751
  };
@@ -36746,10 +36834,18 @@ var BotService = class {
36746
36834
  name,
36747
36835
  provider,
36748
36836
  description,
36749
- avatar
36837
+ avatar,
36838
+ direct_chat_owner: BOT_CONVERSATION_OWNER.chat,
36839
+ request_uid: input.requestUid?.trim() || randomUUID()
36750
36840
  }, session, options.signal);
36751
36841
  } catch (error) {
36752
- if (error instanceof ArkmePluginError && ["arkme-network-error", "arkme-timeout"].includes(error.code)) throw new ArkmePluginError("bot-create-outcome-unknown", "Bot 创建结果未知,请刷新 Bot 列表确认;不会自动重试", false, 409, { cause: error });
36842
+ if (error instanceof ArkmePluginError && ([
36843
+ "arkme-network-error",
36844
+ "arkme-timeout",
36845
+ "arkme-response-invalid",
36846
+ "arkme-code-1002"
36847
+ ].includes(error.code) || error.code === "arkme-http-error" && error.retryable)) throw new ArkmePluginError("bot-create-outcome-unknown", "Bot 创建结果未知,请刷新 Bot 列表确认;不会自动重试", false, 409, { cause: error });
36848
+ if (error instanceof ArkmePluginError && error.code === "arkme-code-1001") throw new ArkmePluginError(error.code, error.message, false, 400, { cause: error });
36753
36849
  throw error;
36754
36850
  }
36755
36851
  try {
@@ -37097,6 +37193,7 @@ var BotService = class {
37097
37193
  const signature = createHmac("sha256", secret).update(`bot-directory-entry-v1:${userId}:${payload}`).digest("base64url");
37098
37194
  return [{
37099
37195
  ...bot,
37196
+ privateChatOutboundEnabled: bot.directChatAvailable && bot.provider === "openclaw",
37100
37197
  botRef: `arkme-bot-directory-entry-v1.${payload}.${signature}`
37101
37198
  }];
37102
37199
  });
@@ -37255,7 +37352,6 @@ var SubjectBotConversationAdapter = class {
37255
37352
  async send(context, contentInput, signal) {
37256
37353
  this.subjectTarget(context);
37257
37354
  const content = contentInput.trim();
37258
- if (context.reference.provider === "webhook") throw new ArkmePluginError("bot-conversation-send-unsupported", "Webhook Bot 仅接收外部系统推送", false, 400);
37259
37355
  if (content === "" || content.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("bot-conversation-content-invalid", "Bot 消息为空或超过长度限制", false, 400);
37260
37356
  let data;
37261
37357
  try {
@@ -37408,6 +37504,7 @@ var BotConversationService = class {
37408
37504
  }
37409
37505
  async send(botRef, content, options = {}) {
37410
37506
  const context = await this.context(botRef);
37507
+ if (context.reference.provider === "webhook") throw new ArkmePluginError("bot-conversation-send-unsupported", "Webhook Bot 仅接收外部系统推送", false, 400);
37411
37508
  const result = await this.adapter(context).send(context, content, options.signal);
37412
37509
  if (context.reference.target.kind === "subject") await this.invalidateRecordProjection();
37413
37510
  return result;
@@ -37636,6 +37733,44 @@ function object(value) {
37636
37733
  function first(source, keys) {
37637
37734
  return keys.map((key) => source[key]).find((value) => value !== void 0 && value !== null && value !== "");
37638
37735
  }
37736
+ /** Flutter-compatible summary templates, resolved on the host before projection. */
37737
+ function renderCallRecordSummary(source, viewerUserId, displayNames = /* @__PURE__ */ new Map()) {
37738
+ const fallback = String(first(source, [
37739
+ "sm",
37740
+ "call_summary",
37741
+ "callSummary",
37742
+ "summary_text",
37743
+ "summaryText",
37744
+ "summary"
37745
+ ]) ?? "").trim();
37746
+ const template = String(first(source, [
37747
+ "smt",
37748
+ "call_summary_template",
37749
+ "callSummaryTemplate",
37750
+ "summary_template",
37751
+ "summaryTemplate"
37752
+ ]) ?? "").trim();
37753
+ const labels = object(first(source, [
37754
+ "ssl",
37755
+ "summary_speaker_labels",
37756
+ "call_summary_speaker_labels"
37757
+ ]));
37758
+ const users = object(first(source, [
37759
+ "ssu",
37760
+ "summary_speaker_user_ids",
37761
+ "call_summary_speaker_user_ids"
37762
+ ]));
37763
+ const rendered = template.replace(/\{\{(user|speaker):([^{}]+)\}\}/g, (token, kind, key) => {
37764
+ const userId = Number(kind === "user" ? key : users[token] ?? users[key]);
37765
+ if (userId > 0 && userId === viewerUserId) return "我";
37766
+ const name = displayNames.get(userId);
37767
+ if (name?.trim()) return name.trim();
37768
+ const label = kind === "speaker" ? labels[token] ?? labels[key] : void 0;
37769
+ return typeof label === "string" && label.trim() ? label.trim() : token;
37770
+ });
37771
+ const text = rendered && !rendered.includes("{{") ? rendered : fallback;
37772
+ return text.includes("{{") ? "" : text;
37773
+ }
37639
37774
  function callRecordCandidates(raw) {
37640
37775
  const root = object(raw);
37641
37776
  const record = object(root.record);
@@ -37769,9 +37904,27 @@ function projectCallRecord(raw, viewerUserId) {
37769
37904
  text = isCaller ? "对方离线" : "未接听(离线)";
37770
37905
  break;
37771
37906
  }
37907
+ const summary = renderCallRecordSummary(source, viewerUserId);
37908
+ const rawStatus = String(first(source, [
37909
+ "ss",
37910
+ "call_summary_status",
37911
+ "summary_status",
37912
+ "summaryStatus"
37913
+ ]) ?? "").toLowerCase();
37914
+ const summaryStatus = [
37915
+ "pending",
37916
+ "processing",
37917
+ "generating"
37918
+ ].includes(rawStatus) ? "pending" : [
37919
+ "done",
37920
+ "success",
37921
+ "finished"
37922
+ ].includes(rawStatus) ? "done" : ["failed", "error"].includes(rawStatus) ? "failed" : void 0;
37772
37923
  return {
37773
37924
  mediaType,
37774
- text
37925
+ text,
37926
+ ...summary && !summary.includes("{{") ? { summaryText: summary } : {},
37927
+ ...summaryStatus ? { summaryStatus } : {}
37775
37928
  };
37776
37929
  }
37777
37930
  //#endregion
@@ -38122,9 +38275,11 @@ function decodeJson(value) {
38122
38275
  var CallHistoryService = class {
38123
38276
  runtime;
38124
38277
  profile;
38125
- constructor(runtime, profile) {
38278
+ media;
38279
+ constructor(runtime, profile, media) {
38126
38280
  this.runtime = runtime;
38127
38281
  this.profile = profile;
38282
+ this.media = media;
38128
38283
  }
38129
38284
  async listCallHistory(options = {}, signal) {
38130
38285
  const session = await this.runtime.requireSession();
@@ -38467,13 +38622,6 @@ var CallHistoryService = class {
38467
38622
  "callResult",
38468
38623
  "result"
38469
38624
  ]);
38470
- const summaryText = safeSummary(firstString$1(raw, [
38471
- "call_summary",
38472
- "callSummary",
38473
- "summary_text",
38474
- "summaryText",
38475
- "summary"
38476
- ]));
38477
38625
  const summaryUpdatedAtMillis = firstEpochMillis(raw, [
38478
38626
  "call_summary_updated_at",
38479
38627
  "callSummaryUpdatedAt",
@@ -38487,7 +38635,7 @@ var CallHistoryService = class {
38487
38635
  "mediaType"
38488
38636
  ]));
38489
38637
  const videoRecord = callVideoRecord(raw, mediaType, session.userId);
38490
- const transcriptSegments = this.transcriptSegments(raw, startedAtMillis).slice(0, 200);
38638
+ const transcriptSegments = this.transcriptSegments(raw, startedAtMillis, session.userId).slice(0, 200);
38491
38639
  const hangupUserId = callResult.toLowerCase().replace(/[_\s-]/g, "") === "normalend" ? listValue$19(raw.member_actions).map(objectValue$2).reverse().find((action) => firstString$1(action, ["action"]).toLowerCase() === "hangup" && firstNumber(action, ["user_id"]) > 0)?.user_id : void 0;
38492
38640
  const participantSeeds = this.participants(raw, session.userId);
38493
38641
  const userIds = [
@@ -38503,6 +38651,7 @@ var CallHistoryService = class {
38503
38651
  ...userId === session.userId ? { isCurrentUser: true } : {}
38504
38652
  });
38505
38653
  const participants = await this.attachParticipantAvatars(participantSeeds.slice(0, 50), session, signal);
38654
+ const summaryText = safeSummary(renderCallRecordSummary(raw, session.userId, new Map(participants.flatMap((participant) => participant.userId ? [[participant.userId, participant.displayName]] : []))));
38506
38655
  const hangupParticipant = participants.find((participant) => participant.userId === numberValue$22(hangupUserId));
38507
38656
  return {
38508
38657
  callRef: await this.sealCallRef({
@@ -38583,7 +38732,7 @@ var CallHistoryService = class {
38583
38732
  };
38584
38733
  }).filter((item) => item !== void 0).slice(0, 50);
38585
38734
  }
38586
- transcriptSegments(raw, startedAtMillis) {
38735
+ transcriptSegments(raw, startedAtMillis, viewerUserId) {
38587
38736
  return listValue$19(firstValue(raw, [
38588
38737
  "segments",
38589
38738
  "transcript_segments",
@@ -38601,6 +38750,13 @@ var CallHistoryService = class {
38601
38750
  "segmentId",
38602
38751
  "id"
38603
38752
  ]) || `segment-${String(index + 1)}`;
38753
+ const remoteAudioUrl = firstString$1(rawItem, ["audio_url", "audioUrl"]);
38754
+ const audioBlock = remoteAudioUrl ? this.media?.forwardContentBlocks([{
38755
+ type: 2,
38756
+ name: "通话片段",
38757
+ download_url: remoteAudioUrl
38758
+ }], viewerUserId).find((block) => block.kind === "audio") : void 0;
38759
+ const audioUrl = audioBlock ? `${this.runtime.config.routePath}/media?ref=${encodeURIComponent(audioBlock.mediaRef)}` : void 0;
38604
38760
  const speakerUserId = Math.trunc(firstNumber(rawItem, [
38605
38761
  "speaker_user_id",
38606
38762
  "speakerUserId",
@@ -38625,6 +38781,7 @@ var CallHistoryService = class {
38625
38781
  const absoluteEnd = end >= 1e11;
38626
38782
  return {
38627
38783
  segmentId,
38784
+ ...audioUrl ? { audioUrl } : {},
38628
38785
  speakerDisplayName: firstString$1(rawItem, [
38629
38786
  "speaker_display_name",
38630
38787
  "speakerDisplayName",
@@ -42673,6 +42830,8 @@ function arkmeRichContentPayload(input, normalizedTextContent, mentionPayload) {
42673
42830
  }
42674
42831
  const OFFICIAL_AUTHOR_USER_ID = 11;
42675
42832
  const OFFICIAL_AUTHOR_FALLBACK_DISPLAY_NAME = "即我作者";
42833
+ const RESERVED_ASEN_BOT_UID = "asen";
42834
+ const RESERVED_ASEN_DISPLAY_NAME = "阿森";
42676
42835
  const MAX_MESSAGE_COPY_LINK_ITEMS = 100;
42677
42836
  const RECORD_UID_PATTERN$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
42678
42837
  const CHAT_MEMBER_REF_PREFIX = "arkme-chat-member-v1";
@@ -43730,7 +43889,7 @@ var ChatService = class {
43730
43889
  callHistory;
43731
43890
  memberEvents;
43732
43891
  favoriteStickerMutationTail = Promise.resolve();
43733
- constructor(runtime, source, profile, media, record, bot, arko, aiPolish, realtime, privacy = new ArkmePrivacyVisibilityService(runtime), messageActions, callHistory = new CallHistoryService(runtime, profile)) {
43892
+ constructor(runtime, source, profile, media, record, bot, arko, aiPolish, realtime, privacy = new ArkmePrivacyVisibilityService(runtime), messageActions, callHistory = new CallHistoryService(runtime, profile, media)) {
43734
43893
  this.runtime = runtime;
43735
43894
  this.source = source;
43736
43895
  this.profile = profile;
@@ -44369,14 +44528,13 @@ var ChatService = class {
44369
44528
  }
44370
44529
  if (source.kind === "topic") {
44371
44530
  const lockedRecordUids = await this.privacy.lockedRecordUids(session, options.signal);
44372
- const data = await this.runtime.authenticatedPost("/api/v1/topics/display/detail", {
44373
- topic_uid: source.ownerRef,
44374
- limit,
44375
- ...options.cursor?.sendAtMillis === void 0 ? {} : { cursor_send_at: options.cursor.sendAtMillis },
44376
- ...options.cursor?.itemUid === void 0 ? {} : { cursor_record_uid: options.cursor.itemUid }
44377
- }, session, options.signal);
44378
- if (arkmePrivacyLockedTopic(data.topic_core)) throw new ArkmePluginError("topic-privacy-locked", "隐私锁主题不能在 Arkme 插件中查看", false, 403);
44379
- const rawRecords = listValue$16(data.records).filter((raw) => !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(this.record.recordUid(raw)));
44531
+ const [page, metadata] = await Promise.all([readTopicRecordPage(this.runtime, session, source.ownerRef, {
44532
+ ...options,
44533
+ limit
44534
+ }), readTopicMetadata(this.runtime, session, source.ownerRef, options.signal)]);
44535
+ if (arkmePrivacyLockedTopic({ privacy_state: page.privacyState }) || metadata.privacyState === 2) throw new ArkmePluginError("topic-privacy-locked", "隐私锁主题不能在 Arkme 插件中查看", false, 403);
44536
+ const topicKind = metadata.topicKind;
44537
+ const rawRecords = page.records.filter((raw) => !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(this.record.recordUid(raw)));
44380
44538
  const media = await this.media.hydrateRecordMediaPage(rawRecords, session, options.signal);
44381
44539
  const signingKey = await this.runtime.stateStore.uniqueCode();
44382
44540
  const records = rawRecords.map((raw) => {
@@ -44388,16 +44546,15 @@ var ChatService = class {
44388
44546
  });
44389
44547
  return this.withRecordTopicAssignmentRef(source, item, session.userId, signingKey, numberValue$19(objectValue$2(raw).owner_user_id), source.ownerRef);
44390
44548
  });
44391
- const nextSendAt = numberValue$19(data.next_cursor_send_at);
44392
- const nextUid = stringValue$2(data.next_cursor_record_uid).trim();
44393
44549
  return {
44394
- source: await this.source.sourceItem(source),
44550
+ source: {
44551
+ ...await this.source.sourceItem(source),
44552
+ topicKind,
44553
+ displayName: arkmeTopicDisplayName(source.displayName, topicKind)
44554
+ },
44395
44555
  items: records.map((item) => this.withRecordMessageActionRef(source, item, session.userId, signingKey)),
44396
- hasMore: data.has_more === true,
44397
- ...nextSendAt > 0 && nextUid !== "" ? { nextCursor: {
44398
- sendAtMillis: nextSendAt,
44399
- itemUid: nextUid
44400
- } } : {}
44556
+ hasMore: page.hasMore,
44557
+ ...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor }
44401
44558
  };
44402
44559
  }
44403
44560
  const aiPolishDecorations = source.kind === "group_chat" && options.cursor === void 0 ? Promise.allSettled([this.aiPolish.queryGroupAiPolishConfig(source.ownerRef, session, options.signal), this.aiPolish.queryGroupAiPolishNotices(source.ownerRef, session, options.signal)]) : void 0;
@@ -44948,13 +45105,21 @@ var ChatService = class {
44948
45105
  parent_record_uid: parentRecordUid,
44949
45106
  limit: 100
44950
45107
  }, session, signal);
44951
- const projections = listValue$16(data.children).flatMap((child) => {
45108
+ const projections = [];
45109
+ for (const child of listValue$16(data.children)) {
44952
45110
  const extension = chatExtensionTreeItemFromData(child, parentRecordUid);
44953
- return extension === void 0 ? [] : [{
44954
- extension,
45111
+ if (extension === void 0) continue;
45112
+ const record = objectValue$2(objectValue$2(objectValue$2(child).item).record);
45113
+ const payload = objectValue$2(record.payload);
45114
+ const mentions = await this.timelineMentionTargets(record, payload, session.userId, chatSessionUid);
45115
+ projections.push({
45116
+ extension: mentions === void 0 ? extension : {
45117
+ ...extension,
45118
+ mentions
45119
+ },
44955
45120
  mediaRecord: chatExtensionMediaRecord(child, extension.recordUid)
44956
- }];
44957
- });
45121
+ });
45122
+ }
44958
45123
  const extensions = (await this.hydrateExtensionMedia(projections, session, signal)).sort((left, right) => right.sendAtMillis - left.sendAtMillis);
44959
45124
  const extensionCount = Math.max(integerLikeValue(data.total), extensions.length);
44960
45125
  return extensionCount <= 0 ? void 0 : {
@@ -45661,22 +45826,30 @@ var ChatService = class {
45661
45826
  const leadingTrim = textFormat === "markdown" ? 0 : rawText.length - rawText.trimStart().length;
45662
45827
  const uniqueRefs = [...new Set(inputs.map((input) => input.botRef.trim()))];
45663
45828
  if (uniqueRefs.some((ref) => ref === "")) throw new ArkmePluginError("bot-mention-ref-invalid", "Bot mention 引用为空", false);
45664
- const references = await Promise.all(uniqueRefs.map(async (ref) => ({
45829
+ const requestedReservedAsen = uniqueRefs.some((ref) => ref.toLowerCase() === RESERVED_ASEN_BOT_UID);
45830
+ const regularRefs = uniqueRefs.filter((ref) => ref.toLowerCase() !== RESERVED_ASEN_BOT_UID);
45831
+ const references = await Promise.all(regularRefs.map(async (ref) => ({
45665
45832
  ref,
45666
45833
  value: await this.bot.openBotRef(ref, session.userId)
45667
45834
  })));
45668
45835
  const allowedByBotId = /* @__PURE__ */ new Map();
45836
+ const reservedAsen = requestedReservedAsen ? {
45837
+ bot_uid: RESERVED_ASEN_BOT_UID,
45838
+ display_name_snapshot: RESERVED_ASEN_DISPLAY_NAME
45839
+ } : void 0;
45669
45840
  if (source.kind === "group_chat") {
45670
- const groupBots = await this.bot.listMentionableGroupBots(source, session, signal);
45671
- const requested = new Set(references.map((item) => item.value.botId));
45672
- for (const { botId, name } of groupBots) {
45673
- if (!requested.has(botId)) continue;
45674
- allowedByBotId.set(botId, {
45675
- bot_uid: botId,
45676
- display_name_snapshot: name
45677
- });
45841
+ if (references.length > 0) {
45842
+ const groupBots = await this.bot.listMentionableGroupBots(source, session, signal);
45843
+ const requested = new Set(references.map((item) => item.value.botId));
45844
+ for (const { botId, name } of groupBots) {
45845
+ if (!requested.has(botId)) continue;
45846
+ allowedByBotId.set(botId, {
45847
+ bot_uid: botId,
45848
+ display_name_snapshot: name
45849
+ });
45850
+ }
45678
45851
  }
45679
- } else {
45852
+ } else if (references.length > 0) {
45680
45853
  const bots = (await this.bot.listBots(signal === void 0 ? {} : { signal })).items;
45681
45854
  const botNameByRef = new Map(bots.filter((bot) => bot.provider === "openclaw").map((bot) => [bot.botRef, bot.name]));
45682
45855
  for (const reference of references) {
@@ -45692,8 +45865,9 @@ var ChatService = class {
45692
45865
  const refToBotId = new Map(references.map((item) => [item.ref, item.value.botId]));
45693
45866
  return [...inputs].sort((left, right) => left.startIndex - right.startIndex).map((input) => {
45694
45867
  const botRef = input.botRef.trim();
45695
- const botId = refToBotId.get(botRef);
45696
- const bot = botId === void 0 ? void 0 : allowedByBotId.get(botId);
45868
+ const isReservedAsen = botRef.toLowerCase() === RESERVED_ASEN_BOT_UID;
45869
+ const botId = isReservedAsen ? void 0 : refToBotId.get(botRef);
45870
+ const bot = isReservedAsen ? reservedAsen : botId === void 0 ? void 0 : allowedByBotId.get(botId);
45697
45871
  if (bot === void 0) throw new ArkmePluginError("bot-mention-not-available", "所选 Bot 当前不可 mention", false, 409);
45698
45872
  const startIndex = Math.trunc(input.startIndex) - leadingTrim;
45699
45873
  const length = Math.trunc(input.length);
@@ -45708,29 +45882,48 @@ var ChatService = class {
45708
45882
  });
45709
45883
  }
45710
45884
  async groupBotMentionSend(sourceRef, source, text, botRefs, recordUid, relationUid, session, signal, options = {}) {
45711
- const uniqueRefs = new Set(botRefs.map((ref) => ref.trim()));
45885
+ const normalizedRefs = botRefs.map((ref) => {
45886
+ const normalized = ref.trim();
45887
+ return normalized.toLowerCase() === RESERVED_ASEN_BOT_UID ? RESERVED_ASEN_BOT_UID : normalized;
45888
+ });
45889
+ const uniqueRefs = new Set(normalizedRefs);
45712
45890
  if (uniqueRefs.has("") || uniqueRefs.size !== botRefs.length) throw new ArkmePluginError("bot-mention-ref-invalid", "Bot mention 引用为空或重复", false);
45713
- const references = await Promise.all([...uniqueRefs].map(async (ref) => ({
45891
+ const requestedReservedAsen = [...uniqueRefs].some((ref) => ref.toLowerCase() === RESERVED_ASEN_BOT_UID);
45892
+ const regularRefs = [...uniqueRefs].filter((ref) => ref.toLowerCase() !== RESERVED_ASEN_BOT_UID);
45893
+ const references = await Promise.all(regularRefs.map(async (ref) => ({
45714
45894
  ref,
45715
45895
  value: await this.bot.openBotRef(ref, session.userId)
45716
45896
  })));
45717
45897
  const requestedById = new Map(references.map((item) => [item.value.botId, item.ref]));
45718
45898
  if (requestedById.size !== references.length) throw new ArkmePluginError("bot-mention-ref-invalid", "Bot mention 引用重复", false);
45719
- const groupBots = await this.bot.listMentionableGroupBots(source, session, signal);
45720
45899
  const mentions = [];
45721
45900
  let visibleText = "";
45722
- for (const { botId, name } of groupBots) {
45723
- if (!requestedById.has(botId)) continue;
45724
- const display = `@${name}`;
45901
+ if (requestedById.size > 0) {
45902
+ const groupBots = await this.bot.listMentionableGroupBots(source, session, signal);
45903
+ for (const { botId, name } of groupBots) {
45904
+ if (!requestedById.has(botId)) continue;
45905
+ const display = `@${name}`;
45906
+ const startIndex = visibleText.length;
45907
+ visibleText += `${display} `;
45908
+ mentions.push({
45909
+ bot_uid: botId,
45910
+ display_name_snapshot: name,
45911
+ start_index: startIndex,
45912
+ length: display.length
45913
+ });
45914
+ requestedById.delete(botId);
45915
+ }
45916
+ }
45917
+ if (requestedReservedAsen) {
45918
+ const display = `@${RESERVED_ASEN_DISPLAY_NAME}`;
45725
45919
  const startIndex = visibleText.length;
45726
45920
  visibleText += `${display} `;
45727
45921
  mentions.push({
45728
- bot_uid: botId,
45729
- display_name_snapshot: name,
45922
+ bot_uid: RESERVED_ASEN_BOT_UID,
45923
+ display_name_snapshot: RESERVED_ASEN_DISPLAY_NAME,
45730
45924
  start_index: startIndex,
45731
45925
  length: display.length
45732
45926
  });
45733
- requestedById.delete(botId);
45734
45927
  }
45735
45928
  if (requestedById.size > 0) throw new ArkmePluginError("bot-mention-not-installed", "所选 Bot 未安装到该群聊", false, 409);
45736
45929
  visibleText += text;
@@ -46186,6 +46379,51 @@ var ChatService = class {
46186
46379
  async chatSourceFromBundle(bundle, session, cached, timelineItems) {
46187
46380
  return await this.source.chatSourceFromBundle(bundle, session, cached, timelineItems);
46188
46381
  }
46382
+ async timelineMentionTargets(record, payload, viewerUserId, chatSessionUid) {
46383
+ const metadata = arkmeMentionMetadataFromRecord(record, payload);
46384
+ const targets = [];
46385
+ for (const rawMention of listValue$16(metadata.human_mentions ?? metadata.humanMentions)) {
46386
+ const mention = objectValue$2(rawMention);
46387
+ const startIndex = integerLikeValue(mention.start_index ?? mention.startIndex);
46388
+ const length = integerLikeValue(mention.length);
46389
+ const displayName = stringValue$2(mention.display_name_snapshot ?? mention.displayNameSnapshot).trim();
46390
+ const userId = integerLikeValue(mention.user_id ?? mention.userId);
46391
+ if (startIndex < 0 || length < 2 || displayName === "") continue;
46392
+ if (userId === 0) {
46393
+ targets.push({
46394
+ kind: "all",
46395
+ startIndex,
46396
+ length,
46397
+ displayName
46398
+ });
46399
+ continue;
46400
+ }
46401
+ if (userId > 0) targets.push({
46402
+ kind: "member",
46403
+ startIndex,
46404
+ length,
46405
+ displayName,
46406
+ memberRef: await this.sealChatMemberRef(viewerUserId, chatSessionUid, userId)
46407
+ });
46408
+ }
46409
+ for (const rawMention of listValue$16(metadata.bot_mentions ?? metadata.botMentions)) {
46410
+ const mention = objectValue$2(rawMention);
46411
+ const startIndex = integerLikeValue(mention.start_index ?? mention.startIndex);
46412
+ const length = integerLikeValue(mention.length);
46413
+ const displayName = stringValue$2(mention.display_name_snapshot ?? mention.displayNameSnapshot).trim();
46414
+ const botRef = stringValue$2(mention.bot_uid ?? mention.botUid).trim();
46415
+ if (startIndex < 0 || length < 2 || displayName === "" || botRef === "") continue;
46416
+ targets.push({
46417
+ kind: "bot",
46418
+ startIndex,
46419
+ length,
46420
+ displayName,
46421
+ botRef
46422
+ });
46423
+ }
46424
+ targets.sort((left, right) => left.startIndex - right.startIndex);
46425
+ return targets.length === 0 ? void 0 : targets;
46426
+ }
46189
46427
  async chatTimelineItems(data, session, chatSessionUid, projectedSourceKind) {
46190
46428
  const items = [];
46191
46429
  const signingKey = await this.runtime.stateStore.uniqueCode();
@@ -46220,6 +46458,7 @@ var ChatService = class {
46220
46458
  const conversationPreview = arkmeChatConversationPreview(item);
46221
46459
  const callRecord = await this.callHistory.timelineCallRecord(item, session.userId);
46222
46460
  const senderName = stringValue$2(relation.display_name_snapshot).trim() || "Arkme用户";
46461
+ const mentions = await this.timelineMentionTargets(record, payload, session.userId, chatSessionUid);
46223
46462
  const mentionsViewer = senderUserId !== session.userId && arkmeMentionMetadataMentionsViewer(record, payload, session.userId);
46224
46463
  items.push({
46225
46464
  itemUid: uid,
@@ -46254,6 +46493,7 @@ var ChatService = class {
46254
46493
  isMe: senderUserId === session.userId,
46255
46494
  ...callRecord === void 0 ? {} : { callRecord },
46256
46495
  ...mentionsViewer ? { mentionsViewer: true } : {},
46496
+ ...mentions === void 0 ? {} : { mentions },
46257
46497
  sendAtMillis,
46258
46498
  title: stringValue$2(payload.title),
46259
46499
  textContent: stringValue$2(payload.text_content),
@@ -46856,6 +47096,7 @@ var ChatService = class {
46856
47096
  const contentBlocks = this.media.richContentBlocks(item, session.userId);
46857
47097
  const extensionProjection = this.timelineExtensionProjection(item, session.userId);
46858
47098
  const senderName = stringValue$2(relation.display_name_snapshot).trim() || "Arkme用户";
47099
+ const mentions = await this.timelineMentionTargets(record, payload, session.userId, source.ownerRef);
46859
47100
  const captureContext = timelineCaptureContext({
46860
47101
  ...record,
46861
47102
  ...payload
@@ -46894,6 +47135,7 @@ var ChatService = class {
46894
47135
  ...agentSource === void 0 ? {} : { agentSource },
46895
47136
  isMe: senderUserId === session.userId,
46896
47137
  ...callRecord === void 0 ? {} : { callRecord },
47138
+ ...mentions === void 0 ? {} : { mentions },
46897
47139
  sendAtMillis,
46898
47140
  title: stringValue$2(payload.title),
46899
47141
  textContent: stringValue$2(payload.text_content),
@@ -78979,6 +79221,10 @@ function allowedSignedImageHost$2(environment, hostname) {
78979
79221
  function allowedSignedAudioHost(environment, hostname) {
78980
79222
  return (environment === "prod" ? ["jotmo-useraudio.oss-cn-hangzhou.aliyuncs.com"] : ["jotmo-useraudio-test.oss-cn-hangzhou.aliyuncs.com"]).includes(hostname.toLowerCase());
78981
79223
  }
79224
+ /** Call transcript clips use a separate COS bucket, not the user-audio OSS bucket. */
79225
+ function allowedSignedCallAudioHost(environment, hostname) {
79226
+ return environment === "prod" && hostname.toLowerCase() === "webrtc-record-prod-1403070603.cos.ap-shanghai.myqcloud.com";
79227
+ }
78982
79228
  /** Record audio can originate from the dedicated audio or generic file bucket. */
78983
79229
  function allowedSignedRecordAudioHost(environment, hostname) {
78984
79230
  return allowedSignedAudioHost(environment, hostname) || allowedSignedImageHost$2(environment, hostname);
@@ -79248,7 +79494,7 @@ var MediaService = class {
79248
79494
  }
79249
79495
  if (originalOnly && descriptor.purpose !== "original") throw new ArkmePluginError("original-unavailable", "原文件不可用,请刷新后重试;不会用预览图代替原文件", false, 404);
79250
79496
  const url = new URL(descriptor.remoteUrl);
79251
- if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || !(allowedSignedImageHost$2(this.runtime.config.environment, url.hostname) || allowedSignedAudioHost(this.runtime.config.environment, url.hostname))) throw new ArkmePluginError("media-host-rejected", "媒体来源不受信任", false, 403);
79497
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "" || url.hash !== "" || !(allowedSignedImageHost$2(this.runtime.config.environment, url.hostname) || allowedSignedAudioHost(this.runtime.config.environment, url.hostname) || allowedSignedCallAudioHost(this.runtime.config.environment, url.hostname))) throw new ArkmePluginError("media-host-rejected", "媒体来源不受信任", false, 403);
79252
79498
  const response = await this.runtime.fetchImpl(url, {
79253
79499
  headers: range === void 0 ? {} : { Range: range },
79254
79500
  redirect: "error",
@@ -79745,7 +79991,7 @@ var MediaService = class {
79745
79991
  const value = safeHttpsUrl(raw);
79746
79992
  if (value === void 0) return void 0;
79747
79993
  const url = new URL(value);
79748
- return url.port === "" && url.hash === "" && (allowedSignedImageHost$2(this.runtime.config.environment, url.hostname) || allowedSignedAudioHost(this.runtime.config.environment, url.hostname)) ? value : void 0;
79994
+ return url.port === "" && url.hash === "" && (allowedSignedImageHost$2(this.runtime.config.environment, url.hostname) || allowedSignedAudioHost(this.runtime.config.environment, url.hostname) || allowedSignedCallAudioHost(this.runtime.config.environment, url.hostname)) ? value : void 0;
79749
79995
  };
79750
79996
  const downloadUrl = trustedUrl(file.download_url ?? file.downloadUrl);
79751
79997
  const previewUrl = trustedUrl(file.preview_url ?? file.previewUrl);
@@ -84236,10 +84482,6 @@ var RelatedRecordingService = class {
84236
84482
  return `arkme-shared-recording-detail-v1.${encoded}.${createHmac("sha256", signingKey).update(encoded).digest("base64url")}`;
84237
84483
  }
84238
84484
  };
84239
- function isDshAgentInputSourceTitle(value) {
84240
- const normalized = value?.trim().toLowerCase();
84241
- return normalized === "dsh agent input" || normalized === "DSH Agent 输入".toLowerCase();
84242
- }
84243
84485
  //#endregion
84244
84486
  //#region src/services/search-service.ts
84245
84487
  function numberValue$2(value) {
@@ -84624,7 +84866,6 @@ var SearchService = class {
84624
84866
  const linkMatch = textContent.match(/https:\/\/[^\s<>()]+/u);
84625
84867
  const sourceTitle = stringValue$2(topic.title ?? chat.title).trim();
84626
84868
  const creationSource = Math.trunc(numberValue$2(core.creation_source ?? item.creation_source));
84627
- const normalizedCreationSource = creationSource > 0 ? creationSource : isDshAgentInputSourceTitle(sourceTitle) ? 3 : 0;
84628
84869
  return {
84629
84870
  recordUid,
84630
84871
  sourceKind: Math.trunc(numberValue$2(item.source_kind)),
@@ -84638,7 +84879,7 @@ var SearchService = class {
84638
84879
  ...stringValue$2(core.nickname).trim() === "" ? {} : { nickname: stringValue$2(core.nickname).trim() },
84639
84880
  ...numberValue$2(core.template_kind) <= 0 ? {} : { templateKind: Math.trunc(numberValue$2(core.template_kind)) },
84640
84881
  ...numberValue$2(core.display_kind) <= 0 ? {} : { displayKind: Math.trunc(numberValue$2(core.display_kind)) },
84641
- ...normalizedCreationSource <= 0 ? {} : { creationSource: normalizedCreationSource },
84882
+ ...creationSource <= 0 ? {} : { creationSource },
84642
84883
  ...sourceTitle === "" ? {} : { sourceTitle },
84643
84884
  media,
84644
84885
  files,
@@ -88296,7 +88537,7 @@ var ArkmeService = class {
88296
88537
  this.calendar = new CalendarService(this.runtime, this.privacy);
88297
88538
  this.wechat = new WechatService(this.runtime);
88298
88539
  this.profile = new ProfileService(this.runtime);
88299
- this.callHistory = new CallHistoryService(this.runtime, this.profile);
88540
+ this.callHistory = new CallHistoryService(this.runtime, this.profile, { forwardContentBlocks: (files, viewerUserId) => this.media.forwardContentBlocks(files, viewerUserId) });
88300
88541
  this.extensionReview = new ExtensionReviewService(this.runtime, this.profile, { createTextForConversation: async (recordUid, textContent) => {
88301
88542
  return await this.createTextForConversation(recordUid, textContent);
88302
88543
  } });
@@ -88635,6 +88876,7 @@ var ArkmeService = class {
88635
88876
  imageLibrary: true,
88636
88877
  sourceDirectory: true,
88637
88878
  localFirstDirectory: true,
88879
+ topicHomeVisibility: true,
88638
88880
  contactDirectoryReads: true,
88639
88881
  sourceTimeline: true,
88640
88882
  forwardContent: true,
@@ -88992,6 +89234,11 @@ var ArkmeService = class {
88992
89234
  async renameTopic(sourceRef, title) {
88993
89235
  return await this.source.renameTopic(sourceRef, title);
88994
89236
  }
89237
+ async topicHomeVisibility(sourceRef, showInHome, signal) {
89238
+ const result = await this.source.topicHomeVisibility(sourceRef, showInHome, signal);
89239
+ if (showInHome !== void 0) await this.realtime.invalidateRecordProjection();
89240
+ return result;
89241
+ }
88995
89242
  async dissolveTopic(sourceRef, parentSourceRef, childSourceRefs, requestId, expectedRecordCount) {
88996
89243
  return await this.source.dissolveTopic(sourceRef, parentSourceRef, childSourceRefs, requestId, expectedRecordCount);
88997
89244
  }
@@ -89301,6 +89548,23 @@ var ArkmeService = class {
89301
89548
  async sendSourceRich(sourceRef, input, options = {}) {
89302
89549
  return await this.chat.sendSourceRich(sourceRef, input, options);
89303
89550
  }
89551
+ async requireRecentEmojiAccount(accountKey, signal) {
89552
+ signal?.throwIfAborted();
89553
+ const { userId } = await this.runtime.requireSession();
89554
+ signal?.throwIfAborted();
89555
+ if (accountKey !== `${this.config.environment}:${userId}`) throw new ArkmePluginError("emoji-recent-account-changed", "登录账号已变化,请重新打开表情面板", false, 409);
89556
+ }
89557
+ async recentEmojiIds(accountKey, signal) {
89558
+ await this.requireRecentEmojiAccount(accountKey, signal);
89559
+ signal?.throwIfAborted();
89560
+ return await this.stateStore.recentEmojiIds(accountKey);
89561
+ }
89562
+ async recordRecentEmoji(accountKey, emojiId, signal) {
89563
+ await this.requireRecentEmojiAccount(accountKey, signal);
89564
+ signal?.throwIfAborted();
89565
+ if (!isRecentEmojiId(emojiId)) throw new ArkmePluginError("emoji-recent-invalid", "表情不存在", false, 400);
89566
+ return await this.stateStore.recordRecentEmoji(accountKey, emojiId);
89567
+ }
89304
89568
  async favoriteStickers(signal) {
89305
89569
  return await this.chat.favoriteStickers(signal);
89306
89570
  }
@@ -91230,4 +91494,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
91230
91494
  return jsonValue(await handler(jsonValue(args)));
91231
91495
  }
91232
91496
  //#endregion
91233
- export { ARKME_DEFAULT_SHARE_WEBSITE as $, inspectNativeBundleArtifactV3 as A, __exportAll as At, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, MAX_RECORDING_IMPORT_BYTES as Ct, bundleSha256 as D, createDesktopLifecycleReader as Dt, arkmeSandboxEntryId as E, retryAfterMillis as Et, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, arkmeRequiredLinkMetadataFallback as J, arkmeExtensionShareRefFromLink as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, __toCommonJS as Mt, readNativeBundleTarballV3 as N, canonicalBundleJson as O, __commonJSMin as Ot, renderArkmeSandboxHostEntry as P, parseArkmeRecordReeditAttachments as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, sameRecordingImportIdentity as St, ArkmeBundleArtifactError as T, ArkmePluginError as Tt, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, arkmeRichBackgroundSound as X, arkmeFileBackgroundSound as Y, ArkmeRecordReeditDraftConflict as Z, arkmeClientContentDigest as _, SecretValue as _t, persistentArkmeExtensionActive as a, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as at, verifyExtensionResolutionSignature as b, isUnresolvedRecordingImportJob as bt, persistentExtensionBundleDirectory as c, mergeMemberJoinEvents as ct, readPersistentExtensionActivation as d, ARKME_TOOL_FILE_MAX_BYTES as dt, ARKME_PROVIDER_CONTRACT_VERSION as et, refreshPersistentClientWrapper as f, arkmeCanInlineLocalFile as ft, activeProfileExtensionOwnerConflicts as g, ArkmeOutgoingCallError as gt, renderArkmeBundleClientBundle as h, isArkmeBotAvatarRef as ht, invokePersistentArkmeExtension as i, ARKME_WORLD_PUBLISH_MAX_IMAGES as it, packBundleFiles as j, __require as jt, inspectBundleArtifact as k, __esmMin as kt, persistentExtensionPackageIdentity as l, validateMemberUpdate as lt, writePersistentExtensionActivation as m, arkmePickedFileKind as mt, applyPersistentArkmeHostExtension as n, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as nt, persistentArkmeExtensionRuntimeState as o, applyMemberUpdate as ot, repairPersistentExtensionInstallation as p, arkmeNormalizedFileMimeType as pt, arkmeIsGenericLinkMetadataTitle as q, deactivatePersistentArkmeExtension as r, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as rt, materializePersistentExtensionBundle as s, cachedMemberItem as st, activatePersistentArkmeExtension as t, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as tt, quarantinePersistentExtension as u, ARKME_RUNTIME_INSTANCE_ID as ut, arkmeClientContentDigestFromSource as v, ArkmeDirectMessageAdmissionError as vt, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, recordingImportFileNameKey as wt, verifyNativeBundleResolutionSignature as x, recordingImportFileKind as xt, verifyBundleResolutionSignature as y, isRecordingInstantOnOrAfterUnixEpoch as yt, ARKME_EXTENSION_FORMAT as z };
91497
+ export { ARKME_DEFAULT_SHARE_WEBSITE as $, inspectNativeBundleArtifactV3 as A, __commonJSMin as At, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, MAX_RECORDING_IMPORT_BYTES as Ct, bundleSha256 as D, ArkmePluginError as Dt, arkmeSandboxEntryId as E, normalizeRecentEmojiIds as Et, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, arkmeRequiredLinkMetadataFallback as J, arkmeExtensionShareRefFromLink as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, __exportAll as Mt, readNativeBundleTarballV3 as N, __require as Nt, canonicalBundleJson as O, retryAfterMillis as Ot, renderArkmeSandboxHostEntry as P, __toCommonJS as Pt, parseArkmeRecordReeditAttachments as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, sameRecordingImportIdentity as St, ArkmeBundleArtifactError as T, isRecentEmojiId as Tt, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, arkmeRichBackgroundSound as X, arkmeFileBackgroundSound as Y, ArkmeRecordReeditDraftConflict as Z, arkmeClientContentDigest as _, SecretValue as _t, persistentArkmeExtensionActive as a, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as at, verifyExtensionResolutionSignature as b, isUnresolvedRecordingImportJob as bt, persistentExtensionBundleDirectory as c, mergeMemberJoinEvents as ct, readPersistentExtensionActivation as d, ARKME_TOOL_FILE_MAX_BYTES as dt, ARKME_PROVIDER_CONTRACT_VERSION as et, refreshPersistentClientWrapper as f, arkmeCanInlineLocalFile as ft, activeProfileExtensionOwnerConflicts as g, ArkmeOutgoingCallError as gt, renderArkmeBundleClientBundle as h, isArkmeBotAvatarRef as ht, invokePersistentArkmeExtension as i, ARKME_WORLD_PUBLISH_MAX_IMAGES as it, packBundleFiles as j, __esmMin as jt, inspectBundleArtifact as k, createDesktopLifecycleReader as kt, persistentExtensionPackageIdentity as l, validateMemberUpdate as lt, writePersistentExtensionActivation as m, arkmePickedFileKind as mt, applyPersistentArkmeHostExtension as n, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as nt, persistentArkmeExtensionRuntimeState as o, applyMemberUpdate as ot, repairPersistentExtensionInstallation as p, arkmeNormalizedFileMimeType as pt, arkmeIsGenericLinkMetadataTitle as q, deactivatePersistentArkmeExtension as r, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as rt, materializePersistentExtensionBundle as s, cachedMemberItem as st, activatePersistentArkmeExtension as t, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as tt, quarantinePersistentExtension as u, ARKME_RUNTIME_INSTANCE_ID as ut, arkmeClientContentDigestFromSource as v, ArkmeDirectMessageAdmissionError as vt, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, recordingImportFileNameKey as wt, verifyNativeBundleResolutionSignature as x, recordingImportFileKind as xt, verifyBundleResolutionSignature as y, isRecordingInstantOnOrAfterUnixEpoch as yt, ARKME_EXTENSION_FORMAT as z };