shennian 0.2.108 → 0.2.110

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 (56) hide show
  1. package/dist/publish-build-manifest.json +63 -58
  2. package/dist/src/agents/model-registry/discovery.js +2 -2
  3. package/dist/src/agents/pi-context.d.ts +1 -1
  4. package/dist/src/agents/pi-context.js +3 -3
  5. package/dist/src/channels/runtime.d.ts +2 -0
  6. package/dist/src/channels/runtime.js +1 -1
  7. package/dist/src/channels/secret-registry.d.ts +2 -0
  8. package/dist/src/channels/wechat-channel/automation-lane.d.ts +12 -0
  9. package/dist/src/channels/wechat-channel/automation-lane.js +2 -1
  10. package/dist/src/channels/wechat-channel/client.d.ts +25 -2
  11. package/dist/src/channels/wechat-channel/client.js +1 -1
  12. package/dist/src/channels/wechat-channel/helper-assets.d.ts +1 -1
  13. package/dist/src/channels/wechat-channel/helper-assets.js +1 -1
  14. package/dist/src/channels/wechat-channel/helper-client.d.ts +2 -0
  15. package/dist/src/channels/wechat-channel/helper-client.js +3 -3
  16. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
  17. package/dist/src/channels/wechat-channel/observer.d.ts +9 -1
  18. package/dist/src/channels/wechat-channel/observer.js +7 -6
  19. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +8 -0
  20. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -1
  21. package/dist/src/channels/wechat-channel/runner.js +1 -1
  22. package/dist/src/channels/wechat-channel/runtime.d.ts +1 -0
  23. package/dist/src/channels/wechat-channel/runtime.js +1 -1
  24. package/dist/src/channels/wechat-channel/scheduler.d.ts +1 -1
  25. package/dist/src/channels/wechat-channel/scheduler.js +1 -1
  26. package/dist/src/channels/wechat-rpa/product-channel.d.ts +1 -0
  27. package/dist/src/channels/wechat-rpa/product-channel.js +4 -4
  28. package/dist/src/channels/wechat-rpa.d.ts +2 -0
  29. package/dist/src/channels/wechat-rpa.js +2 -2
  30. package/dist/src/commands/helper-runtime-official-download.d.ts +80 -0
  31. package/dist/src/commands/helper-runtime-official-download.js +1 -0
  32. package/dist/src/commands/runtime.d.ts +24 -0
  33. package/dist/src/commands/runtime.js +1 -1
  34. package/dist/src/commands/wechat/command.d.ts +1 -0
  35. package/dist/src/commands/wechat/command.js +1 -1
  36. package/dist/src/commands/wechat/ipc.js +1 -1
  37. package/dist/src/commands/wechat/output.js +3 -3
  38. package/dist/src/commands/wechat.d.ts +1 -1
  39. package/dist/src/commands/wechat.js +1 -1
  40. package/dist/src/devtools/wechat-channel-action-smoke.d.ts +3 -1
  41. package/dist/src/devtools/wechat-channel-action-smoke.js +9 -9
  42. package/dist/src/index.js +2 -2
  43. package/dist/src/manager/runtime.js +10 -10
  44. package/dist/src/native-fusion/service.d.ts +0 -10
  45. package/dist/src/native-fusion/service.js +2 -2
  46. package/dist/src/session/handlers/chat.js +2 -2
  47. package/dist/src/session/handlers/session-refresh.js +1 -1
  48. package/dist/src/session/handlers/wechat-helper-runtime.d.ts +31 -0
  49. package/dist/src/session/handlers/wechat-helper-runtime.js +1 -0
  50. package/dist/src/session/manager.d.ts +4 -1
  51. package/dist/src/session/manager.js +1 -1
  52. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  53. package/node_modules/@shennian/wire/dist/methods.d.ts.map +1 -1
  54. package/package.json +1 -1
  55. package/dist/src/agents/codex-control.d.ts +0 -35
  56. package/dist/src/agents/codex-control.js +0 -2
@@ -26,6 +26,7 @@ export type WeChatChannelBindingConfig = {
26
26
  enabled: boolean;
27
27
  allowReply: boolean;
28
28
  downloadMedia: boolean;
29
+ selfTriggerMarker?: string | null;
29
30
  };
30
31
  export type WeChatChannelRuntimeConfig = {
31
32
  runtimeId: string;
@@ -1 +1 @@
1
- const a="wechat_channel",A=1,L=20,C=6e4,u=3e4,c=3e5,s="work";function N(n,e){return{kind:a,runtimeVersion:1,platform:_(e),pollIntervalMs:l(n),minPollIntervalMs:3e4,maxPollIntervalMs:3e5,recentMessageWindow:20,requiresServerDecision:!0,requiresPlatformHelper:!0,productRuntimeUsesLabFacade:!1}}function l(n){return Number.isFinite(n)?Math.min(3e5,Math.max(3e4,Number(n))):6e4}function I(n){const e=i(n.runtimeId,"runtimeId"),t=i(n.machineId,"machineId"),r=l(n.pollIntervalMs),o=_(n.platform);return{kind:a,runtimeId:e,machineId:t,foregroundPolicy:E(n.foregroundPolicy),policy:N(r,o),bindings:d(n.bindings??[])}}function E(n){return n==="polite"?"polite":s}function _(n){return n==="win32"?"win32":n==="darwin"?"darwin":process.platform==="win32"?"win32":"darwin"}function d(n){const e=new Set,t=[];for(const r of n){const o=i(r.bindingId,"bindingId");e.has(o)||(e.add(o),t.push({bindingId:o,sessionId:i(r.sessionId,"sessionId"),conversationDisplayName:i(r.conversationDisplayName,"conversationDisplayName"),enabled:r.enabled!==!1,allowReply:r.allowReply!==!1,downloadMedia:r.downloadMedia!==!1}))}return t}function i(n,e){const t=String(n||"").trim();if(!t)throw new Error(`WeChat channel ${e} is required`);return t}export{s as WECHAT_CHANNEL_DEFAULT_FOREGROUND_POLICY,C as WECHAT_CHANNEL_DEFAULT_POLL_INTERVAL_MS,c as WECHAT_CHANNEL_MAX_POLL_INTERVAL_MS,u as WECHAT_CHANNEL_MIN_POLL_INTERVAL_MS,L as WECHAT_CHANNEL_RECENT_MESSAGE_WINDOW,a as WECHAT_CHANNEL_RUNTIME_KIND,A as WECHAT_CHANNEL_RUNTIME_VERSION,I as createWeChatChannelRuntime,N as defaultWeChatChannelRuntimePolicy,E as normalizeWeChatChannelForegroundPolicy,l as normalizeWeChatChannelPollIntervalMs};
1
+ const a="wechat_channel",u=1,L=20,C=6e4,c=3e4,I=3e5,_="work";function N(n,e){return{kind:a,runtimeVersion:1,platform:s(e),pollIntervalMs:l(n),minPollIntervalMs:3e4,maxPollIntervalMs:3e5,recentMessageWindow:20,requiresServerDecision:!0,requiresPlatformHelper:!0,productRuntimeUsesLabFacade:!1}}function l(n){return Number.isFinite(n)?Math.min(3e5,Math.max(3e4,Number(n))):6e4}function m(n){const e=o(n.runtimeId,"runtimeId"),t=o(n.machineId,"machineId"),r=l(n.pollIntervalMs),i=s(n.platform);return{kind:a,runtimeId:e,machineId:t,foregroundPolicy:E(n.foregroundPolicy),policy:N(r,i),bindings:d(n.bindings??[])}}function E(n){return n==="polite"?"polite":_}function s(n){return n==="win32"?"win32":n==="darwin"?"darwin":process.platform==="win32"?"win32":"darwin"}function d(n){const e=new Set,t=[];for(const r of n){const i=o(r.bindingId,"bindingId");e.has(i)||(e.add(i),t.push({bindingId:i,sessionId:o(r.sessionId,"sessionId"),conversationDisplayName:o(r.conversationDisplayName,"conversationDisplayName"),enabled:r.enabled!==!1,allowReply:r.allowReply!==!1,downloadMedia:r.downloadMedia!==!1,selfTriggerMarker:A(r.selfTriggerMarker)}))}return t}function o(n,e){const t=String(n||"").trim();if(!t)throw new Error(`WeChat channel ${e} is required`);return t}function A(n){return String(n||"").trim()||null}export{_ as WECHAT_CHANNEL_DEFAULT_FOREGROUND_POLICY,C as WECHAT_CHANNEL_DEFAULT_POLL_INTERVAL_MS,I as WECHAT_CHANNEL_MAX_POLL_INTERVAL_MS,c as WECHAT_CHANNEL_MIN_POLL_INTERVAL_MS,L as WECHAT_CHANNEL_RECENT_MESSAGE_WINDOW,a as WECHAT_CHANNEL_RUNTIME_KIND,u as WECHAT_CHANNEL_RUNTIME_VERSION,m as createWeChatChannelRuntime,N as defaultWeChatChannelRuntimePolicy,E as normalizeWeChatChannelForegroundPolicy,l as normalizeWeChatChannelPollIntervalMs};
@@ -67,7 +67,7 @@ export declare class WeChatChannelScheduler {
67
67
  private runningTick;
68
68
  constructor(options: WeChatChannelSchedulerOptions);
69
69
  start(): Promise<void>;
70
- stop(): void;
70
+ stop(): Promise<void>;
71
71
  tick(options?: WeChatChannelSchedulerTickOptions): Promise<WeChatChannelSchedulerTickResult | void>;
72
72
  noteInterruption(bindingId: string, reason?: string, now?: Date): void;
73
73
  private reportOutboundProjections;
@@ -1 +1 @@
1
- import v from"node:path";import{loadWeChatChannelLedger as _,markWeChatChannelBindingDisabled as y,saveWeChatChannelLedger as R,updateWeChatChannelBindingLedger as O}from"./ledger.js";import{classifyWeChatOutboundEchoes as k,isWeChatOutboundTerminalDeliveryStatus as E,loadWeChatChannelOutboundLedger as W,markWeChatOutboundUserActiveTimeout as A,markWeChatOutboundWaitingUserIdle as x,saveWeChatChannelOutboundLedger as M,shouldFailWeChatOutboundForUserActiveTimeout as T}from"./outbound-ledger.js";import{clearExpiredWeChatChannelCooldown as P,isWeChatChannelCooldownActive as U,noteWeChatChannelBillingPause as L,noteWeChatChannelInterruption as B,noteWeChatChannelStableRun as D}from"./cooldown.js";import{sendQueuedWeChatOutboundRecords as j}from"./outbound-sender.js";const K=600*1e3;class sn{options;timer=null;running=!1;runningTick=null;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(t={}){for(;this.running;){if(!t.forceObserve)return;const i=this.runningTick;if(!i)return;await i}this.running=!0;let o=()=>{};this.runningTick=new Promise(i=>{o=i});try{const i=this.options.ledgerPath||S(this.options.workDir,this.options.runtime.runtimeId),r=this.options.outboundLedgerPath||V(this.options.workDir,this.options.runtime.runtimeId),d=_(i,this.options.runtime.runtimeId),c=W(r,this.options.runtime.runtimeId),h=[],g=new Date;for(const e of this.options.runtime.bindings.filter(u=>!u.enabled))d.bindings[e.bindingId]?.disabledSince||y({ledger:d,bindingId:e.bindingId});for(const e of this.options.runtime.bindings.filter(u=>u.enabled)){await this.options.api.upsertRuntime(this.options.runtime,e);const u=d.bindings[e.bindingId],p=c.records.filter(s=>s.bindingId===e.bindingId),C=q(this.options.runtime,p);if(u?.cooldown&&(u.cooldown=P(u.cooldown),U(u.cooldown))){await this.options.api.reportRunStatus(this.options.runtime,e,{status:"cooldown",reasonCode:u.cooldown.manualReviewReason||"user_interruption_cooldown"}),h.push({bindingId:e.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:u.revision});continue}const f=this.options.activityGate?await this.options.activityGate.canObserve(e,g):{ok:!0};if(f.ok===!1){const s=X({records:c.records,bindingId:e.bindingId,reasonCode:f.reasonCode,nextAttemptAt:f.nextAttemptAt,now:g,maxUserActivityWaitMs:this.options.maxUserActivityWaitMs??K});await this.reportOutboundProjections(e,[...s.waitingRecords,...s.failedRecords]),await this.options.api.reportRunStatus(this.options.runtime,e,{status:"observe_deferred_user_active",reasonCode:s.failedRecords[0]?.failureCode||f.reasonCode}),h.push({bindingId:e.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:s.failedRecords.length,waitingOutboundCount:s.waitingRecords.length,confirmedEchoCount:0,manualReviewCount:0,revision:u?.revision??0});continue}if(!t.forceObserve&&this.options.shouldSkipObserve&&N(u,p)){const s=await this.options.shouldSkipObserve(e,{ledgerBinding:u,outboundRecords:p,now:g,foregroundMode:C});if(s.skip){await this.options.api.reportRunStatus(this.options.runtime,e,{status:$(s.reasonCode),reasonCode:s.reasonCode}),h.push({bindingId:e.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:u?.revision??0});continue}}let w;try{w=await this.options.observeBinding(e,{localOutboundEchoAnchors:J(c.records,e.bindingId),foregroundMode:C})}catch(s){if(G(s)){await this.options.api.reportRunStatus(this.options.runtime,e,{status:"observe_deferred_window_unavailable",reasonCode:I(s,"wechat_window_unavailable"),lastErrorSummary:s instanceof Error?s.message:"wechat_window_unavailable"}),h.push({bindingId:e.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:u?.revision??0});continue}if(z(s)){await this.options.api.reportRunStatus(this.options.runtime,e,{status:"wechat_window_unresponsive",reasonCode:"wechat_window_unresponsive",lastErrorSummary:s instanceof Error?s.message:"wechat_window_unresponsive"}),h.push({bindingId:e.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:u?.revision??0});continue}if(F(s)){const m=u??{bindingId:e.bindingId,baselineEstablished:!1,revision:0,recent:[],pendingSendKeys:[]};m.cooldown=L({state:m.cooldown,reason:"insufficient_credits",now:g}),d.bindings[e.bindingId]=m,await this.options.api.reportRunStatus(this.options.runtime,e,{status:"billing_paused",reasonCode:"insufficient_credits",lastErrorSummary:s instanceof Error?s.message:"insufficient_credits"}),h.push({bindingId:e.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:m.revision});continue}throw s}const b=k({ledger:c,bindingId:e.bindingId,messages:w}),l=O({ledger:d,bindingId:e.bindingId,observedMessages:b.remainingMessages}),a=this.options.outboundSender?await j({ledger:c,bindingId:e.bindingId,currentLastInboundRevision:l.binding.revision,sender:this.options.outboundSender,now:g,maxUserActivityWaitMs:this.options.maxUserActivityWaitMs}):{sentRecords:[],staleRecords:[],failedRecords:[],waitingRecords:[],manualReviewRecords:[]};l.binding.cooldown=D(l.binding.cooldown),l.newMessages.length>0&&(await this.options.api.ingest(this.options.runtime,e,{idempotencyKey:`${e.bindingId}:${l.binding.revision}:${H(l.newMessages)}`,messages:l.newMessages}),await this.options.onInboundMessages?.(e,l.newMessages)),await this.reportOutboundProjections(e,[...b.confirmedRecords,...b.manualReviewRecords,...a.sentRecords,...a.staleRecords,...a.failedRecords,...a.waitingRecords,...a.manualReviewRecords]),await this.options.api.reportRunStatus(this.options.runtime,e,{status:Y(l.newMessages.length,b.confirmedRecords,b.manualReviewRecords,a.sentRecords,a.staleRecords,a.failedRecords,a.waitingRecords,a.manualReviewRecords),reasonCode:a.waitingRecords[0]?.deferReason}),h.push({bindingId:e.bindingId,observedCount:w.length,newInboundCount:l.newMessages.length,outboundSentCount:a.sentRecords.length,staleOutboundCount:a.staleRecords.length,failedOutboundCount:a.failedRecords.length,waitingOutboundCount:a.waitingRecords.length,confirmedEchoCount:b.confirmedRecords.length,manualReviewCount:b.manualReviewRecords.length+a.manualReviewRecords.length,revision:l.binding.revision})}return R(i,d),M(r,c),h}finally{o(),this.runningTick=null,this.running=!1}}noteInterruption(t,o,i=new Date){const r=this.options.ledgerPath||S(this.options.workDir,this.options.runtime.runtimeId),d=_(r,this.options.runtime.runtimeId),c=d.bindings[t]??{bindingId:t,baselineEstablished:!1,revision:0,recent:[],pendingSendKeys:[]};c.cooldown=B({state:c.cooldown,reason:o,now:i}),d.bindings[t]=c,R(r,d)}async reportOutboundProjections(t,o){if(this.options.api.reportOutboundStatus)for(const i of o)await this.options.api.reportOutboundStatus(this.options.runtime,t,{replyId:i.replyId,idempotencyKey:i.idempotencyKey,status:i.sendStatus,replyBaseRevision:i.replyBaseRevision,sentAt:i.sentAt,confirmedAt:i.confirmedAt,failureCode:i.failureCode,lastErrorSummary:i.lastErrorSummary,nextAttemptAt:i.nextAttemptAt,attemptCount:i.attemptCount,commitStage:i.commitStage,copyableContentRef:i.copyableContentRef,copyableText:Q(i)?i.text:void 0,agentVisible:E(i)?!1:void 0})}}function F(n){return!n||typeof n!="object"?!1:n.reasonCode==="insufficient_credits"?!0:n instanceof Error&&/\binsufficient_credits\b/.test(n.message)}function q(n,t){return n.foregroundPolicy==="work"||t.some(o=>o.sendStatus==="queued"||o.sendStatus==="sending"||o.sendStatus==="sent_unconfirmed")?"required":"background"}function $(n){return n==="conversation_not_visible_background"?"observe_deferred_window_unavailable":"baseline_or_no_change"}function G(n){const t=I(n);return t==="wechat_window_unavailable"||t==="wechat_window_not_found"||t==="conversation_not_visible_background"||t==="wechat_window_obstructed_background"}function I(n,t="unknown_error"){if(n&&typeof n=="object"){const r=n.reasonCode??n.errorCode;if(typeof r=="string"&&r.trim())return r.trim()}return(n instanceof Error?n.message:String(n||"")).match(/^([a-z0-9_./-]+)(?::|\b)/i)?.[1]||t}function z(n){return n instanceof Error&&/\bwechat_window_unresponsive\b/i.test(n.message)}function N(n,t){return!n?.baselineEstablished||n.disabledSince||!n.recent.length?!1:!t.some(o=>o.sendStatus==="queued"||o.sendStatus==="sending"||o.sendStatus==="sent_unconfirmed")}function Q(n){return(n.sendStatus==="failed"||n.sendStatus==="manual_review")&&!!n.text}function S(n,t){return v.join(n,"wechat-channel",`${t}.ledger.json`)}function V(n,t){return v.join(n,"wechat-channel",`${t}.outbound-ledger.json`)}function H(n){return n.at(-1)?.stableMessageKey||"empty"}function Y(n,t,o,i=[],r=[],d=[],c=[],h=[]){return o.length>0||h.length>0?"manual_review":d.length>0?"outbound_send_failed":r.length>0?"outbound_stale":c.length>0?"send_waiting_user_idle":n>0?"new_messages_ingested":i.length>0?"outbound_sent_unconfirmed":t.length>0?"outbound_echo_confirmed":"baseline_or_no_change"}function J(n,t){return n.filter(o=>o.bindingId===t&&o.sendStatus==="sent_unconfirmed").map(o=>({replyId:o.replyId,idempotencyKey:o.idempotencyKey,expectedEchoAnchor:o.expectedEchoAnchor||o.textNormalized||o.text||""}))}function X(n){const t=n.records.filter(r=>r.bindingId===n.bindingId&&r.sendStatus==="queued"),o=[],i=[];for(const r of t)x({record:r,reasonCode:n.reasonCode,nextAttemptAt:n.nextAttemptAt,now:n.now}),T({record:r,now:n.now,maxWaitMs:n.maxUserActivityWaitMs})?(A(r,n.now),o.push(r)):i.push(r);return{waitingRecords:i,failedRecords:o}}export{sn as WeChatChannelScheduler};
1
+ import v from"node:path";import{loadWeChatChannelLedger as _,markWeChatChannelBindingDisabled as k,saveWeChatChannelLedger as R,updateWeChatChannelBindingLedger as A}from"./ledger.js";import{classifyWeChatOutboundEchoes as E,isWeChatOutboundTerminalDeliveryStatus as W,loadWeChatChannelOutboundLedger as T,markWeChatOutboundUserActiveTimeout as M,markWeChatOutboundWaitingUserIdle as x,saveWeChatChannelOutboundLedger as P,shouldFailWeChatOutboundForUserActiveTimeout as U}from"./outbound-ledger.js";import{clearExpiredWeChatChannelCooldown as L,isWeChatChannelCooldownActive as B,noteWeChatChannelBillingPause as D,noteWeChatChannelInterruption as K,noteWeChatChannelStableRun as j}from"./cooldown.js";import{sendQueuedWeChatOutboundRecords as F}from"./outbound-sender.js";const $=600*1e3,q=8e3;class ln{options;timer=null;running=!1;runningTick=null;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())}async stop(){this.timer&&(clearInterval(this.timer),this.timer=null);const t=this.runningTick;t&&await en(t,q)}async tick(t={}){for(;this.running;){if(!t.forceObserve)return;const e=this.runningTick;if(!e)return;await e}this.running=!0;let i=()=>{};this.runningTick=new Promise(e=>{i=e});try{const e=this.options.ledgerPath||y(this.options.workDir,this.options.runtime.runtimeId),r=this.options.outboundLedgerPath||X(this.options.workDir,this.options.runtime.runtimeId),d=_(e,this.options.runtime.runtimeId),c=T(r,this.options.runtime.runtimeId),h=[],b=new Date;for(const o of this.options.runtime.bindings.filter(a=>!a.enabled))d.bindings[o.bindingId]?.disabledSince||k({ledger:d,bindingId:o.bindingId});for(const o of this.options.runtime.bindings.filter(a=>a.enabled)){await this.options.api.upsertRuntime(this.options.runtime,o);const a=d.bindings[o.bindingId],p=c.records.filter(s=>s.bindingId===o.bindingId),C=z(this.options.runtime,p);if(a?.cooldown&&(a.cooldown=L(a.cooldown),B(a.cooldown))){await this.options.api.reportRunStatus(this.options.runtime,o,{status:"cooldown",reasonCode:a.cooldown.manualReviewReason||"user_interruption_cooldown"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a.revision});continue}const f=this.options.activityGate?await this.options.activityGate.canObserve(o,b):{ok:!0};if(f.ok===!1){const s=sn({records:c.records,bindingId:o.bindingId,reasonCode:f.reasonCode,nextAttemptAt:f.nextAttemptAt,now:b,maxUserActivityWaitMs:this.options.maxUserActivityWaitMs??$});await this.reportOutboundProjections(o,[...s.waitingRecords,...s.failedRecords]),await this.options.api.reportRunStatus(this.options.runtime,o,{status:"observe_deferred_user_active",reasonCode:s.failedRecords[0]?.failureCode||f.reasonCode}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:s.failedRecords.length,waitingOutboundCount:s.waitingRecords.length,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}if(!t.forceObserve&&this.options.shouldSkipObserve&&V(a,p)){const s=await this.options.shouldSkipObserve(o,{ledgerBinding:a,outboundRecords:p,now:b,foregroundMode:C});if(s.skip){await this.options.api.reportRunStatus(this.options.runtime,o,{status:G(s.reasonCode),reasonCode:s.reasonCode}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}}let w;try{w=await this.options.observeBinding(o,{localOutboundEchoAnchors:on(c.records,o.bindingId),foregroundMode:C})}catch(s){if(H(s)){await this.options.api.reportRunStatus(this.options.runtime,o,{status:"observe_deferred_window_unavailable",reasonCode:I(s,"wechat_window_unavailable"),lastErrorSummary:s instanceof Error?s.message:"wechat_window_unavailable"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}if(Q(s)){await this.options.api.reportRunStatus(this.options.runtime,o,{status:"wechat_window_unresponsive",reasonCode:"wechat_window_unresponsive",lastErrorSummary:s instanceof Error?s.message:"wechat_window_unresponsive"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}if(N(s)){const m=a??{bindingId:o.bindingId,baselineEstablished:!1,revision:0,recent:[],pendingSendKeys:[]};m.cooldown=D({state:m.cooldown,reason:"insufficient_credits",now:b}),d.bindings[o.bindingId]=m,await this.options.api.reportRunStatus(this.options.runtime,o,{status:"billing_paused",reasonCode:"insufficient_credits",lastErrorSummary:s instanceof Error?s.message:"insufficient_credits"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:m.revision});continue}throw s}const g=E({ledger:c,bindingId:o.bindingId,messages:w}),O=nn(o,g.remainingMessages),l=A({ledger:d,bindingId:o.bindingId,observedMessages:O}),u=this.options.outboundSender?await F({ledger:c,bindingId:o.bindingId,currentLastInboundRevision:l.binding.revision,sender:this.options.outboundSender,now:b,maxUserActivityWaitMs:this.options.maxUserActivityWaitMs}):{sentRecords:[],staleRecords:[],failedRecords:[],waitingRecords:[],manualReviewRecords:[]};l.binding.cooldown=j(l.binding.cooldown),l.newMessages.length>0&&(await this.options.api.ingest(this.options.runtime,o,{idempotencyKey:`${o.bindingId}:${l.binding.revision}:${Z(l.newMessages)}`,messages:l.newMessages}),await this.options.onInboundMessages?.(o,l.newMessages)),await this.reportOutboundProjections(o,[...g.confirmedRecords,...g.manualReviewRecords,...u.sentRecords,...u.staleRecords,...u.failedRecords,...u.waitingRecords,...u.manualReviewRecords]),await this.options.api.reportRunStatus(this.options.runtime,o,{status:tn(l.newMessages.length,g.confirmedRecords,g.manualReviewRecords,u.sentRecords,u.staleRecords,u.failedRecords,u.waitingRecords,u.manualReviewRecords),reasonCode:u.waitingRecords[0]?.deferReason}),h.push({bindingId:o.bindingId,observedCount:w.length,newInboundCount:l.newMessages.length,outboundSentCount:u.sentRecords.length,staleOutboundCount:u.staleRecords.length,failedOutboundCount:u.failedRecords.length,waitingOutboundCount:u.waitingRecords.length,confirmedEchoCount:g.confirmedRecords.length,manualReviewCount:g.manualReviewRecords.length+u.manualReviewRecords.length,revision:l.binding.revision})}return R(e,d),P(r,c),h}finally{i(),this.runningTick=null,this.running=!1}}noteInterruption(t,i,e=new Date){const r=this.options.ledgerPath||y(this.options.workDir,this.options.runtime.runtimeId),d=_(r,this.options.runtime.runtimeId),c=d.bindings[t]??{bindingId:t,baselineEstablished:!1,revision:0,recent:[],pendingSendKeys:[]};c.cooldown=K({state:c.cooldown,reason:i,now:e}),d.bindings[t]=c,R(r,d)}async reportOutboundProjections(t,i){if(this.options.api.reportOutboundStatus)for(const e of i)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,nextAttemptAt:e.nextAttemptAt,attemptCount:e.attemptCount,commitStage:e.commitStage,copyableContentRef:e.copyableContentRef,copyableText:Y(e)?e.text:void 0,agentVisible:W(e)?!1:void 0})}}function N(n){return!n||typeof n!="object"?!1:n.reasonCode==="insufficient_credits"?!0:n instanceof Error&&/\binsufficient_credits\b/.test(n.message)}function z(n,t){return n.foregroundPolicy==="work"||t.some(i=>i.sendStatus==="queued"||i.sendStatus==="sending"||i.sendStatus==="sent_unconfirmed")?"required":"background"}function G(n){return n==="conversation_not_visible_background"?"observe_deferred_window_unavailable":"baseline_or_no_change"}function H(n){const t=I(n);return t==="wechat_window_unavailable"||t==="wechat_window_not_found"||t==="conversation_not_visible_background"||t==="wechat_window_obstructed_background"}function I(n,t="unknown_error"){if(n&&typeof n=="object"){const r=n.reasonCode??n.errorCode;if(typeof r=="string"&&r.trim())return r.trim()}return(n instanceof Error?n.message:String(n||"")).match(/^([a-z0-9_./-]+)(?::|\b)/i)?.[1]||t}function Q(n){return n instanceof Error&&/\bwechat_window_unresponsive\b/i.test(n.message)}function V(n,t){return!n?.baselineEstablished||n.disabledSince||!n.recent.length?!1:!t.some(i=>i.sendStatus==="queued"||i.sendStatus==="sending"||i.sendStatus==="sent_unconfirmed")}function Y(n){return(n.sendStatus==="failed"||n.sendStatus==="manual_review")&&!!n.text}function J(n){return!!n&&typeof n=="object"&&!Array.isArray(n)}function y(n,t){return v.join(n,"wechat-channel",`${t}.ledger.json`)}function X(n,t){return v.join(n,"wechat-channel",`${t}.outbound-ledger.json`)}function Z(n){return n.at(-1)?.stableMessageKey||"empty"}function nn(n,t){const i=S(n.selfTriggerMarker);return i?t.map(e=>e.senderRole!=="self"||!S(e.anchorText||e.normalizedText||e.textExcerpt||"").includes(i)?e:{...e,stableMessageKey:`self-trigger:${e.stableMessageKey}`,senderRole:"contact",anchorMetadata:{...J(e.anchorMetadata)?e.anchorMetadata:{},selfTrigger:!0,originalSenderRole:"self"}}):t}function S(n){return typeof n=="string"?n.replace(/\s+/g," ").trim():""}async function en(n,t){let i=null;await Promise.race([n.then(()=>{},()=>{}),new Promise(e=>{i=setTimeout(e,t),i.unref?.()})]),i&&clearTimeout(i)}function tn(n,t,i,e=[],r=[],d=[],c=[],h=[]){return i.length>0||h.length>0?"manual_review":d.length>0?"outbound_send_failed":r.length>0?"outbound_stale":c.length>0?"send_waiting_user_idle":n>0?"new_messages_ingested":e.length>0?"outbound_sent_unconfirmed":t.length>0?"outbound_echo_confirmed":"baseline_or_no_change"}function on(n,t){return n.filter(i=>i.bindingId===t&&i.sendStatus==="sent_unconfirmed").map(i=>({replyId:i.replyId,idempotencyKey:i.idempotencyKey,expectedEchoAnchor:i.expectedEchoAnchor||i.textNormalized||i.text||""}))}function sn(n){const t=n.records.filter(r=>r.bindingId===n.bindingId&&r.sendStatus==="queued"),i=[],e=[];for(const r of t)x({record:r,reasonCode:n.reasonCode,nextAttemptAt:n.nextAttemptAt,now:n.now}),U({record:r,now:n.now,maxWaitMs:n.maxUserActivityWaitMs})?(M(r,n.now),i.push(r)):e.push(r);return{waitingRecords:e,failedRecords:i}}export{ln as WeChatChannelScheduler};
@@ -14,6 +14,7 @@ export type WeChatRpaProductSourceSecret = {
14
14
  forceForeground?: boolean;
15
15
  downloadAttachments?: boolean;
16
16
  canReply?: boolean;
17
+ selfTriggerMarker?: string;
17
18
  };
18
19
  export type WeChatChannelProductRunnerLike = {
19
20
  start(): Promise<void>;
@@ -1,9 +1,9 @@
1
- import w from"node:crypto";import m from"node:fs";import f from"node:path";import{loadConfig as W}from"../../config/index.js";import{createWeChatChannelProductRunner as O}from"../wechat-channel/runner.js";import{createWeChatChannelApiClient as k}from"../wechat-channel/client.js";import{createWeChatChannelRuntime as $}from"../wechat-channel/runtime.js";import{cancelWeChatOutboundRecord as D,enqueueWeChatOutboundReply as K,loadWeChatChannelOutboundLedger as h,saveWeChatChannelOutboundLedger as I}from"../wechat-channel/outbound-ledger.js";import{loadWeChatChannelLedger as S}from"../wechat-channel/ledger.js";const T="wechat-channel";function se(e){const t=$({runtimeId:e.config.id,machineId:J(),pollIntervalMs:e.secret.pollIntervalMs,foregroundPolicy:j(e.secret.forceForeground),bindings:H(e.config,e.secret)}),n=x(e.config.workDir,t.runtimeId),a=u(e.config.workDir,t.runtimeId),r=L(e.config.workDir,t.runtimeId),o=[];m.mkdirSync(f.dirname(r),{recursive:!0}),m.writeFileSync(r,"");const s=e.createRunner??O,c=z(),d=s({runtime:t,workDir:e.config.workDir,api:c,ledgerPath:n,outboundLedgerPath:a,helperRequestLogger:_(r),onInboundMessages:(C,A)=>{const M=t.bindings.find(p=>p.bindingId===C.bindingId)??C;for(const p of A){const y=R(e.config,M,p);if(!y)continue;const N=e.onMessage?.({...y,managerSessionId:e.config.managerSessionId})??y;o.push(N)}}});return{runtime:t,runner:d,emitted:o,ledgerPath:n,outboundLedgerPath:a,helperTracePath:r}}function z(){let e=null;const t=()=>(e??=k(),e);return{observe:(...n)=>t().observe(...n),structureWindow:(...n)=>t().structureWindow(...n),embedVisual:(...n)=>t().embedVisual(...n),classifyWindow:(...n)=>t().classifyWindow(...n),upsertRuntime:async(...n)=>(await Promise.resolve().then(()=>t().upsertRuntime(...n)).catch(()=>null),{ok:!0}),ingest:async(...n)=>(await Promise.resolve().then(()=>t().ingest(...n)).catch(()=>null),{ok:!0}),reportRunStatus:async(...n)=>(await Promise.resolve().then(()=>t().reportRunStatus(...n)).catch(()=>null),{ok:!0}),reportOutboundStatus:async(...n)=>(await Promise.resolve().then(()=>t().reportOutboundStatus(...n)).catch(()=>null),{ok:!0})}}function j(e){return e===!1?"polite":"work"}function ce(e){const t=e.config.id,n=P(e.config.id,e.conversationName),a=S(x(e.config.workDir,t),t),r=u(e.config.workDir,t),o=h(r,t),s=e.reply.attachment?[U(e.reply.attachment)]:[],c=K({ledger:o,replyId:e.reply.messageId||e.reply.idempotencyKey||l("wechat-channel-reply",`${Date.now()}:${e.reply.text}`),idempotencyKey:e.reply.idempotencyKey||l("wechat-channel-send",`${e.config.id}
1
+ import w from"node:crypto";import m from"node:fs";import f from"node:path";import{loadConfig as W}from"../../config/index.js";import{createWeChatChannelProductRunner as O}from"../wechat-channel/runner.js";import{createWeChatChannelApiClient as k}from"../wechat-channel/client.js";import{createWeChatChannelRuntime as $}from"../wechat-channel/runtime.js";import{cancelWeChatOutboundRecord as D,enqueueWeChatOutboundReply as T,loadWeChatChannelOutboundLedger as h,saveWeChatChannelOutboundLedger as I}from"../wechat-channel/outbound-ledger.js";import{loadWeChatChannelLedger as S}from"../wechat-channel/ledger.js";const K="wechat-channel";function se(e){const t=$({runtimeId:e.config.id,machineId:J(),pollIntervalMs:e.secret.pollIntervalMs,foregroundPolicy:j(e.secret.forceForeground),bindings:H(e.config,e.secret)}),n=x(e.config.workDir,t.runtimeId),a=u(e.config.workDir,t.runtimeId),r=L(e.config.workDir,t.runtimeId),o=[];m.mkdirSync(f.dirname(r),{recursive:!0}),m.writeFileSync(r,"");const s=e.createRunner??O,c=z(),d=s({runtime:t,workDir:e.config.workDir,api:c,ledgerPath:n,outboundLedgerPath:a,helperRequestLogger:_(r),onInboundMessages:(C,A)=>{const M=t.bindings.find(p=>p.bindingId===C.bindingId)??C;for(const p of A){const y=R(e.config,M,p);if(!y)continue;const N=e.onMessage?.({...y,managerSessionId:e.config.managerSessionId})??y;o.push(N)}}});return{runtime:t,runner:d,emitted:o,ledgerPath:n,outboundLedgerPath:a,helperTracePath:r}}function z(){let e=null;const t=()=>(e??=k(),e);return{observe:(...n)=>t().observe(...n),structureWindow:(...n)=>t().structureWindow(...n),embedVisual:(...n)=>t().embedVisual(...n),classifyWindow:(...n)=>t().classifyWindow(...n),upsertRuntime:async(...n)=>(await Promise.resolve().then(()=>t().upsertRuntime(...n)).catch(()=>null),{ok:!0}),ingest:async(...n)=>(await Promise.resolve().then(()=>t().ingest(...n)).catch(()=>null),{ok:!0}),reportRunStatus:async(...n)=>(await Promise.resolve().then(()=>t().reportRunStatus(...n)).catch(()=>null),{ok:!0}),reportOutboundStatus:async(...n)=>(await Promise.resolve().then(()=>t().reportOutboundStatus(...n)).catch(()=>null),{ok:!0})}}function j(e){return e===!1?"polite":"work"}function ce(e){const t=e.config.id,n=P(e.config.id,e.conversationName),a=S(x(e.config.workDir,t),t),r=u(e.config.workDir,t),o=h(r,t),s=e.reply.attachment?[U(e.reply.attachment)]:[],c=T({ledger:o,replyId:e.reply.messageId||e.reply.idempotencyKey||l("wechat-channel-reply",`${Date.now()}:${e.reply.text}`),idempotencyKey:e.reply.idempotencyKey||l("wechat-channel-send",`${e.config.id}
2
2
  ${e.reply.conversationId}
3
3
  ${e.reply.text}
4
4
  ${s.join(`
5
- `)}`),bindingId:n,runtimeId:t,sessionId:e.config.sessionId||e.config.managerSessionId,conversationName:e.conversationName,replyBaseRevision:a.bindings[n]?.revision??0,text:e.reply.text,attachmentLocalRefs:s});return I(r,o),c}function le(e){return h(u(e.config.workDir,e.config.id),e.config.id).records.filter(n=>["queued","sending","sent_unconfirmed"].includes(n.sendStatus)).length}function de(e){return h(u(e.config.workDir,e.config.id),e.config.id).records.filter(n=>["queued","sending","sent_unconfirmed"].includes(n.sendStatus)).slice(0,100).map(n=>({replyId:n.replyId,idempotencyKey:n.idempotencyKey,status:n.sendStatus,conversationName:n.conversationName,reasonCode:n.deferReason||n.failureCode||null,nextAttemptAt:n.nextAttemptAt??null,queuedAt:n.queuedAt??n.createdAt,lastAttemptAt:n.lastAttemptAt??null,attemptCount:n.attemptCount??0,commitStage:n.commitStage??null}))}function ue(e){const t=Q(e.limit,20,1,100),n=S(e.product.ledgerPath,e.product.runtime.runtimeId),a=[];for(const r of e.product.runtime.bindings){const o=n.bindings[r.bindingId]?.recent??[];for(const s of o.slice(-t)){const c=R(e.config,r,s);c&&a.push(c)}}return a}function me(e){const t=u(e.config.workDir,e.config.id),n=h(t,e.config.id),a=String(e.idempotencyKey??"").trim(),r=String(e.replyId??"").trim();if(!a&&!r)return{cancelled:!1};const o=n.records.find(c=>a&&c.idempotencyKey===a||r&&c.replyId===r);if(!o)return{cancelled:!1};const s=o.sendStatus;return D(o,e.reason||"user_cancelled"),o.sendStatus!==s?(I(t,n),{cancelled:!0,status:o.sendStatus,record:o}):{cancelled:!1,status:o.sendStatus,record:o}}function fe(e){return e===T}function E(e){return l("wechat-conversation",i(e))}function P(e,t){return l("wechat-channel-binding",`${e}
5
+ `)}`),bindingId:n,runtimeId:t,sessionId:e.config.sessionId||e.config.managerSessionId,conversationName:e.conversationName,replyBaseRevision:a.bindings[n]?.revision??0,text:e.reply.text,attachmentLocalRefs:s});return I(r,o),c}function le(e){return h(u(e.config.workDir,e.config.id),e.config.id).records.filter(n=>["queued","sending","sent_unconfirmed"].includes(n.sendStatus)).length}function de(e){return h(u(e.config.workDir,e.config.id),e.config.id).records.filter(n=>["queued","sending","sent_unconfirmed"].includes(n.sendStatus)).slice(0,100).map(n=>({replyId:n.replyId,idempotencyKey:n.idempotencyKey,status:n.sendStatus,conversationName:n.conversationName,reasonCode:n.deferReason||n.failureCode||null,nextAttemptAt:n.nextAttemptAt??null,queuedAt:n.queuedAt??n.createdAt,lastAttemptAt:n.lastAttemptAt??null,attemptCount:n.attemptCount??0,commitStage:n.commitStage??null}))}function ue(e){const t=Q(e.limit,20,1,100),n=S(e.product.ledgerPath,e.product.runtime.runtimeId),a=[];for(const r of e.product.runtime.bindings){const o=n.bindings[r.bindingId]?.recent??[];for(const s of o.slice(-t)){const c=R(e.config,r,s);c&&a.push(c)}}return a}function me(e){const t=u(e.config.workDir,e.config.id),n=h(t,e.config.id),a=String(e.idempotencyKey??"").trim(),r=String(e.replyId??"").trim();if(!a&&!r)return{cancelled:!1};const o=n.records.find(c=>a&&c.idempotencyKey===a||r&&c.replyId===r);if(!o)return{cancelled:!1};const s=o.sendStatus;return D(o,e.reason||"user_cancelled"),o.sendStatus!==s?(I(t,n),{cancelled:!0,status:o.sendStatus,record:o}):{cancelled:!1,status:o.sendStatus,record:o}}function fe(e){return e===K}function E(e){return l("wechat-conversation",i(e))}function P(e,t){return l("wechat-channel-binding",`${e}
6
6
  ${i(t)}`)}function x(e,t){return f.join(e,"wechat-channel",`${b(t)}.ledger.json`)}function u(e,t){return f.join(e,"wechat-channel",`${b(t)}.outbound-ledger.json`)}function L(e,t){return f.join(e,"wechat-channel",`${b(t)}.helper-trace.jsonl`)}function b(e){return String(e||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"wechat-channel-runtime"}function _(e){return t=>{m.appendFileSync(e,`${JSON.stringify(F(t))}
7
- `)}}function F(e){const t={phase:e.phase,at:e.at,id:e.id,command:e.command};return e.traceId&&(t.traceId=e.traceId),e.timeoutMs!=null&&(t.timeoutMs=e.timeoutMs),e.durationMs!=null&&(t.durationMs=e.durationMs),e.params!==void 0&&(t.params=g(e.params)),e.ok!==void 0&&(t.ok=e.ok),e.errorCode&&(t.errorCode=e.errorCode),e.errorSummary&&(t.errorSummary=e.errorSummary),e.latencyMs!=null&&(t.latencyMs=e.latencyMs),e.result!==void 0&&(t.result=g(e.result)),t}function g(e,t="",n=0){if(e==null||typeof e=="number"||typeof e=="boolean")return e;if(typeof e=="string")return e.length>240||/(base64|bytes|data|image|screenshot|png|jpeg|jpg|buffer)/i.test(t)&&e.length>48?{type:"string",length:e.length,sha256:w.createHash("sha256").update(e).digest("hex").slice(0,16),preview:e.slice(0,120)}:e;if(Array.isArray(e)){const o=e.slice(0,10).map(s=>g(s,t,n+1));return e.length>o.length?{type:"array",length:e.length,items:o}:o}if(typeof e!="object")return String(e);if(n>=5)return{type:"object",truncated:!0};const a=e,r={};for(const[o,s]of Object.entries(a))r[o]=g(s,o,n+1);return r}function H(e,t){return G(t).map(n=>({bindingId:P(e.id,n),sessionId:e.sessionId||e.managerSessionId,conversationDisplayName:n,enabled:!0,allowReply:t.canReply!==!1,downloadMedia:t.downloadAttachments!==!1}))}function R(e,t,n){const a=i(n.normalizedText||n.anchorText||n.textExcerpt||""),r=q(n);if(!a&&r.length===0)return null;const o=i(n.senderName||"");return{type:"external.message",channelId:e.id,channelType:"wechat-rpa",conversationId:E(t.conversationDisplayName),conversationName:t.conversationDisplayName,messageId:l("wechat-message",`${t.bindingId}
7
+ `)}}function F(e){const t={phase:e.phase,at:e.at,id:e.id,command:e.command};return e.traceId&&(t.traceId=e.traceId),e.timeoutMs!=null&&(t.timeoutMs=e.timeoutMs),e.durationMs!=null&&(t.durationMs=e.durationMs),e.params!==void 0&&(t.params=g(e.params)),e.ok!==void 0&&(t.ok=e.ok),e.errorCode&&(t.errorCode=e.errorCode),e.errorSummary&&(t.errorSummary=e.errorSummary),e.latencyMs!=null&&(t.latencyMs=e.latencyMs),e.result!==void 0&&(t.result=g(e.result)),t}function g(e,t="",n=0){if(e==null||typeof e=="number"||typeof e=="boolean")return e;if(typeof e=="string")return e.length>240||/(base64|bytes|data|image|screenshot|png|jpeg|jpg|buffer)/i.test(t)&&e.length>48?{type:"string",length:e.length,sha256:w.createHash("sha256").update(e).digest("hex").slice(0,16),preview:e.slice(0,120)}:e;if(Array.isArray(e)){const o=e.slice(0,10).map(s=>g(s,t,n+1));return e.length>o.length?{type:"array",length:e.length,items:o}:o}if(typeof e!="object")return String(e);if(n>=5)return{type:"object",truncated:!0};const a=e,r={};for(const[o,s]of Object.entries(a))r[o]=g(s,o,n+1);return r}function H(e,t){return G(t).map(n=>({bindingId:P(e.id,n),sessionId:e.sessionId||e.managerSessionId,conversationDisplayName:n,enabled:!0,allowReply:t.canReply!==!1,downloadMedia:t.downloadAttachments!==!1,selfTriggerMarker:i(t.selfTriggerMarker||"")||null}))}function R(e,t,n){const a=i(n.normalizedText||n.anchorText||n.textExcerpt||""),r=q(n);if(!a&&r.length===0)return null;const o=i(n.senderName||"");return{type:"external.message",channelId:e.id,channelType:"wechat-rpa",conversationId:E(t.conversationDisplayName),conversationName:t.conversationDisplayName,messageId:l("wechat-message",`${t.bindingId}
8
8
  ${n.stableMessageKey}`),sender:{id:l("wechat-sender",`${t.bindingId}
9
- ${o||n.senderRole}`),name:o||null},text:a,attachments:r,receivedAt:Z(n.observedAt),isMentioned:!1,replyTarget:"",rawRef:n.stableMessageKey}}function q(e){const t=e.mediaMetadata;return B(t).map(a=>{const r=a,o=typeof r.localPath=="string"?i(r.localPath):"",s=typeof r.thumbnailPath=="string"?i(r.thumbnailPath):"",c=typeof r.url=="string"?i(r.url):"",d=Number(r.size??r.sizeBytes??0);return{type:i(String(r.type||r.kind||e.kind||"file"))||"file",...r.name||r.fileName?{name:i(String(r.name||r.fileName))}:{},...c?{url:c}:{},...typeof r.mimeType=="string"?{mimeType:i(r.mimeType)}:{},...typeof r.extension=="string"?{extension:i(r.extension)}:{},...Number.isFinite(d)&&d>0?{size:d}:{},...o?{localPath:o}:{},...s?{thumbnailPath:s}:{},...typeof r.hash=="string"||typeof r.sha256=="string"?{hash:i(String(r.hash||r.sha256))}:{},availability:V(r.availability,{localPath:o,url:c}),...typeof r.providerError=="string"?{providerError:i(r.providerError)}:{},...typeof r.sourceAction=="string"?{sourceAction:i(r.sourceAction)}:{},...v(r.materializationKind)?{materializationKind:r.materializationKind}:{},...typeof r.isOriginal=="boolean"?{isOriginal:r.isOriginal}:{},...typeof r.mimeKindMatches=="boolean"?{mimeKindMatches:r.mimeKindMatches}:{}}})}function B(e){if(!e||typeof e!="object")return[];const t=e;return Array.isArray(t.attachments)?t.attachments:t.attachment&&typeof t.attachment=="object"?[t.attachment]:t.localPath||t.url||t.name||t.fileName||t.availability?[t]:[]}function V(e,t){return e==="edge-local"||e==="server-url"||e==="pending-download"||e==="metadata-only"||e==="unavailable-large"?e:t.localPath?"edge-local":t.url?"server-url":"metadata-only"}function v(e){return e==="original-file"||e==="clipboard-image"||e==="preview-crop"||e==="metadata"}function U(e){const t=i(e.localPath||"");if(!t)throw new Error("WeChat channel product send requires a local attachment path on this machine");if(!m.statSync(t).isFile())throw new Error(`WeChat channel product attachment is not a file: ${t}`);return t}function G(e){return Array.isArray(e.groups)?e.groups.map(t=>i(t?.name||"")).filter(Boolean):[]}function J(){return process.env.SHENNIAN_MACHINE_ID?.trim()||W().machineId||"local"}function Z(e){if(!e)return new Date().toISOString();const t=new Date(e);return Number.isNaN(t.getTime())?new Date().toISOString():t.toISOString()}function i(e){return e.replace(/\s+/g," ").trim()}function Q(e,t,n,a){const r=Number(e);return Number.isFinite(r)?Math.max(n,Math.min(a,Math.floor(r))):t}function l(e,t){return`${e}:${w.createHash("sha256").update(t).digest("hex").slice(0,24)}`}export{T as WECHAT_RPA_PRODUCT_SOURCE,me as cancelWeChatRpaProductOutboundReply,le as countPendingWeChatRpaProductOutbound,se as createWeChatRpaProductSourceConnection,ce as enqueueWeChatRpaProductOutboundReply,fe as isWeChatRpaProductSource,de as listPendingWeChatRpaProductOutbound,ue as listWeChatRpaProductRecentMessages,R as observedMessageToExternalEvent,b as safeWeChatChannelRuntimePathSegment,P as weChatChannelBindingId,E as weChatChannelConversationId,L as weChatChannelHelperTracePath,x as weChatChannelLedgerPath,u as weChatChannelOutboundLedgerPath};
9
+ ${o||n.senderRole}`),name:o||null},text:a,attachments:r,receivedAt:Z(n.observedAt),isMentioned:!1,replyTarget:"",rawRef:n.stableMessageKey}}function q(e){const t=e.mediaMetadata;return B(t).map(a=>{const r=a,o=typeof r.localPath=="string"?i(r.localPath):"",s=typeof r.thumbnailPath=="string"?i(r.thumbnailPath):"",c=typeof r.url=="string"?i(r.url):"",d=Number(r.size??r.sizeBytes??0);return{type:i(String(r.type||r.kind||e.kind||"file"))||"file",...r.name||r.fileName?{name:i(String(r.name||r.fileName))}:{},...c?{url:c}:{},...typeof r.mimeType=="string"?{mimeType:i(r.mimeType)}:{},...typeof r.extension=="string"?{extension:i(r.extension)}:{},...Number.isFinite(d)&&d>0?{size:d}:{},...o?{localPath:o}:{},...s?{thumbnailPath:s}:{},...typeof r.hash=="string"||typeof r.sha256=="string"?{hash:i(String(r.hash||r.sha256))}:{},availability:V(r.availability,{localPath:o,url:c}),...typeof r.providerError=="string"?{providerError:i(r.providerError)}:{},...typeof r.sourceAction=="string"?{sourceAction:i(r.sourceAction)}:{},...v(r.materializationKind)?{materializationKind:r.materializationKind}:{},...typeof r.isOriginal=="boolean"?{isOriginal:r.isOriginal}:{},...typeof r.mimeKindMatches=="boolean"?{mimeKindMatches:r.mimeKindMatches}:{}}})}function B(e){if(!e||typeof e!="object")return[];const t=e;return Array.isArray(t.attachments)?t.attachments:t.attachment&&typeof t.attachment=="object"?[t.attachment]:t.localPath||t.url||t.name||t.fileName||t.availability?[t]:[]}function V(e,t){return e==="edge-local"||e==="server-url"||e==="pending-download"||e==="metadata-only"||e==="unavailable-large"?e:t.localPath?"edge-local":t.url?"server-url":"metadata-only"}function v(e){return e==="original-file"||e==="clipboard-image"||e==="preview-crop"||e==="metadata"}function U(e){const t=i(e.localPath||"");if(!t)throw new Error("WeChat channel product send requires a local attachment path on this machine");if(!m.statSync(t).isFile())throw new Error(`WeChat channel product attachment is not a file: ${t}`);return t}function G(e){return Array.isArray(e.groups)?e.groups.map(t=>i(t?.name||"")).filter(Boolean):[]}function J(){return process.env.SHENNIAN_MACHINE_ID?.trim()||W().machineId||"local"}function Z(e){if(!e)return new Date().toISOString();const t=new Date(e);return Number.isNaN(t.getTime())?new Date().toISOString():t.toISOString()}function i(e){return e.replace(/\s+/g," ").trim()}function Q(e,t,n,a){const r=Number(e);return Number.isFinite(r)?Math.max(n,Math.min(a,Math.floor(r))):t}function l(e,t){return`${e}:${w.createHash("sha256").update(t).digest("hex").slice(0,24)}`}export{K as WECHAT_RPA_PRODUCT_SOURCE,me as cancelWeChatRpaProductOutboundReply,le as countPendingWeChatRpaProductOutbound,se as createWeChatRpaProductSourceConnection,ce as enqueueWeChatRpaProductOutboundReply,fe as isWeChatRpaProductSource,de as listPendingWeChatRpaProductOutbound,ue as listWeChatRpaProductRecentMessages,R as observedMessageToExternalEvent,b as safeWeChatChannelRuntimePathSegment,P as weChatChannelBindingId,E as weChatChannelConversationId,L as weChatChannelHelperTracePath,x as weChatChannelLedgerPath,u as weChatChannelOutboundLedgerPath};
@@ -17,6 +17,8 @@ type WeChatRpaSecret = {
17
17
  idleSeconds?: number;
18
18
  recentLimit?: number;
19
19
  selfNickname?: string;
20
+ selfTriggerMarker?: string;
21
+ deferInitialPoll?: boolean;
20
22
  flowScriptPath?: string;
21
23
  canReply?: boolean;
22
24
  systemPrompt?: string;
@@ -1,8 +1,8 @@
1
- import I from"node:crypto";import f from"node:fs";import m from"node:path";import{fileURLToPath as Z,pathToFileURL as ee}from"node:url";import{ChannelSecretRegistry as te}from"./secret-registry.js";import{resolveShennianPath as re}from"../config/index.js";import{probeMacWeChat as D,observedMessageFromProbe as ne}from"./wechat-rpa/macos.js";import{runMacWeChatRpaFlow as F}from"./wechat-rpa/macos-flow.js";import{normalizeWeChatRpaMessage as ae,WeChatRpaDeduper as K,weChatRpaConversationId as x}from"./wechat-rpa/normalizer.js";import{cancelWeChatRpaProductOutboundReply as se,countPendingWeChatRpaProductOutbound as ie,createWeChatRpaProductSourceConnection as oe,enqueueWeChatRpaProductOutboundReply as ue,isWeChatRpaProductSource as R,listWeChatRpaProductRecentMessages as ce,listPendingWeChatRpaProductOutbound as de,weChatChannelConversationId as N}from"./wechat-rpa/product-channel.js";import{createWeChatChannelApiClient as le}from"./wechat-channel/client.js";const pe=5e3,A=5,me=8,S=Number(process.env.SHENNIAN_WECHAT_RPA_OUTBOUND_ATTACHMENT_MAX_BYTES||20*1024*1024),M=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_THRESHOLD||3),E=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_MS||300*1e3),he=10;function fe(t){return P(t,A,1,50)>=me?"scroll-read":"read-latest"}class at{onMessage;options;type="wechat-rpa";secrets=new te;connections=new Map;constructor(e,r={}){this.onMessage=e,this.options=r}async connect(e){if(!e.enabled)return;const r=this.readSecret(e),n=this.ensureConnection(e);if(n.stopped=!1,n.config=e,T(n,e),O(n,e),this.seedConfiguredConversations(n,r),R(r.source)){if(await this.ensureProductConnection(n,r),n.timer)return;await this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))),n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))).catch(()=>{})},n.product?.runtime.policy.pollIntervalMs??B(r.pollIntervalMs)),n.timer.unref();return}n.timer||(await this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))),n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))).catch(()=>{})},B(r.pollIntervalMs)),n.timer.unref())}async disconnect(e){const r=this.connections.get(e.id);r&&(r.stopped=!0,r.timer&&clearInterval(r.timer),r.product&&await r.product.runner.stop().catch(()=>{}),this.connections.delete(e.id))}async syncNow(e){if(!e.enabled)throw new Error("WeChat RPA channel is disabled");const r=this.readSecret(e),n=this.ensureConnection(e);return n.stopped=!1,n.config=e,T(n,e),O(n,e),this.seedConfiguredConversations(n,r),R(r.source)?(await this.ensureProductConnection(n,r),this.enqueueOperation(n,async()=>{const a=this.readSecret(n.config),o=await this.pollProductOnce(n,a,{forceObserve:!0}),i=await this.ensureProductConnection(n,a),u=ce({config:n.config,product:i,limit:a.recentLimit});return ze(u,o)})):this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config)))}async cancelOutbound(e,r){const n=this.readSecret(e);return R(n.source)?se({config:e,idempotencyKey:r.idempotencyKey,replyId:r.replyId,reason:r.reason}):{cancelled:!1,status:"unsupported_source"}}async send(e,r){const n=this.readSecret(e);if(n.canReply===!1)throw new Error("WeChat RPA channel does not allow replies");const a=this.ensureConnection(e);if(a.config=e,T(a,e),O(a,e),this.seedConfiguredConversations(a,n),R(n.source)){const c=this.resolveConversationName(e,n,r.conversationId);if(!c)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const h=r.text.trim();if(!h&&!r.attachment)throw new Error("Reply text or attachment is required");return await this.ensureProductConnection(a,n),await this.enqueueOperation(a,async()=>{const y=this.readSecret(a.config);ue({config:a.config,secret:y,reply:{...r,text:h},conversationName:c}),a.runtimeState="retrying",g(a,{status:"sent",runId:a.lastRunId??null,summary:`queued ${c}`})}),this.enqueueOperation(a,()=>this.pollProductOnce(a,this.readSecret(a.config))).catch(y=>{a.lastError=y instanceof Error?y.message:String(y)}),{status:"queued"}}if(!Ne(n.source))throw new Error("WeChat RPA reply requires source=macos-flow, windows-visual-flow, or wechat-rpa-lab");const o=this.resolveConversationName(e,n,r.conversationId);if(!o)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const i=Me(e,r);if(a.completedPendingReplyKeys.has(i))return{status:"sent"};const u=a.manualReviewReplies.get(i);if(u)return{status:"manual-review",reason:u.reason};const s=r.attachment?await Ke(e.workDir,r.attachment):void 0,l=r.text.trim();if(!l&&!s)throw new Error("Reply text or attachment is required");if(Q(a,n)){const c="WeChat RPA is cooling down after repeated user activity";return a.runtimeState="cooldown",W(a,{key:i,conversationId:r.conversationId,conversationName:o,text:l,attachmentPath:s,reason:c}),{status:"queued",reason:c}}let d;try{a.runtimeState="syncing",d=await this.enqueueOperation(a,()=>(a.lastRunAt=new Date().toISOString(),G(e,n,o,l,s,i)))}catch(c){throw a.lastError=c instanceof Error?c.message:String(c),g(a,{status:k(a.lastError),runId:a.lastRunId??null,summary:a.lastError}),c}L(a,d);const p=V(d,{text:l,attachmentPath:s});if(p.status==="queued"){const c=p.reason;return W(a,{key:i,conversationId:r.conversationId,conversationName:o,text:p.text,attachmentPath:p.attachmentPath,reason:c,skipText:p.skipText}),a.lastError=c,{status:"queued",reason:c}}if(d.interrupted){const c=d.error||"WeChat RPA send was interrupted by user activity";return $(a,d,c),W(a,{key:i,conversationId:r.conversationId,conversationName:o,text:p.text,attachmentPath:s,reason:c,skipText:p.skipText}),{status:"queued",reason:c}}if(!d.ok){const c=d.error||"WeChat RPA send validator failed; manual review required before retry";return Y(a,i,c),{status:"manual-review",reason:c}}return g(a,{status:"sent",runId:a.lastRunId??null,summary:d.rpaTraceSummary||`sent ${o}`}),C(a),a.lastError=null,{status:"sent"}}async health(e){const r=this.readSecret(e);if((r.source??"macos-probe")==="fixture-jsonl")return r.fixturePath&&f.existsSync(r.fixturePath)?{ok:!0}:{ok:!1,message:"WeChat RPA fixture file is missing"};if(r.source==="macos-flow"){if(process.platform!=="darwin")return{ok:!1,message:"WeChat RPA macOS flow requires macOS"};if(!w(r).length)return{ok:!1,message:"WeChat RPA macOS flow requires at least one group"}}if(R(r.source))return Re(r,process.platform);if(r.source==="wechat-rpa-lab"){if(process.platform!=="darwin")return{ok:!1,message:"WeChat RPA Lab source requires macOS for live runs"};if(!w(r).length)return{ok:!1,message:"WeChat RPA Lab source requires at least one group"};try{z(e.workDir)}catch(a){return{ok:!1,message:a instanceof Error?a.message:String(a)}}return{ok:!0,message:"WeChat RPA Lab source configured"}}if(r.source==="windows-visual-flow")return ye(r,process.platform);const n=await D();return n.ok?{ok:!0,message:n.wechatRunning?"WeChat detected":"WeChat is not running"}:{ok:!1,message:n.message}}async defaultConversation(e){const r=this.readSecret(e),n=w(r)[0];if(!n)throw new Error("WeChat RPA channel has no configured groups");return R(r.source)?{conversationId:N(n),conversationName:n}:{conversationId:x(n),conversationName:n}}runtimeStatus(e){const r=this.connections.get(e.id);return r?{wechatRpaRuntimeState:r.runtimeState,wechatRpaLastRunAt:r.lastRunAt??null,wechatRpaLastMessageAt:r.lastMessageAt??null,wechatRpaLastInterruptedAt:r.lastInterruptedAt??null,wechatRpaLastError:r.lastError??null,wechatRpaLastRunId:r.lastRunId??null,wechatRpaLastTracePath:r.lastTracePath??r.product?.helperTracePath??null,wechatRpaLastTraceSummary:r.lastTraceSummary??null,wechatRpaPendingReplyCount:r.pendingReplies.size+ie({config:e}),wechatRpaPendingReplies:Ee(r,e),wechatRpaRecentTaskSummaries:r.recentTaskSummaries}:{}}ensureConnection(e){let r=this.connections.get(e.id);return r||(r={config:e,timer:null,deduper:new K,stopped:!1,conversations:new Map,pendingReplies:new Map,manualReviewReplies:new Map,completedPendingReplyKeys:new Set,pendingStatePath:void 0,messageStatePath:void 0,operation:Promise.resolve(),recentTaskSummaries:[],runtimeState:"idle_waiting",consecutiveInterruptions:0},this.connections.set(e.id,r)),r}enqueueOperation(e,r){const n=this.options.automationLane?()=>this.options.automationLane?.run(`wechat-rpa:${e.config.id}`,r):r,a=e.operation.then(n,n);return e.operation=a.then(()=>{},()=>{}),a}readSecret(e){const r=this.secrets.get(e.secretRef);if(!r||r.type!=="wechat-rpa")throw new Error("WeChat RPA channel is not configured on this daemon");return r}async pollOnce(e,r){if(e.stopped)return[];const n=[];e.lastRunAt=new Date().toISOString();try{if(Q(e,r))return e.runtimeState="cooldown",[];if(await this.drainPendingReplies(e,r))return[];let o=!1;e.runtimeState="syncing";const i=await this.readObservedMessages(e.config,r,u=>{L(e,u),u.interrupted&&(o=!0,$(e,u,u.error||"WeChat RPA poll was interrupted by user activity"))});e.lastError=null;for(const u of i){const s=ae(u,{selfNicknames:U(r)});if(!s||!e.deduper.accept(s.messageId))continue;Le(e),e.conversations.set(s.conversationId,s.conversationName),e.lastMessageAt=s.receivedAt;const l={type:"external.message",managerSessionId:e.config.managerSessionId,channelId:e.config.id,channelType:"wechat-rpa",conversationId:s.conversationId,conversationName:s.conversationName,messageId:s.messageId,sender:s.sender,text:s.text,attachments:s.attachments,receivedAt:s.receivedAt,isMentioned:s.isMentioned,replyTarget:"",rawRef:s.rawRef};n.push(this.onMessage?.(l)??l),g(e,{status:"received",runId:e.lastRunId??null,summary:`received ${s.conversationName}${s.sender.name?` from ${s.sender.name}`:""}: ${_(s.text||s.attachments[0]?.name||"attachment")}`})}return o||C(e),n}catch(a){throw e.lastError=a instanceof Error?a.message:String(a),g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),a}}async pollProductOnce(e,r,n){if(e.stopped)return[];if(!R(r.source))return[];const a=await this.ensureProductConnection(e,r);if((await H(a).catch(()=>null))?.enabled===!1)return e.lastRunAt=new Date().toISOString(),e.runtimeState="idle_waiting",e.lastError=null,[];const i=a.emitted.length;e.lastRunAt=new Date().toISOString(),e.runtimeState="syncing";try{await a.runner.tick(n);const u=a.emitted.slice(i);e.lastError=null;for(const s of u)e.conversations.set(s.conversationId,s.conversationName||""),e.lastMessageAt=s.receivedAt,g(e,{status:"received",runId:e.lastRunId??null,summary:`received ${s.conversationName||s.conversationId}${s.sender.name?` from ${s.sender.name}`:""}: ${_(s.text||s.attachments[0]?.name||"attachment")}`});return C(e),u}catch(u){throw e.lastError=u instanceof Error?u.message:String(u),e.runtimeState=k(e.lastError)==="blocked"?"interrupted":"idle_waiting",g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),u}}async ensureProductConnection(e,r){const n=ge(e.config,r);return e.product&&e.productConfigKey===n||(e.product&&await e.product.runner.stop().catch(()=>{}),e.product=oe({config:e.config,secret:r,createRunner:this.options.createProductRunner,onMessage:this.onMessage}),await H(e.product).catch(()=>{}),e.productConfigKey=n,e.lastTracePath=e.product.helperTracePath),e.product}async drainPendingReplies(e,r){if(!e.pendingReplies.size)return!1;for(const n of Array.from(e.pendingReplies.values())){e.runtimeState="retrying",n.attempts+=1,n.lastAttemptAt=new Date().toISOString(),v(e);const a=await G(e.config,r,n.conversationName,n.skipText?"":n.text,n.attachmentPath,n.key);L(e,a);const o=V(a,{text:n.skipText?"":n.text,attachmentPath:n.attachmentPath});if(o.status==="queued")return n.text=o.text,n.attachmentPath=o.attachmentPath,n.skipText=o.skipText,n.lastInterruptedReason=o.reason,e.lastError=o.reason,v(e),!0;if(a.interrupted){const i=a.error||"WeChat RPA pending send was interrupted by user activity";return n.text=o.text,n.attachmentPath=o.attachmentPath,n.skipText=o.skipText,n.lastInterruptedReason=i,$(e,a,i),v(e),!0}if(!a.ok){const i=a.error||"WeChat RPA pending send validator failed; manual review required before retry";return e.pendingReplies.delete(n.key),Y(e,n.key,i),v(e),!0}e.pendingReplies.delete(n.key),e.completedPendingReplyKeys.add(n.key),v(e),e.lastError=null,g(e,{status:"sent",runId:e.lastRunId??null,summary:a.rpaTraceSummary||`sent ${n.conversationName}`}),C(e)}return!1}async readObservedMessages(e,r,n){if(r.source==="fixture-jsonl")return Fe(r.fixturePath);if(r.source==="macos-flow")return be(e,r,n);if(r.source==="wechat-rpa-lab")return ve(e,r,n);if(r.source==="windows-visual-flow")return xe(e,r,n);const a=await D(),o=ne(a);return o?[o]:[]}seedConfiguredConversations(e,r){for(const n of w(r))e.conversations.set(x(n),n),e.conversations.set(N(n),n)}resolveConversationName(e,r,n){const o=this.connections.get(e.id)?.conversations.get(n);return o||(w(r).find(i=>x(i)===n||N(i)===n)??null)}}function ge(t,e){return JSON.stringify({id:t.id,workDir:t.workDir,managerSessionId:t.managerSessionId,pollIntervalMs:e.pollIntervalMs,forceForeground:b(e),downloadAttachments:e.downloadAttachments!==!1,canReply:e.canReply!==!1,groups:w(e)})}async function H(t){const e=await le().getRuntimePolicy(),r=e.runtime?.current,n=e.runtime?.scanPolicy??e.usagePolicy?.scanPolicy,a=Number.isFinite(r?.pollIntervalSeconds)?Number(r?.pollIntervalSeconds):n?.defaultPollIntervalSeconds??e.runtime?.defaultPollIntervalSeconds;r?.foregroundPolicy==="polite"&&(t.runtime.foregroundPolicy="polite"),r?.foregroundPolicy==="work"&&(t.runtime.foregroundPolicy="work");const o=r?.status!=="disabled";if(!Number.isFinite(a))return{enabled:o};const i=Number.isFinite(n?.minPollIntervalSeconds)?Number(n?.minPollIntervalSeconds):e.runtime?.minPollIntervalSeconds,u=Number.isFinite(n?.maxPollIntervalSeconds)?Number(n?.maxPollIntervalSeconds):e.runtime?.maxPollIntervalSeconds,s=Number.isFinite(i)?Number(i)*1e3:t.runtime.policy.minPollIntervalMs,l=Number.isFinite(u)?Number(u)*1e3:t.runtime.policy.maxPollIntervalMs,d=Math.min(l,Math.max(s,Number(a)*1e3));return t.runtime.policy.pollIntervalMs=d,t.runtime.policy.minPollIntervalMs=s,t.runtime.policy.maxPollIntervalMs=l,{enabled:o}}function B(t){return Number.isFinite(t)?Math.min(6e4,Math.max(1e3,Number(t))):pe}function w(t){return Array.isArray(t.groups)?t.groups.map(e=>String(e?.name||"").trim()).filter(Boolean):[]}function U(t){return String(t.selfNickname||"").split(/[,\n,、]/).map(e=>e.trim()).filter(Boolean)}function we(t,e){return e.length?e.some(r=>{const n=r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`@\\s*${n}(?=$|\\s|[\uFF0C\u3002\uFF01\uFF1F,.!?:\uFF1A\uFF1B;\u3001)])`).test(t)}):!1}function ye(t,e=process.platform){return e!=="win32"?{ok:!1,message:"WeChat RPA Windows visual flow requires Windows"}:{ok:!1,message:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}}function Re(t,e=process.platform){return e!=="darwin"&&e!=="win32"?{ok:!1,message:"WeChat channel product source requires macOS or Windows"}:(Array.isArray(t.groups)?t.groups.map(n=>String(n?.name||"").trim()).filter(Boolean):[]).length?{ok:!0,message:"WeChat channel product source configured"}:{ok:!1,message:"WeChat channel product source requires at least one group"}}async function ve(t,e,r){const n=await j(t.workDir),a=[];for(const o of w(e)){const i=await Se(n,t,e,o);if(r?.(i),!(!i.ok||i.interrupted))for(const u of i.newMessages??[]){const s=X(o,u,e);s&&a.push(s)}}return a}async function Se(t,e,r,n){const a=fe(r.recentLimit),o={kind:a,requestId:`wechat-rpa:${n}:read:${Date.now()}`,targetGroup:n,policy:r.forceForeground?"work":"polite",limit:P(r.recentLimit,A,1,50),attachmentsDir:q(e,r)},i=await t.runWechatRpaReadLatest(o),u=Array.isArray(i.data?.structuredMessages)?i.data.structuredMessages:[],s=i.validation?.deterministic?.latestMessages??[],l=u.length?u.map((d,p)=>Pe(d,i.runId||"lab-read",p)):s.map((d,p)=>({id:`${i.runId||"lab-read"}:${p}`,text:d,confidence:.8}));return{ok:!!i.ok,groupName:n,interrupted:i.status==="interrupted",reason:i.status,rpaRunId:i.runId,rpaTraceSummary:`${a} ${i.status||(i.ok?"success":"failed")} ${n}`,recentMessages:l,newMessages:l,screenshotPath:i.tracePath,error:J(i)}}function Pe(t,e,r){return{id:`${e}:${t.index??r}`,text:String(t.text||t.card?.title||"").trim(),confidence:typeof t.confidence=="number"?t.confidence:.75,senderName:typeof t.senderName=="string"?t.senderName:null,attachments:Ie(t)}}function Ie(t){const e=String(t.kind||"");if(!["image","file","video-file","video-card","link-card","official-account-card","mini-program-card"].includes(e))return[];const r=typeof t.localPath=="string"?t.localPath:void 0;return[{type:e==="video-file"||e==="video-card"?"video":e==="link-card"||e==="official-account-card"||e==="mini-program-card"?"file":e,localPath:r,availability:r?"edge-local":"metadata-only"}]}async function Ae(t,e,r,n,a,o){const i=await j(t.workDir),u=Ce({groupName:r,text:n,attachmentPath:a,dedupeKey:o,requestId:o,policy:"work"}),s=[];for(const h of u){const y=await i.runWechatRpaTask(h);if(s.push(y),!y.ok)break}const l=s.find(h=>h.kind==="send-text"),d=s.find(h=>h.kind==="send-image"||h.kind==="send-video"||h.kind==="send-file"),p=u.length>0&&s.length===u.length&&s.every(h=>h.ok),c=s.some(h=>h.status==="interrupted");return{ok:p,groupName:r,interrupted:c,reason:s.at(-1)?.status,rpaRunId:s.at(-1)?.runId,rpaTraceSummary:`send ${p?"success":s.at(-1)?.status||"failed"} ${r} (${s.length}/${u.length})`,sentReply:!!n,sentReplyObserved:n?!!l?.ok:!1,sentAttachment:!!a,sentAttachmentObserved:a?!!d?.ok:!1,postSendScreenshotPath:s.at(-1)?.tracePath,error:p?void 0:J(s.at(-1))}}function Ce(t){const e=[],r=String(t.text||"").trim(),n=t.policy||"work";if(r&&e.push({kind:"send-text",requestId:t.requestId?`${t.requestId}:text`:void 0,targetGroup:t.groupName,policy:n,text:r,...t.dedupeKey?{dedupeKey:`${t.dedupeKey}:text`}:{}}),t.attachmentPath){const a=ke(t.attachmentPath);e.push({kind:a,requestId:t.requestId?`${t.requestId}:attachment`:void 0,targetGroup:t.groupName,policy:n,filePath:t.attachmentPath,...t.dedupeKey?{dedupeKey:`${t.dedupeKey}:attachment`}:{}})}return e}function ke(t){const e=m.extname(t).toLowerCase();return[".png",".jpg",".jpeg",".gif",".webp",".heic"].includes(e)?"send-image":[".mp4",".mov",".avi",".mkv"].includes(e)?"send-video":"send-file"}async function j(t){const e=z(t);return await import(ee(e).href)}function z(t){const e=m.dirname(Z(import.meta.url)),n=[process.env.SHENNIAN_WECHAT_RPA_LAB_INDEX,m.resolve(e,"../../../../scripts/wechat-rpa-lab/index.mjs"),t?m.join(t,"scripts/wechat-rpa-lab/index.mjs"):"",m.resolve(process.cwd(),"scripts/wechat-rpa-lab/index.mjs")].filter(Boolean).find(a=>f.existsSync(a));if(!n)throw new Error("WeChat RPA Lab API is missing; set SHENNIAN_WECHAT_RPA_LAB_INDEX or run from the repository root");return m.resolve(n)}function J(t){return t?typeof t.error=="string"?t.error:t.error?.message||(t.ok?void 0:`WeChat RPA Lab task ${t.kind||"<unknown>"} ${t.status||"failed"}`):"WeChat RPA Lab did not return a result"}async function be(t,e,r){const n=[];for(const a of w(e)){const o=await F({groupName:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:b(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:q(t,e)});if(r?.(o),!o.interrupted)for(const i of o.newMessages??[]){const u=X(a,i,e);u&&n.push(u)}}return n}async function xe(t,e,r){const n=w(e)[0]||"<unbound>";return r?.({ok:!1,groupName:n,interrupted:!0,reason:"windows-visual-flow-archived",newMessages:[],recentMessages:[],error:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}),[]}function X(t,e,r){const n=String(e.text||"").trim(),a=Array.isArray(e.attachments)?e.attachments:[];if(!n&&a.length===0)return null;const o=typeof e.senderName=="string"?e.senderName.trim():"",i=String(e.observedAt||e.timestampIso||"").trim()||new Date().toISOString();return{conversationName:t,senderName:o||null,text:n,attachments:je(a),observedAt:i,isMentioned:e.isMentioned===!0||we(n,U(r)),rawId:String(e.id||`${t}:${o}:${n}:${i}`)}}async function G(t,e,r,n,a,o){return e.source==="windows-visual-flow"?{ok:!1,groupName:r,interrupted:!0,reason:"windows-visual-flow-archived",newMessages:[],recentMessages:[],error:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}:e.source==="wechat-rpa-lab"?Ae(t,e,r,n,a,o):F({groupName:r,replyText:n||void 0,attachmentPath:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:b(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:q(t,e)})}function Ne(t){return t==="macos-flow"||t==="wechat-rpa-lab"}function V(t,e){return e.text&&e.attachmentPath&&t.sentReplyObserved&&!t.sentAttachmentObserved?{status:t.interrupted?"continue":"queued",text:"",attachmentPath:e.attachmentPath,skipText:!0,reason:t.error||"WeChat RPA sent the text but did not confirm the attachment; queued attachment-only retry"}:{status:"continue",text:e.text,attachmentPath:e.attachmentPath,reason:t.error||""}}function Me(t,e){return e.idempotencyKey?e.idempotencyKey:I.createHash("sha256").update(`${t.id}
1
+ import I from"node:crypto";import f from"node:fs";import m from"node:path";import{fileURLToPath as Z,pathToFileURL as ee}from"node:url";import{ChannelSecretRegistry as te}from"./secret-registry.js";import{resolveShennianPath as re}from"../config/index.js";import{probeMacWeChat as D,observedMessageFromProbe as ne}from"./wechat-rpa/macos.js";import{runMacWeChatRpaFlow as F}from"./wechat-rpa/macos-flow.js";import{normalizeWeChatRpaMessage as ae,WeChatRpaDeduper as K,weChatRpaConversationId as x}from"./wechat-rpa/normalizer.js";import{cancelWeChatRpaProductOutboundReply as se,countPendingWeChatRpaProductOutbound as ie,createWeChatRpaProductSourceConnection as oe,enqueueWeChatRpaProductOutboundReply as ue,isWeChatRpaProductSource as R,listWeChatRpaProductRecentMessages as ce,listPendingWeChatRpaProductOutbound as de,weChatChannelConversationId as N}from"./wechat-rpa/product-channel.js";import{createWeChatChannelApiClient as le}from"./wechat-channel/client.js";const pe=5e3,A=5,me=8,S=Number(process.env.SHENNIAN_WECHAT_RPA_OUTBOUND_ATTACHMENT_MAX_BYTES||20*1024*1024),M=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_THRESHOLD||3),T=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_MS||300*1e3),he=10;function fe(t){return P(t,A,1,50)>=me?"scroll-read":"read-latest"}class at{onMessage;options;type="wechat-rpa";secrets=new te;connections=new Map;constructor(e,r={}){this.onMessage=e,this.options=r}async connect(e){if(!e.enabled)return;const r=this.readSecret(e),n=this.ensureConnection(e);if(n.stopped=!1,n.config=e,W(n,e),O(n,e),this.seedConfiguredConversations(n,r),R(r.source)){if(await this.ensureProductConnection(n,r),n.timer)return;r.deferInitialPoll||await this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))),n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))).catch(()=>{})},n.product?.runtime.policy.pollIntervalMs??B(r.pollIntervalMs)),n.timer.unref();return}n.timer||(await this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))),n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))).catch(()=>{})},B(r.pollIntervalMs)),n.timer.unref())}async disconnect(e){const r=this.connections.get(e.id);if(!r)return;r.stopped=!0,r.timer&&clearInterval(r.timer),r.timer=null;const n=r.product;await this.enqueueOperation(r,async()=>{n&&await n.runner.stop()}).catch(()=>{}),this.connections.delete(e.id)}async syncNow(e){if(!e.enabled)throw new Error("WeChat RPA channel is disabled");const r=this.readSecret(e),n=this.ensureConnection(e);return n.stopped=!1,n.config=e,W(n,e),O(n,e),this.seedConfiguredConversations(n,r),R(r.source)?(await this.ensureProductConnection(n,r),this.enqueueOperation(n,async()=>{const a=this.readSecret(n.config),o=await this.pollProductOnce(n,a,{forceObserve:!0}),i=await this.ensureProductConnection(n,a),u=ce({config:n.config,product:i,limit:a.recentLimit});return ze(u,o)})):this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config)))}async cancelOutbound(e,r){const n=this.readSecret(e);return R(n.source)?se({config:e,idempotencyKey:r.idempotencyKey,replyId:r.replyId,reason:r.reason}):{cancelled:!1,status:"unsupported_source"}}async send(e,r){const n=this.readSecret(e);if(n.canReply===!1)throw new Error("WeChat RPA channel does not allow replies");const a=this.ensureConnection(e);if(a.config=e,W(a,e),O(a,e),this.seedConfiguredConversations(a,n),R(n.source)){const c=this.resolveConversationName(e,n,r.conversationId);if(!c)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const h=r.text.trim();if(!h&&!r.attachment)throw new Error("Reply text or attachment is required");return await this.ensureProductConnection(a,n),await this.enqueueOperation(a,async()=>{const y=this.readSecret(a.config);ue({config:a.config,secret:y,reply:{...r,text:h},conversationName:c}),a.runtimeState="retrying",g(a,{status:"sent",runId:a.lastRunId??null,summary:`queued ${c}`})}),this.enqueueOperation(a,()=>this.pollProductOnce(a,this.readSecret(a.config))).catch(y=>{a.lastError=y instanceof Error?y.message:String(y)}),{status:"queued"}}if(!Ne(n.source))throw new Error("WeChat RPA reply requires source=macos-flow, windows-visual-flow, or wechat-rpa-lab");const o=this.resolveConversationName(e,n,r.conversationId);if(!o)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const i=Me(e,r);if(a.completedPendingReplyKeys.has(i))return{status:"sent"};const u=a.manualReviewReplies.get(i);if(u)return{status:"manual-review",reason:u.reason};const s=r.attachment?await Ke(e.workDir,r.attachment):void 0,l=r.text.trim();if(!l&&!s)throw new Error("Reply text or attachment is required");if(Q(a,n)){const c="WeChat RPA is cooling down after repeated user activity";return a.runtimeState="cooldown",E(a,{key:i,conversationId:r.conversationId,conversationName:o,text:l,attachmentPath:s,reason:c}),{status:"queued",reason:c}}let d;try{a.runtimeState="syncing",d=await this.enqueueOperation(a,()=>(a.lastRunAt=new Date().toISOString(),G(e,n,o,l,s,i)))}catch(c){throw a.lastError=c instanceof Error?c.message:String(c),g(a,{status:k(a.lastError),runId:a.lastRunId??null,summary:a.lastError}),c}L(a,d);const p=V(d,{text:l,attachmentPath:s});if(p.status==="queued"){const c=p.reason;return E(a,{key:i,conversationId:r.conversationId,conversationName:o,text:p.text,attachmentPath:p.attachmentPath,reason:c,skipText:p.skipText}),a.lastError=c,{status:"queued",reason:c}}if(d.interrupted){const c=d.error||"WeChat RPA send was interrupted by user activity";return $(a,d,c),E(a,{key:i,conversationId:r.conversationId,conversationName:o,text:p.text,attachmentPath:s,reason:c,skipText:p.skipText}),{status:"queued",reason:c}}if(!d.ok){const c=d.error||"WeChat RPA send validator failed; manual review required before retry";return Y(a,i,c),{status:"manual-review",reason:c}}return g(a,{status:"sent",runId:a.lastRunId??null,summary:d.rpaTraceSummary||`sent ${o}`}),C(a),a.lastError=null,{status:"sent"}}async health(e){const r=this.readSecret(e);if((r.source??"macos-probe")==="fixture-jsonl")return r.fixturePath&&f.existsSync(r.fixturePath)?{ok:!0}:{ok:!1,message:"WeChat RPA fixture file is missing"};if(r.source==="macos-flow"){if(process.platform!=="darwin")return{ok:!1,message:"WeChat RPA macOS flow requires macOS"};if(!w(r).length)return{ok:!1,message:"WeChat RPA macOS flow requires at least one group"}}if(R(r.source))return Re(r,process.platform);if(r.source==="wechat-rpa-lab"){if(process.platform!=="darwin")return{ok:!1,message:"WeChat RPA Lab source requires macOS for live runs"};if(!w(r).length)return{ok:!1,message:"WeChat RPA Lab source requires at least one group"};try{z(e.workDir)}catch(a){return{ok:!1,message:a instanceof Error?a.message:String(a)}}return{ok:!0,message:"WeChat RPA Lab source configured"}}if(r.source==="windows-visual-flow")return ye(r,process.platform);const n=await D();return n.ok?{ok:!0,message:n.wechatRunning?"WeChat detected":"WeChat is not running"}:{ok:!1,message:n.message}}async defaultConversation(e){const r=this.readSecret(e),n=w(r)[0];if(!n)throw new Error("WeChat RPA channel has no configured groups");return R(r.source)?{conversationId:N(n),conversationName:n}:{conversationId:x(n),conversationName:n}}runtimeStatus(e){const r=this.connections.get(e.id);return r?{wechatRpaRuntimeState:r.runtimeState,wechatRpaLastRunAt:r.lastRunAt??null,wechatRpaLastMessageAt:r.lastMessageAt??null,wechatRpaLastInterruptedAt:r.lastInterruptedAt??null,wechatRpaLastError:r.lastError??null,wechatRpaLastRunId:r.lastRunId??null,wechatRpaLastTracePath:r.lastTracePath??r.product?.helperTracePath??null,wechatRpaLastTraceSummary:r.lastTraceSummary??null,wechatRpaPendingReplyCount:r.pendingReplies.size+ie({config:e}),wechatRpaPendingReplies:Te(r,e),wechatRpaRecentTaskSummaries:r.recentTaskSummaries}:{}}ensureConnection(e){let r=this.connections.get(e.id);return r||(r={config:e,timer:null,deduper:new K,stopped:!1,conversations:new Map,pendingReplies:new Map,manualReviewReplies:new Map,completedPendingReplyKeys:new Set,pendingStatePath:void 0,messageStatePath:void 0,operation:Promise.resolve(),recentTaskSummaries:[],runtimeState:"idle_waiting",consecutiveInterruptions:0},this.connections.set(e.id,r)),r}enqueueOperation(e,r){const n=this.options.automationLane?()=>this.options.automationLane?.run(`wechat-rpa:${e.config.id}`,r):r,a=e.operation.then(n,n);return e.operation=a.then(()=>{},()=>{}),a}readSecret(e){const r=this.secrets.get(e.secretRef);if(!r||r.type!=="wechat-rpa")throw new Error("WeChat RPA channel is not configured on this daemon");return r}async pollOnce(e,r){if(e.stopped)return[];const n=[];e.lastRunAt=new Date().toISOString();try{if(Q(e,r))return e.runtimeState="cooldown",[];if(await this.drainPendingReplies(e,r))return[];let o=!1;e.runtimeState="syncing";const i=await this.readObservedMessages(e.config,r,u=>{L(e,u),u.interrupted&&(o=!0,$(e,u,u.error||"WeChat RPA poll was interrupted by user activity"))});e.lastError=null;for(const u of i){const s=ae(u,{selfNicknames:U(r)});if(!s||!e.deduper.accept(s.messageId))continue;Le(e),e.conversations.set(s.conversationId,s.conversationName),e.lastMessageAt=s.receivedAt;const l={type:"external.message",managerSessionId:e.config.managerSessionId,channelId:e.config.id,channelType:"wechat-rpa",conversationId:s.conversationId,conversationName:s.conversationName,messageId:s.messageId,sender:s.sender,text:s.text,attachments:s.attachments,receivedAt:s.receivedAt,isMentioned:s.isMentioned,replyTarget:"",rawRef:s.rawRef};n.push(this.onMessage?.(l)??l),g(e,{status:"received",runId:e.lastRunId??null,summary:`received ${s.conversationName}${s.sender.name?` from ${s.sender.name}`:""}: ${_(s.text||s.attachments[0]?.name||"attachment")}`})}return o||C(e),n}catch(a){throw e.lastError=a instanceof Error?a.message:String(a),g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),a}}async pollProductOnce(e,r,n){if(e.stopped)return[];if(!R(r.source))return[];const a=await this.ensureProductConnection(e,r);if((await H(a).catch(()=>null))?.enabled===!1)return e.lastRunAt=new Date().toISOString(),e.runtimeState="idle_waiting",e.lastError=null,[];const i=a.emitted.length;e.lastRunAt=new Date().toISOString(),e.runtimeState="syncing";try{await a.runner.tick(n);const u=a.emitted.slice(i);e.lastError=null;for(const s of u)e.conversations.set(s.conversationId,s.conversationName||""),e.lastMessageAt=s.receivedAt,g(e,{status:"received",runId:e.lastRunId??null,summary:`received ${s.conversationName||s.conversationId}${s.sender.name?` from ${s.sender.name}`:""}: ${_(s.text||s.attachments[0]?.name||"attachment")}`});return C(e),u}catch(u){throw e.lastError=u instanceof Error?u.message:String(u),e.runtimeState=k(e.lastError)==="blocked"?"interrupted":"idle_waiting",g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),u}}async ensureProductConnection(e,r){const n=ge(e.config,r);return e.product&&e.productConfigKey===n||(e.product&&await e.product.runner.stop().catch(()=>{}),e.product=oe({config:e.config,secret:r,createRunner:this.options.createProductRunner,onMessage:this.onMessage}),await H(e.product).catch(()=>{}),e.productConfigKey=n,e.lastTracePath=e.product.helperTracePath),e.product}async drainPendingReplies(e,r){if(!e.pendingReplies.size)return!1;for(const n of Array.from(e.pendingReplies.values())){e.runtimeState="retrying",n.attempts+=1,n.lastAttemptAt=new Date().toISOString(),v(e);const a=await G(e.config,r,n.conversationName,n.skipText?"":n.text,n.attachmentPath,n.key);L(e,a);const o=V(a,{text:n.skipText?"":n.text,attachmentPath:n.attachmentPath});if(o.status==="queued")return n.text=o.text,n.attachmentPath=o.attachmentPath,n.skipText=o.skipText,n.lastInterruptedReason=o.reason,e.lastError=o.reason,v(e),!0;if(a.interrupted){const i=a.error||"WeChat RPA pending send was interrupted by user activity";return n.text=o.text,n.attachmentPath=o.attachmentPath,n.skipText=o.skipText,n.lastInterruptedReason=i,$(e,a,i),v(e),!0}if(!a.ok){const i=a.error||"WeChat RPA pending send validator failed; manual review required before retry";return e.pendingReplies.delete(n.key),Y(e,n.key,i),v(e),!0}e.pendingReplies.delete(n.key),e.completedPendingReplyKeys.add(n.key),v(e),e.lastError=null,g(e,{status:"sent",runId:e.lastRunId??null,summary:a.rpaTraceSummary||`sent ${n.conversationName}`}),C(e)}return!1}async readObservedMessages(e,r,n){if(r.source==="fixture-jsonl")return Fe(r.fixturePath);if(r.source==="macos-flow")return be(e,r,n);if(r.source==="wechat-rpa-lab")return ve(e,r,n);if(r.source==="windows-visual-flow")return xe(e,r,n);const a=await D(),o=ne(a);return o?[o]:[]}seedConfiguredConversations(e,r){for(const n of w(r))e.conversations.set(x(n),n),e.conversations.set(N(n),n)}resolveConversationName(e,r,n){const o=this.connections.get(e.id)?.conversations.get(n);return o||(w(r).find(i=>x(i)===n||N(i)===n)??null)}}function ge(t,e){return JSON.stringify({id:t.id,workDir:t.workDir,managerSessionId:t.managerSessionId,pollIntervalMs:e.pollIntervalMs,forceForeground:b(e),downloadAttachments:e.downloadAttachments!==!1,canReply:e.canReply!==!1,selfTriggerMarker:e.selfTriggerMarker||"",groups:w(e)})}async function H(t){const e=await le().getRuntimePolicy(),r=e.runtime?.current,n=e.runtime?.scanPolicy??e.usagePolicy?.scanPolicy,a=Number.isFinite(r?.pollIntervalSeconds)?Number(r?.pollIntervalSeconds):n?.defaultPollIntervalSeconds??e.runtime?.defaultPollIntervalSeconds;r?.foregroundPolicy==="polite"&&(t.runtime.foregroundPolicy="polite"),r?.foregroundPolicy==="work"&&(t.runtime.foregroundPolicy="work");const o=r?.status!=="disabled";if(!Number.isFinite(a))return{enabled:o};const i=Number.isFinite(n?.minPollIntervalSeconds)?Number(n?.minPollIntervalSeconds):e.runtime?.minPollIntervalSeconds,u=Number.isFinite(n?.maxPollIntervalSeconds)?Number(n?.maxPollIntervalSeconds):e.runtime?.maxPollIntervalSeconds,s=Number.isFinite(i)?Number(i)*1e3:t.runtime.policy.minPollIntervalMs,l=Number.isFinite(u)?Number(u)*1e3:t.runtime.policy.maxPollIntervalMs,d=Math.min(l,Math.max(s,Number(a)*1e3));return t.runtime.policy.pollIntervalMs=d,t.runtime.policy.minPollIntervalMs=s,t.runtime.policy.maxPollIntervalMs=l,{enabled:o}}function B(t){return Number.isFinite(t)?Math.min(6e4,Math.max(1e3,Number(t))):pe}function w(t){return Array.isArray(t.groups)?t.groups.map(e=>String(e?.name||"").trim()).filter(Boolean):[]}function U(t){return String(t.selfNickname||"").split(/[,\n,、]/).map(e=>e.trim()).filter(Boolean)}function we(t,e){return e.length?e.some(r=>{const n=r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`@\\s*${n}(?=$|\\s|[\uFF0C\u3002\uFF01\uFF1F,.!?:\uFF1A\uFF1B;\u3001)])`).test(t)}):!1}function ye(t,e=process.platform){return e!=="win32"?{ok:!1,message:"WeChat RPA Windows visual flow requires Windows"}:{ok:!1,message:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}}function Re(t,e=process.platform){return e!=="darwin"&&e!=="win32"?{ok:!1,message:"WeChat channel product source requires macOS or Windows"}:(Array.isArray(t.groups)?t.groups.map(n=>String(n?.name||"").trim()).filter(Boolean):[]).length?{ok:!0,message:"WeChat channel product source configured"}:{ok:!1,message:"WeChat channel product source requires at least one group"}}async function ve(t,e,r){const n=await j(t.workDir),a=[];for(const o of w(e)){const i=await Se(n,t,e,o);if(r?.(i),!(!i.ok||i.interrupted))for(const u of i.newMessages??[]){const s=X(o,u,e);s&&a.push(s)}}return a}async function Se(t,e,r,n){const a=fe(r.recentLimit),o={kind:a,requestId:`wechat-rpa:${n}:read:${Date.now()}`,targetGroup:n,policy:r.forceForeground?"work":"polite",limit:P(r.recentLimit,A,1,50),attachmentsDir:q(e,r)},i=await t.runWechatRpaReadLatest(o),u=Array.isArray(i.data?.structuredMessages)?i.data.structuredMessages:[],s=i.validation?.deterministic?.latestMessages??[],l=u.length?u.map((d,p)=>Pe(d,i.runId||"lab-read",p)):s.map((d,p)=>({id:`${i.runId||"lab-read"}:${p}`,text:d,confidence:.8}));return{ok:!!i.ok,groupName:n,interrupted:i.status==="interrupted",reason:i.status,rpaRunId:i.runId,rpaTraceSummary:`${a} ${i.status||(i.ok?"success":"failed")} ${n}`,recentMessages:l,newMessages:l,screenshotPath:i.tracePath,error:J(i)}}function Pe(t,e,r){return{id:`${e}:${t.index??r}`,text:String(t.text||t.card?.title||"").trim(),confidence:typeof t.confidence=="number"?t.confidence:.75,senderName:typeof t.senderName=="string"?t.senderName:null,attachments:Ie(t)}}function Ie(t){const e=String(t.kind||"");if(!["image","file","video-file","video-card","link-card","official-account-card","mini-program-card"].includes(e))return[];const r=typeof t.localPath=="string"?t.localPath:void 0;return[{type:e==="video-file"||e==="video-card"?"video":e==="link-card"||e==="official-account-card"||e==="mini-program-card"?"file":e,localPath:r,availability:r?"edge-local":"metadata-only"}]}async function Ae(t,e,r,n,a,o){const i=await j(t.workDir),u=Ce({groupName:r,text:n,attachmentPath:a,dedupeKey:o,requestId:o,policy:"work"}),s=[];for(const h of u){const y=await i.runWechatRpaTask(h);if(s.push(y),!y.ok)break}const l=s.find(h=>h.kind==="send-text"),d=s.find(h=>h.kind==="send-image"||h.kind==="send-video"||h.kind==="send-file"),p=u.length>0&&s.length===u.length&&s.every(h=>h.ok),c=s.some(h=>h.status==="interrupted");return{ok:p,groupName:r,interrupted:c,reason:s.at(-1)?.status,rpaRunId:s.at(-1)?.runId,rpaTraceSummary:`send ${p?"success":s.at(-1)?.status||"failed"} ${r} (${s.length}/${u.length})`,sentReply:!!n,sentReplyObserved:n?!!l?.ok:!1,sentAttachment:!!a,sentAttachmentObserved:a?!!d?.ok:!1,postSendScreenshotPath:s.at(-1)?.tracePath,error:p?void 0:J(s.at(-1))}}function Ce(t){const e=[],r=String(t.text||"").trim(),n=t.policy||"work";if(r&&e.push({kind:"send-text",requestId:t.requestId?`${t.requestId}:text`:void 0,targetGroup:t.groupName,policy:n,text:r,...t.dedupeKey?{dedupeKey:`${t.dedupeKey}:text`}:{}}),t.attachmentPath){const a=ke(t.attachmentPath);e.push({kind:a,requestId:t.requestId?`${t.requestId}:attachment`:void 0,targetGroup:t.groupName,policy:n,filePath:t.attachmentPath,...t.dedupeKey?{dedupeKey:`${t.dedupeKey}:attachment`}:{}})}return e}function ke(t){const e=m.extname(t).toLowerCase();return[".png",".jpg",".jpeg",".gif",".webp",".heic"].includes(e)?"send-image":[".mp4",".mov",".avi",".mkv"].includes(e)?"send-video":"send-file"}async function j(t){const e=z(t);return await import(ee(e).href)}function z(t){const e=m.dirname(Z(import.meta.url)),n=[process.env.SHENNIAN_WECHAT_RPA_LAB_INDEX,m.resolve(e,"../../../../scripts/wechat-rpa-lab/index.mjs"),t?m.join(t,"scripts/wechat-rpa-lab/index.mjs"):"",m.resolve(process.cwd(),"scripts/wechat-rpa-lab/index.mjs")].filter(Boolean).find(a=>f.existsSync(a));if(!n)throw new Error("WeChat RPA Lab API is missing; set SHENNIAN_WECHAT_RPA_LAB_INDEX or run from the repository root");return m.resolve(n)}function J(t){return t?typeof t.error=="string"?t.error:t.error?.message||(t.ok?void 0:`WeChat RPA Lab task ${t.kind||"<unknown>"} ${t.status||"failed"}`):"WeChat RPA Lab did not return a result"}async function be(t,e,r){const n=[];for(const a of w(e)){const o=await F({groupName:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:b(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:q(t,e)});if(r?.(o),!o.interrupted)for(const i of o.newMessages??[]){const u=X(a,i,e);u&&n.push(u)}}return n}async function xe(t,e,r){const n=w(e)[0]||"<unbound>";return r?.({ok:!1,groupName:n,interrupted:!0,reason:"windows-visual-flow-archived",newMessages:[],recentMessages:[],error:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}),[]}function X(t,e,r){const n=String(e.text||"").trim(),a=Array.isArray(e.attachments)?e.attachments:[];if(!n&&a.length===0)return null;const o=typeof e.senderName=="string"?e.senderName.trim():"",i=String(e.observedAt||e.timestampIso||"").trim()||new Date().toISOString();return{conversationName:t,senderName:o||null,text:n,attachments:je(a),observedAt:i,isMentioned:e.isMentioned===!0||we(n,U(r)),rawId:String(e.id||`${t}:${o}:${n}:${i}`)}}async function G(t,e,r,n,a,o){return e.source==="windows-visual-flow"?{ok:!1,groupName:r,interrupted:!0,reason:"windows-visual-flow-archived",newMessages:[],recentMessages:[],error:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}:e.source==="wechat-rpa-lab"?Ae(t,e,r,n,a,o):F({groupName:r,replyText:n||void 0,attachmentPath:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:b(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:q(t,e)})}function Ne(t){return t==="macos-flow"||t==="wechat-rpa-lab"}function V(t,e){return e.text&&e.attachmentPath&&t.sentReplyObserved&&!t.sentAttachmentObserved?{status:t.interrupted?"continue":"queued",text:"",attachmentPath:e.attachmentPath,skipText:!0,reason:t.error||"WeChat RPA sent the text but did not confirm the attachment; queued attachment-only retry"}:{status:"continue",text:e.text,attachmentPath:e.attachmentPath,reason:t.error||""}}function Me(t,e){return e.idempotencyKey?e.idempotencyKey:I.createHash("sha256").update(`${t.id}
2
2
  ${e.conversationId}
3
3
  ${e.text}
4
4
  ${e.attachment?.name||""}
5
- ${e.attachment?.size||0}`).digest("hex").slice(0,24)}function Ee(t,e){return[...Array.from(t.pendingReplies.values()).map(n=>({replyId:n.key,idempotencyKey:n.key,status:"queued",conversationName:n.conversationName,reasonCode:n.lastInterruptedReason??null,nextAttemptAt:null,queuedAt:n.queuedAt,lastAttemptAt:n.lastAttemptAt??null,attemptCount:n.attempts,commitStage:null})),...de({config:e})].slice(0,100)}function W(t,e){const r=t.pendingReplies.get(e.key);t.pendingReplies.set(e.key,{key:e.key,conversationId:e.conversationId,conversationName:e.conversationName,text:e.text,attachmentPath:e.attachmentPath,skipText:e.skipText,queuedAt:r?.queuedAt??new Date().toISOString(),attempts:r?.attempts??0,lastAttemptAt:r?.lastAttemptAt,lastInterruptedReason:e.reason}),v(t)}function Y(t,e,r){t.manualReviewReplies.set(e,{key:e,reason:r,createdAt:t.manualReviewReplies.get(e)?.createdAt??new Date().toISOString()}),t.lastError=r,g(t,{status:"failed",runId:t.lastRunId??null,summary:`manual review required: ${r}`}),v(t)}function T(t,e){const r=$e(e);if(t.pendingStatePath===r)return;t.pendingStatePath=r;const n=We(r);for(const a of n.pending??[])Te(a)&&(t.pendingReplies.has(a.key)||t.pendingReplies.set(a.key,a));for(const a of n.manualReview??[])Oe(a)&&(t.manualReviewReplies.has(a.key)||t.manualReviewReplies.set(a.key,a));for(const a of n.completedKeys??[])typeof a=="string"&&a&&t.completedPendingReplyKeys.add(a)}function v(t){if(!t.pendingStatePath)return;const e=Array.from(t.completedPendingReplyKeys).slice(-500),r=Array.from(t.pendingReplies.values()).slice(0,500),n=Array.from(t.manualReviewReplies.values()).slice(-500);try{f.mkdirSync(m.dirname(t.pendingStatePath),{recursive:!0}),f.writeFileSync(t.pendingStatePath,JSON.stringify({version:1,pending:r,manualReview:n,completedKeys:e},null,2))}catch{}}function We(t){try{const e=JSON.parse(f.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,pending:[],completedKeys:[]}}catch{return{version:1,pending:[],completedKeys:[]}}}function Te(t){if(!t||typeof t!="object")return!1;const e=t;return typeof e.key=="string"&&typeof e.conversationId=="string"&&typeof e.conversationName=="string"&&typeof e.text=="string"&&typeof e.queuedAt=="string"&&typeof e.attempts=="number"&&Number.isFinite(e.attempts)&&(e.attachmentPath===void 0||typeof e.attachmentPath=="string")&&(e.skipText===void 0||typeof e.skipText=="boolean")}function Oe(t){if(!t||typeof t!="object")return!1;const e=t;return typeof e.key=="string"&&typeof e.reason=="string"&&typeof e.createdAt=="string"}function $e(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return m.join(t.workDir,".shennian","wechat-rpa-pending-replies",`${e}.json`)}function O(t,e){const r=qe(e);if(t.messageStatePath===r)return;t.messageStatePath=r;const n=_e(r);t.deduper=new K((n.messageIds??[]).filter(a=>typeof a=="string"&&a.length>0))}function Le(t){if(t.messageStatePath)try{f.mkdirSync(m.dirname(t.messageStatePath),{recursive:!0}),f.writeFileSync(t.messageStatePath,JSON.stringify({version:1,messageIds:t.deduper.snapshot()},null,2))}catch{}}function _e(t){try{const e=JSON.parse(f.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,messageIds:[]}}catch{return{version:1,messageIds:[]}}}function qe(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return m.join(t.workDir,".shennian","wechat-rpa-seen-messages",`${e}.json`)}function $(t,e,r){t.lastInterruptedAt=new Date().toISOString(),t.lastError=null,t.consecutiveInterruptions+=1,t.runtimeState="interrupted",De(t)&&(t.interruptionCooldownUntil=Date.now()+E,t.runtimeState="cooldown"),g(t,{status:"interrupted",runId:e.rpaRunId||t.lastRunId||null,summary:r})}function C(t){t.consecutiveInterruptions=0,t.interruptionCooldownUntil=void 0,t.runtimeState="idle_waiting"}function L(t,e){const r=e.postSendScreenshotPath||e.screenshotPath;e.rpaRunId&&(t.lastRunId=e.rpaRunId),r&&(t.lastTracePath=r),t.lastTraceSummary=e.rpaTraceSummary||[e.groupName?`group=${e.groupName}`:"",e.ok===!1?"failed":e.interrupted?"interrupted":"ok",e.reason?`reason=${e.reason}`:""].filter(Boolean).join(" ")}function g(t,e){const r=e.runId||t.lastRunId||`local:${Date.now().toString(36)}`;t.lastRunId=r,t.recentTaskSummaries.unshift({at:new Date().toISOString(),status:e.status,runId:r,summary:_(e.summary)}),t.recentTaskSummaries=t.recentTaskSummaries.slice(0,he)}function k(t){const e=String(t||"").toLowerCase();return/permission|accessibility|screen recording|automation|window|foreground|target group|refusing|requires|安全|权限|窗口|目标群/.test(e)?"blocked":"failed"}function _(t){return String(t||"").replace(/\s+/g," ").trim().slice(0,180)}function Q(t,e){if(b(e))return!1;const r=t.interruptionCooldownUntil;return r?Date.now()<r?!0:(t.interruptionCooldownUntil=void 0,t.consecutiveInterruptions=0,!1):!1}function b(t){return t.forceForeground!==!1}function De(t){return!Number.isFinite(M)||M<=0||!Number.isFinite(E)||E<=0?!1:t.consecutiveInterruptions>=M}function Fe(t){return!t||!f.existsSync(t)?[]:f.readFileSync(t,"utf-8").split(`
5
+ ${e.attachment?.size||0}`).digest("hex").slice(0,24)}function Te(t,e){return[...Array.from(t.pendingReplies.values()).map(n=>({replyId:n.key,idempotencyKey:n.key,status:"queued",conversationName:n.conversationName,reasonCode:n.lastInterruptedReason??null,nextAttemptAt:null,queuedAt:n.queuedAt,lastAttemptAt:n.lastAttemptAt??null,attemptCount:n.attempts,commitStage:null})),...de({config:e})].slice(0,100)}function E(t,e){const r=t.pendingReplies.get(e.key);t.pendingReplies.set(e.key,{key:e.key,conversationId:e.conversationId,conversationName:e.conversationName,text:e.text,attachmentPath:e.attachmentPath,skipText:e.skipText,queuedAt:r?.queuedAt??new Date().toISOString(),attempts:r?.attempts??0,lastAttemptAt:r?.lastAttemptAt,lastInterruptedReason:e.reason}),v(t)}function Y(t,e,r){t.manualReviewReplies.set(e,{key:e,reason:r,createdAt:t.manualReviewReplies.get(e)?.createdAt??new Date().toISOString()}),t.lastError=r,g(t,{status:"failed",runId:t.lastRunId??null,summary:`manual review required: ${r}`}),v(t)}function W(t,e){const r=$e(e);if(t.pendingStatePath===r)return;t.pendingStatePath=r;const n=Ee(r);for(const a of n.pending??[])We(a)&&(t.pendingReplies.has(a.key)||t.pendingReplies.set(a.key,a));for(const a of n.manualReview??[])Oe(a)&&(t.manualReviewReplies.has(a.key)||t.manualReviewReplies.set(a.key,a));for(const a of n.completedKeys??[])typeof a=="string"&&a&&t.completedPendingReplyKeys.add(a)}function v(t){if(!t.pendingStatePath)return;const e=Array.from(t.completedPendingReplyKeys).slice(-500),r=Array.from(t.pendingReplies.values()).slice(0,500),n=Array.from(t.manualReviewReplies.values()).slice(-500);try{f.mkdirSync(m.dirname(t.pendingStatePath),{recursive:!0}),f.writeFileSync(t.pendingStatePath,JSON.stringify({version:1,pending:r,manualReview:n,completedKeys:e},null,2))}catch{}}function Ee(t){try{const e=JSON.parse(f.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,pending:[],completedKeys:[]}}catch{return{version:1,pending:[],completedKeys:[]}}}function We(t){if(!t||typeof t!="object")return!1;const e=t;return typeof e.key=="string"&&typeof e.conversationId=="string"&&typeof e.conversationName=="string"&&typeof e.text=="string"&&typeof e.queuedAt=="string"&&typeof e.attempts=="number"&&Number.isFinite(e.attempts)&&(e.attachmentPath===void 0||typeof e.attachmentPath=="string")&&(e.skipText===void 0||typeof e.skipText=="boolean")}function Oe(t){if(!t||typeof t!="object")return!1;const e=t;return typeof e.key=="string"&&typeof e.reason=="string"&&typeof e.createdAt=="string"}function $e(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return m.join(t.workDir,".shennian","wechat-rpa-pending-replies",`${e}.json`)}function O(t,e){const r=qe(e);if(t.messageStatePath===r)return;t.messageStatePath=r;const n=_e(r);t.deduper=new K((n.messageIds??[]).filter(a=>typeof a=="string"&&a.length>0))}function Le(t){if(t.messageStatePath)try{f.mkdirSync(m.dirname(t.messageStatePath),{recursive:!0}),f.writeFileSync(t.messageStatePath,JSON.stringify({version:1,messageIds:t.deduper.snapshot()},null,2))}catch{}}function _e(t){try{const e=JSON.parse(f.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,messageIds:[]}}catch{return{version:1,messageIds:[]}}}function qe(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return m.join(t.workDir,".shennian","wechat-rpa-seen-messages",`${e}.json`)}function $(t,e,r){t.lastInterruptedAt=new Date().toISOString(),t.lastError=null,t.consecutiveInterruptions+=1,t.runtimeState="interrupted",De(t)&&(t.interruptionCooldownUntil=Date.now()+T,t.runtimeState="cooldown"),g(t,{status:"interrupted",runId:e.rpaRunId||t.lastRunId||null,summary:r})}function C(t){t.consecutiveInterruptions=0,t.interruptionCooldownUntil=void 0,t.runtimeState="idle_waiting"}function L(t,e){const r=e.postSendScreenshotPath||e.screenshotPath;e.rpaRunId&&(t.lastRunId=e.rpaRunId),r&&(t.lastTracePath=r),t.lastTraceSummary=e.rpaTraceSummary||[e.groupName?`group=${e.groupName}`:"",e.ok===!1?"failed":e.interrupted?"interrupted":"ok",e.reason?`reason=${e.reason}`:""].filter(Boolean).join(" ")}function g(t,e){const r=e.runId||t.lastRunId||`local:${Date.now().toString(36)}`;t.lastRunId=r,t.recentTaskSummaries.unshift({at:new Date().toISOString(),status:e.status,runId:r,summary:_(e.summary)}),t.recentTaskSummaries=t.recentTaskSummaries.slice(0,he)}function k(t){const e=String(t||"").toLowerCase();return/permission|accessibility|screen recording|automation|window|foreground|target group|refusing|requires|安全|权限|窗口|目标群/.test(e)?"blocked":"failed"}function _(t){return String(t||"").replace(/\s+/g," ").trim().slice(0,180)}function Q(t,e){if(b(e))return!1;const r=t.interruptionCooldownUntil;return r?Date.now()<r?!0:(t.interruptionCooldownUntil=void 0,t.consecutiveInterruptions=0,!1):!1}function b(t){return t.forceForeground!==!1}function De(t){return!Number.isFinite(M)||M<=0||!Number.isFinite(T)||T<=0?!1:t.consecutiveInterruptions>=M}function Fe(t){return!t||!f.existsSync(t)?[]:f.readFileSync(t,"utf-8").split(`
6
6
  `).map(e=>e.trim()).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(e=>!!e)}async function Ke(t,e){if(e.localPath){if(!f.existsSync(e.localPath))throw new Error(`WeChat RPA local attachment does not exist: ${e.localPath}`);return e.localPath}if(e.url)return He(t,e);throw new Error("WeChat RPA attachment requires localPath or url; dataBase64 is not accepted over Manager IPC")}async function He(t,e){if(!e.url||!/^https?:\/\//i.test(e.url))throw new Error("WeChat RPA attachment url must be http(s)");if(Number.isFinite(e.size)&&e.size>S)throw new Error(`WeChat RPA cross-machine attachment is too large: ${e.size} bytes. Max: ${S} bytes.`);const r=await fetch(e.url);if(!r.ok)throw new Error(`WeChat RPA attachment url download failed: ${r.status}`);const n=Number(r.headers.get("content-length")||e.size||0);if(n>S)throw new Error(`WeChat RPA cross-machine attachment is too large: ${n} bytes. Max: ${S} bytes.`);const a=Buffer.from(await r.arrayBuffer());return Be(t,e,a)}function Be(t,e,r){if(!r.byteLength)throw new Error("WeChat RPA attachment is empty");if(r.byteLength>S)throw new Error(`WeChat RPA cross-machine attachment is too large: ${r.byteLength} bytes. Max: ${S} bytes.`);const n=m.join(t,".uploads","wechat-rpa","outbound");f.mkdirSync(n,{recursive:!0});const a=Ue(e.name||"attachment"),o=I.createHash("sha256").update(r).digest("hex").slice(0,12),i=m.extname(a),u=i?a.slice(0,-i.length):a,s=m.join(n,`${u}-${o}${i}`);return f.existsSync(s)||f.writeFileSync(s,r),s}function Ue(t){return m.basename(t||"attachment").normalize("NFKC").replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").replace(/\s+/g," ").replace(/^[ ._]+|[ ._]+$/g,"")||"attachment"}function q(t,e){if(e.downloadAttachments===!1)return;const r=e.downloadAttachmentsDir?.trim();if(r)return m.resolve(r);const n=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return re("wechat-rpa","attachments","inbound",n)}function je(t){if(!Array.isArray(t))return[];const e=process.env.SHENNIAN_MACHINE_ID?.trim();return t.map(r=>r?.localPath?{...r,availability:r.availability||"edge-local",...r.machineId||!e?{}:{machineId:e}}:r)}function P(t,e,r,n){const a=Number(t);return Number.isFinite(a)?Math.min(n,Math.max(r,a)):e}function ze(...t){const e=new Set,r=[];for(const n of t.flat()){const a=`${n.channelId}
7
7
  ${n.conversationId}
8
8
  ${n.messageId}`;e.has(a)||(e.add(a),r.push(n))}return r}export{at as WeChatRpaChannelAdapter,je as annotateWeChatRpaInboundAttachments,Ke as materializeWeChatRpaOutboundAttachment,Ce as planWeChatRpaLabSendTasks,q as resolveWeChatRpaInboundAttachmentDir,fe as selectWeChatRpaLabReadKind,ke as weChatRpaLabTaskKindForAttachment,Re as weChatRpaProductSourceHealth,ye as windowsVisualFlowHealth};
@@ -0,0 +1,80 @@
1
+ type HelperRuntimePlatform = 'darwin' | 'win32';
2
+ export type HelperRuntimeOfficialArtifact = {
3
+ platform: HelperRuntimePlatform;
4
+ arch: string;
5
+ helperVersion: string;
6
+ protocolVersion: number;
7
+ minCliVersion: string;
8
+ url: string;
9
+ sizeBytes: number;
10
+ sha256: string;
11
+ packageManifestSha256?: string;
12
+ evidenceUrl?: string;
13
+ };
14
+ export type HelperRuntimeOfficialManifest = {
15
+ schemaVersion: 1;
16
+ kind: 'shennian-helper-runtime-index';
17
+ channel: string;
18
+ generatedAt: string;
19
+ artifacts: HelperRuntimeOfficialArtifact[];
20
+ };
21
+ export type HelperRuntimeOfficialDownloadFailureReason = 'helper_runtime_manifest_unavailable' | 'helper_runtime_artifact_unavailable' | 'helper_runtime_download_failed' | 'helper_runtime_artifact_integrity_mismatch' | 'helper_runtime_artifact_invalid';
22
+ export type HelperRuntimeOfficialDownloadResult = {
23
+ ok: true;
24
+ sourceDir: string;
25
+ tempDir: string;
26
+ cleanup: () => void;
27
+ channel: string;
28
+ manifestUrl: string;
29
+ artifactUrl: string;
30
+ artifactSha256: string;
31
+ artifactSizeBytes: number;
32
+ platform: HelperRuntimePlatform;
33
+ arch: string;
34
+ helperVersion: string;
35
+ protocolVersion: number;
36
+ minCliVersion: string;
37
+ } | {
38
+ ok: false;
39
+ reasonCode: HelperRuntimeOfficialDownloadFailureReason;
40
+ message: string;
41
+ manifestUrl?: string;
42
+ artifactUrl?: string;
43
+ };
44
+ type FetchLike = (url: string, init?: {
45
+ signal?: AbortSignal;
46
+ }) => Promise<Response>;
47
+ export type HelperRuntimeOfficialDownloadOptions = {
48
+ platform: HelperRuntimePlatform;
49
+ arch?: string;
50
+ env?: NodeJS.ProcessEnv;
51
+ channel?: string;
52
+ manifestUrl?: string;
53
+ fetchImpl?: FetchLike;
54
+ timeoutMs?: number;
55
+ maxBytes?: number;
56
+ workRoot?: string;
57
+ downloadFile?: (input: {
58
+ url: string;
59
+ outputPath: string;
60
+ fetchImpl: FetchLike;
61
+ timeoutMs: number;
62
+ maxBytes: number;
63
+ }) => Promise<void>;
64
+ extractZip?: (input: {
65
+ zipPath: string;
66
+ outputDir: string;
67
+ platform: HelperRuntimePlatform;
68
+ }) => Promise<void> | void;
69
+ };
70
+ export declare function downloadOfficialHelperRuntimePackage(options: HelperRuntimeOfficialDownloadOptions): Promise<HelperRuntimeOfficialDownloadResult>;
71
+ export declare function getOfficialHelperRuntimeManifestUrl(input?: {
72
+ env?: NodeJS.ProcessEnv;
73
+ channel?: string;
74
+ }): string;
75
+ export declare function selectOfficialHelperRuntimeArtifact(input: {
76
+ manifest: HelperRuntimeOfficialManifest;
77
+ platform: HelperRuntimePlatform;
78
+ arch: string;
79
+ }): HelperRuntimeOfficialArtifact | null;
80
+ export {};
@@ -0,0 +1 @@
1
+ import U from"node:crypto";import{once as _}from"node:events";import o from"node:fs";import z from"node:os";import h from"node:path";import{spawnSync as y}from"node:child_process";const R="https://videoindustry.oss-cn-hangzhou.aliyuncs.com/shennian/helper-runtime",g="stable",$=12e4,I=350*1024*1024;async function X(e){const r=e.env??process.env,n=S(e.channel??r.SHENNIAN_HELPER_RUNTIME_CHANNEL)??g,t=e.manifestUrl??P({env:r,channel:n}),a=e.arch??process.arch,s=A(r.SHENNIAN_HELPER_RUNTIME_DOWNLOAD_TIMEOUT_MS)??e.timeoutMs??$,c=A(r.SHENNIAN_HELPER_RUNTIME_MAX_BYTES)??e.maxBytes??I,m=e.fetchImpl??globalThis.fetch?.bind(globalThis);if(!m)return{ok:!1,reasonCode:"helper_runtime_manifest_unavailable",message:"This Node.js runtime does not provide fetch for official Helper runtime download.",manifestUrl:t};const l=await L({manifestUrl:t,fetchImpl:m,timeoutMs:s});if(!l.ok)return l;const i=x({manifest:l.manifest,platform:e.platform,arch:a});if(!i)return{ok:!1,reasonCode:"helper_runtime_artifact_unavailable",message:`No official Helper runtime artifact is available for ${e.platform}/${a} on channel ${l.manifest.channel}.`,manifestUrl:t};const f=o.mkdtempSync(h.join(e.workRoot??z.tmpdir(),"shennian-helper-runtime-")),d=h.join(f,"helper-runtime.zip"),p=h.join(f,"source");try{await(e.downloadFile??C)({url:i.url,outputPath:d,fetchImpl:m,timeoutMs:s,maxBytes:c})}catch(u){return o.rmSync(f,{recursive:!0,force:!0}),{ok:!1,reasonCode:"helper_runtime_download_failed",message:`Official Helper runtime download failed: ${u instanceof Error?u.message:String(u)}`,manifestUrl:t,artifactUrl:i.url}}const H=o.statSync(d).size,T=O(d);if(H!==i.sizeBytes||T!==i.sha256)return o.rmSync(f,{recursive:!0,force:!0}),{ok:!1,reasonCode:"helper_runtime_artifact_integrity_mismatch",message:"Official Helper runtime artifact failed size or sha256 verification.",manifestUrl:t,artifactUrl:i.url};try{o.mkdirSync(p,{recursive:!0}),await(e.extractZip??M)({zipPath:d,outputDir:p,platform:e.platform}),v(p)}catch(u){return o.rmSync(f,{recursive:!0,force:!0}),{ok:!1,reasonCode:"helper_runtime_artifact_invalid",message:`Official Helper runtime artifact could not be extracted safely: ${u instanceof Error?u.message:String(u)}`,manifestUrl:t,artifactUrl:i.url}}return{ok:!0,sourceDir:p,tempDir:f,cleanup:()=>o.rmSync(f,{recursive:!0,force:!0}),channel:l.manifest.channel,manifestUrl:t,artifactUrl:i.url,artifactSha256:i.sha256,artifactSizeBytes:i.sizeBytes,platform:i.platform,arch:i.arch,helperVersion:i.helperVersion,protocolVersion:i.protocolVersion,minCliVersion:i.minCliVersion}}function P(e={}){const r=e.env??process.env,n=r.SHENNIAN_HELPER_RUNTIME_MANIFEST_URL?.trim();if(n)return n;const t=S(e.channel??r.SHENNIAN_HELPER_RUNTIME_CHANNEL)??g;return`${(r.SHENNIAN_HELPER_RUNTIME_BASE_URL?.trim()||R).replace(/\/+$/g,"")}/${encodeURIComponent(t)}/manifest.json`}function x(e){return N(e.manifest)?e.manifest.artifacts.filter(n=>n.platform===e.platform).find(n=>D(n.arch,e.arch))??null:null}async function L(e){try{const r=new AbortController,n=setTimeout(()=>r.abort(),e.timeoutMs);try{const t=await e.fetchImpl(e.manifestUrl,{signal:r.signal});if(!t.ok)throw new Error(`HTTP ${t.status}`);const a=JSON.parse(await t.text());if(!N(a))throw new Error("manifest schema is invalid");return{ok:!0,manifest:a}}finally{clearTimeout(n)}}catch(r){return{ok:!1,reasonCode:"helper_runtime_manifest_unavailable",message:`Official Helper runtime manifest is unavailable: ${r instanceof Error?r.message:String(r)}`,manifestUrl:e.manifestUrl}}}async function C(e){const r=new AbortController,n=setTimeout(()=>r.abort(),e.timeoutMs);let t=null;try{const a=await e.fetchImpl(e.url,{signal:r.signal});if(!a.ok)throw new Error(`HTTP ${a.status}`);if(!a.body)throw new Error("response body is empty");t=o.createWriteStream(e.outputPath,{flags:"wx"});const s=a.body.getReader();let c=0;for(;;){const{done:m,value:l}=await s.read();if(m)break;const i=Buffer.from(l);if(c+=i.byteLength,c>e.maxBytes)throw new Error(`download exceeded ${e.maxBytes} bytes`);t.write(i)||await _(t,"drain")}t.end(),await _(t,"finish")}finally{clearTimeout(n),t&&!t.closed&&t.destroy()}}async function M(e){if(k(e.zipPath),process.platform==="win32"){E("powershell.exe",["-NoProfile","-ExecutionPolicy","Bypass","-Command",`Expand-Archive -LiteralPath ${b(e.zipPath)} -DestinationPath ${b(e.outputDir)} -Force`]);return}if(e.platform==="darwin"&&w("ditto")){E("ditto",["-x","-k",e.zipPath,e.outputDir]);return}E("unzip",["-q",e.zipPath,"-d",e.outputDir])}function k(e){const r=w("unzip")?"unzip":w("tar")?"tar":null;if(!r)return;const n=r==="unzip"?y(r,["-Z","-1",e],{encoding:"utf8"}):y(r,["-tf",e],{encoding:"utf8"});if(n.status!==0)throw new Error(`unable to inspect zip entries with ${r}`);for(const t of n.stdout.split(/\r?\n/u).filter(Boolean)){const a=t.replace(/\\/g,"/");if(a.startsWith("/")||a.includes("../")||a===".."||/^[A-Za-z]:\//u.test(a))throw new Error(`unsafe zip entry: ${t}`)}}function v(e){const r=o.realpathSync(e),n=[e];for(;n.length>0;){const t=n.pop();if(t)for(const a of o.readdirSync(t,{withFileTypes:!0})){const s=h.join(t,a.name),c=o.realpathSync(s);if(c!==r&&!c.startsWith(`${r}${h.sep}`))throw new Error(`extracted path escapes target directory: ${s}`);a.isDirectory()&&n.push(s)}}}function N(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const r=e;return r.schemaVersion!==1||r.kind!=="shennian-helper-runtime-index"||!r.channel||typeof r.channel!="string"||!r.generatedAt||typeof r.generatedAt!="string"||!Array.isArray(r.artifacts)?!1:r.artifacts.every(B)}function B(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const r=e;return(r.platform==="darwin"||r.platform==="win32")&&typeof r.arch=="string"&&typeof r.helperVersion=="string"&&Number.isInteger(r.protocolVersion)&&typeof r.minCliVersion=="string"&&typeof r.url=="string"&&typeof r.sizeBytes=="number"&&Number.isInteger(r.sizeBytes)&&r.sizeBytes>0&&typeof r.sha256=="string"&&/^[a-f0-9]{64}$/iu.test(r.sha256)}function D(e,r){const n=e.toLowerCase(),t=r.toLowerCase();return n===t||n==="universal"||n==="all"||n===`win-${t}`||n===`darwin-${t}`}function S(e){const r=e?.trim();return!r||!/^[a-z0-9][a-z0-9._-]{0,63}$/iu.test(r)?null:r}function A(e){if(!e)return null;const r=Number(e);return Number.isInteger(r)&&r>0?r:null}function O(e){return U.createHash("sha256").update(o.readFileSync(e)).digest("hex")}function w(e){return y(e,["--version"],{stdio:"ignore"}).status===0}function E(e,r){const n=y(e,r,{stdio:"ignore"});if(n.status!==0)throw new Error(`${e} exited ${n.status}`)}function b(e){return`'${e.replace(/'/g,"''")}'`}export{X as downloadOfficialHelperRuntimePackage,P as getOfficialHelperRuntimeManifestUrl,x as selectOfficialHelperRuntimeArtifact};
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import type { Command } from 'commander';
3
3
  import { type WeChatChannelHelperRuntimePackageManifest } from '../channels/wechat-channel/helper-assets.js';
4
+ import { type HelperRuntimeOfficialDownloadOptions } from './helper-runtime-official-download.js';
4
5
  type RuntimeCommandPlatform = 'darwin' | 'win32';
5
6
  export type HelperRuntimeInstallResult = {
6
7
  ok: true;
@@ -24,6 +25,15 @@ export type HelperRuntimeInstallResult = {
24
25
  packageKind?: string;
25
26
  installTarget?: WeChatChannelHelperRuntimePackageManifest['installTarget'];
26
27
  signature?: WeChatChannelHelperRuntimePackageManifest['signature'];
28
+ source?: 'local' | 'official-download';
29
+ officialDownload?: {
30
+ channel: string;
31
+ manifestUrl: string;
32
+ artifactUrl: string;
33
+ artifactSha256: string;
34
+ artifactSizeBytes: number;
35
+ arch: string;
36
+ };
27
37
  message: string;
28
38
  } | {
29
39
  ok: false;
@@ -35,6 +45,10 @@ export type HelperRuntimeInstallResult = {
35
45
  message: string;
36
46
  repairSuggestion?: string;
37
47
  checkedSources?: string[];
48
+ officialDownload?: {
49
+ manifestUrl?: string;
50
+ artifactUrl?: string;
51
+ };
38
52
  };
39
53
  export type HelperRuntimeStatusResult = {
40
54
  ok: boolean;
@@ -71,6 +85,7 @@ type RuntimeIo = {
71
85
  renameSync?: typeof fs.renameSync;
72
86
  chmodSync?: typeof fs.chmodSync;
73
87
  homedir?: () => string;
88
+ stopWindowsHelperProcesses?: () => void;
74
89
  };
75
90
  export declare function registerRuntimeCommand(program: Command): void;
76
91
  export declare function getHelperRuntimeStatus(input?: {
@@ -86,4 +101,13 @@ export declare function installHelperRuntime(input?: {
86
101
  force?: boolean;
87
102
  io?: RuntimeIo;
88
103
  }): HelperRuntimeInstallResult;
104
+ export declare function installHelperRuntimeWithOfficialDownload(input?: {
105
+ source?: string;
106
+ platform?: NodeJS.Platform | string;
107
+ env?: NodeJS.ProcessEnv;
108
+ homedir?: string;
109
+ force?: boolean;
110
+ io?: RuntimeIo;
111
+ officialDownload?: false | Partial<HelperRuntimeOfficialDownloadOptions>;
112
+ }): Promise<HelperRuntimeInstallResult>;
89
113
  export {};
@@ -1 +1 @@
1
- import f from"node:fs";import b from"node:os";import a from"node:path";import v from"chalk";import{getDefaultWeChatHelperRuntimeRoot as C,readWeChatChannelHelperRuntimePackageManifest as _,resolveWeChatChannelHelperAsset as y,SHENNIAN_HELPER_RUNTIME_DIR_ENV as I,WECHAT_CHANNEL_HELPER_DIR_ENV as A}from"../channels/wechat-channel/helper-assets.js";import{compareVersions as $,getCurrentVersion as w}from"../upgrade/engine.js";const u="helper-runtime-package.json";function Q(e){const r=e.command("runtime").description("Install, repair, and inspect Shennian local runtimes");r.command("status").description("Print local Shennian runtime status").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(i=>{const o=D({platform:i.platform});P(o,!!i.json),o.ok||(process.exitCode=1)}),r.command("install").description("Install a Shennian runtime component").argument("<component>","Runtime component, currently only: helper").option("--source <path>","Helper runtime package/source directory").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action((i,o)=>{const n=i==="helper"?V({source:o.source,platform:o.platform}):R(i);P(n,!!o.json),n.ok||(process.exitCode=1)}),r.command("repair").description("Repair a Shennian runtime component").argument("[component]","Runtime component, currently only: helper","helper").option("--source <path>","Helper runtime package/source directory").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action((i,o)=>{const n=i==="helper"?V({source:o.source,platform:o.platform,force:!0}):R(i);P(n,!!o.json),n.ok||(process.exitCode=1)})}function D(e={}){const r=j(e.platform);if(!r)return{ok:!1,installed:!1,repairable:!1,reasonCode:"unsupported_platform",message:"Shennian Helper runtime is only available on macOS and Windows.",repairSuggestion:c("unsupported_platform")};const i=e.env??process.env,o=e.homedir,n=y({platform:r,env:i,homedir:o,includeInstalledDesktop:!1}),t=C({platform:r,env:i,homedir:o});if(!n.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:T(n.reasonCode),reasonCode:n.reasonCode,message:n.message,runtimeRoot:t,repairSuggestion:c(n.reasonCode),checkedSources:k({platform:r,env:i,homedir:o})};const s=_({platform:r,helperDir:n.helperDir});if(!s.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:s.reasonCode,message:s.message,helperDir:n.helperDir,helperPath:n.helperPath,helperVersion:n.version,runtimeVersion:n.version,protocolVersion:n.manifest.protocolVersion,runtimeRoot:t,repairSuggestion:c(s.reasonCode),checkedSources:k({platform:r,env:i,homedir:o})};const l=M({platform:r,packageManifest:s.manifest,currentCliVersion:w(),helperVersion:n.version,protocolVersion:n.manifest.protocolVersion});return l.ok?{ok:!0,platform:r,targetPlatform:r,installed:!0,repairable:!1,message:`Shennian Helper runtime is installed: ${n.helperDir}`,helperDir:n.helperDir,helperPath:n.helperPath,helperVersion:n.version,runtimeVersion:n.version,protocolVersion:n.manifest.protocolVersion,minCliVersion:s.manifest.minCliVersion,sha256:s.manifest.sha256,packageManifestPath:s.manifestPath,packageKind:s.manifest.packageKind,installTarget:s.manifest.installTarget,signature:s.manifest.signature,runtimeRoot:t}:{ok:!1,platform:r,targetPlatform:r,installed:!0,repairable:l.repairable,reasonCode:l.reasonCode,message:l.message,helperDir:n.helperDir,helperPath:n.helperPath,helperVersion:n.version,runtimeVersion:n.version,protocolVersion:n.manifest.protocolVersion,minCliVersion:s.manifest.minCliVersion,sha256:s.manifest.sha256,packageManifestPath:s.manifestPath,packageKind:s.manifest.packageKind,installTarget:s.manifest.installTarget,signature:s.manifest.signature,runtimeRoot:t,repairSuggestion:c(l.reasonCode,s.manifest.minCliVersion),checkedSources:k({platform:r,env:i,homedir:o})}}function V(e={}){const r=j(e.platform);if(!r)return{ok:!1,reasonCode:"unsupported_platform",message:"Shennian Helper runtime is only available on macOS and Windows.",repairable:!1,repairSuggestion:c("unsupported_platform")};const i=e.env??process.env,o=E(e.io),n=e.homedir??o.homedir(),t=D({platform:r,env:i,homedir:n});if(t.ok&&!e.force&&t.helperDir&&t.helperPath&&t.helperVersion&&t.protocolVersion)return{ok:!0,action:"already_installed",platform:r,targetPlatform:r,installed:!0,repairable:!1,targetDir:N(r,t.helperDir),helperDir:t.helperDir,helperPath:t.helperPath,helperVersion:t.helperVersion,runtimeVersion:t.helperVersion,protocolVersion:t.protocolVersion,minCliVersion:t.minCliVersion,sha256:t.sha256,packageManifestPath:t.packageManifestPath,packageKind:t.packageKind,installTarget:t.installTarget,signature:t.signature,message:`Shennian Helper runtime is already installed: ${t.helperDir}`};const s=e.source?[a.resolve(e.source)]:k({platform:r,env:i,homedir:n}),l=s.map(S=>x({candidate:S,platform:r,io:o})).find(S=>S!=null);if(!l)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:"helper_runtime_source_missing",message:"No installable Shennian Helper runtime package was found. Open Shennian Desktop, pass --source, or download the official helper package.",repairSuggestion:c("helper_runtime_source_missing"),checkedSources:s};const h=_({platform:r,manifestPath:l.packageManifestPath});if(!h.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:h.reasonCode,message:h.message,repairSuggestion:c(h.reasonCode),checkedSources:s};const d=M({platform:r,packageManifest:h.manifest,currentCliVersion:w()});if(!d.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:d.repairable,reasonCode:d.reasonCode,message:d.message,repairSuggestion:c(d.reasonCode,h.manifest.minCliVersion),checkedSources:s};const g=L({platform:r,env:i,homedir:n,packageKind:l.packageKind});if(!g)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!1,reasonCode:"helper_runtime_target_unavailable",message:"Unable to determine Helper runtime install target for this platform.",repairSuggestion:c("helper_runtime_target_unavailable"),checkedSources:s};F({sourceDir:l.sourceDir,targetDir:g,io:o}),W({sourceManifestPath:l.packageManifestPath,targetManifestPath:l.packageManifestPathInTarget(g),io:o});const p=y({platform:r,baseDir:l.manifestDirInTarget(g),verifyIntegrity:!0});if(!p.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:p.reasonCode,message:`Installed Helper runtime could not be verified: ${p.message}`,repairSuggestion:c(p.reasonCode),checkedSources:s};U(r,p.helperPath,o);const m=_({platform:r,helperDir:p.helperDir});return m.ok?{ok:!0,action:"installed",platform:r,targetPlatform:r,installed:!0,repairable:!1,targetDir:g,helperDir:p.helperDir,helperPath:p.helperPath,helperVersion:p.version,runtimeVersion:p.version,protocolVersion:p.manifest.protocolVersion,minCliVersion:m.manifest.minCliVersion,sha256:m.manifest.sha256,packageManifestPath:m.manifestPath,packageKind:m.manifest.packageKind,installTarget:m.manifest.installTarget,signature:m.manifest.signature,message:`Installed Shennian Helper runtime to ${g}`}:{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:m.reasonCode,message:`Installed Helper runtime package manifest could not be verified: ${m.message}`,repairSuggestion:c(m.reasonCode),checkedSources:s}}function j(e){const r=e??process.platform;return r==="darwin"||r==="win32"?r:null}function M(e){return e.packageManifest.platform!==e.platform?{ok:!1,reasonCode:"helper_runtime_platform_mismatch",message:`Helper runtime package platform ${e.packageManifest.platform} does not match ${e.platform}.`,repairable:!0}:e.helperVersion&&e.packageManifest.helperVersion!==e.helperVersion?{ok:!1,reasonCode:"helper_runtime_version_mismatch",message:`Helper runtime package version ${e.packageManifest.helperVersion} does not match installed helper ${e.helperVersion}.`,repairable:!0}:e.protocolVersion&&e.packageManifest.protocolVersion!==e.protocolVersion?{ok:!1,reasonCode:"helper_runtime_protocol_mismatch",message:`Helper runtime package protocol ${e.packageManifest.protocolVersion} does not match installed helper protocol ${e.protocolVersion}.`,repairable:!0}:$(e.currentCliVersion,e.packageManifest.minCliVersion)!=="none"?{ok:!1,reasonCode:"helper_runtime_cli_too_old",message:`Shennian CLI ${e.currentCliVersion} is older than required ${e.packageManifest.minCliVersion}.`,repairable:!1}:{ok:!0}}function T(e){return e!=="unsupported_platform"&&e!=="helper_runtime_cli_too_old"}function c(e,r){if(e==="unsupported_platform")return"Use Shennian Helper runtime on macOS or Windows.";if(e==="unsupported_runtime_component")return"Use `shennian runtime install helper` or `shennian runtime repair helper`.";if(e==="helper_runtime_cli_too_old")return`Upgrade Shennian CLI${r?` to ${r} or newer`:""}, then retry.`;if(e==="helper_runtime_target_unavailable")return"Set the current user home/LOCALAPPDATA correctly, then retry.";if(e==="helper_runtime_required")return"Install Shennian Desktop or run `shennian runtime install helper` with an official Helper runtime package.";if(e==="helper_runtime_source_missing")return"Open Shennian Desktop, download the official Helper runtime package, or pass `--source <path>`.";if(e==="helper_runtime_package_manifest_missing"||e==="helper_runtime_package_manifest_invalid")return"Reinstall or repair the official Shennian Helper runtime package.";if(e==="helper_runtime_platform_mismatch")return"Install the Helper runtime package for this platform.";if(e==="helper_runtime_version_mismatch"||e==="helper_runtime_protocol_mismatch")return"Run `shennian runtime repair helper` with the matching official Helper runtime package.";if(e==="manifest_missing"||e==="helper_missing"||e==="integrity_mismatch"||e==="helper_not_executable")return"Run `shennian runtime repair helper` or reinstall Shennian Desktop."}function R(e){return{ok:!1,repairable:!1,reasonCode:"unsupported_runtime_component",message:`Unsupported runtime component: ${e}. Supported component: helper.`,repairSuggestion:c("unsupported_runtime_component")}}function E(e){return{existsSync:e?.existsSync??f.existsSync,readFileSync:e?.readFileSync??f.readFileSync,mkdirSync:e?.mkdirSync??f.mkdirSync,rmSync:e?.rmSync??f.rmSync,cpSync:e?.cpSync??f.cpSync,renameSync:e?.renameSync??f.renameSync,chmodSync:e?.chmodSync??f.chmodSync,homedir:e?.homedir??b.homedir}}function x(e){const r=a.resolve(e.candidate);return e.io.existsSync(r)?e.platform==="darwin"?O(r,e.io):K(r,e.io):null}function O(e,r){const i=e.endsWith(".app")?e:a.join(e,"Shennian Helper.app");if(r.existsSync(a.join(i,"Contents","Resources","wechat-channel","macos","manifest.json")))return{sourceDir:i,packageKind:"macos-app",packageManifestPath:H(r,[a.join(i,"Contents","Resources","helper-runtime-package.json"),a.join(e,u)]),packageManifestPathInTarget:n=>a.join(n,"Contents","Resources",u),manifestDirInTarget:n=>a.join(n,"Contents","Resources","wechat-channel","macos")};const o=r.existsSync(a.join(e,"manifest.json"))?e:a.join(e,"wechat-channel","macos");return r.existsSync(a.join(o,"manifest.json"))?{sourceDir:o,packageKind:"asset-dir",packageManifestPath:a.join(o,"helper-runtime-package.json"),packageManifestPathInTarget:n=>a.join(n,u),manifestDirInTarget:n=>n}:null}function K(e,r){const i=a.basename(e).toLowerCase()==="shennian helper"?e:a.join(e,"Shennian Helper");if(r.existsSync(a.join(i,"resources","wechat-channel","windows","manifest.json")))return{sourceDir:i,packageKind:"windows-runtime",packageManifestPath:H(r,[a.join(i,"resources",u),a.join(e,u)]),packageManifestPathInTarget:n=>a.join(n,"resources",u),manifestDirInTarget:n=>a.join(n,"resources","wechat-channel","windows")};const o=r.existsSync(a.join(e,"manifest.json"))?e:a.join(e,"wechat-channel","windows");return r.existsSync(a.join(o,"manifest.json"))?{sourceDir:o,packageKind:"asset-dir",packageManifestPath:a.join(o,"helper-runtime-package.json"),packageManifestPathInTarget:n=>a.join(n,u),manifestDirInTarget:n=>n}:null}function L(e){const r=C(e);if(!r)return null;if(e.platform==="darwin")return e.packageKind==="macos-app"?a.join(r,"Shennian Helper.app"):a.join(r,"wechat-channel","macos");if(e.packageKind==="windows-runtime"){const i=e.env.LOCALAPPDATA||(e.homedir?a.join(e.homedir,"AppData","Local"):"");return i?a.join(i,"Programs","Shennian Helper"):null}return a.join(r,"wechat-channel","windows")}function N(e,r){const i=a.resolve(r);if(e==="darwin"){const n=`${a.sep}Contents${a.sep}Resources${a.sep}wechat-channel${a.sep}macos`;return i.endsWith(n)?i.slice(0,-n.length):i}const o=`${a.sep}resources${a.sep}wechat-channel${a.sep}windows`;return i.endsWith(o)?i.slice(0,-o.length):i}function F(e){const r=a.resolve(e.sourceDir),i=a.resolve(e.targetDir);if(r===i)return;const o=`${i}.tmp-${process.pid}-${Date.now()}`;e.io.rmSync(o,{recursive:!0,force:!0}),e.io.mkdirSync(a.dirname(i),{recursive:!0}),e.io.cpSync(r,o,{recursive:!0}),e.io.rmSync(i,{recursive:!0,force:!0}),e.io.renameSync(o,i)}function W(e){const r=a.resolve(e.sourceManifestPath),i=a.resolve(e.targetManifestPath);!e.io.existsSync(r)||r===i||(e.io.mkdirSync(a.dirname(i),{recursive:!0}),e.io.cpSync(r,i))}function H(e,r){return r.find(i=>e.existsSync(i))??r[0]}function U(e,r,i){if(e==="darwin")try{const o=f.statSync(r);(o.mode&73)===0&&i.chmodSync(r,o.mode|493)}catch{}}function k(e){const r=e.homedir||(e.platform==="win32"?e.env.USERPROFILE:e.env.HOME),i=[],o=n=>{if(!n)return;const t=a.resolve(n);i.includes(t)||i.push(t)};if(o(e.env.SHENNIAN_HELPER_PACKAGE_DIR),o(e.env[A]),o(e.env[I]),e.platform==="darwin")r&&(o(a.join(r,"Applications","Shennian.app","Contents","Resources")),o(a.join(r,"Applications","Shennian Helper.app"))),o(a.join("/Applications","Shennian.app","Contents","Resources")),o(a.join("/Applications","Shennian Helper.app"));else{const n=e.env.LOCALAPPDATA||(r?a.join(r,"AppData","Local"):""),t=e.env.ProgramFiles||e.env.PROGRAMFILES,s=e.env["ProgramFiles(x86)"]||e.env["PROGRAMFILES(X86)"];n&&(o(a.join(n,"Programs","Shennian","resources")),o(a.join(n,"Programs","Shennian Helper"))),t&&(o(a.join(t,"Shennian","resources")),o(a.join(t,"Shennian Helper"))),s&&(o(a.join(s,"Shennian","resources")),o(a.join(s,"Shennian Helper")))}return o(a.resolve(process.cwd(),"packages/helper-runtime/dist",e.platform==="darwin"?"macos":"windows")),o(a.resolve(process.cwd(),"packages/helper-runtime/wechat-channel",e.platform==="darwin"?"macos":"windows")),i}function P(e,r){if(r){console.log(JSON.stringify(e,null,2));return}if(e.ok){console.log(v.green(`\u2713 ${e.message}`)),"helperVersion"in e&&console.log(`version=${e.helperVersion} protocol=${e.protocolVersion}`),"helperPath"in e&&console.log(e.helperPath);return}console.error(v.red(`\u2717 ${e.reasonCode}`)),console.error(e.message)}export{D as getHelperRuntimeStatus,V as installHelperRuntime,Q as registerRuntimeCommand};
1
+ import f from"node:fs";import y from"node:os";import i from"node:path";import{spawnSync as A}from"node:child_process";import v from"chalk";import{getDefaultWeChatHelperRuntimeRoot as D,readWeChatChannelHelperRuntimePackageManifest as w,resolveWeChatChannelHelperAsset as C,SHENNIAN_HELPER_RUNTIME_DIR_ENV as E,WECHAT_CHANNEL_HELPER_DIR_ENV as O}from"../channels/wechat-channel/helper-assets.js";import{downloadOfficialHelperRuntimePackage as $}from"./helper-runtime-official-download.js";import{compareVersions as x,getCurrentVersion as H}from"../upgrade/engine.js";import{SerialWeChatAutomationLane as U}from"../channels/wechat-channel/automation-lane.js";const u="helper-runtime-package.json";function ce(e){const r=e.command("runtime").description("Install, repair, and inspect Shennian local runtimes");r.command("status").description("Print local Shennian runtime status").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(o=>{const n=V({platform:o.platform});P(n,!!o.json),n.ok||(process.exitCode=1)}),r.command("install").description("Install a Shennian runtime component").argument("<component>","Runtime component, currently only: helper").option("--source <path>","Helper runtime package/source directory").option("--channel <channel>","Official Helper runtime channel: stable or next").option("--manifest-url <url>","Official Helper runtime manifest URL override").option("--no-official-download","Do not fall back to Shennian official Helper runtime download").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(async(o,n)=>{const a=o==="helper"?await b({source:n.source,platform:n.platform,officialDownload:n.officialDownload===!1?!1:{channel:n.channel,manifestUrl:n.manifestUrl}}):M(o);P(a,!!n.json),a.ok||(process.exitCode=1)}),r.command("repair").description("Repair a Shennian runtime component").argument("[component]","Runtime component, currently only: helper","helper").option("--source <path>","Helper runtime package/source directory").option("--channel <channel>","Official Helper runtime channel: stable or next").option("--manifest-url <url>","Official Helper runtime manifest URL override").option("--no-official-download","Do not fall back to Shennian official Helper runtime download").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(async(o,n)=>{const a=o==="helper"?await b({source:n.source,platform:n.platform,force:!0,officialDownload:n.officialDownload===!1?!1:{channel:n.channel,manifestUrl:n.manifestUrl}}):M(o);P(a,!!n.json),a.ok||(process.exitCode=1)})}function V(e={}){const r=k(e.platform);if(!r)return{ok:!1,installed:!1,repairable:!1,reasonCode:"unsupported_platform",message:"Shennian Helper runtime is only available on macOS and Windows.",repairSuggestion:l("unsupported_platform")};const o=e.env??process.env,n=e.homedir,a=C({platform:r,env:o,homedir:n,includeInstalledDesktop:!1}),t=D({platform:r,env:o,homedir:n});if(!a.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:K(a.reasonCode),reasonCode:a.reasonCode,message:a.message,runtimeRoot:t,repairSuggestion:l(a.reasonCode),checkedSources:_({platform:r,env:o,homedir:n})};const s=w({platform:r,helperDir:a.helperDir});if(!s.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:s.reasonCode,message:s.message,helperDir:a.helperDir,helperPath:a.helperPath,helperVersion:a.version,runtimeVersion:a.version,protocolVersion:a.manifest.protocolVersion,runtimeRoot:t,repairSuggestion:l(s.reasonCode),checkedSources:_({platform:r,env:o,homedir:n})};const c=R({platform:r,packageManifest:s.manifest,currentCliVersion:H(),helperVersion:a.version,protocolVersion:a.manifest.protocolVersion});return c.ok?{ok:!0,platform:r,targetPlatform:r,installed:!0,repairable:!1,message:`Shennian Helper runtime is installed: ${a.helperDir}`,helperDir:a.helperDir,helperPath:a.helperPath,helperVersion:a.version,runtimeVersion:a.version,protocolVersion:a.manifest.protocolVersion,minCliVersion:s.manifest.minCliVersion,sha256:s.manifest.sha256,packageManifestPath:s.manifestPath,packageKind:s.manifest.packageKind,installTarget:s.manifest.installTarget,signature:s.manifest.signature,runtimeRoot:t}:{ok:!1,platform:r,targetPlatform:r,installed:!0,repairable:c.repairable,reasonCode:c.reasonCode,message:c.message,helperDir:a.helperDir,helperPath:a.helperPath,helperVersion:a.version,runtimeVersion:a.version,protocolVersion:a.manifest.protocolVersion,minCliVersion:s.manifest.minCliVersion,sha256:s.manifest.sha256,packageManifestPath:s.manifestPath,packageKind:s.manifest.packageKind,installTarget:s.manifest.installTarget,signature:s.manifest.signature,runtimeRoot:t,repairSuggestion:l(c.reasonCode,s.manifest.minCliVersion),checkedSources:_({platform:r,env:o,homedir:n})}}function j(e={}){const r=k(e.platform);if(!r)return{ok:!1,reasonCode:"unsupported_platform",message:"Shennian Helper runtime is only available on macOS and Windows.",repairable:!1,repairSuggestion:l("unsupported_platform")};const o=e.env??process.env,n=F(e.io),a=e.homedir??n.homedir(),t=V({platform:r,env:o,homedir:a});if(t.ok&&!e.force&&t.helperDir&&t.helperPath&&t.helperVersion&&t.protocolVersion)return{ok:!0,action:"already_installed",platform:r,targetPlatform:r,installed:!0,repairable:!1,targetDir:q(r,t.helperDir),helperDir:t.helperDir,helperPath:t.helperPath,helperVersion:t.helperVersion,runtimeVersion:t.helperVersion,protocolVersion:t.protocolVersion,minCliVersion:t.minCliVersion,sha256:t.sha256,packageManifestPath:t.packageManifestPath,packageKind:t.packageKind,installTarget:t.installTarget,signature:t.signature,message:`Shennian Helper runtime is already installed: ${t.helperDir}`};const s=e.source?[i.resolve(e.source)]:_({platform:r,env:o,homedir:a}),c=s.map(S=>z({candidate:S,platform:r,io:n})).find(S=>S!=null);if(!c)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:"helper_runtime_source_missing",message:"No installable Shennian Helper runtime package was found. Open Shennian Desktop, pass --source, or download the official helper package.",repairSuggestion:l("helper_runtime_source_missing"),checkedSources:s};const h=w({platform:r,manifestPath:c.packageManifestPath});if(!h.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:h.reasonCode,message:h.message,repairSuggestion:l(h.reasonCode),checkedSources:s};const g=R({platform:r,packageManifest:h.manifest,currentCliVersion:H()});if(!g.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:g.repairable,reasonCode:g.reasonCode,message:g.message,repairSuggestion:l(g.reasonCode,h.manifest.minCliVersion),checkedSources:s};const d=J({platform:r,env:o,homedir:a,packageKind:c.packageKind});if(!d)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!1,reasonCode:"helper_runtime_target_unavailable",message:"Unable to determine Helper runtime install target for this platform.",repairSuggestion:l("helper_runtime_target_unavailable"),checkedSources:s};X({platform:r,sourceDir:c.sourceDir,targetDir:d,io:n}),Y({sourceManifestPath:c.packageManifestPath,targetManifestPath:c.packageManifestPathInTarget(d),io:n});const m=C({platform:r,baseDir:c.manifestDirInTarget(d),verifyIntegrity:!0});if(!m.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:m.reasonCode,message:`Installed Helper runtime could not be verified: ${m.message}`,repairSuggestion:l(m.reasonCode),checkedSources:s};Z(r,m.helperPath,n);const p=w({platform:r,helperDir:m.helperDir});return p.ok?{ok:!0,action:"installed",platform:r,targetPlatform:r,installed:!0,repairable:!1,targetDir:d,helperDir:m.helperDir,helperPath:m.helperPath,helperVersion:m.version,runtimeVersion:m.version,protocolVersion:m.manifest.protocolVersion,minCliVersion:p.manifest.minCliVersion,sha256:p.manifest.sha256,packageManifestPath:p.manifestPath,packageKind:p.manifest.packageKind,installTarget:p.manifest.installTarget,signature:p.manifest.signature,message:`Installed Shennian Helper runtime to ${d}`}:{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:p.reasonCode,message:`Installed Helper runtime package manifest could not be verified: ${p.message}`,repairSuggestion:l(p.reasonCode),checkedSources:s}}async function b(e={}){const r=new U({lockPath:T(e),owner:"helper-runtime-install"});try{return await r.run("helper-runtime-install",()=>N(e))}catch(o){if(L(o)){const n=k(e.platform)??void 0;return{ok:!1,platform:n,targetPlatform:n,installed:!1,repairable:!0,reasonCode:"wechat_automation_busy",message:o instanceof Error?o.message:String(o),repairSuggestion:"Wait for the current WeChat automation run to finish, then retry Helper install or repair."}}throw o}}function T(e){const r=e.env?.SHENNIAN_HOME?.trim()||process.env.SHENNIAN_HOME?.trim(),o=r?i.resolve(r):i.join(e.homedir??y.homedir(),".shennian");return i.join(o,"runtime","wechat-automation.lock")}function L(e){return e instanceof Error?e.message.includes("wechat_automation_busy"):String(e).includes("wechat_automation_busy")}async function N(e={}){const r=j(e);if(r.ok||e.source||e.officialDownload===!1||!W(r.reasonCode))return r.ok?{...r,source:r.source??"local"}:r;const o=k(e.platform);if(!o)return r;const n=await $({...e.officialDownload,platform:o,env:e.env??process.env});if(!n.ok)return{ok:!1,platform:o,targetPlatform:o,installed:!1,repairable:!0,reasonCode:n.reasonCode,message:n.message,repairSuggestion:l(n.reasonCode),checkedSources:r.checkedSources,officialDownload:{manifestUrl:n.manifestUrl,artifactUrl:n.artifactUrl}};try{const a=j({...e,source:n.sourceDir,platform:o});return a.ok?{...a,source:"official-download",officialDownload:{channel:n.channel,manifestUrl:n.manifestUrl,artifactUrl:n.artifactUrl,artifactSha256:n.artifactSha256,artifactSizeBytes:n.artifactSizeBytes,arch:n.arch}}:{...a,reasonCode:a.reasonCode==="helper_runtime_source_missing"?"helper_runtime_artifact_invalid":a.reasonCode,message:a.reasonCode==="helper_runtime_source_missing"?"Official Helper runtime artifact did not contain an installable runtime package.":a.message,repairSuggestion:l(a.reasonCode==="helper_runtime_source_missing"?"helper_runtime_artifact_invalid":a.reasonCode),officialDownload:{manifestUrl:n.manifestUrl,artifactUrl:n.artifactUrl}}}finally{n.cleanup()}}function W(e){return e==="helper_runtime_source_missing"||e==="helper_runtime_package_manifest_missing"||e==="helper_runtime_package_manifest_invalid"}function k(e){const r=e??process.platform;return r==="darwin"||r==="win32"?r:null}function R(e){return e.packageManifest.platform!==e.platform?{ok:!1,reasonCode:"helper_runtime_platform_mismatch",message:`Helper runtime package platform ${e.packageManifest.platform} does not match ${e.platform}.`,repairable:!0}:e.helperVersion&&e.packageManifest.helperVersion!==e.helperVersion?{ok:!1,reasonCode:"helper_runtime_version_mismatch",message:`Helper runtime package version ${e.packageManifest.helperVersion} does not match installed helper ${e.helperVersion}.`,repairable:!0}:e.protocolVersion&&e.packageManifest.protocolVersion!==e.protocolVersion?{ok:!1,reasonCode:"helper_runtime_protocol_mismatch",message:`Helper runtime package protocol ${e.packageManifest.protocolVersion} does not match installed helper protocol ${e.protocolVersion}.`,repairable:!0}:x(e.currentCliVersion,e.packageManifest.minCliVersion)!=="none"?{ok:!1,reasonCode:"helper_runtime_cli_too_old",message:`Shennian CLI ${e.currentCliVersion} is older than required ${e.packageManifest.minCliVersion}.`,repairable:!1}:{ok:!0}}function K(e){return e!=="unsupported_platform"&&e!=="helper_runtime_cli_too_old"}function l(e,r){if(e==="unsupported_platform")return"Use Shennian Helper runtime on macOS or Windows.";if(e==="unsupported_runtime_component")return"Use `shennian runtime install helper` or `shennian runtime repair helper`.";if(e==="helper_runtime_cli_too_old")return`Upgrade Shennian CLI${r?` to ${r} or newer`:""}, then retry.`;if(e==="helper_runtime_target_unavailable")return"Set the current user home/LOCALAPPDATA correctly, then retry.";if(e==="helper_runtime_required")return"Open Shennian Desktop or use the \u4F7F\u7528\u5FAE\u4FE1 page to install Helper.";if(e==="helper_runtime_source_missing")return"Open Shennian Desktop, retry from the \u4F7F\u7528\u5FAE\u4FE1 page, or pass `--source <path>`.";if(e==="helper_runtime_manifest_unavailable")return"Check network access to Shennian official downloads, then retry from the \u4F7F\u7528\u5FAE\u4FE1 page.";if(e==="helper_runtime_artifact_unavailable")return"Upgrade Shennian CLI or wait for this platform Helper runtime to be published.";if(e==="helper_runtime_download_failed")return"Check network access to Shennian official downloads, then retry.";if(e==="helper_runtime_artifact_integrity_mismatch")return"Retry the official Helper runtime download; the downloaded package did not match the published hash.";if(e==="helper_runtime_artifact_invalid")return"Retry after Shennian republishes a valid Helper runtime package for this platform.";if(e==="helper_runtime_package_manifest_missing"||e==="helper_runtime_package_manifest_invalid")return"Reinstall or repair the official Shennian Helper runtime package.";if(e==="helper_runtime_platform_mismatch")return"Install the Helper runtime package for this platform.";if(e==="helper_runtime_version_mismatch"||e==="helper_runtime_protocol_mismatch")return"Run `shennian runtime repair helper` with the matching official Helper runtime package.";if(e==="manifest_missing"||e==="helper_missing"||e==="integrity_mismatch"||e==="helper_not_executable")return"Run `shennian runtime repair helper` or reinstall Shennian Desktop."}function M(e){return{ok:!1,repairable:!1,reasonCode:"unsupported_runtime_component",message:`Unsupported runtime component: ${e}. Supported component: helper.`,repairSuggestion:l("unsupported_runtime_component")}}function F(e){return{existsSync:e?.existsSync??f.existsSync,readFileSync:e?.readFileSync??f.readFileSync,mkdirSync:e?.mkdirSync??f.mkdirSync,rmSync:e?.rmSync??f.rmSync,cpSync:e?.cpSync??f.cpSync,renameSync:e?.renameSync??f.renameSync,chmodSync:e?.chmodSync??f.chmodSync,homedir:e?.homedir??y.homedir,stopWindowsHelperProcesses:e?.stopWindowsHelperProcesses??Q}}function z(e){const r=i.resolve(e.candidate);return e.io.existsSync(r)?e.platform==="darwin"?B(r,e.io):G(r,e.io):null}function B(e,r){const o=e.endsWith(".app")?e:i.join(e,"Shennian Helper.app");if(r.existsSync(i.join(o,"Contents","Resources","wechat-channel","macos","manifest.json")))return{sourceDir:o,packageKind:"macos-app",packageManifestPath:I(r,[i.join(o,"Contents","Resources","helper-runtime-package.json"),i.join(e,u)]),packageManifestPathInTarget:a=>i.join(a,"Contents","Resources",u),manifestDirInTarget:a=>i.join(a,"Contents","Resources","wechat-channel","macos")};const n=r.existsSync(i.join(e,"manifest.json"))?e:i.join(e,"wechat-channel","macos");return r.existsSync(i.join(n,"manifest.json"))?{sourceDir:n,packageKind:"asset-dir",packageManifestPath:i.join(n,"helper-runtime-package.json"),packageManifestPathInTarget:a=>i.join(a,u),manifestDirInTarget:a=>a}:null}function G(e,r){const o=i.basename(e).toLowerCase()==="shennian helper"?e:i.join(e,"Shennian Helper");if(r.existsSync(i.join(o,"resources","wechat-channel","windows","manifest.json")))return{sourceDir:o,packageKind:"windows-runtime",packageManifestPath:I(r,[i.join(o,"resources",u),i.join(e,u)]),packageManifestPathInTarget:a=>i.join(a,"resources",u),manifestDirInTarget:a=>i.join(a,"resources","wechat-channel","windows")};const n=r.existsSync(i.join(e,"manifest.json"))?e:i.join(e,"wechat-channel","windows");return r.existsSync(i.join(n,"manifest.json"))?{sourceDir:n,packageKind:"asset-dir",packageManifestPath:i.join(n,"helper-runtime-package.json"),packageManifestPathInTarget:a=>i.join(a,u),manifestDirInTarget:a=>a}:null}function J(e){const r=D(e);if(!r)return null;if(e.platform==="darwin")return e.packageKind==="macos-app"?i.join(r,"Shennian Helper.app"):i.join(r,"wechat-channel","macos");if(e.packageKind==="windows-runtime"){const o=e.env.LOCALAPPDATA||(e.homedir?i.join(e.homedir,"AppData","Local"):"");return o?i.join(o,"Programs","Shennian Helper"):null}return i.join(r,"wechat-channel","windows")}function q(e,r){const o=i.resolve(r);if(e==="darwin"){const a=`${i.sep}Contents${i.sep}Resources${i.sep}wechat-channel${i.sep}macos`;return o.endsWith(a)?o.slice(0,-a.length):o}const n=`${i.sep}resources${i.sep}wechat-channel${i.sep}windows`;return o.endsWith(n)?o.slice(0,-n.length):o}function X(e){const r=i.resolve(e.sourceDir),o=i.resolve(e.targetDir);if(r===o)return;const n=`${o}.tmp-${process.pid}-${Date.now()}`;e.io.rmSync(n,{recursive:!0,force:!0}),e.io.mkdirSync(i.dirname(o),{recursive:!0}),e.io.cpSync(r,n,{recursive:!0}),e.platform==="win32"&&e.io.stopWindowsHelperProcesses(),e.io.rmSync(o,{recursive:!0,force:!0}),e.io.renameSync(n,o)}function Q(){if(process.platform!=="win32")return;const e=A("taskkill",["/IM","shennian-wechat-channel-helper.exe","/T","/F"],{stdio:"ignore",windowsHide:!0});if(e.status!==0&&e.status!==128)throw new Error(`taskkill shennian-wechat-channel-helper.exe exited ${e.status}`)}function Y(e){const r=i.resolve(e.sourceManifestPath),o=i.resolve(e.targetManifestPath);!e.io.existsSync(r)||r===o||(e.io.mkdirSync(i.dirname(o),{recursive:!0}),e.io.cpSync(r,o))}function I(e,r){return r.find(o=>e.existsSync(o))??r[0]}function Z(e,r,o){if(e==="darwin")try{const n=f.statSync(r);(n.mode&73)===0&&o.chmodSync(r,n.mode|493)}catch{}}function _(e){const r=e.homedir||(e.platform==="win32"?e.env.USERPROFILE:e.env.HOME),o=[],n=a=>{if(!a)return;const t=i.resolve(a);o.includes(t)||o.push(t)};if(n(e.env.SHENNIAN_HELPER_PACKAGE_DIR),n(e.env[O]),n(e.env[E]),e.platform==="darwin")r&&(n(i.join(r,"Applications","Shennian.app","Contents","Resources")),n(i.join(r,"Applications","Shennian Helper.app"))),n(i.join("/Applications","Shennian.app","Contents","Resources")),n(i.join("/Applications","Shennian Helper.app"));else{const a=e.env.LOCALAPPDATA||(r?i.join(r,"AppData","Local"):""),t=e.env.ProgramFiles||e.env.PROGRAMFILES,s=e.env["ProgramFiles(x86)"]||e.env["PROGRAMFILES(X86)"];a&&(n(i.join(a,"Programs","Shennian","resources")),n(i.join(a,"Programs","Shennian Helper"))),t&&(n(i.join(t,"Shennian","resources")),n(i.join(t,"Shennian Helper"))),s&&(n(i.join(s,"Shennian","resources")),n(i.join(s,"Shennian Helper")))}return n(i.resolve(process.cwd(),"packages/helper-runtime/dist",e.platform==="darwin"?"macos":"windows")),n(i.resolve(process.cwd(),"packages/helper-runtime/wechat-channel",e.platform==="darwin"?"macos":"windows")),o}function P(e,r){if(r){console.log(JSON.stringify(e,null,2));return}if(e.ok){console.log(v.green(`\u2713 ${e.message}`)),"helperVersion"in e&&console.log(`version=${e.helperVersion} protocol=${e.protocolVersion}`),"helperPath"in e&&console.log(e.helperPath);return}console.error(v.red(`\u2717 ${e.reasonCode}`)),console.error(e.message)}export{V as getHelperRuntimeStatus,j as installHelperRuntime,b as installHelperRuntimeWithOfficialDownload,ce as registerRuntimeCommand};
@@ -1,2 +1,3 @@
1
1
  import type { Command } from 'commander';
2
2
  export declare function registerWeChatCommand(program: Command): void;
3
+ export declare function resolveWeChatCommandTimeout(value?: string): number;
@@ -1 +1 @@
1
- import{readExternalAttachment as p}from"../external-attachments.js";import{runWeChatDoctor as f}from"./doctor.js";import{runWeChatReadLatest as w,runWeChatSend as g}from"./operations.js";import{printReadLatestResult as k,printToolError as c,printToolResult as v}from"./output.js";import{clampPositiveInteger as m}from"./utils.js";function E(e){const o=e.command("wechat").description("Use WeChat through Shennian local runtime");o.option("--conversation <name>","WeChat conversation/group name"),o.command("doctor").description("Check local Shennian, pairing, entitlement, credits, and WeChat runtime readiness").option("--json","Output JSON",!1).action(async r=>{const t=await f();if(r.json)console.log(JSON.stringify(t,null,2));else for(const a of t.checks){const n=a.ok?"\u2713":a.status==="warning"?"!":"\u2717";console.log(`${n} ${a.id}: ${a.message}`)}t.ok||(process.exitCode=1)}),o.command("write").aliases(["send"]).description("Write a message to a local WeChat conversation through Shennian").argument("[text...]","Message text").option("--conversation <name>","WeChat conversation/group name").option("--text <text>","Message text").option("--file <path>","File attachment path").option("--image <path>","Image attachment path").option("--video <path>","Video attachment path").option("--session-id <id>","Bind to an existing Shennian chat/session; omit for direct local WeChat tool mode").option("--work-dir <path>","Local tool work dir; defaults under ~/.shennian/wechat-cli").option("--idempotency-key <key>","Idempotency key").option("--dry-run","Validate the request and output the planned write without sending",!1).option("--trace-id <id>","Trace id included in JSON output").option("--timeout <ms>","Manager IPC request timeout in milliseconds").option("--transport <mode>","Execution transport: auto, daemon, or direct","auto").option("--format <format>","Output format: json or text","text").option("--json","Output JSON",!1).action(async(r,t,a)=>{let n;try{n=u(a,t.conversation);const i=t.text??r.join(" "),s=C(t),h=await g({conversation:n,text:i,attachment:s,sessionId:t.sessionId,workDir:t.workDir,idempotencyKey:t.idempotencyKey,dryRun:t.dryRun,traceId:t.traceId,timeoutMs:t.timeout?m(t.timeout,24e4):void 0,transport:l(t.transport)});v(h,t.json||t.format==="json")}catch(i){c("write",n||t.conversation,i,t.json||t.format==="json")}}),o.command("read").aliases(["read-latest","read_latest","latest"]).description("Read recent messages from a local WeChat conversation through Shennian").argument("[count]","Number of latest messages","10").option("--conversation <name>","WeChat conversation/group name").option("--limit <n>","Number of latest messages").option("--format <format>","Output format: markdown or json","markdown").option("--download <mode>","Attachment download mode: auto or never","auto").option("--trace-id <id>","Trace id included in JSON output").option("--timeout <ms>","Manager IPC request timeout in milliseconds").option("--session-id <id>","Bind to an existing Shennian chat/session; omit for direct local WeChat tool mode").option("--work-dir <path>","Local tool work dir; defaults under ~/.shennian/wechat-cli").option("--transport <mode>","Execution transport: auto, daemon, or direct","auto").option("--json","Output JSON",!1).action(async(r,t,a)=>{let n,i="markdown";try{n=u(a,t.conversation),i=y(t.json?"json":t.format);const s=await w({conversation:n,limit:m(t.limit??r,10),download:j(t.download),traceId:t.traceId,timeoutMs:t.timeout?m(t.timeout,24e4):void 0,sessionId:t.sessionId,workDir:t.workDir,transport:l(t.transport)});k(s,i)}catch(s){c("read",n||t.conversation,s,t.json||i==="json")}})}function u(e,o){const d=e.parent?.opts()??{},r=o||d.conversation||"";if(!r.trim())throw new Error("--conversation is required");return r}function C(e){const o=[e.file?{kind:"file",path:e.file}:null,e.image?{kind:"image",path:e.image}:null,e.video?{kind:"video",path:e.video}:null].filter(d=>!!d);if(o.length>1)throw new Error("Pass only one of --file, --image, or --video");if(o.length!==0)return p(o[0].path,o[0].kind)}function y(e){const o=String(e||"markdown").trim().toLowerCase();if(o==="json")return"json";if(o==="markdown"||o==="md"||o==="text")return"markdown";throw new Error(`Unsupported --format: ${e}. Use markdown or json.`)}function j(e){const o=String(e||"auto").trim().toLowerCase();if(o==="auto"||o==="never")return o;throw new Error(`Unsupported --download: ${e}. Use auto or never.`)}function l(e){const o=String(e||"auto").trim().toLowerCase();if(o==="auto"||o==="daemon"||o==="direct")return o;throw new Error(`Unsupported --transport: ${e}. Use auto, daemon, or direct.`)}export{E as registerWeChatCommand};
1
+ import{readExternalAttachment as w}from"../external-attachments.js";import{runWeChatDoctor as g}from"./doctor.js";import{runWeChatReadLatest as C,runWeChatSend as k}from"./operations.js";import{printReadLatestResult as v,printToolError as m,printToolResult as y}from"./output.js";import{clampPositiveInteger as u}from"./utils.js";const c=5*6e4,j=10*6e4;function _(e){const o=e.command("wechat").description("Use WeChat through Shennian local runtime");o.option("--conversation <name>","WeChat conversation/group name"),o.command("doctor").description("Check local Shennian, pairing, entitlement, credits, and WeChat runtime readiness").option("--json","Output JSON",!1).action(async r=>{const t=await g();if(r.json)console.log(JSON.stringify(t,null,2));else for(const a of t.checks){const n=a.ok?"\u2713":a.status==="warning"?"!":"\u2717";console.log(`${n} ${a.id}: ${a.message}`)}t.ok||(process.exitCode=1)}),o.command("write").aliases(["send"]).description("Write a message to a local WeChat conversation through Shennian").argument("[text...]","Message text").option("--conversation <name>","WeChat conversation/group name").option("--text <text>","Message text").option("--file <path>","File attachment path").option("--image <path>","Image attachment path").option("--video <path>","Video attachment path").option("--session-id <id>","Bind to an existing Shennian chat/session; omit for direct local WeChat tool mode").option("--work-dir <path>","Local tool work dir; defaults under ~/.shennian/wechat-cli").option("--idempotency-key <key>","Idempotency key").option("--dry-run","Validate the request and output the planned write without sending",!1).option("--trace-id <id>","Trace id included in JSON output").option("--timeout <ms>",`Manager IPC request timeout in milliseconds. Default: ${c}`).option("--transport <mode>","Execution transport: auto, daemon, or direct","auto").option("--format <format>","Output format: json or text","text").option("--json","Output JSON",!1).action(async(r,t,a)=>{let n;try{n=l(a,t.conversation);const i=t.text??r.join(" "),s=x(t),f=await k({conversation:n,text:i,attachment:s,sessionId:t.sessionId,workDir:t.workDir,idempotencyKey:t.idempotencyKey,dryRun:t.dryRun,traceId:t.traceId,timeoutMs:p(t.timeout),transport:h(t.transport)});y(f,t.json||t.format==="json")}catch(i){m("write",n||t.conversation,i,t.json||t.format==="json")}}),o.command("read").aliases(["read-latest","read_latest","latest"]).description("Read recent messages from a local WeChat conversation through Shennian").argument("[count]","Number of latest messages","10").option("--conversation <name>","WeChat conversation/group name").option("--limit <n>","Number of latest messages").option("--format <format>","Output format: markdown or json","markdown").option("--download <mode>","Attachment download mode: auto or never","auto").option("--trace-id <id>","Trace id included in JSON output").option("--timeout <ms>",`Manager IPC request timeout in milliseconds. Default: ${c}`).option("--session-id <id>","Bind to an existing Shennian chat/session; omit for direct local WeChat tool mode").option("--work-dir <path>","Local tool work dir; defaults under ~/.shennian/wechat-cli").option("--transport <mode>","Execution transport: auto, daemon, or direct","auto").option("--json","Output JSON",!1).action(async(r,t,a)=>{let n,i="markdown";try{n=l(a,t.conversation),i=S(t.json?"json":t.format);const s=await C({conversation:n,limit:u(t.limit??r,10),download:W(t.download),traceId:t.traceId,timeoutMs:p(t.timeout),sessionId:t.sessionId,workDir:t.workDir,transport:h(t.transport)});v(s,i)}catch(s){m("read",n||t.conversation,s,t.json||i==="json")}})}function l(e,o){const d=e.parent?.opts()??{},r=o||d.conversation||"";if(!r.trim())throw new Error("--conversation is required");return r}function x(e){const o=[e.file?{kind:"file",path:e.file}:null,e.image?{kind:"image",path:e.image}:null,e.video?{kind:"video",path:e.video}:null].filter(d=>!!d);if(o.length>1)throw new Error("Pass only one of --file, --image, or --video");if(o.length!==0)return w(o[0].path,o[0].kind)}function S(e){const o=String(e||"markdown").trim().toLowerCase();if(o==="json")return"json";if(o==="markdown"||o==="md"||o==="text")return"markdown";throw new Error(`Unsupported --format: ${e}. Use markdown or json.`)}function W(e){const o=String(e||"auto").trim().toLowerCase();if(o==="auto"||o==="never")return o;throw new Error(`Unsupported --download: ${e}. Use auto or never.`)}function h(e){const o=String(e||"auto").trim().toLowerCase();if(o==="auto"||o==="daemon"||o==="direct")return o;throw new Error(`Unsupported --transport: ${e}. Use auto, daemon, or direct.`)}function p(e){return e?u(e,j):c}export{_ as registerWeChatCommand,p as resolveWeChatCommandTimeout};