@senguoyun/dsh-arkme 0.1.27 → 0.1.29

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 (60) hide show
  1. package/lib/client.js +4842 -3518
  2. package/lib/client.js.map +1 -1
  3. package/lib/extension-profile-restart-helper.js +1 -1
  4. package/lib/index.js +341 -40
  5. package/lib/persistent-extension.js +1 -1
  6. package/lib/{persistent-runtime-DGgyCDGU.js → persistent-runtime-DLKV_LTp.js} +854 -361
  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/types/arkme-service.d.ts +22 -4
  11. package/lib/types/arkme-service.d.ts.map +1 -1
  12. package/lib/types/billing-gateway.d.ts +28 -0
  13. package/lib/types/billing-gateway.d.ts.map +1 -0
  14. package/lib/types/client/ArkmeBillingSettings.d.ts +52 -0
  15. package/lib/types/client/ArkmeBillingSettings.d.ts.map +1 -0
  16. package/lib/types/client/ArkmeCallSurface.d.ts.map +1 -1
  17. package/lib/types/client/ArkmeLogin.d.ts +3 -1
  18. package/lib/types/client/ArkmeLogin.d.ts.map +1 -1
  19. package/lib/types/client/ArkmePersistentShell.d.ts +1 -1
  20. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  21. package/lib/types/client/ArkmeProductNavigation.d.ts +2 -1
  22. package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
  23. package/lib/types/client/ArkmeSettingsSurface.d.ts +18 -0
  24. package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeSidebar.d.ts +3 -3
  26. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  27. package/lib/types/client/ArkmeVirtualWorkspace.d.ts +3 -2
  28. package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
  29. package/lib/types/client/arkme-auth-flow.d.ts +1 -0
  30. package/lib/types/client/arkme-auth-flow.d.ts.map +1 -1
  31. package/lib/types/client/arkme-billing.d.ts +37 -0
  32. package/lib/types/client/arkme-billing.d.ts.map +1 -0
  33. package/lib/types/client/arkme-login-locales.d.ts +5 -0
  34. package/lib/types/client/arkme-login-locales.d.ts.map +1 -1
  35. package/lib/types/client/bot-directory-preferences.d.ts +15 -0
  36. package/lib/types/client/bot-directory-preferences.d.ts.map +1 -0
  37. package/lib/types/client/chat-directory-store.d.ts.map +1 -1
  38. package/lib/types/host-api.d.ts.map +1 -1
  39. package/lib/types/index.d.ts.map +1 -1
  40. package/lib/types/managed-ai/adapter.d.ts +20 -0
  41. package/lib/types/managed-ai/adapter.d.ts.map +1 -0
  42. package/lib/types/managed-ai/credential.d.ts +11 -0
  43. package/lib/types/managed-ai/credential.d.ts.map +1 -0
  44. package/lib/types/plugin-update-lifecycle-log.d.ts +17 -0
  45. package/lib/types/plugin-update-lifecycle-log.d.ts.map +1 -0
  46. package/lib/types/plugin-update.d.ts +3 -0
  47. package/lib/types/plugin-update.d.ts.map +1 -1
  48. package/lib/types/plugin-updater-helper.d.ts +1 -2
  49. package/lib/types/plugin-updater-helper.d.ts.map +1 -1
  50. package/lib/types/profile-plugin-entry.d.ts +6 -0
  51. package/lib/types/profile-plugin-entry.d.ts.map +1 -0
  52. package/lib/types/services/auth-service.d.ts +10 -0
  53. package/lib/types/services/auth-service.d.ts.map +1 -1
  54. package/lib/types/services/bot-service.d.ts +1 -0
  55. package/lib/types/services/bot-service.d.ts.map +1 -1
  56. package/lib/types/services/source-service.d.ts +15 -1
  57. package/lib/types/services/source-service.d.ts.map +1 -1
  58. package/lib/types/types.d.ts +66 -1
  59. package/lib/types/types.d.ts.map +1 -1
  60. package/package.json +12 -6
@@ -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;
@@ -303,346 +334,6 @@ var ArkmeOutgoingCallBroker = class {
303
334
  if (intent.signal !== void 0 && intent.abort !== void 0) intent.signal.removeEventListener("abort", intent.abort);
304
335
  }
305
336
  };
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
- }
643
- }
644
- return bestCategory;
645
- }
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;
@@ -3146,6 +3499,15 @@ function arkmeGroupBotBindingBody(source) {
3146
3499
  if (target?.subjectUid !== void 0 && target.subjectUid.trim() !== "") return { subject_uid: target.subjectUid.trim() };
3147
3500
  return { subject_uid: source.ownerRef };
3148
3501
  }
3502
+ function arkmeChatSidebarTargetFromBundle(bundle, fallbackSubjectUid = "") {
3503
+ const chatSession = objectValue$1(bundle.session);
3504
+ const subjectUid = stringValue$1(bundle.subject_uid ?? bundle.subjectUid ?? bundle.topic_uid ?? bundle.topicUid ?? chatSession.subject_uid ?? chatSession.subjectUid ?? chatSession.topic_uid ?? chatSession.topicUid ?? fallbackSubjectUid).trim();
3505
+ if (subjectUid === "") return void 0;
3506
+ return {
3507
+ subjectUid,
3508
+ hiddenAnchorTimestamp: Math.max(0, numberValue$20(bundle.sort_active_at ?? bundle.last_active_at ?? chatSession.last_active_at))
3509
+ };
3510
+ }
3149
3511
  function backendBooleanValue(value) {
3150
3512
  if (value === null || value === void 0) return void 0;
3151
3513
  if (typeof value === "boolean") return value;
@@ -3760,6 +4122,62 @@ var SourceService = class {
3760
4122
  if (this.sourceListInFlight.get(cacheKey) === pending) this.sourceListInFlight.delete(cacheKey);
3761
4123
  }
3762
4124
  }
4125
+ async setChatDirectoryPolicy(sourceRef, options = {}) {
4126
+ const session = await this.runtime.requireSession();
4127
+ const source = await this.openSourceRef(sourceRef, session.userId);
4128
+ if (source.kind !== "private_chat" && source.kind !== "group_chat") throw new ArkmePluginError("chat-directory-policy-invalid", "仅支持更新私聊或群聊的会话列表状态", false);
4129
+ const sidebarTarget = await this.resolveChatSidebarTarget(source, session, options.signal);
4130
+ const current = await this.runtime.authenticatedChatPost("/api/v1/chats/policy/get", { chat_session_uid: source.ownerRef }, session, options.signal);
4131
+ const pinned = options.pinned ?? numberValue$20(current.pin_state) === 2;
4132
+ const hidden = options.hidden ?? numberValue$20(current.show_in_home_state) === 2;
4133
+ const updatedAt = Date.now();
4134
+ const writes = [this.runtime.authenticatedChatPost("/api/v1/chats/policy/update", {
4135
+ chat_session_uid: source.ownerRef,
4136
+ show_in_home_state: hidden ? 2 : 1,
4137
+ privacy_state: numberValue$20(current.privacy_state) || 1,
4138
+ mute_state: numberValue$20(current.mute_state) || 1,
4139
+ pin_state: pinned ? 2 : 1,
4140
+ notify_state: numberValue$20(current.notify_state) || 1,
4141
+ status: numberValue$20(current.status) || 1,
4142
+ update_at: updatedAt
4143
+ }, session, options.signal)];
4144
+ if (options.hidden !== void 0) writes.push(this.runtime.authenticatedSubjectPost("/api/v1/subject/batch-set-sidebar-hidden-status", { items: [{
4145
+ subject_uid: sidebarTarget.subjectUid,
4146
+ hidden,
4147
+ hidden_anchor_timestamp: hidden ? Math.max(1, sidebarTarget.hiddenAnchorTimestamp || updatedAt) : 0
4148
+ }] }, session, options.signal));
4149
+ if (options.pinned !== void 0) writes.push(this.runtime.authenticatedPost("/api/v1/topics/pin/set", {
4150
+ topic_uid: sidebarTarget.subjectUid,
4151
+ pin_state: pinned ? 1 : 2,
4152
+ ...pinned ? { pinned_at: updatedAt } : {}
4153
+ }, session, options.signal));
4154
+ await Promise.all(writes);
4155
+ const cacheKey = `${String(session.userId)}:${source.ownerRef}`;
4156
+ const cached = this.chatSourceCache.get(cacheKey);
4157
+ if (cached !== void 0) this.chatSourceCache.set(cacheKey, {
4158
+ ...cached,
4159
+ isPinned: pinned
4160
+ });
4161
+ this.sourceListCache.clear();
4162
+ return {
4163
+ sourceRef,
4164
+ pinned,
4165
+ hidden
4166
+ };
4167
+ }
4168
+ async resolveChatSidebarTarget(source, session, signal) {
4169
+ if (source.sidebarSubjectUid !== void 0 && source.sidebarSubjectUid.trim() !== "") return {
4170
+ subjectUid: source.sidebarSubjectUid.trim(),
4171
+ hiddenAnchorTimestamp: Math.max(0, source.sidebarHiddenAnchorTimestamp ?? 0)
4172
+ };
4173
+ const target = arkmeChatSidebarTargetFromBundle(await this.runtime.authenticatedChatPost("/api/v1/chats/detail", { chat_session_uid: source.ownerRef }, session, signal, {
4174
+ lane: "interactive-read",
4175
+ key: `chat-sidebar-target:${source.ownerRef}`,
4176
+ failureCooldownMs: 2e3
4177
+ }), source.kind === "group_chat" ? source.ownerRef : "");
4178
+ if (target === void 0) throw new ArkmePluginError("chat-sidebar-target-unavailable", "未能定位该会话的跨端侧边栏数据,请刷新后重试", true, 502);
4179
+ return target;
4180
+ }
3763
4181
  async listGroupSources(options = {}) {
3764
4182
  const session = await this.runtime.requireSession();
3765
4183
  const limit = Math.min(50, Math.max(1, Math.trunc(options.limit ?? 30)));
@@ -3934,7 +4352,9 @@ var SourceService = class {
3934
4352
  const latestRecord = objectValue$1(latestPreview.record);
3935
4353
  const latestPayload = objectValue$1(latestRecord.payload);
3936
4354
  const unread = objectValue$1(bundle.unread_snapshot);
3937
- const isMuted = chatMessageDnd$1(bundle.current_policy) ?? false;
4355
+ const currentPolicy = objectValue$1(bundle.current_policy);
4356
+ const isMuted = chatMessageDnd$1(currentPolicy) ?? false;
4357
+ const isPinned = numberValue$20(currentPolicy.pin_state) === 2;
3938
4358
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
3939
4359
  const sessionKind = numberValue$20(chatSession.session_kind);
3940
4360
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
@@ -3947,8 +4367,12 @@ var SourceService = class {
3947
4367
  const backendMentionState = chatUnreadMentionState(unread);
3948
4368
  const hasUnreadMention = kind !== "group_chat" ? void 0 : unreadCount <= 0 ? false : backendMentionState === void 0 && !latestPreviewMentionsViewer ? void 0 : backendMentionState === true || latestPreviewMentionsViewer;
3949
4369
  const botGroupTarget = kind === "group_chat" ? arkmeGroupBotBindingTargetFromBundle(bundle) : void 0;
4370
+ const sidebarTarget = arkmeChatSidebarTargetFromBundle(bundle, kind === "group_chat" ? uid : "");
3950
4371
  const item = {
3951
- sourceRef: await this.sealSourceRef(session.userId, kind, uid, displayName, botGroupTarget === void 0 ? {} : { botGroupTarget }),
4372
+ sourceRef: await this.sealSourceRef(session.userId, kind, uid, displayName, {
4373
+ ...botGroupTarget === void 0 ? {} : { botGroupTarget },
4374
+ ...sidebarTarget === void 0 ? {} : { sidebarTarget }
4375
+ }),
3952
4376
  sourceKey: await this.chatDirectorySourceKey(session.userId, uid),
3953
4377
  kind,
3954
4378
  displayName,
@@ -3957,6 +4381,7 @@ var SourceService = class {
3957
4381
  unreadCount,
3958
4382
  ...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
3959
4383
  isMuted,
4384
+ isPinned,
3960
4385
  ...numberValue$20(unread.session_last_seq ?? chatSession.last_seq) > 0 ? { latestSequence: numberValue$20(unread.session_last_seq ?? chatSession.last_seq) } : {}
3961
4386
  };
3962
4387
  const itemIndex = items.push(item) - 1;
@@ -4089,13 +4514,18 @@ var SourceService = class {
4089
4514
  }
4090
4515
  async sealSourceRef(userId, kind, ownerRef, displayName, options = {}) {
4091
4516
  const botGroupTarget = kind === "group_chat" ? sanitizeGroupBotBindingTarget(options.botGroupTarget) : void 0;
4517
+ const sidebarTarget = kind === "private_chat" || kind === "group_chat" ? options.sidebarTarget : void 0;
4092
4518
  const payload = encodeOpaqueJson$6({
4093
4519
  version: 1,
4094
4520
  userId,
4095
4521
  kind,
4096
4522
  ownerRef,
4097
4523
  displayName,
4098
- ...botGroupTarget === void 0 ? {} : { botGroupTarget }
4524
+ ...botGroupTarget === void 0 ? {} : { botGroupTarget },
4525
+ ...sidebarTarget === void 0 ? {} : {
4526
+ sidebarSubjectUid: sidebarTarget.subjectUid,
4527
+ sidebarHiddenAnchorTimestamp: sidebarTarget.hiddenAnchorTimestamp
4528
+ }
4099
4529
  });
4100
4530
  return `arkme-source-v1.${payload}.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(payload).digest("base64url")}`;
4101
4531
  }
@@ -4122,6 +4552,14 @@ var SourceService = class {
4122
4552
  ...kind === "group_chat" ? (() => {
4123
4553
  const botGroupTarget = sanitizeGroupBotBindingTarget(parsed.botGroupTarget);
4124
4554
  return botGroupTarget === void 0 ? {} : { botGroupTarget };
4555
+ })() : {},
4556
+ ...kind === "private_chat" || kind === "group_chat" ? (() => {
4557
+ const sidebarSubjectUid = stringValue$1(parsed.sidebarSubjectUid).trim();
4558
+ const sidebarHiddenAnchorTimestamp = Math.max(0, numberValue$20(parsed.sidebarHiddenAnchorTimestamp));
4559
+ return sidebarSubjectUid === "" ? {} : {
4560
+ sidebarSubjectUid,
4561
+ sidebarHiddenAnchorTimestamp
4562
+ };
4125
4563
  })() : {}
4126
4564
  };
4127
4565
  if (parsed.version !== 1 || result.userId !== expectedUserId || !isSourceKind(kind) || result.ownerRef === "" || result.displayName === "") throw new ArkmePluginError("source-ref-invalid", "Arkme 数据源引用与当前账号不匹配", false, 403);
@@ -4130,7 +4568,13 @@ var SourceService = class {
4130
4568
  async sourceItem(source) {
4131
4569
  const sourceKey = source.kind === "private_chat" || source.kind === "group_chat" ? await this.chatDirectorySourceKey(source.userId, source.ownerRef) : void 0;
4132
4570
  return {
4133
- sourceRef: await this.sealSourceRef(source.userId, source.kind, source.ownerRef, source.displayName, source.botGroupTarget === void 0 ? {} : { botGroupTarget: source.botGroupTarget }),
4571
+ sourceRef: await this.sealSourceRef(source.userId, source.kind, source.ownerRef, source.displayName, {
4572
+ ...source.botGroupTarget === void 0 ? {} : { botGroupTarget: source.botGroupTarget },
4573
+ ...source.sidebarSubjectUid === void 0 ? {} : { sidebarTarget: {
4574
+ subjectUid: source.sidebarSubjectUid,
4575
+ hiddenAnchorTimestamp: source.sidebarHiddenAnchorTimestamp ?? 0
4576
+ } }
4577
+ }),
4134
4578
  ...sourceKey === void 0 ? {} : { sourceKey },
4135
4579
  kind: source.kind,
4136
4580
  displayName: source.displayName,
@@ -4143,7 +4587,9 @@ var SourceService = class {
4143
4587
  const counterpart = objectValue$1(bundle.private_counterpart);
4144
4588
  const supplement = objectValue$1(bundle.private_supplement);
4145
4589
  const unread = objectValue$1(bundle.unread_snapshot);
4146
- const isMuted = chatMessageDnd$1(bundle.current_policy) ?? cached?.isMuted ?? false;
4590
+ const currentPolicy = objectValue$1(bundle.current_policy);
4591
+ const isMuted = chatMessageDnd$1(currentPolicy) ?? cached?.isMuted ?? false;
4592
+ const isPinned = numberValue$20(currentPolicy.pin_state) === 2;
4147
4593
  const uid = stringValue$1(chatSession.chat_session_uid).trim();
4148
4594
  const sessionKind = numberValue$20(chatSession.session_kind);
4149
4595
  const kind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : void 0;
@@ -4157,8 +4603,12 @@ var SourceService = class {
4157
4603
  const backendMentionState = chatUnreadMentionState(unread);
4158
4604
  const hasUnreadMention = kind !== "group_chat" ? void 0 : unreadCount <= 0 ? false : backendMentionState === void 0 && !latestMentionsViewer ? cached?.hasUnreadMention : backendMentionState === true || latestMentionsViewer;
4159
4605
  const botGroupTarget = kind === "group_chat" ? arkmeGroupBotBindingTargetFromBundle(bundle) : void 0;
4606
+ const sidebarTarget = arkmeChatSidebarTargetFromBundle(bundle, kind === "group_chat" ? uid : "");
4160
4607
  return {
4161
- sourceRef: await this.sealSourceRef(session.userId, kind, uid, displayName, botGroupTarget === void 0 ? {} : { botGroupTarget }),
4608
+ sourceRef: await this.sealSourceRef(session.userId, kind, uid, displayName, {
4609
+ ...botGroupTarget === void 0 ? {} : { botGroupTarget },
4610
+ ...sidebarTarget === void 0 ? {} : { sidebarTarget }
4611
+ }),
4162
4612
  sourceKey: await this.chatDirectorySourceKey(session.userId, uid),
4163
4613
  kind,
4164
4614
  displayName,
@@ -4170,6 +4620,7 @@ var SourceService = class {
4170
4620
  unreadCount,
4171
4621
  ...hasUnreadMention === void 0 ? {} : { hasUnreadMention },
4172
4622
  isMuted,
4623
+ isPinned,
4173
4624
  ...latestSequence > 0 ? { latestSequence } : {}
4174
4625
  };
4175
4626
  }
@@ -4663,6 +5114,7 @@ var BotService = class {
4663
5114
  const createdAtMillis = botPrivateChatTimestamp(raw.created_at ?? raw.createdAt);
4664
5115
  return {
4665
5116
  botRef: this.sealBotRef(userId, botId, provider),
5117
+ directoryKey: await this.botDirectoryKey(userId, botId),
4666
5118
  name,
4667
5119
  provider,
4668
5120
  description: stringValue$1(raw.description).trim(),
@@ -4772,6 +5224,9 @@ var BotService = class {
4772
5224
  this.pruneBotRefs();
4773
5225
  return botRef;
4774
5226
  }
5227
+ async botDirectoryKey(userId, botId) {
5228
+ return `arkme-bot-directory-v1.${createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(`arkme-bot-directory-v1:${String(userId)}:${botId}`).digest("base64url")}`;
5229
+ }
4775
5230
  async openBotRef(botRef, expectedUserId) {
4776
5231
  const normalized = botRef.trim();
4777
5232
  if (!BOT_REF_PATTERN.test(normalized)) throw new ArkmePluginError("bot-ref-invalid", "Bot 引用无效", false);
@@ -15974,6 +16429,7 @@ var ArkmeService = class {
15974
16429
  fetchImpl;
15975
16430
  pendingSessionStore;
15976
16431
  runtime;
16432
+ billingGateway;
15977
16433
  aiVideo;
15978
16434
  arrangement;
15979
16435
  calendar;
@@ -16004,13 +16460,14 @@ var ArkmeService = class {
16004
16460
  unmarkedSpeaker;
16005
16461
  voiceprint;
16006
16462
  worldVoiceprintInviteVariantIndex = 0;
16007
- constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker()) {
16463
+ constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker(), billingGateway) {
16008
16464
  this.config = config;
16009
16465
  this.sessionStore = sessionStore;
16010
16466
  this.stateStore = stateStore;
16011
16467
  this.fetchImpl = fetchImpl;
16012
16468
  this.pendingSessionStore = pendingSessionStore;
16013
16469
  this.runtime = new ServiceRuntime(config, sessionStore, stateStore, fetchImpl, pendingSessionStore);
16470
+ this.billingGateway = billingGateway ?? new HttpArkmeBillingGateway(this.runtime);
16014
16471
  this.privacy = new ArkmePrivacyVisibilityService(this.runtime);
16015
16472
  this.aiVideo = new AiVideoService(this.runtime);
16016
16473
  this.arrangement = new ArrangementService(this.runtime);
@@ -16077,6 +16534,9 @@ var ArkmeService = class {
16077
16534
  chatRealtimeState() {
16078
16535
  return this.realtime.chatRealtimeState();
16079
16536
  }
16537
+ async resolveManagedAccessCredential() {
16538
+ return await resolveManagedAccessCredential(this.runtime);
16539
+ }
16080
16540
  subscribeChatRealtime(listener) {
16081
16541
  return this.realtime.subscribeChatRealtime(listener);
16082
16542
  }
@@ -16160,11 +16620,32 @@ var ArkmeService = class {
16160
16620
  captchaId: this.config.geetestCaptchaId,
16161
16621
  environment: this.config.environment,
16162
16622
  testLoginEnabled: this.config.environment === "test",
16623
+ jiwoScanLoginEnabled: jiwoScanLoginAvailable(this.config),
16163
16624
  callAssetBasePath: `${this.config.routePath}/call`,
16164
16625
  voiceprintEnrollmentPath: `${this.config.routePath}/voiceprint/enroll`,
16165
16626
  shareWebsite: this.config.shareWebsite ?? "https://app.arkme.ai"
16166
16627
  };
16167
16628
  }
16629
+ async billingQuota(signal) {
16630
+ return await this.callBillingGateway(() => this.billingGateway.quota(signal));
16631
+ }
16632
+ async billingProducts(signal) {
16633
+ return await this.callBillingGateway(() => this.billingGateway.products(signal));
16634
+ }
16635
+ async createBillingOrder(input, signal) {
16636
+ return await this.callBillingGateway(() => this.billingGateway.createOrder(input, signal));
16637
+ }
16638
+ async billingOrderStatus(orderId, signal) {
16639
+ return await this.callBillingGateway(() => this.billingGateway.orderStatus(orderId, signal));
16640
+ }
16641
+ async callBillingGateway(operation) {
16642
+ try {
16643
+ return await operation();
16644
+ } catch (error) {
16645
+ if (error instanceof ArkmeBillingUnavailableError) throw new ArkmePluginError("billing-unavailable", error.message, true, 503, { cause: error });
16646
+ throw error;
16647
+ }
16648
+ }
16168
16649
  providerCapabilities() {
16169
16650
  return {
16170
16651
  contractVersion: 1,
@@ -16434,6 +16915,9 @@ var ArkmeService = class {
16434
16915
  async listSources(directory, options = {}) {
16435
16916
  return await this.source.listSources(directory, options);
16436
16917
  }
16918
+ async setChatDirectoryPolicy(sourceRef, options = {}) {
16919
+ return await this.source.setChatDirectoryPolicy(sourceRef, options);
16920
+ }
16437
16921
  async dshBetaCommunityEntryState(signal) {
16438
16922
  return await this.community.dshBetaCommunityEntryState(signal);
16439
16923
  }
@@ -16657,6 +17141,15 @@ var ArkmeService = class {
16657
17141
  async pollWechatLogin(attemptId) {
16658
17142
  return await this.auth.pollWechatLogin(attemptId);
16659
17143
  }
17144
+ async beginJiwoLogin() {
17145
+ return await this.auth.beginJiwoLogin();
17146
+ }
17147
+ async pollJiwoLogin(attemptId) {
17148
+ return await this.auth.pollJiwoLogin(attemptId);
17149
+ }
17150
+ async cancelJiwoLogin(attemptId) {
17151
+ return await this.auth.cancelJiwoLogin(attemptId);
17152
+ }
16660
17153
  async testLogin(userId) {
16661
17154
  return await this.auth.testLogin(userId);
16662
17155
  }