@openclaw/slack 2026.9.7 → 2026.10.1-beta.1

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 (103) hide show
  1. package/dist/.setup/{account-inspect-DWBXpER6.mjs → account-inspect-Cs-7DXXW.mjs} +1 -1
  2. package/dist/.setup/{accounts-D_N9IqNA.mjs → accounts-CDgkV7Cm.mjs} +4 -16
  3. package/dist/.setup/{action-runtime-DfWvuiZd.mjs → action-runtime-DJ1daRAZ.mjs} +5 -7
  4. package/dist/.setup/action-runtime.runtime-CdWL2mLC.mjs +2 -0
  5. package/dist/.setup/{actions-Df0CzcDl.mjs → actions-DDoAJnrH.mjs} +16 -35
  6. package/dist/.setup/{approval-handler.runtime-Ed6Sw5VK.mjs → approval-handler.runtime-BcE_shWc.mjs} +5 -5
  7. package/dist/.setup/{approval-message-updates-BadvtA7R.mjs → approval-message-updates-EgKGmFXx.mjs} +1 -1
  8. package/dist/.setup/{channel-C7bgPSTE.mjs → channel-DgKSu8Iv.mjs} +46 -48
  9. package/dist/.setup/{channel-type-ClOttrZ5.mjs → channel-type-B9zYYzBU.mjs} +4 -4
  10. package/dist/.setup/{channel.setup-Dh2jzAjN.mjs → channel.setup-BYIlwbQ7.mjs} +27 -27
  11. package/dist/.setup/{config-schema-BZCxmHKG.mjs → config-schema-BH0K9-Um.mjs} +4 -0
  12. package/dist/.setup/{directory-config-BOUWH3hR.mjs → directory-config-B_by7zrc.mjs} +2 -2
  13. package/dist/.setup/{directory-live-B2O4B_Ho.mjs → directory-live-C48wazJt.mjs} +3 -3
  14. package/dist/.setup/{doctor-contract-CRN1Z_c7.mjs → doctor-contract-Cd4-VzIM.mjs} +28 -28
  15. package/dist/.setup/{message-tool-api-Cu4lwlGy.mjs → message-tool-api-CcjLPN3v.mjs} +2 -2
  16. package/dist/.setup/{monitor-BPVfkumU.mjs → monitor-BuYBDmm7.mjs} +190 -315
  17. package/dist/.setup/{outbound-adapter-Bv48XUjq.mjs → outbound-adapter-BZFkK-mw.mjs} +6 -17
  18. package/dist/.setup/{pipeline.runtime-B1H63Frf.mjs → pipeline.runtime-CIepKmZt.mjs} +241 -247
  19. package/dist/.setup/{policy-D-jfM174.mjs → policy-KKTqEiDv.mjs} +12 -22
  20. package/dist/.setup/{probe-B2Y9qc_z.mjs → probe-BVlJ3dp0.mjs} +4 -7
  21. package/dist/.setup/{relay-source-BpKdjsll.mjs → relay-source-CC3h6M7y.mjs} +2 -2
  22. package/dist/.setup/{replies-BAnUtr2f.mjs → replies-DHbuthIR.mjs} +12 -26
  23. package/dist/.setup/{reply-blocks-SE1kTE3I.mjs → reply-blocks-CKnO4BSG.mjs} +4 -29
  24. package/dist/.setup/{resolve-channels-XXBn8UU_.mjs → resolve-channels-HR0TgArR.mjs} +2 -2
  25. package/dist/.setup/{resolve-users-DW0ngD03.mjs → resolve-users-BNYBhF0q.mjs} +4 -4
  26. package/dist/.setup/rolldown-runtime-B4FMCO8f.mjs +28 -0
  27. package/dist/.setup/{scopes-CasuDvaa.mjs → scopes-DrDbyUH2.mjs} +1 -1
  28. package/dist/.setup/secret-contract-B-Qm6aQQ.mjs +65 -0
  29. package/dist/.setup/{secret-contract-api-Dp6290Vg.mjs → secret-contract-api-DmUVxIRs.mjs} +1 -1
  30. package/dist/.setup/{security-audit-BqDc-6VL.mjs → security-audit-C1nOGzJa.mjs} +1 -1
  31. package/dist/.setup/{send-BOVLQi3n.mjs → send-CgPI3Zj8.mjs} +22 -53
  32. package/dist/.setup/{session-status-DscajIsd.mjs → session-status-D46GHwOV.mjs} +39 -49
  33. package/dist/.setup/{setup-plugin-api-DTtbTKNk.mjs → setup-plugin-api-ZrHs0N4U.mjs} +1 -1
  34. package/dist/.setup/{setup-surface-Dn7QgWRE.mjs → setup-surface-83lxvE2y.mjs} +10 -13
  35. package/dist/.setup/{shared-qau-uDGl.mjs → shared-VtmYNj-v.mjs} +53 -57
  36. package/dist/.setup/{slash-dispatch.runtime--AEzLVBF.mjs → slash-dispatch.runtime-gBj_uaLM.mjs} +1 -1
  37. package/dist/account-inspect-api.js +1 -1
  38. package/dist/api.js +13 -13
  39. package/dist/channel-config-api.js +1 -1
  40. package/dist/channel-plugin-api.js +1 -1
  41. package/dist/control-ui/2f4b1e827be6b68ced2ea0d8b7da8529a4eafbe72be27ee37e1064bdd530ec10/index.js +1 -0
  42. package/dist/directory-contract-api.js +1 -1
  43. package/dist/doctor-contract-api.js +1 -1
  44. package/dist/message-tool-api.js +1 -1
  45. package/dist/runtime-api.js +10 -10
  46. package/dist/secret-contract-api.js +1 -1
  47. package/dist/security-contract-api.js +1 -1
  48. package/dist/setup-entry.js +2 -2
  49. package/dist/setup-plugin-api.js +1 -1
  50. package/node_modules/undici/README.md +10 -1
  51. package/node_modules/undici/docs/docs/api/Agent.md +8 -8
  52. package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +9 -6
  53. package/node_modules/undici/docs/docs/api/Dispatcher.md +9 -0
  54. package/node_modules/undici/docs/docs/api/Errors.md +25 -0
  55. package/node_modules/undici/docs/docs/api/Fetch.md +10 -0
  56. package/node_modules/undici/docs/docs/api/Interceptors.md +4 -3
  57. package/node_modules/undici/docs/docs/api/Pool.md +5 -6
  58. package/node_modules/undici/docs/docs/index.md +10 -1
  59. package/node_modules/undici/lib/api/readable.js +1 -2
  60. package/node_modules/undici/lib/core/request.js +23 -2
  61. package/node_modules/undici/lib/core/util.js +0 -39
  62. package/node_modules/undici/lib/dispatcher/client-h1.js +11 -4
  63. package/node_modules/undici/lib/dispatcher/client-h2.js +52 -8
  64. package/node_modules/undici/lib/dispatcher/client.js +6 -1
  65. package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +21 -6
  66. package/node_modules/undici/lib/dispatcher/pool-base.js +63 -2
  67. package/node_modules/undici/lib/dispatcher/pool.js +90 -7
  68. package/node_modules/undici/lib/dispatcher/proxy-agent.js +16 -3
  69. package/node_modules/undici/lib/handler/deduplication-handler.js +24 -11
  70. package/node_modules/undici/lib/handler/redirect-handler.js +5 -0
  71. package/node_modules/undici/lib/handler/retry-handler.js +72 -13
  72. package/node_modules/undici/lib/interceptor/cache.js +4 -1
  73. package/node_modules/undici/lib/interceptor/decompress.js +236 -69
  74. package/node_modules/undici/lib/web/cache/cache.js +17 -8
  75. package/node_modules/undici/lib/web/cache/cachestorage.js +11 -5
  76. package/node_modules/undici/lib/web/cookies/index.js +19 -7
  77. package/node_modules/undici/lib/web/cookies/util.js +3 -1
  78. package/node_modules/undici/lib/web/eventsource/eventsource.js +25 -1
  79. package/node_modules/undici/lib/web/fetch/body.js +22 -15
  80. package/node_modules/undici/lib/web/fetch/data-url.js +6 -6
  81. package/node_modules/undici/lib/web/fetch/formdata.js +30 -36
  82. package/node_modules/undici/lib/web/fetch/headers.js +34 -31
  83. package/node_modules/undici/lib/web/fetch/index.js +46 -25
  84. package/node_modules/undici/lib/web/fetch/request.js +74 -79
  85. package/node_modules/undici/lib/web/fetch/response.js +43 -44
  86. package/node_modules/undici/lib/web/fetch/util.js +7 -7
  87. package/node_modules/undici/lib/web/infra/index.js +16 -1
  88. package/node_modules/undici/lib/web/webidl/index.js +2 -14
  89. package/node_modules/undici/lib/web/websocket/connection.js +33 -12
  90. package/node_modules/undici/lib/web/websocket/events.js +43 -26
  91. package/node_modules/undici/lib/web/websocket/receiver.js +2 -0
  92. package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +13 -10
  93. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +19 -4
  94. package/node_modules/undici/lib/web/websocket/websocket.js +44 -41
  95. package/node_modules/undici/package.json +1 -1
  96. package/node_modules/undici/types/diagnostics-channel.d.ts +2 -1
  97. package/node_modules/undici/types/interceptors.d.ts +1 -1
  98. package/node_modules/undici/types/webidl.d.ts +13 -7
  99. package/openclaw.plugin.json +4 -1
  100. package/package.json +14 -6
  101. package/dist/.setup/action-runtime.runtime-Ck4bAWOX.mjs +0 -2
  102. package/dist/.setup/secret-contract-BaZcu6P_.mjs +0 -82
  103. package/dist/.setup/streaming-compat-C5Wut626.mjs +0 -62
@@ -5,6 +5,9 @@ const {
5
5
  kClients,
6
6
  kNeedDrain,
7
7
  kAddClient,
8
+ kDrainQueue,
9
+ kOnClientBusy,
10
+ kOnClientDrain,
8
11
  kGetDispatcher,
9
12
  kHasDispatcher,
10
13
  kRemoveClient
@@ -14,17 +17,41 @@ const {
14
17
  InvalidArgumentError
15
18
  } = require('../core/errors')
16
19
  const util = require('../core/util')
17
- const { kUrl } = require('../core/symbols')
20
+ const { kConnecting, kHTTPContext, kUrl } = require('../core/symbols')
18
21
  const buildConnector = require('../core/connect')
19
22
 
20
23
  const kOptions = Symbol('options')
21
24
  const kConnections = Symbol('connections')
22
25
  const kFactory = Symbol('factory')
26
+ const kProtocol = Symbol('protocol')
27
+ const kProtocolProbe = Symbol('protocol probe')
23
28
 
24
29
  function defaultFactory (origin, opts) {
25
30
  return new Client(origin, opts)
26
31
  }
27
32
 
33
+ function shouldCreateProtocolProbe (pool, dispatcher) {
34
+ return dispatcher instanceof Client &&
35
+ pool[kProtocol] !== 'h1' &&
36
+ (pool[kOptions].useH2c === true || (pool[kUrl].protocol === 'https:' && pool[kOptions].allowH2 !== false))
37
+ }
38
+
39
+ function createClient (pool) {
40
+ const dispatcher = pool[kFactory](pool[kUrl], pool[kOptions])
41
+
42
+ // HTTPS does not reveal whether the peer selected h1 or h2 until ALPN
43
+ // completes. While h2 is still possible, let one Client probe the protocol
44
+ // and keep later requests in the Pool queue instead of opening one TLS
45
+ // connection per request. A confirmed h1 connection disables this gate and
46
+ // restores the usual Pool fan-out.
47
+ if (shouldCreateProtocolProbe(pool, dispatcher)) {
48
+ pool[kProtocolProbe] = dispatcher
49
+ }
50
+
51
+ pool[kAddClient](dispatcher)
52
+ return dispatcher
53
+ }
54
+
28
55
  class Pool extends PoolBase {
29
56
  constructor (origin, {
30
57
  connections,
@@ -72,6 +99,8 @@ class Pool extends PoolBase {
72
99
  this[kUrl] = util.parseOrigin(origin)
73
100
  this[kOptions] = { ...util.deepClone(options), connect, allowH2, useH2c, clientTtl, socketPath }
74
101
  this[kFactory] = factory
102
+ this[kProtocol] = null
103
+ this[kProtocolProbe] = null
75
104
 
76
105
  this.on('connect', (origin, targets) => {
77
106
  if (clientTtl != null && clientTtl > 0) {
@@ -79,13 +108,42 @@ class Pool extends PoolBase {
79
108
  Object.assign(target, { ttl: Date.now() })
80
109
  }
81
110
  }
111
+
112
+ const client = targets[targets.length - 1]
113
+ if (client instanceof Client) {
114
+ this[kProtocol] = client[kHTTPContext]?.version
115
+ }
116
+
117
+ if (client === this[kProtocolProbe]) {
118
+ // An h2 Client's drain event releases the requests accumulated during
119
+ // negotiation onto that Client. If ALPN selected h1, release the probe
120
+ // immediately and restore normal Pool fan-out instead.
121
+ if (this[kProtocol] !== 'h2') {
122
+ this[kProtocolProbe] = null
123
+ this[kDrainQueue](origin, targets.slice(1))
124
+ }
125
+ }
126
+ })
127
+
128
+ this.on('disconnect', (origin, targets) => {
129
+ if (targets.includes(this[kProtocolProbe])) {
130
+ this[kProtocolProbe] = null
131
+ this[kDrainQueue](origin, targets.slice(1))
132
+ }
82
133
  })
83
134
 
84
- this.on('connectionError', (origin, targets, error) => {
135
+ this.on('connectionError', (origin, targets) => {
136
+ let resumeQueued = false
137
+
85
138
  // If a connection error occurs, we remove the client from the pool,
86
139
  // and emit a connectionError event. They will not be re-used.
87
140
  // Fixes https://github.com/nodejs/undici/issues/3895
88
141
  for (const target of targets) {
142
+ if (target === this[kProtocolProbe]) {
143
+ this[kProtocolProbe] = null
144
+ resumeQueued = true
145
+ }
146
+
89
147
  // Do not use kRemoveClient here, as it will close the client,
90
148
  // but the client cannot be closed in this state.
91
149
  const idx = this[kClients].indexOf(target)
@@ -93,9 +151,29 @@ class Pool extends PoolBase {
93
151
  this[kClients].splice(idx, 1)
94
152
  }
95
153
  }
154
+
155
+ if (resumeQueued) {
156
+ this[kDrainQueue](origin, targets.slice(1))
157
+ }
96
158
  })
97
159
  }
98
160
 
161
+ [kOnClientBusy] (client) {
162
+ if (
163
+ this[kProtocolProbe] === null &&
164
+ client[kConnecting] &&
165
+ shouldCreateProtocolProbe(this, client)
166
+ ) {
167
+ this[kProtocolProbe] = client
168
+ }
169
+ }
170
+
171
+ [kOnClientDrain] (client) {
172
+ if (client === this[kProtocolProbe]) {
173
+ this[kProtocolProbe] = null
174
+ }
175
+ }
176
+
99
177
  [kGetDispatcher] () {
100
178
  const clientTtlOption = this[kOptions].clientTtl
101
179
  for (let i = 0; i < this[kClients].length; i++) {
@@ -110,10 +188,12 @@ class Pool extends PoolBase {
110
188
  }
111
189
  }
112
190
 
191
+ if (this[kProtocolProbe] !== null) {
192
+ return
193
+ }
194
+
113
195
  if (!this[kConnections] || this[kClients].length < this[kConnections]) {
114
- const dispatcher = this[kFactory](this[kUrl], this[kOptions])
115
- this[kAddClient](dispatcher)
116
- return dispatcher
196
+ return createClient(this)
117
197
  }
118
198
  }
119
199
 
@@ -130,9 +210,12 @@ class Pool extends PoolBase {
130
210
  }
131
211
  }
132
212
 
213
+ if (this[kProtocolProbe] !== null) {
214
+ return false
215
+ }
216
+
133
217
  if (!this[kConnections] || this[kClients].length < this[kConnections]) {
134
- const dispatcher = this[kFactory](this[kUrl], this[kOptions])
135
- this[kAddClient](dispatcher)
218
+ createClient(this)
136
219
  return true
137
220
  }
138
221
 
@@ -325,8 +325,8 @@ class ProxyAgent extends DispatcherBase {
325
325
  }
326
326
 
327
327
  /**
328
- * @param {string[] | Record<string, string>} headers
329
- * @returns {Record<string, string>}
328
+ * @param {string[] | Record<string, string> | Iterable<[string, string | string[] | undefined]>} headers
329
+ * @returns {Record<string, string | string[] | undefined>}
330
330
  */
331
331
  function buildHeaders (headers) {
332
332
  // When using undici.fetch, the headers list is stored
@@ -355,7 +355,20 @@ function buildHeaders (headers) {
355
355
  const headersPair = {}
356
356
 
357
357
  for (const [key, value] of headers) {
358
- headersPair[key] = value
358
+ if (!Object.hasOwn(headersPair, key)) {
359
+ headersPair[key] = value
360
+ continue
361
+ }
362
+
363
+ const previous = headersPair[key]
364
+ const values = []
365
+ if (previous !== undefined) {
366
+ values.push(...(Array.isArray(previous) ? previous : [previous]))
367
+ }
368
+ if (value !== undefined) {
369
+ values.push(...(Array.isArray(value) ? value : [value]))
370
+ }
371
+ headersPair[key] = values.length > 1 ? values : values[0]
359
372
  }
360
373
 
361
374
  return headersPair
@@ -251,8 +251,11 @@ class DeduplicationHandler {
251
251
  return
252
252
  }
253
253
 
254
- this.#completed = true
254
+ // Remove the entry before callbacks can synchronously dispatch a retry.
255
+ this.#cleanup()
255
256
  this.#primaryHandler.onResponseEnd?.(controller, trailers)
257
+ // A throwing end callback must still be handled by onResponseError.
258
+ this.#completed = true
256
259
 
257
260
  for (const waitingHandler of this.#waitingHandlers) {
258
261
  if (waitingHandler.done || waitingHandler.controller.aborted) {
@@ -267,22 +270,21 @@ class DeduplicationHandler {
267
270
  continue
268
271
  }
269
272
 
270
- if (waitingHandler.controller.paused && waitingHandler.bufferedChunks.length > 0) {
273
+ if (waitingHandler.controller.paused) {
271
274
  waitingHandler.pendingTrailers = trailers
272
275
  continue
273
276
  }
274
277
 
275
278
  try {
276
279
  waitingHandler.handler.onResponseEnd?.(waitingHandler.controller, trailers)
277
- } catch {
278
- // Ignore errors from waiting handlers
280
+ } catch (err) {
281
+ this.#errorWaitingHandler(waitingHandler, err)
279
282
  }
280
283
 
281
284
  waitingHandler.done = true
282
285
  }
283
286
 
284
287
  this.#pruneDoneWaitingHandlers()
285
- this.#onComplete?.()
286
288
  }
287
289
 
288
290
  /**
@@ -296,6 +298,7 @@ class DeduplicationHandler {
296
298
 
297
299
  this.#aborted = true
298
300
  this.#completed = true
301
+ this.#cleanup()
299
302
 
300
303
  this.#primaryHandler.onResponseError?.(controller, err)
301
304
 
@@ -304,7 +307,12 @@ class DeduplicationHandler {
304
307
  }
305
308
 
306
309
  this.#waitingHandlers = []
307
- this.#onComplete?.()
310
+ }
311
+
312
+ #cleanup () {
313
+ const onComplete = this.#onComplete
314
+ this.#onComplete = null
315
+ onComplete?.()
308
316
  }
309
317
 
310
318
  /**
@@ -346,8 +354,8 @@ class DeduplicationHandler {
346
354
  ) {
347
355
  try {
348
356
  waitingHandler.handler.onResponseEnd?.(waitingHandler.controller, waitingHandler.pendingTrailers)
349
- } catch {
350
- // Ignore errors from waiting handlers
357
+ } catch (err) {
358
+ this.#errorWaitingHandler(waitingHandler, err)
351
359
  }
352
360
 
353
361
  waitingHandler.pendingTrailers = null
@@ -405,7 +413,7 @@ class DeduplicationHandler {
405
413
 
406
414
  if (waitingHandler.bufferedBytes > this.#maxBufferSize) {
407
415
  const err = new RequestAbortedError(`Deduplicated waiting handler exceeded maxBufferSize (${this.#maxBufferSize} bytes) while paused`)
408
- this.#errorWaitingHandler(waitingHandler, err)
416
+ waitingHandler.controller.abort(err)
409
417
  }
410
418
  }
411
419
 
@@ -454,8 +462,13 @@ class DeduplicationHandler {
454
462
  waitingHandler.bufferedChunks = []
455
463
  waitingHandler.bufferedBytes = 0
456
464
 
457
- // controller.abort(err) notifies the handler via onResponseError
458
- waitingHandler.controller.abort(err)
465
+ // A response failure is not a consumer abort: retry handlers must be able
466
+ // to retry it just as they would a failure of the primary request.
467
+ try {
468
+ waitingHandler.handler.onResponseError?.(waitingHandler.controller, err)
469
+ } catch {
470
+ // Ignore errors from waiting handlers
471
+ }
459
472
  }
460
473
 
461
474
  #pruneDoneWaitingHandlers () {
@@ -57,6 +57,11 @@ class RedirectHandler {
57
57
  }
58
58
 
59
59
  onResponseStart (controller, statusCode, headers, statusMessage) {
60
+ if (statusCode < 200) {
61
+ this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
62
+ return
63
+ }
64
+
60
65
  if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
61
66
  throw new Error('max redirects')
62
67
  }
@@ -47,34 +47,51 @@ function validatePartialResponseContentLength (headers, range, statusCode, retry
47
47
  // so nothing outside the handler can trigger it.
48
48
  class RetryController {
49
49
  #onAbort
50
+ #paused = false
51
+ #target = null
50
52
 
51
53
  constructor (onAbort) {
52
54
  this.#onAbort = onAbort
53
- this.target = null
54
55
  }
55
56
 
56
- pause () { this.target?.pause() }
57
- resume () { this.target?.resume() }
57
+ set target (target) {
58
+ this.#target = target
59
+ if (this.#paused) {
60
+ target?.pause()
61
+ }
62
+ }
63
+
64
+ get target () { return this.#target }
65
+
66
+ pause () {
67
+ this.#paused = true
68
+ this.#target?.pause()
69
+ }
70
+
71
+ resume () {
72
+ this.#paused = false
73
+ this.#target?.resume()
74
+ }
58
75
 
59
76
  abort (reason) {
60
- this.target?.abort(reason)
77
+ this.#target?.abort(reason)
61
78
  this.#onAbort(reason)
62
79
  }
63
80
 
64
- get paused () { return this.target?.paused ?? false }
65
- get aborted () { return this.target?.aborted ?? false }
66
- get reason () { return this.target?.reason ?? null }
67
- get rawHeaders () { return this.target?.rawHeaders ?? null }
81
+ get paused () { return this.#paused || (this.#target?.paused ?? false) }
82
+ get aborted () { return this.#target?.aborted ?? false }
83
+ get reason () { return this.#target?.reason ?? null }
84
+ get rawHeaders () { return this.#target?.rawHeaders ?? null }
68
85
  set rawHeaders (value) {
69
- if (this.target) {
70
- this.target.rawHeaders = value
86
+ if (this.#target) {
87
+ this.#target.rawHeaders = value
71
88
  }
72
89
  }
73
90
 
74
- get rawTrailers () { return this.target?.rawTrailers ?? null }
91
+ get rawTrailers () { return this.#target?.rawTrailers ?? null }
75
92
  set rawTrailers (value) {
76
- if (this.target) {
77
- this.target.rawTrailers = value
93
+ if (this.#target) {
94
+ this.#target.rawTrailers = value
78
95
  }
79
96
  }
80
97
  }
@@ -142,6 +159,12 @@ class RetryHandler {
142
159
  // Backoff timer returned by the retry policy, so #onAbort can cancel it.
143
160
  // Null for custom policies that do not return their timer.
144
161
  this.retryTimer = null
162
+ // A response can complete while its controller is paused if the peer closes
163
+ // the connection. Hold its body until the retry policy decides whether to
164
+ // discard it for a retry or forward it as the final response.
165
+ this.pendingResponseData = null
166
+ this.pendingResponseTrailers = null
167
+ this.pendingResponseEnded = false
145
168
  // Set once an abort during the backoff delivered the terminal error
146
169
  // downstream; late policy callbacks and connection errors are then moot.
147
170
  this.aborted = false
@@ -183,6 +206,13 @@ class RetryHandler {
183
206
  this.retryPending = false
184
207
  this.retryTimer = null
185
208
 
209
+ const pendingData = this.pendingResponseData
210
+ const pendingTrailers = this.pendingResponseTrailers
211
+ const pendingEnd = this.pendingResponseEnded
212
+ this.pendingResponseData = null
213
+ this.pendingResponseTrailers = null
214
+ this.pendingResponseEnded = false
215
+
186
216
  if (passedErr) {
187
217
  if (this.headersSent) {
188
218
  // The downstream handler already received the response from an
@@ -193,6 +223,15 @@ class RetryHandler {
193
223
  this.headersSent = true
194
224
  this.checkpointResponseEnd(headers)
195
225
  this.handler.onResponseStart?.(this.controllerProxy, statusCode, headers, statusMessage)
226
+ controller.resume()
227
+
228
+ if (pendingEnd) {
229
+ for (const chunk of pendingData) {
230
+ this.onResponseData(controller, chunk)
231
+ }
232
+ this.onResponseEnd(controller, pendingTrailers)
233
+ }
234
+ return
196
235
  }
197
236
  controller.resume()
198
237
  return
@@ -200,6 +239,9 @@ class RetryHandler {
200
239
 
201
240
  this.error = err
202
241
  controller.resume()
242
+ if (pendingEnd) {
243
+ this.onResponseEnd(controller, pendingTrailers)
244
+ }
203
245
  }
204
246
 
205
247
  // The pause()/resume() pair (here and in shouldRetry) acts on THIS
@@ -213,6 +255,9 @@ class RetryHandler {
213
255
  // The default policy returns its backoff timer so an abort can cancel it;
214
256
  // a custom policy may return anything (or nothing), which is ignored.
215
257
  this.retryPending = true
258
+ this.pendingResponseData = []
259
+ this.pendingResponseTrailers = null
260
+ this.pendingResponseEnded = false
216
261
  this.retryTimer = this.retryOpts.retry(
217
262
  err,
218
263
  {
@@ -464,6 +509,11 @@ class RetryHandler {
464
509
  }
465
510
 
466
511
  onResponseData (_controller, chunk) {
512
+ if (this.pendingResponseData !== null) {
513
+ this.pendingResponseData.push(chunk)
514
+ return
515
+ }
516
+
467
517
  if (this.error) {
468
518
  return
469
519
  }
@@ -474,6 +524,12 @@ class RetryHandler {
474
524
  }
475
525
 
476
526
  onResponseEnd (_controller, trailers) {
527
+ if (this.pendingResponseData !== null) {
528
+ this.pendingResponseTrailers = trailers
529
+ this.pendingResponseEnded = true
530
+ return
531
+ }
532
+
477
533
  if (this.error && this.retryOpts.throwOnError) {
478
534
  throw this.error
479
535
  }
@@ -587,6 +643,9 @@ class RetryHandler {
587
643
  this.retryPending = false
588
644
  clearTimeout(this.retryTimer)
589
645
  this.retryTimer = null
646
+ this.pendingResponseData = null
647
+ this.pendingResponseTrailers = null
648
+ this.pendingResponseEnded = false
590
649
  this.handler.onResponseError?.(this.controllerProxy, reason ?? new RequestAbortedError())
591
650
  }
592
651
  }
@@ -330,6 +330,8 @@ function sendCachedValue (handler, opts, result, age, context, isStale) {
330
330
  assert(!stream.destroyed, 'stream should not be destroyed')
331
331
  assert(!stream.readableDidRead, 'stream should not be readableDidRead')
332
332
 
333
+ let aborted = false
334
+
333
335
  const controller = {
334
336
  rawHeaders: [],
335
337
  rawTrailers: [],
@@ -343,12 +345,13 @@ function sendCachedValue (handler, opts, result, age, context, isStale) {
343
345
  return stream.isPaused()
344
346
  },
345
347
  get aborted () {
346
- return stream.destroyed
348
+ return aborted
347
349
  },
348
350
  get reason () {
349
351
  return stream.errored
350
352
  },
351
353
  abort (reason) {
354
+ aborted = true
352
355
  stream.destroy(reason ?? new AbortError())
353
356
  }
354
357
  }