@senguoyun/dsh-arkme 0.1.33 → 0.1.35

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 (109) hide show
  1. package/README.md +1 -1
  2. package/docs/plugin-release.md +14 -1
  3. package/lib/client.js +36141 -32483
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +162 -16
  6. package/lib/persistent-extension.js +1 -1
  7. package/lib/{persistent-runtime-DUqdJv8K.js → persistent-runtime-e4gV6dqq.js} +1775 -710
  8. package/lib/sdk.js +19 -0
  9. package/lib/types/arkme-service.d.ts +13 -1
  10. package/lib/types/arkme-service.d.ts.map +1 -1
  11. package/lib/types/billing-gateway.d.ts.map +1 -1
  12. package/lib/types/client/ArkmeAvatar.d.ts +16 -10
  13. package/lib/types/client/ArkmeAvatar.d.ts.map +1 -1
  14. package/lib/types/client/ArkmeBillingSettings.d.ts +4 -0
  15. package/lib/types/client/ArkmeBillingSettings.d.ts.map +1 -1
  16. package/lib/types/client/ArkmeCalendarSurface.d.ts.map +1 -1
  17. package/lib/types/client/ArkmeCallHistorySurface.d.ts.map +1 -1
  18. package/lib/types/client/ArkmeCallSurface.d.ts.map +1 -1
  19. package/lib/types/client/ArkmeChatMemberActions.d.ts +4 -3
  20. package/lib/types/client/ArkmeChatMemberActions.d.ts.map +1 -1
  21. package/lib/types/client/ArkmeExtensionReviews.d.ts +2 -1
  22. package/lib/types/client/ArkmeExtensionReviews.d.ts.map +1 -1
  23. package/lib/types/client/ArkmeGroupChatControls.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeMarketplace.d.ts +4 -2
  25. package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -1
  26. package/lib/types/client/ArkmeMessageReportDialog.d.ts +8 -0
  27. package/lib/types/client/ArkmeMessageReportDialog.d.ts.map +1 -0
  28. package/lib/types/client/ArkmeMessageSnapshotDialog.d.ts +17 -0
  29. package/lib/types/client/ArkmeMessageSnapshotDialog.d.ts.map +1 -0
  30. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  31. package/lib/types/client/ArkmeOutgoingCallHost.d.ts.map +1 -1
  32. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  33. package/lib/types/client/ArkmeRelatedQuickNotes.d.ts +47 -0
  34. package/lib/types/client/ArkmeRelatedQuickNotes.d.ts.map +1 -0
  35. package/lib/types/client/ArkmeRichComposerInput.d.ts +3 -1
  36. package/lib/types/client/ArkmeRichComposerInput.d.ts.map +1 -1
  37. package/lib/types/client/ArkmeSearchSurface.d.ts.map +1 -1
  38. package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
  39. package/lib/types/client/ArkmeSidebar.d.ts +7 -4
  40. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  41. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  42. package/lib/types/client/ArkmeWorldSurface.d.ts +6 -1
  43. package/lib/types/client/ArkmeWorldSurface.d.ts.map +1 -1
  44. package/lib/types/client/api.d.ts +1 -1
  45. package/lib/types/client/api.d.ts.map +1 -1
  46. package/lib/types/client/arkme-billing.d.ts +7 -1
  47. package/lib/types/client/arkme-billing.d.ts.map +1 -1
  48. package/lib/types/client/avatar-image-runtime.d.ts +3 -0
  49. package/lib/types/client/avatar-image-runtime.d.ts.map +1 -0
  50. package/lib/types/client/avatar-image-store.d.ts +44 -0
  51. package/lib/types/client/avatar-image-store.d.ts.map +1 -0
  52. package/lib/types/client/calendar-invalidation-store.d.ts +26 -0
  53. package/lib/types/client/calendar-invalidation-store.d.ts.map +1 -0
  54. package/lib/types/client/chat-directory-store.d.ts +23 -13
  55. package/lib/types/client/chat-directory-store.d.ts.map +1 -1
  56. package/lib/types/client/composer-draft-store.d.ts +3 -3
  57. package/lib/types/client/composer-draft-store.d.ts.map +1 -1
  58. package/lib/types/client/composer-placeholder.d.ts +13 -0
  59. package/lib/types/client/composer-placeholder.d.ts.map +1 -0
  60. package/lib/types/client/conversation-memory-cache.d.ts +11 -14
  61. package/lib/types/client/conversation-memory-cache.d.ts.map +1 -1
  62. package/lib/types/client/file-send-tasks.d.ts +3 -1
  63. package/lib/types/client/file-send-tasks.d.ts.map +1 -1
  64. package/lib/types/client/interwoven-moments.d.ts +13 -2
  65. package/lib/types/client/interwoven-moments.d.ts.map +1 -1
  66. package/lib/types/client/navigation-cache.d.ts.map +1 -1
  67. package/lib/types/client/presentation-maintenance-runtime.d.ts +13 -0
  68. package/lib/types/client/presentation-maintenance-runtime.d.ts.map +1 -0
  69. package/lib/types/client/provider-instance-runtime.d.ts +5 -2
  70. package/lib/types/client/provider-instance-runtime.d.ts.map +1 -1
  71. package/lib/types/client/realtime-client-events.d.ts +6 -0
  72. package/lib/types/client/realtime-client-events.d.ts.map +1 -1
  73. package/lib/types/client/record-capture-location.d.ts +8 -0
  74. package/lib/types/client/record-capture-location.d.ts.map +1 -0
  75. package/lib/types/client/redesign/contacts/contacts-tab-store.d.ts.map +1 -1
  76. package/lib/types/client/related-recordings.d.ts +1 -1
  77. package/lib/types/client/source-identity.d.ts +5 -0
  78. package/lib/types/client/source-identity.d.ts.map +1 -0
  79. package/lib/types/client/ui-controller.d.ts +6 -0
  80. package/lib/types/client/ui-controller.d.ts.map +1 -1
  81. package/lib/types/client/use-arkme-avatar-image.d.ts +3 -0
  82. package/lib/types/client/use-arkme-avatar-image.d.ts.map +1 -0
  83. package/lib/types/extensions/manager.d.ts.map +1 -1
  84. package/lib/types/extensions/types.d.ts +1 -0
  85. package/lib/types/extensions/types.d.ts.map +1 -1
  86. package/lib/types/host-api.d.ts.map +1 -1
  87. package/lib/types/index.d.ts +1 -1
  88. package/lib/types/index.d.ts.map +1 -1
  89. package/lib/types/local-database.d.ts +1 -0
  90. package/lib/types/local-database.d.ts.map +1 -1
  91. package/lib/types/request-coordinator.d.ts.map +1 -1
  92. package/lib/types/sdk/index.d.ts +7 -2
  93. package/lib/types/sdk/index.d.ts.map +1 -1
  94. package/lib/types/services/chat-realtime-service.d.ts.map +1 -1
  95. package/lib/types/services/chat-service.d.ts +21 -2
  96. package/lib/types/services/chat-service.d.ts.map +1 -1
  97. package/lib/types/services/group-ai-polish-service.d.ts +3 -1
  98. package/lib/types/services/group-ai-polish-service.d.ts.map +1 -1
  99. package/lib/types/services/interwoven-service.d.ts +3 -0
  100. package/lib/types/services/interwoven-service.d.ts.map +1 -1
  101. package/lib/types/services/record-service.d.ts +10 -3
  102. package/lib/types/services/record-service.d.ts.map +1 -1
  103. package/lib/types/services/related-quick-note-service.d.ts +38 -0
  104. package/lib/types/services/related-quick-note-service.d.ts.map +1 -0
  105. package/lib/types/services/world-service.d.ts.map +1 -1
  106. package/lib/types/state-store.d.ts.map +1 -1
  107. package/lib/types/types.d.ts +111 -5
  108. package/lib/types/types.d.ts.map +1 -1
  109. package/package.json +5 -5
@@ -525,6 +525,7 @@ var ArkmeRequestCoordinator = class {
525
525
  this.bump(request, "staleDropped");
526
526
  throw new ArkmeStaleRequestError();
527
527
  }
528
+ if (this.inFlight.get(fullKey)?.promise !== promise) return value;
528
529
  this.failures.delete(fullKey);
529
530
  if (cacheMs > 0) this.cache.set(fullKey, {
530
531
  value: clone(value),
@@ -533,7 +534,7 @@ var ArkmeRequestCoordinator = class {
533
534
  });
534
535
  return value;
535
536
  }).catch((error) => {
536
- if (this.epoch(scope) === epoch && failureCooldownMs > 0 && (request.shouldCooldown?.(error) ?? true)) {
537
+ if (this.epoch(scope) === epoch && this.inFlight.get(fullKey)?.promise === promise && failureCooldownMs > 0 && (request.shouldCooldown?.(error) ?? true)) {
537
538
  const jitter = .9 + this.random() * .2;
538
539
  this.failures.set(fullKey, {
539
540
  error,
@@ -570,6 +571,7 @@ var ArkmeRequestCoordinator = class {
570
571
  const prefix = `${scopeInput.trim() || "public"}\u0000${keyPrefix}`;
571
572
  for (const key of this.cache.keys()) if (key.startsWith(prefix)) this.cache.delete(key);
572
573
  for (const key of this.failures.keys()) if (key.startsWith(prefix)) this.failures.delete(key);
574
+ for (const key of this.inFlight.keys()) if (key.startsWith(prefix)) this.inFlight.delete(key);
573
575
  }
574
576
  snapshotStats() {
575
577
  return Object.fromEntries([...this.stats].map(([key, value]) => [key, { ...value }]));
@@ -1469,7 +1471,7 @@ var HttpArkmeBillingGateway = class {
1469
1471
  return new ArkmePluginError(error.code, error.message, retryable, error.httpStatus, { cause: error });
1470
1472
  }
1471
1473
  async quota(signal) {
1472
- const data = objectValue$1(await this.post("intelligent", "/api/v1/managed-ai/balance/query", {}, "interactive-read", signal, "managed-ai-balance"));
1474
+ const data = objectValue$1(await this.post("intelligent", "/api/v1/managed-ai/balance/query", {}, "interactive-read", signal));
1473
1475
  const currency = stringValue$1(data.currency).trim();
1474
1476
  if (currency !== "CNY") throw new ArkmePluginError("billing-contract-invalid", "余额接口币种无效", true, 502);
1475
1477
  const availableNanoCny = nanoCny(data.available_nano_cny, "available_nano_cny");
@@ -1843,13 +1845,13 @@ function bestMatchingCategory(text) {
1843
1845
  }
1844
1846
  //#endregion
1845
1847
  //#region src/services/ai-video-service.ts
1846
- function numberValue$25(value) {
1848
+ function numberValue$26(value) {
1847
1849
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
1848
1850
  }
1849
1851
  function booleanValue$15(value) {
1850
1852
  return value === true;
1851
1853
  }
1852
- function listValue$26(value) {
1854
+ function listValue$27(value) {
1853
1855
  return Array.isArray(value) ? value : [];
1854
1856
  }
1855
1857
  var AiVideoService = class {
@@ -1863,9 +1865,9 @@ var AiVideoService = class {
1863
1865
  return {
1864
1866
  allowed: booleanValue$15(data.allowed),
1865
1867
  message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
1866
- selectedDurationMillis: Math.max(0, numberValue$25(data.selected_duration_millis)),
1867
- minimumDurationMillis: Math.max(0, numberValue$25(data.minimum_duration_millis)),
1868
- selectedSegmentCount: Math.max(0, numberValue$25(data.selected_segment_count)),
1868
+ selectedDurationMillis: Math.max(0, numberValue$26(data.selected_duration_millis)),
1869
+ minimumDurationMillis: Math.max(0, numberValue$26(data.minimum_duration_millis)),
1870
+ selectedSegmentCount: Math.max(0, numberValue$26(data.selected_segment_count)),
1869
1871
  retryable: booleanValue$15(data.retryable),
1870
1872
  ...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
1871
1873
  ...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
@@ -1901,7 +1903,7 @@ var AiVideoService = class {
1901
1903
  ...options.statuses === void 0 || options.statuses.length === 0 ? {} : { statuses: options.statuses }
1902
1904
  }, session, options.signal);
1903
1905
  return {
1904
- items: listValue$26(data.items).map((raw) => this.aiVideoListItem(raw)).filter((item) => item !== void 0),
1906
+ items: listValue$27(data.items).map((raw) => this.aiVideoListItem(raw)).filter((item) => item !== void 0),
1905
1907
  hasMore: booleanValue$15(data.has_more),
1906
1908
  ...stringValue$1(data.next_cursor).trim() === "" ? {} : { nextCursor: stringValue$1(data.next_cursor).trim() }
1907
1909
  };
@@ -1915,10 +1917,10 @@ var AiVideoService = class {
1915
1917
  return {
1916
1918
  allowed: booleanValue$15(data.allowed),
1917
1919
  message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
1918
- selectedDurationMillis: Math.max(0, numberValue$25(data.selected_duration_millis)),
1919
- minimumDurationMillis: Math.max(0, numberValue$25(data.minimum_duration_millis)),
1920
- selectedSegmentCount: Math.max(0, numberValue$25(data.selected_segment_count)),
1921
- selectedTextCount: Math.max(0, numberValue$25(data.selected_text_count)),
1920
+ selectedDurationMillis: Math.max(0, numberValue$26(data.selected_duration_millis)),
1921
+ minimumDurationMillis: Math.max(0, numberValue$26(data.minimum_duration_millis)),
1922
+ selectedSegmentCount: Math.max(0, numberValue$26(data.selected_segment_count)),
1923
+ selectedTextCount: Math.max(0, numberValue$26(data.selected_text_count)),
1922
1924
  retryable: booleanValue$15(data.retryable),
1923
1925
  ...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
1924
1926
  ...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
@@ -1958,19 +1960,19 @@ var AiVideoService = class {
1958
1960
  "canceled"
1959
1961
  ])).has(status)) throw new ArkmePluginError("ai-video-contract-invalid", "AI 视频服务返回了无效任务信息", true, 502);
1960
1962
  const selection = objectValue$1(data.selection);
1961
- const segmentCount = listValue$26(selection.segments).length;
1962
- const textCount = listValue$26(selection.texts).length;
1963
+ const segmentCount = listValue$27(selection.segments).length;
1964
+ const textCount = listValue$27(selection.texts).length;
1963
1965
  return {
1964
1966
  jobId,
1965
1967
  status,
1966
1968
  stage: stringValue$1(data.stage).trim() || status,
1967
- progress: Math.min(100, Math.max(0, Math.trunc(numberValue$25(data.progress)))),
1969
+ progress: Math.min(100, Math.max(0, Math.trunc(numberValue$26(data.progress)))),
1968
1970
  selectedSegmentCount: segmentCount,
1969
1971
  ...textCount === 0 ? {} : { selectedTextCount: textCount },
1970
1972
  retryable: booleanValue$15(data.retryable),
1971
1973
  ...stringValue$1(data.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(data.video_asset_uid).trim() },
1972
1974
  ...stringValue$1(data.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(data.cover_asset_uid).trim() },
1973
- ...numberValue$25(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$25(data.video_duration_millis) },
1975
+ ...numberValue$26(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$26(data.video_duration_millis) },
1974
1976
  ...stringValue$1(data.error_code).trim() === "" ? {} : { errorCode: stringValue$1(data.error_code).trim() },
1975
1977
  ...stringValue$1(data.error_message).trim() === "" ? {} : { errorMessage: stringValue$1(data.error_message).trim() },
1976
1978
  ...stringValue$1(data.failure_stage).trim() === "" ? {} : { failureStage: stringValue$1(data.failure_stage).trim() }
@@ -1993,17 +1995,17 @@ var AiVideoService = class {
1993
1995
  sessionId: stringValue$1(item.session_id).trim(),
1994
1996
  status,
1995
1997
  stage: stringValue$1(item.stage).trim() || status,
1996
- progress: Math.min(100, Math.max(0, Math.trunc(numberValue$25(item.progress)))),
1998
+ progress: Math.min(100, Math.max(0, Math.trunc(numberValue$26(item.progress)))),
1997
1999
  title: stringValue$1(source.title).trim() || "长录音 AI 视频",
1998
- sourceStartedAtMillis: numberValue$25(source.started_at),
1999
- selectedDurationMillis: Math.max(0, numberValue$25(source.selected_duration_millis)),
2000
- selectedSegmentCount: Math.max(0, Math.trunc(numberValue$25(item.selected_segment_count))),
2000
+ sourceStartedAtMillis: numberValue$26(source.started_at),
2001
+ selectedDurationMillis: Math.max(0, numberValue$26(source.selected_duration_millis)),
2002
+ selectedSegmentCount: Math.max(0, Math.trunc(numberValue$26(item.selected_segment_count))),
2001
2003
  retryable: booleanValue$15(item.retryable),
2002
- createdAtMillis: numberValue$25(item.created_at),
2003
- updatedAtMillis: numberValue$25(item.updated_at),
2004
+ createdAtMillis: numberValue$26(item.created_at),
2005
+ updatedAtMillis: numberValue$26(item.updated_at),
2004
2006
  ...stringValue$1(item.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(item.cover_asset_uid).trim() },
2005
2007
  ...stringValue$1(item.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(item.video_asset_uid).trim() },
2006
- ...numberValue$25(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$25(item.video_duration_millis) },
2008
+ ...numberValue$26(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$26(item.video_duration_millis) },
2007
2009
  ...stringValue$1(item.error_code).trim() === "" ? {} : { errorCode: stringValue$1(item.error_code).trim() },
2008
2010
  ...stringValue$1(item.error_message).trim() === "" ? {} : { errorMessage: clippedText(item.error_message, 500) }
2009
2011
  };
@@ -2029,13 +2031,13 @@ const ARKO_RUN_STATUSES = /* @__PURE__ */ new Set([
2029
2031
  "expired"
2030
2032
  ]);
2031
2033
  const ARKO_PROFILE_DISPLAY_NAME_CACHE_TTL_MS = 10 * 6e4;
2032
- function numberValue$24(value) {
2034
+ function numberValue$25(value) {
2033
2035
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2034
2036
  }
2035
2037
  function booleanValue$14(value) {
2036
2038
  return value === true;
2037
2039
  }
2038
- function listValue$25(value) {
2040
+ function listValue$26(value) {
2039
2041
  return Array.isArray(value) ? value : [];
2040
2042
  }
2041
2043
  function optionalString$3(value) {
@@ -2064,7 +2066,7 @@ function joinUrlWithQuery(baseUrl, path, query) {
2064
2066
  }
2065
2067
  function arkoProfileFromData(data) {
2066
2068
  const displayName = stringValue$1(data.display_name).trim();
2067
- const version = numberValue$24(data.version);
2069
+ const version = numberValue$25(data.version);
2068
2070
  if (displayName === "" || !Number.isSafeInteger(version) || version < 0) throw new ArkmePluginError("arko-profile-contract-invalid", "Arko 资料响应不完整", true, 502);
2069
2071
  return {
2070
2072
  displayName,
@@ -2096,7 +2098,7 @@ function arkoModelCatalogFromData(data) {
2096
2098
  const defaultRouteKey = arkoModelRouteKey(data.default_route_key);
2097
2099
  const effectiveRouteKey = arkoModelRouteKey(data.effective_route_key);
2098
2100
  const selectionSource = stringValue$1(data.selection_source);
2099
- const options = listValue$25(data.items).map(arkoModelOptionFromData);
2101
+ const options = listValue$26(data.items).map(arkoModelOptionFromData);
2100
2102
  const routeKeys = new Set(options.map((option) => option.routeKey));
2101
2103
  const selected = options.filter((option) => option.selected);
2102
2104
  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);
@@ -2117,9 +2119,9 @@ function parsedJsonRecord(value) {
2117
2119
  }
2118
2120
  function arkoHistoryItemFromData(value) {
2119
2121
  const data = objectValue$1(value);
2120
- const messageId = numberValue$24(data.id ?? data.msg_id ?? data.msgId);
2121
- const sessionId = numberValue$24(data.session_id ?? data.sessionId);
2122
- const roleCode = numberValue$24(data.role ?? data.sender_role ?? data.senderRole);
2122
+ const messageId = numberValue$25(data.id ?? data.msg_id ?? data.msgId);
2123
+ const sessionId = numberValue$25(data.session_id ?? data.sessionId);
2124
+ const roleCode = numberValue$25(data.role ?? data.sender_role ?? data.senderRole);
2123
2125
  if (!Number.isSafeInteger(messageId) || messageId <= 0 || !Number.isSafeInteger(sessionId) || sessionId <= 0 || roleCode !== 2 && roleCode !== 3) return void 0;
2124
2126
  const extra = parsedJsonRecord(data.extra ?? data.metadata ?? data.meta);
2125
2127
  const extraData = parsedJsonRecord(extra.data);
@@ -2140,7 +2142,7 @@ function arkoHistoryItemFromData(value) {
2140
2142
  extraData.updateAt,
2141
2143
  data.updated_at,
2142
2144
  data.updatedAt
2143
- ].map(numberValue$24).find((candidate) => candidate > 0) ?? 0;
2145
+ ].map(numberValue$25).find((candidate) => candidate > 0) ?? 0;
2144
2146
  const runUid = optionalString$3(extra.agent_run_uid);
2145
2147
  const runStatus = optionalString$3(extra.agent_run_status);
2146
2148
  const retryable = optionalBooleanValue$1(extra.agent_run_retryable);
@@ -2153,7 +2155,7 @@ function arkoHistoryItemFromData(value) {
2153
2155
  text: stringValue$1(data.content ?? data.text_content ?? data.textContent),
2154
2156
  reasoning: stringValue$1(data.reason_content ?? data.reason_text ?? data.reasonText),
2155
2157
  createdAtMillis: activityAt > 0 && activityAt < 1e11 ? activityAt * 1e3 : activityAt,
2156
- status: numberValue$24(data.status ?? data.msg_status),
2158
+ status: numberValue$25(data.status ?? data.msg_status),
2157
2159
  ...runUid === void 0 ? {} : { runUid },
2158
2160
  ...runStatus === void 0 ? {} : { runStatus },
2159
2161
  ...retryable === void 0 ? {} : { retryable },
@@ -2179,7 +2181,7 @@ function arkoRunProjectionFromData(data) {
2179
2181
  };
2180
2182
  }
2181
2183
  function arkoCreatedRecordUids(value) {
2182
- return listValue$25(value).map((item) => stringValue$1(item).trim()).filter((item) => item !== "");
2184
+ return listValue$26(value).map((item) => stringValue$1(item).trim()).filter((item) => item !== "");
2183
2185
  }
2184
2186
  function arkoStreamFrame(raw) {
2185
2187
  try {
@@ -2208,7 +2210,7 @@ var ArkoService = class {
2208
2210
  async arkoEnsureSession(signal) {
2209
2211
  const session = await this.runtime.requireSession();
2210
2212
  const latest = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/latest-session", { type: ARKO_AGENT_DIRECT_SESSION_TYPE }, session, signal);
2211
- const latestSessionId = numberValue$24(latest.id);
2213
+ const latestSessionId = numberValue$25(latest.id);
2212
2214
  if (Number.isSafeInteger(latestSessionId) && latestSessionId > 0) return {
2213
2215
  sessionId: latestSessionId,
2214
2216
  created: false,
@@ -2218,7 +2220,7 @@ var ArkoService = class {
2218
2220
  }
2219
2221
  async arkoCreateSession(signal) {
2220
2222
  const session = await this.runtime.requireSession();
2221
- const createdSessionId = numberValue$24((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-session", {
2223
+ const createdSessionId = numberValue$25((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-session", {
2222
2224
  name: ARKO_DEFAULT_SESSION_NAME,
2223
2225
  type: ARKO_AGENT_DIRECT_SESSION_TYPE
2224
2226
  }, session, signal)).session_id);
@@ -2243,7 +2245,7 @@ var ArkoService = class {
2243
2245
  const normalizedLimit = Math.min(ARKO_HISTORY_PAGE_LIMIT, Math.max(1, Math.trunc(limit)));
2244
2246
  const normalizedOffset = Math.max(0, Math.trunc(offset));
2245
2247
  const session = await this.runtime.requireSession();
2246
- const rawItems = listValue$25((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/message-list", {
2248
+ const rawItems = listValue$26((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/message-list", {
2247
2249
  limit: normalizedLimit,
2248
2250
  offset: normalizedOffset,
2249
2251
  session_type: ARKO_AGENT_DIRECT_SESSION_TYPE
@@ -2286,9 +2288,9 @@ var ArkoService = class {
2286
2288
  };
2287
2289
  if (modelRouteKey !== "") body.model_route_key = modelRouteKey;
2288
2290
  const accepted = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-msg-v2", body, session, options.signal);
2289
- const userMsgId = numberValue$24(accepted.user_msg_id);
2290
- const assistantMsgId = numberValue$24(accepted.assistant_msg_id);
2291
- const acceptedSessionId = numberValue$24(accepted.session_id) || sessionId;
2291
+ const userMsgId = numberValue$25(accepted.user_msg_id);
2292
+ const assistantMsgId = numberValue$25(accepted.assistant_msg_id);
2293
+ const acceptedSessionId = numberValue$25(accepted.session_id) || sessionId;
2292
2294
  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);
2293
2295
  const runUid = stringValue$1(accepted.run_uid).trim();
2294
2296
  if (runUid === "") throw new ArkmePluginError("arko-ask-contract-invalid", "Arko 消息响应缺少运行标识", true, 502);
@@ -2324,8 +2326,8 @@ var ArkoService = class {
2324
2326
  }, session, signal);
2325
2327
  const status = stringValue$1(data.status).trim();
2326
2328
  const normalizedRunUid = stringValue$1(data.run_uid).trim() || runUid.trim();
2327
- const sequence = numberValue$24(data.sequence);
2328
- const surfaceAssistantMsgId = numberValue$24(data.surface_assistant_msg_id);
2329
+ const sequence = numberValue$25(data.sequence);
2330
+ const surfaceAssistantMsgId = numberValue$25(data.surface_assistant_msg_id);
2329
2331
  const clientAction = optionalRecord(data.client_action);
2330
2332
  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);
2331
2333
  return {
@@ -2563,13 +2565,13 @@ const ARKME_ARRANGEMENT_REF_TTL_MILLIS = 900 * 1e3;
2563
2565
  const MAX_ARKME_ARRANGEMENT_REFS = 4096;
2564
2566
  const ARKME_ARRANGEMENT_REMINDER_REF_TTL_MILLIS = 900 * 1e3;
2565
2567
  const MAX_ARKME_ARRANGEMENT_REMINDER_REFS = 4096;
2566
- function numberValue$23(value) {
2568
+ function numberValue$24(value) {
2567
2569
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2568
2570
  }
2569
2571
  function booleanValue$13(value) {
2570
2572
  return value === true;
2571
2573
  }
2572
- function listValue$24(value) {
2574
+ function listValue$25(value) {
2573
2575
  return Array.isArray(value) ? value : [];
2574
2576
  }
2575
2577
  function arrangementStatusCode(status) {
@@ -2579,7 +2581,7 @@ function arrangementStatusCode(status) {
2579
2581
  return -1;
2580
2582
  }
2581
2583
  function arrangementStatus(value) {
2582
- const code = Math.trunc(numberValue$23(value));
2584
+ const code = Math.trunc(numberValue$24(value));
2583
2585
  if (code === 1) return "identified";
2584
2586
  if (code === 2) return "following";
2585
2587
  if (code === 3) return "completed";
@@ -2628,9 +2630,9 @@ var ArrangementService = class {
2628
2630
  limit,
2629
2631
  offset
2630
2632
  }, session, options.signal, { lane: "interactive-read" });
2631
- const rawItems = listValue$24(data.list);
2633
+ const rawItems = listValue$25(data.list);
2632
2634
  const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementItem(raw, session.userId)));
2633
- const total = Math.max(0, Math.trunc(numberValue$23(data.total)));
2635
+ const total = Math.max(0, Math.trunc(numberValue$24(data.total)));
2634
2636
  const nextOffset = offset + rawItems.length;
2635
2637
  const hasMore = rawItems.length > 0 && nextOffset < total;
2636
2638
  return {
@@ -2654,9 +2656,9 @@ var ArrangementService = class {
2654
2656
  offset,
2655
2657
  unread_only: options.unreadOnly === true
2656
2658
  }, session, options.signal, { lane: "interactive-read" });
2657
- const rawItems = listValue$24(data.list);
2659
+ const rawItems = listValue$25(data.list);
2658
2660
  const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementReminderEvent(raw, session.userId)));
2659
- const total = Math.max(0, Math.trunc(numberValue$23(data.total)));
2661
+ const total = Math.max(0, Math.trunc(numberValue$24(data.total)));
2660
2662
  const nextOffset = offset + rawItems.length;
2661
2663
  const hasMore = rawItems.length > 0 && nextOffset < total;
2662
2664
  return {
@@ -2679,7 +2681,7 @@ var ArrangementService = class {
2679
2681
  project(data.next_reminder)
2680
2682
  ]);
2681
2683
  return {
2682
- unreadCount: Math.max(0, Math.trunc(numberValue$23(data.unread_count))),
2684
+ unreadCount: Math.max(0, Math.trunc(numberValue$24(data.unread_count))),
2683
2685
  ...latestUnread === void 0 ? {} : { latestUnread },
2684
2686
  ...latestEvent === void 0 ? {} : { latestEvent },
2685
2687
  ...nextReminder === void 0 ? {} : { nextReminder }
@@ -2783,7 +2785,7 @@ var ArrangementService = class {
2783
2785
  }, session, signal);
2784
2786
  return {
2785
2787
  outcome: "confirmed",
2786
- updatedCount: Math.max(0, Math.trunc(numberValue$23(data.updated_count)))
2788
+ updatedCount: Math.max(0, Math.trunc(numberValue$24(data.updated_count)))
2787
2789
  };
2788
2790
  } catch (error) {
2789
2791
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -2801,7 +2803,7 @@ var ArrangementService = class {
2801
2803
  }, session, signal);
2802
2804
  return {
2803
2805
  outcome: "confirmed",
2804
- updatedCount: Math.max(0, Math.trunc(numberValue$23(data.updated_count)))
2806
+ updatedCount: Math.max(0, Math.trunc(numberValue$24(data.updated_count)))
2805
2807
  };
2806
2808
  } catch (error) {
2807
2809
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -2816,7 +2818,7 @@ var ArrangementService = class {
2816
2818
  const data = await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/reminders/clear", {}, session, signal);
2817
2819
  return {
2818
2820
  outcome: "confirmed",
2819
- updatedCount: Math.max(0, Math.trunc(numberValue$23(data.updated_count)))
2821
+ updatedCount: Math.max(0, Math.trunc(numberValue$24(data.updated_count)))
2820
2822
  };
2821
2823
  } catch (error) {
2822
2824
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -2849,7 +2851,7 @@ var ArrangementService = class {
2849
2851
  }
2850
2852
  async reconcileArrangementDelete(arrangementRef, arrangementUid, session) {
2851
2853
  try {
2852
- const visible = listValue$24((await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/list", {
2854
+ const visible = listValue$25((await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/list", {
2853
2855
  status: -1,
2854
2856
  uids: [arrangementUid],
2855
2857
  limit: 1,
@@ -2880,8 +2882,8 @@ var ArrangementService = class {
2880
2882
  const item = objectValue$1(raw);
2881
2883
  const arrangementUid = stringValue$1(item.uid).trim();
2882
2884
  if (arrangementUid === "") throw new ArkmePluginError("arrangement-contract-invalid", "安排响应缺少业务身份", true, 502);
2883
- const dueAtMillis = Math.trunc(numberValue$23(item.due_at));
2884
- const remindAtMillis = Math.trunc(numberValue$23(item.remind_at));
2885
+ const dueAtMillis = Math.trunc(numberValue$24(item.due_at));
2886
+ const remindAtMillis = Math.trunc(numberValue$24(item.remind_at));
2885
2887
  return {
2886
2888
  arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
2887
2889
  title: stringValue$1(item.title),
@@ -2889,8 +2891,8 @@ var ArrangementService = class {
2889
2891
  status: arrangementStatus(item.status),
2890
2892
  reminderEnabled: booleanValue$13(item.reminder_enabled),
2891
2893
  reminderState: stringValue$1(item.reminder_state),
2892
- createdAtMillis: Math.trunc(numberValue$23(item.create_at)),
2893
- updatedAtMillis: Math.trunc(numberValue$23(item.update_at)),
2894
+ createdAtMillis: Math.trunc(numberValue$24(item.create_at)),
2895
+ updatedAtMillis: Math.trunc(numberValue$24(item.update_at)),
2894
2896
  ...dueAtMillis > 0 ? { dueAtMillis } : {},
2895
2897
  ...remindAtMillis > 0 ? { remindAtMillis } : {}
2896
2898
  };
@@ -2911,20 +2913,20 @@ var ArrangementService = class {
2911
2913
  const eventUid = stringValue$1(item.uid).trim();
2912
2914
  const arrangementUid = stringValue$1(item.arrangement_uid).trim();
2913
2915
  if (eventUid === "" || arrangementUid === "") throw new ArkmePluginError("arrangement-reminder-contract-invalid", "安排提醒响应缺少有效身份", true, 502);
2914
- const dueAtMillis = Math.trunc(numberValue$23(item.due_at));
2915
- const remindAtMillis = Math.trunc(numberValue$23(item.remind_at));
2916
- const readAtMillis = Math.trunc(numberValue$23(item.read_at));
2916
+ const dueAtMillis = Math.trunc(numberValue$24(item.due_at));
2917
+ const remindAtMillis = Math.trunc(numberValue$24(item.remind_at));
2918
+ const readAtMillis = Math.trunc(numberValue$24(item.read_at));
2917
2919
  return {
2918
2920
  eventRef: await this.arrangementReminderRef(viewerUserId, eventUid),
2919
2921
  arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
2920
2922
  title: stringValue$1(item.title),
2921
2923
  description: stringValue$1(item.description),
2922
2924
  eventKind: stringValue$1(item.event_kind),
2923
- eventAtMillis: Math.trunc(numberValue$23(item.event_at)),
2925
+ eventAtMillis: Math.trunc(numberValue$24(item.event_at)),
2924
2926
  read: readAtMillis > 0,
2925
2927
  reminderState: stringValue$1(item.reminder_state),
2926
- createdAtMillis: Math.trunc(numberValue$23(item.create_at)),
2927
- updatedAtMillis: Math.trunc(numberValue$23(item.update_at)),
2928
+ createdAtMillis: Math.trunc(numberValue$24(item.create_at)),
2929
+ updatedAtMillis: Math.trunc(numberValue$24(item.update_at)),
2928
2930
  ...dueAtMillis > 0 ? { dueAtMillis } : {},
2929
2931
  ...remindAtMillis > 0 ? { remindAtMillis } : {},
2930
2932
  ...readAtMillis > 0 ? { readAtMillis } : {}
@@ -2981,7 +2983,7 @@ var ArrangementService = class {
2981
2983
  const ARKME_PHONE_BIND_SUCCESS = 1;
2982
2984
  const ARKME_PHONE_BIND_REPEAT = 2;
2983
2985
  const ARKME_PHONE_BIND_CODE_ERR = 3;
2984
- function numberValue$22(value) {
2986
+ function numberValue$23(value) {
2985
2987
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2986
2988
  }
2987
2989
  function jiwoScanLoginAvailable(config) {
@@ -3066,7 +3068,7 @@ var AuthService = class {
3066
3068
  const sceneStr = stringValue$1(data.scene_str).trim();
3067
3069
  const pollToken = stringValue$1(data.poll_token).trim();
3068
3070
  const qrContent = stringValue$1(data.url).trim();
3069
- const expireSeconds = Math.max(30, numberValue$22(data.expire_seconds) || 300);
3071
+ const expireSeconds = Math.max(30, numberValue$23(data.expire_seconds) || 300);
3070
3072
  if (qrContent === "" && sceneStr !== "") throw new ArkmePluginError("wechat-qr-unavailable", "测试环境当前未返回可用微信二维码,请使用手机号登录", true, 503);
3071
3073
  if (sceneStr === "" || pollToken === "" || qrContent === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录二维码响应不完整", true, 502);
3072
3074
  const attemptId = crypto.randomUUID();
@@ -3105,7 +3107,7 @@ var AuthService = class {
3105
3107
  ref: 0,
3106
3108
  keep_cancel: true
3107
3109
  }, void 0, [200]);
3108
- const userId = numberValue$22(data.user_id);
3110
+ const userId = numberValue$23(data.user_id);
3109
3111
  if (userId <= 0) return {
3110
3112
  status: "pending",
3111
3113
  environment: this.runtime.config.environment,
@@ -3135,7 +3137,7 @@ var AuthService = class {
3135
3137
  }, void 0, [200]);
3136
3138
  const ticket = stringValue$1(data.ticket).trim();
3137
3139
  const pollSecret = stringValue$1(data.poll_secret).trim();
3138
- const expiresAtMillis = numberValue$22(data.expires_at);
3140
+ const expiresAtMillis = numberValue$23(data.expires_at);
3139
3141
  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);
3140
3142
  if (attemptGeneration !== this.jiwoAttemptGeneration) {
3141
3143
  await this.cancelJiwoCredentials(ticket, pollSecret);
@@ -3185,7 +3187,7 @@ var AuthService = class {
3185
3187
  };
3186
3188
  }
3187
3189
  if (status !== "authenticated") throw new ArkmePluginError("jiwo-login-contract-invalid", "即我扫码登录状态无效", true, 502);
3188
- const userId = numberValue$22(data.user_id);
3190
+ const userId = numberValue$23(data.user_id);
3189
3191
  const accessToken = stringValue$1(data.access_token);
3190
3192
  const refreshToken = stringValue$1(data.refresh_token);
3191
3193
  if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录成功响应缺少凭据", false, 502);
@@ -3254,7 +3256,7 @@ var AuthService = class {
3254
3256
  const normalizedCaptcha = this.normalizedCaptcha(captcha);
3255
3257
  const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
3256
3258
  if (session !== void 0) {
3257
- if (numberValue$22((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/bind-phone-send-code", {
3259
+ if (numberValue$23((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/bind-phone-send-code", {
3258
3260
  phone: normalizedPhone,
3259
3261
  pre: "86",
3260
3262
  is_test: this.runtime.config.environment === "test",
@@ -3276,7 +3278,7 @@ var AuthService = class {
3276
3278
  if (!/^[0-9]{6}$/.test(normalizedCode)) throw new ArkmePluginError("phone-code-invalid", "请输入有效的短信验证码", false);
3277
3279
  const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
3278
3280
  if (session !== void 0) {
3279
- const result = numberValue$22((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/verify-bind-phone", {
3281
+ const result = numberValue$23((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/verify-bind-phone", {
3280
3282
  phone: normalizedPhone,
3281
3283
  pre: "86",
3282
3284
  code: normalizedCode,
@@ -3297,7 +3299,7 @@ var AuthService = class {
3297
3299
  keep_cancel: true
3298
3300
  }, void 0, [200]);
3299
3301
  if (data.ok === false) throw new ArkmePluginError("phone-code-rejected", "手机号或验证码错误", false, 401);
3300
- const userId = numberValue$22(data.user_id);
3302
+ const userId = numberValue$23(data.user_id);
3301
3303
  const accessToken = stringValue$1(data.access_token);
3302
3304
  const refreshToken = stringValue$1(data.refresh_token);
3303
3305
  if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme手机号登录响应不完整", false, 502);
@@ -3447,7 +3449,7 @@ var ArkmePrivacyVisibilityService = class {
3447
3449
  };
3448
3450
  //#endregion
3449
3451
  //#region src/mention-metadata.ts
3450
- function listValue$23(value) {
3452
+ function listValue$24(value) {
3451
3453
  return Array.isArray(value) ? value : [];
3452
3454
  }
3453
3455
  function integerLikeValue$3(value) {
@@ -3468,7 +3470,7 @@ function arkmeMentionMetadataFromRecord(record, payload) {
3468
3470
  function arkmeMentionMetadataMentionsViewer(record, payload, viewerUserId) {
3469
3471
  if (viewerUserId <= 0) return false;
3470
3472
  const metadata = arkmeMentionMetadataFromRecord(record, payload);
3471
- for (const rawMention of listValue$23(metadata.human_mentions ?? metadata.humanMentions)) {
3473
+ for (const rawMention of listValue$24(metadata.human_mentions ?? metadata.humanMentions)) {
3472
3474
  const mention = objectValue$1(rawMention);
3473
3475
  const mentionedUserId = integerLikeValue$3(mention.user_id ?? mention.userId);
3474
3476
  if (mentionedUserId === 0 || mentionedUserId === viewerUserId) return true;
@@ -3736,7 +3738,7 @@ const GROUP_AVATAR_NEGATIVE_CACHE_TTL_MS = 6e4;
3736
3738
  const TOPIC_DISSOLVE_RECORD_CONCURRENCY = 6;
3737
3739
  const PRIVATE_REMARK_PAGE_LIMIT = 50;
3738
3740
  const PRIVATE_REMARK_MAX_PAGES = 20;
3739
- function numberValue$21(value) {
3741
+ function numberValue$22(value) {
3740
3742
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
3741
3743
  }
3742
3744
  function integerIdentifierValue(value) {
@@ -3748,7 +3750,7 @@ function integerIdentifierValue(value) {
3748
3750
  function booleanValue$12(value) {
3749
3751
  return value === true;
3750
3752
  }
3751
- function listValue$22(value) {
3753
+ function listValue$23(value) {
3752
3754
  return Array.isArray(value) ? value : [];
3753
3755
  }
3754
3756
  function integerLikeValue$2(value) {
@@ -3788,7 +3790,7 @@ function arkmeChatSidebarTargetFromBundle(bundle, fallbackSubjectUid = "") {
3788
3790
  if (subjectUid === "") return void 0;
3789
3791
  return {
3790
3792
  subjectUid,
3791
- hiddenAnchorTimestamp: Math.max(0, numberValue$21(bundle.sort_active_at ?? bundle.last_active_at ?? chatSession.last_active_at))
3793
+ hiddenAnchorTimestamp: Math.max(0, numberValue$22(bundle.sort_active_at ?? bundle.last_active_at ?? chatSession.last_active_at))
3792
3794
  };
3793
3795
  }
3794
3796
  function backendBooleanValue(value) {
@@ -3820,7 +3822,7 @@ function isSourceKind(value) {
3820
3822
  function chatMessageDnd$1(value) {
3821
3823
  if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
3822
3824
  const policy = value;
3823
- return numberValue$21(policy.mute_state) === 2 || numberValue$21(policy.notify_state) === 2;
3825
+ return numberValue$22(policy.mute_state) === 2 || numberValue$22(policy.notify_state) === 2;
3824
3826
  }
3825
3827
  function attachmentPreviewKind(item) {
3826
3828
  const fileName = stringValue$1(item.file_name ?? item.fileName).trim();
@@ -3839,13 +3841,13 @@ function arkmeChatConversationPreview(raw) {
3839
3841
  const nested = stringValue$1(content.text_content ?? content.title ?? content.summary).trim();
3840
3842
  if (nested !== "") return nested.slice(0, 300);
3841
3843
  if (objectValue$1(content.voice).duration !== void 0) return "[语音]";
3842
- const displayItems = listValue$22(raw.media_display_items ?? raw.mediaDisplayItems).map(objectValue$1);
3844
+ const displayItems = listValue$23(raw.media_display_items ?? raw.mediaDisplayItems).map(objectValue$1);
3843
3845
  const displayByAsset = /* @__PURE__ */ new Map();
3844
3846
  for (const item of displayItems) {
3845
3847
  const fileAssetUid = stringValue$1(item.file_asset_uid ?? item.fileAssetUid).trim();
3846
3848
  if (fileAssetUid !== "") displayByAsset.set(fileAssetUid, item);
3847
3849
  }
3848
- const mediaRefs = listValue$22(content.media_refs ?? content.mediaRefs).map(objectValue$1);
3850
+ const mediaRefs = listValue$23(content.media_refs ?? content.mediaRefs).map(objectValue$1);
3849
3851
  const firstAttachment = (mediaRefs.length > 0 ? mediaRefs.map((ref) => ({
3850
3852
  ...displayByAsset.get(stringValue$1(ref.file_asset_uid ?? ref.fileAssetUid).trim()) ?? {},
3851
3853
  ...ref
@@ -3997,11 +3999,11 @@ var SourceService = class {
3997
3999
  lane: "background-read",
3998
4000
  key: `world-author-labels:${pageCursor === void 0 ? "first" : String(page)}`
3999
4001
  });
4000
- for (const raw of listValue$22(data.items)) {
4002
+ for (const raw of listValue$23(data.items)) {
4001
4003
  const bundle = objectValue$1(raw);
4002
- const sessionKind = numberValue$21(objectValue$1(bundle.session).session_kind);
4004
+ const sessionKind = numberValue$22(objectValue$1(bundle.session).session_kind);
4003
4005
  if (sessionKind !== 1 && sessionKind !== 3) continue;
4004
- const targetUserId = numberValue$21(objectValue$1(bundle.private_counterpart).user_id);
4006
+ const targetUserId = numberValue$22(objectValue$1(bundle.private_counterpart).user_id);
4005
4007
  if (!remaining.has(targetUserId)) continue;
4006
4008
  const supplement = objectValue$1(bundle.private_supplement);
4007
4009
  const counterpart = objectValue$1(bundle.private_counterpart);
@@ -4035,7 +4037,7 @@ var SourceService = class {
4035
4037
  key: `private-remarks:${String(offset)}`,
4036
4038
  failureCooldownMs: 2e3
4037
4039
  });
4038
- const rawItems = listValue$22(data.items);
4040
+ const rawItems = listValue$23(data.items);
4039
4041
  for (const value of rawItems) {
4040
4042
  const raw = objectValue$1(value);
4041
4043
  const targetUserId = integerIdentifierValue(raw.user_id);
@@ -4060,9 +4062,9 @@ var SourceService = class {
4060
4062
  key: `private-remarks:direct:${pageCursor === void 0 ? "first" : String(page)}`,
4061
4063
  failureCooldownMs: 2e3
4062
4064
  });
4063
- for (const value of listValue$22(data.items)) {
4065
+ for (const value of listValue$23(data.items)) {
4064
4066
  const bundle = objectValue$1(value);
4065
- const sessionKind = numberValue$21(objectValue$1(bundle.session).session_kind);
4067
+ const sessionKind = numberValue$22(objectValue$1(bundle.session).session_kind);
4066
4068
  if (sessionKind !== 1 && sessionKind !== 3) continue;
4067
4069
  const targetUserId = integerIdentifierValue(objectValue$1(bundle.private_counterpart).user_id);
4068
4070
  if (!remaining.has(targetUserId)) continue;
@@ -4105,10 +4107,10 @@ var SourceService = class {
4105
4107
  extra: { source: "dsh-arkme" }
4106
4108
  }, session);
4107
4109
  const topicUid = stringValue$1(created.topic_uid).trim();
4108
- if (topicUid === "" || numberValue$21(created.status) !== 1) throw new ArkmePluginError("topic-create-contract-invalid", "主题创建响应不完整", true, 502);
4110
+ if (topicUid === "" || numberValue$22(created.status) !== 1) throw new ArkmePluginError("topic-create-contract-invalid", "主题创建响应不完整", true, 502);
4109
4111
  const sourceRef = await this.sealSourceRef(session.userId, "topic", topicUid, title);
4110
4112
  if (parentTopicUid !== void 0) try {
4111
- if (numberValue$21(objectValue$1((await this.runtime.authenticatedPost("/api/v1/topics/hierarchy/bind", {
4113
+ if (numberValue$22(objectValue$1((await this.runtime.authenticatedPost("/api/v1/topics/hierarchy/bind", {
4112
4114
  parent_topic_uid: parentTopicUid,
4113
4115
  child_topic_uid: topicUid
4114
4116
  }, session)).relation).status) !== 1) throw new ArkmePluginError("topic-hierarchy-bind-contract-invalid", "子主题层级响应不完整", true, 502);
@@ -4277,14 +4279,14 @@ var SourceService = class {
4277
4279
  ...cursorSendAt === void 0 ? {} : { cursor_send_at: cursorSendAt },
4278
4280
  ...cursorRecordUid === void 0 ? {} : { cursor_record_uid: cursorRecordUid }
4279
4281
  }, session);
4280
- for (const raw of listValue$22(data.records)) {
4282
+ for (const raw of listValue$23(data.records)) {
4281
4283
  const item = objectValue$1(raw);
4282
4284
  const uid = stringValue$1(item.record_uid ?? objectValue$1(item.record_core).record_uid).trim();
4283
4285
  if (uid !== "") recordUids.add(uid);
4284
4286
  }
4285
4287
  onProgress?.(recordUids.size);
4286
4288
  if (data.has_more !== true) return [...recordUids];
4287
- const nextSendAt = numberValue$21(data.next_cursor_send_at);
4289
+ const nextSendAt = numberValue$22(data.next_cursor_send_at);
4288
4290
  const nextRecordUid = stringValue$1(data.next_cursor_record_uid).trim();
4289
4291
  const cursorKey = `${String(nextSendAt)}:${nextRecordUid}`;
4290
4292
  if (nextSendAt <= 0 || nextRecordUid === "" || seenCursors.has(cursorKey)) throw new ArkmePluginError("topic-dissolve-record-page-invalid", "主题快记加载不完整,未解散主题,请刷新后重试", true, 502);
@@ -4367,7 +4369,7 @@ var SourceService = class {
4367
4369
  parent_topic_uid: nextParentTopicUid ?? "",
4368
4370
  insert_before_topic_uid: ""
4369
4371
  }, session);
4370
- return stringValue$1(moved.topic_uid).trim() === topicUid && stringValue$1(moved.parent_topic_uid).trim() === (nextParentTopicUid ?? "") && numberValue$21(moved.sibling_order) > 0;
4372
+ return stringValue$1(moved.topic_uid).trim() === topicUid && stringValue$1(moved.parent_topic_uid).trim() === (nextParentTopicUid ?? "") && numberValue$22(moved.sibling_order) > 0;
4371
4373
  }
4372
4374
  async rollbackDissolvedTopicChildren(children, topicUid, parentTopicUid, session) {
4373
4375
  for (const child of [...children].reverse()) await this.moveTopicHierarchyForDissolve(child.topic.ownerRef, parentTopicUid ?? "", topicUid, session).catch(() => false);
@@ -4396,7 +4398,7 @@ var SourceService = class {
4396
4398
  }, session);
4397
4399
  const movedTopicUid = stringValue$1(moved.topic_uid).trim();
4398
4400
  const movedParentUid = stringValue$1(moved.parent_topic_uid).trim();
4399
- const siblingOrder = numberValue$21(moved.sibling_order);
4401
+ const siblingOrder = numberValue$22(moved.sibling_order);
4400
4402
  if (movedTopicUid !== topic.ownerRef || movedParentUid !== (nextParentUid ?? "") || siblingOrder <= 0) throw new ArkmePluginError("topic-hierarchy-move-contract-invalid", "主题移动响应不完整,请刷新后重试", true, 502);
4401
4403
  this.sourceListCache.clear();
4402
4404
  return {
@@ -4441,17 +4443,17 @@ var SourceService = class {
4441
4443
  if (source.kind !== "private_chat" && source.kind !== "group_chat") throw new ArkmePluginError("chat-directory-policy-invalid", "仅支持更新私聊或群聊的会话列表状态", false);
4442
4444
  const sidebarTarget = await this.resolveChatSidebarTarget(source, session, options.signal);
4443
4445
  const current = await this.runtime.authenticatedChatPost("/api/v1/chats/policy/get", { chat_session_uid: source.ownerRef }, session, options.signal);
4444
- const pinned = options.pinned ?? numberValue$21(current.pin_state) === 2;
4445
- const hidden = options.hidden ?? numberValue$21(current.show_in_home_state) === 2;
4446
+ const pinned = options.pinned ?? numberValue$22(current.pin_state) === 2;
4447
+ const hidden = options.hidden ?? numberValue$22(current.show_in_home_state) === 2;
4446
4448
  const updatedAt = Date.now();
4447
4449
  const writes = [this.runtime.authenticatedChatPost("/api/v1/chats/policy/update", {
4448
4450
  chat_session_uid: source.ownerRef,
4449
4451
  show_in_home_state: hidden ? 2 : 1,
4450
- privacy_state: numberValue$21(current.privacy_state) || 1,
4451
- mute_state: numberValue$21(current.mute_state) || 1,
4452
+ privacy_state: numberValue$22(current.privacy_state) || 1,
4453
+ mute_state: numberValue$22(current.mute_state) || 1,
4452
4454
  pin_state: pinned ? 2 : 1,
4453
- notify_state: numberValue$21(current.notify_state) || 1,
4454
- status: numberValue$21(current.status) || 1,
4455
+ notify_state: numberValue$22(current.notify_state) || 1,
4456
+ status: numberValue$22(current.status) || 1,
4455
4457
  update_at: updatedAt
4456
4458
  }, session, options.signal)];
4457
4459
  if (options.hidden !== void 0) writes.push(this.runtime.authenticatedSubjectPost("/api/v1/subject/batch-set-sidebar-hidden-status", { items: [{
@@ -4510,7 +4512,7 @@ var SourceService = class {
4510
4512
  key: "directory:groups:count",
4511
4513
  failureCooldownMs: 2e3
4512
4514
  });
4513
- return Math.max(0, numberValue$21(data.total ?? data.total_count));
4515
+ return Math.max(0, numberValue$22(data.total ?? data.total_count));
4514
4516
  }
4515
4517
  async listSourcesUncached(session, directory, options, limit, sessionKind) {
4516
4518
  if (directory === "send_to_self") {
@@ -4528,7 +4530,7 @@ var SourceService = class {
4528
4530
  options.signal?.throwIfAborted();
4529
4531
  const cached = summaryResult.status === "rejected" || latestRecordsResult.status === "rejected" ? await this.runtime.stateStore.cachedSnapshot(session.userId).catch(() => void 0) : void 0;
4530
4532
  const defaultRecordCount = summaryResult.status === "fulfilled" ? summaryResult.value.recordCount : cached?.summary?.recordCount;
4531
- 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);
4533
+ const defaultLatestRecord = latestRecordsResult.status === "fulfilled" ? listValue$23(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
4534
  const defaultLatestPreview = defaultLatestRecord === void 0 ? "" : defaultLatestRecord.textContent.trim() || defaultLatestRecord.title.trim();
4533
4535
  const defaultCategory = {
4534
4536
  sourceRef: await this.sealSourceRef(session.userId, "default_category", "uncategorized", "未分类"),
@@ -4544,24 +4546,24 @@ var SourceService = class {
4544
4546
  const parentTopicUidByChild = /* @__PURE__ */ new Map();
4545
4547
  const siblingOrderByChild = /* @__PURE__ */ new Map();
4546
4548
  const childTopicUidsByParent = /* @__PURE__ */ new Map();
4547
- for (const raw of listValue$22(hierarchyData?.relations)) {
4549
+ for (const raw of listValue$23(hierarchyData?.relations)) {
4548
4550
  const relation = objectValue$1(raw);
4549
- if (numberValue$21(relation.rel_kind) !== 1 || numberValue$21(relation.status) !== 1) continue;
4551
+ if (numberValue$22(relation.rel_kind) !== 1 || numberValue$22(relation.status) !== 1) continue;
4550
4552
  const parentTopicUid = stringValue$1(relation.parent_topic_uid).trim();
4551
4553
  const childTopicUid = stringValue$1(relation.child_topic_uid).trim();
4552
4554
  if (parentTopicUid === "" || childTopicUid === "" || parentTopicUid === childTopicUid) continue;
4553
4555
  parentTopicUidByChild.set(childTopicUid, parentTopicUid);
4554
- siblingOrderByChild.set(childTopicUid, numberValue$21(relation.sibling_order));
4556
+ siblingOrderByChild.set(childTopicUid, numberValue$22(relation.sibling_order));
4555
4557
  const children = childTopicUidsByParent.get(parentTopicUid) ?? /* @__PURE__ */ new Set();
4556
4558
  children.add(childTopicUid);
4557
4559
  childTopicUidsByParent.set(parentTopicUid, children);
4558
4560
  }
4559
- for (const raw of listValue$22(data.items)) {
4561
+ for (const raw of listValue$23(data.items)) {
4560
4562
  const item = objectValue$1(raw);
4561
4563
  if (arkmePrivacyLockedTopic(item)) continue;
4562
4564
  const core = objectValue$1(item.topic_core);
4563
4565
  const status = core.status ?? item.status;
4564
- if (status !== void 0 && numberValue$21(status) !== 1) continue;
4566
+ if (status !== void 0 && numberValue$22(status) !== 1) continue;
4565
4567
  const summary = objectValue$1(item.summary);
4566
4568
  const latest = objectValue$1(item.latest_record_core);
4567
4569
  const parent = objectValue$1(core.parent_topic_core ?? core.parent_topic ?? item.parent_topic_core ?? item.parent_topic);
@@ -4573,12 +4575,12 @@ var SourceService = class {
4573
4575
  topicDescriptors.push({
4574
4576
  topicUid,
4575
4577
  ...parentTopicUid === "" || parentTopicUid === topicUid ? {} : { parentTopicUid },
4576
- siblingOrder: numberValue$21(siblingOrderByChild.get(topicUid) ?? core.sibling_order ?? item.sibling_order),
4578
+ siblingOrder: numberValue$22(siblingOrderByChild.get(topicUid) ?? core.sibling_order ?? item.sibling_order),
4577
4579
  title,
4578
4580
  latestPreview: arkmeChatConversationPreview(latest),
4579
- latestMessageAtMillis: numberValue$21(latest.send_at ?? summary.latest_send_at),
4580
- activeAtMillis: numberValue$21(latest.send_at ?? summary.latest_send_at ?? core.update_at),
4581
- recordCount: numberValue$21(summary.record_count)
4581
+ latestMessageAtMillis: numberValue$22(latest.send_at ?? summary.latest_send_at),
4582
+ activeAtMillis: numberValue$22(latest.send_at ?? summary.latest_send_at ?? core.update_at),
4583
+ recordCount: numberValue$22(summary.record_count)
4582
4584
  });
4583
4585
  }
4584
4586
  const sourceRefByTopicUid = /* @__PURE__ */ new Map();
@@ -4620,12 +4622,12 @@ var SourceService = class {
4620
4622
  unreadCount: 0
4621
4623
  };
4622
4624
  const nextPageCursor = objectValue$1(data.next_page_cursor ?? data.next_cursor);
4623
- const nextOffset = numberValue$21(data.next_offset);
4625
+ const nextOffset = numberValue$22(data.next_offset);
4624
4626
  const preferredPageCursor = Object.keys(nextPageCursor).length > 0 ? nextPageCursor : void 0;
4625
4627
  const preferredOffset = preferredPageCursor === void 0 && nextOffset > 0 ? nextOffset : void 0;
4626
4628
  const hasMore = data.has_more === true && (preferredPageCursor !== void 0 || preferredOffset !== void 0);
4627
4629
  const totalValue = data.total ?? data.total_count;
4628
- const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$21(totalValue));
4630
+ const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$22(totalValue));
4629
4631
  return {
4630
4632
  directory,
4631
4633
  items: [
@@ -4656,7 +4658,7 @@ var SourceService = class {
4656
4658
  const items = [];
4657
4659
  const privateUserIdByIndex = /* @__PURE__ */ new Map();
4658
4660
  const groupSessionUidByIndex = /* @__PURE__ */ new Map();
4659
- for (const raw of listValue$22(data.items)) {
4661
+ for (const raw of listValue$23(data.items)) {
4660
4662
  const bundle = objectValue$1(raw);
4661
4663
  const chatSession = objectValue$1(bundle.session);
4662
4664
  const counterpart = objectValue$1(bundle.private_counterpart);
@@ -4667,14 +4669,14 @@ var SourceService = class {
4667
4669
  const unread = objectValue$1(bundle.unread_snapshot);
4668
4670
  const currentPolicy = objectValue$1(bundle.current_policy);
4669
4671
  const isMuted = chatMessageDnd$1(currentPolicy) ?? false;
4670
- const isPinned = numberValue$21(currentPolicy.pin_state) === 2;
4672
+ const isPinned = numberValue$22(currentPolicy.pin_state) === 2;
4671
4673
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
4672
- const sessionKind = numberValue$21(chatSession.session_kind);
4674
+ const sessionKind = numberValue$22(chatSession.session_kind);
4673
4675
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
4674
4676
  if (uid === "" || kind === void 0) continue;
4675
4677
  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() || "未命名会话";
4676
4678
  const preview = arkmeChatConversationPreview(latestPayload);
4677
- const unreadCount = Math.max(0, Math.trunc(numberValue$21(unread.unread_count)));
4679
+ const unreadCount = Math.max(0, Math.trunc(numberValue$22(unread.unread_count)));
4678
4680
  const latestRelation = objectValue$1(latestPreview.relation);
4679
4681
  const latestPreviewMentionsViewer = integerLikeValue$2(latestRelation.sender_user_id ?? latestRelation.senderUserId) !== session.userId && arkmeMentionMetadataMentionsViewer(latestRecord, latestPayload, session.userId);
4680
4682
  const backendMentionState = chatUnreadMentionState(unread);
@@ -4690,17 +4692,17 @@ var SourceService = class {
4690
4692
  kind,
4691
4693
  displayName,
4692
4694
  ...preview === "" ? {} : { latestPreview: preview },
4693
- activeAtMillis: numberValue$21(bundle.sort_active_at ?? chatSession.last_active_at),
4695
+ activeAtMillis: numberValue$22(bundle.sort_active_at ?? chatSession.last_active_at),
4694
4696
  unreadCount,
4695
4697
  ...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
4696
4698
  isMuted,
4697
4699
  isPinned,
4698
- ...numberValue$21(unread.session_last_seq ?? chatSession.last_seq) > 0 ? { latestSequence: numberValue$21(unread.session_last_seq ?? chatSession.last_seq) } : {}
4700
+ ...numberValue$22(unread.session_last_seq ?? chatSession.last_seq) > 0 ? { latestSequence: numberValue$22(unread.session_last_seq ?? chatSession.last_seq) } : {}
4699
4701
  };
4700
4702
  const itemIndex = items.push(item) - 1;
4701
4703
  this.chatSourceCache.set(`${String(session.userId)}:${uid}`, item);
4702
4704
  if (kind === "private_chat") {
4703
- const counterpartUserId = numberValue$21(counterpart.user_id);
4705
+ const counterpartUserId = numberValue$22(counterpart.user_id);
4704
4706
  if (Number.isSafeInteger(counterpartUserId) && counterpartUserId > 0) {
4705
4707
  item.peerUserId = counterpartUserId;
4706
4708
  privateUserIdByIndex.set(itemIndex, counterpartUserId);
@@ -4714,7 +4716,7 @@ var SourceService = class {
4714
4716
  }
4715
4717
  const hasMore = data.has_more === true;
4716
4718
  const totalValue = data.total ?? data.total_count;
4717
- const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$21(totalValue));
4719
+ const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$22(totalValue));
4718
4720
  const nextPageCursor = objectValue$1(data.next_page_cursor);
4719
4721
  return {
4720
4722
  directory,
@@ -4770,16 +4772,16 @@ var SourceService = class {
4770
4772
  continue;
4771
4773
  }
4772
4774
  const snapshotsByUid = /* @__PURE__ */ new Map();
4773
- for (const raw of listValue$22(data.items)) {
4775
+ for (const raw of listValue$23(data.items)) {
4774
4776
  const snapshot = objectValue$1(raw);
4775
4777
  const uid = stringValue$1(snapshot.chat_session_uid).trim();
4776
4778
  const index = indexByGroupUid.get(uid);
4777
4779
  if (index === void 0 || !groupUids.includes(uid)) continue;
4778
- 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);
4780
+ const memberIds = listValue$23(snapshot.members).map((member) => numberValue$22(objectValue$1(member).user_id)).filter((userId) => Number.isSafeInteger(userId) && userId > 0).slice(0, 5);
4779
4781
  const projection = {
4780
- memberCount: Math.max(0, Math.trunc(numberValue$21(snapshot.member_count))),
4782
+ memberCount: Math.max(0, Math.trunc(numberValue$22(snapshot.member_count))),
4781
4783
  strategy: stringValue$1(snapshot.strategy).trim(),
4782
- computedAtMillis: numberValue$21(snapshot.computed_at),
4784
+ computedAtMillis: numberValue$22(snapshot.computed_at),
4783
4785
  memberIds
4784
4786
  };
4785
4787
  snapshotsByUid.set(uid, projection);
@@ -4858,7 +4860,7 @@ var SourceService = class {
4858
4860
  const kind = parsed.kind;
4859
4861
  const result = {
4860
4862
  version: 1,
4861
- userId: numberValue$21(parsed.userId),
4863
+ userId: numberValue$22(parsed.userId),
4862
4864
  kind: isSourceKind(kind) ? kind : "default_category",
4863
4865
  ownerRef: stringValue$1(parsed.ownerRef).trim(),
4864
4866
  displayName: isSourceKind(kind) && kind === "default_category" ? "未分类" : stringValue$1(parsed.displayName).trim(),
@@ -4868,7 +4870,7 @@ var SourceService = class {
4868
4870
  })() : {},
4869
4871
  ...kind === "private_chat" || kind === "group_chat" ? (() => {
4870
4872
  const sidebarSubjectUid = stringValue$1(parsed.sidebarSubjectUid).trim();
4871
- const sidebarHiddenAnchorTimestamp = Math.max(0, numberValue$21(parsed.sidebarHiddenAnchorTimestamp));
4873
+ const sidebarHiddenAnchorTimestamp = Math.max(0, numberValue$22(parsed.sidebarHiddenAnchorTimestamp));
4872
4874
  return sidebarSubjectUid === "" ? {} : {
4873
4875
  sidebarSubjectUid,
4874
4876
  sidebarHiddenAnchorTimestamp
@@ -4902,16 +4904,16 @@ var SourceService = class {
4902
4904
  const unread = objectValue$1(bundle.unread_snapshot);
4903
4905
  const currentPolicy = objectValue$1(bundle.current_policy);
4904
4906
  const isMuted = chatMessageDnd$1(currentPolicy) ?? cached?.isMuted ?? false;
4905
- const isPinned = numberValue$21(currentPolicy.pin_state) === 2;
4907
+ const isPinned = numberValue$22(currentPolicy.pin_state) === 2;
4906
4908
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
4907
- const sessionKind = numberValue$21(chatSession.session_kind);
4909
+ const sessionKind = numberValue$22(chatSession.session_kind);
4908
4910
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
4909
4911
  if (uid === "" || kind === void 0) throw new Error("invalid chat display snapshot");
4910
4912
  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 || "未命名会话";
4911
4913
  const latestItem = [...timelineItems].sort((left, right) => (right.sequence ?? 0) - (left.sequence ?? 0))[0];
4912
4914
  const latestPreview = latestItem === void 0 ? cached?.latestPreview : latestItem.forwardRecords === void 0 ? arkmeTimelineConversationPreview(latestItem) : `[转发] ${latestItem.forwardRecords.title}`;
4913
- const latestSequence = Math.max(numberValue$21(unread.session_last_seq ?? chatSession.last_seq), latestItem?.sequence ?? 0, cached?.latestSequence ?? 0);
4914
- const unreadCount = Math.max(0, Math.trunc(numberValue$21(unread.unread_count)));
4915
+ const latestSequence = Math.max(numberValue$22(unread.session_last_seq ?? chatSession.last_seq), latestItem?.sequence ?? 0, cached?.latestSequence ?? 0);
4916
+ const unreadCount = Math.max(0, Math.trunc(numberValue$22(unread.unread_count)));
4915
4917
  const latestMentionsViewer = latestItem?.isMe === false && latestItem.mentionsViewer === true;
4916
4918
  const backendMentionState = chatUnreadMentionState(unread);
4917
4919
  const hasUnreadMention = kind !== "group_chat" ? void 0 : unreadCount <= 0 ? false : backendMentionState === void 0 && !latestMentionsViewer ? cached?.hasUnreadMention : backendMentionState === true || latestMentionsViewer;
@@ -4929,7 +4931,7 @@ var SourceService = class {
4929
4931
  ...cached?.avatarRefs === void 0 ? {} : { avatarRefs: cached.avatarRefs },
4930
4932
  ...cached?.groupAvatar === void 0 ? {} : { groupAvatar: cached.groupAvatar },
4931
4933
  ...latestPreview === void 0 || latestPreview === "" ? {} : { latestPreview },
4932
- activeAtMillis: Math.max(numberValue$21(bundle.sort_active_at ?? chatSession.last_active_at), latestItem?.sendAtMillis ?? 0),
4934
+ activeAtMillis: Math.max(numberValue$22(bundle.sort_active_at ?? chatSession.last_active_at), latestItem?.sendAtMillis ?? 0),
4933
4935
  unreadCount,
4934
4936
  ...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
4935
4937
  isMuted,
@@ -4961,7 +4963,7 @@ var SourceService = class {
4961
4963
  const value = this.decodeCursor(cursor);
4962
4964
  if (value.directory !== "send_to_self") throw new ArkmePluginError("source-cursor-invalid", "发给自己的主题分页游标无效", false);
4963
4965
  const pageCursor = objectValue$1(value.pageCursor);
4964
- const offset = numberValue$21(value.offset);
4966
+ const offset = numberValue$22(value.offset);
4965
4967
  if (Object.keys(pageCursor).length === 0 && offset <= 0) throw new ArkmePluginError("source-cursor-invalid", "发给自己的主题分页游标无效", false);
4966
4968
  return {
4967
4969
  ...Object.keys(pageCursor).length === 0 ? {} : { pageCursor },
@@ -4979,13 +4981,13 @@ const BOT_REF_TTL_MILLIS = 30 * 6e4;
4979
4981
  const BOT_REF_CAP = 2e3;
4980
4982
  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;
4981
4983
  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;
4982
- function numberValue$20(value) {
4984
+ function numberValue$21(value) {
4983
4985
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
4984
4986
  }
4985
4987
  function booleanValue$11(value) {
4986
4988
  return value === true;
4987
4989
  }
4988
- function listValue$21(value) {
4990
+ function listValue$22(value) {
4989
4991
  return Array.isArray(value) ? value : [];
4990
4992
  }
4991
4993
  function arkmeNormalizeBotProvider(value) {
@@ -4993,7 +4995,7 @@ function arkmeNormalizeBotProvider(value) {
4993
4995
  if (normalized === "openclaw" || normalized === "webhook") return normalized;
4994
4996
  }
4995
4997
  function botPrivateChatTimestamp(value) {
4996
- const numeric = numberValue$20(value);
4998
+ const numeric = numberValue$21(value);
4997
4999
  if (numeric <= 0) return 0;
4998
5000
  if (numeric < 1e10) return numeric * 1e3;
4999
5001
  let millis = numeric;
@@ -5007,7 +5009,7 @@ function botWebhookSecurity(value) {
5007
5009
  keyword: stringValue$1(raw.keyword).trim(),
5008
5010
  tokenEnabled: booleanValue$11(raw.token_enabled ?? raw.signature_enabled),
5009
5011
  ipWhitelistEnabled: booleanValue$11(raw.ip_whitelist_enabled),
5010
- ipWhitelist: listValue$21(raw.ip_whitelist).map((item) => stringValue$1(item).trim()).filter((item) => item !== "").slice(0, 100)
5012
+ ipWhitelist: listValue$22(raw.ip_whitelist).map((item) => stringValue$1(item).trim()).filter((item) => item !== "").slice(0, 100)
5011
5013
  };
5012
5014
  }
5013
5015
  function botProfileSource(data) {
@@ -5086,7 +5088,7 @@ var BotService = class {
5086
5088
  }
5087
5089
  async listBots(options = {}) {
5088
5090
  const session = await this.runtime.requireSession();
5089
- const rawBots = listValue$21((await this.runtime.authenticatedBotPost("/api/v1/bot/list", {}, session, options.signal)).bots).map(objectValue$1);
5091
+ const rawBots = listValue$22((await this.runtime.authenticatedBotPost("/api/v1/bot/list", {}, session, options.signal)).bots).map(objectValue$1);
5090
5092
  const candidates = rawBots.filter((raw) => stringValue$1(raw.bot_id).trim() !== "" && stringValue$1(raw.name).trim() !== "" && arkmeNormalizeBotProvider(raw.provider) !== void 0);
5091
5093
  const chatTargetCounts = /* @__PURE__ */ new Map();
5092
5094
  for (const raw of rawBots) {
@@ -5112,7 +5114,7 @@ var BotService = class {
5112
5114
  async countBots(options = {}) {
5113
5115
  const session = await this.runtime.requireSession();
5114
5116
  const data = await this.runtime.authenticatedBotPost("/api/v1/bot/list", { limit: 0 }, session, options.signal);
5115
- return Math.max(0, numberValue$20(data.total ?? data.total_count));
5117
+ return Math.max(0, numberValue$21(data.total ?? data.total_count));
5116
5118
  }
5117
5119
  async createBot(input, options = {}) {
5118
5120
  const name = input.name.trim();
@@ -5248,7 +5250,7 @@ var BotService = class {
5248
5250
  const groupTargetBody = await this.resolveGroupBotBindingBody(group, session, options.signal);
5249
5251
  const data = await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", groupTargetBody, session, options.signal);
5250
5252
  const items = [];
5251
- for (const value of listValue$21(data.bots)) {
5253
+ for (const value of listValue$22(data.bots)) {
5252
5254
  const raw = objectValue$1(value);
5253
5255
  if (arkmeNormalizeBotProvider(raw.provider) !== "openclaw") continue;
5254
5256
  const summary = await this.groupBotSummaryFromData(raw, session.userId);
@@ -5300,7 +5302,7 @@ var BotService = class {
5300
5302
  }
5301
5303
  async listMentionableGroupBots(group, session, signal) {
5302
5304
  const groupTargetBody = await this.resolveGroupBotBindingBody(group, session, signal);
5303
- return listValue$21((await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", groupTargetBody, session, signal)).bots).flatMap((value) => {
5305
+ return listValue$22((await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", groupTargetBody, session, signal)).bots).flatMap((value) => {
5304
5306
  const raw = objectValue$1(value);
5305
5307
  if (arkmeNormalizeBotProvider(raw.provider) !== "openclaw" || !booleanValue$11(raw.installed)) return [];
5306
5308
  const botId = stringValue$1(raw.bot_id).trim();
@@ -5360,7 +5362,7 @@ var BotService = class {
5360
5362
  const raw = botProfileSource(data);
5361
5363
  if (stringValue$1(raw.bot_id).trim() !== reference.botId || arkmeNormalizeBotProvider(raw.provider) !== reference.provider) throw new ArkmePluginError("bot-contract-invalid", "Bot 配置响应与当前 Bot 不一致", true, 502);
5362
5364
  const summary = await this.botSummaryFromData(raw, userId, reference.target);
5363
- const joinedGroups = listValue$21(data.joined_groups).map((value) => objectValue$1(value)).map((group) => ({
5365
+ const joinedGroups = listValue$22(data.joined_groups).map((value) => objectValue$1(value)).map((group) => ({
5364
5366
  title: stringValue$1(group.subject_title).trim() || stringValue$1(group.subject_uid).trim() || "未命名群聊",
5365
5367
  installedAtMillis: botPrivateChatTimestamp(group.installed_at)
5366
5368
  }));
@@ -5372,7 +5374,7 @@ var BotService = class {
5372
5374
  tokenRevealEnabled: data.token_reveal_enabled !== false,
5373
5375
  gatewayUrl: stringValue$1(data.gateway_url).trim(),
5374
5376
  webhookUrl: stringValue$1(data.webhook_url).trim(),
5375
- recordCount: Math.max(-1, Math.trunc(numberValue$20(raw.record_count))),
5377
+ recordCount: Math.max(-1, Math.trunc(numberValue$21(raw.record_count))),
5376
5378
  webhookSecurity: botWebhookSecurity(data.webhook_security ?? raw.webhook_security),
5377
5379
  joinedGroups
5378
5380
  };
@@ -5499,14 +5501,14 @@ var BotService = class {
5499
5501
  };
5500
5502
  //#endregion
5501
5503
  //#region src/services/bot-conversation-service.ts
5502
- function numberValue$19(value) {
5504
+ function numberValue$20(value) {
5503
5505
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
5504
5506
  }
5505
- function listValue$20(value) {
5507
+ function listValue$21(value) {
5506
5508
  return Array.isArray(value) ? value : [];
5507
5509
  }
5508
5510
  function botConversationTimestamp(value) {
5509
- const numeric = numberValue$19(value);
5511
+ const numeric = numberValue$20(value);
5510
5512
  if (numeric <= 0) return 0;
5511
5513
  if (numeric < 1e10) return numeric * 1e3;
5512
5514
  let millis = numeric;
@@ -5514,7 +5516,7 @@ function botConversationTimestamp(value) {
5514
5516
  return millis;
5515
5517
  }
5516
5518
  function subjectAttachments(value) {
5517
- return listValue$20(value).map(objectValue$1).flatMap((raw) => {
5519
+ return listValue$21(value).map(objectValue$1).flatMap((raw) => {
5518
5520
  const kind = stringValue$1(raw.kind).trim();
5519
5521
  const fileName = stringValue$1(raw.file_name ?? raw.fileName).trim();
5520
5522
  const mimeType = stringValue$1(raw.mime_type ?? raw.mimeType).trim();
@@ -5524,11 +5526,11 @@ function subjectAttachments(value) {
5524
5526
  kind: kind || "file",
5525
5527
  fileName,
5526
5528
  mimeType,
5527
- size: Math.max(0, Math.trunc(numberValue$19(raw.size))),
5528
- durationMillis: Math.max(0, Math.trunc(numberValue$19(raw.duration_ms ?? raw.durationMillis))),
5529
- width: Math.max(0, Math.trunc(numberValue$19(raw.width))),
5530
- height: Math.max(0, Math.trunc(numberValue$19(raw.height))),
5531
- sortOrder: Math.max(0, Math.trunc(numberValue$19(raw.order ?? raw.sort_order ?? raw.sortOrder)))
5529
+ size: Math.max(0, Math.trunc(numberValue$20(raw.size))),
5530
+ durationMillis: Math.max(0, Math.trunc(numberValue$20(raw.duration_ms ?? raw.durationMillis))),
5531
+ width: Math.max(0, Math.trunc(numberValue$20(raw.width))),
5532
+ height: Math.max(0, Math.trunc(numberValue$20(raw.height))),
5533
+ sortOrder: Math.max(0, Math.trunc(numberValue$20(raw.order ?? raw.sort_order ?? raw.sortOrder)))
5532
5534
  }];
5533
5535
  });
5534
5536
  }
@@ -5597,7 +5599,7 @@ var SubjectBotConversationAdapter = class {
5597
5599
  if (stringValue$1(objectValue$1(data.user_message).message_id).trim() === "") throw new ArkmePluginError("bot-conversation-send-outcome-unknown", "消息可能已发送,但响应缺少确认标识;请刷新会话确认", false, 409);
5598
5600
  return {
5599
5601
  userMessage: subjectMessage(data.user_message, content),
5600
- 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
+ botMessages: dedupeMessages([...listValue$21(data.bot_messages).map((message) => subjectMessage(message)), ...Object.keys(objectValue$1(data.bot_message)).length === 0 ? [] : [subjectMessage(data.bot_message)]]),
5601
5603
  status: stringValue$1(data.status).trim() || "ok"
5602
5604
  };
5603
5605
  }
@@ -5615,7 +5617,7 @@ var SubjectBotConversationAdapter = class {
5615
5617
  }
5616
5618
  async read(context, signal) {
5617
5619
  this.subjectTarget(context);
5618
- 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
+ return { messages: dedupeMessages(listValue$21((await this.runtime.authenticatedBotPost("/api/v1/bot/private-chat/open", { bot_id: context.reference.botId }, context.session, signal)).messages).map((message) => subjectMessage(message))) };
5619
5621
  }
5620
5622
  subjectTarget(context) {
5621
5623
  if (context.reference.target.kind !== "subject") throw new ArkmePluginError("bot-conversation-owner-mismatch", "Bot 会话 owner 不匹配", false, 409);
@@ -5739,14 +5741,14 @@ var BotConversationService = class {
5739
5741
  //#region src/services/calendar-service.ts
5740
5742
  const MAX_CALENDAR_RANGE_DAYS = 62;
5741
5743
  const MAX_DAY_RECORD_LIMIT = 50;
5742
- function numberValue$18(value) {
5744
+ function numberValue$19(value) {
5743
5745
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
5744
5746
  }
5745
5747
  function booleanValue$10(value) {
5746
5748
  if (typeof value === "boolean") return value;
5747
5749
  if (typeof value === "number") return value !== 0;
5748
5750
  }
5749
- function listValue$19(value) {
5751
+ function listValue$20(value) {
5750
5752
  return Array.isArray(value) ? value : [];
5751
5753
  }
5752
5754
  function readCalendarDate(value, field) {
@@ -5779,7 +5781,7 @@ function boundedLimit$2(value) {
5779
5781
  return limit;
5780
5782
  }
5781
5783
  function contentAccessState(value) {
5782
- const code = Math.trunc(numberValue$18(value));
5784
+ const code = Math.trunc(numberValue$19(value));
5783
5785
  if (code === 1) return "available";
5784
5786
  if (code === 2) return "protected";
5785
5787
  return "unknown";
@@ -5826,7 +5828,7 @@ var CalendarService = class {
5826
5828
  endDate,
5827
5829
  timezone: stringValue$1(data.timezone).trim() || timezone,
5828
5830
  refreshedAtMillis: Date.now(),
5829
- days: listValue$19(data.daily_data).map((raw) => this.bucketDay(raw)).filter((day) => day !== void 0)
5831
+ days: listValue$20(data.daily_data).map((raw) => this.bucketDay(raw)).filter((day) => day !== void 0)
5830
5832
  };
5831
5833
  }
5832
5834
  async dayRecords(options) {
@@ -5852,14 +5854,14 @@ var CalendarService = class {
5852
5854
  cacheMs: 1e4,
5853
5855
  failureCooldownMs: 2e3
5854
5856
  });
5855
- const nextSendAt = Math.trunc(numberValue$18(data.next_cursor_send_at));
5857
+ const nextSendAt = Math.trunc(numberValue$19(data.next_cursor_send_at));
5856
5858
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
5857
5859
  return {
5858
5860
  scope: "self",
5859
5861
  bucketDate,
5860
5862
  timezone: stringValue$1(data.timezone).trim() || timezone,
5861
5863
  refreshedAtMillis: Date.now(),
5862
- items: listValue$19(data.items).filter((raw) => !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(stringValue$1(objectValue$1(raw).record_uid ?? objectValue$1(objectValue$1(raw).record_core).record_uid).trim())).map((raw) => this.dayRecord(raw)).filter((item) => item !== void 0),
5864
+ items: listValue$20(data.items).filter((raw) => !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(stringValue$1(objectValue$1(raw).record_uid ?? objectValue$1(objectValue$1(raw).record_core).record_uid).trim())).map((raw) => this.dayRecord(raw)).filter((item) => item !== void 0),
5863
5865
  hasMore: data.has_more === true,
5864
5866
  ...nextSendAt > 0 && nextUid !== "" ? { nextCursor: {
5865
5867
  sendAtMillis: nextSendAt,
@@ -5871,21 +5873,21 @@ var CalendarService = class {
5871
5873
  const item = objectValue$1(raw);
5872
5874
  const bucketDate = stringValue$1(item.bucket_date).trim();
5873
5875
  if (bucketDate === "") return void 0;
5874
- const protectedCount = Math.max(0, Math.trunc(numberValue$18(item.protected_count)));
5875
- const count = Math.max(0, Math.trunc(numberValue$18(item.count)) - protectedCount);
5876
+ const protectedCount = Math.max(0, Math.trunc(numberValue$19(item.protected_count)));
5877
+ const count = Math.max(0, Math.trunc(numberValue$19(item.count)) - protectedCount);
5876
5878
  return {
5877
5879
  bucketDate,
5878
5880
  count,
5879
5881
  protectedCount: 0,
5880
5882
  hasRecords: count > 0,
5881
- ...numberValue$18(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$18(item.first_send_at)) } : {}
5883
+ ...numberValue$19(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$19(item.first_send_at)) } : {}
5882
5884
  };
5883
5885
  }
5884
5886
  dayRecord(raw) {
5885
5887
  const item = objectValue$1(raw);
5886
5888
  const core = objectValue$1(item.record_core);
5887
5889
  const recordUid = stringValue$1(item.record_uid ?? core.record_uid).trim();
5888
- const sendAtMillis = Math.trunc(numberValue$18(item.send_at ?? core.send_at));
5890
+ const sendAtMillis = Math.trunc(numberValue$19(item.send_at ?? core.send_at));
5889
5891
  if (recordUid === "" || sendAtMillis <= 0) return void 0;
5890
5892
  const accessState = contentAccessState(core.content_access_state);
5891
5893
  if (accessState === "protected" || arkmePrivacyLockedRecord(item)) return void 0;
@@ -5906,9 +5908,9 @@ var CalendarService = class {
5906
5908
  preview,
5907
5909
  ...stringValue$1(topic.title).trim() === "" ? {} : { topicTitle: stringValue$1(topic.title).trim() },
5908
5910
  sourceKind: sourceKind(item),
5909
- creationSource: Math.trunc(numberValue$18(core.creation_source)),
5910
- templateKind: Math.trunc(numberValue$18(core.template_kind)),
5911
- displayKind: Math.trunc(numberValue$18(core.display_kind)),
5911
+ creationSource: Math.trunc(numberValue$19(core.creation_source)),
5912
+ templateKind: Math.trunc(numberValue$19(core.template_kind)),
5913
+ displayKind: Math.trunc(numberValue$19(core.display_kind)),
5912
5914
  protected: false,
5913
5915
  ...isUncategorized === void 0 ? {} : { isUncategorized },
5914
5916
  ...hasManualEdit === void 0 ? {} : { hasManualEdit },
@@ -5918,10 +5920,10 @@ var CalendarService = class {
5918
5920
  };
5919
5921
  //#endregion
5920
5922
  //#region src/services/call-history-service.ts
5921
- function listValue$18(value) {
5923
+ function listValue$19(value) {
5922
5924
  return Array.isArray(value) ? value : [];
5923
5925
  }
5924
- function numberValue$17(value) {
5926
+ function numberValue$18(value) {
5925
5927
  if (typeof value === "number" && Number.isFinite(value)) return value;
5926
5928
  if (typeof value === "string" && value.trim() !== "") {
5927
5929
  const parsed = Number(value.trim());
@@ -5935,7 +5937,7 @@ function booleanValue$9(value) {
5935
5937
  function firstValue(source, keys) {
5936
5938
  for (const key of keys) if (source[key] !== void 0 && source[key] !== null) return source[key];
5937
5939
  }
5938
- function firstString(source, keys) {
5940
+ function firstString$1(source, keys) {
5939
5941
  for (const key of keys) {
5940
5942
  const value = stringValue$1(source[key]).trim();
5941
5943
  if (value !== "") return value;
@@ -5944,25 +5946,25 @@ function firstString(source, keys) {
5944
5946
  }
5945
5947
  function firstNumber(source, keys) {
5946
5948
  for (const key of keys) {
5947
- const value = numberValue$17(source[key]);
5949
+ const value = numberValue$18(source[key]);
5948
5950
  if (value !== 0) return value;
5949
5951
  }
5950
5952
  return 0;
5951
5953
  }
5952
- function epochMillis(value) {
5953
- const raw = numberValue$17(value);
5954
+ function epochMillis$1(value) {
5955
+ const raw = numberValue$18(value);
5954
5956
  if (raw <= 0) return 0;
5955
5957
  return raw < 1e10 ? Math.trunc(raw * 1e3) : Math.trunc(raw);
5956
5958
  }
5957
5959
  function firstEpochMillis(source, keys) {
5958
5960
  for (const key of keys) {
5959
- const value = epochMillis(source[key]);
5961
+ const value = epochMillis$1(source[key]);
5960
5962
  if (value > 0) return value;
5961
5963
  }
5962
5964
  return 0;
5963
5965
  }
5964
5966
  function numberList(value) {
5965
- return listValue$18(value).map((item) => Math.trunc(numberValue$17(item))).filter((item) => Number.isSafeInteger(item) && item > 0);
5967
+ return listValue$19(value).map((item) => Math.trunc(numberValue$18(item))).filter((item) => Number.isSafeInteger(item) && item > 0);
5966
5968
  }
5967
5969
  function callMediaType(value) {
5968
5970
  if (value === 0 || value === "0") return "audio";
@@ -6061,7 +6063,7 @@ function videoRecordCandidateObjects(raw) {
6061
6063
  const item = objectValue$1(raw[key]);
6062
6064
  if (Object.keys(item).length > 0) values.push(item);
6063
6065
  }
6064
- for (const key of arrayKeys) for (const item of listValue$18(raw[key])) {
6066
+ for (const key of arrayKeys) for (const item of listValue$19(raw[key])) {
6065
6067
  const value = objectValue$1(item);
6066
6068
  if (Object.keys(value).length > 0) values.push(value);
6067
6069
  }
@@ -6103,7 +6105,7 @@ function perspectiveFromItem(item, viewerUserId) {
6103
6105
  "viewUserId"
6104
6106
  ]));
6105
6107
  if (userId > 0) return userId === viewerUserId ? "self" : "peer";
6106
- return perspectiveFromText(firstString(item, [
6108
+ return perspectiveFromText(firstString$1(item, [
6107
6109
  "perspective",
6108
6110
  "view",
6109
6111
  "viewType",
@@ -6157,12 +6159,12 @@ function videoPerspectiveFromItem(item, viewerUserId) {
6157
6159
  if (videoUrl === "" && posterUrl === "") return void 0;
6158
6160
  return {
6159
6161
  perspective: perspectiveFromItem(item, viewerUserId),
6160
- ...firstString(item, [
6162
+ ...firstString$1(item, [
6161
6163
  "label",
6162
6164
  "display_name",
6163
6165
  "displayName",
6164
6166
  "name"
6165
- ]) === "" ? {} : { label: firstString(item, [
6167
+ ]) === "" ? {} : { label: firstString$1(item, [
6166
6168
  "label",
6167
6169
  "display_name",
6168
6170
  "displayName",
@@ -6275,7 +6277,7 @@ var CallHistoryService = class {
6275
6277
  const validItems = (await Promise.all(this.historyItems(raw).map(async (item) => await this.normalizeHistoryItem(item, session.userId)))).filter((item) => item !== void 0);
6276
6278
  await this.attachPeerAvatars(validItems, session, signal);
6277
6279
  const hasMore = booleanValue$9(raw.has_more ?? raw.hasMore);
6278
- const nextCursor = firstString(raw, [
6280
+ const nextCursor = firstString$1(raw, [
6279
6281
  "next_cursor",
6280
6282
  "nextCursor",
6281
6283
  "cursor"
@@ -6313,7 +6315,7 @@ var CallHistoryService = class {
6313
6315
  return Math.min(50, raw);
6314
6316
  }
6315
6317
  historyItems(raw) {
6316
- return listValue$18(firstValue(raw, [
6318
+ return listValue$19(firstValue(raw, [
6317
6319
  "items",
6318
6320
  "records",
6319
6321
  "histories",
@@ -6326,9 +6328,9 @@ var CallHistoryService = class {
6326
6328
  ...raw,
6327
6329
  ...trtc
6328
6330
  };
6329
- const roomId = firstString(item, ["room_id", "roomId"]);
6331
+ const roomId = firstString$1(item, ["room_id", "roomId"]);
6330
6332
  if (roomId === "") return void 0;
6331
- const stableId = firstString(raw, ["stable_id", "stableId"]) || `trtc:${roomId}`;
6333
+ const stableId = firstString$1(raw, ["stable_id", "stableId"]) || `trtc:${roomId}`;
6332
6334
  const startedAtMillis = firstEpochMillis(item, [
6333
6335
  "started_at_millis",
6334
6336
  "startedAtMillis",
@@ -6350,7 +6352,7 @@ var CallHistoryService = class {
6350
6352
  "endTime"
6351
6353
  ]);
6352
6354
  const duration = durationSeconds(acceptedAtMillis, startedAtMillis, endedAtMillis);
6353
- const callResult = firstString(item, [
6355
+ const callResult = firstString$1(item, [
6354
6356
  "call_result",
6355
6357
  "callResult",
6356
6358
  "result"
@@ -6359,7 +6361,7 @@ var CallHistoryService = class {
6359
6361
  const calleeUserIds = numberList(item.callee_user_ids ?? item.calleeUserIds);
6360
6362
  const connectedUserIds = numberList(item.connected_user_ids ?? item.connectedUserIds);
6361
6363
  const peerUserId = this.resolvePeerUserId(viewerUserId, callerUserId, calleeUserIds, connectedUserIds, item);
6362
- const summaryText = safeSummary(firstString(item, [
6364
+ const summaryText = safeSummary(firstString$1(item, [
6363
6365
  "call_summary",
6364
6366
  "callSummary",
6365
6367
  "summary_text",
@@ -6372,7 +6374,7 @@ var CallHistoryService = class {
6372
6374
  "summary_updated_at",
6373
6375
  "summaryUpdatedAt"
6374
6376
  ]);
6375
- const displayName = firstString(item, [
6377
+ const displayName = firstString$1(item, [
6376
6378
  "peer_display_name",
6377
6379
  "peerDisplayName",
6378
6380
  "display_name",
@@ -6413,12 +6415,12 @@ var CallHistoryService = class {
6413
6415
  ...summaryUpdatedAtMillis > 0 ? { summaryUpdatedAtMillis } : {},
6414
6416
  canOpenDetail: true,
6415
6417
  canRedial: peerUserId > 0,
6416
- ...firstString(item, [
6418
+ ...firstString$1(item, [
6417
6419
  "chat_session_uid",
6418
6420
  "chatSessionUid",
6419
6421
  "resolved_chat_session_uid",
6420
6422
  "resolvedChatSessionUid"
6421
- ]) === "" ? {} : { chatSessionUid: firstString(item, [
6423
+ ]) === "" ? {} : { chatSessionUid: firstString$1(item, [
6422
6424
  "chat_session_uid",
6423
6425
  "chatSessionUid",
6424
6426
  "resolved_chat_session_uid",
@@ -6449,7 +6451,7 @@ var CallHistoryService = class {
6449
6451
  return [...calleeUserIds, ...connectedUserIds].find((userId) => userId > 0 && userId !== viewerUserId) ?? 0;
6450
6452
  }
6451
6453
  async recentContacts(raw, session, signal) {
6452
- const rawContacts = listValue$18(firstValue(raw, ["recent_contacts", "recentContacts"]));
6454
+ const rawContacts = listValue$19(firstValue(raw, ["recent_contacts", "recentContacts"]));
6453
6455
  if (rawContacts.length > 0) {
6454
6456
  const contacts = rawContacts.map((item) => this.recentContactFromRaw(objectValue$1(item))).filter((item) => item !== void 0);
6455
6457
  if (contacts.length > 0) return await this.attachRecentContactAvatars(contacts, session, signal);
@@ -6511,7 +6513,7 @@ var CallHistoryService = class {
6511
6513
  "peerUserId"
6512
6514
  ]));
6513
6515
  if (userId <= 0) return void 0;
6514
- const displayName = firstString(raw, [
6516
+ const displayName = firstString$1(raw, [
6515
6517
  "display_name",
6516
6518
  "displayName",
6517
6519
  "peer_display_name",
@@ -6556,12 +6558,12 @@ var CallHistoryService = class {
6556
6558
  "endTime"
6557
6559
  ]);
6558
6560
  const duration = durationSeconds(acceptedAtMillis, startedAtMillis, endedAtMillis);
6559
- const callResult = firstString(raw, [
6561
+ const callResult = firstString$1(raw, [
6560
6562
  "call_result",
6561
6563
  "callResult",
6562
6564
  "result"
6563
6565
  ]);
6564
- const summaryText = safeSummary(firstString(raw, [
6566
+ const summaryText = safeSummary(firstString$1(raw, [
6565
6567
  "call_summary",
6566
6568
  "callSummary",
6567
6569
  "summary_text",
@@ -6587,7 +6589,7 @@ var CallHistoryService = class {
6587
6589
  ...payload,
6588
6590
  issuedAtMillis: Date.now()
6589
6591
  }),
6590
- title: firstString(raw, [
6592
+ title: firstString$1(raw, [
6591
6593
  "title",
6592
6594
  "display_name",
6593
6595
  "displayName",
@@ -6630,14 +6632,14 @@ var CallHistoryService = class {
6630
6632
  }));
6631
6633
  }
6632
6634
  participants(raw, viewerUserId) {
6633
- return listValue$18(firstValue(raw, [
6635
+ return listValue$19(firstValue(raw, [
6634
6636
  "participants",
6635
6637
  "participant_profiles",
6636
6638
  "participantProfiles"
6637
6639
  ])).map((item) => {
6638
6640
  const rawItem = objectValue$1(item);
6639
6641
  const userId = Math.trunc(firstNumber(rawItem, ["user_id", "userId"]));
6640
- const displayName = firstString(rawItem, [
6642
+ const displayName = firstString$1(rawItem, [
6641
6643
  "display_name",
6642
6644
  "displayName",
6643
6645
  "name",
@@ -6653,19 +6655,19 @@ var CallHistoryService = class {
6653
6655
  }).filter((item) => item !== void 0).slice(0, 50);
6654
6656
  }
6655
6657
  transcriptSegments(raw) {
6656
- return listValue$18(firstValue(raw, [
6658
+ return listValue$19(firstValue(raw, [
6657
6659
  "segments",
6658
6660
  "transcript_segments",
6659
6661
  "room_transcript_segments"
6660
6662
  ])).map((item, index) => {
6661
6663
  const rawItem = objectValue$1(item);
6662
- const text = clippedText(firstString(rawItem, [
6664
+ const text = clippedText(firstString$1(rawItem, [
6663
6665
  "text",
6664
6666
  "transcript",
6665
6667
  "content"
6666
6668
  ]), 2e3);
6667
6669
  if (text === "") return void 0;
6668
- const segmentId = firstString(rawItem, [
6670
+ const segmentId = firstString$1(rawItem, [
6669
6671
  "segment_id",
6670
6672
  "segmentId",
6671
6673
  "id"
@@ -6678,7 +6680,7 @@ var CallHistoryService = class {
6678
6680
  ]));
6679
6681
  return {
6680
6682
  segmentId,
6681
- speakerDisplayName: firstString(rawItem, [
6683
+ speakerDisplayName: firstString$1(rawItem, [
6682
6684
  "speaker_display_name",
6683
6685
  "speakerDisplayName",
6684
6686
  "speaker_name",
@@ -6727,11 +6729,11 @@ var CallHistoryService = class {
6727
6729
  }
6728
6730
  const value = objectValue$1(payload);
6729
6731
  const parsed = {
6730
- version: numberValue$17(value.version),
6731
- userId: numberValue$17(value.userId),
6732
+ version: numberValue$18(value.version),
6733
+ userId: numberValue$18(value.userId),
6732
6734
  roomId: stringValue$1(value.roomId).trim(),
6733
6735
  stableId: stringValue$1(value.stableId).trim(),
6734
- issuedAtMillis: numberValue$17(value.issuedAtMillis)
6736
+ issuedAtMillis: numberValue$18(value.issuedAtMillis)
6735
6737
  };
6736
6738
  if (parsed.version !== 1 || parsed.userId !== expectedUserId || parsed.roomId === "" || parsed.stableId === "") throw new ArkmePluginError("call-ref-invalid", "通话记录引用无效,请重新查询通话记录", false, 400);
6737
6739
  return parsed;
@@ -7188,7 +7190,7 @@ var ArkmeChatRealtimeRuntime = class {
7188
7190
  //#endregion
7189
7191
  //#region src/services/chat-realtime-service.ts
7190
7192
  const MAX_PROJECTION_RETRIES = 5;
7191
- function listValue$17(value) {
7193
+ function listValue$18(value) {
7192
7194
  return Array.isArray(value) ? value : [];
7193
7195
  }
7194
7196
  function safeFailureMessage$5(error) {
@@ -7340,6 +7342,7 @@ var ChatRealtimeService = class {
7340
7342
  const session = await this.runtime.sessionStore.read();
7341
7343
  if (session === void 0) return;
7342
7344
  this.source.invalidateSourceListCache(session.userId, "send_to_self");
7345
+ this.runtime.invalidateKey(this.runtime.requestScope(session.userId), "calendar:");
7343
7346
  this.emitChatClientEvent({
7344
7347
  type: "projection-invalidated",
7345
7348
  revision: this.nextChatClientRevision(),
@@ -7480,7 +7483,7 @@ var ChatRealtimeService = class {
7480
7483
  lane: "background-read",
7481
7484
  key: `projection:display:${projectionBatchKey}`
7482
7485
  });
7483
- const bundles = new Map(listValue$17(displayData.items).map((raw) => {
7486
+ const bundles = new Map(listValue$18(displayData.items).map((raw) => {
7484
7487
  const bundle = objectValue$1(raw);
7485
7488
  return [stringValue$1(objectValue$1(bundle.session).chat_session_uid).trim(), bundle];
7486
7489
  }).filter(([uid]) => uid !== ""));
@@ -7570,6 +7573,7 @@ var ChatRealtimeService = class {
7570
7573
  }
7571
7574
  if (updates.length > 0) {
7572
7575
  this.source.invalidateSourceListCache(session.userId, "root");
7576
+ this.runtime.invalidateKey(this.runtime.requestScope(session.userId), "calendar:");
7573
7577
  this.emitChatClientEvent({
7574
7578
  type: "sessions-delta",
7575
7579
  revision: this.nextChatClientRevision(),
@@ -7601,17 +7605,794 @@ const ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES = 10 * 1024 * 1024;
7601
7605
  const ARKME_WORLD_PUBLISH_MAX_IMAGES = 27;
7602
7606
  const ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES = 20 * 1024 * 1024;
7603
7607
  //#endregion
7608
+ //#region src/services/record-service.ts
7609
+ function numberValue$17(value) {
7610
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
7611
+ }
7612
+ function listValue$17(value) {
7613
+ return Array.isArray(value) ? value : [];
7614
+ }
7615
+ const DSH_AGENT_INPUT_CREATION_SOURCE = 3;
7616
+ const MAX_DEFAULT_CATEGORY_FILTER_BACKFILL_PAGES = 5;
7617
+ function integerLikeValue$1(value) {
7618
+ if (typeof value === "number" && Number.isFinite(value)) return Math.trunc(value);
7619
+ if (typeof value === "string" && value.trim() !== "") {
7620
+ const parsed = Number(value.trim());
7621
+ if (Number.isFinite(parsed)) return Math.trunc(parsed);
7622
+ }
7623
+ return 0;
7624
+ }
7625
+ function recordCreationSource(raw) {
7626
+ const item = objectValue$1(raw);
7627
+ const core = objectValue$1(item.record_core);
7628
+ return integerLikeValue$1(item.creation_source ?? item.creationSource ?? core.creation_source ?? core.creationSource);
7629
+ }
7630
+ function isDSHAgentInputRecord(raw) {
7631
+ return recordCreationSource(raw) === DSH_AGENT_INPUT_CREATION_SOURCE;
7632
+ }
7633
+ function safeFailureMessage$4(error) {
7634
+ if (error instanceof ArkmePluginError) return error.message;
7635
+ if (error instanceof Error && error.message.trim() !== "") return error.message;
7636
+ return "未知错误";
7637
+ }
7638
+ function arkmeRecordCaptureContextPayload(input) {
7639
+ const clientName = input.clientName?.trim().slice(0, 120) ?? "";
7640
+ const networkName = input.networkName?.trim().slice(0, 120) ?? "";
7641
+ const electric = input.electric === void 0 ? void 0 : Math.trunc(input.electric);
7642
+ const charge = Math.trunc(input.charge ?? 0);
7643
+ return {
7644
+ ...clientName === "" ? {} : { client_name: clientName },
7645
+ ...networkName === "" ? {} : { network_name: networkName },
7646
+ ...electric === void 0 || electric < 0 || electric > 100 ? {} : { electric },
7647
+ ...charge < 1 || charge > 3 ? {} : { charge }
7648
+ };
7649
+ }
7650
+ function normalizedRecordCaptureContext(input) {
7651
+ if (input === void 0) return void 0;
7652
+ const payload = arkmeRecordCaptureContextPayload(input);
7653
+ const clientName = stringValue$1(payload.client_name);
7654
+ const networkName = stringValue$1(payload.network_name);
7655
+ const electric = numberValue$17(payload.electric);
7656
+ const charge = numberValue$17(payload.charge);
7657
+ const normalized = {
7658
+ ...clientName === "" ? {} : { clientName },
7659
+ ...networkName === "" ? {} : { networkName },
7660
+ ...payload.electric === void 0 ? {} : { electric },
7661
+ ...charge === 0 ? {} : { charge }
7662
+ };
7663
+ return Object.keys(normalized).length === 0 ? void 0 : normalized;
7664
+ }
7665
+ var RecordService = class {
7666
+ runtime;
7667
+ media;
7668
+ source;
7669
+ privacy;
7670
+ constructor(runtime, media, source, privacy = new ArkmePrivacyVisibilityService(runtime)) {
7671
+ this.runtime = runtime;
7672
+ this.media = media;
7673
+ this.source = source;
7674
+ this.privacy = privacy;
7675
+ }
7676
+ async longArticleDetail(sourceRef, itemUid, signal) {
7677
+ const session = await this.runtime.requireSession();
7678
+ const source = await this.source.openSourceRef(sourceRef, session.userId);
7679
+ const uid = itemUid.trim();
7680
+ if (uid === "") throw new ArkmePluginError("long-article-item-invalid", "长文记录标识无效", false);
7681
+ const data = await this.runtime.authenticatedPost("/api/v1/records/detail", { record_uid: uid }, session, signal);
7682
+ const core = objectValue$1(data.record_core);
7683
+ const recordUid = stringValue$1(core.record_uid).trim();
7684
+ const templateKind = numberValue$17(core.template_kind);
7685
+ const displayKind = numberValue$17(core.display_kind);
7686
+ if (recordUid !== uid || templateKind !== 8 && displayKind !== 1) throw new ArkmePluginError("long-article-not-found", "未找到可用的长文详情", false, 404);
7687
+ const originContainerRef = stringValue$1(core.origin_container_ref).trim();
7688
+ if (source.kind === "topic") {
7689
+ if (stringValue$1(objectValue$1(data.topic_core).topic_uid).trim() !== source.ownerRef) throw new ArkmePluginError("long-article-source-mismatch", "长文不属于当前会话", false, 403);
7690
+ } else if (source.kind === "private_chat" || source.kind === "group_chat") {
7691
+ if (originContainerRef !== source.ownerRef) throw new ArkmePluginError("long-article-source-mismatch", "长文不属于当前会话", false, 403);
7692
+ } else if (numberValue$17(core.owner_user_id) !== session.userId) throw new ArkmePluginError("long-article-source-mismatch", "长文不属于当前会话", false, 403);
7693
+ const recordDurationMillis = Math.max(0, Math.trunc(numberValue$17(core.record_duration_millis)));
7694
+ const editDurationMillis = Math.max(0, Math.trunc(numberValue$17(core.edit_duration_millis)));
7695
+ return {
7696
+ sourceRef,
7697
+ itemUid: recordUid,
7698
+ title: stringValue$1(core.title),
7699
+ textContent: stringValue$1(core.text_content),
7700
+ sendAtMillis: Math.trunc(numberValue$17(core.send_at)),
7701
+ updateAtMillis: Math.trunc(numberValue$17(core.update_at)),
7702
+ recordDurationMillis,
7703
+ editDurationMillis,
7704
+ thinkingDurationMillis: recordDurationMillis + editDurationMillis,
7705
+ version: Math.trunc(numberValue$17(core.version)),
7706
+ editable: numberValue$17(core.owner_user_id) === session.userId && numberValue$17(core.creator_user_id) === session.userId
7707
+ };
7708
+ }
7709
+ async updateLongArticle(sourceRef, itemUid, input) {
7710
+ if (this.runtime.config.richMediaSendEnabled === false) throw new ArkmePluginError("rich-content-disabled", "长文编辑已被插件配置关闭", false, 403);
7711
+ const session = await this.runtime.requireSession();
7712
+ const detail = await this.longArticleDetail(sourceRef, itemUid);
7713
+ const title = input.title.trim();
7714
+ const textContent = input.textContent.trim();
7715
+ const editDurationMillis = Math.max(0, Math.trunc(input.editDurationMillis));
7716
+ if (!detail.editable) throw new ArkmePluginError("long-article-not-editable", "只能编辑自己发布的长文", false, 403);
7717
+ if (title === "" || title.length > 100 || textContent === "" || textContent.length > 4e4 || !Number.isSafeInteger(input.version) || input.version <= 0 || input.version !== detail.version) throw new ArkmePluginError("long-article-update-invalid", "长文内容或版本无效,请刷新后重试", false, 409);
7718
+ await this.runtime.authenticatedPost("/api/v1/records/update", {
7719
+ record_uid: detail.itemUid,
7720
+ template_kind: 1,
7721
+ display_kind: 1,
7722
+ title,
7723
+ text_content: textContent,
7724
+ record_duration_millis: detail.recordDurationMillis,
7725
+ edit_duration_millis: editDurationMillis,
7726
+ version: detail.version
7727
+ }, session);
7728
+ return await this.longArticleDetail(sourceRef, itemUid);
7729
+ }
7730
+ async getLongArticleDraft(sourceRef, itemUid) {
7731
+ const session = await this.runtime.requireSession();
7732
+ await this.source.openSourceRef(sourceRef, session.userId);
7733
+ return await this.runtime.stateStore.getLongArticleDraft(session.userId, sourceRef, itemUid?.trim() || void 0);
7734
+ }
7735
+ async putLongArticleDraft(draft) {
7736
+ const session = await this.runtime.requireSession();
7737
+ await this.source.openSourceRef(draft.sourceRef, session.userId);
7738
+ const itemUid = draft.itemUid?.trim() || void 0;
7739
+ if (draft.title.length > 100 || draft.textContent.length > 4e4 || draft.durationMillis < 0) throw new ArkmePluginError("long-article-draft-invalid", "长文草稿内容无效", false);
7740
+ await this.runtime.stateStore.putLongArticleDraft(session.userId, {
7741
+ sourceRef: draft.sourceRef,
7742
+ ...itemUid === void 0 ? {} : { itemUid },
7743
+ title: draft.title,
7744
+ textContent: draft.textContent,
7745
+ durationMillis: Math.max(0, Math.trunc(draft.durationMillis)),
7746
+ updatedAtMillis: Date.now()
7747
+ });
7748
+ }
7749
+ async removeLongArticleDraft(sourceRef, itemUid) {
7750
+ const session = await this.runtime.requireSession();
7751
+ await this.source.openSourceRef(sourceRef, session.userId);
7752
+ await this.runtime.stateStore.removeLongArticleDraft(session.userId, sourceRef, itemUid?.trim() || void 0);
7753
+ }
7754
+ async cachedSnapshot() {
7755
+ const session = await this.runtime.requireSession();
7756
+ return await this.runtime.stateStore.cachedSnapshot(session.userId);
7757
+ }
7758
+ async queryCached(options) {
7759
+ const session = await this.runtime.requireSession();
7760
+ return await this.runtime.stateStore.queryCached(session.userId, options);
7761
+ }
7762
+ async refreshLatest() {
7763
+ await Promise.all([this.summary(), this.list(50)]);
7764
+ }
7765
+ async refreshSnapshot() {
7766
+ await this.refreshLatest();
7767
+ return await this.cachedSnapshot();
7768
+ }
7769
+ async summary() {
7770
+ const session = await this.runtime.requireSession();
7771
+ const data = await this.runtime.authenticatedPost("/api/v1/records/uncategorized/summary", {}, session);
7772
+ const summary = {
7773
+ recordCount: numberValue$17(data.record_count),
7774
+ wordsCount: numberValue$17(data.words_count ?? data.available_text_rune_count),
7775
+ totalSec: numberValue$17(data.total_sec ?? data.available_voice_duration_sec)
7776
+ };
7777
+ await this.runtime.stateStore.cacheSummary(session.userId, summary);
7778
+ return summary;
7779
+ }
7780
+ async list(limit, cursor) {
7781
+ const session = await this.runtime.requireSession();
7782
+ const lockedRecordUids = await this.privacy.lockedRecordUids(session);
7783
+ const normalizedLimit = Math.min(50, Math.max(1, Math.trunc(limit || 30)));
7784
+ let requestCursor = cursor;
7785
+ let hasMore = false;
7786
+ let backendNextCursor;
7787
+ const visibleRawItems = [];
7788
+ for (let pageIndex = 0; pageIndex < MAX_DEFAULT_CATEGORY_FILTER_BACKFILL_PAGES; pageIndex += 1) {
7789
+ const data = await this.runtime.authenticatedPost("/api/v1/records/uncategorized/query", {
7790
+ limit: normalizedLimit,
7791
+ ...requestCursor === void 0 ? {} : {
7792
+ cursor_send_at: requestCursor.sendAtMillis,
7793
+ cursor_record_uid: requestCursor.recordUid
7794
+ }
7795
+ }, session);
7796
+ const rawPageItems = listValue$17(data.items);
7797
+ const visiblePageItems = rawPageItems.filter((raw) => !isDSHAgentInputRecord(raw) && !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(this.recordUid(raw)));
7798
+ visibleRawItems.push(...visiblePageItems);
7799
+ hasMore = data.has_more === true;
7800
+ const nextSendAt = numberValue$17(data.next_cursor_send_at);
7801
+ const nextUid = stringValue$1(data.next_cursor_record_uid);
7802
+ backendNextCursor = nextSendAt > 0 && nextUid !== "" ? {
7803
+ sendAtMillis: nextSendAt,
7804
+ recordUid: nextUid
7805
+ } : void 0;
7806
+ const filteredCount = rawPageItems.length - visiblePageItems.length;
7807
+ if (visibleRawItems.length >= normalizedLimit || !hasMore || backendNextCursor === void 0 || filteredCount === 0) break;
7808
+ requestCursor = backendNextCursor;
7809
+ }
7810
+ const visibleOverflow = visibleRawItems.length > normalizedLimit;
7811
+ const rawItems = visibleRawItems.slice(0, normalizedLimit);
7812
+ const media = await this.media.hydrateRecordMediaPage(rawItems, session);
7813
+ const items = rawItems.map((raw) => {
7814
+ const recordUid = this.recordUid(raw);
7815
+ const displayItems = media.displayItemsByRecordUid.get(recordUid);
7816
+ return this.recordItem(raw, session.userId, {
7817
+ ...displayItems === void 0 ? {} : { displayItems },
7818
+ mediaUnavailable: media.unavailableRecordUids.has(recordUid)
7819
+ });
7820
+ }).filter((item) => item !== void 0);
7821
+ const lastVisibleRaw = rawItems.at(-1);
7822
+ const lastVisibleCursor = lastVisibleRaw === void 0 ? void 0 : {
7823
+ sendAtMillis: numberValue$17(objectValue$1(lastVisibleRaw).send_at ?? objectValue$1(objectValue$1(lastVisibleRaw).record_core).send_at),
7824
+ recordUid: this.recordUid(lastVisibleRaw)
7825
+ };
7826
+ const filledRequestedPage = rawItems.length >= normalizedLimit;
7827
+ const nextCursor = lastVisibleCursor !== void 0 && lastVisibleCursor.sendAtMillis > 0 && lastVisibleCursor.recordUid !== "" && (visibleOverflow || filledRequestedPage && hasMore) ? lastVisibleCursor : backendNextCursor;
7828
+ const page = {
7829
+ items,
7830
+ hasMore: hasMore || visibleOverflow,
7831
+ ...nextCursor === void 0 ? {} : { nextCursor }
7832
+ };
7833
+ await this.runtime.stateStore.cachePage(session.userId, page, cursor);
7834
+ return page;
7835
+ }
7836
+ async createText(recordUid, textContent, options = {}) {
7837
+ const session = await this.runtime.requireSession();
7838
+ const normalizedUid = recordUid.trim();
7839
+ const normalizedText = textContent.trim();
7840
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
7841
+ if (normalizedText === "") throw new ArkmePluginError("record-text-empty", "请输入要发给自己的内容", false);
7842
+ if (normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-text-too-long", `内容不能超过 ${this.runtime.config.maxTextLength} 个字符`, false);
7843
+ const now = Date.now();
7844
+ const recordDurationMillis = Math.max(0, Math.trunc(options.recordDurationMillis ?? 0));
7845
+ const captureContext = normalizedRecordCaptureContext(options.captureContext);
7846
+ const pending = {
7847
+ recordUid: normalizedUid,
7848
+ textContent: normalizedText,
7849
+ createdAtMillis: now,
7850
+ sendAtMillis: now,
7851
+ attempts: 0,
7852
+ ...recordDurationMillis === 0 ? {} : { recordDurationMillis },
7853
+ ...captureContext === void 0 ? {} : { captureContext }
7854
+ };
7855
+ await this.runtime.stateStore.putPending(session.userId, pending);
7856
+ return await this.sendPending(session, pending);
7857
+ }
7858
+ async createTextForConversation(recordUid, textContent, options = {}) {
7859
+ try {
7860
+ return {
7861
+ ...await this.createText(recordUid, textContent, options),
7862
+ localState: "synced"
7863
+ };
7864
+ } catch (error) {
7865
+ const session = await this.runtime.requireSession();
7866
+ const pending = (await this.runtime.stateStore.listPending(session.userId)).find((item) => item.recordUid === recordUid);
7867
+ if (pending === void 0) throw error;
7868
+ return {
7869
+ recordUid,
7870
+ status: 0,
7871
+ localState: "failed",
7872
+ error: pending.lastError ?? safeFailureMessage$4(error)
7873
+ };
7874
+ }
7875
+ }
7876
+ async createDSHAgentInputText(recordUid, textContent, sendAtMillis) {
7877
+ const session = await this.runtime.requireSession();
7878
+ const normalizedUid = recordUid.trim();
7879
+ const normalizedText = textContent.trim();
7880
+ const normalizedSendAt = Math.max(0, Math.trunc(sendAtMillis));
7881
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
7882
+ if (normalizedText === "") throw new ArkmePluginError("record-text-empty", "请输入要发给自己的内容", false);
7883
+ if (normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-text-too-long", `内容不能超过 ${this.runtime.config.maxTextLength} 个字符`, false);
7884
+ const data = await this.runtime.authenticatedPost("/api/v1/records/dsh-agent-input/create", {
7885
+ record_uid: normalizedUid,
7886
+ template_kind: 1,
7887
+ title: "",
7888
+ text_content: normalizedText,
7889
+ send_at: normalizedSendAt > 0 ? normalizedSendAt : Date.now()
7890
+ }, session, void 0, {
7891
+ scope: this.runtime.requestScope(session.userId),
7892
+ key: `dsh-agent-input:${normalizedUid}`
7893
+ });
7894
+ this.runtime.invalidateScope(this.runtime.requestScope(session.userId));
7895
+ return {
7896
+ recordUid: stringValue$1(data.record_uid) || normalizedUid,
7897
+ status: numberValue$17(data.status)
7898
+ };
7899
+ }
7900
+ async createFileAssetsForConversation(recordUid, textContent, assets) {
7901
+ const session = await this.runtime.requireSession();
7902
+ const normalizedUid = recordUid.trim();
7903
+ const normalizedText = textContent.trim();
7904
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
7905
+ if (assets.length === 0 || assets.length > 20 || normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-file-assets-invalid", "附件内容为空、过长或数量超限", false);
7906
+ for (const asset of assets) if (!/^[A-Za-z0-9._:-]{8,256}$/.test(asset.fileAssetUid) || asset.fileName.trim() === "" || asset.fileName.length > 255 || !Number.isSafeInteger(asset.size) || asset.size <= 0 || ![
7907
+ 1,
7908
+ 2,
7909
+ 3,
7910
+ 4
7911
+ ].includes(asset.fileKind)) throw new ArkmePluginError("record-file-asset-invalid", "附件资产参数无效", false);
7912
+ const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
7913
+ record_uid: normalizedUid,
7914
+ template_kind: 2,
7915
+ display_kind: 0,
7916
+ title: "",
7917
+ text_content: normalizedText,
7918
+ content_payload: {
7919
+ payload_kind: 2,
7920
+ schema_version: 1,
7921
+ text_state: normalizedText === "" ? 3 : 1,
7922
+ media_refs: assets.map((asset, index) => ({
7923
+ file_asset_uid: asset.fileAssetUid,
7924
+ content_file_role: 1,
7925
+ render_role: 1,
7926
+ sort_order: index,
7927
+ file_name: asset.fileName
7928
+ }))
7929
+ },
7930
+ send_at: Date.now()
7931
+ }, session);
7932
+ return {
7933
+ recordUid: stringValue$1(data.record_uid).trim() || normalizedUid,
7934
+ status: numberValue$17(data.status)
7935
+ };
7936
+ }
7937
+ async pendingWrites() {
7938
+ const session = await this.runtime.requireSession();
7939
+ return await this.runtime.stateStore.listPending(session.userId);
7940
+ }
7941
+ async retryPending(recordUid) {
7942
+ const session = await this.runtime.requireSession();
7943
+ const pending = (await this.runtime.stateStore.listPending(session.userId)).find((item) => item.recordUid === recordUid);
7944
+ if (pending === void 0) throw new ArkmePluginError("outbox-entry-not-found", "待重试内容不存在", false, 404);
7945
+ return await this.sendPending(session, pending);
7946
+ }
7947
+ async sendPending(session, pending) {
7948
+ try {
7949
+ const captureContext = pending.captureContext === void 0 ? void 0 : arkmeRecordCaptureContextPayload(pending.captureContext);
7950
+ const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
7951
+ record_uid: pending.recordUid,
7952
+ template_kind: 1,
7953
+ title: "",
7954
+ text_content: pending.textContent,
7955
+ ...Math.max(0, Math.trunc(pending.recordDurationMillis ?? 0)) === 0 ? {} : { record_duration_millis: Math.max(0, Math.trunc(pending.recordDurationMillis ?? 0)) },
7956
+ ...captureContext === void 0 || Object.keys(captureContext).length === 0 ? {} : { capture_context: captureContext },
7957
+ send_at: pending.sendAtMillis
7958
+ }, session);
7959
+ const result = {
7960
+ recordUid: stringValue$1(data.record_uid) || pending.recordUid,
7961
+ status: numberValue$17(data.status)
7962
+ };
7963
+ await this.runtime.stateStore.markSynced(session.userId, pending.recordUid, result.status);
7964
+ return result;
7965
+ } catch (error) {
7966
+ await this.runtime.stateStore.markAttempt(session.userId, pending.recordUid, safeFailureMessage$4(error));
7967
+ throw error;
7968
+ }
7969
+ }
7970
+ recordUid(raw) {
7971
+ const item = objectValue$1(raw);
7972
+ return stringValue$1(item.record_uid ?? objectValue$1(item.record_core).record_uid).trim();
7973
+ }
7974
+ recordTimelineItem(item) {
7975
+ return {
7976
+ itemUid: item.recordUid,
7977
+ senderName: "我",
7978
+ isMe: true,
7979
+ sendAtMillis: item.sendAtMillis,
7980
+ title: item.title,
7981
+ textContent: item.textContent,
7982
+ status: item.status,
7983
+ templateKind: item.templateKind,
7984
+ version: item.version,
7985
+ ...item.displayKind === void 0 ? {} : { displayKind: item.displayKind },
7986
+ ...item.contentBlocks === void 0 ? {} : { contentBlocks: item.contentBlocks },
7987
+ ...item.mediaUnavailable === true ? { mediaUnavailable: true } : {}
7988
+ };
7989
+ }
7990
+ recordTimelineItemFromRaw(raw, userId, options = {}) {
7991
+ const item = objectValue$1(raw);
7992
+ const core = objectValue$1(item.record_core);
7993
+ return {
7994
+ itemUid: stringValue$1(item.record_uid ?? core.record_uid).trim(),
7995
+ senderName: stringValue$1(item.nickname).trim() || "我",
7996
+ isMe: options.isMe ?? numberValue$17(item.creator_user_id ?? item.owner_user_id ?? core.creator_user_id ?? core.owner_user_id) === userId,
7997
+ sendAtMillis: numberValue$17(item.send_at ?? core.send_at),
7998
+ title: stringValue$1(item.title ?? core.title),
7999
+ textContent: stringValue$1(item.text_content ?? core.text_content),
8000
+ status: numberValue$17(item.status ?? core.status),
8001
+ templateKind: numberValue$17(item.template_kind ?? core.template_kind),
8002
+ displayKind: numberValue$17(item.display_kind ?? core.display_kind),
8003
+ version: numberValue$17(item.version ?? core.version),
8004
+ updateAtMillis: numberValue$17(item.update_at ?? core.update_at),
8005
+ recordDurationMillis: numberValue$17(item.record_duration_millis ?? core.record_duration_millis),
8006
+ editDurationMillis: numberValue$17(item.edit_duration_millis ?? core.edit_duration_millis),
8007
+ contentBlocks: this.media.richContentBlocks(raw, userId, options.displayItems),
8008
+ ...options.selfTopic === void 0 ? {} : { selfTopic: options.selfTopic },
8009
+ ...options.mediaUnavailable === true ? { mediaUnavailable: true } : {}
8010
+ };
8011
+ }
8012
+ recordItem(raw, userId, options = {}) {
8013
+ const item = objectValue$1(raw);
8014
+ const core = objectValue$1(item.record_core);
8015
+ const recordUid = stringValue$1(item.record_uid ?? core.record_uid).trim();
8016
+ if (recordUid === "") return void 0;
8017
+ return {
8018
+ recordUid,
8019
+ sendAtMillis: numberValue$17(item.send_at ?? core.send_at),
8020
+ title: stringValue$1(core.title),
8021
+ textContent: stringValue$1(core.text_content),
8022
+ templateKind: numberValue$17(core.template_kind),
8023
+ status: numberValue$17(core.status),
8024
+ version: numberValue$17(core.version),
8025
+ creationSource: recordCreationSource(raw),
8026
+ displayKind: numberValue$17(item.display_kind ?? core.display_kind),
8027
+ ...userId === void 0 ? {} : { contentBlocks: this.media.richContentBlocks(raw, userId, options.displayItems) },
8028
+ ...options.mediaUnavailable === true ? { mediaUnavailable: true } : {}
8029
+ };
8030
+ }
8031
+ isDSHAgentInput(raw) {
8032
+ return isDSHAgentInputRecord(raw);
8033
+ }
8034
+ isPrivacyLocked(raw) {
8035
+ return arkmePrivacyLockedRecord(raw);
8036
+ }
8037
+ async syncHistory(maxPages = 20, signal) {
8038
+ const pageCap = Math.min(20, Math.max(1, Math.trunc(maxPages)));
8039
+ await this.refreshLatest();
8040
+ let snapshot = await this.cachedSnapshot();
8041
+ let pages = 0;
8042
+ while (snapshot.hasMore && snapshot.nextCursor !== void 0 && pages < pageCap) {
8043
+ if (signal?.aborted === true) throw new Error("Arkme历史同步已取消");
8044
+ await this.list(50, snapshot.nextCursor);
8045
+ pages += 1;
8046
+ snapshot = await this.cachedSnapshot();
8047
+ }
8048
+ return {
8049
+ pages,
8050
+ complete: !snapshot.hasMore
8051
+ };
8052
+ }
8053
+ };
8054
+ //#endregion
7604
8055
  //#region src/services/chat-service.ts
7605
8056
  const OFFICIAL_AUTHOR_USER_ID = 11;
7606
8057
  const OFFICIAL_AUTHOR_FALLBACK_DISPLAY_NAME = "即我作者";
7607
8058
  const MAX_MESSAGE_COPY_LINK_ITEMS = 100;
7608
8059
  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;
8060
+ const CHAT_MEMBER_REF_PREFIX = "arkme-chat-member-v1";
8061
+ const CHAT_HUMAN_MENTION_REF_PREFIX = "arkme-chat-human-mention-v1";
7609
8062
  function numberValue$16(value) {
7610
8063
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
7611
8064
  }
8065
+ function finiteNumberValue(value) {
8066
+ if (typeof value === "number" && Number.isFinite(value)) return value;
8067
+ if (typeof value === "string" && value.trim() !== "") {
8068
+ const parsed = Number(value);
8069
+ if (Number.isFinite(parsed)) return parsed;
8070
+ }
8071
+ }
8072
+ function positiveNumberValue$1(...values) {
8073
+ for (const value of values) {
8074
+ const parsed = finiteNumberValue(value);
8075
+ if (parsed !== void 0 && parsed > 0) return parsed;
8076
+ }
8077
+ }
8078
+ function nonNegativeNumberValue(...values) {
8079
+ for (const value of values) {
8080
+ const parsed = finiteNumberValue(value);
8081
+ if (parsed !== void 0 && parsed >= 0) return parsed;
8082
+ }
8083
+ }
7612
8084
  function booleanValue$8(value) {
7613
8085
  return value === true;
7614
8086
  }
8087
+ function timelineCaptureContext(payload) {
8088
+ const raw = objectValue$1(payload.capture_context ?? payload.captureContext);
8089
+ const clientName = stringValue$1(raw.client_name ?? raw.clientName ?? payload.client_name ?? payload.clientName ?? payload.device_name ?? payload.deviceName).trim();
8090
+ const networkName = stringValue$1(raw.network_name ?? raw.networkName ?? payload.network_name ?? payload.networkName ?? payload.network).trim();
8091
+ const electricity = objectValue$1(payload.electricity);
8092
+ const electricValue = finiteNumberValue(raw.electric ?? payload.electric ?? electricity.electric);
8093
+ const electric = electricValue === void 0 ? void 0 : Math.trunc(electricValue);
8094
+ const charge = Math.trunc(numberValue$16(raw.charge ?? payload.charge ?? electricity.charge));
8095
+ const context = {
8096
+ ...clientName === "" ? {} : { clientName },
8097
+ ...networkName === "" ? {} : { networkName },
8098
+ ...electric === void 0 || electric < 0 || electric > 100 ? {} : { electric },
8099
+ ...charge < 1 || charge > 3 ? {} : { charge }
8100
+ };
8101
+ return Object.keys(context).length === 0 ? void 0 : context;
8102
+ }
8103
+ function epochMillis(value) {
8104
+ const parsed = finiteNumberValue(value);
8105
+ if (parsed === void 0 || parsed <= 0) return void 0;
8106
+ return Math.trunc(parsed < 1e11 ? parsed * 1e3 : parsed);
8107
+ }
8108
+ /**
8109
+ * Record lifecycle timestamps are canonical fields of the record itself.
8110
+ * Payload and extra metadata may carry stale (or zero) copies, so they can
8111
+ * only be used as a final fallback. This matches the Flutter record parser,
8112
+ * which creates the Record timestamp model from record_core first.
8113
+ */
8114
+ function firstRecordEpochMillis(sources, keys) {
8115
+ for (const source of sources) for (const key of keys) {
8116
+ const value = epochMillis(source[key]);
8117
+ if (value !== void 0) return value;
8118
+ }
8119
+ }
8120
+ function firstSnapshotText(...values) {
8121
+ for (const value of values) {
8122
+ const text = stringValue$1(value).trim();
8123
+ if (text !== "") return text;
8124
+ }
8125
+ }
8126
+ function snapshotDateLabel(value) {
8127
+ if (value === void 0) return void 0;
8128
+ const date = new Date(value);
8129
+ if (Number.isNaN(date.getTime())) return void 0;
8130
+ return new Intl.DateTimeFormat("zh-CN", {
8131
+ year: "numeric",
8132
+ month: "2-digit",
8133
+ day: "2-digit"
8134
+ }).format(date);
8135
+ }
8136
+ function snapshotMovement(value) {
8137
+ const rawSpeed = value.speed ?? value.speed_mps ?? value.speedMetersPerSecond;
8138
+ const speed = numberValue$16(rawSpeed);
8139
+ if (typeof rawSpeed === "number" && Number.isFinite(rawSpeed)) {
8140
+ const kmh = Math.round(speed * 3.6);
8141
+ return kmh <= 2 ? "静止" : kmh <= 5 ? `缓慢移动(${String(kmh)}km/h)` : kmh <= 60 ? `移动(${String(kmh)}km/h)` : `高速移动(${String(kmh)}km/h)`;
8142
+ }
8143
+ if (typeof value.activity !== "number" || !Number.isFinite(value.activity)) return void 0;
8144
+ const activity = Math.trunc(value.activity);
8145
+ return activity === 0 ? "静止" : activity === 1 ? "步行" : activity === 2 ? "跑步" : activity === 3 ? "骑行" : activity === 4 ? "驾车" : void 0;
8146
+ }
8147
+ function snapshotLocationCapture(value) {
8148
+ const latitude = finiteNumberValue(value.lat ?? value.latitude);
8149
+ const longitude = finiteNumberValue(value.lon ?? value.lng ?? value.longitude);
8150
+ if (latitude === void 0 || longitude === void 0 || latitude === 0 && longitude === 0) return void 0;
8151
+ const altitudeMeters = nonNegativeNumberValue(value.alt ?? value.altitude ?? value.altitude_meters);
8152
+ const accuracyMeters = nonNegativeNumberValue(value.accuracy ?? value.accuracy_meters ?? value.accuracyMeters);
8153
+ const speedMetersPerSecond = nonNegativeNumberValue(value.speed ?? value.speed_mps ?? value.speedMetersPerSecond);
8154
+ return {
8155
+ latitude,
8156
+ longitude,
8157
+ capturedAtMillis: epochMillis(value.captured_at ?? value.capturedAt) ?? Date.now(),
8158
+ ...accuracyMeters === void 0 ? {} : { accuracyMeters },
8159
+ ...altitudeMeters === void 0 ? {} : { altitudeMeters },
8160
+ ...speedMetersPerSecond === void 0 ? {} : { speedMetersPerSecond }
8161
+ };
8162
+ }
8163
+ function snapshotWeatherText(...values) {
8164
+ for (const value of values) {
8165
+ const direct = stringValue$1(value).trim();
8166
+ if (direct !== "") return direct;
8167
+ const weather = objectValue$1(value);
8168
+ if (Object.keys(weather).length === 0) continue;
8169
+ const temperature = finiteNumberValue(weather.temp ?? weather.temperature);
8170
+ const condition = firstSnapshotText(weather.weather, weather.weatherText, weather.weather_text, weather.condition_ch, weather.conditionCh, weather.condition);
8171
+ if (temperature !== void 0 || condition !== void 0) return `${temperature === void 0 ? "" : `${String(Math.round(temperature))}°`}${temperature === void 0 || condition === void 0 ? "" : " "}${condition ?? ""}`.trim();
8172
+ }
8173
+ }
8174
+ function snapshotLocationContext(value) {
8175
+ const root = objectValue$1(value);
8176
+ const nested = objectValue$1(root.data);
8177
+ const hasContext = (candidate) => [
8178
+ candidate.location,
8179
+ candidate.position_detail,
8180
+ candidate.positionDetail,
8181
+ candidate.weather
8182
+ ].some((entry) => Object.keys(parsedObject(entry)).length > 0 || stringValue$1(entry).trim() !== "");
8183
+ return hasContext(root) || !hasContext(nested) ? root : nested;
8184
+ }
8185
+ /**
8186
+ * `/records/detail` and `/records/location/context/get` intentionally split
8187
+ * their metadata. Flutter overlays the latter onto the former, retaining
8188
+ * every existing location field while allowing its resolved weather and
8189
+ * address to win. Keep that same shape before projecting the dialog detail.
8190
+ */
8191
+ function mergeSnapshotLocationContext(raw, contextValue) {
8192
+ const context = snapshotLocationContext(contextValue);
8193
+ if (Object.keys(context).length === 0) return raw;
8194
+ const location = {
8195
+ ...parsedObject(raw.location),
8196
+ ...parsedObject(raw.location_detail),
8197
+ ...parsedObject(context.location),
8198
+ ...parsedObject(context.location_detail)
8199
+ };
8200
+ const position = {
8201
+ ...parsedObject(raw.position_detail),
8202
+ ...parsedObject(raw.positionDetail),
8203
+ ...parsedObject(context.position_detail),
8204
+ ...parsedObject(context.positionDetail)
8205
+ };
8206
+ const weather = context.weather ?? context.weather_text ?? context.weatherText ?? raw.weather;
8207
+ return {
8208
+ ...raw,
8209
+ ...Object.keys(location).length === 0 ? {} : { location },
8210
+ ...Object.keys(position).length === 0 ? {} : { position_detail: position },
8211
+ ...weather === void 0 ? {} : { weather }
8212
+ };
8213
+ }
8214
+ function snapshotDetailFromChatRaw(raw, fallback) {
8215
+ const relation = objectValue$1(raw.relation);
8216
+ const recordCore = objectValue$1(raw.record_core ?? raw.recordCore ?? raw.record ?? raw);
8217
+ const nestedRecord = objectValue$1(raw.record);
8218
+ const record = {
8219
+ ...recordCore,
8220
+ ...nestedRecord
8221
+ };
8222
+ const rootPayload = {
8223
+ ...parsedObject(raw.payload),
8224
+ ...parsedObject(raw.content_payload),
8225
+ ...parsedObject(raw.contentPayload),
8226
+ ...parsedObject(raw.record_payload),
8227
+ ...parsedObject(raw.recordPayload)
8228
+ };
8229
+ const recordPayload = {
8230
+ ...parsedObject(record.payload),
8231
+ ...parsedObject(record.content_payload),
8232
+ ...parsedObject(record.contentPayload),
8233
+ ...parsedObject(record.record_payload),
8234
+ ...parsedObject(record.recordPayload)
8235
+ };
8236
+ const extra = {
8237
+ ...parsedObject(raw.extra),
8238
+ ...parsedObject(raw.record_extra),
8239
+ ...parsedObject(raw.recordExtra),
8240
+ ...parsedObject(raw.e),
8241
+ ...parsedObject(raw.i)
8242
+ };
8243
+ const recordExtra = {
8244
+ ...parsedObject(record.extra),
8245
+ ...parsedObject(record.record_extra),
8246
+ ...parsedObject(record.recordExtra),
8247
+ ...parsedObject(record.e),
8248
+ ...parsedObject(record.i)
8249
+ };
8250
+ const payload = {
8251
+ ...extra,
8252
+ ...recordExtra,
8253
+ ...rootPayload,
8254
+ ...recordPayload
8255
+ };
8256
+ const position = {
8257
+ ...parsedObject(payload.position_detail),
8258
+ ...parsedObject(payload.positionDetail),
8259
+ ...parsedObject(record.position_detail),
8260
+ ...parsedObject(record.positionDetail),
8261
+ ...parsedObject(raw.position_detail),
8262
+ ...parsedObject(raw.positionDetail)
8263
+ };
8264
+ const location = {
8265
+ ...parsedObject(payload.location),
8266
+ ...parsedObject(record.location),
8267
+ ...parsedObject(raw.location)
8268
+ };
8269
+ const values = {
8270
+ ...raw,
8271
+ ...record,
8272
+ ...payload
8273
+ };
8274
+ const recordDurationMillis = positiveNumberValue$1(values.record_duration_millis, values.recordDurationMillis, values.cost_mill_sec, values.costMillSec);
8275
+ const editDurationMillis = positiveNumberValue$1(values.edit_duration_millis, values.editDurationMillis, values.incr_cost_mill_sec, values.incrCostMillSec);
8276
+ const lifecycleSources = [
8277
+ recordCore,
8278
+ nestedRecord,
8279
+ raw,
8280
+ payload
8281
+ ];
8282
+ const startAtMillis = firstRecordEpochMillis(lifecycleSources, [
8283
+ "create_at",
8284
+ "created_at",
8285
+ "createAt",
8286
+ "createdAt",
8287
+ "create_time",
8288
+ "createTime",
8289
+ "create_time_stamp",
8290
+ "createTimeStamp",
8291
+ "create_timestamp",
8292
+ "createTimestamp",
8293
+ "create_at_ms",
8294
+ "createAtMillis",
8295
+ "created_at_ms",
8296
+ "createdAtMillis",
8297
+ "send_at",
8298
+ "sendAt",
8299
+ "send_time",
8300
+ "sendTime",
8301
+ "send_time_stamp",
8302
+ "sendTimeStamp",
8303
+ "send_timestamp",
8304
+ "sendTimestamp",
8305
+ "send_at_ms",
8306
+ "sendAtMillis"
8307
+ ]) ?? epochMillis(relation.attach_at ?? relation.attachAt);
8308
+ const sendAtMillis = firstRecordEpochMillis(lifecycleSources, [
8309
+ "send_at",
8310
+ "sendAt",
8311
+ "send_time",
8312
+ "sendTime",
8313
+ "send_time_stamp",
8314
+ "sendTimeStamp",
8315
+ "send_timestamp",
8316
+ "sendTimestamp",
8317
+ "send_at_ms",
8318
+ "sendAtMillis"
8319
+ ]) ?? epochMillis(relation.attach_at ?? relation.attachAt) ?? epochMillis(fallback.sendAtMillis);
8320
+ const updatedAtMillis = firstRecordEpochMillis(lifecycleSources, [
8321
+ "update_at",
8322
+ "updated_at",
8323
+ "updateAt",
8324
+ "updatedAt",
8325
+ "update_time_stamp",
8326
+ "updateTimeStamp",
8327
+ "update_timestamp",
8328
+ "updateTimestamp",
8329
+ "update_at_ms",
8330
+ "updateAtMillis"
8331
+ ]);
8332
+ const completeAtMillis = updatedAtMillis !== void 0 && updatedAtMillis > (sendAtMillis ?? 0) ? updatedAtMillis : sendAtMillis;
8333
+ const syncedAtMillis = firstRecordEpochMillis(lifecycleSources, [
8334
+ "upload_at",
8335
+ "uploadAt",
8336
+ "upload_timestamp",
8337
+ "uploadTimeStamp",
8338
+ "upload_time_stamp_v2",
8339
+ "uploadTimeStampV2",
8340
+ "upload_at_v2",
8341
+ "uploadAtV2",
8342
+ "upload_at_ms",
8343
+ "uploadAtMillis"
8344
+ ]);
8345
+ const captureContext = timelineCaptureContext(values);
8346
+ const locationCapture = snapshotLocationCapture({
8347
+ ...location,
8348
+ ...position,
8349
+ ...objectValue$1(values.position)
8350
+ });
8351
+ const locationLabel = firstSnapshotText(position.poi, position.poi_name, position.poiName, position.address, position.road, location.poi_name, location.poiName, location.address, location.name);
8352
+ const weather = snapshotWeatherText(position.weather, position.weatherText, location.weather, values.weather);
8353
+ const altitudeMeters = nonNegativeNumberValue(location.altitude, location.alt, position.altitude, position.alt);
8354
+ const movement = snapshotMovement({
8355
+ ...values,
8356
+ ...location,
8357
+ ...position
8358
+ });
8359
+ const backgroundFiles = [
8360
+ ...listValue$16(values.file_assets),
8361
+ ...listValue$16(values.fileAssets),
8362
+ ...listValue$16(values.media_items),
8363
+ ...listValue$16(values.mediaItems),
8364
+ ...listValue$16(values.media_refs),
8365
+ ...listValue$16(values.mediaRefs),
8366
+ ...listValue$16(values.media_display_items),
8367
+ ...listValue$16(values.mediaDisplayItems),
8368
+ ...listValue$16(values.files),
8369
+ ...listValue$16(values.attachments)
8370
+ ].map(objectValue$1);
8371
+ const backgroundSound = backgroundFiles.some((file) => Math.trunc(numberValue$16(file.content_file_role ?? file.contentFileRole)) === 4) || backgroundFiles.some((file) => Math.trunc(numberValue$16(file.binding_type ?? file.bindingType)) === 4) || listValue$16(values.background_sound_amplitudes ?? values.backgroundSoundAmplitudes).length > 0 || listValue$16(parsedObject(values.background_sound ?? values.backgroundSound).amplitudes).length > 0 ? "available" : values.background_sound_enabled === false || values.backgroundSoundEnabled === false ? "disabled" : "not-recorded";
8372
+ const status = numberValue$16(values.status);
8373
+ const syncState = status === 2 ? "syncing" : status < 0 ? "failed" : syncedAtMillis !== void 0 || status === 1 ? "synced" : "not-synced";
8374
+ const belongDate = snapshotDateLabel(startAtMillis ?? completeAtMillis);
8375
+ return {
8376
+ itemUid: firstSnapshotText(relation.record_uid, values.record_uid, raw.record_uid) ?? fallback.itemUid,
8377
+ textContent: firstSnapshotText(record.text_content, record.textContent, values.text_content, values.textContent) ?? fallback.textContent,
8378
+ ...recordDurationMillis === void 0 ? {} : { recordDurationMillis },
8379
+ ...editDurationMillis === void 0 ? {} : { editDurationMillis },
8380
+ ...numberValue$16(values.view_times ?? values.viewTimes) > 0 ? { viewTimes: Math.trunc(numberValue$16(values.view_times ?? values.viewTimes)) } : {},
8381
+ ...numberValue$16(values.share_times ?? values.shareTimes) > 0 ? { shareTimes: Math.trunc(numberValue$16(values.share_times ?? values.shareTimes)) } : {},
8382
+ ...captureContext === void 0 ? {} : { captureContext },
8383
+ backgroundSound,
8384
+ ...locationLabel === void 0 ? {} : { locationLabel },
8385
+ ...weather === void 0 ? {} : { weather },
8386
+ ...altitudeMeters === void 0 ? {} : { altitudeMeters },
8387
+ ...movement === void 0 ? {} : { movement },
8388
+ ...belongDate === void 0 ? {} : { belongDate },
8389
+ ...startAtMillis === void 0 ? {} : { startAtMillis },
8390
+ ...completeAtMillis === void 0 ? {} : { completeAtMillis },
8391
+ ...syncedAtMillis === void 0 ? {} : { syncedAtMillis },
8392
+ syncState,
8393
+ ...locationCapture === void 0 ? {} : { locationCapture }
8394
+ };
8395
+ }
7615
8396
  function favoriteStickerPersistenceItem(item) {
7616
8397
  return {
7617
8398
  file_asset_uid: stringValue$1(item.file_asset_uid).trim(),
@@ -7642,7 +8423,7 @@ function chatMemberStatus$1(value) {
7642
8423
  if (value === "removed" || value === 3) return "removed";
7643
8424
  return "unknown";
7644
8425
  }
7645
- function integerLikeValue$1(value) {
8426
+ function integerLikeValue(value) {
7646
8427
  if (typeof value === "number" && Number.isFinite(value)) return Math.trunc(value);
7647
8428
  if (typeof value === "string" && value.trim() !== "") {
7648
8429
  const parsed = Number(value.trim());
@@ -7650,24 +8431,50 @@ function integerLikeValue$1(value) {
7650
8431
  }
7651
8432
  return 0;
7652
8433
  }
8434
+ function chatRecordOwnerUserId(relation, record, payload, senderUserId) {
8435
+ const candidates = [
8436
+ relation.record_owner_user_id,
8437
+ relation.recordOwnerUserId,
8438
+ relation.owner_user_id,
8439
+ relation.ownerUserId,
8440
+ record.record_owner_user_id,
8441
+ record.recordOwnerUserId,
8442
+ record.owner_user_id,
8443
+ record.ownerUserId,
8444
+ record.user_id,
8445
+ record.userId,
8446
+ payload.record_owner_user_id,
8447
+ payload.recordOwnerUserId,
8448
+ payload.owner_user_id,
8449
+ payload.ownerUserId,
8450
+ payload.user_id,
8451
+ payload.userId,
8452
+ senderUserId
8453
+ ];
8454
+ for (const candidate of candidates) {
8455
+ const userId = integerLikeValue(candidate);
8456
+ if (userId > 0) return userId;
8457
+ }
8458
+ return 0;
8459
+ }
7653
8460
  function epochMillisValue(value) {
7654
- const timestamp = integerLikeValue$1(value);
8461
+ const timestamp = integerLikeValue(value);
7655
8462
  return timestamp > 0 && timestamp < 1e11 ? timestamp * 1e3 : timestamp;
7656
8463
  }
7657
8464
  function messageCopyLinkMediaItemFromData(value) {
7658
8465
  const data = objectValue$1(value);
7659
8466
  return {
7660
- fileKind: integerLikeValue$1(data.file_kind),
8467
+ fileKind: integerLikeValue(data.file_kind),
7661
8468
  fileName: stringValue$1(data.file_name),
7662
- size: Math.max(0, integerLikeValue$1(data.size))
8469
+ size: Math.max(0, integerLikeValue(data.size))
7663
8470
  };
7664
8471
  }
7665
8472
  function messageCopyLinkStructuredContentFromData(value) {
7666
8473
  const data = objectValue$1(value);
7667
8474
  if (Object.keys(data).length === 0) return void 0;
7668
8475
  return {
7669
- structuredKind: integerLikeValue$1(data.structured_kind),
7670
- durationMillis: Math.max(0, integerLikeValue$1(data.duration_millis))
8476
+ structuredKind: integerLikeValue(data.structured_kind),
8477
+ durationMillis: Math.max(0, integerLikeValue(data.duration_millis))
7671
8478
  };
7672
8479
  }
7673
8480
  function messageCopyLinkSnapshotItemFromData(value) {
@@ -7682,9 +8489,9 @@ function messageCopyLinkSnapshotItemFromData(value) {
7682
8489
  title: stringValue$1(data.title),
7683
8490
  textContent: stringValue$1(data.text_content),
7684
8491
  sendAtMillis: epochMillisValue(data.send_at),
7685
- templateKind: integerLikeValue$1(data.template_kind),
7686
- displayKind: integerLikeValue$1(data.display_kind),
7687
- officialMark: integerLikeValue$1(data.official_mark),
8492
+ templateKind: integerLikeValue(data.template_kind),
8493
+ displayKind: integerLikeValue(data.display_kind),
8494
+ officialMark: integerLikeValue(data.official_mark),
7688
8495
  mediaItems: listValue$16(data.media_items).map(messageCopyLinkMediaItemFromData),
7689
8496
  ...structuredContent === void 0 ? {} : { structuredContent }
7690
8497
  };
@@ -7700,7 +8507,7 @@ function messageCopyLinkPresentationNodesFromData(value, itemCount, depth = 1, s
7700
8507
  const data = objectValue$1(rawNode);
7701
8508
  const kind = stringValue$1(data.kind);
7702
8509
  if (kind === "item") {
7703
- const itemIndex = integerLikeValue$1(data.item_index);
8510
+ const itemIndex = integerLikeValue(data.item_index);
7704
8511
  if (itemIndex < 0 || itemIndex >= itemCount || seenItemIndexes.has(itemIndex)) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
7705
8512
  seenItemIndexes.add(itemIndex);
7706
8513
  return {
@@ -7712,7 +8519,7 @@ function messageCopyLinkPresentationNodesFromData(value, itemCount, depth = 1, s
7712
8519
  kind: "forward_bundle",
7713
8520
  title: stringValue$1(data.title),
7714
8521
  commentText: stringValue$1(data.comment_text),
7715
- createdAtMillis: integerLikeValue$1(data.created_at),
8522
+ createdAtMillis: integerLikeValue(data.created_at),
7716
8523
  senderDisplayName: stringValue$1(data.sender_display_name),
7717
8524
  children: messageCopyLinkPresentationNodesFromData(data.children, itemCount, depth + 1, seenItemIndexes)
7718
8525
  };
@@ -7724,8 +8531,8 @@ function messageCopyLinkSourceAnchorFromData(value) {
7724
8531
  return {
7725
8532
  relationUid: stringValue$1(data.rel_uid),
7726
8533
  recordUid: stringValue$1(data.record_uid),
7727
- recordOwnerUserId: integerLikeValue$1(data.record_owner_user_id),
7728
- sequence: integerLikeValue$1(data.seq)
8534
+ recordOwnerUserId: integerLikeValue(data.record_owner_user_id),
8535
+ sequence: integerLikeValue(data.seq)
7729
8536
  };
7730
8537
  }
7731
8538
  function firstTextValue(source, keys) {
@@ -7772,7 +8579,7 @@ function messageCopyLinkExtensionItemFromData(value) {
7772
8579
  if (recordUid === "" || sendAtMillis <= 0 || title.trim() === "" && textContent.trim() === "" && mediaItems.length === 0 && structuredContent === void 0) return;
7773
8580
  return {
7774
8581
  recordUid,
7775
- level: Math.max(2, integerLikeValue$1(data.level) || 2),
8582
+ level: Math.max(2, integerLikeValue(data.level) || 2),
7776
8583
  sourceKind: stringValue$1(data.source_kind ?? data.sourceKind) || "record_extension",
7777
8584
  senderDisplayName,
7778
8585
  senderAvatarUrl: firstTextValue(data, [
@@ -7787,9 +8594,9 @@ function messageCopyLinkExtensionItemFromData(value) {
7787
8594
  title,
7788
8595
  textContent,
7789
8596
  sendAtMillis,
7790
- templateKind: integerLikeValue$1(data.template_kind ?? data.templateKind ?? core.template_kind ?? core.templateKind) || 1,
7791
- displayKind: integerLikeValue$1(data.display_kind ?? data.displayKind ?? core.display_kind ?? core.displayKind),
7792
- officialMark: integerLikeValue$1(data.official_mark ?? data.officialMark ?? core.official_mark ?? core.officialMark),
8597
+ templateKind: integerLikeValue(data.template_kind ?? data.templateKind ?? core.template_kind ?? core.templateKind) || 1,
8598
+ displayKind: integerLikeValue(data.display_kind ?? data.displayKind ?? core.display_kind ?? core.displayKind),
8599
+ officialMark: integerLikeValue(data.official_mark ?? data.officialMark ?? core.official_mark ?? core.officialMark),
7793
8600
  mediaItems,
7794
8601
  ...structuredContent === void 0 ? {} : { structuredContent }
7795
8602
  };
@@ -7797,7 +8604,7 @@ function messageCopyLinkExtensionItemFromData(value) {
7797
8604
  function messageCopyLinkRecordContextFromData(data) {
7798
8605
  const context = objectValue$1(data.record_context ?? data.recordContext);
7799
8606
  const extensions = listValue$16(context.extensions ?? context.extend_records ?? context.extendRecords ?? context.list ?? data.extensions ?? data.extend_records ?? data.extendRecords ?? data.list).map(messageCopyLinkExtensionItemFromData).filter((item) => item !== void 0);
7800
- const extensionCount = Math.max(integerLikeValue$1(context.extension_count ?? context.extensionCount ?? context.total ?? data.extension_count ?? data.extensionCount ?? data.total), extensions.length);
8607
+ const extensionCount = Math.max(integerLikeValue(context.extension_count ?? context.extensionCount ?? context.total ?? data.extension_count ?? data.extensionCount ?? data.total), extensions.length);
7801
8608
  if (extensionCount <= 0 && extensions.length === 0) return void 0;
7802
8609
  extensions.sort((left, right) => right.sendAtMillis - left.sendAtMillis);
7803
8610
  return {
@@ -7845,7 +8652,7 @@ function parsedObject(value) {
7845
8652
  }
7846
8653
  function firstInteger(source, keys) {
7847
8654
  for (const key of keys) {
7848
- const value = integerLikeValue$1(source[key]);
8655
+ const value = integerLikeValue(source[key]);
7849
8656
  if (value > 0) return value;
7850
8657
  }
7851
8658
  return 0;
@@ -7882,8 +8689,20 @@ function resolveChatMemberDisplayNames(input) {
7882
8689
  ].find((value) => isUsable(value) && value !== displayName) ?? ""
7883
8690
  };
7884
8691
  }
8692
+ function projectChatMemberDisplayNames(item, userId, viewerLabel, publicDisplayName) {
8693
+ return resolveChatMemberDisplayNames({
8694
+ userId,
8695
+ remarkCandidates: [],
8696
+ memberNameCandidates: [item.display_name_snapshot],
8697
+ userNameCandidates: [
8698
+ viewerLabel,
8699
+ item.remark,
8700
+ publicDisplayName
8701
+ ]
8702
+ });
8703
+ }
7885
8704
  function normalizedJoinTimestamp(value) {
7886
- const raw = integerLikeValue$1(value);
8705
+ const raw = integerLikeValue(value);
7887
8706
  if (raw <= 0) return 0;
7888
8707
  return raw < 1e11 ? raw * 1e3 : raw;
7889
8708
  }
@@ -7900,12 +8719,12 @@ function rawMemberDisplayName(item) {
7900
8719
  async function projectArkmeConversationMemberJoinEvents(rawItems, options) {
7901
8720
  const membersByUserId = /* @__PURE__ */ new Map();
7902
8721
  for (const item of rawItems) {
7903
- const userId = integerLikeValue$1(item.user_id ?? item.userId);
8722
+ const userId = integerLikeValue(item.user_id ?? item.userId);
7904
8723
  if (userId > 0) membersByUserId.set(userId, item);
7905
8724
  }
7906
8725
  const groups = /* @__PURE__ */ new Map();
7907
8726
  for (const item of rawItems) {
7908
- const inviteeUserId = integerLikeValue$1(item.user_id ?? item.userId);
8727
+ const inviteeUserId = integerLikeValue(item.user_id ?? item.userId);
7909
8728
  if (inviteeUserId <= 0) continue;
7910
8729
  const extra = parsedObject(item.extra);
7911
8730
  if (Object.keys(extra).length === 0) continue;
@@ -8011,7 +8830,7 @@ function agentSourceLabel(displayName) {
8011
8830
  function isAgentCreationSource(relation, record, payload) {
8012
8831
  const relationMetadata = objectValue$1(relation.metadata);
8013
8832
  const recordMetadata = objectValue$1(record.metadata);
8014
- return integerLikeValue$1(payload.creation_source ?? payload.creationSource) === 1 || integerLikeValue$1(record.creation_source ?? record.creationSource) === 1 || integerLikeValue$1(relation.creation_source ?? relation.creationSource) === 1 || integerLikeValue$1(recordMetadata.creation_source ?? recordMetadata.creationSource) === 1 || integerLikeValue$1(relationMetadata.creation_source ?? relationMetadata.creationSource) === 1;
8833
+ return integerLikeValue(payload.creation_source ?? payload.creationSource) === 1 || integerLikeValue(record.creation_source ?? record.creationSource) === 1 || integerLikeValue(relation.creation_source ?? relation.creationSource) === 1 || integerLikeValue(recordMetadata.creation_source ?? recordMetadata.creationSource) === 1 || integerLikeValue(relationMetadata.creation_source ?? relationMetadata.creationSource) === 1;
8015
8834
  }
8016
8835
  function agentDisplayNameFromTimeline(relation, record, payload) {
8017
8836
  const relationMetadata = objectValue$1(relation.metadata);
@@ -8251,7 +9070,11 @@ var ChatService = class {
8251
9070
  const joinEventsEnabled = source.kind === "group_chat" && this.runtime.config.chatMemberJoinEventsEnabled !== false;
8252
9071
  const rawItems = await this.rawChatMembers(source.ownerRef, joinEventsEnabled ? false : activeOnly, session, options.signal);
8253
9072
  const visibleRawItems = activeOnly ? rawItems.filter((item) => chatMemberStatus$1(item.status) === "active") : rawItems;
8254
- const members = await this.projectChatMembers(source.ownerRef, visibleRawItems, session, source.kind === "group_chat", options.signal);
9073
+ const members = await this.projectChatMembers(source.ownerRef, visibleRawItems, session, {
9074
+ includeViewerLabels: source.kind === "group_chat",
9075
+ includeHumanMentionRefs: source.kind === "group_chat",
9076
+ ...options.signal === void 0 ? {} : { signal: options.signal }
9077
+ });
8255
9078
  const signingKey = joinEventsEnabled ? await this.runtime.stateStore.uniqueCode() : "";
8256
9079
  const joinEvents = joinEventsEnabled ? await projectArkmeConversationMemberJoinEvents(rawItems, {
8257
9080
  viewerUserId: session.userId,
@@ -8273,7 +9096,11 @@ var ChatService = class {
8273
9096
  if (mode !== "owner" && mode !== "mentioned") throw new ArkmePluginError("chat-member-record-mode-invalid", "成员快记模式无效", false);
8274
9097
  const reference = await this.openChatMemberRef(memberRef, session.userId, source.ownerRef);
8275
9098
  const rawMembers = await this.rawChatMembers(source.ownerRef, false, session, options.signal);
8276
- const member = (await this.projectChatMembers(source.ownerRef, rawMembers, session, source.kind === "group_chat", options.signal)).find((item) => item.memberRef === memberRef);
9099
+ const member = (await this.projectChatMembers(source.ownerRef, rawMembers, session, {
9100
+ includeViewerLabels: source.kind === "group_chat",
9101
+ includeHumanMentionRefs: source.kind === "group_chat",
9102
+ ...options.signal === void 0 ? {} : { signal: options.signal }
9103
+ })).find((item) => item.memberRef === memberRef);
8277
9104
  if (member === void 0) throw new ArkmePluginError("chat-member-ref-stale", "该成员已不属于当前会话,请刷新后重试", false, 409);
8278
9105
  const limit = Math.min(50, Math.max(1, Math.trunc(options.limit ?? 30)));
8279
9106
  const data = await this.runtime.authenticatedChatPost("/api/v1/chats/members/records/page", {
@@ -8422,6 +9249,7 @@ var ChatService = class {
8422
9249
  }, session, options.signal);
8423
9250
  const rawRecords = listValue$16(data.items).filter((raw) => !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(this.record.recordUid(raw)));
8424
9251
  const media = await this.media.hydrateRecordMediaPage(rawRecords, session, options.signal);
9252
+ const signingKey = await this.runtime.stateStore.uniqueCode();
8425
9253
  const items = (await Promise.all(rawRecords.map(async (raw) => {
8426
9254
  const recordUid = this.record.recordUid(raw);
8427
9255
  const displayItems = media.displayItemsByRecordUid.get(recordUid);
@@ -8438,7 +9266,7 @@ var ChatService = class {
8438
9266
  } },
8439
9267
  mediaUnavailable: media.unavailableRecordUids.has(recordUid)
8440
9268
  });
8441
- }))).filter((item) => item.itemUid !== "");
9269
+ }))).filter((item) => item.itemUid !== "").map((item) => this.withRecordMessageActionRef(source, item, session.userId, signingKey));
8442
9270
  const nextSendAt = numberValue$16(data.next_cursor_send_at);
8443
9271
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
8444
9272
  return {
@@ -8478,6 +9306,7 @@ var ChatService = class {
8478
9306
  if (arkmePrivacyLockedTopic(data.topic_core)) throw new ArkmePluginError("topic-privacy-locked", "隐私锁主题不能在 Arkme 插件中查看", false, 403);
8479
9307
  const rawRecords = listValue$16(data.records).filter((raw) => !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(this.record.recordUid(raw)));
8480
9308
  const media = await this.media.hydrateRecordMediaPage(rawRecords, session, options.signal);
9309
+ const signingKey = await this.runtime.stateStore.uniqueCode();
8481
9310
  const records = rawRecords.map((raw) => {
8482
9311
  const recordUid = this.record.recordUid(raw);
8483
9312
  const displayItems = media.displayItemsByRecordUid.get(recordUid);
@@ -8490,7 +9319,7 @@ var ChatService = class {
8490
9319
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
8491
9320
  return {
8492
9321
  source: await this.source.sourceItem(source),
8493
- items: records,
9322
+ items: records.map((item) => this.withRecordMessageActionRef(source, item, session.userId, signingKey)),
8494
9323
  hasMore: data.has_more === true,
8495
9324
  ...nextSendAt > 0 && nextUid !== "" ? { nextCursor: {
8496
9325
  sendAtMillis: nextSendAt,
@@ -8770,7 +9599,7 @@ var ChatService = class {
8770
9599
  bypassCache: true
8771
9600
  });
8772
9601
  const rawItems = listValue$16(data.list ?? data.items);
8773
- if (offset === 0) total = Math.max(0, integerLikeValue$1(data.total));
9602
+ if (offset === 0) total = Math.max(0, integerLikeValue(data.total));
8774
9603
  const pageItems = rawItems.map((raw) => messageCopyLinkExtensionItemFromData(raw)).filter((item) => item !== void 0);
8775
9604
  extensions.push(...pageItems);
8776
9605
  if (rawItems.length === 0) break;
@@ -8829,12 +9658,7 @@ var ChatService = class {
8829
9658
  const publishedRecordUid = stringValue$1(data.record_uid).trim() || createdRecordUid;
8830
9659
  const senderDisplayName = profile.nickname.trim() || profile.displayName.trim() || "我";
8831
9660
  const senderAvatarUrl = profile.avatarRef.trim();
8832
- this.source.invalidateSourceListCache(session.userId, "send_to_self");
8833
- this.realtime.emitChatClientEvent({
8834
- type: "projection-invalidated",
8835
- revision: this.realtime.nextChatClientRevision(),
8836
- projection: "record"
8837
- });
9661
+ await this.realtime.invalidateRecordProjection();
8838
9662
  return {
8839
9663
  sid: detail.sid,
8840
9664
  recordUid: publishedRecordUid,
@@ -8929,7 +9753,7 @@ var ChatService = class {
8929
9753
  content_payload: contentPayload,
8930
9754
  send_at: Date.now()
8931
9755
  }, session, options.signal);
8932
- this.source.invalidateSourceListCache(session.userId, "send_to_self");
9756
+ await this.realtime.invalidateRecordProjection();
8933
9757
  return await appendCommentWarning({
8934
9758
  sourceRef: targetSourceRef,
8935
9759
  itemUid: stringValue$1(data.record_uid).trim() || recordUid,
@@ -8945,7 +9769,7 @@ var ChatService = class {
8945
9769
  content_payload: contentPayload,
8946
9770
  send_at: Date.now()
8947
9771
  }, session, options.signal);
8948
- this.source.invalidateSourceListCache(session.userId, "send_to_self");
9772
+ await this.realtime.invalidateRecordProjection();
8949
9773
  return await appendCommentWarning({
8950
9774
  sourceRef: targetSourceRef,
8951
9775
  itemUid: stringValue$1(data.record_uid).trim() || recordUid,
@@ -8960,8 +9784,11 @@ var ChatService = class {
8960
9784
  if (text === "" || text.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("source-text-invalid", "发送内容为空或超过长度限制", false);
8961
9785
  const recordUid = options.recordUid?.trim() || randomUUID();
8962
9786
  if (source.kind === "send_to_self" || source.kind === "default_category") {
8963
- const result = await this.record.createTextForConversation(recordUid, text);
8964
- if (result.localState !== "failed") this.source.invalidateSourceListCache(session.userId, "send_to_self");
9787
+ const result = await this.record.createTextForConversation(recordUid, text, {
9788
+ ...options.recordDurationMillis === void 0 ? {} : { recordDurationMillis: options.recordDurationMillis },
9789
+ ...options.captureContext === void 0 ? {} : { captureContext: options.captureContext }
9790
+ });
9791
+ if (result.localState !== "failed") await this.realtime.invalidateRecordProjection();
8965
9792
  return {
8966
9793
  sourceRef,
8967
9794
  itemUid: result.recordUid,
@@ -8971,15 +9798,18 @@ var ChatService = class {
8971
9798
  };
8972
9799
  }
8973
9800
  if (source.kind === "topic") {
9801
+ const captureContext = options.captureContext === void 0 ? void 0 : arkmeRecordCaptureContextPayload(options.captureContext);
8974
9802
  const result = await this.runtime.authenticatedPost("/api/v1/topics/records/create", {
8975
9803
  topic_uid: source.ownerRef,
8976
9804
  record_uid: recordUid,
8977
9805
  template_kind: 1,
8978
9806
  title: "",
8979
9807
  text_content: text,
9808
+ ...Math.max(0, Math.trunc(options.recordDurationMillis ?? 0)) === 0 ? {} : { record_duration_millis: Math.max(0, Math.trunc(options.recordDurationMillis ?? 0)) },
9809
+ ...captureContext === void 0 || Object.keys(captureContext).length === 0 ? {} : { capture_context: captureContext },
8980
9810
  send_at: Date.now()
8981
9811
  }, session);
8982
- this.source.invalidateSourceListCache(session.userId, "send_to_self");
9812
+ await this.realtime.invalidateRecordProjection();
8983
9813
  return {
8984
9814
  sourceRef,
8985
9815
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
@@ -8992,61 +9822,45 @@ var ChatService = class {
8992
9822
  let sent;
8993
9823
  if (source.kind === "group_chat") {
8994
9824
  if ((options.botRefs?.length ?? 0) > 0 && ((options.humanMentions?.length ?? 0) > 0 || (options.botMentions?.length ?? 0) > 0)) throw new ArkmePluginError("mention-kind-conflict", "bot_refs 不能和结构化 mention 同时使用", false);
8995
- if (options.botRefs !== void 0 && options.botRefs.length > 0) sent = await this.groupBotMentionSend(sourceRef, source, text, options.botRefs, recordUid, relationUid, session, options.signal, { agentAuthored });
9825
+ if (options.botRefs !== void 0 && options.botRefs.length > 0) sent = await this.groupBotMentionSend(sourceRef, source, text, options.botRefs, recordUid, relationUid, session, options.signal, {
9826
+ agentAuthored,
9827
+ ...options.recordDurationMillis === void 0 ? {} : { recordDurationMillis: options.recordDurationMillis },
9828
+ ...options.captureContext === void 0 ? {} : { captureContext: options.captureContext }
9829
+ });
8996
9830
  else if ((options.humanMentions?.length ?? 0) > 0 || (options.botMentions?.length ?? 0) > 0) {
8997
- const contentPayload = await this.humanMentionContentPayload(source, textContent, text, options.humanMentions ?? [], session, options.signal, options.botMentions ?? []);
8998
- sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, contentPayload, options.signal, { agentAuthored });
9831
+ const contentPayload = await this.mentionContentPayload(source, textContent, text, options.humanMentions ?? [], options.botMentions ?? [], session, options.signal);
9832
+ sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, contentPayload, options.signal, {
9833
+ agentAuthored,
9834
+ ...options.recordDurationMillis === void 0 ? {} : { recordDurationMillis: options.recordDurationMillis },
9835
+ ...options.captureContext === void 0 ? {} : { captureContext: options.captureContext }
9836
+ });
8999
9837
  } else sent = await this.aiPolish.sendGroupSourceTextWithAiPolish(sourceRef, source.ownerRef, text, recordUid, relationUid, session, {
9000
9838
  agentAuthored,
9839
+ ...options.recordDurationMillis === void 0 ? {} : { recordDurationMillis: options.recordDurationMillis },
9840
+ ...options.captureContext === void 0 ? {} : { captureContext: options.captureContext },
9001
9841
  ...options.signal === void 0 ? {} : { signal: options.signal }
9002
9842
  });
9003
9843
  } else if ((options.humanMentions?.length ?? 0) > 0) throw new ArkmePluginError("mention-group-required", "真人 mention 只能发送到群聊", false);
9004
9844
  else if ((options.botRefs?.length ?? 0) > 0) throw new ArkmePluginError("mention-group-required", "bot_refs 只能发送到群聊", false);
9005
9845
  else if ((options.botMentions?.length ?? 0) > 0) {
9006
- const contentPayload = await this.humanMentionContentPayload(source, textContent, text, [], session, options.signal, options.botMentions ?? []);
9007
- sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, contentPayload, options.signal, { agentAuthored });
9008
- } else sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, void 0, options.signal, { agentAuthored });
9846
+ const contentPayload = await this.mentionContentPayload(source, textContent, text, [], options.botMentions ?? [], session, options.signal);
9847
+ sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, contentPayload, options.signal, {
9848
+ agentAuthored,
9849
+ ...options.recordDurationMillis === void 0 ? {} : { recordDurationMillis: options.recordDurationMillis },
9850
+ ...options.captureContext === void 0 ? {} : { captureContext: options.captureContext }
9851
+ });
9852
+ } else sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, void 0, options.signal, {
9853
+ agentAuthored,
9854
+ ...options.recordDurationMillis === void 0 ? {} : { recordDurationMillis: options.recordDurationMillis },
9855
+ ...options.captureContext === void 0 ? {} : { captureContext: options.captureContext }
9856
+ });
9009
9857
  if (agentAuthored && sent.localState === "synced") this.arko.agentSourceDisplayName(session);
9010
9858
  return sent;
9011
9859
  }
9012
- async humanMentionContentPayload(source, rawText, normalizedText, inputs, session, signal, botInputs = []) {
9013
- if (inputs.length > 50) throw new ArkmePluginError("human-mention-invalid", "单条消息 mention 数量过多", false);
9860
+ async mentionContentPayload(source, rawText, normalizedText, humanInputs, botInputs, session, signal) {
9861
+ if (humanInputs.length > 50) throw new ArkmePluginError("human-mention-invalid", "单条消息 mention 数量过多", false);
9014
9862
  if (botInputs.length > 50) throw new ArkmePluginError("bot-mention-invalid", "单条消息 Bot mention 数量过多", false);
9015
- const leadingTrim = rawText.length - rawText.trimStart().length;
9016
- const rawMembers = inputs.length === 0 ? [] : await this.rawChatMembers(source.ownerRef, true, session, signal);
9017
- const membersByUserId = new Map(rawMembers.map((item) => [Math.trunc(numberValue$16(item.user_id)), item]));
9018
- const mentions = [];
9019
- for (const input of [...inputs].sort((left, right) => left.startIndex - right.startIndex)) {
9020
- const memberRef = input.memberRef?.trim() ?? "";
9021
- const startIndex = Math.trunc(input.startIndex) - leadingTrim;
9022
- const length = Math.trunc(input.length);
9023
- if (startIndex < 0 || length < 2 || startIndex + length > normalizedText.length) throw new ArkmePluginError("human-mention-invalid", "真人 mention 引用或文本区间无效", false);
9024
- const visible = normalizedText.slice(startIndex, startIndex + length);
9025
- const displayName = visible.startsWith("@") ? visible.slice(1).trim() : "";
9026
- let userId;
9027
- if (input.all === true) {
9028
- if (displayName !== "所有人") throw new ArkmePluginError("human-mention-text-mismatch", "真人 mention 文本已变化,请重新选择成员", false, 409);
9029
- userId = 0;
9030
- } else {
9031
- if (memberRef === "") throw new ArkmePluginError("human-mention-invalid", "真人 mention 引用或文本区间无效", false);
9032
- const reference = await this.openChatMemberRef(memberRef, session.userId, source.ownerRef);
9033
- if (reference.targetUserId === session.userId) throw new ArkmePluginError("human-mention-self-invalid", "不能 @ 自己", false);
9034
- const rawMember = membersByUserId.get(reference.targetUserId);
9035
- if (rawMember === void 0) throw new ArkmePluginError("chat-member-ref-stale", "被 @ 成员已不在当前群聊", false, 409);
9036
- const expectedNames = [stringValue$1(rawMember.remark).trim(), stringValue$1(rawMember.display_name_snapshot).trim()].filter((value) => value !== "");
9037
- if (displayName === "" || !expectedNames.includes(displayName)) throw new ArkmePluginError("human-mention-text-mismatch", "真人 mention 文本已变化,请重新选择成员", false, 409);
9038
- userId = reference.targetUserId;
9039
- }
9040
- const previous = mentions.at(-1);
9041
- if (previous !== void 0 && previous.start_index + previous.length > startIndex) throw new ArkmePluginError("human-mention-overlap", "真人 mention 文本区间重叠", false);
9042
- mentions.push({
9043
- user_id: userId,
9044
- display_name_snapshot: displayName,
9045
- start_index: startIndex,
9046
- length
9047
- });
9048
- }
9049
- const botMentions = await this.botMentionMetadata(source, rawText, normalizedText, botInputs, session, signal);
9863
+ const [mentions, botMentions] = await Promise.all([this.humanMentionMetadata(source, rawText, normalizedText, humanInputs, session, signal), this.botMentionMetadata(source, rawText, normalizedText, botInputs, session, signal)]);
9050
9864
  const orderedRanges = [...mentions.map((mention) => ({
9051
9865
  startIndex: mention.start_index,
9052
9866
  length: mention.length
@@ -9084,6 +9898,45 @@ var ChatService = class {
9084
9898
  }
9085
9899
  };
9086
9900
  }
9901
+ async humanMentionMetadata(source, rawText, normalizedText, inputs, session, signal) {
9902
+ const leadingTrim = rawText.length - rawText.trimStart().length;
9903
+ const rawMembers = inputs.some((input) => input.all !== true) ? await this.rawChatMembers(source.ownerRef, true, session, signal) : [];
9904
+ const membersByUserId = new Map(rawMembers.map((item) => [Math.trunc(numberValue$16(item.user_id)), item]));
9905
+ const mentions = [];
9906
+ for (const input of [...inputs].sort((left, right) => left.startIndex - right.startIndex)) {
9907
+ const rawInput = input;
9908
+ const mentionRef = input.mentionRef?.trim() ?? "";
9909
+ const memberRef = stringValue$1(rawInput.memberRef).trim();
9910
+ const startIndex = Math.trunc(input.startIndex) - leadingTrim;
9911
+ const length = Math.trunc(input.length);
9912
+ if (startIndex < 0 || length < 2 || startIndex + length > normalizedText.length) throw new ArkmePluginError("human-mention-invalid", "真人 mention 引用或文本区间无效", false);
9913
+ const visible = normalizedText.slice(startIndex, startIndex + length);
9914
+ const displayName = visible.startsWith("@") ? visible.slice(1) : "";
9915
+ let userId;
9916
+ if (input.all === true) {
9917
+ if (mentionRef !== "" || memberRef !== "") throw new ArkmePluginError("human-mention-invalid", "@所有人不能携带成员引用", false);
9918
+ if (displayName !== "所有人") throw new ArkmePluginError("human-mention-text-mismatch", "真人 mention 文本已变化,请重新选择成员", false, 409);
9919
+ userId = 0;
9920
+ } else {
9921
+ if (mentionRef === "" || memberRef !== "") throw new ArkmePluginError("human-mention-invalid", "真人 mention 必须且只能携带 mention 引用", false);
9922
+ const reference = await this.openChatHumanMentionRef(mentionRef, session.userId, source.ownerRef);
9923
+ const targetUserId = reference.targetUserId;
9924
+ if (targetUserId === session.userId) throw new ArkmePluginError("human-mention-self-invalid", "不能 @ 自己", false);
9925
+ if (membersByUserId.get(targetUserId) === void 0) throw new ArkmePluginError("chat-member-ref-stale", "被 @ 成员已不在当前群聊", false, 409);
9926
+ if (displayName === "" || displayName !== reference.displayNameSnapshot) throw new ArkmePluginError("human-mention-text-mismatch", "真人 mention 文本已变化,请重新选择成员", false, 409);
9927
+ userId = targetUserId;
9928
+ }
9929
+ const previous = mentions.at(-1);
9930
+ if (previous !== void 0 && previous.start_index + previous.length > startIndex) throw new ArkmePluginError("human-mention-overlap", "真人 mention 文本区间重叠", false);
9931
+ mentions.push({
9932
+ user_id: userId,
9933
+ display_name_snapshot: displayName,
9934
+ start_index: startIndex,
9935
+ length
9936
+ });
9937
+ }
9938
+ return mentions;
9939
+ }
9087
9940
  async botMentionMetadata(source, rawText, normalizedText, inputs, session, signal) {
9088
9941
  if (inputs.length === 0) return [];
9089
9942
  if (source.kind !== "group_chat" && source.kind !== "private_chat") throw new ArkmePluginError("mention-chat-required", "Bot mention 只能发送到聊天", false);
@@ -9207,6 +10060,8 @@ var ChatService = class {
9207
10060
  rel_uid: relationUid,
9208
10061
  template_kind: 1,
9209
10062
  text_content: text,
10063
+ ...Math.max(0, Math.trunc(options.recordDurationMillis ?? 0)) === 0 ? {} : { record_duration_millis: Math.max(0, Math.trunc(options.recordDurationMillis ?? 0)) },
10064
+ ...options.captureContext === void 0 ? {} : { capture_context: arkmeRecordCaptureContextPayload(options.captureContext) },
9210
10065
  ...options.agentAuthored === true ? { creation_source: 1 } : {},
9211
10066
  ...options.initialAiPolish === void 0 ? {} : { initial_ai_polish: options.initialAiPolish },
9212
10067
  ...options.contentPayload === void 0 ? {} : { content_payload: options.contentPayload },
@@ -9226,6 +10081,8 @@ var ChatService = class {
9226
10081
  const longArticle = displayKind === 1;
9227
10082
  const maxContentLength = longArticle ? 4e4 : this.runtime.config.maxTextLength;
9228
10083
  const thinkingDurationMillis = Math.max(0, Math.trunc(input.thinkingDurationMillis ?? 0));
10084
+ const recordDurationMillis = Math.max(0, Math.trunc(input.recordDurationMillis ?? (longArticle ? thinkingDurationMillis : 0)));
10085
+ const captureContext = input.captureContext === void 0 ? void 0 : arkmeRecordCaptureContextPayload(input.captureContext);
9229
10086
  if (title.length > (longArticle ? 100 : 500) || textContent.length > maxContentLength || assets.length > 20 || textContent === "" && title === "" && assets.length === 0) throw new ArkmePluginError("rich-content-invalid", "富内容为空、过长或附件数量超限", false);
9230
10087
  if (longArticle && (title === "" || textContent === "")) throw new ArkmePluginError("long-article-invalid", "长文标题和正文不能为空", false);
9231
10088
  for (const asset of assets) if (!/^[A-Za-z0-9._:-]{8,256}$/.test(asset.fileAssetUid) || asset.size < 0 || ![
@@ -9257,7 +10114,7 @@ var ChatService = class {
9257
10114
  if ((input.humanMentions?.length ?? 0) > 0 && source.kind !== "group_chat") throw new ArkmePluginError("mention-group-required", "真人 mention 只能发送到群聊", false);
9258
10115
  if ((input.botMentions?.length ?? 0) > 0 && source.kind !== "group_chat" && source.kind !== "private_chat") throw new ArkmePluginError("mention-chat-required", "Bot mention 只能发送到聊天", false);
9259
10116
  if (longArticle) throw new ArkmePluginError("mention-rich-invalid", "长文暂不支持 mention", false);
9260
- const mentionPayload = await this.humanMentionContentPayload(source, input.textContent ?? "", textContent, input.humanMentions ?? [], session, options.signal, input.botMentions ?? []);
10117
+ const mentionPayload = await this.mentionContentPayload(source, input.textContent ?? "", textContent, input.humanMentions ?? [], input.botMentions ?? [], session, options.signal);
9261
10118
  contentPayload = {
9262
10119
  ...mediaContentPayload ?? {},
9263
10120
  ...mentionPayload
@@ -9269,13 +10126,14 @@ var ChatService = class {
9269
10126
  display_kind: displayKind,
9270
10127
  title,
9271
10128
  text_content: textContent,
9272
- ...longArticle ? { record_duration_millis: thinkingDurationMillis } : {},
10129
+ ...recordDurationMillis === 0 ? {} : { record_duration_millis: recordDurationMillis },
10130
+ ...captureContext === void 0 || Object.keys(captureContext).length === 0 ? {} : { capture_context: captureContext },
9273
10131
  ...contentPayload === void 0 ? {} : { content_payload: contentPayload },
9274
10132
  send_at: Date.now()
9275
10133
  };
9276
10134
  if (source.kind === "send_to_self" || source.kind === "default_category") {
9277
10135
  const result = await this.runtime.authenticatedPost("/api/v1/records/create", commonBody, session, options.signal);
9278
- this.source.invalidateSourceListCache(session.userId, "send_to_self");
10136
+ await this.realtime.invalidateRecordProjection();
9279
10137
  return {
9280
10138
  sourceRef,
9281
10139
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
@@ -9288,7 +10146,7 @@ var ChatService = class {
9288
10146
  topic_uid: source.ownerRef,
9289
10147
  ...commonBody
9290
10148
  }, session, options.signal);
9291
- this.source.invalidateSourceListCache(session.userId, "send_to_self");
10149
+ await this.realtime.invalidateRecordProjection();
9292
10150
  return {
9293
10151
  sourceRef,
9294
10152
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
@@ -9539,8 +10397,9 @@ var ChatService = class {
9539
10397
  const payload = objectValue$1(record.payload);
9540
10398
  const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
9541
10399
  if (uid === "") continue;
9542
- const senderUserId = numberValue$16(relation.sender_user_id);
10400
+ const senderUserId = integerLikeValue(relation.sender_user_id);
9543
10401
  const relationUid = stringValue$1(relation.rel_uid ?? relation.relUid).trim();
10402
+ const recordOwnerUserId = chatRecordOwnerUserId(relation, record, payload, senderUserId);
9544
10403
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
9545
10404
  const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
9546
10405
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
@@ -9548,20 +10407,39 @@ var ChatService = class {
9548
10407
  viewerUserId: session.userId,
9549
10408
  chatSessionUid,
9550
10409
  relationUid,
9551
- recordOwnerUserId: integerLikeValue$1(record.record_owner_user_id ?? record.recordOwnerUserId ?? payload.record_owner_user_id ?? payload.recordOwnerUserId ?? relation.record_owner_user_id ?? relation.recordOwnerUserId),
10410
+ recordOwnerUserId,
9552
10411
  recordUid: uid,
9553
- senderUserId: integerLikeValue$1(relation.sender_user_id),
9554
- sequence: integerLikeValue$1(relation.seq),
10412
+ senderUserId: integerLikeValue(relation.sender_user_id),
10413
+ sequence: integerLikeValue(relation.seq),
9555
10414
  signingKey
9556
10415
  });
9557
10416
  const rawAgentSource = timelineAgentSource(relation, record, payload);
9558
10417
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
9559
10418
  const contentBlocks = this.media.richContentBlocks(item, session.userId);
10419
+ const senderName = stringValue$1(relation.display_name_snapshot).trim() || "Arkme用户";
9560
10420
  const mentionsViewer = senderUserId !== session.userId && arkmeMentionMetadataMentionsViewer(record, payload, session.userId);
9561
10421
  items.push({
9562
10422
  itemUid: uid,
10423
+ ...relationUid === "" ? {} : { messageActionRef: this.sealMessageActionRef(this.messageActionRefPayload({
10424
+ sourceKind: "chat_relation",
10425
+ userId: session.userId,
10426
+ sourceOwnerRef: chatSessionUid,
10427
+ chatSessionUid,
10428
+ relationUid,
10429
+ recordOwnerUserId,
10430
+ recordUid: uid,
10431
+ senderUserId,
10432
+ senderName,
10433
+ title: stringValue$1(payload.title),
10434
+ textContent: stringValue$1(payload.text_content),
10435
+ sendAtMillis,
10436
+ sourceSequence: numberValue$16(relation.seq),
10437
+ templateKind: numberValue$16(payload.template_kind),
10438
+ displayKind: numberValue$16(payload.display_kind),
10439
+ contentBlocks
10440
+ }), signingKey) },
9563
10441
  ...senderUserId > 0 ? { memberRef: await this.sealChatMemberRef(session.userId, chatSessionUid, senderUserId) } : {},
9564
- senderName: stringValue$1(relation.display_name_snapshot).trim() || "Arkme用户",
10442
+ senderName,
9565
10443
  ...agentSource === void 0 ? {} : { agentSource },
9566
10444
  ...senderUserId > 0 ? { avatarRef: await this.profile.sealProfileImageRef(session.userId, senderUserId) } : {},
9567
10445
  isMe: senderUserId === session.userId,
@@ -9581,6 +10459,23 @@ var ChatService = class {
9581
10459
  }
9582
10460
  return items;
9583
10461
  }
10462
+ async relatedQuickNoteLocator(sourceRef, messageActionRef) {
10463
+ const session = await this.runtime.requireSession();
10464
+ const normalizedSourceRef = sourceRef.trim();
10465
+ const source = await this.source.openSourceRef(normalizedSourceRef, session.userId);
10466
+ const reference = await this.openMessageActionRef(messageActionRef, session.userId, source);
10467
+ const ownerUserId = reference.recordOwnerUserId > 0 ? reference.recordOwnerUserId : reference.senderUserId;
10468
+ if (!Number.isSafeInteger(ownerUserId) || ownerUserId <= 0) throw new ArkmePluginError("related-quick-note-source-invalid", "当前快记缺少作者定位,请刷新后重试", false, 409);
10469
+ return {
10470
+ viewerUserId: session.userId,
10471
+ sourceRef: normalizedSourceRef,
10472
+ sourceOwnerRef: source.ownerRef,
10473
+ contextType: reference.sourceKind === "chat_relation" ? "chat" : "record",
10474
+ recordUid: reference.recordUid,
10475
+ recordOwnerUserId: ownerUserId,
10476
+ chatSessionUid: reference.sourceKind === "chat_relation" ? reference.chatSessionUid : ""
10477
+ };
10478
+ }
9584
10479
  async chatForwardRecordsPreview(raw, viewerUserId, fallbackCreatedAtMillis) {
9585
10480
  const contentPayload = this.media.recordContentPayload(raw);
9586
10481
  const nested = objectValue$1(contentPayload.forward_records ?? contentPayload.forwardRecords);
@@ -9727,11 +10622,11 @@ var ChatService = class {
9727
10622
  const participant = objectValue$1(value);
9728
10623
  const displayName = stringValue$1(participant.display_name ?? participant.displayName ?? participant.nick_name ?? participant.nickName ?? participant.speaker_name ?? participant.speakerName ?? participant.name).trim();
9729
10624
  if (displayName === "") return [];
9730
- const refUserId = integerLikeValue$1(participant.ref_user_id ?? participant.refUserId ?? participant.ref_usr_id ?? participant.refUsrId);
10625
+ const refUserId = integerLikeValue(participant.ref_user_id ?? participant.refUserId ?? participant.ref_usr_id ?? participant.refUsrId);
9731
10626
  return [{
9732
10627
  ...refUserId > 0 ? { refUserId } : {},
9733
10628
  displayName,
9734
- role: Math.max(0, integerLikeValue$1(participant.role))
10629
+ role: Math.max(0, integerLikeValue(participant.role))
9735
10630
  }];
9736
10631
  });
9737
10632
  }
@@ -9767,15 +10662,15 @@ var ChatService = class {
9767
10662
  const sourceDigest = stringValue$1(payload.source_digest ?? payload.sourceDigest).trim();
9768
10663
  const title = stringValue$1(payload.title ?? payload.orig_name ?? payload.origName).trim();
9769
10664
  const summary = stringValue$1(payload.summary ?? payload.summary_text ?? payload.summaryText ?? payload.content_summary ?? payload.contentSummary).trim();
9770
- const displayAtMillis = integerLikeValue$1(payload.display_at ?? payload.displayAt);
10665
+ const displayAtMillis = integerLikeValue(payload.display_at ?? payload.displayAt);
9771
10666
  if (sourceDigest === "" || title === "" || summary === "" || displayAtMillis <= 0) return void 0;
9772
10667
  const transcript = this.sharedRecordingTranscriptFromPayload(payload);
9773
10668
  return {
9774
10669
  sourceDigest,
9775
- sharedByUserId: Math.max(0, integerLikeValue$1(payload.shared_by_user_id ?? payload.sharedByUserId)),
9776
- sharedAtMillis: Math.max(0, integerLikeValue$1(payload.shared_at ?? payload.sharedAt)),
10670
+ sharedByUserId: Math.max(0, integerLikeValue(payload.shared_by_user_id ?? payload.sharedByUserId)),
10671
+ sharedAtMillis: Math.max(0, integerLikeValue(payload.shared_at ?? payload.sharedAt)),
9777
10672
  displayAtMillis,
9778
- endAtMillis: Math.max(0, integerLikeValue$1(payload.end_at ?? payload.endAt)),
10673
+ endAtMillis: Math.max(0, integerLikeValue(payload.end_at ?? payload.endAt)),
9779
10674
  timeRangeText: stringValue$1(payload.time_range_text ?? payload.timeRangeText).trim(),
9780
10675
  title,
9781
10676
  summary,
@@ -9846,12 +10741,12 @@ var ChatService = class {
9846
10741
  }
9847
10742
  const result = {
9848
10743
  version: 1,
9849
- viewerUserId: integerLikeValue$1(parsed.viewerUserId),
10744
+ viewerUserId: integerLikeValue(parsed.viewerUserId),
9850
10745
  chatSessionUid: stringValue$1(parsed.chatSessionUid).trim(),
9851
10746
  relationUid: stringValue$1(parsed.relationUid).trim(),
9852
- recordOwnerUserId: integerLikeValue$1(parsed.recordOwnerUserId),
10747
+ recordOwnerUserId: integerLikeValue(parsed.recordOwnerUserId),
9853
10748
  recordUid: stringValue$1(parsed.recordUid).trim(),
9854
- sequence: Math.max(0, integerLikeValue$1(parsed.sequence))
10749
+ sequence: Math.max(0, integerLikeValue(parsed.sequence))
9855
10750
  };
9856
10751
  if (parsed.version !== 1 || result.viewerUserId !== expectedViewerUserId || result.chatSessionUid === "" || result.recordUid === "" || result.recordOwnerUserId <= 0) throw new ArkmePluginError("shared-recording-detail-ref-invalid", "共享录音详情引用与当前账号不匹配,请刷新后重试", false, 403);
9857
10752
  return result;
@@ -9877,35 +10772,177 @@ var ChatService = class {
9877
10772
  detailRef
9878
10773
  };
9879
10774
  }
10775
+ async messageSnapshotDetail(sourceRef, actionRef, options = {}) {
10776
+ const session = await this.runtime.requireSession();
10777
+ const source = await this.source.openSourceRef(sourceRef, session.userId);
10778
+ const isChatSource = source.kind === "private_chat" || source.kind === "group_chat";
10779
+ const isRecordSource = source.kind === "send_to_self" || source.kind === "default_category" || source.kind === "topic";
10780
+ if (!isChatSource && !isRecordSource) throw new ArkmePluginError("message-snapshot-source-unsupported", "当前对话暂不支持读取完整快记详情", false, 400);
10781
+ const reference = await this.openMessageActionRef(actionRef, session.userId, source);
10782
+ if (reference.recordUid === "") throw new ArkmePluginError("message-snapshot-record-invalid", "快记记录身份无效,请刷新后重试", false, 400);
10783
+ if (reference.senderUserId !== session.userId) throw new ArkmePluginError("message-snapshot-not-owned", "只能查看自己发送的快记详情", false, 403);
10784
+ const recordOwnerUserId = reference.recordOwnerUserId > 0 ? reference.recordOwnerUserId : reference.senderUserId;
10785
+ if (recordOwnerUserId <= 0) throw new ArkmePluginError("message-snapshot-owner-missing", "快记记录归属暂时无法确认,请刷新后重试", true, 502);
10786
+ if (reference.sourceKind === "record") {
10787
+ if (!isRecordSource) throw new ArkmePluginError("message-snapshot-record-invalid", "快记记录身份与当前来源不匹配,请刷新后重试", false, 400);
10788
+ return this.hydratedMessageSnapshotDetail(reference, session, options);
10789
+ }
10790
+ if (!isChatSource || reference.relationUid === "") throw new ArkmePluginError("message-snapshot-record-invalid", "快记记录身份无效,请刷新后重试", false, 400);
10791
+ const chatRaw = objectValue$1((await this.runtime.authenticatedChatPost("/api/v1/chats/records/detail", {
10792
+ chat_session_uid: reference.chatSessionUid,
10793
+ record_uid: reference.recordUid,
10794
+ record_owner_user_id: recordOwnerUserId,
10795
+ rel_uid: reference.relationUid,
10796
+ ...reference.sourceSequence <= 0 ? {} : { seq: reference.sourceSequence }
10797
+ }, session, options.signal, {
10798
+ lane: "interactive-read",
10799
+ key: `message-snapshot-detail:${reference.chatSessionUid}:${reference.relationUid}:${reference.recordUid}:${String(reference.sourceSequence)}`,
10800
+ failureCooldownMs: 1e3
10801
+ })).item);
10802
+ if (Object.keys(chatRaw).length === 0) throw new ArkmePluginError("message-snapshot-detail-unavailable", "完整快记详情暂时无法读取,请稍后重试", true, 502);
10803
+ return this.hydratedMessageSnapshotDetail(reference, session, options, chatRaw);
10804
+ }
10805
+ async hydratedMessageSnapshotDetail(reference, session, options, chatRaw = {}) {
10806
+ const recordRaw = objectValue$1(await this.runtime.authenticatedPost("/api/v1/records/detail", { record_uid: reference.recordUid }, session, options.signal, {
10807
+ lane: "interactive-read",
10808
+ key: `message-snapshot-record-core:${reference.recordUid}`,
10809
+ failureCooldownMs: 1e3
10810
+ }));
10811
+ const chatRecord = objectValue$1(chatRaw.record);
10812
+ const recordCore = objectValue$1(recordRaw.record_core ?? recordRaw.recordCore ?? recordRaw.record ?? recordRaw);
10813
+ const detailRecordUid = firstSnapshotText(recordCore.record_uid, recordCore.recordUid, recordCore.uid, recordRaw.record_uid, recordRaw.recordUid, recordRaw.uid);
10814
+ const hasDetailPayload = [
10815
+ recordRaw.payload,
10816
+ recordRaw.content_payload,
10817
+ recordRaw.contentPayload,
10818
+ recordRaw.record_payload,
10819
+ recordRaw.recordPayload,
10820
+ recordRaw.location,
10821
+ recordRaw.position_detail,
10822
+ recordRaw.positionDetail,
10823
+ recordRaw.extra,
10824
+ recordRaw.record_extra,
10825
+ recordRaw.recordExtra,
10826
+ recordRaw.weather,
10827
+ recordRaw.capture_context,
10828
+ recordRaw.captureContext,
10829
+ recordRaw.background_sound,
10830
+ recordRaw.backgroundSound
10831
+ ].some((value) => Object.keys(parsedObject(value)).length > 0 || listValue$16(value).length > 0);
10832
+ if (!Object.keys(recordCore).some((key) => ![
10833
+ "record_uid",
10834
+ "recordUid",
10835
+ "uid"
10836
+ ].includes(key)) && !hasDetailPayload) throw new ArkmePluginError("message-snapshot-detail-unavailable", "完整快记详情暂时无法读取,请稍后重试", true, 502);
10837
+ if (detailRecordUid !== reference.recordUid) throw new ArkmePluginError("message-snapshot-detail-mismatch", "完整快记详情与当前消息不匹配,请刷新后重试", true, 502);
10838
+ const chatRecordPayload = {
10839
+ ...parsedObject(chatRecord.payload),
10840
+ ...parsedObject(chatRecord.content_payload),
10841
+ ...parsedObject(chatRecord.contentPayload),
10842
+ ...parsedObject(chatRecord.record_payload),
10843
+ ...parsedObject(chatRecord.recordPayload)
10844
+ };
10845
+ const detailPayload = {
10846
+ ...parsedObject(recordRaw.payload),
10847
+ ...parsedObject(recordRaw.content_payload),
10848
+ ...parsedObject(recordRaw.contentPayload),
10849
+ ...parsedObject(recordRaw.record_payload),
10850
+ ...parsedObject(recordRaw.recordPayload),
10851
+ ...parsedObject(recordCore.payload),
10852
+ ...parsedObject(recordCore.content_payload),
10853
+ ...parsedObject(recordCore.contentPayload),
10854
+ ...parsedObject(recordCore.record_payload),
10855
+ ...parsedObject(recordCore.recordPayload)
10856
+ };
10857
+ let raw = {
10858
+ ...chatRaw,
10859
+ ...recordRaw,
10860
+ relation: Object.keys(chatRaw).length === 0 ? {
10861
+ record_uid: reference.recordUid,
10862
+ record_owner_user_id: reference.recordOwnerUserId
10863
+ } : objectValue$1(chatRaw.relation),
10864
+ record: {
10865
+ ...chatRecord,
10866
+ ...recordCore,
10867
+ payload: {
10868
+ ...chatRecordPayload,
10869
+ ...detailPayload
10870
+ }
10871
+ }
10872
+ };
10873
+ const locationContext = await this.runtime.authenticatedPost("/api/v1/records/location/context/get", { record_uid: reference.recordUid }, session, options.signal, {
10874
+ lane: "interactive-read",
10875
+ key: `message-snapshot-location-context:${reference.recordUid}`,
10876
+ failureCooldownMs: 1e4
10877
+ }).catch(() => void 0);
10878
+ if (locationContext !== void 0) {
10879
+ const context = snapshotLocationContext(locationContext);
10880
+ const contextEnvelope = objectValue$1(locationContext);
10881
+ const contextData = objectValue$1(contextEnvelope.data);
10882
+ const contextRecordUid = firstSnapshotText(contextEnvelope.record_uid, contextEnvelope.recordUid, contextEnvelope.uid, contextData.record_uid, contextData.recordUid, contextData.uid, context.record_uid, context.recordUid, context.uid);
10883
+ if (contextRecordUid !== void 0 && contextRecordUid !== reference.recordUid) throw new ArkmePluginError("message-snapshot-detail-mismatch", "快记地点上下文与当前消息不匹配,请刷新后重试", true, 502);
10884
+ raw = mergeSnapshotLocationContext(raw, context);
10885
+ }
10886
+ const detail = snapshotDetailFromChatRaw(raw, {
10887
+ itemUid: reference.recordUid,
10888
+ textContent: reference.textContent,
10889
+ sendAtMillis: reference.sendAtMillis
10890
+ });
10891
+ if (detail.itemUid !== reference.recordUid) throw new ArkmePluginError("message-snapshot-detail-mismatch", "快记详情与当前消息不匹配,请刷新后重试", true, 502);
10892
+ return detail;
10893
+ }
10894
+ async saveMessageLocation(sourceRef, itemUid, location, recordVersion, options = {}) {
10895
+ const session = await this.runtime.requireSession();
10896
+ const source = await this.source.openSourceRef(sourceRef, session.userId);
10897
+ const recordUid = itemUid.trim();
10898
+ if (recordUid === "" || ![
10899
+ "private_chat",
10900
+ "group_chat",
10901
+ "send_to_self",
10902
+ "default_category",
10903
+ "topic"
10904
+ ].includes(source.kind)) throw new ArkmePluginError("message-location-target-invalid", "当前消息不能写入位置快照", false, 400);
10905
+ const latitude = location.latitude;
10906
+ const longitude = location.longitude;
10907
+ if (!Number.isFinite(latitude) || latitude < -90 || latitude > 90 || !Number.isFinite(longitude) || longitude < -180 || longitude > 180) throw new ArkmePluginError("message-location-invalid", "位置坐标无效", false, 400);
10908
+ const altitude = location.altitudeMeters;
10909
+ const speed = location.speedMetersPerSecond;
10910
+ const accuracy = location.accuracyMeters;
10911
+ await this.runtime.authenticatedPost("/api/v1/records/location/set", {
10912
+ record_uid: recordUid,
10913
+ ...recordVersion === void 0 || recordVersion <= 0 ? {} : { expected_record_version: Math.trunc(recordVersion) },
10914
+ location: {
10915
+ source_kind: 1,
10916
+ lat: latitude,
10917
+ lon: longitude,
10918
+ ...altitude === void 0 || !Number.isFinite(altitude) ? {} : { alt: altitude },
10919
+ ...speed === void 0 || !Number.isFinite(speed) || speed < 0 ? {} : { speed },
10920
+ ...accuracy === void 0 || !Number.isFinite(accuracy) || accuracy < 0 ? {} : { accuracy },
10921
+ captured_at: Math.max(1, Math.trunc(location.capturedAtMillis))
10922
+ }
10923
+ }, session, options.signal);
10924
+ }
9880
10925
  async rawChatMembers(chatSessionUid, activeOnly, session, signal) {
9881
10926
  return listValue$16((await this.runtime.authenticatedChatPost("/api/v1/chats/members/list", {
9882
10927
  chat_session_uid: chatSessionUid,
9883
10928
  active_only: activeOnly
9884
10929
  }, session, signal)).items).map(objectValue$1);
9885
10930
  }
9886
- async projectChatMembers(chatSessionUid, rawItems, session, includeViewerLabels, signal) {
10931
+ async projectChatMembers(chatSessionUid, rawItems, session, options) {
9887
10932
  const userIds = rawItems.map((item) => Math.trunc(numberValue$16(item.user_id))).filter((userId) => Number.isSafeInteger(userId) && userId > 0);
9888
- const [profiles, viewerLabels] = await Promise.all([this.profile.publicProfileSummariesByUserIds(userIds, session, signal).catch(() => /* @__PURE__ */ new Map()), includeViewerLabels ? this.source.privateDisplayNamesByUserIds(userIds, { ...signal === void 0 ? {} : { signal } }).catch(() => /* @__PURE__ */ new Map()) : Promise.resolve(/* @__PURE__ */ new Map())]);
10933
+ const [profiles, viewerLabels] = await Promise.all([this.profile.publicProfileSummariesByUserIds(userIds, session, options.signal).catch(() => /* @__PURE__ */ new Map()), options.includeViewerLabels ? this.source.privateDisplayNamesByUserIds(userIds, { ...options.signal === void 0 ? {} : { signal: options.signal } }).catch(() => /* @__PURE__ */ new Map()) : Promise.resolve(/* @__PURE__ */ new Map())]);
9889
10934
  const members = [];
9890
10935
  for (const item of rawItems) {
9891
10936
  const userId = Math.trunc(numberValue$16(item.user_id));
9892
10937
  if (!Number.isSafeInteger(userId) || userId <= 0) continue;
9893
10938
  const profile = profiles.get(userId);
9894
- const { displayName, memberName, secondaryName } = resolveChatMemberDisplayNames({
9895
- userId,
9896
- remarkCandidates: [],
9897
- memberNameCandidates: [item.display_name_snapshot],
9898
- userNameCandidates: [
9899
- viewerLabels.get(userId),
9900
- item.remark,
9901
- profile?.displayName
9902
- ]
9903
- });
10939
+ const { displayName, memberName, secondaryName } = projectChatMemberDisplayNames(item, userId, viewerLabels.get(userId), profile?.displayName);
9904
10940
  const role = chatMemberRole$1(item.role);
9905
10941
  const status = chatMemberStatus$1(item.status);
9906
10942
  const extra = parsedObject(item.extra);
9907
10943
  members.push({
9908
10944
  memberRef: await this.sealChatMemberRef(session.userId, chatSessionUid, userId),
10945
+ ...options.includeHumanMentionRefs && status === "active" && userId !== session.userId ? { mentionRef: await this.sealChatHumanMentionRef(session.userId, chatSessionUid, userId, displayName) } : {},
9909
10946
  displayName,
9910
10947
  ...memberName === "" ? {} : { memberName },
9911
10948
  ...secondaryName === "" ? {} : { secondaryName },
@@ -9938,6 +10975,7 @@ var ChatService = class {
9938
10975
  if (uid === "") continue;
9939
10976
  const relationUid = stringValue$1(relation.rel_uid).trim();
9940
10977
  const senderUserId = Math.trunc(numberValue$16(relation.sender_user_id));
10978
+ const recordOwnerUserId = chatRecordOwnerUserId(relation, record, payload, senderUserId);
9941
10979
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
9942
10980
  const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
9943
10981
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
@@ -9945,16 +10983,22 @@ var ChatService = class {
9945
10983
  viewerUserId: session.userId,
9946
10984
  chatSessionUid: source.ownerRef,
9947
10985
  relationUid,
9948
- recordOwnerUserId: integerLikeValue$1(record.record_owner_user_id ?? record.recordOwnerUserId ?? payload.record_owner_user_id ?? payload.recordOwnerUserId ?? relation.record_owner_user_id ?? relation.recordOwnerUserId),
10986
+ recordOwnerUserId,
9949
10987
  recordUid: uid,
9950
10988
  senderUserId,
9951
- sequence: integerLikeValue$1(relation.seq),
10989
+ sequence: integerLikeValue(relation.seq),
9952
10990
  signingKey
9953
10991
  });
9954
10992
  const rawAgentSource = timelineAgentSource(relation, record, payload);
9955
10993
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
9956
10994
  const contentBlocks = this.media.richContentBlocks(item, session.userId);
9957
10995
  const senderName = stringValue$1(relation.display_name_snapshot).trim() || "Arkme用户";
10996
+ const captureContext = timelineCaptureContext({
10997
+ ...record,
10998
+ ...payload
10999
+ });
11000
+ const recordDurationMillis = positiveNumberValue$1(payload.record_duration_millis, record.record_duration_millis, payload.recordDurationMillis, record.recordDurationMillis, payload.cost_mill_sec, record.cost_mill_sec, payload.costMillSec, record.costMillSec);
11001
+ const editDurationMillis = positiveNumberValue$1(payload.edit_duration_millis, record.edit_duration_millis, payload.editDurationMillis, record.editDurationMillis, payload.incr_cost_mill_sec, record.incr_cost_mill_sec, payload.incrCostMillSec, record.incrCostMillSec);
9958
11002
  const itemIndex = items.push({
9959
11003
  itemUid: uid,
9960
11004
  ...source.kind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : { messageRef: this.sealMessageRef(session.userId, source.ownerRef, relationUid, signingKey) },
@@ -9964,7 +11008,7 @@ var ChatService = class {
9964
11008
  sourceOwnerRef: source.ownerRef,
9965
11009
  chatSessionUid: source.ownerRef,
9966
11010
  relationUid,
9967
- recordOwnerUserId: 0,
11011
+ recordOwnerUserId,
9968
11012
  recordUid: uid,
9969
11013
  senderUserId,
9970
11014
  senderName,
@@ -9991,8 +11035,9 @@ var ChatService = class {
9991
11035
  displayKind: numberValue$16(payload.display_kind),
9992
11036
  version: numberValue$16(payload.version ?? record.version),
9993
11037
  updateAtMillis: numberValue$16(payload.update_at ?? record.update_at),
9994
- recordDurationMillis: numberValue$16(payload.record_duration_millis),
9995
- editDurationMillis: numberValue$16(payload.edit_duration_millis),
11038
+ ...recordDurationMillis === void 0 ? {} : { recordDurationMillis },
11039
+ ...editDurationMillis === void 0 ? {} : { editDurationMillis },
11040
+ ...captureContext === void 0 ? {} : { captureContext },
9996
11041
  contentBlocks,
9997
11042
  ...forwardRecords === void 0 ? {} : { forwardRecords },
9998
11043
  ...sharedRecording === void 0 ? {} : { sharedRecording }
@@ -10015,11 +11060,12 @@ var ChatService = class {
10015
11060
  chatSessionUid,
10016
11061
  targetUserId
10017
11062
  });
10018
- return `arkme-chat-member-v1.${encoded}.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest("base64url")}`;
11063
+ const signature = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest("base64url");
11064
+ return `${CHAT_MEMBER_REF_PREFIX}.${encoded}.${signature}`;
10019
11065
  }
10020
11066
  async openChatMemberRef(memberRef, expectedViewerUserId, expectedChatSessionUid) {
10021
11067
  const parts = memberRef.trim().split(".");
10022
- if (parts.length !== 3 || parts[0] !== "arkme-chat-member-v1") throw new ArkmePluginError("chat-member-ref-invalid", "聊天成员引用无效", false);
11068
+ if (parts.length !== 3 || parts[0] !== CHAT_MEMBER_REF_PREFIX) throw new ArkmePluginError("chat-member-ref-invalid", "聊天成员引用无效", false);
10023
11069
  const encoded = parts[1] ?? "";
10024
11070
  const supplied = Buffer.from(parts[2] ?? "", "base64url");
10025
11071
  const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest();
@@ -10039,6 +11085,42 @@ var ChatService = class {
10039
11085
  if (raw.version !== 1 || result.viewerUserId !== expectedViewerUserId || result.chatSessionUid !== expectedChatSessionUid || !Number.isSafeInteger(result.targetUserId) || result.targetUserId <= 0) throw new ArkmePluginError("chat-member-ref-invalid", "聊天成员引用与当前账号或会话不匹配", false, 403);
10040
11086
  return result;
10041
11087
  }
11088
+ async sealChatHumanMentionRef(viewerUserId, chatSessionUid, targetUserId, displayNameSnapshot) {
11089
+ const normalizedDisplayNameSnapshot = normalizedJoinDisplayName(displayNameSnapshot);
11090
+ if (normalizedDisplayNameSnapshot === "") throw new ArkmePluginError("chat-mention-ref-invalid", "真人 mention 展示名无效", false);
11091
+ const encoded = encodeOpaqueJson$6({
11092
+ version: 1,
11093
+ viewerUserId,
11094
+ chatSessionUid,
11095
+ targetUserId,
11096
+ displayNameSnapshot: normalizedDisplayNameSnapshot
11097
+ });
11098
+ const signature = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`${CHAT_HUMAN_MENTION_REF_PREFIX}.${encoded}`).digest("base64url");
11099
+ return `${CHAT_HUMAN_MENTION_REF_PREFIX}.${encoded}.${signature}`;
11100
+ }
11101
+ async openChatHumanMentionRef(mentionRef, expectedViewerUserId, expectedChatSessionUid) {
11102
+ const parts = mentionRef.trim().split(".");
11103
+ if (parts.length !== 3 || parts[0] !== CHAT_HUMAN_MENTION_REF_PREFIX) throw new ArkmePluginError("chat-mention-ref-invalid", "真人 mention 引用无效", false);
11104
+ const encoded = parts[1] ?? "";
11105
+ const supplied = Buffer.from(parts[2] ?? "", "base64url");
11106
+ const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`${CHAT_HUMAN_MENTION_REF_PREFIX}.${encoded}`).digest();
11107
+ if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("chat-mention-ref-invalid", "真人 mention 引用无效", false);
11108
+ let raw;
11109
+ try {
11110
+ raw = objectValue$1(decodeOpaqueJson$5(encoded));
11111
+ } catch (error) {
11112
+ throw new ArkmePluginError("chat-mention-ref-invalid", "真人 mention 引用无效", false, 400, { cause: error });
11113
+ }
11114
+ const result = {
11115
+ version: 1,
11116
+ viewerUserId: Math.trunc(numberValue$16(raw.viewerUserId)),
11117
+ chatSessionUid: stringValue$1(raw.chatSessionUid).trim(),
11118
+ targetUserId: Math.trunc(numberValue$16(raw.targetUserId)),
11119
+ displayNameSnapshot: normalizedJoinDisplayName(raw.displayNameSnapshot)
11120
+ };
11121
+ if (raw.version !== 1 || result.viewerUserId !== expectedViewerUserId || result.chatSessionUid !== expectedChatSessionUid || !Number.isSafeInteger(result.targetUserId) || result.targetUserId <= 0 || result.displayNameSnapshot === "") throw new ArkmePluginError("chat-mention-ref-invalid", "真人 mention 引用与当前账号或会话不匹配", false, 403);
11122
+ return result;
11123
+ }
10042
11124
  messageActionRefPayload(input) {
10043
11125
  const contentBlocks = input.contentBlocks ?? [];
10044
11126
  const fileNames = contentBlocks.filter((block) => block.kind === "file" && block.fileName.trim() !== "").slice(0, 5).map((block) => block.fileName.trim().slice(0, 256));
@@ -39417,7 +40499,7 @@ var CommunityService = class {
39417
40499
  //#region src/services/extension-review-service.ts
39418
40500
  const ARKME_EXTENSION_REVIEW_REF_TTL_MILLIS = 1800 * 1e3;
39419
40501
  const MAX_ARKME_EXTENSION_REVIEW_REFS = 4096;
39420
- function safeFailureMessage$4(error) {
40502
+ function safeFailureMessage$3(error) {
39421
40503
  if (error instanceof ArkmePluginError) return error.message;
39422
40504
  if (error instanceof Error && error.message.trim() !== "") return error.message;
39423
40505
  return "未知错误";
@@ -39545,7 +40627,7 @@ var ExtensionReviewService = class {
39545
40627
  await this.runtime.stateStore.removeExtensionReviewOperation(userId, operation.clientMutationId);
39546
40628
  throw new ArkmePluginError("extension-review-registry-rejected", `评论已写入首页,但市集未接受:${error.message}`, false, error.httpStatus, { cause: error });
39547
40629
  }
39548
- await this.runtime.stateStore.markExtensionReviewOperation(userId, operation.clientMutationId, "failed", safeFailureMessage$4(error));
40630
+ await this.runtime.stateStore.markExtensionReviewOperation(userId, operation.clientMutationId, "failed", safeFailureMessage$3(error));
39549
40631
  throw new ArkmePluginError("extension-review-registry-pending", "评论已写入首页,但同步到扩展详情失败;请保留当前内容并重试", true, 503, { cause: error });
39550
40632
  }
39551
40633
  const authors = await this.extensionAuthors([result.review.user_id], signal);
@@ -39689,7 +40771,7 @@ function compactAiPolishActorLabel(value) {
39689
40771
  const characters = [...new Intl.Segmenter("zh-CN", { granularity: "grapheme" }).segment(normalized)].map((segment) => segment.segment);
39690
40772
  return characters.length <= 4 ? normalized : characters.slice(0, 4).join("") + "…";
39691
40773
  }
39692
- function safeFailureMessage$3(error) {
40774
+ function safeFailureMessage$2(error) {
39693
40775
  if (error instanceof ArkmePluginError) return error.message;
39694
40776
  if (error instanceof Error && error.message.trim() !== "") return error.message;
39695
40777
  return "未知错误";
@@ -40044,7 +41126,7 @@ var GroupAiPolishService = class {
40044
41126
  polished = this.aiPolishTextResult(data);
40045
41127
  } catch (error) {
40046
41128
  const sent = await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, void 0, void 0, options.signal, options);
40047
- return this.withFailedAiPolishRetry(sent, sourceRef, chatSessionUid, relationUid, recordUid, originalText, 1, session.userId, safeFailureMessage$3(error));
41129
+ return this.withFailedAiPolishRetry(sent, sourceRef, chatSessionUid, relationUid, recordUid, originalText, 1, session.userId, safeFailureMessage$2(error));
40048
41130
  }
40049
41131
  if (polished.state === 1 && polished.action === 1 && polished.polishedText !== "") {
40050
41132
  const activeRule = config.rules.find((rule) => rule.ruleUid === polished.ruleUid);
@@ -40938,9 +42020,7 @@ var InterwovenService = class {
40938
42020
  const source = await this.source.openSourceRef(sourceRef, session.userId);
40939
42021
  if (source.kind !== "private_chat") throw new ArkmePluginError("interwoven-source-invalid", "交织瞬间仅支持普通私聊", false, 400);
40940
42022
  const reference = await this.openInterwovenMomentRef(momentRef, session.userId, source.ownerRef);
40941
- if (!this.runtime.config.interwovenMomentsEnabled) throw new ArkmePluginError("interwoven-disabled", "交织瞬间能力当前未开启", false, 403);
40942
- 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);
40943
- await this.assertHumanPrivateSource(source, session, signal);
42023
+ await this.assertInterwovenOperationAllowed(source, session, signal);
40944
42024
  if (reference.detailMode === "owner_payload") return {
40945
42025
  momentId: reference.momentId,
40946
42026
  groupName: reference.groupName,
@@ -40981,6 +42061,29 @@ var InterwovenService = class {
40981
42061
  degraded: status !== 1 || textContent === ""
40982
42062
  };
40983
42063
  }
42064
+ async relatedQuickNoteLocator(sourceRef, momentRef, signal) {
42065
+ const session = await this.runtime.requireSession();
42066
+ const normalizedSourceRef = sourceRef.trim();
42067
+ const source = await this.source.openSourceRef(normalizedSourceRef, session.userId);
42068
+ if (source.kind !== "private_chat") throw new ArkmePluginError("interwoven-source-invalid", "交织瞬间仅支持普通私聊", false, 400);
42069
+ const reference = await this.openInterwovenMomentRef(momentRef, session.userId, source.ownerRef);
42070
+ await this.assertInterwovenOperationAllowed(source, session, signal);
42071
+ return {
42072
+ viewerUserId: session.userId,
42073
+ sourceRef: normalizedSourceRef,
42074
+ sourceOwnerRef: source.ownerRef,
42075
+ contextType: reference.sourceChatSessionUid === "" ? "record" : "chat",
42076
+ recordUid: reference.recordUid,
42077
+ recordOwnerUserId: reference.recordOwnerUserId,
42078
+ chatSessionUid: reference.sourceChatSessionUid
42079
+ };
42080
+ }
42081
+ async assertInterwovenOperationAllowed(source, session, signal) {
42082
+ if (source.kind !== "private_chat") throw new ArkmePluginError("interwoven-source-invalid", "交织瞬间仅支持普通私聊", false, 400);
42083
+ if (!this.runtime.config.interwovenMomentsEnabled) throw new ArkmePluginError("interwoven-disabled", "交织瞬间能力当前未开启", false, 403);
42084
+ 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);
42085
+ await this.assertHumanPrivateSource(source, session, signal);
42086
+ }
40984
42087
  async assertHumanPrivateSource(source, session, signal) {
40985
42088
  const detail = await this.runtime.authenticatedChatPost("/api/v1/chats/detail", { chat_session_uid: source.ownerRef }, session, signal);
40986
42089
  const chatSession = objectValue$1(detail.session);
@@ -42994,416 +44097,327 @@ var ProfileService = class {
42994
44097
  }
42995
44098
  };
42996
44099
  //#endregion
42997
- //#region src/services/record-service.ts
44100
+ //#region src/services/related-quick-note-service.ts
44101
+ const RELATED_QUICK_NOTE_LIMIT = 20;
44102
+ const RELATED_QUICK_NOTE_REF_TTL_MS = 720 * 60 * 1e3;
44103
+ const RELATED_QUICK_NOTE_REF_CAP = 1e3;
42998
44104
  function numberValue$6(value) {
42999
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
43000
- }
43001
- function listValue$7(value) {
43002
- return Array.isArray(value) ? value : [];
43003
- }
43004
- const DSH_AGENT_INPUT_CREATION_SOURCE = 3;
43005
- const MAX_DEFAULT_CATEGORY_FILTER_BACKFILL_PAGES = 5;
43006
- function integerLikeValue(value) {
43007
- if (typeof value === "number" && Number.isFinite(value)) return Math.trunc(value);
44105
+ if (typeof value === "number" && Number.isFinite(value)) return value;
43008
44106
  if (typeof value === "string" && value.trim() !== "") {
43009
44107
  const parsed = Number(value.trim());
43010
- if (Number.isFinite(parsed)) return Math.trunc(parsed);
44108
+ if (Number.isFinite(parsed)) return parsed;
43011
44109
  }
43012
44110
  return 0;
43013
44111
  }
43014
- function recordCreationSource(raw) {
44112
+ function firstString(item, keys) {
44113
+ for (const key of keys) {
44114
+ const value = stringValue$1(item[key]).trim();
44115
+ if (value !== "") return value;
44116
+ }
44117
+ return "";
44118
+ }
44119
+ function firstPositiveInteger(item, keys) {
44120
+ for (const key of keys) {
44121
+ const value = Math.trunc(numberValue$6(item[key]));
44122
+ if (Number.isSafeInteger(value) && value > 0) return value;
44123
+ }
44124
+ return 0;
44125
+ }
44126
+ function recordCore(raw) {
43015
44127
  const item = objectValue$1(raw);
43016
- const core = objectValue$1(item.record_core);
43017
- return integerLikeValue(item.creation_source ?? item.creationSource ?? core.creation_source ?? core.creationSource);
44128
+ return objectValue$1(item.record_core ?? item.recordCore);
43018
44129
  }
43019
- function isDSHAgentInputRecord(raw) {
43020
- return recordCreationSource(raw) === DSH_AGENT_INPUT_CREATION_SOURCE;
44130
+ function recordUid(raw) {
44131
+ const item = objectValue$1(raw);
44132
+ const core = recordCore(raw);
44133
+ return firstString(item, [
44134
+ "record_uid",
44135
+ "recordUid",
44136
+ "uid",
44137
+ "target_record_uid",
44138
+ "targetRecordUid"
44139
+ ]) || firstString(core, [
44140
+ "record_uid",
44141
+ "recordUid",
44142
+ "uid"
44143
+ ]);
43021
44144
  }
43022
- function safeFailureMessage$2(error) {
43023
- if (error instanceof ArkmePluginError) return error.message;
43024
- if (error instanceof Error && error.message.trim() !== "") return error.message;
43025
- return "未知错误";
44145
+ function recordOwnerUserId(raw) {
44146
+ const item = objectValue$1(raw);
44147
+ const core = recordCore(raw);
44148
+ return firstPositiveInteger(item, [
44149
+ "record_owner_user_id",
44150
+ "recordOwnerUserId",
44151
+ "owner_user_id",
44152
+ "ownerUserId"
44153
+ ]) || firstPositiveInteger(core, [
44154
+ "record_owner_user_id",
44155
+ "recordOwnerUserId",
44156
+ "owner_user_id",
44157
+ "ownerUserId"
44158
+ ]);
43026
44159
  }
43027
- var RecordService = class {
44160
+ function matchesRecordIdentity(raw, expectedUid, expectedOwnerUserId) {
44161
+ const uidKeys = [
44162
+ "record_uid",
44163
+ "recordUid",
44164
+ "uid",
44165
+ "target_record_uid",
44166
+ "targetRecordUid"
44167
+ ];
44168
+ const ownerKeys = [
44169
+ "record_owner_user_id",
44170
+ "recordOwnerUserId",
44171
+ "owner_user_id",
44172
+ "ownerUserId"
44173
+ ];
44174
+ const identities = [objectValue$1(raw), recordCore(raw)];
44175
+ const uids = identities.flatMap((item) => uidKeys.map((key) => stringValue$1(item[key]).trim()).filter((value) => value !== ""));
44176
+ const ownerUserIds = identities.flatMap((item) => ownerKeys.map((key) => Math.trunc(numberValue$6(item[key]))).filter((value) => Number.isSafeInteger(value) && value > 0));
44177
+ return uids.length > 0 && ownerUserIds.length > 0 && uids.every((value) => value === expectedUid) && ownerUserIds.every((value) => value === expectedOwnerUserId);
44178
+ }
44179
+ function millisValue(value) {
44180
+ const parsed = Math.trunc(numberValue$6(value));
44181
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) return 0;
44182
+ return parsed > 1e11 ? parsed : parsed * 1e3;
44183
+ }
44184
+ function listValue$7(value) {
44185
+ return Array.isArray(value) ? value : [];
44186
+ }
44187
+ function descriptorFromRaw(raw) {
44188
+ const item = objectValue$1(raw);
44189
+ const core = recordCore(raw);
44190
+ const uid = recordUid(raw);
44191
+ const ownerUserId = recordOwnerUserId(raw);
44192
+ if (uid === "" || ownerUserId <= 0) return void 0;
44193
+ const authorUserId = firstPositiveInteger(item, [
44194
+ "author_user_id",
44195
+ "authorUserId",
44196
+ "creator_user_id",
44197
+ "creatorUserId"
44198
+ ]) || firstPositiveInteger(core, ["creator_user_id", "creatorUserId"]) || ownerUserId;
44199
+ const senderName = firstString(item, [
44200
+ "author_name",
44201
+ "authorName",
44202
+ "author_nickname",
44203
+ "authorNickname",
44204
+ "sender_name",
44205
+ "senderName",
44206
+ "nickname",
44207
+ "nick_name"
44208
+ ]);
44209
+ const title = firstString(item, ["title"]) || firstString(core, ["title"]);
44210
+ const textPreview = firstString(item, [
44211
+ "text_preview",
44212
+ "textPreview",
44213
+ "text",
44214
+ "content",
44215
+ "text_content",
44216
+ "textContent"
44217
+ ]) || firstString(core, ["text_content", "textContent"]);
44218
+ const sendAtMillis = millisValue(item.send_at ?? item.sendAt ?? core.send_at ?? core.sendAt);
44219
+ const sourceLabel = firstString(item, ["source_label", "sourceLabel"]);
44220
+ return {
44221
+ recordUid: uid,
44222
+ recordOwnerUserId: ownerUserId,
44223
+ authorUserId,
44224
+ senderName: senderName.slice(0, 200),
44225
+ sendAtMillis,
44226
+ title: title.slice(0, 500),
44227
+ textPreview: textPreview.slice(0, 2e3),
44228
+ ...sourceLabel === "" ? {} : { sourceLabel: sourceLabel.slice(0, 200) }
44229
+ };
44230
+ }
44231
+ var RelatedQuickNoteService = class {
43028
44232
  runtime;
44233
+ record;
43029
44234
  media;
43030
- source;
44235
+ profile;
43031
44236
  privacy;
43032
- constructor(runtime, media, source, privacy = new ArkmePrivacyVisibilityService(runtime)) {
44237
+ references = /* @__PURE__ */ new Map();
44238
+ constructor(runtime, record, media, profile, privacy = new ArkmePrivacyVisibilityService(runtime)) {
43033
44239
  this.runtime = runtime;
44240
+ this.record = record;
43034
44241
  this.media = media;
43035
- this.source = source;
44242
+ this.profile = profile;
43036
44243
  this.privacy = privacy;
43037
44244
  }
43038
- async longArticleDetail(sourceRef, itemUid, signal) {
43039
- const session = await this.runtime.requireSession();
43040
- const source = await this.source.openSourceRef(sourceRef, session.userId);
43041
- const uid = itemUid.trim();
43042
- if (uid === "") throw new ArkmePluginError("long-article-item-invalid", "长文记录标识无效", false);
43043
- const data = await this.runtime.authenticatedPost("/api/v1/records/detail", { record_uid: uid }, session, signal);
43044
- const core = objectValue$1(data.record_core);
43045
- const recordUid = stringValue$1(core.record_uid).trim();
43046
- const templateKind = numberValue$6(core.template_kind);
43047
- const displayKind = numberValue$6(core.display_kind);
43048
- if (recordUid !== uid || templateKind !== 8 && displayKind !== 1) throw new ArkmePluginError("long-article-not-found", "未找到可用的长文详情", false, 404);
43049
- const originContainerRef = stringValue$1(core.origin_container_ref).trim();
43050
- if (source.kind === "topic") {
43051
- if (stringValue$1(objectValue$1(data.topic_core).topic_uid).trim() !== source.ownerRef) throw new ArkmePluginError("long-article-source-mismatch", "长文不属于当前会话", false, 403);
43052
- } else if (source.kind === "private_chat" || source.kind === "group_chat") {
43053
- if (originContainerRef !== source.ownerRef) throw new ArkmePluginError("long-article-source-mismatch", "长文不属于当前会话", false, 403);
43054
- } else if (numberValue$6(core.owner_user_id) !== session.userId) throw new ArkmePluginError("long-article-source-mismatch", "长文不属于当前会话", false, 403);
43055
- const recordDurationMillis = Math.max(0, Math.trunc(numberValue$6(core.record_duration_millis)));
43056
- const editDurationMillis = Math.max(0, Math.trunc(numberValue$6(core.edit_duration_millis)));
43057
- return {
43058
- sourceRef,
43059
- itemUid: recordUid,
43060
- title: stringValue$1(core.title),
43061
- textContent: stringValue$1(core.text_content),
43062
- sendAtMillis: Math.trunc(numberValue$6(core.send_at)),
43063
- updateAtMillis: Math.trunc(numberValue$6(core.update_at)),
43064
- recordDurationMillis,
43065
- editDurationMillis,
43066
- thinkingDurationMillis: recordDurationMillis + editDurationMillis,
43067
- version: Math.trunc(numberValue$6(core.version)),
43068
- editable: numberValue$6(core.owner_user_id) === session.userId && numberValue$6(core.creator_user_id) === session.userId
43069
- };
43070
- }
43071
- async updateLongArticle(sourceRef, itemUid, input) {
43072
- if (this.runtime.config.richMediaSendEnabled === false) throw new ArkmePluginError("rich-content-disabled", "长文编辑已被插件配置关闭", false, 403);
43073
- const session = await this.runtime.requireSession();
43074
- const detail = await this.longArticleDetail(sourceRef, itemUid);
43075
- const title = input.title.trim();
43076
- const textContent = input.textContent.trim();
43077
- const editDurationMillis = Math.max(0, Math.trunc(input.editDurationMillis));
43078
- if (!detail.editable) throw new ArkmePluginError("long-article-not-editable", "只能编辑自己发布的长文", false, 403);
43079
- if (title === "" || title.length > 100 || textContent === "" || textContent.length > 4e4 || !Number.isSafeInteger(input.version) || input.version <= 0 || input.version !== detail.version) throw new ArkmePluginError("long-article-update-invalid", "长文内容或版本无效,请刷新后重试", false, 409);
43080
- await this.runtime.authenticatedPost("/api/v1/records/update", {
43081
- record_uid: detail.itemUid,
43082
- template_kind: 1,
43083
- display_kind: 1,
43084
- title,
43085
- text_content: textContent,
43086
- record_duration_millis: detail.recordDurationMillis,
43087
- edit_duration_millis: editDurationMillis,
43088
- version: detail.version
43089
- }, session);
43090
- return await this.longArticleDetail(sourceRef, itemUid);
43091
- }
43092
- async getLongArticleDraft(sourceRef, itemUid) {
43093
- const session = await this.runtime.requireSession();
43094
- await this.source.openSourceRef(sourceRef, session.userId);
43095
- return await this.runtime.stateStore.getLongArticleDraft(session.userId, sourceRef, itemUid?.trim() || void 0);
43096
- }
43097
- async putLongArticleDraft(draft) {
43098
- const session = await this.runtime.requireSession();
43099
- await this.source.openSourceRef(draft.sourceRef, session.userId);
43100
- const itemUid = draft.itemUid?.trim() || void 0;
43101
- if (draft.title.length > 100 || draft.textContent.length > 4e4 || draft.durationMillis < 0) throw new ArkmePluginError("long-article-draft-invalid", "长文草稿内容无效", false);
43102
- await this.runtime.stateStore.putLongArticleDraft(session.userId, {
43103
- sourceRef: draft.sourceRef,
43104
- ...itemUid === void 0 ? {} : { itemUid },
43105
- title: draft.title,
43106
- textContent: draft.textContent,
43107
- durationMillis: Math.max(0, Math.trunc(draft.durationMillis)),
43108
- updatedAtMillis: Date.now()
43109
- });
43110
- }
43111
- async removeLongArticleDraft(sourceRef, itemUid) {
43112
- const session = await this.runtime.requireSession();
43113
- await this.source.openSourceRef(sourceRef, session.userId);
43114
- await this.runtime.stateStore.removeLongArticleDraft(session.userId, sourceRef, itemUid?.trim() || void 0);
43115
- }
43116
- async cachedSnapshot() {
43117
- const session = await this.runtime.requireSession();
43118
- return await this.runtime.stateStore.cachedSnapshot(session.userId);
43119
- }
43120
- async queryCached(options) {
43121
- const session = await this.runtime.requireSession();
43122
- return await this.runtime.stateStore.queryCached(session.userId, options);
43123
- }
43124
- async refreshLatest() {
43125
- await Promise.all([this.summary(), this.list(50)]);
43126
- }
43127
- async refreshSnapshot() {
43128
- await this.refreshLatest();
43129
- return await this.cachedSnapshot();
43130
- }
43131
- async summary() {
43132
- const session = await this.runtime.requireSession();
43133
- const data = await this.runtime.authenticatedPost("/api/v1/records/uncategorized/summary", {}, session);
43134
- const summary = {
43135
- recordCount: numberValue$6(data.record_count),
43136
- wordsCount: numberValue$6(data.words_count ?? data.available_text_rune_count),
43137
- totalSec: numberValue$6(data.total_sec ?? data.available_voice_duration_sec)
43138
- };
43139
- await this.runtime.stateStore.cacheSummary(session.userId, summary);
43140
- return summary;
44245
+ dispose() {
44246
+ this.references.clear();
43141
44247
  }
43142
- async list(limit, cursor) {
44248
+ async list(locator, signal) {
43143
44249
  const session = await this.runtime.requireSession();
43144
- const lockedRecordUids = await this.privacy.lockedRecordUids(session);
43145
- const normalizedLimit = Math.min(50, Math.max(1, Math.trunc(limit || 30)));
43146
- let requestCursor = cursor;
43147
- let hasMore = false;
43148
- let backendNextCursor;
43149
- const visibleRawItems = [];
43150
- for (let pageIndex = 0; pageIndex < MAX_DEFAULT_CATEGORY_FILTER_BACKFILL_PAGES; pageIndex += 1) {
43151
- const data = await this.runtime.authenticatedPost("/api/v1/records/uncategorized/query", {
43152
- limit: normalizedLimit,
43153
- ...requestCursor === void 0 ? {} : {
43154
- cursor_send_at: requestCursor.sendAtMillis,
43155
- cursor_record_uid: requestCursor.recordUid
43156
- }
43157
- }, session);
43158
- const rawPageItems = listValue$7(data.items);
43159
- const visiblePageItems = rawPageItems.filter((raw) => !isDSHAgentInputRecord(raw) && !arkmePrivacyLockedRecord(raw) && !lockedRecordUids.has(this.recordUid(raw)));
43160
- visibleRawItems.push(...visiblePageItems);
43161
- hasMore = data.has_more === true;
43162
- const nextSendAt = numberValue$6(data.next_cursor_send_at);
43163
- const nextUid = stringValue$1(data.next_cursor_record_uid);
43164
- backendNextCursor = nextSendAt > 0 && nextUid !== "" ? {
43165
- sendAtMillis: nextSendAt,
43166
- recordUid: nextUid
43167
- } : void 0;
43168
- const filteredCount = rawPageItems.length - visiblePageItems.length;
43169
- if (visibleRawItems.length >= normalizedLimit || !hasMore || backendNextCursor === void 0 || filteredCount === 0) break;
43170
- requestCursor = backendNextCursor;
44250
+ const source = this.validLocator(locator, session.userId);
44251
+ const response = await this.runtime.authenticatedPost("/api/v1/records/related/query", {
44252
+ record_uid: source.recordUid,
44253
+ record_owner_user_id: source.recordOwnerUserId,
44254
+ context_type: source.contextType,
44255
+ ...source.contextType === "chat" ? { chat_session_uid: source.chatSessionUid } : {},
44256
+ limit: RELATED_QUICK_NOTE_LIMIT
44257
+ }, session, signal);
44258
+ const excluded = /* @__PURE__ */ new Set([source.recordUid]);
44259
+ const lockedRecordUids = await this.privacy.lockedRecordUids(session, signal);
44260
+ const directItems = listValue$7(response.items ?? response.records);
44261
+ const candidates = this.relatedCandidates(response, directItems, excluded, lockedRecordUids);
44262
+ const missingUids = candidates.filter((candidate) => descriptorFromRaw(candidate.raw) === void 0).map((candidate) => candidate.uid);
44263
+ const hydratedByUid = /* @__PURE__ */ new Map();
44264
+ if (missingUids.length > 0) {
44265
+ const batch = await this.runtime.authenticatedDataPost("/api/v1/memo/batch-get-records", { record_uids: missingUids }, session, signal);
44266
+ for (const raw of listValue$7(batch.items)) {
44267
+ const uid = recordUid(raw);
44268
+ if (uid !== "" && !hydratedByUid.has(uid)) hydratedByUid.set(uid, raw);
44269
+ }
43171
44270
  }
43172
- const visibleOverflow = visibleRawItems.length > normalizedLimit;
43173
- const rawItems = visibleRawItems.slice(0, normalizedLimit);
43174
- const media = await this.media.hydrateRecordMediaPage(rawItems, session);
43175
- const items = rawItems.map((raw) => {
43176
- const recordUid = this.recordUid(raw);
43177
- const displayItems = media.displayItemsByRecordUid.get(recordUid);
43178
- return this.recordItem(raw, session.userId, {
43179
- ...displayItems === void 0 ? {} : { displayItems },
43180
- mediaUnavailable: media.unavailableRecordUids.has(recordUid)
44271
+ const descriptors = this.descriptorsFromItems(candidates.flatMap((candidate) => descriptorFromRaw(candidate.raw) === void 0 ? hydratedByUid.has(candidate.uid) ? [hydratedByUid.get(candidate.uid)] : [] : [candidate.raw]), excluded, lockedRecordUids);
44272
+ const profiles = await this.profile.publicProfileSummariesByUserIds(descriptors.map((item) => item.authorUserId), session, signal).catch(() => /* @__PURE__ */ new Map());
44273
+ const items = [];
44274
+ for (const descriptor of descriptors) {
44275
+ const profile = profiles.get(descriptor.authorUserId);
44276
+ const senderName = descriptor.senderName || profile?.displayName || (descriptor.authorUserId === session.userId ? "我" : "Arkme 用户");
44277
+ const senderAvatarRef = profile?.avatarUrl === void 0 ? void 0 : await this.profile.sealProfileImageRef(session.userId, descriptor.authorUserId);
44278
+ const relatedRef = await this.sealReference({
44279
+ viewerUserId: session.userId,
44280
+ sourceRef: source.sourceRef,
44281
+ sourceOwnerRef: source.sourceOwnerRef,
44282
+ recordUid: descriptor.recordUid,
44283
+ recordOwnerUserId: descriptor.recordOwnerUserId,
44284
+ senderName,
44285
+ ...senderAvatarRef === void 0 ? {} : { senderAvatarRef }
43181
44286
  });
43182
- }).filter((item) => item !== void 0);
43183
- const lastVisibleRaw = rawItems.at(-1);
43184
- const lastVisibleCursor = lastVisibleRaw === void 0 ? void 0 : {
43185
- sendAtMillis: numberValue$6(objectValue$1(lastVisibleRaw).send_at ?? objectValue$1(objectValue$1(lastVisibleRaw).record_core).send_at),
43186
- recordUid: this.recordUid(lastVisibleRaw)
43187
- };
43188
- const filledRequestedPage = rawItems.length >= normalizedLimit;
43189
- const nextCursor = lastVisibleCursor !== void 0 && lastVisibleCursor.sendAtMillis > 0 && lastVisibleCursor.recordUid !== "" && (visibleOverflow || filledRequestedPage && hasMore) ? lastVisibleCursor : backendNextCursor;
43190
- const page = {
44287
+ items.push({
44288
+ relatedRef,
44289
+ senderName,
44290
+ ...senderAvatarRef === void 0 ? {} : { senderAvatarRef },
44291
+ sendAtMillis: descriptor.sendAtMillis,
44292
+ title: descriptor.title,
44293
+ textPreview: descriptor.textPreview,
44294
+ ...descriptor.sourceLabel === void 0 ? {} : { sourceLabel: descriptor.sourceLabel }
44295
+ });
44296
+ }
44297
+ return {
43191
44298
  items,
43192
- hasMore: hasMore || visibleOverflow,
43193
- ...nextCursor === void 0 ? {} : { nextCursor }
43194
- };
43195
- await this.runtime.stateStore.cachePage(session.userId, page, cursor);
43196
- return page;
43197
- }
43198
- async createText(recordUid, textContent) {
43199
- const session = await this.runtime.requireSession();
43200
- const normalizedUid = recordUid.trim();
43201
- const normalizedText = textContent.trim();
43202
- if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
43203
- if (normalizedText === "") throw new ArkmePluginError("record-text-empty", "请输入要发给自己的内容", false);
43204
- if (normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-text-too-long", `内容不能超过 ${this.runtime.config.maxTextLength} 个字符`, false);
43205
- const now = Date.now();
43206
- const pending = {
43207
- recordUid: normalizedUid,
43208
- textContent: normalizedText,
43209
- createdAtMillis: now,
43210
- sendAtMillis: now,
43211
- attempts: 0
44299
+ total: items.length
43212
44300
  };
43213
- await this.runtime.stateStore.putPending(session.userId, pending);
43214
- return await this.sendPending(session, pending);
43215
- }
43216
- async createTextForConversation(recordUid, textContent) {
43217
- try {
43218
- return {
43219
- ...await this.createText(recordUid, textContent),
43220
- localState: "synced"
43221
- };
43222
- } catch (error) {
43223
- const session = await this.runtime.requireSession();
43224
- const pending = (await this.runtime.stateStore.listPending(session.userId)).find((item) => item.recordUid === recordUid);
43225
- if (pending === void 0) throw error;
43226
- return {
43227
- recordUid,
43228
- status: 0,
43229
- localState: "failed",
43230
- error: pending.lastError ?? safeFailureMessage$2(error)
43231
- };
43232
- }
43233
44301
  }
43234
- async createDSHAgentInputText(recordUid, textContent, sendAtMillis) {
44302
+ async detail(sourceRef, relatedRef, signal) {
43235
44303
  const session = await this.runtime.requireSession();
43236
- const normalizedUid = recordUid.trim();
43237
- const normalizedText = textContent.trim();
43238
- const normalizedSendAt = Math.max(0, Math.trunc(sendAtMillis));
43239
- if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
43240
- if (normalizedText === "") throw new ArkmePluginError("record-text-empty", "请输入要发给自己的内容", false);
43241
- if (normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-text-too-long", `内容不能超过 ${this.runtime.config.maxTextLength} 个字符`, false);
43242
- const data = await this.runtime.authenticatedPost("/api/v1/records/dsh-agent-input/create", {
43243
- record_uid: normalizedUid,
43244
- template_kind: 1,
43245
- title: "",
43246
- text_content: normalizedText,
43247
- send_at: normalizedSendAt > 0 ? normalizedSendAt : Date.now()
43248
- }, session, void 0, {
43249
- scope: this.runtime.requestScope(session.userId),
43250
- key: `dsh-agent-input:${normalizedUid}`
44304
+ const reference = await this.openReference(relatedRef, session.userId, sourceRef.trim());
44305
+ const raw = await this.runtime.authenticatedPost("/api/v1/records/detail", { record_uid: reference.recordUid }, session, signal);
44306
+ if (!matchesRecordIdentity(raw, reference.recordUid, reference.recordOwnerUserId)) throw new ArkmePluginError("related-quick-note-detail-contract-invalid", "相关快记详情响应与所选快记不一致", true, 502);
44307
+ const lockedRecordUids = await this.privacy.lockedRecordUids(session, signal);
44308
+ if (arkmePrivacyLockedRecord(raw) || lockedRecordUids.has(reference.recordUid)) throw new ArkmePluginError("related-quick-note-private", "该相关快记不可查看", false, 403);
44309
+ const hydrated = await this.media.hydrateRecordMediaPage([raw], session, signal);
44310
+ const displayItems = hydrated.displayItemsByRecordUid.get(reference.recordUid);
44311
+ const mediaUnavailable = hydrated.unavailableRecordUids.has(reference.recordUid);
44312
+ const detail = this.record.recordTimelineItemFromRaw(raw, session.userId, {
44313
+ ...displayItems === void 0 ? {} : { displayItems },
44314
+ ...mediaUnavailable ? { mediaUnavailable: true } : {},
44315
+ isMe: reference.recordOwnerUserId === session.userId
43251
44316
  });
43252
- this.runtime.invalidateScope(this.runtime.requestScope(session.userId));
44317
+ if (detail.itemUid !== reference.recordUid) throw new ArkmePluginError("related-quick-note-detail-contract-invalid", "相关快记详情标识无效", true, 502);
43253
44318
  return {
43254
- recordUid: stringValue$1(data.record_uid) || normalizedUid,
43255
- status: numberValue$6(data.status)
43256
- };
43257
- }
43258
- async createFileAssetsForConversation(recordUid, textContent, assets) {
43259
- const session = await this.runtime.requireSession();
43260
- const normalizedUid = recordUid.trim();
43261
- const normalizedText = textContent.trim();
43262
- if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
43263
- if (assets.length === 0 || assets.length > 20 || normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-file-assets-invalid", "附件内容为空、过长或数量超限", false);
43264
- for (const asset of assets) if (!/^[A-Za-z0-9._:-]{8,256}$/.test(asset.fileAssetUid) || asset.fileName.trim() === "" || asset.fileName.length > 255 || !Number.isSafeInteger(asset.size) || asset.size <= 0 || ![
43265
- 1,
43266
- 2,
43267
- 3,
43268
- 4
43269
- ].includes(asset.fileKind)) throw new ArkmePluginError("record-file-asset-invalid", "附件资产参数无效", false);
43270
- const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
43271
- record_uid: normalizedUid,
43272
- template_kind: 2,
43273
- display_kind: 0,
43274
- title: "",
43275
- text_content: normalizedText,
43276
- content_payload: {
43277
- payload_kind: 2,
43278
- schema_version: 1,
43279
- text_state: normalizedText === "" ? 3 : 1,
43280
- media_refs: assets.map((asset, index) => ({
43281
- file_asset_uid: asset.fileAssetUid,
43282
- content_file_role: 1,
43283
- render_role: 1,
43284
- sort_order: index,
43285
- file_name: asset.fileName
43286
- }))
43287
- },
43288
- send_at: Date.now()
43289
- }, session);
44319
+ relatedRef,
44320
+ senderName: reference.senderName,
44321
+ ...reference.senderAvatarRef === void 0 ? {} : { avatarRef: reference.senderAvatarRef },
44322
+ isMe: reference.recordOwnerUserId === session.userId,
44323
+ sendAtMillis: detail.sendAtMillis,
44324
+ title: detail.title,
44325
+ textContent: detail.textContent,
44326
+ status: detail.status,
44327
+ ...detail.recordVersion === void 0 ? {} : { recordVersion: detail.recordVersion },
44328
+ ...detail.aiPolish === void 0 ? {} : { aiPolish: detail.aiPolish },
44329
+ ...detail.templateKind === void 0 ? {} : { templateKind: detail.templateKind },
44330
+ ...detail.displayKind === void 0 ? {} : { displayKind: detail.displayKind },
44331
+ ...detail.version === void 0 ? {} : { version: detail.version },
44332
+ ...detail.updateAtMillis === void 0 ? {} : { updateAtMillis: detail.updateAtMillis },
44333
+ ...detail.recordDurationMillis === void 0 ? {} : { recordDurationMillis: detail.recordDurationMillis },
44334
+ ...detail.editDurationMillis === void 0 ? {} : { editDurationMillis: detail.editDurationMillis },
44335
+ ...detail.contentBlocks === void 0 ? {} : { contentBlocks: detail.contentBlocks },
44336
+ ...detail.mediaUnavailable === void 0 ? {} : { mediaUnavailable: detail.mediaUnavailable },
44337
+ ...detail.forwardRecords === void 0 ? {} : { forwardRecords: detail.forwardRecords }
44338
+ };
44339
+ }
44340
+ validLocator(locator, expectedViewerUserId) {
44341
+ const sourceRef = locator.sourceRef.trim();
44342
+ const sourceOwnerRef = locator.sourceOwnerRef.trim();
44343
+ const recordUidValue = locator.recordUid.trim();
44344
+ const chatSessionUid = locator.chatSessionUid.trim();
44345
+ if (locator.viewerUserId !== expectedViewerUserId || sourceRef === "" || sourceOwnerRef === "" || recordUidValue === "" || !Number.isSafeInteger(locator.recordOwnerUserId) || locator.recordOwnerUserId <= 0 || locator.contextType !== "record" && locator.contextType !== "chat" || locator.contextType === "chat" && chatSessionUid === "") throw new ArkmePluginError("related-quick-note-source-invalid", "当前快记来源无效,请刷新后重试", false, 400);
43290
44346
  return {
43291
- recordUid: stringValue$1(data.record_uid).trim() || normalizedUid,
43292
- status: numberValue$6(data.status)
44347
+ ...locator,
44348
+ sourceRef,
44349
+ sourceOwnerRef,
44350
+ recordUid: recordUidValue,
44351
+ chatSessionUid: locator.contextType === "chat" ? chatSessionUid : ""
43293
44352
  };
43294
44353
  }
43295
- async pendingWrites() {
43296
- const session = await this.runtime.requireSession();
43297
- return await this.runtime.stateStore.listPending(session.userId);
43298
- }
43299
- async retryPending(recordUid) {
43300
- const session = await this.runtime.requireSession();
43301
- const pending = (await this.runtime.stateStore.listPending(session.userId)).find((item) => item.recordUid === recordUid);
43302
- if (pending === void 0) throw new ArkmePluginError("outbox-entry-not-found", "待重试内容不存在", false, 404);
43303
- return await this.sendPending(session, pending);
43304
- }
43305
- async sendPending(session, pending) {
43306
- try {
43307
- const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
43308
- record_uid: pending.recordUid,
43309
- template_kind: 1,
43310
- title: "",
43311
- text_content: pending.textContent,
43312
- send_at: pending.sendAtMillis
43313
- }, session);
43314
- const result = {
43315
- recordUid: stringValue$1(data.record_uid) || pending.recordUid,
43316
- status: numberValue$6(data.status)
43317
- };
43318
- await this.runtime.stateStore.markSynced(session.userId, pending.recordUid, result.status);
43319
- return result;
43320
- } catch (error) {
43321
- await this.runtime.stateStore.markAttempt(session.userId, pending.recordUid, safeFailureMessage$2(error));
43322
- throw error;
44354
+ descriptorsFromItems(rawItems, excluded, lockedRecordUids) {
44355
+ const seen = /* @__PURE__ */ new Set();
44356
+ const result = [];
44357
+ for (const raw of rawItems) {
44358
+ if (result.length >= RELATED_QUICK_NOTE_LIMIT || arkmePrivacyLockedRecord(raw)) continue;
44359
+ const descriptor = descriptorFromRaw(raw);
44360
+ if (descriptor === void 0 || excluded.has(descriptor.recordUid) || lockedRecordUids.has(descriptor.recordUid) || seen.has(descriptor.recordUid)) continue;
44361
+ seen.add(descriptor.recordUid);
44362
+ result.push(descriptor);
43323
44363
  }
44364
+ return result;
43324
44365
  }
43325
- recordUid(raw) {
43326
- const item = objectValue$1(raw);
43327
- return stringValue$1(item.record_uid ?? objectValue$1(item.record_core).record_uid).trim();
43328
- }
43329
- recordTimelineItem(item) {
43330
- return {
43331
- itemUid: item.recordUid,
43332
- senderName: "我",
43333
- isMe: true,
43334
- sendAtMillis: item.sendAtMillis,
43335
- title: item.title,
43336
- textContent: item.textContent,
43337
- status: item.status,
43338
- templateKind: item.templateKind,
43339
- version: item.version,
43340
- ...item.displayKind === void 0 ? {} : { displayKind: item.displayKind },
43341
- ...item.contentBlocks === void 0 ? {} : { contentBlocks: item.contentBlocks },
43342
- ...item.mediaUnavailable === true ? { mediaUnavailable: true } : {}
43343
- };
43344
- }
43345
- recordTimelineItemFromRaw(raw, userId, options = {}) {
43346
- const item = objectValue$1(raw);
43347
- const core = objectValue$1(item.record_core);
43348
- return {
43349
- itemUid: stringValue$1(item.record_uid ?? core.record_uid).trim(),
43350
- senderName: stringValue$1(item.nickname).trim() || "我",
43351
- isMe: options.isMe ?? numberValue$6(item.creator_user_id ?? item.owner_user_id ?? core.creator_user_id ?? core.owner_user_id) === userId,
43352
- sendAtMillis: numberValue$6(item.send_at ?? core.send_at),
43353
- title: stringValue$1(item.title ?? core.title),
43354
- textContent: stringValue$1(item.text_content ?? core.text_content),
43355
- status: numberValue$6(item.status ?? core.status),
43356
- templateKind: numberValue$6(item.template_kind ?? core.template_kind),
43357
- displayKind: numberValue$6(item.display_kind ?? core.display_kind),
43358
- version: numberValue$6(item.version ?? core.version),
43359
- updateAtMillis: numberValue$6(item.update_at ?? core.update_at),
43360
- recordDurationMillis: numberValue$6(item.record_duration_millis ?? core.record_duration_millis),
43361
- editDurationMillis: numberValue$6(item.edit_duration_millis ?? core.edit_duration_millis),
43362
- contentBlocks: this.media.richContentBlocks(raw, userId, options.displayItems),
43363
- ...options.selfTopic === void 0 ? {} : { selfTopic: options.selfTopic },
43364
- ...options.mediaUnavailable === true ? { mediaUnavailable: true } : {}
43365
- };
43366
- }
43367
- recordItem(raw, userId, options = {}) {
43368
- const item = objectValue$1(raw);
43369
- const core = objectValue$1(item.record_core);
43370
- const recordUid = stringValue$1(item.record_uid ?? core.record_uid).trim();
43371
- if (recordUid === "") return void 0;
43372
- return {
43373
- recordUid,
43374
- sendAtMillis: numberValue$6(item.send_at ?? core.send_at),
43375
- title: stringValue$1(core.title),
43376
- textContent: stringValue$1(core.text_content),
43377
- templateKind: numberValue$6(core.template_kind),
43378
- status: numberValue$6(core.status),
43379
- version: numberValue$6(core.version),
43380
- creationSource: recordCreationSource(raw),
43381
- displayKind: numberValue$6(item.display_kind ?? core.display_kind),
43382
- ...userId === void 0 ? {} : { contentBlocks: this.media.richContentBlocks(raw, userId, options.displayItems) },
43383
- ...options.mediaUnavailable === true ? { mediaUnavailable: true } : {}
43384
- };
43385
- }
43386
- isDSHAgentInput(raw) {
43387
- return isDSHAgentInputRecord(raw);
44366
+ relatedCandidates(response, directItems, excluded, lockedRecordUids) {
44367
+ const rawItems = [...directItems, ...listValue$7(response.similarLs ?? response.similar_ls ?? response.similarRecords ?? response.similar_records)];
44368
+ const blocked = new Set(lockedRecordUids);
44369
+ for (const raw of rawItems) {
44370
+ if (!arkmePrivacyLockedRecord(raw)) continue;
44371
+ const uid = typeof raw === "string" ? raw.trim() : recordUid(raw);
44372
+ if (uid !== "") blocked.add(uid);
44373
+ }
44374
+ const result = [];
44375
+ const seen = /* @__PURE__ */ new Set();
44376
+ for (const raw of rawItems) {
44377
+ const uid = typeof raw === "string" ? raw.trim() : recordUid(raw);
44378
+ if (uid === "" || excluded.has(uid) || blocked.has(uid) || seen.has(uid) || arkmePrivacyLockedRecord(raw)) continue;
44379
+ seen.add(uid);
44380
+ result.push({
44381
+ uid,
44382
+ raw
44383
+ });
44384
+ if (result.length >= RELATED_QUICK_NOTE_LIMIT) break;
44385
+ }
44386
+ return result;
43388
44387
  }
43389
- isPrivacyLocked(raw) {
43390
- return arkmePrivacyLockedRecord(raw);
44388
+ async sealReference(reference) {
44389
+ const now = Date.now();
44390
+ for (const [key, value] of this.references) if (value.expiresAtMillis <= now) this.references.delete(key);
44391
+ while (this.references.size >= RELATED_QUICK_NOTE_REF_CAP) {
44392
+ const oldest = this.references.keys().next().value;
44393
+ if (oldest === void 0) break;
44394
+ this.references.delete(oldest);
44395
+ }
44396
+ const nonce = randomUUID();
44397
+ const signature = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(nonce).digest("base64url");
44398
+ this.references.set(nonce, {
44399
+ ...reference,
44400
+ expiresAtMillis: now + RELATED_QUICK_NOTE_REF_TTL_MS
44401
+ });
44402
+ return `arkme-related-quick-note-v1.${nonce}.${signature}`;
43391
44403
  }
43392
- async syncHistory(maxPages = 20, signal) {
43393
- const pageCap = Math.min(20, Math.max(1, Math.trunc(maxPages)));
43394
- await this.refreshLatest();
43395
- let snapshot = await this.cachedSnapshot();
43396
- let pages = 0;
43397
- while (snapshot.hasMore && snapshot.nextCursor !== void 0 && pages < pageCap) {
43398
- if (signal?.aborted === true) throw new Error("Arkme历史同步已取消");
43399
- await this.list(50, snapshot.nextCursor);
43400
- pages += 1;
43401
- snapshot = await this.cachedSnapshot();
44404
+ async openReference(relatedRef, expectedViewerUserId, expectedSourceRef) {
44405
+ const parts = relatedRef.trim().split(".");
44406
+ if (parts.length !== 3 || parts[0] !== "arkme-related-quick-note-v1") throw this.invalidReference();
44407
+ const nonce = parts[1] ?? "";
44408
+ const supplied = Buffer.from(parts[2] ?? "", "base64url");
44409
+ const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(nonce).digest();
44410
+ if (nonce === "" || supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw this.invalidReference();
44411
+ const reference = this.references.get(nonce);
44412
+ if (reference === void 0 || reference.expiresAtMillis <= Date.now()) {
44413
+ this.references.delete(nonce);
44414
+ throw new ArkmePluginError("related-quick-note-ref-expired", "相关快记引用已过期,请刷新后重试", true, 410);
43402
44415
  }
43403
- return {
43404
- pages,
43405
- complete: !snapshot.hasMore
43406
- };
44416
+ if (reference.viewerUserId !== expectedViewerUserId || reference.sourceRef !== expectedSourceRef) throw this.invalidReference();
44417
+ return reference;
44418
+ }
44419
+ invalidReference() {
44420
+ return new ArkmePluginError("related-quick-note-ref-invalid", "相关快记引用无效,请刷新后重试", false, 403);
43407
44421
  }
43408
44422
  };
43409
44423
  //#endregion
@@ -46707,6 +47721,31 @@ function integerValue(value) {
46707
47721
  function listValue(value) {
46708
47722
  return Array.isArray(value) ? value : [];
46709
47723
  }
47724
+ function extensionShareRefFromUrl(rawUrl) {
47725
+ try {
47726
+ return new URL(rawUrl).pathname.split("/").at(-1) ?? "";
47727
+ } catch {
47728
+ return "";
47729
+ }
47730
+ }
47731
+ function worldExtensionPublicationShare(rawPublication, rawItem) {
47732
+ const share = objectValue$1(rawPublication.share ?? rawItem.share);
47733
+ const url = stringValue$1(share.url ?? rawPublication.share_url ?? rawItem.share_url).trim();
47734
+ if (url === "" || url.length > 4096) return void 0;
47735
+ const ref = stringValue$1(share.ref ?? rawPublication.share_ref ?? rawItem.share_ref).trim() || extensionShareRefFromUrl(url);
47736
+ if (!/^extshare_[0-9a-f]{32}$/.test(ref)) return void 0;
47737
+ let parsed;
47738
+ try {
47739
+ parsed = new URL(url);
47740
+ } catch {
47741
+ return;
47742
+ }
47743
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || !parsed.pathname.endsWith(`/${ref}`)) return;
47744
+ return {
47745
+ ref,
47746
+ url: parsed.href
47747
+ };
47748
+ }
46710
47749
  function isTopLevelWorldRecord(raw) {
46711
47750
  return stringValue$1(objectValue$1(raw).parent_record_uid).trim() === "";
46712
47751
  }
@@ -47553,6 +48592,10 @@ var WorldService = class {
47553
48592
  version: stringValue$1(rawExtensionPublication.version).trim(),
47554
48593
  name: stringValue$1(rawExtensionPublication.name).trim() || "未命名插件",
47555
48594
  description: stringValue$1(rawExtensionPublication.description).trim(),
48595
+ ...(() => {
48596
+ const share = worldExtensionPublicationShare(rawExtensionPublication, item);
48597
+ return share === void 0 ? {} : { share };
48598
+ })(),
47556
48599
  ...(() => {
47557
48600
  const iconRef = stringValue$1(rawExtensionPublication.icon_ref).trim();
47558
48601
  return iconRef === "" ? {} : { iconRef };
@@ -47834,6 +48877,7 @@ var ArkmeService = class {
47834
48877
  linkMetadata;
47835
48878
  aiPolish;
47836
48879
  chat;
48880
+ relatedQuickNote;
47837
48881
  contact;
47838
48882
  contactDirectory;
47839
48883
  unmarkedSpeaker;
@@ -47886,6 +48930,7 @@ var ArkmeService = class {
47886
48930
  this.botConversation = new BotConversationService(this.runtime, this.bot, this.chat, async () => {
47887
48931
  await this.realtime.invalidateRecordProjection();
47888
48932
  });
48933
+ this.relatedQuickNote = new RelatedQuickNoteService(this.runtime, this.record, this.media, this.profile, this.privacy);
47889
48934
  this.contactDirectory = new ContactDirectoryService(this.runtime, this.source, this.bot, this.profile, this.world, this.chat);
47890
48935
  this.unmarkedSpeaker = new UnmarkedSpeakerService(this.runtime, this.media);
47891
48936
  this.contact = new ContactService(this.runtime, this.source, this.profile, this.realtime);
@@ -47971,6 +49016,7 @@ var ArkmeService = class {
47971
49016
  this.media.dispose();
47972
49017
  this.aiPolish.dispose();
47973
49018
  this.interwoven.dispose();
49019
+ this.relatedQuickNote.dispose();
47974
49020
  this.world.dispose();
47975
49021
  this.arrangement.dispose();
47976
49022
  this.contact.dispose();
@@ -48132,6 +49178,7 @@ var ArkmeService = class {
48132
49178
  forwardContent: true,
48133
49179
  sourceTextSend: true,
48134
49180
  messageReadReceipts: true,
49181
+ messageReport: true,
48135
49182
  richContentRead: this.config.richMediaRenderEnabled !== false,
48136
49183
  richContentSend: this.config.richMediaSendEnabled !== false,
48137
49184
  fileUpload: this.config.richMediaSendEnabled !== false,
@@ -48230,6 +49277,7 @@ var ArkmeService = class {
48230
49277
  this.runtime.dispose();
48231
49278
  this.outgoingCall.dispose();
48232
49279
  this.interwoven.dispose();
49280
+ this.relatedQuickNote.dispose();
48233
49281
  this.world.dispose();
48234
49282
  this.linkMetadata.dispose();
48235
49283
  }
@@ -48398,6 +49446,15 @@ var ArkmeService = class {
48398
49446
  async interwovenMomentDetail(sourceRef, momentRef, signal) {
48399
49447
  return await this.interwoven.interwovenMomentDetail(sourceRef, momentRef, signal);
48400
49448
  }
49449
+ async relatedQuickNotesFromMessage(sourceRef, messageActionRef, signal) {
49450
+ return await this.relatedQuickNote.list(await this.chat.relatedQuickNoteLocator(sourceRef, messageActionRef), signal);
49451
+ }
49452
+ async relatedQuickNotesFromMoment(sourceRef, momentRef, signal) {
49453
+ return await this.relatedQuickNote.list(await this.interwoven.relatedQuickNoteLocator(sourceRef, momentRef, signal), signal);
49454
+ }
49455
+ async relatedQuickNoteDetail(sourceRef, relatedRef, signal) {
49456
+ return await this.relatedQuickNote.detail(sourceRef, relatedRef, signal);
49457
+ }
48401
49458
  async joinDSHBetaCommunity(signal) {
48402
49459
  return await this.community.joinDSHBetaCommunity(signal);
48403
49460
  }
@@ -48519,6 +49576,12 @@ var ArkmeService = class {
48519
49576
  async sharedRecordingDetail(detailRef, options = {}) {
48520
49577
  return await this.chat.sharedRecordingDetail(detailRef, options);
48521
49578
  }
49579
+ async messageSnapshotDetail(sourceRef, actionRef, options = {}) {
49580
+ return await this.chat.messageSnapshotDetail(sourceRef, actionRef, options);
49581
+ }
49582
+ async saveMessageLocation(sourceRef, itemUid, location, recordVersion, options = {}) {
49583
+ await this.chat.saveMessageLocation(sourceRef, itemUid, location, recordVersion, options);
49584
+ }
48522
49585
  async messageReadReceiptSummaries(sourceRef, items, options = {}) {
48523
49586
  return await this.chat.messageReadReceiptSummaries(sourceRef, items, options);
48524
49587
  }
@@ -48841,25 +49904,27 @@ var ArkmeService = class {
48841
49904
  return await this.world.publishWorldFileAssets(input);
48842
49905
  }
48843
49906
  async createText(recordUid, textContent) {
48844
- return await this.record.createText(recordUid, textContent);
49907
+ const result = await this.record.createText(recordUid, textContent);
49908
+ await this.realtime.invalidateRecordProjection();
49909
+ return result;
48845
49910
  }
48846
49911
  async createTextForConversation(recordUid, textContent) {
48847
- return await this.record.createTextForConversation(recordUid, textContent);
49912
+ const result = await this.record.createTextForConversation(recordUid, textContent);
49913
+ if (result.localState !== "failed") await this.realtime.invalidateRecordProjection();
49914
+ return result;
48848
49915
  }
48849
49916
  async createDSHAgentInputText(recordUid, textContent, sendAtMillis) {
48850
49917
  const result = await this.record.createDSHAgentInputText(recordUid, textContent, sendAtMillis);
48851
- this.realtime.emitChatClientEvent({
48852
- type: "projection-invalidated",
48853
- revision: this.realtime.nextChatClientRevision(),
48854
- projection: "record"
48855
- });
49918
+ await this.realtime.invalidateRecordProjection();
48856
49919
  return result;
48857
49920
  }
48858
49921
  async pendingWrites() {
48859
49922
  return await this.record.pendingWrites();
48860
49923
  }
48861
49924
  async retryPending(recordUid) {
48862
- return await this.record.retryPending(recordUid);
49925
+ const result = await this.record.retryPending(recordUid);
49926
+ await this.realtime.invalidateRecordProjection();
49927
+ return result;
48863
49928
  }
48864
49929
  recordUid(raw) {
48865
49930
  return this.record.recordUid(raw);