@senguoyun/dsh-arkme 0.1.12 → 0.1.14

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 (169) hide show
  1. package/README.md +2 -2
  2. package/cordis.patch.yml +4 -1
  3. package/docs/consumer-plugin-contract.md +15 -4
  4. package/docs/extension-market-controls.md +47 -8
  5. package/docs/gui-production-integration.md +11 -10
  6. package/lib/client.js +44991 -25812
  7. package/lib/client.js.map +1 -1
  8. package/lib/extension-profile-restart-helper.js +19 -3
  9. package/lib/index.js +1475 -217
  10. package/lib/{install-store-D6P-dH2i.js → install-store-Sn3Ccxfu.js} +32 -5
  11. package/lib/persistent-extension.js +1 -1
  12. package/lib/{persistent-runtime-kUpXXUZI.js → persistent-runtime-BAArZI-P.js} +1187 -173
  13. package/lib/plugin-updater-helper-CiPlNqBc.js +346 -0
  14. package/lib/plugin-updater-helper.js +2 -250
  15. package/lib/sdk.js +150 -2
  16. package/lib/types/arkme-service.d.ts +34 -1
  17. package/lib/types/arkme-service.d.ts.map +1 -1
  18. package/lib/types/bot-avatar-ref.d.ts +3 -0
  19. package/lib/types/bot-avatar-ref.d.ts.map +1 -0
  20. package/lib/types/client/ArkmeAppUpdateDialog.d.ts +5 -0
  21. package/lib/types/client/ArkmeAppUpdateDialog.d.ts.map +1 -0
  22. package/lib/types/client/ArkmeBotCreateDialog.d.ts +6 -0
  23. package/lib/types/client/ArkmeBotCreateDialog.d.ts.map +1 -0
  24. package/lib/types/client/ArkmeCalendarSurface.d.ts +4 -1
  25. package/lib/types/client/ArkmeCalendarSurface.d.ts.map +1 -1
  26. package/lib/types/client/ArkmeContactAddSurface.d.ts +9 -0
  27. package/lib/types/client/ArkmeContactAddSurface.d.ts.map +1 -0
  28. package/lib/types/client/ArkmeConversationSurface.d.ts +8 -18
  29. package/lib/types/client/ArkmeConversationSurface.d.ts.map +1 -1
  30. package/lib/types/client/ArkmeExtensionReviews.d.ts +10 -0
  31. package/lib/types/client/ArkmeExtensionReviews.d.ts.map +1 -1
  32. package/lib/types/client/ArkmeFooterDropdown.d.ts.map +1 -1
  33. package/lib/types/client/ArkmeLogin.d.ts.map +1 -1
  34. package/lib/types/client/ArkmeMarketplace.d.ts +164 -0
  35. package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -0
  36. package/lib/types/client/ArkmePersistentShell.d.ts +20 -0
  37. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -0
  38. package/lib/types/client/ArkmeProductNavigation.d.ts +4 -3
  39. package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
  40. package/lib/types/client/ArkmeQuickAdd.d.ts +12 -0
  41. package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -0
  42. package/lib/types/client/ArkmeSettingsRow.d.ts +1 -1
  43. package/lib/types/client/ArkmeSettingsRow.d.ts.map +1 -1
  44. package/lib/types/client/ArkmeSettingsSurface.d.ts +24 -1
  45. package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
  46. package/lib/types/client/ArkmeSidebar.d.ts +7 -1
  47. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  48. package/lib/types/client/ArkmeStartupAuthGate.d.ts.map +1 -1
  49. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +12 -1
  50. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  51. package/lib/types/client/ArkmeWorldSurface.d.ts +89 -0
  52. package/lib/types/client/ArkmeWorldSurface.d.ts.map +1 -0
  53. package/lib/types/client/DeepSeekHarnessSurface.d.ts +6 -0
  54. package/lib/types/client/DeepSeekHarnessSurface.d.ts.map +1 -0
  55. package/lib/types/client/api.d.ts +1 -1
  56. package/lib/types/client/api.d.ts.map +1 -1
  57. package/lib/types/client/app-update-store.d.ts +35 -0
  58. package/lib/types/client/app-update-store.d.ts.map +1 -0
  59. package/lib/types/client/arkme-emoji.d.ts +3 -0
  60. package/lib/types/client/arkme-emoji.d.ts.map +1 -0
  61. package/lib/types/client/extension-market-model.d.ts +1 -0
  62. package/lib/types/client/extension-market-model.d.ts.map +1 -1
  63. package/lib/types/client/index.d.ts +11 -3
  64. package/lib/types/client/index.d.ts.map +1 -1
  65. package/lib/types/client/redesign/ArkmeRootFrame.d.ts +19 -0
  66. package/lib/types/client/redesign/ArkmeRootFrame.d.ts.map +1 -0
  67. package/lib/types/client/redesign/ArkmeTaskSurface.d.ts +15 -0
  68. package/lib/types/client/redesign/ArkmeTaskSurface.d.ts.map +1 -0
  69. package/lib/types/client/redesign/ArkmeWorkspaceDialog.d.ts +9 -0
  70. package/lib/types/client/redesign/ArkmeWorkspaceDialog.d.ts.map +1 -0
  71. package/lib/types/client/redesign/layout-controller.d.ts +17 -0
  72. package/lib/types/client/redesign/layout-controller.d.ts.map +1 -0
  73. package/lib/types/client/redesign/styles.d.ts +3 -0
  74. package/lib/types/client/redesign/styles.d.ts.map +1 -0
  75. package/lib/types/client/redesign/task-session.d.ts +14 -0
  76. package/lib/types/client/redesign/task-session.d.ts.map +1 -0
  77. package/lib/types/client/redesign/theme-presenter.d.ts +10 -0
  78. package/lib/types/client/redesign/theme-presenter.d.ts.map +1 -0
  79. package/lib/types/client/slots-contract.d.ts +2 -2
  80. package/lib/types/client/slots-contract.d.ts.map +1 -1
  81. package/lib/types/client/ui-controller.d.ts +21 -8
  82. package/lib/types/client/ui-controller.d.ts.map +1 -1
  83. package/lib/types/client/world-voiceprint-expectation-copy.d.ts +7 -0
  84. package/lib/types/client/world-voiceprint-expectation-copy.d.ts.map +1 -0
  85. package/lib/types/extensions/bundle-artifact.d.ts +22 -0
  86. package/lib/types/extensions/bundle-artifact.d.ts.map +1 -1
  87. package/lib/types/extensions/install-store.d.ts +1 -0
  88. package/lib/types/extensions/install-store.d.ts.map +1 -1
  89. package/lib/types/extensions/manager.d.ts +38 -2
  90. package/lib/types/extensions/manager.d.ts.map +1 -1
  91. package/lib/types/extensions/owned-inventory.d.ts +29 -3
  92. package/lib/types/extensions/owned-inventory.d.ts.map +1 -1
  93. package/lib/types/extensions/owned-refs.d.ts +2 -1
  94. package/lib/types/extensions/owned-refs.d.ts.map +1 -1
  95. package/lib/types/extensions/owned-store.d.ts +8 -0
  96. package/lib/types/extensions/owned-store.d.ts.map +1 -1
  97. package/lib/types/extensions/owned-types.d.ts +18 -5
  98. package/lib/types/extensions/owned-types.d.ts.map +1 -1
  99. package/lib/types/extensions/profile-owned-inventory.d.ts +1 -0
  100. package/lib/types/extensions/profile-owned-inventory.d.ts.map +1 -1
  101. package/lib/types/extensions/profile-restart-helper.d.ts.map +1 -1
  102. package/lib/types/extensions/publish-client.d.ts +47 -2
  103. package/lib/types/extensions/publish-client.d.ts.map +1 -1
  104. package/lib/types/extensions/publisher-role.d.ts +9 -0
  105. package/lib/types/extensions/publisher-role.d.ts.map +1 -0
  106. package/lib/types/extensions/signature.d.ts +31 -1
  107. package/lib/types/extensions/signature.d.ts.map +1 -1
  108. package/lib/types/extensions/types.d.ts +82 -5
  109. package/lib/types/extensions/types.d.ts.map +1 -1
  110. package/lib/types/host-api.d.ts.map +1 -1
  111. package/lib/types/index.d.ts +10 -4
  112. package/lib/types/index.d.ts.map +1 -1
  113. package/lib/types/plugin-update-artifact.d.ts +46 -0
  114. package/lib/types/plugin-update-artifact.d.ts.map +1 -0
  115. package/lib/types/plugin-update.d.ts +20 -11
  116. package/lib/types/plugin-update.d.ts.map +1 -1
  117. package/lib/types/plugin-updater-helper.d.ts +11 -0
  118. package/lib/types/plugin-updater-helper.d.ts.map +1 -1
  119. package/lib/types/sdk/index.d.ts +77 -7
  120. package/lib/types/sdk/index.d.ts.map +1 -1
  121. package/lib/types/services/bot-service.d.ts +5 -1
  122. package/lib/types/services/bot-service.d.ts.map +1 -1
  123. package/lib/types/services/contact-service.d.ts +25 -0
  124. package/lib/types/services/contact-service.d.ts.map +1 -0
  125. package/lib/types/services/group-service.d.ts +4 -1
  126. package/lib/types/services/group-service.d.ts.map +1 -1
  127. package/lib/types/services/record-service.d.ts +2 -1
  128. package/lib/types/services/record-service.d.ts.map +1 -1
  129. package/lib/types/services/service.d.ts +1 -0
  130. package/lib/types/services/service.d.ts.map +1 -1
  131. package/lib/types/services/world-service.d.ts +53 -6
  132. package/lib/types/services/world-service.d.ts.map +1 -1
  133. package/lib/types/tools/business/bots/create.d.ts.map +1 -1
  134. package/lib/types/tools/business/contacts/index.d.ts +4 -0
  135. package/lib/types/tools/business/contacts/index.d.ts.map +1 -0
  136. package/lib/types/tools/business/groups/create.d.ts +2 -0
  137. package/lib/types/tools/business/groups/create.d.ts.map +1 -0
  138. package/lib/types/tools/business/groups/index.d.ts +1 -0
  139. package/lib/types/tools/business/groups/index.d.ts.map +1 -1
  140. package/lib/types/tools/business/index.d.ts.map +1 -1
  141. package/lib/types/tools/business/world/index.d.ts +4 -0
  142. package/lib/types/tools/business/world/index.d.ts.map +1 -1
  143. package/lib/types/tools/business/world/mine.d.ts +2 -0
  144. package/lib/types/tools/business/world/mine.d.ts.map +1 -0
  145. package/lib/types/tools/business/world/user.d.ts +2 -0
  146. package/lib/types/tools/business/world/user.d.ts.map +1 -0
  147. package/lib/types/tools/business/world/voiceprint-invite.d.ts +2 -0
  148. package/lib/types/tools/business/world/voiceprint-invite.d.ts.map +1 -0
  149. package/lib/types/tools/business/world/voiceprint-social-context.d.ts +2 -0
  150. package/lib/types/tools/business/world/voiceprint-social-context.d.ts.map +1 -0
  151. package/lib/types/tools/extensions/index.d.ts.map +1 -1
  152. package/lib/types/tools/extensions/publish-conversation.d.ts +8 -1
  153. package/lib/types/tools/extensions/publish-conversation.d.ts.map +1 -1
  154. package/lib/types/tools/ports/contacts.d.ts +12 -0
  155. package/lib/types/tools/ports/contacts.d.ts.map +1 -0
  156. package/lib/types/tools/ports/groups.d.ts +4 -1
  157. package/lib/types/tools/ports/groups.d.ts.map +1 -1
  158. package/lib/types/tools/ports/index.d.ts +3 -2
  159. package/lib/types/tools/ports/index.d.ts.map +1 -1
  160. package/lib/types/tools/ports/world.d.ts +21 -1
  161. package/lib/types/tools/ports/world.d.ts.map +1 -1
  162. package/lib/types/tools/prompts/business.d.ts.map +1 -1
  163. package/lib/types/tools/registry/registrar.d.ts.map +1 -1
  164. package/lib/types/types.d.ts +71 -3
  165. package/lib/types/types.d.ts.map +1 -1
  166. package/package.json +8 -4
  167. package/lib/plugin-update-install-state-CIsOCjWK.js +0 -67
  168. package/lib/types/client/ArkmeExtensionCenter.d.ts +0 -74
  169. package/lib/types/client/ArkmeExtensionCenter.d.ts.map +0 -1
@@ -4,10 +4,10 @@ import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:p
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { open } from "node:fs/promises";
6
6
  import OSS from "ali-oss";
7
+ import { gunzipSync, gzipSync } from "node:zlib";
7
8
  import { createContext, runInContext } from "node:vm";
8
9
  import { Context } from "@deepseek-ai/cordis";
9
10
  import { defineTool } from "@deepseek-ai/dsh-tools";
10
- import { gunzipSync, gzipSync } from "node:zlib";
11
11
  import { parseDocument } from "yaml";
12
12
  //#region src/outgoing-call-contract.ts
13
13
  var ArkmeOutgoingCallError = class extends Error {
@@ -1072,7 +1072,7 @@ var ServiceRuntime = class {
1072
1072
  }
1073
1073
  async extensionPost(path, body, signal, options = {}) {
1074
1074
  const baseUrl = this.config.extensionPublishBaseUrl?.trim() ?? "";
1075
- if (baseUrl === "") throw new ArkmePluginError("extension-service-disabled", "扩展市场服务尚未配置", false, 503);
1075
+ if (baseUrl === "") throw new ArkmePluginError("extension-service-disabled", "市集服务尚未配置", false, 503);
1076
1076
  let session = await this.requireSession();
1077
1077
  const requestOptions = () => ({
1078
1078
  ...options,
@@ -1091,10 +1091,10 @@ var ServiceRuntime = class {
1091
1091
  };
1092
1092
  //#endregion
1093
1093
  //#region src/services/ai-video-service.ts
1094
- function numberValue$21(value) {
1094
+ function numberValue$22(value) {
1095
1095
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
1096
1096
  }
1097
- function booleanValue$12(value) {
1097
+ function booleanValue$13(value) {
1098
1098
  return value === true;
1099
1099
  }
1100
1100
  function listValue$21(value) {
@@ -1109,12 +1109,12 @@ var AiVideoService = class {
1109
1109
  const session = await this.runtime.requireSession();
1110
1110
  const data = await this.runtime.authenticatedIntelligentPost("/api/v1/ai-comic-video/preflight", this.aiVideoSelectionBody(sessionId, segments), session, signal);
1111
1111
  return {
1112
- allowed: booleanValue$12(data.allowed),
1112
+ allowed: booleanValue$13(data.allowed),
1113
1113
  message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
1114
- selectedDurationMillis: Math.max(0, numberValue$21(data.selected_duration_millis)),
1115
- minimumDurationMillis: Math.max(0, numberValue$21(data.minimum_duration_millis)),
1116
- selectedSegmentCount: Math.max(0, numberValue$21(data.selected_segment_count)),
1117
- retryable: booleanValue$12(data.retryable),
1114
+ selectedDurationMillis: Math.max(0, numberValue$22(data.selected_duration_millis)),
1115
+ minimumDurationMillis: Math.max(0, numberValue$22(data.minimum_duration_millis)),
1116
+ selectedSegmentCount: Math.max(0, numberValue$22(data.selected_segment_count)),
1117
+ retryable: booleanValue$13(data.retryable),
1118
1118
  ...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
1119
1119
  ...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
1120
1120
  };
@@ -1150,7 +1150,7 @@ var AiVideoService = class {
1150
1150
  }, session, options.signal);
1151
1151
  return {
1152
1152
  items: listValue$21(data.items).map((raw) => this.aiVideoListItem(raw)).filter((item) => item !== void 0),
1153
- hasMore: booleanValue$12(data.has_more),
1153
+ hasMore: booleanValue$13(data.has_more),
1154
1154
  ...stringValue$1(data.next_cursor).trim() === "" ? {} : { nextCursor: stringValue$1(data.next_cursor).trim() }
1155
1155
  };
1156
1156
  }
@@ -1161,13 +1161,13 @@ var AiVideoService = class {
1161
1161
  texts
1162
1162
  }, session, signal);
1163
1163
  return {
1164
- allowed: booleanValue$12(data.allowed),
1164
+ allowed: booleanValue$13(data.allowed),
1165
1165
  message: stringValue$1(data.message).trim() || "AI 视频内容检查已完成",
1166
- selectedDurationMillis: Math.max(0, numberValue$21(data.selected_duration_millis)),
1167
- minimumDurationMillis: Math.max(0, numberValue$21(data.minimum_duration_millis)),
1168
- selectedSegmentCount: Math.max(0, numberValue$21(data.selected_segment_count)),
1169
- selectedTextCount: Math.max(0, numberValue$21(data.selected_text_count)),
1170
- retryable: booleanValue$12(data.retryable),
1166
+ selectedDurationMillis: Math.max(0, numberValue$22(data.selected_duration_millis)),
1167
+ minimumDurationMillis: Math.max(0, numberValue$22(data.minimum_duration_millis)),
1168
+ selectedSegmentCount: Math.max(0, numberValue$22(data.selected_segment_count)),
1169
+ selectedTextCount: Math.max(0, numberValue$22(data.selected_text_count)),
1170
+ retryable: booleanValue$13(data.retryable),
1171
1171
  ...stringValue$1(data.reason_code).trim() === "" ? {} : { reasonCode: stringValue$1(data.reason_code).trim() },
1172
1172
  ...stringValue$1(data.proof).trim() === "" ? {} : { proof: stringValue$1(data.proof).trim() }
1173
1173
  };
@@ -1212,13 +1212,13 @@ var AiVideoService = class {
1212
1212
  jobId,
1213
1213
  status,
1214
1214
  stage: stringValue$1(data.stage).trim() || status,
1215
- progress: Math.min(100, Math.max(0, Math.trunc(numberValue$21(data.progress)))),
1215
+ progress: Math.min(100, Math.max(0, Math.trunc(numberValue$22(data.progress)))),
1216
1216
  selectedSegmentCount: segmentCount,
1217
1217
  ...textCount === 0 ? {} : { selectedTextCount: textCount },
1218
- retryable: booleanValue$12(data.retryable),
1218
+ retryable: booleanValue$13(data.retryable),
1219
1219
  ...stringValue$1(data.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(data.video_asset_uid).trim() },
1220
1220
  ...stringValue$1(data.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(data.cover_asset_uid).trim() },
1221
- ...numberValue$21(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$21(data.video_duration_millis) },
1221
+ ...numberValue$22(data.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$22(data.video_duration_millis) },
1222
1222
  ...stringValue$1(data.error_code).trim() === "" ? {} : { errorCode: stringValue$1(data.error_code).trim() },
1223
1223
  ...stringValue$1(data.error_message).trim() === "" ? {} : { errorMessage: stringValue$1(data.error_message).trim() },
1224
1224
  ...stringValue$1(data.failure_stage).trim() === "" ? {} : { failureStage: stringValue$1(data.failure_stage).trim() }
@@ -1241,17 +1241,17 @@ var AiVideoService = class {
1241
1241
  sessionId: stringValue$1(item.session_id).trim(),
1242
1242
  status,
1243
1243
  stage: stringValue$1(item.stage).trim() || status,
1244
- progress: Math.min(100, Math.max(0, Math.trunc(numberValue$21(item.progress)))),
1244
+ progress: Math.min(100, Math.max(0, Math.trunc(numberValue$22(item.progress)))),
1245
1245
  title: stringValue$1(source.title).trim() || "长录音 AI 视频",
1246
- sourceStartedAtMillis: numberValue$21(source.started_at),
1247
- selectedDurationMillis: Math.max(0, numberValue$21(source.selected_duration_millis)),
1248
- selectedSegmentCount: Math.max(0, Math.trunc(numberValue$21(item.selected_segment_count))),
1249
- retryable: booleanValue$12(item.retryable),
1250
- createdAtMillis: numberValue$21(item.created_at),
1251
- updatedAtMillis: numberValue$21(item.updated_at),
1246
+ sourceStartedAtMillis: numberValue$22(source.started_at),
1247
+ selectedDurationMillis: Math.max(0, numberValue$22(source.selected_duration_millis)),
1248
+ selectedSegmentCount: Math.max(0, Math.trunc(numberValue$22(item.selected_segment_count))),
1249
+ retryable: booleanValue$13(item.retryable),
1250
+ createdAtMillis: numberValue$22(item.created_at),
1251
+ updatedAtMillis: numberValue$22(item.updated_at),
1252
1252
  ...stringValue$1(item.cover_asset_uid).trim() === "" ? {} : { coverAssetUid: stringValue$1(item.cover_asset_uid).trim() },
1253
1253
  ...stringValue$1(item.video_asset_uid).trim() === "" ? {} : { videoAssetUid: stringValue$1(item.video_asset_uid).trim() },
1254
- ...numberValue$21(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$21(item.video_duration_millis) },
1254
+ ...numberValue$22(item.video_duration_millis) <= 0 ? {} : { videoDurationMillis: numberValue$22(item.video_duration_millis) },
1255
1255
  ...stringValue$1(item.error_code).trim() === "" ? {} : { errorCode: stringValue$1(item.error_code).trim() },
1256
1256
  ...stringValue$1(item.error_message).trim() === "" ? {} : { errorMessage: clippedText(item.error_message, 500) }
1257
1257
  };
@@ -1277,10 +1277,10 @@ const ARKO_RUN_STATUSES = /* @__PURE__ */ new Set([
1277
1277
  "expired"
1278
1278
  ]);
1279
1279
  const ARKO_PROFILE_DISPLAY_NAME_CACHE_TTL_MS = 10 * 6e4;
1280
- function numberValue$20(value) {
1280
+ function numberValue$21(value) {
1281
1281
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
1282
1282
  }
1283
- function booleanValue$11(value) {
1283
+ function booleanValue$12(value) {
1284
1284
  return value === true;
1285
1285
  }
1286
1286
  function listValue$20(value) {
@@ -1312,7 +1312,7 @@ function joinUrlWithQuery(baseUrl, path, query) {
1312
1312
  }
1313
1313
  function arkoProfileFromData(data) {
1314
1314
  const displayName = stringValue$1(data.display_name).trim();
1315
- const version = numberValue$20(data.version);
1315
+ const version = numberValue$21(data.version);
1316
1316
  if (displayName === "" || !Number.isSafeInteger(version) || version < 0) throw new ArkmePluginError("arko-profile-contract-invalid", "Arko 资料响应不完整", true, 502);
1317
1317
  return {
1318
1318
  displayName,
@@ -1336,8 +1336,8 @@ function arkoModelOptionFromData(value) {
1336
1336
  displayName,
1337
1337
  provider,
1338
1338
  description,
1339
- recommended: booleanValue$11(data.recommended),
1340
- selected: booleanValue$11(data.selected)
1339
+ recommended: booleanValue$12(data.recommended),
1340
+ selected: booleanValue$12(data.selected)
1341
1341
  };
1342
1342
  }
1343
1343
  function arkoModelCatalogFromData(data) {
@@ -1365,12 +1365,12 @@ function parsedJsonRecord(value) {
1365
1365
  }
1366
1366
  function arkoHistoryItemFromData(value) {
1367
1367
  const data = objectValue$1(value);
1368
- const messageId = numberValue$20(data.id ?? data.msg_id ?? data.msgId);
1369
- const sessionId = numberValue$20(data.session_id ?? data.sessionId);
1370
- const roleCode = numberValue$20(data.role ?? data.sender_role ?? data.senderRole);
1368
+ const messageId = numberValue$21(data.id ?? data.msg_id ?? data.msgId);
1369
+ const sessionId = numberValue$21(data.session_id ?? data.sessionId);
1370
+ const roleCode = numberValue$21(data.role ?? data.sender_role ?? data.senderRole);
1371
1371
  if (!Number.isSafeInteger(messageId) || messageId <= 0 || !Number.isSafeInteger(sessionId) || sessionId <= 0 || roleCode !== 2 && roleCode !== 3) return void 0;
1372
1372
  const extra = parsedJsonRecord(data.extra ?? data.metadata ?? data.meta);
1373
- const createdAt = numberValue$20(data.created_at ?? data.create_at ?? data.createdAt ?? data.createAt);
1373
+ const createdAt = numberValue$21(data.created_at ?? data.create_at ?? data.createdAt ?? data.createAt);
1374
1374
  const runUid = optionalString$3(extra.agent_run_uid);
1375
1375
  const runStatus = optionalString$3(extra.agent_run_status);
1376
1376
  const retryable = optionalBooleanValue$1(extra.agent_run_retryable);
@@ -1383,7 +1383,7 @@ function arkoHistoryItemFromData(value) {
1383
1383
  text: stringValue$1(data.content ?? data.text_content ?? data.textContent),
1384
1384
  reasoning: stringValue$1(data.reason_content ?? data.reason_text ?? data.reasonText),
1385
1385
  createdAtMillis: createdAt > 0 && createdAt < 1e11 ? createdAt * 1e3 : createdAt,
1386
- status: numberValue$20(data.status ?? data.msg_status),
1386
+ status: numberValue$21(data.status ?? data.msg_status),
1387
1387
  ...runUid === void 0 ? {} : { runUid },
1388
1388
  ...runStatus === void 0 ? {} : { runStatus },
1389
1389
  ...retryable === void 0 ? {} : { retryable },
@@ -1402,7 +1402,7 @@ function arkoRunProjectionFromData(data) {
1402
1402
  return {
1403
1403
  runUid,
1404
1404
  status,
1405
- retryable: booleanValue$11(data.agent_run_retryable ?? data.retryable),
1405
+ retryable: booleanValue$12(data.agent_run_retryable ?? data.retryable),
1406
1406
  ...errorCode === "" ? {} : { errorCode },
1407
1407
  ...retryOfRunUid === "" ? {} : { retryOfRunUid },
1408
1408
  ...clientAction === void 0 ? {} : { clientAction }
@@ -1438,7 +1438,7 @@ var ArkoService = class {
1438
1438
  async arkoEnsureSession(signal) {
1439
1439
  const session = await this.runtime.requireSession();
1440
1440
  const latest = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/latest-session", { type: ARKO_AGENT_DIRECT_SESSION_TYPE }, session, signal);
1441
- const latestSessionId = numberValue$20(latest.id);
1441
+ const latestSessionId = numberValue$21(latest.id);
1442
1442
  if (Number.isSafeInteger(latestSessionId) && latestSessionId > 0) return {
1443
1443
  sessionId: latestSessionId,
1444
1444
  created: false,
@@ -1448,7 +1448,7 @@ var ArkoService = class {
1448
1448
  }
1449
1449
  async arkoCreateSession(signal) {
1450
1450
  const session = await this.runtime.requireSession();
1451
- const createdSessionId = numberValue$20((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-session", {
1451
+ const createdSessionId = numberValue$21((await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-session", {
1452
1452
  name: ARKO_DEFAULT_SESSION_NAME,
1453
1453
  type: ARKO_AGENT_DIRECT_SESSION_TYPE
1454
1454
  }, session, signal)).session_id);
@@ -1516,9 +1516,9 @@ var ArkoService = class {
1516
1516
  };
1517
1517
  if (modelRouteKey !== "") body.model_route_key = modelRouteKey;
1518
1518
  const accepted = await this.runtime.authenticatedIntelligentPost("/api/v1/qa/new-msg-v2", body, session, options.signal);
1519
- const userMsgId = numberValue$20(accepted.user_msg_id);
1520
- const assistantMsgId = numberValue$20(accepted.assistant_msg_id);
1521
- const acceptedSessionId = numberValue$20(accepted.session_id) || sessionId;
1519
+ const userMsgId = numberValue$21(accepted.user_msg_id);
1520
+ const assistantMsgId = numberValue$21(accepted.assistant_msg_id);
1521
+ const acceptedSessionId = numberValue$21(accepted.session_id) || sessionId;
1522
1522
  if (!Number.isSafeInteger(acceptedSessionId) || acceptedSessionId <= 0 || !Number.isSafeInteger(userMsgId) || userMsgId <= 0 || !Number.isSafeInteger(assistantMsgId) || assistantMsgId <= 0) throw new ArkmePluginError("arko-ask-contract-invalid", "Arko 消息响应不完整", true, 502);
1523
1523
  const runUid = stringValue$1(accepted.run_uid).trim();
1524
1524
  if (runUid === "") throw new ArkmePluginError("arko-ask-contract-invalid", "Arko 消息响应缺少运行标识", true, 502);
@@ -1554,8 +1554,8 @@ var ArkoService = class {
1554
1554
  }, session, signal);
1555
1555
  const status = stringValue$1(data.status).trim();
1556
1556
  const normalizedRunUid = stringValue$1(data.run_uid).trim() || runUid.trim();
1557
- const sequence = numberValue$20(data.sequence);
1558
- const surfaceAssistantMsgId = numberValue$20(data.surface_assistant_msg_id);
1557
+ const sequence = numberValue$21(data.sequence);
1558
+ const surfaceAssistantMsgId = numberValue$21(data.surface_assistant_msg_id);
1559
1559
  const clientAction = optionalRecord(data.client_action);
1560
1560
  if (normalizedRunUid !== runUid.trim() || !ARKO_RUN_STATUSES.has(status) || !Number.isSafeInteger(sequence) || sequence < 0 || !Number.isSafeInteger(surfaceAssistantMsgId) || surfaceAssistantMsgId <= 0) throw new ArkmePluginError("arko-run-status-contract-invalid", "Arko 运行状态响应不完整", true, 502);
1561
1561
  return {
@@ -1564,7 +1564,7 @@ var ArkoService = class {
1564
1564
  status,
1565
1565
  sequence,
1566
1566
  surfaceAssistantMsgId,
1567
- retryable: booleanValue$11(data.retryable),
1567
+ retryable: booleanValue$12(data.retryable),
1568
1568
  ...stringValue$1(data.error_code).trim() === "" ? {} : { errorCode: stringValue$1(data.error_code).trim() },
1569
1569
  ...stringValue$1(data.retry_of_run_uid).trim() === "" ? {} : { retryOfRunUid: stringValue$1(data.retry_of_run_uid).trim() },
1570
1570
  ...clientAction === void 0 ? {} : { clientAction }
@@ -1739,12 +1739,12 @@ var ArkoService = class {
1739
1739
  if (tailRun !== void 0) {
1740
1740
  run = tailRun;
1741
1741
  status = tailRun.status;
1742
- } else if (booleanValue$11(tail.error) || stringValue$1(tail.status).trim() === "error") status = "failed";
1742
+ } else if (booleanValue$12(tail.error) || stringValue$1(tail.status).trim() === "error") status = "failed";
1743
1743
  else status = ARKO_COMPLETED_STATUS;
1744
1744
  createdRecordUids = arkoCreatedRecordUids(tail.created_record_uids);
1745
1745
  const nextProfile = optionalRecord(tail.agent_profile);
1746
1746
  if (nextProfile !== void 0) profile = arkoProfileFromData(nextProfile);
1747
- if (booleanValue$11(tail.error) || stringValue$1(tail.status).trim() === "error") errorMessage = stringValue$1(tail.error_message).trim() || "Arko 执行失败";
1747
+ if (booleanValue$12(tail.error) || stringValue$1(tail.status).trim() === "error") errorMessage = stringValue$1(tail.error_message).trim() || "Arko 执行失败";
1748
1748
  }
1749
1749
  const type = stringValue$1(frame.type ?? frame.event).trim();
1750
1750
  const payload = optionalRecord(frame.payload) ?? frame;
@@ -1793,10 +1793,10 @@ const ARKME_ARRANGEMENT_REF_TTL_MILLIS = 900 * 1e3;
1793
1793
  const MAX_ARKME_ARRANGEMENT_REFS = 4096;
1794
1794
  const ARKME_ARRANGEMENT_REMINDER_REF_TTL_MILLIS = 900 * 1e3;
1795
1795
  const MAX_ARKME_ARRANGEMENT_REMINDER_REFS = 4096;
1796
- function numberValue$19(value) {
1796
+ function numberValue$20(value) {
1797
1797
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
1798
1798
  }
1799
- function booleanValue$10(value) {
1799
+ function booleanValue$11(value) {
1800
1800
  return value === true;
1801
1801
  }
1802
1802
  function listValue$19(value) {
@@ -1809,7 +1809,7 @@ function arrangementStatusCode(status) {
1809
1809
  return -1;
1810
1810
  }
1811
1811
  function arrangementStatus(value) {
1812
- const code = Math.trunc(numberValue$19(value));
1812
+ const code = Math.trunc(numberValue$20(value));
1813
1813
  if (code === 1) return "identified";
1814
1814
  if (code === 2) return "following";
1815
1815
  if (code === 3) return "completed";
@@ -1860,7 +1860,7 @@ var ArrangementService = class {
1860
1860
  }, session, options.signal, { lane: "interactive-read" });
1861
1861
  const rawItems = listValue$19(data.list);
1862
1862
  const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementItem(raw, session.userId)));
1863
- const total = Math.max(0, Math.trunc(numberValue$19(data.total)));
1863
+ const total = Math.max(0, Math.trunc(numberValue$20(data.total)));
1864
1864
  const nextOffset = offset + rawItems.length;
1865
1865
  const hasMore = rawItems.length > 0 && nextOffset < total;
1866
1866
  return {
@@ -1886,7 +1886,7 @@ var ArrangementService = class {
1886
1886
  }, session, options.signal, { lane: "interactive-read" });
1887
1887
  const rawItems = listValue$19(data.list);
1888
1888
  const items = await Promise.all(rawItems.map(async (raw) => await this.arrangementReminderEvent(raw, session.userId)));
1889
- const total = Math.max(0, Math.trunc(numberValue$19(data.total)));
1889
+ const total = Math.max(0, Math.trunc(numberValue$20(data.total)));
1890
1890
  const nextOffset = offset + rawItems.length;
1891
1891
  const hasMore = rawItems.length > 0 && nextOffset < total;
1892
1892
  return {
@@ -1909,7 +1909,7 @@ var ArrangementService = class {
1909
1909
  project(data.next_reminder)
1910
1910
  ]);
1911
1911
  return {
1912
- unreadCount: Math.max(0, Math.trunc(numberValue$19(data.unread_count))),
1912
+ unreadCount: Math.max(0, Math.trunc(numberValue$20(data.unread_count))),
1913
1913
  ...latestUnread === void 0 ? {} : { latestUnread },
1914
1914
  ...latestEvent === void 0 ? {} : { latestEvent },
1915
1915
  ...nextReminder === void 0 ? {} : { nextReminder }
@@ -2013,7 +2013,7 @@ var ArrangementService = class {
2013
2013
  }, session, signal);
2014
2014
  return {
2015
2015
  outcome: "confirmed",
2016
- updatedCount: Math.max(0, Math.trunc(numberValue$19(data.updated_count)))
2016
+ updatedCount: Math.max(0, Math.trunc(numberValue$20(data.updated_count)))
2017
2017
  };
2018
2018
  } catch (error) {
2019
2019
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -2031,7 +2031,7 @@ var ArrangementService = class {
2031
2031
  }, session, signal);
2032
2032
  return {
2033
2033
  outcome: "confirmed",
2034
- updatedCount: Math.max(0, Math.trunc(numberValue$19(data.updated_count)))
2034
+ updatedCount: Math.max(0, Math.trunc(numberValue$20(data.updated_count)))
2035
2035
  };
2036
2036
  } catch (error) {
2037
2037
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -2046,7 +2046,7 @@ var ArrangementService = class {
2046
2046
  const data = await this.runtime.authenticatedIntelligentPost("/api/v1/arrangements/reminders/clear", {}, session, signal);
2047
2047
  return {
2048
2048
  outcome: "confirmed",
2049
- updatedCount: Math.max(0, Math.trunc(numberValue$19(data.updated_count)))
2049
+ updatedCount: Math.max(0, Math.trunc(numberValue$20(data.updated_count)))
2050
2050
  };
2051
2051
  } catch (error) {
2052
2052
  if (!isAmbiguousArrangementWriteError(error)) throw error;
@@ -2110,17 +2110,17 @@ var ArrangementService = class {
2110
2110
  const item = objectValue$1(raw);
2111
2111
  const arrangementUid = stringValue$1(item.uid).trim();
2112
2112
  if (arrangementUid === "") throw new ArkmePluginError("arrangement-contract-invalid", "安排响应缺少业务身份", true, 502);
2113
- const dueAtMillis = Math.trunc(numberValue$19(item.due_at));
2114
- const remindAtMillis = Math.trunc(numberValue$19(item.remind_at));
2113
+ const dueAtMillis = Math.trunc(numberValue$20(item.due_at));
2114
+ const remindAtMillis = Math.trunc(numberValue$20(item.remind_at));
2115
2115
  return {
2116
2116
  arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
2117
2117
  title: stringValue$1(item.title),
2118
2118
  description: stringValue$1(item.description),
2119
2119
  status: arrangementStatus(item.status),
2120
- reminderEnabled: booleanValue$10(item.reminder_enabled),
2120
+ reminderEnabled: booleanValue$11(item.reminder_enabled),
2121
2121
  reminderState: stringValue$1(item.reminder_state),
2122
- createdAtMillis: Math.trunc(numberValue$19(item.create_at)),
2123
- updatedAtMillis: Math.trunc(numberValue$19(item.update_at)),
2122
+ createdAtMillis: Math.trunc(numberValue$20(item.create_at)),
2123
+ updatedAtMillis: Math.trunc(numberValue$20(item.update_at)),
2124
2124
  ...dueAtMillis > 0 ? { dueAtMillis } : {},
2125
2125
  ...remindAtMillis > 0 ? { remindAtMillis } : {}
2126
2126
  };
@@ -2141,20 +2141,20 @@ var ArrangementService = class {
2141
2141
  const eventUid = stringValue$1(item.uid).trim();
2142
2142
  const arrangementUid = stringValue$1(item.arrangement_uid).trim();
2143
2143
  if (eventUid === "" || arrangementUid === "") throw new ArkmePluginError("arrangement-reminder-contract-invalid", "安排提醒响应缺少有效身份", true, 502);
2144
- const dueAtMillis = Math.trunc(numberValue$19(item.due_at));
2145
- const remindAtMillis = Math.trunc(numberValue$19(item.remind_at));
2146
- const readAtMillis = Math.trunc(numberValue$19(item.read_at));
2144
+ const dueAtMillis = Math.trunc(numberValue$20(item.due_at));
2145
+ const remindAtMillis = Math.trunc(numberValue$20(item.remind_at));
2146
+ const readAtMillis = Math.trunc(numberValue$20(item.read_at));
2147
2147
  return {
2148
2148
  eventRef: await this.arrangementReminderRef(viewerUserId, eventUid),
2149
2149
  arrangementRef: await this.arrangementRef(viewerUserId, arrangementUid),
2150
2150
  title: stringValue$1(item.title),
2151
2151
  description: stringValue$1(item.description),
2152
2152
  eventKind: stringValue$1(item.event_kind),
2153
- eventAtMillis: Math.trunc(numberValue$19(item.event_at)),
2153
+ eventAtMillis: Math.trunc(numberValue$20(item.event_at)),
2154
2154
  read: readAtMillis > 0,
2155
2155
  reminderState: stringValue$1(item.reminder_state),
2156
- createdAtMillis: Math.trunc(numberValue$19(item.create_at)),
2157
- updatedAtMillis: Math.trunc(numberValue$19(item.update_at)),
2156
+ createdAtMillis: Math.trunc(numberValue$20(item.create_at)),
2157
+ updatedAtMillis: Math.trunc(numberValue$20(item.update_at)),
2158
2158
  ...dueAtMillis > 0 ? { dueAtMillis } : {},
2159
2159
  ...remindAtMillis > 0 ? { remindAtMillis } : {},
2160
2160
  ...readAtMillis > 0 ? { readAtMillis } : {}
@@ -2211,7 +2211,7 @@ var ArrangementService = class {
2211
2211
  const ARKME_PHONE_BIND_SUCCESS = 1;
2212
2212
  const ARKME_PHONE_BIND_REPEAT = 2;
2213
2213
  const ARKME_PHONE_BIND_CODE_ERR = 3;
2214
- function numberValue$18(value) {
2214
+ function numberValue$19(value) {
2215
2215
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2216
2216
  }
2217
2217
  var AuthService = class {
@@ -2276,7 +2276,7 @@ var AuthService = class {
2276
2276
  const data = await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/wechat-login-qrcode", {}, void 0, [200]);
2277
2277
  const sceneStr = stringValue$1(data.scene_str).trim();
2278
2278
  const qrContent = stringValue$1(data.url).trim();
2279
- const expireSeconds = Math.max(30, numberValue$18(data.expire_seconds) || 300);
2279
+ const expireSeconds = Math.max(30, numberValue$19(data.expire_seconds) || 300);
2280
2280
  if (qrContent === "" && sceneStr !== "") throw new ArkmePluginError("wechat-qr-unavailable", "测试环境当前未返回可用微信二维码,请使用手机号登录", true, 503);
2281
2281
  if (sceneStr === "" || qrContent === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录二维码响应不完整", true, 502);
2282
2282
  const attemptId = crypto.randomUUID();
@@ -2312,7 +2312,7 @@ var AuthService = class {
2312
2312
  ref: 0,
2313
2313
  keep_cancel: true
2314
2314
  }, void 0, [200]);
2315
- const userId = numberValue$18(data.user_id);
2315
+ const userId = numberValue$19(data.user_id);
2316
2316
  if (userId <= 0) return {
2317
2317
  status: "pending",
2318
2318
  environment: this.runtime.config.environment,
@@ -2356,7 +2356,7 @@ var AuthService = class {
2356
2356
  const normalizedCaptcha = this.normalizedCaptcha(captcha);
2357
2357
  const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
2358
2358
  if (session !== void 0) {
2359
- if (numberValue$18((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/bind-phone-send-code", {
2359
+ if (numberValue$19((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/bind-phone-send-code", {
2360
2360
  phone: normalizedPhone,
2361
2361
  pre: "86",
2362
2362
  is_test: this.runtime.config.environment === "test",
@@ -2378,7 +2378,7 @@ var AuthService = class {
2378
2378
  if (!/^[0-9]{6}$/.test(normalizedCode)) throw new ArkmePluginError("phone-code-invalid", "请输入有效的短信验证码", false);
2379
2379
  const session = await this.runtime.sessionStore.read() ?? await this.runtime.readPendingBindingSession();
2380
2380
  if (session !== void 0) {
2381
- const result = numberValue$18((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/verify-bind-phone", {
2381
+ const result = numberValue$19((await this.runtime.post(this.runtime.config.authBaseUrl, "/api/v1/auth/verify-bind-phone", {
2382
2382
  phone: normalizedPhone,
2383
2383
  pre: "86",
2384
2384
  code: normalizedCode,
@@ -2399,7 +2399,7 @@ var AuthService = class {
2399
2399
  keep_cancel: true
2400
2400
  }, void 0, [200]);
2401
2401
  if (data.ok === false) throw new ArkmePluginError("phone-code-rejected", "手机号或验证码错误", false, 401);
2402
- const userId = numberValue$18(data.user_id);
2402
+ const userId = numberValue$19(data.user_id);
2403
2403
  const accessToken = stringValue$1(data.access_token);
2404
2404
  const refreshToken = stringValue$1(data.refresh_token);
2405
2405
  if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme手机号登录响应不完整", false, 502);
@@ -2454,6 +2454,13 @@ var AuthService = class {
2454
2454
  }
2455
2455
  };
2456
2456
  //#endregion
2457
+ //#region src/bot-avatar-ref.ts
2458
+ const ARKME_BOT_AVATAR_REF_PATTERN = /^file_asset:\/\/[A-Za-z0-9_-]{8,128}$/;
2459
+ /** Bot avatars cross process boundaries only as account-scoped file asset references. */
2460
+ function isArkmeBotAvatarRef(value) {
2461
+ return ARKME_BOT_AVATAR_REF_PATTERN.test(value.trim());
2462
+ }
2463
+ //#endregion
2457
2464
  //#region src/secret-value.ts
2458
2465
  var SecretValue = class {
2459
2466
  #value;
@@ -2466,10 +2473,10 @@ var SecretValue = class {
2466
2473
  };
2467
2474
  //#endregion
2468
2475
  //#region src/services/bot-service.ts
2469
- function numberValue$17(value) {
2476
+ function numberValue$18(value) {
2470
2477
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2471
2478
  }
2472
- function booleanValue$9(value) {
2479
+ function booleanValue$10(value) {
2473
2480
  return value === true;
2474
2481
  }
2475
2482
  function listValue$18(value) {
@@ -2528,6 +2535,7 @@ var BotService = class {
2528
2535
  const avatar = input.avatar?.trim() ?? "";
2529
2536
  if (name === "") throw new ArkmePluginError("bot-name-invalid", "Bot 名称不能为空", false);
2530
2537
  if (provider !== "openclaw" && provider !== "webhook") throw new ArkmePluginError("bot-provider-unsupported", "Bot Provider 不受支持", false, 400);
2538
+ if (avatar !== "" && !isArkmeBotAvatarRef(avatar)) throw new ArkmePluginError("bot-avatar-invalid", "Bot 头像引用无效", false, 400);
2531
2539
  const session = await this.runtime.requireSession();
2532
2540
  let data;
2533
2541
  try {
@@ -2552,6 +2560,10 @@ var BotService = class {
2552
2560
  throw new ArkmePluginError("bot-create-outcome-unknown", "Bot 可能已创建,但响应不完整;请刷新 Bot 列表确认", false, 409, { cause: error });
2553
2561
  }
2554
2562
  }
2563
+ /** Browser-safe result: the one-time credential never leaves the Host owner. */
2564
+ async createBotSummary(input, options = {}) {
2565
+ return (await this.createBot(input, options)).bot;
2566
+ }
2555
2567
  async revealBotSecret(botRef, options = {}) {
2556
2568
  const session = await this.runtime.requireSession();
2557
2569
  const reference = await this.openBotRef(botRef, session.userId);
@@ -2606,13 +2618,13 @@ var BotService = class {
2606
2618
  const { directChatAvailable: _directChatAvailable, ...summary } = await this.botSummaryFromData(raw, session.userId);
2607
2619
  items.push({
2608
2620
  ...summary,
2609
- installed: booleanValue$9(raw.installed)
2621
+ installed: booleanValue$10(raw.installed)
2610
2622
  });
2611
2623
  }
2612
2624
  return {
2613
2625
  groupSourceRef,
2614
2626
  displayName: stringValue$1(data.subject_title).trim() || group.displayName,
2615
- canAddBots: booleanValue$9(data.can_current_user_add_bots),
2627
+ canAddBots: booleanValue$10(data.can_current_user_add_bots),
2616
2628
  items
2617
2629
  };
2618
2630
  }
@@ -2687,7 +2699,7 @@ var BotService = class {
2687
2699
  }
2688
2700
  const reference = {
2689
2701
  version: 1,
2690
- userId: numberValue$17(raw.userId),
2702
+ userId: numberValue$18(raw.userId),
2691
2703
  botId: stringValue$1(raw.botId).trim()
2692
2704
  };
2693
2705
  if (raw.version !== 1 || reference.userId !== expectedUserId || reference.botId === "") throw new ArkmePluginError("bot-ref-invalid", "Bot 引用与当前账号不匹配", false, 403);
@@ -2698,10 +2710,10 @@ var BotService = class {
2698
2710
  //#region src/services/calendar-service.ts
2699
2711
  const MAX_CALENDAR_RANGE_DAYS = 62;
2700
2712
  const MAX_DAY_RECORD_LIMIT = 50;
2701
- function numberValue$16(value) {
2713
+ function numberValue$17(value) {
2702
2714
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2703
2715
  }
2704
- function booleanValue$8(value) {
2716
+ function booleanValue$9(value) {
2705
2717
  if (typeof value === "boolean") return value;
2706
2718
  if (typeof value === "number") return value !== 0;
2707
2719
  }
@@ -2738,7 +2750,7 @@ function boundedLimit(value) {
2738
2750
  return limit;
2739
2751
  }
2740
2752
  function contentAccessState(value) {
2741
- const code = Math.trunc(numberValue$16(value));
2753
+ const code = Math.trunc(numberValue$17(value));
2742
2754
  if (code === 1) return "available";
2743
2755
  if (code === 2) return "protected";
2744
2756
  return "unknown";
@@ -2748,7 +2760,7 @@ function sourceKind(raw) {
2748
2760
  const chat = objectValue$1(raw.chat_core);
2749
2761
  if (stringValue$1(topic.topic_uid).trim() !== "") return "topic";
2750
2762
  if (stringValue$1(chat.chat_session_uid).trim() !== "") return "chat";
2751
- if (booleanValue$8(raw.is_uncategorized) === true) return "self";
2763
+ if (booleanValue$9(raw.is_uncategorized) === true) return "self";
2752
2764
  return "unknown";
2753
2765
  }
2754
2766
  var CalendarService = class {
@@ -2807,7 +2819,7 @@ var CalendarService = class {
2807
2819
  cacheMs: 1e4,
2808
2820
  failureCooldownMs: 2e3
2809
2821
  });
2810
- const nextSendAt = Math.trunc(numberValue$16(data.next_cursor_send_at));
2822
+ const nextSendAt = Math.trunc(numberValue$17(data.next_cursor_send_at));
2811
2823
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
2812
2824
  return {
2813
2825
  scope: "self",
@@ -2826,20 +2838,20 @@ var CalendarService = class {
2826
2838
  const item = objectValue$1(raw);
2827
2839
  const bucketDate = stringValue$1(item.bucket_date).trim();
2828
2840
  if (bucketDate === "") return void 0;
2829
- const count = Math.max(0, Math.trunc(numberValue$16(item.count)));
2841
+ const count = Math.max(0, Math.trunc(numberValue$17(item.count)));
2830
2842
  return {
2831
2843
  bucketDate,
2832
2844
  count,
2833
- protectedCount: Math.max(0, Math.trunc(numberValue$16(item.protected_count))),
2845
+ protectedCount: Math.max(0, Math.trunc(numberValue$17(item.protected_count))),
2834
2846
  hasRecords: count > 0,
2835
- ...numberValue$16(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$16(item.first_send_at)) } : {}
2847
+ ...numberValue$17(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$17(item.first_send_at)) } : {}
2836
2848
  };
2837
2849
  }
2838
2850
  dayRecord(raw) {
2839
2851
  const item = objectValue$1(raw);
2840
2852
  const core = objectValue$1(item.record_core);
2841
2853
  const recordUid = stringValue$1(item.record_uid ?? core.record_uid).trim();
2842
- const sendAtMillis = Math.trunc(numberValue$16(item.send_at ?? core.send_at));
2854
+ const sendAtMillis = Math.trunc(numberValue$17(item.send_at ?? core.send_at));
2843
2855
  if (recordUid === "" || sendAtMillis <= 0) return void 0;
2844
2856
  const accessState = contentAccessState(core.content_access_state);
2845
2857
  const available = accessState === "available";
@@ -2847,9 +2859,9 @@ var CalendarService = class {
2847
2859
  const textContent = available ? clippedText(core.text_content, 4e3) : "";
2848
2860
  const preview = title || clippedText(textContent, 160) || (available ? "无文字内容" : "受保护内容");
2849
2861
  const topic = objectValue$1(item.topic_core);
2850
- const isUncategorized = booleanValue$8(item.is_uncategorized);
2851
- const hasManualEdit = booleanValue$8(core.has_manual_edit);
2852
- const hasPolish = booleanValue$8(core.has_polish);
2862
+ const isUncategorized = booleanValue$9(item.is_uncategorized);
2863
+ const hasManualEdit = booleanValue$9(core.has_manual_edit);
2864
+ const hasPolish = booleanValue$9(core.has_polish);
2853
2865
  return {
2854
2866
  recordUid,
2855
2867
  sendAtMillis,
@@ -2859,9 +2871,9 @@ var CalendarService = class {
2859
2871
  preview,
2860
2872
  ...stringValue$1(topic.title).trim() === "" ? {} : { topicTitle: stringValue$1(topic.title).trim() },
2861
2873
  sourceKind: sourceKind(item),
2862
- creationSource: Math.trunc(numberValue$16(core.creation_source)),
2863
- templateKind: Math.trunc(numberValue$16(core.template_kind)),
2864
- displayKind: Math.trunc(numberValue$16(core.display_kind)),
2874
+ creationSource: Math.trunc(numberValue$17(core.creation_source)),
2875
+ templateKind: Math.trunc(numberValue$17(core.template_kind)),
2876
+ displayKind: Math.trunc(numberValue$17(core.display_kind)),
2865
2877
  protected: accessState === "protected",
2866
2878
  ...isUncategorized === void 0 ? {} : { isUncategorized },
2867
2879
  ...hasManualEdit === void 0 ? {} : { hasManualEdit },
@@ -3556,10 +3568,10 @@ var ChatRealtimeService = class {
3556
3568
  };
3557
3569
  //#endregion
3558
3570
  //#region src/services/chat-service.ts
3559
- function numberValue$15(value) {
3571
+ function numberValue$16(value) {
3560
3572
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
3561
3573
  }
3562
- function booleanValue$7(value) {
3574
+ function booleanValue$8(value) {
3563
3575
  return value === true;
3564
3576
  }
3565
3577
  function listValue$15(value) {
@@ -3658,15 +3670,15 @@ var ChatService = class {
3658
3670
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
3659
3671
  if (uid === "") throw new ArkmePluginError("private-chat-contract-invalid", "私聊会话响应不完整", true, 502);
3660
3672
  const unread = objectValue$1(data.unread_snapshot);
3661
- const latestSequence = numberValue$15(unread.session_last_seq ?? chatSession.last_seq);
3673
+ const latestSequence = numberValue$16(unread.session_last_seq ?? chatSession.last_seq);
3662
3674
  const source = {
3663
3675
  sourceRef: await this.source.sealSourceRef(session.userId, "private_chat", uid, displayName),
3664
3676
  sourceKey: await this.source.chatDirectorySourceKey(session.userId, uid),
3665
3677
  kind: "private_chat",
3666
3678
  displayName,
3667
3679
  ...profile?.avatarUrl === void 0 ? {} : { avatarRef: await this.profile.sealProfileImageRef(session.userId, peerUserId) },
3668
- activeAtMillis: numberValue$15(chatSession.last_active_at) || Date.now(),
3669
- unreadCount: numberValue$15(unread.unread_count),
3680
+ activeAtMillis: numberValue$16(chatSession.last_active_at) || Date.now(),
3681
+ unreadCount: numberValue$16(unread.unread_count),
3670
3682
  ...latestSequence > 0 ? { latestSequence } : {}
3671
3683
  };
3672
3684
  this.source.setChatSourceByKey(`${String(session.userId)}:${uid}`, source);
@@ -3693,7 +3705,7 @@ var ChatService = class {
3693
3705
  mediaUnavailable: media.unavailableRecordUids.has(recordUid)
3694
3706
  });
3695
3707
  }).filter((item) => item.itemUid !== "");
3696
- const nextSendAt = numberValue$15(data.next_cursor_send_at);
3708
+ const nextSendAt = numberValue$16(data.next_cursor_send_at);
3697
3709
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
3698
3710
  return {
3699
3711
  source: await this.source.sourceItem(source),
@@ -3737,7 +3749,7 @@ var ChatService = class {
3737
3749
  mediaUnavailable: media.unavailableRecordUids.has(recordUid)
3738
3750
  });
3739
3751
  });
3740
- const nextSendAt = numberValue$15(data.next_cursor_send_at);
3752
+ const nextSendAt = numberValue$16(data.next_cursor_send_at);
3741
3753
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
3742
3754
  return {
3743
3755
  source: await this.source.sourceItem(source),
@@ -3767,14 +3779,14 @@ var ChatService = class {
3767
3779
  const relation = objectValue$1(item.relation);
3768
3780
  const record = objectValue$1(item.record);
3769
3781
  const payload = objectValue$1(record.payload);
3770
- const recordStatus = numberValue$15(record.status);
3782
+ const recordStatus = numberValue$16(record.status);
3771
3783
  if (recordStatus !== 1) continue;
3772
3784
  const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
3773
3785
  if (uid === "") continue;
3774
3786
  const relationUid = stringValue$1(relation.rel_uid).trim();
3775
- const senderUserId = numberValue$15(relation.sender_user_id);
3787
+ const senderUserId = numberValue$16(relation.sender_user_id);
3776
3788
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
3777
- const sendAtMillis = numberValue$15(relation.attach_at ?? payload.send_at);
3789
+ const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
3778
3790
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
3779
3791
  const rawAgentSource = timelineAgentSource(relation, record, payload);
3780
3792
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
@@ -3788,16 +3800,16 @@ var ChatService = class {
3788
3800
  title: stringValue$1(payload.title),
3789
3801
  textContent: stringValue$1(payload.text_content),
3790
3802
  status: recordStatus,
3791
- sequence: numberValue$15(relation.seq),
3792
- ...numberValue$15(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$15(record.version ?? payload.version) } : {},
3803
+ sequence: numberValue$16(relation.seq),
3804
+ ...numberValue$16(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$16(record.version ?? payload.version) } : {},
3793
3805
  ...aiPolish === void 0 ? {} : { aiPolish },
3794
3806
  ...forwardRecords === void 0 ? {} : { forwardRecords },
3795
- templateKind: numberValue$15(payload.template_kind),
3796
- displayKind: numberValue$15(payload.display_kind),
3797
- version: numberValue$15(payload.version ?? record.version),
3798
- updateAtMillis: numberValue$15(payload.update_at ?? record.update_at),
3799
- recordDurationMillis: numberValue$15(payload.record_duration_millis),
3800
- editDurationMillis: numberValue$15(payload.edit_duration_millis),
3807
+ templateKind: numberValue$16(payload.template_kind),
3808
+ displayKind: numberValue$16(payload.display_kind),
3809
+ version: numberValue$16(payload.version ?? record.version),
3810
+ updateAtMillis: numberValue$16(payload.update_at ?? record.update_at),
3811
+ recordDurationMillis: numberValue$16(payload.record_duration_millis),
3812
+ editDurationMillis: numberValue$16(payload.edit_duration_millis),
3801
3813
  contentBlocks: this.media.richContentBlocks(item, session.userId)
3802
3814
  }) - 1;
3803
3815
  if (Number.isSafeInteger(senderUserId) && senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
@@ -3809,7 +3821,7 @@ var ChatService = class {
3809
3821
  items[index].avatarRef = await this.profile.sealProfileImageRef(session.userId, senderUserId);
3810
3822
  }
3811
3823
  } catch {}
3812
- const beforeSequence = numberValue$15(data.next_before_seq);
3824
+ const beforeSequence = numberValue$16(data.next_before_seq);
3813
3825
  let aiPolishSettings;
3814
3826
  let aiPolishNotices;
3815
3827
  if (aiPolishDecorations !== void 0) {
@@ -3848,7 +3860,7 @@ var ChatService = class {
3848
3860
  return {
3849
3861
  messageRef,
3850
3862
  reportUid,
3851
- status: numberValue$15(report.status)
3863
+ status: numberValue$16(report.status)
3852
3864
  };
3853
3865
  }
3854
3866
  async sendSourceText(sourceRef, textContent, options = {}) {
@@ -3879,7 +3891,7 @@ var ChatService = class {
3879
3891
  return {
3880
3892
  sourceRef,
3881
3893
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
3882
- status: numberValue$15(result.status),
3894
+ status: numberValue$16(result.status),
3883
3895
  localState: "synced"
3884
3896
  };
3885
3897
  }
@@ -3912,7 +3924,7 @@ var ChatService = class {
3912
3924
  const raw = objectValue$1(value);
3913
3925
  const botId = stringValue$1(raw.bot_id).trim();
3914
3926
  if (!requestedById.has(botId)) continue;
3915
- if (stringValue$1(raw.provider).trim() !== "openclaw" || !booleanValue$7(raw.installed)) throw new ArkmePluginError("bot-mention-not-installed", "所选 Bot 未安装到该群聊", false, 409);
3927
+ if (stringValue$1(raw.provider).trim() !== "openclaw" || !booleanValue$8(raw.installed)) throw new ArkmePluginError("bot-mention-not-installed", "所选 Bot 未安装到该群聊", false, 409);
3916
3928
  const name = stringValue$1(raw.name).trim();
3917
3929
  if (name === "") throw new ArkmePluginError("bot-contract-invalid", "Bot 响应不完整", true, 502);
3918
3930
  const display = `@${name}`;
@@ -3964,8 +3976,8 @@ var ChatService = class {
3964
3976
  return {
3965
3977
  sourceRef,
3966
3978
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
3967
- status: numberValue$15(result.audit_status),
3968
- sequence: numberValue$15(result.seq),
3979
+ status: numberValue$16(result.audit_status),
3980
+ sequence: numberValue$16(result.seq),
3969
3981
  localState: "synced"
3970
3982
  };
3971
3983
  }
@@ -4021,7 +4033,7 @@ var ChatService = class {
4021
4033
  return {
4022
4034
  sourceRef,
4023
4035
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
4024
- status: numberValue$15(result.status),
4036
+ status: numberValue$16(result.status),
4025
4037
  localState: "synced"
4026
4038
  };
4027
4039
  }
@@ -4033,7 +4045,7 @@ var ChatService = class {
4033
4045
  return {
4034
4046
  sourceRef,
4035
4047
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
4036
- status: numberValue$15(result.status),
4048
+ status: numberValue$16(result.status),
4037
4049
  localState: "synced"
4038
4050
  };
4039
4051
  }
@@ -4045,8 +4057,8 @@ var ChatService = class {
4045
4057
  return {
4046
4058
  sourceRef,
4047
4059
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
4048
- status: numberValue$15(result.audit_status ?? result.status),
4049
- sequence: numberValue$15(result.seq),
4060
+ status: numberValue$16(result.audit_status ?? result.status),
4061
+ sequence: numberValue$16(result.seq),
4050
4062
  localState: "synced"
4051
4063
  };
4052
4064
  }
@@ -4096,7 +4108,7 @@ var ChatService = class {
4096
4108
  send_at: sendAtMillis
4097
4109
  }, session, options.signal);
4098
4110
  const chatSessionUid = stringValue$1(result.chat_session_uid).trim();
4099
- const sequence = numberValue$15(result.seq);
4111
+ const sequence = numberValue$16(result.seq);
4100
4112
  const targetKind = stringValue$1(result.target_kind).trim();
4101
4113
  if (chatSessionUid === "" || !Number.isSafeInteger(sequence) || sequence <= 0 || targetKind !== "direct") throw new ArkmePluginError("direct-send-response-invalid", "Chat Agent 发送返回了无效响应", true, 502);
4102
4114
  const responseRecordUid = stringValue$1(result.record_uid).trim() || recordUid;
@@ -4124,10 +4136,10 @@ var ChatService = class {
4124
4136
  reason: "arkme_dsh_open_chat"
4125
4137
  }, session);
4126
4138
  const responseSessionUid = stringValue$1(data.chat_session_uid).trim();
4127
- const effectiveReadSequence = numberValue$15(data.effective_read_seq);
4128
- const readAt = numberValue$15(data.read_at);
4129
- const sessionLastSequence = numberValue$15(data.session_last_seq);
4130
- const unreadCount = numberValue$15(data.unread_count);
4139
+ const effectiveReadSequence = numberValue$16(data.effective_read_seq);
4140
+ const readAt = numberValue$16(data.read_at);
4141
+ const sessionLastSequence = numberValue$16(data.session_last_seq);
4142
+ const unreadCount = numberValue$16(data.unread_count);
4131
4143
  if (responseSessionUid !== source.ownerRef || !Number.isSafeInteger(effectiveReadSequence) || effectiveReadSequence < readSequence || readAt <= 0 || sessionLastSequence < effectiveReadSequence || !Number.isSafeInteger(unreadCount) || unreadCount < 0) throw new ArkmePluginError("source-read-ack-invalid", "聊天已读响应不完整", true, 502);
4132
4144
  const cacheKey = `${String(session.userId)}:${source.ownerRef}`;
4133
4145
  const cached = this.source.cachedChatSourceByKey(cacheKey);
@@ -4162,9 +4174,9 @@ var ChatService = class {
4162
4174
  const payload = objectValue$1(record.payload);
4163
4175
  const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
4164
4176
  if (uid === "") continue;
4165
- const senderUserId = numberValue$15(relation.sender_user_id);
4177
+ const senderUserId = numberValue$16(relation.sender_user_id);
4166
4178
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
4167
- const sendAtMillis = numberValue$15(relation.attach_at ?? payload.send_at);
4179
+ const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
4168
4180
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
4169
4181
  const rawAgentSource = timelineAgentSource(relation, record, payload);
4170
4182
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
@@ -4177,12 +4189,12 @@ var ChatService = class {
4177
4189
  sendAtMillis,
4178
4190
  title: stringValue$1(payload.title),
4179
4191
  textContent: stringValue$1(payload.text_content),
4180
- status: numberValue$15(record.status),
4181
- sequence: numberValue$15(relation.seq),
4182
- ...numberValue$15(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$15(record.version ?? payload.version) } : {},
4192
+ status: numberValue$16(record.status),
4193
+ sequence: numberValue$16(relation.seq),
4194
+ ...numberValue$16(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$16(record.version ?? payload.version) } : {},
4183
4195
  ...aiPolish === void 0 ? {} : { aiPolish },
4184
4196
  ...forwardRecords === void 0 ? {} : { forwardRecords },
4185
- displayKind: numberValue$15(payload.display_kind),
4197
+ displayKind: numberValue$16(payload.display_kind),
4186
4198
  contentBlocks: this.media.richContentBlocks(item, session.userId)
4187
4199
  });
4188
4200
  }
@@ -4198,7 +4210,7 @@ var ChatService = class {
4198
4210
  const sorted = values.map((value, index) => ({
4199
4211
  value,
4200
4212
  index,
4201
- order: numberValue$15(objectValue$1(value).item_order)
4213
+ order: numberValue$16(objectValue$1(value).item_order)
4202
4214
  })).sort((left, right) => (left.order || left.index) - (right.order || right.index));
4203
4215
  for (const entry of sorted) {
4204
4216
  if (projectedItems.length >= 100) return;
@@ -4208,19 +4220,19 @@ var ChatService = class {
4208
4220
  await appendItems(listValue$15(nestedForward.items), depth + 1);
4209
4221
  continue;
4210
4222
  }
4211
- const senderUserId = numberValue$15(item.source_sender_user_id ?? item.sourceSenderUserId ?? item.owner_id ?? item.ownerId);
4223
+ const senderUserId = numberValue$16(item.source_sender_user_id ?? item.sourceSenderUserId ?? item.owner_id ?? item.ownerId);
4212
4224
  const senderName = stringValue$1(item.owner_name ?? item.ownerName ?? item.source_display_name ?? item.sourceDisplayName).trim() || "Arkme用户";
4213
4225
  const textContent = stringValue$1(item.text ?? item.text_preview ?? item.textPreview).trim();
4214
4226
  const title = stringValue$1(item.title).trim();
4215
- const imageCount = Math.max(0, Math.trunc(numberValue$15(item.image_count ?? item.imageCount)));
4216
- const voiceCount = Math.max(0, Math.trunc(numberValue$15(item.voice_count ?? item.voiceCount)));
4217
- const fileCount = Math.max(0, Math.trunc(numberValue$15(item.file_count ?? item.fileCount)));
4227
+ const imageCount = Math.max(0, Math.trunc(numberValue$16(item.image_count ?? item.imageCount)));
4228
+ const voiceCount = Math.max(0, Math.trunc(numberValue$16(item.voice_count ?? item.voiceCount)));
4229
+ const fileCount = Math.max(0, Math.trunc(numberValue$16(item.file_count ?? item.fileCount)));
4218
4230
  const fileName = listValue$15(item.file_names ?? item.fileNames).map((value) => stringValue$1(value).trim()).find((value) => value !== "");
4219
4231
  const contentLabel = imageCount > 0 ? imageCount > 1 ? `[${String(imageCount)}张图片]` : "[图片]" : voiceCount > 0 ? "[语音]" : fileCount > 0 ? fileName === void 0 ? "[文件]" : `[文件] ${fileName}` : stringValue$1(item.availability).trim() !== "" ? "原快记暂不可查看" : void 0;
4220
4232
  projectedItems.push({
4221
4233
  senderName,
4222
4234
  ...senderUserId > 0 ? { avatarRef: await this.profile.sealProfileImageRef(viewerUserId, senderUserId) } : {},
4223
- sendAtMillis: numberValue$15(item.send_at ?? item.sendAt),
4235
+ sendAtMillis: numberValue$16(item.send_at ?? item.sendAt),
4224
4236
  title,
4225
4237
  textContent,
4226
4238
  ...contentLabel === void 0 ? {} : { contentLabel }
@@ -4229,7 +4241,7 @@ var ChatService = class {
4229
4241
  };
4230
4242
  await appendItems(listValue$15(payload.items), 0);
4231
4243
  const summaryLines = listValue$15(payload.summary_lines ?? payload.summaryLines).map((value) => stringValue$1(value).trim()).filter((value) => value !== "");
4232
- const createdAtMillis = numberValue$15(payload.created_at ?? payload.createdAt) || fallbackCreatedAtMillis;
4244
+ const createdAtMillis = numberValue$16(payload.created_at ?? payload.createdAt) || fallbackCreatedAtMillis;
4233
4245
  return {
4234
4246
  title: stringValue$1(payload.title).trim() || "转发快记",
4235
4247
  createdAtMillis,
@@ -4261,7 +4273,7 @@ var ChatService = class {
4261
4273
  }
4262
4274
  const result = {
4263
4275
  version: 1,
4264
- userId: numberValue$15(parsed.userId),
4276
+ userId: numberValue$16(parsed.userId),
4265
4277
  chatSessionUid: stringValue$1(parsed.chatSessionUid).trim(),
4266
4278
  relationUid: stringValue$1(parsed.relationUid).trim()
4267
4279
  };
@@ -4270,6 +4282,167 @@ var ChatService = class {
4270
4282
  }
4271
4283
  };
4272
4284
  //#endregion
4285
+ //#region src/services/contact-service.ts
4286
+ const CONTACT_REF_TTL_MS = 10 * 6e4;
4287
+ const MAX_CONTACT_REFS = 512;
4288
+ const CONTACT_REF_PATTERN = /^arkme-contact-v1\.[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4289
+ function numberValue$15(value) {
4290
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
4291
+ return Number.isFinite(parsed) ? parsed : 0;
4292
+ }
4293
+ function booleanValue$7(value) {
4294
+ return value === true;
4295
+ }
4296
+ function normalizeIdentifier(value) {
4297
+ const trimmed = value.trim().replace(/^@/, "");
4298
+ if (trimmed === "" || trimmed.length > 64) throw new ArkmePluginError("contact-identifier-invalid", "请输入手机号或即我号", false);
4299
+ const compactPhone = trimmed.replace(/[\s()-]/g, "").replace(/^\+86/, "");
4300
+ if (/^\d+$/.test(compactPhone)) {
4301
+ if (!/^1[3-9]\d{9}$/.test(compactPhone)) throw new ArkmePluginError("contact-phone-invalid", "请输入有效的中国大陆手机号", false);
4302
+ return {
4303
+ kind: "phone",
4304
+ identifierType: 1,
4305
+ identifier: compactPhone,
4306
+ phonePre: "86"
4307
+ };
4308
+ }
4309
+ if (!/^[A-Za-z][A-Za-z0-9_-]{1,63}$/.test(trimmed)) throw new ArkmePluginError("contact-arkme-id-invalid", "即我号格式无效", false);
4310
+ return {
4311
+ kind: "arkme_id",
4312
+ identifierType: 2,
4313
+ identifier: trimmed,
4314
+ phonePre: "86"
4315
+ };
4316
+ }
4317
+ var ContactService = class {
4318
+ runtime;
4319
+ source;
4320
+ profile;
4321
+ realtime;
4322
+ candidates = /* @__PURE__ */ new Map();
4323
+ constructor(runtime, source, profile, realtime) {
4324
+ this.runtime = runtime;
4325
+ this.source = source;
4326
+ this.profile = profile;
4327
+ this.realtime = realtime;
4328
+ }
4329
+ dispose() {
4330
+ this.candidates.clear();
4331
+ }
4332
+ async search(identifier, options = {}) {
4333
+ const session = await this.runtime.requireSession();
4334
+ const normalized = normalizeIdentifier(identifier);
4335
+ const candidate = await this.lookup(normalized, session, options.signal);
4336
+ this.prune();
4337
+ this.candidates.set(candidate.result.contactRef, candidate);
4338
+ this.prune();
4339
+ return candidate.result;
4340
+ }
4341
+ async add(contactRef, options = {}) {
4342
+ const session = await this.runtime.requireSession();
4343
+ this.prune();
4344
+ const normalizedContactRef = contactRef.trim();
4345
+ if (!CONTACT_REF_PATTERN.test(normalizedContactRef)) throw new ArkmePluginError("contact-ref-invalid", "联系人搜索引用无效,请重新搜索", false);
4346
+ const cached = this.candidates.get(normalizedContactRef);
4347
+ if (cached === void 0 || cached.userId !== session.userId || cached.expiresAtMillis <= Date.now()) throw new ArkmePluginError("contact-ref-expired", "联系人搜索结果已过期,请重新搜索", false, 410);
4348
+ const candidate = await this.lookup(cached.normalized, session, options.signal, normalizedContactRef);
4349
+ if (candidate.targetUserId !== cached.targetUserId || candidate.result.registered !== cached.result.registered) {
4350
+ this.candidates.delete(normalizedContactRef);
4351
+ throw new ArkmePluginError("contact-candidate-changed", "联系人身份已发生变化,请重新搜索后确认", false, 409);
4352
+ }
4353
+ if (candidate.result.isSelf) throw new ArkmePluginError("contact-self-invalid", "不能添加自己为联系人", false);
4354
+ if (!candidate.result.canAdd) throw new ArkmePluginError("contact-add-unavailable", "该账号当前无法添加", false, 409);
4355
+ const remark = options.remark?.trim() ?? "";
4356
+ if (Array.from(remark).length > 100) throw new ArkmePluginError("contact-remark-invalid", "备注不能超过 100 个字符", false);
4357
+ const requestUid = options.requestUid?.trim() || randomUUID();
4358
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(requestUid)) throw new ArkmePluginError("contact-request-uid-invalid", "联系人添加请求标识无效", false);
4359
+ const chatSessionUid = `chat_session_${requestUid}`;
4360
+ const displayName = candidate.result.displayName;
4361
+ const common = {
4362
+ chat_session_uid: chatSessionUid,
4363
+ create_at: Date.now(),
4364
+ ...remark === "" ? {} : { remark }
4365
+ };
4366
+ const registered = candidate.result.registered && candidate.targetUserId !== void 0;
4367
+ const bundle = registered ? await this.runtime.authenticatedChatPost("/api/v1/chats/contacts/add-and-open-private", {
4368
+ ...common,
4369
+ target_user_id: candidate.targetUserId,
4370
+ target_display_name_snapshot: displayName
4371
+ }, session, options.signal, { key: `contact:add:${requestUid}` }) : await this.runtime.authenticatedChatPost("/api/v1/chats/contacts/add-and-open-pending-phone", {
4372
+ ...common,
4373
+ phone_pre: candidate.normalized.phonePre,
4374
+ phone: candidate.phone ?? candidate.normalized.identifier,
4375
+ display_name_snapshot: displayName
4376
+ }, session, options.signal, { key: `contact:add:${requestUid}` });
4377
+ let source = await this.source.chatSourceFromBundle(bundle, session, void 0, []);
4378
+ if (registered && candidate.targetUserId !== void 0) source = {
4379
+ ...source,
4380
+ avatarRef: await this.profile.sealProfileImageRef(session.userId, candidate.targetUserId)
4381
+ };
4382
+ const returnedUid = stringValue$1(objectValue$1(bundle.session).chat_session_uid).trim();
4383
+ if (returnedUid === "") throw new ArkmePluginError("contact-add-contract-invalid", "联系人添加响应不完整", true, 502);
4384
+ this.source.setChatSource(session.userId, returnedUid, source);
4385
+ this.source.invalidateSourceListCache(session.userId, "root");
4386
+ this.realtime.emitChatClientEvent({
4387
+ type: "sessions-delta",
4388
+ revision: this.realtime.nextChatClientRevision(),
4389
+ updates: [{
4390
+ sourceKey: source.sourceKey ?? await this.source.chatDirectorySourceKey(session.userId, returnedUid),
4391
+ source,
4392
+ timelineItems: []
4393
+ }]
4394
+ });
4395
+ this.candidates.delete(normalizedContactRef);
4396
+ return {
4397
+ state: registered ? "ready" : "pending",
4398
+ source
4399
+ };
4400
+ }
4401
+ async lookup(normalized, session, signal, contactRef = `arkme-contact-v1.${randomUUID()}`) {
4402
+ const data = await this.runtime.authenticatedAuthPost("/api/v1/auth/search-contact-account", {
4403
+ identifier_type: normalized.identifierType,
4404
+ identifier: normalized.identifier,
4405
+ phone_pre: normalized.phonePre
4406
+ }, session, signal, {
4407
+ key: `contact:search:${normalized.identifierType}:${normalized.identifier}`,
4408
+ bypassCache: true
4409
+ });
4410
+ const targetUserId = Math.trunc(numberValue$15(data.user_id));
4411
+ const registered = booleanValue$7(data.is_registered) && targetUserId > 0;
4412
+ const isSelf = booleanValue$7(data.is_self);
4413
+ const canAdd = booleanValue$7(data.can_add) && !isSelf && (normalized.kind === "phone" || registered);
4414
+ const displayName = stringValue$1(data.nick_name).trim() || stringValue$1(data.jotmo_id).trim() || (normalized.kind === "phone" ? `+${normalized.phonePre} ${normalized.identifier}` : normalized.identifier);
4415
+ const result = {
4416
+ contactRef,
4417
+ identifierKind: normalized.kind,
4418
+ displayName,
4419
+ ...stringValue$1(data.jotmo_id).trim() === "" ? {} : { arkmeId: stringValue$1(data.jotmo_id).trim() },
4420
+ ...registered ? { avatarRef: await this.profile.sealProfileImageRef(session.userId, targetUserId) } : {},
4421
+ registered,
4422
+ inviteBySms: booleanValue$7(data.invite_by_sms),
4423
+ canAdd,
4424
+ isSelf
4425
+ };
4426
+ return {
4427
+ userId: session.userId,
4428
+ expiresAtMillis: Date.now() + CONTACT_REF_TTL_MS,
4429
+ normalized,
4430
+ result,
4431
+ ...targetUserId > 0 ? { targetUserId } : {},
4432
+ ...normalized.kind === "phone" ? { phone: stringValue$1(data.phone).trim() || normalized.identifier } : {}
4433
+ };
4434
+ }
4435
+ prune() {
4436
+ const now = Date.now();
4437
+ for (const [ref, candidate] of this.candidates) if (candidate.expiresAtMillis <= now) this.candidates.delete(ref);
4438
+ while (this.candidates.size >= MAX_CONTACT_REFS) {
4439
+ const oldest = this.candidates.keys().next().value;
4440
+ if (oldest === void 0) break;
4441
+ this.candidates.delete(oldest);
4442
+ }
4443
+ }
4444
+ };
4445
+ //#endregion
4273
4446
  //#region src/services/community-service.ts
4274
4447
  function numberValue$14(value) {
4275
4448
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -4485,7 +4658,7 @@ var ExtensionReviewService = class {
4485
4658
  422
4486
4659
  ].includes(error.httpStatus)) {
4487
4660
  await this.runtime.stateStore.removeExtensionReviewOperation(userId, operation.clientMutationId);
4488
- throw new ArkmePluginError("extension-review-registry-rejected", `评论已写入首页,但扩展市场未接受:${error.message}`, false, error.httpStatus, { cause: error });
4661
+ throw new ArkmePluginError("extension-review-registry-rejected", `评论已写入首页,但市集未接受:${error.message}`, false, error.httpStatus, { cause: error });
4489
4662
  }
4490
4663
  await this.runtime.stateStore.markExtensionReviewOperation(userId, operation.clientMutationId, "failed", safeFailureMessage$5(error));
4491
4664
  throw new ArkmePluginError("extension-review-registry-pending", "评论已写入首页,但同步到扩展详情失败;请保留当前内容并重试", true, 503, { cause: error });
@@ -5024,6 +5197,7 @@ var GroupAiPolishService = class {
5024
5197
  };
5025
5198
  //#endregion
5026
5199
  //#region src/services/group-service.ts
5200
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
5027
5201
  function numberValue$12(value) {
5028
5202
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
5029
5203
  }
@@ -5356,6 +5530,27 @@ var GroupService = class {
5356
5530
  if (raw.version !== 1 || result.userId !== expectedUserId || result.groupChatSessionUid !== expectedGroupChatSessionUid || !Number.isSafeInteger(result.targetUserId) || result.targetUserId <= 0 || result.targetUserId === expectedUserId || result.displayName === "") throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用与当前账号不匹配", false, 403);
5357
5531
  return result;
5358
5532
  }
5533
+ async createGroup(title, clientMutationId, options = {}) {
5534
+ const normalizedTitle = title.trim();
5535
+ if (normalizedTitle === "" || Array.from(normalizedTitle).length > 80) throw new ArkmePluginError("group-title-invalid", "群聊名称需为 1-80 个字符", false);
5536
+ if (!UUID.test(clientMutationId)) throw new ArkmePluginError("client-mutation-id-invalid", "群聊操作标识无效", false);
5537
+ const session = await this.runtime.requireSession();
5538
+ let data;
5539
+ try {
5540
+ data = await this.runtime.authenticatedChatPost("/api/v1/chats/create-group", {
5541
+ chat_session_uid: clientMutationId,
5542
+ title: normalizedTitle,
5543
+ member_user_ids: [],
5544
+ create_at: Date.now()
5545
+ }, session, options.signal);
5546
+ } catch (error) {
5547
+ if (error instanceof ArkmePluginError && ["arkme-network-error", "arkme-timeout"].includes(error.code)) throw new ArkmePluginError("group-create-outcome-unknown", "群聊创建结果未知,请刷新会话列表确认;不会自动重试", false, 409, { cause: error });
5548
+ throw error;
5549
+ }
5550
+ const source = await this.source.chatSourceFromBundle(data, session, void 0, []);
5551
+ this.source.setChatSourceByKey(`${String(session.userId)}:${clientMutationId}`, source);
5552
+ return source;
5553
+ }
5359
5554
  async listGroupMembers(sourceRef, options = {}) {
5360
5555
  const session = await this.runtime.requireSession();
5361
5556
  const source = await this.source.openSourceRef(sourceRef, session.userId);
@@ -7202,6 +7397,43 @@ var RecordService = class {
7202
7397
  };
7203
7398
  }
7204
7399
  }
7400
+ async createFileAssetsForConversation(recordUid, textContent, assets) {
7401
+ const session = await this.runtime.requireSession();
7402
+ const normalizedUid = recordUid.trim();
7403
+ const normalizedText = textContent.trim();
7404
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
7405
+ if (assets.length === 0 || assets.length > 20 || normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-file-assets-invalid", "附件内容为空、过长或数量超限", false);
7406
+ for (const asset of assets) if (!/^[A-Za-z0-9._:-]{8,256}$/.test(asset.fileAssetUid) || asset.fileName.trim() === "" || asset.fileName.length > 255 || !Number.isSafeInteger(asset.size) || asset.size <= 0 || ![
7407
+ 1,
7408
+ 2,
7409
+ 3,
7410
+ 4
7411
+ ].includes(asset.fileKind)) throw new ArkmePluginError("record-file-asset-invalid", "附件资产参数无效", false);
7412
+ const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
7413
+ record_uid: normalizedUid,
7414
+ template_kind: 2,
7415
+ display_kind: 0,
7416
+ title: "",
7417
+ text_content: normalizedText,
7418
+ content_payload: {
7419
+ payload_kind: 2,
7420
+ schema_version: 1,
7421
+ text_state: normalizedText === "" ? 3 : 1,
7422
+ media_refs: assets.map((asset, index) => ({
7423
+ file_asset_uid: asset.fileAssetUid,
7424
+ content_file_role: 1,
7425
+ render_role: 1,
7426
+ sort_order: index,
7427
+ file_name: asset.fileName
7428
+ }))
7429
+ },
7430
+ send_at: Date.now()
7431
+ }, session);
7432
+ return {
7433
+ recordUid: stringValue$1(data.record_uid).trim() || normalizedUid,
7434
+ status: numberValue$7(data.status)
7435
+ };
7436
+ }
7205
7437
  async pendingWrites() {
7206
7438
  const session = await this.runtime.requireSession();
7207
7439
  return await this.runtime.stateStore.listPending(session.userId);
@@ -9178,14 +9410,63 @@ var WechatService = class {
9178
9410
  }
9179
9411
  };
9180
9412
  //#endregion
9413
+ //#region src/types.ts
9414
+ const ARKME_PROVIDER_CONTRACT_VERSION = 1;
9415
+ const ARKME_DEFAULT_SHARE_WEBSITE = "https://app.arkme.ai";
9416
+ const ARKME_WORLD_PUBLISH_MAX_IMAGES = 9;
9417
+ const ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES = 20 * 1024 * 1024;
9418
+ //#endregion
9181
9419
  //#region src/services/world-service.ts
9182
9420
  const ARKME_WORLD_IMAGE_REF_TTL_MILLIS = 900 * 1e3;
9183
9421
  const MAX_ARKME_WORLD_IMAGE_REFS = 2048;
9184
9422
  const ARKME_WORLD_RECORD_REF_TTL_MILLIS = 900 * 1e3;
9185
9423
  const MAX_ARKME_WORLD_RECORD_REFS = 4096;
9424
+ const ARKME_VOICEPRINT_PLAY_SCOPE = 2;
9425
+ const ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH = 2048;
9426
+ const ARKME_VOICEPRINT_SOCIAL_CACHE_MILLIS = 300 * 1e3;
9427
+ const ARKME_VOICEPRINT_SOCIAL_STALE_MILLIS = 1440 * 60 * 1e3;
9428
+ const ARKME_VOICEPRINT_SOCIAL_SOURCE_TIMEOUT_MILLIS = 4e3;
9429
+ const WORLD_VOICEPRINT_SOCIAL_RELATIONS = {
9430
+ reciprocal_expectation: {
9431
+ type: "reciprocal_expectation",
9432
+ displayLine: "TA也曾期待过你的声音",
9433
+ reasonCode: "relationship_reciprocal_expectation",
9434
+ reasonLabel: "因为TA也曾期待过我的声音"
9435
+ },
9436
+ call: {
9437
+ type: "call",
9438
+ displayLine: "你们曾经通过话",
9439
+ reasonCode: "relationship_call",
9440
+ reasonLabel: "因为我们曾经通过话"
9441
+ },
9442
+ world_interaction: {
9443
+ type: "world_interaction",
9444
+ displayLine: "你们曾在世界回应过彼此",
9445
+ reasonCode: "relationship_world",
9446
+ reasonLabel: "因为我们在世界里回应过彼此"
9447
+ },
9448
+ group_interaction: {
9449
+ type: "group_interaction",
9450
+ displayLine: "你们曾在同一个群里互动过",
9451
+ reasonCode: "relationship_group",
9452
+ reasonLabel: "因为我们在群里有过互动"
9453
+ },
9454
+ private_chat: {
9455
+ type: "private_chat",
9456
+ displayLine: "你们曾经聊过",
9457
+ reasonCode: "relationship_chat",
9458
+ reasonLabel: "因为我们以前聊过"
9459
+ }
9460
+ };
9186
9461
  function numberValue(value) {
9187
9462
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
9188
9463
  }
9464
+ function integerValue(value) {
9465
+ if (typeof value === "number" && Number.isFinite(value)) return Math.trunc(value);
9466
+ if (typeof value !== "string" || !/^-?\d+$/.test(value.trim())) return 0;
9467
+ const parsed = Number(value.trim());
9468
+ return Number.isSafeInteger(parsed) ? parsed : 0;
9469
+ }
9189
9470
  function listValue(value) {
9190
9471
  return Array.isArray(value) ? value : [];
9191
9472
  }
@@ -9194,12 +9475,53 @@ function safeFailureMessage(error) {
9194
9475
  if (error instanceof Error && error.message.trim() !== "") return error.message;
9195
9476
  return "未知错误";
9196
9477
  }
9478
+ function optionalTrimmedText(value, limit) {
9479
+ const normalized = value?.replace(/\s+/g, " ").trim();
9480
+ if (normalized === void 0 || normalized === "") return void 0;
9481
+ return normalized.length <= limit ? normalized : `${normalized.slice(0, limit).trimEnd()}...`;
9482
+ }
9483
+ function buildVoiceprintInviteShareUrl(environment, token) {
9484
+ const url = new URL("/app/voiceprint/invite", environment === "prod" ? "https://jiwo.cc" : "https://jotmo-app.senguo.me");
9485
+ url.hash = `t=${encodeURIComponent(token)}`;
9486
+ return url.toString();
9487
+ }
9488
+ function validVoiceprintInviteToken(token) {
9489
+ if (token === "" || token.length > ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH) return false;
9490
+ let byteLength = 0;
9491
+ for (const char of token) {
9492
+ const codePoint = char.codePointAt(0) ?? 0;
9493
+ if (codePoint <= 31 || codePoint === 127) return false;
9494
+ byteLength += codePoint <= 127 ? 1 : codePoint <= 2047 ? 2 : codePoint <= 65535 ? 3 : 4;
9495
+ if (byteLength > ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH) return false;
9496
+ }
9497
+ return true;
9498
+ }
9197
9499
  function worldVisibility(checkStatus) {
9198
9500
  if (checkStatus === 1) return "pending_review";
9199
9501
  if (checkStatus === 4) return "rejected";
9200
9502
  if (checkStatus === 0 || checkStatus === 2 || checkStatus === 3) return "visible";
9201
9503
  return "unknown";
9202
9504
  }
9505
+ function worldPublicationResult(checkStatus) {
9506
+ const visibility = worldVisibility(checkStatus);
9507
+ if (visibility === "rejected") return {
9508
+ recordSaved: true,
9509
+ recordState: "synced",
9510
+ worldPublished: false,
9511
+ visibility,
9512
+ checkStatus,
9513
+ retryable: false,
9514
+ error: "内容未通过审核,请调整后重试"
9515
+ };
9516
+ return {
9517
+ recordSaved: true,
9518
+ recordState: "synced",
9519
+ worldPublished: true,
9520
+ visibility,
9521
+ checkStatus,
9522
+ retryable: false
9523
+ };
9524
+ }
9203
9525
  function worldTags(text) {
9204
9526
  return [...text.matchAll(/#(\S+)/gu)].map((match) => match[1] ?? "").filter((tag) => tag !== "");
9205
9527
  }
@@ -9207,6 +9529,10 @@ function stableWorldInteractionRecordUid(userId, targetRecordUid, clientMutation
9207
9529
  const hex = createHash("sha256").update(`dsh-arkme:world-interaction:${String(userId)}:${targetRecordUid}:${clientMutationId}`).digest("hex");
9208
9530
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
9209
9531
  }
9532
+ function stableWorldPublishRecordUid(userId, clientMutationId) {
9533
+ const hex = createHash("sha256").update(`dsh-arkme:world-publish:${String(userId)}:${clientMutationId}`).digest("hex");
9534
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
9535
+ }
9210
9536
  function allowedSignedImageHost(environment, hostname) {
9211
9537
  return (environment === "prod" ? ["jotmo-userfiles.oss-cn-hangzhou.aliyuncs.com", "userfiles.jotmo.cc"] : ["jotmo-userfiles-test.oss-cn-hangzhou.aliyuncs.com", "jotmo-userfiles.senguo.me"]).includes(hostname.toLowerCase());
9212
9538
  }
@@ -9246,6 +9572,8 @@ var WorldService = class {
9246
9572
  record;
9247
9573
  worldImageRefs = /* @__PURE__ */ new Map();
9248
9574
  worldRecordRefs = /* @__PURE__ */ new Map();
9575
+ voiceprintSocialCache = /* @__PURE__ */ new Map();
9576
+ voiceprintSocialInFlight = /* @__PURE__ */ new Map();
9249
9577
  constructor(runtime, profile, media, record) {
9250
9578
  this.runtime = runtime;
9251
9579
  this.profile = profile;
@@ -9255,6 +9583,8 @@ var WorldService = class {
9255
9583
  dispose() {
9256
9584
  this.worldImageRefs.clear();
9257
9585
  this.worldRecordRefs.clear();
9586
+ this.voiceprintSocialCache.clear();
9587
+ this.voiceprintSocialInFlight.clear();
9258
9588
  }
9259
9589
  async listWorldRecords(options = {}) {
9260
9590
  const limit = Math.min(20, Math.max(1, Math.trunc(options.limit ?? 10)));
@@ -9296,6 +9626,58 @@ var WorldService = class {
9296
9626
  ...hasMore ? { nextOffset } : {}
9297
9627
  };
9298
9628
  }
9629
+ async listMyWorldFeed(options = {}) {
9630
+ const session = await this.runtime.requireSession();
9631
+ const limit = Math.min(20, Math.max(1, Math.trunc(options.limit ?? 20)));
9632
+ const offset = Math.max(0, Math.trunc(options.offset ?? 0));
9633
+ const data = await this.runtime.authenticatedWorldPost("/api/v1/public-record/my-list", {
9634
+ limit,
9635
+ offset
9636
+ }, session, options.signal);
9637
+ const rawItems = listValue(data.list);
9638
+ const resolvedAvatars = await this.resolveWorldAvatarUrls(rawItems, session, options.signal);
9639
+ const items = (await Promise.all(rawItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars)))).filter((item) => item !== void 0);
9640
+ const total = Math.max(0, Math.trunc(numberValue(data.total)));
9641
+ const nextOffset = offset + rawItems.length;
9642
+ const hasMore = rawItems.length > 0 && nextOffset < total;
9643
+ return {
9644
+ items,
9645
+ total,
9646
+ hasMore,
9647
+ ...hasMore ? { nextOffset } : {}
9648
+ };
9649
+ }
9650
+ async listUserWorldFeed(userId, options = {}) {
9651
+ if (!Number.isSafeInteger(userId) || userId <= 0) throw new ArkmePluginError("world-user-id-invalid", "世界用户 ID 无效,请刷新后重试", false, 400);
9652
+ const session = await this.runtime.requireSession();
9653
+ const limit = Math.min(20, Math.max(1, Math.trunc(options.limit ?? 20)));
9654
+ let offset = Math.max(0, Math.trunc(options.offset ?? 0));
9655
+ let total = 0;
9656
+ let items = [];
9657
+ let hasMore = false;
9658
+ let attempts = 0;
9659
+ do {
9660
+ const data = await this.runtime.post(this.runtime.config.worldBaseUrl, "/api/public/v1/public-record/user-list", {
9661
+ user_id: userId,
9662
+ limit,
9663
+ offset
9664
+ }, void 0, [200], options.signal);
9665
+ const rawItems = listValue(data.list);
9666
+ total = Math.max(0, Math.trunc(numberValue(data.total)));
9667
+ const rootItems = rawItems.filter((raw) => stringValue$1(objectValue$1(raw).parent_record_uid).trim() === "");
9668
+ const resolvedAvatars = await this.resolveWorldAvatarUrls(rootItems, session, options.signal);
9669
+ items = (await Promise.all(rootItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars)))).filter((item) => item !== void 0);
9670
+ offset += rawItems.length;
9671
+ hasMore = rawItems.length > 0 && offset < total;
9672
+ attempts += 1;
9673
+ } while (items.length === 0 && hasMore && attempts < 4);
9674
+ return {
9675
+ items,
9676
+ total,
9677
+ hasMore,
9678
+ ...hasMore ? { nextOffset: offset } : {}
9679
+ };
9680
+ }
9299
9681
  async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
9300
9682
  const session = await this.runtime.requireSession();
9301
9683
  const normalizedRefs = [...new Set(recordRefs.map((value) => value.trim()).filter((value) => value !== ""))].slice(0, 20);
@@ -9357,6 +9739,236 @@ var WorldService = class {
9357
9739
  chunkEndRune
9358
9740
  };
9359
9741
  }
9742
+ async worldVoiceprintSocialContext(recordRef, options = {}) {
9743
+ const session = await this.runtime.requireSession();
9744
+ const entry = this.openWorldRecordRef(recordRef, session.userId);
9745
+ const authorUserId = entry.ownerUserId ?? 0;
9746
+ if (!Number.isSafeInteger(authorUserId) || authorUserId <= 0 || authorUserId === session.userId) return { relations: [] };
9747
+ const cacheKey = `${String(session.userId)}:${String(authorUserId)}`;
9748
+ const cached = this.voiceprintSocialCache.get(cacheKey);
9749
+ const age = cached === void 0 ? Number.POSITIVE_INFINITY : Date.now() - cached.savedAtMillis;
9750
+ if (options.forceRefresh !== true && cached !== void 0 && cached.context.relations.length > 0 && age < ARKME_VOICEPRINT_SOCIAL_CACHE_MILLIS) return cached.context;
9751
+ if (options.forceRefresh !== true && cached !== void 0 && cached.context.relations.length > 0 && age < ARKME_VOICEPRINT_SOCIAL_STALE_MILLIS) {
9752
+ this.refreshWorldVoiceprintSocialContext({
9753
+ cacheKey,
9754
+ flightKey: `${cacheKey}:${entry.recordUid}`,
9755
+ viewerUserId: session.userId,
9756
+ authorUserId,
9757
+ recordUid: entry.recordUid,
9758
+ fallback: cached,
9759
+ ...options.signal === void 0 ? {} : { signal: options.signal }
9760
+ });
9761
+ return cached.context;
9762
+ }
9763
+ return await this.refreshWorldVoiceprintSocialContext({
9764
+ cacheKey,
9765
+ flightKey: `${cacheKey}:${entry.recordUid}`,
9766
+ viewerUserId: session.userId,
9767
+ authorUserId,
9768
+ recordUid: entry.recordUid,
9769
+ ...cached === void 0 ? {} : { fallback: cached },
9770
+ ...options.signal === void 0 ? {} : { signal: options.signal }
9771
+ });
9772
+ }
9773
+ async createWorldVoiceprintInviteIntent(recordRef, signal) {
9774
+ const session = await this.runtime.requireSession();
9775
+ const entry = this.openWorldRecordRef(recordRef, session.userId);
9776
+ const peerUserId = entry.ownerUserId ?? 0;
9777
+ if (!Number.isSafeInteger(peerUserId) || peerUserId <= 0) throw new ArkmePluginError("world-voiceprint-invite-target-missing", "无法确认这条世界动态的作者,请刷新后重试", false);
9778
+ if (peerUserId === session.userId) throw new ArkmePluginError("world-voiceprint-invite-self-invalid", "这是你自己的动态,不需要提醒自己开启声纹", false, 409);
9779
+ const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/invites/create", { scope: ARKME_VOICEPRINT_PLAY_SCOPE }, session, signal, {
9780
+ lane: "write",
9781
+ bypassCache: true
9782
+ });
9783
+ const token = stringValue$1(data.invite_token).trim();
9784
+ const expiresAtMillis = Math.trunc(numberValue(data.expires_at));
9785
+ const scope = Math.trunc(numberValue(data.scope));
9786
+ if (!validVoiceprintInviteToken(token) || expiresAtMillis <= 0 || scope !== ARKME_VOICEPRINT_PLAY_SCOPE) throw new ArkmePluginError("world-voiceprint-invite-contract-invalid", "声纹邀请响应不完整,请稍后重试", true, 502);
9787
+ return {
9788
+ peerUserId,
9789
+ peerDisplayName: optionalTrimmedText(entry.authorName, 64) ?? "这位用户",
9790
+ inviteUrl: buildVoiceprintInviteShareUrl(this.runtime.config.environment, token),
9791
+ expiresAtMillis,
9792
+ ...entry.textPreview === void 0 ? {} : { textPreview: entry.textPreview }
9793
+ };
9794
+ }
9795
+ refreshWorldVoiceprintSocialContext(input) {
9796
+ const existing = this.voiceprintSocialInFlight.get(input.flightKey);
9797
+ if (existing !== void 0) return existing;
9798
+ const refresh = this.loadWorldVoiceprintSocialContext(input).then((result) => {
9799
+ const context = this.mergeWorldVoiceprintSocialContexts(input.fallback?.context, result.context);
9800
+ if (context.relations.length > 0 || result.allSourcesSucceeded) this.voiceprintSocialCache.set(input.cacheKey, {
9801
+ context,
9802
+ savedAtMillis: Date.now()
9803
+ });
9804
+ return context;
9805
+ }).catch(() => input.fallback?.context.relations.length ? input.fallback.context : { relations: [] }).finally(() => {
9806
+ this.voiceprintSocialInFlight.delete(input.flightKey);
9807
+ });
9808
+ this.voiceprintSocialInFlight.set(input.flightKey, refresh);
9809
+ return refresh;
9810
+ }
9811
+ async loadWorldVoiceprintSocialContext(input) {
9812
+ const session = await this.runtime.requireSession();
9813
+ const directChat = this.loadWorldVoiceprintDirectChat(input.authorUserId, session, input.signal);
9814
+ const results = await Promise.all([
9815
+ this.guardWorldVoiceprintSocialSource(async () => await this.loadWorldVoiceprintReciprocalExpectation(input.authorUserId, session, input.signal)),
9816
+ this.guardWorldVoiceprintSocialSource(async () => await this.loadWorldVoiceprintInterwovenRelations(input.authorUserId, directChat, session, input.signal)),
9817
+ this.guardWorldVoiceprintSocialSource(async () => await this.loadWorldVoiceprintPrivateChatRelation(input.authorUserId, directChat)),
9818
+ this.guardWorldVoiceprintSocialSource(async () => await this.loadWorldVoiceprintCommentRelation(input.viewerUserId, input.recordUid, session, input.signal))
9819
+ ]);
9820
+ return {
9821
+ context: { relations: this.dedupeWorldVoiceprintRelations(results.flatMap((result) => result.relations)) },
9822
+ allSourcesSucceeded: results.every((result) => result.succeeded)
9823
+ };
9824
+ }
9825
+ async guardWorldVoiceprintSocialSource(loader) {
9826
+ let timeout;
9827
+ try {
9828
+ return await Promise.race([loader(), new Promise((resolve) => {
9829
+ timeout = setTimeout(() => {
9830
+ resolve({
9831
+ succeeded: false,
9832
+ relations: []
9833
+ });
9834
+ }, ARKME_VOICEPRINT_SOCIAL_SOURCE_TIMEOUT_MILLIS);
9835
+ })]);
9836
+ } catch {
9837
+ return {
9838
+ succeeded: false,
9839
+ relations: []
9840
+ };
9841
+ } finally {
9842
+ if (timeout !== void 0) clearTimeout(timeout);
9843
+ }
9844
+ }
9845
+ async loadWorldVoiceprintReciprocalExpectation(authorUserId, session, signal) {
9846
+ return {
9847
+ succeeded: true,
9848
+ relations: listValue(objectValue$1((await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/world-enrollment-interactions", {}, session, signal, {
9849
+ lane: "interactive-read",
9850
+ key: "world-enrollment-interactions",
9851
+ cacheMs: 5e3
9852
+ })).received).requesters).some((raw) => {
9853
+ const requester = objectValue$1(raw);
9854
+ return integerValue(requester.user_id ?? requester.userId) === authorUserId;
9855
+ }) ? [WORLD_VOICEPRINT_SOCIAL_RELATIONS.reciprocal_expectation] : []
9856
+ };
9857
+ }
9858
+ async loadWorldVoiceprintDirectChat(authorUserId, session, signal) {
9859
+ let pageCursor;
9860
+ const visited = /* @__PURE__ */ new Set();
9861
+ for (;;) {
9862
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/list", {
9863
+ limit: 100,
9864
+ session_kind: 1,
9865
+ ...pageCursor === void 0 ? {} : { page_cursor: pageCursor }
9866
+ }, session, signal, {
9867
+ lane: "interactive-read",
9868
+ bypassCache: true
9869
+ });
9870
+ for (const raw of listValue(data.items)) {
9871
+ const bundle = objectValue$1(raw);
9872
+ const chatSession = objectValue$1(bundle.session);
9873
+ const counterpart = objectValue$1(bundle.private_counterpart);
9874
+ const sessionKind = integerValue(chatSession.session_kind);
9875
+ if (sessionKind !== 1 && sessionKind !== 3 || integerValue(counterpart.user_id ?? counterpart.userId) !== authorUserId) continue;
9876
+ const latestPreview = objectValue$1(bundle.latest_preview);
9877
+ const latestRecord = objectValue$1(latestPreview.record);
9878
+ const latestRelation = objectValue$1(latestPreview.relation);
9879
+ const unread = objectValue$1(bundle.unread_snapshot);
9880
+ const sessionUid = stringValue$1(chatSession.chat_session_uid).trim();
9881
+ if (sessionUid === "") continue;
9882
+ const sharedTopicId = [
9883
+ chatSession,
9884
+ objectValue$1(chatSession.extra),
9885
+ objectValue$1(chatSession.policy_snapshot)
9886
+ ].map((source) => integerValue(source.subject_id ?? source.subjectId ?? source.shared_topic_id ?? source.sharedTopicId)).find((value) => value > 0) ?? 0;
9887
+ const hasPrivateMessageEvidence = Object.keys(latestRecord).length > 0 || Object.keys(objectValue$1(latestRecord.payload)).length > 0 || stringValue$1(latestRecord.record_uid).trim() !== "" || stringValue$1(latestRelation.record_uid).trim() !== "" || integerValue(unread.session_last_seq ?? chatSession.last_seq) > 0;
9888
+ return {
9889
+ sessionUid,
9890
+ sharedTopicId: Math.max(0, sharedTopicId),
9891
+ hasPrivateMessageEvidence
9892
+ };
9893
+ }
9894
+ if (data.has_more !== true) return void 0;
9895
+ const nextCursor = objectValue$1(data.next_page_cursor);
9896
+ if (Object.keys(nextCursor).length === 0) throw new Error("voiceprint direct-chat pagination cursor missing");
9897
+ const identity = JSON.stringify(nextCursor);
9898
+ if (visited.has(identity)) throw new Error("voiceprint direct-chat pagination cursor repeated");
9899
+ visited.add(identity);
9900
+ pageCursor = nextCursor;
9901
+ }
9902
+ }
9903
+ async loadWorldVoiceprintInterwovenRelations(authorUserId, directChatFuture, session, signal) {
9904
+ const directChat = await directChatFuture;
9905
+ if (directChat === void 0 || authorUserId <= 0) return {
9906
+ succeeded: true,
9907
+ relations: []
9908
+ };
9909
+ const data = directChat.sharedTopicId > 0 ? await this.runtime.authenticatedWorldPost("/api/v1/interwoven-moments/summary", { subject_id: directChat.sharedTopicId }, session, signal, {
9910
+ lane: "interactive-read",
9911
+ bypassCache: true
9912
+ }) : await this.runtime.authenticatedChatPost("/api/v1/chats/interwoven/summary", {
9913
+ subject_id: 0,
9914
+ chat_session_uid: directChat.sessionUid
9915
+ }, session, signal, {
9916
+ lane: "interactive-read",
9917
+ bypassCache: true
9918
+ });
9919
+ const stats = listValue(objectValue$1(data.recent_card).total_stats);
9920
+ const countFor = (momentType) => {
9921
+ return integerValue(objectValue$1(stats.find((value) => integerValue(objectValue$1(value).moment_type) === momentType)).count);
9922
+ };
9923
+ const relations = [];
9924
+ const callSummary = data.call_summary;
9925
+ if ((callSummary !== null && typeof callSummary === "object" && !Array.isArray(callSummary) ? integerValue(objectValue$1(callSummary).total_call_count) : countFor(3)) > 0) relations.push(WORLD_VOICEPRINT_SOCIAL_RELATIONS.call);
9926
+ if (countFor(2) > 0) relations.push(WORLD_VOICEPRINT_SOCIAL_RELATIONS.world_interaction);
9927
+ const groupFromStatus = listValue(data.source_status).some((raw) => {
9928
+ const status = objectValue$1(raw);
9929
+ return integerValue(status.moment_type) === 1 && integerValue(status.item_count) > 0;
9930
+ });
9931
+ if (countFor(1) > 0 || groupFromStatus) relations.push(WORLD_VOICEPRINT_SOCIAL_RELATIONS.group_interaction);
9932
+ return {
9933
+ succeeded: true,
9934
+ relations
9935
+ };
9936
+ }
9937
+ async loadWorldVoiceprintPrivateChatRelation(authorUserId, directChatFuture) {
9938
+ const directChat = await directChatFuture;
9939
+ return {
9940
+ succeeded: true,
9941
+ relations: directChat !== void 0 && authorUserId > 0 && directChat.hasPrivateMessageEvidence ? [WORLD_VOICEPRINT_SOCIAL_RELATIONS.private_chat] : []
9942
+ };
9943
+ }
9944
+ async loadWorldVoiceprintCommentRelation(viewerUserId, recordUid, session, signal) {
9945
+ if (recordUid.trim() === "") return {
9946
+ succeeded: true,
9947
+ relations: []
9948
+ };
9949
+ return {
9950
+ succeeded: true,
9951
+ relations: listValue((await this.runtime.authenticatedWorldPost("/api/v1/public-record/extend-list", {
9952
+ record_uid: recordUid,
9953
+ limit: 100,
9954
+ offset: 0
9955
+ }, session, signal, {
9956
+ lane: "interactive-read",
9957
+ bypassCache: true
9958
+ })).list).some((raw) => {
9959
+ const item = objectValue$1(raw);
9960
+ return integerValue(item.user_id ?? item.userId) === viewerUserId;
9961
+ }) ? [WORLD_VOICEPRINT_SOCIAL_RELATIONS.world_interaction] : []
9962
+ };
9963
+ }
9964
+ dedupeWorldVoiceprintRelations(relations) {
9965
+ const seen = /* @__PURE__ */ new Set();
9966
+ return relations.filter((relation) => !seen.has(relation.type) && seen.add(relation.type));
9967
+ }
9968
+ mergeWorldVoiceprintSocialContexts(previous, current) {
9969
+ if (previous === void 0 || previous.relations.length === 0) return current;
9970
+ return { relations: this.dedupeWorldVoiceprintRelations([...previous.relations, ...current.relations]) };
9971
+ }
9360
9972
  async listWorldInteractions(recordRef, options = {}) {
9361
9973
  const session = await this.runtime.requireSession();
9362
9974
  const root = this.openWorldRecordRef(recordRef, session.userId);
@@ -9502,18 +10114,88 @@ var WorldService = class {
9502
10114
  avatar: profile.avatarRef,
9503
10115
  template_kind: 1
9504
10116
  }, void 0, signal);
9505
- const checkStatus = Math.trunc(numberValue(published.check_status));
9506
- return {
10117
+ return worldPublicationResult(Math.trunc(numberValue(published.check_status)));
10118
+ } catch (error) {
10119
+ try {
10120
+ if (await this.worldRecordIsPublic(normalizedUid, signal)) return {
10121
+ recordSaved: true,
10122
+ recordState: "synced",
10123
+ worldPublished: true,
10124
+ visibility: "unknown",
10125
+ checkStatus: 0,
10126
+ retryable: false
10127
+ };
10128
+ } catch {}
10129
+ return this.worldPublishFailure(true, error, "synced");
10130
+ }
10131
+ }
10132
+ async publishWorldText(input) {
10133
+ const session = await this.runtime.requireSession();
10134
+ const clientMutationId = input.clientMutationId.trim();
10135
+ if (!/^[A-Za-z0-9_-]{16,128}$/.test(clientMutationId)) throw new ArkmePluginError("world-publish-mutation-invalid", "发布请求标识无效,请重试", false);
10136
+ return await this.publishWorldTextForConversation(stableWorldPublishRecordUid(session.userId, clientMutationId), input.textContent);
10137
+ }
10138
+ async publishWorldFileAssets(input) {
10139
+ const session = await this.runtime.requireSession();
10140
+ const clientMutationId = input.clientMutationId.trim();
10141
+ const textContent = input.textContent.trim();
10142
+ if (!/^[A-Za-z0-9_-]{16,128}$/.test(clientMutationId)) throw new ArkmePluginError("world-publish-mutation-invalid", "发布请求标识无效,请重试", false);
10143
+ if (textContent === "" || textContent.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("world-text-invalid", "世界内容为空或过长", false);
10144
+ if (input.fileAssets.length === 0 || input.fileAssets.length > 9 || input.fileAssets.some((asset) => !/^[A-Za-z0-9._:-]{8,256}$/.test(asset.fileAssetUid) || asset.fileName.trim() === "" || !asset.mimeType.toLowerCase().startsWith("image/") || !Number.isSafeInteger(asset.size) || asset.size <= 0 || asset.size > 20971520 || asset.fileKind !== 1)) throw new ArkmePluginError("world-publish-assets-invalid", "世界图片参数无效", false);
10145
+ let profile;
10146
+ try {
10147
+ const snapshot = await this.profile.refreshProfile();
10148
+ if (snapshot.profile === null) throw new ArkmePluginError("profile-unavailable", "无法读取当前 Arkme 账号资料", true);
10149
+ profile = snapshot.profile;
10150
+ } catch (error) {
10151
+ return this.worldPublishFailure(false, error);
10152
+ }
10153
+ if (profile.contact.phoneMasked === void 0) return {
10154
+ recordSaved: false,
10155
+ recordState: "not_saved",
10156
+ worldPublished: false,
10157
+ visibility: "not_published",
10158
+ checkStatus: 0,
10159
+ retryable: false,
10160
+ error: "请先在 Arkme 客户端绑定手机号,再发到世界"
10161
+ };
10162
+ const recordUid = stableWorldPublishRecordUid(session.userId, clientMutationId);
10163
+ try {
10164
+ if (await this.worldRecordIsPublic(recordUid)) return {
9507
10165
  recordSaved: true,
9508
10166
  recordState: "synced",
9509
10167
  worldPublished: true,
9510
- visibility: worldVisibility(checkStatus),
9511
- checkStatus,
10168
+ visibility: "unknown",
10169
+ checkStatus: 0,
9512
10170
  retryable: false
9513
10171
  };
10172
+ } catch {}
10173
+ try {
10174
+ await this.record.createFileAssetsForConversation(recordUid, textContent, input.fileAssets);
10175
+ } catch (error) {
10176
+ return this.worldPublishFailure(false, error);
10177
+ }
10178
+ try {
10179
+ const published = await this.runtime.authenticatedWorldPost("/api/v1/public-record/publish-with-file-assets", {
10180
+ record_uid: recordUid,
10181
+ content: textContent,
10182
+ text_content: textContent,
10183
+ file_assets: input.fileAssets.map((asset, index) => ({
10184
+ file_asset_uid: asset.fileAssetUid,
10185
+ media_type: "image",
10186
+ file_kind: 1,
10187
+ sort_order: index
10188
+ })),
10189
+ tags: worldTags(textContent),
10190
+ original_topic_id: 0,
10191
+ created_at: Date.now(),
10192
+ nick_name: profile.nickname || profile.displayName,
10193
+ avatar: profile.avatarRef
10194
+ }, session);
10195
+ return worldPublicationResult(Math.trunc(numberValue(published.check_status)));
9514
10196
  } catch (error) {
9515
10197
  try {
9516
- if (await this.worldRecordIsPublic(normalizedUid, signal)) return {
10198
+ if (await this.worldRecordIsPublic(recordUid)) return {
9517
10199
  recordSaved: true,
9518
10200
  recordState: "synced",
9519
10201
  worldPublished: true,
@@ -9567,7 +10249,14 @@ var WorldService = class {
9567
10249
  }
9568
10250
  const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl) : void 0;
9569
10251
  return {
9570
- recordRef: await this.worldRecordRef(viewerUserId, recordUid, ownerUserId),
10252
+ recordRef: await this.worldRecordRef(viewerUserId, recordUid, {
10253
+ ownerUserId,
10254
+ authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
10255
+ ...(() => {
10256
+ const textPreview = optionalTrimmedText(headline || textContent, 80);
10257
+ return textPreview === void 0 ? {} : { textPreview };
10258
+ })()
10259
+ }),
9571
10260
  authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
9572
10261
  ...avatarRef === void 0 ? {} : { avatarRef },
9573
10262
  ...avatarRef === void 0 && avatarFallback !== void 0 ? { avatarFallback } : {},
@@ -9637,7 +10326,14 @@ var WorldService = class {
9637
10326
  const avatarFallback = worldPhoneDefaultAvatar(rawAvatar);
9638
10327
  const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl) : void 0;
9639
10328
  return {
9640
- interactionRef: await this.worldRecordRef(viewerUserId, recordUid),
10329
+ interactionRef: await this.worldRecordRef(viewerUserId, recordUid, {
10330
+ ownerUserId,
10331
+ authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
10332
+ ...(() => {
10333
+ const textPreview = optionalTrimmedText(textContent, 80);
10334
+ return textPreview === void 0 ? {} : { textPreview };
10335
+ })()
10336
+ }),
9641
10337
  parentRef: await this.worldRecordRef(viewerUserId, parentRecordUid),
9642
10338
  authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
9643
10339
  ...avatarRef === void 0 ? {} : { avatarRef },
@@ -9650,16 +10346,20 @@ var WorldService = class {
9650
10346
  voiceCount
9651
10347
  };
9652
10348
  }
9653
- async worldRecordRef(viewerUserId, recordUid, ownerUserId) {
10349
+ async worldRecordRef(viewerUserId, recordUid, metadata = {}) {
9654
10350
  const recordRef = `arkme-world-record-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`world-record-v1:${String(viewerUserId)}:${recordUid}`).digest("base64url")}`;
9655
10351
  const now = Date.now();
9656
10352
  this.pruneWorldRecordRefs(now);
9657
10353
  const previous = this.worldRecordRefs.get(recordRef);
9658
- const resolvedOwnerUserId = ownerUserId ?? previous?.ownerUserId;
10354
+ const resolvedOwnerUserId = metadata.ownerUserId ?? previous?.ownerUserId;
10355
+ const authorName = optionalTrimmedText(metadata.authorName, 64) ?? previous?.authorName;
10356
+ const textPreview = optionalTrimmedText(metadata.textPreview, 80) ?? previous?.textPreview;
9659
10357
  this.worldRecordRefs.set(recordRef, {
9660
10358
  viewerUserId,
9661
10359
  recordUid,
9662
10360
  ...resolvedOwnerUserId === void 0 || resolvedOwnerUserId <= 0 ? {} : { ownerUserId: resolvedOwnerUserId },
10361
+ ...authorName === void 0 ? {} : { authorName },
10362
+ ...textPreview === void 0 ? {} : { textPreview },
9663
10363
  expiresAtMillis: now + ARKME_WORLD_RECORD_REF_TTL_MILLIS
9664
10364
  });
9665
10365
  return recordRef;
@@ -9739,10 +10439,16 @@ var WorldService = class {
9739
10439
  }
9740
10440
  };
9741
10441
  //#endregion
9742
- //#region src/types.ts
9743
- const ARKME_PROVIDER_CONTRACT_VERSION = 1;
9744
- //#endregion
9745
10442
  //#region src/arkme-service.ts
10443
+ function worldVoiceprintInviteMessage(input) {
10444
+ return `${input.textPreview === void 0 ? "我想邀请你开启声纹。" : `我看到你在世界里发的「${input.textPreview}」,想邀请你开启声纹。`}\n以后看到你的世界动态时,可以直接听到你的声音。\n${input.inviteUrl}`;
10445
+ }
10446
+ function voiceprintInviteRateLimitMessage(error) {
10447
+ if (!(error instanceof ArkmePluginError)) return void 0;
10448
+ if (error.upstreamStatus !== 429 && error.httpStatus !== 429 && !/\bHTTP\s*429\b/.test(error.message)) return void 0;
10449
+ const seconds = error.retryAfterMillis === void 0 ? 0 : Math.ceil(error.retryAfterMillis / 1e3);
10450
+ return seconds > 0 ? `提醒发送太频繁了,请 ${String(seconds)} 秒后再试。` : "提醒发送太频繁了,稍后再试。";
10451
+ }
9746
10452
  var ArkmeService = class {
9747
10453
  config;
9748
10454
  sessionStore;
@@ -9773,6 +10479,7 @@ var ArkmeService = class {
9773
10479
  interwoven;
9774
10480
  aiPolish;
9775
10481
  chat;
10482
+ contact;
9776
10483
  constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker()) {
9777
10484
  this.config = config;
9778
10485
  this.sessionStore = sessionStore;
@@ -9809,6 +10516,7 @@ var ArkmeService = class {
9809
10516
  this.aiPolish = new GroupAiPolishService(this.runtime, this.source, { sendChatSourceTextRaw: async (...args) => await this.chat.sendChatSourceTextRaw(...args) });
9810
10517
  this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session) => await this.chat.chatTimelineItems(data, session) });
9811
10518
  this.chat = new ChatService(this.runtime, this.source, this.profile, this.media, this.record, this.bot, this.arko, this.aiPolish, this.realtime);
10519
+ this.contact = new ContactService(this.runtime, this.source, this.profile, this.realtime);
9812
10520
  this.auth = new AuthService(this.runtime, this.profile, {
9813
10521
  reconnectChatRealtime: () => {
9814
10522
  this.realtime.reconnect();
@@ -9826,6 +10534,7 @@ var ArkmeService = class {
9826
10534
  this.interwoven.dispose();
9827
10535
  this.world.dispose();
9828
10536
  this.arrangement.dispose();
10537
+ this.contact.dispose();
9829
10538
  }
9830
10539
  startChatRealtime() {
9831
10540
  return this.realtime.startChatRealtime();
@@ -9860,6 +10569,9 @@ var ArkmeService = class {
9860
10569
  async createBot(input, options = {}) {
9861
10570
  return await this.bot.createBot(input, options);
9862
10571
  }
10572
+ async createBotSummary(input, options = {}) {
10573
+ return await this.bot.createBotSummary(input, options);
10574
+ }
9863
10575
  async revealBotSecret(botRef, options = {}) {
9864
10576
  return await this.bot.revealBotSecret(botRef, options);
9865
10577
  }
@@ -9886,7 +10598,8 @@ var ArkmeService = class {
9886
10598
  captchaId: this.config.geetestCaptchaId,
9887
10599
  environment: this.config.environment,
9888
10600
  testLoginEnabled: this.config.environment === "test",
9889
- callAssetBasePath: `${this.config.routePath}/call`
10601
+ callAssetBasePath: `${this.config.routePath}/call`,
10602
+ shareWebsite: this.config.shareWebsite ?? "https://app.arkme.ai"
9890
10603
  };
9891
10604
  }
9892
10605
  providerCapabilities() {
@@ -9918,6 +10631,8 @@ var ArkmeService = class {
9918
10631
  groupMemberAdd: true,
9919
10632
  userCard: true,
9920
10633
  openPrivateChat: true,
10634
+ contactAdd: true,
10635
+ conversationQuickAdd: true,
9921
10636
  groupSettings: true,
9922
10637
  extensionManagement: true,
9923
10638
  extensionMetadataEdit: true,
@@ -9925,7 +10640,10 @@ var ArkmeService = class {
9925
10640
  extensionPreviews: true,
9926
10641
  worldFeed: true,
9927
10642
  worldInteractions: true,
10643
+ worldPublish: true,
9928
10644
  worldVoiceprintPlayback: true,
10645
+ worldVoiceprintInvite: true,
10646
+ worldVoiceprintSocialContext: true,
9929
10647
  arrangements: true,
9930
10648
  myExtensions: true,
9931
10649
  extensionPublish: true,
@@ -9974,6 +10692,7 @@ var ArkmeService = class {
9974
10692
  return await this.outgoingCall.releaseOutgoingCall(callRequestId);
9975
10693
  }
9976
10694
  dispose() {
10695
+ this.contact.dispose();
9977
10696
  this.realtime.dispose();
9978
10697
  this.arko.dispose();
9979
10698
  this.auth.dispose();
@@ -9994,6 +10713,12 @@ var ArkmeService = class {
9994
10713
  async cachedProfile() {
9995
10714
  return await this.profile.cachedProfile();
9996
10715
  }
10716
+ async searchContact(identifier, options = {}) {
10717
+ return await this.contact.search(identifier, options);
10718
+ }
10719
+ async addContact(contactRef, options = {}) {
10720
+ return await this.contact.add(contactRef, options);
10721
+ }
9997
10722
  async extensionAuthors(userIds, signal) {
9998
10723
  return await this.extensionReview.extensionAuthors(userIds, signal);
9999
10724
  }
@@ -10141,6 +10866,9 @@ var ArkmeService = class {
10141
10866
  async addGroupMembers(sourceRef, candidateRefs, signal) {
10142
10867
  return await this.group.addGroupMembers(sourceRef, candidateRefs, signal);
10143
10868
  }
10869
+ async createGroup(title, clientMutationId, options = {}) {
10870
+ return await this.group.createGroup(title, clientMutationId, options);
10871
+ }
10144
10872
  async groupSettings(sourceRef, signal) {
10145
10873
  return await this.group.groupSettings(sourceRef, signal);
10146
10874
  }
@@ -10353,12 +11081,47 @@ var ArkmeService = class {
10353
11081
  async listWorldFeed(options = {}) {
10354
11082
  return await this.world.listWorldFeed(options);
10355
11083
  }
11084
+ /** Build the signed-in account's World projection without exposing owner IDs. */
11085
+ async listMyWorldFeed(options = {}) {
11086
+ return await this.world.listMyWorldFeed(options);
11087
+ }
11088
+ async listUserWorldFeed(userId, options = {}) {
11089
+ return await this.world.listUserWorldFeed(userId, options);
11090
+ }
10356
11091
  async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
10357
11092
  return await this.world.worldVoiceprintPlaybackAvailability(recordRefs, signal);
10358
11093
  }
10359
11094
  async generateWorldVoiceprintPlayback(input) {
10360
11095
  return await this.world.generateWorldVoiceprintPlayback(input);
10361
11096
  }
11097
+ async worldVoiceprintSocialContext(recordRef, options = {}) {
11098
+ return await this.world.worldVoiceprintSocialContext(recordRef, options);
11099
+ }
11100
+ async inviteWorldVoiceprint(recordRef, signal) {
11101
+ try {
11102
+ const intent = await this.world.createWorldVoiceprintInviteIntent(recordRef, signal);
11103
+ const privateChat = await this.chat.openPrivateChatFromUser(intent.peerUserId, {
11104
+ displayName: intent.peerDisplayName,
11105
+ ...signal === void 0 ? {} : { signal }
11106
+ });
11107
+ const sent = await this.chat.sendSourceText(privateChat.source.sourceRef, worldVoiceprintInviteMessage(intent), signal === void 0 ? {} : { signal });
11108
+ if (sent.localState !== "synced") throw new ArkmePluginError("world-voiceprint-invite-send-pending", sent.error ?? "声纹邀请已进入发送队列,请稍后在私聊中确认", true);
11109
+ return {
11110
+ sent: true,
11111
+ peerDisplayName: intent.peerDisplayName,
11112
+ messageItemUid: sent.itemUid,
11113
+ expiresAtMillis: intent.expiresAtMillis
11114
+ };
11115
+ } catch (error) {
11116
+ const message = voiceprintInviteRateLimitMessage(error);
11117
+ if (message !== void 0) throw new ArkmePluginError("world-voiceprint-invite-rate-limited", message, true, 429, {
11118
+ cause: error,
11119
+ upstreamStatus: 429,
11120
+ ...!(error instanceof ArkmePluginError) || error.retryAfterMillis === void 0 ? {} : { retryAfterMillis: error.retryAfterMillis }
11121
+ });
11122
+ throw error;
11123
+ }
11124
+ }
10362
11125
  /** Read the authenticated comment/reply tree behind one account-bound World reference. */
10363
11126
  async listWorldInteractions(recordRef, options = {}) {
10364
11127
  return await this.world.listWorldInteractions(recordRef, options);
@@ -10374,6 +11137,12 @@ var ArkmeService = class {
10374
11137
  async publishWorldTextForConversation(recordUid, textContent, signal) {
10375
11138
  return await this.world.publishWorldTextForConversation(recordUid, textContent, signal);
10376
11139
  }
11140
+ async publishWorldText(input) {
11141
+ return await this.world.publishWorldText(input);
11142
+ }
11143
+ async publishWorldFileAssets(input) {
11144
+ return await this.world.publishWorldFileAssets(input);
11145
+ }
10377
11146
  async createText(recordUid, textContent) {
10378
11147
  return await this.record.createText(recordUid, textContent);
10379
11148
  }
@@ -10527,6 +11296,7 @@ function canonicalExtensionSignatureMessage(input) {
10527
11296
  }), "utf8");
10528
11297
  }
10529
11298
  const ARKME_BUNDLE_ARTIFACT_KIND = "dsh-bundle-tgz";
11299
+ const ARKME_NATIVE_BUNDLE_ARTIFACT_KIND = "dsh-native-package-tgz";
10530
11300
  const ARKME_BUNDLE_MAX_BYTES = 100 * 1024 * 1024;
10531
11301
  const TAR_BLOCK_BYTES = 512;
10532
11302
  const MAX_ARCHIVE_FILES = 1024;
@@ -10573,9 +11343,22 @@ function writeOctal(header, offset, length, value) {
10573
11343
  header.write(octal.slice(-(length - 1)), offset, length - 1, "ascii");
10574
11344
  header[offset + length - 1] = 0;
10575
11345
  }
11346
+ function splitTarPath(path) {
11347
+ const direct = Buffer.from(path, "utf8");
11348
+ if (direct.byteLength <= 100) return { name: direct };
11349
+ const separators = [...path.matchAll(/\//g)].map((match) => match.index).reverse();
11350
+ for (const separator of separators) {
11351
+ const prefix = Buffer.from(path.slice(0, separator), "utf8");
11352
+ const name = Buffer.from(path.slice(separator + 1), "utf8");
11353
+ if (prefix.byteLength <= 155 && name.byteLength <= 100) return {
11354
+ name,
11355
+ prefix
11356
+ };
11357
+ }
11358
+ throw new ArkmeBundleArtifactError("bundle-path-too-long", `Bundle 路径过长:${path}`);
11359
+ }
10576
11360
  function tarHeader(path, size) {
10577
- const name = Buffer.from(path, "utf8");
10578
- if (name.byteLength > 100) throw new ArkmeBundleArtifactError("bundle-path-too-long", `Bundle 路径过长:${path}`);
11361
+ const { name, prefix } = splitTarPath(path);
10579
11362
  const header = Buffer.alloc(TAR_BLOCK_BYTES);
10580
11363
  name.copy(header, 0);
10581
11364
  writeOctal(header, 100, 8, 420);
@@ -10587,6 +11370,7 @@ function tarHeader(path, size) {
10587
11370
  header[156] = "0".charCodeAt(0);
10588
11371
  header.write("ustar\0", 257, 6, "ascii");
10589
11372
  header.write("00", 263, 2, "ascii");
11373
+ prefix?.copy(header, 345);
10590
11374
  writeOctal(header, 148, 8, [...header].reduce((sum, byte) => sum + byte, 0));
10591
11375
  return header;
10592
11376
  }
@@ -10621,7 +11405,9 @@ function readTar(tar) {
10621
11405
  while (offset + TAR_BLOCK_BYTES <= tar.byteLength) {
10622
11406
  const header = tar.subarray(offset, offset + TAR_BLOCK_BYTES);
10623
11407
  if (header.every((byte) => byte === 0)) break;
10624
- const path = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
11408
+ const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
11409
+ const prefix = header.subarray(345, 500).toString("utf8").replace(/\0.*$/, "");
11410
+ const path = prefix === "" ? name : `${prefix}/${name}`;
10625
11411
  validateArchivePath(path);
10626
11412
  if (files.has(path)) throw new ArkmeBundleArtifactError("bundle-duplicate-path", `Bundle 包含重复路径:${path}`);
10627
11413
  const type = header[156];
@@ -10706,6 +11492,105 @@ function validatePatch(raw, packageName, executionModel) {
10706
11492
  if (ids.size === 0) throw new ArkmeBundleArtifactError("bundle-patch-empty", "Bundle patch 至少需要一条 row");
10707
11493
  return [...ids].sort();
10708
11494
  }
11495
+ const NATIVE_LIFECYCLE_SCRIPTS = /* @__PURE__ */ new Set([
11496
+ "preinstall",
11497
+ "install",
11498
+ "postinstall",
11499
+ "prepare",
11500
+ "prepublish",
11501
+ "prepublishOnly",
11502
+ "prepack",
11503
+ "postpack"
11504
+ ]);
11505
+ function nonEmptyManifestValue(value) {
11506
+ if (value === void 0 || value === null) return false;
11507
+ if (typeof value === "string") return value.trim() !== "";
11508
+ if (Array.isArray(value)) return value.length > 0;
11509
+ if (typeof value === "object") return Object.keys(value).length > 0;
11510
+ if (typeof value === "boolean") return value;
11511
+ return true;
11512
+ }
11513
+ function inspectNativePatch(raw, packageName) {
11514
+ const document = parseDocument(raw.toString("utf8"), { uniqueKeys: true });
11515
+ if (document.errors.length > 0) throw new ArkmeBundleArtifactError("bundle-patch-invalid", "Bundle patch YAML 格式无效");
11516
+ const value = document.toJS();
11517
+ if (!Array.isArray(value)) throw new ArkmeBundleArtifactError("bundle-patch-invalid", "Bundle patch 必须是操作数组");
11518
+ const patchIds = /* @__PURE__ */ new Set();
11519
+ const capabilities = /* @__PURE__ */ new Set();
11520
+ for (const operation of value) {
11521
+ if (operation === null || typeof operation !== "object" || Array.isArray(operation)) {
11522
+ capabilities.add("profile_patch_override");
11523
+ continue;
11524
+ }
11525
+ const record = operation;
11526
+ if (!(Object.keys(record).length === 1 && Array.isArray(record.insert))) {
11527
+ capabilities.add("profile_patch_override");
11528
+ if (typeof record.id === "string" && record.id.trim() !== "") patchIds.add(record.id.trim());
11529
+ continue;
11530
+ }
11531
+ for (const row of record.insert) {
11532
+ if (row === null || typeof row !== "object" || Array.isArray(row)) continue;
11533
+ const item = row;
11534
+ if (typeof item.id === "string" && item.id.trim() !== "") patchIds.add(item.id.trim());
11535
+ if (typeof item.name === "string" && item.name !== packageName && !item.name.startsWith(`${packageName}/`)) capabilities.add("external_package_reference");
11536
+ }
11537
+ }
11538
+ return {
11539
+ patchIds: [...patchIds].sort(),
11540
+ capabilities: [...capabilities].sort()
11541
+ };
11542
+ }
11543
+ function inspectNativeManifest(manifest, files) {
11544
+ if (typeof manifest.name !== "string" || manifest.name.length > 214 || !PACKAGE_NAME.test(manifest.name) || manifest.name.startsWith("@deepseek-ai/") || manifest.name === "@senguoyun/dsh-arkme" || manifest.name.startsWith("@arkme-local/")) throw new ArkmeBundleArtifactError("bundle-package-name-invalid", "Bundle package name 无效或属于保留集合");
11545
+ if (typeof manifest.version !== "string" || !SEMVER.test(manifest.version)) throw new ArkmeBundleArtifactError("bundle-version-invalid", "Bundle version 必须是严格 SemVer");
11546
+ if (manifest.dsh?.arkme !== void 0 && manifest.dsh.arkme !== null) throw new ArkmeBundleArtifactError("bundle-execution-model-invalid", "原生 DSH V3 Bundle 不允许声明 Arkme sandbox marker");
11547
+ const patch = manifest.dsh?.bundle?.patch;
11548
+ if (typeof patch !== "string" || patch.trim() === "" || patch.includes("\\") || patch.startsWith("/")) throw new ArkmeBundleArtifactError("bundle-patch-missing", "Bundle 必须声明包内 dsh.bundle.patch");
11549
+ const patchPath = `package/${posix.normalize(patch.replace(/^\.\//, ""))}`;
11550
+ if (!files.has(patchPath) || patchPath.includes("/../")) throw new ArkmeBundleArtifactError("bundle-patch-missing", "Bundle patch 不在包内");
11551
+ const capabilities = /* @__PURE__ */ new Set();
11552
+ if (nonEmptyManifestValue(manifest.dependencies)) capabilities.add("runtime_dependencies");
11553
+ if (nonEmptyManifestValue(manifest.optionalDependencies)) capabilities.add("optional_dependencies");
11554
+ if (nonEmptyManifestValue(manifest.bundledDependencies) || nonEmptyManifestValue(manifest.bundleDependencies)) capabilities.add("bundled_dependencies");
11555
+ for (const name of Object.keys(manifest.peerDependencies ?? {})) if (!name.startsWith("@deepseek-ai/") && name !== "@senguoyun/dsh-arkme" && name !== "react" && name !== "react-dom") {
11556
+ capabilities.add("peer_dependencies");
11557
+ break;
11558
+ }
11559
+ if (Object.entries(manifest.scripts ?? {}).some(([name, value]) => NATIVE_LIFECYCLE_SCRIPTS.has(name) && nonEmptyManifestValue(value))) capabilities.add("lifecycle_scripts");
11560
+ if (nonEmptyManifestValue(manifest.bin)) capabilities.add("bin");
11561
+ if ([...files.keys()].some((name) => name.toLowerCase().endsWith(".node"))) capabilities.add("native_addon");
11562
+ return {
11563
+ patchPath,
11564
+ capabilities: [...capabilities].sort()
11565
+ };
11566
+ }
11567
+ function inspectNativeBundleArtifactV3(bytes) {
11568
+ assertArchiveSize(bytes.byteLength, "Bundle");
11569
+ let tar;
11570
+ try {
11571
+ tar = gunzipSync(bytes, { maxOutputLength: 105907200 });
11572
+ } catch {
11573
+ throw new ArkmeBundleArtifactError("bundle-gzip-invalid", "Bundle 不是有效 gzip");
11574
+ }
11575
+ const files = readTar(tar);
11576
+ const packageJSON = files.get("package/package.json");
11577
+ if (packageJSON === void 0) throw new ArkmeBundleArtifactError("bundle-package-json-missing", "Bundle 缺少 package.json");
11578
+ const manifest = readManifest(packageJSON);
11579
+ const inspected = inspectNativeManifest(manifest, files);
11580
+ const patch = inspectNativePatch(files.get(inspected.patchPath), manifest.name);
11581
+ const nativeCapabilities = [.../* @__PURE__ */ new Set([...inspected.capabilities, ...patch.capabilities])].sort();
11582
+ return {
11583
+ bytes: Buffer.from(bytes),
11584
+ bundleSha256: bundleSha256(bytes),
11585
+ packageJsonSha256: bundleSha256(packageJSON),
11586
+ packageName: manifest.name,
11587
+ version: manifest.version,
11588
+ executionModel: "dsh-native",
11589
+ nativeCapabilities,
11590
+ files,
11591
+ patchIds: patch.patchIds
11592
+ };
11593
+ }
10709
11594
  function inspectBundleArtifact(bytes) {
10710
11595
  assertArchiveSize(bytes.byteLength, "Bundle");
10711
11596
  let tar;
@@ -10736,29 +11621,114 @@ function pathInside(root, target) {
10736
11621
  const fromRoot = relative(root, target);
10737
11622
  return fromRoot === "" || !fromRoot.startsWith(`..${sep}`) && fromRoot !== ".." && !isAbsolute(fromRoot);
10738
11623
  }
10739
- function collectPackageFiles(root, manifest) {
11624
+ function globPattern(pattern) {
11625
+ let expression = "^";
11626
+ for (let index = 0; index < pattern.length; index += 1) {
11627
+ const character = pattern[index];
11628
+ if (character === "*") if (pattern[index + 1] === "*") {
11629
+ index += 1;
11630
+ if (pattern[index + 1] === "/") {
11631
+ index += 1;
11632
+ expression += "(?:.*/)?";
11633
+ } else expression += ".*";
11634
+ } else expression += "[^/]*";
11635
+ else if (character === "?") expression += "[^/]";
11636
+ else expression += character.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
11637
+ }
11638
+ return new RegExp(`${expression}$`);
11639
+ }
11640
+ function collectPackageFiles(root, manifest, nativeV3 = false) {
10740
11641
  const canonicalRoot = realpathSync(root);
10741
11642
  const selected = /* @__PURE__ */ new Map();
10742
- const add = (absolute) => {
11643
+ const ignoredNativeRootEntries = /* @__PURE__ */ new Set([
11644
+ ".git",
11645
+ ".hg",
11646
+ ".svn",
11647
+ "node_modules"
11648
+ ]);
11649
+ const ignoredNativeFiles = /* @__PURE__ */ new Set([
11650
+ ".npmrc",
11651
+ ".yarnrc",
11652
+ ".yarnrc.yml",
11653
+ ".pnpmfile.cjs"
11654
+ ]);
11655
+ const add = (absolute, allowIgnore = false) => {
10743
11656
  const stat = lstatSync(absolute);
10744
- if (stat.isSymbolicLink()) throw new ArkmeBundleArtifactError("bundle-link-forbidden", "Bundle source 不允许符号链接");
11657
+ const rel = relative(canonicalRoot, absolute).split(sep).join("/");
11658
+ const first = rel.split("/")[0] ?? "";
11659
+ const basename = rel.split("/").at(-1) ?? "";
11660
+ if (allowIgnore && (ignoredNativeRootEntries.has(first) || ignoredNativeFiles.has(basename) || basename === ".env" || basename.startsWith(".env."))) return;
11661
+ if (stat.isSymbolicLink()) {
11662
+ if (allowIgnore) return;
11663
+ throw new ArkmeBundleArtifactError("bundle-link-forbidden", "Bundle source 不允许符号链接");
11664
+ }
10745
11665
  const canonical = realpathSync(absolute);
10746
11666
  if (!pathInside(canonicalRoot, canonical)) throw new ArkmeBundleArtifactError("bundle-path-invalid", "Bundle source 逃逸 package root");
10747
11667
  if (stat.isDirectory()) {
10748
- for (const child of readdirSync(canonical).sort()) add(join(canonical, child));
11668
+ for (const child of readdirSync(canonical).sort()) add(join(canonical, child), allowIgnore);
10749
11669
  return;
10750
11670
  }
10751
11671
  if (!stat.isFile()) throw new ArkmeBundleArtifactError("bundle-file-type-forbidden", "Bundle source 只允许普通文件");
10752
- const rel = relative(canonicalRoot, canonical).split(sep).join("/");
10753
- selected.set(`package/${rel}`, readFileSync(canonical));
11672
+ const canonicalRel = relative(canonicalRoot, canonical).split(sep).join("/");
11673
+ selected.set(`package/${canonicalRel}`, readFileSync(canonical));
11674
+ };
11675
+ const addNativePackageExtras = () => {
11676
+ if (!nativeV3) return;
11677
+ for (const child of readdirSync(canonicalRoot).sort()) if (/^(?:readme|licen[cs]e|copying|notice)(?:\.|$)/i.test(child)) add(join(canonicalRoot, child), true);
11678
+ const paths = /* @__PURE__ */ new Set();
11679
+ if (typeof manifest.main === "string") paths.add(manifest.main);
11680
+ const bin = manifest.bin;
11681
+ if (typeof bin === "string") paths.add(bin);
11682
+ else if (bin !== null && typeof bin === "object" && !Array.isArray(bin)) {
11683
+ for (const value of Object.values(bin)) if (typeof value === "string") paths.add(value);
11684
+ }
11685
+ const patch = manifest.dsh?.bundle?.patch;
11686
+ if (typeof patch === "string") paths.add(patch);
11687
+ for (const entry of paths) {
11688
+ const target = resolve(canonicalRoot, entry);
11689
+ if (pathInside(canonicalRoot, target) && existsSync(target)) add(target, true);
11690
+ }
10754
11691
  };
10755
11692
  add(join(canonicalRoot, "package.json"));
10756
- for (const entry of manifest.files) {
10757
- if (entry.includes("*") || entry.includes("?") || entry.includes("[") || entry.includes("\\")) throw new ArkmeBundleArtifactError("bundle-files-invalid", "Bundle files 暂不接受 glob 或反斜杠");
11693
+ const entries = manifest.files ?? [];
11694
+ if (entries.length === 0) {
11695
+ if (!nativeV3) throw new ArkmeBundleArtifactError("bundle-files-missing", "Bundle 必须显式声明 files");
11696
+ for (const child of readdirSync(canonicalRoot).sort()) if (child !== "package.json") add(join(canonicalRoot, child), true);
11697
+ addNativePackageExtras();
11698
+ return selected;
11699
+ }
11700
+ let candidates;
11701
+ const listCandidates = () => {
11702
+ if (candidates !== void 0) return candidates;
11703
+ candidates = [];
11704
+ const walk = (directory) => {
11705
+ for (const child of readdirSync(directory).sort()) {
11706
+ const absolute = join(directory, child);
11707
+ const stat = lstatSync(absolute);
11708
+ if (stat.isSymbolicLink()) continue;
11709
+ if (stat.isDirectory()) walk(absolute);
11710
+ else if (stat.isFile()) candidates.push(relative(canonicalRoot, absolute).split(sep).join("/"));
11711
+ }
11712
+ };
11713
+ walk(canonicalRoot);
11714
+ return candidates;
11715
+ };
11716
+ for (const entry of entries) {
11717
+ if (entry.includes("\\") || entry.startsWith("/") || entry.split("/").includes("..")) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
11718
+ const hasGlob = entry.includes("*") || entry.includes("?") || entry.includes("[");
11719
+ if (hasGlob && !nativeV3) throw new ArkmeBundleArtifactError("bundle-files-invalid", "Bundle files 暂不接受 glob 或反斜杠");
11720
+ if (hasGlob) {
11721
+ const matcher = globPattern(entry.replace(/^\.\//, ""));
11722
+ const matches = listCandidates().filter((candidate) => matcher.test(candidate));
11723
+ if (matches.length === 0) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
11724
+ for (const match of matches) add(resolve(canonicalRoot, match), true);
11725
+ continue;
11726
+ }
10758
11727
  const target = resolve(canonicalRoot, entry);
10759
- if (!pathInside(canonicalRoot, target) || !statSync(target).isFile() && !statSync(target).isDirectory()) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
10760
- add(target);
11728
+ if (!pathInside(canonicalRoot, target) || !existsSync(target) || !statSync(target).isFile() && !statSync(target).isDirectory()) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
11729
+ add(target, nativeV3);
10761
11730
  }
11731
+ addNativePackageExtras();
10762
11732
  return selected;
10763
11733
  }
10764
11734
  function packBundleFiles(files) {
@@ -10772,14 +11742,25 @@ function packBundleFiles(files) {
10772
11742
  }
10773
11743
  };
10774
11744
  }
10775
- function packLocalBundleDirectory(directory) {
10776
- return packBundleFiles(collectPackageFiles(directory, readManifest(readFileSync(join(directory, "package.json")))));
11745
+ function packNativeBundleFiles(files) {
11746
+ const bytes = gzipSync(createTar(files), { level: 9 });
11747
+ assertArchiveSize(bytes.byteLength, "Bundle");
11748
+ return {
11749
+ bundle: inspectNativeBundleArtifactV3(bytes),
11750
+ source: {
11751
+ bytes: Buffer.from(bytes),
11752
+ sourceSha256: bundleSha256(bytes)
11753
+ }
11754
+ };
11755
+ }
11756
+ function packLocalNativeBundleDirectoryV3(directory) {
11757
+ return packNativeBundleFiles(collectPackageFiles(directory, readManifest(readFileSync(join(directory, "package.json"))), true));
10777
11758
  }
10778
- function readLocalBundleTarball(path) {
11759
+ function readNativeBundleTarballV3(path) {
10779
11760
  if (!lstatSync(path).isFile()) throw new ArkmeBundleArtifactError("bundle-tarball-invalid", "本地 Bundle tgz 必须是普通文件");
10780
11761
  const bytes = readFileSync(path);
10781
11762
  return {
10782
- bundle: inspectBundleArtifact(bytes),
11763
+ bundle: inspectNativeBundleArtifactV3(bytes),
10783
11764
  source: {
10784
11765
  bytes: Buffer.from(bytes),
10785
11766
  sourceSha256: bundleSha256(bytes)
@@ -10854,6 +11835,39 @@ function verifyBundleResolutionSignature(resolution, trustedKeys) {
10854
11835
  }
10855
11836
  if (!valid) throw new ArkmePluginError("extension-signature-invalid", "扩展 Bundle 平台签名验证失败", false, 403);
10856
11837
  }
11838
+ function canonicalNativeBundleSignatureMessage(input) {
11839
+ return Buffer.from(JSON.stringify({
11840
+ format_version: input.artifact_contract_version,
11841
+ artifact_kind: input.artifact_kind,
11842
+ extension_id: input.extension_id,
11843
+ package_name: input.package_name,
11844
+ version: input.version,
11845
+ execution_model: input.execution_model,
11846
+ bundle_sha256: input.bundle_sha256,
11847
+ package_json_sha256: input.package_json_sha256,
11848
+ source_sha256: input.source_sha256,
11849
+ ...input.native_capabilities.length === 0 ? {} : { native_capabilities: input.native_capabilities },
11850
+ published_at: input.published_at,
11851
+ signing_key_id: input.signing_key_id
11852
+ }), "utf8");
11853
+ }
11854
+ function verifyNativeBundleResolutionSignature(resolution, trustedKeys) {
11855
+ const capabilities = resolution.native_capabilities;
11856
+ if (resolution.artifact_contract_version !== 3 || resolution.artifact_kind !== "dsh-native-package-tgz" || resolution.package_name.trim() === "" || resolution.execution_model !== "dsh-native" || ![
11857
+ resolution.bundle_sha256,
11858
+ resolution.package_json_sha256,
11859
+ resolution.source_sha256
11860
+ ].every((value) => /^[a-f0-9]{64}$/.test(value)) || !Array.isArray(capabilities) || capabilities.some((value) => typeof value !== "string" || value.trim() === "") || capabilities.some((value, index) => index > 0 && capabilities[index - 1] >= value) || !Number.isSafeInteger(resolution.published_at) || resolution.published_at <= 0 || resolution.signature.trim() === "") throw new ArkmePluginError("extension-signature-envelope-invalid", "原生 DSH V3 签名 envelope 字段无效", false, 502);
11861
+ const key = trustedKeys.get(resolution.signing_key_id);
11862
+ if (key === void 0) throw new ArkmePluginError("extension-signing-key-untrusted", `不信任扩展签名密钥 ${resolution.signing_key_id}`, false, 403);
11863
+ let valid = false;
11864
+ try {
11865
+ valid = verify(null, canonicalNativeBundleSignatureMessage(resolution), publicKey(key), Buffer.from(resolution.signature, "base64"));
11866
+ } catch (error) {
11867
+ throw new ArkmePluginError("extension-signature-invalid", "原生 DSH V3 签名格式无效", false, 403, { cause: error });
11868
+ }
11869
+ if (!valid) throw new ArkmePluginError("extension-signature-invalid", "原生 DSH V3 平台签名验证失败", false, 403);
11870
+ }
10857
11871
  //#endregion
10858
11872
  //#region src/extensions/persistent-client-bundle.ts
10859
11873
  /** This function is serialized into each generated browser bundle. It must stay closure-free. */
@@ -11513,4 +12527,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
11513
12527
  return jsonValue(await handler(jsonValue(args)));
11514
12528
  }
11515
12529
  //#endregion
11516
- export { ARKME_EXTENSION_MAX_BYTES as A, renderArkmeSandboxHostEntry as C, unpackArkmeExtension as D, sha256Hex as E, ArkmePluginError as F, ArkmeOutgoingCallError as I, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as M, ArkmeService as N, ARKME_EXTENSION_FORMAT as O, ARKME_PROVIDER_CONTRACT_VERSION as P, readLocalBundleTarball as S, canonicalExtensionJson as T, bundleSha256 as _, persistentArkmeExtensionActive as a, packBundleFiles as b, readPersistentExtensionActivation as c, verifyBundleResolutionSignature as d, verifyExtensionResolutionSignature as f, arkmeSandboxEntryId as g, ArkmeBundleArtifactError as h, invokePersistentArkmeExtension as i, ARKME_EXTENSION_PREVIEW_MAX_BYTES as j, ARKME_EXTENSION_ICON_MAX_BYTES as k, writePersistentExtensionActivation as l, ARKME_BUNDLE_MAX_BYTES as m, applyPersistentArkmeHostExtension as n, persistentArkmeExtensionRuntimeState as o, ARKME_BUNDLE_ARTIFACT_KIND as p, deactivatePersistentArkmeExtension as r, materializePersistentExtensionBundle as s, activatePersistentArkmeExtension as t, renderArkmeBundleClientBundle as u, canonicalBundleJson as v, assertExtensionArtifactSize as w, packLocalBundleDirectory as x, inspectBundleArtifact as y };
12530
+ export { unpackArkmeExtension as A, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as B, packBundleFiles as C, assertExtensionArtifactSize as D, renderArkmeSandboxHostEntry as E, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as F, ArkmePluginError as H, ArkmeService as I, ARKME_DEFAULT_SHARE_WEBSITE as L, ARKME_EXTENSION_ICON_MAX_BYTES as M, ARKME_EXTENSION_MAX_BYTES as N, canonicalExtensionJson as O, ARKME_EXTENSION_PREVIEW_MAX_BYTES as P, ARKME_PROVIDER_CONTRACT_VERSION as R, inspectNativeBundleArtifactV3 as S, readNativeBundleTarballV3 as T, ArkmeOutgoingCallError as U, isArkmeBotAvatarRef as V, ArkmeBundleArtifactError as _, persistentArkmeExtensionActive as a, canonicalBundleJson as b, readPersistentExtensionActivation as c, verifyBundleResolutionSignature as d, verifyExtensionResolutionSignature as f, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as g, ARKME_BUNDLE_MAX_BYTES as h, invokePersistentArkmeExtension as i, ARKME_EXTENSION_FORMAT as j, sha256Hex as k, writePersistentExtensionActivation as l, ARKME_BUNDLE_ARTIFACT_KIND as m, applyPersistentArkmeHostExtension as n, persistentArkmeExtensionRuntimeState as o, verifyNativeBundleResolutionSignature as p, deactivatePersistentArkmeExtension as r, materializePersistentExtensionBundle as s, activatePersistentArkmeExtension as t, renderArkmeBundleClientBundle as u, arkmeSandboxEntryId as v, packLocalNativeBundleDirectoryV3 as w, inspectBundleArtifact as x, bundleSha256 as y, ARKME_WORLD_PUBLISH_MAX_IMAGES as z };