@senguoyun/dsh-arkme 0.1.31 → 0.1.32

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 (108) hide show
  1. package/lib/client.js +1946 -762
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +3693 -171
  4. package/lib/persistent-extension.js +1 -1
  5. package/lib/{persistent-runtime-BJ8konXI.js → persistent-runtime-CFeIg6aw.js} +1520 -681
  6. package/lib/sdk.js +38 -0
  7. package/lib/types/arkme-service.d.ts +25 -10
  8. package/lib/types/arkme-service.d.ts.map +1 -1
  9. package/lib/types/client/ArkmeBillingSettings.d.ts.map +1 -1
  10. package/lib/types/client/ArkmeBotConversationSurface.d.ts.map +1 -1
  11. package/lib/types/client/ArkmeBotSettingsPanel.d.ts.map +1 -1
  12. package/lib/types/client/ArkmeGroupChatControls.d.ts +17 -1
  13. package/lib/types/client/ArkmeGroupChatControls.d.ts.map +1 -1
  14. package/lib/types/client/ArkmeLinkText.d.ts +13 -0
  15. package/lib/types/client/ArkmeLinkText.d.ts.map +1 -0
  16. package/lib/types/client/ArkmeLongArticleDialog.d.ts.map +1 -1
  17. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  18. package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -1
  19. package/lib/types/client/ArkmeRichContent.d.ts +4 -21
  20. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  21. package/lib/types/client/ArkmeRichText.d.ts +12 -0
  22. package/lib/types/client/ArkmeRichText.d.ts.map +1 -0
  23. package/lib/types/client/ArkmeSearchSurface.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  26. package/lib/types/client/api.d.ts +1 -1
  27. package/lib/types/client/api.d.ts.map +1 -1
  28. package/lib/types/client/bot-chat-directory-projection.d.ts +7 -0
  29. package/lib/types/client/bot-chat-directory-projection.d.ts.map +1 -0
  30. package/lib/types/client/link-metadata-client.d.ts +20 -0
  31. package/lib/types/client/link-metadata-client.d.ts.map +1 -0
  32. package/lib/types/client/realtime-client-events.d.ts +4 -1
  33. package/lib/types/client/realtime-client-events.d.ts.map +1 -1
  34. package/lib/types/client/redesign/contacts/AlphabeticalContactList.d.ts +3 -3
  35. package/lib/types/client/redesign/contacts/AlphabeticalContactList.d.ts.map +1 -1
  36. package/lib/types/client/redesign/contacts/ContactDirectorySurface.d.ts +3 -3
  37. package/lib/types/client/redesign/contacts/ContactDirectorySurface.d.ts.map +1 -1
  38. package/lib/types/client/related-recordings.d.ts +3 -0
  39. package/lib/types/client/related-recordings.d.ts.map +1 -1
  40. package/lib/types/client/text-link-parser.d.ts +10 -0
  41. package/lib/types/client/text-link-parser.d.ts.map +1 -0
  42. package/lib/types/client/text-link-tlds.d.ts +2 -0
  43. package/lib/types/client/text-link-tlds.d.ts.map +1 -0
  44. package/lib/types/client/ui-controller.d.ts.map +1 -1
  45. package/lib/types/dsh-host-capabilities.d.ts +10 -0
  46. package/lib/types/dsh-host-capabilities.d.ts.map +1 -0
  47. package/lib/types/dsh-remote/api-proxy-adapter.d.ts +250 -0
  48. package/lib/types/dsh-remote/api-proxy-adapter.d.ts.map +1 -0
  49. package/lib/types/dsh-remote/channel-manager.d.ts +45 -0
  50. package/lib/types/dsh-remote/channel-manager.d.ts.map +1 -0
  51. package/lib/types/dsh-remote/command-ledger.d.ts +60 -0
  52. package/lib/types/dsh-remote/command-ledger.d.ts.map +1 -0
  53. package/lib/types/dsh-remote/control-plane.d.ts +17 -0
  54. package/lib/types/dsh-remote/control-plane.d.ts.map +1 -0
  55. package/lib/types/dsh-remote/crypto.d.ts +11 -0
  56. package/lib/types/dsh-remote/crypto.d.ts.map +1 -0
  57. package/lib/types/dsh-remote/default-socket-factory.d.ts +8 -0
  58. package/lib/types/dsh-remote/default-socket-factory.d.ts.map +1 -0
  59. package/lib/types/dsh-remote/dsh-event-contract.d.ts +29 -0
  60. package/lib/types/dsh-remote/dsh-event-contract.d.ts.map +1 -0
  61. package/lib/types/dsh-remote/errors.d.ts +9 -0
  62. package/lib/types/dsh-remote/errors.d.ts.map +1 -0
  63. package/lib/types/dsh-remote/host.d.ts +85 -0
  64. package/lib/types/dsh-remote/host.d.ts.map +1 -0
  65. package/lib/types/dsh-remote/protocol-v1.d.ts +6 -0
  66. package/lib/types/dsh-remote/protocol-v1.d.ts.map +1 -0
  67. package/lib/types/dsh-remote/realtime-transport.d.ts +84 -0
  68. package/lib/types/dsh-remote/realtime-transport.d.ts.map +1 -0
  69. package/lib/types/dsh-remote/runtime-secret-broker.d.ts +28 -0
  70. package/lib/types/dsh-remote/runtime-secret-broker.d.ts.map +1 -0
  71. package/lib/types/dsh-remote/runtime-store.d.ts +39 -0
  72. package/lib/types/dsh-remote/runtime-store.d.ts.map +1 -0
  73. package/lib/types/dsh-remote/transport-fragment.d.ts +10 -0
  74. package/lib/types/dsh-remote/transport-fragment.d.ts.map +1 -0
  75. package/lib/types/dsh-remote/types.d.ts +164 -0
  76. package/lib/types/dsh-remote/types.d.ts.map +1 -0
  77. package/lib/types/harness-embed-route.d.ts +11 -1
  78. package/lib/types/harness-embed-route.d.ts.map +1 -1
  79. package/lib/types/host-api.d.ts +3 -1
  80. package/lib/types/host-api.d.ts.map +1 -1
  81. package/lib/types/index.d.ts +14 -0
  82. package/lib/types/index.d.ts.map +1 -1
  83. package/lib/types/keychain-store.d.ts +33 -0
  84. package/lib/types/keychain-store.d.ts.map +1 -1
  85. package/lib/types/link-metadata.d.ts +9 -0
  86. package/lib/types/link-metadata.d.ts.map +1 -0
  87. package/lib/types/managed-ai/adapter.d.ts +2 -0
  88. package/lib/types/managed-ai/adapter.d.ts.map +1 -1
  89. package/lib/types/sdk/index.d.ts +9 -2
  90. package/lib/types/sdk/index.d.ts.map +1 -1
  91. package/lib/types/services/bot-conversation-service.d.ts +59 -0
  92. package/lib/types/services/bot-conversation-service.d.ts.map +1 -0
  93. package/lib/types/services/bot-service.d.ts +11 -29
  94. package/lib/types/services/bot-service.d.ts.map +1 -1
  95. package/lib/types/services/chat-service.d.ts +30 -5
  96. package/lib/types/services/chat-service.d.ts.map +1 -1
  97. package/lib/types/services/contact-directory-service.d.ts.map +1 -1
  98. package/lib/types/services/link-metadata-service.d.ts +73 -0
  99. package/lib/types/services/link-metadata-service.d.ts.map +1 -0
  100. package/lib/types/services/related-recording-service.d.ts +1 -0
  101. package/lib/types/services/related-recording-service.d.ts.map +1 -1
  102. package/lib/types/services/service.d.ts +4 -2
  103. package/lib/types/services/service.d.ts.map +1 -1
  104. package/lib/types/tools/ports/bots.d.ts +1 -1
  105. package/lib/types/tools/ports/bots.d.ts.map +1 -1
  106. package/lib/types/types.d.ts +55 -21
  107. package/lib/types/types.d.ts.map +1 -1
  108. package/package.json +23 -23
@@ -4,6 +4,10 @@ import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } fr
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { chmod as chmod$1, copyFile, link, mkdir, open, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
6
6
  import { pinyin } from "pinyin-pro";
7
+ import { Resolver } from "node:dns/promises";
8
+ import { request } from "node:http";
9
+ import { request as request$1 } from "node:https";
10
+ import { BlockList, isIP } from "node:net";
7
11
  import OSS from "ali-oss";
8
12
  import { Readable } from "node:stream";
9
13
  import { gunzipSync, gzipSync } from "node:zlib";
@@ -1006,7 +1010,7 @@ var ServiceRuntime = class {
1006
1010
  signal.removeEventListener("abort", abort);
1007
1011
  }
1008
1012
  }
1009
- async get(baseUrl, path, bearer, successCodes, signal, options = {}) {
1013
+ async get(baseUrl, path, bearer, successCodes, signal, options = {}, preferDataError = false) {
1010
1014
  return await this.requestCoordinator.run({
1011
1015
  scope: options.scope ?? "public",
1012
1016
  lane: options.lane ?? "interactive-read",
@@ -1022,10 +1026,10 @@ var ServiceRuntime = class {
1022
1026
  "login-expired"
1023
1027
  ].includes(error.code),
1024
1028
  serviceCooldownMs: (error) => this.remoteServiceCooldownMs(error),
1025
- operation: async (coordinatedSignal) => await this.getDirect(baseUrl, path, bearer, successCodes, coordinatedSignal)
1029
+ operation: async (coordinatedSignal) => await this.getDirect(baseUrl, path, bearer, successCodes, coordinatedSignal, preferDataError)
1026
1030
  });
1027
1031
  }
1028
- async getDirect(baseUrl, path, bearer, successCodes, signal = new AbortController().signal) {
1032
+ async getDirect(baseUrl, path, bearer, successCodes, signal = new AbortController().signal, preferDataError = false) {
1029
1033
  const controller = new AbortController();
1030
1034
  const abort = () => controller.abort(signal.reason);
1031
1035
  if (signal.aborted) abort();
@@ -1055,7 +1059,12 @@ var ServiceRuntime = class {
1055
1059
  } catch (error) {
1056
1060
  throw new ArkmePluginError("arkme-response-invalid", "Arkme 服务返回了无效响应", true, 502, { cause: error });
1057
1061
  }
1058
- if (!successCodes.includes(envelope.code)) throw new ArkmePluginError(`arkme-code-${envelope.code}`, envelope.message?.trim() || "Arkme 服务请求失败", envelope.code >= 500, 502);
1062
+ if (!successCodes.includes(envelope.code)) {
1063
+ const errorData = objectValue$1(envelope.data);
1064
+ const serviceErrorCode = preferDataError ? stringValue$1(errorData.error_code).trim() : "";
1065
+ const serviceMessage = preferDataError ? stringValue$1(errorData.message).trim() : "";
1066
+ throw new ArkmePluginError(serviceErrorCode || `arkme-code-${envelope.code}`, serviceMessage || envelope.message?.trim() || "Arkme 服务请求失败", serviceErrorCode === "" ? envelope.code >= 500 : serviceErrorCode === "PAIRING_RATE_LIMITED", 502);
1067
+ }
1059
1068
  return envelope.data ?? {};
1060
1069
  } catch (error) {
1061
1070
  if (error instanceof ArkmePluginError) throw error;
@@ -1085,6 +1094,17 @@ var ServiceRuntime = class {
1085
1094
  return await this.get(this.config.authBaseUrl, path, session.accessToken, [200], signal, requestOptions());
1086
1095
  }
1087
1096
  }
1097
+ async authenticatedDshRemoteGet(path, signal) {
1098
+ let session = await this.requireSession();
1099
+ const requestOptions = () => this.authenticatedRequestOptions(session, "auth", "interactive-read", { bypassCache: true });
1100
+ try {
1101
+ return await this.get(this.config.authBaseUrl, path, session.accessToken, [200], signal, requestOptions(), true);
1102
+ } catch (error) {
1103
+ if (!(error instanceof ArkmePluginError) || !["auth-http-401", "auth-http-403"].includes(error.code)) throw error;
1104
+ session = await this.refreshAccessToken(session);
1105
+ return await this.get(this.config.authBaseUrl, path, session.accessToken, [200], signal, requestOptions(), true);
1106
+ }
1107
+ }
1088
1108
  async authenticatedPost(path, body, initialSession, signal, options = {}) {
1089
1109
  let session = initialSession ?? await this.requireSession();
1090
1110
  const requestOptions = () => this.authenticatedRequestOptions(session, "record", "write", options);
@@ -1131,6 +1151,17 @@ var ServiceRuntime = class {
1131
1151
  return await this.post(this.config.authBaseUrl, path, body, session.accessToken, [200], signal, false, requestOptions());
1132
1152
  }
1133
1153
  }
1154
+ async authenticatedDshRemotePost(path, body, initialSession, signal) {
1155
+ let session = initialSession ?? await this.requireSession();
1156
+ const requestOptions = () => this.authenticatedRequestOptions(session, "auth", "write", { bypassCache: true });
1157
+ try {
1158
+ return await this.post(this.config.authBaseUrl, path, body, session.accessToken, [200], signal, true, requestOptions());
1159
+ } catch (error) {
1160
+ if (!(error instanceof ArkmePluginError) || !["auth-http-401", "auth-http-403"].includes(error.code)) throw error;
1161
+ session = await this.refreshAccessToken(session);
1162
+ return await this.post(this.config.authBaseUrl, path, body, session.accessToken, [200], signal, true, requestOptions());
1163
+ }
1164
+ }
1134
1165
  async authenticatedSubjectPost(path, body, initialSession, signal) {
1135
1166
  let session = initialSession ?? await this.requireSession();
1136
1167
  try {
@@ -1813,13 +1844,13 @@ function bestMatchingCategory(text) {
1813
1844
  }
1814
1845
  //#endregion
1815
1846
  //#region src/services/ai-video-service.ts
1816
- function numberValue$24(value) {
1847
+ function numberValue$25(value) {
1817
1848
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
1818
1849
  }
1819
- function booleanValue$14(value) {
1850
+ function booleanValue$15(value) {
1820
1851
  return value === true;
1821
1852
  }
1822
- function listValue$25(value) {
1853
+ function listValue$26(value) {
1823
1854
  return Array.isArray(value) ? value : [];
1824
1855
  }
1825
1856
  var AiVideoService = class {
@@ -1831,12 +1862,12 @@ var AiVideoService = class {
1831
1862
  const session = await this.runtime.requireSession();
1832
1863
  const data = await this.runtime.authenticatedIntelligentPost("/api/v1/ai-comic-video/preflight", this.aiVideoSelectionBody(sessionId, segments), session, signal);
1833
1864
  return {
1834
- allowed: booleanValue$14(data.allowed),
1865
+ allowed: booleanValue$15(data.allowed),
1835
1866
  message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
1836
- selectedDurationMillis: Math.max(0, numberValue$24(data.selected_duration_millis)),
1837
- minimumDurationMillis: Math.max(0, numberValue$24(data.minimum_duration_millis)),
1838
- selectedSegmentCount: Math.max(0, numberValue$24(data.selected_segment_count)),
1839
- retryable: booleanValue$14(data.retryable),
1867
+ selectedDurationMillis: Math.max(0, numberValue$25(data.selected_duration_millis)),
1868
+ minimumDurationMillis: Math.max(0, numberValue$25(data.minimum_duration_millis)),
1869
+ selectedSegmentCount: Math.max(0, numberValue$25(data.selected_segment_count)),
1870
+ retryable: booleanValue$15(data.retryable),
1840
1871
  ...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
1841
1872
  ...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
1842
1873
  };
@@ -1871,8 +1902,8 @@ var AiVideoService = class {
1871
1902
  ...options.statuses === void 0 || options.statuses.length === 0 ? {} : { statuses: options.statuses }
1872
1903
  }, session, options.signal);
1873
1904
  return {
1874
- items: listValue$25(data.items).map((raw) => this.aiVideoListItem(raw)).filter((item) => item !== void 0),
1875
- hasMore: booleanValue$14(data.has_more),
1905
+ items: listValue$26(data.items).map((raw) => this.aiVideoListItem(raw)).filter((item) => item !== void 0),
1906
+ hasMore: booleanValue$15(data.has_more),
1876
1907
  ...stringValue$1(data.next_cursor).trim() === "" ? {} : { nextCursor: stringValue$1(data.next_cursor).trim() }
1877
1908
  };
1878
1909
  }
@@ -1883,13 +1914,13 @@ var AiVideoService = class {
1883
1914
  texts
1884
1915
  }, session, signal);
1885
1916
  return {
1886
- allowed: booleanValue$14(data.allowed),
1917
+ allowed: booleanValue$15(data.allowed),
1887
1918
  message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
1888
- selectedDurationMillis: Math.max(0, numberValue$24(data.selected_duration_millis)),
1889
- minimumDurationMillis: Math.max(0, numberValue$24(data.minimum_duration_millis)),
1890
- selectedSegmentCount: Math.max(0, numberValue$24(data.selected_segment_count)),
1891
- selectedTextCount: Math.max(0, numberValue$24(data.selected_text_count)),
1892
- retryable: booleanValue$14(data.retryable),
1919
+ selectedDurationMillis: Math.max(0, numberValue$25(data.selected_duration_millis)),
1920
+ minimumDurationMillis: Math.max(0, numberValue$25(data.minimum_duration_millis)),
1921
+ selectedSegmentCount: Math.max(0, numberValue$25(data.selected_segment_count)),
1922
+ selectedTextCount: Math.max(0, numberValue$25(data.selected_text_count)),
1923
+ retryable: booleanValue$15(data.retryable),
1893
1924
  ...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
1894
1925
  ...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
1895
1926
  };
@@ -1928,19 +1959,19 @@ var AiVideoService = class {
1928
1959
  "canceled"
1929
1960
  ])).has(status)) throw new ArkmePluginError("ai-video-contract-invalid", "AI 视频服务返回了无效任务信息", true, 502);
1930
1961
  const selection = objectValue$1(data.selection);
1931
- const segmentCount = listValue$25(selection.segments).length;
1932
- const textCount = listValue$25(selection.texts).length;
1962
+ const segmentCount = listValue$26(selection.segments).length;
1963
+ const textCount = listValue$26(selection.texts).length;
1933
1964
  return {
1934
1965
  jobId,
1935
1966
  status,
1936
1967
  stage: stringValue$1(data.stage).trim() || status,
1937
- progress: Math.min(100, Math.max(0, Math.trunc(numberValue$24(data.progress)))),
1968
+ progress: Math.min(100, Math.max(0, Math.trunc(numberValue$25(data.progress)))),
1938
1969
  selectedSegmentCount: segmentCount,
1939
1970
  ...textCount === 0 ? {} : { selectedTextCount: textCount },
1940
- retryable: booleanValue$14(data.retryable),
1971
+ retryable: booleanValue$15(data.retryable),
1941
1972
  ...stringValue$1(data.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(data.video_asset_uid).trim() },
1942
1973
  ...stringValue$1(data.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(data.cover_asset_uid).trim() },
1943
- ...numberValue$24(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$24(data.video_duration_millis) },
1974
+ ...numberValue$25(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$25(data.video_duration_millis) },
1944
1975
  ...stringValue$1(data.error_code).trim() === "" ? {} : { errorCode: stringValue$1(data.error_code).trim() },
1945
1976
  ...stringValue$1(data.error_message).trim() === "" ? {} : { errorMessage: stringValue$1(data.error_message).trim() },
1946
1977
  ...stringValue$1(data.failure_stage).trim() === "" ? {} : { failureStage: stringValue$1(data.failure_stage).trim() }
@@ -1963,17 +1994,17 @@ var AiVideoService = class {
1963
1994
  sessionId: stringValue$1(item.session_id).trim(),
1964
1995
  status,
1965
1996
  stage: stringValue$1(item.stage).trim() || status,
1966
- progress: Math.min(100, Math.max(0, Math.trunc(numberValue$24(item.progress)))),
1997
+ progress: Math.min(100, Math.max(0, Math.trunc(numberValue$25(item.progress)))),
1967
1998
  title: stringValue$1(source.title).trim() || "长录音 AI 视频",
1968
- sourceStartedAtMillis: numberValue$24(source.started_at),
1969
- selectedDurationMillis: Math.max(0, numberValue$24(source.selected_duration_millis)),
1970
- selectedSegmentCount: Math.max(0, Math.trunc(numberValue$24(item.selected_segment_count))),
1971
- retryable: booleanValue$14(item.retryable),
1972
- createdAtMillis: numberValue$24(item.created_at),
1973
- updatedAtMillis: numberValue$24(item.updated_at),
1999
+ sourceStartedAtMillis: numberValue$25(source.started_at),
2000
+ selectedDurationMillis: Math.max(0, numberValue$25(source.selected_duration_millis)),
2001
+ selectedSegmentCount: Math.max(0, Math.trunc(numberValue$25(item.selected_segment_count))),
2002
+ retryable: booleanValue$15(item.retryable),
2003
+ createdAtMillis: numberValue$25(item.created_at),
2004
+ updatedAtMillis: numberValue$25(item.updated_at),
1974
2005
  ...stringValue$1(item.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(item.cover_asset_uid).trim() },
1975
2006
  ...stringValue$1(item.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(item.video_asset_uid).trim() },
1976
- ...numberValue$24(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$24(item.video_duration_millis) },
2007
+ ...numberValue$25(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$25(item.video_duration_millis) },
1977
2008
  ...stringValue$1(item.error_code).trim() === "" ? {} : { errorCode: stringValue$1(item.error_code).trim() },
1978
2009
  ...stringValue$1(item.error_message).trim() === "" ? {} : { errorMessage: clippedText(item.error_message, 500) }
1979
2010
  };
@@ -1999,13 +2030,13 @@ const ARKO_RUN_STATUSES = /* @__PURE__ */ new Set([
1999
2030
  "expired"
2000
2031
  ]);
2001
2032
  const ARKO_PROFILE_DISPLAY_NAME_CACHE_TTL_MS = 10 * 6e4;
2002
- function numberValue$23(value) {
2033
+ function numberValue$24(value) {
2003
2034
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2004
2035
  }
2005
- function booleanValue$13(value) {
2036
+ function booleanValue$14(value) {
2006
2037
  return value === true;
2007
2038
  }
2008
- function listValue$24(value) {
2039
+ function listValue$25(value) {
2009
2040
  return Array.isArray(value) ? value : [];
2010
2041
  }
2011
2042
  function optionalString$3(value) {
@@ -2034,7 +2065,7 @@ function joinUrlWithQuery(baseUrl, path, query) {
2034
2065
  }
2035
2066
  function arkoProfileFromData(data) {
2036
2067
  const displayName = stringValue$1(data.display_name).trim();
2037
- const version = numberValue$23(data.version);
2068
+ const version = numberValue$24(data.version);
2038
2069
  if (displayName === "" || !Number.isSafeInteger(version) || version < 0) throw new ArkmePluginError("arko-profile-contract-invalid", "Arko 资料响应不完整", true, 502);
2039
2070
  return {
2040
2071
  displayName,
@@ -2058,15 +2089,15 @@ function arkoModelOptionFromData(value) {
2058
2089
  displayName,
2059
2090
  provider,
2060
2091
  description,
2061
- recommended: booleanValue$13(data.recommended),
2062
- selected: booleanValue$13(data.selected)
2092
+ recommended: booleanValue$14(data.recommended),
2093
+ selected: booleanValue$14(data.selected)
2063
2094
  };
2064
2095
  }
2065
2096
  function arkoModelCatalogFromData(data) {
2066
2097
  const defaultRouteKey = arkoModelRouteKey(data.default_route_key);
2067
2098
  const effectiveRouteKey = arkoModelRouteKey(data.effective_route_key);
2068
2099
  const selectionSource = stringValue$1(data.selection_source);
2069
- const options = listValue$24(data.items).map(arkoModelOptionFromData);
2100
+ const options = listValue$25(data.items).map(arkoModelOptionFromData);
2070
2101
  const routeKeys = new Set(options.map((option) => option.routeKey));
2071
2102
  const selected = options.filter((option) => option.selected);
2072
2103
  if (selectionSource !== "default" && selectionSource !== "personal" || options.length === 0 || options.length > 16 || routeKeys.size !== options.length || !routeKeys.has(defaultRouteKey) || !routeKeys.has(effectiveRouteKey) || selected.length !== 1 || selected[0]?.routeKey !== effectiveRouteKey) throw new ArkmePluginError("arko-model-contract-invalid", "Arko 模型目录响应无效", true, 502);
@@ -2087,9 +2118,9 @@ function parsedJsonRecord(value) {
2087
2118
  }
2088
2119
  function arkoHistoryItemFromData(value) {
2089
2120
  const data = objectValue$1(value);
2090
- const messageId = numberValue$23(data.id ?? data.msg_id ?? data.msgId);
2091
- const sessionId = numberValue$23(data.session_id ?? data.sessionId);
2092
- const roleCode = numberValue$23(data.role ?? data.sender_role ?? data.senderRole);
2121
+ const messageId = numberValue$24(data.id ?? data.msg_id ?? data.msgId);
2122
+ const sessionId = numberValue$24(data.session_id ?? data.sessionId);
2123
+ const roleCode = numberValue$24(data.role ?? data.sender_role ?? data.senderRole);
2093
2124
  if (!Number.isSafeInteger(messageId) || messageId <= 0 || !Number.isSafeInteger(sessionId) || sessionId <= 0 || roleCode !== 2 && roleCode !== 3) return void 0;
2094
2125
  const extra = parsedJsonRecord(data.extra ?? data.metadata ?? data.meta);
2095
2126
  const extraData = parsedJsonRecord(extra.data);
@@ -2110,7 +2141,7 @@ function arkoHistoryItemFromData(value) {
2110
2141
  extraData.updateAt,
2111
2142
  data.updated_at,
2112
2143
  data.updatedAt
2113
- ].map(numberValue$23).find((candidate) => candidate > 0) ?? 0;
2144
+ ].map(numberValue$24).find((candidate) => candidate > 0) ?? 0;
2114
2145
  const runUid = optionalString$3(extra.agent_run_uid);
2115
2146
  const runStatus = optionalString$3(extra.agent_run_status);
2116
2147
  const retryable = optionalBooleanValue$1(extra.agent_run_retryable);
@@ -2123,7 +2154,7 @@ function arkoHistoryItemFromData(value) {
2123
2154
  text: stringValue$1(data.content ?? data.text_content ?? data.textContent),
2124
2155
  reasoning: stringValue$1(data.reason_content ?? data.reason_text ?? data.reasonText),
2125
2156
  createdAtMillis: activityAt > 0 && activityAt < 1e11 ? activityAt * 1e3 : activityAt,
2126
- status: numberValue$23(data.status ?? data.msg_status),
2157
+ status: numberValue$24(data.status ?? data.msg_status),
2127
2158
  ...runUid === void 0 ? {} : { runUid },
2128
2159
  ...runStatus === void 0 ? {} : { runStatus },
2129
2160
  ...retryable === void 0 ? {} : { retryable },
@@ -2142,14 +2173,14 @@ function arkoRunProjectionFromData(data) {
2142
2173
  return {
2143
2174
  runUid,
2144
2175
  status,
2145
- retryable: booleanValue$13(data.agent_run_retryable ?? data.retryable),
2176
+ retryable: booleanValue$14(data.agent_run_retryable ?? data.retryable),
2146
2177
  ...errorCode === "" ? {} : { errorCode },
2147
2178
  ...retryOfRunUid === "" ? {} : { retryOfRunUid },
2148
2179
  ...clientAction === void 0 ? {} : { clientAction }
2149
2180
  };
2150
2181
  }
2151
2182
  function arkoCreatedRecordUids(value) {
2152
- return listValue$24(value).map((item) => stringValue$1(item).trim()).filter((item) => item !== "");
2183
+ return listValue$25(value).map((item) => stringValue$1(item).trim()).filter((item) => item !== "");
2153
2184
  }
2154
2185
  function arkoStreamFrame(raw) {
2155
2186
  try {
@@ -2178,7 +2209,7 @@ var ArkoService = class {
2178
2209
  async arkoEnsureSession(signal) {
2179
2210
  const session = await this.runtime.requireSession();
2180
2211
  const latest = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/latest-session", { type: ARKO_AGENT_DIRECT_SESSION_TYPE }, session, signal);
2181
- const latestSessionId = numberValue$23(latest.id);
2212
+ const latestSessionId = numberValue$24(latest.id);
2182
2213
  if (Number.isSafeInteger(latestSessionId) && latestSessionId > 0) return {
2183
2214
  sessionId: latestSessionId,
2184
2215
  created: false,
@@ -2188,7 +2219,7 @@ var ArkoService = class {
2188
2219
  }
2189
2220
  async arkoCreateSession(signal) {
2190
2221
  const session = await this.runtime.requireSession();
2191
- const createdSessionId = numberValue$23((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-session", {
2222
+ const createdSessionId = numberValue$24((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-session", {
2192
2223
  name: ARKO_DEFAULT_SESSION_NAME,
2193
2224
  type: ARKO_AGENT_DIRECT_SESSION_TYPE
2194
2225
  }, session, signal)).session_id);
@@ -2213,7 +2244,7 @@ var ArkoService = class {
2213
2244
  const normalizedLimit = Math.min(ARKO_HISTORY_PAGE_LIMIT, Math.max(1, Math.trunc(limit)));
2214
2245
  const normalizedOffset = Math.max(0, Math.trunc(offset));
2215
2246
  const session = await this.runtime.requireSession();
2216
- const rawItems = listValue$24((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/message-list", {
2247
+ const rawItems = listValue$25((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/message-list", {
2217
2248
  limit: normalizedLimit,
2218
2249
  offset: normalizedOffset,
2219
2250
  session_type: ARKO_AGENT_DIRECT_SESSION_TYPE
@@ -2256,9 +2287,9 @@ var ArkoService = class {
2256
2287
  };
2257
2288
  if (modelRouteKey !== "") body.model_route_key = modelRouteKey;
2258
2289
  const accepted = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-msg-v2", body, session, options.signal);
2259
- const userMsgId = numberValue$23(accepted.user_msg_id);
2260
- const assistantMsgId = numberValue$23(accepted.assistant_msg_id);
2261
- const acceptedSessionId = numberValue$23(accepted.session_id) || sessionId;
2290
+ const userMsgId = numberValue$24(accepted.user_msg_id);
2291
+ const assistantMsgId = numberValue$24(accepted.assistant_msg_id);
2292
+ const acceptedSessionId = numberValue$24(accepted.session_id) || sessionId;
2262
2293
  if (!Number.isSafeInteger(acceptedSessionId) || acceptedSessionId <= 0 || !Number.isSafeInteger(userMsgId) || userMsgId <= 0 || !Number.isSafeInteger(assistantMsgId) || assistantMsgId <= 0) throw new ArkmePluginError("arko-ask-contract-invalid", "Arko 消息响应不完整", true, 502);
2263
2294
  const runUid = stringValue$1(accepted.run_uid).trim();
2264
2295
  if (runUid === "") throw new ArkmePluginError("arko-ask-contract-invalid", "Arko 消息响应缺少运行标识", true, 502);
@@ -2294,8 +2325,8 @@ var ArkoService = class {
2294
2325
  }, session, signal);
2295
2326
  const status = stringValue$1(data.status).trim();
2296
2327
  const normalizedRunUid = stringValue$1(data.run_uid).trim() || runUid.trim();
2297
- const sequence = numberValue$23(data.sequence);
2298
- const surfaceAssistantMsgId = numberValue$23(data.surface_assistant_msg_id);
2328
+ const sequence = numberValue$24(data.sequence);
2329
+ const surfaceAssistantMsgId = numberValue$24(data.surface_assistant_msg_id);
2299
2330
  const clientAction = optionalRecord(data.client_action);
2300
2331
  if (normalizedRunUid !== runUid.trim() || !ARKO_RUN_STATUSES.has(status) || !Number.isSafeInteger(sequence) || sequence < 0 || !Number.isSafeInteger(surfaceAssistantMsgId) || surfaceAssistantMsgId <= 0) throw new ArkmePluginError("arko-run-status-contract-invalid", "Arko 运行状态响应不完整", true, 502);
2301
2332
  return {
@@ -2304,7 +2335,7 @@ var ArkoService = class {
2304
2335
  status,
2305
2336
  sequence,
2306
2337
  surfaceAssistantMsgId,
2307
- retryable: booleanValue$13(data.retryable),
2338
+ retryable: booleanValue$14(data.retryable),
2308
2339
  ...stringValue$1(data.error_code).trim() === "" ? {} : { errorCode: stringValue$1(data.error_code).trim() },
2309
2340
  ...stringValue$1(data.retry_of_run_uid).trim() === "" ? {} : { retryOfRunUid: stringValue$1(data.retry_of_run_uid).trim() },
2310
2341
  ...clientAction === void 0 ? {} : { clientAction }
@@ -2479,12 +2510,12 @@ var ArkoService = class {
2479
2510
  if (tailRun !== void 0) {
2480
2511
  run = tailRun;
2481
2512
  status = tailRun.status;
2482
- } else if (booleanValue$13(tail.error) || stringValue$1(tail.status).trim() === "error") status = "failed";
2513
+ } else if (booleanValue$14(tail.error) || stringValue$1(tail.status).trim() === "error") status = "failed";
2483
2514
  else status = ARKO_COMPLETED_STATUS;
2484
2515
  createdRecordUids = arkoCreatedRecordUids(tail.created_record_uids);
2485
2516
  const nextProfile = optionalRecord(tail.agent_profile);
2486
2517
  if (nextProfile !== void 0) profile = arkoProfileFromData(nextProfile);
2487
- if (booleanValue$13(tail.error) || stringValue$1(tail.status).trim() === "error") errorMessage = stringValue$1(tail.error_message).trim() || "Arko 执行失败";
2518
+ if (booleanValue$14(tail.error) || stringValue$1(tail.status).trim() === "error") errorMessage = stringValue$1(tail.error_message).trim() || "Arko 执行失败";
2488
2519
  }
2489
2520
  const type = stringValue$1(frame.type ?? frame.event).trim();
2490
2521
  const payload = optionalRecord(frame.payload) ?? frame;
@@ -2533,13 +2564,13 @@ const ARKME_ARRANGEMENT_REF_TTL_MILLIS = 900 * 1e3;
2533
2564
  const MAX_ARKME_ARRANGEMENT_REFS = 4096;
2534
2565
  const ARKME_ARRANGEMENT_REMINDER_REF_TTL_MILLIS = 900 * 1e3;
2535
2566
  const MAX_ARKME_ARRANGEMENT_REMINDER_REFS = 4096;
2536
- function numberValue$22(value) {
2567
+ function numberValue$23(value) {
2537
2568
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2538
2569
  }
2539
- function booleanValue$12(value) {
2570
+ function booleanValue$13(value) {
2540
2571
  return value === true;
2541
2572
  }
2542
- function listValue$23(value) {
2573
+ function listValue$24(value) {
2543
2574
  return Array.isArray(value) ? value : [];
2544
2575
  }
2545
2576
  function arrangementStatusCode(status) {
@@ -2549,7 +2580,7 @@ function arrangementStatusCode(status) {
2549
2580
  return -1;
2550
2581
  }
2551
2582
  function arrangementStatus(value) {
2552
- const code = Math.trunc(numberValue$22(value));
2583
+ const code = Math.trunc(numberValue$23(value));
2553
2584
  if (code === 1) return "identified";
2554
2585
  if (code === 2) return "following";
2555
2586
  if (code === 3) return "completed";
@@ -2598,9 +2629,9 @@ var ArrangementService = class {
2598
2629
  limit,
2599
2630
  offset
2600
2631
  }, session, options.signal, { lane: "interactive-read" });
2601
- const rawItems = listValue$23(data.list);
2632
+ const rawItems = listValue$24(data.list);
2602
2633
  const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementItem(raw, session.userId)));
2603
- const total = Math.max(0, Math.trunc(numberValue$22(data.total)));
2634
+ const total = Math.max(0, Math.trunc(numberValue$23(data.total)));
2604
2635
  const nextOffset = offset + rawItems.length;
2605
2636
  const hasMore = rawItems.length > 0 && nextOffset < total;
2606
2637
  return {
@@ -2624,9 +2655,9 @@ var ArrangementService = class {
2624
2655
  offset,
2625
2656
  unread_only: options.unreadOnly === true
2626
2657
  }, session, options.signal, { lane: "interactive-read" });
2627
- const rawItems = listValue$23(data.list);
2658
+ const rawItems = listValue$24(data.list);
2628
2659
  const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementReminderEvent(raw, session.userId)));
2629
- const total = Math.max(0, Math.trunc(numberValue$22(data.total)));
2660
+ const total = Math.max(0, Math.trunc(numberValue$23(data.total)));
2630
2661
  const nextOffset = offset + rawItems.length;
2631
2662
  const hasMore = rawItems.length > 0 && nextOffset < total;
2632
2663
  return {
@@ -2649,7 +2680,7 @@ var ArrangementService = class {
2649
2680
  project(data.next_reminder)
2650
2681
  ]);
2651
2682
  return {
2652
- unreadCount: Math.max(0, Math.trunc(numberValue$22(data.unread_count))),
2683
+ unreadCount: Math.max(0, Math.trunc(numberValue$23(data.unread_count))),
2653
2684
  ...latestUnread === void 0 ? {} : { latestUnread },
2654
2685
  ...latestEvent === void 0 ? {} : { latestEvent },
2655
2686
  ...nextReminder === void 0 ? {} : { nextReminder }
@@ -2753,7 +2784,7 @@ var ArrangementService = class {
2753
2784
  }, session, signal);
2754
2785
  return {
2755
2786
  outcome: "confirmed",
2756
- updatedCount: Math.max(0, Math.trunc(numberValue$22(data.updated_count)))
2787
+ updatedCount: Math.max(0, Math.trunc(numberValue$23(data.updated_count)))
2757
2788
  };
2758
2789
  } catch (error) {
2759
2790
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -2771,7 +2802,7 @@ var ArrangementService = class {
2771
2802
  }, session, signal);
2772
2803
  return {
2773
2804
  outcome: "confirmed",
2774
- updatedCount: Math.max(0, Math.trunc(numberValue$22(data.updated_count)))
2805
+ updatedCount: Math.max(0, Math.trunc(numberValue$23(data.updated_count)))
2775
2806
  };
2776
2807
  } catch (error) {
2777
2808
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -2786,7 +2817,7 @@ var ArrangementService = class {
2786
2817
  const data = await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/reminders/clear", {}, session, signal);
2787
2818
  return {
2788
2819
  outcome: "confirmed",
2789
- updatedCount: Math.max(0, Math.trunc(numberValue$22(data.updated_count)))
2820
+ updatedCount: Math.max(0, Math.trunc(numberValue$23(data.updated_count)))
2790
2821
  };
2791
2822
  } catch (error) {
2792
2823
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -2819,7 +2850,7 @@ var ArrangementService = class {
2819
2850
  }
2820
2851
  async reconcileArrangementDelete(arrangementRef, arrangementUid, session) {
2821
2852
  try {
2822
- const visible = listValue$23((await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/list", {
2853
+ const visible = listValue$24((await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/list", {
2823
2854
  status: -1,
2824
2855
  uids: [arrangementUid],
2825
2856
  limit: 1,
@@ -2850,17 +2881,17 @@ var ArrangementService = class {
2850
2881
  const item = objectValue$1(raw);
2851
2882
  const arrangementUid = stringValue$1(item.uid).trim();
2852
2883
  if (arrangementUid === "") throw new ArkmePluginError("arrangement-contract-invalid", "安排响应缺少业务身份", true, 502);
2853
- const dueAtMillis = Math.trunc(numberValue$22(item.due_at));
2854
- const remindAtMillis = Math.trunc(numberValue$22(item.remind_at));
2884
+ const dueAtMillis = Math.trunc(numberValue$23(item.due_at));
2885
+ const remindAtMillis = Math.trunc(numberValue$23(item.remind_at));
2855
2886
  return {
2856
2887
  arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
2857
2888
  title: stringValue$1(item.title),
2858
2889
  description: stringValue$1(item.description),
2859
2890
  status: arrangementStatus(item.status),
2860
- reminderEnabled: booleanValue$12(item.reminder_enabled),
2891
+ reminderEnabled: booleanValue$13(item.reminder_enabled),
2861
2892
  reminderState: stringValue$1(item.reminder_state),
2862
- createdAtMillis: Math.trunc(numberValue$22(item.create_at)),
2863
- updatedAtMillis: Math.trunc(numberValue$22(item.update_at)),
2893
+ createdAtMillis: Math.trunc(numberValue$23(item.create_at)),
2894
+ updatedAtMillis: Math.trunc(numberValue$23(item.update_at)),
2864
2895
  ...dueAtMillis > 0 ? { dueAtMillis } : {},
2865
2896
  ...remindAtMillis > 0 ? { remindAtMillis } : {}
2866
2897
  };
@@ -2881,20 +2912,20 @@ var ArrangementService = class {
2881
2912
  const eventUid = stringValue$1(item.uid).trim();
2882
2913
  const arrangementUid = stringValue$1(item.arrangement_uid).trim();
2883
2914
  if (eventUid === "" || arrangementUid === "") throw new ArkmePluginError("arrangement-reminder-contract-invalid", "安排提醒响应缺少有效身份", true, 502);
2884
- const dueAtMillis = Math.trunc(numberValue$22(item.due_at));
2885
- const remindAtMillis = Math.trunc(numberValue$22(item.remind_at));
2886
- const readAtMillis = Math.trunc(numberValue$22(item.read_at));
2915
+ const dueAtMillis = Math.trunc(numberValue$23(item.due_at));
2916
+ const remindAtMillis = Math.trunc(numberValue$23(item.remind_at));
2917
+ const readAtMillis = Math.trunc(numberValue$23(item.read_at));
2887
2918
  return {
2888
2919
  eventRef: await this.arrangementReminderRef(viewerUserId, eventUid),
2889
2920
  arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
2890
2921
  title: stringValue$1(item.title),
2891
2922
  description: stringValue$1(item.description),
2892
2923
  eventKind: stringValue$1(item.event_kind),
2893
- eventAtMillis: Math.trunc(numberValue$22(item.event_at)),
2924
+ eventAtMillis: Math.trunc(numberValue$23(item.event_at)),
2894
2925
  read: readAtMillis > 0,
2895
2926
  reminderState: stringValue$1(item.reminder_state),
2896
- createdAtMillis: Math.trunc(numberValue$22(item.create_at)),
2897
- updatedAtMillis: Math.trunc(numberValue$22(item.update_at)),
2927
+ createdAtMillis: Math.trunc(numberValue$23(item.create_at)),
2928
+ updatedAtMillis: Math.trunc(numberValue$23(item.update_at)),
2898
2929
  ...dueAtMillis > 0 ? { dueAtMillis } : {},
2899
2930
  ...remindAtMillis > 0 ? { remindAtMillis } : {},
2900
2931
  ...readAtMillis > 0 ? { readAtMillis } : {}
@@ -2951,7 +2982,7 @@ var ArrangementService = class {
2951
2982
  const ARKME_PHONE_BIND_SUCCESS = 1;
2952
2983
  const ARKME_PHONE_BIND_REPEAT = 2;
2953
2984
  const ARKME_PHONE_BIND_CODE_ERR = 3;
2954
- function numberValue$21(value) {
2985
+ function numberValue$22(value) {
2955
2986
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2956
2987
  }
2957
2988
  function jiwoScanLoginAvailable(config) {
@@ -3036,7 +3067,7 @@ var AuthService = class {
3036
3067
  const sceneStr = stringValue$1(data.scene_str).trim();
3037
3068
  const pollToken = stringValue$1(data.poll_token).trim();
3038
3069
  const qrContent = stringValue$1(data.url).trim();
3039
- const expireSeconds = Math.max(30, numberValue$21(data.expire_seconds) || 300);
3070
+ const expireSeconds = Math.max(30, numberValue$22(data.expire_seconds) || 300);
3040
3071
  if (qrContent === "" && sceneStr !== "") throw new ArkmePluginError("wechat-qr-unavailable", "测试环境当前未返回可用微信二维码,请使用手机号登录", true, 503);
3041
3072
  if (sceneStr === "" || pollToken === "" || qrContent === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录二维码响应不完整", true, 502);
3042
3073
  const attemptId = crypto.randomUUID();
@@ -3075,7 +3106,7 @@ var AuthService = class {
3075
3106
  ref: 0,
3076
3107
  keep_cancel: true
3077
3108
  }, void 0, [200]);
3078
- const userId = numberValue$21(data.user_id);
3109
+ const userId = numberValue$22(data.user_id);
3079
3110
  if (userId <= 0) return {
3080
3111
  status: "pending",
3081
3112
  environment: this.runtime.config.environment,
@@ -3105,7 +3136,7 @@ var AuthService = class {
3105
3136
  }, void 0, [200]);
3106
3137
  const ticket = stringValue$1(data.ticket).trim();
3107
3138
  const pollSecret = stringValue$1(data.poll_secret).trim();
3108
- const expiresAtMillis = numberValue$21(data.expires_at);
3139
+ const expiresAtMillis = numberValue$22(data.expires_at);
3109
3140
  if (!/^[A-Za-z0-9_-]{43}$/.test(ticket) || !/^[A-Za-z0-9_-]{43}$/.test(pollSecret) || expiresAtMillis <= Date.now()) throw new ArkmePluginError("jiwo-login-contract-invalid", "即我登录二维码响应不完整", true, 502);
3110
3141
  if (attemptGeneration !== this.jiwoAttemptGeneration) {
3111
3142
  await this.cancelJiwoCredentials(ticket, pollSecret);
@@ -3155,7 +3186,7 @@ var AuthService = class {
3155
3186
  };
3156
3187
  }
3157
3188
  if (status !== "authenticated") throw new ArkmePluginError("jiwo-login-contract-invalid", "即我扫码登录状态无效", true, 502);
3158
- const userId = numberValue$21(data.user_id);
3189
+ const userId = numberValue$22(data.user_id);
3159
3190
  const accessToken = stringValue$1(data.access_token);
3160
3191
  const refreshToken = stringValue$1(data.refresh_token);
3161
3192
  if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录成功响应缺少凭据", false, 502);
@@ -3224,7 +3255,7 @@ var AuthService = class {
3224
3255
  const normalizedCaptcha = this.normalizedCaptcha(captcha);
3225
3256
  const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
3226
3257
  if (session !== void 0) {
3227
- if (numberValue$21((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/bind-phone-send-code", {
3258
+ if (numberValue$22((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/bind-phone-send-code", {
3228
3259
  phone: normalizedPhone,
3229
3260
  pre: "86",
3230
3261
  is_test: this.runtime.config.environment === "test",
@@ -3246,7 +3277,7 @@ var AuthService = class {
3246
3277
  if (!/^[0-9]{6}$/.test(normalizedCode)) throw new ArkmePluginError("phone-code-invalid", "请输入有效的短信验证码", false);
3247
3278
  const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
3248
3279
  if (session !== void 0) {
3249
- const result = numberValue$21((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/verify-bind-phone", {
3280
+ const result = numberValue$22((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/verify-bind-phone", {
3250
3281
  phone: normalizedPhone,
3251
3282
  pre: "86",
3252
3283
  code: normalizedCode,
@@ -3267,7 +3298,7 @@ var AuthService = class {
3267
3298
  keep_cancel: true
3268
3299
  }, void 0, [200]);
3269
3300
  if (data.ok === false) throw new ArkmePluginError("phone-code-rejected", "手机号或验证码错误", false, 401);
3270
- const userId = numberValue$21(data.user_id);
3301
+ const userId = numberValue$22(data.user_id);
3271
3302
  const accessToken = stringValue$1(data.access_token);
3272
3303
  const refreshToken = stringValue$1(data.refresh_token);
3273
3304
  if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme手机号登录响应不完整", false, 502);
@@ -3417,7 +3448,7 @@ var ArkmePrivacyVisibilityService = class {
3417
3448
  };
3418
3449
  //#endregion
3419
3450
  //#region src/mention-metadata.ts
3420
- function listValue$22(value) {
3451
+ function listValue$23(value) {
3421
3452
  return Array.isArray(value) ? value : [];
3422
3453
  }
3423
3454
  function integerLikeValue$3(value) {
@@ -3438,7 +3469,7 @@ function arkmeMentionMetadataFromRecord(record, payload) {
3438
3469
  function arkmeMentionMetadataMentionsViewer(record, payload, viewerUserId) {
3439
3470
  if (viewerUserId <= 0) return false;
3440
3471
  const metadata = arkmeMentionMetadataFromRecord(record, payload);
3441
- for (const rawMention of listValue$22(metadata.human_mentions ?? metadata.humanMentions)) {
3472
+ for (const rawMention of listValue$23(metadata.human_mentions ?? metadata.humanMentions)) {
3442
3473
  const mention = objectValue$1(rawMention);
3443
3474
  const mentionedUserId = integerLikeValue$3(mention.user_id ?? mention.userId);
3444
3475
  if (mentionedUserId === 0 || mentionedUserId === viewerUserId) return true;
@@ -3706,7 +3737,7 @@ const GROUP_AVATAR_NEGATIVE_CACHE_TTL_MS = 6e4;
3706
3737
  const TOPIC_DISSOLVE_RECORD_CONCURRENCY = 6;
3707
3738
  const PRIVATE_REMARK_PAGE_LIMIT = 50;
3708
3739
  const PRIVATE_REMARK_MAX_PAGES = 20;
3709
- function numberValue$20(value) {
3740
+ function numberValue$21(value) {
3710
3741
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
3711
3742
  }
3712
3743
  function integerIdentifierValue(value) {
@@ -3715,10 +3746,10 @@ function integerIdentifierValue(value) {
3715
3746
  const parsed = Number(value);
3716
3747
  return Number.isSafeInteger(parsed) ? parsed : 0;
3717
3748
  }
3718
- function booleanValue$11(value) {
3749
+ function booleanValue$12(value) {
3719
3750
  return value === true;
3720
3751
  }
3721
- function listValue$21(value) {
3752
+ function listValue$22(value) {
3722
3753
  return Array.isArray(value) ? value : [];
3723
3754
  }
3724
3755
  function integerLikeValue$2(value) {
@@ -3758,7 +3789,7 @@ function arkmeChatSidebarTargetFromBundle(bundle, fallbackSubjectUid = "") {
3758
3789
  if (subjectUid === "") return void 0;
3759
3790
  return {
3760
3791
  subjectUid,
3761
- hiddenAnchorTimestamp: Math.max(0, numberValue$20(bundle.sort_active_at ?? bundle.last_active_at ?? chatSession.last_active_at))
3792
+ hiddenAnchorTimestamp: Math.max(0, numberValue$21(bundle.sort_active_at ?? bundle.last_active_at ?? chatSession.last_active_at))
3762
3793
  };
3763
3794
  }
3764
3795
  function backendBooleanValue(value) {
@@ -3778,7 +3809,7 @@ function chatUnreadMentionState(unread) {
3778
3809
  if (rawCount === void 0 || rawCount === null) return void 0;
3779
3810
  return integerLikeValue$2(rawCount) > 0;
3780
3811
  }
3781
- function encodeOpaqueJson$6(value) {
3812
+ function encodeOpaqueJson$7(value) {
3782
3813
  return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
3783
3814
  }
3784
3815
  function decodeOpaqueJson$6(value) {
@@ -3790,7 +3821,7 @@ function isSourceKind(value) {
3790
3821
  function chatMessageDnd$1(value) {
3791
3822
  if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
3792
3823
  const policy = value;
3793
- return numberValue$20(policy.mute_state) === 2 || numberValue$20(policy.notify_state) === 2;
3824
+ return numberValue$21(policy.mute_state) === 2 || numberValue$21(policy.notify_state) === 2;
3794
3825
  }
3795
3826
  function attachmentPreviewKind(item) {
3796
3827
  const fileName = stringValue$1(item.file_name ?? item.fileName).trim();
@@ -3809,13 +3840,13 @@ function arkmeChatConversationPreview(raw) {
3809
3840
  const nested = stringValue$1(content.text_content ?? content.title ?? content.summary).trim();
3810
3841
  if (nested !== "") return nested.slice(0, 300);
3811
3842
  if (objectValue$1(content.voice).duration !== void 0) return "[语音]";
3812
- const displayItems = listValue$21(raw.media_display_items ?? raw.mediaDisplayItems).map(objectValue$1);
3843
+ const displayItems = listValue$22(raw.media_display_items ?? raw.mediaDisplayItems).map(objectValue$1);
3813
3844
  const displayByAsset = /* @__PURE__ */ new Map();
3814
3845
  for (const item of displayItems) {
3815
3846
  const fileAssetUid = stringValue$1(item.file_asset_uid ?? item.fileAssetUid).trim();
3816
3847
  if (fileAssetUid !== "") displayByAsset.set(fileAssetUid, item);
3817
3848
  }
3818
- const mediaRefs = listValue$21(content.media_refs ?? content.mediaRefs).map(objectValue$1);
3849
+ const mediaRefs = listValue$22(content.media_refs ?? content.mediaRefs).map(objectValue$1);
3819
3850
  const firstAttachment = (mediaRefs.length > 0 ? mediaRefs.map((ref) => ({
3820
3851
  ...displayByAsset.get(stringValue$1(ref.file_asset_uid ?? ref.fileAssetUid).trim()) ?? {},
3821
3852
  ...ref
@@ -3967,11 +3998,11 @@ var SourceService = class {
3967
3998
  lane: "background-read",
3968
3999
  key: `world-author-labels:${pageCursor === void 0 ? "first" : String(page)}`
3969
4000
  });
3970
- for (const raw of listValue$21(data.items)) {
4001
+ for (const raw of listValue$22(data.items)) {
3971
4002
  const bundle = objectValue$1(raw);
3972
- const sessionKind = numberValue$20(objectValue$1(bundle.session).session_kind);
4003
+ const sessionKind = numberValue$21(objectValue$1(bundle.session).session_kind);
3973
4004
  if (sessionKind !== 1 && sessionKind !== 3) continue;
3974
- const targetUserId = numberValue$20(objectValue$1(bundle.private_counterpart).user_id);
4005
+ const targetUserId = numberValue$21(objectValue$1(bundle.private_counterpart).user_id);
3975
4006
  if (!remaining.has(targetUserId)) continue;
3976
4007
  const supplement = objectValue$1(bundle.private_supplement);
3977
4008
  const counterpart = objectValue$1(bundle.private_counterpart);
@@ -4005,7 +4036,7 @@ var SourceService = class {
4005
4036
  key: `private-remarks:${String(offset)}`,
4006
4037
  failureCooldownMs: 2e3
4007
4038
  });
4008
- const rawItems = listValue$21(data.items);
4039
+ const rawItems = listValue$22(data.items);
4009
4040
  for (const value of rawItems) {
4010
4041
  const raw = objectValue$1(value);
4011
4042
  const targetUserId = integerIdentifierValue(raw.user_id);
@@ -4030,9 +4061,9 @@ var SourceService = class {
4030
4061
  key: `private-remarks:direct:${pageCursor === void 0 ? "first" : String(page)}`,
4031
4062
  failureCooldownMs: 2e3
4032
4063
  });
4033
- for (const value of listValue$21(data.items)) {
4064
+ for (const value of listValue$22(data.items)) {
4034
4065
  const bundle = objectValue$1(value);
4035
- const sessionKind = numberValue$20(objectValue$1(bundle.session).session_kind);
4066
+ const sessionKind = numberValue$21(objectValue$1(bundle.session).session_kind);
4036
4067
  if (sessionKind !== 1 && sessionKind !== 3) continue;
4037
4068
  const targetUserId = integerIdentifierValue(objectValue$1(bundle.private_counterpart).user_id);
4038
4069
  if (!remaining.has(targetUserId)) continue;
@@ -4075,10 +4106,10 @@ var SourceService = class {
4075
4106
  extra: { source: "dsh-arkme" }
4076
4107
  }, session);
4077
4108
  const topicUid = stringValue$1(created.topic_uid).trim();
4078
- if (topicUid === "" || numberValue$20(created.status) !== 1) throw new ArkmePluginError("topic-create-contract-invalid", "主题创建响应不完整", true, 502);
4109
+ if (topicUid === "" || numberValue$21(created.status) !== 1) throw new ArkmePluginError("topic-create-contract-invalid", "主题创建响应不完整", true, 502);
4079
4110
  const sourceRef = await this.sealSourceRef(session.userId, "topic", topicUid, title);
4080
4111
  if (parentTopicUid !== void 0) try {
4081
- if (numberValue$20(objectValue$1((await this.runtime.authenticatedPost("/api/v1/topics/hierarchy/bind", {
4112
+ if (numberValue$21(objectValue$1((await this.runtime.authenticatedPost("/api/v1/topics/hierarchy/bind", {
4082
4113
  parent_topic_uid: parentTopicUid,
4083
4114
  child_topic_uid: topicUid
4084
4115
  }, session)).relation).status) !== 1) throw new ArkmePluginError("topic-hierarchy-bind-contract-invalid", "子主题层级响应不完整", true, 502);
@@ -4092,7 +4123,7 @@ var SourceService = class {
4092
4123
  status: 2,
4093
4124
  extra: { source: "dsh-arkme" }
4094
4125
  }, session);
4095
- if (stringValue$1(rolledBack.topic_uid).trim() !== topicUid || !booleanValue$11(rolledBack.updated)) throw new ArkmePluginError("topic-rollback-contract-invalid", "子主题清理响应不完整", true, 502);
4126
+ if (stringValue$1(rolledBack.topic_uid).trim() !== topicUid || !booleanValue$12(rolledBack.updated)) throw new ArkmePluginError("topic-rollback-contract-invalid", "子主题清理响应不完整", true, 502);
4096
4127
  } catch {
4097
4128
  return {
4098
4129
  source: {
@@ -4136,7 +4167,7 @@ var SourceService = class {
4136
4167
  status: 1,
4137
4168
  extra: { source: "dsh-arkme" }
4138
4169
  }, session);
4139
- if (stringValue$1(updated.topic_uid).trim() !== topic.ownerRef || !booleanValue$11(updated.updated)) throw new ArkmePluginError("topic-rename-contract-invalid", "主题重命名响应不完整,请重试", true, 502);
4170
+ if (stringValue$1(updated.topic_uid).trim() !== topic.ownerRef || !booleanValue$12(updated.updated)) throw new ArkmePluginError("topic-rename-contract-invalid", "主题重命名响应不完整,请重试", true, 502);
4140
4171
  this.invalidateSourceListCache(session.userId, "send_to_self");
4141
4172
  return {
4142
4173
  sourceRef: await this.sealSourceRef(session.userId, "topic", topic.ownerRef, title),
@@ -4214,7 +4245,7 @@ var SourceService = class {
4214
4245
  status: 2,
4215
4246
  extra: { source: "dsh-arkme" }
4216
4247
  }, session);
4217
- if (stringValue$1(dissolved.topic_uid).trim() !== topic.ownerRef || !booleanValue$11(dissolved.updated)) throw new ArkmePluginError("topic-dissolve-contract-invalid", "主题解散响应不完整,请重试", true, 502);
4248
+ if (stringValue$1(dissolved.topic_uid).trim() !== topic.ownerRef || !booleanValue$12(dissolved.updated)) throw new ArkmePluginError("topic-dissolve-contract-invalid", "主题解散响应不完整,请重试", true, 502);
4218
4249
  } catch (cause) {
4219
4250
  await this.rollbackDissolvedTopicChildren(movedChildren, topic.ownerRef, parent?.ownerRef, session);
4220
4251
  await this.rollbackDissolvedTopicRecords(topic.ownerRef, parent?.ownerRef, movedRecordUids, session);
@@ -4247,14 +4278,14 @@ var SourceService = class {
4247
4278
  ...cursorSendAt === void 0 ? {} : { cursor_send_at: cursorSendAt },
4248
4279
  ...cursorRecordUid === void 0 ? {} : { cursor_record_uid: cursorRecordUid }
4249
4280
  }, session);
4250
- for (const raw of listValue$21(data.records)) {
4281
+ for (const raw of listValue$22(data.records)) {
4251
4282
  const item = objectValue$1(raw);
4252
4283
  const uid = stringValue$1(item.record_uid ?? objectValue$1(item.record_core).record_uid).trim();
4253
4284
  if (uid !== "") recordUids.add(uid);
4254
4285
  }
4255
4286
  onProgress?.(recordUids.size);
4256
4287
  if (data.has_more !== true) return [...recordUids];
4257
- const nextSendAt = numberValue$20(data.next_cursor_send_at);
4288
+ const nextSendAt = numberValue$21(data.next_cursor_send_at);
4258
4289
  const nextRecordUid = stringValue$1(data.next_cursor_record_uid).trim();
4259
4290
  const cursorKey = `${String(nextSendAt)}:${nextRecordUid}`;
4260
4291
  if (nextSendAt <= 0 || nextRecordUid === "" || seenCursors.has(cursorKey)) throw new ArkmePluginError("topic-dissolve-record-page-invalid", "主题快记加载不完整,未解散主题,请刷新后重试", true, 502);
@@ -4337,7 +4368,7 @@ var SourceService = class {
4337
4368
  parent_topic_uid: nextParentTopicUid ?? "",
4338
4369
  insert_before_topic_uid: ""
4339
4370
  }, session);
4340
- return stringValue$1(moved.topic_uid).trim() === topicUid && stringValue$1(moved.parent_topic_uid).trim() === (nextParentTopicUid ?? "") && numberValue$20(moved.sibling_order) > 0;
4371
+ return stringValue$1(moved.topic_uid).trim() === topicUid && stringValue$1(moved.parent_topic_uid).trim() === (nextParentTopicUid ?? "") && numberValue$21(moved.sibling_order) > 0;
4341
4372
  }
4342
4373
  async rollbackDissolvedTopicChildren(children, topicUid, parentTopicUid, session) {
4343
4374
  for (const child of [...children].reverse()) await this.moveTopicHierarchyForDissolve(child.topic.ownerRef, parentTopicUid ?? "", topicUid, session).catch(() => false);
@@ -4366,7 +4397,7 @@ var SourceService = class {
4366
4397
  }, session);
4367
4398
  const movedTopicUid = stringValue$1(moved.topic_uid).trim();
4368
4399
  const movedParentUid = stringValue$1(moved.parent_topic_uid).trim();
4369
- const siblingOrder = numberValue$20(moved.sibling_order);
4400
+ const siblingOrder = numberValue$21(moved.sibling_order);
4370
4401
  if (movedTopicUid !== topic.ownerRef || movedParentUid !== (nextParentUid ?? "") || siblingOrder <= 0) throw new ArkmePluginError("topic-hierarchy-move-contract-invalid", "主题移动响应不完整,请刷新后重试", true, 502);
4371
4402
  this.sourceListCache.clear();
4372
4403
  return {
@@ -4411,17 +4442,17 @@ var SourceService = class {
4411
4442
  if (source.kind !== "private_chat" && source.kind !== "group_chat") throw new ArkmePluginError("chat-directory-policy-invalid", "仅支持更新私聊或群聊的会话列表状态", false);
4412
4443
  const sidebarTarget = await this.resolveChatSidebarTarget(source, session, options.signal);
4413
4444
  const current = await this.runtime.authenticatedChatPost("/api/v1/chats/policy/get", { chat_session_uid: source.ownerRef }, session, options.signal);
4414
- const pinned = options.pinned ?? numberValue$20(current.pin_state) === 2;
4415
- const hidden = options.hidden ?? numberValue$20(current.show_in_home_state) === 2;
4445
+ const pinned = options.pinned ?? numberValue$21(current.pin_state) === 2;
4446
+ const hidden = options.hidden ?? numberValue$21(current.show_in_home_state) === 2;
4416
4447
  const updatedAt = Date.now();
4417
4448
  const writes = [this.runtime.authenticatedChatPost("/api/v1/chats/policy/update", {
4418
4449
  chat_session_uid: source.ownerRef,
4419
4450
  show_in_home_state: hidden ? 2 : 1,
4420
- privacy_state: numberValue$20(current.privacy_state) || 1,
4421
- mute_state: numberValue$20(current.mute_state) || 1,
4451
+ privacy_state: numberValue$21(current.privacy_state) || 1,
4452
+ mute_state: numberValue$21(current.mute_state) || 1,
4422
4453
  pin_state: pinned ? 2 : 1,
4423
- notify_state: numberValue$20(current.notify_state) || 1,
4424
- status: numberValue$20(current.status) || 1,
4454
+ notify_state: numberValue$21(current.notify_state) || 1,
4455
+ status: numberValue$21(current.status) || 1,
4425
4456
  update_at: updatedAt
4426
4457
  }, session, options.signal)];
4427
4458
  if (options.hidden !== void 0) writes.push(this.runtime.authenticatedSubjectPost("/api/v1/subject/batch-set-sidebar-hidden-status", { items: [{
@@ -4480,7 +4511,7 @@ var SourceService = class {
4480
4511
  key: "directory:groups:count",
4481
4512
  failureCooldownMs: 2e3
4482
4513
  });
4483
- return Math.max(0, numberValue$20(data.total ?? data.total_count));
4514
+ return Math.max(0, numberValue$21(data.total ?? data.total_count));
4484
4515
  }
4485
4516
  async listSourcesUncached(session, directory, options, limit, sessionKind) {
4486
4517
  if (directory === "send_to_self") {
@@ -4498,7 +4529,7 @@ var SourceService = class {
4498
4529
  options.signal?.throwIfAborted();
4499
4530
  const cached = summaryResult.status === "rejected" || latestRecordsResult.status === "rejected" ? await this.runtime.stateStore.cachedSnapshot(session.userId).catch(() => void 0) : void 0;
4500
4531
  const defaultRecordCount = summaryResult.status === "fulfilled" ? summaryResult.value.recordCount : cached?.summary?.recordCount;
4501
- const defaultLatestRecord = latestRecordsResult.status === "fulfilled" ? listValue$21(latestRecordsResult.value.items).map((raw) => this.recordReader.isDSHAgentInput?.(raw) === true || this.recordReader.isPrivacyLocked?.(raw) === true || arkmePrivacyLockedRecord(raw) || lockedRecordUids.has(stringValue$1(objectValue$1(raw).record_uid ?? objectValue$1(objectValue$1(raw).record_core).record_uid).trim()) ? void 0 : this.recordReader.recordItem(raw)).find((item) => item !== void 0) : cached?.items.reduce((latest, item) => item.creationSource === 3 ? latest : latest === void 0 || item.sendAtMillis > latest.sendAtMillis ? item : latest, void 0);
4532
+ const defaultLatestRecord = latestRecordsResult.status === "fulfilled" ? listValue$22(latestRecordsResult.value.items).map((raw) => this.recordReader.isDSHAgentInput?.(raw) === true || this.recordReader.isPrivacyLocked?.(raw) === true || arkmePrivacyLockedRecord(raw) || lockedRecordUids.has(stringValue$1(objectValue$1(raw).record_uid ?? objectValue$1(objectValue$1(raw).record_core).record_uid).trim()) ? void 0 : this.recordReader.recordItem(raw)).find((item) => item !== void 0) : cached?.items.reduce((latest, item) => item.creationSource === 3 ? latest : latest === void 0 || item.sendAtMillis > latest.sendAtMillis ? item : latest, void 0);
4502
4533
  const defaultLatestPreview = defaultLatestRecord === void 0 ? "" : defaultLatestRecord.textContent.trim() || defaultLatestRecord.title.trim();
4503
4534
  const defaultCategory = {
4504
4535
  sourceRef: await this.sealSourceRef(session.userId, "default_category", "uncategorized", "未分类"),
@@ -4514,24 +4545,24 @@ var SourceService = class {
4514
4545
  const parentTopicUidByChild = /* @__PURE__ */ new Map();
4515
4546
  const siblingOrderByChild = /* @__PURE__ */ new Map();
4516
4547
  const childTopicUidsByParent = /* @__PURE__ */ new Map();
4517
- for (const raw of listValue$21(hierarchyData?.relations)) {
4548
+ for (const raw of listValue$22(hierarchyData?.relations)) {
4518
4549
  const relation = objectValue$1(raw);
4519
- if (numberValue$20(relation.rel_kind) !== 1 || numberValue$20(relation.status) !== 1) continue;
4550
+ if (numberValue$21(relation.rel_kind) !== 1 || numberValue$21(relation.status) !== 1) continue;
4520
4551
  const parentTopicUid = stringValue$1(relation.parent_topic_uid).trim();
4521
4552
  const childTopicUid = stringValue$1(relation.child_topic_uid).trim();
4522
4553
  if (parentTopicUid === "" || childTopicUid === "" || parentTopicUid === childTopicUid) continue;
4523
4554
  parentTopicUidByChild.set(childTopicUid, parentTopicUid);
4524
- siblingOrderByChild.set(childTopicUid, numberValue$20(relation.sibling_order));
4555
+ siblingOrderByChild.set(childTopicUid, numberValue$21(relation.sibling_order));
4525
4556
  const children = childTopicUidsByParent.get(parentTopicUid) ?? /* @__PURE__ */ new Set();
4526
4557
  children.add(childTopicUid);
4527
4558
  childTopicUidsByParent.set(parentTopicUid, children);
4528
4559
  }
4529
- for (const raw of listValue$21(data.items)) {
4560
+ for (const raw of listValue$22(data.items)) {
4530
4561
  const item = objectValue$1(raw);
4531
4562
  if (arkmePrivacyLockedTopic(item)) continue;
4532
4563
  const core = objectValue$1(item.topic_core);
4533
4564
  const status = core.status ?? item.status;
4534
- if (status !== void 0 && numberValue$20(status) !== 1) continue;
4565
+ if (status !== void 0 && numberValue$21(status) !== 1) continue;
4535
4566
  const summary = objectValue$1(item.summary);
4536
4567
  const latest = objectValue$1(item.latest_record_core);
4537
4568
  const parent = objectValue$1(core.parent_topic_core ?? core.parent_topic ?? item.parent_topic_core ?? item.parent_topic);
@@ -4543,12 +4574,12 @@ var SourceService = class {
4543
4574
  topicDescriptors.push({
4544
4575
  topicUid,
4545
4576
  ...parentTopicUid === "" || parentTopicUid === topicUid ? {} : { parentTopicUid },
4546
- siblingOrder: numberValue$20(siblingOrderByChild.get(topicUid) ?? core.sibling_order ?? item.sibling_order),
4577
+ siblingOrder: numberValue$21(siblingOrderByChild.get(topicUid) ?? core.sibling_order ?? item.sibling_order),
4547
4578
  title,
4548
4579
  latestPreview: arkmeChatConversationPreview(latest),
4549
- latestMessageAtMillis: numberValue$20(latest.send_at ?? summary.latest_send_at),
4550
- activeAtMillis: numberValue$20(latest.send_at ?? summary.latest_send_at ?? core.update_at),
4551
- recordCount: numberValue$20(summary.record_count)
4580
+ latestMessageAtMillis: numberValue$21(latest.send_at ?? summary.latest_send_at),
4581
+ activeAtMillis: numberValue$21(latest.send_at ?? summary.latest_send_at ?? core.update_at),
4582
+ recordCount: numberValue$21(summary.record_count)
4552
4583
  });
4553
4584
  }
4554
4585
  const sourceRefByTopicUid = /* @__PURE__ */ new Map();
@@ -4590,12 +4621,12 @@ var SourceService = class {
4590
4621
  unreadCount: 0
4591
4622
  };
4592
4623
  const nextPageCursor = objectValue$1(data.next_page_cursor ?? data.next_cursor);
4593
- const nextOffset = numberValue$20(data.next_offset);
4624
+ const nextOffset = numberValue$21(data.next_offset);
4594
4625
  const preferredPageCursor = Object.keys(nextPageCursor).length > 0 ? nextPageCursor : void 0;
4595
4626
  const preferredOffset = preferredPageCursor === void 0 && nextOffset > 0 ? nextOffset : void 0;
4596
4627
  const hasMore = data.has_more === true && (preferredPageCursor !== void 0 || preferredOffset !== void 0);
4597
4628
  const totalValue = data.total ?? data.total_count;
4598
- const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$20(totalValue));
4629
+ const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$21(totalValue));
4599
4630
  return {
4600
4631
  directory,
4601
4632
  items: [
@@ -4626,7 +4657,7 @@ var SourceService = class {
4626
4657
  const items = [];
4627
4658
  const privateUserIdByIndex = /* @__PURE__ */ new Map();
4628
4659
  const groupSessionUidByIndex = /* @__PURE__ */ new Map();
4629
- for (const raw of listValue$21(data.items)) {
4660
+ for (const raw of listValue$22(data.items)) {
4630
4661
  const bundle = objectValue$1(raw);
4631
4662
  const chatSession = objectValue$1(bundle.session);
4632
4663
  const counterpart = objectValue$1(bundle.private_counterpart);
@@ -4637,14 +4668,14 @@ var SourceService = class {
4637
4668
  const unread = objectValue$1(bundle.unread_snapshot);
4638
4669
  const currentPolicy = objectValue$1(bundle.current_policy);
4639
4670
  const isMuted = chatMessageDnd$1(currentPolicy) ?? false;
4640
- const isPinned = numberValue$20(currentPolicy.pin_state) === 2;
4671
+ const isPinned = numberValue$21(currentPolicy.pin_state) === 2;
4641
4672
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
4642
- const sessionKind = numberValue$20(chatSession.session_kind);
4673
+ const sessionKind = numberValue$21(chatSession.session_kind);
4643
4674
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
4644
4675
  if (uid === "" || kind === void 0) continue;
4645
4676
  const displayName = (kind === "private_chat" ? stringValue$1(supplement.remark ?? supplement.counterpart_name_snapshot ?? counterpart.display_name_snapshot ?? supplement.pending_name ?? counterpart.visible_phone) : stringValue$1(chatSession.title)).trim() || "未命名会话";
4646
4677
  const preview = arkmeChatConversationPreview(latestPayload);
4647
- const unreadCount = Math.max(0, Math.trunc(numberValue$20(unread.unread_count)));
4678
+ const unreadCount = Math.max(0, Math.trunc(numberValue$21(unread.unread_count)));
4648
4679
  const latestRelation = objectValue$1(latestPreview.relation);
4649
4680
  const latestPreviewMentionsViewer = integerLikeValue$2(latestRelation.sender_user_id ?? latestRelation.senderUserId) !== session.userId && arkmeMentionMetadataMentionsViewer(latestRecord, latestPayload, session.userId);
4650
4681
  const backendMentionState = chatUnreadMentionState(unread);
@@ -4660,17 +4691,17 @@ var SourceService = class {
4660
4691
  kind,
4661
4692
  displayName,
4662
4693
  ...preview === "" ? {} : { latestPreview: preview },
4663
- activeAtMillis: numberValue$20(bundle.sort_active_at ?? chatSession.last_active_at),
4694
+ activeAtMillis: numberValue$21(bundle.sort_active_at ?? chatSession.last_active_at),
4664
4695
  unreadCount,
4665
4696
  ...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
4666
4697
  isMuted,
4667
4698
  isPinned,
4668
- ...numberValue$20(unread.session_last_seq ?? chatSession.last_seq) > 0 ? { latestSequence: numberValue$20(unread.session_last_seq ?? chatSession.last_seq) } : {}
4699
+ ...numberValue$21(unread.session_last_seq ?? chatSession.last_seq) > 0 ? { latestSequence: numberValue$21(unread.session_last_seq ?? chatSession.last_seq) } : {}
4669
4700
  };
4670
4701
  const itemIndex = items.push(item) - 1;
4671
4702
  this.chatSourceCache.set(`${String(session.userId)}:${uid}`, item);
4672
4703
  if (kind === "private_chat") {
4673
- const counterpartUserId = numberValue$20(counterpart.user_id);
4704
+ const counterpartUserId = numberValue$21(counterpart.user_id);
4674
4705
  if (Number.isSafeInteger(counterpartUserId) && counterpartUserId > 0) {
4675
4706
  item.peerUserId = counterpartUserId;
4676
4707
  privateUserIdByIndex.set(itemIndex, counterpartUserId);
@@ -4684,7 +4715,7 @@ var SourceService = class {
4684
4715
  }
4685
4716
  const hasMore = data.has_more === true;
4686
4717
  const totalValue = data.total ?? data.total_count;
4687
- const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$20(totalValue));
4718
+ const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$21(totalValue));
4688
4719
  const nextPageCursor = objectValue$1(data.next_page_cursor);
4689
4720
  return {
4690
4721
  directory,
@@ -4740,16 +4771,16 @@ var SourceService = class {
4740
4771
  continue;
4741
4772
  }
4742
4773
  const snapshotsByUid = /* @__PURE__ */ new Map();
4743
- for (const raw of listValue$21(data.items)) {
4774
+ for (const raw of listValue$22(data.items)) {
4744
4775
  const snapshot = objectValue$1(raw);
4745
4776
  const uid = stringValue$1(snapshot.chat_session_uid).trim();
4746
4777
  const index = indexByGroupUid.get(uid);
4747
4778
  if (index === void 0 || !groupUids.includes(uid)) continue;
4748
- const memberIds = listValue$21(snapshot.members).map((member) => numberValue$20(objectValue$1(member).user_id)).filter((userId) => Number.isSafeInteger(userId) && userId > 0).slice(0, 5);
4779
+ const memberIds = listValue$22(snapshot.members).map((member) => numberValue$21(objectValue$1(member).user_id)).filter((userId) => Number.isSafeInteger(userId) && userId > 0).slice(0, 5);
4749
4780
  const projection = {
4750
- memberCount: Math.max(0, Math.trunc(numberValue$20(snapshot.member_count))),
4781
+ memberCount: Math.max(0, Math.trunc(numberValue$21(snapshot.member_count))),
4751
4782
  strategy: stringValue$1(snapshot.strategy).trim(),
4752
- computedAtMillis: numberValue$20(snapshot.computed_at),
4783
+ computedAtMillis: numberValue$21(snapshot.computed_at),
4753
4784
  memberIds
4754
4785
  };
4755
4786
  snapshotsByUid.set(uid, projection);
@@ -4798,7 +4829,7 @@ var SourceService = class {
4798
4829
  async sealSourceRef(userId, kind, ownerRef, displayName, options = {}) {
4799
4830
  const botGroupTarget = kind === "group_chat" ? sanitizeGroupBotBindingTarget(options.botGroupTarget) : void 0;
4800
4831
  const sidebarTarget = kind === "private_chat" || kind === "group_chat" ? options.sidebarTarget : void 0;
4801
- const payload = encodeOpaqueJson$6({
4832
+ const payload = encodeOpaqueJson$7({
4802
4833
  version: 1,
4803
4834
  userId,
4804
4835
  kind,
@@ -4828,7 +4859,7 @@ var SourceService = class {
4828
4859
  const kind = parsed.kind;
4829
4860
  const result = {
4830
4861
  version: 1,
4831
- userId: numberValue$20(parsed.userId),
4862
+ userId: numberValue$21(parsed.userId),
4832
4863
  kind: isSourceKind(kind) ? kind : "default_category",
4833
4864
  ownerRef: stringValue$1(parsed.ownerRef).trim(),
4834
4865
  displayName: isSourceKind(kind) && kind === "default_category" ? "未分类" : stringValue$1(parsed.displayName).trim(),
@@ -4838,7 +4869,7 @@ var SourceService = class {
4838
4869
  })() : {},
4839
4870
  ...kind === "private_chat" || kind === "group_chat" ? (() => {
4840
4871
  const sidebarSubjectUid = stringValue$1(parsed.sidebarSubjectUid).trim();
4841
- const sidebarHiddenAnchorTimestamp = Math.max(0, numberValue$20(parsed.sidebarHiddenAnchorTimestamp));
4872
+ const sidebarHiddenAnchorTimestamp = Math.max(0, numberValue$21(parsed.sidebarHiddenAnchorTimestamp));
4842
4873
  return sidebarSubjectUid === "" ? {} : {
4843
4874
  sidebarSubjectUid,
4844
4875
  sidebarHiddenAnchorTimestamp
@@ -4872,16 +4903,16 @@ var SourceService = class {
4872
4903
  const unread = objectValue$1(bundle.unread_snapshot);
4873
4904
  const currentPolicy = objectValue$1(bundle.current_policy);
4874
4905
  const isMuted = chatMessageDnd$1(currentPolicy) ?? cached?.isMuted ?? false;
4875
- const isPinned = numberValue$20(currentPolicy.pin_state) === 2;
4906
+ const isPinned = numberValue$21(currentPolicy.pin_state) === 2;
4876
4907
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
4877
- const sessionKind = numberValue$20(chatSession.session_kind);
4908
+ const sessionKind = numberValue$21(chatSession.session_kind);
4878
4909
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
4879
4910
  if (uid === "" || kind === void 0) throw new Error("invalid chat display snapshot");
4880
4911
  const displayName = (kind === "private_chat" ? stringValue$1(supplement.remark ?? supplement.counterpart_name_snapshot ?? counterpart.display_name_snapshot ?? supplement.pending_name ?? counterpart.visible_phone) : stringValue$1(chatSession.title)).trim() || cached?.displayName || "未命名会话";
4881
4912
  const latestItem = [...timelineItems].sort((left, right) => (right.sequence ?? 0) - (left.sequence ?? 0))[0];
4882
4913
  const latestPreview = latestItem === void 0 ? cached?.latestPreview : latestItem.forwardRecords === void 0 ? arkmeTimelineConversationPreview(latestItem) : `[转发] ${latestItem.forwardRecords.title}`;
4883
- const latestSequence = Math.max(numberValue$20(unread.session_last_seq ?? chatSession.last_seq), latestItem?.sequence ?? 0, cached?.latestSequence ?? 0);
4884
- const unreadCount = Math.max(0, Math.trunc(numberValue$20(unread.unread_count)));
4914
+ const latestSequence = Math.max(numberValue$21(unread.session_last_seq ?? chatSession.last_seq), latestItem?.sequence ?? 0, cached?.latestSequence ?? 0);
4915
+ const unreadCount = Math.max(0, Math.trunc(numberValue$21(unread.unread_count)));
4885
4916
  const latestMentionsViewer = latestItem?.isMe === false && latestItem.mentionsViewer === true;
4886
4917
  const backendMentionState = chatUnreadMentionState(unread);
4887
4918
  const hasUnreadMention = kind !== "group_chat" ? void 0 : unreadCount <= 0 ? false : backendMentionState === void 0 && !latestMentionsViewer ? cached?.hasUnreadMention : backendMentionState === true || latestMentionsViewer;
@@ -4899,7 +4930,7 @@ var SourceService = class {
4899
4930
  ...cached?.avatarRefs === void 0 ? {} : { avatarRefs: cached.avatarRefs },
4900
4931
  ...cached?.groupAvatar === void 0 ? {} : { groupAvatar: cached.groupAvatar },
4901
4932
  ...latestPreview === void 0 || latestPreview === "" ? {} : { latestPreview },
4902
- activeAtMillis: Math.max(numberValue$20(bundle.sort_active_at ?? chatSession.last_active_at), latestItem?.sendAtMillis ?? 0),
4933
+ activeAtMillis: Math.max(numberValue$21(bundle.sort_active_at ?? chatSession.last_active_at), latestItem?.sendAtMillis ?? 0),
4903
4934
  unreadCount,
4904
4935
  ...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
4905
4936
  isMuted,
@@ -4908,7 +4939,7 @@ var SourceService = class {
4908
4939
  };
4909
4940
  }
4910
4941
  encodeCursor(value) {
4911
- return `arkme-cursor-v1.${encodeOpaqueJson$6(value)}`;
4942
+ return `arkme-cursor-v1.${encodeOpaqueJson$7(value)}`;
4912
4943
  }
4913
4944
  decodeCursor(cursor) {
4914
4945
  const [prefix, payload, ...extra] = cursor.trim().split(".");
@@ -4931,7 +4962,7 @@ var SourceService = class {
4931
4962
  const value = this.decodeCursor(cursor);
4932
4963
  if (value.directory !== "send_to_self") throw new ArkmePluginError("source-cursor-invalid", "发给自己的主题分页游标无效", false);
4933
4964
  const pageCursor = objectValue$1(value.pageCursor);
4934
- const offset = numberValue$20(value.offset);
4965
+ const offset = numberValue$21(value.offset);
4935
4966
  if (Object.keys(pageCursor).length === 0 && offset <= 0) throw new ArkmePluginError("source-cursor-invalid", "发给自己的主题分页游标无效", false);
4936
4967
  return {
4937
4968
  ...Object.keys(pageCursor).length === 0 ? {} : { pageCursor },
@@ -4943,68 +4974,27 @@ var SourceService = class {
4943
4974
  //#region src/services/bot-service.ts
4944
4975
  const BOT_CONVERSATION_OWNER = {
4945
4976
  subject: "jotmo-subject",
4946
- chat: "jotmo-chat",
4947
- unavailable: "unavailable"
4977
+ chat: "jotmo-chat"
4948
4978
  };
4949
4979
  const BOT_REF_TTL_MILLIS = 30 * 6e4;
4950
4980
  const BOT_REF_CAP = 2e3;
4951
4981
  const BOT_REF_PATTERN = /^arkme-bot-v2\.[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4952
4982
  const BOT_IMAGE_REF_PATTERN = /^arkme-bot-image-v1\.[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4953
- function numberValue$19(value) {
4983
+ function numberValue$20(value) {
4954
4984
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
4955
4985
  }
4956
- function booleanValue$10(value) {
4986
+ function booleanValue$11(value) {
4957
4987
  return value === true;
4958
4988
  }
4959
- function listValue$20(value) {
4989
+ function listValue$21(value) {
4960
4990
  return Array.isArray(value) ? value : [];
4961
4991
  }
4962
4992
  function arkmeNormalizeBotProvider(value) {
4963
4993
  const normalized = stringValue$1(value).trim().toLowerCase();
4964
4994
  if (normalized === "openclaw" || normalized === "webhook") return normalized;
4965
4995
  }
4966
- const BOT_CHAT_SESSION_UID_KEYS = [
4967
- "chat_session_uid",
4968
- "chatSessionUid",
4969
- "direct_chat_session_uid",
4970
- "directChatSessionUid",
4971
- "private_chat_session_uid",
4972
- "privateChatSessionUid"
4973
- ];
4974
- /** Read the standard-session aliases accepted by the Flutter desktop client. */
4975
- function botChatSessionUid(data) {
4976
- const read = (value, allowUid = false) => {
4977
- for (const key of BOT_CHAT_SESSION_UID_KEYS) {
4978
- const candidate = stringValue$1(value[key]).trim();
4979
- if (candidate !== "" && candidate !== "null") return candidate;
4980
- }
4981
- const uid = allowUid ? stringValue$1(value.uid).trim() : "";
4982
- return uid !== "" && uid !== "null" ? uid : void 0;
4983
- };
4984
- const direct = read(data);
4985
- if (direct !== void 0) return direct;
4986
- for (const key of [
4987
- "chat_session",
4988
- "direct_chat_session",
4989
- "private_chat_session"
4990
- ]) {
4991
- const nested = read(objectValue$1(data[key]), true);
4992
- if (nested !== void 0) return nested;
4993
- }
4994
- }
4995
- /**
4996
- * Flutter accepts the legacy `session_id` returned by the Bot endpoint only
4997
- * after proving it resolves to a chat session. Keep the same guard here: a
4998
- * bot id (or a raw Bot topic id) must never become a generic chat source.
4999
- */
5000
- function legacyBotChatSessionCandidate(value, botId) {
5001
- const candidate = stringValue$1(value).trim();
5002
- if (candidate === "" || candidate === botId) return void 0;
5003
- if (candidate.includes("_bot") && !candidate.startsWith("chat_session_")) return void 0;
5004
- return candidate;
5005
- }
5006
4996
  function botPrivateChatTimestamp(value) {
5007
- const numeric = numberValue$19(value);
4997
+ const numeric = numberValue$20(value);
5008
4998
  if (numeric <= 0) return 0;
5009
4999
  if (numeric < 1e10) return numeric * 1e3;
5010
5000
  let millis = numeric;
@@ -5014,89 +5004,44 @@ function botPrivateChatTimestamp(value) {
5014
5004
  function botWebhookSecurity(value) {
5015
5005
  const raw = objectValue$1(value);
5016
5006
  return {
5017
- keywordEnabled: booleanValue$10(raw.keyword_enabled),
5007
+ keywordEnabled: booleanValue$11(raw.keyword_enabled),
5018
5008
  keyword: stringValue$1(raw.keyword).trim(),
5019
- tokenEnabled: booleanValue$10(raw.token_enabled ?? raw.signature_enabled),
5020
- ipWhitelistEnabled: booleanValue$10(raw.ip_whitelist_enabled),
5021
- ipWhitelist: listValue$20(raw.ip_whitelist).map((item) => stringValue$1(item).trim()).filter((item) => item !== "").slice(0, 100)
5009
+ tokenEnabled: booleanValue$11(raw.token_enabled ?? raw.signature_enabled),
5010
+ ipWhitelistEnabled: booleanValue$11(raw.ip_whitelist_enabled),
5011
+ ipWhitelist: listValue$21(raw.ip_whitelist).map((item) => stringValue$1(item).trim()).filter((item) => item !== "").slice(0, 100)
5022
5012
  };
5023
5013
  }
5024
5014
  function botProfileSource(data) {
5025
5015
  const bot = objectValue$1(data.bot);
5026
5016
  return Object.keys(bot).length === 0 ? data : bot;
5027
5017
  }
5028
- function botProfileSubjectUid(data) {
5029
- const bot = botProfileSource(data);
5030
- return stringValue$1(bot.subject_uid ?? bot.topic_uid ?? data.subject_uid ?? data.topic_uid).trim();
5031
- }
5032
- function botConversationOwner(data) {
5033
- const hasSubjectOwner = stringValue$1(data.subject_uid).trim() !== "";
5034
- if (hasSubjectOwner === (stringValue$1(data.chat_session_uid).trim() !== "")) return BOT_CONVERSATION_OWNER.unavailable;
5035
- return hasSubjectOwner ? BOT_CONVERSATION_OWNER.subject : BOT_CONVERSATION_OWNER.chat;
5036
- }
5037
- function botConversationCapabilities(owner, provider) {
5038
- return {
5039
- directChatAvailable: owner !== BOT_CONVERSATION_OWNER.unavailable,
5040
- privateChatOutboundEnabled: owner === BOT_CONVERSATION_OWNER.chat || owner === BOT_CONVERSATION_OWNER.subject && provider === "openclaw",
5041
- refreshOnRecordChanges: owner === BOT_CONVERSATION_OWNER.subject
5018
+ function botConversationTarget(data) {
5019
+ const subjectUid = stringValue$1(data.subject_uid).trim();
5020
+ const chatSessionUid = stringValue$1(data.chat_session_uid).trim();
5021
+ if (subjectUid !== "" && chatSessionUid !== "") return {
5022
+ kind: "unavailable",
5023
+ reason: "conflict"
5024
+ };
5025
+ if (subjectUid !== "") return {
5026
+ kind: "subject",
5027
+ subjectUid
5028
+ };
5029
+ if (chatSessionUid !== "") return {
5030
+ kind: "chat",
5031
+ chatSessionUid
5042
5032
  };
5043
- }
5044
- function botPrivateChatAttachments(value) {
5045
- return listValue$20(value).map(objectValue$1).flatMap((raw) => {
5046
- const kind = stringValue$1(raw.kind).trim();
5047
- const fileName = stringValue$1(raw.file_name ?? raw.fileName).trim();
5048
- const mimeType = stringValue$1(raw.mime_type ?? raw.mimeType).trim();
5049
- const hasSourceFile = stringValue$1(raw.file_id ?? raw.fileId).trim() !== "";
5050
- if (kind === "" && fileName === "" && mimeType === "" && !hasSourceFile) return [];
5051
- return [{
5052
- kind: kind || "file",
5053
- fileName,
5054
- mimeType,
5055
- size: Math.max(0, Math.trunc(numberValue$19(raw.size))),
5056
- durationMillis: Math.max(0, Math.trunc(numberValue$19(raw.duration_ms ?? raw.durationMillis))),
5057
- width: Math.max(0, Math.trunc(numberValue$19(raw.width))),
5058
- height: Math.max(0, Math.trunc(numberValue$19(raw.height))),
5059
- sortOrder: Math.max(0, Math.trunc(numberValue$19(raw.order ?? raw.sort_order ?? raw.sortOrder)))
5060
- }];
5061
- });
5062
- }
5063
- function botPrivateChatMessage(value, fallbackContent = "") {
5064
- const raw = objectValue$1(value);
5065
- const role = stringValue$1(raw.role).trim().toLowerCase() === "user" ? "user" : "assistant";
5066
- const recordUid = stringValue$1(raw.record_uid ?? raw.recordUid).trim();
5067
- const content = stringValue$1(raw.content);
5068
5033
  return {
5069
- messageId: stringValue$1(raw.message_id ?? raw.messageId).trim(),
5070
- ...recordUid === "" ? {} : { recordUid },
5071
- role,
5072
- content: content === "" ? fallbackContent : content,
5073
- status: stringValue$1(raw.status).trim() || "sent",
5074
- createdAtMillis: botPrivateChatTimestamp(raw.created_at ?? raw.createdAt),
5075
- attachments: botPrivateChatAttachments(raw.attachments)
5034
+ kind: "unavailable",
5035
+ reason: "missing"
5076
5036
  };
5077
5037
  }
5078
- function dedupeBotPrivateChatMessages(messages) {
5079
- const seenMessageIds = /* @__PURE__ */ new Set();
5080
- const result = [];
5081
- for (const message of messages) {
5082
- if (message.messageId !== "") {
5083
- if (seenMessageIds.has(message.messageId)) continue;
5084
- seenMessageIds.add(message.messageId);
5085
- }
5086
- result.push(message);
5087
- }
5088
- return result;
5089
- }
5090
- function botWithPrivateChatActivity(bot, messages) {
5091
- const latest = messages.reduce((current, message) => {
5092
- if (current === void 0 || message.createdAtMillis >= current.createdAtMillis) return message;
5093
- return current;
5094
- }, void 0);
5095
- if (latest === void 0) return bot;
5038
+ function botConversationCapabilities(target, provider) {
5039
+ const owner = target.kind === "subject" ? BOT_CONVERSATION_OWNER.subject : target.kind === "chat" ? BOT_CONVERSATION_OWNER.chat : void 0;
5096
5040
  return {
5097
- ...bot,
5098
- ...latest.createdAtMillis > 0 ? { latestMessageAtMillis: latest.createdAtMillis } : {},
5099
- ...latest.content === "" ? {} : { latestMessagePreview: latest.content }
5041
+ directChatAvailable: owner !== void 0,
5042
+ privateChatOutboundEnabled: owner === BOT_CONVERSATION_OWNER.chat || owner === BOT_CONVERSATION_OWNER.subject && provider === "openclaw",
5043
+ refreshOnRecordChanges: owner === BOT_CONVERSATION_OWNER.subject,
5044
+ conversationProjection: owner === BOT_CONVERSATION_OWNER.subject ? "record" : owner === BOT_CONVERSATION_OWNER.chat ? "chat" : "none"
5100
5045
  };
5101
5046
  }
5102
5047
  var BotService = class {
@@ -5142,13 +5087,23 @@ var BotService = class {
5142
5087
  }
5143
5088
  async listBots(options = {}) {
5144
5089
  const session = await this.runtime.requireSession();
5145
- const data = await this.runtime.authenticatedBotPost("/api/v1/bot/list", {}, session, options.signal);
5090
+ const rawBots = listValue$21((await this.runtime.authenticatedBotPost("/api/v1/bot/list", {}, session, options.signal)).bots).map(objectValue$1);
5091
+ const candidates = rawBots.filter((raw) => stringValue$1(raw.bot_id).trim() !== "" && stringValue$1(raw.name).trim() !== "" && arkmeNormalizeBotProvider(raw.provider) !== void 0);
5092
+ const chatTargetCounts = /* @__PURE__ */ new Map();
5093
+ for (const raw of rawBots) {
5094
+ const chatSessionUid = stringValue$1(raw.chat_session_uid).trim();
5095
+ if (chatSessionUid === "") continue;
5096
+ chatTargetCounts.set(chatSessionUid, (chatTargetCounts.get(chatSessionUid) ?? 0) + 1);
5097
+ }
5146
5098
  const items = [];
5147
- for (const value of listValue$20(data.bots)) {
5148
- const raw = objectValue$1(value);
5149
- if (arkmeNormalizeBotProvider(raw.provider) === void 0) continue;
5099
+ for (const raw of candidates) {
5100
+ const candidateTarget = botConversationTarget(raw);
5101
+ const target = candidateTarget.kind === "chat" && (chatTargetCounts.get(candidateTarget.chatSessionUid) ?? 0) > 1 ? {
5102
+ kind: "unavailable",
5103
+ reason: "duplicate_chat_target"
5104
+ } : candidateTarget;
5150
5105
  try {
5151
- items.push(await this.botSummaryFromData(raw, session.userId));
5106
+ items.push(await this.botSummaryFromData(raw, session.userId, target));
5152
5107
  } catch (error) {
5153
5108
  if (!(error instanceof ArkmePluginError) || error.code !== "bot-contract-invalid") throw error;
5154
5109
  }
@@ -5158,7 +5113,7 @@ var BotService = class {
5158
5113
  async countBots(options = {}) {
5159
5114
  const session = await this.runtime.requireSession();
5160
5115
  const data = await this.runtime.authenticatedBotPost("/api/v1/bot/list", { limit: 0 }, session, options.signal);
5161
- return Math.max(0, numberValue$19(data.total ?? data.total_count));
5116
+ return Math.max(0, numberValue$20(data.total ?? data.total_count));
5162
5117
  }
5163
5118
  async createBot(input, options = {}) {
5164
5119
  const name = input.name.trim();
@@ -5207,7 +5162,7 @@ var BotService = class {
5207
5162
  const session = await this.runtime.requireSession();
5208
5163
  const reference = await this.openBotRef(botRef, session.userId);
5209
5164
  const data = await this.runtime.authenticatedBotPost("/api/v1/bot/profile", { bot_id: reference.botId }, session, options.signal);
5210
- return await this.botManageProfileFromData(data, session.userId);
5165
+ return await this.botManageProfileFromData(data, session.userId, reference);
5211
5166
  }
5212
5167
  async updateManagedBot(botRef, input, options = {}) {
5213
5168
  const name = input.name.trim();
@@ -5237,7 +5192,7 @@ var BotService = class {
5237
5192
  } }
5238
5193
  }, session, options.signal);
5239
5194
  const refreshed = await this.runtime.authenticatedBotPost("/api/v1/bot/profile", { bot_id: reference.botId }, session, options.signal);
5240
- return await this.botManageProfileFromData(refreshed, session.userId);
5195
+ return await this.botManageProfileFromData(refreshed, session.userId, reference);
5241
5196
  }
5242
5197
  async revealManagedBotToken(botRef, options = {}) {
5243
5198
  return { token: (await this.revealBotSecret(botRef, options)).reveal() };
@@ -5250,113 +5205,24 @@ var BotService = class {
5250
5205
  await this.runtime.authenticatedBotPost("/api/v1/bot/delete", { bot_id: reference.botId }, session, options.signal);
5251
5206
  this.deleteBotRef(botRef);
5252
5207
  }
5253
- async botNotificationPreference(botRef, options = {}) {
5254
- const { session, subjectUid } = await this.botNotificationTarget(botRef, options);
5255
- return { muted: (await this.runtime.authenticatedSubjectPost("/api/v1/subject/get-able-push-status", { subject_uid: subjectUid }, session, options.signal)).able_push === false };
5256
- }
5257
- async updateBotNotificationPreference(botRef, muted, options = {}) {
5258
- const { session, subjectUid } = await this.botNotificationTarget(botRef, options);
5259
- await this.runtime.authenticatedSubjectPost("/api/v1/subject/set-able-push-status", {
5260
- subject_uid: subjectUid,
5261
- able_push: !muted
5262
- }, session, options.signal);
5263
- return { muted };
5264
- }
5265
- async botNotificationTarget(botRef, options = {}) {
5266
- const session = await this.runtime.requireSession();
5267
- const reference = await this.openBotRef(botRef, session.userId);
5268
- const subjectUid = botProfileSubjectUid(await this.runtime.authenticatedBotPost("/api/v1/bot/profile", { bot_id: reference.botId }, session, options.signal));
5269
- if (subjectUid === "") throw new ArkmePluginError("bot-notification-unavailable", "当前 Bot 未返回可用私聊通知标识", false, 409);
5270
- return {
5271
- session,
5272
- subjectUid
5273
- };
5274
- }
5275
- async openBotPrivateChat(botRef, options = {}) {
5276
- const session = await this.runtime.requireSession();
5277
- const bot = (await this.listBots(options)).items.find((item) => item.botRef === botRef);
5278
- if (bot === void 0) throw new ArkmePluginError("bot-ref-not-owned", "当前账号不存在该 Bot", false, 404);
5279
- return await this.openBotPrivateChatForSummary(bot, session, options.signal);
5280
- }
5281
- async listBotPrivateChatDirectory(options = {}) {
5282
- const session = await this.runtime.requireSession();
5283
- const { items } = await this.listBots(options);
5284
- return { items: await Promise.all(items.map(async (bot) => {
5285
- try {
5286
- return (await this.openBotPrivateChatForSummary(bot, session, options.signal)).bot;
5287
- } catch {
5288
- return bot;
5289
- }
5290
- })) };
5291
- }
5292
- async openBotPrivateChatForSummary(bot, session, signal) {
5293
- const reference = await this.openBotRef(bot.botRef, session.userId);
5294
- if (reference.conversationOwner === BOT_CONVERSATION_OWNER.unavailable) throw new ArkmePluginError("bot-private-chat-owner-unavailable", "当前 Bot 私聊归属信息不可用,请刷新后重试", false, 409);
5295
- const messages = dedupeBotPrivateChatMessages(listValue$20((await this.runtime.authenticatedBotPost("/api/v1/bot/private-chat/open", { bot_id: reference.botId }, session, signal)).messages).map((message) => botPrivateChatMessage(message)));
5296
- return {
5297
- bot: botWithPrivateChatActivity(bot, messages),
5298
- messages
5299
- };
5300
- }
5301
- async sendBotPrivateChatMessage(botRef, contentInput, options = {}) {
5302
- const content = contentInput.trim();
5303
- if (content === "") throw new ArkmePluginError("bot-private-chat-content-invalid", "请输入消息内容", false, 400);
5304
- if (content.length > 2e4) throw new ArkmePluginError("bot-private-chat-content-invalid", "消息不能超过 20000 个字符", false, 400);
5305
- const session = await this.runtime.requireSession();
5306
- const reference = await this.openBotRef(botRef, session.userId);
5307
- if (reference.conversationOwner === BOT_CONVERSATION_OWNER.unavailable) throw new ArkmePluginError("bot-private-chat-owner-unavailable", "当前 Bot 私聊归属信息不可用,请刷新后重试", false, 409);
5308
- if (reference.conversationOwner === BOT_CONVERSATION_OWNER.subject && reference.provider === "webhook") throw new ArkmePluginError("bot-private-chat-send-unsupported", "Webhook Bot 仅接收外部系统推送", false, 400);
5309
- let data;
5310
- try {
5311
- data = await this.runtime.authenticatedBotPost("/api/v1/bot/private-chat/message/send", {
5312
- bot_id: reference.botId,
5313
- content
5314
- }, session, options.signal);
5315
- } catch (error) {
5316
- if (reference.conversationOwner === BOT_CONVERSATION_OWNER.subject && error instanceof ArkmePluginError && (error.retryable || [
5317
- "arkme-network-error",
5318
- "arkme-timeout",
5319
- "arkme-response-invalid"
5320
- ].includes(error.code))) throw new ArkmePluginError("bot-private-chat-send-outcome-unknown", "消息发送结果未知,请刷新会话确认;不会自动重试", false, 409, { cause: error });
5321
- throw error;
5322
- }
5323
- if (reference.conversationOwner === BOT_CONVERSATION_OWNER.subject && stringValue$1(objectValue$1(data.user_message).message_id).trim() === "") throw new ArkmePluginError("bot-private-chat-send-outcome-unknown", "消息可能已发送,但响应缺少确认标识;请刷新会话确认", false, 409);
5324
- const botMessages = dedupeBotPrivateChatMessages([...listValue$20(data.bot_messages).map((message) => botPrivateChatMessage(message)), ...Object.keys(objectValue$1(data.bot_message)).length === 0 ? [] : [botPrivateChatMessage(data.bot_message)]]);
5325
- return {
5326
- recordProjectionChanged: reference.conversationOwner === BOT_CONVERSATION_OWNER.subject,
5327
- result: {
5328
- userMessage: botPrivateChatMessage(data.user_message, content),
5329
- botMessages,
5330
- status: stringValue$1(data.status).trim() || "ok"
5331
- }
5332
- };
5333
- }
5334
5208
  async openBotChat(botRef, options = {}) {
5335
5209
  const session = await this.runtime.requireSession();
5336
5210
  await this.openBotRef(botRef, session.userId);
5337
5211
  const bot = (await this.listBots(options)).items.find((item) => item.botRef === botRef);
5338
5212
  if (bot === void 0) throw new ArkmePluginError("bot-ref-not-owned", "当前账号不存在该 OpenClaw Bot", false, 404);
5339
5213
  const reference = await this.openBotRef(botRef, session.userId);
5340
- if (reference.conversationOwner !== BOT_CONVERSATION_OWNER.chat) throw new ArkmePluginError("bot-chat-source-unavailable", "当前 Bot 不属于 Chat 私聊链路", false, 409);
5341
- const data = await this.runtime.authenticatedBotPost("/api/v1/bot/private-chat/open", { bot_id: reference.botId }, session, options.signal);
5342
- const chatSessionUid = botChatSessionUid(data);
5343
- if (chatSessionUid !== void 0) {
5344
- const source = {
5345
- sourceRef: await this.source.sealSourceRef(session.userId, "private_chat", chatSessionUid, bot.name),
5346
- kind: "private_chat",
5347
- displayName: bot.name,
5348
- activeAtMillis: 0,
5349
- unreadCount: 0
5350
- };
5351
- this.source.setChatSourceByKey(`${String(session.userId)}:${chatSessionUid}`, source);
5352
- return source;
5353
- }
5354
- const legacySessionId = legacyBotChatSessionCandidate(data.session_id, reference.botId);
5355
- if (legacySessionId !== void 0) {
5356
- const source = await this.source.searchTargetSource(3, legacySessionId, bot.name, options.signal);
5357
- if (source !== void 0) return source;
5358
- }
5359
- throw new ArkmePluginError("bot-chat-source-unavailable", "当前 Bot 私聊未返回可用会话,暂时不能复用统一 source 读写链路", false, 409);
5214
+ if (reference.target.kind !== "chat") throw new ArkmePluginError("bot-chat-source-unavailable", "当前 Bot 不属于 Chat 私聊链路", false, 409);
5215
+ const chatSessionUid = stringValue$1((await this.runtime.authenticatedBotPost("/api/v1/bot/private-chat/open", { bot_id: reference.botId }, session, options.signal)).chat_session_uid).trim();
5216
+ if (chatSessionUid === "" || chatSessionUid !== reference.target.chatSessionUid) throw new ArkmePluginError("bot-chat-source-unavailable", "当前 Bot 私聊会话确认不一致", false, 409);
5217
+ const source = {
5218
+ sourceRef: await this.source.sealSourceRef(session.userId, "private_chat", chatSessionUid, bot.name),
5219
+ kind: "private_chat",
5220
+ displayName: bot.name,
5221
+ activeAtMillis: 0,
5222
+ unreadCount: 0
5223
+ };
5224
+ this.source.setChatSourceByKey(`${String(session.userId)}:${chatSessionUid}`, source);
5225
+ return source;
5360
5226
  }
5361
5227
  async resolveBotConnectionMetadata(botRef, options = {}) {
5362
5228
  const session = await this.runtime.requireSession();
@@ -5383,19 +5249,19 @@ var BotService = class {
5383
5249
  const groupTargetBody = await this.resolveGroupBotBindingBody(group, session, options.signal);
5384
5250
  const data = await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", groupTargetBody, session, options.signal);
5385
5251
  const items = [];
5386
- for (const value of listValue$20(data.bots)) {
5252
+ for (const value of listValue$21(data.bots)) {
5387
5253
  const raw = objectValue$1(value);
5388
5254
  if (arkmeNormalizeBotProvider(raw.provider) !== "openclaw") continue;
5389
5255
  const summary = await this.groupBotSummaryFromData(raw, session.userId);
5390
5256
  items.push({
5391
5257
  ...summary,
5392
- installed: booleanValue$10(raw.installed)
5258
+ installed: booleanValue$11(raw.installed)
5393
5259
  });
5394
5260
  }
5395
5261
  return {
5396
5262
  groupSourceRef,
5397
5263
  displayName: stringValue$1(data.subject_title).trim() || group.displayName,
5398
- canAddBots: booleanValue$10(data.can_current_user_add_bots),
5264
+ canAddBots: booleanValue$11(data.can_current_user_add_bots),
5399
5265
  items
5400
5266
  };
5401
5267
  }
@@ -5435,9 +5301,9 @@ var BotService = class {
5435
5301
  }
5436
5302
  async listMentionableGroupBots(group, session, signal) {
5437
5303
  const groupTargetBody = await this.resolveGroupBotBindingBody(group, session, signal);
5438
- return listValue$20((await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", groupTargetBody, session, signal)).bots).flatMap((value) => {
5304
+ return listValue$21((await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", groupTargetBody, session, signal)).bots).flatMap((value) => {
5439
5305
  const raw = objectValue$1(value);
5440
- if (arkmeNormalizeBotProvider(raw.provider) !== "openclaw" || !booleanValue$10(raw.installed)) return [];
5306
+ if (arkmeNormalizeBotProvider(raw.provider) !== "openclaw" || !booleanValue$11(raw.installed)) return [];
5441
5307
  const botId = stringValue$1(raw.bot_id).trim();
5442
5308
  const name = stringValue$1(raw.name).trim();
5443
5309
  if (botId === "" || name === "") throw new ArkmePluginError("bot-contract-invalid", "Bot 响应不完整", true, 502);
@@ -5460,7 +5326,7 @@ var BotService = class {
5460
5326
  botGroupTarget
5461
5327
  });
5462
5328
  }
5463
- async botSummaryBaseFromData(raw, userId, conversationOwner) {
5329
+ async botSummaryBaseFromData(raw, userId, target) {
5464
5330
  const botId = stringValue$1(raw.bot_id).trim();
5465
5331
  const name = stringValue$1(raw.name).trim();
5466
5332
  const provider = arkmeNormalizeBotProvider(raw.provider);
@@ -5469,7 +5335,7 @@ var BotService = class {
5469
5335
  const status = rawStatus === "online" || rawStatus === "offline" ? rawStatus : "unknown";
5470
5336
  const createdAtMillis = botPrivateChatTimestamp(raw.created_at ?? raw.createdAt);
5471
5337
  return {
5472
- botRef: this.sealBotRef(userId, botId, provider, conversationOwner),
5338
+ botRef: this.sealBotRef(userId, botId, provider, target),
5473
5339
  directoryKey: await this.botDirectoryKey(userId, botId),
5474
5340
  name,
5475
5341
  provider,
@@ -5479,21 +5345,23 @@ var BotService = class {
5479
5345
  ...this.botAvatarProjection(raw, userId, botId)
5480
5346
  };
5481
5347
  }
5482
- async botSummaryFromData(raw, userId) {
5483
- const conversationOwner = botConversationOwner(raw);
5484
- const summary = await this.botSummaryBaseFromData(raw, userId, conversationOwner);
5348
+ async botSummaryFromData(raw, userId, knownTarget) {
5349
+ const target = knownTarget ?? botConversationTarget(raw);
5350
+ const summary = await this.botSummaryBaseFromData(raw, userId, target);
5485
5351
  return {
5486
5352
  ...summary,
5487
- ...botConversationCapabilities(conversationOwner, summary.provider)
5353
+ ...botConversationCapabilities(target, summary.provider),
5354
+ ...target.kind === "chat" ? { chatSourceKey: await this.source.chatDirectorySourceKey(userId, target.chatSessionUid) } : {}
5488
5355
  };
5489
5356
  }
5490
5357
  async groupBotSummaryFromData(raw, userId) {
5491
5358
  return await this.botSummaryBaseFromData(raw, userId, void 0);
5492
5359
  }
5493
- async botManageProfileFromData(data, userId) {
5360
+ async botManageProfileFromData(data, userId, reference) {
5494
5361
  const raw = botProfileSource(data);
5495
- const summary = await this.botSummaryFromData(raw, userId);
5496
- const joinedGroups = listValue$20(data.joined_groups).map((value) => objectValue$1(value)).map((group) => ({
5362
+ if (stringValue$1(raw.bot_id).trim() !== reference.botId || arkmeNormalizeBotProvider(raw.provider) !== reference.provider) throw new ArkmePluginError("bot-contract-invalid", "Bot 配置响应与当前 Bot 不一致", true, 502);
5363
+ const summary = await this.botSummaryFromData(raw, userId, reference.target);
5364
+ const joinedGroups = listValue$21(data.joined_groups).map((value) => objectValue$1(value)).map((group) => ({
5497
5365
  title: stringValue$1(group.subject_title).trim() || stringValue$1(group.subject_uid).trim() || "未命名群聊",
5498
5366
  installedAtMillis: botPrivateChatTimestamp(group.installed_at)
5499
5367
  }));
@@ -5505,7 +5373,7 @@ var BotService = class {
5505
5373
  tokenRevealEnabled: data.token_reveal_enabled !== false,
5506
5374
  gatewayUrl: stringValue$1(data.gateway_url).trim(),
5507
5375
  webhookUrl: stringValue$1(data.webhook_url).trim(),
5508
- recordCount: Math.max(-1, Math.trunc(numberValue$19(raw.record_count))),
5376
+ recordCount: Math.max(-1, Math.trunc(numberValue$20(raw.record_count))),
5509
5377
  webhookSecurity: botWebhookSecurity(data.webhook_security ?? raw.webhook_security),
5510
5378
  joinedGroups
5511
5379
  };
@@ -5565,7 +5433,7 @@ var BotService = class {
5565
5433
  expiresAtMillis: entry.expiresAtMillis
5566
5434
  };
5567
5435
  }
5568
- sealBotRef(userId, botId, provider, conversationOwner) {
5436
+ sealBotRef(userId, botId, provider, target) {
5569
5437
  this.pruneBotRefs();
5570
5438
  const key = `${String(userId)}\u0000${provider}\u0000${botId}`;
5571
5439
  const existingRef = this.botRefByKey.get(key);
@@ -5573,7 +5441,7 @@ var BotService = class {
5573
5441
  if (existing !== void 0) {
5574
5442
  this.botRefs.set(existingRef, {
5575
5443
  ...existing,
5576
- ...conversationOwner === void 0 ? {} : { conversationOwner },
5444
+ ...target === void 0 ? {} : { target },
5577
5445
  expiresAtMillis: this.now() + (this.refOptions.ttlMillis ?? BOT_REF_TTL_MILLIS)
5578
5446
  });
5579
5447
  return existingRef;
@@ -5584,7 +5452,10 @@ var BotService = class {
5584
5452
  userId,
5585
5453
  botId,
5586
5454
  provider,
5587
- conversationOwner: conversationOwner ?? BOT_CONVERSATION_OWNER.unavailable,
5455
+ target: target ?? {
5456
+ kind: "unavailable",
5457
+ reason: "missing"
5458
+ },
5588
5459
  key,
5589
5460
  expiresAtMillis: this.now() + (this.refOptions.ttlMillis ?? BOT_REF_TTL_MILLIS)
5590
5461
  });
@@ -5628,13 +5499,251 @@ var BotService = class {
5628
5499
  }
5629
5500
  };
5630
5501
  //#endregion
5502
+ //#region src/services/bot-conversation-service.ts
5503
+ function numberValue$19(value) {
5504
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
5505
+ }
5506
+ function listValue$20(value) {
5507
+ return Array.isArray(value) ? value : [];
5508
+ }
5509
+ function botConversationTimestamp(value) {
5510
+ const numeric = numberValue$19(value);
5511
+ if (numeric <= 0) return 0;
5512
+ if (numeric < 1e10) return numeric * 1e3;
5513
+ let millis = numeric;
5514
+ while (millis >= 0x5af3107a4000) millis = Math.trunc(millis / 1e3);
5515
+ return millis;
5516
+ }
5517
+ function subjectAttachments(value) {
5518
+ return listValue$20(value).map(objectValue$1).flatMap((raw) => {
5519
+ const kind = stringValue$1(raw.kind).trim();
5520
+ const fileName = stringValue$1(raw.file_name ?? raw.fileName).trim();
5521
+ const mimeType = stringValue$1(raw.mime_type ?? raw.mimeType).trim();
5522
+ const hasSourceFile = stringValue$1(raw.file_id ?? raw.fileId).trim() !== "";
5523
+ if (kind === "" && fileName === "" && mimeType === "" && !hasSourceFile) return [];
5524
+ return [{
5525
+ kind: kind || "file",
5526
+ fileName,
5527
+ mimeType,
5528
+ size: Math.max(0, Math.trunc(numberValue$19(raw.size))),
5529
+ durationMillis: Math.max(0, Math.trunc(numberValue$19(raw.duration_ms ?? raw.durationMillis))),
5530
+ width: Math.max(0, Math.trunc(numberValue$19(raw.width))),
5531
+ height: Math.max(0, Math.trunc(numberValue$19(raw.height))),
5532
+ sortOrder: Math.max(0, Math.trunc(numberValue$19(raw.order ?? raw.sort_order ?? raw.sortOrder)))
5533
+ }];
5534
+ });
5535
+ }
5536
+ function subjectMessage(value, fallbackContent = "") {
5537
+ const raw = objectValue$1(value);
5538
+ const recordUid = stringValue$1(raw.record_uid ?? raw.recordUid).trim();
5539
+ const content = stringValue$1(raw.content);
5540
+ return {
5541
+ messageId: stringValue$1(raw.message_id ?? raw.messageId).trim(),
5542
+ ...recordUid === "" ? {} : { recordUid },
5543
+ role: stringValue$1(raw.role).trim().toLowerCase() === "user" ? "user" : "assistant",
5544
+ content: content === "" ? fallbackContent : content,
5545
+ status: stringValue$1(raw.status).trim() || "sent",
5546
+ createdAtMillis: botConversationTimestamp(raw.created_at ?? raw.createdAt),
5547
+ attachments: subjectAttachments(raw.attachments)
5548
+ };
5549
+ }
5550
+ function dedupeMessages(messages) {
5551
+ const seen = /* @__PURE__ */ new Set();
5552
+ return messages.filter((message) => {
5553
+ if (message.messageId === "") return true;
5554
+ if (seen.has(message.messageId)) return false;
5555
+ seen.add(message.messageId);
5556
+ return true;
5557
+ });
5558
+ }
5559
+ function withLatestActivity(bot, messages) {
5560
+ const latest = messages.reduce((current, message) => current === void 0 || message.createdAtMillis >= current.createdAtMillis ? message : current, void 0);
5561
+ if (latest === void 0) return bot;
5562
+ return {
5563
+ ...bot,
5564
+ ...latest.createdAtMillis > 0 ? { latestMessageAtMillis: latest.createdAtMillis } : {},
5565
+ ...latest.content === "" ? {} : { latestMessagePreview: latest.content }
5566
+ };
5567
+ }
5568
+ var SubjectBotConversationAdapter = class {
5569
+ runtime;
5570
+ constructor(runtime) {
5571
+ this.runtime = runtime;
5572
+ }
5573
+ async open(context, signal) {
5574
+ return await this.read(context, signal);
5575
+ }
5576
+ async refresh(context, signal) {
5577
+ return await this.read(context, signal);
5578
+ }
5579
+ async send(context, contentInput, signal) {
5580
+ this.subjectTarget(context);
5581
+ const content = contentInput.trim();
5582
+ if (context.reference.provider === "webhook") throw new ArkmePluginError("bot-conversation-send-unsupported", "Webhook Bot 仅接收外部系统推送", false, 400);
5583
+ if (content === "" || content.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("bot-conversation-content-invalid", "Bot 消息为空或超过长度限制", false, 400);
5584
+ let data;
5585
+ try {
5586
+ data = await this.runtime.authenticatedBotPost("/api/v1/bot/private-chat/message/send", {
5587
+ bot_id: context.reference.botId,
5588
+ content
5589
+ }, context.session, signal);
5590
+ } catch (error) {
5591
+ if (error instanceof ArkmePluginError && (error.retryable || [
5592
+ "arkme-network-error",
5593
+ "arkme-timeout",
5594
+ "arkme-response-invalid"
5595
+ ].includes(error.code))) throw new ArkmePluginError("bot-conversation-send-outcome-unknown", "消息发送结果未知,请刷新会话确认;不会自动重试", false, 409, { cause: error });
5596
+ throw error;
5597
+ }
5598
+ if (stringValue$1(objectValue$1(data.user_message).message_id).trim() === "") throw new ArkmePluginError("bot-conversation-send-outcome-unknown", "消息可能已发送,但响应缺少确认标识;请刷新会话确认", false, 409);
5599
+ return {
5600
+ userMessage: subjectMessage(data.user_message, content),
5601
+ botMessages: dedupeMessages([...listValue$20(data.bot_messages).map((message) => subjectMessage(message)), ...Object.keys(objectValue$1(data.bot_message)).length === 0 ? [] : [subjectMessage(data.bot_message)]]),
5602
+ status: stringValue$1(data.status).trim() || "ok"
5603
+ };
5604
+ }
5605
+ async notificationPreference(context, signal) {
5606
+ const target = this.subjectTarget(context);
5607
+ return { muted: (await this.runtime.authenticatedSubjectPost("/api/v1/subject/get-able-push-status", { subject_uid: target.subjectUid }, context.session, signal)).able_push === false };
5608
+ }
5609
+ async updateNotificationPreference(context, muted, signal) {
5610
+ const target = this.subjectTarget(context);
5611
+ await this.runtime.authenticatedSubjectPost("/api/v1/subject/set-able-push-status", {
5612
+ subject_uid: target.subjectUid,
5613
+ able_push: !muted
5614
+ }, context.session, signal);
5615
+ return { muted };
5616
+ }
5617
+ async read(context, signal) {
5618
+ this.subjectTarget(context);
5619
+ return { messages: dedupeMessages(listValue$20((await this.runtime.authenticatedBotPost("/api/v1/bot/private-chat/open", { bot_id: context.reference.botId }, context.session, signal)).messages).map((message) => subjectMessage(message))) };
5620
+ }
5621
+ subjectTarget(context) {
5622
+ if (context.reference.target.kind !== "subject") throw new ArkmePluginError("bot-conversation-owner-mismatch", "Bot 会话 owner 不匹配", false, 409);
5623
+ return context.reference.target;
5624
+ }
5625
+ };
5626
+ var ChatBotConversationAdapter = class {
5627
+ runtime;
5628
+ chat;
5629
+ constructor(runtime, chat) {
5630
+ this.runtime = runtime;
5631
+ this.chat = chat;
5632
+ }
5633
+ async open(context, signal) {
5634
+ const target = this.chatTarget(context);
5635
+ if (stringValue$1((await this.runtime.authenticatedBotPost("/api/v1/bot/private-chat/open", { bot_id: context.reference.botId }, context.session, signal)).chat_session_uid).trim() !== target.chatSessionUid) throw new ArkmePluginError("bot-conversation-target-mismatch", "Bot Chat 会话确认不一致", false, 409);
5636
+ return await this.chat.readDirectBotConversation(context.reference.botId, target.chatSessionUid, signal === void 0 ? {} : { signal });
5637
+ }
5638
+ async refresh(context, signal) {
5639
+ const target = this.chatTarget(context);
5640
+ return await this.chat.readDirectBotConversation(context.reference.botId, target.chatSessionUid, signal === void 0 ? {} : { signal });
5641
+ }
5642
+ async send(context, content, signal) {
5643
+ const target = this.chatTarget(context);
5644
+ return await this.chat.sendDirectBotText(target.chatSessionUid, content, signal === void 0 ? {} : { signal });
5645
+ }
5646
+ async markRead(context, sequence, signal) {
5647
+ const target = this.chatTarget(context);
5648
+ return await this.chat.markDirectBotRead(target.chatSessionUid, sequence, signal === void 0 ? {} : { signal });
5649
+ }
5650
+ async notificationPreference(context, signal) {
5651
+ const target = this.chatTarget(context);
5652
+ return await this.chat.directBotNotificationPreference(target.chatSessionUid, signal === void 0 ? {} : { signal });
5653
+ }
5654
+ async updateNotificationPreference(context, muted, signal) {
5655
+ const target = this.chatTarget(context);
5656
+ return await this.chat.updateDirectBotNotificationPreference(target.chatSessionUid, muted, signal === void 0 ? {} : { signal });
5657
+ }
5658
+ chatTarget(context) {
5659
+ if (context.reference.target.kind !== "chat") throw new ArkmePluginError("bot-conversation-owner-mismatch", "Bot 会话 owner 不匹配", false, 409);
5660
+ return context.reference.target;
5661
+ }
5662
+ };
5663
+ var BotConversationService = class {
5664
+ runtime;
5665
+ bot;
5666
+ invalidateRecordProjection;
5667
+ subject;
5668
+ chatAdapter;
5669
+ constructor(runtime, bot, chat, invalidateRecordProjection) {
5670
+ this.runtime = runtime;
5671
+ this.bot = bot;
5672
+ this.invalidateRecordProjection = invalidateRecordProjection;
5673
+ this.subject = new SubjectBotConversationAdapter(runtime);
5674
+ this.chatAdapter = new ChatBotConversationAdapter(runtime, chat);
5675
+ }
5676
+ async directory(options = {}) {
5677
+ const session = await this.runtime.requireSession();
5678
+ const { items } = await this.bot.listBots(options);
5679
+ return { items: await Promise.all(items.map(async (bot) => {
5680
+ const reference = await this.bot.openBotRef(bot.botRef, session.userId);
5681
+ if (reference.target.kind !== "subject") return bot;
5682
+ try {
5683
+ return withLatestActivity(bot, (await this.subject.open({
5684
+ session,
5685
+ reference: {
5686
+ ...reference,
5687
+ target: reference.target
5688
+ }
5689
+ }, options.signal)).messages);
5690
+ } catch {
5691
+ return bot;
5692
+ }
5693
+ })) };
5694
+ }
5695
+ async open(botRef, options = {}) {
5696
+ const context = await this.context(botRef);
5697
+ return await this.adapter(context).open(context, options.signal);
5698
+ }
5699
+ async refresh(botRef, options = {}) {
5700
+ const context = await this.context(botRef);
5701
+ return await this.adapter(context).refresh(context, options.signal);
5702
+ }
5703
+ async send(botRef, content, options = {}) {
5704
+ const context = await this.context(botRef);
5705
+ const result = await this.adapter(context).send(context, content, options.signal);
5706
+ if (context.reference.target.kind === "subject") await this.invalidateRecordProjection();
5707
+ return result;
5708
+ }
5709
+ async markRead(botRef, sequence, options = {}) {
5710
+ const context = await this.context(botRef);
5711
+ const adapter = this.adapter(context);
5712
+ if (adapter.markRead === void 0) throw new ArkmePluginError("bot-conversation-read-unsupported", "当前 Bot 会话不支持已读游标", false, 400);
5713
+ return await adapter.markRead(context, sequence, options.signal);
5714
+ }
5715
+ async notificationPreference(botRef, options = {}) {
5716
+ const context = await this.context(botRef);
5717
+ return await this.adapter(context).notificationPreference(context, options.signal);
5718
+ }
5719
+ async updateNotificationPreference(botRef, muted, options = {}) {
5720
+ const context = await this.context(botRef);
5721
+ return await this.adapter(context).updateNotificationPreference(context, muted, options.signal);
5722
+ }
5723
+ async context(botRef) {
5724
+ const session = await this.runtime.requireSession();
5725
+ const reference = await this.bot.openBotRef(botRef, session.userId);
5726
+ if (reference.target.kind === "unavailable") throw new ArkmePluginError("bot-conversation-owner-unavailable", "当前 Bot 会话归属信息不可用,请刷新后重试", false, 409);
5727
+ return {
5728
+ session,
5729
+ reference: {
5730
+ ...reference,
5731
+ target: reference.target
5732
+ }
5733
+ };
5734
+ }
5735
+ adapter(context) {
5736
+ return context.reference.target.kind === "subject" ? this.subject : this.chatAdapter;
5737
+ }
5738
+ };
5739
+ //#endregion
5631
5740
  //#region src/services/calendar-service.ts
5632
5741
  const MAX_CALENDAR_RANGE_DAYS = 62;
5633
5742
  const MAX_DAY_RECORD_LIMIT = 50;
5634
5743
  function numberValue$18(value) {
5635
5744
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
5636
5745
  }
5637
- function booleanValue$9(value) {
5746
+ function booleanValue$10(value) {
5638
5747
  if (typeof value === "boolean") return value;
5639
5748
  if (typeof value === "number") return value !== 0;
5640
5749
  }
@@ -5681,7 +5790,7 @@ function sourceKind(raw) {
5681
5790
  const chat = objectValue$1(raw.chat_core);
5682
5791
  if (stringValue$1(topic.topic_uid).trim() !== "") return "topic";
5683
5792
  if (stringValue$1(chat.chat_session_uid).trim() !== "") return "chat";
5684
- if (booleanValue$9(raw.is_uncategorized) === true) return "self";
5793
+ if (booleanValue$10(raw.is_uncategorized) === true) return "self";
5685
5794
  return "unknown";
5686
5795
  }
5687
5796
  var CalendarService = class {
@@ -5786,9 +5895,9 @@ var CalendarService = class {
5786
5895
  const textContent = available ? clippedText(core.text_content, 4e3) : "";
5787
5896
  const preview = title || clippedText(textContent, 160) || "无文字内容";
5788
5897
  const topic = objectValue$1(item.topic_core);
5789
- const isUncategorized = booleanValue$9(item.is_uncategorized);
5790
- const hasManualEdit = booleanValue$9(core.has_manual_edit);
5791
- const hasPolish = booleanValue$9(core.has_polish);
5898
+ const isUncategorized = booleanValue$10(item.is_uncategorized);
5899
+ const hasManualEdit = booleanValue$10(core.has_manual_edit);
5900
+ const hasPolish = booleanValue$10(core.has_polish);
5792
5901
  return {
5793
5902
  recordUid,
5794
5903
  sendAtMillis,
@@ -5821,7 +5930,7 @@ function numberValue$17(value) {
5821
5930
  }
5822
5931
  return 0;
5823
5932
  }
5824
- function booleanValue$8(value) {
5933
+ function booleanValue$9(value) {
5825
5934
  return value === true;
5826
5935
  }
5827
5936
  function firstValue(source, keys) {
@@ -6166,7 +6275,7 @@ var CallHistoryService = class {
6166
6275
  });
6167
6276
  const validItems = (await Promise.all(this.historyItems(raw).map(async (item) => await this.normalizeHistoryItem(item, session.userId)))).filter((item) => item !== void 0);
6168
6277
  await this.attachPeerAvatars(validItems, session, signal);
6169
- const hasMore = booleanValue$8(raw.has_more ?? raw.hasMore);
6278
+ const hasMore = booleanValue$9(raw.has_more ?? raw.hasMore);
6170
6279
  const nextCursor = firstString(raw, [
6171
6280
  "next_cursor",
6172
6281
  "nextCursor",
@@ -6501,8 +6610,8 @@ var CallHistoryService = class {
6501
6610
  ])),
6502
6611
  ...summaryText === "" ? {} : { summaryText },
6503
6612
  ...summaryUpdatedAtMillis > 0 ? { summaryUpdatedAtMillis } : {},
6504
- transcriptPending: booleanValue$8(raw.transcript_pending ?? raw.transcriptPending),
6505
- transcriptFailed: booleanValue$8(raw.transcript_failed ?? raw.transcriptFailed),
6613
+ transcriptPending: booleanValue$9(raw.transcript_pending ?? raw.transcriptPending),
6614
+ transcriptFailed: booleanValue$9(raw.transcript_failed ?? raw.transcriptFailed),
6506
6615
  ...videoRecord === void 0 ? {} : { videoRecord },
6507
6616
  participants,
6508
6617
  transcriptSegments: this.transcriptSegments(raw).slice(0, 200)
@@ -7497,21 +7606,11 @@ const ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES = 20 * 1024 * 1024;
7497
7606
  const OFFICIAL_AUTHOR_USER_ID = 11;
7498
7607
  const OFFICIAL_AUTHOR_FALLBACK_DISPLAY_NAME = "即我作者";
7499
7608
  const MAX_MESSAGE_COPY_LINK_ITEMS = 100;
7500
- const LINK_METADATA_MAX_BYTES = 128 * 1024;
7501
- const LINK_METADATA_MAX_REDIRECTS = 3;
7502
7609
  const RECORD_UID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7503
- const HTML_ENTITY_MAP = {
7504
- amp: "&",
7505
- lt: "<",
7506
- gt: ">",
7507
- quot: "\"",
7508
- apos: "'",
7509
- nbsp: " "
7510
- };
7511
7610
  function numberValue$16(value) {
7512
7611
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
7513
7612
  }
7514
- function booleanValue$7(value) {
7613
+ function booleanValue$8(value) {
7515
7614
  return value === true;
7516
7615
  }
7517
7616
  function favoriteStickerPersistenceItem(item) {
@@ -7523,7 +7622,7 @@ function favoriteStickerPersistenceItem(item) {
7523
7622
  mime_type: stringValue$1(item.mime_type).trim(),
7524
7623
  file_kind: Math.trunc(numberValue$16(item.file_kind)),
7525
7624
  file_type: Math.trunc(numberValue$16(item.file_type)),
7526
- is_animated: booleanValue$7(item.is_animated),
7625
+ is_animated: booleanValue$8(item.is_animated),
7527
7626
  file_size: Math.max(0, Math.trunc(numberValue$16(item.file_size))),
7528
7627
  file_create_at: Math.max(0, Math.trunc(numberValue$16(item.file_create_at))),
7529
7628
  file_push_at: Math.max(0, Math.trunc(numberValue$16(item.file_push_at)))
@@ -7532,88 +7631,6 @@ function favoriteStickerPersistenceItem(item) {
7532
7631
  function listValue$16(value) {
7533
7632
  return Array.isArray(value) ? value : [];
7534
7633
  }
7535
- function htmlTextValue(value) {
7536
- return value.replace(/<script[\s\S]*?<\/script>/giu, " ").replace(/<style[\s\S]*?<\/style>/giu, " ").replace(/<[^>]+>/gu, " ").replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/giu, (_match, entity) => {
7537
- const normalized = entity.toLowerCase();
7538
- if (normalized.startsWith("#x")) {
7539
- const code = Number.parseInt(normalized.slice(2), 16);
7540
- return Number.isFinite(code) ? String.fromCodePoint(code) : " ";
7541
- }
7542
- if (normalized.startsWith("#")) {
7543
- const code = Number.parseInt(normalized.slice(1), 10);
7544
- return Number.isFinite(code) ? String.fromCodePoint(code) : " ";
7545
- }
7546
- return HTML_ENTITY_MAP[normalized] ?? " ";
7547
- }).replace(/\s+/gu, " ").trim();
7548
- }
7549
- function firstHtmlMetaContent(html, key) {
7550
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
7551
- const patterns = [new RegExp(`<meta\\b(?=[^>]*(?:property|name)=["']${escaped}["'])(?=[^>]*content=["']([^"']*)["'])[^>]*>`, "iu"), new RegExp(`<meta\\b(?=[^>]*content=["']([^"']*)["'])(?=[^>]*(?:property|name)=["']${escaped}["'])[^>]*>`, "iu")];
7552
- for (const pattern of patterns) {
7553
- const value = pattern.exec(html)?.[1];
7554
- if (value !== void 0) return htmlTextValue(value);
7555
- }
7556
- return "";
7557
- }
7558
- function fallbackLinkTitle(url) {
7559
- const host = url.hostname.replace(/^www\./iu, "");
7560
- if (host === "github.com") {
7561
- const parts = url.pathname.split("/").filter(Boolean);
7562
- if (parts.length >= 4 && parts[2] === "pull") return `Pull Request #${parts[3]} · ${parts[0]}/${parts[1]}`;
7563
- if (parts.length >= 2) return `${parts[0]}/${parts[1]}`;
7564
- }
7565
- if (host === "codeup.aliyun.com" || host.endsWith(".codeup.aliyun.com")) {
7566
- const parts = url.pathname.split("/").filter(Boolean);
7567
- const changeIndex = parts.indexOf("change");
7568
- const repository = changeIndex >= 2 ? parts[changeIndex - 1] : parts.at(-1);
7569
- const changeNo = changeIndex >= 0 ? parts[changeIndex + 1] : void 0;
7570
- if (repository !== void 0 && changeNo !== void 0) return `${repository} · Change #${changeNo}`;
7571
- }
7572
- return "分享链接";
7573
- }
7574
- function ensureMetadataUrl(rawUrl) {
7575
- let url;
7576
- try {
7577
- url = new URL(rawUrl.trim());
7578
- } catch {
7579
- throw new ArkmePluginError("link-metadata-url-invalid", "链接暂不可预览", false, 400);
7580
- }
7581
- if (url.protocol !== "http:" && url.protocol !== "https:") throw new ArkmePluginError("link-metadata-url-invalid", "链接暂不可预览", false, 400);
7582
- if (url.username !== "" || url.password !== "") throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
7583
- const hostname = url.hostname.toLowerCase();
7584
- if (hostname === "localhost" || hostname.endsWith(".local")) throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
7585
- if (/^(?:127|10|0|169\.254|192\.168)\./u.test(hostname) || /^172\.(?:1[6-9]|2\d|3[0-1])\./u.test(hostname)) throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
7586
- if (hostname === "::1" || hostname.startsWith("fc") || hostname.startsWith("fd") || hostname.startsWith("fe80")) throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
7587
- return url;
7588
- }
7589
- async function readResponseTextPrefix(response) {
7590
- if (response.body === null) return await response.text();
7591
- const reader = response.body.getReader();
7592
- const chunks = [];
7593
- let bytes = 0;
7594
- try {
7595
- while (bytes < LINK_METADATA_MAX_BYTES) {
7596
- const next = await reader.read();
7597
- if (next.done) break;
7598
- chunks.push(next.value);
7599
- bytes += next.value.byteLength;
7600
- }
7601
- } finally {
7602
- reader.releaseLock();
7603
- }
7604
- return new TextDecoder("utf-8", { fatal: false }).decode(Uint8Array.from(chunks.flatMap((chunk) => [...chunk])));
7605
- }
7606
- function linkMetadataFromHtml(url, html) {
7607
- const title = firstHtmlMetaContent(html, "og:title") || firstHtmlMetaContent(html, "twitter:title") || htmlTextValue(/<title\b[^>]*>([\s\S]*?)<\/title>/iu.exec(html)?.[1] ?? "") || fallbackLinkTitle(url);
7608
- const description = firstHtmlMetaContent(html, "og:description") || firstHtmlMetaContent(html, "description");
7609
- const siteName = firstHtmlMetaContent(html, "og:site_name") || url.hostname.replace(/^www\./iu, "");
7610
- return {
7611
- url: url.toString(),
7612
- title,
7613
- ...description === "" ? {} : { description },
7614
- ...siteName === "" ? {} : { siteName }
7615
- };
7616
- }
7617
7634
  function chatMemberRole$1(value) {
7618
7635
  if (value === "owner" || value === 1) return "owner";
7619
7636
  if (value === "admin" || value === 2) return "admin";
@@ -8027,7 +8044,7 @@ function selfTopicIdentityFromHomeFeed(raw) {
8027
8044
  title
8028
8045
  };
8029
8046
  }
8030
- function encodeOpaqueJson$5(value) {
8047
+ function encodeOpaqueJson$6(value) {
8031
8048
  return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
8032
8049
  }
8033
8050
  function decodeOpaqueJson$5(value) {
@@ -8057,6 +8074,171 @@ var ChatService = class {
8057
8074
  this.realtime = realtime;
8058
8075
  this.privacy = privacy;
8059
8076
  }
8077
+ async readDirectBotConversation(botId, chatSessionUid, options = {}) {
8078
+ const normalizedBotId = botId.trim();
8079
+ const normalizedSessionUid = chatSessionUid.trim();
8080
+ if (normalizedBotId === "" || normalizedSessionUid === "") throw new ArkmePluginError("bot-chat-target-invalid", "Bot Chat 会话目标无效", false, 400);
8081
+ const session = await this.runtime.requireSession();
8082
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chat/timeline/page", {
8083
+ chat_session_uid: normalizedSessionUid,
8084
+ before_seq: 0,
8085
+ limit: 100
8086
+ }, session, options.signal, {
8087
+ lane: "interactive-read",
8088
+ key: `bot-timeline:${normalizedSessionUid}`,
8089
+ failureCooldownMs: 2e3
8090
+ });
8091
+ if (stringValue$1(data.chat_session_uid).trim() !== normalizedSessionUid) throw new ArkmePluginError("bot-chat-timeline-contract-invalid", "Bot Chat 时间线返回了不匹配的会话", true, 502);
8092
+ const projected = [];
8093
+ let latestSequence = 0;
8094
+ for (const raw of listValue$16(data.items)) {
8095
+ const item = objectValue$1(raw);
8096
+ const relation = objectValue$1(item.relation);
8097
+ const record = objectValue$1(item.record);
8098
+ const payload = objectValue$1(record.payload);
8099
+ const relationUid = stringValue$1(relation.rel_uid).trim();
8100
+ const recordUid = stringValue$1(relation.record_uid).trim();
8101
+ const hydratedRecordUid = stringValue$1(payload.record_uid).trim();
8102
+ const sequence = Math.trunc(numberValue$16(relation.seq));
8103
+ const actorKind = Math.trunc(numberValue$16(relation.sender_actor_kind));
8104
+ const senderUserId = Math.trunc(numberValue$16(relation.sender_user_id));
8105
+ const senderBotUid = stringValue$1(relation.sender_bot_uid).trim();
8106
+ const userActor = actorKind === 1 && senderUserId === session.userId && senderBotUid === "";
8107
+ const botActor = actorKind === 2 && senderBotUid === normalizedBotId;
8108
+ if (relationUid === "" || recordUid === "" || hydratedRecordUid !== "" && hydratedRecordUid !== recordUid || !Number.isSafeInteger(sequence) || sequence <= 0 || !userActor && !botActor) throw new ArkmePluginError("bot-chat-timeline-contract-invalid", "Bot Chat 时间线包含无效消息身份", true, 502);
8109
+ latestSequence = Math.max(latestSequence, sequence);
8110
+ if (numberValue$16(record.status) !== 1) continue;
8111
+ const contentBlocks = this.media.richContentBlocks(item, session.userId);
8112
+ projected.push({
8113
+ messageId: relationUid,
8114
+ recordUid,
8115
+ role: userActor ? "user" : "assistant",
8116
+ content: stringValue$1(payload.text_content),
8117
+ status: "sent",
8118
+ createdAtMillis: Math.max(0, Math.trunc(numberValue$16(relation.attach_at ?? payload.send_at))),
8119
+ attachments: contentBlocks.map((block) => ({
8120
+ kind: block.kind,
8121
+ fileName: block.fileName,
8122
+ mimeType: block.mimeType,
8123
+ size: block.size,
8124
+ durationMillis: Math.max(0, Math.trunc((block.durationSec ?? 0) * 1e3)),
8125
+ width: 0,
8126
+ height: 0,
8127
+ sortOrder: block.sortOrder
8128
+ })),
8129
+ sequence
8130
+ });
8131
+ }
8132
+ projected.sort((left, right) => left.sequence - right.sequence || left.createdAtMillis - right.createdAtMillis || left.messageId.localeCompare(right.messageId));
8133
+ const seenRelationUids = /* @__PURE__ */ new Set();
8134
+ const messages = [];
8135
+ for (const item of projected) {
8136
+ if (seenRelationUids.has(item.messageId)) continue;
8137
+ seenRelationUids.add(item.messageId);
8138
+ const { sequence: _sequence, ...message } = item;
8139
+ messages.push(message);
8140
+ }
8141
+ return {
8142
+ messages,
8143
+ ...latestSequence > 0 ? { latestSequence } : {}
8144
+ };
8145
+ }
8146
+ async sendDirectBotText(chatSessionUid, contentInput, options = {}) {
8147
+ const chatSessionUidValue = chatSessionUid.trim();
8148
+ const content = contentInput.trim();
8149
+ if (chatSessionUidValue === "" || content === "" || content.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("bot-conversation-content-invalid", "Bot 消息为空或超过长度限制", false, 400);
8150
+ const session = await this.runtime.requireSession();
8151
+ const recordUid = randomUUID();
8152
+ const relationUid = randomUUID();
8153
+ const sendAtMillis = Date.now();
8154
+ let result;
8155
+ try {
8156
+ result = await this.postChatTextRecord(chatSessionUidValue, content, recordUid, relationUid, sendAtMillis, session, options.signal);
8157
+ } catch (error) {
8158
+ if (error instanceof ArkmePluginError && (error.retryable || [
8159
+ "arkme-network-error",
8160
+ "arkme-timeout",
8161
+ "arkme-response-invalid"
8162
+ ].includes(error.code))) throw new ArkmePluginError("bot-conversation-send-outcome-unknown", "消息发送结果未知,请刷新会话确认;不会自动重试", false, 409, { cause: error });
8163
+ throw error;
8164
+ }
8165
+ const responseSessionUid = stringValue$1(result.chat_session_uid).trim();
8166
+ const responseRecordUid = stringValue$1(result.record_uid).trim();
8167
+ const responseRelationUid = stringValue$1(result.rel_uid).trim();
8168
+ const sequence = Math.trunc(numberValue$16(result.seq));
8169
+ if (responseSessionUid !== chatSessionUidValue || responseRecordUid !== recordUid || responseRelationUid !== relationUid || !Number.isSafeInteger(sequence) || sequence <= 0) throw new ArkmePluginError("bot-conversation-send-outcome-unknown", "消息可能已发送,但服务端确认不完整;请刷新会话确认", false, 409);
8170
+ this.realtime.scheduleChatSessionProjection(chatSessionUidValue, sequence);
8171
+ return {
8172
+ userMessage: {
8173
+ messageId: responseRelationUid,
8174
+ recordUid: responseRecordUid,
8175
+ role: "user",
8176
+ content,
8177
+ status: "sent",
8178
+ createdAtMillis: sendAtMillis,
8179
+ attachments: []
8180
+ },
8181
+ botMessages: [],
8182
+ status: "ok"
8183
+ };
8184
+ }
8185
+ async directBotNotificationPreference(chatSessionUid, options = {}) {
8186
+ const { policy } = await this.directBotPolicy(chatSessionUid, options.signal);
8187
+ return { muted: numberValue$16(policy.mute_state) === 2 || numberValue$16(policy.notify_state) === 2 };
8188
+ }
8189
+ async updateDirectBotNotificationPreference(chatSessionUid, muted, options = {}) {
8190
+ const { session, sessionUid, policy } = await this.directBotPolicy(chatSessionUid, options.signal);
8191
+ const updateAt = Math.max(Date.now(), numberValue$16(policy.update_at) + 1);
8192
+ const updated = await this.runtime.authenticatedChatPost("/api/v1/chats/policy/update", {
8193
+ chat_session_uid: sessionUid,
8194
+ show_in_home_state: numberValue$16(policy.show_in_home_state),
8195
+ privacy_state: numberValue$16(policy.privacy_state),
8196
+ mute_state: muted ? 2 : 1,
8197
+ pin_state: numberValue$16(policy.pin_state),
8198
+ notify_state: muted ? 2 : 1,
8199
+ status: numberValue$16(policy.status),
8200
+ update_at: updateAt
8201
+ }, session, options.signal);
8202
+ if (stringValue$1(updated.chat_session_uid).trim() !== sessionUid || numberValue$16(updated.user_id) !== session.userId || numberValue$16(updated.show_in_home_state) !== numberValue$16(policy.show_in_home_state) || numberValue$16(updated.privacy_state) !== numberValue$16(policy.privacy_state) || numberValue$16(updated.mute_state) !== (muted ? 2 : 1) || numberValue$16(updated.pin_state) !== numberValue$16(policy.pin_state) || numberValue$16(updated.notify_state) !== (muted ? 2 : 1) || numberValue$16(updated.status) !== numberValue$16(policy.status) || !Number.isSafeInteger(numberValue$16(updated.update_at)) || numberValue$16(updated.update_at) < updateAt) throw new ArkmePluginError("bot-chat-policy-contract-invalid", "Bot Chat 通知策略更新确认不完整", true, 502);
8203
+ const cacheKey = `${String(session.userId)}:${sessionUid}`;
8204
+ const cached = this.source.cachedChatSourceByKey(cacheKey);
8205
+ if (cached !== void 0) this.source.setChatSourceByKey(cacheKey, {
8206
+ ...cached,
8207
+ isMuted: muted
8208
+ });
8209
+ return { muted };
8210
+ }
8211
+ async markDirectBotRead(chatSessionUid, readSequence, options = {}) {
8212
+ const sessionUid = chatSessionUid.trim();
8213
+ if (sessionUid === "") throw new ArkmePluginError("bot-chat-target-invalid", "Bot Chat 会话目标无效", false, 400);
8214
+ const session = await this.runtime.requireSession();
8215
+ const sourceRef = await this.source.sealSourceRef(session.userId, "private_chat", sessionUid, "Bot");
8216
+ const result = await this.advanceChatReadCursor(sourceRef, sessionUid, readSequence, session, options.signal);
8217
+ return {
8218
+ effectiveReadSequence: result.effectiveReadSequence,
8219
+ unreadCount: result.unreadCount
8220
+ };
8221
+ }
8222
+ async directBotPolicy(chatSessionUid, signal) {
8223
+ const sessionUid = chatSessionUid.trim();
8224
+ if (sessionUid === "") throw new ArkmePluginError("bot-chat-target-invalid", "Bot Chat 会话目标无效", false, 400);
8225
+ const session = await this.runtime.requireSession();
8226
+ const policy = await this.runtime.authenticatedChatPost("/api/v1/chats/policy/get", { chat_session_uid: sessionUid }, session, signal);
8227
+ const updateAt = numberValue$16(policy.update_at);
8228
+ if (stringValue$1(policy.chat_session_uid).trim() !== sessionUid || numberValue$16(policy.user_id) !== session.userId || ![
8229
+ "show_in_home_state",
8230
+ "privacy_state",
8231
+ "mute_state",
8232
+ "pin_state",
8233
+ "notify_state",
8234
+ "status"
8235
+ ].every((key) => Number.isSafeInteger(numberValue$16(policy[key])) && numberValue$16(policy[key]) > 0) || !Number.isSafeInteger(updateAt) || updateAt <= 0 || updateAt >= Number.MAX_SAFE_INTEGER) throw new ArkmePluginError("bot-chat-policy-contract-invalid", "Bot Chat 通知策略响应不完整", true, 502);
8236
+ return {
8237
+ session,
8238
+ sessionUid,
8239
+ policy
8240
+ };
8241
+ }
8060
8242
  async serializeFavoriteStickerMutation(work) {
8061
8243
  const pending = this.favoriteStickerMutationTail.then(work);
8062
8244
  this.favoriteStickerMutationTail = pending.then(() => void 0, () => void 0);
@@ -8181,7 +8363,7 @@ var ChatService = class {
8181
8363
  }, session, options.signal);
8182
8364
  const rmSubjectId = Math.trunc(numberValue$16(created.rm_subject_id));
8183
8365
  if (!Number.isSafeInteger(rmSubjectId) || rmSubjectId <= 0) {
8184
- if (booleanValue$7(created.already_exist)) throw new ArkmePluginError("private-chat-self-invalid", "不能给自己发起私聊", false, 409);
8366
+ if (booleanValue$8(created.already_exist)) throw new ArkmePluginError("private-chat-self-invalid", "不能给自己发起私聊", false, 409);
8185
8367
  throw new ArkmePluginError("official-author-chat-contract-invalid", "联系作者私聊响应不完整", true, 502);
8186
8368
  }
8187
8369
  const partner = listValue$16((await this.runtime.authenticatedSubjectPost("/api/v1/private/get-partner-info-v2", { rm_subject_ids: [rmSubjectId] }, session, options.signal)).item_ls).map((item) => objectValue$1(item)).find((item) => Math.trunc(numberValue$16(item.rm_subject_id)) === rmSubjectId);
@@ -8676,71 +8858,6 @@ var ChatService = class {
8676
8858
  }
8677
8859
  };
8678
8860
  }
8679
- async resolveLinkMetadata(rawUrl, options = {}) {
8680
- const url = ensureMetadataUrl(rawUrl);
8681
- try {
8682
- return await this.fetchLinkMetadata(url, 0, options.signal);
8683
- } catch (error) {
8684
- if (error instanceof ArkmePluginError && (error.code === "link-metadata-url-invalid" || error.code === "link-metadata-url-forbidden")) throw error;
8685
- return {
8686
- url: url.toString(),
8687
- title: fallbackLinkTitle(url),
8688
- siteName: url.hostname.replace(/^www\./iu, "")
8689
- };
8690
- }
8691
- }
8692
- async fetchLinkMetadata(url, redirectCount, signal) {
8693
- const controller = new AbortController();
8694
- const abort = () => {
8695
- controller.abort(signal?.reason);
8696
- };
8697
- if (signal?.aborted === true) abort();
8698
- else signal?.addEventListener("abort", abort, { once: true });
8699
- const timeout = setTimeout(() => {
8700
- controller.abort();
8701
- }, Math.min(8e3, this.runtime.config.requestTimeoutMs));
8702
- try {
8703
- const response = await this.runtime.fetchImpl(url, {
8704
- method: "GET",
8705
- redirect: "manual",
8706
- headers: {
8707
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
8708
- "Accept-Language": "zh-CN",
8709
- "User-Agent": "Mozilla/5.0 ArkmeDSHPlugin/0.1 LinkPreview"
8710
- },
8711
- signal: controller.signal
8712
- });
8713
- if (response.status >= 300 && response.status < 400) {
8714
- if (redirectCount >= LINK_METADATA_MAX_REDIRECTS) return {
8715
- url: url.toString(),
8716
- title: fallbackLinkTitle(url),
8717
- siteName: url.hostname.replace(/^www\./iu, "")
8718
- };
8719
- const location = response.headers.get("location");
8720
- if (location === null || location.trim() === "") return {
8721
- url: url.toString(),
8722
- title: fallbackLinkTitle(url),
8723
- siteName: url.hostname.replace(/^www\./iu, "")
8724
- };
8725
- return await this.fetchLinkMetadata(ensureMetadataUrl(new URL(location, url).toString()), redirectCount + 1, signal);
8726
- }
8727
- if (!response.ok) return {
8728
- url: url.toString(),
8729
- title: fallbackLinkTitle(url),
8730
- siteName: url.hostname.replace(/^www\./iu, "")
8731
- };
8732
- const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
8733
- if (contentType !== "" && !contentType.includes("html") && !contentType.includes("xml") && !contentType.includes("text/")) return {
8734
- url: url.toString(),
8735
- title: fallbackLinkTitle(url),
8736
- siteName: url.hostname.replace(/^www\./iu, "")
8737
- };
8738
- return linkMetadataFromHtml(url, await readResponseTextPrefix(response));
8739
- } finally {
8740
- clearTimeout(timeout);
8741
- signal?.removeEventListener("abort", abort);
8742
- }
8743
- }
8744
8861
  async forwardSourceMessages(sourceRef, actionRefs, options = {}) {
8745
8862
  const session = await this.runtime.requireSession();
8746
8863
  const source = await this.source.openSourceRef(sourceRef, session.userId);
@@ -9066,20 +9183,14 @@ var ChatService = class {
9066
9183
  bot_mentions: mentions
9067
9184
  }
9068
9185
  };
9069
- return await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, visibleText, recordUid, relationUid, session, void 0, contentPayload, signal, options);
9070
- }
9071
- async sendChatSourceTextRaw(sourceRef, chatSessionUid, text, recordUid, relationUid, session, initialAiPolish, contentPayload, signal, options = {}) {
9072
- const result = await this.runtime.authenticatedChatPost("/api/v1/chats/records/send", {
9073
- chat_session_uid: chatSessionUid,
9074
- record_uid: recordUid,
9075
- rel_uid: relationUid,
9076
- template_kind: 1,
9077
- text_content: text,
9078
- ...options.agentAuthored === true ? { creation_source: 1 } : {},
9079
- ...initialAiPolish === void 0 ? {} : { initial_ai_polish: initialAiPolish },
9080
- ...contentPayload === void 0 ? {} : { content_payload: contentPayload },
9081
- send_at: Date.now()
9082
- }, session, signal);
9186
+ return await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, visibleText, recordUid, relationUid, session, void 0, contentPayload, signal, options);
9187
+ }
9188
+ async sendChatSourceTextRaw(sourceRef, chatSessionUid, text, recordUid, relationUid, session, initialAiPolish, contentPayload, signal, options = {}) {
9189
+ const result = await this.postChatTextRecord(chatSessionUid, text, recordUid, relationUid, Date.now(), session, signal, {
9190
+ ...options,
9191
+ ...initialAiPolish === void 0 ? {} : { initialAiPolish },
9192
+ ...contentPayload === void 0 ? {} : { contentPayload }
9193
+ });
9083
9194
  const sequence = numberValue$16(result.seq);
9084
9195
  this.realtime.scheduleChatSessionProjection(chatSessionUid, sequence);
9085
9196
  return {
@@ -9090,6 +9201,19 @@ var ChatService = class {
9090
9201
  localState: "synced"
9091
9202
  };
9092
9203
  }
9204
+ async postChatTextRecord(chatSessionUid, text, recordUid, relationUid, sendAtMillis, session, signal, options = {}) {
9205
+ return await this.runtime.authenticatedChatPost("/api/v1/chats/records/send", {
9206
+ chat_session_uid: chatSessionUid,
9207
+ record_uid: recordUid,
9208
+ rel_uid: relationUid,
9209
+ template_kind: 1,
9210
+ text_content: text,
9211
+ ...options.agentAuthored === true ? { creation_source: 1 } : {},
9212
+ ...options.initialAiPolish === void 0 ? {} : { initial_ai_polish: options.initialAiPolish },
9213
+ ...options.contentPayload === void 0 ? {} : { content_payload: options.contentPayload },
9214
+ send_at: sendAtMillis
9215
+ }, session, signal);
9216
+ }
9093
9217
  async sendSourceRich(sourceRef, input, options = {}) {
9094
9218
  if (this.runtime.config.richMediaSendEnabled === false) throw new ArkmePluginError("rich-content-disabled", "富内容发送已被插件配置关闭", false, 403);
9095
9219
  const session = await this.runtime.requireSession();
@@ -9208,7 +9332,7 @@ var ChatService = class {
9208
9332
  mimeType,
9209
9333
  size: Math.max(0, Math.trunc(numberValue$16(raw.file_size ?? raw.size))),
9210
9334
  fileKind: 1,
9211
- isAnimated: booleanValue$7(raw.is_animated) || mimeType.toLowerCase() === "image/gif",
9335
+ isAnimated: booleanValue$8(raw.is_animated) || mimeType.toLowerCase() === "image/gif",
9212
9336
  isAvailable: serverAvailable && mediaRef !== void 0,
9213
9337
  ...mediaRef === void 0 ? {} : { mediaRef },
9214
9338
  ...!serverAvailable || mediaRef === void 0 ? { unavailableReason: stringValue$1(raw.unavailable_reason).trim() || "表情已不可用" } : {}
@@ -9362,21 +9486,26 @@ var ChatService = class {
9362
9486
  const session = await this.runtime.requireSession();
9363
9487
  const source = await this.source.openSourceRef(sourceRef, session.userId);
9364
9488
  if (source.kind !== "private_chat" && source.kind !== "group_chat") throw new ArkmePluginError("source-read-unsupported", "当前数据源不支持聊天已读", false);
9489
+ if (!Number.isSafeInteger(readSequence) || readSequence <= 0) throw new ArkmePluginError("source-read-sequence-invalid", "聊天已读游标无效", false);
9490
+ return await this.advanceChatReadCursor(sourceRef, source.ownerRef, readSequence, session, options.signal);
9491
+ }
9492
+ async advanceChatReadCursor(sourceRef, chatSessionUid, readSequence, session, signal) {
9365
9493
  if (!Number.isSafeInteger(readSequence) || readSequence <= 0) throw new ArkmePluginError("source-read-sequence-invalid", "聊天已读游标无效", false);
9366
9494
  const data = await this.runtime.authenticatedChatPost("/api/v1/chats/cursor/update", {
9367
- chat_session_uid: source.ownerRef,
9495
+ chat_session_uid: chatSessionUid,
9368
9496
  read_seq: readSequence,
9369
9497
  read_at: Date.now(),
9370
9498
  client_ack_id: randomUUID(),
9371
9499
  reason: "arkme_dsh_open_chat"
9372
- }, session, options.signal);
9500
+ }, session, signal);
9373
9501
  const responseSessionUid = stringValue$1(data.chat_session_uid).trim();
9502
+ const responseUserId = numberValue$16(data.user_id);
9374
9503
  const effectiveReadSequence = numberValue$16(data.effective_read_seq);
9375
9504
  const readAt = numberValue$16(data.read_at);
9376
9505
  const sessionLastSequence = numberValue$16(data.session_last_seq);
9377
9506
  const unreadCount = numberValue$16(data.unread_count);
9378
- if (responseSessionUid !== source.ownerRef || !Number.isSafeInteger(effectiveReadSequence) || effectiveReadSequence < readSequence || readAt <= 0 || sessionLastSequence < effectiveReadSequence || !Number.isSafeInteger(unreadCount) || unreadCount < 0) throw new ArkmePluginError("source-read-ack-invalid", "聊天已读响应不完整", true, 502);
9379
- const cacheKey = `${String(session.userId)}:${source.ownerRef}`;
9507
+ if (responseSessionUid !== chatSessionUid || responseUserId !== session.userId || !Number.isSafeInteger(effectiveReadSequence) || effectiveReadSequence < readSequence || !Number.isSafeInteger(readAt) || readAt <= 0 || !Number.isSafeInteger(sessionLastSequence) || sessionLastSequence < effectiveReadSequence || !Number.isSafeInteger(unreadCount) || unreadCount < 0) throw new ArkmePluginError("source-read-ack-invalid", "聊天已读响应不完整", true, 502);
9508
+ const cacheKey = `${String(session.userId)}:${chatSessionUid}`;
9380
9509
  const cached = this.source.cachedChatSourceByKey(cacheKey);
9381
9510
  if (cached !== void 0) this.source.setChatSourceByKey(cacheKey, {
9382
9511
  ...cached,
@@ -9387,11 +9516,11 @@ var ChatService = class {
9387
9516
  type: "read-ack",
9388
9517
  revision: this.realtime.nextChatClientRevision(),
9389
9518
  sourceRef,
9390
- sourceKey: await this.source.chatDirectorySourceKey(session.userId, source.ownerRef),
9519
+ sourceKey: await this.source.chatDirectorySourceKey(session.userId, chatSessionUid),
9391
9520
  effectiveReadSequence,
9392
9521
  unreadCount
9393
9522
  });
9394
- this.realtime.scheduleChatSessionProjection(source.ownerRef, sessionLastSequence);
9523
+ this.realtime.scheduleChatSessionProjection(chatSessionUid, sessionLastSequence);
9395
9524
  return {
9396
9525
  sourceRef,
9397
9526
  effectiveReadSequence,
@@ -9403,6 +9532,7 @@ var ChatService = class {
9403
9532
  }
9404
9533
  async chatTimelineItems(data, session, chatSessionUid) {
9405
9534
  const items = [];
9535
+ const signingKey = await this.runtime.stateStore.uniqueCode();
9406
9536
  for (const raw of listValue$16(data.items)) {
9407
9537
  const item = objectValue$1(raw);
9408
9538
  const relation = objectValue$1(item.relation);
@@ -9411,9 +9541,20 @@ var ChatService = class {
9411
9541
  const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
9412
9542
  if (uid === "") continue;
9413
9543
  const senderUserId = numberValue$16(relation.sender_user_id);
9544
+ const relationUid = stringValue$1(relation.rel_uid ?? relation.relUid).trim();
9414
9545
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
9415
9546
  const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
9416
9547
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
9548
+ const sharedRecording = this.withSharedRecordingDetailRef(this.chatSharedRecordingPreview(item), {
9549
+ viewerUserId: session.userId,
9550
+ chatSessionUid,
9551
+ relationUid,
9552
+ recordOwnerUserId: integerLikeValue$1(record.record_owner_user_id ?? record.recordOwnerUserId ?? payload.record_owner_user_id ?? payload.recordOwnerUserId ?? relation.record_owner_user_id ?? relation.recordOwnerUserId),
9553
+ recordUid: uid,
9554
+ senderUserId: integerLikeValue$1(relation.sender_user_id),
9555
+ sequence: integerLikeValue$1(relation.seq),
9556
+ signingKey
9557
+ });
9417
9558
  const rawAgentSource = timelineAgentSource(relation, record, payload);
9418
9559
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
9419
9560
  const contentBlocks = this.media.richContentBlocks(item, session.userId);
@@ -9434,6 +9575,7 @@ var ChatService = class {
9434
9575
  ...numberValue$16(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$16(record.version ?? payload.version) } : {},
9435
9576
  ...aiPolish === void 0 ? {} : { aiPolish },
9436
9577
  ...forwardRecords === void 0 ? {} : { forwardRecords },
9578
+ ...sharedRecording === void 0 ? {} : { sharedRecording },
9437
9579
  displayKind: numberValue$16(payload.display_kind),
9438
9580
  contentBlocks
9439
9581
  });
@@ -9539,6 +9681,203 @@ var ChatService = class {
9539
9681
  ...truncated ? { truncated: true } : {}
9540
9682
  };
9541
9683
  }
9684
+ chatSharedRecordingPreview(raw) {
9685
+ const contentPayload = this.media.recordContentPayload(raw);
9686
+ const direct = this.sharedRecordingPreviewFromPayload(contentPayload);
9687
+ if (direct !== void 0) return direct;
9688
+ const root = objectValue$1(raw);
9689
+ const relation = objectValue$1(root.relation);
9690
+ const relationPayload = parsedObject(relation.render_content_payload ?? relation.renderContentPayload ?? root.render_content_payload ?? root.renderContentPayload);
9691
+ const relationDirect = this.sharedRecordingPreviewFromPayload(relationPayload);
9692
+ if (relationDirect !== void 0) return relationDirect;
9693
+ if (stringValue$1(relationPayload.render_kind ?? relationPayload.renderKind).trim() !== "shared_recording_memory") return void 0;
9694
+ const record = objectValue$1(root.record);
9695
+ const recordPayload = parsedObject(record.payload);
9696
+ const candidates = [
9697
+ contentPayload,
9698
+ recordPayload,
9699
+ parsedObject(recordPayload.content_payload ?? recordPayload.contentPayload),
9700
+ parsedObject(record.content_payload ?? record.contentPayload),
9701
+ parsedObject(root.payload),
9702
+ parsedObject(root.content_payload ?? root.contentPayload)
9703
+ ];
9704
+ for (const candidate of candidates) {
9705
+ const body = objectValue$1(candidate.shared_recording ?? candidate.sharedRecording);
9706
+ const payload = Object.keys(body).length === 0 ? candidate : body;
9707
+ if (!this.sharedRecordingSourceDigestCompatible(payload, relationPayload)) continue;
9708
+ const projection = this.sharedRecordingPreviewFromPayload({
9709
+ ...payload,
9710
+ ...relationPayload,
9711
+ render_kind: "shared_recording_memory"
9712
+ });
9713
+ if (projection !== void 0) return projection;
9714
+ }
9715
+ }
9716
+ sharedRecordingParticipantsFromPayload(payload) {
9717
+ const participants = listValue$16(payload.participants);
9718
+ const mobileParticipants = listValue$16(payload.participant_ls ?? payload.participantLs);
9719
+ const speakers = listValue$16(payload.speaker_ls ?? payload.speakerLs ?? payload.speakers);
9720
+ return (participants.length > 0 ? participants : mobileParticipants.length > 0 ? mobileParticipants : speakers).flatMap((value) => {
9721
+ if (typeof value === "string") {
9722
+ const displayName = value.trim();
9723
+ return displayName === "" ? [] : [{
9724
+ displayName,
9725
+ role: 0
9726
+ }];
9727
+ }
9728
+ const participant = objectValue$1(value);
9729
+ const displayName = stringValue$1(participant.display_name ?? participant.displayName ?? participant.nick_name ?? participant.nickName ?? participant.speaker_name ?? participant.speakerName ?? participant.name).trim();
9730
+ if (displayName === "") return [];
9731
+ const refUserId = integerLikeValue$1(participant.ref_user_id ?? participant.refUserId ?? participant.ref_usr_id ?? participant.refUsrId);
9732
+ return [{
9733
+ ...refUserId > 0 ? { refUserId } : {},
9734
+ displayName,
9735
+ role: Math.max(0, integerLikeValue$1(participant.role))
9736
+ }];
9737
+ });
9738
+ }
9739
+ sharedRecordingTranscriptFromPayload(payload) {
9740
+ const direct = stringValue$1(payload.transcript ?? payload.transcript_text ?? payload.transcriptText ?? payload.original_text ?? payload.originalText).trim();
9741
+ if (direct !== "") return direct;
9742
+ for (const key of [
9743
+ "transcript_ls",
9744
+ "transcriptLs",
9745
+ "transcript_segments",
9746
+ "transcriptSegments",
9747
+ "asr_ls",
9748
+ "asrLs"
9749
+ ]) {
9750
+ const lines = listValue$16(payload[key]).flatMap((value) => {
9751
+ if (typeof value === "string") {
9752
+ const line = value.trim();
9753
+ return line === "" ? [] : [line];
9754
+ }
9755
+ const segment = objectValue$1(value);
9756
+ const text = stringValue$1(segment.text ?? segment.text_content ?? segment.textContent ?? segment.transcript ?? segment.content).trim();
9757
+ if (text === "") return [];
9758
+ const speakerName = stringValue$1(segment.speaker_name ?? segment.speakerName ?? segment.speaker_label ?? segment.speakerLabel ?? segment.nick_name ?? segment.nickName ?? segment.display_name ?? segment.displayName).trim();
9759
+ return [speakerName === "" ? text : `${speakerName}:${text}`];
9760
+ });
9761
+ if (lines.length > 0) return lines.join("\n");
9762
+ }
9763
+ return "";
9764
+ }
9765
+ sharedRecordingPreviewFromPayload(payload, depth = 0) {
9766
+ const renderKind = stringValue$1(payload.render_kind ?? payload.renderKind).trim();
9767
+ if (renderKind === "shared_recording_memory") {
9768
+ const sourceDigest = stringValue$1(payload.source_digest ?? payload.sourceDigest).trim();
9769
+ const title = stringValue$1(payload.title ?? payload.orig_name ?? payload.origName).trim();
9770
+ const summary = stringValue$1(payload.summary ?? payload.summary_text ?? payload.summaryText ?? payload.content_summary ?? payload.contentSummary).trim();
9771
+ const displayAtMillis = integerLikeValue$1(payload.display_at ?? payload.displayAt);
9772
+ if (sourceDigest === "" || title === "" || summary === "" || displayAtMillis <= 0) return void 0;
9773
+ const transcript = this.sharedRecordingTranscriptFromPayload(payload);
9774
+ return {
9775
+ sourceDigest,
9776
+ sharedByUserId: Math.max(0, integerLikeValue$1(payload.shared_by_user_id ?? payload.sharedByUserId)),
9777
+ sharedAtMillis: Math.max(0, integerLikeValue$1(payload.shared_at ?? payload.sharedAt)),
9778
+ displayAtMillis,
9779
+ endAtMillis: Math.max(0, integerLikeValue$1(payload.end_at ?? payload.endAt)),
9780
+ timeRangeText: stringValue$1(payload.time_range_text ?? payload.timeRangeText).trim(),
9781
+ title,
9782
+ summary,
9783
+ ...transcript === "" ? {} : { transcript },
9784
+ transcriptAvailable: transcript !== "",
9785
+ participants: this.sharedRecordingParticipantsFromPayload(payload)
9786
+ };
9787
+ }
9788
+ if (renderKind !== "" || depth >= 4) return void 0;
9789
+ for (const key of [
9790
+ "shared_recording",
9791
+ "sharedRecording",
9792
+ "content_payload",
9793
+ "contentPayload",
9794
+ "record_payload",
9795
+ "recordPayload",
9796
+ "fallback_content_payload",
9797
+ "fallbackContentPayload",
9798
+ "payload",
9799
+ "extra"
9800
+ ]) {
9801
+ const nested = parsedObject(payload[key]);
9802
+ if (Object.keys(nested).length === 0) continue;
9803
+ const projection = this.sharedRecordingPreviewFromPayload(nested, depth + 1);
9804
+ if (projection !== void 0) return projection;
9805
+ }
9806
+ }
9807
+ sharedRecordingSourceDigestCompatible(left, right) {
9808
+ const leftDigest = stringValue$1(left.source_digest ?? left.sourceDigest).trim();
9809
+ const rightDigest = stringValue$1(right.source_digest ?? right.sourceDigest).trim();
9810
+ return leftDigest === "" || rightDigest === "" || leftDigest === rightDigest;
9811
+ }
9812
+ withSharedRecordingDetailRef(recording, input) {
9813
+ if (recording === void 0) return void 0;
9814
+ const recordUid = input.recordUid.trim();
9815
+ const recordOwnerUserId = input.recordOwnerUserId > 0 ? input.recordOwnerUserId : recording.sharedByUserId > 0 ? recording.sharedByUserId : input.senderUserId;
9816
+ const chatSessionUid = input.chatSessionUid.trim();
9817
+ if (chatSessionUid === "" || recordUid === "" || recordOwnerUserId <= 0) return recording;
9818
+ return {
9819
+ ...recording,
9820
+ detailRef: this.sealSharedRecordingDetailRef({
9821
+ version: 1,
9822
+ viewerUserId: input.viewerUserId,
9823
+ chatSessionUid,
9824
+ relationUid: input.relationUid.trim(),
9825
+ recordOwnerUserId: Math.trunc(recordOwnerUserId),
9826
+ recordUid,
9827
+ sequence: Math.max(0, Math.trunc(input.sequence))
9828
+ }, input.signingKey)
9829
+ };
9830
+ }
9831
+ sealSharedRecordingDetailRef(payload, signingKey) {
9832
+ const encoded = encodeOpaqueJson$6(payload);
9833
+ return `arkme-shared-recording-detail-v1.${encoded}.${createHmac("sha256", signingKey).update(encoded).digest("base64url")}`;
9834
+ }
9835
+ async openSharedRecordingDetailRef(detailRef, expectedViewerUserId) {
9836
+ const parts = detailRef.trim().split(".");
9837
+ if (parts.length !== 3 || parts[0] !== "arkme-shared-recording-detail-v1") throw new ArkmePluginError("shared-recording-detail-ref-invalid", "共享录音详情引用无效,请刷新后重试", false);
9838
+ const encoded = parts[1] ?? "";
9839
+ const supplied = Buffer.from(parts[2] ?? "", "base64url");
9840
+ const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest();
9841
+ if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("shared-recording-detail-ref-invalid", "共享录音详情引用无效,请刷新后重试", false);
9842
+ let parsed;
9843
+ try {
9844
+ parsed = objectValue$1(decodeOpaqueJson$5(encoded));
9845
+ } catch (error) {
9846
+ throw new ArkmePluginError("shared-recording-detail-ref-invalid", "共享录音详情引用无效,请刷新后重试", false, 400, { cause: error });
9847
+ }
9848
+ const result = {
9849
+ version: 1,
9850
+ viewerUserId: integerLikeValue$1(parsed.viewerUserId),
9851
+ chatSessionUid: stringValue$1(parsed.chatSessionUid).trim(),
9852
+ relationUid: stringValue$1(parsed.relationUid).trim(),
9853
+ recordOwnerUserId: integerLikeValue$1(parsed.recordOwnerUserId),
9854
+ recordUid: stringValue$1(parsed.recordUid).trim(),
9855
+ sequence: Math.max(0, integerLikeValue$1(parsed.sequence))
9856
+ };
9857
+ if (parsed.version !== 1 || result.viewerUserId !== expectedViewerUserId || result.chatSessionUid === "" || result.recordUid === "" || result.recordOwnerUserId <= 0) throw new ArkmePluginError("shared-recording-detail-ref-invalid", "共享录音详情引用与当前账号不匹配,请刷新后重试", false, 403);
9858
+ return result;
9859
+ }
9860
+ async sharedRecordingDetail(detailRef, options = {}) {
9861
+ const session = await this.runtime.requireSession();
9862
+ const reference = await this.openSharedRecordingDetailRef(detailRef, session.userId);
9863
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/records/detail", {
9864
+ chat_session_uid: reference.chatSessionUid,
9865
+ record_uid: reference.recordUid,
9866
+ record_owner_user_id: reference.recordOwnerUserId,
9867
+ ...reference.relationUid === "" ? {} : { rel_uid: reference.relationUid },
9868
+ ...reference.sequence <= 0 ? {} : { seq: reference.sequence }
9869
+ }, session, options.signal, {
9870
+ lane: "interactive-read",
9871
+ key: `shared-recording-detail:${reference.chatSessionUid}:${reference.recordUid}:${String(reference.sequence)}`,
9872
+ failureCooldownMs: 2e3
9873
+ });
9874
+ const projection = this.chatSharedRecordingPreview(objectValue$1(data.item)) ?? this.chatSharedRecordingPreview(data);
9875
+ if (projection === void 0) throw new ArkmePluginError("shared-recording-detail-unavailable", "共享录音详情暂时无法读取,请稍后重试", true, 502);
9876
+ return {
9877
+ ...projection,
9878
+ detailRef
9879
+ };
9880
+ }
9542
9881
  async rawChatMembers(chatSessionUid, activeOnly, session, signal) {
9543
9882
  return listValue$16((await this.runtime.authenticatedChatPost("/api/v1/chats/members/list", {
9544
9883
  chat_session_uid: chatSessionUid,
@@ -9603,6 +9942,16 @@ var ChatService = class {
9603
9942
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
9604
9943
  const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
9605
9944
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
9945
+ const sharedRecording = this.withSharedRecordingDetailRef(this.chatSharedRecordingPreview(item), {
9946
+ viewerUserId: session.userId,
9947
+ chatSessionUid: source.ownerRef,
9948
+ relationUid,
9949
+ recordOwnerUserId: integerLikeValue$1(record.record_owner_user_id ?? record.recordOwnerUserId ?? payload.record_owner_user_id ?? payload.recordOwnerUserId ?? relation.record_owner_user_id ?? relation.recordOwnerUserId),
9950
+ recordUid: uid,
9951
+ senderUserId,
9952
+ sequence: integerLikeValue$1(relation.seq),
9953
+ signingKey
9954
+ });
9606
9955
  const rawAgentSource = timelineAgentSource(relation, record, payload);
9607
9956
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
9608
9957
  const contentBlocks = this.media.richContentBlocks(item, session.userId);
@@ -9646,7 +9995,8 @@ var ChatService = class {
9646
9995
  recordDurationMillis: numberValue$16(payload.record_duration_millis),
9647
9996
  editDurationMillis: numberValue$16(payload.edit_duration_millis),
9648
9997
  contentBlocks,
9649
- ...forwardRecords === void 0 ? {} : { forwardRecords }
9998
+ ...forwardRecords === void 0 ? {} : { forwardRecords },
9999
+ ...sharedRecording === void 0 ? {} : { sharedRecording }
9650
10000
  }) - 1;
9651
10001
  if (senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
9652
10002
  }
@@ -9660,7 +10010,7 @@ var ChatService = class {
9660
10010
  return items;
9661
10011
  }
9662
10012
  async sealChatMemberRef(viewerUserId, chatSessionUid, targetUserId) {
9663
- const encoded = encodeOpaqueJson$5({
10013
+ const encoded = encodeOpaqueJson$6({
9664
10014
  version: 1,
9665
10015
  viewerUserId,
9666
10016
  chatSessionUid,
@@ -9740,7 +10090,7 @@ var ChatService = class {
9740
10090
  };
9741
10091
  }
9742
10092
  sealMessageActionRef(payload, signingKey) {
9743
- const encoded = encodeOpaqueJson$5(payload);
10093
+ const encoded = encodeOpaqueJson$6(payload);
9744
10094
  return `arkme-message-action-v1.${encoded}.${createHmac("sha256", signingKey).update(encoded).digest("base64url")}`;
9745
10095
  }
9746
10096
  async openMessageActionRefs(actionRefs, expectedUserId, source) {
@@ -9861,7 +10211,7 @@ var ChatService = class {
9861
10211
  };
9862
10212
  }
9863
10213
  sealMessageRef(userId, chatSessionUid, relationUid, signingKey) {
9864
- const payload = encodeOpaqueJson$5({
10214
+ const payload = encodeOpaqueJson$6({
9865
10215
  version: 1,
9866
10216
  userId,
9867
10217
  chatSessionUid,
@@ -9901,7 +10251,7 @@ function numberValue$15(value) {
9901
10251
  const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
9902
10252
  return Number.isFinite(parsed) ? parsed : 0;
9903
10253
  }
9904
- function booleanValue$6(value) {
10254
+ function booleanValue$7(value) {
9905
10255
  return value === true;
9906
10256
  }
9907
10257
  function normalizeIdentifier(value) {
@@ -10032,9 +10382,9 @@ var ContactService = class {
10032
10382
  bypassCache: true
10033
10383
  });
10034
10384
  const targetUserId = Math.trunc(numberValue$15(data.user_id));
10035
- const registered = booleanValue$6(data.is_registered) && targetUserId > 0;
10036
- const isSelf = booleanValue$6(data.is_self);
10037
- const canAdd = booleanValue$6(data.can_add) && !isSelf && (normalized.kind === "phone" || registered);
10385
+ const registered = booleanValue$7(data.is_registered) && targetUserId > 0;
10386
+ const isSelf = booleanValue$7(data.is_self);
10387
+ const canAdd = booleanValue$7(data.can_add) && !isSelf && (normalized.kind === "phone" || registered);
10038
10388
  const displayName = stringValue$1(data.nick_name).trim() || stringValue$1(data.jotmo_id).trim() || (normalized.kind === "phone" ? `+${normalized.phonePre} ${normalized.identifier}` : normalized.identifier);
10039
10389
  const result = {
10040
10390
  contactRef,
@@ -10043,7 +10393,7 @@ var ContactService = class {
10043
10393
  ...stringValue$1(data.jotmo_id).trim() === "" ? {} : { arkmeId: stringValue$1(data.jotmo_id).trim() },
10044
10394
  ...registered ? { avatarRef: await this.profile.sealProfileImageRef(session.userId, targetUserId) } : {},
10045
10395
  registered,
10046
- inviteBySms: booleanValue$6(data.invite_by_sms),
10396
+ inviteBySms: booleanValue$7(data.invite_by_sms),
10047
10397
  canAdd,
10048
10398
  isSelf
10049
10399
  };
@@ -10235,10 +10585,9 @@ var ContactDirectoryService = class {
10235
10585
  const offset = await this.openOffsetCursor("bots", options.cursor, session.userId);
10236
10586
  const bots = (await this.bot.listBots(options.signal === void 0 ? {} : { signal: options.signal })).items;
10237
10587
  const slice = bots.slice(offset, offset + limit);
10238
- const items = slice.map((item) => ({
10588
+ const items = slice.map((bot) => ({
10239
10589
  kind: "bot",
10240
- botRef: item.botRef,
10241
- displayName: item.name
10590
+ bot
10242
10591
  }));
10243
10592
  const hasMore = offset + slice.length < bots.length;
10244
10593
  return {
@@ -10476,7 +10825,7 @@ var ContactDirectoryService = class {
10476
10825
  function numberValue$13(value) {
10477
10826
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
10478
10827
  }
10479
- function booleanValue$5(value) {
10828
+ function booleanValue$6(value) {
10480
10829
  return value === true;
10481
10830
  }
10482
10831
  function listValue$14(value) {
@@ -10495,7 +10844,7 @@ var CommunityService = class {
10495
10844
  const session = await this.runtime.requireSession();
10496
10845
  const data = await this.runtime.authenticatedChatPost("/api/v1/chats/community/dsh-beta/entry-state", {}, session, signal);
10497
10846
  const status = this.dshBetaCommunityStatus(data.status);
10498
- const visible = booleanValue$5(data.visible);
10847
+ const visible = booleanValue$6(data.visible);
10499
10848
  const groupTitle = stringValue$1(data.group_title).trim();
10500
10849
  const snapshot = objectValue$1(data.group_avatar_snapshot);
10501
10850
  const memberCount = Math.max(0, Math.trunc(numberValue$13(snapshot.member_count)));
@@ -10790,7 +11139,7 @@ var ExtensionReviewService = class {
10790
11139
  function numberValue$12(value) {
10791
11140
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
10792
11141
  }
10793
- function booleanValue$4(value) {
11142
+ function booleanValue$5(value) {
10794
11143
  return value === true;
10795
11144
  }
10796
11145
  function listValue$13(value) {
@@ -11101,7 +11450,7 @@ var GroupAiPolishService = class {
11101
11450
  active_rule_uid: "",
11102
11451
  update_at: Date.now()
11103
11452
  }, session, options.signal);
11104
- if (booleanValue$4(objectValue$1(updated.config ?? updated).enabled)) throw new ArkmePluginError("group-ai-polish-disable-invalid", "关闭 AI 表达润色失败,请重试", true, 502);
11453
+ if (booleanValue$5(objectValue$1(updated.config ?? updated).enabled)) throw new ArkmePluginError("group-ai-polish-disable-invalid", "关闭 AI 表达润色失败,请重试", true, 502);
11105
11454
  this.invalidateAiPolishReadCache(session.userId, pending.chatSessionUid);
11106
11455
  this.aiPolishConfirmations.delete(confirmationRef.trim());
11107
11456
  return {
@@ -11248,7 +11597,7 @@ var GroupAiPolishService = class {
11248
11597
  const preview = objectValue$1(payload.ai_polish_preview ?? payload.aiPolishPreview ?? record.ai_polish_preview ?? record.aiPolishPreview);
11249
11598
  const originalText = stringValue$1(preview.original_text ?? preview.originalText);
11250
11599
  const polishedText = stringValue$1(preview.polished_text ?? preview.polishedText);
11251
- if (!(booleanValue$4(payload.has_polish ?? payload.hasPolish ?? record.has_polish ?? record.hasPolish) || originalText !== "" && polishedText !== "") || originalText === "" || polishedText === "") return void 0;
11600
+ if (!(booleanValue$5(payload.has_polish ?? payload.hasPolish ?? record.has_polish ?? record.hasPolish) || originalText !== "" && polishedText !== "") || originalText === "" || polishedText === "") return void 0;
11252
11601
  return {
11253
11602
  state: "polished",
11254
11603
  originalText,
@@ -11275,8 +11624,8 @@ var GroupAiPolishService = class {
11275
11624
  };
11276
11625
  }).filter((rule) => rule.ruleUid !== "" && rule.ruleText !== "");
11277
11626
  return {
11278
- enabled: booleanValue$4(config.enabled ?? config.is_enabled),
11279
- canManage: booleanValue$4(data.can_manage),
11627
+ enabled: booleanValue$5(config.enabled ?? config.is_enabled),
11628
+ canManage: booleanValue$5(data.can_manage),
11280
11629
  viewerRole: numberValue$12(data.viewer_role),
11281
11630
  activeRuleUid,
11282
11631
  activeRuleName: rules.find((rule) => rule.ruleUid === activeRuleUid)?.name ?? "",
@@ -11384,7 +11733,7 @@ function numberValue$11(value) {
11384
11733
  function listValue$12(value) {
11385
11734
  return Array.isArray(value) ? value : [];
11386
11735
  }
11387
- function encodeOpaqueJson$4(value) {
11736
+ function encodeOpaqueJson$5(value) {
11388
11737
  return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
11389
11738
  }
11390
11739
  function decodeOpaqueJson$4(value) {
@@ -11682,7 +12031,7 @@ var GroupService = class {
11682
12031
  };
11683
12032
  }
11684
12033
  async sealCandidateRef(payload) {
11685
- const encoded = encodeOpaqueJson$4(payload);
12034
+ const encoded = encodeOpaqueJson$5(payload);
11686
12035
  return `arkme-group-candidate-v1.${encoded}.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest("base64url")}`;
11687
12036
  }
11688
12037
  async openCandidateRef(candidateRef, expectedUserId, expectedGroupChatSessionUid) {
@@ -11924,7 +12273,7 @@ var GroupService = class {
11924
12273
  function numberValue$10(value) {
11925
12274
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
11926
12275
  }
11927
- function booleanValue$3(value) {
12276
+ function booleanValue$4(value) {
11928
12277
  return value === true;
11929
12278
  }
11930
12279
  function listValue$11(value) {
@@ -11952,7 +12301,7 @@ var InterwovenService = class {
11952
12301
  moments: [],
11953
12302
  preparedAtMillis: Date.now()
11954
12303
  };
11955
- if (!booleanValue$3((await this.runtime.authenticatedAuthPost("/api/v1/auth/able-func", { func_type: 12 }, session, signal)).able)) return {
12304
+ if (!booleanValue$4((await this.runtime.authenticatedAuthPost("/api/v1/auth/able-func", { func_type: 12 }, session, signal)).able)) return {
11956
12305
  state: "disabled",
11957
12306
  moments: [],
11958
12307
  preparedAtMillis: Date.now()
@@ -12007,7 +12356,7 @@ var InterwovenService = class {
12007
12356
  groupName,
12008
12357
  senderUserId,
12009
12358
  summary: stringValue$1(renderPayload.content ?? item.summary).trim().slice(0, 1e3),
12010
- degraded: booleanValue$3(item.is_degraded),
12359
+ degraded: booleanValue$4(item.is_degraded),
12011
12360
  sourceChatSessionUid,
12012
12361
  recordOwnerUserId,
12013
12362
  recordUid,
@@ -12083,7 +12432,7 @@ var InterwovenService = class {
12083
12432
  if (source.kind !== "private_chat") throw new ArkmePluginError("interwoven-source-invalid", "交织瞬间仅支持普通私聊", false, 400);
12084
12433
  const reference = await this.openInterwovenMomentRef(momentRef, session.userId, source.ownerRef);
12085
12434
  if (!this.runtime.config.interwovenMomentsEnabled) throw new ArkmePluginError("interwoven-disabled", "交织瞬间能力当前未开启", false, 403);
12086
- if (!booleanValue$3((await this.runtime.authenticatedAuthPost("/api/v1/auth/able-func", { func_type: 12 }, session, signal)).able)) throw new ArkmePluginError("interwoven-disabled", "交织瞬间能力当前未开放", false, 403);
12435
+ if (!booleanValue$4((await this.runtime.authenticatedAuthPost("/api/v1/auth/able-func", { func_type: 12 }, session, signal)).able)) throw new ArkmePluginError("interwoven-disabled", "交织瞬间能力当前未开放", false, 403);
12087
12436
  await this.assertHumanPrivateSource(source, session, signal);
12088
12437
  if (reference.detailMode === "owner_payload") return {
12089
12438
  momentId: reference.momentId,
@@ -12135,7 +12484,7 @@ var InterwovenService = class {
12135
12484
  }
12136
12485
  async resolveLegacyPrivateSubjectId(counterpartUserId, session, signal) {
12137
12486
  const data = await this.runtime.authenticatedSubjectPost("/api/v1/private/check-contact-chat", { target_user_id: counterpartUserId }, session, signal);
12138
- if (!booleanValue$3(data.exist)) return 0;
12487
+ if (!booleanValue$4(data.exist)) return 0;
12139
12488
  const rmSubjectId = Math.trunc(numberValue$10(data.rm_subject_id));
12140
12489
  if (!Number.isSafeInteger(rmSubjectId) || rmSubjectId <= 0) throw new ArkmePluginError("interwoven-subject-contract-invalid", "私聊交织主题定位响应不完整", true, 502);
12141
12490
  return rmSubjectId;
@@ -12179,6 +12528,365 @@ var InterwovenService = class {
12179
12528
  }
12180
12529
  };
12181
12530
  //#endregion
12531
+ //#region src/services/link-metadata-service.ts
12532
+ const MAX_LINK_METADATA_BYTES = 2 * 1024 * 1024;
12533
+ const LINK_METADATA_TIMEOUT_MS = 8e3;
12534
+ const LINK_METADATA_IDLE_TIMEOUT_MS = 4e3;
12535
+ const MAX_LINK_METADATA_REDIRECTS = 3;
12536
+ const MAX_LINK_TITLE_LENGTH = 300;
12537
+ const MAX_LINK_URL_LENGTH = 4096;
12538
+ const blockedAddresses = new BlockList();
12539
+ const globalIpv6Addresses = new BlockList();
12540
+ globalIpv6Addresses.addSubnet("2000::", 3, "ipv6");
12541
+ for (const [network, prefix] of [
12542
+ ["0.0.0.0", 8],
12543
+ ["10.0.0.0", 8],
12544
+ ["100.64.0.0", 10],
12545
+ ["127.0.0.0", 8],
12546
+ ["169.254.0.0", 16],
12547
+ ["172.16.0.0", 12],
12548
+ ["192.0.0.0", 24],
12549
+ ["192.0.2.0", 24],
12550
+ ["192.88.99.0", 24],
12551
+ ["192.168.0.0", 16],
12552
+ ["198.18.0.0", 15],
12553
+ ["198.51.100.0", 24],
12554
+ ["203.0.113.0", 24],
12555
+ ["224.0.0.0", 4],
12556
+ ["240.0.0.0", 4]
12557
+ ]) blockedAddresses.addSubnet(network, prefix, "ipv4");
12558
+ for (const [network, prefix] of [
12559
+ ["2001::", 23],
12560
+ ["2001:db8::", 32],
12561
+ ["2002::", 16],
12562
+ ["3fff::", 20]
12563
+ ]) blockedAddresses.addSubnet(network, prefix, "ipv6");
12564
+ function hostWithoutBrackets(hostname) {
12565
+ return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
12566
+ }
12567
+ function ipv4FromMappedIpv6(address) {
12568
+ return /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/iu.exec(address)?.[1];
12569
+ }
12570
+ function isPublicNetworkAddress(address) {
12571
+ const mapped = ipv4FromMappedIpv6(address);
12572
+ if (mapped !== void 0) return isIP(mapped) === 4 && !blockedAddresses.check(mapped, "ipv4");
12573
+ const family = isIP(address);
12574
+ if (family === 4) return !blockedAddresses.check(address, "ipv4");
12575
+ if (family === 6) return globalIpv6Addresses.check(address, "ipv6") && !blockedAddresses.check(address, "ipv6");
12576
+ return false;
12577
+ }
12578
+ function safeWebUrl(raw) {
12579
+ let url;
12580
+ try {
12581
+ url = raw instanceof URL ? new URL(raw.href) : new URL(raw);
12582
+ } catch (error) {
12583
+ throw new ArkmePluginError("link-metadata-url-invalid", "网址格式无效", false, 400, { cause: error });
12584
+ }
12585
+ const hostname = hostWithoutBrackets(url.hostname).toLowerCase();
12586
+ if (url.protocol !== "http:" && url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.href.length > MAX_LINK_URL_LENGTH || hostname === "" || hostname === "localhost" || hostname.endsWith(".localhost") || isIP(hostname) !== 0) throw new ArkmePluginError("link-metadata-url-unsafe", "该网址不允许解析标题", false, 400);
12587
+ return url;
12588
+ }
12589
+ function entityValue(value) {
12590
+ if (/^#x[\da-f]+$/iu.test(value)) return String.fromCodePoint(Number.parseInt(value.slice(2), 16));
12591
+ if (/^#\d+$/u.test(value)) return String.fromCodePoint(Number.parseInt(value.slice(1), 10));
12592
+ return {
12593
+ amp: "&",
12594
+ apos: "'",
12595
+ gt: ">",
12596
+ lt: "<",
12597
+ nbsp: " ",
12598
+ quot: "\""
12599
+ }[value.toLowerCase()] ?? `&${value};`;
12600
+ }
12601
+ function normalizedTitle(value) {
12602
+ return value.replace(/<[^>]*>/gu, " ").replace(/&(#x[\da-f]+|#\d+|amp|apos|gt|lt|nbsp|quot);/giu, (_match, entity) => {
12603
+ try {
12604
+ return entityValue(entity);
12605
+ } catch {
12606
+ return "";
12607
+ }
12608
+ }).replace(/[\u0000-\u001f\u007f\s]+/gu, " ").trim().slice(0, MAX_LINK_TITLE_LENGTH);
12609
+ }
12610
+ function tagAttributes(tag) {
12611
+ const attributes = /* @__PURE__ */ new Map();
12612
+ for (const match of tag.matchAll(/([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/gu)) {
12613
+ const name = match[1]?.toLowerCase();
12614
+ if (name !== void 0) attributes.set(name, match[2] ?? match[3] ?? match[4] ?? "");
12615
+ }
12616
+ return attributes;
12617
+ }
12618
+ function documentMetadata(url, html) {
12619
+ const metadata = /* @__PURE__ */ new Map();
12620
+ for (const match of html.matchAll(/<meta\b[^>]*>/giu)) {
12621
+ const attributes = tagAttributes(match[0]);
12622
+ const property = (attributes.get("property") ?? attributes.get("name") ?? "").toLowerCase();
12623
+ if (property === "" || metadata.has(property)) continue;
12624
+ const content = normalizedTitle(attributes.get("content") ?? "");
12625
+ if (content !== "") metadata.set(property, content);
12626
+ }
12627
+ const documentTitle = /<title\b[^>]*>([\s\S]*?)<\/title\s*>/iu.exec(html)?.[1];
12628
+ const title = metadata.get("og:title") ?? metadata.get("twitter:title") ?? (documentTitle === void 0 ? "" : normalizedTitle(documentTitle));
12629
+ if (title === "") return null;
12630
+ const description = metadata.get("og:description") ?? metadata.get("description");
12631
+ const siteName = metadata.get("og:site_name") ?? url.hostname.replace(/^www\./iu, "");
12632
+ return {
12633
+ url: url.href,
12634
+ title,
12635
+ ...description === void 0 ? {} : { description },
12636
+ ...siteName === "" ? {} : { siteName }
12637
+ };
12638
+ }
12639
+ function redirectStatus(status) {
12640
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
12641
+ }
12642
+ function isHtmlContentType(contentType) {
12643
+ return contentType === void 0 || /^text\/html\b|^application\/xhtml\+xml\b/iu.test(contentType);
12644
+ }
12645
+ function pluginError(error) {
12646
+ if (error instanceof ArkmePluginError) return error;
12647
+ return new ArkmePluginError("link-metadata-fetch-failed", "网址名称解析失败", true, 502, { cause: error });
12648
+ }
12649
+ function missingDnsAnswer(error) {
12650
+ const code = error !== null && typeof error === "object" && "code" in error ? String(error.code) : "";
12651
+ return code === "ENODATA" || code === "ENOTFOUND" || code === "ENONAME";
12652
+ }
12653
+ var NodeArkmeHostAddressResolver = class {
12654
+ resolverFactory;
12655
+ constructor(resolverFactory = () => new Resolver()) {
12656
+ this.resolverFactory = resolverFactory;
12657
+ }
12658
+ async lookup(hostname, options = {}) {
12659
+ options.signal?.throwIfAborted();
12660
+ const resolver = this.resolverFactory();
12661
+ const abort = () => {
12662
+ resolver.cancel();
12663
+ };
12664
+ options.signal?.addEventListener("abort", abort, { once: true });
12665
+ try {
12666
+ const [ipv4, ipv6] = await Promise.all([resolver.resolve4(hostname).catch((error) => {
12667
+ if (missingDnsAnswer(error)) return [];
12668
+ throw error;
12669
+ }), resolver.resolve6(hostname).catch((error) => {
12670
+ if (missingDnsAnswer(error)) return [];
12671
+ throw error;
12672
+ })]);
12673
+ options.signal?.throwIfAborted();
12674
+ return [...ipv4.map((address) => ({
12675
+ address,
12676
+ family: 4
12677
+ })), ...ipv6.map((address) => ({
12678
+ address,
12679
+ family: 6
12680
+ }))];
12681
+ } catch (error) {
12682
+ options.signal?.throwIfAborted();
12683
+ throw error;
12684
+ } finally {
12685
+ options.signal?.removeEventListener("abort", abort);
12686
+ }
12687
+ }
12688
+ };
12689
+ var NodeArkmePinnedDocumentTransport = class {
12690
+ maxBytes;
12691
+ idleTimeoutMs;
12692
+ constructor(options = {}) {
12693
+ this.maxBytes = Math.max(1, Math.trunc(options.maxBytes ?? MAX_LINK_METADATA_BYTES));
12694
+ this.idleTimeoutMs = Math.max(1, Math.trunc(options.idleTimeoutMs ?? LINK_METADATA_IDLE_TIMEOUT_MS));
12695
+ }
12696
+ async read(url, addresses, options = {}) {
12697
+ const address = addresses[0];
12698
+ if (address === void 0) throw new ArkmePluginError("link-metadata-fetch-failed", "网址名称解析失败", true, 502);
12699
+ const pinnedLookup = (_requestedHost, lookupOptions, callback) => {
12700
+ if (lookupOptions.all === true) callback(null, [...addresses]);
12701
+ else callback(null, address.address, address.family);
12702
+ };
12703
+ const request$2 = url.protocol === "https:" ? request$1 : request;
12704
+ return await new Promise((resolve, reject) => {
12705
+ let settled = false;
12706
+ const finish = (error, value) => {
12707
+ if (settled) return;
12708
+ settled = true;
12709
+ if (error !== void 0) reject(pluginError(error));
12710
+ else if (value !== void 0) resolve(value);
12711
+ };
12712
+ const req = request$2(url, {
12713
+ method: "GET",
12714
+ lookup: pinnedLookup,
12715
+ signal: options.signal,
12716
+ headers: {
12717
+ Accept: "text/html,application/xhtml+xml;q=0.9,*/*;q=0.1",
12718
+ "Accept-Encoding": "identity",
12719
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
12720
+ }
12721
+ }, (response) => {
12722
+ const chunks = [];
12723
+ let bytes = 0;
12724
+ let headProbe = "";
12725
+ const status = response.statusCode ?? 0;
12726
+ const contentType = typeof response.headers["content-type"] === "string" ? response.headers["content-type"] : void 0;
12727
+ const location = typeof response.headers.location === "string" ? response.headers.location : void 0;
12728
+ const complete = () => finish(void 0, {
12729
+ status,
12730
+ ...contentType === void 0 ? {} : { contentType },
12731
+ ...location === void 0 ? {} : { location },
12732
+ body: Buffer.concat(chunks).toString("utf8")
12733
+ });
12734
+ if (redirectStatus(status) || status < 200 || status >= 300 || !isHtmlContentType(contentType)) {
12735
+ complete();
12736
+ response.destroy();
12737
+ return;
12738
+ }
12739
+ response.on("data", (chunk) => {
12740
+ if (settled) return;
12741
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
12742
+ bytes += buffer.byteLength;
12743
+ if (bytes > this.maxBytes) {
12744
+ finish(new ArkmePluginError("link-metadata-response-too-large", "网址内容过大,无法解析名称", false, 413));
12745
+ response.destroy();
12746
+ return;
12747
+ }
12748
+ chunks.push(buffer);
12749
+ const lowerChunk = buffer.toString("utf8").toLowerCase();
12750
+ if (`${headProbe}${lowerChunk}`.includes("</head>")) {
12751
+ complete();
12752
+ response.destroy();
12753
+ }
12754
+ headProbe = lowerChunk.slice(-6);
12755
+ });
12756
+ response.once("end", () => {
12757
+ if (response.complete) complete();
12758
+ else finish(/* @__PURE__ */ new Error("link metadata response ended before completion"));
12759
+ });
12760
+ response.once("close", () => {
12761
+ if (!settled && response.complete) complete();
12762
+ else if (!settled) finish(/* @__PURE__ */ new Error("link metadata response closed before completion"));
12763
+ });
12764
+ response.once("aborted", () => {
12765
+ if (!settled) finish(/* @__PURE__ */ new Error("link metadata response aborted"));
12766
+ });
12767
+ response.once("error", (error) => {
12768
+ if (!settled) finish(error);
12769
+ });
12770
+ });
12771
+ req.setTimeout(this.idleTimeoutMs, () => {
12772
+ req.destroy(new ArkmePluginError("link-metadata-idle-timeout", "网址名称解析超时", true, 504));
12773
+ });
12774
+ req.once("error", (error) => finish(error));
12775
+ req.end();
12776
+ });
12777
+ }
12778
+ };
12779
+ var NodeArkmeLinkDocumentReader = class {
12780
+ resolver;
12781
+ transport;
12782
+ constructor(resolver = new NodeArkmeHostAddressResolver(), transport = new NodeArkmePinnedDocumentTransport()) {
12783
+ this.resolver = resolver;
12784
+ this.transport = transport;
12785
+ }
12786
+ async read(url, options = {}) {
12787
+ const hostname = hostWithoutBrackets(url.hostname);
12788
+ let addresses;
12789
+ try {
12790
+ addresses = await this.resolver.lookup(hostname, options);
12791
+ } catch (error) {
12792
+ throw pluginError(error);
12793
+ }
12794
+ if (addresses.length === 0 || addresses.some((item) => !isPublicNetworkAddress(item.address))) throw new ArkmePluginError("link-metadata-url-unsafe", "该网址不允许解析标题", false, 400);
12795
+ return await this.transport.read(url, addresses, options);
12796
+ }
12797
+ };
12798
+ var ArkmeLinkMetadataService = class {
12799
+ reader;
12800
+ lifecycle = new AbortController();
12801
+ timeoutMs;
12802
+ coordinator;
12803
+ constructor(reader = new NodeArkmeLinkDocumentReader(), options = {}) {
12804
+ this.reader = reader;
12805
+ const maxConcurrent = Math.max(1, Math.trunc(options.maxConcurrent ?? 4));
12806
+ const maxQueue = Math.max(1, Math.trunc(options.maxQueue ?? 32));
12807
+ const burst = maxConcurrent + maxQueue;
12808
+ this.timeoutMs = Math.max(1, Math.trunc(options.timeoutMs ?? LINK_METADATA_TIMEOUT_MS));
12809
+ this.coordinator = new ArkmeRequestCoordinator({
12810
+ laneLimits: { "background-read": {
12811
+ maxConcurrent,
12812
+ maxQueued: maxQueue,
12813
+ burst,
12814
+ ratePerSecond: burst
12815
+ } },
12816
+ serviceLimits: { other: {
12817
+ maxConcurrent,
12818
+ maxQueued: maxQueue,
12819
+ burst,
12820
+ ratePerSecond: burst
12821
+ } }
12822
+ });
12823
+ }
12824
+ async resolve(rawUrl, options = {}) {
12825
+ if (this.lifecycle.signal.aborted) throw new ArkmePluginError("link-metadata-disposed", "网址名称解析服务已停止", true, 503);
12826
+ const initialUrl = safeWebUrl(rawUrl);
12827
+ initialUrl.hash = "";
12828
+ const deadlineAt = Date.now() + this.timeoutMs;
12829
+ const deadline = new AbortController();
12830
+ const deadlineTimer = setTimeout(() => {
12831
+ deadline.abort(this.timeoutError());
12832
+ }, this.timeoutMs);
12833
+ const signal = AbortSignal.any([
12834
+ this.lifecycle.signal,
12835
+ deadline.signal,
12836
+ ...options.signal === void 0 ? [] : [options.signal]
12837
+ ]);
12838
+ try {
12839
+ return await this.coordinator.run({
12840
+ scope: "link-metadata",
12841
+ lane: "background-read",
12842
+ service: "other",
12843
+ signal,
12844
+ operation: async (operationSignal) => {
12845
+ if (Date.now() >= deadlineAt) throw this.timeoutError();
12846
+ return await this.load(initialUrl, operationSignal);
12847
+ }
12848
+ });
12849
+ } catch {
12850
+ if (this.lifecycle.signal.aborted) throw this.lifecycle.signal.reason;
12851
+ return null;
12852
+ } finally {
12853
+ clearTimeout(deadlineTimer);
12854
+ }
12855
+ }
12856
+ dispose() {
12857
+ this.lifecycle.abort(new ArkmePluginError("link-metadata-disposed", "网址名称解析服务已停止", true, 503));
12858
+ this.coordinator.dispose();
12859
+ }
12860
+ timeoutError() {
12861
+ return new ArkmePluginError("link-metadata-timeout", "网址名称解析超时", true, 504);
12862
+ }
12863
+ async load(initialUrl, signal) {
12864
+ let url = initialUrl;
12865
+ for (let redirect = 0; redirect <= MAX_LINK_METADATA_REDIRECTS; redirect += 1) {
12866
+ let document;
12867
+ try {
12868
+ document = await this.reader.read(url, { signal });
12869
+ } catch (error) {
12870
+ throw pluginError(error);
12871
+ }
12872
+ if (redirectStatus(document.status) && document.location !== void 0) {
12873
+ if (redirect === MAX_LINK_METADATA_REDIRECTS) throw new ArkmePluginError("link-metadata-redirect-limit", "网址跳转次数过多", false, 400);
12874
+ try {
12875
+ url = safeWebUrl(new URL(document.location, url));
12876
+ } catch (error) {
12877
+ if (error instanceof ArkmePluginError && error.code === "link-metadata-url-unsafe") throw error;
12878
+ throw new ArkmePluginError("link-metadata-redirect-invalid", "网址跳转地址无效", false, 400, { cause: error });
12879
+ }
12880
+ continue;
12881
+ }
12882
+ if (document.status < 200 || document.status >= 300) return null;
12883
+ if (!isHtmlContentType(document.contentType)) return null;
12884
+ return documentMetadata(url, document.body);
12885
+ }
12886
+ return null;
12887
+ }
12888
+ };
12889
+ //#endregion
12182
12890
  //#region src/services/media-service.ts
12183
12891
  const MAX_ARKME_IMAGE_BYTES = 2 * 1024 * 1024;
12184
12892
  const MAX_ARKME_PROFILE_IMAGE_BYTES = 8 * 1024 * 1024;
@@ -13338,7 +14046,7 @@ const ARKME_STAFF_ACCOUNT_TYPE = 2;
13338
14046
  function numberValue$7(value) {
13339
14047
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
13340
14048
  }
13341
- function booleanValue$2(value) {
14049
+ function booleanValue$3(value) {
13342
14050
  return value === true;
13343
14051
  }
13344
14052
  function optionalBooleanValue(value) {
@@ -13352,7 +14060,7 @@ function chunksOf(values, size) {
13352
14060
  for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
13353
14061
  return chunks;
13354
14062
  }
13355
- function encodeOpaqueJson$3(value) {
14063
+ function encodeOpaqueJson$4(value) {
13356
14064
  return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
13357
14065
  }
13358
14066
  function decodeOpaqueJson$3(value) {
@@ -13588,9 +14296,9 @@ var ProfileService = class {
13588
14296
  accountType: numberValue$7(data.type),
13589
14297
  createdAt: numberValue$7(data.create_at),
13590
14298
  bindings: {
13591
- apple: booleanValue$2(data.has_bind_apple),
13592
- wechat: booleanValue$2(data.has_bind_wechat),
13593
- google: booleanValue$2(data.has_bind_google)
14299
+ apple: booleanValue$3(data.has_bind_apple),
14300
+ wechat: booleanValue$3(data.has_bind_wechat),
14301
+ google: booleanValue$3(data.has_bind_google)
13594
14302
  },
13595
14303
  ...wechatName === "" ? {} : { bindingNames: { wechat: wechatName } },
13596
14304
  contact: {
@@ -13721,7 +14429,7 @@ var ProfileService = class {
13721
14429
  return profiles;
13722
14430
  }
13723
14431
  async sealProfileImageRef(viewerUserId, targetUserId) {
13724
- const payload = encodeOpaqueJson$3({
14432
+ const payload = encodeOpaqueJson$4({
13725
14433
  version: 1,
13726
14434
  viewerUserId,
13727
14435
  targetUserId
@@ -14522,7 +15230,7 @@ function speakerUserIds(speakerData) {
14522
15230
  return userId === void 0 ? [] : [userId];
14523
15231
  }))];
14524
15232
  }
14525
- function encodeOpaqueJson$2(value) {
15233
+ function encodeOpaqueJson$3(value) {
14526
15234
  return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
14527
15235
  }
14528
15236
  function decodeOpaqueJson$2(value) {
@@ -14609,7 +15317,7 @@ var RecordingService = class {
14609
15317
  }
14610
15318
  async sealRecordingCursor(payload) {
14611
15319
  const session = await this.runtime.requireSession();
14612
- const encoded = encodeOpaqueJson$2(payload);
15320
+ const encoded = encodeOpaqueJson$3(payload);
14613
15321
  return `arkme-recording-cursor-v1.${encoded}.${createHmac("sha256", await this.recordingCursorKey(session.userId)).update(encoded).digest("base64url")}`;
14614
15322
  }
14615
15323
  async openRecordingCursor(cursor) {
@@ -14724,7 +15432,17 @@ const MAX_ARKME_RELATED_RECORDING_CURSOR_LENGTH = 1024;
14724
15432
  const MAX_ARKME_TIMEZONE_OFFSET_MILLIS = 840 * 60 * 1e3;
14725
15433
  const RELATED_RECORDINGS_FUNC_TYPE = 17;
14726
15434
  function numberValue$3(value) {
14727
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
15435
+ if (typeof value === "number" && Number.isFinite(value)) return Math.trunc(value);
15436
+ if (typeof value === "string" && value.trim() !== "") {
15437
+ const parsed = Number(value.trim());
15438
+ if (Number.isFinite(parsed)) return Math.trunc(parsed);
15439
+ }
15440
+ return 0;
15441
+ }
15442
+ function booleanValue$2(value) {
15443
+ if (value === true || value === 1) return true;
15444
+ if (typeof value === "string") return ["1", "true"].includes(value.trim().toLowerCase());
15445
+ return false;
14728
15446
  }
14729
15447
  function listValue$4(value) {
14730
15448
  return Array.isArray(value) ? value : [];
@@ -14737,6 +15455,21 @@ function optionalString$1(value) {
14737
15455
  const text = stringValue$1(value).trim();
14738
15456
  return text === "" ? void 0 : text;
14739
15457
  }
15458
+ function firstOptionalString(source, keys) {
15459
+ for (const key of keys) {
15460
+ const value = optionalString$1(source[key]);
15461
+ if (value !== void 0) return value;
15462
+ }
15463
+ }
15464
+ function encodeOpaqueJson$2(value) {
15465
+ return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
15466
+ }
15467
+ function participantRoleName(role) {
15468
+ if (role === 2) return "我";
15469
+ if (role === 1) return "对方";
15470
+ if (role === 4) return "其他说话人";
15471
+ return "";
15472
+ }
14740
15473
  var RelatedRecordingService = class {
14741
15474
  runtime;
14742
15475
  source;
@@ -14797,23 +15530,24 @@ var RelatedRecordingService = class {
14797
15530
  return source;
14798
15531
  }
14799
15532
  async loadRelatedRecordingEligibility(session, signal) {
14800
- return (await this.runtime.authenticatedAuthPost("/api/v1/auth/able-func", { func_type: RELATED_RECORDINGS_FUNC_TYPE }, session, signal)).able === true;
15533
+ return booleanValue$2((await this.runtime.authenticatedAuthPost("/api/v1/auth/able-func", { func_type: RELATED_RECORDINGS_FUNC_TYPE }, session, signal)).able);
14801
15534
  }
14802
15535
  async relatedRecordingPage(raw, legacyTimeIndexFallback, userId, chatSessionUid) {
14803
15536
  const items = [];
14804
- for (const rawItem of listValue$4(raw.moment_ls)) {
14805
- const item = await this.relatedRecordingItem(rawItem, userId, chatSessionUid);
15537
+ const signingKey = await this.runtime.stateStore.uniqueCode();
15538
+ for (const rawItem of listValue$4(raw.moment_ls ?? raw.momentLs ?? raw.items)) {
15539
+ const item = this.relatedRecordingItem(rawItem, userId, chatSessionUid, signingKey);
14806
15540
  if (item !== void 0) items.push(item);
14807
15541
  }
14808
- const partial = raw.partial === true;
15542
+ const partial = booleanValue$2(raw.partial);
14809
15543
  const stateCode = numberValue$3(raw.state);
14810
15544
  const state = partial ? items.length > 0 ? "partial" : "error" : items.length > 0 ? "success" : stateCode === 2 ? "generating" : stateCode === 4 ? "error" : "empty";
14811
- const nextCursor = stringValue$1(raw.next_cursor).trim();
14812
- const timeIndexComplete = raw.time_index_complete === true && !legacyTimeIndexFallback;
14813
- const monthBuckets = timeIndexComplete ? listValue$4(raw.month_bucket_ls).flatMap((value) => {
15545
+ const nextCursor = stringValue$1(raw.next_cursor ?? raw.nextCursor).trim();
15546
+ const timeIndexComplete = booleanValue$2(raw.time_index_complete ?? raw.timeIndexComplete) && !legacyTimeIndexFallback;
15547
+ const monthBuckets = timeIndexComplete ? listValue$4(raw.month_bucket_ls ?? raw.monthBucketLs ?? raw.monthBuckets).flatMap((value) => {
14814
15548
  const bucket = objectValue$1(value);
14815
- const monthKey = stringValue$1(bucket.month_key).trim();
14816
- const itemCount = numberValue$3(bucket.item_count);
15549
+ const monthKey = stringValue$1(bucket.month_key ?? bucket.monthKey).trim();
15550
+ const itemCount = numberValue$3(bucket.item_count ?? bucket.itemCount);
14817
15551
  return /^\d{4}-(0[1-9]|1[0-2])$/.test(monthKey) && Number.isInteger(itemCount) && itemCount >= 0 ? [{
14818
15552
  monthKey,
14819
15553
  itemCount
@@ -14822,71 +15556,156 @@ var RelatedRecordingService = class {
14822
15556
  return {
14823
15557
  state,
14824
15558
  stateCode,
14825
- stateMessage: stringValue$1(raw.state_msg).trim(),
14826
- hasEntry: raw.has_entry === true,
15559
+ stateMessage: stringValue$1(raw.state_msg ?? raw.stateMessage).trim(),
15560
+ hasEntry: booleanValue$2(raw.has_entry ?? raw.hasEntry),
14827
15561
  items,
14828
- hasMore: raw.has_more === true && nextCursor !== "",
14829
- ...raw.has_more === true && nextCursor !== "" ? { nextCursor } : {},
15562
+ hasMore: booleanValue$2(raw.has_more ?? raw.hasMore) && nextCursor !== "",
15563
+ ...booleanValue$2(raw.has_more ?? raw.hasMore) && nextCursor !== "" ? { nextCursor } : {},
14830
15564
  partial,
14831
15565
  ...timeIndexComplete ? { monthBuckets } : {},
14832
15566
  timeIndexComplete,
14833
15567
  legacyTimeIndexFallback
14834
15568
  };
14835
15569
  }
14836
- async relatedRecordingItem(raw, userId, chatSessionUid) {
15570
+ relatedRecordingItem(raw, userId, chatSessionUid, signingKey) {
14837
15571
  const item = objectValue$1(raw);
14838
- const momentId = stringValue$1(item.moment_id).trim();
14839
- const startAtMillis = numberValue$3(item.start_at);
15572
+ const momentId = stringValue$1(item.moment_id ?? item.momentId).trim();
15573
+ const startAtMillis = numberValue$3(item.start_at ?? item.startAt ?? item.startAtMillis);
14840
15574
  if (momentId === "" || !Number.isSafeInteger(startAtMillis) || startAtMillis <= 0) return void 0;
14841
- const transcript = stringValue$1(item.transcript);
14842
- const speakers = listValue$4(item.speaker_ls).flatMap((value) => {
15575
+ const transcript = stringValue$1(item.transcript ?? item.transcript_text ?? item.transcriptText);
15576
+ const speakers = listValue$4(item.speaker_ls ?? item.speakerLs ?? item.speakers).flatMap((value, index) => {
15577
+ if (typeof value === "string") {
15578
+ const nickname = value.trim();
15579
+ return nickname === "" ? [] : [{
15580
+ speakerId: `speaker:${index}`,
15581
+ nickname
15582
+ }];
15583
+ }
14843
15584
  const speaker = objectValue$1(value);
14844
- const speakerId = stringValue$1(speaker.speaker_id).trim();
14845
- if (speakerId === "") return [];
14846
- const refUserId = optionalPositiveNumber$1(speaker.ref_usr_id);
14847
- const nickname = optionalString$1(speaker.nick_name);
15585
+ const speakerId = firstOptionalString(speaker, [
15586
+ "speaker_id",
15587
+ "speakerId",
15588
+ "id"
15589
+ ]) ?? "";
15590
+ const refUserId = optionalPositiveNumber$1(speaker.ref_usr_id ?? speaker.refUsrId ?? speaker.ref_user_id ?? speaker.refUserId);
15591
+ const nickname = firstOptionalString(speaker, [
15592
+ "nick_name",
15593
+ "nickName",
15594
+ "nickname",
15595
+ "speaker_name",
15596
+ "speakerName",
15597
+ "display_name",
15598
+ "displayName",
15599
+ "name"
15600
+ ]);
15601
+ if (speakerId === "" && nickname === void 0 && refUserId === void 0) return [];
14848
15602
  return [{
14849
15603
  speakerId,
14850
15604
  ...refUserId === void 0 ? {} : { refUserId },
14851
15605
  ...nickname === void 0 ? {} : { nickname }
14852
15606
  }];
14853
15607
  });
14854
- const participants = listValue$4(item.participant_ls).flatMap((value) => {
15608
+ const participantValues = listValue$4(item.participant_ls ?? item.participantLs ?? item.participants);
15609
+ const participants = (participantValues.length > 0 ? participantValues : listValue$4(item.speaker_ls ?? item.speakerLs ?? item.speakers)).flatMap((value, index) => {
15610
+ if (typeof value === "string") {
15611
+ const displayName = value.trim();
15612
+ return displayName === "" ? [] : [{
15613
+ speakerId: `participant:${index}`,
15614
+ displayName,
15615
+ role: 0
15616
+ }];
15617
+ }
14855
15618
  const participant = objectValue$1(value);
14856
- const speakerId = stringValue$1(participant.speaker_id).trim();
14857
- const nickname = optionalString$1(participant.nick_name);
14858
- const displayName = stringValue$1(participant.display_name).trim() || nickname || "";
14859
- if (speakerId === "" || displayName === "") return [];
14860
- const refUserId = optionalPositiveNumber$1(participant.ref_usr_id);
15619
+ const role = Math.max(0, numberValue$3(participant.role));
15620
+ const speakerId = firstOptionalString(participant, [
15621
+ "speaker_id",
15622
+ "speakerId",
15623
+ "id"
15624
+ ]) ?? "";
15625
+ const nickname = firstOptionalString(participant, [
15626
+ "nick_name",
15627
+ "nickName",
15628
+ "nickname",
15629
+ "speaker_name",
15630
+ "speakerName",
15631
+ "name"
15632
+ ]);
15633
+ const displayName = firstOptionalString(participant, [
15634
+ "display_name",
15635
+ "displayName",
15636
+ "display_name_snapshot",
15637
+ "displayNameSnapshot"
15638
+ ]) ?? nickname ?? participantRoleName(role);
15639
+ const refUserId = optionalPositiveNumber$1(participant.ref_usr_id ?? participant.refUsrId ?? participant.ref_user_id ?? participant.refUserId ?? participant.user_id ?? participant.userId);
15640
+ if (displayName === "" && speakerId === "" && refUserId === void 0) return [];
14861
15641
  return [{
14862
- speakerId,
15642
+ speakerId: speakerId === "" ? `participant:${index}` : speakerId,
14863
15643
  ...refUserId === void 0 ? {} : { refUserId },
14864
15644
  ...nickname === void 0 ? {} : { nickname },
14865
15645
  displayName,
14866
- role: numberValue$3(participant.role)
15646
+ role
14867
15647
  }];
14868
15648
  });
14869
- const dateStamp = optionalPositiveNumber$1(item.date_stamp);
14870
- const timezoneOffsetMillis = numberValue$3(item.tz_offset);
15649
+ const dateStamp = optionalPositiveNumber$1(item.date_stamp ?? item.dateStamp);
15650
+ const timezoneOffsetMillis = numberValue$3(item.tz_offset ?? item.tzOffset ?? item.timezone_offset ?? item.timezoneOffset ?? item.timezoneOffsetMillis);
15651
+ const isSharedByOther = booleanValue$2(item.is_shared_by_other ?? item.isSharedByOther);
15652
+ const sharedByUserId = optionalPositiveNumber$1(item.shared_by_user_id ?? item.sharedByUserId);
15653
+ const itemChatSessionUid = firstOptionalString(item, [
15654
+ "chat_session_uid",
15655
+ "chatSessionUid",
15656
+ "source_chat_session_uid",
15657
+ "sourceChatSessionUid"
15658
+ ]) ?? chatSessionUid;
15659
+ const recordUid = firstOptionalString(item, [
15660
+ "record_uid",
15661
+ "recordUid",
15662
+ "shared_record_uid",
15663
+ "sharedRecordUid"
15664
+ ]) ?? "";
15665
+ const recordOwnerUserId = optionalPositiveNumber$1(item.record_owner_user_id ?? item.recordOwnerUserId ?? item.owner_user_id ?? item.ownerUserId) ?? sharedByUserId;
15666
+ const sourceRelationUid = firstOptionalString(item, [
15667
+ "source_relation_uid",
15668
+ "sourceRelationUid",
15669
+ "rel_uid",
15670
+ "relUid",
15671
+ "relation_uid",
15672
+ "relationUid"
15673
+ ]) ?? "";
15674
+ const sharedSequence = numberValue$3(item.shared_sequence ?? item.sharedSequence ?? item.seq ?? item.sequence);
15675
+ const sharedRecordingDetailRef = isSharedByOther && itemChatSessionUid.trim() !== "" && recordUid.trim() !== "" && recordOwnerUserId !== void 0 && recordOwnerUserId > 0 ? this.sharedRecordingDetailRef({
15676
+ version: 1,
15677
+ viewerUserId: userId,
15678
+ chatSessionUid: itemChatSessionUid.trim(),
15679
+ relationUid: sourceRelationUid.trim(),
15680
+ recordOwnerUserId,
15681
+ recordUid: recordUid.trim(),
15682
+ sequence: Math.max(0, sharedSequence)
15683
+ }, signingKey) : void 0;
14871
15684
  return {
14872
- recordingRef: await this.relatedRecordingRef(userId, chatSessionUid, momentId),
15685
+ recordingRef: this.relatedRecordingRef(userId, chatSessionUid, momentId, signingKey),
15686
+ ...sharedRecordingDetailRef === void 0 ? {} : { sharedRecordingDetailRef },
14873
15687
  startAtMillis,
14874
- endAtMillis: numberValue$3(item.end_at),
15688
+ endAtMillis: numberValue$3(item.end_at ?? item.endAt ?? item.endAtMillis),
14875
15689
  ...dateStamp === void 0 ? {} : { dateStamp },
14876
15690
  ...Number.isSafeInteger(timezoneOffsetMillis) ? { timezoneOffsetMillis } : {},
14877
- timeRangeText: stringValue$1(item.time_range_text).trim(),
14878
- title: stringValue$1(item.title).trim(),
14879
- summary: stringValue$1(item.summary).trim(),
14880
- summaryStatus: numberValue$3(item.summary_status),
15691
+ timeRangeText: stringValue$1(item.time_range_text ?? item.timeRangeText).trim(),
15692
+ title: stringValue$1(item.title ?? item.orig_name ?? item.origName).trim(),
15693
+ summary: stringValue$1(item.summary ?? item.summary_text ?? item.summaryText).trim(),
15694
+ summaryStatus: numberValue$3(item.summary_status ?? item.summaryStatus),
14881
15695
  ...transcript === "" ? {} : { transcript },
14882
- transcriptAvailable: item.transcript_available === true && transcript !== "",
15696
+ transcriptAvailable: booleanValue$2(item.transcript_available ?? item.transcriptAvailable) && transcript !== "",
14883
15697
  speakers,
14884
15698
  participants,
14885
- isSharedByOther: item.is_shared_by_other === true
15699
+ isSharedByOther,
15700
+ ...sharedByUserId === void 0 ? {} : { sharedByUserId }
14886
15701
  };
14887
15702
  }
14888
- async relatedRecordingRef(userId, chatSessionUid, momentId) {
14889
- return `arkme-related-recording-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`related-recording:${userId}:${chatSessionUid}:${momentId}`).digest("base64url")}`;
15703
+ relatedRecordingRef(userId, chatSessionUid, momentId, signingKey) {
15704
+ return `arkme-related-recording-v1.${createHmac("sha256", signingKey).update(`related-recording:${userId}:${chatSessionUid}:${momentId}`).digest("base64url")}`;
15705
+ }
15706
+ sharedRecordingDetailRef(payload, signingKey) {
15707
+ const encoded = encodeOpaqueJson$2(payload);
15708
+ return `arkme-shared-recording-detail-v1.${encoded}.${createHmac("sha256", signingKey).update(encoded).digest("base64url")}`;
14890
15709
  }
14891
15710
  };
14892
15711
  function isDshAgentInputSourceTitle(value) {
@@ -18496,6 +19315,7 @@ var ArkmeService = class {
18496
19315
  record;
18497
19316
  search;
18498
19317
  bot;
19318
+ botConversation;
18499
19319
  outgoingCall;
18500
19320
  world;
18501
19321
  arko;
@@ -18504,6 +19324,7 @@ var ArkmeService = class {
18504
19324
  community;
18505
19325
  realtime;
18506
19326
  interwoven;
19327
+ linkMetadata;
18507
19328
  aiPolish;
18508
19329
  chat;
18509
19330
  contact;
@@ -18513,7 +19334,7 @@ var ArkmeService = class {
18513
19334
  fileTransfers;
18514
19335
  localFileOpener;
18515
19336
  worldVoiceprintInviteVariantIndex = 0;
18516
- constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker(), billingGateway) {
19337
+ constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker(), billingGateway, linkDocumentReader) {
18517
19338
  this.config = config;
18518
19339
  this.sessionStore = sessionStore;
18519
19340
  this.stateStore = stateStore;
@@ -18551,9 +19372,13 @@ var ArkmeService = class {
18551
19372
  this.relatedRecording = new RelatedRecordingService(this.runtime, this.source);
18552
19373
  this.community = new CommunityService(this.runtime, this.source, this.profile);
18553
19374
  this.interwoven = new InterwovenService(this.runtime, this.source, this.profile);
19375
+ this.linkMetadata = new ArkmeLinkMetadataService(linkDocumentReader);
18554
19376
  this.aiPolish = new GroupAiPolishService(this.runtime, this.source, { sendChatSourceTextRaw: async (...args) => await this.chat.sendChatSourceTextRaw(...args) });
18555
19377
  this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session, chatSessionUid) => await this.chat.chatTimelineItems(data, session, chatSessionUid) });
18556
19378
  this.chat = new ChatService(this.runtime, this.source, this.profile, this.media, this.record, this.bot, this.arko, this.aiPolish, this.realtime, this.privacy);
19379
+ this.botConversation = new BotConversationService(this.runtime, this.bot, this.chat, async () => {
19380
+ await this.realtime.invalidateRecordProjection();
19381
+ });
18557
19382
  this.contactDirectory = new ContactDirectoryService(this.runtime, this.source, this.bot, this.profile, this.world, this.chat);
18558
19383
  this.unmarkedSpeaker = new UnmarkedSpeakerService(this.runtime, this.media);
18559
19384
  this.contact = new ContactService(this.runtime, this.source, this.profile, this.realtime);
@@ -18679,9 +19504,6 @@ var ArkmeService = class {
18679
19504
  async listBots(options = {}) {
18680
19505
  return await this.bot.listBots(options);
18681
19506
  }
18682
- async listBotPrivateChatDirectory(options = {}) {
18683
- return await this.bot.listBotPrivateChatDirectory(options);
18684
- }
18685
19507
  async createBot(input, options = {}) {
18686
19508
  return await this.bot.createBot(input, options);
18687
19509
  }
@@ -18704,21 +19526,28 @@ var ArkmeService = class {
18704
19526
  await this.bot.deleteManagedBot(botRef, confirmationName, options);
18705
19527
  }
18706
19528
  async botNotificationPreference(botRef, options = {}) {
18707
- return await this.bot.botNotificationPreference(botRef, options);
19529
+ return await this.botConversation.notificationPreference(botRef, options);
18708
19530
  }
18709
19531
  async updateBotNotificationPreference(botRef, muted, options = {}) {
18710
- return await this.bot.updateBotNotificationPreference(botRef, muted, options);
19532
+ return await this.botConversation.updateNotificationPreference(botRef, muted, options);
18711
19533
  }
18712
19534
  async openBotChat(botRef, options = {}) {
18713
19535
  return await this.bot.openBotChat(botRef, options);
18714
19536
  }
19537
+ async listBotPrivateChatDirectory(options = {}) {
19538
+ return await this.botConversation.directory(options);
19539
+ }
18715
19540
  async openBotPrivateChat(botRef, options = {}) {
18716
- return await this.bot.openBotPrivateChat(botRef, options);
19541
+ return await this.botConversation.open(botRef, options);
19542
+ }
19543
+ async refreshBotPrivateChat(botRef, options = {}) {
19544
+ return await this.botConversation.refresh(botRef, options);
18717
19545
  }
18718
19546
  async sendBotPrivateChatMessage(botRef, content, options = {}) {
18719
- const outcome = await this.bot.sendBotPrivateChatMessage(botRef, content, options);
18720
- if (outcome.recordProjectionChanged) await this.realtime.invalidateRecordProjection();
18721
- return outcome.result;
19547
+ return await this.botConversation.send(botRef, content, options);
19548
+ }
19549
+ async markBotPrivateChatRead(botRef, sequence, options = {}) {
19550
+ return await this.botConversation.markRead(botRef, sequence, options);
18722
19551
  }
18723
19552
  async listGroupBots(groupSourceRef, options = {}) {
18724
19553
  return await this.bot.listGroupBots(groupSourceRef, options);
@@ -18735,6 +19564,12 @@ var ArkmeService = class {
18735
19564
  async authStatus() {
18736
19565
  return await this.auth.authStatus();
18737
19566
  }
19567
+ async dshRemoteGet(path, signal) {
19568
+ return await this.runtime.authenticatedDshRemoteGet(path, signal);
19569
+ }
19570
+ async dshRemotePost(path, body, signal) {
19571
+ return await this.runtime.authenticatedDshRemotePost(path, body, void 0, signal);
19572
+ }
18738
19573
  clientConfig() {
18739
19574
  return {
18740
19575
  captchaId: this.config.geetestCaptchaId,
@@ -18889,10 +19724,14 @@ var ArkmeService = class {
18889
19724
  this.outgoingCall.dispose();
18890
19725
  this.interwoven.dispose();
18891
19726
  this.world.dispose();
19727
+ this.linkMetadata.dispose();
18892
19728
  }
18893
19729
  requestStats() {
18894
19730
  return this.runtime.requestStats();
18895
19731
  }
19732
+ async resolveLinkMetadata(url, options = {}) {
19733
+ return await this.linkMetadata.resolve(url, options);
19734
+ }
18896
19735
  async cachedProfile() {
18897
19736
  return await this.profile.cachedProfile();
18898
19737
  }
@@ -19170,6 +20009,9 @@ var ArkmeService = class {
19170
20009
  async readSource(sourceRef, options = {}) {
19171
20010
  return await this.chat.readSource(sourceRef, options);
19172
20011
  }
20012
+ async sharedRecordingDetail(detailRef, options = {}) {
20013
+ return await this.chat.sharedRecordingDetail(detailRef, options);
20014
+ }
19173
20015
  async messageReadReceiptSummaries(sourceRef, items, options = {}) {
19174
20016
  return await this.chat.messageReadReceiptSummaries(sourceRef, items, options);
19175
20017
  }
@@ -19197,9 +20039,6 @@ var ArkmeService = class {
19197
20039
  async extendMessageCopyLink(sid, itemIndex, textContent, recordUid, options = {}) {
19198
20040
  return await this.chat.extendMessageCopyLink(sid, itemIndex, textContent, recordUid, options);
19199
20041
  }
19200
- async resolveLinkMetadata(rawUrl, options = {}) {
19201
- return await this.chat.resolveLinkMetadata(rawUrl, options);
19202
- }
19203
20042
  async forwardSourceMessages(sourceRef, actionRefs, options = {}) {
19204
20043
  return await this.chat.forwardSourceMessages(sourceRef, actionRefs, options);
19205
20044
  }