aws-runtime-bridge 1.9.102 → 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 (46) 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-use-backend.d.ts +111 -0
  6. package/dist/remote-desktop/computer-use-backend.js +1 -0
  7. package/dist/remote-desktop/types.d.ts +79 -0
  8. package/dist/routes/computer.d.ts +1 -0
  9. package/dist/routes/computer.js +2 -0
  10. package/dist/routes/file-change-diff.d.ts +27 -0
  11. package/dist/routes/file-change-diff.js +5 -0
  12. package/dist/routes/instance.js +2 -2
  13. package/dist/routes/pty.d.ts +2 -2
  14. package/dist/routes/pty.js +3 -3
  15. package/dist/routes/task-context.js +1 -1
  16. package/dist/routes/terminal.js +28 -28
  17. package/dist/services/auto-register.d.ts +1 -1
  18. package/dist/services/auto-register.js +1 -1
  19. package/dist/services/instance-features.d.ts +10 -0
  20. package/dist/services/instance-features.js +2 -0
  21. package/dist/services/sdk-tool-file-change-recorder.d.ts +12 -0
  22. package/dist/services/sdk-tool-file-change-recorder.js +2 -0
  23. package/dist/services/workspace-files.js +1 -1
  24. package/dist/services/workspaceTaskChangeWatcher.d.ts +9 -1
  25. package/dist/services/workspaceTaskChangeWatcher.js +3 -1
  26. package/package/acode/dist/built-in-file-tools.d.ts +4 -0
  27. package/package/acode/dist/built-in-file-tools.js +2 -2
  28. package/package/acode/dist/runtime.d.ts +13 -0
  29. package/package/acode/dist/runtime.js +18 -18
  30. package/package/aws-client-agent-mcp/README.md +2 -2
  31. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +14 -4
  32. package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
  33. package/package/aws-client-agent-mcp/dist/agent-client.test.js +36 -36
  34. package/package/aws-client-agent-mcp/dist/config.js +1 -1
  35. package/package/aws-client-agent-mcp/dist/config.test.js +2 -2
  36. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +2 -0
  37. package/package/aws-client-agent-mcp/dist/mcp-server.js +21 -21
  38. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +35 -36
  39. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +6 -0
  40. package/package/aws-client-agent-mcp/dist/mcp-tools.js +25 -5
  41. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +17 -1
  42. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +1 -1
  43. package/package/aws-client-agent-mcp/dist/types.d.ts +14 -12
  44. package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -1
  45. package/package.json +3 -2
  46. package/scripts/ensure-playwright-browser.mjs +118 -0
@@ -1,2 +1,2 @@
1
- import{EventEmitter as m}from"node:events";import c from"ws";import{logger as r}from"./logger.js";const f=9e3,T=3e4,b=1e3,l=100,u=3,d=3,k=50,g=1e3;class w extends m{ws=null;config;agentId=null;heartbeatTimer=null;heartbeatTimeoutTimer=null;reconnectAttempts=0;reconnectTimer=null;isManualClose=!1;websocketFactory;lastRefreshedToken=null;consecutiveRefreshFailures=0;logBatchQueue=[];logBatchTimer=null;logSequenceNumber=0;pendingLogBatches=new Map;isProcessingLogBatch=!1;processedMsgIds=new Map;constructor(e,t){super(),this.config=e,this.websocketFactory=t??(s=>new c(s))}connect(e){return new Promise((t,s)=>{this.prepareConnection(e);const n=this.buildWebSocketUrl(e);r.info(`[WebSocket] \u6B63\u5728\u8FDE\u63A5: ${this.maskRuntimeTokenInUrl(n)}`);const{rejectOnce:o,resolveOnce:i}=this.createSettledHandlers(t,s);try{this.ws=this.websocketFactory(n),this.setupWebSocketEventHandlers(this.ws,o,i)}catch(a){o(a instanceof Error?a:new Error(String(a)))}})}prepareConnection(e){this.agentId=e,this.isManualClose=!1,this.ws&&this.ws.readyState!==c.CLOSED&&(this.ws.removeAllListeners(),this.ws.terminate())}buildWebSocketUrl(e){const t=new URL(this.config.serverUrl);t.searchParams.set("agentId",e);const s=this.config.runtimeAccessToken?.trim();return s&&t.searchParams.set("runtimeAccessToken",s),t.toString()}maskRuntimeTokenInUrl(e){try{const t=new URL(e);return t.searchParams.has("runtimeAccessToken")&&t.searchParams.set("runtimeAccessToken","***"),t.toString()}catch{return e.replace(/([?&]runtimeAccessToken=)[^&]*/u,"$1***")}}createSettledHandlers(e,t){let s=!1;return{rejectOnce:i=>{s||(s=!0,t(i))},resolveOnce:()=>{s||(s=!0,e())}}}setupWebSocketEventHandlers(e,t,s){let n=!1;e.on("open",()=>{n=!0,this.handleWebSocketOpen(s)}),e.on("message",o=>this.handleWebSocketMessage(o)),e.on("pong",()=>this.markHeartbeatReceived()),e.on("close",(o,i)=>this.handleWebSocketClose(o,i,n,t)),e.on("error",o=>this.handleWebSocketError(o,n,t))}handleWebSocketOpen(e){r.info("[WebSocket] \u8FDE\u63A5\u5DF2\u5EFA\u7ACB"),this.reconnectAttempts=0,this.consecutiveRefreshFailures=0,this.lastRefreshedToken=null,this.clearReconnectTimer(),this.startHeartbeat(),this.emit("connected"),e()}handleWebSocketMessage(e){try{const t=JSON.parse(e.toString()),s=this.extractReliableMessageId(t),n=s!==null&&this.markMsgIdProcessed(s);if(this.acknowledgeReliableMessage(t),n){r.info("[WebSocket] \u5FFD\u7565\u91CD\u590D\u6295\u9012\u7684\u53EF\u9760\u6D88\u606F: msgId=%s, type=%s",s,t.type);return}this.handleMessage(t)}catch(t){const s=e.toString(),n=s.length>200?`${s.slice(0,200)}...(truncated, ${s.length} total)`:s;r.warn("[WebSocket] \u6D88\u606F\u89E3\u6790\u5931\u8D25, dataLength=%s, preview=%s:",s.length,n,t)}}extractReliableMessageId(e){const t=e.data;if(!t||typeof t!="object")return null;const s=t,n=String(s._msgId||"").trim(),o=s._requireAck===!0||s._requireAck==="true";return!n||!o?null:n}markMsgIdProcessed(e){if(this.processedMsgIds.has(e))return!0;if(this.processedMsgIds.set(e,Date.now()),this.processedMsgIds.size>g){let t=0;const s=this.processedMsgIds.size-g;for(const n of this.processedMsgIds.keys()){if(t>=s)break;this.processedMsgIds.delete(n),t++}}return!1}acknowledgeReliableMessage(e){const t=this.extractReliableMessageId(e);t&&this.send({type:"MESSAGE_ACK",data:{msgId:t},timestamp:new Date().toISOString()})}async handleWebSocketClose(e,t,s,n){const o=t.toString(),i=this.ws?.readyState;r.warn(`[WebSocket] \u8FDE\u63A5\u5173\u95ED: code=${e}, reason=${o}, readyState=${i}, opened=${s}, reconnectAttempts=${this.reconnectAttempts}`),this.stopHeartbeat(),this.clearHeartbeatTimeout(),this.emit("disconnected",e,t),s||n(new Error(`WebSocket \u8FDE\u63A5\u5728\u5EFA\u7ACB\u524D\u5173\u95ED: code=${e}, reason=${o}`)),this.isManualClose||(e===1008?await this.handleTokenExpiredAndReconnect():this.scheduleReconnect())}async handleTokenExpiredAndReconnect(){const e=await this.refreshRuntimeTokenOnPolicyRejection();if(e==="refreshed"){r.info("[WebSocket] runtime token \u5DF2\u5237\u65B0\uFF0C\u4F7F\u7528\u65B0 token \u91CD\u8FDE"),this.consecutiveRefreshFailures=0,this.scheduleReconnect();return}if(e==="unchanged"){r.error("[WebSocket] bridge \u8FD4\u56DE token \u672A\u53D8\u5316\uFF0C\u4F46\u670D\u52A1\u7AEF\u4ECD\u4EE5 1008 \u62D2\u7EDD\uFF0C\u505C\u6B62\u91CD\u8FDE\uFF08\u9700\u4EBA\u5DE5\u6216 supervisor \u4ECB\u5165\uFF09"),this.emit("tokenRejected",this.config.runtimeAccessToken);return}if(this.consecutiveRefreshFailures+=1,this.consecutiveRefreshFailures>=d){r.error(`[WebSocket] runtime token \u5237\u65B0\u8FDE\u7EED\u5931\u8D25 ${this.consecutiveRefreshFailures} \u6B21\uFF0C\u505C\u6B62\u91CD\u8FDE\uFF08\u4EA4\u7531 supervisor \u91CD\u542F\uFF09`),this.emit("tokenRefreshFailed",this.consecutiveRefreshFailures);return}r.warn(`[WebSocket] runtime token \u5237\u65B0\u5931\u8D25\uFF0C\u7B2C ${this.consecutiveRefreshFailures}/${d} \u6B21\u5C1D\u8BD5\u91CD\u8FDE`),this.scheduleReconnect()}async refreshRuntimeTokenOnPolicyRejection(){const e=this.config.runtimeBridgeBaseUrl;if(!e||!this.agentId)return"failed";const t=this.config.runtimeAccessToken||"",s=this.config.mcpHttpRequestTimeoutMs??12e4,n=new AbortController,o=setTimeout(()=>n.abort(),s);try{const i=await fetch(`${e}/runtime/binding/refresh-token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agentId:this.agentId,currentToken:t,forceRefresh:!0}),signal:n.signal});if(!i.ok)return r.warn(`[WebSocket] bridge refresh-token \u8FD4\u56DE ${i.status}\uFF0C\u65E0\u6CD5\u5237\u65B0`),"failed";const h=(await i.json()).runtimeAccessToken;return!h||h===t?"unchanged":(this.config.runtimeAccessToken=h,this.lastRefreshedToken=t,r.info("[WebSocket] runtimeAccessToken \u5DF2\u901A\u8FC7 bridge refresh \u66F4\u65B0"),"refreshed")}catch(i){return i instanceof Error&&i.name==="AbortError"?r.warn(`[WebSocket] bridge refresh-token \u8D85\u65F6\uFF08${s}ms\uFF09`):(r.warn("[WebSocket] bridge refresh-token \u5F02\u5E38: %s",i instanceof Error?i.message:String(i)),i instanceof Error&&i.stack&&r.warn("[WebSocket] refresh-token \u5F02\u5E38\u5806\u6808:",i.stack.slice(0,2e3))),"failed"}finally{clearTimeout(o)}}handleWebSocketError(e,t,s){const n=this.ws?.readyState;r.error("[WebSocket] \u8FDE\u63A5\u9519\u8BEF: readyState=%s, opened=%s, reconnectAttempts=%s, message=%s",n,t,this.reconnectAttempts,e.message),r.error("[WebSocket] \u9519\u8BEF\u5806\u6808:",e.stack?.slice(0,2e3)),this.emit("error",e),t||s(e)}disconnect(){this.isManualClose=!0,this.stopHeartbeat(),this.clearHeartbeatTimeout(),this.clearReconnectTimer(),this.ws&&(this.ws.readyState===c.OPEN&&this.ws.close(1e3,"Agent logout"),this.ws=null),this.agentId=null,r.info("[WebSocket] \u5DF2\u65AD\u5F00\u8FDE\u63A5")}send(e){const t=this.ws?.readyState;if(!this.ws||t!==c.OPEN)return r.warn("[WebSocket] \u8FDE\u63A5\u672A\u5EFA\u7ACB\uFF0C\u65E0\u6CD5\u53D1\u9001\u6D88\u606F: readyState=%s, isManualClose=%s, agentId=%s",t,this.isManualClose,this.agentId),!1;try{return this.ws.send(JSON.stringify(e)),!0}catch(s){return r.error("[WebSocket] send() \u53D1\u9001\u6D88\u606F\u5F02\u5E38: readyState=%s, error=%s",t,s),!1}}sendMcpNodeLog(e){return this.isConnected()?(this.logBatchQueue.push(e),this.logBatchTimer||(this.logBatchTimer=setTimeout(()=>{this.logBatchTimer=null,this.flushLogBatch()},l)),!0):!1}flushLogBatch(){if(this.logBatchQueue.length===0||this.isProcessingLogBatch)return;const e=this.logBatchQueue.splice(0,k);if(e.length===0)return;this.isProcessingLogBatch=!0,this.logSequenceNumber+=1;const t=this.logSequenceNumber;new Promise((n,o)=>{this.pendingLogBatches.set(t,{logs:e,sequenceNumber:t,retryCount:0,resolve:n,reject:o})}).then(()=>{this.isProcessingLogBatch=!1,this.logBatchQueue.length>0&&(this.logBatchTimer=setTimeout(()=>{this.logBatchTimer=null,this.flushLogBatch()},l))}).catch(()=>{this.isProcessingLogBatch=!1}),this.sendLogBatch(t)}sendLogBatch(e){const t=this.pendingLogBatches.get(e);if(!t)return;this.send({type:"MCP_NODE_LOG_BATCH",data:{sequenceNumber:e,logs:t.logs},timestamp:new Date().toISOString()})||this.handleLogBatchSendFailure(e)}handleLogBatchSendFailure(e){const t=this.pendingLogBatches.get(e);t&&(t.retryCount+=1,t.retryCount<=u?(r.warn(`[WebSocket] \u65E5\u5FD7\u6279\u6B21 #${e} \u53D1\u9001\u5931\u8D25\uFF0C\u7B2C ${t.retryCount} \u6B21\u91CD\u8BD5`),setTimeout(()=>{this.sendLogBatch(e)},l*t.retryCount)):(r.error(`[WebSocket] \u65E5\u5FD7\u6279\u6B21 #${e} \u53D1\u9001\u5931\u8D25\uFF0C\u5DF2\u91CD\u8BD5 ${u} \u6B21\uFF0C\u4E22\u5F03`),this.pendingLogBatches.delete(e),t.reject(new Error(`\u65E5\u5FD7\u6279\u6B21 #${e} \u53D1\u9001\u5931\u8D25`))))}handleLogAck(e){const s=Number(e?.ackSequenceNumber);if(!s||!this.pendingLogBatches.has(s))return;const n=this.pendingLogBatches.get(s);n&&(this.pendingLogBatches.delete(s),n.resolve())}isConnected(){return this.ws!==null&&this.ws.readyState===c.OPEN}handleMessage(e){switch(e.type){case"GROUP_MSG":this.emit("groupMessage",e.data);break;case"DM_MSG":this.emit("directMessage",e.data);break;case"ONLINE_CHANGE":this.emit("onlineChange",e.data);break;case"HEARTBEAT":this.markHeartbeatReceived();break;case"PROFILE_INFO":r.info("[WebSocket] \u6536\u5230 PROFILE_INFO \u6D88\u606F"),this.emit("profileInfo",e.data);break;case"AGENT_STATUS":r.info("[WebSocket] \u6536\u5230 AGENT_STATUS \u6D88\u606F"),this.emit("agentStatus",e.data);break;case"LOG_ACK":this.handleLogAck(e.data);break;case"GROUP_READ_RECEIPT":case"DM_READ_RECEIPT":case"TERMINAL_OUTPUT":case"TERMINAL_STATUS":case"MCP_NODE_LOG":case"AGENT_EVENTS_DELTA":break;default:r.warn("[WebSocket] \u672A\u77E5\u6D88\u606F\u7C7B\u578B:",e.type)}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.isConnected()&&(this.ensureHeartbeatTimeout(),this.sendProtocolPing(),this.send({type:"HEARTBEAT",data:"ping",timestamp:new Date().toISOString()}))},this.config.heartbeatInterval)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.clearHeartbeatTimeout()}sendProtocolPing(){if(!(!this.ws||this.ws.readyState!==c.OPEN))try{this.ws.ping()}catch(e){r.warn("[WebSocket] \u534F\u8BAE\u5C42 ping \u53D1\u9001\u5931\u8D25\uFF0C\u7EC8\u6B62\u8FDE\u63A5\u5E76\u91CD\u8FDE:",e instanceof Error?e.message:String(e)),this.ws.terminate()}}markHeartbeatReceived(){this.clearHeartbeatTimeout()}ensureHeartbeatTimeout(){this.heartbeatTimeoutTimer||(this.heartbeatTimeoutTimer=setTimeout(()=>{this.heartbeatTimeoutTimer=null,!(!this.ws||this.ws.readyState!==c.OPEN)&&(r.warn(`[WebSocket] ${this.resolveHeartbeatTimeout()}ms \u672A\u6536\u5230\u5FC3\u8DF3\u54CD\u5E94\uFF0C\u7EC8\u6B62\u8FDE\u63A5\u5E76\u89E6\u53D1\u91CD\u8FDE`),this.ws.terminate())},this.resolveHeartbeatTimeout()))}clearHeartbeatTimeout(){this.heartbeatTimeoutTimer&&(clearTimeout(this.heartbeatTimeoutTimer),this.heartbeatTimeoutTimer=null)}resolveHeartbeatTimeout(){return this.config.heartbeatTimeout??f}scheduleReconnect(){const e=this.config.maxReconnectAttempts!==-1;if(e&&this.reconnectAttempts>=this.config.maxReconnectAttempts){r.error("[WebSocket] \u91CD\u8FDE\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u653E\u5F03\u91CD\u8FDE"),this.emit("reconnectFailed");return}if(this.reconnectTimer)return;this.reconnectAttempts++;const t=Math.min(this.config.reconnectInterval*Math.pow(2,this.reconnectAttempts-1),this.config.reconnectMaxInterval??T),s=Math.random()*(this.config.reconnectJitterMs??b),n=Math.round(t+s),o=e?`\u7B2C ${this.reconnectAttempts}/${this.config.maxReconnectAttempts} \u6B21`:`\u7B2C ${this.reconnectAttempts} \u6B21\uFF08\u65E0\u9650\u91CD\u8BD5\u6A21\u5F0F\uFF09`;r.warn(`[WebSocket] ${n}ms \u540E\u5C1D\u8BD5${o}\u91CD\u8FDE (base=${Math.round(t)}ms, jitter=+${Math.round(s)}ms)...`),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.agentId&&this.connect(this.agentId).catch(i=>{const a=i instanceof Error?i.message:String(i),h=i instanceof Error?i.stack:void 0;r.warn("[WebSocket] \u91CD\u8FDE\u5931\u8D25 (attempt #%s), \u7EE7\u7EED\u91CD\u8BD5: %s",this.reconnectAttempts,a),h&&r.warn("[WebSocket] \u91CD\u8FDE\u5931\u8D25\u5806\u6808:",h.slice(0,2e3)),this.scheduleReconnect()})},n)}clearReconnectTimer(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}}export{w as WebSocketClient};
1
+ import{EventEmitter as m}from"node:events";import a from"ws";import{logger as r}from"./logger.js";const f=9e3,T=3e4,b=1e3,l=100,u=3,d=3,k=50,g=1e3;class R extends m{ws=null;config;agentId=null;heartbeatTimer=null;heartbeatTimeoutTimer=null;reconnectAttempts=0;reconnectTimer=null;isManualClose=!1;websocketFactory;lastRefreshedToken=null;consecutiveRefreshFailures=0;logBatchQueue=[];logBatchTimer=null;logSequenceNumber=0;pendingLogBatches=new Map;isProcessingLogBatch=!1;processedMsgIds=new Map;constructor(e,t){super(),this.config=e,this.websocketFactory=t??(s=>new a(s))}connect(e){return new Promise((t,s)=>{this.prepareConnection(e);const n=this.buildWebSocketUrl(e);r.info(`[WebSocket] \u6B63\u5728\u8FDE\u63A5: ${this.maskRuntimeTokenInUrl(n)}`);const{rejectOnce:o,resolveOnce:i}=this.createSettledHandlers(t,s);try{this.ws=this.websocketFactory(n),this.setupWebSocketEventHandlers(this.ws,o,i)}catch(c){o(c instanceof Error?c:new Error(String(c)))}})}prepareConnection(e){this.agentId=e,this.isManualClose=!1,this.ws&&this.ws.readyState!==a.CLOSED&&(this.ws.removeAllListeners(),this.ws.terminate())}buildWebSocketUrl(e){const t=new URL(this.config.serverUrl);t.searchParams.set("agentId",e);const s=this.config.runtimeAccessToken?.trim();return s&&t.searchParams.set("runtimeAccessToken",s),t.toString()}maskRuntimeTokenInUrl(e){try{const t=new URL(e);return t.searchParams.has("runtimeAccessToken")&&t.searchParams.set("runtimeAccessToken","***"),t.toString()}catch{return e.replace(/([?&]runtimeAccessToken=)[^&]*/u,"$1***")}}createSettledHandlers(e,t){let s=!1;return{rejectOnce:i=>{s||(s=!0,t(i))},resolveOnce:()=>{s||(s=!0,e())}}}setupWebSocketEventHandlers(e,t,s){let n=!1;e.on("open",()=>{n=!0,this.handleWebSocketOpen(s)}),e.on("message",o=>this.handleWebSocketMessage(o)),e.on("pong",()=>this.markHeartbeatReceived()),e.on("close",(o,i)=>this.handleWebSocketClose(o,i,n,t)),e.on("error",o=>this.handleWebSocketError(o,n,t))}handleWebSocketOpen(e){r.info("[WebSocket] \u8FDE\u63A5\u5DF2\u5EFA\u7ACB"),this.reconnectAttempts=0,this.consecutiveRefreshFailures=0,this.lastRefreshedToken=null,this.clearReconnectTimer(),this.startHeartbeat(),this.emit("connected"),e()}handleWebSocketMessage(e){try{const t=JSON.parse(e.toString()),s=this.extractReliableMessageId(t),n=s!==null&&this.markMsgIdProcessed(s);if(this.acknowledgeReliableMessage(t),n){r.info("[WebSocket] \u5FFD\u7565\u91CD\u590D\u6295\u9012\u7684\u53EF\u9760\u6D88\u606F: msgId=%s, type=%s",s,t.type);return}this.handleMessage(t)}catch(t){const s=e.toString(),n=s.length>200?`${s.slice(0,200)}...(truncated, ${s.length} total)`:s;r.warn("[WebSocket] \u6D88\u606F\u89E3\u6790\u5931\u8D25, dataLength=%s, preview=%s:",s.length,n,t)}}extractReliableMessageId(e){const t=e.data;if(!t||typeof t!="object")return null;const s=t,n=String(s._msgId||"").trim(),o=s._requireAck===!0||s._requireAck==="true";return!n||!o?null:n}markMsgIdProcessed(e){if(this.processedMsgIds.has(e))return!0;if(this.processedMsgIds.set(e,Date.now()),this.processedMsgIds.size>g){let t=0;const s=this.processedMsgIds.size-g;for(const n of this.processedMsgIds.keys()){if(t>=s)break;this.processedMsgIds.delete(n),t++}}return!1}acknowledgeReliableMessage(e){const t=this.extractReliableMessageId(e);t&&this.send({type:"MESSAGE_ACK",data:{msgId:t},timestamp:new Date().toISOString()})}async handleWebSocketClose(e,t,s,n){const o=t.toString(),i=this.ws?.readyState;r.warn(`[WebSocket] \u8FDE\u63A5\u5173\u95ED: code=${e}, reason=${o}, readyState=${i}, opened=${s}, reconnectAttempts=${this.reconnectAttempts}`),this.stopHeartbeat(),this.clearHeartbeatTimeout(),this.emit("disconnected",e,t),s||n(new Error(`WebSocket \u8FDE\u63A5\u5728\u5EFA\u7ACB\u524D\u5173\u95ED: code=${e}, reason=${o}`)),this.isManualClose||(e===1008?await this.handleTokenExpiredAndReconnect():this.scheduleReconnect())}async handleTokenExpiredAndReconnect(){const e=await this.refreshRuntimeTokenOnPolicyRejection();if(e==="refreshed"){r.info("[WebSocket] runtime token \u5DF2\u5237\u65B0\uFF0C\u4F7F\u7528\u65B0 token \u91CD\u8FDE"),this.consecutiveRefreshFailures=0,this.scheduleReconnect();return}if(e==="unchanged"){r.error("[WebSocket] bridge \u8FD4\u56DE token \u672A\u53D8\u5316\uFF0C\u4F46\u670D\u52A1\u7AEF\u4ECD\u4EE5 1008 \u62D2\u7EDD\uFF0C\u505C\u6B62\u91CD\u8FDE\uFF08\u9700\u4EBA\u5DE5\u6216 supervisor \u4ECB\u5165\uFF09"),this.emit("tokenRejected",this.config.runtimeAccessToken);return}if(this.consecutiveRefreshFailures+=1,this.consecutiveRefreshFailures>=d){r.error(`[WebSocket] runtime token \u5237\u65B0\u8FDE\u7EED\u5931\u8D25 ${this.consecutiveRefreshFailures} \u6B21\uFF0C\u505C\u6B62\u91CD\u8FDE\uFF08\u4EA4\u7531 supervisor \u91CD\u542F\uFF09`),this.emit("tokenRefreshFailed",this.consecutiveRefreshFailures);return}r.warn(`[WebSocket] runtime token \u5237\u65B0\u5931\u8D25\uFF0C\u7B2C ${this.consecutiveRefreshFailures}/${d} \u6B21\u5C1D\u8BD5\u91CD\u8FDE`),this.scheduleReconnect()}async refreshRuntimeTokenOnPolicyRejection(){const e=this.config.runtimeBridgeBaseUrl;if(!e||!this.agentId)return"failed";const t=this.config.runtimeAccessToken||"",s=this.config.mcpHttpRequestTimeoutMs??12e4,n=new AbortController,o=setTimeout(()=>n.abort(),s);try{const i=await fetch(`${e}/runtime/binding/refresh-token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agentId:this.agentId,currentToken:t,forceRefresh:!0}),signal:n.signal});if(!i.ok)return r.warn(`[WebSocket] bridge refresh-token \u8FD4\u56DE ${i.status}\uFF0C\u65E0\u6CD5\u5237\u65B0`),"failed";const h=(await i.json()).runtimeAccessToken;return!h||h===t?"unchanged":(this.config.runtimeAccessToken=h,this.lastRefreshedToken=t,r.info("[WebSocket] runtimeAccessToken \u5DF2\u901A\u8FC7 bridge refresh \u66F4\u65B0"),"refreshed")}catch(i){return i instanceof Error&&i.name==="AbortError"?r.warn(`[WebSocket] bridge refresh-token \u8D85\u65F6\uFF08${s}ms\uFF09`):(r.warn("[WebSocket] bridge refresh-token \u5F02\u5E38: %s",i instanceof Error?i.message:String(i)),i instanceof Error&&i.stack&&r.warn("[WebSocket] refresh-token \u5F02\u5E38\u5806\u6808:",i.stack.slice(0,2e3))),"failed"}finally{clearTimeout(o)}}handleWebSocketError(e,t,s){const n=this.ws?.readyState;r.error("[WebSocket] \u8FDE\u63A5\u9519\u8BEF: readyState=%s, opened=%s, reconnectAttempts=%s, message=%s",n,t,this.reconnectAttempts,e.message),r.error("[WebSocket] \u9519\u8BEF\u5806\u6808:",e.stack?.slice(0,2e3)),this.emit("error",e),t||s(e)}disconnect(){this.isManualClose=!0,this.stopHeartbeat(),this.clearHeartbeatTimeout(),this.clearReconnectTimer(),this.ws&&(this.ws.readyState===a.OPEN&&this.ws.close(1e3,"Agent logout"),this.ws=null),this.agentId=null,r.info("[WebSocket] \u5DF2\u65AD\u5F00\u8FDE\u63A5")}send(e){const t=this.ws?.readyState;if(!this.ws||t!==a.OPEN)return r.warn("[WebSocket] \u8FDE\u63A5\u672A\u5EFA\u7ACB\uFF0C\u65E0\u6CD5\u53D1\u9001\u6D88\u606F: readyState=%s, isManualClose=%s, agentId=%s",t,this.isManualClose,this.agentId),!1;try{return this.ws.send(JSON.stringify(e)),!0}catch(s){return r.error("[WebSocket] send() \u53D1\u9001\u6D88\u606F\u5F02\u5E38: readyState=%s, error=%s",t,s),!1}}sendMcpNodeLog(e){return this.isConnected()?(this.logBatchQueue.push(e),this.logBatchTimer||(this.logBatchTimer=setTimeout(()=>{this.logBatchTimer=null,this.flushLogBatch()},l)),!0):!1}flushLogBatch(){if(this.logBatchQueue.length===0||this.isProcessingLogBatch)return;const e=this.logBatchQueue.splice(0,k);if(e.length===0)return;this.isProcessingLogBatch=!0,this.logSequenceNumber+=1;const t=this.logSequenceNumber;new Promise((n,o)=>{this.pendingLogBatches.set(t,{logs:e,sequenceNumber:t,retryCount:0,resolve:n,reject:o})}).then(()=>{this.isProcessingLogBatch=!1,this.logBatchQueue.length>0&&(this.logBatchTimer=setTimeout(()=>{this.logBatchTimer=null,this.flushLogBatch()},l))}).catch(()=>{this.isProcessingLogBatch=!1}),this.sendLogBatch(t)}sendLogBatch(e){const t=this.pendingLogBatches.get(e);if(!t)return;this.send({type:"MCP_NODE_LOG_BATCH",data:{sequenceNumber:e,logs:t.logs},timestamp:new Date().toISOString()})||this.handleLogBatchSendFailure(e)}handleLogBatchSendFailure(e){const t=this.pendingLogBatches.get(e);t&&(t.retryCount+=1,t.retryCount<=u?(r.warn(`[WebSocket] \u65E5\u5FD7\u6279\u6B21 #${e} \u53D1\u9001\u5931\u8D25\uFF0C\u7B2C ${t.retryCount} \u6B21\u91CD\u8BD5`),setTimeout(()=>{this.sendLogBatch(e)},l*t.retryCount)):(r.error(`[WebSocket] \u65E5\u5FD7\u6279\u6B21 #${e} \u53D1\u9001\u5931\u8D25\uFF0C\u5DF2\u91CD\u8BD5 ${u} \u6B21\uFF0C\u4E22\u5F03`),this.pendingLogBatches.delete(e),t.reject(new Error(`\u65E5\u5FD7\u6279\u6B21 #${e} \u53D1\u9001\u5931\u8D25`))))}handleLogAck(e){const s=Number(e?.ackSequenceNumber);if(!s||!this.pendingLogBatches.has(s))return;const n=this.pendingLogBatches.get(s);n&&(this.pendingLogBatches.delete(s),n.resolve())}isConnected(){return this.ws!==null&&this.ws.readyState===a.OPEN}handleMessage(e){switch(e.type){case"GROUP_MSG":this.emit("groupMessage",e.data);break;case"DM_MSG":this.emit("directMessage",e.data);break;case"ONLINE_CHANGE":this.emit("onlineChange",e.data);break;case"HEARTBEAT":this.markHeartbeatReceived();break;case"PROFILE_INFO":r.info("[WebSocket] \u6536\u5230 PROFILE_INFO \u6D88\u606F"),this.emit("profileInfo",e.data);break;case"AGENT_CONFIG":r.info("[WebSocket] \u6536\u5230 AGENT_CONFIG \u6D88\u606F"),this.emit("agentConfig",e.data);break;case"AGENT_STATUS":r.info("[WebSocket] \u6536\u5230 AGENT_STATUS \u6D88\u606F"),this.emit("agentStatus",e.data);break;case"LOG_ACK":this.handleLogAck(e.data);break;case"GROUP_READ_RECEIPT":case"DM_READ_RECEIPT":case"TERMINAL_OUTPUT":case"TERMINAL_STATUS":case"MCP_NODE_LOG":case"AGENT_EVENTS_DELTA":break;default:r.warn("[WebSocket] \u672A\u77E5\u6D88\u606F\u7C7B\u578B:",e.type)}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.isConnected()&&(this.ensureHeartbeatTimeout(),this.sendProtocolPing(),this.send({type:"HEARTBEAT",data:"ping",timestamp:new Date().toISOString()}))},this.config.heartbeatInterval)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.clearHeartbeatTimeout()}sendProtocolPing(){if(!(!this.ws||this.ws.readyState!==a.OPEN))try{this.ws.ping()}catch(e){r.warn("[WebSocket] \u534F\u8BAE\u5C42 ping \u53D1\u9001\u5931\u8D25\uFF0C\u7EC8\u6B62\u8FDE\u63A5\u5E76\u91CD\u8FDE:",e instanceof Error?e.message:String(e)),this.ws.terminate()}}markHeartbeatReceived(){this.clearHeartbeatTimeout()}ensureHeartbeatTimeout(){this.heartbeatTimeoutTimer||(this.heartbeatTimeoutTimer=setTimeout(()=>{this.heartbeatTimeoutTimer=null,!(!this.ws||this.ws.readyState!==a.OPEN)&&(r.warn(`[WebSocket] ${this.resolveHeartbeatTimeout()}ms \u672A\u6536\u5230\u5FC3\u8DF3\u54CD\u5E94\uFF0C\u7EC8\u6B62\u8FDE\u63A5\u5E76\u89E6\u53D1\u91CD\u8FDE`),this.ws.terminate())},this.resolveHeartbeatTimeout()))}clearHeartbeatTimeout(){this.heartbeatTimeoutTimer&&(clearTimeout(this.heartbeatTimeoutTimer),this.heartbeatTimeoutTimer=null)}resolveHeartbeatTimeout(){return this.config.heartbeatTimeout??f}scheduleReconnect(){const e=this.config.maxReconnectAttempts!==-1;if(e&&this.reconnectAttempts>=this.config.maxReconnectAttempts){r.error("[WebSocket] \u91CD\u8FDE\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u653E\u5F03\u91CD\u8FDE"),this.emit("reconnectFailed");return}if(this.reconnectTimer)return;this.reconnectAttempts++;const t=Math.min(this.config.reconnectInterval*Math.pow(2,this.reconnectAttempts-1),this.config.reconnectMaxInterval??T),s=Math.random()*(this.config.reconnectJitterMs??b),n=Math.round(t+s),o=e?`\u7B2C ${this.reconnectAttempts}/${this.config.maxReconnectAttempts} \u6B21`:`\u7B2C ${this.reconnectAttempts} \u6B21\uFF08\u65E0\u9650\u91CD\u8BD5\u6A21\u5F0F\uFF09`;r.warn(`[WebSocket] ${n}ms \u540E\u5C1D\u8BD5${o}\u91CD\u8FDE (base=${Math.round(t)}ms, jitter=+${Math.round(s)}ms)...`),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.agentId&&this.connect(this.agentId).catch(i=>{const c=i instanceof Error?i.message:String(i),h=i instanceof Error?i.stack:void 0;r.warn("[WebSocket] \u91CD\u8FDE\u5931\u8D25 (attempt #%s), \u7EE7\u7EED\u91CD\u8BD5: %s",this.reconnectAttempts,c),h&&r.warn("[WebSocket] \u91CD\u8FDE\u5931\u8D25\u5806\u6808:",h.slice(0,2e3)),this.scheduleReconnect()})},n)}clearReconnectTimer(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}}export{R as WebSocketClient};
2
2
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.9.102",
3
+ "version": "1.9.103",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,6 +12,7 @@
12
12
  "files": [
13
13
  "dist",
14
14
  "scripts/ensure-bin-executable.mjs",
15
+ "scripts/ensure-playwright-browser.mjs",
15
16
  "package/aws-client-agent-mcp/dist",
16
17
  "package/aws-client-agent-mcp/package.json",
17
18
  "package/aws-client-agent-mcp/README.md",
@@ -38,7 +39,7 @@
38
39
  "build:all": "npm run build:sdk && npm run build:acode && npm run build:agent-mcp",
39
40
  "prebuild": "npm run build:all",
40
41
  "build": "tsc -p tsconfig.json && node scripts/minify-dist.mjs",
41
- "postinstall": "node scripts/ensure-bin-executable.mjs",
42
+ "postinstall": "node scripts/ensure-bin-executable.mjs && node scripts/ensure-playwright-browser.mjs",
42
43
  "pretypecheck": "npm run build:all",
43
44
  "typecheck": "tsc -p tsconfig.json --noEmit",
44
45
  "prestart": "npm run build:sdk && npm run build:agent-mcp && npm run build:acode",
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * postinstall 钩子:确保 Playwright Chromium 已就绪。
4
+ *
5
+ * 背景:npm 包不携带浏览器二进制,Linux 上安装 bridge 后 agent 启动浏览器会报
6
+ * 「Playwright 浏览器没有安装」。本脚本在 npm install 时自动下载 Chromium,
7
+ * 保证 `npm install -g aws-runtime-bridge` 后浏览器立即可用。
8
+ *
9
+ * 策略:
10
+ * - Linux + root(如 sudo npm install -g):`install --with-deps chromium`,
11
+ * 浏览器内核与系统依赖(libnss3 等)一步到位;--with-deps 失败时回退为仅下载内核。
12
+ * - Linux 非 root:仅下载内核,缺系统库时输出可操作的 sudo 提示。
13
+ * - 其他平台(Windows/macOS):`install chromium`(系统库由系统自带)。
14
+ * - 幂等:playwright 检测到浏览器已安装时直接跳过,重复安装无副作用。
15
+ * - 容错:任何失败仅警告并给出手动命令,绝不阻断 npm 安装。
16
+ * - 跳过:设置环境变量 AWS_BRIDGE_SKIP_PLAYWRIGHT_INSTALL=1。
17
+ */
18
+ import { execFile } from "node:child_process";
19
+ import { createRequire } from "node:module";
20
+ import { dirname, join } from "node:path";
21
+ import { promisify } from "node:util";
22
+
23
+ const execFileAsync = promisify(execFile);
24
+ const require = createRequire(import.meta.url);
25
+
26
+ /** 下载超时:Chromium 约 150MB,慢网络下允许 15 分钟。 */
27
+ const INSTALL_TIMEOUT_MS = 15 * 60 * 1000;
28
+
29
+ /** 定位 playwright 包的 CLI 入口(兼容 npm hoist 与嵌套安装两种布局)。 */
30
+ function resolvePlaywrightCliPath() {
31
+ try {
32
+ const packageJsonPath = require.resolve("playwright/package.json");
33
+ return join(dirname(packageJsonPath), "cli.js");
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ async function runInstall(cliPath, args) {
40
+ const { stdout, stderr } = await execFileAsync(
41
+ process.execPath,
42
+ [cliPath, ...args],
43
+ {
44
+ timeout: INSTALL_TIMEOUT_MS,
45
+ maxBuffer: 16 * 1024 * 1024,
46
+ windowsHide: true,
47
+ },
48
+ );
49
+ return `${stdout ?? ""}${stderr ?? ""}`.trim();
50
+ }
51
+
52
+ function errorBrief(error) {
53
+ const err = error;
54
+ const detail =
55
+ `${err?.stdout ?? ""}${err?.stderr ?? ""}`.trim() || err?.message || String(error);
56
+ return detail.slice(0, 300);
57
+ }
58
+
59
+ async function main() {
60
+ if (process.env.AWS_BRIDGE_SKIP_PLAYWRIGHT_INSTALL === "1") {
61
+ console.log(
62
+ "[aws-runtime-bridge] AWS_BRIDGE_SKIP_PLAYWRIGHT_INSTALL=1,跳过 Playwright Chromium 自动安装",
63
+ );
64
+ return;
65
+ }
66
+
67
+ const cliPath = resolvePlaywrightCliPath();
68
+ if (!cliPath) {
69
+ console.warn(
70
+ "[aws-runtime-bridge] 未找到 playwright 包,跳过 Chromium 自动安装(浏览器功能将不可用,可手动执行 npx playwright install chromium)",
71
+ );
72
+ return;
73
+ }
74
+
75
+ const isLinux = process.platform === "linux";
76
+ const isRoot =
77
+ typeof process.getuid === "function" && process.getuid() === 0;
78
+
79
+ console.log(
80
+ "[aws-runtime-bridge] 正在准备 Playwright Chromium(首次安装需下载约 150MB,请耐心等待)...",
81
+ );
82
+
83
+ // Linux + root:连系统依赖一起装(sudo npm install -g 场景一步到位)
84
+ if (isLinux && isRoot) {
85
+ try {
86
+ await runInstall(cliPath, ["install", "--with-deps", "chromium"]);
87
+ console.log("[aws-runtime-bridge] Playwright Chromium 与系统依赖安装完成");
88
+ return;
89
+ } catch (error) {
90
+ console.warn(
91
+ `[aws-runtime-bridge] 系统依赖安装失败(${errorBrief(error)}),回退为仅下载浏览器内核...`,
92
+ );
93
+ }
94
+ } else if (isLinux) {
95
+ console.log(
96
+ "[aws-runtime-bridge] 非 root 环境,仅下载浏览器内核;若后续提示缺少系统依赖,请执行:sudo npx playwright install --with-deps chromium",
97
+ );
98
+ }
99
+
100
+ try {
101
+ await runInstall(cliPath, ["install", "chromium"]);
102
+ console.log("[aws-runtime-bridge] Playwright Chromium 安装完成");
103
+ } catch (error) {
104
+ console.warn(
105
+ "[aws-runtime-bridge] Playwright Chromium 自动安装失败,可稍后手动执行:npx playwright install chromium",
106
+ );
107
+ console.warn(
108
+ " (Linux 提示缺少系统库时:sudo npx playwright install --with-deps chromium)",
109
+ );
110
+ console.warn(` 失败原因:${errorBrief(error)}`);
111
+ }
112
+ }
113
+
114
+ main().catch((error) => {
115
+ console.warn(
116
+ `[aws-runtime-bridge] Playwright Chromium 自动安装异常:${errorBrief(error)}`,
117
+ );
118
+ });