@senguoyun/dsh-arkme 0.1.11 → 0.1.12
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 +4 -0
- 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 +24408 -18902
- package/lib/client.js.map +1 -1
- package/lib/index.js +810 -55
- package/lib/persistent-extension.js +2 -2
- package/lib/{persistent-runtime-DInn3Zb1.js → persistent-runtime-kUpXXUZI.js} +1211 -201
- package/lib/sdk.js +88 -1
- package/lib/types/arkme-service.d.ts +38 -1
- 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/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/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/ArkmeExtensionCenter.d.ts +17 -4
- package/lib/types/client/ArkmeExtensionCenter.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/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/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 +2 -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 +6 -1
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
- package/lib/types/client/api.d.ts +1 -1
- package/lib/types/client/api.d.ts.map +1 -1
- package/lib/types/client/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/desktop-notification-runtime.d.ts +35 -0
- package/lib/types/client/desktop-notification-runtime.d.ts.map +1 -0
- 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 +7 -0
- 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/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 +10 -1
- package/lib/types/client/ui-controller.d.ts.map +1 -1
- package/lib/types/extensions/manager.d.ts +18 -1
- package/lib/types/extensions/manager.d.ts.map +1 -1
- package/lib/types/extensions/owned-inventory.d.ts +4 -1
- package/lib/types/extensions/owned-inventory.d.ts.map +1 -1
- package/lib/types/extensions/owned-types.d.ts +1 -0
- 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/publish-client.d.ts +5 -1
- 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/types.d.ts +48 -1
- 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 +4 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/rich-media-routes.d.ts.map +1 -1
- package/lib/types/sdk/index.d.ts +44 -4
- package/lib/types/sdk/index.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/group-service.d.ts +17 -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/search-service.d.ts +10 -2
- package/lib/types/services/search-service.d.ts.map +1 -1
- package/lib/types/services/service.d.ts +1 -0
- package/lib/types/services/service.d.ts.map +1 -1
- package/lib/types/services/world-service.d.ts +7 -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/groups/index.d.ts +3 -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/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/extensions/index.d.ts.map +1 -1
- package/lib/types/tools/extensions/publish-conversation.d.ts +1 -0
- 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/groups.d.ts +11 -0
- package/lib/types/tools/ports/groups.d.ts.map +1 -0
- package/lib/types/tools/ports/index.d.ts +4 -2
- package/lib/types/tools/ports/index.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/prompts/business.d.ts.map +1 -1
- package/lib/types/tools/registry/registrar.d.ts.map +1 -1
- package/lib/types/types.d.ts +172 -2
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +5 -4
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
6
7
|
import { createContext, runInContext } from "node:vm";
|
|
@@ -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);
|
|
@@ -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$21(value) {
|
|
1083
1095
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1084
1096
|
}
|
|
1085
|
-
function booleanValue$
|
|
1097
|
+
function booleanValue$12(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$12(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$21(data.selected_duration_millis)),
|
|
1115
|
+
minimumDurationMillis: Math.max(0, numberValue$21(data.minimum_duration_millis)),
|
|
1116
|
+
selectedSegmentCount: Math.max(0, numberValue$21(data.selected_segment_count)),
|
|
1117
|
+
retryable: booleanValue$12(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$12(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$12(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$21(data.selected_duration_millis)),
|
|
1167
|
+
minimumDurationMillis: Math.max(0, numberValue$21(data.minimum_duration_millis)),
|
|
1168
|
+
selectedSegmentCount: Math.max(0, numberValue$21(data.selected_segment_count)),
|
|
1169
|
+
selectedTextCount: Math.max(0, numberValue$21(data.selected_text_count)),
|
|
1170
|
+
retryable: booleanValue$12(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$21(data.progress)))),
|
|
1204
1216
|
selectedSegmentCount: segmentCount,
|
|
1205
1217
|
...textCount === 0 ? {} : { selectedTextCount: textCount },
|
|
1206
|
-
retryable: booleanValue$
|
|
1218
|
+
retryable: booleanValue$12(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$21(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$21(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$21(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$21(source.started_at),
|
|
1247
|
+
selectedDurationMillis: Math.max(0, numberValue$21(source.selected_duration_millis)),
|
|
1248
|
+
selectedSegmentCount: Math.max(0, Math.trunc(numberValue$21(item.selected_segment_count))),
|
|
1249
|
+
retryable: booleanValue$12(item.retryable),
|
|
1250
|
+
createdAtMillis: numberValue$21(item.created_at),
|
|
1251
|
+
updatedAtMillis: numberValue$21(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$21(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$21(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$20(value) {
|
|
1269
1281
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1270
1282
|
}
|
|
1271
|
-
function booleanValue$
|
|
1283
|
+
function booleanValue$11(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$20(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$11(data.recommended),
|
|
1340
|
+
selected: booleanValue$11(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$20(data.id ?? data.msg_id ?? data.msgId);
|
|
1369
|
+
const sessionId = numberValue$20(data.session_id ?? data.sessionId);
|
|
1370
|
+
const roleCode = numberValue$20(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$20(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$20(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$11(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$20(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$20((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$20(accepted.user_msg_id);
|
|
1520
|
+
const assistantMsgId = numberValue$20(accepted.assistant_msg_id);
|
|
1521
|
+
const acceptedSessionId = numberValue$20(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$20(data.sequence);
|
|
1558
|
+
const surfaceAssistantMsgId = numberValue$20(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$11(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$11(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$11(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$19(value) {
|
|
1785
1797
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1786
1798
|
}
|
|
1787
|
-
function booleanValue$
|
|
1799
|
+
function booleanValue$10(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$19(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$19(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$19(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$19(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$19(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$19(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$19(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$19(item.due_at));
|
|
2114
|
+
const remindAtMillis = Math.trunc(numberValue$19(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$10(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$19(item.create_at)),
|
|
2123
|
+
updatedAtMillis: Math.trunc(numberValue$19(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$19(item.due_at));
|
|
2145
|
+
const remindAtMillis = Math.trunc(numberValue$19(item.remind_at));
|
|
2146
|
+
const readAtMillis = Math.trunc(numberValue$19(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$19(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$19(item.create_at)),
|
|
2157
|
+
updatedAtMillis: Math.trunc(numberValue$19(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$18(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$18(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$18(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$18((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$18((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$18(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$17(value) {
|
|
2458
2470
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2459
2471
|
}
|
|
2460
|
-
function booleanValue$
|
|
2472
|
+
function booleanValue$9(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;
|
|
@@ -2588,19 +2600,19 @@ var BotService = class {
|
|
|
2588
2600
|
const group = await this.openGroupSourceRef(groupSourceRef, session.userId);
|
|
2589
2601
|
const data = await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", { subject_uid: group.ownerRef }, session, options.signal);
|
|
2590
2602
|
const items = [];
|
|
2591
|
-
for (const value of listValue$
|
|
2603
|
+
for (const value of listValue$18(data.bots)) {
|
|
2592
2604
|
const raw = objectValue$1(value);
|
|
2593
2605
|
if (stringValue$1(raw.provider).trim() !== "openclaw") continue;
|
|
2594
2606
|
const { directChatAvailable: _directChatAvailable, ...summary } = await this.botSummaryFromData(raw, session.userId);
|
|
2595
2607
|
items.push({
|
|
2596
2608
|
...summary,
|
|
2597
|
-
installed: booleanValue$
|
|
2609
|
+
installed: booleanValue$9(raw.installed)
|
|
2598
2610
|
});
|
|
2599
2611
|
}
|
|
2600
2612
|
return {
|
|
2601
2613
|
groupSourceRef,
|
|
2602
2614
|
displayName: stringValue$1(data.subject_title).trim() || group.displayName,
|
|
2603
|
-
canAddBots: booleanValue$
|
|
2615
|
+
canAddBots: booleanValue$9(data.can_current_user_add_bots),
|
|
2604
2616
|
items
|
|
2605
2617
|
};
|
|
2606
2618
|
}
|
|
@@ -2653,7 +2665,7 @@ var BotService = class {
|
|
|
2653
2665
|
};
|
|
2654
2666
|
}
|
|
2655
2667
|
async sealBotRef(userId, botId) {
|
|
2656
|
-
const payload = encodeOpaqueJson$
|
|
2668
|
+
const payload = encodeOpaqueJson$6({
|
|
2657
2669
|
version: 1,
|
|
2658
2670
|
userId,
|
|
2659
2671
|
botId
|
|
@@ -2669,13 +2681,13 @@ var BotService = class {
|
|
|
2669
2681
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("bot-ref-invalid", "Bot 引用无效", false);
|
|
2670
2682
|
let raw;
|
|
2671
2683
|
try {
|
|
2672
|
-
raw = objectValue$1(decodeOpaqueJson$
|
|
2684
|
+
raw = objectValue$1(decodeOpaqueJson$6(payload));
|
|
2673
2685
|
} catch (error) {
|
|
2674
2686
|
throw new ArkmePluginError("bot-ref-invalid", "Bot 引用无效", false, 400, { cause: error });
|
|
2675
2687
|
}
|
|
2676
2688
|
const reference = {
|
|
2677
2689
|
version: 1,
|
|
2678
|
-
userId: numberValue$
|
|
2690
|
+
userId: numberValue$17(raw.userId),
|
|
2679
2691
|
botId: stringValue$1(raw.botId).trim()
|
|
2680
2692
|
};
|
|
2681
2693
|
if (raw.version !== 1 || reference.userId !== expectedUserId || reference.botId === "") throw new ArkmePluginError("bot-ref-invalid", "Bot 引用与当前账号不匹配", false, 403);
|
|
@@ -2683,6 +2695,181 @@ var BotService = class {
|
|
|
2683
2695
|
}
|
|
2684
2696
|
};
|
|
2685
2697
|
//#endregion
|
|
2698
|
+
//#region src/services/calendar-service.ts
|
|
2699
|
+
const MAX_CALENDAR_RANGE_DAYS = 62;
|
|
2700
|
+
const MAX_DAY_RECORD_LIMIT = 50;
|
|
2701
|
+
function numberValue$16(value) {
|
|
2702
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2703
|
+
}
|
|
2704
|
+
function booleanValue$8(value) {
|
|
2705
|
+
if (typeof value === "boolean") return value;
|
|
2706
|
+
if (typeof value === "number") return value !== 0;
|
|
2707
|
+
}
|
|
2708
|
+
function listValue$17(value) {
|
|
2709
|
+
return Array.isArray(value) ? value : [];
|
|
2710
|
+
}
|
|
2711
|
+
function readCalendarDate(value, field) {
|
|
2712
|
+
const normalized = value.trim();
|
|
2713
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(normalized);
|
|
2714
|
+
if (match === null) throw new ArkmePluginError("calendar-date-invalid", `${field} 必须是 YYYY-MM-DD`, false);
|
|
2715
|
+
const year = Number(match[1]);
|
|
2716
|
+
const month = Number(match[2]);
|
|
2717
|
+
const day = Number(match[3]);
|
|
2718
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
2719
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) throw new ArkmePluginError("calendar-date-invalid", `${field} 必须是真实日期`, false);
|
|
2720
|
+
return normalized;
|
|
2721
|
+
}
|
|
2722
|
+
function calendarDayNumber(date) {
|
|
2723
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
|
|
2724
|
+
if (match === null) throw new ArkmePluginError("calendar-date-invalid", "日期格式无效", false);
|
|
2725
|
+
const year = Number(match[1]);
|
|
2726
|
+
const month = Number(match[2]);
|
|
2727
|
+
const day = Number(match[3]);
|
|
2728
|
+
return Math.trunc(Date.UTC(year, month - 1, day) / 864e5);
|
|
2729
|
+
}
|
|
2730
|
+
function readTimezone(value) {
|
|
2731
|
+
const normalized = value?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
|
|
2732
|
+
if (normalized.length > 80) throw new ArkmePluginError("calendar-timezone-invalid", "timezone 无效", false);
|
|
2733
|
+
return normalized;
|
|
2734
|
+
}
|
|
2735
|
+
function boundedLimit(value) {
|
|
2736
|
+
const limit = Math.trunc(value ?? 20);
|
|
2737
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_DAY_RECORD_LIMIT) throw new ArkmePluginError("calendar-limit-invalid", `limit 必须是 1-${MAX_DAY_RECORD_LIMIT} 的整数`, false);
|
|
2738
|
+
return limit;
|
|
2739
|
+
}
|
|
2740
|
+
function contentAccessState(value) {
|
|
2741
|
+
const code = Math.trunc(numberValue$16(value));
|
|
2742
|
+
if (code === 1) return "available";
|
|
2743
|
+
if (code === 2) return "protected";
|
|
2744
|
+
return "unknown";
|
|
2745
|
+
}
|
|
2746
|
+
function sourceKind(raw) {
|
|
2747
|
+
const topic = objectValue$1(raw.topic_core);
|
|
2748
|
+
const chat = objectValue$1(raw.chat_core);
|
|
2749
|
+
if (stringValue$1(topic.topic_uid).trim() !== "") return "topic";
|
|
2750
|
+
if (stringValue$1(chat.chat_session_uid).trim() !== "") return "chat";
|
|
2751
|
+
if (booleanValue$8(raw.is_uncategorized) === true) return "self";
|
|
2752
|
+
return "unknown";
|
|
2753
|
+
}
|
|
2754
|
+
var CalendarService = class {
|
|
2755
|
+
runtime;
|
|
2756
|
+
constructor(runtime) {
|
|
2757
|
+
this.runtime = runtime;
|
|
2758
|
+
}
|
|
2759
|
+
async bucketPage(options) {
|
|
2760
|
+
const startDate = readCalendarDate(options.startDate, "start_date");
|
|
2761
|
+
const endDate = readCalendarDate(options.endDate, "end_date");
|
|
2762
|
+
const startDay = calendarDayNumber(startDate);
|
|
2763
|
+
const endDay = calendarDayNumber(endDate);
|
|
2764
|
+
if (startDay > endDay) throw new ArkmePluginError("calendar-range-invalid", "start_date 不能晚于 end_date", false);
|
|
2765
|
+
if (endDay - startDay + 1 > MAX_CALENDAR_RANGE_DAYS) throw new ArkmePluginError("calendar-range-too-wide", `日历范围最多查询 ${MAX_CALENDAR_RANGE_DAYS} 天`, false);
|
|
2766
|
+
const timezone = readTimezone(options.timezone);
|
|
2767
|
+
const session = await this.runtime.requireSession();
|
|
2768
|
+
const data = await this.runtime.authenticatedCalendarPost("/api/v1/calendar/buckets/query", {
|
|
2769
|
+
bucket_scope_kind: 1,
|
|
2770
|
+
bucket_scope_uid: "",
|
|
2771
|
+
start_date: startDate,
|
|
2772
|
+
end_date: endDate,
|
|
2773
|
+
timezone
|
|
2774
|
+
}, session, options.signal, {
|
|
2775
|
+
key: `calendar:buckets:self:${startDate}:${endDate}:${timezone}`,
|
|
2776
|
+
cacheMs: 3e4,
|
|
2777
|
+
failureCooldownMs: 2e3
|
|
2778
|
+
});
|
|
2779
|
+
return {
|
|
2780
|
+
scope: "self",
|
|
2781
|
+
startDate,
|
|
2782
|
+
endDate,
|
|
2783
|
+
timezone: stringValue$1(data.timezone).trim() || timezone,
|
|
2784
|
+
refreshedAtMillis: Date.now(),
|
|
2785
|
+
days: listValue$17(data.daily_data).map((raw) => this.bucketDay(raw)).filter((day) => day !== void 0)
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
async dayRecords(options) {
|
|
2789
|
+
const bucketDate = readCalendarDate(options.bucketDate, "bucket_date");
|
|
2790
|
+
const timezone = readTimezone(options.timezone);
|
|
2791
|
+
const limit = boundedLimit(options.limit);
|
|
2792
|
+
const cursorSendAt = Math.trunc(options.cursor?.sendAtMillis ?? 0);
|
|
2793
|
+
const cursorRecordUid = options.cursor?.recordUid.trim() ?? "";
|
|
2794
|
+
const session = await this.runtime.requireSession();
|
|
2795
|
+
const data = await this.runtime.authenticatedCalendarPost("/api/v1/calendar/records/query", {
|
|
2796
|
+
bucket_scope_kind: 1,
|
|
2797
|
+
bucket_scope_uid: "",
|
|
2798
|
+
bucket_date: bucketDate,
|
|
2799
|
+
timezone,
|
|
2800
|
+
limit,
|
|
2801
|
+
...cursorSendAt > 0 && cursorRecordUid !== "" ? {
|
|
2802
|
+
cursor_send_at: cursorSendAt,
|
|
2803
|
+
cursor_record_uid: cursorRecordUid
|
|
2804
|
+
} : {}
|
|
2805
|
+
}, session, options.signal, {
|
|
2806
|
+
key: `calendar:records:self:${bucketDate}:${timezone}:${String(limit)}:${String(cursorSendAt)}:${cursorRecordUid}`,
|
|
2807
|
+
cacheMs: 1e4,
|
|
2808
|
+
failureCooldownMs: 2e3
|
|
2809
|
+
});
|
|
2810
|
+
const nextSendAt = Math.trunc(numberValue$16(data.next_cursor_send_at));
|
|
2811
|
+
const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
|
|
2812
|
+
return {
|
|
2813
|
+
scope: "self",
|
|
2814
|
+
bucketDate,
|
|
2815
|
+
timezone: stringValue$1(data.timezone).trim() || timezone,
|
|
2816
|
+
refreshedAtMillis: Date.now(),
|
|
2817
|
+
items: listValue$17(data.items).map((raw) => this.dayRecord(raw)).filter((item) => item !== void 0),
|
|
2818
|
+
hasMore: data.has_more === true,
|
|
2819
|
+
...nextSendAt > 0 && nextUid !== "" ? { nextCursor: {
|
|
2820
|
+
sendAtMillis: nextSendAt,
|
|
2821
|
+
recordUid: nextUid
|
|
2822
|
+
} } : {}
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
bucketDay(raw) {
|
|
2826
|
+
const item = objectValue$1(raw);
|
|
2827
|
+
const bucketDate = stringValue$1(item.bucket_date).trim();
|
|
2828
|
+
if (bucketDate === "") return void 0;
|
|
2829
|
+
const count = Math.max(0, Math.trunc(numberValue$16(item.count)));
|
|
2830
|
+
return {
|
|
2831
|
+
bucketDate,
|
|
2832
|
+
count,
|
|
2833
|
+
protectedCount: Math.max(0, Math.trunc(numberValue$16(item.protected_count))),
|
|
2834
|
+
hasRecords: count > 0,
|
|
2835
|
+
...numberValue$16(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$16(item.first_send_at)) } : {}
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
dayRecord(raw) {
|
|
2839
|
+
const item = objectValue$1(raw);
|
|
2840
|
+
const core = objectValue$1(item.record_core);
|
|
2841
|
+
const recordUid = stringValue$1(item.record_uid ?? core.record_uid).trim();
|
|
2842
|
+
const sendAtMillis = Math.trunc(numberValue$16(item.send_at ?? core.send_at));
|
|
2843
|
+
if (recordUid === "" || sendAtMillis <= 0) return void 0;
|
|
2844
|
+
const accessState = contentAccessState(core.content_access_state);
|
|
2845
|
+
const available = accessState === "available";
|
|
2846
|
+
const title = available ? clippedText(core.title, 500) : "";
|
|
2847
|
+
const textContent = available ? clippedText(core.text_content, 4e3) : "";
|
|
2848
|
+
const preview = title || clippedText(textContent, 160) || (available ? "无文字内容" : "受保护内容");
|
|
2849
|
+
const topic = objectValue$1(item.topic_core);
|
|
2850
|
+
const isUncategorized = booleanValue$8(item.is_uncategorized);
|
|
2851
|
+
const hasManualEdit = booleanValue$8(core.has_manual_edit);
|
|
2852
|
+
const hasPolish = booleanValue$8(core.has_polish);
|
|
2853
|
+
return {
|
|
2854
|
+
recordUid,
|
|
2855
|
+
sendAtMillis,
|
|
2856
|
+
accessState,
|
|
2857
|
+
title,
|
|
2858
|
+
textContent,
|
|
2859
|
+
preview,
|
|
2860
|
+
...stringValue$1(topic.title).trim() === "" ? {} : { topicTitle: stringValue$1(topic.title).trim() },
|
|
2861
|
+
sourceKind: sourceKind(item),
|
|
2862
|
+
creationSource: Math.trunc(numberValue$16(core.creation_source)),
|
|
2863
|
+
templateKind: Math.trunc(numberValue$16(core.template_kind)),
|
|
2864
|
+
displayKind: Math.trunc(numberValue$16(core.display_kind)),
|
|
2865
|
+
protected: accessState === "protected",
|
|
2866
|
+
...isUncategorized === void 0 ? {} : { isUncategorized },
|
|
2867
|
+
...hasManualEdit === void 0 ? {} : { hasManualEdit },
|
|
2868
|
+
...hasPolish === void 0 ? {} : { hasPolish }
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
};
|
|
2872
|
+
//#endregion
|
|
2686
2873
|
//#region src/chat-realtime.ts
|
|
2687
2874
|
const ARKME_CHAT_SSE_PATH = "/api/v1/sse/chat/noty";
|
|
2688
2875
|
const DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
|
|
@@ -2690,7 +2877,7 @@ const DEFAULT_INACTIVITY_TIMEOUT_MS = 75e3;
|
|
|
2690
2877
|
const DEFAULT_LEASE_DURATION_MS = 57e4;
|
|
2691
2878
|
const DEFAULT_IDLE_POLL_MS = 2e3;
|
|
2692
2879
|
const DEFAULT_RETRY_BASE_MS = 1e3;
|
|
2693
|
-
const DEFAULT_MAX_RETRY_MS =
|
|
2880
|
+
const DEFAULT_MAX_RETRY_MS = 15e3;
|
|
2694
2881
|
const DEFAULT_STABLE_CONNECTION_MS = 3e4;
|
|
2695
2882
|
const MAX_SEEN_EVENTS = 256;
|
|
2696
2883
|
var ArkmeChatRealtimeAuthError = class extends Error {};
|
|
@@ -2771,11 +2958,14 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2771
2958
|
now;
|
|
2772
2959
|
rootController;
|
|
2773
2960
|
connectionController;
|
|
2961
|
+
waitController;
|
|
2774
2962
|
loop;
|
|
2775
2963
|
revision = 0;
|
|
2776
2964
|
connected = false;
|
|
2777
2965
|
lastAcceptedAccessToken;
|
|
2778
2966
|
lastConnectionLifetimeMs = 0;
|
|
2967
|
+
connectionGeneration = 0;
|
|
2968
|
+
forceReconnectRequested = false;
|
|
2779
2969
|
lastEventAtMillis;
|
|
2780
2970
|
seenEventUids = /* @__PURE__ */ new Set();
|
|
2781
2971
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -2812,14 +3002,19 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2812
3002
|
stop() {
|
|
2813
3003
|
this.rootController?.abort();
|
|
2814
3004
|
this.connectionController?.abort();
|
|
3005
|
+
this.waitController?.abort();
|
|
2815
3006
|
}
|
|
2816
3007
|
reconnect() {
|
|
3008
|
+
if (this.loop === void 0) return;
|
|
3009
|
+
this.forceReconnectRequested = true;
|
|
2817
3010
|
this.connectionController?.abort();
|
|
3011
|
+
this.waitController?.abort();
|
|
2818
3012
|
}
|
|
2819
3013
|
state() {
|
|
2820
3014
|
return {
|
|
2821
3015
|
revision: this.revision,
|
|
2822
3016
|
connected: this.connected,
|
|
3017
|
+
connectionGeneration: this.connectionGeneration,
|
|
2823
3018
|
...this.lastEventAtMillis === void 0 ? {} : { lastEventAtMillis: this.lastEventAtMillis }
|
|
2824
3019
|
};
|
|
2825
3020
|
}
|
|
@@ -2841,16 +3036,17 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2841
3036
|
if (session === void 0) {
|
|
2842
3037
|
this.connected = false;
|
|
2843
3038
|
blockedAccessToken = void 0;
|
|
2844
|
-
await
|
|
3039
|
+
await this.wait(this.idlePollMs, signal);
|
|
2845
3040
|
continue;
|
|
2846
3041
|
}
|
|
2847
3042
|
if (blockedAccessToken === session.accessToken) {
|
|
2848
3043
|
this.connected = false;
|
|
2849
|
-
await
|
|
3044
|
+
await this.wait(this.idlePollMs, signal);
|
|
2850
3045
|
continue;
|
|
2851
3046
|
}
|
|
2852
3047
|
blockedAccessToken = void 0;
|
|
2853
3048
|
let stableConnection = false;
|
|
3049
|
+
this.options.diagnostic?.("reconnect_attempt", { connectionGeneration: this.connectionGeneration + 1 });
|
|
2854
3050
|
try {
|
|
2855
3051
|
await this.consume(session, signal);
|
|
2856
3052
|
stableConnection = this.lastConnectionLifetimeMs >= this.stableConnectionMs;
|
|
@@ -2865,7 +3061,7 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2865
3061
|
if (refreshedButUnacceptedToken === session.accessToken) {
|
|
2866
3062
|
blockedAccessToken = session.accessToken;
|
|
2867
3063
|
refreshedButUnacceptedToken = void 0;
|
|
2868
|
-
await
|
|
3064
|
+
await this.wait(this.idlePollMs, signal);
|
|
2869
3065
|
continue;
|
|
2870
3066
|
}
|
|
2871
3067
|
let refreshed;
|
|
@@ -2876,17 +3072,24 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2876
3072
|
}
|
|
2877
3073
|
if (refreshed !== void 0 && refreshed.accessToken !== session.accessToken) {
|
|
2878
3074
|
refreshedButUnacceptedToken = refreshed.accessToken;
|
|
3075
|
+
this.forceReconnectRequested = false;
|
|
2879
3076
|
retryMs = this.retryBaseMs;
|
|
2880
3077
|
continue;
|
|
2881
3078
|
}
|
|
2882
3079
|
blockedAccessToken = session.accessToken;
|
|
2883
|
-
await
|
|
3080
|
+
await this.wait(this.idlePollMs, signal);
|
|
2884
3081
|
continue;
|
|
2885
3082
|
}
|
|
2886
3083
|
} finally {
|
|
3084
|
+
if (this.connected && !signal.aborted) this.options.diagnostic?.("sse_disconnected", { connectionGeneration: this.connectionGeneration });
|
|
2887
3085
|
this.connected = false;
|
|
2888
3086
|
}
|
|
2889
|
-
|
|
3087
|
+
if (this.forceReconnectRequested) {
|
|
3088
|
+
this.forceReconnectRequested = false;
|
|
3089
|
+
retryMs = this.retryBaseMs;
|
|
3090
|
+
continue;
|
|
3091
|
+
}
|
|
3092
|
+
await this.wait(this.jitteredDelay(retryMs), signal);
|
|
2890
3093
|
if (!stableConnection) retryMs = Math.min(this.maxRetryMs, Math.max(this.retryBaseMs, retryMs * 2));
|
|
2891
3094
|
}
|
|
2892
3095
|
}
|
|
@@ -2922,6 +3125,7 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2922
3125
|
acceptedAtMillis = this.now();
|
|
2923
3126
|
this.connected = true;
|
|
2924
3127
|
this.lastAcceptedAccessToken = session.accessToken;
|
|
3128
|
+
this.connectionGeneration += 1;
|
|
2925
3129
|
this.advanceRevision("reconcile");
|
|
2926
3130
|
leaseTimer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("chat SSE lease rotation")), this.leaseDurationMs);
|
|
2927
3131
|
const reader = response.body.getReader();
|
|
@@ -2946,6 +3150,25 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
2946
3150
|
if (this.connectionController === controller) this.connectionController = void 0;
|
|
2947
3151
|
}
|
|
2948
3152
|
}
|
|
3153
|
+
jitteredDelay(milliseconds) {
|
|
3154
|
+
const random = Math.min(1, Math.max(0, this.random()));
|
|
3155
|
+
return Math.max(0, Math.round(milliseconds * (.8 + random * .4)));
|
|
3156
|
+
}
|
|
3157
|
+
async wait(milliseconds, rootSignal) {
|
|
3158
|
+
if (rootSignal.aborted) return;
|
|
3159
|
+
const controller = new AbortController();
|
|
3160
|
+
const abortFromRoot = () => {
|
|
3161
|
+
controller.abort(rootSignal.reason);
|
|
3162
|
+
};
|
|
3163
|
+
rootSignal.addEventListener("abort", abortFromRoot, { once: true });
|
|
3164
|
+
this.waitController = controller;
|
|
3165
|
+
try {
|
|
3166
|
+
await waitFor(milliseconds, controller.signal);
|
|
3167
|
+
} finally {
|
|
3168
|
+
rootSignal.removeEventListener("abort", abortFromRoot);
|
|
3169
|
+
if (this.waitController === controller) this.waitController = void 0;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
2949
3172
|
acceptLine(line) {
|
|
2950
3173
|
const event = decodeArkmeChatReceiveDataLine(line);
|
|
2951
3174
|
if (event === void 0 || this.seenEventUids.has(event.eventUid)) return;
|
|
@@ -2984,11 +3207,15 @@ var ChatRealtimeService = class {
|
|
|
2984
3207
|
projectionReader;
|
|
2985
3208
|
chatRealtime;
|
|
2986
3209
|
chatClientListeners = /* @__PURE__ */ new Set();
|
|
2987
|
-
|
|
3210
|
+
pendingChatProjections = /* @__PURE__ */ new Map();
|
|
2988
3211
|
projectionRetryCounts = /* @__PURE__ */ new Map();
|
|
2989
3212
|
projectionTimer;
|
|
2990
3213
|
projectionInFlight = false;
|
|
2991
3214
|
projectionFailureCount = 0;
|
|
3215
|
+
notificationBaselineGeneration = 0;
|
|
3216
|
+
notificationBaselineSequences = /* @__PURE__ */ new Map();
|
|
3217
|
+
notificationBaselineRetryTimer;
|
|
3218
|
+
notificationBaselineRetryCount = 0;
|
|
2992
3219
|
chatClientRevision = 0;
|
|
2993
3220
|
constructor(runtime, source, projectionReader) {
|
|
2994
3221
|
this.runtime = runtime;
|
|
@@ -3005,7 +3232,10 @@ var ChatRealtimeService = class {
|
|
|
3005
3232
|
return;
|
|
3006
3233
|
}
|
|
3007
3234
|
},
|
|
3008
|
-
fetchImpl: runtime.fetchImpl
|
|
3235
|
+
fetchImpl: runtime.fetchImpl,
|
|
3236
|
+
diagnostic: (event, details) => {
|
|
3237
|
+
console.info(`dsh-arkme: ${event}`, details);
|
|
3238
|
+
}
|
|
3009
3239
|
});
|
|
3010
3240
|
}
|
|
3011
3241
|
reconnect() {
|
|
@@ -3013,9 +3243,13 @@ var ChatRealtimeService = class {
|
|
|
3013
3243
|
}
|
|
3014
3244
|
dispose() {
|
|
3015
3245
|
if (this.projectionTimer !== void 0) clearTimeout(this.projectionTimer);
|
|
3246
|
+
if (this.notificationBaselineRetryTimer !== void 0) clearTimeout(this.notificationBaselineRetryTimer);
|
|
3016
3247
|
this.projectionTimer = void 0;
|
|
3017
|
-
this.
|
|
3248
|
+
this.notificationBaselineRetryTimer = void 0;
|
|
3249
|
+
this.pendingChatProjections.clear();
|
|
3018
3250
|
this.projectionRetryCounts.clear();
|
|
3251
|
+
this.notificationBaselineSequences.clear();
|
|
3252
|
+
this.notificationBaselineGeneration = 0;
|
|
3019
3253
|
this.chatClientListeners.clear();
|
|
3020
3254
|
}
|
|
3021
3255
|
startChatRealtime() {
|
|
@@ -3027,9 +3261,13 @@ var ChatRealtimeService = class {
|
|
|
3027
3261
|
unsubscribe();
|
|
3028
3262
|
stop();
|
|
3029
3263
|
if (this.projectionTimer !== void 0) clearTimeout(this.projectionTimer);
|
|
3264
|
+
if (this.notificationBaselineRetryTimer !== void 0) clearTimeout(this.notificationBaselineRetryTimer);
|
|
3030
3265
|
this.projectionTimer = void 0;
|
|
3031
|
-
this.
|
|
3266
|
+
this.notificationBaselineRetryTimer = void 0;
|
|
3267
|
+
this.pendingChatProjections.clear();
|
|
3032
3268
|
this.projectionRetryCounts.clear();
|
|
3269
|
+
this.notificationBaselineSequences.clear();
|
|
3270
|
+
this.notificationBaselineGeneration = 0;
|
|
3033
3271
|
};
|
|
3034
3272
|
}
|
|
3035
3273
|
chatRealtimeState() {
|
|
@@ -3047,26 +3285,93 @@ var ChatRealtimeService = class {
|
|
|
3047
3285
|
type: "reconcile",
|
|
3048
3286
|
revision: this.chatClientRevision,
|
|
3049
3287
|
connected: state.connected,
|
|
3288
|
+
connectionGeneration: state.connectionGeneration,
|
|
3050
3289
|
refresh: "if-stale"
|
|
3051
3290
|
};
|
|
3052
3291
|
}
|
|
3053
3292
|
handleChatRealtimeNotice(notice) {
|
|
3054
3293
|
if (notice.cause === "reconcile") {
|
|
3294
|
+
const generation = notice.state.connectionGeneration;
|
|
3295
|
+
this.notificationBaselineGeneration = 0;
|
|
3296
|
+
this.notificationBaselineSequences.clear();
|
|
3297
|
+
this.notificationBaselineRetryCount = 0;
|
|
3298
|
+
if (this.notificationBaselineRetryTimer !== void 0) clearTimeout(this.notificationBaselineRetryTimer);
|
|
3299
|
+
this.notificationBaselineRetryTimer = void 0;
|
|
3055
3300
|
this.emitChatClientEvent({
|
|
3056
3301
|
type: "reconcile",
|
|
3057
3302
|
revision: this.nextChatClientRevision(),
|
|
3058
3303
|
connected: notice.state.connected,
|
|
3304
|
+
connectionGeneration: generation,
|
|
3059
3305
|
refresh: "none"
|
|
3060
3306
|
});
|
|
3307
|
+
this.reconcileChatNotificationBaseline(generation);
|
|
3061
3308
|
return;
|
|
3062
3309
|
}
|
|
3063
|
-
if (notice.cause === "hint" && notice.hint !== void 0) this.scheduleChatSessionProjection(notice.hint.chatSessionUid, notice.hint.latestSequence
|
|
3310
|
+
if (notice.cause === "hint" && notice.hint !== void 0) this.scheduleChatSessionProjection(notice.hint.chatSessionUid, notice.hint.latestSequence, {
|
|
3311
|
+
hint: notice.hint,
|
|
3312
|
+
connectionGeneration: notice.state.connectionGeneration,
|
|
3313
|
+
attempts: 0
|
|
3314
|
+
});
|
|
3064
3315
|
}
|
|
3065
|
-
|
|
3316
|
+
async reconcileChatNotificationBaseline(connectionGeneration) {
|
|
3317
|
+
try {
|
|
3318
|
+
const session = await this.runtime.requireSession();
|
|
3319
|
+
const sequences = /* @__PURE__ */ new Map();
|
|
3320
|
+
let cursor;
|
|
3321
|
+
for (let pageIndex = 0; pageIndex < 10; pageIndex += 1) {
|
|
3322
|
+
const page = await this.source.listSources("root", {
|
|
3323
|
+
limit: 50,
|
|
3324
|
+
refresh: true,
|
|
3325
|
+
...cursor === void 0 ? {} : { cursor }
|
|
3326
|
+
});
|
|
3327
|
+
for (const item of page.items) {
|
|
3328
|
+
if (item.kind !== "private_chat" && item.kind !== "group_chat") continue;
|
|
3329
|
+
const source = await this.source.openSourceRef(item.sourceRef, session.userId);
|
|
3330
|
+
sequences.set(source.ownerRef, item.latestSequence ?? 0);
|
|
3331
|
+
}
|
|
3332
|
+
if (!page.hasMore || page.nextCursor === void 0) break;
|
|
3333
|
+
cursor = page.nextCursor;
|
|
3334
|
+
}
|
|
3335
|
+
const state = this.chatRealtime.state();
|
|
3336
|
+
if (!state.connected || state.connectionGeneration !== connectionGeneration) return;
|
|
3337
|
+
this.notificationBaselineSequences.clear();
|
|
3338
|
+
for (const [uid, sequence] of sequences) this.notificationBaselineSequences.set(uid, sequence);
|
|
3339
|
+
this.notificationBaselineGeneration = connectionGeneration;
|
|
3340
|
+
this.notificationBaselineRetryCount = 0;
|
|
3341
|
+
console.info("dsh-arkme: reconcile_completed", {
|
|
3342
|
+
connectionGeneration,
|
|
3343
|
+
sessionCount: sequences.size
|
|
3344
|
+
});
|
|
3345
|
+
if (this.pendingChatProjections.size > 0 && this.projectionTimer === void 0 && !this.projectionInFlight) this.projectionTimer = setTimeout(() => {
|
|
3346
|
+
this.projectionTimer = void 0;
|
|
3347
|
+
this.flushChatSessionProjections();
|
|
3348
|
+
}, 0);
|
|
3349
|
+
} catch (error) {
|
|
3350
|
+
const state = this.chatRealtime.state();
|
|
3351
|
+
if (!state.connected || state.connectionGeneration !== connectionGeneration) return;
|
|
3352
|
+
this.notificationBaselineRetryCount += 1;
|
|
3353
|
+
const delay = Math.min(15e3, 1e3 * 2 ** Math.min(4, this.notificationBaselineRetryCount - 1));
|
|
3354
|
+
console.warn("dsh-arkme: Chat reconnect reconciliation failed:", safeFailureMessage$6(error));
|
|
3355
|
+
this.notificationBaselineRetryTimer = setTimeout(() => {
|
|
3356
|
+
this.notificationBaselineRetryTimer = void 0;
|
|
3357
|
+
this.reconcileChatNotificationBaseline(connectionGeneration);
|
|
3358
|
+
}, delay);
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
scheduleChatSessionProjection(chatSessionUid, latestSequence, notificationHint) {
|
|
3066
3362
|
const uid = chatSessionUid.trim();
|
|
3067
3363
|
if (uid === "") return;
|
|
3068
|
-
|
|
3069
|
-
|
|
3364
|
+
const current = this.pendingChatProjections.get(uid);
|
|
3365
|
+
if (latestSequence > (current?.latestSequence ?? 0)) this.projectionRetryCounts.delete(uid);
|
|
3366
|
+
const notificationHints = current === void 0 ? [] : [...current.notificationHints];
|
|
3367
|
+
if (notificationHint !== void 0 && !notificationHints.some((item) => item.hint.eventUid === notificationHint.hint.eventUid)) {
|
|
3368
|
+
notificationHints.push(notificationHint);
|
|
3369
|
+
if (notificationHints.length > 256) notificationHints.splice(0, notificationHints.length - 256);
|
|
3370
|
+
}
|
|
3371
|
+
this.pendingChatProjections.set(uid, {
|
|
3372
|
+
latestSequence: Math.max(latestSequence, current?.latestSequence ?? 0),
|
|
3373
|
+
notificationHints
|
|
3374
|
+
});
|
|
3070
3375
|
if (this.projectionTimer !== void 0 || this.projectionInFlight) return;
|
|
3071
3376
|
this.projectionTimer = setTimeout(() => {
|
|
3072
3377
|
this.projectionTimer = void 0;
|
|
@@ -3074,10 +3379,10 @@ var ChatRealtimeService = class {
|
|
|
3074
3379
|
}, 200);
|
|
3075
3380
|
}
|
|
3076
3381
|
async flushChatSessionProjections() {
|
|
3077
|
-
if (this.projectionInFlight || this.
|
|
3382
|
+
if (this.projectionInFlight || this.pendingChatProjections.size === 0) return;
|
|
3078
3383
|
this.projectionInFlight = true;
|
|
3079
|
-
const pending = [...this.
|
|
3080
|
-
for (const [uid] of pending) this.
|
|
3384
|
+
const pending = [...this.pendingChatProjections.entries()].slice(0, 50);
|
|
3385
|
+
for (const [uid] of pending) this.pendingChatProjections.delete(uid);
|
|
3081
3386
|
try {
|
|
3082
3387
|
const failed = await this.refreshChatSessionProjectionBatch(pending);
|
|
3083
3388
|
for (const [uid] of pending) if (!failed.some(([failedUid]) => failedUid === uid)) this.projectionRetryCounts.delete(uid);
|
|
@@ -3092,7 +3397,7 @@ var ChatRealtimeService = class {
|
|
|
3092
3397
|
this.requeueProjectionFailures(pending);
|
|
3093
3398
|
} finally {
|
|
3094
3399
|
this.projectionInFlight = false;
|
|
3095
|
-
if (this.
|
|
3400
|
+
if (this.pendingChatProjections.size > 0 && this.projectionTimer === void 0) {
|
|
3096
3401
|
const retryDelayBase = this.projectionFailureCount === 0 ? 200 : Math.min(5e3, 500 * 2 ** Math.min(3, this.projectionFailureCount - 1));
|
|
3097
3402
|
const retryDelay = Math.max(100, Math.round(retryDelayBase * (.8 + Math.random() * .4)));
|
|
3098
3403
|
this.projectionTimer = setTimeout(() => {
|
|
@@ -3103,7 +3408,7 @@ var ChatRealtimeService = class {
|
|
|
3103
3408
|
}
|
|
3104
3409
|
}
|
|
3105
3410
|
requeueProjectionFailures(failed) {
|
|
3106
|
-
for (const [uid,
|
|
3411
|
+
for (const [uid, projection] of failed) {
|
|
3107
3412
|
const retries = (this.projectionRetryCounts.get(uid) ?? 0) + 1;
|
|
3108
3413
|
if (retries > MAX_PROJECTION_RETRIES) {
|
|
3109
3414
|
this.projectionRetryCounts.delete(uid);
|
|
@@ -3111,9 +3416,25 @@ var ChatRealtimeService = class {
|
|
|
3111
3416
|
continue;
|
|
3112
3417
|
}
|
|
3113
3418
|
this.projectionRetryCounts.set(uid, retries);
|
|
3114
|
-
this.
|
|
3419
|
+
this.mergePendingChatProjection(uid, projection);
|
|
3115
3420
|
}
|
|
3116
3421
|
}
|
|
3422
|
+
mergePendingChatProjection(uid, incoming) {
|
|
3423
|
+
const current = this.pendingChatProjections.get(uid);
|
|
3424
|
+
if (current === void 0) {
|
|
3425
|
+
this.pendingChatProjections.set(uid, {
|
|
3426
|
+
latestSequence: incoming.latestSequence,
|
|
3427
|
+
notificationHints: [...incoming.notificationHints]
|
|
3428
|
+
});
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
const byEventUid = new Map(current.notificationHints.map((item) => [item.hint.eventUid, item]));
|
|
3432
|
+
for (const item of incoming.notificationHints) byEventUid.set(item.hint.eventUid, item);
|
|
3433
|
+
this.pendingChatProjections.set(uid, {
|
|
3434
|
+
latestSequence: Math.max(current.latestSequence, incoming.latestSequence),
|
|
3435
|
+
notificationHints: [...byEventUid.values()].slice(-256)
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3117
3438
|
async refreshChatSessionProjectionBatch(pending) {
|
|
3118
3439
|
const session = await this.runtime.requireSession();
|
|
3119
3440
|
const sessionUids = pending.map(([uid]) => uid).sort();
|
|
@@ -3130,16 +3451,19 @@ var ChatRealtimeService = class {
|
|
|
3130
3451
|
const failedUids = /* @__PURE__ */ new Set();
|
|
3131
3452
|
for (let offset = 0; offset < pending.length; offset += 3) {
|
|
3132
3453
|
const chunk = pending.slice(offset, offset + 3);
|
|
3133
|
-
(await Promise.allSettled(chunk.map(async ([uid,
|
|
3454
|
+
(await Promise.allSettled(chunk.map(async ([uid, projection]) => {
|
|
3134
3455
|
const cached = this.source.cachedChatSourceByKey(`${String(session.userId)}:${uid}`);
|
|
3135
|
-
const
|
|
3456
|
+
const firstHintSequence = projection.notificationHints.reduce((minimum, item) => Math.min(minimum, item.hint.latestSequence), projection.latestSequence);
|
|
3457
|
+
const requiredAfterSequence = Math.max(0, firstHintSequence - 1);
|
|
3458
|
+
const afterSequence = Math.max(0, Math.min(cached?.latestSequence ?? requiredAfterSequence, requiredAfterSequence));
|
|
3459
|
+
const notificationAttempt = projection.notificationHints.reduce((maximum, item) => Math.max(maximum, item.attempts), 0);
|
|
3136
3460
|
const data = await this.runtime.authenticatedChatPost("/api/v1/chat/timeline/tail", {
|
|
3137
3461
|
chat_session_uid: uid,
|
|
3138
3462
|
after_seq: afterSequence,
|
|
3139
3463
|
limit: 50
|
|
3140
3464
|
}, session, void 0, {
|
|
3141
3465
|
lane: "background-read",
|
|
3142
|
-
key: `projection:tail:${uid}:${String(afterSequence)}`
|
|
3466
|
+
key: `projection:tail:${uid}:${String(afterSequence)}:${String(notificationAttempt)}`
|
|
3143
3467
|
});
|
|
3144
3468
|
return [uid, await this.projectionReader.chatTimelineItems(data, session)];
|
|
3145
3469
|
}))).forEach((result, index) => {
|
|
@@ -3150,7 +3474,8 @@ var ChatRealtimeService = class {
|
|
|
3150
3474
|
});
|
|
3151
3475
|
}
|
|
3152
3476
|
const updates = [];
|
|
3153
|
-
|
|
3477
|
+
const notifications = [];
|
|
3478
|
+
for (const [uid, projection] of pending) {
|
|
3154
3479
|
const bundle = bundles.get(uid);
|
|
3155
3480
|
if (bundle === void 0 || failedUids.has(uid)) {
|
|
3156
3481
|
failedUids.add(uid);
|
|
@@ -3161,11 +3486,47 @@ var ChatRealtimeService = class {
|
|
|
3161
3486
|
try {
|
|
3162
3487
|
const source = await this.source.chatSourceFromBundle(bundle, session, this.source.cachedChatSourceByKey(cacheKey), timelineItems);
|
|
3163
3488
|
this.source.setChatSourceByKey(cacheKey, source);
|
|
3489
|
+
const sourceKey = source.sourceKey ?? await this.source.chatDirectorySourceKey(session.userId, uid);
|
|
3164
3490
|
updates.push({
|
|
3165
|
-
sourceKey
|
|
3491
|
+
sourceKey,
|
|
3166
3492
|
source,
|
|
3167
3493
|
timelineItems
|
|
3168
3494
|
});
|
|
3495
|
+
if (source.kind === "private_chat" || source.kind === "group_chat") for (const candidate of projection.notificationHints.sort((left, right) => left.hint.latestSequence - right.hint.latestSequence)) {
|
|
3496
|
+
const state = this.chatRealtime.state();
|
|
3497
|
+
if (candidate.connectionGeneration !== state.connectionGeneration) continue;
|
|
3498
|
+
if (this.notificationBaselineGeneration !== candidate.connectionGeneration) {
|
|
3499
|
+
this.mergePendingChatProjection(uid, {
|
|
3500
|
+
latestSequence: projection.latestSequence,
|
|
3501
|
+
notificationHints: [candidate]
|
|
3502
|
+
});
|
|
3503
|
+
continue;
|
|
3504
|
+
}
|
|
3505
|
+
const baselineSequence = this.notificationBaselineSequences.get(uid) ?? 0;
|
|
3506
|
+
if (candidate.hint.latestSequence <= baselineSequence || candidate.hint.senderUserId === session.userId || source.isMuted === true) continue;
|
|
3507
|
+
const message = timelineItems.find((item) => item.sequence === candidate.hint.latestSequence && !item.isMe);
|
|
3508
|
+
if (message === void 0) {
|
|
3509
|
+
if (candidate.attempts < 4) this.mergePendingChatProjection(uid, {
|
|
3510
|
+
latestSequence: projection.latestSequence,
|
|
3511
|
+
notificationHints: [{
|
|
3512
|
+
...candidate,
|
|
3513
|
+
attempts: candidate.attempts + 1
|
|
3514
|
+
}]
|
|
3515
|
+
});
|
|
3516
|
+
else console.warn("dsh-arkme: Chat notification message was not projected:", candidate.hint.eventUid);
|
|
3517
|
+
continue;
|
|
3518
|
+
}
|
|
3519
|
+
const preview = message.textContent.trim() || "非文本内容";
|
|
3520
|
+
notifications.push({
|
|
3521
|
+
eventUid: candidate.hint.eventUid,
|
|
3522
|
+
sourceRef: source.sourceRef,
|
|
3523
|
+
sourceKey,
|
|
3524
|
+
sourceKind: source.kind,
|
|
3525
|
+
title: source.displayName,
|
|
3526
|
+
body: source.kind === "group_chat" ? `${message.senderName}:${preview}` : preview,
|
|
3527
|
+
eventAtMillis: candidate.hint.eventAtMillis
|
|
3528
|
+
});
|
|
3529
|
+
}
|
|
3169
3530
|
} catch {
|
|
3170
3531
|
failedUids.add(uid);
|
|
3171
3532
|
}
|
|
@@ -3178,6 +3539,11 @@ var ChatRealtimeService = class {
|
|
|
3178
3539
|
updates
|
|
3179
3540
|
});
|
|
3180
3541
|
}
|
|
3542
|
+
for (const notification of notifications) this.emitChatClientEvent({
|
|
3543
|
+
type: "message-notification",
|
|
3544
|
+
revision: this.nextChatClientRevision(),
|
|
3545
|
+
notification
|
|
3546
|
+
});
|
|
3181
3547
|
return pending.filter(([uid]) => failedUids.has(uid));
|
|
3182
3548
|
}
|
|
3183
3549
|
emitChatClientEvent(event) {
|
|
@@ -3242,10 +3608,10 @@ function timelineAgentSource(relation, record, payload) {
|
|
|
3242
3608
|
label: agentSourceLabel(displayName)
|
|
3243
3609
|
};
|
|
3244
3610
|
}
|
|
3245
|
-
function encodeOpaqueJson$
|
|
3611
|
+
function encodeOpaqueJson$5(value) {
|
|
3246
3612
|
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
3247
3613
|
}
|
|
3248
|
-
function decodeOpaqueJson$
|
|
3614
|
+
function decodeOpaqueJson$5(value) {
|
|
3249
3615
|
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
3250
3616
|
}
|
|
3251
3617
|
var ChatService = class {
|
|
@@ -3872,7 +4238,7 @@ var ChatService = class {
|
|
|
3872
4238
|
};
|
|
3873
4239
|
}
|
|
3874
4240
|
sealMessageRef(userId, chatSessionUid, relationUid, signingKey) {
|
|
3875
|
-
const payload = encodeOpaqueJson$
|
|
4241
|
+
const payload = encodeOpaqueJson$5({
|
|
3876
4242
|
version: 1,
|
|
3877
4243
|
userId,
|
|
3878
4244
|
chatSessionUid,
|
|
@@ -3889,7 +4255,7 @@ var ChatService = class {
|
|
|
3889
4255
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("message-ref-invalid", "Arkme 消息引用无效", false);
|
|
3890
4256
|
let parsed;
|
|
3891
4257
|
try {
|
|
3892
|
-
parsed = objectValue$1(decodeOpaqueJson$
|
|
4258
|
+
parsed = objectValue$1(decodeOpaqueJson$5(payload));
|
|
3893
4259
|
} catch (error) {
|
|
3894
4260
|
throw new ArkmePluginError("message-ref-invalid", "Arkme 消息引用无效", false, 400, { cause: error });
|
|
3895
4261
|
}
|
|
@@ -4664,6 +5030,12 @@ function numberValue$12(value) {
|
|
|
4664
5030
|
function listValue$12(value) {
|
|
4665
5031
|
return Array.isArray(value) ? value : [];
|
|
4666
5032
|
}
|
|
5033
|
+
function encodeOpaqueJson$4(value) {
|
|
5034
|
+
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
5035
|
+
}
|
|
5036
|
+
function decodeOpaqueJson$4(value) {
|
|
5037
|
+
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
5038
|
+
}
|
|
4667
5039
|
function chatMessageDnd$1(value) {
|
|
4668
5040
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4669
5041
|
const policy = value;
|
|
@@ -4685,10 +5057,304 @@ var GroupService = class {
|
|
|
4685
5057
|
runtime;
|
|
4686
5058
|
source;
|
|
4687
5059
|
profile;
|
|
4688
|
-
|
|
5060
|
+
inviteSender;
|
|
5061
|
+
constructor(runtime, source, profile, inviteSender) {
|
|
4689
5062
|
this.runtime = runtime;
|
|
4690
5063
|
this.source = source;
|
|
4691
5064
|
this.profile = profile;
|
|
5065
|
+
this.inviteSender = inviteSender;
|
|
5066
|
+
}
|
|
5067
|
+
async groupInvitePreview(sourceRef, signal) {
|
|
5068
|
+
const session = await this.runtime.requireSession();
|
|
5069
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
5070
|
+
if (source.kind !== "group_chat") throw new ArkmePluginError("group-source-invalid", "仅支持邀请群聊协作者", false);
|
|
5071
|
+
const preview = objectValue$1((await this.runtime.authenticatedChatPost("/api/v1/chats/governance/invite-preview", { chat_session_uid: source.ownerRef }, session, signal)).preview);
|
|
5072
|
+
const inviteLink = stringValue$1(preview.invite_link).trim();
|
|
5073
|
+
if (inviteLink === "") throw new ArkmePluginError("group-invite-unavailable", "邀请链接生成失败,请重试", true);
|
|
5074
|
+
return {
|
|
5075
|
+
source: await this.source.sourceItem(source),
|
|
5076
|
+
title: stringValue$1(preview.title).trim() || source.displayName,
|
|
5077
|
+
inviterDisplayName: stringValue$1(preview.creator_display_name).trim() || "Arkme",
|
|
5078
|
+
inviteLink,
|
|
5079
|
+
expireAtMillis: numberValue$12(preview.expire_at),
|
|
5080
|
+
mode: numberValue$12(preview.join_mode) === 2 ? "approval_invite" : "direct_add"
|
|
5081
|
+
};
|
|
5082
|
+
}
|
|
5083
|
+
async listGroupMemberCandidates(sourceRef, options = {}) {
|
|
5084
|
+
const session = await this.runtime.requireSession();
|
|
5085
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
5086
|
+
if (source.kind !== "group_chat") throw new ArkmePluginError("group-source-invalid", "仅支持向群聊添加成员", false);
|
|
5087
|
+
const limit = Math.min(50, Math.max(1, Math.trunc(options.limit ?? 20)));
|
|
5088
|
+
const query = options.query?.trim().toLocaleLowerCase() ?? "";
|
|
5089
|
+
const requestedGroupSourceRefs = [...new Set((options.groupSourceRefs ?? []).map((value) => value.trim()).filter((value) => value !== ""))].slice(0, 20);
|
|
5090
|
+
const [membersData, directoryData, inviteData] = await Promise.all([
|
|
5091
|
+
this.runtime.authenticatedChatPost("/api/v1/chats/members/list", {
|
|
5092
|
+
chat_session_uid: source.ownerRef,
|
|
5093
|
+
active_only: true
|
|
5094
|
+
}, session, options.signal).catch(() => ({})),
|
|
5095
|
+
this.runtime.authenticatedChatPost("/api/v1/chats/list", { limit: 50 }, session, options.signal),
|
|
5096
|
+
this.runtime.authenticatedChatPost("/api/v1/chats/governance/invite-preview", { chat_session_uid: source.ownerRef }, session, options.signal).catch(() => ({}))
|
|
5097
|
+
]);
|
|
5098
|
+
const directoryBundles = listValue$12(directoryData.items);
|
|
5099
|
+
const privateBundles = directoryBundles.filter((raw) => {
|
|
5100
|
+
const kind = numberValue$12(objectValue$1(objectValue$1(raw).session).session_kind);
|
|
5101
|
+
return kind === 1 || kind === 3;
|
|
5102
|
+
});
|
|
5103
|
+
const privateTopicIds = privateBundles.map((raw) => {
|
|
5104
|
+
const chatSession = objectValue$1(objectValue$1(raw).session);
|
|
5105
|
+
return numberValue$12(chatSession.shared_topic_id ?? chatSession.subject_id);
|
|
5106
|
+
}).filter((value) => Number.isSafeInteger(value) && value > 0);
|
|
5107
|
+
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(() => ({}));
|
|
5108
|
+
const contactByTopicId = /* @__PURE__ */ new Map();
|
|
5109
|
+
for (const raw of listValue$12(partnerData.item_ls)) {
|
|
5110
|
+
const item = objectValue$1(raw);
|
|
5111
|
+
const topicId = numberValue$12(item.rm_subject_id ?? item.shared_topic_id);
|
|
5112
|
+
if (topicId > 0 && typeof item.is_contact === "boolean") contactByTopicId.set(topicId, item.is_contact);
|
|
5113
|
+
}
|
|
5114
|
+
const existingMemberUserIds = new Set(listValue$12(objectValue$1(membersData).items).map((item) => numberValue$12(objectValue$1(item).user_id)));
|
|
5115
|
+
const rawCandidates = /* @__PURE__ */ new Map();
|
|
5116
|
+
for (const raw of privateBundles) {
|
|
5117
|
+
const bundle = objectValue$1(raw);
|
|
5118
|
+
const chatSession = objectValue$1(bundle.session);
|
|
5119
|
+
if (numberValue$12(chatSession.session_kind) !== 1 && numberValue$12(chatSession.session_kind) !== 3) continue;
|
|
5120
|
+
const counterpart = objectValue$1(bundle.private_counterpart);
|
|
5121
|
+
const supplement = objectValue$1(bundle.private_supplement);
|
|
5122
|
+
const targetUserId = numberValue$12(counterpart.user_id);
|
|
5123
|
+
const privateChatSessionUid = stringValue$1(chatSession.chat_session_uid).trim();
|
|
5124
|
+
const displayName = stringValue$1(supplement.remark ?? supplement.counterpart_name_snapshot ?? counterpart.display_name_snapshot ?? supplement.pending_name ?? counterpart.visible_phone).trim();
|
|
5125
|
+
const privateTopicId = numberValue$12(chatSession.shared_topic_id ?? chatSession.subject_id);
|
|
5126
|
+
const contactState = numberValue$12(supplement.contact_state);
|
|
5127
|
+
const relation = contactByTopicId.get(privateTopicId) === true || contactState === 1 || contactState === 3 ? "contact" : "stranger";
|
|
5128
|
+
if (!Number.isSafeInteger(targetUserId) || targetUserId <= 0 || targetUserId === session.userId || privateChatSessionUid === "" || displayName === "" || query !== "" && !displayName.toLocaleLowerCase().includes(query)) continue;
|
|
5129
|
+
rawCandidates.set(targetUserId, {
|
|
5130
|
+
targetUserId,
|
|
5131
|
+
displayName,
|
|
5132
|
+
privateChatSessionUid,
|
|
5133
|
+
relation,
|
|
5134
|
+
origin: "private_chat"
|
|
5135
|
+
});
|
|
5136
|
+
}
|
|
5137
|
+
const selected = [...rawCandidates.entries()].slice(0, limit);
|
|
5138
|
+
const sourceByGroupUid = /* @__PURE__ */ new Map();
|
|
5139
|
+
const groupSessionUidByIndex = /* @__PURE__ */ new Map();
|
|
5140
|
+
const groups = [];
|
|
5141
|
+
for (const raw of directoryBundles) {
|
|
5142
|
+
const bundle = objectValue$1(raw);
|
|
5143
|
+
const chatSession = objectValue$1(bundle.session);
|
|
5144
|
+
if (numberValue$12(chatSession.session_kind) !== 2) continue;
|
|
5145
|
+
const groupSessionUid = stringValue$1(chatSession.chat_session_uid).trim();
|
|
5146
|
+
if (groupSessionUid === "" || groupSessionUid === source.ownerRef) continue;
|
|
5147
|
+
const displayName = stringValue$1(chatSession.title ?? chatSession.subject_title ?? objectValue$1(bundle.participant_summary).display_name).trim();
|
|
5148
|
+
const groupItem = {
|
|
5149
|
+
sourceRef: await this.source.sealSourceRef(session.userId, "group_chat", groupSessionUid, displayName || "群聊"),
|
|
5150
|
+
sourceKey: await this.source.chatDirectorySourceKey(session.userId, groupSessionUid),
|
|
5151
|
+
kind: "group_chat",
|
|
5152
|
+
displayName: displayName || "群聊",
|
|
5153
|
+
activeAtMillis: numberValue$12(chatSession.updated_at),
|
|
5154
|
+
unreadCount: 0
|
|
5155
|
+
};
|
|
5156
|
+
const groupIndex = groups.push(groupItem) - 1;
|
|
5157
|
+
groupSessionUidByIndex.set(groupIndex, groupSessionUid);
|
|
5158
|
+
sourceByGroupUid.set(groupSessionUid, groupItem);
|
|
5159
|
+
this.source.setChatSource(session.userId, groupSessionUid, groupItem);
|
|
5160
|
+
}
|
|
5161
|
+
try {
|
|
5162
|
+
await this.source.hydrateSourceAvatars(groups, /* @__PURE__ */ new Map(), groupSessionUidByIndex, session, options.signal);
|
|
5163
|
+
} catch {}
|
|
5164
|
+
const requestedGroupSources = [];
|
|
5165
|
+
for (const groupSourceRef of requestedGroupSourceRefs) {
|
|
5166
|
+
const groupSource = await this.source.openSourceRef(groupSourceRef, session.userId);
|
|
5167
|
+
if (groupSource.kind !== "group_chat" || groupSource.ownerRef === source.ownerRef) continue;
|
|
5168
|
+
requestedGroupSources.push(groupSource);
|
|
5169
|
+
}
|
|
5170
|
+
const groupRawByUid = /* @__PURE__ */ new Map();
|
|
5171
|
+
const groupTargetUserIds = /* @__PURE__ */ new Set();
|
|
5172
|
+
for (const groupSource of requestedGroupSources) {
|
|
5173
|
+
const group = sourceByGroupUid.get(groupSource.ownerRef) ?? await this.source.sourceItem(groupSource);
|
|
5174
|
+
try {
|
|
5175
|
+
const groupMembersData = await this.runtime.authenticatedChatPost("/api/v1/chats/members/list", {
|
|
5176
|
+
chat_session_uid: groupSource.ownerRef,
|
|
5177
|
+
active_only: true
|
|
5178
|
+
}, session, options.signal);
|
|
5179
|
+
const raws = [];
|
|
5180
|
+
const seenUserIds = /* @__PURE__ */ new Set();
|
|
5181
|
+
for (const rawMember of listValue$12(groupMembersData.items)) {
|
|
5182
|
+
const member = objectValue$1(rawMember);
|
|
5183
|
+
const targetUserId = numberValue$12(member.user_id);
|
|
5184
|
+
if (!Number.isSafeInteger(targetUserId) || targetUserId <= 0 || targetUserId === session.userId || seenUserIds.has(targetUserId)) continue;
|
|
5185
|
+
if (chatMemberStatus(member.status) !== "active") continue;
|
|
5186
|
+
const privateCandidate = rawCandidates.get(targetUserId);
|
|
5187
|
+
if (privateCandidate !== void 0) {
|
|
5188
|
+
if (query === "" || privateCandidate.displayName.toLocaleLowerCase().includes(query)) {
|
|
5189
|
+
raws.push(privateCandidate);
|
|
5190
|
+
groupTargetUserIds.add(targetUserId);
|
|
5191
|
+
seenUserIds.add(targetUserId);
|
|
5192
|
+
}
|
|
5193
|
+
continue;
|
|
5194
|
+
}
|
|
5195
|
+
const profileName = stringValue$1(member.profile_display_name).trim();
|
|
5196
|
+
const displayName = [
|
|
5197
|
+
stringValue$1(member.remark).trim(),
|
|
5198
|
+
stringValue$1(member.display_name_snapshot).trim(),
|
|
5199
|
+
profileName === `用户 ${String(targetUserId)}` ? "" : profileName,
|
|
5200
|
+
stringValue$1(objectValue$1(member.user_info).nick_name).trim(),
|
|
5201
|
+
String(targetUserId)
|
|
5202
|
+
].find((value) => value !== "" && value !== "成员" && value !== "群成员") ?? "群成员";
|
|
5203
|
+
if (query !== "" && !displayName.toLocaleLowerCase().includes(query)) continue;
|
|
5204
|
+
raws.push({
|
|
5205
|
+
targetUserId,
|
|
5206
|
+
displayName,
|
|
5207
|
+
relation: "group",
|
|
5208
|
+
origin: "group_chat"
|
|
5209
|
+
});
|
|
5210
|
+
groupTargetUserIds.add(targetUserId);
|
|
5211
|
+
seenUserIds.add(targetUserId);
|
|
5212
|
+
}
|
|
5213
|
+
groupRawByUid.set(groupSource.ownerRef, {
|
|
5214
|
+
group,
|
|
5215
|
+
raws: raws.slice(0, limit),
|
|
5216
|
+
total: raws.length
|
|
5217
|
+
});
|
|
5218
|
+
} catch (error) {
|
|
5219
|
+
groupRawByUid.set(groupSource.ownerRef, {
|
|
5220
|
+
group,
|
|
5221
|
+
raws: [],
|
|
5222
|
+
total: 0,
|
|
5223
|
+
error: error instanceof Error && error.message.trim() !== "" ? error.message : "群成员加载失败,请重试"
|
|
5224
|
+
});
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
const profiles = await this.profile.publicProfileSummariesByUserIds([.../* @__PURE__ */ new Set([...selected.map(([userId]) => userId), ...groupTargetUserIds])], session, options.signal).catch(() => /* @__PURE__ */ new Map());
|
|
5228
|
+
const preview = objectValue$1(objectValue$1(inviteData).preview);
|
|
5229
|
+
const approvalRequired = numberValue$12(preview.join_mode) === 2;
|
|
5230
|
+
const candidateItem = async (targetUserId, candidate) => {
|
|
5231
|
+
const alreadyMember = existingMemberUserIds.has(targetUserId);
|
|
5232
|
+
const cannotInvite = approvalRequired && candidate.privateChatSessionUid === void 0;
|
|
5233
|
+
return {
|
|
5234
|
+
candidateRef: await this.sealCandidateRef({
|
|
5235
|
+
version: 1,
|
|
5236
|
+
userId: session.userId,
|
|
5237
|
+
groupChatSessionUid: source.ownerRef,
|
|
5238
|
+
targetUserId,
|
|
5239
|
+
origin: candidate.origin,
|
|
5240
|
+
displayName: candidate.displayName,
|
|
5241
|
+
...candidate.privateChatSessionUid === void 0 ? {} : { privateChatSessionUid: candidate.privateChatSessionUid }
|
|
5242
|
+
}),
|
|
5243
|
+
displayName: candidate.displayName,
|
|
5244
|
+
...profiles.get(targetUserId)?.avatarUrl === void 0 ? {} : { avatarRef: await this.profile.sealProfileImageRef(session.userId, targetUserId) },
|
|
5245
|
+
origin: candidate.origin,
|
|
5246
|
+
relation: candidate.relation,
|
|
5247
|
+
...alreadyMember ? {
|
|
5248
|
+
disabled: true,
|
|
5249
|
+
alreadyMember: true
|
|
5250
|
+
} : {},
|
|
5251
|
+
...!alreadyMember && cannotInvite ? {
|
|
5252
|
+
disabled: true,
|
|
5253
|
+
statusText: "无法邀请"
|
|
5254
|
+
} : {}
|
|
5255
|
+
};
|
|
5256
|
+
};
|
|
5257
|
+
const items = await Promise.all(selected.map(async ([targetUserId, candidate]) => candidateItem(targetUserId, candidate)));
|
|
5258
|
+
const groupCandidates = await Promise.all([...groupRawByUid.values()].map(async (value) => ({
|
|
5259
|
+
group: value.group,
|
|
5260
|
+
items: await Promise.all(value.raws.map((raw) => candidateItem(raw.targetUserId, raw))),
|
|
5261
|
+
total: value.total,
|
|
5262
|
+
...value.error === void 0 ? {} : { error: value.error }
|
|
5263
|
+
})));
|
|
5264
|
+
return {
|
|
5265
|
+
source: await this.source.sourceItem(source),
|
|
5266
|
+
items,
|
|
5267
|
+
total: rawCandidates.size,
|
|
5268
|
+
hasMore: rawCandidates.size > items.length,
|
|
5269
|
+
mode: numberValue$12(preview.join_mode) === 2 ? "approval_invite" : "direct_add",
|
|
5270
|
+
groups,
|
|
5271
|
+
groupCandidates,
|
|
5272
|
+
contactCount: [...rawCandidates.values()].filter((item) => item.relation === "contact").length,
|
|
5273
|
+
strangerCount: [...rawCandidates.values()].filter((item) => item.relation === "stranger").length
|
|
5274
|
+
};
|
|
5275
|
+
}
|
|
5276
|
+
async addGroupMembers(sourceRef, candidateRefs, signal) {
|
|
5277
|
+
const session = await this.runtime.requireSession();
|
|
5278
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
5279
|
+
if (source.kind !== "group_chat") throw new ArkmePluginError("group-source-invalid", "仅支持向群聊添加成员", false);
|
|
5280
|
+
const refs = candidateRefs.map((value) => value.trim());
|
|
5281
|
+
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);
|
|
5282
|
+
const preview = objectValue$1(objectValue$1(await this.runtime.authenticatedChatPost("/api/v1/chats/governance/invite-preview", { chat_session_uid: source.ownerRef }, session, signal)).preview);
|
|
5283
|
+
const approvalRequired = numberValue$12(preview.join_mode) === 2;
|
|
5284
|
+
const inviteLink = stringValue$1(preview.invite_link).trim();
|
|
5285
|
+
const results = [];
|
|
5286
|
+
for (const candidateRef of refs) {
|
|
5287
|
+
let candidate;
|
|
5288
|
+
try {
|
|
5289
|
+
candidate = await this.openCandidateRef(candidateRef, session.userId, source.ownerRef);
|
|
5290
|
+
if (approvalRequired) {
|
|
5291
|
+
if (candidate.privateChatSessionUid === void 0) throw new ArkmePluginError("group-candidate-invite-unavailable", "该对象暂时无法发送群聊邀请", false);
|
|
5292
|
+
if (this.inviteSender === void 0 || inviteLink === "") throw new ArkmePluginError("group-invite-unavailable", "该群需要发送邀请,但邀请链接暂不可用", true);
|
|
5293
|
+
const privateSourceRef = await this.source.sealSourceRef(session.userId, "private_chat", candidate.privateChatSessionUid, candidate.displayName);
|
|
5294
|
+
await this.inviteSender.sendPrivateText(privateSourceRef, candidate.privateChatSessionUid, `邀请你加入群聊“${source.displayName}”:${inviteLink}`, `chat_record_${randomUUID()}`, `chat_rel_${randomUUID()}`, session, signal);
|
|
5295
|
+
results.push({
|
|
5296
|
+
candidateRef,
|
|
5297
|
+
displayName: candidate.displayName,
|
|
5298
|
+
status: "invite_sent"
|
|
5299
|
+
});
|
|
5300
|
+
continue;
|
|
5301
|
+
}
|
|
5302
|
+
const outcome = stringValue$1(objectValue$1((await this.runtime.authenticatedChatPost("/api/v1/chats/members/add", {
|
|
5303
|
+
chat_session_uid: source.ownerRef,
|
|
5304
|
+
target_user_id: candidate.targetUserId,
|
|
5305
|
+
join_at: Date.now(),
|
|
5306
|
+
display_name_snapshot: candidate.displayName,
|
|
5307
|
+
extra: { source: "dsh_arkme_candidate" }
|
|
5308
|
+
}, session, signal)).item).outcome);
|
|
5309
|
+
results.push({
|
|
5310
|
+
candidateRef,
|
|
5311
|
+
displayName: candidate.displayName,
|
|
5312
|
+
status: outcome === "idempotent_hit" ? "already_member" : outcome === "updated" ? "reactivated" : "added"
|
|
5313
|
+
});
|
|
5314
|
+
} catch (error) {
|
|
5315
|
+
results.push({
|
|
5316
|
+
candidateRef,
|
|
5317
|
+
displayName: candidate?.displayName ?? "成员",
|
|
5318
|
+
status: "failed",
|
|
5319
|
+
error: error instanceof Error && error.message.trim() !== "" ? error.message : "添加成员失败"
|
|
5320
|
+
});
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
return {
|
|
5324
|
+
source: await this.source.sourceItem(source),
|
|
5325
|
+
results,
|
|
5326
|
+
succeededCount: results.filter((item) => item.status !== "failed").length,
|
|
5327
|
+
failedCount: results.filter((item) => item.status === "failed").length
|
|
5328
|
+
};
|
|
5329
|
+
}
|
|
5330
|
+
async sealCandidateRef(payload) {
|
|
5331
|
+
const encoded = encodeOpaqueJson$4(payload);
|
|
5332
|
+
return `arkme-group-candidate-v1.${encoded}.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest("base64url")}`;
|
|
5333
|
+
}
|
|
5334
|
+
async openCandidateRef(candidateRef, expectedUserId, expectedGroupChatSessionUid) {
|
|
5335
|
+
const parts = candidateRef.split(".");
|
|
5336
|
+
if (parts.length !== 3 || parts[0] !== "arkme-group-candidate-v1") throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用无效", false);
|
|
5337
|
+
const encoded = parts[1] ?? "";
|
|
5338
|
+
const supplied = Buffer.from(parts[2] ?? "", "base64url");
|
|
5339
|
+
const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(encoded).digest();
|
|
5340
|
+
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用无效", false);
|
|
5341
|
+
let raw;
|
|
5342
|
+
try {
|
|
5343
|
+
raw = objectValue$1(decodeOpaqueJson$4(encoded));
|
|
5344
|
+
} catch (error) {
|
|
5345
|
+
throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用无效", false, 400, { cause: error });
|
|
5346
|
+
}
|
|
5347
|
+
const result = {
|
|
5348
|
+
version: 1,
|
|
5349
|
+
userId: numberValue$12(raw.userId),
|
|
5350
|
+
groupChatSessionUid: stringValue$1(raw.groupChatSessionUid).trim(),
|
|
5351
|
+
targetUserId: numberValue$12(raw.targetUserId),
|
|
5352
|
+
displayName: stringValue$1(raw.displayName).trim(),
|
|
5353
|
+
...stringValue$1(raw.privateChatSessionUid).trim() === "" ? {} : { privateChatSessionUid: stringValue$1(raw.privateChatSessionUid).trim() },
|
|
5354
|
+
origin: raw.origin === "group_chat" ? "group_chat" : "private_chat"
|
|
5355
|
+
};
|
|
5356
|
+
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);
|
|
5357
|
+
return result;
|
|
4692
5358
|
}
|
|
4693
5359
|
async listGroupMembers(sourceRef, options = {}) {
|
|
4694
5360
|
const session = await this.runtime.requireSession();
|
|
@@ -4731,7 +5397,8 @@ var GroupService = class {
|
|
|
4731
5397
|
status,
|
|
4732
5398
|
isSelf: userId === session.userId,
|
|
4733
5399
|
isOwner: role === "owner",
|
|
4734
|
-
joinedAtMillis: numberValue$12(item.join_at)
|
|
5400
|
+
joinedAtMillis: numberValue$12(item.join_at),
|
|
5401
|
+
recordCount: Math.max(0, Math.trunc(numberValue$12(objectValue$1(item.extra).record_count)))
|
|
4735
5402
|
});
|
|
4736
5403
|
}
|
|
4737
5404
|
const roleRank = (role) => role === "owner" ? 0 : role === "admin" ? 1 : role === "member" ? 2 : 3;
|
|
@@ -5174,6 +5841,33 @@ function md5Text(value) {
|
|
|
5174
5841
|
function allowedSignedImageHost$2(environment, hostname) {
|
|
5175
5842
|
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
5843
|
}
|
|
5844
|
+
function allowedSignedAudioHost(environment, hostname) {
|
|
5845
|
+
return (environment === "prod" ? ["jotmo-useraudio.oss-cn-hangzhou.aliyuncs.com"] : ["jotmo-useraudio-test.oss-cn-hangzhou.aliyuncs.com"]).includes(hostname.toLowerCase());
|
|
5846
|
+
}
|
|
5847
|
+
function trustedWorldVoiceprintAudioUrl(environment, raw) {
|
|
5848
|
+
let parsed;
|
|
5849
|
+
try {
|
|
5850
|
+
parsed = new URL(raw.trim());
|
|
5851
|
+
} catch (error) {
|
|
5852
|
+
throw new ArkmePluginError("world-voiceprint-audio-invalid", "世界声纹音频地址无效", true, 502, { cause: error });
|
|
5853
|
+
}
|
|
5854
|
+
const signature = parsed.searchParams.get("x-oss-signature")?.trim() ?? "";
|
|
5855
|
+
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);
|
|
5856
|
+
return parsed;
|
|
5857
|
+
}
|
|
5858
|
+
function worldVoiceprintFileName(mimeType) {
|
|
5859
|
+
const extension = {
|
|
5860
|
+
wav: "wav",
|
|
5861
|
+
"x-wav": "wav",
|
|
5862
|
+
mpeg: "mp3",
|
|
5863
|
+
mp4: "m4a",
|
|
5864
|
+
aac: "aac",
|
|
5865
|
+
ogg: "ogg",
|
|
5866
|
+
webm: "webm",
|
|
5867
|
+
flac: "flac"
|
|
5868
|
+
}[mimeType.split(";", 1)[0].trim().toLowerCase().replace(/^audio\//, "")];
|
|
5869
|
+
return extension === void 0 ? "世界声纹" : `世界声纹.${extension}`;
|
|
5870
|
+
}
|
|
5177
5871
|
function trustedSignedImageUrl$1(environment, raw) {
|
|
5178
5872
|
let parsed;
|
|
5179
5873
|
try {
|
|
@@ -5234,6 +5928,7 @@ var MediaService = class {
|
|
|
5234
5928
|
worldImages;
|
|
5235
5929
|
recordIdentity;
|
|
5236
5930
|
mediaRefs = /* @__PURE__ */ new Map();
|
|
5931
|
+
stableMediaRefs = /* @__PURE__ */ new Map();
|
|
5237
5932
|
imageCache = /* @__PURE__ */ new Map();
|
|
5238
5933
|
imageInFlight = /* @__PURE__ */ new Map();
|
|
5239
5934
|
imageCacheBytes = 0;
|
|
@@ -5247,6 +5942,7 @@ var MediaService = class {
|
|
|
5247
5942
|
}
|
|
5248
5943
|
dispose() {
|
|
5249
5944
|
this.mediaRefs.clear();
|
|
5945
|
+
this.stableMediaRefs.clear();
|
|
5250
5946
|
this.imageCache.clear();
|
|
5251
5947
|
this.imageInFlight.clear();
|
|
5252
5948
|
this.imageCacheBytes = 0;
|
|
@@ -5353,18 +6049,20 @@ var MediaService = class {
|
|
|
5353
6049
|
throw error;
|
|
5354
6050
|
}
|
|
5355
6051
|
}
|
|
5356
|
-
async fetchMedia(mediaRef, range) {
|
|
6052
|
+
async fetchMedia(mediaRef, range, signal) {
|
|
5357
6053
|
const session = await this.runtime.requireSession();
|
|
5358
6054
|
const descriptor = this.mediaRefs.get(mediaRef);
|
|
5359
6055
|
if (descriptor === void 0 || descriptor.viewerUserId !== session.userId || descriptor.expiresAtMillis <= Date.now()) {
|
|
5360
6056
|
this.mediaRefs.delete(mediaRef);
|
|
6057
|
+
if (descriptor?.stableKey !== void 0) this.stableMediaRefs.delete(descriptor.stableKey);
|
|
5361
6058
|
throw new ArkmePluginError("media-ref-invalid", "媒体引用已失效,请刷新对话后重试", false, 404);
|
|
5362
6059
|
}
|
|
5363
6060
|
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);
|
|
6061
|
+
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
6062
|
const response = await this.runtime.fetchImpl(url, {
|
|
5366
6063
|
headers: range === void 0 ? {} : { Range: range },
|
|
5367
|
-
redirect: "error"
|
|
6064
|
+
redirect: "error",
|
|
6065
|
+
...signal === void 0 ? {} : { signal }
|
|
5368
6066
|
});
|
|
5369
6067
|
if (!response.ok && response.status !== 206) throw new ArkmePluginError("media-fetch-failed", `媒体读取失败(${String(response.status)})`, true, 502);
|
|
5370
6068
|
return {
|
|
@@ -5372,6 +6070,17 @@ var MediaService = class {
|
|
|
5372
6070
|
descriptor
|
|
5373
6071
|
};
|
|
5374
6072
|
}
|
|
6073
|
+
issueWorldVoiceprintMediaRef(viewerUserId, input) {
|
|
6074
|
+
const remoteUrl = trustedWorldVoiceprintAudioUrl(this.runtime.config.environment, input.remoteUrl).toString();
|
|
6075
|
+
const mimeType = input.mimeType.trim() || "audio/wav";
|
|
6076
|
+
if (!mimeType.startsWith("audio/")) throw new ArkmePluginError("world-voiceprint-mime-invalid", "世界声纹音频格式无效", true, 502);
|
|
6077
|
+
return this.issueMediaRef(viewerUserId, {
|
|
6078
|
+
remoteUrl,
|
|
6079
|
+
mimeType,
|
|
6080
|
+
fileName: worldVoiceprintFileName(mimeType),
|
|
6081
|
+
size: 0
|
|
6082
|
+
});
|
|
6083
|
+
}
|
|
5375
6084
|
async readImage(imageRef, options = {}) {
|
|
5376
6085
|
const session = await this.runtime.requireSession();
|
|
5377
6086
|
const maximumBytes = imageRef.trim().startsWith("arkme-profile-image-v1.") ? MAX_ARKME_PROFILE_IMAGE_BYTES : MAX_ARKME_IMAGE_BYTES;
|
|
@@ -5434,6 +6143,11 @@ var MediaService = class {
|
|
|
5434
6143
|
}
|
|
5435
6144
|
}
|
|
5436
6145
|
async readImageUncached(session, imageRef, byteLimit, signal) {
|
|
6146
|
+
if (imageRef.trim().startsWith("arkme-media-v1.")) {
|
|
6147
|
+
const { response, descriptor } = await this.fetchMedia(imageRef.trim(), void 0, signal);
|
|
6148
|
+
if (!descriptor.mimeType.trim().toLowerCase().startsWith("image/")) throw new ArkmePluginError("image-type-unsupported", "该媒体引用不是图片", false, 415);
|
|
6149
|
+
return await this.imageBytesFromResponse(response, byteLimit);
|
|
6150
|
+
}
|
|
5437
6151
|
if (imageRef.trim().startsWith("arkme-profile-image-v1.")) {
|
|
5438
6152
|
const reference = await this.profile.openProfileImageRef(imageRef, session.userId);
|
|
5439
6153
|
if (reference.targetUserId === session.userId) {
|
|
@@ -5563,20 +6277,33 @@ var MediaService = class {
|
|
|
5563
6277
|
unavailableRecordUids
|
|
5564
6278
|
};
|
|
5565
6279
|
}
|
|
5566
|
-
|
|
6280
|
+
issueImageMediaRef(viewerUserId, descriptor, stableIdentity) {
|
|
6281
|
+
return this.issueMediaRef(viewerUserId, descriptor, stableIdentity);
|
|
6282
|
+
}
|
|
6283
|
+
issueMediaRef(viewerUserId, descriptor, stableIdentity) {
|
|
5567
6284
|
const now = Date.now();
|
|
5568
|
-
for (const [key, value] of this.mediaRefs) if (value.expiresAtMillis <= now)
|
|
6285
|
+
for (const [key, value] of this.mediaRefs) if (value.expiresAtMillis <= now) {
|
|
6286
|
+
this.mediaRefs.delete(key);
|
|
6287
|
+
if (value.stableKey !== void 0) this.stableMediaRefs.delete(value.stableKey);
|
|
6288
|
+
}
|
|
5569
6289
|
while (this.mediaRefs.size >= 2e3) {
|
|
5570
6290
|
const oldest = this.mediaRefs.keys().next().value;
|
|
5571
6291
|
if (oldest === void 0) break;
|
|
6292
|
+
const oldestDescriptor = this.mediaRefs.get(oldest);
|
|
5572
6293
|
this.mediaRefs.delete(oldest);
|
|
6294
|
+
if (oldestDescriptor?.stableKey !== void 0) this.stableMediaRefs.delete(oldestDescriptor.stableKey);
|
|
5573
6295
|
}
|
|
5574
|
-
const
|
|
6296
|
+
const stableKey = stableIdentity === void 0 ? void 0 : createHash("sha256").update(`${String(viewerUserId)}\0${stableIdentity}`).digest("base64url");
|
|
6297
|
+
const ref = (stableKey === void 0 ? void 0 : this.stableMediaRefs.get(stableKey)) ?? `arkme-media-v1.${randomUUID()}`;
|
|
6298
|
+
this.mediaRefs.delete(ref);
|
|
6299
|
+
const lifetimeMillis = stableKey === void 0 ? 30 * 6e4 : 1440 * 6e4;
|
|
5575
6300
|
this.mediaRefs.set(ref, {
|
|
5576
6301
|
...descriptor,
|
|
5577
6302
|
viewerUserId,
|
|
5578
|
-
expiresAtMillis: now +
|
|
6303
|
+
expiresAtMillis: now + lifetimeMillis,
|
|
6304
|
+
...stableKey === void 0 ? {} : { stableKey }
|
|
5579
6305
|
});
|
|
6306
|
+
if (stableKey !== void 0) this.stableMediaRefs.set(stableKey, ref);
|
|
5580
6307
|
return ref;
|
|
5581
6308
|
}
|
|
5582
6309
|
richContentBlocks(raw, viewerUserId, hydratedDisplayItems = []) {
|
|
@@ -5619,7 +6346,7 @@ var MediaService = class {
|
|
|
5619
6346
|
mimeType,
|
|
5620
6347
|
fileName,
|
|
5621
6348
|
size: Math.max(0, Math.trunc(numberValue$10(item.size)))
|
|
5622
|
-
}),
|
|
6349
|
+
}, kind === "image" && fileAssetUid !== "" ? fileAssetUid : void 0),
|
|
5623
6350
|
...fileAssetUid === "" ? {} : { fileAssetUid },
|
|
5624
6351
|
fileName,
|
|
5625
6352
|
mimeType,
|
|
@@ -5663,36 +6390,7 @@ var MediaService = class {
|
|
|
5663
6390
|
signal: controller.signal
|
|
5664
6391
|
});
|
|
5665
6392
|
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
|
-
};
|
|
6393
|
+
return await this.imageBytesFromResponse(response, byteLimit);
|
|
5696
6394
|
} catch (error) {
|
|
5697
6395
|
if (error instanceof ArkmePluginError) throw error;
|
|
5698
6396
|
if (error.name === "AbortError") throw new ArkmePluginError("image-download-timeout", "Arkme 图片读取超时或已取消", true, 504, { cause: error });
|
|
@@ -5702,6 +6400,38 @@ var MediaService = class {
|
|
|
5702
6400
|
signal?.removeEventListener("abort", abort);
|
|
5703
6401
|
}
|
|
5704
6402
|
}
|
|
6403
|
+
async imageBytesFromResponse(response, byteLimit) {
|
|
6404
|
+
const declaredLength = Number(response.headers.get("content-length") ?? 0);
|
|
6405
|
+
if (Number.isFinite(declaredLength) && declaredLength > byteLimit) throw new ArkmePluginError("image-too-large", "Arkme 图片超过读取大小限制", false, 413);
|
|
6406
|
+
if (response.body === null) throw new ArkmePluginError("image-response-empty", "Arkme 图片响应为空", true, 502);
|
|
6407
|
+
const chunks = [];
|
|
6408
|
+
let bytes = 0;
|
|
6409
|
+
const reader = response.body.getReader();
|
|
6410
|
+
while (true) {
|
|
6411
|
+
const next = await reader.read();
|
|
6412
|
+
if (next.done) break;
|
|
6413
|
+
bytes += next.value.byteLength;
|
|
6414
|
+
if (bytes > byteLimit) {
|
|
6415
|
+
await reader.cancel();
|
|
6416
|
+
throw new ArkmePluginError("image-too-large", "Arkme 图片超过读取大小限制", false, 413);
|
|
6417
|
+
}
|
|
6418
|
+
chunks.push(next.value);
|
|
6419
|
+
}
|
|
6420
|
+
if (bytes === 0) throw new ArkmePluginError("image-response-empty", "Arkme 图片响应为空", true, 502);
|
|
6421
|
+
const data = new Uint8Array(bytes);
|
|
6422
|
+
let offset = 0;
|
|
6423
|
+
for (const chunk of chunks) {
|
|
6424
|
+
data.set(chunk, offset);
|
|
6425
|
+
offset += chunk.byteLength;
|
|
6426
|
+
}
|
|
6427
|
+
const mediaType = imageMediaType(data);
|
|
6428
|
+
if (mediaType === void 0) throw new ArkmePluginError("image-type-unsupported", "Arkme 图片不是受支持的 PNG、JPEG、WebP 或 GIF", false, 415);
|
|
6429
|
+
return {
|
|
6430
|
+
mediaType,
|
|
6431
|
+
bytes,
|
|
6432
|
+
data
|
|
6433
|
+
};
|
|
6434
|
+
}
|
|
5705
6435
|
};
|
|
5706
6436
|
//#endregion
|
|
5707
6437
|
//#region src/services/outgoing-call-service.ts
|
|
@@ -7248,9 +7978,11 @@ function listValue$3(value) {
|
|
|
7248
7978
|
var SearchService = class {
|
|
7249
7979
|
runtime;
|
|
7250
7980
|
record;
|
|
7251
|
-
|
|
7981
|
+
media;
|
|
7982
|
+
constructor(runtime, record, media) {
|
|
7252
7983
|
this.runtime = runtime;
|
|
7253
7984
|
this.record = record;
|
|
7985
|
+
this.media = media;
|
|
7254
7986
|
}
|
|
7255
7987
|
async searchRecords(options) {
|
|
7256
7988
|
const query = options.query.trim();
|
|
@@ -7326,6 +8058,85 @@ var SearchService = class {
|
|
|
7326
8058
|
}, session, options.signal);
|
|
7327
8059
|
return this.recordSearchResult(data);
|
|
7328
8060
|
}
|
|
8061
|
+
/** Build the desktop image library from the mixed image/video scene. */
|
|
8062
|
+
async searchImages(options) {
|
|
8063
|
+
const session = await this.runtime.requireSession();
|
|
8064
|
+
const pageLimit = Math.min(50, Math.max(1, Math.trunc(options.limit)));
|
|
8065
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
8066
|
+
let cursor = options.cursor?.trim() ?? "";
|
|
8067
|
+
if (cursor !== "") seenCursors.add(cursor);
|
|
8068
|
+
let lastPage;
|
|
8069
|
+
for (let pageIndex = 0; pageIndex < 8; pageIndex += 1) {
|
|
8070
|
+
const page = await this.searchScene({
|
|
8071
|
+
scene: "image_video",
|
|
8072
|
+
limit: pageLimit,
|
|
8073
|
+
...cursor === "" ? {} : { cursor },
|
|
8074
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
8075
|
+
});
|
|
8076
|
+
lastPage = page;
|
|
8077
|
+
const candidates = page.items.flatMap((record) => record.media.flatMap((asset) => {
|
|
8078
|
+
const mimeType = asset.mimeType?.trim().toLowerCase() ?? "";
|
|
8079
|
+
return mimeType.startsWith("image/") || mimeType === "" && (asset.fileKind === void 0 || asset.fileKind === 1) ? [{
|
|
8080
|
+
record,
|
|
8081
|
+
asset
|
|
8082
|
+
}] : [];
|
|
8083
|
+
}));
|
|
8084
|
+
const uniqueAssetUids = [...new Set(candidates.map((candidate) => candidate.asset.fileAssetUid))];
|
|
8085
|
+
const displayItems = [];
|
|
8086
|
+
for (let offset = 0; offset < uniqueAssetUids.length; offset += 50) displayItems.push(...await this.media.queryFileAssets(uniqueAssetUids.slice(offset, offset + 50), options.signal));
|
|
8087
|
+
const displayByUid = new Map(displayItems.map((item) => [item.fileAssetUid, item]));
|
|
8088
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
8089
|
+
const items = candidates.flatMap(({ record, asset }) => {
|
|
8090
|
+
const itemIdentity = `${record.recordUid}\0${asset.fileAssetUid}`;
|
|
8091
|
+
if (emitted.has(itemIdentity)) return [];
|
|
8092
|
+
const display = displayByUid.get(asset.fileAssetUid);
|
|
8093
|
+
if (display === void 0) return [];
|
|
8094
|
+
const mimeType = (display.mimeType ?? asset.mimeType ?? "").trim().toLowerCase();
|
|
8095
|
+
if (!mimeType.startsWith("image/")) return [];
|
|
8096
|
+
const remoteUrl = display.previewUrl ?? display.downloadUrl;
|
|
8097
|
+
if (remoteUrl === void 0) return [];
|
|
8098
|
+
emitted.add(itemIdentity);
|
|
8099
|
+
const fileName = display.fileName ?? asset.fileName ?? "图片";
|
|
8100
|
+
return [{
|
|
8101
|
+
itemKey: createHash("sha256").update(itemIdentity).digest("base64url"),
|
|
8102
|
+
mediaRef: this.media.issueImageMediaRef(session.userId, {
|
|
8103
|
+
remoteUrl,
|
|
8104
|
+
mimeType: mimeType || "application/octet-stream",
|
|
8105
|
+
fileName,
|
|
8106
|
+
size: Math.max(0, Math.trunc(asset.size ?? 0))
|
|
8107
|
+
}, asset.fileAssetUid),
|
|
8108
|
+
recordUid: record.recordUid,
|
|
8109
|
+
sendAtMillis: record.sendAtMillis,
|
|
8110
|
+
fileName,
|
|
8111
|
+
mimeType: mimeType || "application/octet-stream",
|
|
8112
|
+
size: Math.max(0, Math.trunc(asset.size ?? 0)),
|
|
8113
|
+
recordTitle: record.title || record.nickname || "快记",
|
|
8114
|
+
...record.sourceTitle === void 0 ? {} : { sourceTitle: record.sourceTitle }
|
|
8115
|
+
}];
|
|
8116
|
+
});
|
|
8117
|
+
const nextCursor = page.nextCursor?.trim() ?? "";
|
|
8118
|
+
const canContinue = page.hasMore && nextCursor !== "" && !seenCursors.has(nextCursor);
|
|
8119
|
+
if (items.length > 0 || !canContinue) return {
|
|
8120
|
+
items,
|
|
8121
|
+
hasMore: canContinue,
|
|
8122
|
+
...canContinue ? { nextCursor } : {},
|
|
8123
|
+
queryGuard: page.queryGuard
|
|
8124
|
+
};
|
|
8125
|
+
if (pageIndex === 7) return {
|
|
8126
|
+
items: [],
|
|
8127
|
+
hasMore: true,
|
|
8128
|
+
nextCursor,
|
|
8129
|
+
queryGuard: page.queryGuard
|
|
8130
|
+
};
|
|
8131
|
+
seenCursors.add(nextCursor);
|
|
8132
|
+
cursor = nextCursor;
|
|
8133
|
+
}
|
|
8134
|
+
return {
|
|
8135
|
+
items: [],
|
|
8136
|
+
hasMore: false,
|
|
8137
|
+
queryGuard: lastPage?.queryGuard ?? { state: "complete" }
|
|
8138
|
+
};
|
|
8139
|
+
}
|
|
7329
8140
|
async searchRecordings(options) {
|
|
7330
8141
|
const query = options.query.trim();
|
|
7331
8142
|
if (query === "") throw new ArkmePluginError("recording-query-empty", "搜索关键词不能为空", false);
|
|
@@ -8485,6 +9296,67 @@ var WorldService = class {
|
|
|
8485
9296
|
...hasMore ? { nextOffset } : {}
|
|
8486
9297
|
};
|
|
8487
9298
|
}
|
|
9299
|
+
async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
|
|
9300
|
+
const session = await this.runtime.requireSession();
|
|
9301
|
+
const normalizedRefs = [...new Set(recordRefs.map((value) => value.trim()).filter((value) => value !== ""))].slice(0, 20);
|
|
9302
|
+
if (normalizedRefs.length === 0) return { items: [] };
|
|
9303
|
+
const entries = normalizedRefs.map((recordRef) => ({
|
|
9304
|
+
recordRef,
|
|
9305
|
+
entry: this.openWorldRecordRef(recordRef, session.userId)
|
|
9306
|
+
}));
|
|
9307
|
+
const ownerUserIds = [...new Set(entries.map((value) => value.entry.ownerUserId ?? 0).filter((userId) => Number.isSafeInteger(userId) && userId > 0))].sort((left, right) => left - right);
|
|
9308
|
+
if (ownerUserIds.length === 0) return { items: entries.map(({ recordRef }) => ({
|
|
9309
|
+
recordRef,
|
|
9310
|
+
playable: false
|
|
9311
|
+
})) };
|
|
9312
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/world-playback-availability", { user_ids: ownerUserIds }, session, signal, {
|
|
9313
|
+
key: ownerUserIds.join(","),
|
|
9314
|
+
cacheMs: 5e3
|
|
9315
|
+
});
|
|
9316
|
+
const playableByOwner = /* @__PURE__ */ new Map();
|
|
9317
|
+
for (const raw of listValue(data.items)) {
|
|
9318
|
+
const item = objectValue$1(raw);
|
|
9319
|
+
const userId = Math.trunc(numberValue(item.user_id));
|
|
9320
|
+
if (userId > 0) playableByOwner.set(userId, item.playable === true);
|
|
9321
|
+
}
|
|
9322
|
+
return { items: entries.map(({ recordRef, entry }) => ({
|
|
9323
|
+
recordRef,
|
|
9324
|
+
playable: playableByOwner.get(entry.ownerUserId ?? 0) === true
|
|
9325
|
+
})) };
|
|
9326
|
+
}
|
|
9327
|
+
async generateWorldVoiceprintPlayback(input) {
|
|
9328
|
+
const session = await this.runtime.requireSession();
|
|
9329
|
+
const entry = this.openWorldRecordRef(input.recordRef, session.userId);
|
|
9330
|
+
const chunkIndex = Math.trunc(input.chunkIndex ?? 0);
|
|
9331
|
+
if (!Number.isSafeInteger(chunkIndex) || chunkIndex < 0 || chunkIndex >= 334) throw new ArkmePluginError("world-voiceprint-playback-input-invalid", "世界声纹播放参数无效,请刷新后重试", false);
|
|
9332
|
+
const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/generate-playback", {
|
|
9333
|
+
source_scene: 4,
|
|
9334
|
+
source_id: entry.recordUid,
|
|
9335
|
+
source_chunk_index: chunkIndex
|
|
9336
|
+
}, session, input.signal, {
|
|
9337
|
+
lane: "write",
|
|
9338
|
+
bypassCache: true
|
|
9339
|
+
});
|
|
9340
|
+
const mimeType = stringValue$1(data.mime_type).trim() || "audio/wav";
|
|
9341
|
+
const responseChunkIndex = Math.trunc(numberValue(data.source_chunk_index));
|
|
9342
|
+
const chunkCount = Math.trunc(numberValue(data.source_chunk_count));
|
|
9343
|
+
const chunkStartRune = Math.trunc(numberValue(data.source_chunk_start_rune));
|
|
9344
|
+
const chunkEndRune = Math.trunc(numberValue(data.source_chunk_end_rune));
|
|
9345
|
+
if (responseChunkIndex !== chunkIndex || chunkCount <= 0 || chunkCount > 334 || chunkIndex >= chunkCount || chunkStartRune < 0 || chunkEndRune <= chunkStartRune) throw new ArkmePluginError("world-voiceprint-playback-response-invalid", "世界声纹播放响应无效,请重试", true, 502);
|
|
9346
|
+
return {
|
|
9347
|
+
mediaRef: this.media.issueWorldVoiceprintMediaRef(session.userId, {
|
|
9348
|
+
remoteUrl: stringValue$1(data.audio_url),
|
|
9349
|
+
mimeType
|
|
9350
|
+
}),
|
|
9351
|
+
mimeType,
|
|
9352
|
+
durationMillis: Math.max(0, Math.trunc(numberValue(data.duration_ms))),
|
|
9353
|
+
cacheHit: data.cache_hit === true,
|
|
9354
|
+
chunkIndex: responseChunkIndex,
|
|
9355
|
+
chunkCount,
|
|
9356
|
+
chunkStartRune,
|
|
9357
|
+
chunkEndRune
|
|
9358
|
+
};
|
|
9359
|
+
}
|
|
8488
9360
|
async listWorldInteractions(recordRef, options = {}) {
|
|
8489
9361
|
const session = await this.runtime.requireSession();
|
|
8490
9362
|
const root = this.openWorldRecordRef(recordRef, session.userId);
|
|
@@ -8695,7 +9567,7 @@ var WorldService = class {
|
|
|
8695
9567
|
}
|
|
8696
9568
|
const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl) : void 0;
|
|
8697
9569
|
return {
|
|
8698
|
-
recordRef: await this.worldRecordRef(viewerUserId, recordUid),
|
|
9570
|
+
recordRef: await this.worldRecordRef(viewerUserId, recordUid, ownerUserId),
|
|
8699
9571
|
authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
|
|
8700
9572
|
...avatarRef === void 0 ? {} : { avatarRef },
|
|
8701
9573
|
...avatarRef === void 0 && avatarFallback !== void 0 ? { avatarFallback } : {},
|
|
@@ -8778,13 +9650,16 @@ var WorldService = class {
|
|
|
8778
9650
|
voiceCount
|
|
8779
9651
|
};
|
|
8780
9652
|
}
|
|
8781
|
-
async worldRecordRef(viewerUserId, recordUid) {
|
|
9653
|
+
async worldRecordRef(viewerUserId, recordUid, ownerUserId) {
|
|
8782
9654
|
const recordRef = `arkme-world-record-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`world-record-v1:${String(viewerUserId)}:${recordUid}`).digest("base64url")}`;
|
|
8783
9655
|
const now = Date.now();
|
|
8784
9656
|
this.pruneWorldRecordRefs(now);
|
|
9657
|
+
const previous = this.worldRecordRefs.get(recordRef);
|
|
9658
|
+
const resolvedOwnerUserId = ownerUserId ?? previous?.ownerUserId;
|
|
8785
9659
|
this.worldRecordRefs.set(recordRef, {
|
|
8786
9660
|
viewerUserId,
|
|
8787
9661
|
recordUid,
|
|
9662
|
+
...resolvedOwnerUserId === void 0 || resolvedOwnerUserId <= 0 ? {} : { ownerUserId: resolvedOwnerUserId },
|
|
8788
9663
|
expiresAtMillis: now + ARKME_WORLD_RECORD_REF_TTL_MILLIS
|
|
8789
9664
|
});
|
|
8790
9665
|
return recordRef;
|
|
@@ -8877,6 +9752,7 @@ var ArkmeService = class {
|
|
|
8877
9752
|
runtime;
|
|
8878
9753
|
aiVideo;
|
|
8879
9754
|
arrangement;
|
|
9755
|
+
calendar;
|
|
8880
9756
|
wechat;
|
|
8881
9757
|
recording;
|
|
8882
9758
|
profile;
|
|
@@ -8906,6 +9782,7 @@ var ArkmeService = class {
|
|
|
8906
9782
|
this.runtime = new ServiceRuntime(config, sessionStore, stateStore, fetchImpl, pendingSessionStore);
|
|
8907
9783
|
this.aiVideo = new AiVideoService(this.runtime);
|
|
8908
9784
|
this.arrangement = new ArrangementService(this.runtime);
|
|
9785
|
+
this.calendar = new CalendarService(this.runtime);
|
|
8909
9786
|
this.wechat = new WechatService(this.runtime);
|
|
8910
9787
|
this.recording = new RecordingService(this.runtime);
|
|
8911
9788
|
this.profile = new ProfileService(this.runtime);
|
|
@@ -8918,12 +9795,14 @@ var ArkmeService = class {
|
|
|
8918
9795
|
recordItem: (raw) => this.recordItem(raw)
|
|
8919
9796
|
});
|
|
8920
9797
|
this.record = new RecordService(this.runtime, this.media, this.source);
|
|
8921
|
-
this.search = new SearchService(this.runtime, this.record);
|
|
9798
|
+
this.search = new SearchService(this.runtime, this.record, this.media);
|
|
8922
9799
|
this.bot = new BotService(this.runtime, this.source);
|
|
8923
9800
|
this.outgoingCall = new OutgoingCallService(this.runtime, this.source, this.profile, outgoingCallBroker);
|
|
8924
9801
|
this.world = new WorldService(this.runtime, { refreshProfile: async () => await this.refreshProfile() }, this.media, this.record);
|
|
8925
9802
|
this.arko = new ArkoService(this.runtime, this.profile);
|
|
8926
|
-
this.group = new GroupService(this.runtime, this.source, this.profile)
|
|
9803
|
+
this.group = new GroupService(this.runtime, this.source, this.profile, { sendPrivateText: async (sourceRef, chatSessionUid, text, recordUid, relationUid, session, signal) => {
|
|
9804
|
+
await this.chat.sendChatSourceTextRaw(sourceRef, chatSessionUid, text, recordUid, relationUid, session, void 0, void 0, signal);
|
|
9805
|
+
} });
|
|
8927
9806
|
this.relatedRecording = new RelatedRecordingService(this.runtime, this.source);
|
|
8928
9807
|
this.community = new CommunityService(this.runtime, this.source, this.profile);
|
|
8929
9808
|
this.interwoven = new InterwovenService(this.runtime, this.source, this.profile);
|
|
@@ -8964,7 +9843,10 @@ var ArkmeService = class {
|
|
|
8964
9843
|
this.realtime.handleChatRealtimeNotice(notice);
|
|
8965
9844
|
}
|
|
8966
9845
|
async refreshChatSessionProjectionBatch(pending) {
|
|
8967
|
-
return await this.realtime.refreshChatSessionProjectionBatch(pending)
|
|
9846
|
+
return (await this.realtime.refreshChatSessionProjectionBatch(pending.map(([uid, latestSequence]) => [uid, {
|
|
9847
|
+
latestSequence,
|
|
9848
|
+
notificationHints: []
|
|
9849
|
+
}]))).map(([uid, projection]) => [uid, projection.latestSequence]);
|
|
8968
9850
|
}
|
|
8969
9851
|
attachOpenClawProvisioner(provisioner) {
|
|
8970
9852
|
this.bot.attachOpenClawProvisioner(provisioner);
|
|
@@ -9023,6 +9905,8 @@ var ArkmeService = class {
|
|
|
9023
9905
|
revisionPolling: true,
|
|
9024
9906
|
userProfile: true,
|
|
9025
9907
|
imageRead: true,
|
|
9908
|
+
recordCalendar: true,
|
|
9909
|
+
imageLibrary: true,
|
|
9026
9910
|
sourceDirectory: true,
|
|
9027
9911
|
sourceTimeline: true,
|
|
9028
9912
|
sourceTextSend: true,
|
|
@@ -9031,6 +9915,7 @@ var ArkmeService = class {
|
|
|
9031
9915
|
fileUpload: this.config.richMediaSendEnabled !== false,
|
|
9032
9916
|
outgoingCall: true,
|
|
9033
9917
|
groupMembers: true,
|
|
9918
|
+
groupMemberAdd: true,
|
|
9034
9919
|
userCard: true,
|
|
9035
9920
|
openPrivateChat: true,
|
|
9036
9921
|
groupSettings: true,
|
|
@@ -9040,6 +9925,7 @@ var ArkmeService = class {
|
|
|
9040
9925
|
extensionPreviews: true,
|
|
9041
9926
|
worldFeed: true,
|
|
9042
9927
|
worldInteractions: true,
|
|
9928
|
+
worldVoiceprintPlayback: true,
|
|
9043
9929
|
arrangements: true,
|
|
9044
9930
|
myExtensions: true,
|
|
9045
9931
|
extensionPublish: true,
|
|
@@ -9246,6 +10132,15 @@ var ArkmeService = class {
|
|
|
9246
10132
|
async listGroupMembers(sourceRef, options = {}) {
|
|
9247
10133
|
return await this.group.listGroupMembers(sourceRef, options);
|
|
9248
10134
|
}
|
|
10135
|
+
async listGroupMemberCandidates(sourceRef, options = {}) {
|
|
10136
|
+
return await this.group.listGroupMemberCandidates(sourceRef, options);
|
|
10137
|
+
}
|
|
10138
|
+
async groupInvitePreview(sourceRef, signal) {
|
|
10139
|
+
return await this.group.groupInvitePreview(sourceRef, signal);
|
|
10140
|
+
}
|
|
10141
|
+
async addGroupMembers(sourceRef, candidateRefs, signal) {
|
|
10142
|
+
return await this.group.addGroupMembers(sourceRef, candidateRefs, signal);
|
|
10143
|
+
}
|
|
9249
10144
|
async groupSettings(sourceRef, signal) {
|
|
9250
10145
|
return await this.group.groupSettings(sourceRef, signal);
|
|
9251
10146
|
}
|
|
@@ -9394,6 +10289,13 @@ var ArkmeService = class {
|
|
|
9394
10289
|
async searchScene(options) {
|
|
9395
10290
|
return await this.search.searchScene(options);
|
|
9396
10291
|
}
|
|
10292
|
+
/**
|
|
10293
|
+
* Build the desktop image library from the owner's mixed image/video scene.
|
|
10294
|
+
* Signed storage URLs stay inside the Provider and are replaced by account-bound media refs.
|
|
10295
|
+
*/
|
|
10296
|
+
async searchImages(options) {
|
|
10297
|
+
return await this.search.searchImages(options);
|
|
10298
|
+
}
|
|
9397
10299
|
async searchRecordings(options) {
|
|
9398
10300
|
return await this.search.searchRecordings(options);
|
|
9399
10301
|
}
|
|
@@ -9406,6 +10308,12 @@ var ArkmeService = class {
|
|
|
9406
10308
|
async list(limit, cursor) {
|
|
9407
10309
|
return await this.record.list(limit, cursor);
|
|
9408
10310
|
}
|
|
10311
|
+
async calendarBuckets(options) {
|
|
10312
|
+
return await this.calendar.bucketPage(options);
|
|
10313
|
+
}
|
|
10314
|
+
async calendarRecords(options) {
|
|
10315
|
+
return await this.calendar.dayRecords(options);
|
|
10316
|
+
}
|
|
9409
10317
|
async listWorldRecords(options = {}) {
|
|
9410
10318
|
return await this.world.listWorldRecords(options);
|
|
9411
10319
|
}
|
|
@@ -9445,6 +10353,12 @@ var ArkmeService = class {
|
|
|
9445
10353
|
async listWorldFeed(options = {}) {
|
|
9446
10354
|
return await this.world.listWorldFeed(options);
|
|
9447
10355
|
}
|
|
10356
|
+
async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
|
|
10357
|
+
return await this.world.worldVoiceprintPlaybackAvailability(recordRefs, signal);
|
|
10358
|
+
}
|
|
10359
|
+
async generateWorldVoiceprintPlayback(input) {
|
|
10360
|
+
return await this.world.generateWorldVoiceprintPlayback(input);
|
|
10361
|
+
}
|
|
9448
10362
|
/** Read the authenticated comment/reply tree behind one account-bound World reference. */
|
|
9449
10363
|
async listWorldInteractions(recordRef, options = {}) {
|
|
9450
10364
|
return await this.world.listWorldInteractions(recordRef, options);
|
|
@@ -9495,6 +10409,8 @@ const ARKME_EXTENSION_FORMAT = "arkme-cordis-extension";
|
|
|
9495
10409
|
const ARKME_EXTENSION_MAX_BYTES = 100 * 1024 * 1024;
|
|
9496
10410
|
const ARKME_EXTENSION_ICON_MAX_BYTES = 2 * 1024 * 1024;
|
|
9497
10411
|
const ARKME_EXTENSION_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
10412
|
+
/** Browser/model-safe projection. Host filesystem paths and Dynamic Cordis IDs stay private. */
|
|
10413
|
+
const ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE = "插件运行失败,已自动停用。";
|
|
9498
10414
|
//#endregion
|
|
9499
10415
|
//#region src/extensions/artifact.ts
|
|
9500
10416
|
const TAR_BLOCK_BYTES$1 = 512;
|
|
@@ -9977,14 +10893,29 @@ function persistentClientFactory(requireModule, spec) {
|
|
|
9977
10893
|
})
|
|
9978
10894
|
});
|
|
9979
10895
|
const envelope = await response.json();
|
|
9980
|
-
if (!response.ok || envelope.ok !== true)
|
|
10896
|
+
if (!response.ok || envelope.ok !== true) {
|
|
10897
|
+
const error = new Error(envelope.error?.message ?? `Arkme Host returned HTTP ${String(response.status)}`);
|
|
10898
|
+
if (envelope.error?.code !== void 0) error.code = envelope.error.code;
|
|
10899
|
+
throw error;
|
|
10900
|
+
}
|
|
9981
10901
|
return envelope.value;
|
|
9982
10902
|
};
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
10903
|
+
let deactivateClient;
|
|
10904
|
+
const callHost = async (method, args = null) => {
|
|
10905
|
+
try {
|
|
10906
|
+
return await callOperation(spec.operation ?? "extensions.persistent.invoke", {
|
|
10907
|
+
...spec.identityKey === "packageName" ? { packageName: spec.extensionId } : {
|
|
10908
|
+
extensionId: spec.extensionId,
|
|
10909
|
+
version: spec.version
|
|
10910
|
+
},
|
|
10911
|
+
method,
|
|
10912
|
+
args: jsonValue(args)
|
|
10913
|
+
});
|
|
10914
|
+
} catch (error) {
|
|
10915
|
+
if (error.code === "extension-runtime-unavailable") deactivateClient?.();
|
|
10916
|
+
throw error;
|
|
10917
|
+
}
|
|
10918
|
+
};
|
|
9988
10919
|
const guardedService = (service, name) => new Proxy(service, { get(target, property) {
|
|
9989
10920
|
const member = Reflect.get(target, property, target);
|
|
9990
10921
|
if (typeof member !== "function") return member;
|
|
@@ -10044,8 +10975,10 @@ function persistentClientFactory(requireModule, spec) {
|
|
|
10044
10975
|
name: `arkme-extension-client:${spec.extensionId}`,
|
|
10045
10976
|
async apply(ctx) {
|
|
10046
10977
|
if (spec.identityKey !== "packageName") {
|
|
10047
|
-
|
|
10048
|
-
|
|
10978
|
+
if ((await callOperation("extensions.persistent.client-state", {
|
|
10979
|
+
extensionId: spec.extensionId,
|
|
10980
|
+
version: spec.version
|
|
10981
|
+
})).mount !== true) return;
|
|
10049
10982
|
}
|
|
10050
10983
|
const styles = new Styles();
|
|
10051
10984
|
const traps = {
|
|
@@ -10089,7 +11022,16 @@ function persistentClientFactory(requireModule, spec) {
|
|
|
10089
11022
|
}
|
|
10090
11023
|
};
|
|
10091
11024
|
ctx.effect(() => () => styles.dispose(), `arkme-extension:${spec.extensionId}:styles`);
|
|
10092
|
-
|
|
11025
|
+
const fiber = ctx.plugin(guarded);
|
|
11026
|
+
const deactivate = () => {
|
|
11027
|
+
styles.dispose();
|
|
11028
|
+
Promise.resolve(fiber.dispose?.()).catch(() => void 0);
|
|
11029
|
+
};
|
|
11030
|
+
deactivateClient = deactivate;
|
|
11031
|
+
ctx.effect(() => () => {
|
|
11032
|
+
if (deactivateClient === deactivate) deactivateClient = void 0;
|
|
11033
|
+
}, `arkme-extension:${spec.extensionId}:client-fiber`);
|
|
11034
|
+
await fiber;
|
|
10093
11035
|
}
|
|
10094
11036
|
};
|
|
10095
11037
|
}
|
|
@@ -10125,18 +11067,18 @@ function writeSecure(path, content) {
|
|
|
10125
11067
|
});
|
|
10126
11068
|
chmodSync(path, 384);
|
|
10127
11069
|
}
|
|
10128
|
-
function activationText(extensionId, enabled) {
|
|
11070
|
+
function activationText(extensionId, enabled, quarantine) {
|
|
10129
11071
|
return `${JSON.stringify({
|
|
10130
11072
|
schema_version: 1,
|
|
10131
11073
|
extension_id: extensionId,
|
|
10132
|
-
enabled
|
|
11074
|
+
enabled,
|
|
11075
|
+
...quarantine === void 0 ? {} : { quarantine }
|
|
10133
11076
|
}, void 0, 2)}\n`;
|
|
10134
11077
|
}
|
|
10135
|
-
function
|
|
10136
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(extensionId)) throw new Error("扩展身份无效");
|
|
11078
|
+
function writeActivationState(bundleDirectory, state) {
|
|
10137
11079
|
const target = join(bundleDirectory, "activation.json");
|
|
10138
11080
|
const temporary = join(bundleDirectory, `.activation.${randomUUID()}.tmp`);
|
|
10139
|
-
writeSecure(temporary, activationText(
|
|
11081
|
+
writeSecure(temporary, activationText(state.extension_id, state.enabled, state.quarantine));
|
|
10140
11082
|
try {
|
|
10141
11083
|
renameSync(temporary, target);
|
|
10142
11084
|
chmodSync(target, 384);
|
|
@@ -10145,6 +11087,27 @@ function writePersistentExtensionActivation(bundleDirectory, extensionId, enable
|
|
|
10145
11087
|
}
|
|
10146
11088
|
return target;
|
|
10147
11089
|
}
|
|
11090
|
+
function writePersistentExtensionActivation(bundleDirectory, extensionId, enabled) {
|
|
11091
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(extensionId)) throw new Error("扩展身份无效");
|
|
11092
|
+
return writeActivationState(bundleDirectory, {
|
|
11093
|
+
schema_version: 1,
|
|
11094
|
+
extension_id: extensionId,
|
|
11095
|
+
enabled
|
|
11096
|
+
});
|
|
11097
|
+
}
|
|
11098
|
+
function quarantinePersistentExtension(bundleDirectory, extensionId, error, failedAtMillis = Date.now()) {
|
|
11099
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(extensionId)) throw new Error("扩展身份无效");
|
|
11100
|
+
return writeActivationState(bundleDirectory, {
|
|
11101
|
+
schema_version: 1,
|
|
11102
|
+
extension_id: extensionId,
|
|
11103
|
+
enabled: false,
|
|
11104
|
+
quarantine: {
|
|
11105
|
+
code: "runtime-load-failed",
|
|
11106
|
+
failed_at_millis: failedAtMillis,
|
|
11107
|
+
message: ((error instanceof Error ? error.message : String(error)).trim() || "persistent extension failed to load").slice(0, 2e3)
|
|
11108
|
+
}
|
|
11109
|
+
});
|
|
11110
|
+
}
|
|
10148
11111
|
function readPersistentExtensionActivation(installationUrl) {
|
|
10149
11112
|
const activationUrl = new URL("./activation.json", installationUrl);
|
|
10150
11113
|
let value;
|
|
@@ -10164,6 +11127,10 @@ function readPersistentExtensionActivation(installationUrl) {
|
|
|
10164
11127
|
if (value === null || typeof value !== "object") throw new Error("Arkme persistent extension activation state is invalid");
|
|
10165
11128
|
const state = value;
|
|
10166
11129
|
if (state.schema_version !== 1 || typeof state.extension_id !== "string" || typeof state.enabled !== "boolean") throw new Error("Arkme persistent extension activation state is invalid");
|
|
11130
|
+
if (state.quarantine !== void 0) {
|
|
11131
|
+
const quarantine = state.quarantine;
|
|
11132
|
+
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");
|
|
11133
|
+
}
|
|
10167
11134
|
return state;
|
|
10168
11135
|
}
|
|
10169
11136
|
function materializePersistentExtensionBundle(input) {
|
|
@@ -10192,7 +11159,8 @@ function materializePersistentExtensionBundle(input) {
|
|
|
10192
11159
|
if (existing !== void 0) if (existing.artifact_sha256 === installation.artifact_sha256 && existing.version === installation.version) {
|
|
10193
11160
|
try {
|
|
10194
11161
|
const manifest = JSON.parse(readFileSync(join(bundleDirectory, "package.json"), "utf8"));
|
|
10195
|
-
const
|
|
11162
|
+
const clientBundle = input.clientCode === void 0 ? void 0 : readFileSync(join(bundleDirectory, "lib", "client.js"), "utf8");
|
|
11163
|
+
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
11164
|
if (manifest.exports?.["."] === "./lib/index.js" && manifest.exports?.["./package.json"] === "./package.json" && clientReady) {
|
|
10197
11165
|
if (!existsSync(activationPath)) writeSecure(activationPath, activationText(input.resolution.extension_id, true));
|
|
10198
11166
|
return {
|
|
@@ -10453,8 +11421,22 @@ async function mountPersistentArkmeHostExtension(installation, registration) {
|
|
|
10453
11421
|
throw error;
|
|
10454
11422
|
}
|
|
10455
11423
|
}
|
|
11424
|
+
function quarantineRuntimeFailure(installation, installationUrl, error) {
|
|
11425
|
+
try {
|
|
11426
|
+
quarantinePersistentExtension(dirname(fileURLToPath(installationUrl)), installation.extension_id, error);
|
|
11427
|
+
} catch (quarantineError) {
|
|
11428
|
+
console.error(`[arkme-extension:${installation.extension_id}] failed to persist quarantine state`, quarantineError);
|
|
11429
|
+
}
|
|
11430
|
+
console.error(`[arkme-extension:${installation.extension_id}] disabled after runtime load failure`, error);
|
|
11431
|
+
}
|
|
10456
11432
|
async function applyPersistentArkmeHostExtension(ctx, installationUrl) {
|
|
10457
|
-
|
|
11433
|
+
let installation;
|
|
11434
|
+
try {
|
|
11435
|
+
installation = readInstallation(installationUrl);
|
|
11436
|
+
} catch (error) {
|
|
11437
|
+
console.error("[arkme-extension:unknown] ignored invalid persistent installation metadata", error);
|
|
11438
|
+
return;
|
|
11439
|
+
}
|
|
10458
11440
|
const previous = persistentRegistrations.get(installation.extension_id);
|
|
10459
11441
|
if (previous?.ctx === ctx && previous.installationUrl.href === installationUrl.href) return;
|
|
10460
11442
|
if (previous !== void 0) await deactivatePersistentArkmeExtension(installation.extension_id);
|
|
@@ -10463,27 +11445,55 @@ async function applyPersistentArkmeHostExtension(ctx, installationUrl) {
|
|
|
10463
11445
|
installationUrl
|
|
10464
11446
|
};
|
|
10465
11447
|
persistentRegistrations.set(installation.extension_id, registration);
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
11448
|
+
try {
|
|
11449
|
+
ctx.effect(() => () => {
|
|
11450
|
+
if (persistentRegistrations.get(installation.extension_id) !== registration) return;
|
|
11451
|
+
persistentRegistrations.delete(installation.extension_id);
|
|
11452
|
+
persistentHandlers.delete(installation.extension_id);
|
|
11453
|
+
}, `arkme-extension:${installation.extension_id}:registration`);
|
|
11454
|
+
ctx.effect(() => () => {
|
|
11455
|
+
if (persistentHandlers.get(installation.extension_id) === registration.handlers) persistentHandlers.delete(installation.extension_id);
|
|
11456
|
+
}, `arkme-extension:${installation.extension_id}:handlers`);
|
|
11457
|
+
const activation = readPersistentExtensionActivation(installationUrl);
|
|
11458
|
+
if (activation.extension_id !== installation.extension_id) throw new Error("Arkme persistent extension activation identity mismatch");
|
|
11459
|
+
if (!activation.enabled) return;
|
|
11460
|
+
await mountPersistentArkmeHostExtension(installation, registration);
|
|
11461
|
+
} catch (error) {
|
|
11462
|
+
if (persistentRegistrations.get(installation.extension_id) === registration) {
|
|
11463
|
+
persistentRegistrations.delete(installation.extension_id);
|
|
11464
|
+
if (persistentHandlers.get(installation.extension_id) === registration.handlers) persistentHandlers.delete(installation.extension_id);
|
|
11465
|
+
if (persistentClientActivations.get(installation.extension_id) === registration.clientActivation) persistentClientActivations.delete(installation.extension_id);
|
|
11466
|
+
}
|
|
11467
|
+
quarantineRuntimeFailure(installation, installationUrl, error);
|
|
11468
|
+
}
|
|
10478
11469
|
}
|
|
10479
11470
|
function persistentArkmeExtensionActive(extensionId) {
|
|
10480
11471
|
return persistentRegistrations.get(extensionId)?.fiber !== void 0 || persistentClientActivations.has(extensionId);
|
|
10481
11472
|
}
|
|
11473
|
+
function persistentArkmeExtensionRuntimeState(extensionId) {
|
|
11474
|
+
const registration = persistentRegistrations.get(extensionId);
|
|
11475
|
+
if (registration === void 0) return void 0;
|
|
11476
|
+
try {
|
|
11477
|
+
return {
|
|
11478
|
+
version: readInstallation(registration.installationUrl).version,
|
|
11479
|
+
installationUrl: registration.installationUrl.href,
|
|
11480
|
+
active: registration.fiber !== void 0 || registration.clientActivation !== void 0
|
|
11481
|
+
};
|
|
11482
|
+
} catch {
|
|
11483
|
+
return;
|
|
11484
|
+
}
|
|
11485
|
+
}
|
|
10482
11486
|
/** Re-mount a verified Host half when its wrapper is already present in the current DSH process. */
|
|
10483
11487
|
async function activatePersistentArkmeExtension(extensionId) {
|
|
10484
11488
|
const registration = persistentRegistrations.get(extensionId);
|
|
10485
11489
|
if (registration === void 0) return false;
|
|
10486
|
-
|
|
11490
|
+
const installation = readInstallation(registration.installationUrl);
|
|
11491
|
+
try {
|
|
11492
|
+
return await mountPersistentArkmeHostExtension(installation, registration);
|
|
11493
|
+
} catch (error) {
|
|
11494
|
+
quarantineRuntimeFailure(installation, registration.installationUrl, error);
|
|
11495
|
+
throw error;
|
|
11496
|
+
}
|
|
10487
11497
|
}
|
|
10488
11498
|
async function deactivatePersistentArkmeExtension(extensionId) {
|
|
10489
11499
|
const registration = persistentRegistrations.get(extensionId);
|
|
@@ -10503,4 +11513,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
|
|
|
10503
11513
|
return jsonValue(await handler(jsonValue(args)));
|
|
10504
11514
|
}
|
|
10505
11515
|
//#endregion
|
|
10506
|
-
export {
|
|
11516
|
+
export { ARKME_EXTENSION_MAX_BYTES as A, renderArkmeSandboxHostEntry as C, unpackArkmeExtension as D, sha256Hex as E, ArkmePluginError as F, ArkmeOutgoingCallError as I, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as M, ArkmeService as N, ARKME_EXTENSION_FORMAT as O, ARKME_PROVIDER_CONTRACT_VERSION as P, readLocalBundleTarball as S, canonicalExtensionJson as T, bundleSha256 as _, persistentArkmeExtensionActive as a, packBundleFiles as b, readPersistentExtensionActivation as c, verifyBundleResolutionSignature as d, verifyExtensionResolutionSignature as f, arkmeSandboxEntryId as g, ArkmeBundleArtifactError as h, invokePersistentArkmeExtension as i, ARKME_EXTENSION_PREVIEW_MAX_BYTES as j, ARKME_EXTENSION_ICON_MAX_BYTES as k, writePersistentExtensionActivation as l, ARKME_BUNDLE_MAX_BYTES as m, applyPersistentArkmeHostExtension as n, persistentArkmeExtensionRuntimeState as o, ARKME_BUNDLE_ARTIFACT_KIND as p, deactivatePersistentArkmeExtension as r, materializePersistentExtensionBundle as s, activatePersistentArkmeExtension as t, renderArkmeBundleClientBundle as u, canonicalBundleJson as v, assertExtensionArtifactSize as w, packLocalBundleDirectory as x, inspectBundleArtifact as y };
|