shennian 0.2.130 → 0.2.131

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.
@@ -331,8 +331,8 @@
331
331
  },
332
332
  {
333
333
  "file": "src/channels/wechat-rpa.js",
334
- "beforeBytes": 58638,
335
- "afterBytes": 29885
334
+ "beforeBytes": 58837,
335
+ "afterBytes": 30003
336
336
  },
337
337
  {
338
338
  "file": "src/channels/wecom.js",
@@ -1,4 +1,4 @@
1
- import I from"node:crypto";import m from"node:fs";import f from"node:path";import{fileURLToPath as ee,pathToFileURL as te}from"node:url";import{ChannelSecretRegistry as re}from"./secret-registry.js";import{resolveShennianPath as ne}from"../config/index.js";import{probeMacWeChat as q,observedMessageFromProbe as ae}from"./wechat-rpa/macos.js";import{runMacWeChatRpaFlow as F}from"./wechat-rpa/macos-flow.js";import{normalizeWeChatRpaMessage as se,WeChatRpaDeduper as K,weChatRpaConversationId as x}from"./wechat-rpa/normalizer.js";import{cancelWeChatRpaProductOutboundReply as ie,countPendingWeChatRpaProductOutbound as oe,createWeChatRpaProductSourceConnection as ue,enqueueWeChatRpaProductOutboundReply as de,isWeChatRpaProductSource as y,listWeChatRpaProductRecentMessages as ce,listPendingWeChatRpaProductOutbound as le,weChatChannelConversationId as N}from"./wechat-rpa/product-channel.js";import{createWeChatChannelApiClient as H}from"./wechat-channel/client.js";const pe=5e3,A=5,fe=8,S=Number(process.env.SHENNIAN_WECHAT_RPA_OUTBOUND_ATTACHMENT_MAX_BYTES||20*1024*1024),W=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 me(t){return P(t,A,1,50)>=fe?"scroll-read":"read-latest"}class it{onMessage;options;type="wechat-rpa";secrets=new re;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,M(n,e),O(n,e),this.seedConfiguredConversations(n,r),y(r.source)){const a=await this.stopProductWhenControlPlaneDisabled(n,r);if(a||await this.ensureProductConnection(n,r),n.timer)return;!r.deferInitialPoll&&!a&&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(i=>{console.error(`[wechat-rpa] autonomous poll failed id=${e.id}: ${i instanceof Error?i.message:String(i)}`)})},n.product?.runtime.policy.pollIntervalMs??j(r.pollIntervalMs)),n.timer.unref();return}n.timer||(r.deferInitialPoll||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(()=>{})},j(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,M(n,e),O(n,e),this.seedConfiguredConversations(n,r),y(r.source)?this.enqueueOperation(n,async()=>{const a=this.readSecret(n.config);if(await this.stopProductWhenControlPlaneDisabled(n,a))return[];const i=await this.pollProductOnce(n,a,{forceObserve:!0}),s=n.product;if(!s)return i;const u=ce({config:n.config,product:s,limit:a.recentLimit});return Xe(u,i)}):this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config)))}async cancelOutbound(e,r){const n=this.readSecret(e);return y(n.source)?ie({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,M(a,e),O(a,e),this.seedConfiguredConversations(a,n),y(n.source)){const d=this.resolveConversationName(e,n,r.conversationId);if(!d)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");if(await this.stopProductWhenControlPlaneDisabled(a,n))throw new Error("WeChat RPA channel is disabled");return de({config:a.config,secret:n,reply:{...r,text:h},conversationName:d}),a.runtimeState="retrying",w(a,{status:"sent",runId:a.lastRunId??null,summary:`queued ${d}`}),this.enqueueOperation(a,()=>this.pollProductOnce(a,this.readSecret(a.config))).catch(v=>{a.lastError=v instanceof Error?v.message:String(v)}),{status:"queued"}}if(!Ee(n.source))throw new Error("WeChat RPA reply requires source=macos-flow, windows-visual-flow, or wechat-rpa-lab");const i=this.resolveConversationName(e,n,r.conversationId);if(!i)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const s=Te(e,r);if(a.completedPendingReplyKeys.has(s))return{status:"sent"};const u=a.manualReviewReplies.get(s);if(u)return{status:"manual-review",reason:u.reason};const o=r.attachment?await Be(e.workDir,r.attachment):void 0,c=r.text.trim();if(!c&&!o)throw new Error("Reply text or attachment is required");if(Z(a,n)){const d="WeChat RPA is cooling down after repeated user activity";return a.runtimeState="cooldown",T(a,{key:s,conversationId:r.conversationId,conversationName:i,text:c,attachmentPath:o,reason:d}),{status:"queued",reason:d}}let l;try{a.runtimeState="syncing",l=await this.enqueueOperation(a,()=>(a.lastRunAt=new Date().toISOString(),V(e,n,i,c,o,s)))}catch(d){throw a.lastError=d instanceof Error?d.message:String(d),w(a,{status:b(a.lastError),runId:a.lastRunId??null,summary:a.lastError}),d}$(a,l);const p=Y(l,{text:c,attachmentPath:o});if(p.status==="queued"){const d=p.reason;return T(a,{key:s,conversationId:r.conversationId,conversationName:i,text:p.text,attachmentPath:p.attachmentPath,reason:d,skipText:p.skipText}),a.lastError=d,{status:"queued",reason:d}}if(l.interrupted){const d=l.error||"WeChat RPA send was interrupted by user activity";return _(a,l,d),T(a,{key:s,conversationId:r.conversationId,conversationName:i,text:p.text,attachmentPath:o,reason:d,skipText:p.skipText}),{status:"queued",reason:d}}if(!l.ok){const d=l.error||"WeChat RPA send validator failed; manual review required before retry";return Q(a,s,d),{status:"manual-review",reason:d}}return w(a,{status:"sent",runId:a.lastRunId??null,summary:l.rpaTraceSummary||`sent ${i}`}),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&&m.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(!g(r).length)return{ok:!1,message:"WeChat RPA macOS flow requires at least one group"}}if(y(r.source))return Se(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(!g(r).length)return{ok:!1,message:"WeChat RPA Lab source requires at least one group"};try{J(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 ve(r,process.platform);const n=await q();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=g(r)[0];if(!n)throw new Error("WeChat RPA channel has no configured groups");return y(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+oe({config:e}),wechatRpaPendingReplies:Me(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(Z(e,r))return e.runtimeState="cooldown",[];if(await this.drainPendingReplies(e,r))return[];let i=!1;e.runtimeState="syncing";const s=await this.readObservedMessages(e.config,r,u=>{$(e,u),u.interrupted&&(i=!0,_(e,u,u.error||"WeChat RPA poll was interrupted by user activity"))});e.lastError=null;for(const u of s){const o=se(u,{selfNicknames:U(r)});if(!o||!e.deduper.accept(o.messageId))continue;De(e),e.conversations.set(o.conversationId,o.conversationName),e.lastMessageAt=o.receivedAt;const c={type:"external.message",managerSessionId:e.config.managerSessionId,channelId:e.config.id,channelType:"wechat-rpa",conversationId:o.conversationId,conversationName:o.conversationName,messageId:o.messageId,sender:o.sender,text:o.text,attachments:o.attachments,receivedAt:o.receivedAt,isMentioned:o.isMentioned,replyTarget:"",rawRef:o.rawRef};n.push(this.onMessage?.(c)??c),w(e,{status:"received",runId:e.lastRunId??null,summary:`received ${o.conversationName}${o.sender.name?` from ${o.sender.name}`:""}: ${L(o.text||o.attachments[0]?.name||"attachment")}`})}return i||C(e),n}catch(a){throw e.lastError=a instanceof Error?a.message:String(a),w(e,{status:b(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),a}}async pollProductOnce(e,r,n){if(e.stopped)return[];if(!y(r.source))return[];if(await this.stopProductWhenControlPlaneDisabled(e,r))return[];const a=await this.ensureProductConnection(e,r);await B(a).catch(()=>null);const i=a.emitted.length;e.lastRunAt=new Date().toISOString(),e.runtimeState="syncing";try{await a.runner.tick(n);const s=a.emitted.slice(i);e.lastError=null;for(const u of s)e.conversations.set(u.conversationId,u.conversationName||""),e.lastMessageAt=u.receivedAt,w(e,{status:"received",runId:e.lastRunId??null,summary:`received ${u.conversationName||u.conversationId}${u.sender.name?` from ${u.sender.name}`:""}: ${L(u.text||u.attachments[0]?.name||"attachment")}`});return C(e),s}catch(s){throw e.lastError=s instanceof Error?s.message:String(s),e.runtimeState=b(e.lastError)==="blocked"?"interrupted":"idle_waiting",w(e,{status:b(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),s}}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=ue({config:e.config,secret:r,createRunner:this.options.createProductRunner,onMessage:this.onMessage}),await B(e.product).catch(()=>{}),e.productConfigKey=n,e.lastTracePath=e.product.helperTracePath),e.product}async stopProductWhenControlPlaneDisabled(e,r){if(!y(r.source))return!1;let n=null;try{n=await we(e.config,r)}catch(a){return e.productDisabled?(e.lastRunAt=new Date().toISOString(),e.runtimeState="idle_waiting",e.lastError=a instanceof Error?a.message:String(a),!0):!1}if(n?.enabled!==!1)return e.productDisabled=!1,!1;if(e.productDisabled=!0,e.lastRunAt=new Date().toISOString(),e.runtimeState="idle_waiting",e.lastError=null,e.product){const a=e.product;e.product=void 0,e.productConfigKey=void 0,await a.runner.stop().catch(()=>{})}return!0}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(),R(e);const a=await V(e.config,r,n.conversationName,n.skipText?"":n.text,n.attachmentPath,n.key);$(e,a);const i=Y(a,{text:n.skipText?"":n.text,attachmentPath:n.attachmentPath});if(i.status==="queued")return n.text=i.text,n.attachmentPath=i.attachmentPath,n.skipText=i.skipText,n.lastInterruptedReason=i.reason,e.lastError=i.reason,R(e),!0;if(a.interrupted){const s=a.error||"WeChat RPA pending send was interrupted by user activity";return n.text=i.text,n.attachmentPath=i.attachmentPath,n.skipText=i.skipText,n.lastInterruptedReason=s,_(e,a,s),R(e),!0}if(!a.ok){const s=a.error||"WeChat RPA pending send validator failed; manual review required before retry";return e.pendingReplies.delete(n.key),Q(e,n.key,s),R(e),!0}e.pendingReplies.delete(n.key),e.completedPendingReplyKeys.add(n.key),R(e),e.lastError=null,w(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 He(r.fixturePath);if(r.source==="macos-flow")return Ne(e,r,n);if(r.source==="wechat-rpa-lab")return Pe(e,r,n);if(r.source==="windows-visual-flow")return We(e,r,n);const a=await q(),i=ae(a);return i?[i]:[]}seedConfiguredConversations(e,r){for(const n of g(r))e.conversations.set(x(n),n),e.conversations.set(N(n),n)}resolveConversationName(e,r,n){const i=this.connections.get(e.id)?.conversations.get(n);return i||(g(r).find(s=>x(s)===n||N(s)===n)??null)}}function ge(t,e){return JSON.stringify({id:t.id,workDir:t.workDir,managerSessionId:t.managerSessionId,pollIntervalMs:e.pollIntervalMs,forceForeground:k(e),downloadAttachments:e.downloadAttachments!==!1,canReply:e.canReply!==!1,selfTriggerMarker:e.selfTriggerMarker||"",groups:g(e)})}async function we(t,e,r){const n=r??H(),a=await n.getRuntimePolicy();if(a.feature?.enabled===!1)return{enabled:!1,reasonCode:a.feature.reasonCode||"feature_disabled"};if(a.runtime?.current?.status==="disabled")return{enabled:!1,reasonCode:"runtime_disabled"};const i=t.sessionId||t.managerSessionId;if(!i)return{enabled:!1,reasonCode:"session_missing"};let s;try{s=(await n.getBinding({sessionId:i})).binding}catch(o){const c=ye(o);if(c==="binding_not_found"||c==="session_not_found"||c==="machine_not_found")return{enabled:!1,reasonCode:c};throw o}if(!s||s.enabled===!1)return{enabled:!1,reasonCode:"no_enabled_binding"};const u=g(e);return u.length?u.includes(s.conversationName)?{enabled:!0}:{enabled:!1,reasonCode:"binding_conversation_changed"}:{enabled:!1,reasonCode:"no_configured_conversation"}}async function B(t){const r=(await H().getRuntimePolicy()).runtime?.current;return t.runtime.foregroundPolicy="work",{enabled:r?.status!=="disabled"}}function ye(t){if(t&&typeof t=="object"){const e=t.reasonCode;if(typeof e=="string")return e}return""}function j(t){return Number.isFinite(t)?Math.min(6e4,Math.max(1e3,Number(t))):pe}function g(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 Re(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 ve(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 Se(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 Pe(t,e,r){const n=await z(t.workDir),a=[];for(const i of g(e)){const s=await Ie(n,t,e,i);if(r?.(s),!(!s.ok||s.interrupted))for(const u of s.newMessages??[]){const o=G(i,u,e);o&&a.push(o)}}return a}async function Ie(t,e,r,n){const a=me(r.recentLimit),i={kind:a,requestId:`wechat-rpa:${n}:read:${Date.now()}`,targetGroup:n,policy:r.forceForeground?"work":"polite",limit:P(r.recentLimit,A,1,50),attachmentsDir:D(e,r)},s=await t.runWechatRpaReadLatest(i),u=Array.isArray(s.data?.structuredMessages)?s.data.structuredMessages:[],o=s.validation?.deterministic?.latestMessages??[],c=u.length?u.map((l,p)=>Ae(l,s.runId||"lab-read",p)):o.map((l,p)=>({id:`${s.runId||"lab-read"}:${p}`,text:l,confidence:.8}));return{ok:!!s.ok,groupName:n,interrupted:s.status==="interrupted",reason:s.status,rpaRunId:s.runId,rpaTraceSummary:`${a} ${s.status||(s.ok?"success":"failed")} ${n}`,recentMessages:c,newMessages:c,screenshotPath:s.tracePath,error:X(s)}}function Ae(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:Ce(t)}}function Ce(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 be(t,e,r,n,a,i){const s=await z(t.workDir),u=ke({groupName:r,text:n,attachmentPath:a,dedupeKey:i,requestId:i,policy:"work"}),o=[];for(const h of u){const v=await s.runWechatRpaTask(h);if(o.push(v),!v.ok)break}const c=o.find(h=>h.kind==="send-text"),l=o.find(h=>h.kind==="send-image"||h.kind==="send-video"||h.kind==="send-file"),p=u.length>0&&o.length===u.length&&o.every(h=>h.ok),d=o.some(h=>h.status==="interrupted");return{ok:p,groupName:r,interrupted:d,reason:o.at(-1)?.status,rpaRunId:o.at(-1)?.runId,rpaTraceSummary:`send ${p?"success":o.at(-1)?.status||"failed"} ${r} (${o.length}/${u.length})`,sentReply:!!n,sentReplyObserved:n?!!c?.ok:!1,sentAttachment:!!a,sentAttachmentObserved:a?!!l?.ok:!1,postSendScreenshotPath:o.at(-1)?.tracePath,error:p?void 0:X(o.at(-1))}}function ke(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=xe(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 xe(t){const e=f.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 z(t){const e=J(t);return await import(te(e).href)}function J(t){const e=f.dirname(ee(import.meta.url)),n=[process.env.SHENNIAN_WECHAT_RPA_LAB_INDEX,f.resolve(e,"../../../../scripts/wechat-rpa-lab/index.mjs"),t?f.join(t,"scripts/wechat-rpa-lab/index.mjs"):"",f.resolve(process.cwd(),"scripts/wechat-rpa-lab/index.mjs")].filter(Boolean).find(a=>m.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 f.resolve(n)}function X(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 Ne(t,e,r){const n=[];for(const a of g(e)){const i=await F({groupName:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:k(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:D(t,e)});if(r?.(i),!i.interrupted)for(const s of i.newMessages??[]){const u=G(a,s,e);u&&n.push(u)}}return n}async function We(t,e,r){const n=g(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 G(t,e,r){const n=String(e.text||"").trim(),a=Array.isArray(e.attachments)?e.attachments:[];if(!n&&a.length===0)return null;const i=typeof e.senderName=="string"?e.senderName.trim():"",s=String(e.observedAt||e.timestampIso||"").trim()||new Date().toISOString();return{conversationName:t,senderName:i||null,text:n,attachments:Je(a),observedAt:s,isMentioned:e.isMentioned===!0||Re(n,U(r)),rawId:String(e.id||`${t}:${i}:${n}:${s}`)}}async function V(t,e,r,n,a,i){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"?be(t,e,r,n,a,i):F({groupName:r,replyText:n||void 0,attachmentPath:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:k(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:D(t,e)})}function Ee(t){return t==="macos-flow"||t==="wechat-rpa-lab"}function Y(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 Te(t,e){return e.idempotencyKey?e.idempotencyKey:I.createHash("sha256").update(`${t.id}
1
+ import I from"node:crypto";import m from"node:fs";import f from"node:path";import{fileURLToPath as ee,pathToFileURL as te}from"node:url";import{ChannelSecretRegistry as re}from"./secret-registry.js";import{resolveShennianPath as ne}from"../config/index.js";import{probeMacWeChat as q,observedMessageFromProbe as ae}from"./wechat-rpa/macos.js";import{runMacWeChatRpaFlow as F}from"./wechat-rpa/macos-flow.js";import{normalizeWeChatRpaMessage as se,WeChatRpaDeduper as K,weChatRpaConversationId as x}from"./wechat-rpa/normalizer.js";import{cancelWeChatRpaProductOutboundReply as ie,countPendingWeChatRpaProductOutbound as oe,createWeChatRpaProductSourceConnection as ue,enqueueWeChatRpaProductOutboundReply as de,isWeChatRpaProductSource as y,listWeChatRpaProductRecentMessages as ce,listPendingWeChatRpaProductOutbound as le,weChatChannelConversationId as N}from"./wechat-rpa/product-channel.js";import{createWeChatChannelApiClient as H}from"./wechat-channel/client.js";const pe=5e3,A=5,fe=8,S=Number(process.env.SHENNIAN_WECHAT_RPA_OUTBOUND_ATTACHMENT_MAX_BYTES||20*1024*1024),W=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 me(t){return P(t,A,1,50)>=fe?"scroll-read":"read-latest"}class it{onMessage;options;type="wechat-rpa";secrets=new re;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,M(n,e),O(n,e),this.seedConfiguredConversations(n,r),y(r.source)){const a=await this.stopProductWhenControlPlaneDisabled(n,r);if(a||await this.ensureProductConnection(n,r),n.timer)return;n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))).catch(i=>{console.error(`[wechat-rpa] autonomous poll failed id=${e.id}: ${i instanceof Error?i.message:String(i)}`)})},n.product?.runtime.policy.pollIntervalMs??j(r.pollIntervalMs)),n.timer.unref(),!r.deferInitialPoll&&!a&&this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))).catch(i=>{console.error(`[wechat-rpa] initial poll failed id=${e.id}: ${i instanceof Error?i.message:String(i)}`)});return}n.timer||(n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))).catch(()=>{})},j(r.pollIntervalMs)),n.timer.unref(),r.deferInitialPoll||this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))).catch(()=>{}))}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,M(n,e),O(n,e),this.seedConfiguredConversations(n,r),y(r.source)?this.enqueueOperation(n,async()=>{const a=this.readSecret(n.config);if(await this.stopProductWhenControlPlaneDisabled(n,a))return[];const i=await this.pollProductOnce(n,a,{forceObserve:!0}),s=n.product;if(!s)return i;const u=ce({config:n.config,product:s,limit:a.recentLimit});return Xe(u,i)}):this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config)))}async cancelOutbound(e,r){const n=this.readSecret(e);return y(n.source)?ie({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,M(a,e),O(a,e),this.seedConfiguredConversations(a,n),y(n.source)){const d=this.resolveConversationName(e,n,r.conversationId);if(!d)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");if(await this.stopProductWhenControlPlaneDisabled(a,n))throw new Error("WeChat RPA channel is disabled");return de({config:a.config,secret:n,reply:{...r,text:h},conversationName:d}),a.runtimeState="retrying",w(a,{status:"sent",runId:a.lastRunId??null,summary:`queued ${d}`}),this.enqueueOperation(a,()=>this.pollProductOnce(a,this.readSecret(a.config))).catch(v=>{a.lastError=v instanceof Error?v.message:String(v)}),{status:"queued"}}if(!Ee(n.source))throw new Error("WeChat RPA reply requires source=macos-flow, windows-visual-flow, or wechat-rpa-lab");const i=this.resolveConversationName(e,n,r.conversationId);if(!i)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const s=Te(e,r);if(a.completedPendingReplyKeys.has(s))return{status:"sent"};const u=a.manualReviewReplies.get(s);if(u)return{status:"manual-review",reason:u.reason};const o=r.attachment?await Be(e.workDir,r.attachment):void 0,c=r.text.trim();if(!c&&!o)throw new Error("Reply text or attachment is required");if(Z(a,n)){const d="WeChat RPA is cooling down after repeated user activity";return a.runtimeState="cooldown",T(a,{key:s,conversationId:r.conversationId,conversationName:i,text:c,attachmentPath:o,reason:d}),{status:"queued",reason:d}}let l;try{a.runtimeState="syncing",l=await this.enqueueOperation(a,()=>(a.lastRunAt=new Date().toISOString(),V(e,n,i,c,o,s)))}catch(d){throw a.lastError=d instanceof Error?d.message:String(d),w(a,{status:b(a.lastError),runId:a.lastRunId??null,summary:a.lastError}),d}$(a,l);const p=Y(l,{text:c,attachmentPath:o});if(p.status==="queued"){const d=p.reason;return T(a,{key:s,conversationId:r.conversationId,conversationName:i,text:p.text,attachmentPath:p.attachmentPath,reason:d,skipText:p.skipText}),a.lastError=d,{status:"queued",reason:d}}if(l.interrupted){const d=l.error||"WeChat RPA send was interrupted by user activity";return _(a,l,d),T(a,{key:s,conversationId:r.conversationId,conversationName:i,text:p.text,attachmentPath:o,reason:d,skipText:p.skipText}),{status:"queued",reason:d}}if(!l.ok){const d=l.error||"WeChat RPA send validator failed; manual review required before retry";return Q(a,s,d),{status:"manual-review",reason:d}}return w(a,{status:"sent",runId:a.lastRunId??null,summary:l.rpaTraceSummary||`sent ${i}`}),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&&m.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(!g(r).length)return{ok:!1,message:"WeChat RPA macOS flow requires at least one group"}}if(y(r.source))return Se(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(!g(r).length)return{ok:!1,message:"WeChat RPA Lab source requires at least one group"};try{J(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 ve(r,process.platform);const n=await q();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=g(r)[0];if(!n)throw new Error("WeChat RPA channel has no configured groups");return y(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+oe({config:e}),wechatRpaPendingReplies:Me(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(Z(e,r))return e.runtimeState="cooldown",[];if(await this.drainPendingReplies(e,r))return[];let i=!1;e.runtimeState="syncing";const s=await this.readObservedMessages(e.config,r,u=>{$(e,u),u.interrupted&&(i=!0,_(e,u,u.error||"WeChat RPA poll was interrupted by user activity"))});e.lastError=null;for(const u of s){const o=se(u,{selfNicknames:U(r)});if(!o||!e.deduper.accept(o.messageId))continue;De(e),e.conversations.set(o.conversationId,o.conversationName),e.lastMessageAt=o.receivedAt;const c={type:"external.message",managerSessionId:e.config.managerSessionId,channelId:e.config.id,channelType:"wechat-rpa",conversationId:o.conversationId,conversationName:o.conversationName,messageId:o.messageId,sender:o.sender,text:o.text,attachments:o.attachments,receivedAt:o.receivedAt,isMentioned:o.isMentioned,replyTarget:"",rawRef:o.rawRef};n.push(this.onMessage?.(c)??c),w(e,{status:"received",runId:e.lastRunId??null,summary:`received ${o.conversationName}${o.sender.name?` from ${o.sender.name}`:""}: ${L(o.text||o.attachments[0]?.name||"attachment")}`})}return i||C(e),n}catch(a){throw e.lastError=a instanceof Error?a.message:String(a),w(e,{status:b(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),a}}async pollProductOnce(e,r,n){if(e.stopped)return[];if(!y(r.source))return[];if(await this.stopProductWhenControlPlaneDisabled(e,r))return[];const a=await this.ensureProductConnection(e,r);await B(a).catch(()=>null);const i=a.emitted.length;e.lastRunAt=new Date().toISOString(),e.runtimeState="syncing";try{await a.runner.tick(n);const s=a.emitted.slice(i);e.lastError=null;for(const u of s)e.conversations.set(u.conversationId,u.conversationName||""),e.lastMessageAt=u.receivedAt,w(e,{status:"received",runId:e.lastRunId??null,summary:`received ${u.conversationName||u.conversationId}${u.sender.name?` from ${u.sender.name}`:""}: ${L(u.text||u.attachments[0]?.name||"attachment")}`});return C(e),s}catch(s){throw e.lastError=s instanceof Error?s.message:String(s),e.runtimeState=b(e.lastError)==="blocked"?"interrupted":"idle_waiting",w(e,{status:b(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),s}}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=ue({config:e.config,secret:r,createRunner:this.options.createProductRunner,onMessage:this.onMessage}),await B(e.product).catch(()=>{}),e.productConfigKey=n,e.lastTracePath=e.product.helperTracePath),e.product}async stopProductWhenControlPlaneDisabled(e,r){if(!y(r.source))return!1;let n=null;try{n=await we(e.config,r)}catch(a){return e.productDisabled?(e.lastRunAt=new Date().toISOString(),e.runtimeState="idle_waiting",e.lastError=a instanceof Error?a.message:String(a),!0):!1}if(n?.enabled!==!1)return e.productDisabled=!1,!1;if(e.productDisabled=!0,e.lastRunAt=new Date().toISOString(),e.runtimeState="idle_waiting",e.lastError=null,e.product){const a=e.product;e.product=void 0,e.productConfigKey=void 0,await a.runner.stop().catch(()=>{})}return!0}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(),R(e);const a=await V(e.config,r,n.conversationName,n.skipText?"":n.text,n.attachmentPath,n.key);$(e,a);const i=Y(a,{text:n.skipText?"":n.text,attachmentPath:n.attachmentPath});if(i.status==="queued")return n.text=i.text,n.attachmentPath=i.attachmentPath,n.skipText=i.skipText,n.lastInterruptedReason=i.reason,e.lastError=i.reason,R(e),!0;if(a.interrupted){const s=a.error||"WeChat RPA pending send was interrupted by user activity";return n.text=i.text,n.attachmentPath=i.attachmentPath,n.skipText=i.skipText,n.lastInterruptedReason=s,_(e,a,s),R(e),!0}if(!a.ok){const s=a.error||"WeChat RPA pending send validator failed; manual review required before retry";return e.pendingReplies.delete(n.key),Q(e,n.key,s),R(e),!0}e.pendingReplies.delete(n.key),e.completedPendingReplyKeys.add(n.key),R(e),e.lastError=null,w(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 He(r.fixturePath);if(r.source==="macos-flow")return Ne(e,r,n);if(r.source==="wechat-rpa-lab")return Pe(e,r,n);if(r.source==="windows-visual-flow")return We(e,r,n);const a=await q(),i=ae(a);return i?[i]:[]}seedConfiguredConversations(e,r){for(const n of g(r))e.conversations.set(x(n),n),e.conversations.set(N(n),n)}resolveConversationName(e,r,n){const i=this.connections.get(e.id)?.conversations.get(n);return i||(g(r).find(s=>x(s)===n||N(s)===n)??null)}}function ge(t,e){return JSON.stringify({id:t.id,workDir:t.workDir,managerSessionId:t.managerSessionId,pollIntervalMs:e.pollIntervalMs,forceForeground:k(e),downloadAttachments:e.downloadAttachments!==!1,canReply:e.canReply!==!1,selfTriggerMarker:e.selfTriggerMarker||"",groups:g(e)})}async function we(t,e,r){const n=r??H(),a=await n.getRuntimePolicy();if(a.feature?.enabled===!1)return{enabled:!1,reasonCode:a.feature.reasonCode||"feature_disabled"};if(a.runtime?.current?.status==="disabled")return{enabled:!1,reasonCode:"runtime_disabled"};const i=t.sessionId||t.managerSessionId;if(!i)return{enabled:!1,reasonCode:"session_missing"};let s;try{s=(await n.getBinding({sessionId:i})).binding}catch(o){const c=ye(o);if(c==="binding_not_found"||c==="session_not_found"||c==="machine_not_found")return{enabled:!1,reasonCode:c};throw o}if(!s||s.enabled===!1)return{enabled:!1,reasonCode:"no_enabled_binding"};const u=g(e);return u.length?u.includes(s.conversationName)?{enabled:!0}:{enabled:!1,reasonCode:"binding_conversation_changed"}:{enabled:!1,reasonCode:"no_configured_conversation"}}async function B(t){const r=(await H().getRuntimePolicy()).runtime?.current;return t.runtime.foregroundPolicy="work",{enabled:r?.status!=="disabled"}}function ye(t){if(t&&typeof t=="object"){const e=t.reasonCode;if(typeof e=="string")return e}return""}function j(t){return Number.isFinite(t)?Math.min(6e4,Math.max(1e3,Number(t))):pe}function g(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 Re(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 ve(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 Se(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 Pe(t,e,r){const n=await z(t.workDir),a=[];for(const i of g(e)){const s=await Ie(n,t,e,i);if(r?.(s),!(!s.ok||s.interrupted))for(const u of s.newMessages??[]){const o=G(i,u,e);o&&a.push(o)}}return a}async function Ie(t,e,r,n){const a=me(r.recentLimit),i={kind:a,requestId:`wechat-rpa:${n}:read:${Date.now()}`,targetGroup:n,policy:r.forceForeground?"work":"polite",limit:P(r.recentLimit,A,1,50),attachmentsDir:D(e,r)},s=await t.runWechatRpaReadLatest(i),u=Array.isArray(s.data?.structuredMessages)?s.data.structuredMessages:[],o=s.validation?.deterministic?.latestMessages??[],c=u.length?u.map((l,p)=>Ae(l,s.runId||"lab-read",p)):o.map((l,p)=>({id:`${s.runId||"lab-read"}:${p}`,text:l,confidence:.8}));return{ok:!!s.ok,groupName:n,interrupted:s.status==="interrupted",reason:s.status,rpaRunId:s.runId,rpaTraceSummary:`${a} ${s.status||(s.ok?"success":"failed")} ${n}`,recentMessages:c,newMessages:c,screenshotPath:s.tracePath,error:X(s)}}function Ae(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:Ce(t)}}function Ce(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 be(t,e,r,n,a,i){const s=await z(t.workDir),u=ke({groupName:r,text:n,attachmentPath:a,dedupeKey:i,requestId:i,policy:"work"}),o=[];for(const h of u){const v=await s.runWechatRpaTask(h);if(o.push(v),!v.ok)break}const c=o.find(h=>h.kind==="send-text"),l=o.find(h=>h.kind==="send-image"||h.kind==="send-video"||h.kind==="send-file"),p=u.length>0&&o.length===u.length&&o.every(h=>h.ok),d=o.some(h=>h.status==="interrupted");return{ok:p,groupName:r,interrupted:d,reason:o.at(-1)?.status,rpaRunId:o.at(-1)?.runId,rpaTraceSummary:`send ${p?"success":o.at(-1)?.status||"failed"} ${r} (${o.length}/${u.length})`,sentReply:!!n,sentReplyObserved:n?!!c?.ok:!1,sentAttachment:!!a,sentAttachmentObserved:a?!!l?.ok:!1,postSendScreenshotPath:o.at(-1)?.tracePath,error:p?void 0:X(o.at(-1))}}function ke(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=xe(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 xe(t){const e=f.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 z(t){const e=J(t);return await import(te(e).href)}function J(t){const e=f.dirname(ee(import.meta.url)),n=[process.env.SHENNIAN_WECHAT_RPA_LAB_INDEX,f.resolve(e,"../../../../scripts/wechat-rpa-lab/index.mjs"),t?f.join(t,"scripts/wechat-rpa-lab/index.mjs"):"",f.resolve(process.cwd(),"scripts/wechat-rpa-lab/index.mjs")].filter(Boolean).find(a=>m.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 f.resolve(n)}function X(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 Ne(t,e,r){const n=[];for(const a of g(e)){const i=await F({groupName:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:k(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:D(t,e)});if(r?.(i),!i.interrupted)for(const s of i.newMessages??[]){const u=G(a,s,e);u&&n.push(u)}}return n}async function We(t,e,r){const n=g(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 G(t,e,r){const n=String(e.text||"").trim(),a=Array.isArray(e.attachments)?e.attachments:[];if(!n&&a.length===0)return null;const i=typeof e.senderName=="string"?e.senderName.trim():"",s=String(e.observedAt||e.timestampIso||"").trim()||new Date().toISOString();return{conversationName:t,senderName:i||null,text:n,attachments:Je(a),observedAt:s,isMentioned:e.isMentioned===!0||Re(n,U(r)),rawId:String(e.id||`${t}:${i}:${n}:${s}`)}}async function V(t,e,r,n,a,i){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"?be(t,e,r,n,a,i):F({groupName:r,replyText:n||void 0,attachmentPath:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:k(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:D(t,e)})}function Ee(t){return t==="macos-flow"||t==="wechat-rpa-lab"}function Y(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 Te(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||""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.130",
3
+ "version": "0.2.131",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {