aws-runtime-bridge 1.9.101 → 1.9.102

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.
@@ -1,2 +1,2 @@
1
- import{UIAutomation as i}from"./ui-automation.js";class c{constructor(t,e){this.screenCapture=t,this.inputBackend=e,this.uiAutomation=new i}async screenshot(t){return this.screenCapture.captureVirtualDesktop()}async moveMouse(t,e){await this.inputBackend.moveMouse(t,e)}async click(t,e,n="left"){await this.inputBackend.click(t,e,n)}async doubleClick(t,e){await this.inputBackend.doubleClick(t,e)}async mouseDown(t="left"){await this.inputBackend.mouseDown(t)}async mouseUp(t="left"){await this.inputBackend.mouseUp(t)}async scroll(t,e){await this.inputBackend.scroll(t,e)}async type(t){await this.inputBackend.type(t)}async keyPress(t){await this.inputBackend.keyPress(t)}async keyDown(t){await this.inputBackend.keyDown(t)}async keyUp(t){await this.inputBackend.keyUp(t)}async listScreens(){return this.screenCapture.listScreens()}getScreenSize(){return this.inputBackend.getScreenSize()}async listApplications(){return this.uiAutomation.listApplications()}async getUIElements(t){return this.uiAutomation.getUIElements(t)}async clickElement(t){return this.uiAutomation.clickElement(t)}async setElementValue(t,e){return this.uiAutomation.setElementValue(t,e)}}export{c as ComputerControl};
1
+ import{UIAutomation as i}from"./ui-automation.js";class c{constructor(t,e){this.screenCapture=t,this.inputBackend=e,this.uiAutomation=new i}async screenshot(t){const e=await this.screenCapture.captureVirtualDesktop(!0);if(!e||e.kind!=="full")throw new Error("screenshot failed: no frame captured");return{data:e.data,width:e.width,height:e.height}}async moveMouse(t,e){await this.inputBackend.moveMouse(t,e)}async click(t,e,n="left"){await this.inputBackend.click(t,e,n)}async doubleClick(t,e){await this.inputBackend.doubleClick(t,e)}async mouseDown(t="left"){await this.inputBackend.mouseDown(t)}async mouseUp(t="left"){await this.inputBackend.mouseUp(t)}async scroll(t,e){await this.inputBackend.scroll(t,e)}async type(t){await this.inputBackend.type(t)}async keyPress(t){await this.inputBackend.keyPress(t)}async keyDown(t){await this.inputBackend.keyDown(t)}async keyUp(t){await this.inputBackend.keyUp(t)}async listScreens(){return this.screenCapture.listScreens()}getScreenSize(){return this.inputBackend.getScreenSize()}async listApplications(){return this.uiAutomation.listApplications()}async getUIElements(t){return this.uiAutomation.getUIElements(t)}async clickElement(t){return this.uiAutomation.clickElement(t)}async setElementValue(t,e){return this.uiAutomation.setElementValue(t,e)}}export{c as ComputerControl};
2
2
 
@@ -0,0 +1,3 @@
1
+ import type { CaptureFrame } from "./types.js";
2
+
3
+ export declare function encodeFrame(frame: CaptureFrame, seq: number): Buffer;
@@ -0,0 +1,2 @@
1
+ const r=21060,U=2,B=1,E=2;function w(e,c){const i=c>>>0;if(e.kind==="full"){const t=Buffer.allocUnsafe(16);return t.writeUInt16BE(21060,0),t[2]=2,t[3]=2,t.writeUInt32BE(i,4),t.writeUInt32BE(e.width,8),t.writeUInt32BE(e.height,12),Buffer.concat([t,e.data])}const s=e.tiles.length,l=10+s*12;let I=0;for(const t of e.tiles)I+=t.data.length;const n=Buffer.allocUnsafe(l+I);n.writeUInt16BE(21060,0),n[2]=2,n[3]=1,n.writeUInt32BE(i,4),n.writeUInt16BE(s,8);let o=10;for(const t of e.tiles)n.writeUInt16BE(t.x,o),n.writeUInt16BE(t.y,o+2),n.writeUInt16BE(t.w,o+4),n.writeUInt16BE(t.h,o+6),n.writeUInt32BE(t.data.length,o+8),o+=12;for(const t of e.tiles)t.data.copy(n,o),o+=t.data.length;return n}export{w as encodeFrame};
2
+
@@ -1,4 +1,4 @@
1
- import{WebSocketServer as u}from"ws";import{SessionManager as S}from"./session-manager.js";import{PermissionManager as y}from"./permission-manager.js";import{isRuntimeTokenValid as b}from"../middleware/auth.js";import{getFrameBufferLimit as T,shouldDropFrameForBackpressure as P}from"./frame-backpressure.js";const g=new y,i=new S(g);function W(r,o){const e=o.searchParams.get("token");if(e)return e;const t=r.headers["x-runtime-token"];if(typeof t=="string")return t;const s=r.headers.authorization;return typeof s=="string"&&s.startsWith("Bearer ")?s.slice(7):""}function c(r,o){r.readyState===r.OPEN&&r.send(JSON.stringify(o))}function x(r,o,e){switch(e.type){case"remote-desktop:input":{const t=e.data;i.handleInput(o,t).catch(s=>{c(r,{type:"remote-desktop:error",sessionId:o,data:{error:String(s)}})});break}case"remote-desktop:stop":{i.closeSession(o),r.close();break}case"remote-desktop:quality":{const t=i.getSession(o);t&&e.data&&t.screenCapture.setQuality(e.data.quality,e.data.fps);break}case"remote-desktop:control-mode":{const t=i.getSession(o);t&&e.data&&(e.data.enabled?t.enableControl().catch(s=>{console.warn("[remote-desktop] enableControl failed:",s)}):t.stopInputBackend());break}case"remote-desktop:switch-screen":{const t=i.getSession(o);t&&e.data&&t.switchScreen(e.data.screenId||null).catch(s=>{c(r,{type:"remote-desktop:error",sessionId:o,data:{error:String(s)}})});break}case"remote-desktop:ping":{c(r,{type:"remote-desktop:pong",sessionId:o,data:e.data});break}}}function B(r){const o=new u({noServer:!0});r.on("upgrade",(e,t,s)=>{const d=new URL(e.url||"/",`http://${e.headers.host}`);if(d.pathname!=="/remote-desktop")return;const m=W(e,d);if(!b(m)&&!g.validateToken(m)){t.write(`HTTP/1.1 401 Unauthorized\r
1
+ import{WebSocketServer as C}from"ws";import{SessionManager as F}from"./session-manager.js";import{PermissionManager as D}from"./permission-manager.js";import{encodeFrame as M}from"./frame-encode.js";import{getSharpLoadStatus as T}from"./screen-capture.js";import{isRuntimeTokenValid as v}from"../middleware/auth.js";import{getFrameBufferLimit as A,shouldDropFrameForBackpressure as I}from"./frame-backpressure.js";const h=new D,i=new F(h);function P(n,o){const t=o.searchParams.get("token");if(t)return t;const e=n.headers["x-runtime-token"];if(typeof e=="string")return e;const s=n.headers.authorization;return typeof s=="string"&&s.startsWith("Bearer ")?s.slice(7):""}function l(n,o){n.readyState===n.OPEN&&n.send(JSON.stringify(o))}function W(n,o,t){switch(t.type){case"remote-desktop:input":{const e=t.data;i.handleInput(o,e).catch(s=>{l(n,{type:"remote-desktop:error",sessionId:o,data:{error:String(s)}})});break}case"remote-desktop:stop":{i.closeSession(o),n.close();break}case"remote-desktop:quality":{const e=i.getSession(o);e&&t.data&&e.screenCapture.setQuality(t.data.quality,t.data.fps);break}case"remote-desktop:control-mode":{const e=i.getSession(o);e&&t.data&&(t.data.enabled?e.enableControl().catch(s=>{console.warn("[remote-desktop] enableControl failed:",s)}):e.stopInputBackend());break}case"remote-desktop:switch-screen":{const e=i.getSession(o);e&&t.data&&e.switchScreen(t.data.screenId||null).catch(s=>{l(n,{type:"remote-desktop:error",sessionId:o,data:{error:String(s)}})});break}case"remote-desktop:ping":{l(n,{type:"remote-desktop:pong",sessionId:o,data:t.data});break}case"remote-desktop:resync":{const e=i.getSession(o);e&&e.screenCapture.requestFullFrame();break}}}function J(n){const o=new C({noServer:!0});n.on("upgrade",(t,e,s)=>{const k=new URL(t.url||"/",`http://${t.headers.host}`);if(k.pathname!=="/remote-desktop")return;const S=P(t,k);if(!v(S)&&!h.validateToken(S)){e.write(`HTTP/1.1 401 Unauthorized\r
2
2
  \r
3
- `),t.destroy();return}o.handleUpgrade(e,t,s,n=>{const h=d.searchParams.get("agentId")||"default";i.createSession(h,m).then(a=>{const p=i.getSession(a.sessionId);p?.attachWebSocket(n),c(n,{type:"remote-desktop:start-result",sessionId:a.sessionId,data:{screens:a.screens,permissions:a.permissions}}),p&&p.startFrameLoop(f=>{n.readyState===n.OPEN&&!P(n.bufferedAmount,T(p.screenCapture.getQuality()))&&n.send(f)}),n.on("message",f=>{try{const k=f.toString();if(k.startsWith("{")){const l=JSON.parse(k);x(n,a.sessionId,l)}}catch{}}),n.on("close",()=>{i.closeSession(a.sessionId)}),n.on("error",()=>{i.closeSession(a.sessionId)})}).catch(a=>{console.error("[remote-desktop] createSession failed:",a),c(n,{type:"remote-desktop:error",sessionId:"",data:{error:String(a)}}),n.close(1011,String(a))})})})}export{B as attachRemoteDesktopWebSocketServer,g as permissionManager,i as sessionManager};
3
+ `),e.destroy();return}o.handleUpgrade(t,e,s,r=>{const b=k.searchParams.get("agentId")||"default";i.createSession(b,S).then(a=>{const c=i.getSession(a.sessionId);if(c?.attachWebSocket(r),l(r,{type:"remote-desktop:start-result",sessionId:a.sessionId,data:{screens:a.screens,permissions:a.permissions}}),c){let d=0,p=0,m=Date.now();const $=setInterval(()=>{const u=Date.now()-m,f=c.screenCapture.frameStats;console.log(`[remote-desktop] frames: sent=${d} dropped=${p} bufferedAmount=${r.bufferedAmount} silent=${u}ms quality=${c.screenCapture.getQuality()} sharp=${T()} full=${f.full} partial=${f.partial} null=${f.null} sharpFail=${f.sharpFail}`),d=0,p=0,c.screenCapture.resetFrameStats()},5e3);let g=0;c.startFrameLoop(y=>{if(r.readyState===r.OPEN&&!I(r.bufferedAmount,A(c.screenCapture.getQuality())))return r.send(M(y,g)),g=g+1>>>0,d++,m=Date.now(),!0;p++,c.screenCapture.notifyFrameDropped();const u=Date.now()-m;return u>3e3&&console.warn(`[remote-desktop] \u8FDE\u7EED ${u}ms \u65E0\u5E27\u53D1\u9001 bufferedAmount=${r.bufferedAmount} readyState=${r.readyState}`),!1}),r.on("close",()=>clearInterval($))}r.on("message",d=>{try{const p=d.toString();if(p.startsWith("{")){const m=JSON.parse(p);W(r,a.sessionId,m)}}catch{}}),r.on("close",()=>{i.closeSession(a.sessionId)}),r.on("error",()=>{i.closeSession(a.sessionId)})}).catch(a=>{console.error("[remote-desktop] createSession failed:",a),l(r,{type:"remote-desktop:error",sessionId:"",data:{error:String(a)}}),r.close(1011,String(a))})})})}export{J as attachRemoteDesktopWebSocketServer,h as permissionManager,i as sessionManager};
4
4
 
@@ -1,24 +1,99 @@
1
- import type { RemoteDesktopQuality, ScreenInfo, ScreenshotResult } from "./types.js";
1
+ import type { RemoteDesktopQuality, ScreenInfo, CaptureFrame } from "./types.js";
2
+
3
+ export declare function getSharpLoadStatus(): string;
4
+
5
+ export declare function extractRawRegion(raw: Buffer, srcWidth: number, left: number, top: number, w: number, h: number): Buffer | null;
2
6
  export declare class ScreenCapture {
7
+
8
+ private static readonly ACTIVE_INTERVAL_MS;
9
+
10
+ private static readonly STILL_FRAME_COUNT;
11
+
12
+ private static readonly IDLE_INTERVAL_MS;
13
+
14
+ private static readonly PERIODIC_FULL_FRAME_INTERVAL_MS;
3
15
  private screens;
4
- private interval;
16
+
17
+ private captureTimer;
5
18
 
6
19
  private fps;
7
20
 
8
21
  private quality;
9
22
  private activeScreenId;
23
+
10
24
  private onFrame;
11
25
 
12
- private capturing;
26
+ private captureInFlight;
27
+
28
+ private encodePending;
29
+
30
+ private encoding;
31
+
32
+ private consecutiveStillFrames;
33
+
34
+ private inputBoostFrames;
35
+
36
+ private lastRaw;
37
+ private lastRawWidth;
38
+ private lastRawHeight;
39
+
40
+ private pendingReference;
41
+ frameStats: {
42
+ full: number;
43
+ partial: number;
44
+ null: number;
45
+ sharpFail: number;
46
+ };
47
+
48
+ resetFrameStats(): void;
13
49
  listScreens(): Promise<ScreenInfo[]>;
14
50
 
15
- captureVirtualDesktop(): Promise<ScreenshotResult>;
51
+ private cachedMonitors;
52
+ private getActiveMonitor;
53
+
54
+ private acquireShot;
55
+
56
+ private encodeShot;
57
+
58
+ captureVirtualDesktop(force?: boolean): Promise<CaptureFrame | null>;
59
+
60
+ private totalTileCount;
61
+
62
+ private mapLimit;
63
+
64
+ private outputRectToSource;
65
+
66
+ private detectChangedTiles;
67
+
68
+ private commitReferenceFrame;
69
+
70
+ private discardReferenceFrame;
16
71
 
17
72
  setActiveScreen(screenId: string | null): void;
18
73
 
19
- startLoop(onFrame: (frame: Buffer) => void): void;
74
+ startLoop(onFrame: (frame: CaptureFrame) => boolean | void): void;
20
75
 
21
76
  private scheduleLoop;
77
+
78
+ private lastFullFrameAt;
79
+
80
+ private captureOnce;
81
+
82
+ private drainEncode;
83
+
84
+ private consecutiveNullCaptures;
85
+
86
+ private captureSafeFullFrame;
87
+
88
+ private currentIntervalMs;
89
+
90
+ requestFrame(): void;
91
+
92
+ private backpressureThrottleUntil;
93
+
94
+ notifyFrameDropped(): void;
95
+
96
+ requestFullFrame(): void;
22
97
  stopLoop(): void;
23
98
 
24
99
  setQuality(quality?: RemoteDesktopQuality, fps?: number): void;
@@ -1,2 +1,2 @@
1
- let o=null;async function S(){return o||(o=await import("node-screenshots")),o}let u=null;async function v(){return u||(u=await import("sharp")),u}const w={sd:{scale:.5,jpegQuality:55},hd:{scale:1,jpegQuality:72},original:{scale:1,jpegQuality:95}};class M{constructor(){this.screens=[],this.interval=null,this.fps=20,this.quality="hd",this.activeScreenId=null,this.onFrame=null,this.capturing=!1}async listScreens(){try{const t=await S(),{Monitor:i}=t,a=i.all();return this.screens=a.map((e,s)=>({id:String(e.id()),name:e.name()||`Screen ${s+1}`,x:e.x(),y:e.y(),width:e.width(),height:e.height(),scaleFactor:e.scaleFactor()})),this.screens}catch(t){return console.error("[remote-desktop] listScreens failed:",t),this.screens=[{id:"0",name:"Screen 1",x:0,y:0,width:1920,height:1080,scaleFactor:1}],this.screens}}async captureVirtualDesktop(){const t=await S(),{Monitor:i}=t,a=i.all();if(a.length===0)throw new Error("No screens available");let e=a[0];if(this.activeScreenId){const c=a.find(n=>String(n.id())===this.activeScreenId);c&&(e=c)}const s=e.captureImageSync(),r=s.width,l=s.height;let h,p=r,g=l;try{const c=await v(),n=w[this.quality],f=Math.max(1,Math.round(r*n.scale)),y=Math.max(1,Math.round(l*n.scale)),m=s.toRawSync();h=await c(m,{raw:{width:r,height:l,channels:4}}).resize(f,y,{fit:"fill"}).jpeg({quality:n.jpegQuality}).toBuffer(),p=f,g=y}catch{h=s.toJpegSync()}return{data:h,width:p,height:g}}setActiveScreen(t){this.activeScreenId=t}startLoop(t){this.stopLoop(),this.onFrame=t,this.scheduleLoop()}scheduleLoop(){if(!this.onFrame)return;const t=Math.max(16,Math.round(1e3/this.fps));this.interval=setInterval(async()=>{if(!this.capturing){this.capturing=!0;try{const i=await this.captureVirtualDesktop();this.onFrame?.(i.data)}catch{}finally{this.capturing=!1}}},t)}stopLoop(){this.interval&&(clearInterval(this.interval),this.interval=null)}setQuality(t,i){t&&Object.prototype.hasOwnProperty.call(w,t)&&(this.quality=t),i!==void 0&&(this.fps=Math.max(1,Math.min(60,i))),this.interval&&(clearInterval(this.interval),this.interval=null,this.scheduleLoop())}getQuality(){return this.quality}getScreens(){return this.screens}}export{M as ScreenCapture};
1
+ let L=null;async function x(){return L||(L=await import("node-screenshots")),L}let T=null,I="pending";async function k(){if(!T)try{const u=await import("sharp");T=u&&u.default||u,I="ok",console.log("[remote-desktop] sharp \u52A0\u8F7D\u6210\u529F\uFF0C\u542F\u7528 tile \u589E\u91CF\u7F16\u7801 + JPEG \u538B\u7F29")}catch(u){return I="failed",console.warn("[remote-desktop] sharp \u52A0\u8F7D\u5931\u8D25\uFF0C\u964D\u7EA7 toJpegSync \u6574\u5E27\u7F16\u7801\uFF08\u65E0 tile \u589E\u91CF\uFF0C\u4E8B\u4EF6\u5FAA\u73AF\u963B\u585E\uFF0C\u5E27\u7387\u964D\u4F4E\uFF09:",u instanceof Error?u.message:u),T=null,null}return T}function j(){return I}const A={sd:{scale:.5,jpegQuality:55},hd:{scale:1,jpegQuality:72},original:{scale:1,jpegQuality:95}},_=1600,w=160,C=4,O=24,N=1,P=.5,U=4;function b(u,t,e,a,s,n){if(!u||t<=0||s<=0||n<=0)return null;const i=Math.max(0,e),l=Math.max(0,a),m=Math.min(t,e+s),c=Math.min(Math.floor(u.length/4/t),a+n),o=m-i,p=c-l;if(o<=0||p<=0)return null;const f=t*4,r=Buffer.allocUnsafe(o*p*4);for(let h=0;h<p;h++){const d=(l+h)*f+i*4;u.copy(r,h*o*4,d,d+o*4)}return r}function v(u,t,e,a,s){if(!t||t.length!==u.length||e<=0||a<=0)return Number.MAX_SAFE_INTEGER;const n=Math.max(0,s.left),i=Math.max(0,s.top),l=Math.min(e,s.left+s.w),m=Math.min(a,s.top+s.h),c=e*4;let o=0;for(let p=i;p<m;p+=C){const f=p*c;for(let r=n;r<l;r+=C){const h=f+r*4;Math.abs(u[h]-t[h])+Math.abs(u[h+1]-t[h+1])+Math.abs(u[h+2]-t[h+2])>O&&o++}}return o}class M{constructor(){this.screens=[],this.captureTimer=null,this.fps=20,this.quality="hd",this.activeScreenId=null,this.onFrame=null,this.captureInFlight=!1,this.encodePending=null,this.encoding=!1,this.consecutiveStillFrames=0,this.inputBoostFrames=0,this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0,this.pendingReference=null,this.frameStats={full:0,partial:0,null:0,sharpFail:0},this.cachedMonitors=null,this.lastFullFrameAt=0,this.consecutiveNullCaptures=0,this.backpressureThrottleUntil=0}resetFrameStats(){this.frameStats={full:0,partial:0,null:0,sharpFail:0}}async listScreens(){try{const t=await x(),{Monitor:e}=t,a=e.all();return this.cachedMonitors=a,this.screens=a.map((s,n)=>({id:String(s.id()),name:s.name()||`Screen ${n+1}`,x:s.x(),y:s.y(),width:s.width(),height:s.height(),scaleFactor:s.scaleFactor()})),this.screens}catch(t){return console.error("[remote-desktop] listScreens failed:",t),this.screens=[{id:"0",name:"Screen 1",x:0,y:0,width:1920,height:1080,scaleFactor:1}],this.screens}}async getActiveMonitor(){const t=await x();let e=this.cachedMonitors;if((!e||e.length===0)&&(e=t.Monitor.all(),this.cachedMonitors=e),e.length===0)throw new Error("No screens available");if(this.activeScreenId){const a=e.find(s=>String(s.id())===this.activeScreenId);if(a)return a}return e[0]}async acquireShot(){const t=await this.getActiveMonitor();let e;typeof t.captureImage=="function"?e=await t.captureImage():e=t.captureImageSync();let a;return typeof e.toRaw=="function"?a=await e.toRaw():a=e.toRawSync(),{image:e,raw:a,srcWidth:e.width,srcHeight:e.height}}async encodeShot(t,e){const{image:a,raw:s,srcWidth:n,srcHeight:i}=t;let l=null;try{l=await k()}catch{l=null}const m=A[this.quality];let c=Math.max(1,Math.round(n*m.scale)),o=Math.max(1,Math.round(i*m.scale));const p=Math.max(c,o);if(p>_){const r=_/p;c=Math.max(1,Math.round(c*r)),o=Math.max(1,Math.round(o*r))}const f=!!this.lastRaw&&this.lastRawWidth===n&&this.lastRawHeight===i;if(!l){if(this.frameStats.sharpFail++,!e&&f&&v(s,this.lastRaw,n,i,{left:0,top:0,w:n,h:i})<N)return this.frameStats.null++,null;let r;try{r=a.toJpegSync()}catch{return null}return this.pendingReference={raw:s,width:n,height:i},this.frameStats.full++,{kind:"full",data:r,width:n,height:i}}try{if(!e&&f){const d=this.detectChangedTiles(s,n,i,c,o);if(d.changedTileCount>0){const F=this.totalTileCount(c,o);if(d.changedTileCount<Math.ceil(F*P)){const y=await this.mapLimit(d.regions,U,async g=>{const E=b(s,n,g.sx,g.sy,g.sw,g.sh);if(!E)return null;try{const D=await l(E,{raw:{width:g.sw,height:g.sh,channels:4}}).resize(g.w,g.h,{fit:"fill"}).jpeg({quality:m.jpegQuality}).toBuffer();return{x:g.x,y:g.y,w:g.w,h:g.h,data:D}}catch{return null}}),S=y.filter(g=>g!==null),R=y.length!==S.length;return S.length===0?(this.frameStats.null++,null):(R||(this.pendingReference={raw:s,width:n,height:i}),this.frameStats.partial++,{kind:"partial",width:c,height:o,tiles:S})}}else return this.frameStats.null++,null}let r,h=!1;try{r=await l(s,{raw:{width:n,height:i,channels:4}}).resize(c,o,{fit:"fill"}).jpeg({quality:m.jpegQuality}).toBuffer()}catch(d){const F=s.slice(0,8).toString("hex");console.warn("[remote-desktop] sharp \u6574\u5E27\u7F16\u7801\u5931\u8D25\uFF0Cfallback toJpegSync:",`raw.length=${s.length} expected=${n*i*4} head=${F}`,d);try{r=a.toJpegSync(),h=!0}catch{return null}}return this.pendingReference={raw:s,width:n,height:i},h?((c!==n||o!==i)&&(this.pendingReference={raw:null,width:0,height:0}),this.frameStats.full++,{kind:"full",data:r,width:n,height:i}):(this.frameStats.full++,{kind:"full",data:r,width:c,height:o})}catch{return this.frameStats.null++,null}}async captureVirtualDesktop(t=!1){const e=await this.acquireShot(),a=await this.encodeShot(e,t);return this.discardReferenceFrame(),a}totalTileCount(t,e){const a=Math.max(1,Math.ceil(t/w)),s=Math.max(1,Math.ceil(e/w));return a*s}async mapLimit(t,e,a){const s=new Array(t.length);let n=0;const i=Math.max(1,Math.min(e,t.length));return await Promise.all(Array.from({length:i},async()=>{for(;;){const l=n++;if(l>=t.length)return;s[l]=await a(t[l])}})),s}outputRectToSource(t,e,a,s,n,i,l){const m=Math.max(0,Math.min(i-1,Math.round(t/n))),c=Math.max(0,Math.min(l-1,Math.round(e/n))),o=Math.max(1,Math.min(i-m,Math.round(a/n))),p=Math.max(1,Math.min(l-c,Math.round(s/n)));return{left:m,top:c,w:o,h:p}}detectChangedTiles(t,e,a,s,n){const i=s/e,l=Math.max(1,Math.ceil(s/w)),m=Math.max(1,Math.ceil(n/w)),c=new Array(m*l).fill(!1);let o=0;for(let f=0;f<m;f++)for(let r=0;r<l;r++){const h=r*w,d=f*w,F=Math.min(w,s-h),y=Math.min(w,n-d),S=this.outputRectToSource(h,d,F,y,i,e,a);v(t,this.lastRaw,e,a,S)>=N&&(c[f*l+r]=!0,o++)}const p=[];for(let f=0;f<m;f++){let r=0;for(;r<l;){if(!c[f*l+r]){r++;continue}let h=r;for(;h+1<l&&c[f*l+h+1];)h++;const d=r*w,F=f*w,y=Math.min(w*(h-r+1),s-d),S=Math.min(w,n-F),R=this.outputRectToSource(d,F,y,S,i,e,a);p.push({x:d,y:F,w:y,h:S,sx:R.left,sy:R.top,sw:R.w,sh:R.h}),r=h+1}}return{changedTileCount:o,regions:p}}commitReferenceFrame(){this.pendingReference&&(this.pendingReference.raw===null?(this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0):(this.lastRaw=this.pendingReference.raw,this.lastRawWidth=this.pendingReference.width,this.lastRawHeight=this.pendingReference.height),this.pendingReference=null)}discardReferenceFrame(){this.pendingReference=null}setActiveScreen(t){this.activeScreenId=t}startLoop(t){this.stopLoop(),this.onFrame=t,this.scheduleLoop()}scheduleLoop(t){if(!this.onFrame)return;this.captureTimer&&(clearTimeout(this.captureTimer),this.captureTimer=null);const e=t??this.currentIntervalMs();this.captureTimer=setTimeout(()=>{this.captureTimer=null,this.captureOnce()},Math.max(0,e))}async captureOnce(){if(this.captureInFlight){this.scheduleLoop();return}this.inputBoostFrames>0&&this.inputBoostFrames--,this.captureInFlight=!0;const t=Date.now();try{const e=await this.acquireShot(),a=Date.now()-t;this.scheduleLoop(Math.max(0,this.currentIntervalMs()-a)),this.encodePending=e,this.drainEncode()}catch{this.scheduleLoop()}finally{this.captureInFlight=!1}}async drainEncode(){if(!this.encoding){this.encoding=!0;try{for(;this.encodePending;){const t=this.encodePending;this.encodePending=null;const e=this.consecutiveStillFrames>=M.STILL_FRAME_COUNT,a=Date.now()-this.lastFullFrameAt>=M.PERIODIC_FULL_FRAME_INTERVAL_MS;let s=null;try{s=await this.encodeShot(t,e||a)}catch{}if(s){this.consecutiveStillFrames=0,this.consecutiveNullCaptures=0;const n=this.onFrame;(n?n(s):!1)!==!1?(s.kind==="full"&&(this.lastFullFrameAt=Date.now()),this.commitReferenceFrame()):this.discardReferenceFrame()}else if(this.consecutiveStillFrames++,this.consecutiveNullCaptures++,this.consecutiveNullCaptures>=8&&Date.now()-this.lastFullFrameAt>=3e3){const n=await this.captureSafeFullFrame();if(n){this.consecutiveStillFrames=0,this.consecutiveNullCaptures=0;const i=this.onFrame,l=i?i(n):!1;l!==!1?(this.lastFullFrameAt=Date.now(),this.commitReferenceFrame()):this.discardReferenceFrame(),console.warn(`[remote-desktop] \u5B89\u5168\u7F51\u89E6\u53D1\uFF1A\u8FDE\u7EED null \u5DF2\u8FBE\u9608\u503C\uFF0C\u5DF2\u7528 toJpegSync \u5F3A\u5236\u53D1 full \u5E27\uFF08sent=${l!==!1}\uFF09`)}}}}finally{this.encoding=!1}}}async captureSafeFullFrame(){try{const t=await this.getActiveMonitor();let e;typeof t.captureImage=="function"?e=await t.captureImage():e=t.captureImageSync();const a=e.toJpegSync();return this.pendingReference={raw:null,width:0,height:0},{kind:"full",data:a,width:e.width,height:e.height}}catch(t){return console.warn("[remote-desktop] \u5B89\u5168\u7F51 toJpegSync \u4E5F\u5931\u8D25:",t),null}}currentIntervalMs(){if(I==="failed")return this.inputBoostFrames>0?250:this.consecutiveStillFrames>=M.STILL_FRAME_COUNT?M.IDLE_INTERVAL_MS:500;if(this.inputBoostFrames>0)return M.ACTIVE_INTERVAL_MS;if(this.consecutiveStillFrames>=M.STILL_FRAME_COUNT)return M.IDLE_INTERVAL_MS;const t=Math.max(16,Math.round(1e3/this.fps));return Date.now()<this.backpressureThrottleUntil?Math.max(t,100):t}requestFrame(){this.inputBoostFrames=Math.max(this.inputBoostFrames,3),this.captureInFlight||this.scheduleLoop()}notifyFrameDropped(){this.backpressureThrottleUntil=Date.now()+1e3}requestFullFrame(){this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0,this.scheduleLoop()}stopLoop(){this.captureTimer&&(clearTimeout(this.captureTimer),this.captureTimer=null),this.onFrame=null,this.consecutiveStillFrames=0,this.inputBoostFrames=0,this.encodePending=null,this.captureInFlight=!1,this.encoding=!1}setQuality(t,e){t&&Object.prototype.hasOwnProperty.call(A,t)&&(this.quality=t,this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0),e!==void 0&&(this.fps=Math.max(1,Math.min(60,e))),this.onFrame&&this.scheduleLoop()}getQuality(){return this.quality}getScreens(){return this.screens}}M.ACTIVE_INTERVAL_MS=33,M.STILL_FRAME_COUNT=3,M.IDLE_INTERVAL_MS=1e3,M.PERIODIC_FULL_FRAME_INTERVAL_MS=2e3;export{M as ScreenCapture,b as extractRawRegion,j as getSharpLoadStatus};
2
2
 
@@ -3,7 +3,7 @@ import { ScreenCapture } from "./screen-capture.js";
3
3
  import { InputDispatcher } from "./input-dispatcher.js";
4
4
  import { PermissionManager } from "./permission-manager.js";
5
5
  import { ComputerControl } from "./computer-control.js";
6
- import type { InputMessage, RemoteDesktopCapability, ScreenInfo, StartResult } from "./types.js";
6
+ import type { InputMessage, RemoteDesktopCapability, ScreenInfo, StartResult, CaptureFrame } from "./types.js";
7
7
  export declare class RemoteDesktopSession {
8
8
 
9
9
  static readonly FOCUS_POLL_INTERVAL_MS = 1000;
@@ -16,6 +16,7 @@ export declare class RemoteDesktopSession {
16
16
  state: "active" | "closed" | "error";
17
17
  ws: WebSocket | null;
18
18
  private inputBackend;
19
+
19
20
  private onFrame;
20
21
  private focusPollTimer;
21
22
  private lastFocusState;
@@ -40,7 +41,7 @@ export declare class RemoteDesktopSession {
40
41
 
41
42
  private sendFocusState;
42
43
  attachWebSocket(ws: WebSocket): void;
43
- startFrameLoop(onFrame: (frame: Buffer) => void): void;
44
+ startFrameLoop(onFrame: (frame: CaptureFrame) => boolean | void): void;
44
45
  stopFrameLoop(): void;
45
46
 
46
47
  handleInput(message: InputMessage): Promise<void>;
@@ -1,2 +1,2 @@
1
- import{randomUUID as a}from"node:crypto";import{ScreenCapture as c}from"./screen-capture.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ComputerControl as u}from"./computer-control.js";import{SystemBackend as p}from"./system-backend.js";class o{constructor(s,t,e){this.inputDispatcher=null,this.state="active",this.ws=null,this.onFrame=null,this.focusPollTimer=null,this.lastFocusState=null,this.inputChain=Promise.resolve(),this.id=s,this.agentId=t,this.permissions=e,this.screenCapture=new c,this.inputBackend=new p,this.computerControl=new u(this.screenCapture,this.inputBackend)}async init(){const s=await this.screenCapture.listScreens(),t=s[0];return t&&(this.inputDispatcher=new h(this.inputBackend,this.permissions,t),this.inputBackend.setScreenSize(t.width,t.height),this.inputBackend.setActiveScreen(t)),{screens:s}}async switchScreen(s){this.screenCapture.setActiveScreen(s);const t=this.screenCapture.getScreens(),e=s?t.find(i=>i.id===s):t[0];e&&this.inputDispatcher&&(this.inputDispatcher.setScreen(e),this.inputBackend.setScreenSize(e.width,e.height),this.inputBackend.setActiveScreen(e))}startInputBackend(){this.inputBackend.startPersistentProcess()}async enableControl(){this.inputBackend.startPersistentProcess(),this.startFocusPolling(),await this.inputBackend.centerMouse()}stopInputBackend(){this.stopFocusPolling(),this.inputBackend.stopPersistentProcess()}startFocusPolling(){this.stopFocusPolling(),this.focusPollTimer=setInterval(()=>{const s=this.inputBackend.isInputFocused();s!==this.lastFocusState&&(this.lastFocusState=s,this.sendFocusState(s))},o.FOCUS_POLL_INTERVAL_MS)}stopFocusPolling(){this.focusPollTimer&&(clearInterval(this.focusPollTimer),this.focusPollTimer=null),this.lastFocusState=null}sendFocusState(s){!this.ws||this.ws.readyState!==this.ws.OPEN||this.ws.send(JSON.stringify({type:"remote-desktop:focus-state",sessionId:this.id,data:{hasInputFocus:s}}))}attachWebSocket(s){this.ws=s}startFrameLoop(s){this.onFrame=s,this.screenCapture.startLoop(s)}stopFrameLoop(){this.screenCapture.stopLoop(),this.onFrame=null}handleInput(s){if(!this.inputDispatcher)return Promise.resolve();const t=this.inputChain.then(()=>this.inputDispatcher.handle(s));return this.inputChain=t.catch(()=>{}),t}close(){this.state="closed",this.stopFocusPolling(),this.stopFrameLoop(),this.inputBackend.stopPersistentProcess(),this.ws=null}}o.FOCUS_POLL_INTERVAL_MS=1e3;class P{constructor(s){this.sessions=new Map,this.agentSessions=new Map,this.permissionManager=s}async createSession(s,t){if(!this.permissionManager.validateToken(t))throw new Error("Invalid token");const e=this.agentSessions.get(s);e&&this.closeSession(e);const i=this.permissionManager.resolvePermissions(t),n=a(),r=new o(n,s,i);return await r.init(),this.sessions.set(n,r),this.agentSessions.set(s,n),{sessionId:n,screens:r.screenCapture.getScreens(),permissions:Array.from(i)}}getSession(s){return this.sessions.get(s)}async handleInput(s,t){const e=this.sessions.get(s);!e||e.state!=="active"||await e.handleInput(t)}closeSession(s){const t=this.sessions.get(s);if(t){t.close(),this.sessions.delete(s);for(const[e,i]of this.agentSessions)if(i===s){this.agentSessions.delete(e);break}}}closeAll(){for(const s of this.sessions.values())s.close();this.sessions.clear(),this.agentSessions.clear()}}export{o as RemoteDesktopSession,P as SessionManager};
1
+ import{randomUUID as a}from"node:crypto";import{ScreenCapture as c}from"./screen-capture.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ComputerControl as u}from"./computer-control.js";import{SystemBackend as p}from"./system-backend.js";class o{constructor(s,t,e){this.inputDispatcher=null,this.state="active",this.ws=null,this.onFrame=null,this.focusPollTimer=null,this.lastFocusState=null,this.inputChain=Promise.resolve(),this.id=s,this.agentId=t,this.permissions=e,this.screenCapture=new c,this.inputBackend=new p,this.computerControl=new u(this.screenCapture,this.inputBackend)}async init(){const s=await this.screenCapture.listScreens(),t=s[0];return t&&(this.inputDispatcher=new h(this.inputBackend,this.permissions,t),this.inputBackend.setScreenSize(t.width,t.height),this.inputBackend.setActiveScreen(t)),{screens:s}}async switchScreen(s){this.screenCapture.setActiveScreen(s);const t=this.screenCapture.getScreens(),e=s?t.find(i=>i.id===s):t[0];e&&this.inputDispatcher&&(this.inputDispatcher.setScreen(e),this.inputBackend.setScreenSize(e.width,e.height),this.inputBackend.setActiveScreen(e))}startInputBackend(){this.inputBackend.startPersistentProcess()}async enableControl(){this.inputBackend.startPersistentProcess(),this.startFocusPolling(),await this.inputBackend.centerMouse()}stopInputBackend(){this.stopFocusPolling(),this.inputBackend.stopPersistentProcess()}startFocusPolling(){this.stopFocusPolling(),this.focusPollTimer=setInterval(()=>{const s=this.inputBackend.isInputFocused();s!==this.lastFocusState&&(this.lastFocusState=s,this.sendFocusState(s))},o.FOCUS_POLL_INTERVAL_MS)}stopFocusPolling(){this.focusPollTimer&&(clearInterval(this.focusPollTimer),this.focusPollTimer=null),this.lastFocusState=null}sendFocusState(s){!this.ws||this.ws.readyState!==this.ws.OPEN||this.ws.send(JSON.stringify({type:"remote-desktop:focus-state",sessionId:this.id,data:{hasInputFocus:s}}))}attachWebSocket(s){this.ws=s}startFrameLoop(s){this.onFrame=s,this.screenCapture.startLoop(s)}stopFrameLoop(){this.screenCapture.stopLoop(),this.onFrame=null}handleInput(s){if(!this.inputDispatcher)return Promise.resolve();this.screenCapture.requestFrame();const t=this.inputChain.then(async()=>{await this.inputDispatcher.handle(s),this.screenCapture.requestFrame()});return this.inputChain=t.catch(()=>{}),t}close(){this.state="closed",this.stopFocusPolling(),this.stopFrameLoop(),this.inputBackend.stopPersistentProcess(),this.ws=null}}o.FOCUS_POLL_INTERVAL_MS=1e3;class P{constructor(s){this.sessions=new Map,this.agentSessions=new Map,this.permissionManager=s}async createSession(s,t){if(!this.permissionManager.validateToken(t))throw new Error("Invalid token");const e=this.agentSessions.get(s);e&&this.closeSession(e);const i=this.permissionManager.resolvePermissions(t),n=a(),r=new o(n,s,i);return await r.init(),this.sessions.set(n,r),this.agentSessions.set(s,n),{sessionId:n,screens:r.screenCapture.getScreens(),permissions:Array.from(i)}}getSession(s){return this.sessions.get(s)}async handleInput(s,t){const e=this.sessions.get(s);!e||e.state!=="active"||await e.handleInput(t)}closeSession(s){const t=this.sessions.get(s);if(t){t.close(),this.sessions.delete(s);for(const[e,i]of this.agentSessions)if(i===s){this.agentSessions.delete(e);break}}}closeAll(){for(const s of this.sessions.values())s.close();this.sessions.clear(),this.agentSessions.clear()}}export{o as RemoteDesktopSession,P as SessionManager};
2
2
 
@@ -1,5 +1,5 @@
1
1
  import{spawn as y,exec as P}from"node:child_process";import{promisify as E}from"node:util";import{mapKey as a}from"./input-backends/key-map.js";import{toOsMouseCoords as p}from"./coords.js";import{initWin32Api as S,MOUSEEVENTF_LEFTDOWN as h,MOUSEEVENTF_LEFTUP as u,MOUSEEVENTF_RIGHTDOWN as m,MOUSEEVENTF_RIGHTUP as d,MOUSEEVENTF_WHEEL as k,MOUSEEVENTF_HWHEEL as v,KEYEVENTF_KEYUP as f}from"./win32-api.js";const r=E(P);function g(){return process.platform==="win32"?"windows":process.platform==="darwin"?"macos":"linux"}class l{constructor(s){this.screenSize={width:1920,height:1080},this.activeScreen=null,this.win32Api=null,this.psProcess=null,this.psReady=!1,this.psReadyPromise=null,this.psBuffer="",this.cliclickProcess=null,this.xdotoolProcess=null,this.platform=s??g()}startPersistentProcess(){if(this.platform==="windows"){if(this.win32Api=S(),this.win32Api){console.log("[remote-desktop] Win32 FFI API ready (koffi)");return}console.warn("[remote-desktop] Win32 FFI unavailable, falling back to PowerShell"),this.startPowerShellProcess()}}startPowerShellProcess(){this.psProcess||this.psReadyPromise||(this.psReadyPromise=new Promise(s=>{const e=y("powershell.exe",["-NoProfile","-NoLogo","-Command","-"],{stdio:["pipe","pipe","pipe"],windowsHide:!0});e.stdin?.write(`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; echo "__READY__"
2
2
  `),e.stdout?.on("data",t=>{this.psBuffer+=t.toString(),this.psBuffer.includes("__READY__")&&(this.psBuffer="",this.psProcess=e,this.psReady=!0,console.log("[remote-desktop] PowerShell persistent process ready"),s(!0))}),e.stderr?.on("data",t=>{console.warn("[remote-desktop] PowerShell stderr:",t.toString())}),e.on("error",t=>{console.error("[remote-desktop] PowerShell process error:",t),this.psProcess=null,this.psReady=!1,this.psReadyPromise=null,s(!1)}),e.on("exit",()=>{this.psProcess=null,this.psReady=!1,this.psReadyPromise=null,s(!1)})}))}async ensurePowerShell(){return this.psProcess?.stdin?.writable&&this.psReady?!0:(this.psReadyPromise||this.startPowerShellProcess(),await this.psReadyPromise??!1)}stopPersistentProcess(){this.psProcess&&(this.psProcess.stdin?.end(),this.psProcess.kill(),this.psProcess=null,this.psReady=!1,this.psReadyPromise=null),this.cliclickProcess&&(this.cliclickProcess.kill(),this.cliclickProcess=null),this.xdotoolProcess&&(this.xdotoolProcess.kill(),this.xdotoolProcess=null)}psCmd(s){if(this.psProcess?.stdin?.writable&&this.psReady){this.psProcess.stdin.write(s+`
3
3
  `);return}const e=`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; ${s}`,t=Buffer.from(e,"utf16le").toString("base64");r(`powershell.exe -NoProfile -EncodedCommand ${t}`).catch(i=>{console.warn("[remote-desktop] fallback PowerShell command failed:",i.message)})}async moveMouse(s,e){const{x:t,y:i}=p(this.activeScreen,s,e,this.platform);if(this.platform==="windows"){if(this.win32Api){this.win32Api.setCursorPos(t,i);return}this.psCmd(`[W]::SetCursorPos(${t}, ${i})`)}else this.platform==="macos"?r(`cliclick m:${t},${i}`).catch(o=>this.warnInputErr("macOS moveMouse",o)):r(`xdotool mousemove ${t} ${i}`).catch(o=>this.warnInputErr("linux moveMouse",o))}async moveMouseRelative(s,e){if(this.platform==="windows"){if(this.win32Api){const o=this.win32Api.getCursorPos();o?this.win32Api.setCursorPos(o.x+Math.round(s),o.y+Math.round(e)):this.warnInputErr("win32 moveMouseRelative",new Error("getCursorPos returned null"));return}this.psCmd(`[W]::mouse_event(1, ${Math.round(s)}, ${Math.round(e)}, 0, 0)`);return}const{x:t,y:i}=await this.getMousePos();await this.moveMouse(t+s,i+e)}async centerMouse(){this.activeScreen&&await this.moveMouse(this.activeScreen.x+this.activeScreen.width/2,this.activeScreen.y+this.activeScreen.height/2)}async click(s,e,t){const{x:i,y:o}=p(this.activeScreen,s,e,this.platform);if(this.platform==="windows"){if(this.win32Api){const w=t==="right"?m:h,x=t==="right"?d:u;this.win32Api.setCursorPos(i,o),this.win32Api.mouseEvent(w,0,0,0),this.win32Api.mouseEvent(x,0,0,0);return}const n=t==="right"?8:2,c=t==="right"?16:4;this.psCmd(`[W]::SetCursorPos(${i},${o}); [W]::mouse_event(${n},0,0,0,0); [W]::mouse_event(${c},0,0,0,0)`)}else this.platform==="macos"?r(`cliclick ${t==="right"?"rc":"c"}:${i},${o}`).catch(c=>this.warnInputErr("macOS click",c)):r(`xdotool mousemove ${i} ${o} && xdotool click ${t==="right"?3:1}`).catch(c=>this.warnInputErr("linux click",c))}async doubleClick(s,e){const{x:t,y:i}=p(this.activeScreen,s,e,this.platform);if(this.platform==="windows"){if(this.win32Api){this.win32Api.setCursorPos(t,i);for(let o=0;o<2;o++)this.win32Api.mouseEvent(h,0,0,0),this.win32Api.mouseEvent(u,0,0,0);return}this.psCmd(`[W]::SetCursorPos(${t},${i}); [W]::mouse_event(2,0,0,0,0); [W]::mouse_event(4,0,0,0,0); [W]::mouse_event(2,0,0,0,0); [W]::mouse_event(4,0,0,0,0)`)}else this.platform==="macos"?r(`cliclick dd:${t},${i}`).catch(o=>this.warnInputErr("macOS doubleClick",o)):r(`xdotool mousemove ${t} ${i} && xdotool click --repeat 2 1`).catch(o=>this.warnInputErr("linux doubleClick",o))}async mouseDown(s){if(this.platform==="windows"){if(this.win32Api){const t=s==="right"?m:h;this.win32Api.mouseEvent(t,0,0,0);return}const e=s==="right"?8:2;this.psCmd(`[W]::mouse_event(${e},0,0,0,0)`)}else this.platform==="macos"?r(`cliclick ${s==="right"?"rd":"dd"}:`).catch(t=>this.warnInputErr("macOS mouseDown",t)):r(`xdotool mousedown ${s==="right"?3:1}`).catch(t=>this.warnInputErr("linux mouseDown",t))}async mouseUp(s){if(this.platform==="windows"){if(this.win32Api){const t=s==="right"?d:u;this.win32Api.mouseEvent(t,0,0,0);return}const e=s==="right"?16:4;this.psCmd(`[W]::mouse_event(${e},0,0,0,0)`)}else this.platform==="macos"?r(`cliclick ${s==="right"?"ru":"du"}:`).catch(t=>this.warnInputErr("macOS mouseUp",t)):r(`xdotool mouseup ${s==="right"?3:1}`).catch(t=>this.warnInputErr("linux mouseUp",t))}async scroll(s,e){const t=Math.max(1,Math.round(e));if(this.platform==="windows"){const i=s==="left"||s==="right",o=i?v:k,n=i?s==="right"?t*120:-t*120:s==="down"?-t*120:t*120;if(this.win32Api){this.win32Api.mouseEvent(o,0,0,n);return}this.psCmd(`[W]::mouse_event(${o},0,0,${n},0)`)}else this.platform==="macos"?r(`python3 -c "from Quartz import CGEventCreateScrollWheelEvent2, kCGScrollEventUnitPixel; from CoreGraphics import CGEventPost, kCGHIDEventTap; e = CGEventCreateScrollWheelEvent2(None, kCGScrollEventUnitPixel, ${s==="left"||s==="right"?1:0}, ${(s==="down"||s==="right"?-1:1)*t*10}); CGEventPost(kCGHIDEventTap, e)" 2>/dev/null || true`).catch(n=>this.warnInputErr("macOS scroll",n)):r(`xdotool click --repeat ${t} ${s==="up"?4:s==="down"?5:s==="left"?6:7}`).catch(o=>this.warnInputErr("linux scroll",o))}async type(s){if(this.platform==="windows"){const t=`[System.Windows.Forms.SendKeys]::SendWait('${s.replace(/[{}()[\]^%~+]/g,"{$&}").replace(/\n/g,"{ENTER}").replace(/'/g,"''")}')`;if(await this.ensurePowerShell()){this.psProcess?.stdin?.write(t+`
4
- `);return}this.psCmd(t)}else this.platform==="macos"?r(`cliclick t:'${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("macOS type",e)):r(`xdotool type --clearmodifiers '${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("linux type",e))}async keyPress(s){const e=s.map(a);if(this.platform==="windows"){if(this.win32Api){for(const t of e){const i=this.getVirtualKeyCode(t);i&&(this.win32Api.keybdEvent(i,0),await new Promise(o=>setTimeout(o,10)),this.win32Api.keybdEvent(i,f))}return}for(const t of e){const i=this.getVirtualKeyCode(t);i&&this.psCmd(`[W]::keybd_event(${i},0,0,0); Start-Sleep -Milliseconds 10; [W]::keybd_event(${i},0,2,0)`)}}else this.platform==="macos"?r(`cliclick kp:${e.join("+")}`).catch(t=>this.warnInputErr("macOS keyPress",t)):r(`xdotool key '${e.join("+")}'`).catch(t=>this.warnInputErr("linux keyPress",t))}async keyDown(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,0);return}this.psCmd(`[W]::keybd_event(${t},0,0,0)`)}else this.platform==="macos"?r(`cliclick kd:${e}`).catch(t=>this.warnInputErr("macOS keyDown",t)):r(`xdotool keydown ${e}`).catch(t=>this.warnInputErr("linux keyDown",t))}async keyUp(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,f);return}this.psCmd(`[W]::keybd_event(${t},0,2,0)`)}else this.platform==="macos"?r(`cliclick ku:${e}`).catch(t=>this.warnInputErr("macOS keyUp",t)):r(`xdotool keyup ${e}`).catch(t=>this.warnInputErr("linux keyUp",t))}getScreenSize(){return this.screenSize}setScreenSize(s,e){this.screenSize={width:s,height:e}}setActiveScreen(s){this.activeScreen=s,this.screenSize={width:s.width,height:s.height}}async getMousePos(){if(this.platform==="windows"){if(this.win32Api)return this.win32Api.getCursorPos()??{x:0,y:0};const s=`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; $p = [System.Windows.Forms.Cursor]::Position; Write-Output "$($p.X),$($p.Y)"`,e=Buffer.from(s,"utf16le").toString("base64"),{stdout:t}=await r(`powershell.exe -NoProfile -EncodedCommand ${e}`),[i,o]=t.trim().split(",").map(Number);return{x:i||0,y:o||0}}else if(this.platform==="macos"){const{stdout:s}=await r(`osascript -e 'tell application "System Events" to get position of the mouse'`),[e,t]=s.trim().split(",").map(Number);return{x:e||0,y:t||0}}else{const{stdout:s}=await r("xdotool getmouselocation --shell"),e=s.match(/X=(\d+)/),t=s.match(/Y=(\d+)/);return{x:e?Number(e[1]):0,y:t?Number(t[1]):0}}}isInputFocused(){return this.platform!=="windows"||!this.win32Api?!1:this.win32Api.isInputFocused()}warnInputErr(s,e){console.warn(`[remote-desktop] ${s} failed:`,e?.message??String(e))}getVirtualKeyCode(s){return{a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,enter:13,return:13,tab:9,escape:27,backspace:8,delete:46,up:38,down:40,left:37,right:39,home:36,end:35,pageup:33,pagedown:34,space:32,shift:16,control:17,alt:18,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123}[s.toLowerCase()]||0}}l.WIN32_INIT_TYPES=`Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class W { [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); [DllImport("user32.dll")] public static extern void mouse_event(uint f, int dx, int dy, int c, IntPtr i); [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo); }'`;export{l as SystemBackend};
4
+ `);return}this.psCmd(t)}else this.platform==="macos"?r(`cliclick t:'${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("macOS type",e)):r(`xdotool type --clearmodifiers '${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("linux type",e))}async keyPress(s){const e=s.map(a);if(this.platform==="windows"){if(this.win32Api){for(const t of e){const i=this.getVirtualKeyCode(t);i&&(this.win32Api.keybdEvent(i,0),await new Promise(o=>setTimeout(o,10)),this.win32Api.keybdEvent(i,f))}return}for(const t of e){const i=this.getVirtualKeyCode(t);i&&this.psCmd(`[W]::keybd_event(${i},0,0,0); Start-Sleep -Milliseconds 10; [W]::keybd_event(${i},0,2,0)`)}}else this.platform==="macos"?r(`cliclick kp:${e.join("+")}`).catch(t=>this.warnInputErr("macOS keyPress",t)):r(`xdotool key '${e.join("+")}'`).catch(t=>this.warnInputErr("linux keyPress",t))}async keyDown(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,0);return}this.psCmd(`[W]::keybd_event(${t},0,0,0)`)}else this.platform==="macos"?r(`cliclick kd:${e}`).catch(t=>this.warnInputErr("macOS keyDown",t)):r(`xdotool keydown ${e}`).catch(t=>this.warnInputErr("linux keyDown",t))}async keyUp(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,f);return}this.psCmd(`[W]::keybd_event(${t},0,2,0)`)}else this.platform==="macos"?r(`cliclick ku:${e}`).catch(t=>this.warnInputErr("macOS keyUp",t)):r(`xdotool keyup ${e}`).catch(t=>this.warnInputErr("linux keyUp",t))}getScreenSize(){return this.screenSize}setScreenSize(s,e){this.screenSize={width:s,height:e}}setActiveScreen(s){this.activeScreen=s,this.screenSize={width:s.width,height:s.height}}async getMousePos(){if(this.platform==="windows"){if(this.win32Api)return this.win32Api.getCursorPos()??{x:0,y:0};const s=`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; $p = [System.Windows.Forms.Cursor]::Position; Write-Output "$($p.X),$($p.Y)"`,e=Buffer.from(s,"utf16le").toString("base64"),{stdout:t}=await r(`powershell.exe -NoProfile -EncodedCommand ${e}`),[i,o]=t.trim().split(",").map(Number);return{x:i||0,y:o||0}}else if(this.platform==="macos"){const{stdout:s}=await r(`osascript -e 'tell application "System Events" to get position of the mouse'`),[e,t]=s.trim().split(",").map(Number);return{x:e||0,y:t||0}}else{const{stdout:s}=await r("xdotool getmouselocation --shell"),e=s.match(/X=(\d+)/),t=s.match(/Y=(\d+)/);return{x:e?Number(e[1]):0,y:t?Number(t[1]):0}}}isInputFocused(){return this.platform!=="windows"||!this.win32Api?!1:this.win32Api.isInputFocused()}warnInputErr(s,e){console.warn(`[remote-desktop] ${s} failed:`,e?.message??String(e))}getVirtualKeyCode(s){return{a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,enter:13,return:13,tab:9,escape:27,capslock:20,backspace:8,delete:46,up:38,down:40,left:37,right:39,home:36,end:35,pageup:33,pagedown:34,space:32,shift:16,control:17,alt:18,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123}[s.toLowerCase()]||0}}l.WIN32_INIT_TYPES=`Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class W { [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); [DllImport("user32.dll")] public static extern void mouse_event(uint f, int dx, int dy, int c, IntPtr i); [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo); }'`;export{l as SystemBackend};
5
5
 
@@ -31,6 +31,31 @@ export interface ScreenshotResult {
31
31
  height: number;
32
32
  }
33
33
 
34
+ export interface TileRect {
35
+
36
+ x: number;
37
+
38
+ y: number;
39
+
40
+ w: number;
41
+
42
+ h: number;
43
+
44
+ data: Buffer;
45
+ }
46
+
47
+ export type CaptureFrame = {
48
+ kind: "full";
49
+ data: Buffer;
50
+ width: number;
51
+ height: number;
52
+ } | {
53
+ kind: "partial";
54
+ width: number;
55
+ height: number;
56
+ tiles: TileRect[];
57
+ };
58
+
34
59
  export type RemoteDesktopQuality = "sd" | "hd" | "original";
35
60
  export interface StartResult {
36
61
  sessionId: string;
@@ -1,2 +1,2 @@
1
- import k,{AxiosError as U}from"axios";import m from"ws";import{getRuntimeAccessToken as B,normalizeSchedulerBaseUrl as h}from"./runtime-binding.js";import{loadConfigs as E,loadPrimaryLifecycleConfig as _}from"./auto-register.js";import{logger as c}from"../utils/logger.js";import{port as T}from"../config.js";const A=15e3,I=5e3,N=200,O=4001,p=new Map;function M(e){const n=e.serverUrl,r=e.instanceId;if(!n||!r)return null;try{const t=new URL(n),o=t.protocol==="https:"?"wss:":"ws:",i=B(n),l=E().find(a=>h(a.serverUrl)===n)||_(),s=i||l.userKey;if(i?c.debug("[TunnelClient] \u4F7F\u7528 runtime access token \u8FDE\u63A5\u96A7\u9053"):l.userKey&&c.warn("[TunnelClient] getRuntimeAccessToken \u8FD4\u56DE\u7A7A\uFF0C\u56DE\u9000\u5230 userKey\uFF08\u975E runtime token\uFF0C\u9274\u6743\u53EF\u80FD\u5931\u8D25\uFF09: targetUrl=",n),!s)return c.warn("[TunnelClient] \u7F3A\u5C11 runtime token\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u96A7\u9053: targetUrl=",n),null;const u=new URLSearchParams({instanceId:r});return{url:`${o}//${t.host}/ws/bridge-tunnel?${u.toString()}`,token:s}}catch(t){return c.warn("[TunnelClient] \u65E0\u6CD5\u89E3\u6790 serverUrl:",String(t)),null}}function K(e,n){const r=h(e);if(!r){c.warn("[TunnelClient] serverUrl \u65E0\u6548\uFF0C\u65E0\u6CD5\u542F\u52A8\u96A7\u9053:",e);return}const t=p.get(r);t&&(t.shuttingDown=!0,y(t),p.delete(r));const o={serverUrl:r,instanceId:n,streams:new Map,shuttingDown:!1};p.set(r,o),S(o)}function j(){for(const e of p.values())e.shuttingDown=!0,y(e);p.clear()}function y(e){e.timer&&(clearInterval(e.timer),e.timer=void 0),e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0);for(const[n,r]of e.streams.entries()){try{r.ws.close(1e3,"tunnel client stopped")}catch{}e.streams.delete(n)}if(e.ws){e.ws.removeAllListeners();try{e.ws.close(1e3,"tunnel client stopped")}catch{}e.ws=void 0}}function S(e){if(e.shuttingDown)return;const n=e.instanceId;if(!n)return;const r=M(e);if(!r){g(e);return}c.info("[TunnelClient] \u8FDE\u63A5\u96A7\u9053\u670D\u52A1\u7AEF:",r.url);const t=new m(r.url,{handshakeTimeout:15e3,headers:{"X-Runtime-Token":r.token}});e.ws=t,t.on("open",()=>{c.info("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5DF2\u5EFA\u7ACB: server=",e.serverUrl,"instanceId=",n),e.reconnectTimer&&clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0,e.timer=setInterval(()=>{t.readyState===m.OPEN&&t.send(JSON.stringify({t:"ping"}))},A)}),t.on("unexpected-response",(o,i)=>{c.error("[TunnelClient] \u670D\u52A1\u7AEF\u62D2\u7EDD\u8FDE\u63A5\uFF08\u975E 101 \u54CD\u5E94\uFF09: server=",e.serverUrl,"statusCode=",i.statusCode,"statusMessage=",i.statusMessage)}),t.on("upgrade",o=>{c.debug("[TunnelClient] \u6536\u5230\u5347\u7EA7\u54CD\u5E94: statusCode=",o.statusCode)}),t.on("message",o=>{let i;typeof o=="string"?i=o:Buffer.isBuffer(o)?i=o.toString("utf-8"):i=Buffer.from(o).toString("utf-8");try{const l=JSON.parse(i);W(e,l)}catch(l){c.warn("[TunnelClient] \u89E3\u6790\u96A7\u9053\u6D88\u606F\u5931\u8D25:",String(l))}}),t.on("close",(o,i)=>{if(c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5173\u95ED: server=",e.serverUrl,"code=",o,"reason=",String(i)),C(e),o===O){c.warn("[TunnelClient] \u9274\u6743\u5931\u8D25\uFF084001\uFF09\uFF0C\u505C\u6B62\u81EA\u52A8\u91CD\u8FDE\uFF1B\u5F85\u91CD\u65B0\u6CE8\u518C\u540E\u518D\u542F\u52A8\u96A7\u9053: server=",e.serverUrl);return}g(e)}),t.on("error",o=>{c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u9519\u8BEF: server=",e.serverUrl,"err=",o.message,"code=",o.code),C(e),g(e)})}function C(e){e.timer&&(clearInterval(e.timer),e.timer=void 0);for(const[n,r]of e.streams.entries()){try{r.ws.close(1011,"tunnel disconnected")}catch{}e.streams.delete(n)}e.ws=void 0}function g(e){e.shuttingDown||e.reconnectTimer||(e.reconnectTimer=setTimeout(()=>{e.reconnectTimer=void 0,S(e)},I))}function W(e,n){switch(n.t){case"req":x(e,n);break;case"wsopen":D(e,n);break;case"wsmsg":L(e,n);break;case"wsclose":R(e,n);break;case"ping":d(e,{t:"pong"});break;case"pong":break;default:c.debug("[TunnelClient] \u672A\u77E5\u6D88\u606F\u7C7B\u578B: t=",n.t)}}async function x(e,n){const r=n.id,t=n.method||"GET",o=n.path||"/",i=`http://127.0.0.1:${T}${o}`;try{const l=n.body?Buffer.from(n.body,"base64"):void 0,s={};if(n.headers)for(const[w,f]of Object.entries(n.headers))f&&f.length>0&&(s[w]=f[0]);s["x-bridge-tunnel-source"]=e.serverUrl;const u=await k.request({url:i,method:t,headers:s,data:l,timeout:12e4,responseType:"arraybuffer",validateStatus:()=>!0}),a={};for(const[w,f]of Object.entries(u.headers))f!==void 0&&(a[w]=Array.isArray(f)?f:[String(f)]);const v=Buffer.isBuffer(u.data)?u.data.toString("base64"):"";d(e,{t:"res",id:r,status:u.status,headers:a,body:v||null})}catch(l){const s=l instanceof U?l.message:String(l);c.warn("[TunnelClient] \u672C\u5730\u8F6C\u53D1\u5931\u8D25:",t,o,"->",s),d(e,{t:"res",id:r,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(s).toString("base64")})}}function D(e,n){const r=n.id,t=n.path||"/",o=`ws://127.0.0.1:${T}${t}`;let i;try{i=new m(o,{handshakeTimeout:1e4})}catch(s){c.warn("[TunnelClient] \u672C\u5730 WS \u8FDE\u63A5\u5931\u8D25:",String(s)),d(e,{t:"wsclose",id:r,code:1011,reason:"local ws connect failed"});return}const l={ws:i,pending:[]};e.streams.set(r,l),i.on("open",()=>{for(const s of l.pending)b(l,s.data,s.bin);l.pending=[],d(e,{t:"wsopened",id:r})}),i.on("message",(s,u)=>{const a=Buffer.isBuffer(s)?s:Buffer.from(s);d(e,{t:"wsmsg",id:r,bin:u,d:a.toString("base64")})}),i.on("close",(s,u)=>{e.streams.delete(r),d(e,{t:"wsclose",id:r,code:s,reason:String(u)})}),i.on("error",s=>{c.warn("[TunnelClient] \u672C\u5730 WS \u9519\u8BEF: id=",r,"err=",s.message),e.streams.delete(r);try{i.close(1011,s.message)}catch{}d(e,{t:"wsclose",id:r,code:1011,reason:s.message})})}function b(e,n,r){r?e.ws.send(n):e.ws.send(n.toString("utf-8"))}function L(e,n){const r=e.streams.get(n.id);if(!r)return;const t=n.d?Buffer.from(n.d,"base64"):Buffer.alloc(0);if(r.ws.readyState!==m.OPEN){r.pending.length>=N&&(r.pending.shift(),c.warn("[TunnelClient] \u672C\u5730 WS \u672A\u5C31\u7EEA\uFF0C\u6392\u961F\u6D88\u606F\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u4E00\u6761: id=",n.id)),r.pending.push({data:t,bin:!!n.bin});return}b(r,t,!!n.bin)}function R(e,n){const r=e.streams.get(n.id);if(r){e.streams.delete(n.id);try{r.ws.close(n.code??1e3,n.reason||"")}catch{}}}function d(e,n){if(!(!e.ws||e.ws.readyState!==m.OPEN))try{e.ws.send(JSON.stringify(n))}catch(r){c.warn("[TunnelClient] sendJson \u5931\u8D25:",String(r))}}export{K as startTunnelClient,j as stopTunnelClient};
1
+ import k,{AxiosError as U}from"axios";import m from"ws";import{getRuntimeAccessToken as E,normalizeSchedulerBaseUrl as h}from"./runtime-binding.js";import{loadConfigs as _,loadPrimaryLifecycleConfig as B}from"./auto-register.js";import{logger as c}from"../utils/logger.js";import{port as T}from"../config.js";const A=15e3,I=5e3,N=200,O=1*1024*1024,M=4001,w=new Map;function L(e){const n=e.serverUrl,r=e.instanceId;if(!n||!r)return null;try{const t=new URL(n),o=t.protocol==="https:"?"wss:":"ws:",i=E(n),l=_().find(a=>h(a.serverUrl)===n)||B(),s=i||l.userKey;if(i?c.debug("[TunnelClient] \u4F7F\u7528 runtime access token \u8FDE\u63A5\u96A7\u9053"):l.userKey&&c.warn("[TunnelClient] getRuntimeAccessToken \u8FD4\u56DE\u7A7A\uFF0C\u56DE\u9000\u5230 userKey\uFF08\u975E runtime token\uFF0C\u9274\u6743\u53EF\u80FD\u5931\u8D25\uFF09: targetUrl=",n),!s)return c.warn("[TunnelClient] \u7F3A\u5C11 runtime token\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u96A7\u9053: targetUrl=",n),null;const u=new URLSearchParams({instanceId:r});return{url:`${o}//${t.host}/ws/bridge-tunnel?${u.toString()}`,token:s}}catch(t){return c.warn("[TunnelClient] \u65E0\u6CD5\u89E3\u6790 serverUrl:",String(t)),null}}function j(e,n){const r=h(e);if(!r){c.warn("[TunnelClient] serverUrl \u65E0\u6548\uFF0C\u65E0\u6CD5\u542F\u52A8\u96A7\u9053:",e);return}const t=w.get(r);t&&(t.shuttingDown=!0,S(t),w.delete(r));const o={serverUrl:r,instanceId:n,streams:new Map,shuttingDown:!1};w.set(r,o),y(o)}function z(){for(const e of w.values())e.shuttingDown=!0,S(e);w.clear()}function S(e){e.timer&&(clearInterval(e.timer),e.timer=void 0),e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0);for(const[n,r]of e.streams.entries()){try{r.ws.close(1e3,"tunnel client stopped")}catch{}e.streams.delete(n)}if(e.ws){e.ws.removeAllListeners();try{e.ws.close(1e3,"tunnel client stopped")}catch{}e.ws=void 0}}function y(e){if(e.shuttingDown)return;const n=e.instanceId;if(!n)return;const r=L(e);if(!r){g(e);return}c.info("[TunnelClient] \u8FDE\u63A5\u96A7\u9053\u670D\u52A1\u7AEF:",r.url);const t=new m(r.url,{handshakeTimeout:15e3,headers:{"X-Runtime-Token":r.token}});e.ws=t,t.on("open",()=>{c.info("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5DF2\u5EFA\u7ACB: server=",e.serverUrl,"instanceId=",n),e.reconnectTimer&&clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0,e.timer=setInterval(()=>{t.readyState===m.OPEN&&t.send(JSON.stringify({t:"ping"}))},A)}),t.on("unexpected-response",(o,i)=>{c.error("[TunnelClient] \u670D\u52A1\u7AEF\u62D2\u7EDD\u8FDE\u63A5\uFF08\u975E 101 \u54CD\u5E94\uFF09: server=",e.serverUrl,"statusCode=",i.statusCode,"statusMessage=",i.statusMessage)}),t.on("upgrade",o=>{c.debug("[TunnelClient] \u6536\u5230\u5347\u7EA7\u54CD\u5E94: statusCode=",o.statusCode)}),t.on("message",o=>{let i;typeof o=="string"?i=o:Buffer.isBuffer(o)?i=o.toString("utf-8"):i=Buffer.from(o).toString("utf-8");try{const l=JSON.parse(i);R(e,l)}catch(l){c.warn("[TunnelClient] \u89E3\u6790\u96A7\u9053\u6D88\u606F\u5931\u8D25:",String(l))}}),t.on("close",(o,i)=>{if(c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5173\u95ED: server=",e.serverUrl,"code=",o,"reason=",String(i)),C(e),o===M){c.warn("[TunnelClient] \u9274\u6743\u5931\u8D25\uFF084001\uFF09\uFF0C\u505C\u6B62\u81EA\u52A8\u91CD\u8FDE\uFF1B\u5F85\u91CD\u65B0\u6CE8\u518C\u540E\u518D\u542F\u52A8\u96A7\u9053: server=",e.serverUrl);return}g(e)}),t.on("error",o=>{c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u9519\u8BEF: server=",e.serverUrl,"err=",o.message,"code=",o.code),C(e),g(e)})}function C(e){e.timer&&(clearInterval(e.timer),e.timer=void 0);for(const[n,r]of e.streams.entries()){try{r.ws.close(1011,"tunnel disconnected")}catch{}e.streams.delete(n)}e.ws=void 0}function g(e){e.shuttingDown||e.reconnectTimer||(e.reconnectTimer=setTimeout(()=>{e.reconnectTimer=void 0,y(e)},I))}function R(e,n){switch(n.t){case"req":D(e,n);break;case"wsopen":W(e,n);break;case"wsmsg":x(e,n);break;case"wsclose":P(e,n);break;case"ping":d(e,{t:"pong"});break;case"pong":break;default:c.debug("[TunnelClient] \u672A\u77E5\u6D88\u606F\u7C7B\u578B: t=",n.t)}}async function D(e,n){const r=n.id,t=n.method||"GET",o=n.path||"/",i=`http://127.0.0.1:${T}${o}`;try{const l=n.body?Buffer.from(n.body,"base64"):void 0,s={};if(n.headers)for(const[p,f]of Object.entries(n.headers))f&&f.length>0&&(s[p]=f[0]);s["x-bridge-tunnel-source"]=e.serverUrl;const u=await k.request({url:i,method:t,headers:s,data:l,timeout:12e4,responseType:"arraybuffer",validateStatus:()=>!0}),a={};for(const[p,f]of Object.entries(u.headers))f!==void 0&&(a[p]=Array.isArray(f)?f:[String(f)]);const v=Buffer.isBuffer(u.data)?u.data.toString("base64"):"";d(e,{t:"res",id:r,status:u.status,headers:a,body:v||null})}catch(l){const s=l instanceof U?l.message:String(l);c.warn("[TunnelClient] \u672C\u5730\u8F6C\u53D1\u5931\u8D25:",t,o,"->",s),d(e,{t:"res",id:r,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(s).toString("base64")})}}function W(e,n){const r=n.id,t=n.path||"/",o=`ws://127.0.0.1:${T}${t}`;let i;try{i=new m(o,{handshakeTimeout:1e4})}catch(s){c.warn("[TunnelClient] \u672C\u5730 WS \u8FDE\u63A5\u5931\u8D25:",String(s)),d(e,{t:"wsclose",id:r,code:1011,reason:"local ws connect failed"});return}const l={ws:i,pending:[]};e.streams.set(r,l),i.on("open",()=>{for(const s of l.pending)b(l,s.data,s.bin);l.pending=[],d(e,{t:"wsopened",id:r})}),i.on("message",(s,u)=>{const a=Buffer.isBuffer(s)?s:Buffer.from(s);u&&e.ws&&e.ws.bufferedAmount>O||d(e,{t:"wsmsg",id:r,bin:u,d:a.toString("base64")})}),i.on("close",(s,u)=>{e.streams.delete(r),d(e,{t:"wsclose",id:r,code:s,reason:String(u)})}),i.on("error",s=>{c.warn("[TunnelClient] \u672C\u5730 WS \u9519\u8BEF: id=",r,"err=",s.message),e.streams.delete(r);try{i.close(1011,s.message)}catch{}d(e,{t:"wsclose",id:r,code:1011,reason:s.message})})}function b(e,n,r){r?e.ws.send(n):e.ws.send(n.toString("utf-8"))}function x(e,n){const r=e.streams.get(n.id);if(!r)return;const t=n.d?Buffer.from(n.d,"base64"):Buffer.alloc(0);if(r.ws.readyState!==m.OPEN){r.pending.length>=N&&(r.pending.shift(),c.warn("[TunnelClient] \u672C\u5730 WS \u672A\u5C31\u7EEA\uFF0C\u6392\u961F\u6D88\u606F\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u4E00\u6761: id=",n.id)),r.pending.push({data:t,bin:!!n.bin});return}b(r,t,!!n.bin)}function P(e,n){const r=e.streams.get(n.id);if(r){e.streams.delete(n.id);try{r.ws.close(n.code??1e3,n.reason||"")}catch{}}}function d(e,n){if(!(!e.ws||e.ws.readyState!==m.OPEN))try{e.ws.send(JSON.stringify(n))}catch(r){c.warn("[TunnelClient] sendJson \u5931\u8D25:",String(r))}}export{j as startTunnelClient,z as stopTunnelClient};
2
2
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.9.101",
3
+ "version": "1.9.102",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",