@senguoyun/dsh-arkme 0.1.13 → 0.1.15
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/consumer-plugin-contract.md +3 -1
- package/docs/extension-market-controls.md +46 -7
- package/docs/gui-production-integration.md +10 -9
- package/lib/client.js +45125 -43501
- package/lib/client.js.map +1 -1
- package/lib/extension-profile-restart-helper.js +2 -2
- package/lib/index.js +1180 -442
- package/lib/{install-store-Sn3Ccxfu.js → install-store-Beau1y-o.js} +12 -6
- package/lib/persistent-extension.js +1 -1
- package/lib/{persistent-runtime-Cki0XC3L.js → persistent-runtime-D_hNgTX3.js} +1479 -249
- package/lib/{plugin-updater-helper-CiPlNqBc.js → plugin-updater-helper-BEYBTOld.js} +5 -5
- package/lib/plugin-updater-helper.js +1 -1
- package/lib/{profile-package-manager-DvzGgkqe.js → profile-package-manager-DwuQu8ZL.js} +91 -3
- package/lib/sdk.js +74 -17
- package/lib/types/arkme-service.d.ts +62 -9
- package/lib/types/arkme-service.d.ts.map +1 -1
- package/lib/types/bot-avatar-ref.d.ts +3 -0
- package/lib/types/bot-avatar-ref.d.ts.map +1 -0
- package/lib/types/client/ArkmeArkoSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeAvatar.d.ts.map +1 -1
- package/lib/types/client/ArkmeBotCreateDialog.d.ts +6 -0
- package/lib/types/client/ArkmeBotCreateDialog.d.ts.map +1 -0
- package/lib/types/client/ArkmeCalendarSurface.d.ts +4 -1
- package/lib/types/client/ArkmeCalendarSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeChatMemberActions.d.ts +48 -0
- package/lib/types/client/ArkmeChatMemberActions.d.ts.map +1 -0
- package/lib/types/client/ArkmeConversationSurface.d.ts +8 -18
- package/lib/types/client/ArkmeConversationSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeExtensionPublishDialog.d.ts.map +1 -1
- package/lib/types/client/ArkmeExtensionReviews.d.ts +4 -2
- package/lib/types/client/ArkmeExtensionReviews.d.ts.map +1 -1
- package/lib/types/client/ArkmeFooterDropdown.d.ts.map +1 -1
- package/lib/types/client/ArkmeGroupChatControls.d.ts +3 -1
- package/lib/types/client/ArkmeGroupChatControls.d.ts.map +1 -1
- package/lib/types/client/ArkmeLogin.d.ts.map +1 -1
- package/lib/types/client/ArkmeMarketplace.d.ts +31 -6
- package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -1
- package/lib/types/client/ArkmeMentionTextarea.d.ts +13 -0
- package/lib/types/client/ArkmeMentionTextarea.d.ts.map +1 -0
- package/lib/types/client/ArkmePersistentShell.d.ts +20 -0
- package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -0
- package/lib/types/client/ArkmeProductNavigation.d.ts +4 -3
- package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
- package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -1
- package/lib/types/client/ArkmeRecordingSurface.d.ts +3 -109
- package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeRichContent.d.ts +7 -1
- package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
- package/lib/types/client/ArkmeSettingsRow.d.ts +1 -1
- package/lib/types/client/ArkmeSettingsRow.d.ts.map +1 -1
- package/lib/types/client/ArkmeSettingsSurface.d.ts +4 -1
- package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeSidebar.d.ts +9 -1
- package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
- package/lib/types/client/ArkmeStartupAuthGate.d.ts +4 -2
- package/lib/types/client/ArkmeStartupAuthGate.d.ts.map +1 -1
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts +9 -9
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
- package/lib/types/client/ArkmeVoiceprintSurface.d.ts +61 -0
- package/lib/types/client/ArkmeVoiceprintSurface.d.ts.map +1 -0
- package/lib/types/client/ArkmeWorldSurface.d.ts +104 -4
- package/lib/types/client/ArkmeWorldSurface.d.ts.map +1 -1
- package/lib/types/client/DeepSeekHarnessSurface.d.ts +6 -0
- package/lib/types/client/DeepSeekHarnessSurface.d.ts.map +1 -0
- package/lib/types/client/api.d.ts +1 -1
- package/lib/types/client/api.d.ts.map +1 -1
- package/lib/types/client/arkme-emoji.d.ts +3 -0
- package/lib/types/client/arkme-emoji.d.ts.map +1 -0
- package/lib/types/client/composer-draft-store.d.ts +18 -0
- package/lib/types/client/composer-draft-store.d.ts.map +1 -1
- package/lib/types/client/composer-focus.d.ts +8 -0
- package/lib/types/client/composer-focus.d.ts.map +1 -0
- package/lib/types/client/conversation-composer-presentation.d.ts +47 -0
- package/lib/types/client/conversation-composer-presentation.d.ts.map +1 -0
- package/lib/types/client/conversation-memory-cache.d.ts +30 -1
- package/lib/types/client/conversation-memory-cache.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +6 -4
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/interwoven-moments.d.ts +2 -1
- package/lib/types/client/interwoven-moments.d.ts.map +1 -1
- package/lib/types/client/marketplace-sort-preference.d.ts +5 -0
- package/lib/types/client/marketplace-sort-preference.d.ts.map +1 -0
- package/lib/types/client/navigation-cache.d.ts +4 -0
- package/lib/types/client/navigation-cache.d.ts.map +1 -1
- package/lib/types/client/provider-instance-runtime.d.ts +20 -0
- package/lib/types/client/provider-instance-runtime.d.ts.map +1 -0
- package/lib/types/client/redesign/ArkmeRootFrame.d.ts +19 -0
- package/lib/types/client/redesign/ArkmeRootFrame.d.ts.map +1 -0
- package/lib/types/client/redesign/ArkmeTaskSurface.d.ts +15 -0
- package/lib/types/client/redesign/ArkmeTaskSurface.d.ts.map +1 -0
- package/lib/types/client/redesign/ArkmeWorkspaceDialog.d.ts +9 -0
- package/lib/types/client/redesign/ArkmeWorkspaceDialog.d.ts.map +1 -0
- package/lib/types/client/redesign/layout-controller.d.ts +17 -0
- package/lib/types/client/redesign/layout-controller.d.ts.map +1 -0
- package/lib/types/client/redesign/styles.d.ts +3 -0
- package/lib/types/client/redesign/styles.d.ts.map +1 -0
- package/lib/types/client/redesign/task-session.d.ts +14 -0
- package/lib/types/client/redesign/task-session.d.ts.map +1 -0
- package/lib/types/client/redesign/theme-presenter.d.ts +10 -0
- package/lib/types/client/redesign/theme-presenter.d.ts.map +1 -0
- package/lib/types/client/related-recordings.d.ts +1 -0
- package/lib/types/client/related-recordings.d.ts.map +1 -1
- package/lib/types/client/slots-contract.d.ts +2 -2
- package/lib/types/client/slots-contract.d.ts.map +1 -1
- package/lib/types/client/source-list.d.ts +4 -0
- package/lib/types/client/source-list.d.ts.map +1 -1
- package/lib/types/client/ui-controller.d.ts +20 -9
- package/lib/types/client/ui-controller.d.ts.map +1 -1
- package/lib/types/client/voiceprint-enrollment-client.d.ts +12 -0
- package/lib/types/client/voiceprint-enrollment-client.d.ts.map +1 -0
- package/lib/types/client/voiceprint-recorder.d.ts +23 -0
- package/lib/types/client/voiceprint-recorder.d.ts.map +1 -0
- package/lib/types/client/world-voiceprint-expectation-copy.d.ts +7 -0
- package/lib/types/client/world-voiceprint-expectation-copy.d.ts.map +1 -0
- package/lib/types/client/world-voiceprint-playback.d.ts +41 -0
- package/lib/types/client/world-voiceprint-playback.d.ts.map +1 -0
- package/lib/types/extensions/install-store.d.ts +1 -0
- package/lib/types/extensions/install-store.d.ts.map +1 -1
- package/lib/types/extensions/manager.d.ts +5 -0
- package/lib/types/extensions/manager.d.ts.map +1 -1
- package/lib/types/extensions/owned-inventory.d.ts +30 -2
- package/lib/types/extensions/owned-inventory.d.ts.map +1 -1
- package/lib/types/extensions/owned-refs.d.ts +1 -0
- package/lib/types/extensions/owned-refs.d.ts.map +1 -1
- package/lib/types/extensions/owned-store.d.ts +9 -0
- package/lib/types/extensions/owned-store.d.ts.map +1 -1
- package/lib/types/extensions/profile-installer.d.ts.map +1 -1
- package/lib/types/extensions/publish-client.d.ts +2 -0
- package/lib/types/extensions/publish-client.d.ts.map +1 -1
- package/lib/types/extensions/publisher-role.d.ts +9 -0
- package/lib/types/extensions/publisher-role.d.ts.map +1 -0
- package/lib/types/extensions/types.d.ts +11 -0
- package/lib/types/extensions/types.d.ts.map +1 -1
- package/lib/types/host-api.d.ts +1 -1
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/local-database.d.ts +1 -0
- package/lib/types/local-database.d.ts.map +1 -1
- package/lib/types/openclaw/secret-provider.d.ts.map +1 -1
- package/lib/types/outgoing-call-assets.d.ts.map +1 -1
- package/lib/types/plugin-update-install-state.d.ts.map +1 -1
- package/lib/types/plugin-update-state.d.ts.map +1 -1
- package/lib/types/plugin-update.d.ts +6 -0
- package/lib/types/plugin-update.d.ts.map +1 -1
- package/lib/types/private-filesystem.d.ts +5 -0
- package/lib/types/private-filesystem.d.ts.map +1 -0
- package/lib/types/recording-presentation.d.ts +2 -2
- package/lib/types/recording-presentation.d.ts.map +1 -1
- package/lib/types/sdk/index.d.ts +31 -8
- package/lib/types/sdk/index.d.ts.map +1 -1
- package/lib/types/services/bot-service.d.ts.map +1 -1
- package/lib/types/services/chat-realtime-service.d.ts +1 -1
- package/lib/types/services/chat-realtime-service.d.ts.map +1 -1
- package/lib/types/services/chat-service.d.ts +21 -2
- package/lib/types/services/chat-service.d.ts.map +1 -1
- package/lib/types/services/contact-service.d.ts +2 -0
- package/lib/types/services/contact-service.d.ts.map +1 -1
- package/lib/types/services/group-service.d.ts.map +1 -1
- package/lib/types/services/record-service.d.ts +2 -1
- package/lib/types/services/record-service.d.ts.map +1 -1
- package/lib/types/services/recording-service.d.ts +2 -4
- package/lib/types/services/recording-service.d.ts.map +1 -1
- package/lib/types/services/service.d.ts +5 -2
- package/lib/types/services/service.d.ts.map +1 -1
- package/lib/types/services/source-service.d.ts.map +1 -1
- package/lib/types/services/voiceprint-service.d.ts +79 -0
- package/lib/types/services/voiceprint-service.d.ts.map +1 -0
- package/lib/types/services/world-service.d.ts +41 -6
- package/lib/types/services/world-service.d.ts.map +1 -1
- package/lib/types/state-store.d.ts.map +1 -1
- package/lib/types/tools/business/bots/create.d.ts.map +1 -1
- package/lib/types/tools/business/conversation/index.d.ts.map +1 -1
- package/lib/types/tools/business/conversation/member-records.d.ts +3 -0
- package/lib/types/tools/business/conversation/member-records.d.ts.map +1 -0
- package/lib/types/tools/business/index.d.ts.map +1 -1
- package/lib/types/tools/business/recordings/index.d.ts +1 -2
- package/lib/types/tools/business/recordings/index.d.ts.map +1 -1
- package/lib/types/tools/business/voiceprint/grants.d.ts +2 -0
- package/lib/types/tools/business/voiceprint/grants.d.ts.map +1 -0
- package/lib/types/tools/business/voiceprint/index.d.ts +3 -0
- package/lib/types/tools/business/voiceprint/index.d.ts.map +1 -0
- package/lib/types/tools/business/voiceprint/invite.d.ts +2 -0
- package/lib/types/tools/business/voiceprint/invite.d.ts.map +1 -0
- package/lib/types/tools/business/voiceprint/recognized-invite.d.ts +2 -0
- package/lib/types/tools/business/voiceprint/recognized-invite.d.ts.map +1 -0
- package/lib/types/tools/business/voiceprint/recognized-people.d.ts +2 -0
- package/lib/types/tools/business/voiceprint/recognized-people.d.ts.map +1 -0
- package/lib/types/tools/business/voiceprint/restore.d.ts +2 -0
- package/lib/types/tools/business/voiceprint/restore.d.ts.map +1 -0
- package/lib/types/tools/business/voiceprint/revoke.d.ts +2 -0
- package/lib/types/tools/business/voiceprint/revoke.d.ts.map +1 -0
- package/lib/types/tools/business/voiceprint/status.d.ts +2 -0
- package/lib/types/tools/business/voiceprint/status.d.ts.map +1 -0
- package/lib/types/tools/business/world/index.d.ts +2 -0
- package/lib/types/tools/business/world/index.d.ts.map +1 -1
- package/lib/types/tools/business/world/user.d.ts +2 -0
- package/lib/types/tools/business/world/user.d.ts.map +1 -0
- package/lib/types/tools/business/world/voiceprint-social-context.d.ts +2 -0
- package/lib/types/tools/business/world/voiceprint-social-context.d.ts.map +1 -0
- package/lib/types/tools/extensions/index.d.ts.map +1 -1
- package/lib/types/tools/ports/conversations.d.ts +10 -1
- package/lib/types/tools/ports/conversations.d.ts.map +1 -1
- package/lib/types/tools/ports/index.d.ts +3 -2
- package/lib/types/tools/ports/index.d.ts.map +1 -1
- package/lib/types/tools/ports/recordings.d.ts +2 -3
- package/lib/types/tools/ports/recordings.d.ts.map +1 -1
- package/lib/types/tools/ports/voiceprint.d.ts +37 -0
- package/lib/types/tools/ports/voiceprint.d.ts.map +1 -0
- package/lib/types/tools/ports/world.d.ts +10 -1
- package/lib/types/tools/ports/world.d.ts.map +1 -1
- package/lib/types/tools/prompts/business.d.ts.map +1 -1
- package/lib/types/tools/registry/registrar.d.ts.map +1 -1
- package/lib/types/types.d.ts +150 -16
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/voiceprint-routes.d.ts +11 -0
- package/lib/types/voiceprint-routes.d.ts.map +1 -0
- package/lib/types/voiceprint-wav.d.ts +6 -0
- package/lib/types/voiceprint-wav.d.ts.map +1 -0
- package/package.json +7 -4
- package/assets/desktop_call/avatar-lin-xiaoman.jpeg +0 -0
- package/assets/desktop_call/avatar-mother.jpg +0 -0
- package/assets/desktop_call/avatar-self.png +0 -0
- package/assets/desktop_call/call-add-linear.svg +0 -13
- package/assets/desktop_call/icon-scan-add-contact.svg +0 -4
- package/assets/desktop_call/profile-circle-linear.svg +0 -11
- package/assets/desktop_call/user-add-linear.svg +0 -15
- package/lib/types/client/ArkmeCallHistorySurface.d.ts +0 -57
- package/lib/types/client/ArkmeCallHistorySurface.d.ts.map +0 -1
- package/lib/types/client/demo-audio.d.ts +0 -2
- package/lib/types/client/demo-audio.d.ts.map +0 -1
- package/lib/types/client/personal-test-edition.d.ts +0 -17
- package/lib/types/client/personal-test-edition.d.ts.map +0 -1
|
@@ -815,15 +815,16 @@ var ServiceRuntime = class {
|
|
|
815
815
|
else signal.addEventListener("abort", abort, { once: true });
|
|
816
816
|
const timeout = setTimeout(() => controller.abort(), this.config.requestTimeoutMs);
|
|
817
817
|
try {
|
|
818
|
+
const multipart = body instanceof FormData;
|
|
818
819
|
const response = await this.fetchImpl(joinUrl(baseUrl, path), {
|
|
819
820
|
method: "POST",
|
|
820
821
|
headers: {
|
|
821
|
-
"Content-Type": "application/json",
|
|
822
|
+
...multipart ? {} : { "Content-Type": "application/json" },
|
|
822
823
|
"Accept-Language": "zh-CN",
|
|
823
824
|
Usersource: "3",
|
|
824
825
|
...bearer === void 0 ? {} : { Authorization: `Bearer ${bearer}` }
|
|
825
826
|
},
|
|
826
|
-
body: JSON.stringify(body),
|
|
827
|
+
body: multipart ? body : JSON.stringify(body),
|
|
827
828
|
signal: controller.signal
|
|
828
829
|
});
|
|
829
830
|
if (response.status === 401 || response.status === 403 && !preserveForbiddenError) throw new ArkmePluginError(`auth-http-${response.status}`, "Arkme 登录凭据已失效", false, response.status);
|
|
@@ -1033,6 +1034,17 @@ var ServiceRuntime = class {
|
|
|
1033
1034
|
return await this.post(this.config.audioBaseUrl, path, body, session.accessToken, [200], signal, false, requestOptions());
|
|
1034
1035
|
}
|
|
1035
1036
|
}
|
|
1037
|
+
async authenticatedAudioMultipartPost(path, body, initialSession, signal, options = {}) {
|
|
1038
|
+
let session = initialSession ?? await this.requireSession();
|
|
1039
|
+
const requestOptions = () => this.authenticatedRequestOptions(session, "audio", "write", options);
|
|
1040
|
+
try {
|
|
1041
|
+
return await this.post(this.config.audioBaseUrl, path, body, session.accessToken, [200], signal, false, requestOptions());
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
if (!(error instanceof ArkmePluginError) || !["auth-http-401", "auth-http-403"].includes(error.code)) throw error;
|
|
1044
|
+
session = await this.refreshAccessToken(session);
|
|
1045
|
+
return await this.post(this.config.audioBaseUrl, path, body, session.accessToken, [200], signal, false, requestOptions());
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1036
1048
|
async authenticatedRelationPost(path, body, initialSession, signal, options = {}) {
|
|
1037
1049
|
let session = initialSession ?? await this.requireSession();
|
|
1038
1050
|
const requestOptions = () => this.authenticatedRequestOptions(session, "relation", "interactive-read", options);
|
|
@@ -2454,6 +2466,13 @@ var AuthService = class {
|
|
|
2454
2466
|
}
|
|
2455
2467
|
};
|
|
2456
2468
|
//#endregion
|
|
2469
|
+
//#region src/bot-avatar-ref.ts
|
|
2470
|
+
const ARKME_BOT_AVATAR_REF_PATTERN = /^file_asset:\/\/[A-Za-z0-9_-]{8,128}$/;
|
|
2471
|
+
/** Bot avatars cross process boundaries only as account-scoped file asset references. */
|
|
2472
|
+
function isArkmeBotAvatarRef(value) {
|
|
2473
|
+
return ARKME_BOT_AVATAR_REF_PATTERN.test(value.trim());
|
|
2474
|
+
}
|
|
2475
|
+
//#endregion
|
|
2457
2476
|
//#region src/secret-value.ts
|
|
2458
2477
|
var SecretValue = class {
|
|
2459
2478
|
#value;
|
|
@@ -2475,10 +2494,10 @@ function booleanValue$10(value) {
|
|
|
2475
2494
|
function listValue$18(value) {
|
|
2476
2495
|
return Array.isArray(value) ? value : [];
|
|
2477
2496
|
}
|
|
2478
|
-
function encodeOpaqueJson$
|
|
2497
|
+
function encodeOpaqueJson$7(value) {
|
|
2479
2498
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
2480
2499
|
}
|
|
2481
|
-
function decodeOpaqueJson$
|
|
2500
|
+
function decodeOpaqueJson$7(value) {
|
|
2482
2501
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
2483
2502
|
}
|
|
2484
2503
|
var BotService = class {
|
|
@@ -2528,6 +2547,7 @@ var BotService = class {
|
|
|
2528
2547
|
const avatar = input.avatar?.trim() ?? "";
|
|
2529
2548
|
if (name === "") throw new ArkmePluginError("bot-name-invalid", "Bot 名称不能为空", false);
|
|
2530
2549
|
if (provider !== "openclaw" && provider !== "webhook") throw new ArkmePluginError("bot-provider-unsupported", "Bot Provider 不受支持", false, 400);
|
|
2550
|
+
if (avatar !== "" && !isArkmeBotAvatarRef(avatar)) throw new ArkmePluginError("bot-avatar-invalid", "Bot 头像引用无效", false, 400);
|
|
2531
2551
|
const session = await this.runtime.requireSession();
|
|
2532
2552
|
let data;
|
|
2533
2553
|
try {
|
|
@@ -2669,7 +2689,7 @@ var BotService = class {
|
|
|
2669
2689
|
};
|
|
2670
2690
|
}
|
|
2671
2691
|
async sealBotRef(userId, botId) {
|
|
2672
|
-
const payload = encodeOpaqueJson$
|
|
2692
|
+
const payload = encodeOpaqueJson$7({
|
|
2673
2693
|
version: 1,
|
|
2674
2694
|
userId,
|
|
2675
2695
|
botId
|
|
@@ -2685,7 +2705,7 @@ var BotService = class {
|
|
|
2685
2705
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("bot-ref-invalid", "Bot 引用无效", false);
|
|
2686
2706
|
let raw;
|
|
2687
2707
|
try {
|
|
2688
|
-
raw = objectValue$1(decodeOpaqueJson$
|
|
2708
|
+
raw = objectValue$1(decodeOpaqueJson$7(payload));
|
|
2689
2709
|
} catch (error) {
|
|
2690
2710
|
throw new ArkmePluginError("bot-ref-invalid", "Bot 引用无效", false, 400, { cause: error });
|
|
2691
2711
|
}
|
|
@@ -3469,7 +3489,7 @@ var ChatRealtimeService = class {
|
|
|
3469
3489
|
lane: "background-read",
|
|
3470
3490
|
key: `projection:tail:${uid}:${String(afterSequence)}:${String(notificationAttempt)}`
|
|
3471
3491
|
});
|
|
3472
|
-
return [uid, await this.projectionReader.chatTimelineItems(data, session)];
|
|
3492
|
+
return [uid, await this.projectionReader.chatTimelineItems(data, session, uid)];
|
|
3473
3493
|
}))).forEach((result, index) => {
|
|
3474
3494
|
const uid = chunk[index]?.[0];
|
|
3475
3495
|
if (uid === void 0) return;
|
|
@@ -3569,6 +3589,18 @@ function booleanValue$8(value) {
|
|
|
3569
3589
|
function listValue$15(value) {
|
|
3570
3590
|
return Array.isArray(value) ? value : [];
|
|
3571
3591
|
}
|
|
3592
|
+
function chatMemberRole$1(value) {
|
|
3593
|
+
if (value === "owner" || value === 1) return "owner";
|
|
3594
|
+
if (value === "admin" || value === 2) return "admin";
|
|
3595
|
+
if (value === "member" || value === "participant" || value === 3) return "member";
|
|
3596
|
+
return "unknown";
|
|
3597
|
+
}
|
|
3598
|
+
function chatMemberStatus$1(value) {
|
|
3599
|
+
if (value === "active" || value === 1) return "active";
|
|
3600
|
+
if (value === "left" || value === 2) return "left";
|
|
3601
|
+
if (value === "removed" || value === 3) return "removed";
|
|
3602
|
+
return "unknown";
|
|
3603
|
+
}
|
|
3572
3604
|
function integerLikeValue(value) {
|
|
3573
3605
|
if (typeof value === "number" && Number.isFinite(value)) return Math.trunc(value);
|
|
3574
3606
|
if (typeof value === "string" && value.trim() !== "") {
|
|
@@ -3612,10 +3644,10 @@ function timelineAgentSource(relation, record, payload) {
|
|
|
3612
3644
|
label: agentSourceLabel(displayName)
|
|
3613
3645
|
};
|
|
3614
3646
|
}
|
|
3615
|
-
function encodeOpaqueJson$
|
|
3647
|
+
function encodeOpaqueJson$6(value) {
|
|
3616
3648
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
3617
3649
|
}
|
|
3618
|
-
function decodeOpaqueJson$
|
|
3650
|
+
function decodeOpaqueJson$6(value) {
|
|
3619
3651
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
3620
3652
|
}
|
|
3621
3653
|
var ChatService = class {
|
|
@@ -3639,6 +3671,65 @@ var ChatService = class {
|
|
|
3639
3671
|
this.aiPolish = aiPolish;
|
|
3640
3672
|
this.realtime = realtime;
|
|
3641
3673
|
}
|
|
3674
|
+
async listSourceMembers(sourceRef, options = {}) {
|
|
3675
|
+
const session = await this.runtime.requireSession();
|
|
3676
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
3677
|
+
if (source.kind !== "group_chat" && source.kind !== "private_chat") throw new ArkmePluginError("chat-members-source-invalid", "仅支持查看群聊或私聊成员", false);
|
|
3678
|
+
const rawItems = await this.rawChatMembers(source.ownerRef, options.activeOnly !== false, session, options.signal);
|
|
3679
|
+
const members = await this.projectChatMembers(source.ownerRef, rawItems, session, options.signal);
|
|
3680
|
+
return {
|
|
3681
|
+
source: await this.source.sourceItem(source),
|
|
3682
|
+
items: members,
|
|
3683
|
+
total: members.length,
|
|
3684
|
+
activeCount: members.filter((item) => item.status === "active").length
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
async sourceMemberRecords(sourceRef, memberRef, mode, options = {}) {
|
|
3688
|
+
const session = await this.runtime.requireSession();
|
|
3689
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
3690
|
+
if (source.kind !== "group_chat" && source.kind !== "private_chat") throw new ArkmePluginError("chat-members-source-invalid", "仅支持查看群聊或私聊成员快记", false);
|
|
3691
|
+
if (mode !== "owner" && mode !== "mentioned") throw new ArkmePluginError("chat-member-record-mode-invalid", "成员快记模式无效", false);
|
|
3692
|
+
const reference = await this.openChatMemberRef(memberRef, session.userId, source.ownerRef);
|
|
3693
|
+
const rawMembers = await this.rawChatMembers(source.ownerRef, false, session, options.signal);
|
|
3694
|
+
const member = (await this.projectChatMembers(source.ownerRef, rawMembers, session, options.signal)).find((item) => item.memberRef === memberRef);
|
|
3695
|
+
if (member === void 0) throw new ArkmePluginError("chat-member-ref-stale", "该成员已不属于当前会话,请刷新后重试", false, 409);
|
|
3696
|
+
const limit = Math.min(50, Math.max(1, Math.trunc(options.limit ?? 30)));
|
|
3697
|
+
const data = await this.runtime.authenticatedChatPost("/api/v1/chats/members/records/page", {
|
|
3698
|
+
chat_session_uid: source.ownerRef,
|
|
3699
|
+
member_user_id: reference.targetUserId,
|
|
3700
|
+
mode: mode === "owner" ? "sent" : "mentioned",
|
|
3701
|
+
before_seq: Math.max(0, Math.trunc(options.beforeSequence ?? 0)),
|
|
3702
|
+
limit
|
|
3703
|
+
}, session, options.signal, {
|
|
3704
|
+
lane: "interactive-read",
|
|
3705
|
+
key: `member-records:${source.ownerRef}:${String(reference.targetUserId)}:${mode}:${String(Math.max(0, Math.trunc(options.beforeSequence ?? 0)))}`,
|
|
3706
|
+
failureCooldownMs: 2e3
|
|
3707
|
+
});
|
|
3708
|
+
const items = await this.projectChatTimelineItems(listValue$15(data.items), source, session, options.signal);
|
|
3709
|
+
const nextBeforeSequence = Math.max(0, Math.trunc(numberValue$16(data.next_before_seq)));
|
|
3710
|
+
return {
|
|
3711
|
+
source: await this.source.sourceItem(source),
|
|
3712
|
+
member,
|
|
3713
|
+
mode,
|
|
3714
|
+
items,
|
|
3715
|
+
hasMore: data.has_more === true,
|
|
3716
|
+
...nextBeforeSequence > 0 ? { nextCursor: { beforeSequence: nextBeforeSequence } } : {}
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
async openPrivateChatFromMember(sourceRef, memberRef, options = {}) {
|
|
3720
|
+
const session = await this.runtime.requireSession();
|
|
3721
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
3722
|
+
if (source.kind !== "group_chat" && source.kind !== "private_chat") throw new ArkmePluginError("chat-members-source-invalid", "仅支持从聊天成员发起私聊", false);
|
|
3723
|
+
const reference = await this.openChatMemberRef(memberRef, session.userId, source.ownerRef);
|
|
3724
|
+
if (reference.targetUserId === session.userId) throw new ArkmePluginError("private-chat-self-invalid", "不能给自己发起私聊", false, 409);
|
|
3725
|
+
const rawMember = (await this.rawChatMembers(source.ownerRef, true, session, options.signal)).find((item) => Math.trunc(numberValue$16(item.user_id)) === reference.targetUserId);
|
|
3726
|
+
if (rawMember === void 0) throw new ArkmePluginError("chat-member-ref-stale", "该成员已不属于当前会话,请刷新后重试", false, 409);
|
|
3727
|
+
const displayName = stringValue$1(rawMember.remark).trim() || stringValue$1(rawMember.display_name_snapshot).trim() || "群成员";
|
|
3728
|
+
return await this.openPrivateChatFromUser(reference.targetUserId, {
|
|
3729
|
+
displayName,
|
|
3730
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
3731
|
+
});
|
|
3732
|
+
}
|
|
3642
3733
|
async openPrivateChatFromUser(peerUserId, options = {}) {
|
|
3643
3734
|
const session = await this.runtime.requireSession();
|
|
3644
3735
|
if (!Number.isSafeInteger(peerUserId) || peerUserId <= 0) throw new ArkmePluginError("private-chat-peer-invalid", "私聊用户参数无效", false);
|
|
@@ -3763,56 +3854,7 @@ var ChatService = class {
|
|
|
3763
3854
|
key: `timeline:${source.ownerRef}:${String(Math.max(0, Math.trunc(options.cursor?.beforeSequence ?? 0)))}:${String(limit)}`,
|
|
3764
3855
|
failureCooldownMs: 2e3
|
|
3765
3856
|
});
|
|
3766
|
-
const items =
|
|
3767
|
-
const senderUserIdByIndex = /* @__PURE__ */ new Map();
|
|
3768
|
-
const messageRefSigningKey = source.kind === "group_chat" ? await this.runtime.stateStore.uniqueCode() : void 0;
|
|
3769
|
-
for (const raw of listValue$15(data.items)) {
|
|
3770
|
-
const item = objectValue$1(raw);
|
|
3771
|
-
const relation = objectValue$1(item.relation);
|
|
3772
|
-
const record = objectValue$1(item.record);
|
|
3773
|
-
const payload = objectValue$1(record.payload);
|
|
3774
|
-
const recordStatus = numberValue$16(record.status);
|
|
3775
|
-
if (recordStatus !== 1) continue;
|
|
3776
|
-
const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
|
|
3777
|
-
if (uid === "") continue;
|
|
3778
|
-
const relationUid = stringValue$1(relation.rel_uid).trim();
|
|
3779
|
-
const senderUserId = numberValue$16(relation.sender_user_id);
|
|
3780
|
-
const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
|
|
3781
|
-
const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
|
|
3782
|
-
const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
|
|
3783
|
-
const rawAgentSource = timelineAgentSource(relation, record, payload);
|
|
3784
|
-
const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
|
|
3785
|
-
const itemIndex = items.push({
|
|
3786
|
-
itemUid: uid,
|
|
3787
|
-
...source.kind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : { messageRef: this.sealMessageRef(session.userId, source.ownerRef, relationUid, messageRefSigningKey) },
|
|
3788
|
-
senderName: stringValue$1(relation.display_name_snapshot).trim() || "Arkme用户",
|
|
3789
|
-
...agentSource === void 0 ? {} : { agentSource },
|
|
3790
|
-
isMe: senderUserId === session.userId,
|
|
3791
|
-
sendAtMillis,
|
|
3792
|
-
title: stringValue$1(payload.title),
|
|
3793
|
-
textContent: stringValue$1(payload.text_content),
|
|
3794
|
-
status: recordStatus,
|
|
3795
|
-
sequence: numberValue$16(relation.seq),
|
|
3796
|
-
...numberValue$16(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$16(record.version ?? payload.version) } : {},
|
|
3797
|
-
...aiPolish === void 0 ? {} : { aiPolish },
|
|
3798
|
-
...forwardRecords === void 0 ? {} : { forwardRecords },
|
|
3799
|
-
templateKind: numberValue$16(payload.template_kind),
|
|
3800
|
-
displayKind: numberValue$16(payload.display_kind),
|
|
3801
|
-
version: numberValue$16(payload.version ?? record.version),
|
|
3802
|
-
updateAtMillis: numberValue$16(payload.update_at ?? record.update_at),
|
|
3803
|
-
recordDurationMillis: numberValue$16(payload.record_duration_millis),
|
|
3804
|
-
editDurationMillis: numberValue$16(payload.edit_duration_millis),
|
|
3805
|
-
contentBlocks: this.media.richContentBlocks(item, session.userId)
|
|
3806
|
-
}) - 1;
|
|
3807
|
-
if (Number.isSafeInteger(senderUserId) && senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
|
|
3808
|
-
}
|
|
3809
|
-
try {
|
|
3810
|
-
const profiles = await this.profile.publicProfilesByUserIds([...new Set(senderUserIdByIndex.values())], session, options.signal);
|
|
3811
|
-
for (const [index, senderUserId] of senderUserIdByIndex) {
|
|
3812
|
-
if (!profiles.has(senderUserId) || items[index] === void 0) continue;
|
|
3813
|
-
items[index].avatarRef = await this.profile.sealProfileImageRef(session.userId, senderUserId);
|
|
3814
|
-
}
|
|
3815
|
-
} catch {}
|
|
3857
|
+
const items = await this.projectChatTimelineItems(listValue$15(data.items), source, session, options.signal);
|
|
3816
3858
|
const beforeSequence = numberValue$16(data.next_before_seq);
|
|
3817
3859
|
let aiPolishSettings;
|
|
3818
3860
|
let aiPolishNotices;
|
|
@@ -3863,6 +3905,7 @@ var ChatService = class {
|
|
|
3863
3905
|
const recordUid = options.recordUid?.trim() || randomUUID();
|
|
3864
3906
|
if (source.kind === "send_to_self" || source.kind === "default_category") {
|
|
3865
3907
|
const result = await this.record.createTextForConversation(recordUid, text);
|
|
3908
|
+
if (result.localState !== "failed") this.source.invalidateSourceListCache(session.userId, "send_to_self");
|
|
3866
3909
|
return {
|
|
3867
3910
|
sourceRef,
|
|
3868
3911
|
itemUid: result.recordUid,
|
|
@@ -3880,6 +3923,7 @@ var ChatService = class {
|
|
|
3880
3923
|
text_content: text,
|
|
3881
3924
|
send_at: Date.now()
|
|
3882
3925
|
}, session);
|
|
3926
|
+
this.source.invalidateSourceListCache(session.userId, "send_to_self");
|
|
3883
3927
|
return {
|
|
3884
3928
|
sourceRef,
|
|
3885
3929
|
itemUid: stringValue$1(result.record_uid).trim() || recordUid,
|
|
@@ -3890,16 +3934,68 @@ var ChatService = class {
|
|
|
3890
3934
|
const relationUid = options.relationUid?.trim() || randomUUID();
|
|
3891
3935
|
const agentAuthored = isAgentAuthoredChatSend(options);
|
|
3892
3936
|
let sent;
|
|
3893
|
-
if (source.kind === "group_chat")
|
|
3894
|
-
|
|
3895
|
-
agentAuthored
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3937
|
+
if (source.kind === "group_chat") {
|
|
3938
|
+
if ((options.botRefs?.length ?? 0) > 0 && (options.humanMentions?.length ?? 0) > 0) throw new ArkmePluginError("mention-kind-conflict", "单条消息暂不支持同时新增真人和 Bot mention", false);
|
|
3939
|
+
if (options.botRefs !== void 0 && options.botRefs.length > 0) sent = await this.groupBotMentionSend(sourceRef, source, text, options.botRefs, recordUid, relationUid, session, options.signal, { agentAuthored });
|
|
3940
|
+
else if (options.humanMentions !== void 0 && options.humanMentions.length > 0) {
|
|
3941
|
+
const contentPayload = await this.humanMentionContentPayload(source, textContent, text, options.humanMentions, session, options.signal);
|
|
3942
|
+
sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, contentPayload, options.signal, { agentAuthored });
|
|
3943
|
+
} else sent = await this.aiPolish.sendGroupSourceTextWithAiPolish(sourceRef, source.ownerRef, text, recordUid, relationUid, session, {
|
|
3944
|
+
agentAuthored,
|
|
3945
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
3946
|
+
});
|
|
3947
|
+
} else if ((options.botRefs?.length ?? 0) > 0 || (options.humanMentions?.length ?? 0) > 0) throw new ArkmePluginError("mention-group-required", "Mention 只能发送到群聊", false);
|
|
3899
3948
|
else sent = await this.sendChatSourceTextRaw(sourceRef, source.ownerRef, text, recordUid, relationUid, session, void 0, void 0, options.signal, { agentAuthored });
|
|
3900
3949
|
if (agentAuthored && sent.localState === "synced") this.arko.agentSourceDisplayName(session);
|
|
3901
3950
|
return sent;
|
|
3902
3951
|
}
|
|
3952
|
+
async humanMentionContentPayload(source, rawText, normalizedText, inputs, session, signal) {
|
|
3953
|
+
if (inputs.length > 50) throw new ArkmePluginError("human-mention-invalid", "单条消息 mention 数量过多", false);
|
|
3954
|
+
const leadingTrim = rawText.length - rawText.trimStart().length;
|
|
3955
|
+
const rawMembers = await this.rawChatMembers(source.ownerRef, true, session, signal);
|
|
3956
|
+
const membersByUserId = new Map(rawMembers.map((item) => [Math.trunc(numberValue$16(item.user_id)), item]));
|
|
3957
|
+
const mentions = [];
|
|
3958
|
+
for (const input of [...inputs].sort((left, right) => left.startIndex - right.startIndex)) {
|
|
3959
|
+
const memberRef = input.memberRef.trim();
|
|
3960
|
+
const startIndex = Math.trunc(input.startIndex) - leadingTrim;
|
|
3961
|
+
const length = Math.trunc(input.length);
|
|
3962
|
+
if (memberRef === "" || startIndex < 0 || length < 2 || startIndex + length > normalizedText.length) throw new ArkmePluginError("human-mention-invalid", "真人 mention 引用或文本区间无效", false);
|
|
3963
|
+
const reference = await this.openChatMemberRef(memberRef, session.userId, source.ownerRef);
|
|
3964
|
+
if (reference.targetUserId === session.userId) throw new ArkmePluginError("human-mention-self-invalid", "不能 @ 自己", false);
|
|
3965
|
+
const rawMember = membersByUserId.get(reference.targetUserId);
|
|
3966
|
+
if (rawMember === void 0) throw new ArkmePluginError("chat-member-ref-stale", "被 @ 成员已不在当前群聊", false, 409);
|
|
3967
|
+
const visible = normalizedText.slice(startIndex, startIndex + length);
|
|
3968
|
+
const displayName = visible.startsWith("@") ? visible.slice(1).trim() : "";
|
|
3969
|
+
const expectedNames = [stringValue$1(rawMember.remark).trim(), stringValue$1(rawMember.display_name_snapshot).trim()].filter((value) => value !== "");
|
|
3970
|
+
if (displayName === "" || !expectedNames.includes(displayName)) throw new ArkmePluginError("human-mention-text-mismatch", "真人 mention 文本已变化,请重新选择成员", false, 409);
|
|
3971
|
+
const previous = mentions.at(-1);
|
|
3972
|
+
if (previous !== void 0 && previous.start_index + previous.length > startIndex) throw new ArkmePluginError("human-mention-overlap", "真人 mention 文本区间重叠", false);
|
|
3973
|
+
mentions.push({
|
|
3974
|
+
user_id: reference.targetUserId,
|
|
3975
|
+
display_name_snapshot: displayName,
|
|
3976
|
+
start_index: startIndex,
|
|
3977
|
+
length
|
|
3978
|
+
});
|
|
3979
|
+
}
|
|
3980
|
+
const checksumInput = {
|
|
3981
|
+
text_content: normalizedText,
|
|
3982
|
+
human_mentions: mentions.map((mention) => ({
|
|
3983
|
+
user_id: mention.user_id,
|
|
3984
|
+
start_index: mention.start_index,
|
|
3985
|
+
length: mention.length
|
|
3986
|
+
})),
|
|
3987
|
+
bot_mentions: []
|
|
3988
|
+
};
|
|
3989
|
+
return {
|
|
3990
|
+
payload_kind: 2,
|
|
3991
|
+
schema_version: 1,
|
|
3992
|
+
mention_metadata: {
|
|
3993
|
+
schema_version: 1,
|
|
3994
|
+
source_checksum: createHash("sha256").update(JSON.stringify(checksumInput)).digest("hex"),
|
|
3995
|
+
human_mentions: mentions
|
|
3996
|
+
}
|
|
3997
|
+
};
|
|
3998
|
+
}
|
|
3903
3999
|
async groupBotMentionSend(sourceRef, source, text, botRefs, recordUid, relationUid, session, signal, options = {}) {
|
|
3904
4000
|
const uniqueRefs = new Set(botRefs.map((ref) => ref.trim()));
|
|
3905
4001
|
if (uniqueRefs.has("") || uniqueRefs.size !== botRefs.length) throw new ArkmePluginError("bot-mention-ref-invalid", "Bot mention 引用为空或重复", false);
|
|
@@ -3998,7 +4094,7 @@ var ChatService = class {
|
|
|
3998
4094
|
const recordUid = options.recordUid?.trim() || randomUUID();
|
|
3999
4095
|
const relationUid = options.relationUid?.trim() || randomUUID();
|
|
4000
4096
|
const templateKind = longArticle ? 1 : assets.length === 0 ? 1 : 2;
|
|
4001
|
-
const
|
|
4097
|
+
const mediaContentPayload = assets.length === 0 ? void 0 : {
|
|
4002
4098
|
payload_kind: 2,
|
|
4003
4099
|
schema_version: 1,
|
|
4004
4100
|
text_state: textContent === "" ? 3 : 1,
|
|
@@ -4010,6 +4106,16 @@ var ChatService = class {
|
|
|
4010
4106
|
file_name: asset.fileName
|
|
4011
4107
|
}))
|
|
4012
4108
|
};
|
|
4109
|
+
let contentPayload = mediaContentPayload;
|
|
4110
|
+
if ((input.humanMentions?.length ?? 0) > 0) {
|
|
4111
|
+
if (source.kind !== "group_chat") throw new ArkmePluginError("mention-group-required", "Mention 只能发送到群聊", false);
|
|
4112
|
+
if (longArticle) throw new ArkmePluginError("human-mention-rich-invalid", "长文暂不支持成员 mention", false);
|
|
4113
|
+
const mentionPayload = await this.humanMentionContentPayload(source, input.textContent ?? "", textContent, input.humanMentions, session);
|
|
4114
|
+
contentPayload = {
|
|
4115
|
+
...mediaContentPayload ?? {},
|
|
4116
|
+
...mentionPayload
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4013
4119
|
const commonBody = {
|
|
4014
4120
|
record_uid: recordUid,
|
|
4015
4121
|
template_kind: templateKind,
|
|
@@ -4022,6 +4128,7 @@ var ChatService = class {
|
|
|
4022
4128
|
};
|
|
4023
4129
|
if (source.kind === "send_to_self" || source.kind === "default_category") {
|
|
4024
4130
|
const result = await this.runtime.authenticatedPost("/api/v1/records/create", commonBody, session);
|
|
4131
|
+
this.source.invalidateSourceListCache(session.userId, "send_to_self");
|
|
4025
4132
|
return {
|
|
4026
4133
|
sourceRef,
|
|
4027
4134
|
itemUid: stringValue$1(result.record_uid).trim() || recordUid,
|
|
@@ -4034,6 +4141,7 @@ var ChatService = class {
|
|
|
4034
4141
|
topic_uid: source.ownerRef,
|
|
4035
4142
|
...commonBody
|
|
4036
4143
|
}, session);
|
|
4144
|
+
this.source.invalidateSourceListCache(session.userId, "send_to_self");
|
|
4037
4145
|
return {
|
|
4038
4146
|
sourceRef,
|
|
4039
4147
|
itemUid: stringValue$1(result.record_uid).trim() || recordUid,
|
|
@@ -4157,7 +4265,7 @@ var ChatService = class {
|
|
|
4157
4265
|
async chatSourceFromBundle(bundle, session, cached, timelineItems) {
|
|
4158
4266
|
return await this.source.chatSourceFromBundle(bundle, session, cached, timelineItems);
|
|
4159
4267
|
}
|
|
4160
|
-
async chatTimelineItems(data, session) {
|
|
4268
|
+
async chatTimelineItems(data, session, chatSessionUid) {
|
|
4161
4269
|
const items = [];
|
|
4162
4270
|
for (const raw of listValue$15(data.items)) {
|
|
4163
4271
|
const item = objectValue$1(raw);
|
|
@@ -4174,6 +4282,7 @@ var ChatService = class {
|
|
|
4174
4282
|
const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
|
|
4175
4283
|
items.push({
|
|
4176
4284
|
itemUid: uid,
|
|
4285
|
+
...senderUserId > 0 ? { memberRef: await this.sealChatMemberRef(session.userId, chatSessionUid, senderUserId) } : {},
|
|
4177
4286
|
senderName: stringValue$1(relation.display_name_snapshot).trim() || "Arkme用户",
|
|
4178
4287
|
...agentSource === void 0 ? {} : { agentSource },
|
|
4179
4288
|
...senderUserId > 0 ? { avatarRef: await this.profile.sealProfileImageRef(session.userId, senderUserId) } : {},
|
|
@@ -4241,8 +4350,143 @@ var ChatService = class {
|
|
|
4241
4350
|
items: projectedItems
|
|
4242
4351
|
};
|
|
4243
4352
|
}
|
|
4353
|
+
async rawChatMembers(chatSessionUid, activeOnly, session, signal) {
|
|
4354
|
+
return listValue$15((await this.runtime.authenticatedChatPost("/api/v1/chats/members/list", {
|
|
4355
|
+
chat_session_uid: chatSessionUid,
|
|
4356
|
+
active_only: activeOnly
|
|
4357
|
+
}, session, signal)).items).map(objectValue$1);
|
|
4358
|
+
}
|
|
4359
|
+
async projectChatMembers(chatSessionUid, rawItems, session, signal) {
|
|
4360
|
+
const userIds = rawItems.map((item) => Math.trunc(numberValue$16(item.user_id))).filter((userId) => Number.isSafeInteger(userId) && userId > 0);
|
|
4361
|
+
const profiles = await this.profile.publicProfileSummariesByUserIds(userIds, session, signal).catch(() => /* @__PURE__ */ new Map());
|
|
4362
|
+
const members = [];
|
|
4363
|
+
for (const item of rawItems) {
|
|
4364
|
+
const userId = Math.trunc(numberValue$16(item.user_id));
|
|
4365
|
+
if (!Number.isSafeInteger(userId) || userId <= 0) continue;
|
|
4366
|
+
const profile = profiles.get(userId);
|
|
4367
|
+
const remarkName = stringValue$1(item.remark).trim();
|
|
4368
|
+
const memberName = stringValue$1(item.display_name_snapshot).trim();
|
|
4369
|
+
const profileDisplayName = profile?.displayName.trim() ?? "";
|
|
4370
|
+
const publicDisplayName = profileDisplayName === `用户 ${String(userId)}` ? "" : profileDisplayName;
|
|
4371
|
+
const displayName = [
|
|
4372
|
+
remarkName,
|
|
4373
|
+
memberName,
|
|
4374
|
+
publicDisplayName
|
|
4375
|
+
].find((value) => value !== "" && value !== "成员" && value !== "群成员") ?? "群成员";
|
|
4376
|
+
const secondaryName = [
|
|
4377
|
+
memberName,
|
|
4378
|
+
publicDisplayName,
|
|
4379
|
+
remarkName
|
|
4380
|
+
].find((value) => value !== "" && value !== "成员" && value !== "群成员" && value !== displayName) ?? "";
|
|
4381
|
+
const role = chatMemberRole$1(item.role);
|
|
4382
|
+
const status = chatMemberStatus$1(item.status);
|
|
4383
|
+
const extra = objectValue$1(item.extra);
|
|
4384
|
+
members.push({
|
|
4385
|
+
memberRef: await this.sealChatMemberRef(session.userId, chatSessionUid, userId),
|
|
4386
|
+
displayName,
|
|
4387
|
+
...memberName === "" ? {} : { memberName },
|
|
4388
|
+
...secondaryName === "" ? {} : { secondaryName },
|
|
4389
|
+
...profile?.avatarUrl === void 0 ? {} : { avatarRef: await this.profile.sealProfileImageRef(session.userId, userId) },
|
|
4390
|
+
role,
|
|
4391
|
+
status,
|
|
4392
|
+
isSelf: userId === session.userId,
|
|
4393
|
+
isOwner: role === "owner",
|
|
4394
|
+
joinedAtMillis: Math.max(0, Math.trunc(numberValue$16(item.join_at))),
|
|
4395
|
+
recordCount: Math.max(0, Math.trunc(numberValue$16(extra.record_count))),
|
|
4396
|
+
mentionCount: Math.max(0, Math.trunc(numberValue$16(extra.mention_count)))
|
|
4397
|
+
});
|
|
4398
|
+
}
|
|
4399
|
+
const roleRank = (role) => role === "owner" ? 0 : role === "admin" ? 1 : role === "member" ? 2 : 3;
|
|
4400
|
+
members.sort((left, right) => roleRank(left.role) - roleRank(right.role) || (right.status === "active" ? 1 : 0) - (left.status === "active" ? 1 : 0) || left.joinedAtMillis - right.joinedAtMillis || left.displayName.localeCompare(right.displayName));
|
|
4401
|
+
return members;
|
|
4402
|
+
}
|
|
4403
|
+
async projectChatTimelineItems(rawItems, source, session, signal) {
|
|
4404
|
+
const items = [];
|
|
4405
|
+
const senderUserIdByIndex = /* @__PURE__ */ new Map();
|
|
4406
|
+
const signingKey = await this.runtime.stateStore.uniqueCode();
|
|
4407
|
+
for (const raw of rawItems) {
|
|
4408
|
+
const item = objectValue$1(raw);
|
|
4409
|
+
const relation = objectValue$1(item.relation);
|
|
4410
|
+
const record = objectValue$1(item.record);
|
|
4411
|
+
const payload = objectValue$1(record.payload);
|
|
4412
|
+
const recordStatus = numberValue$16(record.status);
|
|
4413
|
+
if (recordStatus !== 1) continue;
|
|
4414
|
+
const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
|
|
4415
|
+
if (uid === "") continue;
|
|
4416
|
+
const relationUid = stringValue$1(relation.rel_uid).trim();
|
|
4417
|
+
const senderUserId = Math.trunc(numberValue$16(relation.sender_user_id));
|
|
4418
|
+
const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
|
|
4419
|
+
const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
|
|
4420
|
+
const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
|
|
4421
|
+
const rawAgentSource = timelineAgentSource(relation, record, payload);
|
|
4422
|
+
const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
|
|
4423
|
+
const itemIndex = items.push({
|
|
4424
|
+
itemUid: uid,
|
|
4425
|
+
...source.kind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : { messageRef: this.sealMessageRef(session.userId, source.ownerRef, relationUid, signingKey) },
|
|
4426
|
+
...senderUserId > 0 ? { memberRef: await this.sealChatMemberRef(session.userId, source.ownerRef, senderUserId) } : {},
|
|
4427
|
+
senderName: stringValue$1(relation.display_name_snapshot).trim() || "Arkme用户",
|
|
4428
|
+
...agentSource === void 0 ? {} : { agentSource },
|
|
4429
|
+
isMe: senderUserId === session.userId,
|
|
4430
|
+
sendAtMillis,
|
|
4431
|
+
title: stringValue$1(payload.title),
|
|
4432
|
+
textContent: stringValue$1(payload.text_content),
|
|
4433
|
+
status: recordStatus,
|
|
4434
|
+
sequence: numberValue$16(relation.seq),
|
|
4435
|
+
...numberValue$16(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$16(record.version ?? payload.version) } : {},
|
|
4436
|
+
...aiPolish === void 0 ? {} : { aiPolish },
|
|
4437
|
+
templateKind: numberValue$16(payload.template_kind),
|
|
4438
|
+
displayKind: numberValue$16(payload.display_kind),
|
|
4439
|
+
version: numberValue$16(payload.version ?? record.version),
|
|
4440
|
+
updateAtMillis: numberValue$16(payload.update_at ?? record.update_at),
|
|
4441
|
+
recordDurationMillis: numberValue$16(payload.record_duration_millis),
|
|
4442
|
+
editDurationMillis: numberValue$16(payload.edit_duration_millis),
|
|
4443
|
+
contentBlocks: this.media.richContentBlocks(item, session.userId),
|
|
4444
|
+
...forwardRecords === void 0 ? {} : { forwardRecords }
|
|
4445
|
+
}) - 1;
|
|
4446
|
+
if (senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
|
|
4447
|
+
}
|
|
4448
|
+
try {
|
|
4449
|
+
const profiles = await this.profile.publicProfilesByUserIds([...new Set(senderUserIdByIndex.values())], session, signal);
|
|
4450
|
+
for (const [index, senderUserId] of senderUserIdByIndex) {
|
|
4451
|
+
if (!profiles.has(senderUserId) || items[index] === void 0) continue;
|
|
4452
|
+
items[index].avatarRef = await this.profile.sealProfileImageRef(session.userId, senderUserId);
|
|
4453
|
+
}
|
|
4454
|
+
} catch {}
|
|
4455
|
+
return items;
|
|
4456
|
+
}
|
|
4457
|
+
async sealChatMemberRef(viewerUserId, chatSessionUid, targetUserId) {
|
|
4458
|
+
const encoded = encodeOpaqueJson$6({
|
|
4459
|
+
version: 1,
|
|
4460
|
+
viewerUserId,
|
|
4461
|
+
chatSessionUid,
|
|
4462
|
+
targetUserId
|
|
4463
|
+
});
|
|
4464
|
+
return `arkme-chat-member-v1.${encoded}.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest("base64url")}`;
|
|
4465
|
+
}
|
|
4466
|
+
async openChatMemberRef(memberRef, expectedViewerUserId, expectedChatSessionUid) {
|
|
4467
|
+
const parts = memberRef.trim().split(".");
|
|
4468
|
+
if (parts.length !== 3 || parts[0] !== "arkme-chat-member-v1") throw new ArkmePluginError("chat-member-ref-invalid", "聊天成员引用无效", false);
|
|
4469
|
+
const encoded = parts[1] ?? "";
|
|
4470
|
+
const supplied = Buffer.from(parts[2] ?? "", "base64url");
|
|
4471
|
+
const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest();
|
|
4472
|
+
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("chat-member-ref-invalid", "聊天成员引用无效", false);
|
|
4473
|
+
let raw;
|
|
4474
|
+
try {
|
|
4475
|
+
raw = objectValue$1(decodeOpaqueJson$6(encoded));
|
|
4476
|
+
} catch (error) {
|
|
4477
|
+
throw new ArkmePluginError("chat-member-ref-invalid", "聊天成员引用无效", false, 400, { cause: error });
|
|
4478
|
+
}
|
|
4479
|
+
const result = {
|
|
4480
|
+
version: 1,
|
|
4481
|
+
viewerUserId: Math.trunc(numberValue$16(raw.viewerUserId)),
|
|
4482
|
+
chatSessionUid: stringValue$1(raw.chatSessionUid).trim(),
|
|
4483
|
+
targetUserId: Math.trunc(numberValue$16(raw.targetUserId))
|
|
4484
|
+
};
|
|
4485
|
+
if (raw.version !== 1 || result.viewerUserId !== expectedViewerUserId || result.chatSessionUid !== expectedChatSessionUid || !Number.isSafeInteger(result.targetUserId) || result.targetUserId <= 0) throw new ArkmePluginError("chat-member-ref-invalid", "聊天成员引用与当前账号或会话不匹配", false, 403);
|
|
4486
|
+
return result;
|
|
4487
|
+
}
|
|
4244
4488
|
sealMessageRef(userId, chatSessionUid, relationUid, signingKey) {
|
|
4245
|
-
const payload = encodeOpaqueJson$
|
|
4489
|
+
const payload = encodeOpaqueJson$6({
|
|
4246
4490
|
version: 1,
|
|
4247
4491
|
userId,
|
|
4248
4492
|
chatSessionUid,
|
|
@@ -4259,7 +4503,7 @@ var ChatService = class {
|
|
|
4259
4503
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("message-ref-invalid", "Arkme 消息引用无效", false);
|
|
4260
4504
|
let parsed;
|
|
4261
4505
|
try {
|
|
4262
|
-
parsed = objectValue$1(decodeOpaqueJson$
|
|
4506
|
+
parsed = objectValue$1(decodeOpaqueJson$6(payload));
|
|
4263
4507
|
} catch (error) {
|
|
4264
4508
|
throw new ArkmePluginError("message-ref-invalid", "Arkme 消息引用无效", false, 400, { cause: error });
|
|
4265
4509
|
}
|
|
@@ -4390,6 +4634,19 @@ var ContactService = class {
|
|
|
4390
4634
|
source
|
|
4391
4635
|
};
|
|
4392
4636
|
}
|
|
4637
|
+
async resolveRegisteredContactUserId(contactRef, session, signal) {
|
|
4638
|
+
this.prune();
|
|
4639
|
+
const normalizedContactRef = contactRef.trim();
|
|
4640
|
+
if (!CONTACT_REF_PATTERN.test(normalizedContactRef)) throw new ArkmePluginError("contact-ref-invalid", "联系人搜索引用无效,请重新搜索", false);
|
|
4641
|
+
const cached = this.candidates.get(normalizedContactRef);
|
|
4642
|
+
if (cached === void 0 || cached.userId !== session.userId || cached.expiresAtMillis <= Date.now()) throw new ArkmePluginError("contact-ref-expired", "联系人搜索结果已过期,请重新搜索", false, 410);
|
|
4643
|
+
const candidate = await this.lookup(cached.normalized, session, signal, normalizedContactRef);
|
|
4644
|
+
if (candidate.targetUserId === void 0 || !candidate.result.registered || candidate.result.isSelf || candidate.targetUserId !== cached.targetUserId) {
|
|
4645
|
+
this.candidates.delete(normalizedContactRef);
|
|
4646
|
+
throw new ArkmePluginError("contact-candidate-changed", "联系人身份已发生变化,请重新搜索后确认", false, 409);
|
|
4647
|
+
}
|
|
4648
|
+
return candidate.targetUserId;
|
|
4649
|
+
}
|
|
4393
4650
|
async lookup(normalized, session, signal, contactRef = `arkme-contact-v1.${randomUUID()}`) {
|
|
4394
4651
|
const data = await this.runtime.authenticatedAuthPost("/api/v1/auth/search-contact-account", {
|
|
4395
4652
|
identifier_type: normalized.identifierType,
|
|
@@ -5196,10 +5453,10 @@ function numberValue$12(value) {
|
|
|
5196
5453
|
function listValue$12(value) {
|
|
5197
5454
|
return Array.isArray(value) ? value : [];
|
|
5198
5455
|
}
|
|
5199
|
-
function encodeOpaqueJson$
|
|
5456
|
+
function encodeOpaqueJson$5(value) {
|
|
5200
5457
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
5201
5458
|
}
|
|
5202
|
-
function decodeOpaqueJson$
|
|
5459
|
+
function decodeOpaqueJson$5(value) {
|
|
5203
5460
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
5204
5461
|
}
|
|
5205
5462
|
function chatMessageDnd$1(value) {
|
|
@@ -5494,7 +5751,7 @@ var GroupService = class {
|
|
|
5494
5751
|
};
|
|
5495
5752
|
}
|
|
5496
5753
|
async sealCandidateRef(payload) {
|
|
5497
|
-
const encoded = encodeOpaqueJson$
|
|
5754
|
+
const encoded = encodeOpaqueJson$5(payload);
|
|
5498
5755
|
return `arkme-group-candidate-v1.${encoded}.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest("base64url")}`;
|
|
5499
5756
|
}
|
|
5500
5757
|
async openCandidateRef(candidateRef, expectedUserId, expectedGroupChatSessionUid) {
|
|
@@ -5506,7 +5763,7 @@ var GroupService = class {
|
|
|
5506
5763
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用无效", false);
|
|
5507
5764
|
let raw;
|
|
5508
5765
|
try {
|
|
5509
|
-
raw = objectValue$1(decodeOpaqueJson$
|
|
5766
|
+
raw = objectValue$1(decodeOpaqueJson$5(encoded));
|
|
5510
5767
|
} catch (error) {
|
|
5511
5768
|
throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用无效", false, 400, { cause: error });
|
|
5512
5769
|
}
|
|
@@ -5585,7 +5842,8 @@ var GroupService = class {
|
|
|
5585
5842
|
isSelf: userId === session.userId,
|
|
5586
5843
|
isOwner: role === "owner",
|
|
5587
5844
|
joinedAtMillis: numberValue$12(item.join_at),
|
|
5588
|
-
recordCount: Math.max(0, Math.trunc(numberValue$12(objectValue$1(item.extra).record_count)))
|
|
5845
|
+
recordCount: Math.max(0, Math.trunc(numberValue$12(objectValue$1(item.extra).record_count))),
|
|
5846
|
+
mentionCount: Math.max(0, Math.trunc(numberValue$12(objectValue$1(item.extra).mention_count)))
|
|
5589
5847
|
});
|
|
5590
5848
|
}
|
|
5591
5849
|
const roleRank = (role) => role === "owner" ? 0 : role === "admin" ? 1 : role === "member" ? 2 : 3;
|
|
@@ -6791,10 +7049,10 @@ function chunksOf$1(values, size) {
|
|
|
6791
7049
|
for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
|
|
6792
7050
|
return chunks;
|
|
6793
7051
|
}
|
|
6794
|
-
function encodeOpaqueJson$
|
|
7052
|
+
function encodeOpaqueJson$4(value) {
|
|
6795
7053
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
6796
7054
|
}
|
|
6797
|
-
function decodeOpaqueJson$
|
|
7055
|
+
function decodeOpaqueJson$4(value) {
|
|
6798
7056
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
6799
7057
|
}
|
|
6800
7058
|
function arkmeIdAvailabilityReason(value) {
|
|
@@ -7137,7 +7395,7 @@ var ProfileService = class {
|
|
|
7137
7395
|
return profiles;
|
|
7138
7396
|
}
|
|
7139
7397
|
async sealProfileImageRef(viewerUserId, targetUserId) {
|
|
7140
|
-
const payload = encodeOpaqueJson$
|
|
7398
|
+
const payload = encodeOpaqueJson$4({
|
|
7141
7399
|
version: 1,
|
|
7142
7400
|
viewerUserId,
|
|
7143
7401
|
targetUserId
|
|
@@ -7153,7 +7411,7 @@ var ProfileService = class {
|
|
|
7153
7411
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("image-ref-invalid", "Arkme头像引用无效", false);
|
|
7154
7412
|
let parsed;
|
|
7155
7413
|
try {
|
|
7156
|
-
parsed = objectValue$1(decodeOpaqueJson$
|
|
7414
|
+
parsed = objectValue$1(decodeOpaqueJson$4(payload));
|
|
7157
7415
|
} catch (error) {
|
|
7158
7416
|
throw new ArkmePluginError("image-ref-invalid", "Arkme头像引用无效", false, 400, { cause: error });
|
|
7159
7417
|
}
|
|
@@ -7389,20 +7647,57 @@ var RecordService = class {
|
|
|
7389
7647
|
};
|
|
7390
7648
|
}
|
|
7391
7649
|
}
|
|
7392
|
-
async
|
|
7393
|
-
const session = await this.runtime.requireSession();
|
|
7394
|
-
return await this.runtime.stateStore.listPending(session.userId);
|
|
7395
|
-
}
|
|
7396
|
-
async retryPending(recordUid) {
|
|
7650
|
+
async createFileAssetsForConversation(recordUid, textContent, assets) {
|
|
7397
7651
|
const session = await this.runtime.requireSession();
|
|
7398
|
-
const
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7652
|
+
const normalizedUid = recordUid.trim();
|
|
7653
|
+
const normalizedText = textContent.trim();
|
|
7654
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
|
|
7655
|
+
if (assets.length === 0 || assets.length > 20 || normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-file-assets-invalid", "附件内容为空、过长或数量超限", false);
|
|
7656
|
+
for (const asset of assets) if (!/^[A-Za-z0-9._:-]{8,256}$/.test(asset.fileAssetUid) || asset.fileName.trim() === "" || asset.fileName.length > 255 || !Number.isSafeInteger(asset.size) || asset.size <= 0 || ![
|
|
7657
|
+
1,
|
|
7658
|
+
2,
|
|
7659
|
+
3,
|
|
7660
|
+
4
|
|
7661
|
+
].includes(asset.fileKind)) throw new ArkmePluginError("record-file-asset-invalid", "附件资产参数无效", false);
|
|
7662
|
+
const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
|
|
7663
|
+
record_uid: normalizedUid,
|
|
7664
|
+
template_kind: 2,
|
|
7665
|
+
display_kind: 0,
|
|
7666
|
+
title: "",
|
|
7667
|
+
text_content: normalizedText,
|
|
7668
|
+
content_payload: {
|
|
7669
|
+
payload_kind: 2,
|
|
7670
|
+
schema_version: 1,
|
|
7671
|
+
text_state: normalizedText === "" ? 3 : 1,
|
|
7672
|
+
media_refs: assets.map((asset, index) => ({
|
|
7673
|
+
file_asset_uid: asset.fileAssetUid,
|
|
7674
|
+
content_file_role: 1,
|
|
7675
|
+
render_role: 1,
|
|
7676
|
+
sort_order: index,
|
|
7677
|
+
file_name: asset.fileName
|
|
7678
|
+
}))
|
|
7679
|
+
},
|
|
7680
|
+
send_at: Date.now()
|
|
7681
|
+
}, session);
|
|
7682
|
+
return {
|
|
7683
|
+
recordUid: stringValue$1(data.record_uid).trim() || normalizedUid,
|
|
7684
|
+
status: numberValue$7(data.status)
|
|
7685
|
+
};
|
|
7686
|
+
}
|
|
7687
|
+
async pendingWrites() {
|
|
7688
|
+
const session = await this.runtime.requireSession();
|
|
7689
|
+
return await this.runtime.stateStore.listPending(session.userId);
|
|
7690
|
+
}
|
|
7691
|
+
async retryPending(recordUid) {
|
|
7692
|
+
const session = await this.runtime.requireSession();
|
|
7693
|
+
const pending = (await this.runtime.stateStore.listPending(session.userId)).find((item) => item.recordUid === recordUid);
|
|
7694
|
+
if (pending === void 0) throw new ArkmePluginError("outbox-entry-not-found", "待重试内容不存在", false, 404);
|
|
7695
|
+
return await this.sendPending(session, pending);
|
|
7696
|
+
}
|
|
7697
|
+
async sendPending(session, pending) {
|
|
7698
|
+
try {
|
|
7699
|
+
const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
|
|
7700
|
+
record_uid: pending.recordUid,
|
|
7406
7701
|
template_kind: 1,
|
|
7407
7702
|
title: "",
|
|
7408
7703
|
text_content: pending.textContent,
|
|
@@ -7532,7 +7827,7 @@ function displayTimestamp(value) {
|
|
|
7532
7827
|
const parsed = Date.parse(stringValue(value));
|
|
7533
7828
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
7534
7829
|
}
|
|
7535
|
-
function projectRecordingTranscripts(response, speakerResponse
|
|
7830
|
+
function projectRecordingTranscripts(response, speakerResponse) {
|
|
7536
7831
|
const data = objectValue(response);
|
|
7537
7832
|
const sessions = /* @__PURE__ */ new Map();
|
|
7538
7833
|
const sessionSpeakerColorIndexes = /* @__PURE__ */ new Map();
|
|
@@ -7576,32 +7871,7 @@ function projectRecordingTranscripts(response, speakerResponse, source = "system
|
|
|
7576
7871
|
const sessionSpeakers = listValue$6(session.spk_ls ?? session.speakers).map(objectValue);
|
|
7577
7872
|
const childOffset = numberValue$6(child.start_at);
|
|
7578
7873
|
const childStart = childOffset >= 1e11 ? childOffset : numberValue$6(session.start_at) + childOffset;
|
|
7579
|
-
const rows = listValue$6(
|
|
7580
|
-
const doubaoStatus = numberValue$6(child.doubao_asr_status);
|
|
7581
|
-
if (source === "doubao" && (doubaoStatus === 1 || doubaoStatus === 2 || doubaoStatus === 4 || doubaoStatus === 5)) {
|
|
7582
|
-
const transcriptStatus = doubaoStatus === 1 || doubaoStatus === 2 ? "processing" : doubaoStatus === 4 ? "silent" : "failed";
|
|
7583
|
-
const text = transcriptStatus === "processing" ? "豆包转写中..." : transcriptStatus === "silent" ? "豆包未识别到人声" : "豆包转写失败";
|
|
7584
|
-
const label = transcriptStatus === "processing" ? "豆包转写中" : transcriptStatus === "silent" ? "豆包静音" : "豆包失败";
|
|
7585
|
-
projected.push({
|
|
7586
|
-
itemId: `${childId || sessionId}:doubao:status`,
|
|
7587
|
-
sessionId,
|
|
7588
|
-
childId,
|
|
7589
|
-
asrItemIndex: 0,
|
|
7590
|
-
transcriptSource: "doubao",
|
|
7591
|
-
startAtMillis: childStart,
|
|
7592
|
-
endAtMillis: childStart + Math.max(0, numberValue$6(child.duration)),
|
|
7593
|
-
speakerNumber: -1,
|
|
7594
|
-
speakerColorIndex: -1,
|
|
7595
|
-
speakerLabel: label,
|
|
7596
|
-
isSelf: false,
|
|
7597
|
-
isBackground: false,
|
|
7598
|
-
text,
|
|
7599
|
-
transcriptStatus,
|
|
7600
|
-
sourceIndex
|
|
7601
|
-
});
|
|
7602
|
-
sourceIndex += 1;
|
|
7603
|
-
continue;
|
|
7604
|
-
}
|
|
7874
|
+
const rows = listValue$6(child.asr);
|
|
7605
7875
|
for (let index = 0; index < rows.length; index += 1) {
|
|
7606
7876
|
const row = objectValue(rows[index]);
|
|
7607
7877
|
const isBackground = numberValue$6(row.b ?? row.background) === 1 || row.background === true;
|
|
@@ -7615,15 +7885,15 @@ function projectRecordingTranscripts(response, speakerResponse, source = "system
|
|
|
7615
7885
|
const persistentSpeakerNumber = optionalNumberValue(sessionSpeaker.speaker_display_number ?? sessionSpeaker.speakerDisplayNumber);
|
|
7616
7886
|
const speakerNumber = persistentSpeakerNumber !== void 0 && persistentSpeakerNumber > 0 ? persistentSpeakerNumber : rawSpeakerNumber;
|
|
7617
7887
|
const speakerColorIndex = sessionSpeakerColorIndexes.get(`${sessionId}:${rawSpeakerNumber}`) ?? Math.max(0, rawSpeakerNumber);
|
|
7618
|
-
const speakerLabel =
|
|
7888
|
+
const speakerLabel = speakerNumber >= 0 ? `说话人 ${speakerNumber}` : "未知说话人";
|
|
7619
7889
|
const startOffset = numberValue$6(row.s ?? row.start_at);
|
|
7620
7890
|
const endOffset = Math.max(startOffset, numberValue$6(row.e ?? row.end_at));
|
|
7621
7891
|
projected.push({
|
|
7622
|
-
itemId:
|
|
7892
|
+
itemId: `${childId || sessionId}:${index}`,
|
|
7623
7893
|
sessionId,
|
|
7624
7894
|
childId,
|
|
7625
7895
|
asrItemIndex: index,
|
|
7626
|
-
transcriptSource:
|
|
7896
|
+
transcriptSource: "system",
|
|
7627
7897
|
startAtMillis: childStart + startOffset,
|
|
7628
7898
|
endAtMillis: childStart + endOffset,
|
|
7629
7899
|
speakerNumber,
|
|
@@ -7632,7 +7902,6 @@ function projectRecordingTranscripts(response, speakerResponse, source = "system
|
|
|
7632
7902
|
isSelf,
|
|
7633
7903
|
isBackground,
|
|
7634
7904
|
text,
|
|
7635
|
-
...source === "doubao" ? { transcriptStatus: "ready" } : {},
|
|
7636
7905
|
sourceIndex
|
|
7637
7906
|
});
|
|
7638
7907
|
sourceIndex += 1;
|
|
@@ -7832,10 +8101,10 @@ function numberValue$5(value) {
|
|
|
7832
8101
|
function listValue$5(value) {
|
|
7833
8102
|
return Array.isArray(value) ? value : [];
|
|
7834
8103
|
}
|
|
7835
|
-
function encodeOpaqueJson$
|
|
8104
|
+
function encodeOpaqueJson$3(value) {
|
|
7836
8105
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
7837
8106
|
}
|
|
7838
|
-
function decodeOpaqueJson$
|
|
8107
|
+
function decodeOpaqueJson$3(value) {
|
|
7839
8108
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
7840
8109
|
}
|
|
7841
8110
|
function safeFailureMessage$2(error) {
|
|
@@ -7879,21 +8148,7 @@ var RecordingService = class {
|
|
|
7879
8148
|
days
|
|
7880
8149
|
};
|
|
7881
8150
|
}
|
|
7882
|
-
async recordingTranscript(dateStamp, signal
|
|
7883
|
-
const sections = await this.recordingTranscriptSections(dateStamp, signal);
|
|
7884
|
-
return source === "doubao" ? sections.doubao : sections.system;
|
|
7885
|
-
}
|
|
7886
|
-
async startRecordingDoubaoBackfill(dateStamp, signal) {
|
|
7887
|
-
const date = this.recordingDayStart(dateStamp).getTime();
|
|
7888
|
-
const session = await this.runtime.requireSession();
|
|
7889
|
-
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/doubao-asr/backfill-day", { start_at: date }, session, signal);
|
|
7890
|
-
return {
|
|
7891
|
-
queuedChildCount: Math.max(0, Math.trunc(numberValue$5(data.queued_child_count))),
|
|
7892
|
-
inFlightChildCount: Math.max(0, Math.trunc(numberValue$5(data.in_flight_child_count))),
|
|
7893
|
-
missingAudioChildCount: Math.max(0, Math.trunc(numberValue$5(data.missing_audio_child_count)))
|
|
7894
|
-
};
|
|
7895
|
-
}
|
|
7896
|
-
async recordingTranscriptSections(dateStamp, signal) {
|
|
8151
|
+
async recordingTranscript(dateStamp, signal) {
|
|
7897
8152
|
const dayStart = this.recordingDayStart(dateStamp);
|
|
7898
8153
|
const date = dayStart.getTime();
|
|
7899
8154
|
const session = await this.runtime.requireSession();
|
|
@@ -7905,29 +8160,14 @@ var RecordingService = class {
|
|
|
7905
8160
|
let totalDurationMillis = 0;
|
|
7906
8161
|
for (const rawSession of listValue$5(transcriptResult.value.session_ls)) totalDurationMillis += Math.max(0, numberValue$5(objectValue$1(rawSession).duration));
|
|
7907
8162
|
const speakerData = speakerResult.status === "fulfilled" ? listValue$5(speakerResult.value.spk_ls) : [];
|
|
7908
|
-
const
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
message: systemItems.length > 0 ? "" : "当天无录音",
|
|
7915
|
-
identityCoverage,
|
|
8163
|
+
const items = projectRecordingTranscripts(transcriptResult.value, speakerData);
|
|
8164
|
+
return {
|
|
8165
|
+
state: items.length > 0 ? "ready" : "empty",
|
|
8166
|
+
items,
|
|
8167
|
+
message: items.length > 0 ? "" : "当天无录音",
|
|
8168
|
+
identityCoverage: speakerResult.status === "fulfilled" ? "complete" : "partial",
|
|
7916
8169
|
totalDurationMillis
|
|
7917
8170
|
};
|
|
7918
|
-
const hasProcessing = doubaoItems.some((item) => item.transcriptStatus === "processing");
|
|
7919
|
-
const hasReady = doubaoItems.some((item) => item.transcriptStatus === "ready" || item.transcriptStatus === "silent" || item.transcriptStatus === void 0);
|
|
7920
|
-
const hasFailed = doubaoItems.some((item) => item.transcriptStatus === "failed");
|
|
7921
|
-
return {
|
|
7922
|
-
system,
|
|
7923
|
-
doubao: {
|
|
7924
|
-
state: hasProcessing ? "processing" : hasReady ? "ready" : hasFailed ? "failed" : "empty",
|
|
7925
|
-
items: doubaoItems,
|
|
7926
|
-
message: hasProcessing ? "豆包转写中" : hasReady ? "" : hasFailed ? "豆包转写失败" : "豆包转写尚未生成",
|
|
7927
|
-
identityCoverage,
|
|
7928
|
-
totalDurationMillis
|
|
7929
|
-
}
|
|
7930
|
-
};
|
|
7931
8171
|
}
|
|
7932
8172
|
async recordingProjection(dateStamp, kind, signal) {
|
|
7933
8173
|
const dayStart = this.recordingDayStart(dateStamp);
|
|
@@ -7944,7 +8184,7 @@ var RecordingService = class {
|
|
|
7944
8184
|
}
|
|
7945
8185
|
async sealRecordingCursor(payload) {
|
|
7946
8186
|
const session = await this.runtime.requireSession();
|
|
7947
|
-
const encoded = encodeOpaqueJson$
|
|
8187
|
+
const encoded = encodeOpaqueJson$3(payload);
|
|
7948
8188
|
return `arkme-recording-cursor-v1.${encoded}.${createHmac("sha256", await this.recordingCursorKey(session.userId)).update(encoded).digest("base64url")}`;
|
|
7949
8189
|
}
|
|
7950
8190
|
async openRecordingCursor(cursor) {
|
|
@@ -7956,7 +8196,7 @@ var RecordingService = class {
|
|
|
7956
8196
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("recording-cursor-invalid", "录音分页游标无效", false);
|
|
7957
8197
|
let raw;
|
|
7958
8198
|
try {
|
|
7959
|
-
raw = objectValue$1(decodeOpaqueJson$
|
|
8199
|
+
raw = objectValue$1(decodeOpaqueJson$3(encoded));
|
|
7960
8200
|
} catch (error) {
|
|
7961
8201
|
throw new ArkmePluginError("recording-cursor-invalid", "录音分页游标无效", false, 400, { cause: error });
|
|
7962
8202
|
}
|
|
@@ -7965,7 +8205,6 @@ var RecordingService = class {
|
|
|
7965
8205
|
version: 1,
|
|
7966
8206
|
dateStamp: numberValue$5(raw.dateStamp),
|
|
7967
8207
|
content: content === "summary" || content === "timeline" ? content : "transcript",
|
|
7968
|
-
...content === "transcript" && (raw.transcriptSource === "system" || raw.transcriptSource === "doubao") ? { transcriptSource: raw.transcriptSource } : {},
|
|
7969
8208
|
itemOffset: numberValue$5(raw.itemOffset),
|
|
7970
8209
|
textOffset: numberValue$5(raw.textOffset),
|
|
7971
8210
|
fingerprint: stringValue$1(raw.fingerprint),
|
|
@@ -7975,31 +8214,25 @@ var RecordingService = class {
|
|
|
7975
8214
|
"transcript",
|
|
7976
8215
|
"summary",
|
|
7977
8216
|
"timeline"
|
|
7978
|
-
].includes(String(content)) ||
|
|
8217
|
+
].includes(String(content)) || !Number.isSafeInteger(payload.dateStamp) || payload.dateStamp <= 0 || !Number.isSafeInteger(payload.itemOffset) || payload.itemOffset < 0 || !Number.isSafeInteger(payload.textOffset) || payload.textOffset < 0 || payload.fingerprint === "") throw new ArkmePluginError("recording-cursor-invalid", "录音分页游标无效", false);
|
|
7979
8218
|
return payload;
|
|
7980
8219
|
}
|
|
7981
8220
|
async recordingDay(dateStamp) {
|
|
7982
8221
|
const date = this.recordingDayStart(dateStamp).getTime();
|
|
7983
8222
|
const [transcriptResult, summaryResult, timelineResult] = await Promise.allSettled([
|
|
7984
|
-
this.
|
|
8223
|
+
this.recordingTranscript(date),
|
|
7985
8224
|
this.recordingProjection(date, "summary"),
|
|
7986
8225
|
this.recordingProjection(date, "timeline")
|
|
7987
8226
|
]);
|
|
7988
|
-
const transcript = transcriptResult.status === "fulfilled" ? transcriptResult.value
|
|
7989
|
-
state: "error",
|
|
7990
|
-
items: [],
|
|
7991
|
-
message: safeFailureMessage$2(transcriptResult.reason)
|
|
7992
|
-
};
|
|
7993
|
-
const doubaoTranscript = transcriptResult.status === "fulfilled" ? transcriptResult.value.doubao : {
|
|
8227
|
+
const transcript = transcriptResult.status === "fulfilled" ? transcriptResult.value : {
|
|
7994
8228
|
state: "error",
|
|
7995
8229
|
items: [],
|
|
7996
8230
|
message: safeFailureMessage$2(transcriptResult.reason)
|
|
7997
8231
|
};
|
|
7998
8232
|
return {
|
|
7999
8233
|
dateStamp: date,
|
|
8000
|
-
totalDurationMillis: transcriptResult.status === "fulfilled" ? transcriptResult.value.
|
|
8234
|
+
totalDurationMillis: transcriptResult.status === "fulfilled" ? transcriptResult.value.totalDurationMillis : 0,
|
|
8001
8235
|
transcript,
|
|
8002
|
-
doubaoTranscript,
|
|
8003
8236
|
summary: summaryResult.status === "fulfilled" ? summaryResult.value : {
|
|
8004
8237
|
state: "error",
|
|
8005
8238
|
items: [],
|
|
@@ -8515,10 +8748,10 @@ function booleanValue$1(value) {
|
|
|
8515
8748
|
function listValue$2(value) {
|
|
8516
8749
|
return Array.isArray(value) ? value : [];
|
|
8517
8750
|
}
|
|
8518
|
-
function encodeOpaqueJson$
|
|
8751
|
+
function encodeOpaqueJson$2(value) {
|
|
8519
8752
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
8520
8753
|
}
|
|
8521
|
-
function decodeOpaqueJson$
|
|
8754
|
+
function decodeOpaqueJson$2(value) {
|
|
8522
8755
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
8523
8756
|
}
|
|
8524
8757
|
function isSourceKind(value) {
|
|
@@ -8700,13 +8933,6 @@ var SourceService = class {
|
|
|
8700
8933
|
...defaultLatestPreview === "" ? {} : { latestPreview: defaultLatestPreview },
|
|
8701
8934
|
...defaultRecordCount === void 0 ? {} : { recordCount: defaultRecordCount }
|
|
8702
8935
|
};
|
|
8703
|
-
const aggregateSource = {
|
|
8704
|
-
sourceRef: await this.sealSourceRef(session.userId, "send_to_self", "all", "发给自己"),
|
|
8705
|
-
kind: "send_to_self",
|
|
8706
|
-
displayName: "发给自己",
|
|
8707
|
-
activeAtMillis: 0,
|
|
8708
|
-
unreadCount: 0
|
|
8709
|
-
};
|
|
8710
8936
|
const topicDescriptors = [];
|
|
8711
8937
|
const seenTopicUids = /* @__PURE__ */ new Set();
|
|
8712
8938
|
const parentTopicUidByChild = /* @__PURE__ */ new Map();
|
|
@@ -8734,30 +8960,44 @@ var SourceService = class {
|
|
|
8734
8960
|
...parentTopicUid === "" || parentTopicUid === topicUid ? {} : { parentTopicUid },
|
|
8735
8961
|
title,
|
|
8736
8962
|
latestPreview: textPreview(latest),
|
|
8963
|
+
latestMessageAtMillis: numberValue$2(latest.send_at ?? summary.latest_send_at),
|
|
8737
8964
|
activeAtMillis: numberValue$2(latest.send_at ?? summary.latest_send_at ?? core.update_at),
|
|
8738
8965
|
recordCount: numberValue$2(summary.record_count)
|
|
8739
8966
|
});
|
|
8740
8967
|
}
|
|
8741
8968
|
const sourceRefByTopicUid = /* @__PURE__ */ new Map();
|
|
8742
8969
|
for (const topic of topicDescriptors) sourceRefByTopicUid.set(topic.topicUid, await this.sealSourceRef(session.userId, "topic", topic.topicUid, topic.title));
|
|
8970
|
+
const topics = topicDescriptors.map((topic) => {
|
|
8971
|
+
const parentSourceRef = topic.parentTopicUid === void 0 ? void 0 : sourceRefByTopicUid.get(topic.parentTopicUid);
|
|
8972
|
+
return {
|
|
8973
|
+
sourceRef: sourceRefByTopicUid.get(topic.topicUid),
|
|
8974
|
+
...parentSourceRef === void 0 ? {} : { parentSourceRef },
|
|
8975
|
+
kind: "topic",
|
|
8976
|
+
displayName: topic.title,
|
|
8977
|
+
...topic.latestPreview === "" ? {} : { latestPreview: topic.latestPreview },
|
|
8978
|
+
activeAtMillis: topic.activeAtMillis,
|
|
8979
|
+
unreadCount: 0,
|
|
8980
|
+
recordCount: topic.recordCount
|
|
8981
|
+
};
|
|
8982
|
+
});
|
|
8983
|
+
const latestAggregateItem = [defaultCategory, ...topics.map((source, index) => ({
|
|
8984
|
+
...source,
|
|
8985
|
+
activeAtMillis: topicDescriptors[index]?.latestMessageAtMillis ?? 0
|
|
8986
|
+
}))].reduce((latest, source) => latest === void 0 || source.activeAtMillis > latest.activeAtMillis ? source : latest, void 0);
|
|
8987
|
+
const aggregateLatestPreview = latestAggregateItem === void 0 || latestAggregateItem.activeAtMillis <= 0 ? "" : latestAggregateItem.latestPreview?.trim() || "非文本内容";
|
|
8743
8988
|
return {
|
|
8744
8989
|
directory,
|
|
8745
8990
|
items: [
|
|
8746
|
-
|
|
8991
|
+
{
|
|
8992
|
+
sourceRef: await this.sealSourceRef(session.userId, "send_to_self", "all", "发给自己"),
|
|
8993
|
+
kind: "send_to_self",
|
|
8994
|
+
displayName: "发给自己",
|
|
8995
|
+
...aggregateLatestPreview === "" ? {} : { latestPreview: aggregateLatestPreview },
|
|
8996
|
+
activeAtMillis: latestAggregateItem?.activeAtMillis ?? 0,
|
|
8997
|
+
unreadCount: 0
|
|
8998
|
+
},
|
|
8747
8999
|
defaultCategory,
|
|
8748
|
-
...
|
|
8749
|
-
const parentSourceRef = topic.parentTopicUid === void 0 ? void 0 : sourceRefByTopicUid.get(topic.parentTopicUid);
|
|
8750
|
-
return {
|
|
8751
|
-
sourceRef: sourceRefByTopicUid.get(topic.topicUid),
|
|
8752
|
-
...parentSourceRef === void 0 ? {} : { parentSourceRef },
|
|
8753
|
-
kind: "topic",
|
|
8754
|
-
displayName: topic.title,
|
|
8755
|
-
...topic.latestPreview === "" ? {} : { latestPreview: topic.latestPreview },
|
|
8756
|
-
activeAtMillis: topic.activeAtMillis,
|
|
8757
|
-
unreadCount: 0,
|
|
8758
|
-
recordCount: topic.recordCount
|
|
8759
|
-
};
|
|
8760
|
-
})
|
|
9000
|
+
...topics
|
|
8761
9001
|
],
|
|
8762
9002
|
hasMore: false
|
|
8763
9003
|
};
|
|
@@ -8923,7 +9163,7 @@ var SourceService = class {
|
|
|
8923
9163
|
return `arkme-chat-source-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`chat-source-key-v1:${String(userId)}:${chatSessionUid.trim()}`).digest("base64url")}`;
|
|
8924
9164
|
}
|
|
8925
9165
|
async sealSourceRef(userId, kind, ownerRef, displayName) {
|
|
8926
|
-
const payload = encodeOpaqueJson$
|
|
9166
|
+
const payload = encodeOpaqueJson$2({
|
|
8927
9167
|
version: 1,
|
|
8928
9168
|
userId,
|
|
8929
9169
|
kind,
|
|
@@ -8941,7 +9181,7 @@ var SourceService = class {
|
|
|
8941
9181
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("source-ref-invalid", "Arkme 数据源引用无效", false);
|
|
8942
9182
|
let parsed;
|
|
8943
9183
|
try {
|
|
8944
|
-
parsed = objectValue$1(decodeOpaqueJson$
|
|
9184
|
+
parsed = objectValue$1(decodeOpaqueJson$2(payload));
|
|
8945
9185
|
} catch (error) {
|
|
8946
9186
|
throw new ArkmePluginError("source-ref-invalid", "Arkme 数据源引用无效", false, 400, { cause: error });
|
|
8947
9187
|
}
|
|
@@ -8997,13 +9237,13 @@ var SourceService = class {
|
|
|
8997
9237
|
};
|
|
8998
9238
|
}
|
|
8999
9239
|
encodeCursor(value) {
|
|
9000
|
-
return `arkme-cursor-v1.${encodeOpaqueJson$
|
|
9240
|
+
return `arkme-cursor-v1.${encodeOpaqueJson$2(value)}`;
|
|
9001
9241
|
}
|
|
9002
9242
|
decodeCursor(cursor) {
|
|
9003
9243
|
const [prefix, payload, ...extra] = cursor.trim().split(".");
|
|
9004
9244
|
if (prefix !== "arkme-cursor-v1" || payload === void 0 || extra.length > 0) throw new ArkmePluginError("source-cursor-invalid", "Arkme 数据源分页游标无效", false);
|
|
9005
9245
|
try {
|
|
9006
|
-
const decoded = objectValue$1(decodeOpaqueJson$
|
|
9246
|
+
const decoded = objectValue$1(decodeOpaqueJson$2(payload));
|
|
9007
9247
|
if (Object.keys(decoded).length === 0) throw new Error("empty cursor");
|
|
9008
9248
|
return decoded;
|
|
9009
9249
|
} catch (error) {
|
|
@@ -9054,10 +9294,10 @@ function optionalString(value) {
|
|
|
9054
9294
|
const text = stringValue$1(value).trim();
|
|
9055
9295
|
return text === "" ? void 0 : text;
|
|
9056
9296
|
}
|
|
9057
|
-
function encodeOpaqueJson(value) {
|
|
9297
|
+
function encodeOpaqueJson$1(value) {
|
|
9058
9298
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
9059
9299
|
}
|
|
9060
|
-
function decodeOpaqueJson(value) {
|
|
9300
|
+
function decodeOpaqueJson$1(value) {
|
|
9061
9301
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
9062
9302
|
}
|
|
9063
9303
|
var WechatService = class {
|
|
@@ -9365,7 +9605,7 @@ var WechatService = class {
|
|
|
9365
9605
|
};
|
|
9366
9606
|
}
|
|
9367
9607
|
async sealWechatConversationRef(userId, importSessionKey) {
|
|
9368
|
-
const payload = encodeOpaqueJson({
|
|
9608
|
+
const payload = encodeOpaqueJson$1({
|
|
9369
9609
|
version: 1,
|
|
9370
9610
|
userId,
|
|
9371
9611
|
importSessionKey
|
|
@@ -9381,7 +9621,7 @@ var WechatService = class {
|
|
|
9381
9621
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("wechat-conversation-ref-invalid", "微信会话引用无效,请先重新查询微信会话列表", false);
|
|
9382
9622
|
let parsed;
|
|
9383
9623
|
try {
|
|
9384
|
-
parsed = objectValue$1(decodeOpaqueJson(payload));
|
|
9624
|
+
parsed = objectValue$1(decodeOpaqueJson$1(payload));
|
|
9385
9625
|
} catch (error) {
|
|
9386
9626
|
throw new ArkmePluginError("wechat-conversation-ref-invalid", "微信会话引用无效,请先重新查询微信会话列表", false, 400, { cause: error });
|
|
9387
9627
|
}
|
|
@@ -9394,7 +9634,7 @@ var WechatService = class {
|
|
|
9394
9634
|
return result;
|
|
9395
9635
|
}
|
|
9396
9636
|
async sealWechatCursor(userId, scope, offset) {
|
|
9397
|
-
const payload = encodeOpaqueJson({
|
|
9637
|
+
const payload = encodeOpaqueJson$1({
|
|
9398
9638
|
version: 1,
|
|
9399
9639
|
userId,
|
|
9400
9640
|
scope,
|
|
@@ -9412,7 +9652,7 @@ var WechatService = class {
|
|
|
9412
9652
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("wechat-cursor-invalid", "微信数据分页游标无效,请从第一页重新查询", false);
|
|
9413
9653
|
let parsed;
|
|
9414
9654
|
try {
|
|
9415
|
-
parsed = objectValue$1(decodeOpaqueJson(payload));
|
|
9655
|
+
parsed = objectValue$1(decodeOpaqueJson$1(payload));
|
|
9416
9656
|
} catch (error) {
|
|
9417
9657
|
throw new ArkmePluginError("wechat-cursor-invalid", "微信数据分页游标无效,请从第一页重新查询", false, 400, { cause: error });
|
|
9418
9658
|
}
|
|
@@ -9427,19 +9667,584 @@ var WechatService = class {
|
|
|
9427
9667
|
}
|
|
9428
9668
|
};
|
|
9429
9669
|
//#endregion
|
|
9670
|
+
//#region src/types.ts
|
|
9671
|
+
const ARKME_PROVIDER_CONTRACT_VERSION = 1;
|
|
9672
|
+
const ARKME_DEFAULT_SHARE_WEBSITE = "https://app.arkme.ai";
|
|
9673
|
+
const ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS = 3e3;
|
|
9674
|
+
const ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS = 6e4;
|
|
9675
|
+
const ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES = 10 * 1024 * 1024;
|
|
9676
|
+
const ARKME_WORLD_PUBLISH_MAX_IMAGES = 9;
|
|
9677
|
+
const ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
9678
|
+
//#endregion
|
|
9679
|
+
//#region src/voiceprint-wav.ts
|
|
9680
|
+
function ascii(bytes, offset, length) {
|
|
9681
|
+
return String.fromCharCode(...bytes.subarray(offset, offset + length));
|
|
9682
|
+
}
|
|
9683
|
+
function validateVoiceprintPcm16Wav(wav, declaredDurationMs) {
|
|
9684
|
+
if (wav.byteLength < 46 || !Number.isSafeInteger(declaredDurationMs) || declaredDurationMs <= 0) throw new TypeError("声纹 WAV 元数据无效");
|
|
9685
|
+
const view = new DataView(wav.buffer, wav.byteOffset, wav.byteLength);
|
|
9686
|
+
const sampleRate = view.getUint32(24, true);
|
|
9687
|
+
const dataBytes = view.getUint32(40, true);
|
|
9688
|
+
if (!(ascii(wav, 0, 4) === "RIFF" && view.getUint32(4, true) === wav.byteLength - 8 && ascii(wav, 8, 4) === "WAVE" && ascii(wav, 12, 4) === "fmt " && view.getUint32(16, true) === 16 && view.getUint16(20, true) === 1 && view.getUint16(22, true) === 1 && sampleRate >= 8e3 && sampleRate <= 192e3 && view.getUint32(28, true) === sampleRate * 2 && view.getUint16(32, true) === 2 && view.getUint16(34, true) === 16 && ascii(wav, 36, 4) === "data" && dataBytes > 0 && dataBytes === wav.byteLength - 44)) throw new TypeError("声纹录音必须是单声道 PCM16 WAV");
|
|
9689
|
+
const durationMs = Math.round(dataBytes * 1e3 / (sampleRate * 2));
|
|
9690
|
+
if (Math.abs(durationMs - declaredDurationMs) > 250) throw new TypeError("声纹 WAV 时长与声明不一致");
|
|
9691
|
+
return {
|
|
9692
|
+
sampleRate,
|
|
9693
|
+
durationMs
|
|
9694
|
+
};
|
|
9695
|
+
}
|
|
9696
|
+
//#endregion
|
|
9697
|
+
//#region src/services/voiceprint-service.ts
|
|
9698
|
+
const GRANT_REF_PREFIX = "arkme-voiceprint-grant-v1";
|
|
9699
|
+
const PERSON_REF_PREFIX = "arkme-voiceprint-person-v1";
|
|
9700
|
+
const SPEAKER_ID_PATTERN = /^[0-9a-f]{24}$/;
|
|
9701
|
+
const SHORT_INVITE_TOKEN_PATTERN = /^[A-Za-z0-9_-]{22}$/;
|
|
9702
|
+
function encodeOpaqueJson(value) {
|
|
9703
|
+
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
9704
|
+
}
|
|
9705
|
+
function decodeOpaqueJson(value) {
|
|
9706
|
+
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
9707
|
+
}
|
|
9708
|
+
function integerValue$1(value) {
|
|
9709
|
+
return typeof value === "number" && Number.isSafeInteger(value) ? value : NaN;
|
|
9710
|
+
}
|
|
9711
|
+
function requiredBoolean(value, code) {
|
|
9712
|
+
if (typeof value !== "boolean") throw contractError(code);
|
|
9713
|
+
return value;
|
|
9714
|
+
}
|
|
9715
|
+
function requiredInteger(value, code, minimum = 0) {
|
|
9716
|
+
const parsed = integerValue$1(value);
|
|
9717
|
+
if (!Number.isSafeInteger(parsed) || parsed < minimum) throw contractError(code);
|
|
9718
|
+
return parsed;
|
|
9719
|
+
}
|
|
9720
|
+
function requiredString(value, code) {
|
|
9721
|
+
if (typeof value !== "string") throw contractError(code);
|
|
9722
|
+
return value;
|
|
9723
|
+
}
|
|
9724
|
+
function contractError(code) {
|
|
9725
|
+
return new ArkmePluginError(code, "声纹服务返回的数据不完整,请稍后重试", true, 502);
|
|
9726
|
+
}
|
|
9727
|
+
function validOpaqueText(value, maxBytes) {
|
|
9728
|
+
return value === value.trim() && Buffer.byteLength(value, "utf8") <= maxBytes && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
9729
|
+
}
|
|
9730
|
+
function validSpeakerId(value) {
|
|
9731
|
+
return SPEAKER_ID_PATTERN.test(value) && value !== "000000000000000000000000";
|
|
9732
|
+
}
|
|
9733
|
+
var VoiceprintService = class {
|
|
9734
|
+
runtime;
|
|
9735
|
+
profile;
|
|
9736
|
+
inviteTarget;
|
|
9737
|
+
constructor(runtime, profile, inviteTarget) {
|
|
9738
|
+
this.runtime = runtime;
|
|
9739
|
+
this.profile = profile;
|
|
9740
|
+
this.inviteTarget = inviteTarget;
|
|
9741
|
+
}
|
|
9742
|
+
async myVoiceprint(options = {}) {
|
|
9743
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/my", {}, void 0, options.signal, {
|
|
9744
|
+
key: "voiceprint:self",
|
|
9745
|
+
cacheMs: 3e3,
|
|
9746
|
+
failureCooldownMs: 2e3
|
|
9747
|
+
});
|
|
9748
|
+
const hasVoiceprint = requiredBoolean(data.has_voiceprint, "voiceprint-self-contract-invalid");
|
|
9749
|
+
const canIdentify = requiredBoolean(data.can_identify, "voiceprint-self-contract-invalid");
|
|
9750
|
+
const canPlay = requiredBoolean(data.can_play, "voiceprint-self-contract-invalid");
|
|
9751
|
+
const canRestorePlayback = requiredBoolean(data.can_restore_playback, "voiceprint-self-contract-invalid");
|
|
9752
|
+
const rawStatus = requiredString(data.enrollment_status ?? "none", "voiceprint-self-contract-invalid").trim();
|
|
9753
|
+
if (![
|
|
9754
|
+
"none",
|
|
9755
|
+
"processing",
|
|
9756
|
+
"ready"
|
|
9757
|
+
].includes(rawStatus)) throw contractError("voiceprint-self-contract-invalid");
|
|
9758
|
+
const enrollmentStatus = rawStatus;
|
|
9759
|
+
const pendingSessionId = requiredString(data.pending_session_id ?? "", "voiceprint-self-contract-invalid").trim();
|
|
9760
|
+
const nickname = requiredString(data.nick_name ?? "", "voiceprint-self-contract-invalid").trim();
|
|
9761
|
+
const updatedAtMillis = requiredInteger(data.updated_at ?? 0, "voiceprint-self-contract-invalid");
|
|
9762
|
+
if (!validOpaqueText(pendingSessionId, 2048) || !hasVoiceprint && (canIdentify || canPlay || canRestorePlayback) || canPlay && !hasVoiceprint) throw contractError("voiceprint-self-contract-invalid");
|
|
9763
|
+
return {
|
|
9764
|
+
hasVoiceprint,
|
|
9765
|
+
nickname,
|
|
9766
|
+
updatedAtMillis,
|
|
9767
|
+
canIdentify,
|
|
9768
|
+
canPlay,
|
|
9769
|
+
canRestorePlayback,
|
|
9770
|
+
enrollmentStatus,
|
|
9771
|
+
enrollmentPending: enrollmentStatus === "processing" || pendingSessionId !== ""
|
|
9772
|
+
};
|
|
9773
|
+
}
|
|
9774
|
+
async outboundGrants(input, options = {}) {
|
|
9775
|
+
if (!validOpaqueText(input.cursor, 512) || !Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 100) throw new ArkmePluginError("voiceprint-grant-page-invalid", "声纹授权分页参数无效", false);
|
|
9776
|
+
const session = await this.runtime.requireSession();
|
|
9777
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/grants", {
|
|
9778
|
+
direction: "outbound",
|
|
9779
|
+
cursor: input.cursor,
|
|
9780
|
+
limit: input.limit
|
|
9781
|
+
}, session, options.signal, {
|
|
9782
|
+
key: `voiceprint:grants:${input.cursor}:${String(input.limit)}`,
|
|
9783
|
+
failureCooldownMs: 2e3
|
|
9784
|
+
});
|
|
9785
|
+
const rawItems = data.grant_ls;
|
|
9786
|
+
if (!Array.isArray(rawItems)) throw contractError("voiceprint-grant-contract-invalid");
|
|
9787
|
+
const grants = rawItems.map((raw) => this.parseOutboundGrant(objectValue$1(raw), session.userId));
|
|
9788
|
+
const granteeUserIds = [...new Set(grants.map((item) => item.granteeUserId))];
|
|
9789
|
+
const profiles = await this.loadPublicProfiles(granteeUserIds, session, options.signal);
|
|
9790
|
+
const items = [];
|
|
9791
|
+
for (const grant of grants) {
|
|
9792
|
+
const publicProfile = profiles.get(grant.granteeUserId);
|
|
9793
|
+
const avatarRef = publicProfile?.avatarUrl === void 0 ? void 0 : await this.optionalAvatarRef(session.userId, grant.granteeUserId);
|
|
9794
|
+
items.push({
|
|
9795
|
+
grantRef: await this.sealRef(GRANT_REF_PREFIX, {
|
|
9796
|
+
version: 1,
|
|
9797
|
+
viewerUserId: session.userId,
|
|
9798
|
+
granteeUserId: grant.granteeUserId
|
|
9799
|
+
}),
|
|
9800
|
+
displayName: publicProfile?.displayName.trim() || "用户资料不可用",
|
|
9801
|
+
...avatarRef === void 0 ? {} : { avatarRef },
|
|
9802
|
+
identifyEnabled: grant.identifyEnabled,
|
|
9803
|
+
playEnabled: grant.playEnabled,
|
|
9804
|
+
grantedAtMillis: grant.grantedAtMillis,
|
|
9805
|
+
updatedAtMillis: grant.updatedAtMillis
|
|
9806
|
+
});
|
|
9807
|
+
}
|
|
9808
|
+
const nextCursor = requiredString(data.next_cursor, "voiceprint-grant-contract-invalid");
|
|
9809
|
+
const hasMore = requiredBoolean(data.has_more, "voiceprint-grant-contract-invalid");
|
|
9810
|
+
if (!validOpaqueText(nextCursor, 512) || hasMore !== (nextCursor !== "")) throw contractError("voiceprint-grant-contract-invalid");
|
|
9811
|
+
return {
|
|
9812
|
+
items,
|
|
9813
|
+
nextCursor,
|
|
9814
|
+
hasMore
|
|
9815
|
+
};
|
|
9816
|
+
}
|
|
9817
|
+
async recognizedPeople(input, options = {}) {
|
|
9818
|
+
if (!validOpaqueText(input.cursor, 2048) || !Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 50) throw new ArkmePluginError("voiceprint-person-page-invalid", "已识别人分页参数无效", false);
|
|
9819
|
+
const session = await this.runtime.requireSession();
|
|
9820
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/recognized-speakers/list", {
|
|
9821
|
+
cursor: input.cursor,
|
|
9822
|
+
limit: input.limit
|
|
9823
|
+
}, session, options.signal, {
|
|
9824
|
+
key: `voiceprint:people:${input.cursor}:${String(input.limit)}`,
|
|
9825
|
+
failureCooldownMs: 2e3
|
|
9826
|
+
});
|
|
9827
|
+
requiredBoolean(data.capability_enabled, "voiceprint-person-contract-invalid");
|
|
9828
|
+
const rawItems = data.items;
|
|
9829
|
+
if (!Array.isArray(rawItems)) throw contractError("voiceprint-person-contract-invalid");
|
|
9830
|
+
const projections = rawItems.map((raw) => this.parseRecognizedPerson(objectValue$1(raw)));
|
|
9831
|
+
if (projections.some((item) => item.targetUserId === session.userId)) throw contractError("voiceprint-person-contract-invalid");
|
|
9832
|
+
const targetUserIds = [...new Set(projections.map((item) => item.targetUserId).filter((userId) => userId > 0))];
|
|
9833
|
+
const profiles = await this.loadPublicProfiles(targetUserIds, session, options.signal);
|
|
9834
|
+
const items = await Promise.all(projections.map(async (item) => await this.projectRecognizedPerson(item, session, profiles)));
|
|
9835
|
+
if (new Set(items.map((item) => item.personRef)).size !== items.length) throw contractError("voiceprint-person-contract-invalid");
|
|
9836
|
+
let reachedUnauthorized = false;
|
|
9837
|
+
for (const item of items) if (!item.playGranted) reachedUnauthorized = true;
|
|
9838
|
+
else if (reachedUnauthorized) throw contractError("voiceprint-person-contract-invalid");
|
|
9839
|
+
const nextCursor = requiredString(data.next_cursor, "voiceprint-person-contract-invalid");
|
|
9840
|
+
const hasMore = requiredBoolean(data.has_more, "voiceprint-person-contract-invalid");
|
|
9841
|
+
if (!validOpaqueText(nextCursor, 2048) || hasMore !== (nextCursor !== "")) throw contractError("voiceprint-person-contract-invalid");
|
|
9842
|
+
return {
|
|
9843
|
+
items,
|
|
9844
|
+
nextCursor,
|
|
9845
|
+
hasMore
|
|
9846
|
+
};
|
|
9847
|
+
}
|
|
9848
|
+
async recognizedPerson(personRef, options = {}) {
|
|
9849
|
+
const session = await this.runtime.requireSession();
|
|
9850
|
+
const reference = await this.openPersonRef(personRef, session.userId);
|
|
9851
|
+
return (await this.loadRecognizedPerson(reference, personRef, session, options)).detail;
|
|
9852
|
+
}
|
|
9853
|
+
async loadRecognizedPerson(reference, personRef, session, options) {
|
|
9854
|
+
const selector = reference.identityKind === "speaker" ? { speaker_id: reference.speakerId } : { target_user_id: reference.targetUserId };
|
|
9855
|
+
let data;
|
|
9856
|
+
try {
|
|
9857
|
+
data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/recognized-speakers/detail", selector, session, options.signal, {
|
|
9858
|
+
key: `voiceprint:person:${personRef}`,
|
|
9859
|
+
failureCooldownMs: 2e3
|
|
9860
|
+
});
|
|
9861
|
+
} catch (error) {
|
|
9862
|
+
if (error instanceof ArkmePluginError && error.code === "arkme-code-1003") throw new ArkmePluginError("voiceprint-person-unavailable", "这条已识别人已不可访问,请刷新列表", false, 410, { cause: error });
|
|
9863
|
+
throw error;
|
|
9864
|
+
}
|
|
9865
|
+
const projection = this.parseRecognizedPerson(data);
|
|
9866
|
+
if (reference.identityKind === "speaker" && projection.speakerId !== reference.speakerId || reference.identityKind === "authorized_user" && projection.targetUserId !== reference.targetUserId || projection.targetUserId === session.userId) throw contractError("voiceprint-person-contract-invalid");
|
|
9867
|
+
const profiles = await this.loadPublicProfiles(projection.targetUserId > 0 ? [projection.targetUserId] : [], session, options.signal);
|
|
9868
|
+
if (requiredBoolean(data.claim_required, "voiceprint-person-contract-invalid") !== (projection.speakerId !== "" && projection.targetUserId === 0)) throw contractError("voiceprint-person-contract-invalid");
|
|
9869
|
+
return {
|
|
9870
|
+
detail: await this.projectRecognizedPerson(projection, session, profiles),
|
|
9871
|
+
targetUserId: projection.targetUserId
|
|
9872
|
+
};
|
|
9873
|
+
}
|
|
9874
|
+
async recognizedPersonVoiceprints(personRef, options = {}) {
|
|
9875
|
+
const session = await this.runtime.requireSession();
|
|
9876
|
+
const reference = await this.openPersonRef(personRef, session.userId);
|
|
9877
|
+
if (reference.identityKind !== "speaker") throw new ArkmePluginError("voiceprint-library-unavailable", "只有录音中识别到的声音可以查看声纹记录", false, 409);
|
|
9878
|
+
const selector = { speaker_id: reference.speakerId };
|
|
9879
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/recognized-speakers/voiceprints/list", selector, session, options.signal, {
|
|
9880
|
+
key: `voiceprint:library:${personRef}`,
|
|
9881
|
+
failureCooldownMs: 2e3
|
|
9882
|
+
});
|
|
9883
|
+
if (!Array.isArray(data.items)) throw contractError("voiceprint-library-contract-invalid");
|
|
9884
|
+
const items = [];
|
|
9885
|
+
const refs = /* @__PURE__ */ new Set();
|
|
9886
|
+
let reachedLocal = false;
|
|
9887
|
+
let previousHitCount;
|
|
9888
|
+
let previousAuthorized;
|
|
9889
|
+
for (const raw of data.items) {
|
|
9890
|
+
const item = objectValue$1(raw);
|
|
9891
|
+
const voiceprintRef = requiredString(item.voiceprint_id, "voiceprint-library-contract-invalid");
|
|
9892
|
+
const kindCode = requiredInteger(item.kind, "voiceprint-library-contract-invalid", 1);
|
|
9893
|
+
const authorized = requiredBoolean(item.is_authorized, "voiceprint-library-contract-invalid");
|
|
9894
|
+
const hitCount = requiredInteger(item.hit_count, "voiceprint-library-contract-invalid");
|
|
9895
|
+
const createdAtMillis = item.created_at === void 0 ? void 0 : requiredInteger(item.created_at, "voiceprint-library-contract-invalid", 1);
|
|
9896
|
+
if (!validOpaqueText(voiceprintRef, 2048) || voiceprintRef === "" || refs.has(voiceprintRef) || ![
|
|
9897
|
+
1,
|
|
9898
|
+
2,
|
|
9899
|
+
3
|
|
9900
|
+
].includes(kindCode) || authorized !== (kindCode === 3) || authorized && hitCount !== 0) throw contractError("voiceprint-library-contract-invalid");
|
|
9901
|
+
if (!authorized) reachedLocal = true;
|
|
9902
|
+
else if (reachedLocal) throw contractError("voiceprint-library-contract-invalid");
|
|
9903
|
+
if (previousAuthorized === authorized && previousHitCount !== void 0 && hitCount > previousHitCount) throw contractError("voiceprint-library-contract-invalid");
|
|
9904
|
+
refs.add(voiceprintRef);
|
|
9905
|
+
previousAuthorized = authorized;
|
|
9906
|
+
previousHitCount = hitCount;
|
|
9907
|
+
items.push({
|
|
9908
|
+
kind: kindCode === 3 ? "authorized" : kindCode === 2 ? "legacy" : "local",
|
|
9909
|
+
hitCount,
|
|
9910
|
+
...createdAtMillis === void 0 ? {} : { createdAtMillis }
|
|
9911
|
+
});
|
|
9912
|
+
}
|
|
9913
|
+
return { items };
|
|
9914
|
+
}
|
|
9915
|
+
async createInvitation(options = {}) {
|
|
9916
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/invites/create", { scope: 2 }, void 0, options.signal, {
|
|
9917
|
+
lane: "write",
|
|
9918
|
+
key: `voiceprint:invite:${Date.now().toString(36)}`,
|
|
9919
|
+
bypassCache: true
|
|
9920
|
+
});
|
|
9921
|
+
const inviteToken = requiredString(data.invite_token, "voiceprint-invite-contract-invalid").trim();
|
|
9922
|
+
const previewToken = requiredString(data.preview_token, "voiceprint-invite-contract-invalid").trim();
|
|
9923
|
+
const shortInviteToken = stringValue$1(data.short_invite_token).trim();
|
|
9924
|
+
const shortPreviewToken = stringValue$1(data.short_preview_token).trim();
|
|
9925
|
+
const expiresAtMillis = requiredInteger(data.expires_at, "voiceprint-invite-contract-invalid", 1);
|
|
9926
|
+
if (requiredInteger(data.scope, "voiceprint-invite-contract-invalid", 1) !== 2 || !validOpaqueText(inviteToken, 2048) || inviteToken === "" || !validOpaqueText(previewToken, 2048) || previewToken === "") throw contractError("voiceprint-invite-contract-invalid");
|
|
9927
|
+
const useShortPair = SHORT_INVITE_TOKEN_PATTERN.test(shortInviteToken) && SHORT_INVITE_TOKEN_PATTERN.test(shortPreviewToken);
|
|
9928
|
+
const token = useShortPair ? shortInviteToken : inviteToken;
|
|
9929
|
+
const preview = useShortPair ? shortPreviewToken : previewToken;
|
|
9930
|
+
return {
|
|
9931
|
+
inviteUrl: this.invitationUrl(token, preview, useShortPair),
|
|
9932
|
+
expiresAtMillis
|
|
9933
|
+
};
|
|
9934
|
+
}
|
|
9935
|
+
async createRecognizedPersonInvitation(personRef, targetContactRef, options = {}) {
|
|
9936
|
+
const session = await this.runtime.requireSession();
|
|
9937
|
+
const reference = await this.openPersonRef(personRef, session.userId);
|
|
9938
|
+
if (reference.identityKind !== "speaker") throw new ArkmePluginError("voiceprint-person-invite-unavailable", "授权用户投影不能作为待认领声纹邀请", false, 409);
|
|
9939
|
+
const current = await this.loadRecognizedPerson(reference, personRef, session, options);
|
|
9940
|
+
if (!current.detail.canInvite || current.detail.playGranted) throw new ArkmePluginError("voiceprint-person-invite-unavailable", "这条已识别人当前不能生成邀请", false, 409);
|
|
9941
|
+
const normalizedContactRef = targetContactRef?.trim() ?? "";
|
|
9942
|
+
const targetSelectionRequired = current.targetUserId === 0;
|
|
9943
|
+
if (!targetSelectionRequired && normalizedContactRef !== "") throw new ArkmePluginError("voiceprint-invite-target-unexpected", "这条已识别人已绑定用户,无需重新选择邀请对象", false);
|
|
9944
|
+
let targetUserId = current.targetUserId;
|
|
9945
|
+
if (targetSelectionRequired) {
|
|
9946
|
+
if (this.inviteTarget === void 0) throw new ArkmePluginError("voiceprint-invite-target-unavailable", "当前运行时无法选择邀请对象", false, 503);
|
|
9947
|
+
if (!validOpaqueText(normalizedContactRef, 256) || normalizedContactRef === "") throw new ArkmePluginError("voiceprint-invite-target-invalid", "邀请对象引用无效,请重新搜索", false);
|
|
9948
|
+
targetUserId = await this.inviteTarget.resolveRegisteredContactUserId(normalizedContactRef, session, options.signal);
|
|
9949
|
+
if (!Number.isSafeInteger(targetUserId) || targetUserId <= 0 || targetUserId === session.userId) throw new ArkmePluginError("voiceprint-invite-target-invalid", "请选择其他已注册的 Arkme 用户", false);
|
|
9950
|
+
}
|
|
9951
|
+
const request = targetSelectionRequired ? {
|
|
9952
|
+
speaker_id: reference.speakerId,
|
|
9953
|
+
target_user_id: targetUserId
|
|
9954
|
+
} : { speaker_id: reference.speakerId };
|
|
9955
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/recognized-speakers/invites/create", request, session, options.signal, {
|
|
9956
|
+
lane: "write",
|
|
9957
|
+
key: `voiceprint:person-invite:${personRef}:${normalizedContactRef || "bound"}`,
|
|
9958
|
+
bypassCache: true
|
|
9959
|
+
});
|
|
9960
|
+
const inviteToken = requiredString(data.invite_token, "voiceprint-person-invite-contract-invalid").trim();
|
|
9961
|
+
const previewToken = requiredString(data.preview_token, "voiceprint-person-invite-contract-invalid").trim();
|
|
9962
|
+
const shortInviteToken = stringValue$1(data.short_invite_token).trim();
|
|
9963
|
+
const shortPreviewToken = stringValue$1(data.short_preview_token).trim();
|
|
9964
|
+
const expiresAtMillis = requiredInteger(data.expires_at, "voiceprint-person-invite-contract-invalid", 1);
|
|
9965
|
+
if (requiredInteger(data.token_version, "voiceprint-person-invite-contract-invalid", 1) !== 2 || requiredString(data.speaker_id, "voiceprint-person-invite-contract-invalid").trim().toLowerCase() !== reference.speakerId || requiredInteger(data.target_user_id, "voiceprint-person-invite-contract-invalid", 1) !== targetUserId || requiredBoolean(data.claim_required, "voiceprint-person-invite-contract-invalid") !== targetSelectionRequired || requiredInteger(data.scope, "voiceprint-person-invite-contract-invalid", 1) !== 2 || !validOpaqueText(inviteToken, 2048) || inviteToken === "" || !validOpaqueText(previewToken, 2048) || previewToken === "") throw contractError("voiceprint-person-invite-contract-invalid");
|
|
9966
|
+
const useShortPair = SHORT_INVITE_TOKEN_PATTERN.test(shortInviteToken) && SHORT_INVITE_TOKEN_PATTERN.test(shortPreviewToken);
|
|
9967
|
+
return {
|
|
9968
|
+
inviteUrl: this.invitationUrl(useShortPair ? shortInviteToken : inviteToken, useShortPair ? shortPreviewToken : previewToken, useShortPair),
|
|
9969
|
+
expiresAtMillis
|
|
9970
|
+
};
|
|
9971
|
+
}
|
|
9972
|
+
async revokePlaybackGrant(grantRef, options = {}) {
|
|
9973
|
+
const session = await this.runtime.requireSession();
|
|
9974
|
+
const reference = await this.openGrantRef(grantRef, session.userId);
|
|
9975
|
+
if (requiredBoolean((await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/grants/set-scope", {
|
|
9976
|
+
grantee_user_id: reference.granteeUserId,
|
|
9977
|
+
scope: 2,
|
|
9978
|
+
enabled: false
|
|
9979
|
+
}, session, options.signal, {
|
|
9980
|
+
lane: "write",
|
|
9981
|
+
key: `voiceprint:revoke:${grantRef}`,
|
|
9982
|
+
bypassCache: true
|
|
9983
|
+
})).revoked, "voiceprint-revoke-contract-invalid") !== true) throw contractError("voiceprint-revoke-contract-invalid");
|
|
9984
|
+
return { revoked: true };
|
|
9985
|
+
}
|
|
9986
|
+
async restorePlayback(options = {}) {
|
|
9987
|
+
const session = await this.runtime.requireSession();
|
|
9988
|
+
this.runtime.invalidateKey(this.runtime.requestScope(session.userId), "voiceprint:self");
|
|
9989
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/restore-playback", {}, session, options.signal, {
|
|
9990
|
+
lane: "write",
|
|
9991
|
+
key: `voiceprint:restore:${Date.now().toString(36)}`,
|
|
9992
|
+
bypassCache: true
|
|
9993
|
+
});
|
|
9994
|
+
const result = {
|
|
9995
|
+
canPlay: requiredBoolean(data.can_play, "voiceprint-restore-contract-invalid"),
|
|
9996
|
+
restored: requiredBoolean(data.restored, "voiceprint-restore-contract-invalid"),
|
|
9997
|
+
updatedAtMillis: requiredInteger(data.updated_at ?? 0, "voiceprint-restore-contract-invalid")
|
|
9998
|
+
};
|
|
9999
|
+
if (result.restored && !result.canPlay) throw contractError("voiceprint-restore-contract-invalid");
|
|
10000
|
+
return result;
|
|
10001
|
+
}
|
|
10002
|
+
async bindEnrollment() {
|
|
10003
|
+
const viewerUserId = (await this.runtime.requireSession()).userId;
|
|
10004
|
+
return { enrollVoiceprintWav: async (input, options = {}) => await this.enrollWav(input, {
|
|
10005
|
+
...options,
|
|
10006
|
+
expectedViewerUserId: viewerUserId
|
|
10007
|
+
}) };
|
|
10008
|
+
}
|
|
10009
|
+
async enrollWav(input, options = {}) {
|
|
10010
|
+
const { wav, durationMs } = input;
|
|
10011
|
+
if (!Number.isSafeInteger(durationMs) || durationMs < 3e3 || durationMs > 6e4 || wav.byteLength > 10485760 || wav.byteLength < 46) throw new ArkmePluginError("voiceprint-enrollment-input-invalid", "声纹录音需为 3 至 60 秒的 WAV 音频", false);
|
|
10012
|
+
try {
|
|
10013
|
+
validateVoiceprintPcm16Wav(wav, durationMs);
|
|
10014
|
+
} catch (error) {
|
|
10015
|
+
throw new ArkmePluginError("voiceprint-enrollment-input-invalid", "声纹录音需为 3 至 60 秒的单声道 PCM16 WAV 音频", false, 400, { cause: error });
|
|
10016
|
+
}
|
|
10017
|
+
const session = await this.runtime.requireSession();
|
|
10018
|
+
if (options.expectedViewerUserId !== void 0 && session.userId !== options.expectedViewerUserId) throw new ArkmePluginError("account-changed", "登录账号已切换,请在当前账号下重新录入", false, 409);
|
|
10019
|
+
const form = new FormData();
|
|
10020
|
+
const wavBuffer = new Uint8Array(wav).buffer;
|
|
10021
|
+
form.set("audio_file", new Blob([wavBuffer], { type: "audio/wav" }), "voiceprint.wav");
|
|
10022
|
+
form.set("duration_ms", String(durationMs));
|
|
10023
|
+
this.runtime.invalidateKey(this.runtime.requestScope(session.userId), "voiceprint:self");
|
|
10024
|
+
const data = await this.runtime.authenticatedAudioMultipartPost("/api/v1/audio/voiceprint/enroll-from-audio", form, session, options.signal, {
|
|
10025
|
+
key: `voiceprint:enroll:${Date.now().toString(36)}`,
|
|
10026
|
+
bypassCache: true
|
|
10027
|
+
});
|
|
10028
|
+
const status = requiredString(data.status, "voiceprint-enrollment-contract-invalid").trim();
|
|
10029
|
+
if (status !== "processing") throw contractError("voiceprint-enrollment-contract-invalid");
|
|
10030
|
+
return {
|
|
10031
|
+
status,
|
|
10032
|
+
cloneReady: requiredBoolean(data.clone_ok, "voiceprint-enrollment-contract-invalid"),
|
|
10033
|
+
updatedAtMillis: requiredInteger(data.updated_at, "voiceprint-enrollment-contract-invalid", 1)
|
|
10034
|
+
};
|
|
10035
|
+
}
|
|
10036
|
+
parseOutboundGrant(raw, viewerUserId) {
|
|
10037
|
+
const ownerUserId = requiredInteger(raw.owner_user_id, "voiceprint-grant-contract-invalid", 1);
|
|
10038
|
+
const granteeUserId = requiredInteger(raw.grantee_user_id, "voiceprint-grant-contract-invalid", 1);
|
|
10039
|
+
const status = requiredInteger(raw.status, "voiceprint-grant-contract-invalid");
|
|
10040
|
+
if (ownerUserId !== viewerUserId || granteeUserId === viewerUserId || status !== 1) throw contractError("voiceprint-grant-contract-invalid");
|
|
10041
|
+
return {
|
|
10042
|
+
granteeUserId,
|
|
10043
|
+
identifyEnabled: requiredBoolean(raw.identify_enabled, "voiceprint-grant-contract-invalid"),
|
|
10044
|
+
playEnabled: requiredBoolean(raw.play_enabled, "voiceprint-grant-contract-invalid"),
|
|
10045
|
+
grantedAtMillis: requiredInteger(raw.play_granted_at ?? 0, "voiceprint-grant-contract-invalid"),
|
|
10046
|
+
updatedAtMillis: requiredInteger(raw.update_at, "voiceprint-grant-contract-invalid")
|
|
10047
|
+
};
|
|
10048
|
+
}
|
|
10049
|
+
parseRecognizedPerson(raw) {
|
|
10050
|
+
const speakerId = requiredString(raw.speaker_id, "voiceprint-person-contract-invalid").trim().toLowerCase();
|
|
10051
|
+
const targetUserId = requiredInteger(raw.target_user_id, "voiceprint-person-contract-invalid");
|
|
10052
|
+
const displayName = requiredString(raw.display_name, "voiceprint-person-contract-invalid").trim();
|
|
10053
|
+
const playGranted = requiredBoolean(raw.play_granted, "voiceprint-person-contract-invalid");
|
|
10054
|
+
const previewAvailable = requiredBoolean(raw.preview_available, "voiceprint-person-contract-invalid");
|
|
10055
|
+
const canInvite = requiredBoolean(raw.can_invite, "voiceprint-person-contract-invalid");
|
|
10056
|
+
const hasSpeaker = validSpeakerId(speakerId);
|
|
10057
|
+
if (speakerId !== "" && !hasSpeaker || !hasSpeaker && targetUserId <= 0 || canInvite === playGranted || previewAvailable && !playGranted || playGranted && targetUserId <= 0 || !hasSpeaker && canInvite) throw contractError("voiceprint-person-contract-invalid");
|
|
10058
|
+
return {
|
|
10059
|
+
speakerId,
|
|
10060
|
+
targetUserId,
|
|
10061
|
+
displayName,
|
|
10062
|
+
playGranted,
|
|
10063
|
+
previewAvailable,
|
|
10064
|
+
canInvite
|
|
10065
|
+
};
|
|
10066
|
+
}
|
|
10067
|
+
async projectRecognizedPerson(item, session, profiles) {
|
|
10068
|
+
const identityKind = item.speakerId === "" ? "authorized_user" : "speaker";
|
|
10069
|
+
const payload = identityKind === "speaker" ? {
|
|
10070
|
+
version: 1,
|
|
10071
|
+
viewerUserId: session.userId,
|
|
10072
|
+
identityKind,
|
|
10073
|
+
speakerId: item.speakerId
|
|
10074
|
+
} : {
|
|
10075
|
+
version: 1,
|
|
10076
|
+
viewerUserId: session.userId,
|
|
10077
|
+
identityKind,
|
|
10078
|
+
targetUserId: item.targetUserId
|
|
10079
|
+
};
|
|
10080
|
+
const avatarRef = (item.targetUserId > 0 ? profiles.get(item.targetUserId) : void 0)?.avatarUrl === void 0 ? void 0 : await this.optionalAvatarRef(session.userId, item.targetUserId);
|
|
10081
|
+
return {
|
|
10082
|
+
personRef: await this.sealRef(PERSON_REF_PREFIX, payload),
|
|
10083
|
+
identityKind,
|
|
10084
|
+
displayName: item.displayName || "未命名声纹",
|
|
10085
|
+
...avatarRef === void 0 ? {} : { avatarRef },
|
|
10086
|
+
playGranted: item.playGranted,
|
|
10087
|
+
previewAvailable: item.previewAvailable,
|
|
10088
|
+
canInvite: item.canInvite,
|
|
10089
|
+
inviteTargetSelectionRequired: item.canInvite && item.targetUserId === 0
|
|
10090
|
+
};
|
|
10091
|
+
}
|
|
10092
|
+
async loadPublicProfiles(userIds, session, signal) {
|
|
10093
|
+
try {
|
|
10094
|
+
return await this.profile.publicProfileSummariesByUserIds(userIds, session, signal);
|
|
10095
|
+
} catch (error) {
|
|
10096
|
+
if (signal?.aborted === true) throw error;
|
|
10097
|
+
return /* @__PURE__ */ new Map();
|
|
10098
|
+
}
|
|
10099
|
+
}
|
|
10100
|
+
async optionalAvatarRef(viewerUserId, targetUserId) {
|
|
10101
|
+
try {
|
|
10102
|
+
return await this.profile.sealProfileImageRef(viewerUserId, targetUserId);
|
|
10103
|
+
} catch {
|
|
10104
|
+
return;
|
|
10105
|
+
}
|
|
10106
|
+
}
|
|
10107
|
+
async sealRef(prefix, payload) {
|
|
10108
|
+
const encoded = encodeOpaqueJson(payload);
|
|
10109
|
+
return `${prefix}.${encoded}.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest("base64url")}`;
|
|
10110
|
+
}
|
|
10111
|
+
async openPersonRef(personRef, expectedViewerUserId) {
|
|
10112
|
+
const normalized = personRef.trim();
|
|
10113
|
+
if (!validOpaqueText(normalized, 2048)) throw this.invalidPersonRef();
|
|
10114
|
+
const parts = normalized.split(".");
|
|
10115
|
+
if (parts.length !== 3 || parts[0] !== PERSON_REF_PREFIX) throw this.invalidPersonRef();
|
|
10116
|
+
const encoded = parts[1] ?? "";
|
|
10117
|
+
const supplied = Buffer.from(parts[2] ?? "", "base64url");
|
|
10118
|
+
const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest();
|
|
10119
|
+
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw this.invalidPersonRef();
|
|
10120
|
+
let raw;
|
|
10121
|
+
try {
|
|
10122
|
+
raw = objectValue$1(decodeOpaqueJson(encoded));
|
|
10123
|
+
} catch (error) {
|
|
10124
|
+
throw new ArkmePluginError("voiceprint-person-ref-invalid", "已识别人引用无效", false, 400, { cause: error });
|
|
10125
|
+
}
|
|
10126
|
+
const viewerUserId = integerValue$1(raw.viewerUserId);
|
|
10127
|
+
if (raw.version !== 1 || viewerUserId !== expectedViewerUserId) throw this.invalidPersonRef(true);
|
|
10128
|
+
if (raw.identityKind === "speaker") {
|
|
10129
|
+
const speakerId = stringValue$1(raw.speakerId).trim().toLowerCase();
|
|
10130
|
+
if (!validSpeakerId(speakerId)) throw this.invalidPersonRef();
|
|
10131
|
+
return {
|
|
10132
|
+
version: 1,
|
|
10133
|
+
viewerUserId,
|
|
10134
|
+
identityKind: "speaker",
|
|
10135
|
+
speakerId
|
|
10136
|
+
};
|
|
10137
|
+
}
|
|
10138
|
+
if (raw.identityKind === "authorized_user") {
|
|
10139
|
+
const targetUserId = integerValue$1(raw.targetUserId);
|
|
10140
|
+
if (!Number.isSafeInteger(targetUserId) || targetUserId <= 0) throw this.invalidPersonRef();
|
|
10141
|
+
return {
|
|
10142
|
+
version: 1,
|
|
10143
|
+
viewerUserId,
|
|
10144
|
+
identityKind: "authorized_user",
|
|
10145
|
+
targetUserId
|
|
10146
|
+
};
|
|
10147
|
+
}
|
|
10148
|
+
throw this.invalidPersonRef();
|
|
10149
|
+
}
|
|
10150
|
+
async openGrantRef(grantRef, expectedViewerUserId) {
|
|
10151
|
+
const normalized = grantRef.trim();
|
|
10152
|
+
if (!validOpaqueText(normalized, 2048)) throw this.invalidGrantRef();
|
|
10153
|
+
const parts = normalized.split(".");
|
|
10154
|
+
if (parts.length !== 3 || parts[0] !== GRANT_REF_PREFIX) throw this.invalidGrantRef();
|
|
10155
|
+
const encoded = parts[1] ?? "";
|
|
10156
|
+
const supplied = Buffer.from(parts[2] ?? "", "base64url");
|
|
10157
|
+
const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest();
|
|
10158
|
+
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw this.invalidGrantRef();
|
|
10159
|
+
let raw;
|
|
10160
|
+
try {
|
|
10161
|
+
raw = objectValue$1(decodeOpaqueJson(encoded));
|
|
10162
|
+
} catch (error) {
|
|
10163
|
+
throw new ArkmePluginError("voiceprint-grant-ref-invalid", "声纹授权引用无效", false, 400, { cause: error });
|
|
10164
|
+
}
|
|
10165
|
+
const viewerUserId = integerValue$1(raw.viewerUserId);
|
|
10166
|
+
const granteeUserId = integerValue$1(raw.granteeUserId);
|
|
10167
|
+
if (raw.version !== 1 || viewerUserId !== expectedViewerUserId || !Number.isSafeInteger(granteeUserId) || granteeUserId <= 0 || granteeUserId === expectedViewerUserId) throw this.invalidGrantRef(viewerUserId !== expectedViewerUserId);
|
|
10168
|
+
return {
|
|
10169
|
+
version: 1,
|
|
10170
|
+
viewerUserId,
|
|
10171
|
+
granteeUserId
|
|
10172
|
+
};
|
|
10173
|
+
}
|
|
10174
|
+
invitationUrl(token, previewToken, compact) {
|
|
10175
|
+
const base = this.runtime.config.environment === "prod" ? "https://jiwo.cc" : "https://jotmo-app.senguo.me";
|
|
10176
|
+
const url = new URL(compact ? "/v" : "/app/voiceprint/invite", base);
|
|
10177
|
+
url.searchParams.set("p", previewToken);
|
|
10178
|
+
url.hash = `t=${token}`;
|
|
10179
|
+
return url.toString();
|
|
10180
|
+
}
|
|
10181
|
+
invalidPersonRef(accountMismatch = false) {
|
|
10182
|
+
return new ArkmePluginError("voiceprint-person-ref-invalid", accountMismatch ? "已识别人引用与当前账号不匹配" : "已识别人引用无效", false, accountMismatch ? 403 : 400);
|
|
10183
|
+
}
|
|
10184
|
+
invalidGrantRef(accountMismatch = false) {
|
|
10185
|
+
return new ArkmePluginError("voiceprint-grant-ref-invalid", accountMismatch ? "声纹授权引用与当前账号不匹配" : "声纹授权引用无效", false, accountMismatch ? 403 : 400);
|
|
10186
|
+
}
|
|
10187
|
+
};
|
|
10188
|
+
//#endregion
|
|
9430
10189
|
//#region src/services/world-service.ts
|
|
9431
10190
|
const ARKME_WORLD_IMAGE_REF_TTL_MILLIS = 900 * 1e3;
|
|
9432
10191
|
const MAX_ARKME_WORLD_IMAGE_REFS = 2048;
|
|
10192
|
+
const ARKME_WORLD_AVATAR_RESOLUTION_CACHE_TTL_MILLIS = 300 * 1e3;
|
|
10193
|
+
const MAX_ARKME_WORLD_AVATAR_RESOLUTION_CACHE_ENTRIES = 2048;
|
|
9433
10194
|
const ARKME_WORLD_RECORD_REF_TTL_MILLIS = 900 * 1e3;
|
|
9434
10195
|
const MAX_ARKME_WORLD_RECORD_REFS = 4096;
|
|
9435
10196
|
const ARKME_VOICEPRINT_PLAY_SCOPE = 2;
|
|
9436
10197
|
const ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH = 2048;
|
|
10198
|
+
const ARKME_VOICEPRINT_SOCIAL_CACHE_MILLIS = 300 * 1e3;
|
|
10199
|
+
const ARKME_VOICEPRINT_SOCIAL_STALE_MILLIS = 1440 * 60 * 1e3;
|
|
10200
|
+
const ARKME_VOICEPRINT_SOCIAL_SOURCE_TIMEOUT_MILLIS = 4e3;
|
|
10201
|
+
const WORLD_VOICEPRINT_SOCIAL_RELATIONS = {
|
|
10202
|
+
reciprocal_expectation: {
|
|
10203
|
+
type: "reciprocal_expectation",
|
|
10204
|
+
displayLine: "TA也曾期待过你的声音",
|
|
10205
|
+
reasonCode: "relationship_reciprocal_expectation",
|
|
10206
|
+
reasonLabel: "因为TA也曾期待过我的声音"
|
|
10207
|
+
},
|
|
10208
|
+
call: {
|
|
10209
|
+
type: "call",
|
|
10210
|
+
displayLine: "你们曾经通过话",
|
|
10211
|
+
reasonCode: "relationship_call",
|
|
10212
|
+
reasonLabel: "因为我们曾经通过话"
|
|
10213
|
+
},
|
|
10214
|
+
world_interaction: {
|
|
10215
|
+
type: "world_interaction",
|
|
10216
|
+
displayLine: "你们曾在世界回应过彼此",
|
|
10217
|
+
reasonCode: "relationship_world",
|
|
10218
|
+
reasonLabel: "因为我们在世界里回应过彼此"
|
|
10219
|
+
},
|
|
10220
|
+
group_interaction: {
|
|
10221
|
+
type: "group_interaction",
|
|
10222
|
+
displayLine: "你们曾在同一个群里互动过",
|
|
10223
|
+
reasonCode: "relationship_group",
|
|
10224
|
+
reasonLabel: "因为我们在群里有过互动"
|
|
10225
|
+
},
|
|
10226
|
+
private_chat: {
|
|
10227
|
+
type: "private_chat",
|
|
10228
|
+
displayLine: "你们曾经聊过",
|
|
10229
|
+
reasonCode: "relationship_chat",
|
|
10230
|
+
reasonLabel: "因为我们以前聊过"
|
|
10231
|
+
}
|
|
10232
|
+
};
|
|
9437
10233
|
function numberValue(value) {
|
|
9438
10234
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
9439
10235
|
}
|
|
10236
|
+
function integerValue(value) {
|
|
10237
|
+
if (typeof value === "number" && Number.isFinite(value)) return Math.trunc(value);
|
|
10238
|
+
if (typeof value !== "string" || !/^-?\d+$/.test(value.trim())) return 0;
|
|
10239
|
+
const parsed = Number(value.trim());
|
|
10240
|
+
return Number.isSafeInteger(parsed) ? parsed : 0;
|
|
10241
|
+
}
|
|
9440
10242
|
function listValue(value) {
|
|
9441
10243
|
return Array.isArray(value) ? value : [];
|
|
9442
10244
|
}
|
|
10245
|
+
function isTopLevelWorldRecord(raw) {
|
|
10246
|
+
return stringValue$1(objectValue$1(raw).parent_record_uid).trim() === "";
|
|
10247
|
+
}
|
|
9443
10248
|
function safeFailureMessage(error) {
|
|
9444
10249
|
if (error instanceof ArkmePluginError) return error.message;
|
|
9445
10250
|
if (error instanceof Error && error.message.trim() !== "") return error.message;
|
|
@@ -9472,6 +10277,26 @@ function worldVisibility(checkStatus) {
|
|
|
9472
10277
|
if (checkStatus === 0 || checkStatus === 2 || checkStatus === 3) return "visible";
|
|
9473
10278
|
return "unknown";
|
|
9474
10279
|
}
|
|
10280
|
+
function worldPublicationResult(checkStatus) {
|
|
10281
|
+
const visibility = worldVisibility(checkStatus);
|
|
10282
|
+
if (visibility === "rejected") return {
|
|
10283
|
+
recordSaved: true,
|
|
10284
|
+
recordState: "synced",
|
|
10285
|
+
worldPublished: false,
|
|
10286
|
+
visibility,
|
|
10287
|
+
checkStatus,
|
|
10288
|
+
retryable: false,
|
|
10289
|
+
error: "内容未通过审核,请调整后重试"
|
|
10290
|
+
};
|
|
10291
|
+
return {
|
|
10292
|
+
recordSaved: true,
|
|
10293
|
+
recordState: "synced",
|
|
10294
|
+
worldPublished: true,
|
|
10295
|
+
visibility,
|
|
10296
|
+
checkStatus,
|
|
10297
|
+
retryable: false
|
|
10298
|
+
};
|
|
10299
|
+
}
|
|
9475
10300
|
function worldTags(text) {
|
|
9476
10301
|
return [...text.matchAll(/#(\S+)/gu)].map((match) => match[1] ?? "").filter((tag) => tag !== "");
|
|
9477
10302
|
}
|
|
@@ -9479,6 +10304,10 @@ function stableWorldInteractionRecordUid(userId, targetRecordUid, clientMutation
|
|
|
9479
10304
|
const hex = createHash("sha256").update(`dsh-arkme:world-interaction:${String(userId)}:${targetRecordUid}:${clientMutationId}`).digest("hex");
|
|
9480
10305
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
9481
10306
|
}
|
|
10307
|
+
function stableWorldPublishRecordUid(userId, clientMutationId) {
|
|
10308
|
+
const hex = createHash("sha256").update(`dsh-arkme:world-publish:${String(userId)}:${clientMutationId}`).digest("hex");
|
|
10309
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
10310
|
+
}
|
|
9482
10311
|
function allowedSignedImageHost(environment, hostname) {
|
|
9483
10312
|
return (environment === "prod" ? ["jotmo-userfiles.oss-cn-hangzhou.aliyuncs.com", "userfiles.jotmo.cc"] : ["jotmo-userfiles-test.oss-cn-hangzhou.aliyuncs.com", "jotmo-userfiles.senguo.me"]).includes(hostname.toLowerCase());
|
|
9484
10313
|
}
|
|
@@ -9511,13 +10340,24 @@ function worldAvatarResolutionKey(ownerUserId, avatarRef) {
|
|
|
9511
10340
|
if (!Number.isSafeInteger(ownerUserId) || ownerUserId <= 0 || !normalized.startsWith("file_asset://")) return "";
|
|
9512
10341
|
return `${String(ownerUserId)}|${normalized}`;
|
|
9513
10342
|
}
|
|
10343
|
+
function worldImageAssetIdentity(raw) {
|
|
10344
|
+
try {
|
|
10345
|
+
const parsed = new URL(raw.trim());
|
|
10346
|
+
return `${parsed.hostname.toLowerCase()}${parsed.pathname}`;
|
|
10347
|
+
} catch {
|
|
10348
|
+
return raw.trim();
|
|
10349
|
+
}
|
|
10350
|
+
}
|
|
9514
10351
|
var WorldService = class {
|
|
9515
10352
|
runtime;
|
|
9516
10353
|
profile;
|
|
9517
10354
|
media;
|
|
9518
10355
|
record;
|
|
9519
10356
|
worldImageRefs = /* @__PURE__ */ new Map();
|
|
10357
|
+
worldAvatarResolutionCache = /* @__PURE__ */ new Map();
|
|
9520
10358
|
worldRecordRefs = /* @__PURE__ */ new Map();
|
|
10359
|
+
voiceprintSocialCache = /* @__PURE__ */ new Map();
|
|
10360
|
+
voiceprintSocialInFlight = /* @__PURE__ */ new Map();
|
|
9521
10361
|
constructor(runtime, profile, media, record) {
|
|
9522
10362
|
this.runtime = runtime;
|
|
9523
10363
|
this.profile = profile;
|
|
@@ -9526,7 +10366,10 @@ var WorldService = class {
|
|
|
9526
10366
|
}
|
|
9527
10367
|
dispose() {
|
|
9528
10368
|
this.worldImageRefs.clear();
|
|
10369
|
+
this.worldAvatarResolutionCache.clear();
|
|
9529
10370
|
this.worldRecordRefs.clear();
|
|
10371
|
+
this.voiceprintSocialCache.clear();
|
|
10372
|
+
this.voiceprintSocialInFlight.clear();
|
|
9530
10373
|
}
|
|
9531
10374
|
async listWorldRecords(options = {}) {
|
|
9532
10375
|
const limit = Math.min(20, Math.max(1, Math.trunc(options.limit ?? 10)));
|
|
@@ -9571,22 +10414,61 @@ var WorldService = class {
|
|
|
9571
10414
|
async listMyWorldFeed(options = {}) {
|
|
9572
10415
|
const session = await this.runtime.requireSession();
|
|
9573
10416
|
const limit = Math.min(20, Math.max(1, Math.trunc(options.limit ?? 20)));
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
10417
|
+
let offset = Math.max(0, Math.trunc(options.offset ?? 0));
|
|
10418
|
+
let total = 0;
|
|
10419
|
+
let items = [];
|
|
10420
|
+
let hasMore = false;
|
|
10421
|
+
let attempts = 0;
|
|
10422
|
+
do {
|
|
10423
|
+
const data = await this.runtime.authenticatedWorldPost("/api/v1/public-record/my-list", {
|
|
10424
|
+
limit,
|
|
10425
|
+
offset
|
|
10426
|
+
}, session, options.signal);
|
|
10427
|
+
const rawItems = listValue(data.list);
|
|
10428
|
+
const rootItems = rawItems.filter(isTopLevelWorldRecord);
|
|
10429
|
+
const resolvedAvatars = await this.resolveWorldAvatarUrls(rootItems, session, options.signal);
|
|
10430
|
+
items = (await Promise.all(rootItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars)))).filter((item) => item !== void 0);
|
|
10431
|
+
total = Math.max(0, Math.trunc(numberValue(data.total)));
|
|
10432
|
+
offset += rawItems.length;
|
|
10433
|
+
hasMore = rawItems.length > 0 && offset < total;
|
|
10434
|
+
attempts += 1;
|
|
10435
|
+
} while (items.length === 0 && hasMore && attempts < 4);
|
|
9585
10436
|
return {
|
|
9586
10437
|
items,
|
|
9587
10438
|
total,
|
|
9588
10439
|
hasMore,
|
|
9589
|
-
...hasMore ? { nextOffset } : {}
|
|
10440
|
+
...hasMore ? { nextOffset: offset } : {}
|
|
10441
|
+
};
|
|
10442
|
+
}
|
|
10443
|
+
async listUserWorldFeed(userId, options = {}) {
|
|
10444
|
+
if (!Number.isSafeInteger(userId) || userId <= 0) throw new ArkmePluginError("world-user-id-invalid", "世界用户 ID 无效,请刷新后重试", false, 400);
|
|
10445
|
+
const session = await this.runtime.requireSession();
|
|
10446
|
+
const limit = Math.min(20, Math.max(1, Math.trunc(options.limit ?? 20)));
|
|
10447
|
+
let offset = Math.max(0, Math.trunc(options.offset ?? 0));
|
|
10448
|
+
let total = 0;
|
|
10449
|
+
let items = [];
|
|
10450
|
+
let hasMore = false;
|
|
10451
|
+
let attempts = 0;
|
|
10452
|
+
do {
|
|
10453
|
+
const data = await this.runtime.post(this.runtime.config.worldBaseUrl, "/api/public/v1/public-record/user-list", {
|
|
10454
|
+
user_id: userId,
|
|
10455
|
+
limit,
|
|
10456
|
+
offset
|
|
10457
|
+
}, void 0, [200], options.signal);
|
|
10458
|
+
const rawItems = listValue(data.list);
|
|
10459
|
+
total = Math.max(0, Math.trunc(numberValue(data.total)));
|
|
10460
|
+
const rootItems = rawItems.filter(isTopLevelWorldRecord);
|
|
10461
|
+
const resolvedAvatars = await this.resolveWorldAvatarUrls(rootItems, session, options.signal);
|
|
10462
|
+
items = (await Promise.all(rootItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars)))).filter((item) => item !== void 0);
|
|
10463
|
+
offset += rawItems.length;
|
|
10464
|
+
hasMore = rawItems.length > 0 && offset < total;
|
|
10465
|
+
attempts += 1;
|
|
10466
|
+
} while (items.length === 0 && hasMore && attempts < 4);
|
|
10467
|
+
return {
|
|
10468
|
+
items,
|
|
10469
|
+
total,
|
|
10470
|
+
hasMore,
|
|
10471
|
+
...hasMore ? { nextOffset: offset } : {}
|
|
9590
10472
|
};
|
|
9591
10473
|
}
|
|
9592
10474
|
async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
|
|
@@ -9625,7 +10507,8 @@ var WorldService = class {
|
|
|
9625
10507
|
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/generate-playback", {
|
|
9626
10508
|
source_scene: 4,
|
|
9627
10509
|
source_id: entry.recordUid,
|
|
9628
|
-
source_chunk_index: chunkIndex
|
|
10510
|
+
source_chunk_index: chunkIndex,
|
|
10511
|
+
source_chunk_max_runes: 120
|
|
9629
10512
|
}, session, input.signal, {
|
|
9630
10513
|
lane: "write",
|
|
9631
10514
|
bypassCache: true
|
|
@@ -9650,6 +10533,37 @@ var WorldService = class {
|
|
|
9650
10533
|
chunkEndRune
|
|
9651
10534
|
};
|
|
9652
10535
|
}
|
|
10536
|
+
async worldVoiceprintSocialContext(recordRef, options = {}) {
|
|
10537
|
+
const session = await this.runtime.requireSession();
|
|
10538
|
+
const entry = this.openWorldRecordRef(recordRef, session.userId);
|
|
10539
|
+
const authorUserId = entry.ownerUserId ?? 0;
|
|
10540
|
+
if (!Number.isSafeInteger(authorUserId) || authorUserId <= 0 || authorUserId === session.userId) return { relations: [] };
|
|
10541
|
+
const cacheKey = `${String(session.userId)}:${String(authorUserId)}`;
|
|
10542
|
+
const cached = this.voiceprintSocialCache.get(cacheKey);
|
|
10543
|
+
const age = cached === void 0 ? Number.POSITIVE_INFINITY : Date.now() - cached.savedAtMillis;
|
|
10544
|
+
if (options.forceRefresh !== true && cached !== void 0 && cached.context.relations.length > 0 && age < ARKME_VOICEPRINT_SOCIAL_CACHE_MILLIS) return cached.context;
|
|
10545
|
+
if (options.forceRefresh !== true && cached !== void 0 && cached.context.relations.length > 0 && age < ARKME_VOICEPRINT_SOCIAL_STALE_MILLIS) {
|
|
10546
|
+
this.refreshWorldVoiceprintSocialContext({
|
|
10547
|
+
cacheKey,
|
|
10548
|
+
flightKey: `${cacheKey}:${entry.recordUid}`,
|
|
10549
|
+
viewerUserId: session.userId,
|
|
10550
|
+
authorUserId,
|
|
10551
|
+
recordUid: entry.recordUid,
|
|
10552
|
+
fallback: cached,
|
|
10553
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
10554
|
+
});
|
|
10555
|
+
return cached.context;
|
|
10556
|
+
}
|
|
10557
|
+
return await this.refreshWorldVoiceprintSocialContext({
|
|
10558
|
+
cacheKey,
|
|
10559
|
+
flightKey: `${cacheKey}:${entry.recordUid}`,
|
|
10560
|
+
viewerUserId: session.userId,
|
|
10561
|
+
authorUserId,
|
|
10562
|
+
recordUid: entry.recordUid,
|
|
10563
|
+
...cached === void 0 ? {} : { fallback: cached },
|
|
10564
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
10565
|
+
});
|
|
10566
|
+
}
|
|
9653
10567
|
async createWorldVoiceprintInviteIntent(recordRef, signal) {
|
|
9654
10568
|
const session = await this.runtime.requireSession();
|
|
9655
10569
|
const entry = this.openWorldRecordRef(recordRef, session.userId);
|
|
@@ -9672,6 +10586,183 @@ var WorldService = class {
|
|
|
9672
10586
|
...entry.textPreview === void 0 ? {} : { textPreview: entry.textPreview }
|
|
9673
10587
|
};
|
|
9674
10588
|
}
|
|
10589
|
+
refreshWorldVoiceprintSocialContext(input) {
|
|
10590
|
+
const existing = this.voiceprintSocialInFlight.get(input.flightKey);
|
|
10591
|
+
if (existing !== void 0) return existing;
|
|
10592
|
+
const refresh = this.loadWorldVoiceprintSocialContext(input).then((result) => {
|
|
10593
|
+
const context = this.mergeWorldVoiceprintSocialContexts(input.fallback?.context, result.context);
|
|
10594
|
+
if (context.relations.length > 0 || result.allSourcesSucceeded) this.voiceprintSocialCache.set(input.cacheKey, {
|
|
10595
|
+
context,
|
|
10596
|
+
savedAtMillis: Date.now()
|
|
10597
|
+
});
|
|
10598
|
+
return context;
|
|
10599
|
+
}).catch(() => input.fallback?.context.relations.length ? input.fallback.context : { relations: [] }).finally(() => {
|
|
10600
|
+
this.voiceprintSocialInFlight.delete(input.flightKey);
|
|
10601
|
+
});
|
|
10602
|
+
this.voiceprintSocialInFlight.set(input.flightKey, refresh);
|
|
10603
|
+
return refresh;
|
|
10604
|
+
}
|
|
10605
|
+
async loadWorldVoiceprintSocialContext(input) {
|
|
10606
|
+
const session = await this.runtime.requireSession();
|
|
10607
|
+
const directChat = this.loadWorldVoiceprintDirectChat(input.authorUserId, session, input.signal);
|
|
10608
|
+
const results = await Promise.all([
|
|
10609
|
+
this.guardWorldVoiceprintSocialSource(async () => await this.loadWorldVoiceprintReciprocalExpectation(input.authorUserId, session, input.signal)),
|
|
10610
|
+
this.guardWorldVoiceprintSocialSource(async () => await this.loadWorldVoiceprintInterwovenRelations(input.authorUserId, directChat, session, input.signal)),
|
|
10611
|
+
this.guardWorldVoiceprintSocialSource(async () => await this.loadWorldVoiceprintPrivateChatRelation(input.authorUserId, directChat)),
|
|
10612
|
+
this.guardWorldVoiceprintSocialSource(async () => await this.loadWorldVoiceprintCommentRelation(input.viewerUserId, input.recordUid, session, input.signal))
|
|
10613
|
+
]);
|
|
10614
|
+
return {
|
|
10615
|
+
context: { relations: this.dedupeWorldVoiceprintRelations(results.flatMap((result) => result.relations)) },
|
|
10616
|
+
allSourcesSucceeded: results.every((result) => result.succeeded)
|
|
10617
|
+
};
|
|
10618
|
+
}
|
|
10619
|
+
async guardWorldVoiceprintSocialSource(loader) {
|
|
10620
|
+
let timeout;
|
|
10621
|
+
try {
|
|
10622
|
+
return await Promise.race([loader(), new Promise((resolve) => {
|
|
10623
|
+
timeout = setTimeout(() => {
|
|
10624
|
+
resolve({
|
|
10625
|
+
succeeded: false,
|
|
10626
|
+
relations: []
|
|
10627
|
+
});
|
|
10628
|
+
}, ARKME_VOICEPRINT_SOCIAL_SOURCE_TIMEOUT_MILLIS);
|
|
10629
|
+
})]);
|
|
10630
|
+
} catch {
|
|
10631
|
+
return {
|
|
10632
|
+
succeeded: false,
|
|
10633
|
+
relations: []
|
|
10634
|
+
};
|
|
10635
|
+
} finally {
|
|
10636
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
10637
|
+
}
|
|
10638
|
+
}
|
|
10639
|
+
async loadWorldVoiceprintReciprocalExpectation(authorUserId, session, signal) {
|
|
10640
|
+
return {
|
|
10641
|
+
succeeded: true,
|
|
10642
|
+
relations: listValue(objectValue$1((await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/world-enrollment-interactions", {}, session, signal, {
|
|
10643
|
+
lane: "interactive-read",
|
|
10644
|
+
key: "world-enrollment-interactions",
|
|
10645
|
+
cacheMs: 5e3
|
|
10646
|
+
})).received).requesters).some((raw) => {
|
|
10647
|
+
const requester = objectValue$1(raw);
|
|
10648
|
+
return integerValue(requester.user_id ?? requester.userId) === authorUserId;
|
|
10649
|
+
}) ? [WORLD_VOICEPRINT_SOCIAL_RELATIONS.reciprocal_expectation] : []
|
|
10650
|
+
};
|
|
10651
|
+
}
|
|
10652
|
+
async loadWorldVoiceprintDirectChat(authorUserId, session, signal) {
|
|
10653
|
+
let pageCursor;
|
|
10654
|
+
const visited = /* @__PURE__ */ new Set();
|
|
10655
|
+
for (;;) {
|
|
10656
|
+
const data = await this.runtime.authenticatedChatPost("/api/v1/chats/list", {
|
|
10657
|
+
limit: 100,
|
|
10658
|
+
session_kind: 1,
|
|
10659
|
+
...pageCursor === void 0 ? {} : { page_cursor: pageCursor }
|
|
10660
|
+
}, session, signal, {
|
|
10661
|
+
lane: "interactive-read",
|
|
10662
|
+
bypassCache: true
|
|
10663
|
+
});
|
|
10664
|
+
for (const raw of listValue(data.items)) {
|
|
10665
|
+
const bundle = objectValue$1(raw);
|
|
10666
|
+
const chatSession = objectValue$1(bundle.session);
|
|
10667
|
+
const counterpart = objectValue$1(bundle.private_counterpart);
|
|
10668
|
+
const sessionKind = integerValue(chatSession.session_kind);
|
|
10669
|
+
if (sessionKind !== 1 && sessionKind !== 3 || integerValue(counterpart.user_id ?? counterpart.userId) !== authorUserId) continue;
|
|
10670
|
+
const latestPreview = objectValue$1(bundle.latest_preview);
|
|
10671
|
+
const latestRecord = objectValue$1(latestPreview.record);
|
|
10672
|
+
const latestRelation = objectValue$1(latestPreview.relation);
|
|
10673
|
+
const unread = objectValue$1(bundle.unread_snapshot);
|
|
10674
|
+
const sessionUid = stringValue$1(chatSession.chat_session_uid).trim();
|
|
10675
|
+
if (sessionUid === "") continue;
|
|
10676
|
+
const sharedTopicId = [
|
|
10677
|
+
chatSession,
|
|
10678
|
+
objectValue$1(chatSession.extra),
|
|
10679
|
+
objectValue$1(chatSession.policy_snapshot)
|
|
10680
|
+
].map((source) => integerValue(source.subject_id ?? source.subjectId ?? source.shared_topic_id ?? source.sharedTopicId)).find((value) => value > 0) ?? 0;
|
|
10681
|
+
const hasPrivateMessageEvidence = Object.keys(latestRecord).length > 0 || Object.keys(objectValue$1(latestRecord.payload)).length > 0 || stringValue$1(latestRecord.record_uid).trim() !== "" || stringValue$1(latestRelation.record_uid).trim() !== "" || integerValue(unread.session_last_seq ?? chatSession.last_seq) > 0;
|
|
10682
|
+
return {
|
|
10683
|
+
sessionUid,
|
|
10684
|
+
sharedTopicId: Math.max(0, sharedTopicId),
|
|
10685
|
+
hasPrivateMessageEvidence
|
|
10686
|
+
};
|
|
10687
|
+
}
|
|
10688
|
+
if (data.has_more !== true) return void 0;
|
|
10689
|
+
const nextCursor = objectValue$1(data.next_page_cursor);
|
|
10690
|
+
if (Object.keys(nextCursor).length === 0) throw new Error("voiceprint direct-chat pagination cursor missing");
|
|
10691
|
+
const identity = JSON.stringify(nextCursor);
|
|
10692
|
+
if (visited.has(identity)) throw new Error("voiceprint direct-chat pagination cursor repeated");
|
|
10693
|
+
visited.add(identity);
|
|
10694
|
+
pageCursor = nextCursor;
|
|
10695
|
+
}
|
|
10696
|
+
}
|
|
10697
|
+
async loadWorldVoiceprintInterwovenRelations(authorUserId, directChatFuture, session, signal) {
|
|
10698
|
+
const directChat = await directChatFuture;
|
|
10699
|
+
if (directChat === void 0 || authorUserId <= 0) return {
|
|
10700
|
+
succeeded: true,
|
|
10701
|
+
relations: []
|
|
10702
|
+
};
|
|
10703
|
+
const data = directChat.sharedTopicId > 0 ? await this.runtime.authenticatedWorldPost("/api/v1/interwoven-moments/summary", { subject_id: directChat.sharedTopicId }, session, signal, {
|
|
10704
|
+
lane: "interactive-read",
|
|
10705
|
+
bypassCache: true
|
|
10706
|
+
}) : await this.runtime.authenticatedChatPost("/api/v1/chats/interwoven/summary", {
|
|
10707
|
+
subject_id: 0,
|
|
10708
|
+
chat_session_uid: directChat.sessionUid
|
|
10709
|
+
}, session, signal, {
|
|
10710
|
+
lane: "interactive-read",
|
|
10711
|
+
bypassCache: true
|
|
10712
|
+
});
|
|
10713
|
+
const stats = listValue(objectValue$1(data.recent_card).total_stats);
|
|
10714
|
+
const countFor = (momentType) => {
|
|
10715
|
+
return integerValue(objectValue$1(stats.find((value) => integerValue(objectValue$1(value).moment_type) === momentType)).count);
|
|
10716
|
+
};
|
|
10717
|
+
const relations = [];
|
|
10718
|
+
const callSummary = data.call_summary;
|
|
10719
|
+
if ((callSummary !== null && typeof callSummary === "object" && !Array.isArray(callSummary) ? integerValue(objectValue$1(callSummary).total_call_count) : countFor(3)) > 0) relations.push(WORLD_VOICEPRINT_SOCIAL_RELATIONS.call);
|
|
10720
|
+
if (countFor(2) > 0) relations.push(WORLD_VOICEPRINT_SOCIAL_RELATIONS.world_interaction);
|
|
10721
|
+
const groupFromStatus = listValue(data.source_status).some((raw) => {
|
|
10722
|
+
const status = objectValue$1(raw);
|
|
10723
|
+
return integerValue(status.moment_type) === 1 && integerValue(status.item_count) > 0;
|
|
10724
|
+
});
|
|
10725
|
+
if (countFor(1) > 0 || groupFromStatus) relations.push(WORLD_VOICEPRINT_SOCIAL_RELATIONS.group_interaction);
|
|
10726
|
+
return {
|
|
10727
|
+
succeeded: true,
|
|
10728
|
+
relations
|
|
10729
|
+
};
|
|
10730
|
+
}
|
|
10731
|
+
async loadWorldVoiceprintPrivateChatRelation(authorUserId, directChatFuture) {
|
|
10732
|
+
const directChat = await directChatFuture;
|
|
10733
|
+
return {
|
|
10734
|
+
succeeded: true,
|
|
10735
|
+
relations: directChat !== void 0 && authorUserId > 0 && directChat.hasPrivateMessageEvidence ? [WORLD_VOICEPRINT_SOCIAL_RELATIONS.private_chat] : []
|
|
10736
|
+
};
|
|
10737
|
+
}
|
|
10738
|
+
async loadWorldVoiceprintCommentRelation(viewerUserId, recordUid, session, signal) {
|
|
10739
|
+
if (recordUid.trim() === "") return {
|
|
10740
|
+
succeeded: true,
|
|
10741
|
+
relations: []
|
|
10742
|
+
};
|
|
10743
|
+
return {
|
|
10744
|
+
succeeded: true,
|
|
10745
|
+
relations: listValue((await this.runtime.authenticatedWorldPost("/api/v1/public-record/extend-list", {
|
|
10746
|
+
record_uid: recordUid,
|
|
10747
|
+
limit: 100,
|
|
10748
|
+
offset: 0
|
|
10749
|
+
}, session, signal, {
|
|
10750
|
+
lane: "interactive-read",
|
|
10751
|
+
bypassCache: true
|
|
10752
|
+
})).list).some((raw) => {
|
|
10753
|
+
const item = objectValue$1(raw);
|
|
10754
|
+
return integerValue(item.user_id ?? item.userId) === viewerUserId;
|
|
10755
|
+
}) ? [WORLD_VOICEPRINT_SOCIAL_RELATIONS.world_interaction] : []
|
|
10756
|
+
};
|
|
10757
|
+
}
|
|
10758
|
+
dedupeWorldVoiceprintRelations(relations) {
|
|
10759
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10760
|
+
return relations.filter((relation) => !seen.has(relation.type) && seen.add(relation.type));
|
|
10761
|
+
}
|
|
10762
|
+
mergeWorldVoiceprintSocialContexts(previous, current) {
|
|
10763
|
+
if (previous === void 0 || previous.relations.length === 0) return current;
|
|
10764
|
+
return { relations: this.dedupeWorldVoiceprintRelations([...previous.relations, ...current.relations]) };
|
|
10765
|
+
}
|
|
9675
10766
|
async listWorldInteractions(recordRef, options = {}) {
|
|
9676
10767
|
const session = await this.runtime.requireSession();
|
|
9677
10768
|
const root = this.openWorldRecordRef(recordRef, session.userId);
|
|
@@ -9756,7 +10847,7 @@ var WorldService = class {
|
|
|
9756
10847
|
async readWorldImage(imageRef, options = {}) {
|
|
9757
10848
|
const session = await this.runtime.requireSession();
|
|
9758
10849
|
const entry = await this.openWorldImageRef(imageRef, session.userId);
|
|
9759
|
-
const byteLimit = Math.min(
|
|
10850
|
+
const byteLimit = Math.min(ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES, Math.max(1, Math.trunc(options.maxBytes ?? 20971520)));
|
|
9760
10851
|
return await this.media.downloadSignedImage(trustedWorldImageUrl(this.runtime.config.environment, entry.sourceUrl), byteLimit, options.signal);
|
|
9761
10852
|
}
|
|
9762
10853
|
async publishWorldTextForConversation(recordUid, textContent, signal) {
|
|
@@ -9817,18 +10908,88 @@ var WorldService = class {
|
|
|
9817
10908
|
avatar: profile.avatarRef,
|
|
9818
10909
|
template_kind: 1
|
|
9819
10910
|
}, void 0, signal);
|
|
9820
|
-
|
|
9821
|
-
|
|
10911
|
+
return worldPublicationResult(Math.trunc(numberValue(published.check_status)));
|
|
10912
|
+
} catch (error) {
|
|
10913
|
+
try {
|
|
10914
|
+
if (await this.worldRecordIsPublic(normalizedUid, signal)) return {
|
|
10915
|
+
recordSaved: true,
|
|
10916
|
+
recordState: "synced",
|
|
10917
|
+
worldPublished: true,
|
|
10918
|
+
visibility: "unknown",
|
|
10919
|
+
checkStatus: 0,
|
|
10920
|
+
retryable: false
|
|
10921
|
+
};
|
|
10922
|
+
} catch {}
|
|
10923
|
+
return this.worldPublishFailure(true, error, "synced");
|
|
10924
|
+
}
|
|
10925
|
+
}
|
|
10926
|
+
async publishWorldText(input) {
|
|
10927
|
+
const session = await this.runtime.requireSession();
|
|
10928
|
+
const clientMutationId = input.clientMutationId.trim();
|
|
10929
|
+
if (!/^[A-Za-z0-9_-]{16,128}$/.test(clientMutationId)) throw new ArkmePluginError("world-publish-mutation-invalid", "发布请求标识无效,请重试", false);
|
|
10930
|
+
return await this.publishWorldTextForConversation(stableWorldPublishRecordUid(session.userId, clientMutationId), input.textContent);
|
|
10931
|
+
}
|
|
10932
|
+
async publishWorldFileAssets(input) {
|
|
10933
|
+
const session = await this.runtime.requireSession();
|
|
10934
|
+
const clientMutationId = input.clientMutationId.trim();
|
|
10935
|
+
const textContent = input.textContent.trim();
|
|
10936
|
+
if (!/^[A-Za-z0-9_-]{16,128}$/.test(clientMutationId)) throw new ArkmePluginError("world-publish-mutation-invalid", "发布请求标识无效,请重试", false);
|
|
10937
|
+
if (textContent === "" || textContent.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("world-text-invalid", "世界内容为空或过长", false);
|
|
10938
|
+
if (input.fileAssets.length === 0 || input.fileAssets.length > 9 || input.fileAssets.some((asset) => !/^[A-Za-z0-9._:-]{8,256}$/.test(asset.fileAssetUid) || asset.fileName.trim() === "" || !asset.mimeType.toLowerCase().startsWith("image/") || !Number.isSafeInteger(asset.size) || asset.size <= 0 || asset.size > 20971520 || asset.fileKind !== 1)) throw new ArkmePluginError("world-publish-assets-invalid", "世界图片参数无效", false);
|
|
10939
|
+
let profile;
|
|
10940
|
+
try {
|
|
10941
|
+
const snapshot = await this.profile.refreshProfile();
|
|
10942
|
+
if (snapshot.profile === null) throw new ArkmePluginError("profile-unavailable", "无法读取当前 Arkme 账号资料", true);
|
|
10943
|
+
profile = snapshot.profile;
|
|
10944
|
+
} catch (error) {
|
|
10945
|
+
return this.worldPublishFailure(false, error);
|
|
10946
|
+
}
|
|
10947
|
+
if (profile.contact.phoneMasked === void 0) return {
|
|
10948
|
+
recordSaved: false,
|
|
10949
|
+
recordState: "not_saved",
|
|
10950
|
+
worldPublished: false,
|
|
10951
|
+
visibility: "not_published",
|
|
10952
|
+
checkStatus: 0,
|
|
10953
|
+
retryable: false,
|
|
10954
|
+
error: "请先在 Arkme 客户端绑定手机号,再发到世界"
|
|
10955
|
+
};
|
|
10956
|
+
const recordUid = stableWorldPublishRecordUid(session.userId, clientMutationId);
|
|
10957
|
+
try {
|
|
10958
|
+
if (await this.worldRecordIsPublic(recordUid)) return {
|
|
9822
10959
|
recordSaved: true,
|
|
9823
10960
|
recordState: "synced",
|
|
9824
10961
|
worldPublished: true,
|
|
9825
|
-
visibility:
|
|
9826
|
-
checkStatus,
|
|
10962
|
+
visibility: "unknown",
|
|
10963
|
+
checkStatus: 0,
|
|
9827
10964
|
retryable: false
|
|
9828
10965
|
};
|
|
10966
|
+
} catch {}
|
|
10967
|
+
try {
|
|
10968
|
+
await this.record.createFileAssetsForConversation(recordUid, textContent, input.fileAssets);
|
|
10969
|
+
} catch (error) {
|
|
10970
|
+
return this.worldPublishFailure(false, error);
|
|
10971
|
+
}
|
|
10972
|
+
try {
|
|
10973
|
+
const published = await this.runtime.authenticatedWorldPost("/api/v1/public-record/publish-with-file-assets", {
|
|
10974
|
+
record_uid: recordUid,
|
|
10975
|
+
content: textContent,
|
|
10976
|
+
text_content: textContent,
|
|
10977
|
+
file_assets: input.fileAssets.map((asset, index) => ({
|
|
10978
|
+
file_asset_uid: asset.fileAssetUid,
|
|
10979
|
+
media_type: "image",
|
|
10980
|
+
file_kind: 1,
|
|
10981
|
+
sort_order: index
|
|
10982
|
+
})),
|
|
10983
|
+
tags: worldTags(textContent),
|
|
10984
|
+
original_topic_id: 0,
|
|
10985
|
+
created_at: Date.now(),
|
|
10986
|
+
nick_name: profile.nickname || profile.displayName,
|
|
10987
|
+
avatar: profile.avatarRef
|
|
10988
|
+
}, session);
|
|
10989
|
+
return worldPublicationResult(Math.trunc(numberValue(published.check_status)));
|
|
9829
10990
|
} catch (error) {
|
|
9830
10991
|
try {
|
|
9831
|
-
if (await this.worldRecordIsPublic(
|
|
10992
|
+
if (await this.worldRecordIsPublic(recordUid)) return {
|
|
9832
10993
|
recordSaved: true,
|
|
9833
10994
|
recordState: "synced",
|
|
9834
10995
|
worldPublished: true,
|
|
@@ -9876,11 +11037,11 @@ var WorldService = class {
|
|
|
9876
11037
|
const avatarUrl = rawAvatar.startsWith("file_asset://") ? resolvedAvatars.get(worldAvatarResolutionKey(ownerUserId, rawAvatar)) ?? "" : rawAvatar;
|
|
9877
11038
|
const avatarFallback = worldPhoneDefaultAvatar(rawAvatar);
|
|
9878
11039
|
const imageRefs = [];
|
|
9879
|
-
for (const signedUrl of rawImages.slice(0, 9)) {
|
|
11040
|
+
for (const [index, signedUrl] of rawImages.slice(0, 9).entries()) {
|
|
9880
11041
|
if (!this.isTrustedWorldImageUrl(signedUrl)) continue;
|
|
9881
|
-
imageRefs.push(await this.sealWorldImageRef(viewerUserId, signedUrl));
|
|
11042
|
+
imageRefs.push(await this.sealWorldImageRef(viewerUserId, signedUrl, `record:${recordUid}:image:${String(index)}:${worldImageAssetIdentity(signedUrl)}`));
|
|
9882
11043
|
}
|
|
9883
|
-
const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl) : void 0;
|
|
11044
|
+
const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl, `avatar:${String(ownerUserId)}:${rawAvatar.startsWith("file_asset://") ? rawAvatar : worldImageAssetIdentity(avatarUrl)}`) : void 0;
|
|
9884
11045
|
return {
|
|
9885
11046
|
recordRef: await this.worldRecordRef(viewerUserId, recordUid, {
|
|
9886
11047
|
ownerUserId,
|
|
@@ -9928,19 +11089,40 @@ var WorldService = class {
|
|
|
9928
11089
|
});
|
|
9929
11090
|
}
|
|
9930
11091
|
if (requested.size === 0) return /* @__PURE__ */ new Map();
|
|
11092
|
+
const now = Date.now();
|
|
11093
|
+
for (const [key, entry] of this.worldAvatarResolutionCache) if (entry.expiresAtMillis <= now) this.worldAvatarResolutionCache.delete(key);
|
|
11094
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
11095
|
+
for (const [key] of requested) {
|
|
11096
|
+
const cached = this.worldAvatarResolutionCache.get(key);
|
|
11097
|
+
if (cached === void 0) continue;
|
|
11098
|
+
this.worldAvatarResolutionCache.delete(key);
|
|
11099
|
+
this.worldAvatarResolutionCache.set(key, cached);
|
|
11100
|
+
resolved.set(key, cached.sourceUrl);
|
|
11101
|
+
}
|
|
11102
|
+
const unresolved = [...requested].filter(([key]) => !resolved.has(key)).map(([, value]) => value);
|
|
11103
|
+
if (unresolved.length === 0) return resolved;
|
|
9931
11104
|
let data;
|
|
9932
11105
|
try {
|
|
9933
|
-
data = await this.runtime.authenticatedAuthPost("/api/v1/auth/resolve-avatar-refs", { items:
|
|
11106
|
+
data = await this.runtime.authenticatedAuthPost("/api/v1/auth/resolve-avatar-refs", { items: unresolved }, session, signal);
|
|
9934
11107
|
} catch {
|
|
9935
|
-
return
|
|
11108
|
+
return resolved;
|
|
9936
11109
|
}
|
|
9937
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
9938
11110
|
for (const raw of listValue(data.items)) {
|
|
9939
11111
|
const item = objectValue$1(raw);
|
|
9940
11112
|
const key = worldAvatarResolutionKey(Math.trunc(numberValue(item.owner_user_id)), stringValue$1(item.avatar_ref).trim());
|
|
9941
11113
|
const url = stringValue$1(item.url).trim();
|
|
9942
11114
|
if (!requested.has(key) || !this.isTrustedWorldImageUrl(url)) continue;
|
|
9943
11115
|
resolved.set(key, url);
|
|
11116
|
+
this.worldAvatarResolutionCache.delete(key);
|
|
11117
|
+
this.worldAvatarResolutionCache.set(key, {
|
|
11118
|
+
sourceUrl: url,
|
|
11119
|
+
expiresAtMillis: Date.now() + ARKME_WORLD_AVATAR_RESOLUTION_CACHE_TTL_MILLIS
|
|
11120
|
+
});
|
|
11121
|
+
}
|
|
11122
|
+
while (this.worldAvatarResolutionCache.size > MAX_ARKME_WORLD_AVATAR_RESOLUTION_CACHE_ENTRIES) {
|
|
11123
|
+
const oldest = this.worldAvatarResolutionCache.keys().next().value;
|
|
11124
|
+
if (oldest === void 0) break;
|
|
11125
|
+
this.worldAvatarResolutionCache.delete(oldest);
|
|
9944
11126
|
}
|
|
9945
11127
|
return resolved;
|
|
9946
11128
|
}
|
|
@@ -9957,7 +11139,7 @@ var WorldService = class {
|
|
|
9957
11139
|
const rawAvatar = stringValue$1(item.avatar ?? item.head_img).trim();
|
|
9958
11140
|
const avatarUrl = rawAvatar.startsWith("file_asset://") ? resolvedAvatars.get(worldAvatarResolutionKey(ownerUserId, rawAvatar)) ?? "" : rawAvatar;
|
|
9959
11141
|
const avatarFallback = worldPhoneDefaultAvatar(rawAvatar);
|
|
9960
|
-
const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl) : void 0;
|
|
11142
|
+
const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl, `avatar:${String(ownerUserId)}:${rawAvatar.startsWith("file_asset://") ? rawAvatar : worldImageAssetIdentity(avatarUrl)}`) : void 0;
|
|
9961
11143
|
return {
|
|
9962
11144
|
interactionRef: await this.worldRecordRef(viewerUserId, recordUid, {
|
|
9963
11145
|
ownerUserId,
|
|
@@ -10022,11 +11204,12 @@ var WorldService = class {
|
|
|
10022
11204
|
this.worldImageRefs.delete(oldest);
|
|
10023
11205
|
}
|
|
10024
11206
|
}
|
|
10025
|
-
async sealWorldImageRef(viewerUserId, sourceUrl) {
|
|
11207
|
+
async sealWorldImageRef(viewerUserId, sourceUrl, stableIdentity) {
|
|
10026
11208
|
const now = Date.now();
|
|
10027
11209
|
this.pruneWorldImageRefs(now);
|
|
10028
|
-
const
|
|
10029
|
-
const
|
|
11210
|
+
const uniqueCode = await this.runtime.stateStore.uniqueCode();
|
|
11211
|
+
const token = createHmac("sha256", uniqueCode).update(`world-image-token-v1:${String(viewerUserId)}:${stableIdentity}`).digest("base64url");
|
|
11212
|
+
const signature = createHmac("sha256", uniqueCode).update(`world-image-v1:${String(viewerUserId)}:${token}`).digest("base64url");
|
|
10030
11213
|
this.worldImageRefs.set(token, {
|
|
10031
11214
|
viewerUserId,
|
|
10032
11215
|
sourceUrl,
|
|
@@ -10072,10 +11255,6 @@ var WorldService = class {
|
|
|
10072
11255
|
}
|
|
10073
11256
|
};
|
|
10074
11257
|
//#endregion
|
|
10075
|
-
//#region src/types.ts
|
|
10076
|
-
const ARKME_PROVIDER_CONTRACT_VERSION = 1;
|
|
10077
|
-
const ARKME_DEFAULT_SHARE_WEBSITE = "https://app.arkme.ai";
|
|
10078
|
-
//#endregion
|
|
10079
11258
|
//#region src/arkme-service.ts
|
|
10080
11259
|
function worldVoiceprintInviteMessage(input) {
|
|
10081
11260
|
return `${input.textPreview === void 0 ? "我想邀请你开启声纹。" : `我看到你在世界里发的「${input.textPreview}」,想邀请你开启声纹。`}\n以后看到你的世界动态时,可以直接听到你的声音。\n${input.inviteUrl}`;
|
|
@@ -10117,6 +11296,7 @@ var ArkmeService = class {
|
|
|
10117
11296
|
aiPolish;
|
|
10118
11297
|
chat;
|
|
10119
11298
|
contact;
|
|
11299
|
+
voiceprint;
|
|
10120
11300
|
constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker()) {
|
|
10121
11301
|
this.config = config;
|
|
10122
11302
|
this.sessionStore = sessionStore;
|
|
@@ -10151,9 +11331,10 @@ var ArkmeService = class {
|
|
|
10151
11331
|
this.community = new CommunityService(this.runtime, this.source, this.profile);
|
|
10152
11332
|
this.interwoven = new InterwovenService(this.runtime, this.source, this.profile);
|
|
10153
11333
|
this.aiPolish = new GroupAiPolishService(this.runtime, this.source, { sendChatSourceTextRaw: async (...args) => await this.chat.sendChatSourceTextRaw(...args) });
|
|
10154
|
-
this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session) => await this.chat.chatTimelineItems(data, session) });
|
|
11334
|
+
this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session, chatSessionUid) => await this.chat.chatTimelineItems(data, session, chatSessionUid) });
|
|
10155
11335
|
this.chat = new ChatService(this.runtime, this.source, this.profile, this.media, this.record, this.bot, this.arko, this.aiPolish, this.realtime);
|
|
10156
11336
|
this.contact = new ContactService(this.runtime, this.source, this.profile, this.realtime);
|
|
11337
|
+
this.voiceprint = new VoiceprintService(this.runtime, this.profile, { resolveRegisteredContactUserId: async (contactRef, session, signal) => await this.contact.resolveRegisteredContactUserId(contactRef, session, signal) });
|
|
10157
11338
|
this.auth = new AuthService(this.runtime, this.profile, {
|
|
10158
11339
|
reconnectChatRealtime: () => {
|
|
10159
11340
|
this.realtime.reconnect();
|
|
@@ -10236,6 +11417,7 @@ var ArkmeService = class {
|
|
|
10236
11417
|
environment: this.config.environment,
|
|
10237
11418
|
testLoginEnabled: this.config.environment === "test",
|
|
10238
11419
|
callAssetBasePath: `${this.config.routePath}/call`,
|
|
11420
|
+
voiceprintEnrollmentPath: `${this.config.routePath}/voiceprint/enroll`,
|
|
10239
11421
|
shareWebsite: this.config.shareWebsite ?? "https://app.arkme.ai"
|
|
10240
11422
|
};
|
|
10241
11423
|
}
|
|
@@ -10277,13 +11459,14 @@ var ArkmeService = class {
|
|
|
10277
11459
|
extensionPreviews: true,
|
|
10278
11460
|
worldFeed: true,
|
|
10279
11461
|
worldInteractions: true,
|
|
11462
|
+
worldPublish: true,
|
|
10280
11463
|
worldVoiceprintPlayback: true,
|
|
10281
11464
|
worldVoiceprintInvite: true,
|
|
11465
|
+
worldVoiceprintSocialContext: true,
|
|
11466
|
+
voiceprintManagement: true,
|
|
10282
11467
|
arrangements: true,
|
|
10283
11468
|
myExtensions: true,
|
|
10284
11469
|
extensionPublish: true,
|
|
10285
|
-
recordingTranscripts: true,
|
|
10286
|
-
recordingDoubaoBackfill: true,
|
|
10287
11470
|
extensionReviews: true,
|
|
10288
11471
|
...this.relatedRecording.isEnabled() ? { relatedRecordings: true } : {}
|
|
10289
11472
|
},
|
|
@@ -10370,12 +11553,8 @@ var ArkmeService = class {
|
|
|
10370
11553
|
return await this.recording.recordingCalendar(fromStamp, toStamp, signal);
|
|
10371
11554
|
}
|
|
10372
11555
|
/** Read-only Audio capability shared by the built-in UI and Arkme recording tools. */
|
|
10373
|
-
async recordingTranscript(dateStamp, signal
|
|
10374
|
-
return await this.recording.recordingTranscript(dateStamp, signal
|
|
10375
|
-
}
|
|
10376
|
-
/** Queue Doubao backfill only after an explicit UI, SDK, or Tool request. */
|
|
10377
|
-
async startRecordingDoubaoBackfill(dateStamp, signal) {
|
|
10378
|
-
return await this.recording.startRecordingDoubaoBackfill(dateStamp, signal);
|
|
11556
|
+
async recordingTranscript(dateStamp, signal) {
|
|
11557
|
+
return await this.recording.recordingTranscript(dateStamp, signal);
|
|
10379
11558
|
}
|
|
10380
11559
|
/** Read-only Audio capability shared by the built-in UI and Arkme recording tools. */
|
|
10381
11560
|
async recordingProjection(dateStamp, kind, signal) {
|
|
@@ -10498,6 +11677,12 @@ var ArkmeService = class {
|
|
|
10498
11677
|
async listGroupMembers(sourceRef, options = {}) {
|
|
10499
11678
|
return await this.group.listGroupMembers(sourceRef, options);
|
|
10500
11679
|
}
|
|
11680
|
+
async listSourceMembers(sourceRef, options = {}) {
|
|
11681
|
+
return await this.chat.listSourceMembers(sourceRef, options);
|
|
11682
|
+
}
|
|
11683
|
+
async sourceMemberRecords(sourceRef, memberRef, mode, options = {}) {
|
|
11684
|
+
return await this.chat.sourceMemberRecords(sourceRef, memberRef, mode, options);
|
|
11685
|
+
}
|
|
10501
11686
|
async listGroupMemberCandidates(sourceRef, options = {}) {
|
|
10502
11687
|
return await this.group.listGroupMemberCandidates(sourceRef, options);
|
|
10503
11688
|
}
|
|
@@ -10534,6 +11719,9 @@ var ArkmeService = class {
|
|
|
10534
11719
|
async openPrivateChatFromUser(peerUserId, options = {}) {
|
|
10535
11720
|
return await this.chat.openPrivateChatFromUser(peerUserId, options);
|
|
10536
11721
|
}
|
|
11722
|
+
async openPrivateChatFromMember(sourceRef, memberRef, options = {}) {
|
|
11723
|
+
return await this.chat.openPrivateChatFromMember(sourceRef, memberRef, options);
|
|
11724
|
+
}
|
|
10537
11725
|
async readSource(sourceRef, options = {}) {
|
|
10538
11726
|
return await this.chat.readSource(sourceRef, options);
|
|
10539
11727
|
}
|
|
@@ -10726,12 +11914,18 @@ var ArkmeService = class {
|
|
|
10726
11914
|
async listMyWorldFeed(options = {}) {
|
|
10727
11915
|
return await this.world.listMyWorldFeed(options);
|
|
10728
11916
|
}
|
|
11917
|
+
async listUserWorldFeed(userId, options = {}) {
|
|
11918
|
+
return await this.world.listUserWorldFeed(userId, options);
|
|
11919
|
+
}
|
|
10729
11920
|
async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
|
|
10730
11921
|
return await this.world.worldVoiceprintPlaybackAvailability(recordRefs, signal);
|
|
10731
11922
|
}
|
|
10732
11923
|
async generateWorldVoiceprintPlayback(input) {
|
|
10733
11924
|
return await this.world.generateWorldVoiceprintPlayback(input);
|
|
10734
11925
|
}
|
|
11926
|
+
async worldVoiceprintSocialContext(recordRef, options = {}) {
|
|
11927
|
+
return await this.world.worldVoiceprintSocialContext(recordRef, options);
|
|
11928
|
+
}
|
|
10735
11929
|
async inviteWorldVoiceprint(recordRef, signal) {
|
|
10736
11930
|
try {
|
|
10737
11931
|
const intent = await this.world.createWorldVoiceprintInviteIntent(recordRef, signal);
|
|
@@ -10757,6 +11951,36 @@ var ArkmeService = class {
|
|
|
10757
11951
|
throw error;
|
|
10758
11952
|
}
|
|
10759
11953
|
}
|
|
11954
|
+
async myVoiceprint(options = {}) {
|
|
11955
|
+
return await this.voiceprint.myVoiceprint(options);
|
|
11956
|
+
}
|
|
11957
|
+
async outboundVoiceprintGrants(input, options = {}) {
|
|
11958
|
+
return await this.voiceprint.outboundGrants(input, options);
|
|
11959
|
+
}
|
|
11960
|
+
async recognizedVoiceprintPeople(input, options = {}) {
|
|
11961
|
+
return await this.voiceprint.recognizedPeople(input, options);
|
|
11962
|
+
}
|
|
11963
|
+
async recognizedVoiceprintPerson(personRef, options = {}) {
|
|
11964
|
+
return await this.voiceprint.recognizedPerson(personRef, options);
|
|
11965
|
+
}
|
|
11966
|
+
async recognizedPersonVoiceprints(personRef, options = {}) {
|
|
11967
|
+
return await this.voiceprint.recognizedPersonVoiceprints(personRef, options);
|
|
11968
|
+
}
|
|
11969
|
+
async createVoiceprintInvitation(options = {}) {
|
|
11970
|
+
return await this.voiceprint.createInvitation(options);
|
|
11971
|
+
}
|
|
11972
|
+
async createRecognizedPersonVoiceprintInvitation(personRef, targetContactRef, options = {}) {
|
|
11973
|
+
return await this.voiceprint.createRecognizedPersonInvitation(personRef, targetContactRef, options);
|
|
11974
|
+
}
|
|
11975
|
+
async revokeVoiceprintPlaybackGrant(grantRef, options = {}) {
|
|
11976
|
+
return await this.voiceprint.revokePlaybackGrant(grantRef, options);
|
|
11977
|
+
}
|
|
11978
|
+
async restoreVoiceprintPlayback(options = {}) {
|
|
11979
|
+
return await this.voiceprint.restorePlayback(options);
|
|
11980
|
+
}
|
|
11981
|
+
async bindVoiceprintEnrollment() {
|
|
11982
|
+
return await this.voiceprint.bindEnrollment();
|
|
11983
|
+
}
|
|
10760
11984
|
/** Read the authenticated comment/reply tree behind one account-bound World reference. */
|
|
10761
11985
|
async listWorldInteractions(recordRef, options = {}) {
|
|
10762
11986
|
return await this.world.listWorldInteractions(recordRef, options);
|
|
@@ -10772,6 +11996,12 @@ var ArkmeService = class {
|
|
|
10772
11996
|
async publishWorldTextForConversation(recordUid, textContent, signal) {
|
|
10773
11997
|
return await this.world.publishWorldTextForConversation(recordUid, textContent, signal);
|
|
10774
11998
|
}
|
|
11999
|
+
async publishWorldText(input) {
|
|
12000
|
+
return await this.world.publishWorldText(input);
|
|
12001
|
+
}
|
|
12002
|
+
async publishWorldFileAssets(input) {
|
|
12003
|
+
return await this.world.publishWorldFileAssets(input);
|
|
12004
|
+
}
|
|
10775
12005
|
async createText(recordUid, textContent) {
|
|
10776
12006
|
return await this.record.createText(recordUid, textContent);
|
|
10777
12007
|
}
|
|
@@ -12156,4 +13386,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
|
|
|
12156
13386
|
return jsonValue(await handler(jsonValue(args)));
|
|
12157
13387
|
}
|
|
12158
13388
|
//#endregion
|
|
12159
|
-
export { unpackArkmeExtension as A,
|
|
13389
|
+
export { unpackArkmeExtension as A, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as B, packBundleFiles as C, assertExtensionArtifactSize as D, renderArkmeSandboxHostEntry as E, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as F, isArkmeBotAvatarRef as G, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as H, ArkmeService as I, ArkmePluginError as K, validateVoiceprintPcm16Wav as L, ARKME_EXTENSION_ICON_MAX_BYTES as M, ARKME_EXTENSION_MAX_BYTES as N, canonicalExtensionJson as O, ARKME_EXTENSION_PREVIEW_MAX_BYTES as P, ARKME_DEFAULT_SHARE_WEBSITE as R, inspectNativeBundleArtifactV3 as S, readNativeBundleTarballV3 as T, ARKME_WORLD_PUBLISH_MAX_IMAGES as U, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as V, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as W, ArkmeBundleArtifactError as _, persistentArkmeExtensionActive as a, canonicalBundleJson as b, readPersistentExtensionActivation as c, verifyBundleResolutionSignature as d, verifyExtensionResolutionSignature as f, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as g, ARKME_BUNDLE_MAX_BYTES as h, invokePersistentArkmeExtension as i, ARKME_EXTENSION_FORMAT as j, sha256Hex as k, writePersistentExtensionActivation as l, ARKME_BUNDLE_ARTIFACT_KIND as m, applyPersistentArkmeHostExtension as n, persistentArkmeExtensionRuntimeState as o, verifyNativeBundleResolutionSignature as p, ArkmeOutgoingCallError as q, deactivatePersistentArkmeExtension as r, materializePersistentExtensionBundle as s, activatePersistentArkmeExtension as t, renderArkmeBundleClientBundle as u, arkmeSandboxEntryId as v, packLocalNativeBundleDirectoryV3 as w, inspectBundleArtifact as x, bundleSha256 as y, ARKME_PROVIDER_CONTRACT_VERSION as z };
|