shennian 0.2.99 → 0.2.101

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 (47) hide show
  1. package/dist/publish-build-manifest.json +77 -27
  2. package/dist/src/channels/runtime.d.ts +5 -1
  3. package/dist/src/channels/runtime.js +3 -3
  4. package/dist/src/channels/wechat-channel/helper-assets.d.ts +59 -1
  5. package/dist/src/channels/wechat-channel/helper-assets.js +1 -1
  6. package/dist/src/channels/wechat-channel/helper-client.js +3 -3
  7. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
  8. package/dist/src/channels/wechat-channel/observer.d.ts +32 -2
  9. package/dist/src/channels/wechat-channel/observer.js +6 -6
  10. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +2 -0
  11. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -1
  12. package/dist/src/channels/wechat-channel/pacing.d.ts +3 -0
  13. package/dist/src/channels/wechat-channel/pacing.js +1 -0
  14. package/dist/src/channels/wechat-channel/preflight.d.ts +7 -0
  15. package/dist/src/channels/wechat-channel/preflight.js +1 -1
  16. package/dist/src/channels/wechat-channel/runner.d.ts +0 -1
  17. package/dist/src/channels/wechat-channel/runner.js +1 -1
  18. package/dist/src/channels/wechat-channel/scheduler.d.ts +4 -3
  19. package/dist/src/channels/wechat-channel/scheduler.js +1 -1
  20. package/dist/src/channels/wechat-rpa.d.ts +5 -1
  21. package/dist/src/channels/wechat-rpa.js +4 -4
  22. package/dist/src/commands/runtime.d.ts +89 -0
  23. package/dist/src/commands/runtime.js +1 -0
  24. package/dist/src/commands/wechat/command.d.ts +2 -0
  25. package/dist/src/commands/wechat/command.js +1 -0
  26. package/dist/src/commands/wechat/direct.d.ts +14 -0
  27. package/dist/src/commands/wechat/direct.js +2 -0
  28. package/dist/src/commands/wechat/doctor.d.ts +2 -0
  29. package/dist/src/commands/wechat/doctor.js +1 -0
  30. package/dist/src/commands/wechat/ipc.d.ts +9 -0
  31. package/dist/src/commands/wechat/ipc.js +1 -0
  32. package/dist/src/commands/wechat/operations.d.ts +3 -0
  33. package/dist/src/commands/wechat/operations.js +4 -0
  34. package/dist/src/commands/wechat/output.d.ts +7 -0
  35. package/dist/src/commands/wechat/output.js +4 -0
  36. package/dist/src/commands/wechat/types.d.ts +141 -0
  37. package/dist/src/commands/wechat/types.js +1 -0
  38. package/dist/src/commands/wechat/utils.d.ts +14 -0
  39. package/dist/src/commands/wechat/utils.js +1 -0
  40. package/dist/src/commands/wechat.d.ts +6 -127
  41. package/dist/src/commands/wechat.js +1 -7
  42. package/dist/src/devtools/wechat-channel-action-smoke.d.ts +5 -0
  43. package/dist/src/devtools/wechat-channel-action-smoke.js +8 -8
  44. package/dist/src/index.js +2 -2
  45. package/dist/src/manager/runtime.d.ts +3 -1
  46. package/dist/src/manager/runtime.js +5 -5
  47. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
- import I from"node:crypto";import h from"node:fs";import m from"node:path";import{fileURLToPath as Y,pathToFileURL as Q}from"node:url";import{ChannelSecretRegistry as Z}from"./secret-registry.js";import{resolveShennianPath as ee}from"../config/index.js";import{probeMacWeChat as q,observedMessageFromProbe as te}from"./wechat-rpa/macos.js";import{runMacWeChatRpaFlow as D}from"./wechat-rpa/macos-flow.js";import{normalizeWeChatRpaMessage as re,WeChatRpaDeduper as F,weChatRpaConversationId as b}from"./wechat-rpa/normalizer.js";import{cancelWeChatRpaProductOutboundReply as ne,countPendingWeChatRpaProductOutbound as ae,createWeChatRpaProductSourceConnection as se,enqueueWeChatRpaProductOutboundReply as ie,isWeChatRpaProductSource as y,listWeChatRpaProductRecentMessages as oe,listPendingWeChatRpaProductOutbound as ue,weChatChannelConversationId as x}from"./wechat-rpa/product-channel.js";import{createWeChatChannelApiClient as ce}from"./wechat-channel/client.js";const de=5e3,A=5,le=8,S=Number(process.env.SHENNIAN_WECHAT_RPA_OUTBOUND_ATTACHMENT_MAX_BYTES||20*1024*1024),N=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_THRESHOLD||3),M=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_MS||300*1e3),me=10;function pe(t){return P(t,A,1,50)>=le?"scroll-read":"read-latest"}class nt{onMessage;type="wechat-rpa";secrets=new Z;connections=new Map;constructor(e){this.onMessage=e}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),T(n,e),this.seedConfiguredConversations(n,r),y(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??K(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(()=>{})},K(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,W(n,e),T(n,e),this.seedConfiguredConversations(n,r),y(r.source)?(await this.ensureProductConnection(n,r),this.enqueueOperation(n,async()=>{const a=this.readSecret(n.config),i=await this.pollProductOnce(n,a),s=await this.ensureProductConnection(n,a),u=oe({config:n.config,product:s,limit:a.recentLimit});return je(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)?ne({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),T(a,e),this.seedConfiguredConversations(a,n),y(n.source)){const c=this.resolveConversationName(e,n,r.conversationId);if(!c)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const p=r.text.trim();if(!p&&!r.attachment)throw new Error("Reply text or attachment is required");return ie({config:e,secret:n,reply:{...r,text:p},conversationName:c}),a.runtimeState="retrying",g(a,{status:"sent",runId:a.lastRunId??null,summary:`queued ${c}`}),await this.ensureProductConnection(a,n),this.enqueueOperation(a,()=>this.pollProductOnce(a,this.readSecret(a.config))).catch(v=>{a.lastError=v instanceof Error?v.message:String(v)}),{status:"queued"}}if(!xe(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=Ne(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 Fe(e.workDir,r.attachment):void 0,f=r.text.trim();if(!f&&!o)throw new Error("Reply text or attachment is required");if(V(a,n)){const c="WeChat RPA is cooling down after repeated user activity";return a.runtimeState="cooldown",E(a,{key:s,conversationId:r.conversationId,conversationName:i,text:f,attachmentPath:o,reason:c}),{status:"queued",reason:c}}let d;try{a.runtimeState="syncing",d=await this.enqueueOperation(a,()=>(a.lastRunAt=new Date().toISOString(),J(e,n,i,f,o,s)))}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}$(a,d);const l=X(d,{text:f,attachmentPath:o});if(l.status==="queued"){const c=l.reason;return E(a,{key:s,conversationId:r.conversationId,conversationName:i,text:l.text,attachmentPath:l.attachmentPath,reason:c,skipText:l.skipText}),a.lastError=c,{status:"queued",reason:c}}if(d.interrupted){const c=d.error||"WeChat RPA send was interrupted by user activity";return O(a,d,c),E(a,{key:s,conversationId:r.conversationId,conversationName:i,text:l.text,attachmentPath:o,reason:c,skipText:l.skipText}),{status:"queued",reason:c}}if(!d.ok){const c=d.error||"WeChat RPA send validator failed; manual review required before retry";return G(a,s,c),{status:"manual-review",reason:c}}return g(a,{status:"sent",runId:a.lastRunId??null,summary:d.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&&h.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(y(r.source))return ye(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{U(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 we(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=w(r)[0];if(!n)throw new Error("WeChat RPA channel has no configured groups");return y(r.source)?{conversationId:x(n),conversationName:n}:{conversationId:b(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??null,wechatRpaLastTraceSummary:r.lastTraceSummary??null,wechatRpaPendingReplyCount:r.pendingReplies.size+ae({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 F,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=e.operation.then(r,r);return e.operation=n.then(()=>{},()=>{}),n}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(V(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,O(e,u,u.error||"WeChat RPA poll was interrupted by user activity"))});e.lastError=null;for(const u of s){const o=re(u,{selfNicknames:B(r)});if(!o||!e.deduper.accept(o.messageId))continue;$e(e),e.conversations.set(o.conversationId,o.conversationName),e.lastMessageAt=o.receivedAt;const f={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?.(f)??f),g(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),g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),a}}async pollProductOnce(e,r){if(e.stopped)return[];if(!y(r.source))return[];const n=await this.ensureProductConnection(e,r),a=n.emitted.length;e.lastRunAt=new Date().toISOString(),e.runtimeState="syncing";try{await n.runner.tick();const i=n.emitted.slice(a);e.lastError=null;for(const s of i)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}`:""}: ${L(s.text||s.attachments[0]?.name||"attachment")}`});return C(e),i}catch(i){throw e.lastError=i instanceof Error?i.message:String(i),e.runtimeState=k(e.lastError)==="blocked"?"interrupted":"idle_waiting",g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),i}}async ensureProductConnection(e,r){const n=he(e.config,r);return e.product&&e.productConfigKey===n||(e.product&&await e.product.runner.stop().catch(()=>{}),e.product=se({config:e.config,secret:r,onMessage:this.onMessage}),Number.isFinite(r.pollIntervalMs)||await fe(e.product).catch(()=>{}),e.productConfigKey=n),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(),R(e);const a=await J(e.config,r,n.conversationName,n.skipText?"":n.text,n.attachmentPath,n.key);$(e,a);const i=X(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,O(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),G(e,n.key,s),R(e),!0}e.pendingReplies.delete(n.key),e.completedPendingReplyKeys.add(n.key),R(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 De(r.fixturePath);if(r.source==="macos-flow")return ke(e,r,n);if(r.source==="wechat-rpa-lab")return Re(e,r,n);if(r.source==="windows-visual-flow")return be(e,r,n);const a=await q(),i=te(a);return i?[i]:[]}seedConfiguredConversations(e,r){for(const n of w(r))e.conversations.set(b(n),n),e.conversations.set(x(n),n)}resolveConversationName(e,r,n){const i=this.connections.get(e.id)?.conversations.get(n);return i||(w(r).find(s=>b(s)===n||x(s)===n)??null)}}function he(t,e){return JSON.stringify({id:t.id,workDir:t.workDir,managerSessionId:t.managerSessionId,pollIntervalMs:e.pollIntervalMs,forceForeground:!!e.forceForeground,downloadAttachments:e.downloadAttachments!==!1,canReply:e.canReply!==!1,groups:w(e)})}async function fe(t){const e=await ce().getRuntimePolicy(),r=e.runtime?.scanPolicy??e.usagePolicy?.scanPolicy,n=r?.defaultPollIntervalSeconds??e.runtime?.defaultPollIntervalSeconds;if(!Number.isFinite(n))return;const a=Number.isFinite(r?.minPollIntervalSeconds)?Number(r?.minPollIntervalSeconds):e.runtime?.minPollIntervalSeconds,i=Number.isFinite(r?.maxPollIntervalSeconds)?Number(r?.maxPollIntervalSeconds):e.runtime?.maxPollIntervalSeconds,s=Number.isFinite(a)?Number(a)*1e3:t.runtime.policy.minPollIntervalMs,u=Number.isFinite(i)?Number(i)*1e3:t.runtime.policy.maxPollIntervalMs,o=Math.min(u,Math.max(s,Number(n)*1e3));t.runtime.policy.pollIntervalMs=o,t.runtime.policy.minPollIntervalMs=s,t.runtime.policy.maxPollIntervalMs=u}function K(t){return Number.isFinite(t)?Math.min(6e4,Math.max(1e3,Number(t))):de}function w(t){return Array.isArray(t.groups)?t.groups.map(e=>String(e?.name||"").trim()).filter(Boolean):[]}function B(t){return String(t.selfNickname||"").split(/[,\n,、]/).map(e=>e.trim()).filter(Boolean)}function ge(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 we(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 ye(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 Re(t,e,r){const n=await H(t.workDir),a=[];for(const i of w(e)){const s=await ve(n,t,e,i);if(r?.(s),!(!s.ok||s.interrupted))for(const u of s.newMessages??[]){const o=z(i,u,e);o&&a.push(o)}}return a}async function ve(t,e,r,n){const a=pe(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:_(e,r)},s=await t.runWechatRpaReadLatest(i),u=Array.isArray(s.data?.structuredMessages)?s.data.structuredMessages:[],o=s.validation?.deterministic?.latestMessages??[],f=u.length?u.map((d,l)=>Se(d,s.runId||"lab-read",l)):o.map((d,l)=>({id:`${s.runId||"lab-read"}:${l}`,text:d,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:f,newMessages:f,screenshotPath:s.tracePath,error:j(s)}}function Se(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:Pe(t)}}function Pe(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 Ie(t,e,r,n,a,i){const s=await H(t.workDir),u=Ae({groupName:r,text:n,attachmentPath:a,dedupeKey:i,requestId:i,policy:"work"}),o=[];for(const p of u){const v=await s.runWechatRpaTask(p);if(o.push(v),!v.ok)break}const f=o.find(p=>p.kind==="send-text"),d=o.find(p=>p.kind==="send-image"||p.kind==="send-video"||p.kind==="send-file"),l=u.length>0&&o.length===u.length&&o.every(p=>p.ok),c=o.some(p=>p.status==="interrupted");return{ok:l,groupName:r,interrupted:c,reason:o.at(-1)?.status,rpaRunId:o.at(-1)?.runId,rpaTraceSummary:`send ${l?"success":o.at(-1)?.status||"failed"} ${r} (${o.length}/${u.length})`,sentReply:!!n,sentReplyObserved:n?!!f?.ok:!1,sentAttachment:!!a,sentAttachmentObserved:a?!!d?.ok:!1,postSendScreenshotPath:o.at(-1)?.tracePath,error:l?void 0:j(o.at(-1))}}function Ae(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=Ce(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 Ce(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 H(t){const e=U(t);return await import(Q(e).href)}function U(t){const e=m.dirname(Y(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=>h.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 ke(t,e,r){const n=[];for(const a of w(e)){const i=await D({groupName:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:!!e.forceForeground,noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:_(t,e)});if(r?.(i),!i.interrupted)for(const s of i.newMessages??[]){const u=z(a,s,e);u&&n.push(u)}}return n}async function be(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 z(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:Ue(a),observedAt:s,isMentioned:e.isMentioned===!0||ge(n,B(r)),rawId:String(e.id||`${t}:${i}:${n}:${s}`)}}async function J(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"?Ie(t,e,r,n,a,i):D({groupName:r,replyText:n||void 0,attachmentPath:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:!!e.forceForeground,noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:_(t,e)})}function xe(t){return t==="macos-flow"||t==="wechat-rpa-lab"}function X(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 Ne(t,e){return e.idempotencyKey?e.idempotencyKey:I.createHash("sha256").update(`${t.id}
1
+ import I from"node:crypto";import h from"node:fs";import p from"node:path";import{fileURLToPath as Y,pathToFileURL as Q}from"node:url";import{ChannelSecretRegistry as Z}from"./secret-registry.js";import{resolveShennianPath as ee}from"../config/index.js";import{probeMacWeChat as q,observedMessageFromProbe as te}from"./wechat-rpa/macos.js";import{runMacWeChatRpaFlow as D}from"./wechat-rpa/macos-flow.js";import{normalizeWeChatRpaMessage as re,WeChatRpaDeduper as F,weChatRpaConversationId as b}from"./wechat-rpa/normalizer.js";import{cancelWeChatRpaProductOutboundReply as ne,countPendingWeChatRpaProductOutbound as ae,createWeChatRpaProductSourceConnection as se,enqueueWeChatRpaProductOutboundReply as ie,isWeChatRpaProductSource as y,listWeChatRpaProductRecentMessages as oe,listPendingWeChatRpaProductOutbound as ue,weChatChannelConversationId as x}from"./wechat-rpa/product-channel.js";import{createWeChatChannelApiClient as ce}from"./wechat-channel/client.js";const de=5e3,A=5,le=8,S=Number(process.env.SHENNIAN_WECHAT_RPA_OUTBOUND_ATTACHMENT_MAX_BYTES||20*1024*1024),N=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_THRESHOLD||3),M=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_MS||300*1e3),pe=10;function me(t){return P(t,A,1,50)>=le?"scroll-read":"read-latest"}class nt{onMessage;options;type="wechat-rpa";secrets=new Z;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),T(n,e),this.seedConfiguredConversations(n,r),y(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??K(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(()=>{})},K(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,W(n,e),T(n,e),this.seedConfiguredConversations(n,r),y(r.source)?(await this.ensureProductConnection(n,r),this.enqueueOperation(n,async()=>{const a=this.readSecret(n.config),i=await this.pollProductOnce(n,a),s=await this.ensureProductConnection(n,a),u=oe({config:n.config,product:s,limit:a.recentLimit});return je(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)?ne({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),T(a,e),this.seedConfiguredConversations(a,n),y(n.source)){const c=this.resolveConversationName(e,n,r.conversationId);if(!c)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const m=r.text.trim();if(!m&&!r.attachment)throw new Error("Reply text or attachment is required");return ie({config:e,secret:n,reply:{...r,text:m},conversationName:c}),a.runtimeState="retrying",g(a,{status:"sent",runId:a.lastRunId??null,summary:`queued ${c}`}),await this.ensureProductConnection(a,n),this.enqueueOperation(a,()=>this.pollProductOnce(a,this.readSecret(a.config))).catch(v=>{a.lastError=v instanceof Error?v.message:String(v)}),{status:"queued"}}if(!xe(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=Ne(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 Fe(e.workDir,r.attachment):void 0,f=r.text.trim();if(!f&&!o)throw new Error("Reply text or attachment is required");if(V(a,n)){const c="WeChat RPA is cooling down after repeated user activity";return a.runtimeState="cooldown",E(a,{key:s,conversationId:r.conversationId,conversationName:i,text:f,attachmentPath:o,reason:c}),{status:"queued",reason:c}}let d;try{a.runtimeState="syncing",d=await this.enqueueOperation(a,()=>(a.lastRunAt=new Date().toISOString(),J(e,n,i,f,o,s)))}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}$(a,d);const l=X(d,{text:f,attachmentPath:o});if(l.status==="queued"){const c=l.reason;return E(a,{key:s,conversationId:r.conversationId,conversationName:i,text:l.text,attachmentPath:l.attachmentPath,reason:c,skipText:l.skipText}),a.lastError=c,{status:"queued",reason:c}}if(d.interrupted){const c=d.error||"WeChat RPA send was interrupted by user activity";return O(a,d,c),E(a,{key:s,conversationId:r.conversationId,conversationName:i,text:l.text,attachmentPath:o,reason:c,skipText:l.skipText}),{status:"queued",reason:c}}if(!d.ok){const c=d.error||"WeChat RPA send validator failed; manual review required before retry";return G(a,s,c),{status:"manual-review",reason:c}}return g(a,{status:"sent",runId:a.lastRunId??null,summary:d.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&&h.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(y(r.source))return ye(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{U(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 we(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=w(r)[0];if(!n)throw new Error("WeChat RPA channel has no configured groups");return y(r.source)?{conversationId:x(n),conversationName:n}:{conversationId:b(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??null,wechatRpaLastTraceSummary:r.lastTraceSummary??null,wechatRpaPendingReplyCount:r.pendingReplies.size+ae({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 F,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=e.operation.then(r,r);return e.operation=n.then(()=>{},()=>{}),n}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(V(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,O(e,u,u.error||"WeChat RPA poll was interrupted by user activity"))});e.lastError=null;for(const u of s){const o=re(u,{selfNicknames:B(r)});if(!o||!e.deduper.accept(o.messageId))continue;$e(e),e.conversations.set(o.conversationId,o.conversationName),e.lastMessageAt=o.receivedAt;const f={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?.(f)??f),g(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),g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),a}}async pollProductOnce(e,r){if(e.stopped)return[];if(!y(r.source))return[];const n=await this.ensureProductConnection(e,r),a=n.emitted.length;e.lastRunAt=new Date().toISOString(),e.runtimeState="syncing";try{await n.runner.tick();const i=n.emitted.slice(a);e.lastError=null;for(const s of i)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}`:""}: ${L(s.text||s.attachments[0]?.name||"attachment")}`});return C(e),i}catch(i){throw e.lastError=i instanceof Error?i.message:String(i),e.runtimeState=k(e.lastError)==="blocked"?"interrupted":"idle_waiting",g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),i}}async ensureProductConnection(e,r){const n=he(e.config,r);return e.product&&e.productConfigKey===n||(e.product&&await e.product.runner.stop().catch(()=>{}),e.product=se({config:e.config,secret:r,createRunner:this.options.createProductRunner,onMessage:this.onMessage}),Number.isFinite(r.pollIntervalMs)||await fe(e.product).catch(()=>{}),e.productConfigKey=n),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(),R(e);const a=await J(e.config,r,n.conversationName,n.skipText?"":n.text,n.attachmentPath,n.key);$(e,a);const i=X(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,O(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),G(e,n.key,s),R(e),!0}e.pendingReplies.delete(n.key),e.completedPendingReplyKeys.add(n.key),R(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 De(r.fixturePath);if(r.source==="macos-flow")return ke(e,r,n);if(r.source==="wechat-rpa-lab")return Re(e,r,n);if(r.source==="windows-visual-flow")return be(e,r,n);const a=await q(),i=te(a);return i?[i]:[]}seedConfiguredConversations(e,r){for(const n of w(r))e.conversations.set(b(n),n),e.conversations.set(x(n),n)}resolveConversationName(e,r,n){const i=this.connections.get(e.id)?.conversations.get(n);return i||(w(r).find(s=>b(s)===n||x(s)===n)??null)}}function he(t,e){return JSON.stringify({id:t.id,workDir:t.workDir,managerSessionId:t.managerSessionId,pollIntervalMs:e.pollIntervalMs,forceForeground:!!e.forceForeground,downloadAttachments:e.downloadAttachments!==!1,canReply:e.canReply!==!1,groups:w(e)})}async function fe(t){const e=await ce().getRuntimePolicy(),r=e.runtime?.scanPolicy??e.usagePolicy?.scanPolicy,n=r?.defaultPollIntervalSeconds??e.runtime?.defaultPollIntervalSeconds;if(!Number.isFinite(n))return;const a=Number.isFinite(r?.minPollIntervalSeconds)?Number(r?.minPollIntervalSeconds):e.runtime?.minPollIntervalSeconds,i=Number.isFinite(r?.maxPollIntervalSeconds)?Number(r?.maxPollIntervalSeconds):e.runtime?.maxPollIntervalSeconds,s=Number.isFinite(a)?Number(a)*1e3:t.runtime.policy.minPollIntervalMs,u=Number.isFinite(i)?Number(i)*1e3:t.runtime.policy.maxPollIntervalMs,o=Math.min(u,Math.max(s,Number(n)*1e3));t.runtime.policy.pollIntervalMs=o,t.runtime.policy.minPollIntervalMs=s,t.runtime.policy.maxPollIntervalMs=u}function K(t){return Number.isFinite(t)?Math.min(6e4,Math.max(1e3,Number(t))):de}function w(t){return Array.isArray(t.groups)?t.groups.map(e=>String(e?.name||"").trim()).filter(Boolean):[]}function B(t){return String(t.selfNickname||"").split(/[,\n,、]/).map(e=>e.trim()).filter(Boolean)}function ge(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 we(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 ye(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 Re(t,e,r){const n=await H(t.workDir),a=[];for(const i of w(e)){const s=await ve(n,t,e,i);if(r?.(s),!(!s.ok||s.interrupted))for(const u of s.newMessages??[]){const o=z(i,u,e);o&&a.push(o)}}return a}async function ve(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:_(e,r)},s=await t.runWechatRpaReadLatest(i),u=Array.isArray(s.data?.structuredMessages)?s.data.structuredMessages:[],o=s.validation?.deterministic?.latestMessages??[],f=u.length?u.map((d,l)=>Se(d,s.runId||"lab-read",l)):o.map((d,l)=>({id:`${s.runId||"lab-read"}:${l}`,text:d,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:f,newMessages:f,screenshotPath:s.tracePath,error:j(s)}}function Se(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:Pe(t)}}function Pe(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 Ie(t,e,r,n,a,i){const s=await H(t.workDir),u=Ae({groupName:r,text:n,attachmentPath:a,dedupeKey:i,requestId:i,policy:"work"}),o=[];for(const m of u){const v=await s.runWechatRpaTask(m);if(o.push(v),!v.ok)break}const f=o.find(m=>m.kind==="send-text"),d=o.find(m=>m.kind==="send-image"||m.kind==="send-video"||m.kind==="send-file"),l=u.length>0&&o.length===u.length&&o.every(m=>m.ok),c=o.some(m=>m.status==="interrupted");return{ok:l,groupName:r,interrupted:c,reason:o.at(-1)?.status,rpaRunId:o.at(-1)?.runId,rpaTraceSummary:`send ${l?"success":o.at(-1)?.status||"failed"} ${r} (${o.length}/${u.length})`,sentReply:!!n,sentReplyObserved:n?!!f?.ok:!1,sentAttachment:!!a,sentAttachmentObserved:a?!!d?.ok:!1,postSendScreenshotPath:o.at(-1)?.tracePath,error:l?void 0:j(o.at(-1))}}function Ae(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=Ce(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 Ce(t){const e=p.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 H(t){const e=U(t);return await import(Q(e).href)}function U(t){const e=p.dirname(Y(import.meta.url)),n=[process.env.SHENNIAN_WECHAT_RPA_LAB_INDEX,p.resolve(e,"../../../../scripts/wechat-rpa-lab/index.mjs"),t?p.join(t,"scripts/wechat-rpa-lab/index.mjs"):"",p.resolve(process.cwd(),"scripts/wechat-rpa-lab/index.mjs")].filter(Boolean).find(a=>h.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 p.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 ke(t,e,r){const n=[];for(const a of w(e)){const i=await D({groupName:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:!!e.forceForeground,noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:_(t,e)});if(r?.(i),!i.interrupted)for(const s of i.newMessages??[]){const u=z(a,s,e);u&&n.push(u)}}return n}async function be(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 z(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:Ue(a),observedAt:s,isMentioned:e.isMentioned===!0||ge(n,B(r)),rawId:String(e.id||`${t}:${i}:${n}:${s}`)}}async function J(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"?Ie(t,e,r,n,a,i):D({groupName:r,replyText:n||void 0,attachmentPath:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:!!e.forceForeground,noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:_(t,e)})}function xe(t){return t==="macos-flow"||t==="wechat-rpa-lab"}function X(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 Ne(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 Me(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})),...ue({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}),R(t)}function G(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}`}),R(t)}function W(t,e){const r=Oe(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??[])Te(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 R(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{h.mkdirSync(m.dirname(t.pendingStatePath),{recursive:!0}),h.writeFileSync(t.pendingStatePath,JSON.stringify({version:1,pending:r,manualReview:n,completedKeys:e},null,2))}catch{}}function Ee(t){try{const e=JSON.parse(h.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 Te(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 Oe(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 T(t,e){const r=_e(e);if(t.messageStatePath===r)return;t.messageStatePath=r;const n=Le(r);t.deduper=new F((n.messageIds??[]).filter(a=>typeof a=="string"&&a.length>0))}function $e(t){if(t.messageStatePath)try{h.mkdirSync(m.dirname(t.messageStatePath),{recursive:!0}),h.writeFileSync(t.messageStatePath,JSON.stringify({version:1,messageIds:t.deduper.snapshot()},null,2))}catch{}}function Le(t){try{const e=JSON.parse(h.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,messageIds:[]}}catch{return{version:1,messageIds:[]}}}function _e(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 O(t,e,r){t.lastInterruptedAt=new Date().toISOString(),t.lastError=null,t.consecutiveInterruptions+=1,t.runtimeState="interrupted",qe(t)&&(t.interruptionCooldownUntil=Date.now()+M,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 $(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:L(e.summary)}),t.recentTaskSummaries=t.recentTaskSummaries.slice(0,me)}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 L(t){return String(t||"").replace(/\s+/g," ").trim().slice(0,180)}function V(t,e){if(e.forceForeground)return!1;const r=t.interruptionCooldownUntil;return r?Date.now()<r?!0:(t.interruptionCooldownUntil=void 0,t.consecutiveInterruptions=0,!1):!1}function qe(t){return!Number.isFinite(N)||N<=0||!Number.isFinite(M)||M<=0?!1:t.consecutiveInterruptions>=N}function De(t){return!t||!h.existsSync(t)?[]:h.readFileSync(t,"utf-8").split(`
6
- `).map(e=>e.trim()).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(e=>!!e)}async function Fe(t,e){if(e.localPath){if(!h.existsSync(e.localPath))throw new Error(`WeChat RPA local attachment does not exist: ${e.localPath}`);return e.localPath}if(e.url)return Ke(t,e);throw new Error("WeChat RPA attachment requires localPath or url; dataBase64 is not accepted over Manager IPC")}async function Ke(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");h.mkdirSync(n,{recursive:!0});const a=He(e.name||"attachment"),i=I.createHash("sha256").update(r).digest("hex").slice(0,12),s=m.extname(a),u=s?a.slice(0,-s.length):a,o=m.join(n,`${u}-${i}${s}`);return h.existsSync(o)||h.writeFileSync(o,r),o}function He(t){return m.basename(t||"attachment").normalize("NFKC").replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").replace(/\s+/g," ").replace(/^[ ._]+|[ ._]+$/g,"")||"attachment"}function _(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 ee("wechat-rpa","attachments","inbound",n)}function Ue(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 je(...t){const e=new Set,r=[];for(const n of t.flat()){const a=`${n.channelId}
5
+ ${e.attachment?.size||0}`).digest("hex").slice(0,24)}function Me(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})),...ue({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}),R(t)}function G(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}`}),R(t)}function W(t,e){const r=Oe(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??[])Te(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 R(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{h.mkdirSync(p.dirname(t.pendingStatePath),{recursive:!0}),h.writeFileSync(t.pendingStatePath,JSON.stringify({version:1,pending:r,manualReview:n,completedKeys:e},null,2))}catch{}}function Ee(t){try{const e=JSON.parse(h.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 Te(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 Oe(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return p.join(t.workDir,".shennian","wechat-rpa-pending-replies",`${e}.json`)}function T(t,e){const r=_e(e);if(t.messageStatePath===r)return;t.messageStatePath=r;const n=Le(r);t.deduper=new F((n.messageIds??[]).filter(a=>typeof a=="string"&&a.length>0))}function $e(t){if(t.messageStatePath)try{h.mkdirSync(p.dirname(t.messageStatePath),{recursive:!0}),h.writeFileSync(t.messageStatePath,JSON.stringify({version:1,messageIds:t.deduper.snapshot()},null,2))}catch{}}function Le(t){try{const e=JSON.parse(h.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,messageIds:[]}}catch{return{version:1,messageIds:[]}}}function _e(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return p.join(t.workDir,".shennian","wechat-rpa-seen-messages",`${e}.json`)}function O(t,e,r){t.lastInterruptedAt=new Date().toISOString(),t.lastError=null,t.consecutiveInterruptions+=1,t.runtimeState="interrupted",qe(t)&&(t.interruptionCooldownUntil=Date.now()+M,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 $(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:L(e.summary)}),t.recentTaskSummaries=t.recentTaskSummaries.slice(0,pe)}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 L(t){return String(t||"").replace(/\s+/g," ").trim().slice(0,180)}function V(t,e){if(e.forceForeground)return!1;const r=t.interruptionCooldownUntil;return r?Date.now()<r?!0:(t.interruptionCooldownUntil=void 0,t.consecutiveInterruptions=0,!1):!1}function qe(t){return!Number.isFinite(N)||N<=0||!Number.isFinite(M)||M<=0?!1:t.consecutiveInterruptions>=N}function De(t){return!t||!h.existsSync(t)?[]:h.readFileSync(t,"utf-8").split(`
6
+ `).map(e=>e.trim()).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(e=>!!e)}async function Fe(t,e){if(e.localPath){if(!h.existsSync(e.localPath))throw new Error(`WeChat RPA local attachment does not exist: ${e.localPath}`);return e.localPath}if(e.url)return Ke(t,e);throw new Error("WeChat RPA attachment requires localPath or url; dataBase64 is not accepted over Manager IPC")}async function Ke(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=p.join(t,".uploads","wechat-rpa","outbound");h.mkdirSync(n,{recursive:!0});const a=He(e.name||"attachment"),i=I.createHash("sha256").update(r).digest("hex").slice(0,12),s=p.extname(a),u=s?a.slice(0,-s.length):a,o=p.join(n,`${u}-${i}${s}`);return h.existsSync(o)||h.writeFileSync(o,r),o}function He(t){return p.basename(t||"attachment").normalize("NFKC").replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").replace(/\s+/g," ").replace(/^[ ._]+|[ ._]+$/g,"")||"attachment"}function _(t,e){if(e.downloadAttachments===!1)return;const r=e.downloadAttachmentsDir?.trim();if(r)return p.resolve(r);const n=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return ee("wechat-rpa","attachments","inbound",n)}function Ue(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 je(...t){const e=new Set,r=[];for(const n of t.flat()){const a=`${n.channelId}
7
7
  ${n.conversationId}
8
- ${n.messageId}`;e.has(a)||(e.add(a),r.push(n))}return r}export{nt as WeChatRpaChannelAdapter,Ue as annotateWeChatRpaInboundAttachments,Fe as materializeWeChatRpaOutboundAttachment,Ae as planWeChatRpaLabSendTasks,_ as resolveWeChatRpaInboundAttachmentDir,pe as selectWeChatRpaLabReadKind,Ce as weChatRpaLabTaskKindForAttachment,ye as weChatRpaProductSourceHealth,we as windowsVisualFlowHealth};
8
+ ${n.messageId}`;e.has(a)||(e.add(a),r.push(n))}return r}export{nt as WeChatRpaChannelAdapter,Ue as annotateWeChatRpaInboundAttachments,Fe as materializeWeChatRpaOutboundAttachment,Ae as planWeChatRpaLabSendTasks,_ as resolveWeChatRpaInboundAttachmentDir,me as selectWeChatRpaLabReadKind,Ce as weChatRpaLabTaskKindForAttachment,ye as weChatRpaProductSourceHealth,we as windowsVisualFlowHealth};
@@ -0,0 +1,89 @@
1
+ import fs from 'node:fs';
2
+ import type { Command } from 'commander';
3
+ import { type WeChatChannelHelperRuntimePackageManifest } from '../channels/wechat-channel/helper-assets.js';
4
+ type RuntimeCommandPlatform = 'darwin' | 'win32';
5
+ export type HelperRuntimeInstallResult = {
6
+ ok: true;
7
+ action: 'installed' | 'already_installed';
8
+ platform: RuntimeCommandPlatform;
9
+ targetPlatform: RuntimeCommandPlatform;
10
+ installed: true;
11
+ repairable: false;
12
+ targetDir: string;
13
+ helperDir: string;
14
+ helperPath: string;
15
+ helperVersion: string;
16
+ runtimeVersion: string;
17
+ protocolVersion: number;
18
+ minCliVersion?: string;
19
+ sha256?: {
20
+ runtimeManifest: string;
21
+ entrypoint: string | null;
22
+ };
23
+ packageManifestPath?: string;
24
+ packageKind?: string;
25
+ installTarget?: WeChatChannelHelperRuntimePackageManifest['installTarget'];
26
+ signature?: WeChatChannelHelperRuntimePackageManifest['signature'];
27
+ message: string;
28
+ } | {
29
+ ok: false;
30
+ platform?: RuntimeCommandPlatform;
31
+ targetPlatform?: RuntimeCommandPlatform;
32
+ installed?: boolean;
33
+ repairable: boolean;
34
+ reasonCode: string;
35
+ message: string;
36
+ repairSuggestion?: string;
37
+ checkedSources?: string[];
38
+ };
39
+ export type HelperRuntimeStatusResult = {
40
+ ok: boolean;
41
+ platform?: RuntimeCommandPlatform;
42
+ targetPlatform?: RuntimeCommandPlatform;
43
+ installed: boolean;
44
+ repairable: boolean;
45
+ reasonCode?: string;
46
+ message: string;
47
+ helperDir?: string;
48
+ helperPath?: string;
49
+ helperVersion?: string;
50
+ runtimeVersion?: string;
51
+ protocolVersion?: number;
52
+ minCliVersion?: string;
53
+ sha256?: {
54
+ runtimeManifest: string;
55
+ entrypoint: string | null;
56
+ };
57
+ packageManifestPath?: string;
58
+ packageKind?: string;
59
+ installTarget?: WeChatChannelHelperRuntimePackageManifest['installTarget'];
60
+ signature?: WeChatChannelHelperRuntimePackageManifest['signature'];
61
+ runtimeRoot?: string | null;
62
+ repairSuggestion?: string;
63
+ checkedSources?: string[];
64
+ };
65
+ type RuntimeIo = {
66
+ existsSync?: typeof fs.existsSync;
67
+ readFileSync?: typeof fs.readFileSync;
68
+ mkdirSync?: typeof fs.mkdirSync;
69
+ rmSync?: typeof fs.rmSync;
70
+ cpSync?: typeof fs.cpSync;
71
+ renameSync?: typeof fs.renameSync;
72
+ chmodSync?: typeof fs.chmodSync;
73
+ homedir?: () => string;
74
+ };
75
+ export declare function registerRuntimeCommand(program: Command): void;
76
+ export declare function getHelperRuntimeStatus(input?: {
77
+ platform?: NodeJS.Platform | string;
78
+ env?: NodeJS.ProcessEnv;
79
+ homedir?: string;
80
+ }): HelperRuntimeStatusResult;
81
+ export declare function installHelperRuntime(input?: {
82
+ source?: string;
83
+ platform?: NodeJS.Platform | string;
84
+ env?: NodeJS.ProcessEnv;
85
+ homedir?: string;
86
+ force?: boolean;
87
+ io?: RuntimeIo;
88
+ }): HelperRuntimeInstallResult;
89
+ export {};
@@ -0,0 +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};
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerWeChatCommand(program: Command): void;
@@ -0,0 +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};
@@ -0,0 +1,14 @@
1
+ import type { ExternalMessageEvent } from '../../channels/base.js';
2
+ import { type WeChatReadLatestOptions, type WeChatSendOptions, type WeChatSendResult, type WeChatToolBinding } from './types.js';
3
+ export declare function sendDirectWeChatMessageOnce(binding: WeChatToolBinding, options: WeChatSendOptions): Promise<{
4
+ pending?: boolean;
5
+ sendStatus?: WeChatSendResult['sendStatus'];
6
+ outDir?: string;
7
+ helperTracePath?: string | null;
8
+ }>;
9
+ export declare function readDirectWeChatLatestOnce(binding: WeChatToolBinding, options: WeChatReadLatestOptions): Promise<{
10
+ messages: ExternalMessageEvent[];
11
+ outDir: string;
12
+ helperTracePath: string | null;
13
+ activityGuardPath?: string | null;
14
+ }>;
@@ -0,0 +1,2 @@
1
+ import p from"node:fs";import f from"node:path";import{observedMessageToExternalEvent as P,weChatChannelBindingId as g}from"../../channels/wechat-rpa/product-channel.js";import{loadConfig as v}from"../../config/index.js";import{runWeChatChannelActionSmoke as w}from"../../devtools/wechat-channel-action-smoke.js";import{WeChatToolOperationError as k}from"./types.js";import{currentWeChatPlatform as E,isRecord as y,normalizeSendStatus as S,safePathSegment as A}from"./utils.js";async function B(e,t){const r=T(t.attachment),o=_(e,t,{kind:"send",steps:["open-read",r],text:t.text,attachment:t.attachment}),s=await w(o),a=s.steps.find(c=>c.step===r);if(!s.ok||!a?.ok){if(O(a))return{pending:!0,sendStatus:"sent_unconfirmed",outDir:s.outDir,helperTracePath:s.helperTracePath};const c=s.steps.find(h=>!h.ok&&!h.skipped),i=a?.reasonCode==="open_read_required"?c??a:a??c,u=i?.reasonCode||"wechat_direct_send_failed",m=i?.errorSummary||u;throw new k({reasonCode:u,message:m,outDir:s.outDir,helperTracePath:s.helperTracePath,traceId:t.traceId})}const n=S(a.details?.sendStatus)??"sent_unconfirmed";return{pending:n==="queued"||n==="pending"||n==="sent_unconfirmed",sendStatus:n,outDir:s.outDir,helperTracePath:s.helperTracePath}}async function H(e,t){const r=[],o=t.download!=="never",s=o?"download-visible-media":"structure-window",a=_(e,t,{kind:"read",steps:[s],allowEmptyMedia:o,activityGuardEvents:r}),n=await w(a),c=r.length>0?N(n.outDir,r):null,i=n.steps.find(d=>d.step===s),u=f.join(n.outDir,o?"download-visible-messages.json":"structure-window-messages.json"),m=W(u),h=o&&m.length>0&&(i?.reasonCode==="media_original_pending_or_metadata_only"||i?.reasonCode==="only_preview_media_downloaded");if(!n.ok&&i?.reasonCode!=="no_observed_messages"&&!h){const d=i?.reasonCode||n.steps.find(l=>!l.ok&&!l.skipped)?.reasonCode||"wechat_direct_read_failed",I=i?.errorSummary||n.steps.find(l=>!l.ok&&!l.skipped)?.errorSummary||d;throw new k({reasonCode:d,message:I,outDir:n.outDir,helperTracePath:n.helperTracePath,activityGuardPath:c,traceId:t.traceId})}const C=x(e,t),D=M(e);return{messages:m.map(d=>P(C,D,d)).filter(d=>d!=null),outDir:n.outDir,helperTracePath:n.helperTracePath,activityGuardPath:c}}function T(e){return e?e.kind==="image"?"send-image":e.kind==="video"?"send-video":"send-file":"send-text"}function _(e,t,r){const o=t.traceId||`wechat-cli-${r.kind}-${Date.now().toString(36)}`,s=f.join(e.workDir,"runs",A(o)),a=r.attachment;return{conversation:e.conversationName,mode:"custom",steps:r.steps,outDir:s,workDir:e.workDir,traceId:o,runtimeId:e.channelId,machineId:process.env.SHENNIAN_MACHINE_ID||v().machineId||"local",sessionId:e.sessionId,serverUrl:v().serverUrl,platform:E(),text:r.text,...a?.localPath&&a.kind==="image"?{imagePath:a.localPath}:{},...a?.localPath&&a.kind==="video"?{videoPath:a.localPath}:{},...a?.localPath&&a.kind!=="image"&&a.kind!=="video"?{filePath:a.localPath}:{},allowEmptyMedia:r.allowEmptyMedia,...r.kind==="read"?{activityGuard:{useAutomationLease:!0,onEvent:r.activityGuardEvents?n=>r.activityGuardEvents?.push(n):void 0}}:{},skipRuntimeUpsert:!0,requireServer:!1}}function N(e,t){const r=f.join(e,"activity-guard.json");return p.mkdirSync(f.dirname(r),{recursive:!0}),p.writeFileSync(r,`${JSON.stringify({schema:"wechat-direct-read-activity-guard/v1",events:t},null,2)}
2
+ `,"utf8"),r}function x(e,t){return{id:e.channelId,type:"wechat-rpa",name:`WeChat ${e.conversationName}`,sessionId:e.sessionId,managerSessionId:e.sessionId,workDir:e.workDir,enabled:!0,secretRef:`wechat-cli:${e.channelId}`}}function M(e){return{bindingId:g(e.channelId,e.conversationName),sessionId:e.sessionId,conversationDisplayName:e.conversationName,enabled:!0,allowReply:!0,downloadMedia:!0}}function W(e){try{const t=JSON.parse(p.readFileSync(e,"utf-8"));return Array.isArray(t)?t.filter(y):[]}catch{return[]}}function O(e){if(e?.reasonCode!=="sent_text_not_observed")return!1;const t=y(e.details)?e.details:{},r=t.sendAccepted===!0,o=S(t.sendStatus);return r&&(o==="sent_unconfirmed"||o==="sent"||o==="ok")}export{H as readDirectWeChatLatestOnce,B as sendDirectWeChatMessageOnce};
@@ -0,0 +1,2 @@
1
+ import type { WeChatDoctorOptions, WeChatDoctorResult } from './types.js';
2
+ export declare function runWeChatDoctor(options?: WeChatDoctorOptions): Promise<WeChatDoctorResult>;
@@ -0,0 +1 @@
1
+ import{loadConfig as k}from"../../config/index.js";import{SERVERS as g}from"../../region.js";import{isRecord as l,stringValue as y}from"./utils.js";async function U(e={}){const r=k(),t=_(e.serverUrl||r.serverUrl||g.cn.url),c=e.machineToken||r.machineToken,o=e.machineId||r.machineId,m=e.fetchImpl||fetch,n=[s("cli_installed","Shennian CLI is installed."),c?s("machine_paired","Machine token is configured."):h("machine_paired","machine_not_paired","Machine is not paired. Run shennian pair first.")];if(!c)return u({checks:n,serverUrl:t,machineId:o});try{const a=await m(`${t}/api/channels/wechat/runtime-policy`,{headers:{authorization:`Bearer ${c}`}}),i=await a.json().catch(()=>null);if(!a.ok||i?.ok===!1){const d=y(i?.reasonCode)||a.statusText||"request_failed";return n.push(h("server_policy",d,`WeChat runtime policy is blocked: ${d}`)),u({checks:n,serverUrl:t,machineId:o})}const f=l(i?.usagePolicy)?i.usagePolicy:{};n.push(s("server_policy","Server WeChat policy is reachable.")),n.push(s("entitlement","Account has Pro / Team external channel entitlement.")),n.push(s("credits",f.billingMode==="credits"?"Credit billing is enabled; balance is checked by server calls.":"Credits are not metered in current billing mode."));const p=l(i?.runtime)&&l(i.runtime.current)?i.runtime.current:null;n.push(p?s("runtime_registered","Local WeChat runtime is registered on the server."):v("runtime_registered","runtime_not_registered","Local WeChat runtime is not registered yet; open Shennian Desktop/daemon and enable Use WeChat."))}catch(a){n.push(h("server_policy","network_unavailable",a instanceof Error?a.message:"Unable to reach Shennian server."))}return u({checks:n,serverUrl:t,machineId:o})}function u(e){return{ok:e.checks.every(r=>r.ok||r.status==="warning"),checks:e.checks,serverUrl:e.serverUrl,machineId:e.machineId}}function s(e,r){return{id:e,ok:!0,status:"ready",message:r}}function v(e,r,t){return{id:e,ok:!1,status:"warning",reasonCode:r,message:t}}function h(e,r,t){return{id:e,ok:!1,status:"blocked",reasonCode:r,message:t}}function _(e){return e.replace(/\/+$/,"")}export{U as runWeChatDoctor};
@@ -0,0 +1,9 @@
1
+ import type { IpcContext, IpcResponse, WeChatManagerIpcResolution, WeChatToolBinding, WeChatToolOptions } from './types.js';
2
+ export declare function loadManagerIpcFromRuntimeFile(): IpcContext;
3
+ export declare function tryLoadManagerIpcFromRuntimeFile(): IpcContext | null;
4
+ export declare function resolveWeChatManagerIpc(options: WeChatToolOptions): WeChatManagerIpcResolution | null;
5
+ export declare function shouldFallbackManagerToolToDirect(options: WeChatToolOptions, error: unknown): boolean;
6
+ export declare function ensureWeChatToolChannel(ctx: IpcContext, binding: WeChatToolBinding, options: WeChatToolOptions): Promise<IpcResponse>;
7
+ export declare function ipc(ctx: IpcContext, pathname: string, body: Record<string, unknown>, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<IpcResponse>;
8
+ export declare function buildWeChatToolBinding(options: WeChatToolOptions): WeChatToolBinding;
9
+ export declare function trySyncWeChatToolChannel(ctx: IpcContext, binding: WeChatToolBinding, options: WeChatToolOptions): Promise<IpcResponse | null>;
@@ -0,0 +1 @@
1
+ import p from"node:fs";import d from"node:path";import{resolveShennianPath as m}from"../../config/index.js";import{weChatChannelConversationId as w}from"../../channels/wechat-rpa/product-channel.js";import{clampPositiveInteger as h,isAbortError as I,processExists as g,safePathSegment as y,stableId as v}from"./utils.js";function c(){const r=m("runtime","manager-ipc.json");let e;try{e=JSON.parse(p.readFileSync(r,"utf-8"))}catch{throw new Error("Manager IPC is not available. Open Shennian Desktop or run `shennian start` first.")}const t=Number(e.pid);if(Number.isInteger(t)&&t>0&&!g(t))throw new Error(`Manager IPC runtime file is stale for PID ${t}. Restart Shennian Desktop or run \`shennian restart\`.`);const n=typeof e.url=="string"?e.url.replace(/\/+$/,""):"",a=typeof e.token=="string"?e.token:"";if(!n||!a)throw new Error(`Manager IPC runtime file is incomplete: ${r}`);return{url:n,token:a}}function P(){try{return c()}catch{return null}}function $(r){if(r.directRuntime)return null;const e=r.transport??"auto";return e==="direct"?null:r.ipc?{ctx:r.ipc,mode:"session"}:r.sessionId?.trim()?{ctx:c(),mode:"session"}:e==="daemon"?{ctx:c(),mode:"tool"}:null}function E(r,e){if((r.transport??"auto")!=="auto"||r.sessionId?.trim()||r.ipc)return!1;const n=e instanceof Error?e.message:String(e||"");return/Unknown manager IPC path:\s*\/wechat-rpa\/tool\//i.test(n)}async function T(r,e,t){return f(r,"/wechat-rpa/channel/upsert",{managerSessionId:e.sessionId,id:e.channelId,name:`WeChat ${e.conversationName}`,workDir:e.workDir,enabled:!0,groups:[{name:e.conversationName}],canReply:!0,source:t.source||"wechat-channel",recentLimit:h(t.recentLimit,20),pollIntervalMs:h(t.pollIntervalMs,3e4),forceForeground:!0,downloadAttachments:t.download!=="never",privacyConsentAccepted:!0},t.fetchImpl,t.timeoutMs)}async function f(r,e,t,n=fetch,a){const l=String(t.managerSessionId||"");if(!l)throw new Error("managerSessionId is required");const s=a?new AbortController:null,u=s?setTimeout(()=>s.abort(),a):null;try{const o=await n(`${r.url}${e}`,{method:"POST",headers:{authorization:`Bearer ${r.token}`,"content-type":"application/json","x-shennian-manager-session-id":l},body:JSON.stringify(t),signal:s?.signal}),i=await o.json().catch(()=>({ok:!1,error:o.statusText}));if(!o.ok||!i.ok)throw new Error(i.error||`Manager IPC failed: ${o.status}`);return i}finally{u&&clearTimeout(u)}}function D(r){const e=r.conversation.trim();if(!e)throw new Error("--conversation is required");const t=v("wechat-cli-conversation",e),n=r.sessionId?.trim()||`wechat-cli-${t.slice(-24)}`,a=`wechat-rpa:${n}`;return{sessionId:n,channelId:a,conversationId:w(e),conversationName:e,workDir:d.resolve(r.workDir||m("wechat-cli",y(e)))}}async function F(r,e,t){try{return await f(r,"/wechat-rpa/channel/sync",{managerSessionId:e.sessionId},t.fetchImpl,t.timeoutMs)}catch(n){if(I(n))return null;throw n}}export{D as buildWeChatToolBinding,T as ensureWeChatToolChannel,f as ipc,c as loadManagerIpcFromRuntimeFile,$ as resolveWeChatManagerIpc,E as shouldFallbackManagerToolToDirect,P as tryLoadManagerIpcFromRuntimeFile,F as trySyncWeChatToolChannel};
@@ -0,0 +1,3 @@
1
+ import type { WeChatReadLatestOptions, WeChatReadLatestResult, WeChatSendOptions, WeChatSendResult } from './types.js';
2
+ export declare function runWeChatSend(options: WeChatSendOptions): Promise<WeChatSendResult>;
3
+ export declare function runWeChatReadLatest(options: WeChatReadLatestOptions): Promise<WeChatReadLatestResult>;
@@ -0,0 +1,4 @@
1
+ import{readDirectWeChatLatestOnce as D,sendDirectWeChatMessageOnce as P}from"./direct.js";import{buildWeChatToolBinding as w,ensureWeChatToolChannel as y,ipc as g,resolveWeChatManagerIpc as C,shouldFallbackManagerToolToDirect as p,trySyncWeChatToolChannel as R}from"./ipc.js";import{clampPositiveInteger as k,isExternalMessageEvent as N,isRecord as v,normalizeSendStatus as T,stableId as W,stringValue as u}from"./utils.js";async function S(t){const s=t.text.trim();if(!s&&!t.attachment)throw new Error("Message text or attachment is required");const e=w(t),n=t.attachment?b(t.attachment):void 0;if(t.dryRun)return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:e.workDir,helperTracePath:null,sendStatus:"dry_run",pending:!1,attachment:n,attachments:n?[n]:[],dryRun:!0,reasonCode:null,message:"Dry run completed. No WeChat message was sent."};const d=C(t);if(!d){const r=t.directRuntime?await t.directRuntime.send(e,t):await P(e,t);return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:r.outDir??e.workDir,helperTracePath:r.helperTracePath??null,sendStatus:r.sendStatus??(r.pending?"pending":"sent"),pending:r.pending===!0,attachment:n,attachments:n?[n]:[],reasonCode:null,message:r.pending?"WeChat message is queued or waiting for confirmation.":"WeChat message sent."}}if(d.mode==="tool"){let r;try{r=await g(d.ctx,"/wechat-rpa/tool/send",{managerSessionId:e.sessionId,binding:e,text:s,attachment:t.attachment,traceId:t.traceId,timeoutMs:t.timeoutMs},t.fetchImpl,t.timeoutMs)}catch(h){if(p(t,h))return S({...t,transport:"direct"});throw h}const l=T(r.sendStatus)??(r.pending===!0?"pending":"sent");return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:u(r.outDir)||e.workDir,helperTracePath:u(r.helperTracePath)||null,sendStatus:l,pending:r.pending===!0,attachment:n,attachments:n?[n]:[],reasonCode:null,message:r.pending?"WeChat message is queued or waiting for confirmation.":"WeChat message sent."}}const m=d.ctx;await y(m,e,t);const c=t.idempotencyKey||W("wechat-cli-send",`${e.channelId}
2
+ ${e.conversationId}
3
+ ${s}
4
+ ${Date.now()}`),i=await g(m,"/external/reply",{managerSessionId:e.sessionId,channelId:e.channelId,conversationId:e.conversationId,text:s,attachment:t.attachment,idempotencyKey:c},t.fetchImpl,t.timeoutMs),o=await R(m,e,t),a=o?x(o,c,i.pending===!0):i.pending===!0?"queued":"ok";return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:e.workDir,helperTracePath:null,sendStatus:a,pending:i.pending===!0&&(a==="queued"||a==="pending"),attachment:n,attachments:n?[n]:[],reasonCode:null,message:o?a==="confirmed_echo"?"WeChat message sent and confirmed.":a==="manual_review"?"WeChat message was submitted but needs manual review.":"WeChat message submitted to local daemon.":"WeChat message submitted to local daemon; confirmation sync timed out."}}async function M(t){const s=k(t.limit,10),e=w(t),n=C(t);if(!n){const a=t.directRuntime?{messages:await t.directRuntime.readLatest(e,{...t,limit:s,recentLimit:t.recentLimit??s}),outDir:e.workDir,helperTracePath:null,activityGuardPath:null}:await D(e,{...t,limit:s,recentLimit:t.recentLimit??s}),r=a.messages.filter(h=>h.conversationId===e.conversationId||h.conversationName===e.conversationName).slice(-s),l=f(r);return{ok:!0,operation:"read",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:a.outDir,helperTracePath:a.helperTracePath,activityGuardPath:a.activityGuardPath??null,messages:r,attachments:l,count:r.length,reasonCode:null,message:`Read ${r.length} WeChat message(s).`}}if(n.mode==="tool"){let a;try{a=await g(n.ctx,"/wechat-rpa/tool/read",{managerSessionId:e.sessionId,binding:e,limit:s,download:t.download,traceId:t.traceId,timeoutMs:t.timeoutMs},t.fetchImpl,t.timeoutMs)}catch(I){if(p(t,I))return M({...t,transport:"direct"});throw I}const l=(Array.isArray(a.messages)?a.messages.filter(N):[]).filter(I=>I.conversationId===e.conversationId||I.conversationName===e.conversationName).slice(-s),h=f(l);return{ok:!0,operation:"read",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:u(a.outDir)||e.workDir,helperTracePath:u(a.helperTracePath)||null,activityGuardPath:u(a.activityGuardPath)||null,messages:l,attachments:h,count:l.length,reasonCode:null,message:`Read ${l.length} WeChat message(s).`}}const d=n.ctx;await y(d,e,{...t,recentLimit:t.recentLimit??s});const m=await g(d,"/wechat-rpa/channel/sync",{managerSessionId:e.sessionId},t.fetchImpl,t.timeoutMs),i=(Array.isArray(m.messages)?m.messages.filter(N):[]).filter(a=>a.conversationId===e.conversationId||a.conversationName===e.conversationName).slice(-s),o=f(i);return{ok:!0,operation:"read",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:e.workDir,helperTracePath:null,activityGuardPath:null,messages:i,attachments:o,count:i.length,reasonCode:null,message:`Read ${i.length} WeChat message(s).`}}function x(t,s,e){const n=v(t.channel)?t.channel:{},m=(Array.isArray(n.wechatRpaPendingReplies)?n.wechatRpaPendingReplies:[]).filter(v).find(a=>u(a.idempotencyKey)===s),c=u(m?.status);if(c==="confirmed_echo"||c==="sent_unconfirmed"||c==="manual_review"||c==="queued")return c;const o=(Array.isArray(n.wechatRpaRecentTaskSummaries)?n.wechatRpaRecentTaskSummaries:[]).filter(v).map(a=>u(a.status)).find(Boolean);return o==="sent"?"sent":o==="confirmed_echo"?"confirmed_echo":o==="manual_review"?"manual_review":e?"pending":"ok"}function b(t){return{kind:t.kind,name:t.name,mimeType:t.mimeType,size:t.size,localPath:t.localPath}}function f(t){return t.flatMap(s=>s.attachments??[])}export{M as runWeChatReadLatest,S as runWeChatSend};
@@ -0,0 +1,7 @@
1
+ import type { WeChatReadLatestResult, WeChatSendResult, WeChatToolErrorResult } from './types.js';
2
+ export type WeChatReadOutputFormat = 'markdown' | 'json';
3
+ export declare function printToolResult(result: WeChatSendResult, json?: boolean): void;
4
+ export declare function printReadLatestResult(result: WeChatReadLatestResult, format: WeChatReadOutputFormat): void;
5
+ export declare function printToolError(operation: 'read' | 'write', conversation: string | undefined, error: unknown, json?: boolean): void;
6
+ export declare function buildWeChatToolErrorResult(operation: 'read' | 'write', conversation: string | undefined, error: unknown): WeChatToolErrorResult;
7
+ export declare function buildWeChatReadMarkdown(result: WeChatReadLatestResult): string;
@@ -0,0 +1,4 @@
1
+ import v from"node:path";import{cleanInline as a,stringValue as o}from"./utils.js";const b=["SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HELPER_RUNTIME_DIR","packages/helper-runtime/wechat-channel","packages\\helper-runtime\\wechat-channel","packages/helper-runtime/dist","packages\\helper-runtime\\dist","wechat-rpa-windows-product-send-runner","PowerShell one-shot runner"];function I(e,i){if(i){console.log(JSON.stringify(e,null,2));return}console.log(e.sendStatus)}function L(e,i){if(i==="json"){console.log(JSON.stringify(e,null,2));return}console.log(k(e).trimEnd())}function H(e,i,n,t){const r=y(e,i,n);t?console.log(JSON.stringify(r,null,2)):console.error(`${r.reasonCode}: ${r.message}`),process.exitCode=1}function y(e,i,n){const t=n instanceof Error?n.message:String(n||"wechat_tool_failed"),r=a(t)||"wechat_tool_failed",c=/^([a-z][a-z0-9_]*)(?::\s*(.*))?$/i.exec(r),s=o(n?.reasonCode)||c?.[1]||"wechat_tool_failed",l=W(s,r),w=a(c?.[2]||r),g=E(l,w),_=o(n?.outDir),d=o(n?.helperTracePath),p=o(n?.activityGuardPath),h=o(n?.traceId),m=q(l);return{ok:!1,operation:e,...i?.trim()?{conversation:i.trim()}:{},reasonCode:l,...s!==l&&!u(s)?{rawReasonCode:s}:{},message:g,...h?{traceId:h}:{},..._?{outDir:_}:{},...d?{helperTracePath:d}:{},...p?{activityGuardPath:p}:{},...m?{userAction:m}:{}}}function k(e){const i=[];if(e.messages.length===0)return`\uFF08\u6CA1\u6709\u8BFB\u5230\uFF09
2
+ `;for(const n of e.messages)i.push(`${S(n)}: ${$(n)}`);return`${i.join(`
3
+ `)}
4
+ `}function W(e,i=""){const n=e.trim().toLowerCase(),t=`${n} ${i}`.toLowerCase();return!n||u(t)?"wechat_tool_failed":n==="insufficient_credits"?"insufficient_credits":n==="entitlement_required"||n==="enterprise_required"||n.includes("entitlement")?"entitlement_required":n==="manual_review_required"||n.includes("manual_review")?"manual_review_required":n==="attachment_pending"||/attachment.*pending|media_original_pending/.test(t)?"attachment_pending":/helper_runtime_required|helper_runtime_source_missing|helper_runtime_package_manifest_missing|manifest_missing|helper_missing|helper_not_executable/.test(n)?"helper_missing":/helper_runtime_cli_too_old|helper_runtime_version_mismatch|helper_runtime_protocol_mismatch|helper_runtime_platform_mismatch|helper_runtime_package_manifest_invalid|integrity_mismatch/.test(n)?"helper_version_incompatible":/^user_active|recent_(keyboard|mouse|scroll)|frontmost_app_changed|user_takeover|activity/.test(n)?"user_active_retry_later":/permission|accessibility|screen_recording|automation|mac_input|input-monitoring/.test(t)?"permission_required":/wechat_login_required|login_required|安全验证|扫码登录|重新登录/.test(t)?"wechat_login_required":/wechat_single_main_window_required|wechat_duplicate_instance|duplicate_main_window/.test(n)?"wechat_single_main_window_required":/visible_desktop|desktop_session|screen_locked|session_locked|rdp_disconnected|rdp.*visible|rdp.*desktop/.test(t)?"windows_visible_desktop_unavailable":/dpi_mapping_failed|display_topology|monitor_topology|multi_?monitor|multi_?screen/.test(t)?"dpi_mapping_failed":/conversation_not|conversation_title_not_confirmed|conversation_not_visible|open_read_required|search_result_not_visible/.test(n)?"conversation_not_found":/wechat_window|wechat_not_running|window_not|window_unavailable|window_unresponsive|helper_command_timeout/.test(n)?"wechat_window_unavailable":n}function q(e){if(e==="wechat_login_required")return"Open WeChat on this machine, complete login or security verification, then retry.";if(e==="wechat_window_unavailable")return"Open the main WeChat window on the active desktop, then retry.";if(e==="wechat_single_main_window_required")return"Close duplicate WeChat main windows, login prompts, or security prompts, then retry with exactly one signed-in WeChat main window.";if(e==="windows_visible_desktop_unavailable")return"Unlock or reconnect the Windows visible desktop session, keep WeChat visible, then retry.";if(e==="dpi_mapping_failed")return"Move WeChat fully onto one visible display or use a supported display scaling setup, then retry.";if(e==="conversation_not_found")return"Check the WeChat conversation name and make sure the conversation can be searched.";if(e==="helper_missing")return"Open Shennian Desktop or run `shennian runtime install helper`, then retry.";if(e==="helper_version_incompatible")return"Run `shennian runtime repair helper` or upgrade Shennian CLI, then retry.";if(e==="permission_required")return"Grant Shennian Helper/Desktop the required macOS permissions, then retry.";if(e==="entitlement_required")return"Enable the WeChat capability for this account in Shennian.";if(e==="insufficient_credits")return"Add credits or adjust the account plan in Shennian.";if(e==="user_active_retry_later")return"Wait until the user is idle, then retry.";if(e==="attachment_pending")return"Open WeChat and let the attachment finish downloading, then retry.";if(e==="manual_review_required")return"Review the current WeChat window before retrying."}function E(e,i){return!i||i===e||u(i)?R(e):i}function u(e){const i=e.replace(/\\/g,"/").toLowerCase();return b.some(n=>{const t=n.replace(/\\/g,"/").toLowerCase();return i.includes(t)})}function R(e){return e==="wechat_login_required"?"WeChat needs login or security verification on this machine.":e==="wechat_window_unavailable"?"WeChat window is not available on the active desktop.":e==="wechat_single_main_window_required"?"Exactly one signed-in WeChat main window is required.":e==="windows_visible_desktop_unavailable"?"Windows visible desktop session is unavailable.":e==="dpi_mapping_failed"?"Windows display scaling or monitor geometry cannot be mapped safely.":e==="conversation_not_found"?"WeChat conversation was not found.":e==="helper_missing"?"Shennian Helper runtime is not installed or cannot be found.":e==="helper_version_incompatible"?"Shennian Helper runtime is incompatible with this CLI.":e==="permission_required"?"Required local permissions are missing.":e==="entitlement_required"?"WeChat capability is not enabled for this account.":e==="insufficient_credits"?"Insufficient credits.":e==="user_active_retry_later"?"User activity is in progress; retry later.":e==="attachment_pending"?"WeChat attachment is still pending.":e==="manual_review_required"?"Manual review is required before retrying.":e}function $(e){const i=e.attachments||[],n=a(e.text);return n&&i.length===0?n:n&&i.length>0?`${n} ${i.map(f).join("\u3001")}`:i.length>0?i.map(f).join("\u3001"):"\uFF08\u6D88\u606F\uFF09"}function S(e){return a(e.sender.name||e.sender.id||"\u5BF9\u65B9")}function f(e){const i=a(e.name||v.basename(e.localPath||"")||e.type||"\u9644\u4EF6"),n=e.localPath||e.url||"",t=e.availability&&e.availability!=="edge-local"?` (${e.availability})`:"";return n?`[${P(i)}](${C(n)})${t}`:`${i}${t}`}function C(e){const i=e.replace(/\\/g,"/");return/[\s()<>]/.test(i)?`<${i.replace(/[<>]/g,"")}>`:i}function P(e){return a(e).replace(/([\\\]])/g,"\\$1")}export{k as buildWeChatReadMarkdown,y as buildWeChatToolErrorResult,L as printReadLatestResult,H as printToolError,I as printToolResult};
@@ -0,0 +1,141 @@
1
+ import type { ExternalMessageAttachment, ExternalMessageEvent, ExternalReplyAttachment } from '../../channels/base.js';
2
+ export type WeChatDoctorCheck = {
3
+ id: string;
4
+ ok: boolean;
5
+ status: 'ready' | 'blocked' | 'warning';
6
+ reasonCode?: string;
7
+ message: string;
8
+ };
9
+ export type WeChatDoctorResult = {
10
+ ok: boolean;
11
+ checks: WeChatDoctorCheck[];
12
+ serverUrl?: string;
13
+ machineId?: string;
14
+ };
15
+ export type WeChatDoctorOptions = {
16
+ serverUrl?: string;
17
+ machineToken?: string;
18
+ machineId?: string;
19
+ fetchImpl?: typeof fetch;
20
+ };
21
+ export type IpcContext = {
22
+ url: string;
23
+ token: string;
24
+ };
25
+ export type IpcResponse = {
26
+ ok: boolean;
27
+ error?: string;
28
+ channel?: unknown;
29
+ messages?: unknown;
30
+ [key: string]: unknown;
31
+ };
32
+ export type WeChatToolBinding = {
33
+ sessionId: string;
34
+ channelId: string;
35
+ conversationId: string;
36
+ conversationName: string;
37
+ workDir: string;
38
+ };
39
+ export type WeChatManagerIpcResolution = {
40
+ ctx: IpcContext;
41
+ mode: 'session' | 'tool';
42
+ };
43
+ export type WeChatToolTransport = 'auto' | 'daemon' | 'direct';
44
+ export type WeChatToolOptions = {
45
+ conversation: string;
46
+ sessionId?: string;
47
+ workDir?: string;
48
+ source?: string;
49
+ download?: 'auto' | 'never';
50
+ transport?: WeChatToolTransport;
51
+ traceId?: string;
52
+ timeoutMs?: number;
53
+ recentLimit?: number;
54
+ pollIntervalMs?: number;
55
+ fetchImpl?: typeof fetch;
56
+ ipc?: IpcContext;
57
+ directRuntime?: WeChatDirectToolRuntime;
58
+ };
59
+ export type WeChatDirectToolRuntime = {
60
+ readLatest(binding: WeChatToolBinding, options: WeChatReadLatestOptions): Promise<ExternalMessageEvent[]>;
61
+ send(binding: WeChatToolBinding, options: WeChatSendOptions): Promise<{
62
+ pending?: boolean;
63
+ sendStatus?: WeChatSendResult['sendStatus'];
64
+ outDir?: string;
65
+ helperTracePath?: string | null;
66
+ }>;
67
+ };
68
+ export type WeChatSendOptions = WeChatToolOptions & {
69
+ text: string;
70
+ attachment?: ExternalReplyAttachment;
71
+ idempotencyKey?: string;
72
+ dryRun?: boolean;
73
+ };
74
+ export type WeChatReadLatestOptions = WeChatToolOptions & {
75
+ limit?: number;
76
+ };
77
+ export type WeChatSendResult = {
78
+ ok: true;
79
+ operation: 'write';
80
+ conversation: string;
81
+ conversationName: string;
82
+ conversationId: string;
83
+ sessionId: string;
84
+ channelId: string;
85
+ traceId?: string;
86
+ outDir: string;
87
+ helperTracePath?: string | null;
88
+ sendStatus: 'dry_run' | 'queued' | 'sent' | 'sent_unconfirmed' | 'confirmed_echo' | 'manual_review' | 'pending' | 'ok';
89
+ pending?: boolean;
90
+ attachment?: Pick<ExternalReplyAttachment, 'kind' | 'name' | 'mimeType' | 'size' | 'localPath'>;
91
+ attachments: Array<Pick<ExternalReplyAttachment, 'kind' | 'name' | 'mimeType' | 'size' | 'localPath'>>;
92
+ dryRun?: boolean;
93
+ reasonCode?: null;
94
+ message?: string;
95
+ };
96
+ export type WeChatReadLatestResult = {
97
+ ok: true;
98
+ operation: 'read';
99
+ conversation: string;
100
+ conversationName: string;
101
+ conversationId: string;
102
+ sessionId: string;
103
+ channelId: string;
104
+ traceId?: string;
105
+ outDir: string;
106
+ helperTracePath?: string | null;
107
+ activityGuardPath?: string | null;
108
+ messages: ExternalMessageEvent[];
109
+ attachments: ExternalMessageAttachment[];
110
+ count: number;
111
+ reasonCode?: null;
112
+ message?: string;
113
+ };
114
+ export type WeChatToolErrorResult = {
115
+ ok: false;
116
+ operation: 'read' | 'write';
117
+ conversation?: string;
118
+ reasonCode: string;
119
+ rawReasonCode?: string;
120
+ message: string;
121
+ traceId?: string;
122
+ outDir?: string;
123
+ helperTracePath?: string | null;
124
+ activityGuardPath?: string | null;
125
+ userAction?: string;
126
+ };
127
+ export declare class WeChatToolOperationError extends Error {
128
+ readonly reasonCode: string;
129
+ readonly outDir?: string;
130
+ readonly helperTracePath?: string | null;
131
+ readonly activityGuardPath?: string | null;
132
+ readonly traceId?: string;
133
+ constructor(input: {
134
+ reasonCode: string;
135
+ message?: string;
136
+ outDir?: string;
137
+ helperTracePath?: string | null;
138
+ activityGuardPath?: string | null;
139
+ traceId?: string;
140
+ });
141
+ }
@@ -0,0 +1 @@
1
+ class t extends Error{reasonCode;outDir;helperTracePath;activityGuardPath;traceId;constructor(e){const r=o(e.message||e.reasonCode);super(r&&r!==e.reasonCode?`${e.reasonCode}: ${r}`:e.reasonCode),this.name="WeChatToolOperationError",this.reasonCode=e.reasonCode,this.outDir=e.outDir,this.helperTracePath=e.helperTracePath,this.activityGuardPath=e.activityGuardPath,this.traceId=e.traceId}}function o(a){return String(a||"").replace(/\s+/g," ").trim()}export{t as WeChatToolOperationError};
@@ -0,0 +1,14 @@
1
+ import type { ExternalMessageEvent } from '../../channels/base.js';
2
+ import type { WeChatChannelRuntimePlatform } from '../../channels/wechat-channel/runtime.js';
3
+ import type { WeChatSendResult } from './types.js';
4
+ export declare function normalizeSendStatus(value: unknown): WeChatSendResult['sendStatus'] | null;
5
+ export declare function currentWeChatPlatform(): WeChatChannelRuntimePlatform | undefined;
6
+ export declare function cleanInline(value: unknown): string;
7
+ export declare function processExists(pid: number): boolean;
8
+ export declare function isAbortError(error: unknown): boolean;
9
+ export declare function stableId(prefix: string, value: string): string;
10
+ export declare function safePathSegment(value: string): string;
11
+ export declare function clampPositiveInteger(value: unknown, fallback: number): number;
12
+ export declare function isExternalMessageEvent(value: unknown): value is ExternalMessageEvent;
13
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
14
+ export declare function stringValue(value: unknown): string | undefined;
@@ -0,0 +1 @@
1
+ import o from"node:crypto";function i(t){return t==="dry_run"||t==="queued"||t==="sent"||t==="sent_unconfirmed"||t==="confirmed_echo"||t==="manual_review"||t==="pending"||t==="ok"?t:null}function c(){if(process.platform==="darwin")return"darwin";if(process.platform==="win32")return"win32"}function f(t){return String(t||"").replace(/\s+/g," ").trim()}function p(t){try{return process.kill(t,0),!0}catch{return!1}}function a(t){if(!t||typeof t!="object")return!1;const r=t,e=typeof r.name=="string"?r.name:"",n=typeof r.message=="string"?r.message:"";return e==="AbortError"||r.code==="ABORT_ERR"||/aborted|abort/i.test(n)}function u(t,r){return`${t}:${o.createHash("sha256").update(r).digest("hex").slice(0,32)}`}function m(t){return t.trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"conversation"}function d(t,r){const e=Number(t);return!Number.isFinite(e)||e<=0?r:Math.floor(e)}function g(t){return!!t&&typeof t=="object"&&!Array.isArray(t)&&t.type==="external.message"&&typeof t.channelId=="string"&&typeof t.conversationId=="string"&&typeof t.messageId=="string"&&typeof t.text=="string"}function y(t){return!!t&&typeof t=="object"&&!Array.isArray(t)}function x(t){return typeof t=="string"&&t?t:void 0}export{d as clampPositiveInteger,f as cleanInline,c as currentWeChatPlatform,a as isAbortError,g as isExternalMessageEvent,y as isRecord,i as normalizeSendStatus,p as processExists,m as safePathSegment,u as stableId,x as stringValue};