@senguoyun/dsh-arkme 0.1.83 → 0.1.84

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 (56) hide show
  1. package/docs/recognized-speaker-directory-backend-handoff-2026-09-28.md +157 -0
  2. package/docs/recognized-speaker-entry-and-visits.md +47 -0
  3. package/docs/recognized-speaker-presence-delivery.md +51 -0
  4. package/docs/recognized-speaker-sorting.md +30 -0
  5. package/lib/client.js +1194 -253
  6. package/lib/client.js.map +1 -1
  7. package/lib/harness-model-client.js +51 -7
  8. package/lib/harness-native-selection-client.js +35 -0
  9. package/lib/harness-sidebar-client.js +20 -0
  10. package/lib/index.js +154 -77
  11. package/lib/persistent-extension.js +1 -1
  12. package/lib/{persistent-runtime-B6vPAaiG.js → persistent-runtime-CuKplHIv.js} +101 -262
  13. package/lib/sdk.js +20 -0
  14. package/lib/types/arkme-service.d.ts +1 -1
  15. package/lib/types/arkme-service.d.ts.map +1 -1
  16. package/lib/types/client/ArkmeAccountUsage.d.ts.map +1 -1
  17. package/lib/types/client/ArkmeModelPricing.d.ts +9 -0
  18. package/lib/types/client/ArkmeModelPricing.d.ts.map +1 -0
  19. package/lib/types/client/ArkmeModelSelect.d.ts.map +1 -1
  20. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  21. package/lib/types/client/ArkmeRecognizedSpeakersSurface.d.ts +6 -2
  22. package/lib/types/client/ArkmeRecognizedSpeakersSurface.d.ts.map +1 -1
  23. package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  25. package/lib/types/client/ArkoModelMenu.d.ts.map +1 -1
  26. package/lib/types/client/locales/en.d.ts.map +1 -1
  27. package/lib/types/client/reaction-preview-store.d.ts +1 -0
  28. package/lib/types/client/reaction-preview-store.d.ts.map +1 -1
  29. package/lib/types/client/recognized-speaker-directory.d.ts +36 -0
  30. package/lib/types/client/recognized-speaker-directory.d.ts.map +1 -0
  31. package/lib/types/client/recognized-speaker-order.d.ts +28 -0
  32. package/lib/types/client/recognized-speaker-order.d.ts.map +1 -0
  33. package/lib/types/client/recognized-speaker-tracker.d.ts +33 -0
  34. package/lib/types/client/recognized-speaker-tracker.d.ts.map +1 -0
  35. package/lib/types/client/recordings/RecognizedSpeakerEntry.d.ts +12 -0
  36. package/lib/types/client/recordings/RecognizedSpeakerEntry.d.ts.map +1 -0
  37. package/lib/types/client/recordings/SpeakerSelfGuide.d.ts +8 -0
  38. package/lib/types/client/recordings/SpeakerSelfGuide.d.ts.map +1 -0
  39. package/lib/types/client/recordings/recording-speaker-options-store.d.ts.map +1 -1
  40. package/lib/types/sdk/index.d.ts +10 -0
  41. package/lib/types/sdk/index.d.ts.map +1 -1
  42. package/lib/types/services/chat-service.d.ts.map +1 -1
  43. package/lib/types/services/recording-service.d.ts +2 -5
  44. package/lib/types/services/recording-service.d.ts.map +1 -1
  45. package/lib/types/services/unmarked-speaker-service.d.ts.map +1 -1
  46. package/lib/types/tools/business/recordings/index.d.ts +1 -1
  47. package/lib/types/tools/business/recordings/index.d.ts.map +1 -1
  48. package/lib/types/tools/business/recordings/speaker-presence.d.ts +2 -0
  49. package/lib/types/tools/business/recordings/speaker-presence.d.ts.map +1 -0
  50. package/lib/types/tools/ports/recordings.d.ts +4 -0
  51. package/lib/types/tools/ports/recordings.d.ts.map +1 -1
  52. package/lib/types/types.d.ts +18 -7
  53. package/lib/types/types.d.ts.map +1 -1
  54. package/package.json +5 -5
  55. package/lib/types/recording-speaker-presence.d.ts +0 -28
  56. package/lib/types/recording-speaker-presence.d.ts.map +0 -1
@@ -4479,7 +4479,7 @@ function modelPointPriceDescription(value) {
4479
4479
  }
4480
4480
  return v;
4481
4481
  };
4482
- return `每 1,000 Token:输入 ${read("cache_miss_input_per_thousand")} 积分,缓存输入 ${read("cache_hit_input_per_thousand")} 积分,输出 ${read("output_per_thousand")} 积分。已含服务费,实际按用量扣分。`;
4482
+ return `每 1,000 Token:输入 ${read("cache_miss_input_per_thousand")} 积分,缓存输入 ${read("cache_hit_input_per_thousand")} 积分,输出 ${read("output_per_thousand")} 积分。`;
4483
4483
  }
4484
4484
  function parseManagedCatalog(payload) {
4485
4485
  const envelope = asRecord(payload);
@@ -15597,7 +15597,7 @@ function tokenizeThematicBreak(effects, ok, nok) {
15597
15597
  * } from 'micromark-util-types'
15598
15598
  */
15599
15599
  /** @type {Construct} */
15600
- const list$5 = {
15600
+ const list$4 = {
15601
15601
  continuation: { tokenize: tokenizeListContinuation },
15602
15602
  exit: tokenizeListEnd,
15603
15603
  name: "list",
@@ -15717,7 +15717,7 @@ function tokenizeListContinuation(effects, ok, nok) {
15717
15717
  function notInCurrentItem(code) {
15718
15718
  self.containerState._closeFlow = true;
15719
15719
  self.interrupt = void 0;
15720
- return factorySpace(effects, effects.attempt(list$5, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
15720
+ return factorySpace(effects, effects.attempt(list$4, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
15721
15721
  }
15722
15722
  }
15723
15723
  /**
@@ -15968,7 +15968,7 @@ function initializeFlow(effects) {
15968
15968
  * } from 'micromark-util-types'
15969
15969
  */
15970
15970
  const resolver = { resolveAll: createResolver() };
15971
- const string$2 = initializeFactory("string");
15971
+ const string$1 = initializeFactory("string");
15972
15972
  const text$10 = initializeFactory("text");
15973
15973
  /**
15974
15974
  * @param {'string' | 'text'} field
@@ -16145,24 +16145,24 @@ var constructs_exports = /* @__PURE__ */ __exportAll({
16145
16145
  flow: () => flow,
16146
16146
  flowInitial: () => flowInitial,
16147
16147
  insideSpan: () => insideSpan,
16148
- string: () => string$1,
16148
+ string: () => string,
16149
16149
  text: () => text$9
16150
16150
  });
16151
16151
  /** @satisfies {Extension['document']} */
16152
16152
  const document$1 = {
16153
- [42]: list$5,
16154
- [43]: list$5,
16155
- [45]: list$5,
16156
- [48]: list$5,
16157
- [49]: list$5,
16158
- [50]: list$5,
16159
- [51]: list$5,
16160
- [52]: list$5,
16161
- [53]: list$5,
16162
- [54]: list$5,
16163
- [55]: list$5,
16164
- [56]: list$5,
16165
- [57]: list$5,
16153
+ [42]: list$4,
16154
+ [43]: list$4,
16155
+ [45]: list$4,
16156
+ [48]: list$4,
16157
+ [49]: list$4,
16158
+ [50]: list$4,
16159
+ [51]: list$4,
16160
+ [52]: list$4,
16161
+ [53]: list$4,
16162
+ [54]: list$4,
16163
+ [55]: list$4,
16164
+ [56]: list$4,
16165
+ [57]: list$4,
16166
16166
  [62]: blockQuote
16167
16167
  };
16168
16168
  /** @satisfies {Extension['contentInitial']} */
@@ -16185,7 +16185,7 @@ const flow = {
16185
16185
  [126]: codeFenced
16186
16186
  };
16187
16187
  /** @satisfies {Extension['string']} */
16188
- const string$1 = {
16188
+ const string = {
16189
16189
  [38]: characterReference,
16190
16190
  [92]: characterEscape
16191
16191
  };
@@ -16705,7 +16705,7 @@ function parse(options) {
16705
16705
  document: create(document$2),
16706
16706
  flow: create(flow$1),
16707
16707
  lazy: {},
16708
- string: create(string$2),
16708
+ string: create(string$1),
16709
16709
  text: create(text$10)
16710
16710
  };
16711
16711
  return parser;
@@ -20500,7 +20500,7 @@ function checkRule(state) {
20500
20500
  * @param {Info} info
20501
20501
  * @returns {string}
20502
20502
  */
20503
- function list$4(node, parent, state, info) {
20503
+ function list$3(node, parent, state, info) {
20504
20504
  const exit = state.enter("list");
20505
20505
  const bulletCurrent = state.bulletCurrent;
20506
20506
  /** @type {string} */
@@ -20775,7 +20775,7 @@ const handle = {
20775
20775
  inlineCode,
20776
20776
  link,
20777
20777
  linkReference,
20778
- list: list$4,
20778
+ list: list$3,
20779
20779
  listItem,
20780
20780
  paragraph,
20781
20781
  root,
@@ -39588,7 +39588,7 @@ async function readTopicMetadata(runtime, session, topicUid, signal) {
39588
39588
  }
39589
39589
  //#endregion
39590
39590
  //#region src/avatar-diagnostics.ts
39591
- function record$1(value) {
39591
+ function record(value) {
39592
39592
  return value !== null && typeof value === "object" ? value : {};
39593
39593
  }
39594
39594
  function positiveId(value) {
@@ -39604,7 +39604,7 @@ function avatarReferenceDiagnostic(imageRef) {
39604
39604
  const encoded = parts[1] ?? "";
39605
39605
  if (parts.length !== 3 || encoded.length > 1024 || !/^[A-Za-z0-9_-]+$/.test(encoded)) return context;
39606
39606
  const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
39607
- const payload = record$1(JSON.parse(globalThis.atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))));
39607
+ const payload = record(JSON.parse(globalThis.atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))));
39608
39608
  if (payload.version !== 1) return context;
39609
39609
  const viewer = positiveId(payload.viewerUserId);
39610
39610
  const target = positiveId(payload.targetUserId);
@@ -39626,8 +39626,8 @@ function imageDownloadStatus(error) {
39626
39626
  }
39627
39627
  function logArkmeAvatarDiagnostic(event, context, error) {
39628
39628
  try {
39629
- const source = record$1(error);
39630
- const body = record$1(source.body);
39629
+ const source = record(error);
39630
+ const body = record(source.body);
39631
39631
  const detail = source.name === "ArkmeClientError" ? body : source;
39632
39632
  const errorName = [
39633
39633
  "Error",
@@ -39647,7 +39647,7 @@ function logArkmeAvatarDiagnostic(event, context, error) {
39647
39647
  retryable: typeof detail.retryable === "boolean" ? detail.retryable : void 0,
39648
39648
  httpStatus: httpStatus(source.httpStatus),
39649
39649
  upstreamStatus: httpStatus(source.upstreamStatus) ?? imageDownloadStatus(source),
39650
- causeCode: safeErrorCode(record$1(source.cause).code)
39650
+ causeCode: safeErrorCode(record(source.cause).code)
39651
39651
  } }
39652
39652
  })}`);
39653
39653
  } catch {}
@@ -46890,7 +46890,7 @@ function botDisplayName(profiles, uid) {
46890
46890
  }
46891
46891
  //#endregion
46892
46892
  //#region src/services/chat-sender-display-reader.ts
46893
- const list$3 = (value) => Array.isArray(value) ? value : [];
46893
+ const list$2 = (value) => Array.isArray(value) ? value : [];
46894
46894
  const text$3 = (value) => stringValue$2(value).trim();
46895
46895
  const first = (raw, keys) => keys.map((key) => text$3(raw[key])).find(Boolean) ?? "";
46896
46896
  /** Normalize wire data once. Neither raw participants nor their extra fields escape this adapter. */
@@ -46900,7 +46900,7 @@ function botDisplaySnapshot(bundle) {
46900
46900
  const extra = objectValue$2(session.extra);
46901
46901
  const groupTarget = arkmeGroupBotBindingTargetFromBundle(bundle) ?? arkmeGroupBotBindingTargetFromBundle({ rm_subject_id: extra.subject_id ?? extra.legacy_subject_id });
46902
46902
  const profiles = /* @__PURE__ */ new Map();
46903
- for (const value of list$3(bundle.bot_participants)) {
46903
+ for (const value of list$2(bundle.bot_participants)) {
46904
46904
  const participant = objectValue$2(value);
46905
46905
  const uid = text$3(participant.bot_uid);
46906
46906
  if (uid === "" || text$3(participant.chat_session_uid) !== chatSessionUid) continue;
@@ -46946,7 +46946,7 @@ var RuntimeBotDisplayProfilesReader = class {
46946
46946
  let snapshot = input.snapshot;
46947
46947
  if (snapshot === void 0) try {
46948
46948
  const data = await this.runtime.authenticatedChatPost("/api/v1/chats/display-snapshots", { chat_session_uids: [input.chatSessionUid] }, session, identitySignal, { refreshOnUnauthorized: false });
46949
- snapshot = list$3(data.items).map((value) => botDisplaySnapshot(objectValue$2(value))).find((item) => item.chatSessionUid === input.chatSessionUid);
46949
+ snapshot = list$2(data.items).map((value) => botDisplaySnapshot(objectValue$2(value))).find((item) => item.chatSessionUid === input.chatSessionUid);
46950
46950
  } catch (error) {
46951
46951
  optionalFailure(error);
46952
46952
  }
@@ -46977,7 +46977,7 @@ var RuntimeBotDisplayProfilesReader = class {
46977
46977
  cancelWhenUnobserved: true
46978
46978
  });
46979
46979
  const profiles = /* @__PURE__ */ new Map();
46980
- for (const value of list$3(data.bots)) {
46980
+ for (const value of list$2(data.bots)) {
46981
46981
  const raw = objectValue$2(value);
46982
46982
  if (raw.installed === true && text$3(raw.bot_id) !== "" && text$3(raw.name) !== "") profiles.set(text$3(raw.bot_id), {
46983
46983
  displayName: text$3(raw.name),
@@ -52006,13 +52006,22 @@ var ChatService = class {
52006
52006
  for (const child of listValue$16(data.children)) {
52007
52007
  const extension = chatExtensionTreeItemFromData(child, parentRecordUid);
52008
52008
  if (extension === void 0) continue;
52009
- const record = objectValue$2(objectValue$2(objectValue$2(child).item).record);
52009
+ const childItem = objectValue$2(objectValue$2(child).item);
52010
+ const relation = objectValue$2(childItem.relation);
52011
+ const record = objectValue$2(childItem.record);
52010
52012
  const payload = objectValue$2(record.payload);
52011
52013
  const mentions = await this.timelineMentionTargets(record, payload, session.userId, chatSessionUid);
52014
+ const senderUserId = integerLikeValue(relation.sender_user_id ?? relation.senderUserId);
52015
+ const humanSender = senderUserId > 0 && !timelineSenderIsBot(relation);
52016
+ const senderMemberRef = humanSender ? await this.sealChatMemberRef(session.userId, chatSessionUid, senderUserId) : void 0;
52017
+ const currentAvatarRef = humanSender ? await this.profile.sealProfileImageRef(session.userId, senderUserId).catch(() => void 0) : void 0;
52012
52018
  projections.push({
52013
- extension: mentions === void 0 ? extension : {
52019
+ extension: {
52014
52020
  ...extension,
52015
- mentions
52021
+ ...mentions === void 0 ? {} : { mentions },
52022
+ ...senderMemberRef === void 0 ? {} : { senderMemberRef },
52023
+ ...humanSender ? { senderIsMe: senderUserId === session.userId } : {},
52024
+ ...currentAvatarRef === void 0 ? {} : { senderAvatarUrl: currentAvatarRef }
52016
52025
  },
52017
52026
  mediaRecord: chatExtensionMediaRecord(child, extension.recordUid)
52018
52027
  });
@@ -86249,7 +86258,7 @@ function sharePreviewIsOwnMessage(raw, viewerUserId) {
86249
86258
  const matching = typeof uid === "string" && uid ? anchors.filter((anchor) => anchor.record_uid === uid) : anchors.length === 1 ? anchors : [];
86250
86259
  return matching.length === 1 && String(matching[0].record_owner_user_id) === String(viewerUserId);
86251
86260
  }
86252
- const list$2 = (value) => Array.isArray(value) ? value.slice(0, 150).map(previewObject) : [];
86261
+ const list$1 = (value) => Array.isArray(value) ? value.slice(0, 150).map(previewObject) : [];
86253
86262
  function previewText(value, max = 200) {
86254
86263
  if (typeof value !== "string") return "";
86255
86264
  const plain = value.slice(0, 12e3).replace(/[\u0000-\u001f\u007f\s]+/gu, " ").trim();
@@ -86280,9 +86289,9 @@ function state(data) {
86280
86289
  function sourceSummary(item) {
86281
86290
  if (typeof item.availability === "string" && item.availability !== "available") return "";
86282
86291
  const structure = previewObject(item.structured_content);
86283
- const segments = list$2(structure.long_recording_segments);
86292
+ const segments = list$1(structure.long_recording_segments);
86284
86293
  const body = item.text_content ?? item.text ?? item.content;
86285
- return previewText(item.text_format === "markdown" && typeof body === "string" ? arkmeMarkdownPlainText(body.slice(0, 12e3)) : body) || segments.slice(0, 3).map((s) => [previewText(s.speaker_label, 30), previewText(s.text, 100)].filter(Boolean).join(":")).join(" · ") || list$2(item.media_items ?? item.files).map((m) => previewText(m.file_name, 80)).filter(Boolean).slice(0, 2).join(" · ");
86294
+ return previewText(item.text_format === "markdown" && typeof body === "string" ? arkmeMarkdownPlainText(body.slice(0, 12e3)) : body) || segments.slice(0, 3).map((s) => [previewText(s.speaker_label, 30), previewText(s.text, 100)].filter(Boolean).join(":")).join(" · ") || list$1(item.media_items ?? item.files).map((m) => previewText(m.file_name, 80)).filter(Boolean).slice(0, 2).join(" · ");
86286
86295
  }
86287
86296
  /** Pure projection: no private lookup, recursive resolution, AI, or media download. */
86288
86297
  function projectSharePreview(target, raw) {
@@ -86305,8 +86314,8 @@ function projectSharePreview(target, raw) {
86305
86314
  });
86306
86315
  if (target.kind === "message" || target.kind === "conversation") {
86307
86316
  const forward = previewObject(data.forward_records);
86308
- const items = list$2(target.kind === "conversation" ? forward.items : data.items);
86309
- const bundle = list$2(data.presentation).find((n) => n.kind === "forward_bundle");
86317
+ const items = list$1(target.kind === "conversation" ? forward.items : data.items);
86318
+ const bundle = list$1(data.presentation).find((n) => n.kind === "forward_bundle");
86310
86319
  if (target.kind === "conversation" || items.length > 1 || bundle) {
86311
86320
  const lines = items.filter((item) => !item.availability || item.availability === "available").slice(0, 3).map((item) => [previewText(item.sender_display_name ?? item.owner_name, 40), sourceSummary(item) || previewText(item.title)].filter(Boolean).join(":")).filter(Boolean);
86312
86321
  const fallbackLines = Array.isArray(forward.summary_lines) ? forward.summary_lines.slice(0, 3).map((v) => previewText(v, 100)) : [];
@@ -86323,7 +86332,7 @@ function projectSharePreview(target, raw) {
86323
86332
  state: "unavailable"
86324
86333
  };
86325
86334
  const structure = previewObject(item.structured_content);
86326
- const media = list$2(item.media_items);
86335
+ const media = list$1(item.media_items);
86327
86336
  const first = media[0];
86328
86337
  const kind = item.display_kind === 1 || item.template_kind === 8 ? "article" : structure.structured_kind === 1 || Array.isArray(structure.long_recording_segments) ? "recording" : [
86329
86338
  "agent_message",
@@ -86345,7 +86354,7 @@ function projectSharePreview(target, raw) {
86345
86354
  };
86346
86355
  }
86347
86356
  if (target.kind === "world") {
86348
- const user = list$2(data.items)[0] ?? {};
86357
+ const user = list$1(data.items)[0] ?? {};
86349
86358
  return Object.keys(user).length ? assign(user.nick_name, void 0, user.head_img, user.jotmo_id) : {
86350
86359
  ...base,
86351
86360
  state: "unavailable"
@@ -90630,123 +90639,6 @@ function recordingSearchIdentityHash(item) {
90630
90639
  ]), "utf8").digest("hex");
90631
90640
  }
90632
90641
  //#endregion
90633
- //#region src/recording-speaker-presence.ts
90634
- const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
90635
- const list$1 = (value) => Array.isArray(value) ? value : [];
90636
- const string = (value) => typeof value === "string" ? value.trim() : "";
90637
- const number$1 = (value) => typeof value === "number" && Number.isFinite(value) ? value : NaN;
90638
- /**
90639
- * Count positive-duration ASR facts, including a segment whose text is empty.
90640
- * The day endpoint already scopes visible recordings; user_id additionally
90641
- * prevents a shared or unrelated session from entering this owner's estimate.
90642
- */
90643
- function projectRecentSpeakerDetails(speakers, days, ownerUserId) {
90644
- const personBySpeaker = /* @__PURE__ */ new Map();
90645
- for (const raw of list$1(speakers)) {
90646
- const speaker = record(raw);
90647
- const id = string(speaker.speaker_id ?? speaker.id ?? speaker.spk_id);
90648
- if (id === "") continue;
90649
- const userId = number$1(speaker.ref_usr_id ?? speaker.ref_user_id ?? speaker.user_id);
90650
- personBySpeaker.set(id, Number.isSafeInteger(userId) && userId > 0 ? `user:${userId}` : `speaker:${id}`);
90651
- }
90652
- const presenceByPerson = /* @__PURE__ */ new Map();
90653
- for (const day of days) {
90654
- const dayEnd = new Date(day.dateStamp);
90655
- dayEnd.setDate(dayEnd.getDate() + 1);
90656
- const sessions = /* @__PURE__ */ new Map();
90657
- const response = record(day.response);
90658
- for (const raw of list$1(response.session_ls ?? response.sessions)) {
90659
- const session = record(raw);
90660
- const sessionId = string(session.id ?? session.session_id);
90661
- if (sessionId === "" || number$1(session.user_id) !== ownerUserId) continue;
90662
- const speakerByNumber = /* @__PURE__ */ new Map();
90663
- for (const rawSpeaker of list$1(session.spk_ls ?? session.speakers)) {
90664
- const speaker = record(rawSpeaker);
90665
- const speakerNumber = number$1(speaker.num ?? speaker.speaker_num);
90666
- if (!Number.isSafeInteger(speakerNumber)) continue;
90667
- const displayNumber = number$1(speaker.speaker_display_number);
90668
- const innerId = string(speaker.inner_spk_id);
90669
- const dayId = string(speaker.day_spk_id);
90670
- const legacyToken = string(speaker.inner_display ?? speaker.label);
90671
- const identityKey = Number.isSafeInteger(displayNumber) && displayNumber > 0 ? `number:${displayNumber}` : innerId !== "" ? `inner:${innerId}` : dayId !== "" ? `day:${dayId}` : "";
90672
- const token = Number.isSafeInteger(displayNumber) && displayNumber > 0 ? String(displayNumber) : legacyToken;
90673
- speakerByNumber.set(speakerNumber, {
90674
- speakerId: string(speaker.spk_id ?? speaker.speaker_id),
90675
- identityKey: identityKey !== "" && token !== "" ? identityKey : "",
90676
- token
90677
- });
90678
- }
90679
- sessions.set(sessionId, {
90680
- startAt: number$1(session.start_at),
90681
- speakerByNumber
90682
- });
90683
- }
90684
- for (const raw of list$1(response.child_ls ?? response.children)) {
90685
- const child = record(raw);
90686
- const session = sessions.get(string(child.session_id));
90687
- if (session === void 0 || !Number.isFinite(session.startAt)) continue;
90688
- const offset = number$1(child.start_at);
90689
- if (!Number.isFinite(offset)) continue;
90690
- const childStart = offset >= 1e11 ? offset : session.startAt + offset;
90691
- for (const rawItem of list$1(child.asr)) {
90692
- const item = record(rawItem);
90693
- const startOffset = number$1(item.s ?? item.start_at);
90694
- const endOffset = number$1(item.e ?? item.end_at);
90695
- const seenAt = childStart + startOffset;
90696
- const seenEndAt = childStart + endOffset;
90697
- if (!Number.isFinite(seenAt) || !Number.isFinite(seenEndAt) || seenEndAt <= seenAt || seenAt < day.dateStamp || seenAt >= dayEnd.getTime()) continue;
90698
- if (string(item.speaker_identity_source) === "system") continue;
90699
- const recognized = session.speakerByNumber.get(number$1(item.n ?? item.speaker_num));
90700
- const sessionSpeakerId = recognized?.speakerId ?? "";
90701
- const effectiveId = string(item.effective_spk_id);
90702
- const directId = string(item.q);
90703
- const explicitlyUnassigned = directId === "" && sessionSpeakerId !== "" && string(item.q_unassigned_spk_id) === sessionSpeakerId;
90704
- const speakerId = effectiveId || (explicitlyUnassigned ? "" : directId || sessionSpeakerId);
90705
- const personKey = personBySpeaker.get(speakerId);
90706
- if (personKey === void 0) continue;
90707
- let presence = presenceByPerson.get(personKey);
90708
- if (presence === void 0) {
90709
- presence = {
90710
- days: /* @__PURE__ */ new Set(),
90711
- lastSeenAt: 0,
90712
- members: /* @__PURE__ */ new Map()
90713
- };
90714
- presenceByPerson.set(personKey, presence);
90715
- }
90716
- presence.days.add(day.dateStamp);
90717
- presence.lastSeenAt = Math.max(presence.lastSeenAt, seenEndAt);
90718
- if (recognized?.identityKey) {
90719
- let member = presence.members.get(recognized.identityKey);
90720
- if (member === void 0) {
90721
- member = {
90722
- token: recognized.token,
90723
- days: /* @__PURE__ */ new Set(),
90724
- lastSeenAt: 0
90725
- };
90726
- presence.members.set(recognized.identityKey, member);
90727
- }
90728
- member.days.add(day.dateStamp);
90729
- member.lastSeenAt = Math.max(member.lastSeenAt, seenEndAt);
90730
- }
90731
- }
90732
- }
90733
- }
90734
- return [...personBySpeaker].flatMap(([speakerId, personKey]) => {
90735
- const presence = presenceByPerson.get(personKey);
90736
- return presence === void 0 ? [] : [{
90737
- speakerId,
90738
- dayCount: presence.days.size,
90739
- lastSeenAt: presence.lastSeenAt,
90740
- members: [...presence.members].map(([identityKey, member]) => ({
90741
- identityKey,
90742
- token: member.token,
90743
- dayCount: member.days.size,
90744
- lastSeenAt: member.lastSeenAt
90745
- })).sort((left, right) => right.lastSeenAt - left.lastSeenAt || left.token.localeCompare(right.token))
90746
- }];
90747
- });
90748
- }
90749
- //#endregion
90750
90642
  //#region src/recording-import-coordinator.ts
90751
90643
  function importFailure(error) {
90752
90644
  if (error instanceof RecordingImportContractError) return {
@@ -91665,7 +91557,6 @@ var RecordingService = class {
91665
91557
  recordingImportSource;
91666
91558
  recordingImportOwnerGateway;
91667
91559
  speakerCacheRevision = 0;
91668
- recentSpeakerDetailsCache;
91669
91560
  unsubscribeSpeakerAccount;
91670
91561
  forwardGateway;
91671
91562
  constructor(runtime, dependencies) {
@@ -91837,22 +91728,17 @@ var RecordingService = class {
91837
91728
  this.assertWorkbenchEnabled();
91838
91729
  const session = await this.runtime.requireSession();
91839
91730
  const revision = this.speakerCacheRevision;
91840
- let data;
91841
- try {
91842
- data = await this.runtime.authenticatedAudioPost("/api/v1/audio/speaker-presence/list", {}, session, signal);
91843
- } catch (error) {
91844
- if (!(error instanceof ArkmePluginError) || error.upstreamStatus !== 404 && error.httpStatus !== 404 && error.code !== "arkme-code-404") throw error;
91845
- return await this.recordingSpeakerPresenceRecent(session, revision, signal);
91846
- }
91731
+ const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/speaker-presence/list", {}, session, signal);
91847
91732
  const rawState = stringValue$2(data.state);
91848
91733
  if (rawState !== "building" && rawState !== "fresh" && rawState !== "stale" && rawState !== "failed") throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91734
+ if (!Array.isArray(data.items) || rawState === "fresh" && stringValue$2(data.version) === "") throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91849
91735
  const speakerRefKey = await this.recordingRefKey("arkme-recording-speaker-v1");
91850
91736
  const items = listValue$5(data.items).flatMap((raw) => {
91851
91737
  const item = objectValue$2(raw);
91852
91738
  const speakerId = stringValue$2(item.speaker_id).trim();
91853
91739
  const dayCount = numberValue$5(item.day_count);
91854
91740
  const lastSeenAt = numberValue$5(item.last_seen_at);
91855
- if (speakerId === "" || !Number.isSafeInteger(dayCount) || dayCount <= 0 || !Number.isSafeInteger(lastSeenAt) || lastSeenAt <= 0) return [];
91741
+ if (speakerId === "" || !Number.isSafeInteger(dayCount) || dayCount <= 0 || !Number.isSafeInteger(lastSeenAt) || lastSeenAt <= 0) throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91856
91742
  return [{
91857
91743
  optionKey: this.recordingSpeakerOptionKey(speakerRefKey, session.userId, "speaker", speakerId),
91858
91744
  dayCount,
@@ -91863,115 +91749,58 @@ var RecordingService = class {
91863
91749
  return {
91864
91750
  state: rawState,
91865
91751
  scope: "all-history",
91866
- items,
91752
+ items: rawState === "fresh" ? items : [],
91753
+ ...stringValue$2(data.version) === "" ? {} : { version: stringValue$2(data.version) },
91867
91754
  ...numberValue$5(data.updated_at) > 0 ? { updatedAt: numberValue$5(data.updated_at) } : {},
91868
91755
  ...numberValue$5(data.retry_after_ms) > 0 ? { retryAfterMs: numberValue$5(data.retry_after_ms) } : {}
91869
91756
  };
91870
91757
  }
91871
- async recordingSpeakerPresenceRecent(session, revision, signal) {
91872
- const snapshot = await this.loadRecentSpeakerDetails(session, revision, signal);
91873
- const speakerRefKey = await this.recordingRefKey("arkme-recording-speaker-v1");
91874
- await this.assertSpeakerReadCurrent(session, revision, signal);
91875
- return {
91876
- state: "fresh",
91877
- scope: "recent-seven-days",
91878
- items: snapshot.details.map((stat) => ({
91879
- optionKey: this.recordingSpeakerOptionKey(speakerRefKey, session.userId, "speaker", stat.speakerId),
91880
- dayCount: stat.dayCount,
91881
- lastSeenAt: stat.lastSeenAt
91882
- })),
91883
- updatedAt: snapshot.updatedAt
91884
- };
91885
- }
91886
- /** Read a selected marked person's verifiable source voices, not a guessed all-history mapping. */
91887
- async recordingSpeakerMembers(speakerRef, signal) {
91758
+ /** All-history source identities, authorized by the same account-bound speaker reference as the UI. */
91759
+ async recordingSpeakerMembers(speakerRef, signal, expectedVersion) {
91888
91760
  this.assertWorkbenchEnabled();
91889
91761
  const session = await this.runtime.requireSession();
91890
91762
  const revision = this.speakerCacheRevision;
91891
91763
  const reference = await this.openRecordingSpeakerRef(speakerRef);
91892
91764
  if (reference.viewerUserId !== session.userId || reference.target.kind !== "speaker") throw new ArkmePluginError("recording-speaker-ref-account-mismatch", "说话人引用与当前账号不匹配", false, 403);
91765
+ const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/speaker-presence/detail", {
91766
+ speaker_id: reference.target.speakerId,
91767
+ ...expectedVersion === void 0 ? {} : { expected_version: expectedVersion }
91768
+ }, session, signal);
91769
+ const state = stringValue$2(data.state);
91770
+ if (![
91771
+ "building",
91772
+ "fresh",
91773
+ "stale",
91774
+ "failed"
91775
+ ].includes(state)) throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91776
+ if (state === "fresh" && (stringValue$2(data.version) === "" || !Array.isArray(data.items) || data.members !== void 0 && !Array.isArray(data.members))) throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91777
+ const key = await this.recordingRefKey("arkme-recording-speaker-v1");
91893
91778
  const targetSpeakerId = reference.target.speakerId;
91894
- const snapshot = await this.loadRecentSpeakerDetails(session, revision, signal);
91895
- if (!snapshot.speakerIds.has(targetSpeakerId)) throw new ArkmePluginError("recording-speaker-target-missing", "该说话人已不存在,请重新选择", false, 409);
91896
- const detail = snapshot.details.find((item) => item.speakerId === targetSpeakerId);
91897
- await this.assertSpeakerReadCurrent(session, revision, signal);
91898
- return {
91899
- scope: "recent-seven-days",
91900
- dayCount: detail?.dayCount ?? 0,
91901
- lastSeenAt: detail?.lastSeenAt ?? 0,
91902
- items: detail?.members.map(({ token, dayCount, lastSeenAt }) => ({
91779
+ const stat = listValue$5(data.items).map(objectValue$2).find((item) => item.speaker_id === targetSpeakerId);
91780
+ if (state === "fresh" && stat !== void 0 && (!Number.isSafeInteger(stat.day_count) || numberValue$5(stat.day_count) <= 0 || !Number.isSafeInteger(stat.last_seen_at) || numberValue$5(stat.last_seen_at) <= 0)) throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91781
+ const items = state !== "fresh" ? [] : listValue$5(data.members).map((raw) => {
91782
+ const item = objectValue$2(raw);
91783
+ const identity = stringValue$2(item.identity_key);
91784
+ const token = stringValue$2(item.token);
91785
+ const dayCount = numberValue$5(item.day_count);
91786
+ const lastSeenAt = numberValue$5(item.last_seen_at);
91787
+ if (stat === void 0 || dayCount > numberValue$5(stat.day_count) || lastSeenAt > numberValue$5(stat.last_seen_at) || identity === "" || token === "" || !Number.isSafeInteger(dayCount) || dayCount <= 0 || !Number.isSafeInteger(lastSeenAt) || lastSeenAt <= 0) throw new ArkmePluginError("recording-speaker-members-invalid", "关联说话人响应无效", true, 502);
91788
+ return {
91789
+ identityKey: this.recordingSpeakerOptionKey(key, session.userId, "speaker", `presence:${identity}`),
91903
91790
  token,
91904
91791
  dayCount,
91905
91792
  lastSeenAt
91906
- })) ?? []
91907
- };
91908
- }
91909
- async loadRecentSpeakerDetails(session, revision, signal) {
91910
- const today = /* @__PURE__ */ new Date();
91911
- today.setHours(0, 0, 0, 0);
91912
- const firstDay = new Date(today);
91913
- firstDay.setDate(firstDay.getDate() - 6);
91914
- const cached = this.recentSpeakerDetailsCache;
91915
- if (cached?.ownerUserId === session.userId && cached.revision === revision && cached.firstDay === firstDay.getTime() && Date.now() - cached.updatedAt < 15e3) {
91916
- await this.assertSpeakerReadCurrent(session, revision, signal);
91917
- return {
91918
- speakerIds: cached.speakerIds,
91919
- details: cached.details,
91920
- updatedAt: cached.updatedAt
91921
91793
  };
91922
- }
91923
- const afterLastDay = new Date(today);
91924
- afterLastDay.setDate(afterLastDay.getDate() + 1);
91925
- const [speakerData, calendar] = await Promise.all([this.runtime.authenticatedAudioPost("/api/v1/audio/get-speaker-ls", {}, session, signal), this.runtime.authenticatedAudioPost("/api/v1/audio/get-calender-summary", {
91926
- from_stamp: firstDay.getTime(),
91927
- to_stamp: afterLastDay.getTime()
91928
- }, session, signal)]);
91929
- const durations = calendar.duration_ls;
91930
- const unreviewed = calendar.un_click_session_ids_per_day;
91931
- if (!Array.isArray(speakerData.spk_ls) || !Array.isArray(durations) || durations.length < 7 || !Array.isArray(unreviewed) || unreviewed.length < 7) throw new ArkmePluginError("recording-speaker-recent-invalid", "近 7 天录音统计响应无效", true, 502);
91932
- const days = [];
91933
- const cursor = new Date(firstDay);
91934
- for (let index = 0; index < 7; index += 1) {
91935
- if (numberValue$5(durations[index]) > 0 || listValue$5(unreviewed[index]).length > 0) days.push(cursor.getTime());
91936
- cursor.setDate(cursor.getDate() + 1);
91937
- }
91938
- const transcripts = [];
91939
- let nextDay = 0;
91940
- const readDay = async () => {
91941
- while (nextDay < days.length) {
91942
- const dateStamp = days[nextDay++];
91943
- signal?.throwIfAborted();
91944
- const day = new Date(dateStamp);
91945
- const response = await this.runtime.authenticatedAudioPost("/api/v1/audio/one-day-trans", {
91946
- start_at: dateStamp,
91947
- tz_offset: -day.getTimezoneOffset() * 6e4
91948
- }, session, signal);
91949
- transcripts.push({
91950
- dateStamp,
91951
- response
91952
- });
91953
- }
91954
- };
91955
- await Promise.all(Array.from({ length: Math.min(2, days.length) }, readDay));
91956
- const details = projectRecentSpeakerDetails(speakerData.spk_ls, transcripts, session.userId);
91957
- const speakerIds = new Set(listValue$5(speakerData.spk_ls).map((raw) => {
91958
- const speaker = objectValue$2(raw);
91959
- return stringValue$2(speaker.speaker_id ?? speaker.id ?? speaker.spk_id).trim();
91960
- }).filter(Boolean));
91794
+ });
91961
91795
  await this.assertSpeakerReadCurrent(session, revision, signal);
91962
- const updatedAt = Date.now();
91963
- this.recentSpeakerDetailsCache = {
91964
- ownerUserId: session.userId,
91965
- revision,
91966
- firstDay: firstDay.getTime(),
91967
- updatedAt,
91968
- speakerIds,
91969
- details
91970
- };
91971
91796
  return {
91972
- speakerIds,
91973
- details,
91974
- updatedAt
91797
+ state,
91798
+ scope: "all-history",
91799
+ dayCount: state === "fresh" ? numberValue$5(stat?.day_count) : 0,
91800
+ lastSeenAt: state === "fresh" ? numberValue$5(stat?.last_seen_at) : 0,
91801
+ items,
91802
+ ...stringValue$2(data.version) === "" ? {} : { version: stringValue$2(data.version) },
91803
+ ...numberValue$5(data.retry_after_ms) > 0 ? { retryAfterMs: numberValue$5(data.retry_after_ms) } : {}
91975
91804
  };
91976
91805
  }
91977
91806
  async recordingSpeakerRecommendation(itemRef, signal) {
@@ -95517,6 +95346,7 @@ var UnmarkedSpeakerService = class {
95517
95346
  const projectionState = data.projection_state === void 0 ? void 0 : knownStringValue(data.projection_state, PROJECTION_STATES);
95518
95347
  if (data.projection_state !== void 0 && projectionState === void 0) throw new ArkmePluginError("unmarked-list-contract-invalid", "未标记说话人列表投影状态无效", true, 502);
95519
95348
  const items = [];
95349
+ const identitySecret = await this.runtime.stateStore.uniqueCode();
95520
95350
  for (const value of listValue$2(data.items).slice(0, LIST_CAP)) {
95521
95351
  const raw = objectValue$2(value);
95522
95352
  const candidateId = stringValue$2(raw.candidate_id).trim();
@@ -95544,6 +95374,14 @@ var UnmarkedSpeakerService = class {
95544
95374
  kind: "unmarked-speaker",
95545
95375
  candidateRef,
95546
95376
  speakerToken,
95377
+ appearanceDays,
95378
+ latestAtMillis,
95379
+ identityKey: createHmac("sha256", identitySecret).update(JSON.stringify([
95380
+ "unmarked-speaker-identity-v1",
95381
+ this.runtime.config.environment,
95382
+ session.userId,
95383
+ candidateId
95384
+ ])).digest("base64url"),
95547
95385
  displayName: unmarkedSpeakerDisplayName({
95548
95386
  speakerToken,
95549
95387
  firstSeenDate: localDate(status === "single_day" ? latestAtMillis : firstSeenAtMillis),
@@ -98730,6 +98568,7 @@ var ArkmeService = class {
98730
98568
  ...this.runtime.stateStore.commonGroups ? { commonGroups: true } : {},
98731
98569
  remoteRecordSearch: true,
98732
98570
  contactDirectoryReads: true,
98571
+ speakerPresence: true,
98733
98572
  sourceTimeline: true,
98734
98573
  forwardContent: true,
98735
98574
  sourceTextSend: true,
@@ -98994,8 +98833,8 @@ var ArkmeService = class {
98994
98833
  async recordingSpeakerPresence(signal) {
98995
98834
  return await this.recording.recordingSpeakerPresence(signal);
98996
98835
  }
98997
- async recordingSpeakerMembers(speakerRef, signal) {
98998
- return await this.recording.recordingSpeakerMembers(speakerRef, signal);
98836
+ async recordingSpeakerMembers(speakerRef, signal, expectedVersion) {
98837
+ return await this.recording.recordingSpeakerMembers(speakerRef, signal, expectedVersion);
98999
98838
  }
99000
98839
  async recordingSpeakerRecommendation(itemRef, signal) {
99001
98840
  return await this.recording.recordingSpeakerRecommendation(itemRef, signal);
package/lib/sdk.js CHANGED
@@ -123,6 +123,26 @@ var ArkmeSdk = class {
123
123
  this.route = route;
124
124
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
125
125
  }
126
+ /** Account-scoped marked speaker candidates; refs can be used only with this provider/account. */
127
+ async recordingSpeakerCandidates(signal) {
128
+ await this.requireSpeakerPresence(signal);
129
+ return await this.call("recordings.speaker.options", {}, signal);
130
+ }
131
+ async recordingSpeakerPresence(signal) {
132
+ await this.requireSpeakerPresence(signal);
133
+ return await this.call("recordings.speaker.presence", {}, signal);
134
+ }
135
+ async recordingSpeakerMembers(speakerRef, options = {}) {
136
+ if (speakerRef.trim() === "") throw new TypeError("speakerRef is required");
137
+ await this.requireSpeakerPresence(options.signal);
138
+ return await this.call("recordings.speaker.members", {
139
+ speakerRef,
140
+ ...options.expectedVersion === void 0 ? {} : { expectedVersion: options.expectedVersion }
141
+ }, options.signal);
142
+ }
143
+ async requireSpeakerPresence(signal) {
144
+ if ((await this.capabilities(signal)).features.speakerPresence !== true) throw new Error("当前 Provider 不支持全历史说话人统计");
145
+ }
126
146
  async capabilities(signal) {
127
147
  const capabilities = await this.call("provider.capabilities", void 0, signal);
128
148
  if (capabilities.contractVersion !== 1) throw new Error(`Unsupported Arkme provider contract version ${String(capabilities.contractVersion)}`);
@@ -333,7 +333,7 @@ export declare class ArkmeService {
333
333
  cachedRecordingSpeakerOptions(signal?: AbortSignal): Promise<ArkmeRecordingSpeakerCandidate[] | null>;
334
334
  recordingSpeakerOptions(signal?: AbortSignal): Promise<ArkmeRecordingSpeakerCandidate[]>;
335
335
  recordingSpeakerPresence(signal?: AbortSignal): Promise<ArkmeRecordingSpeakerPresence>;
336
- recordingSpeakerMembers(speakerRef: string, signal?: AbortSignal): Promise<import("./types.js").ArkmeRecordingSpeakerMembers>;
336
+ recordingSpeakerMembers(speakerRef: string, signal?: AbortSignal, expectedVersion?: string): Promise<import("./types.js").ArkmeRecordingSpeakerMembers>;
337
337
  recordingSpeakerRecommendation(itemRef: string, signal?: AbortSignal): Promise<ArkmeRecordingSpeakerRecommendation>;
338
338
  assignRecordingSpeaker(input: {
339
339
  itemRef: string;