aws-runtime-bridge 1.9.130 → 1.9.134

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 (94) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +15 -0
  2. package/dist/adapter/AcodeSdkAdapter.js +5 -5
  3. package/dist/adapter/types.d.ts +9 -0
  4. package/dist/browser/browser-frame-encode.d.ts +16 -0
  5. package/dist/browser/browser-frame-encode.js +2 -0
  6. package/dist/browser/browser-pool.js +1 -1
  7. package/dist/browser/built-in-browser-tools.js +1 -1
  8. package/dist/browser/screencast-differ.d.ts +35 -0
  9. package/dist/browser/screencast-differ.js +2 -0
  10. package/dist/browser/screencast-manager.d.ts +32 -0
  11. package/dist/browser/screencast-manager.js +1 -1
  12. package/dist/browser/types.d.ts +48 -1
  13. package/dist/browser/types.js +1 -1
  14. package/dist/browser/ws-server.d.ts +1 -1
  15. package/dist/browser/ws-server.js +1 -1
  16. package/dist/routes/background-tasks.js +7 -1
  17. package/dist/routes/instance-tool-status.d.ts +4 -0
  18. package/dist/routes/instance-tool-status.js +2 -0
  19. package/dist/routes/instance.d.ts +0 -2
  20. package/dist/routes/instance.js +1 -1
  21. package/dist/routes/system-metrics.js +1 -1
  22. package/dist/routes/terminal.js +19 -19
  23. package/dist/services/acode-bg-manager.d.ts +36 -0
  24. package/dist/services/acode-bg-manager.js +2 -0
  25. package/dist/services/browser-session-manager.js +1 -1
  26. package/dist/services/instance-features.d.ts +5 -0
  27. package/dist/services/instance-features.js +1 -1
  28. package/dist/services/process-detail.d.ts +77 -0
  29. package/dist/services/process-detail.js +32 -0
  30. package/dist/services/process-top.d.ts +53 -0
  31. package/dist/services/process-top.js +50 -0
  32. package/dist/services/session-output.d.ts +10 -0
  33. package/dist/services/session-output.js +3 -3
  34. package/package/acode/dist/await-complete-tool.js +4 -4
  35. package/package/acode/dist/background-command-manager.d.ts +39 -0
  36. package/package/acode/dist/background-command-manager.js +5 -5
  37. package/package/acode/dist/background-command-tools.js +1 -1
  38. package/package/acode/dist/built-in-file-tools.d.ts +25 -1
  39. package/package/acode/dist/built-in-file-tools.js +2 -2
  40. package/package/acode/dist/builtins/shellCommandResult.d.ts +3 -0
  41. package/package/acode/dist/builtins/shellCommandResult.js +1 -1
  42. package/package/acode/dist/builtins/shellCommandRunner.d.ts +2 -0
  43. package/package/acode/dist/builtins/shellCommandRunner.js +1 -1
  44. package/package/acode/dist/image-utils.d.ts +52 -0
  45. package/package/acode/dist/image-utils.js +2 -0
  46. package/package/acode/dist/index.d.ts +1 -0
  47. package/package/acode/dist/index.js +1 -1
  48. package/package/acode/dist/runtime.d.ts +15 -1
  49. package/package/acode/dist/runtime.js +26 -26
  50. package/package/acode/dist/sub-agent-tools.js +30 -26
  51. package/package/acode/dist/sub-agent-types.d.ts +1 -1
  52. package/package/acode/dist/tool-allocation.d.ts +4 -4
  53. package/package/acode/dist/tool-allocation.js +1 -1
  54. package/package/acode/dist/types.d.ts +3 -1
  55. package/package/aws-client-agent-mcp/dist/http-client.d.ts +4 -0
  56. package/package/aws-client-agent-mcp/dist/http-client.js +1 -1
  57. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +8 -0
  58. package/package/aws-client-agent-mcp/dist/mcp-server.js +28 -25
  59. package/package/aws-client-agent-mcp/dist/mcp-tools.js +6 -6
  60. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +6 -0
  61. package/package/aws-client-agent-mcp/dist/memory-tools.js +2 -2
  62. package/package/aws-client-agent-mcp/dist/types.d.ts +4 -0
  63. package/package/aws-client-agent-mcp/package.json +1 -1
  64. package/package.json +3 -3
  65. package/package/aws-client-agent-mcp/dist/agent-client.test.d.ts +0 -1
  66. package/package/aws-client-agent-mcp/dist/agent-client.test.js +0 -1066
  67. package/package/aws-client-agent-mcp/dist/config.test.d.ts +0 -1
  68. package/package/aws-client-agent-mcp/dist/config.test.js +0 -259
  69. package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts +0 -2
  70. package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -312
  71. package/package/aws-client-agent-mcp/dist/http-client.test.d.ts +0 -1
  72. package/package/aws-client-agent-mcp/dist/http-client.test.js +0 -808
  73. package/package/aws-client-agent-mcp/dist/launch-config-tools.test.d.ts +0 -1
  74. package/package/aws-client-agent-mcp/dist/launch-config-tools.test.js +0 -110
  75. package/package/aws-client-agent-mcp/dist/logger.test.d.ts +0 -1
  76. package/package/aws-client-agent-mcp/dist/logger.test.js +0 -27
  77. package/package/aws-client-agent-mcp/dist/mcp-server.test.d.ts +0 -1
  78. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +0 -2798
  79. package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts +0 -1
  80. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +0 -152
  81. package/package/aws-client-agent-mcp/dist/memory-store.test.d.ts +0 -1
  82. package/package/aws-client-agent-mcp/dist/memory-store.test.js +0 -664
  83. package/package/aws-client-agent-mcp/dist/message-buffer.test.d.ts +0 -1
  84. package/package/aws-client-agent-mcp/dist/message-buffer.test.js +0 -142
  85. package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.d.ts +0 -1
  86. package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.js +0 -81
  87. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.d.ts +0 -1
  88. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +0 -201
  89. package/package/aws-client-agent-mcp/dist/status-reporter.test.d.ts +0 -1
  90. package/package/aws-client-agent-mcp/dist/status-reporter.test.js +0 -157
  91. package/package/aws-client-agent-mcp/dist/watchdog.test.d.ts +0 -1
  92. package/package/aws-client-agent-mcp/dist/watchdog.test.js +0 -124
  93. package/package/aws-client-agent-mcp/dist/websocket-client.test.d.ts +0 -1
  94. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +0 -725
@@ -1,2 +1,2 @@
1
- import{QUALITY_PRESETS as o}from"./types.js";const h=o.original,u=1,f="jpeg",g=30;class d{constructor(t){this.currentPage=null,this.currentSession=null,this.isRunning=!1,this.subscribers=new Set,this.config={quality:t?.quality??h.quality,maxWidth:t?.maxWidth??h.maxWidth,maxHeight:t?.maxHeight??h.maxHeight,everyNthFrame:t?.everyNthFrame??u,maxFramesPerSecond:t?.maxFramesPerSecond??g,format:t?.format??f}}subscribe(t){if(this.subscribers.add(t),this.subscribers.size===1&&this.currentPage&&!this.isRunning&&this.start(this.currentPage).catch(()=>{}),this.lastFrame){const s=this.lastFrame;setImmediate(()=>{if(this.subscribers.has(t))try{t(s)}catch{}})}return()=>{this.subscribers.delete(t)}}async start(t){if(this.currentPage===t&&this.isRunning)return;this.currentPage&&this.currentPage!==t&&await this.stop(this.currentPage),this.currentPage=t;let s;try{s=await t.context().newCDPSession(t)}catch(e){throw this.isRunning=!1,new Error(`Failed to create CDPSession for screencast: ${e instanceof Error?e.message:String(e)}`)}this.currentSession=s,s.on("Page.screencastFrame",e=>{this.handleScreencastFrame(e,t)});try{await s.send("Page.startScreencast",{format:this.config.format,quality:this.config.quality,maxWidth:this.config.maxWidth,maxHeight:this.config.maxHeight,everyNthFrame:this.config.everyNthFrame,...this.config.maxFramesPerSecond?{maxFramesPerSecond:this.config.maxFramesPerSecond}:{}}),this.isRunning=!0}catch(e){try{await s.detach()}catch{}throw this.currentSession=null,this.isRunning=!1,new Error(`Failed to start screencast: ${e instanceof Error?e.message:String(e)}`)}}handleScreencastFrame(t,s){this.currentSession&&t.sessionId&&this.currentSession.send("Page.screencastFrameAck",{sessionId:t.sessionId}).catch(()=>{});const e=t.data??"",i=t.metadata?.deviceWidth??0,r=t.metadata?.deviceHeight??0;let a="";try{a=s.url()}catch{}const n={dataUrl:`data:image/${this.config.format==="png"?"png":"jpeg"};base64,${e}`,base64:e,width:i,height:r,timestamp:Date.now(),url:a};this.lastFrame=n;for(const c of this.subscribers)try{c(n)}catch{}}async stop(t){if(this.currentPage!==t)return;const s=this.currentSession;if(s){try{await s.send("Page.stopScreencast")}catch{}try{await s.detach()}catch{}}this.currentSession=null,this.isRunning=!1,this.currentPage=null}async stopAll(){this.currentPage&&await this.stop(this.currentPage)}async capture(t){if(this.lastFrame&&this.currentPage===t)return this.lastFrame;try{const e=(await t.screenshot({type:this.config.format==="png"?"png":"jpeg",quality:this.config.format==="png"?void 0:this.config.quality})).toString("base64"),i=`data:image/${this.config.format==="png"?"png":"jpeg"};base64,${e}`,r=t.viewportSize();return{dataUrl:i,base64:e,width:r?.width??0,height:r?.height??0,timestamp:Date.now(),url:t.url()}}catch{return}}getLastFrame(){return this.lastFrame}getCurrentCdpSession(){return this.currentSession??void 0}async restartAfterNavigation(t){if(this.currentPage===t)try{await this.stop(t)}catch{}let s=!1;for(let e=0;e<2;e++)try{await this.start(t),s=!0;break}catch{e===0&&await new Promise(i=>setTimeout(i,300))}s||(this.isRunning=!1,this.currentSession=null,this.currentPage=null),await this.forceCaptureAndPush(t).catch(()=>{})}async updateQuality(t){this.config={...this.config,quality:t.quality,maxWidth:t.maxWidth,maxHeight:t.maxHeight};const s=this.currentPage;if(!s||!this.isRunning)return!1;try{await this.stop(s)}catch{}try{await this.start(s)}catch{return!1}return this.forceCaptureAndPush(s).catch(()=>{}),!0}async forceCaptureAndPush(t){if(!t||t.isClosed())return;let s;const e=t.viewportSize(),i=e?.width??0,r=e?.height??0;if(this.currentSession)try{s=(await this.currentSession.send("Page.captureScreenshot",{format:this.config.format==="png"?"png":"jpeg",quality:this.config.format==="png"?void 0:this.config.quality})).data}catch{}if(!s)try{s=(await t.screenshot({type:this.config.format==="png"?"png":"jpeg",quality:this.config.format==="png"?void 0:this.config.quality})).toString("base64")}catch{return}let a="";try{a=t.url()}catch{}const n={dataUrl:`data:image/${this.config.format==="png"?"png":"jpeg"};base64,${s}`,base64:s,width:i,height:r,timestamp:Date.now(),url:a};this.lastFrame=n;for(const c of this.subscribers)try{c(n)}catch{}}async dispose(){await this.stopAll(),this.subscribers.clear(),this.lastFrame=void 0}}export{d as ScreencastManager};
1
+ import{encodeBrowserFrame as d,isIncrementalBinaryFrame as m}from"./browser-frame-encode.js";import{ScreencastDiffer as g}from"./screencast-differ.js";import{QUALITY_PRESETS as y}from"./types.js";const l=y.original,F=1,p="jpeg",w=30;class x{constructor(t){this.currentPage=null,this.currentSession=null,this.isRunning=!1,this.subscribers=new Set,this.differ=null,this.tileMode=!0,this.frameSeq=0,this.pipeline=Promise.resolve(),this.latestEvent=null,this.draining=!1,this.lastProcessStartedAt=0,this.seedCaptureInFlight=!1,this.lastDeviceWidth=0,this.lastDeviceHeight=0,this.lastGeometryWarnAt=0,this.config={quality:t?.quality??l.quality,maxWidth:t?.maxWidth??l.maxWidth,maxHeight:t?.maxHeight??l.maxHeight,everyNthFrame:t?.everyNthFrame??F,maxFramesPerSecond:t?.maxFramesPerSecond??w,format:t?.format??p,tileMode:t?.tileMode??!0},this.tileMode=this.config.tileMode,this.frameIntervalMs=1e3/Math.max(1,this.config.maxFramesPerSecond),this.tileMode&&(this.differ=new g)}enqueue(t){const e=this.pipeline.then(t);return this.pipeline=e.then(()=>{},()=>{}),e}subscribe(t){if(this.subscribers.add(t),this.subscribers.size===1&&this.currentPage&&!this.isRunning&&this.start(this.currentPage).catch(()=>{}),this.differ&&this.currentPage){let e=!1;if(this.lastFrame?.binaryFrame?m(this.lastFrame.binaryFrame)&&(this.lastFrame=void 0,this.differ.reset(),e=!0):this.tileMode&&(this.differ.reset(),e=!0),e&&!this.seedCaptureInFlight){const s=this.currentPage;this.seedCaptureInFlight=!0,this.forceCaptureAndPush(s).catch(()=>{}).finally(()=>{this.seedCaptureInFlight=!1})}}if(this.lastFrame){const e=this.lastFrame;setImmediate(()=>{if(this.subscribers.has(t))try{t(e)}catch{}})}return()=>{this.subscribers.delete(t)}}async start(t){if(this.currentPage===t&&this.isRunning)return;this.currentPage&&this.currentPage!==t&&await this.stop(this.currentPage),this.currentPage=t;let e;try{e=await t.context().newCDPSession(t)}catch(s){throw this.isRunning=!1,new Error(`Failed to create CDPSession for screencast: ${s instanceof Error?s.message:String(s)}`)}this.currentSession=e,e.on("Page.screencastFrame",s=>{this.handleScreencastFrame(s,t)});try{await e.send("Page.startScreencast",{format:this.config.format,quality:this.config.quality,maxWidth:this.config.maxWidth,maxHeight:this.config.maxHeight,everyNthFrame:this.config.everyNthFrame,...this.config.maxFramesPerSecond?{maxFramesPerSecond:this.config.maxFramesPerSecond}:{}}),this.isRunning=!0}catch(s){try{await e.detach()}catch{}throw this.currentSession=null,this.isRunning=!1,new Error(`Failed to start screencast: ${s instanceof Error?s.message:String(s)}`)}}handleScreencastFrame(t,e){if(this.ackFrame(t),e===this.currentPage){if(this.subscribers.size===0){this.lastFrame=void 0;return}this.latestEvent={event:t,page:e},this.draining||(this.draining=!0,this.drainPendingFrames())}}async drainPendingFrames(){try{for(;this.latestEvent;){const{event:t,page:e}=this.latestEvent;this.latestEvent=null;const s=this.frameIntervalMs-(Date.now()-this.lastProcessStartedAt);s>0&&await new Promise(i=>setTimeout(i,s)),this.lastProcessStartedAt=Date.now(),await this.enqueue(()=>this.processScreencastFrame(t,e))}}catch{}finally{this.draining=!1}}ackFrame(t){this.currentSession&&t.sessionId&&this.currentSession.send("Page.screencastFrameAck",{sessionId:t.sessionId}).catch(()=>{})}async processScreencastFrame(t,e){if(e!==this.currentPage)return;const s=t.data??"",i=t.metadata?.deviceWidth??0,r=t.metadata?.deviceHeight??0;let n="";try{n=e.url()}catch{}if(i>0&&r>0&&this.lastDeviceWidth>0&&this.lastDeviceHeight>0&&(i!==this.lastDeviceWidth||r!==this.lastDeviceHeight)){this.differ?.reset();const a=Date.now();a-this.lastGeometryWarnAt>=1e3&&(this.lastGeometryWarnAt=a,console.warn(`[browser-screencast] \u9875\u9762\u51E0\u4F55\u53D8\u5316 ${this.lastDeviceWidth}x${this.lastDeviceHeight} -> ${i}x${r}\uFF0C\u91CD\u7F6E\u5DEE\u5206\u57FA\u51C6`))}if(i>0&&r>0&&(this.lastDeviceWidth=i,this.lastDeviceHeight=r),this.tileMode&&this.differ&&s){const a=Buffer.from(s,"base64"),c=await this.differ.processFrame(a,i,r,this.config.quality);if(!c)return;const f=d(c,this.frameSeq++,{url:n}),o={dataUrl:"",base64:"",width:i,height:r,timestamp:Date.now(),url:n,binaryFrame:f};this.lastFrame=o;for(const u of this.subscribers)try{u(o)}catch{}return}const h={dataUrl:`data:image/${this.config.format==="png"?"png":"jpeg"};base64,${s}`,base64:s,width:i,height:r,timestamp:Date.now(),url:n};this.lastFrame=h;for(const a of this.subscribers)try{a(h)}catch{}}async stop(t){if(this.currentPage!==t)return;const e=this.currentSession;if(e){try{await e.send("Page.stopScreencast")}catch{}try{await e.detach()}catch{}}this.currentSession=null,this.isRunning=!1,this.currentPage=null,this.latestEvent=null,this.differ?.reset()}async stopAll(){this.currentPage&&await this.stop(this.currentPage)}async capture(t){if(this.lastFrame&&this.currentPage===t)return this.lastFrame;try{const s=(await t.screenshot({type:this.config.format==="png"?"png":"jpeg",quality:this.config.format==="png"?void 0:this.config.quality})).toString("base64"),i=`data:image/${this.config.format==="png"?"png":"jpeg"};base64,${s}`,r=t.viewportSize();return{dataUrl:i,base64:s,width:r?.width??0,height:r?.height??0,timestamp:Date.now(),url:t.url()}}catch{return}}getLastFrame(){return this.lastFrame}getCurrentCdpSession(){return this.currentSession??void 0}async restartAfterNavigation(t){if(this.currentPage===t)try{await this.stop(t)}catch{}let e=!1;for(let s=0;s<2;s++)try{await this.start(t),e=!0;break}catch{s===0&&await new Promise(i=>setTimeout(i,300))}e||(this.isRunning=!1,this.currentSession=null,this.currentPage=null),await this.forceCaptureAndPush(t).catch(()=>{})}async updateQuality(t){this.config={...this.config,quality:t.quality,maxWidth:t.maxWidth,maxHeight:t.maxHeight};const e=this.currentPage;if(!e||!this.isRunning)return!1;try{await this.stop(e)}catch{}try{await this.start(e)}catch{return!1}return this.forceCaptureAndPush(e).catch(()=>{}),!0}async forceCaptureAndPush(t){!t||t.isClosed()||await this.enqueue(()=>this.doForceCaptureAndPush(t))}async doForceCaptureAndPush(t){let e;const s=t.viewportSize(),i=s?.width??0,r=s?.height??0;if(this.currentSession)try{e=(await this.currentSession.send("Page.captureScreenshot",{format:this.config.format==="png"?"png":"jpeg",quality:this.config.format==="png"?void 0:this.config.quality})).data}catch{}if(!e)try{e=(await t.screenshot({type:this.config.format==="png"?"png":"jpeg",quality:this.config.format==="png"?void 0:this.config.quality})).toString("base64")}catch{return}let n="";try{n=t.url()}catch{}if(this.tileMode&&this.differ&&e){this.differ.reset();const a=Buffer.from(e,"base64"),c=await this.differ.processFrame(a,i,r,this.config.quality);if(c){const f=d(c,this.frameSeq++,{url:n}),o={dataUrl:"",base64:"",width:i,height:r,timestamp:Date.now(),url:n,binaryFrame:f};this.lastFrame=o;for(const u of this.subscribers)try{u(o)}catch{}return}}const h={dataUrl:`data:image/${this.config.format==="png"?"png":"jpeg"};base64,${e}`,base64:e,width:i,height:r,timestamp:Date.now(),url:n};this.lastFrame=h;for(const a of this.subscribers)try{a(h)}catch{}}async dispose(){await this.stopAll(),this.latestEvent=null,this.differ?.dispose(),this.differ=null,this.subscribers.clear(),this.lastFrame=void 0}}export{x as ScreencastManager};
2
2
 
@@ -1,8 +1,8 @@
1
1
 
2
2
  import type { Browser, BrowserContext, Page } from "playwright";
3
+ import type { PageListenerManager } from "./page-listener-manager.js";
3
4
  import type { ScreencastManager } from "./screencast-manager.js";
4
5
  import type { StepArchive } from "./step-archive.js";
5
- import type { PageListenerManager } from "./page-listener-manager.js";
6
6
 
7
7
  export declare const DEFAULT_BROWSER_ID = "default";
8
8
 
@@ -27,6 +27,8 @@ export interface ScreencastFrame {
27
27
  timestamp: number;
28
28
 
29
29
  url: string;
30
+
31
+ binaryFrame?: Buffer;
30
32
  }
31
33
 
32
34
  export type FrameSubscriber = (frame: ScreencastFrame) => void;
@@ -88,6 +90,8 @@ export interface ScreencastConfig {
88
90
  maxFramesPerSecond?: number;
89
91
 
90
92
  format?: "jpeg" | "png";
93
+
94
+ tileMode?: boolean;
91
95
  }
92
96
 
93
97
  export interface StepArchiveConfig {
@@ -190,3 +194,46 @@ export interface QualityPresetConfig {
190
194
  export declare const QUALITY_PRESETS: Record<QualityPreset, QualityPresetConfig>;
191
195
 
192
196
  export declare function isQualityPreset(value: unknown): value is QualityPreset;
197
+
198
+ export declare const TILE_DIFF_CONFIG: {
199
+
200
+ readonly tileSize: 160;
201
+
202
+ readonly sampleStep: 8;
203
+
204
+ readonly diffThreshold: 24;
205
+
206
+ readonly minChangedSamples: 1;
207
+
208
+ readonly fullFrameFallbackRatio: 0.5;
209
+
210
+ readonly encodeConcurrency: 8;
211
+ };
212
+
213
+ export interface ScreencastTileFrame {
214
+ kind: "tile";
215
+
216
+ width: number;
217
+
218
+ height: number;
219
+
220
+ tiles: Array<{
221
+ x: number;
222
+ y: number;
223
+ w: number;
224
+ h: number;
225
+ data: Buffer;
226
+ }>;
227
+ }
228
+
229
+ export interface ScreencastFullFrame {
230
+ kind: "full";
231
+
232
+ width: number;
233
+
234
+ height: number;
235
+
236
+ data: Buffer;
237
+ }
238
+
239
+ export type ScreencastDiffFrame = ScreencastTileFrame | ScreencastFullFrame;
@@ -1,2 +1,2 @@
1
- const r="default";function e(t,o){return`${t}::${o}`}function S(t){const o=t.indexOf("::");return o<0?null:{agentId:t.slice(0,o),browserId:t.slice(o+2)}}const _=8,n=4,E=5,s=0,x=0,i=7891,c="127.0.0.1",T=2*1024*1024,p=10*1e3,I=30*1e3,a=60*1e3,A={original:{quality:80,maxWidth:1280,maxHeight:800},hd:{quality:60,maxWidth:1024,maxHeight:640},smooth:{quality:45,maxWidth:800,maxHeight:500}};function M(t){return t==="original"||t==="hd"||t==="smooth"}export{r as DEFAULT_BROWSER_ID,a as IDLE_CHECK_INTERVAL_MS,x as IDLE_TIMEOUT_MS,E as MAX_PAGES_PER_SESSION,_ as MAX_SESSIONS,n as MAX_SESSIONS_PER_AGENT,A as QUALITY_PRESETS,s as SESSION_TIMEOUT_MS,p as WS_DEFAULT_HEARTBEAT_MS,I as WS_DEFAULT_IDLE_TIMEOUT_MS,T as WS_DEFAULT_MAX_SEND_BYTES,c as WS_HOST,i as WS_PORT,M as isQualityPreset,e as makeBrowserKey,S as parseBrowserKey};
1
+ const e="default";function r(t,o){return`${t}::${o}`}function n(t){const o=t.indexOf("::");return o<0?null:{agentId:t.slice(0,o),browserId:t.slice(o+2)}}const S=8,_=4,i=5,s=0,E=0,x=7891,c="127.0.0.1",a=2*1024*1024,p=10*1e3,T=30*1e3,I=60*1e3,l={original:{quality:80,maxWidth:1280,maxHeight:800},hd:{quality:60,maxWidth:1024,maxHeight:640},smooth:{quality:45,maxWidth:800,maxHeight:500}};function d(t){return t==="original"||t==="hd"||t==="smooth"}const A={tileSize:160,sampleStep:8,diffThreshold:24,minChangedSamples:1,fullFrameFallbackRatio:.5,encodeConcurrency:8};export{e as DEFAULT_BROWSER_ID,I as IDLE_CHECK_INTERVAL_MS,E as IDLE_TIMEOUT_MS,i as MAX_PAGES_PER_SESSION,S as MAX_SESSIONS,_ as MAX_SESSIONS_PER_AGENT,l as QUALITY_PRESETS,s as SESSION_TIMEOUT_MS,A as TILE_DIFF_CONFIG,p as WS_DEFAULT_HEARTBEAT_MS,T as WS_DEFAULT_IDLE_TIMEOUT_MS,a as WS_DEFAULT_MAX_SEND_BYTES,c as WS_HOST,x as WS_PORT,d as isQualityPreset,r as makeBrowserKey,n as parseBrowserKey};
2
2
 
@@ -1,7 +1,7 @@
1
1
 
2
2
  import type { BrowserPool } from "./browser-pool.js";
3
- import { type WsServerConfig } from "./types.js";
4
3
  import type { PageListenerInfo } from "./page-listener-types.js";
4
+ import { type WsServerConfig } from "./types.js";
5
5
 
6
6
  export declare class BrowserWsServer {
7
7
  private readonly pool;
@@ -1,2 +1,2 @@
1
- import{WebSocketServer as y,WebSocket as p}from"ws";import{DEFAULT_BROWSER_ID as f}from"./types.js";import{WS_DEFAULT_HEARTBEAT_MS as m,WS_DEFAULT_IDLE_TIMEOUT_MS as w,WS_DEFAULT_MAX_SEND_BYTES as k,WS_HOST as b,WS_PORT as v,isQualityPreset as _,QUALITY_PRESETS as I}from"./types.js";import{InputDispatcher as S}from"./input-dispatcher.js";import{installPickerOverlay as P,removePickerOverlay as C,pickElementAt as T,pickElementParent as J}from"./element-picker.js";const A="localhost";class E{constructor(e,r){this.wss=null,this.clients=new Set,this.mainHeartbeat=null,this.lastCursorQueryAt=0,this.lastCursor=null,this.pool=e,this.config={port:r?.port??v,host:r?.host??b,maxSendBytes:r?.maxSendBytes??k,idleTimeoutMs:r?.idleTimeoutMs??w,heartbeatMs:r?.heartbeatMs??m},this.inputDispatcher=new S(e),this.serverPort=this.config.port,this.pool.onSessionClose((t,s)=>{this.inputDispatcher.clearSession(t,s)})}start(){return this.wss?Promise.resolve({port:this.serverPort,host:this.config.host}):this.tryStart(this.config.port).catch(e=>{if(this.config.port===0)throw e;return console.warn(`[browser-ws] port ${this.config.port} bind failed (${e.message}); falling back to OS-assigned port`),this.tryStart(0)})}tryStart(e){return new Promise((r,t)=>{const s=new y({host:this.config.host,port:e,verifyClient:(o,i)=>{if(!o.origin){i(!0);return}/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(o.origin)?i(!0):i(!1,403,"origin not allowed")}});s.on("listening",()=>{const o=s.address(),i=typeof o=="object"&&o?o.port:e;this.serverPort=i,this.wss=s,this.attachHandlers(s),this.startMainHeartbeat(),r({port:i,host:this.config.host})}),s.on("error",o=>{this.wss||t(o)})})}async stop(){this.stopMainHeartbeat();for(const e of Array.from(this.clients))this.closeClient(e,1001,"server shutting down");this.wss&&(await new Promise(e=>{this.wss.close(()=>e())}),this.wss=null)}getPort(){return this.serverPort}getUrl(e,r){const t=this.config.host==="0.0.0.0"?A:this.config.host,s=r&&r.trim()?r.trim():f;return s===f?`ws://${t}:${this.serverPort}/agent/${encodeURIComponent(e)}`:`ws://${t}:${this.serverPort}/agent/${encodeURIComponent(e)}/${encodeURIComponent(s)}`}attachHandlers(e){e.on("connection",(r,t)=>{this.handleConnection(r,t).catch(()=>{try{r.close(1011,"internal error")}catch{}})})}async handleConnection(e,r){const t=new URL(r.url||"/","http://internal"),s=t.pathname.match(/^\/agent\/([^/]+)(?:\/([^/]+))?$/);if(!s){this.sendJson(e,{type:"error",message:`Invalid path; expected /agent/{agentId} or /agent/{agentId}/{browserId}, got ${t.pathname}`}),e.close(1008,"invalid path");return}const o=decodeURIComponent(s[1]),i=s[2]?decodeURIComponent(s[2]):f,n=this.pool.get(o,i);if(!n){this.sendJson(e,{type:"error",message:`Browser session not launched for ${o}/${i}. Call browser_launch first.`}),e.close(1008,"session not launched");return}const a={agentId:o,browserId:n.browserId,clientId:`${o}::${n.browserId}::${Date.now()}::${Math.random().toString(36).slice(2,8)}`,socket:e,heartbeatTimer:null,lastPongAt:Date.now(),unsubscribe:null},l=n.screencast.subscribe(u=>{this.sendFrame(e,u)});a.unsubscribe=l,e.on("pong",()=>{a.lastPongAt=Date.now()}),e.on("message",u=>{try{const d=u.toString("utf-8"),c=JSON.parse(d);if(c.type==="ping"){a.lastPongAt=Date.now(),this.sendJson(e,{type:"pong"});return}if(c.type==="input"&&typeof c.action=="string"){this.handleInputMessage(a,n,c.action,c);return}}catch{}}),e.on("close",()=>{this.closeClient(a,1e3,"client closed")}),e.on("error",()=>{this.closeClient(a,1011,"socket error")}),this.clients.add(a),this.sendJson(e,{type:"ready",agentId:o,browserId:n.browserId,viewport:{width:n.pages[n.activePageIndex]?.viewportSize()?.width??0,height:n.pages[n.activePageIndex]?.viewportSize()?.height??0},qualityPreset:n.qualityPreset})}async handleInputMessage(e,r,t,s){if(this.pool.touchActivity(e.agentId,e.browserId),t==="takeover"){const i=this.inputDispatcher.acquireTakeover(e.agentId,e.browserId,e.clientId);this.sendJson(e.socket,{type:"takeover_state",active:i?!0:r.humanTakeover,ok:i,error:i?void 0:"another client is taking over"});return}if(t==="release_takeover"){this.inputDispatcher.releaseTakeover(e.agentId,e.browserId,e.clientId),this.sendJson(e.socket,{type:"takeover_state",active:!1,ok:!0});return}if(t==="set_quality"){const i=s.preset;if(!_(i)){this.sendJson(e.socket,{type:"quality_changed",ok:!1,error:`invalid preset: ${String(s.preset)}`});return}const n=I[i];let a=!1;try{a=await r.screencast.updateQuality(n)}catch{a=!1}a&&(r.qualityPreset=i),this.sendJson(e.socket,{type:"quality_changed",preset:i,ok:a});return}if(t==="go_back"||t==="go_forward"||t==="reload"){if(!r.humanTakeover||r.takeoverClientId!==e.clientId){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:"not in takeover mode"});return}const i=r.pages[r.activePageIndex];if(!i||i.isClosed()){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:"active page is closed"});return}try{let n=!0,a="";try{a=i.url()}catch{}if(t==="go_back"?n=await i.goBack({waitUntil:"domcontentloaded",timeout:15e3}):t==="go_forward"?n=await i.goForward({waitUntil:"domcontentloaded",timeout:15e3}):await i.reload({waitUntil:"domcontentloaded",timeout:15e3}),n===null){let l="";try{l=i.url()}catch{}l&&l!==a&&(n=!0)}if(n===null&&t==="go_back"){const l=r.pages.indexOf(i);if(l>0){await this.pool.closePage(e.agentId,l,e.browserId);const u=r.pages[r.activePageIndex];u&&!u.isClosed()&&await r.screencast.forceCaptureAndPush(u).catch(()=>{}),this.sendJson(e.socket,{type:"input_result",ok:!0,action:t});return}}if(n===null){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:t==="go_back"?"no previous page in history":"no next page in history"});return}await new Promise(l=>setTimeout(l,200)),await r.screencast.restartAfterNavigation(i),this.sendJson(e.socket,{type:"input_result",ok:!0,action:t})}catch(n){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:n instanceof Error?n.message:String(n)})}return}if(t==="picker_start"||t==="picker_move"||t==="picker_stop"||t==="picker_parent"){if(!r.humanTakeover||r.takeoverClientId!==e.clientId){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:"not in takeover mode"});return}const i=r.pages[r.activePageIndex];if(!i||i.isClosed()){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:"active page is closed"});return}try{if(t==="picker_start")await P(i);else if(t==="picker_stop")await C(i);else if(t==="picker_parent"){const n=await J(i);this.sendJson(e.socket,{type:"picker_result",selector:n.selector,found:n.found,tag:n.tag,text:n.text})}else{const n=typeof s.x=="number"?s.x:0,a=typeof s.y=="number"?s.y:0,l=await T(i,n,a);this.sendJson(e.socket,{type:"picker_result",selector:l.selector,found:l.found,tag:l.tag,text:l.text})}this.sendJson(e.socket,{type:"input_result",ok:!0,action:t})}catch(n){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:n instanceof Error?n.message:String(n)})}return}const o=await this.inputDispatcher.dispatch(e.agentId,e.browserId,t,s);t==="mousemove"&&o.ok&&this.queryAndPushCursor(e,r,s),t==="press_key"&&o.ok&&typeof s.key=="string"&&(s.key==="Control+c"||s.key==="Meta+c")&&this.queryAndPushSelection(e,r),this.sendJson(e.socket,{type:"input_result",ok:o.ok,action:t,error:o.error})}async queryAndPushSelection(e,r){const t=r.pages[r.activePageIndex];if(!(!t||t.isClosed()))try{const s=await t.evaluate(()=>globalThis.getSelection?.()?.toString()??"");typeof s=="string"&&s.length>0&&this.sendJson(e.socket,{type:"selection_text",text:s})}catch{}}async queryAndPushCursor(e,r,t){const s=typeof t.x=="number"?t.x:void 0,o=typeof t.y=="number"?t.y:void 0;if(s===void 0||o===void 0)return;const i=Date.now();if(i-this.lastCursorQueryAt<60)return;this.lastCursorQueryAt=i;const n=r.pages[r.activePageIndex];if(!(!n||n.isClosed()))try{const a=await n.evaluate(l=>{const u=globalThis,d=u.document?.elementFromPoint(l.x,l.y);return d?u.window?.getComputedStyle(d)?.cursor??"default":"default"},{x:s,y:o});typeof a=="string"&&a!==this.lastCursor&&(this.lastCursor=a,this.sendJson(e.socket,{type:"cursor_changed",cursor:a}))}catch{}}sendFrame(e,r){if(e.readyState!==p.OPEN)return;const t=JSON.stringify({type:"frame",frame:r}),s=Buffer.byteLength(t,"utf-8");if(s>this.config.maxSendBytes){console.warn(`[browser-ws] frame payload ${s} bytes exceeds maxSendBytes ${this.config.maxSendBytes}; dropping`);return}try{e.send(t)}catch{}}sendJson(e,r){if(e.readyState===p.OPEN)try{e.send(JSON.stringify(r))}catch{}}closeClient(e,r,t){if(this.clients.has(e)){if(this.clients.delete(e),e.unsubscribe){try{e.unsubscribe()}catch{}e.unsubscribe=null}if(this.inputDispatcher.releaseTakeover(e.agentId,e.browserId,e.clientId),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.socket.readyState===p.OPEN)try{e.socket.close(r,t)}catch{}}}startMainHeartbeat(){this.mainHeartbeat||(this.mainHeartbeat=setInterval(()=>{const e=Date.now();for(const r of Array.from(this.clients)){if(r.socket.readyState===p.OPEN)try{r.socket.ping()}catch{}e-r.lastPongAt>this.config.idleTimeoutMs&&this.closeClient(r,1001,"idle timeout")}},this.config.heartbeatMs),this.mainHeartbeat.unref&&this.mainHeartbeat.unref())}stopMainHeartbeat(){this.mainHeartbeat&&(clearInterval(this.mainHeartbeat),this.mainHeartbeat=null)}broadcastListenerUpdate(e,r,t){const s={type:"listener_update",agentId:e,browserId:r,listeners:t};for(const o of this.clients)o.agentId===e&&o.browserId===r&&this.sendJson(o.socket,s)}broadcastListenerTriggered(e,r,t){const s={type:"listener_triggered",agentId:e,browserId:r,...t};for(const o of this.clients)o.agentId===e&&o.browserId===r&&this.sendJson(o.socket,s)}}let h=null;async function B(g,e){return h||(h=new E(g,e),await h.start()),h}async function O(){h&&(await h.stop(),h=null)}export{E as BrowserWsServer,B as getBrowserWsServer,O as stopBrowserWsServer};
1
+ import{WebSocketServer as m,WebSocket as p}from"ws";import{installPickerOverlay as w,removePickerOverlay as b,pickElementAt as k,pickElementParent as v}from"./element-picker.js";import{InputDispatcher as _}from"./input-dispatcher.js";import{DEFAULT_BROWSER_ID as f}from"./types.js";import{WS_DEFAULT_HEARTBEAT_MS as S,WS_DEFAULT_IDLE_TIMEOUT_MS as I,WS_DEFAULT_MAX_SEND_BYTES as P,WS_HOST as C,WS_PORT as T,isQualityPreset as A,QUALITY_PRESETS as J}from"./types.js";const $="localhost";class E{constructor(e,t){this.wss=null,this.clients=new Set,this.mainHeartbeat=null,this.lastCursorQueryAt=0,this.lastCursor=null,this.pool=e,this.config={port:t?.port??T,host:t?.host??C,maxSendBytes:t?.maxSendBytes??P,idleTimeoutMs:t?.idleTimeoutMs??I,heartbeatMs:t?.heartbeatMs??S},this.inputDispatcher=new _(e),this.serverPort=this.config.port,this.pool.onSessionClose((r,i)=>{this.inputDispatcher.clearSession(r,i)})}start(){return this.wss?Promise.resolve({port:this.serverPort,host:this.config.host}):this.tryStart(this.config.port).catch(e=>{if(this.config.port===0)throw e;return console.warn(`[browser-ws] port ${this.config.port} bind failed (${e.message}); falling back to OS-assigned port`),this.tryStart(0)})}tryStart(e){return new Promise((t,r)=>{const i=new m({host:this.config.host,port:e,verifyClient:(o,n)=>{if(!o.origin){n(!0);return}/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(o.origin)?n(!0):n(!1,403,"origin not allowed")}});i.on("listening",()=>{const o=i.address(),n=typeof o=="object"&&o?o.port:e;this.serverPort=n,this.wss=i,this.attachHandlers(i),this.startMainHeartbeat(),t({port:n,host:this.config.host})}),i.on("error",o=>{this.wss||r(o)})})}async stop(){this.stopMainHeartbeat();for(const e of Array.from(this.clients))this.closeClient(e,1001,"server shutting down");this.wss&&(await new Promise(e=>{this.wss.close(()=>e())}),this.wss=null)}getPort(){return this.serverPort}getUrl(e,t){const r=this.config.host==="0.0.0.0"?$:this.config.host,i=t&&t.trim()?t.trim():f;return i===f?`ws://${r}:${this.serverPort}/agent/${encodeURIComponent(e)}`:`ws://${r}:${this.serverPort}/agent/${encodeURIComponent(e)}/${encodeURIComponent(i)}`}attachHandlers(e){e.on("connection",(t,r)=>{this.handleConnection(t,r).catch(()=>{try{t.close(1011,"internal error")}catch{}})})}async handleConnection(e,t){const r=new URL(t.url||"/","http://internal"),i=r.pathname.match(/^\/agent\/([^/]+)(?:\/([^/]+))?$/);if(!i){this.sendJson(e,{type:"error",message:`Invalid path; expected /agent/{agentId} or /agent/{agentId}/{browserId}, got ${r.pathname}`}),e.close(1008,"invalid path");return}const o=decodeURIComponent(i[1]),n=i[2]?decodeURIComponent(i[2]):f,s=this.pool.get(o,n);if(!s){this.sendJson(e,{type:"error",message:`Browser session not launched for ${o}/${n}. Call browser_launch first.`}),e.close(1008,"session not launched");return}const a={agentId:o,browserId:s.browserId,clientId:`${o}::${s.browserId}::${Date.now()}::${Math.random().toString(36).slice(2,8)}`,socket:e,heartbeatTimer:null,lastPongAt:Date.now(),unsubscribe:null},l=s.screencast.subscribe(h=>{this.sendFrame(e,h)});a.unsubscribe=l,e.on("pong",()=>{a.lastPongAt=Date.now()}),e.on("message",h=>{try{const y=h.toString("utf-8"),d=JSON.parse(y);if(d.type==="ping"){a.lastPongAt=Date.now(),this.sendJson(e,{type:"pong"});return}if(d.type==="input"&&typeof d.action=="string"){this.handleInputMessage(a,s,d.action,d);return}}catch{}}),e.on("close",()=>{this.closeClient(a,1e3,"client closed")}),e.on("error",()=>{this.closeClient(a,1011,"socket error")}),this.clients.add(a),this.sendJson(e,{type:"ready",agentId:o,browserId:s.browserId,viewport:{width:s.pages[s.activePageIndex]?.viewportSize()?.width??0,height:s.pages[s.activePageIndex]?.viewportSize()?.height??0},qualityPreset:s.qualityPreset,takeover:s.humanTakeover===!0});const c=s.pages[s.activePageIndex];c&&!c.isClosed()&&s.screencast.forceCaptureAndPush(c).catch(()=>{})}async handleInputMessage(e,t,r,i){if(this.pool.touchActivity(e.agentId,e.browserId),r==="takeover"){const n=this.inputDispatcher.acquireTakeover(e.agentId,e.browserId,e.clientId);this.sendJson(e.socket,{type:"takeover_state",active:n?!0:t.humanTakeover,ok:n,error:n?void 0:"another client is taking over"});return}if(r==="release_takeover"){this.inputDispatcher.releaseTakeover(e.agentId,e.browserId,e.clientId),this.sendJson(e.socket,{type:"takeover_state",active:!1,ok:!0});return}if(r==="set_quality"){const n=i.preset;if(!A(n)){this.sendJson(e.socket,{type:"quality_changed",ok:!1,error:`invalid preset: ${String(i.preset)}`});return}const s=J[n];let a=!1;try{a=await t.screencast.updateQuality(s)}catch{a=!1}a&&(t.qualityPreset=n),this.sendJson(e.socket,{type:"quality_changed",preset:n,ok:a});return}if(r==="resync"){const n=t.pages[t.activePageIndex];n&&!n.isClosed()&&await t.screencast.forceCaptureAndPush(n);return}if(r==="go_back"||r==="go_forward"||r==="reload"){if(!t.humanTakeover||t.takeoverClientId!==e.clientId){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:"not in takeover mode"});return}const n=t.pages[t.activePageIndex];if(!n||n.isClosed()){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:"active page is closed"});return}try{let s=!0,a="";try{a=n.url()}catch{}if(r==="go_back"?s=await n.goBack({waitUntil:"domcontentloaded",timeout:15e3}):r==="go_forward"?s=await n.goForward({waitUntil:"domcontentloaded",timeout:15e3}):await n.reload({waitUntil:"domcontentloaded",timeout:15e3}),s===null){let l="";try{l=n.url()}catch{}l&&l!==a&&(s=!0)}if(s===null&&r==="go_back"){const l=t.pages.indexOf(n);if(l>0){await this.pool.closePage(e.agentId,l,e.browserId);const c=t.pages[t.activePageIndex];c&&!c.isClosed()&&await t.screencast.forceCaptureAndPush(c).catch(()=>{}),this.sendJson(e.socket,{type:"input_result",ok:!0,action:r});return}}if(s===null){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:r==="go_back"?"no previous page in history":"no next page in history"});return}await new Promise(l=>setTimeout(l,200)),await t.screencast.restartAfterNavigation(n),this.sendJson(e.socket,{type:"input_result",ok:!0,action:r})}catch(s){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:s instanceof Error?s.message:String(s)})}return}if(r==="picker_start"||r==="picker_move"||r==="picker_stop"||r==="picker_parent"){if(!t.humanTakeover||t.takeoverClientId!==e.clientId){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:"not in takeover mode"});return}const n=t.pages[t.activePageIndex];if(!n||n.isClosed()){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:"active page is closed"});return}try{if(r==="picker_start")await w(n);else if(r==="picker_stop")await b(n);else if(r==="picker_parent"){const s=await v(n);this.sendJson(e.socket,{type:"picker_result",selector:s.selector,found:s.found,tag:s.tag,text:s.text})}else{const s=typeof i.x=="number"?i.x:0,a=typeof i.y=="number"?i.y:0,l=await k(n,s,a);this.sendJson(e.socket,{type:"picker_result",selector:l.selector,found:l.found,tag:l.tag,text:l.text})}this.sendJson(e.socket,{type:"input_result",ok:!0,action:r})}catch(s){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:s instanceof Error?s.message:String(s)})}return}const o=await this.inputDispatcher.dispatch(e.agentId,e.browserId,r,i);r==="mousemove"&&o.ok&&this.queryAndPushCursor(e,t,i),r==="press_key"&&o.ok&&typeof i.key=="string"&&(i.key==="Control+c"||i.key==="Meta+c")&&this.queryAndPushSelection(e,t),this.sendJson(e.socket,{type:"input_result",ok:o.ok,action:r,error:o.error})}async queryAndPushSelection(e,t){const r=t.pages[t.activePageIndex];if(!(!r||r.isClosed()))try{const i=await r.evaluate(()=>globalThis.getSelection?.()?.toString()??"");typeof i=="string"&&i.length>0&&this.sendJson(e.socket,{type:"selection_text",text:i})}catch{}}async queryAndPushCursor(e,t,r){const i=typeof r.x=="number"?r.x:void 0,o=typeof r.y=="number"?r.y:void 0;if(i===void 0||o===void 0)return;const n=Date.now();if(n-this.lastCursorQueryAt<60)return;this.lastCursorQueryAt=n;const s=t.pages[t.activePageIndex];if(!(!s||s.isClosed()))try{const a=await s.evaluate(l=>{const c=globalThis,h=c.document?.elementFromPoint(l.x,l.y);return h?c.window?.getComputedStyle(h)?.cursor??"default":"default"},{x:i,y:o});typeof a=="string"&&a!==this.lastCursor&&(this.lastCursor=a,this.sendJson(e.socket,{type:"cursor_changed",cursor:a}))}catch{}}sendFrame(e,t){if(e.readyState!==p.OPEN)return;if(t.binaryFrame){const o=t.binaryFrame.length;if(o>this.config.maxSendBytes){console.warn(`[browser-ws] binary frame ${o} bytes exceeds maxSendBytes ${this.config.maxSendBytes}; dropping`);return}try{e.send(t.binaryFrame)}catch{}return}const r=JSON.stringify({type:"frame",frame:t}),i=Buffer.byteLength(r,"utf-8");if(i>this.config.maxSendBytes){console.warn(`[browser-ws] frame payload ${i} bytes exceeds maxSendBytes ${this.config.maxSendBytes}; dropping`);return}try{e.send(r)}catch{}}sendJson(e,t){if(e.readyState===p.OPEN)try{e.send(JSON.stringify(t))}catch{}}closeClient(e,t,r){if(this.clients.has(e)){if(this.clients.delete(e),e.unsubscribe){try{e.unsubscribe()}catch{}e.unsubscribe=null}if(this.inputDispatcher.releaseTakeover(e.agentId,e.browserId,e.clientId),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.socket.readyState===p.OPEN)try{e.socket.close(t,r)}catch{}}}startMainHeartbeat(){this.mainHeartbeat||(this.mainHeartbeat=setInterval(()=>{const e=Date.now();for(const t of Array.from(this.clients)){if(t.socket.readyState===p.OPEN)try{t.socket.ping()}catch{}e-t.lastPongAt>this.config.idleTimeoutMs&&this.closeClient(t,1001,"idle timeout")}},this.config.heartbeatMs),this.mainHeartbeat.unref&&this.mainHeartbeat.unref())}stopMainHeartbeat(){this.mainHeartbeat&&(clearInterval(this.mainHeartbeat),this.mainHeartbeat=null)}broadcastListenerUpdate(e,t,r){const i={type:"listener_update",agentId:e,browserId:t,listeners:r};for(const o of this.clients)o.agentId===e&&o.browserId===t&&this.sendJson(o.socket,i)}broadcastListenerTriggered(e,t,r){const i={type:"listener_triggered",agentId:e,browserId:t,...r};for(const o of this.clients)o.agentId===e&&o.browserId===t&&this.sendJson(o.socket,i)}}let u=null;async function O(g,e){return u||(u=new E(g,e),await u.start()),u}async function F(){u&&(await u.stop(),u=null)}export{E as BrowserWsServer,O as getBrowserWsServer,F as stopBrowserWsServer};
2
2
 
@@ -1,2 +1,8 @@
1
- import{promises as p}from"node:fs";import k from"node:path";import{Router as l}from"express";import{getAcodeDataHome as A}from"../config.js";import{validateToken as c}from"../middleware/auth.js";import{createLogger as I}from"../utils/logger.js";import{listPtySessions as y}from"./pty.js";const u=I("acode-bg-tasks"),g=l();function S(r){return String(r).replace(/[^a-zA-Z0-9_-]/g,"_")}function w(r){return k.join(A(),"bg-tasks",S(r),"state.json")}g.get("/acode-bg-tasks/:agentId",c,async(r,s)=>{const o=r.params.agentId,e=Array.isArray(o)?o[0]:o;if(!e||!String(e).trim()){s.status(400).json({ok:!1,error:"agentId is required"});return}const d=w(String(e));try{const a=await p.readFile(d,"utf-8"),n=JSON.parse(a),m=(Array.isArray(n.tasks)?n.tasks:[]).map(f=>{const t=f;return{taskId:t.taskId,status:t.status,command:t.commandPreview??t.command,commandPreview:t.commandPreview,cwd:t.cwd,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes}});s.json({ok:!0,agentId:String(e),updatedAt:typeof n.updatedAt=="number"?n.updatedAt:null,tasks:m})}catch(a){if((typeof a=="object"&&a!==null?Reflect.get(a,"code"):void 0)==="ENOENT"){s.json({ok:!0,agentId:String(e),updatedAt:null,tasks:[]});return}if(a instanceof SyntaxError){u.warn("[acode-bg-tasks] \u6587\u4EF6\u635F\u574F\uFF08JSON \u89E3\u6790\u5931\u8D25\uFF09: %s",d),s.json({ok:!0,agentId:String(e),updatedAt:null,tasks:[]});return}const i=a;u.error("[acode-bg-tasks] \u8BFB\u53D6\u5931\u8D25: %s",i.message),s.status(500).json({ok:!1,error:i.message})}}),g.get("/acode-agent-pty-sessions/:agentId",c,(r,s)=>{const o=r.params.agentId,e=Array.isArray(o)?o[0]:o;if(!e||!String(e).trim()){s.status(400).json({ok:!1,error:"agentId is required"});return}const d=String(e),n=y().filter(i=>i.ownerUserId===d);s.json({ok:!0,agentId:d,sessions:n})});export{g as backgroundTasksRouter};
1
+ import{promises as y}from"node:fs";import p from"node:path";import{pathToFileURL as b}from"node:url";import{Router as C}from"express";import{getAcodeDataHome as j}from"../config.js";import{validateToken as m}from"../middleware/auth.js";import{createLogger as B}from"../utils/logger.js";import{getReleasedAcodeEntryPath as T}from"../services/acode-package.js";import{getAcodeBgManager as h}from"../services/acode-bg-manager.js";import{listPtySessions as E}from"./pty.js";let f=null;function I(){return f||(f=(async()=>{try{const e=T(),t=p.join(p.dirname(e),"background-command-store.js"),r=await import(b(t).href);return r.BackgroundCommandStore?new r.BackgroundCommandStore:(g.warn("[acode-bg-tasks] BackgroundCommandStore \u4E0D\u53EF\u7528,\u78C1\u76D8\u515C\u5E95\u7981\u7528"),null)}catch(e){return g.warn("[acode-bg-tasks] \u52A0\u8F7D BackgroundCommandStore \u5931\u8D25: %s",e?.message),null}})().then(e=>(e||(f=null),e)),f)}const g=B("acode-bg-tasks"),k=C();function _(e){return String(e).replace(/[^a-zA-Z0-9_-]/g,"_")}function L(e){return p.join(j(),"bg-tasks",_(e),"state.json")}k.get("/acode-bg-tasks/:agentId",m,async(e,t)=>{const r=e.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const d=L(String(i));try{const n=await y.readFile(d,"utf-8"),s=JSON.parse(n),c=(Array.isArray(s.tasks)?s.tasks:[]).map(o=>{const u=o;return{taskId:u.taskId,status:u.status,command:u.commandPreview??u.command,commandPreview:u.commandPreview,cwd:u.cwd,startedAt:u.startedAt,finishedAt:u.finishedAt,exitCode:u.exitCode,signal:u.signal,outputLineCount:u.outputLineCount,outputBytes:u.outputBytes}});t.json({ok:!0,agentId:String(i),updatedAt:typeof s.updatedAt=="number"?s.updatedAt:null,tasks:c})}catch(n){if((typeof n=="object"&&n!==null?Reflect.get(n,"code"):void 0)==="ENOENT"){t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}if(n instanceof SyntaxError){g.warn("[acode-bg-tasks] \u6587\u4EF6\u635F\u574F\uFF08JSON \u89E3\u6790\u5931\u8D25\uFF09: %s",d),t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}const a=n;g.error("[acode-bg-tasks] \u8BFB\u53D6\u5931\u8D25: %s",a.message),t.status(500).json({ok:!1,error:a.message})}}),k.get("/acode-agent-pty-sessions/:agentId",m,(e,t)=>{const r=e.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const d=String(i),s=E().filter(a=>a.ownerUserId===d);t.json({ok:!0,agentId:d,sessions:s})});const v=15e3;function S(e){return{taskId:e.taskId,status:e.status,exitCode:e.exitCode,signal:e.signal,startedAt:e.startedAt,finishedAt:e.finishedAt,command:e.command,lines:e.lines,nextSeq:e.nextSeq,totalLines:e.totalLines,totalBytes:e.totalBytes,truncated:e.truncated}}async function x(e,t){let i=null;try{i=await y.open(e,"r")}catch(d){if(d?.code==="ENOENT")return{text:[],fileExists:!1,truncated:!1};throw d}try{const n=(await i.stat()).size;if(n===0)return{text:[],fileExists:!0,truncated:!1};const s=Math.min(n,2097152),a=n-s,c=Buffer.alloc(s);await i.read(c,0,s,a);const u=c.toString("utf-8").split(`
2
+ `);return a>0&&u.shift(),{text:u.slice(-t),fileExists:!0,truncated:s<n}}finally{await i.close()}}k.get("/acode-bg-tasks/:agentId/:taskId/output",m,(e,t)=>{(async()=>{const r=String(e.params.agentId),i=String(e.params.taskId),d=Math.min(2e3,Math.max(100,Number(e.query.tailLines)||500));try{const n=await h();if(n){const s=n.getTaskOutputSnapshot(r,i);if(s){t.json({ok:!0,source:"memory",...S(s)});return}}}catch(n){g.warn("[acode-bg-tasks] \u5185\u5B58\u5FEB\u7167\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",n?.message)}try{const n=await I();if(!n){t.status(503).json({ok:!1,error:"store unavailable"});return}const s=await n.readSession(r);if(!s){t.status(404).json({ok:!1,error:"task_not_found"});return}const a=s.tasks.find(l=>l.taskId===i);if(!a){t.status(404).json({ok:!1,error:"task_not_found"});return}const{text:c,fileExists:o,truncated:u}=await x(a.outputPath,d);t.json({ok:!0,source:"file",taskId:a.taskId,status:a.status,exitCode:a.exitCode,signal:a.signal,startedAt:a.startedAt,finishedAt:a.finishedAt,command:a.command,lines:c,nextSeq:0,totalLines:a.outputLineCount,totalBytes:a.outputBytes,truncated:u||!o})}catch(n){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95\u8BFB\u53D6\u5931\u8D25: %s",n?.message),t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] output \u8BFB\u53D6\u5931\u8D25: %s",r?.message),t.status(500).json({ok:!1,error:"internal_error"})})}),k.get("/acode-bg-tasks/:agentId/:taskId/stream",m,(e,t)=>{(async()=>{const r=String(e.params.agentId),i=String(e.params.taskId);t.status(200),t.set({"Content-Type":"text/event-stream","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.flushHeaders?.();const d=(a,c)=>{t.writableEnded||t.destroyed||(c!==void 0&&t.write(`id: ${c}
3
+ `),t.write(`data: ${JSON.stringify(a)}
4
+
5
+ `))};let n,s=null;try{n=await h(),n&&(s=n.getTaskOutputSnapshot(r,i))}catch(a){g.warn("[acode-bg-tasks] \u5185\u5B58\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",a?.message)}if(s){if(d({type:"snapshot",source:"memory",...S(s)}),s.status!=="running"){d({type:"done",status:s.status,exitCode:s.exitCode,signal:s.signal}),t.end();return}let a=null,c=!1;const o=()=>{c||(c=!0,a?.(),clearInterval(u))},u=setInterval(()=>{if(t.writableEnded||t.destroyed){o();return}try{t.write(`: ping
6
+
7
+ `)}catch{o()}},v);if(u.unref?.(),a=n.subscribeTaskOutput(r,i,{onLine:l=>d({type:"output",stream:l.stream,text:l.text,seq:l.seq},l.seq),onDone:l=>{d({type:"done",status:l.status,exitCode:l.exitCode,signal:l.signal}),o(),t.end()}}),!a){d({type:"done",status:s.status,exitCode:s.exitCode,signal:s.signal}),o(),t.end();return}e.on("close",()=>{o(),t.writableEnded||t.end()}),e.on("aborted",()=>o());return}try{const a=await I();if(!a){d({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const c=await a.readSession(r);if(!c){d({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const o=c.tasks.find(w=>w.taskId===i);if(!o){d({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const{text:u,fileExists:l,truncated:A}=await x(o.outputPath,500);d({type:"snapshot",source:"file",taskId:o.taskId,status:o.status,exitCode:o.exitCode,signal:o.signal,startedAt:o.startedAt,finishedAt:o.finishedAt,command:o.command,lines:u,nextSeq:0,totalLines:o.outputLineCount,totalBytes:o.outputBytes,truncated:A||!l}),d({type:"done",status:o.status,exitCode:o.exitCode,signal:o.signal}),t.end()}catch(a){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95 stream \u5931\u8D25: %s",a?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] stream \u5EFA\u7ACB\u5931\u8D25: %s",r?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})})});export{k as backgroundTasksRouter};
2
8
 
@@ -0,0 +1,4 @@
1
+
2
+ export declare const PANEL_TOOL_STATUS_KEYS: readonly ["acode", "claude", "opencode", "codex"];
3
+
4
+ export declare function buildToolStatusDetectionTargets(enabledTools: string[]): string[];
@@ -0,0 +1,2 @@
1
+ const t=["acode","claude","opencode","codex"];function r(e){return Array.from(new Set([...e,...t].map(o=>String(o||"").trim().toLowerCase()).filter(Boolean)))}export{t as PANEL_TOOL_STATUS_KEYS,r as buildToolStatusDetectionTargets};
2
+
@@ -34,8 +34,6 @@ export declare function buildSchedulerPingFailureResponse(error: Error, configur
34
34
 
35
35
  export declare function buildAmbiguousSchedulerBaseUrlResponse(targetUrls: string[]): AmbiguousSchedulerBaseUrlResponse;
36
36
 
37
- export declare function buildToolStatusDetectionTargets(enabledTools: string[]): string[];
38
-
39
37
  export declare function buildUninstallFailureMessage(requestedTools: string[], toolStatus: Record<string, ToolInstallStatus>): string;
40
38
  export declare function summarizeToolStatus(toolStatus: Record<string, ToolInstallStatus>): Record<string, Pick<ToolInstallStatus, "installed" | "version" | "executable" | "error">>;
41
39
  export {};
@@ -1,2 +1,2 @@
1
- import{Router as H}from"express";import A from"axios";import{spawn as E}from"node:child_process";import{createHash as W,timingSafeEqual as G}from"node:crypto";import{fileURLToPath as V}from"node:url";import k from"node:path";import{adapterRegistry as J}from"../adapter/index.js";import{getBridgeVersion as R,resolveSchedulerBaseUrlFrom as Y}from"../config.js";import{validateToken as S,validateTokenOrPanelSession as v}from"../middleware/auth.js";import{getConfiguredConnectionKeys as _,getRegistrationState as Q,requestRuntimeAccessTokenRefreshForServer as Z,resolveSchedulerBaseUrlFromToken as ee}from"../services/auto-register.js";import{discoverCcSwitchConfiguredItems as te,loadCcSwitchSdk as re}from"../services/cc-switch-sdk.js";import{initInstance as se,syncLegacyStateFromSdk as oe}from"../services/instance-init-service.js";import{setAgentFeatures as ne,getAgentFeatures as x}from"../services/instance-features.js";import{createBridgeLifecycleOperation as L,deliverPendingBridgeLifecycleNotifications as C,loadLifecycleState as ae,removeBridgeLifecycleOperation as ie,upsertBridgeLifecycleOperation as b}from"../services/lifecycle-state.js";import{appendUpdateLog as p,readUpdateLog as ce}from"../services/update-log.js";import{checkBridgeUpdate as O}from"../utils/npm-version.js";import{loadInstanceState as $,saveInstanceState as P}from"../services/instance-state.js";import{getRuntimeAccessToken as le}from"../services/runtime-binding.js";import{detectToolStatuses as U,ensureToolsInstalled as de,SUPPORTED_INSTALLABLE_TOOLS as j,SUPPORTED_UNINSTALLABLE_TOOLS as q,uninstallTools as ue}from"../services/tool-installer.js";import{createLogger as ge}from"../utils/logger.js";const a=ge("instance"),fe=["acode","claude","opencode","codex"];let I=null;function We(r){I=r}const d=H();function F(r){return String(r||"").trim()||void 0}function D(r){const t=r.get("host");if(t)return`${r.protocol}://${t}`}function N(){const r=k.resolve(k.dirname(V(import.meta.url)),"..","..");return k.join(r,"dist","index.js")}function pe(){return new Promise((r,t)=>{const e=E(process.execPath,[N(),"update","--no-kill"],{shell:!1,windowsHide:!0});let s="",o="";e.stdout.on("data",n=>{s+=n.toString("utf8")}),e.stderr.on("data",n=>{o+=n.toString("utf8")}),e.on("error",t),e.on("close",n=>{r({exitCode:n,stdout:s,stderr:o})})})}function K(){const r=E(process.execPath,[N(),"service","restart"],{shell:!1,windowsHide:!0,stdio:["ignore","pipe","pipe"]});r.stdout.on("data",t=>{p(String(t)).catch(()=>{})}),r.stderr.on("data",t=>{p(String(t)).catch(()=>{})})}const M=3e3;async function me(r){const t={...r,status:"running",message:"Bridge \u66F4\u65B0\u6B63\u5728\u540E\u53F0\u6267\u884C\u3002"};await b(t);try{a.info("[update-bridge] \u6B63\u5728\u540E\u53F0\u6267\u884C awsb update --yes"),await p(`===== Bridge \u66F4\u65B0\u5F00\u59CB\uFF1Av${R()} -> latest\uFF08\u64CD\u4F5C ${r.operationId}\uFF09 =====`);const e=await pe();if(e.stdout&&await p(e.stdout),e.stderr&&await p(e.stderr),e.exitCode===0){await p("===== Bridge \u66F4\u65B0\u6210\u529F\uFF0C\u51C6\u5907\u81EA\u52A8\u91CD\u542F ===== ");const s={...t,status:"restarting",message:"Bridge \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u542F..."};await b(s),a.info("[update-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("BRIDGE_UPDATE_RESTART",!0).catch(o=>{const n=o;a.error("[update-bridge] graceful shutdown failed:",n),process.exit(42)});return}process.exit(42)},M)}else await p(`===== Bridge \u66F4\u65B0\u5931\u8D25\uFF1A\u9000\u51FA\u7801 ${e.exitCode??"unknown"} =====`),await b({...t,status:"failed",message:`Bridge \u66F4\u65B0\u5931\u8D25\uFF0Cnpm \u9000\u51FA\u7801: ${e.exitCode??"unknown"}`,error:e.stderr||e.stdout}),await C()}catch(e){const s=e;a.error("[update-bridge] background update failed:",s),await p(`===== Bridge \u66F4\u65B0\u5F02\u5E38\uFF1A${s.message} =====`).catch(()=>{}),await b({...t,status:"failed",message:"Bridge \u66F4\u65B0\u5931\u8D25\u3002",error:s.message||"update bridge failed"}),await C()}}async function z(r){const t=await import("node:fs/promises"),e=await import("node:path"),s=await import("node:os"),o=e.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");if(r){await t.mkdir(e.dirname(o),{recursive:!0}),await t.writeFile(o,JSON.stringify({preserveSessions:!0,timestamp:new Date().toISOString()}),"utf-8"),a.info(`[prepare-restart] \u5DF2\u521B\u5EFA\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6: ${o}`);return}try{await t.unlink(o),a.info("[prepare-restart] \u5DF2\u5220\u9664\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6")}catch{}}function he(r){return W("md5").update(r,"utf8").digest("hex")}function Se(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 o=Buffer.from(he(s),"hex");return o.length===e.length&&G(o,e)})}function we(r){const e=_().length>0,s=R();return e?Se(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 be(r,t){const s=t.trim()||"http://localhost:8080",o=/^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:o?"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 ye(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 Ie(r,t){try{const e=await A.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":t}});return{schedulerReachable:e.status>=200&&e.status<300,tokenRefreshed:!1}}catch(e){if(ye(e)!==401)throw e;const s=await Z(r);if(!s.success||!s.runtimeAccessToken)throw e;const o=await A.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":s.runtimeAccessToken}});return{schedulerReachable:o.status>=200&&o.status<300,tokenRefreshed:!0}}}function Be(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 Te(r){return Array.from(new Set([...r,...fe].map(t=>String(t||"").trim().toLowerCase()).filter(Boolean)))}function ke(r,t){const e=r.filter(s=>t[s]?.installed);return e.length===0?"":e.map(s=>{const o=t[s],n=o?.error||o?.executable||"command is still available";return`${s}: ${n}`}).join("; ")}function B(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}]))}d.get("/check-update",async(r,t)=>{t.json(await O())}),d.get("/update-log",v,async(r,t)=>{try{const e=await ce();t.json({ok:!0,...e})}catch(e){const s=e;a.error("[update-log] Error:",s),t.status(500).json({ok:!1,error:s.message||"read update log failed"})}}),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=we(e);t.status(s.status).json(s.body)}),d.get("/ping",S,async(r,t)=>{let e=Y(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 o=ee(r.header("X-Runtime-Token"));if(o)e={url:o,source:"token-match",ambiguousTargetUrls:[]};else{const n=r.header("X-Bridge-Instance-Id");if(n){const i=Q(),c=Object.entries(i.registrations).find(([,l])=>l===n)?.[0];c&&(e={url:c,source:"instance-id-match",ambiguousTargetUrls:[]})}if(!e.url||e.source==="ambiguous-auto-register-targets"){t.status(400).json(Be(e.ambiguousTargetUrls));return}}}const s=e.url;try{const o=le(s);if(!o){t.status(401).json({ok:!1,error:"runtime_access_token_required"});return}const n=await Ie(s,o);t.json({ok:!0,runtimeBridge:"healthy",version:R(),schedulerReachable:n.schedulerReachable,tokenRefreshed:n.tokenRefreshed,schedulerBaseUrl:s,instanceId:e.instanceId,instanceName:e.instanceName})}catch(o){const n=o;t.status(502).json(be(n,s))}}),d.post("/init-instance",S,async(r,t)=>{const{agentId:e,workspacePath:s,skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m}=r.body||{};if(!e||!s){t.status(400).json({error:"agentId and workspacePath are required"});return}try{a.info("[init-instance] Request received",{agentId:String(e),workspacePath:String(s),skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i});const u=await se(e,s,{skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m});a.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;a.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",S,async(r,t)=>{const{agentId:e,features:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}ne(String(e),s),a.info("[instance/features] \u80FD\u529B\u5F00\u5173\u5DF2\u66F4\u65B0",{agentId:String(e),features:x(String(e))});let o=0;for(const n of J.getAll())if(typeof n.applyAgentFeatures=="function")try{await n.applyAgentFeatures(String(e)),o+=1}catch(i){a.error("[instance/features] adapter \u70ED\u540C\u6B65\u5931\u8D25",{agentId:String(e),providerId:n.providerId,error:i?.message||String(i)})}t.json({ok:!0,agentId:String(e),features:x(String(e)),hotSyncedAdapters:o})}),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{a.info("[cc-switch/state] Request received",{agentId:String(e)});const s=await re(e),o=await te(s),i=(await oe(e,s)).state,c=Te(i.enabledTools||[]);a.info("[cc-switch/state] Detecting SDK statuses",{agentId:String(e),detectionTargets:c});const l=await U(c);a.info("[cc-switch/state] SDK status detection completed",{agentId:String(e),toolStatus:B(l),importedMcpCount:o.mcpServers,importedSkillCount:o.skills}),t.json({ok:!0,agentId:String(e),imported:{mcpServers:o.mcpServers,skills:o.skills},state:{...i,toolStatus:l}})}catch(s){const o=s;a.error("[cc-switch/state] Failed to load state",{agentId:String(e),error:o?.message||String(s),stack:o?.stack||null}),t.status(400).json({error:o?.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 o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[];a.info("[cc-switch/install-tools] Request received",{agentId:String(e),requestedTools:o});const n=new Set(j),i=o.filter(c=>n.has(c));if(a.info("[cc-switch/install-tools] Request normalized",{agentId:String(e),requestedTools:o,installableTools:i,supportedInstallableTools:j}),i.length===0){a.warn("[cc-switch/install-tools] No supported installable tools requested",{agentId:String(e),requestedTools:o}),t.status(400).json({error:`tools must include ${j.join(", ")}`});return}try{const c=await $(e),l=Array.from(new Set([...c.enabledTools||[],...i]));a.info("[cc-switch/install-tools] Starting installer",{agentId:String(e),installableTools:i,previousEnabledTools:c.enabledTools||[],nextEnabledTools:l});const m=await de(i);a.info("[cc-switch/install-tools] Installer completed",{agentId:String(e),toolStatus:B(m)});const u=await U(l);a.info("[cc-switch/install-tools] Refreshed enabled SDK statuses",{agentId:String(e),refreshedToolStatus:B(u)});const g={...u,...m},h=await P(e,{...c,enabledTools:l,toolStatus:g}),y=new Set(i),w=Object.entries(g).filter(([f])=>y.has(f)).filter(([,f])=>!f.installed).map(([f,T])=>({tool:f,error:T.error||null}));w.length>0&&a.warn("[cc-switch/install-tools] Some SDKs remain unavailable",{agentId:String(e),failedTools:w}),a.info("[cc-switch/install-tools] State saved",{agentId:String(e),enabledTools:h.enabledTools,toolStatus:B(g)}),t.json({ok:w.length===0,agentId:String(e),toolStatus:g,failedTools:w,state:h})}catch(c){const l=c;a.error("[cc-switch/install-tools] Install route failed",{agentId:String(e),requestedTools:o,installableTools:i,error:l?.message||String(c),stack:l?.stack||null}),t.status(400).json({error:l?.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 o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[],n=new Set(q),i=o.filter(c=>n.has(c));if(i.length===0){t.status(400).json({error:`tools must include ${q.join(", ")}`});return}try{const c=await $(e),l=new Set(i),m=(c.enabledTools||[]).filter(T=>!l.has(String(T||"").trim().toLowerCase())),u=await ue(i),h={...await U([...m,...i]),...u},y=ke(i,h),w=y?c.enabledTools||[]:m,f=await P(e,{...c,enabledTools:w,toolStatus:h});if(y){t.status(400).json({ok:!1,agentId:String(e),error:`uninstall incomplete: ${y}`,toolStatus:h,state:f});return}t.json({ok:!0,agentId:String(e),toolStatus:h,state:f})}catch(c){const l=c;t.status(400).json({error:l?.message||"uninstall tools failed"})}}),d.post("/prepare-restart",S,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{};try{a.info(`[prepare-restart] \u6536\u5230\u51C6\u5907\u91CD\u542F\u901A\u77E5\uFF0CpreserveSessions=${e}`),await z(!!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 o=s;a.error("[prepare-restart] Error:",o),t.status(500).json({error:o.message})}});const Re={update:15*60*1e3,restart:5*60*1e3};async function ve(r){try{const t=await ae(),e=Date.now(),s=Re[r],o=[];for(const n of t.operations){if(n.operationType!==r||!["accepted","running","restarting"].includes(n.status))continue;const i=new Date(n.updatedAt).getTime();if(!Number.isNaN(i)&&e-i<=s)return{operationId:n.operationId};o.push(n.operationId)}for(const n of o)try{await ie(n),a.info(`[${r}-bridge] \u5DF2\u6E05\u7406\u8D85\u65F6\u672A\u4EA4\u4ED8\u7684\u9648\u65E7\u64CD\u4F5C: ${n}`)}catch(i){const c=i;a.warn(`[${r}-bridge] \u6E05\u7406\u9648\u65E7\u64CD\u4F5C\u5931\u8D25\uFF0C\u7EE7\u7EED\u653E\u884C: ${n}, ${c.message}`)}return null}catch(t){const e=t;return a.warn(`[${r}-bridge] \u5728\u9014\u64CD\u4F5C\u68C0\u67E5\u5931\u8D25\uFF0C\u653E\u884C\u8BF7\u6C42: ${e.message}`),null}}async function X(r,t,e){const s=await ve(r);return s?(e.status(409).json({error:t,operationId:s.operationId}),!0):!1}d.post("/update-bridge",v,async(r,t)=>{const e=F(r.body?.requestedByUserId);try{if(!e){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("update","\u5B9E\u4F8B Bridge \u66F4\u65B0\u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u89E6\u53D1",t))return;const s=await O(),o=L({operationType:"update",requestedByUserId:e,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(r),status:"accepted",message:"Bridge \u66F4\u65B0\u8BF7\u6C42\u5DF2\u6536\u5230\uFF0C\u5C06\u5728\u540E\u53F0\u6267\u884C\u5E76\u81EA\u52A8\u91CD\u542F\u3002",latestVersion:s.ok&&s.latestVersion||void 0});await b(o),me(o),t.json({ok:!0,operationId:o.operationId,message:"Bridge \u66F4\u65B0\u5DF2\u5728\u540E\u53F0\u5F00\u59CB\uFF0C\u66F4\u65B0\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u91CD\u542F\u5B9E\u4F8B Bridge\u3002"})}catch(s){const o=s;a.error("[update-bridge] Error:",o),t.status(500).json({error:o.message||"update bridge failed"})}}),d.post("/restart-bridge",v,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{},s=F(r.body?.requestedByUserId);try{if(!s){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("restart","\u5B9E\u4F8B Bridge \u6B63\u5728\u91CD\u542F\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5",t))return;a.info(`[restart-bridge] \u6536\u5230\u5B9E\u4F8B Bridge \u91CD\u542F\u8BF7\u6C42\uFF0CpreserveSessions=${e}`);const o=L({operationType:"restart",requestedByUserId:s,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(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(o),await z(!!e),t.json({ok:!0,operationId:o.operationId,preserveSessions:!!e,message:"Bridge \u6B63\u5728\u91CD\u542F\uFF0C\u670D\u52A1\u7BA1\u7406\u5668\u4F1A\u81EA\u52A8\u62C9\u8D77\u5B9E\u4F8B\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002"}),a.info("[restart-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("PANEL_RESTART",!!e).catch(n=>{const i=n;a.error("[restart-bridge] graceful shutdown failed:",i),process.exit(42)});return}process.exit(42)},M)}catch(o){const n=o;a.error("[restart-bridge] Error:",n),t.status(500).json({error:n.message||"restart bridge failed"})}});export{Be as buildAmbiguousSchedulerBaseUrlResponse,we as buildConnectionCheckResponse,be as buildSchedulerPingFailureResponse,Te as buildToolStatusDetectionTargets,ke as buildUninstallFailureMessage,d as instanceRouter,We as setGracefulShutdownFn,B as summarizeToolStatus};
1
+ import{Router as H}from"express";import E from"axios";import{spawn as A}from"node:child_process";import{createHash as W,timingSafeEqual as G}from"node:crypto";import{fileURLToPath as V}from"node:url";import T from"node:path";import{adapterRegistry as J}from"../adapter/index.js";import{getBridgeVersion as R,resolveSchedulerBaseUrlFrom as Q}from"../config.js";import{validateToken as S,validateTokenOrPanelSession as v}from"../middleware/auth.js";import{getConfiguredConnectionKeys as _,getRegistrationState as Y,requestRuntimeAccessTokenRefreshForServer as Z,resolveSchedulerBaseUrlFromToken as ee}from"../services/auto-register.js";import{discoverCcSwitchConfiguredItems as te,loadCcSwitchSdk as re}from"../services/cc-switch-sdk.js";import{initInstance as se,syncLegacyStateFromSdk as oe}from"../services/instance-init-service.js";import{setAgentFeatures as ne,getAgentFeatures as x}from"../services/instance-features.js";import{createBridgeLifecycleOperation as C,deliverPendingBridgeLifecycleNotifications as L,loadLifecycleState as ae,removeBridgeLifecycleOperation as ie,upsertBridgeLifecycleOperation as b}from"../services/lifecycle-state.js";import{appendUpdateLog as p,readUpdateLog as ce}from"../services/update-log.js";import{checkBridgeUpdate as O}from"../utils/npm-version.js";import{loadInstanceState as $,saveInstanceState as q}from"../services/instance-state.js";import{getRuntimeAccessToken as le}from"../services/runtime-binding.js";import{detectToolStatuses as U,ensureToolsInstalled as de,SUPPORTED_INSTALLABLE_TOOLS as j,SUPPORTED_UNINSTALLABLE_TOOLS as F,uninstallTools as ue}from"../services/tool-installer.js";import{createLogger as ge}from"../utils/logger.js";const a=ge("instance");import{buildToolStatusDetectionTargets as fe}from"./instance-tool-status.js";let I=null;function We(r){I=r}const d=H();function P(r){return String(r||"").trim()||void 0}function D(r){const t=r.get("host");if(t)return`${r.protocol}://${t}`}function N(){const r=T.resolve(T.dirname(V(import.meta.url)),"..","..");return T.join(r,"dist","index.js")}function pe(){return new Promise((r,t)=>{const e=A(process.execPath,[N(),"update","--no-kill"],{shell:!1,windowsHide:!0});let s="",o="";e.stdout.on("data",n=>{s+=n.toString("utf8")}),e.stderr.on("data",n=>{o+=n.toString("utf8")}),e.on("error",t),e.on("close",n=>{r({exitCode:n,stdout:s,stderr:o})})})}function K(){const r=A(process.execPath,[N(),"service","restart"],{shell:!1,windowsHide:!0,stdio:["ignore","pipe","pipe"]});r.stdout.on("data",t=>{p(String(t)).catch(()=>{})}),r.stderr.on("data",t=>{p(String(t)).catch(()=>{})})}const M=3e3;async function me(r){const t={...r,status:"running",message:"Bridge \u66F4\u65B0\u6B63\u5728\u540E\u53F0\u6267\u884C\u3002"};await b(t);try{a.info("[update-bridge] \u6B63\u5728\u540E\u53F0\u6267\u884C awsb update --yes"),await p(`===== Bridge \u66F4\u65B0\u5F00\u59CB\uFF1Av${R()} -> latest\uFF08\u64CD\u4F5C ${r.operationId}\uFF09 =====`);const e=await pe();if(e.stdout&&await p(e.stdout),e.stderr&&await p(e.stderr),e.exitCode===0){await p("===== Bridge \u66F4\u65B0\u6210\u529F\uFF0C\u51C6\u5907\u81EA\u52A8\u91CD\u542F ===== ");const s={...t,status:"restarting",message:"Bridge \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u542F..."};await b(s),a.info("[update-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("BRIDGE_UPDATE_RESTART",!0).catch(o=>{const n=o;a.error("[update-bridge] graceful shutdown failed:",n),process.exit(42)});return}process.exit(42)},M)}else await p(`===== Bridge \u66F4\u65B0\u5931\u8D25\uFF1A\u9000\u51FA\u7801 ${e.exitCode??"unknown"} =====`),await b({...t,status:"failed",message:`Bridge \u66F4\u65B0\u5931\u8D25\uFF0Cnpm \u9000\u51FA\u7801: ${e.exitCode??"unknown"}`,error:e.stderr||e.stdout}),await L()}catch(e){const s=e;a.error("[update-bridge] background update failed:",s),await p(`===== Bridge \u66F4\u65B0\u5F02\u5E38\uFF1A${s.message} =====`).catch(()=>{}),await b({...t,status:"failed",message:"Bridge \u66F4\u65B0\u5931\u8D25\u3002",error:s.message||"update bridge failed"}),await L()}}async function z(r){const t=await import("node:fs/promises"),e=await import("node:path"),s=await import("node:os"),o=e.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");if(r){await t.mkdir(e.dirname(o),{recursive:!0}),await t.writeFile(o,JSON.stringify({preserveSessions:!0,timestamp:new Date().toISOString()}),"utf-8"),a.info(`[prepare-restart] \u5DF2\u521B\u5EFA\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6: ${o}`);return}try{await t.unlink(o),a.info("[prepare-restart] \u5DF2\u5220\u9664\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6")}catch{}}function he(r){return W("md5").update(r,"utf8").digest("hex")}function Se(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 o=Buffer.from(he(s),"hex");return o.length===e.length&&G(o,e)})}function we(r){const e=_().length>0,s=R();return e?Se(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 be(r,t){const s=t.trim()||"http://localhost:8080",o=/^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:o?"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 ye(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 Ie(r,t){try{const e=await E.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":t}});return{schedulerReachable:e.status>=200&&e.status<300,tokenRefreshed:!1}}catch(e){if(ye(e)!==401)throw e;const s=await Z(r);if(!s.success||!s.runtimeAccessToken)throw e;const o=await E.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":s.runtimeAccessToken}});return{schedulerReachable:o.status>=200&&o.status<300,tokenRefreshed:!0}}}function Be(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 ke(r,t){const e=r.filter(s=>t[s]?.installed);return e.length===0?"":e.map(s=>{const o=t[s],n=o?.error||o?.executable||"command is still available";return`${s}: ${n}`}).join("; ")}function B(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}]))}d.get("/check-update",async(r,t)=>{t.json(await O())}),d.get("/update-log",v,async(r,t)=>{try{const e=await ce();t.json({ok:!0,...e})}catch(e){const s=e;a.error("[update-log] Error:",s),t.status(500).json({ok:!1,error:s.message||"read update log failed"})}}),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=we(e);t.status(s.status).json(s.body)}),d.get("/ping",S,async(r,t)=>{let e=Q(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 o=ee(r.header("X-Runtime-Token"));if(o)e={url:o,source:"token-match",ambiguousTargetUrls:[]};else{const n=r.header("X-Bridge-Instance-Id");if(n){const i=Y(),c=Object.entries(i.registrations).find(([,l])=>l===n)?.[0];c&&(e={url:c,source:"instance-id-match",ambiguousTargetUrls:[]})}if(!e.url||e.source==="ambiguous-auto-register-targets"){t.status(400).json(Be(e.ambiguousTargetUrls));return}}}const s=e.url;try{const o=le(s);if(!o){t.status(401).json({ok:!1,error:"runtime_access_token_required"});return}const n=await Ie(s,o);t.json({ok:!0,runtimeBridge:"healthy",version:R(),schedulerReachable:n.schedulerReachable,tokenRefreshed:n.tokenRefreshed,schedulerBaseUrl:s,instanceId:e.instanceId,instanceName:e.instanceName})}catch(o){const n=o;t.status(502).json(be(n,s))}}),d.post("/init-instance",S,async(r,t)=>{const{agentId:e,workspacePath:s,skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m}=r.body||{};if(!e||!s){t.status(400).json({error:"agentId and workspacePath are required"});return}try{a.info("[init-instance] Request received",{agentId:String(e),workspacePath:String(s),skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i});const u=await se(e,s,{skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m});a.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;a.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",S,async(r,t)=>{const{agentId:e,features:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}ne(String(e),s),a.info("[instance/features] \u80FD\u529B\u5F00\u5173\u5DF2\u66F4\u65B0",{agentId:String(e),features:x(String(e))});let o=0;for(const n of J.getAll())if(typeof n.applyAgentFeatures=="function")try{await n.applyAgentFeatures(String(e)),o+=1}catch(i){a.error("[instance/features] adapter \u70ED\u540C\u6B65\u5931\u8D25",{agentId:String(e),providerId:n.providerId,error:i?.message||String(i)})}t.json({ok:!0,agentId:String(e),features:x(String(e)),hotSyncedAdapters:o})}),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{a.info("[cc-switch/state] Request received",{agentId:String(e)});const s=await re(e),o=await te(s),i=(await oe(e,s)).state,c=fe(i.enabledTools||[]);a.info("[cc-switch/state] Detecting SDK statuses",{agentId:String(e),detectionTargets:c});const l=await U(c);a.info("[cc-switch/state] SDK status detection completed",{agentId:String(e),toolStatus:B(l),importedMcpCount:o.mcpServers,importedSkillCount:o.skills}),t.json({ok:!0,agentId:String(e),imported:{mcpServers:o.mcpServers,skills:o.skills},state:{...i,toolStatus:l}})}catch(s){const o=s;a.error("[cc-switch/state] Failed to load state",{agentId:String(e),error:o?.message||String(s),stack:o?.stack||null}),t.status(400).json({error:o?.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 o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[];a.info("[cc-switch/install-tools] Request received",{agentId:String(e),requestedTools:o});const n=new Set(j),i=o.filter(c=>n.has(c));if(a.info("[cc-switch/install-tools] Request normalized",{agentId:String(e),requestedTools:o,installableTools:i,supportedInstallableTools:j}),i.length===0){a.warn("[cc-switch/install-tools] No supported installable tools requested",{agentId:String(e),requestedTools:o}),t.status(400).json({error:`tools must include ${j.join(", ")}`});return}try{const c=await $(e),l=Array.from(new Set([...c.enabledTools||[],...i]));a.info("[cc-switch/install-tools] Starting installer",{agentId:String(e),installableTools:i,previousEnabledTools:c.enabledTools||[],nextEnabledTools:l});const m=await de(i);a.info("[cc-switch/install-tools] Installer completed",{agentId:String(e),toolStatus:B(m)});const u=await U(l);a.info("[cc-switch/install-tools] Refreshed enabled SDK statuses",{agentId:String(e),refreshedToolStatus:B(u)});const g={...u,...m},h=await q(e,{...c,enabledTools:l,toolStatus:g}),y=new Set(i),w=Object.entries(g).filter(([f])=>y.has(f)).filter(([,f])=>!f.installed).map(([f,k])=>({tool:f,error:k.error||null}));w.length>0&&a.warn("[cc-switch/install-tools] Some SDKs remain unavailable",{agentId:String(e),failedTools:w}),a.info("[cc-switch/install-tools] State saved",{agentId:String(e),enabledTools:h.enabledTools,toolStatus:B(g)}),t.json({ok:w.length===0,agentId:String(e),toolStatus:g,failedTools:w,state:h})}catch(c){const l=c;a.error("[cc-switch/install-tools] Install route failed",{agentId:String(e),requestedTools:o,installableTools:i,error:l?.message||String(c),stack:l?.stack||null}),t.status(400).json({error:l?.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 o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[],n=new Set(F),i=o.filter(c=>n.has(c));if(i.length===0){t.status(400).json({error:`tools must include ${F.join(", ")}`});return}try{const c=await $(e),l=new Set(i),m=(c.enabledTools||[]).filter(k=>!l.has(String(k||"").trim().toLowerCase())),u=await ue(i),h={...await U([...m,...i]),...u},y=ke(i,h),w=y?c.enabledTools||[]:m,f=await q(e,{...c,enabledTools:w,toolStatus:h});if(y){t.status(400).json({ok:!1,agentId:String(e),error:`uninstall incomplete: ${y}`,toolStatus:h,state:f});return}t.json({ok:!0,agentId:String(e),toolStatus:h,state:f})}catch(c){const l=c;t.status(400).json({error:l?.message||"uninstall tools failed"})}}),d.post("/prepare-restart",S,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{};try{a.info(`[prepare-restart] \u6536\u5230\u51C6\u5907\u91CD\u542F\u901A\u77E5\uFF0CpreserveSessions=${e}`),await z(!!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 o=s;a.error("[prepare-restart] Error:",o),t.status(500).json({error:o.message})}});const Te={update:15*60*1e3,restart:5*60*1e3};async function Re(r){try{const t=await ae(),e=Date.now(),s=Te[r],o=[];for(const n of t.operations){if(n.operationType!==r||!["accepted","running","restarting"].includes(n.status))continue;const i=new Date(n.updatedAt).getTime();if(!Number.isNaN(i)&&e-i<=s)return{operationId:n.operationId};o.push(n.operationId)}for(const n of o)try{await ie(n),a.info(`[${r}-bridge] \u5DF2\u6E05\u7406\u8D85\u65F6\u672A\u4EA4\u4ED8\u7684\u9648\u65E7\u64CD\u4F5C: ${n}`)}catch(i){const c=i;a.warn(`[${r}-bridge] \u6E05\u7406\u9648\u65E7\u64CD\u4F5C\u5931\u8D25\uFF0C\u7EE7\u7EED\u653E\u884C: ${n}, ${c.message}`)}return null}catch(t){const e=t;return a.warn(`[${r}-bridge] \u5728\u9014\u64CD\u4F5C\u68C0\u67E5\u5931\u8D25\uFF0C\u653E\u884C\u8BF7\u6C42: ${e.message}`),null}}async function X(r,t,e){const s=await Re(r);return s?(e.status(409).json({error:t,operationId:s.operationId}),!0):!1}d.post("/update-bridge",v,async(r,t)=>{const e=P(r.body?.requestedByUserId);try{if(!e){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("update","\u5B9E\u4F8B Bridge \u66F4\u65B0\u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u89E6\u53D1",t))return;const s=await O(),o=C({operationType:"update",requestedByUserId:e,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(r),status:"accepted",message:"Bridge \u66F4\u65B0\u8BF7\u6C42\u5DF2\u6536\u5230\uFF0C\u5C06\u5728\u540E\u53F0\u6267\u884C\u5E76\u81EA\u52A8\u91CD\u542F\u3002",latestVersion:s.ok&&s.latestVersion||void 0});await b(o),me(o),t.json({ok:!0,operationId:o.operationId,message:"Bridge \u66F4\u65B0\u5DF2\u5728\u540E\u53F0\u5F00\u59CB\uFF0C\u66F4\u65B0\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u91CD\u542F\u5B9E\u4F8B Bridge\u3002"})}catch(s){const o=s;a.error("[update-bridge] Error:",o),t.status(500).json({error:o.message||"update bridge failed"})}}),d.post("/restart-bridge",v,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{},s=P(r.body?.requestedByUserId);try{if(!s){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("restart","\u5B9E\u4F8B Bridge \u6B63\u5728\u91CD\u542F\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5",t))return;a.info(`[restart-bridge] \u6536\u5230\u5B9E\u4F8B Bridge \u91CD\u542F\u8BF7\u6C42\uFF0CpreserveSessions=${e}`);const o=C({operationType:"restart",requestedByUserId:s,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(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(o),await z(!!e),t.json({ok:!0,operationId:o.operationId,preserveSessions:!!e,message:"Bridge \u6B63\u5728\u91CD\u542F\uFF0C\u670D\u52A1\u7BA1\u7406\u5668\u4F1A\u81EA\u52A8\u62C9\u8D77\u5B9E\u4F8B\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002"}),a.info("[restart-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("PANEL_RESTART",!!e).catch(n=>{const i=n;a.error("[restart-bridge] graceful shutdown failed:",i),process.exit(42)});return}process.exit(42)},M)}catch(o){const n=o;a.error("[restart-bridge] Error:",n),t.status(500).json({error:n.message||"restart bridge failed"})}});export{Be as buildAmbiguousSchedulerBaseUrlResponse,we as buildConnectionCheckResponse,be as buildSchedulerPingFailureResponse,ke as buildUninstallFailureMessage,d as instanceRouter,We as setGracefulShutdownFn,B as summarizeToolStatus};
2
2
 
@@ -1,2 +1,2 @@
1
- import{Router as o}from"express";import{validateToken as s}from"../middleware/auth.js";import{collectSystemMetrics as m}from"../services/system-metrics.js";import{createLogger as c}from"../utils/logger.js";const i=c("system-metrics-route"),a=o();a.get("/system-metrics",s,async(n,t)=>{try{t.json(await m())}catch(r){const e=r instanceof Error?r:new Error(String(r));i.error("Failed to collect system metrics:",e.message),t.status(500).json({error:e.message})}});export{a as systemMetricsRouter};
1
+ import{Router as a}from"express";import{validateToken as c}from"../middleware/auth.js";import{collectProcessDetail as m}from"../services/process-detail.js";import{clampProcessTopLimit as l,collectProcessTop as u,normalizeProcessTopResource as p}from"../services/process-top.js";import{collectSystemMetrics as g}from"../services/system-metrics.js";import{createLogger as y}from"../utils/logger.js";const i=y("system-metrics-route"),n=a();function f(t){const r=Number(t);return!Number.isInteger(r)||r<1||r>4194304?null:r}n.get("/system-metrics",c,async(t,r)=>{try{r.json(await g())}catch(o){const e=o instanceof Error?o:new Error(String(o));i.error("Failed to collect system metrics:",e.message),r.status(500).json({error:e.message})}}),n.get("/process-top",c,async(t,r)=>{const o=p(t.query.resource);if(!o){r.status(400).json({error:"resource must be one of cpu|memory|storage|network"});return}try{r.json(await u(o,l(t.query.limit)))}catch(e){const s=e instanceof Error?e:new Error(String(e));i.error("Failed to collect process top:",s.message),r.status(500).json({error:s.message})}}),n.get("/process-detail",c,async(t,r)=>{const o=f(t.query.pid);if(o===null){r.status(400).json({error:"pid must be a positive integer"});return}try{r.json(await m(o))}catch(e){const s=e instanceof Error?e:new Error(String(e));i.error("Failed to collect process detail:",s.message),r.status(500).json({error:s.message})}});export{n as systemMetricsRouter};
2
2