@senguoyun/dsh-arkme 0.1.86 → 0.1.87
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.
- package/docs/recognized-speaker-directory-backend-handoff-2026-09-28.md +2 -0
- package/docs/recognized-speaker-entry-and-visits.md +2 -0
- package/docs/recognized-speaker-sorting.md +2 -0
- package/docs/speaker-directory-integration-2026-09-30.md +53 -0
- package/lib/client.js +877 -1053
- package/lib/client.js.map +1 -1
- package/lib/index.js +7 -1
- package/lib/persistent-extension.js +1 -1
- package/lib/{persistent-runtime-DfmCJcxH.js → persistent-runtime-0qn1BZ6S.js} +321 -15
- package/lib/types/arkme-service.d.ts +6 -0
- package/lib/types/arkme-service.d.ts.map +1 -1
- package/lib/types/client/ArkmeAvatar.d.ts +2 -1
- package/lib/types/client/ArkmeAvatar.d.ts.map +1 -1
- package/lib/types/client/ArkmeRecognizedSpeakersSurface.d.ts +3 -30
- package/lib/types/client/ArkmeRecognizedSpeakersSurface.d.ts.map +1 -1
- package/lib/types/client/api.d.ts +1 -1
- package/lib/types/client/api.d.ts.map +1 -1
- package/lib/types/client/locales/actions-en.d.ts.map +1 -1
- package/lib/types/client/recognized-speaker-directory.d.ts +51 -22
- package/lib/types/client/recognized-speaker-directory.d.ts.map +1 -1
- package/lib/types/client/recordings/RecognizedSpeakerEntry.d.ts +2 -4
- package/lib/types/client/recordings/RecognizedSpeakerEntry.d.ts.map +1 -1
- package/lib/types/client/recordings/recording-speaker-options-store.d.ts.map +1 -1
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/recording-presence.d.ts +1 -1
- package/lib/types/services/recording-service.d.ts +4 -0
- package/lib/types/services/recording-service.d.ts.map +1 -1
- package/lib/types/services/service.d.ts.map +1 -1
- package/lib/types/services/speaker-directory-service.d.ts +19 -0
- package/lib/types/services/speaker-directory-service.d.ts.map +1 -0
- package/lib/types/services/unmarked-speaker-service.d.ts +2 -0
- package/lib/types/services/unmarked-speaker-service.d.ts.map +1 -1
- package/lib/types/speaker-directory-contract.d.ts +71 -0
- package/lib/types/speaker-directory-contract.d.ts.map +1 -0
- package/lib/types/types.d.ts +1 -1
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +5 -5
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as inspectNativeBundleArtifactV3, At as recordOwnerId, Bt as __exportAll, Ct as recordingImportFileNameKey, D as bundleSha256, E as arkmeSandboxEntryId, Et as registerManagedAiProvider, F as assertExtensionArtifactSize, Ft as parseArrangementBoardCachePages, G as validateVoiceprintPcm16Wav, Ht as __toCommonJS, I as canonicalExtensionJson, It as registerDSHAgentInputRecordSync, J as arkmeRequiredLinkMetadataFallback, K as arkmeExtensionShareRefFromLink, L as sha256Hex, Lt as createDesktopLifecycleReader, M as packLocalNativeBundleDirectoryV3, Mt as parseArkmeRecordReeditAttachments, N as readNativeBundleTarballV3, Nt as parseArkmeRecordReeditMentions, O as canonicalBundleJson, Ot as ArkmePluginError, P as renderArkmeSandboxHostEntry, Q as ARKME_PROVIDER_CONTRACT_VERSION, R as unpackArkmeExtension, Rt as __commonJSMin, S as ARKME_BUNDLE_ARTIFACT_KIND, T as ArkmeBundleArtifactError, Tt as normalizeRecentEmojiIds, U as ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE, Vt as __require, W as ArkmeService, X as arkmeRichBackgroundSound, Y as arkmeFileBackgroundSound, Z as ARKME_DEFAULT_SHARE_WEBSITE, _ as arkmeClientContentDigest, _t as ArkmeDirectMessageAdmissionError, at as cachedMemberItem, b as verifyExtensionResolutionSignature, bt as recordingImportFileKind, c as persistentExtensionBundleDirectory, ct as SelfRoleSyncStore, d as readPersistentExtensionActivation, dt as arkmeCanInlineLocalFile, f as refreshPersistentClientWrapper, ft as arkmeNormalizedFileMimeType, g as activeProfileExtensionOwnerConflicts, gt as SecretValue, h as renderArkmeBundleClientBundle, ht as ArkmeOutgoingCallError, i as invokePersistentArkmeExtension, it as applyMemberUpdate, j as packBundleFiles, jt as ArkmeRecordReeditDraftConflict, k as inspectBundleArtifact, kt as retryAfterMillis, l as persistentExtensionPackageIdentity, lt as ARKME_RUNTIME_INSTANCE_ID, m as writePersistentExtensionActivation, mt as isArkmeBotAvatarRef, nt as ARKME_WORLD_PUBLISH_MAX_IMAGES, o as persistentArkmeExtensionRuntimeState, ot as mergeMemberJoinEvents, p as repairPersistentExtensionInstallation, pt as arkmePickedFileKind, 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 ARKME_TOOL_FILE_MAX_BYTES, v as arkmeClientContentDigestFromSource, vt as isRecordingInstantOnOrAfterUnixEpoch, w as ARKME_NATIVE_BUNDLE_ARTIFACT_KIND, wt as isRecentEmojiId, x as verifyNativeBundleResolutionSignature, xt as sameRecordingImportIdentity, y as verifyBundleResolutionSignature, yt as isUnresolvedRecordingImportJob, z as ARKME_EXTENSION_FORMAT, zt as __esmMin } from "./persistent-runtime-
|
|
1
|
+
import { A as inspectNativeBundleArtifactV3, At as recordOwnerId, Bt as __exportAll, Ct as recordingImportFileNameKey, D as bundleSha256, E as arkmeSandboxEntryId, Et as registerManagedAiProvider, F as assertExtensionArtifactSize, Ft as parseArrangementBoardCachePages, G as validateVoiceprintPcm16Wav, Ht as __toCommonJS, I as canonicalExtensionJson, It as registerDSHAgentInputRecordSync, J as arkmeRequiredLinkMetadataFallback, K as arkmeExtensionShareRefFromLink, L as sha256Hex, Lt as createDesktopLifecycleReader, M as packLocalNativeBundleDirectoryV3, Mt as parseArkmeRecordReeditAttachments, N as readNativeBundleTarballV3, Nt as parseArkmeRecordReeditMentions, O as canonicalBundleJson, Ot as ArkmePluginError, P as renderArkmeSandboxHostEntry, Q as ARKME_PROVIDER_CONTRACT_VERSION, R as unpackArkmeExtension, Rt as __commonJSMin, S as ARKME_BUNDLE_ARTIFACT_KIND, T as ArkmeBundleArtifactError, Tt as normalizeRecentEmojiIds, U as ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE, Vt as __require, W as ArkmeService, X as arkmeRichBackgroundSound, Y as arkmeFileBackgroundSound, Z as ARKME_DEFAULT_SHARE_WEBSITE, _ as arkmeClientContentDigest, _t as ArkmeDirectMessageAdmissionError, at as cachedMemberItem, b as verifyExtensionResolutionSignature, bt as recordingImportFileKind, c as persistentExtensionBundleDirectory, ct as SelfRoleSyncStore, d as readPersistentExtensionActivation, dt as arkmeCanInlineLocalFile, f as refreshPersistentClientWrapper, ft as arkmeNormalizedFileMimeType, g as activeProfileExtensionOwnerConflicts, gt as SecretValue, h as renderArkmeBundleClientBundle, ht as ArkmeOutgoingCallError, i as invokePersistentArkmeExtension, it as applyMemberUpdate, j as packBundleFiles, jt as ArkmeRecordReeditDraftConflict, k as inspectBundleArtifact, kt as retryAfterMillis, l as persistentExtensionPackageIdentity, lt as ARKME_RUNTIME_INSTANCE_ID, m as writePersistentExtensionActivation, mt as isArkmeBotAvatarRef, nt as ARKME_WORLD_PUBLISH_MAX_IMAGES, o as persistentArkmeExtensionRuntimeState, ot as mergeMemberJoinEvents, p as repairPersistentExtensionInstallation, pt as arkmePickedFileKind, 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 ARKME_TOOL_FILE_MAX_BYTES, v as arkmeClientContentDigestFromSource, vt as isRecordingInstantOnOrAfterUnixEpoch, w as ARKME_NATIVE_BUNDLE_ARTIFACT_KIND, wt as isRecentEmojiId, x as verifyNativeBundleResolutionSignature, xt as sameRecordingImportIdentity, y as verifyBundleResolutionSignature, yt as isUnresolvedRecordingImportJob, z as ARKME_EXTENSION_FORMAT, zt as __esmMin } from "./persistent-runtime-0qn1BZ6S.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";
|
|
@@ -14277,6 +14277,7 @@ function createArkmeHostApi(service, options) {
|
|
|
14277
14277
|
"recordings.import.session.update-ownership",
|
|
14278
14278
|
"recordings.import.session.delete",
|
|
14279
14279
|
"recordings.speaker.assign-item",
|
|
14280
|
+
"speaker-directory.seen",
|
|
14280
14281
|
"openapi.mcp.retry",
|
|
14281
14282
|
"team.create",
|
|
14282
14283
|
"team.join-by-jotmo-id"
|
|
@@ -14643,6 +14644,11 @@ async function dispatchArkmeHostOperation(service, operation, params, updateMana
|
|
|
14643
14644
|
case "recordings.import.session.delete": return await service.deleteRecordingImportSession(stringParam(params, "sessionRef").trim(), requestSignal);
|
|
14644
14645
|
case "recordings.playback.open": return await service.recordingPlayback(stringParam(params, "itemRef").trim(), requestSignal);
|
|
14645
14646
|
case "recordings.speaker.cached-options": return await service.cachedRecordingSpeakerOptions(requestSignal);
|
|
14647
|
+
case "speaker-directory.summary": return await service.speakerDirectorySummary(params, requestSignal);
|
|
14648
|
+
case "speaker-directory.list": return await service.speakerDirectoryList(params, requestSignal);
|
|
14649
|
+
case "speaker-directory.seen": return await service.speakerDirectorySeen(params, requestSignal);
|
|
14650
|
+
case "speaker-directory.open": return await service.speakerDirectoryOpen(params, requestSignal);
|
|
14651
|
+
case "speaker-directory.avatars": return await service.speakerDirectoryAvatars(params, requestSignal);
|
|
14646
14652
|
case "recordings.speaker.options": return await service.recordingSpeakerOptions(requestSignal);
|
|
14647
14653
|
case "recordings.speaker.presence": return await service.recordingSpeakerPresence(requestSignal);
|
|
14648
14654
|
case "recordings.speaker.members": return await service.recordingSpeakerMembers(stringParam(params, "speakerRef").trim(), requestSignal, stringParam(params, "expectedVersion").trim() || void 0);
|
|
@@ -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-
|
|
1
|
+
import { a as persistentArkmeExtensionActive, i as invokePersistentArkmeExtension, n as applyPersistentArkmeHostExtension, o as persistentArkmeExtensionRuntimeState, r as deactivatePersistentArkmeExtension, t as activatePersistentArkmeExtension } from "./persistent-runtime-0qn1BZ6S.js";
|
|
2
2
|
export { activatePersistentArkmeExtension, applyPersistentArkmeHostExtension, deactivatePersistentArkmeExtension, invokePersistentArkmeExtension, persistentArkmeExtensionActive, persistentArkmeExtensionRuntimeState };
|
|
@@ -393,7 +393,7 @@ function keys(value, allowed) {
|
|
|
393
393
|
function text$12(value, max) {
|
|
394
394
|
if (typeof value !== "string" || value.length > max) fail$1();
|
|
395
395
|
}
|
|
396
|
-
function integer$
|
|
396
|
+
function integer$7(value) {
|
|
397
397
|
if (!Number.isSafeInteger(value) || value < 0) fail$1();
|
|
398
398
|
}
|
|
399
399
|
/** Validate the browser projection only; owner IDs and arbitrary payload fields are never persisted. */
|
|
@@ -409,9 +409,9 @@ function parseArrangementBoardCachePages(value) {
|
|
|
409
409
|
"nextOffset",
|
|
410
410
|
"board"
|
|
411
411
|
]);
|
|
412
|
-
integer$
|
|
412
|
+
integer$7(page.total);
|
|
413
413
|
if (typeof page.hasMore !== "boolean" || !Array.isArray(page.items) || page.items.length > 50 || page.items.length > page.total) fail$1();
|
|
414
|
-
if (page.nextOffset !== void 0) integer$
|
|
414
|
+
if (page.nextOffset !== void 0) integer$7(page.nextOffset);
|
|
415
415
|
if (page.board !== void 0) {
|
|
416
416
|
const board = object$8(page.board);
|
|
417
417
|
keys(board, ["supported", "version"]);
|
|
@@ -440,9 +440,9 @@ function parseArrangementBoardCachePages(value) {
|
|
|
440
440
|
text$12(item.title, 2e4);
|
|
441
441
|
text$12(item.description, 2e5);
|
|
442
442
|
text$12(item.reminderState, 256);
|
|
443
|
-
integer$
|
|
444
|
-
integer$
|
|
445
|
-
for (const key of ["dueAtMillis", "remindAtMillis"]) if (item[key] !== void 0) integer$
|
|
443
|
+
integer$7(item.createdAtMillis);
|
|
444
|
+
integer$7(item.updatedAtMillis);
|
|
445
|
+
for (const key of ["dueAtMillis", "remindAtMillis"]) if (item[key] !== void 0) integer$7(item[key]);
|
|
446
446
|
if (item.recognitionState !== void 0) text$12(item.recognitionState, 256);
|
|
447
447
|
if (item.creationSource !== void 0) {
|
|
448
448
|
const source = object$8(item.creationSource);
|
|
@@ -456,12 +456,12 @@ function parseArrangementBoardCachePages(value) {
|
|
|
456
456
|
"input",
|
|
457
457
|
"none"
|
|
458
458
|
].includes(source.kind) || !Array.isArray(source.items) || source.items.length > 100) fail$1();
|
|
459
|
-
integer$
|
|
459
|
+
integer$7(source.unavailableCount);
|
|
460
460
|
for (const rawSource of source.items) {
|
|
461
461
|
const entry = object$8(rawSource);
|
|
462
462
|
keys(entry, ["text", "createdAtMillis"]);
|
|
463
463
|
text$12(entry.text, 2e5);
|
|
464
|
-
if (entry.createdAtMillis !== void 0) integer$
|
|
464
|
+
if (entry.createdAtMillis !== void 0) integer$7(entry.createdAtMillis);
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
467
|
}
|
|
@@ -1306,7 +1306,7 @@ var HttpArkmeAccountScopeBridge = class {
|
|
|
1306
1306
|
this.fetchImpl = fetchImpl;
|
|
1307
1307
|
}
|
|
1308
1308
|
async attest(identity) {
|
|
1309
|
-
return status$
|
|
1309
|
+
return status$2(await this.request("account.scope.attest", identity));
|
|
1310
1310
|
}
|
|
1311
1311
|
async prepare(identity) {
|
|
1312
1312
|
const value = await this.request("account.scope.prepare", identity);
|
|
@@ -1315,17 +1315,17 @@ var HttpArkmeAccountScopeBridge = class {
|
|
|
1315
1315
|
return { transitionRef };
|
|
1316
1316
|
}
|
|
1317
1317
|
async commit(transitionRef) {
|
|
1318
|
-
return status$
|
|
1318
|
+
return status$2(await this.request("account.scope.commit", { transitionRef }));
|
|
1319
1319
|
}
|
|
1320
1320
|
async abort(transitionRef) {
|
|
1321
|
-
if (status$
|
|
1321
|
+
if (status$2(await this.request("account.scope.abort", { transitionRef })).status !== "ready") throw new Error("Desktop account scope abort response is invalid");
|
|
1322
1322
|
return { status: "ready" };
|
|
1323
1323
|
}
|
|
1324
1324
|
async request(action, payload) {
|
|
1325
1325
|
return await requestArkmeDesktopBridge(this.config, this.fetchImpl, action, payload);
|
|
1326
1326
|
}
|
|
1327
1327
|
};
|
|
1328
|
-
function status$
|
|
1328
|
+
function status$2(value) {
|
|
1329
1329
|
if (value.status !== "ready" && value.status !== "relaunch") throw new Error("Desktop account scope response is invalid");
|
|
1330
1330
|
return { status: value.status };
|
|
1331
1331
|
}
|
|
@@ -1799,7 +1799,11 @@ var ServiceRuntime = class {
|
|
|
1799
1799
|
});
|
|
1800
1800
|
if (response.status === 401 || response.status === 403 && !preserveForbiddenError) throw new ArkmePluginError(`auth-http-${response.status}`, "Arkme 登录凭据已失效", false, response.status);
|
|
1801
1801
|
if (!response.ok) {
|
|
1802
|
-
|
|
1802
|
+
let retryAfter = retryAfterMillis(response.headers.get("retry-after"));
|
|
1803
|
+
if (path.startsWith("/api/v1/audio/speaker-directory/") && response.status === 429) try {
|
|
1804
|
+
const milliseconds = objectValue$2(objectValue$2(await response.clone().json()).data).retry_after_ms;
|
|
1805
|
+
if (typeof milliseconds === "number" && Number.isFinite(milliseconds) && milliseconds >= 0) retryAfter = Math.max(retryAfter ?? 0, milliseconds);
|
|
1806
|
+
} catch {}
|
|
1803
1807
|
if (preserveHttpError) {
|
|
1804
1808
|
let errorEnvelope;
|
|
1805
1809
|
try {
|
|
@@ -2160,6 +2164,228 @@ var ServiceRuntime = class {
|
|
|
2160
2164
|
}
|
|
2161
2165
|
};
|
|
2162
2166
|
//#endregion
|
|
2167
|
+
//#region src/services/speaker-directory-service.ts
|
|
2168
|
+
const prefix$2 = "arkme-speaker-directory-detail-v1";
|
|
2169
|
+
const invalid$13 = () => new ArkmePluginError("speaker-directory-contract-invalid", "说话人目录响应无效,请稍后重试", true, 502);
|
|
2170
|
+
function integer$6(value) {
|
|
2171
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw invalid$13();
|
|
2172
|
+
return value;
|
|
2173
|
+
}
|
|
2174
|
+
function str(value) {
|
|
2175
|
+
if (typeof value !== "string") throw invalid$13();
|
|
2176
|
+
return value;
|
|
2177
|
+
}
|
|
2178
|
+
function status$1(data) {
|
|
2179
|
+
if (![
|
|
2180
|
+
"fresh",
|
|
2181
|
+
"stale",
|
|
2182
|
+
"building",
|
|
2183
|
+
"failed",
|
|
2184
|
+
"disabled",
|
|
2185
|
+
"snapshot_expired"
|
|
2186
|
+
].includes(String(data.state)) || !["complete", "unknown"].includes(String(data.coverage))) throw invalid$13();
|
|
2187
|
+
return {
|
|
2188
|
+
state: data.state,
|
|
2189
|
+
coverage: data.coverage,
|
|
2190
|
+
retryAfterMs: integer$6(data.retry_after_ms ?? 0),
|
|
2191
|
+
processingSessionCount: integer$6(data.processing_session_count ?? 0),
|
|
2192
|
+
pendingIdentityAggregationCount: integer$6(data.pending_identity_aggregation_count ?? 0),
|
|
2193
|
+
insufficientEvidenceCount: integer$6(data.insufficient_evidence_count ?? 0),
|
|
2194
|
+
scanTruncated: data.scan_truncated === true
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
function inputString(value, maxBytes, fallback = "") {
|
|
2198
|
+
if (value === void 0) return fallback;
|
|
2199
|
+
if (typeof value !== "string" || Buffer.byteLength(value) > maxBytes) throw new ArkmePluginError("speaker-directory-input-invalid", "说话人查询条件过长或无效", false);
|
|
2200
|
+
return value;
|
|
2201
|
+
}
|
|
2202
|
+
var SpeakerDirectoryService = class {
|
|
2203
|
+
runtime;
|
|
2204
|
+
recording;
|
|
2205
|
+
unmarked;
|
|
2206
|
+
constructor(runtime, recording, unmarked) {
|
|
2207
|
+
this.runtime = runtime;
|
|
2208
|
+
this.recording = recording;
|
|
2209
|
+
this.unmarked = unmarked;
|
|
2210
|
+
}
|
|
2211
|
+
async request(path, body, signal) {
|
|
2212
|
+
const session = await this.runtime.requireSession();
|
|
2213
|
+
const data = await this.runtime.authenticatedAudioPost(`/api/v1/audio/speaker-directory/${path}`, body, session, signal, {
|
|
2214
|
+
lane: path === "seen" ? "write" : "interactive-read",
|
|
2215
|
+
cacheMs: 0,
|
|
2216
|
+
cancelWhenUnobserved: true
|
|
2217
|
+
});
|
|
2218
|
+
signal?.throwIfAborted();
|
|
2219
|
+
const current = await this.runtime.requireSession();
|
|
2220
|
+
if (current.userId !== session.userId || current.refreshToken !== session.refreshToken) throw new ArkmePluginError("speaker-directory-account-changed", "账号已切换,请重新打开目录", false, 409);
|
|
2221
|
+
return {
|
|
2222
|
+
data: objectValue$2(data),
|
|
2223
|
+
session
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
async summary(input, signal) {
|
|
2227
|
+
const body = {};
|
|
2228
|
+
if (input.snapshotVersion !== void 0) body.snapshot_version = inputString(input.snapshotVersion, 128);
|
|
2229
|
+
if (input.seenVersion !== void 0) {
|
|
2230
|
+
if (typeof input.seenVersion !== "number" || !Number.isSafeInteger(input.seenVersion) || input.seenVersion < 0) throw new ArkmePluginError("speaker-directory-input-invalid", "查看版本无效", false);
|
|
2231
|
+
body.seen_version = input.seenVersion;
|
|
2232
|
+
}
|
|
2233
|
+
const { data } = await this.request("summary", body, signal);
|
|
2234
|
+
const state = status$1(data);
|
|
2235
|
+
const count = (value) => value === null || value === void 0 ? null : integer$6(value);
|
|
2236
|
+
const result = {
|
|
2237
|
+
...state,
|
|
2238
|
+
totalCount: count(data.total_count),
|
|
2239
|
+
markedCount: count(data.marked_count),
|
|
2240
|
+
unmarkedCount: count(data.unmarked_count),
|
|
2241
|
+
unseenCount: count(data.unseen_count),
|
|
2242
|
+
snapshotVersion: str(data.snapshot_version ?? ""),
|
|
2243
|
+
seenVersion: integer$6(data.seen_version ?? 0),
|
|
2244
|
+
updatedAt: integer$6(data.updated_at ?? 0),
|
|
2245
|
+
notModified: data.not_modified === true
|
|
2246
|
+
};
|
|
2247
|
+
if (state.coverage === "complete" && state.state !== "disabled" && state.state !== "snapshot_expired" && (result.totalCount === null || result.markedCount === null || result.unmarkedCount === null || result.unseenCount === null || result.totalCount !== result.markedCount + result.unmarkedCount || result.snapshotVersion === "")) throw invalid$13();
|
|
2248
|
+
return result;
|
|
2249
|
+
}
|
|
2250
|
+
async list(input, signal) {
|
|
2251
|
+
const filter = input.filter ?? "all", sort = input.sort ?? "frequent";
|
|
2252
|
+
const query = inputString(input.query, 4096).trim().toLowerCase();
|
|
2253
|
+
inputString(query, 256);
|
|
2254
|
+
const limit = input.limit === void 0 || input.limit === 0 ? 50 : input.limit;
|
|
2255
|
+
if (![
|
|
2256
|
+
"all",
|
|
2257
|
+
"marked",
|
|
2258
|
+
"unmarked"
|
|
2259
|
+
].includes(String(filter)) || !["frequent", "recent"].includes(String(sort)) || typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > 100) throw new ArkmePluginError("speaker-directory-input-invalid", "说话人查询条件无效", false);
|
|
2260
|
+
const { data, session } = await this.request("list", {
|
|
2261
|
+
filter,
|
|
2262
|
+
sort,
|
|
2263
|
+
query,
|
|
2264
|
+
limit,
|
|
2265
|
+
snapshot_version: inputString(input.snapshotVersion, 128),
|
|
2266
|
+
cursor: inputString(input.cursor, 4096)
|
|
2267
|
+
}, signal);
|
|
2268
|
+
const state = status$1(data);
|
|
2269
|
+
if (state.state === "disabled" || state.state === "snapshot_expired" || state.coverage === "unknown") return {
|
|
2270
|
+
...state,
|
|
2271
|
+
items: [],
|
|
2272
|
+
hasMore: false,
|
|
2273
|
+
nextCursor: "",
|
|
2274
|
+
snapshotVersion: "",
|
|
2275
|
+
throughCursor: ""
|
|
2276
|
+
};
|
|
2277
|
+
if (!Array.isArray(data.items) || typeof data.has_more !== "boolean") throw invalid$13();
|
|
2278
|
+
const snapshotVersion = str(data.snapshot_version), throughCursor = str(data.through_cursor), nextCursor = str(data.next_cursor);
|
|
2279
|
+
if (!snapshotVersion || !throughCursor || data.has_more && !nextCursor) throw invalid$13();
|
|
2280
|
+
const key = await this.key();
|
|
2281
|
+
const items = data.items.map((value) => {
|
|
2282
|
+
const raw = objectValue$2(value), ref = objectValue$2(raw.detail_ref);
|
|
2283
|
+
if (!["marked", "unmarked"].includes(String(raw.type)) || typeof raw.is_self !== "boolean" || ref.type !== (raw.type === "marked" ? "speaker" : "candidate") || !str(ref.id) || !str(raw.person_key)) throw invalid$13();
|
|
2284
|
+
if (ref.expected_version !== void 0) str(ref.expected_version);
|
|
2285
|
+
const iv = randomBytes(12), cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
2286
|
+
const encrypted = Buffer.concat([cipher.update(JSON.stringify({
|
|
2287
|
+
userId: session.userId,
|
|
2288
|
+
environment: this.runtime.config.environment,
|
|
2289
|
+
type: ref.type,
|
|
2290
|
+
id: ref.id,
|
|
2291
|
+
expectedVersion: ref.expected_version
|
|
2292
|
+
})), cipher.final()]);
|
|
2293
|
+
return {
|
|
2294
|
+
personKey: str(raw.person_key),
|
|
2295
|
+
type: raw.type,
|
|
2296
|
+
displayName: str(raw.display_name),
|
|
2297
|
+
displayNumber: integer$6(raw.display_number),
|
|
2298
|
+
isSelf: raw.is_self,
|
|
2299
|
+
dayCount: integer$6(raw.day_count),
|
|
2300
|
+
lastSeenAt: integer$6(raw.last_seen_at),
|
|
2301
|
+
detailRef: [
|
|
2302
|
+
prefix$2,
|
|
2303
|
+
iv.toString("base64url"),
|
|
2304
|
+
encrypted.toString("base64url"),
|
|
2305
|
+
cipher.getAuthTag().toString("base64url")
|
|
2306
|
+
].join(".")
|
|
2307
|
+
};
|
|
2308
|
+
});
|
|
2309
|
+
signal?.throwIfAborted();
|
|
2310
|
+
return {
|
|
2311
|
+
...state,
|
|
2312
|
+
items,
|
|
2313
|
+
hasMore: data.has_more,
|
|
2314
|
+
nextCursor,
|
|
2315
|
+
snapshotVersion,
|
|
2316
|
+
throughCursor
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
async seen(input, signal) {
|
|
2320
|
+
const cursor = inputString(input.throughCursor, 4096);
|
|
2321
|
+
if (!cursor) throw new ArkmePluginError("speaker-directory-input-invalid", "缺少目录查看凭据", false);
|
|
2322
|
+
const { data } = await this.request("seen", { through_cursor: cursor }, signal);
|
|
2323
|
+
if (data.success === true) return {
|
|
2324
|
+
success: true,
|
|
2325
|
+
seenVersion: integer$6(data.seen_version)
|
|
2326
|
+
};
|
|
2327
|
+
if (data.state === "disabled" || data.state === "snapshot_expired") return {
|
|
2328
|
+
success: false,
|
|
2329
|
+
state: data.state,
|
|
2330
|
+
retryAfterMs: integer$6(data.retry_after_ms ?? 0)
|
|
2331
|
+
};
|
|
2332
|
+
throw invalid$13();
|
|
2333
|
+
}
|
|
2334
|
+
async open(input, signal) {
|
|
2335
|
+
const session = await this.runtime.requireSession();
|
|
2336
|
+
const ref = await this.readDetailRef(input.detailRef, session.userId);
|
|
2337
|
+
signal?.throwIfAborted();
|
|
2338
|
+
if (ref.type === "candidate") return {
|
|
2339
|
+
type: "candidate",
|
|
2340
|
+
candidateRef: await this.unmarked.directoryCandidateRef(str(ref.id), signal)
|
|
2341
|
+
};
|
|
2342
|
+
if (ref.type !== "speaker") throw invalid$13();
|
|
2343
|
+
return {
|
|
2344
|
+
type: "speaker",
|
|
2345
|
+
speakerRef: await this.recording.directorySpeakerRef(str(ref.id), session.userId),
|
|
2346
|
+
...ref.expectedVersion === void 0 ? {} : { expectedVersion: str(ref.expectedVersion) }
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
async avatars(input, signal) {
|
|
2350
|
+
if (!Array.isArray(input.detailRefs) || input.detailRefs.length > 50) throw new ArkmePluginError("speaker-directory-input-invalid", "头像查询参数无效", false);
|
|
2351
|
+
const session = await this.runtime.requireSession();
|
|
2352
|
+
const requested = await Promise.all([...new Set(input.detailRefs)].map(async (detailRef) => {
|
|
2353
|
+
const ref = await this.readDetailRef(detailRef, session.userId);
|
|
2354
|
+
if (ref.type !== "speaker") throw new ArkmePluginError("speaker-directory-input-invalid", "仅查询已标记人物头像", false);
|
|
2355
|
+
return {
|
|
2356
|
+
detailRef: str(detailRef),
|
|
2357
|
+
id: str(ref.id)
|
|
2358
|
+
};
|
|
2359
|
+
}));
|
|
2360
|
+
signal?.throwIfAborted();
|
|
2361
|
+
const avatars = await this.recording.directorySpeakerAvatars(requested.map((item) => item.id), session.userId, signal);
|
|
2362
|
+
const current = await this.runtime.requireSession();
|
|
2363
|
+
if (current.userId !== session.userId || current.refreshToken !== session.refreshToken) throw new ArkmePluginError("speaker-directory-account-changed", "账号已切换", false, 409);
|
|
2364
|
+
signal?.throwIfAborted();
|
|
2365
|
+
return requested.map(({ detailRef, id }) => ({
|
|
2366
|
+
detailRef,
|
|
2367
|
+
...avatars.has(id) ? { avatarRef: avatars.get(id) } : {}
|
|
2368
|
+
}));
|
|
2369
|
+
}
|
|
2370
|
+
async readDetailRef(value, viewerUserId) {
|
|
2371
|
+
const [kind, iv, bytes, tag, ...extra] = inputString(value, 16384).split(".");
|
|
2372
|
+
let ref;
|
|
2373
|
+
try {
|
|
2374
|
+
if (kind !== prefix$2 || !iv || !bytes || !tag || extra.length) throw new Error("invalid reference");
|
|
2375
|
+
const decipher = createDecipheriv("aes-256-gcm", await this.key(), Buffer.from(iv, "base64url"));
|
|
2376
|
+
decipher.setAuthTag(Buffer.from(tag, "base64url"));
|
|
2377
|
+
ref = objectValue$2(JSON.parse(Buffer.concat([decipher.update(Buffer.from(bytes, "base64url")), decipher.final()]).toString("utf8")));
|
|
2378
|
+
} catch {
|
|
2379
|
+
throw new ArkmePluginError("speaker-directory-detail-invalid", "人物引用已失效,请刷新目录", false, 400);
|
|
2380
|
+
}
|
|
2381
|
+
if (ref.userId !== viewerUserId || ref.environment !== this.runtime.config.environment) throw new ArkmePluginError("speaker-directory-account-mismatch", "人物引用与当前账号不匹配", false, 403);
|
|
2382
|
+
return ref;
|
|
2383
|
+
}
|
|
2384
|
+
async key() {
|
|
2385
|
+
return createHash("sha256").update(await this.runtime.stateStore.uniqueCode()).update(`\0${prefix$2}`).digest();
|
|
2386
|
+
}
|
|
2387
|
+
};
|
|
2388
|
+
//#endregion
|
|
2163
2389
|
//#region src/services/self-role-service.ts
|
|
2164
2390
|
/** Account-scoped role recovery. Existing message senders only request a frozen
|
|
2165
2391
|
* snapshot; role CRUD does not enqueue or resend message content. */
|
|
@@ -92396,6 +92622,46 @@ var RecordingService = class {
|
|
|
92396
92622
|
...numberValue$5(data.retry_after_ms) > 0 ? { retryAfterMs: numberValue$5(data.retry_after_ms) } : {}
|
|
92397
92623
|
};
|
|
92398
92624
|
}
|
|
92625
|
+
/** Called only for an authenticated, Host-verified directory detail_ref. */
|
|
92626
|
+
async directorySpeakerRef(speakerId, viewerUserId) {
|
|
92627
|
+
return this.sealRecordingRefWithKey("arkme-recording-speaker-v1", {
|
|
92628
|
+
version: 1,
|
|
92629
|
+
viewerUserId,
|
|
92630
|
+
target: {
|
|
92631
|
+
kind: "speaker",
|
|
92632
|
+
speakerId
|
|
92633
|
+
}
|
|
92634
|
+
}, await this.recordingRefKey("arkme-recording-speaker-v1"));
|
|
92635
|
+
}
|
|
92636
|
+
/** Enrich only verified directory speakers; do not load contact suggestions or presence stats. */
|
|
92637
|
+
async directorySpeakerAvatars(speakerIds, viewerUserId, signal) {
|
|
92638
|
+
const session = await this.runtime.requireSession();
|
|
92639
|
+
const revision = this.speakerCacheRevision;
|
|
92640
|
+
if (session.userId !== viewerUserId) throw new ArkmePluginError("recording-ref-account-mismatch", "账号已切换", false, 403);
|
|
92641
|
+
if (speakerIds.length === 0) return /* @__PURE__ */ new Map();
|
|
92642
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/get-speaker-ls", {}, session, signal, {
|
|
92643
|
+
lane: "background-read",
|
|
92644
|
+
key: "directory-speaker-avatars",
|
|
92645
|
+
cacheMs: 1e4
|
|
92646
|
+
});
|
|
92647
|
+
if (!Array.isArray(data.spk_ls)) throw new ArkmePluginError("recording-speaker-contract-invalid", "说话人资料响应无效", true, 502);
|
|
92648
|
+
const wanted = new Set(speakerIds);
|
|
92649
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
92650
|
+
for (const value of data.spk_ls) {
|
|
92651
|
+
const raw = objectValue$2(value);
|
|
92652
|
+
const id = stringValue$2(raw.speaker_id ?? raw.id ?? raw.spk_id).trim();
|
|
92653
|
+
const userId = positiveUserId(raw.ref_usr_id ?? raw.ref_user_id ?? raw.user_id);
|
|
92654
|
+
if (wanted.has(id) && userId !== void 0) bindings.set(id, userId);
|
|
92655
|
+
}
|
|
92656
|
+
const profiles = await this.recordingSpeakerProfiles([...new Set(bindings.values())], session, signal, true);
|
|
92657
|
+
await this.assertSpeakerReadCurrent(session, revision, signal);
|
|
92658
|
+
const result = /* @__PURE__ */ new Map();
|
|
92659
|
+
for (const [id, userId] of bindings) {
|
|
92660
|
+
const avatar = profiles.get(userId)?.avatarRef;
|
|
92661
|
+
if (avatar) result.set(id, avatar);
|
|
92662
|
+
}
|
|
92663
|
+
return result;
|
|
92664
|
+
}
|
|
92399
92665
|
/** All-history source identities, authorized by the same account-bound speaker reference as the UI. */
|
|
92400
92666
|
async recordingSpeakerMembers(speakerRef, signal, expectedVersion) {
|
|
92401
92667
|
this.assertWorkbenchEnabled();
|
|
@@ -93462,7 +93728,7 @@ var RecordingService = class {
|
|
|
93462
93728
|
async recordingRefKey(prefix) {
|
|
93463
93729
|
return createHash("sha256").update(await this.runtime.stateStore.uniqueCode()).update(`\0${prefix}`).digest();
|
|
93464
93730
|
}
|
|
93465
|
-
async recordingSpeakerProfiles(userIds, session, signal) {
|
|
93731
|
+
async recordingSpeakerProfiles(userIds, session, signal, propagateFailure = false) {
|
|
93466
93732
|
if (this.profile === void 0) return /* @__PURE__ */ new Map();
|
|
93467
93733
|
if (userIds.length === 0) return /* @__PURE__ */ new Map();
|
|
93468
93734
|
try {
|
|
@@ -93476,7 +93742,8 @@ var RecordingService = class {
|
|
|
93476
93742
|
}];
|
|
93477
93743
|
}));
|
|
93478
93744
|
return new Map(entries);
|
|
93479
|
-
} catch {
|
|
93745
|
+
} catch (error) {
|
|
93746
|
+
if (propagateFailure) throw error;
|
|
93480
93747
|
return /* @__PURE__ */ new Map();
|
|
93481
93748
|
}
|
|
93482
93749
|
}
|
|
@@ -96046,6 +96313,28 @@ var UnmarkedSpeakerService = class {
|
|
|
96046
96313
|
...typeof data.cursor_stale === "boolean" ? { cursorStale: data.cursor_stale } : {}
|
|
96047
96314
|
};
|
|
96048
96315
|
}
|
|
96316
|
+
/** Hydrate on selection, so paginating a large directory never evicts selected references. */
|
|
96317
|
+
async directoryCandidateRef(candidateId, signal) {
|
|
96318
|
+
const session = await this.runtime.requireSession();
|
|
96319
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/unmarked-speakers/detail", { candidate_id: candidateId }, session, signal);
|
|
96320
|
+
signal?.throwIfAborted();
|
|
96321
|
+
const current = await this.runtime.requireSession();
|
|
96322
|
+
if (current.userId !== session.userId || current.refreshToken !== session.refreshToken) throw new ArkmePluginError("unmarked-candidate-ref-account-mismatch", "账号已切换", false, 403);
|
|
96323
|
+
if (data.outcome === "candidate_not_found") throw new ArkmePluginError("unmarked-candidate-not-found", "该人物已变化,请刷新目录", false, 404);
|
|
96324
|
+
const raw = objectValue$2(data.candidate);
|
|
96325
|
+
if (stringValue$2(raw.candidate_id) !== candidateId || !["cross_day", "single_day"].includes(String(raw.status))) throw new ArkmePluginError("unmarked-detail-contract-invalid", "人物详情暂不可用", true, 502);
|
|
96326
|
+
this.pruneRefs();
|
|
96327
|
+
return this.sealCandidateRef(session.userId, {
|
|
96328
|
+
candidateId,
|
|
96329
|
+
status: raw.status,
|
|
96330
|
+
speakerToken: boundedInteger(raw.speaker_display_number) > 0 ? String(raw.speaker_display_number) : stringValue$2(raw.label),
|
|
96331
|
+
appearanceDays: boundedInteger(raw.day_count),
|
|
96332
|
+
validAudioDurationMillis: boundedInteger(raw.total_speech_duration_ms),
|
|
96333
|
+
segmentCount: boundedInteger(raw.segment_count),
|
|
96334
|
+
firstSeenAtMillis: positiveTimestamp(raw.first_seen_at),
|
|
96335
|
+
latestAtMillis: positiveTimestamp(raw.last_seen_at)
|
|
96336
|
+
});
|
|
96337
|
+
}
|
|
96049
96338
|
async markOptions(candidateRef, signal) {
|
|
96050
96339
|
const { session, entry, normalizedRef } = await this.resolveCandidateRef(candidateRef);
|
|
96051
96340
|
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/unmarked-speakers/mark-options", { candidate_id: entry.candidateId }, session, signal, {
|
|
@@ -98731,6 +99020,7 @@ var ArkmeService = class {
|
|
|
98731
99020
|
relatedQuickNote;
|
|
98732
99021
|
contact;
|
|
98733
99022
|
contactDirectory;
|
|
99023
|
+
speakerDirectory;
|
|
98734
99024
|
unmarkedSpeaker;
|
|
98735
99025
|
voiceprint;
|
|
98736
99026
|
userBan;
|
|
@@ -98860,6 +99150,7 @@ var ArkmeService = class {
|
|
|
98860
99150
|
forwardGateway: new OwnerRecordingForwardGateway(this.runtime, this.source, this.realtime, this.chat)
|
|
98861
99151
|
});
|
|
98862
99152
|
this.unmarkedSpeaker = new UnmarkedSpeakerService(this.runtime, this.media);
|
|
99153
|
+
this.speakerDirectory = new SpeakerDirectoryService(this.runtime, this.recording, this.unmarkedSpeaker);
|
|
98863
99154
|
this.contact = new ContactService(this.runtime, this.source, this.profile, this.realtime);
|
|
98864
99155
|
this.voiceprint = new VoiceprintService(this.runtime, this.profile, { resolveRegisteredContactUserId: async (contactRef, session, signal) => await this.contact.resolveRegisteredContactUserId(contactRef, session, signal) });
|
|
98865
99156
|
this.auth = new AuthService(this.runtime, this.profile, {
|
|
@@ -99482,6 +99773,21 @@ var ArkmeService = class {
|
|
|
99482
99773
|
async cachedRecordingSpeakerOptions(signal) {
|
|
99483
99774
|
return await this.recording.cachedRecordingSpeakerOptions(signal);
|
|
99484
99775
|
}
|
|
99776
|
+
async speakerDirectorySummary(input, signal) {
|
|
99777
|
+
return this.speakerDirectory.summary(input, signal);
|
|
99778
|
+
}
|
|
99779
|
+
async speakerDirectoryAvatars(input, signal) {
|
|
99780
|
+
return this.speakerDirectory.avatars(input, signal);
|
|
99781
|
+
}
|
|
99782
|
+
async speakerDirectoryList(input, signal) {
|
|
99783
|
+
return this.speakerDirectory.list(input, signal);
|
|
99784
|
+
}
|
|
99785
|
+
async speakerDirectorySeen(input, signal) {
|
|
99786
|
+
return this.speakerDirectory.seen(input, signal);
|
|
99787
|
+
}
|
|
99788
|
+
async speakerDirectoryOpen(input, signal) {
|
|
99789
|
+
return this.speakerDirectory.open(input, signal);
|
|
99790
|
+
}
|
|
99485
99791
|
async recordingSpeakerOptions(signal) {
|
|
99486
99792
|
return await this.recording.recordingSpeakerOptions(signal);
|
|
99487
99793
|
}
|
|
@@ -83,6 +83,7 @@ export declare class ArkmeService {
|
|
|
83
83
|
private readonly relatedQuickNote;
|
|
84
84
|
private readonly contact;
|
|
85
85
|
private readonly contactDirectory;
|
|
86
|
+
private readonly speakerDirectory;
|
|
86
87
|
private readonly unmarkedSpeaker;
|
|
87
88
|
private readonly voiceprint;
|
|
88
89
|
private readonly userBan;
|
|
@@ -332,6 +333,11 @@ export declare class ArkmeService {
|
|
|
332
333
|
recordingDay(dateStamp: number, signal?: AbortSignal): Promise<ArkmeRecordingDay>;
|
|
333
334
|
recordingPlayback(itemRef: string, signal?: AbortSignal): Promise<ArkmeRecordingPlayback>;
|
|
334
335
|
cachedRecordingSpeakerOptions(signal?: AbortSignal): Promise<ArkmeRecordingSpeakerCandidate[] | null>;
|
|
336
|
+
speakerDirectorySummary(input: Record<string, unknown>, signal?: AbortSignal): Promise<import("./speaker-directory-contract.js").SpeakerDirectorySummary>;
|
|
337
|
+
speakerDirectoryAvatars(input: Record<string, unknown>, signal?: AbortSignal): Promise<import("./speaker-directory-contract.js").SpeakerDirectoryAvatar[]>;
|
|
338
|
+
speakerDirectoryList(input: Record<string, unknown>, signal?: AbortSignal): Promise<import("./speaker-directory-contract.js").SpeakerDirectoryPage>;
|
|
339
|
+
speakerDirectorySeen(input: Record<string, unknown>, signal?: AbortSignal): Promise<import("./speaker-directory-contract.js").SpeakerDirectorySeen>;
|
|
340
|
+
speakerDirectoryOpen(input: Record<string, unknown>, signal?: AbortSignal): Promise<import("./speaker-directory-contract.js").SpeakerDirectoryDetail>;
|
|
335
341
|
recordingSpeakerOptions(signal?: AbortSignal): Promise<ArkmeRecordingSpeakerCandidate[]>;
|
|
336
342
|
recordingSpeakerPresence(signal?: AbortSignal): Promise<ArkmeRecordingSpeakerPresence>;
|
|
337
343
|
recordingSpeakerMembers(speakerRef: string, signal?: AbortSignal, expectedVersion?: string): Promise<import("./types.js").ArkmeRecordingSpeakerMembers>;
|