aws-runtime-bridge 1.9.101 → 1.9.103

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 (56) hide show
  1. package/README.md +2 -0
  2. package/dist/adapter/AcodeSdkAdapter.d.ts +2 -0
  3. package/dist/adapter/AcodeSdkAdapter.js +3 -3
  4. package/dist/index.js +1 -1
  5. package/dist/remote-desktop/computer-control.js +1 -1
  6. package/dist/remote-desktop/computer-use-backend.d.ts +111 -0
  7. package/dist/remote-desktop/computer-use-backend.js +1 -0
  8. package/dist/remote-desktop/frame-encode.d.ts +3 -0
  9. package/dist/remote-desktop/frame-encode.js +2 -0
  10. package/dist/remote-desktop/index.js +2 -2
  11. package/dist/remote-desktop/screen-capture.d.ts +80 -5
  12. package/dist/remote-desktop/screen-capture.js +1 -1
  13. package/dist/remote-desktop/session-manager.d.ts +3 -2
  14. package/dist/remote-desktop/session-manager.js +1 -1
  15. package/dist/remote-desktop/system-backend.js +1 -1
  16. package/dist/remote-desktop/types.d.ts +104 -0
  17. package/dist/routes/computer.d.ts +1 -0
  18. package/dist/routes/computer.js +2 -0
  19. package/dist/routes/file-change-diff.d.ts +27 -0
  20. package/dist/routes/file-change-diff.js +5 -0
  21. package/dist/routes/instance.js +2 -2
  22. package/dist/routes/pty.d.ts +2 -2
  23. package/dist/routes/pty.js +3 -3
  24. package/dist/routes/task-context.js +1 -1
  25. package/dist/routes/terminal.js +28 -28
  26. package/dist/services/auto-register.d.ts +1 -1
  27. package/dist/services/auto-register.js +1 -1
  28. package/dist/services/instance-features.d.ts +10 -0
  29. package/dist/services/instance-features.js +2 -0
  30. package/dist/services/sdk-tool-file-change-recorder.d.ts +12 -0
  31. package/dist/services/sdk-tool-file-change-recorder.js +2 -0
  32. package/dist/services/tunnel-client.js +1 -1
  33. package/dist/services/workspace-files.js +1 -1
  34. package/dist/services/workspaceTaskChangeWatcher.d.ts +9 -1
  35. package/dist/services/workspaceTaskChangeWatcher.js +3 -1
  36. package/package/acode/dist/built-in-file-tools.d.ts +4 -0
  37. package/package/acode/dist/built-in-file-tools.js +2 -2
  38. package/package/acode/dist/runtime.d.ts +13 -0
  39. package/package/acode/dist/runtime.js +18 -18
  40. package/package/aws-client-agent-mcp/README.md +2 -2
  41. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +14 -4
  42. package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
  43. package/package/aws-client-agent-mcp/dist/agent-client.test.js +36 -36
  44. package/package/aws-client-agent-mcp/dist/config.js +1 -1
  45. package/package/aws-client-agent-mcp/dist/config.test.js +2 -2
  46. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +2 -0
  47. package/package/aws-client-agent-mcp/dist/mcp-server.js +21 -21
  48. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +35 -36
  49. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +6 -0
  50. package/package/aws-client-agent-mcp/dist/mcp-tools.js +25 -5
  51. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +17 -1
  52. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +1 -1
  53. package/package/aws-client-agent-mcp/dist/types.d.ts +14 -12
  54. package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -1
  55. package/package.json +3 -2
  56. package/scripts/ensure-playwright-browser.mjs +118 -0
@@ -1,2 +1,2 @@
1
- let o=null;async function S(){return o||(o=await import("node-screenshots")),o}let u=null;async function v(){return u||(u=await import("sharp")),u}const w={sd:{scale:.5,jpegQuality:55},hd:{scale:1,jpegQuality:72},original:{scale:1,jpegQuality:95}};class M{constructor(){this.screens=[],this.interval=null,this.fps=20,this.quality="hd",this.activeScreenId=null,this.onFrame=null,this.capturing=!1}async listScreens(){try{const t=await S(),{Monitor:i}=t,a=i.all();return this.screens=a.map((e,s)=>({id:String(e.id()),name:e.name()||`Screen ${s+1}`,x:e.x(),y:e.y(),width:e.width(),height:e.height(),scaleFactor:e.scaleFactor()})),this.screens}catch(t){return console.error("[remote-desktop] listScreens failed:",t),this.screens=[{id:"0",name:"Screen 1",x:0,y:0,width:1920,height:1080,scaleFactor:1}],this.screens}}async captureVirtualDesktop(){const t=await S(),{Monitor:i}=t,a=i.all();if(a.length===0)throw new Error("No screens available");let e=a[0];if(this.activeScreenId){const c=a.find(n=>String(n.id())===this.activeScreenId);c&&(e=c)}const s=e.captureImageSync(),r=s.width,l=s.height;let h,p=r,g=l;try{const c=await v(),n=w[this.quality],f=Math.max(1,Math.round(r*n.scale)),y=Math.max(1,Math.round(l*n.scale)),m=s.toRawSync();h=await c(m,{raw:{width:r,height:l,channels:4}}).resize(f,y,{fit:"fill"}).jpeg({quality:n.jpegQuality}).toBuffer(),p=f,g=y}catch{h=s.toJpegSync()}return{data:h,width:p,height:g}}setActiveScreen(t){this.activeScreenId=t}startLoop(t){this.stopLoop(),this.onFrame=t,this.scheduleLoop()}scheduleLoop(){if(!this.onFrame)return;const t=Math.max(16,Math.round(1e3/this.fps));this.interval=setInterval(async()=>{if(!this.capturing){this.capturing=!0;try{const i=await this.captureVirtualDesktop();this.onFrame?.(i.data)}catch{}finally{this.capturing=!1}}},t)}stopLoop(){this.interval&&(clearInterval(this.interval),this.interval=null)}setQuality(t,i){t&&Object.prototype.hasOwnProperty.call(w,t)&&(this.quality=t),i!==void 0&&(this.fps=Math.max(1,Math.min(60,i))),this.interval&&(clearInterval(this.interval),this.interval=null,this.scheduleLoop())}getQuality(){return this.quality}getScreens(){return this.screens}}export{M as ScreenCapture};
1
+ let L=null;async function x(){return L||(L=await import("node-screenshots")),L}let T=null,I="pending";async function k(){if(!T)try{const u=await import("sharp");T=u&&u.default||u,I="ok",console.log("[remote-desktop] sharp \u52A0\u8F7D\u6210\u529F\uFF0C\u542F\u7528 tile \u589E\u91CF\u7F16\u7801 + JPEG \u538B\u7F29")}catch(u){return I="failed",console.warn("[remote-desktop] sharp \u52A0\u8F7D\u5931\u8D25\uFF0C\u964D\u7EA7 toJpegSync \u6574\u5E27\u7F16\u7801\uFF08\u65E0 tile \u589E\u91CF\uFF0C\u4E8B\u4EF6\u5FAA\u73AF\u963B\u585E\uFF0C\u5E27\u7387\u964D\u4F4E\uFF09:",u instanceof Error?u.message:u),T=null,null}return T}function j(){return I}const A={sd:{scale:.5,jpegQuality:55},hd:{scale:1,jpegQuality:72},original:{scale:1,jpegQuality:95}},_=1600,w=160,C=4,O=24,N=1,P=.5,U=4;function b(u,t,e,a,s,n){if(!u||t<=0||s<=0||n<=0)return null;const i=Math.max(0,e),l=Math.max(0,a),m=Math.min(t,e+s),c=Math.min(Math.floor(u.length/4/t),a+n),o=m-i,p=c-l;if(o<=0||p<=0)return null;const f=t*4,r=Buffer.allocUnsafe(o*p*4);for(let h=0;h<p;h++){const d=(l+h)*f+i*4;u.copy(r,h*o*4,d,d+o*4)}return r}function v(u,t,e,a,s){if(!t||t.length!==u.length||e<=0||a<=0)return Number.MAX_SAFE_INTEGER;const n=Math.max(0,s.left),i=Math.max(0,s.top),l=Math.min(e,s.left+s.w),m=Math.min(a,s.top+s.h),c=e*4;let o=0;for(let p=i;p<m;p+=C){const f=p*c;for(let r=n;r<l;r+=C){const h=f+r*4;Math.abs(u[h]-t[h])+Math.abs(u[h+1]-t[h+1])+Math.abs(u[h+2]-t[h+2])>O&&o++}}return o}class M{constructor(){this.screens=[],this.captureTimer=null,this.fps=20,this.quality="hd",this.activeScreenId=null,this.onFrame=null,this.captureInFlight=!1,this.encodePending=null,this.encoding=!1,this.consecutiveStillFrames=0,this.inputBoostFrames=0,this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0,this.pendingReference=null,this.frameStats={full:0,partial:0,null:0,sharpFail:0},this.cachedMonitors=null,this.lastFullFrameAt=0,this.consecutiveNullCaptures=0,this.backpressureThrottleUntil=0}resetFrameStats(){this.frameStats={full:0,partial:0,null:0,sharpFail:0}}async listScreens(){try{const t=await x(),{Monitor:e}=t,a=e.all();return this.cachedMonitors=a,this.screens=a.map((s,n)=>({id:String(s.id()),name:s.name()||`Screen ${n+1}`,x:s.x(),y:s.y(),width:s.width(),height:s.height(),scaleFactor:s.scaleFactor()})),this.screens}catch(t){return console.error("[remote-desktop] listScreens failed:",t),this.screens=[{id:"0",name:"Screen 1",x:0,y:0,width:1920,height:1080,scaleFactor:1}],this.screens}}async getActiveMonitor(){const t=await x();let e=this.cachedMonitors;if((!e||e.length===0)&&(e=t.Monitor.all(),this.cachedMonitors=e),e.length===0)throw new Error("No screens available");if(this.activeScreenId){const a=e.find(s=>String(s.id())===this.activeScreenId);if(a)return a}return e[0]}async acquireShot(){const t=await this.getActiveMonitor();let e;typeof t.captureImage=="function"?e=await t.captureImage():e=t.captureImageSync();let a;return typeof e.toRaw=="function"?a=await e.toRaw():a=e.toRawSync(),{image:e,raw:a,srcWidth:e.width,srcHeight:e.height}}async encodeShot(t,e){const{image:a,raw:s,srcWidth:n,srcHeight:i}=t;let l=null;try{l=await k()}catch{l=null}const m=A[this.quality];let c=Math.max(1,Math.round(n*m.scale)),o=Math.max(1,Math.round(i*m.scale));const p=Math.max(c,o);if(p>_){const r=_/p;c=Math.max(1,Math.round(c*r)),o=Math.max(1,Math.round(o*r))}const f=!!this.lastRaw&&this.lastRawWidth===n&&this.lastRawHeight===i;if(!l){if(this.frameStats.sharpFail++,!e&&f&&v(s,this.lastRaw,n,i,{left:0,top:0,w:n,h:i})<N)return this.frameStats.null++,null;let r;try{r=a.toJpegSync()}catch{return null}return this.pendingReference={raw:s,width:n,height:i},this.frameStats.full++,{kind:"full",data:r,width:n,height:i}}try{if(!e&&f){const d=this.detectChangedTiles(s,n,i,c,o);if(d.changedTileCount>0){const F=this.totalTileCount(c,o);if(d.changedTileCount<Math.ceil(F*P)){const y=await this.mapLimit(d.regions,U,async g=>{const E=b(s,n,g.sx,g.sy,g.sw,g.sh);if(!E)return null;try{const D=await l(E,{raw:{width:g.sw,height:g.sh,channels:4}}).resize(g.w,g.h,{fit:"fill"}).jpeg({quality:m.jpegQuality}).toBuffer();return{x:g.x,y:g.y,w:g.w,h:g.h,data:D}}catch{return null}}),S=y.filter(g=>g!==null),R=y.length!==S.length;return S.length===0?(this.frameStats.null++,null):(R||(this.pendingReference={raw:s,width:n,height:i}),this.frameStats.partial++,{kind:"partial",width:c,height:o,tiles:S})}}else return this.frameStats.null++,null}let r,h=!1;try{r=await l(s,{raw:{width:n,height:i,channels:4}}).resize(c,o,{fit:"fill"}).jpeg({quality:m.jpegQuality}).toBuffer()}catch(d){const F=s.slice(0,8).toString("hex");console.warn("[remote-desktop] sharp \u6574\u5E27\u7F16\u7801\u5931\u8D25\uFF0Cfallback toJpegSync:",`raw.length=${s.length} expected=${n*i*4} head=${F}`,d);try{r=a.toJpegSync(),h=!0}catch{return null}}return this.pendingReference={raw:s,width:n,height:i},h?((c!==n||o!==i)&&(this.pendingReference={raw:null,width:0,height:0}),this.frameStats.full++,{kind:"full",data:r,width:n,height:i}):(this.frameStats.full++,{kind:"full",data:r,width:c,height:o})}catch{return this.frameStats.null++,null}}async captureVirtualDesktop(t=!1){const e=await this.acquireShot(),a=await this.encodeShot(e,t);return this.discardReferenceFrame(),a}totalTileCount(t,e){const a=Math.max(1,Math.ceil(t/w)),s=Math.max(1,Math.ceil(e/w));return a*s}async mapLimit(t,e,a){const s=new Array(t.length);let n=0;const i=Math.max(1,Math.min(e,t.length));return await Promise.all(Array.from({length:i},async()=>{for(;;){const l=n++;if(l>=t.length)return;s[l]=await a(t[l])}})),s}outputRectToSource(t,e,a,s,n,i,l){const m=Math.max(0,Math.min(i-1,Math.round(t/n))),c=Math.max(0,Math.min(l-1,Math.round(e/n))),o=Math.max(1,Math.min(i-m,Math.round(a/n))),p=Math.max(1,Math.min(l-c,Math.round(s/n)));return{left:m,top:c,w:o,h:p}}detectChangedTiles(t,e,a,s,n){const i=s/e,l=Math.max(1,Math.ceil(s/w)),m=Math.max(1,Math.ceil(n/w)),c=new Array(m*l).fill(!1);let o=0;for(let f=0;f<m;f++)for(let r=0;r<l;r++){const h=r*w,d=f*w,F=Math.min(w,s-h),y=Math.min(w,n-d),S=this.outputRectToSource(h,d,F,y,i,e,a);v(t,this.lastRaw,e,a,S)>=N&&(c[f*l+r]=!0,o++)}const p=[];for(let f=0;f<m;f++){let r=0;for(;r<l;){if(!c[f*l+r]){r++;continue}let h=r;for(;h+1<l&&c[f*l+h+1];)h++;const d=r*w,F=f*w,y=Math.min(w*(h-r+1),s-d),S=Math.min(w,n-F),R=this.outputRectToSource(d,F,y,S,i,e,a);p.push({x:d,y:F,w:y,h:S,sx:R.left,sy:R.top,sw:R.w,sh:R.h}),r=h+1}}return{changedTileCount:o,regions:p}}commitReferenceFrame(){this.pendingReference&&(this.pendingReference.raw===null?(this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0):(this.lastRaw=this.pendingReference.raw,this.lastRawWidth=this.pendingReference.width,this.lastRawHeight=this.pendingReference.height),this.pendingReference=null)}discardReferenceFrame(){this.pendingReference=null}setActiveScreen(t){this.activeScreenId=t}startLoop(t){this.stopLoop(),this.onFrame=t,this.scheduleLoop()}scheduleLoop(t){if(!this.onFrame)return;this.captureTimer&&(clearTimeout(this.captureTimer),this.captureTimer=null);const e=t??this.currentIntervalMs();this.captureTimer=setTimeout(()=>{this.captureTimer=null,this.captureOnce()},Math.max(0,e))}async captureOnce(){if(this.captureInFlight){this.scheduleLoop();return}this.inputBoostFrames>0&&this.inputBoostFrames--,this.captureInFlight=!0;const t=Date.now();try{const e=await this.acquireShot(),a=Date.now()-t;this.scheduleLoop(Math.max(0,this.currentIntervalMs()-a)),this.encodePending=e,this.drainEncode()}catch{this.scheduleLoop()}finally{this.captureInFlight=!1}}async drainEncode(){if(!this.encoding){this.encoding=!0;try{for(;this.encodePending;){const t=this.encodePending;this.encodePending=null;const e=this.consecutiveStillFrames>=M.STILL_FRAME_COUNT,a=Date.now()-this.lastFullFrameAt>=M.PERIODIC_FULL_FRAME_INTERVAL_MS;let s=null;try{s=await this.encodeShot(t,e||a)}catch{}if(s){this.consecutiveStillFrames=0,this.consecutiveNullCaptures=0;const n=this.onFrame;(n?n(s):!1)!==!1?(s.kind==="full"&&(this.lastFullFrameAt=Date.now()),this.commitReferenceFrame()):this.discardReferenceFrame()}else if(this.consecutiveStillFrames++,this.consecutiveNullCaptures++,this.consecutiveNullCaptures>=8&&Date.now()-this.lastFullFrameAt>=3e3){const n=await this.captureSafeFullFrame();if(n){this.consecutiveStillFrames=0,this.consecutiveNullCaptures=0;const i=this.onFrame,l=i?i(n):!1;l!==!1?(this.lastFullFrameAt=Date.now(),this.commitReferenceFrame()):this.discardReferenceFrame(),console.warn(`[remote-desktop] \u5B89\u5168\u7F51\u89E6\u53D1\uFF1A\u8FDE\u7EED null \u5DF2\u8FBE\u9608\u503C\uFF0C\u5DF2\u7528 toJpegSync \u5F3A\u5236\u53D1 full \u5E27\uFF08sent=${l!==!1}\uFF09`)}}}}finally{this.encoding=!1}}}async captureSafeFullFrame(){try{const t=await this.getActiveMonitor();let e;typeof t.captureImage=="function"?e=await t.captureImage():e=t.captureImageSync();const a=e.toJpegSync();return this.pendingReference={raw:null,width:0,height:0},{kind:"full",data:a,width:e.width,height:e.height}}catch(t){return console.warn("[remote-desktop] \u5B89\u5168\u7F51 toJpegSync \u4E5F\u5931\u8D25:",t),null}}currentIntervalMs(){if(I==="failed")return this.inputBoostFrames>0?250:this.consecutiveStillFrames>=M.STILL_FRAME_COUNT?M.IDLE_INTERVAL_MS:500;if(this.inputBoostFrames>0)return M.ACTIVE_INTERVAL_MS;if(this.consecutiveStillFrames>=M.STILL_FRAME_COUNT)return M.IDLE_INTERVAL_MS;const t=Math.max(16,Math.round(1e3/this.fps));return Date.now()<this.backpressureThrottleUntil?Math.max(t,100):t}requestFrame(){this.inputBoostFrames=Math.max(this.inputBoostFrames,3),this.captureInFlight||this.scheduleLoop()}notifyFrameDropped(){this.backpressureThrottleUntil=Date.now()+1e3}requestFullFrame(){this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0,this.scheduleLoop()}stopLoop(){this.captureTimer&&(clearTimeout(this.captureTimer),this.captureTimer=null),this.onFrame=null,this.consecutiveStillFrames=0,this.inputBoostFrames=0,this.encodePending=null,this.captureInFlight=!1,this.encoding=!1}setQuality(t,e){t&&Object.prototype.hasOwnProperty.call(A,t)&&(this.quality=t,this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0),e!==void 0&&(this.fps=Math.max(1,Math.min(60,e))),this.onFrame&&this.scheduleLoop()}getQuality(){return this.quality}getScreens(){return this.screens}}M.ACTIVE_INTERVAL_MS=33,M.STILL_FRAME_COUNT=3,M.IDLE_INTERVAL_MS=1e3,M.PERIODIC_FULL_FRAME_INTERVAL_MS=2e3;export{M as ScreenCapture,b as extractRawRegion,j as getSharpLoadStatus};
2
2
 
@@ -3,7 +3,7 @@ import { ScreenCapture } from "./screen-capture.js";
3
3
  import { InputDispatcher } from "./input-dispatcher.js";
4
4
  import { PermissionManager } from "./permission-manager.js";
5
5
  import { ComputerControl } from "./computer-control.js";
6
- import type { InputMessage, RemoteDesktopCapability, ScreenInfo, StartResult } from "./types.js";
6
+ import type { InputMessage, RemoteDesktopCapability, ScreenInfo, StartResult, CaptureFrame } from "./types.js";
7
7
  export declare class RemoteDesktopSession {
8
8
 
9
9
  static readonly FOCUS_POLL_INTERVAL_MS = 1000;
@@ -16,6 +16,7 @@ export declare class RemoteDesktopSession {
16
16
  state: "active" | "closed" | "error";
17
17
  ws: WebSocket | null;
18
18
  private inputBackend;
19
+
19
20
  private onFrame;
20
21
  private focusPollTimer;
21
22
  private lastFocusState;
@@ -40,7 +41,7 @@ export declare class RemoteDesktopSession {
40
41
 
41
42
  private sendFocusState;
42
43
  attachWebSocket(ws: WebSocket): void;
43
- startFrameLoop(onFrame: (frame: Buffer) => void): void;
44
+ startFrameLoop(onFrame: (frame: CaptureFrame) => boolean | void): void;
44
45
  stopFrameLoop(): void;
45
46
 
46
47
  handleInput(message: InputMessage): Promise<void>;
@@ -1,2 +1,2 @@
1
- import{randomUUID as a}from"node:crypto";import{ScreenCapture as c}from"./screen-capture.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ComputerControl as u}from"./computer-control.js";import{SystemBackend as p}from"./system-backend.js";class o{constructor(s,t,e){this.inputDispatcher=null,this.state="active",this.ws=null,this.onFrame=null,this.focusPollTimer=null,this.lastFocusState=null,this.inputChain=Promise.resolve(),this.id=s,this.agentId=t,this.permissions=e,this.screenCapture=new c,this.inputBackend=new p,this.computerControl=new u(this.screenCapture,this.inputBackend)}async init(){const s=await this.screenCapture.listScreens(),t=s[0];return t&&(this.inputDispatcher=new h(this.inputBackend,this.permissions,t),this.inputBackend.setScreenSize(t.width,t.height),this.inputBackend.setActiveScreen(t)),{screens:s}}async switchScreen(s){this.screenCapture.setActiveScreen(s);const t=this.screenCapture.getScreens(),e=s?t.find(i=>i.id===s):t[0];e&&this.inputDispatcher&&(this.inputDispatcher.setScreen(e),this.inputBackend.setScreenSize(e.width,e.height),this.inputBackend.setActiveScreen(e))}startInputBackend(){this.inputBackend.startPersistentProcess()}async enableControl(){this.inputBackend.startPersistentProcess(),this.startFocusPolling(),await this.inputBackend.centerMouse()}stopInputBackend(){this.stopFocusPolling(),this.inputBackend.stopPersistentProcess()}startFocusPolling(){this.stopFocusPolling(),this.focusPollTimer=setInterval(()=>{const s=this.inputBackend.isInputFocused();s!==this.lastFocusState&&(this.lastFocusState=s,this.sendFocusState(s))},o.FOCUS_POLL_INTERVAL_MS)}stopFocusPolling(){this.focusPollTimer&&(clearInterval(this.focusPollTimer),this.focusPollTimer=null),this.lastFocusState=null}sendFocusState(s){!this.ws||this.ws.readyState!==this.ws.OPEN||this.ws.send(JSON.stringify({type:"remote-desktop:focus-state",sessionId:this.id,data:{hasInputFocus:s}}))}attachWebSocket(s){this.ws=s}startFrameLoop(s){this.onFrame=s,this.screenCapture.startLoop(s)}stopFrameLoop(){this.screenCapture.stopLoop(),this.onFrame=null}handleInput(s){if(!this.inputDispatcher)return Promise.resolve();const t=this.inputChain.then(()=>this.inputDispatcher.handle(s));return this.inputChain=t.catch(()=>{}),t}close(){this.state="closed",this.stopFocusPolling(),this.stopFrameLoop(),this.inputBackend.stopPersistentProcess(),this.ws=null}}o.FOCUS_POLL_INTERVAL_MS=1e3;class P{constructor(s){this.sessions=new Map,this.agentSessions=new Map,this.permissionManager=s}async createSession(s,t){if(!this.permissionManager.validateToken(t))throw new Error("Invalid token");const e=this.agentSessions.get(s);e&&this.closeSession(e);const i=this.permissionManager.resolvePermissions(t),n=a(),r=new o(n,s,i);return await r.init(),this.sessions.set(n,r),this.agentSessions.set(s,n),{sessionId:n,screens:r.screenCapture.getScreens(),permissions:Array.from(i)}}getSession(s){return this.sessions.get(s)}async handleInput(s,t){const e=this.sessions.get(s);!e||e.state!=="active"||await e.handleInput(t)}closeSession(s){const t=this.sessions.get(s);if(t){t.close(),this.sessions.delete(s);for(const[e,i]of this.agentSessions)if(i===s){this.agentSessions.delete(e);break}}}closeAll(){for(const s of this.sessions.values())s.close();this.sessions.clear(),this.agentSessions.clear()}}export{o as RemoteDesktopSession,P as SessionManager};
1
+ import{randomUUID as a}from"node:crypto";import{ScreenCapture as c}from"./screen-capture.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ComputerControl as u}from"./computer-control.js";import{SystemBackend as p}from"./system-backend.js";class o{constructor(s,t,e){this.inputDispatcher=null,this.state="active",this.ws=null,this.onFrame=null,this.focusPollTimer=null,this.lastFocusState=null,this.inputChain=Promise.resolve(),this.id=s,this.agentId=t,this.permissions=e,this.screenCapture=new c,this.inputBackend=new p,this.computerControl=new u(this.screenCapture,this.inputBackend)}async init(){const s=await this.screenCapture.listScreens(),t=s[0];return t&&(this.inputDispatcher=new h(this.inputBackend,this.permissions,t),this.inputBackend.setScreenSize(t.width,t.height),this.inputBackend.setActiveScreen(t)),{screens:s}}async switchScreen(s){this.screenCapture.setActiveScreen(s);const t=this.screenCapture.getScreens(),e=s?t.find(i=>i.id===s):t[0];e&&this.inputDispatcher&&(this.inputDispatcher.setScreen(e),this.inputBackend.setScreenSize(e.width,e.height),this.inputBackend.setActiveScreen(e))}startInputBackend(){this.inputBackend.startPersistentProcess()}async enableControl(){this.inputBackend.startPersistentProcess(),this.startFocusPolling(),await this.inputBackend.centerMouse()}stopInputBackend(){this.stopFocusPolling(),this.inputBackend.stopPersistentProcess()}startFocusPolling(){this.stopFocusPolling(),this.focusPollTimer=setInterval(()=>{const s=this.inputBackend.isInputFocused();s!==this.lastFocusState&&(this.lastFocusState=s,this.sendFocusState(s))},o.FOCUS_POLL_INTERVAL_MS)}stopFocusPolling(){this.focusPollTimer&&(clearInterval(this.focusPollTimer),this.focusPollTimer=null),this.lastFocusState=null}sendFocusState(s){!this.ws||this.ws.readyState!==this.ws.OPEN||this.ws.send(JSON.stringify({type:"remote-desktop:focus-state",sessionId:this.id,data:{hasInputFocus:s}}))}attachWebSocket(s){this.ws=s}startFrameLoop(s){this.onFrame=s,this.screenCapture.startLoop(s)}stopFrameLoop(){this.screenCapture.stopLoop(),this.onFrame=null}handleInput(s){if(!this.inputDispatcher)return Promise.resolve();this.screenCapture.requestFrame();const t=this.inputChain.then(async()=>{await this.inputDispatcher.handle(s),this.screenCapture.requestFrame()});return this.inputChain=t.catch(()=>{}),t}close(){this.state="closed",this.stopFocusPolling(),this.stopFrameLoop(),this.inputBackend.stopPersistentProcess(),this.ws=null}}o.FOCUS_POLL_INTERVAL_MS=1e3;class P{constructor(s){this.sessions=new Map,this.agentSessions=new Map,this.permissionManager=s}async createSession(s,t){if(!this.permissionManager.validateToken(t))throw new Error("Invalid token");const e=this.agentSessions.get(s);e&&this.closeSession(e);const i=this.permissionManager.resolvePermissions(t),n=a(),r=new o(n,s,i);return await r.init(),this.sessions.set(n,r),this.agentSessions.set(s,n),{sessionId:n,screens:r.screenCapture.getScreens(),permissions:Array.from(i)}}getSession(s){return this.sessions.get(s)}async handleInput(s,t){const e=this.sessions.get(s);!e||e.state!=="active"||await e.handleInput(t)}closeSession(s){const t=this.sessions.get(s);if(t){t.close(),this.sessions.delete(s);for(const[e,i]of this.agentSessions)if(i===s){this.agentSessions.delete(e);break}}}closeAll(){for(const s of this.sessions.values())s.close();this.sessions.clear(),this.agentSessions.clear()}}export{o as RemoteDesktopSession,P as SessionManager};
2
2
 
@@ -1,5 +1,5 @@
1
1
  import{spawn as y,exec as P}from"node:child_process";import{promisify as E}from"node:util";import{mapKey as a}from"./input-backends/key-map.js";import{toOsMouseCoords as p}from"./coords.js";import{initWin32Api as S,MOUSEEVENTF_LEFTDOWN as h,MOUSEEVENTF_LEFTUP as u,MOUSEEVENTF_RIGHTDOWN as m,MOUSEEVENTF_RIGHTUP as d,MOUSEEVENTF_WHEEL as k,MOUSEEVENTF_HWHEEL as v,KEYEVENTF_KEYUP as f}from"./win32-api.js";const r=E(P);function g(){return process.platform==="win32"?"windows":process.platform==="darwin"?"macos":"linux"}class l{constructor(s){this.screenSize={width:1920,height:1080},this.activeScreen=null,this.win32Api=null,this.psProcess=null,this.psReady=!1,this.psReadyPromise=null,this.psBuffer="",this.cliclickProcess=null,this.xdotoolProcess=null,this.platform=s??g()}startPersistentProcess(){if(this.platform==="windows"){if(this.win32Api=S(),this.win32Api){console.log("[remote-desktop] Win32 FFI API ready (koffi)");return}console.warn("[remote-desktop] Win32 FFI unavailable, falling back to PowerShell"),this.startPowerShellProcess()}}startPowerShellProcess(){this.psProcess||this.psReadyPromise||(this.psReadyPromise=new Promise(s=>{const e=y("powershell.exe",["-NoProfile","-NoLogo","-Command","-"],{stdio:["pipe","pipe","pipe"],windowsHide:!0});e.stdin?.write(`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; echo "__READY__"
2
2
  `),e.stdout?.on("data",t=>{this.psBuffer+=t.toString(),this.psBuffer.includes("__READY__")&&(this.psBuffer="",this.psProcess=e,this.psReady=!0,console.log("[remote-desktop] PowerShell persistent process ready"),s(!0))}),e.stderr?.on("data",t=>{console.warn("[remote-desktop] PowerShell stderr:",t.toString())}),e.on("error",t=>{console.error("[remote-desktop] PowerShell process error:",t),this.psProcess=null,this.psReady=!1,this.psReadyPromise=null,s(!1)}),e.on("exit",()=>{this.psProcess=null,this.psReady=!1,this.psReadyPromise=null,s(!1)})}))}async ensurePowerShell(){return this.psProcess?.stdin?.writable&&this.psReady?!0:(this.psReadyPromise||this.startPowerShellProcess(),await this.psReadyPromise??!1)}stopPersistentProcess(){this.psProcess&&(this.psProcess.stdin?.end(),this.psProcess.kill(),this.psProcess=null,this.psReady=!1,this.psReadyPromise=null),this.cliclickProcess&&(this.cliclickProcess.kill(),this.cliclickProcess=null),this.xdotoolProcess&&(this.xdotoolProcess.kill(),this.xdotoolProcess=null)}psCmd(s){if(this.psProcess?.stdin?.writable&&this.psReady){this.psProcess.stdin.write(s+`
3
3
  `);return}const e=`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; ${s}`,t=Buffer.from(e,"utf16le").toString("base64");r(`powershell.exe -NoProfile -EncodedCommand ${t}`).catch(i=>{console.warn("[remote-desktop] fallback PowerShell command failed:",i.message)})}async moveMouse(s,e){const{x:t,y:i}=p(this.activeScreen,s,e,this.platform);if(this.platform==="windows"){if(this.win32Api){this.win32Api.setCursorPos(t,i);return}this.psCmd(`[W]::SetCursorPos(${t}, ${i})`)}else this.platform==="macos"?r(`cliclick m:${t},${i}`).catch(o=>this.warnInputErr("macOS moveMouse",o)):r(`xdotool mousemove ${t} ${i}`).catch(o=>this.warnInputErr("linux moveMouse",o))}async moveMouseRelative(s,e){if(this.platform==="windows"){if(this.win32Api){const o=this.win32Api.getCursorPos();o?this.win32Api.setCursorPos(o.x+Math.round(s),o.y+Math.round(e)):this.warnInputErr("win32 moveMouseRelative",new Error("getCursorPos returned null"));return}this.psCmd(`[W]::mouse_event(1, ${Math.round(s)}, ${Math.round(e)}, 0, 0)`);return}const{x:t,y:i}=await this.getMousePos();await this.moveMouse(t+s,i+e)}async centerMouse(){this.activeScreen&&await this.moveMouse(this.activeScreen.x+this.activeScreen.width/2,this.activeScreen.y+this.activeScreen.height/2)}async click(s,e,t){const{x:i,y:o}=p(this.activeScreen,s,e,this.platform);if(this.platform==="windows"){if(this.win32Api){const w=t==="right"?m:h,x=t==="right"?d:u;this.win32Api.setCursorPos(i,o),this.win32Api.mouseEvent(w,0,0,0),this.win32Api.mouseEvent(x,0,0,0);return}const n=t==="right"?8:2,c=t==="right"?16:4;this.psCmd(`[W]::SetCursorPos(${i},${o}); [W]::mouse_event(${n},0,0,0,0); [W]::mouse_event(${c},0,0,0,0)`)}else this.platform==="macos"?r(`cliclick ${t==="right"?"rc":"c"}:${i},${o}`).catch(c=>this.warnInputErr("macOS click",c)):r(`xdotool mousemove ${i} ${o} && xdotool click ${t==="right"?3:1}`).catch(c=>this.warnInputErr("linux click",c))}async doubleClick(s,e){const{x:t,y:i}=p(this.activeScreen,s,e,this.platform);if(this.platform==="windows"){if(this.win32Api){this.win32Api.setCursorPos(t,i);for(let o=0;o<2;o++)this.win32Api.mouseEvent(h,0,0,0),this.win32Api.mouseEvent(u,0,0,0);return}this.psCmd(`[W]::SetCursorPos(${t},${i}); [W]::mouse_event(2,0,0,0,0); [W]::mouse_event(4,0,0,0,0); [W]::mouse_event(2,0,0,0,0); [W]::mouse_event(4,0,0,0,0)`)}else this.platform==="macos"?r(`cliclick dd:${t},${i}`).catch(o=>this.warnInputErr("macOS doubleClick",o)):r(`xdotool mousemove ${t} ${i} && xdotool click --repeat 2 1`).catch(o=>this.warnInputErr("linux doubleClick",o))}async mouseDown(s){if(this.platform==="windows"){if(this.win32Api){const t=s==="right"?m:h;this.win32Api.mouseEvent(t,0,0,0);return}const e=s==="right"?8:2;this.psCmd(`[W]::mouse_event(${e},0,0,0,0)`)}else this.platform==="macos"?r(`cliclick ${s==="right"?"rd":"dd"}:`).catch(t=>this.warnInputErr("macOS mouseDown",t)):r(`xdotool mousedown ${s==="right"?3:1}`).catch(t=>this.warnInputErr("linux mouseDown",t))}async mouseUp(s){if(this.platform==="windows"){if(this.win32Api){const t=s==="right"?d:u;this.win32Api.mouseEvent(t,0,0,0);return}const e=s==="right"?16:4;this.psCmd(`[W]::mouse_event(${e},0,0,0,0)`)}else this.platform==="macos"?r(`cliclick ${s==="right"?"ru":"du"}:`).catch(t=>this.warnInputErr("macOS mouseUp",t)):r(`xdotool mouseup ${s==="right"?3:1}`).catch(t=>this.warnInputErr("linux mouseUp",t))}async scroll(s,e){const t=Math.max(1,Math.round(e));if(this.platform==="windows"){const i=s==="left"||s==="right",o=i?v:k,n=i?s==="right"?t*120:-t*120:s==="down"?-t*120:t*120;if(this.win32Api){this.win32Api.mouseEvent(o,0,0,n);return}this.psCmd(`[W]::mouse_event(${o},0,0,${n},0)`)}else this.platform==="macos"?r(`python3 -c "from Quartz import CGEventCreateScrollWheelEvent2, kCGScrollEventUnitPixel; from CoreGraphics import CGEventPost, kCGHIDEventTap; e = CGEventCreateScrollWheelEvent2(None, kCGScrollEventUnitPixel, ${s==="left"||s==="right"?1:0}, ${(s==="down"||s==="right"?-1:1)*t*10}); CGEventPost(kCGHIDEventTap, e)" 2>/dev/null || true`).catch(n=>this.warnInputErr("macOS scroll",n)):r(`xdotool click --repeat ${t} ${s==="up"?4:s==="down"?5:s==="left"?6:7}`).catch(o=>this.warnInputErr("linux scroll",o))}async type(s){if(this.platform==="windows"){const t=`[System.Windows.Forms.SendKeys]::SendWait('${s.replace(/[{}()[\]^%~+]/g,"{$&}").replace(/\n/g,"{ENTER}").replace(/'/g,"''")}')`;if(await this.ensurePowerShell()){this.psProcess?.stdin?.write(t+`
4
- `);return}this.psCmd(t)}else this.platform==="macos"?r(`cliclick t:'${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("macOS type",e)):r(`xdotool type --clearmodifiers '${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("linux type",e))}async keyPress(s){const e=s.map(a);if(this.platform==="windows"){if(this.win32Api){for(const t of e){const i=this.getVirtualKeyCode(t);i&&(this.win32Api.keybdEvent(i,0),await new Promise(o=>setTimeout(o,10)),this.win32Api.keybdEvent(i,f))}return}for(const t of e){const i=this.getVirtualKeyCode(t);i&&this.psCmd(`[W]::keybd_event(${i},0,0,0); Start-Sleep -Milliseconds 10; [W]::keybd_event(${i},0,2,0)`)}}else this.platform==="macos"?r(`cliclick kp:${e.join("+")}`).catch(t=>this.warnInputErr("macOS keyPress",t)):r(`xdotool key '${e.join("+")}'`).catch(t=>this.warnInputErr("linux keyPress",t))}async keyDown(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,0);return}this.psCmd(`[W]::keybd_event(${t},0,0,0)`)}else this.platform==="macos"?r(`cliclick kd:${e}`).catch(t=>this.warnInputErr("macOS keyDown",t)):r(`xdotool keydown ${e}`).catch(t=>this.warnInputErr("linux keyDown",t))}async keyUp(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,f);return}this.psCmd(`[W]::keybd_event(${t},0,2,0)`)}else this.platform==="macos"?r(`cliclick ku:${e}`).catch(t=>this.warnInputErr("macOS keyUp",t)):r(`xdotool keyup ${e}`).catch(t=>this.warnInputErr("linux keyUp",t))}getScreenSize(){return this.screenSize}setScreenSize(s,e){this.screenSize={width:s,height:e}}setActiveScreen(s){this.activeScreen=s,this.screenSize={width:s.width,height:s.height}}async getMousePos(){if(this.platform==="windows"){if(this.win32Api)return this.win32Api.getCursorPos()??{x:0,y:0};const s=`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; $p = [System.Windows.Forms.Cursor]::Position; Write-Output "$($p.X),$($p.Y)"`,e=Buffer.from(s,"utf16le").toString("base64"),{stdout:t}=await r(`powershell.exe -NoProfile -EncodedCommand ${e}`),[i,o]=t.trim().split(",").map(Number);return{x:i||0,y:o||0}}else if(this.platform==="macos"){const{stdout:s}=await r(`osascript -e 'tell application "System Events" to get position of the mouse'`),[e,t]=s.trim().split(",").map(Number);return{x:e||0,y:t||0}}else{const{stdout:s}=await r("xdotool getmouselocation --shell"),e=s.match(/X=(\d+)/),t=s.match(/Y=(\d+)/);return{x:e?Number(e[1]):0,y:t?Number(t[1]):0}}}isInputFocused(){return this.platform!=="windows"||!this.win32Api?!1:this.win32Api.isInputFocused()}warnInputErr(s,e){console.warn(`[remote-desktop] ${s} failed:`,e?.message??String(e))}getVirtualKeyCode(s){return{a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,enter:13,return:13,tab:9,escape:27,backspace:8,delete:46,up:38,down:40,left:37,right:39,home:36,end:35,pageup:33,pagedown:34,space:32,shift:16,control:17,alt:18,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123}[s.toLowerCase()]||0}}l.WIN32_INIT_TYPES=`Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class W { [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); [DllImport("user32.dll")] public static extern void mouse_event(uint f, int dx, int dy, int c, IntPtr i); [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo); }'`;export{l as SystemBackend};
4
+ `);return}this.psCmd(t)}else this.platform==="macos"?r(`cliclick t:'${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("macOS type",e)):r(`xdotool type --clearmodifiers '${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("linux type",e))}async keyPress(s){const e=s.map(a);if(this.platform==="windows"){if(this.win32Api){for(const t of e){const i=this.getVirtualKeyCode(t);i&&(this.win32Api.keybdEvent(i,0),await new Promise(o=>setTimeout(o,10)),this.win32Api.keybdEvent(i,f))}return}for(const t of e){const i=this.getVirtualKeyCode(t);i&&this.psCmd(`[W]::keybd_event(${i},0,0,0); Start-Sleep -Milliseconds 10; [W]::keybd_event(${i},0,2,0)`)}}else this.platform==="macos"?r(`cliclick kp:${e.join("+")}`).catch(t=>this.warnInputErr("macOS keyPress",t)):r(`xdotool key '${e.join("+")}'`).catch(t=>this.warnInputErr("linux keyPress",t))}async keyDown(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,0);return}this.psCmd(`[W]::keybd_event(${t},0,0,0)`)}else this.platform==="macos"?r(`cliclick kd:${e}`).catch(t=>this.warnInputErr("macOS keyDown",t)):r(`xdotool keydown ${e}`).catch(t=>this.warnInputErr("linux keyDown",t))}async keyUp(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,f);return}this.psCmd(`[W]::keybd_event(${t},0,2,0)`)}else this.platform==="macos"?r(`cliclick ku:${e}`).catch(t=>this.warnInputErr("macOS keyUp",t)):r(`xdotool keyup ${e}`).catch(t=>this.warnInputErr("linux keyUp",t))}getScreenSize(){return this.screenSize}setScreenSize(s,e){this.screenSize={width:s,height:e}}setActiveScreen(s){this.activeScreen=s,this.screenSize={width:s.width,height:s.height}}async getMousePos(){if(this.platform==="windows"){if(this.win32Api)return this.win32Api.getCursorPos()??{x:0,y:0};const s=`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; $p = [System.Windows.Forms.Cursor]::Position; Write-Output "$($p.X),$($p.Y)"`,e=Buffer.from(s,"utf16le").toString("base64"),{stdout:t}=await r(`powershell.exe -NoProfile -EncodedCommand ${e}`),[i,o]=t.trim().split(",").map(Number);return{x:i||0,y:o||0}}else if(this.platform==="macos"){const{stdout:s}=await r(`osascript -e 'tell application "System Events" to get position of the mouse'`),[e,t]=s.trim().split(",").map(Number);return{x:e||0,y:t||0}}else{const{stdout:s}=await r("xdotool getmouselocation --shell"),e=s.match(/X=(\d+)/),t=s.match(/Y=(\d+)/);return{x:e?Number(e[1]):0,y:t?Number(t[1]):0}}}isInputFocused(){return this.platform!=="windows"||!this.win32Api?!1:this.win32Api.isInputFocused()}warnInputErr(s,e){console.warn(`[remote-desktop] ${s} failed:`,e?.message??String(e))}getVirtualKeyCode(s){return{a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,enter:13,return:13,tab:9,escape:27,capslock:20,backspace:8,delete:46,up:38,down:40,left:37,right:39,home:36,end:35,pageup:33,pagedown:34,space:32,shift:16,control:17,alt:18,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123}[s.toLowerCase()]||0}}l.WIN32_INIT_TYPES=`Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class W { [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); [DllImport("user32.dll")] public static extern void mouse_event(uint f, int dx, int dy, int c, IntPtr i); [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo); }'`;export{l as SystemBackend};
5
5
 
@@ -31,6 +31,31 @@ export interface ScreenshotResult {
31
31
  height: number;
32
32
  }
33
33
 
34
+ export interface TileRect {
35
+
36
+ x: number;
37
+
38
+ y: number;
39
+
40
+ w: number;
41
+
42
+ h: number;
43
+
44
+ data: Buffer;
45
+ }
46
+
47
+ export type CaptureFrame = {
48
+ kind: "full";
49
+ data: Buffer;
50
+ width: number;
51
+ height: number;
52
+ } | {
53
+ kind: "partial";
54
+ width: number;
55
+ height: number;
56
+ tiles: TileRect[];
57
+ };
58
+
34
59
  export type RemoteDesktopQuality = "sd" | "hd" | "original";
35
60
  export interface StartResult {
36
61
  sessionId: string;
@@ -38,6 +63,37 @@ export interface StartResult {
38
63
  permissions: RemoteDesktopCapability[];
39
64
  }
40
65
  export type ConnectionState = 'idle' | 'connecting' | 'authenticating' | 'connected' | 'disconnected' | 'error';
66
+
67
+ export interface DesktopWindowInfo {
68
+ hwnd: string;
69
+ title: string;
70
+ pid?: number;
71
+ processName?: string;
72
+ }
73
+
74
+ export interface OpenWithApp {
75
+
76
+ name: string;
77
+
78
+ command: string;
79
+
80
+ isDefault?: boolean;
81
+ }
82
+
83
+ export interface DesktopFileEntry {
84
+
85
+ name: string;
86
+
87
+ path: string;
88
+
89
+ type: "file" | "directory" | "symlink" | "other";
90
+
91
+ size?: number;
92
+
93
+ mtime?: number;
94
+
95
+ isHidden?: boolean;
96
+ }
41
97
  export interface AppInfo {
42
98
  pid: number;
43
99
  name: string;
@@ -55,6 +111,10 @@ export interface UIElement {
55
111
  role: string;
56
112
  name?: string;
57
113
  value?: string;
114
+
115
+ automationId?: string;
116
+
117
+ fingerprint?: string;
58
118
  bounds?: {
59
119
  x: number;
60
120
  y: number;
@@ -66,3 +126,47 @@ export interface UIElement {
66
126
  isFocused?: boolean;
67
127
  isEditable?: boolean;
68
128
  }
129
+
130
+ export interface FlatNode {
131
+ path: string;
132
+ role: string;
133
+ name?: string;
134
+ value?: string;
135
+ aid?: string;
136
+ x: number;
137
+ y: number;
138
+ w: number;
139
+ h: number;
140
+ actions?: string[];
141
+
142
+ fingerprint?: string;
143
+ }
144
+ export interface StateDiff {
145
+ added: UIElement[];
146
+ removed: UIElement[];
147
+ changed: Array<{
148
+ before: UIElement;
149
+ after: UIElement;
150
+ }>;
151
+
152
+ changedSummary: string;
153
+ }
154
+ export interface ActionVerification {
155
+ stateBefore: string;
156
+ stateAfter: string;
157
+ changed: boolean;
158
+ diff?: StateDiff;
159
+
160
+ toastDetected?: boolean;
161
+
162
+ windowChanged?: boolean;
163
+
164
+ summary: string;
165
+ }
166
+ export interface ActionResult {
167
+ ok: boolean;
168
+ elementId?: string;
169
+ x?: number;
170
+ y?: number;
171
+ verification?: ActionVerification;
172
+ }
@@ -0,0 +1 @@
1
+ export declare const computerRouter: import("express-serve-static-core").Router;
@@ -0,0 +1,2 @@
1
+ import{Router as s}from"express";const e=s();function t(i,o){o.status(503).json({error:"desktop capability is not implemented\uFF08\u684C\u9762\u64CD\u4F5C\u529F\u80FD\u5DF2\u4E0B\u7EBF\uFF0C\u5F85\u540E\u7EED\u7248\u672C\u91CD\u65B0\u5B9E\u73B0\uFF09"})}e.get("/screens",t),e.get("/windows",t),e.get("/window/z-order",t),e.get("/window/foreground",t),e.get("/processes",t),e.get("/file/open-with",t),e.get("/ui-tree",t),e.get("/describe-state",t),e.get("/clipboard",t),e.get("/list-dir",t),e.post("/screenshot",t),e.post("/vision-query",t),e.post("/mouse",t),e.post("/scroll",t),e.post("/keyboard",t),e.post("/drag",t),e.post("/window/activate",t),e.post("/window/move",t),e.post("/window/resize",t),e.post("/window/minimize",t),e.post("/window/maximize",t),e.post("/window/restore",t),e.post("/launch",t),e.post("/open-file",t),e.post("/ui-element/click",t),e.post("/ui-element/set-value",t),e.post("/ui-element/click-by-text",t),e.post("/ui-element/select-menu",t),e.post("/ui-element/fill-form",t),e.post("/clipboard",t);export{e as computerRouter};
2
+
@@ -0,0 +1,27 @@
1
+
2
+ export declare const fileChangeDiffRouter: import("express-serve-static-core").Router;
3
+
4
+ export type ChangeDiffLineType = 'hunk' | 'context' | 'add' | 'remove';
5
+
6
+ export interface ChangeDiffLine {
7
+ type: ChangeDiffLineType;
8
+
9
+ text: string;
10
+
11
+ oldLine: number | null;
12
+
13
+ newLine: number | null;
14
+ }
15
+
16
+ export interface ChangeDiffResponse {
17
+ available: boolean;
18
+ operation: string;
19
+ filePath: string;
20
+ lines: ChangeDiffLine[];
21
+
22
+ error?: string;
23
+ }
24
+
25
+ export declare function parseDiffPatchLines(patch: string): ChangeDiffLine[];
26
+
27
+ export declare function buildChangeDiffResponse(workspacePath: string, cacheKey: string | null | undefined, operation: string, filePath: string): Promise<ChangeDiffResponse>;
@@ -0,0 +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(`
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};
5
+
@@ -1,3 +1,3 @@
1
- import{Router as D}from"express";import I from"axios";import{spawn as N}from"node:child_process";import{createHash as $,timingSafeEqual as F}from"node:crypto";import{getBridgeVersion as y,resolveSchedulerBaseUrlFrom as M}from"../config.js";import{validateToken as S,validateTokenOrPanelSession as j}from"../middleware/auth.js";import{getConfiguredConnectionKeys as _,getRegistrationState as z,requestRuntimeAccessTokenRefreshForServer as H,resolveSchedulerBaseUrlFromToken as X}from"../services/auto-register.js";import{discoverCcSwitchConfiguredItems as V,loadCcSwitchSdk as W}from"../services/cc-switch-sdk.js";import{initInstance as G,syncLegacyStateFromSdk as J}from"../services/instance-init-service.js";import{createBridgeLifecycleOperation as E,deliverPendingBridgeLifecycleNotifications as C,upsertBridgeLifecycleOperation as b}from"../services/lifecycle-state.js";import{loadInstanceState as x,saveInstanceState as L}from"../services/instance-state.js";import{getRuntimeAccessToken as Y}from"../services/runtime-binding.js";import{detectToolStatuses as R,ensureToolsInstalled as Q,SUPPORTED_INSTALLABLE_TOOLS as U,SUPPORTED_UNINSTALLABLE_TOOLS as A,uninstallTools as Z}from"../services/tool-installer.js";import{createLogger as ee}from"../utils/logger.js";const l=ee("instance"),te=["acode","claude","opencode","codex"];let v=null;function Ee(r){v=r}const d=D();function re(){return process.platform==="win32"?"npm.cmd":"npm"}function q(r){return r.length>16e3?`${r.slice(r.length-16e3)}
2
- [output truncated]`:r}function se(r,t){return new Promise((e,s)=>{const n=N(r,t,{shell:!1,windowsHide:!0});let a="",i="";n.stdout.on("data",o=>{a+=o.toString("utf8")}),n.stderr.on("data",o=>{i+=o.toString("utf8")}),n.on("error",s),n.on("close",o=>{e({command:r,args:t,exitCode:o,stdout:q(a),stderr:q(i)})})})}function O(r){return String(r||"").trim()||void 0}function K(r){const t=r.get("host");if(t)return`${r.protocol}://${t}`}async function ne(r){const t={...r.operation,status:"running",message:"Bridge \u66F4\u65B0\u6B63\u5728\u540E\u53F0\u6267\u884C\u3002"};await b(t);try{l.info("[update-bridge] \u6B63\u5728\u540E\u53F0\u66F4\u65B0 aws-runtime-bridge \u5230\u6700\u65B0\u7248\u672C");const e=await se(r.command,r.args),s=e.exitCode===0;await b({...t,status:s?"succeeded":"failed",message:s?"Bridge \u66F4\u65B0\u5B8C\u6210\uFF0C\u8BF7\u91CD\u542F\u5B9E\u4F8B Bridge \u4EE5\u52A0\u8F7D\u65B0\u7248\u672C\u3002":`Bridge \u66F4\u65B0\u5931\u8D25\uFF0Cnpm \u9000\u51FA\u7801: ${e.exitCode??"unknown"}`,error:s?void 0:e.stderr||e.stdout}),await C()}catch(e){const s=e;l.error("[update-bridge] background update failed:",s),await b({...t,status:"failed",message:"Bridge \u66F4\u65B0\u5931\u8D25\u3002",error:s.message||"update bridge failed"}),await C()}}async function P(r){const t=await import("node:fs/promises"),e=await import("node:path"),s=await import("node:os"),n=e.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");if(r){await t.mkdir(e.dirname(n),{recursive:!0}),await t.writeFile(n,JSON.stringify({preserveSessions:!0,timestamp:new Date().toISOString()}),"utf-8"),l.info(`[prepare-restart] \u5DF2\u521B\u5EFA\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6: ${n}`);return}try{await t.unlink(n),l.info("[prepare-restart] \u5DF2\u5220\u9664\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6")}catch{}}function oe(r){return $("md5").update(r,"utf8").digest("hex")}function ae(r){const t=r.trim().toLowerCase();if(!/^[a-f0-9]{32}$/.test(t))return!1;const e=Buffer.from(t,"hex");return _().map(s=>s.trim()).filter(Boolean).some(s=>{const n=Buffer.from(oe(s),"hex");return n.length===e.length&&F(n,e)})}function ie(r){const e=_().length>0,s=y();return e?ae(r)?{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!0}}:{status:401,body:{ok:!1,error:"connection_key_mismatch",version:s,connectionKeyMatched:!1,connectionKeyRequired:!0}}:{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!1}}}function le(r,t){const s=t.trim()||"http://localhost:8080",n=/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(?:\/|$)/i.test(s);return{ok:!1,error:r.message||"scheduler ping failed",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:s,hint:n?"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u5728 bridge \u542F\u52A8\u73AF\u5883\u4E2D\u5C06 AWS_RUNTIME_SCHEDULER_BASE_URL \u914D\u7F6E\u4E3A\u8BE5\u5B9E\u4F8B\u53EF\u8BBF\u95EE\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\uFF0C\u4F8B\u5982 http://<server-host>:7380\u3002":"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u786E\u8BA4 AWS_RUNTIME_SCHEDULER_BASE_URL \u6307\u5411\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\u53EF\u4ECE bridge \u673A\u5668\u8BBF\u95EE\uFF0C\u5E76\u4E14\u8FD0\u884C\u65F6\u8BBF\u95EE\u4EE4\u724C\u4ECD\u6709\u6548\u3002"}}function ce(r){if(typeof r!="object"||r===null)return;const t=Reflect.get(r,"response");if(typeof t!="object"||t===null)return;const e=Reflect.get(t,"status");return typeof e=="number"?e:void 0}async function ue(r,t){try{const e=await I.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":t}});return{schedulerReachable:e.status>=200&&e.status<300,tokenRefreshed:!1}}catch(e){if(ce(e)!==401)throw e;const s=await H(r);if(!s.success||!s.runtimeAccessToken)throw e;const n=await I.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":s.runtimeAccessToken}});return{schedulerReachable:n.status>=200&&n.status<300,tokenRefreshed:!0}}}function de(r){return{ok:!1,error:"ambiguous_scheduler_base_url",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:"",autoRegisterTargetUrls:r,hint:"Multiple autoRegisterTargets.serverUrl values were found, so bridge cannot safely infer which scheduler /runtime/ping should call back. Set AWS_RUNTIME_SCHEDULER_BASE_URL explicitly, or use future schedulerId/token-bound multi-scheduler routing."}}function ge(r){return Array.from(new Set([...r,...te].map(t=>String(t||"").trim().toLowerCase()).filter(Boolean)))}function fe(r,t){const e=r.filter(s=>t[s]?.installed);return e.length===0?"":e.map(s=>{const n=t[s],a=n?.error||n?.executable||"command is still available";return`${s}: ${a}`}).join("; ")}function T(r){return Object.fromEntries(Object.entries(r||{}).map(([t,e])=>[t,{installed:!!e?.installed,version:e?.version||null,executable:e?.executable||null,error:e?.error||null}]))}let k=null;const me=3e5;async function he(){return(await I.get("https://registry.npmjs.org/aws-runtime-bridge/latest",{timeout:1e4,responseType:"json"})).data?.version||"unknown"}d.get("/check-update",async(r,t)=>{try{let e;k&&Date.now()-k.ts<me?e=k.latestVersion:(e=await he(),k={latestVersion:e,ts:Date.now()});const s=y(),n=e!=="unknown"&&s!=="unknown"&&e!==s;t.json({ok:!0,hasUpdate:n,currentVersion:s,latestVersion:e})}catch(e){const s=e;l.warn("[check-update] Failed to query npm registry:",s.message),t.json({ok:!1,hasUpdate:!1,currentVersion:y(),latestVersion:null,error:s.message||"failed to check for updates"})}}),d.get("/healthz",(r,t)=>{t.json({ok:!0,runtimeBridge:"healthy"})}),d.get("/connection-check",(r,t)=>{const e=String(r.header("X-Bridge-Connection-Key-MD5")||""),s=ie(e);t.status(s.status).json(s.body)}),d.get("/ping",S,async(r,t)=>{let e=M(r.header("X-Scheduler-Base-Url"),r.header("X-Bridge-Instance-Id"),r.header("X-Bridge-Instance-Name"));if(e.source==="ambiguous-auto-register-targets"){const n=X(r.header("X-Runtime-Token"));if(n)e={url:n,source:"token-match",ambiguousTargetUrls:[]};else{const a=r.header("X-Bridge-Instance-Id");if(a){const i=z(),o=Object.entries(i.registrations).find(([,c])=>c===a)?.[0];o&&(e={url:o,source:"instance-id-match",ambiguousTargetUrls:[]})}if(!e.url||e.source==="ambiguous-auto-register-targets"){t.status(400).json(de(e.ambiguousTargetUrls));return}}}const s=e.url;try{const n=Y(s);if(!n){t.status(401).json({ok:!1,error:"runtime_access_token_required"});return}const a=await ue(s,n);t.json({ok:!0,runtimeBridge:"healthy",version:y(),schedulerReachable:a.schedulerReachable,tokenRefreshed:a.tokenRefreshed,schedulerBaseUrl:s,instanceId:e.instanceId,instanceName:e.instanceName})}catch(n){const a=n;t.status(502).json(le(a,s))}}),d.post("/init-instance",S,async(r,t)=>{const{agentId:e,workspacePath:s,skillEnabled:n,mcpEnabled:a,ccSwitchEnabledTools:i,skillPackage:o,skillPackages:c,mcpServers:m}=r.body||{};if(!e||!s){t.status(400).json({error:"agentId and workspacePath are required"});return}try{l.info("[init-instance] Request received",{agentId:String(e),workspacePath:String(s),skillEnabled:n,mcpEnabled:a,ccSwitchEnabledTools:i});const u=await G(e,s,{skillEnabled:n,mcpEnabled:a,ccSwitchEnabledTools:i,skillPackage:o,skillPackages:c,mcpServers:m});l.info("[init-instance] Initialization completed",{agentId:String(e),ok:u.ok,logCount:u.logs?.length||0,enabledTools:u.enabledTools}),t.json(u)}catch(u){const g=u;l.error("[init-instance] Initialization failed",{agentId:String(e),workspacePath:String(s),error:g?.message||String(u),stack:g?.stack||null}),t.status(400).json({error:g?.message||"initialize instance failed"})}}),d.post("/cc-switch/state",S,async(r,t)=>{const{agentId:e}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}try{l.info("[cc-switch/state] Request received",{agentId:String(e)});const s=await W(e),n=await V(s),i=(await J(e,s)).state,o=ge(i.enabledTools||[]);l.info("[cc-switch/state] Detecting SDK statuses",{agentId:String(e),detectionTargets:o});const c=await R(o);l.info("[cc-switch/state] SDK status detection completed",{agentId:String(e),toolStatus:T(c),importedMcpCount:n.mcpServers,importedSkillCount:n.skills}),t.json({ok:!0,agentId:String(e),imported:{mcpServers:n.mcpServers,skills:n.skills},state:{...i,toolStatus:c}})}catch(s){const n=s;l.error("[cc-switch/state] Failed to load state",{agentId:String(e),error:n?.message||String(s),stack:n?.stack||null}),t.status(400).json({error:n?.message||"load state failed"})}}),d.post("/cc-switch/install-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const n=Array.isArray(s)?s.map(o=>String(o||"").trim().toLowerCase()).filter(Boolean):[];l.info("[cc-switch/install-tools] Request received",{agentId:String(e),requestedTools:n});const a=new Set(U),i=n.filter(o=>a.has(o));if(l.info("[cc-switch/install-tools] Request normalized",{agentId:String(e),requestedTools:n,installableTools:i,supportedInstallableTools:U}),i.length===0){l.warn("[cc-switch/install-tools] No supported installable tools requested",{agentId:String(e),requestedTools:n}),t.status(400).json({error:`tools must include ${U.join(", ")}`});return}try{const o=await x(e),c=Array.from(new Set([...o.enabledTools||[],...i]));l.info("[cc-switch/install-tools] Starting installer",{agentId:String(e),installableTools:i,previousEnabledTools:o.enabledTools||[],nextEnabledTools:c});const m=await Q(i);l.info("[cc-switch/install-tools] Installer completed",{agentId:String(e),toolStatus:T(m)});const u=await R(c);l.info("[cc-switch/install-tools] Refreshed enabled SDK statuses",{agentId:String(e),refreshedToolStatus:T(u)});const g={...u,...m},h=await L(e,{...o,enabledTools:c,toolStatus:g}),w=new Set(i),p=Object.entries(g).filter(([f])=>w.has(f)).filter(([,f])=>!f.installed).map(([f,B])=>({tool:f,error:B.error||null}));p.length>0&&l.warn("[cc-switch/install-tools] Some SDKs remain unavailable",{agentId:String(e),failedTools:p}),l.info("[cc-switch/install-tools] State saved",{agentId:String(e),enabledTools:h.enabledTools,toolStatus:T(g)}),t.json({ok:p.length===0,agentId:String(e),toolStatus:g,failedTools:p,state:h})}catch(o){const c=o;l.error("[cc-switch/install-tools] Install route failed",{agentId:String(e),requestedTools:n,installableTools:i,error:c?.message||String(o),stack:c?.stack||null}),t.status(400).json({error:c?.message||"install tools failed"})}}),d.post("/cc-switch/uninstall-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const n=Array.isArray(s)?s.map(o=>String(o||"").trim().toLowerCase()).filter(Boolean):[],a=new Set(A),i=n.filter(o=>a.has(o));if(i.length===0){t.status(400).json({error:`tools must include ${A.join(", ")}`});return}try{const o=await x(e),c=new Set(i),m=(o.enabledTools||[]).filter(B=>!c.has(String(B||"").trim().toLowerCase())),u=await Z(i),h={...await R([...m,...i]),...u},w=fe(i,h),p=w?o.enabledTools||[]:m,f=await L(e,{...o,enabledTools:p,toolStatus:h});if(w){t.status(400).json({ok:!1,agentId:String(e),error:`uninstall incomplete: ${w}`,toolStatus:h,state:f});return}t.json({ok:!0,agentId:String(e),toolStatus:h,state:f})}catch(o){const c=o;t.status(400).json({error:c?.message||"uninstall tools failed"})}}),d.post("/prepare-restart",S,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{};try{l.info(`[prepare-restart] \u6536\u5230\u51C6\u5907\u91CD\u542F\u901A\u77E5\uFF0CpreserveSessions=${e}`),await P(!!e),t.json({ok:!0,preserveSessions:e,message:e?"Bridge will preserve sessions for aws-mcp-server restart":"Bridge will clean up sessions on shutdown"})}catch(s){const n=s;l.error("[prepare-restart] Error:",n),t.status(500).json({error:n.message})}}),d.post("/update-bridge",j,async(r,t)=>{const e=re(),s=["install","-g","aws-runtime-bridge@latest","--omit=optional"],n=O(r.body?.requestedByUserId);try{if(!n){t.status(400).json({error:"requestedByUserId is required"});return}const a=E({operationType:"update",requestedByUserId:n,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:K(r),status:"accepted",message:"Bridge \u66F4\u65B0\u8BF7\u6C42\u5DF2\u6536\u5230\uFF0C\u5C06\u5728\u540E\u53F0\u6267\u884C\u3002"});await b(a),ne({command:e,args:s,operation:a}),t.json({ok:!0,operationId:a.operationId,message:"Bridge \u66F4\u65B0\u5DF2\u5728\u540E\u53F0\u5F00\u59CB\uFF0C\u5B8C\u6210\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002"})}catch(a){const i=a;l.error("[update-bridge] Error:",i),t.status(500).json({error:i.message||"update bridge failed"})}}),d.post("/restart-bridge",j,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{},s=O(r.body?.requestedByUserId);try{if(!s){t.status(400).json({error:"requestedByUserId is required"});return}l.info(`[restart-bridge] \u6536\u5230\u5B9E\u4F8B Bridge \u91CD\u542F\u8BF7\u6C42\uFF0CpreserveSessions=${e}`);const n=E({operationType:"restart",requestedByUserId:s,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:K(r),status:"accepted",message:"Bridge \u5DF2\u6536\u5230\u91CD\u542F\u8BF7\u6C42\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u53D1\u9001\u786E\u8BA4\u901A\u77E5\u3002"});await b(n),await P(!!e),t.json({ok:!0,operationId:n.operationId,preserveSessions:!!e,message:"Bridge \u6B63\u5728\u4F18\u96C5\u9000\u51FA\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002\u82E5\u672A\u7531\u5B88\u62A4\u8FDB\u7A0B\u7BA1\u7406\uFF0C\u8BF7\u624B\u52A8\u91CD\u65B0\u542F\u52A8\u3002"}),setTimeout(()=>{if(process.exitCode=42,v){v("PANEL_RESTART",!!e).catch(a=>{const i=a;l.error("[restart-bridge] graceful shutdown failed:",i),process.exit(1)});return}process.exit(42)},250)}catch(n){const a=n;l.error("[restart-bridge] Error:",a),t.status(500).json({error:a.message||"restart bridge failed"})}});export{de as buildAmbiguousSchedulerBaseUrlResponse,ie as buildConnectionCheckResponse,le as buildSchedulerPingFailureResponse,ge as buildToolStatusDetectionTargets,fe as buildUninstallFailureMessage,d as instanceRouter,Ee as setGracefulShutdownFn,T as summarizeToolStatus};
1
+ import{Router as F}from"express";import I from"axios";import{spawn as N}from"node:child_process";import{createHash as $,timingSafeEqual as M}from"node:crypto";import{getBridgeVersion as y,resolveSchedulerBaseUrlFrom as z}from"../config.js";import{validateToken as h,validateTokenOrPanelSession as v}from"../middleware/auth.js";import{getConfiguredConnectionKeys as _,getRegistrationState as H,requestRuntimeAccessTokenRefreshForServer as X,resolveSchedulerBaseUrlFromToken as V}from"../services/auto-register.js";import{discoverCcSwitchConfiguredItems as W,loadCcSwitchSdk as G}from"../services/cc-switch-sdk.js";import{initInstance as J,syncLegacyStateFromSdk as Y}from"../services/instance-init-service.js";import{setAgentFeatures as Q,getAgentFeatures as E}from"../services/instance-features.js";import{createBridgeLifecycleOperation as C,deliverPendingBridgeLifecycleNotifications as x,upsertBridgeLifecycleOperation as b}from"../services/lifecycle-state.js";import{loadInstanceState as L,saveInstanceState as A}from"../services/instance-state.js";import{getRuntimeAccessToken as Z}from"../services/runtime-binding.js";import{detectToolStatuses as R,ensureToolsInstalled as ee,SUPPORTED_INSTALLABLE_TOOLS as U,SUPPORTED_UNINSTALLABLE_TOOLS as q,uninstallTools as te}from"../services/tool-installer.js";import{createLogger as re}from"../utils/logger.js";const l=re("instance"),se=["acode","claude","opencode","codex"];let j=null;function Le(r){j=r}const d=F();function ne(){return process.platform==="win32"?"npm.cmd":"npm"}function O(r){return r.length>16e3?`${r.slice(r.length-16e3)}
2
+ [output truncated]`:r}function oe(r,t){return new Promise((e,s)=>{const n=N(r,t,{shell:!1,windowsHide:!0});let a="",i="";n.stdout.on("data",o=>{a+=o.toString("utf8")}),n.stderr.on("data",o=>{i+=o.toString("utf8")}),n.on("error",s),n.on("close",o=>{e({command:r,args:t,exitCode:o,stdout:O(a),stderr:O(i)})})})}function K(r){return String(r||"").trim()||void 0}function P(r){const t=r.get("host");if(t)return`${r.protocol}://${t}`}async function ae(r){const t={...r.operation,status:"running",message:"Bridge \u66F4\u65B0\u6B63\u5728\u540E\u53F0\u6267\u884C\u3002"};await b(t);try{l.info("[update-bridge] \u6B63\u5728\u540E\u53F0\u66F4\u65B0 aws-runtime-bridge \u5230\u6700\u65B0\u7248\u672C");const e=await oe(r.command,r.args),s=e.exitCode===0;await b({...t,status:s?"succeeded":"failed",message:s?"Bridge \u66F4\u65B0\u5B8C\u6210\uFF0C\u8BF7\u91CD\u542F\u5B9E\u4F8B Bridge \u4EE5\u52A0\u8F7D\u65B0\u7248\u672C\u3002":`Bridge \u66F4\u65B0\u5931\u8D25\uFF0Cnpm \u9000\u51FA\u7801: ${e.exitCode??"unknown"}`,error:s?void 0:e.stderr||e.stdout}),await x()}catch(e){const s=e;l.error("[update-bridge] background update failed:",s),await b({...t,status:"failed",message:"Bridge \u66F4\u65B0\u5931\u8D25\u3002",error:s.message||"update bridge failed"}),await x()}}async function D(r){const t=await import("node:fs/promises"),e=await import("node:path"),s=await import("node:os"),n=e.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");if(r){await t.mkdir(e.dirname(n),{recursive:!0}),await t.writeFile(n,JSON.stringify({preserveSessions:!0,timestamp:new Date().toISOString()}),"utf-8"),l.info(`[prepare-restart] \u5DF2\u521B\u5EFA\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6: ${n}`);return}try{await t.unlink(n),l.info("[prepare-restart] \u5DF2\u5220\u9664\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6")}catch{}}function ie(r){return $("md5").update(r,"utf8").digest("hex")}function le(r){const t=r.trim().toLowerCase();if(!/^[a-f0-9]{32}$/.test(t))return!1;const e=Buffer.from(t,"hex");return _().map(s=>s.trim()).filter(Boolean).some(s=>{const n=Buffer.from(ie(s),"hex");return n.length===e.length&&M(n,e)})}function ce(r){const e=_().length>0,s=y();return e?le(r)?{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!0}}:{status:401,body:{ok:!1,error:"connection_key_mismatch",version:s,connectionKeyMatched:!1,connectionKeyRequired:!0}}:{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!1}}}function ue(r,t){const s=t.trim()||"http://localhost:8080",n=/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(?:\/|$)/i.test(s);return{ok:!1,error:r.message||"scheduler ping failed",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:s,hint:n?"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u5728 bridge \u542F\u52A8\u73AF\u5883\u4E2D\u5C06 AWS_RUNTIME_SCHEDULER_BASE_URL \u914D\u7F6E\u4E3A\u8BE5\u5B9E\u4F8B\u53EF\u8BBF\u95EE\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\uFF0C\u4F8B\u5982 http://<server-host>:7380\u3002":"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u786E\u8BA4 AWS_RUNTIME_SCHEDULER_BASE_URL \u6307\u5411\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\u53EF\u4ECE bridge \u673A\u5668\u8BBF\u95EE\uFF0C\u5E76\u4E14\u8FD0\u884C\u65F6\u8BBF\u95EE\u4EE4\u724C\u4ECD\u6709\u6548\u3002"}}function de(r){if(typeof r!="object"||r===null)return;const t=Reflect.get(r,"response");if(typeof t!="object"||t===null)return;const e=Reflect.get(t,"status");return typeof e=="number"?e:void 0}async function ge(r,t){try{const e=await I.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":t}});return{schedulerReachable:e.status>=200&&e.status<300,tokenRefreshed:!1}}catch(e){if(de(e)!==401)throw e;const s=await X(r);if(!s.success||!s.runtimeAccessToken)throw e;const n=await I.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":s.runtimeAccessToken}});return{schedulerReachable:n.status>=200&&n.status<300,tokenRefreshed:!0}}}function fe(r){return{ok:!1,error:"ambiguous_scheduler_base_url",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:"",autoRegisterTargetUrls:r,hint:"Multiple autoRegisterTargets.serverUrl values were found, so bridge cannot safely infer which scheduler /runtime/ping should call back. Set AWS_RUNTIME_SCHEDULER_BASE_URL explicitly, or use future schedulerId/token-bound multi-scheduler routing."}}function me(r){return Array.from(new Set([...r,...se].map(t=>String(t||"").trim().toLowerCase()).filter(Boolean)))}function pe(r,t){const e=r.filter(s=>t[s]?.installed);return e.length===0?"":e.map(s=>{const n=t[s],a=n?.error||n?.executable||"command is still available";return`${s}: ${a}`}).join("; ")}function T(r){return Object.fromEntries(Object.entries(r||{}).map(([t,e])=>[t,{installed:!!e?.installed,version:e?.version||null,executable:e?.executable||null,error:e?.error||null}]))}let k=null;const he=3e5;async function Se(){return(await I.get("https://registry.npmjs.org/aws-runtime-bridge/latest",{timeout:1e4,responseType:"json"})).data?.version||"unknown"}d.get("/check-update",async(r,t)=>{try{let e;k&&Date.now()-k.ts<he?e=k.latestVersion:(e=await Se(),k={latestVersion:e,ts:Date.now()});const s=y(),n=e!=="unknown"&&s!=="unknown"&&e!==s;t.json({ok:!0,hasUpdate:n,currentVersion:s,latestVersion:e})}catch(e){const s=e;l.warn("[check-update] Failed to query npm registry:",s.message),t.json({ok:!1,hasUpdate:!1,currentVersion:y(),latestVersion:null,error:s.message||"failed to check for updates"})}}),d.get("/healthz",(r,t)=>{t.json({ok:!0,runtimeBridge:"healthy"})}),d.get("/connection-check",(r,t)=>{const e=String(r.header("X-Bridge-Connection-Key-MD5")||""),s=ce(e);t.status(s.status).json(s.body)}),d.get("/ping",h,async(r,t)=>{let e=z(r.header("X-Scheduler-Base-Url"),r.header("X-Bridge-Instance-Id"),r.header("X-Bridge-Instance-Name"));if(e.source==="ambiguous-auto-register-targets"){const n=V(r.header("X-Runtime-Token"));if(n)e={url:n,source:"token-match",ambiguousTargetUrls:[]};else{const a=r.header("X-Bridge-Instance-Id");if(a){const i=H(),o=Object.entries(i.registrations).find(([,c])=>c===a)?.[0];o&&(e={url:o,source:"instance-id-match",ambiguousTargetUrls:[]})}if(!e.url||e.source==="ambiguous-auto-register-targets"){t.status(400).json(fe(e.ambiguousTargetUrls));return}}}const s=e.url;try{const n=Z(s);if(!n){t.status(401).json({ok:!1,error:"runtime_access_token_required"});return}const a=await ge(s,n);t.json({ok:!0,runtimeBridge:"healthy",version:y(),schedulerReachable:a.schedulerReachable,tokenRefreshed:a.tokenRefreshed,schedulerBaseUrl:s,instanceId:e.instanceId,instanceName:e.instanceName})}catch(n){const a=n;t.status(502).json(ue(a,s))}}),d.post("/init-instance",h,async(r,t)=>{const{agentId:e,workspacePath:s,skillEnabled:n,mcpEnabled:a,ccSwitchEnabledTools:i,skillPackage:o,skillPackages:c,mcpServers:m}=r.body||{};if(!e||!s){t.status(400).json({error:"agentId and workspacePath are required"});return}try{l.info("[init-instance] Request received",{agentId:String(e),workspacePath:String(s),skillEnabled:n,mcpEnabled:a,ccSwitchEnabledTools:i});const u=await J(e,s,{skillEnabled:n,mcpEnabled:a,ccSwitchEnabledTools:i,skillPackage:o,skillPackages:c,mcpServers:m});l.info("[init-instance] Initialization completed",{agentId:String(e),ok:u.ok,logCount:u.logs?.length||0,enabledTools:u.enabledTools}),t.json(u)}catch(u){const g=u;l.error("[init-instance] Initialization failed",{agentId:String(e),workspacePath:String(s),error:g?.message||String(u),stack:g?.stack||null}),t.status(400).json({error:g?.message||"initialize instance failed"})}}),d.post("/features",h,async(r,t)=>{const{agentId:e,features:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}Q(String(e),s),l.info("[instance/features] \u80FD\u529B\u5F00\u5173\u5DF2\u66F4\u65B0",{agentId:String(e),features:E(String(e))}),t.json({ok:!0,agentId:String(e),features:E(String(e))})}),d.post("/cc-switch/state",h,async(r,t)=>{const{agentId:e}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}try{l.info("[cc-switch/state] Request received",{agentId:String(e)});const s=await G(e),n=await W(s),i=(await Y(e,s)).state,o=me(i.enabledTools||[]);l.info("[cc-switch/state] Detecting SDK statuses",{agentId:String(e),detectionTargets:o});const c=await R(o);l.info("[cc-switch/state] SDK status detection completed",{agentId:String(e),toolStatus:T(c),importedMcpCount:n.mcpServers,importedSkillCount:n.skills}),t.json({ok:!0,agentId:String(e),imported:{mcpServers:n.mcpServers,skills:n.skills},state:{...i,toolStatus:c}})}catch(s){const n=s;l.error("[cc-switch/state] Failed to load state",{agentId:String(e),error:n?.message||String(s),stack:n?.stack||null}),t.status(400).json({error:n?.message||"load state failed"})}}),d.post("/cc-switch/install-tools",h,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const n=Array.isArray(s)?s.map(o=>String(o||"").trim().toLowerCase()).filter(Boolean):[];l.info("[cc-switch/install-tools] Request received",{agentId:String(e),requestedTools:n});const a=new Set(U),i=n.filter(o=>a.has(o));if(l.info("[cc-switch/install-tools] Request normalized",{agentId:String(e),requestedTools:n,installableTools:i,supportedInstallableTools:U}),i.length===0){l.warn("[cc-switch/install-tools] No supported installable tools requested",{agentId:String(e),requestedTools:n}),t.status(400).json({error:`tools must include ${U.join(", ")}`});return}try{const o=await L(e),c=Array.from(new Set([...o.enabledTools||[],...i]));l.info("[cc-switch/install-tools] Starting installer",{agentId:String(e),installableTools:i,previousEnabledTools:o.enabledTools||[],nextEnabledTools:c});const m=await ee(i);l.info("[cc-switch/install-tools] Installer completed",{agentId:String(e),toolStatus:T(m)});const u=await R(c);l.info("[cc-switch/install-tools] Refreshed enabled SDK statuses",{agentId:String(e),refreshedToolStatus:T(u)});const g={...u,...m},p=await A(e,{...o,enabledTools:c,toolStatus:g}),w=new Set(i),S=Object.entries(g).filter(([f])=>w.has(f)).filter(([,f])=>!f.installed).map(([f,B])=>({tool:f,error:B.error||null}));S.length>0&&l.warn("[cc-switch/install-tools] Some SDKs remain unavailable",{agentId:String(e),failedTools:S}),l.info("[cc-switch/install-tools] State saved",{agentId:String(e),enabledTools:p.enabledTools,toolStatus:T(g)}),t.json({ok:S.length===0,agentId:String(e),toolStatus:g,failedTools:S,state:p})}catch(o){const c=o;l.error("[cc-switch/install-tools] Install route failed",{agentId:String(e),requestedTools:n,installableTools:i,error:c?.message||String(o),stack:c?.stack||null}),t.status(400).json({error:c?.message||"install tools failed"})}}),d.post("/cc-switch/uninstall-tools",h,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const n=Array.isArray(s)?s.map(o=>String(o||"").trim().toLowerCase()).filter(Boolean):[],a=new Set(q),i=n.filter(o=>a.has(o));if(i.length===0){t.status(400).json({error:`tools must include ${q.join(", ")}`});return}try{const o=await L(e),c=new Set(i),m=(o.enabledTools||[]).filter(B=>!c.has(String(B||"").trim().toLowerCase())),u=await te(i),p={...await R([...m,...i]),...u},w=pe(i,p),S=w?o.enabledTools||[]:m,f=await A(e,{...o,enabledTools:S,toolStatus:p});if(w){t.status(400).json({ok:!1,agentId:String(e),error:`uninstall incomplete: ${w}`,toolStatus:p,state:f});return}t.json({ok:!0,agentId:String(e),toolStatus:p,state:f})}catch(o){const c=o;t.status(400).json({error:c?.message||"uninstall tools failed"})}}),d.post("/prepare-restart",h,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{};try{l.info(`[prepare-restart] \u6536\u5230\u51C6\u5907\u91CD\u542F\u901A\u77E5\uFF0CpreserveSessions=${e}`),await D(!!e),t.json({ok:!0,preserveSessions:e,message:e?"Bridge will preserve sessions for aws-mcp-server restart":"Bridge will clean up sessions on shutdown"})}catch(s){const n=s;l.error("[prepare-restart] Error:",n),t.status(500).json({error:n.message})}}),d.post("/update-bridge",v,async(r,t)=>{const e=ne(),s=["install","-g","aws-runtime-bridge@latest","--omit=optional"],n=K(r.body?.requestedByUserId);try{if(!n){t.status(400).json({error:"requestedByUserId is required"});return}const a=C({operationType:"update",requestedByUserId:n,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:P(r),status:"accepted",message:"Bridge \u66F4\u65B0\u8BF7\u6C42\u5DF2\u6536\u5230\uFF0C\u5C06\u5728\u540E\u53F0\u6267\u884C\u3002"});await b(a),ae({command:e,args:s,operation:a}),t.json({ok:!0,operationId:a.operationId,message:"Bridge \u66F4\u65B0\u5DF2\u5728\u540E\u53F0\u5F00\u59CB\uFF0C\u5B8C\u6210\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002"})}catch(a){const i=a;l.error("[update-bridge] Error:",i),t.status(500).json({error:i.message||"update bridge failed"})}}),d.post("/restart-bridge",v,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{},s=K(r.body?.requestedByUserId);try{if(!s){t.status(400).json({error:"requestedByUserId is required"});return}l.info(`[restart-bridge] \u6536\u5230\u5B9E\u4F8B Bridge \u91CD\u542F\u8BF7\u6C42\uFF0CpreserveSessions=${e}`);const n=C({operationType:"restart",requestedByUserId:s,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:P(r),status:"accepted",message:"Bridge \u5DF2\u6536\u5230\u91CD\u542F\u8BF7\u6C42\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u53D1\u9001\u786E\u8BA4\u901A\u77E5\u3002"});await b(n),await D(!!e),t.json({ok:!0,operationId:n.operationId,preserveSessions:!!e,message:"Bridge \u6B63\u5728\u4F18\u96C5\u9000\u51FA\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002\u82E5\u672A\u7531\u5B88\u62A4\u8FDB\u7A0B\u7BA1\u7406\uFF0C\u8BF7\u624B\u52A8\u91CD\u65B0\u542F\u52A8\u3002"}),setTimeout(()=>{if(process.exitCode=42,j){j("PANEL_RESTART",!!e).catch(a=>{const i=a;l.error("[restart-bridge] graceful shutdown failed:",i),process.exit(1)});return}process.exit(42)},250)}catch(n){const a=n;l.error("[restart-bridge] Error:",a),t.status(500).json({error:a.message||"restart bridge failed"})}});export{fe as buildAmbiguousSchedulerBaseUrlResponse,ce as buildConnectionCheckResponse,ue as buildSchedulerPingFailureResponse,me as buildToolStatusDetectionTargets,pe as buildUninstallFailureMessage,d as instanceRouter,Le as setGracefulShutdownFn,T as summarizeToolStatus};
3
3
 
@@ -20,7 +20,7 @@ export interface PtySessionSummary {
20
20
  attachedClients: number;
21
21
  ownerUserId?: string;
22
22
  tenantId?: string;
23
- projectName?: string;
23
+ projectId?: string;
24
24
  }
25
25
  interface PtySessionEntry extends PtySessionSummary {
26
26
  ptyProcess: pty.IPty;
@@ -57,7 +57,7 @@ export declare function createPtySession(input: {
57
57
  preCommand?: unknown;
58
58
  ownerUserId?: unknown;
59
59
  tenantId?: unknown;
60
- projectName?: unknown;
60
+ projectId?: unknown;
61
61
  }): PtySessionSummary;
62
62
  export declare function resolvePtyExitedTtlMs(env?: NodeJS.ProcessEnv): number;
63
63
  export declare function resolveMaxPtySessions(env?: NodeJS.ProcessEnv): number;
@@ -1,6 +1,6 @@
1
- import O from"node:crypto";import I from"node:fs";import l from"node:path";import{Router as L}from"express";import*as D from"node-pty";import{v4 as M}from"uuid";import U,{WebSocketServer as z}from"ws";import{isRuntimeTokenValid as x,validateToken as h}from"../middleware/auth.js";import{createLogger as R}from"../utils/logger.js";const p=R("pty"),j=80,W=24,_=2e5,E=30*60*1e3,$=5*60*1e3,v=60*1e3,b=8,F=new Set(["false","halt","nologin","reboot","shutdown","sync","true"]),a=new Map,w=new Map;function B(e=process.env){const t=String(e.AWS_PTY_IDLE_TTL_MS||"").trim();if(!t)return E;const r=Number(t);return!Number.isFinite(r)||r<1e3?E:r}function Y(e=process.platform,t=process.env,r=I.existsSync){return e==="win32"?{shell:String(t.ComSpec||t.COMSPEC||"").trim()||"powershell.exe",args:[]}:{shell:[String(t.SHELL||"").trim(),"/bin/bash","/bin/sh"].find(o=>X(o,r))||"/bin/sh",args:[]}}function X(e,t){const r=e.trim();if(!r)return!1;const n=l.basename(r).toLowerCase();return F.has(n)?!1:!l.isAbsolute(r)||t(r)}function H(e,t=process.platform,r=process.env){const n=String(e||"auto").trim();if(!n||n==="auto")return Y(t,r);const i=new Set(["bash","sh","zsh","fish","pwsh","pwsh.exe","powershell","powershell.exe","cmd","cmd.exe"]),o=(t==="win32"?l.win32.basename(n):l.basename(n)).toLowerCase();if(!i.has(o))throw new Error(`shell is not allowed: ${n}`);return{shell:/^[A-Za-z]:[\\/]/.test(n)||n.startsWith("\\")||n.startsWith("/")?n:t==="win32"?{cmd:"cmd.exe",powershell:"powershell.exe",pwsh:"pwsh.exe"}[o]??n:n,args:[]}}function J(e,t){const r=l.relative(l.resolve(t),l.resolve(e));return r===""||!r.startsWith("..")&&!l.isAbsolute(r)}function V(e=process.env){return String(e.AWS_PTY_ALLOWED_ROOTS||"").split(l.delimiter).map(t=>t.trim()).filter(Boolean).map(t=>l.resolve(t))}function K(e,t=process.env){const r=String(e||"").trim();if(!r)throw new Error("workspacePath is required");if(!l.isAbsolute(r))throw new Error("workspacePath must be absolute");const n=l.resolve(r);if(!I.statSync(n).isDirectory())throw new Error("workspacePath must be an existing directory");const o=V(t);if(o.length>0&&!o.some(c=>J(n,c)))throw new Error("workspacePath is outside allowed roots");return n}function T(e,t){const r=Number(e);return!Number.isInteger(r)||r<=0?t:Math.min(r,500)}function q(e){return{...e,TERM:e.TERM||"xterm-256color",COLORTERM:e.COLORTERM||"truecolor"}}function Z(e,t){e.outputBuffer=`${e.outputBuffer}${t}`,e.outputBuffer.length>_&&(e.outputBuffer=e.outputBuffer.slice(-_))}function g(e){return{id:e.id,title:e.title,workspacePath:e.workspacePath,shell:e.shell,cols:e.cols,rows:e.rows,status:e.status,createdAt:e.createdAt,lastActiveAt:e.lastActiveAt,exitCode:e.exitCode,signal:e.signal,attachedClients:e.sockets.size,ownerUserId:e.ownerUserId,tenantId:e.tenantId,projectName:e.projectName}}function k(e,t){e.readyState===U.OPEN&&e.send(JSON.stringify(t))}function P(e,t){for(const r of e.sockets)k(r,t)}function A(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{e.sockets.size===0&&S(e.id,"idle-timeout")},B())}function C(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null)}function G(e){if(a.size>=re())throw new Error("maximum PTY sessions reached");const t=K(e.workspacePath),r=H(e.shell),n=T(e.cols,j),i=T(e.rows,W),o=`pty_${M()}`,c=new Date().toISOString(),d=String(e.title||"").trim()||`Terminal ${a.size+1}`,u=D.spawn(r.shell,r.args,{name:"xterm-256color",cols:n,rows:i,cwd:t,env:q(process.env)}),s={id:o,title:d,workspacePath:t,shell:r.shell,cols:n,rows:i,status:"running",createdAt:c,lastActiveAt:c,attachedClients:0,ptyProcess:u,sockets:new Set,outputBuffer:"",idleTimer:null,persistent:e.persistent===!0,ownerUserId:String(e.ownerUserId||"").trim()||void 0,tenantId:String(e.tenantId||"").trim()||void 0,projectName:String(e.projectName||"").trim()||void 0};return u.onData(f=>{s.lastActiveAt=new Date().toISOString(),Z(s,f),P(s,{type:"output",data:f})}),u.onExit(({exitCode:f,signal:m})=>{s.status="exited",s.exitCode=f,s.signal=m,s.lastActiveAt=new Date().toISOString(),P(s,{type:"exit",exitCode:f,signal:m}),s.sockets.size===0&&!s.persistent?A(s):s.persistent&&(s.idleTimer&&clearTimeout(s.idleTimer),s.idleTimer=setTimeout(()=>{S(s.id,"exited-timeout")},te()))}),a.set(o,s),s.persistent||A(s),Q(s,e.preCommand),p.info(`created pty session id=${o}, cwd=${t}, shell=${r.shell}`),g(s)}function Q(e,t){const r=typeof t=="string"?t:"";if(!r.trim())return;const n=`${r.replace(/\n/g,"\r").replace(/\r+$/g,"")}\r`;try{e.ptyProcess.write(n)}catch(i){const o=i;p.warn(`failed to inject pty pre-command sessionId=${e.id}: ${o.message}`)}}function ee(e=process.env){const t=String(e.AWS_PTY_CONNECT_TOKEN_TTL_MS||"").trim();if(!t)return v;const r=Number(t);return!Number.isFinite(r)||r<5e3||r>10*60*1e3?v:r}function te(e=process.env){const t=String(e.AWS_PTY_EXITED_TTL_MS||"").trim();if(!t)return $;const r=Number(t);return!Number.isFinite(r)||r<1e3?$:r}function re(e=process.env){const t=String(e.AWS_PTY_MAX_SESSIONS||"").trim();if(!t)return b;const r=Number(t);return!Number.isInteger(r)||r<1||r>100?b:r}function ne(e){if(!e||typeof e!="object")return{workspacePath:void 0};const t=e;return{workspacePath:t.workspacePath,title:t.title,shell:t.shell,cols:t.cols,rows:t.rows,persistent:t.persistent,preCommand:t.preCommand,ownerUserId:t.ownerUserId,tenantId:t.tenantId,projectName:t.projectName}}function se(){return Array.from(a.values()).map(g)}function oe(e){const t=a.get(e);return t?g(t):void 0}function ie(e){if(!a.has(e))return;const t=`ptyws_${O.randomBytes(32).toString("base64url")}`,r=Date.now()+ee();return w.set(t,{sessionId:e,expiresAt:r}),{token:t,expiresAt:new Date(r).toISOString()}}function ae(e,t){const r=w.get(t);return r?r.expiresAt<Date.now()?(w.delete(t),p.warn(`[pty-ws] consumePtyConnectToken: token \u5DF2\u8FC7\u671F, sessionId=${e}, expiredAt=${new Date(r.expiresAt).toISOString()}`),!1):r.sessionId!==e?(p.warn(`[pty-ws] consumePtyConnectToken: sessionId \u4E0D\u5339\u914D, expected=${r.sessionId}, actual=${e}`),!1):(w.delete(t),p.info(`[pty-ws] consumePtyConnectToken: \u6210\u529F, sessionId=${e}`),!0):(p.warn(`[pty-ws] consumePtyConnectToken: token \u4E0D\u5B58\u5728, sessionId=${e}, ptyConnectTokens.size=${w.size}`),!1)}function ce(e){for(const[t,r]of w.entries())r.sessionId===e&&w.delete(t)}function S(e,t="closed"){const r=a.get(e);if(!r)return!1;C(r),ce(e),P(r,{type:"status",status:"closing",reason:t});for(const n of r.sockets)n.close(1e3,t);return r.sockets.clear(),r.status==="running"&&r.ptyProcess.kill(),a.delete(e),p.info(`closed pty session id=${e}, reason=${t}`),!0}function ge(e="shutdown"){for(const t of Array.from(a.keys()))S(t,e)}const y=L();y.get("/sessions",h,(e,t)=>{t.json({ok:!0,sessions:se()})}),y.post("/sessions",h,(e,t)=>{try{const r=G(ne(e.body));t.status(201).json({ok:!0,session:r})}catch(r){const n=r;t.status(400).json({error:n.message})}}),y.get("/sessions/:sessionId",h,(e,t)=>{const r=oe(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,session:r})}),y.delete("/sessions/:sessionId",h,(e,t)=>{if(!S(e.params.sessionId,"explicit-close")){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0})}),y.post("/sessions/:sessionId/connect-token",h,(e,t)=>{const r=ie(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,...r})});function ue(e){const t=e.toString();try{return JSON.parse(t)}catch{return{type:"input",data:t}}}function le(e,t){C(e),e.sockets.add(t),e.lastActiveAt=new Date().toISOString(),k(t,{type:"status",status:e.status,session:g(e)}),e.outputBuffer&&k(t,{type:"output",data:e.outputBuffer,replay:!0}),t.on("message",r=>{const n=ue(r);if(!n||typeof n!="object")return;const i=n,o=String(i.type||"");if(e.lastActiveAt=new Date().toISOString(),o==="input"){e.ptyProcess.write(String(i.data||""));return}if(o==="resize"){const c=T(i.cols,e.cols),d=T(i.rows,e.rows);e.cols=c,e.rows=d,e.ptyProcess.resize(c,d);return}if(o==="close"){S(e.id,"client-close");return}o==="ping"&&k(t,{type:"pong"})}),t.on("close",()=>{e.sockets.delete(t),e.lastActiveAt=new Date().toISOString(),e.sockets.size===0&&e.status==="running"&&!e.persistent&&A(e)})}function pe(e,t){const r=t.searchParams.get("token")||"";if(r.trim())return r.trim();const n=e.headers["x-runtime-token"];return Array.isArray(n)?n[0]?.trim()||"":typeof n=="string"?n.trim():String(e.headers.authorization||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}function ke(e){const t=new z({noServer:!0});e.on("upgrade",(r,n,i)=>{const o=r.headers.host||"localhost",c=new URL(r.url||"/",`http://${o}`),d=c.pathname.match(/^\/pty\/sessions\/([^/]+)\/stream$/);if(!d)return;const u=pe(r,c),s=decodeURIComponent(d[1]||""),f=u?u.length>12?u.substring(0,12)+"...":u:"empty";if(p.info(`[pty-ws] upgrade \u8BF7\u6C42: sessionId=${s}, token=${f}, pathname=${c.pathname}`),!x(u)&&!ae(s,u)){p.warn(`[pty-ws] token \u6821\u9A8C\u5931\u8D25: sessionId=${s}, token=${f}, runtimeTokenValid=${x(u)}, ptySessions.size=${a.size}`),n.write(`HTTP/1.1 401 Unauthorized\r
1
+ import L from"node:crypto";import A from"node:fs";import l from"node:path";import{Router as N}from"express";import*as D from"node-pty";import{v4 as M}from"uuid";import U,{WebSocketServer as z}from"ws";import{isRuntimeTokenValid as x,validateToken as h}from"../middleware/auth.js";import{createLogger as R}from"../utils/logger.js";const p=R("pty"),j=80,W=24,_=2e5,E=30*60*1e3,$=5*60*1e3,v=60*1e3,b=8,F=new Set(["false","halt","nologin","reboot","shutdown","sync","true"]),c=new Map,w=new Map;function B(e=process.env){const t=String(e.AWS_PTY_IDLE_TTL_MS||"").trim();if(!t)return E;const r=Number(t);return!Number.isFinite(r)||r<1e3?E:r}function Y(e=process.platform,t=process.env,r=A.existsSync){return e==="win32"?{shell:String(t.ComSpec||t.COMSPEC||"").trim()||"powershell.exe",args:[]}:{shell:[String(t.SHELL||"").trim(),"/bin/bash","/bin/sh"].find(o=>X(o,r))||"/bin/sh",args:[]}}function X(e,t){const r=e.trim();if(!r)return!1;const n=l.basename(r).toLowerCase();return F.has(n)?!1:!l.isAbsolute(r)||t(r)}function H(e,t=process.platform,r=process.env){const n=String(e||"auto").trim();if(!n||n==="auto")return Y(t,r);const i=new Set(["bash","sh","zsh","fish","pwsh","pwsh.exe","powershell","powershell.exe","cmd","cmd.exe"]),o=(t==="win32"?l.win32.basename(n):l.basename(n)).toLowerCase();if(!i.has(o))throw new Error(`shell is not allowed: ${n}`);return{shell:/^[A-Za-z]:[\\/]/.test(n)||n.startsWith("\\")||n.startsWith("/")?n:t==="win32"?{cmd:"cmd.exe",powershell:"powershell.exe",pwsh:"pwsh.exe"}[o]??n:n,args:[]}}function J(e,t){const r=l.relative(l.resolve(t),l.resolve(e));return r===""||!r.startsWith("..")&&!l.isAbsolute(r)}function V(e=process.env){return String(e.AWS_PTY_ALLOWED_ROOTS||"").split(l.delimiter).map(t=>t.trim()).filter(Boolean).map(t=>l.resolve(t))}function K(e,t=process.env){const r=String(e||"").trim();if(!r)throw new Error("workspacePath is required");if(!l.isAbsolute(r))throw new Error("workspacePath must be absolute");const n=l.resolve(r);if(!A.statSync(n).isDirectory())throw new Error("workspacePath must be an existing directory");const o=V(t);if(o.length>0&&!o.some(a=>J(n,a)))throw new Error("workspacePath is outside allowed roots");return n}function T(e,t){const r=Number(e);return!Number.isInteger(r)||r<=0?t:Math.min(r,500)}function q(e){return{...e,TERM:e.TERM||"xterm-256color",COLORTERM:e.COLORTERM||"truecolor"}}function Z(e,t){e.outputBuffer=`${e.outputBuffer}${t}`,e.outputBuffer.length>_&&(e.outputBuffer=e.outputBuffer.slice(-_))}function g(e){return{id:e.id,title:e.title,workspacePath:e.workspacePath,shell:e.shell,cols:e.cols,rows:e.rows,status:e.status,createdAt:e.createdAt,lastActiveAt:e.lastActiveAt,exitCode:e.exitCode,signal:e.signal,attachedClients:e.sockets.size,ownerUserId:e.ownerUserId,tenantId:e.tenantId,projectId:e.projectId}}function k(e,t){e.readyState===U.OPEN&&e.send(JSON.stringify(t))}function I(e,t){for(const r of e.sockets)k(r,t)}function P(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{e.sockets.size===0&&S(e.id,"idle-timeout")},B())}function C(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null)}function G(e){if(c.size>=re())throw new Error("maximum PTY sessions reached");const t=K(e.workspacePath),r=H(e.shell),n=T(e.cols,j),i=T(e.rows,W),o=`pty_${M()}`,a=new Date().toISOString(),d=String(e.title||"").trim()||`Terminal ${c.size+1}`,u=D.spawn(r.shell,r.args,{name:"xterm-256color",cols:n,rows:i,cwd:t,env:q(process.env)}),s={id:o,title:d,workspacePath:t,shell:r.shell,cols:n,rows:i,status:"running",createdAt:a,lastActiveAt:a,attachedClients:0,ptyProcess:u,sockets:new Set,outputBuffer:"",idleTimer:null,persistent:e.persistent===!0,ownerUserId:String(e.ownerUserId||"").trim()||void 0,tenantId:String(e.tenantId||"").trim()||void 0,projectId:String(e.projectId||"").trim()||void 0};return u.onData(f=>{s.lastActiveAt=new Date().toISOString(),Z(s,f),I(s,{type:"output",data:f})}),u.onExit(({exitCode:f,signal:m})=>{s.status="exited",s.exitCode=f,s.signal=m,s.lastActiveAt=new Date().toISOString(),I(s,{type:"exit",exitCode:f,signal:m}),s.sockets.size===0&&!s.persistent?P(s):s.persistent&&(s.idleTimer&&clearTimeout(s.idleTimer),s.idleTimer=setTimeout(()=>{S(s.id,"exited-timeout")},te()))}),c.set(o,s),s.persistent||P(s),Q(s,e.preCommand),p.info(`created pty session id=${o}, cwd=${t}, shell=${r.shell}`),g(s)}function Q(e,t){const r=typeof t=="string"?t:"";if(!r.trim())return;const n=`${r.replace(/\n/g,"\r").replace(/\r+$/g,"")}\r`;try{e.ptyProcess.write(n)}catch(i){const o=i;p.warn(`failed to inject pty pre-command sessionId=${e.id}: ${o.message}`)}}function ee(e=process.env){const t=String(e.AWS_PTY_CONNECT_TOKEN_TTL_MS||"").trim();if(!t)return v;const r=Number(t);return!Number.isFinite(r)||r<5e3||r>10*60*1e3?v:r}function te(e=process.env){const t=String(e.AWS_PTY_EXITED_TTL_MS||"").trim();if(!t)return $;const r=Number(t);return!Number.isFinite(r)||r<1e3?$:r}function re(e=process.env){const t=String(e.AWS_PTY_MAX_SESSIONS||"").trim();if(!t)return b;const r=Number(t);return!Number.isInteger(r)||r<1||r>100?b:r}function ne(e){if(!e||typeof e!="object")return{workspacePath:void 0};const t=e;return{workspacePath:t.workspacePath,title:t.title,shell:t.shell,cols:t.cols,rows:t.rows,persistent:t.persistent,preCommand:t.preCommand,ownerUserId:t.ownerUserId,tenantId:t.tenantId,projectId:t.projectId}}function se(){return Array.from(c.values()).map(g)}function oe(e){const t=c.get(e);return t?g(t):void 0}function ie(e){if(!c.has(e))return;const t=`ptyws_${L.randomBytes(32).toString("base64url")}`,r=Date.now()+ee();return w.set(t,{sessionId:e,expiresAt:r}),{token:t,expiresAt:new Date(r).toISOString()}}function ce(e,t){const r=w.get(t);return r?r.expiresAt<Date.now()?(w.delete(t),p.warn(`[pty-ws] consumePtyConnectToken: token \u5DF2\u8FC7\u671F, sessionId=${e}, expiredAt=${new Date(r.expiresAt).toISOString()}`),!1):r.sessionId!==e?(p.warn(`[pty-ws] consumePtyConnectToken: sessionId \u4E0D\u5339\u914D, expected=${r.sessionId}, actual=${e}`),!1):(w.delete(t),p.info(`[pty-ws] consumePtyConnectToken: \u6210\u529F, sessionId=${e}`),!0):(p.warn(`[pty-ws] consumePtyConnectToken: token \u4E0D\u5B58\u5728, sessionId=${e}, ptyConnectTokens.size=${w.size}`),!1)}function ae(e){for(const[t,r]of w.entries())r.sessionId===e&&w.delete(t)}function S(e,t="closed"){const r=c.get(e);if(!r)return!1;C(r),ae(e),I(r,{type:"status",status:"closing",reason:t});for(const n of r.sockets)n.close(1e3,t);return r.sockets.clear(),r.status==="running"&&r.ptyProcess.kill(),c.delete(e),p.info(`closed pty session id=${e}, reason=${t}`),!0}function ge(e="shutdown"){for(const t of Array.from(c.keys()))S(t,e)}const y=N();y.get("/sessions",h,(e,t)=>{t.json({ok:!0,sessions:se()})}),y.post("/sessions",h,(e,t)=>{try{const r=G(ne(e.body));t.status(201).json({ok:!0,session:r})}catch(r){const n=r;t.status(400).json({error:n.message})}}),y.get("/sessions/:sessionId",h,(e,t)=>{const r=oe(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,session:r})}),y.delete("/sessions/:sessionId",h,(e,t)=>{if(!S(e.params.sessionId,"explicit-close")){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0})}),y.post("/sessions/:sessionId/connect-token",h,(e,t)=>{const r=ie(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,...r})});function ue(e){const t=e.toString();try{return JSON.parse(t)}catch{return{type:"input",data:t}}}function le(e,t){C(e),e.sockets.add(t),e.lastActiveAt=new Date().toISOString(),k(t,{type:"status",status:e.status,session:g(e)}),e.outputBuffer&&k(t,{type:"output",data:e.outputBuffer,replay:!0}),t.on("message",r=>{const n=ue(r);if(!n||typeof n!="object")return;const i=n,o=String(i.type||"");if(e.lastActiveAt=new Date().toISOString(),o==="input"){e.ptyProcess.write(String(i.data||""));return}if(o==="resize"){const a=T(i.cols,e.cols),d=T(i.rows,e.rows);e.cols=a,e.rows=d,e.ptyProcess.resize(a,d);return}if(o==="close"){S(e.id,"client-close");return}o==="ping"&&k(t,{type:"pong"})}),t.on("close",()=>{e.sockets.delete(t),e.lastActiveAt=new Date().toISOString(),e.sockets.size===0&&e.status==="running"&&!e.persistent&&P(e)})}function pe(e,t){const r=t.searchParams.get("token")||"";if(r.trim())return r.trim();const n=e.headers["x-runtime-token"];return Array.isArray(n)?n[0]?.trim()||"":typeof n=="string"?n.trim():String(e.headers.authorization||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}function ke(e){const t=new z({noServer:!0});e.on("upgrade",(r,n,i)=>{const o=r.headers.host||"localhost",a=new URL(r.url||"/",`http://${o}`),d=a.pathname.match(/^\/pty\/sessions\/([^/]+)\/stream$/);if(!d)return;const u=pe(r,a),s=decodeURIComponent(d[1]||""),f=u?u.length>12?u.substring(0,12)+"...":u:"empty";if(p.info(`[pty-ws] upgrade \u8BF7\u6C42: sessionId=${s}, token=${f}, pathname=${a.pathname}`),!x(u)&&!ce(s,u)){p.warn(`[pty-ws] token \u6821\u9A8C\u5931\u8D25: sessionId=${s}, token=${f}, runtimeTokenValid=${x(u)}, ptySessions.size=${c.size}`),n.write(`HTTP/1.1 401 Unauthorized\r
2
2
  \r
3
- `),n.destroy();return}const m=a.get(s);if(!m){p.warn(`[pty-ws] session \u4E0D\u5B58\u5728: sessionId=${s}, ptySessions.size=${a.size}, keys=${Array.from(a.keys()).join(",")}`),n.write(`HTTP/1.1 404 Not Found\r
3
+ `),n.destroy();return}const m=c.get(s);if(!m){p.warn(`[pty-ws] session \u4E0D\u5B58\u5728: sessionId=${s}, ptySessions.size=${c.size}, keys=${Array.from(c.keys()).join(",")}`),n.write(`HTTP/1.1 404 Not Found\r
4
4
  \r
5
- `),n.destroy();return}p.info(`[pty-ws] handleUpgrade: sessionId=${s}, status=${m.status}, persistent=${m.persistent}, sockets=${m.sockets.size}`),t.handleUpgrade(r,n,i,N=>{le(m,N)})})}export{ke as attachPtyWebSocketServer,ge as closeAllPtySessions,S as closePtySession,ie as createPtyConnectToken,G as createPtySession,oe as getPtySessionSummary,J as isPathInsideRoot,se as listPtySessions,y as ptyRouter,a as ptySessions,Y as resolveDefaultShell,re as resolveMaxPtySessions,te as resolvePtyExitedTtlMs,B as resolvePtyIdleTtlMs,K as resolvePtyWorkspacePath,H as resolveRequestedShell};
5
+ `),n.destroy();return}p.info(`[pty-ws] handleUpgrade: sessionId=${s}, status=${m.status}, persistent=${m.persistent}, sockets=${m.sockets.size}`),t.handleUpgrade(r,n,i,O=>{le(m,O)})})}export{ke as attachPtyWebSocketServer,ge as closeAllPtySessions,S as closePtySession,ie as createPtyConnectToken,G as createPtySession,oe as getPtySessionSummary,J as isPathInsideRoot,se as listPtySessions,y as ptyRouter,c as ptySessions,Y as resolveDefaultShell,re as resolveMaxPtySessions,te as resolvePtyExitedTtlMs,B as resolvePtyIdleTtlMs,K as resolvePtyWorkspacePath,H as resolveRequestedShell};
6
6
 
@@ -1,2 +1,2 @@
1
- import{Router as g}from"express";import{createLogger as m}from"../utils/logger.js";import{validateToken as s}from"../middleware/auth.js";import{setTaskContext as I,clearTaskContext as x,getTaskContext as p,startWatching as u,stopWatching as j}from"../services/workspaceTaskChangeWatcher.js";const a=m("task-context"),c=g();c.post("/task-context",s,async(o,r)=>{try{const{agentId:t,taskInstanceId:e,taskNodeId:i,tenantId:k,workspacePath:n,projectName:d}=o.body||{};if(!t||!e||!i||!k||!n){r.status(400).json({error:"agentId, taskInstanceId, taskNodeId, tenantId, workspacePath are required"});return}await u(String(n).trim()),I({agentId:String(t).trim(),taskInstanceId:String(e).trim(),taskNodeId:String(i).trim(),tenantId:String(k).trim(),workspacePath:String(n).trim(),projectName:String(d||"").trim()}),a.info(`Task context set via API: agent=${t}, taskInstance=${e}`),r.json({ok:!0})}catch(t){const e=t;a.error(`Failed to set task context: ${e.message}`),r.status(500).json({error:e.message})}}),c.delete("/task-context",s,async(o,r)=>{try{await j(),x(),a.info("Task context cleared via API"),r.json({ok:!0})}catch(t){const e=t;a.error(`Failed to clear task context: ${e.message}`),r.status(500).json({error:e.message})}}),c.get("/task-context",s,async(o,r)=>{const t=p();r.json({ok:!0,context:t})});export{c as taskContextRouter};
1
+ import{Router as g}from"express";import{createLogger as I}from"../utils/logger.js";import{validateToken as s}from"../middleware/auth.js";import{setTaskContext as m,clearTaskContext as x,getTaskContext as p,startWatching as u,stopWatching as j}from"../services/workspaceTaskChangeWatcher.js";const a=I("task-context"),c=g();c.post("/task-context",s,async(o,r)=>{try{const{agentId:t,taskInstanceId:e,taskNodeId:i,tenantId:k,workspacePath:n,projectId:d}=o.body||{};if(!t||!e||!i||!k||!n){r.status(400).json({error:"agentId, taskInstanceId, taskNodeId, tenantId, workspacePath are required"});return}await u(String(n).trim()),m({agentId:String(t).trim(),taskInstanceId:String(e).trim(),taskNodeId:String(i).trim(),tenantId:String(k).trim(),workspacePath:String(n).trim(),projectId:String(d||"").trim()}),a.info(`Task context set via API: agent=${t}, taskInstance=${e}`),r.json({ok:!0})}catch(t){const e=t;a.error(`Failed to set task context: ${e.message}`),r.status(500).json({error:e.message})}}),c.delete("/task-context",s,async(o,r)=>{try{await j(),x(),a.info("Task context cleared via API"),r.json({ok:!0})}catch(t){const e=t;a.error(`Failed to clear task context: ${e.message}`),r.status(500).json({error:e.message})}}),c.get("/task-context",s,async(o,r)=>{const t=p();r.json({ok:!0,context:t})});export{c as taskContextRouter};
2
2