@senguoyun/dsh-arkme 0.1.11 → 0.1.13
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/README.md +6 -2
- 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 +13 -0
- package/assets/desktop_call/icon-scan-add-contact.svg +4 -0
- package/assets/desktop_call/profile-circle-linear.svg +11 -0
- package/assets/desktop_call/user-add-linear.svg +15 -0
- package/cordis.patch.yml +4 -1
- package/docs/consumer-plugin-contract.md +13 -4
- package/docs/extension-market-controls.md +1 -1
- package/docs/gui-production-integration.md +46 -0
- package/docs/record-calendar-ui-flow.md +88 -0
- package/docs/tool-registry.md +4 -0
- package/lib/client.js +43275 -15385
- package/lib/client.js.map +1 -1
- package/lib/extension-profile-restart-helper.js +19 -3
- package/lib/index.js +2293 -420
- package/lib/{install-store-D6P-dH2i.js → install-store-Sn3Ccxfu.js} +32 -5
- package/lib/persistent-extension.js +2 -2
- package/lib/{persistent-runtime-DInn3Zb1.js → persistent-runtime-Cki0XC3L.js} +1941 -288
- package/lib/plugin-updater-helper-CiPlNqBc.js +346 -0
- package/lib/plugin-updater-helper.js +2 -250
- package/lib/sdk.js +202 -3
- package/lib/types/arkme-service.d.ts +67 -2
- package/lib/types/arkme-service.d.ts.map +1 -1
- package/lib/types/chat-realtime.d.ts +8 -0
- package/lib/types/chat-realtime.d.ts.map +1 -1
- package/lib/types/client/ArkmeAccountMenu.d.ts +4 -0
- package/lib/types/client/ArkmeAccountMenu.d.ts.map +1 -0
- package/lib/types/client/ArkmeAppUpdateDialog.d.ts +5 -0
- package/lib/types/client/ArkmeAppUpdateDialog.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/ArkmeBrand.d.ts +35 -0
- package/lib/types/client/ArkmeBrand.d.ts.map +1 -0
- package/lib/types/client/ArkmeCalendarSurface.d.ts +10 -0
- package/lib/types/client/ArkmeCalendarSurface.d.ts.map +1 -0
- package/lib/types/client/ArkmeCallHistorySurface.d.ts +57 -0
- package/lib/types/client/ArkmeCallHistorySurface.d.ts.map +1 -0
- package/lib/types/client/ArkmeContactAddSurface.d.ts +9 -0
- package/lib/types/client/ArkmeContactAddSurface.d.ts.map +1 -0
- package/lib/types/client/ArkmeConversationSurface.d.ts +5 -3
- package/lib/types/client/ArkmeConversationSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeExtensionAvatar.d.ts.map +1 -1
- package/lib/types/client/ArkmeExtensionPublishDialog.d.ts.map +1 -1
- package/lib/types/client/ArkmeExtensionReviews.d.ts +10 -0
- package/lib/types/client/ArkmeExtensionReviews.d.ts.map +1 -1
- package/lib/types/client/ArkmeFooterDropdown.d.ts +1 -1
- package/lib/types/client/ArkmeFooterDropdown.d.ts.map +1 -1
- package/lib/types/client/ArkmeGroupChatControls.d.ts +1 -0
- 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 +153 -0
- package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -0
- package/lib/types/client/ArkmePrivateCallMenu.d.ts.map +1 -1
- package/lib/types/client/ArkmeProductNavigation.d.ts +8 -0
- package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -0
- package/lib/types/client/ArkmeQuickAdd.d.ts +12 -0
- package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -0
- package/lib/types/client/ArkmeRecordingSurface.d.ts +109 -3
- package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeSearchSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeSendToSelfIcon.d.ts +5 -0
- package/lib/types/client/ArkmeSendToSelfIcon.d.ts.map +1 -0
- package/lib/types/client/ArkmeSettingsRow.d.ts.map +1 -1
- package/lib/types/client/ArkmeSettingsSurface.d.ts +22 -0
- package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -0
- package/lib/types/client/ArkmeSharedExtensionDetail.d.ts +7 -0
- package/lib/types/client/ArkmeSharedExtensionDetail.d.ts.map +1 -0
- package/lib/types/client/ArkmeSidebar.d.ts +11 -2
- package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
- package/lib/types/client/ArkmeTopicCreateDialog.d.ts +1 -1
- package/lib/types/client/ArkmeTopicCreateDialog.d.ts.map +1 -1
- package/lib/types/client/ArkmeTopicDirectoryPopover.d.ts +1 -0
- package/lib/types/client/ArkmeTopicDirectoryPopover.d.ts.map +1 -1
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts +18 -1
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
- package/lib/types/client/ArkmeWorldSurface.d.ts +30 -0
- package/lib/types/client/ArkmeWorldSurface.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/app-update-store.d.ts +35 -0
- package/lib/types/client/app-update-store.d.ts.map +1 -0
- package/lib/types/client/arkme-assets.d.ts +2 -2
- package/lib/types/client/arkme-assets.d.ts.map +1 -1
- package/lib/types/client/arkme-theme.d.ts +5 -3
- package/lib/types/client/arkme-theme.d.ts.map +1 -1
- package/lib/types/client/arkme-wordmark.d.ts +3 -0
- package/lib/types/client/arkme-wordmark.d.ts.map +1 -0
- package/lib/types/client/demo-audio.d.ts +2 -0
- package/lib/types/client/demo-audio.d.ts.map +1 -0
- package/lib/types/client/desktop-notification-runtime.d.ts +35 -0
- package/lib/types/client/desktop-notification-runtime.d.ts.map +1 -0
- package/lib/types/client/extension-market-model.d.ts +1 -0
- package/lib/types/client/extension-market-model.d.ts.map +1 -1
- package/lib/types/client/extension-share-deeplink.d.ts +5 -0
- package/lib/types/client/extension-share-deeplink.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +15 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/interwoven-moments.d.ts +1 -1
- package/lib/types/client/interwoven-moments.d.ts.map +1 -1
- package/lib/types/client/notification-activation-store.d.ts +16 -0
- package/lib/types/client/notification-activation-store.d.ts.map +1 -0
- package/lib/types/client/personal-test-edition.d.ts +17 -0
- package/lib/types/client/personal-test-edition.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/ui-controller.d.ts +13 -1
- package/lib/types/client/ui-controller.d.ts.map +1 -1
- package/lib/types/extensions/bundle-artifact.d.ts +22 -0
- package/lib/types/extensions/bundle-artifact.d.ts.map +1 -1
- 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 +52 -2
- package/lib/types/extensions/manager.d.ts.map +1 -1
- package/lib/types/extensions/owned-inventory.d.ts +6 -3
- package/lib/types/extensions/owned-inventory.d.ts.map +1 -1
- package/lib/types/extensions/owned-refs.d.ts +1 -1
- package/lib/types/extensions/owned-refs.d.ts.map +1 -1
- package/lib/types/extensions/owned-types.d.ts +19 -5
- package/lib/types/extensions/owned-types.d.ts.map +1 -1
- package/lib/types/extensions/persistent-bundle.d.ts +7 -0
- package/lib/types/extensions/persistent-bundle.d.ts.map +1 -1
- package/lib/types/extensions/persistent-client-bundle.d.ts.map +1 -1
- package/lib/types/extensions/persistent-runtime.d.ts +6 -0
- package/lib/types/extensions/persistent-runtime.d.ts.map +1 -1
- package/lib/types/extensions/profile-owned-inventory.d.ts +1 -0
- package/lib/types/extensions/profile-owned-inventory.d.ts.map +1 -1
- package/lib/types/extensions/profile-restart-helper.d.ts.map +1 -1
- package/lib/types/extensions/publish-client.d.ts +51 -2
- package/lib/types/extensions/publish-client.d.ts.map +1 -1
- package/lib/types/extensions/share-discovery-relay.d.ts +33 -0
- package/lib/types/extensions/share-discovery-relay.d.ts.map +1 -0
- package/lib/types/extensions/signature.d.ts +31 -1
- package/lib/types/extensions/signature.d.ts.map +1 -1
- package/lib/types/extensions/types.d.ts +118 -5
- package/lib/types/extensions/types.d.ts.map +1 -1
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/index.d.ts +12 -4
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/outgoing-call-assets.d.ts.map +1 -1
- package/lib/types/plugin-update-artifact.d.ts +46 -0
- package/lib/types/plugin-update-artifact.d.ts.map +1 -0
- package/lib/types/plugin-update.d.ts +20 -11
- package/lib/types/plugin-update.d.ts.map +1 -1
- package/lib/types/plugin-updater-helper.d.ts +11 -0
- package/lib/types/plugin-updater-helper.d.ts.map +1 -1
- package/lib/types/recording-presentation.d.ts +2 -2
- package/lib/types/recording-presentation.d.ts.map +1 -1
- package/lib/types/rich-media-routes.d.ts.map +1 -1
- package/lib/types/sdk/index.d.ts +103 -7
- package/lib/types/sdk/index.d.ts.map +1 -1
- package/lib/types/services/bot-service.d.ts +5 -1
- package/lib/types/services/bot-service.d.ts.map +1 -1
- package/lib/types/services/calendar-service.d.ts +25 -0
- package/lib/types/services/calendar-service.d.ts.map +1 -0
- package/lib/types/services/chat-realtime-service.d.ts +19 -4
- package/lib/types/services/chat-realtime-service.d.ts.map +1 -1
- package/lib/types/services/contact-service.d.ts +25 -0
- package/lib/types/services/contact-service.d.ts.map +1 -0
- package/lib/types/services/group-service.d.ts +20 -2
- package/lib/types/services/group-service.d.ts.map +1 -1
- package/lib/types/services/media-service.d.ts +9 -1
- package/lib/types/services/media-service.d.ts.map +1 -1
- package/lib/types/services/recording-service.d.ts +4 -2
- package/lib/types/services/recording-service.d.ts.map +1 -1
- package/lib/types/services/search-service.d.ts +10 -2
- package/lib/types/services/search-service.d.ts.map +1 -1
- package/lib/types/services/service.d.ts +2 -0
- package/lib/types/services/service.d.ts.map +1 -1
- package/lib/types/services/world-service.d.ts +20 -1
- package/lib/types/services/world-service.d.ts.map +1 -1
- package/lib/types/tools/business/calendar/index.d.ts +4 -0
- package/lib/types/tools/business/calendar/index.d.ts.map +1 -0
- package/lib/types/tools/business/contacts/index.d.ts +4 -0
- package/lib/types/tools/business/contacts/index.d.ts.map +1 -0
- package/lib/types/tools/business/groups/create.d.ts +2 -0
- package/lib/types/tools/business/groups/create.d.ts.map +1 -0
- package/lib/types/tools/business/groups/index.d.ts +4 -0
- package/lib/types/tools/business/groups/index.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 +2 -1
- package/lib/types/tools/business/recordings/index.d.ts.map +1 -1
- package/lib/types/tools/business/records/images.d.ts +2 -0
- package/lib/types/tools/business/records/images.d.ts.map +1 -0
- package/lib/types/tools/business/records/index.d.ts.map +1 -1
- 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/mine.d.ts +2 -0
- package/lib/types/tools/business/world/mine.d.ts.map +1 -0
- package/lib/types/tools/business/world/voiceprint-invite.d.ts +2 -0
- package/lib/types/tools/business/world/voiceprint-invite.d.ts.map +1 -0
- package/lib/types/tools/extensions/index.d.ts.map +1 -1
- package/lib/types/tools/extensions/publish-conversation.d.ts +9 -1
- package/lib/types/tools/extensions/publish-conversation.d.ts.map +1 -1
- package/lib/types/tools/ports/calendar.d.ts +17 -0
- package/lib/types/tools/ports/calendar.d.ts.map +1 -0
- package/lib/types/tools/ports/contacts.d.ts +12 -0
- package/lib/types/tools/ports/contacts.d.ts.map +1 -0
- package/lib/types/tools/ports/groups.d.ts +14 -0
- package/lib/types/tools/ports/groups.d.ts.map +1 -0
- package/lib/types/tools/ports/index.d.ts +5 -2
- package/lib/types/tools/ports/index.d.ts.map +1 -1
- package/lib/types/tools/ports/recordings.d.ts +3 -2
- package/lib/types/tools/ports/recordings.d.ts.map +1 -1
- package/lib/types/tools/ports/records.d.ts +6 -1
- package/lib/types/tools/ports/records.d.ts.map +1 -1
- package/lib/types/tools/ports/world.d.ts +12 -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 +218 -3
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +6 -4
- package/lib/plugin-update-install-state-CIsOCjWK.js +0 -67
- package/lib/types/client/ArkmeExtensionCenter.d.ts +0 -61
- package/lib/types/client/ArkmeExtensionCenter.d.ts.map +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { chmodSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { createHash, createHmac, createPublicKey, randomUUID, timingSafeEqual, verify } from "node:crypto";
|
|
3
3
|
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { open } from "node:fs/promises";
|
|
5
6
|
import OSS from "ali-oss";
|
|
7
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
6
8
|
import { createContext, runInContext } from "node:vm";
|
|
7
9
|
import { Context } from "@deepseek-ai/cordis";
|
|
8
10
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
9
|
-
import { gunzipSync, gzipSync } from "node:zlib";
|
|
10
11
|
import { parseDocument } from "yaml";
|
|
11
12
|
//#region src/outgoing-call-contract.ts
|
|
12
13
|
var ArkmeOutgoingCallError = class extends Error {
|
|
@@ -957,6 +958,17 @@ var ServiceRuntime = class {
|
|
|
957
958
|
return await this.post(this.config.recordBaseUrl, path, body, session.accessToken, [0], signal, false, requestOptions());
|
|
958
959
|
}
|
|
959
960
|
}
|
|
961
|
+
async authenticatedCalendarPost(path, body, initialSession, signal, options = {}) {
|
|
962
|
+
let session = initialSession ?? await this.requireSession();
|
|
963
|
+
const requestOptions = () => this.authenticatedRequestOptions(session, "record", "interactive-read", options);
|
|
964
|
+
try {
|
|
965
|
+
return await this.post(this.config.recordBaseUrl, path, body, session.accessToken, [0, 200], signal, false, requestOptions());
|
|
966
|
+
} catch (error) {
|
|
967
|
+
if (!(error instanceof ArkmePluginError) || !["auth-http-401", "auth-http-403"].includes(error.code)) throw error;
|
|
968
|
+
session = await this.refreshAccessToken(session);
|
|
969
|
+
return await this.post(this.config.recordBaseUrl, path, body, session.accessToken, [0, 200], signal, false, requestOptions());
|
|
970
|
+
}
|
|
971
|
+
}
|
|
960
972
|
async authenticatedAuthPost(path, body, initialSession, signal, options = {}) {
|
|
961
973
|
let session = initialSession ?? await this.requireSession();
|
|
962
974
|
const requestOptions = () => this.authenticatedRequestOptions(session, "auth", "write", options);
|
|
@@ -1060,7 +1072,7 @@ var ServiceRuntime = class {
|
|
|
1060
1072
|
}
|
|
1061
1073
|
async extensionPost(path, body, signal, options = {}) {
|
|
1062
1074
|
const baseUrl = this.config.extensionPublishBaseUrl?.trim() ?? "";
|
|
1063
|
-
if (baseUrl === "") throw new ArkmePluginError("extension-service-disabled", "
|
|
1075
|
+
if (baseUrl === "") throw new ArkmePluginError("extension-service-disabled", "市集服务尚未配置", false, 503);
|
|
1064
1076
|
let session = await this.requireSession();
|
|
1065
1077
|
const requestOptions = () => ({
|
|
1066
1078
|
...options,
|
|
@@ -1079,13 +1091,13 @@ var ServiceRuntime = class {
|
|
|
1079
1091
|
};
|
|
1080
1092
|
//#endregion
|
|
1081
1093
|
//#region src/services/ai-video-service.ts
|
|
1082
|
-
function numberValue$
|
|
1094
|
+
function numberValue$22(value) {
|
|
1083
1095
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1084
1096
|
}
|
|
1085
|
-
function booleanValue$
|
|
1097
|
+
function booleanValue$13(value) {
|
|
1086
1098
|
return value === true;
|
|
1087
1099
|
}
|
|
1088
|
-
function listValue$
|
|
1100
|
+
function listValue$21(value) {
|
|
1089
1101
|
return Array.isArray(value) ? value : [];
|
|
1090
1102
|
}
|
|
1091
1103
|
var AiVideoService = class {
|
|
@@ -1097,12 +1109,12 @@ var AiVideoService = class {
|
|
|
1097
1109
|
const session = await this.runtime.requireSession();
|
|
1098
1110
|
const data = await this.runtime.authenticatedIntelligentPost("/api/v1/ai-comic-video/preflight", this.aiVideoSelectionBody(sessionId, segments), session, signal);
|
|
1099
1111
|
return {
|
|
1100
|
-
allowed: booleanValue$
|
|
1112
|
+
allowed: booleanValue$13(data.allowed),
|
|
1101
1113
|
message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
|
|
1102
|
-
selectedDurationMillis: Math.max(0, numberValue$
|
|
1103
|
-
minimumDurationMillis: Math.max(0, numberValue$
|
|
1104
|
-
selectedSegmentCount: Math.max(0, numberValue$
|
|
1105
|
-
retryable: booleanValue$
|
|
1114
|
+
selectedDurationMillis: Math.max(0, numberValue$22(data.selected_duration_millis)),
|
|
1115
|
+
minimumDurationMillis: Math.max(0, numberValue$22(data.minimum_duration_millis)),
|
|
1116
|
+
selectedSegmentCount: Math.max(0, numberValue$22(data.selected_segment_count)),
|
|
1117
|
+
retryable: booleanValue$13(data.retryable),
|
|
1106
1118
|
...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
|
|
1107
1119
|
...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
|
|
1108
1120
|
};
|
|
@@ -1137,8 +1149,8 @@ var AiVideoService = class {
|
|
|
1137
1149
|
...options.statuses === void 0 || options.statuses.length === 0 ? {} : { statuses: options.statuses }
|
|
1138
1150
|
}, session, options.signal);
|
|
1139
1151
|
return {
|
|
1140
|
-
items: listValue$
|
|
1141
|
-
hasMore: booleanValue$
|
|
1152
|
+
items: listValue$21(data.items).map((raw) => this.aiVideoListItem(raw)).filter((item) => item !== void 0),
|
|
1153
|
+
hasMore: booleanValue$13(data.has_more),
|
|
1142
1154
|
...stringValue$1(data.next_cursor).trim() === "" ? {} : { nextCursor: stringValue$1(data.next_cursor).trim() }
|
|
1143
1155
|
};
|
|
1144
1156
|
}
|
|
@@ -1149,13 +1161,13 @@ var AiVideoService = class {
|
|
|
1149
1161
|
texts
|
|
1150
1162
|
}, session, signal);
|
|
1151
1163
|
return {
|
|
1152
|
-
allowed: booleanValue$
|
|
1164
|
+
allowed: booleanValue$13(data.allowed),
|
|
1153
1165
|
message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
|
|
1154
|
-
selectedDurationMillis: Math.max(0, numberValue$
|
|
1155
|
-
minimumDurationMillis: Math.max(0, numberValue$
|
|
1156
|
-
selectedSegmentCount: Math.max(0, numberValue$
|
|
1157
|
-
selectedTextCount: Math.max(0, numberValue$
|
|
1158
|
-
retryable: booleanValue$
|
|
1166
|
+
selectedDurationMillis: Math.max(0, numberValue$22(data.selected_duration_millis)),
|
|
1167
|
+
minimumDurationMillis: Math.max(0, numberValue$22(data.minimum_duration_millis)),
|
|
1168
|
+
selectedSegmentCount: Math.max(0, numberValue$22(data.selected_segment_count)),
|
|
1169
|
+
selectedTextCount: Math.max(0, numberValue$22(data.selected_text_count)),
|
|
1170
|
+
retryable: booleanValue$13(data.retryable),
|
|
1159
1171
|
...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
|
|
1160
1172
|
...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
|
|
1161
1173
|
};
|
|
@@ -1194,19 +1206,19 @@ var AiVideoService = class {
|
|
|
1194
1206
|
"canceled"
|
|
1195
1207
|
])).has(status)) throw new ArkmePluginError("ai-video-contract-invalid", "AI 视频服务返回了无效任务信息", true, 502);
|
|
1196
1208
|
const selection = objectValue$1(data.selection);
|
|
1197
|
-
const segmentCount = listValue$
|
|
1198
|
-
const textCount = listValue$
|
|
1209
|
+
const segmentCount = listValue$21(selection.segments).length;
|
|
1210
|
+
const textCount = listValue$21(selection.texts).length;
|
|
1199
1211
|
return {
|
|
1200
1212
|
jobId,
|
|
1201
1213
|
status,
|
|
1202
1214
|
stage: stringValue$1(data.stage).trim() || status,
|
|
1203
|
-
progress: Math.min(100, Math.max(0, Math.trunc(numberValue$
|
|
1215
|
+
progress: Math.min(100, Math.max(0, Math.trunc(numberValue$22(data.progress)))),
|
|
1204
1216
|
selectedSegmentCount: segmentCount,
|
|
1205
1217
|
...textCount === 0 ? {} : { selectedTextCount: textCount },
|
|
1206
|
-
retryable: booleanValue$
|
|
1218
|
+
retryable: booleanValue$13(data.retryable),
|
|
1207
1219
|
...stringValue$1(data.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(data.video_asset_uid).trim() },
|
|
1208
1220
|
...stringValue$1(data.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(data.cover_asset_uid).trim() },
|
|
1209
|
-
...numberValue$
|
|
1221
|
+
...numberValue$22(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$22(data.video_duration_millis) },
|
|
1210
1222
|
...stringValue$1(data.error_code).trim() === "" ? {} : { errorCode: stringValue$1(data.error_code).trim() },
|
|
1211
1223
|
...stringValue$1(data.error_message).trim() === "" ? {} : { errorMessage: stringValue$1(data.error_message).trim() },
|
|
1212
1224
|
...stringValue$1(data.failure_stage).trim() === "" ? {} : { failureStage: stringValue$1(data.failure_stage).trim() }
|
|
@@ -1229,17 +1241,17 @@ var AiVideoService = class {
|
|
|
1229
1241
|
sessionId: stringValue$1(item.session_id).trim(),
|
|
1230
1242
|
status,
|
|
1231
1243
|
stage: stringValue$1(item.stage).trim() || status,
|
|
1232
|
-
progress: Math.min(100, Math.max(0, Math.trunc(numberValue$
|
|
1244
|
+
progress: Math.min(100, Math.max(0, Math.trunc(numberValue$22(item.progress)))),
|
|
1233
1245
|
title: stringValue$1(source.title).trim() || "长录音 AI 视频",
|
|
1234
|
-
sourceStartedAtMillis: numberValue$
|
|
1235
|
-
selectedDurationMillis: Math.max(0, numberValue$
|
|
1236
|
-
selectedSegmentCount: Math.max(0, Math.trunc(numberValue$
|
|
1237
|
-
retryable: booleanValue$
|
|
1238
|
-
createdAtMillis: numberValue$
|
|
1239
|
-
updatedAtMillis: numberValue$
|
|
1246
|
+
sourceStartedAtMillis: numberValue$22(source.started_at),
|
|
1247
|
+
selectedDurationMillis: Math.max(0, numberValue$22(source.selected_duration_millis)),
|
|
1248
|
+
selectedSegmentCount: Math.max(0, Math.trunc(numberValue$22(item.selected_segment_count))),
|
|
1249
|
+
retryable: booleanValue$13(item.retryable),
|
|
1250
|
+
createdAtMillis: numberValue$22(item.created_at),
|
|
1251
|
+
updatedAtMillis: numberValue$22(item.updated_at),
|
|
1240
1252
|
...stringValue$1(item.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(item.cover_asset_uid).trim() },
|
|
1241
1253
|
...stringValue$1(item.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(item.video_asset_uid).trim() },
|
|
1242
|
-
...numberValue$
|
|
1254
|
+
...numberValue$22(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$22(item.video_duration_millis) },
|
|
1243
1255
|
...stringValue$1(item.error_code).trim() === "" ? {} : { errorCode: stringValue$1(item.error_code).trim() },
|
|
1244
1256
|
...stringValue$1(item.error_message).trim() === "" ? {} : { errorMessage: clippedText(item.error_message, 500) }
|
|
1245
1257
|
};
|
|
@@ -1265,13 +1277,13 @@ const ARKO_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
|
1265
1277
|
"expired"
|
|
1266
1278
|
]);
|
|
1267
1279
|
const ARKO_PROFILE_DISPLAY_NAME_CACHE_TTL_MS = 10 * 6e4;
|
|
1268
|
-
function numberValue$
|
|
1280
|
+
function numberValue$21(value) {
|
|
1269
1281
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1270
1282
|
}
|
|
1271
|
-
function booleanValue$
|
|
1283
|
+
function booleanValue$12(value) {
|
|
1272
1284
|
return value === true;
|
|
1273
1285
|
}
|
|
1274
|
-
function listValue$
|
|
1286
|
+
function listValue$20(value) {
|
|
1275
1287
|
return Array.isArray(value) ? value : [];
|
|
1276
1288
|
}
|
|
1277
1289
|
function optionalString$3(value) {
|
|
@@ -1300,7 +1312,7 @@ function joinUrlWithQuery(baseUrl, path, query) {
|
|
|
1300
1312
|
}
|
|
1301
1313
|
function arkoProfileFromData(data) {
|
|
1302
1314
|
const displayName = stringValue$1(data.display_name).trim();
|
|
1303
|
-
const version = numberValue$
|
|
1315
|
+
const version = numberValue$21(data.version);
|
|
1304
1316
|
if (displayName === "" || !Number.isSafeInteger(version) || version < 0) throw new ArkmePluginError("arko-profile-contract-invalid", "Arko 资料响应不完整", true, 502);
|
|
1305
1317
|
return {
|
|
1306
1318
|
displayName,
|
|
@@ -1324,15 +1336,15 @@ function arkoModelOptionFromData(value) {
|
|
|
1324
1336
|
displayName,
|
|
1325
1337
|
provider,
|
|
1326
1338
|
description,
|
|
1327
|
-
recommended: booleanValue$
|
|
1328
|
-
selected: booleanValue$
|
|
1339
|
+
recommended: booleanValue$12(data.recommended),
|
|
1340
|
+
selected: booleanValue$12(data.selected)
|
|
1329
1341
|
};
|
|
1330
1342
|
}
|
|
1331
1343
|
function arkoModelCatalogFromData(data) {
|
|
1332
1344
|
const defaultRouteKey = arkoModelRouteKey(data.default_route_key);
|
|
1333
1345
|
const effectiveRouteKey = arkoModelRouteKey(data.effective_route_key);
|
|
1334
1346
|
const selectionSource = stringValue$1(data.selection_source);
|
|
1335
|
-
const options = listValue$
|
|
1347
|
+
const options = listValue$20(data.items).map(arkoModelOptionFromData);
|
|
1336
1348
|
const routeKeys = new Set(options.map((option) => option.routeKey));
|
|
1337
1349
|
const selected = options.filter((option) => option.selected);
|
|
1338
1350
|
if (selectionSource !== "default" && selectionSource !== "personal" || options.length === 0 || options.length > 16 || routeKeys.size !== options.length || !routeKeys.has(defaultRouteKey) || !routeKeys.has(effectiveRouteKey) || selected.length !== 1 || selected[0]?.routeKey !== effectiveRouteKey) throw new ArkmePluginError("arko-model-contract-invalid", "Arko 模型目录响应无效", true, 502);
|
|
@@ -1353,12 +1365,12 @@ function parsedJsonRecord(value) {
|
|
|
1353
1365
|
}
|
|
1354
1366
|
function arkoHistoryItemFromData(value) {
|
|
1355
1367
|
const data = objectValue$1(value);
|
|
1356
|
-
const messageId = numberValue$
|
|
1357
|
-
const sessionId = numberValue$
|
|
1358
|
-
const roleCode = numberValue$
|
|
1368
|
+
const messageId = numberValue$21(data.id ?? data.msg_id ?? data.msgId);
|
|
1369
|
+
const sessionId = numberValue$21(data.session_id ?? data.sessionId);
|
|
1370
|
+
const roleCode = numberValue$21(data.role ?? data.sender_role ?? data.senderRole);
|
|
1359
1371
|
if (!Number.isSafeInteger(messageId) || messageId <= 0 || !Number.isSafeInteger(sessionId) || sessionId <= 0 || roleCode !== 2 && roleCode !== 3) return void 0;
|
|
1360
1372
|
const extra = parsedJsonRecord(data.extra ?? data.metadata ?? data.meta);
|
|
1361
|
-
const createdAt = numberValue$
|
|
1373
|
+
const createdAt = numberValue$21(data.created_at ?? data.create_at ?? data.createdAt ?? data.createAt);
|
|
1362
1374
|
const runUid = optionalString$3(extra.agent_run_uid);
|
|
1363
1375
|
const runStatus = optionalString$3(extra.agent_run_status);
|
|
1364
1376
|
const retryable = optionalBooleanValue$1(extra.agent_run_retryable);
|
|
@@ -1371,7 +1383,7 @@ function arkoHistoryItemFromData(value) {
|
|
|
1371
1383
|
text: stringValue$1(data.content ?? data.text_content ?? data.textContent),
|
|
1372
1384
|
reasoning: stringValue$1(data.reason_content ?? data.reason_text ?? data.reasonText),
|
|
1373
1385
|
createdAtMillis: createdAt > 0 && createdAt < 1e11 ? createdAt * 1e3 : createdAt,
|
|
1374
|
-
status: numberValue$
|
|
1386
|
+
status: numberValue$21(data.status ?? data.msg_status),
|
|
1375
1387
|
...runUid === void 0 ? {} : { runUid },
|
|
1376
1388
|
...runStatus === void 0 ? {} : { runStatus },
|
|
1377
1389
|
...retryable === void 0 ? {} : { retryable },
|
|
@@ -1390,14 +1402,14 @@ function arkoRunProjectionFromData(data) {
|
|
|
1390
1402
|
return {
|
|
1391
1403
|
runUid,
|
|
1392
1404
|
status,
|
|
1393
|
-
retryable: booleanValue$
|
|
1405
|
+
retryable: booleanValue$12(data.agent_run_retryable ?? data.retryable),
|
|
1394
1406
|
...errorCode === "" ? {} : { errorCode },
|
|
1395
1407
|
...retryOfRunUid === "" ? {} : { retryOfRunUid },
|
|
1396
1408
|
...clientAction === void 0 ? {} : { clientAction }
|
|
1397
1409
|
};
|
|
1398
1410
|
}
|
|
1399
1411
|
function arkoCreatedRecordUids(value) {
|
|
1400
|
-
return listValue$
|
|
1412
|
+
return listValue$20(value).map((item) => stringValue$1(item).trim()).filter((item) => item !== "");
|
|
1401
1413
|
}
|
|
1402
1414
|
function arkoStreamFrame(raw) {
|
|
1403
1415
|
try {
|
|
@@ -1426,7 +1438,7 @@ var ArkoService = class {
|
|
|
1426
1438
|
async arkoEnsureSession(signal) {
|
|
1427
1439
|
const session = await this.runtime.requireSession();
|
|
1428
1440
|
const latest = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/latest-session", { type: ARKO_AGENT_DIRECT_SESSION_TYPE }, session, signal);
|
|
1429
|
-
const latestSessionId = numberValue$
|
|
1441
|
+
const latestSessionId = numberValue$21(latest.id);
|
|
1430
1442
|
if (Number.isSafeInteger(latestSessionId) && latestSessionId > 0) return {
|
|
1431
1443
|
sessionId: latestSessionId,
|
|
1432
1444
|
created: false,
|
|
@@ -1436,7 +1448,7 @@ var ArkoService = class {
|
|
|
1436
1448
|
}
|
|
1437
1449
|
async arkoCreateSession(signal) {
|
|
1438
1450
|
const session = await this.runtime.requireSession();
|
|
1439
|
-
const createdSessionId = numberValue$
|
|
1451
|
+
const createdSessionId = numberValue$21((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-session", {
|
|
1440
1452
|
name: ARKO_DEFAULT_SESSION_NAME,
|
|
1441
1453
|
type: ARKO_AGENT_DIRECT_SESSION_TYPE
|
|
1442
1454
|
}, session, signal)).session_id);
|
|
@@ -1461,7 +1473,7 @@ var ArkoService = class {
|
|
|
1461
1473
|
const normalizedLimit = Math.min(ARKO_HISTORY_PAGE_LIMIT, Math.max(1, Math.trunc(limit)));
|
|
1462
1474
|
const normalizedOffset = Math.max(0, Math.trunc(offset));
|
|
1463
1475
|
const session = await this.runtime.requireSession();
|
|
1464
|
-
const rawItems = listValue$
|
|
1476
|
+
const rawItems = listValue$20((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/message-list", {
|
|
1465
1477
|
limit: normalizedLimit,
|
|
1466
1478
|
offset: normalizedOffset,
|
|
1467
1479
|
session_type: ARKO_AGENT_DIRECT_SESSION_TYPE
|
|
@@ -1504,9 +1516,9 @@ var ArkoService = class {
|
|
|
1504
1516
|
};
|
|
1505
1517
|
if (modelRouteKey !== "") body.model_route_key = modelRouteKey;
|
|
1506
1518
|
const accepted = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-msg-v2", body, session, options.signal);
|
|
1507
|
-
const userMsgId = numberValue$
|
|
1508
|
-
const assistantMsgId = numberValue$
|
|
1509
|
-
const acceptedSessionId = numberValue$
|
|
1519
|
+
const userMsgId = numberValue$21(accepted.user_msg_id);
|
|
1520
|
+
const assistantMsgId = numberValue$21(accepted.assistant_msg_id);
|
|
1521
|
+
const acceptedSessionId = numberValue$21(accepted.session_id) || sessionId;
|
|
1510
1522
|
if (!Number.isSafeInteger(acceptedSessionId) || acceptedSessionId <= 0 || !Number.isSafeInteger(userMsgId) || userMsgId <= 0 || !Number.isSafeInteger(assistantMsgId) || assistantMsgId <= 0) throw new ArkmePluginError("arko-ask-contract-invalid", "Arko 消息响应不完整", true, 502);
|
|
1511
1523
|
const runUid = stringValue$1(accepted.run_uid).trim();
|
|
1512
1524
|
if (runUid === "") throw new ArkmePluginError("arko-ask-contract-invalid", "Arko 消息响应缺少运行标识", true, 502);
|
|
@@ -1542,8 +1554,8 @@ var ArkoService = class {
|
|
|
1542
1554
|
}, session, signal);
|
|
1543
1555
|
const status = stringValue$1(data.status).trim();
|
|
1544
1556
|
const normalizedRunUid = stringValue$1(data.run_uid).trim() || runUid.trim();
|
|
1545
|
-
const sequence = numberValue$
|
|
1546
|
-
const surfaceAssistantMsgId = numberValue$
|
|
1557
|
+
const sequence = numberValue$21(data.sequence);
|
|
1558
|
+
const surfaceAssistantMsgId = numberValue$21(data.surface_assistant_msg_id);
|
|
1547
1559
|
const clientAction = optionalRecord(data.client_action);
|
|
1548
1560
|
if (normalizedRunUid !== runUid.trim() || !ARKO_RUN_STATUSES.has(status) || !Number.isSafeInteger(sequence) || sequence < 0 || !Number.isSafeInteger(surfaceAssistantMsgId) || surfaceAssistantMsgId <= 0) throw new ArkmePluginError("arko-run-status-contract-invalid", "Arko 运行状态响应不完整", true, 502);
|
|
1549
1561
|
return {
|
|
@@ -1552,7 +1564,7 @@ var ArkoService = class {
|
|
|
1552
1564
|
status,
|
|
1553
1565
|
sequence,
|
|
1554
1566
|
surfaceAssistantMsgId,
|
|
1555
|
-
retryable: booleanValue$
|
|
1567
|
+
retryable: booleanValue$12(data.retryable),
|
|
1556
1568
|
...stringValue$1(data.error_code).trim() === "" ? {} : { errorCode: stringValue$1(data.error_code).trim() },
|
|
1557
1569
|
...stringValue$1(data.retry_of_run_uid).trim() === "" ? {} : { retryOfRunUid: stringValue$1(data.retry_of_run_uid).trim() },
|
|
1558
1570
|
...clientAction === void 0 ? {} : { clientAction }
|
|
@@ -1727,12 +1739,12 @@ var ArkoService = class {
|
|
|
1727
1739
|
if (tailRun !== void 0) {
|
|
1728
1740
|
run = tailRun;
|
|
1729
1741
|
status = tailRun.status;
|
|
1730
|
-
} else if (booleanValue$
|
|
1742
|
+
} else if (booleanValue$12(tail.error) || stringValue$1(tail.status).trim() === "error") status = "failed";
|
|
1731
1743
|
else status = ARKO_COMPLETED_STATUS;
|
|
1732
1744
|
createdRecordUids = arkoCreatedRecordUids(tail.created_record_uids);
|
|
1733
1745
|
const nextProfile = optionalRecord(tail.agent_profile);
|
|
1734
1746
|
if (nextProfile !== void 0) profile = arkoProfileFromData(nextProfile);
|
|
1735
|
-
if (booleanValue$
|
|
1747
|
+
if (booleanValue$12(tail.error) || stringValue$1(tail.status).trim() === "error") errorMessage = stringValue$1(tail.error_message).trim() || "Arko 执行失败";
|
|
1736
1748
|
}
|
|
1737
1749
|
const type = stringValue$1(frame.type ?? frame.event).trim();
|
|
1738
1750
|
const payload = optionalRecord(frame.payload) ?? frame;
|
|
@@ -1781,13 +1793,13 @@ const ARKME_ARRANGEMENT_REF_TTL_MILLIS = 900 * 1e3;
|
|
|
1781
1793
|
const MAX_ARKME_ARRANGEMENT_REFS = 4096;
|
|
1782
1794
|
const ARKME_ARRANGEMENT_REMINDER_REF_TTL_MILLIS = 900 * 1e3;
|
|
1783
1795
|
const MAX_ARKME_ARRANGEMENT_REMINDER_REFS = 4096;
|
|
1784
|
-
function numberValue$
|
|
1796
|
+
function numberValue$20(value) {
|
|
1785
1797
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1786
1798
|
}
|
|
1787
|
-
function booleanValue$
|
|
1799
|
+
function booleanValue$11(value) {
|
|
1788
1800
|
return value === true;
|
|
1789
1801
|
}
|
|
1790
|
-
function listValue$
|
|
1802
|
+
function listValue$19(value) {
|
|
1791
1803
|
return Array.isArray(value) ? value : [];
|
|
1792
1804
|
}
|
|
1793
1805
|
function arrangementStatusCode(status) {
|
|
@@ -1797,7 +1809,7 @@ function arrangementStatusCode(status) {
|
|
|
1797
1809
|
return -1;
|
|
1798
1810
|
}
|
|
1799
1811
|
function arrangementStatus(value) {
|
|
1800
|
-
const code = Math.trunc(numberValue$
|
|
1812
|
+
const code = Math.trunc(numberValue$20(value));
|
|
1801
1813
|
if (code === 1) return "identified";
|
|
1802
1814
|
if (code === 2) return "following";
|
|
1803
1815
|
if (code === 3) return "completed";
|
|
@@ -1846,9 +1858,9 @@ var ArrangementService = class {
|
|
|
1846
1858
|
limit,
|
|
1847
1859
|
offset
|
|
1848
1860
|
}, session, options.signal, { lane: "interactive-read" });
|
|
1849
|
-
const rawItems = listValue$
|
|
1861
|
+
const rawItems = listValue$19(data.list);
|
|
1850
1862
|
const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementItem(raw, session.userId)));
|
|
1851
|
-
const total = Math.max(0, Math.trunc(numberValue$
|
|
1863
|
+
const total = Math.max(0, Math.trunc(numberValue$20(data.total)));
|
|
1852
1864
|
const nextOffset = offset + rawItems.length;
|
|
1853
1865
|
const hasMore = rawItems.length > 0 && nextOffset < total;
|
|
1854
1866
|
return {
|
|
@@ -1872,9 +1884,9 @@ var ArrangementService = class {
|
|
|
1872
1884
|
offset,
|
|
1873
1885
|
unread_only: options.unreadOnly === true
|
|
1874
1886
|
}, session, options.signal, { lane: "interactive-read" });
|
|
1875
|
-
const rawItems = listValue$
|
|
1887
|
+
const rawItems = listValue$19(data.list);
|
|
1876
1888
|
const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementReminderEvent(raw, session.userId)));
|
|
1877
|
-
const total = Math.max(0, Math.trunc(numberValue$
|
|
1889
|
+
const total = Math.max(0, Math.trunc(numberValue$20(data.total)));
|
|
1878
1890
|
const nextOffset = offset + rawItems.length;
|
|
1879
1891
|
const hasMore = rawItems.length > 0 && nextOffset < total;
|
|
1880
1892
|
return {
|
|
@@ -1897,7 +1909,7 @@ var ArrangementService = class {
|
|
|
1897
1909
|
project(data.next_reminder)
|
|
1898
1910
|
]);
|
|
1899
1911
|
return {
|
|
1900
|
-
unreadCount: Math.max(0, Math.trunc(numberValue$
|
|
1912
|
+
unreadCount: Math.max(0, Math.trunc(numberValue$20(data.unread_count))),
|
|
1901
1913
|
...latestUnread === void 0 ? {} : { latestUnread },
|
|
1902
1914
|
...latestEvent === void 0 ? {} : { latestEvent },
|
|
1903
1915
|
...nextReminder === void 0 ? {} : { nextReminder }
|
|
@@ -2001,7 +2013,7 @@ var ArrangementService = class {
|
|
|
2001
2013
|
}, session, signal);
|
|
2002
2014
|
return {
|
|
2003
2015
|
outcome: "confirmed",
|
|
2004
|
-
updatedCount: Math.max(0, Math.trunc(numberValue$
|
|
2016
|
+
updatedCount: Math.max(0, Math.trunc(numberValue$20(data.updated_count)))
|
|
2005
2017
|
};
|
|
2006
2018
|
} catch (error) {
|
|
2007
2019
|
if (!isAmbiguousArrangementWriteError(error)) throw error;
|
|
@@ -2019,7 +2031,7 @@ var ArrangementService = class {
|
|
|
2019
2031
|
}, session, signal);
|
|
2020
2032
|
return {
|
|
2021
2033
|
outcome: "confirmed",
|
|
2022
|
-
updatedCount: Math.max(0, Math.trunc(numberValue$
|
|
2034
|
+
updatedCount: Math.max(0, Math.trunc(numberValue$20(data.updated_count)))
|
|
2023
2035
|
};
|
|
2024
2036
|
} catch (error) {
|
|
2025
2037
|
if (!isAmbiguousArrangementWriteError(error)) throw error;
|
|
@@ -2034,7 +2046,7 @@ var ArrangementService = class {
|
|
|
2034
2046
|
const data = await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/reminders/clear", {}, session, signal);
|
|
2035
2047
|
return {
|
|
2036
2048
|
outcome: "confirmed",
|
|
2037
|
-
updatedCount: Math.max(0, Math.trunc(numberValue$
|
|
2049
|
+
updatedCount: Math.max(0, Math.trunc(numberValue$20(data.updated_count)))
|
|
2038
2050
|
};
|
|
2039
2051
|
} catch (error) {
|
|
2040
2052
|
if (!isAmbiguousArrangementWriteError(error)) throw error;
|
|
@@ -2067,7 +2079,7 @@ var ArrangementService = class {
|
|
|
2067
2079
|
}
|
|
2068
2080
|
async reconcileArrangementDelete(arrangementRef, arrangementUid, session) {
|
|
2069
2081
|
try {
|
|
2070
|
-
const visible = listValue$
|
|
2082
|
+
const visible = listValue$19((await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/list", {
|
|
2071
2083
|
status: -1,
|
|
2072
2084
|
uids: [arrangementUid],
|
|
2073
2085
|
limit: 1,
|
|
@@ -2098,17 +2110,17 @@ var ArrangementService = class {
|
|
|
2098
2110
|
const item = objectValue$1(raw);
|
|
2099
2111
|
const arrangementUid = stringValue$1(item.uid).trim();
|
|
2100
2112
|
if (arrangementUid === "") throw new ArkmePluginError("arrangement-contract-invalid", "安排响应缺少业务身份", true, 502);
|
|
2101
|
-
const dueAtMillis = Math.trunc(numberValue$
|
|
2102
|
-
const remindAtMillis = Math.trunc(numberValue$
|
|
2113
|
+
const dueAtMillis = Math.trunc(numberValue$20(item.due_at));
|
|
2114
|
+
const remindAtMillis = Math.trunc(numberValue$20(item.remind_at));
|
|
2103
2115
|
return {
|
|
2104
2116
|
arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
|
|
2105
2117
|
title: stringValue$1(item.title),
|
|
2106
2118
|
description: stringValue$1(item.description),
|
|
2107
2119
|
status: arrangementStatus(item.status),
|
|
2108
|
-
reminderEnabled: booleanValue$
|
|
2120
|
+
reminderEnabled: booleanValue$11(item.reminder_enabled),
|
|
2109
2121
|
reminderState: stringValue$1(item.reminder_state),
|
|
2110
|
-
createdAtMillis: Math.trunc(numberValue$
|
|
2111
|
-
updatedAtMillis: Math.trunc(numberValue$
|
|
2122
|
+
createdAtMillis: Math.trunc(numberValue$20(item.create_at)),
|
|
2123
|
+
updatedAtMillis: Math.trunc(numberValue$20(item.update_at)),
|
|
2112
2124
|
...dueAtMillis > 0 ? { dueAtMillis } : {},
|
|
2113
2125
|
...remindAtMillis > 0 ? { remindAtMillis } : {}
|
|
2114
2126
|
};
|
|
@@ -2129,20 +2141,20 @@ var ArrangementService = class {
|
|
|
2129
2141
|
const eventUid = stringValue$1(item.uid).trim();
|
|
2130
2142
|
const arrangementUid = stringValue$1(item.arrangement_uid).trim();
|
|
2131
2143
|
if (eventUid === "" || arrangementUid === "") throw new ArkmePluginError("arrangement-reminder-contract-invalid", "安排提醒响应缺少有效身份", true, 502);
|
|
2132
|
-
const dueAtMillis = Math.trunc(numberValue$
|
|
2133
|
-
const remindAtMillis = Math.trunc(numberValue$
|
|
2134
|
-
const readAtMillis = Math.trunc(numberValue$
|
|
2144
|
+
const dueAtMillis = Math.trunc(numberValue$20(item.due_at));
|
|
2145
|
+
const remindAtMillis = Math.trunc(numberValue$20(item.remind_at));
|
|
2146
|
+
const readAtMillis = Math.trunc(numberValue$20(item.read_at));
|
|
2135
2147
|
return {
|
|
2136
2148
|
eventRef: await this.arrangementReminderRef(viewerUserId, eventUid),
|
|
2137
2149
|
arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
|
|
2138
2150
|
title: stringValue$1(item.title),
|
|
2139
2151
|
description: stringValue$1(item.description),
|
|
2140
2152
|
eventKind: stringValue$1(item.event_kind),
|
|
2141
|
-
eventAtMillis: Math.trunc(numberValue$
|
|
2153
|
+
eventAtMillis: Math.trunc(numberValue$20(item.event_at)),
|
|
2142
2154
|
read: readAtMillis > 0,
|
|
2143
2155
|
reminderState: stringValue$1(item.reminder_state),
|
|
2144
|
-
createdAtMillis: Math.trunc(numberValue$
|
|
2145
|
-
updatedAtMillis: Math.trunc(numberValue$
|
|
2156
|
+
createdAtMillis: Math.trunc(numberValue$20(item.create_at)),
|
|
2157
|
+
updatedAtMillis: Math.trunc(numberValue$20(item.update_at)),
|
|
2146
2158
|
...dueAtMillis > 0 ? { dueAtMillis } : {},
|
|
2147
2159
|
...remindAtMillis > 0 ? { remindAtMillis } : {},
|
|
2148
2160
|
...readAtMillis > 0 ? { readAtMillis } : {}
|
|
@@ -2199,7 +2211,7 @@ var ArrangementService = class {
|
|
|
2199
2211
|
const ARKME_PHONE_BIND_SUCCESS = 1;
|
|
2200
2212
|
const ARKME_PHONE_BIND_REPEAT = 2;
|
|
2201
2213
|
const ARKME_PHONE_BIND_CODE_ERR = 3;
|
|
2202
|
-
function numberValue$
|
|
2214
|
+
function numberValue$19(value) {
|
|
2203
2215
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2204
2216
|
}
|
|
2205
2217
|
var AuthService = class {
|
|
@@ -2264,7 +2276,7 @@ var AuthService = class {
|
|
|
2264
2276
|
const data = await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/wechat-login-qrcode", {}, void 0, [200]);
|
|
2265
2277
|
const sceneStr = stringValue$1(data.scene_str).trim();
|
|
2266
2278
|
const qrContent = stringValue$1(data.url).trim();
|
|
2267
|
-
const expireSeconds = Math.max(30, numberValue$
|
|
2279
|
+
const expireSeconds = Math.max(30, numberValue$19(data.expire_seconds) || 300);
|
|
2268
2280
|
if (qrContent === "" && sceneStr !== "") throw new ArkmePluginError("wechat-qr-unavailable", "测试环境当前未返回可用微信二维码,请使用手机号登录", true, 503);
|
|
2269
2281
|
if (sceneStr === "" || qrContent === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录二维码响应不完整", true, 502);
|
|
2270
2282
|
const attemptId = crypto.randomUUID();
|
|
@@ -2300,7 +2312,7 @@ var AuthService = class {
|
|
|
2300
2312
|
ref: 0,
|
|
2301
2313
|
keep_cancel: true
|
|
2302
2314
|
}, void 0, [200]);
|
|
2303
|
-
const userId = numberValue$
|
|
2315
|
+
const userId = numberValue$19(data.user_id);
|
|
2304
2316
|
if (userId <= 0) return {
|
|
2305
2317
|
status: "pending",
|
|
2306
2318
|
environment: this.runtime.config.environment,
|
|
@@ -2344,7 +2356,7 @@ var AuthService = class {
|
|
|
2344
2356
|
const normalizedCaptcha = this.normalizedCaptcha(captcha);
|
|
2345
2357
|
const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
|
|
2346
2358
|
if (session !== void 0) {
|
|
2347
|
-
if (numberValue$
|
|
2359
|
+
if (numberValue$19((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/bind-phone-send-code", {
|
|
2348
2360
|
phone: normalizedPhone,
|
|
2349
2361
|
pre: "86",
|
|
2350
2362
|
is_test: this.runtime.config.environment === "test",
|
|
@@ -2366,7 +2378,7 @@ var AuthService = class {
|
|
|
2366
2378
|
if (!/^[0-9]{6}$/.test(normalizedCode)) throw new ArkmePluginError("phone-code-invalid", "请输入有效的短信验证码", false);
|
|
2367
2379
|
const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
|
|
2368
2380
|
if (session !== void 0) {
|
|
2369
|
-
const result = numberValue$
|
|
2381
|
+
const result = numberValue$19((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/verify-bind-phone", {
|
|
2370
2382
|
phone: normalizedPhone,
|
|
2371
2383
|
pre: "86",
|
|
2372
2384
|
code: normalizedCode,
|
|
@@ -2387,7 +2399,7 @@ var AuthService = class {
|
|
|
2387
2399
|
keep_cancel: true
|
|
2388
2400
|
}, void 0, [200]);
|
|
2389
2401
|
if (data.ok === false) throw new ArkmePluginError("phone-code-rejected", "手机号或验证码错误", false, 401);
|
|
2390
|
-
const userId = numberValue$
|
|
2402
|
+
const userId = numberValue$19(data.user_id);
|
|
2391
2403
|
const accessToken = stringValue$1(data.access_token);
|
|
2392
2404
|
const refreshToken = stringValue$1(data.refresh_token);
|
|
2393
2405
|
if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme手机号登录响应不完整", false, 502);
|
|
@@ -2454,19 +2466,19 @@ var SecretValue = class {
|
|
|
2454
2466
|
};
|
|
2455
2467
|
//#endregion
|
|
2456
2468
|
//#region src/services/bot-service.ts
|
|
2457
|
-
function numberValue$
|
|
2469
|
+
function numberValue$18(value) {
|
|
2458
2470
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2459
2471
|
}
|
|
2460
|
-
function booleanValue$
|
|
2472
|
+
function booleanValue$10(value) {
|
|
2461
2473
|
return value === true;
|
|
2462
2474
|
}
|
|
2463
|
-
function listValue$
|
|
2475
|
+
function listValue$18(value) {
|
|
2464
2476
|
return Array.isArray(value) ? value : [];
|
|
2465
2477
|
}
|
|
2466
|
-
function encodeOpaqueJson$
|
|
2478
|
+
function encodeOpaqueJson$6(value) {
|
|
2467
2479
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
2468
2480
|
}
|
|
2469
|
-
function decodeOpaqueJson$
|
|
2481
|
+
function decodeOpaqueJson$6(value) {
|
|
2470
2482
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
2471
2483
|
}
|
|
2472
2484
|
var BotService = class {
|
|
@@ -2501,7 +2513,7 @@ var BotService = class {
|
|
|
2501
2513
|
const session = await this.runtime.requireSession();
|
|
2502
2514
|
const data = await this.runtime.authenticatedBotPost("/api/v1/bot/list", {}, session, options.signal);
|
|
2503
2515
|
const items = [];
|
|
2504
|
-
for (const value of listValue$
|
|
2516
|
+
for (const value of listValue$18(data.bots)) {
|
|
2505
2517
|
const raw = objectValue$1(value);
|
|
2506
2518
|
const provider = stringValue$1(raw.provider).trim();
|
|
2507
2519
|
if (provider !== "openclaw" && provider !== "webhook") continue;
|
|
@@ -2540,6 +2552,10 @@ var BotService = class {
|
|
|
2540
2552
|
throw new ArkmePluginError("bot-create-outcome-unknown", "Bot 可能已创建,但响应不完整;请刷新 Bot 列表确认", false, 409, { cause: error });
|
|
2541
2553
|
}
|
|
2542
2554
|
}
|
|
2555
|
+
/** Browser-safe result: the one-time credential never leaves the Host owner. */
|
|
2556
|
+
async createBotSummary(input, options = {}) {
|
|
2557
|
+
return (await this.createBot(input, options)).bot;
|
|
2558
|
+
}
|
|
2543
2559
|
async revealBotSecret(botRef, options = {}) {
|
|
2544
2560
|
const session = await this.runtime.requireSession();
|
|
2545
2561
|
const reference = await this.openBotRef(botRef, session.userId);
|
|
@@ -2588,19 +2604,19 @@ var BotService = class {
|
|
|
2588
2604
|
const group = await this.openGroupSourceRef(groupSourceRef, session.userId);
|
|
2589
2605
|
const data = await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", { subject_uid: group.ownerRef }, session, options.signal);
|
|
2590
2606
|
const items = [];
|
|
2591
|
-
for (const value of listValue$
|
|
2607
|
+
for (const value of listValue$18(data.bots)) {
|
|
2592
2608
|
const raw = objectValue$1(value);
|
|
2593
2609
|
if (stringValue$1(raw.provider).trim() !== "openclaw") continue;
|
|
2594
2610
|
const { directChatAvailable: _directChatAvailable, ...summary } = await this.botSummaryFromData(raw, session.userId);
|
|
2595
2611
|
items.push({
|
|
2596
2612
|
...summary,
|
|
2597
|
-
installed: booleanValue$
|
|
2613
|
+
installed: booleanValue$10(raw.installed)
|
|
2598
2614
|
});
|
|
2599
2615
|
}
|
|
2600
2616
|
return {
|
|
2601
2617
|
groupSourceRef,
|
|
2602
2618
|
displayName: stringValue$1(data.subject_title).trim() || group.displayName,
|
|
2603
|
-
canAddBots: booleanValue$
|
|
2619
|
+
canAddBots: booleanValue$10(data.can_current_user_add_bots),
|
|
2604
2620
|
items
|
|
2605
2621
|
};
|
|
2606
2622
|
}
|
|
@@ -2653,7 +2669,7 @@ var BotService = class {
|
|
|
2653
2669
|
};
|
|
2654
2670
|
}
|
|
2655
2671
|
async sealBotRef(userId, botId) {
|
|
2656
|
-
const payload = encodeOpaqueJson$
|
|
2672
|
+
const payload = encodeOpaqueJson$6({
|
|
2657
2673
|
version: 1,
|
|
2658
2674
|
userId,
|
|
2659
2675
|
botId
|
|
@@ -2669,13 +2685,13 @@ var BotService = class {
|
|
|
2669
2685
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("bot-ref-invalid", "Bot 引用无效", false);
|
|
2670
2686
|
let raw;
|
|
2671
2687
|
try {
|
|
2672
|
-
raw = objectValue$1(decodeOpaqueJson$
|
|
2688
|
+
raw = objectValue$1(decodeOpaqueJson$6(payload));
|
|
2673
2689
|
} catch (error) {
|
|
2674
2690
|
throw new ArkmePluginError("bot-ref-invalid", "Bot 引用无效", false, 400, { cause: error });
|
|
2675
2691
|
}
|
|
2676
2692
|
const reference = {
|
|
2677
2693
|
version: 1,
|
|
2678
|
-
userId: numberValue$
|
|
2694
|
+
userId: numberValue$18(raw.userId),
|
|
2679
2695
|
botId: stringValue$1(raw.botId).trim()
|
|
2680
2696
|
};
|
|
2681
2697
|
if (raw.version !== 1 || reference.userId !== expectedUserId || reference.botId === "") throw new ArkmePluginError("bot-ref-invalid", "Bot 引用与当前账号不匹配", false, 403);
|
|
@@ -2683,6 +2699,181 @@ var BotService = class {
|
|
|
2683
2699
|
}
|
|
2684
2700
|
};
|
|
2685
2701
|
//#endregion
|
|
2702
|
+
//#region src/services/calendar-service.ts
|
|
2703
|
+
const MAX_CALENDAR_RANGE_DAYS = 62;
|
|
2704
|
+
const MAX_DAY_RECORD_LIMIT = 50;
|
|
2705
|
+
function numberValue$17(value) {
|
|
2706
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2707
|
+
}
|
|
2708
|
+
function booleanValue$9(value) {
|
|
2709
|
+
if (typeof value === "boolean") return value;
|
|
2710
|
+
if (typeof value === "number") return value !== 0;
|
|
2711
|
+
}
|
|
2712
|
+
function listValue$17(value) {
|
|
2713
|
+
return Array.isArray(value) ? value : [];
|
|
2714
|
+
}
|
|
2715
|
+
function readCalendarDate(value, field) {
|
|
2716
|
+
const normalized = value.trim();
|
|
2717
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(normalized);
|
|
2718
|
+
if (match === null) throw new ArkmePluginError("calendar-date-invalid", `${field} 必须是 YYYY-MM-DD`, false);
|
|
2719
|
+
const year = Number(match[1]);
|
|
2720
|
+
const month = Number(match[2]);
|
|
2721
|
+
const day = Number(match[3]);
|
|
2722
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
2723
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) throw new ArkmePluginError("calendar-date-invalid", `${field} 必须是真实日期`, false);
|
|
2724
|
+
return normalized;
|
|
2725
|
+
}
|
|
2726
|
+
function calendarDayNumber(date) {
|
|
2727
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
|
|
2728
|
+
if (match === null) throw new ArkmePluginError("calendar-date-invalid", "日期格式无效", false);
|
|
2729
|
+
const year = Number(match[1]);
|
|
2730
|
+
const month = Number(match[2]);
|
|
2731
|
+
const day = Number(match[3]);
|
|
2732
|
+
return Math.trunc(Date.UTC(year, month - 1, day) / 864e5);
|
|
2733
|
+
}
|
|
2734
|
+
function readTimezone(value) {
|
|
2735
|
+
const normalized = value?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
|
|
2736
|
+
if (normalized.length > 80) throw new ArkmePluginError("calendar-timezone-invalid", "timezone 无效", false);
|
|
2737
|
+
return normalized;
|
|
2738
|
+
}
|
|
2739
|
+
function boundedLimit(value) {
|
|
2740
|
+
const limit = Math.trunc(value ?? 20);
|
|
2741
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_DAY_RECORD_LIMIT) throw new ArkmePluginError("calendar-limit-invalid", `limit 必须是 1-${MAX_DAY_RECORD_LIMIT} 的整数`, false);
|
|
2742
|
+
return limit;
|
|
2743
|
+
}
|
|
2744
|
+
function contentAccessState(value) {
|
|
2745
|
+
const code = Math.trunc(numberValue$17(value));
|
|
2746
|
+
if (code === 1) return "available";
|
|
2747
|
+
if (code === 2) return "protected";
|
|
2748
|
+
return "unknown";
|
|
2749
|
+
}
|
|
2750
|
+
function sourceKind(raw) {
|
|
2751
|
+
const topic = objectValue$1(raw.topic_core);
|
|
2752
|
+
const chat = objectValue$1(raw.chat_core);
|
|
2753
|
+
if (stringValue$1(topic.topic_uid).trim() !== "") return "topic";
|
|
2754
|
+
if (stringValue$1(chat.chat_session_uid).trim() !== "") return "chat";
|
|
2755
|
+
if (booleanValue$9(raw.is_uncategorized) === true) return "self";
|
|
2756
|
+
return "unknown";
|
|
2757
|
+
}
|
|
2758
|
+
var CalendarService = class {
|
|
2759
|
+
runtime;
|
|
2760
|
+
constructor(runtime) {
|
|
2761
|
+
this.runtime = runtime;
|
|
2762
|
+
}
|
|
2763
|
+
async bucketPage(options) {
|
|
2764
|
+
const startDate = readCalendarDate(options.startDate, "start_date");
|
|
2765
|
+
const endDate = readCalendarDate(options.endDate, "end_date");
|
|
2766
|
+
const startDay = calendarDayNumber(startDate);
|
|
2767
|
+
const endDay = calendarDayNumber(endDate);
|
|
2768
|
+
if (startDay > endDay) throw new ArkmePluginError("calendar-range-invalid", "start_date 不能晚于 end_date", false);
|
|
2769
|
+
if (endDay - startDay + 1 > MAX_CALENDAR_RANGE_DAYS) throw new ArkmePluginError("calendar-range-too-wide", `日历范围最多查询 ${MAX_CALENDAR_RANGE_DAYS} 天`, false);
|
|
2770
|
+
const timezone = readTimezone(options.timezone);
|
|
2771
|
+
const session = await this.runtime.requireSession();
|
|
2772
|
+
const data = await this.runtime.authenticatedCalendarPost("/api/v1/calendar/buckets/query", {
|
|
2773
|
+
bucket_scope_kind: 1,
|
|
2774
|
+
bucket_scope_uid: "",
|
|
2775
|
+
start_date: startDate,
|
|
2776
|
+
end_date: endDate,
|
|
2777
|
+
timezone
|
|
2778
|
+
}, session, options.signal, {
|
|
2779
|
+
key: `calendar:buckets:self:${startDate}:${endDate}:${timezone}`,
|
|
2780
|
+
cacheMs: 3e4,
|
|
2781
|
+
failureCooldownMs: 2e3
|
|
2782
|
+
});
|
|
2783
|
+
return {
|
|
2784
|
+
scope: "self",
|
|
2785
|
+
startDate,
|
|
2786
|
+
endDate,
|
|
2787
|
+
timezone: stringValue$1(data.timezone).trim() || timezone,
|
|
2788
|
+
refreshedAtMillis: Date.now(),
|
|
2789
|
+
days: listValue$17(data.daily_data).map((raw) => this.bucketDay(raw)).filter((day) => day !== void 0)
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
async dayRecords(options) {
|
|
2793
|
+
const bucketDate = readCalendarDate(options.bucketDate, "bucket_date");
|
|
2794
|
+
const timezone = readTimezone(options.timezone);
|
|
2795
|
+
const limit = boundedLimit(options.limit);
|
|
2796
|
+
const cursorSendAt = Math.trunc(options.cursor?.sendAtMillis ?? 0);
|
|
2797
|
+
const cursorRecordUid = options.cursor?.recordUid.trim() ?? "";
|
|
2798
|
+
const session = await this.runtime.requireSession();
|
|
2799
|
+
const data = await this.runtime.authenticatedCalendarPost("/api/v1/calendar/records/query", {
|
|
2800
|
+
bucket_scope_kind: 1,
|
|
2801
|
+
bucket_scope_uid: "",
|
|
2802
|
+
bucket_date: bucketDate,
|
|
2803
|
+
timezone,
|
|
2804
|
+
limit,
|
|
2805
|
+
...cursorSendAt > 0 && cursorRecordUid !== "" ? {
|
|
2806
|
+
cursor_send_at: cursorSendAt,
|
|
2807
|
+
cursor_record_uid: cursorRecordUid
|
|
2808
|
+
} : {}
|
|
2809
|
+
}, session, options.signal, {
|
|
2810
|
+
key: `calendar:records:self:${bucketDate}:${timezone}:${String(limit)}:${String(cursorSendAt)}:${cursorRecordUid}`,
|
|
2811
|
+
cacheMs: 1e4,
|
|
2812
|
+
failureCooldownMs: 2e3
|
|
2813
|
+
});
|
|
2814
|
+
const nextSendAt = Math.trunc(numberValue$17(data.next_cursor_send_at));
|
|
2815
|
+
const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
|
|
2816
|
+
return {
|
|
2817
|
+
scope: "self",
|
|
2818
|
+
bucketDate,
|
|
2819
|
+
timezone: stringValue$1(data.timezone).trim() || timezone,
|
|
2820
|
+
refreshedAtMillis: Date.now(),
|
|
2821
|
+
items: listValue$17(data.items).map((raw) => this.dayRecord(raw)).filter((item) => item !== void 0),
|
|
2822
|
+
hasMore: data.has_more === true,
|
|
2823
|
+
...nextSendAt > 0 && nextUid !== "" ? { nextCursor: {
|
|
2824
|
+
sendAtMillis: nextSendAt,
|
|
2825
|
+
recordUid: nextUid
|
|
2826
|
+
} } : {}
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
bucketDay(raw) {
|
|
2830
|
+
const item = objectValue$1(raw);
|
|
2831
|
+
const bucketDate = stringValue$1(item.bucket_date).trim();
|
|
2832
|
+
if (bucketDate === "") return void 0;
|
|
2833
|
+
const count = Math.max(0, Math.trunc(numberValue$17(item.count)));
|
|
2834
|
+
return {
|
|
2835
|
+
bucketDate,
|
|
2836
|
+
count,
|
|
2837
|
+
protectedCount: Math.max(0, Math.trunc(numberValue$17(item.protected_count))),
|
|
2838
|
+
hasRecords: count > 0,
|
|
2839
|
+
...numberValue$17(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$17(item.first_send_at)) } : {}
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
dayRecord(raw) {
|
|
2843
|
+
const item = objectValue$1(raw);
|
|
2844
|
+
const core = objectValue$1(item.record_core);
|
|
2845
|
+
const recordUid = stringValue$1(item.record_uid ?? core.record_uid).trim();
|
|
2846
|
+
const sendAtMillis = Math.trunc(numberValue$17(item.send_at ?? core.send_at));
|
|
2847
|
+
if (recordUid === "" || sendAtMillis <= 0) return void 0;
|
|
2848
|
+
const accessState = contentAccessState(core.content_access_state);
|
|
2849
|
+
const available = accessState === "available";
|
|
2850
|
+
const title = available ? clippedText(core.title, 500) : "";
|
|
2851
|
+
const textContent = available ? clippedText(core.text_content, 4e3) : "";
|
|
2852
|
+
const preview = title || clippedText(textContent, 160) || (available ? "无文字内容" : "受保护内容");
|
|
2853
|
+
const topic = objectValue$1(item.topic_core);
|
|
2854
|
+
const isUncategorized = booleanValue$9(item.is_uncategorized);
|
|
2855
|
+
const hasManualEdit = booleanValue$9(core.has_manual_edit);
|
|
2856
|
+
const hasPolish = booleanValue$9(core.has_polish);
|
|
2857
|
+
return {
|
|
2858
|
+
recordUid,
|
|
2859
|
+
sendAtMillis,
|
|
2860
|
+
accessState,
|
|
2861
|
+
title,
|
|
2862
|
+
textContent,
|
|
2863
|
+
preview,
|
|
2864
|
+
...stringValue$1(topic.title).trim() === "" ? {} : { topicTitle: stringValue$1(topic.title).trim() },
|
|
2865
|
+
sourceKind: sourceKind(item),
|
|
2866
|
+
creationSource: Math.trunc(numberValue$17(core.creation_source)),
|
|
2867
|
+
templateKind: Math.trunc(numberValue$17(core.template_kind)),
|
|
2868
|
+
displayKind: Math.trunc(numberValue$17(core.display_kind)),
|
|
2869
|
+
protected: accessState === "protected",
|
|
2870
|
+
...isUncategorized === void 0 ? {} : { isUncategorized },
|
|
2871
|
+
...hasManualEdit === void 0 ? {} : { hasManualEdit },
|
|
2872
|
+
...hasPolish === void 0 ? {} : { hasPolish }
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2875
|
+
};
|
|
2876
|
+
//#endregion
|
|
2686
2877
|
//#region src/chat-realtime.ts
|
|
2687
2878
|
const ARKME_CHAT_SSE_PATH = "/api/v1/sse/chat/noty";
|
|
2688
2879
|
const DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
|
|
@@ -2690,7 +2881,7 @@ const DEFAULT_INACTIVITY_TIMEOUT_MS = 75e3;
|
|
|
2690
2881
|
const DEFAULT_LEASE_DURATION_MS = 57e4;
|
|
2691
2882
|
const DEFAULT_IDLE_POLL_MS = 2e3;
|
|
2692
2883
|
const DEFAULT_RETRY_BASE_MS = 1e3;
|
|
2693
|
-
const DEFAULT_MAX_RETRY_MS =
|
|
2884
|
+
const DEFAULT_MAX_RETRY_MS = 15e3;
|
|
2694
2885
|
const DEFAULT_STABLE_CONNECTION_MS = 3e4;
|
|
2695
2886
|
const MAX_SEEN_EVENTS = 256;
|
|
2696
2887
|
var ArkmeChatRealtimeAuthError = class extends Error {};
|
|
@@ -2771,11 +2962,14 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2771
2962
|
now;
|
|
2772
2963
|
rootController;
|
|
2773
2964
|
connectionController;
|
|
2965
|
+
waitController;
|
|
2774
2966
|
loop;
|
|
2775
2967
|
revision = 0;
|
|
2776
2968
|
connected = false;
|
|
2777
2969
|
lastAcceptedAccessToken;
|
|
2778
2970
|
lastConnectionLifetimeMs = 0;
|
|
2971
|
+
connectionGeneration = 0;
|
|
2972
|
+
forceReconnectRequested = false;
|
|
2779
2973
|
lastEventAtMillis;
|
|
2780
2974
|
seenEventUids = /* @__PURE__ */ new Set();
|
|
2781
2975
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -2812,14 +3006,19 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2812
3006
|
stop() {
|
|
2813
3007
|
this.rootController?.abort();
|
|
2814
3008
|
this.connectionController?.abort();
|
|
3009
|
+
this.waitController?.abort();
|
|
2815
3010
|
}
|
|
2816
3011
|
reconnect() {
|
|
3012
|
+
if (this.loop === void 0) return;
|
|
3013
|
+
this.forceReconnectRequested = true;
|
|
2817
3014
|
this.connectionController?.abort();
|
|
3015
|
+
this.waitController?.abort();
|
|
2818
3016
|
}
|
|
2819
3017
|
state() {
|
|
2820
3018
|
return {
|
|
2821
3019
|
revision: this.revision,
|
|
2822
3020
|
connected: this.connected,
|
|
3021
|
+
connectionGeneration: this.connectionGeneration,
|
|
2823
3022
|
...this.lastEventAtMillis === void 0 ? {} : { lastEventAtMillis: this.lastEventAtMillis }
|
|
2824
3023
|
};
|
|
2825
3024
|
}
|
|
@@ -2841,16 +3040,17 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2841
3040
|
if (session === void 0) {
|
|
2842
3041
|
this.connected = false;
|
|
2843
3042
|
blockedAccessToken = void 0;
|
|
2844
|
-
await
|
|
3043
|
+
await this.wait(this.idlePollMs, signal);
|
|
2845
3044
|
continue;
|
|
2846
3045
|
}
|
|
2847
3046
|
if (blockedAccessToken === session.accessToken) {
|
|
2848
3047
|
this.connected = false;
|
|
2849
|
-
await
|
|
3048
|
+
await this.wait(this.idlePollMs, signal);
|
|
2850
3049
|
continue;
|
|
2851
3050
|
}
|
|
2852
3051
|
blockedAccessToken = void 0;
|
|
2853
3052
|
let stableConnection = false;
|
|
3053
|
+
this.options.diagnostic?.("reconnect_attempt", { connectionGeneration: this.connectionGeneration + 1 });
|
|
2854
3054
|
try {
|
|
2855
3055
|
await this.consume(session, signal);
|
|
2856
3056
|
stableConnection = this.lastConnectionLifetimeMs >= this.stableConnectionMs;
|
|
@@ -2865,7 +3065,7 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2865
3065
|
if (refreshedButUnacceptedToken === session.accessToken) {
|
|
2866
3066
|
blockedAccessToken = session.accessToken;
|
|
2867
3067
|
refreshedButUnacceptedToken = void 0;
|
|
2868
|
-
await
|
|
3068
|
+
await this.wait(this.idlePollMs, signal);
|
|
2869
3069
|
continue;
|
|
2870
3070
|
}
|
|
2871
3071
|
let refreshed;
|
|
@@ -2876,17 +3076,24 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2876
3076
|
}
|
|
2877
3077
|
if (refreshed !== void 0 && refreshed.accessToken !== session.accessToken) {
|
|
2878
3078
|
refreshedButUnacceptedToken = refreshed.accessToken;
|
|
3079
|
+
this.forceReconnectRequested = false;
|
|
2879
3080
|
retryMs = this.retryBaseMs;
|
|
2880
3081
|
continue;
|
|
2881
3082
|
}
|
|
2882
3083
|
blockedAccessToken = session.accessToken;
|
|
2883
|
-
await
|
|
3084
|
+
await this.wait(this.idlePollMs, signal);
|
|
2884
3085
|
continue;
|
|
2885
3086
|
}
|
|
2886
3087
|
} finally {
|
|
3088
|
+
if (this.connected && !signal.aborted) this.options.diagnostic?.("sse_disconnected", { connectionGeneration: this.connectionGeneration });
|
|
2887
3089
|
this.connected = false;
|
|
2888
3090
|
}
|
|
2889
|
-
|
|
3091
|
+
if (this.forceReconnectRequested) {
|
|
3092
|
+
this.forceReconnectRequested = false;
|
|
3093
|
+
retryMs = this.retryBaseMs;
|
|
3094
|
+
continue;
|
|
3095
|
+
}
|
|
3096
|
+
await this.wait(this.jitteredDelay(retryMs), signal);
|
|
2890
3097
|
if (!stableConnection) retryMs = Math.min(this.maxRetryMs, Math.max(this.retryBaseMs, retryMs * 2));
|
|
2891
3098
|
}
|
|
2892
3099
|
}
|
|
@@ -2922,6 +3129,7 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2922
3129
|
acceptedAtMillis = this.now();
|
|
2923
3130
|
this.connected = true;
|
|
2924
3131
|
this.lastAcceptedAccessToken = session.accessToken;
|
|
3132
|
+
this.connectionGeneration += 1;
|
|
2925
3133
|
this.advanceRevision("reconcile");
|
|
2926
3134
|
leaseTimer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("chat SSE lease rotation")), this.leaseDurationMs);
|
|
2927
3135
|
const reader = response.body.getReader();
|
|
@@ -2946,6 +3154,25 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2946
3154
|
if (this.connectionController === controller) this.connectionController = void 0;
|
|
2947
3155
|
}
|
|
2948
3156
|
}
|
|
3157
|
+
jitteredDelay(milliseconds) {
|
|
3158
|
+
const random = Math.min(1, Math.max(0, this.random()));
|
|
3159
|
+
return Math.max(0, Math.round(milliseconds * (.8 + random * .4)));
|
|
3160
|
+
}
|
|
3161
|
+
async wait(milliseconds, rootSignal) {
|
|
3162
|
+
if (rootSignal.aborted) return;
|
|
3163
|
+
const controller = new AbortController();
|
|
3164
|
+
const abortFromRoot = () => {
|
|
3165
|
+
controller.abort(rootSignal.reason);
|
|
3166
|
+
};
|
|
3167
|
+
rootSignal.addEventListener("abort", abortFromRoot, { once: true });
|
|
3168
|
+
this.waitController = controller;
|
|
3169
|
+
try {
|
|
3170
|
+
await waitFor(milliseconds, controller.signal);
|
|
3171
|
+
} finally {
|
|
3172
|
+
rootSignal.removeEventListener("abort", abortFromRoot);
|
|
3173
|
+
if (this.waitController === controller) this.waitController = void 0;
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
2949
3176
|
acceptLine(line) {
|
|
2950
3177
|
const event = decodeArkmeChatReceiveDataLine(line);
|
|
2951
3178
|
if (event === void 0 || this.seenEventUids.has(event.eventUid)) return;
|
|
@@ -2984,11 +3211,15 @@ var ChatRealtimeService = class {
|
|
|
2984
3211
|
projectionReader;
|
|
2985
3212
|
chatRealtime;
|
|
2986
3213
|
chatClientListeners = /* @__PURE__ */ new Set();
|
|
2987
|
-
|
|
3214
|
+
pendingChatProjections = /* @__PURE__ */ new Map();
|
|
2988
3215
|
projectionRetryCounts = /* @__PURE__ */ new Map();
|
|
2989
3216
|
projectionTimer;
|
|
2990
3217
|
projectionInFlight = false;
|
|
2991
3218
|
projectionFailureCount = 0;
|
|
3219
|
+
notificationBaselineGeneration = 0;
|
|
3220
|
+
notificationBaselineSequences = /* @__PURE__ */ new Map();
|
|
3221
|
+
notificationBaselineRetryTimer;
|
|
3222
|
+
notificationBaselineRetryCount = 0;
|
|
2992
3223
|
chatClientRevision = 0;
|
|
2993
3224
|
constructor(runtime, source, projectionReader) {
|
|
2994
3225
|
this.runtime = runtime;
|
|
@@ -3005,7 +3236,10 @@ var ChatRealtimeService = class {
|
|
|
3005
3236
|
return;
|
|
3006
3237
|
}
|
|
3007
3238
|
},
|
|
3008
|
-
fetchImpl: runtime.fetchImpl
|
|
3239
|
+
fetchImpl: runtime.fetchImpl,
|
|
3240
|
+
diagnostic: (event, details) => {
|
|
3241
|
+
console.info(`dsh-arkme: ${event}`, details);
|
|
3242
|
+
}
|
|
3009
3243
|
});
|
|
3010
3244
|
}
|
|
3011
3245
|
reconnect() {
|
|
@@ -3013,9 +3247,13 @@ var ChatRealtimeService = class {
|
|
|
3013
3247
|
}
|
|
3014
3248
|
dispose() {
|
|
3015
3249
|
if (this.projectionTimer !== void 0) clearTimeout(this.projectionTimer);
|
|
3250
|
+
if (this.notificationBaselineRetryTimer !== void 0) clearTimeout(this.notificationBaselineRetryTimer);
|
|
3016
3251
|
this.projectionTimer = void 0;
|
|
3017
|
-
this.
|
|
3252
|
+
this.notificationBaselineRetryTimer = void 0;
|
|
3253
|
+
this.pendingChatProjections.clear();
|
|
3018
3254
|
this.projectionRetryCounts.clear();
|
|
3255
|
+
this.notificationBaselineSequences.clear();
|
|
3256
|
+
this.notificationBaselineGeneration = 0;
|
|
3019
3257
|
this.chatClientListeners.clear();
|
|
3020
3258
|
}
|
|
3021
3259
|
startChatRealtime() {
|
|
@@ -3027,9 +3265,13 @@ var ChatRealtimeService = class {
|
|
|
3027
3265
|
unsubscribe();
|
|
3028
3266
|
stop();
|
|
3029
3267
|
if (this.projectionTimer !== void 0) clearTimeout(this.projectionTimer);
|
|
3268
|
+
if (this.notificationBaselineRetryTimer !== void 0) clearTimeout(this.notificationBaselineRetryTimer);
|
|
3030
3269
|
this.projectionTimer = void 0;
|
|
3031
|
-
this.
|
|
3270
|
+
this.notificationBaselineRetryTimer = void 0;
|
|
3271
|
+
this.pendingChatProjections.clear();
|
|
3032
3272
|
this.projectionRetryCounts.clear();
|
|
3273
|
+
this.notificationBaselineSequences.clear();
|
|
3274
|
+
this.notificationBaselineGeneration = 0;
|
|
3033
3275
|
};
|
|
3034
3276
|
}
|
|
3035
3277
|
chatRealtimeState() {
|
|
@@ -3047,26 +3289,93 @@ var ChatRealtimeService = class {
|
|
|
3047
3289
|
type: "reconcile",
|
|
3048
3290
|
revision: this.chatClientRevision,
|
|
3049
3291
|
connected: state.connected,
|
|
3292
|
+
connectionGeneration: state.connectionGeneration,
|
|
3050
3293
|
refresh: "if-stale"
|
|
3051
3294
|
};
|
|
3052
3295
|
}
|
|
3053
3296
|
handleChatRealtimeNotice(notice) {
|
|
3054
3297
|
if (notice.cause === "reconcile") {
|
|
3298
|
+
const generation = notice.state.connectionGeneration;
|
|
3299
|
+
this.notificationBaselineGeneration = 0;
|
|
3300
|
+
this.notificationBaselineSequences.clear();
|
|
3301
|
+
this.notificationBaselineRetryCount = 0;
|
|
3302
|
+
if (this.notificationBaselineRetryTimer !== void 0) clearTimeout(this.notificationBaselineRetryTimer);
|
|
3303
|
+
this.notificationBaselineRetryTimer = void 0;
|
|
3055
3304
|
this.emitChatClientEvent({
|
|
3056
3305
|
type: "reconcile",
|
|
3057
3306
|
revision: this.nextChatClientRevision(),
|
|
3058
3307
|
connected: notice.state.connected,
|
|
3308
|
+
connectionGeneration: generation,
|
|
3059
3309
|
refresh: "none"
|
|
3060
3310
|
});
|
|
3311
|
+
this.reconcileChatNotificationBaseline(generation);
|
|
3061
3312
|
return;
|
|
3062
3313
|
}
|
|
3063
|
-
if (notice.cause === "hint" && notice.hint !== void 0) this.scheduleChatSessionProjection(notice.hint.chatSessionUid, notice.hint.latestSequence
|
|
3314
|
+
if (notice.cause === "hint" && notice.hint !== void 0) this.scheduleChatSessionProjection(notice.hint.chatSessionUid, notice.hint.latestSequence, {
|
|
3315
|
+
hint: notice.hint,
|
|
3316
|
+
connectionGeneration: notice.state.connectionGeneration,
|
|
3317
|
+
attempts: 0
|
|
3318
|
+
});
|
|
3064
3319
|
}
|
|
3065
|
-
|
|
3320
|
+
async reconcileChatNotificationBaseline(connectionGeneration) {
|
|
3321
|
+
try {
|
|
3322
|
+
const session = await this.runtime.requireSession();
|
|
3323
|
+
const sequences = /* @__PURE__ */ new Map();
|
|
3324
|
+
let cursor;
|
|
3325
|
+
for (let pageIndex = 0; pageIndex < 10; pageIndex += 1) {
|
|
3326
|
+
const page = await this.source.listSources("root", {
|
|
3327
|
+
limit: 50,
|
|
3328
|
+
refresh: true,
|
|
3329
|
+
...cursor === void 0 ? {} : { cursor }
|
|
3330
|
+
});
|
|
3331
|
+
for (const item of page.items) {
|
|
3332
|
+
if (item.kind !== "private_chat" && item.kind !== "group_chat") continue;
|
|
3333
|
+
const source = await this.source.openSourceRef(item.sourceRef, session.userId);
|
|
3334
|
+
sequences.set(source.ownerRef, item.latestSequence ?? 0);
|
|
3335
|
+
}
|
|
3336
|
+
if (!page.hasMore || page.nextCursor === void 0) break;
|
|
3337
|
+
cursor = page.nextCursor;
|
|
3338
|
+
}
|
|
3339
|
+
const state = this.chatRealtime.state();
|
|
3340
|
+
if (!state.connected || state.connectionGeneration !== connectionGeneration) return;
|
|
3341
|
+
this.notificationBaselineSequences.clear();
|
|
3342
|
+
for (const [uid, sequence] of sequences) this.notificationBaselineSequences.set(uid, sequence);
|
|
3343
|
+
this.notificationBaselineGeneration = connectionGeneration;
|
|
3344
|
+
this.notificationBaselineRetryCount = 0;
|
|
3345
|
+
console.info("dsh-arkme: reconcile_completed", {
|
|
3346
|
+
connectionGeneration,
|
|
3347
|
+
sessionCount: sequences.size
|
|
3348
|
+
});
|
|
3349
|
+
if (this.pendingChatProjections.size > 0 && this.projectionTimer === void 0 && !this.projectionInFlight) this.projectionTimer = setTimeout(() => {
|
|
3350
|
+
this.projectionTimer = void 0;
|
|
3351
|
+
this.flushChatSessionProjections();
|
|
3352
|
+
}, 0);
|
|
3353
|
+
} catch (error) {
|
|
3354
|
+
const state = this.chatRealtime.state();
|
|
3355
|
+
if (!state.connected || state.connectionGeneration !== connectionGeneration) return;
|
|
3356
|
+
this.notificationBaselineRetryCount += 1;
|
|
3357
|
+
const delay = Math.min(15e3, 1e3 * 2 ** Math.min(4, this.notificationBaselineRetryCount - 1));
|
|
3358
|
+
console.warn("dsh-arkme: Chat reconnect reconciliation failed:", safeFailureMessage$6(error));
|
|
3359
|
+
this.notificationBaselineRetryTimer = setTimeout(() => {
|
|
3360
|
+
this.notificationBaselineRetryTimer = void 0;
|
|
3361
|
+
this.reconcileChatNotificationBaseline(connectionGeneration);
|
|
3362
|
+
}, delay);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
scheduleChatSessionProjection(chatSessionUid, latestSequence, notificationHint) {
|
|
3066
3366
|
const uid = chatSessionUid.trim();
|
|
3067
3367
|
if (uid === "") return;
|
|
3068
|
-
|
|
3069
|
-
|
|
3368
|
+
const current = this.pendingChatProjections.get(uid);
|
|
3369
|
+
if (latestSequence > (current?.latestSequence ?? 0)) this.projectionRetryCounts.delete(uid);
|
|
3370
|
+
const notificationHints = current === void 0 ? [] : [...current.notificationHints];
|
|
3371
|
+
if (notificationHint !== void 0 && !notificationHints.some((item) => item.hint.eventUid === notificationHint.hint.eventUid)) {
|
|
3372
|
+
notificationHints.push(notificationHint);
|
|
3373
|
+
if (notificationHints.length > 256) notificationHints.splice(0, notificationHints.length - 256);
|
|
3374
|
+
}
|
|
3375
|
+
this.pendingChatProjections.set(uid, {
|
|
3376
|
+
latestSequence: Math.max(latestSequence, current?.latestSequence ?? 0),
|
|
3377
|
+
notificationHints
|
|
3378
|
+
});
|
|
3070
3379
|
if (this.projectionTimer !== void 0 || this.projectionInFlight) return;
|
|
3071
3380
|
this.projectionTimer = setTimeout(() => {
|
|
3072
3381
|
this.projectionTimer = void 0;
|
|
@@ -3074,10 +3383,10 @@ var ChatRealtimeService = class {
|
|
|
3074
3383
|
}, 200);
|
|
3075
3384
|
}
|
|
3076
3385
|
async flushChatSessionProjections() {
|
|
3077
|
-
if (this.projectionInFlight || this.
|
|
3386
|
+
if (this.projectionInFlight || this.pendingChatProjections.size === 0) return;
|
|
3078
3387
|
this.projectionInFlight = true;
|
|
3079
|
-
const pending = [...this.
|
|
3080
|
-
for (const [uid] of pending) this.
|
|
3388
|
+
const pending = [...this.pendingChatProjections.entries()].slice(0, 50);
|
|
3389
|
+
for (const [uid] of pending) this.pendingChatProjections.delete(uid);
|
|
3081
3390
|
try {
|
|
3082
3391
|
const failed = await this.refreshChatSessionProjectionBatch(pending);
|
|
3083
3392
|
for (const [uid] of pending) if (!failed.some(([failedUid]) => failedUid === uid)) this.projectionRetryCounts.delete(uid);
|
|
@@ -3092,7 +3401,7 @@ var ChatRealtimeService = class {
|
|
|
3092
3401
|
this.requeueProjectionFailures(pending);
|
|
3093
3402
|
} finally {
|
|
3094
3403
|
this.projectionInFlight = false;
|
|
3095
|
-
if (this.
|
|
3404
|
+
if (this.pendingChatProjections.size > 0 && this.projectionTimer === void 0) {
|
|
3096
3405
|
const retryDelayBase = this.projectionFailureCount === 0 ? 200 : Math.min(5e3, 500 * 2 ** Math.min(3, this.projectionFailureCount - 1));
|
|
3097
3406
|
const retryDelay = Math.max(100, Math.round(retryDelayBase * (.8 + Math.random() * .4)));
|
|
3098
3407
|
this.projectionTimer = setTimeout(() => {
|
|
@@ -3103,7 +3412,7 @@ var ChatRealtimeService = class {
|
|
|
3103
3412
|
}
|
|
3104
3413
|
}
|
|
3105
3414
|
requeueProjectionFailures(failed) {
|
|
3106
|
-
for (const [uid,
|
|
3415
|
+
for (const [uid, projection] of failed) {
|
|
3107
3416
|
const retries = (this.projectionRetryCounts.get(uid) ?? 0) + 1;
|
|
3108
3417
|
if (retries > MAX_PROJECTION_RETRIES) {
|
|
3109
3418
|
this.projectionRetryCounts.delete(uid);
|
|
@@ -3111,8 +3420,24 @@ var ChatRealtimeService = class {
|
|
|
3111
3420
|
continue;
|
|
3112
3421
|
}
|
|
3113
3422
|
this.projectionRetryCounts.set(uid, retries);
|
|
3114
|
-
this.
|
|
3423
|
+
this.mergePendingChatProjection(uid, projection);
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
mergePendingChatProjection(uid, incoming) {
|
|
3427
|
+
const current = this.pendingChatProjections.get(uid);
|
|
3428
|
+
if (current === void 0) {
|
|
3429
|
+
this.pendingChatProjections.set(uid, {
|
|
3430
|
+
latestSequence: incoming.latestSequence,
|
|
3431
|
+
notificationHints: [...incoming.notificationHints]
|
|
3432
|
+
});
|
|
3433
|
+
return;
|
|
3115
3434
|
}
|
|
3435
|
+
const byEventUid = new Map(current.notificationHints.map((item) => [item.hint.eventUid, item]));
|
|
3436
|
+
for (const item of incoming.notificationHints) byEventUid.set(item.hint.eventUid, item);
|
|
3437
|
+
this.pendingChatProjections.set(uid, {
|
|
3438
|
+
latestSequence: Math.max(current.latestSequence, incoming.latestSequence),
|
|
3439
|
+
notificationHints: [...byEventUid.values()].slice(-256)
|
|
3440
|
+
});
|
|
3116
3441
|
}
|
|
3117
3442
|
async refreshChatSessionProjectionBatch(pending) {
|
|
3118
3443
|
const session = await this.runtime.requireSession();
|
|
@@ -3130,16 +3455,19 @@ var ChatRealtimeService = class {
|
|
|
3130
3455
|
const failedUids = /* @__PURE__ */ new Set();
|
|
3131
3456
|
for (let offset = 0; offset < pending.length; offset += 3) {
|
|
3132
3457
|
const chunk = pending.slice(offset, offset + 3);
|
|
3133
|
-
(await Promise.allSettled(chunk.map(async ([uid,
|
|
3458
|
+
(await Promise.allSettled(chunk.map(async ([uid, projection]) => {
|
|
3134
3459
|
const cached = this.source.cachedChatSourceByKey(`${String(session.userId)}:${uid}`);
|
|
3135
|
-
const
|
|
3460
|
+
const firstHintSequence = projection.notificationHints.reduce((minimum, item) => Math.min(minimum, item.hint.latestSequence), projection.latestSequence);
|
|
3461
|
+
const requiredAfterSequence = Math.max(0, firstHintSequence - 1);
|
|
3462
|
+
const afterSequence = Math.max(0, Math.min(cached?.latestSequence ?? requiredAfterSequence, requiredAfterSequence));
|
|
3463
|
+
const notificationAttempt = projection.notificationHints.reduce((maximum, item) => Math.max(maximum, item.attempts), 0);
|
|
3136
3464
|
const data = await this.runtime.authenticatedChatPost("/api/v1/chat/timeline/tail", {
|
|
3137
3465
|
chat_session_uid: uid,
|
|
3138
3466
|
after_seq: afterSequence,
|
|
3139
3467
|
limit: 50
|
|
3140
3468
|
}, session, void 0, {
|
|
3141
3469
|
lane: "background-read",
|
|
3142
|
-
key: `projection:tail:${uid}:${String(afterSequence)}`
|
|
3470
|
+
key: `projection:tail:${uid}:${String(afterSequence)}:${String(notificationAttempt)}`
|
|
3143
3471
|
});
|
|
3144
3472
|
return [uid, await this.projectionReader.chatTimelineItems(data, session)];
|
|
3145
3473
|
}))).forEach((result, index) => {
|
|
@@ -3150,7 +3478,8 @@ var ChatRealtimeService = class {
|
|
|
3150
3478
|
});
|
|
3151
3479
|
}
|
|
3152
3480
|
const updates = [];
|
|
3153
|
-
|
|
3481
|
+
const notifications = [];
|
|
3482
|
+
for (const [uid, projection] of pending) {
|
|
3154
3483
|
const bundle = bundles.get(uid);
|
|
3155
3484
|
if (bundle === void 0 || failedUids.has(uid)) {
|
|
3156
3485
|
failedUids.add(uid);
|
|
@@ -3161,11 +3490,47 @@ var ChatRealtimeService = class {
|
|
|
3161
3490
|
try {
|
|
3162
3491
|
const source = await this.source.chatSourceFromBundle(bundle, session, this.source.cachedChatSourceByKey(cacheKey), timelineItems);
|
|
3163
3492
|
this.source.setChatSourceByKey(cacheKey, source);
|
|
3493
|
+
const sourceKey = source.sourceKey ?? await this.source.chatDirectorySourceKey(session.userId, uid);
|
|
3164
3494
|
updates.push({
|
|
3165
|
-
sourceKey
|
|
3495
|
+
sourceKey,
|
|
3166
3496
|
source,
|
|
3167
3497
|
timelineItems
|
|
3168
3498
|
});
|
|
3499
|
+
if (source.kind === "private_chat" || source.kind === "group_chat") for (const candidate of projection.notificationHints.sort((left, right) => left.hint.latestSequence - right.hint.latestSequence)) {
|
|
3500
|
+
const state = this.chatRealtime.state();
|
|
3501
|
+
if (candidate.connectionGeneration !== state.connectionGeneration) continue;
|
|
3502
|
+
if (this.notificationBaselineGeneration !== candidate.connectionGeneration) {
|
|
3503
|
+
this.mergePendingChatProjection(uid, {
|
|
3504
|
+
latestSequence: projection.latestSequence,
|
|
3505
|
+
notificationHints: [candidate]
|
|
3506
|
+
});
|
|
3507
|
+
continue;
|
|
3508
|
+
}
|
|
3509
|
+
const baselineSequence = this.notificationBaselineSequences.get(uid) ?? 0;
|
|
3510
|
+
if (candidate.hint.latestSequence <= baselineSequence || candidate.hint.senderUserId === session.userId || source.isMuted === true) continue;
|
|
3511
|
+
const message = timelineItems.find((item) => item.sequence === candidate.hint.latestSequence && !item.isMe);
|
|
3512
|
+
if (message === void 0) {
|
|
3513
|
+
if (candidate.attempts < 4) this.mergePendingChatProjection(uid, {
|
|
3514
|
+
latestSequence: projection.latestSequence,
|
|
3515
|
+
notificationHints: [{
|
|
3516
|
+
...candidate,
|
|
3517
|
+
attempts: candidate.attempts + 1
|
|
3518
|
+
}]
|
|
3519
|
+
});
|
|
3520
|
+
else console.warn("dsh-arkme: Chat notification message was not projected:", candidate.hint.eventUid);
|
|
3521
|
+
continue;
|
|
3522
|
+
}
|
|
3523
|
+
const preview = message.textContent.trim() || "非文本内容";
|
|
3524
|
+
notifications.push({
|
|
3525
|
+
eventUid: candidate.hint.eventUid,
|
|
3526
|
+
sourceRef: source.sourceRef,
|
|
3527
|
+
sourceKey,
|
|
3528
|
+
sourceKind: source.kind,
|
|
3529
|
+
title: source.displayName,
|
|
3530
|
+
body: source.kind === "group_chat" ? `${message.senderName}:${preview}` : preview,
|
|
3531
|
+
eventAtMillis: candidate.hint.eventAtMillis
|
|
3532
|
+
});
|
|
3533
|
+
}
|
|
3169
3534
|
} catch {
|
|
3170
3535
|
failedUids.add(uid);
|
|
3171
3536
|
}
|
|
@@ -3178,6 +3543,11 @@ var ChatRealtimeService = class {
|
|
|
3178
3543
|
updates
|
|
3179
3544
|
});
|
|
3180
3545
|
}
|
|
3546
|
+
for (const notification of notifications) this.emitChatClientEvent({
|
|
3547
|
+
type: "message-notification",
|
|
3548
|
+
revision: this.nextChatClientRevision(),
|
|
3549
|
+
notification
|
|
3550
|
+
});
|
|
3181
3551
|
return pending.filter(([uid]) => failedUids.has(uid));
|
|
3182
3552
|
}
|
|
3183
3553
|
emitChatClientEvent(event) {
|
|
@@ -3190,10 +3560,10 @@ var ChatRealtimeService = class {
|
|
|
3190
3560
|
};
|
|
3191
3561
|
//#endregion
|
|
3192
3562
|
//#region src/services/chat-service.ts
|
|
3193
|
-
function numberValue$
|
|
3563
|
+
function numberValue$16(value) {
|
|
3194
3564
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3195
3565
|
}
|
|
3196
|
-
function booleanValue$
|
|
3566
|
+
function booleanValue$8(value) {
|
|
3197
3567
|
return value === true;
|
|
3198
3568
|
}
|
|
3199
3569
|
function listValue$15(value) {
|
|
@@ -3242,10 +3612,10 @@ function timelineAgentSource(relation, record, payload) {
|
|
|
3242
3612
|
label: agentSourceLabel(displayName)
|
|
3243
3613
|
};
|
|
3244
3614
|
}
|
|
3245
|
-
function encodeOpaqueJson$
|
|
3615
|
+
function encodeOpaqueJson$5(value) {
|
|
3246
3616
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
3247
3617
|
}
|
|
3248
|
-
function decodeOpaqueJson$
|
|
3618
|
+
function decodeOpaqueJson$5(value) {
|
|
3249
3619
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
3250
3620
|
}
|
|
3251
3621
|
var ChatService = class {
|
|
@@ -3292,15 +3662,15 @@ var ChatService = class {
|
|
|
3292
3662
|
const uid = stringValue$1(chatSession.chat_session_uid).trim();
|
|
3293
3663
|
if (uid === "") throw new ArkmePluginError("private-chat-contract-invalid", "私聊会话响应不完整", true, 502);
|
|
3294
3664
|
const unread = objectValue$1(data.unread_snapshot);
|
|
3295
|
-
const latestSequence = numberValue$
|
|
3665
|
+
const latestSequence = numberValue$16(unread.session_last_seq ?? chatSession.last_seq);
|
|
3296
3666
|
const source = {
|
|
3297
3667
|
sourceRef: await this.source.sealSourceRef(session.userId, "private_chat", uid, displayName),
|
|
3298
3668
|
sourceKey: await this.source.chatDirectorySourceKey(session.userId, uid),
|
|
3299
3669
|
kind: "private_chat",
|
|
3300
3670
|
displayName,
|
|
3301
3671
|
...profile?.avatarUrl === void 0 ? {} : { avatarRef: await this.profile.sealProfileImageRef(session.userId, peerUserId) },
|
|
3302
|
-
activeAtMillis: numberValue$
|
|
3303
|
-
unreadCount: numberValue$
|
|
3672
|
+
activeAtMillis: numberValue$16(chatSession.last_active_at) || Date.now(),
|
|
3673
|
+
unreadCount: numberValue$16(unread.unread_count),
|
|
3304
3674
|
...latestSequence > 0 ? { latestSequence } : {}
|
|
3305
3675
|
};
|
|
3306
3676
|
this.source.setChatSourceByKey(`${String(session.userId)}:${uid}`, source);
|
|
@@ -3327,7 +3697,7 @@ var ChatService = class {
|
|
|
3327
3697
|
mediaUnavailable: media.unavailableRecordUids.has(recordUid)
|
|
3328
3698
|
});
|
|
3329
3699
|
}).filter((item) => item.itemUid !== "");
|
|
3330
|
-
const nextSendAt = numberValue$
|
|
3700
|
+
const nextSendAt = numberValue$16(data.next_cursor_send_at);
|
|
3331
3701
|
const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
|
|
3332
3702
|
return {
|
|
3333
3703
|
source: await this.source.sourceItem(source),
|
|
@@ -3371,7 +3741,7 @@ var ChatService = class {
|
|
|
3371
3741
|
mediaUnavailable: media.unavailableRecordUids.has(recordUid)
|
|
3372
3742
|
});
|
|
3373
3743
|
});
|
|
3374
|
-
const nextSendAt = numberValue$
|
|
3744
|
+
const nextSendAt = numberValue$16(data.next_cursor_send_at);
|
|
3375
3745
|
const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
|
|
3376
3746
|
return {
|
|
3377
3747
|
source: await this.source.sourceItem(source),
|
|
@@ -3401,14 +3771,14 @@ var ChatService = class {
|
|
|
3401
3771
|
const relation = objectValue$1(item.relation);
|
|
3402
3772
|
const record = objectValue$1(item.record);
|
|
3403
3773
|
const payload = objectValue$1(record.payload);
|
|
3404
|
-
const recordStatus = numberValue$
|
|
3774
|
+
const recordStatus = numberValue$16(record.status);
|
|
3405
3775
|
if (recordStatus !== 1) continue;
|
|
3406
3776
|
const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
|
|
3407
3777
|
if (uid === "") continue;
|
|
3408
3778
|
const relationUid = stringValue$1(relation.rel_uid).trim();
|
|
3409
|
-
const senderUserId = numberValue$
|
|
3779
|
+
const senderUserId = numberValue$16(relation.sender_user_id);
|
|
3410
3780
|
const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
|
|
3411
|
-
const sendAtMillis = numberValue$
|
|
3781
|
+
const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
|
|
3412
3782
|
const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
|
|
3413
3783
|
const rawAgentSource = timelineAgentSource(relation, record, payload);
|
|
3414
3784
|
const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
|
|
@@ -3422,16 +3792,16 @@ var ChatService = class {
|
|
|
3422
3792
|
title: stringValue$1(payload.title),
|
|
3423
3793
|
textContent: stringValue$1(payload.text_content),
|
|
3424
3794
|
status: recordStatus,
|
|
3425
|
-
sequence: numberValue$
|
|
3426
|
-
...numberValue$
|
|
3795
|
+
sequence: numberValue$16(relation.seq),
|
|
3796
|
+
...numberValue$16(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$16(record.version ?? payload.version) } : {},
|
|
3427
3797
|
...aiPolish === void 0 ? {} : { aiPolish },
|
|
3428
3798
|
...forwardRecords === void 0 ? {} : { forwardRecords },
|
|
3429
|
-
templateKind: numberValue$
|
|
3430
|
-
displayKind: numberValue$
|
|
3431
|
-
version: numberValue$
|
|
3432
|
-
updateAtMillis: numberValue$
|
|
3433
|
-
recordDurationMillis: numberValue$
|
|
3434
|
-
editDurationMillis: numberValue$
|
|
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),
|
|
3435
3805
|
contentBlocks: this.media.richContentBlocks(item, session.userId)
|
|
3436
3806
|
}) - 1;
|
|
3437
3807
|
if (Number.isSafeInteger(senderUserId) && senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
|
|
@@ -3443,7 +3813,7 @@ var ChatService = class {
|
|
|
3443
3813
|
items[index].avatarRef = await this.profile.sealProfileImageRef(session.userId, senderUserId);
|
|
3444
3814
|
}
|
|
3445
3815
|
} catch {}
|
|
3446
|
-
const beforeSequence = numberValue$
|
|
3816
|
+
const beforeSequence = numberValue$16(data.next_before_seq);
|
|
3447
3817
|
let aiPolishSettings;
|
|
3448
3818
|
let aiPolishNotices;
|
|
3449
3819
|
if (aiPolishDecorations !== void 0) {
|
|
@@ -3482,7 +3852,7 @@ var ChatService = class {
|
|
|
3482
3852
|
return {
|
|
3483
3853
|
messageRef,
|
|
3484
3854
|
reportUid,
|
|
3485
|
-
status: numberValue$
|
|
3855
|
+
status: numberValue$16(report.status)
|
|
3486
3856
|
};
|
|
3487
3857
|
}
|
|
3488
3858
|
async sendSourceText(sourceRef, textContent, options = {}) {
|
|
@@ -3513,7 +3883,7 @@ var ChatService = class {
|
|
|
3513
3883
|
return {
|
|
3514
3884
|
sourceRef,
|
|
3515
3885
|
itemUid: stringValue$1(result.record_uid).trim() || recordUid,
|
|
3516
|
-
status: numberValue$
|
|
3886
|
+
status: numberValue$16(result.status),
|
|
3517
3887
|
localState: "synced"
|
|
3518
3888
|
};
|
|
3519
3889
|
}
|
|
@@ -3546,7 +3916,7 @@ var ChatService = class {
|
|
|
3546
3916
|
const raw = objectValue$1(value);
|
|
3547
3917
|
const botId = stringValue$1(raw.bot_id).trim();
|
|
3548
3918
|
if (!requestedById.has(botId)) continue;
|
|
3549
|
-
if (stringValue$1(raw.provider).trim() !== "openclaw" || !booleanValue$
|
|
3919
|
+
if (stringValue$1(raw.provider).trim() !== "openclaw" || !booleanValue$8(raw.installed)) throw new ArkmePluginError("bot-mention-not-installed", "所选 Bot 未安装到该群聊", false, 409);
|
|
3550
3920
|
const name = stringValue$1(raw.name).trim();
|
|
3551
3921
|
if (name === "") throw new ArkmePluginError("bot-contract-invalid", "Bot 响应不完整", true, 502);
|
|
3552
3922
|
const display = `@${name}`;
|
|
@@ -3598,8 +3968,8 @@ var ChatService = class {
|
|
|
3598
3968
|
return {
|
|
3599
3969
|
sourceRef,
|
|
3600
3970
|
itemUid: stringValue$1(result.record_uid).trim() || recordUid,
|
|
3601
|
-
status: numberValue$
|
|
3602
|
-
sequence: numberValue$
|
|
3971
|
+
status: numberValue$16(result.audit_status),
|
|
3972
|
+
sequence: numberValue$16(result.seq),
|
|
3603
3973
|
localState: "synced"
|
|
3604
3974
|
};
|
|
3605
3975
|
}
|
|
@@ -3655,7 +4025,7 @@ var ChatService = class {
|
|
|
3655
4025
|
return {
|
|
3656
4026
|
sourceRef,
|
|
3657
4027
|
itemUid: stringValue$1(result.record_uid).trim() || recordUid,
|
|
3658
|
-
status: numberValue$
|
|
4028
|
+
status: numberValue$16(result.status),
|
|
3659
4029
|
localState: "synced"
|
|
3660
4030
|
};
|
|
3661
4031
|
}
|
|
@@ -3667,7 +4037,7 @@ var ChatService = class {
|
|
|
3667
4037
|
return {
|
|
3668
4038
|
sourceRef,
|
|
3669
4039
|
itemUid: stringValue$1(result.record_uid).trim() || recordUid,
|
|
3670
|
-
status: numberValue$
|
|
4040
|
+
status: numberValue$16(result.status),
|
|
3671
4041
|
localState: "synced"
|
|
3672
4042
|
};
|
|
3673
4043
|
}
|
|
@@ -3679,8 +4049,8 @@ var ChatService = class {
|
|
|
3679
4049
|
return {
|
|
3680
4050
|
sourceRef,
|
|
3681
4051
|
itemUid: stringValue$1(result.record_uid).trim() || recordUid,
|
|
3682
|
-
status: numberValue$
|
|
3683
|
-
sequence: numberValue$
|
|
4052
|
+
status: numberValue$16(result.audit_status ?? result.status),
|
|
4053
|
+
sequence: numberValue$16(result.seq),
|
|
3684
4054
|
localState: "synced"
|
|
3685
4055
|
};
|
|
3686
4056
|
}
|
|
@@ -3730,7 +4100,7 @@ var ChatService = class {
|
|
|
3730
4100
|
send_at: sendAtMillis
|
|
3731
4101
|
}, session, options.signal);
|
|
3732
4102
|
const chatSessionUid = stringValue$1(result.chat_session_uid).trim();
|
|
3733
|
-
const sequence = numberValue$
|
|
4103
|
+
const sequence = numberValue$16(result.seq);
|
|
3734
4104
|
const targetKind = stringValue$1(result.target_kind).trim();
|
|
3735
4105
|
if (chatSessionUid === "" || !Number.isSafeInteger(sequence) || sequence <= 0 || targetKind !== "direct") throw new ArkmePluginError("direct-send-response-invalid", "Chat Agent 发送返回了无效响应", true, 502);
|
|
3736
4106
|
const responseRecordUid = stringValue$1(result.record_uid).trim() || recordUid;
|
|
@@ -3758,10 +4128,10 @@ var ChatService = class {
|
|
|
3758
4128
|
reason: "arkme_dsh_open_chat"
|
|
3759
4129
|
}, session);
|
|
3760
4130
|
const responseSessionUid = stringValue$1(data.chat_session_uid).trim();
|
|
3761
|
-
const effectiveReadSequence = numberValue$
|
|
3762
|
-
const readAt = numberValue$
|
|
3763
|
-
const sessionLastSequence = numberValue$
|
|
3764
|
-
const unreadCount = numberValue$
|
|
4131
|
+
const effectiveReadSequence = numberValue$16(data.effective_read_seq);
|
|
4132
|
+
const readAt = numberValue$16(data.read_at);
|
|
4133
|
+
const sessionLastSequence = numberValue$16(data.session_last_seq);
|
|
4134
|
+
const unreadCount = numberValue$16(data.unread_count);
|
|
3765
4135
|
if (responseSessionUid !== source.ownerRef || !Number.isSafeInteger(effectiveReadSequence) || effectiveReadSequence < readSequence || readAt <= 0 || sessionLastSequence < effectiveReadSequence || !Number.isSafeInteger(unreadCount) || unreadCount < 0) throw new ArkmePluginError("source-read-ack-invalid", "聊天已读响应不完整", true, 502);
|
|
3766
4136
|
const cacheKey = `${String(session.userId)}:${source.ownerRef}`;
|
|
3767
4137
|
const cached = this.source.cachedChatSourceByKey(cacheKey);
|
|
@@ -3796,9 +4166,9 @@ var ChatService = class {
|
|
|
3796
4166
|
const payload = objectValue$1(record.payload);
|
|
3797
4167
|
const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
|
|
3798
4168
|
if (uid === "") continue;
|
|
3799
|
-
const senderUserId = numberValue$
|
|
4169
|
+
const senderUserId = numberValue$16(relation.sender_user_id);
|
|
3800
4170
|
const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
|
|
3801
|
-
const sendAtMillis = numberValue$
|
|
4171
|
+
const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
|
|
3802
4172
|
const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
|
|
3803
4173
|
const rawAgentSource = timelineAgentSource(relation, record, payload);
|
|
3804
4174
|
const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
|
|
@@ -3811,12 +4181,12 @@ var ChatService = class {
|
|
|
3811
4181
|
sendAtMillis,
|
|
3812
4182
|
title: stringValue$1(payload.title),
|
|
3813
4183
|
textContent: stringValue$1(payload.text_content),
|
|
3814
|
-
status: numberValue$
|
|
3815
|
-
sequence: numberValue$
|
|
3816
|
-
...numberValue$
|
|
4184
|
+
status: numberValue$16(record.status),
|
|
4185
|
+
sequence: numberValue$16(relation.seq),
|
|
4186
|
+
...numberValue$16(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$16(record.version ?? payload.version) } : {},
|
|
3817
4187
|
...aiPolish === void 0 ? {} : { aiPolish },
|
|
3818
4188
|
...forwardRecords === void 0 ? {} : { forwardRecords },
|
|
3819
|
-
displayKind: numberValue$
|
|
4189
|
+
displayKind: numberValue$16(payload.display_kind),
|
|
3820
4190
|
contentBlocks: this.media.richContentBlocks(item, session.userId)
|
|
3821
4191
|
});
|
|
3822
4192
|
}
|
|
@@ -3832,7 +4202,7 @@ var ChatService = class {
|
|
|
3832
4202
|
const sorted = values.map((value, index) => ({
|
|
3833
4203
|
value,
|
|
3834
4204
|
index,
|
|
3835
|
-
order: numberValue$
|
|
4205
|
+
order: numberValue$16(objectValue$1(value).item_order)
|
|
3836
4206
|
})).sort((left, right) => (left.order || left.index) - (right.order || right.index));
|
|
3837
4207
|
for (const entry of sorted) {
|
|
3838
4208
|
if (projectedItems.length >= 100) return;
|
|
@@ -3842,19 +4212,19 @@ var ChatService = class {
|
|
|
3842
4212
|
await appendItems(listValue$15(nestedForward.items), depth + 1);
|
|
3843
4213
|
continue;
|
|
3844
4214
|
}
|
|
3845
|
-
const senderUserId = numberValue$
|
|
4215
|
+
const senderUserId = numberValue$16(item.source_sender_user_id ?? item.sourceSenderUserId ?? item.owner_id ?? item.ownerId);
|
|
3846
4216
|
const senderName = stringValue$1(item.owner_name ?? item.ownerName ?? item.source_display_name ?? item.sourceDisplayName).trim() || "Arkme用户";
|
|
3847
4217
|
const textContent = stringValue$1(item.text ?? item.text_preview ?? item.textPreview).trim();
|
|
3848
4218
|
const title = stringValue$1(item.title).trim();
|
|
3849
|
-
const imageCount = Math.max(0, Math.trunc(numberValue$
|
|
3850
|
-
const voiceCount = Math.max(0, Math.trunc(numberValue$
|
|
3851
|
-
const fileCount = Math.max(0, Math.trunc(numberValue$
|
|
4219
|
+
const imageCount = Math.max(0, Math.trunc(numberValue$16(item.image_count ?? item.imageCount)));
|
|
4220
|
+
const voiceCount = Math.max(0, Math.trunc(numberValue$16(item.voice_count ?? item.voiceCount)));
|
|
4221
|
+
const fileCount = Math.max(0, Math.trunc(numberValue$16(item.file_count ?? item.fileCount)));
|
|
3852
4222
|
const fileName = listValue$15(item.file_names ?? item.fileNames).map((value) => stringValue$1(value).trim()).find((value) => value !== "");
|
|
3853
4223
|
const contentLabel = imageCount > 0 ? imageCount > 1 ? `[${String(imageCount)}张图片]` : "[图片]" : voiceCount > 0 ? "[语音]" : fileCount > 0 ? fileName === void 0 ? "[文件]" : `[文件] ${fileName}` : stringValue$1(item.availability).trim() !== "" ? "原快记暂不可查看" : void 0;
|
|
3854
4224
|
projectedItems.push({
|
|
3855
4225
|
senderName,
|
|
3856
4226
|
...senderUserId > 0 ? { avatarRef: await this.profile.sealProfileImageRef(viewerUserId, senderUserId) } : {},
|
|
3857
|
-
sendAtMillis: numberValue$
|
|
4227
|
+
sendAtMillis: numberValue$16(item.send_at ?? item.sendAt),
|
|
3858
4228
|
title,
|
|
3859
4229
|
textContent,
|
|
3860
4230
|
...contentLabel === void 0 ? {} : { contentLabel }
|
|
@@ -3863,7 +4233,7 @@ var ChatService = class {
|
|
|
3863
4233
|
};
|
|
3864
4234
|
await appendItems(listValue$15(payload.items), 0);
|
|
3865
4235
|
const summaryLines = listValue$15(payload.summary_lines ?? payload.summaryLines).map((value) => stringValue$1(value).trim()).filter((value) => value !== "");
|
|
3866
|
-
const createdAtMillis = numberValue$
|
|
4236
|
+
const createdAtMillis = numberValue$16(payload.created_at ?? payload.createdAt) || fallbackCreatedAtMillis;
|
|
3867
4237
|
return {
|
|
3868
4238
|
title: stringValue$1(payload.title).trim() || "转发快记",
|
|
3869
4239
|
createdAtMillis,
|
|
@@ -3872,7 +4242,7 @@ var ChatService = class {
|
|
|
3872
4242
|
};
|
|
3873
4243
|
}
|
|
3874
4244
|
sealMessageRef(userId, chatSessionUid, relationUid, signingKey) {
|
|
3875
|
-
const payload = encodeOpaqueJson$
|
|
4245
|
+
const payload = encodeOpaqueJson$5({
|
|
3876
4246
|
version: 1,
|
|
3877
4247
|
userId,
|
|
3878
4248
|
chatSessionUid,
|
|
@@ -3889,13 +4259,13 @@ var ChatService = class {
|
|
|
3889
4259
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("message-ref-invalid", "Arkme 消息引用无效", false);
|
|
3890
4260
|
let parsed;
|
|
3891
4261
|
try {
|
|
3892
|
-
parsed = objectValue$1(decodeOpaqueJson$
|
|
4262
|
+
parsed = objectValue$1(decodeOpaqueJson$5(payload));
|
|
3893
4263
|
} catch (error) {
|
|
3894
4264
|
throw new ArkmePluginError("message-ref-invalid", "Arkme 消息引用无效", false, 400, { cause: error });
|
|
3895
4265
|
}
|
|
3896
4266
|
const result = {
|
|
3897
4267
|
version: 1,
|
|
3898
|
-
userId: numberValue$
|
|
4268
|
+
userId: numberValue$16(parsed.userId),
|
|
3899
4269
|
chatSessionUid: stringValue$1(parsed.chatSessionUid).trim(),
|
|
3900
4270
|
relationUid: stringValue$1(parsed.relationUid).trim()
|
|
3901
4271
|
};
|
|
@@ -3904,6 +4274,167 @@ var ChatService = class {
|
|
|
3904
4274
|
}
|
|
3905
4275
|
};
|
|
3906
4276
|
//#endregion
|
|
4277
|
+
//#region src/services/contact-service.ts
|
|
4278
|
+
const CONTACT_REF_TTL_MS = 10 * 6e4;
|
|
4279
|
+
const MAX_CONTACT_REFS = 512;
|
|
4280
|
+
const CONTACT_REF_PATTERN = /^arkme-contact-v1\.[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4281
|
+
function numberValue$15(value) {
|
|
4282
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
4283
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
4284
|
+
}
|
|
4285
|
+
function booleanValue$7(value) {
|
|
4286
|
+
return value === true;
|
|
4287
|
+
}
|
|
4288
|
+
function normalizeIdentifier(value) {
|
|
4289
|
+
const trimmed = value.trim().replace(/^@/, "");
|
|
4290
|
+
if (trimmed === "" || trimmed.length > 64) throw new ArkmePluginError("contact-identifier-invalid", "请输入手机号或即我号", false);
|
|
4291
|
+
const compactPhone = trimmed.replace(/[\s()-]/g, "").replace(/^\+86/, "");
|
|
4292
|
+
if (/^\d+$/.test(compactPhone)) {
|
|
4293
|
+
if (!/^1[3-9]\d{9}$/.test(compactPhone)) throw new ArkmePluginError("contact-phone-invalid", "请输入有效的中国大陆手机号", false);
|
|
4294
|
+
return {
|
|
4295
|
+
kind: "phone",
|
|
4296
|
+
identifierType: 1,
|
|
4297
|
+
identifier: compactPhone,
|
|
4298
|
+
phonePre: "86"
|
|
4299
|
+
};
|
|
4300
|
+
}
|
|
4301
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]{1,63}$/.test(trimmed)) throw new ArkmePluginError("contact-arkme-id-invalid", "即我号格式无效", false);
|
|
4302
|
+
return {
|
|
4303
|
+
kind: "arkme_id",
|
|
4304
|
+
identifierType: 2,
|
|
4305
|
+
identifier: trimmed,
|
|
4306
|
+
phonePre: "86"
|
|
4307
|
+
};
|
|
4308
|
+
}
|
|
4309
|
+
var ContactService = class {
|
|
4310
|
+
runtime;
|
|
4311
|
+
source;
|
|
4312
|
+
profile;
|
|
4313
|
+
realtime;
|
|
4314
|
+
candidates = /* @__PURE__ */ new Map();
|
|
4315
|
+
constructor(runtime, source, profile, realtime) {
|
|
4316
|
+
this.runtime = runtime;
|
|
4317
|
+
this.source = source;
|
|
4318
|
+
this.profile = profile;
|
|
4319
|
+
this.realtime = realtime;
|
|
4320
|
+
}
|
|
4321
|
+
dispose() {
|
|
4322
|
+
this.candidates.clear();
|
|
4323
|
+
}
|
|
4324
|
+
async search(identifier, options = {}) {
|
|
4325
|
+
const session = await this.runtime.requireSession();
|
|
4326
|
+
const normalized = normalizeIdentifier(identifier);
|
|
4327
|
+
const candidate = await this.lookup(normalized, session, options.signal);
|
|
4328
|
+
this.prune();
|
|
4329
|
+
this.candidates.set(candidate.result.contactRef, candidate);
|
|
4330
|
+
this.prune();
|
|
4331
|
+
return candidate.result;
|
|
4332
|
+
}
|
|
4333
|
+
async add(contactRef, options = {}) {
|
|
4334
|
+
const session = await this.runtime.requireSession();
|
|
4335
|
+
this.prune();
|
|
4336
|
+
const normalizedContactRef = contactRef.trim();
|
|
4337
|
+
if (!CONTACT_REF_PATTERN.test(normalizedContactRef)) throw new ArkmePluginError("contact-ref-invalid", "联系人搜索引用无效,请重新搜索", false);
|
|
4338
|
+
const cached = this.candidates.get(normalizedContactRef);
|
|
4339
|
+
if (cached === void 0 || cached.userId !== session.userId || cached.expiresAtMillis <= Date.now()) throw new ArkmePluginError("contact-ref-expired", "联系人搜索结果已过期,请重新搜索", false, 410);
|
|
4340
|
+
const candidate = await this.lookup(cached.normalized, session, options.signal, normalizedContactRef);
|
|
4341
|
+
if (candidate.targetUserId !== cached.targetUserId || candidate.result.registered !== cached.result.registered) {
|
|
4342
|
+
this.candidates.delete(normalizedContactRef);
|
|
4343
|
+
throw new ArkmePluginError("contact-candidate-changed", "联系人身份已发生变化,请重新搜索后确认", false, 409);
|
|
4344
|
+
}
|
|
4345
|
+
if (candidate.result.isSelf) throw new ArkmePluginError("contact-self-invalid", "不能添加自己为联系人", false);
|
|
4346
|
+
if (!candidate.result.canAdd) throw new ArkmePluginError("contact-add-unavailable", "该账号当前无法添加", false, 409);
|
|
4347
|
+
const remark = options.remark?.trim() ?? "";
|
|
4348
|
+
if (Array.from(remark).length > 100) throw new ArkmePluginError("contact-remark-invalid", "备注不能超过 100 个字符", false);
|
|
4349
|
+
const requestUid = options.requestUid?.trim() || randomUUID();
|
|
4350
|
+
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(requestUid)) throw new ArkmePluginError("contact-request-uid-invalid", "联系人添加请求标识无效", false);
|
|
4351
|
+
const chatSessionUid = `chat_session_${requestUid}`;
|
|
4352
|
+
const displayName = candidate.result.displayName;
|
|
4353
|
+
const common = {
|
|
4354
|
+
chat_session_uid: chatSessionUid,
|
|
4355
|
+
create_at: Date.now(),
|
|
4356
|
+
...remark === "" ? {} : { remark }
|
|
4357
|
+
};
|
|
4358
|
+
const registered = candidate.result.registered && candidate.targetUserId !== void 0;
|
|
4359
|
+
const bundle = registered ? await this.runtime.authenticatedChatPost("/api/v1/chats/contacts/add-and-open-private", {
|
|
4360
|
+
...common,
|
|
4361
|
+
target_user_id: candidate.targetUserId,
|
|
4362
|
+
target_display_name_snapshot: displayName
|
|
4363
|
+
}, session, options.signal, { key: `contact:add:${requestUid}` }) : await this.runtime.authenticatedChatPost("/api/v1/chats/contacts/add-and-open-pending-phone", {
|
|
4364
|
+
...common,
|
|
4365
|
+
phone_pre: candidate.normalized.phonePre,
|
|
4366
|
+
phone: candidate.phone ?? candidate.normalized.identifier,
|
|
4367
|
+
display_name_snapshot: displayName
|
|
4368
|
+
}, session, options.signal, { key: `contact:add:${requestUid}` });
|
|
4369
|
+
let source = await this.source.chatSourceFromBundle(bundle, session, void 0, []);
|
|
4370
|
+
if (registered && candidate.targetUserId !== void 0) source = {
|
|
4371
|
+
...source,
|
|
4372
|
+
avatarRef: await this.profile.sealProfileImageRef(session.userId, candidate.targetUserId)
|
|
4373
|
+
};
|
|
4374
|
+
const returnedUid = stringValue$1(objectValue$1(bundle.session).chat_session_uid).trim();
|
|
4375
|
+
if (returnedUid === "") throw new ArkmePluginError("contact-add-contract-invalid", "联系人添加响应不完整", true, 502);
|
|
4376
|
+
this.source.setChatSource(session.userId, returnedUid, source);
|
|
4377
|
+
this.source.invalidateSourceListCache(session.userId, "root");
|
|
4378
|
+
this.realtime.emitChatClientEvent({
|
|
4379
|
+
type: "sessions-delta",
|
|
4380
|
+
revision: this.realtime.nextChatClientRevision(),
|
|
4381
|
+
updates: [{
|
|
4382
|
+
sourceKey: source.sourceKey ?? await this.source.chatDirectorySourceKey(session.userId, returnedUid),
|
|
4383
|
+
source,
|
|
4384
|
+
timelineItems: []
|
|
4385
|
+
}]
|
|
4386
|
+
});
|
|
4387
|
+
this.candidates.delete(normalizedContactRef);
|
|
4388
|
+
return {
|
|
4389
|
+
state: registered ? "ready" : "pending",
|
|
4390
|
+
source
|
|
4391
|
+
};
|
|
4392
|
+
}
|
|
4393
|
+
async lookup(normalized, session, signal, contactRef = `arkme-contact-v1.${randomUUID()}`) {
|
|
4394
|
+
const data = await this.runtime.authenticatedAuthPost("/api/v1/auth/search-contact-account", {
|
|
4395
|
+
identifier_type: normalized.identifierType,
|
|
4396
|
+
identifier: normalized.identifier,
|
|
4397
|
+
phone_pre: normalized.phonePre
|
|
4398
|
+
}, session, signal, {
|
|
4399
|
+
key: `contact:search:${normalized.identifierType}:${normalized.identifier}`,
|
|
4400
|
+
bypassCache: true
|
|
4401
|
+
});
|
|
4402
|
+
const targetUserId = Math.trunc(numberValue$15(data.user_id));
|
|
4403
|
+
const registered = booleanValue$7(data.is_registered) && targetUserId > 0;
|
|
4404
|
+
const isSelf = booleanValue$7(data.is_self);
|
|
4405
|
+
const canAdd = booleanValue$7(data.can_add) && !isSelf && (normalized.kind === "phone" || registered);
|
|
4406
|
+
const displayName = stringValue$1(data.nick_name).trim() || stringValue$1(data.jotmo_id).trim() || (normalized.kind === "phone" ? `+${normalized.phonePre} ${normalized.identifier}` : normalized.identifier);
|
|
4407
|
+
const result = {
|
|
4408
|
+
contactRef,
|
|
4409
|
+
identifierKind: normalized.kind,
|
|
4410
|
+
displayName,
|
|
4411
|
+
...stringValue$1(data.jotmo_id).trim() === "" ? {} : { arkmeId: stringValue$1(data.jotmo_id).trim() },
|
|
4412
|
+
...registered ? { avatarRef: await this.profile.sealProfileImageRef(session.userId, targetUserId) } : {},
|
|
4413
|
+
registered,
|
|
4414
|
+
inviteBySms: booleanValue$7(data.invite_by_sms),
|
|
4415
|
+
canAdd,
|
|
4416
|
+
isSelf
|
|
4417
|
+
};
|
|
4418
|
+
return {
|
|
4419
|
+
userId: session.userId,
|
|
4420
|
+
expiresAtMillis: Date.now() + CONTACT_REF_TTL_MS,
|
|
4421
|
+
normalized,
|
|
4422
|
+
result,
|
|
4423
|
+
...targetUserId > 0 ? { targetUserId } : {},
|
|
4424
|
+
...normalized.kind === "phone" ? { phone: stringValue$1(data.phone).trim() || normalized.identifier } : {}
|
|
4425
|
+
};
|
|
4426
|
+
}
|
|
4427
|
+
prune() {
|
|
4428
|
+
const now = Date.now();
|
|
4429
|
+
for (const [ref, candidate] of this.candidates) if (candidate.expiresAtMillis <= now) this.candidates.delete(ref);
|
|
4430
|
+
while (this.candidates.size >= MAX_CONTACT_REFS) {
|
|
4431
|
+
const oldest = this.candidates.keys().next().value;
|
|
4432
|
+
if (oldest === void 0) break;
|
|
4433
|
+
this.candidates.delete(oldest);
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
};
|
|
4437
|
+
//#endregion
|
|
3907
4438
|
//#region src/services/community-service.ts
|
|
3908
4439
|
function numberValue$14(value) {
|
|
3909
4440
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
@@ -4119,7 +4650,7 @@ var ExtensionReviewService = class {
|
|
|
4119
4650
|
422
|
|
4120
4651
|
].includes(error.httpStatus)) {
|
|
4121
4652
|
await this.runtime.stateStore.removeExtensionReviewOperation(userId, operation.clientMutationId);
|
|
4122
|
-
throw new ArkmePluginError("extension-review-registry-rejected",
|
|
4653
|
+
throw new ArkmePluginError("extension-review-registry-rejected", `评论已写入首页,但市集未接受:${error.message}`, false, error.httpStatus, { cause: error });
|
|
4123
4654
|
}
|
|
4124
4655
|
await this.runtime.stateStore.markExtensionReviewOperation(userId, operation.clientMutationId, "failed", safeFailureMessage$5(error));
|
|
4125
4656
|
throw new ArkmePluginError("extension-review-registry-pending", "评论已写入首页,但同步到扩展详情失败;请保留当前内容并重试", true, 503, { cause: error });
|
|
@@ -4658,12 +5189,19 @@ var GroupAiPolishService = class {
|
|
|
4658
5189
|
};
|
|
4659
5190
|
//#endregion
|
|
4660
5191
|
//#region src/services/group-service.ts
|
|
5192
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4661
5193
|
function numberValue$12(value) {
|
|
4662
5194
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
4663
5195
|
}
|
|
4664
5196
|
function listValue$12(value) {
|
|
4665
5197
|
return Array.isArray(value) ? value : [];
|
|
4666
5198
|
}
|
|
5199
|
+
function encodeOpaqueJson$4(value) {
|
|
5200
|
+
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
5201
|
+
}
|
|
5202
|
+
function decodeOpaqueJson$4(value) {
|
|
5203
|
+
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
5204
|
+
}
|
|
4667
5205
|
function chatMessageDnd$1(value) {
|
|
4668
5206
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4669
5207
|
const policy = value;
|
|
@@ -4685,10 +5223,325 @@ var GroupService = class {
|
|
|
4685
5223
|
runtime;
|
|
4686
5224
|
source;
|
|
4687
5225
|
profile;
|
|
4688
|
-
|
|
5226
|
+
inviteSender;
|
|
5227
|
+
constructor(runtime, source, profile, inviteSender) {
|
|
4689
5228
|
this.runtime = runtime;
|
|
4690
5229
|
this.source = source;
|
|
4691
5230
|
this.profile = profile;
|
|
5231
|
+
this.inviteSender = inviteSender;
|
|
5232
|
+
}
|
|
5233
|
+
async groupInvitePreview(sourceRef, signal) {
|
|
5234
|
+
const session = await this.runtime.requireSession();
|
|
5235
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
5236
|
+
if (source.kind !== "group_chat") throw new ArkmePluginError("group-source-invalid", "仅支持邀请群聊协作者", false);
|
|
5237
|
+
const preview = objectValue$1((await this.runtime.authenticatedChatPost("/api/v1/chats/governance/invite-preview", { chat_session_uid: source.ownerRef }, session, signal)).preview);
|
|
5238
|
+
const inviteLink = stringValue$1(preview.invite_link).trim();
|
|
5239
|
+
if (inviteLink === "") throw new ArkmePluginError("group-invite-unavailable", "邀请链接生成失败,请重试", true);
|
|
5240
|
+
return {
|
|
5241
|
+
source: await this.source.sourceItem(source),
|
|
5242
|
+
title: stringValue$1(preview.title).trim() || source.displayName,
|
|
5243
|
+
inviterDisplayName: stringValue$1(preview.creator_display_name).trim() || "Arkme",
|
|
5244
|
+
inviteLink,
|
|
5245
|
+
expireAtMillis: numberValue$12(preview.expire_at),
|
|
5246
|
+
mode: numberValue$12(preview.join_mode) === 2 ? "approval_invite" : "direct_add"
|
|
5247
|
+
};
|
|
5248
|
+
}
|
|
5249
|
+
async listGroupMemberCandidates(sourceRef, options = {}) {
|
|
5250
|
+
const session = await this.runtime.requireSession();
|
|
5251
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
5252
|
+
if (source.kind !== "group_chat") throw new ArkmePluginError("group-source-invalid", "仅支持向群聊添加成员", false);
|
|
5253
|
+
const limit = Math.min(50, Math.max(1, Math.trunc(options.limit ?? 20)));
|
|
5254
|
+
const query = options.query?.trim().toLocaleLowerCase() ?? "";
|
|
5255
|
+
const requestedGroupSourceRefs = [...new Set((options.groupSourceRefs ?? []).map((value) => value.trim()).filter((value) => value !== ""))].slice(0, 20);
|
|
5256
|
+
const [membersData, directoryData, inviteData] = await Promise.all([
|
|
5257
|
+
this.runtime.authenticatedChatPost("/api/v1/chats/members/list", {
|
|
5258
|
+
chat_session_uid: source.ownerRef,
|
|
5259
|
+
active_only: true
|
|
5260
|
+
}, session, options.signal).catch(() => ({})),
|
|
5261
|
+
this.runtime.authenticatedChatPost("/api/v1/chats/list", { limit: 50 }, session, options.signal),
|
|
5262
|
+
this.runtime.authenticatedChatPost("/api/v1/chats/governance/invite-preview", { chat_session_uid: source.ownerRef }, session, options.signal).catch(() => ({}))
|
|
5263
|
+
]);
|
|
5264
|
+
const directoryBundles = listValue$12(directoryData.items);
|
|
5265
|
+
const privateBundles = directoryBundles.filter((raw) => {
|
|
5266
|
+
const kind = numberValue$12(objectValue$1(objectValue$1(raw).session).session_kind);
|
|
5267
|
+
return kind === 1 || kind === 3;
|
|
5268
|
+
});
|
|
5269
|
+
const privateTopicIds = privateBundles.map((raw) => {
|
|
5270
|
+
const chatSession = objectValue$1(objectValue$1(raw).session);
|
|
5271
|
+
return numberValue$12(chatSession.shared_topic_id ?? chatSession.subject_id);
|
|
5272
|
+
}).filter((value) => Number.isSafeInteger(value) && value > 0);
|
|
5273
|
+
const partnerData = privateTopicIds.length === 0 ? {} : await this.runtime.authenticatedSubjectPost("/api/v1/private/get-partner-info-v2", { rm_subject_ids: [...new Set(privateTopicIds)] }, session, options.signal).catch(() => ({}));
|
|
5274
|
+
const contactByTopicId = /* @__PURE__ */ new Map();
|
|
5275
|
+
for (const raw of listValue$12(partnerData.item_ls)) {
|
|
5276
|
+
const item = objectValue$1(raw);
|
|
5277
|
+
const topicId = numberValue$12(item.rm_subject_id ?? item.shared_topic_id);
|
|
5278
|
+
if (topicId > 0 && typeof item.is_contact === "boolean") contactByTopicId.set(topicId, item.is_contact);
|
|
5279
|
+
}
|
|
5280
|
+
const existingMemberUserIds = new Set(listValue$12(objectValue$1(membersData).items).map((item) => numberValue$12(objectValue$1(item).user_id)));
|
|
5281
|
+
const rawCandidates = /* @__PURE__ */ new Map();
|
|
5282
|
+
for (const raw of privateBundles) {
|
|
5283
|
+
const bundle = objectValue$1(raw);
|
|
5284
|
+
const chatSession = objectValue$1(bundle.session);
|
|
5285
|
+
if (numberValue$12(chatSession.session_kind) !== 1 && numberValue$12(chatSession.session_kind) !== 3) continue;
|
|
5286
|
+
const counterpart = objectValue$1(bundle.private_counterpart);
|
|
5287
|
+
const supplement = objectValue$1(bundle.private_supplement);
|
|
5288
|
+
const targetUserId = numberValue$12(counterpart.user_id);
|
|
5289
|
+
const privateChatSessionUid = stringValue$1(chatSession.chat_session_uid).trim();
|
|
5290
|
+
const displayName = stringValue$1(supplement.remark ?? supplement.counterpart_name_snapshot ?? counterpart.display_name_snapshot ?? supplement.pending_name ?? counterpart.visible_phone).trim();
|
|
5291
|
+
const privateTopicId = numberValue$12(chatSession.shared_topic_id ?? chatSession.subject_id);
|
|
5292
|
+
const contactState = numberValue$12(supplement.contact_state);
|
|
5293
|
+
const relation = contactByTopicId.get(privateTopicId) === true || contactState === 1 || contactState === 3 ? "contact" : "stranger";
|
|
5294
|
+
if (!Number.isSafeInteger(targetUserId) || targetUserId <= 0 || targetUserId === session.userId || privateChatSessionUid === "" || displayName === "" || query !== "" && !displayName.toLocaleLowerCase().includes(query)) continue;
|
|
5295
|
+
rawCandidates.set(targetUserId, {
|
|
5296
|
+
targetUserId,
|
|
5297
|
+
displayName,
|
|
5298
|
+
privateChatSessionUid,
|
|
5299
|
+
relation,
|
|
5300
|
+
origin: "private_chat"
|
|
5301
|
+
});
|
|
5302
|
+
}
|
|
5303
|
+
const selected = [...rawCandidates.entries()].slice(0, limit);
|
|
5304
|
+
const sourceByGroupUid = /* @__PURE__ */ new Map();
|
|
5305
|
+
const groupSessionUidByIndex = /* @__PURE__ */ new Map();
|
|
5306
|
+
const groups = [];
|
|
5307
|
+
for (const raw of directoryBundles) {
|
|
5308
|
+
const bundle = objectValue$1(raw);
|
|
5309
|
+
const chatSession = objectValue$1(bundle.session);
|
|
5310
|
+
if (numberValue$12(chatSession.session_kind) !== 2) continue;
|
|
5311
|
+
const groupSessionUid = stringValue$1(chatSession.chat_session_uid).trim();
|
|
5312
|
+
if (groupSessionUid === "" || groupSessionUid === source.ownerRef) continue;
|
|
5313
|
+
const displayName = stringValue$1(chatSession.title ?? chatSession.subject_title ?? objectValue$1(bundle.participant_summary).display_name).trim();
|
|
5314
|
+
const groupItem = {
|
|
5315
|
+
sourceRef: await this.source.sealSourceRef(session.userId, "group_chat", groupSessionUid, displayName || "群聊"),
|
|
5316
|
+
sourceKey: await this.source.chatDirectorySourceKey(session.userId, groupSessionUid),
|
|
5317
|
+
kind: "group_chat",
|
|
5318
|
+
displayName: displayName || "群聊",
|
|
5319
|
+
activeAtMillis: numberValue$12(chatSession.updated_at),
|
|
5320
|
+
unreadCount: 0
|
|
5321
|
+
};
|
|
5322
|
+
const groupIndex = groups.push(groupItem) - 1;
|
|
5323
|
+
groupSessionUidByIndex.set(groupIndex, groupSessionUid);
|
|
5324
|
+
sourceByGroupUid.set(groupSessionUid, groupItem);
|
|
5325
|
+
this.source.setChatSource(session.userId, groupSessionUid, groupItem);
|
|
5326
|
+
}
|
|
5327
|
+
try {
|
|
5328
|
+
await this.source.hydrateSourceAvatars(groups, /* @__PURE__ */ new Map(), groupSessionUidByIndex, session, options.signal);
|
|
5329
|
+
} catch {}
|
|
5330
|
+
const requestedGroupSources = [];
|
|
5331
|
+
for (const groupSourceRef of requestedGroupSourceRefs) {
|
|
5332
|
+
const groupSource = await this.source.openSourceRef(groupSourceRef, session.userId);
|
|
5333
|
+
if (groupSource.kind !== "group_chat" || groupSource.ownerRef === source.ownerRef) continue;
|
|
5334
|
+
requestedGroupSources.push(groupSource);
|
|
5335
|
+
}
|
|
5336
|
+
const groupRawByUid = /* @__PURE__ */ new Map();
|
|
5337
|
+
const groupTargetUserIds = /* @__PURE__ */ new Set();
|
|
5338
|
+
for (const groupSource of requestedGroupSources) {
|
|
5339
|
+
const group = sourceByGroupUid.get(groupSource.ownerRef) ?? await this.source.sourceItem(groupSource);
|
|
5340
|
+
try {
|
|
5341
|
+
const groupMembersData = await this.runtime.authenticatedChatPost("/api/v1/chats/members/list", {
|
|
5342
|
+
chat_session_uid: groupSource.ownerRef,
|
|
5343
|
+
active_only: true
|
|
5344
|
+
}, session, options.signal);
|
|
5345
|
+
const raws = [];
|
|
5346
|
+
const seenUserIds = /* @__PURE__ */ new Set();
|
|
5347
|
+
for (const rawMember of listValue$12(groupMembersData.items)) {
|
|
5348
|
+
const member = objectValue$1(rawMember);
|
|
5349
|
+
const targetUserId = numberValue$12(member.user_id);
|
|
5350
|
+
if (!Number.isSafeInteger(targetUserId) || targetUserId <= 0 || targetUserId === session.userId || seenUserIds.has(targetUserId)) continue;
|
|
5351
|
+
if (chatMemberStatus(member.status) !== "active") continue;
|
|
5352
|
+
const privateCandidate = rawCandidates.get(targetUserId);
|
|
5353
|
+
if (privateCandidate !== void 0) {
|
|
5354
|
+
if (query === "" || privateCandidate.displayName.toLocaleLowerCase().includes(query)) {
|
|
5355
|
+
raws.push(privateCandidate);
|
|
5356
|
+
groupTargetUserIds.add(targetUserId);
|
|
5357
|
+
seenUserIds.add(targetUserId);
|
|
5358
|
+
}
|
|
5359
|
+
continue;
|
|
5360
|
+
}
|
|
5361
|
+
const profileName = stringValue$1(member.profile_display_name).trim();
|
|
5362
|
+
const displayName = [
|
|
5363
|
+
stringValue$1(member.remark).trim(),
|
|
5364
|
+
stringValue$1(member.display_name_snapshot).trim(),
|
|
5365
|
+
profileName === `用户 ${String(targetUserId)}` ? "" : profileName,
|
|
5366
|
+
stringValue$1(objectValue$1(member.user_info).nick_name).trim(),
|
|
5367
|
+
String(targetUserId)
|
|
5368
|
+
].find((value) => value !== "" && value !== "成员" && value !== "群成员") ?? "群成员";
|
|
5369
|
+
if (query !== "" && !displayName.toLocaleLowerCase().includes(query)) continue;
|
|
5370
|
+
raws.push({
|
|
5371
|
+
targetUserId,
|
|
5372
|
+
displayName,
|
|
5373
|
+
relation: "group",
|
|
5374
|
+
origin: "group_chat"
|
|
5375
|
+
});
|
|
5376
|
+
groupTargetUserIds.add(targetUserId);
|
|
5377
|
+
seenUserIds.add(targetUserId);
|
|
5378
|
+
}
|
|
5379
|
+
groupRawByUid.set(groupSource.ownerRef, {
|
|
5380
|
+
group,
|
|
5381
|
+
raws: raws.slice(0, limit),
|
|
5382
|
+
total: raws.length
|
|
5383
|
+
});
|
|
5384
|
+
} catch (error) {
|
|
5385
|
+
groupRawByUid.set(groupSource.ownerRef, {
|
|
5386
|
+
group,
|
|
5387
|
+
raws: [],
|
|
5388
|
+
total: 0,
|
|
5389
|
+
error: error instanceof Error && error.message.trim() !== "" ? error.message : "群成员加载失败,请重试"
|
|
5390
|
+
});
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
const profiles = await this.profile.publicProfileSummariesByUserIds([.../* @__PURE__ */ new Set([...selected.map(([userId]) => userId), ...groupTargetUserIds])], session, options.signal).catch(() => /* @__PURE__ */ new Map());
|
|
5394
|
+
const preview = objectValue$1(objectValue$1(inviteData).preview);
|
|
5395
|
+
const approvalRequired = numberValue$12(preview.join_mode) === 2;
|
|
5396
|
+
const candidateItem = async (targetUserId, candidate) => {
|
|
5397
|
+
const alreadyMember = existingMemberUserIds.has(targetUserId);
|
|
5398
|
+
const cannotInvite = approvalRequired && candidate.privateChatSessionUid === void 0;
|
|
5399
|
+
return {
|
|
5400
|
+
candidateRef: await this.sealCandidateRef({
|
|
5401
|
+
version: 1,
|
|
5402
|
+
userId: session.userId,
|
|
5403
|
+
groupChatSessionUid: source.ownerRef,
|
|
5404
|
+
targetUserId,
|
|
5405
|
+
origin: candidate.origin,
|
|
5406
|
+
displayName: candidate.displayName,
|
|
5407
|
+
...candidate.privateChatSessionUid === void 0 ? {} : { privateChatSessionUid: candidate.privateChatSessionUid }
|
|
5408
|
+
}),
|
|
5409
|
+
displayName: candidate.displayName,
|
|
5410
|
+
...profiles.get(targetUserId)?.avatarUrl === void 0 ? {} : { avatarRef: await this.profile.sealProfileImageRef(session.userId, targetUserId) },
|
|
5411
|
+
origin: candidate.origin,
|
|
5412
|
+
relation: candidate.relation,
|
|
5413
|
+
...alreadyMember ? {
|
|
5414
|
+
disabled: true,
|
|
5415
|
+
alreadyMember: true
|
|
5416
|
+
} : {},
|
|
5417
|
+
...!alreadyMember && cannotInvite ? {
|
|
5418
|
+
disabled: true,
|
|
5419
|
+
statusText: "无法邀请"
|
|
5420
|
+
} : {}
|
|
5421
|
+
};
|
|
5422
|
+
};
|
|
5423
|
+
const items = await Promise.all(selected.map(async ([targetUserId, candidate]) => candidateItem(targetUserId, candidate)));
|
|
5424
|
+
const groupCandidates = await Promise.all([...groupRawByUid.values()].map(async (value) => ({
|
|
5425
|
+
group: value.group,
|
|
5426
|
+
items: await Promise.all(value.raws.map((raw) => candidateItem(raw.targetUserId, raw))),
|
|
5427
|
+
total: value.total,
|
|
5428
|
+
...value.error === void 0 ? {} : { error: value.error }
|
|
5429
|
+
})));
|
|
5430
|
+
return {
|
|
5431
|
+
source: await this.source.sourceItem(source),
|
|
5432
|
+
items,
|
|
5433
|
+
total: rawCandidates.size,
|
|
5434
|
+
hasMore: rawCandidates.size > items.length,
|
|
5435
|
+
mode: numberValue$12(preview.join_mode) === 2 ? "approval_invite" : "direct_add",
|
|
5436
|
+
groups,
|
|
5437
|
+
groupCandidates,
|
|
5438
|
+
contactCount: [...rawCandidates.values()].filter((item) => item.relation === "contact").length,
|
|
5439
|
+
strangerCount: [...rawCandidates.values()].filter((item) => item.relation === "stranger").length
|
|
5440
|
+
};
|
|
5441
|
+
}
|
|
5442
|
+
async addGroupMembers(sourceRef, candidateRefs, signal) {
|
|
5443
|
+
const session = await this.runtime.requireSession();
|
|
5444
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
5445
|
+
if (source.kind !== "group_chat") throw new ArkmePluginError("group-source-invalid", "仅支持向群聊添加成员", false);
|
|
5446
|
+
const refs = candidateRefs.map((value) => value.trim());
|
|
5447
|
+
if (refs.length === 0 || refs.length > 20 || refs.some((value) => value === "") || new Set(refs).size !== refs.length) throw new ArkmePluginError("group-member-candidates-invalid", "请选择 1-20 个不重复的成员候选项", false);
|
|
5448
|
+
const preview = objectValue$1(objectValue$1(await this.runtime.authenticatedChatPost("/api/v1/chats/governance/invite-preview", { chat_session_uid: source.ownerRef }, session, signal)).preview);
|
|
5449
|
+
const approvalRequired = numberValue$12(preview.join_mode) === 2;
|
|
5450
|
+
const inviteLink = stringValue$1(preview.invite_link).trim();
|
|
5451
|
+
const results = [];
|
|
5452
|
+
for (const candidateRef of refs) {
|
|
5453
|
+
let candidate;
|
|
5454
|
+
try {
|
|
5455
|
+
candidate = await this.openCandidateRef(candidateRef, session.userId, source.ownerRef);
|
|
5456
|
+
if (approvalRequired) {
|
|
5457
|
+
if (candidate.privateChatSessionUid === void 0) throw new ArkmePluginError("group-candidate-invite-unavailable", "该对象暂时无法发送群聊邀请", false);
|
|
5458
|
+
if (this.inviteSender === void 0 || inviteLink === "") throw new ArkmePluginError("group-invite-unavailable", "该群需要发送邀请,但邀请链接暂不可用", true);
|
|
5459
|
+
const privateSourceRef = await this.source.sealSourceRef(session.userId, "private_chat", candidate.privateChatSessionUid, candidate.displayName);
|
|
5460
|
+
await this.inviteSender.sendPrivateText(privateSourceRef, candidate.privateChatSessionUid, `邀请你加入群聊“${source.displayName}”:${inviteLink}`, `chat_record_${randomUUID()}`, `chat_rel_${randomUUID()}`, session, signal);
|
|
5461
|
+
results.push({
|
|
5462
|
+
candidateRef,
|
|
5463
|
+
displayName: candidate.displayName,
|
|
5464
|
+
status: "invite_sent"
|
|
5465
|
+
});
|
|
5466
|
+
continue;
|
|
5467
|
+
}
|
|
5468
|
+
const outcome = stringValue$1(objectValue$1((await this.runtime.authenticatedChatPost("/api/v1/chats/members/add", {
|
|
5469
|
+
chat_session_uid: source.ownerRef,
|
|
5470
|
+
target_user_id: candidate.targetUserId,
|
|
5471
|
+
join_at: Date.now(),
|
|
5472
|
+
display_name_snapshot: candidate.displayName,
|
|
5473
|
+
extra: { source: "dsh_arkme_candidate" }
|
|
5474
|
+
}, session, signal)).item).outcome);
|
|
5475
|
+
results.push({
|
|
5476
|
+
candidateRef,
|
|
5477
|
+
displayName: candidate.displayName,
|
|
5478
|
+
status: outcome === "idempotent_hit" ? "already_member" : outcome === "updated" ? "reactivated" : "added"
|
|
5479
|
+
});
|
|
5480
|
+
} catch (error) {
|
|
5481
|
+
results.push({
|
|
5482
|
+
candidateRef,
|
|
5483
|
+
displayName: candidate?.displayName ?? "成员",
|
|
5484
|
+
status: "failed",
|
|
5485
|
+
error: error instanceof Error && error.message.trim() !== "" ? error.message : "添加成员失败"
|
|
5486
|
+
});
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
return {
|
|
5490
|
+
source: await this.source.sourceItem(source),
|
|
5491
|
+
results,
|
|
5492
|
+
succeededCount: results.filter((item) => item.status !== "failed").length,
|
|
5493
|
+
failedCount: results.filter((item) => item.status === "failed").length
|
|
5494
|
+
};
|
|
5495
|
+
}
|
|
5496
|
+
async sealCandidateRef(payload) {
|
|
5497
|
+
const encoded = encodeOpaqueJson$4(payload);
|
|
5498
|
+
return `arkme-group-candidate-v1.${encoded}.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest("base64url")}`;
|
|
5499
|
+
}
|
|
5500
|
+
async openCandidateRef(candidateRef, expectedUserId, expectedGroupChatSessionUid) {
|
|
5501
|
+
const parts = candidateRef.split(".");
|
|
5502
|
+
if (parts.length !== 3 || parts[0] !== "arkme-group-candidate-v1") throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用无效", false);
|
|
5503
|
+
const encoded = parts[1] ?? "";
|
|
5504
|
+
const supplied = Buffer.from(parts[2] ?? "", "base64url");
|
|
5505
|
+
const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest();
|
|
5506
|
+
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用无效", false);
|
|
5507
|
+
let raw;
|
|
5508
|
+
try {
|
|
5509
|
+
raw = objectValue$1(decodeOpaqueJson$4(encoded));
|
|
5510
|
+
} catch (error) {
|
|
5511
|
+
throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用无效", false, 400, { cause: error });
|
|
5512
|
+
}
|
|
5513
|
+
const result = {
|
|
5514
|
+
version: 1,
|
|
5515
|
+
userId: numberValue$12(raw.userId),
|
|
5516
|
+
groupChatSessionUid: stringValue$1(raw.groupChatSessionUid).trim(),
|
|
5517
|
+
targetUserId: numberValue$12(raw.targetUserId),
|
|
5518
|
+
displayName: stringValue$1(raw.displayName).trim(),
|
|
5519
|
+
...stringValue$1(raw.privateChatSessionUid).trim() === "" ? {} : { privateChatSessionUid: stringValue$1(raw.privateChatSessionUid).trim() },
|
|
5520
|
+
origin: raw.origin === "group_chat" ? "group_chat" : "private_chat"
|
|
5521
|
+
};
|
|
5522
|
+
if (raw.version !== 1 || result.userId !== expectedUserId || result.groupChatSessionUid !== expectedGroupChatSessionUid || !Number.isSafeInteger(result.targetUserId) || result.targetUserId <= 0 || result.targetUserId === expectedUserId || result.displayName === "") throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用与当前账号不匹配", false, 403);
|
|
5523
|
+
return result;
|
|
5524
|
+
}
|
|
5525
|
+
async createGroup(title, clientMutationId, options = {}) {
|
|
5526
|
+
const normalizedTitle = title.trim();
|
|
5527
|
+
if (normalizedTitle === "" || Array.from(normalizedTitle).length > 80) throw new ArkmePluginError("group-title-invalid", "群聊名称需为 1-80 个字符", false);
|
|
5528
|
+
if (!UUID.test(clientMutationId)) throw new ArkmePluginError("client-mutation-id-invalid", "群聊操作标识无效", false);
|
|
5529
|
+
const session = await this.runtime.requireSession();
|
|
5530
|
+
let data;
|
|
5531
|
+
try {
|
|
5532
|
+
data = await this.runtime.authenticatedChatPost("/api/v1/chats/create-group", {
|
|
5533
|
+
chat_session_uid: clientMutationId,
|
|
5534
|
+
title: normalizedTitle,
|
|
5535
|
+
member_user_ids: [],
|
|
5536
|
+
create_at: Date.now()
|
|
5537
|
+
}, session, options.signal);
|
|
5538
|
+
} catch (error) {
|
|
5539
|
+
if (error instanceof ArkmePluginError && ["arkme-network-error", "arkme-timeout"].includes(error.code)) throw new ArkmePluginError("group-create-outcome-unknown", "群聊创建结果未知,请刷新会话列表确认;不会自动重试", false, 409, { cause: error });
|
|
5540
|
+
throw error;
|
|
5541
|
+
}
|
|
5542
|
+
const source = await this.source.chatSourceFromBundle(data, session, void 0, []);
|
|
5543
|
+
this.source.setChatSourceByKey(`${String(session.userId)}:${clientMutationId}`, source);
|
|
5544
|
+
return source;
|
|
4692
5545
|
}
|
|
4693
5546
|
async listGroupMembers(sourceRef, options = {}) {
|
|
4694
5547
|
const session = await this.runtime.requireSession();
|
|
@@ -4731,7 +5584,8 @@ var GroupService = class {
|
|
|
4731
5584
|
status,
|
|
4732
5585
|
isSelf: userId === session.userId,
|
|
4733
5586
|
isOwner: role === "owner",
|
|
4734
|
-
joinedAtMillis: numberValue$12(item.join_at)
|
|
5587
|
+
joinedAtMillis: numberValue$12(item.join_at),
|
|
5588
|
+
recordCount: Math.max(0, Math.trunc(numberValue$12(objectValue$1(item.extra).record_count)))
|
|
4735
5589
|
});
|
|
4736
5590
|
}
|
|
4737
5591
|
const roleRank = (role) => role === "owner" ? 0 : role === "admin" ? 1 : role === "member" ? 2 : 3;
|
|
@@ -5174,6 +6028,33 @@ function md5Text(value) {
|
|
|
5174
6028
|
function allowedSignedImageHost$2(environment, hostname) {
|
|
5175
6029
|
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());
|
|
5176
6030
|
}
|
|
6031
|
+
function allowedSignedAudioHost(environment, hostname) {
|
|
6032
|
+
return (environment === "prod" ? ["jotmo-useraudio.oss-cn-hangzhou.aliyuncs.com"] : ["jotmo-useraudio-test.oss-cn-hangzhou.aliyuncs.com"]).includes(hostname.toLowerCase());
|
|
6033
|
+
}
|
|
6034
|
+
function trustedWorldVoiceprintAudioUrl(environment, raw) {
|
|
6035
|
+
let parsed;
|
|
6036
|
+
try {
|
|
6037
|
+
parsed = new URL(raw.trim());
|
|
6038
|
+
} catch (error) {
|
|
6039
|
+
throw new ArkmePluginError("world-voiceprint-audio-invalid", "世界声纹音频地址无效", true, 502, { cause: error });
|
|
6040
|
+
}
|
|
6041
|
+
const signature = parsed.searchParams.get("x-oss-signature")?.trim() ?? "";
|
|
6042
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.hash !== "" || !allowedSignedAudioHost(environment, parsed.hostname) || parsed.pathname.replace(/^\/+/, "") === "" || signature === "") throw new ArkmePluginError("world-voiceprint-audio-rejected", "世界声纹音频来源不受信任", false, 502);
|
|
6043
|
+
return parsed;
|
|
6044
|
+
}
|
|
6045
|
+
function worldVoiceprintFileName(mimeType) {
|
|
6046
|
+
const extension = {
|
|
6047
|
+
wav: "wav",
|
|
6048
|
+
"x-wav": "wav",
|
|
6049
|
+
mpeg: "mp3",
|
|
6050
|
+
mp4: "m4a",
|
|
6051
|
+
aac: "aac",
|
|
6052
|
+
ogg: "ogg",
|
|
6053
|
+
webm: "webm",
|
|
6054
|
+
flac: "flac"
|
|
6055
|
+
}[mimeType.split(";", 1)[0].trim().toLowerCase().replace(/^audio\//, "")];
|
|
6056
|
+
return extension === void 0 ? "世界声纹" : `世界声纹.${extension}`;
|
|
6057
|
+
}
|
|
5177
6058
|
function trustedSignedImageUrl$1(environment, raw) {
|
|
5178
6059
|
let parsed;
|
|
5179
6060
|
try {
|
|
@@ -5234,6 +6115,7 @@ var MediaService = class {
|
|
|
5234
6115
|
worldImages;
|
|
5235
6116
|
recordIdentity;
|
|
5236
6117
|
mediaRefs = /* @__PURE__ */ new Map();
|
|
6118
|
+
stableMediaRefs = /* @__PURE__ */ new Map();
|
|
5237
6119
|
imageCache = /* @__PURE__ */ new Map();
|
|
5238
6120
|
imageInFlight = /* @__PURE__ */ new Map();
|
|
5239
6121
|
imageCacheBytes = 0;
|
|
@@ -5247,6 +6129,7 @@ var MediaService = class {
|
|
|
5247
6129
|
}
|
|
5248
6130
|
dispose() {
|
|
5249
6131
|
this.mediaRefs.clear();
|
|
6132
|
+
this.stableMediaRefs.clear();
|
|
5250
6133
|
this.imageCache.clear();
|
|
5251
6134
|
this.imageInFlight.clear();
|
|
5252
6135
|
this.imageCacheBytes = 0;
|
|
@@ -5353,18 +6236,20 @@ var MediaService = class {
|
|
|
5353
6236
|
throw error;
|
|
5354
6237
|
}
|
|
5355
6238
|
}
|
|
5356
|
-
async fetchMedia(mediaRef, range) {
|
|
6239
|
+
async fetchMedia(mediaRef, range, signal) {
|
|
5357
6240
|
const session = await this.runtime.requireSession();
|
|
5358
6241
|
const descriptor = this.mediaRefs.get(mediaRef);
|
|
5359
6242
|
if (descriptor === void 0 || descriptor.viewerUserId !== session.userId || descriptor.expiresAtMillis <= Date.now()) {
|
|
5360
6243
|
this.mediaRefs.delete(mediaRef);
|
|
6244
|
+
if (descriptor?.stableKey !== void 0) this.stableMediaRefs.delete(descriptor.stableKey);
|
|
5361
6245
|
throw new ArkmePluginError("media-ref-invalid", "媒体引用已失效,请刷新对话后重试", false, 404);
|
|
5362
6246
|
}
|
|
5363
6247
|
const url = new URL(descriptor.remoteUrl);
|
|
5364
|
-
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || !allowedSignedImageHost$2(this.runtime.config.environment, url.hostname)) throw new ArkmePluginError("media-host-rejected", "媒体来源不受信任", false, 403);
|
|
6248
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || !(allowedSignedImageHost$2(this.runtime.config.environment, url.hostname) || allowedSignedAudioHost(this.runtime.config.environment, url.hostname))) throw new ArkmePluginError("media-host-rejected", "媒体来源不受信任", false, 403);
|
|
5365
6249
|
const response = await this.runtime.fetchImpl(url, {
|
|
5366
6250
|
headers: range === void 0 ? {} : { Range: range },
|
|
5367
|
-
redirect: "error"
|
|
6251
|
+
redirect: "error",
|
|
6252
|
+
...signal === void 0 ? {} : { signal }
|
|
5368
6253
|
});
|
|
5369
6254
|
if (!response.ok && response.status !== 206) throw new ArkmePluginError("media-fetch-failed", `媒体读取失败(${String(response.status)})`, true, 502);
|
|
5370
6255
|
return {
|
|
@@ -5372,6 +6257,17 @@ var MediaService = class {
|
|
|
5372
6257
|
descriptor
|
|
5373
6258
|
};
|
|
5374
6259
|
}
|
|
6260
|
+
issueWorldVoiceprintMediaRef(viewerUserId, input) {
|
|
6261
|
+
const remoteUrl = trustedWorldVoiceprintAudioUrl(this.runtime.config.environment, input.remoteUrl).toString();
|
|
6262
|
+
const mimeType = input.mimeType.trim() || "audio/wav";
|
|
6263
|
+
if (!mimeType.startsWith("audio/")) throw new ArkmePluginError("world-voiceprint-mime-invalid", "世界声纹音频格式无效", true, 502);
|
|
6264
|
+
return this.issueMediaRef(viewerUserId, {
|
|
6265
|
+
remoteUrl,
|
|
6266
|
+
mimeType,
|
|
6267
|
+
fileName: worldVoiceprintFileName(mimeType),
|
|
6268
|
+
size: 0
|
|
6269
|
+
});
|
|
6270
|
+
}
|
|
5375
6271
|
async readImage(imageRef, options = {}) {
|
|
5376
6272
|
const session = await this.runtime.requireSession();
|
|
5377
6273
|
const maximumBytes = imageRef.trim().startsWith("arkme-profile-image-v1.") ? MAX_ARKME_PROFILE_IMAGE_BYTES : MAX_ARKME_IMAGE_BYTES;
|
|
@@ -5434,6 +6330,11 @@ var MediaService = class {
|
|
|
5434
6330
|
}
|
|
5435
6331
|
}
|
|
5436
6332
|
async readImageUncached(session, imageRef, byteLimit, signal) {
|
|
6333
|
+
if (imageRef.trim().startsWith("arkme-media-v1.")) {
|
|
6334
|
+
const { response, descriptor } = await this.fetchMedia(imageRef.trim(), void 0, signal);
|
|
6335
|
+
if (!descriptor.mimeType.trim().toLowerCase().startsWith("image/")) throw new ArkmePluginError("image-type-unsupported", "该媒体引用不是图片", false, 415);
|
|
6336
|
+
return await this.imageBytesFromResponse(response, byteLimit);
|
|
6337
|
+
}
|
|
5437
6338
|
if (imageRef.trim().startsWith("arkme-profile-image-v1.")) {
|
|
5438
6339
|
const reference = await this.profile.openProfileImageRef(imageRef, session.userId);
|
|
5439
6340
|
if (reference.targetUserId === session.userId) {
|
|
@@ -5563,20 +6464,33 @@ var MediaService = class {
|
|
|
5563
6464
|
unavailableRecordUids
|
|
5564
6465
|
};
|
|
5565
6466
|
}
|
|
5566
|
-
|
|
6467
|
+
issueImageMediaRef(viewerUserId, descriptor, stableIdentity) {
|
|
6468
|
+
return this.issueMediaRef(viewerUserId, descriptor, stableIdentity);
|
|
6469
|
+
}
|
|
6470
|
+
issueMediaRef(viewerUserId, descriptor, stableIdentity) {
|
|
5567
6471
|
const now = Date.now();
|
|
5568
|
-
for (const [key, value] of this.mediaRefs) if (value.expiresAtMillis <= now)
|
|
6472
|
+
for (const [key, value] of this.mediaRefs) if (value.expiresAtMillis <= now) {
|
|
6473
|
+
this.mediaRefs.delete(key);
|
|
6474
|
+
if (value.stableKey !== void 0) this.stableMediaRefs.delete(value.stableKey);
|
|
6475
|
+
}
|
|
5569
6476
|
while (this.mediaRefs.size >= 2e3) {
|
|
5570
6477
|
const oldest = this.mediaRefs.keys().next().value;
|
|
5571
6478
|
if (oldest === void 0) break;
|
|
6479
|
+
const oldestDescriptor = this.mediaRefs.get(oldest);
|
|
5572
6480
|
this.mediaRefs.delete(oldest);
|
|
6481
|
+
if (oldestDescriptor?.stableKey !== void 0) this.stableMediaRefs.delete(oldestDescriptor.stableKey);
|
|
5573
6482
|
}
|
|
5574
|
-
const
|
|
6483
|
+
const stableKey = stableIdentity === void 0 ? void 0 : createHash("sha256").update(`${String(viewerUserId)}\0${stableIdentity}`).digest("base64url");
|
|
6484
|
+
const ref = (stableKey === void 0 ? void 0 : this.stableMediaRefs.get(stableKey)) ?? `arkme-media-v1.${randomUUID()}`;
|
|
6485
|
+
this.mediaRefs.delete(ref);
|
|
6486
|
+
const lifetimeMillis = stableKey === void 0 ? 30 * 6e4 : 1440 * 6e4;
|
|
5575
6487
|
this.mediaRefs.set(ref, {
|
|
5576
6488
|
...descriptor,
|
|
5577
6489
|
viewerUserId,
|
|
5578
|
-
expiresAtMillis: now +
|
|
6490
|
+
expiresAtMillis: now + lifetimeMillis,
|
|
6491
|
+
...stableKey === void 0 ? {} : { stableKey }
|
|
5579
6492
|
});
|
|
6493
|
+
if (stableKey !== void 0) this.stableMediaRefs.set(stableKey, ref);
|
|
5580
6494
|
return ref;
|
|
5581
6495
|
}
|
|
5582
6496
|
richContentBlocks(raw, viewerUserId, hydratedDisplayItems = []) {
|
|
@@ -5619,7 +6533,7 @@ var MediaService = class {
|
|
|
5619
6533
|
mimeType,
|
|
5620
6534
|
fileName,
|
|
5621
6535
|
size: Math.max(0, Math.trunc(numberValue$10(item.size)))
|
|
5622
|
-
}),
|
|
6536
|
+
}, kind === "image" && fileAssetUid !== "" ? fileAssetUid : void 0),
|
|
5623
6537
|
...fileAssetUid === "" ? {} : { fileAssetUid },
|
|
5624
6538
|
fileName,
|
|
5625
6539
|
mimeType,
|
|
@@ -5663,36 +6577,7 @@ var MediaService = class {
|
|
|
5663
6577
|
signal: controller.signal
|
|
5664
6578
|
});
|
|
5665
6579
|
if (!response.ok) throw new ArkmePluginError("image-download-failed", `Arkme 图片读取返回 HTTP ${response.status}`, true, 502);
|
|
5666
|
-
|
|
5667
|
-
if (Number.isFinite(declaredLength) && declaredLength > byteLimit) throw new ArkmePluginError("image-too-large", "Arkme 图片超过读取大小限制", false, 413);
|
|
5668
|
-
if (response.body === null) throw new ArkmePluginError("image-response-empty", "Arkme 图片响应为空", true, 502);
|
|
5669
|
-
const chunks = [];
|
|
5670
|
-
let bytes = 0;
|
|
5671
|
-
const reader = response.body.getReader();
|
|
5672
|
-
while (true) {
|
|
5673
|
-
const next = await reader.read();
|
|
5674
|
-
if (next.done) break;
|
|
5675
|
-
bytes += next.value.byteLength;
|
|
5676
|
-
if (bytes > byteLimit) {
|
|
5677
|
-
await reader.cancel();
|
|
5678
|
-
throw new ArkmePluginError("image-too-large", "Arkme 图片超过读取大小限制", false, 413);
|
|
5679
|
-
}
|
|
5680
|
-
chunks.push(next.value);
|
|
5681
|
-
}
|
|
5682
|
-
if (bytes === 0) throw new ArkmePluginError("image-response-empty", "Arkme 图片响应为空", true, 502);
|
|
5683
|
-
const data = new Uint8Array(bytes);
|
|
5684
|
-
let offset = 0;
|
|
5685
|
-
for (const chunk of chunks) {
|
|
5686
|
-
data.set(chunk, offset);
|
|
5687
|
-
offset += chunk.byteLength;
|
|
5688
|
-
}
|
|
5689
|
-
const mediaType = imageMediaType(data);
|
|
5690
|
-
if (mediaType === void 0) throw new ArkmePluginError("image-type-unsupported", "Arkme 图片不是受支持的 PNG、JPEG、WebP 或 GIF", false, 415);
|
|
5691
|
-
return {
|
|
5692
|
-
mediaType,
|
|
5693
|
-
bytes,
|
|
5694
|
-
data
|
|
5695
|
-
};
|
|
6580
|
+
return await this.imageBytesFromResponse(response, byteLimit);
|
|
5696
6581
|
} catch (error) {
|
|
5697
6582
|
if (error instanceof ArkmePluginError) throw error;
|
|
5698
6583
|
if (error.name === "AbortError") throw new ArkmePluginError("image-download-timeout", "Arkme 图片读取超时或已取消", true, 504, { cause: error });
|
|
@@ -5702,6 +6587,38 @@ var MediaService = class {
|
|
|
5702
6587
|
signal?.removeEventListener("abort", abort);
|
|
5703
6588
|
}
|
|
5704
6589
|
}
|
|
6590
|
+
async imageBytesFromResponse(response, byteLimit) {
|
|
6591
|
+
const declaredLength = Number(response.headers.get("content-length") ?? 0);
|
|
6592
|
+
if (Number.isFinite(declaredLength) && declaredLength > byteLimit) throw new ArkmePluginError("image-too-large", "Arkme 图片超过读取大小限制", false, 413);
|
|
6593
|
+
if (response.body === null) throw new ArkmePluginError("image-response-empty", "Arkme 图片响应为空", true, 502);
|
|
6594
|
+
const chunks = [];
|
|
6595
|
+
let bytes = 0;
|
|
6596
|
+
const reader = response.body.getReader();
|
|
6597
|
+
while (true) {
|
|
6598
|
+
const next = await reader.read();
|
|
6599
|
+
if (next.done) break;
|
|
6600
|
+
bytes += next.value.byteLength;
|
|
6601
|
+
if (bytes > byteLimit) {
|
|
6602
|
+
await reader.cancel();
|
|
6603
|
+
throw new ArkmePluginError("image-too-large", "Arkme 图片超过读取大小限制", false, 413);
|
|
6604
|
+
}
|
|
6605
|
+
chunks.push(next.value);
|
|
6606
|
+
}
|
|
6607
|
+
if (bytes === 0) throw new ArkmePluginError("image-response-empty", "Arkme 图片响应为空", true, 502);
|
|
6608
|
+
const data = new Uint8Array(bytes);
|
|
6609
|
+
let offset = 0;
|
|
6610
|
+
for (const chunk of chunks) {
|
|
6611
|
+
data.set(chunk, offset);
|
|
6612
|
+
offset += chunk.byteLength;
|
|
6613
|
+
}
|
|
6614
|
+
const mediaType = imageMediaType(data);
|
|
6615
|
+
if (mediaType === void 0) throw new ArkmePluginError("image-type-unsupported", "Arkme 图片不是受支持的 PNG、JPEG、WebP 或 GIF", false, 415);
|
|
6616
|
+
return {
|
|
6617
|
+
mediaType,
|
|
6618
|
+
bytes,
|
|
6619
|
+
data
|
|
6620
|
+
};
|
|
6621
|
+
}
|
|
5705
6622
|
};
|
|
5706
6623
|
//#endregion
|
|
5707
6624
|
//#region src/services/outgoing-call-service.ts
|
|
@@ -6615,7 +7532,7 @@ function displayTimestamp(value) {
|
|
|
6615
7532
|
const parsed = Date.parse(stringValue(value));
|
|
6616
7533
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
6617
7534
|
}
|
|
6618
|
-
function projectRecordingTranscripts(response, speakerResponse) {
|
|
7535
|
+
function projectRecordingTranscripts(response, speakerResponse, source = "system") {
|
|
6619
7536
|
const data = objectValue(response);
|
|
6620
7537
|
const sessions = /* @__PURE__ */ new Map();
|
|
6621
7538
|
const sessionSpeakerColorIndexes = /* @__PURE__ */ new Map();
|
|
@@ -6659,7 +7576,32 @@ function projectRecordingTranscripts(response, speakerResponse) {
|
|
|
6659
7576
|
const sessionSpeakers = listValue$6(session.spk_ls ?? session.speakers).map(objectValue);
|
|
6660
7577
|
const childOffset = numberValue$6(child.start_at);
|
|
6661
7578
|
const childStart = childOffset >= 1e11 ? childOffset : numberValue$6(session.start_at) + childOffset;
|
|
6662
|
-
const rows = listValue$6(child.asr);
|
|
7579
|
+
const rows = listValue$6(source === "doubao" ? child.doubao_asr : child.asr);
|
|
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
|
+
}
|
|
6663
7605
|
for (let index = 0; index < rows.length; index += 1) {
|
|
6664
7606
|
const row = objectValue(rows[index]);
|
|
6665
7607
|
const isBackground = numberValue$6(row.b ?? row.background) === 1 || row.background === true;
|
|
@@ -6673,15 +7615,15 @@ function projectRecordingTranscripts(response, speakerResponse) {
|
|
|
6673
7615
|
const persistentSpeakerNumber = optionalNumberValue(sessionSpeaker.speaker_display_number ?? sessionSpeaker.speakerDisplayNumber);
|
|
6674
7616
|
const speakerNumber = persistentSpeakerNumber !== void 0 && persistentSpeakerNumber > 0 ? persistentSpeakerNumber : rawSpeakerNumber;
|
|
6675
7617
|
const speakerColorIndex = sessionSpeakerColorIndexes.get(`${sessionId}:${rawSpeakerNumber}`) ?? Math.max(0, rawSpeakerNumber);
|
|
6676
|
-
const speakerLabel = speakerNumber >= 0 ? `说话人 ${speakerNumber}` : "未知说话人";
|
|
7618
|
+
const speakerLabel = source === "doubao" ? rawSpeakerNumber >= 0 ? `豆包说话人 ${rawSpeakerNumber + 1}` : "豆包说话人" : speakerNumber >= 0 ? `说话人 ${speakerNumber}` : "未知说话人";
|
|
6677
7619
|
const startOffset = numberValue$6(row.s ?? row.start_at);
|
|
6678
7620
|
const endOffset = Math.max(startOffset, numberValue$6(row.e ?? row.end_at));
|
|
6679
7621
|
projected.push({
|
|
6680
|
-
itemId: `${childId || sessionId}:${index}`,
|
|
7622
|
+
itemId: source === "system" ? `${childId || sessionId}:${index}` : `${childId || sessionId}:doubao:${index}`,
|
|
6681
7623
|
sessionId,
|
|
6682
7624
|
childId,
|
|
6683
7625
|
asrItemIndex: index,
|
|
6684
|
-
transcriptSource:
|
|
7626
|
+
transcriptSource: source,
|
|
6685
7627
|
startAtMillis: childStart + startOffset,
|
|
6686
7628
|
endAtMillis: childStart + endOffset,
|
|
6687
7629
|
speakerNumber,
|
|
@@ -6690,6 +7632,7 @@ function projectRecordingTranscripts(response, speakerResponse) {
|
|
|
6690
7632
|
isSelf,
|
|
6691
7633
|
isBackground,
|
|
6692
7634
|
text,
|
|
7635
|
+
...source === "doubao" ? { transcriptStatus: "ready" } : {},
|
|
6693
7636
|
sourceIndex
|
|
6694
7637
|
});
|
|
6695
7638
|
sourceIndex += 1;
|
|
@@ -6936,7 +7879,21 @@ var RecordingService = class {
|
|
|
6936
7879
|
days
|
|
6937
7880
|
};
|
|
6938
7881
|
}
|
|
6939
|
-
async recordingTranscript(dateStamp, signal) {
|
|
7882
|
+
async recordingTranscript(dateStamp, signal, source = "system") {
|
|
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) {
|
|
6940
7897
|
const dayStart = this.recordingDayStart(dateStamp);
|
|
6941
7898
|
const date = dayStart.getTime();
|
|
6942
7899
|
const session = await this.runtime.requireSession();
|
|
@@ -6948,14 +7905,29 @@ var RecordingService = class {
|
|
|
6948
7905
|
let totalDurationMillis = 0;
|
|
6949
7906
|
for (const rawSession of listValue$5(transcriptResult.value.session_ls)) totalDurationMillis += Math.max(0, numberValue$5(objectValue$1(rawSession).duration));
|
|
6950
7907
|
const speakerData = speakerResult.status === "fulfilled" ? listValue$5(speakerResult.value.spk_ls) : [];
|
|
6951
|
-
const
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
7908
|
+
const identityCoverage = speakerResult.status === "fulfilled" ? "complete" : "partial";
|
|
7909
|
+
const systemItems = projectRecordingTranscripts(transcriptResult.value, speakerData, "system");
|
|
7910
|
+
const doubaoItems = projectRecordingTranscripts(transcriptResult.value, speakerData, "doubao");
|
|
7911
|
+
const system = {
|
|
7912
|
+
state: systemItems.length > 0 ? "ready" : "empty",
|
|
7913
|
+
items: systemItems,
|
|
7914
|
+
message: systemItems.length > 0 ? "" : "当天无录音",
|
|
7915
|
+
identityCoverage,
|
|
6957
7916
|
totalDurationMillis
|
|
6958
7917
|
};
|
|
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
|
+
};
|
|
6959
7931
|
}
|
|
6960
7932
|
async recordingProjection(dateStamp, kind, signal) {
|
|
6961
7933
|
const dayStart = this.recordingDayStart(dateStamp);
|
|
@@ -6993,6 +7965,7 @@ var RecordingService = class {
|
|
|
6993
7965
|
version: 1,
|
|
6994
7966
|
dateStamp: numberValue$5(raw.dateStamp),
|
|
6995
7967
|
content: content === "summary" || content === "timeline" ? content : "transcript",
|
|
7968
|
+
...content === "transcript" && (raw.transcriptSource === "system" || raw.transcriptSource === "doubao") ? { transcriptSource: raw.transcriptSource } : {},
|
|
6996
7969
|
itemOffset: numberValue$5(raw.itemOffset),
|
|
6997
7970
|
textOffset: numberValue$5(raw.textOffset),
|
|
6998
7971
|
fingerprint: stringValue$1(raw.fingerprint),
|
|
@@ -7002,25 +7975,31 @@ var RecordingService = class {
|
|
|
7002
7975
|
"transcript",
|
|
7003
7976
|
"summary",
|
|
7004
7977
|
"timeline"
|
|
7005
|
-
].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);
|
|
7978
|
+
].includes(String(content)) || content === "transcript" && raw.transcriptSource !== void 0 && raw.transcriptSource !== "system" && raw.transcriptSource !== "doubao" || !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);
|
|
7006
7979
|
return payload;
|
|
7007
7980
|
}
|
|
7008
7981
|
async recordingDay(dateStamp) {
|
|
7009
7982
|
const date = this.recordingDayStart(dateStamp).getTime();
|
|
7010
7983
|
const [transcriptResult, summaryResult, timelineResult] = await Promise.allSettled([
|
|
7011
|
-
this.
|
|
7984
|
+
this.recordingTranscriptSections(date),
|
|
7012
7985
|
this.recordingProjection(date, "summary"),
|
|
7013
7986
|
this.recordingProjection(date, "timeline")
|
|
7014
7987
|
]);
|
|
7015
|
-
const transcript = transcriptResult.status === "fulfilled" ? transcriptResult.value : {
|
|
7988
|
+
const transcript = transcriptResult.status === "fulfilled" ? transcriptResult.value.system : {
|
|
7989
|
+
state: "error",
|
|
7990
|
+
items: [],
|
|
7991
|
+
message: safeFailureMessage$2(transcriptResult.reason)
|
|
7992
|
+
};
|
|
7993
|
+
const doubaoTranscript = transcriptResult.status === "fulfilled" ? transcriptResult.value.doubao : {
|
|
7016
7994
|
state: "error",
|
|
7017
7995
|
items: [],
|
|
7018
7996
|
message: safeFailureMessage$2(transcriptResult.reason)
|
|
7019
7997
|
};
|
|
7020
7998
|
return {
|
|
7021
7999
|
dateStamp: date,
|
|
7022
|
-
totalDurationMillis: transcriptResult.status === "fulfilled" ? transcriptResult.value.totalDurationMillis : 0,
|
|
8000
|
+
totalDurationMillis: transcriptResult.status === "fulfilled" ? transcriptResult.value.system.totalDurationMillis : 0,
|
|
7023
8001
|
transcript,
|
|
8002
|
+
doubaoTranscript,
|
|
7024
8003
|
summary: summaryResult.status === "fulfilled" ? summaryResult.value : {
|
|
7025
8004
|
state: "error",
|
|
7026
8005
|
items: [],
|
|
@@ -7248,9 +8227,11 @@ function listValue$3(value) {
|
|
|
7248
8227
|
var SearchService = class {
|
|
7249
8228
|
runtime;
|
|
7250
8229
|
record;
|
|
7251
|
-
|
|
8230
|
+
media;
|
|
8231
|
+
constructor(runtime, record, media) {
|
|
7252
8232
|
this.runtime = runtime;
|
|
7253
8233
|
this.record = record;
|
|
8234
|
+
this.media = media;
|
|
7254
8235
|
}
|
|
7255
8236
|
async searchRecords(options) {
|
|
7256
8237
|
const query = options.query.trim();
|
|
@@ -7326,6 +8307,85 @@ var SearchService = class {
|
|
|
7326
8307
|
}, session, options.signal);
|
|
7327
8308
|
return this.recordSearchResult(data);
|
|
7328
8309
|
}
|
|
8310
|
+
/** Build the desktop image library from the mixed image/video scene. */
|
|
8311
|
+
async searchImages(options) {
|
|
8312
|
+
const session = await this.runtime.requireSession();
|
|
8313
|
+
const pageLimit = Math.min(50, Math.max(1, Math.trunc(options.limit)));
|
|
8314
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
8315
|
+
let cursor = options.cursor?.trim() ?? "";
|
|
8316
|
+
if (cursor !== "") seenCursors.add(cursor);
|
|
8317
|
+
let lastPage;
|
|
8318
|
+
for (let pageIndex = 0; pageIndex < 8; pageIndex += 1) {
|
|
8319
|
+
const page = await this.searchScene({
|
|
8320
|
+
scene: "image_video",
|
|
8321
|
+
limit: pageLimit,
|
|
8322
|
+
...cursor === "" ? {} : { cursor },
|
|
8323
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
8324
|
+
});
|
|
8325
|
+
lastPage = page;
|
|
8326
|
+
const candidates = page.items.flatMap((record) => record.media.flatMap((asset) => {
|
|
8327
|
+
const mimeType = asset.mimeType?.trim().toLowerCase() ?? "";
|
|
8328
|
+
return mimeType.startsWith("image/") || mimeType === "" && (asset.fileKind === void 0 || asset.fileKind === 1) ? [{
|
|
8329
|
+
record,
|
|
8330
|
+
asset
|
|
8331
|
+
}] : [];
|
|
8332
|
+
}));
|
|
8333
|
+
const uniqueAssetUids = [...new Set(candidates.map((candidate) => candidate.asset.fileAssetUid))];
|
|
8334
|
+
const displayItems = [];
|
|
8335
|
+
for (let offset = 0; offset < uniqueAssetUids.length; offset += 50) displayItems.push(...await this.media.queryFileAssets(uniqueAssetUids.slice(offset, offset + 50), options.signal));
|
|
8336
|
+
const displayByUid = new Map(displayItems.map((item) => [item.fileAssetUid, item]));
|
|
8337
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
8338
|
+
const items = candidates.flatMap(({ record, asset }) => {
|
|
8339
|
+
const itemIdentity = `${record.recordUid}\0${asset.fileAssetUid}`;
|
|
8340
|
+
if (emitted.has(itemIdentity)) return [];
|
|
8341
|
+
const display = displayByUid.get(asset.fileAssetUid);
|
|
8342
|
+
if (display === void 0) return [];
|
|
8343
|
+
const mimeType = (display.mimeType ?? asset.mimeType ?? "").trim().toLowerCase();
|
|
8344
|
+
if (!mimeType.startsWith("image/")) return [];
|
|
8345
|
+
const remoteUrl = display.previewUrl ?? display.downloadUrl;
|
|
8346
|
+
if (remoteUrl === void 0) return [];
|
|
8347
|
+
emitted.add(itemIdentity);
|
|
8348
|
+
const fileName = display.fileName ?? asset.fileName ?? "图片";
|
|
8349
|
+
return [{
|
|
8350
|
+
itemKey: createHash("sha256").update(itemIdentity).digest("base64url"),
|
|
8351
|
+
mediaRef: this.media.issueImageMediaRef(session.userId, {
|
|
8352
|
+
remoteUrl,
|
|
8353
|
+
mimeType: mimeType || "application/octet-stream",
|
|
8354
|
+
fileName,
|
|
8355
|
+
size: Math.max(0, Math.trunc(asset.size ?? 0))
|
|
8356
|
+
}, asset.fileAssetUid),
|
|
8357
|
+
recordUid: record.recordUid,
|
|
8358
|
+
sendAtMillis: record.sendAtMillis,
|
|
8359
|
+
fileName,
|
|
8360
|
+
mimeType: mimeType || "application/octet-stream",
|
|
8361
|
+
size: Math.max(0, Math.trunc(asset.size ?? 0)),
|
|
8362
|
+
recordTitle: record.title || record.nickname || "快记",
|
|
8363
|
+
...record.sourceTitle === void 0 ? {} : { sourceTitle: record.sourceTitle }
|
|
8364
|
+
}];
|
|
8365
|
+
});
|
|
8366
|
+
const nextCursor = page.nextCursor?.trim() ?? "";
|
|
8367
|
+
const canContinue = page.hasMore && nextCursor !== "" && !seenCursors.has(nextCursor);
|
|
8368
|
+
if (items.length > 0 || !canContinue) return {
|
|
8369
|
+
items,
|
|
8370
|
+
hasMore: canContinue,
|
|
8371
|
+
...canContinue ? { nextCursor } : {},
|
|
8372
|
+
queryGuard: page.queryGuard
|
|
8373
|
+
};
|
|
8374
|
+
if (pageIndex === 7) return {
|
|
8375
|
+
items: [],
|
|
8376
|
+
hasMore: true,
|
|
8377
|
+
nextCursor,
|
|
8378
|
+
queryGuard: page.queryGuard
|
|
8379
|
+
};
|
|
8380
|
+
seenCursors.add(nextCursor);
|
|
8381
|
+
cursor = nextCursor;
|
|
8382
|
+
}
|
|
8383
|
+
return {
|
|
8384
|
+
items: [],
|
|
8385
|
+
hasMore: false,
|
|
8386
|
+
queryGuard: lastPage?.queryGuard ?? { state: "complete" }
|
|
8387
|
+
};
|
|
8388
|
+
}
|
|
7329
8389
|
async searchRecordings(options) {
|
|
7330
8390
|
const query = options.query.trim();
|
|
7331
8391
|
if (query === "") throw new ArkmePluginError("recording-query-empty", "搜索关键词不能为空", false);
|
|
@@ -8372,6 +9432,8 @@ const ARKME_WORLD_IMAGE_REF_TTL_MILLIS = 900 * 1e3;
|
|
|
8372
9432
|
const MAX_ARKME_WORLD_IMAGE_REFS = 2048;
|
|
8373
9433
|
const ARKME_WORLD_RECORD_REF_TTL_MILLIS = 900 * 1e3;
|
|
8374
9434
|
const MAX_ARKME_WORLD_RECORD_REFS = 4096;
|
|
9435
|
+
const ARKME_VOICEPRINT_PLAY_SCOPE = 2;
|
|
9436
|
+
const ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH = 2048;
|
|
8375
9437
|
function numberValue(value) {
|
|
8376
9438
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
8377
9439
|
}
|
|
@@ -8383,6 +9445,27 @@ function safeFailureMessage(error) {
|
|
|
8383
9445
|
if (error instanceof Error && error.message.trim() !== "") return error.message;
|
|
8384
9446
|
return "未知错误";
|
|
8385
9447
|
}
|
|
9448
|
+
function optionalTrimmedText(value, limit) {
|
|
9449
|
+
const normalized = value?.replace(/\s+/g, " ").trim();
|
|
9450
|
+
if (normalized === void 0 || normalized === "") return void 0;
|
|
9451
|
+
return normalized.length <= limit ? normalized : `${normalized.slice(0, limit).trimEnd()}...`;
|
|
9452
|
+
}
|
|
9453
|
+
function buildVoiceprintInviteShareUrl(environment, token) {
|
|
9454
|
+
const url = new URL("/app/voiceprint/invite", environment === "prod" ? "https://jiwo.cc" : "https://jotmo-app.senguo.me");
|
|
9455
|
+
url.hash = `t=${encodeURIComponent(token)}`;
|
|
9456
|
+
return url.toString();
|
|
9457
|
+
}
|
|
9458
|
+
function validVoiceprintInviteToken(token) {
|
|
9459
|
+
if (token === "" || token.length > ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH) return false;
|
|
9460
|
+
let byteLength = 0;
|
|
9461
|
+
for (const char of token) {
|
|
9462
|
+
const codePoint = char.codePointAt(0) ?? 0;
|
|
9463
|
+
if (codePoint <= 31 || codePoint === 127) return false;
|
|
9464
|
+
byteLength += codePoint <= 127 ? 1 : codePoint <= 2047 ? 2 : codePoint <= 65535 ? 3 : 4;
|
|
9465
|
+
if (byteLength > ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH) return false;
|
|
9466
|
+
}
|
|
9467
|
+
return true;
|
|
9468
|
+
}
|
|
8386
9469
|
function worldVisibility(checkStatus) {
|
|
8387
9470
|
if (checkStatus === 1) return "pending_review";
|
|
8388
9471
|
if (checkStatus === 4) return "rejected";
|
|
@@ -8485,6 +9568,110 @@ var WorldService = class {
|
|
|
8485
9568
|
...hasMore ? { nextOffset } : {}
|
|
8486
9569
|
};
|
|
8487
9570
|
}
|
|
9571
|
+
async listMyWorldFeed(options = {}) {
|
|
9572
|
+
const session = await this.runtime.requireSession();
|
|
9573
|
+
const limit = Math.min(20, Math.max(1, Math.trunc(options.limit ?? 20)));
|
|
9574
|
+
const offset = Math.max(0, Math.trunc(options.offset ?? 0));
|
|
9575
|
+
const data = await this.runtime.authenticatedWorldPost("/api/v1/public-record/my-list", {
|
|
9576
|
+
limit,
|
|
9577
|
+
offset
|
|
9578
|
+
}, session, options.signal);
|
|
9579
|
+
const rawItems = listValue(data.list);
|
|
9580
|
+
const resolvedAvatars = await this.resolveWorldAvatarUrls(rawItems, session, options.signal);
|
|
9581
|
+
const items = (await Promise.all(rawItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars)))).filter((item) => item !== void 0);
|
|
9582
|
+
const total = Math.max(0, Math.trunc(numberValue(data.total)));
|
|
9583
|
+
const nextOffset = offset + rawItems.length;
|
|
9584
|
+
const hasMore = rawItems.length > 0 && nextOffset < total;
|
|
9585
|
+
return {
|
|
9586
|
+
items,
|
|
9587
|
+
total,
|
|
9588
|
+
hasMore,
|
|
9589
|
+
...hasMore ? { nextOffset } : {}
|
|
9590
|
+
};
|
|
9591
|
+
}
|
|
9592
|
+
async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
|
|
9593
|
+
const session = await this.runtime.requireSession();
|
|
9594
|
+
const normalizedRefs = [...new Set(recordRefs.map((value) => value.trim()).filter((value) => value !== ""))].slice(0, 20);
|
|
9595
|
+
if (normalizedRefs.length === 0) return { items: [] };
|
|
9596
|
+
const entries = normalizedRefs.map((recordRef) => ({
|
|
9597
|
+
recordRef,
|
|
9598
|
+
entry: this.openWorldRecordRef(recordRef, session.userId)
|
|
9599
|
+
}));
|
|
9600
|
+
const ownerUserIds = [...new Set(entries.map((value) => value.entry.ownerUserId ?? 0).filter((userId) => Number.isSafeInteger(userId) && userId > 0))].sort((left, right) => left - right);
|
|
9601
|
+
if (ownerUserIds.length === 0) return { items: entries.map(({ recordRef }) => ({
|
|
9602
|
+
recordRef,
|
|
9603
|
+
playable: false
|
|
9604
|
+
})) };
|
|
9605
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/world-playback-availability", { user_ids: ownerUserIds }, session, signal, {
|
|
9606
|
+
key: ownerUserIds.join(","),
|
|
9607
|
+
cacheMs: 5e3
|
|
9608
|
+
});
|
|
9609
|
+
const playableByOwner = /* @__PURE__ */ new Map();
|
|
9610
|
+
for (const raw of listValue(data.items)) {
|
|
9611
|
+
const item = objectValue$1(raw);
|
|
9612
|
+
const userId = Math.trunc(numberValue(item.user_id));
|
|
9613
|
+
if (userId > 0) playableByOwner.set(userId, item.playable === true);
|
|
9614
|
+
}
|
|
9615
|
+
return { items: entries.map(({ recordRef, entry }) => ({
|
|
9616
|
+
recordRef,
|
|
9617
|
+
playable: playableByOwner.get(entry.ownerUserId ?? 0) === true
|
|
9618
|
+
})) };
|
|
9619
|
+
}
|
|
9620
|
+
async generateWorldVoiceprintPlayback(input) {
|
|
9621
|
+
const session = await this.runtime.requireSession();
|
|
9622
|
+
const entry = this.openWorldRecordRef(input.recordRef, session.userId);
|
|
9623
|
+
const chunkIndex = Math.trunc(input.chunkIndex ?? 0);
|
|
9624
|
+
if (!Number.isSafeInteger(chunkIndex) || chunkIndex < 0 || chunkIndex >= 334) throw new ArkmePluginError("world-voiceprint-playback-input-invalid", "世界声纹播放参数无效,请刷新后重试", false);
|
|
9625
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/generate-playback", {
|
|
9626
|
+
source_scene: 4,
|
|
9627
|
+
source_id: entry.recordUid,
|
|
9628
|
+
source_chunk_index: chunkIndex
|
|
9629
|
+
}, session, input.signal, {
|
|
9630
|
+
lane: "write",
|
|
9631
|
+
bypassCache: true
|
|
9632
|
+
});
|
|
9633
|
+
const mimeType = stringValue$1(data.mime_type).trim() || "audio/wav";
|
|
9634
|
+
const responseChunkIndex = Math.trunc(numberValue(data.source_chunk_index));
|
|
9635
|
+
const chunkCount = Math.trunc(numberValue(data.source_chunk_count));
|
|
9636
|
+
const chunkStartRune = Math.trunc(numberValue(data.source_chunk_start_rune));
|
|
9637
|
+
const chunkEndRune = Math.trunc(numberValue(data.source_chunk_end_rune));
|
|
9638
|
+
if (responseChunkIndex !== chunkIndex || chunkCount <= 0 || chunkCount > 334 || chunkIndex >= chunkCount || chunkStartRune < 0 || chunkEndRune <= chunkStartRune) throw new ArkmePluginError("world-voiceprint-playback-response-invalid", "世界声纹播放响应无效,请重试", true, 502);
|
|
9639
|
+
return {
|
|
9640
|
+
mediaRef: this.media.issueWorldVoiceprintMediaRef(session.userId, {
|
|
9641
|
+
remoteUrl: stringValue$1(data.audio_url),
|
|
9642
|
+
mimeType
|
|
9643
|
+
}),
|
|
9644
|
+
mimeType,
|
|
9645
|
+
durationMillis: Math.max(0, Math.trunc(numberValue(data.duration_ms))),
|
|
9646
|
+
cacheHit: data.cache_hit === true,
|
|
9647
|
+
chunkIndex: responseChunkIndex,
|
|
9648
|
+
chunkCount,
|
|
9649
|
+
chunkStartRune,
|
|
9650
|
+
chunkEndRune
|
|
9651
|
+
};
|
|
9652
|
+
}
|
|
9653
|
+
async createWorldVoiceprintInviteIntent(recordRef, signal) {
|
|
9654
|
+
const session = await this.runtime.requireSession();
|
|
9655
|
+
const entry = this.openWorldRecordRef(recordRef, session.userId);
|
|
9656
|
+
const peerUserId = entry.ownerUserId ?? 0;
|
|
9657
|
+
if (!Number.isSafeInteger(peerUserId) || peerUserId <= 0) throw new ArkmePluginError("world-voiceprint-invite-target-missing", "无法确认这条世界动态的作者,请刷新后重试", false);
|
|
9658
|
+
if (peerUserId === session.userId) throw new ArkmePluginError("world-voiceprint-invite-self-invalid", "这是你自己的动态,不需要提醒自己开启声纹", false, 409);
|
|
9659
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/invites/create", { scope: ARKME_VOICEPRINT_PLAY_SCOPE }, session, signal, {
|
|
9660
|
+
lane: "write",
|
|
9661
|
+
bypassCache: true
|
|
9662
|
+
});
|
|
9663
|
+
const token = stringValue$1(data.invite_token).trim();
|
|
9664
|
+
const expiresAtMillis = Math.trunc(numberValue(data.expires_at));
|
|
9665
|
+
const scope = Math.trunc(numberValue(data.scope));
|
|
9666
|
+
if (!validVoiceprintInviteToken(token) || expiresAtMillis <= 0 || scope !== ARKME_VOICEPRINT_PLAY_SCOPE) throw new ArkmePluginError("world-voiceprint-invite-contract-invalid", "声纹邀请响应不完整,请稍后重试", true, 502);
|
|
9667
|
+
return {
|
|
9668
|
+
peerUserId,
|
|
9669
|
+
peerDisplayName: optionalTrimmedText(entry.authorName, 64) ?? "这位用户",
|
|
9670
|
+
inviteUrl: buildVoiceprintInviteShareUrl(this.runtime.config.environment, token),
|
|
9671
|
+
expiresAtMillis,
|
|
9672
|
+
...entry.textPreview === void 0 ? {} : { textPreview: entry.textPreview }
|
|
9673
|
+
};
|
|
9674
|
+
}
|
|
8488
9675
|
async listWorldInteractions(recordRef, options = {}) {
|
|
8489
9676
|
const session = await this.runtime.requireSession();
|
|
8490
9677
|
const root = this.openWorldRecordRef(recordRef, session.userId);
|
|
@@ -8695,7 +9882,14 @@ var WorldService = class {
|
|
|
8695
9882
|
}
|
|
8696
9883
|
const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl) : void 0;
|
|
8697
9884
|
return {
|
|
8698
|
-
recordRef: await this.worldRecordRef(viewerUserId, recordUid
|
|
9885
|
+
recordRef: await this.worldRecordRef(viewerUserId, recordUid, {
|
|
9886
|
+
ownerUserId,
|
|
9887
|
+
authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
|
|
9888
|
+
...(() => {
|
|
9889
|
+
const textPreview = optionalTrimmedText(headline || textContent, 80);
|
|
9890
|
+
return textPreview === void 0 ? {} : { textPreview };
|
|
9891
|
+
})()
|
|
9892
|
+
}),
|
|
8699
9893
|
authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
|
|
8700
9894
|
...avatarRef === void 0 ? {} : { avatarRef },
|
|
8701
9895
|
...avatarRef === void 0 && avatarFallback !== void 0 ? { avatarFallback } : {},
|
|
@@ -8765,7 +9959,14 @@ var WorldService = class {
|
|
|
8765
9959
|
const avatarFallback = worldPhoneDefaultAvatar(rawAvatar);
|
|
8766
9960
|
const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl) : void 0;
|
|
8767
9961
|
return {
|
|
8768
|
-
interactionRef: await this.worldRecordRef(viewerUserId, recordUid
|
|
9962
|
+
interactionRef: await this.worldRecordRef(viewerUserId, recordUid, {
|
|
9963
|
+
ownerUserId,
|
|
9964
|
+
authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
|
|
9965
|
+
...(() => {
|
|
9966
|
+
const textPreview = optionalTrimmedText(textContent, 80);
|
|
9967
|
+
return textPreview === void 0 ? {} : { textPreview };
|
|
9968
|
+
})()
|
|
9969
|
+
}),
|
|
8769
9970
|
parentRef: await this.worldRecordRef(viewerUserId, parentRecordUid),
|
|
8770
9971
|
authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
|
|
8771
9972
|
...avatarRef === void 0 ? {} : { avatarRef },
|
|
@@ -8778,13 +9979,20 @@ var WorldService = class {
|
|
|
8778
9979
|
voiceCount
|
|
8779
9980
|
};
|
|
8780
9981
|
}
|
|
8781
|
-
async worldRecordRef(viewerUserId, recordUid) {
|
|
9982
|
+
async worldRecordRef(viewerUserId, recordUid, metadata = {}) {
|
|
8782
9983
|
const recordRef = `arkme-world-record-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`world-record-v1:${String(viewerUserId)}:${recordUid}`).digest("base64url")}`;
|
|
8783
9984
|
const now = Date.now();
|
|
8784
9985
|
this.pruneWorldRecordRefs(now);
|
|
9986
|
+
const previous = this.worldRecordRefs.get(recordRef);
|
|
9987
|
+
const resolvedOwnerUserId = metadata.ownerUserId ?? previous?.ownerUserId;
|
|
9988
|
+
const authorName = optionalTrimmedText(metadata.authorName, 64) ?? previous?.authorName;
|
|
9989
|
+
const textPreview = optionalTrimmedText(metadata.textPreview, 80) ?? previous?.textPreview;
|
|
8785
9990
|
this.worldRecordRefs.set(recordRef, {
|
|
8786
9991
|
viewerUserId,
|
|
8787
9992
|
recordUid,
|
|
9993
|
+
...resolvedOwnerUserId === void 0 || resolvedOwnerUserId <= 0 ? {} : { ownerUserId: resolvedOwnerUserId },
|
|
9994
|
+
...authorName === void 0 ? {} : { authorName },
|
|
9995
|
+
...textPreview === void 0 ? {} : { textPreview },
|
|
8788
9996
|
expiresAtMillis: now + ARKME_WORLD_RECORD_REF_TTL_MILLIS
|
|
8789
9997
|
});
|
|
8790
9998
|
return recordRef;
|
|
@@ -8866,8 +10074,18 @@ var WorldService = class {
|
|
|
8866
10074
|
//#endregion
|
|
8867
10075
|
//#region src/types.ts
|
|
8868
10076
|
const ARKME_PROVIDER_CONTRACT_VERSION = 1;
|
|
10077
|
+
const ARKME_DEFAULT_SHARE_WEBSITE = "https://app.arkme.ai";
|
|
8869
10078
|
//#endregion
|
|
8870
10079
|
//#region src/arkme-service.ts
|
|
10080
|
+
function worldVoiceprintInviteMessage(input) {
|
|
10081
|
+
return `${input.textPreview === void 0 ? "我想邀请你开启声纹。" : `我看到你在世界里发的「${input.textPreview}」,想邀请你开启声纹。`}\n以后看到你的世界动态时,可以直接听到你的声音。\n${input.inviteUrl}`;
|
|
10082
|
+
}
|
|
10083
|
+
function voiceprintInviteRateLimitMessage(error) {
|
|
10084
|
+
if (!(error instanceof ArkmePluginError)) return void 0;
|
|
10085
|
+
if (error.upstreamStatus !== 429 && error.httpStatus !== 429 && !/\bHTTP\s*429\b/.test(error.message)) return void 0;
|
|
10086
|
+
const seconds = error.retryAfterMillis === void 0 ? 0 : Math.ceil(error.retryAfterMillis / 1e3);
|
|
10087
|
+
return seconds > 0 ? `提醒发送太频繁了,请 ${String(seconds)} 秒后再试。` : "提醒发送太频繁了,稍后再试。";
|
|
10088
|
+
}
|
|
8871
10089
|
var ArkmeService = class {
|
|
8872
10090
|
config;
|
|
8873
10091
|
sessionStore;
|
|
@@ -8877,6 +10095,7 @@ var ArkmeService = class {
|
|
|
8877
10095
|
runtime;
|
|
8878
10096
|
aiVideo;
|
|
8879
10097
|
arrangement;
|
|
10098
|
+
calendar;
|
|
8880
10099
|
wechat;
|
|
8881
10100
|
recording;
|
|
8882
10101
|
profile;
|
|
@@ -8897,6 +10116,7 @@ var ArkmeService = class {
|
|
|
8897
10116
|
interwoven;
|
|
8898
10117
|
aiPolish;
|
|
8899
10118
|
chat;
|
|
10119
|
+
contact;
|
|
8900
10120
|
constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker()) {
|
|
8901
10121
|
this.config = config;
|
|
8902
10122
|
this.sessionStore = sessionStore;
|
|
@@ -8906,6 +10126,7 @@ var ArkmeService = class {
|
|
|
8906
10126
|
this.runtime = new ServiceRuntime(config, sessionStore, stateStore, fetchImpl, pendingSessionStore);
|
|
8907
10127
|
this.aiVideo = new AiVideoService(this.runtime);
|
|
8908
10128
|
this.arrangement = new ArrangementService(this.runtime);
|
|
10129
|
+
this.calendar = new CalendarService(this.runtime);
|
|
8909
10130
|
this.wechat = new WechatService(this.runtime);
|
|
8910
10131
|
this.recording = new RecordingService(this.runtime);
|
|
8911
10132
|
this.profile = new ProfileService(this.runtime);
|
|
@@ -8918,18 +10139,21 @@ var ArkmeService = class {
|
|
|
8918
10139
|
recordItem: (raw) => this.recordItem(raw)
|
|
8919
10140
|
});
|
|
8920
10141
|
this.record = new RecordService(this.runtime, this.media, this.source);
|
|
8921
|
-
this.search = new SearchService(this.runtime, this.record);
|
|
10142
|
+
this.search = new SearchService(this.runtime, this.record, this.media);
|
|
8922
10143
|
this.bot = new BotService(this.runtime, this.source);
|
|
8923
10144
|
this.outgoingCall = new OutgoingCallService(this.runtime, this.source, this.profile, outgoingCallBroker);
|
|
8924
10145
|
this.world = new WorldService(this.runtime, { refreshProfile: async () => await this.refreshProfile() }, this.media, this.record);
|
|
8925
10146
|
this.arko = new ArkoService(this.runtime, this.profile);
|
|
8926
|
-
this.group = new GroupService(this.runtime, this.source, this.profile)
|
|
10147
|
+
this.group = new GroupService(this.runtime, this.source, this.profile, { sendPrivateText: async (sourceRef, chatSessionUid, text, recordUid, relationUid, session, signal) => {
|
|
10148
|
+
await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, text, recordUid, relationUid, session, void 0, void 0, signal);
|
|
10149
|
+
} });
|
|
8927
10150
|
this.relatedRecording = new RelatedRecordingService(this.runtime, this.source);
|
|
8928
10151
|
this.community = new CommunityService(this.runtime, this.source, this.profile);
|
|
8929
10152
|
this.interwoven = new InterwovenService(this.runtime, this.source, this.profile);
|
|
8930
10153
|
this.aiPolish = new GroupAiPolishService(this.runtime, this.source, { sendChatSourceTextRaw: async (...args) => await this.chat.sendChatSourceTextRaw(...args) });
|
|
8931
10154
|
this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session) => await this.chat.chatTimelineItems(data, session) });
|
|
8932
10155
|
this.chat = new ChatService(this.runtime, this.source, this.profile, this.media, this.record, this.bot, this.arko, this.aiPolish, this.realtime);
|
|
10156
|
+
this.contact = new ContactService(this.runtime, this.source, this.profile, this.realtime);
|
|
8933
10157
|
this.auth = new AuthService(this.runtime, this.profile, {
|
|
8934
10158
|
reconnectChatRealtime: () => {
|
|
8935
10159
|
this.realtime.reconnect();
|
|
@@ -8947,6 +10171,7 @@ var ArkmeService = class {
|
|
|
8947
10171
|
this.interwoven.dispose();
|
|
8948
10172
|
this.world.dispose();
|
|
8949
10173
|
this.arrangement.dispose();
|
|
10174
|
+
this.contact.dispose();
|
|
8950
10175
|
}
|
|
8951
10176
|
startChatRealtime() {
|
|
8952
10177
|
return this.realtime.startChatRealtime();
|
|
@@ -8964,7 +10189,10 @@ var ArkmeService = class {
|
|
|
8964
10189
|
this.realtime.handleChatRealtimeNotice(notice);
|
|
8965
10190
|
}
|
|
8966
10191
|
async refreshChatSessionProjectionBatch(pending) {
|
|
8967
|
-
return await this.realtime.refreshChatSessionProjectionBatch(pending)
|
|
10192
|
+
return (await this.realtime.refreshChatSessionProjectionBatch(pending.map(([uid, latestSequence]) => [uid, {
|
|
10193
|
+
latestSequence,
|
|
10194
|
+
notificationHints: []
|
|
10195
|
+
}]))).map(([uid, projection]) => [uid, projection.latestSequence]);
|
|
8968
10196
|
}
|
|
8969
10197
|
attachOpenClawProvisioner(provisioner) {
|
|
8970
10198
|
this.bot.attachOpenClawProvisioner(provisioner);
|
|
@@ -8978,6 +10206,9 @@ var ArkmeService = class {
|
|
|
8978
10206
|
async createBot(input, options = {}) {
|
|
8979
10207
|
return await this.bot.createBot(input, options);
|
|
8980
10208
|
}
|
|
10209
|
+
async createBotSummary(input, options = {}) {
|
|
10210
|
+
return await this.bot.createBotSummary(input, options);
|
|
10211
|
+
}
|
|
8981
10212
|
async revealBotSecret(botRef, options = {}) {
|
|
8982
10213
|
return await this.bot.revealBotSecret(botRef, options);
|
|
8983
10214
|
}
|
|
@@ -9004,7 +10235,8 @@ var ArkmeService = class {
|
|
|
9004
10235
|
captchaId: this.config.geetestCaptchaId,
|
|
9005
10236
|
environment: this.config.environment,
|
|
9006
10237
|
testLoginEnabled: this.config.environment === "test",
|
|
9007
|
-
callAssetBasePath: `${this.config.routePath}/call
|
|
10238
|
+
callAssetBasePath: `${this.config.routePath}/call`,
|
|
10239
|
+
shareWebsite: this.config.shareWebsite ?? "https://app.arkme.ai"
|
|
9008
10240
|
};
|
|
9009
10241
|
}
|
|
9010
10242
|
providerCapabilities() {
|
|
@@ -9023,6 +10255,8 @@ var ArkmeService = class {
|
|
|
9023
10255
|
revisionPolling: true,
|
|
9024
10256
|
userProfile: true,
|
|
9025
10257
|
imageRead: true,
|
|
10258
|
+
recordCalendar: true,
|
|
10259
|
+
imageLibrary: true,
|
|
9026
10260
|
sourceDirectory: true,
|
|
9027
10261
|
sourceTimeline: true,
|
|
9028
10262
|
sourceTextSend: true,
|
|
@@ -9031,8 +10265,11 @@ var ArkmeService = class {
|
|
|
9031
10265
|
fileUpload: this.config.richMediaSendEnabled !== false,
|
|
9032
10266
|
outgoingCall: true,
|
|
9033
10267
|
groupMembers: true,
|
|
10268
|
+
groupMemberAdd: true,
|
|
9034
10269
|
userCard: true,
|
|
9035
10270
|
openPrivateChat: true,
|
|
10271
|
+
contactAdd: true,
|
|
10272
|
+
conversationQuickAdd: true,
|
|
9036
10273
|
groupSettings: true,
|
|
9037
10274
|
extensionManagement: true,
|
|
9038
10275
|
extensionMetadataEdit: true,
|
|
@@ -9040,9 +10277,13 @@ var ArkmeService = class {
|
|
|
9040
10277
|
extensionPreviews: true,
|
|
9041
10278
|
worldFeed: true,
|
|
9042
10279
|
worldInteractions: true,
|
|
10280
|
+
worldVoiceprintPlayback: true,
|
|
10281
|
+
worldVoiceprintInvite: true,
|
|
9043
10282
|
arrangements: true,
|
|
9044
10283
|
myExtensions: true,
|
|
9045
10284
|
extensionPublish: true,
|
|
10285
|
+
recordingTranscripts: true,
|
|
10286
|
+
recordingDoubaoBackfill: true,
|
|
9046
10287
|
extensionReviews: true,
|
|
9047
10288
|
...this.relatedRecording.isEnabled() ? { relatedRecordings: true } : {}
|
|
9048
10289
|
},
|
|
@@ -9088,6 +10329,7 @@ var ArkmeService = class {
|
|
|
9088
10329
|
return await this.outgoingCall.releaseOutgoingCall(callRequestId);
|
|
9089
10330
|
}
|
|
9090
10331
|
dispose() {
|
|
10332
|
+
this.contact.dispose();
|
|
9091
10333
|
this.realtime.dispose();
|
|
9092
10334
|
this.arko.dispose();
|
|
9093
10335
|
this.auth.dispose();
|
|
@@ -9108,6 +10350,12 @@ var ArkmeService = class {
|
|
|
9108
10350
|
async cachedProfile() {
|
|
9109
10351
|
return await this.profile.cachedProfile();
|
|
9110
10352
|
}
|
|
10353
|
+
async searchContact(identifier, options = {}) {
|
|
10354
|
+
return await this.contact.search(identifier, options);
|
|
10355
|
+
}
|
|
10356
|
+
async addContact(contactRef, options = {}) {
|
|
10357
|
+
return await this.contact.add(contactRef, options);
|
|
10358
|
+
}
|
|
9111
10359
|
async extensionAuthors(userIds, signal) {
|
|
9112
10360
|
return await this.extensionReview.extensionAuthors(userIds, signal);
|
|
9113
10361
|
}
|
|
@@ -9122,8 +10370,12 @@ var ArkmeService = class {
|
|
|
9122
10370
|
return await this.recording.recordingCalendar(fromStamp, toStamp, signal);
|
|
9123
10371
|
}
|
|
9124
10372
|
/** Read-only Audio capability shared by the built-in UI and Arkme recording tools. */
|
|
9125
|
-
async recordingTranscript(dateStamp, signal) {
|
|
9126
|
-
return await this.recording.recordingTranscript(dateStamp, signal);
|
|
10373
|
+
async recordingTranscript(dateStamp, signal, source = "system") {
|
|
10374
|
+
return await this.recording.recordingTranscript(dateStamp, signal, source);
|
|
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);
|
|
9127
10379
|
}
|
|
9128
10380
|
/** Read-only Audio capability shared by the built-in UI and Arkme recording tools. */
|
|
9129
10381
|
async recordingProjection(dateStamp, kind, signal) {
|
|
@@ -9246,6 +10498,18 @@ var ArkmeService = class {
|
|
|
9246
10498
|
async listGroupMembers(sourceRef, options = {}) {
|
|
9247
10499
|
return await this.group.listGroupMembers(sourceRef, options);
|
|
9248
10500
|
}
|
|
10501
|
+
async listGroupMemberCandidates(sourceRef, options = {}) {
|
|
10502
|
+
return await this.group.listGroupMemberCandidates(sourceRef, options);
|
|
10503
|
+
}
|
|
10504
|
+
async groupInvitePreview(sourceRef, signal) {
|
|
10505
|
+
return await this.group.groupInvitePreview(sourceRef, signal);
|
|
10506
|
+
}
|
|
10507
|
+
async addGroupMembers(sourceRef, candidateRefs, signal) {
|
|
10508
|
+
return await this.group.addGroupMembers(sourceRef, candidateRefs, signal);
|
|
10509
|
+
}
|
|
10510
|
+
async createGroup(title, clientMutationId, options = {}) {
|
|
10511
|
+
return await this.group.createGroup(title, clientMutationId, options);
|
|
10512
|
+
}
|
|
9249
10513
|
async groupSettings(sourceRef, signal) {
|
|
9250
10514
|
return await this.group.groupSettings(sourceRef, signal);
|
|
9251
10515
|
}
|
|
@@ -9394,6 +10658,13 @@ var ArkmeService = class {
|
|
|
9394
10658
|
async searchScene(options) {
|
|
9395
10659
|
return await this.search.searchScene(options);
|
|
9396
10660
|
}
|
|
10661
|
+
/**
|
|
10662
|
+
* Build the desktop image library from the owner's mixed image/video scene.
|
|
10663
|
+
* Signed storage URLs stay inside the Provider and are replaced by account-bound media refs.
|
|
10664
|
+
*/
|
|
10665
|
+
async searchImages(options) {
|
|
10666
|
+
return await this.search.searchImages(options);
|
|
10667
|
+
}
|
|
9397
10668
|
async searchRecordings(options) {
|
|
9398
10669
|
return await this.search.searchRecordings(options);
|
|
9399
10670
|
}
|
|
@@ -9406,6 +10677,12 @@ var ArkmeService = class {
|
|
|
9406
10677
|
async list(limit, cursor) {
|
|
9407
10678
|
return await this.record.list(limit, cursor);
|
|
9408
10679
|
}
|
|
10680
|
+
async calendarBuckets(options) {
|
|
10681
|
+
return await this.calendar.bucketPage(options);
|
|
10682
|
+
}
|
|
10683
|
+
async calendarRecords(options) {
|
|
10684
|
+
return await this.calendar.dayRecords(options);
|
|
10685
|
+
}
|
|
9409
10686
|
async listWorldRecords(options = {}) {
|
|
9410
10687
|
return await this.world.listWorldRecords(options);
|
|
9411
10688
|
}
|
|
@@ -9445,6 +10722,41 @@ var ArkmeService = class {
|
|
|
9445
10722
|
async listWorldFeed(options = {}) {
|
|
9446
10723
|
return await this.world.listWorldFeed(options);
|
|
9447
10724
|
}
|
|
10725
|
+
/** Build the signed-in account's World projection without exposing owner IDs. */
|
|
10726
|
+
async listMyWorldFeed(options = {}) {
|
|
10727
|
+
return await this.world.listMyWorldFeed(options);
|
|
10728
|
+
}
|
|
10729
|
+
async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
|
|
10730
|
+
return await this.world.worldVoiceprintPlaybackAvailability(recordRefs, signal);
|
|
10731
|
+
}
|
|
10732
|
+
async generateWorldVoiceprintPlayback(input) {
|
|
10733
|
+
return await this.world.generateWorldVoiceprintPlayback(input);
|
|
10734
|
+
}
|
|
10735
|
+
async inviteWorldVoiceprint(recordRef, signal) {
|
|
10736
|
+
try {
|
|
10737
|
+
const intent = await this.world.createWorldVoiceprintInviteIntent(recordRef, signal);
|
|
10738
|
+
const privateChat = await this.chat.openPrivateChatFromUser(intent.peerUserId, {
|
|
10739
|
+
displayName: intent.peerDisplayName,
|
|
10740
|
+
...signal === void 0 ? {} : { signal }
|
|
10741
|
+
});
|
|
10742
|
+
const sent = await this.chat.sendSourceText(privateChat.source.sourceRef, worldVoiceprintInviteMessage(intent), signal === void 0 ? {} : { signal });
|
|
10743
|
+
if (sent.localState !== "synced") throw new ArkmePluginError("world-voiceprint-invite-send-pending", sent.error ?? "声纹邀请已进入发送队列,请稍后在私聊中确认", true);
|
|
10744
|
+
return {
|
|
10745
|
+
sent: true,
|
|
10746
|
+
peerDisplayName: intent.peerDisplayName,
|
|
10747
|
+
messageItemUid: sent.itemUid,
|
|
10748
|
+
expiresAtMillis: intent.expiresAtMillis
|
|
10749
|
+
};
|
|
10750
|
+
} catch (error) {
|
|
10751
|
+
const message = voiceprintInviteRateLimitMessage(error);
|
|
10752
|
+
if (message !== void 0) throw new ArkmePluginError("world-voiceprint-invite-rate-limited", message, true, 429, {
|
|
10753
|
+
cause: error,
|
|
10754
|
+
upstreamStatus: 429,
|
|
10755
|
+
...!(error instanceof ArkmePluginError) || error.retryAfterMillis === void 0 ? {} : { retryAfterMillis: error.retryAfterMillis }
|
|
10756
|
+
});
|
|
10757
|
+
throw error;
|
|
10758
|
+
}
|
|
10759
|
+
}
|
|
9448
10760
|
/** Read the authenticated comment/reply tree behind one account-bound World reference. */
|
|
9449
10761
|
async listWorldInteractions(recordRef, options = {}) {
|
|
9450
10762
|
return await this.world.listWorldInteractions(recordRef, options);
|
|
@@ -9495,6 +10807,8 @@ const ARKME_EXTENSION_FORMAT = "arkme-cordis-extension";
|
|
|
9495
10807
|
const ARKME_EXTENSION_MAX_BYTES = 100 * 1024 * 1024;
|
|
9496
10808
|
const ARKME_EXTENSION_ICON_MAX_BYTES = 2 * 1024 * 1024;
|
|
9497
10809
|
const ARKME_EXTENSION_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
10810
|
+
/** Browser/model-safe projection. Host filesystem paths and Dynamic Cordis IDs stay private. */
|
|
10811
|
+
const ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE = "插件运行失败,已自动停用。";
|
|
9498
10812
|
//#endregion
|
|
9499
10813
|
//#region src/extensions/artifact.ts
|
|
9500
10814
|
const TAR_BLOCK_BYTES$1 = 512;
|
|
@@ -9611,6 +10925,7 @@ function canonicalExtensionSignatureMessage(input) {
|
|
|
9611
10925
|
}), "utf8");
|
|
9612
10926
|
}
|
|
9613
10927
|
const ARKME_BUNDLE_ARTIFACT_KIND = "dsh-bundle-tgz";
|
|
10928
|
+
const ARKME_NATIVE_BUNDLE_ARTIFACT_KIND = "dsh-native-package-tgz";
|
|
9614
10929
|
const ARKME_BUNDLE_MAX_BYTES = 100 * 1024 * 1024;
|
|
9615
10930
|
const TAR_BLOCK_BYTES = 512;
|
|
9616
10931
|
const MAX_ARCHIVE_FILES = 1024;
|
|
@@ -9657,9 +10972,22 @@ function writeOctal(header, offset, length, value) {
|
|
|
9657
10972
|
header.write(octal.slice(-(length - 1)), offset, length - 1, "ascii");
|
|
9658
10973
|
header[offset + length - 1] = 0;
|
|
9659
10974
|
}
|
|
10975
|
+
function splitTarPath(path) {
|
|
10976
|
+
const direct = Buffer.from(path, "utf8");
|
|
10977
|
+
if (direct.byteLength <= 100) return { name: direct };
|
|
10978
|
+
const separators = [...path.matchAll(/\//g)].map((match) => match.index).reverse();
|
|
10979
|
+
for (const separator of separators) {
|
|
10980
|
+
const prefix = Buffer.from(path.slice(0, separator), "utf8");
|
|
10981
|
+
const name = Buffer.from(path.slice(separator + 1), "utf8");
|
|
10982
|
+
if (prefix.byteLength <= 155 && name.byteLength <= 100) return {
|
|
10983
|
+
name,
|
|
10984
|
+
prefix
|
|
10985
|
+
};
|
|
10986
|
+
}
|
|
10987
|
+
throw new ArkmeBundleArtifactError("bundle-path-too-long", `Bundle 路径过长:${path}`);
|
|
10988
|
+
}
|
|
9660
10989
|
function tarHeader(path, size) {
|
|
9661
|
-
const name =
|
|
9662
|
-
if (name.byteLength > 100) throw new ArkmeBundleArtifactError("bundle-path-too-long", `Bundle 路径过长:${path}`);
|
|
10990
|
+
const { name, prefix } = splitTarPath(path);
|
|
9663
10991
|
const header = Buffer.alloc(TAR_BLOCK_BYTES);
|
|
9664
10992
|
name.copy(header, 0);
|
|
9665
10993
|
writeOctal(header, 100, 8, 420);
|
|
@@ -9671,6 +10999,7 @@ function tarHeader(path, size) {
|
|
|
9671
10999
|
header[156] = "0".charCodeAt(0);
|
|
9672
11000
|
header.write("ustar\0", 257, 6, "ascii");
|
|
9673
11001
|
header.write("00", 263, 2, "ascii");
|
|
11002
|
+
prefix?.copy(header, 345);
|
|
9674
11003
|
writeOctal(header, 148, 8, [...header].reduce((sum, byte) => sum + byte, 0));
|
|
9675
11004
|
return header;
|
|
9676
11005
|
}
|
|
@@ -9705,7 +11034,9 @@ function readTar(tar) {
|
|
|
9705
11034
|
while (offset + TAR_BLOCK_BYTES <= tar.byteLength) {
|
|
9706
11035
|
const header = tar.subarray(offset, offset + TAR_BLOCK_BYTES);
|
|
9707
11036
|
if (header.every((byte) => byte === 0)) break;
|
|
9708
|
-
const
|
|
11037
|
+
const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
|
|
11038
|
+
const prefix = header.subarray(345, 500).toString("utf8").replace(/\0.*$/, "");
|
|
11039
|
+
const path = prefix === "" ? name : `${prefix}/${name}`;
|
|
9709
11040
|
validateArchivePath(path);
|
|
9710
11041
|
if (files.has(path)) throw new ArkmeBundleArtifactError("bundle-duplicate-path", `Bundle 包含重复路径:${path}`);
|
|
9711
11042
|
const type = header[156];
|
|
@@ -9790,6 +11121,105 @@ function validatePatch(raw, packageName, executionModel) {
|
|
|
9790
11121
|
if (ids.size === 0) throw new ArkmeBundleArtifactError("bundle-patch-empty", "Bundle patch 至少需要一条 row");
|
|
9791
11122
|
return [...ids].sort();
|
|
9792
11123
|
}
|
|
11124
|
+
const NATIVE_LIFECYCLE_SCRIPTS = /* @__PURE__ */ new Set([
|
|
11125
|
+
"preinstall",
|
|
11126
|
+
"install",
|
|
11127
|
+
"postinstall",
|
|
11128
|
+
"prepare",
|
|
11129
|
+
"prepublish",
|
|
11130
|
+
"prepublishOnly",
|
|
11131
|
+
"prepack",
|
|
11132
|
+
"postpack"
|
|
11133
|
+
]);
|
|
11134
|
+
function nonEmptyManifestValue(value) {
|
|
11135
|
+
if (value === void 0 || value === null) return false;
|
|
11136
|
+
if (typeof value === "string") return value.trim() !== "";
|
|
11137
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
11138
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
11139
|
+
if (typeof value === "boolean") return value;
|
|
11140
|
+
return true;
|
|
11141
|
+
}
|
|
11142
|
+
function inspectNativePatch(raw, packageName) {
|
|
11143
|
+
const document = parseDocument(raw.toString("utf8"), { uniqueKeys: true });
|
|
11144
|
+
if (document.errors.length > 0) throw new ArkmeBundleArtifactError("bundle-patch-invalid", "Bundle patch YAML 格式无效");
|
|
11145
|
+
const value = document.toJS();
|
|
11146
|
+
if (!Array.isArray(value)) throw new ArkmeBundleArtifactError("bundle-patch-invalid", "Bundle patch 必须是操作数组");
|
|
11147
|
+
const patchIds = /* @__PURE__ */ new Set();
|
|
11148
|
+
const capabilities = /* @__PURE__ */ new Set();
|
|
11149
|
+
for (const operation of value) {
|
|
11150
|
+
if (operation === null || typeof operation !== "object" || Array.isArray(operation)) {
|
|
11151
|
+
capabilities.add("profile_patch_override");
|
|
11152
|
+
continue;
|
|
11153
|
+
}
|
|
11154
|
+
const record = operation;
|
|
11155
|
+
if (!(Object.keys(record).length === 1 && Array.isArray(record.insert))) {
|
|
11156
|
+
capabilities.add("profile_patch_override");
|
|
11157
|
+
if (typeof record.id === "string" && record.id.trim() !== "") patchIds.add(record.id.trim());
|
|
11158
|
+
continue;
|
|
11159
|
+
}
|
|
11160
|
+
for (const row of record.insert) {
|
|
11161
|
+
if (row === null || typeof row !== "object" || Array.isArray(row)) continue;
|
|
11162
|
+
const item = row;
|
|
11163
|
+
if (typeof item.id === "string" && item.id.trim() !== "") patchIds.add(item.id.trim());
|
|
11164
|
+
if (typeof item.name === "string" && item.name !== packageName && !item.name.startsWith(`${packageName}/`)) capabilities.add("external_package_reference");
|
|
11165
|
+
}
|
|
11166
|
+
}
|
|
11167
|
+
return {
|
|
11168
|
+
patchIds: [...patchIds].sort(),
|
|
11169
|
+
capabilities: [...capabilities].sort()
|
|
11170
|
+
};
|
|
11171
|
+
}
|
|
11172
|
+
function inspectNativeManifest(manifest, files) {
|
|
11173
|
+
if (typeof manifest.name !== "string" || manifest.name.length > 214 || !PACKAGE_NAME.test(manifest.name) || manifest.name.startsWith("@deepseek-ai/") || manifest.name === "@senguoyun/dsh-arkme" || manifest.name.startsWith("@arkme-local/")) throw new ArkmeBundleArtifactError("bundle-package-name-invalid", "Bundle package name 无效或属于保留集合");
|
|
11174
|
+
if (typeof manifest.version !== "string" || !SEMVER.test(manifest.version)) throw new ArkmeBundleArtifactError("bundle-version-invalid", "Bundle version 必须是严格 SemVer");
|
|
11175
|
+
if (manifest.dsh?.arkme !== void 0 && manifest.dsh.arkme !== null) throw new ArkmeBundleArtifactError("bundle-execution-model-invalid", "原生 DSH V3 Bundle 不允许声明 Arkme sandbox marker");
|
|
11176
|
+
const patch = manifest.dsh?.bundle?.patch;
|
|
11177
|
+
if (typeof patch !== "string" || patch.trim() === "" || patch.includes("\\") || patch.startsWith("/")) throw new ArkmeBundleArtifactError("bundle-patch-missing", "Bundle 必须声明包内 dsh.bundle.patch");
|
|
11178
|
+
const patchPath = `package/${posix.normalize(patch.replace(/^\.\//, ""))}`;
|
|
11179
|
+
if (!files.has(patchPath) || patchPath.includes("/../")) throw new ArkmeBundleArtifactError("bundle-patch-missing", "Bundle patch 不在包内");
|
|
11180
|
+
const capabilities = /* @__PURE__ */ new Set();
|
|
11181
|
+
if (nonEmptyManifestValue(manifest.dependencies)) capabilities.add("runtime_dependencies");
|
|
11182
|
+
if (nonEmptyManifestValue(manifest.optionalDependencies)) capabilities.add("optional_dependencies");
|
|
11183
|
+
if (nonEmptyManifestValue(manifest.bundledDependencies) || nonEmptyManifestValue(manifest.bundleDependencies)) capabilities.add("bundled_dependencies");
|
|
11184
|
+
for (const name of Object.keys(manifest.peerDependencies ?? {})) if (!name.startsWith("@deepseek-ai/") && name !== "@senguoyun/dsh-arkme" && name !== "react" && name !== "react-dom") {
|
|
11185
|
+
capabilities.add("peer_dependencies");
|
|
11186
|
+
break;
|
|
11187
|
+
}
|
|
11188
|
+
if (Object.entries(manifest.scripts ?? {}).some(([name, value]) => NATIVE_LIFECYCLE_SCRIPTS.has(name) && nonEmptyManifestValue(value))) capabilities.add("lifecycle_scripts");
|
|
11189
|
+
if (nonEmptyManifestValue(manifest.bin)) capabilities.add("bin");
|
|
11190
|
+
if ([...files.keys()].some((name) => name.toLowerCase().endsWith(".node"))) capabilities.add("native_addon");
|
|
11191
|
+
return {
|
|
11192
|
+
patchPath,
|
|
11193
|
+
capabilities: [...capabilities].sort()
|
|
11194
|
+
};
|
|
11195
|
+
}
|
|
11196
|
+
function inspectNativeBundleArtifactV3(bytes) {
|
|
11197
|
+
assertArchiveSize(bytes.byteLength, "Bundle");
|
|
11198
|
+
let tar;
|
|
11199
|
+
try {
|
|
11200
|
+
tar = gunzipSync(bytes, { maxOutputLength: 105907200 });
|
|
11201
|
+
} catch {
|
|
11202
|
+
throw new ArkmeBundleArtifactError("bundle-gzip-invalid", "Bundle 不是有效 gzip");
|
|
11203
|
+
}
|
|
11204
|
+
const files = readTar(tar);
|
|
11205
|
+
const packageJSON = files.get("package/package.json");
|
|
11206
|
+
if (packageJSON === void 0) throw new ArkmeBundleArtifactError("bundle-package-json-missing", "Bundle 缺少 package.json");
|
|
11207
|
+
const manifest = readManifest(packageJSON);
|
|
11208
|
+
const inspected = inspectNativeManifest(manifest, files);
|
|
11209
|
+
const patch = inspectNativePatch(files.get(inspected.patchPath), manifest.name);
|
|
11210
|
+
const nativeCapabilities = [.../* @__PURE__ */ new Set([...inspected.capabilities, ...patch.capabilities])].sort();
|
|
11211
|
+
return {
|
|
11212
|
+
bytes: Buffer.from(bytes),
|
|
11213
|
+
bundleSha256: bundleSha256(bytes),
|
|
11214
|
+
packageJsonSha256: bundleSha256(packageJSON),
|
|
11215
|
+
packageName: manifest.name,
|
|
11216
|
+
version: manifest.version,
|
|
11217
|
+
executionModel: "dsh-native",
|
|
11218
|
+
nativeCapabilities,
|
|
11219
|
+
files,
|
|
11220
|
+
patchIds: patch.patchIds
|
|
11221
|
+
};
|
|
11222
|
+
}
|
|
9793
11223
|
function inspectBundleArtifact(bytes) {
|
|
9794
11224
|
assertArchiveSize(bytes.byteLength, "Bundle");
|
|
9795
11225
|
let tar;
|
|
@@ -9820,29 +11250,114 @@ function pathInside(root, target) {
|
|
|
9820
11250
|
const fromRoot = relative(root, target);
|
|
9821
11251
|
return fromRoot === "" || !fromRoot.startsWith(`..${sep}`) && fromRoot !== ".." && !isAbsolute(fromRoot);
|
|
9822
11252
|
}
|
|
9823
|
-
function
|
|
11253
|
+
function globPattern(pattern) {
|
|
11254
|
+
let expression = "^";
|
|
11255
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
11256
|
+
const character = pattern[index];
|
|
11257
|
+
if (character === "*") if (pattern[index + 1] === "*") {
|
|
11258
|
+
index += 1;
|
|
11259
|
+
if (pattern[index + 1] === "/") {
|
|
11260
|
+
index += 1;
|
|
11261
|
+
expression += "(?:.*/)?";
|
|
11262
|
+
} else expression += ".*";
|
|
11263
|
+
} else expression += "[^/]*";
|
|
11264
|
+
else if (character === "?") expression += "[^/]";
|
|
11265
|
+
else expression += character.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
11266
|
+
}
|
|
11267
|
+
return new RegExp(`${expression}$`);
|
|
11268
|
+
}
|
|
11269
|
+
function collectPackageFiles(root, manifest, nativeV3 = false) {
|
|
9824
11270
|
const canonicalRoot = realpathSync(root);
|
|
9825
11271
|
const selected = /* @__PURE__ */ new Map();
|
|
9826
|
-
const
|
|
11272
|
+
const ignoredNativeRootEntries = /* @__PURE__ */ new Set([
|
|
11273
|
+
".git",
|
|
11274
|
+
".hg",
|
|
11275
|
+
".svn",
|
|
11276
|
+
"node_modules"
|
|
11277
|
+
]);
|
|
11278
|
+
const ignoredNativeFiles = /* @__PURE__ */ new Set([
|
|
11279
|
+
".npmrc",
|
|
11280
|
+
".yarnrc",
|
|
11281
|
+
".yarnrc.yml",
|
|
11282
|
+
".pnpmfile.cjs"
|
|
11283
|
+
]);
|
|
11284
|
+
const add = (absolute, allowIgnore = false) => {
|
|
9827
11285
|
const stat = lstatSync(absolute);
|
|
9828
|
-
|
|
11286
|
+
const rel = relative(canonicalRoot, absolute).split(sep).join("/");
|
|
11287
|
+
const first = rel.split("/")[0] ?? "";
|
|
11288
|
+
const basename = rel.split("/").at(-1) ?? "";
|
|
11289
|
+
if (allowIgnore && (ignoredNativeRootEntries.has(first) || ignoredNativeFiles.has(basename) || basename === ".env" || basename.startsWith(".env."))) return;
|
|
11290
|
+
if (stat.isSymbolicLink()) {
|
|
11291
|
+
if (allowIgnore) return;
|
|
11292
|
+
throw new ArkmeBundleArtifactError("bundle-link-forbidden", "Bundle source 不允许符号链接");
|
|
11293
|
+
}
|
|
9829
11294
|
const canonical = realpathSync(absolute);
|
|
9830
11295
|
if (!pathInside(canonicalRoot, canonical)) throw new ArkmeBundleArtifactError("bundle-path-invalid", "Bundle source 逃逸 package root");
|
|
9831
11296
|
if (stat.isDirectory()) {
|
|
9832
|
-
for (const child of readdirSync(canonical).sort()) add(join(canonical, child));
|
|
11297
|
+
for (const child of readdirSync(canonical).sort()) add(join(canonical, child), allowIgnore);
|
|
9833
11298
|
return;
|
|
9834
11299
|
}
|
|
9835
11300
|
if (!stat.isFile()) throw new ArkmeBundleArtifactError("bundle-file-type-forbidden", "Bundle source 只允许普通文件");
|
|
9836
|
-
const
|
|
9837
|
-
selected.set(`package/${
|
|
11301
|
+
const canonicalRel = relative(canonicalRoot, canonical).split(sep).join("/");
|
|
11302
|
+
selected.set(`package/${canonicalRel}`, readFileSync(canonical));
|
|
11303
|
+
};
|
|
11304
|
+
const addNativePackageExtras = () => {
|
|
11305
|
+
if (!nativeV3) return;
|
|
11306
|
+
for (const child of readdirSync(canonicalRoot).sort()) if (/^(?:readme|licen[cs]e|copying|notice)(?:\.|$)/i.test(child)) add(join(canonicalRoot, child), true);
|
|
11307
|
+
const paths = /* @__PURE__ */ new Set();
|
|
11308
|
+
if (typeof manifest.main === "string") paths.add(manifest.main);
|
|
11309
|
+
const bin = manifest.bin;
|
|
11310
|
+
if (typeof bin === "string") paths.add(bin);
|
|
11311
|
+
else if (bin !== null && typeof bin === "object" && !Array.isArray(bin)) {
|
|
11312
|
+
for (const value of Object.values(bin)) if (typeof value === "string") paths.add(value);
|
|
11313
|
+
}
|
|
11314
|
+
const patch = manifest.dsh?.bundle?.patch;
|
|
11315
|
+
if (typeof patch === "string") paths.add(patch);
|
|
11316
|
+
for (const entry of paths) {
|
|
11317
|
+
const target = resolve(canonicalRoot, entry);
|
|
11318
|
+
if (pathInside(canonicalRoot, target) && existsSync(target)) add(target, true);
|
|
11319
|
+
}
|
|
9838
11320
|
};
|
|
9839
11321
|
add(join(canonicalRoot, "package.json"));
|
|
9840
|
-
|
|
9841
|
-
|
|
11322
|
+
const entries = manifest.files ?? [];
|
|
11323
|
+
if (entries.length === 0) {
|
|
11324
|
+
if (!nativeV3) throw new ArkmeBundleArtifactError("bundle-files-missing", "Bundle 必须显式声明 files");
|
|
11325
|
+
for (const child of readdirSync(canonicalRoot).sort()) if (child !== "package.json") add(join(canonicalRoot, child), true);
|
|
11326
|
+
addNativePackageExtras();
|
|
11327
|
+
return selected;
|
|
11328
|
+
}
|
|
11329
|
+
let candidates;
|
|
11330
|
+
const listCandidates = () => {
|
|
11331
|
+
if (candidates !== void 0) return candidates;
|
|
11332
|
+
candidates = [];
|
|
11333
|
+
const walk = (directory) => {
|
|
11334
|
+
for (const child of readdirSync(directory).sort()) {
|
|
11335
|
+
const absolute = join(directory, child);
|
|
11336
|
+
const stat = lstatSync(absolute);
|
|
11337
|
+
if (stat.isSymbolicLink()) continue;
|
|
11338
|
+
if (stat.isDirectory()) walk(absolute);
|
|
11339
|
+
else if (stat.isFile()) candidates.push(relative(canonicalRoot, absolute).split(sep).join("/"));
|
|
11340
|
+
}
|
|
11341
|
+
};
|
|
11342
|
+
walk(canonicalRoot);
|
|
11343
|
+
return candidates;
|
|
11344
|
+
};
|
|
11345
|
+
for (const entry of entries) {
|
|
11346
|
+
if (entry.includes("\\") || entry.startsWith("/") || entry.split("/").includes("..")) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
|
|
11347
|
+
const hasGlob = entry.includes("*") || entry.includes("?") || entry.includes("[");
|
|
11348
|
+
if (hasGlob && !nativeV3) throw new ArkmeBundleArtifactError("bundle-files-invalid", "Bundle files 暂不接受 glob 或反斜杠");
|
|
11349
|
+
if (hasGlob) {
|
|
11350
|
+
const matcher = globPattern(entry.replace(/^\.\//, ""));
|
|
11351
|
+
const matches = listCandidates().filter((candidate) => matcher.test(candidate));
|
|
11352
|
+
if (matches.length === 0) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
|
|
11353
|
+
for (const match of matches) add(resolve(canonicalRoot, match), true);
|
|
11354
|
+
continue;
|
|
11355
|
+
}
|
|
9842
11356
|
const target = resolve(canonicalRoot, entry);
|
|
9843
|
-
if (!pathInside(canonicalRoot, target) || !statSync(target).isFile() && !statSync(target).isDirectory()) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
|
|
9844
|
-
add(target);
|
|
11357
|
+
if (!pathInside(canonicalRoot, target) || !existsSync(target) || !statSync(target).isFile() && !statSync(target).isDirectory()) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
|
|
11358
|
+
add(target, nativeV3);
|
|
9845
11359
|
}
|
|
11360
|
+
addNativePackageExtras();
|
|
9846
11361
|
return selected;
|
|
9847
11362
|
}
|
|
9848
11363
|
function packBundleFiles(files) {
|
|
@@ -9856,14 +11371,25 @@ function packBundleFiles(files) {
|
|
|
9856
11371
|
}
|
|
9857
11372
|
};
|
|
9858
11373
|
}
|
|
9859
|
-
function
|
|
9860
|
-
|
|
11374
|
+
function packNativeBundleFiles(files) {
|
|
11375
|
+
const bytes = gzipSync(createTar(files), { level: 9 });
|
|
11376
|
+
assertArchiveSize(bytes.byteLength, "Bundle");
|
|
11377
|
+
return {
|
|
11378
|
+
bundle: inspectNativeBundleArtifactV3(bytes),
|
|
11379
|
+
source: {
|
|
11380
|
+
bytes: Buffer.from(bytes),
|
|
11381
|
+
sourceSha256: bundleSha256(bytes)
|
|
11382
|
+
}
|
|
11383
|
+
};
|
|
11384
|
+
}
|
|
11385
|
+
function packLocalNativeBundleDirectoryV3(directory) {
|
|
11386
|
+
return packNativeBundleFiles(collectPackageFiles(directory, readManifest(readFileSync(join(directory, "package.json"))), true));
|
|
9861
11387
|
}
|
|
9862
|
-
function
|
|
11388
|
+
function readNativeBundleTarballV3(path) {
|
|
9863
11389
|
if (!lstatSync(path).isFile()) throw new ArkmeBundleArtifactError("bundle-tarball-invalid", "本地 Bundle tgz 必须是普通文件");
|
|
9864
11390
|
const bytes = readFileSync(path);
|
|
9865
11391
|
return {
|
|
9866
|
-
bundle:
|
|
11392
|
+
bundle: inspectNativeBundleArtifactV3(bytes),
|
|
9867
11393
|
source: {
|
|
9868
11394
|
bytes: Buffer.from(bytes),
|
|
9869
11395
|
sourceSha256: bundleSha256(bytes)
|
|
@@ -9938,6 +11464,39 @@ function verifyBundleResolutionSignature(resolution, trustedKeys) {
|
|
|
9938
11464
|
}
|
|
9939
11465
|
if (!valid) throw new ArkmePluginError("extension-signature-invalid", "扩展 Bundle 平台签名验证失败", false, 403);
|
|
9940
11466
|
}
|
|
11467
|
+
function canonicalNativeBundleSignatureMessage(input) {
|
|
11468
|
+
return Buffer.from(JSON.stringify({
|
|
11469
|
+
format_version: input.artifact_contract_version,
|
|
11470
|
+
artifact_kind: input.artifact_kind,
|
|
11471
|
+
extension_id: input.extension_id,
|
|
11472
|
+
package_name: input.package_name,
|
|
11473
|
+
version: input.version,
|
|
11474
|
+
execution_model: input.execution_model,
|
|
11475
|
+
bundle_sha256: input.bundle_sha256,
|
|
11476
|
+
package_json_sha256: input.package_json_sha256,
|
|
11477
|
+
source_sha256: input.source_sha256,
|
|
11478
|
+
...input.native_capabilities.length === 0 ? {} : { native_capabilities: input.native_capabilities },
|
|
11479
|
+
published_at: input.published_at,
|
|
11480
|
+
signing_key_id: input.signing_key_id
|
|
11481
|
+
}), "utf8");
|
|
11482
|
+
}
|
|
11483
|
+
function verifyNativeBundleResolutionSignature(resolution, trustedKeys) {
|
|
11484
|
+
const capabilities = resolution.native_capabilities;
|
|
11485
|
+
if (resolution.artifact_contract_version !== 3 || resolution.artifact_kind !== "dsh-native-package-tgz" || resolution.package_name.trim() === "" || resolution.execution_model !== "dsh-native" || ![
|
|
11486
|
+
resolution.bundle_sha256,
|
|
11487
|
+
resolution.package_json_sha256,
|
|
11488
|
+
resolution.source_sha256
|
|
11489
|
+
].every((value) => /^[a-f0-9]{64}$/.test(value)) || !Array.isArray(capabilities) || capabilities.some((value) => typeof value !== "string" || value.trim() === "") || capabilities.some((value, index) => index > 0 && capabilities[index - 1] >= value) || !Number.isSafeInteger(resolution.published_at) || resolution.published_at <= 0 || resolution.signature.trim() === "") throw new ArkmePluginError("extension-signature-envelope-invalid", "原生 DSH V3 签名 envelope 字段无效", false, 502);
|
|
11490
|
+
const key = trustedKeys.get(resolution.signing_key_id);
|
|
11491
|
+
if (key === void 0) throw new ArkmePluginError("extension-signing-key-untrusted", `不信任扩展签名密钥 ${resolution.signing_key_id}`, false, 403);
|
|
11492
|
+
let valid = false;
|
|
11493
|
+
try {
|
|
11494
|
+
valid = verify(null, canonicalNativeBundleSignatureMessage(resolution), publicKey(key), Buffer.from(resolution.signature, "base64"));
|
|
11495
|
+
} catch (error) {
|
|
11496
|
+
throw new ArkmePluginError("extension-signature-invalid", "原生 DSH V3 签名格式无效", false, 403, { cause: error });
|
|
11497
|
+
}
|
|
11498
|
+
if (!valid) throw new ArkmePluginError("extension-signature-invalid", "原生 DSH V3 平台签名验证失败", false, 403);
|
|
11499
|
+
}
|
|
9941
11500
|
//#endregion
|
|
9942
11501
|
//#region src/extensions/persistent-client-bundle.ts
|
|
9943
11502
|
/** This function is serialized into each generated browser bundle. It must stay closure-free. */
|
|
@@ -9977,14 +11536,29 @@ function persistentClientFactory(requireModule, spec) {
|
|
|
9977
11536
|
})
|
|
9978
11537
|
});
|
|
9979
11538
|
const envelope = await response.json();
|
|
9980
|
-
if (!response.ok || envelope.ok !== true)
|
|
11539
|
+
if (!response.ok || envelope.ok !== true) {
|
|
11540
|
+
const error = new Error(envelope.error?.message ?? `Arkme Host returned HTTP ${String(response.status)}`);
|
|
11541
|
+
if (envelope.error?.code !== void 0) error.code = envelope.error.code;
|
|
11542
|
+
throw error;
|
|
11543
|
+
}
|
|
9981
11544
|
return envelope.value;
|
|
9982
11545
|
};
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
11546
|
+
let deactivateClient;
|
|
11547
|
+
const callHost = async (method, args = null) => {
|
|
11548
|
+
try {
|
|
11549
|
+
return await callOperation(spec.operation ?? "extensions.persistent.invoke", {
|
|
11550
|
+
...spec.identityKey === "packageName" ? { packageName: spec.extensionId } : {
|
|
11551
|
+
extensionId: spec.extensionId,
|
|
11552
|
+
version: spec.version
|
|
11553
|
+
},
|
|
11554
|
+
method,
|
|
11555
|
+
args: jsonValue(args)
|
|
11556
|
+
});
|
|
11557
|
+
} catch (error) {
|
|
11558
|
+
if (error.code === "extension-runtime-unavailable") deactivateClient?.();
|
|
11559
|
+
throw error;
|
|
11560
|
+
}
|
|
11561
|
+
};
|
|
9988
11562
|
const guardedService = (service, name) => new Proxy(service, { get(target, property) {
|
|
9989
11563
|
const member = Reflect.get(target, property, target);
|
|
9990
11564
|
if (typeof member !== "function") return member;
|
|
@@ -10044,8 +11618,10 @@ function persistentClientFactory(requireModule, spec) {
|
|
|
10044
11618
|
name: `arkme-extension-client:${spec.extensionId}`,
|
|
10045
11619
|
async apply(ctx) {
|
|
10046
11620
|
if (spec.identityKey !== "packageName") {
|
|
10047
|
-
|
|
10048
|
-
|
|
11621
|
+
if ((await callOperation("extensions.persistent.client-state", {
|
|
11622
|
+
extensionId: spec.extensionId,
|
|
11623
|
+
version: spec.version
|
|
11624
|
+
})).mount !== true) return;
|
|
10049
11625
|
}
|
|
10050
11626
|
const styles = new Styles();
|
|
10051
11627
|
const traps = {
|
|
@@ -10089,7 +11665,16 @@ function persistentClientFactory(requireModule, spec) {
|
|
|
10089
11665
|
}
|
|
10090
11666
|
};
|
|
10091
11667
|
ctx.effect(() => () => styles.dispose(), `arkme-extension:${spec.extensionId}:styles`);
|
|
10092
|
-
|
|
11668
|
+
const fiber = ctx.plugin(guarded);
|
|
11669
|
+
const deactivate = () => {
|
|
11670
|
+
styles.dispose();
|
|
11671
|
+
Promise.resolve(fiber.dispose?.()).catch(() => void 0);
|
|
11672
|
+
};
|
|
11673
|
+
deactivateClient = deactivate;
|
|
11674
|
+
ctx.effect(() => () => {
|
|
11675
|
+
if (deactivateClient === deactivate) deactivateClient = void 0;
|
|
11676
|
+
}, `arkme-extension:${spec.extensionId}:client-fiber`);
|
|
11677
|
+
await fiber;
|
|
10093
11678
|
}
|
|
10094
11679
|
};
|
|
10095
11680
|
}
|
|
@@ -10125,18 +11710,18 @@ function writeSecure(path, content) {
|
|
|
10125
11710
|
});
|
|
10126
11711
|
chmodSync(path, 384);
|
|
10127
11712
|
}
|
|
10128
|
-
function activationText(extensionId, enabled) {
|
|
11713
|
+
function activationText(extensionId, enabled, quarantine) {
|
|
10129
11714
|
return `${JSON.stringify({
|
|
10130
11715
|
schema_version: 1,
|
|
10131
11716
|
extension_id: extensionId,
|
|
10132
|
-
enabled
|
|
11717
|
+
enabled,
|
|
11718
|
+
...quarantine === void 0 ? {} : { quarantine }
|
|
10133
11719
|
}, void 0, 2)}\n`;
|
|
10134
11720
|
}
|
|
10135
|
-
function
|
|
10136
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(extensionId)) throw new Error("扩展身份无效");
|
|
11721
|
+
function writeActivationState(bundleDirectory, state) {
|
|
10137
11722
|
const target = join(bundleDirectory, "activation.json");
|
|
10138
11723
|
const temporary = join(bundleDirectory, `.activation.${randomUUID()}.tmp`);
|
|
10139
|
-
writeSecure(temporary, activationText(
|
|
11724
|
+
writeSecure(temporary, activationText(state.extension_id, state.enabled, state.quarantine));
|
|
10140
11725
|
try {
|
|
10141
11726
|
renameSync(temporary, target);
|
|
10142
11727
|
chmodSync(target, 384);
|
|
@@ -10145,6 +11730,27 @@ function writePersistentExtensionActivation(bundleDirectory, extensionId, enable
|
|
|
10145
11730
|
}
|
|
10146
11731
|
return target;
|
|
10147
11732
|
}
|
|
11733
|
+
function writePersistentExtensionActivation(bundleDirectory, extensionId, enabled) {
|
|
11734
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(extensionId)) throw new Error("扩展身份无效");
|
|
11735
|
+
return writeActivationState(bundleDirectory, {
|
|
11736
|
+
schema_version: 1,
|
|
11737
|
+
extension_id: extensionId,
|
|
11738
|
+
enabled
|
|
11739
|
+
});
|
|
11740
|
+
}
|
|
11741
|
+
function quarantinePersistentExtension(bundleDirectory, extensionId, error, failedAtMillis = Date.now()) {
|
|
11742
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(extensionId)) throw new Error("扩展身份无效");
|
|
11743
|
+
return writeActivationState(bundleDirectory, {
|
|
11744
|
+
schema_version: 1,
|
|
11745
|
+
extension_id: extensionId,
|
|
11746
|
+
enabled: false,
|
|
11747
|
+
quarantine: {
|
|
11748
|
+
code: "runtime-load-failed",
|
|
11749
|
+
failed_at_millis: failedAtMillis,
|
|
11750
|
+
message: ((error instanceof Error ? error.message : String(error)).trim() || "persistent extension failed to load").slice(0, 2e3)
|
|
11751
|
+
}
|
|
11752
|
+
});
|
|
11753
|
+
}
|
|
10148
11754
|
function readPersistentExtensionActivation(installationUrl) {
|
|
10149
11755
|
const activationUrl = new URL("./activation.json", installationUrl);
|
|
10150
11756
|
let value;
|
|
@@ -10164,6 +11770,10 @@ function readPersistentExtensionActivation(installationUrl) {
|
|
|
10164
11770
|
if (value === null || typeof value !== "object") throw new Error("Arkme persistent extension activation state is invalid");
|
|
10165
11771
|
const state = value;
|
|
10166
11772
|
if (state.schema_version !== 1 || typeof state.extension_id !== "string" || typeof state.enabled !== "boolean") throw new Error("Arkme persistent extension activation state is invalid");
|
|
11773
|
+
if (state.quarantine !== void 0) {
|
|
11774
|
+
const quarantine = state.quarantine;
|
|
11775
|
+
if (state.enabled || quarantine.code !== "runtime-load-failed" || !Number.isSafeInteger(quarantine.failed_at_millis) || (quarantine.failed_at_millis ?? 0) < 0 || typeof quarantine.message !== "string" || quarantine.message.trim() === "" || quarantine.message.length > 2e3) throw new Error("Arkme persistent extension quarantine state is invalid");
|
|
11776
|
+
}
|
|
10167
11777
|
return state;
|
|
10168
11778
|
}
|
|
10169
11779
|
function materializePersistentExtensionBundle(input) {
|
|
@@ -10192,7 +11802,8 @@ function materializePersistentExtensionBundle(input) {
|
|
|
10192
11802
|
if (existing !== void 0) if (existing.artifact_sha256 === installation.artifact_sha256 && existing.version === installation.version) {
|
|
10193
11803
|
try {
|
|
10194
11804
|
const manifest = JSON.parse(readFileSync(join(bundleDirectory, "package.json"), "utf8"));
|
|
10195
|
-
const
|
|
11805
|
+
const clientBundle = input.clientCode === void 0 ? void 0 : readFileSync(join(bundleDirectory, "lib", "client.js"), "utf8");
|
|
11806
|
+
const clientReady = input.clientCode === void 0 || manifest.exports?.["./client"] === "./lib/client.js" && manifest.dsh?.client?.inject?.length === 0 && clientBundle?.includes("extensions.persistent.invoke") === true && clientBundle.includes("extensions.persistent.client-state");
|
|
10196
11807
|
if (manifest.exports?.["."] === "./lib/index.js" && manifest.exports?.["./package.json"] === "./package.json" && clientReady) {
|
|
10197
11808
|
if (!existsSync(activationPath)) writeSecure(activationPath, activationText(input.resolution.extension_id, true));
|
|
10198
11809
|
return {
|
|
@@ -10453,8 +12064,22 @@ async function mountPersistentArkmeHostExtension(installation, registration) {
|
|
|
10453
12064
|
throw error;
|
|
10454
12065
|
}
|
|
10455
12066
|
}
|
|
12067
|
+
function quarantineRuntimeFailure(installation, installationUrl, error) {
|
|
12068
|
+
try {
|
|
12069
|
+
quarantinePersistentExtension(dirname(fileURLToPath(installationUrl)), installation.extension_id, error);
|
|
12070
|
+
} catch (quarantineError) {
|
|
12071
|
+
console.error(`[arkme-extension:${installation.extension_id}] failed to persist quarantine state`, quarantineError);
|
|
12072
|
+
}
|
|
12073
|
+
console.error(`[arkme-extension:${installation.extension_id}] disabled after runtime load failure`, error);
|
|
12074
|
+
}
|
|
10456
12075
|
async function applyPersistentArkmeHostExtension(ctx, installationUrl) {
|
|
10457
|
-
|
|
12076
|
+
let installation;
|
|
12077
|
+
try {
|
|
12078
|
+
installation = readInstallation(installationUrl);
|
|
12079
|
+
} catch (error) {
|
|
12080
|
+
console.error("[arkme-extension:unknown] ignored invalid persistent installation metadata", error);
|
|
12081
|
+
return;
|
|
12082
|
+
}
|
|
10458
12083
|
const previous = persistentRegistrations.get(installation.extension_id);
|
|
10459
12084
|
if (previous?.ctx === ctx && previous.installationUrl.href === installationUrl.href) return;
|
|
10460
12085
|
if (previous !== void 0) await deactivatePersistentArkmeExtension(installation.extension_id);
|
|
@@ -10463,27 +12088,55 @@ async function applyPersistentArkmeHostExtension(ctx, installationUrl) {
|
|
|
10463
12088
|
installationUrl
|
|
10464
12089
|
};
|
|
10465
12090
|
persistentRegistrations.set(installation.extension_id, registration);
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
12091
|
+
try {
|
|
12092
|
+
ctx.effect(() => () => {
|
|
12093
|
+
if (persistentRegistrations.get(installation.extension_id) !== registration) return;
|
|
12094
|
+
persistentRegistrations.delete(installation.extension_id);
|
|
12095
|
+
persistentHandlers.delete(installation.extension_id);
|
|
12096
|
+
}, `arkme-extension:${installation.extension_id}:registration`);
|
|
12097
|
+
ctx.effect(() => () => {
|
|
12098
|
+
if (persistentHandlers.get(installation.extension_id) === registration.handlers) persistentHandlers.delete(installation.extension_id);
|
|
12099
|
+
}, `arkme-extension:${installation.extension_id}:handlers`);
|
|
12100
|
+
const activation = readPersistentExtensionActivation(installationUrl);
|
|
12101
|
+
if (activation.extension_id !== installation.extension_id) throw new Error("Arkme persistent extension activation identity mismatch");
|
|
12102
|
+
if (!activation.enabled) return;
|
|
12103
|
+
await mountPersistentArkmeHostExtension(installation, registration);
|
|
12104
|
+
} catch (error) {
|
|
12105
|
+
if (persistentRegistrations.get(installation.extension_id) === registration) {
|
|
12106
|
+
persistentRegistrations.delete(installation.extension_id);
|
|
12107
|
+
if (persistentHandlers.get(installation.extension_id) === registration.handlers) persistentHandlers.delete(installation.extension_id);
|
|
12108
|
+
if (persistentClientActivations.get(installation.extension_id) === registration.clientActivation) persistentClientActivations.delete(installation.extension_id);
|
|
12109
|
+
}
|
|
12110
|
+
quarantineRuntimeFailure(installation, installationUrl, error);
|
|
12111
|
+
}
|
|
10478
12112
|
}
|
|
10479
12113
|
function persistentArkmeExtensionActive(extensionId) {
|
|
10480
12114
|
return persistentRegistrations.get(extensionId)?.fiber !== void 0 || persistentClientActivations.has(extensionId);
|
|
10481
12115
|
}
|
|
12116
|
+
function persistentArkmeExtensionRuntimeState(extensionId) {
|
|
12117
|
+
const registration = persistentRegistrations.get(extensionId);
|
|
12118
|
+
if (registration === void 0) return void 0;
|
|
12119
|
+
try {
|
|
12120
|
+
return {
|
|
12121
|
+
version: readInstallation(registration.installationUrl).version,
|
|
12122
|
+
installationUrl: registration.installationUrl.href,
|
|
12123
|
+
active: registration.fiber !== void 0 || registration.clientActivation !== void 0
|
|
12124
|
+
};
|
|
12125
|
+
} catch {
|
|
12126
|
+
return;
|
|
12127
|
+
}
|
|
12128
|
+
}
|
|
10482
12129
|
/** Re-mount a verified Host half when its wrapper is already present in the current DSH process. */
|
|
10483
12130
|
async function activatePersistentArkmeExtension(extensionId) {
|
|
10484
12131
|
const registration = persistentRegistrations.get(extensionId);
|
|
10485
12132
|
if (registration === void 0) return false;
|
|
10486
|
-
|
|
12133
|
+
const installation = readInstallation(registration.installationUrl);
|
|
12134
|
+
try {
|
|
12135
|
+
return await mountPersistentArkmeHostExtension(installation, registration);
|
|
12136
|
+
} catch (error) {
|
|
12137
|
+
quarantineRuntimeFailure(installation, registration.installationUrl, error);
|
|
12138
|
+
throw error;
|
|
12139
|
+
}
|
|
10487
12140
|
}
|
|
10488
12141
|
async function deactivatePersistentArkmeExtension(extensionId) {
|
|
10489
12142
|
const registration = persistentRegistrations.get(extensionId);
|
|
@@ -10503,4 +12156,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
|
|
|
10503
12156
|
return jsonValue(await handler(jsonValue(args)));
|
|
10504
12157
|
}
|
|
10505
12158
|
//#endregion
|
|
10506
|
-
export {
|
|
12159
|
+
export { unpackArkmeExtension as A, ArkmeOutgoingCallError as B, packBundleFiles as C, assertExtensionArtifactSize as D, renderArkmeSandboxHostEntry as E, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as F, ArkmeService as I, ARKME_DEFAULT_SHARE_WEBSITE 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_PROVIDER_CONTRACT_VERSION as R, inspectNativeBundleArtifactV3 as S, readNativeBundleTarballV3 as T, 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, deactivatePersistentArkmeExtension as r, materializePersistentExtensionBundle as s, activatePersistentArkmeExtension as t, renderArkmeBundleClientBundle as u, arkmeSandboxEntryId as v, packLocalNativeBundleDirectoryV3 as w, inspectBundleArtifact as x, bundleSha256 as y, ArkmePluginError as z };
|