aws-runtime-bridge 1.9.139 → 1.9.141
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.
- package/dist/adapter/AcodeSdkAdapter.d.ts +2 -1
- package/dist/adapter/AcodeSdkAdapter.js +2 -2
- package/dist/adapter/types.js +1 -1
- package/dist/config.d.ts +22 -6
- package/dist/config.js +2 -2
- package/dist/index.js +1 -1
- package/dist/remote-desktop/computer-control.d.ts +1 -1
- package/dist/remote-desktop/index.js +2 -2
- package/dist/remote-desktop/screen-capture.js +1 -1
- package/dist/remote-desktop/session-manager.js +1 -1
- package/dist/remote-desktop/types.d.ts +1 -1
- package/dist/remote-desktop/ui-automation.d.ts +2 -2
- package/dist/remote-desktop/ui-automation.js +5 -5
- package/dist/routes/ai-sources.js +1 -1
- package/dist/routes/background-tasks.js +5 -5
- package/dist/routes/dashboard.js +1 -1
- package/dist/routes/git.js +20 -20
- package/dist/routes/terminal.js +27 -27
- package/dist/services/agent-capability-sync.js +1 -1
- package/dist/services/ai-config-service.js +2 -2
- package/dist/services/auto-register.js +1 -1
- package/dist/services/aws-client-agent-mcp.d.ts +5 -0
- package/dist/services/aws-client-agent-mcp.js +2 -2
- package/dist/services/browser-session-manager.js +3 -3
- package/dist/services/capability-config.js +2 -2
- package/dist/services/event-delta-pusher.d.ts +1 -1
- package/dist/services/fileSnapshotStore.js +1 -1
- package/dist/services/instance-ai-config-service.js +1 -1
- package/dist/services/instance-init-service.js +1 -1
- package/dist/services/instance-state.d.ts +1 -1
- package/dist/services/instance-state.js +1 -1
- package/dist/services/legacy-path-migration.d.ts +0 -2
- package/dist/services/legacy-path-migration.js +1 -1
- package/dist/services/mcp-deploy.js +2 -2
- package/dist/services/plugin-manager.d.ts +1 -1
- package/dist/services/plugin-manager.js +9 -9
- package/dist/services/skill-manager.js +2 -2
- package/package/acode/dist/cli.js +2 -2
- package/package/acode/dist/runtime.d.ts +2 -0
- package/package/acode/dist/runtime.js +12 -12
- package/package/acode/dist/tool-allocation.js +1 -1
- package/package/acode/dist/types.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +2 -4
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +3 -3
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +0 -2
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +8 -8
- package/package/aws-client-agent-mcp/dist/status-reporter.js +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +9 -7
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
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};
|
|
1
|
+
let E=null;async function A(){return E||(E=await import("node-screenshots")),E}let I=null,L="pending";async function O(){if(!I)try{const f=await import("sharp");I=f&&f.default||f,L="ok",console.log("[remote-desktop] sharp \u52A0\u8F7D\u6210\u529F\uFF0C\u542F\u7528 tile \u589E\u91CF\u7F16\u7801 + JPEG \u538B\u7F29")}catch(f){return L="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:",f instanceof Error?f.message:f),I=null,null}return I}function B(){return L}const _={sd:{scale:.5,jpegQuality:55},hd:{scale:1,jpegQuality:72},original:{scale:1,jpegQuality:95}},C=1600,M=160,N=4,P=24,v=1,U=.5,b=4;function j(f,t,e,a,s,n){if(!f||t<=0||s<=0||n<=0)return null;const r=Math.max(0,e),l=Math.max(0,a),u=Math.min(t,e+s),o=Math.min(Math.floor(f.length/4/t),a+n),h=u-r,m=o-l;if(h<=0||m<=0)return null;const g=t*4,c=Buffer.allocUnsafe(h*m*4);for(let i=0;i<m;i++){const d=(l+i)*g+r*4;f.copy(c,i*h*4,d,d+h*4)}return c}function D(f,t,e,a,s){if(!t||t.length!==f.length||e<=0||a<=0)return Number.MAX_SAFE_INTEGER;const n=Math.max(0,s.left),r=Math.max(0,s.top),l=Math.min(e,s.left+s.w),u=Math.min(a,s.top+s.h),o=e*4;let h=0;for(let m=r;m<u;m+=N){const g=m*o;for(let c=n;c<l;c+=N){const i=g+c*4;Math.abs(f[i]-t[i])+Math.abs(f[i+1]-t[i+1])+Math.abs(f[i+2]-t[i+2])>P&&h++}}return h}class F{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 A(),{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 A();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:r}=t;let l=null;try{l=await O()}catch{l=null}const u=_[this.quality];let o=Math.max(1,Math.round(n*u.scale)),h=Math.max(1,Math.round(r*u.scale));const m=Math.max(o,h);if(m>C){const i=C/m;o=Math.max(1,Math.round(o*i)),h=Math.max(1,Math.round(h*i))}const g=this.lastRaw,c=!!g&&this.lastRawWidth===n&&this.lastRawHeight===r;if(!l){if(this.frameStats.sharpFail++,!e&&c&&g&&D(s,g,n,r,{left:0,top:0,w:n,h:r})<v)return this.frameStats.null++,null;let i;try{i=a.toJpegSync()}catch{return null}return this.pendingReference={raw:s,width:n,height:r},this.frameStats.full++,{kind:"full",data:i,width:n,height:r}}try{if(!e&&c&&g){const w=this.detectChangedTiles(g,s,n,r,o,h);if(w.changedTileCount>0){const S=this.totalTileCount(o,h);if(w.changedTileCount<Math.ceil(S*U)){const R=await this.mapLimit(w.regions,b,async p=>{const x=j(s,n,p.sx,p.sy,p.sw,p.sh);if(!x)return null;try{const k=await l(x,{raw:{width:p.sw,height:p.sh,channels:4}}).resize(p.w,p.h,{fit:"fill"}).jpeg({quality:u.jpegQuality}).toBuffer();return{x:p.x,y:p.y,w:p.w,h:p.h,data:k}}catch{return null}}),y=R.filter(p=>p!==null),T=R.length!==y.length;return y.length===0?(this.frameStats.null++,null):(T||(this.pendingReference={raw:s,width:n,height:r}),this.frameStats.partial++,{kind:"partial",width:o,height:h,tiles:y})}}else return this.frameStats.null++,null}let i,d=!1;try{i=await l(s,{raw:{width:n,height:r,channels:4}}).resize(o,h,{fit:"fill"}).jpeg({quality:u.jpegQuality}).toBuffer()}catch(w){const S=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*r*4} head=${S}`,w);try{i=a.toJpegSync(),d=!0}catch{return null}}return this.pendingReference={raw:s,width:n,height:r},d?((o!==n||h!==r)&&(this.pendingReference={raw:null,width:0,height:0}),this.frameStats.full++,{kind:"full",data:i,width:n,height:r}):(this.frameStats.full++,{kind:"full",data:i,width:o,height:h})}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/M)),s=Math.max(1,Math.ceil(e/M));return a*s}async mapLimit(t,e,a){const s=new Array(t.length);let n=0;const r=Math.max(1,Math.min(e,t.length));return await Promise.all(Array.from({length:r},async()=>{for(;;){const l=n++;if(l>=t.length)return;s[l]=await a(t[l])}})),s}outputRectToSource(t,e,a,s,n,r,l){const u=Math.max(0,Math.min(r-1,Math.round(t/n))),o=Math.max(0,Math.min(l-1,Math.round(e/n))),h=Math.max(1,Math.min(r-u,Math.round(a/n))),m=Math.max(1,Math.min(l-o,Math.round(s/n)));return{left:u,top:o,w:h,h:m}}detectChangedTiles(t,e,a,s,n,r){const l=n/a,u=Math.max(1,Math.ceil(n/M)),o=Math.max(1,Math.ceil(r/M)),h=new Array(o*u).fill(!1);let m=0;for(let c=0;c<o;c++)for(let i=0;i<u;i++){const d=i*M,w=c*M,S=Math.min(M,n-d),R=Math.min(M,r-w),y=this.outputRectToSource(d,w,S,R,l,a,s);D(e,t,a,s,y)>=v&&(h[c*u+i]=!0,m++)}const g=[];for(let c=0;c<o;c++){let i=0;for(;i<u;){if(!h[c*u+i]){i++;continue}let d=i;for(;d+1<u&&h[c*u+d+1];)d++;const w=i*M,S=c*M,R=Math.min(M*(d-i+1),n-w),y=Math.min(M,r-S),T=this.outputRectToSource(w,S,R,y,l,a,s);g.push({x:w,y:S,w:R,h:y,sx:T.left,sy:T.top,sw:T.w,sh:T.h}),i=d+1}}return{changedTileCount:m,regions:g}}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>=F.STILL_FRAME_COUNT,a=Date.now()-this.lastFullFrameAt>=F.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 r=this.onFrame,l=r?r(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(L==="failed")return this.inputBoostFrames>0?250:this.consecutiveStillFrames>=F.STILL_FRAME_COUNT?F.IDLE_INTERVAL_MS:500;if(this.inputBoostFrames>0)return F.ACTIVE_INTERVAL_MS;if(this.consecutiveStillFrames>=F.STILL_FRAME_COUNT)return F.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(_,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}}F.ACTIVE_INTERVAL_MS=33,F.STILL_FRAME_COUNT=3,F.IDLE_INTERVAL_MS=1e3,F.PERIODIC_FULL_FRAME_INTERVAL_MS=2e3;export{F as ScreenCapture,j as extractRawRegion,B as getSharpLoadStatus};
|
|
2
2
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as a}from"node:crypto";import{ComputerControl as c}from"./computer-control.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ScreenCapture as u}from"./screen-capture.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 u,this.inputBackend=new p,this.computerControl=new c(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(!
|
|
1
|
+
import{randomUUID as a}from"node:crypto";import{ComputerControl as c}from"./computer-control.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ScreenCapture as u}from"./screen-capture.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 u,this.inputBackend=new p,this.computerControl=new c(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){const t=this.inputDispatcher;if(!t)return Promise.resolve();this.screenCapture.requestFrame();const e=this.inputChain.then(async()=>{await t.handle(s),this.screenCapture.requestFrame()});return this.inputChain=e.catch(()=>{}),e}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
|
|
|
@@ -32,9 +32,9 @@ export declare class UIAutomation {
|
|
|
32
32
|
|
|
33
33
|
getUIElements(pid: number): Promise<UIElement[]>;
|
|
34
34
|
|
|
35
|
-
clickElement(
|
|
35
|
+
clickElement(_elementId: string): Promise<void>;
|
|
36
36
|
|
|
37
|
-
setElementValue(
|
|
37
|
+
setElementValue(_elementId: string, _value: string): Promise<void>;
|
|
38
38
|
private listAppsWindows;
|
|
39
39
|
private parseWindowsApps;
|
|
40
40
|
private getUIElementsWindows;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{exec as
|
|
2
|
-
`).filter(Boolean).map(o=>{const e=o.split(" "),[s,
|
|
3
|
-
`).filter(Boolean).map((o,e)=>{const s=o.split(" "),[
|
|
1
|
+
import{exec as c}from"node:child_process";import{promisify as y}from"node:util";const p=y(c);function r(l){return typeof l=="number"?l:void 0}class h{async listApplications(){return process.platform==="win32"?this.listAppsWindows():process.platform==="darwin"?this.listAppsMacOS():this.listAppsLinux()}async getUIElements(t){return process.platform==="win32"?this.getUIElementsWindows(t):process.platform==="darwin"?this.getUIElementsMacOS(t):this.getUIElementsLinux(t)}async clickElement(t){throw new Error(`clickElement not yet implemented for ${process.platform}. Use click(x, y) with element bounds.`)}async setElementValue(t,n){throw new Error(`setElementValue not yet implemented for ${process.platform}. Use type(text) with element focus.`)}async listAppsWindows(){const t='powershell.exe -NoProfile -Command "Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes; $root = [System.Windows.Automation.AutomationElement]::RootElement; $cond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ControlTypeProperty, [System.Windows.Automation.ControlType]::Window); $wins = $root.FindAll([System.Windows.Automation.TreeScope]::Children, $cond); $result = $wins | ForEach-Object { $r = $_.Current.BoundingRectangle; [PSCustomObject]@{ pid=$_.Current.ProcessId; name=$_.Current.ProcessName; title=$_.Current.Name; x=$r.X; y=$r.Y; w=$r.Width; h=$r.Height } }; $result | ConvertTo-Json -Compress"';try{const{stdout:n}=await p(t,{timeout:1e4,maxBuffer:1048576});return this.parseWindowsApps(n)}catch{return[]}}parseWindowsApps(t){try{const n=JSON.parse(t.trim());return(Array.isArray(n)?n:n?[n]:[]).map((e,s)=>({pid:typeof e.pid=="number"?e.pid:0,name:typeof e.name=="string"?e.name:`Process ${String(e.pid??"")}`,isForeground:!1,windowTitle:typeof e.title=="string"?e.title:void 0,windowBounds:typeof e.w=="number"?{x:r(e.x)??0,y:r(e.y)??0,width:e.w,height:r(e.h)??0}:void 0}))}catch{return[]}}async getUIElementsWindows(t){const n=`powershell.exe -NoProfile -Command "Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes; $root = [System.Windows.Automation.AutomationElement]::RootElement; $pidCond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ProcessIdProperty, ${t}); $app = $root.FindFirst([System.Windows.Automation.TreeScope]::Children, $pidCond); if ($app) { $children = $app.FindAll([System.Windows.Automation.TreeScope]::Descendants, [System.Windows.Automation.Condition]::TrueCondition); $children | ForEach-Object { $r = $_.Current.BoundingRectangle; [PSCustomObject]@{ id=$_.GetRuntimeId(); role=$_.Current.ControlType.ProgrammaticName; name=$_.Current.Name; value=$_.Current.HelpText; x=$r.X; y=$r.Y; w=$r.Width; h=$r.Height } } | ConvertTo-Json -Compress }"`;try{const{stdout:o}=await p(n,{timeout:1e4,maxBuffer:1048576});return this.parseWindowsElements(o)}catch{return[]}}parseWindowsElements(t){try{const n=JSON.parse(t.trim());return(Array.isArray(n)?n:n?[n]:[]).map(e=>({id:String(e.id??""),role:(typeof e.role=="string"?e.role:"").replace("ControlType.",""),name:typeof e.name=="string"?e.name:void 0,value:typeof e.value=="string"?e.value:void 0,bounds:typeof e.w=="number"?{x:r(e.x)??0,y:r(e.y)??0,width:e.w,height:r(e.h)??0}:void 0}))}catch{return[]}}async listAppsMacOS(){const t=`osascript -e 'tell application "System Events" to set appList to (every process whose background only is false)' -e 'set output to ""' -e 'repeat with proc in appList' -e 'set procName to name of proc' -e 'set procPid to unix id of proc' -e 'set winList to every window of proc' -e 'repeat with win in winList' -e 'try' -e 'set winName to name of win' -e 'set {winX, winY, winW, winH} to {position, size} of win' -e 'set output to output & procPid & "\\t" & procName & "\\t" & winName & "\\t" & winX & "," & winY & "," & winW & "," & winH & "\\n"' -e 'end try' -e 'end repeat' -e 'end repeat' -e 'output'`;try{const{stdout:n}=await p(t,{timeout:1e4});return this.parseMacOSApps(n)}catch{return[]}}parseMacOSApps(t){return t.trim().split(`
|
|
2
|
+
`).filter(Boolean).map(o=>{const e=o.split(" "),[s,u,m,d]=e,i=d?d.split(",").map(Number):[];return{pid:Number(s)||0,name:u||"",isForeground:!1,windowTitle:m||void 0,windowBounds:i.length===4?{x:i[0],y:i[1],width:i[2],height:i[3]}:void 0}})}async getUIElementsMacOS(t){const n=`osascript -e 'tell application "System Events"' -e 'set proc to first process whose unix id is ${t}' -e 'set output to ""' -e 'repeat with win in (every window of proc)' -e 'set winName to name of win' -e 'set elemList to every UI element of win' -e 'repeat with elem in elemList' -e 'set eRole to role of elem' -e 'set eName to name of elem' -e 'set eDesc to description of elem' -e 'set {eX, eY} to position of elem' -e 'set {eW, eH} to size of elem' -e 'set output to output & eRole & "\\t" & eName & "\\t" & eDesc & "\\t" & eX & "," & eY & "," & eW & "," & eH & "\\n"' -e 'end repeat' -e 'end repeat' -e 'end tell' -e 'output'`;try{const{stdout:o}=await p(n,{timeout:1e4});return this.parseMacOSElements(o)}catch{return[]}}parseMacOSElements(t){return t.trim().split(`
|
|
3
|
+
`).filter(Boolean).map((o,e)=>{const s=o.split(" "),[u,m,d,i]=s,a=i?i.split(",").map(Number):[];return{id:`mac-${e}`,role:(u||"").replace("AX","").toLowerCase(),name:m||void 0,value:d||void 0,bounds:a.length===4?{x:a[0],y:a[1],width:a[2],height:a[3]}:void 0}})}async listAppsLinux(){const t=`python3 -c "
|
|
4
4
|
import pyatspi, json
|
|
5
5
|
desktop = pyatspi.Registry.getDesktop(0)
|
|
6
6
|
apps = []
|
|
@@ -9,7 +9,7 @@ for i in range(desktop.childCount):
|
|
|
9
9
|
if app:
|
|
10
10
|
apps.append({'pid': app.getApplicationID(), 'name': app.name, 'role': app.getRoleName()})
|
|
11
11
|
print(json.dumps(apps))
|
|
12
|
-
" 2>/dev/null`;try{const{stdout:n}=await
|
|
12
|
+
" 2>/dev/null`;try{const{stdout:n}=await p(t,{timeout:1e4}),o=JSON.parse(n.trim());return(Array.isArray(o)?o:o?[o]:[]).map(e=>({pid:typeof e.pid=="number"?e.pid:0,name:typeof e.name=="string"?e.name:"",isForeground:!1,windowTitle:typeof e.name=="string"?e.name:void 0}))}catch{return[]}}async getUIElementsLinux(t){const n=`python3 -c "
|
|
13
13
|
import pyatspi, json
|
|
14
14
|
desktop = pyatspi.Registry.getDesktop(0)
|
|
15
15
|
elements = []
|
|
@@ -27,5 +27,5 @@ for i in range(desktop.childCount):
|
|
|
27
27
|
'x': b.x, 'y': b.y, 'w': b.width, 'h': b.height
|
|
28
28
|
})
|
|
29
29
|
print(json.dumps(elements))
|
|
30
|
-
" 2>/dev/null`;try{const{stdout:o}=await
|
|
30
|
+
" 2>/dev/null`;try{const{stdout:o}=await p(n,{timeout:1e4}),e=JSON.parse(o.trim());return(Array.isArray(e)?e:e?[e]:[]).map(s=>({id:String(s.id??""),role:typeof s.role=="string"?s.role:"",name:typeof s.name=="string"?s.name:void 0,bounds:typeof s.w=="number"?{x:r(s.x)??0,y:r(s.y)??0,width:s.w,height:r(s.h)??0}:void 0}))}catch{return[]}}}export{h as UIAutomation};
|
|
31
31
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import m from"axios";import{Router as U}from"express";import{validateToken as f}from"../middleware/auth.js";import{createAiConfigService as $}from"../services/ai-config-service.js";import{applyCurrentAiConfigToInstalledTools as k}from"../services/ai-source-apply.js";import{detectToolStatuses as z}from"../services/tool-installer.js";import{createLogger as C}from"../utils/logger.js";import{ensureEnabledTools as F}from"../utils/validation.js";const u=C("ai-sources"),g=U();function S(r){if(!r||typeof r!="object"||Array.isArray(r))return[];const o=r,d=Array.isArray(o.data)?o.data:[],e=new Set;for(const a of d){if(!a||typeof a!="object"||Array.isArray(a))continue;const t=String(a.id||"").trim();t&&e.add(t)}return[...e]}async function R(r,o,d){const e=r.replace(/\/+$/,"");if(d==="anthropic-compatible"){const t=`${e.endsWith("/v1")?e.replace(/\/v1$/,""):e}/v1/models`,
|
|
1
|
+
import m from"axios";import{Router as U}from"express";import{validateToken as f}from"../middleware/auth.js";import{createAiConfigService as $}from"../services/ai-config-service.js";import{applyCurrentAiConfigToInstalledTools as k}from"../services/ai-source-apply.js";import{detectToolStatuses as z}from"../services/tool-installer.js";import{createLogger as C}from"../utils/logger.js";import{ensureEnabledTools as F}from"../utils/validation.js";const u=C("ai-sources"),g=U();function S(r){if(!r||typeof r!="object"||Array.isArray(r))return[];const o=r,d=Array.isArray(o.data)?o.data:[],e=new Set;for(const a of d){if(!a||typeof a!="object"||Array.isArray(a))continue;const t=String(a.id||"").trim();t&&e.add(t)}return[...e]}async function R(r,o,d){const e=r.replace(/\/+$/,"");if(d==="anthropic-compatible"){const t=`${e.endsWith("/v1")?e.replace(/\/v1$/,""):e}/v1/models`,s=await m.get(t,{headers:{"x-api-key":o,"anthropic-version":"2023-06-01"},timeout:1e4});return S(s.data)}const a=e.endsWith("/v1")?[`${e}/models`]:[`${e}/models`,`${e}/v1/models`];for(const t of a)try{const s=await m.get(t,{headers:{Authorization:`Bearer ${o}`},timeout:1e4}),i=S(s.data);if(i.length>0)return i}catch{}return[]}g.post("/cc-switch/apply-ai-sources",f,async(r,o)=>{const{agentId:d,template:e,ccSwitchEnabledTools:a,acodeTemplateId:t,claudecodeTemplateId:s,codexTemplateId:i,opencodeTemplateId:n,acodeModelEffort:y,claudecodeModelEffort:T,codexModelEffort:A,opencodeModelEffort:I,acodeSubAgentTemplateId:b,acodeSubAgentModel:h,acodeSubAgentModelEffort:M,acode:E,claudecode:v,codex:w,opencode:j}=r.body||{};if(!d){o.status(400).json({error:"agentId is required"});return}try{const l=$(),c=F(a),x=await z(c),p=await k({template:e,requestedSources:{acode:E,claudecode:v,codex:w,opencode:j},toolStatus:x,aiConfigService:l});o.json({ok:!0,agentId:String(d),acodeTemplateId:String(t||""),claudecodeTemplateId:String(s||""),codexTemplateId:String(i||""),opencodeTemplateId:String(n||""),acodeModelEffort:String(y||""),claudecodeModelEffort:String(T||""),codexModelEffort:String(A||""),opencodeModelEffort:String(I||""),acodeSubAgentTemplateId:String(b||""),acodeSubAgentModel:String(h||""),acodeSubAgentModelEffort:String(M||""),generatedFromUnifiedTemplate:p.generatedFromUnifiedTemplate,applied:p.applied,skippedTools:p.skippedTools})}catch(l){const c=l;u.error("Failed to apply AI sources",{error:c?.message,agentId:String(d||"")}),o.status(400).json({error:c?.message||"apply ai sources failed"})}}),g.post("/cc-switch/list-ai-models",f,async(r,o)=>{const{baseUrl:d,apiKey:e,protocolType:a}=r.body||{},t=String(d||"").trim(),s=String(e||"").trim();if(!t||!s){o.status(400).json({error:"baseUrl and apiKey are required"});return}try{const i=await R(t,s,a);o.json({ok:!0,models:i})}catch(i){const n=i;u.error("Failed to list remote AI models",{error:n?.message,baseUrl:t}),o.status(400).json({error:n?.message||"list ai models failed"})}});export{g as aiSourcesRouter};
|
|
2
2
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{promises as
|
|
2
|
-
`);return
|
|
3
|
-
`),t.write(`data: ${JSON.stringify(
|
|
1
|
+
import{promises as h}from"node:fs";import y from"node:path";import{pathToFileURL as b}from"node:url";import{Router as j}from"express";import{getAcodeDataHome as B}from"../config.js";import{validateToken as k}from"../middleware/auth.js";import{getAcodeBgManager as I}from"../services/acode-bg-manager.js";import{getReleasedAcodeEntryPath as T}from"../services/acode-package.js";import{createLogger as E}from"../utils/logger.js";import{listPtySessions as _}from"./pty.js";let m=null;function S(){return m||(m=(async()=>{try{const a=T(),t=y.join(y.dirname(a),"background-command-store.js"),r=await import(b(t).href);return r.BackgroundCommandStore?new r.BackgroundCommandStore:(g.warn("[acode-bg-tasks] BackgroundCommandStore \u4E0D\u53EF\u7528,\u78C1\u76D8\u515C\u5E95\u7981\u7528"),null)}catch(a){return g.warn("[acode-bg-tasks] \u52A0\u8F7D BackgroundCommandStore \u5931\u8D25: %s",a?.message),null}})().then(a=>(a||(m=null),a)),m)}const g=E("acode-bg-tasks"),p=j();function L(a){return String(a).replace(/[^a-zA-Z0-9_-]/g,"_")}function v(a){return y.join(B(),"bg-tasks",L(a),"state.json")}p.get("/acode-bg-tasks/:agentId",k,async(a,t)=>{const r=a.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const o=v(String(i));try{const s=await h.readFile(o,"utf-8"),d=JSON.parse(s),u=(Array.isArray(d.tasks)?d.tasks:[]).map(l=>{const e=l;return{taskId:e.taskId,status:e.status,command:e.commandPreview??e.command,commandPreview:e.commandPreview,cwd:e.cwd,startedAt:e.startedAt,finishedAt:e.finishedAt,exitCode:e.exitCode,signal:e.signal,outputLineCount:e.outputLineCount,outputBytes:e.outputBytes}});t.json({ok:!0,agentId:String(i),updatedAt:typeof d.updatedAt=="number"?d.updatedAt:null,tasks:u})}catch(s){if((typeof s=="object"&&s!==null?Reflect.get(s,"code"):void 0)==="ENOENT"){t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}if(s instanceof SyntaxError){g.warn("[acode-bg-tasks] \u6587\u4EF6\u635F\u574F\uFF08JSON \u89E3\u6790\u5931\u8D25\uFF09: %s",o),t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}const n=s;g.error("[acode-bg-tasks] \u8BFB\u53D6\u5931\u8D25: %s",n.message),t.status(500).json({ok:!1,error:n.message})}}),p.get("/acode-agent-pty-sessions/:agentId",k,(a,t)=>{const r=a.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const o=String(i),d=_().filter(n=>n.ownerUserId===o);t.json({ok:!0,agentId:o,sessions:d})});const P=15e3;function x(a){return{taskId:a.taskId,status:a.status,exitCode:a.exitCode,signal:a.signal,startedAt:a.startedAt,finishedAt:a.finishedAt,command:a.command,lines:a.lines,nextSeq:a.nextSeq,totalLines:a.totalLines,totalBytes:a.totalBytes,truncated:a.truncated}}async function A(a,t){let i=null;try{i=await h.open(a,"r")}catch(o){if(o?.code==="ENOENT")return{text:[],fileExists:!1,truncated:!1};throw o}try{const s=(await i.stat()).size;if(s===0)return{text:[],fileExists:!0,truncated:!1};const d=Math.min(s,2097152),n=s-d,u=Buffer.alloc(d);await i.read(u,0,d,n);const e=u.toString("utf-8").split(`
|
|
2
|
+
`);return n>0&&e.shift(),{text:e.slice(-t),fileExists:!0,truncated:d<s}}finally{await i.close()}}p.get("/acode-bg-tasks/:agentId/:taskId/output",k,(a,t)=>{(async()=>{const r=String(a.params.agentId),i=String(a.params.taskId),o=Math.min(2e3,Math.max(100,Number(a.query.tailLines)||500));try{const s=await I();if(s){const d=s.getTaskOutputSnapshot(r,i);if(d){t.json({ok:!0,source:"memory",...x(d)});return}}}catch(s){g.warn("[acode-bg-tasks] \u5185\u5B58\u5FEB\u7167\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",s?.message)}try{const s=await S();if(!s){t.status(503).json({ok:!1,error:"store unavailable"});return}const d=await s.readSession(r);if(!d){t.status(404).json({ok:!1,error:"task_not_found"});return}const n=d.tasks.find(f=>f.taskId===i);if(!n){t.status(404).json({ok:!1,error:"task_not_found"});return}const{text:u,fileExists:l,truncated:e}=await A(n.outputPath,o);t.json({ok:!0,source:"file",taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:u,nextSeq:0,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:e||!l})}catch(s){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95\u8BFB\u53D6\u5931\u8D25: %s",s?.message),t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] output \u8BFB\u53D6\u5931\u8D25: %s",r?.message),t.status(500).json({ok:!1,error:"internal_error"})})}),p.get("/acode-bg-tasks/:agentId/:taskId/stream",k,(a,t)=>{(async()=>{const r=String(a.params.agentId),i=String(a.params.taskId);t.status(200),t.set({"Content-Type":"text/event-stream","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.flushHeaders?.();const o=(u,l)=>{t.writableEnded||t.destroyed||(l!==void 0&&t.write(`id: ${l}
|
|
3
|
+
`),t.write(`data: ${JSON.stringify(u)}
|
|
4
4
|
|
|
5
|
-
`))};let
|
|
5
|
+
`))};let s,d=null,n=null;try{s=await I(),s&&(d=s,n=s.getTaskOutputSnapshot(r,i))}catch(u){g.warn("[acode-bg-tasks] \u5185\u5B58\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",u?.message)}if(n){if(!d){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}if(o({type:"snapshot",source:"memory",...x(n)}),n.status!=="running"){o({type:"done",status:n.status,exitCode:n.exitCode,signal:n.signal}),t.end();return}let u=null,l=!1;const e=()=>{l||(l=!0,u?.(),clearInterval(f))},f=setInterval(()=>{if(t.writableEnded||t.destroyed){e();return}try{t.write(`: ping
|
|
6
6
|
|
|
7
|
-
`)}catch{
|
|
7
|
+
`)}catch{e()}},P);if(f.unref?.(),u=d.subscribeTaskOutput(r,i,{onLine:c=>o({type:"output",stream:c.stream,text:c.text,seq:c.seq},c.seq),onDone:c=>{o({type:"done",status:c.status,exitCode:c.exitCode,signal:c.signal}),e(),t.end()}}),!u){o({type:"done",status:n.status,exitCode:n.exitCode,signal:n.signal}),e(),t.end();return}a.on("close",()=>{e(),t.writableEnded||t.end()}),a.on("aborted",()=>e());return}try{const u=await S();if(!u){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const l=await u.readSession(r);if(!l){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const e=l.tasks.find(C=>C.taskId===i);if(!e){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const{text:f,fileExists:c,truncated:w}=await A(e.outputPath,500);o({type:"snapshot",source:"file",taskId:e.taskId,status:e.status,exitCode:e.exitCode,signal:e.signal,startedAt:e.startedAt,finishedAt:e.finishedAt,command:e.command,lines:f,nextSeq:0,totalLines:e.outputLineCount,totalBytes:e.outputBytes,truncated:w||!c}),o({type:"done",status:e.status,exitCode:e.exitCode,signal:e.signal}),t.end()}catch(u){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95 stream \u5931\u8D25: %s",u?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] stream \u5EFA\u7ACB\u5931\u8D25: %s",r?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})})});export{p as backgroundTasksRouter};
|
|
8
8
|
|
package/dist/routes/dashboard.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{execFile as $}from"node:child_process";import v from"node:fs";import I from"node:os";import K from"node:path";import{Router as Y}from"express";import{getBridgeVersion as Q,getRuntimeHomeDir as X,getUserHomeDir as Z}from"../config.js";import{getAccessControlEntries as ee,isValidIpEntry as te,parseAllowIps as ne,refreshAccessControlCache as se}from"../services/access-control.js";import{getAgentProcessManager as C}from"../services/agent-process-manager.js";import{autoRegister as re,getRegistrationState as H,getAutoRegisterConfigFilePath as B}from"../services/auto-register.js";import{createSession as ae,getPanelCredentials as N,validateSession as U,destroySession as O,validateCredentials as z,hasConfiguredCredentials as E,isDefaultCredentials as oe,setPanelCredentials as ie,getAllSessions as de,getActiveSessionCount as le,checkLoginAllowed as ce,recordLoginFailure as ue,recordLoginSuccess as ge}from"../services/panel-auth.js";import{getRuntimeBindingPublicState as F,getRuntimePairingCode as pe,normalizeSchedulerBaseUrl as fe}from"../services/runtime-binding.js";import{collectSystemMetrics as me}from"../services/system-metrics.js";import{loadPersistedSessions as be}from"../services/terminal-persistence.js";import{runExclusiveConfigWrite as _}from"../utils/config-write-queue.js";import{createLogger as he,getRecentLogs as ve}from"../utils/logger.js";import{sdkSessions as ye}from"./terminal.js";const x=he("dashboard");let T=null;const xe=1e4;let L=null,P=null;const we=3e4;async function D(){me().then(t=>{L={data:t,ts:Date.now()}}).catch(()=>{}),C().discoverAllProcesses().then(t=>{P={data:t,ts:Date.now()}}).catch(()=>{}),C().cleanupStaleRegistry().catch(()=>{})}D(),setInterval(D,we);const V=Q();async function Se(){const t=await import("node:fs/promises"),n=(await import("node:path")).join(X(),".aws-bridge","lifecycle-state.json");try{const s=await t.readFile(n,"utf8"),i=JSON.parse(s),r=Array.isArray(i.operations)?i.operations:[];return r.length===0?null:(r.sort((a,o)=>new Date(o.updatedAt).getTime()-new Date(a.updatedAt).getTime()),r[0]||null)}catch{return null}}const g=Y(),J="panel_sid";function Ie(t){const e={};if(!t)return e;for(const n of t.split(";")){const s=n.indexOf("=");if(s===-1)continue;const i=n.slice(0,s).trim(),r=n.slice(s+1).trim();i&&(e[i]=r)}return e}function A(t){return Ie(t.headers.cookie)[J]}function q(t,e,n){const s=t.secure||t.headers["x-forwarded-proto"]==="https";return`${J}=${e}; HttpOnly; SameSite=Lax; Path=/${s?"; Secure":""}; Max-Age=${n}`}function m(t,e){const n=A(t);return n&&U(n)?!0:t.xhr||t.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}g.post("/runtime/login",(t,e)=>{const n=t.socket.remoteAddress||"unknown",s=ce(n);if(!s.allowed){e.status(429).set("Retry-After",String(s.retryAfterSec)).json({ok:!1,error:"too many failed login attempts, try again later",retryAfterSec:s.retryAfterSec});return}const{username:i,password:r}=t.body||{};if(!i||!r){e.status(400).json({ok:!1,error:"username and password required"});return}if(!z(String(i),String(r))){ue(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}ge(n);const a=ae(),o=!E();e.setHeader("Set-Cookie",q(t,a,86400)),e.json({ok:!0,needSetup:o})}),g.post("/runtime/logout",(t,e)=>{const n=A(t);n&&O(n),e.setHeader("Set-Cookie",q(t,"",0)),e.json({ok:!0})}),g.get("/runtime/credentials-status",(t,e)=>{if(!m(t,e))return;const n=N();e.json({ok:!0,isDefault:oe(),configured:E(),username:n.username})}),g.post("/runtime/change-credentials",async(t,e)=>{if(!m(t,e))return;const{username:n,password:s,passwordConfirm:i,oldPassword:r}=t.body||{};if(s&&String(s).trim()){if(String(s)!==String(i)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(E()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const o=N();if(!z(o.username,String(r))){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!n||!String(n).trim()){e.status(400).json({ok:!1,error:"username required"});return}const a=s&&String(s).trim()?String(s).trim():N().password;try{await ie(String(n).trim(),a),e.json({ok:!0})}catch(o){const l=o;x.error("Failed to change panel credentials:",l.message),e.status(500).json({ok:!1,error:l.message})}});function W(t){const e=typeof t=="string"?t.trim():"";if(e)return e.length<8?"\u2022\u2022\u2022\u2022":"\u2022\u2022\u2022\u2022"+e.slice(-4)}function G(t){const e=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))):[];return e.length>0?e:["serverUrl","schedulerBaseUrl","userKey"].some(s=>!!String(t[s]??"").trim())?[t]:[]}const ke=["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","projectId","roleName","workspacePath","registerAddr","registerIp","virtualIp","tunnel","preferSameLanIp","maxRetries","retryInterval"];function Ce(t){const e={};for(const n of ke)t[n]!==void 0&&(e[n]=t[n]);return e}function Be(t,e){const n=String(t.connectionKey??"").trim()||String(e??"").trim();return{serverUrl:String(t.serverUrl??t.schedulerBaseUrl??"").trim(),tenantId:String(t.tenantId??"").trim(),instanceName:String(t.instanceName??"").trim(),instanceId:String(t.instanceId??"").trim()||void 0,registerAddr:String(t.registerAddr??"").trim(),tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,userKeyMasked:W(t.userKey),userKeySet:!!String(t.userKey??"").trim(),connectionKeyMasked:W(n),connectionKeySet:!!n}}function R(t){return fe(t)??String(t||"").trim()}g.get("/runtime/server-config",(t,e)=>{if(!m(t,e))return;const n=B();let s={};if(v.existsSync(n))try{s=JSON.parse(v.readFileSync(n,"utf-8"))}catch{e.status(500).json({ok:!1,error:"config.json is not valid JSON; fix it manually first"});return}const i=String(s.connectionKey??"").trim()||void 0,r=G(s).map(a=>Be(a,i));e.json({ok:!0,targets:r})});function M(t,e){const n=new Error(e);return n.status=t,n}g.post("/runtime/server-config",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).targets;if(!Array.isArray(n)){e.status(400).json({ok:!1,error:"targets array is required"});return}const s=[];for(let r=0;r<n.length;r++){const a=n[r];if(!a||typeof a!="object"||Array.isArray(a)){e.status(400).json({ok:!1,error:`targets[${r}] must be an object`});return}const o=a,l=String(o.serverUrl??o.schedulerBaseUrl??"").trim();if(!l){e.status(400).json({ok:!1,error:`targets[${r}].serverUrl is required`});return}try{const d=new URL(l);if(!["http:","https:"].includes(d.protocol))throw new Error("protocol must be http or https")}catch{e.status(400).json({ok:!1,error:`targets[${r}].serverUrl must be a valid http(s) URL`});return}const f=o.tunnel;let p;if(f==="auto"||f===""||f==null)p=void 0;else if(f===!0||f==="true")p=!0;else if(f===!1||f==="false")p=!1;else{e.status(400).json({ok:!1,error:`targets[${r}].tunnel must be auto, true or false`});return}const h=String(o.originalServerUrl??"").trim();s.push({serverUrl:l,originalServerUrl:h&&h!==l?h:"",userKey:String(o.userKey??"").trim(),tenantId:String(o.tenantId??"").trim(),instanceName:String(o.instanceName??"").trim(),connectionKey:String(o.connectionKey??"").trim(),registerAddr:String(o.registerAddr??"").trim(),tunnel:p})}const i=new Set;for(const r of s){const a=R(r.serverUrl);if(i.has(a)){e.status(400).json({ok:!1,error:"duplicate serverUrl in targets"});return}i.add(a)}try{const r=B();await _(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=String(a.connectionKey??"").trim(),l=G(a),f=l.length>0&&l[0]===a,p=new Map(l.map(d=>[R(String(d.serverUrl??d.schedulerBaseUrl??"").trim()),f?Ce(d):d])),h=[];for(const d of s){let b;if(d.originalServerUrl){const w=R(d.originalServerUrl);b=p.get(w),b&&p.delete(w)}if(!b){const w=R(d.serverUrl);b=p.get(w),b&&p.delete(w)}const c=b?{...b}:{};if(c.serverUrl=d.serverUrl,c.tenantId=d.tenantId,c.instanceName=d.instanceName,c.registerAddr=d.registerAddr,d.tunnel===void 0?delete c.tunnel:c.tunnel=d.tunnel,d.userKey)c.userKey=d.userKey;else if(!String(c.userKey??"").trim())throw M(400,`userKey is required for ${d.serverUrl} (new target without inheritable userKey)`);d.connectionKey?c.connectionKey=d.connectionKey:!String(c.connectionKey??"").trim()&&o&&(c.connectionKey=o),h.push(c)}a.autoRegisterTargets=h;for(const d of["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","registerAddr","registerIp","virtualIp","tunnel"])delete a[d];v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8")}),x.info("Server connection config updated via panel",{configPath:r,targetCount:s.length}),e.json({ok:!0})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save server connection config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/access-control",(t,e)=>{m(t,e)&&e.json({ok:!0,allowIps:ee(),defaultAllowIps:["0.0.0.0"]})}),g.post("/runtime/access-control",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).allowIps;if(typeof n!="string"&&!Array.isArray(n)){e.status(400).json({ok:!1,error:"allowIps must be a string or an array"});return}const s=ne(n),i=s.filter(r=>!te(r));if(i.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${i.join(", ")}`});return}try{const r=B();await _(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=a.accessControl&&typeof a.accessControl=="object"&&!Array.isArray(a.accessControl)?{...a.accessControl}:{};o.allowIps=s,a.accessControl=o,v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8"),se()}),x.info("Access control config updated via panel",{configPath:r,entryCount:s.length}),e.json({ok:!0,allowIps:s})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save access control config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/node-info",(t,e)=>{if(!m(t,e))return;const n=F(),s=H();e.json({ok:!0,hostname:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:V,configPath:B(),homeDir:Z(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:le()})}),g.get("/runtime/logs",(t,e)=>{if(!m(t,e))return;const n=t.query.level||void 0,s=Math.min(Number(t.query.count)||200,500),r=n&&["debug","info","warn","error"].includes(n)?n:void 0,a=ve(s,r);e.json({ok:!0,logs:a})}),g.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await re();e.json({ok:n})}catch(n){const s=n;e.status(500).json({ok:!1,error:s.message})}}),g.get("/runtime/admin/sessions",(t,e)=>{if(!m(t,e))return;const n=de().map(s=>({token:s.token.slice(0,8)+"...",createdAt:new Date(s.createdAt).toISOString(),lastUsedAt:new Date(s.lastUsedAt).toISOString(),age:Math.floor((Date.now()-s.createdAt)/1e3)}));e.json({ok:!0,sessions:n})}),g.post("/runtime/admin/sessions/:token/destroy",(t,e)=>{m(t,e)&&(O(t.params.token),e.json({ok:!0}))});function Ee(t){return new Promise((e,n)=>{const s=I.platform()==="win32",i=s?"taskkill":"kill",r=s?["/PID",String(t),"/F"]:["-9",String(t)];$(i,r,{timeout:5e3},a=>a?n(a):e())})}g.post("/runtime/kill-process",async(t,e)=>{if(!m(t,e))return;const n=Number(t.body.pid);if(!n||!Number.isInteger(n)||n<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}const s=C(),i=()=>{const a=new Set,o=P?.data??[];for(const l of o)typeof l.pid=="number"&&a.add(l.pid);for(const l of s.getAllManagedProcesses())typeof l.pid=="number"&&a.add(l.pid);return a};let r=i();if(r.size===0)try{P={data:await s.discoverAllProcesses(),ts:Date.now()},r=i()}catch{}if(!r.has(n)){x.warn("Rejected kill-process for unrecognized PID",{pid:n}),e.status(404).json({ok:!1,error:"PID is not a bridge-discovered or registered agent process"});return}try{await Ee(n),x.info("Killed process",{pid:n}),e.json({ok:!0})}catch(a){const o=a;x.warn("Failed to kill process",{pid:n,error:o.message}),e.status(500).json({ok:!1,error:o.message})}}),g.get("/runtime/dashboard",async(t,e)=>{if(m(t,e)){if(T&&Date.now()-T.ts<xe)return void e.json(T.data);try{const n=L?.data??null,s=P?.data??null;L||D();const[i,r,a]=await Promise.all([Promise.resolve(F()),Promise.resolve(H()),be().catch(()=>[])]),l=C().getAllManagedProcesses(),f=Array.from(ye.entries()).map(([S,y])=>({sessionId:S,agentId:y.agentId,workspacePath:y.workspacePath,runtimeStatus:y.currentRuntimeStatus||"running",runtimeActionType:y.currentRuntimeActionType,runtimeActionLabel:y.currentRuntimeActionLabel,providerId:y.providerId}));let p={managed:0,orphan:0,unknown:0},h=[];if(s){p={managed:s.filter(u=>u.managementStatus==="managed").length,orphan:s.filter(u=>u.managementStatus==="orphan").length,unknown:s.filter(u=>u.managementStatus==="unknown").length};const S=new Map(l.map(u=>[u.pid,u]));h=s.map(u=>{const k=S.get(u.pid);return{agentId:u.agentId||k?.agentId,pid:u.pid,managementStatus:u.managementStatus,sessionId:k?.sessionId,state:k?.state,command:u.command,cwd:u.cwd,startedAt:u.startedAt}});const y={managed:0,orphan:1,unknown:2};h.sort((u,k)=>(y[u.managementStatus]??9)-(y[k.managementStatus]??9))}const d=new Set(f.map(S=>S.sessionId)),b=a.filter(S=>!d.has(S.sessionId)),c=i,w=await Se(),j={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:V,hostname:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:c.status,paired:c.paired,instanceId:c.instanceId,userId:c.userId,schedulerBaseUrl:c.schedulerBaseUrl,pairingCode:pe()},registration:{registered:r.registered,instanceId:r.instanceId,lastAttempt:r.lastAttempt?.toISOString(),error:r.error},metrics:n,sessions:{active:f,activeCount:f.length,persisted:b,persistedCount:b.length,total:f.length+b.length},processes:{...p,total:p.managed+p.orphan+p.unknown,list:h,registryCount:l.length},lifecycleOperation:w};T={data:j,ts:Date.now()},e.json(j)}catch(n){const s=n;x.error("Dashboard aggregation failed:",s.message),e.status(500).json({ok:!1,error:s.message})}}}),g.get("/login",(t,e)=>{e.type("text/html; charset=utf-8"),e.send(Pe)}),g.get("/",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}if(!E()){e.redirect("/setup");return}const s=t.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(Te.replace("{{LANG}}",s))}),g.get("/setup",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(Ae)});const Te=`<!DOCTYPE html>
|
|
1
|
+
import{execFile as $}from"node:child_process";import v from"node:fs";import I from"node:os";import K from"node:path";import{Router as Y}from"express";import{getBridgeVersion as Q,getRuntimeHomeDir as X,getUserHomeDir as Z}from"../config.js";import{getAccessControlEntries as ee,isValidIpEntry as te,parseAllowIps as ne,refreshAccessControlCache as se}from"../services/access-control.js";import{getAgentProcessManager as C}from"../services/agent-process-manager.js";import{autoRegister as re,getRegistrationState as H,getAutoRegisterConfigFilePath as B}from"../services/auto-register.js";import{createSession as ae,getPanelCredentials as N,validateSession as U,destroySession as O,validateCredentials as z,hasConfiguredCredentials as E,isDefaultCredentials as oe,setPanelCredentials as ie,getAllSessions as de,getActiveSessionCount as le,checkLoginAllowed as ce,recordLoginFailure as ue,recordLoginSuccess as ge}from"../services/panel-auth.js";import{getRuntimeBindingPublicState as F,getRuntimePairingCode as pe,normalizeSchedulerBaseUrl as fe}from"../services/runtime-binding.js";import{collectSystemMetrics as me}from"../services/system-metrics.js";import{loadPersistedSessions as be}from"../services/terminal-persistence.js";import{runExclusiveConfigWrite as _}from"../utils/config-write-queue.js";import{createLogger as he,getRecentLogs as ve}from"../utils/logger.js";import{sdkSessions as ye}from"./terminal.js";const x=he("dashboard");let T=null;const xe=1e4;let L=null,P=null;const we=3e4;async function D(){me().then(t=>{L={data:t,ts:Date.now()}}).catch(()=>{}),C().discoverAllProcesses().then(t=>{P={data:t,ts:Date.now()}}).catch(()=>{}),C().cleanupStaleRegistry().catch(()=>{})}D(),setInterval(D,we);const V=Q();async function Se(){const t=await import("node:fs/promises"),n=(await import("node:path")).join(X(),".aws-bridge","lifecycle-state.json");try{const s=await t.readFile(n,"utf8"),i=JSON.parse(s),r=Array.isArray(i.operations)?i.operations:[];return r.length===0?null:(r.sort((a,o)=>new Date(o.updatedAt).getTime()-new Date(a.updatedAt).getTime()),r[0]||null)}catch{return null}}const g=Y(),J="panel_sid";function Ie(t){const e={};if(!t)return e;for(const n of t.split(";")){const s=n.indexOf("=");if(s===-1)continue;const i=n.slice(0,s).trim(),r=n.slice(s+1).trim();i&&(e[i]=r)}return e}function A(t){return Ie(t.headers.cookie)[J]}function q(t,e,n){const s=t.secure||t.headers["x-forwarded-proto"]==="https";return`${J}=${e}; HttpOnly; SameSite=Lax; Path=/${s?"; Secure":""}; Max-Age=${n}`}function m(t,e){const n=A(t);return n&&U(n)?!0:t.xhr||t.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}g.post("/runtime/login",(t,e)=>{const n=t.socket.remoteAddress||"unknown",s=ce(n);if(!s.allowed){e.status(429).set("Retry-After",String(s.retryAfterSec)).json({ok:!1,error:"too many failed login attempts, try again later",retryAfterSec:s.retryAfterSec});return}const{username:i,password:r}=t.body||{};if(!i||!r){e.status(400).json({ok:!1,error:"username and password required"});return}if(!z(String(i),String(r))){ue(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}ge(n);const a=ae(),o=!E();e.setHeader("Set-Cookie",q(t,a,86400)),e.json({ok:!0,needSetup:o})}),g.post("/runtime/logout",(t,e)=>{const n=A(t);n&&O(n),e.setHeader("Set-Cookie",q(t,"",0)),e.json({ok:!0})}),g.get("/runtime/credentials-status",(t,e)=>{if(!m(t,e))return;const n=N();e.json({ok:!0,isDefault:oe(),configured:E(),username:n.username})}),g.post("/runtime/change-credentials",async(t,e)=>{if(!m(t,e))return;const{username:n,password:s,passwordConfirm:i,oldPassword:r}=t.body||{};if(s&&String(s).trim()){if(String(s)!==String(i)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(E()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const o=N();if(!z(o.username,String(r))){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!n||!String(n).trim()){e.status(400).json({ok:!1,error:"username required"});return}const a=s&&String(s).trim()?String(s).trim():N().password;try{await ie(String(n).trim(),a),e.json({ok:!0})}catch(o){const l=o;x.error("Failed to change panel credentials:",l.message),e.status(500).json({ok:!1,error:l.message})}});function W(t){const e=typeof t=="string"?t.trim():"";if(e)return e.length<8?"\u2022\u2022\u2022\u2022":"\u2022\u2022\u2022\u2022"+e.slice(-4)}function G(t){const e=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))):[];return e.length>0?e:["serverUrl","schedulerBaseUrl","userKey"].some(s=>!!String(t[s]??"").trim())?[t]:[]}const ke=["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","projectId","roleName","workspacePath","registerAddr","registerIp","virtualIp","tunnel","preferSameLanIp","maxRetries","retryInterval"];function Ce(t){const e={};for(const n of ke)t[n]!==void 0&&(e[n]=t[n]);return e}function Be(t,e){const n=String(t.connectionKey??"").trim()||String(e??"").trim();return{serverUrl:String(t.serverUrl??t.schedulerBaseUrl??"").trim(),tenantId:String(t.tenantId??"").trim(),instanceName:String(t.instanceName??"").trim(),instanceId:String(t.instanceId??"").trim()||void 0,registerAddr:String(t.registerAddr??"").trim(),tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,userKeyMasked:W(t.userKey),userKeySet:!!String(t.userKey??"").trim(),connectionKeyMasked:W(n),connectionKeySet:!!n}}function R(t){return fe(t)??String(t||"").trim()}g.get("/runtime/server-config",(t,e)=>{if(!m(t,e))return;const n=B();let s={};if(v.existsSync(n))try{s=JSON.parse(v.readFileSync(n,"utf-8"))}catch{e.status(500).json({ok:!1,error:"config.json is not valid JSON; fix it manually first"});return}const i=String(s.connectionKey??"").trim()||void 0,r=G(s).map(a=>Be(a,i));e.json({ok:!0,targets:r})});function M(t,e){const n=new Error(e);return n.status=t,n}g.post("/runtime/server-config",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).targets;if(!Array.isArray(n)){e.status(400).json({ok:!1,error:"targets array is required"});return}const s=[];for(let r=0;r<n.length;r++){const a=n[r];if(!a||typeof a!="object"||Array.isArray(a)){e.status(400).json({ok:!1,error:`targets[${r}] must be an object`});return}const o=a,l=String(o.serverUrl??o.schedulerBaseUrl??"").trim();if(!l){e.status(400).json({ok:!1,error:`targets[${r}].serverUrl is required`});return}try{const d=new URL(l);if(!["http:","https:"].includes(d.protocol))throw new Error("protocol must be http or https")}catch{e.status(400).json({ok:!1,error:`targets[${r}].serverUrl must be a valid http(s) URL`});return}const p=o.tunnel;let f;if(p==="auto"||p===""||p===null||p===void 0)f=void 0;else if(p===!0||p==="true")f=!0;else if(p===!1||p==="false")f=!1;else{e.status(400).json({ok:!1,error:`targets[${r}].tunnel must be auto, true or false`});return}const h=String(o.originalServerUrl??"").trim();s.push({serverUrl:l,originalServerUrl:h&&h!==l?h:"",userKey:String(o.userKey??"").trim(),tenantId:String(o.tenantId??"").trim(),instanceName:String(o.instanceName??"").trim(),connectionKey:String(o.connectionKey??"").trim(),registerAddr:String(o.registerAddr??"").trim(),tunnel:f})}const i=new Set;for(const r of s){const a=R(r.serverUrl);if(i.has(a)){e.status(400).json({ok:!1,error:"duplicate serverUrl in targets"});return}i.add(a)}try{const r=B();await _(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=String(a.connectionKey??"").trim(),l=G(a),p=l.length>0&&l[0]===a,f=new Map(l.map(d=>[R(String(d.serverUrl??d.schedulerBaseUrl??"").trim()),p?Ce(d):d])),h=[];for(const d of s){let b;if(d.originalServerUrl){const w=R(d.originalServerUrl);b=f.get(w),b&&f.delete(w)}if(!b){const w=R(d.serverUrl);b=f.get(w),b&&f.delete(w)}const c=b?{...b}:{};if(c.serverUrl=d.serverUrl,c.tenantId=d.tenantId,c.instanceName=d.instanceName,c.registerAddr=d.registerAddr,d.tunnel===void 0?delete c.tunnel:c.tunnel=d.tunnel,d.userKey)c.userKey=d.userKey;else if(!String(c.userKey??"").trim())throw M(400,`userKey is required for ${d.serverUrl} (new target without inheritable userKey)`);d.connectionKey?c.connectionKey=d.connectionKey:!String(c.connectionKey??"").trim()&&o&&(c.connectionKey=o),h.push(c)}a.autoRegisterTargets=h;for(const d of["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","registerAddr","registerIp","virtualIp","tunnel"])delete a[d];v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8")}),x.info("Server connection config updated via panel",{configPath:r,targetCount:s.length}),e.json({ok:!0})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save server connection config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/access-control",(t,e)=>{m(t,e)&&e.json({ok:!0,allowIps:ee(),defaultAllowIps:["0.0.0.0"]})}),g.post("/runtime/access-control",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).allowIps;if(typeof n!="string"&&!Array.isArray(n)){e.status(400).json({ok:!1,error:"allowIps must be a string or an array"});return}const s=ne(n),i=s.filter(r=>!te(r));if(i.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${i.join(", ")}`});return}try{const r=B();await _(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=a.accessControl&&typeof a.accessControl=="object"&&!Array.isArray(a.accessControl)?{...a.accessControl}:{};o.allowIps=s,a.accessControl=o,v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8"),se()}),x.info("Access control config updated via panel",{configPath:r,entryCount:s.length}),e.json({ok:!0,allowIps:s})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save access control config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/node-info",(t,e)=>{if(!m(t,e))return;const n=F(),s=H();e.json({ok:!0,hostname:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:V,configPath:B(),homeDir:Z(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:le()})}),g.get("/runtime/logs",(t,e)=>{if(!m(t,e))return;const n=t.query.level||void 0,s=Math.min(Number(t.query.count)||200,500),r=n&&["debug","info","warn","error"].includes(n)?n:void 0,a=ve(s,r);e.json({ok:!0,logs:a})}),g.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await re();e.json({ok:n})}catch(n){const s=n;e.status(500).json({ok:!1,error:s.message})}}),g.get("/runtime/admin/sessions",(t,e)=>{if(!m(t,e))return;const n=de().map(s=>({token:s.token.slice(0,8)+"...",createdAt:new Date(s.createdAt).toISOString(),lastUsedAt:new Date(s.lastUsedAt).toISOString(),age:Math.floor((Date.now()-s.createdAt)/1e3)}));e.json({ok:!0,sessions:n})}),g.post("/runtime/admin/sessions/:token/destroy",(t,e)=>{m(t,e)&&(O(t.params.token),e.json({ok:!0}))});function Ee(t){return new Promise((e,n)=>{const s=I.platform()==="win32",i=s?"taskkill":"kill",r=s?["/PID",String(t),"/F"]:["-9",String(t)];$(i,r,{timeout:5e3},a=>a?n(a):e())})}g.post("/runtime/kill-process",async(t,e)=>{if(!m(t,e))return;const n=Number(t.body.pid);if(!n||!Number.isInteger(n)||n<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}const s=C(),i=()=>{const a=new Set,o=P?.data??[];for(const l of o)typeof l.pid=="number"&&a.add(l.pid);for(const l of s.getAllManagedProcesses())typeof l.pid=="number"&&a.add(l.pid);return a};let r=i();if(r.size===0)try{P={data:await s.discoverAllProcesses(),ts:Date.now()},r=i()}catch{}if(!r.has(n)){x.warn("Rejected kill-process for unrecognized PID",{pid:n}),e.status(404).json({ok:!1,error:"PID is not a bridge-discovered or registered agent process"});return}try{await Ee(n),x.info("Killed process",{pid:n}),e.json({ok:!0})}catch(a){const o=a;x.warn("Failed to kill process",{pid:n,error:o.message}),e.status(500).json({ok:!1,error:o.message})}}),g.get("/runtime/dashboard",async(t,e)=>{if(m(t,e)){if(T&&Date.now()-T.ts<xe)return void e.json(T.data);try{const n=L?.data??null,s=P?.data??null;L||D();const[i,r,a]=await Promise.all([Promise.resolve(F()),Promise.resolve(H()),be().catch(()=>[])]),l=C().getAllManagedProcesses(),p=Array.from(ye.entries()).map(([S,y])=>({sessionId:S,agentId:y.agentId,workspacePath:y.workspacePath,runtimeStatus:y.currentRuntimeStatus||"running",runtimeActionType:y.currentRuntimeActionType,runtimeActionLabel:y.currentRuntimeActionLabel,providerId:y.providerId}));let f={managed:0,orphan:0,unknown:0},h=[];if(s){f={managed:s.filter(u=>u.managementStatus==="managed").length,orphan:s.filter(u=>u.managementStatus==="orphan").length,unknown:s.filter(u=>u.managementStatus==="unknown").length};const S=new Map(l.map(u=>[u.pid,u]));h=s.map(u=>{const k=S.get(u.pid);return{agentId:u.agentId||k?.agentId,pid:u.pid,managementStatus:u.managementStatus,sessionId:k?.sessionId,state:k?.state,command:u.command,cwd:u.cwd,startedAt:u.startedAt}});const y={managed:0,orphan:1,unknown:2};h.sort((u,k)=>(y[u.managementStatus]??9)-(y[k.managementStatus]??9))}const d=new Set(p.map(S=>S.sessionId)),b=a.filter(S=>!d.has(S.sessionId)),c=i,w=await Se(),j={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:V,hostname:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:c.status,paired:c.paired,instanceId:c.instanceId,userId:c.userId,schedulerBaseUrl:c.schedulerBaseUrl,pairingCode:pe()},registration:{registered:r.registered,instanceId:r.instanceId,lastAttempt:r.lastAttempt?.toISOString(),error:r.error},metrics:n,sessions:{active:p,activeCount:p.length,persisted:b,persistedCount:b.length,total:p.length+b.length},processes:{...f,total:f.managed+f.orphan+f.unknown,list:h,registryCount:l.length},lifecycleOperation:w};T={data:j,ts:Date.now()},e.json(j)}catch(n){const s=n;x.error("Dashboard aggregation failed:",s.message),e.status(500).json({ok:!1,error:s.message})}}}),g.get("/login",(t,e)=>{e.type("text/html; charset=utf-8"),e.send(Pe)}),g.get("/",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}if(!E()){e.redirect("/setup");return}const s=t.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(Te.replace("{{LANG}}",s))}),g.get("/setup",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(Ae)});const Te=`<!DOCTYPE html>
|
|
2
2
|
<html lang="{{LANG}}">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|