@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
@@ -9,6 +9,12 @@ window.__ModuleLoader__.load({
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  Object.fromEntries(`
11
11
  个人世界|Personal world
12
+ 说话人排序|Sort speakers
13
+ 经常出现|Most frequent
14
+ 最近出现|Most recent
15
+ 正在补齐说话人列表,排序仍在更新…|Loading the remaining speakers. Order is still updating…
16
+ 列表尚未完整,当前仅对已加载的说话人排序和搜索。|The list is incomplete. Sorting and search cover loaded speakers only.
17
+ 显示更多说话人|Show more speakers
12
18
  语音通话邀请|Voice call invitation
13
19
  视频通话邀请|Video call invitation
14
20
  公开快记|Public note
@@ -588,6 +594,7 @@ AI智能体|AI agent
588
594
  群聊名称|Group name
589
595
  最新|Latest
590
596
  部分历史附件暂不可用|Some earlier attachments are unavailable
597
+ 附件暂不可用|Attachment unavailable
591
598
  正在加载编辑记录…|Loading edit history…
592
599
  暂无编辑记录|No edit history
593
600
  重新加载历史附件|Reload earlier attachments
@@ -1631,6 +1638,14 @@ Bot 已移出群聊|Bot removed from the group
1631
1638
  {v0}人|{v0} people
1632
1639
  正在读取群 Bot…|Loading group Bots…
1633
1640
  Bot 列表加载失败,点击重试|Could not load Bots. Click to retry
1641
+ 新识别 {v0} 个|{v0} newly identified
1642
+ 识别我的声音|Identify my voice
1643
+ 你的声纹正在处理中|Your voiceprint is being processed
1644
+ 已录入声纹,暂未在录音中匹配到你|Voiceprint enrolled. No match found in your recordings yet.
1645
+ 还没有标记你的声音|Your voice has not been labeled yet
1646
+ 打开你的录音,点击说话人名称并选择自己;请先试听确认。|Open your recording, listen to confirm, then click the speaker name and select yourself.
1647
+ 从录音中标记我|Label myself in a recording
1648
+ 管理声纹|Manage voiceprint
1634
1649
  `.trim().split("\n").map((line) => {
1635
1650
  const index = line.indexOf("|");
1636
1651
  return [line.slice(0, index), line.slice(index + 1)];
@@ -2224,6 +2239,26 @@ AI 额度充值流程|AI point purchase
2224
2239
  this.route = route;
2225
2240
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
2226
2241
  }
2242
+ /** Account-scoped marked speaker candidates; refs can be used only with this provider/account. */
2243
+ async recordingSpeakerCandidates(signal) {
2244
+ await this.requireSpeakerPresence(signal);
2245
+ return await this.call("recordings.speaker.options", {}, signal);
2246
+ }
2247
+ async recordingSpeakerPresence(signal) {
2248
+ await this.requireSpeakerPresence(signal);
2249
+ return await this.call("recordings.speaker.presence", {}, signal);
2250
+ }
2251
+ async recordingSpeakerMembers(speakerRef, options = {}) {
2252
+ if (speakerRef.trim() === "") throw new TypeError("speakerRef is required");
2253
+ await this.requireSpeakerPresence(options.signal);
2254
+ return await this.call("recordings.speaker.members", {
2255
+ speakerRef,
2256
+ ...options.expectedVersion === void 0 ? {} : { expectedVersion: options.expectedVersion }
2257
+ }, options.signal);
2258
+ }
2259
+ async requireSpeakerPresence(signal) {
2260
+ if ((await this.capabilities(signal)).features.speakerPresence !== true) throw new Error("当前 Provider 不支持全历史说话人统计");
2261
+ }
2227
2262
  async capabilities(signal) {
2228
2263
  const capabilities = await this.call("provider.capabilities", void 0, signal);
2229
2264
  if (capabilities.contractVersion !== 1) throw new Error(`Unsupported Arkme provider contract version ${String(capabilities.contractVersion)}`);
@@ -7081,8 +7116,19 @@ AI 额度充值流程|AI point purchase
7081
7116
  ] });
7082
7117
  }
7083
7118
  //#endregion
7119
+ //#region src/client/ArkmeModelPricing.tsx
7120
+ /** One disclosure per menu keeps pricing available without repeating it in every row. */
7121
+ function ArkmeModelPricing({ models }) {
7122
+ const pricedModels = models.filter((model) => model.description);
7123
+ if (pricedModels.length === 0) return null;
7124
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
7125
+ className: "arkme-model-price",
7126
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: tr("按用量扣积分 · 计费说明") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", { children: pricedModels.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: model.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: model.description })] }, model.id)) })]
7127
+ });
7128
+ }
7129
+ //#endregion
7084
7130
  //#region src/client/arkme-model-select.css?inline
7085
- var arkme_model_select_default = ".arkme-model-select {\n min-width: 0;\n font-family: inherit;\n position: relative;\n}\n\n.arkme-model-select button {\n font: inherit;\n cursor: pointer;\n}\n\n.arkme-model-select button:disabled {\n opacity: .5;\n cursor: default;\n}\n\n.arkme-model-select button:focus-visible {\n outline-offset: -2px;\n outline: 2px solid #3578e5;\n}\n\n.arkme-model-trigger {\n max-width: min(360px, 45vw);\n height: 28px;\n color: var(--dsw-alias-label-secondary, #626772);\n background: none;\n border: 0;\n border-radius: 14px;\n align-items: center;\n gap: 5px;\n padding: 0 8px;\n display: flex;\n font-size: 13px !important;\n}\n\n.arkme-model-trigger > span:first-child {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.arkme-model-trigger small {\n flex-shrink: 0;\n}\n\n.arkme-model-menu {\n z-index: 30;\n box-sizing: border-box;\n border: 1px solid var(--dsw-alias-border-inverted, #e8e8eb);\n background: var(--dsw-specific-menu, #fff);\n width: max-content;\n min-width: min(280px, 100vw - 32px);\n max-width: min(420px, 100vw - 32px);\n max-height: min(400px, 100vh - 96px);\n color: var(--dsw-alias-label-primary, #20232c);\n border-radius: 12px;\n padding: 5px;\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n overflow-y: auto;\n box-shadow: 0 8px 30px #0002;\n}\n\n.arkme-model-group-heading {\n color: var(--dsw-alias-label-tertiary, #8a8e96);\n justify-content: space-between;\n align-items: center;\n gap: 18px;\n padding: 7px 9px 4px;\n font-size: 12px;\n display: flex;\n}\n\n.arkme-model-balance-actions {\n flex-shrink: 0;\n gap: 8px;\n display: flex;\n}\n\n.arkme-model-balance-actions button {\n color: #3478e5;\n white-space: nowrap;\n background: none;\n border: 0;\n padding: 2px 0;\n}\n\n.arkme-model-balance-actions button:hover {\n text-decoration: underline;\n}\n\n.arkme-model-option {\n width: 100%;\n min-height: 38px;\n color: inherit;\n text-align: left;\n background: none;\n border: 0;\n border-radius: 8px;\n justify-content: space-between;\n align-items: center;\n gap: 18px;\n padding: 8px 10px;\n display: flex;\n font-size: 14px !important;\n}\n\n.arkme-model-option:hover, .arkme-model-trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover, #f3f4f6);\n}\n\n.arkme-model-option > span:first-child {\n overflow-wrap: anywhere;\n min-width: 0;\n}\n\n.arkme-model-option small {\n color: var(--dsw-alias-label-tertiary, #8a8e96);\n font-size: 12px;\n display: block;\n}\n\n.arkme-model-status, .arkme-model-error {\n padding: 8px 10px;\n font-size: 12px;\n}\n\n.arkme-model-error {\n color: var(--dsw-alias-state-error-primary, #b54841);\n}\n\n.arkme-model-error button {\n color: inherit;\n background: none;\n border: 0;\n text-decoration: underline;\n}\n\n.arkme-model-price {\n color: var(--dsw-alias-label-tertiary, #8a8e96);\n padding: 0 10px 8px;\n font-size: 12px;\n}\n\n.arkme-model-price summary {\n cursor: pointer;\n}\n\n.arkme-model-price small {\n overflow-wrap: anywhere;\n padding-top: 6px;\n line-height: 1.6;\n display: block;\n}\n";
7131
+ var arkme_model_select_default = ".arkme-model-select {\n min-width: 0;\n font-family: inherit;\n position: relative;\n}\n\n.arkme-model-select button {\n font: inherit;\n cursor: pointer;\n}\n\n.arkme-model-select button:disabled {\n opacity: .5;\n cursor: default;\n}\n\n.arkme-model-select button:focus-visible {\n outline-offset: -2px;\n outline: 2px solid #3578e5;\n}\n\n.arkme-model-trigger {\n max-width: min(360px, 45vw);\n height: 28px;\n color: var(--dsw-alias-label-secondary, #626772);\n background: none;\n border: 0;\n border-radius: 14px;\n align-items: center;\n gap: 5px;\n padding: 0 8px;\n display: flex;\n font-size: 13px !important;\n}\n\n.arkme-model-trigger > span:first-child {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.arkme-model-trigger small {\n flex-shrink: 0;\n}\n\n.arkme-model-menu {\n z-index: 30;\n box-sizing: border-box;\n border: 1px solid var(--dsw-alias-border-inverted, #e8e8eb);\n background: var(--dsw-specific-menu, #fff);\n width: max-content;\n min-width: min(280px, 100vw - 32px);\n max-width: min(420px, 100vw - 32px);\n max-height: min(400px, 100vh - 96px);\n color: var(--dsw-alias-label-primary, #20232c);\n border-radius: 12px;\n padding: 5px;\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n overflow-y: auto;\n box-shadow: 0 8px 30px #0002;\n}\n\n.arkme-model-group-heading {\n color: var(--dsw-alias-label-tertiary, #8a8e96);\n justify-content: space-between;\n align-items: center;\n gap: 18px;\n padding: 7px 9px 4px;\n font-size: 12px;\n display: flex;\n}\n\n.arkme-model-balance-actions {\n flex-shrink: 0;\n gap: 8px;\n display: flex;\n}\n\n.arkme-model-balance-actions button {\n color: #3478e5;\n white-space: nowrap;\n background: none;\n border: 0;\n padding: 2px 0;\n}\n\n.arkme-model-balance-actions button:hover {\n text-decoration: underline;\n}\n\n.arkme-model-option {\n width: 100%;\n min-height: 38px;\n color: inherit;\n text-align: left;\n background: none;\n border: 0;\n border-radius: 8px;\n justify-content: space-between;\n align-items: center;\n gap: 18px;\n padding: 8px 10px;\n display: flex;\n font-size: 14px !important;\n}\n\n.arkme-model-option:hover, .arkme-model-trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover, #f3f4f6);\n}\n\n.arkme-model-option > span:first-child {\n overflow-wrap: anywhere;\n min-width: 0;\n}\n\n.arkme-model-option small {\n color: var(--dsw-alias-label-tertiary, #8a8e96);\n font-size: 12px;\n display: block;\n}\n\n.arkme-model-status, .arkme-model-error {\n padding: 8px 10px;\n font-size: 12px;\n}\n\n.arkme-model-error {\n color: var(--dsw-alias-state-error-primary, #b54841);\n}\n\n.arkme-model-error button {\n color: inherit;\n background: none;\n border: 0;\n text-decoration: underline;\n}\n\n.arkme-model-price {\n border-top: 1px solid var(--dsw-alias-border-inverted, #e8e8eb);\n color: var(--dsw-alias-label-tertiary, #8a8e96);\n margin-top: 4px;\n padding: 10px;\n font-size: 12px;\n}\n\n.arkme-model-price summary {\n cursor: pointer;\n}\n\n.arkme-model-price summary:focus-visible {\n outline-offset: 2px;\n outline: 2px solid #3578e5;\n}\n\n.arkme-model-price dl {\n overflow-wrap: anywhere;\n margin: 0;\n line-height: 1.6;\n}\n\n.arkme-model-price dl > div {\n margin-top: 10px;\n}\n\n.arkme-model-price dt {\n color: var(--dsw-alias-label-primary, #20232c);\n}\n\n.arkme-model-price dd {\n margin: 2px 0 0;\n}\n";
7086
7132
  //#endregion
7087
7133
  //#region src/client/ArkmeModelSelect.tsx
7088
7134
  function ArkmeModelSelect({ directory, locked, available }) {
@@ -7135,7 +7181,7 @@ AI 额度充值流程|AI point purchase
7135
7181
  "Home",
7136
7182
  "End"
7137
7183
  ].includes(event.key)) return;
7138
- const items = [...root.current?.querySelectorAll("[role=\"menu\"] button:not(:disabled)") ?? []];
7184
+ const items = [...root.current?.querySelectorAll("[role=\"menu\"] button:not(:disabled), [role=\"menu\"] summary") ?? []];
7139
7185
  if (items.length === 0) return;
7140
7186
  event.preventDefault();
7141
7187
  const current = items.indexOf(document.activeElement);
@@ -7261,7 +7307,7 @@ AI 额度充值流程|AI point purchase
7261
7307
  })]
7262
7308
  }), provider.models.map((item) => {
7263
7309
  const selected = state.current?.provider === provider.id && state.current.model === item.id;
7264
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7310
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7265
7311
  type: "button",
7266
7312
  role: "menuitemradio",
7267
7313
  className: "arkme-model-option",
@@ -7275,12 +7321,10 @@ AI 额度充值流程|AI point purchase
7275
7321
  "aria-hidden": true,
7276
7322
  children: "✓"
7277
7323
  })]
7278
- }), provider.id === "arkme-managed" && item.description && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
7279
- className: "arkme-model-price",
7280
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: tr("按用量扣积分 · 计费说明") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: item.description })]
7281
- })] }, item.id);
7324
+ }) }, item.id);
7282
7325
  })]
7283
7326
  }, provider.id)),
7327
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArkmeModelPricing, { models: state.groups.filter((provider) => provider.id === "arkme-managed").flatMap((provider) => provider.models) }),
7284
7328
  state.status === "ready" && state.groups.every((item) => item.models.length === 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7285
7329
  className: "arkme-model-status",
7286
7330
  children: tr("暂无可用模型")
@@ -274,6 +274,26 @@ window.__ModuleLoader__.load({
274
274
  this.route = route;
275
275
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
276
276
  }
277
+ /** Account-scoped marked speaker candidates; refs can be used only with this provider/account. */
278
+ async recordingSpeakerCandidates(signal) {
279
+ await this.requireSpeakerPresence(signal);
280
+ return await this.call("recordings.speaker.options", {}, signal);
281
+ }
282
+ async recordingSpeakerPresence(signal) {
283
+ await this.requireSpeakerPresence(signal);
284
+ return await this.call("recordings.speaker.presence", {}, signal);
285
+ }
286
+ async recordingSpeakerMembers(speakerRef, options = {}) {
287
+ if (speakerRef.trim() === "") throw new TypeError("speakerRef is required");
288
+ await this.requireSpeakerPresence(options.signal);
289
+ return await this.call("recordings.speaker.members", {
290
+ speakerRef,
291
+ ...options.expectedVersion === void 0 ? {} : { expectedVersion: options.expectedVersion }
292
+ }, options.signal);
293
+ }
294
+ async requireSpeakerPresence(signal) {
295
+ if ((await this.capabilities(signal)).features.speakerPresence !== true) throw new Error("当前 Provider 不支持全历史说话人统计");
296
+ }
277
297
  async capabilities(signal) {
278
298
  const capabilities = await this.call("provider.capabilities", void 0, signal);
279
299
  if (capabilities.contractVersion !== 1) throw new Error(`Unsupported Arkme provider contract version ${String(capabilities.contractVersion)}`);
@@ -2419,6 +2439,12 @@ window.__ModuleLoader__.load({
2419
2439
  }
2420
2440
  Object.fromEntries(`
2421
2441
  个人世界|Personal world
2442
+ 说话人排序|Sort speakers
2443
+ 经常出现|Most frequent
2444
+ 最近出现|Most recent
2445
+ 正在补齐说话人列表,排序仍在更新…|Loading the remaining speakers. Order is still updating…
2446
+ 列表尚未完整,当前仅对已加载的说话人排序和搜索。|The list is incomplete. Sorting and search cover loaded speakers only.
2447
+ 显示更多说话人|Show more speakers
2422
2448
  语音通话邀请|Voice call invitation
2423
2449
  视频通话邀请|Video call invitation
2424
2450
  公开快记|Public note
@@ -2998,6 +3024,7 @@ AI智能体|AI agent
2998
3024
  群聊名称|Group name
2999
3025
  最新|Latest
3000
3026
  部分历史附件暂不可用|Some earlier attachments are unavailable
3027
+ 附件暂不可用|Attachment unavailable
3001
3028
  正在加载编辑记录…|Loading edit history…
3002
3029
  暂无编辑记录|No edit history
3003
3030
  重新加载历史附件|Reload earlier attachments
@@ -4041,6 +4068,14 @@ Bot 已移出群聊|Bot removed from the group
4041
4068
  {v0}人|{v0} people
4042
4069
  正在读取群 Bot…|Loading group Bots…
4043
4070
  Bot 列表加载失败,点击重试|Could not load Bots. Click to retry
4071
+ 新识别 {v0} 个|{v0} newly identified
4072
+ 识别我的声音|Identify my voice
4073
+ 你的声纹正在处理中|Your voiceprint is being processed
4074
+ 已录入声纹,暂未在录音中匹配到你|Voiceprint enrolled. No match found in your recordings yet.
4075
+ 还没有标记你的声音|Your voice has not been labeled yet
4076
+ 打开你的录音,点击说话人名称并选择自己;请先试听确认。|Open your recording, listen to confirm, then click the speaker name and select yourself.
4077
+ 从录音中标记我|Label myself in a recording
4078
+ 管理声纹|Manage voiceprint
4044
4079
  `.trim().split("\n").map((line) => {
4045
4080
  const index = line.indexOf("|");
4046
4081
  return [line.slice(0, index), line.slice(index + 1)];
@@ -216,6 +216,26 @@ window.__ModuleLoader__.load({
216
216
  this.route = route;
217
217
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
218
218
  }
219
+ /** Account-scoped marked speaker candidates; refs can be used only with this provider/account. */
220
+ async recordingSpeakerCandidates(signal) {
221
+ await this.requireSpeakerPresence(signal);
222
+ return await this.call("recordings.speaker.options", {}, signal);
223
+ }
224
+ async recordingSpeakerPresence(signal) {
225
+ await this.requireSpeakerPresence(signal);
226
+ return await this.call("recordings.speaker.presence", {}, signal);
227
+ }
228
+ async recordingSpeakerMembers(speakerRef, options = {}) {
229
+ if (speakerRef.trim() === "") throw new TypeError("speakerRef is required");
230
+ await this.requireSpeakerPresence(options.signal);
231
+ return await this.call("recordings.speaker.members", {
232
+ speakerRef,
233
+ ...options.expectedVersion === void 0 ? {} : { expectedVersion: options.expectedVersion }
234
+ }, options.signal);
235
+ }
236
+ async requireSpeakerPresence(signal) {
237
+ if ((await this.capabilities(signal)).features.speakerPresence !== true) throw new Error("当前 Provider 不支持全历史说话人统计");
238
+ }
219
239
  async capabilities(signal) {
220
240
  const capabilities = await this.call("provider.capabilities", void 0, signal);
221
241
  if (capabilities.contractVersion !== 1) throw new Error(`Unsupported Arkme provider contract version ${String(capabilities.contractVersion)}`);
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as inspectNativeBundleArtifactV3, At as ArkmeRecordReeditDraftConflict, Bt as __require, Ct as isRecentEmojiId, D as bundleSha256, Dt as ArkmePluginError, E as arkmeSandboxEntryId, F as assertExtensionArtifactSize, Ft as registerDSHAgentInputRecordSync, G as validateVoiceprintPcm16Wav, I as canonicalExtensionJson, It as createDesktopLifecycleReader, J as arkmeRequiredLinkMetadataFallback, K as arkmeExtensionShareRefFromLink, L as sha256Hex, Lt as __commonJSMin, M as packLocalNativeBundleDirectoryV3, Mt as parseArkmeRecordReeditMentions, N as readNativeBundleTarballV3, O as canonicalBundleJson, Ot as retryAfterMillis, P as renderArkmeSandboxHostEntry, Pt as parseArrangementBoardCachePages, Q as ARKME_PROVIDER_CONTRACT_VERSION, R as unpackArkmeExtension, Rt as __esmMin, S as ARKME_BUNDLE_ARTIFACT_KIND, St as recordingImportFileNameKey, T as ArkmeBundleArtifactError, Tt as registerManagedAiProvider, U as ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE, Vt as __toCommonJS, W as ArkmeService, X as arkmeRichBackgroundSound, Y as arkmeFileBackgroundSound, Z as ARKME_DEFAULT_SHARE_WEBSITE, _ as arkmeClientContentDigest, _t as isRecordingInstantOnOrAfterUnixEpoch, at as cachedMemberItem, b as verifyExtensionResolutionSignature, bt as sameRecordingImportIdentity, c as persistentExtensionBundleDirectory, ct as ARKME_RUNTIME_INSTANCE_ID, d as readPersistentExtensionActivation, dt as arkmeNormalizedFileMimeType, f as refreshPersistentClientWrapper, ft as arkmePickedFileKind, g as activeProfileExtensionOwnerConflicts, gt as ArkmeDirectMessageAdmissionError, h as renderArkmeBundleClientBundle, ht as SecretValue, i as invokePersistentArkmeExtension, it as applyMemberUpdate, j as packBundleFiles, jt as parseArkmeRecordReeditAttachments, k as inspectBundleArtifact, kt as recordOwnerId, l as persistentExtensionPackageIdentity, lt as ARKME_TOOL_FILE_MAX_BYTES, m as writePersistentExtensionActivation, mt as ArkmeOutgoingCallError, nt as ARKME_WORLD_PUBLISH_MAX_IMAGES, o as persistentArkmeExtensionRuntimeState, ot as mergeMemberJoinEvents, p as repairPersistentExtensionInstallation, pt as isArkmeBotAvatarRef, q as arkmeIsGenericLinkMetadataTitle, r as deactivatePersistentArkmeExtension, rt as ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES, s as materializePersistentExtensionBundle, st as validateMemberUpdate, t as activatePersistentArkmeExtension, u as quarantinePersistentExtension, ut as arkmeCanInlineLocalFile, v as arkmeClientContentDigestFromSource, vt as isUnresolvedRecordingImportJob, w as ARKME_NATIVE_BUNDLE_ARTIFACT_KIND, wt as normalizeRecentEmojiIds, x as verifyNativeBundleResolutionSignature, y as verifyBundleResolutionSignature, yt as recordingImportFileKind, z as ARKME_EXTENSION_FORMAT, zt as __exportAll } from "./persistent-runtime-B6vPAaiG.js";
1
+ import { A as inspectNativeBundleArtifactV3, At as ArkmeRecordReeditDraftConflict, Bt as __require, Ct as isRecentEmojiId, D as bundleSha256, Dt as ArkmePluginError, E as arkmeSandboxEntryId, F as assertExtensionArtifactSize, Ft as registerDSHAgentInputRecordSync, G as validateVoiceprintPcm16Wav, I as canonicalExtensionJson, It as createDesktopLifecycleReader, J as arkmeRequiredLinkMetadataFallback, K as arkmeExtensionShareRefFromLink, L as sha256Hex, Lt as __commonJSMin, M as packLocalNativeBundleDirectoryV3, Mt as parseArkmeRecordReeditMentions, N as readNativeBundleTarballV3, O as canonicalBundleJson, Ot as retryAfterMillis, P as renderArkmeSandboxHostEntry, Pt as parseArrangementBoardCachePages, Q as ARKME_PROVIDER_CONTRACT_VERSION, R as unpackArkmeExtension, Rt as __esmMin, S as ARKME_BUNDLE_ARTIFACT_KIND, St as recordingImportFileNameKey, T as ArkmeBundleArtifactError, Tt as registerManagedAiProvider, U as ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE, Vt as __toCommonJS, W as ArkmeService, X as arkmeRichBackgroundSound, Y as arkmeFileBackgroundSound, Z as ARKME_DEFAULT_SHARE_WEBSITE, _ as arkmeClientContentDigest, _t as isRecordingInstantOnOrAfterUnixEpoch, at as cachedMemberItem, b as verifyExtensionResolutionSignature, bt as sameRecordingImportIdentity, c as persistentExtensionBundleDirectory, ct as ARKME_RUNTIME_INSTANCE_ID, d as readPersistentExtensionActivation, dt as arkmeNormalizedFileMimeType, f as refreshPersistentClientWrapper, ft as arkmePickedFileKind, g as activeProfileExtensionOwnerConflicts, gt as ArkmeDirectMessageAdmissionError, h as renderArkmeBundleClientBundle, ht as SecretValue, i as invokePersistentArkmeExtension, it as applyMemberUpdate, j as packBundleFiles, jt as parseArkmeRecordReeditAttachments, k as inspectBundleArtifact, kt as recordOwnerId, l as persistentExtensionPackageIdentity, lt as ARKME_TOOL_FILE_MAX_BYTES, m as writePersistentExtensionActivation, mt as ArkmeOutgoingCallError, nt as ARKME_WORLD_PUBLISH_MAX_IMAGES, o as persistentArkmeExtensionRuntimeState, ot as mergeMemberJoinEvents, p as repairPersistentExtensionInstallation, pt as isArkmeBotAvatarRef, q as arkmeIsGenericLinkMetadataTitle, r as deactivatePersistentArkmeExtension, rt as ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES, s as materializePersistentExtensionBundle, st as validateMemberUpdate, t as activatePersistentArkmeExtension, u as quarantinePersistentExtension, ut as arkmeCanInlineLocalFile, v as arkmeClientContentDigestFromSource, vt as isUnresolvedRecordingImportJob, w as ARKME_NATIVE_BUNDLE_ARTIFACT_KIND, wt as normalizeRecentEmojiIds, x as verifyNativeBundleResolutionSignature, y as verifyBundleResolutionSignature, yt as recordingImportFileKind, z as ARKME_EXTENSION_FORMAT, zt as __exportAll } from "./persistent-runtime-CuKplHIv.js";
2
2
  import { i as securePrivateFileSync, n as securePrivateDirectorySync, r as securePrivateFile, t as securePrivateDirectory } from "./private-filesystem-CGh4FtyA.js";
3
3
  import { _ as parsePluginUpdateManifest, b as validatePluginUpdateServiceOrigin, d as writePluginUpdateLifecycleLog, f as PluginUpdateInstallStateStore, g as existingCachedPluginArtifactPath, h as downloadAndCachePluginArtifact, m as PluginUpdateArtifactError, n as buildTargetInstallArgs, o as reconcilePluginUpdaterProfilePolicy, p as ARKME_PLUGIN_PACKAGE_NAME$1, t as assertTargetArtifactIntegrity, u as detachManagedProfilePluginLink, v as pluginUpdateEndpointUrl, y as validatePluginUpdateArtifactOrigin } from "./plugin-updater-helper-dNxHpiVg.js";
4
4
  import { i as prepareProfilePackageManager, n as isArkmeProfilePackageName, r as localExtensionPnpmArgs, t as applyArkmeProfileBundlePolicy } from "./profile-bundle-policy-8_y3QfHe.js";
@@ -14645,7 +14645,7 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
14645
14645
  case "recordings.speaker.cached-options": return await service.cachedRecordingSpeakerOptions(requestSignal);
14646
14646
  case "recordings.speaker.options": return await service.recordingSpeakerOptions(requestSignal);
14647
14647
  case "recordings.speaker.presence": return await service.recordingSpeakerPresence(requestSignal);
14648
- case "recordings.speaker.members": return await service.recordingSpeakerMembers(stringParam(params, "speakerRef").trim(), requestSignal);
14648
+ case "recordings.speaker.members": return await service.recordingSpeakerMembers(stringParam(params, "speakerRef").trim(), requestSignal, stringParam(params, "expectedVersion").trim() || void 0);
14649
14649
  case "recordings.speaker.recommendation": return await service.recordingSpeakerRecommendation(stringParam(params, "itemRef").trim(), requestSignal);
14650
14650
  case "recordings.speaker.assign-item": return await service.assignRecordingSpeaker({
14651
14651
  itemRef: stringParam(params, "itemRef").trim(),
@@ -30422,6 +30422,79 @@ const fileToolModules = [
30422
30422
  })
30423
30423
  ];
30424
30424
  //#endregion
30425
+ //#region src/tools/business/recordings/speaker-presence.ts
30426
+ const speakerPresenceToolModule = defineArkmeCoreToolModule({
30427
+ meta: {
30428
+ id: "business.recordings.speaker-presence.v1",
30429
+ toolName: "arkme_speaker_presence",
30430
+ kind: "business",
30431
+ phase: "core",
30432
+ effect: "read",
30433
+ profiles: ["business", "hybrid"]
30434
+ },
30435
+ create: (ports) => defineTool({
30436
+ name: "arkme_speaker_presence",
30437
+ description: "Read the current account’s all-history confirmed speaker appearance statistics or original voice associations. Counts use transcribed system speech in visible recordings, not voiceprint usage or other people’s recordings. List returns opaque speakerRef values; pass one unchanged to detail. A non-fresh state is incomplete and must never be reported as zero appearances. Do not poll continuously; report that processing is in progress.",
30438
+ parameters: {
30439
+ action: {
30440
+ type: "string",
30441
+ enum: ["list", "detail"],
30442
+ required: true
30443
+ },
30444
+ speaker_ref: {
30445
+ type: "string",
30446
+ description: "Detail only: unchanged speakerRef returned by list."
30447
+ },
30448
+ expected_version: {
30449
+ type: "string",
30450
+ description: "Optional version from list, to avoid mixing snapshots."
30451
+ }
30452
+ },
30453
+ output: TEXT_OUTPUT$3,
30454
+ isConcurrencySafe: () => true,
30455
+ execute: async (args, exec) => {
30456
+ exec.signal?.throwIfAborted();
30457
+ if (args.action === "list") {
30458
+ if (args.speaker_ref !== void 0 || args.expected_version !== void 0) throw new TypeError("列表不接受详情参数");
30459
+ const [candidates, presence] = await Promise.all([ports.recordingSpeakerOptions(exec.signal), ports.recordingSpeakerPresence(exec.signal)]);
30460
+ return taggedJSON("已识别说话人出现统计", {
30461
+ candidates: candidates.filter((item) => item.kind === "speaker").map(({ speakerRef, optionKey, personKey, label }) => ({
30462
+ speakerRef,
30463
+ optionKey,
30464
+ personKey,
30465
+ label
30466
+ })),
30467
+ state: presence.state,
30468
+ version: presence.version,
30469
+ retryAfterMs: presence.retryAfterMs,
30470
+ items: presence.state === "fresh" ? presence.items.map(({ optionKey, dayCount, lastSeenAt }) => ({
30471
+ optionKey,
30472
+ dayCount,
30473
+ lastSeenAt
30474
+ })) : []
30475
+ });
30476
+ }
30477
+ if (args.action !== "detail" || typeof args.speaker_ref !== "string" || args.speaker_ref.trim() === "" || args.speaker_ref.length > 4096) throw new TypeError("详情需要列表返回的 speaker_ref");
30478
+ const result = await ports.recordingSpeakerMembers(args.speaker_ref, exec.signal, args.expected_version);
30479
+ return taggedJSON("说话人原始声音关联", {
30480
+ state: result.state,
30481
+ version: result.version,
30482
+ retryAfterMs: result.retryAfterMs,
30483
+ ...result.state === "fresh" ? {
30484
+ dayCount: result.dayCount,
30485
+ lastSeenAt: result.lastSeenAt
30486
+ } : {},
30487
+ items: result.state === "fresh" ? result.items.map(({ identityKey, token, dayCount, lastSeenAt }) => ({
30488
+ identityKey,
30489
+ token,
30490
+ dayCount,
30491
+ lastSeenAt
30492
+ })) : []
30493
+ });
30494
+ }
30495
+ })
30496
+ });
30497
+ //#endregion
30425
30498
  //#region src/tools/business/recordings/import.ts
30426
30499
  function importResult(job) {
30427
30500
  const failureMessage = job.retryable ? "录音导入失败,可使用最新任务版本重试。" : "录音导入失败,请在录音导入页面查看原因。";
@@ -30589,85 +30662,89 @@ function directoryConfirmation(input, prepared) {
30589
30662
  }
30590
30663
  //#endregion
30591
30664
  //#region src/tools/business/recordings/index.ts
30592
- const recordingToolModules = [recordingImportToolModule, defineArkmeCoreToolModule({
30593
- meta: {
30594
- id: "business.recordings.import-folder.v1",
30595
- toolName: "arkme_recording_import_folder",
30596
- kind: "business",
30597
- phase: "core",
30598
- effect: "write",
30599
- grant: "explicit-user-write",
30600
- profiles: ["business", "hybrid"]
30601
- },
30602
- create: (ports) => withArkmeConfirmationContext(defineTool({
30603
- name: "arkme_recording_import_folder",
30604
- description: "Import WAV, MP3 and M4A recordings from a human-selected local absolute or ~/ directory. Use action=prepare to check or refresh cloud matches without uploading, directly once the directory is known. The preflight returns one final confirmation with upload counts, exceptional file paths and ownership, which defaults to self. Only after a later human confirmation use action=upload with the same directory, scope, ownership and recording times; missing, expired or changed confirmation scope requires a fresh confirmation. Includes subdirectories by default and does not follow symlinks. Filename YYYYMMDD-HHMMSS uses the host local timezone; use start_times for unknown recording times. matched_uploaded means metadata match and upload finalization, not content hash verification. Returns after Audio receives the uploads; transcription is separate.",
30605
- parameters: {
30606
- action: {
30607
- type: "string",
30608
- enum: ["prepare", "upload"],
30609
- required: true,
30610
- description: "Use prepare for read-only preflight or refresh; use upload after the human confirms the returned scope."
30611
- },
30612
- directory_path: {
30613
- type: "string",
30614
- required: true,
30615
- description: "Exact local absolute or ~/ recording directory selected by the human."
30616
- },
30617
- recursive: {
30618
- type: "boolean",
30619
- description: "Include subdirectories; defaults to true."
30620
- },
30621
- ownership: {
30622
- type: "string",
30623
- enum: ["self", "other"],
30624
- description: "Recording ownership; defaults to self."
30625
- },
30626
- start_times: {
30627
- type: "array",
30628
- description: "Actual per-file recording starts when filename time is unknown or needs correction.",
30629
- items: {
30630
- type: "object",
30631
- additionalProperties: false,
30632
- properties: {
30633
- relative_path: {
30634
- type: "string",
30635
- required: true,
30636
- description: "Exact path relative to directory_path."
30637
- },
30638
- start_at_millis: {
30639
- type: "integer",
30640
- required: true,
30641
- description: "Actual recording start in Unix milliseconds."
30665
+ const recordingToolModules = [
30666
+ recordingImportToolModule,
30667
+ defineArkmeCoreToolModule({
30668
+ meta: {
30669
+ id: "business.recordings.import-folder.v1",
30670
+ toolName: "arkme_recording_import_folder",
30671
+ kind: "business",
30672
+ phase: "core",
30673
+ effect: "write",
30674
+ grant: "explicit-user-write",
30675
+ profiles: ["business", "hybrid"]
30676
+ },
30677
+ create: (ports) => withArkmeConfirmationContext(defineTool({
30678
+ name: "arkme_recording_import_folder",
30679
+ description: "Import WAV, MP3 and M4A recordings from a human-selected local absolute or ~/ directory. Use action=prepare to check or refresh cloud matches without uploading, directly once the directory is known. The preflight returns one final confirmation with upload counts, exceptional file paths and ownership, which defaults to self. Only after a later human confirmation use action=upload with the same directory, scope, ownership and recording times; missing, expired or changed confirmation scope requires a fresh confirmation. Includes subdirectories by default and does not follow symlinks. Filename YYYYMMDD-HHMMSS uses the host local timezone; use start_times for unknown recording times. matched_uploaded means metadata match and upload finalization, not content hash verification. Returns after Audio receives the uploads; transcription is separate.",
30680
+ parameters: {
30681
+ action: {
30682
+ type: "string",
30683
+ enum: ["prepare", "upload"],
30684
+ required: true,
30685
+ description: "Use prepare for read-only preflight or refresh; use upload after the human confirms the returned scope."
30686
+ },
30687
+ directory_path: {
30688
+ type: "string",
30689
+ required: true,
30690
+ description: "Exact local absolute or ~/ recording directory selected by the human."
30691
+ },
30692
+ recursive: {
30693
+ type: "boolean",
30694
+ description: "Include subdirectories; defaults to true."
30695
+ },
30696
+ ownership: {
30697
+ type: "string",
30698
+ enum: ["self", "other"],
30699
+ description: "Recording ownership; defaults to self."
30700
+ },
30701
+ start_times: {
30702
+ type: "array",
30703
+ description: "Actual per-file recording starts when filename time is unknown or needs correction.",
30704
+ items: {
30705
+ type: "object",
30706
+ additionalProperties: false,
30707
+ properties: {
30708
+ relative_path: {
30709
+ type: "string",
30710
+ required: true,
30711
+ description: "Exact path relative to directory_path."
30712
+ },
30713
+ start_at_millis: {
30714
+ type: "integer",
30715
+ required: true,
30716
+ description: "Actual recording start in Unix milliseconds."
30717
+ }
30642
30718
  }
30643
30719
  }
30644
30720
  }
30721
+ },
30722
+ output: TEXT_OUTPUT$3,
30723
+ isConcurrencySafe: () => true,
30724
+ execute: async (args, exec) => {
30725
+ exec.signal?.throwIfAborted();
30726
+ const input = directoryInput(args);
30727
+ const prepared = await ports.prepareRecordingDirectory(input, exec.signal);
30728
+ if (args.action === "prepare") return directoryPreviewResult(prepared);
30729
+ return directoryResult(await ports.importRecordingDirectory(input, prepared, exec.signal));
30645
30730
  }
30646
- },
30647
- output: TEXT_OUTPUT$3,
30648
- isConcurrencySafe: () => true,
30649
- execute: async (args, exec) => {
30650
- exec.signal?.throwIfAborted();
30651
- const input = directoryInput(args);
30652
- const prepared = await ports.prepareRecordingDirectory(input, exec.signal);
30653
- if (args.action === "prepare") return directoryPreviewResult(prepared);
30654
- return directoryResult(await ports.importRecordingDirectory(input, prepared, exec.signal));
30655
- }
30656
- }), {
30657
- confirmationRequest: directoryConfirmationRequest,
30658
- question: (args, prepared) => directoryConfirmation(directoryInput(args), prepared),
30659
- prepare: async (args, exec) => {
30660
- exec.signal?.throwIfAborted();
30661
- return await ports.prepareRecordingDirectory(directoryInput(args), exec.signal);
30662
- },
30663
- execute: async (args, exec, prepared) => {
30664
- exec.signal?.throwIfAborted();
30665
- const request = directoryConfirmationRequest(args);
30666
- if (request.forcePrepare) return directoryPreviewResult(prepared);
30667
- return directoryResult(await ports.importRecordingDirectory(request.arguments, prepared, exec.signal));
30668
- }
30669
- })
30670
- })];
30731
+ }), {
30732
+ confirmationRequest: directoryConfirmationRequest,
30733
+ question: (args, prepared) => directoryConfirmation(directoryInput(args), prepared),
30734
+ prepare: async (args, exec) => {
30735
+ exec.signal?.throwIfAborted();
30736
+ return await ports.prepareRecordingDirectory(directoryInput(args), exec.signal);
30737
+ },
30738
+ execute: async (args, exec, prepared) => {
30739
+ exec.signal?.throwIfAborted();
30740
+ const request = directoryConfirmationRequest(args);
30741
+ if (request.forcePrepare) return directoryPreviewResult(prepared);
30742
+ return directoryResult(await ports.importRecordingDirectory(request.arguments, prepared, exec.signal));
30743
+ }
30744
+ })
30745
+ }),
30746
+ speakerPresenceToolModule
30747
+ ];
30671
30748
  //#endregion
30672
30749
  //#region src/tools/business/records/create.ts
30673
30750
  const createRecordToolModule = defineArkmeCoreToolModule({
@@ -1,2 +1,2 @@
1
- import { a as persistentArkmeExtensionActive, i as invokePersistentArkmeExtension, n as applyPersistentArkmeHostExtension, o as persistentArkmeExtensionRuntimeState, r as deactivatePersistentArkmeExtension, t as activatePersistentArkmeExtension } from "./persistent-runtime-B6vPAaiG.js";
1
+ import { a as persistentArkmeExtensionActive, i as invokePersistentArkmeExtension, n as applyPersistentArkmeHostExtension, o as persistentArkmeExtensionRuntimeState, r as deactivatePersistentArkmeExtension, t as activatePersistentArkmeExtension } from "./persistent-runtime-CuKplHIv.js";
2
2
  export { activatePersistentArkmeExtension, applyPersistentArkmeHostExtension, deactivatePersistentArkmeExtension, invokePersistentArkmeExtension, persistentArkmeExtensionActive, persistentArkmeExtensionRuntimeState };