@senguoyun/dsh-arkme 0.1.50 → 0.1.52

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 (132) hide show
  1. package/docs/dsh-input-system-topic.md +26 -0
  2. package/lib/client.js +4430 -3115
  3. package/lib/client.js.map +1 -1
  4. package/lib/extension-profile-restart-helper.js +10 -6
  5. package/lib/harness-model-client.js +8 -0
  6. package/lib/index.js +99 -57
  7. package/lib/persistent-extension.js +1 -1
  8. package/lib/{persistent-runtime-YkrM3K7s.js → persistent-runtime-BRy2sNmS.js} +312 -81
  9. package/lib/{plugin-updater-helper-B0yVav4f.js → plugin-updater-helper-n8JrMPum.js} +1 -1
  10. package/lib/{plugin-updater-helper-BbjKUV9T.js → plugin-updater-helper-y7wzJLOx.js} +11 -4
  11. package/lib/plugin-updater-helper.js +1 -1
  12. package/lib/sdk.js +8 -0
  13. package/lib/types/arkme-service.d.ts +3 -0
  14. package/lib/types/arkme-service.d.ts.map +1 -1
  15. package/lib/types/call-record-presentation.d.ts +2 -0
  16. package/lib/types/call-record-presentation.d.ts.map +1 -1
  17. package/lib/types/client/ArkmeArkoSurface.d.ts.map +1 -1
  18. package/lib/types/client/ArkmeBotConversationSurface.d.ts.map +1 -1
  19. package/lib/types/client/ArkmeBotCreateDialog.d.ts.map +1 -1
  20. package/lib/types/client/ArkmeCallDetailContent.d.ts +13 -0
  21. package/lib/types/client/ArkmeCallDetailContent.d.ts.map +1 -0
  22. package/lib/types/client/ArkmeCallDetailDrawer.d.ts +2 -1
  23. package/lib/types/client/ArkmeCallDetailDrawer.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeCallRecordContent.d.ts +4 -2
  25. package/lib/types/client/ArkmeCallRecordContent.d.ts.map +1 -1
  26. package/lib/types/client/ArkmeCallSurface.d.ts.map +1 -1
  27. package/lib/types/client/ArkmeComposerSendButton.d.ts +10 -0
  28. package/lib/types/client/ArkmeComposerSendButton.d.ts.map +1 -0
  29. package/lib/types/client/ArkmeDshAgentInputMarker.d.ts +1 -1
  30. package/lib/types/client/ArkmeDshAgentInputMarker.d.ts.map +1 -1
  31. package/lib/types/client/ArkmeLinkText.d.ts +1 -1
  32. package/lib/types/client/ArkmeLinkText.d.ts.map +1 -1
  33. package/lib/types/client/ArkmeLogin.d.ts.map +1 -1
  34. package/lib/types/client/ArkmeMarketplace.d.ts +1 -0
  35. package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -1
  36. package/lib/types/client/ArkmeMentionSuggestionRow.d.ts +20 -0
  37. package/lib/types/client/ArkmeMentionSuggestionRow.d.ts.map +1 -0
  38. package/lib/types/client/ArkmeMessageActions.d.ts.map +1 -1
  39. package/lib/types/client/ArkmeNoteDetails.d.ts +6 -2
  40. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  41. package/lib/types/client/ArkmeOfficialAuthorGuide.d.ts +7 -0
  42. package/lib/types/client/ArkmeOfficialAuthorGuide.d.ts.map +1 -0
  43. package/lib/types/client/ArkmePersistentShell.d.ts +1 -1
  44. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  45. package/lib/types/client/ArkmeRichContent.d.ts +2 -1
  46. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  47. package/lib/types/client/ArkmeRichText.d.ts +9 -5
  48. package/lib/types/client/ArkmeRichText.d.ts.map +1 -1
  49. package/lib/types/client/ArkmeScanGuide.d.ts +5 -0
  50. package/lib/types/client/ArkmeScanGuide.d.ts.map +1 -0
  51. package/lib/types/client/ArkmeSidebar.d.ts +3 -35
  52. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  53. package/lib/types/client/ArkmeSourceBreadcrumb.d.ts.map +1 -1
  54. package/lib/types/client/ArkmeTopicDirectoryPopover.d.ts +2 -1
  55. package/lib/types/client/ArkmeTopicDirectoryPopover.d.ts.map +1 -1
  56. package/lib/types/client/ArkmeTopicReadOnlyNotice.d.ts +3 -0
  57. package/lib/types/client/ArkmeTopicReadOnlyNotice.d.ts.map +1 -0
  58. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +13 -7
  59. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  60. package/lib/types/client/api.d.ts +1 -1
  61. package/lib/types/client/api.d.ts.map +1 -1
  62. package/lib/types/client/arkme-login-locales.d.ts +13 -0
  63. package/lib/types/client/arkme-login-locales.d.ts.map +1 -1
  64. package/lib/types/client/call-detail-presentation.d.ts +13 -0
  65. package/lib/types/client/call-detail-presentation.d.ts.map +1 -0
  66. package/lib/types/client/chat-directory-store.d.ts.map +1 -1
  67. package/lib/types/client/composer-draft-store.d.ts +7 -0
  68. package/lib/types/client/composer-draft-store.d.ts.map +1 -1
  69. package/lib/types/client/conversation-composer-presentation.d.ts +3 -0
  70. package/lib/types/client/conversation-composer-presentation.d.ts.map +1 -1
  71. package/lib/types/client/conversation-resize-anchor.d.ts +1 -1
  72. package/lib/types/client/conversation-resize-anchor.d.ts.map +1 -1
  73. package/lib/types/client/index.d.ts +2 -1
  74. package/lib/types/client/index.d.ts.map +1 -1
  75. package/lib/types/client/mention-candidates.d.ts +46 -0
  76. package/lib/types/client/mention-candidates.d.ts.map +1 -0
  77. package/lib/types/client/navigation-cache.d.ts.map +1 -1
  78. package/lib/types/client/read-deadline.d.ts +3 -0
  79. package/lib/types/client/read-deadline.d.ts.map +1 -0
  80. package/lib/types/client/realtime-client-events.d.ts.map +1 -1
  81. package/lib/types/client/realtime-connection.d.ts +9 -0
  82. package/lib/types/client/realtime-connection.d.ts.map +1 -0
  83. package/lib/types/client/recordings/RecordingTranscriptForward.d.ts.map +1 -1
  84. package/lib/types/client/scan-guide-icons.d.ts +7 -0
  85. package/lib/types/client/scan-guide-icons.d.ts.map +1 -0
  86. package/lib/types/client/scan-guide-styles.d.ts +2 -0
  87. package/lib/types/client/scan-guide-styles.d.ts.map +1 -0
  88. package/lib/types/client/slots-contract.d.ts +23 -0
  89. package/lib/types/client/slots-contract.d.ts.map +1 -1
  90. package/lib/types/client/source-tree.d.ts.map +1 -1
  91. package/lib/types/client/use-call-record-preview.d.ts +8 -0
  92. package/lib/types/client/use-call-record-preview.d.ts.map +1 -0
  93. package/lib/types/client/use-call-transcript-playback.d.ts +13 -0
  94. package/lib/types/client/use-call-transcript-playback.d.ts.map +1 -0
  95. package/lib/types/dsh-agent-input-source.d.ts +0 -1
  96. package/lib/types/dsh-agent-input-source.d.ts.map +1 -1
  97. package/lib/types/extensions/profile-restart-helper.d.ts.map +1 -1
  98. package/lib/types/host-api.d.ts.map +1 -1
  99. package/lib/types/index.d.ts.map +1 -1
  100. package/lib/types/plugin-updater-helper.d.ts.map +1 -1
  101. package/lib/types/realtime-events.d.ts +9 -5
  102. package/lib/types/realtime-events.d.ts.map +1 -1
  103. package/lib/types/sdk/index.d.ts +4 -0
  104. package/lib/types/sdk/index.d.ts.map +1 -1
  105. package/lib/types/services/bot-conversation-service.d.ts.map +1 -1
  106. package/lib/types/services/bot-service.d.ts.map +1 -1
  107. package/lib/types/services/call-history-service.d.ts +3 -1
  108. package/lib/types/services/call-history-service.d.ts.map +1 -1
  109. package/lib/types/services/chat-service.d.ts +1 -0
  110. package/lib/types/services/chat-service.d.ts.map +1 -1
  111. package/lib/types/services/media-service.d.ts.map +1 -1
  112. package/lib/types/services/search-service.d.ts.map +1 -1
  113. package/lib/types/services/source-service.d.ts +4 -0
  114. package/lib/types/services/source-service.d.ts.map +1 -1
  115. package/lib/types/services/topic-metadata.d.ts +10 -0
  116. package/lib/types/services/topic-metadata.d.ts.map +1 -0
  117. package/lib/types/services/topic-record-page.d.ts +18 -0
  118. package/lib/types/services/topic-record-page.d.ts.map +1 -0
  119. package/lib/types/tools/business/conversation/index.d.ts.map +1 -1
  120. package/lib/types/tools/business/conversation/topic-home-visibility.d.ts +2 -0
  121. package/lib/types/tools/business/conversation/topic-home-visibility.d.ts.map +1 -0
  122. package/lib/types/tools/business/index.d.ts.map +1 -1
  123. package/lib/types/tools/ports/bots.d.ts +1 -0
  124. package/lib/types/tools/ports/bots.d.ts.map +1 -1
  125. package/lib/types/tools/ports/conversations.d.ts +3 -0
  126. package/lib/types/tools/ports/conversations.d.ts.map +1 -1
  127. package/lib/types/tools/registry/registrar.d.ts.map +1 -1
  128. package/lib/types/topic-policy.d.ts +9 -0
  129. package/lib/types/topic-policy.d.ts.map +1 -0
  130. package/lib/types/types.d.ts +22 -1
  131. package/lib/types/types.d.ts.map +1 -1
  132. package/package.json +5 -5
@@ -15558,6 +15558,48 @@ async function patchChatPolicy(runtime, session, chatSessionUid, patch, signal)
15558
15558
  return updated;
15559
15559
  }
15560
15560
  //#endregion
15561
+ //#region src/services/topic-record-page.ts
15562
+ /** Transport contract shared by timeline reading and complete pre-dissolution enumeration.
15563
+ * Privacy visibility and mutation policy remain in their respective business owners.
15564
+ */
15565
+ async function readTopicRecordPage(runtime, session, topicUid, options) {
15566
+ const data = await runtime.authenticatedPost("/api/v1/topics/display/records/page", {
15567
+ topic_uid: topicUid,
15568
+ limit: options.limit,
15569
+ ...options.cursor === void 0 ? {} : {
15570
+ cursor_send_at: options.cursor.sendAtMillis,
15571
+ cursor_record_uid: options.cursor.itemUid
15572
+ }
15573
+ }, session, options.signal);
15574
+ const sendAt = data.next_cursor_send_at;
15575
+ const uid = stringValue$2(data.next_cursor_record_uid).trim();
15576
+ const hasCursor = typeof sendAt === "number" && Number.isSafeInteger(sendAt) && sendAt > 0 && uid !== "";
15577
+ 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);
15578
+ 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);
15579
+ return {
15580
+ records: data.records,
15581
+ privacyState: data.privacy_state,
15582
+ hasMore: data.has_more,
15583
+ ...hasCursor ? { nextCursor: {
15584
+ sendAtMillis: sendAt,
15585
+ itemUid: uid
15586
+ } } : {}
15587
+ };
15588
+ }
15589
+ //#endregion
15590
+ //#region src/services/topic-metadata.ts
15591
+ /** Lightweight topic identity/settings contract. It never loads records or summary statistics. */
15592
+ async function readTopicMetadata(runtime, session, topicUid, signal) {
15593
+ const core = objectValue$2((await runtime.authenticatedPost("/api/v1/topics/display/metadata", { topic_uid: topicUid }, session, signal)).topic_core);
15594
+ const topicKind = typeof core.kind === "number" ? core.kind : 0;
15595
+ 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);
15596
+ return {
15597
+ topicKind,
15598
+ privacyState: core.privacy_state,
15599
+ showInHome: core.show_in_home
15600
+ };
15601
+ }
15602
+ //#endregion
15561
15603
  //#region node_modules/bail/index.js
15562
15604
  /**
15563
15605
  * Throw a given error.
@@ -35063,6 +35105,17 @@ function arkmeEmojiPlainText(value) {
35063
35105
  if (value === "" || !value.includes("_emoji:")) return value;
35064
35106
  return arkmeEmojiTextRuns(value).map((run) => run.kind === "emoji" ? run.emoji.unicode : run.text).join("");
35065
35107
  }
35108
+ const ARKME_DSH_INPUT_TOPIC_TITLE = "发给 DSH 的消息";
35109
+ /** Display only; source identities and stored titles remain unchanged. */
35110
+ function arkmeTopicDisplayName(title, topicKind) {
35111
+ return topicKind === 3 ? ARKME_DSH_INPUT_TOPIC_TITLE : title;
35112
+ }
35113
+ function isArkmeDSHInputTopic(source) {
35114
+ return source?.kind === "topic" && source.topicKind === 3;
35115
+ }
35116
+ function arkmeSourceAllowsUserWrite(source) {
35117
+ return !isArkmeDSHInputTopic(source);
35118
+ }
35066
35119
  //#endregion
35067
35120
  //#region src/services/source-service.ts
35068
35121
  const SOURCE_LIST_CACHE_TTL_MS = 3e4;
@@ -35674,6 +35727,20 @@ var SourceService = class {
35674
35727
  displayName: title
35675
35728
  };
35676
35729
  }
35730
+ /** Reuse the record owner's topic policy, without replaying title/privacy defaults. */
35731
+ async topicHomeVisibility(sourceRef, showInHome, signal) {
35732
+ const session = await this.runtime.requireSession();
35733
+ const topic = await this.openSourceRef(sourceRef, session.userId);
35734
+ if (topic.kind !== "topic") throw new ArkmePluginError("topic-policy-invalid", "请选择主题", false);
35735
+ if (showInHome === void 0) return { showInHome: (await readTopicMetadata(this.runtime, session, topic.ownerRef, signal)).showInHome };
35736
+ const value = (await this.runtime.authenticatedPost("/api/v1/topics/display/policy/set", {
35737
+ topic_uid: topic.ownerRef,
35738
+ show_in_home: showInHome
35739
+ }, session, void 0)).show_in_home;
35740
+ if (typeof value !== "boolean") throw new ArkmePluginError("topic-policy-contract-invalid", "主题设置响应不完整,请重试", true, 502);
35741
+ this.invalidateSourceListCache(session.userId, "send_to_self");
35742
+ return { showInHome: value };
35743
+ }
35677
35744
  /**
35678
35745
  * Dissolving a topic promotes its direct children and moves the topic's own
35679
35746
  * records into its parent. Root-topic records return to the default category.
@@ -35772,21 +35839,18 @@ var SourceService = class {
35772
35839
  let cursorSendAt;
35773
35840
  let cursorRecordUid;
35774
35841
  for (let pageIndex = 0; pageIndex < 1e3; pageIndex += 1) {
35775
- const data = await this.runtime.authenticatedPost("/api/v1/topics/display/detail", {
35776
- topic_uid: topicUid,
35842
+ const page = await readTopicRecordPage(this.runtime, session, topicUid, {
35777
35843
  limit: 100,
35778
- ...cursorSendAt === void 0 ? {} : { cursor_send_at: cursorSendAt },
35779
- ...cursorRecordUid === void 0 ? {} : { cursor_record_uid: cursorRecordUid }
35780
- }, session);
35781
- for (const raw of listValue$23(data.records)) {
35782
- const item = objectValue$2(raw);
35783
- const uid = stringValue$2(item.record_uid ?? objectValue$2(item.record_core).record_uid).trim();
35784
- if (uid !== "") recordUids.add(uid);
35785
- }
35844
+ ...cursorSendAt === void 0 || cursorRecordUid === void 0 ? {} : { cursor: {
35845
+ sendAtMillis: cursorSendAt,
35846
+ itemUid: cursorRecordUid
35847
+ } }
35848
+ });
35849
+ for (const raw of page.records) recordUids.add(stringValue$2(objectValue$2(raw).record_uid).trim());
35786
35850
  onProgress?.(recordUids.size);
35787
- if (data.has_more !== true) return [...recordUids];
35788
- const nextSendAt = numberValue$26(data.next_cursor_send_at);
35789
- const nextRecordUid = stringValue$2(data.next_cursor_record_uid).trim();
35851
+ if (!page.hasMore) return [...recordUids];
35852
+ const nextSendAt = page.nextCursor?.sendAtMillis ?? 0;
35853
+ const nextRecordUid = page.nextCursor?.itemUid ?? "";
35790
35854
  const cursorKey = `${String(nextSendAt)}:${nextRecordUid}`;
35791
35855
  if (nextSendAt <= 0 || nextRecordUid === "" || seenCursors.has(cursorKey)) throw new ArkmePluginError("topic-dissolve-record-page-invalid", "主题快记加载不完整,未解散主题,请刷新后重试", true, 502);
35792
35856
  seenCursors.add(cursorKey);
@@ -35924,6 +35988,11 @@ var SourceService = class {
35924
35988
  const entry = objectValue$2(raw);
35925
35989
  const core = objectValue$2(entry.topic_core);
35926
35990
  if (arkmePrivacyLockedTopic(entry) || core.status !== 1) continue;
35991
+ const topicKind = numberValue$26(core.kind) || 1;
35992
+ if (!arkmeSourceAllowsUserWrite({
35993
+ kind: "topic",
35994
+ topicKind
35995
+ })) continue;
35927
35996
  const topicUid = stringValue$2(core.topic_uid).trim();
35928
35997
  const title = stringValue$2(core.title).trim();
35929
35998
  if (!topicUid || !title || seen.has(topicUid)) continue;
@@ -35931,6 +36000,7 @@ var SourceService = class {
35931
36000
  const recordCount = objectValue$2(entry.summary).record_count;
35932
36001
  items.push({
35933
36002
  kind: "topic",
36003
+ topicKind,
35934
36004
  displayName: title,
35935
36005
  activeAtMillis: numberValue$26(core.update_at),
35936
36006
  unreadCount: 0,
@@ -36121,6 +36191,7 @@ var SourceService = class {
36121
36191
  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();
36122
36192
  topicDescriptors.push({
36123
36193
  topicUid,
36194
+ topicKind: numberValue$26(core.kind) || 1,
36124
36195
  ...parentTopicUid === "" || parentTopicUid === topicUid ? {} : { parentTopicUid },
36125
36196
  siblingOrder: numberValue$26(siblingOrderByChild.get(topicUid) ?? core.sibling_order ?? item.sibling_order),
36126
36197
  title,
@@ -36147,7 +36218,8 @@ var SourceService = class {
36147
36218
  ...parentTopicHierarchyKey === void 0 ? {} : { parentTopicHierarchyKey },
36148
36219
  ...topic.siblingOrder > 0 ? { siblingOrder: topic.siblingOrder } : {},
36149
36220
  kind: "topic",
36150
- displayName: topic.title,
36221
+ topicKind: topic.topicKind,
36222
+ displayName: arkmeTopicDisplayName(topic.title, topic.topicKind),
36151
36223
  ...topic.latestPreview === "" ? {} : { latestPreview: topic.latestPreview },
36152
36224
  activeAtMillis: topic.activeAtMillis,
36153
36225
  unreadCount: 0,
@@ -36665,7 +36737,7 @@ function botConversationCapabilities(target, provider) {
36665
36737
  const owner = target.kind === "subject" ? BOT_CONVERSATION_OWNER.subject : target.kind === "chat" ? BOT_CONVERSATION_OWNER.chat : void 0;
36666
36738
  return {
36667
36739
  directChatAvailable: owner !== void 0,
36668
- privateChatOutboundEnabled: owner === BOT_CONVERSATION_OWNER.chat || owner === BOT_CONVERSATION_OWNER.subject && provider === "openclaw",
36740
+ privateChatOutboundEnabled: owner !== void 0 && provider === "openclaw",
36669
36741
  refreshOnRecordChanges: owner === BOT_CONVERSATION_OWNER.subject,
36670
36742
  conversationProjection: owner === BOT_CONVERSATION_OWNER.subject ? "record" : owner === BOT_CONVERSATION_OWNER.chat ? "chat" : "none"
36671
36743
  };
@@ -36754,10 +36826,18 @@ var BotService = class {
36754
36826
  name,
36755
36827
  provider,
36756
36828
  description,
36757
- avatar
36829
+ avatar,
36830
+ direct_chat_owner: BOT_CONVERSATION_OWNER.chat,
36831
+ request_uid: input.requestUid?.trim() || randomUUID()
36758
36832
  }, session, options.signal);
36759
36833
  } catch (error) {
36760
- 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 });
36834
+ if (error instanceof ArkmePluginError && ([
36835
+ "arkme-network-error",
36836
+ "arkme-timeout",
36837
+ "arkme-response-invalid",
36838
+ "arkme-code-1002"
36839
+ ].includes(error.code) || error.code === "arkme-http-error" && error.retryable)) throw new ArkmePluginError("bot-create-outcome-unknown", "Bot 创建结果未知,请刷新 Bot 列表确认;不会自动重试", false, 409, { cause: error });
36840
+ if (error instanceof ArkmePluginError && error.code === "arkme-code-1001") throw new ArkmePluginError(error.code, error.message, false, 400, { cause: error });
36761
36841
  throw error;
36762
36842
  }
36763
36843
  try {
@@ -37105,6 +37185,7 @@ var BotService = class {
37105
37185
  const signature = createHmac("sha256", secret).update(`bot-directory-entry-v1:${userId}:${payload}`).digest("base64url");
37106
37186
  return [{
37107
37187
  ...bot,
37188
+ privateChatOutboundEnabled: bot.directChatAvailable && bot.provider === "openclaw",
37108
37189
  botRef: `arkme-bot-directory-entry-v1.${payload}.${signature}`
37109
37190
  }];
37110
37191
  });
@@ -37263,7 +37344,6 @@ var SubjectBotConversationAdapter = class {
37263
37344
  async send(context, contentInput, signal) {
37264
37345
  this.subjectTarget(context);
37265
37346
  const content = contentInput.trim();
37266
- if (context.reference.provider === "webhook") throw new ArkmePluginError("bot-conversation-send-unsupported", "Webhook Bot 仅接收外部系统推送", false, 400);
37267
37347
  if (content === "" || content.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("bot-conversation-content-invalid", "Bot 消息为空或超过长度限制", false, 400);
37268
37348
  let data;
37269
37349
  try {
@@ -37416,6 +37496,7 @@ var BotConversationService = class {
37416
37496
  }
37417
37497
  async send(botRef, content, options = {}) {
37418
37498
  const context = await this.context(botRef);
37499
+ if (context.reference.provider === "webhook") throw new ArkmePluginError("bot-conversation-send-unsupported", "Webhook Bot 仅接收外部系统推送", false, 400);
37419
37500
  const result = await this.adapter(context).send(context, content, options.signal);
37420
37501
  if (context.reference.target.kind === "subject") await this.invalidateRecordProjection();
37421
37502
  return result;
@@ -37644,6 +37725,44 @@ function object(value) {
37644
37725
  function first(source, keys) {
37645
37726
  return keys.map((key) => source[key]).find((value) => value !== void 0 && value !== null && value !== "");
37646
37727
  }
37728
+ /** Flutter-compatible summary templates, resolved on the host before projection. */
37729
+ function renderCallRecordSummary(source, viewerUserId, displayNames = /* @__PURE__ */ new Map()) {
37730
+ const fallback = String(first(source, [
37731
+ "sm",
37732
+ "call_summary",
37733
+ "callSummary",
37734
+ "summary_text",
37735
+ "summaryText",
37736
+ "summary"
37737
+ ]) ?? "").trim();
37738
+ const template = String(first(source, [
37739
+ "smt",
37740
+ "call_summary_template",
37741
+ "callSummaryTemplate",
37742
+ "summary_template",
37743
+ "summaryTemplate"
37744
+ ]) ?? "").trim();
37745
+ const labels = object(first(source, [
37746
+ "ssl",
37747
+ "summary_speaker_labels",
37748
+ "call_summary_speaker_labels"
37749
+ ]));
37750
+ const users = object(first(source, [
37751
+ "ssu",
37752
+ "summary_speaker_user_ids",
37753
+ "call_summary_speaker_user_ids"
37754
+ ]));
37755
+ const rendered = template.replace(/\{\{(user|speaker):([^{}]+)\}\}/g, (token, kind, key) => {
37756
+ const userId = Number(kind === "user" ? key : users[token] ?? users[key]);
37757
+ if (userId > 0 && userId === viewerUserId) return "我";
37758
+ const name = displayNames.get(userId);
37759
+ if (name?.trim()) return name.trim();
37760
+ const label = kind === "speaker" ? labels[token] ?? labels[key] : void 0;
37761
+ return typeof label === "string" && label.trim() ? label.trim() : token;
37762
+ });
37763
+ const text = rendered && !rendered.includes("{{") ? rendered : fallback;
37764
+ return text.includes("{{") ? "" : text;
37765
+ }
37647
37766
  function callRecordCandidates(raw) {
37648
37767
  const root = object(raw);
37649
37768
  const record = object(root.record);
@@ -37777,9 +37896,27 @@ function projectCallRecord(raw, viewerUserId) {
37777
37896
  text = isCaller ? "对方离线" : "未接听(离线)";
37778
37897
  break;
37779
37898
  }
37899
+ const summary = renderCallRecordSummary(source, viewerUserId);
37900
+ const rawStatus = String(first(source, [
37901
+ "ss",
37902
+ "call_summary_status",
37903
+ "summary_status",
37904
+ "summaryStatus"
37905
+ ]) ?? "").toLowerCase();
37906
+ const summaryStatus = [
37907
+ "pending",
37908
+ "processing",
37909
+ "generating"
37910
+ ].includes(rawStatus) ? "pending" : [
37911
+ "done",
37912
+ "success",
37913
+ "finished"
37914
+ ].includes(rawStatus) ? "done" : ["failed", "error"].includes(rawStatus) ? "failed" : void 0;
37780
37915
  return {
37781
37916
  mediaType,
37782
- text
37917
+ text,
37918
+ ...summary && !summary.includes("{{") ? { summaryText: summary } : {},
37919
+ ...summaryStatus ? { summaryStatus } : {}
37783
37920
  };
37784
37921
  }
37785
37922
  //#endregion
@@ -38130,9 +38267,11 @@ function decodeJson(value) {
38130
38267
  var CallHistoryService = class {
38131
38268
  runtime;
38132
38269
  profile;
38133
- constructor(runtime, profile) {
38270
+ media;
38271
+ constructor(runtime, profile, media) {
38134
38272
  this.runtime = runtime;
38135
38273
  this.profile = profile;
38274
+ this.media = media;
38136
38275
  }
38137
38276
  async listCallHistory(options = {}, signal) {
38138
38277
  const session = await this.runtime.requireSession();
@@ -38475,13 +38614,6 @@ var CallHistoryService = class {
38475
38614
  "callResult",
38476
38615
  "result"
38477
38616
  ]);
38478
- const summaryText = safeSummary(firstString$1(raw, [
38479
- "call_summary",
38480
- "callSummary",
38481
- "summary_text",
38482
- "summaryText",
38483
- "summary"
38484
- ]));
38485
38617
  const summaryUpdatedAtMillis = firstEpochMillis(raw, [
38486
38618
  "call_summary_updated_at",
38487
38619
  "callSummaryUpdatedAt",
@@ -38495,7 +38627,7 @@ var CallHistoryService = class {
38495
38627
  "mediaType"
38496
38628
  ]));
38497
38629
  const videoRecord = callVideoRecord(raw, mediaType, session.userId);
38498
- const transcriptSegments = this.transcriptSegments(raw, startedAtMillis).slice(0, 200);
38630
+ const transcriptSegments = this.transcriptSegments(raw, startedAtMillis, session.userId).slice(0, 200);
38499
38631
  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;
38500
38632
  const participantSeeds = this.participants(raw, session.userId);
38501
38633
  const userIds = [
@@ -38511,6 +38643,7 @@ var CallHistoryService = class {
38511
38643
  ...userId === session.userId ? { isCurrentUser: true } : {}
38512
38644
  });
38513
38645
  const participants = await this.attachParticipantAvatars(participantSeeds.slice(0, 50), session, signal);
38646
+ const summaryText = safeSummary(renderCallRecordSummary(raw, session.userId, new Map(participants.flatMap((participant) => participant.userId ? [[participant.userId, participant.displayName]] : []))));
38514
38647
  const hangupParticipant = participants.find((participant) => participant.userId === numberValue$22(hangupUserId));
38515
38648
  return {
38516
38649
  callRef: await this.sealCallRef({
@@ -38591,7 +38724,7 @@ var CallHistoryService = class {
38591
38724
  };
38592
38725
  }).filter((item) => item !== void 0).slice(0, 50);
38593
38726
  }
38594
- transcriptSegments(raw, startedAtMillis) {
38727
+ transcriptSegments(raw, startedAtMillis, viewerUserId) {
38595
38728
  return listValue$19(firstValue(raw, [
38596
38729
  "segments",
38597
38730
  "transcript_segments",
@@ -38609,6 +38742,13 @@ var CallHistoryService = class {
38609
38742
  "segmentId",
38610
38743
  "id"
38611
38744
  ]) || `segment-${String(index + 1)}`;
38745
+ const remoteAudioUrl = firstString$1(rawItem, ["audio_url", "audioUrl"]);
38746
+ const audioBlock = remoteAudioUrl ? this.media?.forwardContentBlocks([{
38747
+ type: 2,
38748
+ name: "通话片段",
38749
+ download_url: remoteAudioUrl
38750
+ }], viewerUserId).find((block) => block.kind === "audio") : void 0;
38751
+ const audioUrl = audioBlock ? `${this.runtime.config.routePath}/media?ref=${encodeURIComponent(audioBlock.mediaRef)}` : void 0;
38612
38752
  const speakerUserId = Math.trunc(firstNumber(rawItem, [
38613
38753
  "speaker_user_id",
38614
38754
  "speakerUserId",
@@ -38633,6 +38773,7 @@ var CallHistoryService = class {
38633
38773
  const absoluteEnd = end >= 1e11;
38634
38774
  return {
38635
38775
  segmentId,
38776
+ ...audioUrl ? { audioUrl } : {},
38636
38777
  speakerDisplayName: firstString$1(rawItem, [
38637
38778
  "speaker_display_name",
38638
38779
  "speakerDisplayName",
@@ -42681,6 +42822,8 @@ function arkmeRichContentPayload(input, normalizedTextContent, mentionPayload) {
42681
42822
  }
42682
42823
  const OFFICIAL_AUTHOR_USER_ID = 11;
42683
42824
  const OFFICIAL_AUTHOR_FALLBACK_DISPLAY_NAME = "即我作者";
42825
+ const RESERVED_ASEN_BOT_UID = "asen";
42826
+ const RESERVED_ASEN_DISPLAY_NAME = "阿森";
42684
42827
  const MAX_MESSAGE_COPY_LINK_ITEMS = 100;
42685
42828
  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;
42686
42829
  const CHAT_MEMBER_REF_PREFIX = "arkme-chat-member-v1";
@@ -43738,7 +43881,7 @@ var ChatService = class {
43738
43881
  callHistory;
43739
43882
  memberEvents;
43740
43883
  favoriteStickerMutationTail = Promise.resolve();
43741
- constructor(runtime, source, profile, media, record, bot, arko, aiPolish, realtime, privacy = new ArkmePrivacyVisibilityService(runtime), messageActions, callHistory = new CallHistoryService(runtime, profile)) {
43884
+ constructor(runtime, source, profile, media, record, bot, arko, aiPolish, realtime, privacy = new ArkmePrivacyVisibilityService(runtime), messageActions, callHistory = new CallHistoryService(runtime, profile, media)) {
43742
43885
  this.runtime = runtime;
43743
43886
  this.source = source;
43744
43887
  this.profile = profile;
@@ -44377,14 +44520,13 @@ var ChatService = class {
44377
44520
  }
44378
44521
  if (source.kind === "topic") {
44379
44522
  const lockedRecordUids = await this.privacy.lockedRecordUids(session, options.signal);
44380
- const data = await this.runtime.authenticatedPost("/api/v1/topics/display/detail", {
44381
- topic_uid: source.ownerRef,
44382
- limit,
44383
- ...options.cursor?.sendAtMillis === void 0 ? {} : { cursor_send_at: options.cursor.sendAtMillis },
44384
- ...options.cursor?.itemUid === void 0 ? {} : { cursor_record_uid: options.cursor.itemUid }
44385
- }, session, options.signal);
44386
- if (arkmePrivacyLockedTopic(data.topic_core)) throw new ArkmePluginError("topic-privacy-locked", "隐私锁主题不能在 Arkme 插件中查看", false, 403);
44387
- const rawRecords = listValue$16(data.records).filter((raw) => !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(this.record.recordUid(raw)));
44523
+ const [page, metadata] = await Promise.all([readTopicRecordPage(this.runtime, session, source.ownerRef, {
44524
+ ...options,
44525
+ limit
44526
+ }), readTopicMetadata(this.runtime, session, source.ownerRef, options.signal)]);
44527
+ if (arkmePrivacyLockedTopic({ privacy_state: page.privacyState }) || metadata.privacyState === 2) throw new ArkmePluginError("topic-privacy-locked", "隐私锁主题不能在 Arkme 插件中查看", false, 403);
44528
+ const topicKind = metadata.topicKind;
44529
+ const rawRecords = page.records.filter((raw) => !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(this.record.recordUid(raw)));
44388
44530
  const media = await this.media.hydrateRecordMediaPage(rawRecords, session, options.signal);
44389
44531
  const signingKey = await this.runtime.stateStore.uniqueCode();
44390
44532
  const records = rawRecords.map((raw) => {
@@ -44396,16 +44538,15 @@ var ChatService = class {
44396
44538
  });
44397
44539
  return this.withRecordTopicAssignmentRef(source, item, session.userId, signingKey, numberValue$19(objectValue$2(raw).owner_user_id), source.ownerRef);
44398
44540
  });
44399
- const nextSendAt = numberValue$19(data.next_cursor_send_at);
44400
- const nextUid = stringValue$2(data.next_cursor_record_uid).trim();
44401
44541
  return {
44402
- source: await this.source.sourceItem(source),
44542
+ source: {
44543
+ ...await this.source.sourceItem(source),
44544
+ topicKind,
44545
+ displayName: arkmeTopicDisplayName(source.displayName, topicKind)
44546
+ },
44403
44547
  items: records.map((item) => this.withRecordMessageActionRef(source, item, session.userId, signingKey)),
44404
- hasMore: data.has_more === true,
44405
- ...nextSendAt > 0 && nextUid !== "" ? { nextCursor: {
44406
- sendAtMillis: nextSendAt,
44407
- itemUid: nextUid
44408
- } } : {}
44548
+ hasMore: page.hasMore,
44549
+ ...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor }
44409
44550
  };
44410
44551
  }
44411
44552
  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;
@@ -44956,13 +45097,21 @@ var ChatService = class {
44956
45097
  parent_record_uid: parentRecordUid,
44957
45098
  limit: 100
44958
45099
  }, session, signal);
44959
- const projections = listValue$16(data.children).flatMap((child) => {
45100
+ const projections = [];
45101
+ for (const child of listValue$16(data.children)) {
44960
45102
  const extension = chatExtensionTreeItemFromData(child, parentRecordUid);
44961
- return extension === void 0 ? [] : [{
44962
- extension,
45103
+ if (extension === void 0) continue;
45104
+ const record = objectValue$2(objectValue$2(objectValue$2(child).item).record);
45105
+ const payload = objectValue$2(record.payload);
45106
+ const mentions = await this.timelineMentionTargets(record, payload, session.userId, chatSessionUid);
45107
+ projections.push({
45108
+ extension: mentions === void 0 ? extension : {
45109
+ ...extension,
45110
+ mentions
45111
+ },
44963
45112
  mediaRecord: chatExtensionMediaRecord(child, extension.recordUid)
44964
- }];
44965
- });
45113
+ });
45114
+ }
44966
45115
  const extensions = (await this.hydrateExtensionMedia(projections, session, signal)).sort((left, right) => right.sendAtMillis - left.sendAtMillis);
44967
45116
  const extensionCount = Math.max(integerLikeValue(data.total), extensions.length);
44968
45117
  return extensionCount <= 0 ? void 0 : {
@@ -45669,22 +45818,30 @@ var ChatService = class {
45669
45818
  const leadingTrim = textFormat === "markdown" ? 0 : rawText.length - rawText.trimStart().length;
45670
45819
  const uniqueRefs = [...new Set(inputs.map((input) => input.botRef.trim()))];
45671
45820
  if (uniqueRefs.some((ref) => ref === "")) throw new ArkmePluginError("bot-mention-ref-invalid", "Bot mention 引用为空", false);
45672
- const references = await Promise.all(uniqueRefs.map(async (ref) => ({
45821
+ const requestedReservedAsen = uniqueRefs.some((ref) => ref.toLowerCase() === RESERVED_ASEN_BOT_UID);
45822
+ const regularRefs = uniqueRefs.filter((ref) => ref.toLowerCase() !== RESERVED_ASEN_BOT_UID);
45823
+ const references = await Promise.all(regularRefs.map(async (ref) => ({
45673
45824
  ref,
45674
45825
  value: await this.bot.openBotRef(ref, session.userId)
45675
45826
  })));
45676
45827
  const allowedByBotId = /* @__PURE__ */ new Map();
45828
+ const reservedAsen = requestedReservedAsen ? {
45829
+ bot_uid: RESERVED_ASEN_BOT_UID,
45830
+ display_name_snapshot: RESERVED_ASEN_DISPLAY_NAME
45831
+ } : void 0;
45677
45832
  if (source.kind === "group_chat") {
45678
- const groupBots = await this.bot.listMentionableGroupBots(source, session, signal);
45679
- const requested = new Set(references.map((item) => item.value.botId));
45680
- for (const { botId, name } of groupBots) {
45681
- if (!requested.has(botId)) continue;
45682
- allowedByBotId.set(botId, {
45683
- bot_uid: botId,
45684
- display_name_snapshot: name
45685
- });
45833
+ if (references.length > 0) {
45834
+ const groupBots = await this.bot.listMentionableGroupBots(source, session, signal);
45835
+ const requested = new Set(references.map((item) => item.value.botId));
45836
+ for (const { botId, name } of groupBots) {
45837
+ if (!requested.has(botId)) continue;
45838
+ allowedByBotId.set(botId, {
45839
+ bot_uid: botId,
45840
+ display_name_snapshot: name
45841
+ });
45842
+ }
45686
45843
  }
45687
- } else {
45844
+ } else if (references.length > 0) {
45688
45845
  const bots = (await this.bot.listBots(signal === void 0 ? {} : { signal })).items;
45689
45846
  const botNameByRef = new Map(bots.filter((bot) => bot.provider === "openclaw").map((bot) => [bot.botRef, bot.name]));
45690
45847
  for (const reference of references) {
@@ -45700,8 +45857,9 @@ var ChatService = class {
45700
45857
  const refToBotId = new Map(references.map((item) => [item.ref, item.value.botId]));
45701
45858
  return [...inputs].sort((left, right) => left.startIndex - right.startIndex).map((input) => {
45702
45859
  const botRef = input.botRef.trim();
45703
- const botId = refToBotId.get(botRef);
45704
- const bot = botId === void 0 ? void 0 : allowedByBotId.get(botId);
45860
+ const isReservedAsen = botRef.toLowerCase() === RESERVED_ASEN_BOT_UID;
45861
+ const botId = isReservedAsen ? void 0 : refToBotId.get(botRef);
45862
+ const bot = isReservedAsen ? reservedAsen : botId === void 0 ? void 0 : allowedByBotId.get(botId);
45705
45863
  if (bot === void 0) throw new ArkmePluginError("bot-mention-not-available", "所选 Bot 当前不可 mention", false, 409);
45706
45864
  const startIndex = Math.trunc(input.startIndex) - leadingTrim;
45707
45865
  const length = Math.trunc(input.length);
@@ -45716,29 +45874,48 @@ var ChatService = class {
45716
45874
  });
45717
45875
  }
45718
45876
  async groupBotMentionSend(sourceRef, source, text, botRefs, recordUid, relationUid, session, signal, options = {}) {
45719
- const uniqueRefs = new Set(botRefs.map((ref) => ref.trim()));
45877
+ const normalizedRefs = botRefs.map((ref) => {
45878
+ const normalized = ref.trim();
45879
+ return normalized.toLowerCase() === RESERVED_ASEN_BOT_UID ? RESERVED_ASEN_BOT_UID : normalized;
45880
+ });
45881
+ const uniqueRefs = new Set(normalizedRefs);
45720
45882
  if (uniqueRefs.has("") || uniqueRefs.size !== botRefs.length) throw new ArkmePluginError("bot-mention-ref-invalid", "Bot mention 引用为空或重复", false);
45721
- const references = await Promise.all([...uniqueRefs].map(async (ref) => ({
45883
+ const requestedReservedAsen = [...uniqueRefs].some((ref) => ref.toLowerCase() === RESERVED_ASEN_BOT_UID);
45884
+ const regularRefs = [...uniqueRefs].filter((ref) => ref.toLowerCase() !== RESERVED_ASEN_BOT_UID);
45885
+ const references = await Promise.all(regularRefs.map(async (ref) => ({
45722
45886
  ref,
45723
45887
  value: await this.bot.openBotRef(ref, session.userId)
45724
45888
  })));
45725
45889
  const requestedById = new Map(references.map((item) => [item.value.botId, item.ref]));
45726
45890
  if (requestedById.size !== references.length) throw new ArkmePluginError("bot-mention-ref-invalid", "Bot mention 引用重复", false);
45727
- const groupBots = await this.bot.listMentionableGroupBots(source, session, signal);
45728
45891
  const mentions = [];
45729
45892
  let visibleText = "";
45730
- for (const { botId, name } of groupBots) {
45731
- if (!requestedById.has(botId)) continue;
45732
- const display = `@${name}`;
45893
+ if (requestedById.size > 0) {
45894
+ const groupBots = await this.bot.listMentionableGroupBots(source, session, signal);
45895
+ for (const { botId, name } of groupBots) {
45896
+ if (!requestedById.has(botId)) continue;
45897
+ const display = `@${name}`;
45898
+ const startIndex = visibleText.length;
45899
+ visibleText += `${display} `;
45900
+ mentions.push({
45901
+ bot_uid: botId,
45902
+ display_name_snapshot: name,
45903
+ start_index: startIndex,
45904
+ length: display.length
45905
+ });
45906
+ requestedById.delete(botId);
45907
+ }
45908
+ }
45909
+ if (requestedReservedAsen) {
45910
+ const display = `@${RESERVED_ASEN_DISPLAY_NAME}`;
45733
45911
  const startIndex = visibleText.length;
45734
45912
  visibleText += `${display} `;
45735
45913
  mentions.push({
45736
- bot_uid: botId,
45737
- display_name_snapshot: name,
45914
+ bot_uid: RESERVED_ASEN_BOT_UID,
45915
+ display_name_snapshot: RESERVED_ASEN_DISPLAY_NAME,
45738
45916
  start_index: startIndex,
45739
45917
  length: display.length
45740
45918
  });
45741
- requestedById.delete(botId);
45742
45919
  }
45743
45920
  if (requestedById.size > 0) throw new ArkmePluginError("bot-mention-not-installed", "所选 Bot 未安装到该群聊", false, 409);
45744
45921
  visibleText += text;
@@ -46194,6 +46371,51 @@ var ChatService = class {
46194
46371
  async chatSourceFromBundle(bundle, session, cached, timelineItems) {
46195
46372
  return await this.source.chatSourceFromBundle(bundle, session, cached, timelineItems);
46196
46373
  }
46374
+ async timelineMentionTargets(record, payload, viewerUserId, chatSessionUid) {
46375
+ const metadata = arkmeMentionMetadataFromRecord(record, payload);
46376
+ const targets = [];
46377
+ for (const rawMention of listValue$16(metadata.human_mentions ?? metadata.humanMentions)) {
46378
+ const mention = objectValue$2(rawMention);
46379
+ const startIndex = integerLikeValue(mention.start_index ?? mention.startIndex);
46380
+ const length = integerLikeValue(mention.length);
46381
+ const displayName = stringValue$2(mention.display_name_snapshot ?? mention.displayNameSnapshot).trim();
46382
+ const userId = integerLikeValue(mention.user_id ?? mention.userId);
46383
+ if (startIndex < 0 || length < 2 || displayName === "") continue;
46384
+ if (userId === 0) {
46385
+ targets.push({
46386
+ kind: "all",
46387
+ startIndex,
46388
+ length,
46389
+ displayName
46390
+ });
46391
+ continue;
46392
+ }
46393
+ if (userId > 0) targets.push({
46394
+ kind: "member",
46395
+ startIndex,
46396
+ length,
46397
+ displayName,
46398
+ memberRef: await this.sealChatMemberRef(viewerUserId, chatSessionUid, userId)
46399
+ });
46400
+ }
46401
+ for (const rawMention of listValue$16(metadata.bot_mentions ?? metadata.botMentions)) {
46402
+ const mention = objectValue$2(rawMention);
46403
+ const startIndex = integerLikeValue(mention.start_index ?? mention.startIndex);
46404
+ const length = integerLikeValue(mention.length);
46405
+ const displayName = stringValue$2(mention.display_name_snapshot ?? mention.displayNameSnapshot).trim();
46406
+ const botRef = stringValue$2(mention.bot_uid ?? mention.botUid).trim();
46407
+ if (startIndex < 0 || length < 2 || displayName === "" || botRef === "") continue;
46408
+ targets.push({
46409
+ kind: "bot",
46410
+ startIndex,
46411
+ length,
46412
+ displayName,
46413
+ botRef
46414
+ });
46415
+ }
46416
+ targets.sort((left, right) => left.startIndex - right.startIndex);
46417
+ return targets.length === 0 ? void 0 : targets;
46418
+ }
46197
46419
  async chatTimelineItems(data, session, chatSessionUid, projectedSourceKind) {
46198
46420
  const items = [];
46199
46421
  const signingKey = await this.runtime.stateStore.uniqueCode();
@@ -46228,6 +46450,7 @@ var ChatService = class {
46228
46450
  const conversationPreview = arkmeChatConversationPreview(item);
46229
46451
  const callRecord = await this.callHistory.timelineCallRecord(item, session.userId);
46230
46452
  const senderName = stringValue$2(relation.display_name_snapshot).trim() || "Arkme用户";
46453
+ const mentions = await this.timelineMentionTargets(record, payload, session.userId, chatSessionUid);
46231
46454
  const mentionsViewer = senderUserId !== session.userId && arkmeMentionMetadataMentionsViewer(record, payload, session.userId);
46232
46455
  items.push({
46233
46456
  itemUid: uid,
@@ -46262,6 +46485,7 @@ var ChatService = class {
46262
46485
  isMe: senderUserId === session.userId,
46263
46486
  ...callRecord === void 0 ? {} : { callRecord },
46264
46487
  ...mentionsViewer ? { mentionsViewer: true } : {},
46488
+ ...mentions === void 0 ? {} : { mentions },
46265
46489
  sendAtMillis,
46266
46490
  title: stringValue$2(payload.title),
46267
46491
  textContent: stringValue$2(payload.text_content),
@@ -46864,6 +47088,7 @@ var ChatService = class {
46864
47088
  const contentBlocks = this.media.richContentBlocks(item, session.userId);
46865
47089
  const extensionProjection = this.timelineExtensionProjection(item, session.userId);
46866
47090
  const senderName = stringValue$2(relation.display_name_snapshot).trim() || "Arkme用户";
47091
+ const mentions = await this.timelineMentionTargets(record, payload, session.userId, source.ownerRef);
46867
47092
  const captureContext = timelineCaptureContext({
46868
47093
  ...record,
46869
47094
  ...payload
@@ -46902,6 +47127,7 @@ var ChatService = class {
46902
47127
  ...agentSource === void 0 ? {} : { agentSource },
46903
47128
  isMe: senderUserId === session.userId,
46904
47129
  ...callRecord === void 0 ? {} : { callRecord },
47130
+ ...mentions === void 0 ? {} : { mentions },
46905
47131
  sendAtMillis,
46906
47132
  title: stringValue$2(payload.title),
46907
47133
  textContent: stringValue$2(payload.text_content),
@@ -78987,6 +79213,10 @@ function allowedSignedImageHost$2(environment, hostname) {
78987
79213
  function allowedSignedAudioHost(environment, hostname) {
78988
79214
  return (environment === "prod" ? ["jotmo-useraudio.oss-cn-hangzhou.aliyuncs.com"] : ["jotmo-useraudio-test.oss-cn-hangzhou.aliyuncs.com"]).includes(hostname.toLowerCase());
78989
79215
  }
79216
+ /** Call transcript clips use a separate COS bucket, not the user-audio OSS bucket. */
79217
+ function allowedSignedCallAudioHost(environment, hostname) {
79218
+ return environment === "prod" && hostname.toLowerCase() === "webrtc-record-prod-1403070603.cos.ap-shanghai.myqcloud.com";
79219
+ }
78990
79220
  /** Record audio can originate from the dedicated audio or generic file bucket. */
78991
79221
  function allowedSignedRecordAudioHost(environment, hostname) {
78992
79222
  return allowedSignedAudioHost(environment, hostname) || allowedSignedImageHost$2(environment, hostname);
@@ -79256,7 +79486,7 @@ var MediaService = class {
79256
79486
  }
79257
79487
  if (originalOnly && descriptor.purpose !== "original") throw new ArkmePluginError("original-unavailable", "原文件不可用,请刷新后重试;不会用预览图代替原文件", false, 404);
79258
79488
  const url = new URL(descriptor.remoteUrl);
79259
- 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);
79489
+ 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);
79260
79490
  const response = await this.runtime.fetchImpl(url, {
79261
79491
  headers: range === void 0 ? {} : { Range: range },
79262
79492
  redirect: "error",
@@ -79753,7 +79983,7 @@ var MediaService = class {
79753
79983
  const value = safeHttpsUrl(raw);
79754
79984
  if (value === void 0) return void 0;
79755
79985
  const url = new URL(value);
79756
- return url.port === "" && url.hash === "" && (allowedSignedImageHost$2(this.runtime.config.environment, url.hostname) || allowedSignedAudioHost(this.runtime.config.environment, url.hostname)) ? value : void 0;
79986
+ 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;
79757
79987
  };
79758
79988
  const downloadUrl = trustedUrl(file.download_url ?? file.downloadUrl);
79759
79989
  const previewUrl = trustedUrl(file.preview_url ?? file.previewUrl);
@@ -84244,10 +84474,6 @@ var RelatedRecordingService = class {
84244
84474
  return `arkme-shared-recording-detail-v1.${encoded}.${createHmac("sha256", signingKey).update(encoded).digest("base64url")}`;
84245
84475
  }
84246
84476
  };
84247
- function isDshAgentInputSourceTitle(value) {
84248
- const normalized = value?.trim().toLowerCase();
84249
- return normalized === "dsh agent input" || normalized === "DSH Agent 输入".toLowerCase();
84250
- }
84251
84477
  //#endregion
84252
84478
  //#region src/services/search-service.ts
84253
84479
  function numberValue$2(value) {
@@ -84632,7 +84858,6 @@ var SearchService = class {
84632
84858
  const linkMatch = textContent.match(/https:\/\/[^\s<>()]+/u);
84633
84859
  const sourceTitle = stringValue$2(topic.title ?? chat.title).trim();
84634
84860
  const creationSource = Math.trunc(numberValue$2(core.creation_source ?? item.creation_source));
84635
- const normalizedCreationSource = creationSource > 0 ? creationSource : isDshAgentInputSourceTitle(sourceTitle) ? 3 : 0;
84636
84861
  return {
84637
84862
  recordUid,
84638
84863
  sourceKind: Math.trunc(numberValue$2(item.source_kind)),
@@ -84646,7 +84871,7 @@ var SearchService = class {
84646
84871
  ...stringValue$2(core.nickname).trim() === "" ? {} : { nickname: stringValue$2(core.nickname).trim() },
84647
84872
  ...numberValue$2(core.template_kind) <= 0 ? {} : { templateKind: Math.trunc(numberValue$2(core.template_kind)) },
84648
84873
  ...numberValue$2(core.display_kind) <= 0 ? {} : { displayKind: Math.trunc(numberValue$2(core.display_kind)) },
84649
- ...normalizedCreationSource <= 0 ? {} : { creationSource: normalizedCreationSource },
84874
+ ...creationSource <= 0 ? {} : { creationSource },
84650
84875
  ...sourceTitle === "" ? {} : { sourceTitle },
84651
84876
  media,
84652
84877
  files,
@@ -88304,7 +88529,7 @@ var ArkmeService = class {
88304
88529
  this.calendar = new CalendarService(this.runtime, this.privacy);
88305
88530
  this.wechat = new WechatService(this.runtime);
88306
88531
  this.profile = new ProfileService(this.runtime);
88307
- this.callHistory = new CallHistoryService(this.runtime, this.profile);
88532
+ this.callHistory = new CallHistoryService(this.runtime, this.profile, { forwardContentBlocks: (files, viewerUserId) => this.media.forwardContentBlocks(files, viewerUserId) });
88308
88533
  this.extensionReview = new ExtensionReviewService(this.runtime, this.profile, { createTextForConversation: async (recordUid, textContent) => {
88309
88534
  return await this.createTextForConversation(recordUid, textContent);
88310
88535
  } });
@@ -88643,6 +88868,7 @@ var ArkmeService = class {
88643
88868
  imageLibrary: true,
88644
88869
  sourceDirectory: true,
88645
88870
  localFirstDirectory: true,
88871
+ topicHomeVisibility: true,
88646
88872
  contactDirectoryReads: true,
88647
88873
  sourceTimeline: true,
88648
88874
  forwardContent: true,
@@ -89000,6 +89226,11 @@ var ArkmeService = class {
89000
89226
  async renameTopic(sourceRef, title) {
89001
89227
  return await this.source.renameTopic(sourceRef, title);
89002
89228
  }
89229
+ async topicHomeVisibility(sourceRef, showInHome, signal) {
89230
+ const result = await this.source.topicHomeVisibility(sourceRef, showInHome, signal);
89231
+ if (showInHome !== void 0) await this.realtime.invalidateRecordProjection();
89232
+ return result;
89233
+ }
89003
89234
  async dissolveTopic(sourceRef, parentSourceRef, childSourceRefs, requestId, expectedRecordCount) {
89004
89235
  return await this.source.dissolveTopic(sourceRef, parentSourceRef, childSourceRefs, requestId, expectedRecordCount);
89005
89236
  }