aws-runtime-bridge 1.9.137 → 1.9.140

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +4 -1
  2. package/dist/adapter/AcodeSdkAdapter.js +2 -2
  3. package/dist/adapter/ClaudeSdkAdapter.js +3 -3
  4. package/dist/adapter/CodexSdkAdapter.js +3 -3
  5. package/dist/adapter/OpencodeSdkAdapter.js +3 -3
  6. package/dist/browser/browser-pool.js +1 -1
  7. package/dist/browser/built-in-browser-tools.d.ts +1 -2
  8. package/dist/browser/built-in-browser-tools.js +1 -1
  9. package/dist/browser/ws-server.js +1 -1
  10. package/dist/config.d.ts +22 -6
  11. package/dist/config.js +2 -2
  12. package/dist/index.js +1 -1
  13. package/dist/middleware/auth.js +1 -1
  14. package/dist/remote-desktop/computer-control.d.ts +2 -2
  15. package/dist/remote-desktop/index.d.ts +1 -1
  16. package/dist/remote-desktop/index.js +2 -2
  17. package/dist/remote-desktop/screen-capture.js +1 -1
  18. package/dist/remote-desktop/session-manager.d.ts +2 -2
  19. package/dist/remote-desktop/session-manager.js +1 -1
  20. package/dist/remote-desktop/system-backend.js +3 -3
  21. package/dist/remote-desktop/types.d.ts +1 -1
  22. package/dist/remote-desktop/ui-automation.d.ts +2 -2
  23. package/dist/remote-desktop/ui-automation.js +5 -5
  24. package/dist/routes/ai-sources.js +1 -1
  25. package/dist/routes/background-tasks.js +5 -5
  26. package/dist/routes/browser-listeners.js +1 -1
  27. package/dist/routes/capabilities.js +1 -1
  28. package/dist/routes/dashboard.js +1 -1
  29. package/dist/routes/file-change-diff.js +3 -3
  30. package/dist/routes/file-diff-rollback.js +1 -1
  31. package/dist/routes/file-snapshot.js +1 -1
  32. package/dist/routes/git.js +20 -20
  33. package/dist/routes/instance.js +1 -1
  34. package/dist/routes/launch.js +1 -1
  35. package/dist/routes/mcp.js +1 -1
  36. package/dist/routes/plugins.js +1 -1
  37. package/dist/routes/properties.js +1 -1
  38. package/dist/routes/pty.d.ts +1 -1
  39. package/dist/routes/pty.js +1 -1
  40. package/dist/routes/runtime-binding.js +1 -1
  41. package/dist/routes/skills.js +1 -1
  42. package/dist/routes/sub-agents.js +1 -1
  43. package/dist/routes/task-context.js +1 -1
  44. package/dist/routes/terminal.js +27 -27
  45. package/dist/routes/yml.js +1 -1
  46. package/dist/services/access-control.js +1 -1
  47. package/dist/services/acode-bg-manager.js +1 -1
  48. package/dist/services/agent-capability-sync.js +1 -1
  49. package/dist/services/agent-process-manager.d.ts +1 -1
  50. package/dist/services/agent-process-manager.js +1 -1
  51. package/dist/services/ai-config-service.js +2 -2
  52. package/dist/services/ai-source-apply.d.ts +1 -1
  53. package/dist/services/auto-register.js +1 -1
  54. package/dist/services/aws-client-agent-mcp.d.ts +5 -0
  55. package/dist/services/aws-client-agent-mcp.js +2 -2
  56. package/dist/services/bridge-package-publish-hooks.js +1 -1
  57. package/dist/services/browser-session-manager.d.ts +3 -3
  58. package/dist/services/browser-session-manager.js +3 -3
  59. package/dist/services/capability-config.js +2 -2
  60. package/dist/services/cli-commands.js +2 -2
  61. package/dist/services/desktop-mcp-manager.js +1 -1
  62. package/dist/services/event-delta-pusher.d.ts +1 -1
  63. package/dist/services/fileSnapshotStore.js +1 -1
  64. package/dist/services/instance-ai-config-service.js +1 -1
  65. package/dist/services/instance-init-service.js +1 -1
  66. package/dist/services/instance-state.d.ts +1 -1
  67. package/dist/services/instance-state.js +1 -1
  68. package/dist/services/jdk-scanner.js +1 -1
  69. package/dist/services/launch-manager.js +1 -1
  70. package/dist/services/legacy-path-migration.d.ts +0 -2
  71. package/dist/services/legacy-path-migration.js +1 -1
  72. package/dist/services/lifecycle-state.js +2 -2
  73. package/dist/services/mcp-deploy.js +2 -2
  74. package/dist/services/orphan-monitor.js +1 -1
  75. package/dist/services/plugin-manager.d.ts +27 -5
  76. package/dist/services/plugin-manager.js +15 -9
  77. package/dist/services/process-detail.js +2 -2
  78. package/dist/services/process-detector.js +10 -10
  79. package/dist/services/process-registry.js +1 -1
  80. package/dist/services/process-top.js +2 -2
  81. package/dist/services/runtime-binding.js +4 -4
  82. package/dist/services/sdk-tool-file-change-recorder.js +1 -1
  83. package/dist/services/skill-manager.js +2 -2
  84. package/dist/services/startup-config-wizard.js +6 -6
  85. package/dist/services/system-metrics.js +1 -1
  86. package/dist/services/tunnel-client.js +1 -1
  87. package/dist/services/update-notifier.js +2 -2
  88. package/dist/services/windows-service-manager.js +1 -1
  89. package/dist/services/workspace-files.js +1 -1
  90. package/dist/services/workspaceTaskChangeWatcher.js +3 -3
  91. package/dist/utils/file-utils.js +2 -2
  92. package/dist/utils/instance-ai-config-debug-logger.js +1 -1
  93. package/dist/utils/mcp-utils.js +1 -1
  94. package/dist/utils/yaml-utils.js +1 -1
  95. package/package/acode/dist/cli.js +2 -2
  96. package/package/acode/dist/plugin-tools.js +3 -8
  97. package/package/acode/dist/plugin-types.d.ts +10 -0
  98. package/package/acode/dist/runtime.d.ts +4 -0
  99. package/package/acode/dist/runtime.js +18 -18
  100. package/package/acode/dist/types.d.ts +4 -0
  101. package/package/aws-client-agent-mcp/dist/http-client.js +1 -1
  102. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +0 -1
  103. package/package/aws-client-agent-mcp/dist/mcp-server.js +10 -10
  104. package/package/aws-client-agent-mcp/dist/memory-tools.js +6 -6
  105. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +1 -1
  106. package/package/aws-client-agent-mcp/dist/server-memory-store.js +1 -1
  107. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +2 -0
  108. package/package/aws-client-agent-mcp/dist/status-reporter.js +3 -2
  109. package/package/aws-client-agent-mcp/dist/types.d.ts +1 -0
  110. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import{WebSocketServer as C}from"ws";import{SessionManager as F}from"./session-manager.js";import{PermissionManager as A}from"./permission-manager.js";import{encodeFrame as D}from"./frame-encode.js";import{getSharpLoadStatus as M}from"./screen-capture.js";import{isRuntimeTokenValid as T}from"../middleware/auth.js";import{isUpgradeAllowed as v}from"../services/access-control.js";import{getFrameBufferLimit as I,shouldDropFrameForBackpressure as P}from"./frame-backpressure.js";const h=new A,i=new F(h);function W(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 x(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 q(n){const o=new C({noServer:!0});n.on("upgrade",(t,e,s)=>{if(!v(t,e))return;const S=new URL(t.url||"/",`http://${t.headers.host}`);if(S.pathname!=="/remote-desktop")return;const k=W(t,S);if(!T(k)&&!h.validateToken(k)){e.write(`HTTP/1.1 401 Unauthorized\r
1
+ import{WebSocketServer as C}from"ws";import{isRuntimeTokenValid as F}from"../middleware/auth.js";import{isUpgradeAllowed as A}from"../services/access-control.js";import{getFrameBufferLimit as D,shouldDropFrameForBackpressure as M}from"./frame-backpressure.js";import{encodeFrame as T}from"./frame-encode.js";import{PermissionManager as v}from"./permission-manager.js";import{getSharpLoadStatus as I}from"./screen-capture.js";import{SessionManager as P}from"./session-manager.js";const h=new v,i=new P(h);function W(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 f(n,o){n.readyState===n.OPEN&&n.send(JSON.stringify(o))}function x(n,o,t){switch(t.type){case"remote-desktop:input":{const e=t.data;i.handleInput(o,e).catch(s=>{f(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);if(e&&t.data){const s=t.data;e.screenCapture.setQuality(s.quality,s.fps)}break}case"remote-desktop:control-mode":{const e=i.getSession(o);e&&t.data&&(t.data.enabled?e.enableControl().catch(p=>{console.warn("[remote-desktop] enableControl failed:",p)}):e.stopInputBackend());break}case"remote-desktop:switch-screen":{const e=i.getSession(o);if(e&&t.data){const s=t.data;e.switchScreen(s.screenId||null).catch(p=>{f(n,{type:"remote-desktop:error",sessionId:o,data:{error:String(p)}})})}break}case"remote-desktop:ping":{f(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 q(n){const o=new C({noServer:!0});n.on("upgrade",(t,e,s)=>{if(!A(t,e))return;const p=new URL(t.url||"/",`http://${t.headers.host}`);if(p.pathname!=="/remote-desktop")return;const k=W(t,p);if(!F(k)&&!h.validateToken(k)){e.write(`HTTP/1.1 401 Unauthorized\r
2
2
  \r
3
- `),e.destroy();return}o.handleUpgrade(t,e,s,r=>{const b=S.searchParams.get("agentId")||"default";i.createSession(b,k).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 f=Date.now()-m,u=c.screenCapture.frameStats;console.log(`[remote-desktop] frames: sent=${d} dropped=${p} bufferedAmount=${r.bufferedAmount} silent=${f}ms quality=${c.screenCapture.getQuality()} sharp=${M()} full=${u.full} partial=${u.partial} null=${u.null} sharpFail=${u.sharpFail}`),d=0,p=0,c.screenCapture.resetFrameStats()},5e3);let g=0;c.startFrameLoop(y=>{if(r.readyState===r.OPEN&&!P(r.bufferedAmount,I(c.screenCapture.getQuality())))return r.send(D(y,g)),g=g+1>>>0,d++,m=Date.now(),!0;p++,c.screenCapture.notifyFrameDropped();const f=Date.now()-m;return f>3e3&&console.warn(`[remote-desktop] \u8FDE\u7EED ${f}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);x(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{q as attachRemoteDesktopWebSocketServer,h as permissionManager,i as sessionManager};
3
+ `),e.destroy();return}o.handleUpgrade(t,e,s,r=>{const b=p.searchParams.get("agentId")||"default";i.createSession(b,k).then(a=>{const c=i.getSession(a.sessionId);if(c?.attachWebSocket(r),f(r,{type:"remote-desktop:start-result",sessionId:a.sessionId,data:{screens:a.screens,permissions:a.permissions}}),c){let l=0,d=0,m=Date.now();const $=setInterval(()=>{const u=Date.now()-m,S=c.screenCapture.frameStats;console.warn(`[remote-desktop] frames: sent=${l} dropped=${d} bufferedAmount=${r.bufferedAmount} silent=${u}ms quality=${c.screenCapture.getQuality()} sharp=${I()} full=${S.full} partial=${S.partial} null=${S.null} sharpFail=${S.sharpFail}`),l=0,d=0,c.screenCapture.resetFrameStats()},5e3);let g=0;c.startFrameLoop(y=>{if(r.readyState===r.OPEN&&!M(r.bufferedAmount,D(c.screenCapture.getQuality())))return r.send(T(y,g)),g=g+1>>>0,l++,m=Date.now(),!0;d++,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",l=>{try{const d=l.toString();if(d.startsWith("{")){const m=JSON.parse(d);x(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),f(r,{type:"remote-desktop:error",sessionId:"",data:{error:String(a)}}),r.close(1011,String(a))})})})}export{q as attachRemoteDesktopWebSocketServer,h as permissionManager,i as sessionManager};
4
4
 
@@ -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,8 +1,8 @@
1
1
  import type { WebSocket } from "ws";
2
- import { ScreenCapture } from "./screen-capture.js";
2
+ import { ComputerControl } from "./computer-control.js";
3
3
  import { InputDispatcher } from "./input-dispatcher.js";
4
4
  import { PermissionManager } from "./permission-manager.js";
5
- import { ComputerControl } from "./computer-control.js";
5
+ import { ScreenCapture } from "./screen-capture.js";
6
6
  import type { InputMessage, RemoteDesktopCapability, ScreenInfo, StartResult, CaptureFrame } from "./types.js";
7
7
  export declare class RemoteDesktopSession {
8
8
 
@@ -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();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};
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
 
@@ -1,5 +1,5 @@
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__"
1
+ import{spawn as y,exec as P}from"node:child_process";import{promisify as E}from"node:util";import{toOsMouseCoords as a}from"./coords.js";import{mapKey as p}from"./input-backends/key-map.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
- `);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,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};
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}=a(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}=a(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}=a(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(p);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=p(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=p(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
 
@@ -23,7 +23,7 @@ export type RemoteDesktopCapability = 'remote-desktop:view' | 'remote-desktop:mo
23
23
  export interface RemoteDesktopMessage {
24
24
  type: string;
25
25
  sessionId: string;
26
- data?: any;
26
+ data?: unknown;
27
27
  }
28
28
  export interface ScreenshotResult {
29
29
  data: Buffer;
@@ -32,9 +32,9 @@ export declare class UIAutomation {
32
32
 
33
33
  getUIElements(pid: number): Promise<UIElement[]>;
34
34
 
35
- clickElement(elementId: string): Promise<void>;
35
+ clickElement(_elementId: string): Promise<void>;
36
36
 
37
- setElementValue(elementId: string, value: string): Promise<void>;
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 m}from"node:child_process";import{promisify as c}from"node:util";const r=c(m);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 r(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]).map((e,s)=>({pid:e.pid||0,name:e.name||`Process ${e.pid}`,isForeground:!1,windowTitle:e.title||void 0,windowBounds:e.w?{x:e.x,y:e.y,width:e.w,height:e.h}: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 r(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]).map(e=>({id:String(e.id||""),role:(e.role||"").replace("ControlType.",""),name:e.name||void 0,value:e.value||void 0,bounds:e.w?{x:e.x,y:e.y,width:e.w,height:e.h}: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 r(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,d,l,p]=e,i=p?p.split(",").map(Number):[];return{pid:Number(s)||0,name:d||"",isForeground:!1,windowTitle:l||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 r(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(" "),[d,l,p,i]=s,a=i?i.split(",").map(Number):[];return{id:`mac-${e}`,role:(d||"").replace("AX","").toLowerCase(),name:l||void 0,value:p||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 "
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 r(t,{timeout:1e4});return JSON.parse(n.trim()).map(e=>({pid:e.pid||0,name:e.name||"",isForeground:!1,windowTitle:e.name}))}catch{return[]}}async getUIElementsLinux(t){const n=`python3 -c "
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 r(n,{timeout:1e4});return JSON.parse(o.trim()).map(s=>({id:s.id,role:s.role,name:s.name||void 0,bounds:{x:s.x,y:s.y,width:s.w,height:s.h}}))}catch{return[]}}}export{h as UIAutomation};
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{applyCurrentAiConfigToInstalledTools as $}from"../services/ai-source-apply.js";import{createAiConfigService as k}from"../services/ai-config-service.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`,n=await m.get(t,{headers:{"x-api-key":o,"anthropic-version":"2023-06-01"},timeout:1e4});return S(n.data)}const a=e.endsWith("/v1")?[`${e}/models`]:[`${e}/models`,`${e}/v1/models`];for(const t of a)try{const n=await m.get(t,{headers:{Authorization:`Bearer ${o}`},timeout:1e4}),s=S(n.data);if(s.length>0)return s}catch{}return[]}g.post("/cc-switch/apply-ai-sources",f,async(r,o)=>{const{agentId:d,template:e,ccSwitchEnabledTools:a,acodeTemplateId:t,claudecodeTemplateId:n,codexTemplateId:s,opencodeTemplateId:i,acodeModelEffort:y,claudecodeModelEffort:T,codexModelEffort:A,opencodeModelEffort:b,acodeSubAgentTemplateId:I,acodeSubAgentModel:h,acodeSubAgentModelEffort:M,acode:E,acodeSubAgent:q,claudecode:v,codex:w,opencode:j}=r.body||{};if(!d){o.status(400).json({error:"agentId is required"});return}try{const l=k(),c=F(a),x=await z(c),p=await $({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(n||""),codexTemplateId:String(s||""),opencodeTemplateId:String(i||""),acodeModelEffort:String(y||""),claudecodeModelEffort:String(T||""),codexModelEffort:String(A||""),opencodeModelEffort:String(b||""),acodeSubAgentTemplateId:String(I||""),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(),n=String(e||"").trim();if(!t||!n){o.status(400).json({error:"baseUrl and apiKey are required"});return}try{const s=await R(t,n,a);o.json({ok:!0,models:s})}catch(s){const i=s;u.error("Failed to list remote AI models",{error:i?.message,baseUrl:t}),o.status(400).json({error:i?.message||"list ai models failed"})}});export{g as aiSourcesRouter};
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 y}from"node:fs";import p from"node:path";import{pathToFileURL as b}from"node:url";import{Router as C}from"express";import{getAcodeDataHome as j}from"../config.js";import{validateToken as m}from"../middleware/auth.js";import{createLogger as B}from"../utils/logger.js";import{getReleasedAcodeEntryPath as T}from"../services/acode-package.js";import{getAcodeBgManager as h}from"../services/acode-bg-manager.js";import{listPtySessions as E}from"./pty.js";let f=null;function I(){return f||(f=(async()=>{try{const e=T(),t=p.join(p.dirname(e),"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(e){return g.warn("[acode-bg-tasks] \u52A0\u8F7D BackgroundCommandStore \u5931\u8D25: %s",e?.message),null}})().then(e=>(e||(f=null),e)),f)}const g=B("acode-bg-tasks"),k=C();function _(e){return String(e).replace(/[^a-zA-Z0-9_-]/g,"_")}function L(e){return p.join(j(),"bg-tasks",_(e),"state.json")}k.get("/acode-bg-tasks/:agentId",m,async(e,t)=>{const r=e.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 d=L(String(i));try{const n=await y.readFile(d,"utf-8"),s=JSON.parse(n),c=(Array.isArray(s.tasks)?s.tasks:[]).map(o=>{const u=o;return{taskId:u.taskId,status:u.status,command:u.commandPreview??u.command,commandPreview:u.commandPreview,cwd:u.cwd,startedAt:u.startedAt,finishedAt:u.finishedAt,exitCode:u.exitCode,signal:u.signal,outputLineCount:u.outputLineCount,outputBytes:u.outputBytes}});t.json({ok:!0,agentId:String(i),updatedAt:typeof s.updatedAt=="number"?s.updatedAt:null,tasks:c})}catch(n){if((typeof n=="object"&&n!==null?Reflect.get(n,"code"):void 0)==="ENOENT"){t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}if(n instanceof SyntaxError){g.warn("[acode-bg-tasks] \u6587\u4EF6\u635F\u574F\uFF08JSON \u89E3\u6790\u5931\u8D25\uFF09: %s",d),t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}const a=n;g.error("[acode-bg-tasks] \u8BFB\u53D6\u5931\u8D25: %s",a.message),t.status(500).json({ok:!1,error:a.message})}}),k.get("/acode-agent-pty-sessions/:agentId",m,(e,t)=>{const r=e.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 d=String(i),s=E().filter(a=>a.ownerUserId===d);t.json({ok:!0,agentId:d,sessions:s})});const v=15e3;function S(e){return{taskId:e.taskId,status:e.status,exitCode:e.exitCode,signal:e.signal,startedAt:e.startedAt,finishedAt:e.finishedAt,command:e.command,lines:e.lines,nextSeq:e.nextSeq,totalLines:e.totalLines,totalBytes:e.totalBytes,truncated:e.truncated}}async function x(e,t){let i=null;try{i=await y.open(e,"r")}catch(d){if(d?.code==="ENOENT")return{text:[],fileExists:!1,truncated:!1};throw d}try{const n=(await i.stat()).size;if(n===0)return{text:[],fileExists:!0,truncated:!1};const s=Math.min(n,2097152),a=n-s,c=Buffer.alloc(s);await i.read(c,0,s,a);const u=c.toString("utf-8").split(`
2
- `);return a>0&&u.shift(),{text:u.slice(-t),fileExists:!0,truncated:s<n}}finally{await i.close()}}k.get("/acode-bg-tasks/:agentId/:taskId/output",m,(e,t)=>{(async()=>{const r=String(e.params.agentId),i=String(e.params.taskId),d=Math.min(2e3,Math.max(100,Number(e.query.tailLines)||500));try{const n=await h();if(n){const s=n.getTaskOutputSnapshot(r,i);if(s){t.json({ok:!0,source:"memory",...S(s)});return}}}catch(n){g.warn("[acode-bg-tasks] \u5185\u5B58\u5FEB\u7167\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",n?.message)}try{const n=await I();if(!n){t.status(503).json({ok:!1,error:"store unavailable"});return}const s=await n.readSession(r);if(!s){t.status(404).json({ok:!1,error:"task_not_found"});return}const a=s.tasks.find(l=>l.taskId===i);if(!a){t.status(404).json({ok:!1,error:"task_not_found"});return}const{text:c,fileExists:o,truncated:u}=await x(a.outputPath,d);t.json({ok:!0,source:"file",taskId:a.taskId,status:a.status,exitCode:a.exitCode,signal:a.signal,startedAt:a.startedAt,finishedAt:a.finishedAt,command:a.command,lines:c,nextSeq:0,totalLines:a.outputLineCount,totalBytes:a.outputBytes,truncated:u||!o})}catch(n){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95\u8BFB\u53D6\u5931\u8D25: %s",n?.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"})})}),k.get("/acode-bg-tasks/:agentId/:taskId/stream",m,(e,t)=>{(async()=>{const r=String(e.params.agentId),i=String(e.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 d=(a,c)=>{t.writableEnded||t.destroyed||(c!==void 0&&t.write(`id: ${c}
3
- `),t.write(`data: ${JSON.stringify(a)}
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 n,s=null;try{n=await h(),n&&(s=n.getTaskOutputSnapshot(r,i))}catch(a){g.warn("[acode-bg-tasks] \u5185\u5B58\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",a?.message)}if(s){if(d({type:"snapshot",source:"memory",...S(s)}),s.status!=="running"){d({type:"done",status:s.status,exitCode:s.exitCode,signal:s.signal}),t.end();return}let a=null,c=!1;const o=()=>{c||(c=!0,a?.(),clearInterval(u))},u=setInterval(()=>{if(t.writableEnded||t.destroyed){o();return}try{t.write(`: ping
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{o()}},v);if(u.unref?.(),a=n.subscribeTaskOutput(r,i,{onLine:l=>d({type:"output",stream:l.stream,text:l.text,seq:l.seq},l.seq),onDone:l=>{d({type:"done",status:l.status,exitCode:l.exitCode,signal:l.signal}),o(),t.end()}}),!a){d({type:"done",status:s.status,exitCode:s.exitCode,signal:s.signal}),o(),t.end();return}e.on("close",()=>{o(),t.writableEnded||t.end()}),e.on("aborted",()=>o());return}try{const a=await I();if(!a){d({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const c=await a.readSession(r);if(!c){d({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const o=c.tasks.find(w=>w.taskId===i);if(!o){d({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const{text:u,fileExists:l,truncated:A}=await x(o.outputPath,500);d({type:"snapshot",source:"file",taskId:o.taskId,status:o.status,exitCode:o.exitCode,signal:o.signal,startedAt:o.startedAt,finishedAt:o.finishedAt,command:o.command,lines:u,nextSeq:0,totalLines:o.outputLineCount,totalBytes:o.outputBytes,truncated:A||!l}),d({type:"done",status:o.status,exitCode:o.exitCode,signal:o.signal}),t.end()}catch(a){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95 stream \u5931\u8D25: %s",a?.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{k as backgroundTasksRouter};
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
 
@@ -1,2 +1,2 @@
1
- import{Router as u}from"express";import{validateToken as p,validateTokenOrLoopback as f}from"../middleware/auth.js";import{createLogger as w}from"../utils/logger.js";import{addBrowserListener as y,listBrowserListeners as k,removeBrowserListener as h,toggleBrowserListener as j,updateBrowserListener as S,collectAgentListenerEvents as v}from"../services/browser-session-manager.js";const g=w("browser-listeners"),m=u();m.get("/browser-listeners/:agentId/events",f,async(s,r)=>{const n=String(s.params.agentId??"").trim();if(!n){r.status(400).json({ok:!1,error:"agentId is required"});return}try{const o=await v(n);r.json({ok:!0,agentId:n,events:o})}catch(o){const e=o;g.error("[browser-listeners] \u6536\u96C6\u4E8B\u4EF6\u5931\u8D25: %s",e.message),r.status(500).json({ok:!1,error:e.message})}}),m.post("/browser-listeners/:agentId/:browserId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim();if(!n||!o){r.status(400).json({ok:!1,error:"agentId and browserId are required"});return}const e=s.body,t=typeof e.selector=="string"?e.selector.trim():"",d=typeof e.prompt=="string"?e.prompt.trim():"";if(!t){r.status(400).json({ok:!1,error:"selector is required"});return}if(!d){r.status(400).json({ok:!1,error:"prompt is required"});return}const c=typeof e.pathPattern=="string"&&e.pathPattern.trim()?e.pathPattern.trim():"",l=typeof e.enabled=="boolean"?e.enabled:!0,I=Array.isArray(e.changeTypes)?e.changeTypes.filter(i=>typeof i=="string"&&(i==="added"||i==="removed"||i==="changed"||i==="exists")):void 0;try{const i=await y(n,o,{selector:t,pathPattern:c,prompt:d,enabled:l,changeTypes:I});r.json({ok:!0,listener:i})}catch(i){const a=i;g.error("[browser-listeners] \u6DFB\u52A0\u5931\u8D25: %s",a.message),r.status(500).json({ok:!1,error:a.message})}}),m.get("/browser-listeners/:agentId/:browserId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim();if(!n||!o){r.status(400).json({ok:!1,error:"agentId and browserId are required"});return}try{const e=await k(n,o);r.json({ok:!0,agentId:n,browserId:o,listeners:e})}catch(e){const t=e;g.error("[browser-listeners] \u5217\u8868\u5931\u8D25: %s",t.message),r.status(500).json({ok:!1,error:t.message})}}),m.patch("/browser-listeners/:agentId/:browserId/:listenerId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim(),e=String(s.params.listenerId??"").trim();if(!n||!o||!e){r.status(400).json({ok:!1,error:"agentId, browserId and listenerId are required"});return}const t=s.body,d=typeof t.enabled=="boolean"?t.enabled:void 0;try{const c=await j(n,o,e,d);if(!c){r.status(404).json({ok:!1,error:`listener ${e} not found`});return}r.json({ok:!0,listener:c})}catch(c){const l=c;g.error("[browser-listeners] \u5207\u6362\u5931\u8D25: %s",l.message),r.status(500).json({ok:!1,error:l.message})}}),m.put("/browser-listeners/:agentId/:browserId/:listenerId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim(),e=String(s.params.listenerId??"").trim();if(!n||!o||!e){r.status(400).json({ok:!1,error:"agentId, browserId and listenerId are required"});return}const t=s.body,d=typeof t.selector=="string"?t.selector.trim():void 0,c=typeof t.prompt=="string"?t.prompt.trim():void 0,l=typeof t.pathPattern=="string"?t.pathPattern:void 0,I=typeof t.enabled=="boolean"?t.enabled:void 0,i=Array.isArray(t.changeTypes)?t.changeTypes.filter(a=>typeof a=="string"&&(a==="added"||a==="removed"||a==="changed"||a==="exists")):void 0;try{const a=await S(n,o,e,{selector:d,pathPattern:l,prompt:c,enabled:I,changeTypes:i});if(!a){r.status(404).json({ok:!1,error:`listener ${e} not found`});return}r.json({ok:!0,listener:a})}catch(a){const b=a;g.error("[browser-listeners] \u66F4\u65B0\u5931\u8D25: %s",b.message),r.status(500).json({ok:!1,error:b.message})}}),m.delete("/browser-listeners/:agentId/:browserId/:listenerId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim(),e=String(s.params.listenerId??"").trim();if(!n||!o||!e){r.status(400).json({ok:!1,error:"agentId, browserId and listenerId are required"});return}try{const t=await h(n,o,e);r.json({ok:t,listenerId:e,removed:t})}catch(t){const d=t;g.error("[browser-listeners] \u5220\u9664\u5931\u8D25: %s",d.message),r.status(500).json({ok:!1,error:d.message})}});export{m as browserListenersRouter};
1
+ import{Router as u}from"express";import{validateToken as p,validateTokenOrLoopback as f}from"../middleware/auth.js";import{addBrowserListener as w,listBrowserListeners as y,removeBrowserListener as k,toggleBrowserListener as h,updateBrowserListener as j,collectAgentListenerEvents as S}from"../services/browser-session-manager.js";import{createLogger as v}from"../utils/logger.js";const g=v("browser-listeners"),m=u();m.get("/browser-listeners/:agentId/events",f,async(s,r)=>{const n=String(s.params.agentId??"").trim();if(!n){r.status(400).json({ok:!1,error:"agentId is required"});return}try{const o=await S(n);r.json({ok:!0,agentId:n,events:o})}catch(o){const e=o;g.error("[browser-listeners] \u6536\u96C6\u4E8B\u4EF6\u5931\u8D25: %s",e.message),r.status(500).json({ok:!1,error:e.message})}}),m.post("/browser-listeners/:agentId/:browserId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim();if(!n||!o){r.status(400).json({ok:!1,error:"agentId and browserId are required"});return}const e=s.body,t=typeof e.selector=="string"?e.selector.trim():"",d=typeof e.prompt=="string"?e.prompt.trim():"";if(!t){r.status(400).json({ok:!1,error:"selector is required"});return}if(!d){r.status(400).json({ok:!1,error:"prompt is required"});return}const c=typeof e.pathPattern=="string"&&e.pathPattern.trim()?e.pathPattern.trim():"",l=typeof e.enabled=="boolean"?e.enabled:!0,I=Array.isArray(e.changeTypes)?e.changeTypes.filter(i=>typeof i=="string"&&(i==="added"||i==="removed"||i==="changed"||i==="exists")):void 0;try{const i=await w(n,o,{selector:t,pathPattern:c,prompt:d,enabled:l,changeTypes:I});r.json({ok:!0,listener:i})}catch(i){const a=i;g.error("[browser-listeners] \u6DFB\u52A0\u5931\u8D25: %s",a.message),r.status(500).json({ok:!1,error:a.message})}}),m.get("/browser-listeners/:agentId/:browserId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim();if(!n||!o){r.status(400).json({ok:!1,error:"agentId and browserId are required"});return}try{const e=await y(n,o);r.json({ok:!0,agentId:n,browserId:o,listeners:e})}catch(e){const t=e;g.error("[browser-listeners] \u5217\u8868\u5931\u8D25: %s",t.message),r.status(500).json({ok:!1,error:t.message})}}),m.patch("/browser-listeners/:agentId/:browserId/:listenerId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim(),e=String(s.params.listenerId??"").trim();if(!n||!o||!e){r.status(400).json({ok:!1,error:"agentId, browserId and listenerId are required"});return}const t=s.body,d=typeof t.enabled=="boolean"?t.enabled:void 0;try{const c=await h(n,o,e,d);if(!c){r.status(404).json({ok:!1,error:`listener ${e} not found`});return}r.json({ok:!0,listener:c})}catch(c){const l=c;g.error("[browser-listeners] \u5207\u6362\u5931\u8D25: %s",l.message),r.status(500).json({ok:!1,error:l.message})}}),m.put("/browser-listeners/:agentId/:browserId/:listenerId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim(),e=String(s.params.listenerId??"").trim();if(!n||!o||!e){r.status(400).json({ok:!1,error:"agentId, browserId and listenerId are required"});return}const t=s.body,d=typeof t.selector=="string"?t.selector.trim():void 0,c=typeof t.prompt=="string"?t.prompt.trim():void 0,l=typeof t.pathPattern=="string"?t.pathPattern:void 0,I=typeof t.enabled=="boolean"?t.enabled:void 0,i=Array.isArray(t.changeTypes)?t.changeTypes.filter(a=>typeof a=="string"&&(a==="added"||a==="removed"||a==="changed"||a==="exists")):void 0;try{const a=await j(n,o,e,{selector:d,pathPattern:l,prompt:c,enabled:I,changeTypes:i});if(!a){r.status(404).json({ok:!1,error:`listener ${e} not found`});return}r.json({ok:!0,listener:a})}catch(a){const b=a;g.error("[browser-listeners] \u66F4\u65B0\u5931\u8D25: %s",b.message),r.status(500).json({ok:!1,error:b.message})}}),m.delete("/browser-listeners/:agentId/:browserId/:listenerId",p,async(s,r)=>{const n=String(s.params.agentId??"").trim(),o=String(s.params.browserId??"").trim(),e=String(s.params.listenerId??"").trim();if(!n||!o||!e){r.status(400).json({ok:!1,error:"agentId, browserId and listenerId are required"});return}try{const t=await k(n,o,e);r.json({ok:t,listenerId:e,removed:t})}catch(t){const d=t;g.error("[browser-listeners] \u5220\u9664\u5931\u8D25: %s",d.message),r.status(500).json({ok:!1,error:d.message})}});export{m as browserListenersRouter};
2
2
 
@@ -1,2 +1,2 @@
1
- import{Router as p}from"express";import{validateToken as c}from"../middleware/auth.js";import{getEffective as d,getDefaults as m,getOverrides as u}from"../services/capability-config.js";import{skillManager as f}from"../services/skill-manager.js";import{mcpDeployManager as g}from"../services/mcp-deploy.js";import{pluginManager as y}from"../services/plugin-manager.js";import{createLogger as b}from"../utils/logger.js";const k=b("capabilities"),D=p();function l(i,t,o){const s=u(i,t),r=m(t),a={};for(const e of o){const n=s[e]!==void 0?"agent":r[e]!==void 0?"bridge":"default";a[e]={enabled:d(i,t,e),source:n}}return a}D.post("/capabilities/state",c,async(i,t)=>{const o=String((i.body??{})?.agentId??"").trim();if(!o){t.status(400).json({ok:!1,error:"agentId is required"});return}try{const s=f.listDeployed().map(e=>e.skillName),r=g.listDeployed().map(e=>e.name),a=(await y.listDeployed()).map(e=>e.pluginName);t.json({ok:!0,agentId:o,skills:l(o,"skills",s),mcp:l(o,"mcp",r),plugins:l(o,"plugins",a),deployedSkills:s,deployedMcp:r,deployedPlugins:a})}catch(s){k.warn("[capabilities/state] \u8BFB\u53D6\u80FD\u529B\u72B6\u6001\u5931\u8D25: %s",s.message),t.status(500).json({ok:!1,error:s.message||"read capability state failed"})}});export{D as capabilitiesRouter};
1
+ import{Router as p}from"express";import{validateToken as c}from"../middleware/auth.js";import{getEffective as d,getDefaults as m,getOverrides as u}from"../services/capability-config.js";import{mcpDeployManager as f}from"../services/mcp-deploy.js";import{pluginManager as g}from"../services/plugin-manager.js";import{skillManager as y}from"../services/skill-manager.js";import{createLogger as b}from"../utils/logger.js";const k=b("capabilities"),D=p();function l(i,t,o){const s=u(i,t),r=m(t),a={};for(const e of o){const n=s[e]!==void 0?"agent":r[e]!==void 0?"bridge":"default";a[e]={enabled:d(i,t,e),source:n}}return a}D.post("/capabilities/state",c,async(i,t)=>{const o=String((i.body??{})?.agentId??"").trim();if(!o){t.status(400).json({ok:!1,error:"agentId is required"});return}try{const s=y.listDeployed().map(e=>e.skillName),r=f.listDeployed().map(e=>e.name),a=(await g.listDeployed()).map(e=>e.pluginName);t.json({ok:!0,agentId:o,skills:l(o,"skills",s),mcp:l(o,"mcp",r),plugins:l(o,"plugins",a),deployedSkills:s,deployedMcp:r,deployedPlugins:a})}catch(s){k.warn("[capabilities/state] \u8BFB\u53D6\u80FD\u529B\u72B6\u6001\u5931\u8D25: %s",s.message),t.status(500).json({ok:!1,error:s.message||"read capability state failed"})}});export{D as capabilitiesRouter};
2
2
 
@@ -1,4 +1,4 @@
1
- import{Router as $}from"express";import v from"node:fs";import K from"node:path";import I from"node:os";import{execFile as Y}from"node:child_process";import{getRuntimeBindingPublicState as H,getRuntimePairingCode as Q,normalizeSchedulerBaseUrl as X}from"../services/runtime-binding.js";import{getAgentProcessManager as C}from"../services/agent-process-manager.js";import{collectSystemMetrics as Z}from"../services/system-metrics.js";import{loadPersistedSessions as ee}from"../services/terminal-persistence.js";import{createLogger as te}from"../utils/logger.js";import{getBridgeVersion as ne,getRuntimeHomeDir as se,getUserHomeDir as re}from"../config.js";import{runExclusiveConfigWrite as O}from"../utils/config-write-queue.js";import{sdkSessions as ae}from"./terminal.js";import{createSession as oe,getPanelCredentials as N,validateSession as U,destroySession as z,validateCredentials as F,hasConfiguredCredentials as B,isDefaultCredentials as ie,setPanelCredentials as de,getAllSessions as le,getActiveSessionCount as ce,checkLoginAllowed as ue,recordLoginFailure as ge,recordLoginSuccess as pe}from"../services/panel-auth.js";import{getRecentLogs as fe}from"../utils/logger.js";import{autoRegister as me,getRegistrationState as _,getAutoRegisterConfigFilePath as E}from"../services/auto-register.js";import{getAccessControlEntries as be,isValidIpEntry as he,parseAllowIps as ve,refreshAccessControlCache as ye}from"../services/access-control.js";const x=te("dashboard");let T=null;const xe=1e4;let L=null,P=null;const we=3e4;async function D(){Z().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=ne();async function Se(){const t=await import("node:fs/promises"),n=(await import("node:path")).join(se(),".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=$(),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=ue(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(!F(String(i),String(r))){ge(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}pe(n);const a=oe(),o=!B();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&&z(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:ie(),configured:B(),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(B()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const o=N();if(!F(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 de(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 X(t)??String(t||"").trim()}g.get("/runtime/server-config",(t,e)=>{if(!m(t,e))return;const n=E();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=E();await O(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:be(),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=ve(n),i=s.filter(r=>!he(r));if(i.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${i.join(", ")}`});return}try{const r=E();await O(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"),ye()}),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=H(),s=_();e.json({ok:!0,hostname:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:V,configPath:E(),homeDir:re(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:ce()})}),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=fe(s,r);e.json({ok:!0,logs:a})}),g.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await me();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=le().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)&&(z(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)];Y(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(H()),Promise.resolve(_()),ee().catch(()=>[])]),l=C().getAllManagedProcesses(),f=Array.from(ae.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:Q()},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(!B()){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">
@@ -1,5 +1,5 @@
1
- import{promises as m}from"node:fs";import p from"node:path";import{Router as d}from"express";import{createLogger as h}from"../utils/logger.js";import{validateToken as g}from"../middleware/auth.js";import{readBeforeContentFromCache as v,readDiffFromCache as b}from"../services/workspaceTaskChangeWatcher.js";const x=h("file-change-diff"),y=d();function L(s){const n=[];let o=0,e=0;for(const r of s.split(`
2
- `)){const t=r;if(t.startsWith("@@")){const i=t.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);i&&(o=Number(i[1]),e=Number(i[2])),n.push({type:"hunk",text:t,oldLine:o,newLine:e});continue}if(!(t.startsWith("--- ")||t.startsWith("+++ ")||t.startsWith("Index:")||t.startsWith("===")||t.startsWith("\\ No newline"))){if(t.startsWith("+")){n.push({type:"add",text:t,oldLine:null,newLine:e}),e++;continue}if(t.startsWith("-")){n.push({type:"remove",text:t,oldLine:o,newLine:null}),o++;continue}if(t.startsWith(" ")){n.push({type:"context",text:t,oldLine:o,newLine:e}),o++,e++;continue}}}return n}async function W(s,n,o,e){const r=String(o||"").toUpperCase();if(r==="UPDATE"){if(!n)return{available:!1,operation:r,filePath:e,lines:[],error:"\u7F3A\u5C11 cacheKey\uFF0C\u65E0\u6CD5\u8BFB\u53D6 diff \u7F13\u5B58"};const t=await b(s,n);return t===null?{available:!1,operation:r,filePath:e,lines:[],error:`\u672C\u5730\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230 diff \u8865\u4E01\uFF08cacheKey=${n}\uFF09\uFF0C\u7F13\u5B58\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406`}:{available:!0,operation:r,filePath:e,lines:L(t)}}if(r==="DELETE"){if(!n)return{available:!1,operation:r,filePath:e,lines:[],error:"\u7F3A\u5C11 cacheKey\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u53D8\u66F4\u524D\u5185\u5BB9"};const t=await v(s,n);if(t===null)return{available:!1,operation:r,filePath:e,lines:[],error:`\u672C\u5730\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u53D8\u66F4\u524D\u5185\u5BB9\uFF08cacheKey=${n}\uFF09\uFF0C\u7F13\u5B58\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406`};const i=t.split(`
1
+ import{promises as m}from"node:fs";import p from"node:path";import{Router as d}from"express";import{validateToken as h}from"../middleware/auth.js";import{readBeforeContentFromCache as g,readDiffFromCache as v}from"../services/workspaceTaskChangeWatcher.js";import{createLogger as b}from"../utils/logger.js";const x=b("file-change-diff"),y=d();function L(s){const n=[];let o=0,e=0;for(const r of s.split(`
2
+ `)){const t=r;if(t.startsWith("@@")){const i=t.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);i&&(o=Number(i[1]),e=Number(i[2])),n.push({type:"hunk",text:t,oldLine:o,newLine:e});continue}if(!(t.startsWith("--- ")||t.startsWith("+++ ")||t.startsWith("Index:")||t.startsWith("===")||t.startsWith("\\ No newline"))){if(t.startsWith("+")){n.push({type:"add",text:t,oldLine:null,newLine:e}),e++;continue}if(t.startsWith("-")){n.push({type:"remove",text:t,oldLine:o,newLine:null}),o++;continue}if(t.startsWith(" ")){n.push({type:"context",text:t,oldLine:o,newLine:e}),o++,e++;continue}}}return n}async function W(s,n,o,e){const r=String(o||"").toUpperCase();if(r==="UPDATE"){if(!n)return{available:!1,operation:r,filePath:e,lines:[],error:"\u7F3A\u5C11 cacheKey\uFF0C\u65E0\u6CD5\u8BFB\u53D6 diff \u7F13\u5B58"};const t=await v(s,n);return t===null?{available:!1,operation:r,filePath:e,lines:[],error:`\u672C\u5730\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230 diff \u8865\u4E01\uFF08cacheKey=${n}\uFF09\uFF0C\u7F13\u5B58\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406`}:{available:!0,operation:r,filePath:e,lines:L(t)}}if(r==="DELETE"){if(!n)return{available:!1,operation:r,filePath:e,lines:[],error:"\u7F3A\u5C11 cacheKey\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u53D8\u66F4\u524D\u5185\u5BB9"};const t=await g(s,n);if(t===null)return{available:!1,operation:r,filePath:e,lines:[],error:`\u672C\u5730\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u53D8\u66F4\u524D\u5185\u5BB9\uFF08cacheKey=${n}\uFF09\uFF0C\u7F13\u5B58\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406`};const i=t.split(`
3
3
  `);i.length>0&&i[i.length-1]===""&&i.pop();const l=i.map((a,f)=>({type:"remove",text:`-${a}`,oldLine:f+1,newLine:null}));return{available:!0,operation:r,filePath:e,lines:l}}if(r==="CREATE")try{const t=p.resolve(s),i=p.resolve(s,e);if(!i.startsWith(t))return{available:!1,operation:r,filePath:e,lines:[],error:"\u8DEF\u5F84\u8D8A\u754C\uFF0C\u62D2\u7EDD\u8BFB\u53D6\u5DE5\u4F5C\u533A\u5916\u6587\u4EF6"};const a=(await m.readFile(i,"utf-8")).split(`
4
- `);a.length>0&&a[a.length-1]===""&&a.pop();const f=a.map((u,c)=>({type:"add",text:`+${u}`,oldLine:null,newLine:c+1}));return{available:!0,operation:r,filePath:e,lines:f}}catch{return{available:!1,operation:r,filePath:e,lines:[],error:"\u65B0\u589E\u6587\u4EF6\u5F53\u524D\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BFB\uFF08\u53EF\u80FD\u5DF2\u88AB\u5220\u9664\uFF09\uFF0C\u65E0\u6CD5\u5C55\u793A\u5185\u5BB9"}}return{available:!1,operation:r,filePath:e,lines:[],error:`\u4E0D\u652F\u6301\u7684\u64CD\u4F5C\u7C7B\u578B: ${r}`}}y.post("/file-change-diff/read",g,async(s,n)=>{const{workspacePath:o,cacheKey:e,operation:r,filePath:t}=s.body||{};if(!o||!String(o).trim()){n.status(400).json({error:"workspacePath is required"});return}if(!r||!String(r).trim()){n.status(400).json({error:"operation is required"});return}if(!t||!String(t).trim()){n.status(400).json({error:"filePath is required"});return}try{const i=await W(String(o).trim(),e?String(e).trim():null,String(r).trim(),String(t).trim());n.json(i)}catch(i){const l=i instanceof Error?i.message:String(i);x.warn(`[file-change-diff] \u8BFB\u53D6 diff \u5931\u8D25: ${l}`),n.status(500).json({ok:!1,error:l})}});export{W as buildChangeDiffResponse,y as fileChangeDiffRouter,L as parseDiffPatchLines};
4
+ `);a.length>0&&a[a.length-1]===""&&a.pop();const f=a.map((u,c)=>({type:"add",text:`+${u}`,oldLine:null,newLine:c+1}));return{available:!0,operation:r,filePath:e,lines:f}}catch{return{available:!1,operation:r,filePath:e,lines:[],error:"\u65B0\u589E\u6587\u4EF6\u5F53\u524D\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BFB\uFF08\u53EF\u80FD\u5DF2\u88AB\u5220\u9664\uFF09\uFF0C\u65E0\u6CD5\u5C55\u793A\u5185\u5BB9"}}return{available:!1,operation:r,filePath:e,lines:[],error:`\u4E0D\u652F\u6301\u7684\u64CD\u4F5C\u7C7B\u578B: ${r}`}}y.post("/file-change-diff/read",h,async(s,n)=>{const{workspacePath:o,cacheKey:e,operation:r,filePath:t}=s.body||{};if(!o||!String(o).trim()){n.status(400).json({error:"workspacePath is required"});return}if(!r||!String(r).trim()){n.status(400).json({error:"operation is required"});return}if(!t||!String(t).trim()){n.status(400).json({error:"filePath is required"});return}try{const i=await W(String(o).trim(),e?String(e).trim():null,String(r).trim(),String(t).trim());n.json(i)}catch(i){const l=i instanceof Error?i.message:String(i);x.warn(`[file-change-diff] \u8BFB\u53D6 diff \u5931\u8D25: ${l}`),n.status(500).json({ok:!1,error:l})}});export{W as buildChangeDiffResponse,y as fileChangeDiffRouter,L as parseDiffPatchLines};
5
5
 
@@ -1,2 +1,2 @@
1
- import{promises as w}from"node:fs";import g from"node:path";import{Router as O}from"express";import*as P from"diff";import{createLogger as R}from"../utils/logger.js";import{validateToken as T}from"../middleware/auth.js";import{readBeforeContentFromCache as U,readDiffFromCache as _}from"../services/workspaceTaskChangeWatcher.js";const k=R("file-diff-rollback"),D=O();async function m(h,u){const r=[],l=[],s=[],i=g.resolve(String(h).trim()),t=[...u].sort((a,c)=>new Date(c.createdAt).getTime()-new Date(a.createdAt).getTime()),o=new Map,p=new Map;for(const a of t){const c=g.join(i,a.relativePath),e=a.relativePath.replace(/\\/g,"/");if(!g.resolve(c).startsWith(i)){const n=`Path escape detected: ${e}`;r.push(n),s.push({relativePath:e,originalOperation:a.originalOperation,status:"preview_failed",reason:n});continue}if(!o.has(e)){let n=null,f=!1;try{n=await w.readFile(c,"utf-8"),f=!0}catch{f=!1}o.set(e,n),p.set(e,f)}switch(a.originalOperation){case"CREATE":{l.push({type:"DELETE",fullPath:c,relativePath:e,originalOperation:"CREATE"}),s.push({relativePath:e,originalOperation:"CREATE",status:"preview_passed",action:"\u5220\u9664\u6587\u4EF6"}),o.set(e,null),p.set(e,!1);break}case"UPDATE":{if(!a.cacheKey){const d="\u7F3A\u5C11 cacheKey\uFF0C\u65E0\u6CD5\u8BFB\u53D6 diff \u8865\u4E01";r.push(`UPDATE rollback failed: ${d} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:d});break}const n=o.get(e)??null;if(n===null){const d="\u6587\u4EF6\u5F53\u524D\u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u5E94\u7528\u53CD\u5411 diff \u8865\u4E01";r.push(`UPDATE rollback failed: ${d} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:d});break}const f=await _(i,a.cacheKey);if(f===null){const d=`\u672C\u5730\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230 diff \u8865\u4E01 (cacheKey=${a.cacheKey})\uFF0C\u65E0\u6CD5\u7CBE\u786E\u56DE\u6EDA`;r.push(`UPDATE rollback failed: ${d} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:d});break}let v=null;try{const d=P.parsePatch(f),E=P.reversePatch(d),$=P.applyPatch(n,E);if($!==!1)v=$;else{const b="\u53CD\u5411 diff \u8865\u4E01\u65E0\u6CD5\u7CBE\u786E\u5E94\u7528\uFF1A\u6587\u4EF6\u5F53\u524D\u5185\u5BB9\u4E0E AI \u53D8\u66F4\u540E\u7684\u9884\u671F\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D\uFF0C\u53EF\u80FD\u5DF2\u88AB\u5176\u4ED6\u4EFB\u52A1\u6216\u7528\u6237\u4FEE\u6539\u4E86\u76F8\u540C\u533A\u57DF\uFF0C\u5B58\u5728\u51B2\u7A81";r.push(`UPDATE rollback failed: ${b} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:b});break}}catch(d){const E=`\u53CD\u5411 diff \u8865\u4E01\u5E94\u7528\u5F02\u5E38: ${d.message}`;r.push(`UPDATE rollback failed: ${E} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:E});break}l.push({type:"WRITE",fullPath:c,relativePath:e,originalOperation:"UPDATE",content:v}),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_passed",action:"\u5E94\u7528\u53CD\u5411 diff \u8865\u4E01"}),o.set(e,v);break}case"DELETE":{if(!a.cacheKey){const f="\u7F3A\u5C11 cacheKey\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u5220\u9664\u524D\u7684\u6587\u4EF6\u5185\u5BB9";r.push(`DELETE rollback failed: ${f} for ${e}`),s.push({relativePath:e,originalOperation:"DELETE",status:"preview_failed",reason:f});break}const n=await U(i,a.cacheKey);if(n===null){const f=`\u672C\u5730\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u5220\u9664\u524D\u7684\u6587\u4EF6\u5185\u5BB9 (cacheKey=${a.cacheKey})`;r.push(`DELETE rollback failed: ${f} for ${e}`),s.push({relativePath:e,originalOperation:"DELETE",status:"preview_failed",reason:f});break}l.push({type:"WRITE",fullPath:c,relativePath:e,originalOperation:"DELETE",content:n}),s.push({relativePath:e,originalOperation:"DELETE",status:"preview_passed",action:"\u91CD\u5EFA\u6587\u4EF6"}),o.set(e,n),p.set(e,!0);break}default:{const n=`\u672A\u77E5\u7684\u64CD\u4F5C\u7C7B\u578B "${a.originalOperation}"`;r.push(`${n} for ${e}`),s.push({relativePath:e,originalOperation:a.originalOperation,status:"preview_failed",reason:n});break}}}return{ok:r.length===0,errors:r,plan:l,details:s}}async function A(h,u){let r=0,l=0;const s=[],i=u.map(t=>({...t}));for(const t of h){const o=i.find(p=>p.relativePath===t.relativePath&&p.status==="preview_passed");try{t.type==="DELETE"?(await w.unlink(t.fullPath),k.info(`Rollback executed: deleted created file ${t.relativePath}`),o&&(o.status="success",o.action="\u5220\u9664\u6587\u4EF6"),r++):t.type==="WRITE"&&(await w.mkdir(g.dirname(t.fullPath),{recursive:!0}),await w.writeFile(t.fullPath,t.content,"utf-8"),k.info(`Rollback executed: restored file ${t.relativePath}`),o&&(o.status="success",o.action=t.originalOperation==="UPDATE"?"\u5E94\u7528\u53CD\u5411 diff \u8865\u4E01":"\u91CD\u5EFA\u6587\u4EF6"),r++)}catch(p){const a=`${p.message}`,c=`Rollback failed for ${t.relativePath}: ${a}`;s.push(c),k.warn(c),o&&(o.status="failed",o.reason=a),l++}}return{restored:r,skipped:l,errors:s,details:i}}D.post("/file-diff-rollback/preview",T,async(h,u)=>{const{workspacePath:r,operations:l}=h.body;if(!r||!Array.isArray(l)){u.status(400).json({error:"workspacePath and operations (array) are required"});return}const{ok:s,errors:i,plan:t,details:o}=await m(r,l);u.json({ok:s,errors:i,plan:t.map(p=>({type:p.type,relativePath:p.relativePath})),totalOperations:l.length,plannedOperations:t.length,details:o})}),D.post("/file-diff-rollback/execute",T,async(h,u)=>{const{workspacePath:r,operations:l,skipFailed:s}=h.body;if(!r||!Array.isArray(l)){u.status(400).json({error:"workspacePath and operations (array) are required"});return}const i=await m(r,l);if(!i.ok){if(s&&i.plan.length>0){const a=await A(i.plan,i.details),c=i.details.filter(y=>y.status==="preview_failed").length;k.warn(`Diff rollback partial execute: passed=${a.restored}, skippedFailed=${c}, ioSkipped=${a.skipped}`);const e={ok:a.errors.length===0,restored:a.restored,skipped:a.skipped+c,errors:a.errors,previewFailed:!1,partial:!0,details:a.details};u.json(e);return}const p={ok:!1,restored:0,skipped:l.length,errors:i.errors,previewFailed:!0,details:i.details};k.warn(`Diff rollback preview failed, no files touched: errors=${i.errors.length}`),u.json(p);return}const t=await A(i.plan,i.details),o={ok:t.errors.length===0,restored:t.restored,skipped:t.skipped,errors:t.errors,previewFailed:!1,details:t.details};k.info(`Diff rollback completed: restored=${t.restored}, skipped=${t.skipped}, errors=${t.errors.length}`),u.json(o)});export{D as fileDiffRollbackRouter};
1
+ import{promises as w}from"node:fs";import g from"node:path";import*as P from"diff";import{Router as O}from"express";import{validateToken as b}from"../middleware/auth.js";import{readBeforeContentFromCache as R,readDiffFromCache as U}from"../services/workspaceTaskChangeWatcher.js";import{createLogger as _}from"../utils/logger.js";const k=_("file-diff-rollback"),D=O();async function m(h,u){const r=[],n=[],s=[],i=g.resolve(String(h).trim()),t=[...u].sort((a,c)=>new Date(c.createdAt).getTime()-new Date(a.createdAt).getTime()),o=new Map,l=new Map;for(const a of t){const c=g.join(i,a.relativePath),e=a.relativePath.replace(/\\/g,"/");if(!g.resolve(c).startsWith(i)){const p=`Path escape detected: ${e}`;r.push(p),s.push({relativePath:e,originalOperation:a.originalOperation,status:"preview_failed",reason:p});continue}if(!o.has(e)){let p=null,f=!1;try{p=await w.readFile(c,"utf-8"),f=!0}catch{f=!1}o.set(e,p),l.set(e,f)}switch(a.originalOperation){case"CREATE":{n.push({type:"DELETE",fullPath:c,relativePath:e,originalOperation:"CREATE"}),s.push({relativePath:e,originalOperation:"CREATE",status:"preview_passed",action:"\u5220\u9664\u6587\u4EF6"}),o.set(e,null),l.set(e,!1);break}case"UPDATE":{if(!a.cacheKey){const d="\u7F3A\u5C11 cacheKey\uFF0C\u65E0\u6CD5\u8BFB\u53D6 diff \u8865\u4E01";r.push(`UPDATE rollback failed: ${d} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:d});break}const p=o.get(e)??null;if(p===null){const d="\u6587\u4EF6\u5F53\u524D\u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u5E94\u7528\u53CD\u5411 diff \u8865\u4E01";r.push(`UPDATE rollback failed: ${d} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:d});break}const f=await U(i,a.cacheKey);if(f===null){const d=`\u672C\u5730\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230 diff \u8865\u4E01 (cacheKey=${a.cacheKey})\uFF0C\u65E0\u6CD5\u7CBE\u786E\u56DE\u6EDA`;r.push(`UPDATE rollback failed: ${d} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:d});break}let v=null;try{const d=P.parsePatch(f),E=P.reversePatch(d),T=P.applyPatch(p,E);if(T!==!1)v=T;else{const $="\u53CD\u5411 diff \u8865\u4E01\u65E0\u6CD5\u7CBE\u786E\u5E94\u7528\uFF1A\u6587\u4EF6\u5F53\u524D\u5185\u5BB9\u4E0E AI \u53D8\u66F4\u540E\u7684\u9884\u671F\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D\uFF0C\u53EF\u80FD\u5DF2\u88AB\u5176\u4ED6\u4EFB\u52A1\u6216\u7528\u6237\u4FEE\u6539\u4E86\u76F8\u540C\u533A\u57DF\uFF0C\u5B58\u5728\u51B2\u7A81";r.push(`UPDATE rollback failed: ${$} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:$});break}}catch(d){const E=`\u53CD\u5411 diff \u8865\u4E01\u5E94\u7528\u5F02\u5E38: ${d.message}`;r.push(`UPDATE rollback failed: ${E} for ${e}`),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_failed",reason:E});break}n.push({type:"WRITE",fullPath:c,relativePath:e,originalOperation:"UPDATE",content:v}),s.push({relativePath:e,originalOperation:"UPDATE",status:"preview_passed",action:"\u5E94\u7528\u53CD\u5411 diff \u8865\u4E01"}),o.set(e,v);break}case"DELETE":{if(!a.cacheKey){const f="\u7F3A\u5C11 cacheKey\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u5220\u9664\u524D\u7684\u6587\u4EF6\u5185\u5BB9";r.push(`DELETE rollback failed: ${f} for ${e}`),s.push({relativePath:e,originalOperation:"DELETE",status:"preview_failed",reason:f});break}const p=await R(i,a.cacheKey);if(p===null){const f=`\u672C\u5730\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u5220\u9664\u524D\u7684\u6587\u4EF6\u5185\u5BB9 (cacheKey=${a.cacheKey})`;r.push(`DELETE rollback failed: ${f} for ${e}`),s.push({relativePath:e,originalOperation:"DELETE",status:"preview_failed",reason:f});break}n.push({type:"WRITE",fullPath:c,relativePath:e,originalOperation:"DELETE",content:p}),s.push({relativePath:e,originalOperation:"DELETE",status:"preview_passed",action:"\u91CD\u5EFA\u6587\u4EF6"}),o.set(e,p),l.set(e,!0);break}default:{const p=`\u672A\u77E5\u7684\u64CD\u4F5C\u7C7B\u578B "${a.originalOperation}"`;r.push(`${p} for ${e}`),s.push({relativePath:e,originalOperation:a.originalOperation,status:"preview_failed",reason:p});break}}}return{ok:r.length===0,errors:r,plan:n,details:s}}async function A(h,u){let r=0,n=0;const s=[],i=u.map(t=>({...t}));for(const t of h){const o=i.find(l=>l.relativePath===t.relativePath&&l.status==="preview_passed");try{if(t.type==="DELETE")await w.unlink(t.fullPath),k.info(`Rollback executed: deleted created file ${t.relativePath}`),o&&(o.status="success",o.action="\u5220\u9664\u6587\u4EF6"),r++;else if(t.type==="WRITE"){const l=t.content??"";await w.mkdir(g.dirname(t.fullPath),{recursive:!0}),await w.writeFile(t.fullPath,l,"utf-8"),k.info(`Rollback executed: restored file ${t.relativePath}`),o&&(o.status="success",o.action=t.originalOperation==="UPDATE"?"\u5E94\u7528\u53CD\u5411 diff \u8865\u4E01":"\u91CD\u5EFA\u6587\u4EF6"),r++}}catch(l){const a=`${l.message}`,c=`Rollback failed for ${t.relativePath}: ${a}`;s.push(c),k.warn(c),o&&(o.status="failed",o.reason=a),n++}}return{restored:r,skipped:n,errors:s,details:i}}D.post("/file-diff-rollback/preview",b,async(h,u)=>{const{workspacePath:r,operations:n}=h.body;if(!r||!Array.isArray(n)){u.status(400).json({error:"workspacePath and operations (array) are required"});return}const{ok:s,errors:i,plan:t,details:o}=await m(r,n);u.json({ok:s,errors:i,plan:t.map(l=>({type:l.type,relativePath:l.relativePath})),totalOperations:n.length,plannedOperations:t.length,details:o})}),D.post("/file-diff-rollback/execute",b,async(h,u)=>{const{workspacePath:r,operations:n,skipFailed:s}=h.body;if(!r||!Array.isArray(n)){u.status(400).json({error:"workspacePath and operations (array) are required"});return}const i=await m(r,n);if(!i.ok){if(s&&i.plan.length>0){const a=await A(i.plan,i.details),c=i.details.filter(y=>y.status==="preview_failed").length;k.warn(`Diff rollback partial execute: passed=${a.restored}, skippedFailed=${c}, ioSkipped=${a.skipped}`);const e={ok:a.errors.length===0,restored:a.restored,skipped:a.skipped+c,errors:a.errors,previewFailed:!1,partial:!0,details:a.details};u.json(e);return}const l={ok:!1,restored:0,skipped:n.length,errors:i.errors,previewFailed:!0,details:i.details};k.warn(`Diff rollback preview failed, no files touched: errors=${i.errors.length}`),u.json(l);return}const t=await A(i.plan,i.details),o={ok:t.errors.length===0,restored:t.restored,skipped:t.skipped,errors:t.errors,previewFailed:!1,details:t.details};k.info(`Diff rollback completed: restored=${t.restored}, skipped=${t.skipped}, errors=${t.errors.length}`),u.json(o)});export{D as fileDiffRollbackRouter};
2
2
 
@@ -1,2 +1,2 @@
1
- import{Router as c}from"express";import{validateToken as p}from"../middleware/auth.js";import{createLogger as u}from"../utils/logger.js";import{createSnapshot as f,restoreSnapshot as m,listSnapshots as d,deleteSnapshot as g,getSnapshotRootPath as S}from"../services/fileSnapshotStore.js";const h=c(),i=u("file-snapshot");h.post("/file-snapshot/create",p,async(n,t)=>{const{workspacePath:r,snapshotId:s,snapshotName:a,triggerType:o}=n.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const e=await f({workspacePath:String(r).trim(),snapshotId:s?String(s).trim():void 0,snapshotName:a?String(a).trim():void 0,triggerType:o?String(o).trim():void 0});if(!e.ok){i.warn(`[FileSnapshot] \u521B\u5EFA\u5FEB\u7167\u5931\u8D25: ${e.error}`),t.status(500).json({ok:!1,error:e.error});return}t.json({ok:!0,snapshotId:e.snapshotId,snapshotKey:e.snapshotKey,fileCount:e.fileCount,totalBytes:e.totalBytes,workspaceHash:e.workspaceHash,workspacePath:String(r).trim()})}catch(e){const l=e;i.error(`[FileSnapshot] \u521B\u5EFA\u5FEB\u7167\u5F02\u5E38: ${l.message}`),t.status(500).json({ok:!1,error:l.message||"create snapshot failed"})}}),h.post("/file-snapshot/restore",p,async(n,t)=>{const{workspacePath:r,snapshotKey:s,preRestoreBackup:a}=n.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!s||!String(s).trim()){t.status(400).json({error:"snapshotKey is required"});return}try{const o=await m({workspacePath:String(r).trim(),snapshotKey:String(s).trim(),preRestoreBackup:a!==!1});if(!o.ok){i.warn(`[FileSnapshot] \u8FD8\u539F\u5FEB\u7167\u5931\u8D25: ${o.error}`),t.status(500).json({ok:!1,error:o.error});return}t.json({ok:!0,filesRestored:o.filesRestored,filesDeleted:o.filesDeleted,filesSkipped:o.filesSkipped,preRestoreSnapshotKey:o.preRestoreSnapshotKey||null,workspacePath:String(r).trim()})}catch(o){const e=o;i.error(`[FileSnapshot] \u8FD8\u539F\u5FEB\u7167\u5F02\u5E38: ${e.message}`),t.status(500).json({ok:!1,error:e.message||"restore snapshot failed"})}}),h.post("/file-snapshot/list",p,async(n,t)=>{const{workspacePath:r}=n.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await d(String(r).trim());t.json({ok:!0,snapshots:s,count:s.length,workspacePath:String(r).trim()})}catch(s){const a=s;i.error(`[FileSnapshot] \u5217\u51FA\u5FEB\u7167\u5F02\u5E38: ${a.message}`),t.status(500).json({ok:!1,error:a.message||"list snapshots failed"})}}),h.post("/file-snapshot/delete",p,async(n,t)=>{const{snapshotKey:r}=n.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"snapshotKey is required"});return}try{const s=await g(String(r).trim());if(!s.ok){i.warn(`[FileSnapshot] \u5220\u9664\u5FEB\u7167\u5931\u8D25: ${s.error}`),t.status(s.error?.includes("not found")?404:500).json({ok:!1,error:s.error});return}t.json({ok:!0,snapshotKey:String(r).trim(),message:`\u5DF2\u5220\u9664\u5FEB\u7167 ${r}`})}catch(s){const a=s;i.error(`[FileSnapshot] \u5220\u9664\u5FEB\u7167\u5F02\u5E38: ${a.message}`),t.status(500).json({ok:!1,error:a.message||"delete snapshot failed"})}}),h.post("/file-snapshot/info",p,async(n,t)=>{t.json({ok:!0,snapshotRoot:S()})});export{h as fileSnapshotRouter};
1
+ import{Router as c}from"express";import{validateToken as p}from"../middleware/auth.js";import{createSnapshot as u,restoreSnapshot as f,listSnapshots as m,deleteSnapshot as d,getSnapshotRootPath as g}from"../services/fileSnapshotStore.js";import{createLogger as S}from"../utils/logger.js";const h=c(),i=S("file-snapshot");h.post("/file-snapshot/create",p,async(n,t)=>{const{workspacePath:r,snapshotId:s,snapshotName:a,triggerType:o}=n.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const e=await u({workspacePath:String(r).trim(),snapshotId:s?String(s).trim():void 0,snapshotName:a?String(a).trim():void 0,triggerType:o?String(o).trim():void 0});if(!e.ok){i.warn(`[FileSnapshot] \u521B\u5EFA\u5FEB\u7167\u5931\u8D25: ${e.error}`),t.status(500).json({ok:!1,error:e.error});return}t.json({ok:!0,snapshotId:e.snapshotId,snapshotKey:e.snapshotKey,fileCount:e.fileCount,totalBytes:e.totalBytes,workspaceHash:e.workspaceHash,workspacePath:String(r).trim()})}catch(e){const l=e;i.error(`[FileSnapshot] \u521B\u5EFA\u5FEB\u7167\u5F02\u5E38: ${l.message}`),t.status(500).json({ok:!1,error:l.message||"create snapshot failed"})}}),h.post("/file-snapshot/restore",p,async(n,t)=>{const{workspacePath:r,snapshotKey:s,preRestoreBackup:a}=n.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!s||!String(s).trim()){t.status(400).json({error:"snapshotKey is required"});return}try{const o=await f({workspacePath:String(r).trim(),snapshotKey:String(s).trim(),preRestoreBackup:a!==!1});if(!o.ok){i.warn(`[FileSnapshot] \u8FD8\u539F\u5FEB\u7167\u5931\u8D25: ${o.error}`),t.status(500).json({ok:!1,error:o.error});return}t.json({ok:!0,filesRestored:o.filesRestored,filesDeleted:o.filesDeleted,filesSkipped:o.filesSkipped,preRestoreSnapshotKey:o.preRestoreSnapshotKey||null,workspacePath:String(r).trim()})}catch(o){const e=o;i.error(`[FileSnapshot] \u8FD8\u539F\u5FEB\u7167\u5F02\u5E38: ${e.message}`),t.status(500).json({ok:!1,error:e.message||"restore snapshot failed"})}}),h.post("/file-snapshot/list",p,async(n,t)=>{const{workspacePath:r}=n.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await m(String(r).trim());t.json({ok:!0,snapshots:s,count:s.length,workspacePath:String(r).trim()})}catch(s){const a=s;i.error(`[FileSnapshot] \u5217\u51FA\u5FEB\u7167\u5F02\u5E38: ${a.message}`),t.status(500).json({ok:!1,error:a.message||"list snapshots failed"})}}),h.post("/file-snapshot/delete",p,async(n,t)=>{const{snapshotKey:r}=n.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"snapshotKey is required"});return}try{const s=await d(String(r).trim());if(!s.ok){i.warn(`[FileSnapshot] \u5220\u9664\u5FEB\u7167\u5931\u8D25: ${s.error}`),t.status(s.error?.includes("not found")?404:500).json({ok:!1,error:s.error});return}t.json({ok:!0,snapshotKey:String(r).trim(),message:`\u5DF2\u5220\u9664\u5FEB\u7167 ${r}`})}catch(s){const a=s;i.error(`[FileSnapshot] \u5220\u9664\u5FEB\u7167\u5F02\u5E38: ${a.message}`),t.status(500).json({ok:!1,error:a.message||"delete snapshot failed"})}}),h.post("/file-snapshot/info",p,async(n,t)=>{t.json({ok:!0,snapshotRoot:g()})});export{h as fileSnapshotRouter};
2
2