@senguoyun/dsh-arkme 0.1.51 → 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 (129) hide show
  1. package/docs/dsh-input-system-topic.md +26 -0
  2. package/lib/client.js +4279 -3022
  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-Be8cvpQd.js → persistent-runtime-BRy2sNmS.js} +321 -82
  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/ArkmeBotConversationSurface.d.ts.map +1 -1
  18. package/lib/types/client/ArkmeBotCreateDialog.d.ts.map +1 -1
  19. package/lib/types/client/ArkmeCallDetailContent.d.ts +13 -0
  20. package/lib/types/client/ArkmeCallDetailContent.d.ts.map +1 -0
  21. package/lib/types/client/ArkmeCallDetailDrawer.d.ts +2 -1
  22. package/lib/types/client/ArkmeCallDetailDrawer.d.ts.map +1 -1
  23. package/lib/types/client/ArkmeCallRecordContent.d.ts +4 -2
  24. package/lib/types/client/ArkmeCallRecordContent.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeCallSurface.d.ts.map +1 -1
  26. package/lib/types/client/ArkmeComposerSendButton.d.ts +10 -0
  27. package/lib/types/client/ArkmeComposerSendButton.d.ts.map +1 -0
  28. package/lib/types/client/ArkmeDshAgentInputMarker.d.ts +1 -1
  29. package/lib/types/client/ArkmeDshAgentInputMarker.d.ts.map +1 -1
  30. package/lib/types/client/ArkmeLinkText.d.ts +1 -1
  31. package/lib/types/client/ArkmeLinkText.d.ts.map +1 -1
  32. package/lib/types/client/ArkmeLogin.d.ts.map +1 -1
  33. package/lib/types/client/ArkmeMarketplace.d.ts +1 -0
  34. package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -1
  35. package/lib/types/client/ArkmeMentionSuggestionRow.d.ts +20 -0
  36. package/lib/types/client/ArkmeMentionSuggestionRow.d.ts.map +1 -0
  37. package/lib/types/client/ArkmeMessageActions.d.ts.map +1 -1
  38. package/lib/types/client/ArkmeNoteDetails.d.ts +6 -2
  39. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  40. package/lib/types/client/ArkmeOfficialAuthorGuide.d.ts +7 -0
  41. package/lib/types/client/ArkmeOfficialAuthorGuide.d.ts.map +1 -0
  42. package/lib/types/client/ArkmePersistentShell.d.ts +1 -1
  43. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  44. package/lib/types/client/ArkmeRichContent.d.ts +2 -1
  45. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  46. package/lib/types/client/ArkmeRichText.d.ts +9 -5
  47. package/lib/types/client/ArkmeRichText.d.ts.map +1 -1
  48. package/lib/types/client/ArkmeScanGuide.d.ts +5 -0
  49. package/lib/types/client/ArkmeScanGuide.d.ts.map +1 -0
  50. package/lib/types/client/ArkmeSidebar.d.ts +3 -35
  51. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  52. package/lib/types/client/ArkmeSourceBreadcrumb.d.ts.map +1 -1
  53. package/lib/types/client/ArkmeTopicDirectoryPopover.d.ts +2 -1
  54. package/lib/types/client/ArkmeTopicDirectoryPopover.d.ts.map +1 -1
  55. package/lib/types/client/ArkmeTopicReadOnlyNotice.d.ts +3 -0
  56. package/lib/types/client/ArkmeTopicReadOnlyNotice.d.ts.map +1 -0
  57. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +13 -7
  58. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  59. package/lib/types/client/api.d.ts +1 -1
  60. package/lib/types/client/api.d.ts.map +1 -1
  61. package/lib/types/client/arkme-login-locales.d.ts +13 -0
  62. package/lib/types/client/arkme-login-locales.d.ts.map +1 -1
  63. package/lib/types/client/call-detail-presentation.d.ts +13 -0
  64. package/lib/types/client/call-detail-presentation.d.ts.map +1 -0
  65. package/lib/types/client/chat-directory-store.d.ts.map +1 -1
  66. package/lib/types/client/composer-draft-store.d.ts +7 -0
  67. package/lib/types/client/composer-draft-store.d.ts.map +1 -1
  68. package/lib/types/client/conversation-composer-presentation.d.ts +3 -0
  69. package/lib/types/client/conversation-composer-presentation.d.ts.map +1 -1
  70. package/lib/types/client/index.d.ts +2 -1
  71. package/lib/types/client/index.d.ts.map +1 -1
  72. package/lib/types/client/mention-candidates.d.ts +46 -0
  73. package/lib/types/client/mention-candidates.d.ts.map +1 -0
  74. package/lib/types/client/navigation-cache.d.ts.map +1 -1
  75. package/lib/types/client/read-deadline.d.ts +3 -0
  76. package/lib/types/client/read-deadline.d.ts.map +1 -0
  77. package/lib/types/client/realtime-client-events.d.ts.map +1 -1
  78. package/lib/types/client/realtime-connection.d.ts +9 -0
  79. package/lib/types/client/realtime-connection.d.ts.map +1 -0
  80. package/lib/types/client/recordings/RecordingTranscriptForward.d.ts.map +1 -1
  81. package/lib/types/client/scan-guide-icons.d.ts +7 -0
  82. package/lib/types/client/scan-guide-icons.d.ts.map +1 -0
  83. package/lib/types/client/scan-guide-styles.d.ts +2 -0
  84. package/lib/types/client/scan-guide-styles.d.ts.map +1 -0
  85. package/lib/types/client/slots-contract.d.ts +23 -0
  86. package/lib/types/client/slots-contract.d.ts.map +1 -1
  87. package/lib/types/client/source-tree.d.ts.map +1 -1
  88. package/lib/types/client/use-call-record-preview.d.ts +8 -0
  89. package/lib/types/client/use-call-record-preview.d.ts.map +1 -0
  90. package/lib/types/client/use-call-transcript-playback.d.ts +13 -0
  91. package/lib/types/client/use-call-transcript-playback.d.ts.map +1 -0
  92. package/lib/types/dsh-agent-input-source.d.ts +0 -1
  93. package/lib/types/dsh-agent-input-source.d.ts.map +1 -1
  94. package/lib/types/extensions/profile-restart-helper.d.ts.map +1 -1
  95. package/lib/types/host-api.d.ts.map +1 -1
  96. package/lib/types/index.d.ts.map +1 -1
  97. package/lib/types/plugin-updater-helper.d.ts.map +1 -1
  98. package/lib/types/realtime-events.d.ts +9 -5
  99. package/lib/types/realtime-events.d.ts.map +1 -1
  100. package/lib/types/sdk/index.d.ts +4 -0
  101. package/lib/types/sdk/index.d.ts.map +1 -1
  102. package/lib/types/services/bot-conversation-service.d.ts.map +1 -1
  103. package/lib/types/services/bot-service.d.ts.map +1 -1
  104. package/lib/types/services/call-history-service.d.ts +3 -1
  105. package/lib/types/services/call-history-service.d.ts.map +1 -1
  106. package/lib/types/services/chat-service.d.ts +1 -0
  107. package/lib/types/services/chat-service.d.ts.map +1 -1
  108. package/lib/types/services/media-service.d.ts.map +1 -1
  109. package/lib/types/services/search-service.d.ts.map +1 -1
  110. package/lib/types/services/source-service.d.ts +4 -0
  111. package/lib/types/services/source-service.d.ts.map +1 -1
  112. package/lib/types/services/topic-metadata.d.ts +10 -0
  113. package/lib/types/services/topic-metadata.d.ts.map +1 -0
  114. package/lib/types/services/topic-record-page.d.ts +18 -0
  115. package/lib/types/services/topic-record-page.d.ts.map +1 -0
  116. package/lib/types/tools/business/conversation/index.d.ts.map +1 -1
  117. package/lib/types/tools/business/conversation/topic-home-visibility.d.ts +2 -0
  118. package/lib/types/tools/business/conversation/topic-home-visibility.d.ts.map +1 -0
  119. package/lib/types/tools/business/index.d.ts.map +1 -1
  120. package/lib/types/tools/ports/bots.d.ts +1 -0
  121. package/lib/types/tools/ports/bots.d.ts.map +1 -1
  122. package/lib/types/tools/ports/conversations.d.ts +3 -0
  123. package/lib/types/tools/ports/conversations.d.ts.map +1 -1
  124. package/lib/types/tools/registry/registrar.d.ts.map +1 -1
  125. package/lib/types/topic-policy.d.ts +9 -0
  126. package/lib/types/topic-policy.d.ts.map +1 -0
  127. package/lib/types/types.d.ts +22 -1
  128. package/lib/types/types.d.ts.map +1 -1
  129. 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;
@@ -35600,6 +35653,7 @@ var SourceService = class {
35600
35653
  const topicUid = stringValue$2(created.topic_uid).trim();
35601
35654
  if (topicUid === "" || numberValue$26(created.status) !== 1) throw new ArkmePluginError("topic-create-contract-invalid", "主题创建响应不完整", true, 502);
35602
35655
  const sourceRef = await this.sealSourceRef(session.userId, "topic", topicUid, title);
35656
+ const topicHierarchyKey = await this.topicHierarchyKey(session.userId, topicUid);
35603
35657
  if (parentTopicUid !== void 0) try {
35604
35658
  if (numberValue$26(objectValue$2((await this.runtime.authenticatedPost("/api/v1/topics/hierarchy/bind", {
35605
35659
  parent_topic_uid: parentTopicUid,
@@ -35620,6 +35674,7 @@ var SourceService = class {
35620
35674
  return {
35621
35675
  source: {
35622
35676
  sourceRef,
35677
+ topicHierarchyKey,
35623
35678
  kind: "topic",
35624
35679
  displayName: title,
35625
35680
  activeAtMillis: createdAtMillis,
@@ -35630,10 +35685,16 @@ var SourceService = class {
35630
35685
  };
35631
35686
  }
35632
35687
  throw new ArkmePluginError("topic-hierarchy-bind-failed", "未能创建子主题,已自动清理,请重试", true, 409, { cause: bindError });
35688
+ } finally {
35689
+ this.invalidateSourceListCache(session.userId, "send_to_self");
35633
35690
  }
35634
35691
  return { source: {
35635
35692
  sourceRef,
35636
- ...parentSourceRef !== void 0 ? { parentSourceRef } : {},
35693
+ topicHierarchyKey,
35694
+ ...parentSourceRef !== void 0 && parentTopicUid !== void 0 ? {
35695
+ parentSourceRef,
35696
+ parentTopicHierarchyKey: await this.topicHierarchyKey(session.userId, parentTopicUid)
35697
+ } : {},
35637
35698
  kind: "topic",
35638
35699
  displayName: title,
35639
35700
  activeAtMillis: createdAtMillis,
@@ -35666,6 +35727,20 @@ var SourceService = class {
35666
35727
  displayName: title
35667
35728
  };
35668
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
+ }
35669
35744
  /**
35670
35745
  * Dissolving a topic promotes its direct children and moves the topic's own
35671
35746
  * records into its parent. Root-topic records return to the default category.
@@ -35764,21 +35839,18 @@ var SourceService = class {
35764
35839
  let cursorSendAt;
35765
35840
  let cursorRecordUid;
35766
35841
  for (let pageIndex = 0; pageIndex < 1e3; pageIndex += 1) {
35767
- const data = await this.runtime.authenticatedPost("/api/v1/topics/display/detail", {
35768
- topic_uid: topicUid,
35842
+ const page = await readTopicRecordPage(this.runtime, session, topicUid, {
35769
35843
  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
- }
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());
35778
35850
  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();
35851
+ if (!page.hasMore) return [...recordUids];
35852
+ const nextSendAt = page.nextCursor?.sendAtMillis ?? 0;
35853
+ const nextRecordUid = page.nextCursor?.itemUid ?? "";
35782
35854
  const cursorKey = `${String(nextSendAt)}:${nextRecordUid}`;
35783
35855
  if (nextSendAt <= 0 || nextRecordUid === "" || seenCursors.has(cursorKey)) throw new ArkmePluginError("topic-dissolve-record-page-invalid", "主题快记加载不完整,未解散主题,请刷新后重试", true, 502);
35784
35856
  seenCursors.add(cursorKey);
@@ -35916,6 +35988,11 @@ var SourceService = class {
35916
35988
  const entry = objectValue$2(raw);
35917
35989
  const core = objectValue$2(entry.topic_core);
35918
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;
35919
35996
  const topicUid = stringValue$2(core.topic_uid).trim();
35920
35997
  const title = stringValue$2(core.title).trim();
35921
35998
  if (!topicUid || !title || seen.has(topicUid)) continue;
@@ -35923,6 +36000,7 @@ var SourceService = class {
35923
36000
  const recordCount = objectValue$2(entry.summary).record_count;
35924
36001
  items.push({
35925
36002
  kind: "topic",
36003
+ topicKind,
35926
36004
  displayName: title,
35927
36005
  activeAtMillis: numberValue$26(core.update_at),
35928
36006
  unreadCount: 0,
@@ -36113,6 +36191,7 @@ var SourceService = class {
36113
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();
36114
36192
  topicDescriptors.push({
36115
36193
  topicUid,
36194
+ topicKind: numberValue$26(core.kind) || 1,
36116
36195
  ...parentTopicUid === "" || parentTopicUid === topicUid ? {} : { parentTopicUid },
36117
36196
  siblingOrder: numberValue$26(siblingOrderByChild.get(topicUid) ?? core.sibling_order ?? item.sibling_order),
36118
36197
  title,
@@ -36139,7 +36218,8 @@ var SourceService = class {
36139
36218
  ...parentTopicHierarchyKey === void 0 ? {} : { parentTopicHierarchyKey },
36140
36219
  ...topic.siblingOrder > 0 ? { siblingOrder: topic.siblingOrder } : {},
36141
36220
  kind: "topic",
36142
- displayName: topic.title,
36221
+ topicKind: topic.topicKind,
36222
+ displayName: arkmeTopicDisplayName(topic.title, topic.topicKind),
36143
36223
  ...topic.latestPreview === "" ? {} : { latestPreview: topic.latestPreview },
36144
36224
  activeAtMillis: topic.activeAtMillis,
36145
36225
  unreadCount: 0,
@@ -36657,7 +36737,7 @@ function botConversationCapabilities(target, provider) {
36657
36737
  const owner = target.kind === "subject" ? BOT_CONVERSATION_OWNER.subject : target.kind === "chat" ? BOT_CONVERSATION_OWNER.chat : void 0;
36658
36738
  return {
36659
36739
  directChatAvailable: owner !== void 0,
36660
- privateChatOutboundEnabled: owner === BOT_CONVERSATION_OWNER.chat || owner === BOT_CONVERSATION_OWNER.subject && provider === "openclaw",
36740
+ privateChatOutboundEnabled: owner !== void 0 && provider === "openclaw",
36661
36741
  refreshOnRecordChanges: owner === BOT_CONVERSATION_OWNER.subject,
36662
36742
  conversationProjection: owner === BOT_CONVERSATION_OWNER.subject ? "record" : owner === BOT_CONVERSATION_OWNER.chat ? "chat" : "none"
36663
36743
  };
@@ -36746,10 +36826,18 @@ var BotService = class {
36746
36826
  name,
36747
36827
  provider,
36748
36828
  description,
36749
- avatar
36829
+ avatar,
36830
+ direct_chat_owner: BOT_CONVERSATION_OWNER.chat,
36831
+ request_uid: input.requestUid?.trim() || randomUUID()
36750
36832
  }, session, options.signal);
36751
36833
  } 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 });
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 });
36753
36841
  throw error;
36754
36842
  }
36755
36843
  try {
@@ -37097,6 +37185,7 @@ var BotService = class {
37097
37185
  const signature = createHmac("sha256", secret).update(`bot-directory-entry-v1:${userId}:${payload}`).digest("base64url");
37098
37186
  return [{
37099
37187
  ...bot,
37188
+ privateChatOutboundEnabled: bot.directChatAvailable && bot.provider === "openclaw",
37100
37189
  botRef: `arkme-bot-directory-entry-v1.${payload}.${signature}`
37101
37190
  }];
37102
37191
  });
@@ -37255,7 +37344,6 @@ var SubjectBotConversationAdapter = class {
37255
37344
  async send(context, contentInput, signal) {
37256
37345
  this.subjectTarget(context);
37257
37346
  const content = contentInput.trim();
37258
- if (context.reference.provider === "webhook") throw new ArkmePluginError("bot-conversation-send-unsupported", "Webhook Bot 仅接收外部系统推送", false, 400);
37259
37347
  if (content === "" || content.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("bot-conversation-content-invalid", "Bot 消息为空或超过长度限制", false, 400);
37260
37348
  let data;
37261
37349
  try {
@@ -37408,6 +37496,7 @@ var BotConversationService = class {
37408
37496
  }
37409
37497
  async send(botRef, content, options = {}) {
37410
37498
  const context = await this.context(botRef);
37499
+ if (context.reference.provider === "webhook") throw new ArkmePluginError("bot-conversation-send-unsupported", "Webhook Bot 仅接收外部系统推送", false, 400);
37411
37500
  const result = await this.adapter(context).send(context, content, options.signal);
37412
37501
  if (context.reference.target.kind === "subject") await this.invalidateRecordProjection();
37413
37502
  return result;
@@ -37636,6 +37725,44 @@ function object(value) {
37636
37725
  function first(source, keys) {
37637
37726
  return keys.map((key) => source[key]).find((value) => value !== void 0 && value !== null && value !== "");
37638
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
+ }
37639
37766
  function callRecordCandidates(raw) {
37640
37767
  const root = object(raw);
37641
37768
  const record = object(root.record);
@@ -37769,9 +37896,27 @@ function projectCallRecord(raw, viewerUserId) {
37769
37896
  text = isCaller ? "对方离线" : "未接听(离线)";
37770
37897
  break;
37771
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;
37772
37915
  return {
37773
37916
  mediaType,
37774
- text
37917
+ text,
37918
+ ...summary && !summary.includes("{{") ? { summaryText: summary } : {},
37919
+ ...summaryStatus ? { summaryStatus } : {}
37775
37920
  };
37776
37921
  }
37777
37922
  //#endregion
@@ -38122,9 +38267,11 @@ function decodeJson(value) {
38122
38267
  var CallHistoryService = class {
38123
38268
  runtime;
38124
38269
  profile;
38125
- constructor(runtime, profile) {
38270
+ media;
38271
+ constructor(runtime, profile, media) {
38126
38272
  this.runtime = runtime;
38127
38273
  this.profile = profile;
38274
+ this.media = media;
38128
38275
  }
38129
38276
  async listCallHistory(options = {}, signal) {
38130
38277
  const session = await this.runtime.requireSession();
@@ -38467,13 +38614,6 @@ var CallHistoryService = class {
38467
38614
  "callResult",
38468
38615
  "result"
38469
38616
  ]);
38470
- const summaryText = safeSummary(firstString$1(raw, [
38471
- "call_summary",
38472
- "callSummary",
38473
- "summary_text",
38474
- "summaryText",
38475
- "summary"
38476
- ]));
38477
38617
  const summaryUpdatedAtMillis = firstEpochMillis(raw, [
38478
38618
  "call_summary_updated_at",
38479
38619
  "callSummaryUpdatedAt",
@@ -38487,7 +38627,7 @@ var CallHistoryService = class {
38487
38627
  "mediaType"
38488
38628
  ]));
38489
38629
  const videoRecord = callVideoRecord(raw, mediaType, session.userId);
38490
- const transcriptSegments = this.transcriptSegments(raw, startedAtMillis).slice(0, 200);
38630
+ const transcriptSegments = this.transcriptSegments(raw, startedAtMillis, session.userId).slice(0, 200);
38491
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;
38492
38632
  const participantSeeds = this.participants(raw, session.userId);
38493
38633
  const userIds = [
@@ -38503,6 +38643,7 @@ var CallHistoryService = class {
38503
38643
  ...userId === session.userId ? { isCurrentUser: true } : {}
38504
38644
  });
38505
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]] : []))));
38506
38647
  const hangupParticipant = participants.find((participant) => participant.userId === numberValue$22(hangupUserId));
38507
38648
  return {
38508
38649
  callRef: await this.sealCallRef({
@@ -38583,7 +38724,7 @@ var CallHistoryService = class {
38583
38724
  };
38584
38725
  }).filter((item) => item !== void 0).slice(0, 50);
38585
38726
  }
38586
- transcriptSegments(raw, startedAtMillis) {
38727
+ transcriptSegments(raw, startedAtMillis, viewerUserId) {
38587
38728
  return listValue$19(firstValue(raw, [
38588
38729
  "segments",
38589
38730
  "transcript_segments",
@@ -38601,6 +38742,13 @@ var CallHistoryService = class {
38601
38742
  "segmentId",
38602
38743
  "id"
38603
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;
38604
38752
  const speakerUserId = Math.trunc(firstNumber(rawItem, [
38605
38753
  "speaker_user_id",
38606
38754
  "speakerUserId",
@@ -38625,6 +38773,7 @@ var CallHistoryService = class {
38625
38773
  const absoluteEnd = end >= 1e11;
38626
38774
  return {
38627
38775
  segmentId,
38776
+ ...audioUrl ? { audioUrl } : {},
38628
38777
  speakerDisplayName: firstString$1(rawItem, [
38629
38778
  "speaker_display_name",
38630
38779
  "speakerDisplayName",
@@ -42673,6 +42822,8 @@ function arkmeRichContentPayload(input, normalizedTextContent, mentionPayload) {
42673
42822
  }
42674
42823
  const OFFICIAL_AUTHOR_USER_ID = 11;
42675
42824
  const OFFICIAL_AUTHOR_FALLBACK_DISPLAY_NAME = "即我作者";
42825
+ const RESERVED_ASEN_BOT_UID = "asen";
42826
+ const RESERVED_ASEN_DISPLAY_NAME = "阿森";
42676
42827
  const MAX_MESSAGE_COPY_LINK_ITEMS = 100;
42677
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;
42678
42829
  const CHAT_MEMBER_REF_PREFIX = "arkme-chat-member-v1";
@@ -43730,7 +43881,7 @@ var ChatService = class {
43730
43881
  callHistory;
43731
43882
  memberEvents;
43732
43883
  favoriteStickerMutationTail = Promise.resolve();
43733
- 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)) {
43734
43885
  this.runtime = runtime;
43735
43886
  this.source = source;
43736
43887
  this.profile = profile;
@@ -44369,14 +44520,13 @@ var ChatService = class {
44369
44520
  }
44370
44521
  if (source.kind === "topic") {
44371
44522
  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)));
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)));
44380
44530
  const media = await this.media.hydrateRecordMediaPage(rawRecords, session, options.signal);
44381
44531
  const signingKey = await this.runtime.stateStore.uniqueCode();
44382
44532
  const records = rawRecords.map((raw) => {
@@ -44388,16 +44538,15 @@ var ChatService = class {
44388
44538
  });
44389
44539
  return this.withRecordTopicAssignmentRef(source, item, session.userId, signingKey, numberValue$19(objectValue$2(raw).owner_user_id), source.ownerRef);
44390
44540
  });
44391
- const nextSendAt = numberValue$19(data.next_cursor_send_at);
44392
- const nextUid = stringValue$2(data.next_cursor_record_uid).trim();
44393
44541
  return {
44394
- source: await this.source.sourceItem(source),
44542
+ source: {
44543
+ ...await this.source.sourceItem(source),
44544
+ topicKind,
44545
+ displayName: arkmeTopicDisplayName(source.displayName, topicKind)
44546
+ },
44395
44547
  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
- } } : {}
44548
+ hasMore: page.hasMore,
44549
+ ...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor }
44401
44550
  };
44402
44551
  }
44403
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;
@@ -44948,13 +45097,21 @@ var ChatService = class {
44948
45097
  parent_record_uid: parentRecordUid,
44949
45098
  limit: 100
44950
45099
  }, session, signal);
44951
- const projections = listValue$16(data.children).flatMap((child) => {
45100
+ const projections = [];
45101
+ for (const child of listValue$16(data.children)) {
44952
45102
  const extension = chatExtensionTreeItemFromData(child, parentRecordUid);
44953
- return extension === void 0 ? [] : [{
44954
- 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
+ },
44955
45112
  mediaRecord: chatExtensionMediaRecord(child, extension.recordUid)
44956
- }];
44957
- });
45113
+ });
45114
+ }
44958
45115
  const extensions = (await this.hydrateExtensionMedia(projections, session, signal)).sort((left, right) => right.sendAtMillis - left.sendAtMillis);
44959
45116
  const extensionCount = Math.max(integerLikeValue(data.total), extensions.length);
44960
45117
  return extensionCount <= 0 ? void 0 : {
@@ -45661,22 +45818,30 @@ var ChatService = class {
45661
45818
  const leadingTrim = textFormat === "markdown" ? 0 : rawText.length - rawText.trimStart().length;
45662
45819
  const uniqueRefs = [...new Set(inputs.map((input) => input.botRef.trim()))];
45663
45820
  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) => ({
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) => ({
45665
45824
  ref,
45666
45825
  value: await this.bot.openBotRef(ref, session.userId)
45667
45826
  })));
45668
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;
45669
45832
  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
- });
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
+ }
45678
45843
  }
45679
- } else {
45844
+ } else if (references.length > 0) {
45680
45845
  const bots = (await this.bot.listBots(signal === void 0 ? {} : { signal })).items;
45681
45846
  const botNameByRef = new Map(bots.filter((bot) => bot.provider === "openclaw").map((bot) => [bot.botRef, bot.name]));
45682
45847
  for (const reference of references) {
@@ -45692,8 +45857,9 @@ var ChatService = class {
45692
45857
  const refToBotId = new Map(references.map((item) => [item.ref, item.value.botId]));
45693
45858
  return [...inputs].sort((left, right) => left.startIndex - right.startIndex).map((input) => {
45694
45859
  const botRef = input.botRef.trim();
45695
- const botId = refToBotId.get(botRef);
45696
- 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);
45697
45863
  if (bot === void 0) throw new ArkmePluginError("bot-mention-not-available", "所选 Bot 当前不可 mention", false, 409);
45698
45864
  const startIndex = Math.trunc(input.startIndex) - leadingTrim;
45699
45865
  const length = Math.trunc(input.length);
@@ -45708,29 +45874,48 @@ var ChatService = class {
45708
45874
  });
45709
45875
  }
45710
45876
  async groupBotMentionSend(sourceRef, source, text, botRefs, recordUid, relationUid, session, signal, options = {}) {
45711
- 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);
45712
45882
  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) => ({
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) => ({
45714
45886
  ref,
45715
45887
  value: await this.bot.openBotRef(ref, session.userId)
45716
45888
  })));
45717
45889
  const requestedById = new Map(references.map((item) => [item.value.botId, item.ref]));
45718
45890
  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
45891
  const mentions = [];
45721
45892
  let visibleText = "";
45722
- for (const { botId, name } of groupBots) {
45723
- if (!requestedById.has(botId)) continue;
45724
- 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}`;
45725
45911
  const startIndex = visibleText.length;
45726
45912
  visibleText += `${display} `;
45727
45913
  mentions.push({
45728
- bot_uid: botId,
45729
- display_name_snapshot: name,
45914
+ bot_uid: RESERVED_ASEN_BOT_UID,
45915
+ display_name_snapshot: RESERVED_ASEN_DISPLAY_NAME,
45730
45916
  start_index: startIndex,
45731
45917
  length: display.length
45732
45918
  });
45733
- requestedById.delete(botId);
45734
45919
  }
45735
45920
  if (requestedById.size > 0) throw new ArkmePluginError("bot-mention-not-installed", "所选 Bot 未安装到该群聊", false, 409);
45736
45921
  visibleText += text;
@@ -46186,6 +46371,51 @@ var ChatService = class {
46186
46371
  async chatSourceFromBundle(bundle, session, cached, timelineItems) {
46187
46372
  return await this.source.chatSourceFromBundle(bundle, session, cached, timelineItems);
46188
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
+ }
46189
46419
  async chatTimelineItems(data, session, chatSessionUid, projectedSourceKind) {
46190
46420
  const items = [];
46191
46421
  const signingKey = await this.runtime.stateStore.uniqueCode();
@@ -46220,6 +46450,7 @@ var ChatService = class {
46220
46450
  const conversationPreview = arkmeChatConversationPreview(item);
46221
46451
  const callRecord = await this.callHistory.timelineCallRecord(item, session.userId);
46222
46452
  const senderName = stringValue$2(relation.display_name_snapshot).trim() || "Arkme用户";
46453
+ const mentions = await this.timelineMentionTargets(record, payload, session.userId, chatSessionUid);
46223
46454
  const mentionsViewer = senderUserId !== session.userId && arkmeMentionMetadataMentionsViewer(record, payload, session.userId);
46224
46455
  items.push({
46225
46456
  itemUid: uid,
@@ -46254,6 +46485,7 @@ var ChatService = class {
46254
46485
  isMe: senderUserId === session.userId,
46255
46486
  ...callRecord === void 0 ? {} : { callRecord },
46256
46487
  ...mentionsViewer ? { mentionsViewer: true } : {},
46488
+ ...mentions === void 0 ? {} : { mentions },
46257
46489
  sendAtMillis,
46258
46490
  title: stringValue$2(payload.title),
46259
46491
  textContent: stringValue$2(payload.text_content),
@@ -46856,6 +47088,7 @@ var ChatService = class {
46856
47088
  const contentBlocks = this.media.richContentBlocks(item, session.userId);
46857
47089
  const extensionProjection = this.timelineExtensionProjection(item, session.userId);
46858
47090
  const senderName = stringValue$2(relation.display_name_snapshot).trim() || "Arkme用户";
47091
+ const mentions = await this.timelineMentionTargets(record, payload, session.userId, source.ownerRef);
46859
47092
  const captureContext = timelineCaptureContext({
46860
47093
  ...record,
46861
47094
  ...payload
@@ -46894,6 +47127,7 @@ var ChatService = class {
46894
47127
  ...agentSource === void 0 ? {} : { agentSource },
46895
47128
  isMe: senderUserId === session.userId,
46896
47129
  ...callRecord === void 0 ? {} : { callRecord },
47130
+ ...mentions === void 0 ? {} : { mentions },
46897
47131
  sendAtMillis,
46898
47132
  title: stringValue$2(payload.title),
46899
47133
  textContent: stringValue$2(payload.text_content),
@@ -78979,6 +79213,10 @@ function allowedSignedImageHost$2(environment, hostname) {
78979
79213
  function allowedSignedAudioHost(environment, hostname) {
78980
79214
  return (environment === "prod" ? ["jotmo-useraudio.oss-cn-hangzhou.aliyuncs.com"] : ["jotmo-useraudio-test.oss-cn-hangzhou.aliyuncs.com"]).includes(hostname.toLowerCase());
78981
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
+ }
78982
79220
  /** Record audio can originate from the dedicated audio or generic file bucket. */
78983
79221
  function allowedSignedRecordAudioHost(environment, hostname) {
78984
79222
  return allowedSignedAudioHost(environment, hostname) || allowedSignedImageHost$2(environment, hostname);
@@ -79248,7 +79486,7 @@ var MediaService = class {
79248
79486
  }
79249
79487
  if (originalOnly && descriptor.purpose !== "original") throw new ArkmePluginError("original-unavailable", "原文件不可用,请刷新后重试;不会用预览图代替原文件", false, 404);
79250
79488
  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);
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);
79252
79490
  const response = await this.runtime.fetchImpl(url, {
79253
79491
  headers: range === void 0 ? {} : { Range: range },
79254
79492
  redirect: "error",
@@ -79745,7 +79983,7 @@ var MediaService = class {
79745
79983
  const value = safeHttpsUrl(raw);
79746
79984
  if (value === void 0) return void 0;
79747
79985
  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;
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;
79749
79987
  };
79750
79988
  const downloadUrl = trustedUrl(file.download_url ?? file.downloadUrl);
79751
79989
  const previewUrl = trustedUrl(file.preview_url ?? file.previewUrl);
@@ -84236,10 +84474,6 @@ var RelatedRecordingService = class {
84236
84474
  return `arkme-shared-recording-detail-v1.${encoded}.${createHmac("sha256", signingKey).update(encoded).digest("base64url")}`;
84237
84475
  }
84238
84476
  };
84239
- function isDshAgentInputSourceTitle(value) {
84240
- const normalized = value?.trim().toLowerCase();
84241
- return normalized === "dsh agent input" || normalized === "DSH Agent 输入".toLowerCase();
84242
- }
84243
84477
  //#endregion
84244
84478
  //#region src/services/search-service.ts
84245
84479
  function numberValue$2(value) {
@@ -84624,7 +84858,6 @@ var SearchService = class {
84624
84858
  const linkMatch = textContent.match(/https:\/\/[^\s<>()]+/u);
84625
84859
  const sourceTitle = stringValue$2(topic.title ?? chat.title).trim();
84626
84860
  const creationSource = Math.trunc(numberValue$2(core.creation_source ?? item.creation_source));
84627
- const normalizedCreationSource = creationSource > 0 ? creationSource : isDshAgentInputSourceTitle(sourceTitle) ? 3 : 0;
84628
84861
  return {
84629
84862
  recordUid,
84630
84863
  sourceKind: Math.trunc(numberValue$2(item.source_kind)),
@@ -84638,7 +84871,7 @@ var SearchService = class {
84638
84871
  ...stringValue$2(core.nickname).trim() === "" ? {} : { nickname: stringValue$2(core.nickname).trim() },
84639
84872
  ...numberValue$2(core.template_kind) <= 0 ? {} : { templateKind: Math.trunc(numberValue$2(core.template_kind)) },
84640
84873
  ...numberValue$2(core.display_kind) <= 0 ? {} : { displayKind: Math.trunc(numberValue$2(core.display_kind)) },
84641
- ...normalizedCreationSource <= 0 ? {} : { creationSource: normalizedCreationSource },
84874
+ ...creationSource <= 0 ? {} : { creationSource },
84642
84875
  ...sourceTitle === "" ? {} : { sourceTitle },
84643
84876
  media,
84644
84877
  files,
@@ -88296,7 +88529,7 @@ var ArkmeService = class {
88296
88529
  this.calendar = new CalendarService(this.runtime, this.privacy);
88297
88530
  this.wechat = new WechatService(this.runtime);
88298
88531
  this.profile = new ProfileService(this.runtime);
88299
- 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) });
88300
88533
  this.extensionReview = new ExtensionReviewService(this.runtime, this.profile, { createTextForConversation: async (recordUid, textContent) => {
88301
88534
  return await this.createTextForConversation(recordUid, textContent);
88302
88535
  } });
@@ -88635,6 +88868,7 @@ var ArkmeService = class {
88635
88868
  imageLibrary: true,
88636
88869
  sourceDirectory: true,
88637
88870
  localFirstDirectory: true,
88871
+ topicHomeVisibility: true,
88638
88872
  contactDirectoryReads: true,
88639
88873
  sourceTimeline: true,
88640
88874
  forwardContent: true,
@@ -88992,6 +89226,11 @@ var ArkmeService = class {
88992
89226
  async renameTopic(sourceRef, title) {
88993
89227
  return await this.source.renameTopic(sourceRef, title);
88994
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
+ }
88995
89234
  async dissolveTopic(sourceRef, parentSourceRef, childSourceRefs, requestId, expectedRecordCount) {
88996
89235
  return await this.source.dissolveTopic(sourceRef, parentSourceRef, childSourceRefs, requestId, expectedRecordCount);
88997
89236
  }