shennian 0.2.89 → 0.2.90

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 (118) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +13 -4
  2. package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
  3. package/dist/bin/shennian.js +1 -1
  4. package/dist/publish-build-manifest.json +548 -0
  5. package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
  6. package/dist/src/agent-env.js +4 -105
  7. package/dist/src/agents/adapter.js +1 -19
  8. package/dist/src/agents/claude.js +8 -305
  9. package/dist/src/agents/codex-control.js +2 -188
  10. package/dist/src/agents/codex-utils.js +7 -200
  11. package/dist/src/agents/codex.js +15 -916
  12. package/dist/src/agents/command-spec.js +2 -413
  13. package/dist/src/agents/config-status.js +1 -226
  14. package/dist/src/agents/cursor.js +1 -249
  15. package/dist/src/agents/custom.js +4 -271
  16. package/dist/src/agents/detect.js +1 -56
  17. package/dist/src/agents/external-channel-instructions.js +10 -94
  18. package/dist/src/agents/gemini.js +1 -173
  19. package/dist/src/agents/manager.js +13 -157
  20. package/dist/src/agents/model-registry/cache.js +1 -37
  21. package/dist/src/agents/model-registry/discovery.js +2 -187
  22. package/dist/src/agents/model-registry/parsers.js +4 -447
  23. package/dist/src/agents/model-registry/runner.js +1 -30
  24. package/dist/src/agents/model-registry/service.js +1 -78
  25. package/dist/src/agents/model-registry/types.js +1 -8
  26. package/dist/src/agents/model-registry.js +1 -18
  27. package/dist/src/agents/openclaw.js +2 -275
  28. package/dist/src/agents/opencode.js +1 -231
  29. package/dist/src/agents/pi-context.js +12 -217
  30. package/dist/src/agents/pi.js +14 -723
  31. package/dist/src/agents/platform-instructions.js +9 -54
  32. package/dist/src/channels/base.js +1 -3
  33. package/dist/src/channels/registry.js +1 -30
  34. package/dist/src/channels/reply-split.js +10 -89
  35. package/dist/src/channels/runtime.js +5 -564
  36. package/dist/src/channels/secret-registry.js +1 -46
  37. package/dist/src/channels/websocket.js +8 -378
  38. package/dist/src/channels/wechat-channel/anchor.js +1 -65
  39. package/dist/src/channels/wechat-channel/client.js +1 -96
  40. package/dist/src/channels/wechat-channel/cooldown.js +1 -38
  41. package/dist/src/channels/wechat-channel/fingerprint.js +1 -71
  42. package/dist/src/channels/wechat-channel/helper-assets.d.ts +10 -1
  43. package/dist/src/channels/wechat-channel/helper-assets.js +1 -68
  44. package/dist/src/channels/wechat-channel/helper-client.js +3 -149
  45. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
  46. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -115
  47. package/dist/src/channels/wechat-channel/index.d.ts +1 -0
  48. package/dist/src/channels/wechat-channel/index.js +1 -19
  49. package/dist/src/channels/wechat-channel/ledger.js +1 -54
  50. package/dist/src/channels/wechat-channel/media-resolver.js +1 -181
  51. package/dist/src/channels/wechat-channel/message-key.js +1 -105
  52. package/dist/src/channels/wechat-channel/observer.js +1 -118
  53. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +3 -0
  54. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -112
  55. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  56. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  57. package/dist/src/channels/wechat-channel/preflight.js +1 -48
  58. package/dist/src/channels/wechat-channel/runner.js +1 -84
  59. package/dist/src/channels/wechat-channel/runtime.js +1 -66
  60. package/dist/src/channels/wechat-channel/scheduler.d.ts +5 -0
  61. package/dist/src/channels/wechat-channel/scheduler.js +1 -152
  62. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  63. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  64. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  65. package/dist/src/channels/wechat-rpa.js +6 -1028
  66. package/dist/src/channels/wecom.js +4 -357
  67. package/dist/src/commands/agent.js +6 -131
  68. package/dist/src/commands/daemon-windows.js +8 -48
  69. package/dist/src/commands/daemon.js +19 -1013
  70. package/dist/src/commands/external-attachments.js +1 -51
  71. package/dist/src/commands/external.js +1 -137
  72. package/dist/src/commands/manager.js +2 -391
  73. package/dist/src/commands/pair-qr.js +1 -6
  74. package/dist/src/commands/pair.js +9 -287
  75. package/dist/src/commands/tools.js +1 -34
  76. package/dist/src/commands/upgrade.js +1 -198
  77. package/dist/src/config/index.js +1 -35
  78. package/dist/src/daemon-log.js +6 -58
  79. package/dist/src/env-path.js +1 -64
  80. package/dist/src/fs/boundary.js +1 -126
  81. package/dist/src/fs/handler.js +1 -130
  82. package/dist/src/fs/security.js +1 -32
  83. package/dist/src/fs/text-decoder.js +1 -110
  84. package/dist/src/index.js +2 -404
  85. package/dist/src/log-reporter.js +1 -16
  86. package/dist/src/manager/prompt.js +29 -34
  87. package/dist/src/manager/registry.js +2 -269
  88. package/dist/src/manager/runtime.js +19 -1007
  89. package/dist/src/native-fusion/config.js +1 -5
  90. package/dist/src/native-fusion/opencode-parser.js +3 -123
  91. package/dist/src/native-fusion/parser-common.js +8 -264
  92. package/dist/src/native-fusion/parsers.js +8 -729
  93. package/dist/src/native-fusion/service.js +2 -225
  94. package/dist/src/native-fusion/state.js +1 -22
  95. package/dist/src/native-fusion/types.js +1 -1
  96. package/dist/src/region.js +1 -88
  97. package/dist/src/relay/client.js +1 -343
  98. package/dist/src/session/archive-zip.js +1 -220
  99. package/dist/src/session/handlers/agent-config.js +1 -150
  100. package/dist/src/session/handlers/agents.js +1 -55
  101. package/dist/src/session/handlers/chat.js +2 -751
  102. package/dist/src/session/handlers/control.js +1 -55
  103. package/dist/src/session/handlers/fs.js +1 -783
  104. package/dist/src/session/handlers/session-refresh.js +1 -47
  105. package/dist/src/session/handlers/skills.js +1 -121
  106. package/dist/src/session/handlers/title.js +1 -60
  107. package/dist/src/session/handlers/tool-detail.js +1 -218
  108. package/dist/src/session/manager.js +1 -319
  109. package/dist/src/session/projection.js +1 -54
  110. package/dist/src/session/queue.js +4 -317
  111. package/dist/src/session/remote-attachments.js +1 -72
  112. package/dist/src/session/store.js +3 -109
  113. package/dist/src/session/types.js +1 -4
  114. package/dist/src/skills/registry.js +15 -148
  115. package/dist/src/skills/setup.js +1 -101
  116. package/dist/src/tools/markdown-to-pdf.js +10 -346
  117. package/dist/src/upgrade/engine.js +3 -347
  118. package/package.json +3 -2
@@ -1,84 +1 @@
1
- // @arch docs/features/wechat-rpa-productization-plan.md
2
- // @test src/__tests__/wechat-channel-runner.test.ts
3
- import path from 'node:path';
4
- import { createWeChatChannelApiClient, } from './client.js';
5
- import { WeChatChannelHelperClient } from './helper-client.js';
6
- import { resolveWeChatChannelHelperAsset, WECHAT_CHANNEL_HELPER_VERSION } from './helper-assets.js';
7
- import { observeWeChatChannelBindingViaHelper } from './observer.js';
8
- import { WeChatChannelScheduler } from './scheduler.js';
9
- import { loadWeChatChannelLedger } from './ledger.js';
10
- export class WeChatChannelProductRunner {
11
- options;
12
- scheduler;
13
- helper;
14
- helperStarted = false;
15
- constructor(options) {
16
- this.options = options;
17
- const api = options.api ?? createWeChatChannelApiClient();
18
- this.helper = options.helper ?? createHelperClient(options.helperClientOptions);
19
- this.scheduler = new WeChatChannelScheduler({
20
- runtime: options.runtime,
21
- workDir: options.workDir,
22
- api,
23
- ledgerPath: options.ledgerPath,
24
- outboundLedgerPath: options.outboundLedgerPath,
25
- onInboundMessages: options.onInboundMessages,
26
- observeBinding: (binding) => observeWeChatChannelBindingViaHelper({
27
- runtime: options.runtime,
28
- binding,
29
- helper: this.helper,
30
- api,
31
- localLedgerTailAnchors: buildLocalLedgerTailAnchors(options.ledgerPath ?? defaultLedgerPath(options.workDir, options.runtime.runtimeId), options.runtime.runtimeId, binding.bindingId),
32
- }),
33
- });
34
- }
35
- async start() {
36
- await this.ensureHelperStarted();
37
- await this.scheduler.start();
38
- }
39
- async tick() {
40
- await this.ensureHelperStarted();
41
- return this.scheduler.tick();
42
- }
43
- async stop() {
44
- this.scheduler.stop();
45
- if (this.helperStarted && this.helper.stop)
46
- await this.helper.stop();
47
- this.helperStarted = false;
48
- }
49
- async ensureHelperStarted() {
50
- if (this.helperStarted)
51
- return;
52
- if (this.helper.start)
53
- await this.helper.start();
54
- this.helperStarted = true;
55
- }
56
- }
57
- export function createWeChatChannelProductRunner(options) {
58
- return new WeChatChannelProductRunner(options);
59
- }
60
- export function buildLocalLedgerTailAnchors(ledgerPath, runtimeId, bindingId, limit = 3) {
61
- const ledger = loadWeChatChannelLedger(ledgerPath, runtimeId);
62
- const recent = ledger.bindings[bindingId]?.recent ?? [];
63
- return recent.slice(-limit).map((message) => ({
64
- stableMessageKey: message.stableMessageKey,
65
- senderRole: message.senderRole,
66
- kind: message.kind,
67
- anchorText: message.anchorText ?? message.normalizedText ?? message.textExcerpt ?? null,
68
- anchorMetadata: message.anchorMetadata ?? null,
69
- }));
70
- }
71
- function createHelperClient(options) {
72
- if (options)
73
- return new WeChatChannelHelperClient(options);
74
- const resolved = resolveWeChatChannelHelperAsset();
75
- if (!resolved.ok)
76
- throw new Error(`${resolved.reasonCode}: ${resolved.message}`);
77
- return new WeChatChannelHelperClient({
78
- helperPath: resolved.helperPath,
79
- expectedHelperVersion: resolved.version || WECHAT_CHANNEL_HELPER_VERSION,
80
- });
81
- }
82
- function defaultLedgerPath(workDir, runtimeId) {
83
- return path.join(workDir, 'wechat-channel', `${runtimeId}.ledger.json`);
84
- }
1
+ import i from"node:path";import{createWeChatChannelApiClient as o}from"./client.js";import{WeChatChannelHelperClient as h}from"./helper-client.js";import{resolveWeChatChannelHelperAsset as s,WECHAT_CHANNEL_HELPER_VERSION as d}from"./helper-assets.js";import{ensureHelperPreflight as u,focusWeChatWindow as c,observeWeChatChannelBindingViaHelper as p,openConversationInVisibleList as C}from"./observer.js";import{WeChatChannelScheduler as w}from"./scheduler.js";import{loadWeChatChannelLedger as f}from"./ledger.js";import{WeChatChannelOutboundSender as m}from"./outbound-sender.js";class b{options;scheduler;helper;helperStarted=!1;constructor(e){this.options=e;const a=e.api??o();this.helper=e.helper??H(e.helperClientOptions),this.scheduler=new w({runtime:e.runtime,workDir:e.workDir,api:a,ledgerPath:e.ledgerPath,outboundLedgerPath:e.outboundLedgerPath,onInboundMessages:e.onInboundMessages,outboundSender:new m({helper:this.helper,openConversation:async t=>{await u(this.helper);const l=await c(this.helper);return C({helper:this.helper,window:l,binding:{bindingId:`outbound:${t}`,sessionId:"outbound",conversationDisplayName:t,enabled:!0,allowReply:!0,downloadMedia:!0}})}}),observeBinding:t=>p({runtime:e.runtime,binding:t,helper:this.helper,api:a,localLedgerTailAnchors:g(e.ledgerPath??P(e.workDir,e.runtime.runtimeId),e.runtime.runtimeId,t.bindingId)})})}async start(){await this.ensureHelperStarted(),await this.scheduler.start()}async tick(){return await this.ensureHelperStarted(),this.scheduler.tick()}async stop(){this.scheduler.stop(),this.helperStarted&&this.helper.stop&&await this.helper.stop(),this.helperStarted=!1}async ensureHelperStarted(){this.helperStarted||(this.helper.start&&await this.helper.start(),this.helperStarted=!0)}}function M(r){return new b(r)}function g(r,e,a,t=3){return(f(r,e).bindings[a]?.recent??[]).slice(-t).map(n=>({stableMessageKey:n.stableMessageKey,senderRole:n.senderRole,kind:n.kind,anchorText:n.anchorText??n.normalizedText??n.textExcerpt??null,anchorMetadata:n.anchorMetadata??null}))}function H(r){if(r)return new h(r);const e=s();if(!e.ok)throw new Error(`${e.reasonCode}: ${e.message}`);return new h({helperPath:e.helperPath,expectedHelperVersion:e.version||d})}function P(r,e){return i.join(r,"wechat-channel",`${e}.ledger.json`)}export{b as WeChatChannelProductRunner,g as buildLocalLedgerTailAnchors,M as createWeChatChannelProductRunner};
@@ -1,66 +1 @@
1
- // @arch docs/features/wechat-rpa-productization-plan.md
2
- // @arch docs/features/wechat-rpa-helper-runtime.md
3
- // @test src/__tests__/wechat-channel-runtime.test.ts
4
- export const WECHAT_CHANNEL_RUNTIME_KIND = 'wechat_channel';
5
- export const WECHAT_CHANNEL_RUNTIME_VERSION = 1;
6
- export const WECHAT_CHANNEL_RECENT_MESSAGE_WINDOW = 20;
7
- export const WECHAT_CHANNEL_DEFAULT_POLL_INTERVAL_MS = 5 * 60 * 1000;
8
- export const WECHAT_CHANNEL_MIN_POLL_INTERVAL_MS = 60 * 1000;
9
- export const WECHAT_CHANNEL_MAX_POLL_INTERVAL_MS = 10 * 60 * 1000;
10
- export function defaultWeChatChannelRuntimePolicy(pollIntervalMs) {
11
- return {
12
- kind: WECHAT_CHANNEL_RUNTIME_KIND,
13
- runtimeVersion: WECHAT_CHANNEL_RUNTIME_VERSION,
14
- platform: 'darwin',
15
- pollIntervalMs: normalizeWeChatChannelPollIntervalMs(pollIntervalMs),
16
- minPollIntervalMs: WECHAT_CHANNEL_MIN_POLL_INTERVAL_MS,
17
- maxPollIntervalMs: WECHAT_CHANNEL_MAX_POLL_INTERVAL_MS,
18
- recentMessageWindow: WECHAT_CHANNEL_RECENT_MESSAGE_WINDOW,
19
- requiresServerDecision: true,
20
- requiresMacHelper: true,
21
- productRuntimeUsesLabFacade: false,
22
- };
23
- }
24
- export function normalizeWeChatChannelPollIntervalMs(value) {
25
- if (!Number.isFinite(value))
26
- return WECHAT_CHANNEL_DEFAULT_POLL_INTERVAL_MS;
27
- return Math.min(WECHAT_CHANNEL_MAX_POLL_INTERVAL_MS, Math.max(WECHAT_CHANNEL_MIN_POLL_INTERVAL_MS, Number(value)));
28
- }
29
- export function createWeChatChannelRuntime(config) {
30
- const runtimeId = normalizeRequiredString(config.runtimeId, 'runtimeId');
31
- const machineId = normalizeRequiredString(config.machineId, 'machineId');
32
- const pollIntervalMs = normalizeWeChatChannelPollIntervalMs(config.pollIntervalMs);
33
- return {
34
- kind: WECHAT_CHANNEL_RUNTIME_KIND,
35
- runtimeId,
36
- machineId,
37
- foregroundPolicy: config.foregroundPolicy === 'work' ? 'work' : 'polite',
38
- policy: defaultWeChatChannelRuntimePolicy(pollIntervalMs),
39
- bindings: normalizeBindings(config.bindings ?? []),
40
- };
41
- }
42
- function normalizeBindings(bindings) {
43
- const seen = new Set();
44
- const result = [];
45
- for (const binding of bindings) {
46
- const bindingId = normalizeRequiredString(binding.bindingId, 'bindingId');
47
- if (seen.has(bindingId))
48
- continue;
49
- seen.add(bindingId);
50
- result.push({
51
- bindingId,
52
- sessionId: normalizeRequiredString(binding.sessionId, 'sessionId'),
53
- conversationDisplayName: normalizeRequiredString(binding.conversationDisplayName, 'conversationDisplayName'),
54
- enabled: binding.enabled !== false,
55
- allowReply: binding.allowReply !== false,
56
- downloadMedia: binding.downloadMedia !== false,
57
- });
58
- }
59
- return result;
60
- }
61
- function normalizeRequiredString(value, field) {
62
- const normalized = String(value || '').trim();
63
- if (!normalized)
64
- throw new Error(`WeChat channel ${field} is required`);
65
- return normalized;
66
- }
1
+ const a="wechat_channel",N=1,E=20,d=3e5,A=6e4,L=6e5;function s(e){return{kind:a,runtimeVersion:1,platform:"darwin",pollIntervalMs:_(e),minPollIntervalMs:6e4,maxPollIntervalMs:6e5,recentMessageWindow:20,requiresServerDecision:!0,requiresMacHelper:!0,productRuntimeUsesLabFacade:!1}}function _(e){return Number.isFinite(e)?Math.min(6e5,Math.max(6e4,Number(e))):3e5}function I(e){const t=o(e.runtimeId,"runtimeId"),r=o(e.machineId,"machineId"),n=_(e.pollIntervalMs);return{kind:a,runtimeId:t,machineId:r,foregroundPolicy:e.foregroundPolicy==="work"?"work":"polite",policy:s(n),bindings:l(e.bindings??[])}}function l(e){const t=new Set,r=[];for(const n of e){const i=o(n.bindingId,"bindingId");t.has(i)||(t.add(i),r.push({bindingId:i,sessionId:o(n.sessionId,"sessionId"),conversationDisplayName:o(n.conversationDisplayName,"conversationDisplayName"),enabled:n.enabled!==!1,allowReply:n.allowReply!==!1,downloadMedia:n.downloadMedia!==!1}))}return r}function o(e,t){const r=String(e||"").trim();if(!r)throw new Error(`WeChat channel ${t} is required`);return r}export{d as WECHAT_CHANNEL_DEFAULT_POLL_INTERVAL_MS,L as WECHAT_CHANNEL_MAX_POLL_INTERVAL_MS,A as WECHAT_CHANNEL_MIN_POLL_INTERVAL_MS,E as WECHAT_CHANNEL_RECENT_MESSAGE_WINDOW,a as WECHAT_CHANNEL_RUNTIME_KIND,N as WECHAT_CHANNEL_RUNTIME_VERSION,I as createWeChatChannelRuntime,s as defaultWeChatChannelRuntimePolicy,_ as normalizeWeChatChannelPollIntervalMs};
@@ -1,11 +1,13 @@
1
1
  import type { WeChatChannelRuntime, WeChatChannelBindingConfig } from './runtime.js';
2
2
  import type { WeChatChannelApiClient, WeChatChannelObservedMessage } from './client.js';
3
+ import { type WeChatChannelOutboundSender } from './outbound-sender.js';
3
4
  export type WeChatChannelSchedulerOptions = {
4
5
  runtime: WeChatChannelRuntime;
5
6
  workDir: string;
6
7
  api: Pick<WeChatChannelApiClient, 'upsertRuntime' | 'ingest' | 'reportRunStatus'> & Partial<Pick<WeChatChannelApiClient, 'reportOutboundStatus'>>;
7
8
  observeBinding: (binding: WeChatChannelBindingConfig) => Promise<WeChatChannelObservedMessage[]>;
8
9
  onInboundMessages?: (binding: WeChatChannelBindingConfig, messages: WeChatChannelObservedMessage[]) => Promise<void> | void;
10
+ outboundSender?: WeChatChannelOutboundSender;
9
11
  ledgerPath?: string;
10
12
  outboundLedgerPath?: string;
11
13
  };
@@ -13,6 +15,9 @@ export type WeChatChannelSchedulerTickResult = {
13
15
  bindingId: string;
14
16
  observedCount: number;
15
17
  newInboundCount: number;
18
+ outboundSentCount: number;
19
+ staleOutboundCount: number;
20
+ failedOutboundCount: number;
16
21
  confirmedEchoCount: number;
17
22
  manualReviewCount: number;
18
23
  revision: number;
@@ -1,152 +1 @@
1
- // @arch docs/features/wechat-rpa-productization-plan.md
2
- // @test src/__tests__/wechat-channel-scheduler.test.ts
3
- import path from 'node:path';
4
- import { loadWeChatChannelLedger, markWeChatChannelBindingDisabled, saveWeChatChannelLedger, updateWeChatChannelBindingLedger, } from './ledger.js';
5
- import { classifyWeChatOutboundEchoes, loadWeChatChannelOutboundLedger, saveWeChatChannelOutboundLedger, } from './outbound-ledger.js';
6
- import { clearExpiredWeChatChannelCooldown, isWeChatChannelCooldownActive, noteWeChatChannelInterruption, noteWeChatChannelStableRun, } from './cooldown.js';
7
- export class WeChatChannelScheduler {
8
- options;
9
- timer = null;
10
- running = false;
11
- constructor(options) {
12
- this.options = options;
13
- }
14
- async start() {
15
- if (this.timer)
16
- return;
17
- await this.tick();
18
- this.timer = setInterval(() => {
19
- void this.tick().catch(() => { });
20
- }, this.options.runtime.policy.pollIntervalMs);
21
- this.timer.unref();
22
- }
23
- stop() {
24
- if (!this.timer)
25
- return;
26
- clearInterval(this.timer);
27
- this.timer = null;
28
- }
29
- async tick() {
30
- if (this.running)
31
- return;
32
- this.running = true;
33
- try {
34
- const ledgerPath = this.options.ledgerPath || defaultLedgerPath(this.options.workDir, this.options.runtime.runtimeId);
35
- const outboundLedgerPath = this.options.outboundLedgerPath || defaultOutboundLedgerPath(this.options.workDir, this.options.runtime.runtimeId);
36
- const ledger = loadWeChatChannelLedger(ledgerPath, this.options.runtime.runtimeId);
37
- const outboundLedger = loadWeChatChannelOutboundLedger(outboundLedgerPath, this.options.runtime.runtimeId);
38
- const results = [];
39
- for (const binding of this.options.runtime.bindings.filter((item) => !item.enabled)) {
40
- if (!ledger.bindings[binding.bindingId]?.disabledSince) {
41
- markWeChatChannelBindingDisabled({ ledger, bindingId: binding.bindingId });
42
- }
43
- }
44
- for (const binding of this.options.runtime.bindings.filter((item) => item.enabled)) {
45
- await this.options.api.upsertRuntime(this.options.runtime, binding);
46
- const existingBinding = ledger.bindings[binding.bindingId];
47
- if (existingBinding?.cooldown) {
48
- existingBinding.cooldown = clearExpiredWeChatChannelCooldown(existingBinding.cooldown);
49
- if (isWeChatChannelCooldownActive(existingBinding.cooldown)) {
50
- await this.options.api.reportRunStatus(this.options.runtime, binding, {
51
- status: 'cooldown',
52
- reasonCode: existingBinding.cooldown.manualReviewReason || 'user_interruption_cooldown',
53
- });
54
- results.push({
55
- bindingId: binding.bindingId,
56
- observedCount: 0,
57
- newInboundCount: 0,
58
- confirmedEchoCount: 0,
59
- manualReviewCount: 0,
60
- revision: existingBinding.revision,
61
- });
62
- continue;
63
- }
64
- }
65
- const observedMessages = await this.options.observeBinding(binding);
66
- const echoes = classifyWeChatOutboundEchoes({
67
- ledger: outboundLedger,
68
- bindingId: binding.bindingId,
69
- messages: observedMessages,
70
- });
71
- const state = updateWeChatChannelBindingLedger({
72
- ledger,
73
- bindingId: binding.bindingId,
74
- observedMessages,
75
- });
76
- state.binding.cooldown = noteWeChatChannelStableRun(state.binding.cooldown);
77
- await this.options.api.ingest(this.options.runtime, binding, {
78
- idempotencyKey: `${binding.bindingId}:${state.binding.revision}:${lastMessageKey(state.binding.recent)}`,
79
- messages: state.binding.recent,
80
- });
81
- if (state.newMessages.length > 0)
82
- await this.options.onInboundMessages?.(binding, state.newMessages);
83
- await this.reportOutboundProjections(binding, [...echoes.confirmedRecords, ...echoes.manualReviewRecords]);
84
- await this.options.api.reportRunStatus(this.options.runtime, binding, {
85
- status: runStatusForTick(state.newMessages.length, echoes.confirmedRecords, echoes.manualReviewRecords),
86
- });
87
- results.push({
88
- bindingId: binding.bindingId,
89
- observedCount: observedMessages.length,
90
- newInboundCount: state.newMessages.length,
91
- confirmedEchoCount: echoes.confirmedRecords.length,
92
- manualReviewCount: echoes.manualReviewRecords.length,
93
- revision: state.binding.revision,
94
- });
95
- }
96
- saveWeChatChannelLedger(ledgerPath, ledger);
97
- saveWeChatChannelOutboundLedger(outboundLedgerPath, outboundLedger);
98
- return results;
99
- }
100
- finally {
101
- this.running = false;
102
- }
103
- }
104
- noteInterruption(bindingId, reason, now = new Date()) {
105
- const ledgerPath = this.options.ledgerPath || defaultLedgerPath(this.options.workDir, this.options.runtime.runtimeId);
106
- const ledger = loadWeChatChannelLedger(ledgerPath, this.options.runtime.runtimeId);
107
- const existing = ledger.bindings[bindingId] ?? {
108
- bindingId,
109
- baselineEstablished: false,
110
- revision: 0,
111
- recent: [],
112
- pendingSendKeys: [],
113
- };
114
- existing.cooldown = noteWeChatChannelInterruption({ state: existing.cooldown, reason, now });
115
- ledger.bindings[bindingId] = existing;
116
- saveWeChatChannelLedger(ledgerPath, ledger);
117
- }
118
- async reportOutboundProjections(binding, records) {
119
- if (!this.options.api.reportOutboundStatus)
120
- return;
121
- for (const record of records) {
122
- await this.options.api.reportOutboundStatus(this.options.runtime, binding, {
123
- replyId: record.replyId,
124
- idempotencyKey: record.idempotencyKey,
125
- status: record.sendStatus,
126
- replyBaseRevision: record.replyBaseRevision,
127
- sentAt: record.sentAt,
128
- confirmedAt: record.confirmedAt,
129
- failureCode: record.failureCode,
130
- lastErrorSummary: record.lastErrorSummary,
131
- });
132
- }
133
- }
134
- }
135
- function defaultLedgerPath(workDir, runtimeId) {
136
- return path.join(workDir, 'wechat-channel', `${runtimeId}.ledger.json`);
137
- }
138
- function defaultOutboundLedgerPath(workDir, runtimeId) {
139
- return path.join(workDir, 'wechat-channel', `${runtimeId}.outbound-ledger.json`);
140
- }
141
- function lastMessageKey(messages) {
142
- return messages.at(-1)?.stableMessageKey || 'empty';
143
- }
144
- function runStatusForTick(newMessageCount, confirmedRecords, manualReviewRecords) {
145
- if (manualReviewRecords.length > 0)
146
- return 'manual_review';
147
- if (newMessageCount > 0)
148
- return 'new_messages_ingested';
149
- if (confirmedRecords.length > 0)
150
- return 'outbound_echo_confirmed';
151
- return 'baseline_or_no_change';
152
- }
1
+ import c from"node:path";import{loadWeChatChannelLedger as g,markWeChatChannelBindingDisabled as m,saveWeChatChannelLedger as b,updateWeChatChannelBindingLedger as f}from"./ledger.js";import{classifyWeChatOutboundEchoes as w,loadWeChatChannelOutboundLedger as C,saveWeChatChannelOutboundLedger as R}from"./outbound-ledger.js";import{clearExpiredWeChatChannelCooldown as I,isWeChatChannelCooldownActive as v,noteWeChatChannelInterruption as y,noteWeChatChannelStableRun as S}from"./cooldown.js";import{sendQueuedWeChatOutboundRecords as _}from"./outbound-sender.js";class B{options;timer=null;running=!1;constructor(t){this.options=t}async start(){this.timer||(await this.tick(),this.timer=setInterval(()=>{this.tick().catch(()=>{})},this.options.runtime.policy.pollIntervalMs),this.timer.unref())}stop(){this.timer&&(clearInterval(this.timer),this.timer=null)}async tick(){if(!this.running){this.running=!0;try{const t=this.options.ledgerPath||p(this.options.workDir,this.options.runtime.runtimeId),a=this.options.outboundLedgerPath||O(this.options.workDir,this.options.runtime.runtimeId),e=g(t,this.options.runtime.runtimeId),u=C(a,this.options.runtime.runtimeId),s=[];for(const n of this.options.runtime.bindings.filter(o=>!o.enabled))e.bindings[n.bindingId]?.disabledSince||m({ledger:e,bindingId:n.bindingId});for(const n of this.options.runtime.bindings.filter(o=>o.enabled)){await this.options.api.upsertRuntime(this.options.runtime,n);const o=e.bindings[n.bindingId];if(o?.cooldown&&(o.cooldown=I(o.cooldown),v(o.cooldown))){await this.options.api.reportRunStatus(this.options.runtime,n,{status:"cooldown",reasonCode:o.cooldown.manualReviewReason||"user_interruption_cooldown"}),s.push({bindingId:n.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:o.revision});continue}const h=await this.options.observeBinding(n),l=w({ledger:u,bindingId:n.bindingId,messages:h}),i=f({ledger:e,bindingId:n.bindingId,observedMessages:h}),r=this.options.outboundSender?await _({ledger:u,bindingId:n.bindingId,currentLastInboundRevision:i.binding.revision,sender:this.options.outboundSender}):{sentRecords:[],staleRecords:[],failedRecords:[]};i.binding.cooldown=S(i.binding.cooldown),await this.options.api.ingest(this.options.runtime,n,{idempotencyKey:`${n.bindingId}:${i.binding.revision}:${W(i.binding.recent)}`,messages:i.binding.recent}),i.newMessages.length>0&&await this.options.onInboundMessages?.(n,i.newMessages),await this.reportOutboundProjections(n,[...l.confirmedRecords,...l.manualReviewRecords,...r.sentRecords,...r.staleRecords,...r.failedRecords]),await this.options.api.reportRunStatus(this.options.runtime,n,{status:L(i.newMessages.length,l.confirmedRecords,l.manualReviewRecords,r.sentRecords,r.staleRecords,r.failedRecords)}),s.push({bindingId:n.bindingId,observedCount:h.length,newInboundCount:i.newMessages.length,outboundSentCount:r.sentRecords.length,staleOutboundCount:r.staleRecords.length,failedOutboundCount:r.failedRecords.length,confirmedEchoCount:l.confirmedRecords.length,manualReviewCount:l.manualReviewRecords.length,revision:i.binding.revision})}return b(t,e),R(a,u),s}finally{this.running=!1}}}noteInterruption(t,a,e=new Date){const u=this.options.ledgerPath||p(this.options.workDir,this.options.runtime.runtimeId),s=g(u,this.options.runtime.runtimeId),n=s.bindings[t]??{bindingId:t,baselineEstablished:!1,revision:0,recent:[],pendingSendKeys:[]};n.cooldown=y({state:n.cooldown,reason:a,now:e}),s.bindings[t]=n,b(u,s)}async reportOutboundProjections(t,a){if(this.options.api.reportOutboundStatus)for(const e of a)await this.options.api.reportOutboundStatus(this.options.runtime,t,{replyId:e.replyId,idempotencyKey:e.idempotencyKey,status:e.sendStatus,replyBaseRevision:e.replyBaseRevision,sentAt:e.sentAt,confirmedAt:e.confirmedAt,failureCode:e.failureCode,lastErrorSummary:e.lastErrorSummary})}}function p(d,t){return c.join(d,"wechat-channel",`${t}.ledger.json`)}function O(d,t){return c.join(d,"wechat-channel",`${t}.outbound-ledger.json`)}function W(d){return d.at(-1)?.stableMessageKey||"empty"}function L(d,t,a,e=[],u=[],s=[]){return a.length>0?"manual_review":s.length>0?"outbound_send_failed":u.length>0?"outbound_stale":d>0?"new_messages_ingested":e.length>0?"outbound_sent_unconfirmed":t.length>0?"outbound_echo_confirmed":"baseline_or_no_change"}export{B as WeChatChannelScheduler};
@@ -1,96 +1 @@
1
- // @arch docs/features/wechat-rpa-channel.md
2
- // @test src/__tests__/wechat-rpa-normalizer.test.ts
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import { execFile } from 'node:child_process';
6
- import { promisify } from 'node:util';
7
- const execFileAsync = promisify(execFile);
8
- export async function runMacWeChatRpaFlow(options) {
9
- if (process.platform !== 'darwin') {
10
- throw new Error('WeChat RPA macOS flow can only run on macOS');
11
- }
12
- const scriptPath = resolveFlowScriptPath(options.scriptPath, options.workDir);
13
- const args = [
14
- scriptPath,
15
- '--group',
16
- options.groupName,
17
- '--idle-seconds',
18
- String(Number.isFinite(options.idleSeconds) ? options.idleSeconds : 15),
19
- ];
20
- if (options.forceForeground)
21
- args.push('--force');
22
- if (options.noRestore)
23
- args.push('--no-restore');
24
- if (options.replyText)
25
- args.push('--reply-text', options.replyText);
26
- if (options.attachmentPath)
27
- args.push('--attachment-path', options.attachmentPath);
28
- if (options.downloadAttachmentsDir)
29
- args.push('--download-attachments-dir', options.downloadAttachmentsDir);
30
- if (Number.isFinite(options.recentLimit) && Number(options.recentLimit) > 0) {
31
- args.push('--recent-limit', String(options.recentLimit));
32
- }
33
- let stdout = '';
34
- let stderr = '';
35
- try {
36
- const result = await execFileAsync(process.execPath, args, {
37
- cwd: options.workDir || process.cwd(),
38
- timeout: options.timeoutMs ?? 120_000,
39
- maxBuffer: 20 * 1024 * 1024,
40
- });
41
- stdout = result.stdout;
42
- stderr = result.stderr;
43
- }
44
- catch (error) {
45
- const execError = error;
46
- stdout = execError.stdout || '';
47
- stderr = execError.stderr || '';
48
- const parsed = parseFlowStdout(stdout);
49
- if (parsed?.interrupted)
50
- return parsed;
51
- if (parsed && hasPartialSendProgress(parsed))
52
- return parsed;
53
- throw new Error(stderr.trim() || stdout.slice(0, 1_000) || execError.message || 'WeChat RPA flow failed');
54
- }
55
- try {
56
- const parsed = JSON.parse(stdout);
57
- if (parsed.interrupted)
58
- return parsed;
59
- if (!parsed.ok && hasSendAttempt(parsed))
60
- return parsed;
61
- if (!parsed.ok)
62
- throw new Error(parsed.error || 'WeChat RPA flow failed');
63
- return parsed;
64
- }
65
- catch (error) {
66
- const detail = stderr.trim() || stdout.slice(0, 1_000);
67
- throw new Error(detail || (error instanceof Error ? error.message : String(error)));
68
- }
69
- }
70
- function parseFlowStdout(stdout) {
71
- try {
72
- return JSON.parse(stdout);
73
- }
74
- catch {
75
- return null;
76
- }
77
- }
78
- function hasPartialSendProgress(result) {
79
- return Boolean(result.sentReplyObserved || result.sentAttachmentObserved);
80
- }
81
- function hasSendAttempt(result) {
82
- return Boolean(result.sentReply || result.sentAttachment || result.sentReplyObserved || result.sentAttachmentObserved);
83
- }
84
- function resolveFlowScriptPath(scriptPath, workDir) {
85
- const candidates = [
86
- scriptPath,
87
- process.env.SHENNIAN_WECHAT_RPA_FLOW_SCRIPT,
88
- workDir ? path.join(workDir, 'scripts/wechat-rpa-flow.mjs') : '',
89
- path.resolve(process.cwd(), 'scripts/wechat-rpa-flow.mjs'),
90
- ].filter(Boolean);
91
- const found = candidates.find((candidate) => fs.existsSync(candidate));
92
- if (!found) {
93
- throw new Error('WeChat RPA flow script is missing; set SHENNIAN_WECHAT_RPA_FLOW_SCRIPT or channel flowScriptPath');
94
- }
95
- return path.resolve(found);
96
- }
1
+ import d from"node:fs";import i from"node:path";import{execFile as h}from"node:child_process";import{promisify as f}from"node:util";const l=f(h);async function R(e){if(process.platform!=="darwin")throw new Error("WeChat RPA macOS flow can only run on macOS");const n=[p(e.scriptPath,e.workDir),"--group",e.groupName,"--idle-seconds",String(Number.isFinite(e.idleSeconds)?e.idleSeconds:15)];e.forceForeground&&n.push("--force"),e.noRestore&&n.push("--no-restore"),e.replyText&&n.push("--reply-text",e.replyText),e.attachmentPath&&n.push("--attachment-path",e.attachmentPath),e.downloadAttachmentsDir&&n.push("--download-attachments-dir",e.downloadAttachmentsDir),Number.isFinite(e.recentLimit)&&Number(e.recentLimit)>0&&n.push("--recent-limit",String(e.recentLimit));let t="",s="";try{const r=await l(process.execPath,n,{cwd:e.workDir||process.cwd(),timeout:e.timeoutMs??12e4,maxBuffer:20971520});t=r.stdout,s=r.stderr}catch(r){const a=r;t=a.stdout||"",s=a.stderr||"";const c=m(t);if(c?.interrupted||c&&u(c))return c;throw new Error(s.trim()||t.slice(0,1e3)||a.message||"WeChat RPA flow failed")}try{const r=JSON.parse(t);if(r.interrupted||!r.ok&&w(r))return r;if(!r.ok)throw new Error(r.error||"WeChat RPA flow failed");return r}catch(r){const a=s.trim()||t.slice(0,1e3);throw new Error(a||(r instanceof Error?r.message:String(r)))}}function m(e){try{return JSON.parse(e)}catch{return null}}function u(e){return!!(e.sentReplyObserved||e.sentAttachmentObserved)}function w(e){return!!(e.sentReply||e.sentAttachment||e.sentReplyObserved||e.sentAttachmentObserved)}function p(e,o){const t=[e,process.env.SHENNIAN_WECHAT_RPA_FLOW_SCRIPT,o?i.join(o,"scripts/wechat-rpa-flow.mjs"):"",i.resolve(process.cwd(),"scripts/wechat-rpa-flow.mjs")].filter(Boolean).find(s=>d.existsSync(s));if(!t)throw new Error("WeChat RPA flow script is missing; set SHENNIAN_WECHAT_RPA_FLOW_SCRIPT or channel flowScriptPath");return i.resolve(t)}export{R as runMacWeChatRpaFlow};
@@ -1,9 +1,4 @@
1
- // @arch docs/features/wechat-rpa-channel.md
2
- // @test src/__tests__/wechat-rpa-normalizer.test.ts
3
- import { execFile } from 'node:child_process';
4
- import { promisify } from 'node:util';
5
- const execFileAsync = promisify(execFile);
6
- const WECHAT_PROBE_SCRIPT = `
1
+ import{execFile as r}from"node:child_process";import{promisify as s}from"node:util";const i=s(r),a=`
7
2
  tell application "System Events"
8
3
  set isRunning to exists process "WeChat"
9
4
  if isRunning is false then
@@ -17,47 +12,10 @@ tell application "System Events"
17
12
  set titleText to name of window 1
18
13
  end try
19
14
  end if
20
- return (isFront as text) & "\n" & titleText
15
+ return (isFront as text) & "
16
+ " & titleText
21
17
  end tell
22
18
  end tell
23
- `;
24
- export async function probeMacWeChat() {
25
- if (process.platform !== 'darwin') {
26
- return { ok: false, platform: process.platform, wechatRunning: false, frontmost: false, message: 'macOS only' };
27
- }
28
- try {
29
- const { stdout } = await execFileAsync('osascript', ['-e', WECHAT_PROBE_SCRIPT], { timeout: 5_000 });
30
- const output = stdout.trim();
31
- if (output === 'not_running') {
32
- return { ok: true, platform: process.platform, wechatRunning: false, frontmost: false };
33
- }
34
- const [frontmostText, ...titleParts] = output.split('\n');
35
- return {
36
- ok: true,
37
- platform: process.platform,
38
- wechatRunning: true,
39
- frontmost: frontmostText === 'true',
40
- windowTitle: titleParts.join('\n').trim() || undefined,
41
- };
42
- }
43
- catch (err) {
44
- return {
45
- ok: false,
46
- platform: process.platform,
47
- wechatRunning: false,
48
- frontmost: false,
49
- message: err instanceof Error ? err.message : String(err),
50
- };
51
- }
52
- }
53
- export function observedMessageFromProbe(result) {
54
- if (!result.ok || !result.wechatRunning || !result.windowTitle)
55
- return null;
56
- return {
57
- conversationName: result.windowTitle,
58
- senderName: 'WeChat RPA',
59
- text: `[微信窗口可见性探测] WeChat ${result.frontmost ? '位于前台' : '未位于前台'},当前窗口:${result.windowTitle}`,
60
- observedAt: new Date().toISOString(),
61
- rawId: `probe:${result.windowTitle}:${result.frontmost}`,
62
- };
63
- }
19
+ `;async function m(){if(process.platform!=="darwin")return{ok:!1,platform:process.platform,wechatRunning:!1,frontmost:!1,message:"macOS only"};try{const{stdout:t}=await i("osascript",["-e",a],{timeout:5e3}),e=t.trim();if(e==="not_running")return{ok:!0,platform:process.platform,wechatRunning:!1,frontmost:!1};const[n,...o]=e.split(`
20
+ `);return{ok:!0,platform:process.platform,wechatRunning:!0,frontmost:n==="true",windowTitle:o.join(`
21
+ `).trim()||void 0}}catch(t){return{ok:!1,platform:process.platform,wechatRunning:!1,frontmost:!1,message:t instanceof Error?t.message:String(t)}}}function c(t){return!t.ok||!t.wechatRunning||!t.windowTitle?null:{conversationName:t.windowTitle,senderName:"WeChat RPA",text:`[\u5FAE\u4FE1\u7A97\u53E3\u53EF\u89C1\u6027\u63A2\u6D4B] WeChat ${t.frontmost?"\u4F4D\u4E8E\u524D\u53F0":"\u672A\u4F4D\u4E8E\u524D\u53F0"}\uFF0C\u5F53\u524D\u7A97\u53E3\uFF1A${t.windowTitle}`,observedAt:new Date().toISOString(),rawId:`probe:${t.windowTitle}:${t.frontmost}`}}export{c as observedMessageFromProbe,m as probeMacWeChat};
@@ -1,127 +1,7 @@
1
- // @arch docs/features/wechat-rpa-channel.md
2
- // @test src/__tests__/wechat-rpa-normalizer.test.ts
3
- import { createHash } from 'node:crypto';
4
- const MAX_DEDUP_KEYS = 500;
5
- export class WeChatRpaDeduper {
6
- seen = new Set();
7
- queue = [];
8
- constructor(initialMessageIds = []) {
9
- for (const id of initialMessageIds) {
10
- if (!id || this.seen.has(id))
11
- continue;
12
- this.seen.add(id);
13
- this.queue.push(id);
14
- }
15
- this.trim();
16
- }
17
- accept(messageId) {
18
- if (this.seen.has(messageId))
19
- return false;
20
- this.seen.add(messageId);
21
- this.queue.push(messageId);
22
- this.trim();
23
- return true;
24
- }
25
- snapshot() {
26
- return [...this.queue];
27
- }
28
- trim() {
29
- while (this.queue.length > MAX_DEDUP_KEYS) {
30
- const old = this.queue.shift();
31
- if (old)
32
- this.seen.delete(old);
33
- }
34
- }
35
- }
36
- export function normalizeWeChatRpaMessage(input, options = {}) {
37
- const conversationName = cleanText(input.conversationName);
38
- const text = cleanText(input.text);
39
- const attachments = normalizeAttachments(input.attachments);
40
- if (!conversationName || (!text && attachments.length === 0))
41
- return null;
42
- const senderName = cleanText(input.senderName || '') || null;
43
- const receivedAt = normalizeIso(input.observedAt);
44
- const conversationId = weChatRpaConversationId(conversationName);
45
- const senderId = stableId('wechat-sender', senderName || 'unknown');
46
- const rawRef = cleanText(input.rawId || '') || null;
47
- const isMentioned = input.isMentioned === true || isMentionedByText(text, options.selfNicknames);
48
- const messageId = rawRef
49
- ? stableId('wechat-message', `${conversationName}\n${rawRef}`)
50
- : stableId('wechat-message', `${conversationName}\n${senderName || ''}\n${text}\n${attachments.map((item) => item.name || item.url || item.type).join('\n')}\n${receivedAt}`);
51
- return {
52
- conversationId,
53
- conversationName,
54
- messageId,
55
- sender: { id: senderId, name: senderName },
56
- text,
57
- attachments,
58
- receivedAt,
59
- isMentioned,
60
- rawRef,
61
- };
62
- }
63
- export function weChatRpaConversationId(conversationName) {
64
- return stableId('wechat-conversation', cleanText(conversationName));
65
- }
66
- function cleanText(value) {
67
- return value.replace(/\s+/g, ' ').trim();
68
- }
69
- function normalizeAttachments(value) {
70
- if (!Array.isArray(value))
71
- return [];
72
- return value
73
- .map((item) => {
74
- const url = item?.url ? cleanText(item.url) : '';
75
- const localPath = item?.localPath ? cleanText(item.localPath) : '';
76
- const availability = normalizeAvailability(item?.availability, { url, localPath });
77
- return {
78
- type: cleanText(String(item?.type || 'file')) || 'file',
79
- ...(item?.name ? { name: cleanText(item.name) } : {}),
80
- ...(url ? { url } : {}),
81
- ...(item?.mimeType ? { mimeType: cleanText(item.mimeType) } : {}),
82
- ...(Number.isFinite(item?.size) && Number(item.size) > 0 ? { size: Number(item.size) } : {}),
83
- ...(localPath ? { localPath } : {}),
84
- ...(item?.thumbnailPath ? { thumbnailPath: cleanText(item.thumbnailPath) } : {}),
85
- ...(item?.hash ? { hash: cleanText(item.hash) } : {}),
86
- ...(availability ? { availability } : {}),
87
- ...(item?.machineId ? { machineId: cleanText(item.machineId) } : {}),
88
- ...(item?.expiresAt ? { expiresAt: cleanText(item.expiresAt) } : {}),
89
- ...(item?.providerError ? { providerError: cleanText(item.providerError) } : {}),
90
- };
91
- })
92
- .filter((item) => item.type);
93
- }
94
- function normalizeAvailability(value, sources) {
95
- if (value === 'edge-local'
96
- || value === 'server-url'
97
- || value === 'pending-download'
98
- || value === 'metadata-only'
99
- || value === 'unavailable-large') {
100
- return value;
101
- }
102
- if (sources.localPath)
103
- return 'edge-local';
104
- if (sources.url)
105
- return 'server-url';
106
- return 'metadata-only';
107
- }
108
- function normalizeIso(value) {
109
- if (!value)
110
- return new Date().toISOString();
111
- const date = new Date(value);
112
- return Number.isNaN(date.getTime()) ? new Date().toISOString() : date.toISOString();
113
- }
114
- function stableId(prefix, value) {
115
- return `${prefix}:${createHash('sha256').update(value).digest('hex').slice(0, 24)}`;
116
- }
117
- function isMentionedByText(text, selfNicknames) {
118
- const aliases = Array.isArray(selfNicknames)
119
- ? selfNicknames.map((item) => cleanText(item)).filter(Boolean)
120
- : [];
121
- if (!aliases.length)
122
- return false;
123
- return aliases.some((alias) => {
124
- const escaped = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
125
- return new RegExp(`@\\s*${escaped}(?=$|\\s|[,。!?,.!?::;;、)])`).test(text);
126
- });
127
- }
1
+ import{createHash as m}from"node:crypto";const g=500;class I{seen=new Set;queue=[];constructor(e=[]){for(const t of e)!t||this.seen.has(t)||(this.seen.add(t),this.queue.push(t));this.trim()}accept(e){return this.seen.has(e)?!1:(this.seen.add(e),this.queue.push(e),this.trim(),!0)}snapshot(){return[...this.queue]}trim(){for(;this.queue.length>g;){const e=this.queue.shift();e&&this.seen.delete(e)}}}function N(n,e={}){const t=r(n.conversationName),a=r(n.text),s=w(n.attachments);if(!t||!a&&s.length===0)return null;const i=r(n.senderName||"")||null,h=$(n.observedAt),u=y(t),d=o("wechat-sender",i||"unknown"),c=r(n.rawId||"")||null,f=n.isMentioned===!0||x(a,e.selfNicknames),p=c?o("wechat-message",`${t}
2
+ ${c}`):o("wechat-message",`${t}
3
+ ${i||""}
4
+ ${a}
5
+ ${s.map(l=>l.name||l.url||l.type).join(`
6
+ `)}
7
+ ${h}`);return{conversationId:u,conversationName:t,messageId:p,sender:{id:d,name:i},text:a,attachments:s,receivedAt:h,isMentioned:f,rawRef:c}}function y(n){return o("wechat-conversation",r(n))}function r(n){return n.replace(/\s+/g," ").trim()}function w(n){return Array.isArray(n)?n.map(e=>{const t=e?.url?r(e.url):"",a=e?.localPath?r(e.localPath):"",s=b(e?.availability,{url:t,localPath:a});return{type:r(String(e?.type||"file"))||"file",...e?.name?{name:r(e.name)}:{},...t?{url:t}:{},...e?.mimeType?{mimeType:r(e.mimeType)}:{},...Number.isFinite(e?.size)&&Number(e.size)>0?{size:Number(e.size)}:{},...a?{localPath:a}:{},...e?.thumbnailPath?{thumbnailPath:r(e.thumbnailPath)}:{},...e?.hash?{hash:r(e.hash)}:{},...s?{availability:s}:{},...e?.machineId?{machineId:r(e.machineId)}:{},...e?.expiresAt?{expiresAt:r(e.expiresAt)}:{},...e?.providerError?{providerError:r(e.providerError)}:{}}}).filter(e=>e.type):[]}function b(n,e){return n==="edge-local"||n==="server-url"||n==="pending-download"||n==="metadata-only"||n==="unavailable-large"?n:e.localPath?"edge-local":e.url?"server-url":"metadata-only"}function $(n){if(!n)return new Date().toISOString();const e=new Date(n);return Number.isNaN(e.getTime())?new Date().toISOString():e.toISOString()}function o(n,e){return`${n}:${m("sha256").update(e).digest("hex").slice(0,24)}`}function x(n,e){const t=Array.isArray(e)?e.map(a=>r(a)).filter(Boolean):[];return t.length?t.some(a=>{const s=a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`@\\s*${s}(?=$|\\s|[\uFF0C\u3002\uFF01\uFF1F,.!?:\uFF1A\uFF1B;\u3001)])`).test(n)}):!1}export{I as WeChatRpaDeduper,N as normalizeWeChatRpaMessage,y as weChatRpaConversationId};