shennian 0.2.115 → 0.2.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/publish-build-manifest.json +48 -48
  2. package/dist/src/agents/external-channel-instructions.js +5 -5
  3. package/dist/src/agents/model-registry/discovery.js +2 -2
  4. package/dist/src/agents/pi-context.d.ts +1 -1
  5. package/dist/src/agents/pi-context.js +3 -3
  6. package/dist/src/channels/registry.js +1 -1
  7. package/dist/src/channels/runtime.d.ts +1 -0
  8. package/dist/src/channels/runtime.js +3 -3
  9. package/dist/src/channels/secret-registry.js +1 -1
  10. package/dist/src/channels/wechat-channel/anchor.js +1 -1
  11. package/dist/src/channels/wechat-channel/automation-lane.js +2 -2
  12. package/dist/src/channels/wechat-channel/fingerprint.d.ts +3 -0
  13. package/dist/src/channels/wechat-channel/fingerprint.js +1 -1
  14. package/dist/src/channels/wechat-channel/helper-assets.d.ts +1 -1
  15. package/dist/src/channels/wechat-channel/helper-assets.js +1 -1
  16. package/dist/src/channels/wechat-channel/helper-client.js +3 -3
  17. package/dist/src/channels/wechat-channel/ledger.js +2 -1
  18. package/dist/src/channels/wechat-channel/media-resolver.d.ts +1 -1
  19. package/dist/src/channels/wechat-channel/media-resolver.js +1 -1
  20. package/dist/src/channels/wechat-channel/observer.js +6 -6
  21. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +1 -0
  22. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -2
  23. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -1
  24. package/dist/src/channels/wechat-channel/runner.js +1 -1
  25. package/dist/src/channels/wechat-channel/scheduler.js +1 -1
  26. package/dist/src/channels/wechat-rpa.js +3 -3
  27. package/dist/src/commands/daemon.js +3 -3
  28. package/dist/src/commands/wechat/direct.js +2 -2
  29. package/dist/src/commands/wechat/operations.js +3 -3
  30. package/dist/src/commands/wechat/output.js +3 -3
  31. package/dist/src/commands/wechat/utils.d.ts +2 -0
  32. package/dist/src/commands/wechat/utils.js +1 -1
  33. package/dist/src/config/index.d.ts +1 -1
  34. package/dist/src/devtools/wechat-channel-action-smoke.d.ts +1 -0
  35. package/dist/src/devtools/wechat-channel-action-smoke.js +8 -8
  36. package/dist/src/index.js +2 -2
  37. package/package.json +1 -1
@@ -1,2 +1,2 @@
1
- import f from"node:fs";import S from"node:path";import{normalizeWeChatAnchorText as u,weChatTextSimilarity as m}from"./anchor.js";const h=500,A=2,x=600*1e3;function E(e,t){try{const n=JSON.parse(f.readFileSync(e,"utf8"));if(n?.version===1&&n.runtimeId===t&&Array.isArray(n.records))return n}catch{}return{version:1,runtimeId:t,records:[]}}function R(e,t){f.mkdirSync(S.dirname(e),{recursive:!0}),t.records=t.records.slice(-h),f.writeFileSync(e,JSON.stringify(t,null,2))}function w(e){const t=e.ledger.records.find(s=>s.idempotencyKey===e.idempotencyKey);if(t)return t;const n=(e.now??new Date).toISOString(),o=u(e.text),i={replyId:e.replyId,idempotencyKey:e.idempotencyKey,bindingId:e.bindingId,runtimeId:e.runtimeId,sessionId:e.sessionId,conversationName:e.conversationName,replyBaseRevision:e.replyBaseRevision,text:C(e.text)||void 0,textNormalized:o||void 0,attachmentLocalRefs:e.attachmentLocalRefs??[],createdAt:n,queuedAt:n,sendStatus:"queued",commitStage:"queued",copyableContentRef:`reply:${e.replyId}`,expectedEchoAnchor:o||void 0,observeAttemptsAfterSent:0};return e.ledger.records.push(i),i}function I(e){return e.currentLastInboundRevision<=e.record.replyBaseRevision?{ok:!0}:(e.record.sendStatus="stale",e.record.failureCode="reply_revision_stale",e.record.lastErrorSummary="Inbound revision advanced before reply send",e.record.commitStage=e.record.commitStage??"queued",{ok:!1,reason:"stale"})}function _(e){e.sendStatus="sending",e.failureCode=void 0,e.lastErrorSummary=void 0,e.deferReason=void 0}function T(e,t){e.commitStage=t}function W(e,t=new Date){e.sendStatus="sent_unconfirmed",e.sentAt=t.toISOString(),e.commitStage="sent",e.observeAttemptsAfterSent=0,e.failureCode=void 0,e.lastErrorSummary=void 0,e.deferReason=void 0,e.nextAttemptAt=void 0}function k(e,t,n){e.sendStatus="failed",e.failureCode=t,e.lastErrorSummary=n.slice(0,500)}function M(e,t,n,o){e.sendStatus="manual_review",e.failureCode=t,e.lastErrorSummary=n.slice(0,500),e.nextAttemptAt=void 0,e.deferReason=void 0,o&&(e.commitStage=o)}function N(e,t=new Date){e.sendStatus="failed",e.failureCode="user_active_timeout",e.lastErrorSummary="User stayed active until outbound send wait deadline expired",e.nextAttemptAt=void 0,e.deferReason="user_active_timeout",e.lastAttemptAt=t.toISOString()}function D(e,t="user_cancelled",n=new Date){e.sendStatus==="queued"&&(e.sendStatus="failed",e.failureCode=t,e.lastErrorSummary="Queued outbound send was cancelled",e.cancelledAt=n.toISOString(),e.nextAttemptAt=void 0)}function U(e){const t=e.now??new Date;e.record.sendStatus="queued",e.record.deferReason=e.reasonCode,e.record.nextAttemptAt=e.nextAttemptAt.toISOString(),e.record.lastAttemptAt=t.toISOString(),e.record.attemptCount=(e.record.attemptCount??0)+1,e.record.firstUserActivityBlockedAt??=t.toISOString(),e.record.commitStage??="queued",e.record.failureCode=void 0,e.record.lastErrorSummary=void 0}function F(e){if(e.record.sendStatus!=="queued"||!e.record.firstUserActivityBlockedAt)return!1;const t=new Date(e.record.firstUserActivityBlockedAt).getTime();return Number.isFinite(t)?(e.now??new Date).getTime()-t>=e.maxWaitMs:!1}function q(e){return e.sendStatus==="failed"||e.sendStatus==="manual_review"||e.sendStatus==="confirmed_echo"}function z(e){const t=e.now??new Date,n=[],o=[],i=e.ledger.records.filter(r=>r.bindingId===e.bindingId&&r.sendStatus==="sent_unconfirmed"),s=new Set;for(const r of i){const d=e.messages.findIndex((a,c)=>!s.has(c)&&l(r,a));if(d>=0){s.add(d);const a=e.messages[d];r.sendStatus="confirmed_echo",r.confirmedAt=t.toISOString(),r.commitStage="sent",r.confirmedEchoAnchor=u(a.anchorText||a.normalizedText||a.textExcerpt||"")||void 0,n.push(r)}for(const[a,c]of e.messages.entries())s.has(a)||g(r,c)&&s.add(a);d>=0||(r.observeAttemptsAfterSent=(r.observeAttemptsAfterSent??0)+1,b(r,t)&&(r.sendStatus="manual_review",r.failureCode="echo_confirmation_timeout",r.lastErrorSummary="Sent reply echo was not confirmed after observe threshold",r.commitStage="sent",o.push(r)))}return{remainingMessages:e.messages.filter((r,d)=>!s.has(d)),confirmedRecords:n,manualReviewRecords:o}}function B(e){return e.filter(t=>t.senderRole!=="self")}function l(e,t){if(t.senderRole!=="self")return!1;const n=e.expectedEchoAnchor||e.textNormalized||"",o=u(t.anchorText||t.normalizedText||t.textExcerpt||"");return!n||!o?!1:n===o?!0:Math.min(n.length,o.length)<24?!1:m(n,o)>=.9}function g(e,t){const n=e.expectedEchoAnchor||e.textNormalized||"",o=u(t.anchorText||t.normalizedText||t.textExcerpt||"");if(!n||!o)return!1;if(l(e,t)||m(n,o)>=.9)return!0;const i=n.replace(/\s+/g,""),s=o.replace(/\s+/g,"");return s.length>=y(s)&&i.includes(s)}function y(e){return Array.from(e).filter(n=>new RegExp("\\p{Script=Han}","u").test(n)).length>=3?4:8}function b(e,t){if((e.observeAttemptsAfterSent??0)>=A)return!0;if(!e.sentAt)return!1;const n=new Date(e.sentAt).getTime();return Number.isFinite(n)&&t.getTime()-n>=x}function C(e){return typeof e=="string"?e.replace(/\r\n/g,`
2
- `).trim():""}export{D as cancelWeChatOutboundRecord,z as classifyWeChatOutboundEchoes,w as enqueueWeChatOutboundReply,I as guardWeChatOutboundRevision,q as isWeChatOutboundTerminalDeliveryStatus,E as loadWeChatChannelOutboundLedger,T as markWeChatOutboundCommitStage,k as markWeChatOutboundFailed,M as markWeChatOutboundManualReview,_ as markWeChatOutboundSending,W as markWeChatOutboundSentUnconfirmed,N as markWeChatOutboundUserActiveTimeout,U as markWeChatOutboundWaitingUserIdle,R as saveWeChatChannelOutboundLedger,F as shouldFailWeChatOutboundForUserActiveTimeout,B as suppressSelfOnlyWeChatMessages};
1
+ import f from"node:fs";import S from"node:path";import{normalizeWeChatAnchorText as u,weChatTextSimilarity as m}from"./anchor.js";const h=500,A=2,x=600*1e3;function y(e,t){try{const n=JSON.parse(f.readFileSync(e,"utf8"));if(n?.version===1&&n.runtimeId===t&&Array.isArray(n.records))return n}catch{}return{version:1,runtimeId:t,records:[]}}function g(e,t){f.mkdirSync(S.dirname(e),{recursive:!0}),t.records=t.records.slice(-h),f.writeFileSync(e,JSON.stringify(t,null,2))}function R(e,t){const n=y(e,t.runtimeId),r=new Set(t.records.map(s=>s.idempotencyKey)),a=t.records.concat(n.records.filter(s=>!r.has(s.idempotencyKey)));g(e,{...t,records:a})}function I(e){const t=e.ledger.records.find(s=>s.idempotencyKey===e.idempotencyKey);if(t)return t;const n=(e.now??new Date).toISOString(),r=u(e.text),a={replyId:e.replyId,idempotencyKey:e.idempotencyKey,bindingId:e.bindingId,runtimeId:e.runtimeId,sessionId:e.sessionId,conversationName:e.conversationName,replyBaseRevision:e.replyBaseRevision,text:v(e.text)||void 0,textNormalized:r||void 0,attachmentLocalRefs:e.attachmentLocalRefs??[],createdAt:n,queuedAt:n,sendStatus:"queued",commitStage:"queued",copyableContentRef:`reply:${e.replyId}`,expectedEchoAnchor:r||void 0,observeAttemptsAfterSent:0};return e.ledger.records.push(a),a}function _(e){return e.currentLastInboundRevision<=e.record.replyBaseRevision?{ok:!0}:(e.record.sendStatus="stale",e.record.failureCode="reply_revision_stale",e.record.lastErrorSummary="Inbound revision advanced before reply send",e.record.commitStage=e.record.commitStage??"queued",{ok:!1,reason:"stale"})}function T(e){e.sendStatus="sending",e.failureCode=void 0,e.lastErrorSummary=void 0,e.deferReason=void 0}function W(e,t){e.commitStage=t}function k(e,t=new Date){e.sendStatus="sent_unconfirmed",e.sentAt=t.toISOString(),e.commitStage="sent",e.observeAttemptsAfterSent=0,e.failureCode=void 0,e.lastErrorSummary=void 0,e.deferReason=void 0,e.nextAttemptAt=void 0}function M(e,t,n){e.sendStatus="failed",e.failureCode=t,e.lastErrorSummary=n.slice(0,500)}function D(e,t,n,r){e.sendStatus="manual_review",e.failureCode=t,e.lastErrorSummary=n.slice(0,500),e.nextAttemptAt=void 0,e.deferReason=void 0,r&&(e.commitStage=r)}function N(e,t=new Date){e.sendStatus="failed",e.failureCode="user_active_timeout",e.lastErrorSummary="User stayed active until outbound send wait deadline expired",e.nextAttemptAt=void 0,e.deferReason="user_active_timeout",e.lastAttemptAt=t.toISOString()}function U(e,t="user_cancelled",n=new Date){e.sendStatus==="queued"&&(e.sendStatus="failed",e.failureCode=t,e.lastErrorSummary="Queued outbound send was cancelled",e.cancelledAt=n.toISOString(),e.nextAttemptAt=void 0)}function F(e){const t=e.now??new Date;e.record.sendStatus="queued",e.record.deferReason=e.reasonCode,e.record.nextAttemptAt=e.nextAttemptAt.toISOString(),e.record.lastAttemptAt=t.toISOString(),e.record.attemptCount=(e.record.attemptCount??0)+1,e.record.firstUserActivityBlockedAt??=t.toISOString(),e.record.commitStage??="queued",e.record.failureCode=void 0,e.record.lastErrorSummary=void 0}function q(e){if(e.record.sendStatus!=="queued"||!e.record.firstUserActivityBlockedAt)return!1;const t=new Date(e.record.firstUserActivityBlockedAt).getTime();return Number.isFinite(t)?(e.now??new Date).getTime()-t>=e.maxWaitMs:!1}function z(e){return e.sendStatus==="failed"||e.sendStatus==="manual_review"||e.sendStatus==="confirmed_echo"}function L(e){const t=e.now??new Date,n=[],r=[],a=e.ledger.records.filter(o=>o.bindingId===e.bindingId&&o.sendStatus==="sent_unconfirmed"),s=new Set;for(const o of a){const d=e.messages.findIndex((i,c)=>!s.has(c)&&l(o,i));if(d>=0){s.add(d);const i=e.messages[d];o.sendStatus="confirmed_echo",o.confirmedAt=t.toISOString(),o.commitStage="sent",o.confirmedEchoAnchor=u(i.anchorText||i.normalizedText||i.textExcerpt||"")||void 0,n.push(o)}for(const[i,c]of e.messages.entries())s.has(i)||b(o,c)&&s.add(i);d>=0||(o.observeAttemptsAfterSent=(o.observeAttemptsAfterSent??0)+1,C(o,t)&&(o.sendStatus="manual_review",o.failureCode="echo_confirmation_timeout",o.lastErrorSummary="Sent reply echo was not confirmed after observe threshold",o.commitStage="sent",r.push(o)))}return{remainingMessages:e.messages.filter((o,d)=>!s.has(d)),confirmedRecords:n,manualReviewRecords:r}}function B(e){return e.filter(t=>t.senderRole!=="self")}function l(e,t){if(t.senderRole!=="self")return!1;const n=e.expectedEchoAnchor||e.textNormalized||"",r=u(t.anchorText||t.normalizedText||t.textExcerpt||"");return!n||!r?!1:n===r?!0:Math.min(n.length,r.length)<24?!1:m(n,r)>=.9}function b(e,t){const n=e.expectedEchoAnchor||e.textNormalized||"",r=u(t.anchorText||t.normalizedText||t.textExcerpt||"");if(!n||!r)return!1;if(l(e,t)||m(n,r)>=.9)return!0;const a=n.replace(/\s+/g,""),s=r.replace(/\s+/g,"");return s.length>=p(s)&&a.includes(s)}function p(e){return Array.from(e).filter(n=>new RegExp("\\p{Script=Han}","u").test(n)).length>=3?4:8}function C(e,t){if((e.observeAttemptsAfterSent??0)>=A)return!0;if(!e.sentAt)return!1;const n=new Date(e.sentAt).getTime();return Number.isFinite(n)&&t.getTime()-n>=x}function v(e){return typeof e=="string"?e.replace(/\r\n/g,`
2
+ `).trim():""}export{U as cancelWeChatOutboundRecord,L as classifyWeChatOutboundEchoes,I as enqueueWeChatOutboundReply,_ as guardWeChatOutboundRevision,z as isWeChatOutboundTerminalDeliveryStatus,y as loadWeChatChannelOutboundLedger,W as markWeChatOutboundCommitStage,M as markWeChatOutboundFailed,D as markWeChatOutboundManualReview,T as markWeChatOutboundSending,k as markWeChatOutboundSentUnconfirmed,N as markWeChatOutboundUserActiveTimeout,F as markWeChatOutboundWaitingUserIdle,g as saveWeChatChannelOutboundLedger,R as saveWeChatChannelOutboundLedgerMerging,q as shouldFailWeChatOutboundForUserActiveTimeout,B as suppressSelfOnlyWeChatMessages};
@@ -1 +1 @@
1
- import g from"node:fs";import A from"node:path";import{waitForWeChatChannelPacing as d}from"./pacing.js";import{guardWeChatOutboundRevision as v,markWeChatOutboundFailed as C,markWeChatOutboundManualReview as x,markWeChatOutboundCommitStage as o,markWeChatOutboundSending as P,markWeChatOutboundSentUnconfirmed as S,markWeChatOutboundUserActiveTimeout as h,markWeChatOutboundWaitingUserIdle as _,shouldFailWeChatOutboundForUserActiveTimeout as l}from"./outbound-ledger.js";import{decideWeChatChannelActivityGate as I,nextWeChatChannelActivityRetryAt as b,normalizeWeChatChannelActivitySnapshot as k}from"./human-coordination.js";const W=600*1e3,f=520;async function Y(e){const t={sentRecords:[],staleRecords:[],failedRecords:[],waitingRecords:[],manualReviewRecords:[]},s=e.now??new Date,a=e.maxUserActivityWaitMs??W,n=e.ledger.records.filter(i=>i.bindingId===e.bindingId&&i.sendStatus==="queued");for(const i of n){if(l({record:i,now:s,maxWaitMs:a})){h(i,s),t.failedRecords.push(i);continue}if(!v({record:i,currentLastInboundRevision:e.currentLastInboundRevision,now:s}).ok){t.staleRecords.push(i);continue}try{const r=await e.sender.canStart(i,s);if(r.ok===!1){_({record:i,reasonCode:r.reasonCode,nextAttemptAt:r.nextAttemptAt,now:s}),l({record:i,now:s,maxWaitMs:a})?(h(i,s),t.failedRecords.push(i)):t.waitingRecords.push(i);continue}P(i),await e.sender.send(i),S(i,s),t.sentRecords.push(i)}catch(r){if(r instanceof m){if(H(r)){x(i,"user_takeover_before_return","User activity was detected after paste and before Return; manual WeChat inspection is required",r.stage),t.manualReviewRecords.push(i);continue}_({record:i,reasonCode:r.reasonCode,nextAttemptAt:r.nextAttemptAt,now:s}),l({record:i,now:s,maxWaitMs:a})?(h(i,s),t.failedRecords.push(i)):t.waitingRecords.push(i);continue}C(i,z(r),r instanceof Error?r.message:String(r)),t.failedRecords.push(i)}}return t}class J{options;activeAutomationLeaseId=null;focus;windowsTextClipboardPrimed=!1;constructor(t){this.options=t}async canStart(t,s=new Date){const a=await this.activitySnapshot();await $(this.options.helper,a,this.options.platform,this.options.traceId);const n=I({snapshot:a,stage:"send_start",policy:this.options.activityGatePolicy});return n.ok===!1?{ok:!1,reasonCode:n.reasonCode,nextAttemptAt:b(s,n.waitMs)}:{ok:!0}}async send(t){await this.withAutomationLease(t,()=>this.sendWithLease(t))}async sendWithLease(t){await this.ensureStageIdle(t,"open_conversation"),o(t,"open_conversation");const s=await this.options.openConversation(t.conversationName);if(!s.opened)throw new Error(s.reason||"conversation_not_opened");if(D(this.options.platform)&&!s.inputPoint)throw new Error("wechat_message_input_point_required: Windows send requires a vision-detected message input point");o(t,"focus_input"),this.focus={windowId:s.windowId??void 0,inputPoint:s.inputPoint??void 0},await this.focusMessageInputWithRetry(t),await this.ensureStageIdle(t,"clipboard_snapshot"),o(t,"clipboard_snapshot");const a=await this.options.helper.request("clipboard.snapshot",{},this.options.traceId);u(a,"clipboard.snapshot");let n=!1;try{await this.sendTextIfPresent(t,()=>{n=!0}),await this.sendAttachmentsIfPresent(t,()=>{n=!0})}finally{const i=a.result&&typeof a.result=="object"?a.result:{},c=await this.options.helper.request("clipboard.restore",i,this.options.traceId);n||u(c,"clipboard.restore")}}async sendTextIfPresent(t,s){if(!t.text)return;if(F(this.options.platform)){await this.ensureStageIdle(t,"clipboard_set"),o(t,"clipboard_set"),await this.ensureStageIdle(t,"paste"),o(t,"paste");const n=await this.options.helper.request("wechat.pasteAndSubmit",{text:t.text,waitMs:f,pasteWaitMs:this.options.postPasteSettleMs??900,...this.focus?.windowId?{windowId:this.focus.windowId}:{},...this.focus?.inputPoint?{inputPoint:this.focus.inputPoint}:{}},this.options.traceId);u(n,"wechat.pasteAndSubmit"),s(),o(t,"after_return");return}await this.ensureStageIdle(t,"clipboard_set"),o(t,"clipboard_set"),O(this.options.platform)||(await p(this.options.helper,"a",w(this.options.platform),this.options.traceId),await p(this.options.helper,"backspace",[],this.options.traceId));const a=await this.options.helper.request("clipboard.setText",{text:t.text},this.options.traceId);u(a,"clipboard.setText"),U(this.options.platform)&&await this.focusMessageInputWithRetry(t,"clipboard_set"),await this.ensureStageIdle(t,"paste"),o(t,"paste"),await this.primeWindowsTextPasteIfNeeded(t),await p(this.options.helper,"v",w(this.options.platform),this.options.traceId),await this.waitAfterPaste(t),await this.options.onAfterPaste?.({record:t,kind:"text"}),await this.ensureStageIdle(t,"before_return"),o(t,"before_return"),await p(this.options.helper,"return",[],this.options.traceId),s(),o(t,"after_return")}async primeWindowsTextPasteIfNeeded(t){if(!E(this.options.platform)||this.windowsTextClipboardPrimed)return;const s=await this.options.helper.request("keyboard.primeTextPaste",{},this.options.traceId);u(s,"keyboard.primeTextPaste"),this.windowsTextClipboardPrimed=!0,await this.ensureStageIdle(t,"paste")}async sendAttachmentsIfPresent(t,s){for(const a of t.attachmentLocalRefs??[]){M(a),await this.ensureStageIdle(t,"clipboard_set"),o(t,"clipboard_set");const n=q(a),i=n==="image"&&y(this.options.platform)?await this.options.helper.request("clipboard.setImage",{filePath:a},this.options.traceId):await this.options.helper.request("clipboard.setFiles",{filePaths:[a]},this.options.traceId);u(i,n==="image"&&y(this.options.platform)?"clipboard.setImage":"clipboard.setFiles"),await this.ensureStageIdle(t,"paste"),o(t,"paste"),await p(this.options.helper,"v",w(this.options.platform),this.options.traceId),await this.waitAfterPaste(t),await this.options.onAfterPaste?.({record:t,kind:"attachment"}),await this.ensureStageIdle(t,"before_return"),o(t,"before_return"),await p(this.options.helper,"return",[],this.options.traceId),s(),o(t,"after_return")}}async withAutomationLease(t,s){const a=await this.options.helper.request("automation.lease.acquire",{owner:"wechat-channel",purpose:`send:${t.bindingId}`,ttlMs:6e4},this.options.traceId);u(a,"automation.lease.acquire");const n=typeof a.result?.leaseId=="string"?a.result.leaseId:"";try{this.activeAutomationLeaseId=n||null,await s()}finally{this.activeAutomationLeaseId=null,n&&await this.releaseAutomationLease(n)}}async releaseAutomationLease(t){try{await this.options.helper.request("automation.lease.release",{leaseId:t},this.options.traceId)}catch{}}async waitAfterPaste(t){const s=(this.options.platform??process.platform)==="win32"?1e3:void 0;await d("send-post-paste",`${this.options.traceId||""}:${t.replyId}:post-paste`,this.options.postPasteSettleMs??s)}async focusMessageInputWithRetry(t,s="focus_input"){await T(this.options.helper,this.options.traceId,L(this.focus)),await this.ensureStageIdle(t,s)}async ensureStageIdle(t,s){if(this.options.takeoverCheck===!1)return;if(this.activeAutomationLeaseId){const i=await this.automationLeaseDecision();if(i.ok===!1)throw new m({reasonCode:i.reasonCode,nextAttemptAt:i.nextAttemptAt,stage:s,replyId:t.replyId});return}const a=await this.activitySnapshot(),n=I({snapshot:a,stage:R(s),policy:this.options.activityGatePolicy});if(!n.ok)throw new m({reasonCode:n.reasonCode,nextAttemptAt:b(new Date,n.waitMs),stage:s,replyId:t.replyId})}async activitySnapshot(){const t=await this.options.helper.request("activity.snapshot",{},this.options.traceId);return u(t,"activity.snapshot"),k(t.result)}async automationLeaseDecision(){if(!this.activeAutomationLeaseId)return{ok:!0};const t=await this.options.helper.request("automation.lease.status",{},this.options.traceId);u(t,"automation.lease.status");const s=t.result??{};return s.active===!1||s.leaseId!==this.activeAutomationLeaseId||!s.interrupted?{ok:!0}:{ok:!1,reasonCode:G(s.interruptReason),nextAttemptAt:new Date(Date.now()+1e3)}}}class m extends Error{reasonCode;nextAttemptAt;stage;replyId;constructor(t){super(`user_takeover_aborted:${t.reasonCode}:${t.stage}`),this.name="WeChatChannelUserTakeoverAbort",this.reasonCode=t.reasonCode,this.nextAttemptAt=t.nextAttemptAt,this.stage=t.stage,this.replyId=t.replyId}}function R(e){return e==="open_conversation"?"open_conversation":e==="queued"?"send_start":"dangerous_action"}async function T(e,t,s){const a={waitMs:f,...s?.windowId?{windowId:s.windowId}:{},...s?.inputPoint?{inputPoint:s.inputPoint}:{}};if((await e.request("wechat.focusMessageInput",a,t)).ok){await d("send-focus-stabilize",`${t||""}:focus-stabilize`);const c=await e.request("wechat.focusMessageInput",a,t);u(c,"wechat.focusMessageInput");return}await d("send-focus-retry",`${t||""}:focus-retry`);const i=await e.request("wechat.focusMessageInput",{...a,waitMs:Math.max(f,700)},t);u(i,"wechat.focusMessageInput")}async function p(e,t,s,a){const n=await e.request("keyboard.shortcut",{key:t,modifiers:s},a);u(n,"keyboard.shortcut")}function M(e){if(!g.statSync(e).isFile())throw new Error(`wechat_channel_attachment_not_file:${e}`)}function q(e){const t=A.extname(e).toLowerCase();return[".png",".jpg",".jpeg",".gif",".webp",".heic",".tiff",".bmp"].includes(t)?"image":[".mp4",".mov",".m4v",".avi",".mkv",".webm"].includes(t)?"video":"file"}function y(e){return(e??process.platform)==="win32"}function w(e){return(e??process.platform)==="win32"?["control"]:["command"]}function L(e){if(e)return{...e.windowId?{windowId:e.windowId}:{},...e.inputPoint?{inputPoint:e.inputPoint}:{}}}function O(e){return(e??process.platform)==="win32"}function U(e){return(e??process.platform)==="win32"}function E(e){return(e??process.platform)==="win32"}function F(e){return(e??process.platform)==="win32"}function D(e){return(e??process.platform)==="win32"}async function $(e,t,s,a){if((s??process.platform)!=="darwin")return;const n=t?.permissions;if(!n)return;const i=n.accessibilityTrusted===!1?"accessibility":n.iohidListenGranted===!1?"input-monitoring":null;if(i)throw await N(e,i==="accessibility"?"permissions.requestAccessibility":"permissions.requestInputMonitoring",a),new Error(`permission_missing:mac_input:${i}`)}async function N(e,t,s){try{await e.request(t,{},s)}catch{}}function z(e){const t=e instanceof Error?e.message:String(e);return/conversation_not|conversation.*visible|conversation.*open/i.test(t)?"conversation_not_opened":/wechat_message_input_point_required|wechat_message_input_not_found/i.test(t)?"wechat_message_input_not_found":/permission|accessibility|screen|automation/i.test(t)?"permission_missing":/clipboard/i.test(t)?"clipboard_failed":/attachment|not_file|ENOENT|no such file/i.test(t)?"attachment_unavailable":"send_failed"}function G(e){return e==="recent_mouse_activity"||e==="recent_mouse_click"||e==="recent_scroll_activity"||e==="recent_keyboard_activity"||e==="frontmost_app_changed"?e:"user_activity_unknown"}function H(e){return e.stage==="before_return"}function u(e,t){if(!e.ok)throw new Error(`${e.errorCode||"helper_command_failed"}: ${e.errorSummary||t}`)}export{J as WeChatChannelOutboundSender,m as WeChatChannelUserTakeoverAbort,Y as sendQueuedWeChatOutboundRecords};
1
+ import g from"node:fs";import A from"node:path";import{waitForWeChatChannelPacing as d}from"./pacing.js";import{guardWeChatOutboundRevision as C,markWeChatOutboundFailed as v,markWeChatOutboundManualReview as S,markWeChatOutboundCommitStage as o,markWeChatOutboundSending as x,markWeChatOutboundSentUnconfirmed as P,markWeChatOutboundUserActiveTimeout as h,markWeChatOutboundWaitingUserIdle as _,shouldFailWeChatOutboundForUserActiveTimeout as l}from"./outbound-ledger.js";import{decideWeChatChannelActivityGate as I,nextWeChatChannelActivityRetryAt as b,normalizeWeChatChannelActivitySnapshot as k}from"./human-coordination.js";const W=600*1e3,f=520;function T(){return process.env.SHENNIAN_WECHAT_FOCUS_SINGLE_CALL!=="0"}async function J(e){const t={sentRecords:[],staleRecords:[],failedRecords:[],waitingRecords:[],manualReviewRecords:[]},s=e.now??new Date,n=e.maxUserActivityWaitMs??W,a=e.ledger.records.filter(i=>i.bindingId===e.bindingId&&i.sendStatus==="queued");for(const i of a){if(l({record:i,now:s,maxWaitMs:n})){h(i,s),t.failedRecords.push(i);continue}if(!C({record:i,currentLastInboundRevision:e.currentLastInboundRevision,now:s}).ok){t.staleRecords.push(i);continue}try{const r=await e.sender.canStart(i,s);if(r.ok===!1){_({record:i,reasonCode:r.reasonCode,nextAttemptAt:r.nextAttemptAt,now:s}),l({record:i,now:s,maxWaitMs:n})?(h(i,s),t.failedRecords.push(i)):t.waitingRecords.push(i);continue}x(i),await e.sender.send(i),P(i,s),t.sentRecords.push(i)}catch(r){if(r instanceof m){if(j(r)){S(i,"user_takeover_before_return","User activity was detected after paste and before Return; manual WeChat inspection is required",r.stage),t.manualReviewRecords.push(i);continue}_({record:i,reasonCode:r.reasonCode,nextAttemptAt:r.nextAttemptAt,now:s}),l({record:i,now:s,maxWaitMs:n})?(h(i,s),t.failedRecords.push(i)):t.waitingRecords.push(i);continue}v(i,H(r),r instanceof Error?r.message:String(r)),t.failedRecords.push(i)}}return t}class X{options;activeAutomationLeaseId=null;focus;windowsTextClipboardPrimed=!1;constructor(t){this.options=t}async canStart(t,s=new Date){const n=await this.activitySnapshot();await $(this.options.helper,n,this.options.platform,this.options.traceId);const a=I({snapshot:n,stage:"send_start",policy:this.options.activityGatePolicy});return a.ok===!1?{ok:!1,reasonCode:a.reasonCode,nextAttemptAt:b(s,a.waitMs)}:{ok:!0}}async send(t){await this.withAutomationLease(t,()=>this.sendWithLease(t))}async sendWithLease(t){await this.ensureStageIdle(t,"open_conversation"),o(t,"open_conversation");const s=await this.options.openConversation(t.conversationName);if(!s.opened)throw new Error(s.reason||"conversation_not_opened");if(N(this.options.platform)&&!s.inputPoint)throw new Error("wechat_message_input_point_required: Windows send requires a vision-detected message input point");o(t,"focus_input"),this.focus={windowId:s.windowId??void 0,inputPoint:s.inputPoint??void 0},await this.focusMessageInputWithRetry(t),await this.ensureStageIdle(t,"clipboard_snapshot"),o(t,"clipboard_snapshot");const n=await this.options.helper.request("clipboard.snapshot",{},this.options.traceId);u(n,"clipboard.snapshot");let a=!1;try{await this.sendTextIfPresent(t,()=>{a=!0}),await this.sendAttachmentsIfPresent(t,()=>{a=!0})}finally{const i=n.result&&typeof n.result=="object"?n.result:{},c=await this.options.helper.request("clipboard.restore",i,this.options.traceId);a||u(c,"clipboard.restore")}}async sendTextIfPresent(t,s){if(!t.text)return;if(D(this.options.platform)){await this.ensureStageIdle(t,"clipboard_set"),o(t,"clipboard_set"),await this.ensureStageIdle(t,"paste"),o(t,"paste");const a=await this.options.helper.request("wechat.pasteAndSubmit",{text:t.text,waitMs:f,pasteWaitMs:this.options.postPasteSettleMs??900,...this.focus?.windowId?{windowId:this.focus.windowId}:{},...this.focus?.inputPoint?{inputPoint:this.focus.inputPoint}:{}},this.options.traceId);u(a,"wechat.pasteAndSubmit"),s(),o(t,"after_return");return}await this.ensureStageIdle(t,"clipboard_set"),o(t,"clipboard_set"),O(this.options.platform)||(await p(this.options.helper,"a",w(this.options.platform),this.options.traceId),await p(this.options.helper,"backspace",[],this.options.traceId));const n=await this.options.helper.request("clipboard.setText",{text:t.text},this.options.traceId);u(n,"clipboard.setText"),U(this.options.platform)&&await this.focusMessageInputWithRetry(t,"clipboard_set"),await this.ensureStageIdle(t,"paste"),o(t,"paste"),await this.primeWindowsTextPasteIfNeeded(t),await p(this.options.helper,"v",w(this.options.platform),this.options.traceId),await this.waitAfterPaste(t),await this.options.onAfterPaste?.({record:t,kind:"text"}),await this.ensureStageIdle(t,"before_return"),o(t,"before_return"),await p(this.options.helper,"return",[],this.options.traceId),s(),o(t,"after_return")}async primeWindowsTextPasteIfNeeded(t){if(!F(this.options.platform)||this.windowsTextClipboardPrimed)return;const s=await this.options.helper.request("keyboard.primeTextPaste",{},this.options.traceId);u(s,"keyboard.primeTextPaste"),this.windowsTextClipboardPrimed=!0,await this.ensureStageIdle(t,"paste")}async sendAttachmentsIfPresent(t,s){for(const n of t.attachmentLocalRefs??[]){q(n),await this.ensureStageIdle(t,"clipboard_set"),o(t,"clipboard_set");const a=L(n),i=a==="image"&&y(this.options.platform)?await this.options.helper.request("clipboard.setImage",{filePath:n},this.options.traceId):await this.options.helper.request("clipboard.setFiles",{filePaths:[n]},this.options.traceId);u(i,a==="image"&&y(this.options.platform)?"clipboard.setImage":"clipboard.setFiles"),await this.ensureStageIdle(t,"paste"),o(t,"paste"),await p(this.options.helper,"v",w(this.options.platform),this.options.traceId),await this.waitAfterPaste(t),await this.options.onAfterPaste?.({record:t,kind:"attachment"}),await this.ensureStageIdle(t,"before_return"),o(t,"before_return"),await p(this.options.helper,"return",[],this.options.traceId),s(),o(t,"after_return")}}async withAutomationLease(t,s){const n=await this.options.helper.request("automation.lease.acquire",{owner:"wechat-channel",purpose:`send:${t.bindingId}`,ttlMs:6e4},this.options.traceId);u(n,"automation.lease.acquire");const a=typeof n.result?.leaseId=="string"?n.result.leaseId:"";try{this.activeAutomationLeaseId=a||null,await s()}finally{this.activeAutomationLeaseId=null,a&&await this.releaseAutomationLease(a)}}async releaseAutomationLease(t){try{await this.options.helper.request("automation.lease.release",{leaseId:t},this.options.traceId)}catch{}}async waitAfterPaste(t){const s=(this.options.platform??process.platform)==="win32"?1e3:void 0;await d("send-post-paste",`${this.options.traceId||""}:${t.replyId}:post-paste`,this.options.postPasteSettleMs??s)}async focusMessageInputWithRetry(t,s="focus_input"){await M(this.options.helper,this.options.traceId,E(this.focus)),await this.ensureStageIdle(t,s)}async ensureStageIdle(t,s){if(this.options.takeoverCheck===!1)return;if(this.activeAutomationLeaseId){const i=await this.automationLeaseDecision();if(i.ok===!1)throw new m({reasonCode:i.reasonCode,nextAttemptAt:i.nextAttemptAt,stage:s,replyId:t.replyId});return}const n=await this.activitySnapshot(),a=I({snapshot:n,stage:R(s),policy:this.options.activityGatePolicy});if(!a.ok)throw new m({reasonCode:a.reasonCode,nextAttemptAt:b(new Date,a.waitMs),stage:s,replyId:t.replyId})}async activitySnapshot(){const t=await this.options.helper.request("activity.snapshot",{},this.options.traceId);return u(t,"activity.snapshot"),k(t.result)}async automationLeaseDecision(){if(!this.activeAutomationLeaseId)return{ok:!0};const t=await this.options.helper.request("automation.lease.status",{},this.options.traceId);u(t,"automation.lease.status");const s=t.result??{};return s.active===!1||s.leaseId!==this.activeAutomationLeaseId||!s.interrupted?{ok:!0}:{ok:!1,reasonCode:z(s.interruptReason),nextAttemptAt:new Date(Date.now()+1e3)}}}class m extends Error{reasonCode;nextAttemptAt;stage;replyId;constructor(t){super(`user_takeover_aborted:${t.reasonCode}:${t.stage}`),this.name="WeChatChannelUserTakeoverAbort",this.reasonCode=t.reasonCode,this.nextAttemptAt=t.nextAttemptAt,this.stage=t.stage,this.replyId=t.replyId}}function R(e){return e==="open_conversation"?"open_conversation":e==="queued"?"send_start":"dangerous_action"}async function M(e,t,s){const n={waitMs:f,...s?.windowId?{windowId:s.windowId}:{},...s?.inputPoint?{inputPoint:s.inputPoint}:{}};if((await e.request("wechat.focusMessageInput",n,t)).ok){if(T())return;await d("send-focus-stabilize",`${t||""}:focus-stabilize`);const c=await e.request("wechat.focusMessageInput",n,t);u(c,"wechat.focusMessageInput");return}await d("send-focus-retry",`${t||""}:focus-retry`);const i=await e.request("wechat.focusMessageInput",{...n,waitMs:Math.max(f,700)},t);u(i,"wechat.focusMessageInput")}async function p(e,t,s,n){const a=await e.request("keyboard.shortcut",{key:t,modifiers:s},n);u(a,"keyboard.shortcut")}function q(e){if(!g.statSync(e).isFile())throw new Error(`wechat_channel_attachment_not_file:${e}`)}function L(e){const t=A.extname(e).toLowerCase();return[".png",".jpg",".jpeg",".gif",".webp",".heic",".tiff",".bmp"].includes(t)?"image":[".mp4",".mov",".m4v",".avi",".mkv",".webm"].includes(t)?"video":"file"}function y(e){return(e??process.platform)==="win32"}function w(e){return(e??process.platform)==="win32"?["control"]:["command"]}function E(e){if(e)return{...e.windowId?{windowId:e.windowId}:{},...e.inputPoint?{inputPoint:e.inputPoint}:{}}}function O(e){return(e??process.platform)==="win32"}function U(e){return(e??process.platform)==="win32"}function F(e){return(e??process.platform)==="win32"}function D(e){return(e??process.platform)==="win32"}function N(e){return(e??process.platform)==="win32"}async function $(e,t,s,n){if((s??process.platform)!=="darwin")return;const a=t?.permissions;if(!a)return;const i=a.accessibilityTrusted===!1?"accessibility":a.iohidListenGranted===!1?"input-monitoring":null;if(i)throw await G(e,i==="accessibility"?"permissions.requestAccessibility":"permissions.requestInputMonitoring",n),new Error(`permission_missing:mac_input:${i}`)}async function G(e,t,s){try{await e.request(t,{},s)}catch{}}function H(e){const t=e instanceof Error?e.message:String(e);return/conversation_not|conversation.*visible|conversation.*open/i.test(t)?"conversation_not_opened":/wechat_message_input_point_required|wechat_message_input_not_found/i.test(t)?"wechat_message_input_not_found":/permission|accessibility|screen|automation/i.test(t)?"permission_missing":/clipboard/i.test(t)?"clipboard_failed":/attachment|not_file|ENOENT|no such file/i.test(t)?"attachment_unavailable":"send_failed"}function z(e){return e==="recent_mouse_activity"||e==="recent_mouse_click"||e==="recent_scroll_activity"||e==="recent_keyboard_activity"||e==="frontmost_app_changed"?e:"user_activity_unknown"}function j(e){return e.stage==="before_return"}function u(e,t){if(!e.ok)throw new Error(`${e.errorCode||"helper_command_failed"}: ${e.errorSummary||t}`)}export{X as WeChatChannelOutboundSender,m as WeChatChannelUserTakeoverAbort,J as sendQueuedWeChatOutboundRecords};
@@ -1 +1 @@
1
- import u from"node:path";import h from"node:fs";import{createWeChatChannelApiClient as m}from"./client.js";import{WeChatChannelHelperClient as f}from"./helper-client.js";import{resolveWeChatChannelHelperAsset as k,WECHAT_CHANNEL_HELPER_VERSION as W}from"./helper-assets.js";import{requiredWindowsWeChatChannelHelperCapabilitiesForProfile as c}from"./helper-protocol.js";import{captureWeChatWindow as S,detectWeChatMessageInputPointByServerVision as I,focusKnownWeChatWindow as g,observeWeChatChannelBindingViaHelper as P,openConversationBySearch as A,recognizeWeChatScreenshot as O,selectWeChatWindowForBinding as w}from"./observer.js";import{WeChatChannelScheduler as D}from"./scheduler.js";import{loadWeChatChannelLedger as F}from"./ledger.js";import{buildWeChatConversationFingerprint as M,matchWeChatConversationFingerprints as T}from"./fingerprint.js";import{WeChatChannelOutboundSender as x}from"./outbound-sender.js";import{SerialWeChatAutomationLane as L}from"./automation-lane.js";import{decideWeChatChannelActivityGate as _,nextWeChatChannelActivityRetryAt as q,normalizeWeChatChannelActivitySnapshot as H}from"./human-coordination.js";class R{options;scheduler;helper;helperStarted=!1;lane;constructor(e){this.options=e;const n=e.api??m();this.helper=e.helper??B(e),this.lane=e.lane??new L,this.scheduler=new D({runtime:e.runtime,workDir:e.workDir,api:n,ledgerPath:e.ledgerPath,outboundLedgerPath:e.outboundLedgerPath,onInboundMessages:e.onInboundMessages,activityGate:{canObserve:async(i,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 t=_({snapshot:H(o.result),stage:"observe"});return t.ok?{ok:!0}:{ok:!1,reasonCode:t.reasonCode,nextAttemptAt:q(a,t.waitMs)}}},shouldSkipObserve:(i,a)=>this.shouldSkipObserveForUnchangedFingerprint(i,a.foregroundMode),outboundSender:new x({helper:this.helper,platform:e.runtime.policy.platform,activityGatePolicy:e.runtime.foregroundPolicy==="work"?{mouseMovedThresholdMs:0,mouseClickThresholdMs:0,scrollWheelThresholdMs:0,keyDownThresholdMs:0}:void 0,takeoverCheck:e.runtime.foregroundPolicy!=="work",openConversation:async i=>{const a={bindingId:`outbound:${i}`,sessionId:"outbound",conversationDisplayName:i,enabled:!0,allowReply:!0,downloadMedia:!0},o=await w({runtime:e.runtime,binding:a,helper:this.helper,api:n,workDir:e.workDir}),t=await g(this.helper,o),s=await A({helper:this.helper,window:t,settleToBottom:!1,binding:a,runtime:e.runtime,api:n,workDir:e.workDir});if(!s.opened)return{...s,windowId:t.windowId,inputPoint:null};const l=await I({runtime:e.runtime,binding:a,helper:this.helper,api:n,workDir:e.workDir,window:t});return{...s,windowId:t.windowId,inputPoint:l}}}),observeBinding:(i,a)=>P({runtime:e.runtime,binding:i,helper:this.helper,api:n,workDir:e.workDir,localLedgerTailAnchors:E(e.ledgerPath??U(e.workDir,e.runtime.runtimeId),e.runtime.runtimeId,i.bindingId),localOutboundEchoAnchors:a?.localOutboundEchoAnchors,foregroundMode:a?.foregroundMode,onObservationEvidence:({ocr:o})=>{G({statePath:b(e.workDir,e.runtime.runtimeId),runtimeId:e.runtime.runtimeId,binding:i,ocr:o})}})})}async start(){return this.lane.run("wechat.runner.start",async()=>{await this.ensureHelperStarted(),await this.scheduler.start()})}async tick(e){return this.lane.run("wechat.runner.tick",async()=>(await this.ensureHelperStarted(),this.scheduler.tick(e)))}async stop(){return this.lane.run("wechat.runner.stop",async()=>{await 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 shouldSkipObserveForUnchangedFingerprint(e,n="required"){if(this.options.runtime.policy.platform!=="darwin"&&this.options.runtime.policy.platform!=="win32")return{skip:!1};const i=b(this.options.workDir,this.options.runtime.runtimeId),a=p(i,this.options.runtime.runtimeId).bindings[e.bindingId];if(!a?.fingerprint)return{skip:!1};try{const o=this.options.api??m(),t=await w({runtime:this.options.runtime,binding:e,helper:this.helper,api:o,workDir:this.options.workDir},{foregroundMode:n}),s=n==="background"?t:await g(this.helper,t),l=await S(this.helper,s.windowId,void 0,s.bounds),v=await O(this.helper,l),d=y({binding:e,ocr:v,prior:a});return!d?.fingerprint||d.fingerprint!==a.fingerprint?{skip:!1}:{skip:!0,reasonCode:"conversation_fingerprint_unchanged",fingerprint:d.fingerprint}}catch{return{skip:!1}}}}function ie(r){return new R(r)}function E(r,e,n,i=3){return(F(r,e).bindings[n]?.recent??[]).slice(-i).map(t=>({stableMessageKey:t.stableMessageKey,senderRole:t.senderRole,kind:t.kind,anchorText:t.anchorText??t.normalizedText??t.textExcerpt??null,anchorMetadata:t.anchorMetadata??null}))}function B(r){if(r.helperClientOptions)return new f({...r.helperClientOptions,requiredCapabilities:r.helperClientOptions.requiredCapabilities??C(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",requestLogger:r.helperClientOptions.requestLogger??r.helperRequestLogger});const e=k();if(!e.ok)throw new Error(`${e.reasonCode}: ${e.message}`);return new f({helperPath:e.helperPath,expectedHelperVersion:e.version||W,requiredCapabilities:C(r.runtime),guardUnsafeWindowsCommands:r.runtime.policy.platform==="win32",skipUserActivityGuard:r.runtime.foregroundPolicy==="work",cleanupWindowsOverlays:r.runtime.policy.platform==="win32",requestLogger:r.helperRequestLogger})}function C(r){if(r.policy.platform==="win32")return r.bindings.some(e=>e.enabled!==!1&&e.allowReply!==!1)?c("send"):r.bindings.some(e=>e.enabled!==!1&&e.downloadMedia!==!1)?c("download"):c("observe")}function U(r,e){return u.join(r,"wechat-channel",`${e}.ledger.json`)}function b(r,e){return u.join(r,"wechat-channel",`${$(e)}.conversation-fingerprints.json`)}function p(r,e){try{const n=JSON.parse(h.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 N(r,e){h.mkdirSync(u.dirname(r),{recursive:!0}),h.writeFileSync(r,JSON.stringify(e,null,2))}function G(r){const e=y({binding:r.binding,ocr:r.ocr,prior:p(r.statePath,r.runtimeId).bindings[r.binding.bindingId]});if(!e?.fingerprint)return;const n=p(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()},N(r.statePath,n)}function y(r){const e=r.ocr.visibleConversationFingerprints??[],n=T({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 i=n.item.fingerprint||M(n.item);return i?{item:n.item,fingerprint:i}:null}function $(r){return String(r||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"wechat-channel-runtime"}export{R as WeChatChannelProductRunner,E as buildLocalLedgerTailAnchors,ie as createWeChatChannelProductRunner};
1
+ import u from"node:path";import h from"node:fs";import{createWeChatChannelApiClient as m}from"./client.js";import{WeChatChannelHelperClient as f}from"./helper-client.js";import{resolveWeChatChannelHelperAsset as k,WECHAT_CHANNEL_HELPER_VERSION as W}from"./helper-assets.js";import{requiredWindowsWeChatChannelHelperCapabilitiesForProfile as c}from"./helper-protocol.js";import{captureWeChatWindow as S,detectWeChatMessageInputPointByServerVision as I,focusKnownWeChatWindow as g,observeWeChatChannelBindingViaHelper as P,openConversationBySearch as A,recognizeWeChatScreenshot as O,selectWeChatWindowForBinding as w}from"./observer.js";import{WeChatChannelScheduler as D}from"./scheduler.js";import{loadWeChatChannelLedger as F}from"./ledger.js";import{buildWeChatConversationFingerprint as M,matchWeChatConversationFingerprints as T}from"./fingerprint.js";import{WeChatChannelOutboundSender as x}from"./outbound-sender.js";import{SerialWeChatAutomationLane as L}from"./automation-lane.js";import{decideWeChatChannelActivityGate as _,nextWeChatChannelActivityRetryAt as q,normalizeWeChatChannelActivitySnapshot as H}from"./human-coordination.js";class R{options;scheduler;helper;helperStarted=!1;lane;constructor(e){this.options=e;const n=e.api??m();this.helper=e.helper??B(e),this.lane=e.lane??new L,this.scheduler=new D({runtime:e.runtime,workDir:e.workDir,api:n,ledgerPath:e.ledgerPath,outboundLedgerPath:e.outboundLedgerPath,onInboundMessages:e.onInboundMessages,activityGate:{canObserve:async(i,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 t=_({snapshot:H(o.result),stage:"observe"});return t.ok?{ok:!0}:{ok:!1,reasonCode:t.reasonCode,nextAttemptAt:q(a,t.waitMs)}}},shouldSkipObserve:(i,a)=>this.shouldSkipObserveForUnchangedFingerprint(i,a.foregroundMode),outboundSender:new x({helper:this.helper,platform:e.runtime.policy.platform,activityGatePolicy:e.runtime.foregroundPolicy==="work"?{mouseMovedThresholdMs:0,mouseClickThresholdMs:0,scrollWheelThresholdMs:0,keyDownThresholdMs:0}:void 0,takeoverCheck:e.runtime.foregroundPolicy!=="work",openConversation:async i=>{const a={bindingId:`outbound:${i}`,sessionId:"outbound",conversationDisplayName:i,enabled:!0,allowReply:!0,downloadMedia:!0},o=await w({runtime:e.runtime,binding:a,helper:this.helper,api:n,workDir:e.workDir}),t=await g(this.helper,o),s=await A({helper:this.helper,window:t,settleToBottom:!1,binding:a,runtime:e.runtime,api:n,workDir:e.workDir});if(!s.opened)return{...s,windowId:t.windowId,inputPoint:null};const l=await I({runtime:e.runtime,binding:a,helper:this.helper,api:n,workDir:e.workDir,window:t});return{...s,windowId:t.windowId,inputPoint:l}}}),observeBinding:(i,a)=>P({runtime:e.runtime,binding:i,helper:this.helper,api:n,workDir:e.workDir,localLedgerTailAnchors:E(e.ledgerPath??U(e.workDir,e.runtime.runtimeId),e.runtime.runtimeId,i.bindingId),localOutboundEchoAnchors:a?.localOutboundEchoAnchors,foregroundMode:a?.foregroundMode,onObservationEvidence:({ocr:o})=>{G({statePath:b(e.workDir,e.runtime.runtimeId),runtimeId:e.runtime.runtimeId,binding:i,ocr:o})}})})}async start(){return this.lane.run("wechat.runner.start",async()=>{await this.ensureHelperStarted(),await this.scheduler.start()})}async tick(e){return this.lane.run("wechat.runner.tick",async()=>(await this.ensureHelperStarted(),this.scheduler.tick(e)))}async stop(){return this.lane.run("wechat.runner.stop",async()=>{await 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 shouldSkipObserveForUnchangedFingerprint(e,n="required"){if(this.options.runtime.policy.platform!=="darwin"&&this.options.runtime.policy.platform!=="win32")return{skip:!1};const i=b(this.options.workDir,this.options.runtime.runtimeId),a=p(i,this.options.runtime.runtimeId).bindings[e.bindingId];if(!a?.fingerprint)return{skip:!1};try{const o=this.options.api??m(),t=await w({runtime:this.options.runtime,binding:e,helper:this.helper,api:o,workDir:this.options.workDir},{foregroundMode:n}),s=n==="background"?t:await g(this.helper,t),l=await S(this.helper,s.windowId,void 0,s.bounds),v=await O(this.helper,l),d=y({binding:e,ocr:v,prior:a});return!d?.fingerprint||d.fingerprint!==a.fingerprint?{skip:!1}:{skip:!0,reasonCode:"conversation_fingerprint_unchanged",fingerprint:d.fingerprint}}catch{return{skip:!1}}}}function ie(r){return new R(r)}function E(r,e,n,i=3){return(F(r,e).bindings[n]?.recent??[]).slice(-i).map(t=>({stableMessageKey:t.stableMessageKey,senderRole:t.senderRole,kind:t.kind,anchorText:t.anchorText??t.normalizedText??t.textExcerpt??null,anchorMetadata:t.anchorMetadata??null}))}function B(r){if(r.helperClientOptions)return new f({...r.helperClientOptions,requiredCapabilities:r.helperClientOptions.requiredCapabilities??C(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",requestLogger:r.helperClientOptions.requestLogger??r.helperRequestLogger});const e=k();if(!e.ok)throw new Error(`${e.reasonCode}: ${e.message}`);return new f({helperPath:e.helperPath,expectedHelperVersion:e.version||W,requiredCapabilities:C(r.runtime),guardUnsafeWindowsCommands:r.runtime.policy.platform==="win32",skipUserActivityGuard:r.runtime.foregroundPolicy==="work",cleanupWindowsOverlays:r.runtime.policy.platform==="win32",requestLogger:r.helperRequestLogger})}function C(r){if(r.policy.platform==="win32")return r.bindings.some(e=>e.enabled!==!1&&e.allowReply!==!1)?c("send"):r.bindings.some(e=>e.enabled!==!1&&e.downloadMedia!==!1)?c("download"):c("observe")}function U(r,e){return u.join(r,"wechat-channel",`${e}.ledger.json`)}function b(r,e){return u.join(r,"wechat-channel",`${$(e)}.conversation-fingerprints.json`)}function p(r,e){try{const n=JSON.parse(h.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 N(r,e){h.mkdirSync(u.dirname(r),{recursive:!0}),h.writeFileSync(r,JSON.stringify(e,null,2))}function G(r){const e=y({binding:r.binding,ocr:r.ocr,prior:p(r.statePath,r.runtimeId).bindings[r.binding.bindingId]});if(!e?.fingerprint)return;const n=p(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()},N(r.statePath,n)}function y(r){const e=r.ocr.visibleConversationFingerprints??[],n=T({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 i=M(n.item);return i?{item:n.item,fingerprint:i,reason:n.reason}:null}function $(r){return String(r||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"wechat-channel-runtime"}export{R as WeChatChannelProductRunner,E as buildLocalLedgerTailAnchors,ie as createWeChatChannelProductRunner};
@@ -1 +1 @@
1
- import v from"node:path";import{loadWeChatChannelLedger as _,markWeChatChannelBindingDisabled as k,saveWeChatChannelLedger as R,updateWeChatChannelBindingLedger as A}from"./ledger.js";import{classifyWeChatOutboundEchoes as E,isWeChatOutboundTerminalDeliveryStatus as W,loadWeChatChannelOutboundLedger as T,markWeChatOutboundUserActiveTimeout as M,markWeChatOutboundWaitingUserIdle as x,saveWeChatChannelOutboundLedger as P,shouldFailWeChatOutboundForUserActiveTimeout as U}from"./outbound-ledger.js";import{clearExpiredWeChatChannelCooldown as L,isWeChatChannelCooldownActive as B,noteWeChatChannelBillingPause as D,noteWeChatChannelInterruption as K,noteWeChatChannelStableRun as j}from"./cooldown.js";import{sendQueuedWeChatOutboundRecords as F}from"./outbound-sender.js";const $=600*1e3,q=8e3;class ln{options;timer=null;running=!1;runningTick=null;constructor(t){this.options=t}async start(){this.timer||(await this.tick(),this.timer=setInterval(()=>{this.tick().catch(()=>{})},this.options.runtime.policy.pollIntervalMs),this.timer.unref())}async stop(){this.timer&&(clearInterval(this.timer),this.timer=null);const t=this.runningTick;t&&await en(t,q)}async tick(t={}){for(;this.running;){if(!t.forceObserve)return;const e=this.runningTick;if(!e)return;await e}this.running=!0;let i=()=>{};this.runningTick=new Promise(e=>{i=e});try{const e=this.options.ledgerPath||y(this.options.workDir,this.options.runtime.runtimeId),r=this.options.outboundLedgerPath||X(this.options.workDir,this.options.runtime.runtimeId),d=_(e,this.options.runtime.runtimeId),c=T(r,this.options.runtime.runtimeId),h=[],b=new Date;for(const o of this.options.runtime.bindings.filter(a=>!a.enabled))d.bindings[o.bindingId]?.disabledSince||k({ledger:d,bindingId:o.bindingId});for(const o of this.options.runtime.bindings.filter(a=>a.enabled)){await this.options.api.upsertRuntime(this.options.runtime,o);const a=d.bindings[o.bindingId],p=c.records.filter(s=>s.bindingId===o.bindingId),C=z(this.options.runtime,p);if(a?.cooldown&&(a.cooldown=L(a.cooldown),B(a.cooldown))){await this.options.api.reportRunStatus(this.options.runtime,o,{status:"cooldown",reasonCode:a.cooldown.manualReviewReason||"user_interruption_cooldown"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a.revision});continue}const f=this.options.activityGate?await this.options.activityGate.canObserve(o,b):{ok:!0};if(f.ok===!1){const s=sn({records:c.records,bindingId:o.bindingId,reasonCode:f.reasonCode,nextAttemptAt:f.nextAttemptAt,now:b,maxUserActivityWaitMs:this.options.maxUserActivityWaitMs??$});await this.reportOutboundProjections(o,[...s.waitingRecords,...s.failedRecords]),await this.options.api.reportRunStatus(this.options.runtime,o,{status:"observe_deferred_user_active",reasonCode:s.failedRecords[0]?.failureCode||f.reasonCode}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:s.failedRecords.length,waitingOutboundCount:s.waitingRecords.length,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}if(!t.forceObserve&&this.options.shouldSkipObserve&&V(a,p)){const s=await this.options.shouldSkipObserve(o,{ledgerBinding:a,outboundRecords:p,now:b,foregroundMode:C});if(s.skip){await this.options.api.reportRunStatus(this.options.runtime,o,{status:G(s.reasonCode),reasonCode:s.reasonCode}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}}let w;try{w=await this.options.observeBinding(o,{localOutboundEchoAnchors:on(c.records,o.bindingId),foregroundMode:C})}catch(s){if(H(s)){await this.options.api.reportRunStatus(this.options.runtime,o,{status:"observe_deferred_window_unavailable",reasonCode:I(s,"wechat_window_unavailable"),lastErrorSummary:s instanceof Error?s.message:"wechat_window_unavailable"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}if(Q(s)){await this.options.api.reportRunStatus(this.options.runtime,o,{status:"wechat_window_unresponsive",reasonCode:"wechat_window_unresponsive",lastErrorSummary:s instanceof Error?s.message:"wechat_window_unresponsive"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}if(N(s)){const m=a??{bindingId:o.bindingId,baselineEstablished:!1,revision:0,recent:[],pendingSendKeys:[]};m.cooldown=D({state:m.cooldown,reason:"insufficient_credits",now:b}),d.bindings[o.bindingId]=m,await this.options.api.reportRunStatus(this.options.runtime,o,{status:"billing_paused",reasonCode:"insufficient_credits",lastErrorSummary:s instanceof Error?s.message:"insufficient_credits"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:m.revision});continue}throw s}const g=E({ledger:c,bindingId:o.bindingId,messages:w}),O=nn(o,g.remainingMessages),l=A({ledger:d,bindingId:o.bindingId,observedMessages:O}),u=this.options.outboundSender?await F({ledger:c,bindingId:o.bindingId,currentLastInboundRevision:l.binding.revision,sender:this.options.outboundSender,now:b,maxUserActivityWaitMs:this.options.maxUserActivityWaitMs}):{sentRecords:[],staleRecords:[],failedRecords:[],waitingRecords:[],manualReviewRecords:[]};l.binding.cooldown=j(l.binding.cooldown),l.newMessages.length>0&&(await this.options.api.ingest(this.options.runtime,o,{idempotencyKey:`${o.bindingId}:${l.binding.revision}:${Z(l.newMessages)}`,messages:l.newMessages}),await this.options.onInboundMessages?.(o,l.newMessages)),await this.reportOutboundProjections(o,[...g.confirmedRecords,...g.manualReviewRecords,...u.sentRecords,...u.staleRecords,...u.failedRecords,...u.waitingRecords,...u.manualReviewRecords]),await this.options.api.reportRunStatus(this.options.runtime,o,{status:tn(l.newMessages.length,g.confirmedRecords,g.manualReviewRecords,u.sentRecords,u.staleRecords,u.failedRecords,u.waitingRecords,u.manualReviewRecords),reasonCode:u.waitingRecords[0]?.deferReason}),h.push({bindingId:o.bindingId,observedCount:w.length,newInboundCount:l.newMessages.length,outboundSentCount:u.sentRecords.length,staleOutboundCount:u.staleRecords.length,failedOutboundCount:u.failedRecords.length,waitingOutboundCount:u.waitingRecords.length,confirmedEchoCount:g.confirmedRecords.length,manualReviewCount:g.manualReviewRecords.length+u.manualReviewRecords.length,revision:l.binding.revision})}return R(e,d),P(r,c),h}finally{i(),this.runningTick=null,this.running=!1}}noteInterruption(t,i,e=new Date){const r=this.options.ledgerPath||y(this.options.workDir,this.options.runtime.runtimeId),d=_(r,this.options.runtime.runtimeId),c=d.bindings[t]??{bindingId:t,baselineEstablished:!1,revision:0,recent:[],pendingSendKeys:[]};c.cooldown=K({state:c.cooldown,reason:i,now:e}),d.bindings[t]=c,R(r,d)}async reportOutboundProjections(t,i){if(this.options.api.reportOutboundStatus)for(const e of i)await this.options.api.reportOutboundStatus(this.options.runtime,t,{replyId:e.replyId,idempotencyKey:e.idempotencyKey,status:e.sendStatus,replyBaseRevision:e.replyBaseRevision,sentAt:e.sentAt,confirmedAt:e.confirmedAt,failureCode:e.failureCode,lastErrorSummary:e.lastErrorSummary,nextAttemptAt:e.nextAttemptAt,attemptCount:e.attemptCount,commitStage:e.commitStage,copyableContentRef:e.copyableContentRef,copyableText:Y(e)?e.text:void 0,agentVisible:W(e)?!1:void 0})}}function N(n){return!n||typeof n!="object"?!1:n.reasonCode==="insufficient_credits"?!0:n instanceof Error&&/\binsufficient_credits\b/.test(n.message)}function z(n,t){return n.foregroundPolicy==="work"||t.some(i=>i.sendStatus==="queued"||i.sendStatus==="sending"||i.sendStatus==="sent_unconfirmed")?"required":"background"}function G(n){return n==="conversation_not_visible_background"?"observe_deferred_window_unavailable":"baseline_or_no_change"}function H(n){const t=I(n);return t==="wechat_window_unavailable"||t==="wechat_window_not_found"||t==="conversation_not_visible_background"||t==="wechat_window_obstructed_background"}function I(n,t="unknown_error"){if(n&&typeof n=="object"){const r=n.reasonCode??n.errorCode;if(typeof r=="string"&&r.trim())return r.trim()}return(n instanceof Error?n.message:String(n||"")).match(/^([a-z0-9_./-]+)(?::|\b)/i)?.[1]||t}function Q(n){return n instanceof Error&&/\bwechat_window_unresponsive\b/i.test(n.message)}function V(n,t){return!n?.baselineEstablished||n.disabledSince||!n.recent.length?!1:!t.some(i=>i.sendStatus==="queued"||i.sendStatus==="sending"||i.sendStatus==="sent_unconfirmed")}function Y(n){return(n.sendStatus==="failed"||n.sendStatus==="manual_review")&&!!n.text}function J(n){return!!n&&typeof n=="object"&&!Array.isArray(n)}function y(n,t){return v.join(n,"wechat-channel",`${t}.ledger.json`)}function X(n,t){return v.join(n,"wechat-channel",`${t}.outbound-ledger.json`)}function Z(n){return n.at(-1)?.stableMessageKey||"empty"}function nn(n,t){const i=S(n.selfTriggerMarker);return i?t.map(e=>e.senderRole!=="self"||!S(e.anchorText||e.normalizedText||e.textExcerpt||"").includes(i)?e:{...e,stableMessageKey:`self-trigger:${e.stableMessageKey}`,senderRole:"contact",anchorMetadata:{...J(e.anchorMetadata)?e.anchorMetadata:{},selfTrigger:!0,originalSenderRole:"self"}}):t}function S(n){return typeof n=="string"?n.replace(/\s+/g," ").trim():""}async function en(n,t){let i=null;await Promise.race([n.then(()=>{},()=>{}),new Promise(e=>{i=setTimeout(e,t),i.unref?.()})]),i&&clearTimeout(i)}function tn(n,t,i,e=[],r=[],d=[],c=[],h=[]){return i.length>0||h.length>0?"manual_review":d.length>0?"outbound_send_failed":r.length>0?"outbound_stale":c.length>0?"send_waiting_user_idle":n>0?"new_messages_ingested":e.length>0?"outbound_sent_unconfirmed":t.length>0?"outbound_echo_confirmed":"baseline_or_no_change"}function on(n,t){return n.filter(i=>i.bindingId===t&&i.sendStatus==="sent_unconfirmed").map(i=>({replyId:i.replyId,idempotencyKey:i.idempotencyKey,expectedEchoAnchor:i.expectedEchoAnchor||i.textNormalized||i.text||""}))}function sn(n){const t=n.records.filter(r=>r.bindingId===n.bindingId&&r.sendStatus==="queued"),i=[],e=[];for(const r of t)x({record:r,reasonCode:n.reasonCode,nextAttemptAt:n.nextAttemptAt,now:n.now}),U({record:r,now:n.now,maxWaitMs:n.maxUserActivityWaitMs})?(M(r,n.now),i.push(r)):e.push(r);return{waitingRecords:e,failedRecords:i}}export{ln as WeChatChannelScheduler};
1
+ import v from"node:path";import{loadWeChatChannelLedger as _,markWeChatChannelBindingDisabled as k,saveWeChatChannelLedger as R,updateWeChatChannelBindingLedger as A}from"./ledger.js";import{classifyWeChatOutboundEchoes as E,isWeChatOutboundTerminalDeliveryStatus as W,loadWeChatChannelOutboundLedger as M,markWeChatOutboundUserActiveTimeout as T,markWeChatOutboundWaitingUserIdle as x,saveWeChatChannelOutboundLedgerMerging as P,shouldFailWeChatOutboundForUserActiveTimeout as U}from"./outbound-ledger.js";import{clearExpiredWeChatChannelCooldown as L,isWeChatChannelCooldownActive as D,noteWeChatChannelBillingPause as j,noteWeChatChannelInterruption as B,noteWeChatChannelStableRun as K}from"./cooldown.js";import{sendQueuedWeChatOutboundRecords as F}from"./outbound-sender.js";const $=600*1e3,q=8e3;class hn{options;timer=null;running=!1;runningTick=null;constructor(t){this.options=t}async start(){this.timer||(await this.tick(),this.timer=setInterval(()=>{this.tick().catch(()=>{})},this.options.runtime.policy.pollIntervalMs),this.timer.unref())}async stop(){this.timer&&(clearInterval(this.timer),this.timer=null);const t=this.runningTick;t&&await tn(t,q)}async tick(t={}){for(;this.running;){if(!t.forceObserve)return;const e=this.runningTick;if(!e)return;await e}this.running=!0;let i=()=>{};this.runningTick=new Promise(e=>{i=e});try{const e=this.options.ledgerPath||y(this.options.workDir,this.options.runtime.runtimeId),r=this.options.outboundLedgerPath||Z(this.options.workDir,this.options.runtime.runtimeId),d=_(e,this.options.runtime.runtimeId),c=M(r,this.options.runtime.runtimeId),h=[],b=new Date;for(const o of this.options.runtime.bindings.filter(a=>!a.enabled))d.bindings[o.bindingId]?.disabledSince||k({ledger:d,bindingId:o.bindingId});for(const o of this.options.runtime.bindings.filter(a=>a.enabled)){await this.options.api.upsertRuntime(this.options.runtime,o);const a=d.bindings[o.bindingId],p=c.records.filter(s=>s.bindingId===o.bindingId),C=z(this.options.runtime,p);if(a?.cooldown&&(a.cooldown=L(a.cooldown),D(a.cooldown))){await this.options.api.reportRunStatus(this.options.runtime,o,{status:"cooldown",reasonCode:a.cooldown.manualReviewReason||"user_interruption_cooldown"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a.revision});continue}const f=this.options.activityGate?await this.options.activityGate.canObserve(o,b):{ok:!0};if(f.ok===!1){const s=rn({records:c.records,bindingId:o.bindingId,reasonCode:f.reasonCode,nextAttemptAt:f.nextAttemptAt,now:b,maxUserActivityWaitMs:this.options.maxUserActivityWaitMs??$});await this.reportOutboundProjections(o,[...s.waitingRecords,...s.failedRecords]),await this.options.api.reportRunStatus(this.options.runtime,o,{status:"observe_deferred_user_active",reasonCode:s.failedRecords[0]?.failureCode||f.reasonCode}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:s.failedRecords.length,waitingOutboundCount:s.waitingRecords.length,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}if(!t.forceObserve&&this.options.shouldSkipObserve&&V(a,p)){const s=await this.options.shouldSkipObserve(o,{ledgerBinding:a,outboundRecords:p,now:b,foregroundMode:C});if(s.skip){await this.options.api.reportRunStatus(this.options.runtime,o,{status:G(s.reasonCode),reasonCode:s.reasonCode}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}}let w;try{w=await this.options.observeBinding(o,{localOutboundEchoAnchors:sn(c.records,o.bindingId),foregroundMode:C})}catch(s){if(H(s)){await this.options.api.reportRunStatus(this.options.runtime,o,{status:"observe_deferred_window_unavailable",reasonCode:I(s,"wechat_window_unavailable"),lastErrorSummary:s instanceof Error?s.message:"wechat_window_unavailable"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}if(Q(s)){await this.options.api.reportRunStatus(this.options.runtime,o,{status:"wechat_window_unresponsive",reasonCode:"wechat_window_unresponsive",lastErrorSummary:s instanceof Error?s.message:"wechat_window_unresponsive"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:a?.revision??0});continue}if(N(s)){const m=a??{bindingId:o.bindingId,baselineEstablished:!1,revision:0,recent:[],pendingSendKeys:[]};m.cooldown=j({state:m.cooldown,reason:"insufficient_credits",now:b}),d.bindings[o.bindingId]=m,await this.options.api.reportRunStatus(this.options.runtime,o,{status:"billing_paused",reasonCode:"insufficient_credits",lastErrorSummary:s instanceof Error?s.message:"insufficient_credits"}),h.push({bindingId:o.bindingId,observedCount:0,newInboundCount:0,outboundSentCount:0,staleOutboundCount:0,failedOutboundCount:0,waitingOutboundCount:0,confirmedEchoCount:0,manualReviewCount:0,revision:m.revision});continue}throw s}const g=E({ledger:c,bindingId:o.bindingId,messages:w}),O=en(o,g.remainingMessages),l=A({ledger:d,bindingId:o.bindingId,observedMessages:O}),u=this.options.outboundSender?await F({ledger:c,bindingId:o.bindingId,currentLastInboundRevision:l.binding.revision,sender:this.options.outboundSender,now:b,maxUserActivityWaitMs:this.options.maxUserActivityWaitMs}):{sentRecords:[],staleRecords:[],failedRecords:[],waitingRecords:[],manualReviewRecords:[]};l.binding.cooldown=K(l.binding.cooldown),l.newMessages.length>0&&(await this.options.api.ingest(this.options.runtime,o,{idempotencyKey:`${o.bindingId}:${l.binding.revision}:${nn(l.newMessages)}`,messages:l.newMessages}),await this.options.onInboundMessages?.(o,l.newMessages)),await this.reportOutboundProjections(o,[...g.confirmedRecords,...g.manualReviewRecords,...u.sentRecords,...u.staleRecords,...u.failedRecords,...u.waitingRecords,...u.manualReviewRecords]),await this.options.api.reportRunStatus(this.options.runtime,o,{status:on(l.newMessages.length,g.confirmedRecords,g.manualReviewRecords,u.sentRecords,u.staleRecords,u.failedRecords,u.waitingRecords,u.manualReviewRecords),reasonCode:u.waitingRecords[0]?.deferReason}),h.push({bindingId:o.bindingId,observedCount:w.length,newInboundCount:l.newMessages.length,outboundSentCount:u.sentRecords.length,staleOutboundCount:u.staleRecords.length,failedOutboundCount:u.failedRecords.length,waitingOutboundCount:u.waitingRecords.length,confirmedEchoCount:g.confirmedRecords.length,manualReviewCount:g.manualReviewRecords.length+u.manualReviewRecords.length,revision:l.binding.revision})}return R(e,d),P(r,c),h}finally{i(),this.runningTick=null,this.running=!1}}noteInterruption(t,i,e=new Date){const r=this.options.ledgerPath||y(this.options.workDir,this.options.runtime.runtimeId),d=_(r,this.options.runtime.runtimeId),c=d.bindings[t]??{bindingId:t,baselineEstablished:!1,revision:0,recent:[],pendingSendKeys:[]};c.cooldown=B({state:c.cooldown,reason:i,now:e}),d.bindings[t]=c,R(r,d)}async reportOutboundProjections(t,i){if(this.options.api.reportOutboundStatus)for(const e of i)await this.options.api.reportOutboundStatus(this.options.runtime,t,{replyId:e.replyId,idempotencyKey:e.idempotencyKey,status:e.sendStatus,replyBaseRevision:e.replyBaseRevision,sentAt:e.sentAt,confirmedAt:e.confirmedAt,failureCode:e.failureCode,lastErrorSummary:e.lastErrorSummary,nextAttemptAt:e.nextAttemptAt,attemptCount:e.attemptCount,commitStage:e.commitStage,copyableContentRef:e.copyableContentRef,copyableText:J(e)?e.text:void 0,agentVisible:W(e)?!1:void 0})}}function N(n){return!n||typeof n!="object"?!1:n.reasonCode==="insufficient_credits"?!0:n instanceof Error&&/\binsufficient_credits\b/.test(n.message)}function z(n,t){return n.foregroundPolicy==="work"||t.some(i=>i.sendStatus==="queued"||i.sendStatus==="sending"||i.sendStatus==="sent_unconfirmed")?"required":"background"}function G(n){return n==="conversation_not_visible_background"?"observe_deferred_window_unavailable":"baseline_or_no_change"}function H(n){const t=I(n);return t==="wechat_window_unavailable"||t==="wechat_window_not_found"||t==="conversation_not_visible_background"||t==="wechat_window_obstructed_background"}function I(n,t="unknown_error"){if(n&&typeof n=="object"){const r=n.reasonCode??n.errorCode;if(typeof r=="string"&&r.trim())return r.trim()}return(n instanceof Error?n.message:String(n||"")).match(/^([a-z0-9_./-]+)(?::|\b)/i)?.[1]||t}function Q(n){return n instanceof Error&&/\bwechat_window_unresponsive\b/i.test(n.message)}function V(n,t){return!n?.baselineEstablished||n.disabledSince||!n.recent.length||Y(n)?!1:!t.some(i=>i.sendStatus==="queued"||i.sendStatus==="sending"||i.sendStatus==="sent_unconfirmed")}function Y(n){return Object.values(n.attachmentStates??{}).some(t=>t.availability==="pending-download")}function J(n){return(n.sendStatus==="failed"||n.sendStatus==="manual_review")&&!!n.text}function X(n){return!!n&&typeof n=="object"&&!Array.isArray(n)}function y(n,t){return v.join(n,"wechat-channel",`${t}.ledger.json`)}function Z(n,t){return v.join(n,"wechat-channel",`${t}.outbound-ledger.json`)}function nn(n){return n.at(-1)?.stableMessageKey||"empty"}function en(n,t){const i=S(n.selfTriggerMarker);return i?t.map(e=>e.senderRole!=="self"||!S(e.anchorText||e.normalizedText||e.textExcerpt||"").includes(i)?e:{...e,stableMessageKey:`self-trigger:${e.stableMessageKey}`,senderRole:"contact",anchorMetadata:{...X(e.anchorMetadata)?e.anchorMetadata:{},selfTrigger:!0,originalSenderRole:"self"}}):t}function S(n){return typeof n=="string"?n.replace(/\s+/g," ").trim():""}async function tn(n,t){let i=null;await Promise.race([n.then(()=>{},()=>{}),new Promise(e=>{i=setTimeout(e,t),i.unref?.()})]),i&&clearTimeout(i)}function on(n,t,i,e=[],r=[],d=[],c=[],h=[]){return i.length>0||h.length>0?"manual_review":d.length>0?"outbound_send_failed":r.length>0?"outbound_stale":c.length>0?"send_waiting_user_idle":n>0?"new_messages_ingested":e.length>0?"outbound_sent_unconfirmed":t.length>0?"outbound_echo_confirmed":"baseline_or_no_change"}function sn(n,t){return n.filter(i=>i.bindingId===t&&i.sendStatus==="sent_unconfirmed").map(i=>({replyId:i.replyId,idempotencyKey:i.idempotencyKey,expectedEchoAnchor:i.expectedEchoAnchor||i.textNormalized||i.text||""}))}function rn(n){const t=n.records.filter(r=>r.bindingId===n.bindingId&&r.sendStatus==="queued"),i=[],e=[];for(const r of t)x({record:r,reasonCode:n.reasonCode,nextAttemptAt:n.nextAttemptAt,now:n.now}),U({record:r,now:n.now,maxWaitMs:n.maxUserActivityWaitMs})?(T(r,n.now),i.push(r)):e.push(r);return{waitingRecords:e,failedRecords:i}}export{hn as WeChatChannelScheduler};
@@ -1,8 +1,8 @@
1
- import I from"node:crypto";import f from"node:fs";import m from"node:path";import{fileURLToPath as Z,pathToFileURL as ee}from"node:url";import{ChannelSecretRegistry as te}from"./secret-registry.js";import{resolveShennianPath as re}from"../config/index.js";import{probeMacWeChat as D,observedMessageFromProbe as ne}from"./wechat-rpa/macos.js";import{runMacWeChatRpaFlow as F}from"./wechat-rpa/macos-flow.js";import{normalizeWeChatRpaMessage as ae,WeChatRpaDeduper as K,weChatRpaConversationId as x}from"./wechat-rpa/normalizer.js";import{cancelWeChatRpaProductOutboundReply as se,countPendingWeChatRpaProductOutbound as ie,createWeChatRpaProductSourceConnection as oe,enqueueWeChatRpaProductOutboundReply as ue,isWeChatRpaProductSource as R,listWeChatRpaProductRecentMessages as ce,listPendingWeChatRpaProductOutbound as de,weChatChannelConversationId as N}from"./wechat-rpa/product-channel.js";import{createWeChatChannelApiClient as le}from"./wechat-channel/client.js";const pe=5e3,A=5,me=8,S=Number(process.env.SHENNIAN_WECHAT_RPA_OUTBOUND_ATTACHMENT_MAX_BYTES||20*1024*1024),M=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_THRESHOLD||3),T=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_MS||300*1e3),he=10;function fe(t){return P(t,A,1,50)>=me?"scroll-read":"read-latest"}class at{onMessage;options;type="wechat-rpa";secrets=new te;connections=new Map;constructor(e,r={}){this.onMessage=e,this.options=r}async connect(e){if(!e.enabled)return;const r=this.readSecret(e),n=this.ensureConnection(e);if(n.stopped=!1,n.config=e,W(n,e),O(n,e),this.seedConfiguredConversations(n,r),R(r.source)){if(await this.ensureProductConnection(n,r),n.timer)return;r.deferInitialPoll||await this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))),n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))).catch(()=>{})},n.product?.runtime.policy.pollIntervalMs??B(r.pollIntervalMs)),n.timer.unref();return}n.timer||(await this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))),n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))).catch(()=>{})},B(r.pollIntervalMs)),n.timer.unref())}async disconnect(e){const r=this.connections.get(e.id);if(!r)return;r.stopped=!0,r.timer&&clearInterval(r.timer),r.timer=null;const n=r.product;await this.enqueueOperation(r,async()=>{n&&await n.runner.stop()}).catch(()=>{}),this.connections.delete(e.id)}async syncNow(e){if(!e.enabled)throw new Error("WeChat RPA channel is disabled");const r=this.readSecret(e),n=this.ensureConnection(e);return n.stopped=!1,n.config=e,W(n,e),O(n,e),this.seedConfiguredConversations(n,r),R(r.source)?(await this.ensureProductConnection(n,r),this.enqueueOperation(n,async()=>{const a=this.readSecret(n.config),o=await this.pollProductOnce(n,a,{forceObserve:!0}),i=await this.ensureProductConnection(n,a),u=ce({config:n.config,product:i,limit:a.recentLimit});return ze(u,o)})):this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config)))}async cancelOutbound(e,r){const n=this.readSecret(e);return R(n.source)?se({config:e,idempotencyKey:r.idempotencyKey,replyId:r.replyId,reason:r.reason}):{cancelled:!1,status:"unsupported_source"}}async send(e,r){const n=this.readSecret(e);if(n.canReply===!1)throw new Error("WeChat RPA channel does not allow replies");const a=this.ensureConnection(e);if(a.config=e,W(a,e),O(a,e),this.seedConfiguredConversations(a,n),R(n.source)){const c=this.resolveConversationName(e,n,r.conversationId);if(!c)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const h=r.text.trim();if(!h&&!r.attachment)throw new Error("Reply text or attachment is required");return await this.ensureProductConnection(a,n),await this.enqueueOperation(a,async()=>{const y=this.readSecret(a.config);ue({config:a.config,secret:y,reply:{...r,text:h},conversationName:c}),a.runtimeState="retrying",g(a,{status:"sent",runId:a.lastRunId??null,summary:`queued ${c}`})}),this.enqueueOperation(a,()=>this.pollProductOnce(a,this.readSecret(a.config))).catch(y=>{a.lastError=y instanceof Error?y.message:String(y)}),{status:"queued"}}if(!Ne(n.source))throw new Error("WeChat RPA reply requires source=macos-flow, windows-visual-flow, or wechat-rpa-lab");const o=this.resolveConversationName(e,n,r.conversationId);if(!o)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const i=Me(e,r);if(a.completedPendingReplyKeys.has(i))return{status:"sent"};const u=a.manualReviewReplies.get(i);if(u)return{status:"manual-review",reason:u.reason};const s=r.attachment?await Ke(e.workDir,r.attachment):void 0,l=r.text.trim();if(!l&&!s)throw new Error("Reply text or attachment is required");if(Q(a,n)){const c="WeChat RPA is cooling down after repeated user activity";return a.runtimeState="cooldown",E(a,{key:i,conversationId:r.conversationId,conversationName:o,text:l,attachmentPath:s,reason:c}),{status:"queued",reason:c}}let d;try{a.runtimeState="syncing",d=await this.enqueueOperation(a,()=>(a.lastRunAt=new Date().toISOString(),G(e,n,o,l,s,i)))}catch(c){throw a.lastError=c instanceof Error?c.message:String(c),g(a,{status:k(a.lastError),runId:a.lastRunId??null,summary:a.lastError}),c}L(a,d);const p=V(d,{text:l,attachmentPath:s});if(p.status==="queued"){const c=p.reason;return E(a,{key:i,conversationId:r.conversationId,conversationName:o,text:p.text,attachmentPath:p.attachmentPath,reason:c,skipText:p.skipText}),a.lastError=c,{status:"queued",reason:c}}if(d.interrupted){const c=d.error||"WeChat RPA send was interrupted by user activity";return $(a,d,c),E(a,{key:i,conversationId:r.conversationId,conversationName:o,text:p.text,attachmentPath:s,reason:c,skipText:p.skipText}),{status:"queued",reason:c}}if(!d.ok){const c=d.error||"WeChat RPA send validator failed; manual review required before retry";return Y(a,i,c),{status:"manual-review",reason:c}}return g(a,{status:"sent",runId:a.lastRunId??null,summary:d.rpaTraceSummary||`sent ${o}`}),C(a),a.lastError=null,{status:"sent"}}async health(e){const r=this.readSecret(e);if((r.source??"macos-probe")==="fixture-jsonl")return r.fixturePath&&f.existsSync(r.fixturePath)?{ok:!0}:{ok:!1,message:"WeChat RPA fixture file is missing"};if(r.source==="macos-flow"){if(process.platform!=="darwin")return{ok:!1,message:"WeChat RPA macOS flow requires macOS"};if(!w(r).length)return{ok:!1,message:"WeChat RPA macOS flow requires at least one group"}}if(R(r.source))return Re(r,process.platform);if(r.source==="wechat-rpa-lab"){if(process.platform!=="darwin")return{ok:!1,message:"WeChat RPA Lab source requires macOS for live runs"};if(!w(r).length)return{ok:!1,message:"WeChat RPA Lab source requires at least one group"};try{z(e.workDir)}catch(a){return{ok:!1,message:a instanceof Error?a.message:String(a)}}return{ok:!0,message:"WeChat RPA Lab source configured"}}if(r.source==="windows-visual-flow")return ye(r,process.platform);const n=await D();return n.ok?{ok:!0,message:n.wechatRunning?"WeChat detected":"WeChat is not running"}:{ok:!1,message:n.message}}async defaultConversation(e){const r=this.readSecret(e),n=w(r)[0];if(!n)throw new Error("WeChat RPA channel has no configured groups");return R(r.source)?{conversationId:N(n),conversationName:n}:{conversationId:x(n),conversationName:n}}runtimeStatus(e){const r=this.connections.get(e.id);return r?{wechatRpaRuntimeState:r.runtimeState,wechatRpaLastRunAt:r.lastRunAt??null,wechatRpaLastMessageAt:r.lastMessageAt??null,wechatRpaLastInterruptedAt:r.lastInterruptedAt??null,wechatRpaLastError:r.lastError??null,wechatRpaLastRunId:r.lastRunId??null,wechatRpaLastTracePath:r.lastTracePath??r.product?.helperTracePath??null,wechatRpaLastTraceSummary:r.lastTraceSummary??null,wechatRpaPendingReplyCount:r.pendingReplies.size+ie({config:e}),wechatRpaPendingReplies:Te(r,e),wechatRpaRecentTaskSummaries:r.recentTaskSummaries}:{}}ensureConnection(e){let r=this.connections.get(e.id);return r||(r={config:e,timer:null,deduper:new K,stopped:!1,conversations:new Map,pendingReplies:new Map,manualReviewReplies:new Map,completedPendingReplyKeys:new Set,pendingStatePath:void 0,messageStatePath:void 0,operation:Promise.resolve(),recentTaskSummaries:[],runtimeState:"idle_waiting",consecutiveInterruptions:0},this.connections.set(e.id,r)),r}enqueueOperation(e,r){const n=this.options.automationLane?()=>this.options.automationLane?.run(`wechat-rpa:${e.config.id}`,r):r,a=e.operation.then(n,n);return e.operation=a.then(()=>{},()=>{}),a}readSecret(e){const r=this.secrets.get(e.secretRef);if(!r||r.type!=="wechat-rpa")throw new Error("WeChat RPA channel is not configured on this daemon");return r}async pollOnce(e,r){if(e.stopped)return[];const n=[];e.lastRunAt=new Date().toISOString();try{if(Q(e,r))return e.runtimeState="cooldown",[];if(await this.drainPendingReplies(e,r))return[];let o=!1;e.runtimeState="syncing";const i=await this.readObservedMessages(e.config,r,u=>{L(e,u),u.interrupted&&(o=!0,$(e,u,u.error||"WeChat RPA poll was interrupted by user activity"))});e.lastError=null;for(const u of i){const s=ae(u,{selfNicknames:U(r)});if(!s||!e.deduper.accept(s.messageId))continue;Le(e),e.conversations.set(s.conversationId,s.conversationName),e.lastMessageAt=s.receivedAt;const l={type:"external.message",managerSessionId:e.config.managerSessionId,channelId:e.config.id,channelType:"wechat-rpa",conversationId:s.conversationId,conversationName:s.conversationName,messageId:s.messageId,sender:s.sender,text:s.text,attachments:s.attachments,receivedAt:s.receivedAt,isMentioned:s.isMentioned,replyTarget:"",rawRef:s.rawRef};n.push(this.onMessage?.(l)??l),g(e,{status:"received",runId:e.lastRunId??null,summary:`received ${s.conversationName}${s.sender.name?` from ${s.sender.name}`:""}: ${_(s.text||s.attachments[0]?.name||"attachment")}`})}return o||C(e),n}catch(a){throw e.lastError=a instanceof Error?a.message:String(a),g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),a}}async pollProductOnce(e,r,n){if(e.stopped)return[];if(!R(r.source))return[];const a=await this.ensureProductConnection(e,r);if((await H(a).catch(()=>null))?.enabled===!1)return e.lastRunAt=new Date().toISOString(),e.runtimeState="idle_waiting",e.lastError=null,[];const i=a.emitted.length;e.lastRunAt=new Date().toISOString(),e.runtimeState="syncing";try{await a.runner.tick(n);const u=a.emitted.slice(i);e.lastError=null;for(const s of u)e.conversations.set(s.conversationId,s.conversationName||""),e.lastMessageAt=s.receivedAt,g(e,{status:"received",runId:e.lastRunId??null,summary:`received ${s.conversationName||s.conversationId}${s.sender.name?` from ${s.sender.name}`:""}: ${_(s.text||s.attachments[0]?.name||"attachment")}`});return C(e),u}catch(u){throw e.lastError=u instanceof Error?u.message:String(u),e.runtimeState=k(e.lastError)==="blocked"?"interrupted":"idle_waiting",g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),u}}async ensureProductConnection(e,r){const n=ge(e.config,r);return e.product&&e.productConfigKey===n||(e.product&&await e.product.runner.stop().catch(()=>{}),e.product=oe({config:e.config,secret:r,createRunner:this.options.createProductRunner,onMessage:this.onMessage}),await H(e.product).catch(()=>{}),e.productConfigKey=n,e.lastTracePath=e.product.helperTracePath),e.product}async drainPendingReplies(e,r){if(!e.pendingReplies.size)return!1;for(const n of Array.from(e.pendingReplies.values())){e.runtimeState="retrying",n.attempts+=1,n.lastAttemptAt=new Date().toISOString(),v(e);const a=await G(e.config,r,n.conversationName,n.skipText?"":n.text,n.attachmentPath,n.key);L(e,a);const o=V(a,{text:n.skipText?"":n.text,attachmentPath:n.attachmentPath});if(o.status==="queued")return n.text=o.text,n.attachmentPath=o.attachmentPath,n.skipText=o.skipText,n.lastInterruptedReason=o.reason,e.lastError=o.reason,v(e),!0;if(a.interrupted){const i=a.error||"WeChat RPA pending send was interrupted by user activity";return n.text=o.text,n.attachmentPath=o.attachmentPath,n.skipText=o.skipText,n.lastInterruptedReason=i,$(e,a,i),v(e),!0}if(!a.ok){const i=a.error||"WeChat RPA pending send validator failed; manual review required before retry";return e.pendingReplies.delete(n.key),Y(e,n.key,i),v(e),!0}e.pendingReplies.delete(n.key),e.completedPendingReplyKeys.add(n.key),v(e),e.lastError=null,g(e,{status:"sent",runId:e.lastRunId??null,summary:a.rpaTraceSummary||`sent ${n.conversationName}`}),C(e)}return!1}async readObservedMessages(e,r,n){if(r.source==="fixture-jsonl")return Fe(r.fixturePath);if(r.source==="macos-flow")return be(e,r,n);if(r.source==="wechat-rpa-lab")return ve(e,r,n);if(r.source==="windows-visual-flow")return xe(e,r,n);const a=await D(),o=ne(a);return o?[o]:[]}seedConfiguredConversations(e,r){for(const n of w(r))e.conversations.set(x(n),n),e.conversations.set(N(n),n)}resolveConversationName(e,r,n){const o=this.connections.get(e.id)?.conversations.get(n);return o||(w(r).find(i=>x(i)===n||N(i)===n)??null)}}function ge(t,e){return JSON.stringify({id:t.id,workDir:t.workDir,managerSessionId:t.managerSessionId,pollIntervalMs:e.pollIntervalMs,forceForeground:b(e),downloadAttachments:e.downloadAttachments!==!1,canReply:e.canReply!==!1,selfTriggerMarker:e.selfTriggerMarker||"",groups:w(e)})}async function H(t){const e=await le().getRuntimePolicy(),r=e.runtime?.current,n=e.runtime?.scanPolicy??e.usagePolicy?.scanPolicy,a=Number.isFinite(r?.pollIntervalSeconds)?Number(r?.pollIntervalSeconds):n?.defaultPollIntervalSeconds??e.runtime?.defaultPollIntervalSeconds;r?.foregroundPolicy==="polite"&&(t.runtime.foregroundPolicy="polite"),r?.foregroundPolicy==="work"&&(t.runtime.foregroundPolicy="work");const o=r?.status!=="disabled";if(!Number.isFinite(a))return{enabled:o};const i=Number.isFinite(n?.minPollIntervalSeconds)?Number(n?.minPollIntervalSeconds):e.runtime?.minPollIntervalSeconds,u=Number.isFinite(n?.maxPollIntervalSeconds)?Number(n?.maxPollIntervalSeconds):e.runtime?.maxPollIntervalSeconds,s=Number.isFinite(i)?Number(i)*1e3:t.runtime.policy.minPollIntervalMs,l=Number.isFinite(u)?Number(u)*1e3:t.runtime.policy.maxPollIntervalMs,d=Math.min(l,Math.max(s,Number(a)*1e3));return t.runtime.policy.pollIntervalMs=d,t.runtime.policy.minPollIntervalMs=s,t.runtime.policy.maxPollIntervalMs=l,{enabled:o}}function B(t){return Number.isFinite(t)?Math.min(6e4,Math.max(1e3,Number(t))):pe}function w(t){return Array.isArray(t.groups)?t.groups.map(e=>String(e?.name||"").trim()).filter(Boolean):[]}function U(t){return String(t.selfNickname||"").split(/[,\n,、]/).map(e=>e.trim()).filter(Boolean)}function we(t,e){return e.length?e.some(r=>{const n=r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`@\\s*${n}(?=$|\\s|[\uFF0C\u3002\uFF01\uFF1F,.!?:\uFF1A\uFF1B;\u3001)])`).test(t)}):!1}function ye(t,e=process.platform){return e!=="win32"?{ok:!1,message:"WeChat RPA Windows visual flow requires Windows"}:{ok:!1,message:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}}function Re(t,e=process.platform){return e!=="darwin"&&e!=="win32"?{ok:!1,message:"WeChat channel product source requires macOS or Windows"}:(Array.isArray(t.groups)?t.groups.map(n=>String(n?.name||"").trim()).filter(Boolean):[]).length?{ok:!0,message:"WeChat channel product source configured"}:{ok:!1,message:"WeChat channel product source requires at least one group"}}async function ve(t,e,r){const n=await j(t.workDir),a=[];for(const o of w(e)){const i=await Se(n,t,e,o);if(r?.(i),!(!i.ok||i.interrupted))for(const u of i.newMessages??[]){const s=X(o,u,e);s&&a.push(s)}}return a}async function Se(t,e,r,n){const a=fe(r.recentLimit),o={kind:a,requestId:`wechat-rpa:${n}:read:${Date.now()}`,targetGroup:n,policy:r.forceForeground?"work":"polite",limit:P(r.recentLimit,A,1,50),attachmentsDir:q(e,r)},i=await t.runWechatRpaReadLatest(o),u=Array.isArray(i.data?.structuredMessages)?i.data.structuredMessages:[],s=i.validation?.deterministic?.latestMessages??[],l=u.length?u.map((d,p)=>Pe(d,i.runId||"lab-read",p)):s.map((d,p)=>({id:`${i.runId||"lab-read"}:${p}`,text:d,confidence:.8}));return{ok:!!i.ok,groupName:n,interrupted:i.status==="interrupted",reason:i.status,rpaRunId:i.runId,rpaTraceSummary:`${a} ${i.status||(i.ok?"success":"failed")} ${n}`,recentMessages:l,newMessages:l,screenshotPath:i.tracePath,error:J(i)}}function Pe(t,e,r){return{id:`${e}:${t.index??r}`,text:String(t.text||t.card?.title||"").trim(),confidence:typeof t.confidence=="number"?t.confidence:.75,senderName:typeof t.senderName=="string"?t.senderName:null,attachments:Ie(t)}}function Ie(t){const e=String(t.kind||"");if(!["image","file","video-file","video-card","link-card","official-account-card","mini-program-card"].includes(e))return[];const r=typeof t.localPath=="string"?t.localPath:void 0;return[{type:e==="video-file"||e==="video-card"?"video":e==="link-card"||e==="official-account-card"||e==="mini-program-card"?"file":e,localPath:r,availability:r?"edge-local":"metadata-only"}]}async function Ae(t,e,r,n,a,o){const i=await j(t.workDir),u=Ce({groupName:r,text:n,attachmentPath:a,dedupeKey:o,requestId:o,policy:"work"}),s=[];for(const h of u){const y=await i.runWechatRpaTask(h);if(s.push(y),!y.ok)break}const l=s.find(h=>h.kind==="send-text"),d=s.find(h=>h.kind==="send-image"||h.kind==="send-video"||h.kind==="send-file"),p=u.length>0&&s.length===u.length&&s.every(h=>h.ok),c=s.some(h=>h.status==="interrupted");return{ok:p,groupName:r,interrupted:c,reason:s.at(-1)?.status,rpaRunId:s.at(-1)?.runId,rpaTraceSummary:`send ${p?"success":s.at(-1)?.status||"failed"} ${r} (${s.length}/${u.length})`,sentReply:!!n,sentReplyObserved:n?!!l?.ok:!1,sentAttachment:!!a,sentAttachmentObserved:a?!!d?.ok:!1,postSendScreenshotPath:s.at(-1)?.tracePath,error:p?void 0:J(s.at(-1))}}function Ce(t){const e=[],r=String(t.text||"").trim(),n=t.policy||"work";if(r&&e.push({kind:"send-text",requestId:t.requestId?`${t.requestId}:text`:void 0,targetGroup:t.groupName,policy:n,text:r,...t.dedupeKey?{dedupeKey:`${t.dedupeKey}:text`}:{}}),t.attachmentPath){const a=ke(t.attachmentPath);e.push({kind:a,requestId:t.requestId?`${t.requestId}:attachment`:void 0,targetGroup:t.groupName,policy:n,filePath:t.attachmentPath,...t.dedupeKey?{dedupeKey:`${t.dedupeKey}:attachment`}:{}})}return e}function ke(t){const e=m.extname(t).toLowerCase();return[".png",".jpg",".jpeg",".gif",".webp",".heic"].includes(e)?"send-image":[".mp4",".mov",".avi",".mkv"].includes(e)?"send-video":"send-file"}async function j(t){const e=z(t);return await import(ee(e).href)}function z(t){const e=m.dirname(Z(import.meta.url)),n=[process.env.SHENNIAN_WECHAT_RPA_LAB_INDEX,m.resolve(e,"../../../../scripts/wechat-rpa-lab/index.mjs"),t?m.join(t,"scripts/wechat-rpa-lab/index.mjs"):"",m.resolve(process.cwd(),"scripts/wechat-rpa-lab/index.mjs")].filter(Boolean).find(a=>f.existsSync(a));if(!n)throw new Error("WeChat RPA Lab API is missing; set SHENNIAN_WECHAT_RPA_LAB_INDEX or run from the repository root");return m.resolve(n)}function J(t){return t?typeof t.error=="string"?t.error:t.error?.message||(t.ok?void 0:`WeChat RPA Lab task ${t.kind||"<unknown>"} ${t.status||"failed"}`):"WeChat RPA Lab did not return a result"}async function be(t,e,r){const n=[];for(const a of w(e)){const o=await F({groupName:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:b(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:q(t,e)});if(r?.(o),!o.interrupted)for(const i of o.newMessages??[]){const u=X(a,i,e);u&&n.push(u)}}return n}async function xe(t,e,r){const n=w(e)[0]||"<unbound>";return r?.({ok:!1,groupName:n,interrupted:!0,reason:"windows-visual-flow-archived",newMessages:[],recentMessages:[],error:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}),[]}function X(t,e,r){const n=String(e.text||"").trim(),a=Array.isArray(e.attachments)?e.attachments:[];if(!n&&a.length===0)return null;const o=typeof e.senderName=="string"?e.senderName.trim():"",i=String(e.observedAt||e.timestampIso||"").trim()||new Date().toISOString();return{conversationName:t,senderName:o||null,text:n,attachments:je(a),observedAt:i,isMentioned:e.isMentioned===!0||we(n,U(r)),rawId:String(e.id||`${t}:${o}:${n}:${i}`)}}async function G(t,e,r,n,a,o){return e.source==="windows-visual-flow"?{ok:!1,groupName:r,interrupted:!0,reason:"windows-visual-flow-archived",newMessages:[],recentMessages:[],error:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}:e.source==="wechat-rpa-lab"?Ae(t,e,r,n,a,o):F({groupName:r,replyText:n||void 0,attachmentPath:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:b(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:q(t,e)})}function Ne(t){return t==="macos-flow"||t==="wechat-rpa-lab"}function V(t,e){return e.text&&e.attachmentPath&&t.sentReplyObserved&&!t.sentAttachmentObserved?{status:t.interrupted?"continue":"queued",text:"",attachmentPath:e.attachmentPath,skipText:!0,reason:t.error||"WeChat RPA sent the text but did not confirm the attachment; queued attachment-only retry"}:{status:"continue",text:e.text,attachmentPath:e.attachmentPath,reason:t.error||""}}function Me(t,e){return e.idempotencyKey?e.idempotencyKey:I.createHash("sha256").update(`${t.id}
1
+ import I from"node:crypto";import m from"node:fs";import p from"node:path";import{fileURLToPath as Z,pathToFileURL as ee}from"node:url";import{ChannelSecretRegistry as te}from"./secret-registry.js";import{resolveShennianPath as re}from"../config/index.js";import{probeMacWeChat as D,observedMessageFromProbe as ne}from"./wechat-rpa/macos.js";import{runMacWeChatRpaFlow as F}from"./wechat-rpa/macos-flow.js";import{normalizeWeChatRpaMessage as ae,WeChatRpaDeduper as K,weChatRpaConversationId as x}from"./wechat-rpa/normalizer.js";import{cancelWeChatRpaProductOutboundReply as se,countPendingWeChatRpaProductOutbound as ie,createWeChatRpaProductSourceConnection as oe,enqueueWeChatRpaProductOutboundReply as ue,isWeChatRpaProductSource as y,listWeChatRpaProductRecentMessages as ce,listPendingWeChatRpaProductOutbound as de,weChatChannelConversationId as N}from"./wechat-rpa/product-channel.js";import{createWeChatChannelApiClient as le}from"./wechat-channel/client.js";const pe=5e3,A=5,he=8,S=Number(process.env.SHENNIAN_WECHAT_RPA_OUTBOUND_ATTACHMENT_MAX_BYTES||20*1024*1024),E=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_THRESHOLD||3),T=Number(process.env.SHENNIAN_WECHAT_RPA_INTERRUPTION_COOLDOWN_MS||300*1e3),me=10;function fe(t){return P(t,A,1,50)>=he?"scroll-read":"read-latest"}class at{onMessage;options;type="wechat-rpa";secrets=new te;connections=new Map;constructor(e,r={}){this.onMessage=e,this.options=r}async connect(e){if(!e.enabled)return;const r=this.readSecret(e),n=this.ensureConnection(e);if(n.stopped=!1,n.config=e,M(n,e),O(n,e),this.seedConfiguredConversations(n,r),y(r.source)){if(await this.ensureProductConnection(n,r),n.timer)return;r.deferInitialPoll||await this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))),n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollProductOnce(n,this.readSecret(n.config))).catch(a=>{console.error(`[wechat-rpa] autonomous poll failed id=${e.id}: ${a instanceof Error?a.message:String(a)}`)})},n.product?.runtime.policy.pollIntervalMs??B(r.pollIntervalMs)),n.timer.unref();return}n.timer||(r.deferInitialPoll||await this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))),n.timer=setInterval(()=>{this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config))).catch(()=>{})},B(r.pollIntervalMs)),n.timer.unref())}async disconnect(e){const r=this.connections.get(e.id);if(!r)return;r.stopped=!0,r.timer&&clearInterval(r.timer),r.timer=null;const n=r.product;await this.enqueueOperation(r,async()=>{n&&await n.runner.stop()}).catch(()=>{}),this.connections.delete(e.id)}async syncNow(e){if(!e.enabled)throw new Error("WeChat RPA channel is disabled");const r=this.readSecret(e),n=this.ensureConnection(e);return n.stopped=!1,n.config=e,M(n,e),O(n,e),this.seedConfiguredConversations(n,r),y(r.source)?(await this.ensureProductConnection(n,r),this.enqueueOperation(n,async()=>{const a=this.readSecret(n.config),o=await this.pollProductOnce(n,a,{forceObserve:!0}),i=await this.ensureProductConnection(n,a),u=ce({config:n.config,product:i,limit:a.recentLimit});return ze(u,o)})):this.enqueueOperation(n,()=>this.pollOnce(n,this.readSecret(n.config)))}async cancelOutbound(e,r){const n=this.readSecret(e);return y(n.source)?se({config:e,idempotencyKey:r.idempotencyKey,replyId:r.replyId,reason:r.reason}):{cancelled:!1,status:"unsupported_source"}}async send(e,r){const n=this.readSecret(e);if(n.canReply===!1)throw new Error("WeChat RPA channel does not allow replies");const a=this.ensureConnection(e);if(a.config=e,M(a,e),O(a,e),this.seedConfiguredConversations(a,n),y(n.source)){const c=this.resolveConversationName(e,n,r.conversationId);if(!c)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const h=r.text.trim();if(!h&&!r.attachment)throw new Error("Reply text or attachment is required");return await this.ensureProductConnection(a,n),ue({config:a.config,secret:n,reply:{...r,text:h},conversationName:c}),a.runtimeState="retrying",g(a,{status:"sent",runId:a.lastRunId??null,summary:`queued ${c}`}),this.enqueueOperation(a,()=>this.pollProductOnce(a,this.readSecret(a.config))).catch(v=>{a.lastError=v instanceof Error?v.message:String(v)}),{status:"queued"}}if(!Ne(n.source))throw new Error("WeChat RPA reply requires source=macos-flow, windows-visual-flow, or wechat-rpa-lab");const o=this.resolveConversationName(e,n,r.conversationId);if(!o)throw new Error(`Unknown WeChat RPA conversation: ${r.conversationId}`);const i=Ee(e,r);if(a.completedPendingReplyKeys.has(i))return{status:"sent"};const u=a.manualReviewReplies.get(i);if(u)return{status:"manual-review",reason:u.reason};const s=r.attachment?await Ke(e.workDir,r.attachment):void 0,f=r.text.trim();if(!f&&!s)throw new Error("Reply text or attachment is required");if(Q(a,n)){const c="WeChat RPA is cooling down after repeated user activity";return a.runtimeState="cooldown",W(a,{key:i,conversationId:r.conversationId,conversationName:o,text:f,attachmentPath:s,reason:c}),{status:"queued",reason:c}}let d;try{a.runtimeState="syncing",d=await this.enqueueOperation(a,()=>(a.lastRunAt=new Date().toISOString(),G(e,n,o,f,s,i)))}catch(c){throw a.lastError=c instanceof Error?c.message:String(c),g(a,{status:k(a.lastError),runId:a.lastRunId??null,summary:a.lastError}),c}L(a,d);const l=V(d,{text:f,attachmentPath:s});if(l.status==="queued"){const c=l.reason;return W(a,{key:i,conversationId:r.conversationId,conversationName:o,text:l.text,attachmentPath:l.attachmentPath,reason:c,skipText:l.skipText}),a.lastError=c,{status:"queued",reason:c}}if(d.interrupted){const c=d.error||"WeChat RPA send was interrupted by user activity";return $(a,d,c),W(a,{key:i,conversationId:r.conversationId,conversationName:o,text:l.text,attachmentPath:s,reason:c,skipText:l.skipText}),{status:"queued",reason:c}}if(!d.ok){const c=d.error||"WeChat RPA send validator failed; manual review required before retry";return Y(a,i,c),{status:"manual-review",reason:c}}return g(a,{status:"sent",runId:a.lastRunId??null,summary:d.rpaTraceSummary||`sent ${o}`}),C(a),a.lastError=null,{status:"sent"}}async health(e){const r=this.readSecret(e);if((r.source??"macos-probe")==="fixture-jsonl")return r.fixturePath&&m.existsSync(r.fixturePath)?{ok:!0}:{ok:!1,message:"WeChat RPA fixture file is missing"};if(r.source==="macos-flow"){if(process.platform!=="darwin")return{ok:!1,message:"WeChat RPA macOS flow requires macOS"};if(!w(r).length)return{ok:!1,message:"WeChat RPA macOS flow requires at least one group"}}if(y(r.source))return Re(r,process.platform);if(r.source==="wechat-rpa-lab"){if(process.platform!=="darwin")return{ok:!1,message:"WeChat RPA Lab source requires macOS for live runs"};if(!w(r).length)return{ok:!1,message:"WeChat RPA Lab source requires at least one group"};try{z(e.workDir)}catch(a){return{ok:!1,message:a instanceof Error?a.message:String(a)}}return{ok:!0,message:"WeChat RPA Lab source configured"}}if(r.source==="windows-visual-flow")return ye(r,process.platform);const n=await D();return n.ok?{ok:!0,message:n.wechatRunning?"WeChat detected":"WeChat is not running"}:{ok:!1,message:n.message}}async defaultConversation(e){const r=this.readSecret(e),n=w(r)[0];if(!n)throw new Error("WeChat RPA channel has no configured groups");return y(r.source)?{conversationId:N(n),conversationName:n}:{conversationId:x(n),conversationName:n}}runtimeStatus(e){const r=this.connections.get(e.id);return r?{wechatRpaRuntimeState:r.runtimeState,wechatRpaLastRunAt:r.lastRunAt??null,wechatRpaLastMessageAt:r.lastMessageAt??null,wechatRpaLastInterruptedAt:r.lastInterruptedAt??null,wechatRpaLastError:r.lastError??null,wechatRpaLastRunId:r.lastRunId??null,wechatRpaLastTracePath:r.lastTracePath??r.product?.helperTracePath??null,wechatRpaLastTraceSummary:r.lastTraceSummary??null,wechatRpaPendingReplyCount:r.pendingReplies.size+ie({config:e}),wechatRpaPendingReplies:Te(r,e),wechatRpaRecentTaskSummaries:r.recentTaskSummaries}:{}}ensureConnection(e){let r=this.connections.get(e.id);return r||(r={config:e,timer:null,deduper:new K,stopped:!1,conversations:new Map,pendingReplies:new Map,manualReviewReplies:new Map,completedPendingReplyKeys:new Set,pendingStatePath:void 0,messageStatePath:void 0,operation:Promise.resolve(),recentTaskSummaries:[],runtimeState:"idle_waiting",consecutiveInterruptions:0},this.connections.set(e.id,r)),r}enqueueOperation(e,r){const n=this.options.automationLane?()=>this.options.automationLane?.run(`wechat-rpa:${e.config.id}`,r):r,a=e.operation.then(n,n);return e.operation=a.then(()=>{},()=>{}),a}readSecret(e){const r=this.secrets.get(e.secretRef);if(!r||r.type!=="wechat-rpa")throw new Error("WeChat RPA channel is not configured on this daemon");return r}async pollOnce(e,r){if(e.stopped)return[];const n=[];e.lastRunAt=new Date().toISOString();try{if(Q(e,r))return e.runtimeState="cooldown",[];if(await this.drainPendingReplies(e,r))return[];let o=!1;e.runtimeState="syncing";const i=await this.readObservedMessages(e.config,r,u=>{L(e,u),u.interrupted&&(o=!0,$(e,u,u.error||"WeChat RPA poll was interrupted by user activity"))});e.lastError=null;for(const u of i){const s=ae(u,{selfNicknames:U(r)});if(!s||!e.deduper.accept(s.messageId))continue;Le(e),e.conversations.set(s.conversationId,s.conversationName),e.lastMessageAt=s.receivedAt;const f={type:"external.message",managerSessionId:e.config.managerSessionId,channelId:e.config.id,channelType:"wechat-rpa",conversationId:s.conversationId,conversationName:s.conversationName,messageId:s.messageId,sender:s.sender,text:s.text,attachments:s.attachments,receivedAt:s.receivedAt,isMentioned:s.isMentioned,replyTarget:"",rawRef:s.rawRef};n.push(this.onMessage?.(f)??f),g(e,{status:"received",runId:e.lastRunId??null,summary:`received ${s.conversationName}${s.sender.name?` from ${s.sender.name}`:""}: ${_(s.text||s.attachments[0]?.name||"attachment")}`})}return o||C(e),n}catch(a){throw e.lastError=a instanceof Error?a.message:String(a),g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),a}}async pollProductOnce(e,r,n){if(e.stopped)return[];if(!y(r.source))return[];const a=await this.ensureProductConnection(e,r);if((await H(a).catch(()=>null))?.enabled===!1)return e.lastRunAt=new Date().toISOString(),e.runtimeState="idle_waiting",e.lastError=null,[];const i=a.emitted.length;e.lastRunAt=new Date().toISOString(),e.runtimeState="syncing";try{await a.runner.tick(n);const u=a.emitted.slice(i);e.lastError=null;for(const s of u)e.conversations.set(s.conversationId,s.conversationName||""),e.lastMessageAt=s.receivedAt,g(e,{status:"received",runId:e.lastRunId??null,summary:`received ${s.conversationName||s.conversationId}${s.sender.name?` from ${s.sender.name}`:""}: ${_(s.text||s.attachments[0]?.name||"attachment")}`});return C(e),u}catch(u){throw e.lastError=u instanceof Error?u.message:String(u),e.runtimeState=k(e.lastError)==="blocked"?"interrupted":"idle_waiting",g(e,{status:k(e.lastError),runId:e.lastRunId??null,summary:e.lastError}),u}}async ensureProductConnection(e,r){const n=ge(e.config,r);return e.product&&e.productConfigKey===n||(e.product&&await e.product.runner.stop().catch(()=>{}),e.product=oe({config:e.config,secret:r,createRunner:this.options.createProductRunner,onMessage:this.onMessage}),await H(e.product).catch(()=>{}),e.productConfigKey=n,e.lastTracePath=e.product.helperTracePath),e.product}async drainPendingReplies(e,r){if(!e.pendingReplies.size)return!1;for(const n of Array.from(e.pendingReplies.values())){e.runtimeState="retrying",n.attempts+=1,n.lastAttemptAt=new Date().toISOString(),R(e);const a=await G(e.config,r,n.conversationName,n.skipText?"":n.text,n.attachmentPath,n.key);L(e,a);const o=V(a,{text:n.skipText?"":n.text,attachmentPath:n.attachmentPath});if(o.status==="queued")return n.text=o.text,n.attachmentPath=o.attachmentPath,n.skipText=o.skipText,n.lastInterruptedReason=o.reason,e.lastError=o.reason,R(e),!0;if(a.interrupted){const i=a.error||"WeChat RPA pending send was interrupted by user activity";return n.text=o.text,n.attachmentPath=o.attachmentPath,n.skipText=o.skipText,n.lastInterruptedReason=i,$(e,a,i),R(e),!0}if(!a.ok){const i=a.error||"WeChat RPA pending send validator failed; manual review required before retry";return e.pendingReplies.delete(n.key),Y(e,n.key,i),R(e),!0}e.pendingReplies.delete(n.key),e.completedPendingReplyKeys.add(n.key),R(e),e.lastError=null,g(e,{status:"sent",runId:e.lastRunId??null,summary:a.rpaTraceSummary||`sent ${n.conversationName}`}),C(e)}return!1}async readObservedMessages(e,r,n){if(r.source==="fixture-jsonl")return Fe(r.fixturePath);if(r.source==="macos-flow")return be(e,r,n);if(r.source==="wechat-rpa-lab")return ve(e,r,n);if(r.source==="windows-visual-flow")return xe(e,r,n);const a=await D(),o=ne(a);return o?[o]:[]}seedConfiguredConversations(e,r){for(const n of w(r))e.conversations.set(x(n),n),e.conversations.set(N(n),n)}resolveConversationName(e,r,n){const o=this.connections.get(e.id)?.conversations.get(n);return o||(w(r).find(i=>x(i)===n||N(i)===n)??null)}}function ge(t,e){return JSON.stringify({id:t.id,workDir:t.workDir,managerSessionId:t.managerSessionId,pollIntervalMs:e.pollIntervalMs,forceForeground:b(e),downloadAttachments:e.downloadAttachments!==!1,canReply:e.canReply!==!1,selfTriggerMarker:e.selfTriggerMarker||"",groups:w(e)})}async function H(t){const r=(await le().getRuntimePolicy()).runtime?.current;return t.runtime.foregroundPolicy="work",{enabled:r?.status!=="disabled"}}function B(t){return Number.isFinite(t)?Math.min(6e4,Math.max(1e3,Number(t))):pe}function w(t){return Array.isArray(t.groups)?t.groups.map(e=>String(e?.name||"").trim()).filter(Boolean):[]}function U(t){return String(t.selfNickname||"").split(/[,\n,、]/).map(e=>e.trim()).filter(Boolean)}function we(t,e){return e.length?e.some(r=>{const n=r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`@\\s*${n}(?=$|\\s|[\uFF0C\u3002\uFF01\uFF1F,.!?:\uFF1A\uFF1B;\u3001)])`).test(t)}):!1}function ye(t,e=process.platform){return e!=="win32"?{ok:!1,message:"WeChat RPA Windows visual flow requires Windows"}:{ok:!1,message:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}}function Re(t,e=process.platform){return e!=="darwin"&&e!=="win32"?{ok:!1,message:"WeChat channel product source requires macOS or Windows"}:(Array.isArray(t.groups)?t.groups.map(n=>String(n?.name||"").trim()).filter(Boolean):[]).length?{ok:!0,message:"WeChat channel product source configured"}:{ok:!1,message:"WeChat channel product source requires at least one group"}}async function ve(t,e,r){const n=await j(t.workDir),a=[];for(const o of w(e)){const i=await Se(n,t,e,o);if(r?.(i),!(!i.ok||i.interrupted))for(const u of i.newMessages??[]){const s=X(o,u,e);s&&a.push(s)}}return a}async function Se(t,e,r,n){const a=fe(r.recentLimit),o={kind:a,requestId:`wechat-rpa:${n}:read:${Date.now()}`,targetGroup:n,policy:r.forceForeground?"work":"polite",limit:P(r.recentLimit,A,1,50),attachmentsDir:q(e,r)},i=await t.runWechatRpaReadLatest(o),u=Array.isArray(i.data?.structuredMessages)?i.data.structuredMessages:[],s=i.validation?.deterministic?.latestMessages??[],f=u.length?u.map((d,l)=>Pe(d,i.runId||"lab-read",l)):s.map((d,l)=>({id:`${i.runId||"lab-read"}:${l}`,text:d,confidence:.8}));return{ok:!!i.ok,groupName:n,interrupted:i.status==="interrupted",reason:i.status,rpaRunId:i.runId,rpaTraceSummary:`${a} ${i.status||(i.ok?"success":"failed")} ${n}`,recentMessages:f,newMessages:f,screenshotPath:i.tracePath,error:J(i)}}function Pe(t,e,r){return{id:`${e}:${t.index??r}`,text:String(t.text||t.card?.title||"").trim(),confidence:typeof t.confidence=="number"?t.confidence:.75,senderName:typeof t.senderName=="string"?t.senderName:null,attachments:Ie(t)}}function Ie(t){const e=String(t.kind||"");if(!["image","file","video-file","video-card","link-card","official-account-card","mini-program-card"].includes(e))return[];const r=typeof t.localPath=="string"?t.localPath:void 0;return[{type:e==="video-file"||e==="video-card"?"video":e==="link-card"||e==="official-account-card"||e==="mini-program-card"?"file":e,localPath:r,availability:r?"edge-local":"metadata-only"}]}async function Ae(t,e,r,n,a,o){const i=await j(t.workDir),u=Ce({groupName:r,text:n,attachmentPath:a,dedupeKey:o,requestId:o,policy:"work"}),s=[];for(const h of u){const v=await i.runWechatRpaTask(h);if(s.push(v),!v.ok)break}const f=s.find(h=>h.kind==="send-text"),d=s.find(h=>h.kind==="send-image"||h.kind==="send-video"||h.kind==="send-file"),l=u.length>0&&s.length===u.length&&s.every(h=>h.ok),c=s.some(h=>h.status==="interrupted");return{ok:l,groupName:r,interrupted:c,reason:s.at(-1)?.status,rpaRunId:s.at(-1)?.runId,rpaTraceSummary:`send ${l?"success":s.at(-1)?.status||"failed"} ${r} (${s.length}/${u.length})`,sentReply:!!n,sentReplyObserved:n?!!f?.ok:!1,sentAttachment:!!a,sentAttachmentObserved:a?!!d?.ok:!1,postSendScreenshotPath:s.at(-1)?.tracePath,error:l?void 0:J(s.at(-1))}}function Ce(t){const e=[],r=String(t.text||"").trim(),n=t.policy||"work";if(r&&e.push({kind:"send-text",requestId:t.requestId?`${t.requestId}:text`:void 0,targetGroup:t.groupName,policy:n,text:r,...t.dedupeKey?{dedupeKey:`${t.dedupeKey}:text`}:{}}),t.attachmentPath){const a=ke(t.attachmentPath);e.push({kind:a,requestId:t.requestId?`${t.requestId}:attachment`:void 0,targetGroup:t.groupName,policy:n,filePath:t.attachmentPath,...t.dedupeKey?{dedupeKey:`${t.dedupeKey}:attachment`}:{}})}return e}function ke(t){const e=p.extname(t).toLowerCase();return[".png",".jpg",".jpeg",".gif",".webp",".heic"].includes(e)?"send-image":[".mp4",".mov",".avi",".mkv"].includes(e)?"send-video":"send-file"}async function j(t){const e=z(t);return await import(ee(e).href)}function z(t){const e=p.dirname(Z(import.meta.url)),n=[process.env.SHENNIAN_WECHAT_RPA_LAB_INDEX,p.resolve(e,"../../../../scripts/wechat-rpa-lab/index.mjs"),t?p.join(t,"scripts/wechat-rpa-lab/index.mjs"):"",p.resolve(process.cwd(),"scripts/wechat-rpa-lab/index.mjs")].filter(Boolean).find(a=>m.existsSync(a));if(!n)throw new Error("WeChat RPA Lab API is missing; set SHENNIAN_WECHAT_RPA_LAB_INDEX or run from the repository root");return p.resolve(n)}function J(t){return t?typeof t.error=="string"?t.error:t.error?.message||(t.ok?void 0:`WeChat RPA Lab task ${t.kind||"<unknown>"} ${t.status||"failed"}`):"WeChat RPA Lab did not return a result"}async function be(t,e,r){const n=[];for(const a of w(e)){const o=await F({groupName:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:b(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:q(t,e)});if(r?.(o),!o.interrupted)for(const i of o.newMessages??[]){const u=X(a,i,e);u&&n.push(u)}}return n}async function xe(t,e,r){const n=w(e)[0]||"<unbound>";return r?.({ok:!1,groupName:n,interrupted:!0,reason:"windows-visual-flow-archived",newMessages:[],recentMessages:[],error:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}),[]}function X(t,e,r){const n=String(e.text||"").trim(),a=Array.isArray(e.attachments)?e.attachments:[];if(!n&&a.length===0)return null;const o=typeof e.senderName=="string"?e.senderName.trim():"",i=String(e.observedAt||e.timestampIso||"").trim()||new Date().toISOString();return{conversationName:t,senderName:o||null,text:n,attachments:je(a),observedAt:i,isMentioned:e.isMentioned===!0||we(n,U(r)),rawId:String(e.id||`${t}:${o}:${n}:${i}`)}}async function G(t,e,r,n,a,o){return e.source==="windows-visual-flow"?{ok:!1,groupName:r,interrupted:!0,reason:"windows-visual-flow-archived",newMessages:[],recentMessages:[],error:"WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it"}:e.source==="wechat-rpa-lab"?Ae(t,e,r,n,a,o):F({groupName:r,replyText:n||void 0,attachmentPath:a,scriptPath:e.flowScriptPath,workDir:t.workDir,forceForeground:b(e),noRestore:e.noRestore!==!1,idleSeconds:P(e.idleSeconds,15,0,3600),recentLimit:P(e.recentLimit,A,0,50),downloadAttachmentsDir:q(t,e)})}function Ne(t){return t==="macos-flow"||t==="wechat-rpa-lab"}function V(t,e){return e.text&&e.attachmentPath&&t.sentReplyObserved&&!t.sentAttachmentObserved?{status:t.interrupted?"continue":"queued",text:"",attachmentPath:e.attachmentPath,skipText:!0,reason:t.error||"WeChat RPA sent the text but did not confirm the attachment; queued attachment-only retry"}:{status:"continue",text:e.text,attachmentPath:e.attachmentPath,reason:t.error||""}}function Ee(t,e){return e.idempotencyKey?e.idempotencyKey:I.createHash("sha256").update(`${t.id}
2
2
  ${e.conversationId}
3
3
  ${e.text}
4
4
  ${e.attachment?.name||""}
5
- ${e.attachment?.size||0}`).digest("hex").slice(0,24)}function Te(t,e){return[...Array.from(t.pendingReplies.values()).map(n=>({replyId:n.key,idempotencyKey:n.key,status:"queued",conversationName:n.conversationName,reasonCode:n.lastInterruptedReason??null,nextAttemptAt:null,queuedAt:n.queuedAt,lastAttemptAt:n.lastAttemptAt??null,attemptCount:n.attempts,commitStage:null})),...de({config:e})].slice(0,100)}function E(t,e){const r=t.pendingReplies.get(e.key);t.pendingReplies.set(e.key,{key:e.key,conversationId:e.conversationId,conversationName:e.conversationName,text:e.text,attachmentPath:e.attachmentPath,skipText:e.skipText,queuedAt:r?.queuedAt??new Date().toISOString(),attempts:r?.attempts??0,lastAttemptAt:r?.lastAttemptAt,lastInterruptedReason:e.reason}),v(t)}function Y(t,e,r){t.manualReviewReplies.set(e,{key:e,reason:r,createdAt:t.manualReviewReplies.get(e)?.createdAt??new Date().toISOString()}),t.lastError=r,g(t,{status:"failed",runId:t.lastRunId??null,summary:`manual review required: ${r}`}),v(t)}function W(t,e){const r=$e(e);if(t.pendingStatePath===r)return;t.pendingStatePath=r;const n=Ee(r);for(const a of n.pending??[])We(a)&&(t.pendingReplies.has(a.key)||t.pendingReplies.set(a.key,a));for(const a of n.manualReview??[])Oe(a)&&(t.manualReviewReplies.has(a.key)||t.manualReviewReplies.set(a.key,a));for(const a of n.completedKeys??[])typeof a=="string"&&a&&t.completedPendingReplyKeys.add(a)}function v(t){if(!t.pendingStatePath)return;const e=Array.from(t.completedPendingReplyKeys).slice(-500),r=Array.from(t.pendingReplies.values()).slice(0,500),n=Array.from(t.manualReviewReplies.values()).slice(-500);try{f.mkdirSync(m.dirname(t.pendingStatePath),{recursive:!0}),f.writeFileSync(t.pendingStatePath,JSON.stringify({version:1,pending:r,manualReview:n,completedKeys:e},null,2))}catch{}}function Ee(t){try{const e=JSON.parse(f.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,pending:[],completedKeys:[]}}catch{return{version:1,pending:[],completedKeys:[]}}}function We(t){if(!t||typeof t!="object")return!1;const e=t;return typeof e.key=="string"&&typeof e.conversationId=="string"&&typeof e.conversationName=="string"&&typeof e.text=="string"&&typeof e.queuedAt=="string"&&typeof e.attempts=="number"&&Number.isFinite(e.attempts)&&(e.attachmentPath===void 0||typeof e.attachmentPath=="string")&&(e.skipText===void 0||typeof e.skipText=="boolean")}function Oe(t){if(!t||typeof t!="object")return!1;const e=t;return typeof e.key=="string"&&typeof e.reason=="string"&&typeof e.createdAt=="string"}function $e(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return m.join(t.workDir,".shennian","wechat-rpa-pending-replies",`${e}.json`)}function O(t,e){const r=qe(e);if(t.messageStatePath===r)return;t.messageStatePath=r;const n=_e(r);t.deduper=new K((n.messageIds??[]).filter(a=>typeof a=="string"&&a.length>0))}function Le(t){if(t.messageStatePath)try{f.mkdirSync(m.dirname(t.messageStatePath),{recursive:!0}),f.writeFileSync(t.messageStatePath,JSON.stringify({version:1,messageIds:t.deduper.snapshot()},null,2))}catch{}}function _e(t){try{const e=JSON.parse(f.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,messageIds:[]}}catch{return{version:1,messageIds:[]}}}function qe(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return m.join(t.workDir,".shennian","wechat-rpa-seen-messages",`${e}.json`)}function $(t,e,r){t.lastInterruptedAt=new Date().toISOString(),t.lastError=null,t.consecutiveInterruptions+=1,t.runtimeState="interrupted",De(t)&&(t.interruptionCooldownUntil=Date.now()+T,t.runtimeState="cooldown"),g(t,{status:"interrupted",runId:e.rpaRunId||t.lastRunId||null,summary:r})}function C(t){t.consecutiveInterruptions=0,t.interruptionCooldownUntil=void 0,t.runtimeState="idle_waiting"}function L(t,e){const r=e.postSendScreenshotPath||e.screenshotPath;e.rpaRunId&&(t.lastRunId=e.rpaRunId),r&&(t.lastTracePath=r),t.lastTraceSummary=e.rpaTraceSummary||[e.groupName?`group=${e.groupName}`:"",e.ok===!1?"failed":e.interrupted?"interrupted":"ok",e.reason?`reason=${e.reason}`:""].filter(Boolean).join(" ")}function g(t,e){const r=e.runId||t.lastRunId||`local:${Date.now().toString(36)}`;t.lastRunId=r,t.recentTaskSummaries.unshift({at:new Date().toISOString(),status:e.status,runId:r,summary:_(e.summary)}),t.recentTaskSummaries=t.recentTaskSummaries.slice(0,he)}function k(t){const e=String(t||"").toLowerCase();return/permission|accessibility|screen recording|automation|window|foreground|target group|refusing|requires|安全|权限|窗口|目标群/.test(e)?"blocked":"failed"}function _(t){return String(t||"").replace(/\s+/g," ").trim().slice(0,180)}function Q(t,e){if(b(e))return!1;const r=t.interruptionCooldownUntil;return r?Date.now()<r?!0:(t.interruptionCooldownUntil=void 0,t.consecutiveInterruptions=0,!1):!1}function b(t){return t.forceForeground!==!1}function De(t){return!Number.isFinite(M)||M<=0||!Number.isFinite(T)||T<=0?!1:t.consecutiveInterruptions>=M}function Fe(t){return!t||!f.existsSync(t)?[]:f.readFileSync(t,"utf-8").split(`
6
- `).map(e=>e.trim()).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(e=>!!e)}async function Ke(t,e){if(e.localPath){if(!f.existsSync(e.localPath))throw new Error(`WeChat RPA local attachment does not exist: ${e.localPath}`);return e.localPath}if(e.url)return He(t,e);throw new Error("WeChat RPA attachment requires localPath or url; dataBase64 is not accepted over Manager IPC")}async function He(t,e){if(!e.url||!/^https?:\/\//i.test(e.url))throw new Error("WeChat RPA attachment url must be http(s)");if(Number.isFinite(e.size)&&e.size>S)throw new Error(`WeChat RPA cross-machine attachment is too large: ${e.size} bytes. Max: ${S} bytes.`);const r=await fetch(e.url);if(!r.ok)throw new Error(`WeChat RPA attachment url download failed: ${r.status}`);const n=Number(r.headers.get("content-length")||e.size||0);if(n>S)throw new Error(`WeChat RPA cross-machine attachment is too large: ${n} bytes. Max: ${S} bytes.`);const a=Buffer.from(await r.arrayBuffer());return Be(t,e,a)}function Be(t,e,r){if(!r.byteLength)throw new Error("WeChat RPA attachment is empty");if(r.byteLength>S)throw new Error(`WeChat RPA cross-machine attachment is too large: ${r.byteLength} bytes. Max: ${S} bytes.`);const n=m.join(t,".uploads","wechat-rpa","outbound");f.mkdirSync(n,{recursive:!0});const a=Ue(e.name||"attachment"),o=I.createHash("sha256").update(r).digest("hex").slice(0,12),i=m.extname(a),u=i?a.slice(0,-i.length):a,s=m.join(n,`${u}-${o}${i}`);return f.existsSync(s)||f.writeFileSync(s,r),s}function Ue(t){return m.basename(t||"attachment").normalize("NFKC").replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").replace(/\s+/g," ").replace(/^[ ._]+|[ ._]+$/g,"")||"attachment"}function q(t,e){if(e.downloadAttachments===!1)return;const r=e.downloadAttachmentsDir?.trim();if(r)return m.resolve(r);const n=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return re("wechat-rpa","attachments","inbound",n)}function je(t){if(!Array.isArray(t))return[];const e=process.env.SHENNIAN_MACHINE_ID?.trim();return t.map(r=>r?.localPath?{...r,availability:r.availability||"edge-local",...r.machineId||!e?{}:{machineId:e}}:r)}function P(t,e,r,n){const a=Number(t);return Number.isFinite(a)?Math.min(n,Math.max(r,a)):e}function ze(...t){const e=new Set,r=[];for(const n of t.flat()){const a=`${n.channelId}
5
+ ${e.attachment?.size||0}`).digest("hex").slice(0,24)}function Te(t,e){return[...Array.from(t.pendingReplies.values()).map(n=>({replyId:n.key,idempotencyKey:n.key,status:"queued",conversationName:n.conversationName,reasonCode:n.lastInterruptedReason??null,nextAttemptAt:null,queuedAt:n.queuedAt,lastAttemptAt:n.lastAttemptAt??null,attemptCount:n.attempts,commitStage:null})),...de({config:e})].slice(0,100)}function W(t,e){const r=t.pendingReplies.get(e.key);t.pendingReplies.set(e.key,{key:e.key,conversationId:e.conversationId,conversationName:e.conversationName,text:e.text,attachmentPath:e.attachmentPath,skipText:e.skipText,queuedAt:r?.queuedAt??new Date().toISOString(),attempts:r?.attempts??0,lastAttemptAt:r?.lastAttemptAt,lastInterruptedReason:e.reason}),R(t)}function Y(t,e,r){t.manualReviewReplies.set(e,{key:e,reason:r,createdAt:t.manualReviewReplies.get(e)?.createdAt??new Date().toISOString()}),t.lastError=r,g(t,{status:"failed",runId:t.lastRunId??null,summary:`manual review required: ${r}`}),R(t)}function M(t,e){const r=$e(e);if(t.pendingStatePath===r)return;t.pendingStatePath=r;const n=We(r);for(const a of n.pending??[])Me(a)&&(t.pendingReplies.has(a.key)||t.pendingReplies.set(a.key,a));for(const a of n.manualReview??[])Oe(a)&&(t.manualReviewReplies.has(a.key)||t.manualReviewReplies.set(a.key,a));for(const a of n.completedKeys??[])typeof a=="string"&&a&&t.completedPendingReplyKeys.add(a)}function R(t){if(!t.pendingStatePath)return;const e=Array.from(t.completedPendingReplyKeys).slice(-500),r=Array.from(t.pendingReplies.values()).slice(0,500),n=Array.from(t.manualReviewReplies.values()).slice(-500);try{m.mkdirSync(p.dirname(t.pendingStatePath),{recursive:!0}),m.writeFileSync(t.pendingStatePath,JSON.stringify({version:1,pending:r,manualReview:n,completedKeys:e},null,2))}catch{}}function We(t){try{const e=JSON.parse(m.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,pending:[],completedKeys:[]}}catch{return{version:1,pending:[],completedKeys:[]}}}function Me(t){if(!t||typeof t!="object")return!1;const e=t;return typeof e.key=="string"&&typeof e.conversationId=="string"&&typeof e.conversationName=="string"&&typeof e.text=="string"&&typeof e.queuedAt=="string"&&typeof e.attempts=="number"&&Number.isFinite(e.attempts)&&(e.attachmentPath===void 0||typeof e.attachmentPath=="string")&&(e.skipText===void 0||typeof e.skipText=="boolean")}function Oe(t){if(!t||typeof t!="object")return!1;const e=t;return typeof e.key=="string"&&typeof e.reason=="string"&&typeof e.createdAt=="string"}function $e(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return p.join(t.workDir,".shennian","wechat-rpa-pending-replies",`${e}.json`)}function O(t,e){const r=qe(e);if(t.messageStatePath===r)return;t.messageStatePath=r;const n=_e(r);t.deduper=new K((n.messageIds??[]).filter(a=>typeof a=="string"&&a.length>0))}function Le(t){if(t.messageStatePath)try{m.mkdirSync(p.dirname(t.messageStatePath),{recursive:!0}),m.writeFileSync(t.messageStatePath,JSON.stringify({version:1,messageIds:t.deduper.snapshot()},null,2))}catch{}}function _e(t){try{const e=JSON.parse(m.readFileSync(t,"utf8"));return e&&e.version===1?e:{version:1,messageIds:[]}}catch{return{version:1,messageIds:[]}}}function qe(t){const e=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return p.join(t.workDir,".shennian","wechat-rpa-seen-messages",`${e}.json`)}function $(t,e,r){t.lastInterruptedAt=new Date().toISOString(),t.lastError=null,t.consecutiveInterruptions+=1,t.runtimeState="interrupted",De(t)&&(t.interruptionCooldownUntil=Date.now()+T,t.runtimeState="cooldown"),g(t,{status:"interrupted",runId:e.rpaRunId||t.lastRunId||null,summary:r})}function C(t){t.consecutiveInterruptions=0,t.interruptionCooldownUntil=void 0,t.runtimeState="idle_waiting"}function L(t,e){const r=e.postSendScreenshotPath||e.screenshotPath;e.rpaRunId&&(t.lastRunId=e.rpaRunId),r&&(t.lastTracePath=r),t.lastTraceSummary=e.rpaTraceSummary||[e.groupName?`group=${e.groupName}`:"",e.ok===!1?"failed":e.interrupted?"interrupted":"ok",e.reason?`reason=${e.reason}`:""].filter(Boolean).join(" ")}function g(t,e){const r=e.runId||t.lastRunId||`local:${Date.now().toString(36)}`;t.lastRunId=r,t.recentTaskSummaries.unshift({at:new Date().toISOString(),status:e.status,runId:r,summary:_(e.summary)}),t.recentTaskSummaries=t.recentTaskSummaries.slice(0,me)}function k(t){const e=String(t||"").toLowerCase();return/permission|accessibility|screen recording|automation|window|foreground|target group|refusing|requires|安全|权限|窗口|目标群/.test(e)?"blocked":"failed"}function _(t){return String(t||"").replace(/\s+/g," ").trim().slice(0,180)}function Q(t,e){if(b(e))return!1;const r=t.interruptionCooldownUntil;return r?Date.now()<r?!0:(t.interruptionCooldownUntil=void 0,t.consecutiveInterruptions=0,!1):!1}function b(t){return t.forceForeground!==!1}function De(t){return!Number.isFinite(E)||E<=0||!Number.isFinite(T)||T<=0?!1:t.consecutiveInterruptions>=E}function Fe(t){return!t||!m.existsSync(t)?[]:m.readFileSync(t,"utf-8").split(`
6
+ `).map(e=>e.trim()).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(e=>!!e)}async function Ke(t,e){if(e.localPath){if(!m.existsSync(e.localPath))throw new Error(`WeChat RPA local attachment does not exist: ${e.localPath}`);return e.localPath}if(e.url)return He(t,e);throw new Error("WeChat RPA attachment requires localPath or url; dataBase64 is not accepted over Manager IPC")}async function He(t,e){if(!e.url||!/^https?:\/\//i.test(e.url))throw new Error("WeChat RPA attachment url must be http(s)");if(Number.isFinite(e.size)&&e.size>S)throw new Error(`WeChat RPA cross-machine attachment is too large: ${e.size} bytes. Max: ${S} bytes.`);const r=await fetch(e.url);if(!r.ok)throw new Error(`WeChat RPA attachment url download failed: ${r.status}`);const n=Number(r.headers.get("content-length")||e.size||0);if(n>S)throw new Error(`WeChat RPA cross-machine attachment is too large: ${n} bytes. Max: ${S} bytes.`);const a=Buffer.from(await r.arrayBuffer());return Be(t,e,a)}function Be(t,e,r){if(!r.byteLength)throw new Error("WeChat RPA attachment is empty");if(r.byteLength>S)throw new Error(`WeChat RPA cross-machine attachment is too large: ${r.byteLength} bytes. Max: ${S} bytes.`);const n=p.join(t,".uploads","wechat-rpa","outbound");m.mkdirSync(n,{recursive:!0});const a=Ue(e.name||"attachment"),o=I.createHash("sha256").update(r).digest("hex").slice(0,12),i=p.extname(a),u=i?a.slice(0,-i.length):a,s=p.join(n,`${u}-${o}${i}`);return m.existsSync(s)||m.writeFileSync(s,r),s}function Ue(t){return p.basename(t||"attachment").normalize("NFKC").replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").replace(/\s+/g," ").replace(/^[ ._]+|[ ._]+$/g,"")||"attachment"}function q(t,e){if(e.downloadAttachments===!1)return;const r=e.downloadAttachmentsDir?.trim();if(r)return p.resolve(r);const n=I.createHash("sha256").update(t.id).digest("hex").slice(0,16);return re("wechat-rpa","attachments","inbound",n)}function je(t){if(!Array.isArray(t))return[];const e=process.env.SHENNIAN_MACHINE_ID?.trim();return t.map(r=>r?.localPath?{...r,availability:r.availability||"edge-local",...r.machineId||!e?{}:{machineId:e}}:r)}function P(t,e,r,n){const a=Number(t);return Number.isFinite(a)?Math.min(n,Math.max(r,a)):e}function ze(...t){const e=new Set,r=[];for(const n of t.flat()){const a=`${n.channelId}
7
7
  ${n.conversationId}
8
8
  ${n.messageId}`;e.has(a)||(e.add(a),r.push(n))}return r}export{at as WeChatRpaChannelAdapter,je as annotateWeChatRpaInboundAttachments,Ke as materializeWeChatRpaOutboundAttachment,Ce as planWeChatRpaLabSendTasks,q as resolveWeChatRpaInboundAttachmentDir,fe as selectWeChatRpaLabReadKind,ke as weChatRpaLabTaskKindForAttachment,Re as weChatRpaProductSourceHealth,ye as windowsVisualFlowHealth};
@@ -1,5 +1,5 @@
1
- import s from"chalk";import o from"node:fs";import d from"node:path";import a from"node:os";import{execSync as c,spawn as cn}from"node:child_process";import{randomUUID as an}from"node:crypto";import{fileURLToPath as un}from"node:url";import{getShennianDir as dn,loadConfig as C,resolveShennianPath as h,saveConfig as ln}from"../config/index.js";import{buildAugmentedPath as pn}from"../env-path.js";import{buildWindowsLauncherCommand as mn,buildWindowsScheduledTaskXml as fn,buildWindowsStartupVbs as Sn,escapeXml as N,isWindowsCmdScript as hn,quoteCmdArg as j,quoteSystemdArg as gn}from"./daemon-windows.js";import{findRunningWindowsDaemonProcessIds as yn,isShennianRunServiceCommand as wn}from"./daemon-process.js";import{buildWindowsLauncherCommand as pe,buildWindowsScheduledTaskXml as me,buildWindowsStartupVbs as fe}from"./daemon-windows.js";import{isShennianRunServiceCommand as he}from"./daemon-process.js";const vn=d.dirname(un(import.meta.url)),y=dn(),p=h("daemon.pid"),u=h("daemon.log"),b=h("remote-access.disabled"),I=h("daemon-launcher.json"),W=d.resolve(vn,"../../bin/shennian.js"),F=process.execPath,B=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_NATIVE_FUSION_DISABLED"]);function En(n){return B.has(n)}function l(){const n=a.platform();return n==="darwin"||n==="linux"||n==="win32"?n:"linux"}function L(n){const e=n.replace(/\\/g,"/").toLowerCase(),t=a.tmpdir().replace(/\\/g,"/").toLowerCase();return e.includes("/_npx/")||e.includes("/npm-cache/_npx/")||e.includes("/pnpm/dlx/")||e.startsWith(t.endsWith("/")?t:`${t}/`)}function kn(n){return o.existsSync(n.scriptPath)&&!L(n.scriptPath)?{command:n.nodeExec,args:[n.scriptPath,"run-service"],mode:"direct"}:n.shennianCommandPath&&!L(n.shennianCommandPath)?{command:n.shennianCommandPath,args:["run-service"],mode:"global-shim"}:n.npxPath?{command:n.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:n.nodeExec,args:[n.scriptPath,"run-service"],mode:"direct"}}function Pn(n){const e=n.trim();if(!e)return null;if(e.startsWith("{"))try{const r=JSON.parse(e),i=Number(r.pid);return!Number.isInteger(i)||i<=0?null:{pid:i,...typeof r.instanceId=="string"?{instanceId:r.instanceId}:{},...typeof r.version=="string"?{version:r.version}:{},...r.launcher==="desktop-managed"||r.launcher==="global-cli"||r.launcher==="unknown"?{launcher:r.launcher}:{},...typeof r.startedAt=="string"?{startedAt:r.startedAt}:{},...r.adopted===!0?{adopted:!0}:{}}}catch{return null}const t=parseInt(e,10);return isNaN(t)?null:{pid:t}}function R(){try{return Pn(o.readFileSync(p,"utf-8"))}catch{return null}}function An(){return R()?.pid??null}function w(n){try{return process.kill(n,0),!0}catch{return!1}}function In(n,e=3e3){const t=Date.now();for(;Date.now()-t<e;){if(!w(n))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!w(n)}function Dn(){return o.existsSync(b)}function Nn(){try{o.unlinkSync(b)}catch{}}function V(){return"global-cli"}function bn(){return`${process.pid}-${Date.now()}-${an()}`}function Ln(n,e,t={}){o.mkdirSync(y,{recursive:!0}),o.writeFileSync(p,JSON.stringify({pid:n,instanceId:e,...t.version?{version:t.version}:{},launcher:t.launcher??V(),...t.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function ae(n,e){const t=R();if(!(t?.pid!==n||t.instanceId!==e))try{o.unlinkSync(p)}catch{}}function J(n,e=V(),t){o.mkdirSync(y,{recursive:!0}),o.writeFileSync(I,JSON.stringify({pid:n,launcher:e,...t?{instanceId:t}:{},updatedAt:new Date().toISOString()},null,2))}function O(n){if(n)try{if(JSON.parse(o.readFileSync(I,"utf-8")).instanceId!==n)return}catch{return}try{o.unlinkSync(I)}catch{}}function Rn(n){n&&J(n)}function On(n,e){if(!n||!e)return"unknown";try{const t=JSON.parse(o.readFileSync(I,"utf-8"));if(t.pid!==n)return"unknown";if(t.launcher==="desktop-managed"||t.launcher==="global-cli")return t.launcher}catch{}return T(n)}function T(n){if(l()==="win32")return"unknown";try{const e=c(`ps -p ${n} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(e.includes("/node_modules/shennian/")||e.includes("/bin/shennian")||e.includes(" shennian run-service")||e.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function G(n=process.pid){if(l()==="win32")return yn(n);try{return c("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(t=>{const r=t.trim().match(/^(\d+)\s+(.+)$/);if(!r)return null;const i=Number(r[1]),S=r[2];return!Number.isInteger(i)||i===n?null:wn(S)?i:null}).filter(t=>typeof t=="number")}catch{return[]}}function f(n={}){let e=R(),t=e?.pid??null,r=t!==null&&w(t),i=t!==null&&!r,S=e?.adopted===!0;const v=C();if(i&&n.cleanupStale){try{o.unlinkSync(p)}catch{}e=null,t=null,r=!1,i=!1}if(!r){const g=G()[0];if(g&&(t=g,r=!0,i=!1,S=!0,n.cleanupStale)){const M=bn();Ln(g,M,{launcher:T(g),adopted:!0}),J(g,T(g)),e={pid:g,instanceId:M,adopted:!0}}}return{running:r,pid:t,stale:i,remoteAccessDisabled:Dn(),launcher:On(t,r),platform:l(),shennianDir:y,pidFile:p,logFile:u,...e?.instanceId?{instanceId:e.instanceId}:{},...S?{adopted:!0}:{},...v.machineId?{machineId:v.machineId}:{},paired:!!(v.machineToken&&v.machineId),...v.serverUrl?{serverUrl:v.serverUrl}:{}}}function E(n){process.stdout.write(`${JSON.stringify(n,null,2)}
2
- `)}function K(n){const e=n?.trim();if(!e)return;const t=C();t.serverUrl=e,ln(t)}function X(n){return n?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const x="com.shennian.agent",m=d.join(a.homedir(),"Library/LaunchAgents",`${x}.plist`),Tn=d.join(a.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),$=h("autostart.cmd"),H=h("autostart.vbs"),_=h("autostart.xml"),xn=d.join(Tn,"ShennianAgent.vbs");function q(n){const e=l()==="win32"?`where ${n}`:`command -v ${n}`;try{return c(e,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(i=>i.trim()).find(Boolean)??null}catch{return null}}function $n(){const n=l()==="win32"?"npx.cmd":"npx",e=d.join(d.dirname(F),n);return o.existsSync(e)?e:q("npx")}function k(){return kn({nodeExec:F,scriptPath:W,shennianCommandPath:q("shennian"),npxPath:$n()})}function Y(){try{c(`schtasks /delete /tn "${P}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function z(){try{o.unlinkSync(xn)}catch{}}function Hn(){const n=process.env.USERDOMAIN?.trim(),e=process.env.COMPUTERNAME?.trim(),t=process.env.USERNAME?.trim()||a.userInfo().username;return n&&n.toUpperCase()!=="WORKGROUP"?`${n}\\${t}`:e?`${e}\\${t}`:t}function _n(n){if(l()==="win32"&&hn(n.command)){const e=[n.command,...n.args].map(j).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${e}"`],windowsVerbatimArguments:!0}}return{command:n.command,args:n.args}}function Un(n,e,t=process.env){return{detached:!0,stdio:["ignore",e,e],env:t,windowsHide:!0,...n.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Mn(){const n=k();o.writeFileSync($,mn(n,u)),o.writeFileSync(H,Sn($)),z(),Y();const e=fn({userId:Hn(),command:d.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[j(H)]});o.writeFileSync(_,e,"utf8");try{return c(`schtasks /create /tn "${P}" /xml "${_}" /f`,{stdio:"pipe",windowsHide:!0}),c(`schtasks /run /tn "${P}"`,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function Q(){const n={};for(const e of B)process.env[e]&&(n[e]=process.env[e]);if(n.HOME??=a.homedir(),n.PATH=pn({pathValue:n.PATH,env:n}),n.USER??=a.userInfo().username,l()==="win32"){const e=process.env.TEMP||process.env.TMP||a.tmpdir(),t=a.homedir();n.USERPROFILE??=t;const r=d.win32.parse(t);n.HOMEDRIVE??=r.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",n.HOMEPATH??=t.slice((n.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",n.USERNAME??=process.env.USERNAME||a.userInfo().username,n.COMPUTERNAME??=process.env.COMPUTERNAME||"",n.TMPDIR??=e,n.TEMP??=e,n.TMP??=e}else n.TMPDIR??="/tmp";return n}function Cn(){const n=Q(),e=k(),t=Object.entries(n).map(([i,S])=>` <key>${N(i)}</key>
1
+ import s from"chalk";import o from"node:fs";import d from"node:path";import a from"node:os";import{execSync as c,spawn as cn}from"node:child_process";import{randomUUID as an}from"node:crypto";import{fileURLToPath as un}from"node:url";import{getShennianDir as dn,loadConfig as M,resolveShennianPath as h,saveConfig as ln}from"../config/index.js";import{buildAugmentedPath as pn}from"../env-path.js";import{buildWindowsLauncherCommand as mn,buildWindowsScheduledTaskXml as fn,buildWindowsStartupVbs as Sn,escapeXml as N,isWindowsCmdScript as hn,quoteCmdArg as j,quoteSystemdArg as gn}from"./daemon-windows.js";import{findRunningWindowsDaemonProcessIds as yn,isShennianRunServiceCommand as wn}from"./daemon-process.js";import{buildWindowsLauncherCommand as pe,buildWindowsScheduledTaskXml as me,buildWindowsStartupVbs as fe}from"./daemon-windows.js";import{isShennianRunServiceCommand as he}from"./daemon-process.js";const vn=d.dirname(un(import.meta.url)),y=dn(),p=h("daemon.pid"),u=h("daemon.log"),b=h("remote-access.disabled"),I=h("daemon-launcher.json"),W=d.resolve(vn,"../../bin/shennian.js"),F=process.execPath,B=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_NATIVE_FUSION_DISABLED","SHENNIAN_WECHAT_DELIVERABILITY_TRACE"]);function En(n){return B.has(n)}function l(){const n=a.platform();return n==="darwin"||n==="linux"||n==="win32"?n:"linux"}function L(n){const e=n.replace(/\\/g,"/").toLowerCase(),t=a.tmpdir().replace(/\\/g,"/").toLowerCase();return e.includes("/_npx/")||e.includes("/npm-cache/_npx/")||e.includes("/pnpm/dlx/")||e.startsWith(t.endsWith("/")?t:`${t}/`)}function kn(n){return o.existsSync(n.scriptPath)&&!L(n.scriptPath)?{command:n.nodeExec,args:[n.scriptPath,"run-service"],mode:"direct"}:n.shennianCommandPath&&!L(n.shennianCommandPath)?{command:n.shennianCommandPath,args:["run-service"],mode:"global-shim"}:n.npxPath?{command:n.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:n.nodeExec,args:[n.scriptPath,"run-service"],mode:"direct"}}function Pn(n){const e=n.trim();if(!e)return null;if(e.startsWith("{"))try{const r=JSON.parse(e),i=Number(r.pid);return!Number.isInteger(i)||i<=0?null:{pid:i,...typeof r.instanceId=="string"?{instanceId:r.instanceId}:{},...typeof r.version=="string"?{version:r.version}:{},...r.launcher==="desktop-managed"||r.launcher==="global-cli"||r.launcher==="unknown"?{launcher:r.launcher}:{},...typeof r.startedAt=="string"?{startedAt:r.startedAt}:{},...r.adopted===!0?{adopted:!0}:{}}}catch{return null}const t=parseInt(e,10);return isNaN(t)?null:{pid:t}}function R(){try{return Pn(o.readFileSync(p,"utf-8"))}catch{return null}}function An(){return R()?.pid??null}function w(n){try{return process.kill(n,0),!0}catch{return!1}}function In(n,e=3e3){const t=Date.now();for(;Date.now()-t<e;){if(!w(n))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!w(n)}function Dn(){return o.existsSync(b)}function Nn(){try{o.unlinkSync(b)}catch{}}function V(){return"global-cli"}function bn(){return`${process.pid}-${Date.now()}-${an()}`}function Ln(n,e,t={}){o.mkdirSync(y,{recursive:!0}),o.writeFileSync(p,JSON.stringify({pid:n,instanceId:e,...t.version?{version:t.version}:{},launcher:t.launcher??V(),...t.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function ae(n,e){const t=R();if(!(t?.pid!==n||t.instanceId!==e))try{o.unlinkSync(p)}catch{}}function J(n,e=V(),t){o.mkdirSync(y,{recursive:!0}),o.writeFileSync(I,JSON.stringify({pid:n,launcher:e,...t?{instanceId:t}:{},updatedAt:new Date().toISOString()},null,2))}function O(n){if(n)try{if(JSON.parse(o.readFileSync(I,"utf-8")).instanceId!==n)return}catch{return}try{o.unlinkSync(I)}catch{}}function Rn(n){n&&J(n)}function On(n,e){if(!n||!e)return"unknown";try{const t=JSON.parse(o.readFileSync(I,"utf-8"));if(t.pid!==n)return"unknown";if(t.launcher==="desktop-managed"||t.launcher==="global-cli")return t.launcher}catch{}return T(n)}function T(n){if(l()==="win32")return"unknown";try{const e=c(`ps -p ${n} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(e.includes("/node_modules/shennian/")||e.includes("/bin/shennian")||e.includes(" shennian run-service")||e.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function G(n=process.pid){if(l()==="win32")return yn(n);try{return c("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(t=>{const r=t.trim().match(/^(\d+)\s+(.+)$/);if(!r)return null;const i=Number(r[1]),S=r[2];return!Number.isInteger(i)||i===n?null:wn(S)?i:null}).filter(t=>typeof t=="number")}catch{return[]}}function f(n={}){let e=R(),t=e?.pid??null,r=t!==null&&w(t),i=t!==null&&!r,S=e?.adopted===!0;const v=M();if(i&&n.cleanupStale){try{o.unlinkSync(p)}catch{}e=null,t=null,r=!1,i=!1}if(!r){const g=G()[0];if(g&&(t=g,r=!0,i=!1,S=!0,n.cleanupStale)){const C=bn();Ln(g,C,{launcher:T(g),adopted:!0}),J(g,T(g)),e={pid:g,instanceId:C,adopted:!0}}}return{running:r,pid:t,stale:i,remoteAccessDisabled:Dn(),launcher:On(t,r),platform:l(),shennianDir:y,pidFile:p,logFile:u,...e?.instanceId?{instanceId:e.instanceId}:{},...S?{adopted:!0}:{},...v.machineId?{machineId:v.machineId}:{},paired:!!(v.machineToken&&v.machineId),...v.serverUrl?{serverUrl:v.serverUrl}:{}}}function E(n){process.stdout.write(`${JSON.stringify(n,null,2)}
2
+ `)}function K(n){const e=n?.trim();if(!e)return;const t=M();t.serverUrl=e,ln(t)}function X(n){return n?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const x="com.shennian.agent",m=d.join(a.homedir(),"Library/LaunchAgents",`${x}.plist`),Tn=d.join(a.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),$=h("autostart.cmd"),H=h("autostart.vbs"),_=h("autostart.xml"),xn=d.join(Tn,"ShennianAgent.vbs");function q(n){const e=l()==="win32"?`where ${n}`:`command -v ${n}`;try{return c(e,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(i=>i.trim()).find(Boolean)??null}catch{return null}}function $n(){const n=l()==="win32"?"npx.cmd":"npx",e=d.join(d.dirname(F),n);return o.existsSync(e)?e:q("npx")}function k(){return kn({nodeExec:F,scriptPath:W,shennianCommandPath:q("shennian"),npxPath:$n()})}function Y(){try{c(`schtasks /delete /tn "${P}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function z(){try{o.unlinkSync(xn)}catch{}}function Hn(){const n=process.env.USERDOMAIN?.trim(),e=process.env.COMPUTERNAME?.trim(),t=process.env.USERNAME?.trim()||a.userInfo().username;return n&&n.toUpperCase()!=="WORKGROUP"?`${n}\\${t}`:e?`${e}\\${t}`:t}function _n(n){if(l()==="win32"&&hn(n.command)){const e=[n.command,...n.args].map(j).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${e}"`],windowsVerbatimArguments:!0}}return{command:n.command,args:n.args}}function Un(n,e,t=process.env){return{detached:!0,stdio:["ignore",e,e],env:t,windowsHide:!0,...n.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Cn(){const n=k();o.writeFileSync($,mn(n,u)),o.writeFileSync(H,Sn($)),z(),Y();const e=fn({userId:Hn(),command:d.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[j(H)]});o.writeFileSync(_,e,"utf8");try{return c(`schtasks /create /tn "${P}" /xml "${_}" /f`,{stdio:"pipe",windowsHide:!0}),c(`schtasks /run /tn "${P}"`,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function Q(){const n={};for(const e of B)process.env[e]&&(n[e]=process.env[e]);if(n.HOME??=a.homedir(),n.PATH=pn({pathValue:n.PATH,env:n}),n.USER??=a.userInfo().username,l()==="win32"){const e=process.env.TEMP||process.env.TMP||a.tmpdir(),t=a.homedir();n.USERPROFILE??=t;const r=d.win32.parse(t);n.HOMEDRIVE??=r.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",n.HOMEPATH??=t.slice((n.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",n.USERNAME??=process.env.USERNAME||a.userInfo().username,n.COMPUTERNAME??=process.env.COMPUTERNAME||"",n.TMPDIR??=e,n.TEMP??=e,n.TMP??=e}else n.TMPDIR??="/tmp";return n}function Mn(){const n=Q(),e=k(),t=Object.entries(n).map(([i,S])=>` <key>${N(i)}</key>
3
3
  <string>${N(S)}</string>`).join(`
4
4
  `),r=[e.command,...e.args].map(i=>` <string>${N(i)}</string>`).join(`
5
5
  `);return`<?xml version="1.0" encoding="UTF-8"?>
@@ -41,6 +41,6 @@ StandardOutput=append:${u}
41
41
  StandardError=append:${u}
42
42
 
43
43
  [Install]
44
- WantedBy=default.target`}const P="ShennianAgent";function Wn(n){c(n,{stdio:"pipe",windowsHide:!0})}function Fn(){const n=process.getuid?.();if(typeof n=="number")return n;const e=a.userInfo();return typeof e.uid=="number"?e.uid:e.username}function Z(n=Fn()){return`gui/${n}/${x}`}function Bn(n=Wn){try{return n(`launchctl unload "${m}" 2>/dev/null; launchctl load -w "${m}"`),n(`launchctl kickstart -k "${Z()}"`),!0}catch{try{return n(`launchctl load -w "${m}"`),n(`launchctl kickstart -k "${Z()}"`),!0}catch{return!1}}}function Vn(){o.mkdirSync(y,{recursive:!0});const n={};for(const[e,t]of Object.entries(process.env))t!==void 0&&En(e)&&(n[e]=t);o.writeFileSync(h("env.json"),JSON.stringify(n,null,2))}function nn(n={}){o.mkdirSync(y,{recursive:!0});const e=f({cleanupStale:!0});if(e.pid!==null&&e.running&&!e.adopted){n.quiet||console.log(s.green(`\u2713 Background service already running (PID ${e.pid})`));return}en(e);const t=o.openSync(u,"a"),r=_n(k()),i=cn(r.command,r.args,Un(r,t));i.unref(),o.closeSync(t),Rn(i.pid),n.quiet||(console.log(s.green(`\u2713 Background service started (PID ${i.pid})`)),console.log(s.gray(` Logs: ${u}`)))}function en(n=f({cleanupStale:!0})){if(!(n.pid===null||!n.running||!n.adopted))try{process.kill(n.pid,"SIGTERM"),In(n.pid);try{o.unlinkSync(p)}catch{}}catch{}}function tn(){o.mkdirSync(y,{recursive:!0});const n=l();switch(k().mode==="direct"&&L(W)&&console.warn(s.yellow("\u26A0 Warning: Current CLI path is temporary (npx). Auto-start may not work after reboot.\n Run `npm install -g shennian@latest` for reliable auto-start.")),n){case"darwin":{const t=d.dirname(m);return o.mkdirSync(t,{recursive:!0}),o.writeFileSync(m,Cn()),Bn()}case"linux":{const t=d.dirname(D);o.mkdirSync(t,{recursive:!0}),o.writeFileSync(D,jn());try{c("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{c(`loginctl enable-linger ${a.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return c("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return Mn()}}function Jn(){const e=f({cleanupStale:!0}).pid;if(e===null)return{};if(!w(e)){try{o.unlinkSync(p)}catch{}return{stalePid:e}}try{return process.kill(e,"SIGTERM"),{stoppedPid:e}}catch(t){return{error:t instanceof Error?t.message:String(t)}}}async function Gn(n=5e3){const e=Jn();if(!e.stoppedPid)return e;if(!await A(e.stoppedPid,n))try{process.kill(e.stoppedPid,"SIGKILL"),await A(e.stoppedPid,2e3)}catch(r){return{...e,error:r instanceof Error?r.message:String(r)}}try{o.unlinkSync(p)}catch{}O();for(const r of G())if(r!==e.stoppedPid)try{process.kill(r,"SIGTERM"),await A(r,2e3)}catch{}return e}function Kn(n={}){K(X(n.api)),Nn(),en(),tn()||nn({quiet:n.json}),n.json?E(f()):console.log(s.green("\u2713 Background service started"))}async function Xn(n={}){switch(o.mkdirSync(y,{recursive:!0}),o.writeFileSync(b,new Date().toISOString()),l()){case"darwin":{if(o.existsSync(m))try{c(`launchctl unload -w "${m}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}case"linux":{try{c("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}break}case"win32":{try{c(`schtasks /change /tn "${P}" /disable`,{stdio:"pipe",windowsHide:!0})}catch{}try{c(`schtasks /end /tn "${P}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}}const t=await Gn(),r=f({cleanupStale:!0});if(n.json){E(t.error?{...r,error:t.error}:r);return}t.error?console.error(s.red(`\u2717 Failed to stop: ${t.error}`)):console.log(s.green("\u2713 Background service stopped"))}function rn(n){Vn(),Kn(n)}async function U(n={}){await Xn(n)}async function A(n,e=5e3){const t=Date.now();for(;Date.now()-t<e;){if(!w(n))return!0;await new Promise(r=>setTimeout(r,100))}return!w(n)}async function qn(n={}){K(X(n.api));const e=An();if(e!==null&&w(e))try{process.kill(e,"SIGTERM"),await A(e)||(process.kill(e,"SIGKILL"),await A(e,2e3));try{o.unlinkSync(p)}catch{}O(),n.json||console.log(s.green(`\u2713 Background service stopped (PID ${e})`))}catch(t){const r=t instanceof Error?t.message:String(t);n.json?E({...f(),error:r}):console.error(s.red(`\u2717 Failed to stop: ${r}`));return}else if(e!==null){try{o.unlinkSync(p)}catch{}O(),n.json||console.log(s.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${e}`))}else n.json||console.log(s.gray("\u25CF Background service not running, starting it now"));if(l()==="linux"&&tn()){n.json&&E(f());return}nn({quiet:n.json}),n.json&&E(f())}function on(n={}){const e=f({cleanupStale:!0});if(n.json){E(e);return}if(e.pid===null){console.log(s.gray("\u25CF Background service not running"));return}e.running?(console.log(s.green(`\u25CF Background service running (PID ${e.pid})`)),console.log(s.gray(` Logs: ${u}`))):console.log(s.yellow(`\u25CF Background service stopped (PID ${e.pid} is stale)`))}function sn(n){if(!o.existsSync(u)){console.log(s.gray("No logs yet"));return}try{const e=c(a.platform()==="win32"?`powershell Get-Content -Tail ${n.lines} "${u}"`:`tail -n ${n.lines} "${u}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(e)}catch{const e=o.readFileSync(u,"utf-8").split(`
44
+ WantedBy=default.target`}const P="ShennianAgent";function Wn(n){c(n,{stdio:"pipe",windowsHide:!0})}function Fn(){const n=process.getuid?.();if(typeof n=="number")return n;const e=a.userInfo();return typeof e.uid=="number"?e.uid:e.username}function Z(n=Fn()){return`gui/${n}/${x}`}function Bn(n=Wn){try{return n(`launchctl unload "${m}" 2>/dev/null; launchctl load -w "${m}"`),n(`launchctl kickstart -k "${Z()}"`),!0}catch{try{return n(`launchctl load -w "${m}"`),n(`launchctl kickstart -k "${Z()}"`),!0}catch{return!1}}}function Vn(){o.mkdirSync(y,{recursive:!0});const n={};for(const[e,t]of Object.entries(process.env))t!==void 0&&En(e)&&(n[e]=t);o.writeFileSync(h("env.json"),JSON.stringify(n,null,2))}function nn(n={}){o.mkdirSync(y,{recursive:!0});const e=f({cleanupStale:!0});if(e.pid!==null&&e.running&&!e.adopted){n.quiet||console.log(s.green(`\u2713 Background service already running (PID ${e.pid})`));return}en(e);const t=o.openSync(u,"a"),r=_n(k()),i=cn(r.command,r.args,Un(r,t));i.unref(),o.closeSync(t),Rn(i.pid),n.quiet||(console.log(s.green(`\u2713 Background service started (PID ${i.pid})`)),console.log(s.gray(` Logs: ${u}`)))}function en(n=f({cleanupStale:!0})){if(!(n.pid===null||!n.running||!n.adopted))try{process.kill(n.pid,"SIGTERM"),In(n.pid);try{o.unlinkSync(p)}catch{}}catch{}}function tn(){o.mkdirSync(y,{recursive:!0});const n=l();switch(k().mode==="direct"&&L(W)&&console.warn(s.yellow("\u26A0 Warning: Current CLI path is temporary (npx). Auto-start may not work after reboot.\n Run `npm install -g shennian@latest` for reliable auto-start.")),n){case"darwin":{const t=d.dirname(m);return o.mkdirSync(t,{recursive:!0}),o.writeFileSync(m,Mn()),Bn()}case"linux":{const t=d.dirname(D);o.mkdirSync(t,{recursive:!0}),o.writeFileSync(D,jn());try{c("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{c(`loginctl enable-linger ${a.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return c("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return Cn()}}function Jn(){const e=f({cleanupStale:!0}).pid;if(e===null)return{};if(!w(e)){try{o.unlinkSync(p)}catch{}return{stalePid:e}}try{return process.kill(e,"SIGTERM"),{stoppedPid:e}}catch(t){return{error:t instanceof Error?t.message:String(t)}}}async function Gn(n=5e3){const e=Jn();if(!e.stoppedPid)return e;if(!await A(e.stoppedPid,n))try{process.kill(e.stoppedPid,"SIGKILL"),await A(e.stoppedPid,2e3)}catch(r){return{...e,error:r instanceof Error?r.message:String(r)}}try{o.unlinkSync(p)}catch{}O();for(const r of G())if(r!==e.stoppedPid)try{process.kill(r,"SIGTERM"),await A(r,2e3)}catch{}return e}function Kn(n={}){K(X(n.api)),Nn(),en(),tn()||nn({quiet:n.json}),n.json?E(f()):console.log(s.green("\u2713 Background service started"))}async function Xn(n={}){switch(o.mkdirSync(y,{recursive:!0}),o.writeFileSync(b,new Date().toISOString()),l()){case"darwin":{if(o.existsSync(m))try{c(`launchctl unload -w "${m}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}case"linux":{try{c("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}break}case"win32":{try{c(`schtasks /change /tn "${P}" /disable`,{stdio:"pipe",windowsHide:!0})}catch{}try{c(`schtasks /end /tn "${P}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}}const t=await Gn(),r=f({cleanupStale:!0});if(n.json){E(t.error?{...r,error:t.error}:r);return}t.error?console.error(s.red(`\u2717 Failed to stop: ${t.error}`)):console.log(s.green("\u2713 Background service stopped"))}function rn(n){Vn(),Kn(n)}async function U(n={}){await Xn(n)}async function A(n,e=5e3){const t=Date.now();for(;Date.now()-t<e;){if(!w(n))return!0;await new Promise(r=>setTimeout(r,100))}return!w(n)}async function qn(n={}){K(X(n.api));const e=An();if(e!==null&&w(e))try{process.kill(e,"SIGTERM"),await A(e)||(process.kill(e,"SIGKILL"),await A(e,2e3));try{o.unlinkSync(p)}catch{}O(),n.json||console.log(s.green(`\u2713 Background service stopped (PID ${e})`))}catch(t){const r=t instanceof Error?t.message:String(t);n.json?E({...f(),error:r}):console.error(s.red(`\u2717 Failed to stop: ${r}`));return}else if(e!==null){try{o.unlinkSync(p)}catch{}O(),n.json||console.log(s.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${e}`))}else n.json||console.log(s.gray("\u25CF Background service not running, starting it now"));if(l()==="linux"&&tn()){n.json&&E(f());return}nn({quiet:n.json}),n.json&&E(f())}function on(n={}){const e=f({cleanupStale:!0});if(n.json){E(e);return}if(e.pid===null){console.log(s.gray("\u25CF Background service not running"));return}e.running?(console.log(s.green(`\u25CF Background service running (PID ${e.pid})`)),console.log(s.gray(` Logs: ${u}`))):console.log(s.yellow(`\u25CF Background service stopped (PID ${e.pid} is stale)`))}function sn(n){if(!o.existsSync(u)){console.log(s.gray("No logs yet"));return}try{const e=c(a.platform()==="win32"?`powershell Get-Content -Tail ${n.lines} "${u}"`:`tail -n ${n.lines} "${u}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(e)}catch{const e=o.readFileSync(u,"utf-8").split(`
45
45
  `).slice(-n.lines);console.log(e.join(`
46
46
  `))}}async function Yn(){switch(await U(),l()){case"darwin":{if(o.existsSync(m)){try{c(`launchctl unload -w "${m}"`,{stdio:"pipe",windowsHide:!0})}catch{}o.unlinkSync(m),console.log(s.green("\u2713 Auto-start uninstalled (launchd)"))}break}case"linux":{try{c("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(o.existsSync(D)){o.unlinkSync(D);try{c("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(s.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{Y();try{o.unlinkSync($)}catch{}try{o.unlinkSync(H)}catch{}try{o.unlinkSync(_)}catch{}z(),console.log(s.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function ue(n){n.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>rn(r)),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(r=>U(r)),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>on(r)),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>sn({lines:parseInt(r.lines,10)}));const e=n.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),t=(r,i)=>{i||console.error(s.yellow(`\u26A0 Deprecated command. Use \`shennian ${r}\` instead.`))};e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("start",r.json),rn(r)}),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async r=>{t("stop",r.json),await U(r)}),e.command("restart").description("Restart the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("stop && shennian start",r.json),qn(r)}),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>{t("status",r.json),on(r)}),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>{t("logs"),sn({lines:parseInt(r.lines,10)})}),e.command("uninstall").description("Uninstall auto-start service").action(Yn)}export{Un as buildDaemonSpawnOptions,pe as buildWindowsLauncherCommand,me as buildWindowsScheduledTaskXml,fe as buildWindowsStartupVbs,Q as captureEnvForService,O as clearDaemonLauncher,ae as clearDaemonPidIfOwner,Nn as clearRemoteAccessDisabled,bn as createDaemonInstanceId,G as findRunningDaemonProcessIds,V as getCurrentProcessDaemonLauncher,f as getDaemonStatus,Z as getLaunchdServiceTarget,tn as installService,L as isEphemeralCliPath,Dn as isRemoteAccessDisabled,En as isSafeSnapshotEnvKey,he as isShennianRunServiceCommand,Rn as recordStartedDaemon,ue as registerDaemonCommand,Bn as reloadLaunchdAgent,kn as resolveServiceLaunchSpec,Vn as saveEnvSnapshot,nn as startDaemonProcess,J as writeDaemonLauncher,Ln as writeDaemonPid};
@@ -1,2 +1,2 @@
1
- import p from"node:fs";import f from"node:path";import{observedMessageToExternalEvent as P,weChatChannelBindingId as g}from"../../channels/wechat-rpa/product-channel.js";import{loadConfig as v}from"../../config/index.js";import{runWeChatChannelActionSmoke as w}from"../../devtools/wechat-channel-action-smoke.js";import{WeChatToolOperationError as k}from"./types.js";import{currentWeChatPlatform as E,isRecord as y,normalizeSendStatus as S,safePathSegment as A}from"./utils.js";async function B(e,t){const r=T(t.attachment),o=_(e,t,{kind:"send",steps:["open-read",r],text:t.text,attachment:t.attachment}),s=await w(o),a=s.steps.find(c=>c.step===r);if(!s.ok||!a?.ok){if(O(a))return{pending:!0,sendStatus:"sent_unconfirmed",outDir:s.outDir,helperTracePath:s.helperTracePath};const c=s.steps.find(h=>!h.ok&&!h.skipped),i=a?.reasonCode==="open_read_required"?c??a:a??c,u=i?.reasonCode||"wechat_direct_send_failed",m=i?.errorSummary||u;throw new k({reasonCode:u,message:m,outDir:s.outDir,helperTracePath:s.helperTracePath,traceId:t.traceId})}const n=S(a.details?.sendStatus)??"sent_unconfirmed";return{pending:n==="queued"||n==="pending"||n==="sent_unconfirmed",sendStatus:n,outDir:s.outDir,helperTracePath:s.helperTracePath}}async function H(e,t){const r=[],o=t.download!=="never",s=o?"download-visible-media":"structure-window",a=_(e,t,{kind:"read",steps:[s],allowEmptyMedia:o,activityGuardEvents:r}),n=await w(a),c=r.length>0?N(n.outDir,r):null,i=n.steps.find(d=>d.step===s),u=f.join(n.outDir,o?"download-visible-messages.json":"structure-window-messages.json"),m=W(u),h=o&&m.length>0&&(i?.reasonCode==="media_original_pending_or_metadata_only"||i?.reasonCode==="only_preview_media_downloaded");if(!n.ok&&i?.reasonCode!=="no_observed_messages"&&!h){const d=i?.reasonCode||n.steps.find(l=>!l.ok&&!l.skipped)?.reasonCode||"wechat_direct_read_failed",I=i?.errorSummary||n.steps.find(l=>!l.ok&&!l.skipped)?.errorSummary||d;throw new k({reasonCode:d,message:I,outDir:n.outDir,helperTracePath:n.helperTracePath,activityGuardPath:c,traceId:t.traceId})}const C=x(e,t),D=M(e);return{messages:m.map(d=>P(C,D,d)).filter(d=>d!=null),outDir:n.outDir,helperTracePath:n.helperTracePath,activityGuardPath:c}}function T(e){return e?e.kind==="image"?"send-image":e.kind==="video"?"send-video":"send-file":"send-text"}function _(e,t,r){const o=t.traceId||`wechat-cli-${r.kind}-${Date.now().toString(36)}`,s=f.join(e.workDir,"runs",A(o)),a=r.attachment;return{conversation:e.conversationName,mode:"custom",steps:r.steps,outDir:s,workDir:e.workDir,traceId:o,runtimeId:e.channelId,machineId:process.env.SHENNIAN_MACHINE_ID||v().machineId||"local",sessionId:e.sessionId,serverUrl:v().serverUrl,platform:E(),text:r.text,...a?.localPath&&a.kind==="image"?{imagePath:a.localPath}:{},...a?.localPath&&a.kind==="video"?{videoPath:a.localPath}:{},...a?.localPath&&a.kind!=="image"&&a.kind!=="video"?{filePath:a.localPath}:{},allowEmptyMedia:r.allowEmptyMedia,...r.kind==="read"?{activityGuard:{useAutomationLease:!0,onEvent:r.activityGuardEvents?n=>r.activityGuardEvents?.push(n):void 0}}:{},skipRuntimeUpsert:!0,requireServer:!1}}function N(e,t){const r=f.join(e,"activity-guard.json");return p.mkdirSync(f.dirname(r),{recursive:!0}),p.writeFileSync(r,`${JSON.stringify({schema:"wechat-direct-read-activity-guard/v1",events:t},null,2)}
2
- `,"utf8"),r}function x(e,t){return{id:e.channelId,type:"wechat-rpa",name:`WeChat ${e.conversationName}`,sessionId:e.sessionId,managerSessionId:e.sessionId,workDir:e.workDir,enabled:!0,secretRef:`wechat-cli:${e.channelId}`}}function M(e){return{bindingId:g(e.channelId,e.conversationName),sessionId:e.sessionId,conversationDisplayName:e.conversationName,enabled:!0,allowReply:!0,downloadMedia:!0}}function W(e){try{const t=JSON.parse(p.readFileSync(e,"utf-8"));return Array.isArray(t)?t.filter(y):[]}catch{return[]}}function O(e){if(e?.reasonCode!=="sent_text_not_observed")return!1;const t=y(e.details)?e.details:{},r=t.sendAccepted===!0,o=S(t.sendStatus);return r&&(o==="sent_unconfirmed"||o==="sent"||o==="ok")}export{H as readDirectWeChatLatestOnce,B as sendDirectWeChatMessageOnce};
1
+ import p from"node:fs";import f from"node:path";import{observedMessageToExternalEvent as P,weChatChannelBindingId as g}from"../../channels/wechat-rpa/product-channel.js";import{loadConfig as v}from"../../config/index.js";import{runWeChatChannelActionSmoke as w}from"../../devtools/wechat-channel-action-smoke.js";import{WeChatToolOperationError as k}from"./types.js";import{currentWeChatPlatform as E,isPendingSendStatus as A,isRecord as y,normalizeSendStatus as S,safePathSegment as T}from"./utils.js";async function H(e,t){const r=N(t.attachment),o=_(e,t,{kind:"send",steps:["open-read",r],text:t.text,attachment:t.attachment}),s=await w(o),a=s.steps.find(c=>c.step===r);if(!s.ok||!a?.ok){if(j(a))return{pending:!1,sendStatus:"sent_unconfirmed",outDir:s.outDir,helperTracePath:s.helperTracePath};const c=s.steps.find(h=>!h.ok&&!h.skipped),i=a?.reasonCode==="open_read_required"?c??a:a??c,u=i?.reasonCode||"wechat_direct_send_failed",m=i?.errorSummary||u;throw new k({reasonCode:u,message:m,outDir:s.outDir,helperTracePath:s.helperTracePath,traceId:t.traceId})}const n=S(a.details?.sendStatus)??"sent_unconfirmed";return{pending:A(n),sendStatus:n,outDir:s.outDir,helperTracePath:s.helperTracePath}}async function J(e,t){const r=[],o=t.download!=="never",s=o?"download-visible-media":"structure-window",a=_(e,t,{kind:"read",steps:[s],allowEmptyMedia:o,activityGuardEvents:r}),n=await w(a),c=r.length>0?x(n.outDir,r):null,i=n.steps.find(d=>d.step===s),u=f.join(n.outDir,o?"download-visible-messages.json":"structure-window-messages.json"),m=O(u),h=o&&m.length>0&&(i?.reasonCode==="media_original_pending_or_metadata_only"||i?.reasonCode==="only_preview_media_downloaded");if(!n.ok&&i?.reasonCode!=="no_observed_messages"&&!h){const d=i?.reasonCode||n.steps.find(l=>!l.ok&&!l.skipped)?.reasonCode||"wechat_direct_read_failed",I=i?.errorSummary||n.steps.find(l=>!l.ok&&!l.skipped)?.errorSummary||d;throw new k({reasonCode:d,message:I,outDir:n.outDir,helperTracePath:n.helperTracePath,activityGuardPath:c,traceId:t.traceId})}const C=M(e,t),D=W(e);return{messages:m.map(d=>P(C,D,d)).filter(d=>d!=null),outDir:n.outDir,helperTracePath:n.helperTracePath,activityGuardPath:c}}function N(e){return e?e.kind==="image"?"send-image":e.kind==="video"?"send-video":"send-file":"send-text"}function _(e,t,r){const o=t.traceId||`wechat-cli-${r.kind}-${Date.now().toString(36)}`,s=f.join(e.workDir,"runs",T(o)),a=r.attachment;return{conversation:e.conversationName,mode:"custom",steps:r.steps,outDir:s,workDir:e.workDir,traceId:o,runtimeId:e.channelId,machineId:process.env.SHENNIAN_MACHINE_ID||v().machineId||"local",sessionId:e.sessionId,serverUrl:v().serverUrl,platform:E(),text:r.text,...a?.localPath&&a.kind==="image"?{imagePath:a.localPath}:{},...a?.localPath&&a.kind==="video"?{videoPath:a.localPath}:{},...a?.localPath&&a.kind!=="image"&&a.kind!=="video"?{filePath:a.localPath}:{},allowEmptyMedia:r.allowEmptyMedia,...r.kind==="read"?{activityGuard:{useAutomationLease:!0,onEvent:r.activityGuardEvents?n=>r.activityGuardEvents?.push(n):void 0}}:{},skipRuntimeUpsert:!0,requireServer:!1}}function x(e,t){const r=f.join(e,"activity-guard.json");return p.mkdirSync(f.dirname(r),{recursive:!0}),p.writeFileSync(r,`${JSON.stringify({schema:"wechat-direct-read-activity-guard/v1",events:t},null,2)}
2
+ `,"utf8"),r}function M(e,t){return{id:e.channelId,type:"wechat-rpa",name:`WeChat ${e.conversationName}`,sessionId:e.sessionId,managerSessionId:e.sessionId,workDir:e.workDir,enabled:!0,secretRef:`wechat-cli:${e.channelId}`}}function W(e){return{bindingId:g(e.channelId,e.conversationName),sessionId:e.sessionId,conversationDisplayName:e.conversationName,enabled:!0,allowReply:!0,downloadMedia:!0}}function O(e){try{const t=JSON.parse(p.readFileSync(e,"utf-8"));return Array.isArray(t)?t.filter(y):[]}catch{return[]}}function j(e){if(e?.reasonCode!=="sent_text_not_observed")return!1;const t=y(e.details)?e.details:{},r=t.sendAccepted===!0,o=S(t.sendStatus);return r&&(o==="sent_unconfirmed"||o==="sent"||o==="ok")}export{J as readDirectWeChatLatestOnce,H as sendDirectWeChatMessageOnce};
@@ -1,4 +1,4 @@
1
- import{readDirectWeChatLatestOnce as D,sendDirectWeChatMessageOnce as P}from"./direct.js";import{buildWeChatToolBinding as w,ensureWeChatToolChannel as y,ipc as g,resolveWeChatManagerIpc as C,shouldFallbackManagerToolToDirect as p,trySyncWeChatToolChannel as R}from"./ipc.js";import{clampPositiveInteger as k,isExternalMessageEvent as N,isRecord as v,normalizeSendStatus as T,stableId as W,stringValue as u}from"./utils.js";async function S(t){const s=t.text.trim();if(!s&&!t.attachment)throw new Error("Message text or attachment is required");const e=w(t),n=t.attachment?b(t.attachment):void 0;if(t.dryRun)return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:e.workDir,helperTracePath:null,sendStatus:"dry_run",pending:!1,attachment:n,attachments:n?[n]:[],dryRun:!0,reasonCode:null,message:"Dry run completed. No WeChat message was sent."};const d=C(t);if(!d){const r=t.directRuntime?await t.directRuntime.send(e,t):await P(e,t);return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:r.outDir??e.workDir,helperTracePath:r.helperTracePath??null,sendStatus:r.sendStatus??(r.pending?"pending":"sent"),pending:r.pending===!0,attachment:n,attachments:n?[n]:[],reasonCode:null,message:r.pending?"WeChat message is queued or waiting for confirmation.":"WeChat message sent."}}if(d.mode==="tool"){let r;try{r=await g(d.ctx,"/wechat-rpa/tool/send",{managerSessionId:e.sessionId,binding:e,text:s,attachment:t.attachment,traceId:t.traceId,timeoutMs:t.timeoutMs},t.fetchImpl,t.timeoutMs)}catch(h){if(p(t,h))return S({...t,transport:"direct"});throw h}const l=T(r.sendStatus)??(r.pending===!0?"pending":"sent");return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:u(r.outDir)||e.workDir,helperTracePath:u(r.helperTracePath)||null,sendStatus:l,pending:r.pending===!0,attachment:n,attachments:n?[n]:[],reasonCode:null,message:r.pending?"WeChat message is queued or waiting for confirmation.":"WeChat message sent."}}const m=d.ctx;await y(m,e,t);const c=t.idempotencyKey||W("wechat-cli-send",`${e.channelId}
1
+ import{readDirectWeChatLatestOnce as P,sendDirectWeChatMessageOnce as R}from"./direct.js";import{buildWeChatToolBinding as y,ensureWeChatToolChannel as C,ipc as g,resolveWeChatManagerIpc as N,shouldFallbackManagerToolToDirect as p,trySyncWeChatToolChannel as S}from"./ipc.js";import{clampPositiveInteger as k,isExternalMessageEvent as D,isPendingSendStatus as v,isRecord as f,normalizeSendStatus as T,stableId as W,stringValue as u}from"./utils.js";async function M(t){const r=t.text.trim();if(!r&&!t.attachment)throw new Error("Message text or attachment is required");const e=y(t),n=t.attachment?A(t.attachment):void 0;if(t.dryRun)return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:e.workDir,helperTracePath:null,sendStatus:"dry_run",pending:!1,attachment:n,attachments:n?[n]:[],dryRun:!0,reasonCode:null,message:"Dry run completed. No WeChat message was sent."};const d=N(t);if(!d){const s=t.directRuntime?await t.directRuntime.send(e,t):await R(e,t);return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:s.outDir??e.workDir,helperTracePath:s.helperTracePath??null,sendStatus:s.sendStatus??(s.pending?"pending":"sent"),pending:s.pending===!0,attachment:n,attachments:n?[n]:[],reasonCode:null,message:s.pending?"WeChat message is queued for local sending; do not resend.":"WeChat message sent; do not resend."}}if(d.mode==="tool"){let s;try{s=await g(d.ctx,"/wechat-rpa/tool/send",{managerSessionId:e.sessionId,binding:e,text:r,attachment:t.attachment,traceId:t.traceId,timeoutMs:t.timeoutMs},t.fetchImpl,t.timeoutMs)}catch(h){if(p(t,h))return M({...t,transport:"direct"});throw h}const c=T(s.sendStatus)??(s.pending===!0?"pending":"sent");return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:u(s.outDir)||e.workDir,helperTracePath:u(s.helperTracePath)||null,sendStatus:c,pending:v(c),attachment:n,attachments:n?[n]:[],reasonCode:null,message:v(c)?"WeChat message is queued for local sending; do not resend.":"WeChat message sent; do not resend."}}const m=d.ctx;await C(m,e,t);const i=t.idempotencyKey||W("wechat-cli-send",`${e.channelId}
2
2
  ${e.conversationId}
3
- ${s}
4
- ${Date.now()}`),i=await g(m,"/external/reply",{managerSessionId:e.sessionId,channelId:e.channelId,conversationId:e.conversationId,text:s,attachment:t.attachment,idempotencyKey:c},t.fetchImpl,t.timeoutMs),o=await R(m,e,t),a=o?x(o,c,i.pending===!0):i.pending===!0?"queued":"ok";return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:e.workDir,helperTracePath:null,sendStatus:a,pending:i.pending===!0&&(a==="queued"||a==="pending"),attachment:n,attachments:n?[n]:[],reasonCode:null,message:o?a==="confirmed_echo"?"WeChat message sent and confirmed.":a==="manual_review"?"WeChat message was submitted but needs manual review.":"WeChat message submitted to local daemon.":"WeChat message submitted to local daemon; confirmation sync timed out."}}async function M(t){const s=k(t.limit,10),e=w(t),n=C(t);if(!n){const a=t.directRuntime?{messages:await t.directRuntime.readLatest(e,{...t,limit:s,recentLimit:t.recentLimit??s}),outDir:e.workDir,helperTracePath:null,activityGuardPath:null}:await D(e,{...t,limit:s,recentLimit:t.recentLimit??s}),r=a.messages.filter(h=>h.conversationId===e.conversationId||h.conversationName===e.conversationName).slice(-s),l=f(r);return{ok:!0,operation:"read",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:a.outDir,helperTracePath:a.helperTracePath,activityGuardPath:a.activityGuardPath??null,messages:r,attachments:l,count:r.length,reasonCode:null,message:`Read ${r.length} WeChat message(s).`}}if(n.mode==="tool"){let a;try{a=await g(n.ctx,"/wechat-rpa/tool/read",{managerSessionId:e.sessionId,binding:e,limit:s,download:t.download,traceId:t.traceId,timeoutMs:t.timeoutMs},t.fetchImpl,t.timeoutMs)}catch(I){if(p(t,I))return M({...t,transport:"direct"});throw I}const l=(Array.isArray(a.messages)?a.messages.filter(N):[]).filter(I=>I.conversationId===e.conversationId||I.conversationName===e.conversationName).slice(-s),h=f(l);return{ok:!0,operation:"read",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:u(a.outDir)||e.workDir,helperTracePath:u(a.helperTracePath)||null,activityGuardPath:u(a.activityGuardPath)||null,messages:l,attachments:h,count:l.length,reasonCode:null,message:`Read ${l.length} WeChat message(s).`}}const d=n.ctx;await y(d,e,{...t,recentLimit:t.recentLimit??s});const m=await g(d,"/wechat-rpa/channel/sync",{managerSessionId:e.sessionId},t.fetchImpl,t.timeoutMs),i=(Array.isArray(m.messages)?m.messages.filter(N):[]).filter(a=>a.conversationId===e.conversationId||a.conversationName===e.conversationName).slice(-s),o=f(i);return{ok:!0,operation:"read",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:e.workDir,helperTracePath:null,activityGuardPath:null,messages:i,attachments:o,count:i.length,reasonCode:null,message:`Read ${i.length} WeChat message(s).`}}function x(t,s,e){const n=v(t.channel)?t.channel:{},m=(Array.isArray(n.wechatRpaPendingReplies)?n.wechatRpaPendingReplies:[]).filter(v).find(a=>u(a.idempotencyKey)===s),c=u(m?.status);if(c==="confirmed_echo"||c==="sent_unconfirmed"||c==="manual_review"||c==="queued")return c;const o=(Array.isArray(n.wechatRpaRecentTaskSummaries)?n.wechatRpaRecentTaskSummaries:[]).filter(v).map(a=>u(a.status)).find(Boolean);return o==="sent"?"sent":o==="confirmed_echo"?"confirmed_echo":o==="manual_review"?"manual_review":e?"pending":"ok"}function b(t){return{kind:t.kind,name:t.name,mimeType:t.mimeType,size:t.size,localPath:t.localPath}}function f(t){return t.flatMap(s=>s.attachments??[])}export{M as runWeChatReadLatest,S as runWeChatSend};
3
+ ${r}
4
+ ${Date.now()}`),l=await g(m,"/external/reply",{managerSessionId:e.sessionId,channelId:e.channelId,conversationId:e.conversationId,text:r,attachment:t.attachment,idempotencyKey:i},t.fetchImpl,t.timeoutMs),o=await S(m,e,t),a=o?x(o,i,l.pending===!0):l.pending===!0?"queued":"ok";return{ok:!0,operation:"write",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:e.workDir,helperTracePath:null,sendStatus:a,pending:v(a),attachment:n,attachments:n?[n]:[],reasonCode:null,message:o?a==="confirmed_echo"?"WeChat message sent and confirmed; do not resend.":a==="manual_review"?"WeChat message was submitted but needs manual review; do not resend.":"WeChat message submitted to local daemon; do not resend.":"WeChat message submitted to local daemon; confirmation sync timed out, but the message was already submitted \u2014 do not resend."}}async function b(t){const r=k(t.limit,10),e=y(t),n=N(t);if(!n){const a=t.directRuntime?{messages:await t.directRuntime.readLatest(e,{...t,limit:r,recentLimit:t.recentLimit??r}),outDir:e.workDir,helperTracePath:null,activityGuardPath:null}:await P(e,{...t,limit:r,recentLimit:t.recentLimit??r}),s=a.messages.filter(h=>h.conversationId===e.conversationId||h.conversationName===e.conversationName).slice(-r),c=w(s);return{ok:!0,operation:"read",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:a.outDir,helperTracePath:a.helperTracePath,activityGuardPath:a.activityGuardPath??null,messages:s,attachments:c,count:s.length,reasonCode:null,message:`Read ${s.length} WeChat message(s).`}}if(n.mode==="tool"){let a;try{a=await g(n.ctx,"/wechat-rpa/tool/read",{managerSessionId:e.sessionId,binding:e,limit:r,download:t.download,traceId:t.traceId,timeoutMs:t.timeoutMs},t.fetchImpl,t.timeoutMs)}catch(I){if(p(t,I))return b({...t,transport:"direct"});throw I}const c=(Array.isArray(a.messages)?a.messages.filter(D):[]).filter(I=>I.conversationId===e.conversationId||I.conversationName===e.conversationName).slice(-r),h=w(c);return{ok:!0,operation:"read",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:u(a.outDir)||e.workDir,helperTracePath:u(a.helperTracePath)||null,activityGuardPath:u(a.activityGuardPath)||null,messages:c,attachments:h,count:c.length,reasonCode:null,message:`Read ${c.length} WeChat message(s).`}}const d=n.ctx;await C(d,e,{...t,recentLimit:t.recentLimit??r});const m=await g(d,"/wechat-rpa/channel/sync",{managerSessionId:e.sessionId},t.fetchImpl,t.timeoutMs),l=(Array.isArray(m.messages)?m.messages.filter(D):[]).filter(a=>a.conversationId===e.conversationId||a.conversationName===e.conversationName).slice(-r),o=w(l);return{ok:!0,operation:"read",conversation:e.conversationName,conversationName:e.conversationName,conversationId:e.conversationId,sessionId:e.sessionId,channelId:e.channelId,traceId:t.traceId,outDir:e.workDir,helperTracePath:null,activityGuardPath:null,messages:l,attachments:o,count:l.length,reasonCode:null,message:`Read ${l.length} WeChat message(s).`}}function x(t,r,e){const n=f(t.channel)?t.channel:{},m=(Array.isArray(n.wechatRpaPendingReplies)?n.wechatRpaPendingReplies:[]).filter(f).find(a=>u(a.idempotencyKey)===r),i=u(m?.status);if(i==="confirmed_echo"||i==="sent_unconfirmed"||i==="manual_review"||i==="queued")return i;const o=(Array.isArray(n.wechatRpaRecentTaskSummaries)?n.wechatRpaRecentTaskSummaries:[]).filter(f).map(a=>u(a.status)).find(Boolean);return o==="sent"?"sent":o==="confirmed_echo"?"confirmed_echo":o==="manual_review"?"manual_review":e?"pending":"ok"}function A(t){return{kind:t.kind,name:t.name,mimeType:t.mimeType,size:t.size,localPath:t.localPath}}function w(t){return t.flatMap(r=>r.attachments??[])}export{b as runWeChatReadLatest,M as runWeChatSend};
@@ -1,4 +1,4 @@
1
- import y from"node:path";import{cleanInline as a,stringValue as o}from"./utils.js";const b=["SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HELPER_RUNTIME_DIR","packages/helper-runtime/wechat-channel","packages\\helper-runtime\\wechat-channel","packages/helper-runtime/dist","packages\\helper-runtime\\dist","wechat-rpa-windows-product-send-runner","PowerShell one-shot runner","activity.snapshot","automation.lease."],w=new Set(["wechat_not_running","wechat_window_not_found","permission_screen_recording_missing","permission_accessibility_missing","permission_input_monitoring_missing"]);function x(e,i){if(i){console.log(JSON.stringify(e,null,2));return}console.log(e.sendStatus)}function T(e,i){if(i==="json"){console.log(JSON.stringify(e,null,2));return}console.log(k(e).trimEnd())}function C(e,i,n,t){const r=S(e,i,n);t?console.log(JSON.stringify(r,null,2)):console.error(`${r.reasonCode}: ${r.message}`),process.exitCode=1}function S(e,i,n){const t=n instanceof Error?n.message:String(n||"wechat_tool_failed"),r=a(t)||"wechat_tool_failed",l=/^([a-z][a-z0-9_]*)(?::\s*(.*))?$/i.exec(r),s=o(n?.reasonCode)||l?.[1]||"wechat_tool_failed",c=W(s,r),_=a(l?.[2]||r),v=E(c,_,s),p=o(n?.outDir),d=o(n?.helperTracePath),h=o(n?.activityGuardPath),m=o(n?.traceId),f=q(c,s);return{ok:!1,operation:e,...i?.trim()?{conversation:i.trim()}:{},reasonCode:c,...$(s,c,_)?{rawReasonCode:s}:{},message:v,...m?{traceId:m}:{},...p?{outDir:p}:{},...d?{helperTracePath:d}:{},...h?{activityGuardPath:h}:{},...f?{userAction:f}:{}}}function k(e){const i=[];if(e.messages.length===0)return`\uFF08\u6CA1\u6709\u8BFB\u5230\uFF09
2
- `;for(const n of e.messages)i.push(`${A(n)}: ${I(n)}`);return`${i.join(`
1
+ import y from"node:path";import{cleanInline as a,describeSendStatus as b,stringValue as o}from"./utils.js";const S=["SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HELPER_RUNTIME_DIR","packages/helper-runtime/wechat-channel","packages\\helper-runtime\\wechat-channel","packages/helper-runtime/dist","packages\\helper-runtime\\dist","wechat-rpa-windows-product-send-runner","PowerShell one-shot runner","activity.snapshot","automation.lease."],w=new Set(["wechat_not_running","wechat_window_not_found","permission_screen_recording_missing","permission_accessibility_missing","permission_input_monitoring_missing"]);function T(e,i){if(i){console.log(JSON.stringify(e,null,2));return}console.log(b(e.sendStatus))}function C(e,i){if(i==="json"){console.log(JSON.stringify(e,null,2));return}console.log(W(e).trimEnd())}function D(e,i,n,t){const r=k(e,i,n);t?console.log(JSON.stringify(r,null,2)):console.error(`${r.reasonCode}: ${r.message}`),process.exitCode=1}function k(e,i,n){const t=n instanceof Error?n.message:String(n||"wechat_tool_failed"),r=a(t)||"wechat_tool_failed",l=/^([a-z][a-z0-9_]*)(?::\s*(.*))?$/i.exec(r),s=o(n?.reasonCode)||l?.[1]||"wechat_tool_failed",c=q(s,r),_=a(l?.[2]||r),v=R(c,_,s),p=o(n?.outDir),d=o(n?.helperTracePath),h=o(n?.activityGuardPath),m=o(n?.traceId),f=E(c,s);return{ok:!1,operation:e,...i?.trim()?{conversation:i.trim()}:{},reasonCode:c,...P(s,c,_)?{rawReasonCode:s}:{},message:v,...m?{traceId:m}:{},...p?{outDir:p}:{},...d?{helperTracePath:d}:{},...h?{activityGuardPath:h}:{},...f?{userAction:f}:{}}}function W(e){const i=[];if(e.messages.length===0)return`\uFF08\u6CA1\u6709\u8BFB\u5230\uFF09
2
+ `;for(const n of e.messages)i.push(`${L(n)}: ${A(n)}`);return`${i.join(`
3
3
  `)}
4
- `}function W(e,i=""){const n=e.trim().toLowerCase(),t=`${n} ${i}`.toLowerCase();return n?n==="insufficient_credits"?"insufficient_credits":n==="entitlement_required"||n==="enterprise_required"||n.includes("entitlement")?"entitlement_required":n==="manual_review_required"||n.includes("manual_review")?"manual_review_required":n==="attachment_pending"||/attachment.*pending|media_original_pending/.test(t)?"attachment_pending":/helper_runtime_required|helper_runtime_source_missing|helper_runtime_package_manifest_missing|manifest_missing|helper_missing|helper_not_executable/.test(n)?"helper_missing":/helper_runtime_cli_too_old|helper_runtime_version_mismatch|helper_runtime_protocol_mismatch|helper_runtime_platform_mismatch|helper_runtime_package_manifest_invalid|integrity_mismatch/.test(n)?"helper_version_incompatible":/^user_active|recent_(keyboard|mouse|scroll)|frontmost_app_changed|user_takeover|activity/.test(n)?"user_active_retry_later":/^manager_ipc_(timeout|network_failed)$/.test(n)?n:/helper_command_timeout/.test(n)?"wechat_window_unavailable":u(t)?"wechat_tool_failed":/permission|accessibility|screen_recording|automation|mac_input|input-monitoring/.test(t)?"permission_required":/wechat_login_required|login_required|安全验证|扫码登录|重新登录/.test(t)?"wechat_login_required":/wechat_single_main_window_required|wechat_duplicate_instance|duplicate_main_window/.test(n)?"wechat_window_unavailable":/visible_desktop|desktop_session|screen_locked|session_locked|rdp_disconnected|rdp.*visible|rdp.*desktop/.test(t)?"windows_visible_desktop_unavailable":/dpi_mapping_failed|display_topology|monitor_topology|multi_?monitor|multi_?screen/.test(t)?"dpi_mapping_failed":/conversation_not|conversation_title_not_confirmed|conversation_not_visible|open_read_required|search_result_not_visible/.test(n)?"conversation_not_found":/wechat_window|wechat_not_running|window_not|window_unavailable|window_unresponsive/.test(n)?"wechat_window_unavailable":n:"wechat_tool_failed"}function q(e,i=""){if(e==="wechat_login_required")return"Open WeChat on this machine, complete login or security verification, then retry.";if(e==="wechat_window_unavailable")return"Open Shennian Desktop and the main WeChat window on the active desktop, then retry. If running this command over SSH on Windows, retry with the local daemon available.";if(e==="wechat_single_main_window_required")return"Keep WeChat open and visible. If the window state is unclear, let Shennian re-check it with visual classification.";if(e==="windows_visible_desktop_unavailable")return"Unlock or reconnect the Windows visible desktop session, keep WeChat visible, then retry.";if(e==="dpi_mapping_failed")return"Move WeChat fully onto one visible display or use a supported display scaling setup, then retry.";if(e==="conversation_not_found")return"Check the WeChat conversation name and make sure the conversation can be searched.";if(e==="helper_missing")return"Open the \u4F7F\u7528\u5FAE\u4FE1 page in Shennian and click Install Helper, or open Shennian Desktop, then retry.";if(e==="helper_version_incompatible")return"Use Repair Helper on the \u4F7F\u7528\u5FAE\u4FE1 page, or upgrade Shennian CLI, then retry.";if(e==="permission_required")return H(i);if(e==="entitlement_required")return"Enable the WeChat capability for this account in Shennian.";if(e==="insufficient_credits")return"Add credits or adjust the account plan in Shennian.";if(e==="user_active_retry_later")return"Wait until the user is idle, then retry.";if(e==="attachment_pending")return"Open WeChat and let the attachment finish downloading, then retry.";if(e==="manual_review_required")return"Review the current WeChat window before retrying.";if(e==="manager_ipc_timeout"||e==="manager_ipc_network_failed")return"Restart Shennian Desktop or run this command with --transport direct, then retry."}function E(e,i,n){const t=n.trim().toLowerCase();return e==="permission_required"?R(t):!i||i===e||u(i)||w.has(t)?P(e):i}function R(e){return e.includes("screen_recording")?"Shennian Helper.app needs Screen Recording permission.":e.includes("accessibility")?"Shennian Helper.app needs Accessibility permission.":e.includes("input_monitoring")||e.includes("input-monitoring")?"Shennian Helper.app needs Input Monitoring permission.":"Shennian Helper.app needs macOS privacy permissions."}function H(e){const i=e.trim().toLowerCase();return i.includes("screen_recording")?"Grant Shennian Helper.app Screen Recording in Privacy & Security, then retry.":i.includes("accessibility")?"Grant Shennian Helper.app Accessibility in Privacy & Security, then retry.":i.includes("input_monitoring")||i.includes("input-monitoring")?"Grant Shennian Helper.app Input Monitoring in Privacy & Security, then retry.":"Grant Shennian Helper.app the required macOS permissions in Privacy & Security, then retry."}function $(e,i,n){const t=e.trim().toLowerCase();return!(e===i||w.has(t)||u(`${e} ${n}`))}function u(e){const i=e.replace(/\\/g,"/").toLowerCase();return b.some(n=>{const t=n.replace(/\\/g,"/").toLowerCase();return i.includes(t)})}function P(e){return e==="wechat_login_required"?"WeChat needs login or security verification on this machine.":e==="wechat_window_unavailable"?"WeChat window is not available on the active desktop.":e==="wechat_single_main_window_required"?"WeChat window state needs visual confirmation.":e==="windows_visible_desktop_unavailable"?"Windows visible desktop session is unavailable.":e==="dpi_mapping_failed"?"Windows display scaling or monitor geometry cannot be mapped safely.":e==="conversation_not_found"?"WeChat conversation was not found.":e==="helper_missing"?"Shennian Helper runtime is not installed or cannot be found.":e==="helper_version_incompatible"?"Shennian Helper runtime is incompatible with this CLI.":e==="permission_required"?"Shennian Helper.app needs macOS privacy permissions.":e==="entitlement_required"?"WeChat capability is not enabled for this account.":e==="insufficient_credits"?"Insufficient credits.":e==="user_active_retry_later"?"User activity is in progress; retry later.":e==="attachment_pending"?"WeChat attachment is still pending.":e==="manual_review_required"?"Manual review is required before retrying.":e==="manager_ipc_timeout"?"Local Shennian daemon did not respond before the request timed out.":e==="manager_ipc_network_failed"?"Local Shennian daemon connection failed during the request.":e}function I(e){const i=e.attachments||[],n=a(e.text);return n&&i.length===0?n:n&&i.length>0?`${n} ${i.map(g).join("\u3001")}`:i.length>0?i.map(g).join("\u3001"):"\uFF08\u6D88\u606F\uFF09"}function A(e){return a(e.sender.name||e.sender.id||"\u5BF9\u65B9")}function g(e){const i=a(e.name||y.basename(e.localPath||"")||e.type||"\u9644\u4EF6"),n=e.localPath||e.url||"",t=e.availability&&e.availability!=="edge-local"?` (${e.availability})`:"";return n?`[${M(i)}](${L(n)})${t}`:`${i}${t}`}function L(e){const i=e.replace(/\\/g,"/");return/[\s()<>]/.test(i)?`<${i.replace(/[<>]/g,"")}>`:i}function M(e){return a(e).replace(/([\\\]])/g,"\\$1")}export{k as buildWeChatReadMarkdown,S as buildWeChatToolErrorResult,T as printReadLatestResult,C as printToolError,x as printToolResult};
4
+ `}function q(e,i=""){const n=e.trim().toLowerCase(),t=`${n} ${i}`.toLowerCase();return n?n==="insufficient_credits"?"insufficient_credits":n==="entitlement_required"||n==="enterprise_required"||n.includes("entitlement")?"entitlement_required":n==="manual_review_required"||n.includes("manual_review")?"manual_review_required":n==="attachment_pending"||/attachment.*pending|media_original_pending/.test(t)?"attachment_pending":/helper_runtime_required|helper_runtime_source_missing|helper_runtime_package_manifest_missing|manifest_missing|helper_missing|helper_not_executable/.test(n)?"helper_missing":/helper_runtime_cli_too_old|helper_runtime_version_mismatch|helper_runtime_protocol_mismatch|helper_runtime_platform_mismatch|helper_runtime_package_manifest_invalid|integrity_mismatch/.test(n)?"helper_version_incompatible":/^user_active|recent_(keyboard|mouse|scroll)|frontmost_app_changed|user_takeover|activity|automation_busy/.test(n)?"user_active_retry_later":/^manager_ipc_(timeout|network_failed)$/.test(n)?n:/helper_command_timeout/.test(n)?"wechat_window_unavailable":u(t)?"wechat_tool_failed":/permission|accessibility|screen_recording|automation|mac_input|input-monitoring/.test(t)?"permission_required":/wechat_login_required|login_required|安全验证|扫码登录|重新登录/.test(t)?"wechat_login_required":/wechat_single_main_window_required|wechat_duplicate_instance|duplicate_main_window/.test(n)?"wechat_window_unavailable":/visible_desktop|desktop_session|screen_locked|session_locked|rdp_disconnected|rdp.*visible|rdp.*desktop/.test(t)?"windows_visible_desktop_unavailable":/dpi_mapping_failed|display_topology|monitor_topology|multi_?monitor|multi_?screen/.test(t)?"dpi_mapping_failed":/conversation_not|conversation_title_not_confirmed|conversation_not_visible|open_read_required|search_result_not_visible/.test(n)?"conversation_not_found":/wechat_window|wechat_not_running|window_not|window_unavailable|window_unresponsive/.test(n)?"wechat_window_unavailable":n:"wechat_tool_failed"}function E(e,i=""){if(e==="wechat_login_required")return"Open WeChat on this machine, complete login or security verification, then retry.";if(e==="wechat_window_unavailable")return"Open Shennian Desktop and the main WeChat window on the active desktop, then retry. If running this command over SSH on Windows, retry with the local daemon available.";if(e==="wechat_single_main_window_required")return"Keep WeChat open and visible. If the window state is unclear, let Shennian re-check it with visual classification.";if(e==="windows_visible_desktop_unavailable")return"Unlock or reconnect the Windows visible desktop session, keep WeChat visible, then retry.";if(e==="dpi_mapping_failed")return"Move WeChat fully onto one visible display or use a supported display scaling setup, then retry.";if(e==="conversation_not_found")return"Check the WeChat conversation name and make sure the conversation can be searched.";if(e==="helper_missing")return"Open the \u4F7F\u7528\u5FAE\u4FE1 page in Shennian and click Install Helper, or open Shennian Desktop, then retry.";if(e==="helper_version_incompatible")return"Use Repair Helper on the \u4F7F\u7528\u5FAE\u4FE1 page, or upgrade Shennian CLI, then retry.";if(e==="permission_required")return $(i);if(e==="entitlement_required")return"Enable the WeChat capability for this account in Shennian.";if(e==="insufficient_credits")return"Add credits or adjust the account plan in Shennian.";if(e==="user_active_retry_later")return"Wait until the user is idle, then retry.";if(e==="attachment_pending")return"Open WeChat and let the attachment finish downloading, then retry.";if(e==="manual_review_required")return"Review the current WeChat window before retrying.";if(e==="manager_ipc_timeout"||e==="manager_ipc_network_failed")return"Restart Shennian Desktop or run this command with --transport direct, then retry."}function R(e,i,n){const t=n.trim().toLowerCase();return e==="permission_required"?H(t):!i||i===e||u(i)||w.has(t)?I(e):i}function H(e){return e.includes("screen_recording")?"Shennian Helper.app needs Screen Recording permission.":e.includes("accessibility")?"Shennian Helper.app needs Accessibility permission.":e.includes("input_monitoring")||e.includes("input-monitoring")?"Shennian Helper.app needs Input Monitoring permission.":"Shennian Helper.app needs macOS privacy permissions."}function $(e){const i=e.trim().toLowerCase();return i.includes("screen_recording")?"Grant Shennian Helper.app Screen Recording in Privacy & Security, then retry.":i.includes("accessibility")?"Grant Shennian Helper.app Accessibility in Privacy & Security, then retry.":i.includes("input_monitoring")||i.includes("input-monitoring")?"Grant Shennian Helper.app Input Monitoring in Privacy & Security, then retry.":"Grant Shennian Helper.app the required macOS permissions in Privacy & Security, then retry."}function P(e,i,n){const t=e.trim().toLowerCase();return!(e===i||w.has(t)||u(`${e} ${n}`))}function u(e){const i=e.replace(/\\/g,"/").toLowerCase();return S.some(n=>{const t=n.replace(/\\/g,"/").toLowerCase();return i.includes(t)})}function I(e){return e==="wechat_login_required"?"WeChat needs login or security verification on this machine.":e==="wechat_window_unavailable"?"WeChat window is not available on the active desktop.":e==="wechat_single_main_window_required"?"WeChat window state needs visual confirmation.":e==="windows_visible_desktop_unavailable"?"Windows visible desktop session is unavailable.":e==="dpi_mapping_failed"?"Windows display scaling or monitor geometry cannot be mapped safely.":e==="conversation_not_found"?"WeChat conversation was not found.":e==="helper_missing"?"Shennian Helper runtime is not installed or cannot be found.":e==="helper_version_incompatible"?"Shennian Helper runtime is incompatible with this CLI.":e==="permission_required"?"Shennian Helper.app needs macOS privacy permissions.":e==="entitlement_required"?"WeChat capability is not enabled for this account.":e==="insufficient_credits"?"Insufficient credits.":e==="user_active_retry_later"?"User activity is in progress; retry later.":e==="attachment_pending"?"WeChat attachment is still pending.":e==="manual_review_required"?"Manual review is required before retrying.":e==="manager_ipc_timeout"?"Local Shennian daemon did not respond before the request timed out.":e==="manager_ipc_network_failed"?"Local Shennian daemon connection failed during the request.":e}function A(e){const i=e.attachments||[],n=a(e.text);return n&&i.length===0?n:n&&i.length>0?`${n} ${i.map(g).join("\u3001")}`:i.length>0?i.map(g).join("\u3001"):"\uFF08\u6D88\u606F\uFF09"}function L(e){return a(e.sender.name||e.sender.id||"\u5BF9\u65B9")}function g(e){const i=a(e.name||y.basename(e.localPath||"")||e.type||"\u9644\u4EF6"),n=e.localPath||e.url||"",t=e.availability&&e.availability!=="edge-local"?` (${e.availability})`:"";return n?`[${N(i)}](${M(n)})${t}`:`${i}${t}`}function M(e){const i=e.replace(/\\/g,"/");return/[\s()<>]/.test(i)?`<${i.replace(/[<>]/g,"")}>`:i}function N(e){return a(e).replace(/([\\\]])/g,"\\$1")}export{W as buildWeChatReadMarkdown,k as buildWeChatToolErrorResult,C as printReadLatestResult,D as printToolError,T as printToolResult};