@senguoyun/dsh-arkme 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +2 -2
  2. package/assets/desktop_call/avatar-lin-xiaoman.jpeg +0 -0
  3. package/assets/desktop_call/avatar-mother.jpg +0 -0
  4. package/assets/desktop_call/avatar-self.png +0 -0
  5. package/assets/desktop_call/call-add-linear.svg +13 -0
  6. package/assets/desktop_call/icon-scan-add-contact.svg +4 -0
  7. package/assets/desktop_call/profile-circle-linear.svg +11 -0
  8. package/assets/desktop_call/user-add-linear.svg +15 -0
  9. package/cordis.patch.yml +4 -1
  10. package/docs/consumer-plugin-contract.md +13 -4
  11. package/docs/extension-market-controls.md +1 -1
  12. package/docs/gui-production-integration.md +1 -1
  13. package/lib/client.js +27533 -5149
  14. package/lib/client.js.map +1 -1
  15. package/lib/extension-profile-restart-helper.js +19 -3
  16. package/lib/index.js +1498 -380
  17. package/lib/{install-store-D6P-dH2i.js → install-store-Sn3Ccxfu.js} +32 -5
  18. package/lib/persistent-extension.js +1 -1
  19. package/lib/{persistent-runtime-kUpXXUZI.js → persistent-runtime-Cki0XC3L.js} +826 -183
  20. package/lib/plugin-updater-helper-CiPlNqBc.js +346 -0
  21. package/lib/plugin-updater-helper.js +2 -250
  22. package/lib/sdk.js +114 -2
  23. package/lib/types/arkme-service.d.ts +30 -2
  24. package/lib/types/arkme-service.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeAppUpdateDialog.d.ts +5 -0
  26. package/lib/types/client/ArkmeAppUpdateDialog.d.ts.map +1 -0
  27. package/lib/types/client/ArkmeCallHistorySurface.d.ts +57 -0
  28. package/lib/types/client/ArkmeCallHistorySurface.d.ts.map +1 -0
  29. package/lib/types/client/ArkmeContactAddSurface.d.ts +9 -0
  30. package/lib/types/client/ArkmeContactAddSurface.d.ts.map +1 -0
  31. package/lib/types/client/ArkmeExtensionPublishDialog.d.ts.map +1 -1
  32. package/lib/types/client/ArkmeExtensionReviews.d.ts +10 -0
  33. package/lib/types/client/ArkmeExtensionReviews.d.ts.map +1 -1
  34. package/lib/types/client/ArkmeMarketplace.d.ts +153 -0
  35. package/lib/types/client/ArkmeMarketplace.d.ts.map +1 -0
  36. package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
  37. package/lib/types/client/ArkmeQuickAdd.d.ts +12 -0
  38. package/lib/types/client/ArkmeQuickAdd.d.ts.map +1 -0
  39. package/lib/types/client/ArkmeRecordingSurface.d.ts +109 -3
  40. package/lib/types/client/ArkmeRecordingSurface.d.ts.map +1 -1
  41. package/lib/types/client/ArkmeSettingsSurface.d.ts +20 -0
  42. package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
  43. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  44. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +12 -0
  45. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  46. package/lib/types/client/ArkmeWorldSurface.d.ts +30 -0
  47. package/lib/types/client/ArkmeWorldSurface.d.ts.map +1 -0
  48. package/lib/types/client/api.d.ts +1 -1
  49. package/lib/types/client/api.d.ts.map +1 -1
  50. package/lib/types/client/app-update-store.d.ts +35 -0
  51. package/lib/types/client/app-update-store.d.ts.map +1 -0
  52. package/lib/types/client/demo-audio.d.ts +2 -0
  53. package/lib/types/client/demo-audio.d.ts.map +1 -0
  54. package/lib/types/client/extension-market-model.d.ts +1 -0
  55. package/lib/types/client/extension-market-model.d.ts.map +1 -1
  56. package/lib/types/client/index.d.ts +8 -2
  57. package/lib/types/client/index.d.ts.map +1 -1
  58. package/lib/types/client/personal-test-edition.d.ts +17 -0
  59. package/lib/types/client/personal-test-edition.d.ts.map +1 -0
  60. package/lib/types/client/ui-controller.d.ts +4 -1
  61. package/lib/types/client/ui-controller.d.ts.map +1 -1
  62. package/lib/types/extensions/bundle-artifact.d.ts +22 -0
  63. package/lib/types/extensions/bundle-artifact.d.ts.map +1 -1
  64. package/lib/types/extensions/install-store.d.ts +1 -0
  65. package/lib/types/extensions/install-store.d.ts.map +1 -1
  66. package/lib/types/extensions/manager.d.ts +35 -2
  67. package/lib/types/extensions/manager.d.ts.map +1 -1
  68. package/lib/types/extensions/owned-inventory.d.ts +2 -2
  69. package/lib/types/extensions/owned-inventory.d.ts.map +1 -1
  70. package/lib/types/extensions/owned-refs.d.ts +1 -1
  71. package/lib/types/extensions/owned-refs.d.ts.map +1 -1
  72. package/lib/types/extensions/owned-types.d.ts +18 -5
  73. package/lib/types/extensions/owned-types.d.ts.map +1 -1
  74. package/lib/types/extensions/profile-owned-inventory.d.ts +1 -0
  75. package/lib/types/extensions/profile-owned-inventory.d.ts.map +1 -1
  76. package/lib/types/extensions/profile-restart-helper.d.ts.map +1 -1
  77. package/lib/types/extensions/publish-client.d.ts +47 -2
  78. package/lib/types/extensions/publish-client.d.ts.map +1 -1
  79. package/lib/types/extensions/signature.d.ts +31 -1
  80. package/lib/types/extensions/signature.d.ts.map +1 -1
  81. package/lib/types/extensions/types.d.ts +71 -5
  82. package/lib/types/extensions/types.d.ts.map +1 -1
  83. package/lib/types/host-api.d.ts.map +1 -1
  84. package/lib/types/index.d.ts +8 -2
  85. package/lib/types/index.d.ts.map +1 -1
  86. package/lib/types/outgoing-call-assets.d.ts.map +1 -1
  87. package/lib/types/plugin-update-artifact.d.ts +46 -0
  88. package/lib/types/plugin-update-artifact.d.ts.map +1 -0
  89. package/lib/types/plugin-update.d.ts +20 -11
  90. package/lib/types/plugin-update.d.ts.map +1 -1
  91. package/lib/types/plugin-updater-helper.d.ts +11 -0
  92. package/lib/types/plugin-updater-helper.d.ts.map +1 -1
  93. package/lib/types/recording-presentation.d.ts +2 -2
  94. package/lib/types/recording-presentation.d.ts.map +1 -1
  95. package/lib/types/sdk/index.d.ts +63 -7
  96. package/lib/types/sdk/index.d.ts.map +1 -1
  97. package/lib/types/services/bot-service.d.ts +5 -1
  98. package/lib/types/services/bot-service.d.ts.map +1 -1
  99. package/lib/types/services/contact-service.d.ts +25 -0
  100. package/lib/types/services/contact-service.d.ts.map +1 -0
  101. package/lib/types/services/group-service.d.ts +4 -1
  102. package/lib/types/services/group-service.d.ts.map +1 -1
  103. package/lib/types/services/recording-service.d.ts +4 -2
  104. package/lib/types/services/recording-service.d.ts.map +1 -1
  105. package/lib/types/services/service.d.ts +1 -0
  106. package/lib/types/services/service.d.ts.map +1 -1
  107. package/lib/types/services/world-service.d.ts +13 -0
  108. package/lib/types/services/world-service.d.ts.map +1 -1
  109. package/lib/types/tools/business/contacts/index.d.ts +4 -0
  110. package/lib/types/tools/business/contacts/index.d.ts.map +1 -0
  111. package/lib/types/tools/business/groups/create.d.ts +2 -0
  112. package/lib/types/tools/business/groups/create.d.ts.map +1 -0
  113. package/lib/types/tools/business/groups/index.d.ts +1 -0
  114. package/lib/types/tools/business/groups/index.d.ts.map +1 -1
  115. package/lib/types/tools/business/index.d.ts.map +1 -1
  116. package/lib/types/tools/business/recordings/index.d.ts +2 -1
  117. package/lib/types/tools/business/recordings/index.d.ts.map +1 -1
  118. package/lib/types/tools/business/world/index.d.ts +2 -0
  119. package/lib/types/tools/business/world/index.d.ts.map +1 -1
  120. package/lib/types/tools/business/world/mine.d.ts +2 -0
  121. package/lib/types/tools/business/world/mine.d.ts.map +1 -0
  122. package/lib/types/tools/business/world/voiceprint-invite.d.ts +2 -0
  123. package/lib/types/tools/business/world/voiceprint-invite.d.ts.map +1 -0
  124. package/lib/types/tools/extensions/index.d.ts.map +1 -1
  125. package/lib/types/tools/extensions/publish-conversation.d.ts +8 -1
  126. package/lib/types/tools/extensions/publish-conversation.d.ts.map +1 -1
  127. package/lib/types/tools/ports/contacts.d.ts +12 -0
  128. package/lib/types/tools/ports/contacts.d.ts.map +1 -0
  129. package/lib/types/tools/ports/groups.d.ts +4 -1
  130. package/lib/types/tools/ports/groups.d.ts.map +1 -1
  131. package/lib/types/tools/ports/index.d.ts +3 -2
  132. package/lib/types/tools/ports/index.d.ts.map +1 -1
  133. package/lib/types/tools/ports/recordings.d.ts +3 -2
  134. package/lib/types/tools/ports/recordings.d.ts.map +1 -1
  135. package/lib/types/tools/ports/world.d.ts +12 -1
  136. package/lib/types/tools/ports/world.d.ts.map +1 -1
  137. package/lib/types/tools/prompts/business.d.ts.map +1 -1
  138. package/lib/types/tools/registry/registrar.d.ts.map +1 -1
  139. package/lib/types/types.d.ts +48 -3
  140. package/lib/types/types.d.ts.map +1 -1
  141. package/package.json +5 -4
  142. package/lib/plugin-update-install-state-CIsOCjWK.js +0 -67
  143. package/lib/types/client/ArkmeExtensionCenter.d.ts +0 -74
  144. 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);
@@ -2466,10 +2466,10 @@ var SecretValue = class {
2466
2466
  };
2467
2467
  //#endregion
2468
2468
  //#region src/services/bot-service.ts
2469
- function numberValue$17(value) {
2469
+ function numberValue$18(value) {
2470
2470
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2471
2471
  }
2472
- function booleanValue$9(value) {
2472
+ function booleanValue$10(value) {
2473
2473
  return value === true;
2474
2474
  }
2475
2475
  function listValue$18(value) {
@@ -2552,6 +2552,10 @@ var BotService = class {
2552
2552
  throw new ArkmePluginError("bot-create-outcome-unknown", "Bot 可能已创建,但响应不完整;请刷新 Bot 列表确认", false, 409, { cause: error });
2553
2553
  }
2554
2554
  }
2555
+ /** Browser-safe result: the one-time credential never leaves the Host owner. */
2556
+ async createBotSummary(input, options = {}) {
2557
+ return (await this.createBot(input, options)).bot;
2558
+ }
2555
2559
  async revealBotSecret(botRef, options = {}) {
2556
2560
  const session = await this.runtime.requireSession();
2557
2561
  const reference = await this.openBotRef(botRef, session.userId);
@@ -2606,13 +2610,13 @@ var BotService = class {
2606
2610
  const { directChatAvailable: _directChatAvailable, ...summary } = await this.botSummaryFromData(raw, session.userId);
2607
2611
  items.push({
2608
2612
  ...summary,
2609
- installed: booleanValue$9(raw.installed)
2613
+ installed: booleanValue$10(raw.installed)
2610
2614
  });
2611
2615
  }
2612
2616
  return {
2613
2617
  groupSourceRef,
2614
2618
  displayName: stringValue$1(data.subject_title).trim() || group.displayName,
2615
- canAddBots: booleanValue$9(data.can_current_user_add_bots),
2619
+ canAddBots: booleanValue$10(data.can_current_user_add_bots),
2616
2620
  items
2617
2621
  };
2618
2622
  }
@@ -2687,7 +2691,7 @@ var BotService = class {
2687
2691
  }
2688
2692
  const reference = {
2689
2693
  version: 1,
2690
- userId: numberValue$17(raw.userId),
2694
+ userId: numberValue$18(raw.userId),
2691
2695
  botId: stringValue$1(raw.botId).trim()
2692
2696
  };
2693
2697
  if (raw.version !== 1 || reference.userId !== expectedUserId || reference.botId === "") throw new ArkmePluginError("bot-ref-invalid", "Bot 引用与当前账号不匹配", false, 403);
@@ -2698,10 +2702,10 @@ var BotService = class {
2698
2702
  //#region src/services/calendar-service.ts
2699
2703
  const MAX_CALENDAR_RANGE_DAYS = 62;
2700
2704
  const MAX_DAY_RECORD_LIMIT = 50;
2701
- function numberValue$16(value) {
2705
+ function numberValue$17(value) {
2702
2706
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2703
2707
  }
2704
- function booleanValue$8(value) {
2708
+ function booleanValue$9(value) {
2705
2709
  if (typeof value === "boolean") return value;
2706
2710
  if (typeof value === "number") return value !== 0;
2707
2711
  }
@@ -2738,7 +2742,7 @@ function boundedLimit(value) {
2738
2742
  return limit;
2739
2743
  }
2740
2744
  function contentAccessState(value) {
2741
- const code = Math.trunc(numberValue$16(value));
2745
+ const code = Math.trunc(numberValue$17(value));
2742
2746
  if (code === 1) return "available";
2743
2747
  if (code === 2) return "protected";
2744
2748
  return "unknown";
@@ -2748,7 +2752,7 @@ function sourceKind(raw) {
2748
2752
  const chat = objectValue$1(raw.chat_core);
2749
2753
  if (stringValue$1(topic.topic_uid).trim() !== "") return "topic";
2750
2754
  if (stringValue$1(chat.chat_session_uid).trim() !== "") return "chat";
2751
- if (booleanValue$8(raw.is_uncategorized) === true) return "self";
2755
+ if (booleanValue$9(raw.is_uncategorized) === true) return "self";
2752
2756
  return "unknown";
2753
2757
  }
2754
2758
  var CalendarService = class {
@@ -2807,7 +2811,7 @@ var CalendarService = class {
2807
2811
  cacheMs: 1e4,
2808
2812
  failureCooldownMs: 2e3
2809
2813
  });
2810
- const nextSendAt = Math.trunc(numberValue$16(data.next_cursor_send_at));
2814
+ const nextSendAt = Math.trunc(numberValue$17(data.next_cursor_send_at));
2811
2815
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
2812
2816
  return {
2813
2817
  scope: "self",
@@ -2826,20 +2830,20 @@ var CalendarService = class {
2826
2830
  const item = objectValue$1(raw);
2827
2831
  const bucketDate = stringValue$1(item.bucket_date).trim();
2828
2832
  if (bucketDate === "") return void 0;
2829
- const count = Math.max(0, Math.trunc(numberValue$16(item.count)));
2833
+ const count = Math.max(0, Math.trunc(numberValue$17(item.count)));
2830
2834
  return {
2831
2835
  bucketDate,
2832
2836
  count,
2833
- protectedCount: Math.max(0, Math.trunc(numberValue$16(item.protected_count))),
2837
+ protectedCount: Math.max(0, Math.trunc(numberValue$17(item.protected_count))),
2834
2838
  hasRecords: count > 0,
2835
- ...numberValue$16(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$16(item.first_send_at)) } : {}
2839
+ ...numberValue$17(item.first_send_at) > 0 ? { firstSendAtMillis: Math.trunc(numberValue$17(item.first_send_at)) } : {}
2836
2840
  };
2837
2841
  }
2838
2842
  dayRecord(raw) {
2839
2843
  const item = objectValue$1(raw);
2840
2844
  const core = objectValue$1(item.record_core);
2841
2845
  const recordUid = stringValue$1(item.record_uid ?? core.record_uid).trim();
2842
- const sendAtMillis = Math.trunc(numberValue$16(item.send_at ?? core.send_at));
2846
+ const sendAtMillis = Math.trunc(numberValue$17(item.send_at ?? core.send_at));
2843
2847
  if (recordUid === "" || sendAtMillis <= 0) return void 0;
2844
2848
  const accessState = contentAccessState(core.content_access_state);
2845
2849
  const available = accessState === "available";
@@ -2847,9 +2851,9 @@ var CalendarService = class {
2847
2851
  const textContent = available ? clippedText(core.text_content, 4e3) : "";
2848
2852
  const preview = title || clippedText(textContent, 160) || (available ? "无文字内容" : "受保护内容");
2849
2853
  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);
2854
+ const isUncategorized = booleanValue$9(item.is_uncategorized);
2855
+ const hasManualEdit = booleanValue$9(core.has_manual_edit);
2856
+ const hasPolish = booleanValue$9(core.has_polish);
2853
2857
  return {
2854
2858
  recordUid,
2855
2859
  sendAtMillis,
@@ -2859,9 +2863,9 @@ var CalendarService = class {
2859
2863
  preview,
2860
2864
  ...stringValue$1(topic.title).trim() === "" ? {} : { topicTitle: stringValue$1(topic.title).trim() },
2861
2865
  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)),
2866
+ creationSource: Math.trunc(numberValue$17(core.creation_source)),
2867
+ templateKind: Math.trunc(numberValue$17(core.template_kind)),
2868
+ displayKind: Math.trunc(numberValue$17(core.display_kind)),
2865
2869
  protected: accessState === "protected",
2866
2870
  ...isUncategorized === void 0 ? {} : { isUncategorized },
2867
2871
  ...hasManualEdit === void 0 ? {} : { hasManualEdit },
@@ -3556,10 +3560,10 @@ var ChatRealtimeService = class {
3556
3560
  };
3557
3561
  //#endregion
3558
3562
  //#region src/services/chat-service.ts
3559
- function numberValue$15(value) {
3563
+ function numberValue$16(value) {
3560
3564
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
3561
3565
  }
3562
- function booleanValue$7(value) {
3566
+ function booleanValue$8(value) {
3563
3567
  return value === true;
3564
3568
  }
3565
3569
  function listValue$15(value) {
@@ -3658,15 +3662,15 @@ var ChatService = class {
3658
3662
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
3659
3663
  if (uid === "") throw new ArkmePluginError("private-chat-contract-invalid", "私聊会话响应不完整", true, 502);
3660
3664
  const unread = objectValue$1(data.unread_snapshot);
3661
- const latestSequence = numberValue$15(unread.session_last_seq ?? chatSession.last_seq);
3665
+ const latestSequence = numberValue$16(unread.session_last_seq ?? chatSession.last_seq);
3662
3666
  const source = {
3663
3667
  sourceRef: await this.source.sealSourceRef(session.userId, "private_chat", uid, displayName),
3664
3668
  sourceKey: await this.source.chatDirectorySourceKey(session.userId, uid),
3665
3669
  kind: "private_chat",
3666
3670
  displayName,
3667
3671
  ...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),
3672
+ activeAtMillis: numberValue$16(chatSession.last_active_at) || Date.now(),
3673
+ unreadCount: numberValue$16(unread.unread_count),
3670
3674
  ...latestSequence > 0 ? { latestSequence } : {}
3671
3675
  };
3672
3676
  this.source.setChatSourceByKey(`${String(session.userId)}:${uid}`, source);
@@ -3693,7 +3697,7 @@ var ChatService = class {
3693
3697
  mediaUnavailable: media.unavailableRecordUids.has(recordUid)
3694
3698
  });
3695
3699
  }).filter((item) => item.itemUid !== "");
3696
- const nextSendAt = numberValue$15(data.next_cursor_send_at);
3700
+ const nextSendAt = numberValue$16(data.next_cursor_send_at);
3697
3701
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
3698
3702
  return {
3699
3703
  source: await this.source.sourceItem(source),
@@ -3737,7 +3741,7 @@ var ChatService = class {
3737
3741
  mediaUnavailable: media.unavailableRecordUids.has(recordUid)
3738
3742
  });
3739
3743
  });
3740
- const nextSendAt = numberValue$15(data.next_cursor_send_at);
3744
+ const nextSendAt = numberValue$16(data.next_cursor_send_at);
3741
3745
  const nextUid = stringValue$1(data.next_cursor_record_uid).trim();
3742
3746
  return {
3743
3747
  source: await this.source.sourceItem(source),
@@ -3767,14 +3771,14 @@ var ChatService = class {
3767
3771
  const relation = objectValue$1(item.relation);
3768
3772
  const record = objectValue$1(item.record);
3769
3773
  const payload = objectValue$1(record.payload);
3770
- const recordStatus = numberValue$15(record.status);
3774
+ const recordStatus = numberValue$16(record.status);
3771
3775
  if (recordStatus !== 1) continue;
3772
3776
  const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
3773
3777
  if (uid === "") continue;
3774
3778
  const relationUid = stringValue$1(relation.rel_uid).trim();
3775
- const senderUserId = numberValue$15(relation.sender_user_id);
3779
+ const senderUserId = numberValue$16(relation.sender_user_id);
3776
3780
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
3777
- const sendAtMillis = numberValue$15(relation.attach_at ?? payload.send_at);
3781
+ const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
3778
3782
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
3779
3783
  const rawAgentSource = timelineAgentSource(relation, record, payload);
3780
3784
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
@@ -3788,16 +3792,16 @@ var ChatService = class {
3788
3792
  title: stringValue$1(payload.title),
3789
3793
  textContent: stringValue$1(payload.text_content),
3790
3794
  status: recordStatus,
3791
- sequence: numberValue$15(relation.seq),
3792
- ...numberValue$15(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$15(record.version ?? payload.version) } : {},
3795
+ sequence: numberValue$16(relation.seq),
3796
+ ...numberValue$16(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$16(record.version ?? payload.version) } : {},
3793
3797
  ...aiPolish === void 0 ? {} : { aiPolish },
3794
3798
  ...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),
3799
+ templateKind: numberValue$16(payload.template_kind),
3800
+ displayKind: numberValue$16(payload.display_kind),
3801
+ version: numberValue$16(payload.version ?? record.version),
3802
+ updateAtMillis: numberValue$16(payload.update_at ?? record.update_at),
3803
+ recordDurationMillis: numberValue$16(payload.record_duration_millis),
3804
+ editDurationMillis: numberValue$16(payload.edit_duration_millis),
3801
3805
  contentBlocks: this.media.richContentBlocks(item, session.userId)
3802
3806
  }) - 1;
3803
3807
  if (Number.isSafeInteger(senderUserId) && senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
@@ -3809,7 +3813,7 @@ var ChatService = class {
3809
3813
  items[index].avatarRef = await this.profile.sealProfileImageRef(session.userId, senderUserId);
3810
3814
  }
3811
3815
  } catch {}
3812
- const beforeSequence = numberValue$15(data.next_before_seq);
3816
+ const beforeSequence = numberValue$16(data.next_before_seq);
3813
3817
  let aiPolishSettings;
3814
3818
  let aiPolishNotices;
3815
3819
  if (aiPolishDecorations !== void 0) {
@@ -3848,7 +3852,7 @@ var ChatService = class {
3848
3852
  return {
3849
3853
  messageRef,
3850
3854
  reportUid,
3851
- status: numberValue$15(report.status)
3855
+ status: numberValue$16(report.status)
3852
3856
  };
3853
3857
  }
3854
3858
  async sendSourceText(sourceRef, textContent, options = {}) {
@@ -3879,7 +3883,7 @@ var ChatService = class {
3879
3883
  return {
3880
3884
  sourceRef,
3881
3885
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
3882
- status: numberValue$15(result.status),
3886
+ status: numberValue$16(result.status),
3883
3887
  localState: "synced"
3884
3888
  };
3885
3889
  }
@@ -3912,7 +3916,7 @@ var ChatService = class {
3912
3916
  const raw = objectValue$1(value);
3913
3917
  const botId = stringValue$1(raw.bot_id).trim();
3914
3918
  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);
3919
+ if (stringValue$1(raw.provider).trim() !== "openclaw" || !booleanValue$8(raw.installed)) throw new ArkmePluginError("bot-mention-not-installed", "所选 Bot 未安装到该群聊", false, 409);
3916
3920
  const name = stringValue$1(raw.name).trim();
3917
3921
  if (name === "") throw new ArkmePluginError("bot-contract-invalid", "Bot 响应不完整", true, 502);
3918
3922
  const display = `@${name}`;
@@ -3964,8 +3968,8 @@ var ChatService = class {
3964
3968
  return {
3965
3969
  sourceRef,
3966
3970
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
3967
- status: numberValue$15(result.audit_status),
3968
- sequence: numberValue$15(result.seq),
3971
+ status: numberValue$16(result.audit_status),
3972
+ sequence: numberValue$16(result.seq),
3969
3973
  localState: "synced"
3970
3974
  };
3971
3975
  }
@@ -4021,7 +4025,7 @@ var ChatService = class {
4021
4025
  return {
4022
4026
  sourceRef,
4023
4027
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
4024
- status: numberValue$15(result.status),
4028
+ status: numberValue$16(result.status),
4025
4029
  localState: "synced"
4026
4030
  };
4027
4031
  }
@@ -4033,7 +4037,7 @@ var ChatService = class {
4033
4037
  return {
4034
4038
  sourceRef,
4035
4039
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
4036
- status: numberValue$15(result.status),
4040
+ status: numberValue$16(result.status),
4037
4041
  localState: "synced"
4038
4042
  };
4039
4043
  }
@@ -4045,8 +4049,8 @@ var ChatService = class {
4045
4049
  return {
4046
4050
  sourceRef,
4047
4051
  itemUid: stringValue$1(result.record_uid).trim() || recordUid,
4048
- status: numberValue$15(result.audit_status ?? result.status),
4049
- sequence: numberValue$15(result.seq),
4052
+ status: numberValue$16(result.audit_status ?? result.status),
4053
+ sequence: numberValue$16(result.seq),
4050
4054
  localState: "synced"
4051
4055
  };
4052
4056
  }
@@ -4096,7 +4100,7 @@ var ChatService = class {
4096
4100
  send_at: sendAtMillis
4097
4101
  }, session, options.signal);
4098
4102
  const chatSessionUid = stringValue$1(result.chat_session_uid).trim();
4099
- const sequence = numberValue$15(result.seq);
4103
+ const sequence = numberValue$16(result.seq);
4100
4104
  const targetKind = stringValue$1(result.target_kind).trim();
4101
4105
  if (chatSessionUid === "" || !Number.isSafeInteger(sequence) || sequence <= 0 || targetKind !== "direct") throw new ArkmePluginError("direct-send-response-invalid", "Chat Agent 发送返回了无效响应", true, 502);
4102
4106
  const responseRecordUid = stringValue$1(result.record_uid).trim() || recordUid;
@@ -4124,10 +4128,10 @@ var ChatService = class {
4124
4128
  reason: "arkme_dsh_open_chat"
4125
4129
  }, session);
4126
4130
  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);
4131
+ const effectiveReadSequence = numberValue$16(data.effective_read_seq);
4132
+ const readAt = numberValue$16(data.read_at);
4133
+ const sessionLastSequence = numberValue$16(data.session_last_seq);
4134
+ const unreadCount = numberValue$16(data.unread_count);
4131
4135
  if (responseSessionUid !== source.ownerRef || !Number.isSafeInteger(effectiveReadSequence) || effectiveReadSequence < readSequence || readAt <= 0 || sessionLastSequence < effectiveReadSequence || !Number.isSafeInteger(unreadCount) || unreadCount < 0) throw new ArkmePluginError("source-read-ack-invalid", "聊天已读响应不完整", true, 502);
4132
4136
  const cacheKey = `${String(session.userId)}:${source.ownerRef}`;
4133
4137
  const cached = this.source.cachedChatSourceByKey(cacheKey);
@@ -4162,9 +4166,9 @@ var ChatService = class {
4162
4166
  const payload = objectValue$1(record.payload);
4163
4167
  const uid = stringValue$1(relation.record_uid ?? payload.record_uid).trim();
4164
4168
  if (uid === "") continue;
4165
- const senderUserId = numberValue$15(relation.sender_user_id);
4169
+ const senderUserId = numberValue$16(relation.sender_user_id);
4166
4170
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
4167
- const sendAtMillis = numberValue$15(relation.attach_at ?? payload.send_at);
4171
+ const sendAtMillis = numberValue$16(relation.attach_at ?? payload.send_at);
4168
4172
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
4169
4173
  const rawAgentSource = timelineAgentSource(relation, record, payload);
4170
4174
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
@@ -4177,12 +4181,12 @@ var ChatService = class {
4177
4181
  sendAtMillis,
4178
4182
  title: stringValue$1(payload.title),
4179
4183
  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) } : {},
4184
+ status: numberValue$16(record.status),
4185
+ sequence: numberValue$16(relation.seq),
4186
+ ...numberValue$16(record.version ?? payload.version) > 0 ? { recordVersion: numberValue$16(record.version ?? payload.version) } : {},
4183
4187
  ...aiPolish === void 0 ? {} : { aiPolish },
4184
4188
  ...forwardRecords === void 0 ? {} : { forwardRecords },
4185
- displayKind: numberValue$15(payload.display_kind),
4189
+ displayKind: numberValue$16(payload.display_kind),
4186
4190
  contentBlocks: this.media.richContentBlocks(item, session.userId)
4187
4191
  });
4188
4192
  }
@@ -4198,7 +4202,7 @@ var ChatService = class {
4198
4202
  const sorted = values.map((value, index) => ({
4199
4203
  value,
4200
4204
  index,
4201
- order: numberValue$15(objectValue$1(value).item_order)
4205
+ order: numberValue$16(objectValue$1(value).item_order)
4202
4206
  })).sort((left, right) => (left.order || left.index) - (right.order || right.index));
4203
4207
  for (const entry of sorted) {
4204
4208
  if (projectedItems.length >= 100) return;
@@ -4208,19 +4212,19 @@ var ChatService = class {
4208
4212
  await appendItems(listValue$15(nestedForward.items), depth + 1);
4209
4213
  continue;
4210
4214
  }
4211
- const senderUserId = numberValue$15(item.source_sender_user_id ?? item.sourceSenderUserId ?? item.owner_id ?? item.ownerId);
4215
+ const senderUserId = numberValue$16(item.source_sender_user_id ?? item.sourceSenderUserId ?? item.owner_id ?? item.ownerId);
4212
4216
  const senderName = stringValue$1(item.owner_name ?? item.ownerName ?? item.source_display_name ?? item.sourceDisplayName).trim() || "Arkme用户";
4213
4217
  const textContent = stringValue$1(item.text ?? item.text_preview ?? item.textPreview).trim();
4214
4218
  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)));
4219
+ const imageCount = Math.max(0, Math.trunc(numberValue$16(item.image_count ?? item.imageCount)));
4220
+ const voiceCount = Math.max(0, Math.trunc(numberValue$16(item.voice_count ?? item.voiceCount)));
4221
+ const fileCount = Math.max(0, Math.trunc(numberValue$16(item.file_count ?? item.fileCount)));
4218
4222
  const fileName = listValue$15(item.file_names ?? item.fileNames).map((value) => stringValue$1(value).trim()).find((value) => value !== "");
4219
4223
  const contentLabel = imageCount > 0 ? imageCount > 1 ? `[${String(imageCount)}张图片]` : "[图片]" : voiceCount > 0 ? "[语音]" : fileCount > 0 ? fileName === void 0 ? "[文件]" : `[文件] ${fileName}` : stringValue$1(item.availability).trim() !== "" ? "原快记暂不可查看" : void 0;
4220
4224
  projectedItems.push({
4221
4225
  senderName,
4222
4226
  ...senderUserId > 0 ? { avatarRef: await this.profile.sealProfileImageRef(viewerUserId, senderUserId) } : {},
4223
- sendAtMillis: numberValue$15(item.send_at ?? item.sendAt),
4227
+ sendAtMillis: numberValue$16(item.send_at ?? item.sendAt),
4224
4228
  title,
4225
4229
  textContent,
4226
4230
  ...contentLabel === void 0 ? {} : { contentLabel }
@@ -4229,7 +4233,7 @@ var ChatService = class {
4229
4233
  };
4230
4234
  await appendItems(listValue$15(payload.items), 0);
4231
4235
  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;
4236
+ const createdAtMillis = numberValue$16(payload.created_at ?? payload.createdAt) || fallbackCreatedAtMillis;
4233
4237
  return {
4234
4238
  title: stringValue$1(payload.title).trim() || "转发快记",
4235
4239
  createdAtMillis,
@@ -4261,7 +4265,7 @@ var ChatService = class {
4261
4265
  }
4262
4266
  const result = {
4263
4267
  version: 1,
4264
- userId: numberValue$15(parsed.userId),
4268
+ userId: numberValue$16(parsed.userId),
4265
4269
  chatSessionUid: stringValue$1(parsed.chatSessionUid).trim(),
4266
4270
  relationUid: stringValue$1(parsed.relationUid).trim()
4267
4271
  };
@@ -4270,6 +4274,167 @@ var ChatService = class {
4270
4274
  }
4271
4275
  };
4272
4276
  //#endregion
4277
+ //#region src/services/contact-service.ts
4278
+ const CONTACT_REF_TTL_MS = 10 * 6e4;
4279
+ const MAX_CONTACT_REFS = 512;
4280
+ const CONTACT_REF_PATTERN = /^arkme-contact-v1\.[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4281
+ function numberValue$15(value) {
4282
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
4283
+ return Number.isFinite(parsed) ? parsed : 0;
4284
+ }
4285
+ function booleanValue$7(value) {
4286
+ return value === true;
4287
+ }
4288
+ function normalizeIdentifier(value) {
4289
+ const trimmed = value.trim().replace(/^@/, "");
4290
+ if (trimmed === "" || trimmed.length > 64) throw new ArkmePluginError("contact-identifier-invalid", "请输入手机号或即我号", false);
4291
+ const compactPhone = trimmed.replace(/[\s()-]/g, "").replace(/^\+86/, "");
4292
+ if (/^\d+$/.test(compactPhone)) {
4293
+ if (!/^1[3-9]\d{9}$/.test(compactPhone)) throw new ArkmePluginError("contact-phone-invalid", "请输入有效的中国大陆手机号", false);
4294
+ return {
4295
+ kind: "phone",
4296
+ identifierType: 1,
4297
+ identifier: compactPhone,
4298
+ phonePre: "86"
4299
+ };
4300
+ }
4301
+ if (!/^[A-Za-z][A-Za-z0-9_-]{1,63}$/.test(trimmed)) throw new ArkmePluginError("contact-arkme-id-invalid", "即我号格式无效", false);
4302
+ return {
4303
+ kind: "arkme_id",
4304
+ identifierType: 2,
4305
+ identifier: trimmed,
4306
+ phonePre: "86"
4307
+ };
4308
+ }
4309
+ var ContactService = class {
4310
+ runtime;
4311
+ source;
4312
+ profile;
4313
+ realtime;
4314
+ candidates = /* @__PURE__ */ new Map();
4315
+ constructor(runtime, source, profile, realtime) {
4316
+ this.runtime = runtime;
4317
+ this.source = source;
4318
+ this.profile = profile;
4319
+ this.realtime = realtime;
4320
+ }
4321
+ dispose() {
4322
+ this.candidates.clear();
4323
+ }
4324
+ async search(identifier, options = {}) {
4325
+ const session = await this.runtime.requireSession();
4326
+ const normalized = normalizeIdentifier(identifier);
4327
+ const candidate = await this.lookup(normalized, session, options.signal);
4328
+ this.prune();
4329
+ this.candidates.set(candidate.result.contactRef, candidate);
4330
+ this.prune();
4331
+ return candidate.result;
4332
+ }
4333
+ async add(contactRef, options = {}) {
4334
+ const session = await this.runtime.requireSession();
4335
+ this.prune();
4336
+ const normalizedContactRef = contactRef.trim();
4337
+ if (!CONTACT_REF_PATTERN.test(normalizedContactRef)) throw new ArkmePluginError("contact-ref-invalid", "联系人搜索引用无效,请重新搜索", false);
4338
+ const cached = this.candidates.get(normalizedContactRef);
4339
+ if (cached === void 0 || cached.userId !== session.userId || cached.expiresAtMillis <= Date.now()) throw new ArkmePluginError("contact-ref-expired", "联系人搜索结果已过期,请重新搜索", false, 410);
4340
+ const candidate = await this.lookup(cached.normalized, session, options.signal, normalizedContactRef);
4341
+ if (candidate.targetUserId !== cached.targetUserId || candidate.result.registered !== cached.result.registered) {
4342
+ this.candidates.delete(normalizedContactRef);
4343
+ throw new ArkmePluginError("contact-candidate-changed", "联系人身份已发生变化,请重新搜索后确认", false, 409);
4344
+ }
4345
+ if (candidate.result.isSelf) throw new ArkmePluginError("contact-self-invalid", "不能添加自己为联系人", false);
4346
+ if (!candidate.result.canAdd) throw new ArkmePluginError("contact-add-unavailable", "该账号当前无法添加", false, 409);
4347
+ const remark = options.remark?.trim() ?? "";
4348
+ if (Array.from(remark).length > 100) throw new ArkmePluginError("contact-remark-invalid", "备注不能超过 100 个字符", false);
4349
+ const requestUid = options.requestUid?.trim() || randomUUID();
4350
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(requestUid)) throw new ArkmePluginError("contact-request-uid-invalid", "联系人添加请求标识无效", false);
4351
+ const chatSessionUid = `chat_session_${requestUid}`;
4352
+ const displayName = candidate.result.displayName;
4353
+ const common = {
4354
+ chat_session_uid: chatSessionUid,
4355
+ create_at: Date.now(),
4356
+ ...remark === "" ? {} : { remark }
4357
+ };
4358
+ const registered = candidate.result.registered && candidate.targetUserId !== void 0;
4359
+ const bundle = registered ? await this.runtime.authenticatedChatPost("/api/v1/chats/contacts/add-and-open-private", {
4360
+ ...common,
4361
+ target_user_id: candidate.targetUserId,
4362
+ target_display_name_snapshot: displayName
4363
+ }, session, options.signal, { key: `contact:add:${requestUid}` }) : await this.runtime.authenticatedChatPost("/api/v1/chats/contacts/add-and-open-pending-phone", {
4364
+ ...common,
4365
+ phone_pre: candidate.normalized.phonePre,
4366
+ phone: candidate.phone ?? candidate.normalized.identifier,
4367
+ display_name_snapshot: displayName
4368
+ }, session, options.signal, { key: `contact:add:${requestUid}` });
4369
+ let source = await this.source.chatSourceFromBundle(bundle, session, void 0, []);
4370
+ if (registered && candidate.targetUserId !== void 0) source = {
4371
+ ...source,
4372
+ avatarRef: await this.profile.sealProfileImageRef(session.userId, candidate.targetUserId)
4373
+ };
4374
+ const returnedUid = stringValue$1(objectValue$1(bundle.session).chat_session_uid).trim();
4375
+ if (returnedUid === "") throw new ArkmePluginError("contact-add-contract-invalid", "联系人添加响应不完整", true, 502);
4376
+ this.source.setChatSource(session.userId, returnedUid, source);
4377
+ this.source.invalidateSourceListCache(session.userId, "root");
4378
+ this.realtime.emitChatClientEvent({
4379
+ type: "sessions-delta",
4380
+ revision: this.realtime.nextChatClientRevision(),
4381
+ updates: [{
4382
+ sourceKey: source.sourceKey ?? await this.source.chatDirectorySourceKey(session.userId, returnedUid),
4383
+ source,
4384
+ timelineItems: []
4385
+ }]
4386
+ });
4387
+ this.candidates.delete(normalizedContactRef);
4388
+ return {
4389
+ state: registered ? "ready" : "pending",
4390
+ source
4391
+ };
4392
+ }
4393
+ async lookup(normalized, session, signal, contactRef = `arkme-contact-v1.${randomUUID()}`) {
4394
+ const data = await this.runtime.authenticatedAuthPost("/api/v1/auth/search-contact-account", {
4395
+ identifier_type: normalized.identifierType,
4396
+ identifier: normalized.identifier,
4397
+ phone_pre: normalized.phonePre
4398
+ }, session, signal, {
4399
+ key: `contact:search:${normalized.identifierType}:${normalized.identifier}`,
4400
+ bypassCache: true
4401
+ });
4402
+ const targetUserId = Math.trunc(numberValue$15(data.user_id));
4403
+ const registered = booleanValue$7(data.is_registered) && targetUserId > 0;
4404
+ const isSelf = booleanValue$7(data.is_self);
4405
+ const canAdd = booleanValue$7(data.can_add) && !isSelf && (normalized.kind === "phone" || registered);
4406
+ const displayName = stringValue$1(data.nick_name).trim() || stringValue$1(data.jotmo_id).trim() || (normalized.kind === "phone" ? `+${normalized.phonePre} ${normalized.identifier}` : normalized.identifier);
4407
+ const result = {
4408
+ contactRef,
4409
+ identifierKind: normalized.kind,
4410
+ displayName,
4411
+ ...stringValue$1(data.jotmo_id).trim() === "" ? {} : { arkmeId: stringValue$1(data.jotmo_id).trim() },
4412
+ ...registered ? { avatarRef: await this.profile.sealProfileImageRef(session.userId, targetUserId) } : {},
4413
+ registered,
4414
+ inviteBySms: booleanValue$7(data.invite_by_sms),
4415
+ canAdd,
4416
+ isSelf
4417
+ };
4418
+ return {
4419
+ userId: session.userId,
4420
+ expiresAtMillis: Date.now() + CONTACT_REF_TTL_MS,
4421
+ normalized,
4422
+ result,
4423
+ ...targetUserId > 0 ? { targetUserId } : {},
4424
+ ...normalized.kind === "phone" ? { phone: stringValue$1(data.phone).trim() || normalized.identifier } : {}
4425
+ };
4426
+ }
4427
+ prune() {
4428
+ const now = Date.now();
4429
+ for (const [ref, candidate] of this.candidates) if (candidate.expiresAtMillis <= now) this.candidates.delete(ref);
4430
+ while (this.candidates.size >= MAX_CONTACT_REFS) {
4431
+ const oldest = this.candidates.keys().next().value;
4432
+ if (oldest === void 0) break;
4433
+ this.candidates.delete(oldest);
4434
+ }
4435
+ }
4436
+ };
4437
+ //#endregion
4273
4438
  //#region src/services/community-service.ts
4274
4439
  function numberValue$14(value) {
4275
4440
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -4485,7 +4650,7 @@ var ExtensionReviewService = class {
4485
4650
  422
4486
4651
  ].includes(error.httpStatus)) {
4487
4652
  await this.runtime.stateStore.removeExtensionReviewOperation(userId, operation.clientMutationId);
4488
- throw new ArkmePluginError("extension-review-registry-rejected", `评论已写入首页,但扩展市场未接受:${error.message}`, false, error.httpStatus, { cause: error });
4653
+ throw new ArkmePluginError("extension-review-registry-rejected", `评论已写入首页,但市集未接受:${error.message}`, false, error.httpStatus, { cause: error });
4489
4654
  }
4490
4655
  await this.runtime.stateStore.markExtensionReviewOperation(userId, operation.clientMutationId, "failed", safeFailureMessage$5(error));
4491
4656
  throw new ArkmePluginError("extension-review-registry-pending", "评论已写入首页,但同步到扩展详情失败;请保留当前内容并重试", true, 503, { cause: error });
@@ -5024,6 +5189,7 @@ var GroupAiPolishService = class {
5024
5189
  };
5025
5190
  //#endregion
5026
5191
  //#region src/services/group-service.ts
5192
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
5027
5193
  function numberValue$12(value) {
5028
5194
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
5029
5195
  }
@@ -5356,6 +5522,27 @@ var GroupService = class {
5356
5522
  if (raw.version !== 1 || result.userId !== expectedUserId || result.groupChatSessionUid !== expectedGroupChatSessionUid || !Number.isSafeInteger(result.targetUserId) || result.targetUserId <= 0 || result.targetUserId === expectedUserId || result.displayName === "") throw new ArkmePluginError("group-candidate-ref-invalid", "群成员候选引用与当前账号不匹配", false, 403);
5357
5523
  return result;
5358
5524
  }
5525
+ async createGroup(title, clientMutationId, options = {}) {
5526
+ const normalizedTitle = title.trim();
5527
+ if (normalizedTitle === "" || Array.from(normalizedTitle).length > 80) throw new ArkmePluginError("group-title-invalid", "群聊名称需为 1-80 个字符", false);
5528
+ if (!UUID.test(clientMutationId)) throw new ArkmePluginError("client-mutation-id-invalid", "群聊操作标识无效", false);
5529
+ const session = await this.runtime.requireSession();
5530
+ let data;
5531
+ try {
5532
+ data = await this.runtime.authenticatedChatPost("/api/v1/chats/create-group", {
5533
+ chat_session_uid: clientMutationId,
5534
+ title: normalizedTitle,
5535
+ member_user_ids: [],
5536
+ create_at: Date.now()
5537
+ }, session, options.signal);
5538
+ } catch (error) {
5539
+ if (error instanceof ArkmePluginError && ["arkme-network-error", "arkme-timeout"].includes(error.code)) throw new ArkmePluginError("group-create-outcome-unknown", "群聊创建结果未知,请刷新会话列表确认;不会自动重试", false, 409, { cause: error });
5540
+ throw error;
5541
+ }
5542
+ const source = await this.source.chatSourceFromBundle(data, session, void 0, []);
5543
+ this.source.setChatSourceByKey(`${String(session.userId)}:${clientMutationId}`, source);
5544
+ return source;
5545
+ }
5359
5546
  async listGroupMembers(sourceRef, options = {}) {
5360
5547
  const session = await this.runtime.requireSession();
5361
5548
  const source = await this.source.openSourceRef(sourceRef, session.userId);
@@ -7345,7 +7532,7 @@ function displayTimestamp(value) {
7345
7532
  const parsed = Date.parse(stringValue(value));
7346
7533
  return Number.isFinite(parsed) ? parsed : 0;
7347
7534
  }
7348
- function projectRecordingTranscripts(response, speakerResponse) {
7535
+ function projectRecordingTranscripts(response, speakerResponse, source = "system") {
7349
7536
  const data = objectValue(response);
7350
7537
  const sessions = /* @__PURE__ */ new Map();
7351
7538
  const sessionSpeakerColorIndexes = /* @__PURE__ */ new Map();
@@ -7389,7 +7576,32 @@ function projectRecordingTranscripts(response, speakerResponse) {
7389
7576
  const sessionSpeakers = listValue$6(session.spk_ls ?? session.speakers).map(objectValue);
7390
7577
  const childOffset = numberValue$6(child.start_at);
7391
7578
  const childStart = childOffset >= 1e11 ? childOffset : numberValue$6(session.start_at) + childOffset;
7392
- const rows = listValue$6(child.asr);
7579
+ const rows = listValue$6(source === "doubao" ? child.doubao_asr : child.asr);
7580
+ const doubaoStatus = numberValue$6(child.doubao_asr_status);
7581
+ if (source === "doubao" && (doubaoStatus === 1 || doubaoStatus === 2 || doubaoStatus === 4 || doubaoStatus === 5)) {
7582
+ const transcriptStatus = doubaoStatus === 1 || doubaoStatus === 2 ? "processing" : doubaoStatus === 4 ? "silent" : "failed";
7583
+ const text = transcriptStatus === "processing" ? "豆包转写中..." : transcriptStatus === "silent" ? "豆包未识别到人声" : "豆包转写失败";
7584
+ const label = transcriptStatus === "processing" ? "豆包转写中" : transcriptStatus === "silent" ? "豆包静音" : "豆包失败";
7585
+ projected.push({
7586
+ itemId: `${childId || sessionId}:doubao:status`,
7587
+ sessionId,
7588
+ childId,
7589
+ asrItemIndex: 0,
7590
+ transcriptSource: "doubao",
7591
+ startAtMillis: childStart,
7592
+ endAtMillis: childStart + Math.max(0, numberValue$6(child.duration)),
7593
+ speakerNumber: -1,
7594
+ speakerColorIndex: -1,
7595
+ speakerLabel: label,
7596
+ isSelf: false,
7597
+ isBackground: false,
7598
+ text,
7599
+ transcriptStatus,
7600
+ sourceIndex
7601
+ });
7602
+ sourceIndex += 1;
7603
+ continue;
7604
+ }
7393
7605
  for (let index = 0; index < rows.length; index += 1) {
7394
7606
  const row = objectValue(rows[index]);
7395
7607
  const isBackground = numberValue$6(row.b ?? row.background) === 1 || row.background === true;
@@ -7403,15 +7615,15 @@ function projectRecordingTranscripts(response, speakerResponse) {
7403
7615
  const persistentSpeakerNumber = optionalNumberValue(sessionSpeaker.speaker_display_number ?? sessionSpeaker.speakerDisplayNumber);
7404
7616
  const speakerNumber = persistentSpeakerNumber !== void 0 && persistentSpeakerNumber > 0 ? persistentSpeakerNumber : rawSpeakerNumber;
7405
7617
  const speakerColorIndex = sessionSpeakerColorIndexes.get(`${sessionId}:${rawSpeakerNumber}`) ?? Math.max(0, rawSpeakerNumber);
7406
- const speakerLabel = speakerNumber >= 0 ? `说话人 ${speakerNumber}` : "未知说话人";
7618
+ const speakerLabel = source === "doubao" ? rawSpeakerNumber >= 0 ? `豆包说话人 ${rawSpeakerNumber + 1}` : "豆包说话人" : speakerNumber >= 0 ? `说话人 ${speakerNumber}` : "未知说话人";
7407
7619
  const startOffset = numberValue$6(row.s ?? row.start_at);
7408
7620
  const endOffset = Math.max(startOffset, numberValue$6(row.e ?? row.end_at));
7409
7621
  projected.push({
7410
- itemId: `${childId || sessionId}:${index}`,
7622
+ itemId: source === "system" ? `${childId || sessionId}:${index}` : `${childId || sessionId}:doubao:${index}`,
7411
7623
  sessionId,
7412
7624
  childId,
7413
7625
  asrItemIndex: index,
7414
- transcriptSource: "system",
7626
+ transcriptSource: source,
7415
7627
  startAtMillis: childStart + startOffset,
7416
7628
  endAtMillis: childStart + endOffset,
7417
7629
  speakerNumber,
@@ -7420,6 +7632,7 @@ function projectRecordingTranscripts(response, speakerResponse) {
7420
7632
  isSelf,
7421
7633
  isBackground,
7422
7634
  text,
7635
+ ...source === "doubao" ? { transcriptStatus: "ready" } : {},
7423
7636
  sourceIndex
7424
7637
  });
7425
7638
  sourceIndex += 1;
@@ -7666,7 +7879,21 @@ var RecordingService = class {
7666
7879
  days
7667
7880
  };
7668
7881
  }
7669
- async recordingTranscript(dateStamp, signal) {
7882
+ async recordingTranscript(dateStamp, signal, source = "system") {
7883
+ const sections = await this.recordingTranscriptSections(dateStamp, signal);
7884
+ return source === "doubao" ? sections.doubao : sections.system;
7885
+ }
7886
+ async startRecordingDoubaoBackfill(dateStamp, signal) {
7887
+ const date = this.recordingDayStart(dateStamp).getTime();
7888
+ const session = await this.runtime.requireSession();
7889
+ const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/doubao-asr/backfill-day", { start_at: date }, session, signal);
7890
+ return {
7891
+ queuedChildCount: Math.max(0, Math.trunc(numberValue$5(data.queued_child_count))),
7892
+ inFlightChildCount: Math.max(0, Math.trunc(numberValue$5(data.in_flight_child_count))),
7893
+ missingAudioChildCount: Math.max(0, Math.trunc(numberValue$5(data.missing_audio_child_count)))
7894
+ };
7895
+ }
7896
+ async recordingTranscriptSections(dateStamp, signal) {
7670
7897
  const dayStart = this.recordingDayStart(dateStamp);
7671
7898
  const date = dayStart.getTime();
7672
7899
  const session = await this.runtime.requireSession();
@@ -7678,14 +7905,29 @@ var RecordingService = class {
7678
7905
  let totalDurationMillis = 0;
7679
7906
  for (const rawSession of listValue$5(transcriptResult.value.session_ls)) totalDurationMillis += Math.max(0, numberValue$5(objectValue$1(rawSession).duration));
7680
7907
  const speakerData = speakerResult.status === "fulfilled" ? listValue$5(speakerResult.value.spk_ls) : [];
7681
- const items = projectRecordingTranscripts(transcriptResult.value, speakerData);
7682
- return {
7683
- state: items.length > 0 ? "ready" : "empty",
7684
- items,
7685
- message: items.length > 0 ? "" : "当天无录音",
7686
- identityCoverage: speakerResult.status === "fulfilled" ? "complete" : "partial",
7908
+ const identityCoverage = speakerResult.status === "fulfilled" ? "complete" : "partial";
7909
+ const systemItems = projectRecordingTranscripts(transcriptResult.value, speakerData, "system");
7910
+ const doubaoItems = projectRecordingTranscripts(transcriptResult.value, speakerData, "doubao");
7911
+ const system = {
7912
+ state: systemItems.length > 0 ? "ready" : "empty",
7913
+ items: systemItems,
7914
+ message: systemItems.length > 0 ? "" : "当天无录音",
7915
+ identityCoverage,
7687
7916
  totalDurationMillis
7688
7917
  };
7918
+ const hasProcessing = doubaoItems.some((item) => item.transcriptStatus === "processing");
7919
+ const hasReady = doubaoItems.some((item) => item.transcriptStatus === "ready" || item.transcriptStatus === "silent" || item.transcriptStatus === void 0);
7920
+ const hasFailed = doubaoItems.some((item) => item.transcriptStatus === "failed");
7921
+ return {
7922
+ system,
7923
+ doubao: {
7924
+ state: hasProcessing ? "processing" : hasReady ? "ready" : hasFailed ? "failed" : "empty",
7925
+ items: doubaoItems,
7926
+ message: hasProcessing ? "豆包转写中" : hasReady ? "" : hasFailed ? "豆包转写失败" : "豆包转写尚未生成",
7927
+ identityCoverage,
7928
+ totalDurationMillis
7929
+ }
7930
+ };
7689
7931
  }
7690
7932
  async recordingProjection(dateStamp, kind, signal) {
7691
7933
  const dayStart = this.recordingDayStart(dateStamp);
@@ -7723,6 +7965,7 @@ var RecordingService = class {
7723
7965
  version: 1,
7724
7966
  dateStamp: numberValue$5(raw.dateStamp),
7725
7967
  content: content === "summary" || content === "timeline" ? content : "transcript",
7968
+ ...content === "transcript" && (raw.transcriptSource === "system" || raw.transcriptSource === "doubao") ? { transcriptSource: raw.transcriptSource } : {},
7726
7969
  itemOffset: numberValue$5(raw.itemOffset),
7727
7970
  textOffset: numberValue$5(raw.textOffset),
7728
7971
  fingerprint: stringValue$1(raw.fingerprint),
@@ -7732,25 +7975,31 @@ var RecordingService = class {
7732
7975
  "transcript",
7733
7976
  "summary",
7734
7977
  "timeline"
7735
- ].includes(String(content)) || !Number.isSafeInteger(payload.dateStamp) || payload.dateStamp <= 0 || !Number.isSafeInteger(payload.itemOffset) || payload.itemOffset < 0 || !Number.isSafeInteger(payload.textOffset) || payload.textOffset < 0 || payload.fingerprint === "") throw new ArkmePluginError("recording-cursor-invalid", "录音分页游标无效", false);
7978
+ ].includes(String(content)) || content === "transcript" && raw.transcriptSource !== void 0 && raw.transcriptSource !== "system" && raw.transcriptSource !== "doubao" || !Number.isSafeInteger(payload.dateStamp) || payload.dateStamp <= 0 || !Number.isSafeInteger(payload.itemOffset) || payload.itemOffset < 0 || !Number.isSafeInteger(payload.textOffset) || payload.textOffset < 0 || payload.fingerprint === "") throw new ArkmePluginError("recording-cursor-invalid", "录音分页游标无效", false);
7736
7979
  return payload;
7737
7980
  }
7738
7981
  async recordingDay(dateStamp) {
7739
7982
  const date = this.recordingDayStart(dateStamp).getTime();
7740
7983
  const [transcriptResult, summaryResult, timelineResult] = await Promise.allSettled([
7741
- this.recordingTranscript(date),
7984
+ this.recordingTranscriptSections(date),
7742
7985
  this.recordingProjection(date, "summary"),
7743
7986
  this.recordingProjection(date, "timeline")
7744
7987
  ]);
7745
- const transcript = transcriptResult.status === "fulfilled" ? transcriptResult.value : {
7988
+ const transcript = transcriptResult.status === "fulfilled" ? transcriptResult.value.system : {
7989
+ state: "error",
7990
+ items: [],
7991
+ message: safeFailureMessage$2(transcriptResult.reason)
7992
+ };
7993
+ const doubaoTranscript = transcriptResult.status === "fulfilled" ? transcriptResult.value.doubao : {
7746
7994
  state: "error",
7747
7995
  items: [],
7748
7996
  message: safeFailureMessage$2(transcriptResult.reason)
7749
7997
  };
7750
7998
  return {
7751
7999
  dateStamp: date,
7752
- totalDurationMillis: transcriptResult.status === "fulfilled" ? transcriptResult.value.totalDurationMillis : 0,
8000
+ totalDurationMillis: transcriptResult.status === "fulfilled" ? transcriptResult.value.system.totalDurationMillis : 0,
7753
8001
  transcript,
8002
+ doubaoTranscript,
7754
8003
  summary: summaryResult.status === "fulfilled" ? summaryResult.value : {
7755
8004
  state: "error",
7756
8005
  items: [],
@@ -9183,6 +9432,8 @@ const ARKME_WORLD_IMAGE_REF_TTL_MILLIS = 900 * 1e3;
9183
9432
  const MAX_ARKME_WORLD_IMAGE_REFS = 2048;
9184
9433
  const ARKME_WORLD_RECORD_REF_TTL_MILLIS = 900 * 1e3;
9185
9434
  const MAX_ARKME_WORLD_RECORD_REFS = 4096;
9435
+ const ARKME_VOICEPRINT_PLAY_SCOPE = 2;
9436
+ const ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH = 2048;
9186
9437
  function numberValue(value) {
9187
9438
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
9188
9439
  }
@@ -9194,6 +9445,27 @@ function safeFailureMessage(error) {
9194
9445
  if (error instanceof Error && error.message.trim() !== "") return error.message;
9195
9446
  return "未知错误";
9196
9447
  }
9448
+ function optionalTrimmedText(value, limit) {
9449
+ const normalized = value?.replace(/\s+/g, " ").trim();
9450
+ if (normalized === void 0 || normalized === "") return void 0;
9451
+ return normalized.length <= limit ? normalized : `${normalized.slice(0, limit).trimEnd()}...`;
9452
+ }
9453
+ function buildVoiceprintInviteShareUrl(environment, token) {
9454
+ const url = new URL("/app/voiceprint/invite", environment === "prod" ? "https://jiwo.cc" : "https://jotmo-app.senguo.me");
9455
+ url.hash = `t=${encodeURIComponent(token)}`;
9456
+ return url.toString();
9457
+ }
9458
+ function validVoiceprintInviteToken(token) {
9459
+ if (token === "" || token.length > ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH) return false;
9460
+ let byteLength = 0;
9461
+ for (const char of token) {
9462
+ const codePoint = char.codePointAt(0) ?? 0;
9463
+ if (codePoint <= 31 || codePoint === 127) return false;
9464
+ byteLength += codePoint <= 127 ? 1 : codePoint <= 2047 ? 2 : codePoint <= 65535 ? 3 : 4;
9465
+ if (byteLength > ARKME_VOICEPRINT_INVITE_TOKEN_MAX_LENGTH) return false;
9466
+ }
9467
+ return true;
9468
+ }
9197
9469
  function worldVisibility(checkStatus) {
9198
9470
  if (checkStatus === 1) return "pending_review";
9199
9471
  if (checkStatus === 4) return "rejected";
@@ -9296,6 +9568,27 @@ var WorldService = class {
9296
9568
  ...hasMore ? { nextOffset } : {}
9297
9569
  };
9298
9570
  }
9571
+ async listMyWorldFeed(options = {}) {
9572
+ const session = await this.runtime.requireSession();
9573
+ const limit = Math.min(20, Math.max(1, Math.trunc(options.limit ?? 20)));
9574
+ const offset = Math.max(0, Math.trunc(options.offset ?? 0));
9575
+ const data = await this.runtime.authenticatedWorldPost("/api/v1/public-record/my-list", {
9576
+ limit,
9577
+ offset
9578
+ }, session, options.signal);
9579
+ const rawItems = listValue(data.list);
9580
+ const resolvedAvatars = await this.resolveWorldAvatarUrls(rawItems, session, options.signal);
9581
+ const items = (await Promise.all(rawItems.map((raw) => this.worldFeedItem(raw, session.userId, resolvedAvatars)))).filter((item) => item !== void 0);
9582
+ const total = Math.max(0, Math.trunc(numberValue(data.total)));
9583
+ const nextOffset = offset + rawItems.length;
9584
+ const hasMore = rawItems.length > 0 && nextOffset < total;
9585
+ return {
9586
+ items,
9587
+ total,
9588
+ hasMore,
9589
+ ...hasMore ? { nextOffset } : {}
9590
+ };
9591
+ }
9299
9592
  async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
9300
9593
  const session = await this.runtime.requireSession();
9301
9594
  const normalizedRefs = [...new Set(recordRefs.map((value) => value.trim()).filter((value) => value !== ""))].slice(0, 20);
@@ -9357,6 +9650,28 @@ var WorldService = class {
9357
9650
  chunkEndRune
9358
9651
  };
9359
9652
  }
9653
+ async createWorldVoiceprintInviteIntent(recordRef, signal) {
9654
+ const session = await this.runtime.requireSession();
9655
+ const entry = this.openWorldRecordRef(recordRef, session.userId);
9656
+ const peerUserId = entry.ownerUserId ?? 0;
9657
+ if (!Number.isSafeInteger(peerUserId) || peerUserId <= 0) throw new ArkmePluginError("world-voiceprint-invite-target-missing", "无法确认这条世界动态的作者,请刷新后重试", false);
9658
+ if (peerUserId === session.userId) throw new ArkmePluginError("world-voiceprint-invite-self-invalid", "这是你自己的动态,不需要提醒自己开启声纹", false, 409);
9659
+ const data = await this.runtime.authenticatedAudioPost("/api/v1/audio/voiceprint/invites/create", { scope: ARKME_VOICEPRINT_PLAY_SCOPE }, session, signal, {
9660
+ lane: "write",
9661
+ bypassCache: true
9662
+ });
9663
+ const token = stringValue$1(data.invite_token).trim();
9664
+ const expiresAtMillis = Math.trunc(numberValue(data.expires_at));
9665
+ const scope = Math.trunc(numberValue(data.scope));
9666
+ if (!validVoiceprintInviteToken(token) || expiresAtMillis <= 0 || scope !== ARKME_VOICEPRINT_PLAY_SCOPE) throw new ArkmePluginError("world-voiceprint-invite-contract-invalid", "声纹邀请响应不完整,请稍后重试", true, 502);
9667
+ return {
9668
+ peerUserId,
9669
+ peerDisplayName: optionalTrimmedText(entry.authorName, 64) ?? "这位用户",
9670
+ inviteUrl: buildVoiceprintInviteShareUrl(this.runtime.config.environment, token),
9671
+ expiresAtMillis,
9672
+ ...entry.textPreview === void 0 ? {} : { textPreview: entry.textPreview }
9673
+ };
9674
+ }
9360
9675
  async listWorldInteractions(recordRef, options = {}) {
9361
9676
  const session = await this.runtime.requireSession();
9362
9677
  const root = this.openWorldRecordRef(recordRef, session.userId);
@@ -9567,7 +9882,14 @@ var WorldService = class {
9567
9882
  }
9568
9883
  const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl) : void 0;
9569
9884
  return {
9570
- recordRef: await this.worldRecordRef(viewerUserId, recordUid, ownerUserId),
9885
+ recordRef: await this.worldRecordRef(viewerUserId, recordUid, {
9886
+ ownerUserId,
9887
+ authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
9888
+ ...(() => {
9889
+ const textPreview = optionalTrimmedText(headline || textContent, 80);
9890
+ return textPreview === void 0 ? {} : { textPreview };
9891
+ })()
9892
+ }),
9571
9893
  authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
9572
9894
  ...avatarRef === void 0 ? {} : { avatarRef },
9573
9895
  ...avatarRef === void 0 && avatarFallback !== void 0 ? { avatarFallback } : {},
@@ -9637,7 +9959,14 @@ var WorldService = class {
9637
9959
  const avatarFallback = worldPhoneDefaultAvatar(rawAvatar);
9638
9960
  const avatarRef = this.isTrustedWorldImageUrl(avatarUrl) ? await this.sealWorldImageRef(viewerUserId, avatarUrl) : void 0;
9639
9961
  return {
9640
- interactionRef: await this.worldRecordRef(viewerUserId, recordUid),
9962
+ interactionRef: await this.worldRecordRef(viewerUserId, recordUid, {
9963
+ ownerUserId,
9964
+ authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
9965
+ ...(() => {
9966
+ const textPreview = optionalTrimmedText(textContent, 80);
9967
+ return textPreview === void 0 ? {} : { textPreview };
9968
+ })()
9969
+ }),
9641
9970
  parentRef: await this.worldRecordRef(viewerUserId, parentRecordUid),
9642
9971
  authorName: stringValue$1(item.nick_name ?? item.nickname).trim() || "Arkme用户",
9643
9972
  ...avatarRef === void 0 ? {} : { avatarRef },
@@ -9650,16 +9979,20 @@ var WorldService = class {
9650
9979
  voiceCount
9651
9980
  };
9652
9981
  }
9653
- async worldRecordRef(viewerUserId, recordUid, ownerUserId) {
9982
+ async worldRecordRef(viewerUserId, recordUid, metadata = {}) {
9654
9983
  const recordRef = `arkme-world-record-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`world-record-v1:${String(viewerUserId)}:${recordUid}`).digest("base64url")}`;
9655
9984
  const now = Date.now();
9656
9985
  this.pruneWorldRecordRefs(now);
9657
9986
  const previous = this.worldRecordRefs.get(recordRef);
9658
- const resolvedOwnerUserId = ownerUserId ?? previous?.ownerUserId;
9987
+ const resolvedOwnerUserId = metadata.ownerUserId ?? previous?.ownerUserId;
9988
+ const authorName = optionalTrimmedText(metadata.authorName, 64) ?? previous?.authorName;
9989
+ const textPreview = optionalTrimmedText(metadata.textPreview, 80) ?? previous?.textPreview;
9659
9990
  this.worldRecordRefs.set(recordRef, {
9660
9991
  viewerUserId,
9661
9992
  recordUid,
9662
9993
  ...resolvedOwnerUserId === void 0 || resolvedOwnerUserId <= 0 ? {} : { ownerUserId: resolvedOwnerUserId },
9994
+ ...authorName === void 0 ? {} : { authorName },
9995
+ ...textPreview === void 0 ? {} : { textPreview },
9663
9996
  expiresAtMillis: now + ARKME_WORLD_RECORD_REF_TTL_MILLIS
9664
9997
  });
9665
9998
  return recordRef;
@@ -9741,8 +10074,18 @@ var WorldService = class {
9741
10074
  //#endregion
9742
10075
  //#region src/types.ts
9743
10076
  const ARKME_PROVIDER_CONTRACT_VERSION = 1;
10077
+ const ARKME_DEFAULT_SHARE_WEBSITE = "https://app.arkme.ai";
9744
10078
  //#endregion
9745
10079
  //#region src/arkme-service.ts
10080
+ function worldVoiceprintInviteMessage(input) {
10081
+ return `${input.textPreview === void 0 ? "我想邀请你开启声纹。" : `我看到你在世界里发的「${input.textPreview}」,想邀请你开启声纹。`}\n以后看到你的世界动态时,可以直接听到你的声音。\n${input.inviteUrl}`;
10082
+ }
10083
+ function voiceprintInviteRateLimitMessage(error) {
10084
+ if (!(error instanceof ArkmePluginError)) return void 0;
10085
+ if (error.upstreamStatus !== 429 && error.httpStatus !== 429 && !/\bHTTP\s*429\b/.test(error.message)) return void 0;
10086
+ const seconds = error.retryAfterMillis === void 0 ? 0 : Math.ceil(error.retryAfterMillis / 1e3);
10087
+ return seconds > 0 ? `提醒发送太频繁了,请 ${String(seconds)} 秒后再试。` : "提醒发送太频繁了,稍后再试。";
10088
+ }
9746
10089
  var ArkmeService = class {
9747
10090
  config;
9748
10091
  sessionStore;
@@ -9773,6 +10116,7 @@ var ArkmeService = class {
9773
10116
  interwoven;
9774
10117
  aiPolish;
9775
10118
  chat;
10119
+ contact;
9776
10120
  constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker()) {
9777
10121
  this.config = config;
9778
10122
  this.sessionStore = sessionStore;
@@ -9809,6 +10153,7 @@ var ArkmeService = class {
9809
10153
  this.aiPolish = new GroupAiPolishService(this.runtime, this.source, { sendChatSourceTextRaw: async (...args) => await this.chat.sendChatSourceTextRaw(...args) });
9810
10154
  this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session) => await this.chat.chatTimelineItems(data, session) });
9811
10155
  this.chat = new ChatService(this.runtime, this.source, this.profile, this.media, this.record, this.bot, this.arko, this.aiPolish, this.realtime);
10156
+ this.contact = new ContactService(this.runtime, this.source, this.profile, this.realtime);
9812
10157
  this.auth = new AuthService(this.runtime, this.profile, {
9813
10158
  reconnectChatRealtime: () => {
9814
10159
  this.realtime.reconnect();
@@ -9826,6 +10171,7 @@ var ArkmeService = class {
9826
10171
  this.interwoven.dispose();
9827
10172
  this.world.dispose();
9828
10173
  this.arrangement.dispose();
10174
+ this.contact.dispose();
9829
10175
  }
9830
10176
  startChatRealtime() {
9831
10177
  return this.realtime.startChatRealtime();
@@ -9860,6 +10206,9 @@ var ArkmeService = class {
9860
10206
  async createBot(input, options = {}) {
9861
10207
  return await this.bot.createBot(input, options);
9862
10208
  }
10209
+ async createBotSummary(input, options = {}) {
10210
+ return await this.bot.createBotSummary(input, options);
10211
+ }
9863
10212
  async revealBotSecret(botRef, options = {}) {
9864
10213
  return await this.bot.revealBotSecret(botRef, options);
9865
10214
  }
@@ -9886,7 +10235,8 @@ var ArkmeService = class {
9886
10235
  captchaId: this.config.geetestCaptchaId,
9887
10236
  environment: this.config.environment,
9888
10237
  testLoginEnabled: this.config.environment === "test",
9889
- callAssetBasePath: `${this.config.routePath}/call`
10238
+ callAssetBasePath: `${this.config.routePath}/call`,
10239
+ shareWebsite: this.config.shareWebsite ?? "https://app.arkme.ai"
9890
10240
  };
9891
10241
  }
9892
10242
  providerCapabilities() {
@@ -9918,6 +10268,8 @@ var ArkmeService = class {
9918
10268
  groupMemberAdd: true,
9919
10269
  userCard: true,
9920
10270
  openPrivateChat: true,
10271
+ contactAdd: true,
10272
+ conversationQuickAdd: true,
9921
10273
  groupSettings: true,
9922
10274
  extensionManagement: true,
9923
10275
  extensionMetadataEdit: true,
@@ -9926,9 +10278,12 @@ var ArkmeService = class {
9926
10278
  worldFeed: true,
9927
10279
  worldInteractions: true,
9928
10280
  worldVoiceprintPlayback: true,
10281
+ worldVoiceprintInvite: true,
9929
10282
  arrangements: true,
9930
10283
  myExtensions: true,
9931
10284
  extensionPublish: true,
10285
+ recordingTranscripts: true,
10286
+ recordingDoubaoBackfill: true,
9932
10287
  extensionReviews: true,
9933
10288
  ...this.relatedRecording.isEnabled() ? { relatedRecordings: true } : {}
9934
10289
  },
@@ -9974,6 +10329,7 @@ var ArkmeService = class {
9974
10329
  return await this.outgoingCall.releaseOutgoingCall(callRequestId);
9975
10330
  }
9976
10331
  dispose() {
10332
+ this.contact.dispose();
9977
10333
  this.realtime.dispose();
9978
10334
  this.arko.dispose();
9979
10335
  this.auth.dispose();
@@ -9994,6 +10350,12 @@ var ArkmeService = class {
9994
10350
  async cachedProfile() {
9995
10351
  return await this.profile.cachedProfile();
9996
10352
  }
10353
+ async searchContact(identifier, options = {}) {
10354
+ return await this.contact.search(identifier, options);
10355
+ }
10356
+ async addContact(contactRef, options = {}) {
10357
+ return await this.contact.add(contactRef, options);
10358
+ }
9997
10359
  async extensionAuthors(userIds, signal) {
9998
10360
  return await this.extensionReview.extensionAuthors(userIds, signal);
9999
10361
  }
@@ -10008,8 +10370,12 @@ var ArkmeService = class {
10008
10370
  return await this.recording.recordingCalendar(fromStamp, toStamp, signal);
10009
10371
  }
10010
10372
  /** Read-only Audio capability shared by the built-in UI and Arkme recording tools. */
10011
- async recordingTranscript(dateStamp, signal) {
10012
- return await this.recording.recordingTranscript(dateStamp, signal);
10373
+ async recordingTranscript(dateStamp, signal, source = "system") {
10374
+ return await this.recording.recordingTranscript(dateStamp, signal, source);
10375
+ }
10376
+ /** Queue Doubao backfill only after an explicit UI, SDK, or Tool request. */
10377
+ async startRecordingDoubaoBackfill(dateStamp, signal) {
10378
+ return await this.recording.startRecordingDoubaoBackfill(dateStamp, signal);
10013
10379
  }
10014
10380
  /** Read-only Audio capability shared by the built-in UI and Arkme recording tools. */
10015
10381
  async recordingProjection(dateStamp, kind, signal) {
@@ -10141,6 +10507,9 @@ var ArkmeService = class {
10141
10507
  async addGroupMembers(sourceRef, candidateRefs, signal) {
10142
10508
  return await this.group.addGroupMembers(sourceRef, candidateRefs, signal);
10143
10509
  }
10510
+ async createGroup(title, clientMutationId, options = {}) {
10511
+ return await this.group.createGroup(title, clientMutationId, options);
10512
+ }
10144
10513
  async groupSettings(sourceRef, signal) {
10145
10514
  return await this.group.groupSettings(sourceRef, signal);
10146
10515
  }
@@ -10353,12 +10722,41 @@ var ArkmeService = class {
10353
10722
  async listWorldFeed(options = {}) {
10354
10723
  return await this.world.listWorldFeed(options);
10355
10724
  }
10725
+ /** Build the signed-in account's World projection without exposing owner IDs. */
10726
+ async listMyWorldFeed(options = {}) {
10727
+ return await this.world.listMyWorldFeed(options);
10728
+ }
10356
10729
  async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
10357
10730
  return await this.world.worldVoiceprintPlaybackAvailability(recordRefs, signal);
10358
10731
  }
10359
10732
  async generateWorldVoiceprintPlayback(input) {
10360
10733
  return await this.world.generateWorldVoiceprintPlayback(input);
10361
10734
  }
10735
+ async inviteWorldVoiceprint(recordRef, signal) {
10736
+ try {
10737
+ const intent = await this.world.createWorldVoiceprintInviteIntent(recordRef, signal);
10738
+ const privateChat = await this.chat.openPrivateChatFromUser(intent.peerUserId, {
10739
+ displayName: intent.peerDisplayName,
10740
+ ...signal === void 0 ? {} : { signal }
10741
+ });
10742
+ const sent = await this.chat.sendSourceText(privateChat.source.sourceRef, worldVoiceprintInviteMessage(intent), signal === void 0 ? {} : { signal });
10743
+ if (sent.localState !== "synced") throw new ArkmePluginError("world-voiceprint-invite-send-pending", sent.error ?? "声纹邀请已进入发送队列,请稍后在私聊中确认", true);
10744
+ return {
10745
+ sent: true,
10746
+ peerDisplayName: intent.peerDisplayName,
10747
+ messageItemUid: sent.itemUid,
10748
+ expiresAtMillis: intent.expiresAtMillis
10749
+ };
10750
+ } catch (error) {
10751
+ const message = voiceprintInviteRateLimitMessage(error);
10752
+ if (message !== void 0) throw new ArkmePluginError("world-voiceprint-invite-rate-limited", message, true, 429, {
10753
+ cause: error,
10754
+ upstreamStatus: 429,
10755
+ ...!(error instanceof ArkmePluginError) || error.retryAfterMillis === void 0 ? {} : { retryAfterMillis: error.retryAfterMillis }
10756
+ });
10757
+ throw error;
10758
+ }
10759
+ }
10362
10760
  /** Read the authenticated comment/reply tree behind one account-bound World reference. */
10363
10761
  async listWorldInteractions(recordRef, options = {}) {
10364
10762
  return await this.world.listWorldInteractions(recordRef, options);
@@ -10527,6 +10925,7 @@ function canonicalExtensionSignatureMessage(input) {
10527
10925
  }), "utf8");
10528
10926
  }
10529
10927
  const ARKME_BUNDLE_ARTIFACT_KIND = "dsh-bundle-tgz";
10928
+ const ARKME_NATIVE_BUNDLE_ARTIFACT_KIND = "dsh-native-package-tgz";
10530
10929
  const ARKME_BUNDLE_MAX_BYTES = 100 * 1024 * 1024;
10531
10930
  const TAR_BLOCK_BYTES = 512;
10532
10931
  const MAX_ARCHIVE_FILES = 1024;
@@ -10573,9 +10972,22 @@ function writeOctal(header, offset, length, value) {
10573
10972
  header.write(octal.slice(-(length - 1)), offset, length - 1, "ascii");
10574
10973
  header[offset + length - 1] = 0;
10575
10974
  }
10975
+ function splitTarPath(path) {
10976
+ const direct = Buffer.from(path, "utf8");
10977
+ if (direct.byteLength <= 100) return { name: direct };
10978
+ const separators = [...path.matchAll(/\//g)].map((match) => match.index).reverse();
10979
+ for (const separator of separators) {
10980
+ const prefix = Buffer.from(path.slice(0, separator), "utf8");
10981
+ const name = Buffer.from(path.slice(separator + 1), "utf8");
10982
+ if (prefix.byteLength <= 155 && name.byteLength <= 100) return {
10983
+ name,
10984
+ prefix
10985
+ };
10986
+ }
10987
+ throw new ArkmeBundleArtifactError("bundle-path-too-long", `Bundle 路径过长:${path}`);
10988
+ }
10576
10989
  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}`);
10990
+ const { name, prefix } = splitTarPath(path);
10579
10991
  const header = Buffer.alloc(TAR_BLOCK_BYTES);
10580
10992
  name.copy(header, 0);
10581
10993
  writeOctal(header, 100, 8, 420);
@@ -10587,6 +10999,7 @@ function tarHeader(path, size) {
10587
10999
  header[156] = "0".charCodeAt(0);
10588
11000
  header.write("ustar\0", 257, 6, "ascii");
10589
11001
  header.write("00", 263, 2, "ascii");
11002
+ prefix?.copy(header, 345);
10590
11003
  writeOctal(header, 148, 8, [...header].reduce((sum, byte) => sum + byte, 0));
10591
11004
  return header;
10592
11005
  }
@@ -10621,7 +11034,9 @@ function readTar(tar) {
10621
11034
  while (offset + TAR_BLOCK_BYTES <= tar.byteLength) {
10622
11035
  const header = tar.subarray(offset, offset + TAR_BLOCK_BYTES);
10623
11036
  if (header.every((byte) => byte === 0)) break;
10624
- const path = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
11037
+ const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
11038
+ const prefix = header.subarray(345, 500).toString("utf8").replace(/\0.*$/, "");
11039
+ const path = prefix === "" ? name : `${prefix}/${name}`;
10625
11040
  validateArchivePath(path);
10626
11041
  if (files.has(path)) throw new ArkmeBundleArtifactError("bundle-duplicate-path", `Bundle 包含重复路径:${path}`);
10627
11042
  const type = header[156];
@@ -10706,6 +11121,105 @@ function validatePatch(raw, packageName, executionModel) {
10706
11121
  if (ids.size === 0) throw new ArkmeBundleArtifactError("bundle-patch-empty", "Bundle patch 至少需要一条 row");
10707
11122
  return [...ids].sort();
10708
11123
  }
11124
+ const NATIVE_LIFECYCLE_SCRIPTS = /* @__PURE__ */ new Set([
11125
+ "preinstall",
11126
+ "install",
11127
+ "postinstall",
11128
+ "prepare",
11129
+ "prepublish",
11130
+ "prepublishOnly",
11131
+ "prepack",
11132
+ "postpack"
11133
+ ]);
11134
+ function nonEmptyManifestValue(value) {
11135
+ if (value === void 0 || value === null) return false;
11136
+ if (typeof value === "string") return value.trim() !== "";
11137
+ if (Array.isArray(value)) return value.length > 0;
11138
+ if (typeof value === "object") return Object.keys(value).length > 0;
11139
+ if (typeof value === "boolean") return value;
11140
+ return true;
11141
+ }
11142
+ function inspectNativePatch(raw, packageName) {
11143
+ const document = parseDocument(raw.toString("utf8"), { uniqueKeys: true });
11144
+ if (document.errors.length > 0) throw new ArkmeBundleArtifactError("bundle-patch-invalid", "Bundle patch YAML 格式无效");
11145
+ const value = document.toJS();
11146
+ if (!Array.isArray(value)) throw new ArkmeBundleArtifactError("bundle-patch-invalid", "Bundle patch 必须是操作数组");
11147
+ const patchIds = /* @__PURE__ */ new Set();
11148
+ const capabilities = /* @__PURE__ */ new Set();
11149
+ for (const operation of value) {
11150
+ if (operation === null || typeof operation !== "object" || Array.isArray(operation)) {
11151
+ capabilities.add("profile_patch_override");
11152
+ continue;
11153
+ }
11154
+ const record = operation;
11155
+ if (!(Object.keys(record).length === 1 && Array.isArray(record.insert))) {
11156
+ capabilities.add("profile_patch_override");
11157
+ if (typeof record.id === "string" && record.id.trim() !== "") patchIds.add(record.id.trim());
11158
+ continue;
11159
+ }
11160
+ for (const row of record.insert) {
11161
+ if (row === null || typeof row !== "object" || Array.isArray(row)) continue;
11162
+ const item = row;
11163
+ if (typeof item.id === "string" && item.id.trim() !== "") patchIds.add(item.id.trim());
11164
+ if (typeof item.name === "string" && item.name !== packageName && !item.name.startsWith(`${packageName}/`)) capabilities.add("external_package_reference");
11165
+ }
11166
+ }
11167
+ return {
11168
+ patchIds: [...patchIds].sort(),
11169
+ capabilities: [...capabilities].sort()
11170
+ };
11171
+ }
11172
+ function inspectNativeManifest(manifest, files) {
11173
+ if (typeof manifest.name !== "string" || manifest.name.length > 214 || !PACKAGE_NAME.test(manifest.name) || manifest.name.startsWith("@deepseek-ai/") || manifest.name === "@senguoyun/dsh-arkme" || manifest.name.startsWith("@arkme-local/")) throw new ArkmeBundleArtifactError("bundle-package-name-invalid", "Bundle package name 无效或属于保留集合");
11174
+ if (typeof manifest.version !== "string" || !SEMVER.test(manifest.version)) throw new ArkmeBundleArtifactError("bundle-version-invalid", "Bundle version 必须是严格 SemVer");
11175
+ if (manifest.dsh?.arkme !== void 0 && manifest.dsh.arkme !== null) throw new ArkmeBundleArtifactError("bundle-execution-model-invalid", "原生 DSH V3 Bundle 不允许声明 Arkme sandbox marker");
11176
+ const patch = manifest.dsh?.bundle?.patch;
11177
+ if (typeof patch !== "string" || patch.trim() === "" || patch.includes("\\") || patch.startsWith("/")) throw new ArkmeBundleArtifactError("bundle-patch-missing", "Bundle 必须声明包内 dsh.bundle.patch");
11178
+ const patchPath = `package/${posix.normalize(patch.replace(/^\.\//, ""))}`;
11179
+ if (!files.has(patchPath) || patchPath.includes("/../")) throw new ArkmeBundleArtifactError("bundle-patch-missing", "Bundle patch 不在包内");
11180
+ const capabilities = /* @__PURE__ */ new Set();
11181
+ if (nonEmptyManifestValue(manifest.dependencies)) capabilities.add("runtime_dependencies");
11182
+ if (nonEmptyManifestValue(manifest.optionalDependencies)) capabilities.add("optional_dependencies");
11183
+ if (nonEmptyManifestValue(manifest.bundledDependencies) || nonEmptyManifestValue(manifest.bundleDependencies)) capabilities.add("bundled_dependencies");
11184
+ for (const name of Object.keys(manifest.peerDependencies ?? {})) if (!name.startsWith("@deepseek-ai/") && name !== "@senguoyun/dsh-arkme" && name !== "react" && name !== "react-dom") {
11185
+ capabilities.add("peer_dependencies");
11186
+ break;
11187
+ }
11188
+ if (Object.entries(manifest.scripts ?? {}).some(([name, value]) => NATIVE_LIFECYCLE_SCRIPTS.has(name) && nonEmptyManifestValue(value))) capabilities.add("lifecycle_scripts");
11189
+ if (nonEmptyManifestValue(manifest.bin)) capabilities.add("bin");
11190
+ if ([...files.keys()].some((name) => name.toLowerCase().endsWith(".node"))) capabilities.add("native_addon");
11191
+ return {
11192
+ patchPath,
11193
+ capabilities: [...capabilities].sort()
11194
+ };
11195
+ }
11196
+ function inspectNativeBundleArtifactV3(bytes) {
11197
+ assertArchiveSize(bytes.byteLength, "Bundle");
11198
+ let tar;
11199
+ try {
11200
+ tar = gunzipSync(bytes, { maxOutputLength: 105907200 });
11201
+ } catch {
11202
+ throw new ArkmeBundleArtifactError("bundle-gzip-invalid", "Bundle 不是有效 gzip");
11203
+ }
11204
+ const files = readTar(tar);
11205
+ const packageJSON = files.get("package/package.json");
11206
+ if (packageJSON === void 0) throw new ArkmeBundleArtifactError("bundle-package-json-missing", "Bundle 缺少 package.json");
11207
+ const manifest = readManifest(packageJSON);
11208
+ const inspected = inspectNativeManifest(manifest, files);
11209
+ const patch = inspectNativePatch(files.get(inspected.patchPath), manifest.name);
11210
+ const nativeCapabilities = [.../* @__PURE__ */ new Set([...inspected.capabilities, ...patch.capabilities])].sort();
11211
+ return {
11212
+ bytes: Buffer.from(bytes),
11213
+ bundleSha256: bundleSha256(bytes),
11214
+ packageJsonSha256: bundleSha256(packageJSON),
11215
+ packageName: manifest.name,
11216
+ version: manifest.version,
11217
+ executionModel: "dsh-native",
11218
+ nativeCapabilities,
11219
+ files,
11220
+ patchIds: patch.patchIds
11221
+ };
11222
+ }
10709
11223
  function inspectBundleArtifact(bytes) {
10710
11224
  assertArchiveSize(bytes.byteLength, "Bundle");
10711
11225
  let tar;
@@ -10736,29 +11250,114 @@ function pathInside(root, target) {
10736
11250
  const fromRoot = relative(root, target);
10737
11251
  return fromRoot === "" || !fromRoot.startsWith(`..${sep}`) && fromRoot !== ".." && !isAbsolute(fromRoot);
10738
11252
  }
10739
- function collectPackageFiles(root, manifest) {
11253
+ function globPattern(pattern) {
11254
+ let expression = "^";
11255
+ for (let index = 0; index < pattern.length; index += 1) {
11256
+ const character = pattern[index];
11257
+ if (character === "*") if (pattern[index + 1] === "*") {
11258
+ index += 1;
11259
+ if (pattern[index + 1] === "/") {
11260
+ index += 1;
11261
+ expression += "(?:.*/)?";
11262
+ } else expression += ".*";
11263
+ } else expression += "[^/]*";
11264
+ else if (character === "?") expression += "[^/]";
11265
+ else expression += character.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
11266
+ }
11267
+ return new RegExp(`${expression}$`);
11268
+ }
11269
+ function collectPackageFiles(root, manifest, nativeV3 = false) {
10740
11270
  const canonicalRoot = realpathSync(root);
10741
11271
  const selected = /* @__PURE__ */ new Map();
10742
- const add = (absolute) => {
11272
+ const ignoredNativeRootEntries = /* @__PURE__ */ new Set([
11273
+ ".git",
11274
+ ".hg",
11275
+ ".svn",
11276
+ "node_modules"
11277
+ ]);
11278
+ const ignoredNativeFiles = /* @__PURE__ */ new Set([
11279
+ ".npmrc",
11280
+ ".yarnrc",
11281
+ ".yarnrc.yml",
11282
+ ".pnpmfile.cjs"
11283
+ ]);
11284
+ const add = (absolute, allowIgnore = false) => {
10743
11285
  const stat = lstatSync(absolute);
10744
- if (stat.isSymbolicLink()) throw new ArkmeBundleArtifactError("bundle-link-forbidden", "Bundle source 不允许符号链接");
11286
+ const rel = relative(canonicalRoot, absolute).split(sep).join("/");
11287
+ const first = rel.split("/")[0] ?? "";
11288
+ const basename = rel.split("/").at(-1) ?? "";
11289
+ if (allowIgnore && (ignoredNativeRootEntries.has(first) || ignoredNativeFiles.has(basename) || basename === ".env" || basename.startsWith(".env."))) return;
11290
+ if (stat.isSymbolicLink()) {
11291
+ if (allowIgnore) return;
11292
+ throw new ArkmeBundleArtifactError("bundle-link-forbidden", "Bundle source 不允许符号链接");
11293
+ }
10745
11294
  const canonical = realpathSync(absolute);
10746
11295
  if (!pathInside(canonicalRoot, canonical)) throw new ArkmeBundleArtifactError("bundle-path-invalid", "Bundle source 逃逸 package root");
10747
11296
  if (stat.isDirectory()) {
10748
- for (const child of readdirSync(canonical).sort()) add(join(canonical, child));
11297
+ for (const child of readdirSync(canonical).sort()) add(join(canonical, child), allowIgnore);
10749
11298
  return;
10750
11299
  }
10751
11300
  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));
11301
+ const canonicalRel = relative(canonicalRoot, canonical).split(sep).join("/");
11302
+ selected.set(`package/${canonicalRel}`, readFileSync(canonical));
11303
+ };
11304
+ const addNativePackageExtras = () => {
11305
+ if (!nativeV3) return;
11306
+ for (const child of readdirSync(canonicalRoot).sort()) if (/^(?:readme|licen[cs]e|copying|notice)(?:\.|$)/i.test(child)) add(join(canonicalRoot, child), true);
11307
+ const paths = /* @__PURE__ */ new Set();
11308
+ if (typeof manifest.main === "string") paths.add(manifest.main);
11309
+ const bin = manifest.bin;
11310
+ if (typeof bin === "string") paths.add(bin);
11311
+ else if (bin !== null && typeof bin === "object" && !Array.isArray(bin)) {
11312
+ for (const value of Object.values(bin)) if (typeof value === "string") paths.add(value);
11313
+ }
11314
+ const patch = manifest.dsh?.bundle?.patch;
11315
+ if (typeof patch === "string") paths.add(patch);
11316
+ for (const entry of paths) {
11317
+ const target = resolve(canonicalRoot, entry);
11318
+ if (pathInside(canonicalRoot, target) && existsSync(target)) add(target, true);
11319
+ }
10754
11320
  };
10755
11321
  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 或反斜杠");
11322
+ const entries = manifest.files ?? [];
11323
+ if (entries.length === 0) {
11324
+ if (!nativeV3) throw new ArkmeBundleArtifactError("bundle-files-missing", "Bundle 必须显式声明 files");
11325
+ for (const child of readdirSync(canonicalRoot).sort()) if (child !== "package.json") add(join(canonicalRoot, child), true);
11326
+ addNativePackageExtras();
11327
+ return selected;
11328
+ }
11329
+ let candidates;
11330
+ const listCandidates = () => {
11331
+ if (candidates !== void 0) return candidates;
11332
+ candidates = [];
11333
+ const walk = (directory) => {
11334
+ for (const child of readdirSync(directory).sort()) {
11335
+ const absolute = join(directory, child);
11336
+ const stat = lstatSync(absolute);
11337
+ if (stat.isSymbolicLink()) continue;
11338
+ if (stat.isDirectory()) walk(absolute);
11339
+ else if (stat.isFile()) candidates.push(relative(canonicalRoot, absolute).split(sep).join("/"));
11340
+ }
11341
+ };
11342
+ walk(canonicalRoot);
11343
+ return candidates;
11344
+ };
11345
+ for (const entry of entries) {
11346
+ if (entry.includes("\\") || entry.startsWith("/") || entry.split("/").includes("..")) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
11347
+ const hasGlob = entry.includes("*") || entry.includes("?") || entry.includes("[");
11348
+ if (hasGlob && !nativeV3) throw new ArkmeBundleArtifactError("bundle-files-invalid", "Bundle files 暂不接受 glob 或反斜杠");
11349
+ if (hasGlob) {
11350
+ const matcher = globPattern(entry.replace(/^\.\//, ""));
11351
+ const matches = listCandidates().filter((candidate) => matcher.test(candidate));
11352
+ if (matches.length === 0) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
11353
+ for (const match of matches) add(resolve(canonicalRoot, match), true);
11354
+ continue;
11355
+ }
10758
11356
  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);
11357
+ if (!pathInside(canonicalRoot, target) || !existsSync(target) || !statSync(target).isFile() && !statSync(target).isDirectory()) throw new ArkmeBundleArtifactError("bundle-files-invalid", `Bundle files 路径无效:${entry}`);
11358
+ add(target, nativeV3);
10761
11359
  }
11360
+ addNativePackageExtras();
10762
11361
  return selected;
10763
11362
  }
10764
11363
  function packBundleFiles(files) {
@@ -10772,14 +11371,25 @@ function packBundleFiles(files) {
10772
11371
  }
10773
11372
  };
10774
11373
  }
10775
- function packLocalBundleDirectory(directory) {
10776
- return packBundleFiles(collectPackageFiles(directory, readManifest(readFileSync(join(directory, "package.json")))));
11374
+ function packNativeBundleFiles(files) {
11375
+ const bytes = gzipSync(createTar(files), { level: 9 });
11376
+ assertArchiveSize(bytes.byteLength, "Bundle");
11377
+ return {
11378
+ bundle: inspectNativeBundleArtifactV3(bytes),
11379
+ source: {
11380
+ bytes: Buffer.from(bytes),
11381
+ sourceSha256: bundleSha256(bytes)
11382
+ }
11383
+ };
10777
11384
  }
10778
- function readLocalBundleTarball(path) {
11385
+ function packLocalNativeBundleDirectoryV3(directory) {
11386
+ return packNativeBundleFiles(collectPackageFiles(directory, readManifest(readFileSync(join(directory, "package.json"))), true));
11387
+ }
11388
+ function readNativeBundleTarballV3(path) {
10779
11389
  if (!lstatSync(path).isFile()) throw new ArkmeBundleArtifactError("bundle-tarball-invalid", "本地 Bundle tgz 必须是普通文件");
10780
11390
  const bytes = readFileSync(path);
10781
11391
  return {
10782
- bundle: inspectBundleArtifact(bytes),
11392
+ bundle: inspectNativeBundleArtifactV3(bytes),
10783
11393
  source: {
10784
11394
  bytes: Buffer.from(bytes),
10785
11395
  sourceSha256: bundleSha256(bytes)
@@ -10854,6 +11464,39 @@ function verifyBundleResolutionSignature(resolution, trustedKeys) {
10854
11464
  }
10855
11465
  if (!valid) throw new ArkmePluginError("extension-signature-invalid", "扩展 Bundle 平台签名验证失败", false, 403);
10856
11466
  }
11467
+ function canonicalNativeBundleSignatureMessage(input) {
11468
+ return Buffer.from(JSON.stringify({
11469
+ format_version: input.artifact_contract_version,
11470
+ artifact_kind: input.artifact_kind,
11471
+ extension_id: input.extension_id,
11472
+ package_name: input.package_name,
11473
+ version: input.version,
11474
+ execution_model: input.execution_model,
11475
+ bundle_sha256: input.bundle_sha256,
11476
+ package_json_sha256: input.package_json_sha256,
11477
+ source_sha256: input.source_sha256,
11478
+ ...input.native_capabilities.length === 0 ? {} : { native_capabilities: input.native_capabilities },
11479
+ published_at: input.published_at,
11480
+ signing_key_id: input.signing_key_id
11481
+ }), "utf8");
11482
+ }
11483
+ function verifyNativeBundleResolutionSignature(resolution, trustedKeys) {
11484
+ const capabilities = resolution.native_capabilities;
11485
+ if (resolution.artifact_contract_version !== 3 || resolution.artifact_kind !== "dsh-native-package-tgz" || resolution.package_name.trim() === "" || resolution.execution_model !== "dsh-native" || ![
11486
+ resolution.bundle_sha256,
11487
+ resolution.package_json_sha256,
11488
+ resolution.source_sha256
11489
+ ].every((value) => /^[a-f0-9]{64}$/.test(value)) || !Array.isArray(capabilities) || capabilities.some((value) => typeof value !== "string" || value.trim() === "") || capabilities.some((value, index) => index > 0 && capabilities[index - 1] >= value) || !Number.isSafeInteger(resolution.published_at) || resolution.published_at <= 0 || resolution.signature.trim() === "") throw new ArkmePluginError("extension-signature-envelope-invalid", "原生 DSH V3 签名 envelope 字段无效", false, 502);
11490
+ const key = trustedKeys.get(resolution.signing_key_id);
11491
+ if (key === void 0) throw new ArkmePluginError("extension-signing-key-untrusted", `不信任扩展签名密钥 ${resolution.signing_key_id}`, false, 403);
11492
+ let valid = false;
11493
+ try {
11494
+ valid = verify(null, canonicalNativeBundleSignatureMessage(resolution), publicKey(key), Buffer.from(resolution.signature, "base64"));
11495
+ } catch (error) {
11496
+ throw new ArkmePluginError("extension-signature-invalid", "原生 DSH V3 签名格式无效", false, 403, { cause: error });
11497
+ }
11498
+ if (!valid) throw new ArkmePluginError("extension-signature-invalid", "原生 DSH V3 平台签名验证失败", false, 403);
11499
+ }
10857
11500
  //#endregion
10858
11501
  //#region src/extensions/persistent-client-bundle.ts
10859
11502
  /** This function is serialized into each generated browser bundle. It must stay closure-free. */
@@ -11513,4 +12156,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
11513
12156
  return jsonValue(await handler(jsonValue(args)));
11514
12157
  }
11515
12158
  //#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 };
12159
+ export { unpackArkmeExtension as A, ArkmeOutgoingCallError as B, packBundleFiles as C, assertExtensionArtifactSize as D, renderArkmeSandboxHostEntry as E, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as F, ArkmeService as I, ARKME_DEFAULT_SHARE_WEBSITE as L, ARKME_EXTENSION_ICON_MAX_BYTES as M, ARKME_EXTENSION_MAX_BYTES as N, canonicalExtensionJson as O, ARKME_EXTENSION_PREVIEW_MAX_BYTES as P, ARKME_PROVIDER_CONTRACT_VERSION as R, inspectNativeBundleArtifactV3 as S, readNativeBundleTarballV3 as T, ArkmeBundleArtifactError as _, persistentArkmeExtensionActive as a, canonicalBundleJson as b, readPersistentExtensionActivation as c, verifyBundleResolutionSignature as d, verifyExtensionResolutionSignature as f, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as g, ARKME_BUNDLE_MAX_BYTES as h, invokePersistentArkmeExtension as i, ARKME_EXTENSION_FORMAT as j, sha256Hex as k, writePersistentExtensionActivation as l, ARKME_BUNDLE_ARTIFACT_KIND as m, applyPersistentArkmeHostExtension as n, persistentArkmeExtensionRuntimeState as o, verifyNativeBundleResolutionSignature as p, deactivatePersistentArkmeExtension as r, materializePersistentExtensionBundle as s, activatePersistentArkmeExtension as t, renderArkmeBundleClientBundle as u, arkmeSandboxEntryId as v, packLocalNativeBundleDirectoryV3 as w, inspectBundleArtifact as x, bundleSha256 as y, ArkmePluginError as z };