@senguoyun/dsh-arkme 0.1.45 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/direct-message-refusal.md +39 -0
- package/docs/plugin-release.md +28 -3
- package/lib/client.js +1719 -562
- package/lib/client.js.map +1 -1
- package/lib/harness-model-client.js +4922 -0
- package/lib/index.js +1605 -110
- package/lib/persistent-extension.js +1 -1
- package/lib/{persistent-runtime-CEGGFpR7.js → persistent-runtime-DpNP9UKr.js} +1152 -155
- package/lib/sdk.js +25 -2
- package/lib/types/arkme-service.d.ts +10 -1
- package/lib/types/arkme-service.d.ts.map +1 -1
- package/lib/types/chat-pin-projection.d.ts +4 -0
- package/lib/types/chat-pin-projection.d.ts.map +1 -0
- package/lib/types/chat-realtime.d.ts +13 -1
- package/lib/types/chat-realtime.d.ts.map +1 -1
- package/lib/types/client/ArkmeAttachmentStrip.d.ts +2 -1
- package/lib/types/client/ArkmeAttachmentStrip.d.ts.map +1 -1
- package/lib/types/client/ArkmeBillingSettings.d.ts +7 -1
- package/lib/types/client/ArkmeBillingSettings.d.ts.map +1 -1
- package/lib/types/client/ArkmeMarkdownComposerInput.d.ts.map +1 -1
- package/lib/types/client/ArkmeModelSelect.d.ts +20 -0
- package/lib/types/client/ArkmeModelSelect.d.ts.map +1 -0
- package/lib/types/client/ArkmeNoteDetails.d.ts +3 -1
- package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
- package/lib/types/client/ArkmeRichContent.d.ts +3 -1
- package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
- package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
- package/lib/types/client/chat-directory-store.d.ts +7 -1
- package/lib/types/client/chat-directory-store.d.ts.map +1 -1
- package/lib/types/client/conversation-memory-cache.d.ts +3 -0
- package/lib/types/client/conversation-memory-cache.d.ts.map +1 -1
- package/lib/types/client/conversation-timeline-refresh.d.ts +5 -0
- package/lib/types/client/conversation-timeline-refresh.d.ts.map +1 -0
- package/lib/types/client/direct-message-admission.d.ts +15 -0
- package/lib/types/client/direct-message-admission.d.ts.map +1 -0
- package/lib/types/client/harness-model-client.d.ts +15 -0
- package/lib/types/client/harness-model-client.d.ts.map +1 -0
- package/lib/types/client/realtime-client-events.d.ts.map +1 -1
- package/lib/types/client/record-reedit-editors.d.ts +38 -0
- package/lib/types/client/record-reedit-editors.d.ts.map +1 -0
- package/lib/types/client/record-reedit-submissions.d.ts +12 -0
- package/lib/types/client/record-reedit-submissions.d.ts.map +1 -0
- package/lib/types/client/timeline-media.d.ts +4 -0
- package/lib/types/client/timeline-media.d.ts.map +1 -0
- package/lib/types/client/ui-controller.d.ts.map +1 -1
- package/lib/types/direct-message-admission.d.ts +17 -0
- package/lib/types/direct-message-admission.d.ts.map +1 -0
- package/lib/types/file-transfer-contract.d.ts +2 -0
- package/lib/types/file-transfer-contract.d.ts.map +1 -1
- package/lib/types/file-transfer-owner.d.ts.map +1 -1
- package/lib/types/harness-embed-contract.d.ts +1 -0
- package/lib/types/harness-embed-contract.d.ts.map +1 -1
- package/lib/types/harness-embed-route.d.ts +2 -1
- package/lib/types/harness-embed-route.d.ts.map +1 -1
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/local-database.d.ts +7 -2
- package/lib/types/local-database.d.ts.map +1 -1
- package/lib/types/managed-ai/adapter.d.ts +4 -1
- package/lib/types/managed-ai/adapter.d.ts.map +1 -1
- package/lib/types/managed-ai/image-preparer.d.ts +19 -0
- package/lib/types/managed-ai/image-preparer.d.ts.map +1 -0
- package/lib/types/managed-ai/multipart-upload.d.ts +8 -0
- package/lib/types/managed-ai/multipart-upload.d.ts.map +1 -0
- package/lib/types/managed-ai/transport.d.ts +59 -0
- package/lib/types/managed-ai/transport.d.ts.map +1 -0
- package/lib/types/record-reedit-contract.d.ts +84 -1
- package/lib/types/record-reedit-contract.d.ts.map +1 -1
- package/lib/types/rich-media-routes.d.ts.map +1 -1
- package/lib/types/sdk/index.d.ts +6 -1
- package/lib/types/sdk/index.d.ts.map +1 -1
- package/lib/types/services/chat-realtime-service.d.ts +4 -3
- package/lib/types/services/chat-realtime-service.d.ts.map +1 -1
- package/lib/types/services/chat-service.d.ts.map +1 -1
- package/lib/types/services/direct-message-admission-service.d.ts +21 -0
- package/lib/types/services/direct-message-admission-service.d.ts.map +1 -0
- package/lib/types/services/file-transfers.d.ts +8 -1
- package/lib/types/services/file-transfers.d.ts.map +1 -1
- package/lib/types/services/media-service.d.ts +2 -0
- package/lib/types/services/media-service.d.ts.map +1 -1
- package/lib/types/services/message-action-infrastructure.d.ts.map +1 -1
- package/lib/types/services/record-reedit-attachments.d.ts +30 -0
- package/lib/types/services/record-reedit-attachments.d.ts.map +1 -0
- package/lib/types/services/record-reedit-submissions.d.ts +37 -0
- package/lib/types/services/record-reedit-submissions.d.ts.map +1 -0
- package/lib/types/services/record-service.d.ts +30 -2
- package/lib/types/services/record-service.d.ts.map +1 -1
- package/lib/types/services/recording-forward-gateway.d.ts.map +1 -1
- package/lib/types/services/service.d.ts +12 -2
- package/lib/types/services/service.d.ts.map +1 -1
- package/lib/types/services/source-service.d.ts +0 -1
- package/lib/types/services/source-service.d.ts.map +1 -1
- package/lib/types/state-store.d.ts +8 -2
- package/lib/types/state-store.d.ts.map +1 -1
- package/lib/types/tools/business/conversation/direct-message-refusal.d.ts +3 -0
- package/lib/types/tools/business/conversation/direct-message-refusal.d.ts.map +1 -0
- package/lib/types/tools/business/index.d.ts.map +1 -1
- package/lib/types/tools/business/records/reedit.d.ts.map +1 -1
- package/lib/types/tools/ports/index.d.ts +2 -1
- package/lib/types/tools/ports/index.d.ts.map +1 -1
- package/lib/types/tools/registry/registrar.d.ts.map +1 -1
- package/lib/types/types.d.ts +24 -2
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +6 -6
|
@@ -48,6 +48,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
48
48
|
enumerable: true
|
|
49
49
|
}) : target, mod));
|
|
50
50
|
//#endregion
|
|
51
|
+
//#region src/direct-message-admission.ts
|
|
52
|
+
function directMessageAdmissionMessage(admission) {
|
|
53
|
+
switch (admission.state) {
|
|
54
|
+
case "allowed": return "";
|
|
55
|
+
case "refused_by_self": return "你已拒收对方的消息";
|
|
56
|
+
case "refused_by_counterpart": return "对方已拒收你的消息";
|
|
57
|
+
case "mutually_refused": return "你们已互相拒收消息";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
51
61
|
//#region src/request-coordinator.ts
|
|
52
62
|
const DEFAULT_LANE_LIMITS = {
|
|
53
63
|
auth: {
|
|
@@ -846,6 +856,14 @@ var ArkmePluginError = class extends Error {
|
|
|
846
856
|
if (options?.writeOutcomeUnknown === true) this.writeOutcomeUnknown = true;
|
|
847
857
|
}
|
|
848
858
|
};
|
|
859
|
+
/** Opaque upstream body, for the owning business adapter only. Never serialize this error wholesale. */
|
|
860
|
+
var ArkmeUpstreamResponseError = class extends ArkmePluginError {
|
|
861
|
+
responseData;
|
|
862
|
+
constructor(code, message, retryable, httpStatus, responseData) {
|
|
863
|
+
super(code, message, retryable, httpStatus);
|
|
864
|
+
this.responseData = responseData;
|
|
865
|
+
}
|
|
866
|
+
};
|
|
849
867
|
function remoteWriteOutcomeUnknown(error) {
|
|
850
868
|
if ([
|
|
851
869
|
"arkme-network-error",
|
|
@@ -1110,7 +1128,7 @@ var ServiceRuntime = class {
|
|
|
1110
1128
|
const errorData = objectValue$2(envelope.data);
|
|
1111
1129
|
const serviceErrorCode = preferDataError ? stringValue$2(errorData.error_code).trim() : "";
|
|
1112
1130
|
const serviceMessage = preferDataError ? stringValue$2(errorData.message).trim() : "";
|
|
1113
|
-
throw new
|
|
1131
|
+
throw new ArkmeUpstreamResponseError(serviceErrorCode || `arkme-code-${envelope.code}`, serviceMessage || envelope.message?.trim() || "Arkme 服务请求失败", serviceErrorCode === "" ? envelope.code !== 1004 && envelope.code >= 500 : serviceErrorCode === "ai_comic_video_rate_limited", 502, envelope.data);
|
|
1114
1132
|
}
|
|
1115
1133
|
return envelope.data ?? {};
|
|
1116
1134
|
} catch (error) {
|
|
@@ -1417,6 +1435,102 @@ var ServiceRuntime = class {
|
|
|
1417
1435
|
}
|
|
1418
1436
|
};
|
|
1419
1437
|
//#endregion
|
|
1438
|
+
//#region src/services/direct-message-admission-service.ts
|
|
1439
|
+
function projectDirectMessageAdmission(value, chatSessionUid) {
|
|
1440
|
+
const data = objectValue$2(value);
|
|
1441
|
+
const validStatus = (value) => value === 1 || value === 2;
|
|
1442
|
+
const validRevision = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1443
|
+
if (chatSessionUid === "" || data.chat_session_uid !== chatSessionUid || !validStatus(data.own_refusal_status) || !validStatus(data.counterpart_refusal_status) || !validRevision(data.own_revision) || !validRevision(data.counterpart_revision) || data.own_refusal_status === 1 && data.own_revision === 0 || data.counterpart_refusal_status === 1 && data.counterpart_revision === 0 || typeof data.refusal_creation_enabled !== "boolean") throw new ArkmePluginError("arkme-response-invalid", "私聊发送状态无效,请刷新后重试", false, 502);
|
|
1444
|
+
const ownRefused = data.own_refusal_status === 1;
|
|
1445
|
+
const counterpartRefused = data.counterpart_refusal_status === 1;
|
|
1446
|
+
const state = ownRefused ? counterpartRefused ? 4 : 2 : counterpartRefused ? 3 : 1;
|
|
1447
|
+
if (data.admission_state !== state || data.can_send !== (state === 1)) throw new ArkmePluginError("arkme-response-invalid", "私聊发送状态不一致,请刷新后重试", false, 502);
|
|
1448
|
+
return {
|
|
1449
|
+
state: [
|
|
1450
|
+
"allowed",
|
|
1451
|
+
"refused_by_self",
|
|
1452
|
+
"refused_by_counterpart",
|
|
1453
|
+
"mutually_refused"
|
|
1454
|
+
][state - 1],
|
|
1455
|
+
canSend: state === 1,
|
|
1456
|
+
ownRefused,
|
|
1457
|
+
counterpartRefused,
|
|
1458
|
+
ownRevision: data.own_revision,
|
|
1459
|
+
counterpartRevision: data.counterpart_revision,
|
|
1460
|
+
refusalCreationEnabled: data.refusal_creation_enabled
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
var ArkmeDirectMessageAdmissionError = class extends ArkmePluginError {
|
|
1464
|
+
admission;
|
|
1465
|
+
constructor(code, admission, message) {
|
|
1466
|
+
super(code, message ?? (code === "arkme-code-2002" ? "拒收状态已变化,请确认最新状态后重试" : directMessageAdmissionMessage(admission)), false, 409);
|
|
1467
|
+
this.admission = admission;
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
function admissionResponseError(error, chatSessionUid, expectedCode) {
|
|
1471
|
+
if (!(error instanceof ArkmeUpstreamResponseError) || error.code !== expectedCode) return error;
|
|
1472
|
+
try {
|
|
1473
|
+
const projection = projectDirectMessageAdmission(error.responseData, chatSessionUid);
|
|
1474
|
+
if (expectedCode === "arkme-code-1004" && projection.canSend) return error;
|
|
1475
|
+
return new ArkmeDirectMessageAdmissionError(error.code, projection);
|
|
1476
|
+
} catch {
|
|
1477
|
+
return error;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
/** Only new receiver-visible Chat messages use this adapter, never reads, reedit or historical mirroring. */
|
|
1481
|
+
async function postChatMessageCreation(runtime, path, body, session, signal, options) {
|
|
1482
|
+
try {
|
|
1483
|
+
return await runtime.authenticatedChatPost(path, body, session, signal, ...options === void 0 ? [] : [options]);
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
throw admissionResponseError(error, String(body.chat_session_uid ?? ""), "arkme-code-1004");
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
/** Chat remains the fact and eligibility owner; source references enforce account binding on the Host. */
|
|
1489
|
+
var DirectMessageAdmissionService = class {
|
|
1490
|
+
runtime;
|
|
1491
|
+
source;
|
|
1492
|
+
constructor(runtime, source) {
|
|
1493
|
+
this.runtime = runtime;
|
|
1494
|
+
this.source = source;
|
|
1495
|
+
}
|
|
1496
|
+
async context(sourceRef) {
|
|
1497
|
+
const session = await this.runtime.requireSession();
|
|
1498
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
1499
|
+
if (source.kind !== "private_chat") throw new ArkmePluginError("source-invalid", "仅真人私聊支持拒收消息", false);
|
|
1500
|
+
return {
|
|
1501
|
+
session,
|
|
1502
|
+
source
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
async directMessageAdmission(sourceRef, signal) {
|
|
1506
|
+
const { session, source } = await this.context(sourceRef);
|
|
1507
|
+
return projectDirectMessageAdmission(await this.runtime.authenticatedChatPost("/api/v1/chats/direct-message-admission/query", { chat_session_uid: source.ownerRef }, session, signal, {
|
|
1508
|
+
lane: "interactive-read",
|
|
1509
|
+
bypassCache: true
|
|
1510
|
+
}), source.ownerRef);
|
|
1511
|
+
}
|
|
1512
|
+
async setDirectMessageRefusal(sourceRef, refused, expectedRevision, signal) {
|
|
1513
|
+
if (typeof refused !== "boolean" || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0) throw new ArkmePluginError("request-invalid", "拒收设置参数无效", false);
|
|
1514
|
+
const { session, source } = await this.context(sourceRef);
|
|
1515
|
+
try {
|
|
1516
|
+
return projectDirectMessageAdmission(await this.runtime.authenticatedChatPost("/api/v1/chats/direct-message-refusal/set", {
|
|
1517
|
+
chat_session_uid: source.ownerRef,
|
|
1518
|
+
status: refused ? 1 : 2,
|
|
1519
|
+
expected_revision: expectedRevision
|
|
1520
|
+
}, session, signal), source.ownerRef);
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
if (error instanceof ArkmeUpstreamResponseError && error.code === "arkme-code-1004") {
|
|
1523
|
+
let projection;
|
|
1524
|
+
try {
|
|
1525
|
+
projection = projectDirectMessageAdmission(error.responseData, source.ownerRef);
|
|
1526
|
+
} catch {}
|
|
1527
|
+
if (projection?.refusalCreationEnabled === false) throw new ArkmeDirectMessageAdmissionError(error.code, projection, "新增拒收暂未开放,已有拒收仍可解除");
|
|
1528
|
+
}
|
|
1529
|
+
throw admissionResponseError(error, source.ownerRef, "arkme-code-2002");
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
//#endregion
|
|
1420
1534
|
//#region src/services/recording-forward-gateway.ts
|
|
1421
1535
|
/** Deliver Audio selectors through each destination owner's native forward contract. */
|
|
1422
1536
|
var OwnerRecordingForwardGateway = class {
|
|
@@ -1442,7 +1556,7 @@ var OwnerRecordingForwardGateway = class {
|
|
|
1442
1556
|
transcript_source: item.transcriptSource
|
|
1443
1557
|
}));
|
|
1444
1558
|
if (target.kind === "private_chat" || target.kind === "group_chat") {
|
|
1445
|
-
const data = await this.runtime
|
|
1559
|
+
const data = await postChatMessageCreation(this.runtime, "/api/v1/chats/records/forward", {
|
|
1446
1560
|
chat_session_uid: target.ownerRef,
|
|
1447
1561
|
client_request_id: input.requestId,
|
|
1448
1562
|
source_items: [{
|
|
@@ -32844,6 +32958,17 @@ function projectArkmeChatAttentionFromMuted(rawUnreadCount, isMuted) {
|
|
|
32844
32958
|
return projectArkmeChatAttention(rawUnreadCount, void 0, isMuted);
|
|
32845
32959
|
}
|
|
32846
32960
|
//#endregion
|
|
32961
|
+
//#region src/chat-pin-projection.ts
|
|
32962
|
+
/** Policy freshness is independent of message sequence and event delivery order. */
|
|
32963
|
+
function retainNewerArkmeChatPin(current, incoming) {
|
|
32964
|
+
if (incoming.kind !== "private_chat" && incoming.kind !== "group_chat" || current?.kind !== incoming.kind || current.isPinned === void 0 || current.chatPolicyUpdatedAtMillis === void 0 || current.chatPolicyUpdatedAtMillis <= (incoming.chatPolicyUpdatedAtMillis ?? 0)) return incoming;
|
|
32965
|
+
return {
|
|
32966
|
+
...incoming,
|
|
32967
|
+
isPinned: current.isPinned,
|
|
32968
|
+
chatPolicyUpdatedAtMillis: current.chatPolicyUpdatedAtMillis
|
|
32969
|
+
};
|
|
32970
|
+
}
|
|
32971
|
+
//#endregion
|
|
32847
32972
|
//#region src/arkme-emoji-text.ts
|
|
32848
32973
|
/** Asset-free emoji catalog shared by Browser UI and Host notification text. */
|
|
32849
32974
|
const arkmeDefaultEmojiSeeds = Object.freeze([
|
|
@@ -33498,7 +33623,8 @@ var SourceService = class {
|
|
|
33498
33623
|
};
|
|
33499
33624
|
}
|
|
33500
33625
|
storeChatSourceByKey(cacheKey, source) {
|
|
33501
|
-
this.chatSourceCache.
|
|
33626
|
+
const projected = retainNewerArkmeChatPin(this.chatSourceCache.get(cacheKey), source);
|
|
33627
|
+
this.chatSourceCache.set(cacheKey, cloneSourceItem(this.projectChatSourceAttention(projected)));
|
|
33502
33628
|
}
|
|
33503
33629
|
/**
|
|
33504
33630
|
* Server-owned tray summary. `summary` covers every visible conversation;
|
|
@@ -34026,7 +34152,8 @@ var SourceService = class {
|
|
|
34026
34152
|
if (existing !== void 0) return cloneSourceList(await existing);
|
|
34027
34153
|
const pending = this.listSourcesUncached(session, directory, {
|
|
34028
34154
|
...options,
|
|
34029
|
-
...cursor === "" ? {} : { cursor }
|
|
34155
|
+
...cursor === "" ? {} : { cursor },
|
|
34156
|
+
isCurrent: () => this.sourceListInFlight.get(cacheKey) === pending
|
|
34030
34157
|
}, limit);
|
|
34031
34158
|
this.sourceListInFlight.set(cacheKey, pending);
|
|
34032
34159
|
try {
|
|
@@ -34048,33 +34175,42 @@ var SourceService = class {
|
|
|
34048
34175
|
const session = await this.runtime.requireSession();
|
|
34049
34176
|
const source = await this.openSourceRef(sourceRef, session.userId);
|
|
34050
34177
|
if (source.kind !== "private_chat" && source.kind !== "group_chat") throw new ArkmePluginError("chat-directory-policy-invalid", "仅支持更新私聊或群聊的会话列表状态", false);
|
|
34051
|
-
|
|
34178
|
+
if (signal?.aborted === true) throw new DOMException("The operation was aborted", "AbortError");
|
|
34052
34179
|
const current = await this.runtime.authenticatedChatPost("/api/v1/chats/policy/get", { chat_session_uid: source.ownerRef }, session, signal);
|
|
34053
|
-
|
|
34054
|
-
|
|
34180
|
+
if ([
|
|
34181
|
+
"show_in_home_state",
|
|
34182
|
+
"privacy_state",
|
|
34183
|
+
"mute_state",
|
|
34184
|
+
"pin_state",
|
|
34185
|
+
"notify_state",
|
|
34186
|
+
"status"
|
|
34187
|
+
].some((field) => !Number.isSafeInteger(current[field]) || Number(current[field]) <= 0)) throw new ArkmePluginError("chat-pin-policy-invalid", "会话设置读取不完整,请重试", true, 502);
|
|
34188
|
+
const updated = await this.runtime.authenticatedChatPost("/api/v1/chats/policy/update", {
|
|
34055
34189
|
chat_session_uid: source.ownerRef,
|
|
34056
|
-
show_in_home_state:
|
|
34057
|
-
privacy_state:
|
|
34058
|
-
mute_state:
|
|
34190
|
+
show_in_home_state: current.show_in_home_state,
|
|
34191
|
+
privacy_state: current.privacy_state,
|
|
34192
|
+
mute_state: current.mute_state,
|
|
34059
34193
|
pin_state: pinned ? 2 : 1,
|
|
34060
|
-
notify_state:
|
|
34061
|
-
status:
|
|
34062
|
-
update_at:
|
|
34063
|
-
}, session, signal)
|
|
34064
|
-
|
|
34065
|
-
|
|
34066
|
-
|
|
34067
|
-
}, session, signal)]);
|
|
34194
|
+
notify_state: current.notify_state,
|
|
34195
|
+
status: current.status,
|
|
34196
|
+
update_at: Date.now()
|
|
34197
|
+
}, session, signal);
|
|
34198
|
+
if (updated.chat_session_uid !== source.ownerRef || updated.pin_state !== 1 && updated.pin_state !== 2 || !Number.isSafeInteger(updated.update_at) || Number(updated.update_at) <= 0) throw new ArkmePluginError("chat-pin-result-invalid", "无法确认置顶结果,请刷新后重试", true, 502);
|
|
34199
|
+
const effectivePinned = updated.pin_state === 2;
|
|
34200
|
+
const policyUpdatedAtMillis = Number(updated.update_at);
|
|
34068
34201
|
const cacheKey = `${String(session.userId)}:${source.ownerRef}`;
|
|
34069
34202
|
const cached = this.chatSourceCache.get(cacheKey);
|
|
34070
34203
|
if (cached !== void 0) this.storeChatSourceByKey(cacheKey, {
|
|
34071
34204
|
...cached,
|
|
34072
|
-
isPinned:
|
|
34205
|
+
isPinned: effectivePinned,
|
|
34206
|
+
chatPolicyUpdatedAtMillis: policyUpdatedAtMillis
|
|
34073
34207
|
});
|
|
34074
|
-
this.
|
|
34208
|
+
this.invalidateSourceListCache(session.userId, "root");
|
|
34209
|
+
if (effectivePinned !== pinned) throw new ArkmePluginError("chat-pin-conflict", "会话置顶状态已变化,请刷新后重试", true, 409);
|
|
34075
34210
|
return {
|
|
34076
34211
|
sourceRef,
|
|
34077
|
-
pinned
|
|
34212
|
+
pinned: effectivePinned,
|
|
34213
|
+
policyUpdatedAtMillis
|
|
34078
34214
|
};
|
|
34079
34215
|
}
|
|
34080
34216
|
async chatConversationListPreferenceEntry(sourceRef, signal) {
|
|
@@ -34113,16 +34249,6 @@ var SourceService = class {
|
|
|
34113
34249
|
}
|
|
34114
34250
|
};
|
|
34115
34251
|
}
|
|
34116
|
-
async resolveChatPinTarget(source, session, signal) {
|
|
34117
|
-
if (source.sidebarSubjectUid !== void 0 && source.sidebarSubjectUid.trim() !== "") return { subjectUid: source.sidebarSubjectUid.trim() };
|
|
34118
|
-
const target = arkmeChatDirectoryMetadataFromBundle(await this.runtime.authenticatedChatPost("/api/v1/chats/detail", { chat_session_uid: source.ownerRef }, session, signal, {
|
|
34119
|
-
lane: "interactive-read",
|
|
34120
|
-
key: `chat-sidebar-target:${source.ownerRef}`,
|
|
34121
|
-
failureCooldownMs: 2e3
|
|
34122
|
-
}), source.kind === "group_chat" ? source.ownerRef : "");
|
|
34123
|
-
if (target.subjectUid === void 0) throw new ArkmePluginError("chat-sidebar-target-unavailable", "未能定位该会话的跨端侧边栏数据,请刷新后重试", true, 502);
|
|
34124
|
-
return { subjectUid: target.subjectUid };
|
|
34125
|
-
}
|
|
34126
34252
|
async listGroupSources(options = {}) {
|
|
34127
34253
|
const session = await this.runtime.requireSession();
|
|
34128
34254
|
const limit = Math.min(50, Math.max(1, Math.trunc(options.limit ?? 30)));
|
|
@@ -34315,13 +34441,14 @@ var SourceService = class {
|
|
|
34315
34441
|
const botGroupTarget = kind === "group_chat" ? arkmeGroupBotBindingTargetFromBundle(bundle) : void 0;
|
|
34316
34442
|
const cached = this.chatSourceCache.get(`${String(session.userId)}:${uid}`);
|
|
34317
34443
|
const chatDirectoryMetadata = arkmeChatDirectoryMetadataFromBundle(bundle, kind === "group_chat" ? uid : "");
|
|
34318
|
-
const item = {
|
|
34444
|
+
const item = retainNewerArkmeChatPin(cached, {
|
|
34319
34445
|
sourceRef: await this.sealSourceRef(session.userId, kind, uid, displayName, {
|
|
34320
34446
|
...botGroupTarget === void 0 ? {} : { botGroupTarget },
|
|
34321
34447
|
chatDirectoryMetadata
|
|
34322
34448
|
}),
|
|
34323
34449
|
sourceKey: await this.chatDirectorySourceKey(session.userId, uid),
|
|
34324
34450
|
kind,
|
|
34451
|
+
directMessageAdmissionApplicable: sessionKind === 1 && numberValue$26(counterpart.user_id) > 0,
|
|
34325
34452
|
displayName,
|
|
34326
34453
|
...kind === "private_chat" && cached?.avatarRef !== void 0 ? { avatarRef: cached.avatarRef } : {},
|
|
34327
34454
|
...kind === "group_chat" && cached?.avatarRefs !== void 0 ? { avatarRefs: [...cached.avatarRefs] } : {},
|
|
@@ -34331,8 +34458,9 @@ var SourceService = class {
|
|
|
34331
34458
|
...attention,
|
|
34332
34459
|
...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
|
|
34333
34460
|
isPinned,
|
|
34461
|
+
chatPolicyUpdatedAtMillis: numberValue$26(currentPolicy.update_at),
|
|
34334
34462
|
...numberValue$26(unread.session_last_seq ?? chatSession.last_seq) > 0 ? { latestSequence: numberValue$26(unread.session_last_seq ?? chatSession.last_seq) } : {}
|
|
34335
|
-
};
|
|
34463
|
+
});
|
|
34336
34464
|
const itemIndex = items.push(item) - 1;
|
|
34337
34465
|
chatSessionUidByIndex.set(itemIndex, uid);
|
|
34338
34466
|
if (kind === "private_chat") {
|
|
@@ -34348,7 +34476,7 @@ var SourceService = class {
|
|
|
34348
34476
|
} catch (error) {
|
|
34349
34477
|
console.warn("dsh-arkme: Chat avatar hydration failed:", safeFailureMessage$6(error));
|
|
34350
34478
|
}
|
|
34351
|
-
for (const [index, uid] of chatSessionUidByIndex) {
|
|
34479
|
+
if (options.isCurrent?.() !== false) for (const [index, uid] of chatSessionUidByIndex) {
|
|
34352
34480
|
const item = items[index];
|
|
34353
34481
|
if (item !== void 0) this.storeChatSourceByKey(`${String(session.userId)}:${uid}`, item);
|
|
34354
34482
|
}
|
|
@@ -34365,6 +34493,7 @@ var SourceService = class {
|
|
|
34365
34493
|
};
|
|
34366
34494
|
}
|
|
34367
34495
|
invalidateSourceListCache(userId, directory) {
|
|
34496
|
+
if (directory === void 0 || directory === "root") this.runtime.invalidateKey(this.runtime.requestScope(userId), "directory:root:");
|
|
34368
34497
|
const prefix = `${String(userId)}:`;
|
|
34369
34498
|
const matches = (key) => key.startsWith(prefix) && (directory === void 0 || key.startsWith(`${prefix}${directory}:`));
|
|
34370
34499
|
for (const key of this.sourceListCache.keys()) if (matches(key)) this.sourceListCache.delete(key);
|
|
@@ -34607,13 +34736,14 @@ var SourceService = class {
|
|
|
34607
34736
|
const hasUnreadMention = kind !== "group_chat" ? void 0 : unreadCount <= 0 ? false : backendMentionState === void 0 && !latestMentionsViewer ? cached?.hasUnreadMention : backendMentionState === true || latestMentionsViewer;
|
|
34608
34737
|
const botGroupTarget = kind === "group_chat" ? arkmeGroupBotBindingTargetFromBundle(bundle) : void 0;
|
|
34609
34738
|
const chatDirectoryMetadata = arkmeChatDirectoryMetadataFromBundle(bundle, kind === "group_chat" ? uid : "");
|
|
34610
|
-
return {
|
|
34739
|
+
return retainNewerArkmeChatPin(cached, {
|
|
34611
34740
|
sourceRef: await this.sealSourceRef(session.userId, kind, uid, displayName, {
|
|
34612
34741
|
...botGroupTarget === void 0 ? {} : { botGroupTarget },
|
|
34613
34742
|
chatDirectoryMetadata
|
|
34614
34743
|
}),
|
|
34615
34744
|
sourceKey: await this.chatDirectorySourceKey(session.userId, uid),
|
|
34616
34745
|
kind,
|
|
34746
|
+
directMessageAdmissionApplicable: sessionKind === 1 && numberValue$26(counterpart.user_id) > 0,
|
|
34617
34747
|
displayName,
|
|
34618
34748
|
...cached?.avatarRef === void 0 ? {} : { avatarRef: cached.avatarRef },
|
|
34619
34749
|
...cached?.avatarRefs === void 0 ? {} : { avatarRefs: cached.avatarRefs },
|
|
@@ -34623,8 +34753,9 @@ var SourceService = class {
|
|
|
34623
34753
|
...attention,
|
|
34624
34754
|
...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
|
|
34625
34755
|
isPinned,
|
|
34756
|
+
chatPolicyUpdatedAtMillis: numberValue$26(currentPolicy.update_at),
|
|
34626
34757
|
...latestSequence > 0 ? { latestSequence } : {}
|
|
34627
|
-
};
|
|
34758
|
+
});
|
|
34628
34759
|
}
|
|
34629
34760
|
encodeCursor(value) {
|
|
34630
34761
|
return `arkme-cursor-v1.${encodeOpaqueJson$7(value)}`;
|
|
@@ -36618,6 +36749,41 @@ function decodeArkmeChatReadCursorAdvancedDataLine(line) {
|
|
|
36618
36749
|
eventAtMillis
|
|
36619
36750
|
};
|
|
36620
36751
|
}
|
|
36752
|
+
const POLICY_UPDATED_FORBIDDEN_FIELDS = [
|
|
36753
|
+
"rel_uid",
|
|
36754
|
+
"latest_seq",
|
|
36755
|
+
"sender_user_id",
|
|
36756
|
+
"read_seq",
|
|
36757
|
+
"read_at",
|
|
36758
|
+
"unread_count",
|
|
36759
|
+
"record",
|
|
36760
|
+
"record_uid",
|
|
36761
|
+
"record_body",
|
|
36762
|
+
"text_content",
|
|
36763
|
+
"payload",
|
|
36764
|
+
"receiver_user_ids"
|
|
36765
|
+
];
|
|
36766
|
+
/** Policy events invalidate a directory projection; Chat list remains the source of truth. */
|
|
36767
|
+
function decodeArkmeChatPolicyUpdatedDataLine(line) {
|
|
36768
|
+
const source = decodeDataLine(line);
|
|
36769
|
+
if (source === void 0 || positiveInteger(source.t) !== 19 || POLICY_UPDATED_FORBIDDEN_FIELDS.some((field) => Object.hasOwn(source, field))) return void 0;
|
|
36770
|
+
const eventUid = nonEmptyString(source.event_uid);
|
|
36771
|
+
const chatSessionUid = nonEmptyString(source.chat_session_uid);
|
|
36772
|
+
const userId = positiveInteger(source.user_id);
|
|
36773
|
+
const pinState = positiveInteger(source.pin_state);
|
|
36774
|
+
const policyUpdateAtMillis = positiveInteger(source.policy_update_at);
|
|
36775
|
+
const eventAtMillis = positiveInteger(source.event_at);
|
|
36776
|
+
const sourceClientId = source.source_client_id === void 0 ? 0 : nonNegativeInteger(source.source_client_id);
|
|
36777
|
+
if (eventUid === void 0 || chatSessionUid === void 0 || userId === void 0 || pinState !== 1 && pinState !== 2 || policyUpdateAtMillis === void 0 || eventAtMillis === void 0 || sourceClientId === void 0) return void 0;
|
|
36778
|
+
return {
|
|
36779
|
+
eventUid,
|
|
36780
|
+
chatSessionUid,
|
|
36781
|
+
userId,
|
|
36782
|
+
pinState,
|
|
36783
|
+
policyUpdateAtMillis,
|
|
36784
|
+
eventAtMillis
|
|
36785
|
+
};
|
|
36786
|
+
}
|
|
36621
36787
|
const TIMELINE_CHANGED_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
|
|
36622
36788
|
"t",
|
|
36623
36789
|
"event_uid",
|
|
@@ -37075,6 +37241,8 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
37075
37241
|
}
|
|
37076
37242
|
acceptLine(line, connectionUserId, connectionSignal, connectionStartedAtMillis) {
|
|
37077
37243
|
if (connectionSignal.aborted) return;
|
|
37244
|
+
const policyUpdated = decodeArkmeChatPolicyUpdatedDataLine(line);
|
|
37245
|
+
if (policyUpdated !== void 0 && policyUpdated.userId !== connectionUserId) return;
|
|
37078
37246
|
const memberEvent = decodeArkmeMemberEventDataLine(line);
|
|
37079
37247
|
const conversationListPreferenceUpdated = decodeArkmeConversationListPreferenceUpdatedDataLine(line);
|
|
37080
37248
|
const projectionInvalidation = conversationListPreferenceUpdated === void 0 ? decodeArkmeProjectionInvalidatedDataLine(line) : void 0;
|
|
@@ -37082,15 +37250,20 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
37082
37250
|
const timelineChanged = conversationListPreferenceUpdated === void 0 && projectionInvalidation === void 0 && readCursorAdvanced === void 0 ? decodeArkmeChatTimelineChangedDataLine(line) : void 0;
|
|
37083
37251
|
const messagePreparing = decodeArkmeChatMessagePreparingDataLine(line);
|
|
37084
37252
|
const hint = conversationListPreferenceUpdated === void 0 && projectionInvalidation === void 0 && readCursorAdvanced === void 0 && timelineChanged === void 0 ? decodeArkmeChatReceiveDataLine(line) : void 0;
|
|
37085
|
-
const eventUid = memberEvent?.eventUid ?? conversationListPreferenceUpdated?.eventUid ?? projectionInvalidation?.eventUid ?? readCursorAdvanced?.eventUid ?? timelineChanged?.eventUid ?? messagePreparing?.eventUid ?? hint?.eventUid;
|
|
37253
|
+
const eventUid = policyUpdated?.eventUid ?? memberEvent?.eventUid ?? conversationListPreferenceUpdated?.eventUid ?? projectionInvalidation?.eventUid ?? readCursorAdvanced?.eventUid ?? timelineChanged?.eventUid ?? messagePreparing?.eventUid ?? hint?.eventUid;
|
|
37086
37254
|
if (eventUid === void 0 || this.seenEventUids.has(eventUid)) return;
|
|
37087
37255
|
this.seenEventUids.add(eventUid);
|
|
37088
37256
|
if (this.seenEventUids.size > MAX_SEEN_EVENTS) {
|
|
37089
37257
|
const oldest = this.seenEventUids.values().next().value;
|
|
37090
37258
|
if (oldest !== void 0) this.seenEventUids.delete(oldest);
|
|
37091
37259
|
}
|
|
37092
|
-
this.lastEventAtMillis = memberEvent?.eventAtMillis ?? conversationListPreferenceUpdated?.acceptedAtMillis ?? projectionInvalidation?.eventAtMillis ?? readCursorAdvanced?.eventAtMillis ?? timelineChanged?.eventAtMillis ?? messagePreparing?.eventAtMillis ?? hint?.eventAtMillis;
|
|
37093
|
-
if (
|
|
37260
|
+
this.lastEventAtMillis = policyUpdated?.eventAtMillis ?? memberEvent?.eventAtMillis ?? conversationListPreferenceUpdated?.acceptedAtMillis ?? projectionInvalidation?.eventAtMillis ?? readCursorAdvanced?.eventAtMillis ?? timelineChanged?.eventAtMillis ?? messagePreparing?.eventAtMillis ?? hint?.eventAtMillis;
|
|
37261
|
+
if (policyUpdated !== void 0) this.advanceRevision("chat-policy-invalidation", {
|
|
37262
|
+
policyUpdated,
|
|
37263
|
+
connectionUserId,
|
|
37264
|
+
connectionSignal
|
|
37265
|
+
});
|
|
37266
|
+
else if (memberEvent !== void 0) this.advanceRevision("chat-hint", { memberEvent });
|
|
37094
37267
|
else if (conversationListPreferenceUpdated !== void 0) this.advanceRevision("conversation-list-preference-invalidation", { conversationListPreferenceUpdated });
|
|
37095
37268
|
else if (projectionInvalidation !== void 0) this.advanceRevision("projection-invalidation", { projectionInvalidation });
|
|
37096
37269
|
else if (timelineChanged !== void 0) this.advanceRevision("chat-hint", {
|
|
@@ -37114,7 +37287,7 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
37114
37287
|
});
|
|
37115
37288
|
}
|
|
37116
37289
|
advanceRevision(cause, evidence = {}) {
|
|
37117
|
-
const { hint, readCursorAdvanced, projectionInvalidation, timelineChanged, messagePreparing, connectionUserId, connectionSignal, connectionStartedAtMillis, conversationListPreferenceUpdated, memberEvent } = evidence;
|
|
37290
|
+
const { hint, readCursorAdvanced, policyUpdated, projectionInvalidation, timelineChanged, messagePreparing, connectionUserId, connectionSignal, connectionStartedAtMillis, conversationListPreferenceUpdated, memberEvent } = evidence;
|
|
37118
37291
|
this.revision += 1;
|
|
37119
37292
|
const notice = {
|
|
37120
37293
|
state: this.state(),
|
|
@@ -37125,6 +37298,7 @@ var ArkmeChatRealtimeRuntime = class {
|
|
|
37125
37298
|
...connectionUserId === void 0 ? {} : { connectionUserId },
|
|
37126
37299
|
...connectionSignal === void 0 ? {} : { connectionSignal },
|
|
37127
37300
|
...connectionStartedAtMillis === void 0 ? {} : { connectionStartedAtMillis },
|
|
37301
|
+
...policyUpdated === void 0 ? {} : { policyUpdated },
|
|
37128
37302
|
...hint === void 0 ? {} : { hint },
|
|
37129
37303
|
...readCursorAdvanced === void 0 ? {} : { readCursorAdvanced },
|
|
37130
37304
|
...projectionInvalidation === void 0 ? {} : { projectionInvalidation },
|
|
@@ -37199,8 +37373,8 @@ var ChatRealtimeService = class {
|
|
|
37199
37373
|
notificationBaselineUserId;
|
|
37200
37374
|
notificationBaselineOwnerGeneration = 0;
|
|
37201
37375
|
notificationBaselineSequences = /* @__PURE__ */ new Map();
|
|
37202
|
-
|
|
37203
|
-
|
|
37376
|
+
connectionBaselineRetryTimer;
|
|
37377
|
+
connectionBaselineRetryCount = 0;
|
|
37204
37378
|
chatClientRevision = 0;
|
|
37205
37379
|
attentionSummaryVersion = 0;
|
|
37206
37380
|
attentionSummaryFingerprint = "";
|
|
@@ -37243,11 +37417,11 @@ var ChatRealtimeService = class {
|
|
|
37243
37417
|
dispose() {
|
|
37244
37418
|
this.disposed = true;
|
|
37245
37419
|
if (this.projectionTimer !== void 0) clearTimeout(this.projectionTimer);
|
|
37246
|
-
if (this.
|
|
37420
|
+
if (this.connectionBaselineRetryTimer !== void 0) clearTimeout(this.connectionBaselineRetryTimer);
|
|
37247
37421
|
if (this.attentionRetryTimer !== void 0) clearTimeout(this.attentionRetryTimer);
|
|
37248
37422
|
this.projectionTimer = void 0;
|
|
37249
37423
|
this.projectionTimerDueAtMillis = 0;
|
|
37250
|
-
this.
|
|
37424
|
+
this.connectionBaselineRetryTimer = void 0;
|
|
37251
37425
|
this.attentionRetryTimer = void 0;
|
|
37252
37426
|
this.pendingChatProjections.clear();
|
|
37253
37427
|
this.projectionRetryCounts.clear();
|
|
@@ -37267,11 +37441,11 @@ var ChatRealtimeService = class {
|
|
|
37267
37441
|
unsubscribe();
|
|
37268
37442
|
stop();
|
|
37269
37443
|
if (this.projectionTimer !== void 0) clearTimeout(this.projectionTimer);
|
|
37270
|
-
if (this.
|
|
37444
|
+
if (this.connectionBaselineRetryTimer !== void 0) clearTimeout(this.connectionBaselineRetryTimer);
|
|
37271
37445
|
if (this.attentionRetryTimer !== void 0) clearTimeout(this.attentionRetryTimer);
|
|
37272
37446
|
this.projectionTimer = void 0;
|
|
37273
37447
|
this.projectionTimerDueAtMillis = 0;
|
|
37274
|
-
this.
|
|
37448
|
+
this.connectionBaselineRetryTimer = void 0;
|
|
37275
37449
|
this.attentionRetryTimer = void 0;
|
|
37276
37450
|
this.pendingChatProjections.clear();
|
|
37277
37451
|
this.projectionRetryCounts.clear();
|
|
@@ -37302,6 +37476,10 @@ var ChatRealtimeService = class {
|
|
|
37302
37476
|
};
|
|
37303
37477
|
}
|
|
37304
37478
|
handleChatRealtimeNotice(notice) {
|
|
37479
|
+
if (notice.cause === "chat-policy-invalidation" && notice.policyUpdated !== void 0) {
|
|
37480
|
+
this.invalidateChatPolicyForCurrentSession(notice);
|
|
37481
|
+
return;
|
|
37482
|
+
}
|
|
37305
37483
|
if (notice.cause === "chat-hint" && notice.memberEvent !== void 0) {
|
|
37306
37484
|
this.handleMemberEvent(notice.memberEvent);
|
|
37307
37485
|
return;
|
|
@@ -37313,9 +37491,9 @@ var ChatRealtimeService = class {
|
|
|
37313
37491
|
this.notificationBaselineUserId = void 0;
|
|
37314
37492
|
this.notificationBaselineOwnerGeneration = 0;
|
|
37315
37493
|
this.notificationBaselineSequences.clear();
|
|
37316
|
-
this.
|
|
37317
|
-
if (this.
|
|
37318
|
-
this.
|
|
37494
|
+
this.connectionBaselineRetryCount = 0;
|
|
37495
|
+
if (this.connectionBaselineRetryTimer !== void 0) clearTimeout(this.connectionBaselineRetryTimer);
|
|
37496
|
+
this.connectionBaselineRetryTimer = void 0;
|
|
37319
37497
|
this.emitChatClientEvent({
|
|
37320
37498
|
type: "reconcile",
|
|
37321
37499
|
revision: this.nextChatClientRevision(),
|
|
@@ -37323,11 +37501,19 @@ var ChatRealtimeService = class {
|
|
|
37323
37501
|
connectionGeneration: generation,
|
|
37324
37502
|
refresh: "none"
|
|
37325
37503
|
});
|
|
37326
|
-
this.
|
|
37504
|
+
this.reconcileChatConnectionBaseline(generation, notice.connectionUserId);
|
|
37327
37505
|
this.refreshAttentionSummary();
|
|
37328
37506
|
this.invalidateRecordProjection();
|
|
37329
37507
|
return;
|
|
37330
37508
|
}
|
|
37509
|
+
if (notice.cause === "projection-invalidation" && notice.projectionInvalidation?.projection === "chat.direct_message_admission") {
|
|
37510
|
+
this.emitChatClientEvent({
|
|
37511
|
+
type: "projection-invalidated",
|
|
37512
|
+
projection: "chat.direct_message_admission",
|
|
37513
|
+
revision: this.nextChatClientRevision()
|
|
37514
|
+
});
|
|
37515
|
+
return;
|
|
37516
|
+
}
|
|
37331
37517
|
if (notice.cause === "projection-invalidation" && notice.projectionInvalidation?.projection === "record") {
|
|
37332
37518
|
this.invalidateRecordProjection();
|
|
37333
37519
|
return;
|
|
@@ -37619,9 +37805,9 @@ var ChatRealtimeService = class {
|
|
|
37619
37805
|
this.notificationBaselineGeneration = 0;
|
|
37620
37806
|
this.notificationBaselineUserId = void 0;
|
|
37621
37807
|
this.notificationBaselineOwnerGeneration = 0;
|
|
37622
|
-
this.
|
|
37623
|
-
if (this.
|
|
37624
|
-
this.
|
|
37808
|
+
this.connectionBaselineRetryCount = 0;
|
|
37809
|
+
if (this.connectionBaselineRetryTimer !== void 0) clearTimeout(this.connectionBaselineRetryTimer);
|
|
37810
|
+
this.connectionBaselineRetryTimer = void 0;
|
|
37625
37811
|
const resetVersion = Math.max(Date.now(), this.attentionSummaryVersion + 1);
|
|
37626
37812
|
this.emitChatClientEvent({
|
|
37627
37813
|
type: "attention-summary",
|
|
@@ -37639,6 +37825,21 @@ var ChatRealtimeService = class {
|
|
|
37639
37825
|
this.attentionSummaryFingerprint = "";
|
|
37640
37826
|
this.latestAttentionSummary = void 0;
|
|
37641
37827
|
}
|
|
37828
|
+
async invalidateChatPolicyForCurrentSession(notice) {
|
|
37829
|
+
const hint = notice.policyUpdated;
|
|
37830
|
+
if (this.disposed || hint === void 0 || notice.connectionSignal?.aborted || notice.connectionUserId !== hint.userId) return;
|
|
37831
|
+
try {
|
|
37832
|
+
const session = await this.runtime.sessionStore.read();
|
|
37833
|
+
if (this.disposed || notice.connectionSignal?.aborted || session?.userId !== hint.userId) return;
|
|
37834
|
+
this.source.invalidateSourceListCache(session.userId, "root");
|
|
37835
|
+
this.emitChatClientEvent({
|
|
37836
|
+
type: "chat-policy-invalidated",
|
|
37837
|
+
revision: this.nextChatClientRevision()
|
|
37838
|
+
});
|
|
37839
|
+
} catch (error) {
|
|
37840
|
+
console.warn("dsh-arkme: Chat policy invalidation failed:", safeFailureMessage$5(error));
|
|
37841
|
+
}
|
|
37842
|
+
}
|
|
37642
37843
|
/** Browser invalidation only; raw source/Bot caches belong to different projections. */
|
|
37643
37844
|
async invalidateConversationListPreferenceForCurrentSession(expectedUserId) {
|
|
37644
37845
|
try {
|
|
@@ -37652,15 +37853,20 @@ var ChatRealtimeService = class {
|
|
|
37652
37853
|
console.warn("dsh-arkme: Conversation-list preference invalidation failed:", safeFailureMessage$5(error));
|
|
37653
37854
|
}
|
|
37654
37855
|
}
|
|
37655
|
-
async
|
|
37856
|
+
async reconcileChatConnectionBaseline(connectionGeneration, expectedUserId) {
|
|
37857
|
+
if (this.disposed) return;
|
|
37656
37858
|
try {
|
|
37657
37859
|
const session = await this.runtime.requireSession();
|
|
37658
|
-
if (expectedUserId !== void 0 && session.userId !== expectedUserId) return;
|
|
37860
|
+
if (this.disposed || expectedUserId !== void 0 && session.userId !== expectedUserId) return;
|
|
37861
|
+
const initialState = this.chatRealtime.state();
|
|
37862
|
+
if (!initialState.connected || initialState.connectionGeneration !== connectionGeneration) return;
|
|
37659
37863
|
expectedUserId = session.userId;
|
|
37660
37864
|
this.activateAttentionOwner(session.userId);
|
|
37661
37865
|
const ownerGeneration = this.attentionOwnerGeneration;
|
|
37662
37866
|
this.notificationBaselineSequences.clear();
|
|
37663
37867
|
const sequences = /* @__PURE__ */ new Map();
|
|
37868
|
+
const pins = [];
|
|
37869
|
+
this.source.invalidateSourceListCache(session.userId, "root");
|
|
37664
37870
|
let cursor;
|
|
37665
37871
|
for (let pageIndex = 0; pageIndex < 10; pageIndex += 1) {
|
|
37666
37872
|
const page = await this.source.listSources("root", {
|
|
@@ -37668,37 +37874,51 @@ var ChatRealtimeService = class {
|
|
|
37668
37874
|
refresh: true,
|
|
37669
37875
|
...cursor === void 0 ? {} : { cursor }
|
|
37670
37876
|
});
|
|
37877
|
+
const activeSession = await this.runtime.sessionStore.read();
|
|
37878
|
+
const state = this.chatRealtime.state();
|
|
37879
|
+
if (this.disposed || !state.connected || state.connectionGeneration !== connectionGeneration || activeSession?.userId !== session.userId || ownerGeneration !== this.attentionOwnerGeneration) return;
|
|
37671
37880
|
for (const item of page.items) {
|
|
37672
37881
|
if (item.kind !== "private_chat" && item.kind !== "group_chat") continue;
|
|
37673
37882
|
const source = await this.source.openSourceRef(item.sourceRef, session.userId);
|
|
37674
37883
|
sequences.set(source.ownerRef, item.latestSequence ?? 0);
|
|
37884
|
+
if (item.sourceKey !== void 0 && item.isPinned !== void 0 && item.chatPolicyUpdatedAtMillis !== void 0) pins.push({
|
|
37885
|
+
sourceKey: item.sourceKey,
|
|
37886
|
+
pinned: item.isPinned,
|
|
37887
|
+
policyUpdatedAtMillis: item.chatPolicyUpdatedAtMillis
|
|
37888
|
+
});
|
|
37675
37889
|
}
|
|
37676
37890
|
if (!page.hasMore || page.nextCursor === void 0) break;
|
|
37677
37891
|
cursor = page.nextCursor;
|
|
37678
37892
|
}
|
|
37679
|
-
const
|
|
37680
|
-
|
|
37893
|
+
const activeSession = await this.runtime.sessionStore.read();
|
|
37894
|
+
const state = this.chatRealtime.state();
|
|
37895
|
+
if (this.disposed || !state.connected || state.connectionGeneration !== connectionGeneration || activeSession?.userId !== session.userId || ownerGeneration !== this.attentionOwnerGeneration) return;
|
|
37681
37896
|
this.notificationBaselineSequences.clear();
|
|
37682
37897
|
for (const [uid, sequence] of sequences) this.notificationBaselineSequences.set(uid, sequence);
|
|
37683
37898
|
this.notificationBaselineGeneration = connectionGeneration;
|
|
37684
37899
|
this.notificationBaselineUserId = session.userId;
|
|
37685
37900
|
this.notificationBaselineOwnerGeneration = ownerGeneration;
|
|
37686
|
-
this.
|
|
37901
|
+
this.connectionBaselineRetryCount = 0;
|
|
37902
|
+
if (pins.length > 0) this.emitChatClientEvent({
|
|
37903
|
+
type: "chat-pins-reconciled",
|
|
37904
|
+
revision: this.nextChatClientRevision(),
|
|
37905
|
+
pins
|
|
37906
|
+
});
|
|
37687
37907
|
console.info("dsh-arkme: reconcile_completed", {
|
|
37688
37908
|
connectionGeneration,
|
|
37689
37909
|
sessionCount: sequences.size
|
|
37690
37910
|
});
|
|
37691
37911
|
if (this.pendingChatProjections.size > 0) this.scheduleProjectionFlush(0);
|
|
37692
37912
|
} catch (error) {
|
|
37693
|
-
const state = this.chatRealtime.state();
|
|
37694
37913
|
const activeSession = await this.runtime.sessionStore.read().catch(() => void 0);
|
|
37695
|
-
|
|
37696
|
-
this.
|
|
37697
|
-
|
|
37914
|
+
const state = this.chatRealtime.state();
|
|
37915
|
+
if (this.disposed || !state.connected || state.connectionGeneration !== connectionGeneration || expectedUserId === void 0 || activeSession?.userId !== expectedUserId) return;
|
|
37916
|
+
this.connectionBaselineRetryCount += 1;
|
|
37917
|
+
const delay = Math.min(15e3, 1e3 * 2 ** Math.min(4, this.connectionBaselineRetryCount - 1));
|
|
37698
37918
|
console.warn("dsh-arkme: Chat reconnect reconciliation failed:", safeFailureMessage$5(error));
|
|
37699
|
-
this.
|
|
37700
|
-
this.
|
|
37701
|
-
this.
|
|
37919
|
+
this.connectionBaselineRetryTimer = setTimeout(() => {
|
|
37920
|
+
this.connectionBaselineRetryTimer = void 0;
|
|
37921
|
+
this.reconcileChatConnectionBaseline(connectionGeneration, expectedUserId);
|
|
37702
37922
|
}, delay);
|
|
37703
37923
|
}
|
|
37704
37924
|
}
|
|
@@ -38350,6 +38570,286 @@ const ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES = 10 * 1024 * 1024;
|
|
|
38350
38570
|
const ARKME_WORLD_PUBLISH_MAX_IMAGES = 27;
|
|
38351
38571
|
const ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
38352
38572
|
//#endregion
|
|
38573
|
+
//#region src/record-reedit-contract.ts
|
|
38574
|
+
var ArkmeRecordReeditDraftConflict = class extends Error {};
|
|
38575
|
+
/** Only identities are persisted, never browser Files, URLs or host paths. */
|
|
38576
|
+
function parseArkmeRecordReeditAttachments(value) {
|
|
38577
|
+
if (!Array.isArray(value) || value.length > 9) throw new TypeError("请选择至多 9 个附件");
|
|
38578
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38579
|
+
return value.map((raw) => {
|
|
38580
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new TypeError("附件引用无效");
|
|
38581
|
+
const item = raw;
|
|
38582
|
+
const asset = item.fileAssetUid;
|
|
38583
|
+
const file = item.fileRef;
|
|
38584
|
+
if (asset === void 0 === (file === void 0) || Object.keys(item).some((key) => key !== "fileAssetUid" && key !== "fileRef")) throw new TypeError("附件必须指定一个资产或本地文件引用");
|
|
38585
|
+
const id = asset ?? file;
|
|
38586
|
+
if (typeof id !== "string" || id.trim() === "" || id !== id.trim() || id.length > 200 || file !== void 0 && !/^arkme-file-v1\.[0-9a-f-]{36}$/.test(id)) throw new TypeError("附件引用无效");
|
|
38587
|
+
const key = `${asset === void 0 ? "file" : "asset"}:${id}`;
|
|
38588
|
+
if (seen.has(key)) throw new TypeError("附件不可重复");
|
|
38589
|
+
seen.add(key);
|
|
38590
|
+
return asset === void 0 ? { fileRef: id } : { fileAssetUid: id };
|
|
38591
|
+
});
|
|
38592
|
+
}
|
|
38593
|
+
//#endregion
|
|
38594
|
+
//#region src/services/record-reedit-submissions.ts
|
|
38595
|
+
function recordReeditSubmissionView(job) {
|
|
38596
|
+
const { submissionId, state, attachments, voiceFileAssetUid, voiceBlock, result, error } = job;
|
|
38597
|
+
const { itemUid, baseVersion } = job.context;
|
|
38598
|
+
const { title, textContent } = job.draft;
|
|
38599
|
+
return structuredClone({
|
|
38600
|
+
submissionId,
|
|
38601
|
+
baseVersion,
|
|
38602
|
+
itemUid,
|
|
38603
|
+
state,
|
|
38604
|
+
title,
|
|
38605
|
+
textContent,
|
|
38606
|
+
attachments,
|
|
38607
|
+
...voiceFileAssetUid ? { voiceFileAssetUid } : {},
|
|
38608
|
+
...voiceBlock ? { voiceBlock } : {},
|
|
38609
|
+
...result ? { result } : {},
|
|
38610
|
+
...error ? { error } : {}
|
|
38611
|
+
});
|
|
38612
|
+
}
|
|
38613
|
+
/** Owns only delivery of an existing Record edit, not new-message outbox semantics. */
|
|
38614
|
+
var RecordReeditSubmissions = class {
|
|
38615
|
+
ports;
|
|
38616
|
+
running = /* @__PURE__ */ new Map();
|
|
38617
|
+
knownCompletions = /* @__PURE__ */ new Map();
|
|
38618
|
+
closed = false;
|
|
38619
|
+
constructor(ports) {
|
|
38620
|
+
this.ports = ports;
|
|
38621
|
+
}
|
|
38622
|
+
dispose() {
|
|
38623
|
+
this.closed = true;
|
|
38624
|
+
}
|
|
38625
|
+
async accept(candidate) {
|
|
38626
|
+
if (this.closed) throw new ArkmePluginError("record-reedit-unavailable", "插件正在重启,请稍后重试", false, 409);
|
|
38627
|
+
const userId = candidate.context.expectedUserId;
|
|
38628
|
+
const previous = (await this.ports.list(userId)).find((job) => job.context.sourceIdentityKey === candidate.context.sourceIdentityKey && job.context.itemUid === candidate.context.itemUid);
|
|
38629
|
+
if (previous && [
|
|
38630
|
+
"pending",
|
|
38631
|
+
"committing",
|
|
38632
|
+
"uncertain"
|
|
38633
|
+
].includes(previous.state)) {
|
|
38634
|
+
if (previous.draft.draftRevision === candidate.draft.draftRevision) return recordReeditSubmissionView(previous);
|
|
38635
|
+
throw new ArkmePluginError("record-reedit-in-progress", "这条快记仍在保存或核对,请稍后再编辑;其他消息不受影响", false, 409);
|
|
38636
|
+
}
|
|
38637
|
+
const job = {
|
|
38638
|
+
...structuredClone(candidate),
|
|
38639
|
+
submissionId: randomUUID(),
|
|
38640
|
+
state: "pending"
|
|
38641
|
+
};
|
|
38642
|
+
await this.ports.put(userId, job, previous?.submissionId);
|
|
38643
|
+
const receipt = recordReeditSubmissionView(job);
|
|
38644
|
+
this.start(job, false);
|
|
38645
|
+
return receipt;
|
|
38646
|
+
}
|
|
38647
|
+
async list(userId, sourceIdentityKey) {
|
|
38648
|
+
return (await this.ports.list(userId)).filter((job) => job.context.sourceIdentityKey === sourceIdentityKey).map((job) => recordReeditSubmissionView(this.knownCompletions.get(job.submissionId) ?? job));
|
|
38649
|
+
}
|
|
38650
|
+
async settleKnownCompletions(userId, sourceIdentityKey, itemUid) {
|
|
38651
|
+
for (const job of this.knownCompletions.values()) {
|
|
38652
|
+
if (job.context.expectedUserId !== userId || job.context.sourceIdentityKey !== sourceIdentityKey || itemUid !== void 0 && job.context.itemUid !== itemUid) continue;
|
|
38653
|
+
try {
|
|
38654
|
+
await this.persistKnownCompletion(job);
|
|
38655
|
+
} catch (error) {
|
|
38656
|
+
if (itemUid !== void 0) throw error;
|
|
38657
|
+
}
|
|
38658
|
+
}
|
|
38659
|
+
}
|
|
38660
|
+
/** Tool waits for the remote result, but shares UI delivery's write-ahead evidence. */
|
|
38661
|
+
async writeConfirmed(candidate, fingerprint, write) {
|
|
38662
|
+
if (this.closed) throw new ArkmePluginError("record-reedit-unavailable", "插件正在重启,请稍后重试", false, 409);
|
|
38663
|
+
const userId = candidate.context.expectedUserId;
|
|
38664
|
+
const previous = (await this.ports.list(userId)).find((job) => job.context.sourceIdentityKey === candidate.context.sourceIdentityKey && job.context.itemUid === candidate.context.itemUid);
|
|
38665
|
+
if (previous && [
|
|
38666
|
+
"pending",
|
|
38667
|
+
"committing",
|
|
38668
|
+
"uncertain"
|
|
38669
|
+
].includes(previous.state)) throw new ArkmePluginError("record-reedit-in-progress", "这条快记仍在保存或核对,请稍后再编辑", false, 409);
|
|
38670
|
+
const job = {
|
|
38671
|
+
...structuredClone(candidate),
|
|
38672
|
+
submissionId: randomUUID(),
|
|
38673
|
+
state: "committing",
|
|
38674
|
+
expectedCommittedFingerprint: fingerprint
|
|
38675
|
+
};
|
|
38676
|
+
const work = (async () => {
|
|
38677
|
+
await this.ports.put(userId, job, previous?.submissionId);
|
|
38678
|
+
if (this.closed) throw new ArkmePluginError("record-reedit-unavailable", "插件已停止,提交等待核对", false, 409);
|
|
38679
|
+
const outcome = await write();
|
|
38680
|
+
await this.finish(job, outcome);
|
|
38681
|
+
return outcome;
|
|
38682
|
+
})();
|
|
38683
|
+
this.running.set(job.submissionId, work.then(() => void 0, () => void 0));
|
|
38684
|
+
try {
|
|
38685
|
+
return await work;
|
|
38686
|
+
} finally {
|
|
38687
|
+
this.running.delete(job.submissionId);
|
|
38688
|
+
}
|
|
38689
|
+
}
|
|
38690
|
+
async resume(userId, sourceIdentityKey, reconcile = false, sourceRef) {
|
|
38691
|
+
await this.settleKnownCompletions(userId, sourceIdentityKey);
|
|
38692
|
+
const jobs = (await this.ports.list(userId)).filter((job) => job.context.sourceIdentityKey === sourceIdentityKey);
|
|
38693
|
+
for (const job of jobs) {
|
|
38694
|
+
if (this.running.has(job.submissionId) || this.knownCompletions.has(job.submissionId)) continue;
|
|
38695
|
+
if (sourceRef) job.context.sourceRef = sourceRef;
|
|
38696
|
+
if (job.state === "pending") this.start(job, false);
|
|
38697
|
+
else if (job.state === "committing" || job.state === "uncertain" && reconcile) this.start(job, true);
|
|
38698
|
+
}
|
|
38699
|
+
}
|
|
38700
|
+
start(job, reconcile) {
|
|
38701
|
+
if (this.closed || this.running.has(job.submissionId)) return;
|
|
38702
|
+
const work = this.run(structuredClone(job), reconcile).catch(() => void 0).finally(() => this.running.delete(job.submissionId));
|
|
38703
|
+
this.running.set(job.submissionId, work);
|
|
38704
|
+
}
|
|
38705
|
+
async run(initial, reconcile) {
|
|
38706
|
+
let job = initial;
|
|
38707
|
+
const { context, draft, attachments, voiceFileAssetUid, voiceBlock, submissionId } = initial;
|
|
38708
|
+
const candidate = {
|
|
38709
|
+
context,
|
|
38710
|
+
draft,
|
|
38711
|
+
attachments,
|
|
38712
|
+
submissionId,
|
|
38713
|
+
...voiceFileAssetUid ? { voiceFileAssetUid } : {},
|
|
38714
|
+
...voiceBlock ? { voiceBlock } : {}
|
|
38715
|
+
};
|
|
38716
|
+
const save = () => this.ports.put(job.context.expectedUserId, job, job.submissionId);
|
|
38717
|
+
const outcome = reconcile ? await this.ports.reconcile(job) : await this.ports.commit(job, async (fingerprint) => {
|
|
38718
|
+
if (this.closed) throw new ArkmePluginError("record-reedit-unavailable", "插件已停止,提交等待恢复", false, 409);
|
|
38719
|
+
job = {
|
|
38720
|
+
...candidate,
|
|
38721
|
+
state: "committing",
|
|
38722
|
+
expectedCommittedFingerprint: fingerprint
|
|
38723
|
+
};
|
|
38724
|
+
await save();
|
|
38725
|
+
if (this.closed) throw new ArkmePluginError("record-reedit-unavailable", "插件已停止,提交等待核对", false, 409);
|
|
38726
|
+
});
|
|
38727
|
+
await this.finish(job, outcome);
|
|
38728
|
+
}
|
|
38729
|
+
async finish(initial, outcome) {
|
|
38730
|
+
const { context, draft, attachments, voiceFileAssetUid, voiceBlock, submissionId } = initial;
|
|
38731
|
+
const candidate = {
|
|
38732
|
+
context,
|
|
38733
|
+
draft,
|
|
38734
|
+
attachments,
|
|
38735
|
+
submissionId,
|
|
38736
|
+
...voiceFileAssetUid ? { voiceFileAssetUid } : {},
|
|
38737
|
+
...voiceBlock ? { voiceBlock } : {}
|
|
38738
|
+
};
|
|
38739
|
+
const checkpoint = initial.expectedCommittedFingerprint;
|
|
38740
|
+
let job;
|
|
38741
|
+
if (outcome.kind === "committed") {
|
|
38742
|
+
job = {
|
|
38743
|
+
...candidate,
|
|
38744
|
+
state: "committed",
|
|
38745
|
+
result: outcome.result,
|
|
38746
|
+
...checkpoint ? { expectedCommittedFingerprint: checkpoint } : {}
|
|
38747
|
+
};
|
|
38748
|
+
this.knownCompletions.set(job.submissionId, job);
|
|
38749
|
+
} else if (outcome.kind === "uncertain") {
|
|
38750
|
+
if (!checkpoint) throw new Error("Unknown Record edit outcome requires a persisted write checkpoint");
|
|
38751
|
+
job = {
|
|
38752
|
+
...candidate,
|
|
38753
|
+
state: "uncertain",
|
|
38754
|
+
expectedCommittedFingerprint: checkpoint,
|
|
38755
|
+
error: outcome.message
|
|
38756
|
+
};
|
|
38757
|
+
} else job = {
|
|
38758
|
+
...candidate,
|
|
38759
|
+
state: "failed",
|
|
38760
|
+
error: outcome.message,
|
|
38761
|
+
...checkpoint ? { expectedCommittedFingerprint: checkpoint } : {}
|
|
38762
|
+
};
|
|
38763
|
+
if (this.closed) return;
|
|
38764
|
+
if (job.state === "committed") {
|
|
38765
|
+
await this.persistKnownCompletion(job).catch(() => void 0);
|
|
38766
|
+
return;
|
|
38767
|
+
}
|
|
38768
|
+
await this.ports.put(job.context.expectedUserId, job, job.submissionId);
|
|
38769
|
+
}
|
|
38770
|
+
async persistKnownCompletion(job) {
|
|
38771
|
+
if (this.closed) return;
|
|
38772
|
+
await this.ports.put(job.context.expectedUserId, job, job.submissionId);
|
|
38773
|
+
if (this.closed || this.knownCompletions.get(job.submissionId) !== job) return;
|
|
38774
|
+
this.knownCompletions.delete(job.submissionId);
|
|
38775
|
+
await this.ports.committed?.(job.context.expectedUserId).catch(() => void 0);
|
|
38776
|
+
}
|
|
38777
|
+
};
|
|
38778
|
+
//#endregion
|
|
38779
|
+
//#region src/services/record-reedit-attachments.ts
|
|
38780
|
+
const invalid$1 = () => new ArkmePluginError("record-reedit-attachment-invalid", "附件引用无效或不属于这条快记,请重新读取后选择", false, 409);
|
|
38781
|
+
const refs = (payload) => Array.isArray(payload?.media_refs) ? payload.media_refs.map(objectValue$2) : [];
|
|
38782
|
+
/** A Live Photo is one editable item with its original companion resources. */
|
|
38783
|
+
function recordReeditMediaGroups(payload) {
|
|
38784
|
+
const media = refs(payload).sort((a, b) => Number(a.sort_order ?? 0) - Number(b.sort_order ?? 0));
|
|
38785
|
+
return media.filter((ref) => ref.render_role !== 4 && ref.render_role !== 2).map((primary) => {
|
|
38786
|
+
const logicalUid = stringValue$2(objectValue$2(primary.dynamic_photo).logical_uid);
|
|
38787
|
+
return {
|
|
38788
|
+
fileAssetUid: stringValue$2(primary.file_asset_uid),
|
|
38789
|
+
primary,
|
|
38790
|
+
refs: [primary, ...media.filter((ref) => ref !== primary && ref.render_role === 4 && logicalUid !== "" && stringValue$2(objectValue$2(ref.dynamic_photo).logical_uid) === logicalUid)]
|
|
38791
|
+
};
|
|
38792
|
+
});
|
|
38793
|
+
}
|
|
38794
|
+
function recordReeditAttachmentSelection(value, payload) {
|
|
38795
|
+
let selection;
|
|
38796
|
+
try {
|
|
38797
|
+
selection = parseArkmeRecordReeditAttachments(value);
|
|
38798
|
+
} catch {
|
|
38799
|
+
throw invalid$1();
|
|
38800
|
+
}
|
|
38801
|
+
const groups = recordReeditMediaGroups(payload);
|
|
38802
|
+
const supported = new Set(groups.map((group) => group.fileAssetUid));
|
|
38803
|
+
if (selection.some((item) => item.fileAssetUid !== void 0 && !supported.has(item.fileAssetUid))) throw invalid$1();
|
|
38804
|
+
const grouped = new Set(groups.flatMap((group) => group.refs));
|
|
38805
|
+
if (refs(payload).some((ref) => ref.render_role === 4 && !groups.some((group) => group.refs.some((item) => item.file_asset_uid === ref.file_asset_uid)))) throw invalid$1();
|
|
38806
|
+
if (groups.some((group) => group.fileAssetUid === "") || grouped.size !== groups.reduce((sum, group) => sum + group.refs.length, 0)) throw invalid$1();
|
|
38807
|
+
return selection;
|
|
38808
|
+
}
|
|
38809
|
+
function recordReeditAttachmentChanges(payload, selection) {
|
|
38810
|
+
const old = recordReeditMediaGroups(payload).map((group) => group.fileAssetUid);
|
|
38811
|
+
const kept = selection.flatMap((item) => item.fileAssetUid === void 0 ? [] : [item.fileAssetUid]);
|
|
38812
|
+
return {
|
|
38813
|
+
added: selection.length - kept.length,
|
|
38814
|
+
removed: old.filter((uid) => !kept.includes(uid)).length,
|
|
38815
|
+
retained: kept.length,
|
|
38816
|
+
reordered: old.filter((uid) => kept.includes(uid)).join("\0") !== kept.join("\0")
|
|
38817
|
+
};
|
|
38818
|
+
}
|
|
38819
|
+
function recordReeditWithAttachments(payload, selection, uploads) {
|
|
38820
|
+
const groups = recordReeditMediaGroups(payload);
|
|
38821
|
+
const output = structuredClone(payload ?? {
|
|
38822
|
+
schema_version: 1,
|
|
38823
|
+
text_state: 1
|
|
38824
|
+
});
|
|
38825
|
+
const media = selection.flatMap((item) => {
|
|
38826
|
+
if (item.fileAssetUid !== void 0) {
|
|
38827
|
+
const group = groups.find((value) => value.fileAssetUid === item.fileAssetUid);
|
|
38828
|
+
if (group === void 0) throw invalid$1();
|
|
38829
|
+
return group.refs.map((ref) => structuredClone(ref));
|
|
38830
|
+
}
|
|
38831
|
+
const asset = uploads.get(item.fileRef);
|
|
38832
|
+
if (asset === void 0 || asset.fileAssetUid.trim() === "") throw invalid$1();
|
|
38833
|
+
return [{
|
|
38834
|
+
file_asset_uid: asset.fileAssetUid,
|
|
38835
|
+
render_role: 1,
|
|
38836
|
+
file_name: asset.fileName
|
|
38837
|
+
}];
|
|
38838
|
+
});
|
|
38839
|
+
media.push(...refs(payload).filter((ref) => ref.render_role === 2).map((ref) => structuredClone(ref)));
|
|
38840
|
+
if (new Set(media.map((ref) => ref.file_asset_uid)).size !== media.length) throw new ArkmePluginError("record-reedit-attachment-invalid", "部分附件内容重复,请保留一份后重试;草稿已保留", false, 409);
|
|
38841
|
+
output.media_refs = media.map((ref, sortOrder) => ({
|
|
38842
|
+
...ref,
|
|
38843
|
+
sort_order: sortOrder
|
|
38844
|
+
}));
|
|
38845
|
+
const templateKind = stringValue$2(objectValue$2(output.voice).source_file_asset_uid) !== "" ? media.length > 0 ? 4 : 3 : media.length > 0 ? 2 : 1;
|
|
38846
|
+
output.payload_kind = templateKind;
|
|
38847
|
+
return {
|
|
38848
|
+
templateKind,
|
|
38849
|
+
contentPayload: output
|
|
38850
|
+
};
|
|
38851
|
+
}
|
|
38852
|
+
//#endregion
|
|
38353
38853
|
//#region src/services/record-service.ts
|
|
38354
38854
|
function numberValue$20(value) {
|
|
38355
38855
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
@@ -38415,6 +38915,16 @@ function recordReeditDynamicPhoto(raw) {
|
|
|
38415
38915
|
function recordReeditReadContentPayload(raw) {
|
|
38416
38916
|
const source = objectValue$2(raw);
|
|
38417
38917
|
if (Object.keys(source).length === 0) return void 0;
|
|
38918
|
+
if ([
|
|
38919
|
+
"forward_records",
|
|
38920
|
+
"shared_recording",
|
|
38921
|
+
"structured_anchor",
|
|
38922
|
+
"call_record"
|
|
38923
|
+
].some((key) => Object.keys(objectValue$2(source[key])).length > 0)) throw new ArkmePluginError("record-reedit-shape-unsupported", "卡片类内容暂不支持普通重新编辑,原内容和草稿均保留", false, 409);
|
|
38924
|
+
if (source.background_sound_amplitudes !== void 0 || listValue$17(source.media_refs).some((rawRef) => {
|
|
38925
|
+
const ref = objectValue$2(rawRef);
|
|
38926
|
+
return numberValue$20(ref.content_file_role) === 4 || numberValue$20(ref.binding_type) === 4;
|
|
38927
|
+
})) throw new ArkmePluginError("record-reedit-shape-unsupported", "该快记包含背景音,当前接口无法安全保留,暂不支持重新编辑;已有草稿不会删除", false, 409);
|
|
38418
38928
|
if (listValue$17(source.legacy_file_refs).length > 0) throw new ArkmePluginError("record-reedit-legacy-files-unsupported", "该快记包含尚未升级的历史附件,当前无法安全重新编辑", false, 409);
|
|
38419
38929
|
const output = cloneKnownFields(source, [
|
|
38420
38930
|
"payload_kind",
|
|
@@ -38480,13 +38990,24 @@ function recordReeditReadContentPayload(raw) {
|
|
|
38480
38990
|
"length"
|
|
38481
38991
|
])).filter((mention) => Object.keys(mention).length > 0);
|
|
38482
38992
|
if (locationMentions.length > 0) output.location_mentions = locationMentions;
|
|
38993
|
+
const hashTags = listValue$17(source.hash_tags).map((rawTag) => cloneKnownFields(objectValue$2(rawTag), [
|
|
38994
|
+
"tag",
|
|
38995
|
+
"start_index",
|
|
38996
|
+
"length"
|
|
38997
|
+
]));
|
|
38998
|
+
if (hashTags.length > 0) output.hash_tags = hashTags;
|
|
38483
38999
|
return output;
|
|
38484
39000
|
}
|
|
38485
39001
|
function recordReeditContentPayloadForWrite(contentPayload, currentText, nextText) {
|
|
38486
|
-
if (contentPayload === void 0) return
|
|
39002
|
+
if (contentPayload === void 0) return arkmeHashTagContentPayload(nextText);
|
|
38487
39003
|
if (nextText !== currentText && (Object.keys(objectValue$2(contentPayload.mention_metadata)).length > 0 || listValue$17(contentPayload.location_mentions).length > 0)) throw new ArkmePluginError("record-reedit-rich-text-unsupported", "该快记包含 @ 或位置内容,当前只能保持正文不变后修改标题", false, 409);
|
|
38488
39004
|
const output = structuredClone(contentPayload);
|
|
38489
|
-
if (
|
|
39005
|
+
if (nextText !== currentText) {
|
|
39006
|
+
const hashTags = arkmeHashTagPayload(nextText);
|
|
39007
|
+
if (hashTags.length > 0) output.hash_tags = hashTags;
|
|
39008
|
+
else delete output.hash_tags;
|
|
39009
|
+
}
|
|
39010
|
+
if (nextText !== currentText && Object.prototype.hasOwnProperty.call(output, "text_state")) output.text_state = nextText === "" ? 3 : 1;
|
|
38490
39011
|
return output;
|
|
38491
39012
|
}
|
|
38492
39013
|
function recordReeditHasAttachments(contentPayload) {
|
|
@@ -38528,11 +39049,160 @@ var RecordService = class {
|
|
|
38528
39049
|
media;
|
|
38529
39050
|
source;
|
|
38530
39051
|
privacy;
|
|
38531
|
-
|
|
39052
|
+
reeditFiles;
|
|
39053
|
+
reeditEditors = /* @__PURE__ */ new Map();
|
|
39054
|
+
submissions;
|
|
39055
|
+
reeditAdmissions = /* @__PURE__ */ new Map();
|
|
39056
|
+
activeRecordCommits = /* @__PURE__ */ new Set();
|
|
39057
|
+
closed = false;
|
|
39058
|
+
constructor(runtime, media, source, privacy = new ArkmePrivacyVisibilityService(runtime), reeditFiles, onReeditCommitted) {
|
|
38532
39059
|
this.runtime = runtime;
|
|
38533
39060
|
this.media = media;
|
|
38534
39061
|
this.source = source;
|
|
38535
39062
|
this.privacy = privacy;
|
|
39063
|
+
this.reeditFiles = reeditFiles;
|
|
39064
|
+
this.submissions = new RecordReeditSubmissions({
|
|
39065
|
+
list: (userId) => this.runtime.stateStore.listRecordReeditSubmissions(userId),
|
|
39066
|
+
put: (userId, job, expectedId) => {
|
|
39067
|
+
this.assertReeditActive();
|
|
39068
|
+
const persist = async () => {
|
|
39069
|
+
this.assertReeditActive();
|
|
39070
|
+
await this.runtime.stateStore.putRecordReeditSubmission(userId, job, expectedId);
|
|
39071
|
+
};
|
|
39072
|
+
const refs = job.draft.attachments?.flatMap((item) => item.fileRef === void 0 ? [] : [item.fileRef]) ?? [];
|
|
39073
|
+
return expectedId !== job.submissionId && refs.length > 0 ? this.recordReeditFiles().withReferences(refs, userId, persist) : persist();
|
|
39074
|
+
},
|
|
39075
|
+
committed: async (userId) => {
|
|
39076
|
+
if ((await this.runtime.requireSession()).userId === userId && !this.closed) await onReeditCommitted?.();
|
|
39077
|
+
},
|
|
39078
|
+
commit: async (job, beforeWrite) => {
|
|
39079
|
+
let writeStarted = false;
|
|
39080
|
+
try {
|
|
39081
|
+
return {
|
|
39082
|
+
kind: "committed",
|
|
39083
|
+
result: await this.executeRecordReeditCommand(job, async (fingerprint, _owner, write) => {
|
|
39084
|
+
await beforeWrite(fingerprint);
|
|
39085
|
+
writeStarted = true;
|
|
39086
|
+
return await write();
|
|
39087
|
+
})
|
|
39088
|
+
};
|
|
39089
|
+
} catch (error) {
|
|
39090
|
+
return this.recordReeditExecutionFailure(error, writeStarted);
|
|
39091
|
+
}
|
|
39092
|
+
},
|
|
39093
|
+
reconcile: async (job) => {
|
|
39094
|
+
try {
|
|
39095
|
+
const session = await this.runtime.requireSession();
|
|
39096
|
+
if (session.userId !== job.context.expectedUserId || !job.expectedCommittedFingerprint) return {
|
|
39097
|
+
kind: "uncertain",
|
|
39098
|
+
message: "请切回原账号后核对提交结果"
|
|
39099
|
+
};
|
|
39100
|
+
return {
|
|
39101
|
+
kind: "committed",
|
|
39102
|
+
result: await this.reconcileRecordReeditUnknownOutcome(session, job.context, job.expectedCommittedFingerprint)
|
|
39103
|
+
};
|
|
39104
|
+
} catch (error) {
|
|
39105
|
+
return this.recordReeditExecutionFailure(error, true, true);
|
|
39106
|
+
}
|
|
39107
|
+
}
|
|
39108
|
+
});
|
|
39109
|
+
}
|
|
39110
|
+
recordReeditExecutionFailure(error, writeStarted, reconciling = false) {
|
|
39111
|
+
const message = error instanceof Error ? error.message : "保存失败,请恢复编辑后重试";
|
|
39112
|
+
if (error instanceof ArkmePluginError && error.code === "record-reedit-conflict") return {
|
|
39113
|
+
kind: "conflict",
|
|
39114
|
+
message
|
|
39115
|
+
};
|
|
39116
|
+
if (reconciling || writeStarted && (!(error instanceof ArkmePluginError) || error.writeOutcomeUnknown || error.code === "record-reedit-outcome-unknown")) return {
|
|
39117
|
+
kind: "uncertain",
|
|
39118
|
+
message
|
|
39119
|
+
};
|
|
39120
|
+
return {
|
|
39121
|
+
kind: "failed",
|
|
39122
|
+
message
|
|
39123
|
+
};
|
|
39124
|
+
}
|
|
39125
|
+
reeditBaselineKey(userId, sourceRef, itemUid) {
|
|
39126
|
+
return JSON.stringify([
|
|
39127
|
+
userId,
|
|
39128
|
+
sourceRef,
|
|
39129
|
+
itemUid
|
|
39130
|
+
]);
|
|
39131
|
+
}
|
|
39132
|
+
dispose() {
|
|
39133
|
+
this.closed = true;
|
|
39134
|
+
this.submissions.dispose();
|
|
39135
|
+
this.reeditEditors.clear();
|
|
39136
|
+
}
|
|
39137
|
+
assertReeditActive() {
|
|
39138
|
+
if (this.closed) throw new ArkmePluginError("record-reedit-unavailable", "插件已停止,请在新实例中恢复编辑或核对结果", false, 409);
|
|
39139
|
+
}
|
|
39140
|
+
async saveRecordReeditDraft(input) {
|
|
39141
|
+
return (await this.prepareLocalRecordReedit(input, true)).context;
|
|
39142
|
+
}
|
|
39143
|
+
async submitRecordReedit(input) {
|
|
39144
|
+
const session = await this.runtime.requireSession();
|
|
39145
|
+
const key = JSON.stringify([session.userId, input.itemUid.trim()]);
|
|
39146
|
+
const work = (this.reeditAdmissions.get(key) ?? Promise.resolve()).catch(() => void 0).then(() => this.acceptRecordReedit(input));
|
|
39147
|
+
this.reeditAdmissions.set(key, work);
|
|
39148
|
+
try {
|
|
39149
|
+
return await work;
|
|
39150
|
+
} finally {
|
|
39151
|
+
if (this.reeditAdmissions.get(key) === work) this.reeditAdmissions.delete(key);
|
|
39152
|
+
}
|
|
39153
|
+
}
|
|
39154
|
+
async acceptRecordReedit(input) {
|
|
39155
|
+
const session = await this.runtime.requireSession();
|
|
39156
|
+
const source = await this.source.openSourceRef(input.sourceRef, session.userId);
|
|
39157
|
+
const identity = await this.recordReeditSourceIdentityKey(source);
|
|
39158
|
+
await this.submissions.settleKnownCompletions(session.userId, identity, input.itemUid);
|
|
39159
|
+
const previous = (await this.runtime.stateStore.listRecordReeditSubmissions(session.userId)).find((job) => job.context.itemUid === input.itemUid && job.context.sourceIdentityKey === identity);
|
|
39160
|
+
if (previous && [
|
|
39161
|
+
"pending",
|
|
39162
|
+
"committing",
|
|
39163
|
+
"uncertain"
|
|
39164
|
+
].includes(previous.state)) {
|
|
39165
|
+
if ((input.newText === void 0 || input.newText === previous.draft.textContent) && (input.newTitle === void 0 || input.newTitle.trim() === previous.draft.title) && input.expectedVersion === previous.context.baseVersion && (input.attachments === void 0 || JSON.stringify(input.attachments) === JSON.stringify(previous.draft.attachments))) return recordReeditSubmissionView(previous);
|
|
39166
|
+
throw new ArkmePluginError("record-reedit-in-progress", "这条快记仍在保存或核对,请稍后再编辑;其他消息不受影响", false, 409);
|
|
39167
|
+
}
|
|
39168
|
+
if (this.activeRecordCommits.has(JSON.stringify([
|
|
39169
|
+
session.userId,
|
|
39170
|
+
identity,
|
|
39171
|
+
input.itemUid
|
|
39172
|
+
]))) throw new ArkmePluginError("record-reedit-in-progress", "该快记正在提交,请稍后重新确认", false, 409);
|
|
39173
|
+
const { context, owner, view } = await this.prepareLocalRecordReedit(input, false);
|
|
39174
|
+
const draft = await this.runtime.stateStore.getRecordReeditDraft(context.expectedUserId, context.sourceIdentityKey, context.itemUid);
|
|
39175
|
+
if (!draft || draft.draftRevision !== context.draftRevision) throw new ArkmeRecordReeditDraftConflict("草稿已变化,请重新读取");
|
|
39176
|
+
const selections = draft.attachments ?? view.attachments.map((item) => item.selection);
|
|
39177
|
+
const attachments = await this.recordReeditAttachmentViews(owner, selections, view.attachments.flatMap((item) => item.block ? [item.block] : []));
|
|
39178
|
+
if ((await this.runtime.requireSession()).userId !== context.expectedUserId) throw new ArkmePluginError("record-reedit-account-changed", "账号已切换,请重新打开", false, 409);
|
|
39179
|
+
const voiceFileAssetUid = stringValue$2(objectValue$2(owner.contentPayload?.voice).source_file_asset_uid);
|
|
39180
|
+
return await this.submissions.accept({
|
|
39181
|
+
context,
|
|
39182
|
+
draft,
|
|
39183
|
+
attachments,
|
|
39184
|
+
...voiceFileAssetUid ? { voiceFileAssetUid } : {},
|
|
39185
|
+
...view.voiceBlock ? { voiceBlock: view.voiceBlock } : {}
|
|
39186
|
+
});
|
|
39187
|
+
}
|
|
39188
|
+
async recordReeditSubmissions(sourceRef) {
|
|
39189
|
+
const session = await this.runtime.requireSession();
|
|
39190
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
39191
|
+
return await this.submissions.list(session.userId, await this.recordReeditSourceIdentityKey(source));
|
|
39192
|
+
}
|
|
39193
|
+
async resumeRecordReeditSubmissions(sourceRef, reconcile = false) {
|
|
39194
|
+
const session = await this.runtime.requireSession();
|
|
39195
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
39196
|
+
await this.submissions.resume(session.userId, await this.recordReeditSourceIdentityKey(source), reconcile, sourceRef);
|
|
39197
|
+
}
|
|
39198
|
+
async acknowledgeRecordReeditSubmission(sourceRef, submissionId, version) {
|
|
39199
|
+
const session = await this.runtime.requireSession();
|
|
39200
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
39201
|
+
if (!Number.isSafeInteger(version) || version <= 0) return;
|
|
39202
|
+
const identity = await this.recordReeditSourceIdentityKey(source);
|
|
39203
|
+
await this.submissions.settleKnownCompletions(session.userId, identity);
|
|
39204
|
+
this.assertReeditActive();
|
|
39205
|
+
await this.runtime.stateStore.acknowledgeRecordReeditSubmission(session.userId, identity, submissionId, version);
|
|
38536
39206
|
}
|
|
38537
39207
|
async prepareRecordReedit(input, options = {}) {
|
|
38538
39208
|
const session = await this.runtime.requireSession();
|
|
@@ -38540,32 +39210,76 @@ var RecordService = class {
|
|
|
38540
39210
|
const itemUid = input.itemUid.trim();
|
|
38541
39211
|
if (sourceRef === "" || itemUid === "") throw new ArkmePluginError("record-reedit-target-invalid", "重新编辑目标无效", false);
|
|
38542
39212
|
const owner = await this.recordReeditOwnerSnapshot(sourceRef, itemUid, session);
|
|
39213
|
+
await this.submissions.settleKnownCompletions(session.userId, owner.sourceIdentityKey, itemUid);
|
|
39214
|
+
const expectedVersion = input.expectedVersion ?? options.expectedBaseVersion;
|
|
39215
|
+
return await this.prepareRecordReeditCandidate({
|
|
39216
|
+
...input,
|
|
39217
|
+
sourceRef,
|
|
39218
|
+
itemUid,
|
|
39219
|
+
...expectedVersion === void 0 ? {} : { expectedVersion }
|
|
39220
|
+
}, owner, session, options.draftOnly === true);
|
|
39221
|
+
}
|
|
39222
|
+
async prepareLocalRecordReedit(input, draftOnly) {
|
|
39223
|
+
this.assertReeditActive();
|
|
39224
|
+
const session = await this.runtime.requireSession();
|
|
39225
|
+
const sourceRef = input.sourceRef.trim();
|
|
39226
|
+
const itemUid = input.itemUid.trim();
|
|
39227
|
+
let editor = this.reeditEditors.get(this.reeditBaselineKey(session.userId, sourceRef, itemUid));
|
|
39228
|
+
if (!editor) {
|
|
39229
|
+
if (!Number.isSafeInteger(input.expectedVersion) || (input.expectedVersion ?? 0) <= 0) throw new ArkmePluginError("record-reedit-version-invalid", "恢复编辑上下文需要原快记版本,请重新读取后确认", false, 409);
|
|
39230
|
+
editor = await this.loadRecordReeditEditor(sourceRef, itemUid, session);
|
|
39231
|
+
}
|
|
39232
|
+
if (input.expectedVersion !== void 0 && editor.owner.version !== input.expectedVersion) throw new ArkmePluginError("record-reedit-conflict", "快记已在其他位置更新,草稿已保留,请检查后重新确认", false, 409);
|
|
39233
|
+
await this.source.openSourceRef(sourceRef, session.userId);
|
|
39234
|
+
return {
|
|
39235
|
+
context: await this.prepareRecordReeditCandidate({
|
|
39236
|
+
...input,
|
|
39237
|
+
sourceRef,
|
|
39238
|
+
itemUid
|
|
39239
|
+
}, editor.owner, session, draftOnly),
|
|
39240
|
+
...editor
|
|
39241
|
+
};
|
|
39242
|
+
}
|
|
39243
|
+
async prepareRecordReeditCandidate(input, owner, session, draftOnly) {
|
|
39244
|
+
const { sourceRef, itemUid } = input;
|
|
38543
39245
|
const previous = await this.runtime.stateStore.getRecordReeditDraft(session.userId, owner.sourceIdentityKey, itemUid);
|
|
38544
|
-
const expectedBaseVersion =
|
|
39246
|
+
const expectedBaseVersion = input.expectedVersion;
|
|
38545
39247
|
if (expectedBaseVersion !== void 0 && (!Number.isSafeInteger(expectedBaseVersion) || expectedBaseVersion <= 0)) throw new ArkmePluginError("record-reedit-version-invalid", "重新编辑版本无效,请重新打开后再试", false, 409);
|
|
38546
|
-
|
|
38547
|
-
if (
|
|
38548
|
-
|
|
39248
|
+
if (!(input.newText !== void 0) && input.newTitle === void 0 && input.attachments === void 0 && previous === void 0) throw new ArkmePluginError("record-reedit-draft-not-found", "该快记没有可恢复的重新编辑草稿,请提供新的正文", false, 404);
|
|
39249
|
+
if (input.expectedDraftRevision !== void 0 && (!Number.isSafeInteger(input.expectedDraftRevision) || input.expectedDraftRevision < 0 || input.expectedDraftRevision !== (previous?.draftRevision ?? 0))) throw new ArkmePluginError("record-reedit-draft-changed", "重新编辑草稿已变化,请重新打开后确认", false, 409);
|
|
39250
|
+
if (input.attachments !== void 0 && expectedBaseVersion === void 0) throw new ArkmePluginError("record-reedit-version-invalid", "修改附件需要读取时的快记版本", false, 409);
|
|
39251
|
+
if (input.attachments !== void 0 && expectedBaseVersion !== owner.version) throw new ArkmePluginError("record-reedit-conflict", "快记已在其他位置更新,草稿已保留,请重新读取附件后确认", false, 409);
|
|
39252
|
+
const attachments = input.attachments === void 0 ? previous?.attachments : recordReeditAttachmentSelection(input.attachments, owner.contentPayload);
|
|
39253
|
+
if (owner.displayKind === 1 && (attachments?.length ?? 0) > 0) throw new ArkmePluginError("record-reedit-shape-unsupported", "长文只支持纯文本,不能添加附件;请使用普通快记", false, 409);
|
|
39254
|
+
const candidateText = input.newText ?? previous?.textContent ?? owner.textContent;
|
|
38549
39255
|
const textContent = arkmeRecordTextFormat(owner.contentPayload) === "markdown" ? candidateText : candidateText.trim();
|
|
38550
|
-
const title = (input.newTitle
|
|
39256
|
+
const title = (input.newTitle ?? previous?.title ?? owner.title).trim();
|
|
38551
39257
|
const maxTextLength = owner.displayKind === 1 ? 4e4 : this.runtime.config.maxTextLength;
|
|
38552
|
-
|
|
39258
|
+
const hasVoice = stringValue$2(objectValue$2(owner.contentPayload?.voice).source_file_asset_uid) !== "";
|
|
39259
|
+
const hasMedia = attachments === void 0 ? recordReeditHasAttachments(owner.contentPayload) : attachments.length > 0;
|
|
39260
|
+
if (!draftOnly && textContent.trim() === "" && !hasVoice && !hasMedia || textContent.length > maxTextLength || title.length > 100) throw new ArkmePluginError("record-reedit-content-invalid", "重新编辑的标题或正文长度无效", false);
|
|
38553
39261
|
recordReeditContentPayloadForWrite(owner.contentPayload, owner.textContent, textContent);
|
|
39262
|
+
if (input.attachments !== void 0 && !draftOnly) {
|
|
39263
|
+
for (const item of attachments ?? []) if (item.fileRef !== void 0) await this.recordReeditFiles().readLocal(item.fileRef);
|
|
39264
|
+
}
|
|
39265
|
+
if ((await this.runtime.requireSession()).userId !== session.userId) throw new ArkmePluginError("record-reedit-account-changed", "当前账号已变化,请切回原账号后重新确认", false, 409);
|
|
38554
39266
|
const preservesExpectedBaseline = expectedBaseVersion !== void 0 && previous?.baseVersion === expectedBaseVersion;
|
|
38555
|
-
const
|
|
38556
|
-
const
|
|
38557
|
-
const
|
|
39267
|
+
const retainsAttachmentBaseline = input.attachments === void 0 && previous?.attachments !== void 0;
|
|
39268
|
+
const baseVersion = retainsAttachmentBaseline ? previous.baseVersion : expectedBaseVersion ?? owner.version;
|
|
39269
|
+
const baseContentFingerprint = preservesExpectedBaseline || retainsAttachmentBaseline ? previous.baseContentFingerprint : owner.fingerprint;
|
|
39270
|
+
const editing = await this.putRecordReeditDraft(session.userId, {
|
|
38558
39271
|
schemaVersion: 1,
|
|
38559
39272
|
sourceIdentityKey: owner.sourceIdentityKey,
|
|
38560
39273
|
lastSourceRef: sourceRef,
|
|
38561
39274
|
itemUid,
|
|
38562
39275
|
title,
|
|
38563
39276
|
textContent,
|
|
39277
|
+
...attachments === void 0 ? {} : { attachments },
|
|
38564
39278
|
baseVersion,
|
|
38565
39279
|
baseContentFingerprint,
|
|
38566
39280
|
editDurationMillis: previous?.editDurationMillis ?? owner.editDurationMillis,
|
|
38567
39281
|
updatedAtMillis: Date.now()
|
|
38568
|
-
});
|
|
39282
|
+
}, input.expectedDraftRevision ?? previous?.draftRevision ?? 0);
|
|
38569
39283
|
return {
|
|
38570
39284
|
expectedUserId: session.userId,
|
|
38571
39285
|
sourceRef,
|
|
@@ -38581,17 +39295,36 @@ var RecordService = class {
|
|
|
38581
39295
|
newTitle: title,
|
|
38582
39296
|
newTextPreview: arkmeEmojiTokenSafePrefix(textContent, 160, "codeUnits"),
|
|
38583
39297
|
sendAtMillis: owner.sendAtMillis,
|
|
38584
|
-
preservesAttachments: recordReeditHasAttachments(owner.contentPayload)
|
|
39298
|
+
preservesAttachments: attachments === void 0 && recordReeditHasAttachments(owner.contentPayload),
|
|
39299
|
+
...attachments === void 0 ? {} : { attachmentChanges: recordReeditAttachmentChanges(owner.contentPayload, attachments) }
|
|
38585
39300
|
};
|
|
38586
39301
|
}
|
|
38587
39302
|
async recordReeditEditor(sourceRefInput, itemUidInput) {
|
|
38588
39303
|
const session = await this.runtime.requireSession();
|
|
38589
|
-
|
|
38590
|
-
|
|
39304
|
+
return (await this.loadRecordReeditEditor(sourceRefInput.trim(), itemUidInput.trim(), session)).view;
|
|
39305
|
+
}
|
|
39306
|
+
async loadRecordReeditEditor(sourceRef, itemUid, session) {
|
|
39307
|
+
this.assertReeditActive();
|
|
38591
39308
|
if (sourceRef === "" || itemUid === "") throw new ArkmePluginError("record-reedit-target-invalid", "重新编辑目标无效", false);
|
|
38592
39309
|
const owner = await this.recordReeditOwnerSnapshot(sourceRef, itemUid, session);
|
|
38593
|
-
const
|
|
38594
|
-
|
|
39310
|
+
const baselineKey = this.reeditBaselineKey(session.userId, sourceRef, itemUid);
|
|
39311
|
+
await this.submissions.settleKnownCompletions(session.userId, owner.sourceIdentityKey, itemUid);
|
|
39312
|
+
let draft = await this.runtime.stateStore.getRecordReeditDraft(session.userId, owner.sourceIdentityKey, itemUid);
|
|
39313
|
+
if (!draft) {
|
|
39314
|
+
const failed = (await this.runtime.stateStore.listRecordReeditSubmissions(session.userId)).find((job) => job.context.itemUid === itemUid && job.context.sourceIdentityKey === owner.sourceIdentityKey && job.state === "failed");
|
|
39315
|
+
if (failed) draft = await this.putRecordReeditDraft(session.userId, {
|
|
39316
|
+
...failed.draft,
|
|
39317
|
+
lastSourceRef: sourceRef
|
|
39318
|
+
}, 0);
|
|
39319
|
+
}
|
|
39320
|
+
const selections = recordReeditMediaGroups(owner.contentPayload).map((group) => ({ fileAssetUid: group.fileAssetUid }));
|
|
39321
|
+
const blocks = await this.recordReeditMediaBlocks(owner, session);
|
|
39322
|
+
const voiceFileAssetUid = stringValue$2(objectValue$2(owner.contentPayload?.voice).source_file_asset_uid);
|
|
39323
|
+
const voiceBlock = blocks.find((block) => block.kind === "audio" && block.fileAssetUid === voiceFileAssetUid);
|
|
39324
|
+
const attachments = await this.recordReeditAttachmentViews(owner, selections, blocks);
|
|
39325
|
+
const draftAttachments = draft?.attachments === void 0 ? void 0 : await this.recordReeditAttachmentViews(owner, draft.attachments, blocks);
|
|
39326
|
+
if ((await this.runtime.requireSession()).userId !== session.userId) throw new ArkmePluginError("record-reedit-account-changed", "当前账号已变化,请重新打开", false, 409);
|
|
39327
|
+
const snapshot = {
|
|
38595
39328
|
sourceRef,
|
|
38596
39329
|
itemUid,
|
|
38597
39330
|
title: owner.title,
|
|
@@ -38603,40 +39336,131 @@ var RecordService = class {
|
|
|
38603
39336
|
version: owner.version,
|
|
38604
39337
|
maxTextLength: owner.displayKind === 1 ? 4e4 : this.runtime.config.maxTextLength,
|
|
38605
39338
|
preservesAttachments: recordReeditHasAttachments(owner.contentPayload),
|
|
39339
|
+
attachments,
|
|
39340
|
+
hasVoice: voiceFileAssetUid !== "",
|
|
39341
|
+
...voiceBlock ? { voiceBlock } : {},
|
|
39342
|
+
maxAttachments: owner.displayKind === 1 ? 0 : 9,
|
|
38606
39343
|
...draft === void 0 ? {} : { draft: {
|
|
38607
39344
|
title: draft.title,
|
|
38608
39345
|
textContent: draft.textContent,
|
|
38609
|
-
updatedAtMillis: draft.updatedAtMillis
|
|
39346
|
+
updatedAtMillis: draft.updatedAtMillis,
|
|
39347
|
+
baseVersion: draft.baseVersion,
|
|
39348
|
+
draftRevision: draft.draftRevision,
|
|
39349
|
+
...draftAttachments === void 0 ? {} : { attachments: draftAttachments }
|
|
38610
39350
|
} }
|
|
38611
39351
|
};
|
|
39352
|
+
this.assertReeditActive();
|
|
39353
|
+
const editor = {
|
|
39354
|
+
owner,
|
|
39355
|
+
view: snapshot
|
|
39356
|
+
};
|
|
39357
|
+
this.reeditEditors.delete(baselineKey);
|
|
39358
|
+
if (this.reeditEditors.size >= 100) this.reeditEditors.delete(this.reeditEditors.keys().next().value);
|
|
39359
|
+
this.reeditEditors.set(baselineKey, editor);
|
|
39360
|
+
return editor;
|
|
38612
39361
|
}
|
|
38613
39362
|
async commitRecordReedit(context) {
|
|
39363
|
+
const session = await this.runtime.requireSession();
|
|
39364
|
+
if (session.userId !== context.expectedUserId) throw new ArkmePluginError("record-reedit-account-changed", "账号已切换,请重新确认", false, 409);
|
|
39365
|
+
await this.submissions.settleKnownCompletions(session.userId, context.sourceIdentityKey, context.itemUid);
|
|
39366
|
+
if ((await this.runtime.stateStore.listRecordReeditSubmissions(session.userId)).some((job) => job.context.itemUid === context.itemUid && job.context.sourceIdentityKey === context.sourceIdentityKey && [
|
|
39367
|
+
"pending",
|
|
39368
|
+
"committing",
|
|
39369
|
+
"uncertain"
|
|
39370
|
+
].includes(job.state))) throw new ArkmePluginError("record-reedit-in-progress", "该快记已有提交正在保存或核对,请稍后重新确认", false, 409);
|
|
39371
|
+
const draft = await this.runtime.stateStore.getRecordReeditDraft(session.userId, context.sourceIdentityKey, context.itemUid);
|
|
39372
|
+
if (!draft || draft.draftRevision !== context.draftRevision) throw new ArkmePluginError("record-reedit-draft-changed", "重新编辑草稿已变化,请重新确认", false, 409);
|
|
39373
|
+
const command = structuredClone({
|
|
39374
|
+
context,
|
|
39375
|
+
draft
|
|
39376
|
+
});
|
|
39377
|
+
return await this.executeRecordReeditCommand(command, async (fingerprint, owner, write) => {
|
|
39378
|
+
const selections = draft.attachments ?? recordReeditMediaGroups(owner.contentPayload).map((group) => ({ fileAssetUid: group.fileAssetUid }));
|
|
39379
|
+
const blocks = await this.recordReeditMediaBlocks(owner, session);
|
|
39380
|
+
const attachments = await this.recordReeditAttachmentViews(owner, selections, blocks);
|
|
39381
|
+
const voiceFileAssetUid = stringValue$2(objectValue$2(owner.contentPayload?.voice).source_file_asset_uid);
|
|
39382
|
+
const voiceBlock = blocks.find((block) => block.kind === "audio" && block.fileAssetUid === voiceFileAssetUid);
|
|
39383
|
+
if ((await this.runtime.stateStore.getRecordReeditDraft(session.userId, context.sourceIdentityKey, context.itemUid))?.draftRevision !== draft.draftRevision) throw new ArkmePluginError("record-reedit-draft-changed", "重新编辑草稿已变化,请重新确认", false, 409);
|
|
39384
|
+
let failure;
|
|
39385
|
+
const outcome = await this.submissions.writeConfirmed({
|
|
39386
|
+
...command,
|
|
39387
|
+
attachments,
|
|
39388
|
+
...voiceFileAssetUid ? { voiceFileAssetUid } : {},
|
|
39389
|
+
...voiceBlock ? { voiceBlock } : {}
|
|
39390
|
+
}, fingerprint, async () => {
|
|
39391
|
+
try {
|
|
39392
|
+
if ((await this.runtime.stateStore.getRecordReeditDraft(session.userId, context.sourceIdentityKey, context.itemUid))?.draftRevision !== draft.draftRevision) throw new ArkmePluginError("record-reedit-draft-changed", "重新编辑草稿已变化,请重新确认", false, 409);
|
|
39393
|
+
return {
|
|
39394
|
+
kind: "committed",
|
|
39395
|
+
result: await write()
|
|
39396
|
+
};
|
|
39397
|
+
} catch (error) {
|
|
39398
|
+
failure = error;
|
|
39399
|
+
return this.recordReeditExecutionFailure(error, true);
|
|
39400
|
+
}
|
|
39401
|
+
});
|
|
39402
|
+
if (outcome.kind === "committed") return outcome.result;
|
|
39403
|
+
throw failure;
|
|
39404
|
+
});
|
|
39405
|
+
}
|
|
39406
|
+
async executeRecordReeditCommand(command, atWrite) {
|
|
39407
|
+
const { context } = command;
|
|
39408
|
+
const key = JSON.stringify([
|
|
39409
|
+
context.expectedUserId,
|
|
39410
|
+
context.sourceIdentityKey,
|
|
39411
|
+
context.itemUid
|
|
39412
|
+
]);
|
|
39413
|
+
if (this.activeRecordCommits.has(key)) throw new ArkmePluginError("record-reedit-in-progress", "该快记正在提交,请稍后重新确认", false, 409);
|
|
39414
|
+
this.activeRecordCommits.add(key);
|
|
39415
|
+
try {
|
|
39416
|
+
return await this.performRecordReeditCommit(command, atWrite);
|
|
39417
|
+
} finally {
|
|
39418
|
+
this.activeRecordCommits.delete(key);
|
|
39419
|
+
}
|
|
39420
|
+
}
|
|
39421
|
+
async performRecordReeditCommit(command, atWrite) {
|
|
39422
|
+
const { context, draft } = command;
|
|
38614
39423
|
const session = await this.runtime.requireSession();
|
|
38615
39424
|
if (session.userId !== context.expectedUserId) throw new ArkmePluginError("record-reedit-account-changed", "当前账号已变化,请切回原账号后重新确认", false, 409);
|
|
38616
39425
|
const source = await this.source.openSourceRef(context.sourceRef, session.userId);
|
|
38617
|
-
|
|
38618
|
-
if (sourceIdentityKey !== context.sourceIdentityKey) throw new ArkmePluginError("record-reedit-source-changed", "重新编辑来源已变化,请重新发起", false, 409);
|
|
38619
|
-
const draft = await this.runtime.stateStore.getRecordReeditDraft(session.userId, sourceIdentityKey, context.itemUid);
|
|
39426
|
+
if (await this.recordReeditSourceIdentityKey(source) !== context.sourceIdentityKey) throw new ArkmePluginError("record-reedit-source-changed", "重新编辑来源已变化,请重新发起", false, 409);
|
|
38620
39427
|
if (draft === void 0 || draft.draftRevision !== context.draftRevision) throw new ArkmePluginError("record-reedit-draft-changed", "重新编辑草稿已变化,请重新确认", false, 409);
|
|
38621
39428
|
const owner = await this.recordReeditOwnerSnapshot(context.sourceRef, context.itemUid, session, source);
|
|
38622
39429
|
if (owner.version !== context.baseVersion || owner.fingerprint !== context.baseContentFingerprint) throw new ArkmePluginError("record-reedit-conflict", "快记已在其他位置更新,草稿已保留,请检查后重新确认", false, 409);
|
|
39430
|
+
if (draft.textContent.trim() === "" && stringValue$2(objectValue$2(owner.contentPayload?.voice).source_file_asset_uid) === "" && (draft.attachments === void 0 ? !recordReeditHasAttachments(owner.contentPayload) : draft.attachments.length === 0)) throw new ArkmePluginError("record-reedit-content-invalid", "请保留正文或至少一个附件", false);
|
|
38623
39431
|
const body = this.recordReeditUpdateBody(owner, draft.title, draft.textContent);
|
|
38624
|
-
|
|
38625
|
-
|
|
39432
|
+
if (draft.attachments !== void 0) {
|
|
39433
|
+
const selection = recordReeditAttachmentSelection(draft.attachments, owner.contentPayload);
|
|
39434
|
+
const fileRefs = selection.flatMap((item) => item.fileRef === void 0 ? [] : [item.fileRef]);
|
|
39435
|
+
const assets = fileRefs.length === 0 ? [] : await this.recordReeditFiles().uploadRefs(fileRefs);
|
|
39436
|
+
if ((await this.runtime.requireSession()).userId !== session.userId) throw new ArkmePluginError("record-reedit-account-changed", "当前账号已变化,请切回原账号后重新确认", false, 409);
|
|
39437
|
+
const updated = recordReeditWithAttachments(recordReeditReadContentPayload(body.content_payload), selection, new Map(fileRefs.map((ref, index) => [ref, assets[index]])));
|
|
39438
|
+
body.template_kind = updated.templateKind;
|
|
39439
|
+
body.content_payload = updated.contentPayload;
|
|
39440
|
+
if (draft.textContent === "" && updated.contentPayload.text_state === 1) updated.contentPayload.text_state = 3;
|
|
39441
|
+
}
|
|
39442
|
+
const committedContentPayload = recordReeditReadContentPayload(body.content_payload);
|
|
39443
|
+
const { source: _source, sourceIdentityKey: _sourceIdentityKey, fingerprint: _fingerprint, attachmentData: _attachmentData, ...baseFields } = owner;
|
|
38626
39444
|
const expectedCommittedFingerprint = recordReeditFingerprint({
|
|
38627
39445
|
...baseFields,
|
|
38628
39446
|
title: draft.title,
|
|
38629
39447
|
textContent: draft.textContent,
|
|
38630
|
-
|
|
39448
|
+
templateKind: numberValue$20(body.template_kind),
|
|
39449
|
+
...committedContentPayload === void 0 ? {} : { contentPayload: committedContentPayload },
|
|
38631
39450
|
version: owner.version + 1
|
|
38632
39451
|
});
|
|
38633
|
-
|
|
38634
|
-
|
|
38635
|
-
|
|
38636
|
-
|
|
38637
|
-
|
|
38638
|
-
|
|
38639
|
-
|
|
39452
|
+
if ((await this.runtime.requireSession()).userId !== session.userId) throw new ArkmePluginError("record-reedit-account-changed", "当前账号已变化,请切回原账号后重新确认", false, 409);
|
|
39453
|
+
return await atWrite(expectedCommittedFingerprint, owner, async () => {
|
|
39454
|
+
if ((await this.runtime.requireSession()).userId !== session.userId) throw new ArkmePluginError("record-reedit-account-changed", "账号已切换,请恢复编辑后重新确认", false, 409);
|
|
39455
|
+
this.assertReeditActive();
|
|
39456
|
+
try {
|
|
39457
|
+
const data = await this.runtime.authenticatedPost("/api/v1/records/update", body, session, void 0, { trackWriteOutcome: true });
|
|
39458
|
+
return this.recordReeditCommitResult(context, data);
|
|
39459
|
+
} catch (error) {
|
|
39460
|
+
if (!(error instanceof ArkmePluginError) || error.writeOutcomeUnknown !== true) throw error;
|
|
39461
|
+
return await this.reconcileRecordReeditUnknownOutcome(session, context, expectedCommittedFingerprint);
|
|
39462
|
+
}
|
|
39463
|
+
});
|
|
38640
39464
|
}
|
|
38641
39465
|
async prepareDiscardRecordReeditDraft(sourceRefInput, itemUidInput) {
|
|
38642
39466
|
const session = await this.runtime.requireSession();
|
|
@@ -38663,7 +39487,8 @@ var RecordService = class {
|
|
|
38663
39487
|
const source = await this.source.openSourceRef(context.sourceRef, session.userId);
|
|
38664
39488
|
const sourceIdentityKey = await this.recordReeditSourceIdentityKey(source);
|
|
38665
39489
|
if (sourceIdentityKey !== context.sourceIdentityKey) throw new ArkmePluginError("record-reedit-source-changed", "重新编辑来源已变化,请重新发起", false, 409);
|
|
38666
|
-
|
|
39490
|
+
this.assertReeditActive();
|
|
39491
|
+
if (!await this.runtime.stateStore.discardRecordReeditCandidate(session.userId, sourceIdentityKey, context.itemUid, context.draftRevision)) throw new ArkmePluginError("record-reedit-draft-changed", "重新编辑草稿已变化,请重新确认", false, 409);
|
|
38667
39492
|
return {
|
|
38668
39493
|
status: "discarded",
|
|
38669
39494
|
itemUid: context.itemUid
|
|
@@ -38707,6 +39532,9 @@ var RecordService = class {
|
|
|
38707
39532
|
if (this.runtime.config.richMediaSendEnabled === false) throw new ArkmePluginError("rich-content-disabled", "长文编辑已被插件配置关闭", false, 403);
|
|
38708
39533
|
const session = await this.runtime.requireSession();
|
|
38709
39534
|
const detail = await this.longArticleDetail(sourceRef, itemUid);
|
|
39535
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
39536
|
+
const sourceIdentityKey = await this.recordReeditSourceIdentityKey(source);
|
|
39537
|
+
this.assertLongArticleDraftHasNoAttachmentChanges(await this.runtime.stateStore.getRecordReeditDraft(session.userId, sourceIdentityKey, itemUid));
|
|
38710
39538
|
const title = input.title.trim();
|
|
38711
39539
|
const textContent = detail.textFormat === "markdown" ? input.textContent : input.textContent.trim();
|
|
38712
39540
|
const editDurationMillis = Math.max(0, Math.trunc(input.editDurationMillis));
|
|
@@ -38731,11 +39559,12 @@ var RecordService = class {
|
|
|
38731
39559
|
if (uid === void 0) return await this.runtime.stateStore.getLongArticleDraft(session.userId, sourceRef);
|
|
38732
39560
|
const sourceIdentityKey = await this.recordReeditSourceIdentityKey(source);
|
|
38733
39561
|
const current = await this.runtime.stateStore.getRecordReeditDraft(session.userId, sourceIdentityKey, uid);
|
|
39562
|
+
this.assertLongArticleDraftHasNoAttachmentChanges(current);
|
|
38734
39563
|
if (current !== void 0) return this.longArticleDraftFromRecordReedit(current, sourceRef);
|
|
38735
39564
|
const legacy = await this.runtime.stateStore.getLongArticleDraft(session.userId, sourceRef, uid);
|
|
38736
39565
|
if (legacy === void 0) return void 0;
|
|
38737
39566
|
const detail = await this.longArticleDetail(sourceRef, uid);
|
|
38738
|
-
const migrated = await this.
|
|
39567
|
+
const migrated = await this.putRecordReeditDraft(session.userId, {
|
|
38739
39568
|
schemaVersion: 1,
|
|
38740
39569
|
sourceIdentityKey,
|
|
38741
39570
|
lastSourceRef: sourceRef,
|
|
@@ -38746,7 +39575,8 @@ var RecordService = class {
|
|
|
38746
39575
|
baseContentFingerprint: this.longArticleContentFingerprint(detail),
|
|
38747
39576
|
editDurationMillis: legacy.durationMillis,
|
|
38748
39577
|
updatedAtMillis: legacy.updatedAtMillis
|
|
38749
|
-
});
|
|
39578
|
+
}, 0);
|
|
39579
|
+
this.assertReeditActive();
|
|
38750
39580
|
await this.runtime.stateStore.removeLongArticleDraft(session.userId, sourceRef, uid);
|
|
38751
39581
|
return this.longArticleDraftFromRecordReedit(migrated, sourceRef);
|
|
38752
39582
|
}
|
|
@@ -38767,8 +39597,9 @@ var RecordService = class {
|
|
|
38767
39597
|
}
|
|
38768
39598
|
const sourceIdentityKey = await this.recordReeditSourceIdentityKey(source);
|
|
38769
39599
|
const current = await this.runtime.stateStore.getRecordReeditDraft(session.userId, sourceIdentityKey, itemUid);
|
|
39600
|
+
this.assertLongArticleDraftHasNoAttachmentChanges(current);
|
|
38770
39601
|
const detail = current === void 0 ? await this.longArticleDetail(draft.sourceRef, itemUid) : void 0;
|
|
38771
|
-
await this.
|
|
39602
|
+
await this.putRecordReeditDraft(session.userId, {
|
|
38772
39603
|
schemaVersion: 1,
|
|
38773
39604
|
sourceIdentityKey,
|
|
38774
39605
|
lastSourceRef: draft.sourceRef,
|
|
@@ -38779,7 +39610,8 @@ var RecordService = class {
|
|
|
38779
39610
|
baseContentFingerprint: current?.baseContentFingerprint ?? this.longArticleContentFingerprint(detail),
|
|
38780
39611
|
editDurationMillis: Math.max(0, Math.trunc(draft.durationMillis)),
|
|
38781
39612
|
updatedAtMillis: Date.now()
|
|
38782
|
-
});
|
|
39613
|
+
}, current?.draftRevision ?? 0);
|
|
39614
|
+
this.assertReeditActive();
|
|
38783
39615
|
await this.runtime.stateStore.removeLongArticleDraft(session.userId, draft.sourceRef, itemUid);
|
|
38784
39616
|
}
|
|
38785
39617
|
async removeLongArticleDraft(sourceRef, itemUid) {
|
|
@@ -38789,13 +39621,35 @@ var RecordService = class {
|
|
|
38789
39621
|
if (uid !== void 0) {
|
|
38790
39622
|
const sourceIdentityKey = await this.recordReeditSourceIdentityKey(source);
|
|
38791
39623
|
const current = await this.runtime.stateStore.getRecordReeditDraft(session.userId, sourceIdentityKey, uid);
|
|
38792
|
-
|
|
39624
|
+
this.assertLongArticleDraftHasNoAttachmentChanges(current);
|
|
39625
|
+
if (current !== void 0) {
|
|
39626
|
+
this.assertReeditActive();
|
|
39627
|
+
await this.runtime.stateStore.removeRecordReeditDraft(session.userId, sourceIdentityKey, uid, current.draftRevision);
|
|
39628
|
+
}
|
|
38793
39629
|
}
|
|
39630
|
+
this.assertReeditActive();
|
|
38794
39631
|
await this.runtime.stateStore.removeLongArticleDraft(session.userId, sourceRef, uid);
|
|
38795
39632
|
}
|
|
38796
39633
|
async recordReeditSourceIdentityKey(source) {
|
|
38797
39634
|
return `arkme-record-reedit-source-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`record-reedit-source-v1:${String(source.userId)}:${source.kind}:${source.ownerRef}`).digest("base64url")}`;
|
|
38798
39635
|
}
|
|
39636
|
+
async putRecordReeditDraft(userId, draft, expectedRevision) {
|
|
39637
|
+
try {
|
|
39638
|
+
this.assertReeditActive();
|
|
39639
|
+
const persist = async () => {
|
|
39640
|
+
this.assertReeditActive();
|
|
39641
|
+
return await this.runtime.stateStore.putRecordReeditDraft(userId, draft, expectedRevision);
|
|
39642
|
+
};
|
|
39643
|
+
const refs = draft.attachments?.flatMap((item) => item.fileRef === void 0 ? [] : [item.fileRef]) ?? [];
|
|
39644
|
+
return refs.length === 0 ? await persist() : await this.recordReeditFiles().withReferences(refs, userId, persist);
|
|
39645
|
+
} catch (error) {
|
|
39646
|
+
if (error instanceof ArkmeRecordReeditDraftConflict) throw new ArkmePluginError("record-reedit-draft-changed", "重新编辑草稿已变化,请重新确认", false, 409);
|
|
39647
|
+
throw error;
|
|
39648
|
+
}
|
|
39649
|
+
}
|
|
39650
|
+
assertLongArticleDraftHasNoAttachmentChanges(draft) {
|
|
39651
|
+
if (draft?.attachments !== void 0) throw new ArkmePluginError("record-reedit-attachments-editor-required", "此草稿包含附件编辑,请使用重新编辑入口或 Arkme 重新编辑工具继续,不能在纯文本长文编辑器中覆盖", false, 409);
|
|
39652
|
+
}
|
|
38799
39653
|
async recordReeditOwnerSnapshot(sourceRef, itemUid, session, openedSource) {
|
|
38800
39654
|
const source = openedSource ?? await this.source.openSourceRef(sourceRef, session.userId);
|
|
38801
39655
|
const data = await this.runtime.authenticatedPost("/api/v1/records/detail", { record_uid: itemUid }, session);
|
|
@@ -38836,12 +39690,59 @@ var RecordService = class {
|
|
|
38836
39690
|
version
|
|
38837
39691
|
};
|
|
38838
39692
|
return {
|
|
39693
|
+
attachmentData: data,
|
|
38839
39694
|
source,
|
|
38840
39695
|
sourceIdentityKey: await this.recordReeditSourceIdentityKey(source),
|
|
38841
39696
|
...snapshotFields,
|
|
38842
39697
|
fingerprint: recordReeditFingerprint(snapshotFields)
|
|
38843
39698
|
};
|
|
38844
39699
|
}
|
|
39700
|
+
recordReeditFiles() {
|
|
39701
|
+
if (this.reeditFiles === void 0) throw new ArkmePluginError("file-flow-unavailable", "当前宿主不支持本地文件流程,请升级插件", false, 501);
|
|
39702
|
+
return this.reeditFiles;
|
|
39703
|
+
}
|
|
39704
|
+
async recordReeditMediaBlocks(owner, session) {
|
|
39705
|
+
if (!recordReeditHasAttachments(owner.contentPayload)) return [];
|
|
39706
|
+
const hydration = await this.media.hydrateRecordMediaPage([owner.attachmentData], session);
|
|
39707
|
+
return this.media.richContentBlocks(owner.attachmentData, session.userId, hydration.displayItemsByRecordUid.get(owner.itemUid) ?? []);
|
|
39708
|
+
}
|
|
39709
|
+
async recordReeditAttachmentViews(owner, selections, blocks) {
|
|
39710
|
+
if (selections.length === 0) return [];
|
|
39711
|
+
const localFiles = selections.some((item) => item.fileRef !== void 0) ? await this.recordReeditFiles().files() : [];
|
|
39712
|
+
const rawRefs = listValue$17(objectValue$2(objectValue$2(owner.attachmentData.record_core).content_payload).media_refs).map(objectValue$2);
|
|
39713
|
+
return selections.map((selection) => {
|
|
39714
|
+
if (selection.fileRef !== void 0) {
|
|
39715
|
+
const file = localFiles.find((value) => value.fileRef === selection.fileRef);
|
|
39716
|
+
return {
|
|
39717
|
+
selection,
|
|
39718
|
+
localFile: file ?? {
|
|
39719
|
+
fileRef: selection.fileRef,
|
|
39720
|
+
fileName: "附件已不可用,请移除或重新添加",
|
|
39721
|
+
mimeType: "application/octet-stream",
|
|
39722
|
+
size: 0,
|
|
39723
|
+
fileKind: 4
|
|
39724
|
+
},
|
|
39725
|
+
...file === void 0 ? { unavailable: true } : {}
|
|
39726
|
+
};
|
|
39727
|
+
}
|
|
39728
|
+
const raw = rawRefs.find((item) => item.file_asset_uid === selection.fileAssetUid);
|
|
39729
|
+
const block = blocks.find((item) => item.fileAssetUid === selection.fileAssetUid);
|
|
39730
|
+
const fileName = block?.fileName ?? (stringValue$2(raw?.file_name) || "附件");
|
|
39731
|
+
const mimeType = block?.mimeType ?? arkmeNormalizedFileMimeType(stringValue$2(raw?.mime_type), fileName);
|
|
39732
|
+
return {
|
|
39733
|
+
selection,
|
|
39734
|
+
asset: {
|
|
39735
|
+
fileAssetUid: selection.fileAssetUid,
|
|
39736
|
+
fileName,
|
|
39737
|
+
mimeType,
|
|
39738
|
+
size: block?.size ?? numberValue$20(raw?.size),
|
|
39739
|
+
fileKind: arkmePickedFileKind(mimeType, fileName)
|
|
39740
|
+
},
|
|
39741
|
+
...block === void 0 ? {} : { block },
|
|
39742
|
+
...raw === void 0 ? { unavailable: true } : {}
|
|
39743
|
+
};
|
|
39744
|
+
});
|
|
39745
|
+
}
|
|
38845
39746
|
recordReeditUpdateBody(owner, title, textContent) {
|
|
38846
39747
|
const contentPayload = recordReeditContentPayloadForWrite(owner.contentPayload, owner.textContent, textContent);
|
|
38847
39748
|
return {
|
|
@@ -38856,13 +39757,12 @@ var RecordService = class {
|
|
|
38856
39757
|
version: owner.version
|
|
38857
39758
|
};
|
|
38858
39759
|
}
|
|
38859
|
-
|
|
39760
|
+
recordReeditCommitResult(context, data) {
|
|
38860
39761
|
const core = objectValue$2(data.record_core);
|
|
38861
39762
|
const itemUid = stringValue$2(core.record_uid).trim();
|
|
38862
39763
|
const version = Math.trunc(numberValue$20(core.version));
|
|
38863
39764
|
const revisionUid = stringValue$2(data.revision_uid).trim();
|
|
38864
39765
|
if (itemUid !== context.itemUid || version !== context.baseVersion + 1 || revisionUid === "") throw new ArkmePluginError("record-reedit-response-invalid", "快记更新响应不完整,正在等待状态确认", false, 502, { writeOutcomeUnknown: true });
|
|
38865
|
-
if (!await this.runtime.stateStore.removeRecordReeditDraft(userId, context.sourceIdentityKey, context.itemUid, draft.draftRevision)) throw new ArkmePluginError("record-reedit-draft-changed", "快记已更新,但本机草稿发生变化,请勿重复提交", false, 409);
|
|
38866
39766
|
return {
|
|
38867
39767
|
status: "committed",
|
|
38868
39768
|
itemUid,
|
|
@@ -38871,23 +39771,20 @@ var RecordService = class {
|
|
|
38871
39771
|
projectionState: "pending"
|
|
38872
39772
|
};
|
|
38873
39773
|
}
|
|
38874
|
-
async reconcileRecordReeditUnknownOutcome(session, context,
|
|
39774
|
+
async reconcileRecordReeditUnknownOutcome(session, context, expectedCommittedFingerprint) {
|
|
38875
39775
|
let current;
|
|
38876
39776
|
try {
|
|
38877
39777
|
current = await this.recordReeditOwnerSnapshot(context.sourceRef, context.itemUid, session);
|
|
38878
39778
|
} catch (error) {
|
|
38879
39779
|
throw new ArkmePluginError("record-reedit-outcome-unknown", "提交结果暂时无法确认,草稿已保留,请稍后查询,勿重复提交", false, 409, { cause: error });
|
|
38880
39780
|
}
|
|
38881
|
-
if (current.version === context.baseVersion + 1 && current.fingerprint === expectedCommittedFingerprint) {
|
|
38882
|
-
|
|
38883
|
-
|
|
38884
|
-
|
|
38885
|
-
|
|
38886
|
-
|
|
38887
|
-
|
|
38888
|
-
projectionState: "pending"
|
|
38889
|
-
};
|
|
38890
|
-
}
|
|
39781
|
+
if (current.version === context.baseVersion + 1 && current.fingerprint === expectedCommittedFingerprint) return {
|
|
39782
|
+
status: "committed",
|
|
39783
|
+
itemUid: current.itemUid,
|
|
39784
|
+
version: current.version,
|
|
39785
|
+
revisionUid: "",
|
|
39786
|
+
projectionState: "pending"
|
|
39787
|
+
};
|
|
38891
39788
|
if (current.version === context.baseVersion && current.fingerprint === context.baseContentFingerprint) throw new ArkmePluginError("record-reedit-outcome-unknown", "提交结果暂时无法确认,草稿已保留,请稍后查询,勿重复提交", false, 409);
|
|
38892
39789
|
throw new ArkmePluginError("record-reedit-conflict", "快记内容已变化,草稿已保留,请检查后重新确认", false, 409);
|
|
38893
39790
|
}
|
|
@@ -39290,6 +40187,7 @@ var RecordService = class {
|
|
|
39290
40187
|
const core = objectValue$2(item.record_core);
|
|
39291
40188
|
const extensionProjection = this.recordExtensionProjection(raw, userId);
|
|
39292
40189
|
const forwardRecords = projectRecordRecordingForward(item.content_payload ?? core.content_payload);
|
|
40190
|
+
const contentBlocks = this.media.richContentBlocks(raw, userId, options.displayItems);
|
|
39293
40191
|
return {
|
|
39294
40192
|
itemUid: stringValue$2(item.record_uid ?? core.record_uid).trim(),
|
|
39295
40193
|
senderName: stringValue$2(item.nickname).trim() || "我",
|
|
@@ -39305,11 +40203,11 @@ var RecordService = class {
|
|
|
39305
40203
|
updateAtMillis: numberValue$20(item.update_at ?? core.update_at),
|
|
39306
40204
|
recordDurationMillis: numberValue$20(item.record_duration_millis ?? core.record_duration_millis),
|
|
39307
40205
|
editDurationMillis: numberValue$20(item.edit_duration_millis ?? core.edit_duration_millis),
|
|
39308
|
-
contentBlocks
|
|
40206
|
+
contentBlocks,
|
|
39309
40207
|
...forwardRecords === void 0 ? {} : { forwardRecords },
|
|
39310
40208
|
...extensionProjection === void 0 ? {} : extensionProjection,
|
|
39311
40209
|
...options.selfTopic === void 0 ? {} : { selfTopic: options.selfTopic },
|
|
39312
|
-
...options.mediaUnavailable === true ? { mediaUnavailable: true } : {}
|
|
40210
|
+
...options.mediaUnavailable === true || this.media.recordMediaUnavailable(raw, contentBlocks) ? { mediaUnavailable: true } : {}
|
|
39313
40211
|
};
|
|
39314
40212
|
}
|
|
39315
40213
|
recordItem(raw, userId, options = {}) {
|
|
@@ -39319,6 +40217,7 @@ var RecordService = class {
|
|
|
39319
40217
|
if (recordUid === "") return void 0;
|
|
39320
40218
|
const extensionProjection = userId === void 0 ? void 0 : this.recordExtensionProjection(raw, userId);
|
|
39321
40219
|
const forwardRecords = projectRecordRecordingForward(item.content_payload ?? core.content_payload);
|
|
40220
|
+
const contentBlocks = userId === void 0 ? void 0 : this.media.richContentBlocks(raw, userId, options.displayItems);
|
|
39322
40221
|
return {
|
|
39323
40222
|
recordUid,
|
|
39324
40223
|
sendAtMillis: numberValue$20(item.send_at ?? core.send_at),
|
|
@@ -39331,9 +40230,9 @@ var RecordService = class {
|
|
|
39331
40230
|
creationSource: recordCreationSource(raw),
|
|
39332
40231
|
displayKind: numberValue$20(item.display_kind ?? core.display_kind),
|
|
39333
40232
|
...forwardRecords === void 0 ? {} : { forwardRecords },
|
|
39334
|
-
...
|
|
40233
|
+
...contentBlocks === void 0 ? {} : { contentBlocks },
|
|
39335
40234
|
...extensionProjection === void 0 ? {} : extensionProjection,
|
|
39336
|
-
...options.mediaUnavailable === true ? { mediaUnavailable: true } : {}
|
|
40235
|
+
...options.mediaUnavailable === true || contentBlocks !== void 0 && this.media.recordMediaUnavailable(raw, contentBlocks) ? { mediaUnavailable: true } : {}
|
|
39337
40236
|
};
|
|
39338
40237
|
}
|
|
39339
40238
|
isDSHAgentInput(raw) {
|
|
@@ -41627,7 +42526,7 @@ var ChatService = class {
|
|
|
41627
42526
|
const evidence = await this.mentionContentPayload(source, textContent, normalizedText, options.humanMentions ?? [], options.botMentions ?? [], session, options.signal, options.textFormat);
|
|
41628
42527
|
Object.assign(contentPayload, { mention_metadata: evidence.mention_metadata });
|
|
41629
42528
|
}
|
|
41630
|
-
const data = await this.runtime
|
|
42529
|
+
const data = await postChatMessageCreation(this.runtime, "/api/v1/chats/extensions/children/create", {
|
|
41631
42530
|
chat_session_uid: reference.chatSessionUid,
|
|
41632
42531
|
parent_record_owner_user_id: reference.recordOwnerUserId,
|
|
41633
42532
|
parent_record_uid: reference.recordUid,
|
|
@@ -42001,7 +42900,7 @@ var ChatService = class {
|
|
|
42001
42900
|
const sendAtMillis = Date.now();
|
|
42002
42901
|
const clientRequestId = this.forwardClientRequestId(targetSource.ownerRef, references, sendAtMillis);
|
|
42003
42902
|
const sourceRecordUids = references.some((reference) => reference.sourceKind === "chat_relation") ? [] : references.map((reference) => reference.recordUid).filter((recordUid) => recordUid.trim() !== "");
|
|
42004
|
-
const data = await this.runtime
|
|
42903
|
+
const data = await postChatMessageCreation(this.runtime, "/api/v1/chats/records/forward", {
|
|
42005
42904
|
chat_session_uid: targetSource.ownerRef,
|
|
42006
42905
|
client_request_id: clientRequestId,
|
|
42007
42906
|
...sourceRecordUids.length === 0 ? {} : { source_record_uids: sourceRecordUids },
|
|
@@ -42375,7 +43274,7 @@ var ChatService = class {
|
|
|
42375
43274
|
});
|
|
42376
43275
|
}
|
|
42377
43276
|
async postChatTextRecord(chatSessionUid, text, recordUid, relationUid, sendAtMillis, session, signal, options = {}) {
|
|
42378
|
-
return await this.runtime
|
|
43277
|
+
return await postChatMessageCreation(this.runtime, "/api/v1/chats/records/send", {
|
|
42379
43278
|
chat_session_uid: chatSessionUid,
|
|
42380
43279
|
record_uid: recordUid,
|
|
42381
43280
|
rel_uid: relationUid,
|
|
@@ -42488,7 +43387,7 @@ var ChatService = class {
|
|
|
42488
43387
|
assets
|
|
42489
43388
|
});
|
|
42490
43389
|
}
|
|
42491
|
-
const result = await this.runtime
|
|
43390
|
+
const result = await postChatMessageCreation(this.runtime, "/api/v1/chats/records/send", {
|
|
42492
43391
|
chat_session_uid: source.ownerRef,
|
|
42493
43392
|
rel_uid: relationUid,
|
|
42494
43393
|
...commonBody
|
|
@@ -42598,7 +43497,7 @@ var ChatService = class {
|
|
|
42598
43497
|
if (sticker === void 0 || sticker.is_available === false) throw new ArkmePluginError("favorite-sticker-unavailable", "该收藏表情已不可用", false, 404);
|
|
42599
43498
|
const recordUid = options.recordUid?.trim() || randomUUID();
|
|
42600
43499
|
const relationUid = options.relationUid?.trim() || randomUUID();
|
|
42601
|
-
const result = await this.runtime
|
|
43500
|
+
const result = await postChatMessageCreation(this.runtime, "/api/v1/chats/records/send", {
|
|
42602
43501
|
chat_session_uid: source.ownerRef,
|
|
42603
43502
|
record_uid: recordUid,
|
|
42604
43503
|
rel_uid: relationUid,
|
|
@@ -42854,8 +43753,10 @@ var ChatService = class {
|
|
|
42854
43753
|
...forwardRecords === void 0 ? {} : { forwardRecords },
|
|
42855
43754
|
...sharedRecording === void 0 ? {} : { sharedRecording },
|
|
42856
43755
|
...extensionProjection === void 0 ? {} : extensionProjection,
|
|
43756
|
+
templateKind: numberValue$19(payload.template_kind),
|
|
42857
43757
|
displayKind: numberValue$19(payload.display_kind),
|
|
42858
|
-
contentBlocks
|
|
43758
|
+
contentBlocks,
|
|
43759
|
+
...this.media.recordMediaUnavailable(item, contentBlocks) ? { mediaUnavailable: true } : {}
|
|
42859
43760
|
});
|
|
42860
43761
|
}
|
|
42861
43762
|
this.hydrateTimelineExtensionParents(items);
|
|
@@ -43493,6 +44394,7 @@ var ChatService = class {
|
|
|
43493
44394
|
...captureContext === void 0 ? {} : { captureContext },
|
|
43494
44395
|
contentBlocks,
|
|
43495
44396
|
...forwardRecords === void 0 ? {} : { forwardRecords },
|
|
44397
|
+
...this.media.recordMediaUnavailable(item, contentBlocks) ? { mediaUnavailable: true } : {},
|
|
43496
44398
|
...sharedRecording === void 0 ? {} : { sharedRecording },
|
|
43497
44399
|
...extensionProjection === void 0 ? {} : extensionProjection
|
|
43498
44400
|
}) - 1;
|
|
@@ -76126,6 +77028,12 @@ var MediaService = class {
|
|
|
76126
77028
|
if (stableKey !== void 0) this.stableMediaRefs.set(stableKey, ref);
|
|
76127
77029
|
return ref;
|
|
76128
77030
|
}
|
|
77031
|
+
/** A current Record version does not imply that every media URL was resolved. */
|
|
77032
|
+
recordMediaUnavailable(raw, blocks) {
|
|
77033
|
+
if (this.runtime.config.richMediaRenderEnabled === false) return false;
|
|
77034
|
+
const displayed = new Set(blocks.map((block) => block.fileAssetUid));
|
|
77035
|
+
return this.recordMediaRefs(raw).some((ref) => !displayed.has(stringValue$2(ref.file_asset_uid).trim()));
|
|
77036
|
+
}
|
|
76129
77037
|
richContentBlocks(raw, viewerUserId, hydratedDisplayItems = []) {
|
|
76130
77038
|
if (this.runtime.config.richMediaRenderEnabled === false) return [];
|
|
76131
77039
|
const root = objectValue$2(raw);
|
|
@@ -76603,7 +77511,7 @@ var ArkmeMessageActionGateway = class {
|
|
|
76603
77511
|
};
|
|
76604
77512
|
}
|
|
76605
77513
|
async forwardToChat(input) {
|
|
76606
|
-
const data = await this.runtime
|
|
77514
|
+
const data = await postChatMessageCreation(this.runtime, "/api/v1/chats/records/forward", {
|
|
76607
77515
|
chat_session_uid: input.target.ownerRef,
|
|
76608
77516
|
client_request_id: input.requestId,
|
|
76609
77517
|
source_items: input.references.map((reference) => this.chatForwardSourceItem(reference)),
|
|
@@ -80908,6 +81816,8 @@ var FileTransfers = class {
|
|
|
80908
81816
|
states = /* @__PURE__ */ new Map();
|
|
80909
81817
|
writes = Promise.resolve();
|
|
80910
81818
|
mutations = Promise.resolve();
|
|
81819
|
+
directUploads = Promise.resolve();
|
|
81820
|
+
uploadingRefs = /* @__PURE__ */ new Set();
|
|
80911
81821
|
queue = Promise.resolve();
|
|
80912
81822
|
jobs = /* @__PURE__ */ new Set();
|
|
80913
81823
|
controllers = /* @__PURE__ */ new Set();
|
|
@@ -80932,6 +81842,7 @@ var FileTransfers = class {
|
|
|
80932
81842
|
}
|
|
80933
81843
|
async settled() {
|
|
80934
81844
|
await this.queue;
|
|
81845
|
+
await this.directUploads.catch(() => {});
|
|
80935
81846
|
await Promise.all(this.jobs);
|
|
80936
81847
|
await this.writes;
|
|
80937
81848
|
}
|
|
@@ -81030,7 +81941,7 @@ var FileTransfers = class {
|
|
|
81030
81941
|
const limit = metadata.mimeType.startsWith("image/") ? this.policy.maxImageBytes : this.policy.maxFileBytes;
|
|
81031
81942
|
if (!metadata.fileName.trim() || metadata.fileName.length > 255 || /[\u0000-\u001f]/.test(metadata.fileName) || metadata.mimeType.length > 200 || !/^[a-zA-Z0-9!#$&^_.+-]+\/[a-zA-Z0-9!#$&^_.+-]+$/.test(metadata.mimeType) || !Number.isSafeInteger(metadata.size) || metadata.size <= 0 || metadata.size > limit) throw fail("file-input-invalid", `文件为空或超出限制(${Math.floor(limit / 1024 / 1024)} MiB)`);
|
|
81032
81943
|
}
|
|
81033
|
-
async stage(temporaryPath, metadata, expectedUserId) {
|
|
81944
|
+
async stage(temporaryPath, metadata, expectedUserId, retention) {
|
|
81034
81945
|
const userId = await this.ports.currentUser();
|
|
81035
81946
|
if (expectedUserId !== void 0 && expectedUserId !== userId) throw fail("file-account-changed", "账号已切换,本次文件导入已取消");
|
|
81036
81947
|
const normalizedMetadata = {
|
|
@@ -81053,7 +81964,8 @@ var FileTransfers = class {
|
|
|
81053
81964
|
fileRef: ref,
|
|
81054
81965
|
fileKind: arkmePickedFileKind(normalizedMetadata.mimeType, normalizedMetadata.fileName),
|
|
81055
81966
|
sha256: hash.digest("hex"),
|
|
81056
|
-
createdAtMillis: Date.now()
|
|
81967
|
+
createdAtMillis: Date.now(),
|
|
81968
|
+
...retention ? { retention } : {}
|
|
81057
81969
|
};
|
|
81058
81970
|
await copyFile(temporaryPath, this.path(userId, ref));
|
|
81059
81971
|
await chmod$1(this.path(userId, ref), 384);
|
|
@@ -81070,17 +81982,50 @@ var FileTransfers = class {
|
|
|
81070
81982
|
}
|
|
81071
81983
|
async prune(userId, state) {
|
|
81072
81984
|
const retained = new Set(state.tasks.filter((task) => task.state !== "sent").flatMap((task) => task.fileRefs));
|
|
81985
|
+
let editRefs;
|
|
81986
|
+
try {
|
|
81987
|
+
editRefs = await this.ports.retainedFileRefs?.(userId) ?? [];
|
|
81988
|
+
} catch {
|
|
81989
|
+
return;
|
|
81990
|
+
}
|
|
81991
|
+
for (const ref of editRefs) retained.add(ref);
|
|
81073
81992
|
const completed = new Set(state.tasks.filter((task) => task.state === "sent").flatMap((task) => task.fileRefs));
|
|
81074
|
-
|
|
81075
|
-
if (Date.now() - file.createdAtMillis < 168 * 36e5 || retained.has(file.fileRef) ||
|
|
81076
|
-
|
|
81993
|
+
const expired = Object.values(state.files).filter((file) => {
|
|
81994
|
+
if (Date.now() - file.createdAtMillis < 168 * 36e5 || retained.has(file.fileRef) || this.uploadingRefs.has(`${userId}:${file.fileRef}`)) return false;
|
|
81995
|
+
return file.retention === "references" ? this.ports.retainedFileRefs !== void 0 : completed.has(file.fileRef);
|
|
81996
|
+
});
|
|
81997
|
+
const expiredRefs = new Set(expired.map((file) => file.fileRef));
|
|
81998
|
+
const tasks = state.tasks.filter((task) => task.state !== "sent" || Date.now() - task.createdAtMillis < 168 * 36e5 || task.fileRefs.some((ref) => state.files[ref] !== void 0 && !expiredRefs.has(ref)));
|
|
81999
|
+
if (expired.length === 0 && tasks.length === state.tasks.length) return;
|
|
82000
|
+
const previous = {
|
|
82001
|
+
files: state.files,
|
|
82002
|
+
tasks: state.tasks
|
|
82003
|
+
};
|
|
82004
|
+
state.files = Object.fromEntries(Object.entries(state.files).filter(([ref]) => !expiredRefs.has(ref)));
|
|
82005
|
+
state.tasks = tasks;
|
|
82006
|
+
try {
|
|
82007
|
+
await this.save(userId, state);
|
|
82008
|
+
} catch (error) {
|
|
82009
|
+
Object.assign(state, previous);
|
|
82010
|
+
throw error;
|
|
82011
|
+
}
|
|
82012
|
+
for (const file of expired) {
|
|
81077
82013
|
await unlink(this.path(userId, file.fileRef)).catch(() => {});
|
|
81078
82014
|
await rm(this.openDirectory(userId, file.fileRef), {
|
|
81079
82015
|
recursive: true,
|
|
81080
82016
|
force: true
|
|
81081
82017
|
});
|
|
81082
82018
|
}
|
|
81083
|
-
|
|
82019
|
+
}
|
|
82020
|
+
/** Validate new references and persist their owner without a cleanup/removal gap. Local I/O only. */
|
|
82021
|
+
async withReferences(refs, userId, persist) {
|
|
82022
|
+
return this.exclusive(async () => {
|
|
82023
|
+
await this.assertUser(userId);
|
|
82024
|
+
const retained = new Set(await this.ports.retainedFileRefs?.(userId) ?? []);
|
|
82025
|
+
for (const ref of new Set(refs)) if (!retained.has(ref)) await this.readLocal(ref);
|
|
82026
|
+
await this.assertUser(userId);
|
|
82027
|
+
return await persist();
|
|
82028
|
+
});
|
|
81084
82029
|
}
|
|
81085
82030
|
async readLocal(ref) {
|
|
81086
82031
|
const userId = await this.ports.currentUser();
|
|
@@ -81128,6 +82073,8 @@ var FileTransfers = class {
|
|
|
81128
82073
|
const state = await this.state(userId);
|
|
81129
82074
|
await this.assertUser(userId);
|
|
81130
82075
|
if (state.tasks.some((task) => task.fileRefs.includes(ref))) throw fail("file-in-use", "文件仍被本地发送任务引用,请先移除该任务");
|
|
82076
|
+
if (this.uploadingRefs.has(`${userId}:${ref}`)) throw fail("file-in-use", "文件正在上传,请稍后移除");
|
|
82077
|
+
if ((await this.ports.retainedFileRefs?.(userId))?.includes(ref)) throw fail("file-in-use", "文件仍被重新编辑草稿引用,请先从草稿移除");
|
|
81131
82078
|
if (!state.files[ref]) return;
|
|
81132
82079
|
delete state.files[ref];
|
|
81133
82080
|
await this.save(userId, state);
|
|
@@ -81273,38 +82220,53 @@ var FileTransfers = class {
|
|
|
81273
82220
|
async uploadRefs(fileRefs, signal) {
|
|
81274
82221
|
const userId = await this.ports.currentUser();
|
|
81275
82222
|
if (fileRefs.length < 1 || fileRefs.length > this.policy.maxAttachments || new Set(fileRefs).size !== fileRefs.length || fileRefs.some((ref) => !REF.test(ref))) throw fail("file-upload-invalid", "请选择 1 至 9 个有效附件");
|
|
81276
|
-
|
|
81277
|
-
|
|
81278
|
-
|
|
81279
|
-
|
|
81280
|
-
|
|
81281
|
-
|
|
81282
|
-
|
|
81283
|
-
|
|
81284
|
-
this.
|
|
82223
|
+
const controller = new AbortController();
|
|
82224
|
+
const abort = () => {
|
|
82225
|
+
controller.abort(signal?.reason);
|
|
82226
|
+
};
|
|
82227
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
82228
|
+
if (signal?.aborted) abort();
|
|
82229
|
+
this.controllers.add(controller);
|
|
82230
|
+
const work = this.directUploads.catch(() => {}).then(async () => {
|
|
82231
|
+
const state = await this.exclusive(async () => {
|
|
82232
|
+
await this.assertUser(userId, controller.signal);
|
|
82233
|
+
const current = await this.state(userId);
|
|
82234
|
+
if (fileRefs.some((ref) => !current.files[ref])) throw fail("file-local-missing", "本地附件已不存在");
|
|
82235
|
+
for (const ref of fileRefs) this.uploadingRefs.add(`${userId}:${ref}`);
|
|
82236
|
+
return current;
|
|
82237
|
+
});
|
|
81285
82238
|
try {
|
|
81286
82239
|
const assets = [];
|
|
81287
82240
|
for (const fileRef of fileRefs) {
|
|
81288
82241
|
await this.assertUser(userId, controller.signal);
|
|
81289
82242
|
const stored = state.files[fileRef];
|
|
81290
82243
|
if (stored === void 0) throw fail("file-local-missing", "本地附件已不存在");
|
|
81291
|
-
let asset = stored.asset
|
|
82244
|
+
let asset = stored.asset?.fileKind === stored.fileKind ? stored.asset : Object.values(state.files).find((other) => other.sha256 === stored.sha256 && other.asset?.fileKind === stored.fileKind)?.asset;
|
|
81292
82245
|
if (asset === void 0) asset = await this.ports.upload(this.path(userId, fileRef), stored, () => {}, userId, controller.signal);
|
|
82246
|
+
await this.assertUser(userId, controller.signal);
|
|
81293
82247
|
asset = {
|
|
81294
82248
|
...asset,
|
|
81295
82249
|
fileName: stored.fileName
|
|
81296
82250
|
};
|
|
81297
|
-
|
|
81298
|
-
|
|
82251
|
+
await this.exclusive(async () => {
|
|
82252
|
+
stored.asset = asset;
|
|
82253
|
+
await this.save(userId, state);
|
|
82254
|
+
});
|
|
81299
82255
|
assets.push(asset);
|
|
81300
82256
|
}
|
|
81301
82257
|
await this.assertUser(userId, controller.signal);
|
|
81302
82258
|
return clone(assets);
|
|
81303
82259
|
} finally {
|
|
81304
|
-
|
|
81305
|
-
this.controllers.delete(controller);
|
|
82260
|
+
for (const ref of fileRefs) this.uploadingRefs.delete(`${userId}:${ref}`);
|
|
81306
82261
|
}
|
|
81307
82262
|
});
|
|
82263
|
+
this.directUploads = work;
|
|
82264
|
+
try {
|
|
82265
|
+
return await work;
|
|
82266
|
+
} finally {
|
|
82267
|
+
signal?.removeEventListener("abort", abort);
|
|
82268
|
+
this.controllers.delete(controller);
|
|
82269
|
+
}
|
|
81308
82270
|
}
|
|
81309
82271
|
async retry(taskRef) {
|
|
81310
82272
|
const userId = await this.ports.currentUser();
|
|
@@ -81315,6 +82277,7 @@ var FileTransfers = class {
|
|
|
81315
82277
|
await this.assertUser(userId);
|
|
81316
82278
|
if (task.state === "uncertain") throw fail("file-send-uncertain", "发送结果待确认,请先核对原会话,不能自动重复发送");
|
|
81317
82279
|
if (task.state !== "failed") return clone(task);
|
|
82280
|
+
if (task.retryable === false) throw fail(task.errorCode ?? "file-send-rejected", task.error ?? "该发送已被拒绝,不能重试");
|
|
81318
82281
|
task.state = "queued";
|
|
81319
82282
|
delete task.error;
|
|
81320
82283
|
delete task.errorCode;
|
|
@@ -81388,6 +82351,8 @@ var FileTransfers = class {
|
|
|
81388
82351
|
} else if (ownerOutcome.kind === "owner_not_accepted") {
|
|
81389
82352
|
task.state = "failed";
|
|
81390
82353
|
task.error = ownerOutcome.message;
|
|
82354
|
+
if (ownerOutcome.retryable === false) task.retryable = false;
|
|
82355
|
+
else delete task.retryable;
|
|
81391
82356
|
if (ownerOutcome.code === void 0) delete task.errorCode;
|
|
81392
82357
|
else task.errorCode = ownerOutcome.code;
|
|
81393
82358
|
} else {
|
|
@@ -81502,6 +82467,7 @@ function createArkmeFileTransfers(options) {
|
|
|
81502
82467
|
if (options.directory === void 0) return void 0;
|
|
81503
82468
|
return new FileTransfers(options.directory, {
|
|
81504
82469
|
currentUser: async () => (await options.runtime.requireSession()).userId,
|
|
82470
|
+
retainedFileRefs: async (userId) => await options.runtime.stateStore.recordReeditFileRefs(userId),
|
|
81505
82471
|
validateSource: async (sourceRef) => {
|
|
81506
82472
|
await options.source.openSourceRef(sourceRef, (await options.runtime.requireSession()).userId);
|
|
81507
82473
|
},
|
|
@@ -81546,7 +82512,8 @@ function createArkmeFileTransfers(options) {
|
|
|
81546
82512
|
} : {
|
|
81547
82513
|
kind: "owner_not_accepted",
|
|
81548
82514
|
message: error.message,
|
|
81549
|
-
code: error.code
|
|
82515
|
+
code: error.code,
|
|
82516
|
+
retryable: error.retryable
|
|
81550
82517
|
};
|
|
81551
82518
|
}
|
|
81552
82519
|
},
|
|
@@ -84413,6 +85380,7 @@ var ArkmeService = class {
|
|
|
84413
85380
|
unmarkedSpeaker;
|
|
84414
85381
|
voiceprint;
|
|
84415
85382
|
userBan;
|
|
85383
|
+
directMessageAdmissionOwner;
|
|
84416
85384
|
backgroundSoundPreferenceOwner;
|
|
84417
85385
|
fileTransfers;
|
|
84418
85386
|
localFileOpener;
|
|
@@ -84443,7 +85411,14 @@ var ArkmeService = class {
|
|
|
84443
85411
|
isDSHAgentInput: (raw) => this.record.isDSHAgentInput(raw),
|
|
84444
85412
|
isPrivacyLocked: (raw) => this.record.isPrivacyLocked(raw)
|
|
84445
85413
|
}, this.privacy);
|
|
84446
|
-
this.record = new RecordService(this.runtime, this.media, this.source, this.privacy
|
|
85414
|
+
this.record = new RecordService(this.runtime, this.media, this.source, this.privacy, {
|
|
85415
|
+
files: async () => await this.filesOwner().files(),
|
|
85416
|
+
readLocal: async (ref) => ({ file: (await this.filesOwner().readLocal(ref)).file }),
|
|
85417
|
+
uploadRefs: async (refs) => await this.filesOwner().uploadRefs(refs),
|
|
85418
|
+
withReferences: async (refs, userId, persist) => await this.filesOwner().withReferences(refs, userId, persist)
|
|
85419
|
+
}, async () => {
|
|
85420
|
+
await this.realtime.invalidateRecordProjection();
|
|
85421
|
+
});
|
|
84447
85422
|
this.search = new SearchService(this.runtime, this.record, this.media, this.source, this.privacy);
|
|
84448
85423
|
this.bot = new BotService(this.runtime, this.source);
|
|
84449
85424
|
this.messageActions = new MessageActionService(new ArkmeMessageActionGateway(this.runtime, this.source, async (target, sequence) => {
|
|
@@ -84467,6 +85442,7 @@ var ArkmeService = class {
|
|
|
84467
85442
|
this.conversationDirectoryVisibility = new ConversationDirectoryVisibilityService(new ConversationListPreferenceService(this.runtime), this.source, this.bot, this.realtime);
|
|
84468
85443
|
this.chat = new ChatService(this.runtime, this.source, this.profile, this.media, this.record, this.bot, this.arko, this.aiPolish, this.realtime, this.privacy, this.messageActions);
|
|
84469
85444
|
this.userBan = new UserBanService(this.runtime, this.chat);
|
|
85445
|
+
this.directMessageAdmissionOwner = new DirectMessageAdmissionService(this.runtime, this.source);
|
|
84470
85446
|
this.botConversation = new BotConversationService(this.runtime, this.bot, this.chat, async () => {
|
|
84471
85447
|
await this.realtime.invalidateRecordProjection();
|
|
84472
85448
|
}, this.messageActions);
|
|
@@ -84520,8 +85496,8 @@ var ArkmeService = class {
|
|
|
84520
85496
|
async fileSessionUser() {
|
|
84521
85497
|
return (await this.runtime.requireSession()).userId;
|
|
84522
85498
|
}
|
|
84523
|
-
async fileStage(path, metadata, expectedUserId) {
|
|
84524
|
-
return await this.filesOwner().stage(path, metadata, expectedUserId);
|
|
85499
|
+
async fileStage(path, metadata, expectedUserId, retention) {
|
|
85500
|
+
return await this.filesOwner().stage(path, metadata, expectedUserId, retention);
|
|
84525
85501
|
}
|
|
84526
85502
|
async fileList() {
|
|
84527
85503
|
return await this.filesOwner().files();
|
|
@@ -84742,6 +85718,7 @@ var ArkmeService = class {
|
|
|
84742
85718
|
messageReadReceipts: true,
|
|
84743
85719
|
messageReport: true,
|
|
84744
85720
|
userBanManagement: true,
|
|
85721
|
+
directMessageAdmission: true,
|
|
84745
85722
|
groupOwnerGovernance: true,
|
|
84746
85723
|
...this.config.markdownQuickNotesEnabled === true ? { markdownQuickNotes: true } : {},
|
|
84747
85724
|
richContentRead: this.config.richMediaRenderEnabled !== false,
|
|
@@ -84827,6 +85804,7 @@ var ArkmeService = class {
|
|
|
84827
85804
|
return await this.callHistory.retryCallSummary(callRef, signal);
|
|
84828
85805
|
}
|
|
84829
85806
|
dispose() {
|
|
85807
|
+
this.record.dispose();
|
|
84830
85808
|
this.realtime.resetAttentionSummary();
|
|
84831
85809
|
this.fileTransfers?.cancelActive();
|
|
84832
85810
|
this.contact.dispose();
|
|
@@ -85190,6 +86168,12 @@ var ArkmeService = class {
|
|
|
85190
86168
|
async userBanStatus(sourceRef, signal) {
|
|
85191
86169
|
return await this.userBan.status(sourceRef, signal);
|
|
85192
86170
|
}
|
|
86171
|
+
directMessageAdmission(sourceRef, signal) {
|
|
86172
|
+
return this.directMessageAdmissionOwner.directMessageAdmission(sourceRef, signal);
|
|
86173
|
+
}
|
|
86174
|
+
setDirectMessageRefusal(sourceRef, refused, expectedRevision, signal) {
|
|
86175
|
+
return this.directMessageAdmissionOwner.setDirectMessageRefusal(sourceRef, refused, expectedRevision, signal);
|
|
86176
|
+
}
|
|
85193
86177
|
async banPrivateChatUser(sourceRef, remark = "", signal) {
|
|
85194
86178
|
return await this.userBan.ban(sourceRef, remark, signal);
|
|
85195
86179
|
}
|
|
@@ -85347,13 +86331,26 @@ var ArkmeService = class {
|
|
|
85347
86331
|
async prepareRecordReedit(input, options = {}) {
|
|
85348
86332
|
return await this.record.prepareRecordReedit(input, options);
|
|
85349
86333
|
}
|
|
86334
|
+
async saveRecordReeditDraft(input) {
|
|
86335
|
+
return await this.record.saveRecordReeditDraft(input);
|
|
86336
|
+
}
|
|
86337
|
+
async submitRecordReedit(input) {
|
|
86338
|
+
return await this.record.submitRecordReedit(input);
|
|
86339
|
+
}
|
|
86340
|
+
async recordReeditSubmissions(sourceRef) {
|
|
86341
|
+
return await this.record.recordReeditSubmissions(sourceRef);
|
|
86342
|
+
}
|
|
86343
|
+
async resumeRecordReeditSubmissions(sourceRef, reconcile = false) {
|
|
86344
|
+
return await this.record.resumeRecordReeditSubmissions(sourceRef, reconcile);
|
|
86345
|
+
}
|
|
86346
|
+
async acknowledgeRecordReeditSubmission(sourceRef, submissionId, version) {
|
|
86347
|
+
return await this.record.acknowledgeRecordReeditSubmission(sourceRef, submissionId, version);
|
|
86348
|
+
}
|
|
85350
86349
|
async recordReeditEditor(sourceRef, itemUid) {
|
|
85351
86350
|
return await this.record.recordReeditEditor(sourceRef, itemUid);
|
|
85352
86351
|
}
|
|
85353
86352
|
async commitRecordReedit(context) {
|
|
85354
|
-
|
|
85355
|
-
await this.realtime.invalidateRecordProjection().catch(() => void 0);
|
|
85356
|
-
return result;
|
|
86353
|
+
return await this.record.commitRecordReedit(context);
|
|
85357
86354
|
}
|
|
85358
86355
|
async prepareDiscardRecordReeditDraft(sourceRef, itemUid) {
|
|
85359
86356
|
return await this.record.prepareDiscardRecordReeditDraft(sourceRef, itemUid);
|
|
@@ -87233,4 +88230,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
|
|
|
87233
88230
|
return jsonValue(await handler(jsonValue(args)));
|
|
87234
88231
|
}
|
|
87235
88232
|
//#endregion
|
|
87236
|
-
export {
|
|
88233
|
+
export { parseArkmeRecordReeditAttachments as $, inspectNativeBundleArtifactV3 as A, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, bundleSha256 as D, arkmeSandboxEntryId as E, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, arkmeIsGenericLinkMetadataTitle as J, isRecordingInstantOnOrAfterUnixEpoch as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, readNativeBundleTarballV3 as N, canonicalBundleJson as O, renderArkmeSandboxHostEntry as P, ArkmeRecordReeditDraftConflict as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, ArkmeBundleArtifactError as T, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, arkmeFileBackgroundSound as X, arkmeRequiredLinkMetadataFallback as Y, arkmeRichBackgroundSound as Z, arkmeClientContentDigest as _, ArkmeOutgoingCallError as _t, persistentArkmeExtensionActive as a, ARKME_WORLD_PUBLISH_MAX_IMAGES as at, verifyExtensionResolutionSignature as b, ArkmePluginError as bt, persistentExtensionBundleDirectory as c, ARKME_TOOL_FILE_MAX_BYTES as ct, readPersistentExtensionActivation as d, arkmePickedFileKind as dt, ARKME_DEFAULT_SHARE_WEBSITE as et, refreshPersistentClientWrapper as f, isArkmeBotAvatarRef as ft, activeProfileExtensionOwnerConflicts as g, recordingImportFileNameKey as gt, renderArkmeBundleClientBundle as h, MAX_RECORDING_IMPORT_BYTES as ht, invokePersistentArkmeExtension as i, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as it, packBundleFiles as j, inspectBundleArtifact as k, persistentExtensionPackageIdentity as l, arkmeCanInlineLocalFile as lt, writePersistentExtensionActivation as m, sameRecordingImportIdentity as mt, applyPersistentArkmeHostExtension as n, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as nt, persistentArkmeExtensionRuntimeState as o, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as ot, repairPersistentExtensionInstallation as p, recordingImportFileKind as pt, arkmeExtensionShareRefFromLink as q, deactivatePersistentArkmeExtension as r, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as rt, materializePersistentExtensionBundle as s, ARKME_RUNTIME_INSTANCE_ID as st, activatePersistentArkmeExtension as t, ARKME_PROVIDER_CONTRACT_VERSION as tt, quarantinePersistentExtension as u, arkmeNormalizedFileMimeType as ut, arkmeClientContentDigestFromSource as v, SecretValue as vt, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, verifyNativeBundleResolutionSignature as x, verifyBundleResolutionSignature as y, ArkmeDirectMessageAdmissionError as yt, ARKME_EXTENSION_FORMAT as z };
|