shennian 0.2.107 → 0.2.108

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.
@@ -226,8 +226,8 @@
226
226
  },
227
227
  {
228
228
  "file": "src/channels/wechat-channel/helper-client.js",
229
- "beforeBytes": 27641,
230
- "afterBytes": 14420
229
+ "beforeBytes": 28901,
230
+ "afterBytes": 15127
231
231
  },
232
232
  {
233
233
  "file": "src/channels/wechat-channel/helper-protocol.js",
@@ -9,6 +9,9 @@ export type WeChatChannelHelperClientOptions = {
9
9
  skipUserActivityGuard?: boolean;
10
10
  cleanupWindowsOverlays?: boolean;
11
11
  requestLogger?: (event: WeChatChannelHelperRequestTraceEvent) => void;
12
+ openHelperAppForTest?: (command: string, args: string[]) => Promise<void>;
13
+ macosRuntimeInitialReadyTimeoutMs?: number;
14
+ macosRuntimeRelaunchReadyTimeoutMs?: number;
12
15
  };
13
16
  export type WeChatChannelHelperRequestTraceEvent = {
14
17
  phase: 'request' | 'response' | 'error';
@@ -1,3 +1,3 @@
1
- import{spawn as w}from"node:child_process";import{randomUUID as E}from"node:crypto";import f from"node:fs";import W from"node:net";import _ from"node:os";import d from"node:path";import{createInterface as m}from"node:readline";import{decideWeChatChannelActivityGate as b,normalizeWeChatChannelActivitySnapshot as T}from"./human-coordination.js";import{createWeChatChannelHelperHello as y,extractWeChatChannelHelperWarmupSnapshot as v,timeoutForWeChatChannelHelperCommand as P,validateWeChatChannelHelperReady as g}from"./helper-protocol.js";const R=["en","v"].join("");class re{options;child=null;socket=null;lines=null;readyState=null;warmupState=null;stderrTail="";startPromise=null;requestQueueTail=Promise.resolve();pending=new Map;constructor(e){this.options=e}async start(){return this.hasTransport()&&this.readyState?this.readyState:this.startPromise?this.startPromise:(this.hasTransport()&&await this.stop(),this.startPromise=this.startFresh().catch(async e=>{throw await this.stop().catch(()=>{}),e}).finally(()=>{this.startPromise=null}),this.startPromise)}async startFresh(){const e=H(this.options.helperPath,this.options.args??[]);return e.transport==="macos-socket"?this.startDarwinAppSocketFresh(e):this.startStdioFresh(e)}async startStdioFresh(e){const t=w(e.command,e.args,{cwd:this.options.cwd,stdio:["pipe","pipe","pipe"],windowsHide:!0});this.child=t,this.stderrTail="",this.lines=m({input:t.stdout}),this.lines.on("line",i=>this.handleLine(i)),t.stderr.on("data",i=>{this.captureStderr(i)}),t.stdin.on("error",i=>{this.rejectAllPending(i instanceof Error?i:new Error(String(i)))}),t.once("error",i=>{this.child===t&&(this.child=null,this.readyState=null,this.lines?.close(),this.lines=null),this.rejectAllPending(i instanceof Error?i:new Error(String(i)))}),t.once("exit",(i,o)=>{this.child===t&&(this.child=null,this.readyState=null,this.lines?.close(),this.lines=null),this.rejectAllPending(new Error(this.formatHelperExitMessage(i,o)))});const s=new Promise((i,o)=>{const a=setTimeout(()=>o(new Error("WeChat channel helper handshake timed out")),1e4),l=u=>{clearTimeout(a),this.lines?.off("line",c),t.off("exit",n),o(new Error(`WeChat channel helper failed to start: ${u.message}`))},n=(u,h)=>{clearTimeout(a),t.off("error",l),this.lines?.off("line",c),o(new Error(this.formatHelperExitMessage(u,h)))},c=u=>{let h;try{h=JSON.parse(u)}catch{clearTimeout(a),t.off("error",l),this.lines?.off("line",c),o(new Error("WeChat channel helper sent invalid handshake JSON"));return}if(!C(h))return;clearTimeout(a),t.off("error",l),t.off("exit",n),this.lines?.off("line",c);const p=g(h,this.options.expectedHelperVersion,this.options.requiredCapabilities);p.ok?(this.readyState=h,this.captureWarmupSnapshot(h),i(h)):o(new Error(`${p.errorCode}: ${p.errorSummary}`))};t.once("error",l),t.once("exit",n),this.lines?.on("line",c)});try{t.stdin.write(`${JSON.stringify(y(this.options.expectedHelperVersion,this.options.requiredCapabilities))}
2
- `)}catch(i){throw await this.stop().catch(()=>{}),i}return s}async startDarwinAppSocketFresh(e){const t=await this.connectDarwinHelperSocket(e);this.socket=t,this.stderrTail="",this.lines=m({input:t}),this.lines.on("line",i=>this.handleLine(i)),t.on("error",i=>{this.socket===t&&(this.socket=null,this.readyState=null,this.lines?.close(),this.lines=null),this.rejectAllPending(i instanceof Error?i:new Error(String(i)))}),t.once("close",()=>{this.socket===t&&(this.socket=null,this.readyState=null,this.lines?.close(),this.lines=null),this.rejectAllPending(new Error("WeChat channel helper socket closed"))});const s=new Promise((i,o)=>{const a=setTimeout(()=>o(new Error("WeChat channel helper socket handshake timed out")),1e4),l=u=>{clearTimeout(a),this.lines?.off("line",c),t.off("close",n),o(new Error(`WeChat channel helper socket failed: ${u.message}`))},n=()=>{clearTimeout(a),t.off("error",l),this.lines?.off("line",c),o(new Error("WeChat channel helper socket closed before handshake"))},c=u=>{let h;try{h=JSON.parse(u)}catch{clearTimeout(a),t.off("error",l),t.off("close",n),this.lines?.off("line",c),o(new Error("WeChat channel helper sent invalid socket handshake JSON"));return}if(!C(h))return;clearTimeout(a),t.off("error",l),t.off("close",n),this.lines?.off("line",c);const p=g(h,this.options.expectedHelperVersion,this.options.requiredCapabilities);p.ok?(this.readyState=h,this.captureWarmupSnapshot(h),i(h)):o(new Error(`${p.errorCode}: ${p.errorSummary}`))};t.once("error",l),t.once("close",n),this.lines?.on("line",c)});try{this.writeFrame(y(this.options.expectedHelperVersion,this.options.requiredCapabilities))}catch(i){throw await this.stop().catch(()=>{}),i}return s}async request(e,t,s){const i=this.requestQueueTail.catch(()=>{}).then(()=>this.sendRequest(e,t,s));return this.requestQueueTail=i.then(()=>{},()=>{}),i}async sendRequest(e,t,s){await this.guardUnsafeWindowsCommand(e,s);try{return await this.sendRawRequest(e,t,s)}finally{await this.cleanupWindowsOverlaysAfterCommand(e,s).catch(()=>{})}}async sendRawRequest(e,t,s){if((!this.hasTransport()||!this.readyState)&&await this.start(),!this.hasTransport())throw new Error("WeChat channel helper is not started");const i=E(),o=P(e),a=Date.now();this.emitRequestTrace({phase:"request",at:new Date(a).toISOString(),id:i,command:e,traceId:s,params:t,timeoutMs:o});const l=new Promise((n,c)=>{const u=setTimeout(()=>{this.pending.delete(i),c(new Error(`helper_command_timeout: ${e}`))},o);this.pending.set(i,{resolve:n,reject:c,timer:u})});try{this.writeFrame({id:i,command:e,params:t,traceId:s})}catch(n){const c=this.pending.get(i);throw c&&clearTimeout(c.timer),this.pending.delete(i),this.emitRequestTrace({phase:"error",at:new Date().toISOString(),id:i,command:e,traceId:s,durationMs:Date.now()-a,errorSummary:n instanceof Error?n.message:String(n)}),n}try{const n=await l;return this.emitRequestTrace({phase:"response",at:new Date().toISOString(),id:i,command:e,traceId:s,durationMs:Date.now()-a,ok:n.ok,errorCode:n.errorCode,errorSummary:n.errorSummary,latencyMs:n.latencyMs,result:n.result}),this.captureWarmupSnapshot(n),this.captureWarmupSnapshot(n.result),n}catch(n){throw this.emitRequestTrace({phase:"error",at:new Date().toISOString(),id:i,command:e,traceId:s,durationMs:Date.now()-a,errorSummary:n instanceof Error?n.message:String(n)}),n}}async guardUnsafeWindowsCommand(e,t){if(!this.options.guardUnsafeWindowsCommands||!I(e))return;const s=await this.sendRawRequest("permissions.check",{},t);if(!s.ok)throw new Error(`${s.errorCode??"permissions_check_failed"}: ${s.errorSummary??e}`);const i=s.result??{},o=await this.requestFirstMissingMacPermissionPrompt(i,t);if(o==="screen-recording")throw new Error("permission_screen_recording_missing");if(o==="accessibility")throw new Error("permission_accessibility_missing");if(o==="input-monitoring")throw new Error("permission_input_monitoring_missing");if(i.automation===!1)throw new Error("permission_automation_missing");if(i.wechatRunning===!1)throw new Error("wechat_not_running");if(M(i))throw new Error("wechat_login_required");if(q(i))throw new Error("windows_visible_desktop_unavailable");if(i.dpiMappingAvailable===!1||i.displayTopologySupported===!1)throw new Error("dpi_mapping_failed");if(i.wechatWindowAvailable===!1)throw new Error("wechat_window_unavailable");if(i.wechatMainWindowResponsive===!1)throw new Error("wechat_window_unresponsive");if(!this.options.skipUserActivityGuard&&j(e)){const a=await this.sendRawRequest("activity.snapshot",{},t);if(!a.ok)throw new Error(`${a.errorCode??"user_activity_unknown"}: ${a.errorSummary??e}`);const l=b({snapshot:T(a.result),stage:"dangerous_action"});if(!l.ok)throw new Error(`user_active:${l.reasonCode}`)}await this.cleanupWindowsOverlays("before",e,t)}async requestFirstMissingMacPermissionPrompt(e,t){const s=U(e);if(!s||process.platform!=="darwin")return s;const i=V(s);try{await this.sendRawRequest(i,{},t)}catch{}return s}async cleanupWindowsOverlaysAfterCommand(e,t){!this.options.guardUnsafeWindowsCommands||!J(e)||await this.cleanupWindowsOverlays("after",e,t)}async cleanupWindowsOverlays(e,t,s){if(this.options.cleanupWindowsOverlays===!1)return;if(!this.readyState?.capabilities.includes("overlayCleanup"))throw new Error("helper_capability_missing: overlayCleanup");const i=await this.sendRawRequest("windows.cleanupOverlays",{stage:e,command:t,includeGeneratedMediaPreviews:!0,includeToolingTerminals:!1,waitMs:e==="after"?220:120},s);if(!i.ok)throw new Error(`${i.errorCode??"overlay_cleanup_failed"}: ${i.errorSummary??t}`)}async healthCheck(e){return this.request("health.check",{},e)}getReadyState(){return this.readyState?{...this.readyState,capabilities:[...this.readyState.capabilities]}:null}getWarmupState(){return this.warmupState?{warmState:this.warmupState.warmState,metrics:this.warmupState.metrics?{...this.warmupState.metrics}:void 0}:null}async stop(){const e=this.child,t=this.socket;this.child=null,this.socket=null,this.readyState=null,this.lines?.close(),this.lines=null,this.rejectAllPending(new Error("WeChat channel helper stopped")),t&&!t.destroyed&&t.destroy(),!(!e||e.killed)&&e.kill("SIGTERM")}hasTransport(){return!!this.child||!!this.socket}writeFrame(e){const t=`${JSON.stringify(e)}
3
- `;if(this.socket){this.socket.write(t);return}if(this.child){this.child.stdin.write(t);return}throw new Error("WeChat channel helper is not started")}handleLine(e){let t;try{t=JSON.parse(e)}catch{return}if(!G(t))return;const s=this.pending.get(t.id);s&&(clearTimeout(s.timer),this.pending.delete(t.id),s.resolve(t))}captureWarmupSnapshot(e){const t=v(e);t&&(this.warmupState=t)}captureStderr(e){const t=Buffer.isBuffer(e)?e.toString("utf8"):String(e??"");t&&(this.stderrTail=(this.stderrTail+t).slice(-2e3))}formatHelperExitMessage(e,t){const s=[e===null?null:`code=${e}`,t?`signal=${t}`:null,this.stderrTail.trim()?`stderr=${this.stderrTail.trim()}`:null].filter(Boolean);return s.length?`WeChat channel helper process exited (${s.join(", ")})`:"WeChat channel helper process exited"}rejectAllPending(e){for(const[t,s]of this.pending)clearTimeout(s.timer),s.reject(e),this.pending.delete(t)}emitRequestTrace(e){try{this.options.requestLogger?.(e)}catch{}}async connectDarwinHelperSocket(e){try{return await S(e.runtimeFile)}catch{f.rmSync(e.runtimeFile,{force:!0})}await N(e);const t=Date.now()+1e4;let s=null;for(;Date.now()<t;)try{return await S(e.runtimeFile)}catch(i){s=i instanceof Error?i:new Error(String(i)),await L(150)}throw new Error(`WeChat channel Helper.app socket did not become ready: ${s?.message??"runtime file missing"}`)}}function M(r){return r.wechatWindowAvailable!==!1?!1:![...r.captureCandidates??[],...r.restoreCandidates??[],...r.hiddenRestoreCandidates??[]].some($)}function q(r){return r.windowsVisibleDesktopAvailable===!1||r.rdpVisibleDesktopAvailable===!1||r.desktopSessionVisible===!1||r.screenLocked===!0||r.rdpDisconnected===!0}function A(r){const e=String(r.appName||"").toLowerCase();return e==="wechat"||e==="weixin"}function $(r){return!(!A(r)||r.visible===!1||r.minimized===!0)}function H(r,e,t=process.platform){if(t==="darwin"){const s=x(r);if(s&&e.length===0){const i=D();return{transport:"macos-socket",command:"/usr/bin/open",args:["-g",s,"--args","--socket-runtime",i],appPath:s,runtimeDir:i,runtimeFile:d.join(i,"runtime.json")}}}return{transport:"legacy-stdio",command:r,args:e}}function x(r){const e=d.resolve(r),t=`${d.sep}Contents${d.sep}MacOS${d.sep}`,s=e.lastIndexOf(t);if(s<0)return null;const i=e.slice(0,s);return i.endsWith(".app")?i:null}function D(){const r=O().SHENNIAN_HELPER_RUNTIME_DIR?.trim();return r?d.resolve(r):d.join(_.homedir(),"Library","Application Support","Shennian","Helper")}function O(){return globalThis.process?.[R]??{}}function F(r){let e;try{e=JSON.parse(f.readFileSync(r,"utf8"))}catch(s){throw new Error(`runtime_file_unreadable: ${s instanceof Error?s.message:String(s)}`)}if(!e||typeof e!="object")throw new Error("runtime_file_invalid");const t=e.socketPath;if(typeof t!="string"||!t.trim())throw new Error("runtime_socket_missing");return{socketPath:t}}async function S(r){const{socketPath:e}=F(r);return new Promise((t,s)=>{const i=W.createConnection(e),o=setTimeout(()=>{i.destroy(),s(new Error(`runtime_socket_connect_timeout: ${e}`))},1e3);i.once("connect",()=>{clearTimeout(o),i.off("error",a),t(i)});const a=l=>{clearTimeout(o),i.destroy(),s(l)};i.once("error",a)})}async function N(r){f.mkdirSync(r.runtimeDir,{recursive:!0,mode:448});try{await k(r.command,r.args)}catch(e){if(!(e instanceof Error?e.message:String(e)).includes("-1712"))throw e;await k(r.command,["-n",...r.args])}}async function k(r,e){await new Promise((t,s)=>{const i=w(r,e,{stdio:["ignore","ignore","pipe"],windowsHide:!0});let o="";i.stderr.on("data",a=>{o=(o+String(a)).slice(-2e3)}),i.once("error",s),i.once("exit",(a,l)=>{a===0?t():s(new Error(`open Helper.app failed (${l?`signal=${l}`:`code=${a}`}${o.trim()?`, stderr=${o.trim()}`:""})`))})})}function L(r){return new Promise(e=>setTimeout(e,r))}function U(r){return r.screenRecording===!1?"screen-recording":r.accessibility===!1?"accessibility":r.inputMonitoring===!1?"input-monitoring":null}function V(r){return r==="screen-recording"?"permissions.requestScreenRecording":r==="accessibility"?"permissions.requestAccessibility":"permissions.requestInputMonitoring"}function C(r){if(!r||typeof r!="object")return!1;const e=r;return e.type==="ready"&&typeof e.helperVersion=="string"&&typeof e.protocolVersion=="number"&&Array.isArray(e.capabilities)&&typeof e.pid=="number"}function G(r){if(!r||typeof r!="object")return!1;const e=r;return typeof e.id=="string"&&typeof e.ok=="boolean"&&typeof e.latencyMs=="number"}function I(r){return r==="windows.focus"||r==="windows.capture"||r==="screen.capture"||r==="ocr.recognize"||r==="image.cropHash"||r==="wechat.searchConversation"||r==="wechat.focusMessageInput"||r.startsWith("mouse.")||r.startsWith("keyboard.")||r.startsWith("clipboard.")||r==="menu.pickItem"||r==="savePanel.saveToPath"}function J(r){return r.startsWith("mouse.")||r.startsWith("keyboard.")||r.startsWith("clipboard.")||r==="menu.pickItem"||r==="savePanel.saveToPath"}function j(r){return r==="windows.focus"||r==="wechat.searchConversation"||r==="wechat.focusMessageInput"||r.startsWith("mouse.")||r.startsWith("keyboard.")||r.startsWith("clipboard.set")||r==="clipboard.restore"||r==="menu.pickItem"||r==="savePanel.saveToPath"}export{re as WeChatChannelHelperClient,H as helperLaunchSpec};
1
+ import{spawn as w}from"node:child_process";import{randomUUID as _}from"node:crypto";import f from"node:fs";import W from"node:net";import b from"node:os";import d from"node:path";import{createInterface as m}from"node:readline";import{decideWeChatChannelActivityGate as T,normalizeWeChatChannelActivitySnapshot as v}from"./human-coordination.js";import{createWeChatChannelHelperHello as y,extractWeChatChannelHelperWarmupSnapshot as P,timeoutForWeChatChannelHelperCommand as R,validateWeChatChannelHelperReady as g}from"./helper-protocol.js";const M=["en","v"].join("");class se{options;child=null;socket=null;lines=null;readyState=null;warmupState=null;stderrTail="";startPromise=null;requestQueueTail=Promise.resolve();pending=new Map;constructor(e){this.options=e}async start(){return this.hasTransport()&&this.readyState?this.readyState:this.startPromise?this.startPromise:(this.hasTransport()&&await this.stop(),this.startPromise=this.startFresh().catch(async e=>{throw await this.stop().catch(()=>{}),e}).finally(()=>{this.startPromise=null}),this.startPromise)}async startFresh(){const e=F(this.options.helperPath,this.options.args??[]);return e.transport==="macos-socket"?this.startDarwinAppSocketFresh(e):this.startStdioFresh(e)}async startStdioFresh(e){const t=w(e.command,e.args,{cwd:this.options.cwd,stdio:["pipe","pipe","pipe"],windowsHide:!0});this.child=t,this.stderrTail="",this.lines=m({input:t.stdout}),this.lines.on("line",i=>this.handleLine(i)),t.stderr.on("data",i=>{this.captureStderr(i)}),t.stdin.on("error",i=>{this.rejectAllPending(i instanceof Error?i:new Error(String(i)))}),t.once("error",i=>{this.child===t&&(this.child=null,this.readyState=null,this.lines?.close(),this.lines=null),this.rejectAllPending(i instanceof Error?i:new Error(String(i)))}),t.once("exit",(i,n)=>{this.child===t&&(this.child=null,this.readyState=null,this.lines?.close(),this.lines=null),this.rejectAllPending(new Error(this.formatHelperExitMessage(i,n)))});const s=new Promise((i,n)=>{const a=setTimeout(()=>n(new Error("WeChat channel helper handshake timed out")),1e4),l=u=>{clearTimeout(a),this.lines?.off("line",c),t.off("exit",o),n(new Error(`WeChat channel helper failed to start: ${u.message}`))},o=(u,h)=>{clearTimeout(a),t.off("error",l),this.lines?.off("line",c),n(new Error(this.formatHelperExitMessage(u,h)))},c=u=>{let h;try{h=JSON.parse(u)}catch{clearTimeout(a),t.off("error",l),this.lines?.off("line",c),n(new Error("WeChat channel helper sent invalid handshake JSON"));return}if(!C(h))return;clearTimeout(a),t.off("error",l),t.off("exit",o),this.lines?.off("line",c);const p=g(h,this.options.expectedHelperVersion,this.options.requiredCapabilities);p.ok?(this.readyState=h,this.captureWarmupSnapshot(h),i(h)):n(new Error(`${p.errorCode}: ${p.errorSummary}`))};t.once("error",l),t.once("exit",o),this.lines?.on("line",c)});try{t.stdin.write(`${JSON.stringify(y(this.options.expectedHelperVersion,this.options.requiredCapabilities))}
2
+ `)}catch(i){throw await this.stop().catch(()=>{}),i}return s}async startDarwinAppSocketFresh(e){const t=await this.connectDarwinHelperSocket(e);this.socket=t,this.stderrTail="",this.lines=m({input:t}),this.lines.on("line",i=>this.handleLine(i)),t.on("error",i=>{this.socket===t&&(this.socket=null,this.readyState=null,this.lines?.close(),this.lines=null),this.rejectAllPending(i instanceof Error?i:new Error(String(i)))}),t.once("close",()=>{this.socket===t&&(this.socket=null,this.readyState=null,this.lines?.close(),this.lines=null),this.rejectAllPending(new Error("WeChat channel helper socket closed"))});const s=new Promise((i,n)=>{const a=setTimeout(()=>n(new Error("WeChat channel helper socket handshake timed out")),1e4),l=u=>{clearTimeout(a),this.lines?.off("line",c),t.off("close",o),n(new Error(`WeChat channel helper socket failed: ${u.message}`))},o=()=>{clearTimeout(a),t.off("error",l),this.lines?.off("line",c),n(new Error("WeChat channel helper socket closed before handshake"))},c=u=>{let h;try{h=JSON.parse(u)}catch{clearTimeout(a),t.off("error",l),t.off("close",o),this.lines?.off("line",c),n(new Error("WeChat channel helper sent invalid socket handshake JSON"));return}if(!C(h))return;clearTimeout(a),t.off("error",l),t.off("close",o),this.lines?.off("line",c);const p=g(h,this.options.expectedHelperVersion,this.options.requiredCapabilities);p.ok?(this.readyState=h,this.captureWarmupSnapshot(h),i(h)):n(new Error(`${p.errorCode}: ${p.errorSummary}`))};t.once("error",l),t.once("close",o),this.lines?.on("line",c)});try{this.writeFrame(y(this.options.expectedHelperVersion,this.options.requiredCapabilities))}catch(i){throw await this.stop().catch(()=>{}),i}return s}async request(e,t,s){const i=this.requestQueueTail.catch(()=>{}).then(()=>this.sendRequest(e,t,s));return this.requestQueueTail=i.then(()=>{},()=>{}),i}async sendRequest(e,t,s){await this.guardUnsafeWindowsCommand(e,s);try{return await this.sendRawRequest(e,t,s)}finally{await this.cleanupWindowsOverlaysAfterCommand(e,s).catch(()=>{})}}async sendRawRequest(e,t,s){if((!this.hasTransport()||!this.readyState)&&await this.start(),!this.hasTransport())throw new Error("WeChat channel helper is not started");const i=_(),n=R(e),a=Date.now();this.emitRequestTrace({phase:"request",at:new Date(a).toISOString(),id:i,command:e,traceId:s,params:t,timeoutMs:n});const l=new Promise((o,c)=>{const u=setTimeout(()=>{this.pending.delete(i),c(new Error(`helper_command_timeout: ${e}`))},n);this.pending.set(i,{resolve:o,reject:c,timer:u})});try{this.writeFrame({id:i,command:e,params:t,traceId:s})}catch(o){const c=this.pending.get(i);throw c&&clearTimeout(c.timer),this.pending.delete(i),this.emitRequestTrace({phase:"error",at:new Date().toISOString(),id:i,command:e,traceId:s,durationMs:Date.now()-a,errorSummary:o instanceof Error?o.message:String(o)}),o}try{const o=await l;return this.emitRequestTrace({phase:"response",at:new Date().toISOString(),id:i,command:e,traceId:s,durationMs:Date.now()-a,ok:o.ok,errorCode:o.errorCode,errorSummary:o.errorSummary,latencyMs:o.latencyMs,result:o.result}),this.captureWarmupSnapshot(o),this.captureWarmupSnapshot(o.result),o}catch(o){throw this.emitRequestTrace({phase:"error",at:new Date().toISOString(),id:i,command:e,traceId:s,durationMs:Date.now()-a,errorSummary:o instanceof Error?o.message:String(o)}),o}}async guardUnsafeWindowsCommand(e,t){if(!this.options.guardUnsafeWindowsCommands||!j(e))return;const s=await this.sendRawRequest("permissions.check",{},t);if(!s.ok)throw new Error(`${s.errorCode??"permissions_check_failed"}: ${s.errorSummary??e}`);const i=s.result??{},n=await this.requestFirstMissingMacPermissionPrompt(i,t);if(n==="screen-recording")throw new Error("permission_screen_recording_missing");if(n==="accessibility")throw new Error("permission_accessibility_missing");if(n==="input-monitoring")throw new Error("permission_input_monitoring_missing");if(i.automation===!1)throw new Error("permission_automation_missing");if(i.wechatRunning===!1)throw new Error("wechat_not_running");if(A(i))throw new Error("wechat_login_required");if($(i))throw new Error("windows_visible_desktop_unavailable");if(i.dpiMappingAvailable===!1||i.displayTopologySupported===!1)throw new Error("dpi_mapping_failed");if(i.wechatWindowAvailable===!1)throw new Error("wechat_window_unavailable");if(i.wechatMainWindowResponsive===!1)throw new Error("wechat_window_unresponsive");if(!this.options.skipUserActivityGuard&&B(e)){const a=await this.sendRawRequest("activity.snapshot",{},t);if(!a.ok)throw new Error(`${a.errorCode??"user_activity_unknown"}: ${a.errorSummary??e}`);const l=T({snapshot:v(a.result),stage:"dangerous_action"});if(!l.ok)throw new Error(`user_active:${l.reasonCode}`)}await this.cleanupWindowsOverlays("before",e,t)}async requestFirstMissingMacPermissionPrompt(e,t){const s=V(e);if(!s||process.platform!=="darwin")return s;const i=G(s);try{await this.sendRawRequest(i,{},t)}catch{}return s}async cleanupWindowsOverlaysAfterCommand(e,t){!this.options.guardUnsafeWindowsCommands||!z(e)||await this.cleanupWindowsOverlays("after",e,t)}async cleanupWindowsOverlays(e,t,s){if(this.options.cleanupWindowsOverlays===!1)return;if(!this.readyState?.capabilities.includes("overlayCleanup"))throw new Error("helper_capability_missing: overlayCleanup");const i=await this.sendRawRequest("windows.cleanupOverlays",{stage:e,command:t,includeGeneratedMediaPreviews:!0,includeToolingTerminals:!1,waitMs:e==="after"?220:120},s);if(!i.ok)throw new Error(`${i.errorCode??"overlay_cleanup_failed"}: ${i.errorSummary??t}`)}async healthCheck(e){return this.request("health.check",{},e)}getReadyState(){return this.readyState?{...this.readyState,capabilities:[...this.readyState.capabilities]}:null}getWarmupState(){return this.warmupState?{warmState:this.warmupState.warmState,metrics:this.warmupState.metrics?{...this.warmupState.metrics}:void 0}:null}async stop(){const e=this.child,t=this.socket;this.child=null,this.socket=null,this.readyState=null,this.lines?.close(),this.lines=null,this.rejectAllPending(new Error("WeChat channel helper stopped")),t&&!t.destroyed&&t.destroy(),!(!e||e.killed)&&e.kill("SIGTERM")}hasTransport(){return!!this.child||!!this.socket}writeFrame(e){const t=`${JSON.stringify(e)}
3
+ `;if(this.socket){this.socket.write(t);return}if(this.child){this.child.stdin.write(t);return}throw new Error("WeChat channel helper is not started")}handleLine(e){let t;try{t=JSON.parse(e)}catch{return}if(!J(t))return;const s=this.pending.get(t.id);s&&(clearTimeout(s.timer),this.pending.delete(t.id),s.resolve(t))}captureWarmupSnapshot(e){const t=P(e);t&&(this.warmupState=t)}captureStderr(e){const t=Buffer.isBuffer(e)?e.toString("utf8"):String(e??"");t&&(this.stderrTail=(this.stderrTail+t).slice(-2e3))}formatHelperExitMessage(e,t){const s=[e===null?null:`code=${e}`,t?`signal=${t}`:null,this.stderrTail.trim()?`stderr=${this.stderrTail.trim()}`:null].filter(Boolean);return s.length?`WeChat channel helper process exited (${s.join(", ")})`:"WeChat channel helper process exited"}rejectAllPending(e){for(const[t,s]of this.pending)clearTimeout(s.timer),s.reject(e),this.pending.delete(t)}emitRequestTrace(e){try{this.options.requestLogger?.(e)}catch{}}async connectDarwinHelperSocket(e){try{return await S(e.runtimeFile)}catch{f.rmSync(e.runtimeFile,{force:!0})}await E(e,{},this.options.openHelperAppForTest);const t=await k(e.runtimeFile,this.options.macosRuntimeInitialReadyTimeoutMs??5e3);if(t.ok)return t.socket;f.rmSync(e.runtimeFile,{force:!0}),await E(e,{forceNewInstance:!0},this.options.openHelperAppForTest);const s=await k(e.runtimeFile,this.options.macosRuntimeRelaunchReadyTimeoutMs??1e4);if(s.ok)return s.socket;throw new Error(`WeChat channel Helper.app socket did not become ready: ${s.error.message}`)}}function A(r){return r.wechatWindowAvailable!==!1?!1:![...r.captureCandidates??[],...r.restoreCandidates??[],...r.hiddenRestoreCandidates??[]].some(H)}function $(r){return r.windowsVisibleDesktopAvailable===!1||r.rdpVisibleDesktopAvailable===!1||r.desktopSessionVisible===!1||r.screenLocked===!0||r.rdpDisconnected===!0}function q(r){const e=String(r.appName||"").toLowerCase();return e==="wechat"||e==="weixin"}function H(r){return!(!q(r)||r.visible===!1||r.minimized===!0)}function F(r,e,t=process.platform){if(t==="darwin"){const s=x(r);if(s&&e.length===0){const i=D();return{transport:"macos-socket",command:"/usr/bin/open",args:["-g",s,"--args","--socket-runtime",i],appPath:s,runtimeDir:i,runtimeFile:d.join(i,"runtime.json")}}}return{transport:"legacy-stdio",command:r,args:e}}function x(r){const e=d.resolve(r),t=`${d.sep}Contents${d.sep}MacOS${d.sep}`,s=e.lastIndexOf(t);if(s<0)return null;const i=e.slice(0,s);return i.endsWith(".app")?i:null}function D(){const r=O().SHENNIAN_HELPER_RUNTIME_DIR?.trim();return r?d.resolve(r):d.join(b.homedir(),"Library","Application Support","Shennian","Helper")}function O(){return globalThis.process?.[M]??{}}function N(r){let e;try{e=JSON.parse(f.readFileSync(r,"utf8"))}catch(i){throw new Error(`runtime_file_unreadable: ${i instanceof Error?i.message:String(i)}`)}if(!e||typeof e!="object")throw new Error("runtime_file_invalid");const t=e.socketPath;if(typeof t!="string"||!t.trim())throw new Error("runtime_socket_missing");const s=e.pid;return{socketPath:t,...typeof s=="number"&&Number.isInteger(s)&&s>0?{pid:s}:{}}}async function S(r){const{socketPath:e,pid:t}=N(r);if(t&&!L(t))throw new Error(`runtime_process_stale: ${t}`);return new Promise((s,i)=>{const n=W.createConnection(e),a=setTimeout(()=>{n.destroy(),i(new Error(`runtime_socket_connect_timeout: ${e}`))},1e3);n.once("connect",()=>{clearTimeout(a),n.off("error",l),s(n)});const l=o=>{clearTimeout(a),n.destroy(),i(o)};n.once("error",l)})}async function k(r,e){const t=Date.now()+e;let s=null;for(;Date.now()<t;)try{return{ok:!0,socket:await S(r)}}catch(i){s=i instanceof Error?i:new Error(String(i));const n=t-Date.now();n>0&&await I(Math.min(150,n))}return{ok:!1,error:s??new Error("runtime file missing")}}async function E(r,e={},t=U){f.mkdirSync(r.runtimeDir,{recursive:!0,mode:448});const s=e.forceNewInstance?["-n",...r.args]:r.args;try{await t(r.command,s)}catch(i){if(!(i instanceof Error?i.message:String(i)).includes("-1712"))throw i;await t(r.command,["-n",...r.args])}}function L(r){try{return process.kill(r,0),!0}catch{return!1}}async function U(r,e){await new Promise((t,s)=>{const i=w(r,e,{stdio:["ignore","ignore","pipe"],windowsHide:!0});let n="";i.stderr.on("data",a=>{n=(n+String(a)).slice(-2e3)}),i.once("error",s),i.once("exit",(a,l)=>{a===0?t():s(new Error(`open Helper.app failed (${l?`signal=${l}`:`code=${a}`}${n.trim()?`, stderr=${n.trim()}`:""})`))})})}function I(r){return new Promise(e=>setTimeout(e,r))}function V(r){return r.screenRecording===!1?"screen-recording":r.accessibility===!1?"accessibility":r.inputMonitoring===!1?"input-monitoring":null}function G(r){return r==="screen-recording"?"permissions.requestScreenRecording":r==="accessibility"?"permissions.requestAccessibility":"permissions.requestInputMonitoring"}function C(r){if(!r||typeof r!="object")return!1;const e=r;return e.type==="ready"&&typeof e.helperVersion=="string"&&typeof e.protocolVersion=="number"&&Array.isArray(e.capabilities)&&typeof e.pid=="number"}function J(r){if(!r||typeof r!="object")return!1;const e=r;return typeof e.id=="string"&&typeof e.ok=="boolean"&&typeof e.latencyMs=="number"}function j(r){return r==="windows.focus"||r==="windows.capture"||r==="screen.capture"||r==="ocr.recognize"||r==="image.cropHash"||r==="wechat.searchConversation"||r==="wechat.focusMessageInput"||r.startsWith("mouse.")||r.startsWith("keyboard.")||r.startsWith("clipboard.")||r==="menu.pickItem"||r==="savePanel.saveToPath"}function z(r){return r.startsWith("mouse.")||r.startsWith("keyboard.")||r.startsWith("clipboard.")||r==="menu.pickItem"||r==="savePanel.saveToPath"}function B(r){return r==="windows.focus"||r==="wechat.searchConversation"||r==="wechat.focusMessageInput"||r.startsWith("mouse.")||r.startsWith("keyboard.")||r.startsWith("clipboard.set")||r==="clipboard.restore"||r==="menu.pickItem"||r==="savePanel.saveToPath"}export{se as WeChatChannelHelperClient,F as helperLaunchSpec};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.107",
3
+ "version": "0.2.108",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {