@senguoyun/dsh-arkme 0.1.67 → 0.1.68

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 (167) hide show
  1. package/docs/chat-add-long-article.md +39 -0
  2. package/docs/desktop-direct-recording.md +40 -0
  3. package/docs/desktop-membership-handoff.md +36 -0
  4. package/docs/desktop-regression-2026-09-19.md +77 -0
  5. package/docs/desktop-screenshot.md +59 -0
  6. package/docs/private-interaction-directory-handoff.md +95 -0
  7. package/docs/recording-calendar-coverage.md +41 -0
  8. package/docs/recording-presence-handoff.md +155 -0
  9. package/lib/client.js +49834 -45144
  10. package/lib/client.js.map +1 -1
  11. package/lib/harness-model-client.js +8 -0
  12. package/lib/harness-native-selection-client.js +72 -5
  13. package/lib/harness-sidebar-client.js +46 -2
  14. package/lib/index.js +27 -3
  15. package/lib/persistent-extension.js +1 -1
  16. package/lib/{persistent-runtime-C_Geaa4B.js → persistent-runtime-DlJzDZlE.js} +816 -94
  17. package/lib/sdk.js +8 -0
  18. package/lib/types/arkme-service.d.ts +18 -1
  19. package/lib/types/arkme-service.d.ts.map +1 -1
  20. package/lib/types/call-record-presentation.d.ts +2 -0
  21. package/lib/types/call-record-presentation.d.ts.map +1 -1
  22. package/lib/types/client/ArkmeArticlePicker.d.ts +10 -0
  23. package/lib/types/client/ArkmeArticlePicker.d.ts.map +1 -0
  24. package/lib/types/client/ArkmeCallDetailContent.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeCallRecordContent.d.ts.map +1 -1
  26. package/lib/types/client/ArkmeCallSurface.d.ts +3 -1
  27. package/lib/types/client/ArkmeCallSurface.d.ts.map +1 -1
  28. package/lib/types/client/ArkmeChatMemberActions.d.ts +2 -0
  29. package/lib/types/client/ArkmeChatMemberActions.d.ts.map +1 -1
  30. package/lib/types/client/ArkmeComposerScreenshotButton.d.ts +13 -0
  31. package/lib/types/client/ArkmeComposerScreenshotButton.d.ts.map +1 -0
  32. package/lib/types/client/ArkmeComposerSendButton.d.ts.map +1 -1
  33. package/lib/types/client/ArkmeDetailShell.d.ts +2 -1
  34. package/lib/types/client/ArkmeDetailShell.d.ts.map +1 -1
  35. package/lib/types/client/ArkmeDshMenu.d.ts +2 -0
  36. package/lib/types/client/ArkmeDshMenu.d.ts.map +1 -1
  37. package/lib/types/client/ArkmeEmojiPicker.d.ts.map +1 -1
  38. package/lib/types/client/ArkmeFileViewer.d.ts.map +1 -1
  39. package/lib/types/client/ArkmeGroupChatControls.d.ts +32 -0
  40. package/lib/types/client/ArkmeGroupChatControls.d.ts.map +1 -1
  41. package/lib/types/client/ArkmeLinkQuickView.d.ts +8 -0
  42. package/lib/types/client/ArkmeLinkQuickView.d.ts.map +1 -0
  43. package/lib/types/client/ArkmeLongArticleDialog.d.ts +6 -3
  44. package/lib/types/client/ArkmeLongArticleDialog.d.ts.map +1 -1
  45. package/lib/types/client/ArkmeLongArticleQuickView.d.ts +6 -0
  46. package/lib/types/client/ArkmeLongArticleQuickView.d.ts.map +1 -0
  47. package/lib/types/client/ArkmeMarkdownBody.d.ts +4 -1
  48. package/lib/types/client/ArkmeMarkdownBody.d.ts.map +1 -1
  49. package/lib/types/client/ArkmeMembershipDialog.d.ts +14 -0
  50. package/lib/types/client/ArkmeMembershipDialog.d.ts.map +1 -0
  51. package/lib/types/client/ArkmeMessageReadReceipt.d.ts.map +1 -1
  52. package/lib/types/client/ArkmeNoteDetails.d.ts +1 -1
  53. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  54. package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
  55. package/lib/types/client/ArkmeQuickAdd.d.ts +2 -1
  56. package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -1
  57. package/lib/types/client/ArkmeReadReceiptIcon.d.ts +7 -0
  58. package/lib/types/client/ArkmeReadReceiptIcon.d.ts.map +1 -0
  59. package/lib/types/client/ArkmeRecordingSurface.d.ts +3 -1
  60. package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
  61. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  62. package/lib/types/client/ArkmeRichText.d.ts +6 -2
  63. package/lib/types/client/ArkmeRichText.d.ts.map +1 -1
  64. package/lib/types/client/ArkmeRightPanelHeader.d.ts +3 -1
  65. package/lib/types/client/ArkmeRightPanelHeader.d.ts.map +1 -1
  66. package/lib/types/client/ArkmeScreenshotCropDialog.d.ts +9 -0
  67. package/lib/types/client/ArkmeScreenshotCropDialog.d.ts.map +1 -0
  68. package/lib/types/client/ArkmeSearchSurface.d.ts.map +1 -1
  69. package/lib/types/client/ArkmeSidebar.d.ts +9 -0
  70. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  71. package/lib/types/client/ArkmeSourceBreadcrumb.d.ts.map +1 -1
  72. package/lib/types/client/ArkmeVoiceprintSurface.d.ts.map +1 -1
  73. package/lib/types/client/ArkmeWideConversation.d.ts.map +1 -1
  74. package/lib/types/client/api.d.ts +1 -1
  75. package/lib/types/client/api.d.ts.map +1 -1
  76. package/lib/types/client/arkme-membership.d.ts +15 -0
  77. package/lib/types/client/arkme-membership.d.ts.map +1 -0
  78. package/lib/types/client/browser-screenshot.d.ts +27 -0
  79. package/lib/types/client/browser-screenshot.d.ts.map +1 -0
  80. package/lib/types/client/chat-directory-store.d.ts.map +1 -1
  81. package/lib/types/client/composer-article-store.d.ts +35 -0
  82. package/lib/types/client/composer-article-store.d.ts.map +1 -0
  83. package/lib/types/client/composer-paste-focus.d.ts +21 -0
  84. package/lib/types/client/composer-paste-focus.d.ts.map +1 -0
  85. package/lib/types/client/composer-selection-request.d.ts +2 -0
  86. package/lib/types/client/composer-selection-request.d.ts.map +1 -1
  87. package/lib/types/client/conversation-menu-layer.d.ts +9 -2
  88. package/lib/types/client/conversation-menu-layer.d.ts.map +1 -1
  89. package/lib/types/client/conversation-selector-style.d.ts +2 -2
  90. package/lib/types/client/conversation-selector-style.d.ts.map +1 -1
  91. package/lib/types/client/directory-search-layout.d.ts +1 -1
  92. package/lib/types/client/directory-search-layout.d.ts.map +1 -1
  93. package/lib/types/client/file-send-tasks.d.ts +1 -1
  94. package/lib/types/client/file-send-tasks.d.ts.map +1 -1
  95. package/lib/types/client/harness-session-hover.d.ts.map +1 -1
  96. package/lib/types/client/mention-read-status.d.ts +10 -0
  97. package/lib/types/client/mention-read-status.d.ts.map +1 -0
  98. package/lib/types/client/menu-hover-region.d.ts +5 -0
  99. package/lib/types/client/menu-hover-region.d.ts.map +1 -0
  100. package/lib/types/client/recordings/ArkmeDirectRecording.d.ts +10 -0
  101. package/lib/types/client/recordings/ArkmeDirectRecording.d.ts.map +1 -0
  102. package/lib/types/client/recordings/ArkmeRecordingNavigationHint.d.ts +9 -0
  103. package/lib/types/client/recordings/ArkmeRecordingNavigationHint.d.ts.map +1 -0
  104. package/lib/types/client/recordings/ArkmeRecordingTimeline.d.ts +4 -2
  105. package/lib/types/client/recordings/ArkmeRecordingTimeline.d.ts.map +1 -1
  106. package/lib/types/client/recordings/direct-recording-capture.d.ts +14 -0
  107. package/lib/types/client/recordings/direct-recording-capture.d.ts.map +1 -0
  108. package/lib/types/client/recordings/direct-recording-journal.d.ts +33 -0
  109. package/lib/types/client/recordings/direct-recording-journal.d.ts.map +1 -0
  110. package/lib/types/client/recordings/direct-recording-store.d.ts +68 -0
  111. package/lib/types/client/recordings/direct-recording-store.d.ts.map +1 -0
  112. package/lib/types/client/recordings/recording-breath.d.ts +6 -0
  113. package/lib/types/client/recordings/recording-breath.d.ts.map +1 -0
  114. package/lib/types/client/recordings/recording-coverage.d.ts +6 -0
  115. package/lib/types/client/recordings/recording-coverage.d.ts.map +1 -0
  116. package/lib/types/client/redesign/styles.d.ts.map +1 -1
  117. package/lib/types/client/search-source-rows.d.ts +9 -0
  118. package/lib/types/client/search-source-rows.d.ts.map +1 -0
  119. package/lib/types/client/self-topic-menu-bridge.d.ts +2 -6
  120. package/lib/types/client/self-topic-menu-bridge.d.ts.map +1 -1
  121. package/lib/types/client/self-topic-menu-hover.d.ts.map +1 -1
  122. package/lib/types/client/text-link-parser.d.ts +1 -9
  123. package/lib/types/client/text-link-parser.d.ts.map +1 -1
  124. package/lib/types/client/text-link-tlds.d.ts +1 -1
  125. package/lib/types/client/text-link-tlds.d.ts.map +1 -1
  126. package/lib/types/client/use-conversation-name-search.d.ts +9 -0
  127. package/lib/types/client/use-conversation-name-search.d.ts.map +1 -0
  128. package/lib/types/client/use-resizable-note-detail.d.ts +3 -3
  129. package/lib/types/client/use-resizable-note-detail.d.ts.map +1 -1
  130. package/lib/types/desktop-screenshot-contract.d.ts +14 -0
  131. package/lib/types/desktop-screenshot-contract.d.ts.map +1 -0
  132. package/lib/types/desktop-screenshot.d.ts +22 -0
  133. package/lib/types/desktop-screenshot.d.ts.map +1 -0
  134. package/lib/types/host-api.d.ts.map +1 -1
  135. package/lib/types/markdown.d.ts +6 -1
  136. package/lib/types/markdown.d.ts.map +1 -1
  137. package/lib/types/recording-coverage.d.ts +12 -0
  138. package/lib/types/recording-coverage.d.ts.map +1 -0
  139. package/lib/types/sdk/index.d.ts +6 -0
  140. package/lib/types/sdk/index.d.ts.map +1 -1
  141. package/lib/types/search-record-links.d.ts +3 -0
  142. package/lib/types/search-record-links.d.ts.map +1 -0
  143. package/lib/types/services/background-sound-membership-service.d.ts +5 -0
  144. package/lib/types/services/background-sound-membership-service.d.ts.map +1 -1
  145. package/lib/types/services/call-history-service.d.ts +1 -1
  146. package/lib/types/services/call-history-service.d.ts.map +1 -1
  147. package/lib/types/services/chat-service.d.ts +8 -1
  148. package/lib/types/services/chat-service.d.ts.map +1 -1
  149. package/lib/types/services/membership-service.d.ts +13 -0
  150. package/lib/types/services/membership-service.d.ts.map +1 -0
  151. package/lib/types/services/record-service.d.ts +1 -1
  152. package/lib/types/services/record-service.d.ts.map +1 -1
  153. package/lib/types/services/recording-service.d.ts.map +1 -1
  154. package/lib/types/services/search-service.d.ts.map +1 -1
  155. package/lib/types/services/service.d.ts +1 -1
  156. package/lib/types/services/service.d.ts.map +1 -1
  157. package/lib/types/services/source-service.d.ts +7 -1
  158. package/lib/types/services/source-service.d.ts.map +1 -1
  159. package/lib/types/state-store.d.ts +1 -1
  160. package/lib/types/state-store.d.ts.map +1 -1
  161. package/lib/types/text-link-parser.d.ts +10 -0
  162. package/lib/types/text-link-parser.d.ts.map +1 -0
  163. package/lib/types/text-link-tlds.d.ts +2 -0
  164. package/lib/types/text-link-tlds.d.ts.map +1 -0
  165. package/lib/types/types.d.ts +53 -3
  166. package/lib/types/types.d.ts.map +1 -1
  167. package/package.json +5 -5
@@ -2,12 +2,13 @@ import { createRequire } from "node:module";
2
2
  import { defineTool } from "@deepseek-ai/dsh-tools";
3
3
  import { chmodSync, constants, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicKey, randomBytes, randomUUID, timingSafeEqual, verify } from "node:crypto";
5
- import { chmod as chmod$1, copyFile, lstat, mkdir, open, opendir, readFile, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
5
+ import { access, chmod as chmod$1, copyFile, lstat, mkdir, mkdtemp, open, opendir, readFile, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
6
+ import { execFile } from "node:child_process";
6
7
  import minpath, { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
7
8
  import { fileURLToPath, fileURLToPath as urlToPath } from "node:url";
8
9
  import { gunzipSync, gzipSync } from "node:zlib";
9
10
  import { request } from "node:http";
10
- import { homedir } from "node:os";
11
+ import { homedir, tmpdir } from "node:os";
11
12
  import { request as request$1 } from "node:https";
12
13
  import { Readable } from "node:stream";
13
14
  import { BlockList, isIP } from "node:net";
@@ -1936,8 +1937,137 @@ var ServiceRuntime = class {
1936
1937
  }
1937
1938
  };
1938
1939
  //#endregion
1940
+ //#region src/desktop-screenshot.ts
1941
+ const executable = "/usr/sbin/screencapture";
1942
+ const pngSignature = Buffer.from([
1943
+ 137,
1944
+ 80,
1945
+ 78,
1946
+ 71,
1947
+ 13,
1948
+ 10,
1949
+ 26,
1950
+ 10
1951
+ ]);
1952
+ /** Always interactive: no full-screen capture, shell, caller paths or clipboard mutation. */
1953
+ const captureProcess = (path, signal) => new Promise((resolve, reject) => {
1954
+ execFile(executable, [
1955
+ "-i",
1956
+ "-s",
1957
+ "-x",
1958
+ "-t",
1959
+ "png",
1960
+ path
1961
+ ], {
1962
+ signal,
1963
+ maxBuffer: 64 * 1024
1964
+ }, (error, _stdout, stderr) => {
1965
+ if (signal.aborted) {
1966
+ reject(signal.reason);
1967
+ return;
1968
+ }
1969
+ if (error !== null && typeof error.code !== "number") {
1970
+ reject(error);
1971
+ return;
1972
+ }
1973
+ resolve({
1974
+ code: typeof error?.code === "number" ? error.code : 0,
1975
+ stderr
1976
+ });
1977
+ });
1978
+ });
1979
+ var ArkmeDesktopScreenshot = class {
1980
+ options;
1981
+ active;
1982
+ constructor(options) {
1983
+ this.options = options;
1984
+ }
1985
+ async capability() {
1986
+ if ((this.options.platform ?? process.platform) !== "darwin") return {
1987
+ available: false,
1988
+ reason: "截屏暂仅支持 macOS,可使用系统截屏后粘贴图片"
1989
+ };
1990
+ return await (this.options.executableAvailable ?? (async () => {
1991
+ try {
1992
+ await access(executable, constants.X_OK);
1993
+ return true;
1994
+ } catch {
1995
+ return false;
1996
+ }
1997
+ }))() ? { available: true } : {
1998
+ available: false,
1999
+ reason: "系统截屏暂不可用,可使用系统截屏后粘贴图片"
2000
+ };
2001
+ }
2002
+ cancel() {
2003
+ this.active?.abort();
2004
+ }
2005
+ async capture(expectedUserId, requestSignal) {
2006
+ if (!Number.isSafeInteger(expectedUserId) || expectedUserId <= 0) throw new ArkmePluginError("screenshot-account-required", "请先登录后再截屏", false, 400);
2007
+ if (this.active !== void 0) throw new ArkmePluginError("screenshot-busy", "正在截屏,请先完成或按 Esc 取消", false, 409);
2008
+ const controller = new AbortController();
2009
+ this.active = controller;
2010
+ const abort = () => controller.abort();
2011
+ requestSignal?.addEventListener("abort", abort, { once: true });
2012
+ if (requestSignal?.aborted) abort();
2013
+ let timedOut = false;
2014
+ const timer = setTimeout(() => {
2015
+ timedOut = true;
2016
+ abort();
2017
+ }, this.options.timeoutMs ?? 12e4);
2018
+ let directory;
2019
+ const check = async () => {
2020
+ controller.signal.throwIfAborted();
2021
+ if (await this.options.currentUser() !== expectedUserId) throw new ArkmePluginError("screenshot-account-changed", "账号已切换,请重新截屏", false, 409);
2022
+ controller.signal.throwIfAborted();
2023
+ };
2024
+ try {
2025
+ await check();
2026
+ const capability = await this.capability();
2027
+ if (!capability.available) throw new ArkmePluginError("screenshot-unavailable", capability.reason, false, 501);
2028
+ directory = await mkdtemp(join(tmpdir(), "arkme-screenshot-"));
2029
+ const path = join(directory, "capture.png");
2030
+ controller.signal.throwIfAborted();
2031
+ const result = await (this.options.capture ?? captureProcess)(path, controller.signal);
2032
+ await check();
2033
+ const info = await stat(path).catch((error) => {
2034
+ if (error.code === "ENOENT") return void 0;
2035
+ throw error;
2036
+ });
2037
+ if (info === void 0 && (result.code === 0 || result.code === 1) && result.stderr.trim() === "") return { status: "cancelled" };
2038
+ if (result.code !== 0 || info === void 0) throw new ArkmePluginError("screenshot-permission", "无法截屏。请在系统设置 → 隐私与安全性 → 屏幕录制中允许当前运行环境,然后重试", false, 403);
2039
+ if (!info.isFile() || info.size > this.options.maxImageBytes()) throw new ArkmePluginError("screenshot-too-large", "截图过大,请缩小框选区域后重试", false, 413);
2040
+ const bytes = await readFile(path);
2041
+ if (!bytes.subarray(0, 8).equals(pngSignature)) throw new ArkmePluginError("screenshot-invalid", "未能生成有效截图,请重试", true, 502);
2042
+ await check();
2043
+ return {
2044
+ status: "captured",
2045
+ fileName: `截图-${Date.now()}.png`,
2046
+ mimeType: "image/png",
2047
+ contentBase64: bytes.toString("base64")
2048
+ };
2049
+ } catch (error) {
2050
+ if (timedOut) throw new ArkmePluginError("screenshot-timeout", "截屏已超时,请重新点击截屏", true, 408);
2051
+ if (controller.signal.aborted) return { status: "cancelled" };
2052
+ if (error instanceof ArkmePluginError) throw error;
2053
+ throw new ArkmePluginError("screenshot-failed", "截屏失败,请检查系统屏幕录制权限后重试", true, 502);
2054
+ } finally {
2055
+ clearTimeout(timer);
2056
+ requestSignal?.removeEventListener("abort", abort);
2057
+ try {
2058
+ if (directory !== void 0) await rm(directory, {
2059
+ recursive: true,
2060
+ force: true
2061
+ });
2062
+ } finally {
2063
+ if (this.active === controller) this.active = void 0;
2064
+ }
2065
+ }
2066
+ }
2067
+ };
2068
+ //#endregion
1939
2069
  //#region src/services/record-edit-history-service.ts
1940
- function invalid$4() {
2070
+ function invalid$5() {
1941
2071
  throw new ArkmePluginError("record-edit-history-invalid", "编辑记录暂不可用,请刷新后重试", true, 502);
1942
2072
  }
1943
2073
  var RecordEditHistoryService = class {
@@ -1948,9 +2078,9 @@ var RecordEditHistoryService = class {
1948
2078
  this.projector = projector;
1949
2079
  }
1950
2080
  async page(target, cursorEditAt = 0, signal) {
1951
- if (!Number.isSafeInteger(cursorEditAt) || cursorEditAt < 0 || target.recordUid.trim() === "") invalid$4();
2081
+ if (!Number.isSafeInteger(cursorEditAt) || cursorEditAt < 0 || target.recordUid.trim() === "") invalid$5();
1952
2082
  const session = await this.runtime.requireSession();
1953
- if (session.userId !== target.viewerUserId) invalid$4();
2083
+ if (session.userId !== target.viewerUserId) invalid$5();
1954
2084
  const window = {
1955
2085
  limit: 50,
1956
2086
  ...cursorEditAt === 0 ? {} : { cursor_edit_at: cursorEditAt }
@@ -1963,22 +2093,22 @@ var RecordEditHistoryService = class {
1963
2093
  rel_uid: target.relationUid,
1964
2094
  ...window
1965
2095
  }, session, signal));
1966
- if (signal?.aborted || (await this.runtime.requireSession()).userId !== session.userId) invalid$4();
1967
- if (!Array.isArray(data.items) || typeof data.has_more !== "boolean") invalid$4();
2096
+ if (signal?.aborted || (await this.runtime.requireSession()).userId !== session.userId) invalid$5();
2097
+ if (!Array.isArray(data.items) || typeof data.has_more !== "boolean") invalid$5();
1968
2098
  const next = data.next_cursor_edit_at;
1969
- if (data.has_more && (typeof next !== "number" || !Number.isSafeInteger(next) || next <= 0 || cursorEditAt > 0 && next >= cursorEditAt)) invalid$4();
2099
+ if (data.has_more && (typeof next !== "number" || !Number.isSafeInteger(next) || next <= 0 || cursorEditAt > 0 && next >= cursorEditAt)) invalid$5();
1970
2100
  const seen = /* @__PURE__ */ new Set();
1971
2101
  const snapshots = data.items.flatMap((raw) => {
1972
2102
  const revision = objectValue$2(raw);
1973
- if (revision.record_uid !== target.recordUid || typeof revision.revision_uid !== "string" || revision.revision_uid.trim() === "") invalid$4();
2103
+ if (revision.record_uid !== target.recordUid || typeof revision.revision_uid !== "string" || revision.revision_uid.trim() === "") invalid$5();
1974
2104
  if (revision.revision_type !== 1 && revision.revision_type !== 5) return [];
1975
- if (typeof revision.edit_at !== "number" || !Number.isSafeInteger(revision.edit_at) || revision.edit_at <= 0 || !Number.isFinite(new Date(revision.edit_at).getTime()) || typeof revision.text_content !== "string") invalid$4();
2105
+ if (typeof revision.edit_at !== "number" || !Number.isSafeInteger(revision.edit_at) || revision.edit_at <= 0 || !Number.isFinite(new Date(revision.edit_at).getTime()) || typeof revision.text_content !== "string") invalid$5();
1976
2106
  if (seen.has(revision.revision_uid)) return [];
1977
2107
  seen.add(revision.revision_uid);
1978
2108
  return [revision];
1979
2109
  });
1980
2110
  const contents = snapshots.length === 0 ? [] : await this.projector.projectPage(snapshots, target, signal);
1981
- if (signal?.aborted || (await this.runtime.requireSession()).userId !== session.userId || contents.length !== snapshots.length) invalid$4();
2111
+ if (signal?.aborted || (await this.runtime.requireSession()).userId !== session.userId || contents.length !== snapshots.length) invalid$5();
1982
2112
  const items = snapshots.map((revision, index) => ({
1983
2113
  revisionUid: revision.revision_uid,
1984
2114
  kind: revision.revision_type === 5 ? "original" : "manual",
@@ -12498,7 +12628,7 @@ function tokenizeThematicBreak(effects, ok, nok) {
12498
12628
  * } from 'micromark-util-types'
12499
12629
  */
12500
12630
  /** @type {Construct} */
12501
- const list$2 = {
12631
+ const list$3 = {
12502
12632
  continuation: { tokenize: tokenizeListContinuation },
12503
12633
  exit: tokenizeListEnd,
12504
12634
  name: "list",
@@ -12618,7 +12748,7 @@ function tokenizeListContinuation(effects, ok, nok) {
12618
12748
  function notInCurrentItem(code) {
12619
12749
  self.containerState._closeFlow = true;
12620
12750
  self.interrupt = void 0;
12621
- return factorySpace(effects, effects.attempt(list$2, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
12751
+ return factorySpace(effects, effects.attempt(list$3, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
12622
12752
  }
12623
12753
  }
12624
12754
  /**
@@ -13051,19 +13181,19 @@ var constructs_exports = /* @__PURE__ */ __exportAll({
13051
13181
  });
13052
13182
  /** @satisfies {Extension['document']} */
13053
13183
  const document$1 = {
13054
- [42]: list$2,
13055
- [43]: list$2,
13056
- [45]: list$2,
13057
- [48]: list$2,
13058
- [49]: list$2,
13059
- [50]: list$2,
13060
- [51]: list$2,
13061
- [52]: list$2,
13062
- [53]: list$2,
13063
- [54]: list$2,
13064
- [55]: list$2,
13065
- [56]: list$2,
13066
- [57]: list$2,
13184
+ [42]: list$3,
13185
+ [43]: list$3,
13186
+ [45]: list$3,
13187
+ [48]: list$3,
13188
+ [49]: list$3,
13189
+ [50]: list$3,
13190
+ [51]: list$3,
13191
+ [52]: list$3,
13192
+ [53]: list$3,
13193
+ [54]: list$3,
13194
+ [55]: list$3,
13195
+ [56]: list$3,
13196
+ [57]: list$3,
13067
13197
  [62]: blockQuote
13068
13198
  };
13069
13199
  /** @satisfies {Extension['contentInitial']} */
@@ -17401,7 +17531,7 @@ function checkRule(state) {
17401
17531
  * @param {Info} info
17402
17532
  * @returns {string}
17403
17533
  */
17404
- function list$1(node, parent, state, info) {
17534
+ function list$2(node, parent, state, info) {
17405
17535
  const exit = state.enter("list");
17406
17536
  const bulletCurrent = state.bulletCurrent;
17407
17537
  /** @type {string} */
@@ -17676,7 +17806,7 @@ const handle = {
17676
17806
  inlineCode,
17677
17807
  link,
17678
17808
  linkReference,
17679
- list: list$1,
17809
+ list: list$2,
17680
17810
  listItem,
17681
17811
  paragraph,
17682
17812
  root,
@@ -20532,23 +20662,23 @@ function arkmeArticleImageReference(value) {
20532
20662
  //#endregion
20533
20663
  //#region src/record-deletion-ref.ts
20534
20664
  const prefix$1 = "arkme-record-delete-v1";
20535
- const invalid$3 = () => new ArkmePluginError("record-delete-reference-invalid", "删除引用已失效,请刷新后重新选择", false, 409);
20665
+ const invalid$4 = () => new ArkmePluginError("record-delete-reference-invalid", "删除引用已失效,请刷新后重新选择", false, 409);
20536
20666
  function sealRecordDeletionRef(ref, key) {
20537
20667
  const payload = Buffer.from(JSON.stringify(ref)).toString("base64url");
20538
20668
  return `${prefix$1}.${payload}.${createHmac("sha256", key).update(`${prefix$1}.${payload}`).digest("base64url")}`;
20539
20669
  }
20540
20670
  function openRecordDeletionRef(value, key) {
20541
- if (typeof value !== "string" || value.length > 2048) throw invalid$3();
20671
+ if (typeof value !== "string" || value.length > 2048) throw invalid$4();
20542
20672
  const [kind, payload, signature, extra] = value.split(".");
20543
- if (kind !== prefix$1 || !payload || !signature || extra !== void 0) throw invalid$3();
20673
+ if (kind !== prefix$1 || !payload || !signature || extra !== void 0) throw invalid$4();
20544
20674
  const actual = Buffer.from(signature, "base64url");
20545
20675
  const expected = createHmac("sha256", key).update(`${prefix$1}.${payload}`).digest();
20546
- if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) throw invalid$3();
20676
+ if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) throw invalid$4();
20547
20677
  let ref;
20548
20678
  try {
20549
20679
  ref = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
20550
20680
  } catch {
20551
- throw invalid$3();
20681
+ throw invalid$4();
20552
20682
  }
20553
20683
  if (!ref || !Number.isSafeInteger(ref.userId) || ref.userId <= 0 || !Number.isSafeInteger(ref.recordVersion) || ref.recordVersion <= 0 || ![
20554
20684
  "private_chat",
@@ -20556,7 +20686,7 @@ function openRecordDeletionRef(value, key) {
20556
20686
  "send_to_self",
20557
20687
  "default_category",
20558
20688
  "topic"
20559
- ].includes(ref.sourceKind) || typeof ref.sourceOwnerRef !== "string" || !ref.sourceOwnerRef.trim() || typeof ref.recordUid !== "string" || !ref.recordUid.trim()) throw invalid$3();
20689
+ ].includes(ref.sourceKind) || typeof ref.sourceOwnerRef !== "string" || !ref.sourceOwnerRef.trim() || typeof ref.recordUid !== "string" || !ref.recordUid.trim()) throw invalid$4();
20560
20690
  return ref;
20561
20691
  }
20562
20692
  /** Only authoritative Record snapshots can grant deletion; missing versions never fall back to a relation version. */
@@ -20669,31 +20799,31 @@ var RecordDeletionService = class {
20669
20799
  };
20670
20800
  //#endregion
20671
20801
  //#region src/record-topic-assignment-ref.ts
20672
- const invalid$2 = () => new ArkmePluginError("record-topic-reference-invalid", "快记归属引用已失效,请刷新后重新选择", false, 409);
20802
+ const invalid$3 = () => new ArkmePluginError("record-topic-reference-invalid", "快记归属引用已失效,请刷新后重新选择", false, 409);
20673
20803
  const prefix = "arkme-record-topic-v1";
20674
20804
  function sealRecordTopicAssignmentRef(reference, key) {
20675
20805
  const payload = Buffer.from(JSON.stringify(reference)).toString("base64url");
20676
20806
  return `${prefix}.${payload}.${createHmac("sha256", key).update(`${prefix}.${payload}`).digest("base64url")}`;
20677
20807
  }
20678
20808
  function openRecordTopicAssignmentRef(value, key) {
20679
- if (typeof value !== "string" || value.length > 2048) throw invalid$2();
20809
+ if (typeof value !== "string" || value.length > 2048) throw invalid$3();
20680
20810
  const parts = value.split(".");
20681
- if (parts.length !== 3 || parts[0] !== prefix) throw invalid$2();
20811
+ if (parts.length !== 3 || parts[0] !== prefix) throw invalid$3();
20682
20812
  const payload = parts[1];
20683
20813
  const signature = Buffer.from(parts[2], "base64url");
20684
20814
  const expected = createHmac("sha256", key).update(`${prefix}.${payload}`).digest();
20685
- if (signature.length !== expected.length || !timingSafeEqual(signature, expected)) throw invalid$2();
20815
+ if (signature.length !== expected.length || !timingSafeEqual(signature, expected)) throw invalid$3();
20686
20816
  let parsed;
20687
20817
  try {
20688
20818
  parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
20689
20819
  } catch {
20690
- throw invalid$2();
20820
+ throw invalid$3();
20691
20821
  }
20692
20822
  if (!parsed || !Number.isSafeInteger(parsed.userId) || parsed.userId <= 0 || ![
20693
20823
  "send_to_self",
20694
20824
  "default_category",
20695
20825
  "topic"
20696
- ].includes(parsed.sourceKind) || typeof parsed.sourceOwnerRef !== "string" || parsed.sourceOwnerRef.trim() === "" || typeof parsed.recordUid !== "string" || parsed.recordUid.trim() === "" || typeof parsed.sourceTopicUid !== "string") throw invalid$2();
20826
+ ].includes(parsed.sourceKind) || typeof parsed.sourceOwnerRef !== "string" || parsed.sourceOwnerRef.trim() === "" || typeof parsed.recordUid !== "string" || parsed.recordUid.trim() === "" || typeof parsed.sourceTopicUid !== "string") throw invalid$3();
20697
20827
  return parsed;
20698
20828
  }
20699
20829
  //#endregion
@@ -34752,6 +34882,16 @@ var BackgroundSoundMembershipService = class {
34752
34882
  this.runtime = runtime;
34753
34883
  }
34754
34884
  async current(options = {}) {
34885
+ const { raw, userId } = await this.readCurrent(options);
34886
+ const memberType = memberTypeFromResponse(raw);
34887
+ return {
34888
+ userId,
34889
+ memberType,
34890
+ eligible: memberType > 0
34891
+ };
34892
+ }
34893
+ /** Shared account-bound read for the membership UI and background-sound eligibility. */
34894
+ async readCurrent(options = {}) {
34755
34895
  const expectedUserId = options.expectedUserId;
34756
34896
  if (expectedUserId !== void 0 && (!Number.isSafeInteger(expectedUserId) || expectedUserId <= 0)) throw new ArkmePluginError("background-sound-membership-expected-user-invalid", "背景音会员资格的预期账号无效", false, 400);
34757
34897
  const session = await this.runtime.requireSession();
@@ -34759,11 +34899,87 @@ var BackgroundSoundMembershipService = class {
34759
34899
  const raw = await this.runtime.authenticatedAuthReadPost("/api/v1/premium/get/member", {}, session, options.signal);
34760
34900
  const currentSession = await this.runtime.requireSession();
34761
34901
  if (currentSession.userId !== session.userId || expectedUserId !== void 0 && currentSession.userId !== expectedUserId) throw new ArkmePluginError("background-sound-membership-account-changed", "账号已切换,本次会员资格查询结果已丢弃", false, 409);
34762
- const memberType = memberTypeFromResponse(raw);
34902
+ memberTypeFromResponse(raw);
34763
34903
  return {
34764
- userId: session.userId,
34904
+ raw,
34905
+ userId: session.userId
34906
+ };
34907
+ }
34908
+ };
34909
+ //#endregion
34910
+ //#region src/services/membership-service.ts
34911
+ function invalid$2() {
34912
+ throw new ArkmePluginError("membership-contract-invalid", "会员信息暂时无法确认,请稍后重试", true, 502);
34913
+ }
34914
+ function integer$1(value) {
34915
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return invalid$2();
34916
+ return value;
34917
+ }
34918
+ function timestamp(value) {
34919
+ if (value === void 0 || value === null || value === 0) return null;
34920
+ const micros = integer$1(value);
34921
+ const millis = Math.floor(micros / 1e3);
34922
+ if (millis < Date.UTC(2e3, 0) || millis > Date.UTC(3e3, 0)) return invalid$2();
34923
+ return millis;
34924
+ }
34925
+ function parseMembership(raw, userId, now = Date.now()) {
34926
+ const memberType = integer$1(raw.member_type);
34927
+ if (memberType > 2) return invalid$2();
34928
+ const expireAtMillis = timestamp(raw.expire_at);
34929
+ return {
34930
+ userId,
34931
+ memberType,
34932
+ expireAtMillis,
34933
+ gifted: raw.is_gifted === 1,
34934
+ lifetime: memberType === 2 && expireAtMillis !== null && expireAtMillis > now + 100 * 365.25 * 864e5
34935
+ };
34936
+ }
34937
+ function parseMembershipProducts(raw) {
34938
+ if (!Array.isArray(raw.data_ls)) return invalid$2();
34939
+ const products = raw.data_ls.map((entry) => {
34940
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return invalid$2();
34941
+ const row = entry;
34942
+ const memberType = integer$1(row.spu_code);
34943
+ if (memberType !== 1 && memberType !== 2) return invalid$2();
34944
+ if (typeof row.name !== "string" || !row.name.trim() || typeof row.is_period !== "boolean") return invalid$2();
34945
+ const alipayId = integer$1(row.alipay_product_id);
34946
+ const wechatId = integer$1(row.wechat_product_id);
34947
+ if (alipayId === 0 && wechatId === 0) return invalid$2();
34948
+ return {
34949
+ id: `${memberType}:${alipayId}:${wechatId}:${row.is_period ? "recurring" : "once"}`,
34765
34950
  memberType,
34766
- eligible: memberType > 0
34951
+ name: row.name.trim(),
34952
+ priceMinor: integer$1(row.money),
34953
+ currency: "CNY",
34954
+ recurring: row.is_period,
34955
+ monthCount: integer$1(row.month_count)
34956
+ };
34957
+ });
34958
+ if (new Set(products.map((p) => p.id)).size !== products.length) return invalid$2();
34959
+ return products.sort((a, b) => a.memberType - b.memberType || a.monthCount - b.monthCount || a.priceMinor - b.priceMinor);
34960
+ }
34961
+ /** Membership is distinct from managed-AI balance billing. Only read endpoints are exposed. */
34962
+ var MembershipService = class {
34963
+ runtime;
34964
+ membership;
34965
+ constructor(runtime) {
34966
+ this.runtime = runtime;
34967
+ this.membership = new BackgroundSoundMembershipService(runtime);
34968
+ }
34969
+ async current(expectedUserId) {
34970
+ const { raw, userId } = await this.membership.readCurrent({ expectedUserId });
34971
+ return parseMembership(raw, userId);
34972
+ }
34973
+ async catalog(expectedUserId) {
34974
+ if (!Number.isSafeInteger(expectedUserId) || expectedUserId <= 0) return invalid$2();
34975
+ const session = await this.runtime.requireSession();
34976
+ if (session.userId !== expectedUserId) throw new ArkmePluginError("membership-account-changed", "账号已切换,请重新打开会员页面", false, 409);
34977
+ const raw = await this.runtime.authenticatedAuthReadPost("/api/v1/payment/get/sale-product", {}, session);
34978
+ if ((await this.runtime.requireSession()).userId !== session.userId) throw new ArkmePluginError("membership-account-changed", "账号已切换,已丢弃套餐结果", false, 409);
34979
+ return {
34980
+ userId: session.userId,
34981
+ products: parseMembershipProducts(raw),
34982
+ checkout: "mobile-app-only"
34767
34983
  };
34768
34984
  }
34769
34985
  };
@@ -35677,9 +35893,9 @@ function arkmePickedFileKind(mimeType, fileName = "") {
35677
35893
  }
35678
35894
  //#endregion
35679
35895
  //#region src/call-record-presentation.ts
35680
- function object$1(value) {
35896
+ function object$2(value) {
35681
35897
  if (typeof value === "string") try {
35682
- return object$1(JSON.parse(value));
35898
+ return object$2(JSON.parse(value));
35683
35899
  } catch {
35684
35900
  return {};
35685
35901
  }
@@ -35688,6 +35904,27 @@ function object$1(value) {
35688
35904
  function first$1(source, keys) {
35689
35905
  return keys.map((key) => source[key]).find((value) => value !== void 0 && value !== null && value !== "");
35690
35906
  }
35907
+ /** Only explicit template identities, never names inferred from summary prose. */
35908
+ function callSummaryUserIds(source) {
35909
+ const template = String(first$1(source, [
35910
+ "smt",
35911
+ "call_summary_template",
35912
+ "callSummaryTemplate",
35913
+ "summary_template",
35914
+ "summaryTemplate"
35915
+ ]) ?? "");
35916
+ const users = object$2(first$1(source, [
35917
+ "ssu",
35918
+ "summary_speaker_user_ids",
35919
+ "call_summary_speaker_user_ids"
35920
+ ]));
35921
+ const ids = /* @__PURE__ */ new Set();
35922
+ for (const match of template.matchAll(/\{\{(user|speaker):([^{}]+)\}\}/g)) {
35923
+ const userId = Number(match[1] === "user" ? match[2] : users[match[0]] ?? users[match[2]]);
35924
+ if (Number.isSafeInteger(userId) && userId > 0) ids.add(userId);
35925
+ }
35926
+ return [...ids];
35927
+ }
35691
35928
  /** Flutter-compatible summary templates, resolved on the host before projection. */
35692
35929
  function renderCallRecordSummary(source, viewerUserId, displayNames = /* @__PURE__ */ new Map()) {
35693
35930
  const fallback = String(first$1(source, [
@@ -35705,12 +35942,12 @@ function renderCallRecordSummary(source, viewerUserId, displayNames = /* @__PURE
35705
35942
  "summary_template",
35706
35943
  "summaryTemplate"
35707
35944
  ]) ?? "").trim();
35708
- const labels = object$1(first$1(source, [
35945
+ const labels = object$2(first$1(source, [
35709
35946
  "ssl",
35710
35947
  "summary_speaker_labels",
35711
35948
  "call_summary_speaker_labels"
35712
35949
  ]));
35713
- const users = object$1(first$1(source, [
35950
+ const users = object$2(first$1(source, [
35714
35951
  "ssu",
35715
35952
  "summary_speaker_user_ids",
35716
35953
  "call_summary_speaker_user_ids"
@@ -35727,10 +35964,10 @@ function renderCallRecordSummary(source, viewerUserId, displayNames = /* @__PURE
35727
35964
  return text.includes("{{") ? "" : text;
35728
35965
  }
35729
35966
  function callRecordCandidates(raw) {
35730
- const root = object$1(raw);
35731
- const record = object$1(root.record);
35732
- const core = object$1(root.record_core);
35733
- const payload = object$1(record.payload ?? root.payload);
35967
+ const root = object$2(raw);
35968
+ const record = object$2(root.record);
35969
+ const core = object$2(root.record_core);
35970
+ const payload = object$2(record.payload ?? root.payload);
35734
35971
  const candidates = [];
35735
35972
  const seen = /* @__PURE__ */ new Set();
35736
35973
  const visit = (value, depth) => {
@@ -35745,7 +35982,7 @@ function callRecordCandidates(raw) {
35745
35982
  "payload",
35746
35983
  "record",
35747
35984
  "record_core"
35748
- ]) if (value[key] != null) visit(object$1(value[key]), depth + 1);
35985
+ ]) if (value[key] != null) visit(object$2(value[key]), depth + 1);
35749
35986
  };
35750
35987
  for (const value of [
35751
35988
  payload,
@@ -35768,10 +36005,10 @@ function callRecordSource(raw) {
35768
36005
  "call_detail",
35769
36006
  "callDetail"
35770
36007
  ]) {
35771
- const nested = object$1(value[key]);
36008
+ const nested = object$2(value[key]);
35772
36009
  if (Object.keys(nested).length > 0) return nested;
35773
36010
  }
35774
- const extra = value.extra == null ? void 0 : details(object$1(value.extra), depth + 1);
36011
+ const extra = value.extra == null ? void 0 : details(object$2(value.extra), depth + 1);
35775
36012
  if (extra) return extra;
35776
36013
  if (first$1(value, [
35777
36014
  "mt",
@@ -35801,7 +36038,7 @@ function callRecordRoomId(raw) {
35801
36038
  ]);
35802
36039
  if (typeof room === "string" && room.trim()) return room.trim();
35803
36040
  for (const candidate of callRecordCandidates(raw)) {
35804
- const anchor = object$1(candidate.structured_anchor ?? candidate.structuredAnchor ?? candidate);
36041
+ const anchor = object$2(candidate.structured_anchor ?? candidate.structuredAnchor ?? candidate);
35805
36042
  if (Number(anchor.anchor_kind ?? anchor.anchorKind) !== 2) continue;
35806
36043
  const uid = anchor.anchor_uid ?? anchor.anchorUid;
35807
36044
  if (typeof uid === "string" && uid.trim()) return uid.trim();
@@ -35858,7 +36095,7 @@ function callRecordConversationPreview(raw, viewerUserId = 0) {
35858
36095
  "payload_kind",
35859
36096
  "payloadKind"
35860
36097
  ]));
35861
- const anchor = object$1(value.structured_anchor ?? value.structuredAnchor ?? value);
36098
+ const anchor = object$2(value.structured_anchor ?? value.structuredAnchor ?? value);
35862
36099
  return template === 9 || (!Number.isFinite(template) || template === 5) && Number(anchor.anchor_kind ?? anchor.anchorKind) === 2;
35863
36100
  }))) return void 0;
35864
36101
  if (!source) return "[通话]";
@@ -36397,6 +36634,46 @@ var SourceService = class {
36397
36634
  if (sourceKind !== 3) return void 0;
36398
36635
  return (await this.chatSourcesBySessionUids([ownerRef], signal)).get(ownerRef);
36399
36636
  }
36637
+ /** Search one canonical directory page, including names with no matching messages. */
36638
+ async searchConversationNames(options) {
36639
+ const keyword = options.query.trim().normalize("NFKC").toLocaleLowerCase();
36640
+ if (keyword === "") throw new ArkmePluginError("conversation-query-empty", "搜索关键词不能为空", false);
36641
+ const session = await this.runtime.requireSession();
36642
+ const page = await this.listSources("root", {
36643
+ limit: 50,
36644
+ firstPaint: true,
36645
+ ...options.cursor === void 0 ? {} : { cursor: options.cursor },
36646
+ ...options.signal === void 0 ? {} : { signal: options.signal }
36647
+ });
36648
+ options.signal?.throwIfAborted();
36649
+ if (page.hasMore && (!page.nextCursor || page.nextCursor === options.cursor)) throw new ArkmePluginError("conversation-search-incomplete", "会话名称查找未完成,请重试", true);
36650
+ const peerIds = page.items.flatMap((item) => item.kind === "private_chat" && item.peerUserId !== void 0 ? [item.peerUserId] : []);
36651
+ const profiles = peerIds.length === 0 ? /* @__PURE__ */ new Map() : await this.profile.publicProfileSummariesByUserIds(peerIds, session, options.signal);
36652
+ const items = [];
36653
+ for (const directorySource of page.items) {
36654
+ const nickname = profiles.get(directorySource.peerUserId ?? 0)?.nickname.trim() || directorySource.privateNickname;
36655
+ const targetSource = nickname ? {
36656
+ ...directorySource,
36657
+ privateNickname: nickname
36658
+ } : directorySource;
36659
+ if (targetSource.kind !== "private_chat" && targetSource.kind !== "group_chat") continue;
36660
+ if (![targetSource.displayName, targetSource.privateNickname ?? ""].some((name) => name.normalize("NFKC").toLocaleLowerCase().includes(keyword))) continue;
36661
+ const ref = await this.openSourceRef(targetSource.sourceRef, session.userId);
36662
+ items.push({
36663
+ sourceKind: 3,
36664
+ sourceUid: ref.ownerRef,
36665
+ title: targetSource.displayName,
36666
+ targetSource,
36667
+ ...targetSource.privateNickname ? { nickname: targetSource.privateNickname } : {}
36668
+ });
36669
+ }
36670
+ options.signal?.throwIfAborted();
36671
+ return {
36672
+ items,
36673
+ hasMore: page.hasMore,
36674
+ ...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor }
36675
+ };
36676
+ }
36400
36677
  /** Resolve a bounded set in one directory walk, reusing viewer-scoped source projections. */
36401
36678
  async chatSourcesBySessionUids(uids, signal) {
36402
36679
  const session = await this.runtime.requireSession();
@@ -37310,7 +37587,8 @@ var SourceService = class {
37310
37587
  const sessionKind = numberValue$26(chatSession.session_kind);
37311
37588
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
37312
37589
  if (uid === "" || kind === void 0) continue;
37313
- const displayName = (kind === "private_chat" ? stringValue$2(supplement.remark ?? supplement.counterpart_name_snapshot ?? counterpart.display_name_snapshot ?? supplement.pending_name ?? counterpart.visible_phone) : stringValue$2(chatSession.title)).trim() || "未命名会话";
37590
+ const privateNickname = stringValue$2(supplement.counterpart_name_snapshot).trim() || stringValue$2(counterpart.display_name_snapshot).trim();
37591
+ const displayName = (kind === "private_chat" ? stringValue$2(supplement.remark).trim() || privateNickname || stringValue$2(supplement.pending_name).trim() || stringValue$2(counterpart.visible_phone).trim() : stringValue$2(chatSession.title)).trim() || "未命名会话";
37314
37592
  const preview = arkmeChatConversationPreview(latestRecord, session.userId);
37315
37593
  const unreadCount = attention.unreadCount;
37316
37594
  const latestRelation = objectValue$2(latestPreview.relation);
@@ -37329,6 +37607,7 @@ var SourceService = class {
37329
37607
  kind,
37330
37608
  directMessageAdmissionApplicable: sessionKind === 1 && numberValue$26(counterpart.user_id) > 0,
37331
37609
  displayName,
37610
+ ...kind === "private_chat" && privateNickname !== "" ? { privateNickname } : {},
37332
37611
  ...kind === "private_chat" && cached?.avatarRef !== void 0 ? { avatarRef: cached.avatarRef } : {},
37333
37612
  ...kind === "group_chat" && cached?.avatarRefs !== void 0 ? { avatarRefs: [...cached.avatarRefs] } : {},
37334
37613
  ...kind === "group_chat" && cached?.groupAvatar !== void 0 ? { groupAvatar: cloneSourceItem(cached).groupAvatar } : {},
@@ -37484,6 +37763,8 @@ var SourceService = class {
37484
37763
  for (const [index, targetUserId] of privateUserIdByIndex) {
37485
37764
  const item = items[index];
37486
37765
  if (item === void 0) continue;
37766
+ const nickname = profiles.get(targetUserId)?.nickname?.trim();
37767
+ if (nickname) item.privateNickname = nickname;
37487
37768
  if (profiles.get(targetUserId)?.avatarUrl === void 0) {
37488
37769
  delete item.avatarRef;
37489
37770
  continue;
@@ -37627,7 +37908,8 @@ var SourceService = class {
37627
37908
  const peerUserId = numberValue$26(counterpart.user_id);
37628
37909
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
37629
37910
  if (uid === "" || kind === void 0) throw new Error("invalid chat display snapshot");
37630
- const displayName = (kind === "private_chat" ? stringValue$2(supplement.remark ?? supplement.counterpart_name_snapshot ?? counterpart.display_name_snapshot ?? supplement.pending_name ?? counterpart.visible_phone) : stringValue$2(chatSession.title)).trim() || cached?.displayName || "未命名会话";
37911
+ const privateNickname = stringValue$2(supplement.counterpart_name_snapshot).trim() || stringValue$2(counterpart.display_name_snapshot).trim();
37912
+ const displayName = (kind === "private_chat" ? stringValue$2(supplement.remark).trim() || privateNickname || stringValue$2(supplement.pending_name).trim() || stringValue$2(counterpart.visible_phone).trim() : stringValue$2(chatSession.title)).trim() || cached?.displayName || "未命名会话";
37631
37913
  const latestItem = [...timelineItems].sort((left, right) => (right.sequence ?? 0) - (left.sequence ?? 0))[0];
37632
37914
  const latestPreview = latestItem === void 0 ? cached?.latestPreview : latestItem.forwardRecords === void 0 ? arkmeTimelineConversationPreview(latestItem) : `[转发] ${latestItem.forwardRecords.title}`;
37633
37915
  const latestSequence = Math.max(numberValue$26(unread.session_last_seq ?? chatSession.last_seq), latestItem?.sequence ?? 0, cached?.latestSequence ?? 0);
@@ -37646,6 +37928,7 @@ var SourceService = class {
37646
37928
  kind,
37647
37929
  directMessageAdmissionApplicable: sessionKind === 1 && numberValue$26(counterpart.user_id) > 0,
37648
37930
  displayName,
37931
+ ...kind === "private_chat" && privateNickname !== "" ? { privateNickname } : {},
37649
37932
  ...kind === "private_chat" && Number.isSafeInteger(peerUserId) && peerUserId > 0 ? { peerUserId } : {},
37650
37933
  ...kind === "private_chat" && Number.isSafeInteger(peerUserId) && peerUserId > 0 ? { peerMemberType: arkmePeerMemberType(counterpart.member_type) } : {},
37651
37934
  ...cached?.avatarRef === void 0 ? {} : { avatarRef: cached.avatarRef },
@@ -39998,8 +40281,15 @@ var CallHistoryService = class {
39998
40281
  displayName: userId === session.userId ? "我" : "通话参与者",
39999
40282
  ...userId === session.userId ? { isCurrentUser: true } : {}
40000
40283
  });
40001
- const participants = await this.attachParticipantAvatars(participantSeeds.slice(0, 50), session, signal);
40002
- const summaryText = safeSummary(renderCallRecordSummary(raw, session.userId, new Map(participants.flatMap((participant) => participant.userId ? [[participant.userId, participant.displayName]] : []))));
40284
+ const seeds = participantSeeds.slice(0, 50);
40285
+ const summarySeeds = callSummaryUserIds(raw).filter((id) => !seeds.some((participant) => participant.userId === id)).slice(0, 50).map((userId) => ({
40286
+ userId,
40287
+ displayName: ""
40288
+ }));
40289
+ const presentations = await this.attachParticipantPresentation([...seeds, ...summarySeeds], session, signal);
40290
+ const participants = presentations.slice(0, seeds.length);
40291
+ const summaryNames = new Map(presentations.flatMap((participant) => participant.userId && participant.displayName.trim() && !/^(Arkme 用户 \d+|通话参与者)$/.test(participant.displayName.trim()) ? [[participant.userId, participant.displayName]] : []));
40292
+ const summaryText = safeSummary(renderCallRecordSummary(raw, session.userId, summaryNames));
40003
40293
  const hangupParticipant = participants.find((participant) => participant.userId === numberValue$22(hangupUserId));
40004
40294
  return {
40005
40295
  callRef: await this.sealCallRef({
@@ -40042,17 +40332,18 @@ var CallHistoryService = class {
40042
40332
  ...hangupParticipant ? { hangupParticipant } : {}
40043
40333
  };
40044
40334
  }
40045
- async attachParticipantAvatars(participants, session, signal) {
40335
+ async attachParticipantPresentation(participants, session, signal) {
40046
40336
  const ids = [...new Set(participants.flatMap((participant) => participant.userId === void 0 ? [] : [participant.userId]))];
40047
40337
  if (ids.length === 0) return participants;
40048
- const profiles = await this.profile.publicProfileSummariesByUserIds(ids, session, signal).catch(() => /* @__PURE__ */ new Map());
40338
+ const [profiles, remarks] = await Promise.all([this.profile.publicProfileSummariesByUserIds(ids, session, signal).catch(() => /* @__PURE__ */ new Map()), this.source?.privateRemarksByUserIds(ids, signal === void 0 ? {} : { signal }).catch(() => /* @__PURE__ */ new Map()) ?? Promise.resolve(/* @__PURE__ */ new Map())]);
40049
40339
  return await Promise.all(participants.map(async (participant) => {
40050
40340
  const userId = participant.userId;
40051
40341
  if (userId === void 0) return participant;
40052
40342
  const profile = profiles.get(userId);
40343
+ const remark = userId === session.userId ? "" : remarks.get(userId)?.trim();
40053
40344
  return {
40054
40345
  ...participant,
40055
- displayName: profile?.displayName || participant.displayName,
40346
+ displayName: remark || profile?.displayName || participant.displayName,
40056
40347
  ...profile?.avatarUrl === void 0 ? {} : { avatarRef: await this.profile.sealProfileImageRef(session.userId, userId) }
40057
40348
  };
40058
40349
  }));
@@ -42119,7 +42410,7 @@ function botDisplayName(profiles, uid) {
42119
42410
  }
42120
42411
  //#endregion
42121
42412
  //#region src/services/chat-sender-display-reader.ts
42122
- const list = (value) => Array.isArray(value) ? value : [];
42413
+ const list$1 = (value) => Array.isArray(value) ? value : [];
42123
42414
  const text$1 = (value) => stringValue$2(value).trim();
42124
42415
  const first = (raw, keys) => keys.map((key) => text$1(raw[key])).find(Boolean) ?? "";
42125
42416
  /** Normalize wire data once. Neither raw participants nor their extra fields escape this adapter. */
@@ -42129,7 +42420,7 @@ function botDisplaySnapshot(bundle) {
42129
42420
  const extra = objectValue$2(session.extra);
42130
42421
  const groupTarget = arkmeGroupBotBindingTargetFromBundle(bundle) ?? arkmeGroupBotBindingTargetFromBundle({ rm_subject_id: extra.subject_id ?? extra.legacy_subject_id });
42131
42422
  const profiles = /* @__PURE__ */ new Map();
42132
- for (const value of list(bundle.bot_participants)) {
42423
+ for (const value of list$1(bundle.bot_participants)) {
42133
42424
  const participant = objectValue$2(value);
42134
42425
  const uid = text$1(participant.bot_uid);
42135
42426
  if (uid === "" || text$1(participant.chat_session_uid) !== chatSessionUid) continue;
@@ -42175,7 +42466,7 @@ var RuntimeBotDisplayProfilesReader = class {
42175
42466
  let snapshot = input.snapshot;
42176
42467
  if (snapshot === void 0) try {
42177
42468
  const data = await this.runtime.authenticatedChatPost("/api/v1/chats/display-snapshots", { chat_session_uids: [input.chatSessionUid] }, session, identitySignal, { refreshOnUnauthorized: false });
42178
- snapshot = list(data.items).map((value) => botDisplaySnapshot(objectValue$2(value))).find((item) => item.chatSessionUid === input.chatSessionUid);
42469
+ snapshot = list$1(data.items).map((value) => botDisplaySnapshot(objectValue$2(value))).find((item) => item.chatSessionUid === input.chatSessionUid);
42179
42470
  } catch (error) {
42180
42471
  optionalFailure(error);
42181
42472
  }
@@ -42206,7 +42497,7 @@ var RuntimeBotDisplayProfilesReader = class {
42206
42497
  cancelWhenUnobserved: true
42207
42498
  });
42208
42499
  const profiles = /* @__PURE__ */ new Map();
42209
- for (const value of list(data.bots)) {
42500
+ for (const value of list$1(data.bots)) {
42210
42501
  const raw = objectValue$2(value);
42211
42502
  if (raw.installed === true && text$1(raw.bot_id) !== "" && text$1(raw.name) !== "") profiles.set(text$1(raw.bot_id), {
42212
42503
  displayName: text$1(raw.name),
@@ -42674,7 +42965,7 @@ async function recordReeditMentionMetadata(text, prepared, resolve) {
42674
42965
  }
42675
42966
  //#endregion
42676
42967
  //#region src/record-sender-snapshot.ts
42677
- function object(value) {
42968
+ function object$1(value) {
42678
42969
  return value !== null && typeof value === "object" ? value : {};
42679
42970
  }
42680
42971
  function text(...values) {
@@ -42682,10 +42973,10 @@ function text(...values) {
42682
42973
  }
42683
42974
  /** Keep the recorded actor, never resolve a historical record through today's profile. */
42684
42975
  function recordSenderSnapshot(raw) {
42685
- const item = object(raw);
42686
- const core = object(item.record_core);
42687
- const snapshot = object(core.sender_snapshot ?? item.sender_snapshot);
42688
- const extra = object(core.extra ?? item.extra);
42976
+ const item = object$1(raw);
42977
+ const core = object$1(item.record_core);
42978
+ const snapshot = object$1(core.sender_snapshot ?? item.sender_snapshot);
42979
+ const extra = object$1(core.extra ?? item.extra);
42689
42980
  const avatar = text(snapshot.avatar, core.avatar, item.avatar, extra.sender_avatar_ref);
42690
42981
  return {
42691
42982
  avatarSnapshot: true,
@@ -43784,10 +44075,15 @@ var RecordService = class {
43784
44075
  this.assertReeditActive();
43785
44076
  await this.runtime.stateStore.removeLongArticleDraft(session.userId, draft.sourceRef, itemUid);
43786
44077
  }
43787
- async removeLongArticleDraft(sourceRef, itemUid) {
44078
+ async removeLongArticleDraft(sourceRef, itemUid, expectedRecordUid) {
43788
44079
  const session = await this.runtime.requireSession();
43789
44080
  const source = await this.source.openSourceRef(sourceRef, session.userId);
43790
44081
  const uid = itemUid?.trim() || void 0;
44082
+ if (expectedRecordUid !== void 0) {
44083
+ if (uid !== void 0 || expectedRecordUid.trim() === "") throw new ArkmePluginError("long-article-draft-invalid", "长文草稿标识无效", false);
44084
+ await this.runtime.stateStore.removeLongArticleDraft(session.userId, sourceRef, void 0, expectedRecordUid);
44085
+ return;
44086
+ }
43791
44087
  if (uid !== void 0) {
43792
44088
  const sourceIdentityKey = await this.recordReeditSourceIdentityKey(source);
43793
44089
  const current = await this.runtime.stateStore.getRecordReeditDraft(session.userId, sourceIdentityKey, uid);
@@ -47542,6 +47838,8 @@ var ChatService = class {
47542
47838
  }
47543
47839
  async forwardSourceMessages(sourceRef, actionRefs, options = {}) {
47544
47840
  const session = await this.runtime.requireSession();
47841
+ if (options.expectedUserId !== void 0 && options.expectedUserId !== session.userId) throw new ArkmePluginError("file-account-changed", "账号已切换", false, 403);
47842
+ if (options.sendAtMillis !== void 0 && (!Number.isSafeInteger(options.sendAtMillis) || options.sendAtMillis <= 0)) throw new ArkmePluginError("message-actions-time-invalid", "发送时间无效", false);
47545
47843
  const source = await this.source.openSourceRef(sourceRef, session.userId);
47546
47844
  const targetSourceRef = options.targetSourceRef?.trim() || sourceRef;
47547
47845
  const targetSource = targetSourceRef === sourceRef ? source : await this.source.openSourceRef(targetSourceRef, session.userId);
@@ -47581,7 +47879,7 @@ var ChatService = class {
47581
47879
  }
47582
47880
  };
47583
47881
  if (targetSource.kind === "private_chat" || targetSource.kind === "group_chat") {
47584
- const sendAtMillis = Date.now();
47882
+ const sendAtMillis = options.sendAtMillis ?? Date.now();
47585
47883
  const clientRequestId = this.forwardClientRequestId(targetSource.ownerRef, references, sendAtMillis);
47586
47884
  const sourceRecordUids = references.some((reference) => reference.sourceKind === "chat_relation") ? [] : references.map((reference) => reference.recordUid).filter((recordUid) => recordUid.trim() !== "");
47587
47885
  const data = await postChatMessageCreation(this.runtime, "/api/v1/chats/records/forward", {
@@ -48297,6 +48595,29 @@ var ChatService = class {
48297
48595
  editable: numberValue$19(core.owner_user_id) === session.userId && numberValue$19(core.creator_user_id) === session.userId
48298
48596
  };
48299
48597
  }
48598
+ /** Resolve a complete, owned article, never trust a search excerpt as send content. */
48599
+ async ownLongArticle(itemUid, expectedUserId, signal) {
48600
+ const session = await this.runtime.requireSession();
48601
+ if (session.userId !== expectedUserId) throw new ArkmePluginError("file-account-changed", "账号已切换", false, 403);
48602
+ const target = await this.source.selfTarget(signal);
48603
+ const source = await this.source.openSourceRef(target.sourceRef, session.userId);
48604
+ const detail = await this.record.longArticleDetail(target.sourceRef, itemUid, signal);
48605
+ if (!detail.editable) throw new ArkmePluginError("long-article-not-owned", "只能添加自己创建的长文", false, 403);
48606
+ if ((await this.runtime.requireSession()).userId !== session.userId) throw new ArkmePluginError("file-account-changed", "账号已切换", false, 403);
48607
+ const item = this.withRecordMessageActionRef(source, {
48608
+ ...detail,
48609
+ isMe: true,
48610
+ senderName: "我",
48611
+ status: 1,
48612
+ templateKind: 8,
48613
+ displayKind: 1
48614
+ }, session.userId, await this.runtime.stateStore.uniqueCode());
48615
+ if (!item.messageActionRef) throw new ArkmePluginError("long-article-ref-invalid", "长文暂不可添加,请重试", true);
48616
+ return {
48617
+ detail,
48618
+ messageActionRef: item.messageActionRef
48619
+ };
48620
+ }
48300
48621
  async updateLongArticle(sourceRef, itemUid, input) {
48301
48622
  return await this.record.updateLongArticle(sourceRef, itemUid, input);
48302
48623
  }
@@ -48306,8 +48627,8 @@ var ChatService = class {
48306
48627
  async putLongArticleDraft(draft) {
48307
48628
  return await this.record.putLongArticleDraft(draft);
48308
48629
  }
48309
- async removeLongArticleDraft(sourceRef, itemUid) {
48310
- return await this.record.removeLongArticleDraft(sourceRef, itemUid);
48630
+ async removeLongArticleDraft(sourceRef, itemUid, expectedRecordUid) {
48631
+ return await this.record.removeLongArticleDraft(sourceRef, itemUid, expectedRecordUid);
48311
48632
  }
48312
48633
  async uploadLocalFile(filePath, metadata, options = {}) {
48313
48634
  return await this.media.uploadLocalFile(filePath, metadata, options);
@@ -84832,6 +85153,81 @@ function projectRecordingVersions(response, kind) {
84832
85153
  return versions.sort((left, right) => right.generatedAtMillis - left.generatedAtMillis || left.sourceIndex - right.sourceIndex).map(({ sourceIndex: _sourceIndex, ...version }) => version);
84833
85154
  }
84834
85155
  //#endregion
85156
+ //#region src/recording-coverage.ts
85157
+ const object = (value) => value !== null && typeof value === "object" ? value : {};
85158
+ const list = (value) => Array.isArray(value) ? value : [];
85159
+ const number = (value) => typeof value === "number" && Number.isFinite(value) ? value : NaN;
85160
+ /** Upload ownership and speaker identity are not the recording's explicit memory attribution. */
85161
+ function recordingBelongsToViewer(session, viewerUserId) {
85162
+ return Number.isSafeInteger(viewerUserId) && viewerUserId > 0 && number(object(session).belong_usr) === viewerUserId;
85163
+ }
85164
+ function clipRecordingCoverage(interval, start, end) {
85165
+ const from = Math.max(start, interval.startAtMillis);
85166
+ const to = Math.min(end, interval.endAtMillis);
85167
+ return Number.isFinite(from) && Number.isFinite(to) && from >= 0 && to > from ? {
85168
+ ...interval,
85169
+ startAtMillis: from,
85170
+ endAtMillis: to
85171
+ } : void 0;
85172
+ }
85173
+ /** Use physical audio children, never first/last speech or a session envelope spanning gaps. */
85174
+ function projectRecordingCoverage(response, dayStart, dayEnd, viewerUserId) {
85175
+ const data = object(response);
85176
+ const rawSessions = data.session_ls ?? data.sessions;
85177
+ const rawChildren = data.child_ls ?? data.children;
85178
+ const sessions = new Map(list(rawSessions).map((value) => {
85179
+ const session = object(value);
85180
+ return [String(session.id ?? session.session_id ?? ""), session];
85181
+ }));
85182
+ let incomplete = !Array.isArray(rawSessions) || !Array.isArray(rawChildren) || !Number.isSafeInteger(viewerUserId) || viewerUserId <= 0 || [...sessions.values()].some((session) => !Number.isSafeInteger(session.belong_usr) || number(session.belong_usr) < 0);
85183
+ const childrenBySession = /* @__PURE__ */ new Set();
85184
+ const intervals = [];
85185
+ const add = (interval) => {
85186
+ const clipped = clipRecordingCoverage(interval, dayStart, dayEnd);
85187
+ if (clipped) intervals.push(clipped);
85188
+ };
85189
+ for (const raw of list(rawChildren)) {
85190
+ const child = object(raw);
85191
+ const id = String(child.session_id ?? "");
85192
+ childrenBySession.add(id);
85193
+ const session = sessions.get(id);
85194
+ if (session === void 0) {
85195
+ incomplete = true;
85196
+ continue;
85197
+ }
85198
+ if (!recordingBelongsToViewer(session, viewerUserId)) continue;
85199
+ const start = number(child.start_at);
85200
+ const absoluteStart = start >= 1e11 ? start : number(session?.start_at) + start;
85201
+ const duration = number(child.duration);
85202
+ if (!Number.isFinite(absoluteStart) || absoluteStart < 0 || !(duration > 0)) {
85203
+ incomplete = true;
85204
+ continue;
85205
+ }
85206
+ add({
85207
+ startAtMillis: absoluteStart,
85208
+ endAtMillis: absoluteStart + duration,
85209
+ sourceLabel: typeof session?.orig_name === "string" && session.orig_name.trim() ? session.orig_name : "已同步录音",
85210
+ status: child.has_asr === false ? "processing" : "saved"
85211
+ });
85212
+ }
85213
+ for (const [id, session] of sessions) {
85214
+ if (!recordingBelongsToViewer(session, viewerUserId)) continue;
85215
+ if (childrenBySession.has(id)) continue;
85216
+ const start = number(session.start_at), end = number(session.end_at), duration = number(session.duration);
85217
+ if (session.has_finish_spk === true && duration > 0 && end > start && Math.abs(end - start - duration) < 1) add({
85218
+ startAtMillis: start,
85219
+ endAtMillis: end,
85220
+ sourceLabel: typeof session.orig_name === "string" && session.orig_name ? session.orig_name : "已同步录音",
85221
+ status: "saved"
85222
+ });
85223
+ else incomplete = true;
85224
+ }
85225
+ return {
85226
+ state: incomplete ? "partial" : "ready",
85227
+ intervals: intervals.sort((a, b) => a.startAtMillis - b.startAtMillis)
85228
+ };
85229
+ }
85230
+ //#endregion
84835
85231
  //#region src/services/recording-service.ts
84836
85232
  function numberValue$5(value) {
84837
85233
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -85734,6 +86130,8 @@ var RecordingService = class {
85734
86130
  dayEndMillis: dayEnd.getTime()
85735
86131
  };
85736
86132
  const response = transcriptResult.value;
86133
+ const coverage = projectRecordingCoverage(response, date, dayEnd.getTime(), session.userId);
86134
+ const ownSessionIds = new Set(listValue$5(response.session_ls ?? response.sessions).filter((raw) => recordingBelongsToViewer(raw, session.userId)).map((raw) => stringValue$2(objectValue$2(raw).id ?? objectValue$2(raw).session_id).trim()).filter((id) => id !== ""));
85737
86135
  const { processingCount: pendingDoubao, candidateCount, failedCount, silentCount } = recordingDoubaoProgress(response, options);
85738
86136
  const section = (transcriptSource) => {
85739
86137
  const items = projectRecordingTranscripts(response, speakerData, profilesByUserId, {
@@ -85744,7 +86142,7 @@ var RecordingService = class {
85744
86142
  return {
85745
86143
  state: items.length > 0 ? "ready" : processingCount > 0 ? "processing" : transcriptSource === "doubao" && failedCount > 0 ? "error" : "empty",
85746
86144
  items,
85747
- message: items.length > 0 ? "" : processingCount > 0 ? "音频文字正在导入&转写中" : transcriptSource === "doubao" && failedCount > 0 ? "豆包转写失败,请稍后重试" : transcriptSource === "system" ? "当天无录音" : "暂无豆包转写内容",
86145
+ message: items.length > 0 ? "" : processingCount > 0 ? "音频文字正在导入&转写中" : transcriptSource === "doubao" && failedCount > 0 ? "豆包转写失败,请稍后重试" : transcriptSource === "system" ? coverage.intervals.length > 0 ? "已有录音,暂无转写内容" : "当天无录音" : "暂无豆包转写内容",
85748
86146
  identityCoverage: speakerResult.status === "fulfilled" ? "complete" : "partial",
85749
86147
  totalDurationMillis,
85750
86148
  processingCount
@@ -85754,7 +86152,9 @@ var RecordingService = class {
85754
86152
  section,
85755
86153
  candidateCount,
85756
86154
  failedCount,
85757
- silentCount
86155
+ silentCount,
86156
+ coverage,
86157
+ ownSessionIds
85758
86158
  };
85759
86159
  }
85760
86160
  async recordingComparison(dateStamp, signal) {
@@ -85877,13 +86277,14 @@ var RecordingService = class {
85877
86277
  async recordingDayWithSession(dateStamp, session, signal) {
85878
86278
  const date = this.recordingDayStart(dateStamp).getTime();
85879
86279
  const [transcriptResult, summaryResult, timelineResult] = await Promise.allSettled([
85880
- this.recordingTranscriptWithSession(date, session, signal),
86280
+ this.readRecordingTranscripts(date, session, signal),
85881
86281
  this.recordingProjectionWithSession(date, "summary", session, signal),
85882
86282
  this.recordingProjectionWithSession(date, "timeline", session, signal)
85883
86283
  ]);
85884
86284
  let transcript;
85885
86285
  if (transcriptResult.status === "fulfilled") {
85886
- const items = transcriptResult.value.items;
86286
+ const section = transcriptResult.value.section("system");
86287
+ const items = section.items;
85887
86288
  const counts = /* @__PURE__ */ new Map();
85888
86289
  for (const item of items) {
85889
86290
  const key = this.recordingSpeakerMutationKey(item);
@@ -85892,8 +86293,11 @@ var RecordingService = class {
85892
86293
  const recordingItemRefKey = await this.recordingRefKey("arkme-recording-item-v1");
85893
86294
  const speakerRefKey = await this.recordingRefKey("arkme-recording-speaker-v1");
85894
86295
  transcript = {
85895
- ...transcriptResult.value,
85896
- items: items.map((item) => this.workbenchItem(date, item, counts.get(this.recordingSpeakerMutationKey(item)) ?? 1, session.userId, recordingItemRefKey, speakerRefKey))
86296
+ ...section,
86297
+ items: items.map((item) => ({
86298
+ ...this.workbenchItem(date, item, counts.get(this.recordingSpeakerMutationKey(item)) ?? 1, session.userId, recordingItemRefKey, speakerRefKey),
86299
+ recordingBelongsToViewer: transcriptResult.value.ownSessionIds.has(item.sessionId)
86300
+ }))
85897
86301
  };
85898
86302
  } else transcript = {
85899
86303
  state: "error",
@@ -85904,7 +86308,11 @@ var RecordingService = class {
85904
86308
  };
85905
86309
  return {
85906
86310
  dateStamp: date,
85907
- totalDurationMillis: transcriptResult.status === "fulfilled" ? transcriptResult.value.totalDurationMillis : 0,
86311
+ coverage: transcriptResult.status === "fulfilled" ? transcriptResult.value.coverage : {
86312
+ state: "error",
86313
+ intervals: []
86314
+ },
86315
+ totalDurationMillis: transcriptResult.status === "fulfilled" ? transcript.totalDurationMillis : 0,
85908
86316
  transcript,
85909
86317
  summary: summaryResult.status === "fulfilled" ? summaryResult.value : {
85910
86318
  state: "error",
@@ -86970,6 +87378,257 @@ async function resolveDshSearchOrigins(provider, items, signal) {
86970
87378
  } : item);
86971
87379
  }
86972
87380
  //#endregion
87381
+ //#region src/text-link-tlds.ts
87382
+ const validLinkTlds = new Set(`
87383
+ aarp abb abbott abbvie abc able abogado abudhabi ac academy accenture accountant accountants aco actor ad
87384
+ ads adult ae aeg aero aetna af afl africa ag agakhan agency ai aig airbus airforce
87385
+ airtel akdn al alibaba alipay allfinanz allstate ally alsace alstom am amazon americanexpress americanfamily amex amfam
87386
+ amica amsterdam analytics android anquan anz ao aol apartments app apple aq aquarelle ar arab aramco
87387
+ archi army arpa art arte as asda asia associates at athleta attorney au auction audi audible
87388
+ audio auspost author auto autos aw aws ax axa az azure ba baby baidu banamex band
87389
+ bank bar barcelona barclaycard barclays barefoot bargains baseball basketball bauhaus bayern bb bbc bbt bbva bcg
87390
+ bcn bd be beats beauty beer berlin best bestbuy bet bf bg bh bharti bi bible
87391
+ bid bike bing bingo bio biz bj black blackfriday blockbuster blog bloomberg blue bm bms bmw
87392
+ bn bnpparibas bo boats boehringer bofa bom bond boo book booking bosch bostik boston bot boutique
87393
+ box br bradesco bridgestone broadway broker brother brussels bs bt build builders business buy buzz bv
87394
+ bw by bz bzh ca cab cafe cal call calvinklein cam camera camp canon capetown capital
87395
+ capitalone car caravan cards care career careers cars casa case cash casino cat catering catholic cba
87396
+ cbn cbre cc cd center ceo cern cf cfa cfd cg ch chanel channel charity chase
87397
+ chat cheap chintai christmas chrome church ci cipriani circle cisco citadel citi citic city ck cl
87398
+ claims cleaning click clinic clinique clothing cloud club clubmed cm cn co coach codes coffee college
87399
+ cologne com commbank community company compare computer comsec condos construction consulting contact contractors cooking cool coop
87400
+ corsica country coupon coupons courses cpa cr credit creditcard creditunion cricket crown crs cruise cruises cu
87401
+ cuisinella cv cw cx cy cymru cyou cz dad dance data date dating datsun day dclk
87402
+ dds de deal dealer deals degree delivery dell deloitte delta democrat dental dentist desi design dev
87403
+ dhl diamonds diet digital direct directory discount discover dish diy dj dk dm dnp do docs
87404
+ doctor dog domains dot download drive dtv dubai dunlop dupont durban dvag dvr dz earth eat
87405
+ ec eco edeka edu education ee eg email emerck energy engineer engineering enterprises epson equipment er
87406
+ ericsson erni es esq estate et eu eurovision eus events exchange expert exposed express extraspace fage
87407
+ fail fairwinds faith family fan fans farm farmers fashion fast fedex feedback ferrari ferrero fi fidelity
87408
+ fido film final finance financial fire firestone firmdale fish fishing fit fitness fj fk flickr flights
87409
+ flir florist flowers fly fm fo foo food football ford forex forsale forum foundation fox fr
87410
+ free fresenius frl frogans frontier ftr fujitsu fun fund furniture futbol fyi ga gal gallery gallo
87411
+ gallup game games gap garden gay gb gbiz gd gdn ge gea gent genting george gf
87412
+ gg ggee gh gi gift gifts gives giving gl glass gle global globo gm gmail gmbh
87413
+ gmo gmx gn godaddy gold goldpoint golf goo goodyear goog google gop got gov gp gq
87414
+ gr grainger graphics gratis green gripe grocery group gs gt gu gucci guge guide guitars guru
87415
+ gw gy hair hamburg hangout haus hbo hdfc hdfcbank health healthcare help helsinki here hermes hiphop
87416
+ hisamitsu hitachi hiv hk hkt hm hn hockey holdings holiday homedepot homegoods homes homesense honda horse
87417
+ hospital host hosting hot hotels hotmail house how hr hsbc ht hu hughes hyatt hyundai ibm
87418
+ icbc ice icu id ie ieee ifm ikano il im imamat imdb immo immobilien in inc
87419
+ industries infiniti info ing ink institute insurance insure int international intuit investments io ipiranga iq ir
87420
+ irish is ismaili ist istanbul it itau itv jaguar java jcb je jeep jetzt jewelry jio
87421
+ jll jm jmp jnj jo jobs joburg jot joy jp jpmorgan jprs juegos juniper kaufen kddi
87422
+ ke kerryhotels kerryproperties kfh kg kh ki kia kids kim kindle kitchen kiwi km kn koeln
87423
+ komatsu kosher kp kpmg kpn kr krd kred kuokgroup kw ky kyoto kz la lacaixa lamborghini
87424
+ lamer land landrover lanxess lasalle lat latino latrobe law lawyer lb lc lds lease leclerc lefrak
87425
+ legal lego lexus lgbt li lidl life lifeinsurance lifestyle lighting like lilly limited limo lincoln link
87426
+ live living lk llc llp loan loans locker locus lol london lotte lotto love lpl lplfinancial
87427
+ lr ls lt ltd ltda lu lundbeck luxe luxury lv ly ma madrid maif maison makeup
87428
+ man management mango map market marketing markets marriott marshalls mattel mba mc mckinsey md me med
87429
+ media meet melbourne meme memorial men menu merckmsd mg mh miami microsoft mil mini mint mit
87430
+ mitsubishi mk ml mlb mls mm mma mn mo mobi mobile moda moe moi mom monash
87431
+ money monster mormon mortgage moscow moto motorcycles mov movie mp mq mr ms msd mt mtn
87432
+ mtr mu museum music mv mw mx my mz na nab nagoya name navy nba nc
87433
+ ne nec net netbank netflix network neustar new news next nextdirect nexus nf nfl ng ngo
87434
+ nhk ni nico nike nikon ninja nissan nissay nl no nokia norton now nowruz nowtv np
87435
+ nr nra nrw ntt nu nyc nz obi observer office okinawa olayan olayangroup ollo om omega
87436
+ one ong onl online ooo open oracle orange org organic origins osaka otsuka ott ovh pa
87437
+ page panasonic paris pars partners parts party pay pccw pe pet pf pfizer pg ph pharmacy
87438
+ phd philips phone photo photography photos physio pics pictet pictures pid pin ping pink pioneer pizza
87439
+ pk pl place play playstation plumbing plus pm pn pnc pohl poker politie porn post pr
87440
+ praxi press prime pro prod productions prof progressive promo properties property protection pru prudential ps pt
87441
+ pub pw pwc py qa qpon quebec quest racing radio re read realestate realtor realty recipes
87442
+ red redstone redumbrella rehab reise reisen reit reliance ren rent rentals repair report republican rest restaurant
87443
+ review reviews rexroth rich richardli ricoh ril rio rip ro rocks rodeo rogers room rs rsvp
87444
+ ru rugby ruhr run rw rwe ryukyu sa saarland safe safety sakura sale salon samsclub samsung
87445
+ sandvik sandvikcoromant sanofi sap sarl sas save saxo sb sbi sbs sc scb schaeffler schmidt scholarships
87446
+ school schule schwarz science scot sd se search seat secure security seek select sener services seven
87447
+ sew sex sexy sfr sg sh shangrila sharp shell shia shiksha shoes shop shopping shouji show
87448
+ si silk sina singles site sj sk ski skin sky skype sl sling sm smart smile
87449
+ sn sncf so soccer social softbank software sohu solar solutions song sony soy spa space sport
87450
+ spot sr srl ss st stada staples star statebank statefarm stc stcgroup stockholm storage store stream
87451
+ studio study style su sucks supplies supply support surf surgery suzuki sv swatch swiss sx sy
87452
+ sydney systems sz tab taipei talk taobao target tatamotors tatar tattoo tax taxi tc tci td
87453
+ tdk team tech technology tel temasek tennis teva tf tg th thd theater theatre tiaa tickets
87454
+ tienda tips tires tirol tj tjmaxx tjx tk tkmaxx tl tm tmall tn to today tokyo
87455
+ tools top toray toshiba total tours town toyota toys tr trade trading training travel travelers travelersinsurance
87456
+ trust trv tt tube tui tunes tushu tv tvs tw tz ua ubank ubs ug uk
87457
+ unicom university uno uol ups us uy uz va vacations vana vanguard vc ve vegas ventures
87458
+ verisign versicherung vet vg vi viajes video vig viking villas vin vip virgin visa vision viva
87459
+ vivo vlaanderen vn vodka volvo vote voting voto voyage vu wales walmart walter wang wanggou watch
87460
+ watches weather weatherchannel webcam weber website wed wedding weibo weir wf whoswho wien wiki williamhill win
87461
+ windows wine winners wme wolterskluwer woodside work works world wow ws wtc wtf xbox xerox xihuan
87462
+ xin xn--11b4c3d xn--1ck2e1b xn--1qqw23a xn--2scrj9c xn--30rr7y xn--3bst00m xn--3ds443g xn--3e0b707e xn--3hcrj9c xn--3pxu8k xn--42c2d9a xn--45br5cyl xn--45brj9c xn--45q11c xn--4dbrk0ce
87463
+ xn--4gbrim xn--54b7fta0cc xn--55qw42g xn--55qx5d xn--5su34j936bgsg xn--5tzm5g xn--6frz82g xn--6qq986b3xl xn--80adxhks xn--80ao21a xn--80aqecdr1a xn--80asehdb xn--80aswg xn--8y0a063a xn--90a3ac xn--90ae
87464
+ xn--90ais xn--9dbq2a xn--9et52u xn--9krt00a xn--b4w605ferd xn--bck1b9a5dre4c xn--c1avg xn--c2br7g xn--cck2b3b xn--cckwcxetd xn--cg4bki xn--clchc0ea0b2g2a9gcd xn--czr694b xn--czrs0t xn--czru2d xn--d1acj3b
87465
+ xn--d1alf xn--e1a4c xn--eckvdtc9d xn--efvy88h xn--fct429k xn--fhbei xn--fiq228c5hs xn--fiq64b xn--fiqs8s xn--fiqz9s xn--fjq720a xn--flw351e xn--fpcrj9c3d xn--fzc2c9e2c xn--fzys8d69uvgm xn--g2xx48c
87466
+ xn--gckr3f0f xn--gecrj9c xn--gk3at1e xn--h2breg3eve xn--h2brj9c xn--h2brj9c8c xn--hxt814e xn--i1b6b1a6a2e xn--imr513n xn--io0a7i xn--j1aef xn--j1amh xn--j6w193g xn--jlq480n2rg xn--jvr189m xn--kcrx77d1x4a
87467
+ xn--kprw13d xn--kpry57d xn--kput3i xn--l1acc xn--lgbbat1ad8j xn--mgb9awbf xn--mgba3a3ejt xn--mgba3a4f16a xn--mgba7c0bbn0a xn--mgbaam7a8h xn--mgbab2bd xn--mgbah1a3hjkrd xn--mgbai9azgqp6j xn--mgbayh7gpa xn--mgbbh1a xn--mgbbh1a71e
87468
+ xn--mgbc0a9azcg xn--mgbca7dzdo xn--mgbcpq6gpa1a xn--mgberp4a5d4ar xn--mgbgu82a xn--mgbi4ecexp xn--mgbpl2fh xn--mgbt3dhd xn--mgbtx2b xn--mgbx4cd0ab xn--mix891f xn--mk1bu44c xn--mxtq1m xn--ngbc5azd xn--ngbe9e0a xn--ngbrx
87469
+ xn--node xn--nqv7f xn--nqv7fs00ema xn--nyqy26a xn--o3cw4h xn--ogbpf8fl xn--otu796d xn--p1acf xn--p1ai xn--pgbs0dh xn--pssy2u xn--q7ce6a xn--q9jyb4c xn--qcka1pmc xn--qxa6a xn--qxam
87470
+ xn--rhqv96g xn--rovu88b xn--rvc1e0am3e xn--s9brj9c xn--ses554g xn--t60b56a xn--tckwe xn--tiq49xqyj xn--unup4y xn--vermgensberater-ctb xn--vermgensberatung-pwb xn--vhquv xn--vuq861b xn--w4r85el8fhu5dnra xn--w4rs40l xn--wgbh1c
87471
+ xn--wgbl6a xn--xhq521b xn--xkc2al3hye2a xn--xkc2dl3a5ee0h xn--y9a3aq xn--yfro4i67o xn--ygbi2ammx xn--zfr164b xxx xyz yachts yahoo yamaxun yandex ye yodobashi
87472
+ yoga yokohama you youtube yt yun za zappos zara zero zip zm zone zuerich zw
87473
+ `.trim().split(/\s+/u));
87474
+ function isValidLinkTld(value) {
87475
+ return validLinkTlds.has(value.toLowerCase());
87476
+ }
87477
+ //#endregion
87478
+ //#region src/text-link-parser.ts
87479
+ const urlCandidatePattern = /(?<![a-z0-9_-])(?:[a-z][a-z0-9+.-]*:\/\/[-a-z0-9._~%!$&'*+,;=:@\\]+(?:[/?#][^\s<>{}()()【】,。!?;、“”‘’]*)?|(?:javascript|data):[^\s<>{}()()【】,。!?;、“”‘’]*|(?:www\.)?(?=[a-z0-9.-]*\.)[a-z0-9][a-z0-9.-]*(?::[a-z0-9_+-]*)?(?:[/?#\\][^\s<>{}()()【】,。!?;:、“”‘’]*)?)/giu;
87480
+ const explicitSchemePattern = /^[a-z][a-z0-9+.-]*:\/\//iu;
87481
+ const trailingPunctuationPattern = /[.,!?;:,。!?;:、'"“”‘’]+$/u;
87482
+ const asciiContinuationPattern = /[a-z0-9_-]/iu;
87483
+ const ipv4Pattern = /^(?:\d{1,3}\.){3}\d{1,3}$/u;
87484
+ const domainLabelPattern = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/iu;
87485
+ const riskyBareFileLikeTlds = /* @__PURE__ */ new Set([
87486
+ "apk",
87487
+ "avi",
87488
+ "conf",
87489
+ "csv",
87490
+ "doc",
87491
+ "docs",
87492
+ "docx",
87493
+ "dmg",
87494
+ "exe",
87495
+ "gif",
87496
+ "gz",
87497
+ "ini",
87498
+ "ipa",
87499
+ "jpeg",
87500
+ "jpg",
87501
+ "json",
87502
+ "log",
87503
+ "md",
87504
+ "mkv",
87505
+ "mov",
87506
+ "mp3",
87507
+ "mp4",
87508
+ "msi",
87509
+ "pdf",
87510
+ "png",
87511
+ "rar",
87512
+ "svg",
87513
+ "tar",
87514
+ "txt",
87515
+ "webp",
87516
+ "xml",
87517
+ "yaml",
87518
+ "yml",
87519
+ "zip"
87520
+ ]);
87521
+ function isExplicitUrl(text) {
87522
+ return explicitSchemePattern.test(text);
87523
+ }
87524
+ function trimTrailingPunctuation(text) {
87525
+ let candidate = text.replace(trailingPunctuationPattern, "");
87526
+ let squareBracketBalance = 0;
87527
+ for (const character of candidate) if (character === "[") squareBracketBalance += 1;
87528
+ else if (character === "]") squareBracketBalance -= 1;
87529
+ while (candidate.endsWith("]") && squareBracketBalance < 0) {
87530
+ candidate = candidate.slice(0, -1);
87531
+ squareBracketBalance += 1;
87532
+ }
87533
+ return candidate;
87534
+ }
87535
+ function isValidIpv4(host) {
87536
+ if (!ipv4Pattern.test(host) || host === "0.0.0.0") return false;
87537
+ return host.split(".").every((part) => {
87538
+ const value = Number(part);
87539
+ return Number.isInteger(value) && value >= 0 && value <= 255;
87540
+ });
87541
+ }
87542
+ function isValidDomain(host) {
87543
+ if (host.length > 253) return false;
87544
+ const labels = host.split(".");
87545
+ if (labels.length < 2 || labels.some((label) => !domainLabelPattern.test(label))) return false;
87546
+ const tld = labels.at(-1)?.toLowerCase();
87547
+ return tld !== void 0 && isValidLinkTld(tld);
87548
+ }
87549
+ function isValidHost(host) {
87550
+ if (host === "localhost") return true;
87551
+ return ipv4Pattern.test(host) ? isValidIpv4(host) : isValidDomain(host);
87552
+ }
87553
+ function isLikelyNumericShortBareDomain(text, host) {
87554
+ if (isExplicitUrl(text) || /^www\./iu.test(text) || ipv4Pattern.test(host)) return false;
87555
+ const labels = host.split(".");
87556
+ return labels.length === 2 && /^\d$/u.test(labels[0] ?? "");
87557
+ }
87558
+ function isLikelyFileLikeBareDomain(text, host) {
87559
+ if (isExplicitUrl(text) || /^www\./iu.test(text) || ipv4Pattern.test(host)) return false;
87560
+ const labels = host.split(".");
87561
+ return labels.length === 2 && riskyBareFileLikeTlds.has(labels.at(-1)?.toLowerCase() ?? "");
87562
+ }
87563
+ function rawHost(text) {
87564
+ const authorityStart = isExplicitUrl(text) ? text.indexOf("://") + 3 : 0;
87565
+ const host = (text.slice(authorityStart).split(/[/?#\\]/u, 1)[0] ?? "").replace(/:\d{1,5}$/u, "").toLowerCase();
87566
+ return host === "" ? void 0 : host;
87567
+ }
87568
+ function normalizedHref(text) {
87569
+ if (text.includes("\\")) return void 0;
87570
+ const sourceHost = rawHost(text);
87571
+ if (sourceHost === void 0 || !isValidHost(sourceHost)) return void 0;
87572
+ if (sourceHost === "localhost" && !isExplicitUrl(text)) return void 0;
87573
+ const href = isExplicitUrl(text) ? text : `https://${text}`;
87574
+ try {
87575
+ const url = new URL(href);
87576
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
87577
+ if (url.username !== "" || url.password !== "" || !isValidHost(url.hostname)) return void 0;
87578
+ if (url.pathname.includes("\\") || url.search.includes("\\") || url.hash.includes("\\")) return void 0;
87579
+ if (url.search.toLowerCase().includes("javascript:") || url.hash.includes("##")) return void 0;
87580
+ if (isLikelyNumericShortBareDomain(text, sourceHost)) return void 0;
87581
+ if (isLikelyFileLikeBareDomain(text, sourceHost)) return void 0;
87582
+ return href;
87583
+ } catch {
87584
+ return;
87585
+ }
87586
+ }
87587
+ function pushText(runs, text) {
87588
+ if (text === "") return;
87589
+ const previous = runs.at(-1);
87590
+ if (previous?.kind === "text") {
87591
+ runs[runs.length - 1] = {
87592
+ kind: "text",
87593
+ text: previous.text + text
87594
+ };
87595
+ return;
87596
+ }
87597
+ runs.push({
87598
+ kind: "text",
87599
+ text
87600
+ });
87601
+ }
87602
+ function textLinkRuns(text) {
87603
+ const runs = [];
87604
+ let cursor = 0;
87605
+ for (const match of text.matchAll(urlCandidatePattern)) {
87606
+ const candidate = trimTrailingPunctuation(match[0]);
87607
+ if (candidate === "") continue;
87608
+ const start = match.index;
87609
+ const end = start + candidate.length;
87610
+ const href = normalizedHref(candidate);
87611
+ const hasEmailBoundary = start > 0 && text[start - 1] === "@";
87612
+ const hasSuspiciousRightBoundary = !isExplicitUrl(candidate) && !/^www\./iu.test(candidate) && end < text.length && asciiContinuationPattern.test(text[end] ?? "");
87613
+ if (href === void 0 || hasEmailBoundary || hasSuspiciousRightBoundary) continue;
87614
+ pushText(runs, text.slice(cursor, start));
87615
+ runs.push({
87616
+ kind: "link",
87617
+ text: candidate,
87618
+ href
87619
+ });
87620
+ cursor = end;
87621
+ }
87622
+ pushText(runs, text.slice(cursor));
87623
+ return runs;
87624
+ }
87625
+ //#endregion
87626
+ //#region src/search-record-links.ts
87627
+ /** Extract before the search excerpt is clipped, using the same safe URLs as chat rendering. */
87628
+ function arkmeSearchRecordLinks(text) {
87629
+ return [...new Set(textLinkRuns(text).flatMap((run) => run.kind === "link" ? [run.href] : []))];
87630
+ }
87631
+ //#endregion
86973
87632
  //#region src/services/search-service.ts
86974
87633
  function numberValue$2(value) {
86975
87634
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -87201,7 +87860,7 @@ var SearchService = class {
87201
87860
  };
87202
87861
  }
87203
87862
  async withNavigationTargets(result, signal, sourceRef) {
87204
- if (this.source === void 0 || result.items.length === 0) return result;
87863
+ if (this.source === void 0 || result.items.length === 0 && result.sourceAggregates.length === 0) return result;
87205
87864
  if (sourceRef !== void 0) {
87206
87865
  const session = await this.runtime.requireSession();
87207
87866
  const source = await this.source.openSourceRef(sourceRef, session.userId);
@@ -87210,7 +87869,8 @@ var SearchService = class {
87210
87869
  ...result,
87211
87870
  items: result.items.map((item) => item.sourceKind === 3 && (item.sourceUid ?? item.routeTargetUid) === source.ownerRef ? {
87212
87871
  ...item,
87213
- targetSource
87872
+ targetSource,
87873
+ sourceTitle: targetSource.displayName
87214
87874
  } : item)
87215
87875
  };
87216
87876
  }
@@ -87218,14 +87878,33 @@ var SearchService = class {
87218
87878
  const aggregateTitleByKey = new Map(result.sourceAggregates.map((item) => [`${String(item.sourceKind)}:${item.sourceUid}`, item.title]));
87219
87879
  for (const item of result.items) {
87220
87880
  const sourceUid = item.sourceUid ?? item.routeTargetUid ?? "";
87221
- targets.set(`${String(item.sourceKind)}:${sourceUid}`, item);
87881
+ const key = `${String(item.sourceKind)}:${sourceUid}`;
87882
+ targets.set(key, {
87883
+ sourceKind: item.sourceKind,
87884
+ sourceUid,
87885
+ title: item.sourceTitle ?? aggregateTitleByKey.get(key) ?? ""
87886
+ });
87887
+ }
87888
+ for (const item of result.sourceAggregates) {
87889
+ const key = `${String(item.sourceKind)}:${item.sourceUid}`;
87890
+ if (!targets.has(key)) targets.set(key, item);
87222
87891
  }
87223
87892
  const sourceByKey = /* @__PURE__ */ new Map();
87224
- for (const [key, item] of targets) try {
87225
- sourceByKey.set(key, await this.source.searchTargetSource(item.sourceKind, item.sourceUid ?? item.routeTargetUid ?? "", item.sourceTitle ?? aggregateTitleByKey.get(key) ?? "", signal));
87893
+ const chatUids = [...targets.values()].filter((item) => item.sourceKind === 3).map((item) => item.sourceUid);
87894
+ if (chatUids.length > 0) try {
87895
+ const chats = await this.source.chatSourcesBySessionUids(chatUids, signal);
87896
+ for (const [uid, source] of chats) sourceByKey.set(`3:${uid}`, source);
87226
87897
  } catch (error) {
87227
87898
  if (signal?.aborted) throw error;
87228
- sourceByKey.set(key, void 0);
87899
+ }
87900
+ for (const [key, item] of targets) {
87901
+ if (item.sourceKind === 3) continue;
87902
+ try {
87903
+ sourceByKey.set(key, await this.source.searchTargetSource(item.sourceKind, item.sourceUid, item.title, signal));
87904
+ } catch (error) {
87905
+ if (signal?.aborted) throw error;
87906
+ sourceByKey.set(key, void 0);
87907
+ }
87229
87908
  }
87230
87909
  return {
87231
87910
  ...result,
@@ -87234,7 +87913,17 @@ var SearchService = class {
87234
87913
  const targetSource = sourceByKey.get(`${String(item.sourceKind)}:${sourceUid}`);
87235
87914
  return targetSource === void 0 ? item : {
87236
87915
  ...item,
87237
- targetSource
87916
+ targetSource,
87917
+ sourceTitle: targetSource.displayName
87918
+ };
87919
+ }),
87920
+ sourceAggregates: result.sourceAggregates.map((item) => {
87921
+ const targetSource = sourceByKey.get(`${String(item.sourceKind)}:${item.sourceUid}`);
87922
+ return targetSource === void 0 ? item : {
87923
+ ...item,
87924
+ title: targetSource.displayName,
87925
+ targetSource,
87926
+ ...targetSource.privateNickname ? { nickname: targetSource.privateNickname } : {}
87238
87927
  };
87239
87928
  })
87240
87929
  };
@@ -87391,13 +88080,14 @@ var SearchService = class {
87391
88080
  const files = listValue$3(item.file_ls).map(assetItem).filter((value) => value !== void 0);
87392
88081
  const voice = assetItem(payload.voice);
87393
88082
  const textContent = clippedText(core.text_content, 2e3);
87394
- const linkMatch = textContent.match(/https:\/\/[^\s<>()]+/u);
88083
+ const linkUrls = arkmeSearchRecordLinks(stringValue$2(core.text_content));
87395
88084
  const sourceTitle = stringValue$2(topic.title ?? chat.title).trim();
87396
88085
  const creationSource = Math.trunc(numberValue$2(core.creation_source ?? item.creation_source));
87397
88086
  const recordOwnerUserId = recordOwnerId(core.owner_user_id);
87398
88087
  return {
87399
88088
  recordUid,
87400
88089
  ...recordOwnerUserId !== 0 ? { recordOwnerUserId } : {},
88090
+ ...recordOwnerId(core.creator_user_id) !== 0 ? { recordCreatorUserId: recordOwnerId(core.creator_user_id) } : {},
87401
88091
  sourceKind: Math.trunc(numberValue$2(item.source_kind)),
87402
88092
  ...stringValue$2(item.source_uid).trim() === "" ? {} : { sourceUid: stringValue$2(item.source_uid).trim() },
87403
88093
  routeTargetKind: stringValue$2(item.route_target_kind).trim(),
@@ -87414,7 +88104,10 @@ var SearchService = class {
87414
88104
  media,
87415
88105
  files,
87416
88106
  ...voice === void 0 ? {} : { voice },
87417
- ...linkMatch === null ? {} : { linkUrl: linkMatch[0] },
88107
+ ...linkUrls.length === 0 ? {} : {
88108
+ linkUrl: linkUrls[0],
88109
+ linkUrls
88110
+ },
87418
88111
  ...numberValue$2(core.duration_millis ?? core.record_duration_millis) <= 0 ? {} : { recordDurationMillis: numberValue$2(core.duration_millis ?? core.record_duration_millis) },
87419
88112
  ...numberValue$2(item.scene_item_count) <= 0 ? {} : { sceneItemCount: numberValue$2(item.scene_item_count) },
87420
88113
  ...numberValue$2(item.scene_item_size) <= 0 ? {} : { sceneItemSize: numberValue$2(item.scene_item_size) }
@@ -91135,7 +91828,9 @@ var ArkmeService = class {
91135
91828
  userBan;
91136
91829
  directMessageAdmissionOwner;
91137
91830
  backgroundSoundPreferenceOwner;
91831
+ membershipOwner;
91138
91832
  fileTransfers;
91833
+ desktopScreenshot;
91139
91834
  localFileOpener;
91140
91835
  worldVoiceprintInviteVariantIndex = 0;
91141
91836
  constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker(), billingGateway, linkDocumentReader, localDshQuery) {
@@ -91257,6 +91952,11 @@ var ArkmeService = class {
91257
91952
  }
91258
91953
  });
91259
91954
  this.backgroundSoundPreferenceOwner = new BackgroundSoundPreferenceService(this.runtime, new BackgroundSoundMembershipService(this.runtime));
91955
+ this.membershipOwner = new MembershipService(this.runtime);
91956
+ this.desktopScreenshot = new ArkmeDesktopScreenshot({
91957
+ currentUser: async () => (await this.runtime.requireSession()).userId,
91958
+ maxImageBytes: () => this.filesOwner().capabilities().maxImageBytes
91959
+ });
91260
91960
  this.fileTransfers = createArkmeFileTransfers({
91261
91961
  directory: config.fileStateDirectory,
91262
91962
  maxUploadBytes: config.maxUploadBytes,
@@ -91277,6 +91977,13 @@ var ArkmeService = class {
91277
91977
  fileCapabilities() {
91278
91978
  return this.filesOwner().capabilities();
91279
91979
  }
91980
+ async screenshotCapability() {
91981
+ await this.runtime.requireSession();
91982
+ return await this.desktopScreenshot.capability();
91983
+ }
91984
+ async captureScreenshot(expectedUserId, signal) {
91985
+ return await this.desktopScreenshot.capture(expectedUserId, signal);
91986
+ }
91280
91987
  async fileSearch(options) {
91281
91988
  return await this.search.searchFiles(options);
91282
91989
  }
@@ -91331,6 +92038,12 @@ var ArkmeService = class {
91331
92038
  async fileReceive(mediaRef, start = false) {
91332
92039
  return await this.filesOwner().reception(mediaRef, start);
91333
92040
  }
92041
+ async membershipCurrent(expectedUserId) {
92042
+ return await this.membershipOwner.current(expectedUserId);
92043
+ }
92044
+ async membershipCatalog(expectedUserId) {
92045
+ return await this.membershipOwner.catalog(expectedUserId);
92046
+ }
91334
92047
  async backgroundSoundPreference(signal) {
91335
92048
  return await this.backgroundSoundPreferenceOwner.preference(signal);
91336
92049
  }
@@ -91338,6 +92051,7 @@ var ArkmeService = class {
91338
92051
  return await this.backgroundSoundPreferenceOwner.update(enabled, signal, expectedUserId);
91339
92052
  }
91340
92053
  clearAccountState(userIds) {
92054
+ this.desktopScreenshot.cancel();
91341
92055
  this.calendar.dispose();
91342
92056
  this.directory.reset();
91343
92057
  this.realtime.resetAttentionSummary();
@@ -91625,6 +92339,7 @@ var ArkmeService = class {
91625
92339
  return await this.callHistory.retryCallSummary(callRef, signal);
91626
92340
  }
91627
92341
  dispose() {
92342
+ this.desktopScreenshot.cancel();
91628
92343
  this.directory.reset();
91629
92344
  this.record.dispose();
91630
92345
  this.realtime.resetAttentionSummary();
@@ -92229,6 +92944,9 @@ var ArkmeService = class {
92229
92944
  async forwardSourceMessages(sourceRef, actionRefs, options = {}) {
92230
92945
  return await this.chat.forwardSourceMessages(sourceRef, actionRefs, options);
92231
92946
  }
92947
+ async ownLongArticle(itemUid, expectedUserId, signal) {
92948
+ return await this.chat.ownLongArticle(itemUid, expectedUserId, signal);
92949
+ }
92232
92950
  async forwardMessageActions(conversationRef, actionRefs, options) {
92233
92951
  return await this.messageActions.forward(conversationRef, actionRefs, options);
92234
92952
  }
@@ -92316,6 +93034,7 @@ var ArkmeService = class {
92316
93034
  async publishLongArticle(sourceRef, input, signal) {
92317
93035
  if (!/^[A-Za-z0-9._:-]{8,256}$/.test(input.recordUid) || !/^[A-Za-z0-9._:-]{8,256}$/.test(input.relationUid)) throw new ArkmePluginError("long-article-id-invalid", "长文提交标识无效", false);
92318
93036
  const userId = (await this.runtime.requireSession()).userId;
93037
+ if (input.expectedUserId !== void 0 && input.expectedUserId !== userId) throw new ArkmePluginError("file-account-changed", "账号已切换", false, 403);
92319
93038
  let previous;
92320
93039
  try {
92321
93040
  previous = await this.chat.longArticleDetail(sourceRef, input.recordUid, signal);
@@ -92381,8 +93100,8 @@ var ArkmeService = class {
92381
93100
  async putLongArticleDraft(draft) {
92382
93101
  return await this.chat.putLongArticleDraft(draft);
92383
93102
  }
92384
- async removeLongArticleDraft(sourceRef, itemUid) {
92385
- return await this.chat.removeLongArticleDraft(sourceRef, itemUid);
93103
+ async removeLongArticleDraft(sourceRef, itemUid, expectedRecordUid) {
93104
+ return await this.chat.removeLongArticleDraft(sourceRef, itemUid, expectedRecordUid);
92386
93105
  }
92387
93106
  async prepareRecordReedit(input, options = {}) {
92388
93107
  return await this.record.prepareRecordReedit(input, options);
@@ -92505,6 +93224,9 @@ var ArkmeService = class {
92505
93224
  async searchRemote(options) {
92506
93225
  return await this.search.searchRemote(options);
92507
93226
  }
93227
+ async searchConversationNames(options) {
93228
+ return await this.source.searchConversationNames(options);
93229
+ }
92508
93230
  async searchTagRecords(options) {
92509
93231
  return await this.search.searchTagRecords(options);
92510
93232
  }