shennian 0.2.96 → 0.2.98

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.
@@ -161,8 +161,8 @@
161
161
  },
162
162
  {
163
163
  "file": "src/channels/runtime.js",
164
- "beforeBytes": 28763,
165
- "afterBytes": 16002
164
+ "beforeBytes": 29792,
165
+ "afterBytes": 16367
166
166
  },
167
167
  {
168
168
  "file": "src/channels/secret-registry.js",
@@ -286,8 +286,8 @@
286
286
  },
287
287
  {
288
288
  "file": "src/channels/wechat-channel/runner.js",
289
- "beforeBytes": 12862,
290
- "afterBytes": 6568
289
+ "beforeBytes": 12915,
290
+ "afterBytes": 6617
291
291
  },
292
292
  {
293
293
  "file": "src/channels/wechat-channel/runtime.js",
@@ -321,8 +321,8 @@
321
321
  },
322
322
  {
323
323
  "file": "src/channels/wechat-rpa/product-channel.js",
324
- "beforeBytes": 13524,
325
- "afterBytes": 7360
324
+ "beforeBytes": 13531,
325
+ "afterBytes": 7396
326
326
  },
327
327
  {
328
328
  "file": "src/channels/wechat-rpa.js",
@@ -386,8 +386,8 @@
386
386
  },
387
387
  {
388
388
  "file": "src/commands/wechat.js",
389
- "beforeBytes": 33347,
390
- "afterBytes": 18752
389
+ "beforeBytes": 32343,
390
+ "afterBytes": 17929
391
391
  },
392
392
  {
393
393
  "file": "src/config/index.js",
@@ -69,6 +69,7 @@ export declare class ChannelRuntime {
69
69
  channel: ExternalChannelView;
70
70
  }>;
71
71
  private recordRecentMessage;
72
+ private findRecentMessageDuplicate;
72
73
  private getRecentMessages;
73
74
  upsertManagerChannel(input: {
74
75
  id: string;
@@ -1,5 +1,5 @@
1
- import A from"node:crypto";import g from"node:fs";import y from"node:path";import{ChannelConfigRegistry as v}from"./registry.js";import{ChannelSecretRegistry as P}from"./secret-registry.js";import{WeComChannelAdapter as M}from"./wecom.js";import{WeChatRpaChannelAdapter as N}from"./wechat-rpa.js";import{ExternalWebSocketChannelAdapter as B}from"./websocket.js";import{splitExternalReplyText as D}from"./reply-split.js";class G{onExternalMessage;createReplyTarget;configs=new v;secrets=new P;adapters=new Map;completedReplyKeys=new Map;recentMessages=new Map;constructor(e,t){this.onExternalMessage=e,this.createReplyTarget=t;const n=new M(r=>this.ingest({...r,type:"external.message"}));this.adapters.set(n.type,n);const o=new B(r=>this.ingest({...r,type:"external.message"}));this.adapters.set(o.type,o);const a=new N(r=>this.ingest(r));this.adapters.set(a.type,a)}async start(){for(const e of this.configs.list().filter(t=>t.enabled))await this.adapters.get(e.type)?.connect(e).catch(()=>{})}async stop(){for(const e of this.configs.list())await this.adapters.get(e.type)?.disconnect(e).catch(()=>{})}ingest(e){const t=this.configs.get(e.channelId),n=e.managerSessionId??t?.sessionId??t?.managerSessionId;if(!n)throw new Error(`No session bound for channel ${e.channelId}`);const o=e.replyTarget||this.createReplyTarget({managerSessionId:n,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId}),a={...e,replyTarget:o};return this.onExternalMessage(n,a),this.recordRecentMessage(a),a}async reply(e){const t=this.configs.get(e.channelId);if(!t)return{ok:!1,error:`Unknown channel: ${e.channelId}`};if((t.sessionId??t.managerSessionId)!==e.managerSessionId)return{ok:!1,error:"Channel is not bound to this session"};const n=this.adapters.get(t.type);if(!n)return{ok:!1,error:`Unsupported channel type: ${t.type}`};try{const o=E(t.type,e);if(!o.length)return{ok:!1,error:"Reply text or attachment is required"};let a=!1;for(const r of o){const i=r.idempotencyKey;if(i&&this.isReplyCompleted(t,e.conversationId,i))continue;const c=await n.send(t,{...e,...r});if(c?.status==="queued"){a=!0;continue}if(c?.status==="manual-review")return{ok:!1,error:c.reason||"Reply requires manual review before retry"};i&&this.markReplyCompleted(t,e.conversationId,i)}return a?{ok:!0,pending:!0}:{ok:!0}}catch(o){return{ok:!1,error:o instanceof Error?o.message:String(o)}}}isReplyCompleted(e,t,n){return this.loadReplyCompletionSet(e).has(k(e.id,t,n))}markReplyCompleted(e,t,n){const o=this.loadReplyCompletionSet(e);o.add(k(e.id,t,n));try{K(e.workDir,o)}catch{}}loadReplyCompletionSet(e){const t=y.resolve(e.workDir),n=this.completedReplyKeys.get(t);if(n)return n;const o=F(e.workDir);return this.completedReplyKeys.set(t,o),o}async getDefaultReplyTarget(e){const t=this.configs.list().find(a=>(a.sessionId??a.managerSessionId)===e&&a.enabled);if(!t)throw new Error("No enabled external channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.defaultConversation)throw new Error(`External channel ${t.type} has no default conversation`);const o=await n.defaultConversation(t);return{channelId:t.id,conversationId:o.conversationId}}getManagerChannel(e,t,n={}){const o=this.configs.list().filter(c=>(c.sessionId??c.managerSessionId)===e&&c.type===t),a=o.find(c=>c.enabled)??o.at(-1);if(!a)return null;const r=this.secrets.get(a.secretRef),i=this.adapters.get(a.type)?.runtimeStatus?.(a)??{};return{id:a.id,type:a.type,name:a.name,sessionId:a.sessionId??a.managerSessionId,managerSessionId:a.managerSessionId,workDir:a.workDir,agentType:a.agentType,agentSessionId:a.agentSessionId,modelId:a.modelId,enabled:a.enabled,wsUrl:r?.wsUrl??"",token:n.includeSecret?r?.token??"":"",tokenConfigured:!!r?.token,canReply:!!r?.canReply,systemPrompt:typeof r?.systemPrompt=="string"?r.systemPrompt:"",...R(r),...i}}getChannelById(e,t={}){const n=this.configs.get(e);if(!n)return null;const o=this.secrets.get(n.secretRef),a=this.adapters.get(n.type)?.runtimeStatus?.(n)??{};return{id:n.id,type:n.type,name:n.name,sessionId:n.sessionId??n.managerSessionId,managerSessionId:n.managerSessionId,workDir:n.workDir,agentType:n.agentType,agentSessionId:n.agentSessionId,modelId:n.modelId,enabled:n.enabled,wsUrl:o?.wsUrl??"",token:t.includeSecret?o?.token??"":"",tokenConfigured:!!o?.token,canReply:!!o?.canReply,systemPrompt:typeof o?.systemPrompt=="string"?o.systemPrompt:"",...R(o),...a}}getChannelStatusById(e){const t=this.configs.get(e);if(!t)return null;const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:h(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...u(n),...o}}getManagerChannelStatus(e){const t=this.configs.list().find(a=>(a.sessionId??a.managerSessionId)===e&&a.enabled);if(!t)return null;const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:h(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...u(n),...o}}listManagerChannelStatuses(){return this.configs.list().filter(e=>e.enabled).map(e=>({managerSessionId:e.sessionId??e.managerSessionId,status:this.getManagerChannelStatus(e.sessionId??e.managerSessionId)})).filter(e=>!!e.status)}listManagerExternalChannels(e){return this.configs.list().filter(t=>t.enabled&&(t.sessionId??t.managerSessionId)===e).map(t=>{const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:h(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...u(n),...o}})}async syncManagerWeChatRpaChannel(e){const t=this.configs.list().find(r=>r.enabled&&r.type==="wechat-rpa"&&(r.sessionId??r.managerSessionId)===e);if(!t)throw new Error("No enabled WeChat RPA channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.syncNow)throw new Error("WeChat RPA channel does not support manual sync");const o=Date.now()-120*1e3,a=W(await n.syncNow(t)??[],this.getRecentMessages(t.id,o));return{channel:this.getManagerChannel(e,"wechat-rpa",{includeSecret:!0}),messages:a}}async cancelManagerWeChatRpaOutbound(e){const t=this.configs.list().find(a=>a.enabled&&a.type==="wechat-rpa"&&(a.sessionId??a.managerSessionId)===e.managerSessionId);if(!t)throw new Error("No enabled WeChat RPA channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.cancelOutbound)throw new Error("WeChat RPA channel does not support outbound cancellation");const o=await n.cancelOutbound(t,{idempotencyKey:e.idempotencyKey,replyId:e.replyId,reason:e.reason});return{cancelled:o.cancelled,status:o.status,channel:this.getManagerChannel(e.managerSessionId,"wechat-rpa",{includeSecret:!0})}}recordRecentMessage(e){const t=this.recentMessages.get(e.channelId)??[];t.push(e),this.recentMessages.set(e.channelId,t.slice(-50))}getRecentMessages(e,t){return(this.recentMessages.get(e)??[]).filter(o=>{const a=Date.parse(o.receivedAt);return!Number.isFinite(a)||a>=t})}async upsertManagerChannel(e){const t=this.configs.get(e.id),n=this.configs.list(),o=e.sessionId||e.managerSessionId,a={id:e.id,type:e.type,name:e.name?.trim()||t?.name||"\u5916\u90E8\u6D88\u606F\u901A\u9053",sessionId:o,managerSessionId:o,workDir:e.workDir,agentType:e.agentType||t?.agentType,agentSessionId:e.agentSessionId??t?.agentSessionId??null,modelId:e.modelId??t?.modelId??null,enabled:e.enabled,secretRef:t?.secretRef||`channel:${e.id}`},r=this.secrets.get(a.secretRef),i=e.wsUrl?.trim()||r?.wsUrl||"",c=e.token?.trim()||r?.token||"",p=e.canReply??r?.canReply??!1,m=e.systemPrompt??(typeof r?.systemPrompt=="string"?r.systemPrompt:"");if(a.enabled&&(!i||!c))throw new Error("WebSocket \u5730\u5740\u548C Token \u5FC5\u586B");const f=n.filter(l=>l.id!==a.id).map(l=>(l.sessionId??l.managerSessionId)===o&&l.type===e.type?{...l,enabled:!1}:l);f.push(a),this.configs.replaceAll(f),(i||c)&&this.secrets.upsert(a.secretRef,{type:"websocket",wsUrl:i,token:c,canReply:p,systemPrompt:m});const d=this.adapters.get(a.type);for(const l of n)(l.sessionId??l.managerSessionId)===o&&l.type===e.type&&l.enabled&&await this.adapters.get(l.type)?.disconnect(l).catch(()=>{});return a.enabled&&d?.connect(a).catch(()=>{}),this.getManagerChannel(o,e.type,{includeSecret:!0})}async upsertManagerWeChatRpaChannel(e){const t=this.configs.get(e.id),n=this.configs.list(),o=e.sessionId||e.managerSessionId,a=w(e.groups);if(e.enabled&&!a.length)throw new Error("WeChat RPA \u81F3\u5C11\u9700\u8981\u914D\u7F6E\u4E00\u4E2A\u7FA4");if(e.enabled&&a.length>1)throw new Error("WeChat RPA \u6BCF\u4E2A\u5BF9\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u4E2A\u7FA4");const r={id:e.id,type:"wechat-rpa",name:e.name?.trim()||t?.name||"\u672C\u673A\u5FAE\u4FE1 RPA",sessionId:o,managerSessionId:o,workDir:e.workDir,agentType:e.agentType||t?.agentType,agentSessionId:e.agentSessionId??t?.agentSessionId??null,modelId:e.modelId??t?.modelId??null,enabled:e.enabled,secretRef:t?.secretRef||`channel:${e.id}`},i=this.secrets.get(r.secretRef),c=e.source||(i?.source==="wechat-channel"||i?.source==="macos-probe"||i?.source==="fixture-jsonl"||i?.source==="macos-flow"||i?.source==="windows-visual-flow"||i?.source==="wechat-rpa-lab"?i.source:x());if(e.enabled&&c==="windows-visual-flow")throw new Error("\u65E7 Windows visual flow \u5DF2\u5F52\u6863\uFF1BmacOS \u548C Windows \u6B63\u5F0F\u901A\u9053\u90FD\u4F7F\u7528 wechat-channel");const p=e.privacyConsentAccepted??!!i?.privacyConsentAccepted;if(e.enabled&&!p)throw new Error("\u542F\u7528\u524D\u9700\u8981\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743");const m=n.filter(d=>d.id!==r.id).map(d=>(d.sessionId??d.managerSessionId)===o&&d.type==="wechat-rpa"?{...d,enabled:!1}:d);m.push(r),this.configs.replaceAll(m),this.secrets.upsert(r.secretRef,{type:"wechat-rpa",source:c,groups:a,pollIntervalMs:I(e.pollIntervalMs,i?.pollIntervalMs),recentLimit:I(e.recentLimit,i?.recentLimit),idleSeconds:I(e.idleSeconds,i?.idleSeconds),forceForeground:e.forceForeground??!!i?.forceForeground,noRestore:e.noRestore??(i?.noRestore===void 0?!0:!!i.noRestore),downloadAttachments:e.downloadAttachments??(i?.downloadAttachments===void 0?!0:!!i.downloadAttachments),downloadAttachmentsDir:e.downloadAttachmentsDir?.trim()||S(i?.downloadAttachmentsDir),selfNickname:e.selfNickname?.trim()||S(i?.selfNickname),privacyConsentAccepted:p,flowScriptPath:e.flowScriptPath?.trim()||S(i?.flowScriptPath),canReply:e.canReply??i?.canReply??!1,systemPrompt:e.systemPrompt??(typeof i?.systemPrompt=="string"?i.systemPrompt:"")});const f=this.adapters.get(r.type);for(const d of n)(d.sessionId??d.managerSessionId)===o&&d.type==="wechat-rpa"&&d.enabled&&await this.adapters.get(d.type)?.disconnect(d).catch(()=>{});return r.enabled&&f?.connect(r).catch(()=>{}),this.getManagerChannel(o,"wechat-rpa",{includeSecret:!0})}}function h(s,e){return!e||e.type!==s.type?!1:s.type==="wechat-rpa"?!0:s.type==="websocket"?!!(e.wsUrl&&e.token):s.type==="wecom"?!!(e.token||e.botId||e.secret):!!e.token}function R(s){return!s||s.type!=="wechat-rpa"?{}:{wechatRpaSource:typeof s.source=="string"?s.source:"",wechatRpaGroups:w(Array.isArray(s.groups)?s.groups:[]),pollIntervalMs:Number.isFinite(s.pollIntervalMs)?Number(s.pollIntervalMs):void 0,recentLimit:Number.isFinite(s.recentLimit)?Number(s.recentLimit):void 0,idleSeconds:Number.isFinite(s.idleSeconds)?Number(s.idleSeconds):void 0,forceForeground:!!s.forceForeground,noRestore:s.noRestore===void 0?void 0:!!s.noRestore,downloadAttachments:s.downloadAttachments===void 0?!0:!!s.downloadAttachments,downloadAttachmentsDir:typeof s.downloadAttachmentsDir=="string"?s.downloadAttachmentsDir:"",selfNickname:typeof s.selfNickname=="string"?s.selfNickname:"",wechatRpaPrivacyConsentAccepted:!!s.privacyConsentAccepted,wechatRpaServerDecisionAvailable:!0,wechatRpaPreflightChecks:b(s)}}function u(s){return!s||s.type!=="wechat-rpa"?{}:{wechatRpaSource:typeof s.source=="string"?s.source:null,wechatRpaGroups:w(Array.isArray(s.groups)?s.groups:[]),pollIntervalMs:Number.isFinite(s.pollIntervalMs)?Number(s.pollIntervalMs):null,recentLimit:Number.isFinite(s.recentLimit)?Number(s.recentLimit):null,idleSeconds:Number.isFinite(s.idleSeconds)?Number(s.idleSeconds):null,forceForeground:!!s.forceForeground,noRestore:s.noRestore===void 0?null:!!s.noRestore,downloadAttachments:s.downloadAttachments===void 0?!0:!!s.downloadAttachments,downloadAttachmentsDir:typeof s.downloadAttachmentsDir=="string"?s.downloadAttachmentsDir:null,selfNickname:typeof s.selfNickname=="string"?s.selfNickname:null,wechatRpaPrivacyConsentAccepted:!!s.privacyConsentAccepted,wechatRpaServerDecisionAvailable:!0,wechatRpaPreflightChecks:b(s)}}function w(s){const e=new Set,t=[];for(const n of s){const o=String(n?.name||"").replace(/\s+/g," ").trim();!o||e.has(o)||(e.add(o),t.push({name:o}))}return t}function x(){return"wechat-channel"}function b(s){const e=[];if(s.lastHelperPermissions&&typeof s.lastHelperPermissions=="object"){const t=s.lastHelperPermissions,n=t.duplicateInstanceBlocked===!0||Number(t.duplicateMainWindowCount??0)>0||Number(t.unsafeAuxiliaryWindowCount??0)>0;e.push({code:"wechat_single_main_window_required",ok:!n,severity:"blocking",message:n?"\u8BF7\u53EA\u4FDD\u7559\u4E00\u4E2A\u5DF2\u767B\u5F55\u7684\u5FAE\u4FE1\u4E3B\u7A97\u53E3\uFF0C\u5173\u95ED\u91CD\u590D\u4E3B\u7A97\u53E3\u3001\u89C6\u9891\u9884\u89C8\u6216\u767B\u5F55\u63D0\u793A\u540E\u518D\u8BD5\u3002":"\u5F53\u524D\u53EA\u6709\u4E00\u4E2A\u53EF\u7528\u7684\u5FAE\u4FE1\u4E3B\u7A97\u53E3\u3002"})}return e.push({code:"platform_unsupported",ok:s.source!=="windows-visual-flow",severity:"blocking",message:s.source==="windows-visual-flow"?"\u65E7 Windows visual flow \u5DF2\u5F52\u6863\uFF0C\u8BF7\u6539\u7528 wechat-channel\u3002":"\u5F53\u524D\u914D\u7F6E\u4F7F\u7528\u8DE8\u5E73\u53F0\u5FAE\u4FE1\u901A\u9053\u3002"}),e.push({code:"privacy_consent_required",ok:!!s.privacyConsentAccepted,severity:"blocking",message:s.privacyConsentAccepted?"\u5DF2\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743\u3002":"\u542F\u7528\u524D\u9700\u8981\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743\u3002"}),e.push({code:"server_decision_unavailable",ok:!0,severity:"blocking",message:"\u670D\u52A1\u7AEF\u5224\u65AD\u80FD\u529B\u53EF\u7528\u3002"}),e}function E(s,e){const t=D(e.text);if(!t.length&&!e.attachment)return[];if(s==="wechat-rpa"&&e.attachment&&t.length<=1)return[{text:t[0]??"",attachment:e.attachment,idempotencyKey:e.idempotencyKey}];const n=t.map((o,a)=>({text:o,attachment:void 0,idempotencyKey:t.length>1&&e.idempotencyKey?`${e.idempotencyKey}:${a+1}`:e.idempotencyKey}));return e.attachment&&n.push({text:"",attachment:e.attachment,idempotencyKey:t.length&&e.idempotencyKey?`${e.idempotencyKey}:attachment`:e.idempotencyKey}),n}function W(...s){const e=new Set,t=[];for(const n of s.flat()){const o=`${n.channelId}
2
- ${n.conversationId}
3
- ${n.messageId}`;e.has(o)||(e.add(o),t.push(n))}return t}function k(s,e,t){return A.createHash("sha256").update(`${s}
1
+ import P from"node:crypto";import g from"node:fs";import u from"node:path";import{ChannelConfigRegistry as v}from"./registry.js";import{ChannelSecretRegistry as N}from"./secret-registry.js";import{WeComChannelAdapter as D}from"./wecom.js";import{WeChatRpaChannelAdapter as B}from"./wechat-rpa.js";import{ExternalWebSocketChannelAdapter as x}from"./websocket.js";import{splitExternalReplyText as E}from"./reply-split.js";class J{onExternalMessage;createReplyTarget;configs=new v;secrets=new N;adapters=new Map;completedReplyKeys=new Map;recentMessages=new Map;constructor(e,t){this.onExternalMessage=e,this.createReplyTarget=t;const n=new D(a=>this.ingest({...a,type:"external.message"}));this.adapters.set(n.type,n);const o=new x(a=>this.ingest({...a,type:"external.message"}));this.adapters.set(o.type,o);const r=new B(a=>this.ingest(a));this.adapters.set(r.type,r)}async start(){for(const e of this.configs.list().filter(t=>t.enabled))await this.adapters.get(e.type)?.connect(e).catch(()=>{})}async stop(){for(const e of this.configs.list())await this.adapters.get(e.type)?.disconnect(e).catch(()=>{})}ingest(e){const t=this.findRecentMessageDuplicate(e);if(t)return t;const n=this.configs.get(e.channelId),o=e.managerSessionId??n?.sessionId??n?.managerSessionId;if(!o)throw new Error(`No session bound for channel ${e.channelId}`);const r=e.replyTarget||this.createReplyTarget({managerSessionId:o,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId}),a={...e,replyTarget:r};return this.onExternalMessage(o,a),this.recordRecentMessage(a),a}async reply(e){const t=this.configs.get(e.channelId);if(!t)return{ok:!1,error:`Unknown channel: ${e.channelId}`};if((t.sessionId??t.managerSessionId)!==e.managerSessionId)return{ok:!1,error:"Channel is not bound to this session"};const n=this.adapters.get(t.type);if(!n)return{ok:!1,error:`Unsupported channel type: ${t.type}`};try{const o=W(t.type,e);if(!o.length)return{ok:!1,error:"Reply text or attachment is required"};let r=!1;for(const a of o){const i=a.idempotencyKey;if(i&&this.isReplyCompleted(t,e.conversationId,i))continue;const c=await n.send(t,{...e,...a});if(c?.status==="queued"){r=!0;continue}if(c?.status==="manual-review")return{ok:!1,error:c.reason||"Reply requires manual review before retry"};i&&this.markReplyCompleted(t,e.conversationId,i)}return r?{ok:!0,pending:!0}:{ok:!0}}catch(o){return{ok:!1,error:o instanceof Error?o.message:String(o)}}}isReplyCompleted(e,t,n){return this.loadReplyCompletionSet(e).has(A(e.id,t,n))}markReplyCompleted(e,t,n){const o=this.loadReplyCompletionSet(e);o.add(A(e.id,t,n));try{$(e.workDir,o)}catch{}}loadReplyCompletionSet(e){const t=u.resolve(e.workDir),n=this.completedReplyKeys.get(t);if(n)return n;const o=T(e.workDir);return this.completedReplyKeys.set(t,o),o}async getDefaultReplyTarget(e){const t=this.configs.list().find(r=>(r.sessionId??r.managerSessionId)===e&&r.enabled);if(!t)throw new Error("No enabled external channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.defaultConversation)throw new Error(`External channel ${t.type} has no default conversation`);const o=await n.defaultConversation(t);return{channelId:t.id,conversationId:o.conversationId}}getManagerChannel(e,t,n={}){const o=this.configs.list().filter(c=>(c.sessionId??c.managerSessionId)===e&&c.type===t),r=o.find(c=>c.enabled)??o.at(-1);if(!r)return null;const a=this.secrets.get(r.secretRef),i=this.adapters.get(r.type)?.runtimeStatus?.(r)??{};return{id:r.id,type:r.type,name:r.name,sessionId:r.sessionId??r.managerSessionId,managerSessionId:r.managerSessionId,workDir:r.workDir,agentType:r.agentType,agentSessionId:r.agentSessionId,modelId:r.modelId,enabled:r.enabled,wsUrl:a?.wsUrl??"",token:n.includeSecret?a?.token??"":"",tokenConfigured:!!a?.token,canReply:!!a?.canReply,systemPrompt:typeof a?.systemPrompt=="string"?a.systemPrompt:"",...k(a),...i}}getChannelById(e,t={}){const n=this.configs.get(e);if(!n)return null;const o=this.secrets.get(n.secretRef),r=this.adapters.get(n.type)?.runtimeStatus?.(n)??{};return{id:n.id,type:n.type,name:n.name,sessionId:n.sessionId??n.managerSessionId,managerSessionId:n.managerSessionId,workDir:n.workDir,agentType:n.agentType,agentSessionId:n.agentSessionId,modelId:n.modelId,enabled:n.enabled,wsUrl:o?.wsUrl??"",token:t.includeSecret?o?.token??"":"",tokenConfigured:!!o?.token,canReply:!!o?.canReply,systemPrompt:typeof o?.systemPrompt=="string"?o.systemPrompt:"",...k(o),...r}}getChannelStatusById(e){const t=this.configs.get(e);if(!t)return null;const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:y(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...h(n),...o}}getManagerChannelStatus(e){const t=this.configs.list().find(r=>(r.sessionId??r.managerSessionId)===e&&r.enabled);if(!t)return null;const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:y(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...h(n),...o}}listManagerChannelStatuses(){return this.configs.list().filter(e=>e.enabled).map(e=>({managerSessionId:e.sessionId??e.managerSessionId,status:this.getManagerChannelStatus(e.sessionId??e.managerSessionId)})).filter(e=>!!e.status)}listManagerExternalChannels(e){return this.configs.list().filter(t=>t.enabled&&(t.sessionId??t.managerSessionId)===e).map(t=>{const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:y(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...h(n),...o}})}async syncManagerWeChatRpaChannel(e){const t=this.configs.list().find(a=>a.enabled&&a.type==="wechat-rpa"&&(a.sessionId??a.managerSessionId)===e);if(!t)throw new Error("No enabled WeChat RPA channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.syncNow)throw new Error("WeChat RPA channel does not support manual sync");const o=Date.now()-120*1e3,r=F(await n.syncNow(t)??[],this.getRecentMessages(t.id,o));return{channel:this.getManagerChannel(e,"wechat-rpa",{includeSecret:!0}),messages:r}}async cancelManagerWeChatRpaOutbound(e){const t=this.configs.list().find(r=>r.enabled&&r.type==="wechat-rpa"&&(r.sessionId??r.managerSessionId)===e.managerSessionId);if(!t)throw new Error("No enabled WeChat RPA channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.cancelOutbound)throw new Error("WeChat RPA channel does not support outbound cancellation");const o=await n.cancelOutbound(t,{idempotencyKey:e.idempotencyKey,replyId:e.replyId,reason:e.reason});return{cancelled:o.cancelled,status:o.status,channel:this.getManagerChannel(e.managerSessionId,"wechat-rpa",{includeSecret:!0})}}recordRecentMessage(e){const t=this.recentMessages.get(e.channelId)??[];this.findRecentMessageDuplicate(e)||(t.push(e),this.recentMessages.set(e.channelId,t.slice(-50)))}findRecentMessageDuplicate(e){const t=I(e);return t?(this.recentMessages.get(e.channelId)??[]).find(o=>I(o)===t)??null:null}getRecentMessages(e,t){return(this.recentMessages.get(e)??[]).filter(o=>{const r=Date.parse(o.receivedAt);return!Number.isFinite(r)||r>=t})}async upsertManagerChannel(e){const t=this.configs.get(e.id),n=this.configs.list(),o=e.sessionId||e.managerSessionId,r={id:e.id,type:e.type,name:e.name?.trim()||t?.name||"\u5916\u90E8\u6D88\u606F\u901A\u9053",sessionId:o,managerSessionId:o,workDir:e.workDir,agentType:e.agentType||t?.agentType,agentSessionId:e.agentSessionId??t?.agentSessionId??null,modelId:e.modelId??t?.modelId??null,enabled:e.enabled,secretRef:t?.secretRef||`channel:${e.id}`},a=this.secrets.get(r.secretRef),i=e.wsUrl?.trim()||a?.wsUrl||"",c=e.token?.trim()||a?.token||"",p=e.canReply??a?.canReply??!1,m=e.systemPrompt??(typeof a?.systemPrompt=="string"?a.systemPrompt:"");if(r.enabled&&(!i||!c))throw new Error("WebSocket \u5730\u5740\u548C Token \u5FC5\u586B");const f=n.filter(l=>l.id!==r.id).map(l=>(l.sessionId??l.managerSessionId)===o&&l.type===e.type?{...l,enabled:!1}:l);f.push(r),this.configs.replaceAll(f),(i||c)&&this.secrets.upsert(r.secretRef,{type:"websocket",wsUrl:i,token:c,canReply:p,systemPrompt:m});const d=this.adapters.get(r.type);for(const l of n)(l.sessionId??l.managerSessionId)===o&&l.type===e.type&&l.enabled&&await this.adapters.get(l.type)?.disconnect(l).catch(()=>{});return r.enabled&&d?.connect(r).catch(()=>{}),this.getManagerChannel(o,e.type,{includeSecret:!0})}async upsertManagerWeChatRpaChannel(e){const t=this.configs.get(e.id),n=this.configs.list(),o=e.sessionId||e.managerSessionId,r=w(e.groups);if(e.enabled&&!r.length)throw new Error("WeChat RPA \u81F3\u5C11\u9700\u8981\u914D\u7F6E\u4E00\u4E2A\u7FA4");if(e.enabled&&r.length>1)throw new Error("WeChat RPA \u6BCF\u4E2A\u5BF9\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u4E2A\u7FA4");const a={id:e.id,type:"wechat-rpa",name:e.name?.trim()||t?.name||"\u672C\u673A\u5FAE\u4FE1 RPA",sessionId:o,managerSessionId:o,workDir:e.workDir,agentType:e.agentType||t?.agentType,agentSessionId:e.agentSessionId??t?.agentSessionId??null,modelId:e.modelId??t?.modelId??null,enabled:e.enabled,secretRef:t?.secretRef||`channel:${e.id}`},i=this.secrets.get(a.secretRef),c=e.source||(i?.source==="wechat-channel"||i?.source==="macos-probe"||i?.source==="fixture-jsonl"||i?.source==="macos-flow"||i?.source==="windows-visual-flow"||i?.source==="wechat-rpa-lab"?i.source:K());if(e.enabled&&c==="windows-visual-flow")throw new Error("\u65E7 Windows visual flow \u5DF2\u5F52\u6863\uFF1BmacOS \u548C Windows \u6B63\u5F0F\u901A\u9053\u90FD\u4F7F\u7528 wechat-channel");const p=e.privacyConsentAccepted??!!i?.privacyConsentAccepted;if(e.enabled&&!p)throw new Error("\u542F\u7528\u524D\u9700\u8981\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743");const m=n.filter(d=>d.id!==a.id).map(d=>(d.sessionId??d.managerSessionId)===o&&d.type==="wechat-rpa"?{...d,enabled:!1}:d);m.push(a),this.configs.replaceAll(m),this.secrets.upsert(a.secretRef,{type:"wechat-rpa",source:c,groups:r,pollIntervalMs:R(e.pollIntervalMs,i?.pollIntervalMs),recentLimit:R(e.recentLimit,i?.recentLimit),idleSeconds:R(e.idleSeconds,i?.idleSeconds),forceForeground:e.forceForeground??!!i?.forceForeground,noRestore:e.noRestore??(i?.noRestore===void 0?!0:!!i.noRestore),downloadAttachments:e.downloadAttachments??(i?.downloadAttachments===void 0?!0:!!i.downloadAttachments),downloadAttachmentsDir:e.downloadAttachmentsDir?.trim()||b(i?.downloadAttachmentsDir),selfNickname:e.selfNickname?.trim()||b(i?.selfNickname),privacyConsentAccepted:p,flowScriptPath:e.flowScriptPath?.trim()||b(i?.flowScriptPath),canReply:e.canReply??i?.canReply??!1,systemPrompt:e.systemPrompt??(typeof i?.systemPrompt=="string"?i.systemPrompt:"")});const f=this.adapters.get(a.type);for(const d of n)(d.sessionId??d.managerSessionId)===o&&d.type==="wechat-rpa"&&d.enabled&&await this.adapters.get(d.type)?.disconnect(d).catch(()=>{});return a.enabled&&f?.connect(a).catch(()=>{}),this.getManagerChannel(o,"wechat-rpa",{includeSecret:!0})}}function y(s,e){return!e||e.type!==s.type?!1:s.type==="wechat-rpa"?!0:s.type==="websocket"?!!(e.wsUrl&&e.token):s.type==="wecom"?!!(e.token||e.botId||e.secret):!!e.token}function k(s){return!s||s.type!=="wechat-rpa"?{}:{wechatRpaSource:typeof s.source=="string"?s.source:"",wechatRpaGroups:w(Array.isArray(s.groups)?s.groups:[]),pollIntervalMs:Number.isFinite(s.pollIntervalMs)?Number(s.pollIntervalMs):void 0,recentLimit:Number.isFinite(s.recentLimit)?Number(s.recentLimit):void 0,idleSeconds:Number.isFinite(s.idleSeconds)?Number(s.idleSeconds):void 0,forceForeground:!!s.forceForeground,noRestore:s.noRestore===void 0?void 0:!!s.noRestore,downloadAttachments:s.downloadAttachments===void 0?!0:!!s.downloadAttachments,downloadAttachmentsDir:typeof s.downloadAttachmentsDir=="string"?s.downloadAttachmentsDir:"",selfNickname:typeof s.selfNickname=="string"?s.selfNickname:"",wechatRpaPrivacyConsentAccepted:!!s.privacyConsentAccepted,wechatRpaServerDecisionAvailable:!0,wechatRpaPreflightChecks:C(s)}}function h(s){return!s||s.type!=="wechat-rpa"?{}:{wechatRpaSource:typeof s.source=="string"?s.source:null,wechatRpaGroups:w(Array.isArray(s.groups)?s.groups:[]),pollIntervalMs:Number.isFinite(s.pollIntervalMs)?Number(s.pollIntervalMs):null,recentLimit:Number.isFinite(s.recentLimit)?Number(s.recentLimit):null,idleSeconds:Number.isFinite(s.idleSeconds)?Number(s.idleSeconds):null,forceForeground:!!s.forceForeground,noRestore:s.noRestore===void 0?null:!!s.noRestore,downloadAttachments:s.downloadAttachments===void 0?!0:!!s.downloadAttachments,downloadAttachmentsDir:typeof s.downloadAttachmentsDir=="string"?s.downloadAttachmentsDir:null,selfNickname:typeof s.selfNickname=="string"?s.selfNickname:null,wechatRpaPrivacyConsentAccepted:!!s.privacyConsentAccepted,wechatRpaServerDecisionAvailable:!0,wechatRpaPreflightChecks:C(s)}}function w(s){const e=new Set,t=[];for(const n of s){const o=String(n?.name||"").replace(/\s+/g," ").trim();!o||e.has(o)||(e.add(o),t.push({name:o}))}return t}function K(){return"wechat-channel"}function C(s){const e=[];if(s.lastHelperPermissions&&typeof s.lastHelperPermissions=="object"){const t=s.lastHelperPermissions,n=t.duplicateInstanceBlocked===!0||Number(t.duplicateMainWindowCount??0)>0||Number(t.unsafeAuxiliaryWindowCount??0)>0;e.push({code:"wechat_single_main_window_required",ok:!n,severity:"blocking",message:n?"\u8BF7\u53EA\u4FDD\u7559\u4E00\u4E2A\u5DF2\u767B\u5F55\u7684\u5FAE\u4FE1\u4E3B\u7A97\u53E3\uFF0C\u5173\u95ED\u91CD\u590D\u4E3B\u7A97\u53E3\u3001\u89C6\u9891\u9884\u89C8\u6216\u767B\u5F55\u63D0\u793A\u540E\u518D\u8BD5\u3002":"\u5F53\u524D\u53EA\u6709\u4E00\u4E2A\u53EF\u7528\u7684\u5FAE\u4FE1\u4E3B\u7A97\u53E3\u3002"})}return e.push({code:"platform_unsupported",ok:s.source!=="windows-visual-flow",severity:"blocking",message:s.source==="windows-visual-flow"?"\u65E7 Windows visual flow \u5DF2\u5F52\u6863\uFF0C\u8BF7\u6539\u7528 wechat-channel\u3002":"\u5F53\u524D\u914D\u7F6E\u4F7F\u7528\u8DE8\u5E73\u53F0\u5FAE\u4FE1\u901A\u9053\u3002"}),e.push({code:"privacy_consent_required",ok:!!s.privacyConsentAccepted,severity:"blocking",message:s.privacyConsentAccepted?"\u5DF2\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743\u3002":"\u542F\u7528\u524D\u9700\u8981\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743\u3002"}),e.push({code:"server_decision_unavailable",ok:!0,severity:"blocking",message:"\u670D\u52A1\u7AEF\u5224\u65AD\u80FD\u529B\u53EF\u7528\u3002"}),e}function W(s,e){const t=E(e.text);if(!t.length&&!e.attachment)return[];if(s==="wechat-rpa"&&e.attachment&&t.length<=1)return[{text:t[0]??"",attachment:e.attachment,idempotencyKey:e.idempotencyKey}];const n=t.map((o,r)=>({text:o,attachment:void 0,idempotencyKey:t.length>1&&e.idempotencyKey?`${e.idempotencyKey}:${r+1}`:e.idempotencyKey}));return e.attachment&&n.push({text:"",attachment:e.attachment,idempotencyKey:t.length&&e.idempotencyKey?`${e.idempotencyKey}:attachment`:e.idempotencyKey}),n}function F(...s){const e=new Set,t=[];for(const n of s.flat()){const o=I(n);if(!o){t.push(n);continue}e.has(o)||(e.add(o),t.push(n))}return t}function I(s){const e=S(s.channelId),t=S(s.conversationId),n=S(s.messageId);return!e||!t||!n?null:`${e}
2
+ ${t}
3
+ ${n}`}function S(s){return typeof s=="string"?s.trim():""}function A(s,e,t){return P.createHash("sha256").update(`${s}
4
4
  ${e}
5
- ${t}`).digest("hex").slice(0,32)}function C(s){return y.join(s,".shennian","external-reply-idempotency.json")}function F(s){try{const e=JSON.parse(g.readFileSync(C(s),"utf8")),t=Array.isArray(e.completed)?e.completed:[];return new Set(t.map(n=>typeof n=="string"?n:n&&typeof n=="object"&&typeof n.key=="string"?n.key:"").filter(Boolean))}catch{return new Set}}function K(s,e){const t=C(s);g.mkdirSync(y.dirname(t),{recursive:!0});const n=Array.from(e).slice(-500);g.writeFileSync(t,JSON.stringify({updatedAt:new Date().toISOString(),completed:n.map(o=>({key:o}))},null,2)),e.clear();for(const o of n)e.add(o)}function I(s,e){const t=Number(s??e);return Number.isFinite(t)&&t>=0?t:void 0}function S(s){return typeof s=="string"&&s.trim()?s.trim():void 0}export{G as ChannelRuntime,E as planExternalReplySends};
5
+ ${t}`).digest("hex").slice(0,32)}function M(s){return u.join(s,".shennian","external-reply-idempotency.json")}function T(s){try{const e=JSON.parse(g.readFileSync(M(s),"utf8")),t=Array.isArray(e.completed)?e.completed:[];return new Set(t.map(n=>typeof n=="string"?n:n&&typeof n=="object"&&typeof n.key=="string"?n.key:"").filter(Boolean))}catch{return new Set}}function $(s,e){const t=M(s);g.mkdirSync(u.dirname(t),{recursive:!0});const n=Array.from(e).slice(-500);g.writeFileSync(t,JSON.stringify({updatedAt:new Date().toISOString(),completed:n.map(o=>({key:o}))},null,2)),e.clear();for(const o of n)e.add(o)}function R(s,e){const t=Number(s??e);return Number.isFinite(t)&&t>=0?t:void 0}function b(s){return typeof s=="string"&&s.trim()?s.trim():void 0}export{J as ChannelRuntime,W as planExternalReplySends};
@@ -1 +1 @@
1
- import l from"node:path";import d from"node:fs";import{createWeChatChannelApiClient as b}from"./client.js";import{WeChatChannelHelperClient as c}from"./helper-client.js";import{resolveWeChatChannelHelperAsset as v,WECHAT_CHANNEL_HELPER_VERSION as y}from"./helper-assets.js";import{requiredWindowsWeChatChannelHelperCapabilitiesForProfile as h}from"./helper-protocol.js";import{captureWeChatWindow as W,ensureWeChatWindowReady as p,fallbackWeChatMessageInputPointForWindow as k,focusKnownWeChatWindow as f,observeWeChatChannelBindingViaHelper as S,openConversationBySearch as I,recognizeWeChatScreenshot as P}from"./observer.js";import{WeChatChannelScheduler as A}from"./scheduler.js";import{loadWeChatChannelLedger as O}from"./ledger.js";import{buildWeChatConversationFingerprint as F,matchWeChatConversationFingerprints as x}from"./fingerprint.js";import{WeChatChannelOutboundSender as _}from"./outbound-sender.js";import{decideWeChatChannelActivityGate as D,nextWeChatChannelActivityRetryAt as H,normalizeWeChatChannelActivitySnapshot as T}from"./human-coordination.js";class R{options;scheduler;helper;helperStarted=!1;constructor(e){this.options=e;const n=e.api??b();this.helper=e.helper??M(e),this.scheduler=new A({runtime:e.runtime,workDir:e.workDir,api:n,ledgerPath:e.ledgerPath,outboundLedgerPath:e.outboundLedgerPath,onInboundMessages:e.onInboundMessages,activityGate:{canObserve:async(t,a)=>{if(e.runtime.foregroundPolicy==="work")return{ok:!0};const o=await this.helper.request("activity.snapshot",{});if(!o.ok)return{ok:!1,reasonCode:"user_activity_unknown",nextAttemptAt:new Date(a.getTime()+5e3)};const i=D({snapshot:T(o.result),stage:"observe"});return i.ok?{ok:!0}:{ok:!1,reasonCode:i.reasonCode,nextAttemptAt:H(a,i.waitMs)}}},shouldSkipObserve:t=>this.shouldSkipObserveForUnchangedFingerprint(t),outboundSender:new _({helper:this.helper,platform:e.runtime.policy.platform,openConversation:async t=>{const a=await p(this.helper),o=await f(this.helper,a);return{...await I({helper:this.helper,window:o,settleToBottom:!1,binding:{bindingId:`outbound:${t}`,sessionId:"outbound",conversationDisplayName:t,enabled:!0,allowReply:!0,downloadMedia:!0}}),inputPoint:k(o)}}}),recoverWeChat:(t,a)=>this.reportUnrecoverableWindowsWeChat(t,a),observeBinding:(t,a)=>S({runtime:e.runtime,binding:t,helper:this.helper,api:n,workDir:e.workDir,localLedgerTailAnchors:E(e.ledgerPath??U(e.workDir,e.runtime.runtimeId),e.runtime.runtimeId,t.bindingId),localOutboundEchoAnchors:a?.localOutboundEchoAnchors,onObservationEvidence:({ocr:o})=>{N({statePath:g(e.workDir,e.runtime.runtimeId),runtimeId:e.runtime.runtimeId,binding:t,ocr:o})}})})}async start(){await this.ensureHelperStarted(),await this.scheduler.start()}async tick(){return await this.ensureHelperStarted(),this.scheduler.tick()}async stop(){this.scheduler.stop(),this.helperStarted&&this.helper.stop&&await this.helper.stop(),this.helperStarted=!1}async ensureHelperStarted(){this.helperStarted||(this.helper.start&&await this.helper.start(),this.helperStarted=!0)}async reportUnrecoverableWindowsWeChat(e,n){return this.options.runtime.policy.platform!=="win32",!1}async shouldSkipObserveForUnchangedFingerprint(e){if(this.options.runtime.policy.platform!=="win32")return{skip:!1};const n=g(this.options.workDir,this.options.runtime.runtimeId),t=u(n,this.options.runtime.runtimeId).bindings[e.bindingId];if(!t?.fingerprint)return{skip:!1};try{const a=await p(this.helper),o=await f(this.helper,a),i=await W(this.helper,o.windowId,void 0,o.bounds),C=await P(this.helper,i),s=w({binding:e,ocr:C,prior:t});return!s?.fingerprint||s.fingerprint!==t.fingerprint?{skip:!1}:{skip:!0,reasonCode:"conversation_fingerprint_unchanged",fingerprint:s.fingerprint}}catch{return{skip:!1}}}}function ee(r){return new R(r)}function E(r,e,n,t=3){return(O(r,e).bindings[n]?.recent??[]).slice(-t).map(i=>({stableMessageKey:i.stableMessageKey,senderRole:i.senderRole,kind:i.kind,anchorText:i.anchorText??i.normalizedText??i.textExcerpt??null,anchorMetadata:i.anchorMetadata??null}))}function M(r){if(r.helperClientOptions)return new c({...r.helperClientOptions,requiredCapabilities:r.helperClientOptions.requiredCapabilities??m(r.runtime),guardUnsafeWindowsCommands:r.helperClientOptions.guardUnsafeWindowsCommands??r.runtime.policy.platform==="win32",skipUserActivityGuard:r.helperClientOptions.skipUserActivityGuard??r.runtime.foregroundPolicy==="work",cleanupWindowsOverlays:r.helperClientOptions.cleanupWindowsOverlays??r.runtime.policy.platform==="win32"});const e=v();if(!e.ok)throw new Error(`${e.reasonCode}: ${e.message}`);return new c({helperPath:e.helperPath,expectedHelperVersion:e.version||y,requiredCapabilities:m(r.runtime),guardUnsafeWindowsCommands:r.runtime.policy.platform==="win32",skipUserActivityGuard:r.runtime.foregroundPolicy==="work",cleanupWindowsOverlays:r.runtime.policy.platform==="win32"})}function m(r){if(r.policy.platform==="win32")return r.bindings.some(e=>e.enabled!==!1&&e.allowReply!==!1)?h("send"):r.bindings.some(e=>e.enabled!==!1&&e.downloadMedia!==!1)?h("download"):h("observe")}function U(r,e){return l.join(r,"wechat-channel",`${e}.ledger.json`)}function g(r,e){return l.join(r,"wechat-channel",`${q(e)}.conversation-fingerprints.json`)}function u(r,e){try{const n=JSON.parse(d.readFileSync(r,"utf8"));if(n?.version===1&&n.runtimeId===e&&n.bindings&&typeof n.bindings=="object")return n}catch{}return{version:1,runtimeId:e,bindings:{}}}function L(r,e){d.mkdirSync(l.dirname(r),{recursive:!0}),d.writeFileSync(r,JSON.stringify(e,null,2))}function N(r){const e=w({binding:r.binding,ocr:r.ocr,prior:u(r.statePath,r.runtimeId).bindings[r.binding.bindingId]});if(!e?.fingerprint)return;const n=u(r.statePath,r.runtimeId);n.bindings[r.binding.bindingId]={bindingId:r.binding.bindingId,fingerprint:e.fingerprint,title:e.item.title??null,preview:e.item.preview??null,timeText:e.item.timeText??null,unreadText:e.item.unreadText??null,observedAt:new Date().toISOString()},L(r.statePath,n)}function w(r){const e=r.ocr.visibleConversationFingerprints??[],n=x({visibleItems:e,targets:[{bindingId:r.binding.bindingId,conversationDisplayName:r.binding.conversationDisplayName,lastConversationFingerprint:r.prior?.fingerprint,lastPreview:r.prior?.preview??void 0}]})[0];if(!n)return null;const t=n.item.fingerprint||F(n.item);return t?{item:n.item,fingerprint:t}:null}function q(r){return String(r||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"wechat-channel-runtime"}export{R as WeChatChannelProductRunner,E as buildLocalLedgerTailAnchors,ee as createWeChatChannelProductRunner};
1
+ import l from"node:path";import d from"node:fs";import{createWeChatChannelApiClient as b}from"./client.js";import{WeChatChannelHelperClient as c}from"./helper-client.js";import{resolveWeChatChannelHelperAsset as v,WECHAT_CHANNEL_HELPER_VERSION as y}from"./helper-assets.js";import{requiredWindowsWeChatChannelHelperCapabilitiesForProfile as h}from"./helper-protocol.js";import{captureWeChatWindow as W,ensureWeChatWindowReady as p,fallbackWeChatMessageInputPointForWindow as k,focusKnownWeChatWindow as f,observeWeChatChannelBindingViaHelper as S,openConversationBySearch as I,recognizeWeChatScreenshot as P}from"./observer.js";import{WeChatChannelScheduler as A}from"./scheduler.js";import{loadWeChatChannelLedger as O}from"./ledger.js";import{buildWeChatConversationFingerprint as F,matchWeChatConversationFingerprints as x}from"./fingerprint.js";import{WeChatChannelOutboundSender as _}from"./outbound-sender.js";import{decideWeChatChannelActivityGate as D,nextWeChatChannelActivityRetryAt as H,normalizeWeChatChannelActivitySnapshot as T}from"./human-coordination.js";class R{options;scheduler;helper;helperStarted=!1;constructor(e){this.options=e;const n=e.api??b();this.helper=e.helper??M(e),this.scheduler=new A({runtime:e.runtime,workDir:e.workDir,api:n,ledgerPath:e.ledgerPath,outboundLedgerPath:e.outboundLedgerPath,onInboundMessages:e.onInboundMessages,activityGate:{canObserve:async(t,a)=>{if(e.runtime.foregroundPolicy==="work")return{ok:!0};const o=await this.helper.request("activity.snapshot",{});if(!o.ok)return{ok:!1,reasonCode:"user_activity_unknown",nextAttemptAt:new Date(a.getTime()+5e3)};const i=D({snapshot:T(o.result),stage:"observe"});return i.ok?{ok:!0}:{ok:!1,reasonCode:i.reasonCode,nextAttemptAt:H(a,i.waitMs)}}},shouldSkipObserve:t=>this.shouldSkipObserveForUnchangedFingerprint(t),outboundSender:new _({helper:this.helper,platform:e.runtime.policy.platform,openConversation:async t=>{const a=await p(this.helper),o=await f(this.helper,a);return{...await I({helper:this.helper,window:o,settleToBottom:!1,binding:{bindingId:`outbound:${t}`,sessionId:"outbound",conversationDisplayName:t,enabled:!0,allowReply:!0,downloadMedia:!0}}),inputPoint:k(o)}}}),recoverWeChat:(t,a)=>this.reportUnrecoverableWindowsWeChat(t,a),observeBinding:(t,a)=>S({runtime:e.runtime,binding:t,helper:this.helper,api:n,workDir:e.workDir,localLedgerTailAnchors:E(e.ledgerPath??U(e.workDir,e.runtime.runtimeId),e.runtime.runtimeId,t.bindingId),localOutboundEchoAnchors:a?.localOutboundEchoAnchors,onObservationEvidence:({ocr:o})=>{N({statePath:g(e.workDir,e.runtime.runtimeId),runtimeId:e.runtime.runtimeId,binding:t,ocr:o})}})})}async start(){await this.ensureHelperStarted(),await this.scheduler.start()}async tick(){return await this.ensureHelperStarted(),this.scheduler.tick()}async stop(){this.scheduler.stop(),this.helperStarted&&this.helper.stop&&await this.helper.stop(),this.helperStarted=!1}async ensureHelperStarted(){this.helperStarted||(this.helper.start&&await this.helper.start(),this.helperStarted=!0)}async reportUnrecoverableWindowsWeChat(e,n){return this.options.runtime.policy.platform!=="win32",!1}async shouldSkipObserveForUnchangedFingerprint(e){if(this.options.runtime.policy.platform!=="darwin"&&this.options.runtime.policy.platform!=="win32")return{skip:!1};const n=g(this.options.workDir,this.options.runtime.runtimeId),t=u(n,this.options.runtime.runtimeId).bindings[e.bindingId];if(!t?.fingerprint)return{skip:!1};try{const a=await p(this.helper),o=await f(this.helper,a),i=await W(this.helper,o.windowId,void 0,o.bounds),C=await P(this.helper,i),s=w({binding:e,ocr:C,prior:t});return!s?.fingerprint||s.fingerprint!==t.fingerprint?{skip:!1}:{skip:!0,reasonCode:"conversation_fingerprint_unchanged",fingerprint:s.fingerprint}}catch{return{skip:!1}}}}function ee(r){return new R(r)}function E(r,e,n,t=3){return(O(r,e).bindings[n]?.recent??[]).slice(-t).map(i=>({stableMessageKey:i.stableMessageKey,senderRole:i.senderRole,kind:i.kind,anchorText:i.anchorText??i.normalizedText??i.textExcerpt??null,anchorMetadata:i.anchorMetadata??null}))}function M(r){if(r.helperClientOptions)return new c({...r.helperClientOptions,requiredCapabilities:r.helperClientOptions.requiredCapabilities??m(r.runtime),guardUnsafeWindowsCommands:r.helperClientOptions.guardUnsafeWindowsCommands??r.runtime.policy.platform==="win32",skipUserActivityGuard:r.helperClientOptions.skipUserActivityGuard??r.runtime.foregroundPolicy==="work",cleanupWindowsOverlays:r.helperClientOptions.cleanupWindowsOverlays??r.runtime.policy.platform==="win32"});const e=v();if(!e.ok)throw new Error(`${e.reasonCode}: ${e.message}`);return new c({helperPath:e.helperPath,expectedHelperVersion:e.version||y,requiredCapabilities:m(r.runtime),guardUnsafeWindowsCommands:r.runtime.policy.platform==="win32",skipUserActivityGuard:r.runtime.foregroundPolicy==="work",cleanupWindowsOverlays:r.runtime.policy.platform==="win32"})}function m(r){if(r.policy.platform==="win32")return r.bindings.some(e=>e.enabled!==!1&&e.allowReply!==!1)?h("send"):r.bindings.some(e=>e.enabled!==!1&&e.downloadMedia!==!1)?h("download"):h("observe")}function U(r,e){return l.join(r,"wechat-channel",`${e}.ledger.json`)}function g(r,e){return l.join(r,"wechat-channel",`${q(e)}.conversation-fingerprints.json`)}function u(r,e){try{const n=JSON.parse(d.readFileSync(r,"utf8"));if(n?.version===1&&n.runtimeId===e&&n.bindings&&typeof n.bindings=="object")return n}catch{}return{version:1,runtimeId:e,bindings:{}}}function L(r,e){d.mkdirSync(l.dirname(r),{recursive:!0}),d.writeFileSync(r,JSON.stringify(e,null,2))}function N(r){const e=w({binding:r.binding,ocr:r.ocr,prior:u(r.statePath,r.runtimeId).bindings[r.binding.bindingId]});if(!e?.fingerprint)return;const n=u(r.statePath,r.runtimeId);n.bindings[r.binding.bindingId]={bindingId:r.binding.bindingId,fingerprint:e.fingerprint,title:e.item.title??null,preview:e.item.preview??null,timeText:e.item.timeText??null,unreadText:e.item.unreadText??null,observedAt:new Date().toISOString()},L(r.statePath,n)}function w(r){const e=r.ocr.visibleConversationFingerprints??[],n=x({visibleItems:e,targets:[{bindingId:r.binding.bindingId,conversationDisplayName:r.binding.conversationDisplayName,lastConversationFingerprint:r.prior?.fingerprint,lastPreview:r.prior?.preview??void 0}]})[0];if(!n)return null;const t=n.item.fingerprint||F(n.item);return t?{item:n.item,fingerprint:t}:null}function q(r){return String(r||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"wechat-channel-runtime"}export{R as WeChatChannelProductRunner,E as buildLocalLedgerTailAnchors,ee as createWeChatChannelProductRunner};
@@ -1,7 +1,8 @@
1
1
  import type { ExternalChannelConfig, ExternalMessageEvent, ExternalReply } from '../base.js';
2
2
  import type { WeChatRpaPendingReplyProjection } from '@shennian/wire';
3
3
  import { type WeChatChannelProductRunnerOptions } from '../wechat-channel/runner.js';
4
- import { type WeChatChannelRuntime } from '../wechat-channel/runtime.js';
4
+ import { type WeChatChannelBindingConfig, type WeChatChannelRuntime } from '../wechat-channel/runtime.js';
5
+ import type { WeChatChannelObservedMessage } from '../wechat-channel/client.js';
5
6
  import { type WeChatChannelOutboundRecord } from '../wechat-channel/outbound-ledger.js';
6
7
  export declare const WECHAT_RPA_PRODUCT_SOURCE: "wechat-channel";
7
8
  export type WeChatRpaProductSourceSecret = {
@@ -67,3 +68,4 @@ export declare function weChatChannelBindingId(channelId: string, conversationNa
67
68
  export declare function weChatChannelLedgerPath(workDir: string, runtimeId: string): string;
68
69
  export declare function weChatChannelOutboundLedgerPath(workDir: string, runtimeId: string): string;
69
70
  export declare function safeWeChatChannelRuntimePathSegment(runtimeId: string): string;
71
+ export declare function observedMessageToExternalEvent(config: ExternalChannelConfig, binding: WeChatChannelBindingConfig, message: WeChatChannelObservedMessage): ExternalMessageEvent | null;
@@ -5,4 +5,4 @@ ${s.join(`
5
5
  `)}`),bindingId:r,runtimeId:n,sessionId:e.config.sessionId||e.config.managerSessionId,conversationName:e.conversationName,replyBaseRevision:a.bindings[r]?.revision??0,text:e.reply.text,attachmentLocalRefs:s});return p(t,o),c}function Y(e){return m(u(e.config.workDir,e.config.id),e.config.id).records.filter(r=>["queued","sending","sent_unconfirmed"].includes(r.sendStatus)).length}function ee(e){return m(u(e.config.workDir,e.config.id),e.config.id).records.filter(r=>["queued","sending","sent_unconfirmed"].includes(r.sendStatus)).slice(0,100).map(r=>({replyId:r.replyId,idempotencyKey:r.idempotencyKey,status:r.sendStatus,conversationName:r.conversationName,reasonCode:r.deferReason||r.failureCode||null,nextAttemptAt:r.nextAttemptAt??null,queuedAt:r.queuedAt??r.createdAt,lastAttemptAt:r.lastAttemptAt??null,attemptCount:r.attemptCount??0,commitStage:r.commitStage??null}))}function ne(e){const n=q(e.limit,20,1,100),r=y(e.product.ledgerPath,e.product.runtime.runtimeId),a=[];for(const t of e.product.runtime.bindings){const o=r.bindings[t.bindingId]?.recent??[];for(const s of o.slice(-n)){const c=w(e.config,t,s);c&&a.push(c)}}return a}function te(e){const n=u(e.config.workDir,e.config.id),r=m(n,e.config.id),a=String(e.idempotencyKey??"").trim(),t=String(e.replyId??"").trim();if(!a&&!t)return{cancelled:!1};const o=r.records.find(c=>a&&c.idempotencyKey===a||t&&c.replyId===t);if(!o)return{cancelled:!1};const s=o.sendStatus;return M(o,e.reason||"user_cancelled"),o.sendStatus!==s?(p(n,r),{cancelled:!0,status:o.sendStatus,record:o}):{cancelled:!1,status:o.sendStatus,record:o}}function re(e){return e===W}function D(e){return d("wechat-conversation",i(e))}function b(e,n){return d("wechat-channel-binding",`${e}
6
6
  ${i(n)}`)}function I(e,n){return g.join(e,"wechat-channel",`${C(n)}.ledger.json`)}function u(e,n){return g.join(e,"wechat-channel",`${C(n)}.outbound-ledger.json`)}function C(e){return String(e||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"wechat-channel-runtime"}function $(e,n){return L(n).map(r=>({bindingId:b(e.id,r),sessionId:e.sessionId||e.managerSessionId,conversationDisplayName:r,enabled:!0,allowReply:n.canReply!==!1,downloadMedia:n.downloadAttachments!==!1}))}function w(e,n,r){const a=i(r.normalizedText||r.anchorText||r.textExcerpt||""),t=K(r);if(!a&&t.length===0)return null;const o=i(r.senderName||"");return{type:"external.message",channelId:e.id,channelType:"wechat-rpa",conversationId:D(n.conversationDisplayName),conversationName:n.conversationDisplayName,messageId:d("wechat-message",`${n.bindingId}
7
7
  ${r.stableMessageKey}`),sender:{id:d("wechat-sender",`${n.bindingId}
8
- ${o||r.senderRole}`),name:o||null},text:a,attachments:t,receivedAt:j(r.observedAt),isMentioned:!1,replyTarget:"",rawRef:r.stableMessageKey}}function K(e){const n=e.mediaMetadata;return z(n).map(a=>{const t=a,o=typeof t.localPath=="string"?i(t.localPath):"",s=typeof t.thumbnailPath=="string"?i(t.thumbnailPath):"",c=typeof t.url=="string"?i(t.url):"",l=Number(t.size??t.sizeBytes??0);return{type:i(String(t.type||t.kind||e.kind||"file"))||"file",...t.name||t.fileName?{name:i(String(t.name||t.fileName))}:{},...c?{url:c}:{},...typeof t.mimeType=="string"?{mimeType:i(t.mimeType)}:{},...typeof t.extension=="string"?{extension:i(t.extension)}:{},...Number.isFinite(l)&&l>0?{size:l}:{},...o?{localPath:o}:{},...s?{thumbnailPath:s}:{},...typeof t.hash=="string"||typeof t.sha256=="string"?{hash:i(String(t.hash||t.sha256))}:{},availability:E(t.availability,{localPath:o,url:c}),...typeof t.providerError=="string"?{providerError:i(t.providerError)}:{},...typeof t.sourceAction=="string"?{sourceAction:i(t.sourceAction)}:{},...k(t.materializationKind)?{materializationKind:t.materializationKind}:{},...typeof t.isOriginal=="boolean"?{isOriginal:t.isOriginal}:{},...typeof t.mimeKindMatches=="boolean"?{mimeKindMatches:t.mimeKindMatches}:{}}})}function z(e){if(!e||typeof e!="object")return[];const n=e;return Array.isArray(n.attachments)?n.attachments:n.attachment&&typeof n.attachment=="object"?[n.attachment]:n.localPath||n.url||n.name||n.fileName||n.availability?[n]:[]}function E(e,n){return e==="edge-local"||e==="server-url"||e==="pending-download"||e==="metadata-only"||e==="unavailable-large"?e:n.localPath?"edge-local":n.url?"server-url":"metadata-only"}function k(e){return e==="original-file"||e==="clipboard-image"||e==="preview-crop"||e==="metadata"}function T(e){const n=i(e.localPath||"");if(!n)throw new Error("WeChat channel product send requires a local attachment path on this machine");if(!S.statSync(n).isFile())throw new Error(`WeChat channel product attachment is not a file: ${n}`);return n}function L(e){return Array.isArray(e.groups)?e.groups.map(n=>i(n?.name||"")).filter(Boolean):[]}function _(){return process.env.SHENNIAN_MACHINE_ID?.trim()||A().machineId||"local"}function j(e){if(!e)return new Date().toISOString();const n=new Date(e);return Number.isNaN(n.getTime())?new Date().toISOString():n.toISOString()}function i(e){return e.replace(/\s+/g," ").trim()}function q(e,n,r,a){const t=Number(e);return Number.isFinite(t)?Math.max(r,Math.min(a,Math.floor(t))):n}function d(e,n){return`${e}:${P.createHash("sha256").update(n).digest("hex").slice(0,24)}`}export{W as WECHAT_RPA_PRODUCT_SOURCE,te as cancelWeChatRpaProductOutboundReply,Y as countPendingWeChatRpaProductOutbound,V as createWeChatRpaProductSourceConnection,X as enqueueWeChatRpaProductOutboundReply,re as isWeChatRpaProductSource,ee as listPendingWeChatRpaProductOutbound,ne as listWeChatRpaProductRecentMessages,C as safeWeChatChannelRuntimePathSegment,b as weChatChannelBindingId,D as weChatChannelConversationId,I as weChatChannelLedgerPath,u as weChatChannelOutboundLedgerPath};
8
+ ${o||r.senderRole}`),name:o||null},text:a,attachments:t,receivedAt:j(r.observedAt),isMentioned:!1,replyTarget:"",rawRef:r.stableMessageKey}}function K(e){const n=e.mediaMetadata;return z(n).map(a=>{const t=a,o=typeof t.localPath=="string"?i(t.localPath):"",s=typeof t.thumbnailPath=="string"?i(t.thumbnailPath):"",c=typeof t.url=="string"?i(t.url):"",l=Number(t.size??t.sizeBytes??0);return{type:i(String(t.type||t.kind||e.kind||"file"))||"file",...t.name||t.fileName?{name:i(String(t.name||t.fileName))}:{},...c?{url:c}:{},...typeof t.mimeType=="string"?{mimeType:i(t.mimeType)}:{},...typeof t.extension=="string"?{extension:i(t.extension)}:{},...Number.isFinite(l)&&l>0?{size:l}:{},...o?{localPath:o}:{},...s?{thumbnailPath:s}:{},...typeof t.hash=="string"||typeof t.sha256=="string"?{hash:i(String(t.hash||t.sha256))}:{},availability:E(t.availability,{localPath:o,url:c}),...typeof t.providerError=="string"?{providerError:i(t.providerError)}:{},...typeof t.sourceAction=="string"?{sourceAction:i(t.sourceAction)}:{},...k(t.materializationKind)?{materializationKind:t.materializationKind}:{},...typeof t.isOriginal=="boolean"?{isOriginal:t.isOriginal}:{},...typeof t.mimeKindMatches=="boolean"?{mimeKindMatches:t.mimeKindMatches}:{}}})}function z(e){if(!e||typeof e!="object")return[];const n=e;return Array.isArray(n.attachments)?n.attachments:n.attachment&&typeof n.attachment=="object"?[n.attachment]:n.localPath||n.url||n.name||n.fileName||n.availability?[n]:[]}function E(e,n){return e==="edge-local"||e==="server-url"||e==="pending-download"||e==="metadata-only"||e==="unavailable-large"?e:n.localPath?"edge-local":n.url?"server-url":"metadata-only"}function k(e){return e==="original-file"||e==="clipboard-image"||e==="preview-crop"||e==="metadata"}function T(e){const n=i(e.localPath||"");if(!n)throw new Error("WeChat channel product send requires a local attachment path on this machine");if(!S.statSync(n).isFile())throw new Error(`WeChat channel product attachment is not a file: ${n}`);return n}function L(e){return Array.isArray(e.groups)?e.groups.map(n=>i(n?.name||"")).filter(Boolean):[]}function _(){return process.env.SHENNIAN_MACHINE_ID?.trim()||A().machineId||"local"}function j(e){if(!e)return new Date().toISOString();const n=new Date(e);return Number.isNaN(n.getTime())?new Date().toISOString():n.toISOString()}function i(e){return e.replace(/\s+/g," ").trim()}function q(e,n,r,a){const t=Number(e);return Number.isFinite(t)?Math.max(r,Math.min(a,Math.floor(t))):n}function d(e,n){return`${e}:${P.createHash("sha256").update(n).digest("hex").slice(0,24)}`}export{W as WECHAT_RPA_PRODUCT_SOURCE,te as cancelWeChatRpaProductOutboundReply,Y as countPendingWeChatRpaProductOutbound,V as createWeChatRpaProductSourceConnection,X as enqueueWeChatRpaProductOutboundReply,re as isWeChatRpaProductSource,ee as listPendingWeChatRpaProductOutbound,ne as listWeChatRpaProductRecentMessages,w as observedMessageToExternalEvent,C as safeWeChatChannelRuntimePathSegment,b as weChatChannelBindingId,D as weChatChannelConversationId,I as weChatChannelLedgerPath,u as weChatChannelOutboundLedgerPath};
@@ -30,12 +30,14 @@ type WeChatToolBinding = {
30
30
  conversationName: string;
31
31
  workDir: string;
32
32
  };
33
+ export type WeChatToolTransport = 'auto' | 'daemon' | 'direct';
33
34
  export type WeChatToolOptions = {
34
35
  conversation: string;
35
36
  sessionId?: string;
36
37
  workDir?: string;
37
38
  source?: string;
38
39
  download?: 'auto' | 'never';
40
+ transport?: WeChatToolTransport;
39
41
  traceId?: string;
40
42
  timeoutMs?: number;
41
43
  recentLimit?: number;
@@ -49,6 +51,8 @@ export type WeChatDirectToolRuntime = {
49
51
  send(binding: WeChatToolBinding, options: WeChatSendOptions): Promise<{
50
52
  pending?: boolean;
51
53
  sendStatus?: WeChatSendResult['sendStatus'];
54
+ outDir?: string;
55
+ helperTracePath?: string | null;
52
56
  }>;
53
57
  };
54
58
  export type WeChatSendOptions = WeChatToolOptions & {
@@ -1,12 +1,7 @@
1
- import j from"node:crypto";import q from"node:fs";import W from"node:path";import{SERVERS as U}from"../region.js";import{loadConfig as B,resolveShennianPath as R}from"../config/index.js";import{createWeChatRpaProductSourceConnection as z,listWeChatRpaProductRecentMessages as F,weChatChannelConversationId as J}from"../channels/wechat-rpa/product-channel.js";import{createWeChatChannelProductRunner as H}from"../channels/wechat-channel/runner.js";import{createWeChatChannelApiClient as K}from"../channels/wechat-channel/client.js";import{readExternalAttachment as V}from"./external-attachments.js";import{resolveWeChatChannelHelperAsset as Q,WECHAT_CHANNEL_HELPER_VERSION as Z}from"../channels/wechat-channel/helper-assets.js";import{WeChatChannelHelperClient as G}from"../channels/wechat-channel/helper-client.js";import{requiredWindowsWeChatChannelHelperCapabilitiesForProfile as X}from"../channels/wechat-channel/helper-protocol.js";import{ensureWeChatWindowReady as Y,fallbackWeChatMessageInputPointForWindow as ee,focusKnownWeChatWindow as ne,openConversationBySearch as te}from"../channels/wechat-channel/observer.js";import{enqueueWeChatOutboundReply as re}from"../channels/wechat-channel/outbound-ledger.js";import{sendQueuedWeChatOutboundRecords as _,WeChatChannelOutboundSender as ae}from"../channels/wechat-channel/outbound-sender.js";async function oe(e={}){const n=B(),t=he(e.serverUrl||n.serverUrl||U.cn.url),a=e.machineToken||n.machineToken,r=e.machineId||n.machineId,s=e.fetchImpl||fetch,o=[f("cli_installed","Shennian CLI is installed."),a?f("machine_paired","Machine token is configured."):v("machine_paired","machine_not_paired","Machine is not paired. Run shennian pair first.")];if(!a)return y({checks:o,serverUrl:t,machineId:r});try{const d=await s(`${t}/api/channels/wechat/runtime-policy`,{headers:{authorization:`Bearer ${a}`}}),i=await d.json().catch(()=>null);if(!d.ok||i?.ok===!1){const l=I(i?.reasonCode)||d.statusText||"request_failed";return o.push(v("server_policy",l,`WeChat runtime policy is blocked: ${l}`)),y({checks:o,serverUrl:t,machineId:r})}const c=g(i?.usagePolicy)?i.usagePolicy:{};o.push(f("server_policy","Server WeChat policy is reachable.")),o.push(f("entitlement","Account has Pro / Team external channel entitlement.")),o.push(f("credits",c.billingMode==="credits"?"Credit billing is enabled; balance is checked by server calls.":"Credits are not metered in current billing mode."));const h=g(i?.runtime)&&g(i.runtime.current)?i.runtime.current:null;o.push(h?f("runtime_registered","Local WeChat runtime is registered on the server."):me("runtime_registered","runtime_not_registered","Local WeChat runtime is not registered yet; open Shennian Desktop/daemon and enable Use WeChat."))}catch(d){o.push(v("server_policy","network_unavailable",d instanceof Error?d.message:"Unable to reach Shennian server."))}return y({checks:o,serverUrl:t,machineId:r})}async function ie(e){const n=e.text.trim();if(!n&&!e.attachment)throw new Error("Message text or attachment is required");const t=x(e),a=e.attachment?We(e.attachment):void 0;if(e.dryRun)return{ok:!0,operation:"write",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:t.workDir,helperTracePath:null,sendStatus:"dry_run",pending:!1,attachment:a,attachments:a?[a]:[],dryRun:!0,reasonCode:null,message:"Dry run completed. No WeChat message was sent."};if(!M(e)){const c=await(e.directRuntime??b).send(t,e);return{ok:!0,operation:"write",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:t.workDir,helperTracePath:null,sendStatus:c.sendStatus??(c.pending?"pending":"sent"),pending:c.pending===!0,attachment:a,attachments:a?[a]:[],reasonCode:null,message:c.pending?"WeChat message is queued or waiting for confirmation.":"WeChat message sent."}}const r=e.ipc??$();await N(r,t,e);const s=e.idempotencyKey||C("wechat-cli-send",`${t.channelId}
1
+ import L from"node:crypto";import S from"node:fs";import g from"node:path";import{SERVERS as O}from"../region.js";import{loadConfig as k,resolveShennianPath as _}from"../config/index.js";import{observedMessageToExternalEvent as U,weChatChannelConversationId as q,weChatChannelBindingId as z}from"../channels/wechat-rpa/product-channel.js";import{readExternalAttachment as B}from"./external-attachments.js";import{runWeChatChannelActionSmoke as $}from"../devtools/wechat-channel-action-smoke.js";async function F(e={}){const n=k(),t=Q(e.serverUrl||n.serverUrl||O.cn.url),a=e.machineToken||n.machineToken,r=e.machineId||n.machineId,o=e.fetchImpl||fetch,s=[m("cli_installed","Shennian CLI is installed."),a?m("machine_paired","Machine token is configured."):C("machine_paired","machine_not_paired","Machine is not paired. Run shennian pair first.")];if(!a)return v({checks:s,serverUrl:t,machineId:r});try{const d=await o(`${t}/api/channels/wechat/runtime-policy`,{headers:{authorization:`Bearer ${a}`}}),i=await d.json().catch(()=>null);if(!d.ok||i?.ok===!1){const l=y(i?.reasonCode)||d.statusText||"request_failed";return s.push(C("server_policy",l,`WeChat runtime policy is blocked: ${l}`)),v({checks:s,serverUrl:t,machineId:r})}const c=u(i?.usagePolicy)?i.usagePolicy:{};s.push(m("server_policy","Server WeChat policy is reachable.")),s.push(m("entitlement","Account has Pro / Team external channel entitlement.")),s.push(m("credits",c.billingMode==="credits"?"Credit billing is enabled; balance is checked by server calls.":"Credits are not metered in current billing mode."));const f=u(i?.runtime)&&u(i.runtime.current)?i.runtime.current:null;s.push(f?m("runtime_registered","Local WeChat runtime is registered on the server."):G("runtime_registered","runtime_not_registered","Local WeChat runtime is not registered yet; open Shennian Desktop/daemon and enable Use WeChat."))}catch(d){s.push(C("server_policy","network_unavailable",d instanceof Error?d.message:"Unable to reach Shennian server."))}return v({checks:s,serverUrl:t,machineId:r})}async function J(e){const n=e.text.trim();if(!n&&!e.attachment)throw new Error("Message text or attachment is required");const t=x(e),a=e.attachment?fe(e.attachment):void 0;if(e.dryRun)return{ok:!0,operation:"write",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:t.workDir,helperTracePath:null,sendStatus:"dry_run",pending:!1,attachment:a,attachments:a?[a]:[],dryRun:!0,reasonCode:null,message:"Dry run completed. No WeChat message was sent."};const r=D(e);if(!r){const c=e.directRuntime?await e.directRuntime.send(t,e):await H(t,e);return{ok:!0,operation:"write",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:c.outDir??t.workDir,helperTracePath:c.helperTracePath??null,sendStatus:c.sendStatus??(c.pending?"pending":"sent"),pending:c.pending===!0,attachment:a,attachments:a?[a]:[],reasonCode:null,message:c.pending?"WeChat message is queued or waiting for confirmation.":"WeChat message sent."}}await P(r,t,e);const o=e.idempotencyKey||T("wechat-cli-send",`${t.channelId}
2
2
  ${t.conversationId}
3
3
  ${n}
4
- ${Date.now()}`),o=await w(r,"/external/reply",{managerSessionId:t.sessionId,channelId:t.channelId,conversationId:t.conversationId,text:n,attachment:e.attachment,idempotencyKey:s},e.fetchImpl,e.timeoutMs),d=await w(r,"/wechat-rpa/channel/sync",{managerSessionId:t.sessionId},e.fetchImpl,e.timeoutMs),i=Ie(d,s,o.pending===!0);return{ok:!0,operation:"write",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:t.workDir,helperTracePath:null,sendStatus:i,pending:o.pending===!0&&(i==="queued"||i==="pending"),attachment:a,attachments:a?[a]:[],reasonCode:null,message:i==="confirmed_echo"?"WeChat message sent and confirmed.":i==="manual_review"?"WeChat message was submitted but needs manual review.":"WeChat message submitted to local daemon."}}async function se(e){const n=m(e.limit,10),t=x(e);if(!M(e)){const c=(await(e.directRuntime??b).readLatest(t,{...e,limit:n,recentLimit:e.recentLimit??n})).filter(l=>l.conversationId===t.conversationId||l.conversationName===t.conversationName).slice(-n),h=T(c);return{ok:!0,operation:"read",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:t.workDir,helperTracePath:null,messages:c,attachments:h,count:c.length,reasonCode:null,message:`Read ${c.length} WeChat message(s).`}}const a=e.ipc??$();await N(a,t,{...e,recentLimit:e.recentLimit??n});const r=await w(a,"/wechat-rpa/channel/sync",{managerSessionId:t.sessionId},e.fetchImpl,e.timeoutMs),o=(Array.isArray(r.messages)?r.messages.filter(Ne):[]).filter(i=>i.conversationId===t.conversationId||i.conversationName===t.conversationName).slice(-n),d=T(o);return{ok:!0,operation:"read",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:t.workDir,helperTracePath:null,messages:o,attachments:d,count:o.length,reasonCode:null,message:`Read ${o.length} WeChat message(s).`}}const b={async readLatest(e,n){const{config:t,secret:a}=D(e,n),r=P(t,a,n);try{return await r.runner.tick(),F({config:t,product:r,limit:n.limit??n.recentLimit??10})}finally{await r.runner.stop().catch(()=>{})}},async send(e,n){return ce(e,n)}};async function ce(e,n){const{config:t,secret:a}=D(e,n),r=P(t,a,n),s=r.runtime.bindings.find(l=>l.conversationDisplayName===e.conversationName)??r.runtime.bindings[0];if(!s)throw new Error("wechat_direct_binding_missing");const o=r.runtime.policy.platform,d=Q({platform:o});if(!d.ok)throw new Error(`${d.reasonCode}: ${d.message}`);const i=new G({helperPath:d.helperPath,expectedHelperVersion:d.version||Z,requiredCapabilities:o==="win32"?X("send"):void 0,guardUnsafeWindowsCommands:o==="win32",cleanupWindowsOverlays:o==="win32"}),c={version:1,runtimeId:r.runtime.runtimeId,records:[]},h=n.idempotencyKey||C("wechat-cli-send",`${e.channelId}
5
- ${e.conversationId}
6
- ${n.text}
7
- ${n.attachment?.localPath||""}
8
- ${Date.now()}`);re({ledger:c,replyId:`wechat-cli:${h}`,idempotencyKey:h,bindingId:s.bindingId,runtimeId:r.runtime.runtimeId,sessionId:s.sessionId,conversationName:s.conversationDisplayName,replyBaseRevision:0,text:n.text,attachmentLocalRefs:n.attachment?.localPath?[n.attachment.localPath]:[]});try{await i.start();const l=new ae({helper:i,platform:o,traceId:n.traceId,openConversation:async L=>{const O=await Y(i,n.traceId),S=await ne(i,O,n.traceId);return{...await te({helper:i,window:S,settleToBottom:!1,binding:{...s,conversationDisplayName:L},traceId:n.traceId}),inputPoint:ee(S)}}}),u=await de({ledger:c,bindingId:s.bindingId,sender:l,maxWaitMs:n.timeoutMs??9e4}),k=c.records[0];if(u.sentRecords.length>0)return{pending:!0,sendStatus:"sent_unconfirmed"};if(u.manualReviewRecords.length>0)return{pending:!1,sendStatus:"manual_review"};if(u.waitingRecords.length>0)return{pending:!0,sendStatus:"pending"};if(u.staleRecords.length>0)throw new Error(u.staleRecords[0]?.failureCode||"reply_revision_stale");if(u.failedRecords.length>0)throw new Error(u.failedRecords[0]?.failureCode||u.failedRecords[0]?.lastErrorSummary||"wechat_direct_send_failed");return{pending:k?.sendStatus==="queued",sendStatus:k?.sendStatus==="queued"?"queued":"pending"}}finally{await i.stop().catch(()=>{}),await r.runner.stop().catch(()=>{})}}async function de(e){const n=e.maxAttempts??24,t=e.maxWaitMs??9e4,a=Date.now();let r=await _({ledger:e.ledger,bindingId:e.bindingId,currentLastInboundRevision:0,sender:e.sender,maxUserActivityWaitMs:t});for(let s=2;r.waitingRecords.length>0&&s<=n&&!(Date.now()-a>=t);s+=1){const o=le(r.waitingRecords[0]?.nextAttemptAt,a,t);await ue(o),r=await _({ledger:e.ledger,bindingId:e.bindingId,currentLastInboundRevision:0,sender:e.sender,maxUserActivityWaitMs:t})}return r}function le(e,n,t){const a=Math.max(0,n+t-Date.now());if(a<=0)return 0;if(!e)return Math.min(1200,a);const r=new Date(e).getTime();return Number.isFinite(r)?Math.max(250,Math.min(r-Date.now(),a)):Math.min(1200,a)}function ue(e){return new Promise(n=>setTimeout(n,Math.max(0,e)))}function He(e){const n=e.command("wechat").description("Use WeChat through Shennian local runtime");n.option("--conversation <name>","WeChat conversation/group name"),n.command("doctor").description("Check local Shennian, pairing, entitlement, credits, and WeChat runtime readiness").option("--json","Output JSON",!1).action(async a=>{const r=await oe();if(a.json)console.log(JSON.stringify(r,null,2));else for(const s of r.checks){const o=s.ok?"\u2713":s.status==="warning"?"!":"\u2717";console.log(`${o} ${s.id}: ${s.message}`)}r.ok||(process.exitCode=1)}),n.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("--format <format>","Output format: json or text","text").option("--json","Output JSON",!1).action(async(a,r,s)=>{const o=A(s,r.conversation),d=r.text??a.join(" "),i=Ce(r),c=await ie({conversation:o,text:d,attachment:i,sessionId:r.sessionId,workDir:r.workDir,idempotencyKey:r.idempotencyKey,dryRun:r.dryRun,traceId:r.traceId,timeoutMs:r.timeout?m(r.timeout,24e4):void 0});ge(c,r.json||r.format==="json")}),n.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("--json","Output JSON",!1).action(async(a,r,s)=>{const o=A(s,r.conversation),d=ke(r.json?"json":r.format),i=await se({conversation:o,limit:m(r.limit??a,10),download:Se(r.download),traceId:r.traceId,timeoutMs:r.timeout?m(r.timeout,24e4):void 0,sessionId:r.sessionId,workDir:r.workDir});pe(i,d)})}function y(e){return{ok:e.checks.every(n=>n.ok||n.status==="warning"),checks:e.checks,serverUrl:e.serverUrl,machineId:e.machineId}}function f(e,n){return{id:e,ok:!0,status:"ready",message:n}}function me(e,n,t){return{id:e,ok:!1,status:"warning",reasonCode:n,message:t}}function v(e,n,t){return{id:e,ok:!1,status:"blocked",reasonCode:n,message:t}}function he(e){return e.replace(/\/+$/,"")}function $(){const e=R("runtime","manager-ipc.json");let n;try{n=JSON.parse(q.readFileSync(e,"utf-8"))}catch{throw new Error("Manager IPC is not available. Open Shennian Desktop or run `shennian start` first.")}const t=Number(n.pid);if(Number.isInteger(t)&&t>0&&!be(t))throw new Error(`Manager IPC runtime file is stale for PID ${t}. Restart Shennian Desktop or run \`shennian restart\`.`);const a=typeof n.url=="string"?n.url.replace(/\/+$/,""):"",r=typeof n.token=="string"?n.token:"";if(!a||!r)throw new Error(`Manager IPC runtime file is incomplete: ${e}`);return{url:a,token:r}}async function N(e,n,t){return w(e,"/wechat-rpa/channel/upsert",{managerSessionId:n.sessionId,id:n.channelId,name:`WeChat ${n.conversationName}`,workDir:n.workDir,enabled:!0,groups:[{name:n.conversationName}],canReply:!0,source:t.source||"wechat-channel",recentLimit:m(t.recentLimit,20),pollIntervalMs:m(t.pollIntervalMs,3e4),forceForeground:!0,downloadAttachments:t.download!=="never",privacyConsentAccepted:!0},t.fetchImpl,t.timeoutMs)}async function w(e,n,t,a=fetch,r){const s=String(t.managerSessionId||"");if(!s)throw new Error("managerSessionId is required");const o=r?new AbortController:null,d=o?setTimeout(()=>o.abort(),r):null;try{const i=await a(`${e.url}${n}`,{method:"POST",headers:{authorization:`Bearer ${e.token}`,"content-type":"application/json","x-shennian-manager-session-id":s},body:JSON.stringify(t),signal:o?.signal}),c=await i.json().catch(()=>({ok:!1,error:i.statusText}));if(!i.ok||!c.ok)throw new Error(c.error||`Manager IPC failed: ${i.status}`);return c}finally{d&&clearTimeout(d)}}function x(e){const n=e.conversation.trim();if(!n)throw new Error("--conversation is required");const t=C("wechat-cli-conversation",n),a=e.sessionId?.trim()||`wechat-cli-${t.slice(-24)}`,r=`wechat-rpa:${a}`;return{sessionId:a,channelId:r,conversationId:J(n),conversationName:n,workDir:W.resolve(e.workDir||R("wechat-cli",$e(n)))}}function M(e){return!e.directRuntime}function D(e,n){return{config:{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}`},secret:{groups:[{name:e.conversationName}],pollIntervalMs:m(n.pollIntervalMs,3e4),forceForeground:!0,downloadAttachments:n.download!=="never",canReply:!0}}}function P(e,n,t){const a=fe(t);return z({config:e,secret:n,createRunner:r=>H({...r,api:a})})}function fe(e){return{...K({fetchImpl:e.fetchImpl}),upsertRuntime:async()=>({ok:!0,status:"local_direct_tool"}),ingest:async()=>({ok:!0,status:"local_direct_tool"}),reportRunStatus:async()=>({ok:!0,status:"local_direct_tool"}),reportOutboundStatus:async()=>({ok:!0,status:"local_direct_tool"})}}function A(e,n){const t=e.parent?.opts()??{},a=n||t.conversation||"";if(!a.trim())throw new Error("--conversation is required");return a}function ge(e,n){if(n){console.log(JSON.stringify(e,null,2));return}console.log(e.sendStatus)}function pe(e,n){if(n==="json"){console.log(JSON.stringify(e,null,2));return}console.log(we(e).trimEnd())}function we(e){const n=[];if(n.push(`${p(e.conversationName||e.conversation)}:`),e.messages.length===0)return n.push("\u6D88\u606F: \uFF08\u6CA1\u6709\u8BFB\u5230\uFF09"),`${n.join(`
4
+ ${Date.now()}`),s=await w(r,"/external/reply",{managerSessionId:t.sessionId,channelId:t.channelId,conversationId:t.conversationId,text:n,attachment:e.attachment,idempotencyKey:o},e.fetchImpl,e.timeoutMs),d=await Y(r,t,e),i=d?ce(d,o,s.pending===!0):s.pending===!0?"queued":"ok";return{ok:!0,operation:"write",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:t.workDir,helperTracePath:null,sendStatus:i,pending:s.pending===!0&&(i==="queued"||i==="pending"),attachment:a,attachments:a?[a]:[],reasonCode:null,message:d?i==="confirmed_echo"?"WeChat message sent and confirmed.":i==="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 K(e){const n=h(e.limit,10),t=x(e),a=D(e);if(!a){const i=e.directRuntime?{messages:await e.directRuntime.readLatest(t,{...e,limit:n,recentLimit:e.recentLimit??n}),outDir:t.workDir,helperTracePath:null}:await Z(t,{...e,limit:n,recentLimit:e.recentLimit??n}),c=i.messages.filter(l=>l.conversationId===t.conversationId||l.conversationName===t.conversationName).slice(-n),f=E(c);return{ok:!0,operation:"read",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:i.outDir,helperTracePath:i.helperTracePath,messages:c,attachments:f,count:c.length,reasonCode:null,message:`Read ${c.length} WeChat message(s).`}}await P(a,t,{...e,recentLimit:e.recentLimit??n});const r=await w(a,"/wechat-rpa/channel/sync",{managerSessionId:t.sessionId},e.fetchImpl,e.timeoutMs),s=(Array.isArray(r.messages)?r.messages.filter(ye):[]).filter(i=>i.conversationId===t.conversationId||i.conversationName===t.conversationName).slice(-n),d=E(s);return{ok:!0,operation:"read",conversation:t.conversationName,conversationName:t.conversationName,conversationId:t.conversationId,sessionId:t.sessionId,channelId:t.channelId,traceId:e.traceId,outDir:t.workDir,helperTracePath:null,messages:s,attachments:d,count:s.length,reasonCode:null,message:`Read ${s.length} WeChat message(s).`}}async function H(e,n){const t=V(n.attachment),a=R(e,n,{kind:"send",steps:["open-read",t],text:n.text,attachment:n.attachment}),r=await $(a),o=r.steps.find(d=>d.step===t);if(!r.ok||!o?.ok){const d=o?.reasonCode||r.steps.find(c=>!c.ok&&!c.skipped)?.reasonCode||"wechat_direct_send_failed",i=o?.errorSummary||r.steps.find(c=>!c.ok&&!c.skipped)?.errorSummary||d;throw new Error(`${d}: ${i}`)}const s=re(o.details?.sendStatus)??"sent_unconfirmed";return{pending:s==="queued"||s==="pending"||s==="sent_unconfirmed",sendStatus:s,outDir:r.outDir,helperTracePath:r.helperTracePath}}function V(e){return e?e.kind==="image"?"send-image":e.kind==="video"?"send-video":"send-file":"send-text"}async function Z(e,n){const t=n.download!=="never",a=t?"download-visible-media":"structure-window",r=R(e,n,{kind:"read",steps:[a],allowEmptyMedia:t}),o=await $(r),s=o.steps.find(l=>l.step===a);if(!o.ok&&s?.reasonCode!=="no_observed_messages"){const l=s?.reasonCode||o.steps.find(p=>!p.ok&&!p.skipped)?.reasonCode||"wechat_direct_read_failed",j=s?.errorSummary||o.steps.find(p=>!p.ok&&!p.skipped)?.errorSummary||l;throw new Error(`${l}: ${j}`)}const d=g.join(o.outDir,t?"download-visible-messages.json":"structure-window-messages.json"),i=te(d),c=ee(e,n),f=ne(e);return{messages:i.map(l=>U(c,f,l)).filter(l=>l!=null),outDir:o.outDir,helperTracePath:o.helperTracePath}}function Pe(e){const n=e.command("wechat").description("Use WeChat through Shennian local runtime");n.option("--conversation <name>","WeChat conversation/group name"),n.command("doctor").description("Check local Shennian, pairing, entitlement, credits, and WeChat runtime readiness").option("--json","Output JSON",!1).action(async a=>{const r=await F();if(a.json)console.log(JSON.stringify(r,null,2));else for(const o of r.checks){const s=o.ok?"\u2713":o.status==="warning"?"!":"\u2717";console.log(`${s} ${o.id}: ${o.message}`)}r.ok||(process.exitCode=1)}),n.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(a,r,o)=>{const s=W(o,r.conversation),d=r.text??a.join(" "),i=ue(r),c=await J({conversation:s,text:d,attachment:i,sessionId:r.sessionId,workDir:r.workDir,idempotencyKey:r.idempotencyKey,dryRun:r.dryRun,traceId:r.traceId,timeoutMs:r.timeout?h(r.timeout,24e4):void 0,transport:M(r.transport)});oe(c,r.json||r.format==="json")}),n.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(a,r,o)=>{const s=W(o,r.conversation),d=me(r.json?"json":r.format),i=await K({conversation:s,limit:h(r.limit??a,10),download:he(r.download),traceId:r.traceId,timeoutMs:r.timeout?h(r.timeout,24e4):void 0,sessionId:r.sessionId,workDir:r.workDir,transport:M(r.transport)});se(i,d)})}function v(e){return{ok:e.checks.every(n=>n.ok||n.status==="warning"),checks:e.checks,serverUrl:e.serverUrl,machineId:e.machineId}}function m(e,n){return{id:e,ok:!0,status:"ready",message:n}}function G(e,n,t){return{id:e,ok:!1,status:"warning",reasonCode:n,message:t}}function C(e,n,t){return{id:e,ok:!1,status:"blocked",reasonCode:n,message:t}}function Q(e){return e.replace(/\/+$/,"")}function N(){const e=_("runtime","manager-ipc.json");let n;try{n=JSON.parse(S.readFileSync(e,"utf-8"))}catch{throw new Error("Manager IPC is not available. Open Shennian Desktop or run `shennian start` first.")}const t=Number(n.pid);if(Number.isInteger(t)&&t>0&&!we(t))throw new Error(`Manager IPC runtime file is stale for PID ${t}. Restart Shennian Desktop or run \`shennian restart\`.`);const a=typeof n.url=="string"?n.url.replace(/\/+$/,""):"",r=typeof n.token=="string"?n.token:"";if(!a||!r)throw new Error(`Manager IPC runtime file is incomplete: ${e}`);return{url:a,token:r}}function X(){try{return N()}catch{return null}}function D(e){if(e.directRuntime)return null;const n=e.transport??"auto";return n==="direct"?null:e.ipc?e.ipc:n==="daemon"||e.sessionId?.trim()?N():X()}async function P(e,n,t){return w(e,"/wechat-rpa/channel/upsert",{managerSessionId:n.sessionId,id:n.channelId,name:`WeChat ${n.conversationName}`,workDir:n.workDir,enabled:!0,groups:[{name:n.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 w(e,n,t,a=fetch,r){const o=String(t.managerSessionId||"");if(!o)throw new Error("managerSessionId is required");const s=r?new AbortController:null,d=s?setTimeout(()=>s.abort(),r):null;try{const i=await a(`${e.url}${n}`,{method:"POST",headers:{authorization:`Bearer ${e.token}`,"content-type":"application/json","x-shennian-manager-session-id":o},body:JSON.stringify(t),signal:s?.signal}),c=await i.json().catch(()=>({ok:!1,error:i.statusText}));if(!i.ok||!c.ok)throw new Error(c.error||`Manager IPC failed: ${i.status}`);return c}finally{d&&clearTimeout(d)}}function x(e){const n=e.conversation.trim();if(!n)throw new Error("--conversation is required");const t=T("wechat-cli-conversation",n),a=e.sessionId?.trim()||`wechat-cli-${t.slice(-24)}`,r=`wechat-rpa:${a}`;return{sessionId:a,channelId:r,conversationId:q(n),conversationName:n,workDir:g.resolve(e.workDir||_("wechat-cli",A(n)))}}async function Y(e,n,t){try{return await w(e,"/wechat-rpa/channel/sync",{managerSessionId:n.sessionId},t.fetchImpl,t.timeoutMs)}catch(a){if(Ie(a))return null;throw a}}function R(e,n,t){const a=n.traceId||`wechat-cli-${t.kind}-${Date.now().toString(36)}`,r=g.join(e.workDir,"runs",A(a)),o=t.attachment;return{conversation:e.conversationName,mode:"custom",steps:t.steps,outDir:r,workDir:e.workDir,traceId:a,runtimeId:e.channelId,machineId:process.env.SHENNIAN_MACHINE_ID||k().machineId||"local",sessionId:e.sessionId,serverUrl:k().serverUrl,platform:ae(),text:t.text,...o?.localPath&&o.kind==="image"?{imagePath:o.localPath}:{},...o?.localPath&&o.kind==="video"?{videoPath:o.localPath}:{},...o?.localPath&&o.kind!=="image"&&o.kind!=="video"?{filePath:o.localPath}:{},allowEmptyMedia:t.allowEmptyMedia,requireServer:!1}}function ee(e,n){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 ne(e){return{bindingId:z(e.channelId,e.conversationName),sessionId:e.sessionId,conversationDisplayName:e.conversationName,enabled:!0,allowReply:!0,downloadMedia:!0}}function te(e){try{const n=JSON.parse(S.readFileSync(e,"utf-8"));return Array.isArray(n)?n.filter(u):[]}catch{return[]}}function re(e){return e==="dry_run"||e==="queued"||e==="sent"||e==="sent_unconfirmed"||e==="confirmed_echo"||e==="manual_review"||e==="pending"||e==="ok"?e:null}function ae(){if(process.platform==="darwin")return"darwin";if(process.platform==="win32")return"win32"}function W(e,n){const t=e.parent?.opts()??{},a=n||t.conversation||"";if(!a.trim())throw new Error("--conversation is required");return a}function oe(e,n){if(n){console.log(JSON.stringify(e,null,2));return}console.log(e.sendStatus)}function se(e,n){if(n==="json"){console.log(JSON.stringify(e,null,2));return}console.log(ie(e).trimEnd())}function ie(e){const n=[];if(e.messages.length===0)return`\uFF08\u6CA1\u6709\u8BFB\u5230\uFF09
5
+ `;for(const t of e.messages)n.push(`${le(t)}: ${de(t)}`);return`${n.join(`
9
6
  `)}
10
- `;for(const t of e.messages)n.push(`${ve(t)}: ${ye(t)}`);return`${n.join(`
11
- `)}
12
- `}function Ie(e,n,t){const a=g(e.channel)?e.channel:{},s=(Array.isArray(a.wechatRpaPendingReplies)?a.wechatRpaPendingReplies:[]).filter(g).find(c=>I(c.idempotencyKey)===n),o=I(s?.status);if(o==="confirmed_echo"||o==="sent_unconfirmed"||o==="manual_review"||o==="queued")return o;const i=(Array.isArray(a.wechatRpaRecentTaskSummaries)?a.wechatRpaRecentTaskSummaries:[]).filter(g).map(c=>I(c.status)).find(Boolean);return i==="sent"?"sent":i==="confirmed_echo"?"confirmed_echo":i==="manual_review"?"manual_review":t?"pending":"ok"}function ye(e){const n=e.attachments||[],t=p(e.text);return t&&n.length===0?t:t&&n.length>0?`${t} ${n.map(E).join("\u3001")}`:n.length>0?n.map(E).join("\u3001"):"\uFF08\u6D88\u606F\uFF09"}function ve(e){return p(e.sender.name||e.sender.id||"\u5BF9\u65B9")}function E(e){const n=p(e.name||W.basename(e.localPath||"")||e.type||"\u9644\u4EF6"),t=e.localPath||e.url||"",a=e.availability&&e.availability!=="edge-local"?` (${e.availability})`:"";return t?`[${_e(n)}](${Re(t)})${a}`:`${n}${a}`}function Ce(e){const n=[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(t=>!!t);if(n.length>1)throw new Error("Pass only one of --file, --image, or --video");if(n.length!==0)return V(n[0].path,n[0].kind)}function ke(e){const n=String(e||"markdown").trim().toLowerCase();if(n==="json")return"json";if(n==="markdown"||n==="md"||n==="text")return"markdown";throw new Error(`Unsupported --format: ${e}. Use markdown or json.`)}function Se(e){const n=String(e||"auto").trim().toLowerCase();if(n==="auto"||n==="never")return n;throw new Error(`Unsupported --download: ${e}. Use auto or never.`)}function We(e){return{kind:e.kind,name:e.name,mimeType:e.mimeType,size:e.size,localPath:e.localPath}}function T(e){return e.flatMap(n=>n.attachments??[])}function Re(e){const n=e.replace(/\\/g,"/");return/[\s()<>]/.test(n)?`<${n.replace(/[<>]/g,"")}>`:n}function _e(e){return p(e).replace(/([\\\]])/g,"\\$1")}function p(e){return String(e||"").replace(/\s+/g," ").trim()}function be(e){try{return process.kill(e,0),!0}catch{return!1}}function C(e,n){return`${e}:${j.createHash("sha256").update(n).digest("hex").slice(0,32)}`}function $e(e){return e.trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"conversation"}function m(e,n){const t=Number(e);return!Number.isFinite(t)||t<=0?n:Math.floor(t)}function Ne(e){return!!e&&typeof e=="object"&&!Array.isArray(e)&&e.type==="external.message"&&typeof e.channelId=="string"&&typeof e.conversationId=="string"&&typeof e.messageId=="string"&&typeof e.text=="string"}function g(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function I(e){return typeof e=="string"&&e?e:void 0}export{we as buildWeChatReadMarkdown,He as registerWeChatCommand,oe as runWeChatDoctor,se as runWeChatReadLatest,ie as runWeChatSend};
7
+ `}function ce(e,n,t){const a=u(e.channel)?e.channel:{},o=(Array.isArray(a.wechatRpaPendingReplies)?a.wechatRpaPendingReplies:[]).filter(u).find(c=>y(c.idempotencyKey)===n),s=y(o?.status);if(s==="confirmed_echo"||s==="sent_unconfirmed"||s==="manual_review"||s==="queued")return s;const i=(Array.isArray(a.wechatRpaRecentTaskSummaries)?a.wechatRpaRecentTaskSummaries:[]).filter(u).map(c=>y(c.status)).find(Boolean);return i==="sent"?"sent":i==="confirmed_echo"?"confirmed_echo":i==="manual_review"?"manual_review":t?"pending":"ok"}function de(e){const n=e.attachments||[],t=I(e.text);return t&&n.length===0?t:t&&n.length>0?`${t} ${n.map(b).join("\u3001")}`:n.length>0?n.map(b).join("\u3001"):"\uFF08\u6D88\u606F\uFF09"}function le(e){return I(e.sender.name||e.sender.id||"\u5BF9\u65B9")}function b(e){const n=I(e.name||g.basename(e.localPath||"")||e.type||"\u9644\u4EF6"),t=e.localPath||e.url||"",a=e.availability&&e.availability!=="edge-local"?` (${e.availability})`:"";return t?`[${ge(n)}](${pe(t)})${a}`:`${n}${a}`}function ue(e){const n=[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(t=>!!t);if(n.length>1)throw new Error("Pass only one of --file, --image, or --video");if(n.length!==0)return B(n[0].path,n[0].kind)}function me(e){const n=String(e||"markdown").trim().toLowerCase();if(n==="json")return"json";if(n==="markdown"||n==="md"||n==="text")return"markdown";throw new Error(`Unsupported --format: ${e}. Use markdown or json.`)}function he(e){const n=String(e||"auto").trim().toLowerCase();if(n==="auto"||n==="never")return n;throw new Error(`Unsupported --download: ${e}. Use auto or never.`)}function M(e){const n=String(e||"auto").trim().toLowerCase();if(n==="auto"||n==="daemon"||n==="direct")return n;throw new Error(`Unsupported --transport: ${e}. Use auto, daemon, or direct.`)}function fe(e){return{kind:e.kind,name:e.name,mimeType:e.mimeType,size:e.size,localPath:e.localPath}}function E(e){return e.flatMap(n=>n.attachments??[])}function pe(e){const n=e.replace(/\\/g,"/");return/[\s()<>]/.test(n)?`<${n.replace(/[<>]/g,"")}>`:n}function ge(e){return I(e).replace(/([\\\]])/g,"\\$1")}function I(e){return String(e||"").replace(/\s+/g," ").trim()}function we(e){try{return process.kill(e,0),!0}catch{return!1}}function Ie(e){if(!e||typeof e!="object")return!1;const n=e,t=typeof n.name=="string"?n.name:"",a=typeof n.message=="string"?n.message:"";return t==="AbortError"||n.code==="ABORT_ERR"||/aborted|abort/i.test(a)}function T(e,n){return`${e}:${L.createHash("sha256").update(n).digest("hex").slice(0,32)}`}function A(e){return e.trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"conversation"}function h(e,n){const t=Number(e);return!Number.isFinite(t)||t<=0?n:Math.floor(t)}function ye(e){return!!e&&typeof e=="object"&&!Array.isArray(e)&&e.type==="external.message"&&typeof e.channelId=="string"&&typeof e.conversationId=="string"&&typeof e.messageId=="string"&&typeof e.text=="string"}function u(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function y(e){return typeof e=="string"&&e?e:void 0}export{ie as buildWeChatReadMarkdown,Pe as registerWeChatCommand,F as runWeChatDoctor,K as runWeChatReadLatest,J as runWeChatSend};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.96",
3
+ "version": "0.2.98",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {