@senguoyun/dsh-arkme 0.1.28 → 0.1.30

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 (91) hide show
  1. package/lib/client.js +8151 -4250
  2. package/lib/client.js.map +1 -1
  3. package/lib/extension-profile-restart-helper.js +1 -1
  4. package/lib/index.js +547 -23
  5. package/lib/persistent-extension.js +1 -1
  6. package/lib/{persistent-runtime-CaPUsLwg.js → persistent-runtime-D-NwFt9z.js} +1631 -397
  7. package/lib/{plugin-updater-helper-Ddppbids.js → plugin-updater-helper-BByro9U_.js} +1 -1
  8. package/lib/{plugin-updater-helper-Lmz7W2ph.js → plugin-updater-helper-BByw0ZyG.js} +171 -13
  9. package/lib/plugin-updater-helper.js +1 -1
  10. package/lib/sdk.js +44 -0
  11. package/lib/types/arkme-service.d.ts +36 -4
  12. package/lib/types/arkme-service.d.ts.map +1 -1
  13. package/lib/types/billing-gateway.d.ts +28 -0
  14. package/lib/types/billing-gateway.d.ts.map +1 -0
  15. package/lib/types/client/ArkmeBillingSettings.d.ts +52 -0
  16. package/lib/types/client/ArkmeBillingSettings.d.ts.map +1 -0
  17. package/lib/types/client/ArkmeCallSurface.d.ts.map +1 -1
  18. package/lib/types/client/ArkmeLogin.d.ts +3 -1
  19. package/lib/types/client/ArkmeLogin.d.ts.map +1 -1
  20. package/lib/types/client/ArkmeNoteDetails.d.ts +3 -1
  21. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  22. package/lib/types/client/ArkmePersistentShell.d.ts +3 -0
  23. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  24. package/lib/types/client/ArkmeProductNavigation.d.ts +2 -1
  25. package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
  26. package/lib/types/client/ArkmeRichContent.d.ts +15 -1
  27. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  28. package/lib/types/client/ArkmeSettingsSurface.d.ts +18 -0
  29. package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
  30. package/lib/types/client/ArkmeSidebar.d.ts +17 -3
  31. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  32. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +9 -2
  33. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  34. package/lib/types/client/ArkmeWebLoginOverlay.d.ts +6 -0
  35. package/lib/types/client/ArkmeWebLoginOverlay.d.ts.map +1 -0
  36. package/lib/types/client/DeepSeekHarnessSurface.d.ts +7 -4
  37. package/lib/types/client/DeepSeekHarnessSurface.d.ts.map +1 -1
  38. package/lib/types/client/api.d.ts +1 -1
  39. package/lib/types/client/api.d.ts.map +1 -1
  40. package/lib/types/client/arkme-auth-flow.d.ts +5 -0
  41. package/lib/types/client/arkme-auth-flow.d.ts.map +1 -1
  42. package/lib/types/client/arkme-billing.d.ts +37 -0
  43. package/lib/types/client/arkme-billing.d.ts.map +1 -0
  44. package/lib/types/client/arkme-login-locales.d.ts +5 -0
  45. package/lib/types/client/arkme-login-locales.d.ts.map +1 -1
  46. package/lib/types/client/chat-directory-store.d.ts.map +1 -1
  47. package/lib/types/client/index.d.ts +1 -0
  48. package/lib/types/client/index.d.ts.map +1 -1
  49. package/lib/types/client/ui-controller.d.ts +4 -0
  50. package/lib/types/client/ui-controller.d.ts.map +1 -1
  51. package/lib/types/extensions/manager.d.ts +4 -0
  52. package/lib/types/extensions/manager.d.ts.map +1 -1
  53. package/lib/types/extensions/persistent-bundle.d.ts +14 -0
  54. package/lib/types/extensions/persistent-bundle.d.ts.map +1 -1
  55. package/lib/types/extensions/persistent-client-bundle.d.ts +1 -1
  56. package/lib/types/extensions/persistent-client-bundle.d.ts.map +1 -1
  57. package/lib/types/extensions/persistent-runtime.d.ts.map +1 -1
  58. package/lib/types/extensions/profile-installer.d.ts.map +1 -1
  59. package/lib/types/harness-embed-contract.d.ts +2 -0
  60. package/lib/types/harness-embed-contract.d.ts.map +1 -0
  61. package/lib/types/harness-embed-route.d.ts +32 -0
  62. package/lib/types/harness-embed-route.d.ts.map +1 -0
  63. package/lib/types/host-api.d.ts.map +1 -1
  64. package/lib/types/index.d.ts.map +1 -1
  65. package/lib/types/managed-ai/adapter.d.ts +20 -0
  66. package/lib/types/managed-ai/adapter.d.ts.map +1 -0
  67. package/lib/types/managed-ai/credential.d.ts +11 -0
  68. package/lib/types/managed-ai/credential.d.ts.map +1 -0
  69. package/lib/types/plugin-update-lifecycle-log.d.ts +17 -0
  70. package/lib/types/plugin-update-lifecycle-log.d.ts.map +1 -0
  71. package/lib/types/plugin-update.d.ts +3 -0
  72. package/lib/types/plugin-update.d.ts.map +1 -1
  73. package/lib/types/plugin-updater-helper.d.ts +1 -2
  74. package/lib/types/plugin-updater-helper.d.ts.map +1 -1
  75. package/lib/types/profile-plugin-entry.d.ts +6 -0
  76. package/lib/types/profile-plugin-entry.d.ts.map +1 -0
  77. package/lib/types/sdk/index.d.ts +17 -2
  78. package/lib/types/sdk/index.d.ts.map +1 -1
  79. package/lib/types/services/auth-service.d.ts +10 -0
  80. package/lib/types/services/auth-service.d.ts.map +1 -1
  81. package/lib/types/services/chat-service.d.ts +37 -4
  82. package/lib/types/services/chat-service.d.ts.map +1 -1
  83. package/lib/types/tools/business/conversation/copy-link-extend.d.ts +2 -0
  84. package/lib/types/tools/business/conversation/copy-link-extend.d.ts.map +1 -0
  85. package/lib/types/tools/business/index.d.ts.map +1 -1
  86. package/lib/types/tools/ports/conversations.d.ts +4 -1
  87. package/lib/types/tools/ports/conversations.d.ts.map +1 -1
  88. package/lib/types/tools/prompts/business.d.ts.map +1 -1
  89. package/lib/types/types.d.ts +137 -1
  90. package/lib/types/types.d.ts.map +1 -1
  91. package/package.json +12 -6
@@ -1,6 +1,6 @@
1
1
  import { chmodSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
2
2
  import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicKey, randomBytes, randomUUID, timingSafeEqual, verify } from "node:crypto";
3
- import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
3
+ import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { open } from "node:fs/promises";
6
6
  import { pinyin } from "pinyin-pro";
@@ -10,6 +10,37 @@ import { createContext, runInContext } from "node:vm";
10
10
  import { Context } from "@deepseek-ai/cordis";
11
11
  import { defineTool } from "@deepseek-ai/dsh-tools";
12
12
  import { parseDocument } from "yaml";
13
+ //#region src/secret-value.ts
14
+ var SecretValue = class {
15
+ #value;
16
+ constructor(value) {
17
+ this.#value = value;
18
+ }
19
+ reveal() {
20
+ return this.#value;
21
+ }
22
+ };
23
+ //#endregion
24
+ //#region src/managed-ai/credential.ts
25
+ const MAX_JWT_PAYLOAD_CHARS = 16 * 1024;
26
+ /** Resolve the Host-owned bearer, refreshing a near-expiry JWT before it reaches the relay. */
27
+ async function resolveManagedAccessCredential(runtime, refreshWindowMillis = 6e4) {
28
+ let session = await runtime.requireSession();
29
+ if (managedAccessTokenExpiresWithin(session.accessToken, refreshWindowMillis)) session = await runtime.refreshAccessToken(session);
30
+ return new SecretValue(session.accessToken);
31
+ }
32
+ /** Treat a parseable JWT expiry as a refresh hint; opaque access tokens remain usable. */
33
+ function managedAccessTokenExpiresWithin(accessToken, windowMillis, nowMillis = Date.now()) {
34
+ const payload = accessToken.split(".")[1];
35
+ if (payload === void 0 || payload === "" || payload.length > MAX_JWT_PAYLOAD_CHARS) return false;
36
+ try {
37
+ const value = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
38
+ return typeof value.exp === "number" && Number.isSafeInteger(value.exp) && value.exp > 0 && value.exp * 1e3 - nowMillis < windowMillis;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+ //#endregion
13
44
  //#region src/outgoing-call-contract.ts
14
45
  var ArkmeOutgoingCallError = class extends Error {
15
46
  code;
@@ -296,353 +327,13 @@ var ArkmeOutgoingCallBroker = class {
296
327
  intentId: intent.intentId,
297
328
  callRequestId: intent.callRequestId,
298
329
  claimed: intent.claimed,
299
- toolSettled: intent.toolSettled
300
- });
301
- }
302
- detachAbort(intent) {
303
- if (intent.signal !== void 0 && intent.abort !== void 0) intent.signal.removeEventListener("abort", intent.abort);
304
- }
305
- };
306
- const contentCopies = {
307
- unclear: {
308
- prompt: "有点看不懂,反而更想听TA怎么说",
309
- alternatePrompts: ["越看不懂,越想听TA亲口表达", "这段有点跳脱,想听TA怎么读"],
310
- invitationReactions: [
311
- "我有点没看懂,却不是想划走,反而更想听你亲口解释它是什么意思。",
312
- "这段像藏着一点只有你知道的意思,让我很好奇你会怎么说出来。",
313
- "它读起来有点跳脱,也正因为这样,我更想听见你自己的语气。",
314
- "我盯着这段看了一会儿,还是觉得由你亲口讲出来一定更明白。"
315
- ]
316
- },
317
- numeric: {
318
- prompt: "像一串暗号,想听TA怎么读",
319
- alternatePrompts: ["这串数字藏着什么,想听TA念出来", "看起来像个暗号,想听TA怎么解开"],
320
- invitationReactions: [
321
- "这串内容看起来像个小暗号,我很好奇它从你嘴里说出来会是什么语气。",
322
- "我看到它时忍不住猜了好一会儿,真想听你亲口把这个暗号解开。",
323
- "只有这些字符,反而给人留下了很多想象,我想听听你会怎么读。",
324
- "它越简短越让人好奇,感觉只有听你说出来才算完整。"
325
- ]
326
- },
327
- short: {
328
- prompt: "字不多,更想听TA怎么说",
329
- alternatePrompts: ["只有几个字,反而更好奇TA的语气", "话很短,想听TA会怎么念"],
330
- invitationReactions: [
331
- "你只写了短短几个字,反而让我更好奇你当时真正的语气。",
332
- "这句话很短,却挺有余味的,我很想听你亲口说一遍。",
333
- "字不多,但感觉里面还有没写出来的意思,想听听你会怎么表达。",
334
- "我看到这句时停了一下,总觉得带上你的声音会更有感觉。"
335
- ]
336
- },
337
- sad: {
338
- prompt: "文字里有些情绪,更想听见TA的语气",
339
- alternatePrompts: ["这段情绪藏在字里,想听TA怎么说", "有些感受看得见,也想听得见"],
340
- invitationReactions: [
341
- "读到这里有点心疼。文字能写下经历,但我更想听见你当时真正的语气。",
342
- "这段话里的情绪很真实,让我没有办法只把它当成普通文字划过去。",
343
- "看到这些话,我很想更认真地听你说说,而不只是隔着文字猜你的感受。",
344
- "有些心情写出来已经很不容易了,如果能听见你的声音,也许会更懂你。"
345
- ]
346
- },
347
- funny: {
348
- prompt: "这段很有画面,带上声音会更有趣",
349
- alternatePrompts: ["光看文字就有画面,想听TA怎么演绎", "这段有点好玩,想听TA会怎么说"],
350
- invitationReactions: [
351
- "光看文字就已经很有画面了,我开始好奇你亲口讲出来会有多好玩。",
352
- "我看到这里真的笑了一下,要是配上你的语气,感觉一定更有意思。",
353
- "这段太适合听你现场讲了,文字已经有趣,声音应该会更生动。",
354
- "你把这件事写得很有意思,我很想知道你说到这里时会是什么语气。"
355
- ]
356
- },
357
- opinion: {
358
- prompt: "这段很有态度,想听TA怎么说",
359
- alternatePrompts: ["观点写在字里,也想听见TA的语气", "这几句话很有主张,想听TA亲口说"],
360
- invitationReactions: [
361
- "这个想法很有你的态度,我很想听你亲口说出来时的语气和停顿。",
362
- "你的观点让我停下来想了想,也让我好奇你会怎样当面讲清楚它。",
363
- "这几句话很有主张,不只是想看文字,也想听你完整地表达一次。",
364
- "我能从字里看到你的判断,但还是很想听见这份态度本来的声音。"
365
- ]
366
- },
367
- travel: {
368
- prompt: "这段旅途很有画面,想听TA亲口讲",
369
- alternatePrompts: ["风景写进文字里,也想听TA说出来", "这段经历,让人想听见沿途的心情"],
370
- invitationReactions: [
371
- "你写的这段经历特别有画面感,我读完真的很想听你亲口讲讲当时的见闻。",
372
- "看到这些沿途的片段,我好像也跟着走了一小段,很想听听你当时的心情。",
373
- "这段旅途被你写得很有感觉,如果配上你的讲述,应该会更像身临其境。",
374
- "我很喜欢你记录风景的方式,也开始期待从你的声音里再走一遍这段路。"
375
- ]
376
- },
377
- food: {
378
- prompt: "这份味道写得很诱人,想听TA聊聊",
379
- alternatePrompts: ["光看就有点馋,想听TA亲口安利", "这段吃喝日常,也想听见TA的满足"],
380
- invitationReactions: [
381
- "你写得我都有点馋了,很想听你亲口讲讲它到底有多好吃。",
382
- "光看这段分享就能想象到味道,如果听你安利,应该会更有感染力。",
383
- "这份吃喝日常很有生活气,我好奇你说起它时是不是也会忍不住开心。",
384
- "你把这份味道写得很诱人,我真的很想听到你的现场评价。"
385
- ]
386
- },
387
- workStudy: {
388
- prompt: "这段思考很认真,想听TA展开说说",
389
- alternatePrompts: ["字里有不少思考,也想听TA讲明白", "这段成长和复盘,值得被亲口说出来"],
390
- invitationReactions: [
391
- "这段记录里有很多认真思考,我很想听你用自己的方式再展开讲讲。",
392
- "能看出你在这件事上花了不少心思,我也很好奇你一路是怎么想过来的。",
393
- "这份复盘很真诚,文字说清了结果,我还想听见你经历过程时的感受。",
394
- "你的思路让我很受启发,如果由你亲口讲出来,应该会更清楚也更有力量。"
395
- ]
396
- },
397
- creative: {
398
- prompt: "这份创作有自己的味道,想听TA聊聊",
399
- alternatePrompts: ["作品被看见了,也想听见背后的想法", "这份表达不只有画面,也该有声音"],
400
- invitationReactions: [
401
- "这份表达很有你自己的味道,我不只想看作品,也想听你聊聊背后的想法。",
402
- "我很喜欢你记录和创作的方式,也开始好奇你会怎样介绍它。",
403
- "作品已经让人停下来看了,如果再听见你的讲述,感觉会更完整。",
404
- "这段内容有很鲜明的个人感觉,我想听听你创作它时真正想表达什么。"
405
- ]
406
- },
407
- pet: {
408
- prompt: "这份可爱很有画面,想听TA聊聊",
409
- alternatePrompts: ["小家伙被记录下来,也想听TA的宠爱", "光看就很可爱,想听TA亲口分享"],
410
- invitationReactions: [
411
- "这个小家伙也太可爱了,我很想听你亲口讲讲它平时是什么样子。",
412
- "看得出来你很喜欢它,照片和文字之外,我也想听见你说起它时的语气。",
413
- "这份可爱让我忍不住多看了一会儿,感觉听你分享一定更有意思。",
414
- "你记录它的方式很温柔,我也开始期待听你聊聊你们之间的小故事。"
415
- ]
416
- },
417
- story: {
418
- prompt: "这个故事,也让人想听见",
419
- alternatePrompts: ["故事写下来了,也想听TA讲出来", "这段像在讲故事,想听TA亲口说"],
420
- invitationReactions: [
421
- "这段经历读起来像一个有画面的故事,我很想听你亲口把它讲出来。",
422
- "你写下来的这些细节让我跟着看了进去,也开始好奇你的真实语气。",
423
- "故事写在这里已经很动人,如果听你亲自讲,感觉会离它更近一点。",
424
- "我读完还在想后面的画面,很想听你用自己的声音把这段经历补完整。"
425
- ]
426
- },
427
- warm: {
428
- prompt: "这段很温柔,带上声音会更完整",
429
- alternatePrompts: ["文字里有温度,想听TA怎么说", "这份温柔,也想从TA的声音里听见"],
430
- invitationReactions: [
431
- "这段文字很温柔,我也很想从你的声音里听见这份温度。",
432
- "读到这里心里会松一下,如果由你亲口说出来,应该会更治愈。",
433
- "你的表达让人觉得很舒服,我开始期待听见你说这些话时的声音。",
434
- "这份温柔不该只停在文字里,我很想听你亲自把它说出来。"
435
- ]
436
- },
437
- useful: {
438
- prompt: "这段分享,听起来可能更容易懂",
439
- alternatePrompts: ["这段很有用,想听TA讲得更明白", "不只想看这段分享,也想听TA说"],
440
- invitationReactions: [
441
- "这段分享很有用,我也很想听你用自己的方式再讲一遍。",
442
- "你整理得很清楚,看完之后还会想听你补充那些文字没展开的细节。",
443
- "这个方法让我觉得很受用,如果听你亲口解释,应该会更容易记住。",
444
- "能感受到你是真心想把经验分享出来,我也很期待听见你的讲解。"
445
- ]
446
- },
447
- default: {
448
- prompt: "想让这段文字带上TA的声音",
449
- alternatePrompts: ["这段话如果有声音,会是什么感觉", "看见了TA的文字,也想听见TA的声音"],
450
- invitationReactions: [
451
- "这段话让我停下来多看了一会儿,也开始好奇,如果由你亲口说出来会是什么感觉。",
452
- "我不是只想看完就划过去,而是真的很想听听你当时是怎样说这些话的。",
453
- "文字让我看见了你的表达,也让我开始期待它带上属于你的声音。",
454
- "看到这条快记时,我第一反应就是:如果能听见你亲口说,应该会更有感觉。"
455
- ]
456
- }
457
- };
458
- const categoryKeywords = {
459
- sad: [
460
- "难过",
461
- "悲伤",
462
- "遗憾",
463
- "绝望",
464
- "失去",
465
- "离开",
466
- "哭",
467
- "痛苦",
468
- "疲惫",
469
- "好难",
470
- "难熬"
471
- ],
472
- funny: [
473
- "哈哈",
474
- "笑死",
475
- "搞笑",
476
- "好玩",
477
- "有趣",
478
- "逗"
479
- ],
480
- opinion: [
481
- "我觉得",
482
- "我认为",
483
- "观点",
484
- "想法",
485
- "其实",
486
- "应该",
487
- "不能"
488
- ],
489
- travel: [
490
- "旅行",
491
- "旅途",
492
- "出发",
493
- "风景",
494
- "景色",
495
- "海边",
496
- "爬山",
497
- "城市",
498
- "远方",
499
- "路上",
500
- "散步"
501
- ],
502
- food: [
503
- "好吃",
504
- "美食",
505
- "餐厅",
506
- "咖啡",
507
- "甜品",
508
- "味道",
509
- "烤肉",
510
- "火锅",
511
- "做饭"
512
- ],
513
- workStudy: [
514
- "工作",
515
- "项目",
516
- "学习",
517
- "论文",
518
- "考试",
519
- "复盘",
520
- "成长",
521
- "加班",
522
- "研究",
523
- "产品"
524
- ],
525
- creative: [
526
- "摄影",
527
- "音乐",
528
- "电影",
529
- "读书",
530
- "作品",
531
- "创作",
532
- "设计",
533
- "照片",
534
- "画画"
535
- ],
536
- pet: [
537
- "小猫",
538
- "小狗",
539
- "猫咪",
540
- "狗狗",
541
- "宠物",
542
- "毛孩子"
543
- ],
544
- useful: [
545
- "教程",
546
- "方法",
547
- "步骤",
548
- "经验",
549
- "分享",
550
- "建议",
551
- "怎么做"
552
- ],
553
- warm: [
554
- "温柔",
555
- "治愈",
556
- "希望",
557
- "拥抱",
558
- "阳光",
559
- "安慰"
560
- ],
561
- story: [
562
- "曾经",
563
- "后来",
564
- "那年",
565
- "小时候",
566
- "有一天",
567
- "今天",
568
- "昨天",
569
- "故事"
570
- ]
571
- };
572
- const invitationClosings = [
573
- "如果你愿意,来录入一下声纹吧。以后再看到你的世界快记,我也能直接听见你的声音:",
574
- "愿意的话,也来录入声纹,给这条快记添上你的声音吧。我很期待以后不只看到文字,也能听见你:",
575
- "所以想认真邀请你录入声纹。这样下次在世界遇见你的文字,也能听到你亲口表达:",
576
- "要是你愿意,就来留下声纹吧。以后你的世界快记不只有文字,也会有属于你的声音:"
577
- ];
578
- const urlPattern = /(?:(?:https?|ftp):\/\/|www\.)[^\s,。!?;:、"“”‘’()【】《》<>]+/gi;
579
- const trailingUrlPunctuation = /[.,;:!?,。;:!?))\]}>…]+$/;
580
- const whitespace = /\s+/g;
581
- const meaningfulCharacter = /[\p{L}\p{N}]/gu;
582
- const digitCharacter = /\p{N}/u;
583
- const latinCharacter = /[a-z]/i;
584
- const chineseCharacter = /[\u3400-\u9fff]/;
585
- const mathSymbol = /[+\-×÷*/=<>]/;
586
- function worldVoiceprintReadableText(value) {
587
- const normalized = value.replace(urlPattern, (matchedUrl) => matchedUrl.match(trailingUrlPunctuation)?.[0] ?? "").replace(/<>/g, " ").replace(whitespace, " ").trim();
588
- return /[\p{L}\p{N}]/u.test(normalized) ? normalized : "";
589
- }
590
- function buildWorldVoiceprintInviteMessage(input) {
591
- const preview = input.textPreview?.replace(whitespace, " ").trim();
592
- const variant = normalizedVariant(input.variantIndex ?? 0, 4);
593
- const copy = contentCopies[worldVoiceprintContentKind(preview ?? "")];
594
- const name = input.peerDisplayName.replace(whitespace, " ").trim();
595
- const greeting = name === "" || name === "这位用户" ? "我是看到你在世界发的这条快记才来找你的:" : `${name},我是看到你在世界发的这条快记才来找你的:`;
596
- const media = mediaSummary(input);
597
- return `${greeting}\n\n【来自世界的快记】\n${preview === void 0 || preview === "" ? `(你发布了一条${media === "" ? "" : `${media}的`}世界快记)` : `“${preview}”${media === "" ? "" : `\n(还包含${media})`}`}\n\n${copy.invitationReactions[variant]}\n\n${invitationClosings[variant]}\n${input.inviteUrl}`;
598
- }
599
- function mediaSummary(input) {
600
- const parts = [];
601
- if ((input.imageCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.imageCount))}张图片`);
602
- if ((input.videoCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.videoCount))}段视频`);
603
- if ((input.voiceCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.voiceCount))}段语音`);
604
- return parts.join("、");
605
- }
606
- function normalizedVariant(value, count) {
607
- if (!Number.isFinite(value)) return 0;
608
- return (Math.trunc(value) % count + count) % count;
609
- }
610
- function worldVoiceprintContentKind(rawText) {
611
- const text = worldVoiceprintReadableText(rawText).toLowerCase();
612
- const meaningful = text.match(meaningfulCharacter) ?? [];
613
- if (meaningful.length === 0) return "unclear";
614
- if (looksNumericOrFormula(text, meaningful)) return "numeric";
615
- if (looksUnclear(text, meaningful)) return "unclear";
616
- if (meaningful.length <= 6) return "short";
617
- return bestMatchingCategory(text) ?? "default";
618
- }
619
- function looksNumericOrFormula(text, meaningful) {
620
- const digitCount = meaningful.filter((character) => digitCharacter.test(character)).length;
621
- if (digitCount === 0) return false;
622
- if (mathSymbol.test(text)) return true;
623
- return digitCount / meaningful.length >= .7;
624
- }
625
- function looksUnclear(text, meaningful) {
626
- const uniqueCharacters = new Set(meaningful);
627
- if (meaningful.length >= 5 && uniqueCharacters.size === 1) return true;
628
- if (meaningful.length >= 8 && uniqueCharacters.size <= 2) return true;
629
- const hasChinese = chineseCharacter.test(text);
630
- const hasLatin = latinCharacter.test(text);
631
- const hasDigit = digitCharacter.test(text);
632
- return !hasChinese && hasLatin && hasDigit && !text.includes(" ");
633
- }
634
- function bestMatchingCategory(text) {
635
- let bestCategory;
636
- let bestScore = 0;
637
- for (const [category, keywords] of Object.entries(categoryKeywords)) {
638
- const score = keywords.filter((keyword) => text.includes(keyword)).length;
639
- if (score > bestScore) {
640
- bestCategory = category;
641
- bestScore = score;
642
- }
330
+ toolSettled: intent.toolSettled
331
+ });
643
332
  }
644
- return bestCategory;
645
- }
333
+ detachAbort(intent) {
334
+ if (intent.signal !== void 0 && intent.abort !== void 0) intent.signal.removeEventListener("abort", intent.abort);
335
+ }
336
+ };
646
337
  //#endregion
647
338
  //#region src/request-coordinator.ts
648
339
  const DEFAULT_LANE_LIMITS = {
@@ -1570,7 +1261,555 @@ var ServiceRuntime = class {
1570
1261
  return await this.post(baseUrl, path, body, session.accessToken, [0], signal, false, requestOptions(), true, true);
1571
1262
  }
1572
1263
  }
1573
- };
1264
+ };
1265
+ //#endregion
1266
+ //#region src/billing-gateway.ts
1267
+ var ArkmeBillingUnavailableError = class extends Error {
1268
+ constructor() {
1269
+ super("购买服务暂不可用");
1270
+ this.name = "ArkmeBillingUnavailableError";
1271
+ }
1272
+ };
1273
+ function nanoCny(value, field) {
1274
+ const normalized = stringValue$1(value).trim();
1275
+ if (!/^(?:0|[1-9]\d*)$/.test(normalized)) throw new ArkmePluginError("billing-contract-invalid", `余额接口字段 ${field} 无效`, true, 502);
1276
+ return normalized;
1277
+ }
1278
+ function requiredString$1(value, field) {
1279
+ const normalized = stringValue$1(value).trim();
1280
+ if (normalized === "") throw new ArkmePluginError("billing-contract-invalid", `支付接口字段 ${field} 无效`, true, 502);
1281
+ return normalized;
1282
+ }
1283
+ function positiveSafeInteger(value, field) {
1284
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new ArkmePluginError("billing-contract-invalid", `支付接口字段 ${field} 无效`, true, 502);
1285
+ return value;
1286
+ }
1287
+ function optionalTimestampMillis(value, field) {
1288
+ if (value === void 0 || value === null || value === "") return void 0;
1289
+ const millis = Date.parse(requiredString$1(value, field));
1290
+ if (!Number.isFinite(millis)) throw new ArkmePluginError("billing-contract-invalid", `支付接口字段 ${field} 无效`, true, 502);
1291
+ return millis;
1292
+ }
1293
+ function uuid(value, field, errorCode = "billing-contract-invalid") {
1294
+ const normalized = stringValue$1(value).trim().toLowerCase();
1295
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(normalized)) {
1296
+ const upstreamContract = errorCode === "billing-contract-invalid";
1297
+ throw new ArkmePluginError(errorCode, `支付接口字段 ${field} 无效`, upstreamContract, upstreamContract ? 502 : 400);
1298
+ }
1299
+ return normalized;
1300
+ }
1301
+ function paymentMethods(value) {
1302
+ if (!Array.isArray(value)) throw new ArkmePluginError("billing-contract-invalid", "支付接口字段 payment_methods 无效", true, 502);
1303
+ const result = [];
1304
+ for (const rawMethod of value) {
1305
+ const method = objectValue$1(rawMethod);
1306
+ const id = requiredString$1(method.id, "payment_methods.id");
1307
+ const provider = requiredString$1(method.provider, "payment_methods.provider");
1308
+ const actionType = requiredString$1(method.action_type, "payment_methods.action_type");
1309
+ if (!(id === "wechat_native" && provider === "wechat" && actionType === "display_qr" || id === "alipay_pc_web" && provider === "alipay" && actionType === "open_url")) throw new ArkmePluginError("billing-contract-invalid", "支付接口包含未知支付方式", true, 502);
1310
+ if (!result.some((item) => item.id === id)) result.push({
1311
+ id,
1312
+ provider,
1313
+ actionType
1314
+ });
1315
+ }
1316
+ if (result.length === 0) throw new ArkmePluginError("billing-contract-invalid", "支付接口未返回可用渠道", true, 502);
1317
+ return result;
1318
+ }
1319
+ function productSnapshot(value, currency) {
1320
+ const product = objectValue$1(value);
1321
+ const description = stringValue$1(product.description).trim();
1322
+ return {
1323
+ productId: requiredString$1(product.product_code, "product_code"),
1324
+ title: requiredString$1(product.title, "title"),
1325
+ ...description === "" ? {} : { description },
1326
+ creditNanoCny: nanoCny(product.credit_nano_cny, "credit_nano_cny"),
1327
+ priceMinor: positiveSafeInteger(product.price_cent, "price_cent"),
1328
+ currency,
1329
+ paymentMethods: paymentMethods(product.payment_methods),
1330
+ enabled: true
1331
+ };
1332
+ }
1333
+ function paymentMethodContract(paymentMethod, provider, actionType) {
1334
+ return paymentMethod === "wechat_native" && provider === "wechat" && actionType === "display_qr" || paymentMethod === "alipay_pc_web" && provider === "alipay" && actionType === "open_url";
1335
+ }
1336
+ function paymentAction(value) {
1337
+ const action = objectValue$1(value);
1338
+ const type = requiredString$1(action.type, "payment_action.type");
1339
+ if (type === "open_url") {
1340
+ const url = requiredString$1(action.url, "payment_action.url");
1341
+ let parsed;
1342
+ try {
1343
+ parsed = new URL(url);
1344
+ } catch {
1345
+ throw new ArkmePluginError("billing-contract-invalid", "支付接口字段 payment_action.url 无效", true, 502);
1346
+ }
1347
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new ArkmePluginError("billing-contract-invalid", "支付接口字段 payment_action.url 无效", true, 502);
1348
+ return {
1349
+ type,
1350
+ url: parsed.href
1351
+ };
1352
+ }
1353
+ if (type === "display_qr") return {
1354
+ type,
1355
+ qrContent: requiredString$1(action.qr_code_content, "payment_action.qr_code_content")
1356
+ };
1357
+ throw new ArkmePluginError("billing-contract-invalid", "支付接口返回未知支付动作", true, 502);
1358
+ }
1359
+ function orderSnapshot(value) {
1360
+ const order = objectValue$1(value);
1361
+ const mappedStatus = {
1362
+ pending_payment: "pending",
1363
+ crediting: "crediting",
1364
+ credited: "paid",
1365
+ closed: "closed"
1366
+ }[requiredString$1(order.status, "status")];
1367
+ if (mappedStatus === void 0) throw new ArkmePluginError("billing-contract-invalid", "支付接口返回未知订单状态", true, 502);
1368
+ const paymentProvider = requiredString$1(order.payment_provider, "payment_provider");
1369
+ const paymentMethod = requiredString$1(order.payment_method, "payment_method");
1370
+ const rawAction = order.payment_action;
1371
+ const action = rawAction === void 0 || rawAction === null ? void 0 : paymentAction(rawAction);
1372
+ if (!paymentMethodContract(paymentMethod, paymentProvider, action?.type ?? (paymentMethod === "wechat_native" ? "display_qr" : paymentMethod === "alipay_pc_web" ? "open_url" : ""))) throw new ArkmePluginError("billing-contract-invalid", "支付接口返回未知支付方式", true, 502);
1373
+ if (requiredString$1(order.currency, "currency") !== "CNY") throw new ArkmePluginError("billing-contract-invalid", "支付接口币种无效", true, 502);
1374
+ const expiresAtMillis = optionalTimestampMillis(order.expires_at, "expires_at");
1375
+ if (expiresAtMillis === void 0) throw new ArkmePluginError("billing-contract-invalid", "支付接口字段 expires_at 无效", true, 502);
1376
+ if (mappedStatus === "pending" && action === void 0) throw new ArkmePluginError("billing-contract-invalid", "支付接口未返回有效支付动作", true, 502);
1377
+ const pollInterval = order.poll_interval_ms;
1378
+ const pollIntervalMillis = typeof pollInterval === "number" && Number.isSafeInteger(pollInterval) && pollInterval > 0 ? Math.min(1e4, Math.max(500, pollInterval)) : void 0;
1379
+ return {
1380
+ orderId: uuid(order.order_uid, "order_uid"),
1381
+ paymentProvider,
1382
+ paymentMethod,
1383
+ status: mappedStatus,
1384
+ amountMinor: positiveSafeInteger(order.price_cent, "price_cent"),
1385
+ currency: "CNY",
1386
+ creditNanoCny: nanoCny(order.credit_nano_cny, "credit_nano_cny"),
1387
+ expiresAtMillis,
1388
+ ...action === void 0 ? {} : { paymentAction: action },
1389
+ ...pollIntervalMillis === void 0 ? {} : { pollIntervalMillis },
1390
+ ...optionalTimestamp("paidAtMillis", order.paid_at, "paid_at"),
1391
+ ...optionalTimestamp("creditedAtMillis", order.credited_at, "credited_at")
1392
+ };
1393
+ }
1394
+ function optionalTimestamp(key, value, field) {
1395
+ const millis = optionalTimestampMillis(value, field);
1396
+ return millis === void 0 ? {} : { [key]: millis };
1397
+ }
1398
+ var HttpArkmeBillingGateway = class {
1399
+ runtime;
1400
+ constructor(runtime) {
1401
+ this.runtime = runtime;
1402
+ }
1403
+ async post(service, path, body, lane, signal, key) {
1404
+ let session = await this.runtime.requireSession();
1405
+ const request = async () => await this.runtime.post(service === "auth" ? this.runtime.config.authBaseUrl : this.runtime.config.intelligentBaseUrl, path, body, session.accessToken, [200], signal, true, this.runtime.authenticatedRequestOptions(session, service, lane, {
1406
+ lane,
1407
+ ...key === void 0 ? {} : {
1408
+ key,
1409
+ bypassCache: true
1410
+ }
1411
+ }));
1412
+ try {
1413
+ return await request();
1414
+ } catch (error) {
1415
+ if (!(error instanceof ArkmePluginError) || ![
1416
+ "auth-http-401",
1417
+ "auth-http-403",
1418
+ "invalid_access_token"
1419
+ ].includes(error.code)) throw this.normalizeError(error);
1420
+ session = await this.runtime.refreshAccessToken(session);
1421
+ try {
1422
+ return await request();
1423
+ } catch (retryError) {
1424
+ throw this.normalizeError(retryError);
1425
+ }
1426
+ }
1427
+ }
1428
+ normalizeError(error) {
1429
+ if (!(error instanceof ArkmePluginError)) return error;
1430
+ const retryable = [
1431
+ "order_processing",
1432
+ "purchase_unavailable",
1433
+ "balance_unavailable",
1434
+ "arkme-network-error",
1435
+ "arkme-timeout"
1436
+ ].includes(error.code);
1437
+ if (retryable === error.retryable) return error;
1438
+ return new ArkmePluginError(error.code, error.message, retryable, error.httpStatus, { cause: error });
1439
+ }
1440
+ async quota(signal) {
1441
+ const data = objectValue$1(await this.post("intelligent", "/api/v1/managed-ai/balance/query", {}, "interactive-read", signal, "managed-ai-balance"));
1442
+ const currency = stringValue$1(data.currency).trim();
1443
+ if (currency !== "CNY") throw new ArkmePluginError("billing-contract-invalid", "余额接口币种无效", true, 502);
1444
+ const availableNanoCny = nanoCny(data.available_nano_cny, "available_nano_cny");
1445
+ const totalNanoCny = nanoCny(data.total_nano_cny, "total_nano_cny");
1446
+ const reservedNanoCny = nanoCny(data.reserved_nano_cny, "reserved_nano_cny");
1447
+ if (BigInt(availableNanoCny) + BigInt(reservedNanoCny) !== BigInt(totalNanoCny)) throw new ArkmePluginError("billing-contract-invalid", "余额接口金额不一致", true, 502);
1448
+ return {
1449
+ availableNanoCny,
1450
+ totalNanoCny,
1451
+ reservedNanoCny,
1452
+ currency
1453
+ };
1454
+ }
1455
+ async products(signal) {
1456
+ const data = objectValue$1(await this.post("auth", "/api/v1/managed-ai/products/query", {}, "interactive-read", signal, "managed-ai-products"));
1457
+ if (stringValue$1(data.currency).trim() !== "CNY" || !Array.isArray(data.products)) throw new ArkmePluginError("billing-contract-invalid", "商品接口响应无效", true, 502);
1458
+ return { items: data.products.map((product) => productSnapshot(product, "CNY")) };
1459
+ }
1460
+ async createOrder(input, signal) {
1461
+ const clientOrderUid = uuid(input.clientRequestId, "client_order_uid", "billing-client-request-id-invalid");
1462
+ return orderSnapshot(await this.post("auth", "/api/v1/managed-ai/purchase/create", {
1463
+ client_order_uid: clientOrderUid,
1464
+ product_code: requiredString$1(input.productId, "product_code"),
1465
+ payment_method: input.paymentMethod
1466
+ }, "write", signal));
1467
+ }
1468
+ async orderStatus(orderId, signal) {
1469
+ const orderUid = uuid(orderId, "order_uid", "billing-order-id-invalid");
1470
+ return orderSnapshot(await this.post("auth", "/api/v1/managed-ai/purchase/status", { order_uid: orderUid }, "interactive-read", signal));
1471
+ }
1472
+ };
1473
+ const contentCopies = {
1474
+ unclear: {
1475
+ prompt: "有点看不懂,反而更想听TA怎么说",
1476
+ alternatePrompts: ["越看不懂,越想听TA亲口表达", "这段有点跳脱,想听TA怎么读"],
1477
+ invitationReactions: [
1478
+ "我有点没看懂,却不是想划走,反而更想听你亲口解释它是什么意思。",
1479
+ "这段像藏着一点只有你知道的意思,让我很好奇你会怎么说出来。",
1480
+ "它读起来有点跳脱,也正因为这样,我更想听见你自己的语气。",
1481
+ "我盯着这段看了一会儿,还是觉得由你亲口讲出来一定更明白。"
1482
+ ]
1483
+ },
1484
+ numeric: {
1485
+ prompt: "像一串暗号,想听TA怎么读",
1486
+ alternatePrompts: ["这串数字藏着什么,想听TA念出来", "看起来像个暗号,想听TA怎么解开"],
1487
+ invitationReactions: [
1488
+ "这串内容看起来像个小暗号,我很好奇它从你嘴里说出来会是什么语气。",
1489
+ "我看到它时忍不住猜了好一会儿,真想听你亲口把这个暗号解开。",
1490
+ "只有这些字符,反而给人留下了很多想象,我想听听你会怎么读。",
1491
+ "它越简短越让人好奇,感觉只有听你说出来才算完整。"
1492
+ ]
1493
+ },
1494
+ short: {
1495
+ prompt: "字不多,更想听TA怎么说",
1496
+ alternatePrompts: ["只有几个字,反而更好奇TA的语气", "话很短,想听TA会怎么念"],
1497
+ invitationReactions: [
1498
+ "你只写了短短几个字,反而让我更好奇你当时真正的语气。",
1499
+ "这句话很短,却挺有余味的,我很想听你亲口说一遍。",
1500
+ "字不多,但感觉里面还有没写出来的意思,想听听你会怎么表达。",
1501
+ "我看到这句时停了一下,总觉得带上你的声音会更有感觉。"
1502
+ ]
1503
+ },
1504
+ sad: {
1505
+ prompt: "文字里有些情绪,更想听见TA的语气",
1506
+ alternatePrompts: ["这段情绪藏在字里,想听TA怎么说", "有些感受看得见,也想听得见"],
1507
+ invitationReactions: [
1508
+ "读到这里有点心疼。文字能写下经历,但我更想听见你当时真正的语气。",
1509
+ "这段话里的情绪很真实,让我没有办法只把它当成普通文字划过去。",
1510
+ "看到这些话,我很想更认真地听你说说,而不只是隔着文字猜你的感受。",
1511
+ "有些心情写出来已经很不容易了,如果能听见你的声音,也许会更懂你。"
1512
+ ]
1513
+ },
1514
+ funny: {
1515
+ prompt: "这段很有画面,带上声音会更有趣",
1516
+ alternatePrompts: ["光看文字就有画面,想听TA怎么演绎", "这段有点好玩,想听TA会怎么说"],
1517
+ invitationReactions: [
1518
+ "光看文字就已经很有画面了,我开始好奇你亲口讲出来会有多好玩。",
1519
+ "我看到这里真的笑了一下,要是配上你的语气,感觉一定更有意思。",
1520
+ "这段太适合听你现场讲了,文字已经有趣,声音应该会更生动。",
1521
+ "你把这件事写得很有意思,我很想知道你说到这里时会是什么语气。"
1522
+ ]
1523
+ },
1524
+ opinion: {
1525
+ prompt: "这段很有态度,想听TA怎么说",
1526
+ alternatePrompts: ["观点写在字里,也想听见TA的语气", "这几句话很有主张,想听TA亲口说"],
1527
+ invitationReactions: [
1528
+ "这个想法很有你的态度,我很想听你亲口说出来时的语气和停顿。",
1529
+ "你的观点让我停下来想了想,也让我好奇你会怎样当面讲清楚它。",
1530
+ "这几句话很有主张,不只是想看文字,也想听你完整地表达一次。",
1531
+ "我能从字里看到你的判断,但还是很想听见这份态度本来的声音。"
1532
+ ]
1533
+ },
1534
+ travel: {
1535
+ prompt: "这段旅途很有画面,想听TA亲口讲",
1536
+ alternatePrompts: ["风景写进文字里,也想听TA说出来", "这段经历,让人想听见沿途的心情"],
1537
+ invitationReactions: [
1538
+ "你写的这段经历特别有画面感,我读完真的很想听你亲口讲讲当时的见闻。",
1539
+ "看到这些沿途的片段,我好像也跟着走了一小段,很想听听你当时的心情。",
1540
+ "这段旅途被你写得很有感觉,如果配上你的讲述,应该会更像身临其境。",
1541
+ "我很喜欢你记录风景的方式,也开始期待从你的声音里再走一遍这段路。"
1542
+ ]
1543
+ },
1544
+ food: {
1545
+ prompt: "这份味道写得很诱人,想听TA聊聊",
1546
+ alternatePrompts: ["光看就有点馋,想听TA亲口安利", "这段吃喝日常,也想听见TA的满足"],
1547
+ invitationReactions: [
1548
+ "你写得我都有点馋了,很想听你亲口讲讲它到底有多好吃。",
1549
+ "光看这段分享就能想象到味道,如果听你安利,应该会更有感染力。",
1550
+ "这份吃喝日常很有生活气,我好奇你说起它时是不是也会忍不住开心。",
1551
+ "你把这份味道写得很诱人,我真的很想听到你的现场评价。"
1552
+ ]
1553
+ },
1554
+ workStudy: {
1555
+ prompt: "这段思考很认真,想听TA展开说说",
1556
+ alternatePrompts: ["字里有不少思考,也想听TA讲明白", "这段成长和复盘,值得被亲口说出来"],
1557
+ invitationReactions: [
1558
+ "这段记录里有很多认真思考,我很想听你用自己的方式再展开讲讲。",
1559
+ "能看出你在这件事上花了不少心思,我也很好奇你一路是怎么想过来的。",
1560
+ "这份复盘很真诚,文字说清了结果,我还想听见你经历过程时的感受。",
1561
+ "你的思路让我很受启发,如果由你亲口讲出来,应该会更清楚也更有力量。"
1562
+ ]
1563
+ },
1564
+ creative: {
1565
+ prompt: "这份创作有自己的味道,想听TA聊聊",
1566
+ alternatePrompts: ["作品被看见了,也想听见背后的想法", "这份表达不只有画面,也该有声音"],
1567
+ invitationReactions: [
1568
+ "这份表达很有你自己的味道,我不只想看作品,也想听你聊聊背后的想法。",
1569
+ "我很喜欢你记录和创作的方式,也开始好奇你会怎样介绍它。",
1570
+ "作品已经让人停下来看了,如果再听见你的讲述,感觉会更完整。",
1571
+ "这段内容有很鲜明的个人感觉,我想听听你创作它时真正想表达什么。"
1572
+ ]
1573
+ },
1574
+ pet: {
1575
+ prompt: "这份可爱很有画面,想听TA聊聊",
1576
+ alternatePrompts: ["小家伙被记录下来,也想听TA的宠爱", "光看就很可爱,想听TA亲口分享"],
1577
+ invitationReactions: [
1578
+ "这个小家伙也太可爱了,我很想听你亲口讲讲它平时是什么样子。",
1579
+ "看得出来你很喜欢它,照片和文字之外,我也想听见你说起它时的语气。",
1580
+ "这份可爱让我忍不住多看了一会儿,感觉听你分享一定更有意思。",
1581
+ "你记录它的方式很温柔,我也开始期待听你聊聊你们之间的小故事。"
1582
+ ]
1583
+ },
1584
+ story: {
1585
+ prompt: "这个故事,也让人想听见",
1586
+ alternatePrompts: ["故事写下来了,也想听TA讲出来", "这段像在讲故事,想听TA亲口说"],
1587
+ invitationReactions: [
1588
+ "这段经历读起来像一个有画面的故事,我很想听你亲口把它讲出来。",
1589
+ "你写下来的这些细节让我跟着看了进去,也开始好奇你的真实语气。",
1590
+ "故事写在这里已经很动人,如果听你亲自讲,感觉会离它更近一点。",
1591
+ "我读完还在想后面的画面,很想听你用自己的声音把这段经历补完整。"
1592
+ ]
1593
+ },
1594
+ warm: {
1595
+ prompt: "这段很温柔,带上声音会更完整",
1596
+ alternatePrompts: ["文字里有温度,想听TA怎么说", "这份温柔,也想从TA的声音里听见"],
1597
+ invitationReactions: [
1598
+ "这段文字很温柔,我也很想从你的声音里听见这份温度。",
1599
+ "读到这里心里会松一下,如果由你亲口说出来,应该会更治愈。",
1600
+ "你的表达让人觉得很舒服,我开始期待听见你说这些话时的声音。",
1601
+ "这份温柔不该只停在文字里,我很想听你亲自把它说出来。"
1602
+ ]
1603
+ },
1604
+ useful: {
1605
+ prompt: "这段分享,听起来可能更容易懂",
1606
+ alternatePrompts: ["这段很有用,想听TA讲得更明白", "不只想看这段分享,也想听TA说"],
1607
+ invitationReactions: [
1608
+ "这段分享很有用,我也很想听你用自己的方式再讲一遍。",
1609
+ "你整理得很清楚,看完之后还会想听你补充那些文字没展开的细节。",
1610
+ "这个方法让我觉得很受用,如果听你亲口解释,应该会更容易记住。",
1611
+ "能感受到你是真心想把经验分享出来,我也很期待听见你的讲解。"
1612
+ ]
1613
+ },
1614
+ default: {
1615
+ prompt: "想让这段文字带上TA的声音",
1616
+ alternatePrompts: ["这段话如果有声音,会是什么感觉", "看见了TA的文字,也想听见TA的声音"],
1617
+ invitationReactions: [
1618
+ "这段话让我停下来多看了一会儿,也开始好奇,如果由你亲口说出来会是什么感觉。",
1619
+ "我不是只想看完就划过去,而是真的很想听听你当时是怎样说这些话的。",
1620
+ "文字让我看见了你的表达,也让我开始期待它带上属于你的声音。",
1621
+ "看到这条快记时,我第一反应就是:如果能听见你亲口说,应该会更有感觉。"
1622
+ ]
1623
+ }
1624
+ };
1625
+ const categoryKeywords = {
1626
+ sad: [
1627
+ "难过",
1628
+ "悲伤",
1629
+ "遗憾",
1630
+ "绝望",
1631
+ "失去",
1632
+ "离开",
1633
+ "哭",
1634
+ "痛苦",
1635
+ "疲惫",
1636
+ "好难",
1637
+ "难熬"
1638
+ ],
1639
+ funny: [
1640
+ "哈哈",
1641
+ "笑死",
1642
+ "搞笑",
1643
+ "好玩",
1644
+ "有趣",
1645
+ "逗"
1646
+ ],
1647
+ opinion: [
1648
+ "我觉得",
1649
+ "我认为",
1650
+ "观点",
1651
+ "想法",
1652
+ "其实",
1653
+ "应该",
1654
+ "不能"
1655
+ ],
1656
+ travel: [
1657
+ "旅行",
1658
+ "旅途",
1659
+ "出发",
1660
+ "风景",
1661
+ "景色",
1662
+ "海边",
1663
+ "爬山",
1664
+ "城市",
1665
+ "远方",
1666
+ "路上",
1667
+ "散步"
1668
+ ],
1669
+ food: [
1670
+ "好吃",
1671
+ "美食",
1672
+ "餐厅",
1673
+ "咖啡",
1674
+ "甜品",
1675
+ "味道",
1676
+ "烤肉",
1677
+ "火锅",
1678
+ "做饭"
1679
+ ],
1680
+ workStudy: [
1681
+ "工作",
1682
+ "项目",
1683
+ "学习",
1684
+ "论文",
1685
+ "考试",
1686
+ "复盘",
1687
+ "成长",
1688
+ "加班",
1689
+ "研究",
1690
+ "产品"
1691
+ ],
1692
+ creative: [
1693
+ "摄影",
1694
+ "音乐",
1695
+ "电影",
1696
+ "读书",
1697
+ "作品",
1698
+ "创作",
1699
+ "设计",
1700
+ "照片",
1701
+ "画画"
1702
+ ],
1703
+ pet: [
1704
+ "小猫",
1705
+ "小狗",
1706
+ "猫咪",
1707
+ "狗狗",
1708
+ "宠物",
1709
+ "毛孩子"
1710
+ ],
1711
+ useful: [
1712
+ "教程",
1713
+ "方法",
1714
+ "步骤",
1715
+ "经验",
1716
+ "分享",
1717
+ "建议",
1718
+ "怎么做"
1719
+ ],
1720
+ warm: [
1721
+ "温柔",
1722
+ "治愈",
1723
+ "希望",
1724
+ "拥抱",
1725
+ "阳光",
1726
+ "安慰"
1727
+ ],
1728
+ story: [
1729
+ "曾经",
1730
+ "后来",
1731
+ "那年",
1732
+ "小时候",
1733
+ "有一天",
1734
+ "今天",
1735
+ "昨天",
1736
+ "故事"
1737
+ ]
1738
+ };
1739
+ const invitationClosings = [
1740
+ "如果你愿意,来录入一下声纹吧。以后再看到你的世界快记,我也能直接听见你的声音:",
1741
+ "愿意的话,也来录入声纹,给这条快记添上你的声音吧。我很期待以后不只看到文字,也能听见你:",
1742
+ "所以想认真邀请你录入声纹。这样下次在世界遇见你的文字,也能听到你亲口表达:",
1743
+ "要是你愿意,就来留下声纹吧。以后你的世界快记不只有文字,也会有属于你的声音:"
1744
+ ];
1745
+ const urlPattern = /(?:(?:https?|ftp):\/\/|www\.)[^\s,。!?;:、"“”‘’()【】《》<>]+/gi;
1746
+ const trailingUrlPunctuation = /[.,;:!?,。;:!?))\]}>…]+$/;
1747
+ const whitespace = /\s+/g;
1748
+ const meaningfulCharacter = /[\p{L}\p{N}]/gu;
1749
+ const digitCharacter = /\p{N}/u;
1750
+ const latinCharacter = /[a-z]/i;
1751
+ const chineseCharacter = /[\u3400-\u9fff]/;
1752
+ const mathSymbol = /[+\-×÷*/=<>]/;
1753
+ function worldVoiceprintReadableText(value) {
1754
+ const normalized = value.replace(urlPattern, (matchedUrl) => matchedUrl.match(trailingUrlPunctuation)?.[0] ?? "").replace(/<>/g, " ").replace(whitespace, " ").trim();
1755
+ return /[\p{L}\p{N}]/u.test(normalized) ? normalized : "";
1756
+ }
1757
+ function buildWorldVoiceprintInviteMessage(input) {
1758
+ const preview = input.textPreview?.replace(whitespace, " ").trim();
1759
+ const variant = normalizedVariant(input.variantIndex ?? 0, 4);
1760
+ const copy = contentCopies[worldVoiceprintContentKind(preview ?? "")];
1761
+ const name = input.peerDisplayName.replace(whitespace, " ").trim();
1762
+ const greeting = name === "" || name === "这位用户" ? "我是看到你在世界发的这条快记才来找你的:" : `${name},我是看到你在世界发的这条快记才来找你的:`;
1763
+ const media = mediaSummary(input);
1764
+ return `${greeting}\n\n【来自世界的快记】\n${preview === void 0 || preview === "" ? `(你发布了一条${media === "" ? "" : `${media}的`}世界快记)` : `“${preview}”${media === "" ? "" : `\n(还包含${media})`}`}\n\n${copy.invitationReactions[variant]}\n\n${invitationClosings[variant]}\n${input.inviteUrl}`;
1765
+ }
1766
+ function mediaSummary(input) {
1767
+ const parts = [];
1768
+ if ((input.imageCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.imageCount))}张图片`);
1769
+ if ((input.videoCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.videoCount))}段视频`);
1770
+ if ((input.voiceCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.voiceCount))}段语音`);
1771
+ return parts.join("、");
1772
+ }
1773
+ function normalizedVariant(value, count) {
1774
+ if (!Number.isFinite(value)) return 0;
1775
+ return (Math.trunc(value) % count + count) % count;
1776
+ }
1777
+ function worldVoiceprintContentKind(rawText) {
1778
+ const text = worldVoiceprintReadableText(rawText).toLowerCase();
1779
+ const meaningful = text.match(meaningfulCharacter) ?? [];
1780
+ if (meaningful.length === 0) return "unclear";
1781
+ if (looksNumericOrFormula(text, meaningful)) return "numeric";
1782
+ if (looksUnclear(text, meaningful)) return "unclear";
1783
+ if (meaningful.length <= 6) return "short";
1784
+ return bestMatchingCategory(text) ?? "default";
1785
+ }
1786
+ function looksNumericOrFormula(text, meaningful) {
1787
+ const digitCount = meaningful.filter((character) => digitCharacter.test(character)).length;
1788
+ if (digitCount === 0) return false;
1789
+ if (mathSymbol.test(text)) return true;
1790
+ return digitCount / meaningful.length >= .7;
1791
+ }
1792
+ function looksUnclear(text, meaningful) {
1793
+ const uniqueCharacters = new Set(meaningful);
1794
+ if (meaningful.length >= 5 && uniqueCharacters.size === 1) return true;
1795
+ if (meaningful.length >= 8 && uniqueCharacters.size <= 2) return true;
1796
+ const hasChinese = chineseCharacter.test(text);
1797
+ const hasLatin = latinCharacter.test(text);
1798
+ const hasDigit = digitCharacter.test(text);
1799
+ return !hasChinese && hasLatin && hasDigit && !text.includes(" ");
1800
+ }
1801
+ function bestMatchingCategory(text) {
1802
+ let bestCategory;
1803
+ let bestScore = 0;
1804
+ for (const [category, keywords] of Object.entries(categoryKeywords)) {
1805
+ const score = keywords.filter((keyword) => text.includes(keyword)).length;
1806
+ if (score > bestScore) {
1807
+ bestCategory = category;
1808
+ bestScore = score;
1809
+ }
1810
+ }
1811
+ return bestCategory;
1812
+ }
1574
1813
  //#endregion
1575
1814
  //#region src/services/ai-video-service.ts
1576
1815
  function numberValue$24(value) {
@@ -2714,18 +2953,33 @@ const ARKME_PHONE_BIND_CODE_ERR = 3;
2714
2953
  function numberValue$21(value) {
2715
2954
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
2716
2955
  }
2956
+ function jiwoScanLoginAvailable(config) {
2957
+ try {
2958
+ const authUrl = new URL(config.authBaseUrl);
2959
+ const shareUrl = new URL(config.shareWebsite ?? "");
2960
+ if ([authUrl, shareUrl].some((url) => url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "")) return false;
2961
+ const authHost = authUrl.hostname.toLowerCase();
2962
+ const shareHost = shareUrl.hostname.toLowerCase();
2963
+ return config.environment === "prod" ? authHost === "api.jotmo.cc" && shareHost === "jiwo.cc" : authHost === "jotmo.senguo.me" && shareHost === "jotmo-app.senguo.me";
2964
+ } catch {
2965
+ return false;
2966
+ }
2967
+ }
2717
2968
  var AuthService = class {
2718
2969
  runtime;
2719
2970
  profile;
2720
2971
  lifecycle;
2721
2972
  attempts = /* @__PURE__ */ new Map();
2973
+ jiwoAttemptGeneration = 0;
2722
2974
  constructor(runtime, profile, lifecycle) {
2723
2975
  this.runtime = runtime;
2724
2976
  this.profile = profile;
2725
2977
  this.lifecycle = lifecycle;
2726
2978
  }
2727
2979
  dispose() {
2728
- this.attempts.clear();
2980
+ this.jiwoAttemptGeneration += 1;
2981
+ this.cancelAllJiwoLoginAttempts();
2982
+ for (const [attemptId, attempt] of this.attempts) if (attempt.kind === "wechat") this.attempts.delete(attemptId);
2729
2983
  }
2730
2984
  async logout() {
2731
2985
  const activeSession = await this.runtime.sessionStore.read();
@@ -2738,6 +2992,8 @@ var AuthService = class {
2738
2992
  await this.runtime.sessionStore.delete();
2739
2993
  await this.runtime.clearPendingBindingSession();
2740
2994
  this.profile.invalidate();
2995
+ this.jiwoAttemptGeneration += 1;
2996
+ await this.cancelAllJiwoLoginAttempts();
2741
2997
  this.attempts.clear();
2742
2998
  this.lifecycle.clearAccountState(userIds);
2743
2999
  this.lifecycle.reconnectChatRealtime();
@@ -2773,6 +3029,8 @@ var AuthService = class {
2773
3029
  };
2774
3030
  }
2775
3031
  async beginWechatLogin() {
3032
+ this.jiwoAttemptGeneration += 1;
3033
+ await this.cancelAllJiwoLoginAttempts();
2776
3034
  const data = await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/wechat-oauth-login-qrcode", {}, void 0, [200]);
2777
3035
  const sceneStr = stringValue$1(data.scene_str).trim();
2778
3036
  const pollToken = stringValue$1(data.poll_token).trim();
@@ -2782,6 +3040,7 @@ var AuthService = class {
2782
3040
  if (sceneStr === "" || pollToken === "" || qrContent === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录二维码响应不完整", true, 502);
2783
3041
  const attemptId = crypto.randomUUID();
2784
3042
  const attempt = {
3043
+ kind: "wechat",
2785
3044
  attemptId,
2786
3045
  sceneStr,
2787
3046
  pollToken,
@@ -2800,7 +3059,7 @@ var AuthService = class {
2800
3059
  }
2801
3060
  async pollWechatLogin(attemptId) {
2802
3061
  const attempt = this.attempts.get(attemptId);
2803
- if (attempt === void 0) throw new ArkmePluginError("login-attempt-not-found", "登录二维码已失效,请重新获取", false, 404);
3062
+ if (attempt === void 0 || attempt.kind !== "wechat") throw new ArkmePluginError("login-attempt-not-found", "登录二维码已失效,请重新获取", false, 404);
2804
3063
  if (Date.now() >= attempt.expiresAtMillis) {
2805
3064
  this.attempts.delete(attemptId);
2806
3065
  return {
@@ -2834,6 +3093,111 @@ var AuthService = class {
2834
3093
  this.attempts.delete(attemptId);
2835
3094
  return await this.acceptLoginSession(session);
2836
3095
  }
3096
+ async beginJiwoLogin() {
3097
+ if (!jiwoScanLoginAvailable(this.runtime.config)) throw new ArkmePluginError("jiwo-scan-login-disabled", "即我扫码登录当前未启用", false, 403);
3098
+ const attemptGeneration = ++this.jiwoAttemptGeneration;
3099
+ await this.cancelAllJiwoLoginAttempts();
3100
+ const data = await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/app-scan-login/start", {
3101
+ unique_code: await this.runtime.stateStore.uniqueCode(),
3102
+ ref: 0,
3103
+ keep_cancel: true
3104
+ }, void 0, [200]);
3105
+ const ticket = stringValue$1(data.ticket).trim();
3106
+ const pollSecret = stringValue$1(data.poll_secret).trim();
3107
+ const expiresAtMillis = numberValue$21(data.expires_at);
3108
+ if (!/^[A-Za-z0-9_-]{43}$/.test(ticket) || !/^[A-Za-z0-9_-]{43}$/.test(pollSecret) || expiresAtMillis <= Date.now()) throw new ArkmePluginError("jiwo-login-contract-invalid", "即我登录二维码响应不完整", true, 502);
3109
+ if (attemptGeneration !== this.jiwoAttemptGeneration) {
3110
+ await this.cancelJiwoCredentials(ticket, pollSecret);
3111
+ throw new ArkmePluginError("login-attempt-canceled", "登录方式已切换", false, 409);
3112
+ }
3113
+ const shareWebsite = this.runtime.config.shareWebsite ?? "";
3114
+ const qrUrl = new URL("/login/desktop", shareWebsite);
3115
+ qrUrl.searchParams.set("ticket", ticket);
3116
+ const attemptId = crypto.randomUUID();
3117
+ const attempt = {
3118
+ kind: "jiwo",
3119
+ attemptId,
3120
+ ticket,
3121
+ pollSecret,
3122
+ qrContent: qrUrl.toString(),
3123
+ expiresAtMillis
3124
+ };
3125
+ this.attempts.clear();
3126
+ this.attempts.set(attemptId, attempt);
3127
+ return this.jiwoPendingSnapshot(attempt);
3128
+ }
3129
+ async pollJiwoLogin(attemptId) {
3130
+ const attempt = this.attempts.get(attemptId);
3131
+ if (attempt === void 0 || attempt.kind !== "jiwo") throw new ArkmePluginError("login-attempt-not-found", "登录二维码已失效,请重新获取", false, 404);
3132
+ if (Date.now() >= attempt.expiresAtMillis) {
3133
+ await this.cancelJiwoLogin(attemptId);
3134
+ return {
3135
+ status: "expired",
3136
+ environment: this.runtime.config.environment
3137
+ };
3138
+ }
3139
+ const data = await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/app-scan-login/poll", {
3140
+ ticket: attempt.ticket,
3141
+ poll_secret: attempt.pollSecret
3142
+ }, void 0, [200]);
3143
+ if (this.attempts.get(attemptId) !== attempt) return {
3144
+ status: "expired",
3145
+ environment: this.runtime.config.environment
3146
+ };
3147
+ const status = stringValue$1(data.status).trim();
3148
+ if (status === "pending") return this.jiwoPendingSnapshot(attempt);
3149
+ if (status === "expired") {
3150
+ this.attempts.delete(attemptId);
3151
+ return {
3152
+ status: "expired",
3153
+ environment: this.runtime.config.environment
3154
+ };
3155
+ }
3156
+ if (status !== "authenticated") throw new ArkmePluginError("jiwo-login-contract-invalid", "即我扫码登录状态无效", true, 502);
3157
+ const userId = numberValue$21(data.user_id);
3158
+ const accessToken = stringValue$1(data.access_token);
3159
+ const refreshToken = stringValue$1(data.refresh_token);
3160
+ if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录成功响应缺少凭据", false, 502);
3161
+ this.attempts.delete(attemptId);
3162
+ return await this.acceptLoginSession({
3163
+ accessToken,
3164
+ refreshToken,
3165
+ userId
3166
+ });
3167
+ }
3168
+ async cancelJiwoLogin(attemptId) {
3169
+ if (attemptId.trim() === "") return { canceled: true };
3170
+ const attempt = this.attempts.get(attemptId);
3171
+ if (attempt === void 0 || attempt.kind !== "jiwo") return { canceled: true };
3172
+ this.jiwoAttemptGeneration += 1;
3173
+ this.attempts.delete(attemptId);
3174
+ await this.cancelJiwoCredentials(attempt.ticket, attempt.pollSecret);
3175
+ return { canceled: true };
3176
+ }
3177
+ async cancelJiwoCredentials(ticket, pollSecret) {
3178
+ try {
3179
+ await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/app-scan-login/cancel", {
3180
+ ticket,
3181
+ poll_secret: pollSecret
3182
+ }, void 0, [200]);
3183
+ } catch {}
3184
+ }
3185
+ jiwoPendingSnapshot(attempt) {
3186
+ return {
3187
+ status: "pending",
3188
+ environment: this.runtime.config.environment,
3189
+ attemptId: attempt.attemptId,
3190
+ qrContent: attempt.qrContent,
3191
+ expiresAtMillis: attempt.expiresAtMillis
3192
+ };
3193
+ }
3194
+ async cancelAllJiwoLoginAttempts() {
3195
+ const attempts = [...this.attempts.values()].filter((attempt) => attempt.kind === "jiwo");
3196
+ for (const attempt of attempts) this.attempts.delete(attempt.attemptId);
3197
+ await Promise.all(attempts.map(async (attempt) => {
3198
+ await this.cancelJiwoCredentials(attempt.ticket, attempt.pollSecret);
3199
+ }));
3200
+ }
2837
3201
  async testLogin(userId) {
2838
3202
  if (this.runtime.config.environment !== "test") throw new ArkmePluginError("test-login-disabled", "测试账号登录仅允许测试环境使用", false, 403);
2839
3203
  if (!Number.isSafeInteger(userId) || userId <= 0) throw new ArkmePluginError("test-user-id-invalid", "请输入有效的测试账号 user_id", false);
@@ -2964,17 +3328,6 @@ function isArkmeBotAvatarRef(value) {
2964
3328
  return ARKME_BOT_AVATAR_REF_PATTERN.test(value.trim());
2965
3329
  }
2966
3330
  //#endregion
2967
- //#region src/secret-value.ts
2968
- var SecretValue = class {
2969
- #value;
2970
- constructor(value) {
2971
- this.#value = value;
2972
- }
2973
- reveal() {
2974
- return this.#value;
2975
- }
2976
- };
2977
- //#endregion
2978
3331
  //#region src/services/privacy-visibility.ts
2979
3332
  const CONTENT_ACCESS_PROTECTED = 2;
2980
3333
  const TOPIC_PRIVACY_PRIVATE = 2;
@@ -6724,30 +7077,124 @@ const ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES = 20 * 1024 * 1024;
6724
7077
  //#region src/services/chat-service.ts
6725
7078
  const OFFICIAL_AUTHOR_USER_ID = 11;
6726
7079
  const OFFICIAL_AUTHOR_FALLBACK_DISPLAY_NAME = "即我作者";
7080
+ const MAX_MESSAGE_COPY_LINK_ITEMS = 100;
7081
+ const LINK_METADATA_MAX_BYTES = 128 * 1024;
7082
+ const LINK_METADATA_MAX_REDIRECTS = 3;
7083
+ const RECORD_UID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7084
+ const HTML_ENTITY_MAP = {
7085
+ amp: "&",
7086
+ lt: "<",
7087
+ gt: ">",
7088
+ quot: "\"",
7089
+ apos: "'",
7090
+ nbsp: " "
7091
+ };
6727
7092
  function numberValue$16(value) {
6728
7093
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
6729
7094
  }
6730
7095
  function booleanValue$7(value) {
6731
7096
  return value === true;
6732
7097
  }
6733
- function favoriteStickerPersistenceItem(item) {
7098
+ function favoriteStickerPersistenceItem(item) {
7099
+ return {
7100
+ file_asset_uid: stringValue$1(item.file_asset_uid).trim(),
7101
+ file_id: stringValue$1(item.file_id).trim(),
7102
+ file_hash: stringValue$1(item.file_hash).trim(),
7103
+ file_name: stringValue$1(item.file_name).trim(),
7104
+ mime_type: stringValue$1(item.mime_type).trim(),
7105
+ file_kind: Math.trunc(numberValue$16(item.file_kind)),
7106
+ file_type: Math.trunc(numberValue$16(item.file_type)),
7107
+ is_animated: booleanValue$7(item.is_animated),
7108
+ file_size: Math.max(0, Math.trunc(numberValue$16(item.file_size))),
7109
+ file_create_at: Math.max(0, Math.trunc(numberValue$16(item.file_create_at))),
7110
+ file_push_at: Math.max(0, Math.trunc(numberValue$16(item.file_push_at)))
7111
+ };
7112
+ }
7113
+ function listValue$16(value) {
7114
+ return Array.isArray(value) ? value : [];
7115
+ }
7116
+ function htmlTextValue(value) {
7117
+ return value.replace(/<script[\s\S]*?<\/script>/giu, " ").replace(/<style[\s\S]*?<\/style>/giu, " ").replace(/<[^>]+>/gu, " ").replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/giu, (_match, entity) => {
7118
+ const normalized = entity.toLowerCase();
7119
+ if (normalized.startsWith("#x")) {
7120
+ const code = Number.parseInt(normalized.slice(2), 16);
7121
+ return Number.isFinite(code) ? String.fromCodePoint(code) : " ";
7122
+ }
7123
+ if (normalized.startsWith("#")) {
7124
+ const code = Number.parseInt(normalized.slice(1), 10);
7125
+ return Number.isFinite(code) ? String.fromCodePoint(code) : " ";
7126
+ }
7127
+ return HTML_ENTITY_MAP[normalized] ?? " ";
7128
+ }).replace(/\s+/gu, " ").trim();
7129
+ }
7130
+ function firstHtmlMetaContent(html, key) {
7131
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
7132
+ const patterns = [new RegExp(`<meta\\b(?=[^>]*(?:property|name)=["']${escaped}["'])(?=[^>]*content=["']([^"']*)["'])[^>]*>`, "iu"), new RegExp(`<meta\\b(?=[^>]*content=["']([^"']*)["'])(?=[^>]*(?:property|name)=["']${escaped}["'])[^>]*>`, "iu")];
7133
+ for (const pattern of patterns) {
7134
+ const value = pattern.exec(html)?.[1];
7135
+ if (value !== void 0) return htmlTextValue(value);
7136
+ }
7137
+ return "";
7138
+ }
7139
+ function fallbackLinkTitle(url) {
7140
+ const host = url.hostname.replace(/^www\./iu, "");
7141
+ if (host === "github.com") {
7142
+ const parts = url.pathname.split("/").filter(Boolean);
7143
+ if (parts.length >= 4 && parts[2] === "pull") return `Pull Request #${parts[3]} · ${parts[0]}/${parts[1]}`;
7144
+ if (parts.length >= 2) return `${parts[0]}/${parts[1]}`;
7145
+ }
7146
+ if (host === "codeup.aliyun.com" || host.endsWith(".codeup.aliyun.com")) {
7147
+ const parts = url.pathname.split("/").filter(Boolean);
7148
+ const changeIndex = parts.indexOf("change");
7149
+ const repository = changeIndex >= 2 ? parts[changeIndex - 1] : parts.at(-1);
7150
+ const changeNo = changeIndex >= 0 ? parts[changeIndex + 1] : void 0;
7151
+ if (repository !== void 0 && changeNo !== void 0) return `${repository} · Change #${changeNo}`;
7152
+ }
7153
+ return "分享链接";
7154
+ }
7155
+ function ensureMetadataUrl(rawUrl) {
7156
+ let url;
7157
+ try {
7158
+ url = new URL(rawUrl.trim());
7159
+ } catch {
7160
+ throw new ArkmePluginError("link-metadata-url-invalid", "链接暂不可预览", false, 400);
7161
+ }
7162
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new ArkmePluginError("link-metadata-url-invalid", "链接暂不可预览", false, 400);
7163
+ if (url.username !== "" || url.password !== "") throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
7164
+ const hostname = url.hostname.toLowerCase();
7165
+ if (hostname === "localhost" || hostname.endsWith(".local")) throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
7166
+ if (/^(?:127|10|0|169\.254|192\.168)\./u.test(hostname) || /^172\.(?:1[6-9]|2\d|3[0-1])\./u.test(hostname)) throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
7167
+ if (hostname === "::1" || hostname.startsWith("fc") || hostname.startsWith("fd") || hostname.startsWith("fe80")) throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
7168
+ return url;
7169
+ }
7170
+ async function readResponseTextPrefix(response) {
7171
+ if (response.body === null) return await response.text();
7172
+ const reader = response.body.getReader();
7173
+ const chunks = [];
7174
+ let bytes = 0;
7175
+ try {
7176
+ while (bytes < LINK_METADATA_MAX_BYTES) {
7177
+ const next = await reader.read();
7178
+ if (next.done) break;
7179
+ chunks.push(next.value);
7180
+ bytes += next.value.byteLength;
7181
+ }
7182
+ } finally {
7183
+ reader.releaseLock();
7184
+ }
7185
+ return new TextDecoder("utf-8", { fatal: false }).decode(Uint8Array.from(chunks.flatMap((chunk) => [...chunk])));
7186
+ }
7187
+ function linkMetadataFromHtml(url, html) {
7188
+ const title = firstHtmlMetaContent(html, "og:title") || firstHtmlMetaContent(html, "twitter:title") || htmlTextValue(/<title\b[^>]*>([\s\S]*?)<\/title>/iu.exec(html)?.[1] ?? "") || fallbackLinkTitle(url);
7189
+ const description = firstHtmlMetaContent(html, "og:description") || firstHtmlMetaContent(html, "description");
7190
+ const siteName = firstHtmlMetaContent(html, "og:site_name") || url.hostname.replace(/^www\./iu, "");
6734
7191
  return {
6735
- file_asset_uid: stringValue$1(item.file_asset_uid).trim(),
6736
- file_id: stringValue$1(item.file_id).trim(),
6737
- file_hash: stringValue$1(item.file_hash).trim(),
6738
- file_name: stringValue$1(item.file_name).trim(),
6739
- mime_type: stringValue$1(item.mime_type).trim(),
6740
- file_kind: Math.trunc(numberValue$16(item.file_kind)),
6741
- file_type: Math.trunc(numberValue$16(item.file_type)),
6742
- is_animated: booleanValue$7(item.is_animated),
6743
- file_size: Math.max(0, Math.trunc(numberValue$16(item.file_size))),
6744
- file_create_at: Math.max(0, Math.trunc(numberValue$16(item.file_create_at))),
6745
- file_push_at: Math.max(0, Math.trunc(numberValue$16(item.file_push_at)))
7192
+ url: url.toString(),
7193
+ title,
7194
+ ...description === "" ? {} : { description },
7195
+ ...siteName === "" ? {} : { siteName }
6746
7196
  };
6747
7197
  }
6748
- function listValue$16(value) {
6749
- return Array.isArray(value) ? value : [];
6750
- }
6751
7198
  function chatMemberRole$1(value) {
6752
7199
  if (value === "owner" || value === 1) return "owner";
6753
7200
  if (value === "admin" || value === 2) return "admin";
@@ -6768,6 +7215,185 @@ function integerLikeValue$1(value) {
6768
7215
  }
6769
7216
  return 0;
6770
7217
  }
7218
+ function epochMillisValue(value) {
7219
+ const timestamp = integerLikeValue$1(value);
7220
+ return timestamp > 0 && timestamp < 1e11 ? timestamp * 1e3 : timestamp;
7221
+ }
7222
+ function messageCopyLinkMediaItemFromData(value) {
7223
+ const data = objectValue$1(value);
7224
+ return {
7225
+ fileKind: integerLikeValue$1(data.file_kind),
7226
+ fileName: stringValue$1(data.file_name),
7227
+ size: Math.max(0, integerLikeValue$1(data.size))
7228
+ };
7229
+ }
7230
+ function messageCopyLinkStructuredContentFromData(value) {
7231
+ const data = objectValue$1(value);
7232
+ if (Object.keys(data).length === 0) return void 0;
7233
+ return {
7234
+ structuredKind: integerLikeValue$1(data.structured_kind),
7235
+ durationMillis: Math.max(0, integerLikeValue$1(data.duration_millis))
7236
+ };
7237
+ }
7238
+ function messageCopyLinkSnapshotItemFromData(value) {
7239
+ const data = objectValue$1(value);
7240
+ const structuredContent = messageCopyLinkStructuredContentFromData(data.structured_content);
7241
+ const recordUid = stringValue$1(data.record_uid ?? data.recordUid ?? data.uid).trim();
7242
+ const item = {
7243
+ ...recordUid === "" ? {} : { recordUid },
7244
+ sourceKind: stringValue$1(data.source_kind),
7245
+ senderDisplayName: stringValue$1(data.sender_display_name),
7246
+ senderAvatarUrl: stringValue$1(data.sender_avatar_url),
7247
+ title: stringValue$1(data.title),
7248
+ textContent: stringValue$1(data.text_content),
7249
+ sendAtMillis: epochMillisValue(data.send_at),
7250
+ templateKind: integerLikeValue$1(data.template_kind),
7251
+ displayKind: integerLikeValue$1(data.display_kind),
7252
+ officialMark: integerLikeValue$1(data.official_mark),
7253
+ mediaItems: listValue$16(data.media_items).map(messageCopyLinkMediaItemFromData),
7254
+ ...structuredContent === void 0 ? {} : { structuredContent }
7255
+ };
7256
+ if (item.sendAtMillis <= 0 || item.senderDisplayName.trim() === "" && item.sourceKind !== "agent_message") throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
7257
+ if (item.title.trim() === "" && item.textContent.trim() === "" && item.mediaItems.length === 0 && item.structuredContent === void 0) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
7258
+ return item;
7259
+ }
7260
+ function messageCopyLinkPresentationNodesFromData(value, itemCount, depth = 1, seenItemIndexes = /* @__PURE__ */ new Set()) {
7261
+ const rawNodes = listValue$16(value);
7262
+ if (rawNodes.length === 0) return [];
7263
+ if (depth > 8) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
7264
+ return rawNodes.map((rawNode) => {
7265
+ const data = objectValue$1(rawNode);
7266
+ const kind = stringValue$1(data.kind);
7267
+ if (kind === "item") {
7268
+ const itemIndex = integerLikeValue$1(data.item_index);
7269
+ if (itemIndex < 0 || itemIndex >= itemCount || seenItemIndexes.has(itemIndex)) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
7270
+ seenItemIndexes.add(itemIndex);
7271
+ return {
7272
+ kind: "item",
7273
+ itemIndex
7274
+ };
7275
+ }
7276
+ if (kind === "forward_bundle") return {
7277
+ kind: "forward_bundle",
7278
+ title: stringValue$1(data.title),
7279
+ commentText: stringValue$1(data.comment_text),
7280
+ createdAtMillis: integerLikeValue$1(data.created_at),
7281
+ senderDisplayName: stringValue$1(data.sender_display_name),
7282
+ children: messageCopyLinkPresentationNodesFromData(data.children, itemCount, depth + 1, seenItemIndexes)
7283
+ };
7284
+ throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
7285
+ });
7286
+ }
7287
+ function messageCopyLinkSourceAnchorFromData(value) {
7288
+ const data = objectValue$1(value);
7289
+ return {
7290
+ relationUid: stringValue$1(data.rel_uid),
7291
+ recordUid: stringValue$1(data.record_uid),
7292
+ recordOwnerUserId: integerLikeValue$1(data.record_owner_user_id),
7293
+ sequence: integerLikeValue$1(data.seq)
7294
+ };
7295
+ }
7296
+ function firstTextValue(source, keys) {
7297
+ for (const key of keys) {
7298
+ const value = stringValue$1(source[key]).trim();
7299
+ if (value !== "") return value;
7300
+ }
7301
+ return "";
7302
+ }
7303
+ function messageCopyLinkExtensionItemFromData(value) {
7304
+ const data = objectValue$1(value);
7305
+ const core = objectValue$1(data.record_core ?? data.recordCore);
7306
+ const recordUid = firstTextValue(data, [
7307
+ "record_uid",
7308
+ "recordUid",
7309
+ "uid"
7310
+ ]) || firstTextValue(core, [
7311
+ "record_uid",
7312
+ "recordUid",
7313
+ "uid"
7314
+ ]);
7315
+ const senderDisplayName = firstTextValue(data, [
7316
+ "sender_display_name",
7317
+ "senderDisplayName",
7318
+ "nickname",
7319
+ "nick_name",
7320
+ "nickName",
7321
+ "owner_name",
7322
+ "ownerName"
7323
+ ]) || firstTextValue(core, [
7324
+ "sender_display_name",
7325
+ "senderDisplayName",
7326
+ "nickname",
7327
+ "nick_name",
7328
+ "nickName",
7329
+ "owner_name",
7330
+ "ownerName"
7331
+ ]) || "Arkme用户";
7332
+ const sendAtMillis = epochMillisValue(data.send_at ?? data.sendAt ?? data.created_at ?? data.createdAt ?? data.published_at ?? data.publishedAt ?? core.send_at ?? core.sendAt ?? core.created_at ?? core.createdAt ?? core.published_at ?? core.publishedAt);
7333
+ const title = stringValue$1(data.title ?? core.title);
7334
+ const textContent = stringValue$1(data.text_content ?? data.textContent ?? data.content ?? core.text_content ?? core.textContent ?? core.content);
7335
+ const mediaItems = listValue$16(data.media_items ?? data.mediaItems ?? core.media_items ?? core.mediaItems).map(messageCopyLinkMediaItemFromData);
7336
+ const structuredContent = messageCopyLinkStructuredContentFromData(data.structured_content ?? data.structuredContent ?? core.structured_content ?? core.structuredContent);
7337
+ if (recordUid === "" || sendAtMillis <= 0 || title.trim() === "" && textContent.trim() === "" && mediaItems.length === 0 && structuredContent === void 0) return;
7338
+ return {
7339
+ recordUid,
7340
+ level: Math.max(2, integerLikeValue$1(data.level) || 2),
7341
+ sourceKind: stringValue$1(data.source_kind ?? data.sourceKind) || "record_extension",
7342
+ senderDisplayName,
7343
+ senderAvatarUrl: firstTextValue(data, [
7344
+ "sender_avatar_url",
7345
+ "senderAvatarUrl",
7346
+ "avatar_ref",
7347
+ "avatarRef",
7348
+ "avatar_url",
7349
+ "avatarUrl",
7350
+ "avatar"
7351
+ ]),
7352
+ title,
7353
+ textContent,
7354
+ sendAtMillis,
7355
+ templateKind: integerLikeValue$1(data.template_kind ?? data.templateKind ?? core.template_kind ?? core.templateKind) || 1,
7356
+ displayKind: integerLikeValue$1(data.display_kind ?? data.displayKind ?? core.display_kind ?? core.displayKind),
7357
+ officialMark: integerLikeValue$1(data.official_mark ?? data.officialMark ?? core.official_mark ?? core.officialMark),
7358
+ mediaItems,
7359
+ ...structuredContent === void 0 ? {} : { structuredContent }
7360
+ };
7361
+ }
7362
+ function messageCopyLinkRecordContextFromData(data) {
7363
+ const context = objectValue$1(data.record_context ?? data.recordContext);
7364
+ const extensions = listValue$16(context.extensions ?? context.extend_records ?? context.extendRecords ?? context.list ?? data.extensions ?? data.extend_records ?? data.extendRecords ?? data.list).map(messageCopyLinkExtensionItemFromData).filter((item) => item !== void 0);
7365
+ const extensionCount = Math.max(integerLikeValue$1(context.extension_count ?? context.extensionCount ?? context.total ?? data.extension_count ?? data.extensionCount ?? data.total), extensions.length);
7366
+ if (extensionCount <= 0 && extensions.length === 0) return void 0;
7367
+ extensions.sort((left, right) => right.sendAtMillis - left.sendAtMillis);
7368
+ return {
7369
+ extensionCount,
7370
+ extensions
7371
+ };
7372
+ }
7373
+ function mergeMessageCopyLinkRecordContexts(left, right) {
7374
+ if (left === void 0) return right;
7375
+ if (right === void 0) return left;
7376
+ const byUid = /* @__PURE__ */ new Map();
7377
+ for (const item of [...left.extensions, ...right.extensions]) byUid.set(item.recordUid, item);
7378
+ const extensions = [...byUid.values()].sort((a, b) => b.sendAtMillis - a.sendAtMillis);
7379
+ return {
7380
+ extensionCount: Math.max(left.extensionCount, right.extensionCount, extensions.length),
7381
+ extensions
7382
+ };
7383
+ }
7384
+ function messageCopyLinkWorldTags(text) {
7385
+ return [...text.matchAll(/#(\S+)/gu)].map((match) => match[1]?.trim() ?? "").filter((tag) => tag !== "");
7386
+ }
7387
+ function messageCopyLinkRecordUidCandidates(items, anchors) {
7388
+ const candidates = [];
7389
+ const push = (value) => {
7390
+ const normalized = value?.trim() ?? "";
7391
+ if (normalized !== "" && !candidates.includes(normalized)) candidates.push(normalized);
7392
+ };
7393
+ for (const item of items) push(item.recordUid);
7394
+ for (const anchor of anchors) push(anchor.recordUid);
7395
+ return candidates;
7396
+ }
6771
7397
  function optionalString$2(value) {
6772
7398
  const text = stringValue$1(value).trim();
6773
7399
  return text === "" ? void 0 : text;
@@ -7126,6 +7752,8 @@ var ChatService = class {
7126
7752
  }
7127
7753
  async openOfficialAuthorPrivateChat(options = {}) {
7128
7754
  const session = await this.runtime.requireSession();
7755
+ const existing = await this.findPrivateChatByPeerUserId(OFFICIAL_AUTHOR_USER_ID, session, options.signal);
7756
+ if (existing !== void 0) return { source: existing };
7129
7757
  const created = await this.runtime.authenticatedSubjectPost("/api/v1/private/create-chat-ref-asen", {
7130
7758
  subject_uid: `dsh_official_author_${randomUUID().replace(/-/g, "")}`,
7131
7759
  network: "dsh",
@@ -7147,6 +7775,25 @@ var ChatService = class {
7147
7775
  ...options.signal === void 0 ? {} : { signal: options.signal }
7148
7776
  });
7149
7777
  }
7778
+ /**
7779
+ * Contact-author is an entry point, not a second conversation type. Scan the
7780
+ * ordinary directory first so repeated clicks always reuse the existing chat.
7781
+ */
7782
+ async findPrivateChatByPeerUserId(peerUserId, session, signal) {
7783
+ let cursor;
7784
+ for (let pageIndex = 0; pageIndex < 10; pageIndex += 1) {
7785
+ const page = await this.source.listSources("root", {
7786
+ limit: 50,
7787
+ refresh: true,
7788
+ ...cursor === void 0 ? {} : { cursor },
7789
+ ...signal === void 0 ? {} : { signal }
7790
+ });
7791
+ const existing = page.items.find((source) => source.kind === "private_chat" && source.peerUserId === peerUserId);
7792
+ if (existing !== void 0) return existing;
7793
+ if (!page.hasMore || page.nextCursor === void 0) return void 0;
7794
+ cursor = page.nextCursor;
7795
+ }
7796
+ }
7150
7797
  async officialAuthorProfile(options = {}) {
7151
7798
  const session = await this.runtime.requireSession();
7152
7799
  const profile = (await this.profile.publicProfileSummariesByUserIds([OFFICIAL_AUTHOR_USER_ID], session, options.signal)).get(OFFICIAL_AUTHOR_USER_ID);
@@ -7209,9 +7856,10 @@ var ChatService = class {
7209
7856
  sendAtMillis: options.cursor.sendAtMillis,
7210
7857
  recordUid: options.cursor.itemUid
7211
7858
  } : void 0);
7859
+ const signingKey = await this.runtime.stateStore.uniqueCode();
7212
7860
  return {
7213
7861
  source: await this.source.sourceItem(source),
7214
- items: page.items.map((item) => this.record.recordTimelineItem(item)),
7862
+ items: page.items.map((item) => this.withRecordMessageActionRef(source, this.record.recordTimelineItem(item), session.userId, signingKey)),
7215
7863
  hasMore: page.hasMore,
7216
7864
  ...page.nextCursor === void 0 ? {} : { nextCursor: {
7217
7865
  sendAtMillis: page.nextCursor.sendAtMillis,
@@ -7447,6 +8095,329 @@ var ChatService = class {
7447
8095
  readReceiptItemKey(itemUid, sequence) {
7448
8096
  return `${itemUid}\u0000${String(sequence)}`;
7449
8097
  }
8098
+ async copySourceMessageLink(sourceRef, actionRefs, options = {}) {
8099
+ const session = await this.runtime.requireSession();
8100
+ const source = await this.source.openSourceRef(sourceRef, session.userId);
8101
+ const references = this.forwardReferencesOrderedForCommand(await this.openMessageActionRefs(actionRefs, session.userId, source));
8102
+ if (references.length === 0 || references.length > 100) throw new ArkmePluginError("message-actions-selection-invalid", "请选择 1 至 100 条消息", false);
8103
+ this.validateForwardReferences(references);
8104
+ const sources = references.map((reference) => reference.sourceKind === "chat_relation" ? {
8105
+ kind: "chat_relation",
8106
+ chat_session_uid: reference.chatSessionUid,
8107
+ relation_uid: reference.relationUid
8108
+ } : {
8109
+ kind: "record",
8110
+ record_owner_user_id: reference.recordOwnerUserId,
8111
+ record_uid: reference.recordUid
8112
+ });
8113
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/messages/copy-link/get-or-create", { sources }, session, options.signal);
8114
+ const sid = stringValue$1(data.sid).trim();
8115
+ const url = stringValue$1(data.url).trim();
8116
+ if (sid === "" || !/^https?:\/\//i.test(url)) throw new ArkmePluginError("message-copy-link-response-invalid", "复制链接失败,请稍后重试", true, 502);
8117
+ return {
8118
+ sid,
8119
+ url
8120
+ };
8121
+ }
8122
+ async resolveMessageCopyLink(sid, options = {}) {
8123
+ const normalizedSid = sid.trim();
8124
+ if (!/^[0-9A-Za-z]{16}$/.test(normalizedSid)) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
8125
+ const session = await this.runtime.requireSession();
8126
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/messages/copy-link/resolve", { sid: normalizedSid }, session, options.signal);
8127
+ const rawItems = listValue$16(data.items);
8128
+ if (rawItems.length === 0 && stringValue$1(data.audit_status) === "auditing") throw new ArkmePluginError("message-copy-link-auditing", "审核中", true, 409);
8129
+ const items = rawItems.map(messageCopyLinkSnapshotItemFromData);
8130
+ if (items.length === 0 || items.length > MAX_MESSAGE_COPY_LINK_ITEMS) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
8131
+ const presentation = messageCopyLinkPresentationNodesFromData(data.presentation, items.length);
8132
+ const sourceContext = objectValue$1(data.source_context);
8133
+ const accessMode = stringValue$1(data.access_mode);
8134
+ if (accessMode !== "normal" && accessMode !== "link_read_only") throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
8135
+ const anchors = accessMode === "normal" ? listValue$16(sourceContext.anchors).map(messageCopyLinkSourceAnchorFromData).filter((anchor) => anchor.relationUid.trim() !== "" && anchor.recordUid.trim() !== "" && anchor.recordOwnerUserId > 0) : [];
8136
+ const recordContext = mergeMessageCopyLinkRecordContexts(messageCopyLinkRecordContextFromData(data), accessMode === "normal" ? await this.loadMessageCopyLinkPublicExtensions(messageCopyLinkRecordUidCandidates(items, anchors), session, options.signal) : void 0);
8137
+ const result = {
8138
+ sid: stringValue$1(data.sid),
8139
+ displayTitle: stringValue$1(data.display_title),
8140
+ generatedAtMillis: epochMillisValue(data.generated_at),
8141
+ accessMode,
8142
+ items,
8143
+ presentation,
8144
+ ...accessMode === "normal" ? {
8145
+ sourceSessionUid: stringValue$1(sourceContext.chat_session_uid),
8146
+ sourceAnchors: anchors
8147
+ } : {},
8148
+ ...recordContext === void 0 ? {} : { recordContext }
8149
+ };
8150
+ if (result.sid !== normalizedSid || accessMode === "normal" && (result.sourceSessionUid === void 0 || result.sourceSessionUid.trim() === "" || anchors.length === 0)) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
8151
+ return result;
8152
+ }
8153
+ async loadMessageCopyLinkPublicExtensions(recordUids, session, signal) {
8154
+ const worldPost = this.runtime.authenticatedWorldPost;
8155
+ const candidates = recordUids.map((recordUid) => recordUid.trim()).filter((recordUid, index, array) => recordUid !== "" && array.indexOf(recordUid) === index).slice(0, 3);
8156
+ if (candidates.length === 0 || worldPost === void 0) return void 0;
8157
+ for (const normalizedRecordUid of candidates) {
8158
+ const extensions = [];
8159
+ let offset = 0;
8160
+ let total = 0;
8161
+ let hasMore;
8162
+ try {
8163
+ while (true) {
8164
+ const data = await worldPost.call(this.runtime, "/api/v1/public-record/extend-list", {
8165
+ record_uid: normalizedRecordUid,
8166
+ limit: 50,
8167
+ offset
8168
+ }, session, signal, {
8169
+ lane: "interactive-read",
8170
+ bypassCache: true
8171
+ });
8172
+ const rawItems = listValue$16(data.list ?? data.items);
8173
+ if (offset === 0) total = Math.max(0, integerLikeValue$1(data.total));
8174
+ const pageItems = rawItems.map((raw) => messageCopyLinkExtensionItemFromData(raw)).filter((item) => item !== void 0);
8175
+ extensions.push(...pageItems);
8176
+ if (rawItems.length === 0) break;
8177
+ offset += rawItems.length;
8178
+ hasMore = data.has_more === true || data.hasMore === true ? true : data.has_more === false || data.hasMore === false ? false : void 0;
8179
+ if (total > 0 && offset >= total) break;
8180
+ if (hasMore === false) break;
8181
+ if (hasMore === void 0 && total <= 0) break;
8182
+ }
8183
+ } catch {
8184
+ continue;
8185
+ }
8186
+ const extensionCount = Math.max(total, extensions.length);
8187
+ if (extensionCount <= 0 && extensions.length === 0) continue;
8188
+ extensions.sort((left, right) => right.sendAtMillis - left.sendAtMillis);
8189
+ return {
8190
+ extensionCount,
8191
+ extensions
8192
+ };
8193
+ }
8194
+ }
8195
+ async extendMessageCopyLink(sid, itemIndex, textContent, recordUid, options = {}) {
8196
+ const normalizedSid = sid.trim();
8197
+ const normalizedIndex = Math.trunc(itemIndex);
8198
+ const normalizedUid = recordUid.trim();
8199
+ const normalizedText = textContent.trim();
8200
+ if (!/^[0-9A-Za-z]{16}$/.test(normalizedSid) || normalizedIndex < 0 || normalizedIndex >= MAX_MESSAGE_COPY_LINK_ITEMS) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
8201
+ if (!RECORD_UID_PATTERN.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
8202
+ if (normalizedText === "") throw new ArkmePluginError("record-text-empty", "请输入延展内容", false);
8203
+ if (normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-text-too-long", `内容不能超过 ${this.runtime.config.maxTextLength} 个字符`, false);
8204
+ const session = await this.runtime.requireSession();
8205
+ const detail = await this.resolveMessageCopyLink(normalizedSid, options);
8206
+ const item = detail.items[normalizedIndex];
8207
+ const anchor = detail.accessMode === "normal" ? detail.sourceAnchors?.[normalizedIndex] : void 0;
8208
+ const parentRecordUid = item === void 0 ? "" : messageCopyLinkRecordUidCandidates([item], anchor === void 0 ? [] : [anchor])[0] ?? "";
8209
+ if (item === void 0 || parentRecordUid === "") throw new ArkmePluginError("message-copy-link-extension-unavailable", "链接暂不可延展", false, 409);
8210
+ const profile = (await this.profile.refreshProfile()).profile;
8211
+ if (profile === null) throw new ArkmePluginError("profile-unavailable", "无法读取当前 Arkme 账号资料", true);
8212
+ if (profile.contact.phoneMasked === void 0) throw new ArkmePluginError("world-phone-binding-required", "请先在 Arkme 客户端绑定手机号,再延展快记", false);
8213
+ const sendAtMillis = Date.now();
8214
+ const recordResult = await this.record.createTextForConversation(normalizedUid, normalizedText);
8215
+ if (recordResult.localState !== "synced") throw new ArkmePluginError("message-copy-link-extension-record-pending", recordResult.error ?? "延展已保存到待重试队列,请稍后重试", true);
8216
+ const createdRecordUid = recordResult.recordUid.trim() || normalizedUid;
8217
+ const data = await this.runtime.authenticatedWorldPost("/api/v1/public-record/publish", {
8218
+ record_uid: createdRecordUid,
8219
+ content: normalizedText,
8220
+ text_content: normalizedText,
8221
+ tags: messageCopyLinkWorldTags(normalizedText),
8222
+ original_topic_id: 0,
8223
+ created_at: sendAtMillis,
8224
+ nick_name: profile.nickname || profile.displayName,
8225
+ avatar: profile.avatarRef,
8226
+ template_kind: 1,
8227
+ parent_record_uid: parentRecordUid
8228
+ }, session, options.signal);
8229
+ const publishedRecordUid = stringValue$1(data.record_uid).trim() || createdRecordUid;
8230
+ const senderDisplayName = profile.nickname.trim() || profile.displayName.trim() || "我";
8231
+ const senderAvatarUrl = profile.avatarRef.trim();
8232
+ this.source.invalidateSourceListCache(session.userId, "send_to_self");
8233
+ this.realtime.emitChatClientEvent({
8234
+ type: "projection-invalidated",
8235
+ revision: this.realtime.nextChatClientRevision(),
8236
+ projection: "record"
8237
+ });
8238
+ return {
8239
+ sid: detail.sid,
8240
+ recordUid: publishedRecordUid,
8241
+ parentRecordUid,
8242
+ status: numberValue$16(data.status ?? data.check_status ?? recordResult.status),
8243
+ localState: "synced",
8244
+ extension: {
8245
+ recordUid: publishedRecordUid,
8246
+ level: 2,
8247
+ sourceKind: "record_extension",
8248
+ senderDisplayName,
8249
+ ...senderAvatarUrl === "" ? {} : { senderAvatarUrl },
8250
+ title: "",
8251
+ textContent: normalizedText,
8252
+ sendAtMillis,
8253
+ templateKind: 1,
8254
+ displayKind: 0,
8255
+ officialMark: 0,
8256
+ mediaItems: []
8257
+ }
8258
+ };
8259
+ }
8260
+ async resolveLinkMetadata(rawUrl, options = {}) {
8261
+ const url = ensureMetadataUrl(rawUrl);
8262
+ try {
8263
+ return await this.fetchLinkMetadata(url, 0, options.signal);
8264
+ } catch (error) {
8265
+ if (error instanceof ArkmePluginError && (error.code === "link-metadata-url-invalid" || error.code === "link-metadata-url-forbidden")) throw error;
8266
+ return {
8267
+ url: url.toString(),
8268
+ title: fallbackLinkTitle(url),
8269
+ siteName: url.hostname.replace(/^www\./iu, "")
8270
+ };
8271
+ }
8272
+ }
8273
+ async fetchLinkMetadata(url, redirectCount, signal) {
8274
+ const controller = new AbortController();
8275
+ const abort = () => {
8276
+ controller.abort(signal?.reason);
8277
+ };
8278
+ if (signal?.aborted === true) abort();
8279
+ else signal?.addEventListener("abort", abort, { once: true });
8280
+ const timeout = setTimeout(() => {
8281
+ controller.abort();
8282
+ }, Math.min(8e3, this.runtime.config.requestTimeoutMs));
8283
+ try {
8284
+ const response = await this.runtime.fetchImpl(url, {
8285
+ method: "GET",
8286
+ redirect: "manual",
8287
+ headers: {
8288
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
8289
+ "Accept-Language": "zh-CN",
8290
+ "User-Agent": "Mozilla/5.0 ArkmeDSHPlugin/0.1 LinkPreview"
8291
+ },
8292
+ signal: controller.signal
8293
+ });
8294
+ if (response.status >= 300 && response.status < 400) {
8295
+ if (redirectCount >= LINK_METADATA_MAX_REDIRECTS) return {
8296
+ url: url.toString(),
8297
+ title: fallbackLinkTitle(url),
8298
+ siteName: url.hostname.replace(/^www\./iu, "")
8299
+ };
8300
+ const location = response.headers.get("location");
8301
+ if (location === null || location.trim() === "") return {
8302
+ url: url.toString(),
8303
+ title: fallbackLinkTitle(url),
8304
+ siteName: url.hostname.replace(/^www\./iu, "")
8305
+ };
8306
+ return await this.fetchLinkMetadata(ensureMetadataUrl(new URL(location, url).toString()), redirectCount + 1, signal);
8307
+ }
8308
+ if (!response.ok) return {
8309
+ url: url.toString(),
8310
+ title: fallbackLinkTitle(url),
8311
+ siteName: url.hostname.replace(/^www\./iu, "")
8312
+ };
8313
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
8314
+ if (contentType !== "" && !contentType.includes("html") && !contentType.includes("xml") && !contentType.includes("text/")) return {
8315
+ url: url.toString(),
8316
+ title: fallbackLinkTitle(url),
8317
+ siteName: url.hostname.replace(/^www\./iu, "")
8318
+ };
8319
+ return linkMetadataFromHtml(url, await readResponseTextPrefix(response));
8320
+ } finally {
8321
+ clearTimeout(timeout);
8322
+ signal?.removeEventListener("abort", abort);
8323
+ }
8324
+ }
8325
+ async forwardSourceMessages(sourceRef, actionRefs, options = {}) {
8326
+ const session = await this.runtime.requireSession();
8327
+ const source = await this.source.openSourceRef(sourceRef, session.userId);
8328
+ const targetSourceRef = options.targetSourceRef?.trim() || sourceRef;
8329
+ const targetSource = targetSourceRef === sourceRef ? source : await this.source.openSourceRef(targetSourceRef, session.userId);
8330
+ const commentText = options.commentText?.trim() ?? "";
8331
+ if (commentText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("source-text-invalid", "发送内容超过长度限制", false);
8332
+ if (targetSource.kind !== "private_chat" && targetSource.kind !== "group_chat" && targetSource.kind !== "send_to_self" && targetSource.kind !== "default_category" && targetSource.kind !== "topic") throw new ArkmePluginError("message-actions-source-invalid", "当前数据源暂不支持转发", false, 409);
8333
+ const references = this.forwardReferencesOrderedForCommand(await this.openMessageActionRefs(actionRefs, session.userId, source));
8334
+ if (references.length === 0 || references.length > 100) throw new ArkmePluginError("message-actions-selection-invalid", "请选择 1 至 100 条消息", false);
8335
+ this.validateForwardReferences(references);
8336
+ const recordUid = options.recordUid?.trim() || randomUUID();
8337
+ const title = references.length === 1 ? "转发快记" : `转发 ${String(references.length)} 条快记`;
8338
+ const contentPayload = {
8339
+ payload_kind: 1,
8340
+ schema_version: 1,
8341
+ text_state: 1,
8342
+ forward_records: {
8343
+ render_kind: "forward_records",
8344
+ schema_version: 1,
8345
+ source_type: "quick_records",
8346
+ title: "转发快记",
8347
+ source_record_uids: references.map((reference) => reference.recordUid).filter((recordUid) => recordUid.trim() !== ""),
8348
+ created_at: Date.now(),
8349
+ summary_lines: references.slice(0, 3).map((reference) => `${reference.senderName}: ${reference.textContent.trim() || reference.title.trim() || "非文本内容"}`),
8350
+ items: references.map((reference, index) => this.forwardPayloadItem(reference, index))
8351
+ }
8352
+ };
8353
+ const appendCommentWarning = async (sent) => {
8354
+ if (commentText === "") return sent;
8355
+ try {
8356
+ await this.sendSourceText(targetSourceRef, commentText, { ...options.signal === void 0 ? {} : { signal: options.signal } });
8357
+ return sent;
8358
+ } catch {
8359
+ return {
8360
+ ...sent,
8361
+ warningText: "转发已完成,附言发送失败"
8362
+ };
8363
+ }
8364
+ };
8365
+ if (targetSource.kind === "private_chat" || targetSource.kind === "group_chat") {
8366
+ const sendAtMillis = Date.now();
8367
+ const clientRequestId = this.forwardClientRequestId(targetSource.ownerRef, references, sendAtMillis);
8368
+ const sourceRecordUids = references.some((reference) => reference.sourceKind === "chat_relation") ? [] : references.map((reference) => reference.recordUid).filter((recordUid) => recordUid.trim() !== "");
8369
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/records/forward", {
8370
+ chat_session_uid: targetSource.ownerRef,
8371
+ client_request_id: clientRequestId,
8372
+ ...sourceRecordUids.length === 0 ? {} : { source_record_uids: sourceRecordUids },
8373
+ source_items: references.map((reference) => this.forwardSourceItemBody(reference)),
8374
+ ...commentText === "" ? {} : { comment_text: commentText },
8375
+ send_at: sendAtMillis
8376
+ }, session, options.signal);
8377
+ const sequence = numberValue$16(data.seq ?? data.sequence);
8378
+ this.realtime.scheduleChatSessionProjection(targetSource.ownerRef, sequence);
8379
+ return {
8380
+ sourceRef: targetSourceRef,
8381
+ itemUid: stringValue$1(data.record_uid).trim() || recordUid,
8382
+ status: numberValue$16(data.audit_status ?? data.status) || 1,
8383
+ ...sequence > 0 ? { sequence } : {},
8384
+ localState: "synced"
8385
+ };
8386
+ }
8387
+ if (targetSource.kind === "topic") {
8388
+ const data = await this.runtime.authenticatedPost("/api/v1/topics/records/create", {
8389
+ topic_uid: targetSource.ownerRef,
8390
+ record_uid: recordUid,
8391
+ template_kind: 1,
8392
+ title: "",
8393
+ text_content: title,
8394
+ content_payload: contentPayload,
8395
+ send_at: Date.now()
8396
+ }, session, options.signal);
8397
+ this.source.invalidateSourceListCache(session.userId, "send_to_self");
8398
+ return await appendCommentWarning({
8399
+ sourceRef: targetSourceRef,
8400
+ itemUid: stringValue$1(data.record_uid).trim() || recordUid,
8401
+ status: numberValue$16(data.status),
8402
+ localState: "synced"
8403
+ });
8404
+ }
8405
+ const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
8406
+ record_uid: recordUid,
8407
+ template_kind: 1,
8408
+ title: "",
8409
+ text_content: title,
8410
+ content_payload: contentPayload,
8411
+ send_at: Date.now()
8412
+ }, session, options.signal);
8413
+ this.source.invalidateSourceListCache(session.userId, "send_to_self");
8414
+ return await appendCommentWarning({
8415
+ sourceRef: targetSourceRef,
8416
+ itemUid: stringValue$1(data.record_uid).trim() || recordUid,
8417
+ status: numberValue$16(data.status),
8418
+ localState: "synced"
8419
+ });
8420
+ }
7450
8421
  async sendSourceText(sourceRef, textContent, options = {}) {
7451
8422
  const session = await this.runtime.requireSession();
7452
8423
  const source = await this.source.openSourceRef(sourceRef, session.userId);
@@ -7700,9 +8671,6 @@ var ChatService = class {
7700
8671
  localState: "synced"
7701
8672
  };
7702
8673
  }
7703
- async sendGroupSourceTextWithAiPolish(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, options = {}) {
7704
- return await this.aiPolish.sendGroupSourceTextWithAiPolish(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, options);
7705
- }
7706
8674
  async sendSourceRich(sourceRef, input, options = {}) {
7707
8675
  if (this.runtime.config.richMediaSendEnabled === false) throw new ArkmePluginError("rich-content-disabled", "富内容发送已被插件配置关闭", false, 403);
7708
8676
  const session = await this.runtime.requireSession();
@@ -7932,12 +8900,6 @@ var ChatService = class {
7932
8900
  async fetchMedia(mediaRef, range, signal) {
7933
8901
  return await this.media.fetchMedia(mediaRef, range, signal);
7934
8902
  }
7935
- currentUserAgentSourceFallback(userId, source) {
7936
- return this.arko.currentUserAgentSourceFallback(userId, source);
7937
- }
7938
- async agentSourceDisplayName(session) {
7939
- return await this.arko.agentSourceDisplayName(session);
7940
- }
7941
8903
  async sendDirectText(recipientArkmeId, textContent, options = {}) {
7942
8904
  const session = await this.runtime.requireSession();
7943
8905
  const recipient = recipientArkmeId.trim();
@@ -8030,6 +8992,7 @@ var ChatService = class {
8030
8992
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
8031
8993
  const rawAgentSource = timelineAgentSource(relation, record, payload);
8032
8994
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
8995
+ const contentBlocks = this.media.richContentBlocks(item, session.userId);
8033
8996
  const mentionsViewer = senderUserId !== session.userId && arkmeMentionMetadataMentionsViewer(record, payload, session.userId);
8034
8997
  items.push({
8035
8998
  itemUid: uid,
@@ -8048,7 +9011,7 @@ var ChatService = class {
8048
9011
  ...aiPolish === void 0 ? {} : { aiPolish },
8049
9012
  ...forwardRecords === void 0 ? {} : { forwardRecords },
8050
9013
  displayKind: numberValue$16(payload.display_kind),
8051
- contentBlocks: this.media.richContentBlocks(item, session.userId)
9014
+ contentBlocks
8052
9015
  });
8053
9016
  }
8054
9017
  return items;
@@ -8218,11 +9181,31 @@ var ChatService = class {
8218
9181
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
8219
9182
  const rawAgentSource = timelineAgentSource(relation, record, payload);
8220
9183
  const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
9184
+ const contentBlocks = this.media.richContentBlocks(item, session.userId);
9185
+ const senderName = stringValue$1(relation.display_name_snapshot).trim() || "Arkme用户";
8221
9186
  const itemIndex = items.push({
8222
9187
  itemUid: uid,
8223
9188
  ...source.kind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : { messageRef: this.sealMessageRef(session.userId, source.ownerRef, relationUid, signingKey) },
9189
+ ...relationUid === "" ? {} : { messageActionRef: this.sealMessageActionRef(this.messageActionRefPayload({
9190
+ sourceKind: "chat_relation",
9191
+ userId: session.userId,
9192
+ sourceOwnerRef: source.ownerRef,
9193
+ chatSessionUid: source.ownerRef,
9194
+ relationUid,
9195
+ recordOwnerUserId: 0,
9196
+ recordUid: uid,
9197
+ senderUserId,
9198
+ senderName,
9199
+ title: stringValue$1(payload.title),
9200
+ textContent: stringValue$1(payload.text_content),
9201
+ sendAtMillis,
9202
+ sourceSequence: numberValue$16(relation.seq),
9203
+ templateKind: numberValue$16(payload.template_kind),
9204
+ displayKind: numberValue$16(payload.display_kind),
9205
+ contentBlocks
9206
+ }), signingKey) },
8224
9207
  ...senderUserId > 0 ? { memberRef: await this.sealChatMemberRef(session.userId, source.ownerRef, senderUserId) } : {},
8225
- senderName: stringValue$1(relation.display_name_snapshot).trim() || "Arkme用户",
9208
+ senderName,
8226
9209
  ...agentSource === void 0 ? {} : { agentSource },
8227
9210
  isMe: senderUserId === session.userId,
8228
9211
  sendAtMillis,
@@ -8238,7 +9221,7 @@ var ChatService = class {
8238
9221
  updateAtMillis: numberValue$16(payload.update_at ?? record.update_at),
8239
9222
  recordDurationMillis: numberValue$16(payload.record_duration_millis),
8240
9223
  editDurationMillis: numberValue$16(payload.edit_duration_millis),
8241
- contentBlocks: this.media.richContentBlocks(item, session.userId),
9224
+ contentBlocks,
8242
9225
  ...forwardRecords === void 0 ? {} : { forwardRecords }
8243
9226
  }) - 1;
8244
9227
  if (senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
@@ -8283,6 +9266,176 @@ var ChatService = class {
8283
9266
  if (raw.version !== 1 || result.viewerUserId !== expectedViewerUserId || result.chatSessionUid !== expectedChatSessionUid || !Number.isSafeInteger(result.targetUserId) || result.targetUserId <= 0) throw new ArkmePluginError("chat-member-ref-invalid", "聊天成员引用与当前账号或会话不匹配", false, 403);
8284
9267
  return result;
8285
9268
  }
9269
+ messageActionRefPayload(input) {
9270
+ const contentBlocks = input.contentBlocks ?? [];
9271
+ const fileNames = contentBlocks.filter((block) => block.kind === "file" && block.fileName.trim() !== "").slice(0, 5).map((block) => block.fileName.trim().slice(0, 256));
9272
+ return {
9273
+ version: 1,
9274
+ sourceKind: input.sourceKind,
9275
+ userId: input.userId,
9276
+ sourceOwnerRef: input.sourceOwnerRef,
9277
+ chatSessionUid: input.chatSessionUid,
9278
+ relationUid: input.relationUid,
9279
+ recordOwnerUserId: Math.trunc(input.recordOwnerUserId),
9280
+ recordUid: input.recordUid,
9281
+ senderUserId: Math.trunc(input.senderUserId),
9282
+ senderName: input.senderName.trim() || "Arkme用户",
9283
+ title: input.title.slice(0, 500),
9284
+ textContent: input.textContent.slice(0, this.runtime.config.maxTextLength),
9285
+ sendAtMillis: Math.trunc(input.sendAtMillis),
9286
+ sourceSequence: Math.max(0, Math.trunc(numberValue$16(input.sourceSequence))),
9287
+ templateKind: Math.trunc(input.templateKind),
9288
+ displayKind: Math.trunc(input.displayKind),
9289
+ imageCount: contentBlocks.filter((block) => block.kind === "image").length,
9290
+ voiceCount: contentBlocks.filter((block) => block.kind === "audio").length,
9291
+ fileCount: contentBlocks.filter((block) => block.kind === "file").length,
9292
+ fileNames
9293
+ };
9294
+ }
9295
+ withRecordMessageActionRef(source, item, userId, signingKey) {
9296
+ if (source.kind !== "send_to_self" && source.kind !== "default_category" && source.kind !== "topic" || item.itemUid.trim() === "" || !item.isMe) return item;
9297
+ return {
9298
+ ...item,
9299
+ messageActionRef: this.sealMessageActionRef(this.messageActionRefPayload({
9300
+ sourceKind: "record",
9301
+ userId,
9302
+ sourceOwnerRef: source.ownerRef,
9303
+ chatSessionUid: "",
9304
+ relationUid: "",
9305
+ recordOwnerUserId: userId,
9306
+ recordUid: item.itemUid,
9307
+ senderUserId: userId,
9308
+ senderName: item.senderName,
9309
+ title: item.title,
9310
+ textContent: item.textContent,
9311
+ sendAtMillis: item.sendAtMillis,
9312
+ templateKind: item.templateKind ?? 1,
9313
+ displayKind: item.displayKind ?? 0,
9314
+ contentBlocks: item.contentBlocks
9315
+ }), signingKey)
9316
+ };
9317
+ }
9318
+ sealMessageActionRef(payload, signingKey) {
9319
+ const encoded = encodeOpaqueJson$5(payload);
9320
+ return `arkme-message-action-v1.${encoded}.${createHmac("sha256", signingKey).update(encoded).digest("base64url")}`;
9321
+ }
9322
+ async openMessageActionRefs(actionRefs, expectedUserId, source) {
9323
+ const normalizedRefs = actionRefs.map((value) => value.trim()).filter((value) => value !== "");
9324
+ const uniqueRefs = new Set(normalizedRefs);
9325
+ if (normalizedRefs.length !== uniqueRefs.size) throw new ArkmePluginError("message-actions-selection-invalid", "不能选择重复消息", false);
9326
+ const references = [];
9327
+ const relationIdentities = /* @__PURE__ */ new Set();
9328
+ for (const actionRef of normalizedRefs) {
9329
+ const reference = await this.openMessageActionRef(actionRef, expectedUserId, source);
9330
+ const relationIdentity = reference.sourceKind === "chat_relation" ? `chat_relation\u0000${reference.chatSessionUid}\u0000${reference.relationUid}` : `record\u0000${String(reference.recordOwnerUserId)}\u0000${reference.recordUid}`;
9331
+ if (!relationIdentities.add(relationIdentity)) throw new ArkmePluginError("message-actions-selection-invalid", "不能选择重复消息", false);
9332
+ references.push(reference);
9333
+ }
9334
+ return references;
9335
+ }
9336
+ async openMessageActionRef(actionRef, expectedUserId, source) {
9337
+ const parts = actionRef.trim().split(".");
9338
+ if (parts.length !== 3 || parts[0] !== "arkme-message-action-v1") throw new ArkmePluginError("message-action-ref-invalid", "消息操作引用无效,请刷新后重试", false);
9339
+ const payload = parts[1] ?? "";
9340
+ const supplied = Buffer.from(parts[2] ?? "", "base64url");
9341
+ const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(payload).digest();
9342
+ if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("message-action-ref-invalid", "消息操作引用无效,请刷新后重试", false);
9343
+ let parsed;
9344
+ try {
9345
+ parsed = objectValue$1(decodeOpaqueJson$5(payload));
9346
+ } catch (error) {
9347
+ throw new ArkmePluginError("message-action-ref-invalid", "消息操作引用无效,请刷新后重试", false, 400, { cause: error });
9348
+ }
9349
+ const fileNames = listValue$16(parsed.fileNames).map((value) => stringValue$1(value).trim()).filter((value) => value !== "").slice(0, 5);
9350
+ const result = {
9351
+ version: 1,
9352
+ sourceKind: stringValue$1(parsed.sourceKind).trim() === "record" ? "record" : "chat_relation",
9353
+ userId: Math.trunc(numberValue$16(parsed.userId)),
9354
+ sourceOwnerRef: stringValue$1(parsed.sourceOwnerRef).trim(),
9355
+ chatSessionUid: stringValue$1(parsed.chatSessionUid).trim(),
9356
+ relationUid: stringValue$1(parsed.relationUid).trim(),
9357
+ recordOwnerUserId: Math.trunc(numberValue$16(parsed.recordOwnerUserId)),
9358
+ recordUid: stringValue$1(parsed.recordUid).trim(),
9359
+ senderUserId: Math.trunc(numberValue$16(parsed.senderUserId)),
9360
+ senderName: stringValue$1(parsed.senderName).trim() || "Arkme用户",
9361
+ title: stringValue$1(parsed.title),
9362
+ textContent: stringValue$1(parsed.textContent),
9363
+ sendAtMillis: Math.trunc(numberValue$16(parsed.sendAtMillis)),
9364
+ sourceSequence: Math.max(0, Math.trunc(numberValue$16(parsed.sourceSequence))),
9365
+ templateKind: Math.trunc(numberValue$16(parsed.templateKind)),
9366
+ displayKind: Math.trunc(numberValue$16(parsed.displayKind)),
9367
+ imageCount: Math.max(0, Math.trunc(numberValue$16(parsed.imageCount))),
9368
+ voiceCount: Math.max(0, Math.trunc(numberValue$16(parsed.voiceCount))),
9369
+ fileCount: Math.max(0, Math.trunc(numberValue$16(parsed.fileCount))),
9370
+ fileNames
9371
+ };
9372
+ const matchesChatSource = result.sourceKind === "chat_relation" && (source.kind === "private_chat" || source.kind === "group_chat") && result.sourceOwnerRef === source.ownerRef && result.chatSessionUid === source.ownerRef && result.relationUid !== "" && result.recordUid !== "";
9373
+ const matchesRecordSource = result.sourceKind === "record" && (source.kind === "send_to_self" || source.kind === "default_category" || source.kind === "topic") && result.sourceOwnerRef === source.ownerRef && result.chatSessionUid === "" && result.relationUid === "" && result.recordOwnerUserId === expectedUserId && result.recordUid !== "";
9374
+ if (parsed.version !== 1 || result.userId !== expectedUserId || !matchesChatSource && !matchesRecordSource) throw new ArkmePluginError("message-action-ref-invalid", "消息操作引用与当前账号或会话不匹配,请刷新后重试", false, 403);
9375
+ return result;
9376
+ }
9377
+ forwardPayloadItem(reference, itemOrder) {
9378
+ return {
9379
+ item_order: itemOrder,
9380
+ source_kind: reference.sourceKind,
9381
+ record_uid: reference.recordUid,
9382
+ source_type: reference.sourceKind === "chat_relation" ? "chat_record" : "record",
9383
+ ...reference.recordOwnerUserId > 0 ? { owner_id: reference.recordOwnerUserId } : {},
9384
+ ...reference.relationUid === "" ? {} : { source_rel_uid: reference.relationUid },
9385
+ ...reference.chatSessionUid === "" ? {} : { source_chat_session_uid: reference.chatSessionUid },
9386
+ ...reference.sourceSequence > 0 ? { source_seq: reference.sourceSequence } : {},
9387
+ source_sender_user_id: reference.senderUserId,
9388
+ source_display_name: reference.senderName,
9389
+ owner_name: reference.senderName,
9390
+ send_at: reference.sendAtMillis,
9391
+ title: reference.title,
9392
+ text: reference.textContent,
9393
+ text_preview: reference.textContent.trim().slice(0, 500),
9394
+ template_kind: reference.templateKind,
9395
+ display_kind: reference.displayKind,
9396
+ image_count: reference.imageCount,
9397
+ voice_count: reference.voiceCount,
9398
+ file_count: reference.fileCount,
9399
+ file_names: reference.fileNames
9400
+ };
9401
+ }
9402
+ forwardClientRequestId(targetChatSessionUid, references, sendAtMillis) {
9403
+ const sourceIdentity = references.map((reference) => reference.sourceKind === "chat_relation" ? `chat_record:${reference.chatSessionUid}:${reference.relationUid}` : reference.recordUid).join("|");
9404
+ const sourceHash = createHash("sha256").update(sourceIdentity).digest("hex").slice(0, 16);
9405
+ return `forward_${targetChatSessionUid}_${String(Math.trunc(sendAtMillis))}_${sourceHash}`;
9406
+ }
9407
+ forwardReferencesOrderedForCommand(references) {
9408
+ if (references.length < 2 || !references.every((reference) => reference.sourceKind === "chat_relation" && reference.sourceSequence > 0 && reference.chatSessionUid.trim() !== "")) return references;
9409
+ const sourceSessionUid = references[0]?.chatSessionUid.trim() ?? "";
9410
+ if (sourceSessionUid === "" || !references.every((reference) => reference.chatSessionUid.trim() === sourceSessionUid)) return references;
9411
+ const ordered = references.map((reference, index) => ({
9412
+ reference,
9413
+ index
9414
+ })).sort((left, right) => {
9415
+ const sequenceCompare = left.reference.sourceSequence - right.reference.sourceSequence;
9416
+ return sequenceCompare === 0 ? left.index - right.index : sequenceCompare;
9417
+ }).map((item) => item.reference);
9418
+ return ordered.every((reference, index) => reference === references[index]) ? references : ordered;
9419
+ }
9420
+ validateForwardReferences(references) {
9421
+ const sourceTypes = new Set(references.map((reference) => reference.sourceKind));
9422
+ if (sourceTypes.size > 1) throw new ArkmePluginError("message-actions-selection-invalid", "暂不支持混合转发不同类型的消息", false);
9423
+ if (!sourceTypes.has("chat_relation")) return;
9424
+ if (new Set(references.map((reference) => reference.chatSessionUid.trim()).filter((value) => value !== "")).size > 1) throw new ArkmePluginError("message-actions-selection-invalid", "暂不支持跨会话转发聊天消息", false);
9425
+ }
9426
+ forwardSourceItemBody(reference) {
9427
+ if (reference.sourceKind === "chat_relation") return {
9428
+ source_type: "chat_record",
9429
+ record_uid: reference.recordUid,
9430
+ source_chat_session_uid: reference.chatSessionUid,
9431
+ source_rel_uid: reference.relationUid,
9432
+ ...reference.sourceSequence > 0 ? { source_seq: reference.sourceSequence } : {}
9433
+ };
9434
+ return {
9435
+ source_type: "record",
9436
+ record_uid: reference.recordUid
9437
+ };
9438
+ }
8286
9439
  sealMessageRef(userId, chatSessionUid, relationUid, signingKey) {
8287
9440
  const payload = encodeOpaqueJson$5({
8288
9441
  version: 1,
@@ -16076,6 +17229,7 @@ var ArkmeService = class {
16076
17229
  fetchImpl;
16077
17230
  pendingSessionStore;
16078
17231
  runtime;
17232
+ billingGateway;
16079
17233
  aiVideo;
16080
17234
  arrangement;
16081
17235
  calendar;
@@ -16106,13 +17260,14 @@ var ArkmeService = class {
16106
17260
  unmarkedSpeaker;
16107
17261
  voiceprint;
16108
17262
  worldVoiceprintInviteVariantIndex = 0;
16109
- constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker()) {
17263
+ constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker(), billingGateway) {
16110
17264
  this.config = config;
16111
17265
  this.sessionStore = sessionStore;
16112
17266
  this.stateStore = stateStore;
16113
17267
  this.fetchImpl = fetchImpl;
16114
17268
  this.pendingSessionStore = pendingSessionStore;
16115
17269
  this.runtime = new ServiceRuntime(config, sessionStore, stateStore, fetchImpl, pendingSessionStore);
17270
+ this.billingGateway = billingGateway ?? new HttpArkmeBillingGateway(this.runtime);
16116
17271
  this.privacy = new ArkmePrivacyVisibilityService(this.runtime);
16117
17272
  this.aiVideo = new AiVideoService(this.runtime);
16118
17273
  this.arrangement = new ArrangementService(this.runtime);
@@ -16179,6 +17334,9 @@ var ArkmeService = class {
16179
17334
  chatRealtimeState() {
16180
17335
  return this.realtime.chatRealtimeState();
16181
17336
  }
17337
+ async resolveManagedAccessCredential() {
17338
+ return await resolveManagedAccessCredential(this.runtime);
17339
+ }
16182
17340
  subscribeChatRealtime(listener) {
16183
17341
  return this.realtime.subscribeChatRealtime(listener);
16184
17342
  }
@@ -16262,11 +17420,32 @@ var ArkmeService = class {
16262
17420
  captchaId: this.config.geetestCaptchaId,
16263
17421
  environment: this.config.environment,
16264
17422
  testLoginEnabled: this.config.environment === "test",
17423
+ jiwoScanLoginEnabled: jiwoScanLoginAvailable(this.config),
16265
17424
  callAssetBasePath: `${this.config.routePath}/call`,
16266
17425
  voiceprintEnrollmentPath: `${this.config.routePath}/voiceprint/enroll`,
16267
17426
  shareWebsite: this.config.shareWebsite ?? "https://app.arkme.ai"
16268
17427
  };
16269
17428
  }
17429
+ async billingQuota(signal) {
17430
+ return await this.callBillingGateway(() => this.billingGateway.quota(signal));
17431
+ }
17432
+ async billingProducts(signal) {
17433
+ return await this.callBillingGateway(() => this.billingGateway.products(signal));
17434
+ }
17435
+ async createBillingOrder(input, signal) {
17436
+ return await this.callBillingGateway(() => this.billingGateway.createOrder(input, signal));
17437
+ }
17438
+ async billingOrderStatus(orderId, signal) {
17439
+ return await this.callBillingGateway(() => this.billingGateway.orderStatus(orderId, signal));
17440
+ }
17441
+ async callBillingGateway(operation) {
17442
+ try {
17443
+ return await operation();
17444
+ } catch (error) {
17445
+ if (error instanceof ArkmeBillingUnavailableError) throw new ArkmePluginError("billing-unavailable", error.message, true, 503, { cause: error });
17446
+ throw error;
17447
+ }
17448
+ }
16270
17449
  providerCapabilities() {
16271
17450
  return {
16272
17451
  contractVersion: 1,
@@ -16680,6 +17859,21 @@ var ArkmeService = class {
16680
17859
  async reportMessage(messageRef, reportType, options = {}) {
16681
17860
  return await this.chat.reportMessage(messageRef, reportType, options);
16682
17861
  }
17862
+ async copySourceMessageLink(sourceRef, actionRefs, options = {}) {
17863
+ return await this.chat.copySourceMessageLink(sourceRef, actionRefs, options);
17864
+ }
17865
+ async resolveMessageCopyLink(sid, options = {}) {
17866
+ return await this.chat.resolveMessageCopyLink(sid, options);
17867
+ }
17868
+ async extendMessageCopyLink(sid, itemIndex, textContent, recordUid, options = {}) {
17869
+ return await this.chat.extendMessageCopyLink(sid, itemIndex, textContent, recordUid, options);
17870
+ }
17871
+ async resolveLinkMetadata(rawUrl, options = {}) {
17872
+ return await this.chat.resolveLinkMetadata(rawUrl, options);
17873
+ }
17874
+ async forwardSourceMessages(sourceRef, actionRefs, options = {}) {
17875
+ return await this.chat.forwardSourceMessages(sourceRef, actionRefs, options);
17876
+ }
16683
17877
  async sendSourceText(sourceRef, textContent, options = {}) {
16684
17878
  return await this.chat.sendSourceText(sourceRef, textContent, options);
16685
17879
  }
@@ -16762,6 +17956,15 @@ var ArkmeService = class {
16762
17956
  async pollWechatLogin(attemptId) {
16763
17957
  return await this.auth.pollWechatLogin(attemptId);
16764
17958
  }
17959
+ async beginJiwoLogin() {
17960
+ return await this.auth.beginJiwoLogin();
17961
+ }
17962
+ async pollJiwoLogin(attemptId) {
17963
+ return await this.auth.pollJiwoLogin(attemptId);
17964
+ }
17965
+ async cancelJiwoLogin(attemptId) {
17966
+ return await this.auth.cancelJiwoLogin(attemptId);
17967
+ }
16765
17968
  async testLogin(userId) {
16766
17969
  return await this.auth.testLogin(userId);
16767
17970
  }
@@ -17918,6 +19121,7 @@ function persistentClientFactory(requireModule, spec) {
17918
19121
  return {
17919
19122
  name: `arkme-extension-client:${spec.extensionId}`,
17920
19123
  async apply(ctx) {
19124
+ if ((typeof document.location?.search === "string" ? document.location.search : "").split(/[?&]/).includes("arkme-harness-embed=1")) return;
17921
19125
  const styles = new Styles();
17922
19126
  const taggedConsole = {
17923
19127
  ...console,
@@ -18032,7 +19236,7 @@ function renderPersistentClientBundle(packageName, spec) {
18032
19236
  const renderedSpec = {
18033
19237
  ...spec,
18034
19238
  clientContentDigest: spec.clientContentDigest ?? arkmeClientContentDigest(spec.code),
18035
- wrapperVersion: 3
19239
+ wrapperVersion: 4
18036
19240
  };
18037
19241
  return [
18038
19242
  `window.__ModuleLoader__.load({ id: ${JSON.stringify(packageName)}, factory: (require) => {`,
@@ -18050,13 +19254,16 @@ function renderArkmeBundleClientBundle(packageName, spec) {
18050
19254
  identityKey: "packageName"
18051
19255
  });
18052
19256
  }
18053
- function packageIdentity(extensionId) {
19257
+ function persistentExtensionPackageIdentity(extensionId) {
18054
19258
  const suffix = createHash("sha256").update(extensionId).digest("hex").slice(0, 16);
18055
19259
  return {
18056
19260
  packageName: `@arkme-local/ext-${suffix}`,
18057
19261
  entryId: `arkme-extension-${suffix}`
18058
19262
  };
18059
19263
  }
19264
+ function persistentExtensionBundleDirectory(profileDirectory, extensionId, version) {
19265
+ return join(profileDirectory, "arkme-extensions", createHash("sha256").update(extensionId).digest("hex").slice(0, 16), version);
19266
+ }
18060
19267
  function writeSecure(path, content) {
18061
19268
  writeFileSync(path, content, {
18062
19269
  encoding: "utf8",
@@ -18153,9 +19360,22 @@ function readPersistentExtensionActivation(installationUrl) {
18153
19360
  }
18154
19361
  return state;
18155
19362
  }
19363
+ function repairPersistentExtensionInstallation(input) {
19364
+ const installationPath = join(input.bundleDirectory, "installation.json");
19365
+ const installation = JSON.parse(readFileSync(installationPath, "utf8"));
19366
+ if (installation.format_version !== 1 || installation.extension_id !== input.extensionId || installation.version !== input.version || typeof installation.artifact_path !== "string" || installation.artifact_path.trim() === "" || installation.artifact_sha256 !== input.artifactSha256 || installation.manifest_sha256 !== input.manifestSha256 || typeof installation.signature !== "string" || installation.signature.trim() === "" || typeof installation.signing_key_id !== "string" || installation.signing_key_id.trim() === "" || typeof installation.trusted_public_key !== "string" || installation.trusted_public_key.trim() === "" || !Number.isSafeInteger(installation.published_at) || (installation.published_at ?? -1) < 0) throw new Error("Arkme persistent extension installation identity mismatch");
19367
+ const manifest = JSON.parse(readFileSync(join(input.bundleDirectory, "package.json"), "utf8"));
19368
+ if (manifest.name !== input.packageName || manifest.version !== input.version) throw new Error("Arkme persistent extension package identity mismatch");
19369
+ if (installation.artifact_path === input.artifactPath) return false;
19370
+ replaceSecure(installationPath, `${JSON.stringify({
19371
+ ...installation,
19372
+ artifact_path: input.artifactPath
19373
+ }, void 0, 2)}\n`);
19374
+ return true;
19375
+ }
18156
19376
  function materializePersistentExtensionBundle(input) {
18157
- const { packageName, entryId } = packageIdentity(input.resolution.extension_id);
18158
- const bundleDirectory = join(input.profileDirectory, "arkme-extensions", createHash("sha256").update(input.resolution.extension_id).digest("hex").slice(0, 16), input.resolution.version);
19377
+ const { packageName, entryId } = persistentExtensionPackageIdentity(input.resolution.extension_id);
19378
+ const bundleDirectory = persistentExtensionBundleDirectory(input.profileDirectory, input.resolution.extension_id, input.resolution.version);
18159
19379
  const installation = {
18160
19380
  format_version: 1,
18161
19381
  extension_id: input.resolution.extension_id,
@@ -18180,7 +19400,7 @@ function materializePersistentExtensionBundle(input) {
18180
19400
  try {
18181
19401
  const manifest = JSON.parse(readFileSync(join(bundleDirectory, "package.json"), "utf8"));
18182
19402
  const clientBundle = input.clientCode === void 0 ? void 0 : readFileSync(join(bundleDirectory, "lib", "client.js"), "utf8");
18183
- const clientReady = input.clientCode === void 0 || manifest.exports?.["./client"] === "./lib/client.js" && manifest.dsh?.client?.inject?.length === 0 && clientBundle?.includes("extensions.persistent.invoke") === true && clientBundle.includes("extensions.persistent.client-state") && clientBundle.includes(`\"wrapperVersion\":${String(3)}`);
19403
+ const clientReady = input.clientCode === void 0 || manifest.exports?.["./client"] === "./lib/client.js" && manifest.dsh?.client?.inject?.length === 0 && clientBundle?.includes("extensions.persistent.invoke") === true && clientBundle.includes("extensions.persistent.client-state") && clientBundle.includes(`\"wrapperVersion\":${String(4)}`);
18184
19404
  if (manifest.exports?.["."] === "./lib/index.js" && manifest.exports?.["./package.json"] === "./package.json" && clientReady) {
18185
19405
  if (!existsSync(activationPath)) writeSecure(activationPath, activationText(input.resolution.extension_id, true));
18186
19406
  return {
@@ -18266,7 +19486,21 @@ const VM_TIMEOUT_MS = 5e3;
18266
19486
  function readInstallation(url) {
18267
19487
  const value = JSON.parse(readFileSync(url, "utf8"));
18268
19488
  if (value.format_version !== 1 || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value.extension_id) || value.version.trim() === "" || value.artifact_path.trim() === "" || value.trusted_public_key.trim() === "") throw new Error("Arkme persistent extension installation metadata is invalid");
18269
- return value;
19489
+ return {
19490
+ ...value,
19491
+ artifact_path: resolveRuntimeArtifactPath(value, url)
19492
+ };
19493
+ }
19494
+ function resolveRuntimeArtifactPath(installation, installationUrl) {
19495
+ const dshHome = process.env.DSH_HOME?.trim();
19496
+ const filename = basename(installation.artifact_path);
19497
+ if (dshHome !== void 0 && dshHome !== "" && filename !== "" && filename !== "." && filename !== "..") try {
19498
+ const root = realpathSync(join(dshHome, "arkme-self", "extensions"));
19499
+ const candidate = realpathSync(join(root, installation.extension_id, installation.version, filename));
19500
+ const fromRoot = relative(root, candidate);
19501
+ if (fromRoot !== "" && fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`) && !isAbsolute(fromRoot) && sha256Hex(readFileSync(candidate)) === installation.artifact_sha256) return candidate;
19502
+ } catch {}
19503
+ return isAbsolute(installation.artifact_path) ? installation.artifact_path : resolve(dirname(fileURLToPath(installationUrl)), installation.artifact_path);
18270
19504
  }
18271
19505
  function jsonValue(value) {
18272
19506
  if (value === void 0) return null;
@@ -18534,4 +19768,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
18534
19768
  return jsonValue(await handler(jsonValue(args)));
18535
19769
  }
18536
19770
  //#endregion
18537
- export { readNativeBundleTarballV3 as A, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as B, arkmeSandboxEntryId as C, inspectNativeBundleArtifactV3 as D, inspectBundleArtifact as E, unpackArkmeExtension as F, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as G, validateVoiceprintPcm16Wav as H, ARKME_EXTENSION_FORMAT as I, ARKME_WORLD_PUBLISH_MAX_IMAGES as J, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as K, ARKME_EXTENSION_ICON_MAX_BYTES as L, assertExtensionArtifactSize as M, canonicalExtensionJson as N, packBundleFiles as O, sha256Hex as P, ArkmeOutgoingCallError as Q, ARKME_EXTENSION_MAX_BYTES as R, ArkmeBundleArtifactError as S, canonicalBundleJson as T, ARKME_DEFAULT_SHARE_WEBSITE as U, ArkmeService as V, ARKME_PROVIDER_CONTRACT_VERSION as W, isArkmeBotAvatarRef as X, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as Y, ArkmePluginError as Z, verifyExtensionResolutionSignature as _, persistentArkmeExtensionActive as a, ARKME_BUNDLE_MAX_BYTES as b, quarantinePersistentExtension as c, writePersistentExtensionActivation as d, renderArkmeBundleClientBundle as f, verifyBundleResolutionSignature as g, arkmeClientContentDigestFromSource as h, invokePersistentArkmeExtension as i, renderArkmeSandboxHostEntry as j, packLocalNativeBundleDirectoryV3 as k, readPersistentExtensionActivation as l, arkmeClientContentDigest as m, applyPersistentArkmeHostExtension as n, persistentArkmeExtensionRuntimeState as o, activeProfileExtensionOwnerConflicts as p, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as q, deactivatePersistentArkmeExtension as r, materializePersistentExtensionBundle as s, activatePersistentArkmeExtension as t, refreshPersistentClientWrapper as u, verifyNativeBundleResolutionSignature as v, bundleSha256 as w, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as x, ARKME_BUNDLE_ARTIFACT_KIND as y, ARKME_EXTENSION_PREVIEW_MAX_BYTES as z };
19771
+ export { isArkmeBotAvatarRef as $, inspectNativeBundleArtifactV3 as A, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, bundleSha256 as D, arkmeSandboxEntryId as E, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as J, ARKME_DEFAULT_SHARE_WEBSITE as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, readNativeBundleTarballV3 as N, canonicalBundleJson as O, renderArkmeSandboxHostEntry as P, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, ArkmeBundleArtifactError as T, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as X, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as Y, ARKME_WORLD_PUBLISH_MAX_IMAGES as Z, arkmeClientContentDigest as _, persistentArkmeExtensionActive as a, verifyExtensionResolutionSignature as b, persistentExtensionBundleDirectory as c, readPersistentExtensionActivation as d, ArkmePluginError as et, refreshPersistentClientWrapper as f, activeProfileExtensionOwnerConflicts as g, renderArkmeBundleClientBundle as h, invokePersistentArkmeExtension as i, packBundleFiles as j, inspectBundleArtifact as k, persistentExtensionPackageIdentity as l, writePersistentExtensionActivation as m, applyPersistentArkmeHostExtension as n, persistentArkmeExtensionRuntimeState as o, repairPersistentExtensionInstallation as p, ARKME_PROVIDER_CONTRACT_VERSION as q, deactivatePersistentArkmeExtension as r, materializePersistentExtensionBundle as s, activatePersistentArkmeExtension as t, ArkmeOutgoingCallError as tt, quarantinePersistentExtension as u, arkmeClientContentDigestFromSource as v, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, verifyNativeBundleResolutionSignature as x, verifyBundleResolutionSignature as y, ARKME_EXTENSION_FORMAT as z };