@senguoyun/dsh-arkme 0.1.85 → 0.1.86

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 (52) hide show
  1. package/docs/self-role-avatar-cache.md +59 -0
  2. package/lib/client.js +460 -116
  3. package/lib/client.js.map +1 -1
  4. package/lib/harness-model-client.js +14 -0
  5. package/lib/harness-native-selection-client.js +14 -0
  6. package/lib/harness-sidebar-client.js +14 -0
  7. package/lib/index.js +22 -9
  8. package/lib/persistent-extension.js +1 -1
  9. package/lib/{persistent-runtime-adGFmkIa.js → persistent-runtime-DfmCJcxH.js} +412 -121
  10. package/lib/sdk.js +14 -0
  11. package/lib/types/arkme-service.d.ts +1 -0
  12. package/lib/types/arkme-service.d.ts.map +1 -1
  13. package/lib/types/client/ArkmeSelfRolePicker.d.ts.map +1 -1
  14. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  15. package/lib/types/client/avatar-image-runtime.d.ts.map +1 -1
  16. package/lib/types/client/avatar-image-store.d.ts +10 -0
  17. package/lib/types/client/avatar-image-store.d.ts.map +1 -1
  18. package/lib/types/client/avatar-persistent-cache.d.ts +16 -0
  19. package/lib/types/client/avatar-persistent-cache.d.ts.map +1 -0
  20. package/lib/types/client/reaction-notifications.d.ts.map +1 -1
  21. package/lib/types/client/reaction-preview-store.d.ts +4 -1
  22. package/lib/types/client/reaction-preview-store.d.ts.map +1 -1
  23. package/lib/types/client/reaction-transport.d.ts +4 -0
  24. package/lib/types/client/reaction-transport.d.ts.map +1 -0
  25. package/lib/types/client/realtime-client-events.d.ts.map +1 -1
  26. package/lib/types/host-api.d.ts.map +1 -1
  27. package/lib/types/local-database.d.ts +1 -0
  28. package/lib/types/local-database.d.ts.map +1 -1
  29. package/lib/types/reaction-contract.d.ts +4 -0
  30. package/lib/types/reaction-contract.d.ts.map +1 -1
  31. package/lib/types/reaction-diagnostics.d.ts +4 -0
  32. package/lib/types/reaction-diagnostics.d.ts.map +1 -0
  33. package/lib/types/reaction-host-diagnostics.d.ts +6 -0
  34. package/lib/types/reaction-host-diagnostics.d.ts.map +1 -0
  35. package/lib/types/request-coordinator.d.ts +2 -0
  36. package/lib/types/request-coordinator.d.ts.map +1 -1
  37. package/lib/types/sdk/index.d.ts +2 -0
  38. package/lib/types/sdk/index.d.ts.map +1 -1
  39. package/lib/types/services/chat-service.d.ts +1 -0
  40. package/lib/types/services/chat-service.d.ts.map +1 -1
  41. package/lib/types/services/media-service.d.ts +14 -0
  42. package/lib/types/services/media-service.d.ts.map +1 -1
  43. package/lib/types/services/profile-service.d.ts.map +1 -1
  44. package/lib/types/services/reaction-service.d.ts.map +1 -1
  45. package/lib/types/services/service.d.ts +1 -0
  46. package/lib/types/services/service.d.ts.map +1 -1
  47. package/lib/types/tools/business/media/read-image.d.ts.map +1 -1
  48. package/lib/types/tools/ports/media.d.ts +1 -0
  49. package/lib/types/tools/ports/media.d.ts.map +1 -1
  50. package/lib/types/types.d.ts +1 -0
  51. package/lib/types/types.d.ts.map +1 -1
  52. package/package.json +5 -5
package/lib/client.js CHANGED
@@ -800,6 +800,20 @@ window.__ModuleLoader__.load({
800
800
  if (imageRef.trim() === "") throw new TypeError("Arkme image reference must not be empty");
801
801
  return await this.call("image.read", { imageRef }, signal);
802
802
  }
803
+ /** Probe immutable image bytes without starting an upstream request. */
804
+ async readCachedImage(imageRef, signal) {
805
+ if (imageRef.trim() === "") throw new TypeError("Arkme image reference must not be empty");
806
+ if ((await this.capabilities(signal)).features.imageCacheRead !== true) throw new Error("当前 Arkme Provider 不支持本地头像探测,请升级");
807
+ try {
808
+ return await this.call("image.read", {
809
+ imageRef,
810
+ cacheOnly: true
811
+ }, signal);
812
+ } catch (error) {
813
+ if (error instanceof ArkmeClientError && error.body.code === "image-cache-miss") return void 0;
814
+ throw error;
815
+ }
816
+ }
803
817
  /** Convert a Provider image payload into a browser-renderable data URL. */
804
818
  imageDataUrl(image) {
805
819
  return `data:${image.mediaType};base64,${image.dataBase64}`;
@@ -54565,84 +54579,15 @@ body[data-ds-dark-theme] .arkme-bot-identity {
54565
54579
  });
54566
54580
  }
54567
54581
  //#endregion
54568
- //#region src/avatar-diagnostics.ts
54569
- function record(value) {
54570
- return value !== null && typeof value === "object" ? value : {};
54571
- }
54572
- function positiveId(value) {
54573
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
54574
- }
54575
- /** Decoded IDs are diagnostic hints only, never authentication or authorization inputs. */
54576
- function avatarReferenceDiagnostic(imageRef) {
54577
- const ref = imageRef.trim();
54578
- if (!ref.startsWith("arkme-profile-image-v1.")) return { referenceKind: ref.startsWith("arkme-media-v1.") ? "media" : "other" };
54579
- const context = { referenceKind: "profile" };
54580
- try {
54581
- const parts = ref.split(".");
54582
- const encoded = parts[1] ?? "";
54583
- if (parts.length !== 3 || encoded.length > 1024 || !/^[A-Za-z0-9_-]+$/.test(encoded)) return context;
54584
- const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
54585
- const payload = record(JSON.parse(globalThis.atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))));
54586
- if (payload.version !== 1) return context;
54587
- const viewer = positiveId(payload.viewerUserId);
54588
- const target = positiveId(payload.targetUserId);
54589
- if (viewer !== void 0) context.referenceViewerUserId = viewer;
54590
- if (target !== void 0) context.referenceTargetUserId = target;
54591
- } catch {}
54592
- return context;
54593
- }
54594
- function avatarScopeDiagnostic(scopeKey) {
54595
- const match = /^(prod|test):([1-9][0-9]*)$/.exec(scopeKey ?? "");
54596
- const viewerUserId = positiveId(Number(match?.[2]));
54597
- return match === null || viewerUserId === void 0 ? {} : {
54598
- environment: match[1],
54599
- viewerUserId
54600
- };
54601
- }
54602
- function safeErrorCode(value) {
54603
- return typeof value === "string" && /^(?:(?:arkme|image|auth-http|local|profile)-[A-Za-z0-9_-]{1,80}|UND_ERR_[A-Z_]{1,64}|E[A-Z_]{1,64})$/.test(value) ? value : void 0;
54604
- }
54605
- function httpStatus(value) {
54606
- return typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599 ? value : void 0;
54607
- }
54608
- function imageDownloadStatus(error) {
54609
- if (error.name !== "ArkmePluginError" || error.code !== "image-download-failed" || typeof error.message !== "string") return void 0;
54610
- const match = /^Arkme 图片读取返回 HTTP ([1-5][0-9]{2})$/.exec(error.message);
54611
- return match === null ? void 0 : httpStatus(Number(match[1]));
54612
- }
54613
- function logArkmeAvatarDiagnostic(event, context, error) {
54614
- try {
54615
- const source = record(error);
54616
- const body = record(source.body);
54617
- const detail = source.name === "ArkmeClientError" ? body : source;
54618
- const errorName = [
54619
- "Error",
54620
- "TypeError",
54621
- "AbortError",
54622
- "TimeoutError",
54623
- "ArkmePluginError",
54624
- "ArkmeClientError"
54625
- ].includes(String(source.name)) ? String(source.name) : "unknown";
54626
- console.warn(`[ArkmeAvatarDiag] ${JSON.stringify({
54627
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
54628
- event,
54629
- ...context,
54630
- ...error === void 0 ? {} : { error: {
54631
- name: errorName,
54632
- code: safeErrorCode(detail.code),
54633
- retryable: typeof detail.retryable === "boolean" ? detail.retryable : void 0,
54634
- httpStatus: httpStatus(source.httpStatus),
54635
- upstreamStatus: httpStatus(source.upstreamStatus) ?? imageDownloadStatus(source),
54636
- causeCode: safeErrorCode(record(source.cause).code)
54637
- } }
54638
- })}`);
54639
- } catch {}
54640
- }
54641
- //#endregion
54642
54582
  //#region src/client/avatar-image-store.ts
54583
+ /** These references identify immutable bytes, unlike mutable profile references. */
54584
+ function isImmutableAvatarRef(ref) {
54585
+ return /^file_asset:\/\/[A-Za-z0-9_-]{8,128}$/.test(ref) || /^arkme-self-role-image-v1\.[A-Za-z0-9_-]+$/.test(ref);
54586
+ }
54643
54587
  const DEFAULT_TTL_MILLIS = 600 * 1e3;
54644
54588
  const DEFAULT_JITTER_MILLIS$1 = 120 * 1e3;
54645
- const DEFAULT_CONCURRENCY = 6;
54589
+ const DEFAULT_CONCURRENCY = 3;
54590
+ const IMMUTABLE_CONCURRENCY = 2;
54646
54591
  const MAX_RETAINED_AVATARS = 256;
54647
54592
  const MAX_QUEUED_AVATARS = 512;
54648
54593
  const AVATAR_IMAGE_SCOPE_CHANGED = "Avatar image scope changed";
@@ -54656,6 +54601,8 @@ body[data-ds-dark-theme] .arkme-bot-identity {
54656
54601
  jitterMillis;
54657
54602
  concurrency;
54658
54603
  activeDownloads = 0;
54604
+ activeImmutableReads = 0;
54605
+ activeCacheProbes = 0;
54659
54606
  generation = 0;
54660
54607
  scopeKey;
54661
54608
  constructor(options) {
@@ -54709,10 +54656,26 @@ body[data-ds-dark-theme] .arkme-bot-identity {
54709
54656
  expiresAtMillis: 0,
54710
54657
  pending: void 0
54711
54658
  };
54712
- const pending = this.schedule(async () => {
54659
+ const readRemote = () => this.schedule(isImmutableAvatarRef(imageRef) ? "immutable" : "remote", async () => {
54713
54660
  if (generation !== this.generation) throw new Error(AVATAR_IMAGE_SCOPE_CHANGED);
54714
54661
  return await this.options.reader(imageRef);
54715
- }).then((payload) => {
54662
+ });
54663
+ const persistent = scopeKey !== void 0 && isImmutableAvatarRef(imageRef) ? this.options.persistentCache : void 0;
54664
+ const probe = isImmutableAvatarRef(imageRef) ? this.options.cachedReader : void 0;
54665
+ const pending = (persistent === void 0 && probe === void 0 ? readRemote() : (async () => {
54666
+ const cached = await persistent?.read(scopeKey, imageRef).catch(() => void 0);
54667
+ if (generation !== this.generation) throw new Error(AVATAR_IMAGE_SCOPE_CHANGED);
54668
+ if (cached !== void 0) return cached;
54669
+ const local = probe === void 0 ? void 0 : await this.schedule("probe", async () => {
54670
+ if (generation !== this.generation) throw new Error(AVATAR_IMAGE_SCOPE_CHANGED);
54671
+ return await probe(imageRef);
54672
+ });
54673
+ if (generation !== this.generation) throw new Error(AVATAR_IMAGE_SCOPE_CHANGED);
54674
+ const payload = local ?? await readRemote();
54675
+ if (generation !== this.generation) throw new Error(AVATAR_IMAGE_SCOPE_CHANGED);
54676
+ persistent?.write(scopeKey, imageRef, payload).catch(() => void 0);
54677
+ return payload;
54678
+ })()).then((payload) => {
54716
54679
  if (generation !== this.generation) throw new Error(AVATAR_IMAGE_SCOPE_CHANGED);
54717
54680
  const value = `data:${payload.mediaType};base64,${payload.dataBase64}`;
54718
54681
  const previousValue = entry.value;
@@ -54751,27 +54714,35 @@ body[data-ds-dark-theme] .arkme-bot-identity {
54751
54714
  if (entry.pending === void 0 && !this.listeners.has(ref)) this.entries.delete(ref);
54752
54715
  }
54753
54716
  }
54754
- schedule(load) {
54717
+ schedule(lane, load) {
54755
54718
  return new Promise((resolve, reject) => {
54756
54719
  if (this.queue.length >= MAX_QUEUED_AVATARS) {
54757
54720
  reject(/* @__PURE__ */ new Error("Avatar loading queue is full"));
54758
54721
  return;
54759
54722
  }
54760
- this.queue.push(() => {
54761
- load().then(resolve, reject).finally(() => {
54762
- this.activeDownloads -= 1;
54763
- this.drainQueue();
54764
- });
54723
+ this.queue.push({
54724
+ lane,
54725
+ start: () => {
54726
+ load().then(resolve, reject).finally(() => {
54727
+ if (lane === "probe") this.activeCacheProbes -= 1;
54728
+ else if (lane === "immutable") this.activeImmutableReads -= 1;
54729
+ else this.activeDownloads -= 1;
54730
+ this.drainQueue();
54731
+ });
54732
+ }
54765
54733
  });
54766
54734
  this.drainQueue();
54767
54735
  });
54768
54736
  }
54769
54737
  drainQueue() {
54770
- while (this.activeDownloads < this.concurrency) {
54771
- const start = this.queue.shift();
54772
- if (start === void 0) return;
54773
- this.activeDownloads += 1;
54774
- start();
54738
+ for (;;) {
54739
+ const index = this.queue.findIndex((job) => job.lane === "probe" ? this.activeCacheProbes < 1 : job.lane === "immutable" ? this.activeImmutableReads < IMMUTABLE_CONCURRENCY : this.activeDownloads < this.concurrency);
54740
+ if (index === -1) return;
54741
+ const job = this.queue.splice(index, 1)[0];
54742
+ if (job.lane === "probe") this.activeCacheProbes += 1;
54743
+ else if (job.lane === "immutable") this.activeImmutableReads += 1;
54744
+ else this.activeDownloads += 1;
54745
+ job.start();
54775
54746
  }
54776
54747
  }
54777
54748
  notify(imageRef, value) {
@@ -54779,8 +54750,185 @@ body[data-ds-dark-theme] .arkme-bot-identity {
54779
54750
  }
54780
54751
  };
54781
54752
  //#endregion
54753
+ //#region src/client/avatar-persistent-cache.ts
54754
+ const CACHE_NAME = "arkme-immutable-avatars-v1";
54755
+ const MAX_ENTRIES$1 = 64;
54756
+ const MAX_BYTES$1 = 64 * 1024 * 1024;
54757
+ const MAX_BASE64_LENGTH = Math.ceil(8 * 1024 * 1024 / 3) * 4;
54758
+ function validPayload(value) {
54759
+ if (typeof value !== "object" || value === null) return false;
54760
+ const payload = value;
54761
+ return typeof payload.mediaType === "string" && /^image\/(png|jpeg|webp|gif|avif)$/.test(payload.mediaType) && typeof payload.dataBase64 === "string" && payload.dataBase64.length > 0 && payload.dataBase64.length <= MAX_BASE64_LENGTH && payload.dataBase64.length % 4 === 0 && !/[^A-Za-z0-9+/]/.test(payload.dataBase64.replace(/={1,2}$/, ""));
54762
+ }
54763
+ /** Validate browser-persisted bytes too: base64 syntax is not image validity. */
54764
+ async function decodableAvatar(payload) {
54765
+ try {
54766
+ const bytes = Uint8Array.from(atob(payload.dataBase64), (character) => character.charCodeAt(0));
54767
+ const blob = new Blob([bytes], { type: payload.mediaType });
54768
+ if (typeof createImageBitmap === "function") {
54769
+ (await createImageBitmap(blob)).close();
54770
+ return true;
54771
+ }
54772
+ if (typeof Image === "undefined") return false;
54773
+ const url = URL.createObjectURL(blob);
54774
+ try {
54775
+ return await new Promise((resolve) => {
54776
+ const image = new Image();
54777
+ image.onload = () => resolve(image.naturalWidth > 0 && image.naturalHeight > 0);
54778
+ image.onerror = () => resolve(false);
54779
+ image.src = url;
54780
+ });
54781
+ } finally {
54782
+ URL.revokeObjectURL(url);
54783
+ }
54784
+ } catch {
54785
+ return false;
54786
+ }
54787
+ }
54788
+ /** Browser bytes survive reloads. Host remains the authenticated source on misses.
54789
+ * No TTL for immutable refs; quota eviction and clearing site data are cache misses.
54790
+ * Synthetic keys are never fetched and include the environment/account scope.
54791
+ */
54792
+ var BrowserAvatarPersistentCache = class {
54793
+ decode;
54794
+ constructor(decode = decodableAvatar) {
54795
+ this.decode = decode;
54796
+ }
54797
+ writes = Promise.resolve();
54798
+ key(scope, ref) {
54799
+ try {
54800
+ if (!scope.trim() || !isImmutableAvatarRef(ref) || typeof globalThis.caches === "undefined" || typeof globalThis.location === "undefined") return void 0;
54801
+ return new URL(`/arkme-self/avatar-cache/v1/${encodeURIComponent(scope)}/${encodeURIComponent(ref)}`, globalThis.location.origin).href;
54802
+ } catch {
54803
+ return;
54804
+ }
54805
+ }
54806
+ async read(scope, ref) {
54807
+ const key = this.key(scope, ref);
54808
+ if (key === void 0) return void 0;
54809
+ try {
54810
+ const cache = await caches.open(CACHE_NAME);
54811
+ const response = await cache.match(key);
54812
+ if (response === void 0) return void 0;
54813
+ const payload = await response.json().catch(() => void 0);
54814
+ if (validPayload(payload) && await this.decode(payload)) return payload;
54815
+ await cache.delete(key);
54816
+ } catch {}
54817
+ }
54818
+ write(scope, ref, payload) {
54819
+ const key = this.key(scope, ref);
54820
+ if (key === void 0 || !validPayload(payload)) return Promise.resolve();
54821
+ const operation = this.writes.then(async () => {
54822
+ if (!await this.decode(payload)) return;
54823
+ const cache = await caches.open(CACHE_NAME);
54824
+ const body = JSON.stringify(payload);
54825
+ const keys = (await cache.keys()).filter((request) => request.url !== key);
54826
+ const sizes = await Promise.all(keys.map(async (request) => Number((await cache.match(request))?.headers.get("Content-Length")) || 0));
54827
+ let bytes = sizes.reduce((sum, size) => sum + size, 0) + body.length;
54828
+ let count = keys.length + 1;
54829
+ for (let index = 0; index < keys.length && (count > MAX_ENTRIES$1 || bytes > MAX_BYTES$1); index++) {
54830
+ await cache.delete(keys[index]);
54831
+ bytes -= sizes[index];
54832
+ count -= 1;
54833
+ }
54834
+ await cache.put(key, new Response(body, { headers: {
54835
+ "Content-Type": "application/json",
54836
+ "Content-Length": String(body.length)
54837
+ } }));
54838
+ });
54839
+ this.writes = operation.catch(() => void 0);
54840
+ return this.writes;
54841
+ }
54842
+ };
54843
+ //#endregion
54844
+ //#region src/avatar-diagnostics.ts
54845
+ function record(value) {
54846
+ return value !== null && typeof value === "object" ? value : {};
54847
+ }
54848
+ function positiveId(value) {
54849
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
54850
+ }
54851
+ /** Decoded IDs are diagnostic hints only, never authentication or authorization inputs. */
54852
+ function avatarReferenceDiagnostic(imageRef) {
54853
+ const ref = imageRef.trim();
54854
+ if (!ref.startsWith("arkme-profile-image-v1.")) return { referenceKind: ref.startsWith("arkme-media-v1.") ? "media" : "other" };
54855
+ const context = { referenceKind: "profile" };
54856
+ try {
54857
+ const parts = ref.split(".");
54858
+ const encoded = parts[1] ?? "";
54859
+ if (parts.length !== 3 || encoded.length > 1024 || !/^[A-Za-z0-9_-]+$/.test(encoded)) return context;
54860
+ const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
54861
+ const payload = record(JSON.parse(globalThis.atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))));
54862
+ if (payload.version !== 1) return context;
54863
+ const viewer = positiveId(payload.viewerUserId);
54864
+ const target = positiveId(payload.targetUserId);
54865
+ if (viewer !== void 0) context.referenceViewerUserId = viewer;
54866
+ if (target !== void 0) context.referenceTargetUserId = target;
54867
+ } catch {}
54868
+ return context;
54869
+ }
54870
+ function avatarScopeDiagnostic(scopeKey) {
54871
+ const match = /^(prod|test):([1-9][0-9]*)$/.exec(scopeKey ?? "");
54872
+ const viewerUserId = positiveId(Number(match?.[2]));
54873
+ return match === null || viewerUserId === void 0 ? {} : {
54874
+ environment: match[1],
54875
+ viewerUserId
54876
+ };
54877
+ }
54878
+ function safeErrorCode(value) {
54879
+ return typeof value === "string" && /^(?:(?:arkme|image|auth-http|local|profile)-[A-Za-z0-9_-]{1,80}|UND_ERR_[A-Z_]{1,64}|E[A-Z_]{1,64})$/.test(value) ? value : void 0;
54880
+ }
54881
+ function httpStatus(value) {
54882
+ return typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599 ? value : void 0;
54883
+ }
54884
+ function imageDownloadStatus(error) {
54885
+ if (error.name !== "ArkmePluginError" || error.code !== "image-download-failed" || typeof error.message !== "string") return void 0;
54886
+ const match = /^Arkme 图片读取返回 HTTP ([1-5][0-9]{2})$/.exec(error.message);
54887
+ return match === null ? void 0 : httpStatus(Number(match[1]));
54888
+ }
54889
+ function logArkmeAvatarDiagnostic(event, context, error) {
54890
+ try {
54891
+ const source = record(error);
54892
+ const body = record(source.body);
54893
+ const detail = source.name === "ArkmeClientError" ? body : source;
54894
+ const errorName = [
54895
+ "Error",
54896
+ "TypeError",
54897
+ "AbortError",
54898
+ "TimeoutError",
54899
+ "ArkmePluginError",
54900
+ "ArkmeClientError"
54901
+ ].includes(String(source.name)) ? String(source.name) : "unknown";
54902
+ console.warn(`[ArkmeAvatarDiag] ${JSON.stringify({
54903
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
54904
+ event,
54905
+ ...context,
54906
+ ...error === void 0 ? {} : { error: {
54907
+ name: errorName,
54908
+ code: safeErrorCode(detail.code),
54909
+ retryable: typeof detail.retryable === "boolean" ? detail.retryable : void 0,
54910
+ httpStatus: httpStatus(source.httpStatus),
54911
+ upstreamStatus: httpStatus(source.upstreamStatus) ?? imageDownloadStatus(source),
54912
+ causeCode: safeErrorCode(record(source.cause).code)
54913
+ } }
54914
+ })}`);
54915
+ } catch {}
54916
+ }
54917
+ //#endregion
54782
54918
  //#region src/client/avatar-image-runtime.ts
54783
54919
  const arkmeAvatarImages = new InMemoryArkmeAvatarImageStore({
54920
+ persistentCache: new BrowserAvatarPersistentCache(),
54921
+ cachedReader: async (imageRef) => {
54922
+ try {
54923
+ return await callArkme("image.read", {
54924
+ imageRef,
54925
+ cacheOnly: true
54926
+ });
54927
+ } catch (error) {
54928
+ if (error instanceof ArkmeClientError && error.body.code === "image-cache-miss") return void 0;
54929
+ throw error;
54930
+ }
54931
+ },
54784
54932
  reader: async (imageRef) => await callArkme("image.read", { imageRef }),
54785
54933
  onLoadFailure: ({ imageRef, scopeKey, error, ...context }) => {
54786
54934
  logArkmeAvatarDiagnostic("image_load_failed", {
@@ -55526,6 +55674,54 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55526
55674
  return stop;
55527
55675
  }
55528
55676
  //#endregion
55677
+ //#region src/reaction-diagnostics.ts
55678
+ /** Timing metadata only: no credentials, message refs, names or message bodies. */
55679
+ function reactionTraceId(value) {
55680
+ return value && /^[a-f0-9-]{36}$/.test(value) ? value : globalThis.crypto.randomUUID();
55681
+ }
55682
+ function logReactionTiming(traceId, stage, started, detail = {}) {
55683
+ console.info(`[ArkmeReactionTiming] ${JSON.stringify({
55684
+ traceId,
55685
+ stage,
55686
+ at: Date.now(),
55687
+ durationMs: Math.round((performance.now() - started) * 100) / 100,
55688
+ ...detail
55689
+ })}`);
55690
+ }
55691
+ //#endregion
55692
+ //#region src/client/reaction-transport.ts
55693
+ const timings = /* @__PURE__ */ new WeakMap();
55694
+ function traceReactionApplied(value, stage) {
55695
+ const timing = timings.get(value);
55696
+ if (timing) logReactionTiming(timing.traceId, stage, timing.started);
55697
+ }
55698
+ async function callReaction(input, signal) {
55699
+ let diagnostics = false;
55700
+ try {
55701
+ diagnostics = globalThis.sessionStorage?.getItem("arkme.reaction.diagnostics") === "1";
55702
+ } catch {}
55703
+ if (!diagnostics) return await callArkme("reactions", input, signal);
55704
+ const traceId = reactionTraceId(input.traceId), started = performance.now();
55705
+ let ok = false;
55706
+ try {
55707
+ const value = await callArkme("reactions", {
55708
+ ...input,
55709
+ traceId
55710
+ }, signal);
55711
+ ok = true;
55712
+ if (diagnostics && value && typeof value === "object") timings.set(value, {
55713
+ traceId,
55714
+ started
55715
+ });
55716
+ return value;
55717
+ } finally {
55718
+ if (diagnostics) logReactionTiming(traceId, "browser-request", started, {
55719
+ action: input.action,
55720
+ ok
55721
+ });
55722
+ }
55723
+ }
55724
+ //#endregion
55529
55725
  //#region src/client/reaction-label-content.ts
55530
55726
  const reactionHandIds = [
55531
55727
  "ok_hand",
@@ -55778,10 +55974,12 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55778
55974
  targets = /* @__PURE__ */ new Map();
55779
55975
  states = /* @__PURE__ */ new Map();
55780
55976
  inactive = /* @__PURE__ */ new Set();
55977
+ actorRefreshAt = /* @__PURE__ */ new Map();
55781
55978
  knownTargets = /* @__PURE__ */ new Map();
55782
55979
  failures = /* @__PURE__ */ new Map();
55783
55980
  listeners = /* @__PURE__ */ new Set();
55784
55981
  revision = 0;
55982
+ reading;
55785
55983
  refreshing;
55786
55984
  timer;
55787
55985
  timerDue = 0;
@@ -55789,7 +55987,7 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55789
55987
  retryMillis = 2e3;
55790
55988
  pending;
55791
55989
  writing = false;
55792
- constructor(transport = (input, signal) => callArkme("reactions", input, signal)) {
55990
+ constructor(transport = callReaction) {
55793
55991
  this.transport = transport;
55794
55992
  }
55795
55993
  subscribe = (listener) => {
@@ -55811,6 +56009,7 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55811
56009
  this.targets.clear();
55812
56010
  this.states.clear();
55813
56011
  this.inactive.clear();
56012
+ this.actorRefreshAt.clear();
55814
56013
  this.knownTargets.clear();
55815
56014
  this.failures.clear();
55816
56015
  this.pending = void 0;
@@ -55823,6 +56022,11 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55823
56022
  }
55824
56023
  watch(scope, target) {
55825
56024
  if (scope !== this.scope) return () => {};
56025
+ const resolver = target.resolveActor ?? this.knownTargets.get(target.id)?.resolveActor;
56026
+ if (resolver) target = {
56027
+ ...target,
56028
+ resolveActor: resolver
56029
+ };
55826
56030
  const existing = this.targets.get(target.id);
55827
56031
  if (!existing && this.targets.size >= 200) {
55828
56032
  this.failures.set(target.id, "请打开消息后刷新表态");
@@ -55851,6 +56055,8 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55851
56055
  this.failures.delete(target.id);
55852
56056
  this.retainInactive(target.id);
55853
56057
  }
56058
+ const reading = this.reading;
56059
+ if (reading && ![...reading.entries].some(([id, entry]) => this.targets.get(id) === entry)) reading.controller.abort();
55854
56060
  if (!this.targets.size) this.stop();
55855
56061
  };
55856
56062
  }
@@ -55862,6 +56068,7 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55862
56068
  const oldest = this.inactive.values().next().value;
55863
56069
  this.inactive.delete(oldest);
55864
56070
  this.states.delete(oldest);
56071
+ this.actorRefreshAt.delete(oldest);
55865
56072
  this.knownTargets.delete(oldest);
55866
56073
  this.failures.delete(oldest);
55867
56074
  }
@@ -55884,9 +56091,20 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55884
56091
  await this.refresh();
55885
56092
  if (active && !signal.aborted && scope === this.scope && (alreadyReading || missing.some((target) => !this.states.has(target.id) && !this.failures.has(target.id)))) await this.refresh();
55886
56093
  };
56094
+ let unsubscribe = () => {};
56095
+ const factsReady = new Promise((resolve) => {
56096
+ unsubscribe = this.subscribe(() => {
56097
+ if (missing.every((target) => this.states.has(target.id) || this.failures.has(target.id))) resolve();
56098
+ });
56099
+ });
55887
56100
  try {
55888
- await Promise.race([load(), deadline]);
56101
+ await Promise.race([
56102
+ load(),
56103
+ deadline,
56104
+ factsReady
56105
+ ]);
55889
56106
  } finally {
56107
+ unsubscribe();
55890
56108
  active = false;
55891
56109
  clearTimeout(timer);
55892
56110
  signal.removeEventListener("abort", finish);
@@ -55947,11 +56165,17 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55947
56165
  }
55948
56166
  async refresh() {
55949
56167
  if (this.refreshing) return this.refreshing;
55950
- const scope = this.scope, signal = this.controller.signal;
56168
+ const scope = this.scope;
55951
56169
  if (!scope) return;
56170
+ const controller = new AbortController(), entries = new Map(this.targets);
56171
+ const reading = {
56172
+ controller,
56173
+ entries
56174
+ };
56175
+ this.reading = reading;
56176
+ const signal = AbortSignal.any([this.controller.signal, controller.signal]);
55952
56177
  const operation = (async () => {
55953
56178
  let failed = false;
55954
- const entries = new Map(this.targets);
55955
56179
  const targets = [...entries.values()].map((item) => item.target);
55956
56180
  for (let offset = 0; offset < targets.length; offset += 50) {
55957
56181
  const batch = targets.slice(offset, offset + 50);
@@ -55960,7 +56184,8 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55960
56184
  const page = await this.transport({
55961
56185
  action: "query",
55962
56186
  accountKey: scope,
55963
- targets: batch
56187
+ targets: batch,
56188
+ actorPresentation: "deferred"
55964
56189
  }, signal);
55965
56190
  if (signal.aborted) return;
55966
56191
  const returned = new Set(page.items.map((item) => item.target_id));
@@ -55973,8 +56198,25 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55973
56198
  }
55974
56199
  for (const item of page.items) if (entries.has(item.target_id) && this.targets.get(item.target_id) === entries.get(item.target_id)) {
55975
56200
  const previous = this.states.get(item.target_id);
55976
- if (!previous || previous.mine.revision <= item.mine.revision) this.states.set(item.target_id, item);
56201
+ if (!previous || previous.mine.revision <= item.mine.revision) {
56202
+ const known = new Map((Date.now() - (this.actorRefreshAt.get(item.target_id) ?? 0) < 6e4 ? previous?.groups : void 0)?.flatMap((group) => group.actors ?? []).filter((actor) => !actor.presentationPending).map((actor) => [actor.userId, actor]));
56203
+ const resolve = this.targets.get(item.target_id)?.target.resolveActor;
56204
+ this.states.set(item.target_id, {
56205
+ ...item,
56206
+ groups: item.groups.map((group) => ({
56207
+ ...group,
56208
+ actors: (group.actors ?? []).map((actor) => resolve?.(actor) ?? (actor.presentationPending && known.has(actor.userId) ? {
56209
+ ...actor,
56210
+ ...known.get(actor.userId),
56211
+ ...actor.memberRef ? { memberRef: actor.memberRef } : {},
56212
+ presentationPending: false
56213
+ } : actor))
56214
+ }))
56215
+ });
56216
+ }
55977
56217
  }
56218
+ if (before !== JSON.stringify(batch.map((t) => [this.states.get(t.id), this.failures.get(t.id)]))) this.publish();
56219
+ traceReactionApplied(page, "browser-facts-published");
55978
56220
  } catch (error) {
55979
56221
  if (signal.aborted) return;
55980
56222
  failed = true;
@@ -55983,7 +56225,52 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55983
56225
  this.failures.set(target.id, error instanceof Error ? error.message : "表态加载失败");
55984
56226
  }
55985
56227
  }
55986
- if (!signal.aborted && before !== JSON.stringify(batch.map((t) => [this.states.get(t.id), this.failures.get(t.id)]))) this.publish();
56228
+ if (failed && !signal.aborted && before !== JSON.stringify(batch.map((t) => [this.states.get(t.id), this.failures.get(t.id)]))) this.publish();
56229
+ const missing = batch.filter((target) => this.states.get(target.id)?.groups.some((group) => group.actors?.some((actor) => actor.presentationPending)));
56230
+ if (missing.length && !signal.aborted) {
56231
+ const initial = new Map(missing.map((target) => [target.id, this.states.get(target.id)]));
56232
+ try {
56233
+ const enriched = await this.transport({
56234
+ action: "query",
56235
+ accountKey: scope,
56236
+ targets: missing
56237
+ }, signal);
56238
+ if (signal.aborted) return;
56239
+ for (const item of enriched.items) {
56240
+ const current = this.states.get(item.target_id);
56241
+ if (!current || current !== initial.get(item.target_id) || this.targets.get(item.target_id) !== entries.get(item.target_id)) continue;
56242
+ this.actorRefreshAt.set(item.target_id, Date.now());
56243
+ if (!item.actors_visible) {
56244
+ this.states.set(item.target_id, {
56245
+ ...current,
56246
+ actors_visible: false,
56247
+ private: item.private,
56248
+ groups: current.groups.map((group) => ({
56249
+ ...group,
56250
+ actors: []
56251
+ }))
56252
+ });
56253
+ continue;
56254
+ }
56255
+ const actors = new Map(item.groups.flatMap((group) => group.actors ?? []).map((actor) => [actor.userId, actor]));
56256
+ const resolve = this.targets.get(item.target_id)?.target.resolveActor;
56257
+ this.states.set(item.target_id, {
56258
+ ...current,
56259
+ groups: current.groups.map((group) => ({
56260
+ ...group,
56261
+ actors: (group.actors ?? []).map((actor) => resolve?.(actor) ?? (actor.presentationPending && actors.has(actor.userId) ? {
56262
+ ...actor,
56263
+ ...actors.get(actor.userId),
56264
+ ...actor.memberRef ? { memberRef: actor.memberRef } : {},
56265
+ presentationPending: false
56266
+ } : actor))
56267
+ }))
56268
+ });
56269
+ }
56270
+ this.publish();
56271
+ traceReactionApplied(enriched, "browser-actors-published");
56272
+ } catch {}
56273
+ }
55987
56274
  }
55988
56275
  if (!signal.aborted) this.retryMillis = failed ? Math.min(12e4, this.retryMillis * 2) : 2e3;
55989
56276
  })();
@@ -55991,6 +56278,7 @@ body[data-ds-dark-theme] .arkme-bot-identity {
55991
56278
  try {
55992
56279
  await operation;
55993
56280
  } finally {
56281
+ if (this.reading === reading) this.reading = void 0;
55994
56282
  if (this.refreshing === operation) {
55995
56283
  this.refreshing = void 0;
55996
56284
  if (this.refreshAgain) {
@@ -56158,7 +56446,7 @@ body[data-ds-dark-theme] .arkme-bot-identity {
56158
56446
  users = 0;
56159
56447
  error = "";
56160
56448
  hasMore = false;
56161
- constructor(transport = (input, signal) => callArkme("reactions", input, signal)) {
56449
+ constructor(transport = callReaction) {
56162
56450
  this.transport = transport;
56163
56451
  }
56164
56452
  subscribe = (fn) => {
@@ -56279,6 +56567,7 @@ body[data-ds-dark-theme] .arkme-bot-identity {
56279
56567
  limit: 50
56280
56568
  }, signal);
56281
56569
  if (signal.aborted) return;
56570
+ traceReactionApplied(page, "browser-notifications-received");
56282
56571
  rows.push(...page.items);
56283
56572
  hasMore = page.has_more;
56284
56573
  if (!page.has_more) break;
@@ -57947,29 +58236,29 @@ body[data-ds-dark-theme] .arkme-bot-identity {
57947
58236
  this.timer = void 0;
57948
58237
  }
57949
58238
  };
57950
- const caches = /* @__PURE__ */ new Map();
58239
+ const caches$1 = /* @__PURE__ */ new Map();
57951
58240
  function activateSelfTopicDirectoryAccount(account) {
57952
- for (const [key, cache] of caches) {
58241
+ for (const [key, cache] of caches$1) {
57953
58242
  if (key === account) continue;
57954
58243
  cache.dispose();
57955
- caches.delete(key);
58244
+ caches$1.delete(key);
57956
58245
  }
57957
58246
  }
57958
58247
  function selfTopicDirectory(userId, environment) {
57959
58248
  const key = `${environment}:${String(userId)}`;
57960
- let cache = caches.get(key);
58249
+ let cache = caches$1.get(key);
57961
58250
  if (!cache) {
57962
58251
  cache = new SelfTopicDirectoryCache(userId, environment);
57963
- caches.set(key, cache);
58252
+ caches$1.set(key, cache);
57964
58253
  }
57965
58254
  return cache;
57966
58255
  }
57967
58256
  function invalidateSelfTopicDirectories(hard = false) {
57968
- for (const cache of caches.values()) cache.invalidate(hard);
58257
+ for (const cache of caches$1.values()) cache.invalidate(hard);
57969
58258
  }
57970
58259
  function resetSelfTopicDirectories() {
57971
- for (const cache of caches.values()) cache.dispose();
57972
- caches.clear();
58260
+ for (const cache of caches$1.values()) cache.dispose();
58261
+ caches$1.clear();
57973
58262
  }
57974
58263
  //#endregion
57975
58264
  //#region src/client/provider-instance-runtime.ts
@@ -118596,7 +118885,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
118596
118885
  displayName: actorGroupNickname?.trim() || actorName,
118597
118886
  ...actorGroupNickname ? { groupNickname: actorGroupNickname } : {},
118598
118887
  ...actorAvatarRef ? { avatarRef: actorAvatarRef } : {}
118599
- } : actor;
118888
+ } : target.resolveActor?.(actor) ?? actor;
118600
118889
  const name = identity.displayName;
118601
118890
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [index > 0 && "、", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
118602
118891
  type: "button",
@@ -120961,14 +121250,17 @@ Please report this to https://github.com/markedjs/marked.`, e) {
120961
121250
  scope,
120962
121251
  roles: [],
120963
121252
  loading: true,
121253
+ loaded: false,
120964
121254
  error: ""
120965
121255
  });
120966
121256
  const visibleState = roleState.scope === scope ? roleState : {
120967
121257
  scope,
120968
121258
  roles: [],
120969
121259
  loading: true,
121260
+ loaded: false,
120970
121261
  error: ""
120971
121262
  };
121263
+ const initialLoading = visibleState.loading && !visibleState.loaded;
120972
121264
  const [refreshRevision, setRefreshRevision] = (0, react$1.useState)(0);
120973
121265
  (0, react$1.useEffect)(() => {
120974
121266
  if (typeof window === "undefined") return;
@@ -120986,6 +121278,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
120986
121278
  const [saving, setSaving] = (0, react$1.useState)(false);
120987
121279
  const [formError, setFormError] = (0, react$1.useState)("");
120988
121280
  const fileInputRef = (0, react$1.useRef)(null);
121281
+ const listControllerRef = (0, react$1.useRef)();
120989
121282
  const saveControllerRef = (0, react$1.useRef)();
120990
121283
  (0, react$1.useEffect)(() => {
120991
121284
  setMenuOpen(false);
@@ -121004,31 +121297,37 @@ Please report this to https://github.com/markedjs/marked.`, e) {
121004
121297
  }, [scope]);
121005
121298
  (0, react$1.useEffect)(() => {
121006
121299
  const controller = new AbortController();
121007
- setRoleState({
121300
+ listControllerRef.current = controller;
121301
+ setRoleState((current) => ({
121008
121302
  scope,
121009
- roles: roleState.scope === scope ? roleState.roles : [],
121303
+ roles: current.scope === scope ? current.roles : [],
121304
+ loaded: current.scope === scope && current.loaded,
121010
121305
  loading: true,
121011
121306
  error: ""
121012
- });
121307
+ }));
121013
121308
  callArkme("self-roles.list", { expectedUserId: userId }, controller.signal).then((roles) => {
121014
121309
  if (controller.signal.aborted || scopeRef.current !== scope) return;
121015
121310
  setRoleState({
121016
121311
  scope,
121017
121312
  roles,
121018
121313
  loading: false,
121314
+ loaded: true,
121019
121315
  error: ""
121020
121316
  });
121317
+ for (const ref of new Set(roles.map((role) => role.avatarRef?.trim()).filter((ref) => !!ref))) arkmeAvatarImages.load(ref).catch(() => void 0);
121021
121318
  }).catch((caught) => {
121022
121319
  if (controller.signal.aborted || scopeRef.current !== scope) return;
121023
121320
  setRoleState((current) => ({
121024
121321
  scope,
121025
121322
  roles: current.scope === scope ? current.roles : [],
121323
+ loaded: current.scope === scope && current.loaded,
121026
121324
  loading: false,
121027
121325
  error: caught instanceof Error ? caught.message : "角色加载失败,请重试"
121028
121326
  }));
121029
121327
  });
121030
121328
  return () => {
121031
121329
  controller.abort();
121330
+ if (listControllerRef.current === controller) listControllerRef.current = void 0;
121032
121331
  };
121033
121332
  }, [
121034
121333
  scope,
@@ -121071,7 +121370,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
121071
121370
  const createRole = async (event) => {
121072
121371
  event.preventDefault();
121073
121372
  const roleName = name.trim();
121074
- if (disabled || saveControllerRef.current !== void 0 || visibleState.loading || visibleState.error !== "" || editingRole === void 0 && visibleState.roles.length >= MAX_ROLES || !roleName || Array.from(roleName).length > MAX_NAME_LENGTH) return;
121373
+ if (disabled || saveControllerRef.current !== void 0 || initialLoading || visibleState.error !== "" || editingRole === void 0 && visibleState.roles.length >= MAX_ROLES || !roleName || Array.from(roleName).length > MAX_NAME_LENGTH) return;
121075
121374
  const controller = new AbortController();
121076
121375
  saveControllerRef.current = controller;
121077
121376
  const submittingScope = scope;
@@ -121087,8 +121386,12 @@ Please report this to https://github.com/markedjs/marked.`, e) {
121087
121386
  ...avatarRef === void 0 ? {} : { avatarRef }
121088
121387
  }, controller.signal);
121089
121388
  if (controller.signal.aborted || scopeRef.current !== submittingScope) return;
121389
+ listControllerRef.current?.abort();
121090
121390
  setRoleState((current) => current.scope === submittingScope ? {
121091
121391
  ...current,
121392
+ loading: false,
121393
+ loaded: true,
121394
+ error: "",
121092
121395
  roles: [...current.roles.filter((role) => role.roleId !== created.roleId), created]
121093
121396
  } : current);
121094
121397
  setCreateOpen(false);
@@ -121120,7 +121423,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
121120
121423
  event.stopPropagation();
121121
121424
  },
121122
121425
  onClick: () => {
121123
- if (!menuOpen) setRefreshRevision((value) => value + 1);
121426
+ if (!menuOpen && !visibleState.loading) setRefreshRevision((value) => value + 1);
121124
121427
  setMenuOpen((value) => !value);
121125
121428
  },
121126
121429
  children: [
@@ -121168,8 +121471,12 @@ Please report this to https://github.com/markedjs/marked.`, e) {
121168
121471
  roleId: role.roleId
121169
121472
  }).then(() => {
121170
121473
  if (scopeRef.current !== scope) return;
121474
+ listControllerRef.current?.abort();
121171
121475
  setRoleState((current) => current.scope === scope ? {
121172
121476
  ...current,
121477
+ loading: false,
121478
+ loaded: true,
121479
+ error: "",
121173
121480
  roles: current.roles.filter((item) => item.roleId !== role.roleId)
121174
121481
  } : current);
121175
121482
  setRefreshRevision((value) => value + 1);
@@ -121219,7 +121526,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
121219
121526
  onSelect: () => deleteRole(role)
121220
121527
  }]
121221
121528
  })),
121222
- ...visibleState.loading ? [{
121529
+ ...initialLoading ? [{
121223
121530
  type: "label",
121224
121531
  id: "role-loading",
121225
121532
  text: "正在加载角色…"
@@ -121242,7 +121549,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
121242
121549
  {
121243
121550
  id: "role-create",
121244
121551
  label: "+ 创建角色",
121245
- disabled: visibleState.loading || visibleState.error !== "" || visibleState.roles.length >= MAX_ROLES,
121552
+ disabled: initialLoading || visibleState.error !== "" || visibleState.roles.length >= MAX_ROLES,
121246
121553
  onSelect: () => {
121247
121554
  setMenuOpen(false);
121248
121555
  setEditingRole(void 0);
@@ -212533,7 +212840,7 @@ registerProcessor('arkme-pcm-journal', ArkmePcmJournal);
212533
212840
  const c$3 = e$20;
212534
212841
  //#endregion
212535
212842
  //#region package.json
212536
- var version = "0.1.85";
212843
+ var version = "0.1.86";
212537
212844
  //#endregion
212538
212845
  //#region src/client/arkme-membership.ts
212539
212846
  function membershipLabel(state) {
@@ -234417,7 +234724,6 @@ registerProcessor('arkme-pcm-journal', ArkmePcmJournal);
234417
234724
  return result;
234418
234725
  }, { signal }), controller.signal);
234419
234726
  page = refreshWindow === void 0 ? await readPage(cursor) : await readConversationTimelineWindow(refreshWindow, readPage, controller.signal);
234420
- if (refreshWindow === void 0 && intent !== "return-to-latest") await prepareTimelineReactions(page.items, controller.signal);
234421
234727
  } catch (caught) {
234422
234728
  if (isArkmeRequestAbort(caught, controller.signal)) return;
234423
234729
  throw caught;
@@ -234510,7 +234816,6 @@ registerProcessor('arkme-pcm-journal', ArkmePcmJournal);
234510
234816
  acknowledgeRead,
234511
234817
  confirmedSendRetention,
234512
234818
  interwovenMoments,
234513
- prepareTimelineReactions,
234514
234819
  source,
234515
234820
  sourceIsChat,
234516
234821
  sourceProjectionRevision
@@ -236422,6 +236727,43 @@ registerProcessor('arkme-pcm-journal', ArkmePcmJournal);
236422
236727
  return projected.isMuted === true;
236423
236728
  }, [updateSourceProjection]);
236424
236729
  const conversationMemberByRef = (0, react$1.useMemo)(() => new Map(conversationMembers.map((member) => [member.memberRef, member])), [conversationMembers]);
236730
+ const localReactionActors = (0, react$1.useMemo)(() => {
236731
+ const names = /* @__PURE__ */ new Map();
236732
+ for (const item of items) if (item.memberRef && !item.selfRole && item.senderKind !== "bot") names.set(item.memberRef, {
236733
+ displayName: arkmeTimelineSenderName(item, selfProfile),
236734
+ ...item.avatarRef ? { avatarRef: item.avatarRef } : {}
236735
+ });
236736
+ for (const member of conversationMembers) if (member.status === "active" && member.displayName.trim() && member.displayName !== "群成员") names.set(member.memberRef, {
236737
+ displayName: member.displayName,
236738
+ ...member.avatarRef ? { avatarRef: member.avatarRef } : {}
236739
+ });
236740
+ return names;
236741
+ }, [
236742
+ items,
236743
+ selfProfile,
236744
+ conversationMembers
236745
+ ]);
236746
+ const resolveReactionActor = (0, react$1.useCallback)((actor) => {
236747
+ const self = conversationMembers.find((member) => member.isSelf);
236748
+ const local = actor.userId === authenticatedUserId ? {
236749
+ displayName: (source?.kind === "group_chat" ? self?.memberName : void 0) || selfProfile?.displayName || selfProfile?.nickname || "我",
236750
+ ...selfProfile?.avatarRef ? { avatarRef: selfProfile.avatarRef } : {}
236751
+ } : source?.kind === "private_chat" && actor.userId === source.peerUserId ? {
236752
+ displayName: source.displayName,
236753
+ ...source.avatarRef ? { avatarRef: source.avatarRef } : {}
236754
+ } : actor.memberRef ? localReactionActors.get(actor.memberRef) : void 0;
236755
+ return local ? {
236756
+ ...actor,
236757
+ ...local,
236758
+ presentationPending: false
236759
+ } : void 0;
236760
+ }, [
236761
+ authenticatedUserId,
236762
+ source,
236763
+ selfProfile,
236764
+ conversationMembers,
236765
+ localReactionActors
236766
+ ]);
236425
236767
  const composerMentionsEnabled = source?.kind === "group_chat" || source?.kind === "private_chat";
236426
236768
  const mentionCandidates = (0, react$1.useMemo)(() => {
236427
236769
  if (!composerMentionsEnabled || mentionTrigger === void 0) return [];
@@ -238971,6 +239313,7 @@ registerProcessor('arkme-pcm-journal', ArkmePcmJournal);
238971
239313
  },
238972
239314
  children: [!isSharedRecordingCard && !isExtensionMessage && messageHeader, (() => {
238973
239315
  const reactionTarget = {
239316
+ resolveActor: resolveReactionActor,
238974
239317
  sourceKey: source.sourceKey,
238975
239318
  itemUid: item.itemUid,
238976
239319
  id: `${conversationKey}:${arkmeTimelineOccurrenceKey(item)}`,
@@ -239215,6 +239558,7 @@ registerProcessor('arkme-pcm-journal', ArkmePcmJournal);
239215
239558
  selfProfile,
239216
239559
  selfConversationMember,
239217
239560
  conversationMemberByRef,
239561
+ resolveReactionActor,
239218
239562
  activeSelectMode,
239219
239563
  archiveReadOnly,
239220
239564
  authenticatedAccountKey,
@@ -241885,7 +242229,7 @@ registerProcessor('arkme-pcm-journal', ArkmePcmJournal);
241885
242229
  };
241886
242230
  });
241887
242231
  const foreground = arkmeRealtimeTimelineDeliveryAllowed(browserDocument?.visibilityState, browserDocument?.hasFocus?.() ?? true);
241888
- if (foreground && timelineUpdates.length > 0) arkmeChatTimelineDelta.publish(timelineUpdates);
242232
+ if (timelineUpdates.length > 0) arkmeChatTimelineDelta.publish(timelineUpdates);
241889
242233
  for (const dateStamp of arkmeChatDeltaCalendarDateStamps(update)) arkmeCalendarInvalidations.publish({ dateStamp });
241890
242234
  if (foreground && arkmeSelectedBotAffectedByChatDelta(arkmeUi.getSnapshot().selectedBot, update)) arkmeUi.chatChanged();
241891
242235
  for (const sourceKey of arkmeChatDeltaSourceKeys(update)) arkmeInterwovenInvalidation.invalidate(sourceKey);