@senguoyun/dsh-arkme 0.1.41 → 0.1.42

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 (133) hide show
  1. package/lib/client.js +23797 -21177
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +2172 -1053
  4. package/lib/persistent-extension.js +1 -1
  5. package/lib/{persistent-runtime-B1gJH2Nx.js → persistent-runtime-LJ4nj7os.js} +1956 -273
  6. package/lib/sdk.js +40 -0
  7. package/lib/types/arkme-service.d.ts +38 -8
  8. package/lib/types/arkme-service.d.ts.map +1 -1
  9. package/lib/types/chat-realtime.d.ts +31 -1
  10. package/lib/types/chat-realtime.d.ts.map +1 -1
  11. package/lib/types/client/ArkmeChatMemberActions.d.ts +10 -2
  12. package/lib/types/client/ArkmeChatMemberActions.d.ts.map +1 -1
  13. package/lib/types/client/ArkmeConfirmDialog.d.ts +19 -0
  14. package/lib/types/client/ArkmeConfirmDialog.d.ts.map +1 -0
  15. package/lib/types/client/ArkmeGroupChatControls.d.ts +1 -0
  16. package/lib/types/client/ArkmeGroupChatControls.d.ts.map +1 -1
  17. package/lib/types/client/ArkmeLinkText.d.ts +12 -1
  18. package/lib/types/client/ArkmeLinkText.d.ts.map +1 -1
  19. package/lib/types/client/ArkmeMentionTextarea.d.ts +2 -2
  20. package/lib/types/client/ArkmeMentionTextarea.d.ts.map +1 -1
  21. package/lib/types/client/ArkmeMessageWithdrawalDialog.d.ts +8 -0
  22. package/lib/types/client/ArkmeMessageWithdrawalDialog.d.ts.map +1 -0
  23. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  24. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeRecordingSurface.d.ts +5 -1
  26. package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
  27. package/lib/types/client/ArkmeRelatedQuickNotes.d.ts.map +1 -1
  28. package/lib/types/client/ArkmeRichComposerInput.d.ts.map +1 -1
  29. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  30. package/lib/types/client/ArkmeRichText.d.ts +9 -3
  31. package/lib/types/client/ArkmeRichText.d.ts.map +1 -1
  32. package/lib/types/client/ArkmeSearchSurface.d.ts +6 -3
  33. package/lib/types/client/ArkmeSearchSurface.d.ts.map +1 -1
  34. package/lib/types/client/ArkmeSidebar.d.ts +5 -2
  35. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  36. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +1 -1
  37. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  38. package/lib/types/client/ArkmeWorldSurface.d.ts +7 -4
  39. package/lib/types/client/ArkmeWorldSurface.d.ts.map +1 -1
  40. package/lib/types/client/api.d.ts +4 -4
  41. package/lib/types/client/api.d.ts.map +1 -1
  42. package/lib/types/client/bot-chat-directory-projection.d.ts +2 -0
  43. package/lib/types/client/bot-chat-directory-projection.d.ts.map +1 -1
  44. package/lib/types/client/bot-directory-preferences.d.ts +0 -3
  45. package/lib/types/client/bot-directory-preferences.d.ts.map +1 -1
  46. package/lib/types/client/chat-directory-store.d.ts +15 -0
  47. package/lib/types/client/chat-directory-store.d.ts.map +1 -1
  48. package/lib/types/client/composer-draft-store.d.ts +1 -0
  49. package/lib/types/client/composer-draft-store.d.ts.map +1 -1
  50. package/lib/types/client/conversation-directory-visibility-overlay.d.ts +27 -0
  51. package/lib/types/client/conversation-directory-visibility-overlay.d.ts.map +1 -0
  52. package/lib/types/client/realtime-client-events.d.ts.map +1 -1
  53. package/lib/types/client/recordings/ArkmeRecordingImportDialog.d.ts +6 -1
  54. package/lib/types/client/recordings/ArkmeRecordingImportDialog.d.ts.map +1 -1
  55. package/lib/types/client/recordings/recording-import-selection.d.ts.map +1 -1
  56. package/lib/types/client/ui-controller.d.ts +7 -0
  57. package/lib/types/client/ui-controller.d.ts.map +1 -1
  58. package/lib/types/dsh-remote/api-proxy-adapter.d.ts.map +1 -1
  59. package/lib/types/dsh-remote/channel-manager.d.ts +1 -0
  60. package/lib/types/dsh-remote/channel-manager.d.ts.map +1 -1
  61. package/lib/types/dsh-remote/control-plane.d.ts +5 -0
  62. package/lib/types/dsh-remote/control-plane.d.ts.map +1 -1
  63. package/lib/types/dsh-remote/host.d.ts +49 -13
  64. package/lib/types/dsh-remote/host.d.ts.map +1 -1
  65. package/lib/types/dsh-remote/realtime-transport.d.ts.map +1 -1
  66. package/lib/types/dsh-remote/turn-projector.d.ts.map +1 -1
  67. package/lib/types/dsh-remote/turn-upload-outbox.d.ts +86 -0
  68. package/lib/types/dsh-remote/turn-upload-outbox.d.ts.map +1 -0
  69. package/lib/types/dsh-remote/types.d.ts +18 -2
  70. package/lib/types/dsh-remote/types.d.ts.map +1 -1
  71. package/lib/types/hashtag.d.ts +47 -0
  72. package/lib/types/hashtag.d.ts.map +1 -0
  73. package/lib/types/host-api.d.ts.map +1 -1
  74. package/lib/types/index.d.ts +1 -1
  75. package/lib/types/index.d.ts.map +1 -1
  76. package/lib/types/link-metadata.d.ts +2 -0
  77. package/lib/types/link-metadata.d.ts.map +1 -1
  78. package/lib/types/local-database.d.ts +1 -0
  79. package/lib/types/local-database.d.ts.map +1 -1
  80. package/lib/types/recording-import-contract.d.ts +66 -2
  81. package/lib/types/recording-import-contract.d.ts.map +1 -1
  82. package/lib/types/recording-import-coordinator.d.ts.map +1 -1
  83. package/lib/types/recording-import-probe.d.ts.map +1 -1
  84. package/lib/types/recording-import-shared.d.ts +78 -0
  85. package/lib/types/recording-import-shared.d.ts.map +1 -1
  86. package/lib/types/recording-wave-probe.d.ts +9 -0
  87. package/lib/types/recording-wave-probe.d.ts.map +1 -0
  88. package/lib/types/sdk/index.d.ts +23 -2
  89. package/lib/types/sdk/index.d.ts.map +1 -1
  90. package/lib/types/services/bot-service.d.ts +3 -0
  91. package/lib/types/services/bot-service.d.ts.map +1 -1
  92. package/lib/types/services/chat-realtime-service.d.ts +4 -1
  93. package/lib/types/services/chat-realtime-service.d.ts.map +1 -1
  94. package/lib/types/services/chat-service.d.ts +22 -2
  95. package/lib/types/services/chat-service.d.ts.map +1 -1
  96. package/lib/types/services/conversation-directory-visibility-service.d.ts +34 -0
  97. package/lib/types/services/conversation-directory-visibility-service.d.ts.map +1 -0
  98. package/lib/types/services/conversation-list-preference-service.d.ts +56 -0
  99. package/lib/types/services/conversation-list-preference-service.d.ts.map +1 -0
  100. package/lib/types/services/group-ai-polish-service.d.ts +3 -0
  101. package/lib/types/services/group-ai-polish-service.d.ts.map +1 -1
  102. package/lib/types/services/link-metadata-service.d.ts +5 -2
  103. package/lib/types/services/link-metadata-service.d.ts.map +1 -1
  104. package/lib/types/services/record-service.d.ts +8 -1
  105. package/lib/types/services/record-service.d.ts.map +1 -1
  106. package/lib/types/services/recording-import-gateway.d.ts +45 -2
  107. package/lib/types/services/recording-import-gateway.d.ts.map +1 -1
  108. package/lib/types/services/recording-service.d.ts +18 -2
  109. package/lib/types/services/recording-service.d.ts.map +1 -1
  110. package/lib/types/services/search-service.d.ts +8 -0
  111. package/lib/types/services/search-service.d.ts.map +1 -1
  112. package/lib/types/services/service.d.ts +1 -0
  113. package/lib/types/services/service.d.ts.map +1 -1
  114. package/lib/types/services/source-service.d.ts +25 -12
  115. package/lib/types/services/source-service.d.ts.map +1 -1
  116. package/lib/types/services/world-service.d.ts +4 -0
  117. package/lib/types/services/world-service.d.ts.map +1 -1
  118. package/lib/types/state-store.d.ts +1 -0
  119. package/lib/types/state-store.d.ts.map +1 -1
  120. package/lib/types/tools/business/conversation/index.d.ts.map +1 -1
  121. package/lib/types/tools/business/conversation/withdraw-message.d.ts +2 -0
  122. package/lib/types/tools/business/conversation/withdraw-message.d.ts.map +1 -0
  123. package/lib/types/tools/business/groups/index.d.ts.map +1 -1
  124. package/lib/types/tools/business/index.d.ts.map +1 -1
  125. package/lib/types/tools/ports/conversations.d.ts +5 -1
  126. package/lib/types/tools/ports/conversations.d.ts.map +1 -1
  127. package/lib/types/tools/ports/groups.d.ts +13 -1
  128. package/lib/types/tools/ports/groups.d.ts.map +1 -1
  129. package/lib/types/tools/prompts/business.d.ts.map +1 -1
  130. package/lib/types/tools/registry/registrar.d.ts.map +1 -1
  131. package/lib/types/types.d.ts +75 -4
  132. package/lib/types/types.d.ts.map +1 -1
  133. package/package.json +5 -5
@@ -4,7 +4,7 @@ import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } fr
4
4
  import { fileURLToPath } from "node:url";
5
5
  import * as fs from "node:fs/promises";
6
6
  import { chmod as chmod$1, copyFile, link, mkdir, open, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
7
- import { Resolver } from "node:dns/promises";
7
+ import { Resolver, lookup } from "node:dns/promises";
8
8
  import { request } from "node:http";
9
9
  import { request as request$1 } from "node:https";
10
10
  import { BlockList, isIP } from "node:net";
@@ -11324,6 +11324,9 @@ var Id3V2Reader = class {
11324
11324
  //#endregion
11325
11325
  //#region src/recording-import-shared.ts
11326
11326
  const MAX_RECORDING_IMPORT_BYTES = 1024 * 1024 * 1024;
11327
+ function recordingImportFileNameKey(fileName) {
11328
+ return fileName.trim().toLowerCase();
11329
+ }
11327
11330
  //#endregion
11328
11331
  //#region src/recording-import-contract.ts
11329
11332
  function sameRecordingImportIdentity(left, right) {
@@ -11405,7 +11408,10 @@ function advanceRecordingImportJob(current, input) {
11405
11408
  }
11406
11409
  function toPublicRecordingImportJob(job, importRef) {
11407
11410
  const progress = job.fileSize <= 0 ? 0 : Math.min(1, Math.max(0, job.uploadedBytes / job.fileSize));
11411
+ const status = job.phase === "prepared" ? "preparing" : job.phase === "uploading" ? "uploading" : job.phase === "finalizing" ? "processing" : job.phase === "accepted" ? "accepted" : job.phase === "failed" ? "failed" : "cancelled";
11412
+ const statusDetail = job.phase === "prepared" ? "准备中" : job.phase === "uploading" ? "上传中" : job.phase === "finalizing" ? "处理中" : job.phase === "accepted" ? "Audio 已接收" : job.phase === "failed" ? job.errorMessage?.trim() || "导入失败" : "已取消";
11408
11413
  return {
11414
+ kind: "local",
11409
11415
  importRef,
11410
11416
  revision: job.revision,
11411
11417
  phase: job.phase,
@@ -11413,7 +11419,11 @@ function toPublicRecordingImportJob(job, importRef) {
11413
11419
  fileName: job.fileName,
11414
11420
  fileSize: job.fileSize,
11415
11421
  durationMillis: job.durationMillis,
11422
+ startAtMillis: job.startAtMillis,
11423
+ endAtMillis: job.startAtMillis + job.durationMillis,
11416
11424
  progress,
11425
+ status,
11426
+ statusDetail,
11417
11427
  createdAtMillis: job.createdAtMillis,
11418
11428
  updatedAtMillis: job.updatedAtMillis,
11419
11429
  ...job.errorCode === void 0 ? {} : { errorCode: job.errorCode },
@@ -11422,18 +11432,115 @@ function toPublicRecordingImportJob(job, importRef) {
11422
11432
  };
11423
11433
  }
11424
11434
  //#endregion
11435
+ //#region src/recording-wave-probe.ts
11436
+ const MAX_WAVE_CHUNK_COUNT = 1024;
11437
+ function ascii$1(bytes, offset, length) {
11438
+ return String.fromCharCode(...bytes.subarray(offset, offset + length));
11439
+ }
11440
+ function uint16(bytes, offset) {
11441
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint16(offset, true);
11442
+ }
11443
+ function uint32(bytes, offset) {
11444
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(offset, true);
11445
+ }
11446
+ async function readExact(source, offset, length) {
11447
+ const bytes = await source.read(offset, length);
11448
+ if (bytes.byteLength !== length) throw new TypeError("WAV 文件内容不完整");
11449
+ return bytes;
11450
+ }
11451
+ async function probeImaAdpcmWave(source) {
11452
+ if (!Number.isSafeInteger(source.size) || source.size < 12) return void 0;
11453
+ const header = await readExact(source, 0, 12);
11454
+ if (ascii$1(header, 0, 4) !== "RIFF" || ascii$1(header, 8, 4) !== "WAVE") return void 0;
11455
+ const riffEnd = uint32(header, 4) + 8;
11456
+ if (riffEnd < 12 || riffEnd > source.size) throw new TypeError("WAV RIFF 长度无效");
11457
+ let offset = 12;
11458
+ let format;
11459
+ let dataSize;
11460
+ let chunkCount = 0;
11461
+ while (offset + 8 <= riffEnd) {
11462
+ chunkCount += 1;
11463
+ if (chunkCount > MAX_WAVE_CHUNK_COUNT) throw new TypeError("WAV chunk 数量过多");
11464
+ const chunkHeader = await readExact(source, offset, 8);
11465
+ const chunkId = ascii$1(chunkHeader, 0, 4);
11466
+ const chunkSize = uint32(chunkHeader, 4);
11467
+ const chunkOffset = offset + 8;
11468
+ const nextOffset = chunkOffset + chunkSize + chunkSize % 2;
11469
+ if (!Number.isSafeInteger(nextOffset) || nextOffset > riffEnd) throw new TypeError("WAV chunk 长度无效");
11470
+ if (chunkId === "fmt ") {
11471
+ if (chunkSize < 2) throw new TypeError("WAV fmt chunk 无效");
11472
+ const bytes = await readExact(source, chunkOffset, Math.min(chunkSize, 20));
11473
+ if (uint16(bytes, 0) !== 17) return void 0;
11474
+ if (bytes.byteLength < 20 || uint16(bytes, 16) < 2) throw new TypeError("IMA ADPCM fmt chunk 无效");
11475
+ format = {
11476
+ channels: uint16(bytes, 2),
11477
+ sampleRate: uint32(bytes, 4),
11478
+ blockAlign: uint16(bytes, 12),
11479
+ bitsPerSample: uint16(bytes, 14),
11480
+ samplesPerBlock: uint16(bytes, 18)
11481
+ };
11482
+ } else if (chunkId === "data") dataSize = chunkSize;
11483
+ if (format !== void 0 && dataSize !== void 0) break;
11484
+ offset = nextOffset;
11485
+ }
11486
+ if (format === void 0) throw new TypeError("IMA ADPCM WAV 缺少 fmt chunk");
11487
+ if (dataSize === void 0 || dataSize <= 0) throw new TypeError("IMA ADPCM WAV 缺少音频数据");
11488
+ const { channels, sampleRate, blockAlign, bitsPerSample, samplesPerBlock } = format;
11489
+ if (channels <= 0 || sampleRate <= 0 || bitsPerSample !== 4) throw new TypeError("IMA ADPCM WAV 编码参数无效");
11490
+ const blockHeaderSize = channels * 4;
11491
+ const encodedGroupSize = channels * 4;
11492
+ const blockPayloadSize = blockAlign - blockHeaderSize;
11493
+ if (blockPayloadSize < 0 || blockPayloadSize % encodedGroupSize !== 0) throw new TypeError("IMA ADPCM WAV 编码参数无效");
11494
+ const expectedSamplesPerBlock = 1 + blockPayloadSize / encodedGroupSize * 8;
11495
+ if (samplesPerBlock <= 0 || samplesPerBlock !== expectedSamplesPerBlock) throw new TypeError("IMA ADPCM WAV 编码参数无效");
11496
+ const completeBlocks = Math.floor(dataSize / blockAlign);
11497
+ const remainder = dataSize % blockAlign;
11498
+ if (remainder !== 0 && (remainder < blockHeaderSize || (remainder - blockHeaderSize) % encodedGroupSize !== 0)) throw new TypeError("IMA ADPCM WAV 音频数据无效");
11499
+ const partialBlockSamples = remainder === 0 ? 0 : 1 + (remainder - blockHeaderSize) / encodedGroupSize * 8;
11500
+ const totalSamples = completeBlocks * samplesPerBlock + partialBlockSamples;
11501
+ const durationMillis = Math.round(totalSamples * 1e3 / sampleRate);
11502
+ if (!Number.isSafeInteger(durationMillis) || durationMillis <= 0) throw new TypeError("IMA ADPCM WAV 时长无效");
11503
+ return { durationMillis };
11504
+ }
11505
+ //#endregion
11425
11506
  //#region src/recording-import-probe.ts
11426
11507
  async function probeRecordingImportFile(filePath, metadata) {
11427
- if ((await stat(filePath)).size !== metadata.fileSize) throw new RecordingImportContractError("recording-import-size-mismatch", "录音文件大小与声明不一致");
11428
- const input = new Input({
11429
- source: new FilePathSource(filePath),
11430
- formats: [
11431
- WAVE,
11432
- MP3,
11433
- MP4
11434
- ]
11435
- });
11508
+ const actualSize = (await stat(filePath)).size;
11509
+ if (actualSize !== metadata.fileSize) throw new RecordingImportContractError("recording-import-size-mismatch", "录音文件大小与声明不一致");
11510
+ let input;
11436
11511
  try {
11512
+ const handle = await open(filePath, "r");
11513
+ let imaAdpcm;
11514
+ try {
11515
+ imaAdpcm = await probeImaAdpcmWave({
11516
+ size: actualSize,
11517
+ read: async (offset, length) => {
11518
+ const bytes = new Uint8Array(length);
11519
+ const { bytesRead } = await handle.read(bytes, 0, length, offset);
11520
+ return bytes.subarray(0, bytesRead);
11521
+ }
11522
+ });
11523
+ } finally {
11524
+ await handle.close();
11525
+ }
11526
+ if (imaAdpcm !== void 0) {
11527
+ if (recordingImportFileKind({
11528
+ ...metadata,
11529
+ durationMillis: imaAdpcm.durationMillis
11530
+ }) !== "wav") throw new RecordingImportContractError("recording-import-format-mismatch", "录音格式与文件内容不一致");
11531
+ return {
11532
+ kind: "wav",
11533
+ durationMillis: imaAdpcm.durationMillis
11534
+ };
11535
+ }
11536
+ input = new Input({
11537
+ source: new FilePathSource(filePath),
11538
+ formats: [
11539
+ WAVE,
11540
+ MP3,
11541
+ MP4
11542
+ ]
11543
+ });
11437
11544
  if (!await input.canRead()) throw new RecordingImportContractError("recording-import-format-invalid", "无法识别录音文件内容");
11438
11545
  const actualFormat = await input.getFormat();
11439
11546
  const actualKind = actualFormat === WAVE ? "wav" : actualFormat === MP3 ? "mp3" : actualFormat === MP4 ? "m4a" : void 0;
@@ -11455,7 +11562,7 @@ async function probeRecordingImportFile(filePath, metadata) {
11455
11562
  if (error instanceof RecordingImportContractError) throw error;
11456
11563
  throw new RecordingImportContractError("recording-import-probe-failed", "录音文件校验失败");
11457
11564
  } finally {
11458
- input.dispose();
11565
+ input?.dispose();
11459
11566
  }
11460
11567
  }
11461
11568
  var LocalRecordingImportSource = class {
@@ -11808,7 +11915,7 @@ function bestMatchingCategory(text) {
11808
11915
  }
11809
11916
  //#endregion
11810
11917
  //#region src/services/ai-video-service.ts
11811
- function numberValue$29(value) {
11918
+ function numberValue$30(value) {
11812
11919
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
11813
11920
  }
11814
11921
  function booleanValue$15(value) {
@@ -11828,9 +11935,9 @@ var AiVideoService = class {
11828
11935
  return {
11829
11936
  allowed: booleanValue$15(data.allowed),
11830
11937
  message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
11831
- selectedDurationMillis: Math.max(0, numberValue$29(data.selected_duration_millis)),
11832
- minimumDurationMillis: Math.max(0, numberValue$29(data.minimum_duration_millis)),
11833
- selectedSegmentCount: Math.max(0, numberValue$29(data.selected_segment_count)),
11938
+ selectedDurationMillis: Math.max(0, numberValue$30(data.selected_duration_millis)),
11939
+ minimumDurationMillis: Math.max(0, numberValue$30(data.minimum_duration_millis)),
11940
+ selectedSegmentCount: Math.max(0, numberValue$30(data.selected_segment_count)),
11834
11941
  retryable: booleanValue$15(data.retryable),
11835
11942
  ...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
11836
11943
  ...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
@@ -11880,10 +11987,10 @@ var AiVideoService = class {
11880
11987
  return {
11881
11988
  allowed: booleanValue$15(data.allowed),
11882
11989
  message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
11883
- selectedDurationMillis: Math.max(0, numberValue$29(data.selected_duration_millis)),
11884
- minimumDurationMillis: Math.max(0, numberValue$29(data.minimum_duration_millis)),
11885
- selectedSegmentCount: Math.max(0, numberValue$29(data.selected_segment_count)),
11886
- selectedTextCount: Math.max(0, numberValue$29(data.selected_text_count)),
11990
+ selectedDurationMillis: Math.max(0, numberValue$30(data.selected_duration_millis)),
11991
+ minimumDurationMillis: Math.max(0, numberValue$30(data.minimum_duration_millis)),
11992
+ selectedSegmentCount: Math.max(0, numberValue$30(data.selected_segment_count)),
11993
+ selectedTextCount: Math.max(0, numberValue$30(data.selected_text_count)),
11887
11994
  retryable: booleanValue$15(data.retryable),
11888
11995
  ...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
11889
11996
  ...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
@@ -11929,13 +12036,13 @@ var AiVideoService = class {
11929
12036
  jobId,
11930
12037
  status,
11931
12038
  stage: stringValue$1(data.stage).trim() || status,
11932
- progress: Math.min(100, Math.max(0, Math.trunc(numberValue$29(data.progress)))),
12039
+ progress: Math.min(100, Math.max(0, Math.trunc(numberValue$30(data.progress)))),
11933
12040
  selectedSegmentCount: segmentCount,
11934
12041
  ...textCount === 0 ? {} : { selectedTextCount: textCount },
11935
12042
  retryable: booleanValue$15(data.retryable),
11936
12043
  ...stringValue$1(data.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(data.video_asset_uid).trim() },
11937
12044
  ...stringValue$1(data.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(data.cover_asset_uid).trim() },
11938
- ...numberValue$29(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$29(data.video_duration_millis) },
12045
+ ...numberValue$30(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$30(data.video_duration_millis) },
11939
12046
  ...stringValue$1(data.error_code).trim() === "" ? {} : { errorCode: stringValue$1(data.error_code).trim() },
11940
12047
  ...stringValue$1(data.error_message).trim() === "" ? {} : { errorMessage: stringValue$1(data.error_message).trim() },
11941
12048
  ...stringValue$1(data.failure_stage).trim() === "" ? {} : { failureStage: stringValue$1(data.failure_stage).trim() }
@@ -11958,17 +12065,17 @@ var AiVideoService = class {
11958
12065
  sessionId: stringValue$1(item.session_id).trim(),
11959
12066
  status,
11960
12067
  stage: stringValue$1(item.stage).trim() || status,
11961
- progress: Math.min(100, Math.max(0, Math.trunc(numberValue$29(item.progress)))),
12068
+ progress: Math.min(100, Math.max(0, Math.trunc(numberValue$30(item.progress)))),
11962
12069
  title: stringValue$1(source.title).trim() || "长录音 AI 视频",
11963
- sourceStartedAtMillis: numberValue$29(source.started_at),
11964
- selectedDurationMillis: Math.max(0, numberValue$29(source.selected_duration_millis)),
11965
- selectedSegmentCount: Math.max(0, Math.trunc(numberValue$29(item.selected_segment_count))),
12070
+ sourceStartedAtMillis: numberValue$30(source.started_at),
12071
+ selectedDurationMillis: Math.max(0, numberValue$30(source.selected_duration_millis)),
12072
+ selectedSegmentCount: Math.max(0, Math.trunc(numberValue$30(item.selected_segment_count))),
11966
12073
  retryable: booleanValue$15(item.retryable),
11967
- createdAtMillis: numberValue$29(item.created_at),
11968
- updatedAtMillis: numberValue$29(item.updated_at),
12074
+ createdAtMillis: numberValue$30(item.created_at),
12075
+ updatedAtMillis: numberValue$30(item.updated_at),
11969
12076
  ...stringValue$1(item.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(item.cover_asset_uid).trim() },
11970
12077
  ...stringValue$1(item.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(item.video_asset_uid).trim() },
11971
- ...numberValue$29(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$29(item.video_duration_millis) },
12078
+ ...numberValue$30(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$30(item.video_duration_millis) },
11972
12079
  ...stringValue$1(item.error_code).trim() === "" ? {} : { errorCode: stringValue$1(item.error_code).trim() },
11973
12080
  ...stringValue$1(item.error_message).trim() === "" ? {} : { errorMessage: clippedText(item.error_message, 500) }
11974
12081
  };
@@ -11994,7 +12101,7 @@ const ARKO_RUN_STATUSES = /* @__PURE__ */ new Set([
11994
12101
  "expired"
11995
12102
  ]);
11996
12103
  const ARKO_PROFILE_DISPLAY_NAME_CACHE_TTL_MS = 10 * 6e4;
11997
- function numberValue$28(value) {
12104
+ function numberValue$29(value) {
11998
12105
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
11999
12106
  }
12000
12107
  function booleanValue$14(value) {
@@ -12029,7 +12136,7 @@ function joinUrlWithQuery(baseUrl, path, query) {
12029
12136
  }
12030
12137
  function arkoProfileFromData(data) {
12031
12138
  const displayName = stringValue$1(data.display_name).trim();
12032
- const version = numberValue$28(data.version);
12139
+ const version = numberValue$29(data.version);
12033
12140
  if (displayName === "" || !Number.isSafeInteger(version) || version < 0) throw new ArkmePluginError("arko-profile-contract-invalid", "Arko 资料响应不完整", true, 502);
12034
12141
  return {
12035
12142
  displayName,
@@ -12082,9 +12189,9 @@ function parsedJsonRecord(value) {
12082
12189
  }
12083
12190
  function arkoHistoryItemFromData(value) {
12084
12191
  const data = objectValue$1(value);
12085
- const messageId = numberValue$28(data.id ?? data.msg_id ?? data.msgId);
12086
- const sessionId = numberValue$28(data.session_id ?? data.sessionId);
12087
- const roleCode = numberValue$28(data.role ?? data.sender_role ?? data.senderRole);
12192
+ const messageId = numberValue$29(data.id ?? data.msg_id ?? data.msgId);
12193
+ const sessionId = numberValue$29(data.session_id ?? data.sessionId);
12194
+ const roleCode = numberValue$29(data.role ?? data.sender_role ?? data.senderRole);
12088
12195
  if (!Number.isSafeInteger(messageId) || messageId <= 0 || !Number.isSafeInteger(sessionId) || sessionId <= 0 || roleCode !== 2 && roleCode !== 3) return void 0;
12089
12196
  const extra = parsedJsonRecord(data.extra ?? data.metadata ?? data.meta);
12090
12197
  const extraData = parsedJsonRecord(extra.data);
@@ -12105,7 +12212,7 @@ function arkoHistoryItemFromData(value) {
12105
12212
  extraData.updateAt,
12106
12213
  data.updated_at,
12107
12214
  data.updatedAt
12108
- ].map(numberValue$28).find((candidate) => candidate > 0) ?? 0;
12215
+ ].map(numberValue$29).find((candidate) => candidate > 0) ?? 0;
12109
12216
  const runUid = optionalString$3(extra.agent_run_uid);
12110
12217
  const runStatus = optionalString$3(extra.agent_run_status);
12111
12218
  const retryable = optionalBooleanValue$1(extra.agent_run_retryable);
@@ -12119,7 +12226,7 @@ function arkoHistoryItemFromData(value) {
12119
12226
  text: stringValue$1(data.content ?? data.text_content ?? data.textContent),
12120
12227
  reasoning: stringValue$1(data.reason_content ?? data.reason_text ?? data.reasonText),
12121
12228
  createdAtMillis: activityAt > 0 && activityAt < 1e11 ? activityAt * 1e3 : activityAt,
12122
- status: numberValue$28(data.status ?? data.msg_status),
12229
+ status: numberValue$29(data.status ?? data.msg_status),
12123
12230
  ...runUid === void 0 ? {} : { runUid },
12124
12231
  ...runStatus === void 0 ? {} : { runStatus },
12125
12232
  ...retryable === void 0 ? {} : { retryable },
@@ -12177,7 +12284,7 @@ var ArkoService = class {
12177
12284
  async arkoEnsureSession(signal) {
12178
12285
  const session = await this.runtime.requireSession();
12179
12286
  const latest = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/latest-session", { type: ARKO_AGENT_DIRECT_SESSION_TYPE }, session, signal);
12180
- const latestSessionId = numberValue$28(latest.id);
12287
+ const latestSessionId = numberValue$29(latest.id);
12181
12288
  if (Number.isSafeInteger(latestSessionId) && latestSessionId > 0) return {
12182
12289
  sessionId: latestSessionId,
12183
12290
  created: false,
@@ -12187,7 +12294,7 @@ var ArkoService = class {
12187
12294
  }
12188
12295
  async arkoCreateSession(signal) {
12189
12296
  const session = await this.runtime.requireSession();
12190
- const createdSessionId = numberValue$28((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-session", {
12297
+ const createdSessionId = numberValue$29((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-session", {
12191
12298
  name: ARKO_DEFAULT_SESSION_NAME,
12192
12299
  type: ARKO_AGENT_DIRECT_SESSION_TYPE
12193
12300
  }, session, signal)).session_id);
@@ -12270,9 +12377,9 @@ var ArkoService = class {
12270
12377
  };
12271
12378
  if (modelRouteKey !== "") body.model_route_key = modelRouteKey;
12272
12379
  const accepted = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-msg-v2", body, session, options.signal);
12273
- const userMsgId = numberValue$28(accepted.user_msg_id);
12274
- const assistantMsgId = numberValue$28(accepted.assistant_msg_id);
12275
- const acceptedSessionId = numberValue$28(accepted.session_id) || sessionId;
12380
+ const userMsgId = numberValue$29(accepted.user_msg_id);
12381
+ const assistantMsgId = numberValue$29(accepted.assistant_msg_id);
12382
+ const acceptedSessionId = numberValue$29(accepted.session_id) || sessionId;
12276
12383
  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);
12277
12384
  const runUid = stringValue$1(accepted.run_uid).trim();
12278
12385
  if (runUid === "") throw new ArkmePluginError("arko-ask-contract-invalid", "Arko 消息响应缺少运行标识", true, 502);
@@ -12308,8 +12415,8 @@ var ArkoService = class {
12308
12415
  }, session, signal);
12309
12416
  const status = stringValue$1(data.status).trim();
12310
12417
  const normalizedRunUid = stringValue$1(data.run_uid).trim() || runUid.trim();
12311
- const sequence = numberValue$28(data.sequence);
12312
- const surfaceAssistantMsgId = numberValue$28(data.surface_assistant_msg_id);
12418
+ const sequence = numberValue$29(data.sequence);
12419
+ const surfaceAssistantMsgId = numberValue$29(data.surface_assistant_msg_id);
12313
12420
  const clientAction = optionalRecord$1(data.client_action);
12314
12421
  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);
12315
12422
  return {
@@ -12547,7 +12654,7 @@ const ARKME_ARRANGEMENT_REF_TTL_MILLIS = 900 * 1e3;
12547
12654
  const MAX_ARKME_ARRANGEMENT_REFS = 4096;
12548
12655
  const ARKME_ARRANGEMENT_REMINDER_REF_TTL_MILLIS = 900 * 1e3;
12549
12656
  const MAX_ARKME_ARRANGEMENT_REMINDER_REFS = 4096;
12550
- function numberValue$27(value) {
12657
+ function numberValue$28(value) {
12551
12658
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
12552
12659
  }
12553
12660
  function booleanValue$13(value) {
@@ -12563,7 +12670,7 @@ function arrangementStatusCode(status) {
12563
12670
  return -1;
12564
12671
  }
12565
12672
  function arrangementStatus(value) {
12566
- const code = Math.trunc(numberValue$27(value));
12673
+ const code = Math.trunc(numberValue$28(value));
12567
12674
  if (code === 1) return "identified";
12568
12675
  if (code === 2) return "following";
12569
12676
  if (code === 3) return "completed";
@@ -12614,7 +12721,7 @@ var ArrangementService = class {
12614
12721
  }, session, options.signal, { lane: "interactive-read" });
12615
12722
  const rawItems = listValue$25(data.list);
12616
12723
  const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementItem(raw, session.userId)));
12617
- const total = Math.max(0, Math.trunc(numberValue$27(data.total)));
12724
+ const total = Math.max(0, Math.trunc(numberValue$28(data.total)));
12618
12725
  const nextOffset = offset + rawItems.length;
12619
12726
  const hasMore = rawItems.length > 0 && nextOffset < total;
12620
12727
  return {
@@ -12640,7 +12747,7 @@ var ArrangementService = class {
12640
12747
  }, session, options.signal, { lane: "interactive-read" });
12641
12748
  const rawItems = listValue$25(data.list);
12642
12749
  const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementReminderEvent(raw, session.userId)));
12643
- const total = Math.max(0, Math.trunc(numberValue$27(data.total)));
12750
+ const total = Math.max(0, Math.trunc(numberValue$28(data.total)));
12644
12751
  const nextOffset = offset + rawItems.length;
12645
12752
  const hasMore = rawItems.length > 0 && nextOffset < total;
12646
12753
  return {
@@ -12663,7 +12770,7 @@ var ArrangementService = class {
12663
12770
  project(data.next_reminder)
12664
12771
  ]);
12665
12772
  return {
12666
- unreadCount: Math.max(0, Math.trunc(numberValue$27(data.unread_count))),
12773
+ unreadCount: Math.max(0, Math.trunc(numberValue$28(data.unread_count))),
12667
12774
  ...latestUnread === void 0 ? {} : { latestUnread },
12668
12775
  ...latestEvent === void 0 ? {} : { latestEvent },
12669
12776
  ...nextReminder === void 0 ? {} : { nextReminder }
@@ -12767,7 +12874,7 @@ var ArrangementService = class {
12767
12874
  }, session, signal);
12768
12875
  return {
12769
12876
  outcome: "confirmed",
12770
- updatedCount: Math.max(0, Math.trunc(numberValue$27(data.updated_count)))
12877
+ updatedCount: Math.max(0, Math.trunc(numberValue$28(data.updated_count)))
12771
12878
  };
12772
12879
  } catch (error) {
12773
12880
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -12785,7 +12892,7 @@ var ArrangementService = class {
12785
12892
  }, session, signal);
12786
12893
  return {
12787
12894
  outcome: "confirmed",
12788
- updatedCount: Math.max(0, Math.trunc(numberValue$27(data.updated_count)))
12895
+ updatedCount: Math.max(0, Math.trunc(numberValue$28(data.updated_count)))
12789
12896
  };
12790
12897
  } catch (error) {
12791
12898
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -12800,7 +12907,7 @@ var ArrangementService = class {
12800
12907
  const data = await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/reminders/clear", {}, session, signal);
12801
12908
  return {
12802
12909
  outcome: "confirmed",
12803
- updatedCount: Math.max(0, Math.trunc(numberValue$27(data.updated_count)))
12910
+ updatedCount: Math.max(0, Math.trunc(numberValue$28(data.updated_count)))
12804
12911
  };
12805
12912
  } catch (error) {
12806
12913
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -12864,8 +12971,8 @@ var ArrangementService = class {
12864
12971
  const item = objectValue$1(raw);
12865
12972
  const arrangementUid = stringValue$1(item.uid).trim();
12866
12973
  if (arrangementUid === "") throw new ArkmePluginError("arrangement-contract-invalid", "安排响应缺少业务身份", true, 502);
12867
- const dueAtMillis = Math.trunc(numberValue$27(item.due_at));
12868
- const remindAtMillis = Math.trunc(numberValue$27(item.remind_at));
12974
+ const dueAtMillis = Math.trunc(numberValue$28(item.due_at));
12975
+ const remindAtMillis = Math.trunc(numberValue$28(item.remind_at));
12869
12976
  return {
12870
12977
  arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
12871
12978
  title: stringValue$1(item.title),
@@ -12873,8 +12980,8 @@ var ArrangementService = class {
12873
12980
  status: arrangementStatus(item.status),
12874
12981
  reminderEnabled: booleanValue$13(item.reminder_enabled),
12875
12982
  reminderState: stringValue$1(item.reminder_state),
12876
- createdAtMillis: Math.trunc(numberValue$27(item.create_at)),
12877
- updatedAtMillis: Math.trunc(numberValue$27(item.update_at)),
12983
+ createdAtMillis: Math.trunc(numberValue$28(item.create_at)),
12984
+ updatedAtMillis: Math.trunc(numberValue$28(item.update_at)),
12878
12985
  ...dueAtMillis > 0 ? { dueAtMillis } : {},
12879
12986
  ...remindAtMillis > 0 ? { remindAtMillis } : {}
12880
12987
  };
@@ -12895,20 +13002,20 @@ var ArrangementService = class {
12895
13002
  const eventUid = stringValue$1(item.uid).trim();
12896
13003
  const arrangementUid = stringValue$1(item.arrangement_uid).trim();
12897
13004
  if (eventUid === "" || arrangementUid === "") throw new ArkmePluginError("arrangement-reminder-contract-invalid", "安排提醒响应缺少有效身份", true, 502);
12898
- const dueAtMillis = Math.trunc(numberValue$27(item.due_at));
12899
- const remindAtMillis = Math.trunc(numberValue$27(item.remind_at));
12900
- const readAtMillis = Math.trunc(numberValue$27(item.read_at));
13005
+ const dueAtMillis = Math.trunc(numberValue$28(item.due_at));
13006
+ const remindAtMillis = Math.trunc(numberValue$28(item.remind_at));
13007
+ const readAtMillis = Math.trunc(numberValue$28(item.read_at));
12901
13008
  return {
12902
13009
  eventRef: await this.arrangementReminderRef(viewerUserId, eventUid),
12903
13010
  arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
12904
13011
  title: stringValue$1(item.title),
12905
13012
  description: stringValue$1(item.description),
12906
13013
  eventKind: stringValue$1(item.event_kind),
12907
- eventAtMillis: Math.trunc(numberValue$27(item.event_at)),
13014
+ eventAtMillis: Math.trunc(numberValue$28(item.event_at)),
12908
13015
  read: readAtMillis > 0,
12909
13016
  reminderState: stringValue$1(item.reminder_state),
12910
- createdAtMillis: Math.trunc(numberValue$27(item.create_at)),
12911
- updatedAtMillis: Math.trunc(numberValue$27(item.update_at)),
13017
+ createdAtMillis: Math.trunc(numberValue$28(item.create_at)),
13018
+ updatedAtMillis: Math.trunc(numberValue$28(item.update_at)),
12912
13019
  ...dueAtMillis > 0 ? { dueAtMillis } : {},
12913
13020
  ...remindAtMillis > 0 ? { remindAtMillis } : {},
12914
13021
  ...readAtMillis > 0 ? { readAtMillis } : {}
@@ -12965,7 +13072,7 @@ var ArrangementService = class {
12965
13072
  const ARKME_PHONE_BIND_SUCCESS = 1;
12966
13073
  const ARKME_PHONE_BIND_REPEAT = 2;
12967
13074
  const ARKME_PHONE_BIND_CODE_ERR = 3;
12968
- function numberValue$26(value) {
13075
+ function numberValue$27(value) {
12969
13076
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
12970
13077
  }
12971
13078
  function jiwoScanLoginAvailable(config) {
@@ -13050,7 +13157,7 @@ var AuthService = class {
13050
13157
  const sceneStr = stringValue$1(data.scene_str).trim();
13051
13158
  const pollToken = stringValue$1(data.poll_token).trim();
13052
13159
  const qrContent = stringValue$1(data.url).trim();
13053
- const expireSeconds = Math.max(30, numberValue$26(data.expire_seconds) || 300);
13160
+ const expireSeconds = Math.max(30, numberValue$27(data.expire_seconds) || 300);
13054
13161
  if (qrContent === "" && sceneStr !== "") throw new ArkmePluginError("wechat-qr-unavailable", "测试环境当前未返回可用微信二维码,请使用手机号登录", true, 503);
13055
13162
  if (sceneStr === "" || pollToken === "" || qrContent === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录二维码响应不完整", true, 502);
13056
13163
  const attemptId = crypto.randomUUID();
@@ -13089,7 +13196,7 @@ var AuthService = class {
13089
13196
  ref: 0,
13090
13197
  keep_cancel: true
13091
13198
  }, void 0, [200]);
13092
- const userId = numberValue$26(data.user_id);
13199
+ const userId = numberValue$27(data.user_id);
13093
13200
  if (userId <= 0) return {
13094
13201
  status: "pending",
13095
13202
  environment: this.runtime.config.environment,
@@ -13119,7 +13226,7 @@ var AuthService = class {
13119
13226
  }, void 0, [200]);
13120
13227
  const ticket = stringValue$1(data.ticket).trim();
13121
13228
  const pollSecret = stringValue$1(data.poll_secret).trim();
13122
- const expiresAtMillis = numberValue$26(data.expires_at);
13229
+ const expiresAtMillis = numberValue$27(data.expires_at);
13123
13230
  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);
13124
13231
  if (attemptGeneration !== this.jiwoAttemptGeneration) {
13125
13232
  await this.cancelJiwoCredentials(ticket, pollSecret);
@@ -13169,7 +13276,7 @@ var AuthService = class {
13169
13276
  };
13170
13277
  }
13171
13278
  if (status !== "authenticated") throw new ArkmePluginError("jiwo-login-contract-invalid", "即我扫码登录状态无效", true, 502);
13172
- const userId = numberValue$26(data.user_id);
13279
+ const userId = numberValue$27(data.user_id);
13173
13280
  const accessToken = stringValue$1(data.access_token);
13174
13281
  const refreshToken = stringValue$1(data.refresh_token);
13175
13282
  if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录成功响应缺少凭据", false, 502);
@@ -13238,7 +13345,7 @@ var AuthService = class {
13238
13345
  const normalizedCaptcha = this.normalizedCaptcha(captcha);
13239
13346
  const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
13240
13347
  if (session !== void 0) {
13241
- if (numberValue$26((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/bind-phone-send-code", {
13348
+ if (numberValue$27((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/bind-phone-send-code", {
13242
13349
  phone: normalizedPhone,
13243
13350
  pre: "86",
13244
13351
  is_test: this.runtime.config.environment === "test",
@@ -13260,7 +13367,7 @@ var AuthService = class {
13260
13367
  if (!/^[0-9]{6}$/.test(normalizedCode)) throw new ArkmePluginError("phone-code-invalid", "请输入有效的短信验证码", false);
13261
13368
  const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
13262
13369
  if (session !== void 0) {
13263
- const result = numberValue$26((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/verify-bind-phone", {
13370
+ const result = numberValue$27((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/verify-bind-phone", {
13264
13371
  phone: normalizedPhone,
13265
13372
  pre: "86",
13266
13373
  code: normalizedCode,
@@ -13281,7 +13388,7 @@ var AuthService = class {
13281
13388
  keep_cancel: true
13282
13389
  }, void 0, [200]);
13283
13390
  if (data.ok === false) throw new ArkmePluginError("phone-code-rejected", "手机号或验证码错误", false, 401);
13284
- const userId = numberValue$26(data.user_id);
13391
+ const userId = numberValue$27(data.user_id);
13285
13392
  const accessToken = stringValue$1(data.access_token);
13286
13393
  const refreshToken = stringValue$1(data.refresh_token);
13287
13394
  if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme手机号登录响应不完整", false, 502);
@@ -13541,6 +13648,194 @@ var ArkmePrivacyVisibilityService = class {
13541
13648
  return immutable;
13542
13649
  }
13543
13650
  };
13651
+ const UPDATED_OUTCOME = 1;
13652
+ const IDEMPOTENT_OUTCOME = 2;
13653
+ const MAX_BATCH_SIZE = 200;
13654
+ const MAX_QUERY_REFS = 1e3;
13655
+ const MAX_ENTITY_UID_BYTES = 128;
13656
+ /** HTTP/CAS adapter. Only revision hints are cached; owner snapshots stay server-owned. */
13657
+ var ConversationListPreferenceService = class {
13658
+ runtime;
13659
+ revisionOwnerUserId;
13660
+ revisionHints = /* @__PURE__ */ new Map();
13661
+ constructor(runtime) {
13662
+ this.runtime = runtime;
13663
+ }
13664
+ async query(refs, operation) {
13665
+ const normalized = normalizeRefs(refs);
13666
+ if (normalized.length === 0) return [];
13667
+ const session = await this.requireOwnerSession(operation.ownerUserId);
13668
+ return await this.queryWithSession(normalized, session, operation.signal);
13669
+ }
13670
+ async dismiss(rawRef, rawEvidence, operation) {
13671
+ const ref = normalizeRef(rawRef);
13672
+ const evidence = normalizeEvidence(rawEvidence);
13673
+ if (evidence.sequence === 0 && evidence.activityAtMillis === 0) throw new ArkmePluginError("conversation-list-preference-evidence-unavailable", "当前会话活动信息不完整,请刷新后重试", true, 409);
13674
+ if (ref.entityKind === 2 && evidence.sequence !== 0) throw new ArkmePluginError("conversation-list-preference-evidence-invalid", "Bot 会话活动信息无效,请刷新后重试", false, 400);
13675
+ const session = await this.requireOwnerSession(operation.ownerUserId);
13676
+ const key = conversationListPreferenceRefKey(ref);
13677
+ const expectedRevision = this.revisionHints.get(key) ?? (await this.queryWithSession([ref], session, operation.signal))[0].revision;
13678
+ const result = (await this.set([{
13679
+ ref,
13680
+ expectedRevision,
13681
+ targetVisibilityState: 2,
13682
+ evidence
13683
+ }], session, operation.signal))[0];
13684
+ if (result.outcome !== UPDATED_OUTCOME && result.outcome !== IDEMPOTENT_OUTCOME || result.snapshot.visibilityState !== 2 || !conversationListPreferenceIsDismissed(result.snapshot, evidence)) throw new ArkmePluginError("conversation-list-preference-conflict", "会话状态已变化,已保留在列表中", false, 409);
13685
+ return result.snapshot;
13686
+ }
13687
+ async restore(refs, operation) {
13688
+ const normalized = normalizeRefs(refs);
13689
+ if (normalized.length === 0) return;
13690
+ const session = await this.requireOwnerSession(operation.ownerUserId);
13691
+ const writes = (await this.queryWithSession(normalized, session, operation.signal)).filter((snapshot) => snapshot.visibilityState === 2).map((snapshot) => ({
13692
+ ref: snapshot.ref,
13693
+ expectedRevision: snapshot.revision,
13694
+ targetVisibilityState: 1,
13695
+ evidence: {
13696
+ sequence: 0,
13697
+ activityAtMillis: 0
13698
+ }
13699
+ }));
13700
+ for (let offset = 0; offset < writes.length; offset += MAX_BATCH_SIZE) await this.set(writes.slice(offset, offset + MAX_BATCH_SIZE), session, operation.signal);
13701
+ }
13702
+ async restoreIfUnchanged(snapshots, operation) {
13703
+ if (snapshots.length === 0) return;
13704
+ const session = await this.requireOwnerSession(operation.ownerUserId);
13705
+ const writes = snapshots.map((snapshot) => {
13706
+ if (snapshot.visibilityState !== 2 || !Number.isSafeInteger(snapshot.revision) || snapshot.revision <= 0) throw contractError$1("restore-snapshot");
13707
+ return {
13708
+ ref: normalizeRef(snapshot.ref),
13709
+ expectedRevision: snapshot.revision,
13710
+ targetVisibilityState: 1,
13711
+ evidence: {
13712
+ sequence: 0,
13713
+ activityAtMillis: 0
13714
+ }
13715
+ };
13716
+ });
13717
+ for (let offset = 0; offset < writes.length; offset += MAX_BATCH_SIZE) await this.set(writes.slice(offset, offset + MAX_BATCH_SIZE), session, operation.signal);
13718
+ }
13719
+ async queryWithSession(refs, session, signal) {
13720
+ const snapshots = [];
13721
+ for (let offset = 0; offset < refs.length; offset += MAX_BATCH_SIZE) {
13722
+ const chunk = refs.slice(offset, offset + MAX_BATCH_SIZE);
13723
+ const items = requiredList((await this.runtime.authenticatedChatPost("/api/v1/conversation-list/preferences/query", { entry_refs: chunk.map(refBody) }, session, signal)).items, "query-items");
13724
+ if (items.length !== chunk.length) throw contractError$1("query-shape");
13725
+ items.forEach((value, index) => {
13726
+ const snapshot = parseSnapshot(objectValue$1(value));
13727
+ if (!sameRef(snapshot.ref, chunk[index])) throw contractError$1("query-identity");
13728
+ snapshots.push(snapshot);
13729
+ });
13730
+ }
13731
+ this.rememberRevisions(snapshots, session.userId);
13732
+ return snapshots;
13733
+ }
13734
+ async set(writes, session, signal) {
13735
+ if (writes.length === 0 || writes.length > MAX_BATCH_SIZE) throw contractError$1("set-batch");
13736
+ const items = requiredList((await this.runtime.authenticatedChatPost("/api/v1/conversation-list/preferences/set", { items: writes.map((write) => ({
13737
+ ...refBody(write.ref),
13738
+ target_visibility_state: write.targetVisibilityState,
13739
+ expected_revision: write.expectedRevision,
13740
+ observed_seq: write.evidence.sequence,
13741
+ observed_activity_at: write.evidence.activityAtMillis
13742
+ })) }, session, signal)).items, "set-items");
13743
+ if (items.length !== writes.length) throw contractError$1("set-shape");
13744
+ const results = items.map((value, index) => {
13745
+ const raw = objectValue$1(value);
13746
+ const outcome = requiredInteger$1(raw.outcome, "outcome");
13747
+ const snapshot = parseSnapshot(raw);
13748
+ if (outcome < 1 || outcome > 3) throw contractError$1("outcome");
13749
+ if (!sameRef(snapshot.ref, writes[index].ref)) throw contractError$1("set-identity");
13750
+ return {
13751
+ outcome,
13752
+ snapshot
13753
+ };
13754
+ });
13755
+ this.rememberRevisions(results.map((result) => result.snapshot), session.userId);
13756
+ return results;
13757
+ }
13758
+ async requireOwnerSession(ownerUserId) {
13759
+ const session = await this.runtime.requireSession();
13760
+ if (!Number.isSafeInteger(ownerUserId) || ownerUserId <= 0 || session.userId !== ownerUserId) throw new ArkmePluginError("login-context-changed", "登录账号已切换,请重试当前操作", false, 409);
13761
+ if (this.revisionOwnerUserId !== ownerUserId) {
13762
+ this.revisionOwnerUserId = ownerUserId;
13763
+ this.revisionHints.clear();
13764
+ }
13765
+ return session;
13766
+ }
13767
+ rememberRevisions(snapshots, ownerUserId) {
13768
+ if (this.revisionOwnerUserId !== ownerUserId) return;
13769
+ for (const snapshot of snapshots) {
13770
+ const key = conversationListPreferenceRefKey(snapshot.ref);
13771
+ this.revisionHints.set(key, Math.max(this.revisionHints.get(key) ?? 0, snapshot.revision));
13772
+ }
13773
+ }
13774
+ };
13775
+ function conversationListPreferenceIsDismissed(snapshot, evidence) {
13776
+ return snapshot.visibilityState === 2 && evidence.sequence <= snapshot.dismissedThroughSequence && evidence.activityAtMillis <= snapshot.dismissedThroughActivityAtMillis;
13777
+ }
13778
+ function conversationListPreferenceRefKey(ref) {
13779
+ return `${String(ref.entityKind)}:${ref.entityUid}`;
13780
+ }
13781
+ function parseSnapshot(raw) {
13782
+ const ref = normalizeRef({
13783
+ entityKind: requiredInteger$1(raw.entity_kind, "entity-kind"),
13784
+ entityUid: stringValue$1(raw.entity_uid)
13785
+ });
13786
+ const visibilityState = requiredInteger$1(raw.visibility_state, "visibility-state");
13787
+ if (visibilityState !== 1 && visibilityState !== 2) throw contractError$1("visibility-state");
13788
+ const snapshot = {
13789
+ ref,
13790
+ visibilityState,
13791
+ dismissedThroughSequence: requiredInteger$1(raw.dismissed_through_seq, "dismissed-sequence"),
13792
+ dismissedThroughActivityAtMillis: requiredInteger$1(raw.dismissed_through_activity_at, "dismissed-activity"),
13793
+ revision: requiredInteger$1(raw.revision, "revision"),
13794
+ updatedAtMillis: requiredInteger$1(raw.updated_at, "updated-at")
13795
+ };
13796
+ if (snapshot.visibilityState === 1 && (snapshot.dismissedThroughSequence !== 0 || snapshot.dismissedThroughActivityAtMillis !== 0)) throw contractError$1("visible-anchor");
13797
+ if (snapshot.visibilityState === 2 && snapshot.dismissedThroughSequence === 0 && snapshot.dismissedThroughActivityAtMillis === 0) throw contractError$1("dismissed-anchor");
13798
+ if (snapshot.ref.entityKind === 2 && snapshot.dismissedThroughSequence !== 0) throw contractError$1("bot-sequence");
13799
+ if (snapshot.revision === 0 !== (snapshot.updatedAtMillis === 0) || snapshot.visibilityState === 2 && snapshot.revision === 0) throw contractError$1("revision");
13800
+ return snapshot;
13801
+ }
13802
+ function normalizeRefs(refs) {
13803
+ if (refs.length > MAX_QUERY_REFS) throw contractError$1("query-too-large");
13804
+ const unique = /* @__PURE__ */ new Map();
13805
+ for (const value of refs) {
13806
+ const ref = normalizeRef(value);
13807
+ unique.set(conversationListPreferenceRefKey(ref), ref);
13808
+ }
13809
+ return [...unique.values()];
13810
+ }
13811
+ function normalizeRef(ref) {
13812
+ if (ref.entityKind !== 1 && ref.entityKind !== 2 || ref.entityUid === "" || ref.entityUid !== ref.entityUid.trim() || Buffer.byteLength(ref.entityUid, "utf8") > MAX_ENTITY_UID_BYTES) throw contractError$1("identity");
13813
+ return { ...ref };
13814
+ }
13815
+ function normalizeEvidence(evidence) {
13816
+ if (!Number.isSafeInteger(evidence.sequence) || evidence.sequence < 0 || !Number.isSafeInteger(evidence.activityAtMillis) || evidence.activityAtMillis < 0) throw contractError$1("evidence");
13817
+ return { ...evidence };
13818
+ }
13819
+ function refBody(ref) {
13820
+ return {
13821
+ entity_kind: ref.entityKind,
13822
+ entity_uid: ref.entityUid
13823
+ };
13824
+ }
13825
+ function sameRef(left, right) {
13826
+ return left.entityKind === right.entityKind && left.entityUid === right.entityUid;
13827
+ }
13828
+ function requiredList(value, field) {
13829
+ if (!Array.isArray(value)) throw contractError$1(field);
13830
+ return value;
13831
+ }
13832
+ function requiredInteger$1(value, field) {
13833
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw contractError$1(field);
13834
+ return value;
13835
+ }
13836
+ function contractError$1(field) {
13837
+ return new ArkmePluginError(`conversation-list-preference-${field}-invalid`, "会话列表偏好响应不完整,请刷新后重试", true, 502);
13838
+ }
13544
13839
  //#endregion
13545
13840
  //#region src/mention-metadata.ts
13546
13841
  function listValue$24(value) {
@@ -13862,7 +14157,7 @@ const GROUP_AVATAR_NEGATIVE_CACHE_TTL_MS = 6e4;
13862
14157
  const TOPIC_DISSOLVE_RECORD_CONCURRENCY = 6;
13863
14158
  const PRIVATE_REMARK_PAGE_LIMIT = 50;
13864
14159
  const PRIVATE_REMARK_MAX_PAGES = 20;
13865
- function numberValue$25(value) {
14160
+ function numberValue$26(value) {
13866
14161
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
13867
14162
  }
13868
14163
  function integerIdentifierValue(value) {
@@ -13908,15 +14203,20 @@ function arkmeGroupBotBindingBody(source) {
13908
14203
  if (target?.subjectUid !== void 0 && target.subjectUid.trim() !== "") return { subject_uid: target.subjectUid.trim() };
13909
14204
  return { subject_uid: source.ownerRef };
13910
14205
  }
13911
- function arkmeChatSidebarTargetFromBundle(bundle, fallbackSubjectUid = "") {
14206
+ function arkmeChatDirectoryMetadataFromBundle(bundle, fallbackSubjectUid = "") {
13912
14207
  const chatSession = objectValue$1(bundle.session);
14208
+ const unread = objectValue$1(bundle.unread_snapshot);
13913
14209
  const subjectUid = stringValue$1(bundle.subject_uid ?? bundle.subjectUid ?? bundle.topic_uid ?? bundle.topicUid ?? chatSession.subject_uid ?? chatSession.subjectUid ?? chatSession.topic_uid ?? chatSession.topicUid ?? fallbackSubjectUid).trim();
13914
- if (subjectUid === "") return void 0;
13915
14210
  return {
13916
- subjectUid,
13917
- hiddenAnchorTimestamp: Math.max(0, numberValue$25(bundle.sort_active_at ?? bundle.last_active_at ?? chatSession.last_active_at))
14211
+ ...subjectUid === "" ? {} : { subjectUid },
14212
+ activityAtMillis: arkmeChatSortActiveAt(bundle, chatSession),
14213
+ latestSequence: Math.max(0, numberValue$26(unread.session_last_seq ?? chatSession.last_seq))
13918
14214
  };
13919
14215
  }
14216
+ function arkmeChatSortActiveAt(bundle, chatSession) {
14217
+ const projected = numberValue$26(bundle.sort_active_at);
14218
+ return Math.max(0, projected > 0 ? projected : numberValue$26(bundle.last_active_at ?? chatSession.last_active_at));
14219
+ }
13920
14220
  function backendBooleanValue(value) {
13921
14221
  if (value === null || value === void 0) return void 0;
13922
14222
  if (typeof value === "boolean") return value;
@@ -14160,9 +14460,17 @@ var SourceService = class {
14160
14460
  * the resolved label into their own viewer-bound response.
14161
14461
  */
14162
14462
  async privateDisplayNamesByUserIds(userIds, options = {}) {
14463
+ const labels = await this.privateChatViewerLabelsByUserIds(userIds, options);
14464
+ return new Map([...labels].map(([userId, label]) => [userId, label.displayName]));
14465
+ }
14466
+ /**
14467
+ * Resolve the current viewer's private-chat display label and its exact
14468
+ * remark source without compressing fallback snapshots into remark facts.
14469
+ */
14470
+ async privateChatViewerLabelsByUserIds(userIds, options = {}) {
14163
14471
  const session = await this.runtime.requireSession();
14164
14472
  const remaining = new Set(userIds.filter((userId) => Number.isSafeInteger(userId) && userId > 0 && userId !== session.userId));
14165
- const displayNames = /* @__PURE__ */ new Map();
14473
+ const labels = /* @__PURE__ */ new Map();
14166
14474
  let pageCursor;
14167
14475
  for (let page = 0; page < 20 && remaining.size > 0; page += 1) {
14168
14476
  const data = await this.runtime.authenticatedChatPost("/api/v1/chats/list", {
@@ -14170,18 +14478,22 @@ var SourceService = class {
14170
14478
  ...pageCursor === void 0 ? {} : { page_cursor: pageCursor }
14171
14479
  }, session, options.signal, {
14172
14480
  lane: "background-read",
14173
- key: `world-author-labels:${pageCursor === void 0 ? "first" : String(page)}`
14481
+ key: `private-chat-viewer-labels:${pageCursor === void 0 ? "first" : String(page)}`
14174
14482
  });
14175
14483
  for (const raw of listValue$23(data.items)) {
14176
14484
  const bundle = objectValue$1(raw);
14177
- const sessionKind = numberValue$25(objectValue$1(bundle.session).session_kind);
14485
+ const sessionKind = numberValue$26(objectValue$1(bundle.session).session_kind);
14178
14486
  if (sessionKind !== 1 && sessionKind !== 3) continue;
14179
- const targetUserId = numberValue$25(objectValue$1(bundle.private_counterpart).user_id);
14487
+ const targetUserId = numberValue$26(objectValue$1(bundle.private_counterpart).user_id);
14180
14488
  if (!remaining.has(targetUserId)) continue;
14181
14489
  const supplement = objectValue$1(bundle.private_supplement);
14182
14490
  const counterpart = objectValue$1(bundle.private_counterpart);
14183
- const displayName = stringValue$1(supplement.remark).trim() || stringValue$1(supplement.counterpart_name_snapshot).trim() || stringValue$1(counterpart.display_name_snapshot).trim() || stringValue$1(supplement.pending_name).trim() || stringValue$1(counterpart.visible_phone).trim();
14184
- if (displayName !== "") displayNames.set(targetUserId, displayName);
14491
+ const remark = stringValue$1(supplement.remark).trim();
14492
+ const displayName = remark || stringValue$1(supplement.counterpart_name_snapshot).trim() || stringValue$1(counterpart.display_name_snapshot).trim() || stringValue$1(supplement.pending_name).trim() || stringValue$1(counterpart.visible_phone).trim();
14493
+ if (displayName !== "") labels.set(targetUserId, {
14494
+ displayName,
14495
+ remark
14496
+ });
14185
14497
  remaining.delete(targetUserId);
14186
14498
  }
14187
14499
  if (data.has_more !== true) break;
@@ -14189,7 +14501,7 @@ var SourceService = class {
14189
14501
  if (Object.keys(next).length === 0) break;
14190
14502
  pageCursor = next;
14191
14503
  }
14192
- return displayNames;
14504
+ return labels;
14193
14505
  }
14194
14506
  /**
14195
14507
  * Resolve only viewer-owned contact remarks for the supplied people.
@@ -14237,7 +14549,7 @@ var SourceService = class {
14237
14549
  });
14238
14550
  for (const value of listValue$23(data.items)) {
14239
14551
  const bundle = objectValue$1(value);
14240
- const sessionKind = numberValue$25(objectValue$1(bundle.session).session_kind);
14552
+ const sessionKind = numberValue$26(objectValue$1(bundle.session).session_kind);
14241
14553
  if (sessionKind !== 1 && sessionKind !== 3) continue;
14242
14554
  const targetUserId = integerIdentifierValue(objectValue$1(bundle.private_counterpart).user_id);
14243
14555
  if (!remaining.has(targetUserId)) continue;
@@ -14280,10 +14592,10 @@ var SourceService = class {
14280
14592
  extra: { source: "dsh-arkme" }
14281
14593
  }, session);
14282
14594
  const topicUid = stringValue$1(created.topic_uid).trim();
14283
- if (topicUid === "" || numberValue$25(created.status) !== 1) throw new ArkmePluginError("topic-create-contract-invalid", "主题创建响应不完整", true, 502);
14595
+ if (topicUid === "" || numberValue$26(created.status) !== 1) throw new ArkmePluginError("topic-create-contract-invalid", "主题创建响应不完整", true, 502);
14284
14596
  const sourceRef = await this.sealSourceRef(session.userId, "topic", topicUid, title);
14285
14597
  if (parentTopicUid !== void 0) try {
14286
- if (numberValue$25(objectValue$1((await this.runtime.authenticatedPost("/api/v1/topics/hierarchy/bind", {
14598
+ if (numberValue$26(objectValue$1((await this.runtime.authenticatedPost("/api/v1/topics/hierarchy/bind", {
14287
14599
  parent_topic_uid: parentTopicUid,
14288
14600
  child_topic_uid: topicUid
14289
14601
  }, session)).relation).status) !== 1) throw new ArkmePluginError("topic-hierarchy-bind-contract-invalid", "子主题层级响应不完整", true, 502);
@@ -14459,7 +14771,7 @@ var SourceService = class {
14459
14771
  }
14460
14772
  onProgress?.(recordUids.size);
14461
14773
  if (data.has_more !== true) return [...recordUids];
14462
- const nextSendAt = numberValue$25(data.next_cursor_send_at);
14774
+ const nextSendAt = numberValue$26(data.next_cursor_send_at);
14463
14775
  const nextRecordUid = stringValue$1(data.next_cursor_record_uid).trim();
14464
14776
  const cursorKey = `${String(nextSendAt)}:${nextRecordUid}`;
14465
14777
  if (nextSendAt <= 0 || nextRecordUid === "" || seenCursors.has(cursorKey)) throw new ArkmePluginError("topic-dissolve-record-page-invalid", "主题快记加载不完整,未解散主题,请刷新后重试", true, 502);
@@ -14542,7 +14854,7 @@ var SourceService = class {
14542
14854
  parent_topic_uid: nextParentTopicUid ?? "",
14543
14855
  insert_before_topic_uid: ""
14544
14856
  }, session);
14545
- return stringValue$1(moved.topic_uid).trim() === topicUid && stringValue$1(moved.parent_topic_uid).trim() === (nextParentTopicUid ?? "") && numberValue$25(moved.sibling_order) > 0;
14857
+ return stringValue$1(moved.topic_uid).trim() === topicUid && stringValue$1(moved.parent_topic_uid).trim() === (nextParentTopicUid ?? "") && numberValue$26(moved.sibling_order) > 0;
14546
14858
  }
14547
14859
  async rollbackDissolvedTopicChildren(children, topicUid, parentTopicUid, session) {
14548
14860
  for (const child of [...children].reverse()) await this.moveTopicHierarchyForDissolve(child.topic.ownerRef, parentTopicUid ?? "", topicUid, session).catch(() => false);
@@ -14571,7 +14883,7 @@ var SourceService = class {
14571
14883
  }, session);
14572
14884
  const movedTopicUid = stringValue$1(moved.topic_uid).trim();
14573
14885
  const movedParentUid = stringValue$1(moved.parent_topic_uid).trim();
14574
- const siblingOrder = numberValue$25(moved.sibling_order);
14886
+ const siblingOrder = numberValue$26(moved.sibling_order);
14575
14887
  if (movedTopicUid !== topic.ownerRef || movedParentUid !== (nextParentUid ?? "") || siblingOrder <= 0) throw new ArkmePluginError("topic-hierarchy-move-contract-invalid", "主题移动响应不完整,请刷新后重试", true, 502);
14576
14888
  this.sourceListCache.clear();
14577
14889
  return {
@@ -14610,36 +14922,27 @@ var SourceService = class {
14610
14922
  if (this.sourceListInFlight.get(cacheKey) === pending) this.sourceListInFlight.delete(cacheKey);
14611
14923
  }
14612
14924
  }
14613
- async setChatDirectoryPolicy(sourceRef, options = {}) {
14925
+ async setChatDirectoryPin(sourceRef, pinned, signal) {
14614
14926
  const session = await this.runtime.requireSession();
14615
14927
  const source = await this.openSourceRef(sourceRef, session.userId);
14616
14928
  if (source.kind !== "private_chat" && source.kind !== "group_chat") throw new ArkmePluginError("chat-directory-policy-invalid", "仅支持更新私聊或群聊的会话列表状态", false);
14617
- const sidebarTarget = await this.resolveChatSidebarTarget(source, session, options.signal);
14618
- const current = await this.runtime.authenticatedChatPost("/api/v1/chats/policy/get", { chat_session_uid: source.ownerRef }, session, options.signal);
14619
- const pinned = options.pinned ?? numberValue$25(current.pin_state) === 2;
14620
- const hidden = options.hidden ?? numberValue$25(current.show_in_home_state) === 2;
14929
+ const pinTarget = await this.resolveChatPinTarget(source, session, signal);
14930
+ const current = await this.runtime.authenticatedChatPost("/api/v1/chats/policy/get", { chat_session_uid: source.ownerRef }, session, signal);
14621
14931
  const updatedAt = Date.now();
14622
- const writes = [this.runtime.authenticatedChatPost("/api/v1/chats/policy/update", {
14932
+ await Promise.all([this.runtime.authenticatedChatPost("/api/v1/chats/policy/update", {
14623
14933
  chat_session_uid: source.ownerRef,
14624
- show_in_home_state: hidden ? 2 : 1,
14625
- privacy_state: numberValue$25(current.privacy_state) || 1,
14626
- mute_state: numberValue$25(current.mute_state) || 1,
14934
+ show_in_home_state: numberValue$26(current.show_in_home_state) || 1,
14935
+ privacy_state: numberValue$26(current.privacy_state) || 1,
14936
+ mute_state: numberValue$26(current.mute_state) || 1,
14627
14937
  pin_state: pinned ? 2 : 1,
14628
- notify_state: numberValue$25(current.notify_state) || 1,
14629
- status: numberValue$25(current.status) || 1,
14938
+ notify_state: numberValue$26(current.notify_state) || 1,
14939
+ status: numberValue$26(current.status) || 1,
14630
14940
  update_at: updatedAt
14631
- }, session, options.signal)];
14632
- if (options.hidden !== void 0) writes.push(this.runtime.authenticatedSubjectPost("/api/v1/subject/batch-set-sidebar-hidden-status", { items: [{
14633
- subject_uid: sidebarTarget.subjectUid,
14634
- hidden,
14635
- hidden_anchor_timestamp: hidden ? Math.max(1, sidebarTarget.hiddenAnchorTimestamp || updatedAt) : 0
14636
- }] }, session, options.signal));
14637
- if (options.pinned !== void 0) writes.push(this.runtime.authenticatedPost("/api/v1/topics/pin/set", {
14638
- topic_uid: sidebarTarget.subjectUid,
14941
+ }, session, signal), this.runtime.authenticatedPost("/api/v1/topics/pin/set", {
14942
+ topic_uid: pinTarget.subjectUid,
14639
14943
  pin_state: pinned ? 1 : 2,
14640
14944
  ...pinned ? { pinned_at: updatedAt } : {}
14641
- }, session, options.signal));
14642
- await Promise.all(writes);
14945
+ }, session, signal)]);
14643
14946
  const cacheKey = `${String(session.userId)}:${source.ownerRef}`;
14644
14947
  const cached = this.chatSourceCache.get(cacheKey);
14645
14948
  if (cached !== void 0) this.storeChatSourceByKey(cacheKey, {
@@ -14649,22 +14952,54 @@ var SourceService = class {
14649
14952
  this.sourceListCache.clear();
14650
14953
  return {
14651
14954
  sourceRef,
14652
- pinned,
14653
- hidden
14955
+ pinned
14654
14956
  };
14655
14957
  }
14656
- async resolveChatSidebarTarget(source, session, signal) {
14657
- if (source.sidebarSubjectUid !== void 0 && source.sidebarSubjectUid.trim() !== "") return {
14658
- subjectUid: source.sidebarSubjectUid.trim(),
14659
- hiddenAnchorTimestamp: Math.max(0, source.sidebarHiddenAnchorTimestamp ?? 0)
14958
+ async chatConversationListPreferenceEntry(sourceRef, signal) {
14959
+ const session = await this.runtime.requireSession();
14960
+ const source = await this.openSourceRef(sourceRef, session.userId);
14961
+ if (source.kind !== "private_chat" && source.kind !== "group_chat") throw new ArkmePluginError("chat-directory-policy-invalid", "仅支持读取私聊或群聊的会话列表状态", false);
14962
+ if (signal?.aborted === true) throw new DOMException("The operation was aborted", "AbortError");
14963
+ const cached = this.chatSourceCache.get(`${String(session.userId)}:${source.ownerRef}`);
14964
+ return {
14965
+ ownerUserId: session.userId,
14966
+ ref: {
14967
+ entityKind: 1,
14968
+ entityUid: source.ownerRef
14969
+ },
14970
+ evidence: {
14971
+ sequence: Math.max(source.conversationListLatestSequence ?? 0, cached?.latestSequence ?? 0),
14972
+ activityAtMillis: Math.max(source.conversationListActivityAtMillis ?? 0, cached?.activeAtMillis ?? 0)
14973
+ }
14660
14974
  };
14661
- const target = arkmeChatSidebarTargetFromBundle(await this.runtime.authenticatedChatPost("/api/v1/chats/detail", { chat_session_uid: source.ownerRef }, session, signal, {
14975
+ }
14976
+ async chatConversationListPreferenceEntryBySessionUid(chatSessionUid, expectedOwnerUserId) {
14977
+ const session = await this.runtime.requireSession();
14978
+ const entityUid = chatSessionUid.trim();
14979
+ if (session.userId !== expectedOwnerUserId) throw new ArkmePluginError("login-context-changed", "登录账号已切换,请重试当前操作", false, 409);
14980
+ if (entityUid === "") throw new ArkmePluginError("chat-directory-preference-identity-invalid", "会话列表身份无效", false, 400);
14981
+ const cached = this.chatSourceCache.get(`${String(session.userId)}:${entityUid}`);
14982
+ return {
14983
+ ownerUserId: session.userId,
14984
+ ref: {
14985
+ entityKind: 1,
14986
+ entityUid
14987
+ },
14988
+ evidence: {
14989
+ sequence: cached?.latestSequence ?? 0,
14990
+ activityAtMillis: cached?.activeAtMillis ?? 0
14991
+ }
14992
+ };
14993
+ }
14994
+ async resolveChatPinTarget(source, session, signal) {
14995
+ if (source.sidebarSubjectUid !== void 0 && source.sidebarSubjectUid.trim() !== "") return { subjectUid: source.sidebarSubjectUid.trim() };
14996
+ const target = arkmeChatDirectoryMetadataFromBundle(await this.runtime.authenticatedChatPost("/api/v1/chats/detail", { chat_session_uid: source.ownerRef }, session, signal, {
14662
14997
  lane: "interactive-read",
14663
14998
  key: `chat-sidebar-target:${source.ownerRef}`,
14664
14999
  failureCooldownMs: 2e3
14665
15000
  }), source.kind === "group_chat" ? source.ownerRef : "");
14666
- if (target === void 0) throw new ArkmePluginError("chat-sidebar-target-unavailable", "未能定位该会话的跨端侧边栏数据,请刷新后重试", true, 502);
14667
- return target;
15001
+ if (target.subjectUid === void 0) throw new ArkmePluginError("chat-sidebar-target-unavailable", "未能定位该会话的跨端侧边栏数据,请刷新后重试", true, 502);
15002
+ return { subjectUid: target.subjectUid };
14668
15003
  }
14669
15004
  async listGroupSources(options = {}) {
14670
15005
  const session = await this.runtime.requireSession();
@@ -14685,7 +15020,7 @@ var SourceService = class {
14685
15020
  key: "directory:groups:count",
14686
15021
  failureCooldownMs: 2e3
14687
15022
  });
14688
- return Math.max(0, numberValue$25(data.total ?? data.total_count));
15023
+ return Math.max(0, numberValue$26(data.total ?? data.total_count));
14689
15024
  }
14690
15025
  async listSourcesUncached(session, directory, options, limit, sessionKind) {
14691
15026
  if (directory === "send_to_self") {
@@ -14721,12 +15056,12 @@ var SourceService = class {
14721
15056
  const childTopicUidsByParent = /* @__PURE__ */ new Map();
14722
15057
  for (const raw of listValue$23(hierarchyData?.relations)) {
14723
15058
  const relation = objectValue$1(raw);
14724
- if (numberValue$25(relation.rel_kind) !== 1 || numberValue$25(relation.status) !== 1) continue;
15059
+ if (numberValue$26(relation.rel_kind) !== 1 || numberValue$26(relation.status) !== 1) continue;
14725
15060
  const parentTopicUid = stringValue$1(relation.parent_topic_uid).trim();
14726
15061
  const childTopicUid = stringValue$1(relation.child_topic_uid).trim();
14727
15062
  if (parentTopicUid === "" || childTopicUid === "" || parentTopicUid === childTopicUid) continue;
14728
15063
  parentTopicUidByChild.set(childTopicUid, parentTopicUid);
14729
- siblingOrderByChild.set(childTopicUid, numberValue$25(relation.sibling_order));
15064
+ siblingOrderByChild.set(childTopicUid, numberValue$26(relation.sibling_order));
14730
15065
  const children = childTopicUidsByParent.get(parentTopicUid) ?? /* @__PURE__ */ new Set();
14731
15066
  children.add(childTopicUid);
14732
15067
  childTopicUidsByParent.set(parentTopicUid, children);
@@ -14736,7 +15071,7 @@ var SourceService = class {
14736
15071
  if (arkmePrivacyLockedTopic(item)) continue;
14737
15072
  const core = objectValue$1(item.topic_core);
14738
15073
  const status = core.status ?? item.status;
14739
- if (status !== void 0 && numberValue$25(status) !== 1) continue;
15074
+ if (status !== void 0 && numberValue$26(status) !== 1) continue;
14740
15075
  const summary = objectValue$1(item.summary);
14741
15076
  const latest = objectValue$1(item.latest_record_core);
14742
15077
  const parent = objectValue$1(core.parent_topic_core ?? core.parent_topic ?? item.parent_topic_core ?? item.parent_topic);
@@ -14748,12 +15083,12 @@ var SourceService = class {
14748
15083
  topicDescriptors.push({
14749
15084
  topicUid,
14750
15085
  ...parentTopicUid === "" || parentTopicUid === topicUid ? {} : { parentTopicUid },
14751
- siblingOrder: numberValue$25(siblingOrderByChild.get(topicUid) ?? core.sibling_order ?? item.sibling_order),
15086
+ siblingOrder: numberValue$26(siblingOrderByChild.get(topicUid) ?? core.sibling_order ?? item.sibling_order),
14752
15087
  title,
14753
15088
  latestPreview: arkmeChatConversationPreview(latest),
14754
- latestMessageAtMillis: numberValue$25(latest.send_at ?? summary.latest_send_at),
14755
- activeAtMillis: numberValue$25(latest.send_at ?? summary.latest_send_at ?? core.update_at),
14756
- recordCount: numberValue$25(summary.record_count)
15089
+ latestMessageAtMillis: numberValue$26(latest.send_at ?? summary.latest_send_at),
15090
+ activeAtMillis: numberValue$26(latest.send_at ?? summary.latest_send_at ?? core.update_at),
15091
+ recordCount: numberValue$26(summary.record_count)
14757
15092
  });
14758
15093
  }
14759
15094
  const sourceRefByTopicUid = /* @__PURE__ */ new Map();
@@ -14795,12 +15130,12 @@ var SourceService = class {
14795
15130
  unreadCount: 0
14796
15131
  };
14797
15132
  const nextPageCursor = objectValue$1(data.next_page_cursor ?? data.next_cursor);
14798
- const nextOffset = numberValue$25(data.next_offset);
15133
+ const nextOffset = numberValue$26(data.next_offset);
14799
15134
  const preferredPageCursor = Object.keys(nextPageCursor).length > 0 ? nextPageCursor : void 0;
14800
15135
  const preferredOffset = preferredPageCursor === void 0 && nextOffset > 0 ? nextOffset : void 0;
14801
15136
  const hasMore = data.has_more === true && (preferredPageCursor !== void 0 || preferredOffset !== void 0);
14802
15137
  const totalValue = data.total ?? data.total_count;
14803
- const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$25(totalValue));
15138
+ const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$26(totalValue));
14804
15139
  return {
14805
15140
  directory,
14806
15141
  items: [
@@ -14843,9 +15178,9 @@ var SourceService = class {
14843
15178
  const unread = objectValue$1(bundle.unread_snapshot);
14844
15179
  const currentPolicy = objectValue$1(bundle.current_policy);
14845
15180
  const attention = projectArkmeChatAttention(unread.unread_count, currentPolicy);
14846
- const isPinned = numberValue$25(currentPolicy.pin_state) === 2;
15181
+ const isPinned = numberValue$26(currentPolicy.pin_state) === 2;
14847
15182
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
14848
- const sessionKind = numberValue$25(chatSession.session_kind);
15183
+ const sessionKind = numberValue$26(chatSession.session_kind);
14849
15184
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
14850
15185
  if (uid === "" || kind === void 0) continue;
14851
15186
  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() || "未命名会话";
@@ -14856,12 +15191,12 @@ var SourceService = class {
14856
15191
  const backendMentionState = chatUnreadMentionState(unread);
14857
15192
  const hasUnreadMention = kind !== "group_chat" ? void 0 : unreadCount <= 0 ? false : backendMentionState === void 0 && !latestPreviewMentionsViewer ? void 0 : backendMentionState === true || latestPreviewMentionsViewer;
14858
15193
  const botGroupTarget = kind === "group_chat" ? arkmeGroupBotBindingTargetFromBundle(bundle) : void 0;
14859
- const sidebarTarget = arkmeChatSidebarTargetFromBundle(bundle, kind === "group_chat" ? uid : "");
14860
15194
  const cached = this.chatSourceCache.get(`${String(session.userId)}:${uid}`);
15195
+ const chatDirectoryMetadata = arkmeChatDirectoryMetadataFromBundle(bundle, kind === "group_chat" ? uid : "");
14861
15196
  const item = {
14862
15197
  sourceRef: await this.sealSourceRef(session.userId, kind, uid, displayName, {
14863
15198
  ...botGroupTarget === void 0 ? {} : { botGroupTarget },
14864
- ...sidebarTarget === void 0 ? {} : { sidebarTarget }
15199
+ chatDirectoryMetadata
14865
15200
  }),
14866
15201
  sourceKey: await this.chatDirectorySourceKey(session.userId, uid),
14867
15202
  kind,
@@ -14870,16 +15205,16 @@ var SourceService = class {
14870
15205
  ...kind === "group_chat" && cached?.avatarRefs !== void 0 ? { avatarRefs: [...cached.avatarRefs] } : {},
14871
15206
  ...kind === "group_chat" && cached?.groupAvatar !== void 0 ? { groupAvatar: cloneSourceItem(cached).groupAvatar } : {},
14872
15207
  ...preview === "" ? {} : { latestPreview: preview },
14873
- activeAtMillis: numberValue$25(bundle.sort_active_at ?? chatSession.last_active_at),
15208
+ activeAtMillis: arkmeChatSortActiveAt(bundle, chatSession),
14874
15209
  ...attention,
14875
15210
  ...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
14876
15211
  isPinned,
14877
- ...numberValue$25(unread.session_last_seq ?? chatSession.last_seq) > 0 ? { latestSequence: numberValue$25(unread.session_last_seq ?? chatSession.last_seq) } : {}
15212
+ ...numberValue$26(unread.session_last_seq ?? chatSession.last_seq) > 0 ? { latestSequence: numberValue$26(unread.session_last_seq ?? chatSession.last_seq) } : {}
14878
15213
  };
14879
15214
  const itemIndex = items.push(item) - 1;
14880
15215
  chatSessionUidByIndex.set(itemIndex, uid);
14881
15216
  if (kind === "private_chat") {
14882
- const counterpartUserId = numberValue$25(counterpart.user_id);
15217
+ const counterpartUserId = numberValue$26(counterpart.user_id);
14883
15218
  if (Number.isSafeInteger(counterpartUserId) && counterpartUserId > 0) {
14884
15219
  item.peerUserId = counterpartUserId;
14885
15220
  privateUserIdByIndex.set(itemIndex, counterpartUserId);
@@ -14897,7 +15232,7 @@ var SourceService = class {
14897
15232
  }
14898
15233
  const hasMore = data.has_more === true;
14899
15234
  const totalValue = data.total ?? data.total_count;
14900
- const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$25(totalValue));
15235
+ const total = totalValue === void 0 ? void 0 : Math.max(0, numberValue$26(totalValue));
14901
15236
  const nextPageCursor = objectValue$1(data.next_page_cursor);
14902
15237
  return {
14903
15238
  directory,
@@ -14964,11 +15299,11 @@ var SourceService = class {
14964
15299
  const uid = stringValue$1(snapshot.chat_session_uid).trim();
14965
15300
  const index = indexByGroupUid.get(uid);
14966
15301
  if (index === void 0 || !groupUids.includes(uid)) continue;
14967
- const memberIds = listValue$23(snapshot.members).map((member) => numberValue$25(objectValue$1(member).user_id)).filter((userId) => Number.isSafeInteger(userId) && userId > 0).slice(0, 5);
15302
+ const memberIds = listValue$23(snapshot.members).map((member) => numberValue$26(objectValue$1(member).user_id)).filter((userId) => Number.isSafeInteger(userId) && userId > 0).slice(0, 5);
14968
15303
  const projection = {
14969
- memberCount: Math.max(0, Math.trunc(numberValue$25(snapshot.member_count))),
15304
+ memberCount: Math.max(0, Math.trunc(numberValue$26(snapshot.member_count))),
14970
15305
  strategy: stringValue$1(snapshot.strategy).trim(),
14971
- computedAtMillis: numberValue$25(snapshot.computed_at),
15306
+ computedAtMillis: numberValue$26(snapshot.computed_at),
14972
15307
  memberIds
14973
15308
  };
14974
15309
  snapshotsByUid.set(uid, projection);
@@ -15035,12 +15370,15 @@ var SourceService = class {
15035
15370
  async chatDirectorySourceKey(userId, chatSessionUid) {
15036
15371
  return `arkme-chat-source-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`chat-source-key-v1:${String(userId)}:${chatSessionUid.trim()}`).digest("base64url")}`;
15037
15372
  }
15373
+ async chatTimelineItemKey(userId, chatSessionUid, relationUid) {
15374
+ return `arkme-chat-timeline-item-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`chat-timeline-item-key-v1:${String(userId)}:${chatSessionUid.trim()}:${relationUid.trim()}`).digest("base64url")}`;
15375
+ }
15038
15376
  async topicHierarchyKey(userId, topicUid) {
15039
15377
  return `arkme-topic-hierarchy-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`topic-hierarchy-key-v1:${String(userId)}:${topicUid.trim()}`).digest("base64url")}`;
15040
15378
  }
15041
15379
  async sealSourceRef(userId, kind, ownerRef, displayName, options = {}) {
15042
15380
  const botGroupTarget = kind === "group_chat" ? sanitizeGroupBotBindingTarget(options.botGroupTarget) : void 0;
15043
- const sidebarTarget = kind === "private_chat" || kind === "group_chat" ? options.sidebarTarget : void 0;
15381
+ const chatDirectoryMetadata = kind === "private_chat" || kind === "group_chat" ? options.chatDirectoryMetadata : void 0;
15044
15382
  const payload = encodeOpaqueJson$7({
15045
15383
  version: 1,
15046
15384
  userId,
@@ -15048,9 +15386,10 @@ var SourceService = class {
15048
15386
  ownerRef,
15049
15387
  displayName,
15050
15388
  ...botGroupTarget === void 0 ? {} : { botGroupTarget },
15051
- ...sidebarTarget === void 0 ? {} : {
15052
- sidebarSubjectUid: sidebarTarget.subjectUid,
15053
- sidebarHiddenAnchorTimestamp: sidebarTarget.hiddenAnchorTimestamp
15389
+ ...chatDirectoryMetadata === void 0 ? {} : {
15390
+ ...chatDirectoryMetadata.subjectUid === void 0 ? {} : { sidebarSubjectUid: chatDirectoryMetadata.subjectUid },
15391
+ conversationListActivityAtMillis: chatDirectoryMetadata.activityAtMillis,
15392
+ conversationListLatestSequence: chatDirectoryMetadata.latestSequence
15054
15393
  }
15055
15394
  });
15056
15395
  return `arkme-source-v1.${payload}.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(payload).digest("base64url")}`;
@@ -15071,7 +15410,7 @@ var SourceService = class {
15071
15410
  const kind = parsed.kind;
15072
15411
  const result = {
15073
15412
  version: 1,
15074
- userId: numberValue$25(parsed.userId),
15413
+ userId: numberValue$26(parsed.userId),
15075
15414
  kind: isSourceKind(kind) ? kind : "default_category",
15076
15415
  ownerRef: stringValue$1(parsed.ownerRef).trim(),
15077
15416
  displayName: isSourceKind(kind) && kind === "default_category" ? "未分类" : stringValue$1(parsed.displayName).trim(),
@@ -15081,10 +15420,10 @@ var SourceService = class {
15081
15420
  })() : {},
15082
15421
  ...kind === "private_chat" || kind === "group_chat" ? (() => {
15083
15422
  const sidebarSubjectUid = stringValue$1(parsed.sidebarSubjectUid).trim();
15084
- const sidebarHiddenAnchorTimestamp = Math.max(0, numberValue$25(parsed.sidebarHiddenAnchorTimestamp));
15085
- return sidebarSubjectUid === "" ? {} : {
15086
- sidebarSubjectUid,
15087
- sidebarHiddenAnchorTimestamp
15423
+ return {
15424
+ ...sidebarSubjectUid === "" ? {} : { sidebarSubjectUid },
15425
+ conversationListActivityAtMillis: Math.max(0, numberValue$26(parsed.conversationListActivityAtMillis)),
15426
+ conversationListLatestSequence: Math.max(0, numberValue$26(parsed.conversationListLatestSequence))
15088
15427
  };
15089
15428
  })() : {}
15090
15429
  };
@@ -15096,16 +15435,18 @@ var SourceService = class {
15096
15435
  return {
15097
15436
  sourceRef: await this.sealSourceRef(source.userId, source.kind, source.ownerRef, source.displayName, {
15098
15437
  ...source.botGroupTarget === void 0 ? {} : { botGroupTarget: source.botGroupTarget },
15099
- ...source.sidebarSubjectUid === void 0 ? {} : { sidebarTarget: {
15100
- subjectUid: source.sidebarSubjectUid,
15101
- hiddenAnchorTimestamp: source.sidebarHiddenAnchorTimestamp ?? 0
15102
- } }
15438
+ chatDirectoryMetadata: {
15439
+ ...source.sidebarSubjectUid === void 0 ? {} : { subjectUid: source.sidebarSubjectUid },
15440
+ activityAtMillis: source.conversationListActivityAtMillis ?? 0,
15441
+ latestSequence: source.conversationListLatestSequence ?? 0
15442
+ }
15103
15443
  }),
15104
15444
  ...sourceKey === void 0 ? {} : { sourceKey },
15105
15445
  kind: source.kind,
15106
15446
  displayName: source.displayName,
15107
- activeAtMillis: 0,
15108
- unreadCount: 0
15447
+ activeAtMillis: source.conversationListActivityAtMillis ?? 0,
15448
+ unreadCount: 0,
15449
+ ...(source.conversationListLatestSequence ?? 0) > 0 ? { latestSequence: source.conversationListLatestSequence } : {}
15109
15450
  };
15110
15451
  }
15111
15452
  async chatSourceFromBundle(bundle, session, cached, timelineItems) {
@@ -15115,25 +15456,25 @@ var SourceService = class {
15115
15456
  const unread = objectValue$1(bundle.unread_snapshot);
15116
15457
  const currentPolicy = objectValue$1(bundle.current_policy);
15117
15458
  const attention = projectArkmeChatAttention(unread.unread_count, currentPolicy, cached?.isMuted ?? false);
15118
- const isPinned = numberValue$25(currentPolicy.pin_state) === 2;
15459
+ const isPinned = numberValue$26(currentPolicy.pin_state) === 2;
15119
15460
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
15120
- const sessionKind = numberValue$25(chatSession.session_kind);
15461
+ const sessionKind = numberValue$26(chatSession.session_kind);
15121
15462
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
15122
15463
  if (uid === "" || kind === void 0) throw new Error("invalid chat display snapshot");
15123
15464
  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 || "未命名会话";
15124
15465
  const latestItem = [...timelineItems].sort((left, right) => (right.sequence ?? 0) - (left.sequence ?? 0))[0];
15125
15466
  const latestPreview = latestItem === void 0 ? cached?.latestPreview : latestItem.forwardRecords === void 0 ? arkmeTimelineConversationPreview(latestItem) : `[转发] ${latestItem.forwardRecords.title}`;
15126
- const latestSequence = Math.max(numberValue$25(unread.session_last_seq ?? chatSession.last_seq), latestItem?.sequence ?? 0, cached?.latestSequence ?? 0);
15467
+ const latestSequence = Math.max(numberValue$26(unread.session_last_seq ?? chatSession.last_seq), latestItem?.sequence ?? 0, cached?.latestSequence ?? 0);
15127
15468
  const unreadCount = attention.unreadCount;
15128
15469
  const latestMentionsViewer = latestItem?.isMe === false && latestItem.mentionsViewer === true;
15129
15470
  const backendMentionState = chatUnreadMentionState(unread);
15130
15471
  const hasUnreadMention = kind !== "group_chat" ? void 0 : unreadCount <= 0 ? false : backendMentionState === void 0 && !latestMentionsViewer ? cached?.hasUnreadMention : backendMentionState === true || latestMentionsViewer;
15131
15472
  const botGroupTarget = kind === "group_chat" ? arkmeGroupBotBindingTargetFromBundle(bundle) : void 0;
15132
- const sidebarTarget = arkmeChatSidebarTargetFromBundle(bundle, kind === "group_chat" ? uid : "");
15473
+ const chatDirectoryMetadata = arkmeChatDirectoryMetadataFromBundle(bundle, kind === "group_chat" ? uid : "");
15133
15474
  return {
15134
15475
  sourceRef: await this.sealSourceRef(session.userId, kind, uid, displayName, {
15135
15476
  ...botGroupTarget === void 0 ? {} : { botGroupTarget },
15136
- ...sidebarTarget === void 0 ? {} : { sidebarTarget }
15477
+ chatDirectoryMetadata
15137
15478
  }),
15138
15479
  sourceKey: await this.chatDirectorySourceKey(session.userId, uid),
15139
15480
  kind,
@@ -15142,7 +15483,7 @@ var SourceService = class {
15142
15483
  ...cached?.avatarRefs === void 0 ? {} : { avatarRefs: cached.avatarRefs },
15143
15484
  ...cached?.groupAvatar === void 0 ? {} : { groupAvatar: cached.groupAvatar },
15144
15485
  ...latestPreview === void 0 || latestPreview === "" ? {} : { latestPreview },
15145
- activeAtMillis: Math.max(numberValue$25(bundle.sort_active_at ?? chatSession.last_active_at), latestItem?.sendAtMillis ?? 0),
15486
+ activeAtMillis: Math.max(arkmeChatSortActiveAt(bundle, chatSession), latestItem?.sendAtMillis ?? 0),
15146
15487
  ...attention,
15147
15488
  ...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
15148
15489
  isPinned,
@@ -15173,7 +15514,7 @@ var SourceService = class {
15173
15514
  const value = this.decodeCursor(cursor);
15174
15515
  if (value.directory !== "send_to_self") throw new ArkmePluginError("source-cursor-invalid", "发给自己的主题分页游标无效", false);
15175
15516
  const pageCursor = objectValue$1(value.pageCursor);
15176
- const offset = numberValue$25(value.offset);
15517
+ const offset = numberValue$26(value.offset);
15177
15518
  if (Object.keys(pageCursor).length === 0 && offset <= 0) throw new ArkmePluginError("source-cursor-invalid", "发给自己的主题分页游标无效", false);
15178
15519
  return {
15179
15520
  ...Object.keys(pageCursor).length === 0 ? {} : { pageCursor },
@@ -15191,7 +15532,7 @@ const BOT_REF_TTL_MILLIS = 30 * 6e4;
15191
15532
  const BOT_REF_CAP = 2e3;
15192
15533
  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;
15193
15534
  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;
15194
- function numberValue$24(value) {
15535
+ function numberValue$25(value) {
15195
15536
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
15196
15537
  }
15197
15538
  function booleanValue$11(value) {
@@ -15205,7 +15546,7 @@ function arkmeNormalizeBotProvider(value) {
15205
15546
  if (normalized === "openclaw" || normalized === "webhook") return normalized;
15206
15547
  }
15207
15548
  function botPrivateChatTimestamp(value) {
15208
- const numeric = numberValue$24(value);
15549
+ const numeric = numberValue$25(value);
15209
15550
  if (numeric <= 0) return 0;
15210
15551
  if (numeric < 1e10) return numeric * 1e3;
15211
15552
  let millis = numeric;
@@ -15324,7 +15665,7 @@ var BotService = class {
15324
15665
  async countBots(options = {}) {
15325
15666
  const session = await this.runtime.requireSession();
15326
15667
  const data = await this.runtime.authenticatedBotPost("/api/v1/bot/list", { limit: 0 }, session, options.signal);
15327
- return Math.max(0, numberValue$24(data.total ?? data.total_count));
15668
+ return Math.max(0, numberValue$25(data.total ?? data.total_count));
15328
15669
  }
15329
15670
  async createBot(input, options = {}) {
15330
15671
  const name = input.name.trim();
@@ -15545,14 +15886,18 @@ var BotService = class {
15545
15886
  const rawStatus = stringValue$1(raw.status).trim();
15546
15887
  const status = rawStatus === "online" || rawStatus === "offline" ? rawStatus : "unknown";
15547
15888
  const createdAtMillis = botPrivateChatTimestamp(raw.created_at ?? raw.createdAt);
15889
+ const conversationListActivityAtMillis = botPrivateChatTimestamp(raw.latest_activity_at ?? raw.latestActivityAt);
15890
+ const latestMessageAtMillis = botPrivateChatTimestamp(raw.latest_message_at ?? raw.latestMessageAt ?? raw.last_message_at ?? raw.lastMessageAt);
15548
15891
  return {
15549
- botRef: this.sealBotRef(userId, botId, provider, target),
15892
+ botRef: this.sealBotRef(userId, botId, provider, target, Math.max(createdAtMillis, conversationListActivityAtMillis, latestMessageAtMillis)),
15550
15893
  directoryKey: await this.botDirectoryKey(userId, botId),
15551
15894
  name,
15552
15895
  provider,
15553
15896
  description: stringValue$1(raw.description).trim(),
15554
15897
  status,
15555
15898
  ...createdAtMillis === 0 ? {} : { createdAtMillis },
15899
+ ...conversationListActivityAtMillis === 0 ? {} : { conversationListActivityAtMillis },
15900
+ ...latestMessageAtMillis === 0 ? {} : { latestMessageAtMillis },
15556
15901
  ...this.botAvatarProjection(raw, userId, botId)
15557
15902
  };
15558
15903
  }
@@ -15584,7 +15929,7 @@ var BotService = class {
15584
15929
  tokenRevealEnabled: data.token_reveal_enabled !== false,
15585
15930
  gatewayUrl: stringValue$1(data.gateway_url).trim(),
15586
15931
  webhookUrl: stringValue$1(data.webhook_url).trim(),
15587
- recordCount: Math.max(-1, Math.trunc(numberValue$24(raw.record_count))),
15932
+ recordCount: Math.max(-1, Math.trunc(numberValue$25(raw.record_count))),
15588
15933
  webhookSecurity: botWebhookSecurity(data.webhook_security ?? raw.webhook_security),
15589
15934
  joinedGroups
15590
15935
  };
@@ -15644,7 +15989,7 @@ var BotService = class {
15644
15989
  expiresAtMillis: entry.expiresAtMillis
15645
15990
  };
15646
15991
  }
15647
- sealBotRef(userId, botId, provider, target) {
15992
+ sealBotRef(userId, botId, provider, target, conversationListActivityAtMillis = 0) {
15648
15993
  this.pruneBotRefs();
15649
15994
  const key = `${String(userId)}\u0000${provider}\u0000${botId}`;
15650
15995
  const existingRef = this.botRefByKey.get(key);
@@ -15653,6 +15998,7 @@ var BotService = class {
15653
15998
  this.botRefs.set(existingRef, {
15654
15999
  ...existing,
15655
16000
  ...target === void 0 ? {} : { target },
16001
+ conversationListActivityAtMillis: Math.max(existing.conversationListActivityAtMillis, conversationListActivityAtMillis),
15656
16002
  expiresAtMillis: this.now() + (this.refOptions.ttlMillis ?? BOT_REF_TTL_MILLIS)
15657
16003
  });
15658
16004
  return existingRef;
@@ -15667,6 +16013,7 @@ var BotService = class {
15667
16013
  kind: "unavailable",
15668
16014
  reason: "missing"
15669
16015
  },
16016
+ conversationListActivityAtMillis: Math.max(0, conversationListActivityAtMillis),
15670
16017
  key,
15671
16018
  expiresAtMillis: this.now() + (this.refOptions.ttlMillis ?? BOT_REF_TTL_MILLIS)
15672
16019
  });
@@ -15690,6 +16037,23 @@ var BotService = class {
15690
16037
  const { expiresAtMillis: _expiresAtMillis, key: _key, ...reference } = entry;
15691
16038
  return { ...reference };
15692
16039
  }
16040
+ async botConversationListPreferenceEntry(botRef) {
16041
+ const session = await this.runtime.requireSession();
16042
+ const bot = await this.openBotRef(botRef, session.userId);
16043
+ if (bot.target.kind === "chat") return await this.source.chatConversationListPreferenceEntryBySessionUid(bot.target.chatSessionUid, session.userId);
16044
+ if (bot.target.kind === "unavailable" && bot.target.reason !== "missing") throw new ArkmePluginError("bot-conversation-preference-identity-unavailable", "当前 Bot 会话归属不明确,请刷新后重试", false, 409);
16045
+ return {
16046
+ ownerUserId: session.userId,
16047
+ ref: {
16048
+ entityKind: 2,
16049
+ entityUid: bot.botId
16050
+ },
16051
+ evidence: {
16052
+ sequence: 0,
16053
+ activityAtMillis: bot.conversationListActivityAtMillis
16054
+ }
16055
+ };
16056
+ }
15693
16057
  pruneBotRefs() {
15694
16058
  const now = this.now();
15695
16059
  for (const [ref, entry] of this.botRefs) if (entry.expiresAtMillis <= now) this.deleteBotRef(ref, entry);
@@ -15711,14 +16075,14 @@ var BotService = class {
15711
16075
  };
15712
16076
  //#endregion
15713
16077
  //#region src/services/bot-conversation-service.ts
15714
- function numberValue$23(value) {
16078
+ function numberValue$24(value) {
15715
16079
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
15716
16080
  }
15717
16081
  function listValue$21(value) {
15718
16082
  return Array.isArray(value) ? value : [];
15719
16083
  }
15720
16084
  function botConversationTimestamp(value) {
15721
- const numeric = numberValue$23(value);
16085
+ const numeric = numberValue$24(value);
15722
16086
  if (numeric <= 0) return 0;
15723
16087
  if (numeric < 1e10) return numeric * 1e3;
15724
16088
  let millis = numeric;
@@ -15736,11 +16100,11 @@ function subjectAttachments(value) {
15736
16100
  kind: kind || "file",
15737
16101
  fileName,
15738
16102
  mimeType,
15739
- size: Math.max(0, Math.trunc(numberValue$23(raw.size))),
15740
- durationMillis: Math.max(0, Math.trunc(numberValue$23(raw.duration_ms ?? raw.durationMillis))),
15741
- width: Math.max(0, Math.trunc(numberValue$23(raw.width))),
15742
- height: Math.max(0, Math.trunc(numberValue$23(raw.height))),
15743
- sortOrder: Math.max(0, Math.trunc(numberValue$23(raw.order ?? raw.sort_order ?? raw.sortOrder)))
16103
+ size: Math.max(0, Math.trunc(numberValue$24(raw.size))),
16104
+ durationMillis: Math.max(0, Math.trunc(numberValue$24(raw.duration_ms ?? raw.durationMillis))),
16105
+ width: Math.max(0, Math.trunc(numberValue$24(raw.width))),
16106
+ height: Math.max(0, Math.trunc(numberValue$24(raw.height))),
16107
+ sortOrder: Math.max(0, Math.trunc(numberValue$24(raw.order ?? raw.sort_order ?? raw.sortOrder)))
15744
16108
  }];
15745
16109
  });
15746
16110
  }
@@ -15984,7 +16348,7 @@ var BotConversationService = class {
15984
16348
  //#region src/services/calendar-service.ts
15985
16349
  const MAX_CALENDAR_RANGE_DAYS = 62;
15986
16350
  const MAX_DAY_RECORD_LIMIT = 50;
15987
- function numberValue$22(value) {
16351
+ function numberValue$23(value) {
15988
16352
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
15989
16353
  }
15990
16354
  function booleanValue$10(value) {
@@ -16024,7 +16388,7 @@ function boundedLimit$2(value) {
16024
16388
  return limit;
16025
16389
  }
16026
16390
  function contentAccessState(value) {
16027
- const code = Math.trunc(numberValue$22(value));
16391
+ const code = Math.trunc(numberValue$23(value));
16028
16392
  if (code === 1) return "available";
16029
16393
  if (code === 2) return "protected";
16030
16394
  return "unknown";
@@ -16097,7 +16461,7 @@ var CalendarService = class {
16097
16461
  cacheMs: 1e4,
16098
16462
  failureCooldownMs: 2e3
16099
16463
  });
16100
- const nextSendAt = Math.trunc(numberValue$22(data.next_cursor_send_at));
16464
+ const nextSendAt = Math.trunc(numberValue$23(data.next_cursor_send_at));
16101
16465
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
16102
16466
  return {
16103
16467
  scope: "self",
@@ -16116,21 +16480,21 @@ var CalendarService = class {
16116
16480
  const item = objectValue$1(raw);
16117
16481
  const bucketDate = stringValue$1(item.bucket_date).trim();
16118
16482
  if (bucketDate === "") return void 0;
16119
- const protectedCount = Math.max(0, Math.trunc(numberValue$22(item.protected_count)));
16120
- const count = Math.max(0, Math.trunc(numberValue$22(item.count)) - protectedCount);
16483
+ const protectedCount = Math.max(0, Math.trunc(numberValue$23(item.protected_count)));
16484
+ const count = Math.max(0, Math.trunc(numberValue$23(item.count)) - protectedCount);
16121
16485
  return {
16122
16486
  bucketDate,
16123
16487
  count,
16124
16488
  protectedCount: 0,
16125
16489
  hasRecords: count > 0,
16126
- ...numberValue$22(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$22(item.first_send_at)) } : {}
16490
+ ...numberValue$23(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$23(item.first_send_at)) } : {}
16127
16491
  };
16128
16492
  }
16129
16493
  dayRecord(raw) {
16130
16494
  const item = objectValue$1(raw);
16131
16495
  const core = objectValue$1(item.record_core);
16132
16496
  const recordUid = stringValue$1(item.record_uid ?? core.record_uid).trim();
16133
- const sendAtMillis = Math.trunc(numberValue$22(item.send_at ?? core.send_at));
16497
+ const sendAtMillis = Math.trunc(numberValue$23(item.send_at ?? core.send_at));
16134
16498
  if (recordUid === "" || sendAtMillis <= 0) return void 0;
16135
16499
  const accessState = contentAccessState(core.content_access_state);
16136
16500
  if (accessState === "protected" || arkmePrivacyLockedRecord(item)) return void 0;
@@ -16151,9 +16515,9 @@ var CalendarService = class {
16151
16515
  preview,
16152
16516
  ...stringValue$1(topic.title).trim() === "" ? {} : { topicTitle: stringValue$1(topic.title).trim() },
16153
16517
  sourceKind: sourceKind(item),
16154
- creationSource: Math.trunc(numberValue$22(core.creation_source)),
16155
- templateKind: Math.trunc(numberValue$22(core.template_kind)),
16156
- displayKind: Math.trunc(numberValue$22(core.display_kind)),
16518
+ creationSource: Math.trunc(numberValue$23(core.creation_source)),
16519
+ templateKind: Math.trunc(numberValue$23(core.template_kind)),
16520
+ displayKind: Math.trunc(numberValue$23(core.display_kind)),
16157
16521
  protected: false,
16158
16522
  ...isUncategorized === void 0 ? {} : { isUncategorized },
16159
16523
  ...hasManualEdit === void 0 ? {} : { hasManualEdit },
@@ -16166,7 +16530,7 @@ var CalendarService = class {
16166
16530
  function listValue$19(value) {
16167
16531
  return Array.isArray(value) ? value : [];
16168
16532
  }
16169
- function numberValue$21(value) {
16533
+ function numberValue$22(value) {
16170
16534
  if (typeof value === "number" && Number.isFinite(value)) return value;
16171
16535
  if (typeof value === "string" && value.trim() !== "") {
16172
16536
  const parsed = Number(value.trim());
@@ -16189,13 +16553,13 @@ function firstString$1(source, keys) {
16189
16553
  }
16190
16554
  function firstNumber(source, keys) {
16191
16555
  for (const key of keys) {
16192
- const value = numberValue$21(source[key]);
16556
+ const value = numberValue$22(source[key]);
16193
16557
  if (value !== 0) return value;
16194
16558
  }
16195
16559
  return 0;
16196
16560
  }
16197
16561
  function epochMillis$1(value) {
16198
- const raw = numberValue$21(value);
16562
+ const raw = numberValue$22(value);
16199
16563
  if (raw <= 0) return 0;
16200
16564
  return raw < 1e10 ? Math.trunc(raw * 1e3) : Math.trunc(raw);
16201
16565
  }
@@ -16207,7 +16571,7 @@ function firstEpochMillis(source, keys) {
16207
16571
  return 0;
16208
16572
  }
16209
16573
  function numberList(value) {
16210
- return listValue$19(value).map((item) => Math.trunc(numberValue$21(item))).filter((item) => Number.isSafeInteger(item) && item > 0);
16574
+ return listValue$19(value).map((item) => Math.trunc(numberValue$22(item))).filter((item) => Number.isSafeInteger(item) && item > 0);
16211
16575
  }
16212
16576
  function callMediaType(value) {
16213
16577
  if (value === 0 || value === "0") return "audio";
@@ -16972,11 +17336,11 @@ var CallHistoryService = class {
16972
17336
  }
16973
17337
  const value = objectValue$1(payload);
16974
17338
  const parsed = {
16975
- version: numberValue$21(value.version),
16976
- userId: numberValue$21(value.userId),
17339
+ version: numberValue$22(value.version),
17340
+ userId: numberValue$22(value.userId),
16977
17341
  roomId: stringValue$1(value.roomId).trim(),
16978
17342
  stableId: stringValue$1(value.stableId).trim(),
16979
- issuedAtMillis: numberValue$21(value.issuedAtMillis)
17343
+ issuedAtMillis: numberValue$22(value.issuedAtMillis)
16980
17344
  };
16981
17345
  if (parsed.version !== 1 || parsed.userId !== expectedUserId || parsed.roomId === "" || parsed.stableId === "") throw new ArkmePluginError("call-ref-invalid", "通话记录引用无效,请重新查询通话记录", false, 400);
16982
17346
  return parsed;
@@ -17099,6 +17463,49 @@ function decodeArkmeChatReadCursorAdvancedDataLine(line) {
17099
17463
  eventAtMillis
17100
17464
  };
17101
17465
  }
17466
+ const TIMELINE_CHANGED_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
17467
+ "t",
17468
+ "event_uid",
17469
+ "chat_session_uid",
17470
+ "rel_uid",
17471
+ "latest_seq",
17472
+ "actor_user_id",
17473
+ "change_kind",
17474
+ "change_version",
17475
+ "relation_terminal",
17476
+ "event_at",
17477
+ "source_client_id"
17478
+ ]);
17479
+ /** Decode one metadata-only timeline invalidation. Record content remains owned by Chat reads. */
17480
+ function decodeArkmeChatTimelineChangedDataLine(line) {
17481
+ const source = decodeDataLine(line);
17482
+ if (source === void 0 || positiveInteger(source.t) !== 20) return void 0;
17483
+ if (Object.keys(source).some((field) => !TIMELINE_CHANGED_ALLOWED_FIELDS.has(field))) return void 0;
17484
+ const eventUid = nonEmptyString(source.event_uid);
17485
+ const chatSessionUid = nonEmptyString(source.chat_session_uid);
17486
+ const relationUid = nonEmptyString(source.rel_uid);
17487
+ const latestSequence = positiveInteger(source.latest_seq);
17488
+ const actorUserId = positiveInteger(source.actor_user_id);
17489
+ const changeVersion = positiveInteger(source.change_version);
17490
+ const relationTerminal = source.relation_terminal === void 0 ? false : typeof source.relation_terminal === "boolean" ? source.relation_terminal : void 0;
17491
+ const eventAtMillis = positiveInteger(source.event_at);
17492
+ const sourceClientId = source.source_client_id === void 0 ? 0 : nonNegativeInteger(source.source_client_id);
17493
+ const changeKindValue = positiveInteger(source.change_kind);
17494
+ const changeKind = changeKindValue === 1 ? "deleted" : changeKindValue === 2 ? "recovered" : changeKindValue === 3 ? "reedited" : changeKindValue === 4 ? "extended" : void 0;
17495
+ if (eventUid === void 0 || chatSessionUid === void 0 || relationUid === void 0 || latestSequence === void 0 || actorUserId === void 0 || changeKind === void 0 || changeVersion === void 0 || relationTerminal === void 0 || eventAtMillis === void 0 || sourceClientId === void 0) return void 0;
17496
+ if (relationTerminal && changeKind !== "deleted") return void 0;
17497
+ return {
17498
+ eventUid,
17499
+ chatSessionUid,
17500
+ relationUid,
17501
+ latestSequence,
17502
+ actorUserId,
17503
+ changeKind,
17504
+ changeVersion,
17505
+ relationTerminal,
17506
+ eventAtMillis
17507
+ };
17508
+ }
17102
17509
  const PROJECTION_NAME_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/;
17103
17510
  const PROJECTION_INVALIDATION_FORBIDDEN_FIELDS = [
17104
17511
  "receiver_user_ids",
@@ -17125,6 +17532,58 @@ function decodeArkmeProjectionInvalidatedDataLine(line) {
17125
17532
  eventAtMillis
17126
17533
  };
17127
17534
  }
17535
+ const CONVERSATION_LIST_PREFERENCE_ALLOWED_ITEM_FIELDS = /* @__PURE__ */ new Set([
17536
+ "entity_kind",
17537
+ "entity_uid",
17538
+ "revision"
17539
+ ]);
17540
+ const CONVERSATION_LIST_PREFERENCE_FORBIDDEN_FIELDS = [
17541
+ "visibility_state",
17542
+ "dismissed_through_seq",
17543
+ "dismissed_through_activity_at",
17544
+ "chat_session_uid",
17545
+ "pin_state",
17546
+ "unread_count",
17547
+ "payload",
17548
+ "receiver_user_ids"
17549
+ ];
17550
+ /** Decode a metadata-only invalidation. The Browser must query the Chat owner for current state. */
17551
+ function decodeArkmeConversationListPreferenceUpdatedDataLine(line) {
17552
+ const source = decodeDataLine(line);
17553
+ if (source === void 0 || positiveInteger(source.t) !== 26 || CONVERSATION_LIST_PREFERENCE_FORBIDDEN_FIELDS.some((field) => Object.hasOwn(source, field))) return void 0;
17554
+ const eventUid = nonEmptyString(source.event_uid);
17555
+ const userId = positiveInteger(source.user_id);
17556
+ const acceptedAtMillis = positiveInteger(source.accepted_at);
17557
+ const sourceClientId = source.source_client_id === void 0 ? 0 : nonNegativeInteger(source.source_client_id);
17558
+ const rawItems = Array.isArray(source.items) ? source.items : [];
17559
+ if (eventUid === void 0 || userId === void 0 || acceptedAtMillis === void 0 || sourceClientId === void 0 || rawItems.length === 0 || rawItems.length > 200) return void 0;
17560
+ const seen = /* @__PURE__ */ new Set();
17561
+ const items = [];
17562
+ for (const value of rawItems) {
17563
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
17564
+ const item = value;
17565
+ if (Object.keys(item).some((field) => !CONVERSATION_LIST_PREFERENCE_ALLOWED_ITEM_FIELDS.has(field))) return void 0;
17566
+ const entityKind = positiveInteger(item.entity_kind);
17567
+ const entityUid = typeof item.entity_uid === "string" && item.entity_uid !== "" && item.entity_uid.trim() === item.entity_uid ? item.entity_uid : void 0;
17568
+ const revision = positiveInteger(item.revision);
17569
+ if (entityKind !== 1 && entityKind !== 2 || entityUid === void 0 || revision === void 0 || Buffer.byteLength(entityUid, "utf8") > 128) return void 0;
17570
+ const identity = `${String(entityKind)}:${String(Buffer.byteLength(entityUid, "utf8"))}:${entityUid}`;
17571
+ if (seen.has(identity)) return void 0;
17572
+ seen.add(identity);
17573
+ items.push({
17574
+ entityKind,
17575
+ entityUid,
17576
+ revision
17577
+ });
17578
+ }
17579
+ return {
17580
+ eventUid,
17581
+ userId,
17582
+ items,
17583
+ acceptedAtMillis,
17584
+ sourceClientId
17585
+ };
17586
+ }
17128
17587
  function waitFor(milliseconds, signal) {
17129
17588
  if (signal.aborted) return Promise.resolve();
17130
17589
  return new Promise((resolve) => {
@@ -17404,28 +17863,36 @@ var ArkmeChatRealtimeRuntime = class {
17404
17863
  }
17405
17864
  }
17406
17865
  acceptLine(line) {
17407
- const projectionInvalidation = decodeArkmeProjectionInvalidatedDataLine(line);
17408
- const readCursorAdvanced = projectionInvalidation === void 0 ? decodeArkmeChatReadCursorAdvancedDataLine(line) : void 0;
17409
- const hint = projectionInvalidation === void 0 && readCursorAdvanced === void 0 ? decodeArkmeChatReceiveDataLine(line) : void 0;
17410
- const eventUid = projectionInvalidation?.eventUid ?? readCursorAdvanced?.eventUid ?? hint?.eventUid;
17866
+ const conversationListPreferenceUpdated = decodeArkmeConversationListPreferenceUpdatedDataLine(line);
17867
+ const projectionInvalidation = conversationListPreferenceUpdated === void 0 ? decodeArkmeProjectionInvalidatedDataLine(line) : void 0;
17868
+ const readCursorAdvanced = conversationListPreferenceUpdated === void 0 && projectionInvalidation === void 0 ? decodeArkmeChatReadCursorAdvancedDataLine(line) : void 0;
17869
+ const timelineChanged = conversationListPreferenceUpdated === void 0 && projectionInvalidation === void 0 && readCursorAdvanced === void 0 ? decodeArkmeChatTimelineChangedDataLine(line) : void 0;
17870
+ const hint = conversationListPreferenceUpdated === void 0 && projectionInvalidation === void 0 && readCursorAdvanced === void 0 && timelineChanged === void 0 ? decodeArkmeChatReceiveDataLine(line) : void 0;
17871
+ const eventUid = conversationListPreferenceUpdated?.eventUid ?? projectionInvalidation?.eventUid ?? readCursorAdvanced?.eventUid ?? timelineChanged?.eventUid ?? hint?.eventUid;
17411
17872
  if (eventUid === void 0 || this.seenEventUids.has(eventUid)) return;
17412
17873
  this.seenEventUids.add(eventUid);
17413
17874
  if (this.seenEventUids.size > MAX_SEEN_EVENTS) {
17414
17875
  const oldest = this.seenEventUids.values().next().value;
17415
17876
  if (oldest !== void 0) this.seenEventUids.delete(oldest);
17416
17877
  }
17417
- this.lastEventAtMillis = projectionInvalidation?.eventAtMillis ?? readCursorAdvanced?.eventAtMillis ?? hint?.eventAtMillis;
17418
- if (projectionInvalidation !== void 0) this.advanceRevision("projection-invalidation", void 0, void 0, projectionInvalidation);
17419
- else this.advanceRevision("chat-hint", hint, readCursorAdvanced);
17878
+ this.lastEventAtMillis = conversationListPreferenceUpdated?.acceptedAtMillis ?? projectionInvalidation?.eventAtMillis ?? readCursorAdvanced?.eventAtMillis ?? timelineChanged?.eventAtMillis ?? hint?.eventAtMillis;
17879
+ if (conversationListPreferenceUpdated !== void 0) this.advanceRevision("conversation-list-preference-invalidation", { conversationListPreferenceUpdated });
17880
+ else if (projectionInvalidation !== void 0) this.advanceRevision("projection-invalidation", { projectionInvalidation });
17881
+ else if (timelineChanged !== void 0) this.advanceRevision("chat-hint", { timelineChanged });
17882
+ else if (readCursorAdvanced !== void 0) this.advanceRevision("chat-hint", { readCursorAdvanced });
17883
+ else if (hint !== void 0) this.advanceRevision("chat-hint", { hint });
17420
17884
  }
17421
- advanceRevision(cause, hint, readCursorAdvanced, projectionInvalidation) {
17885
+ advanceRevision(cause, evidence = {}) {
17886
+ const { hint, readCursorAdvanced, projectionInvalidation, timelineChanged, conversationListPreferenceUpdated } = evidence;
17422
17887
  this.revision += 1;
17423
17888
  const notice = {
17424
17889
  state: this.state(),
17425
17890
  cause,
17891
+ ...timelineChanged === void 0 ? {} : { timelineChanged },
17426
17892
  ...hint === void 0 ? {} : { hint },
17427
17893
  ...readCursorAdvanced === void 0 ? {} : { readCursorAdvanced },
17428
- ...projectionInvalidation === void 0 ? {} : { projectionInvalidation }
17894
+ ...projectionInvalidation === void 0 ? {} : { projectionInvalidation },
17895
+ ...conversationListPreferenceUpdated === void 0 ? {} : { conversationListPreferenceUpdated }
17429
17896
  };
17430
17897
  for (const listener of [...this.listeners]) listener(notice);
17431
17898
  }
@@ -17435,6 +17902,9 @@ var ArkmeChatRealtimeRuntime = class {
17435
17902
  const MAX_PROJECTION_RETRIES = 5;
17436
17903
  const MAX_ATTENTION_SUMMARY_RETRIES = 5;
17437
17904
  const MAX_NATIVE_NOTIFICATION_DELIVERY_CONCURRENCY = 3;
17905
+ function numberValue$21(value) {
17906
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
17907
+ }
17438
17908
  function listValue$18(value) {
17439
17909
  return Array.isArray(value) ? value : [];
17440
17910
  }
@@ -17576,16 +18046,46 @@ var ChatRealtimeService = class {
17576
18046
  this.invalidateRecordProjection();
17577
18047
  return;
17578
18048
  }
18049
+ if (notice.cause === "conversation-list-preference-invalidation" && notice.conversationListPreferenceUpdated !== void 0) {
18050
+ this.invalidateConversationListPreferenceForCurrentSession(notice.conversationListPreferenceUpdated.userId);
18051
+ return;
18052
+ }
17579
18053
  if (notice.cause === "chat-hint" && notice.readCursorAdvanced !== void 0) {
17580
18054
  this.handleReadCursorAdvanced(notice.readCursorAdvanced);
17581
18055
  return;
17582
18056
  }
18057
+ if (notice.cause === "chat-hint" && notice.timelineChanged !== void 0) {
18058
+ this.handleTimelineChanged(notice.timelineChanged);
18059
+ return;
18060
+ }
17583
18061
  if (notice.cause === "chat-hint" && notice.hint !== void 0) this.scheduleChatSessionProjection(notice.hint.chatSessionUid, notice.hint.latestSequence, {
17584
18062
  hint: notice.hint,
17585
18063
  connectionGeneration: notice.state.connectionGeneration,
17586
18064
  attempts: 0
17587
18065
  });
17588
18066
  }
18067
+ async handleTimelineChanged(hint) {
18068
+ try {
18069
+ const session = await this.runtime.sessionStore.read();
18070
+ if (session === void 0) return;
18071
+ const [sourceKey, timelineItemKey] = await Promise.all([this.source.chatDirectorySourceKey(session.userId, hint.chatSessionUid), this.source.chatTimelineItemKey(session.userId, hint.chatSessionUid, hint.relationUid)]);
18072
+ if ((await this.runtime.sessionStore.read())?.userId !== session.userId) return;
18073
+ this.emitChatClientEvent({
18074
+ type: "timeline-changed",
18075
+ revision: this.nextChatClientRevision(),
18076
+ sourceKey,
18077
+ timelineItemKey,
18078
+ changeKind: hint.changeKind,
18079
+ changeVersion: hint.changeVersion,
18080
+ relationTerminal: hint.relationTerminal,
18081
+ throughSequence: hint.latestSequence
18082
+ });
18083
+ this.scheduleChatSessionProjection(hint.chatSessionUid, hint.latestSequence);
18084
+ this.refreshAttentionSummary();
18085
+ } catch (error) {
18086
+ console.warn("dsh-arkme: Chat timeline invalidation failed:", safeFailureMessage$5(error));
18087
+ }
18088
+ }
17589
18089
  async handleReadCursorAdvanced(hint) {
17590
18090
  try {
17591
18091
  const session = await this.runtime.sessionStore.read();
@@ -17753,6 +18253,19 @@ var ChatRealtimeService = class {
17753
18253
  this.attentionSummaryFingerprint = "";
17754
18254
  this.latestAttentionSummary = void 0;
17755
18255
  }
18256
+ /** Browser invalidation only; raw source/Bot caches belong to different projections. */
18257
+ async invalidateConversationListPreferenceForCurrentSession(expectedUserId) {
18258
+ try {
18259
+ const session = await this.runtime.sessionStore.read();
18260
+ if (session === void 0 || expectedUserId !== void 0 && session.userId !== expectedUserId) return;
18261
+ this.emitChatClientEvent({
18262
+ type: "conversation-list-preference-invalidated",
18263
+ revision: this.nextChatClientRevision()
18264
+ });
18265
+ } catch (error) {
18266
+ console.warn("dsh-arkme: Conversation-list preference invalidation failed:", safeFailureMessage$5(error));
18267
+ }
18268
+ }
17756
18269
  async reconcileChatNotificationBaseline(connectionGeneration) {
17757
18270
  try {
17758
18271
  const session = await this.runtime.requireSession();
@@ -17908,7 +18421,8 @@ var ChatRealtimeService = class {
17908
18421
  lane: "background-read",
17909
18422
  key: `projection:tail:${uid}:${String(afterSequence)}:${String(notificationAttempt)}`
17910
18423
  });
17911
- return [uid, await this.projectionReader.chatTimelineItems(data, session, uid)];
18424
+ const sessionKind = numberValue$21(objectValue$1(bundles.get(uid)).session_kind ?? objectValue$1(objectValue$1(bundles.get(uid)).session).session_kind);
18425
+ return [uid, await this.projectionReader.chatTimelineItems(data, session, uid, sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0)];
17912
18426
  }))).forEach((result, index) => {
17913
18427
  const uid = chunk[index]?.[0];
17914
18428
  if (uid === void 0) return;
@@ -18050,6 +18564,54 @@ const ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES = 10 * 1024 * 1024;
18050
18564
  const ARKME_WORLD_PUBLISH_MAX_IMAGES = 27;
18051
18565
  const ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES = 20 * 1024 * 1024;
18052
18566
  //#endregion
18567
+ //#region src/hashtag.ts
18568
+ /** Flutter-compatible tag terminators. Keep this list in sync with chat_input_rich_support.dart. */
18569
+ const ARKME_HASH_TAG_TERMINATORS = /* @__PURE__ */ new Set([..." \n##,。!?;:、\"()【】《》,.!?;:'()[]<>"]);
18570
+ function isHashTagAnchor(value) {
18571
+ return value === "#" || value === "#";
18572
+ }
18573
+ /** Normalizes either rendered tag form to the key accepted by the tag projection API. */
18574
+ function arkmeNormalizedHashTag(value) {
18575
+ return value.trim().replace(/^[##]+/u, "").trim();
18576
+ }
18577
+ /** Finds all non-empty tags. An unterminated final tag is complete enough to send. */
18578
+ function arkmeHashTagRanges(text) {
18579
+ const ranges = [];
18580
+ let index = 0;
18581
+ while (index < text.length) {
18582
+ if (!isHashTagAnchor(text[index])) {
18583
+ index += 1;
18584
+ continue;
18585
+ }
18586
+ const contentStart = index + 1;
18587
+ let contentEnd = contentStart;
18588
+ while (contentEnd < text.length && !ARKME_HASH_TAG_TERMINATORS.has(text[contentEnd])) contentEnd += 1;
18589
+ if (contentEnd > contentStart) ranges.push({
18590
+ tag: text.slice(contentStart, contentEnd),
18591
+ startIndex: index,
18592
+ length: contentEnd - index
18593
+ });
18594
+ index = contentEnd > index ? contentEnd : index + 1;
18595
+ }
18596
+ return ranges;
18597
+ }
18598
+ function arkmeHashTagPayload(text) {
18599
+ return arkmeHashTagRanges(text).map((item) => ({
18600
+ tag: item.tag,
18601
+ start_index: item.startIndex,
18602
+ length: item.length
18603
+ }));
18604
+ }
18605
+ function arkmeHashTagContentPayload(text) {
18606
+ const hashTags = arkmeHashTagPayload(text);
18607
+ return hashTags.length === 0 ? void 0 : {
18608
+ payload_kind: 1,
18609
+ schema_version: 1,
18610
+ text_state: 1,
18611
+ hash_tags: hashTags
18612
+ };
18613
+ }
18614
+ //#endregion
18053
18615
  //#region src/services/record-service.ts
18054
18616
  function numberValue$20(value) {
18055
18617
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -18207,6 +18769,45 @@ var RecordService = class {
18207
18769
  async refreshLatest() {
18208
18770
  await Promise.all([this.summary(), this.list(50)]);
18209
18771
  }
18772
+ async listTags(limit = 100, signal) {
18773
+ const session = await this.runtime.requireSession();
18774
+ return { items: listValue$17((await this.runtime.authenticatedPost("/api/v1/records/tags/list", { limit: Math.max(1, Math.min(200, Math.trunc(limit))) }, session, signal)).items).flatMap((raw) => {
18775
+ const item = objectValue$1(raw);
18776
+ const tagText = stringValue$1(item.tag_text ?? item.tagText).trim();
18777
+ if (tagText === "") return [];
18778
+ return [{
18779
+ normalizedTag: stringValue$1(item.normalized_tag ?? item.normalizedTag).trim() || tagText.toLowerCase(),
18780
+ tagText,
18781
+ recordCount: Math.max(0, Math.trunc(numberValue$20(item.record_count ?? item.recordCount))),
18782
+ latestRecordUid: stringValue$1(item.latest_record_uid ?? item.latestRecordUid).trim(),
18783
+ latestSendAtMillis: Math.max(0, Math.trunc(numberValue$20(item.latest_send_at ?? item.latestSendAtMillis)))
18784
+ }];
18785
+ }) };
18786
+ }
18787
+ /**
18788
+ * Mirrors Flutter's post-commit tag sync. Record creation is already the
18789
+ * commit boundary, so a projection-side failure is diagnostic only and must
18790
+ * never turn an accepted message into a retryable send.
18791
+ */
18792
+ async syncCreatedRecordTags(recordUid, textContent, expectedUserId, signal) {
18793
+ const tags = arkmeHashTagPayload(textContent);
18794
+ if (tags.length === 0) return;
18795
+ try {
18796
+ const session = await this.runtime.requireSession();
18797
+ if (expectedUserId !== void 0 && session.userId !== expectedUserId) return;
18798
+ await this.runtime.authenticatedPost("/api/v1/records/tags/set", {
18799
+ record_uid: recordUid.trim(),
18800
+ expected_record_version: 1,
18801
+ tags: tags.map((tag) => ({
18802
+ tag_text: tag.tag,
18803
+ start_index: tag.start_index,
18804
+ length: tag.length
18805
+ }))
18806
+ }, session, signal);
18807
+ } catch (error) {
18808
+ if (signal?.aborted !== true) console.warn("dsh-arkme: record tag post-commit sync failed", safeFailureMessage$4(error));
18809
+ }
18810
+ }
18210
18811
  async refreshSnapshot() {
18211
18812
  await this.refreshLatest();
18212
18813
  return await this.cachedSnapshot();
@@ -18356,6 +18957,7 @@ var RecordService = class {
18356
18957
  3,
18357
18958
  4
18358
18959
  ].includes(asset.fileKind)) throw new ArkmePluginError("record-file-asset-invalid", "附件资产参数无效", false);
18960
+ const hashTags = arkmeHashTagPayload(normalizedText);
18359
18961
  const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
18360
18962
  record_uid: normalizedUid,
18361
18963
  template_kind: 2,
@@ -18366,6 +18968,7 @@ var RecordService = class {
18366
18968
  payload_kind: 2,
18367
18969
  schema_version: 1,
18368
18970
  text_state: normalizedText === "" ? 3 : 1,
18971
+ ...hashTags.length === 0 ? {} : { hash_tags: hashTags },
18369
18972
  media_refs: assets.map((asset, index) => ({
18370
18973
  file_asset_uid: asset.fileAssetUid,
18371
18974
  content_file_role: 1,
@@ -18376,10 +18979,12 @@ var RecordService = class {
18376
18979
  },
18377
18980
  send_at: Date.now()
18378
18981
  }, session);
18379
- return {
18982
+ const result = {
18380
18983
  recordUid: stringValue$1(data.record_uid).trim() || normalizedUid,
18381
18984
  status: numberValue$20(data.status)
18382
18985
  };
18986
+ await this.syncCreatedRecordTags(result.recordUid, normalizedText, session.userId);
18987
+ return result;
18383
18988
  }
18384
18989
  async createExtensionForConversation(parentRecordUid, recordUid, textContent, assets = []) {
18385
18990
  const session = await this.runtime.requireSession();
@@ -18394,6 +18999,7 @@ var RecordService = class {
18394
18999
  3,
18395
19000
  4
18396
19001
  ].includes(asset.fileKind)) throw new ArkmePluginError("record-file-asset-invalid", "附件资产参数无效", false);
19002
+ const hashTags = arkmeHashTagPayload(normalizedText);
18397
19003
  const data = await this.runtime.authenticatedPost("/api/v1/records/extensions/create", {
18398
19004
  parent_record_uid: normalizedParentUid,
18399
19005
  record_uid: normalizedUid,
@@ -18404,6 +19010,7 @@ var RecordService = class {
18404
19010
  payload_kind: assets.length === 0 ? 1 : 2,
18405
19011
  schema_version: 1,
18406
19012
  text_state: normalizedText === "" ? 3 : 1,
19013
+ ...hashTags.length === 0 ? {} : { hash_tags: hashTags },
18407
19014
  ...assets.length === 0 ? {} : { media_refs: assets.map((asset, index) => ({
18408
19015
  file_asset_uid: asset.fileAssetUid,
18409
19016
  content_file_role: 1,
@@ -18421,6 +19028,7 @@ var RecordService = class {
18421
19028
  const createdParentUid = stringValue$1(data.parent_record_uid).trim();
18422
19029
  const edgeUid = stringValue$1(data.edge_uid).trim();
18423
19030
  if (createdRecordUid !== normalizedUid || createdParentUid !== normalizedParentUid || edgeUid === "") throw new ArkmePluginError("record-extension-response-invalid", "延展写入结果无效,请刷新后重试", true, 502);
19031
+ await this.syncCreatedRecordTags(createdRecordUid, normalizedText, session.userId);
18424
19032
  return {
18425
19033
  recordUid: createdRecordUid,
18426
19034
  status: numberValue$20(data.record_status ?? data.status),
@@ -18440,6 +19048,7 @@ var RecordService = class {
18440
19048
  async sendPending(session, pending) {
18441
19049
  try {
18442
19050
  const captureContext = pending.captureContext === void 0 ? void 0 : arkmeRecordCaptureContextPayload(pending.captureContext);
19051
+ const hashTags = arkmeHashTagPayload(pending.textContent);
18443
19052
  const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
18444
19053
  record_uid: pending.recordUid,
18445
19054
  template_kind: 1,
@@ -18447,12 +19056,19 @@ var RecordService = class {
18447
19056
  text_content: pending.textContent,
18448
19057
  ...Math.max(0, Math.trunc(pending.recordDurationMillis ?? 0)) === 0 ? {} : { record_duration_millis: Math.max(0, Math.trunc(pending.recordDurationMillis ?? 0)) },
18449
19058
  ...captureContext === void 0 || Object.keys(captureContext).length === 0 ? {} : { capture_context: captureContext },
19059
+ ...hashTags.length === 0 ? {} : { content_payload: {
19060
+ payload_kind: 1,
19061
+ schema_version: 1,
19062
+ text_state: 1,
19063
+ hash_tags: hashTags
19064
+ } },
18450
19065
  send_at: pending.sendAtMillis
18451
19066
  }, session);
18452
19067
  const result = {
18453
19068
  recordUid: stringValue$1(data.record_uid) || pending.recordUid,
18454
19069
  status: numberValue$20(data.status)
18455
19070
  };
19071
+ await this.syncCreatedRecordTags(result.recordUid, pending.textContent, session.userId);
18456
19072
  await this.runtime.stateStore.markSynced(session.userId, pending.recordUid, result.status);
18457
19073
  return result;
18458
19074
  } catch (error) {
@@ -18640,7 +19256,8 @@ function arkmeRichContentPayload(input, normalizedTextContent, mentionPayload) {
18640
19256
  const assets = input.assets ?? [];
18641
19257
  const backgroundSound = arkmeRichBackgroundSound(input.backgroundSound);
18642
19258
  const mentionMetadata = mentionPayload?.mention_metadata;
18643
- if (assets.length === 0 && backgroundSound === void 0 && mentionMetadata === void 0) return void 0;
19259
+ const hashTags = arkmeHashTagPayload(normalizedTextContent);
19260
+ if (assets.length === 0 && backgroundSound === void 0 && mentionMetadata === void 0 && hashTags.length === 0) return void 0;
18644
19261
  const mediaRefs = [...assets.map((asset, index) => ({
18645
19262
  file_asset_uid: asset.fileAssetUid,
18646
19263
  content_file_role: 1,
@@ -18661,7 +19278,8 @@ function arkmeRichContentPayload(input, normalizedTextContent, mentionPayload) {
18661
19278
  text_state: normalizedTextContent === "" ? 3 : 1,
18662
19279
  ...mediaRefs.length === 0 ? {} : { media_refs: mediaRefs },
18663
19280
  ...backgroundSound === void 0 || backgroundSound.amplitudes.length === 0 ? {} : { background_sound_amplitudes: backgroundSound.amplitudes },
18664
- ...mentionMetadata === void 0 ? {} : { mention_metadata: mentionMetadata }
19281
+ ...mentionMetadata === void 0 ? {} : { mention_metadata: mentionMetadata },
19282
+ ...hashTags.length === 0 ? {} : { hash_tags: hashTags }
18665
19283
  };
18666
19284
  }
18667
19285
  const OFFICIAL_AUTHOR_USER_ID = 11;
@@ -18669,6 +19287,8 @@ const OFFICIAL_AUTHOR_FALLBACK_DISPLAY_NAME = "即我作者";
18669
19287
  const MAX_MESSAGE_COPY_LINK_ITEMS = 100;
18670
19288
  const RECORD_UID_PATTERN$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
18671
19289
  const CHAT_MEMBER_REF_PREFIX = "arkme-chat-member-v1";
19290
+ const MESSAGE_WITHDRAWAL_REF_PREFIX = "arkme-message-withdrawal-v1";
19291
+ const JOIN_RESTRICTION_CURSOR_PREFIX = "arkme-join-restriction-cursor-v1";
18672
19292
  const CHAT_HUMAN_MENTION_REF_PREFIX = "arkme-chat-human-mention-v1";
18673
19293
  function numberValue$19(value) {
18674
19294
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -19494,25 +20114,30 @@ function firstJoinDisplayName(source, keys) {
19494
20114
  }
19495
20115
  return "";
19496
20116
  }
20117
+ function isUsableChatMemberName(value, userId) {
20118
+ return value !== "" && value !== "成员" && value !== "群成员" && value !== `用户 ${String(userId)}`;
20119
+ }
20120
+ function firstUsableChatMemberName(values, userId) {
20121
+ return values.map(normalizedJoinDisplayName).find((value) => isUsableChatMemberName(value, userId)) ?? "";
20122
+ }
19497
20123
  function resolveChatMemberDisplayNames(input) {
19498
- const isUsable = (value) => value !== "" && value !== "成员" && value !== "群成员" && value !== `用户 ${String(input.userId)}`;
19499
- const firstUsable = (values) => values.map(normalizedJoinDisplayName).find(isUsable) ?? "";
19500
- const remarkName = firstUsable(input.remarkCandidates ?? []);
19501
- const memberName = firstUsable(input.memberNameCandidates ?? []);
19502
- const userName = firstUsable(input.userNameCandidates ?? []);
20124
+ const remarkName = firstUsableChatMemberName(input.remarkCandidates ?? [], input.userId);
20125
+ const memberName = firstUsableChatMemberName(input.memberNameCandidates ?? [], input.userId);
20126
+ const userName = firstUsableChatMemberName(input.userNameCandidates ?? [], input.userId);
19503
20127
  const displayName = [
19504
20128
  remarkName,
19505
20129
  memberName,
19506
20130
  userName
19507
- ].find(isUsable) ?? "群成员";
20131
+ ].find((value) => isUsableChatMemberName(value, input.userId)) ?? "群成员";
19508
20132
  return {
19509
20133
  displayName,
19510
20134
  memberName,
20135
+ userName,
19511
20136
  secondaryName: [
19512
20137
  memberName,
19513
20138
  userName,
19514
20139
  remarkName
19515
- ].find((value) => isUsable(value) && value !== displayName) ?? ""
20140
+ ].find((value) => isUsableChatMemberName(value, input.userId) && value !== displayName) ?? ""
19516
20141
  };
19517
20142
  }
19518
20143
  function projectChatMemberDisplayNames(item, userId, viewerLabel, publicDisplayName) {
@@ -19523,11 +20148,13 @@ function projectChatMemberDisplayNames(item, userId, viewerLabel, publicDisplayN
19523
20148
  userNameCandidates: [item.display_name, publicDisplayName]
19524
20149
  });
19525
20150
  }
19526
- function projectChatMemberMentionDisplayName(item, userId, publicDisplayName) {
19527
- return resolveChatMemberDisplayNames({
19528
- userId,
19529
- userNameCandidates: [item.display_name, publicDisplayName]
19530
- }).displayName;
20151
+ function projectChatMemberMentionDisplayNames(names, remarkCandidates, userId) {
20152
+ const mentionDisplayName = names.memberName || names.userName || "群成员";
20153
+ const remarkName = firstUsableChatMemberName(remarkCandidates, userId);
20154
+ return {
20155
+ mentionDisplayName,
20156
+ mentionSecondaryName: remarkName !== mentionDisplayName ? remarkName : ""
20157
+ };
19531
20158
  }
19532
20159
  function normalizedJoinTimestamp(value) {
19533
20160
  const raw = integerLikeValue(value);
@@ -20347,6 +20974,127 @@ var ChatService = class {
20347
20974
  status: numberValue$19(report.status)
20348
20975
  };
20349
20976
  }
20977
+ async withdrawGroupMessage(messageWithdrawalRef, options = {}) {
20978
+ const session = await this.runtime.requireSession();
20979
+ const normalizedMessageWithdrawalRef = messageWithdrawalRef.trim();
20980
+ const reference = await this.openMessageWithdrawalRef(normalizedMessageWithdrawalRef, session.userId);
20981
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/messages/withdraw", {
20982
+ chat_session_uid: reference.chatSessionUid,
20983
+ rel_uid: reference.relationUid
20984
+ }, session, options.signal);
20985
+ const withdrawnAtMillis = numberValue$19(data.withdrawn_at);
20986
+ if (stringValue$1(data.chat_session_uid).trim() !== reference.chatSessionUid || stringValue$1(data.rel_uid).trim() !== reference.relationUid || !Number.isSafeInteger(withdrawnAtMillis) || withdrawnAtMillis <= 0 || typeof data.already_withdrawn !== "boolean") throw new ArkmePluginError("message-withdraw-invalid-response", "撤回服务返回无效", true, 502);
20987
+ return {
20988
+ messageWithdrawalRef: normalizedMessageWithdrawalRef,
20989
+ timelineItemKey: await this.source.chatTimelineItemKey(session.userId, reference.chatSessionUid, reference.relationUid),
20990
+ withdrawnAtMillis,
20991
+ alreadyWithdrawn: data.already_withdrawn
20992
+ };
20993
+ }
20994
+ async removeGroupMember(sourceRef, memberRef, options = {}) {
20995
+ const session = await this.runtime.requireSession();
20996
+ const normalizedSourceRef = sourceRef.trim();
20997
+ const normalizedMemberRef = memberRef.trim();
20998
+ const source = await this.source.openSourceRef(normalizedSourceRef, session.userId);
20999
+ if (source.kind !== "group_chat") throw new ArkmePluginError("group-source-invalid", "仅支持移除群聊成员", false);
21000
+ const member = await this.openChatMemberRef(normalizedMemberRef, session.userId, source.ownerRef);
21001
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/members/update", {
21002
+ chat_session_uid: source.ownerRef,
21003
+ target_user_id: member.targetUserId,
21004
+ action: 2,
21005
+ prevent_rejoin: options.preventRejoin === true
21006
+ }, session, options.signal);
21007
+ const item = objectValue$1(data.item);
21008
+ if (stringValue$1(data.chat_session_uid).trim() !== source.ownerRef || !Number.isSafeInteger(numberValue$19(item.user_id)) || numberValue$19(item.user_id) !== member.targetUserId || !Number.isSafeInteger(numberValue$19(item.status)) || numberValue$19(item.status) !== 3 || typeof item.join_restricted !== "boolean" || options.preventRejoin === true && item.join_restricted !== true) throw new ArkmePluginError("group-member-remove-invalid-response", "移除成员服务返回无效", true, 502);
21009
+ return {
21010
+ sourceRef: normalizedSourceRef,
21011
+ memberRef: normalizedMemberRef,
21012
+ status: "removed",
21013
+ joinRestricted: item.join_restricted
21014
+ };
21015
+ }
21016
+ async listGroupJoinRestrictions(sourceRef, options = {}) {
21017
+ const session = await this.runtime.requireSession();
21018
+ const normalizedSourceRef = sourceRef.trim();
21019
+ const source = await this.source.openSourceRef(normalizedSourceRef, session.userId);
21020
+ if (source.kind !== "group_chat") throw new ArkmePluginError("group-source-invalid", "仅支持查看群聊限制名单", false);
21021
+ const cursor = options.cursor === void 0 ? void 0 : await this.openJoinRestrictionCursor(options.cursor, session.userId, source.ownerRef);
21022
+ const limit = Math.min(100, Math.max(1, Math.trunc(options.limit ?? 30)));
21023
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/governance/join-restriction/page", {
21024
+ chat_session_uid: source.ownerRef,
21025
+ limit,
21026
+ ...cursor === void 0 ? {} : { cursor: {
21027
+ updated_at: cursor.updatedAt,
21028
+ user_id: cursor.targetUserId
21029
+ } }
21030
+ }, session, options.signal);
21031
+ if (stringValue$1(data.chat_session_uid).trim() !== source.ownerRef || !Array.isArray(data.items)) throw new ArkmePluginError("join-restrictions-invalid-response", "限制名单服务返回无效", true, 502);
21032
+ const rawItems = listValue$16(data.items).map(objectValue$1);
21033
+ for (const raw of rawItems) {
21034
+ const userId = numberValue$19(raw.user_id);
21035
+ const restrictedAtMillis = numberValue$19(objectValue$1(raw.restriction).updated_at);
21036
+ if (!Number.isSafeInteger(userId) || userId <= 0 || !Number.isSafeInteger(restrictedAtMillis) || restrictedAtMillis <= 0) throw new ArkmePluginError("join-restrictions-invalid-response", "限制名单服务返回无效", true, 502);
21037
+ }
21038
+ const projected = await this.projectChatMembers(source.ownerRef, rawItems, session, {
21039
+ includeViewerLabels: true,
21040
+ includeHumanMentionRefs: false,
21041
+ ...options.signal === void 0 ? {} : { signal: options.signal }
21042
+ });
21043
+ const projectedByRef = new Map(projected.map((item) => [item.memberRef, item]));
21044
+ const items = [];
21045
+ for (const raw of rawItems) {
21046
+ const userId = numberValue$19(raw.user_id);
21047
+ const memberRef = await this.sealChatMemberRef(session.userId, source.ownerRef, userId);
21048
+ const member = projectedByRef.get(memberRef);
21049
+ const restrictedAtMillis = numberValue$19(objectValue$1(raw.restriction).updated_at);
21050
+ if (member === void 0) throw new ArkmePluginError("join-restrictions-invalid-response", "限制名单服务返回无效", true, 502);
21051
+ items.push({
21052
+ memberRef,
21053
+ displayName: member.displayName,
21054
+ ...member.memberName === void 0 ? {} : { memberName: member.memberName },
21055
+ ...member.secondaryName === void 0 ? {} : { secondaryName: member.secondaryName },
21056
+ ...member.avatarRef === void 0 ? {} : { avatarRef: member.avatarRef },
21057
+ restrictedAtMillis
21058
+ });
21059
+ }
21060
+ const hasNextCursor = data.next_cursor !== void 0 && data.next_cursor !== null;
21061
+ const rawNextCursor = objectValue$1(data.next_cursor);
21062
+ const nextUpdatedAt = numberValue$19(rawNextCursor.updated_at);
21063
+ const nextUserId = numberValue$19(rawNextCursor.user_id);
21064
+ if (hasNextCursor && (!Number.isSafeInteger(nextUpdatedAt) || nextUpdatedAt <= 0 || !Number.isSafeInteger(nextUserId) || nextUserId <= 0)) throw new ArkmePluginError("join-restrictions-invalid-response", "限制名单服务返回无效", true, 502);
21065
+ return {
21066
+ sourceRef: normalizedSourceRef,
21067
+ items,
21068
+ ...!hasNextCursor ? {} : { nextCursor: await this.sealJoinRestrictionCursor({
21069
+ version: 1,
21070
+ userId: session.userId,
21071
+ chatSessionUid: source.ownerRef,
21072
+ updatedAt: nextUpdatedAt,
21073
+ targetUserId: nextUserId
21074
+ }) }
21075
+ };
21076
+ }
21077
+ async setGroupJoinRestriction(sourceRef, memberRef, restricted, options = {}) {
21078
+ const session = await this.runtime.requireSession();
21079
+ const normalizedSourceRef = sourceRef.trim();
21080
+ const normalizedMemberRef = memberRef.trim();
21081
+ const source = await this.source.openSourceRef(normalizedSourceRef, session.userId);
21082
+ if (source.kind !== "group_chat") throw new ArkmePluginError("group-source-invalid", "仅支持设置群聊加入限制", false);
21083
+ const member = await this.openChatMemberRef(normalizedMemberRef, session.userId, source.ownerRef);
21084
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/governance/join-restriction/set", {
21085
+ chat_session_uid: source.ownerRef,
21086
+ target_user_id: member.targetUserId,
21087
+ restricted
21088
+ }, session, options.signal);
21089
+ const updatedAtMillis = numberValue$19(data.updated_at);
21090
+ if (stringValue$1(data.chat_session_uid).trim() !== source.ownerRef || !Number.isSafeInteger(numberValue$19(data.user_id)) || numberValue$19(data.user_id) !== member.targetUserId || data.restricted !== restricted || !Number.isSafeInteger(updatedAtMillis) || (restricted ? updatedAtMillis <= 0 : updatedAtMillis !== 0)) throw new ArkmePluginError("join-restriction-invalid-response", "加入限制服务返回无效", true, 502);
21091
+ return {
21092
+ sourceRef: normalizedSourceRef,
21093
+ memberRef: normalizedMemberRef,
21094
+ restricted,
21095
+ ...updatedAtMillis <= 0 ? {} : { updatedAtMillis }
21096
+ };
21097
+ }
20350
21098
  async messageReadReceiptSummaries(sourceRef, rawItems, options = {}) {
20351
21099
  const session = await this.runtime.requireSession();
20352
21100
  const source = await this.requireReadReceiptChatSource(sourceRef, session.userId);
@@ -20673,11 +21421,13 @@ var ChatService = class {
20673
21421
  const profile = (await this.profile.refreshProfile()).profile;
20674
21422
  if (profile === null) throw new ArkmePluginError("profile-unavailable", "无法读取当前 Arkme 账号资料", true);
20675
21423
  const sendAtMillis = Date.now();
20676
- const contentPayload = assets.length === 0 ? void 0 : {
20677
- payload_kind: 2,
21424
+ const hashTags = arkmeHashTagPayload(normalizedText);
21425
+ const contentPayload = assets.length === 0 && hashTags.length === 0 ? void 0 : {
21426
+ payload_kind: assets.length === 0 ? 1 : 2,
20678
21427
  schema_version: 1,
20679
21428
  text_state: normalizedText === "" ? 3 : 1,
20680
- media_refs: assets.map((asset, index) => ({
21429
+ ...hashTags.length === 0 ? {} : { hash_tags: hashTags },
21430
+ ...assets.length === 0 ? {} : { media_refs: assets.map((asset, index) => ({
20681
21431
  file_asset_uid: asset.fileAssetUid,
20682
21432
  content_file_role: 1,
20683
21433
  render_role: 1,
@@ -20686,7 +21436,7 @@ var ChatService = class {
20686
21436
  file_kind: asset.fileKind,
20687
21437
  mime_type: asset.mimeType,
20688
21438
  size: asset.size
20689
- }))
21439
+ })) }
20690
21440
  };
20691
21441
  const data = await this.runtime.authenticatedChatPost("/api/v1/chats/extensions/children/create", {
20692
21442
  chat_session_uid: reference.chatSessionUid,
@@ -20737,11 +21487,13 @@ var ChatService = class {
20737
21487
  const profile = (await this.profile.refreshProfile()).profile;
20738
21488
  if (profile === null) throw new ArkmePluginError("profile-unavailable", "无法读取当前 Arkme 账号资料", true);
20739
21489
  const sendAtMillis = Date.now();
20740
- const contentPayload = assets.length === 0 ? void 0 : {
20741
- payload_kind: 2,
21490
+ const hashTags = arkmeHashTagPayload(normalizedText);
21491
+ const contentPayload = assets.length === 0 && hashTags.length === 0 ? void 0 : {
21492
+ payload_kind: assets.length === 0 ? 1 : 2,
20742
21493
  schema_version: 1,
20743
21494
  text_state: normalizedText === "" ? 3 : 1,
20744
- media_refs: assets.map((asset, index) => ({
21495
+ ...hashTags.length === 0 ? {} : { hash_tags: hashTags },
21496
+ ...assets.length === 0 ? {} : { media_refs: assets.map((asset, index) => ({
20745
21497
  file_asset_uid: asset.fileAssetUid,
20746
21498
  content_file_role: 1,
20747
21499
  render_role: 1,
@@ -20750,7 +21502,7 @@ var ChatService = class {
20750
21502
  file_kind: asset.fileKind,
20751
21503
  mime_type: asset.mimeType,
20752
21504
  size: asset.size
20753
- }))
21505
+ })) }
20754
21506
  };
20755
21507
  const data = await this.runtime.authenticatedPost("/api/v1/topics/records/extensions/create", {
20756
21508
  topic_uid: source.ownerRef,
@@ -20767,6 +21519,7 @@ var ChatService = class {
20767
21519
  const createdTopicUid = stringValue$1(data.topic_uid).trim();
20768
21520
  const edgeUid = stringValue$1(data.edge_uid).trim();
20769
21521
  if (createdRecordUid !== normalizedUid || createdParentUid !== reference.recordUid || createdTopicUid !== source.ownerRef || edgeUid === "") throw new ArkmePluginError("record-extension-response-invalid", "主题延展写入结果无效,请刷新后重试", true, 502);
21522
+ await this.record.syncCreatedRecordTags?.(createdRecordUid, normalizedText, session.userId);
20770
21523
  const relationUid = stringValue$1(data.rel_uid).trim();
20771
21524
  this.source.invalidateSourceListCache(session.userId, "send_to_self");
20772
21525
  this.realtime.emitChatClientEvent({
@@ -21109,6 +21862,7 @@ var ChatService = class {
21109
21862
  const text = textContent.trim();
21110
21863
  if (text === "" || text.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("source-text-invalid", "发送内容为空或超过长度限制", false);
21111
21864
  const recordUid = options.recordUid?.trim() || randomUUID();
21865
+ const hashTagContentPayload = arkmeHashTagContentPayload(text);
21112
21866
  if (source.kind === "send_to_self" || source.kind === "default_category") {
21113
21867
  const sendAtMillis = Date.now();
21114
21868
  const result = await this.record.createTextForConversation(recordUid, text, {
@@ -21145,8 +21899,10 @@ var ChatService = class {
21145
21899
  text_content: text,
21146
21900
  ...Math.max(0, Math.trunc(options.recordDurationMillis ?? 0)) === 0 ? {} : { record_duration_millis: Math.max(0, Math.trunc(options.recordDurationMillis ?? 0)) },
21147
21901
  ...captureContext === void 0 || Object.keys(captureContext).length === 0 ? {} : { capture_context: captureContext },
21902
+ ...hashTagContentPayload === void 0 ? {} : { content_payload: hashTagContentPayload },
21148
21903
  send_at: sendAtMillis
21149
21904
  }, session);
21905
+ await this.record.syncCreatedRecordTags?.(stringValue$1(result.record_uid).trim() || recordUid, text, session.userId);
21150
21906
  await this.realtime.invalidateRecordProjection();
21151
21907
  return await this.withSentMessageActionRef({
21152
21908
  sourceRef,
@@ -21185,6 +21941,7 @@ var ChatService = class {
21185
21941
  agentAuthored,
21186
21942
  ...options.recordDurationMillis === void 0 ? {} : { recordDurationMillis: options.recordDurationMillis },
21187
21943
  ...options.captureContext === void 0 ? {} : { captureContext: options.captureContext },
21944
+ ...hashTagContentPayload === void 0 ? {} : { contentPayload: hashTagContentPayload },
21188
21945
  ...options.signal === void 0 ? {} : { signal: options.signal }
21189
21946
  });
21190
21947
  } else if ((options.humanMentions?.length ?? 0) > 0) throw new ArkmePluginError("mention-group-required", "真人 mention 只能发送到群聊", false);
@@ -21196,7 +21953,7 @@ var ChatService = class {
21196
21953
  ...options.recordDurationMillis === void 0 ? {} : { recordDurationMillis: options.recordDurationMillis },
21197
21954
  ...options.captureContext === void 0 ? {} : { captureContext: options.captureContext }
21198
21955
  });
21199
- } else sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, void 0, options.signal, {
21956
+ } else sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, hashTagContentPayload, options.signal, {
21200
21957
  agentAuthored,
21201
21958
  ...options.recordDurationMillis === void 0 ? {} : { recordDurationMillis: options.recordDurationMillis },
21202
21959
  ...options.captureContext === void 0 ? {} : { captureContext: options.captureContext }
@@ -21237,6 +21994,7 @@ var ChatService = class {
21237
21994
  payload_kind: 1,
21238
21995
  schema_version: 1,
21239
21996
  text_state: 1,
21997
+ ...arkmeHashTagPayload(normalizedText).length === 0 ? {} : { hash_tags: arkmeHashTagPayload(normalizedText) },
21240
21998
  mention_metadata: {
21241
21999
  schema_version: 1,
21242
22000
  source_checksum: createHash("sha256").update(JSON.stringify(checksumInput)).digest("hex"),
@@ -21376,6 +22134,7 @@ var ChatService = class {
21376
22134
  payload_kind: 1,
21377
22135
  schema_version: 1,
21378
22136
  text_state: 1,
22137
+ ...arkmeHashTagPayload(visibleText).length === 0 ? {} : { hash_tags: arkmeHashTagPayload(visibleText) },
21379
22138
  mention_metadata: {
21380
22139
  schema_version: 1,
21381
22140
  source_checksum: createHash("sha256").update(JSON.stringify(checksumInput)).digest("hex"),
@@ -21479,6 +22238,7 @@ var ChatService = class {
21479
22238
  };
21480
22239
  if (source.kind === "send_to_self" || source.kind === "default_category") {
21481
22240
  const result = await this.runtime.authenticatedPost("/api/v1/records/create", commonBody, session, options.signal, { trackWriteOutcome: true });
22241
+ await this.record.syncCreatedRecordTags?.(stringValue$1(result.record_uid).trim() || recordUid, textContent, session.userId);
21482
22242
  await this.realtime.invalidateRecordProjection();
21483
22243
  return await this.withSentMessageActionRef({
21484
22244
  sourceRef,
@@ -21502,6 +22262,7 @@ var ChatService = class {
21502
22262
  topic_uid: source.ownerRef,
21503
22263
  ...commonBody
21504
22264
  }, session, options.signal, { trackWriteOutcome: true });
22265
+ await this.record.syncCreatedRecordTags?.(stringValue$1(result.record_uid).trim() || recordUid, textContent, session.userId);
21505
22266
  await this.realtime.invalidateRecordProjection();
21506
22267
  return await this.withSentMessageActionRef({
21507
22268
  sourceRef,
@@ -21765,9 +22526,10 @@ var ChatService = class {
21765
22526
  async chatSourceFromBundle(bundle, session, cached, timelineItems) {
21766
22527
  return await this.source.chatSourceFromBundle(bundle, session, cached, timelineItems);
21767
22528
  }
21768
- async chatTimelineItems(data, session, chatSessionUid) {
22529
+ async chatTimelineItems(data, session, chatSessionUid, projectedSourceKind) {
21769
22530
  const items = [];
21770
22531
  const signingKey = await this.runtime.stateStore.uniqueCode();
22532
+ const sourceKind = projectedSourceKind;
21771
22533
  for (const raw of listValue$16(data.items)) {
21772
22534
  const item = objectValue$1(raw);
21773
22535
  const relation = objectValue$1(item.relation);
@@ -21799,6 +22561,11 @@ var ChatService = class {
21799
22561
  const mentionsViewer = senderUserId !== session.userId && arkmeMentionMetadataMentionsViewer(record, payload, session.userId);
21800
22562
  items.push({
21801
22563
  itemUid: uid,
22564
+ ...relationUid === "" ? {} : { timelineItemKey: await this.source.chatTimelineItemKey(session.userId, chatSessionUid, relationUid) },
22565
+ ...sourceKind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : {
22566
+ messageRef: this.sealMessageRef(session.userId, chatSessionUid, relationUid, signingKey),
22567
+ messageWithdrawalRef: this.sealMessageWithdrawalRef(session.userId, chatSessionUid, relationUid, signingKey)
22568
+ },
21802
22569
  ...relationUid === "" ? {} : { messageActionRef: this.sealMessageActionRef(this.messageActionRefPayload({
21803
22570
  sourceKind: "chat_relation",
21804
22571
  userId: session.userId,
@@ -22344,14 +23111,16 @@ var ChatService = class {
22344
23111
  }
22345
23112
  async projectChatMembers(chatSessionUid, rawItems, session, options) {
22346
23113
  const userIds = rawItems.map((item) => Math.trunc(numberValue$19(item.user_id))).filter((userId) => Number.isSafeInteger(userId) && userId > 0);
22347
- 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())]);
23114
+ const [profiles, viewerLabels] = await Promise.all([this.profile.publicProfileSummariesByUserIds(userIds, session, options.signal).catch(() => /* @__PURE__ */ new Map()), options.includeViewerLabels ? this.source.privateChatViewerLabelsByUserIds(userIds, { ...options.signal === void 0 ? {} : { signal: options.signal } }).catch(() => /* @__PURE__ */ new Map()) : Promise.resolve(/* @__PURE__ */ new Map())]);
22348
23115
  const members = [];
22349
23116
  for (const item of rawItems) {
22350
23117
  const userId = Math.trunc(numberValue$19(item.user_id));
22351
23118
  if (!Number.isSafeInteger(userId) || userId <= 0) continue;
22352
23119
  const profile = profiles.get(userId);
22353
- const { displayName, memberName, secondaryName } = projectChatMemberDisplayNames(item, userId, viewerLabels.get(userId), profile?.displayName);
22354
- const mentionDisplayName = projectChatMemberMentionDisplayName(item, userId, profile?.displayName);
23120
+ const viewerLabel = viewerLabels.get(userId);
23121
+ const names = projectChatMemberDisplayNames(item, userId, viewerLabel?.displayName, profile?.displayName);
23122
+ const { displayName, memberName, secondaryName } = names;
23123
+ const { mentionDisplayName, mentionSecondaryName } = projectChatMemberMentionDisplayNames(names, [viewerLabel?.remark, item.remark], userId);
22355
23124
  const role = chatMemberRole$1(item.role);
22356
23125
  const status = chatMemberStatus$1(item.status);
22357
23126
  const extra = parsedObject(item.extra);
@@ -22359,7 +23128,8 @@ var ChatService = class {
22359
23128
  memberRef: await this.sealChatMemberRef(session.userId, chatSessionUid, userId),
22360
23129
  ...options.includeHumanMentionRefs && status === "active" && userId !== session.userId ? {
22361
23130
  mentionRef: await this.sealChatHumanMentionRef(session.userId, chatSessionUid, userId, mentionDisplayName),
22362
- mentionDisplayName
23131
+ mentionDisplayName,
23132
+ ...mentionSecondaryName === "" ? {} : { mentionSecondaryName }
22363
23133
  } : {},
22364
23134
  displayName,
22365
23135
  ...memberName === "" ? {} : { memberName },
@@ -22420,7 +23190,11 @@ var ChatService = class {
22420
23190
  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);
22421
23191
  const itemIndex = items.push({
22422
23192
  itemUid: uid,
22423
- ...source.kind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : { messageRef: this.sealMessageRef(session.userId, source.ownerRef, relationUid, signingKey) },
23193
+ ...relationUid === "" ? {} : { timelineItemKey: await this.source.chatTimelineItemKey(session.userId, source.ownerRef, relationUid) },
23194
+ ...source.kind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : {
23195
+ messageRef: this.sealMessageRef(session.userId, source.ownerRef, relationUid, signingKey),
23196
+ messageWithdrawalRef: this.sealMessageWithdrawalRef(session.userId, source.ownerRef, relationUid, signingKey)
23197
+ },
22424
23198
  ...relationUid === "" ? {} : { messageActionRef: this.sealMessageActionRef(this.messageActionRefPayload({
22425
23199
  sourceKind: "chat_relation",
22426
23200
  userId: session.userId,
@@ -22811,6 +23585,57 @@ var ChatService = class {
22811
23585
  });
22812
23586
  return `arkme-message-v1.${payload}.${createHmac("sha256", signingKey).update(payload).digest("base64url")}`;
22813
23587
  }
23588
+ sealMessageWithdrawalRef(userId, chatSessionUid, relationUid, signingKey) {
23589
+ const payload = encodeOpaqueJson$6({
23590
+ version: 1,
23591
+ userId,
23592
+ chatSessionUid,
23593
+ relationUid
23594
+ });
23595
+ const signature = createHmac("sha256", signingKey).update(payload).digest("base64url");
23596
+ return `${MESSAGE_WITHDRAWAL_REF_PREFIX}.${payload}.${signature}`;
23597
+ }
23598
+ async openMessageWithdrawalRef(messageWithdrawalRef, expectedUserId) {
23599
+ const parsed = await this.openSignedOpaquePayload(messageWithdrawalRef, MESSAGE_WITHDRAWAL_REF_PREFIX, "message-withdrawal-ref-invalid", "消息撤回引用无效,请刷新后重试");
23600
+ const result = {
23601
+ version: 1,
23602
+ userId: Math.trunc(numberValue$19(parsed.userId)),
23603
+ chatSessionUid: stringValue$1(parsed.chatSessionUid).trim(),
23604
+ relationUid: stringValue$1(parsed.relationUid).trim()
23605
+ };
23606
+ if (parsed.version !== 1 || result.userId !== expectedUserId || result.chatSessionUid === "" || result.relationUid === "") throw new ArkmePluginError("message-withdrawal-ref-invalid", "消息撤回引用与当前账号不匹配", false, 403);
23607
+ return result;
23608
+ }
23609
+ async sealJoinRestrictionCursor(payload) {
23610
+ const encoded = encodeOpaqueJson$6(payload);
23611
+ const signature = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest("base64url");
23612
+ return `${JOIN_RESTRICTION_CURSOR_PREFIX}.${encoded}.${signature}`;
23613
+ }
23614
+ async openJoinRestrictionCursor(cursor, expectedUserId, expectedChatSessionUid) {
23615
+ const parsed = await this.openSignedOpaquePayload(cursor, JOIN_RESTRICTION_CURSOR_PREFIX, "join-restriction-cursor-invalid", "限制名单游标无效,请重新打开");
23616
+ const result = {
23617
+ version: 1,
23618
+ userId: Math.trunc(numberValue$19(parsed.userId)),
23619
+ chatSessionUid: stringValue$1(parsed.chatSessionUid).trim(),
23620
+ updatedAt: Math.trunc(numberValue$19(parsed.updatedAt)),
23621
+ targetUserId: Math.trunc(numberValue$19(parsed.targetUserId))
23622
+ };
23623
+ if (parsed.version !== 1 || result.userId !== expectedUserId || result.chatSessionUid !== expectedChatSessionUid || result.updatedAt <= 0 || result.targetUserId <= 0) throw new ArkmePluginError("join-restriction-cursor-invalid", "限制名单游标与当前账号或群聊不匹配", false, 403);
23624
+ return result;
23625
+ }
23626
+ async openSignedOpaquePayload(reference, prefix, code, message) {
23627
+ const parts = reference.trim().split(".");
23628
+ if (parts.length !== 3 || parts[0] !== prefix) throw new ArkmePluginError(code, message, false);
23629
+ const encoded = parts[1] ?? "";
23630
+ const supplied = Buffer.from(parts[2] ?? "", "base64url");
23631
+ const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest();
23632
+ if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError(code, message, false);
23633
+ try {
23634
+ return objectValue$1(decodeOpaqueJson$5(encoded));
23635
+ } catch (error) {
23636
+ throw new ArkmePluginError(code, message, false, 400, { cause: error });
23637
+ }
23638
+ }
22814
23639
  async openMessageRef(messageRef, expectedUserId) {
22815
23640
  const parts = messageRef.trim().split(".");
22816
23641
  if (parts.length !== 3 || parts[0] !== "arkme-message-v1") throw new ArkmePluginError("message-ref-invalid", "Arkme 消息引用无效", false);
@@ -22835,6 +23660,130 @@ var ChatService = class {
22835
23660
  }
22836
23661
  };
22837
23662
  //#endregion
23663
+ //#region src/services/conversation-directory-visibility-service.ts
23664
+ const MAX_DIRECTORY_VISIBILITY_REFS = 1e3;
23665
+ /** Sidebar application service; raw source and Bot APIs remain unfiltered owner projections. */
23666
+ var ConversationDirectoryVisibilityService = class {
23667
+ preference;
23668
+ source;
23669
+ bot;
23670
+ invalidation;
23671
+ constructor(preference, source, bot, invalidation) {
23672
+ this.preference = preference;
23673
+ this.source = source;
23674
+ this.bot = bot;
23675
+ this.invalidation = invalidation;
23676
+ }
23677
+ async query(sourceRefs, botRefs, signal) {
23678
+ if (sourceRefs.length + botRefs.length > MAX_DIRECTORY_VISIBILITY_REFS) throw new ArkmePluginError("conversation-directory-visibility-too-large", "会话列表状态查询数量过多", false, 400);
23679
+ const handles = [...sourceRefs.map((entryRef) => ({
23680
+ entryKind: "source",
23681
+ entryRef
23682
+ })), ...botRefs.map((entryRef) => ({
23683
+ entryKind: "bot",
23684
+ entryRef
23685
+ }))];
23686
+ const resolved = await Promise.allSettled(handles.map(async (handle) => ({
23687
+ ...handle,
23688
+ ...handle.entryKind === "source" ? await this.source.chatConversationListPreferenceEntry(handle.entryRef, signal) : await this.bot.botConversationListPreferenceEntry(handle.entryRef)
23689
+ })));
23690
+ const fatal = resolved.find((result) => result.status === "rejected" && isFatalResolutionError(result.reason));
23691
+ if (fatal?.status === "rejected") throw fatal.reason;
23692
+ const entries = resolved.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
23693
+ if (entries.length === 0) return { items: handles.map((handle) => ({
23694
+ ...handle,
23695
+ hidden: false
23696
+ })) };
23697
+ const ownerUserId = entries[0].ownerUserId;
23698
+ if (entries.some((entry) => entry.ownerUserId !== ownerUserId)) throw new ArkmePluginError("login-context-changed", "登录账号已切换,请重试当前操作", false, 409);
23699
+ const owner = await this.preference.query(entries.map((entry) => entry.ref), {
23700
+ ownerUserId,
23701
+ ...signal === void 0 ? {} : { signal }
23702
+ });
23703
+ const snapshots = new Map(owner.map((item) => [conversationListPreferenceRefKey(item.ref), item]));
23704
+ const evidenceByOwnerRef = /* @__PURE__ */ new Map();
23705
+ for (const entry of entries) {
23706
+ const key = conversationListPreferenceRefKey(entry.ref);
23707
+ const current = evidenceByOwnerRef.get(key);
23708
+ evidenceByOwnerRef.set(key, {
23709
+ sequence: Math.max(current?.sequence ?? 0, entry.evidence.sequence),
23710
+ activityAtMillis: Math.max(current?.activityAtMillis ?? 0, entry.evidence.activityAtMillis)
23711
+ });
23712
+ }
23713
+ const staleSnapshots = [...evidenceByOwnerRef.entries()].flatMap(([key, evidence]) => {
23714
+ const snapshot = snapshots.get(key);
23715
+ const evidenceAvailable = evidence.sequence > 0 || evidence.activityAtMillis > 0;
23716
+ return snapshot?.visibilityState === 2 && evidenceAvailable && !conversationListPreferenceIsDismissed(snapshot, evidence) ? [snapshot] : [];
23717
+ });
23718
+ if (staleSnapshots.length > 0) this.preference.restoreIfUnchanged(staleSnapshots, { ownerUserId }).catch(() => void 0);
23719
+ return { items: resolved.map((result, index) => {
23720
+ if (result.status === "rejected") return {
23721
+ ...handles[index],
23722
+ hidden: false
23723
+ };
23724
+ const entry = result.value;
23725
+ const key = conversationListPreferenceRefKey(entry.ref);
23726
+ const snapshot = snapshots.get(key);
23727
+ const evidence = evidenceByOwnerRef.get(key) ?? entry.evidence;
23728
+ const hidden = (evidence.sequence > 0 || evidence.activityAtMillis > 0) && snapshot !== void 0 && conversationListPreferenceIsDismissed(snapshot, evidence);
23729
+ return {
23730
+ entryKind: entry.entryKind,
23731
+ entryRef: entry.entryRef,
23732
+ hidden
23733
+ };
23734
+ }) };
23735
+ }
23736
+ async setVisibility(entryKind, entryRef, hidden, signal) {
23737
+ const entry = await this.resolveEntry(entryKind, entryRef, signal);
23738
+ const operation = {
23739
+ ownerUserId: entry.ownerUserId,
23740
+ ...signal === void 0 ? {} : { signal }
23741
+ };
23742
+ if (hidden) await this.preference.dismiss(entry.ref, entry.evidence, operation);
23743
+ else await this.preference.restore([entry.ref], operation);
23744
+ await this.invalidateBestEffort();
23745
+ }
23746
+ async restoreSource(source, signal) {
23747
+ const entry = await this.source.chatConversationListPreferenceEntry(source.sourceRef, signal);
23748
+ await this.restoreEntries([entry], signal);
23749
+ }
23750
+ async openBotContactConversation(botRef, options = {}) {
23751
+ const previous = await this.bot.botConversationListPreferenceEntry(botRef).catch(() => void 0);
23752
+ const source = await this.bot.openBotChat(botRef, options);
23753
+ (previous === void 0 ? this.restoreSource(source) : this.restoreBotConversation(previous, source)).catch(() => void 0);
23754
+ return source;
23755
+ }
23756
+ /** Restores the exact owner identities when a Bot directory entry resolves from direct Bot to Chat. */
23757
+ async restoreBotConversation(previous, source, signal) {
23758
+ const current = await this.source.chatConversationListPreferenceEntry(source.sourceRef, signal);
23759
+ if (previous.ownerUserId !== current.ownerUserId) throw new ArkmePluginError("login-context-changed", "登录账号已切换,请重试当前操作", false, 409);
23760
+ const previousIsCurrentChat = previous.ref.entityKind === 1 && previous.ref.entityUid === current.ref.entityUid;
23761
+ if (current.ref.entityKind !== 1 || previous.ref.entityKind !== 2 && !previousIsCurrentChat) throw new ArkmePluginError("bot-conversation-preference-identity-changed", "Bot 会话归属已变化,请刷新后重试", false, 409);
23762
+ await this.restoreEntries([previous, current], signal);
23763
+ }
23764
+ async restoreEntries(entries, signal) {
23765
+ const ownerUserId = entries[0]?.ownerUserId;
23766
+ if (ownerUserId === void 0 || entries.some((entry) => entry.ownerUserId !== ownerUserId)) throw new ArkmePluginError("login-context-changed", "登录账号已切换,请重试当前操作", false, 409);
23767
+ await this.preference.restore(entries.map((entry) => entry.ref), {
23768
+ ownerUserId,
23769
+ ...signal === void 0 ? {} : { signal }
23770
+ });
23771
+ await this.invalidateBestEffort();
23772
+ }
23773
+ async resolveEntry(entryKind, entryRef, signal) {
23774
+ return entryKind === "source" ? await this.source.chatConversationListPreferenceEntry(entryRef, signal) : await this.bot.botConversationListPreferenceEntry(entryRef);
23775
+ }
23776
+ async invalidateBestEffort() {
23777
+ try {
23778
+ await this.invalidation.invalidateConversationListPreferenceForCurrentSession();
23779
+ } catch {}
23780
+ }
23781
+ };
23782
+ function isFatalResolutionError(error) {
23783
+ if (error instanceof Error && error.name === "AbortError") return true;
23784
+ return error !== null && typeof error === "object" && "code" in error && error.code === "login-context-changed";
23785
+ }
23786
+ //#endregion
22838
23787
  //#region src/services/contact-service.ts
22839
23788
  const CONTACT_REF_TTL_MS = 10 * 6e4;
22840
23789
  const MAX_CONTACT_REFS = 512;
@@ -52639,9 +53588,9 @@ var GroupAiPolishService = class {
52639
53588
  try {
52640
53589
  config = await this.queryGroupAiPolishConfig(chatSessionUid, session, options.signal);
52641
53590
  } catch {
52642
- return await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, void 0, void 0, options.signal, options);
53591
+ return await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, void 0, options.contentPayload, options.signal, options);
52643
53592
  }
52644
- if (!config.enabled || config.activeRuleUid === "") return await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, void 0, void 0, options.signal, options);
53593
+ if (!config.enabled || config.activeRuleUid === "") return await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, void 0, options.contentPayload, options.signal, options);
52645
53594
  const taskUid = randomUUID();
52646
53595
  let polished;
52647
53596
  try {
@@ -52657,7 +53606,7 @@ var GroupAiPolishService = class {
52657
53606
  }, session, options.signal);
52658
53607
  polished = this.aiPolishTextResult(data);
52659
53608
  } catch (error) {
52660
- const sent = await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, void 0, void 0, options.signal, options);
53609
+ const sent = await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, void 0, options.contentPayload, options.signal, options);
52661
53610
  return this.withFailedAiPolishRetry(sent, sourceRef, chatSessionUid, relationUid, recordUid, originalText, 1, session.userId, safeFailureMessage$2(error));
52662
53611
  }
52663
53612
  if (polished.state === 1 && polished.action === 1 && polished.polishedText !== "") {
@@ -52673,7 +53622,7 @@ var GroupAiPolishService = class {
52673
53622
  model: polished.modelVersion,
52674
53623
  prompt: polished.promptVersion,
52675
53624
  ...Object.keys(polished.extra).length === 0 ? {} : { extra: polished.extra }
52676
- }, void 0, options.signal, options),
53625
+ }, options.contentPayload, options.signal, options),
52677
53626
  aiPolish: {
52678
53627
  state: "polished",
52679
53628
  originalText,
@@ -52681,7 +53630,7 @@ var GroupAiPolishService = class {
52681
53630
  }
52682
53631
  };
52683
53632
  }
52684
- const sent = await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, void 0, void 0, options.signal, options);
53633
+ const sent = await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, void 0, options.contentPayload, options.signal, options);
52685
53634
  if (polished.action === 2) return {
52686
53635
  ...sent,
52687
53636
  aiPolish: {
@@ -53650,6 +54599,79 @@ var InterwovenService = class {
53650
54599
  }
53651
54600
  };
53652
54601
  //#endregion
54602
+ //#region src/link-metadata.ts
54603
+ function arkmeIsGenericLinkMetadataTitle(rawTitle) {
54604
+ const title = rawTitle?.trim() ?? "";
54605
+ if (title === "") return true;
54606
+ if (title === "分享链接") return true;
54607
+ if (title === "即我" || title === "即我-对话发现自我" || title === "即我 - 对话发现自我") return true;
54608
+ if (title === "即我-进入Ta的世界" || title === "即我 - 进入Ta的世界") return true;
54609
+ if (title.toLowerCase().includes("this page could not be found")) return true;
54610
+ return /^404\b/u.test(title);
54611
+ }
54612
+ function linkUrl(rawUrl) {
54613
+ return rawUrl instanceof URL ? new URL(rawUrl.href) : new URL(rawUrl);
54614
+ }
54615
+ function arkmeExtensionShareRefFromLink(rawUrl) {
54616
+ let url;
54617
+ try {
54618
+ url = linkUrl(rawUrl);
54619
+ } catch {
54620
+ return;
54621
+ }
54622
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "") return void 0;
54623
+ const host = url.hostname.replace(/^www\./iu, "").toLowerCase();
54624
+ if (![
54625
+ "jiwo.cc",
54626
+ "app.arkme.ai",
54627
+ "app-test.arkme.ai",
54628
+ "jotmo-app.senguo.me"
54629
+ ].includes(host)) return void 0;
54630
+ const parts = url.pathname.split("/").filter(Boolean);
54631
+ const shareRef = parts.length === 4 && parts[0] === "app" && parts[1] === "share" && parts[2] === "extension" ? parts[3] : void 0;
54632
+ return shareRef !== void 0 && /^extshare_[0-9a-f]{32}$/u.test(shareRef) ? shareRef : void 0;
54633
+ }
54634
+ function arkmeKnownLinkMetadataFallback(rawUrl) {
54635
+ try {
54636
+ const url = linkUrl(rawUrl);
54637
+ const host = url.hostname.replace(/^www\./iu, "").toLowerCase();
54638
+ const parts = url.pathname.split("/").filter(Boolean);
54639
+ if (host === "github.com") {
54640
+ if (parts.length >= 4 && parts[2] === "pull") return {
54641
+ url: url.href,
54642
+ title: `Pull Request #${parts[3]} · ${parts[0]}/${parts[1]}`
54643
+ };
54644
+ if (parts.length >= 2) return {
54645
+ url: url.href,
54646
+ title: `${parts[0]}/${parts[1]}`
54647
+ };
54648
+ }
54649
+ if ((host === "codeup.aliyun.com" || host.endsWith(".codeup.aliyun.com")) && parts.includes("change")) {
54650
+ const changeIndex = parts.indexOf("change");
54651
+ const repository = changeIndex >= 2 ? parts[changeIndex - 1] : void 0;
54652
+ const changeNo = parts[changeIndex + 1];
54653
+ if (repository !== void 0 && changeNo !== void 0) return {
54654
+ url: url.href,
54655
+ title: `${repository} · Change #${changeNo}`
54656
+ };
54657
+ }
54658
+ } catch {}
54659
+ return null;
54660
+ }
54661
+ function arkmeRequiredLinkMetadataFallback(rawUrl) {
54662
+ const url = linkUrl(rawUrl);
54663
+ const siteName = url.hostname.replace(/^www\./iu, "");
54664
+ const known = arkmeKnownLinkMetadataFallback(url);
54665
+ return known === null ? {
54666
+ url: url.href,
54667
+ title: "分享链接",
54668
+ siteName
54669
+ } : {
54670
+ ...known,
54671
+ siteName
54672
+ };
54673
+ }
54674
+ //#endregion
53653
54675
  //#region src/services/link-metadata-service.ts
53654
54676
  const MAX_LINK_METADATA_BYTES = 2 * 1024 * 1024;
53655
54677
  const LINK_METADATA_TIMEOUT_MS = 8e3;
@@ -53658,8 +54680,10 @@ const MAX_LINK_METADATA_REDIRECTS = 3;
53658
54680
  const MAX_LINK_TITLE_LENGTH = 300;
53659
54681
  const MAX_LINK_URL_LENGTH = 4096;
53660
54682
  const blockedAddresses = new BlockList();
54683
+ const proxySyntheticAddresses = new BlockList();
53661
54684
  const globalIpv6Addresses = new BlockList();
53662
54685
  globalIpv6Addresses.addSubnet("2000::", 3, "ipv6");
54686
+ proxySyntheticAddresses.addSubnet("198.18.0.0", 15, "ipv4");
53663
54687
  for (const [network, prefix] of [
53664
54688
  ["0.0.0.0", 8],
53665
54689
  ["10.0.0.0", 8],
@@ -53697,6 +54721,14 @@ function isPublicNetworkAddress(address) {
53697
54721
  if (family === 6) return globalIpv6Addresses.check(address, "ipv6") && !blockedAddresses.check(address, "ipv6");
53698
54722
  return false;
53699
54723
  }
54724
+ function isProxySyntheticNetworkAddress(address) {
54725
+ const mapped = ipv4FromMappedIpv6(address);
54726
+ if (mapped !== void 0) return isIP(mapped) === 4 && proxySyntheticAddresses.check(mapped, "ipv4");
54727
+ return isIP(address) === 4 && proxySyntheticAddresses.check(address, "ipv4");
54728
+ }
54729
+ function isAllowedLinkMetadataAddress(address) {
54730
+ return isPublicNetworkAddress(address) || isProxySyntheticNetworkAddress(address);
54731
+ }
53700
54732
  function safeWebUrl(raw) {
53701
54733
  let url;
53702
54734
  try {
@@ -53737,6 +54769,10 @@ function tagAttributes(tag) {
53737
54769
  }
53738
54770
  return attributes;
53739
54771
  }
54772
+ function preferredDocumentTitle(candidates) {
54773
+ for (const candidate of candidates) if (candidate !== void 0 && !arkmeIsGenericLinkMetadataTitle(candidate)) return candidate;
54774
+ return "";
54775
+ }
53740
54776
  function documentMetadata(url, html) {
53741
54777
  const metadata = /* @__PURE__ */ new Map();
53742
54778
  for (const match of html.matchAll(/<meta\b[^>]*>/giu)) {
@@ -53747,7 +54783,11 @@ function documentMetadata(url, html) {
53747
54783
  if (content !== "") metadata.set(property, content);
53748
54784
  }
53749
54785
  const documentTitle = /<title\b[^>]*>([\s\S]*?)<\/title\s*>/iu.exec(html)?.[1];
53750
- const title = metadata.get("og:title") ?? metadata.get("twitter:title") ?? (documentTitle === void 0 ? "" : normalizedTitle(documentTitle));
54786
+ const title = preferredDocumentTitle([
54787
+ metadata.get("og:title"),
54788
+ metadata.get("twitter:title"),
54789
+ documentTitle === void 0 ? void 0 : normalizedTitle(documentTitle)
54790
+ ]);
53751
54791
  if (title === "") return null;
53752
54792
  const description = metadata.get("og:description") ?? metadata.get("description");
53753
54793
  const siteName = metadata.get("og:site_name") ?? url.hostname.replace(/^www\./iu, "");
@@ -53774,8 +54814,13 @@ function missingDnsAnswer(error) {
53774
54814
  }
53775
54815
  var NodeArkmeHostAddressResolver = class {
53776
54816
  resolverFactory;
53777
- constructor(resolverFactory = () => new Resolver()) {
54817
+ systemLookup;
54818
+ constructor(resolverFactory = () => new Resolver(), systemLookup = async (hostname) => await lookup(hostname, {
54819
+ all: true,
54820
+ verbatim: true
54821
+ })) {
53778
54822
  this.resolverFactory = resolverFactory;
54823
+ this.systemLookup = systemLookup;
53779
54824
  }
53780
54825
  async lookup(hostname, options = {}) {
53781
54826
  options.signal?.throwIfAborted();
@@ -53802,7 +54847,13 @@ var NodeArkmeHostAddressResolver = class {
53802
54847
  }))];
53803
54848
  } catch (error) {
53804
54849
  options.signal?.throwIfAborted();
53805
- throw error;
54850
+ try {
54851
+ const addresses = await this.systemLookup(hostname);
54852
+ options.signal?.throwIfAborted();
54853
+ return addresses;
54854
+ } catch {
54855
+ throw error;
54856
+ }
53806
54857
  } finally {
53807
54858
  options.signal?.removeEventListener("abort", abort);
53808
54859
  }
@@ -53913,7 +54964,7 @@ var NodeArkmeLinkDocumentReader = class {
53913
54964
  } catch (error) {
53914
54965
  throw pluginError(error);
53915
54966
  }
53916
- if (addresses.length === 0 || addresses.some((item) => !isPublicNetworkAddress(item.address))) throw new ArkmePluginError("link-metadata-url-unsafe", "该网址不允许解析标题", false, 400);
54967
+ if (addresses.length === 0 || addresses.some((item) => !isAllowedLinkMetadataAddress(item.address))) throw new ArkmePluginError("link-metadata-url-unsafe", "该网址不允许解析标题", false, 400);
53917
54968
  return await this.transport.read(url, addresses, options);
53918
54969
  }
53919
54970
  };
@@ -56513,9 +57564,6 @@ var RecordingImportCoordinator = class {
56513
57564
  job = await this.transition(job, "accepted", {
56514
57565
  sourceHandle: "",
56515
57566
  sha256: "",
56516
- sessionId: void 0,
56517
- childId: void 0,
56518
- childFinished: void 0,
56519
57567
  uploadCheckpoint: void 0
56520
57568
  });
56521
57569
  await this.source.discard(sourceHandle).catch(() => void 0);
@@ -57138,8 +58186,78 @@ function safeFailureMessage$1(error) {
57138
58186
  if (error instanceof Error && error.message.trim() !== "") return error.message;
57139
58187
  return "未知错误";
57140
58188
  }
58189
+ function recordingImportErrorCode(error) {
58190
+ if (error === null || typeof error !== "object" || !("code" in error)) return void 0;
58191
+ return typeof error.code === "string" ? error.code : void 0;
58192
+ }
57141
58193
  const RECORDING_IMPORT_VISIBLE_LIMIT = 20;
57142
58194
  const RECORDING_IMPORT_UNRESOLVED_LIMIT = 20;
58195
+ const RECORDING_IMPORT_HISTORY_MAX_PAGE_SIZE = 50;
58196
+ const RECORDING_IMPORT_SESSION_REF_PREFIX = "arkme-recording-import-session-v1";
58197
+ function recordingImportOwnerStatus(task) {
58198
+ const owner = task.session;
58199
+ const ownerProgress = task.progress;
58200
+ if (!owner.hasFinishedUpload) return {
58201
+ status: "uploading",
58202
+ statusDetail: "上传中"
58203
+ };
58204
+ if (task.processingCompleted) {
58205
+ if (ownerProgress?.displayStatus === "partial") return {
58206
+ status: "partial",
58207
+ statusDetail: "部分完成"
58208
+ };
58209
+ if (ownerProgress?.displayStatus === "failed") return {
58210
+ status: "failed",
58211
+ statusDetail: "处理失败"
58212
+ };
58213
+ if (ownerProgress?.displayStatus === "completed") return {
58214
+ status: "completed",
58215
+ statusDetail: "导入完成"
58216
+ };
58217
+ return {
58218
+ status: "completed",
58219
+ statusDetail: "已完成"
58220
+ };
58221
+ }
58222
+ if ([
58223
+ "completed",
58224
+ "partial",
58225
+ "failed"
58226
+ ].includes(ownerProgress?.displayStatus ?? "")) return {
58227
+ status: "processing",
58228
+ statusDetail: "处理中"
58229
+ };
58230
+ switch (ownerProgress?.displayStatus) {
58231
+ case "speaker-waiting": return {
58232
+ status: "speaker-waiting",
58233
+ statusDetail: "等待识别说话人"
58234
+ };
58235
+ case "speaker-recognizing": return {
58236
+ status: "speaker-recognizing",
58237
+ statusDetail: "说话人识别中"
58238
+ };
58239
+ case "transcript-waiting": return {
58240
+ status: "transcript-waiting",
58241
+ statusDetail: "等待转写"
58242
+ };
58243
+ case "transcribing": return {
58244
+ status: "transcribing",
58245
+ statusDetail: "转写中"
58246
+ };
58247
+ case "unavailable": return {
58248
+ status: "unavailable",
58249
+ statusDetail: "状态不可用"
58250
+ };
58251
+ default: return {
58252
+ status: "waiting",
58253
+ statusDetail: "等待中"
58254
+ };
58255
+ }
58256
+ }
58257
+ function recordingImportProcessingDuration(timing) {
58258
+ if (timing === void 0 || timing.timingState === "unavailable") return void 0;
58259
+ return Math.max(0, timing.totalDurationMillis);
58260
+ }
57143
58261
  var RecordingService = class {
57144
58262
  runtime;
57145
58263
  recordingImports;
@@ -57148,12 +58266,14 @@ var RecordingService = class {
57148
58266
  media;
57149
58267
  userCandidates;
57150
58268
  recordingImportSource;
58269
+ recordingImportOwnerGateway;
57151
58270
  constructor(runtime, dependencies) {
57152
58271
  this.runtime = runtime;
57153
58272
  this.profile = dependencies.profile;
57154
58273
  this.media = dependencies.media;
57155
58274
  this.userCandidates = dependencies.userCandidates;
57156
58275
  this.recordingImportSource = dependencies.recordingImportSource;
58276
+ this.recordingImportOwnerGateway = dependencies.recordingImportOwnerGateway;
57157
58277
  this.recordingImports = new RecordingImportCoordinator(runtime.stateStore, dependencies.recordingImportGateway, dependencies.recordingImportSource, async () => (await runtime.requireSession()).userId);
57158
58278
  }
57159
58279
  dispose() {
@@ -57332,11 +58452,26 @@ var RecordingService = class {
57332
58452
  }
57333
58453
  async recordingImportPreflight(fileNames, signal) {
57334
58454
  this.assertWorkbenchEnabled();
57335
- const normalized = [...new Set(fileNames.map((value) => value.trim()).filter((value) => value !== ""))];
57336
- if (normalized.length === 0 || normalized.length > 100 || normalized.some((value) => value.length > 255)) throw new ArkmePluginError("recording-import-preflight-invalid", "待检查录音文件名无效", false);
57337
- const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/check-exist-same-orig", { orig_names: normalized }, await this.runtime.requireSession(), signal);
57338
- const duplicates = new Set(listValue$5(data.exist_names).map(stringValue$1).map((value) => value.trim()).filter((value) => value !== ""));
57339
- return { duplicateFileNames: normalized.filter((value) => duplicates.has(value)) };
58455
+ const candidatesByKey = /* @__PURE__ */ new Map();
58456
+ for (const value of fileNames) {
58457
+ const candidate = value.trim();
58458
+ const key = recordingImportFileNameKey(candidate);
58459
+ if (key !== "" && !candidatesByKey.has(key)) candidatesByKey.set(key, candidate);
58460
+ }
58461
+ const candidates = [...candidatesByKey.values()];
58462
+ if (candidates.length === 0 || candidates.some((value) => value.length > 255)) throw new ArkmePluginError("recording-import-preflight-invalid", "待检查录音文件名无效", false);
58463
+ const session = await this.runtime.requireSession();
58464
+ const unresolvedLocalFileNameKeys = new Set((await this.runtime.stateStore.listRecordingImportJobs(session.userId)).filter((job) => !["accepted", "cancelled"].includes(job.phase)).map((job) => recordingImportFileNameKey(job.fileName)));
58465
+ const audioOwnerFileNameKeys = /* @__PURE__ */ new Set();
58466
+ for (const value of await this.recordingImportOwnerGateway.findExistingFileNames({
58467
+ viewerUserId: session.userId,
58468
+ fileNames: candidates,
58469
+ ...signal === void 0 ? {} : { signal }
58470
+ })) {
58471
+ const key = recordingImportFileNameKey(value);
58472
+ if (key !== "") audioOwnerFileNameKeys.add(key);
58473
+ }
58474
+ return { duplicateFileNames: candidates.filter((value) => unresolvedLocalFileNameKeys.has(recordingImportFileNameKey(value)) || audioOwnerFileNameKeys.has(recordingImportFileNameKey(value))) };
57340
58475
  }
57341
58476
  async acceptRecordingImport(sourceHandle, metadata, expectedUserId) {
57342
58477
  this.assertWorkbenchEnabled();
@@ -57360,7 +58495,8 @@ var RecordingService = class {
57360
58495
  await this.recordingImportSource.discard(sourceHandle).catch(() => void 0);
57361
58496
  return toPublicRecordingImportJob(existing, await this.recordingImportRef(existing));
57362
58497
  }
57363
- if (unresolvedJobs.some((job) => job.fileName === metadata.fileName)) throw new ArkmePluginError("recording-import-duplicate-in-progress", "同名录音正在导入,请等待当前任务完成或取消后重试", false, 409);
58498
+ const fileNameKey = recordingImportFileNameKey(metadata.fileName);
58499
+ if (unresolvedJobs.some((job) => recordingImportFileNameKey(job.fileName) === fileNameKey)) throw new ArkmePluginError("recording-import-duplicate-in-progress", "同名录音正在导入,请等待当前任务完成或取消后重试", false, 409);
57364
58500
  if (unresolvedJobs.length >= RECORDING_IMPORT_UNRESOLVED_LIMIT) throw new ArkmePluginError("recording-import-pending-limit", "待处理录音过多,请先重试或取消现有任务", false, 409);
57365
58501
  const probe = await this.recordingImportSource.inspect(sourceHandle, metadata);
57366
58502
  session = await this.requireRecordingImportSession(expectedUserId);
@@ -57402,7 +58538,7 @@ var RecordingService = class {
57402
58538
  if (job === void 0) throw new ArkmePluginError("recording-import-not-found", "录音导入任务不存在", false, 404);
57403
58539
  return toPublicRecordingImportJob(job, importRef);
57404
58540
  }
57405
- async recordingImportList() {
58541
+ async recordingImportList(signal) {
57406
58542
  this.assertWorkbenchEnabled();
57407
58543
  const session = await this.runtime.requireSession();
57408
58544
  const jobs = await this.runtime.stateStore.listRecordingImportJobs(session.userId);
@@ -57413,9 +58549,106 @@ var RecordingService = class {
57413
58549
  ].includes(job.phase)) this.runRecordingImport(job);
57414
58550
  const newestFirst = (left, right) => right.createdAtMillis - left.createdAtMillis;
57415
58551
  const unresolved = jobs.filter((job) => !["accepted", "cancelled"].includes(job.phase)).sort(newestFirst);
57416
- const accepted = jobs.filter((job) => job.phase === "accepted").sort(newestFirst).slice(0, Math.max(0, RECORDING_IMPORT_VISIBLE_LIMIT - unresolved.length));
57417
- const visible = [...unresolved, ...accepted].sort(newestFirst);
57418
- return await Promise.all(visible.map(async (job) => toPublicRecordingImportJob(job, await this.recordingImportRef(job))));
58552
+ let activeOwnerTasks;
58553
+ let owner = { state: "available" };
58554
+ try {
58555
+ activeOwnerTasks = (await this.recordingImportOwnerGateway.listOwnerTasks({
58556
+ viewerUserId: session.userId,
58557
+ scope: "active",
58558
+ toMillis: Date.now(),
58559
+ limit: RECORDING_IMPORT_VISIBLE_LIMIT,
58560
+ offset: 0,
58561
+ ...signal === void 0 ? {} : { signal }
58562
+ })).tasks;
58563
+ } catch (error) {
58564
+ if (signal?.aborted === true || recordingImportErrorCode(error) === "recording-import-account-mismatch") throw error;
58565
+ owner = {
58566
+ state: "unavailable",
58567
+ message: "Audio 上传任务读取失败,请稍后重试"
58568
+ };
58569
+ }
58570
+ const visibleLocal = unresolved;
58571
+ const projectedLocal = await Promise.all(visibleLocal.map(async (job) => toPublicRecordingImportJob(job, await this.recordingImportRef(job))));
58572
+ if (activeOwnerTasks === void 0) return {
58573
+ items: projectedLocal,
58574
+ owner
58575
+ };
58576
+ const ownerRefKey = await this.recordingRefKey(RECORDING_IMPORT_SESSION_REF_PREFIX);
58577
+ const unresolvedOwnerSessionIds = new Set(unresolved.flatMap((job) => job.sessionId === void 0 ? [] : [job.sessionId]));
58578
+ const projectedOwner = await Promise.all(activeOwnerTasks.filter((task) => !unresolvedOwnerSessionIds.has(task.session.sessionId)).map(async (task) => await this.projectRecordingImportOwnerTask(task, session.userId, ownerRefKey)));
58579
+ return {
58580
+ items: [...projectedLocal, ...projectedOwner].sort((left, right) => right.createdAtMillis - left.createdAtMillis),
58581
+ owner
58582
+ };
58583
+ }
58584
+ async recordingImportHistory(input, signal) {
58585
+ this.assertWorkbenchEnabled();
58586
+ const session = await this.runtime.requireSession();
58587
+ const toMillis = Math.trunc(input.toMillis);
58588
+ const limit = Math.trunc(input.limit);
58589
+ const offset = Math.trunc(input.offset);
58590
+ if (!Number.isSafeInteger(toMillis) || toMillis <= 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > RECORDING_IMPORT_HISTORY_MAX_PAGE_SIZE || !Number.isSafeInteger(offset) || offset < 0) throw new ArkmePluginError("recording-import-history-invalid", "已完成任务查询参数无效", false);
58591
+ const page = await this.recordingImportOwnerGateway.listOwnerTasks({
58592
+ viewerUserId: session.userId,
58593
+ scope: "completed",
58594
+ toMillis,
58595
+ limit,
58596
+ offset,
58597
+ ...signal === void 0 ? {} : { signal }
58598
+ });
58599
+ if (page.tasks.some((task) => !task.session.hasFinishedUpload || !task.processingCompleted)) throw new ArkmePluginError("recording-import-history-owner-invalid", "已完成任务数据暂不可用,请稍后重试", true, 502);
58600
+ const ownerRefKey = await this.recordingRefKey(RECORDING_IMPORT_SESSION_REF_PREFIX);
58601
+ return {
58602
+ items: await Promise.all(page.tasks.map(async (task) => await this.projectRecordingImportHistoryItem(task, session.userId, ownerRefKey))),
58603
+ ...page.total === void 0 ? {} : { total: page.total },
58604
+ offset,
58605
+ hasMore: page.hasMore
58606
+ };
58607
+ }
58608
+ async updateRecordingImportSessionStart(sessionRef, startAtMillis, signal) {
58609
+ this.assertWorkbenchEnabled();
58610
+ const start = Math.trunc(startAtMillis);
58611
+ if (!isRecordingInstantOnOrAfterUnixEpoch(start) || start > Date.now()) throw new ArkmePluginError("recording-import-start-invalid", "录音开始时间无效", false);
58612
+ const session = await this.runtime.requireSession();
58613
+ const target = await this.openRecordingImportSessionRef(sessionRef);
58614
+ await this.assertRecordingImportOwnerMutationSafe(session.userId, target.sessionId);
58615
+ if (start + (await this.recordingImportOwnerGateway.loadOwnerSession({
58616
+ viewerUserId: session.userId,
58617
+ sessionId: target.sessionId,
58618
+ ...signal === void 0 ? {} : { signal }
58619
+ })).durationMillis > Date.now()) throw new ArkmePluginError("recording-import-end-invalid", "录音结束时间不能晚于当前时间", false);
58620
+ await this.recordingImportOwnerGateway.updateOwnerSessionStart({
58621
+ viewerUserId: session.userId,
58622
+ sessionId: target.sessionId,
58623
+ startAtMillis: start,
58624
+ ...signal === void 0 ? {} : { signal }
58625
+ });
58626
+ }
58627
+ async updateRecordingImportSessionOwnership(sessionRef, ownership, signal) {
58628
+ this.assertWorkbenchEnabled();
58629
+ const session = await this.runtime.requireSession();
58630
+ const target = await this.openRecordingImportSessionRef(sessionRef);
58631
+ const belongUserId = ownership === "self" ? session.userId : 0;
58632
+ await this.assertRecordingImportOwnerMutationSafe(session.userId, target.sessionId);
58633
+ await this.recordingImportOwnerGateway.updateOwnerSessionOwnership({
58634
+ viewerUserId: session.userId,
58635
+ sessionId: target.sessionId,
58636
+ belongUserId,
58637
+ ...signal === void 0 ? {} : { signal }
58638
+ });
58639
+ }
58640
+ async deleteRecordingImportSession(sessionRef, signal) {
58641
+ this.assertWorkbenchEnabled();
58642
+ const session = await this.runtime.requireSession();
58643
+ const target = await this.openRecordingImportSessionRef(sessionRef);
58644
+ await this.assertRecordingImportOwnerMutationSafe(session.userId, target.sessionId);
58645
+ const jobs = await this.runtime.stateStore.listRecordingImportJobs(session.userId);
58646
+ await this.recordingImportOwnerGateway.deleteOwnerSession({
58647
+ viewerUserId: session.userId,
58648
+ sessionId: target.sessionId,
58649
+ ...signal === void 0 ? {} : { signal }
58650
+ });
58651
+ await Promise.all(jobs.filter((job) => job.sessionId === target.sessionId).map(async (job) => await this.runtime.stateStore.removeRecordingImportJob(session.userId, job.jobId)));
57419
58652
  }
57420
58653
  async retryRecordingImport(importRef, expectedRevision) {
57421
58654
  this.assertWorkbenchEnabled();
@@ -57494,6 +58727,81 @@ var RecordingService = class {
57494
58727
  throw error;
57495
58728
  }
57496
58729
  }
58730
+ async projectRecordingImportHistoryItem(task, viewerUserId, ownerRefKey) {
58731
+ const owner = task.session;
58732
+ const ownerProgress = task.progress;
58733
+ const status = recordingImportOwnerStatus(task);
58734
+ const processingDurationMillis = recordingImportProcessingDuration(ownerProgress?.processingTiming);
58735
+ return {
58736
+ taskKey: await this.recordingImportTaskKey(owner.sessionId, viewerUserId, ownerRefKey),
58737
+ sessionRef: await this.recordingImportSessionRef(owner, viewerUserId, ownerRefKey),
58738
+ ownership: owner.ownership,
58739
+ fileName: owner.fileName,
58740
+ fileSize: owner.fileSize,
58741
+ parsedSize: owner.parsedSize,
58742
+ durationMillis: owner.durationMillis,
58743
+ startAtMillis: owner.startAtMillis,
58744
+ endAtMillis: owner.endAtMillis,
58745
+ progress: 1,
58746
+ status: status.status,
58747
+ statusDetail: status.statusDetail,
58748
+ ...processingDurationMillis === void 0 ? {} : { processingDurationMillis },
58749
+ createdAtMillis: owner.createdAtMillis,
58750
+ updatedAtMillis: owner.updatedAtMillis,
58751
+ ...ownerProgress?.processingTiming === void 0 ? {} : { processing: ownerProgress.processingTiming }
58752
+ };
58753
+ }
58754
+ async projectRecordingImportOwnerTask(task, viewerUserId, ownerRefKey) {
58755
+ const owner = task.session;
58756
+ const ownerProgress = task.progress;
58757
+ const status = recordingImportOwnerStatus(task);
58758
+ const processingDurationMillis = recordingImportProcessingDuration(ownerProgress?.processingTiming);
58759
+ return {
58760
+ kind: "owner",
58761
+ taskKey: await this.recordingImportTaskKey(owner.sessionId, viewerUserId, ownerRefKey),
58762
+ sessionRef: await this.recordingImportSessionRef(owner, viewerUserId, ownerRefKey),
58763
+ ownership: owner.ownership,
58764
+ fileName: owner.fileName,
58765
+ fileSize: owner.fileSize,
58766
+ parsedSize: owner.parsedSize,
58767
+ durationMillis: owner.durationMillis,
58768
+ startAtMillis: owner.startAtMillis,
58769
+ endAtMillis: owner.endAtMillis,
58770
+ progress: owner.hasFinishedUpload ? 1 : owner.fileSize <= 0 ? 0 : Math.min(1, owner.parsedSize / owner.fileSize),
58771
+ status: status.status,
58772
+ statusDetail: status.statusDetail,
58773
+ ...processingDurationMillis === void 0 ? {} : { processingDurationMillis },
58774
+ createdAtMillis: owner.createdAtMillis,
58775
+ updatedAtMillis: owner.updatedAtMillis,
58776
+ ...ownerProgress?.processingTiming === void 0 ? {} : { processing: ownerProgress.processingTiming }
58777
+ };
58778
+ }
58779
+ async recordingImportTaskKey(sessionId, viewerUserId, refKey) {
58780
+ const userId = viewerUserId ?? (await this.runtime.requireSession()).userId;
58781
+ return createHmac("sha256", refKey ?? await this.recordingRefKey(RECORDING_IMPORT_SESSION_REF_PREFIX)).update(`${String(userId)}\0${sessionId}`).digest("base64url");
58782
+ }
58783
+ async recordingImportSessionRef(owner, viewerUserId, refKey) {
58784
+ const payload = {
58785
+ version: 1,
58786
+ viewerUserId: viewerUserId ?? (await this.runtime.requireSession()).userId,
58787
+ sessionId: owner.sessionId
58788
+ };
58789
+ return this.sealRecordingRefWithKey(RECORDING_IMPORT_SESSION_REF_PREFIX, payload, refKey ?? await this.recordingRefKey(RECORDING_IMPORT_SESSION_REF_PREFIX));
58790
+ }
58791
+ async openRecordingImportSessionRef(sessionRef) {
58792
+ const raw = await this.openRecordingRef(RECORDING_IMPORT_SESSION_REF_PREFIX, sessionRef);
58793
+ const payload = {
58794
+ version: 1,
58795
+ viewerUserId: numberValue$5(raw.viewerUserId),
58796
+ sessionId: stringValue$1(raw.sessionId).trim()
58797
+ };
58798
+ if (payload.sessionId === "") throw new ArkmePluginError("recording-import-session-ref-invalid", "录音导入任务引用无效", false);
58799
+ return payload;
58800
+ }
58801
+ async assertRecordingImportOwnerMutationSafe(userId, sessionId) {
58802
+ if ((await this.runtime.stateStore.listRecordingImportJobs(userId)).find((job) => job.sessionId === sessionId && !["accepted", "cancelled"].includes(job.phase)) === void 0) return;
58803
+ throw new ArkmePluginError("recording-import-owner-mutation-active", "录音仍在本机上传任务中,请先重试或取消当前任务", false, 409);
58804
+ }
57497
58805
  async recordingCalendar(fromStamp, toStamp, signal) {
57498
58806
  const from = Math.trunc(fromStamp);
57499
58807
  const to = Math.trunc(toStamp);
@@ -57874,12 +59182,115 @@ var RecordingService = class {
57874
59182
  //#endregion
57875
59183
  //#region src/services/recording-import-gateway.ts
57876
59184
  const AUDIO_SESSION_RECOVERY_LIMIT = 500;
59185
+ const AUDIO_IMPORT_DUPLICATE_BATCH_SIZE = 100;
59186
+ const AUDIO_IMPORT_PROGRESS_BATCH_SIZE = 100;
59187
+ const AUDIO_IMPORT_OWNER_PAGE_SIZE = 100;
59188
+ const PROCESSING_STAGES = /* @__PURE__ */ new Set([
59189
+ "sd",
59190
+ "vad",
59191
+ "asr"
59192
+ ]);
59193
+ const PROCESSING_OUTCOMES = /* @__PURE__ */ new Set(["success", "failed"]);
57877
59194
  function numberValue$4(value) {
57878
59195
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
57879
59196
  }
57880
59197
  function optionalIntegerValue(value) {
57881
59198
  return typeof value === "number" && Number.isSafeInteger(value) ? value : void 0;
57882
59199
  }
59200
+ function nonNegativeIntegerValue(value) {
59201
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : 0;
59202
+ }
59203
+ function processingTimingState(value) {
59204
+ return value === "processing" || value === "completed" ? value : "unavailable";
59205
+ }
59206
+ function processingTimingRow(value) {
59207
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
59208
+ const raw = value;
59209
+ const stage = stringValue$1(raw.stage).trim().toLowerCase();
59210
+ const outcome = stringValue$1(raw.outcome).trim().toLowerCase();
59211
+ const startedAtMillis = optionalIntegerValue(raw.started_at_ms);
59212
+ const endedAtMillis = optionalIntegerValue(raw.ended_at_ms);
59213
+ const durationMillis = optionalIntegerValue(raw.duration_ms);
59214
+ const provider = stringValue$1(raw.provider).trim();
59215
+ const model = stringValue$1(raw.model).trim();
59216
+ if (!PROCESSING_STAGES.has(stage) || !PROCESSING_OUTCOMES.has(outcome) || startedAtMillis === void 0 || endedAtMillis === void 0 || durationMillis === void 0 || startedAtMillis <= 0 || endedAtMillis < startedAtMillis || durationMillis < 0 || provider === "" || model === "") return void 0;
59217
+ return {
59218
+ stage,
59219
+ outcome,
59220
+ startedAtMillis,
59221
+ endedAtMillis,
59222
+ durationMillis,
59223
+ provider,
59224
+ model,
59225
+ modelVersion: stringValue$1(raw.model_version).trim()
59226
+ };
59227
+ }
59228
+ function processingTiming(value, timingState) {
59229
+ if (!Array.isArray(value)) return {
59230
+ timingState: "unavailable",
59231
+ totalDurationMillis: 0,
59232
+ rows: []
59233
+ };
59234
+ const parsedRows = value.map(processingTimingRow);
59235
+ if (parsedRows.some((row) => row === void 0)) return {
59236
+ timingState: "unavailable",
59237
+ totalDurationMillis: 0,
59238
+ rows: []
59239
+ };
59240
+ const rows = parsedRows;
59241
+ const totalDurationMillis = rows.reduce((total, row) => total + row.durationMillis, 0);
59242
+ if (!Number.isSafeInteger(totalDurationMillis)) return {
59243
+ timingState: "unavailable",
59244
+ totalDurationMillis: 0,
59245
+ rows: []
59246
+ };
59247
+ return {
59248
+ timingState,
59249
+ totalDurationMillis,
59250
+ rows
59251
+ };
59252
+ }
59253
+ function ownerDisplayStatus(values) {
59254
+ const statuses = values.filter((value) => Number.isSafeInteger(value) && value >= 1 && value <= 6);
59255
+ if (statuses.length === 0) return void 0;
59256
+ const active = statuses.filter((value) => value <= 4);
59257
+ if (active.length > 0) switch (Math.min(...active)) {
59258
+ case 1: return "speaker-waiting";
59259
+ case 2: return "speaker-recognizing";
59260
+ case 3: return "transcript-waiting";
59261
+ case 4: return "transcribing";
59262
+ }
59263
+ const completedCount = statuses.filter((value) => value === 5).length;
59264
+ const failedCount = statuses.filter((value) => value === 6).length;
59265
+ if (completedCount > 0 && failedCount > 0) return "partial";
59266
+ if (failedCount > 0) return "failed";
59267
+ if (completedCount > 0) return "completed";
59268
+ }
59269
+ function ownerSession(value, viewerUserId) {
59270
+ const owner = objectValue$1(value);
59271
+ const sessionId = stringValue$1(owner.id).trim();
59272
+ const rawStartAtMillis = optionalIntegerValue(owner.start_at);
59273
+ if (optionalIntegerValue(owner.source) !== 2 || sessionId === "" || rawStartAtMillis === void 0 || rawStartAtMillis < 0) return void 0;
59274
+ const startAtMillis = rawStartAtMillis;
59275
+ const durationMillis = optionalIntegerValue(owner.duration);
59276
+ const explicitEndAtMillis = owner.end_at === void 0 ? 0 : optionalIntegerValue(owner.end_at);
59277
+ if (durationMillis === void 0 || durationMillis < 0 || explicitEndAtMillis === void 0 || explicitEndAtMillis < 0) return void 0;
59278
+ const inferredEndAtMillis = startAtMillis + durationMillis;
59279
+ if (!Number.isSafeInteger(inferredEndAtMillis)) return void 0;
59280
+ return {
59281
+ sessionId,
59282
+ ownership: numberValue$4(owner.belong_usr) === viewerUserId ? "self" : "other",
59283
+ fileName: stringValue$1(owner.orig_name).trim(),
59284
+ fileSize: nonNegativeIntegerValue(owner.source_size),
59285
+ parsedSize: nonNegativeIntegerValue(owner.size),
59286
+ durationMillis,
59287
+ startAtMillis,
59288
+ endAtMillis: explicitEndAtMillis > startAtMillis ? explicitEndAtMillis : inferredEndAtMillis,
59289
+ createdAtMillis: nonNegativeIntegerValue(owner.create_at),
59290
+ updatedAtMillis: nonNegativeIntegerValue(owner.update_at),
59291
+ hasFinishedUpload: owner.has_finish_spk === true
59292
+ };
59293
+ }
57883
59294
  function remoteFileName(job) {
57884
59295
  const extension = job.fileName.trim().toLowerCase().match(/\.[^.]+$/)?.[0] ?? "";
57885
59296
  return `arkme_${job.jobId}_0${extension}`;
@@ -57891,12 +59302,137 @@ var AudioRecordingImportGateway = class {
57891
59302
  this.runtime = runtime;
57892
59303
  this.createOssClient = createOssClient;
57893
59304
  }
59305
+ async findExistingFileNames(input) {
59306
+ const existing = [];
59307
+ for (let offset = 0; offset < input.fileNames.length; offset += AUDIO_IMPORT_DUPLICATE_BATCH_SIZE) {
59308
+ const data = await this.authenticatedOwnerPost(input.viewerUserId, "/api/v1/audio/check-exist-same-orig", { orig_names: input.fileNames.slice(offset, offset + AUDIO_IMPORT_DUPLICATE_BATCH_SIZE) }, input.signal);
59309
+ if (!Array.isArray(data.exist_names)) continue;
59310
+ existing.push(...data.exist_names.map(stringValue$1).map((value) => value.trim()).filter(Boolean));
59311
+ }
59312
+ return existing;
59313
+ }
59314
+ async listOwnerTasks(input) {
59315
+ const offset = Math.max(0, Math.trunc(input.offset));
59316
+ const limit = Math.max(0, Math.trunc(input.limit));
59317
+ if (limit === 0) return {
59318
+ tasks: [],
59319
+ hasMore: false
59320
+ };
59321
+ const scoped = [];
59322
+ const targetCount = offset + limit + 1;
59323
+ let remoteOffset = 0;
59324
+ let exhausted = false;
59325
+ while (scoped.length < targetCount) {
59326
+ const data = await this.authenticatedOwnerPost(input.viewerUserId, "/api/v1/audio/get-session-ls", {
59327
+ to_stamp: Math.trunc(input.toMillis),
59328
+ limit: AUDIO_IMPORT_OWNER_PAGE_SIZE,
59329
+ offset: remoteOffset,
59330
+ query_new: false,
59331
+ task_scope: input.scope
59332
+ }, input.signal);
59333
+ const rawSessions = Array.isArray(data.session_ls) ? data.session_ls : [];
59334
+ const ownerSnapshots = rawSessions.map((raw) => ({
59335
+ raw: objectValue$1(raw),
59336
+ session: ownerSession(raw, input.viewerUserId)
59337
+ })).filter((owner) => owner.session !== void 0).map(({ raw, session: owner }) => {
59338
+ if (typeof raw.has_done_child !== "boolean") throw new RecordingImportContractError("recording-import-owner-completion-unavailable", "Audio 暂未返回录音处理终态,请稍后重试", true);
59339
+ const processingCompleted = raw.has_done_child;
59340
+ if (input.scope === "completed" !== processingCompleted) throw new RecordingImportContractError("recording-import-owner-scope-invalid", "Audio 返回的录音任务范围无效,请稍后重试", true);
59341
+ return {
59342
+ session: owner,
59343
+ processingCompleted
59344
+ };
59345
+ });
59346
+ const sealed = ownerSnapshots.filter((task) => task.session.hasFinishedUpload);
59347
+ let progress = /* @__PURE__ */ new Map();
59348
+ if (sealed.length > 0) try {
59349
+ progress = await this.loadOwnerProgressForSession(input.viewerUserId, sealed.map((task) => task.session.sessionId), input.signal);
59350
+ } catch (error) {
59351
+ if (input.signal?.aborted === true || this.isAccountMismatch(error)) throw error;
59352
+ }
59353
+ for (const task of ownerSnapshots) {
59354
+ const ownerProgress = progress.get(task.session.sessionId);
59355
+ scoped.push({
59356
+ ...task,
59357
+ ...ownerProgress === void 0 ? {} : { progress: ownerProgress }
59358
+ });
59359
+ }
59360
+ remoteOffset += rawSessions.length;
59361
+ if (rawSessions.length < AUDIO_IMPORT_OWNER_PAGE_SIZE) {
59362
+ exhausted = true;
59363
+ break;
59364
+ }
59365
+ }
59366
+ return {
59367
+ tasks: scoped.slice(offset, offset + limit),
59368
+ ...exhausted ? { total: scoped.length } : {},
59369
+ hasMore: scoped.length > offset + limit || !exhausted
59370
+ };
59371
+ }
59372
+ async loadOwnerSession(input) {
59373
+ const normalized = input.sessionId.trim();
59374
+ if (normalized === "") throw new RecordingImportContractError("recording-import-session-invalid", "Audio 会话引用无效");
59375
+ const owner = ownerSession(await this.authenticatedOwnerPost(input.viewerUserId, "/api/v1/audio/get-session-by-id", { session_id: normalized }, input.signal), input.viewerUserId);
59376
+ if (owner === void 0 || owner.sessionId !== normalized) throw new RecordingImportContractError("recording-import-session-invalid", "Audio 会话数据无效", true);
59377
+ return owner;
59378
+ }
59379
+ async loadOwnerProgressForSession(viewerUserId, sessionIds, signal) {
59380
+ const normalized = [...new Set(sessionIds.map((value) => value.trim()).filter((value) => value !== ""))];
59381
+ if (normalized.length === 0) return /* @__PURE__ */ new Map();
59382
+ const result = /* @__PURE__ */ new Map();
59383
+ for (let offset = 0; offset < normalized.length; offset += AUDIO_IMPORT_PROGRESS_BATCH_SIZE) {
59384
+ const batch = normalized.slice(offset, offset + AUDIO_IMPORT_PROGRESS_BATCH_SIZE);
59385
+ const data = await this.authenticatedOwnerPost(viewerUserId, "/api/v1/audio/get-session-deal-status", { session_ids: batch }, signal);
59386
+ for (const raw of Array.isArray(data.item_ls) ? data.item_ls : []) {
59387
+ const owner = objectValue$1(raw);
59388
+ const sessionId = stringValue$1(owner.session_id).trim();
59389
+ if (sessionId === "" || !batch.includes(sessionId)) continue;
59390
+ const statusListAvailable = Array.isArray(owner.child_status_ls);
59391
+ const parsedStatuses = (statusListAvailable ? owner.child_status_ls : []).map((child) => optionalIntegerValue(objectValue$1(child).status));
59392
+ const displayStatus = statusListAvailable && parsedStatuses.every((status) => status !== void 0 && status >= 1 && status <= 6) ? ownerDisplayStatus(parsedStatuses) : "unavailable";
59393
+ const timingState = processingTimingState(owner.timing_state);
59394
+ result.set(sessionId, {
59395
+ ...displayStatus === void 0 ? {} : { displayStatus },
59396
+ timingState,
59397
+ processingTiming: processingTiming(owner.processing_timing_ls, timingState)
59398
+ });
59399
+ }
59400
+ }
59401
+ return result;
59402
+ }
59403
+ async updateOwnerSessionStart(input) {
59404
+ await this.authenticatedOwnerPost(input.viewerUserId, "/api/v1/audio/modify-session-start", {
59405
+ session_id: input.sessionId.trim(),
59406
+ start_at: Math.trunc(input.startAtMillis),
59407
+ tz_offset: -new Date(input.startAtMillis).getTimezoneOffset() * 6e4
59408
+ }, input.signal, {
59409
+ lane: "write",
59410
+ bypassCache: true
59411
+ });
59412
+ }
59413
+ async updateOwnerSessionOwnership(input) {
59414
+ if (input.belongUserId !== 0 && input.belongUserId !== input.viewerUserId) throw new ArkmePluginError("recording-import-owner-invalid", "录音数据归属无效", false);
59415
+ await this.authenticatedOwnerPost(input.viewerUserId, "/api/v1/audio/modify-session-belong-usr", {
59416
+ session_id: input.sessionId.trim(),
59417
+ belong_usr: input.belongUserId
59418
+ }, input.signal, {
59419
+ lane: "write",
59420
+ bypassCache: true
59421
+ });
59422
+ }
59423
+ async deleteOwnerSession(input) {
59424
+ await this.authenticatedOwnerPost(input.viewerUserId, "/api/v1/audio/del-session", { session_id: input.sessionId.trim() }, input.signal, {
59425
+ lane: "write",
59426
+ bypassCache: true
59427
+ });
59428
+ }
57894
59429
  async ensureSession(job, signal) {
57895
59430
  const recovered = await this.recoverSession(job, signal);
57896
59431
  if (recovered !== void 0) return recovered.sessionId;
57897
59432
  let session = await this.requireJobSession(job);
57898
59433
  const duplicate = await this.runtime.authenticatedAudioPost("/api/v1/audio/check-exist-same-orig", { orig_names: [job.fileName] }, session, signal);
57899
- if ((Array.isArray(duplicate.exist_names) ? duplicate.exist_names : []).some((value) => stringValue$1(value) === job.fileName)) throw new RecordingImportContractError("recording-import-duplicate", "已存在同名录音,请更名后重试");
59434
+ const fileNameKey = recordingImportFileNameKey(job.fileName);
59435
+ if ((Array.isArray(duplicate.exist_names) ? duplicate.exist_names : []).some((value) => recordingImportFileNameKey(stringValue$1(value)) === fileNameKey)) throw new RecordingImportContractError("recording-import-duplicate", "已存在同名录音,请更名后重试");
57900
59436
  const timezoneOffsetMillis = -new Date(job.startAtMillis).getTimezoneOffset() * 6e4;
57901
59437
  session = await this.requireJobSession(job);
57902
59438
  const sessionId = stringValue$1((await this.runtime.authenticatedAudioPost("/api/v1/audio/new-session", {
@@ -58020,10 +59556,19 @@ var AudioRecordingImportGateway = class {
58020
59556
  return credentials;
58021
59557
  }
58022
59558
  async requireJobSession(job) {
59559
+ return await this.requireViewerSession(job.userId);
59560
+ }
59561
+ async requireViewerSession(viewerUserId) {
58023
59562
  const session = await this.runtime.requireSession();
58024
- if (session.userId !== job.userId) throw new ArkmePluginError("recording-import-account-mismatch", "登录账号已变化,已停止录音导入", true, 403);
59563
+ if (session.userId !== viewerUserId) throw new ArkmePluginError("recording-import-account-mismatch", "登录账号已变化,已停止录音导入", true, 403);
58025
59564
  return session;
58026
59565
  }
59566
+ async authenticatedOwnerPost(viewerUserId, path, body, signal, options = {}) {
59567
+ return await this.runtime.authenticatedAudioPost(path, body, await this.requireViewerSession(viewerUserId), signal, options);
59568
+ }
59569
+ isAccountMismatch(error) {
59570
+ return error instanceof ArkmePluginError && error.code === "recording-import-account-mismatch";
59571
+ }
58027
59572
  async recoverSession(job, signal, normalizeOwnership = true) {
58028
59573
  const session = await this.requireJobSession(job);
58029
59574
  const matchesIdentity = (item) => numberValue$4(item.source) === 2 && numberValue$4(item.start_at) === job.startAtMillis && stringValue$1(item.orig_name) === job.fileName;
@@ -58414,6 +59959,42 @@ var SearchService = class {
58414
59959
  }, session, options.signal);
58415
59960
  return await this.withNavigationTargets(this.recordSearchResult(data, lockedRecordUids), options.signal);
58416
59961
  }
59962
+ /** Query the canonical record-tag projection used by Flutter's tag search. */
59963
+ async searchTagRecords(options) {
59964
+ const normalizedTag = arkmeNormalizedHashTag(options.normalizedTag);
59965
+ if (normalizedTag === "") throw new ArkmePluginError("record-tag-query-empty", "搜索标签不能为空", false);
59966
+ const session = await this.runtime.requireSession();
59967
+ const data = await this.runtime.authenticatedPost("/api/v1/records/tags/query", {
59968
+ normalized_tag: normalizedTag,
59969
+ limit: Math.min(100, Math.max(1, Math.trunc(options.limit))),
59970
+ ...options.cursorSendAt === void 0 ? {} : { cursor_send_at: Math.trunc(options.cursorSendAt) },
59971
+ ...options.cursorRecordUid?.trim() ? { cursor_record_uid: options.cursorRecordUid.trim() } : {}
59972
+ }, session, options.signal);
59973
+ const items = listValue$3(data.items).map((raw) => {
59974
+ const item = objectValue$1(raw);
59975
+ const core = objectValue$1(item.record_core);
59976
+ const sourceKind = Math.trunc(numberValue$2(core.origin_kind));
59977
+ const sourceUid = stringValue$1(core.origin_container_ref).trim();
59978
+ return {
59979
+ ...item,
59980
+ source_kind: sourceKind,
59981
+ ...sourceUid === "" ? {} : { source_uid: sourceUid },
59982
+ route_target_kind: sourceKind === 2 ? "topic" : "default_category",
59983
+ match_summary: { snippet: stringValue$1(core.text_content) }
59984
+ };
59985
+ });
59986
+ const nextSendAt = numberValue$2(data.next_send_at);
59987
+ const nextRecordUid = stringValue$1(data.next_record_uid).trim();
59988
+ const result = this.recordSearchResult({
59989
+ items,
59990
+ source_aggregates: [],
59991
+ has_more: booleanValue$1(data.has_more),
59992
+ query_guard: { state: "complete" },
59993
+ page_summary: { item_count: items.length },
59994
+ ...nextSendAt > 0 && nextRecordUid !== "" ? { next_cursor: `${String(nextSendAt)}:${nextRecordUid}` } : {}
59995
+ });
59996
+ return await this.withNavigationTargets(result, options.signal);
59997
+ }
58417
59998
  async searchFiles(options) {
58418
59999
  const page = options.query?.trim() ? await this.searchRemote({
58419
60000
  ...options,
@@ -61011,6 +62592,8 @@ const ARKME_WORLD_IMAGE_REF_TTL_MILLIS = 900 * 1e3;
61011
62592
  const MAX_ARKME_WORLD_IMAGE_REFS = 2048;
61012
62593
  const ARKME_WORLD_AVATAR_RESOLUTION_CACHE_TTL_MILLIS = 300 * 1e3;
61013
62594
  const MAX_ARKME_WORLD_AVATAR_RESOLUTION_CACHE_ENTRIES = 2048;
62595
+ const ARKME_WORLD_EXTENSION_SHARE_CACHE_TTL_MILLIS = 300 * 1e3;
62596
+ const MAX_ARKME_WORLD_EXTENSION_SHARE_CACHE_ENTRIES = 512;
61014
62597
  const ARKME_WORLD_RECORD_REF_TTL_MILLIS = 900 * 1e3;
61015
62598
  const MAX_ARKME_WORLD_RECORD_REFS = 4096;
61016
62599
  const ARKME_VOICEPRINT_PLAY_SCOPE = 2;
@@ -61087,6 +62670,18 @@ function worldExtensionPublicationShare(rawPublication, rawItem) {
61087
62670
  url: parsed.href
61088
62671
  };
61089
62672
  }
62673
+ function worldExtensionShareFromCatalogDetail(raw) {
62674
+ const response = objectValue$1(raw);
62675
+ return worldExtensionPublicationShare({ share: objectValue$1(response.extension ?? response).share }, {});
62676
+ }
62677
+ function worldTextWithExtensionShare(textContent, share) {
62678
+ const content = textContent.trim();
62679
+ if (share === void 0) return content;
62680
+ const shareUrl = share.url.trim();
62681
+ if (shareUrl === "") return content;
62682
+ if (content.includes(shareUrl)) return content;
62683
+ return content === "" ? shareUrl : `${content}\n${shareUrl}`;
62684
+ }
61090
62685
  function isTopLevelWorldRecord(raw) {
61091
62686
  return stringValue$1(objectValue$1(raw).parent_record_uid).trim() === "";
61092
62687
  }
@@ -61201,6 +62796,8 @@ var WorldService = class {
61201
62796
  viewerLabels;
61202
62797
  worldImageRefs = /* @__PURE__ */ new Map();
61203
62798
  worldAvatarResolutionCache = /* @__PURE__ */ new Map();
62799
+ extensionPublicationShareCache = /* @__PURE__ */ new Map();
62800
+ extensionPublicationShareInFlight = /* @__PURE__ */ new Map();
61204
62801
  worldRecordRefs = /* @__PURE__ */ new Map();
61205
62802
  voiceprintSocialCache = /* @__PURE__ */ new Map();
61206
62803
  voiceprintSocialInFlight = /* @__PURE__ */ new Map();
@@ -61214,6 +62811,8 @@ var WorldService = class {
61214
62811
  dispose() {
61215
62812
  this.worldImageRefs.clear();
61216
62813
  this.worldAvatarResolutionCache.clear();
62814
+ this.extensionPublicationShareCache.clear();
62815
+ this.extensionPublicationShareInFlight.clear();
61217
62816
  this.worldRecordRefs.clear();
61218
62817
  this.voiceprintSocialCache.clear();
61219
62818
  this.voiceprintSocialInFlight.clear();
@@ -61247,7 +62846,7 @@ var WorldService = class {
61247
62846
  }, void 0, [200], options.signal);
61248
62847
  const rawItems = listValue(data.list);
61249
62848
  const resolvedAvatars = await this.resolveWorldAvatarUrls(rawItems, session, options.signal);
61250
- const items = (await Promise.all(rawItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars)))).filter((item) => item !== void 0);
62849
+ const items = (await Promise.all(rawItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars, options.signal)))).filter((item) => item !== void 0);
61251
62850
  const total = Math.max(0, Math.trunc(numberValue(data.total)));
61252
62851
  const nextOffset = offset + rawItems.length;
61253
62852
  const hasMore = rawItems.length > 0 && nextOffset < total;
@@ -61274,7 +62873,7 @@ var WorldService = class {
61274
62873
  const rawItems = listValue(data.list);
61275
62874
  const rootItems = rawItems.filter(isTopLevelWorldRecord);
61276
62875
  const resolvedAvatars = await this.resolveWorldAvatarUrls(rootItems, session, options.signal);
61277
- items = (await Promise.all(rootItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars)))).filter((item) => item !== void 0);
62876
+ items = (await Promise.all(rootItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars, options.signal)))).filter((item) => item !== void 0);
61278
62877
  total = Math.max(0, Math.trunc(numberValue(data.total)));
61279
62878
  offset += rawItems.length;
61280
62879
  hasMore = rawItems.length > 0 && offset < total;
@@ -61306,7 +62905,7 @@ var WorldService = class {
61306
62905
  total = Math.max(0, Math.trunc(numberValue(data.total)));
61307
62906
  const rootItems = rawItems.filter(isTopLevelWorldRecord);
61308
62907
  const resolvedAvatars = await this.resolveWorldAvatarUrls(rootItems, session, options.signal);
61309
- items = (await Promise.all(rootItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars)))).filter((item) => item !== void 0);
62908
+ items = (await Promise.all(rootItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars, options.signal)))).filter((item) => item !== void 0);
61310
62909
  offset += rawItems.length;
61311
62910
  hasMore = rawItems.length > 0 && offset < total;
61312
62911
  attempts += 1;
@@ -61873,7 +63472,7 @@ var WorldService = class {
61873
63472
  extendCount: Math.max(0, Math.trunc(numberValue(item.extend_count)))
61874
63473
  };
61875
63474
  }
61876
- async worldFeedItem(raw, viewerUserId, resolvedAvatars) {
63475
+ async worldFeedItem(raw, viewerUserId, resolvedAvatars, signal) {
61877
63476
  const item = objectValue$1(raw);
61878
63477
  const recordUid = stringValue$1(item.record_uid).trim();
61879
63478
  const textContent = stringValue$1(item.text_content ?? item.content).trim();
@@ -61883,6 +63482,8 @@ var WorldService = class {
61883
63482
  const extensionId = stringValue$1(rawExtensionPublication.extension_id).trim();
61884
63483
  const extensionVisibility = stringValue$1(rawExtensionPublication.visibility).trim();
61885
63484
  const isExtensionPublication = recordType === "extension_publication" && extensionId !== "" && extensionVisibility === "public";
63485
+ let extensionShare = isExtensionPublication ? worldExtensionPublicationShare(rawExtensionPublication, item) : void 0;
63486
+ if (isExtensionPublication && extensionShare === void 0) extensionShare = await this.extensionPublicationShare(extensionId, signal);
61886
63487
  const rawImages = listValue(item.images).map(stringValue$1).map((value) => value.trim()).filter((value) => value !== "");
61887
63488
  const videoCount = listValue(item.videos).length;
61888
63489
  const voiceCount = listValue(item.voices).length;
@@ -61916,7 +63517,7 @@ var WorldService = class {
61916
63517
  ...avatarRef === void 0 ? {} : { avatarRef },
61917
63518
  ...avatarRef === void 0 && avatarFallback !== void 0 ? { avatarFallback } : {},
61918
63519
  headline,
61919
- textContent,
63520
+ textContent: isExtensionPublication ? worldTextWithExtensionShare(textContent, extensionShare) : textContent,
61920
63521
  tags: listValue(item.tags).map(stringValue$1).map((tag) => tag.trim()).filter((tag) => tag !== ""),
61921
63522
  templateKind: Math.trunc(numberValue(item.template_kind)),
61922
63523
  createdAtMillis: Math.trunc(numberValue(item.created_at)),
@@ -61933,10 +63534,7 @@ var WorldService = class {
61933
63534
  version: stringValue$1(rawExtensionPublication.version).trim(),
61934
63535
  name: stringValue$1(rawExtensionPublication.name).trim() || "未命名插件",
61935
63536
  description: stringValue$1(rawExtensionPublication.description).trim(),
61936
- ...(() => {
61937
- const share = worldExtensionPublicationShare(rawExtensionPublication, item);
61938
- return share === void 0 ? {} : { share };
61939
- })(),
63537
+ ...extensionShare === void 0 ? {} : { share: extensionShare },
61940
63538
  ...(() => {
61941
63539
  const iconRef = stringValue$1(rawExtensionPublication.icon_ref).trim();
61942
63540
  return iconRef === "" ? {} : { iconRef };
@@ -61953,6 +63551,44 @@ var WorldService = class {
61953
63551
  } : {}
61954
63552
  };
61955
63553
  }
63554
+ async extensionPublicationShare(extensionId, signal) {
63555
+ const normalized = extensionId.trim();
63556
+ if (normalized === "") return void 0;
63557
+ const now = Date.now();
63558
+ const cached = this.extensionPublicationShareCache.get(normalized);
63559
+ if (cached !== void 0 && cached.expiresAtMillis > now) return cached.share;
63560
+ const inFlight = this.extensionPublicationShareInFlight.get(normalized);
63561
+ if (inFlight !== void 0) return await inFlight;
63562
+ const load = this.loadExtensionPublicationShare(normalized, signal);
63563
+ this.extensionPublicationShareInFlight.set(normalized, load);
63564
+ try {
63565
+ const share = await load;
63566
+ if (this.extensionPublicationShareCache.size >= MAX_ARKME_WORLD_EXTENSION_SHARE_CACHE_ENTRIES) {
63567
+ const oldestKey = this.extensionPublicationShareCache.keys().next().value;
63568
+ if (oldestKey !== void 0) this.extensionPublicationShareCache.delete(oldestKey);
63569
+ }
63570
+ this.extensionPublicationShareCache.set(normalized, {
63571
+ ...share === void 0 ? {} : { share },
63572
+ expiresAtMillis: Date.now() + ARKME_WORLD_EXTENSION_SHARE_CACHE_TTL_MILLIS
63573
+ });
63574
+ return share;
63575
+ } finally {
63576
+ this.extensionPublicationShareInFlight.delete(normalized);
63577
+ }
63578
+ }
63579
+ async loadExtensionPublicationShare(extensionId, signal) {
63580
+ try {
63581
+ return worldExtensionShareFromCatalogDetail(await this.runtime.extensionPost("/api/public/v1/extensions/detail", { extension_id: extensionId }, signal, {
63582
+ lane: "background-read",
63583
+ key: `world-extension-share:${extensionId}`,
63584
+ cacheMs: ARKME_WORLD_EXTENSION_SHARE_CACHE_TTL_MILLIS,
63585
+ failureCooldownMs: 3e4
63586
+ }));
63587
+ } catch (error) {
63588
+ if (error instanceof Error && error.name === "AbortError") throw error;
63589
+ return;
63590
+ }
63591
+ }
61956
63592
  isTrustedWorldImageUrl(raw) {
61957
63593
  if (raw.length > 4096) return false;
61958
63594
  try {
@@ -62204,6 +63840,7 @@ var ArkmeService = class {
62204
63840
  media;
62205
63841
  privacy;
62206
63842
  source;
63843
+ conversationDirectoryVisibility;
62207
63844
  record;
62208
63845
  search;
62209
63846
  bot;
@@ -62276,7 +63913,8 @@ var ArkmeService = class {
62276
63913
  this.interwoven = new InterwovenService(this.runtime, this.source, this.profile);
62277
63914
  this.linkMetadata = new ArkmeLinkMetadataService(linkDocumentReader);
62278
63915
  this.aiPolish = new GroupAiPolishService(this.runtime, this.source, { sendChatSourceTextRaw: async (...args) => await this.chat.sendChatSourceTextRaw(...args) });
62279
- this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session, chatSessionUid) => await this.chat.chatTimelineItems(data, session, chatSessionUid) });
63916
+ this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session, chatSessionUid, sourceKind) => await this.chat.chatTimelineItems(data, session, chatSessionUid, sourceKind) });
63917
+ this.conversationDirectoryVisibility = new ConversationDirectoryVisibilityService(new ConversationListPreferenceService(this.runtime), this.source, this.bot, this.realtime);
62280
63918
  this.chat = new ChatService(this.runtime, this.source, this.profile, this.media, this.record, this.bot, this.arko, this.aiPolish, this.realtime, this.privacy, this.messageActions);
62281
63919
  this.userBan = new UserBanService(this.runtime, this.chat);
62282
63920
  this.botConversation = new BotConversationService(this.runtime, this.bot, this.chat, async () => {
@@ -62284,8 +63922,10 @@ var ArkmeService = class {
62284
63922
  }, this.messageActions);
62285
63923
  this.relatedQuickNote = new RelatedQuickNoteService(this.runtime, this.record, this.media, this.profile, this.privacy);
62286
63924
  this.contactDirectory = new ContactDirectoryService(this.runtime, this.source, this.bot, this.profile, this.world, this.chat);
63925
+ const recordingImportGateway = new AudioRecordingImportGateway(this.runtime);
62287
63926
  this.recording = new RecordingService(this.runtime, {
62288
- recordingImportGateway: new AudioRecordingImportGateway(this.runtime),
63927
+ recordingImportGateway,
63928
+ recordingImportOwnerGateway: recordingImportGateway,
62289
63929
  recordingImportSource: new LocalRecordingImportSource(),
62290
63930
  profile: this.profile,
62291
63931
  media: this.media,
@@ -62452,7 +64092,7 @@ var ArkmeService = class {
62452
64092
  return await this.botConversation.updateNotificationPreference(botRef, muted, options);
62453
64093
  }
62454
64094
  async openBotChat(botRef, options = {}) {
62455
- return await this.bot.openBotChat(botRef, options);
64095
+ return await this.conversationDirectoryVisibility.openBotContactConversation(botRef, options);
62456
64096
  }
62457
64097
  async listBotPrivateChatDirectory(options = {}) {
62458
64098
  return await this.botConversation.directory(options);
@@ -62550,6 +64190,7 @@ var ArkmeService = class {
62550
64190
  messageReadReceipts: true,
62551
64191
  messageReport: true,
62552
64192
  userBanManagement: true,
64193
+ groupOwnerGovernance: true,
62553
64194
  richContentRead: this.config.richMediaRenderEnabled !== false,
62554
64195
  richContentSend: this.config.richMediaSendEnabled !== false,
62555
64196
  ...this.config.richMediaSendEnabled === false ? {} : { backgroundSound: true },
@@ -62677,10 +64318,14 @@ var ArkmeService = class {
62677
64318
  return await this.contactDirectory.contactWorld(contactRef, options);
62678
64319
  }
62679
64320
  async openDirectoryContactChat(contactRef, signal) {
62680
- return await this.contactDirectory.openContactChat(contactRef, signal);
64321
+ const result = await this.contactDirectory.openContactChat(contactRef, signal);
64322
+ this.conversationDirectoryVisibility.restoreSource(result.source).catch(() => void 0);
64323
+ return result;
62681
64324
  }
62682
64325
  async openDirectoryGroupChat(sourceRef, signal) {
62683
- return await this.contactDirectory.openGroupChat(sourceRef, signal);
64326
+ const source = await this.contactDirectory.openGroupChat(sourceRef, signal);
64327
+ this.conversationDirectoryVisibility.restoreSource(source).catch(() => void 0);
64328
+ return source;
62684
64329
  }
62685
64330
  async unmarkedSpeakerOptions(candidateRef, signal) {
62686
64331
  return await this.unmarkedSpeaker.markOptions(candidateRef, signal);
@@ -62745,8 +64390,11 @@ var ArkmeService = class {
62745
64390
  /** @internal Built-in loopback UI only. */ async recordingImportStatus(importRef) {
62746
64391
  return await this.recording.recordingImportStatus(importRef);
62747
64392
  }
62748
- /** @internal Built-in loopback UI only. */ async recordingImportList() {
62749
- return await this.recording.recordingImportList();
64393
+ /** @internal Built-in loopback UI only. */ async recordingImportList(signal) {
64394
+ return await this.recording.recordingImportList(signal);
64395
+ }
64396
+ /** @internal Built-in loopback UI only. */ async recordingImportHistory(input, signal) {
64397
+ return await this.recording.recordingImportHistory(input, signal);
62750
64398
  }
62751
64399
  /** @internal Built-in loopback UI only. */ async retryRecordingImport(importRef, expectedRevision) {
62752
64400
  return await this.recording.retryRecordingImport(importRef, expectedRevision);
@@ -62754,6 +64402,15 @@ var ArkmeService = class {
62754
64402
  /** @internal Built-in loopback UI only. */ async cancelRecordingImport(importRef, expectedRevision) {
62755
64403
  return await this.recording.cancelRecordingImport(importRef, expectedRevision);
62756
64404
  }
64405
+ /** @internal Built-in loopback UI only. */ async updateRecordingImportSessionStart(sessionRef, startAtMillis, signal) {
64406
+ await this.recording.updateRecordingImportSessionStart(sessionRef, startAtMillis, signal);
64407
+ }
64408
+ /** @internal Built-in loopback UI only. */ async updateRecordingImportSessionOwnership(sessionRef, ownership, signal) {
64409
+ await this.recording.updateRecordingImportSessionOwnership(sessionRef, ownership, signal);
64410
+ }
64411
+ /** @internal Built-in loopback UI only. */ async deleteRecordingImportSession(sessionRef, signal) {
64412
+ await this.recording.deleteRecordingImportSession(sessionRef, signal);
64413
+ }
62757
64414
  async resumeRecordingImports() {
62758
64415
  await this.recording.resumeRecordingImports();
62759
64416
  }
@@ -62835,8 +64492,14 @@ var ArkmeService = class {
62835
64492
  async listSources(directory, options = {}) {
62836
64493
  return await this.source.listSources(directory, options);
62837
64494
  }
62838
- async setChatDirectoryPolicy(sourceRef, options = {}) {
62839
- return await this.source.setChatDirectoryPolicy(sourceRef, options);
64495
+ async setChatDirectoryPin(sourceRef, pinned, signal) {
64496
+ return await this.source.setChatDirectoryPin(sourceRef, pinned, signal);
64497
+ }
64498
+ async conversationDirectoryVisibilitySnapshot(sourceRefs, botRefs, signal) {
64499
+ return await this.conversationDirectoryVisibility.query(sourceRefs, botRefs, signal);
64500
+ }
64501
+ async setConversationDirectoryVisibility(entryKind, entryRef, hidden, signal) {
64502
+ await this.conversationDirectoryVisibility.setVisibility(entryKind, entryRef, hidden, signal);
62840
64503
  }
62841
64504
  async dshBetaCommunityEntryState(signal) {
62842
64505
  return await this.community.dshBetaCommunityEntryState(signal);
@@ -62961,7 +64624,9 @@ var ArkmeService = class {
62961
64624
  async openPrivateChatFromContact(contactRef, options = {}) {
62962
64625
  const session = await this.runtime.requireSession();
62963
64626
  const peerUserId = await this.contact.resolveRegisteredContactUserId(contactRef, session, options.signal);
62964
- return await this.chat.openPrivateChatFromUser(peerUserId, options);
64627
+ const result = await this.chat.openPrivateChatFromUser(peerUserId, options);
64628
+ this.conversationDirectoryVisibility.restoreSource(result.source).catch(() => void 0);
64629
+ return result;
62965
64630
  }
62966
64631
  async officialAuthorProfile(signal) {
62967
64632
  return await this.chat.officialAuthorProfile(signal === void 0 ? {} : { signal });
@@ -63015,6 +64680,18 @@ var ArkmeService = class {
63015
64680
  async reportMessage(messageRef, reportType, options = {}) {
63016
64681
  return await this.chat.reportMessage(messageRef, reportType, options);
63017
64682
  }
64683
+ async withdrawGroupMessage(messageWithdrawalRef, options = {}) {
64684
+ return await this.chat.withdrawGroupMessage(messageWithdrawalRef, options);
64685
+ }
64686
+ async removeGroupMember(sourceRef, memberRef, options = {}) {
64687
+ return await this.chat.removeGroupMember(sourceRef, memberRef, options);
64688
+ }
64689
+ async listGroupJoinRestrictions(sourceRef, options = {}) {
64690
+ return await this.chat.listGroupJoinRestrictions(sourceRef, options);
64691
+ }
64692
+ async setGroupJoinRestriction(sourceRef, memberRef, restricted, options = {}) {
64693
+ return await this.chat.setGroupJoinRestriction(sourceRef, memberRef, restricted, options);
64694
+ }
63018
64695
  async copySourceMessageLink(sourceRef, actionRefs, options = {}) {
63019
64696
  return await this.chat.copySourceMessageLink(sourceRef, actionRefs, options);
63020
64697
  }
@@ -63164,6 +64841,9 @@ var ArkmeService = class {
63164
64841
  async searchRemote(options) {
63165
64842
  return await this.search.searchRemote(options);
63166
64843
  }
64844
+ async searchTagRecords(options) {
64845
+ return await this.search.searchTagRecords(options);
64846
+ }
63167
64847
  async searchHistory(limit = 10) {
63168
64848
  return await this.search.searchHistory(limit);
63169
64849
  }
@@ -63339,6 +65019,9 @@ var ArkmeService = class {
63339
65019
  await this.realtime.invalidateRecordProjection();
63340
65020
  return result;
63341
65021
  }
65022
+ async listRecordTags(limit = 100, signal) {
65023
+ return await this.record.listTags(limit, signal);
65024
+ }
63342
65025
  async createTextForConversation(recordUid, textContent) {
63343
65026
  const result = await this.record.createTextForConversation(recordUid, textContent);
63344
65027
  if (result.localState !== "failed") await this.realtime.invalidateRecordProjection();
@@ -64939,4 +66622,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
64939
66622
  return jsonValue(await handler(jsonValue(args)));
64940
66623
  }
64941
66624
  //#endregion
64942
- export { ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as $, inspectNativeBundleArtifactV3 as A, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, bundleSha256 as D, arkmeSandboxEntryId as E, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, arkmeRichBackgroundSound as J, isRecordingInstantOnOrAfterUnixEpoch as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, readNativeBundleTarballV3 as N, canonicalBundleJson as O, renderArkmeSandboxHostEntry as P, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, ArkmeBundleArtifactError as T, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, ARKME_PROVIDER_CONTRACT_VERSION as X, ARKME_DEFAULT_SHARE_WEBSITE as Y, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as Z, arkmeClientContentDigest as _, persistentArkmeExtensionActive as a, arkmeNormalizedFileMimeType as at, verifyExtensionResolutionSignature as b, persistentExtensionBundleDirectory as c, recordingImportFileKind as ct, readPersistentExtensionActivation as d, ArkmePluginError as dt, ARKME_WORLD_PUBLISH_MAX_IMAGES as et, refreshPersistentClientWrapper as f, ArkmeOutgoingCallError as ft, activeProfileExtensionOwnerConflicts as g, renderArkmeBundleClientBundle as h, invokePersistentArkmeExtension as i, arkmeCanInlineLocalFile as it, packBundleFiles as j, inspectBundleArtifact as k, persistentExtensionPackageIdentity as l, sameRecordingImportIdentity as lt, writePersistentExtensionActivation as m, applyPersistentArkmeHostExtension as n, ARKME_RUNTIME_INSTANCE_ID as nt, persistentArkmeExtensionRuntimeState as o, arkmePickedFileKind as ot, repairPersistentExtensionInstallation as p, SecretValue as pt, arkmeFileBackgroundSound as q, deactivatePersistentArkmeExtension as r, ARKME_TOOL_FILE_MAX_BYTES as rt, materializePersistentExtensionBundle as s, isArkmeBotAvatarRef as st, activatePersistentArkmeExtension as t, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as tt, quarantinePersistentExtension as u, MAX_RECORDING_IMPORT_BYTES as ut, arkmeClientContentDigestFromSource as v, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, verifyNativeBundleResolutionSignature as x, verifyBundleResolutionSignature as y, ARKME_EXTENSION_FORMAT as z };
66625
+ export { ARKME_PROVIDER_CONTRACT_VERSION as $, inspectNativeBundleArtifactV3 as A, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, bundleSha256 as D, arkmeSandboxEntryId as E, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, arkmeIsGenericLinkMetadataTitle as J, isRecordingInstantOnOrAfterUnixEpoch as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, readNativeBundleTarballV3 as N, canonicalBundleJson as O, renderArkmeSandboxHostEntry as P, ARKME_DEFAULT_SHARE_WEBSITE as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, ArkmeBundleArtifactError as T, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, arkmeFileBackgroundSound as X, arkmeRequiredLinkMetadataFallback as Y, arkmeRichBackgroundSound as Z, arkmeClientContentDigest as _, SecretValue as _t, persistentArkmeExtensionActive as a, ARKME_RUNTIME_INSTANCE_ID as at, verifyExtensionResolutionSignature as b, persistentExtensionBundleDirectory as c, arkmeNormalizedFileMimeType as ct, readPersistentExtensionActivation as d, recordingImportFileKind as dt, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as et, refreshPersistentClientWrapper as f, sameRecordingImportIdentity as ft, activeProfileExtensionOwnerConflicts as g, ArkmeOutgoingCallError as gt, renderArkmeBundleClientBundle as h, ArkmePluginError as ht, invokePersistentArkmeExtension as i, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as it, packBundleFiles as j, inspectBundleArtifact as k, persistentExtensionPackageIdentity as l, arkmePickedFileKind as lt, writePersistentExtensionActivation as m, recordingImportFileNameKey as mt, applyPersistentArkmeHostExtension as n, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as nt, persistentArkmeExtensionRuntimeState as o, ARKME_TOOL_FILE_MAX_BYTES as ot, repairPersistentExtensionInstallation as p, MAX_RECORDING_IMPORT_BYTES as pt, arkmeExtensionShareRefFromLink as q, deactivatePersistentArkmeExtension as r, ARKME_WORLD_PUBLISH_MAX_IMAGES as rt, materializePersistentExtensionBundle as s, arkmeCanInlineLocalFile as st, activatePersistentArkmeExtension as t, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as tt, quarantinePersistentExtension as u, isArkmeBotAvatarRef as ut, arkmeClientContentDigestFromSource as v, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, verifyNativeBundleResolutionSignature as x, verifyBundleResolutionSignature as y, ARKME_EXTENSION_FORMAT as z };