@senguoyun/dsh-arkme 0.1.82 → 0.1.84

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.
Files changed (108) hide show
  1. package/docs/ai-points-implementation.md +43 -0
  2. package/docs/interwoven-compact-summary-regression-2026-09-28.md +35 -0
  3. package/docs/interwoven-original-read-receipt-regression-2026-09-28.md +50 -0
  4. package/docs/personal-detail-consistency-regression-2026-09-28.md +40 -0
  5. package/docs/recognized-speaker-directory-backend-handoff-2026-09-28.md +157 -0
  6. package/docs/recognized-speaker-entry-and-visits.md +47 -0
  7. package/docs/recognized-speaker-presence-delivery.md +51 -0
  8. package/docs/recognized-speaker-sorting.md +30 -0
  9. package/docs/send-to-self-roles-cross-client-api-handoff-2026-09-27.md +9 -0
  10. package/lib/client.js +3358 -1946
  11. package/lib/client.js.map +1 -1
  12. package/lib/harness-model-client.js +168 -38
  13. package/lib/harness-native-selection-client.js +92 -1
  14. package/lib/harness-sidebar-client.js +31 -0
  15. package/lib/index.js +347 -132
  16. package/lib/persistent-extension.js +1 -1
  17. package/lib/{persistent-runtime-DV9Hme-U.js → persistent-runtime-CuKplHIv.js} +607 -303
  18. package/lib/sdk.js +31 -0
  19. package/lib/types/ai-points.d.ts +61 -0
  20. package/lib/types/ai-points.d.ts.map +1 -0
  21. package/lib/types/arkme-service.d.ts +11 -1
  22. package/lib/types/arkme-service.d.ts.map +1 -1
  23. package/lib/types/billing-gateway.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeAccountUsage.d.ts +1 -3
  25. package/lib/types/client/ArkmeAccountUsage.d.ts.map +1 -1
  26. package/lib/types/client/ArkmeArkoSurface.d.ts.map +1 -1
  27. package/lib/types/client/ArkmeBillingSettings.d.ts +3 -2
  28. package/lib/types/client/ArkmeBillingSettings.d.ts.map +1 -1
  29. package/lib/types/client/ArkmeDataManagementSettings.d.ts.map +1 -1
  30. package/lib/types/client/ArkmeDocumentComposerInput.d.ts.map +1 -1
  31. package/lib/types/client/ArkmeEmojiPicker.d.ts.map +1 -1
  32. package/lib/types/client/ArkmeInterwovenReadReceipt.d.ts +12 -0
  33. package/lib/types/client/ArkmeInterwovenReadReceipt.d.ts.map +1 -0
  34. package/lib/types/client/ArkmeModelPricing.d.ts +9 -0
  35. package/lib/types/client/ArkmeModelPricing.d.ts.map +1 -0
  36. package/lib/types/client/ArkmeModelSelect.d.ts.map +1 -1
  37. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  38. package/lib/types/client/ArkmePointsConsumption.d.ts +5 -0
  39. package/lib/types/client/ArkmePointsConsumption.d.ts.map +1 -0
  40. package/lib/types/client/ArkmeRecognizedSpeakersSurface.d.ts +6 -2
  41. package/lib/types/client/ArkmeRecognizedSpeakersSurface.d.ts.map +1 -1
  42. package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
  43. package/lib/types/client/ArkmeRelatedQuickNotes.d.ts.map +1 -1
  44. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  45. package/lib/types/client/ArkoModelMenu.d.ts.map +1 -1
  46. package/lib/types/client/api.d.ts +1 -1
  47. package/lib/types/client/api.d.ts.map +1 -1
  48. package/lib/types/client/interwoven-moments.d.ts.map +1 -1
  49. package/lib/types/client/interwoven-read-receipt-store.d.ts +32 -0
  50. package/lib/types/client/interwoven-read-receipt-store.d.ts.map +1 -0
  51. package/lib/types/client/locales/actions-en.d.ts.map +1 -1
  52. package/lib/types/client/locales/dynamic-en.d.ts.map +1 -1
  53. package/lib/types/client/locales/en.d.ts.map +1 -1
  54. package/lib/types/client/reaction-preview-store.d.ts +1 -0
  55. package/lib/types/client/reaction-preview-store.d.ts.map +1 -1
  56. package/lib/types/client/recognized-speaker-directory.d.ts +36 -0
  57. package/lib/types/client/recognized-speaker-directory.d.ts.map +1 -0
  58. package/lib/types/client/recognized-speaker-order.d.ts +28 -0
  59. package/lib/types/client/recognized-speaker-order.d.ts.map +1 -0
  60. package/lib/types/client/recognized-speaker-tracker.d.ts +33 -0
  61. package/lib/types/client/recognized-speaker-tracker.d.ts.map +1 -0
  62. package/lib/types/client/recordings/RecognizedSpeakerEntry.d.ts +12 -0
  63. package/lib/types/client/recordings/RecognizedSpeakerEntry.d.ts.map +1 -0
  64. package/lib/types/client/recordings/SpeakerSelfGuide.d.ts +8 -0
  65. package/lib/types/client/recordings/SpeakerSelfGuide.d.ts.map +1 -0
  66. package/lib/types/client/recordings/recording-speaker-options-store.d.ts.map +1 -1
  67. package/lib/types/client/self-role-presentation.d.ts +6 -1
  68. package/lib/types/client/self-role-presentation.d.ts.map +1 -1
  69. package/lib/types/client/use-personal-extension-parents.d.ts +4 -0
  70. package/lib/types/client/use-personal-extension-parents.d.ts.map +1 -0
  71. package/lib/types/host-api.d.ts.map +1 -1
  72. package/lib/types/index.d.ts.map +1 -1
  73. package/lib/types/managed-ai/adapter.d.ts +2 -1
  74. package/lib/types/managed-ai/adapter.d.ts.map +1 -1
  75. package/lib/types/managed-ai/operation.d.ts +17 -0
  76. package/lib/types/managed-ai/operation.d.ts.map +1 -0
  77. package/lib/types/managed-ai/transport.d.ts +1 -0
  78. package/lib/types/managed-ai/transport.d.ts.map +1 -1
  79. package/lib/types/record-extension-tree.d.ts +2 -0
  80. package/lib/types/record-extension-tree.d.ts.map +1 -1
  81. package/lib/types/sdk/index.d.ts +14 -0
  82. package/lib/types/sdk/index.d.ts.map +1 -1
  83. package/lib/types/services/ai-points-service.d.ts +13 -0
  84. package/lib/types/services/ai-points-service.d.ts.map +1 -0
  85. package/lib/types/services/arko-service.d.ts.map +1 -1
  86. package/lib/types/services/chat-service.d.ts +9 -1
  87. package/lib/types/services/chat-service.d.ts.map +1 -1
  88. package/lib/types/services/interwoven-service.d.ts +3 -1
  89. package/lib/types/services/interwoven-service.d.ts.map +1 -1
  90. package/lib/types/services/recording-service.d.ts +2 -5
  91. package/lib/types/services/recording-service.d.ts.map +1 -1
  92. package/lib/types/services/unmarked-speaker-service.d.ts.map +1 -1
  93. package/lib/types/tools/business/account/ai-points.d.ts +4 -0
  94. package/lib/types/tools/business/account/ai-points.d.ts.map +1 -0
  95. package/lib/types/tools/business/account/index.d.ts.map +1 -1
  96. package/lib/types/tools/business/recordings/index.d.ts +1 -1
  97. package/lib/types/tools/business/recordings/index.d.ts.map +1 -1
  98. package/lib/types/tools/business/recordings/speaker-presence.d.ts +2 -0
  99. package/lib/types/tools/business/recordings/speaker-presence.d.ts.map +1 -0
  100. package/lib/types/tools/ports/profile.d.ts +3 -0
  101. package/lib/types/tools/ports/profile.d.ts.map +1 -1
  102. package/lib/types/tools/ports/recordings.d.ts +4 -0
  103. package/lib/types/tools/ports/recordings.d.ts.map +1 -1
  104. package/lib/types/types.d.ts +33 -8
  105. package/lib/types/types.d.ts.map +1 -1
  106. package/package.json +5 -5
  107. package/lib/types/recording-speaker-presence.d.ts +0 -28
  108. package/lib/types/recording-speaker-presence.d.ts.map +0 -1
@@ -389,7 +389,7 @@ function object$8(value) {
389
389
  function keys(value, allowed) {
390
390
  if (Object.keys(value).some((key) => !allowed.includes(key))) fail$1();
391
391
  }
392
- function text$11(value, max) {
392
+ function text$12(value, max) {
393
393
  if (typeof value !== "string" || value.length > max) fail$1();
394
394
  }
395
395
  function integer$6(value) {
@@ -415,7 +415,7 @@ function parseArrangementBoardCachePages(value) {
415
415
  const board = object$8(page.board);
416
416
  keys(board, ["supported", "version"]);
417
417
  if (typeof board.supported !== "boolean") fail$1();
418
- text$11(board.version, 1024);
418
+ text$12(board.version, 1024);
419
419
  }
420
420
  const refs = /* @__PURE__ */ new Set();
421
421
  for (const rawItem of page.items) {
@@ -436,13 +436,13 @@ function parseArrangementBoardCachePages(value) {
436
436
  ]);
437
437
  if (item.status !== column || typeof item.reminderEnabled !== "boolean" || typeof item.arrangementRef !== "string" || !/^arkme-arrangement-v1\.[A-Za-z0-9_-]{43}$/.test(item.arrangementRef) || refs.has(item.arrangementRef)) fail$1();
438
438
  refs.add(item.arrangementRef);
439
- text$11(item.title, 2e4);
440
- text$11(item.description, 2e5);
441
- text$11(item.reminderState, 256);
439
+ text$12(item.title, 2e4);
440
+ text$12(item.description, 2e5);
441
+ text$12(item.reminderState, 256);
442
442
  integer$6(item.createdAtMillis);
443
443
  integer$6(item.updatedAtMillis);
444
444
  for (const key of ["dueAtMillis", "remindAtMillis"]) if (item[key] !== void 0) integer$6(item[key]);
445
- if (item.recognitionState !== void 0) text$11(item.recognitionState, 256);
445
+ if (item.recognitionState !== void 0) text$12(item.recognitionState, 256);
446
446
  if (item.creationSource !== void 0) {
447
447
  const source = object$8(item.creationSource);
448
448
  keys(source, [
@@ -459,7 +459,7 @@ function parseArrangementBoardCachePages(value) {
459
459
  for (const rawSource of source.items) {
460
460
  const entry = object$8(rawSource);
461
461
  keys(entry, ["text", "createdAtMillis"]);
462
- text$11(entry.text, 2e5);
462
+ text$12(entry.text, 2e5);
463
463
  if (entry.createdAtMillis !== void 0) integer$6(entry.createdAtMillis);
464
464
  }
465
465
  }
@@ -2024,7 +2024,11 @@ var ServiceRuntime = class {
2024
2024
  try {
2025
2025
  return await this.post(this.config.intelligentBaseUrl, path, body, session.accessToken, [200], signal, true, requestOptions());
2026
2026
  } catch (error) {
2027
- if (!(error instanceof ArkmePluginError) || !["auth-http-401", "auth-http-403"].includes(error.code)) throw error;
2027
+ if (!(error instanceof ArkmePluginError) || ![
2028
+ "auth-http-401",
2029
+ "auth-http-403",
2030
+ "invalid_access_token"
2031
+ ].includes(error.code)) throw error;
2028
2032
  session = await this.refreshAccessToken(session);
2029
2033
  return await this.post(this.config.intelligentBaseUrl, path, body, session.accessToken, [200], signal, true, requestOptions());
2030
2034
  }
@@ -2049,6 +2053,180 @@ var ServiceRuntime = class {
2049
2053
  }
2050
2054
  };
2051
2055
  //#endregion
2056
+ //#region src/ai-points.ts
2057
+ function pointsUnits(value) {
2058
+ if (!/^(?:0|[1-9]\d*)(?:\.\d{1,7})?$/.test(value)) throw new TypeError("Invalid exact AI points");
2059
+ const [whole = "0", fraction = ""] = value.split(".");
2060
+ const units = BigInt(whole) * 10000000n + BigInt(fraction.padEnd(7, "0"));
2061
+ if (units > 9223372036854775807n) throw new TypeError("AI points exceed accounting range");
2062
+ return units;
2063
+ }
2064
+ /** Existing recharge values retain their nano-CNY meaning: ¥1 = 100 points. */
2065
+ function nanoCnyToPoints(value) {
2066
+ if (!/^(?:0|[1-9]\d*)$/.test(value)) throw new TypeError("Invalid nano-CNY");
2067
+ const units = BigInt(value);
2068
+ const whole = units / 10000000n;
2069
+ const fraction = (units % 10000000n).toString().padStart(7, "0").replace(/0+$/, "");
2070
+ return fraction ? `${whole}.${fraction}` : whole.toString();
2071
+ }
2072
+ /** The server pages whole operations. Call-level charges remain the only facts. */
2073
+ function groupPointsConsumption(items) {
2074
+ const groups = /* @__PURE__ */ new Map();
2075
+ for (const item of items) {
2076
+ const key = item.businessCode === "agent" && item.operationUid ? `agent:${item.operationUid}` : `request:${item.requestUid}`;
2077
+ const calls = groups.get(key) ?? [];
2078
+ calls.push(item);
2079
+ groups.set(key, calls);
2080
+ }
2081
+ return [...groups].map(([key, calls]) => ({
2082
+ key,
2083
+ calls,
2084
+ chargedPoints: nanoCnyToPoints(calls.reduce((sum, call) => sum + pointsUnits(call.chargedPoints), 0n).toString())
2085
+ }));
2086
+ }
2087
+ //#endregion
2088
+ //#region src/services/ai-points-service.ts
2089
+ function invalid$12() {
2090
+ throw new ArkmePluginError("ai-points-contract-invalid", "积分数据暂时无法确认,请重试", true, 502);
2091
+ }
2092
+ function points(value) {
2093
+ if (typeof value !== "string") return invalid$12();
2094
+ try {
2095
+ pointsUnits(value);
2096
+ } catch {
2097
+ return invalid$12();
2098
+ }
2099
+ return value;
2100
+ }
2101
+ function timestamp$1(value) {
2102
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return invalid$12();
2103
+ return value;
2104
+ }
2105
+ function text$11(value) {
2106
+ if (typeof value !== "string" || !value.trim()) return invalid$12();
2107
+ return value;
2108
+ }
2109
+ function integerString(value) {
2110
+ if (typeof value !== "string" || !/^(?:0|[1-9]\d*)$/.test(value)) return invalid$12();
2111
+ return value;
2112
+ }
2113
+ function unit(raw) {
2114
+ if (raw.unit !== "ai_points") invalid$12();
2115
+ }
2116
+ function parseAiPointsAccount(raw, accountScope) {
2117
+ unit(raw);
2118
+ if (raw.points_per_cny !== 100 || !Array.isArray(raw.grants)) return invalid$12();
2119
+ const result = {
2120
+ accountScope,
2121
+ unit: "ai_points",
2122
+ availablePoints: points(raw.available_points),
2123
+ reservedPoints: points(raw.reserved_points),
2124
+ grantedPoints: points(raw.granted_points),
2125
+ purchasedPoints: points(raw.purchased_points),
2126
+ observedAt: timestamp$1(raw.observed_at),
2127
+ grants: raw.grants.map((value) => {
2128
+ const row = objectValue$2(value);
2129
+ return {
2130
+ source: text$11(row.source),
2131
+ availablePoints: points(row.available_points),
2132
+ expiresAt: timestamp$1(row.expires_at)
2133
+ };
2134
+ })
2135
+ };
2136
+ if (pointsUnits(result.availablePoints) !== pointsUnits(result.grantedPoints) + pointsUnits(result.purchasedPoints) || result.grants.reduce((total, grant) => total + pointsUnits(grant.availablePoints), 0n) !== pointsUnits(result.grantedPoints)) return invalid$12();
2137
+ return result;
2138
+ }
2139
+ function parseAiPointsPage(raw, accountScope, month) {
2140
+ unit(raw);
2141
+ if (raw.month !== month || !Array.isArray(raw.items)) return invalid$12();
2142
+ const nextBeforeId = raw.next_before_id === "" ? "" : integerString(raw.next_before_id);
2143
+ const result = {
2144
+ accountScope,
2145
+ unit: "ai_points",
2146
+ month,
2147
+ chargedPoints: points(raw.charged_points),
2148
+ nextBeforeId,
2149
+ items: raw.items.map((value) => {
2150
+ const row = objectValue$2(value), tokens = objectValue$2(row.tokens);
2151
+ const chargedPoints = points(row.charged_points), grantedPoints = points(row.granted_points), purchasedPoints = points(row.purchased_points);
2152
+ if (pointsUnits(chargedPoints) <= 0n || pointsUnits(chargedPoints) !== pointsUnits(grantedPoints) + pointsUnits(purchasedPoints)) return invalid$12();
2153
+ const modelPoints = points(row.model_points);
2154
+ if (!Array.isArray(row.services) || row.services.length > 16) return invalid$12();
2155
+ const services = row.services.map((value) => {
2156
+ const service = objectValue$2(value);
2157
+ const quantity = integerString(service.quantity), chargedPoints = points(service.charged_points);
2158
+ if (quantity === "0" || pointsUnits(chargedPoints) === 0n) return invalid$12();
2159
+ return {
2160
+ code: text$11(service.code),
2161
+ quantity,
2162
+ chargedPoints
2163
+ };
2164
+ });
2165
+ if (new Set(services.map((service) => service.code)).size !== services.length || pointsUnits(modelPoints) + services.reduce((sum, service) => sum + pointsUnits(service.chargedPoints), 0n) !== pointsUnits(chargedPoints)) return invalid$12();
2166
+ if (typeof row.business_code !== "string") return invalid$12();
2167
+ return {
2168
+ operationUid: typeof row.operation_uid === "string" ? row.operation_uid : "",
2169
+ requestUid: text$11(row.request_uid),
2170
+ businessCode: row.business_code,
2171
+ model: text$11(row.model),
2172
+ chargedPoints,
2173
+ modelPoints,
2174
+ services,
2175
+ grantedPoints,
2176
+ purchasedPoints,
2177
+ createdAt: timestamp$1(row.created_at),
2178
+ completedAt: timestamp$1(row.completed_at),
2179
+ tokens: {
2180
+ cacheHitInput: integerString(tokens.cache_hit_input),
2181
+ cacheMissInput: integerString(tokens.cache_miss_input),
2182
+ output: integerString(tokens.output)
2183
+ }
2184
+ };
2185
+ })
2186
+ };
2187
+ if (new Set(result.items.map((item) => item.requestUid)).size !== result.items.length || groupPointsConsumption(result.items).length > 20) return invalid$12();
2188
+ return result;
2189
+ }
2190
+ /** One owner for UI, SDK and Tools. It never reads raw usage telemetry. */
2191
+ var AiPointsService = class {
2192
+ runtime;
2193
+ constructor(runtime) {
2194
+ this.runtime = runtime;
2195
+ }
2196
+ async session(expectedScope) {
2197
+ const session = await this.runtime.requireSession();
2198
+ const scope = `${this.runtime.config.environment}:${session.userId}`;
2199
+ if (expectedScope !== void 0 && expectedScope !== scope) throw new ArkmePluginError("ai-points-account-changed", "账号已切换,请重新读取积分", false, 409);
2200
+ return {
2201
+ session,
2202
+ scope
2203
+ };
2204
+ }
2205
+ async account(expectedScope, signal) {
2206
+ const { session, scope } = await this.session(expectedScope);
2207
+ const raw = await this.runtime.authenticatedIntelligentPost("/api/v1/managed-ai/points/query", {}, session, signal, {
2208
+ lane: "interactive-read",
2209
+ bypassCache: true
2210
+ });
2211
+ await this.session(scope);
2212
+ return parseAiPointsAccount(raw, scope);
2213
+ }
2214
+ async consumption(query, expectedScope, signal) {
2215
+ if (!/^\d{4}-(?:0[1-9]|1[0-2])$/.test(query.month) || query.beforeId !== void 0 && !/^[1-9]\d*$/.test(query.beforeId)) throw new ArkmePluginError("ai-points-query-invalid", "请选择有效月份", false, 400);
2216
+ const { session, scope } = await this.session(expectedScope);
2217
+ const raw = await this.runtime.authenticatedIntelligentPost("/api/v1/managed-ai/points/consumption/query", {
2218
+ month: query.month,
2219
+ before_id: query.beforeId ?? "",
2220
+ limit: 20
2221
+ }, session, signal, {
2222
+ lane: "interactive-read",
2223
+ bypassCache: true
2224
+ });
2225
+ await this.session(scope);
2226
+ return parseAiPointsPage(raw, scope, query.month);
2227
+ }
2228
+ };
2229
+ //#endregion
2052
2230
  //#region src/self-role-avatar-store.ts
2053
2231
  const MAX_SELF_ROLE_AVATAR_BYTES = 8 * 1024 * 1024;
2054
2232
  const PREFIX = "arkme-self-role-image-v1.";
@@ -3147,6 +3325,13 @@ function errorCode$1(status, body) {
3147
3325
  const providerCode = typeof providerError?.code === "string" ? providerError.code.toLowerCase() : "";
3148
3326
  if (status === 401 || status === 403) return "AUTH";
3149
3327
  if (status === 402 || providerCode.includes("balance") || providerCode.includes("quota")) return "QUOTA";
3328
+ if (status === 409 && [
3329
+ "operation_closed",
3330
+ "operation_limit",
3331
+ "operation_conflict"
3332
+ ].includes(providerCode)) return "OPERATION_ENDED";
3333
+ if (providerCode === "unsupported_model") return "UNKNOWN_MODEL";
3334
+ if (status === 409 && providerCode === "request_in_progress") return "REQUEST_IN_PROGRESS";
3150
3335
  if (status === 408 || status === 504) return "TIMEOUT";
3151
3336
  if (status === 429) return "RATE_LIMIT";
3152
3337
  if (status === 400 || status === 413 || status === 422) return "INVALID_REQUEST";
@@ -3618,6 +3803,7 @@ var ManagedAiTransport = class {
3618
3803
  const imageAttachments = requestImageAttachments(request);
3619
3804
  assertImageRequestWithinCapability(imageAttachments, capability);
3620
3805
  const bearer = await this.options.resolveBearer();
3806
+ const operationUid = this.options.prepareOperation?.(request, bearer);
3621
3807
  const imageCapability = capability.image;
3622
3808
  for (let dispatchAttempt = 0;; dispatchAttempt++) {
3623
3809
  let resolvedImageBytes = 0;
@@ -3634,6 +3820,7 @@ var ManagedAiTransport = class {
3634
3820
  method: "POST",
3635
3821
  headers: {
3636
3822
  Authorization: `Bearer ${bearer}`,
3823
+ ...operationUid === void 0 ? {} : { "X-Arkme-Turn-ID": operationUid },
3637
3824
  Accept: "text/event-stream",
3638
3825
  "Content-Type": "application/json",
3639
3826
  ...attributionHeaders(),
@@ -3893,7 +4080,7 @@ const ARKME_MANAGED_CATALOG_MAX_STALE_MS = 5 * ARKME_MANAGED_CATALOG_TTL_MS;
3893
4080
  const ARKME_MANAGED_CATALOG_TIMEOUT_MS = 1e4;
3894
4081
  const ARKME_MANAGED_MAXIMUM_IMAGE_PIXELS = 4e7;
3895
4082
  const ARKME_MANAGED_MAXIMUM_REQUEST_IMAGE_BYTES = 1 << 30;
3896
- const ARKME_INSUFFICIENT_BALANCE_MESSAGE = "Arkme AI 余额不足,请前往 Arkme 设置中的余额充值后重试";
4083
+ const ARKME_INSUFFICIENT_BALANCE_MESSAGE = "AI 额度不足,请充值后继续";
3897
4084
  const ARKME_LOGIN_MESSAGE = "请先登录或重新登录 Arkme 后再使用托管模型";
3898
4085
  const ARKME_AUTH_FAILURE_CODES = /* @__PURE__ */ new Set([
3899
4086
  "login-required",
@@ -3928,6 +4115,14 @@ function managedAiFailureFacts(error) {
3928
4115
  };
3929
4116
  }
3930
4117
  function managedAiLocalizedFailure(facts) {
4118
+ if (facts.code === "OPERATION_ENDED") return {
4119
+ code: facts.code,
4120
+ message: "本轮任务已结束,请发送新消息继续"
4121
+ };
4122
+ if (facts.code === "REQUEST_IN_PROGRESS") return {
4123
+ code: facts.code,
4124
+ message: "已有 AI 请求正在完成,请等待结果后重试"
4125
+ };
3931
4126
  if (facts.status === 402 || ["QUOTA", "INSUFFICIENT_BALANCE"].includes(facts.code)) return {
3932
4127
  code: "INSUFFICIENT_BALANCE",
3933
4128
  message: ARKME_INSUFFICIENT_BALANCE_MESSAGE
@@ -4271,6 +4466,21 @@ function parseReasoning(value) {
4271
4466
  defaultEffort: ReasoningEffortId(defaultEffort)
4272
4467
  };
4273
4468
  }
4469
+ function modelPointPriceDescription(value) {
4470
+ const price = asRecord(value);
4471
+ if (!price) return "计费信息暂时无法读取,实际按用量扣积分";
4472
+ const read = (key) => {
4473
+ const v = price[key];
4474
+ if (typeof v !== "string") throw new LlmError("模型积分报价无效", "MALFORMED_RESPONSE");
4475
+ try {
4476
+ pointsUnits(v);
4477
+ } catch {
4478
+ throw new LlmError("模型积分报价无效", "MALFORMED_RESPONSE");
4479
+ }
4480
+ return v;
4481
+ };
4482
+ return `每 1,000 Token:输入 ${read("cache_miss_input_per_thousand")} 积分,缓存输入 ${read("cache_hit_input_per_thousand")} 积分,输出 ${read("output_per_thousand")} 积分。`;
4483
+ }
4274
4484
  function parseManagedCatalog(payload) {
4275
4485
  const envelope = asRecord(payload);
4276
4486
  if (envelope?.code !== 200) {
@@ -4298,6 +4508,7 @@ function parseManagedCatalog(payload) {
4298
4508
  return {
4299
4509
  id,
4300
4510
  name: requiredCatalogText(item, "display_name", "显示名称"),
4511
+ description: modelPointPriceDescription(item.point_pricing),
4301
4512
  contextWindow,
4302
4513
  maxTokens: defaultMaxTokens
4303
4514
  };
@@ -4358,7 +4569,7 @@ var ManagedModelCatalog = class {
4358
4569
  canUseLastGood() {
4359
4570
  return this.hasRemoteSnapshot && Date.now() - this.refreshedAt < ARKME_MANAGED_CATALOG_MAX_STALE_MS;
4360
4571
  }
4361
- async fetchCatalog(signal) {
4572
+ async fetchCatalog(signal, resolveModel) {
4362
4573
  const controller = new AbortController();
4363
4574
  let timedOut = false;
4364
4575
  const abortFromCaller = () => {
@@ -4380,7 +4591,7 @@ var ManagedModelCatalog = class {
4380
4591
  Authorization: `Bearer ${bearer}`,
4381
4592
  "Content-Type": "application/json"
4382
4593
  },
4383
- body: "{}",
4594
+ body: JSON.stringify(resolveModel === void 0 ? {} : { resolve_model: resolveModel }),
4384
4595
  signal: controller.signal
4385
4596
  });
4386
4597
  if (!response.ok) {
@@ -4398,12 +4609,19 @@ var ManagedModelCatalog = class {
4398
4609
  throw new LlmError("Arkme 模型目录返回异常", "MALFORMED_RESPONSE", { cause: error });
4399
4610
  }
4400
4611
  const snapshot = parseManagedCatalog(payload);
4401
- this.connectionSnapshot = managedConnection(this.baseUrl, snapshot.models);
4402
- this.modelIds = new Set(snapshot.models.map((model) => model.id));
4403
- this.capabilitySnapshot = snapshot.capabilities;
4404
- this.reasoningSnapshot = snapshot.reasoning;
4405
- this.hasRemoteSnapshot = true;
4406
- this.refreshedAt = Date.now();
4612
+ if (resolveModel !== void 0) {
4613
+ if (snapshot.models.length !== 1 || snapshot.models[0]?.id !== resolveModel) throw new LlmError("原回合模型信息无效", "UNKNOWN_MODEL");
4614
+ this.connectionSnapshot = managedConnection(this.baseUrl, [...this.connectionSnapshot.models.filter((item) => item.id !== resolveModel), ...snapshot.models]);
4615
+ for (const [key, value] of snapshot.capabilities) this.capabilitySnapshot.set(key, value);
4616
+ for (const [key, value] of snapshot.reasoning) this.reasoningSnapshot.set(key, value);
4617
+ } else {
4618
+ this.connectionSnapshot = managedConnection(this.baseUrl, snapshot.models);
4619
+ this.modelIds = new Set(snapshot.models.map((model) => model.id));
4620
+ this.capabilitySnapshot = snapshot.capabilities;
4621
+ this.reasoningSnapshot = snapshot.reasoning;
4622
+ this.hasRemoteSnapshot = true;
4623
+ this.refreshedAt = Date.now();
4624
+ }
4407
4625
  } catch (error) {
4408
4626
  if (signal?.aborted === true) throw localizeManagedAiError(new LlmError("Arkme 模型目录请求已取消", "ABORTED", { cause: error }));
4409
4627
  if (timedOut) throw localizeManagedAiError(new LlmError("Arkme 模型目录请求超时", "TIMEOUT", { cause: error }));
@@ -4433,15 +4651,29 @@ var ManagedModelCatalog = class {
4433
4651
  }
4434
4652
  async ensureModel(model, signal) {
4435
4653
  const known = this.modelIds.has(model);
4436
- if (this.isFresh()) return;
4437
- try {
4654
+ if (!this.isFresh()) try {
4438
4655
  await this.refresh(signal);
4439
4656
  } catch (error) {
4440
4657
  if (signal?.aborted === true || !this.canUseLastGood() || !known) throw error;
4441
4658
  }
4659
+ if (!this.modelIds.has(model)) try {
4660
+ await this.fetchCatalog(signal, model);
4661
+ } catch (error) {
4662
+ if (signal?.aborted === true) throw error;
4663
+ const facts = managedAiFailureFacts(error);
4664
+ if ((facts.status ?? 0) >= 500 || [
4665
+ "TRANSPORT",
4666
+ "TIMEOUT",
4667
+ "AUTH"
4668
+ ].includes(facts.code)) throw error;
4669
+ throw new LlmError("原回合模型已不可用,请重新选择模型", "UNKNOWN_MODEL", { cause: error });
4670
+ }
4671
+ }
4672
+ isListed(model) {
4673
+ return this.modelIds.has(model);
4442
4674
  }
4443
4675
  assertModel(model) {
4444
- if (!this.modelIds.has(model)) throw new LlmError(`当前 Arkme 托管服务不支持模型“${model}”,请重新选择模型`, "UNKNOWN_MODEL");
4676
+ if (!this.capabilitySnapshot.has(model)) throw new LlmError(`当前 Arkme 托管服务不支持模型“${model}”,请重新选择模型`, "UNKNOWN_MODEL");
4445
4677
  }
4446
4678
  };
4447
4679
  var ManagedAiLlmAdapter = class extends LlmAdapter {
@@ -4466,7 +4698,7 @@ var ManagedAiLlmAdapter = class extends LlmAdapter {
4466
4698
  async listModels(provider) {
4467
4699
  assertManagedProvider(provider);
4468
4700
  await this.catalog.refreshForListing();
4469
- return (await this.delegate.listModels(provider)).map((model) => ({
4701
+ return (await this.delegate.listModels(provider)).filter((model) => this.catalog.isListed(model.id)).map((model) => ({
4470
4702
  ...model,
4471
4703
  inputModalities: this.catalog.capability(model.id).inputModalities
4472
4704
  }));
@@ -4511,7 +4743,8 @@ function createManagedAiLlmAdapter(options) {
4511
4743
  resolveAttachmentReader: options.resolveAttachmentReader ?? (() => void 0),
4512
4744
  fetchImpl,
4513
4745
  resolveBearer: async () => await resolveBearer(options.credentialOwner),
4514
- resolveAnonymousUserId
4746
+ resolveAnonymousUserId,
4747
+ ...options.prepareOperation === void 0 ? {} : { prepareOperation: options.prepareOperation }
4515
4748
  }));
4516
4749
  }
4517
4750
  function registerManagedAiProvider(ctx, options) {
@@ -15364,7 +15597,7 @@ function tokenizeThematicBreak(effects, ok, nok) {
15364
15597
  * } from 'micromark-util-types'
15365
15598
  */
15366
15599
  /** @type {Construct} */
15367
- const list$5 = {
15600
+ const list$4 = {
15368
15601
  continuation: { tokenize: tokenizeListContinuation },
15369
15602
  exit: tokenizeListEnd,
15370
15603
  name: "list",
@@ -15484,7 +15717,7 @@ function tokenizeListContinuation(effects, ok, nok) {
15484
15717
  function notInCurrentItem(code) {
15485
15718
  self.containerState._closeFlow = true;
15486
15719
  self.interrupt = void 0;
15487
- return factorySpace(effects, effects.attempt(list$5, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
15720
+ return factorySpace(effects, effects.attempt(list$4, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
15488
15721
  }
15489
15722
  }
15490
15723
  /**
@@ -15735,7 +15968,7 @@ function initializeFlow(effects) {
15735
15968
  * } from 'micromark-util-types'
15736
15969
  */
15737
15970
  const resolver = { resolveAll: createResolver() };
15738
- const string$2 = initializeFactory("string");
15971
+ const string$1 = initializeFactory("string");
15739
15972
  const text$10 = initializeFactory("text");
15740
15973
  /**
15741
15974
  * @param {'string' | 'text'} field
@@ -15912,24 +16145,24 @@ var constructs_exports = /* @__PURE__ */ __exportAll({
15912
16145
  flow: () => flow,
15913
16146
  flowInitial: () => flowInitial,
15914
16147
  insideSpan: () => insideSpan,
15915
- string: () => string$1,
16148
+ string: () => string,
15916
16149
  text: () => text$9
15917
16150
  });
15918
16151
  /** @satisfies {Extension['document']} */
15919
16152
  const document$1 = {
15920
- [42]: list$5,
15921
- [43]: list$5,
15922
- [45]: list$5,
15923
- [48]: list$5,
15924
- [49]: list$5,
15925
- [50]: list$5,
15926
- [51]: list$5,
15927
- [52]: list$5,
15928
- [53]: list$5,
15929
- [54]: list$5,
15930
- [55]: list$5,
15931
- [56]: list$5,
15932
- [57]: list$5,
16153
+ [42]: list$4,
16154
+ [43]: list$4,
16155
+ [45]: list$4,
16156
+ [48]: list$4,
16157
+ [49]: list$4,
16158
+ [50]: list$4,
16159
+ [51]: list$4,
16160
+ [52]: list$4,
16161
+ [53]: list$4,
16162
+ [54]: list$4,
16163
+ [55]: list$4,
16164
+ [56]: list$4,
16165
+ [57]: list$4,
15933
16166
  [62]: blockQuote
15934
16167
  };
15935
16168
  /** @satisfies {Extension['contentInitial']} */
@@ -15952,7 +16185,7 @@ const flow = {
15952
16185
  [126]: codeFenced
15953
16186
  };
15954
16187
  /** @satisfies {Extension['string']} */
15955
- const string$1 = {
16188
+ const string = {
15956
16189
  [38]: characterReference,
15957
16190
  [92]: characterEscape
15958
16191
  };
@@ -16472,7 +16705,7 @@ function parse(options) {
16472
16705
  document: create(document$2),
16473
16706
  flow: create(flow$1),
16474
16707
  lazy: {},
16475
- string: create(string$2),
16708
+ string: create(string$1),
16476
16709
  text: create(text$10)
16477
16710
  };
16478
16711
  return parser;
@@ -20267,7 +20500,7 @@ function checkRule(state) {
20267
20500
  * @param {Info} info
20268
20501
  * @returns {string}
20269
20502
  */
20270
- function list$4(node, parent, state, info) {
20503
+ function list$3(node, parent, state, info) {
20271
20504
  const exit = state.enter("list");
20272
20505
  const bulletCurrent = state.bulletCurrent;
20273
20506
  /** @type {string} */
@@ -20542,7 +20775,7 @@ const handle = {
20542
20775
  inlineCode,
20543
20776
  link,
20544
20777
  linkReference,
20545
- list: list$4,
20778
+ list: list$3,
20546
20779
  listItem,
20547
20780
  paragraph,
20548
20781
  root,
@@ -36218,18 +36451,14 @@ var HttpArkmeBillingGateway = class {
36218
36451
  return new ArkmePluginError(error.code, error.message, retryable, error.httpStatus, { cause: error });
36219
36452
  }
36220
36453
  async quota(signal) {
36221
- const data = objectValue$2(await this.post("intelligent", "/api/v1/managed-ai/balance/query", {}, "interactive-read", signal));
36222
- const currency = stringValue$2(data.currency).trim();
36223
- if (currency !== "CNY") throw new ArkmePluginError("billing-contract-invalid", "余额接口币种无效", true, 502);
36224
- const availableNanoCny = nanoCny(data.available_nano_cny, "available_nano_cny");
36225
- const totalNanoCny = nanoCny(data.total_nano_cny, "total_nano_cny");
36226
- const reservedNanoCny = nanoCny(data.reserved_nano_cny, "reserved_nano_cny");
36227
- if (BigInt(availableNanoCny) + BigInt(reservedNanoCny) !== BigInt(totalNanoCny)) throw new ArkmePluginError("billing-contract-invalid", "余额接口金额不一致", true, 502);
36454
+ const account = parseAiPointsAccount(objectValue$2(await this.post("intelligent", "/api/v1/managed-ai/points/query", {}, "interactive-read", signal)), "");
36455
+ const availableNanoCny = pointsUnits(account.availablePoints);
36456
+ const reservedNanoCny = pointsUnits(account.reservedPoints);
36228
36457
  return {
36229
- availableNanoCny,
36230
- totalNanoCny,
36231
- reservedNanoCny,
36232
- currency
36458
+ availableNanoCny: String(availableNanoCny),
36459
+ totalNanoCny: String(availableNanoCny + reservedNanoCny),
36460
+ reservedNanoCny: String(reservedNanoCny),
36461
+ currency: "CNY"
36233
36462
  };
36234
36463
  }
36235
36464
  async products(signal) {
@@ -36855,6 +37084,7 @@ function arkoModelOptionFromData(value) {
36855
37084
  displayName,
36856
37085
  provider,
36857
37086
  description,
37087
+ costDescription: stringValue$2(data.cost_description),
36858
37088
  recommended: booleanValue$14(data.recommended),
36859
37089
  selected: booleanValue$14(data.selected)
36860
37090
  };
@@ -36866,7 +37096,7 @@ function arkoModelCatalogFromData(data) {
36866
37096
  const options = listValue$26(data.items).map(arkoModelOptionFromData);
36867
37097
  const routeKeys = new Set(options.map((option) => option.routeKey));
36868
37098
  const selected = options.filter((option) => option.selected);
36869
- if (selectionSource !== "default" && selectionSource !== "personal" || options.length === 0 || options.length > 16 || routeKeys.size !== options.length || !routeKeys.has(defaultRouteKey) || !routeKeys.has(effectiveRouteKey) || selected.length !== 1 || selected[0]?.routeKey !== effectiveRouteKey) throw new ArkmePluginError("arko-model-contract-invalid", "Arko 模型目录响应无效", true, 502);
37099
+ if (selectionSource !== "default" && selectionSource !== "personal" && selectionSource !== "unavailable" || options.length === 0 || options.length > 256 || routeKeys.size !== options.length || !routeKeys.has(defaultRouteKey) || (selectionSource === "unavailable" ? routeKeys.has(effectiveRouteKey) || selected.length !== 0 : !routeKeys.has(effectiveRouteKey) || selected.length !== 1 || selected[0]?.routeKey !== effectiveRouteKey)) throw new ArkmePluginError("arko-model-contract-invalid", "Arko 模型目录响应无效", true, 502);
36870
37100
  return {
36871
37101
  defaultRouteKey,
36872
37102
  effectiveRouteKey,
@@ -37053,8 +37283,7 @@ var ArkoService = class {
37053
37283
  let modelRouteKey = options.modelRouteKey?.trim() ?? "";
37054
37284
  if (hasContinuationRun && modelRouteKey !== "") throw new ArkmePluginError("arko-continuation-model-invalid", "继续 Arko 任务时不能切换模型", false);
37055
37285
  if (!hasContinuationRun && modelRouteKey === "") try {
37056
- const catalog = await this.arkoModelCatalog(options.signal);
37057
- if (catalog.options.length > 1) modelRouteKey = catalog.effectiveRouteKey;
37286
+ modelRouteKey = (await this.arkoModelCatalog(options.signal)).effectiveRouteKey;
37058
37287
  } catch {}
37059
37288
  if (modelRouteKey !== "" && (modelRouteKey.length > 128 || !ARKO_MODEL_ROUTE_PATTERN.test(modelRouteKey))) throw new ArkmePluginError("arko-model-route-invalid", "请选择有效的 Arko 模型", false);
37060
37289
  const body = {
@@ -39359,7 +39588,7 @@ async function readTopicMetadata(runtime, session, topicUid, signal) {
39359
39588
  }
39360
39589
  //#endregion
39361
39590
  //#region src/avatar-diagnostics.ts
39362
- function record$1(value) {
39591
+ function record(value) {
39363
39592
  return value !== null && typeof value === "object" ? value : {};
39364
39593
  }
39365
39594
  function positiveId(value) {
@@ -39375,7 +39604,7 @@ function avatarReferenceDiagnostic(imageRef) {
39375
39604
  const encoded = parts[1] ?? "";
39376
39605
  if (parts.length !== 3 || encoded.length > 1024 || !/^[A-Za-z0-9_-]+$/.test(encoded)) return context;
39377
39606
  const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
39378
- const payload = record$1(JSON.parse(globalThis.atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))));
39607
+ const payload = record(JSON.parse(globalThis.atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))));
39379
39608
  if (payload.version !== 1) return context;
39380
39609
  const viewer = positiveId(payload.viewerUserId);
39381
39610
  const target = positiveId(payload.targetUserId);
@@ -39397,8 +39626,8 @@ function imageDownloadStatus(error) {
39397
39626
  }
39398
39627
  function logArkmeAvatarDiagnostic(event, context, error) {
39399
39628
  try {
39400
- const source = record$1(error);
39401
- const body = record$1(source.body);
39629
+ const source = record(error);
39630
+ const body = record(source.body);
39402
39631
  const detail = source.name === "ArkmeClientError" ? body : source;
39403
39632
  const errorName = [
39404
39633
  "Error",
@@ -39418,7 +39647,7 @@ function logArkmeAvatarDiagnostic(event, context, error) {
39418
39647
  retryable: typeof detail.retryable === "boolean" ? detail.retryable : void 0,
39419
39648
  httpStatus: httpStatus(source.httpStatus),
39420
39649
  upstreamStatus: httpStatus(source.upstreamStatus) ?? imageDownloadStatus(source),
39421
- causeCode: safeErrorCode(record$1(source.cause).code)
39650
+ causeCode: safeErrorCode(record(source.cause).code)
39422
39651
  } }
39423
39652
  })}`);
39424
39653
  } catch {}
@@ -46661,7 +46890,7 @@ function botDisplayName(profiles, uid) {
46661
46890
  }
46662
46891
  //#endregion
46663
46892
  //#region src/services/chat-sender-display-reader.ts
46664
- const list$3 = (value) => Array.isArray(value) ? value : [];
46893
+ const list$2 = (value) => Array.isArray(value) ? value : [];
46665
46894
  const text$3 = (value) => stringValue$2(value).trim();
46666
46895
  const first = (raw, keys) => keys.map((key) => text$3(raw[key])).find(Boolean) ?? "";
46667
46896
  /** Normalize wire data once. Neither raw participants nor their extra fields escape this adapter. */
@@ -46671,7 +46900,7 @@ function botDisplaySnapshot(bundle) {
46671
46900
  const extra = objectValue$2(session.extra);
46672
46901
  const groupTarget = arkmeGroupBotBindingTargetFromBundle(bundle) ?? arkmeGroupBotBindingTargetFromBundle({ rm_subject_id: extra.subject_id ?? extra.legacy_subject_id });
46673
46902
  const profiles = /* @__PURE__ */ new Map();
46674
- for (const value of list$3(bundle.bot_participants)) {
46903
+ for (const value of list$2(bundle.bot_participants)) {
46675
46904
  const participant = objectValue$2(value);
46676
46905
  const uid = text$3(participant.bot_uid);
46677
46906
  if (uid === "" || text$3(participant.chat_session_uid) !== chatSessionUid) continue;
@@ -46717,7 +46946,7 @@ var RuntimeBotDisplayProfilesReader = class {
46717
46946
  let snapshot = input.snapshot;
46718
46947
  if (snapshot === void 0) try {
46719
46948
  const data = await this.runtime.authenticatedChatPost("/api/v1/chats/display-snapshots", { chat_session_uids: [input.chatSessionUid] }, session, identitySignal, { refreshOnUnauthorized: false });
46720
- snapshot = list$3(data.items).map((value) => botDisplaySnapshot(objectValue$2(value))).find((item) => item.chatSessionUid === input.chatSessionUid);
46949
+ snapshot = list$2(data.items).map((value) => botDisplaySnapshot(objectValue$2(value))).find((item) => item.chatSessionUid === input.chatSessionUid);
46721
46950
  } catch (error) {
46722
46951
  optionalFailure(error);
46723
46952
  }
@@ -46748,7 +46977,7 @@ var RuntimeBotDisplayProfilesReader = class {
46748
46977
  cancelWhenUnobserved: true
46749
46978
  });
46750
46979
  const profiles = /* @__PURE__ */ new Map();
46751
- for (const value of list$3(data.bots)) {
46980
+ for (const value of list$2(data.bots)) {
46752
46981
  const raw = objectValue$2(value);
46753
46982
  if (raw.installed === true && text$3(raw.bot_id) !== "" && text$3(raw.name) !== "") profiles.set(text$3(raw.bot_id), {
46754
46983
  displayName: text$3(raw.name),
@@ -47108,6 +47337,23 @@ function integer$1(value) {
47108
47337
  const parsed = typeof value === "number" ? value : Number(value);
47109
47338
  return Number.isSafeInteger(parsed) ? parsed : 0;
47110
47339
  }
47340
+ /** The tree can be centered on the child while its parent lives outside the topic. */
47341
+ function projectOwnedRecordParent(response, openedRecordUid) {
47342
+ const data = object$3(response);
47343
+ if (openedRecordUid === "" || text$2(data.record_uid) !== openedRecordUid || text$2(data.root_record_uid) === "") throw new Error("记录延展树与当前快记不匹配");
47344
+ let parentUid = text$2(data.parent_record_uid);
47345
+ if (data.parent_record_uid === void 0) parentUid = text$2((Array.isArray(data.edges) ? data.edges : []).map(object$3).find((edge) => text$2(edge.child_record_uid) === openedRecordUid && (edge.status === void 0 || integer$1(edge.status) === 1))?.parent_record_uid);
47346
+ if (parentUid === "" || parentUid === openedRecordUid) return void 0;
47347
+ const record = (Array.isArray(data.records) ? data.records : []).map(object$3).find((record) => text$2(record.record_uid) === parentUid);
47348
+ return {
47349
+ recordUid: parentUid,
47350
+ parentRecordUid: "",
47351
+ level: 0,
47352
+ record: record ?? {},
47353
+ createdAtMillis: 0,
47354
+ protectedContent: record === void 0 || integer$1(record.content_access_state) !== 1 || record.status !== void 0 && integer$1(record.status) !== 1
47355
+ };
47356
+ }
47111
47357
  function parseNode(value, visiting, budget) {
47112
47358
  const raw = object$3(value);
47113
47359
  const recordUid = text$2(raw.record_uid);
@@ -51760,13 +52006,22 @@ var ChatService = class {
51760
52006
  for (const child of listValue$16(data.children)) {
51761
52007
  const extension = chatExtensionTreeItemFromData(child, parentRecordUid);
51762
52008
  if (extension === void 0) continue;
51763
- const record = objectValue$2(objectValue$2(objectValue$2(child).item).record);
52009
+ const childItem = objectValue$2(objectValue$2(child).item);
52010
+ const relation = objectValue$2(childItem.relation);
52011
+ const record = objectValue$2(childItem.record);
51764
52012
  const payload = objectValue$2(record.payload);
51765
52013
  const mentions = await this.timelineMentionTargets(record, payload, session.userId, chatSessionUid);
52014
+ const senderUserId = integerLikeValue(relation.sender_user_id ?? relation.senderUserId);
52015
+ const humanSender = senderUserId > 0 && !timelineSenderIsBot(relation);
52016
+ const senderMemberRef = humanSender ? await this.sealChatMemberRef(session.userId, chatSessionUid, senderUserId) : void 0;
52017
+ const currentAvatarRef = humanSender ? await this.profile.sealProfileImageRef(session.userId, senderUserId).catch(() => void 0) : void 0;
51766
52018
  projections.push({
51767
- extension: mentions === void 0 ? extension : {
52019
+ extension: {
51768
52020
  ...extension,
51769
- mentions
52021
+ ...mentions === void 0 ? {} : { mentions },
52022
+ ...senderMemberRef === void 0 ? {} : { senderMemberRef },
52023
+ ...humanSender ? { senderIsMe: senderUserId === session.userId } : {},
52024
+ ...currentAvatarRef === void 0 ? {} : { senderAvatarUrl: currentAvatarRef }
51770
52025
  },
51771
52026
  mediaRecord: chatExtensionMediaRecord(child, extension.recordUid)
51772
52027
  });
@@ -51779,12 +52034,66 @@ var ChatService = class {
51779
52034
  };
51780
52035
  }
51781
52036
  /** Personal and topic notes share the record-owned tree, including replies outside the original topic. */
52037
+ async ownedRecordParentFromTree(data, recordUid, session, signal) {
52038
+ const parent = projectOwnedRecordParent(data, recordUid);
52039
+ if (parent === void 0) return void 0;
52040
+ const owner = recordOwnerId(parent.record.owner_user_id);
52041
+ if (parent.protectedContent || owner !== 0 && owner !== session.userId || arkmePrivacyLockedRecord(parent.record) || this.privacy !== void 0 && (await this.privacy.lockedRecordUids(session, signal)).has(parent.recordUid)) return {
52042
+ itemUid: parent.recordUid,
52043
+ senderName: "",
52044
+ title: "",
52045
+ textContent: "该延展源暂不可用"
52046
+ };
52047
+ const hydration = await this.media.hydrateRecordMediaPage([parent.record], session, signal);
52048
+ const item = this.record.recordTimelineItemFromRaw(parent.record, session.userId, {
52049
+ displayItems: hydration.displayItemsByRecordUid.get(parent.recordUid) ?? [],
52050
+ mediaUnavailable: hydration.unavailableRecordUids.has(parent.recordUid)
52051
+ });
52052
+ return {
52053
+ itemUid: parent.recordUid,
52054
+ senderName: item.senderName,
52055
+ title: item.title,
52056
+ textContent: item.textContent,
52057
+ textFormat: item.textFormat ?? "plain",
52058
+ sendAtMillis: item.sendAtMillis,
52059
+ ...owner === 0 ? {} : { recordOwnerUserId: owner },
52060
+ ...item.contentBlocks === void 0 ? {} : { contentBlocks: item.contentBlocks }
52061
+ };
52062
+ }
52063
+ /** Lightweight visible-row enrichment; never reads an entire topic before showing it. */
52064
+ async sourceMessageExtensionParent(sourceRef, actionRef, options = {}) {
52065
+ const session = await this.runtime.requireSession();
52066
+ const source = await this.source.openSourceRef(sourceRef.trim(), session.userId);
52067
+ const reference = await this.openMessageActionRef(actionRef, session.userId, source);
52068
+ if (reference.sourceKind !== "record" || ![
52069
+ "send_to_self",
52070
+ "default_category",
52071
+ "topic"
52072
+ ].includes(source.kind)) throw new ArkmePluginError("record-extension-source-invalid", "当前来源不支持读取个人延展源", false, 403);
52073
+ const data = await this.runtime.authenticatedPost("/api/v1/records/extensions/tree", { record_uid: reference.recordUid }, session, options.signal, {
52074
+ lane: "background-read",
52075
+ key: `record-extension-parent:${reference.recordUid}`,
52076
+ cacheMs: 5e3,
52077
+ failureCooldownMs: 2e3
52078
+ });
52079
+ const extensionParent = await this.ownedRecordParentFromTree(data, reference.recordUid, session, options.signal);
52080
+ return {
52081
+ recordUid: reference.recordUid,
52082
+ ...extensionParent === void 0 ? {} : { extensionParent }
52083
+ };
52084
+ }
51782
52085
  async loadOwnedRecordExtensions(recordUid, session, signal) {
51783
- const nodes = projectOwnedRecordExtensions(await this.runtime.authenticatedPost("/api/v1/records/extensions/tree", { record_uid: recordUid }, session, signal, {
52086
+ const data = await this.runtime.authenticatedPost("/api/v1/records/extensions/tree", { record_uid: recordUid }, session, signal, {
51784
52087
  lane: "interactive-read",
51785
52088
  bypassCache: true
51786
- }), recordUid);
51787
- if (nodes.length === 0) return void 0;
52089
+ });
52090
+ const nodes = projectOwnedRecordExtensions(data, recordUid);
52091
+ const extensionParent = await this.ownedRecordParentFromTree(data, recordUid, session, signal);
52092
+ if (nodes.length === 0) return extensionParent === void 0 ? void 0 : {
52093
+ extensionCount: 0,
52094
+ extensions: [],
52095
+ extensionParent
52096
+ };
51788
52097
  const readable = nodes.filter((node) => !node.protectedContent);
51789
52098
  const hydration = await this.media.hydrateRecordMediaPage(readable.map((node) => node.record), session, signal);
51790
52099
  const extensions = nodes.map((node) => {
@@ -51838,7 +52147,8 @@ var ChatService = class {
51838
52147
  extensions.sort((left, right) => right.sendAtMillis - left.sendAtMillis);
51839
52148
  return {
51840
52149
  extensionCount: extensions.length,
51841
- extensions
52150
+ extensions,
52151
+ ...extensionParent === void 0 ? {} : { extensionParent }
51842
52152
  };
51843
52153
  }
51844
52154
  async recordEditHistoryTarget(sourceRef, messageActionRef) {
@@ -51911,7 +52221,8 @@ var ChatService = class {
51911
52221
  return {
51912
52222
  parentRecordUid: reference.recordUid,
51913
52223
  extensionCount: recordContext?.extensionCount ?? 0,
51914
- extensions: recordContext?.extensions ?? []
52224
+ extensions: recordContext?.extensions ?? [],
52225
+ ...recordContext !== void 0 && "extensionParent" in recordContext ? { extensionParent: recordContext.extensionParent } : {}
51915
52226
  };
51916
52227
  }
51917
52228
  async extendSourceMessage(sourceRef, messageActionRef, textContent, recordUid, assets = [], options = {}) {
@@ -85947,7 +86258,7 @@ function sharePreviewIsOwnMessage(raw, viewerUserId) {
85947
86258
  const matching = typeof uid === "string" && uid ? anchors.filter((anchor) => anchor.record_uid === uid) : anchors.length === 1 ? anchors : [];
85948
86259
  return matching.length === 1 && String(matching[0].record_owner_user_id) === String(viewerUserId);
85949
86260
  }
85950
- const list$2 = (value) => Array.isArray(value) ? value.slice(0, 150).map(previewObject) : [];
86261
+ const list$1 = (value) => Array.isArray(value) ? value.slice(0, 150).map(previewObject) : [];
85951
86262
  function previewText(value, max = 200) {
85952
86263
  if (typeof value !== "string") return "";
85953
86264
  const plain = value.slice(0, 12e3).replace(/[\u0000-\u001f\u007f\s]+/gu, " ").trim();
@@ -85978,9 +86289,9 @@ function state(data) {
85978
86289
  function sourceSummary(item) {
85979
86290
  if (typeof item.availability === "string" && item.availability !== "available") return "";
85980
86291
  const structure = previewObject(item.structured_content);
85981
- const segments = list$2(structure.long_recording_segments);
86292
+ const segments = list$1(structure.long_recording_segments);
85982
86293
  const body = item.text_content ?? item.text ?? item.content;
85983
- return previewText(item.text_format === "markdown" && typeof body === "string" ? arkmeMarkdownPlainText(body.slice(0, 12e3)) : body) || segments.slice(0, 3).map((s) => [previewText(s.speaker_label, 30), previewText(s.text, 100)].filter(Boolean).join(":")).join(" · ") || list$2(item.media_items ?? item.files).map((m) => previewText(m.file_name, 80)).filter(Boolean).slice(0, 2).join(" · ");
86294
+ return previewText(item.text_format === "markdown" && typeof body === "string" ? arkmeMarkdownPlainText(body.slice(0, 12e3)) : body) || segments.slice(0, 3).map((s) => [previewText(s.speaker_label, 30), previewText(s.text, 100)].filter(Boolean).join(":")).join(" · ") || list$1(item.media_items ?? item.files).map((m) => previewText(m.file_name, 80)).filter(Boolean).slice(0, 2).join(" · ");
85984
86295
  }
85985
86296
  /** Pure projection: no private lookup, recursive resolution, AI, or media download. */
85986
86297
  function projectSharePreview(target, raw) {
@@ -86003,8 +86314,8 @@ function projectSharePreview(target, raw) {
86003
86314
  });
86004
86315
  if (target.kind === "message" || target.kind === "conversation") {
86005
86316
  const forward = previewObject(data.forward_records);
86006
- const items = list$2(target.kind === "conversation" ? forward.items : data.items);
86007
- const bundle = list$2(data.presentation).find((n) => n.kind === "forward_bundle");
86317
+ const items = list$1(target.kind === "conversation" ? forward.items : data.items);
86318
+ const bundle = list$1(data.presentation).find((n) => n.kind === "forward_bundle");
86008
86319
  if (target.kind === "conversation" || items.length > 1 || bundle) {
86009
86320
  const lines = items.filter((item) => !item.availability || item.availability === "available").slice(0, 3).map((item) => [previewText(item.sender_display_name ?? item.owner_name, 40), sourceSummary(item) || previewText(item.title)].filter(Boolean).join(":")).filter(Boolean);
86010
86321
  const fallbackLines = Array.isArray(forward.summary_lines) ? forward.summary_lines.slice(0, 3).map((v) => previewText(v, 100)) : [];
@@ -86021,7 +86332,7 @@ function projectSharePreview(target, raw) {
86021
86332
  state: "unavailable"
86022
86333
  };
86023
86334
  const structure = previewObject(item.structured_content);
86024
- const media = list$2(item.media_items);
86335
+ const media = list$1(item.media_items);
86025
86336
  const first = media[0];
86026
86337
  const kind = item.display_kind === 1 || item.template_kind === 8 ? "article" : structure.structured_kind === 1 || Array.isArray(structure.long_recording_segments) ? "recording" : [
86027
86338
  "agent_message",
@@ -86043,7 +86354,7 @@ function projectSharePreview(target, raw) {
86043
86354
  };
86044
86355
  }
86045
86356
  if (target.kind === "world") {
86046
- const user = list$2(data.items)[0] ?? {};
86357
+ const user = list$1(data.items)[0] ?? {};
86047
86358
  return Object.keys(user).length ? assign(user.nick_name, void 0, user.head_img, user.jotmo_id) : {
86048
86359
  ...base,
86049
86360
  state: "unavailable"
@@ -86468,6 +86779,7 @@ var InterwovenService = class {
86468
86779
  summary: stringValue$2(renderPayload.content ?? item.summary).trim().slice(0, 1e3),
86469
86780
  degraded: booleanValue$4(item.is_degraded),
86470
86781
  sourceChatSessionUid,
86782
+ ...stringValue$2(jumpTarget.subject_uid).trim() === "" ? {} : { legacyGroupUid: stringValue$2(jumpTarget.subject_uid).trim() },
86471
86783
  recordOwnerUserId,
86472
86784
  recordUid,
86473
86785
  relationUid,
@@ -86492,6 +86804,7 @@ var InterwovenService = class {
86492
86804
  userId: session.userId,
86493
86805
  sourceOwnerRef: source.ownerRef,
86494
86806
  sourceChatSessionUid: descriptor.sourceChatSessionUid,
86807
+ ...descriptor.legacyGroupUid === void 0 ? {} : { legacyGroupUid: descriptor.legacyGroupUid },
86495
86808
  recordOwnerUserId: descriptor.recordOwnerUserId,
86496
86809
  recordUid: descriptor.recordUid,
86497
86810
  relationUid: descriptor.relationUid,
@@ -86536,6 +86849,154 @@ var InterwovenService = class {
86536
86849
  ...partial ? { message: "部分交织瞬间暂时不可用,可稍后重试" } : {}
86537
86850
  };
86538
86851
  }
86852
+ async interwovenReadReceipts(sourceRef, momentRefs, signal) {
86853
+ const epoch = this.interactionEpoch;
86854
+ const session = await this.runtime.requireSession();
86855
+ const source = await this.source.openSourceRef(sourceRef, session.userId);
86856
+ if (source.kind !== "private_chat" || momentRefs.length < 1 || momentRefs.length > 20 || momentRefs.some((ref) => typeof ref !== "string" || !ref.trim() || ref.length > 4096) || new Set(momentRefs).size !== momentRefs.length) throw new ArkmePluginError("interwoven-param-invalid", "群互动已读查询参数无效", false, 400);
86857
+ const references = await Promise.all(momentRefs.map((ref) => this.openInterwovenMomentRef(ref, session.userId, source.ownerRef)));
86858
+ const peerId = await this.assertInterwovenOperationAllowed(source, session, signal);
86859
+ await this.resolveReceiptBridgeReferences(sourceRef, source.ownerRef, references, signal);
86860
+ const groupReads = /* @__PURE__ */ new Map();
86861
+ const read = (path, body) => this.runtime.authenticatedChatPost(path, body, session, signal, {
86862
+ lane: "interactive-read",
86863
+ bypassCache: true
86864
+ });
86865
+ const groupDetail = (uid) => {
86866
+ let pending = groupReads.get(uid);
86867
+ if (!pending) {
86868
+ pending = read("/api/v1/chats/detail", { chat_session_uid: uid });
86869
+ groupReads.set(uid, pending);
86870
+ }
86871
+ return pending;
86872
+ };
86873
+ const items = new Array(references.length);
86874
+ let next = 0;
86875
+ const worker = async () => {
86876
+ while (next < references.length) {
86877
+ signal?.throwIfAborted();
86878
+ const index = next++;
86879
+ const ref = references[index];
86880
+ const reader = ref.senderUserId === session.userId ? "peer" : "self";
86881
+ const unknown = {
86882
+ momentId: ref.momentId,
86883
+ reader,
86884
+ status: "unknown"
86885
+ };
86886
+ items[index] = unknown;
86887
+ const groupUid = ref.sourceChatSessionUid || ref.legacyGroupUid || "";
86888
+ if (!groupUid || reader === "self" && ref.senderUserId !== peerId) continue;
86889
+ try {
86890
+ const group = await groupDetail(groupUid);
86891
+ const groupSession = objectValue$2(group.session);
86892
+ if (groupSession.chat_session_uid !== groupUid || groupSession.session_kind !== 2 || groupSession.status !== 1) continue;
86893
+ let sequence = ref.sequence || ref.receiptSequence || 0;
86894
+ const receiptRecordUid = ref.receiptRecordUid || ref.recordUid;
86895
+ if (sequence <= 0) {
86896
+ if (ref.recordUid.startsWith("chat_legacy_receive_")) continue;
86897
+ const around = await read("/api/v1/chat/timeline/around", {
86898
+ chat_session_uid: groupUid,
86899
+ record_uid: ref.recordUid,
86900
+ record_owner_user_id: ref.recordOwnerUserId,
86901
+ before_limit: 1,
86902
+ after_limit: 1
86903
+ });
86904
+ if (around.chat_session_uid !== groupUid) continue;
86905
+ const candidates = [...listValue$11(around.items), around.anchor].map(objectValue$2).filter((item) => {
86906
+ const relation = objectValue$2(item.relation ?? item);
86907
+ return relation.chat_session_uid === groupUid && relation.record_uid === ref.recordUid && relation.record_owner_user_id === ref.recordOwnerUserId && relation.sender_user_id === ref.senderUserId && Number.isSafeInteger(relation.seq) && numberValue$13(relation.seq) > 0 && objectValue$2(item.record).status === 1;
86908
+ });
86909
+ const sequences = new Set(candidates.map((item) => numberValue$13(objectValue$2(item.relation ?? item).seq)));
86910
+ if (sequences.size !== 1) continue;
86911
+ sequence = [...sequences][0];
86912
+ ref.receiptSequence = sequence;
86913
+ }
86914
+ if (reader === "self") {
86915
+ const cursor = objectValue$2(group.current_cursor);
86916
+ const snapshot = objectValue$2(group.unread_snapshot);
86917
+ if (cursor.user_id !== session.userId || cursor.chat_session_uid !== groupUid || cursor.status !== 1) continue;
86918
+ const readSeq = snapshot.read_seq ?? cursor.read_seq;
86919
+ const lastSeq = snapshot.session_last_seq ?? groupSession.last_seq;
86920
+ if (typeof readSeq !== "number" || !Number.isSafeInteger(readSeq) || readSeq < 0 || typeof lastSeq !== "number" || !Number.isSafeInteger(lastSeq) || lastSeq < sequence) continue;
86921
+ items[index] = {
86922
+ ...unknown,
86923
+ status: readSeq >= sequence ? "read" : "unread"
86924
+ };
86925
+ } else {
86926
+ const detail = await read("/api/v1/chats/read-receipts/detail", {
86927
+ chat_session_uid: groupUid,
86928
+ record_uid: receiptRecordUid,
86929
+ seq: sequence
86930
+ });
86931
+ if (detail.chat_session_uid !== groupUid || detail.record_uid !== receiptRecordUid || detail.seq !== sequence || !Array.isArray(detail.items)) continue;
86932
+ const peers = detail.items.map(objectValue$2).filter((member) => member.user_id === peerId);
86933
+ if (peers.length !== 1) continue;
86934
+ const peer = peers[0];
86935
+ if (peer.read_status !== "read" && peer.read_status !== "unread" || typeof peer.read_at !== "number" || !Number.isSafeInteger(peer.read_at) || peer.read_at < 0 || peer.read_status === "unread" && peer.read_at !== 0) continue;
86936
+ items[index] = {
86937
+ ...unknown,
86938
+ status: peer.read_status,
86939
+ ...peer.read_status === "read" && peer.read_at > 0 ? { readAtMillis: peer.read_at } : {}
86940
+ };
86941
+ }
86942
+ } catch {
86943
+ signal?.throwIfAborted();
86944
+ }
86945
+ }
86946
+ };
86947
+ await Promise.all(Array.from({ length: Math.min(3, references.length) }, worker));
86948
+ await this.assertInteractionAccount(session.userId, epoch, signal);
86949
+ return { items };
86950
+ }
86951
+ async resolveReceiptBridgeReferences(sourceRef, privateUid, references, signal) {
86952
+ const pending = references.filter((ref) => !ref.sequence && !ref.receiptSequence && /^chat_legacy_receive_[a-f0-9]{32}$/.test(ref.recordUid));
86953
+ if (!pending.length) return;
86954
+ const matches = /* @__PURE__ */ new Map();
86955
+ let cursor;
86956
+ let expectedVersion;
86957
+ try {
86958
+ for (let page = 0; page < 3; page++) {
86959
+ const { data, userId } = await this.readPrivateInteractions("occurrences/query", {
86960
+ sourceRef,
86961
+ limit: 50,
86962
+ ...cursor === void 0 ? {} : { cursor },
86963
+ ...expectedVersion === void 0 ? {} : { expectedVersion },
86964
+ ...signal === void 0 ? {} : { signal }
86965
+ });
86966
+ const coverage = this.interactionCoverage(data);
86967
+ if (expectedVersion && coverage.version !== expectedVersion) return;
86968
+ expectedVersion = coverage.version;
86969
+ if (!Array.isArray(data.items) || data.items.length > 50 || typeof data.has_more !== "boolean") return;
86970
+ for (const raw of data.items) {
86971
+ const item = objectValue$2(raw);
86972
+ if (this.interactionCount(item.seq) === 0) return;
86973
+ const groupUid = stringValue$2(item.source_chat_session_uid).trim();
86974
+ const relationUid = stringValue$2(item.rel_uid).trim();
86975
+ const recordUid = stringValue$2(item.record_uid).trim();
86976
+ if (!relationUid || !recordUid || item.private_chat_session_uid !== privateUid) continue;
86977
+ const alias = `chat_legacy_receive_${createHash("sha256").update(`${groupUid}:${relationUid}`).digest("hex").slice(0, 32)}`;
86978
+ for (const ref of pending) {
86979
+ if (ref.recordUid !== alias || (ref.sourceChatSessionUid || ref.legacyGroupUid) !== groupUid || ref.recordOwnerUserId !== item.record_owner_user_id || ref.senderUserId !== item.sender_user_id || item.sender_is_me !== (ref.senderUserId === userId)) continue;
86980
+ const next = {
86981
+ recordUid,
86982
+ sequence: item.seq
86983
+ };
86984
+ const previous = matches.get(ref);
86985
+ matches.set(ref, previous === null || previous && (previous.recordUid !== recordUid || previous.sequence !== next.sequence) ? null : next);
86986
+ }
86987
+ }
86988
+ if (!data.has_more || pending.every((ref) => matches.has(ref))) break;
86989
+ if (typeof data.next_cursor !== "string" || !data.next_cursor || data.next_cursor.length > 2048) return;
86990
+ cursor = data.next_cursor;
86991
+ }
86992
+ for (const [ref, match] of matches) if (match) {
86993
+ ref.receiptRecordUid = match.recordUid;
86994
+ ref.receiptSequence = match.sequence;
86995
+ }
86996
+ } catch {
86997
+ signal?.throwIfAborted();
86998
+ }
86999
+ }
86539
87000
  async interwovenMomentDetail(sourceRef, momentRef, signal) {
86540
87001
  const session = await this.runtime.requireSession();
86541
87002
  const source = await this.source.openSourceRef(sourceRef, session.userId);
@@ -86603,7 +87064,7 @@ var InterwovenService = class {
86603
87064
  if (source.kind !== "private_chat") throw new ArkmePluginError("interwoven-source-invalid", "交织瞬间仅支持普通私聊", false, 400);
86604
87065
  if (!this.runtime.config.interwovenMomentsEnabled) throw new ArkmePluginError("interwoven-disabled", "交织瞬间能力当前未开启", false, 403);
86605
87066
  if (!booleanValue$4((await this.runtime.authenticatedAuthPost("/api/v1/auth/able-func", { func_type: 12 }, session, signal)).able)) throw new ArkmePluginError("interwoven-disabled", "交织瞬间能力当前未开放", false, 403);
86606
- await this.assertHumanPrivateSource(source, session, signal);
87067
+ return await this.assertHumanPrivateSource(source, session, signal);
86607
87068
  }
86608
87069
  async assertHumanPrivateSource(source, session, signal) {
86609
87070
  const detail = await this.runtime.authenticatedChatPost("/api/v1/chats/detail", { chat_session_uid: source.ownerRef }, session, signal);
@@ -90178,123 +90639,6 @@ function recordingSearchIdentityHash(item) {
90178
90639
  ]), "utf8").digest("hex");
90179
90640
  }
90180
90641
  //#endregion
90181
- //#region src/recording-speaker-presence.ts
90182
- const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
90183
- const list$1 = (value) => Array.isArray(value) ? value : [];
90184
- const string = (value) => typeof value === "string" ? value.trim() : "";
90185
- const number$1 = (value) => typeof value === "number" && Number.isFinite(value) ? value : NaN;
90186
- /**
90187
- * Count positive-duration ASR facts, including a segment whose text is empty.
90188
- * The day endpoint already scopes visible recordings; user_id additionally
90189
- * prevents a shared or unrelated session from entering this owner's estimate.
90190
- */
90191
- function projectRecentSpeakerDetails(speakers, days, ownerUserId) {
90192
- const personBySpeaker = /* @__PURE__ */ new Map();
90193
- for (const raw of list$1(speakers)) {
90194
- const speaker = record(raw);
90195
- const id = string(speaker.speaker_id ?? speaker.id ?? speaker.spk_id);
90196
- if (id === "") continue;
90197
- const userId = number$1(speaker.ref_usr_id ?? speaker.ref_user_id ?? speaker.user_id);
90198
- personBySpeaker.set(id, Number.isSafeInteger(userId) && userId > 0 ? `user:${userId}` : `speaker:${id}`);
90199
- }
90200
- const presenceByPerson = /* @__PURE__ */ new Map();
90201
- for (const day of days) {
90202
- const dayEnd = new Date(day.dateStamp);
90203
- dayEnd.setDate(dayEnd.getDate() + 1);
90204
- const sessions = /* @__PURE__ */ new Map();
90205
- const response = record(day.response);
90206
- for (const raw of list$1(response.session_ls ?? response.sessions)) {
90207
- const session = record(raw);
90208
- const sessionId = string(session.id ?? session.session_id);
90209
- if (sessionId === "" || number$1(session.user_id) !== ownerUserId) continue;
90210
- const speakerByNumber = /* @__PURE__ */ new Map();
90211
- for (const rawSpeaker of list$1(session.spk_ls ?? session.speakers)) {
90212
- const speaker = record(rawSpeaker);
90213
- const speakerNumber = number$1(speaker.num ?? speaker.speaker_num);
90214
- if (!Number.isSafeInteger(speakerNumber)) continue;
90215
- const displayNumber = number$1(speaker.speaker_display_number);
90216
- const innerId = string(speaker.inner_spk_id);
90217
- const dayId = string(speaker.day_spk_id);
90218
- const legacyToken = string(speaker.inner_display ?? speaker.label);
90219
- const identityKey = Number.isSafeInteger(displayNumber) && displayNumber > 0 ? `number:${displayNumber}` : innerId !== "" ? `inner:${innerId}` : dayId !== "" ? `day:${dayId}` : "";
90220
- const token = Number.isSafeInteger(displayNumber) && displayNumber > 0 ? String(displayNumber) : legacyToken;
90221
- speakerByNumber.set(speakerNumber, {
90222
- speakerId: string(speaker.spk_id ?? speaker.speaker_id),
90223
- identityKey: identityKey !== "" && token !== "" ? identityKey : "",
90224
- token
90225
- });
90226
- }
90227
- sessions.set(sessionId, {
90228
- startAt: number$1(session.start_at),
90229
- speakerByNumber
90230
- });
90231
- }
90232
- for (const raw of list$1(response.child_ls ?? response.children)) {
90233
- const child = record(raw);
90234
- const session = sessions.get(string(child.session_id));
90235
- if (session === void 0 || !Number.isFinite(session.startAt)) continue;
90236
- const offset = number$1(child.start_at);
90237
- if (!Number.isFinite(offset)) continue;
90238
- const childStart = offset >= 1e11 ? offset : session.startAt + offset;
90239
- for (const rawItem of list$1(child.asr)) {
90240
- const item = record(rawItem);
90241
- const startOffset = number$1(item.s ?? item.start_at);
90242
- const endOffset = number$1(item.e ?? item.end_at);
90243
- const seenAt = childStart + startOffset;
90244
- const seenEndAt = childStart + endOffset;
90245
- if (!Number.isFinite(seenAt) || !Number.isFinite(seenEndAt) || seenEndAt <= seenAt || seenAt < day.dateStamp || seenAt >= dayEnd.getTime()) continue;
90246
- if (string(item.speaker_identity_source) === "system") continue;
90247
- const recognized = session.speakerByNumber.get(number$1(item.n ?? item.speaker_num));
90248
- const sessionSpeakerId = recognized?.speakerId ?? "";
90249
- const effectiveId = string(item.effective_spk_id);
90250
- const directId = string(item.q);
90251
- const explicitlyUnassigned = directId === "" && sessionSpeakerId !== "" && string(item.q_unassigned_spk_id) === sessionSpeakerId;
90252
- const speakerId = effectiveId || (explicitlyUnassigned ? "" : directId || sessionSpeakerId);
90253
- const personKey = personBySpeaker.get(speakerId);
90254
- if (personKey === void 0) continue;
90255
- let presence = presenceByPerson.get(personKey);
90256
- if (presence === void 0) {
90257
- presence = {
90258
- days: /* @__PURE__ */ new Set(),
90259
- lastSeenAt: 0,
90260
- members: /* @__PURE__ */ new Map()
90261
- };
90262
- presenceByPerson.set(personKey, presence);
90263
- }
90264
- presence.days.add(day.dateStamp);
90265
- presence.lastSeenAt = Math.max(presence.lastSeenAt, seenEndAt);
90266
- if (recognized?.identityKey) {
90267
- let member = presence.members.get(recognized.identityKey);
90268
- if (member === void 0) {
90269
- member = {
90270
- token: recognized.token,
90271
- days: /* @__PURE__ */ new Set(),
90272
- lastSeenAt: 0
90273
- };
90274
- presence.members.set(recognized.identityKey, member);
90275
- }
90276
- member.days.add(day.dateStamp);
90277
- member.lastSeenAt = Math.max(member.lastSeenAt, seenEndAt);
90278
- }
90279
- }
90280
- }
90281
- }
90282
- return [...personBySpeaker].flatMap(([speakerId, personKey]) => {
90283
- const presence = presenceByPerson.get(personKey);
90284
- return presence === void 0 ? [] : [{
90285
- speakerId,
90286
- dayCount: presence.days.size,
90287
- lastSeenAt: presence.lastSeenAt,
90288
- members: [...presence.members].map(([identityKey, member]) => ({
90289
- identityKey,
90290
- token: member.token,
90291
- dayCount: member.days.size,
90292
- lastSeenAt: member.lastSeenAt
90293
- })).sort((left, right) => right.lastSeenAt - left.lastSeenAt || left.token.localeCompare(right.token))
90294
- }];
90295
- });
90296
- }
90297
- //#endregion
90298
90642
  //#region src/recording-import-coordinator.ts
90299
90643
  function importFailure(error) {
90300
90644
  if (error instanceof RecordingImportContractError) return {
@@ -91213,7 +91557,6 @@ var RecordingService = class {
91213
91557
  recordingImportSource;
91214
91558
  recordingImportOwnerGateway;
91215
91559
  speakerCacheRevision = 0;
91216
- recentSpeakerDetailsCache;
91217
91560
  unsubscribeSpeakerAccount;
91218
91561
  forwardGateway;
91219
91562
  constructor(runtime, dependencies) {
@@ -91385,22 +91728,17 @@ var RecordingService = class {
91385
91728
  this.assertWorkbenchEnabled();
91386
91729
  const session = await this.runtime.requireSession();
91387
91730
  const revision = this.speakerCacheRevision;
91388
- let data;
91389
- try {
91390
- data = await this.runtime.authenticatedAudioPost("/api/v1/audio/speaker-presence/list", {}, session, signal);
91391
- } catch (error) {
91392
- if (!(error instanceof ArkmePluginError) || error.upstreamStatus !== 404 && error.httpStatus !== 404 && error.code !== "arkme-code-404") throw error;
91393
- return await this.recordingSpeakerPresenceRecent(session, revision, signal);
91394
- }
91731
+ const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/speaker-presence/list", {}, session, signal);
91395
91732
  const rawState = stringValue$2(data.state);
91396
91733
  if (rawState !== "building" && rawState !== "fresh" && rawState !== "stale" && rawState !== "failed") throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91734
+ if (!Array.isArray(data.items) || rawState === "fresh" && stringValue$2(data.version) === "") throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91397
91735
  const speakerRefKey = await this.recordingRefKey("arkme-recording-speaker-v1");
91398
91736
  const items = listValue$5(data.items).flatMap((raw) => {
91399
91737
  const item = objectValue$2(raw);
91400
91738
  const speakerId = stringValue$2(item.speaker_id).trim();
91401
91739
  const dayCount = numberValue$5(item.day_count);
91402
91740
  const lastSeenAt = numberValue$5(item.last_seen_at);
91403
- if (speakerId === "" || !Number.isSafeInteger(dayCount) || dayCount <= 0 || !Number.isSafeInteger(lastSeenAt) || lastSeenAt <= 0) return [];
91741
+ if (speakerId === "" || !Number.isSafeInteger(dayCount) || dayCount <= 0 || !Number.isSafeInteger(lastSeenAt) || lastSeenAt <= 0) throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91404
91742
  return [{
91405
91743
  optionKey: this.recordingSpeakerOptionKey(speakerRefKey, session.userId, "speaker", speakerId),
91406
91744
  dayCount,
@@ -91411,115 +91749,58 @@ var RecordingService = class {
91411
91749
  return {
91412
91750
  state: rawState,
91413
91751
  scope: "all-history",
91414
- items,
91752
+ items: rawState === "fresh" ? items : [],
91753
+ ...stringValue$2(data.version) === "" ? {} : { version: stringValue$2(data.version) },
91415
91754
  ...numberValue$5(data.updated_at) > 0 ? { updatedAt: numberValue$5(data.updated_at) } : {},
91416
91755
  ...numberValue$5(data.retry_after_ms) > 0 ? { retryAfterMs: numberValue$5(data.retry_after_ms) } : {}
91417
91756
  };
91418
91757
  }
91419
- async recordingSpeakerPresenceRecent(session, revision, signal) {
91420
- const snapshot = await this.loadRecentSpeakerDetails(session, revision, signal);
91421
- const speakerRefKey = await this.recordingRefKey("arkme-recording-speaker-v1");
91422
- await this.assertSpeakerReadCurrent(session, revision, signal);
91423
- return {
91424
- state: "fresh",
91425
- scope: "recent-seven-days",
91426
- items: snapshot.details.map((stat) => ({
91427
- optionKey: this.recordingSpeakerOptionKey(speakerRefKey, session.userId, "speaker", stat.speakerId),
91428
- dayCount: stat.dayCount,
91429
- lastSeenAt: stat.lastSeenAt
91430
- })),
91431
- updatedAt: snapshot.updatedAt
91432
- };
91433
- }
91434
- /** Read a selected marked person's verifiable source voices, not a guessed all-history mapping. */
91435
- async recordingSpeakerMembers(speakerRef, signal) {
91758
+ /** All-history source identities, authorized by the same account-bound speaker reference as the UI. */
91759
+ async recordingSpeakerMembers(speakerRef, signal, expectedVersion) {
91436
91760
  this.assertWorkbenchEnabled();
91437
91761
  const session = await this.runtime.requireSession();
91438
91762
  const revision = this.speakerCacheRevision;
91439
91763
  const reference = await this.openRecordingSpeakerRef(speakerRef);
91440
91764
  if (reference.viewerUserId !== session.userId || reference.target.kind !== "speaker") throw new ArkmePluginError("recording-speaker-ref-account-mismatch", "说话人引用与当前账号不匹配", false, 403);
91765
+ const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/speaker-presence/detail", {
91766
+ speaker_id: reference.target.speakerId,
91767
+ ...expectedVersion === void 0 ? {} : { expected_version: expectedVersion }
91768
+ }, session, signal);
91769
+ const state = stringValue$2(data.state);
91770
+ if (![
91771
+ "building",
91772
+ "fresh",
91773
+ "stale",
91774
+ "failed"
91775
+ ].includes(state)) throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91776
+ if (state === "fresh" && (stringValue$2(data.version) === "" || !Array.isArray(data.items) || data.members !== void 0 && !Array.isArray(data.members))) throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91777
+ const key = await this.recordingRefKey("arkme-recording-speaker-v1");
91441
91778
  const targetSpeakerId = reference.target.speakerId;
91442
- const snapshot = await this.loadRecentSpeakerDetails(session, revision, signal);
91443
- if (!snapshot.speakerIds.has(targetSpeakerId)) throw new ArkmePluginError("recording-speaker-target-missing", "该说话人已不存在,请重新选择", false, 409);
91444
- const detail = snapshot.details.find((item) => item.speakerId === targetSpeakerId);
91445
- await this.assertSpeakerReadCurrent(session, revision, signal);
91446
- return {
91447
- scope: "recent-seven-days",
91448
- dayCount: detail?.dayCount ?? 0,
91449
- lastSeenAt: detail?.lastSeenAt ?? 0,
91450
- items: detail?.members.map(({ token, dayCount, lastSeenAt }) => ({
91779
+ const stat = listValue$5(data.items).map(objectValue$2).find((item) => item.speaker_id === targetSpeakerId);
91780
+ if (state === "fresh" && stat !== void 0 && (!Number.isSafeInteger(stat.day_count) || numberValue$5(stat.day_count) <= 0 || !Number.isSafeInteger(stat.last_seen_at) || numberValue$5(stat.last_seen_at) <= 0)) throw new ArkmePluginError("recording-speaker-presence-invalid", "说话人出现统计响应无效", true, 502);
91781
+ const items = state !== "fresh" ? [] : listValue$5(data.members).map((raw) => {
91782
+ const item = objectValue$2(raw);
91783
+ const identity = stringValue$2(item.identity_key);
91784
+ const token = stringValue$2(item.token);
91785
+ const dayCount = numberValue$5(item.day_count);
91786
+ const lastSeenAt = numberValue$5(item.last_seen_at);
91787
+ if (stat === void 0 || dayCount > numberValue$5(stat.day_count) || lastSeenAt > numberValue$5(stat.last_seen_at) || identity === "" || token === "" || !Number.isSafeInteger(dayCount) || dayCount <= 0 || !Number.isSafeInteger(lastSeenAt) || lastSeenAt <= 0) throw new ArkmePluginError("recording-speaker-members-invalid", "关联说话人响应无效", true, 502);
91788
+ return {
91789
+ identityKey: this.recordingSpeakerOptionKey(key, session.userId, "speaker", `presence:${identity}`),
91451
91790
  token,
91452
91791
  dayCount,
91453
91792
  lastSeenAt
91454
- })) ?? []
91455
- };
91456
- }
91457
- async loadRecentSpeakerDetails(session, revision, signal) {
91458
- const today = /* @__PURE__ */ new Date();
91459
- today.setHours(0, 0, 0, 0);
91460
- const firstDay = new Date(today);
91461
- firstDay.setDate(firstDay.getDate() - 6);
91462
- const cached = this.recentSpeakerDetailsCache;
91463
- if (cached?.ownerUserId === session.userId && cached.revision === revision && cached.firstDay === firstDay.getTime() && Date.now() - cached.updatedAt < 15e3) {
91464
- await this.assertSpeakerReadCurrent(session, revision, signal);
91465
- return {
91466
- speakerIds: cached.speakerIds,
91467
- details: cached.details,
91468
- updatedAt: cached.updatedAt
91469
91793
  };
91470
- }
91471
- const afterLastDay = new Date(today);
91472
- afterLastDay.setDate(afterLastDay.getDate() + 1);
91473
- const [speakerData, calendar] = await Promise.all([this.runtime.authenticatedAudioPost("/api/v1/audio/get-speaker-ls", {}, session, signal), this.runtime.authenticatedAudioPost("/api/v1/audio/get-calender-summary", {
91474
- from_stamp: firstDay.getTime(),
91475
- to_stamp: afterLastDay.getTime()
91476
- }, session, signal)]);
91477
- const durations = calendar.duration_ls;
91478
- const unreviewed = calendar.un_click_session_ids_per_day;
91479
- if (!Array.isArray(speakerData.spk_ls) || !Array.isArray(durations) || durations.length < 7 || !Array.isArray(unreviewed) || unreviewed.length < 7) throw new ArkmePluginError("recording-speaker-recent-invalid", "近 7 天录音统计响应无效", true, 502);
91480
- const days = [];
91481
- const cursor = new Date(firstDay);
91482
- for (let index = 0; index < 7; index += 1) {
91483
- if (numberValue$5(durations[index]) > 0 || listValue$5(unreviewed[index]).length > 0) days.push(cursor.getTime());
91484
- cursor.setDate(cursor.getDate() + 1);
91485
- }
91486
- const transcripts = [];
91487
- let nextDay = 0;
91488
- const readDay = async () => {
91489
- while (nextDay < days.length) {
91490
- const dateStamp = days[nextDay++];
91491
- signal?.throwIfAborted();
91492
- const day = new Date(dateStamp);
91493
- const response = await this.runtime.authenticatedAudioPost("/api/v1/audio/one-day-trans", {
91494
- start_at: dateStamp,
91495
- tz_offset: -day.getTimezoneOffset() * 6e4
91496
- }, session, signal);
91497
- transcripts.push({
91498
- dateStamp,
91499
- response
91500
- });
91501
- }
91502
- };
91503
- await Promise.all(Array.from({ length: Math.min(2, days.length) }, readDay));
91504
- const details = projectRecentSpeakerDetails(speakerData.spk_ls, transcripts, session.userId);
91505
- const speakerIds = new Set(listValue$5(speakerData.spk_ls).map((raw) => {
91506
- const speaker = objectValue$2(raw);
91507
- return stringValue$2(speaker.speaker_id ?? speaker.id ?? speaker.spk_id).trim();
91508
- }).filter(Boolean));
91794
+ });
91509
91795
  await this.assertSpeakerReadCurrent(session, revision, signal);
91510
- const updatedAt = Date.now();
91511
- this.recentSpeakerDetailsCache = {
91512
- ownerUserId: session.userId,
91513
- revision,
91514
- firstDay: firstDay.getTime(),
91515
- updatedAt,
91516
- speakerIds,
91517
- details
91518
- };
91519
91796
  return {
91520
- speakerIds,
91521
- details,
91522
- updatedAt
91797
+ state,
91798
+ scope: "all-history",
91799
+ dayCount: state === "fresh" ? numberValue$5(stat?.day_count) : 0,
91800
+ lastSeenAt: state === "fresh" ? numberValue$5(stat?.last_seen_at) : 0,
91801
+ items,
91802
+ ...stringValue$2(data.version) === "" ? {} : { version: stringValue$2(data.version) },
91803
+ ...numberValue$5(data.retry_after_ms) > 0 ? { retryAfterMs: numberValue$5(data.retry_after_ms) } : {}
91523
91804
  };
91524
91805
  }
91525
91806
  async recordingSpeakerRecommendation(itemRef, signal) {
@@ -95065,6 +95346,7 @@ var UnmarkedSpeakerService = class {
95065
95346
  const projectionState = data.projection_state === void 0 ? void 0 : knownStringValue(data.projection_state, PROJECTION_STATES);
95066
95347
  if (data.projection_state !== void 0 && projectionState === void 0) throw new ArkmePluginError("unmarked-list-contract-invalid", "未标记说话人列表投影状态无效", true, 502);
95067
95348
  const items = [];
95349
+ const identitySecret = await this.runtime.stateStore.uniqueCode();
95068
95350
  for (const value of listValue$2(data.items).slice(0, LIST_CAP)) {
95069
95351
  const raw = objectValue$2(value);
95070
95352
  const candidateId = stringValue$2(raw.candidate_id).trim();
@@ -95092,6 +95374,14 @@ var UnmarkedSpeakerService = class {
95092
95374
  kind: "unmarked-speaker",
95093
95375
  candidateRef,
95094
95376
  speakerToken,
95377
+ appearanceDays,
95378
+ latestAtMillis,
95379
+ identityKey: createHmac("sha256", identitySecret).update(JSON.stringify([
95380
+ "unmarked-speaker-identity-v1",
95381
+ this.runtime.config.environment,
95382
+ session.userId,
95383
+ candidateId
95384
+ ])).digest("base64url"),
95095
95385
  displayName: unmarkedSpeakerDisplayName({
95096
95386
  speakerToken,
95097
95387
  firstSeenDate: localDate(status === "single_day" ? latestAtMillis : firstSeenAtMillis),
@@ -98026,6 +98316,12 @@ var ArkmeService = class {
98026
98316
  async membershipCatalog(expectedUserId) {
98027
98317
  return await this.membershipOwner.catalog(expectedUserId);
98028
98318
  }
98319
+ async aiPointsAccount(expectedScope, signal) {
98320
+ return await new AiPointsService(this.runtime).account(expectedScope, signal);
98321
+ }
98322
+ async aiPointsConsumption(query, expectedScope, signal) {
98323
+ return await new AiPointsService(this.runtime).consumption(query, expectedScope, signal);
98324
+ }
98029
98325
  async accountTokenUsage(expectedScope) {
98030
98326
  return await new AccountUsageService(this.runtime).tokens(expectedScope);
98031
98327
  }
@@ -98260,6 +98556,7 @@ var ArkmeService = class {
98260
98556
  revisionPolling: true,
98261
98557
  userProfile: true,
98262
98558
  accountSettings: true,
98559
+ aiPoints: true,
98263
98560
  imageRead: true,
98264
98561
  recordCalendar: true,
98265
98562
  imageLibrary: true,
@@ -98271,6 +98568,7 @@ var ArkmeService = class {
98271
98568
  ...this.runtime.stateStore.commonGroups ? { commonGroups: true } : {},
98272
98569
  remoteRecordSearch: true,
98273
98570
  contactDirectoryReads: true,
98571
+ speakerPresence: true,
98274
98572
  sourceTimeline: true,
98275
98573
  forwardContent: true,
98276
98574
  sourceTextSend: true,
@@ -98535,8 +98833,8 @@ var ArkmeService = class {
98535
98833
  async recordingSpeakerPresence(signal) {
98536
98834
  return await this.recording.recordingSpeakerPresence(signal);
98537
98835
  }
98538
- async recordingSpeakerMembers(speakerRef, signal) {
98539
- return await this.recording.recordingSpeakerMembers(speakerRef, signal);
98836
+ async recordingSpeakerMembers(speakerRef, signal, expectedVersion) {
98837
+ return await this.recording.recordingSpeakerMembers(speakerRef, signal, expectedVersion);
98540
98838
  }
98541
98839
  async recordingSpeakerRecommendation(itemRef, signal) {
98542
98840
  return await this.recording.recordingSpeakerRecommendation(itemRef, signal);
@@ -98775,6 +99073,9 @@ var ArkmeService = class {
98775
99073
  async interwovenMomentDetail(sourceRef, momentRef, signal) {
98776
99074
  return await this.interwoven.interwovenMomentDetail(sourceRef, momentRef, signal);
98777
99075
  }
99076
+ async interwovenReadReceipts(sourceRef, momentRefs, signal) {
99077
+ return await this.interwoven.interwovenReadReceipts(sourceRef, momentRefs, signal);
99078
+ }
98778
99079
  async recordEditHistoryPage(sourceRef, messageActionRef, cursorEditAt = 0, signal) {
98779
99080
  return await this.recordEditHistory.page(await this.chat.recordEditHistoryTarget(sourceRef, messageActionRef), cursorEditAt, signal);
98780
99081
  }
@@ -99212,6 +99513,9 @@ var ArkmeService = class {
99212
99513
  })
99213
99514
  };
99214
99515
  }
99516
+ async sourceMessageExtensionParent(sourceRef, messageActionRef, options = {}) {
99517
+ return this.chat.sourceMessageExtensionParent(sourceRef, messageActionRef, options);
99518
+ }
99215
99519
  async extendSourceMessage(sourceRef, messageActionRef, textContent, recordUid, fileRefs = [], options = {}) {
99216
99520
  if (options.textFormat === "markdown" && this.config.markdownQuickNotesEnabled !== true) throw new ArkmePluginError("markdown-send-disabled", "Markdown 发送尚未开放,请稍后重试", false, 403);
99217
99521
  const context = await this.chat.sourceMessageExtensionContext(sourceRef, messageActionRef, options);