pi-sessions 0.8.0 → 0.9.0

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 (104) hide show
  1. package/README.md +70 -37
  2. package/extensions/pi-sessions.ts +125 -0
  3. package/extensions/session-ask/agent.ts +34 -14
  4. package/extensions/{session-ask.ts → session-ask/install.ts} +73 -90
  5. package/extensions/session-ask/navigate.ts +3 -1
  6. package/extensions/session-ask/presenter.ts +23 -0
  7. package/extensions/session-ask/renderer.ts +63 -0
  8. package/extensions/session-ask/tool-contract.ts +27 -0
  9. package/extensions/session-ask/view-model.ts +18 -0
  10. package/extensions/session-auto-title/generate.ts +18 -18
  11. package/extensions/{session-auto-title.ts → session-auto-title/install.ts} +60 -53
  12. package/extensions/session-auto-title/model.ts +25 -17
  13. package/extensions/session-auto-title/retitle.ts +17 -18
  14. package/extensions/session-auto-title/wizard.ts +12 -11
  15. package/extensions/session-handoff/board-view-model.ts +181 -0
  16. package/extensions/session-handoff/board.ts +663 -0
  17. package/extensions/session-handoff/bootstrap.ts +174 -0
  18. package/extensions/session-handoff/extract.ts +101 -122
  19. package/extensions/session-handoff/install.ts +225 -0
  20. package/extensions/session-handoff/kickoff.ts +92 -0
  21. package/extensions/session-handoff/launch/backend.ts +16 -0
  22. package/extensions/session-handoff/launch/deferred.ts +20 -0
  23. package/extensions/session-handoff/launch/ghostty.ts +99 -0
  24. package/extensions/session-handoff/launch/resolution.ts +57 -0
  25. package/extensions/session-handoff/launch/shell.ts +7 -0
  26. package/extensions/session-handoff/launch/tmux.ts +31 -0
  27. package/extensions/session-handoff/launch-options.ts +61 -0
  28. package/extensions/session-handoff/launch-target.ts +91 -0
  29. package/extensions/session-handoff/metadata.ts +152 -63
  30. package/extensions/session-handoff/model.ts +58 -0
  31. package/extensions/session-handoff/query.ts +0 -1
  32. package/extensions/session-handoff/receipt.ts +96 -0
  33. package/extensions/session-handoff/review.ts +2 -28
  34. package/extensions/session-handoff/spawn.ts +76 -181
  35. package/extensions/session-handoff/strong-modal.ts +13 -0
  36. package/extensions/session-handoff/tool-contract.ts +12 -0
  37. package/extensions/session-handoff/tool-presenter.ts +35 -0
  38. package/extensions/session-handoff/tool-renderer.ts +66 -0
  39. package/extensions/session-handoff/tool-schema.ts +54 -0
  40. package/extensions/session-handoff/tool-view-model.ts +49 -0
  41. package/extensions/session-handoff/tool.ts +206 -0
  42. package/extensions/session-handoff/ui.ts +66 -0
  43. package/extensions/session-hooks/install.ts +75 -0
  44. package/extensions/{session-index.ts → session-index/install.ts} +9 -7
  45. package/extensions/session-messaging/broker/process.ts +21 -28
  46. package/extensions/session-messaging/install.ts +78 -0
  47. package/extensions/session-messaging/pi/cancel-session-presenter.ts +64 -0
  48. package/extensions/session-messaging/pi/incoming-message-presenter.ts +32 -0
  49. package/extensions/session-messaging/pi/incoming-message-view-model.ts +20 -0
  50. package/extensions/session-messaging/pi/incoming-runtime.ts +27 -23
  51. package/extensions/session-messaging/pi/message-contracts.ts +32 -0
  52. package/extensions/session-messaging/pi/renderer.ts +19 -12
  53. package/extensions/session-messaging/pi/send-message-presenter.ts +39 -0
  54. package/extensions/session-messaging/pi/send-message-view-model.ts +54 -0
  55. package/extensions/session-messaging/pi/service.ts +262 -81
  56. package/extensions/session-messaging/pi/tools.ts +184 -40
  57. package/extensions/session-search/extract.ts +60 -14
  58. package/extensions/session-search/hooks.ts +10 -13
  59. package/extensions/session-search/install.ts +373 -0
  60. package/extensions/session-search/renderer.ts +195 -0
  61. package/extensions/session-search/tool-contract.ts +51 -0
  62. package/extensions/shared/composition.ts +20 -0
  63. package/extensions/shared/errors.ts +3 -0
  64. package/extensions/shared/model-resolution.ts +73 -0
  65. package/extensions/shared/model-runtime.ts +33 -0
  66. package/extensions/shared/model.ts +7 -10
  67. package/extensions/shared/rendering/collapsible-text.ts +89 -0
  68. package/extensions/shared/rendering/expandable-content-layout.ts +84 -0
  69. package/extensions/shared/rendering/theme.ts +5 -0
  70. package/extensions/shared/session-broker/client.ts +30 -29
  71. package/extensions/shared/session-broker/protocol.ts +128 -10
  72. package/extensions/shared/session-index/common.ts +5 -5
  73. package/extensions/shared/session-index/lineage.ts +0 -3
  74. package/extensions/shared/session-index/schema.ts +0 -1
  75. package/extensions/shared/session-index/scoring.ts +1 -2
  76. package/extensions/shared/session-index/search.ts +11 -4
  77. package/extensions/shared/session-index/store.ts +4 -9
  78. package/extensions/shared/settings.ts +76 -38
  79. package/extensions/shared/text.ts +5 -0
  80. package/extensions/shared/thinking-levels.ts +22 -0
  81. package/extensions/shared/tmux.ts +205 -0
  82. package/extensions/subagents/cancel.ts +111 -0
  83. package/extensions/subagents/classify.ts +52 -0
  84. package/extensions/subagents/install.ts +344 -0
  85. package/extensions/subagents/launch-target.ts +75 -0
  86. package/extensions/subagents/ledger.ts +217 -0
  87. package/extensions/subagents/reconcile.ts +386 -0
  88. package/extensions/subagents/report-message-presenter.ts +36 -0
  89. package/extensions/subagents/report-message-renderer.ts +21 -0
  90. package/extensions/subagents/report-message-view-model.ts +29 -0
  91. package/extensions/subagents/report.ts +159 -0
  92. package/extensions/subagents/roster.ts +262 -0
  93. package/extensions/subagents/wake.ts +173 -0
  94. package/images/handoff-board-subagents.png +0 -0
  95. package/images/handoff-board-user-sessions.png +0 -0
  96. package/images/handoff-subagent-report.png +0 -0
  97. package/images/session-handoff-tool.png +0 -0
  98. package/package.json +10 -16
  99. package/extensions/session-handoff.ts +0 -741
  100. package/extensions/session-hooks.ts +0 -75
  101. package/extensions/session-messaging/pi/message-view.ts +0 -131
  102. package/extensions/session-messaging.ts +0 -30
  103. package/extensions/session-search.ts +0 -419
  104. package/extensions/shared/session-broker/active.ts +0 -26
@@ -1,15 +1,20 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ import { formatError } from "../../shared/errors.ts";
3
4
  import {
4
- type ActiveSessionBrokerConnection,
5
- setActiveSessionBrokerConnection,
6
- } from "../../shared/session-broker/active.ts";
7
- import {
8
- INCOMING_SESSION_MESSAGE_EVENT,
9
- type SessionMessageSendResult,
5
+ INCOMING_SESSION_ENVELOPE_EVENT,
6
+ type SessionEnvelopeSendResult,
10
7
  SessionMessagingClient,
11
8
  } from "../../shared/session-broker/client.ts";
12
- import type { SessionMessagePayload } from "../../shared/session-broker/protocol.ts";
9
+ import type {
10
+ OutboundSessionEnvelope,
11
+ SessionCancelEnvelope,
12
+ SessionEnvelope,
13
+ SessionEnvelopeSendFailureReason,
14
+ SessionMessageEnvelope,
15
+ SessionSubagentReportEnvelope,
16
+ TaskReport,
17
+ } from "../../shared/session-broker/protocol.ts";
13
18
  import type {
14
19
  SessionLineageRelation,
15
20
  SessionLineageRow,
@@ -20,12 +25,10 @@ import {
20
25
  withSessionIndex,
21
26
  } from "../../shared/session-index/index.ts";
22
27
  import { spawnSessionMessagingBrokerIfNeeded } from "../broker/spawn.ts";
23
- import type { IncomingSessionMessageRuntime } from "./incoming-runtime.ts";
24
28
  import type { ReceivedMessageEndpoint, ReceivedMessageEntry } from "./message-contracts.ts";
25
29
 
26
- export const MESSAGE_SENT_CUSTOM_TYPE = "pi-sessions.message_sent";
27
-
28
30
  const RECONNECT_DELAYS_MS = [250, 500, 1_000, 2_000, 5_000, 10_000, 30_000] as const;
31
+ const SESSION_WAIT_POLL_MS = 100;
29
32
 
30
33
  export interface SendMessageRequest {
31
34
  target: string;
@@ -34,6 +37,49 @@ export interface SendMessageRequest {
34
37
  sourceToolCallId?: string | undefined;
35
38
  }
36
39
 
40
+ export type SendMessageResult =
41
+ | {
42
+ messageId: string;
43
+ delivered: false;
44
+ reason?: SessionEnvelopeSendFailureReason | undefined;
45
+ error?: string | undefined;
46
+ }
47
+ | {
48
+ messageId: string;
49
+ delivered: true;
50
+ target: ReceivedMessageEndpoint;
51
+ relation?: SessionLineageRelation | undefined;
52
+ };
53
+
54
+ export type CancelSessionResult =
55
+ | {
56
+ kind: "transport";
57
+ cancelId: string;
58
+ delivered: false;
59
+ reason?: SessionEnvelopeSendFailureReason | undefined;
60
+ error?: string | undefined;
61
+ }
62
+ | {
63
+ kind: "transport";
64
+ cancelId: string;
65
+ delivered: true;
66
+ target: ReceivedMessageEndpoint;
67
+ relation?: SessionLineageRelation | undefined;
68
+ };
69
+
70
+ export type IncomingMessageHandler = (envelope: SessionMessageEnvelope) => Promise<void> | void;
71
+ export type IncomingCancelHandler = (envelope: SessionCancelEnvelope) => Promise<void> | void;
72
+ export type IncomingSubagentReportHandler = (
73
+ envelope: SessionSubagentReportEnvelope,
74
+ ) => Promise<void> | void;
75
+
76
+ export type SendSubagentReportResult = SessionEnvelopeSendResult;
77
+
78
+ export interface SendSubagentReportRequest extends TaskReport {
79
+ target: string;
80
+ reportId: string;
81
+ }
82
+
37
83
  interface IncomingMessageIndexContext {
38
84
  source: ReceivedMessageEndpoint;
39
85
  target: ReceivedMessageEndpoint;
@@ -42,68 +88,171 @@ interface IncomingMessageIndexContext {
42
88
 
43
89
  export class SessionMessagingService {
44
90
  private readonly indexPath: string;
45
- private readonly incomingRuntime: IncomingSessionMessageRuntime;
46
- private readonly appendEntry: (customType: string, data: unknown) => void;
47
91
  private relationBySessionId = new Map<string, SessionLineageRelation | undefined>();
48
- private readonly connection = new BrokerConnection((requestId, message) =>
49
- this.handleIncoming(requestId, message),
50
- );
92
+ private incomingMessageHandler: IncomingMessageHandler | undefined;
93
+ private incomingCancelHandler: IncomingCancelHandler | undefined;
94
+ private incomingSubagentReportHandler: IncomingSubagentReportHandler | undefined;
95
+ private readonly connection = new BrokerConnection((envelope) => this.acceptIncoming(envelope));
51
96
 
52
- constructor(
53
- indexPath: string,
54
- incomingRuntime: IncomingSessionMessageRuntime,
55
- appendEntry: (customType: string, data: unknown) => void,
56
- ) {
97
+ constructor(indexPath: string) {
57
98
  this.indexPath = indexPath;
58
- this.incomingRuntime = incomingRuntime;
59
- this.appendEntry = appendEntry;
60
99
  }
61
100
 
62
101
  async start(ctx: ExtensionContext): Promise<void> {
63
102
  const sessionId = ctx.sessionManager.getSessionId();
64
103
  this.refreshCachedRelations(sessionId);
65
- setActiveSessionBrokerConnection(this.connection);
66
104
  await this.connection.start(sessionId);
67
105
  }
68
106
 
69
107
  stop(): void {
70
- setActiveSessionBrokerConnection(undefined);
71
108
  this.relationBySessionId.clear();
72
109
  this.connection.stop();
73
110
  }
74
111
 
75
- async sendMessage(request: SendMessageRequest): Promise<SessionMessageSendResult> {
112
+ onIncomingMessage(handler: IncomingMessageHandler): void {
113
+ if (this.incomingMessageHandler) {
114
+ throw new Error("An incoming message handler is already registered.");
115
+ }
116
+ this.incomingMessageHandler = handler;
117
+ }
118
+
119
+ onIncomingCancel(handler: IncomingCancelHandler): void {
120
+ if (this.incomingCancelHandler) {
121
+ throw new Error("An incoming cancel handler is already registered.");
122
+ }
123
+ this.incomingCancelHandler = handler;
124
+ }
125
+
126
+ onIncomingSubagentReport(handler: IncomingSubagentReportHandler): void {
127
+ if (this.incomingSubagentReportHandler) {
128
+ throw new Error("An incoming subagent report handler is already registered.");
129
+ }
130
+ this.incomingSubagentReportHandler = handler;
131
+ }
132
+
133
+ async listSessions(): Promise<string[]> {
134
+ return this.connection.listSessionIds();
135
+ }
136
+
137
+ async waitForSession(sessionId: string, timeoutMs: number): Promise<boolean> {
138
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
139
+ throw new Error("Session wait timeout must be a non-negative finite number.");
140
+ }
141
+
142
+ const deadline = Date.now() + timeoutMs;
143
+ while (true) {
144
+ if ((await this.listSessions()).includes(sessionId)) {
145
+ return true;
146
+ }
147
+
148
+ const remainingMs = deadline - Date.now();
149
+ if (remainingMs <= 0) {
150
+ return false;
151
+ }
152
+ await delay(Math.min(SESSION_WAIT_POLL_MS, remainingMs));
153
+ }
154
+ }
155
+
156
+ async sendMessage(request: SendMessageRequest): Promise<SendMessageResult> {
76
157
  const relation = this.getCachedRelationTo(request.target, true);
77
- const sentAt = new Date().toISOString();
78
158
  const messageId = randomUUID();
79
- const result = await this.connection.sendMessage({
159
+ const result = await this.connection.sendEnvelope({
160
+ target: request.target,
161
+ envelope: {
162
+ kind: "message",
163
+ messageId,
164
+ body: request.body,
165
+ ...(request.requestResponse === undefined
166
+ ? {}
167
+ : { requestResponse: request.requestResponse }),
168
+ ...(request.sourceToolCallId === undefined
169
+ ? {}
170
+ : { sourceToolCallId: request.sourceToolCallId }),
171
+ sentAt: new Date().toISOString(),
172
+ },
173
+ });
174
+
175
+ if (!result.delivered) {
176
+ return {
177
+ messageId,
178
+ delivered: false,
179
+ ...(result.reason === undefined ? {} : { reason: result.reason }),
180
+ ...(result.error === undefined ? {} : { error: result.error }),
181
+ };
182
+ }
183
+
184
+ return {
80
185
  messageId,
186
+ delivered: true,
187
+ target: this.getTargetEndpoint(request.target),
188
+ ...(relation === undefined ? {} : { relation }),
189
+ };
190
+ }
191
+
192
+ async sendSubagentReport(request: SendSubagentReportRequest): Promise<SendSubagentReportResult> {
193
+ return this.connection.sendEnvelope({
81
194
  target: request.target,
82
- body: request.body,
83
- sentAt,
84
- requestResponse: request.requestResponse,
85
- sourceToolCallId: request.sourceToolCallId,
195
+ envelope: {
196
+ kind: "subagent_report",
197
+ reportId: request.reportId,
198
+ status: request.status,
199
+ summary: request.summary,
200
+ ...(request.details ? { details: request.details } : {}),
201
+ ...(request.references ? { references: request.references } : {}),
202
+ ...(request.nextSteps ? { nextSteps: request.nextSteps } : {}),
203
+ sentAt: new Date().toISOString(),
204
+ },
86
205
  });
206
+ }
87
207
 
88
- if (result.delivered) {
89
- this.appendEntry(MESSAGE_SENT_CUSTOM_TYPE, {
90
- messageId: result.messageId,
91
- target: request.target,
92
- body: request.body,
93
- requestResponse: request.requestResponse,
94
- sourceToolCallId: request.sourceToolCallId,
95
- sentAt,
96
- ...(relation === undefined ? {} : { relation }),
97
- });
208
+ async cancelSession(sessionId: string): Promise<CancelSessionResult> {
209
+ const relation = this.getCachedRelationTo(sessionId, true);
210
+ const cancelId = randomUUID();
211
+ const result = await this.connection.sendEnvelope({
212
+ target: sessionId,
213
+ envelope: {
214
+ kind: "cancel",
215
+ cancelId,
216
+ sentAt: new Date().toISOString(),
217
+ },
218
+ });
219
+
220
+ if (!result.delivered) {
221
+ return {
222
+ kind: "transport",
223
+ cancelId,
224
+ delivered: false,
225
+ ...(result.reason === undefined ? {} : { reason: result.reason }),
226
+ ...(result.error === undefined ? {} : { error: result.error }),
227
+ };
98
228
  }
99
229
 
100
- return result;
230
+ return {
231
+ kind: "transport",
232
+ cancelId,
233
+ delivered: true,
234
+ target: this.getTargetEndpoint(sessionId),
235
+ ...(relation === undefined ? {} : { relation }),
236
+ };
101
237
  }
102
238
 
103
- private handleIncoming(requestId: string, message: SessionMessagePayload): void {
104
- const received = this.buildReceivedMessageEntry(message);
105
- const result = this.incomingRuntime.deliver(received);
106
- this.connection.acknowledgeIncoming(requestId, message.messageId, result);
239
+ buildReceivedMessage(message: SessionMessageEnvelope): ReceivedMessageEntry {
240
+ const context = this.getIncomingMessageIndexContext(message.source, message.target);
241
+ return {
242
+ messageId: message.messageId,
243
+ source: context.source,
244
+ target: context.target,
245
+ body: message.body,
246
+ sentAt: message.sentAt,
247
+ receivedAt: new Date().toISOString(),
248
+ ...(message.requestResponse === undefined
249
+ ? {}
250
+ : { requestResponse: message.requestResponse }),
251
+ ...(message.sourceToolCallId === undefined
252
+ ? {}
253
+ : { sourceToolCallId: message.sourceToolCallId }),
254
+ ...(context.relation === undefined ? {} : { relation: context.relation }),
255
+ };
107
256
  }
108
257
 
109
258
  getCachedRelationTo(
@@ -135,23 +284,46 @@ export class SessionMessagingService {
135
284
  return this.relationBySessionId.get(sessionId);
136
285
  }
137
286
 
138
- private buildReceivedMessageEntry(message: SessionMessagePayload): ReceivedMessageEntry {
139
- const context = this.getIncomingMessageIndexContext(message.source, message.target);
140
- return {
141
- messageId: message.messageId,
142
- source: context.source,
143
- target: context.target,
144
- body: message.body,
145
- sentAt: message.sentAt,
146
- receivedAt: new Date().toISOString(),
147
- ...(message.requestResponse === undefined
148
- ? {}
149
- : { requestResponse: message.requestResponse }),
150
- ...(message.sourceToolCallId === undefined
151
- ? {}
152
- : { sourceToolCallId: message.sourceToolCallId }),
153
- ...(context.relation === undefined ? {} : { relation: context.relation }),
154
- };
287
+ private async acceptIncoming(envelope: SessionEnvelope): Promise<IncomingAcceptance> {
288
+ try {
289
+ switch (envelope.kind) {
290
+ case "message": {
291
+ if (!this.incomingMessageHandler) {
292
+ throw new Error("Target session has no incoming message handler.");
293
+ }
294
+ await this.incomingMessageHandler(envelope);
295
+ break;
296
+ }
297
+ case "cancel": {
298
+ if (!this.incomingCancelHandler) {
299
+ throw new Error("Target session has no incoming cancel handler.");
300
+ }
301
+ await this.incomingCancelHandler(envelope);
302
+ break;
303
+ }
304
+ case "subagent_report": {
305
+ if (!this.incomingSubagentReportHandler) {
306
+ throw new Error("Target session has no incoming subagent report handler.");
307
+ }
308
+ await this.incomingSubagentReportHandler(envelope);
309
+ break;
310
+ }
311
+ }
312
+ return { delivered: true };
313
+ } catch (error) {
314
+ return {
315
+ delivered: false,
316
+ error: formatError(error),
317
+ };
318
+ }
319
+ }
320
+
321
+ private getTargetEndpoint(targetSessionId: string): ReceivedMessageEndpoint {
322
+ return (
323
+ withSessionIndex(this.indexPath, { mode: "read", required: false }, ({ db }) =>
324
+ buildReceivedMessageEndpoint(targetSessionId, getSessionById(db, targetSessionId)),
325
+ ) ?? buildReceivedMessageEndpoint(targetSessionId)
326
+ );
155
327
  }
156
328
 
157
329
  private getIncomingMessageIndexContext(
@@ -207,7 +379,9 @@ export class SessionMessagingService {
207
379
  }
208
380
  }
209
381
 
210
- class BrokerConnection implements ActiveSessionBrokerConnection {
382
+ type IncomingAcceptance = { delivered: true } | { delivered: false; error: string };
383
+
384
+ class BrokerConnection {
211
385
  private client: SessionMessagingClient | undefined;
212
386
  private sessionId: string | undefined;
213
387
  private active = false;
@@ -216,7 +390,7 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
216
390
  private connectPromise: Promise<void> | undefined;
217
391
 
218
392
  constructor(
219
- private readonly onIncoming: (requestId: string, message: SessionMessagePayload) => void,
393
+ private readonly onIncoming: (envelope: SessionEnvelope) => Promise<IncomingAcceptance>,
220
394
  ) {}
221
395
 
222
396
  get currentSessionId(): string | undefined {
@@ -243,25 +417,26 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
243
417
  return this.requireClient().listSessionIds();
244
418
  }
245
419
 
246
- async sendMessage(options: {
247
- messageId: string;
420
+ async sendEnvelope(options: {
248
421
  target: string;
249
- body: string;
250
- sentAt: string;
251
- requestResponse?: boolean | undefined;
252
- sourceToolCallId?: string | undefined;
253
- }): Promise<SessionMessageSendResult> {
422
+ envelope: OutboundSessionEnvelope;
423
+ }): Promise<SessionEnvelopeSendResult> {
254
424
  await this.ensureConnectedNow();
255
- return this.requireClient().sendMessage(options);
425
+ return this.requireClient().sendEnvelope(options);
256
426
  }
257
427
 
258
- acknowledgeIncoming(
428
+ private async acceptIncoming(
429
+ client: SessionMessagingClient,
259
430
  requestId: string,
260
- messageId: string,
261
- result: { delivered: true } | { delivered: false; error: string },
262
- ): void {
431
+ envelope: SessionEnvelope,
432
+ ): Promise<void> {
433
+ const result = await this.onIncoming(envelope);
434
+ if (this.client !== client || !client.isConnected) {
435
+ return;
436
+ }
437
+
263
438
  try {
264
- this.requireClient().acknowledgeIncoming(requestId, messageId, result);
439
+ client.acknowledgeIncoming(requestId, result);
265
440
  } catch {}
266
441
  }
267
442
 
@@ -299,7 +474,9 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
299
474
 
300
475
  await spawnSessionMessagingBrokerIfNeeded();
301
476
  const client = new SessionMessagingClient();
302
- client.on(INCOMING_SESSION_MESSAGE_EVENT, this.onIncoming);
477
+ client.on(INCOMING_SESSION_ENVELOPE_EVENT, (requestId: string, envelope: SessionEnvelope) => {
478
+ void this.acceptIncoming(client, requestId, envelope);
479
+ });
303
480
  client.on("disconnect", () => {
304
481
  if (this.client === client) {
305
482
  this.client = undefined;
@@ -317,7 +494,7 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
317
494
  return;
318
495
  }
319
496
 
320
- const delay = RECONNECT_DELAYS_MS[this.reconnectDelayIndex] ?? 30_000;
497
+ const delayMs = RECONNECT_DELAYS_MS[this.reconnectDelayIndex] ?? 30_000;
321
498
  this.reconnectDelayIndex = Math.min(
322
499
  this.reconnectDelayIndex + 1,
323
500
  RECONNECT_DELAYS_MS.length - 1,
@@ -325,7 +502,7 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
325
502
  this.reconnectTimer = setTimeout(() => {
326
503
  this.reconnectTimer = undefined;
327
504
  this.ensureConnectedNow().catch(() => {});
328
- }, delay);
505
+ }, delayMs);
329
506
  }
330
507
 
331
508
  private clearReconnectTimer(): void {
@@ -368,3 +545,7 @@ function getRelationMapForSession(
368
545
  ) ?? new Map()
369
546
  );
370
547
  }
548
+
549
+ function delay(ms: number): Promise<void> {
550
+ return new Promise((resolve) => setTimeout(resolve, ms));
551
+ }