88 resolveEffectiveDebugProxyUrl ,
99 resolveDebugProxySettings ,
1010} from "openclaw/plugin-sdk/proxy-capture" ;
11- import { danger } from "openclaw/plugin-sdk/runtime-env" ;
11+ import { danger , warn } from "openclaw/plugin-sdk/runtime-env" ;
1212import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env" ;
1313import * as ws from "ws" ;
1414import * as discordGateway from "../internal/gateway.js" ;
@@ -31,6 +31,9 @@ export {
3131} from "./gateway-metadata.js" ;
3232
3333const DISCORD_GATEWAY_HANDSHAKE_TIMEOUT_MS = 30_000 ;
34+ const DISCORD_GATEWAY_POLICY_VIOLATION_CLOSE_CODE = 1008 ;
35+ const DISCORD_GATEWAY_WS_RECEIVER_LIMIT_CODE = "WS_ERR_TOO_MANY_BUFFERED_PARTS" ;
36+ const DISCORD_GATEWAY_CLOSE_REASON_LOG_MAX_CHARS = 240 ;
3437const discordDnsLookup = createDiscordDnsLookup ( ) ;
3538
3639type DiscordGatewayWebSocketCtor = new (
@@ -55,6 +58,13 @@ type DiscordGatewayRegistrationState = {
5558 ws ?: unknown ;
5659 isConnecting ?: boolean ;
5760} ;
61+ type DiscordGatewayTransportErrorDetails = {
62+ name ?: string ;
63+ message : string ;
64+ code ?: string ;
65+ closeCode ?: number ;
66+ statusCode ?: number ;
67+ } ;
5868
5969function assignGatewayClient (
6070 plugin : discordGateway . GatewayPlugin ,
@@ -68,6 +78,94 @@ function hasGatewaySocketStarted(plugin: discordGateway.GatewayPlugin): boolean
6878 return state . ws != null || state . isConnecting === true ;
6979}
7080
81+ function readStringProperty ( value : object , key : string ) : string | undefined {
82+ const property = ( value as Record < string , unknown > ) [ key ] ;
83+ return typeof property === "string" && property ? property : undefined ;
84+ }
85+
86+ function readNumberProperty ( value : object , key : string ) : number | undefined {
87+ const property = ( value as Record < string , unknown > ) [ key ] ;
88+ return typeof property === "number" && Number . isFinite ( property ) ? property : undefined ;
89+ }
90+
91+ function describeDiscordGatewayTransportError ( error : Error ) : DiscordGatewayTransportErrorDetails {
92+ const code = readStringProperty ( error , "code" ) ;
93+ const closeCode = readNumberProperty ( error , "closeCode" ) ;
94+ const statusCode = readNumberProperty ( error , "statusCode" ) ;
95+ return {
96+ ...( error . name ? { name : error . name } : { } ) ,
97+ message : error . message ,
98+ ...( code ? { code } : { } ) ,
99+ ...( closeCode !== undefined ? { closeCode } : { } ) ,
100+ ...( statusCode !== undefined ? { statusCode } : { } ) ,
101+ } ;
102+ }
103+
104+ function formatDiscordGatewayCloseReason ( reason : Buffer ) : string {
105+ if ( ! reason . length ) {
106+ return "<empty>" ;
107+ }
108+ const text = reason . toString ( "utf8" ) . replaceAll ( / \s + / g, " " ) . trim ( ) ;
109+ if ( ! text ) {
110+ return `<${ reason . length } bytes>` ;
111+ }
112+ if ( text . length <= DISCORD_GATEWAY_CLOSE_REASON_LOG_MAX_CHARS ) {
113+ return text ;
114+ }
115+ return `${ text . slice ( 0 , DISCORD_GATEWAY_CLOSE_REASON_LOG_MAX_CHARS ) } ...` ;
116+ }
117+
118+ function formatDiscordGatewayTransportErrorLog ( params : {
119+ flowId : string ;
120+ error : DiscordGatewayTransportErrorDetails ;
121+ } ) : string {
122+ const details = [
123+ `flow=${ params . flowId } ` ,
124+ params . error . name ? `name=${ params . error . name } ` : undefined ,
125+ params . error . code ? `code=${ params . error . code } ` : undefined ,
126+ typeof params . error . closeCode === "number" ? `closeCode=${ params . error . closeCode } ` : undefined ,
127+ typeof params . error . statusCode === "number"
128+ ? `statusCode=${ params . error . statusCode } `
129+ : undefined ,
130+ `message=${ params . error . message } ` ,
131+ ] . filter ( Boolean ) ;
132+ return `discord: gateway websocket error ${ details . join ( " " ) } ` ;
133+ }
134+
135+ function formatDiscordGatewayTransportCloseLog ( params : {
136+ flowId : string ;
137+ code : number ;
138+ reason : Buffer ;
139+ lastError ?: DiscordGatewayTransportErrorDetails ;
140+ } ) : string {
141+ const receiverLimit =
142+ params . code === DISCORD_GATEWAY_POLICY_VIOLATION_CLOSE_CODE ||
143+ params . lastError ?. code === DISCORD_GATEWAY_WS_RECEIVER_LIMIT_CODE ;
144+ const details = [
145+ `flow=${ params . flowId } ` ,
146+ `code=${ params . code } ` ,
147+ `reasonBytes=${ params . reason . length } ` ,
148+ `reason=${ formatDiscordGatewayCloseReason ( params . reason ) } ` ,
149+ params . lastError ?. code ? `lastErrorCode=${ params . lastError . code } ` : undefined ,
150+ params . lastError ?. message ? `lastError=${ params . lastError . message } ` : undefined ,
151+ receiverLimit ? "hint=possible ws receiver buffered-parts limit" : undefined ,
152+ ] . filter ( Boolean ) ;
153+ return `discord: gateway websocket closed ${ details . join ( " " ) } ` ;
154+ }
155+
156+ function shouldLogDiscordGatewayTransportClose ( params : {
157+ code : number ;
158+ reason : Buffer ;
159+ lastError ?: DiscordGatewayTransportErrorDetails ;
160+ } ) : boolean {
161+ return (
162+ ( params . code !== 1000 && params . code !== 1001 ) ||
163+ params . reason . length > 0 ||
164+ params . lastError !== undefined ||
165+ params . code === DISCORD_GATEWAY_POLICY_VIOLATION_CLOSE_CODE
166+ ) ;
167+ }
168+
71169type ResolveDiscordGatewayIntentsParams = {
72170 intentsConfig ?: import ( "openclaw/plugin-sdk/config-contracts" ) . DiscordIntentsConfig ;
73171 voiceEnabled ?: boolean ;
@@ -170,6 +268,7 @@ function createGatewayPlugin(params: {
170268 handshakeTimeout : DISCORD_GATEWAY_HANDSHAKE_TIMEOUT_MS ,
171269 ...( params . wsAgent ? { agent : params . wsAgent } : { } ) ,
172270 } ) ;
271+ let lastTransportError : DiscordGatewayTransportErrorDetails | undefined ;
173272 const emitTransportActivity = ( ) => {
174273 if ( ( this as unknown as { ws ?: unknown } ) . ws !== socket ) {
175274 return ;
@@ -195,17 +294,37 @@ function createGatewayPlugin(params: {
195294 } ) ;
196295 } ) ;
197296 socket . on ?.( "close" , ( code : number , reason : Buffer ) => {
297+ const closeReason = Buffer . isBuffer ( reason ) ? reason : Buffer . from ( String ( reason ?? "" ) ) ;
198298 captureWsEvent ( {
199299 url,
200300 direction : "local" ,
201301 kind : "ws-close" ,
202302 flowId : wsFlowId ,
203303 closeCode : code ,
204- payload : reason ,
304+ payload : closeReason ,
205305 meta : { subsystem : "discord-gateway" } ,
206306 } ) ;
307+ if (
308+ shouldLogDiscordGatewayTransportClose ( {
309+ code,
310+ reason : closeReason ,
311+ lastError : lastTransportError ,
312+ } )
313+ ) {
314+ params . runtime ?. log ?.(
315+ warn (
316+ formatDiscordGatewayTransportCloseLog ( {
317+ flowId : wsFlowId ,
318+ code,
319+ reason : closeReason ,
320+ lastError : lastTransportError ,
321+ } ) ,
322+ ) ,
323+ ) ;
324+ }
207325 } ) ;
208326 socket . on ?.( "error" , ( error : Error ) => {
327+ lastTransportError = describeDiscordGatewayTransportError ( error ) ;
209328 captureWsEvent ( {
210329 url,
211330 direction : "local" ,
@@ -214,6 +333,11 @@ function createGatewayPlugin(params: {
214333 errorText : error . message ,
215334 meta : { subsystem : "discord-gateway" } ,
216335 } ) ;
336+ params . runtime ?. log ?.(
337+ warn (
338+ formatDiscordGatewayTransportErrorLog ( { flowId : wsFlowId , error : lastTransportError } ) ,
339+ ) ,
340+ ) ;
217341 } ) ;
218342 if ( "binaryType" in socket ) {
219343 try {
0 commit comments