@copilotkit/runtime 1.64.2-canary.rc-1 → 1.64.2

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 (79) hide show
  1. package/dist/package.cjs +1 -1
  2. package/dist/runtime/package.mjs +1 -1
  3. package/dist/v2/runtime/core/channel-activation-config.cjs +5 -2
  4. package/dist/v2/runtime/core/channel-activation-config.cjs.map +1 -1
  5. package/dist/v2/runtime/core/channel-activation-config.d.cts +13 -0
  6. package/dist/v2/runtime/core/channel-activation-config.d.cts.map +1 -1
  7. package/dist/v2/runtime/core/channel-activation-config.d.mts +13 -0
  8. package/dist/v2/runtime/core/channel-activation-config.d.mts.map +1 -1
  9. package/dist/v2/runtime/core/channel-activation-config.mjs +5 -2
  10. package/dist/v2/runtime/core/channel-activation-config.mjs.map +1 -1
  11. package/dist/v2/runtime/core/channel-manager.cjs +257 -20
  12. package/dist/v2/runtime/core/channel-manager.cjs.map +1 -1
  13. package/dist/v2/runtime/core/channel-manager.d.cts +9 -5
  14. package/dist/v2/runtime/core/channel-manager.d.cts.map +1 -1
  15. package/dist/v2/runtime/core/channel-manager.d.mts +9 -5
  16. package/dist/v2/runtime/core/channel-manager.d.mts.map +1 -1
  17. package/dist/v2/runtime/core/channel-manager.mjs +257 -20
  18. package/dist/v2/runtime/core/channel-manager.mjs.map +1 -1
  19. package/dist/v2/runtime/core/fetch-handler.cjs +4 -0
  20. package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
  21. package/dist/v2/runtime/core/fetch-handler.d.cts +5 -0
  22. package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
  23. package/dist/v2/runtime/core/fetch-handler.d.mts +5 -0
  24. package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
  25. package/dist/v2/runtime/core/fetch-handler.mjs +4 -0
  26. package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
  27. package/dist/v2/runtime/endpoints/auto-start-channels.cjs +47 -0
  28. package/dist/v2/runtime/endpoints/auto-start-channels.cjs.map +1 -0
  29. package/dist/v2/runtime/endpoints/auto-start-channels.mjs +46 -0
  30. package/dist/v2/runtime/endpoints/auto-start-channels.mjs.map +1 -0
  31. package/dist/v2/runtime/endpoints/express.cjs +2 -0
  32. package/dist/v2/runtime/endpoints/express.cjs.map +1 -1
  33. package/dist/v2/runtime/endpoints/express.d.cts +9 -8
  34. package/dist/v2/runtime/endpoints/express.d.cts.map +1 -1
  35. package/dist/v2/runtime/endpoints/express.d.mts +9 -8
  36. package/dist/v2/runtime/endpoints/express.d.mts.map +1 -1
  37. package/dist/v2/runtime/endpoints/express.mjs +2 -0
  38. package/dist/v2/runtime/endpoints/express.mjs.map +1 -1
  39. package/dist/v2/runtime/endpoints/hono.cjs.map +1 -1
  40. package/dist/v2/runtime/endpoints/hono.d.cts +14 -5
  41. package/dist/v2/runtime/endpoints/hono.d.cts.map +1 -1
  42. package/dist/v2/runtime/endpoints/hono.d.mts +14 -5
  43. package/dist/v2/runtime/endpoints/hono.d.mts.map +1 -1
  44. package/dist/v2/runtime/endpoints/hono.mjs.map +1 -1
  45. package/dist/v2/runtime/endpoints/node.cjs +2 -0
  46. package/dist/v2/runtime/endpoints/node.cjs.map +1 -1
  47. package/dist/v2/runtime/endpoints/node.d.cts +23 -13
  48. package/dist/v2/runtime/endpoints/node.d.cts.map +1 -1
  49. package/dist/v2/runtime/endpoints/node.d.mts +23 -13
  50. package/dist/v2/runtime/endpoints/node.d.mts.map +1 -1
  51. package/dist/v2/runtime/endpoints/node.mjs +2 -0
  52. package/dist/v2/runtime/endpoints/node.mjs.map +1 -1
  53. package/dist/v2/runtime/intelligence-platform/client.cjs +33 -2
  54. package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
  55. package/dist/v2/runtime/intelligence-platform/client.d.cts +14 -3
  56. package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
  57. package/dist/v2/runtime/intelligence-platform/client.d.mts +14 -3
  58. package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
  59. package/dist/v2/runtime/intelligence-platform/client.mjs +33 -2
  60. package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
  61. package/dist/v2/runtime/runner/agent-runner.cjs.map +1 -1
  62. package/dist/v2/runtime/runner/agent-runner.d.cts +4 -0
  63. package/dist/v2/runtime/runner/agent-runner.d.cts.map +1 -1
  64. package/dist/v2/runtime/runner/agent-runner.d.mts +4 -0
  65. package/dist/v2/runtime/runner/agent-runner.d.mts.map +1 -1
  66. package/dist/v2/runtime/runner/agent-runner.mjs.map +1 -1
  67. package/dist/v2/runtime/runner/in-memory.cjs +3 -5
  68. package/dist/v2/runtime/runner/in-memory.cjs.map +1 -1
  69. package/dist/v2/runtime/runner/in-memory.d.cts.map +1 -1
  70. package/dist/v2/runtime/runner/in-memory.d.mts.map +1 -1
  71. package/dist/v2/runtime/runner/in-memory.mjs +3 -5
  72. package/dist/v2/runtime/runner/in-memory.mjs.map +1 -1
  73. package/dist/v2/runtime/runner/intelligence.cjs +38 -31
  74. package/dist/v2/runtime/runner/intelligence.cjs.map +1 -1
  75. package/dist/v2/runtime/runner/intelligence.d.cts.map +1 -1
  76. package/dist/v2/runtime/runner/intelligence.d.mts.map +1 -1
  77. package/dist/v2/runtime/runner/intelligence.mjs +38 -31
  78. package/dist/v2/runtime/runner/intelligence.mjs.map +1 -1
  79. package/package.json +5 -5
@@ -1,6 +1,8 @@
1
1
  import "reflect-metadata";
2
2
  import { ChannelConfigError, deriveChannelActivationConfig } from "./channel-activation-config.mjs";
3
+ import { EMPTY } from "rxjs";
3
4
  import { randomUUID } from "node:crypto";
5
+ import { AbstractAgent, EventType } from "@ag-ui/client";
4
6
 
5
7
  //#region src/v2/runtime/core/channel-manager.ts
6
8
  /**
@@ -45,7 +47,7 @@ const CHANNELS_INTELLIGENCE_SPECIFIER = "@copilotkit/channels-intelligence";
45
47
  * transport-level drop diagnostics are not silent in the managed path.
46
48
  * @returns The launcher's {@link ChannelsHandle}.
47
49
  */
48
- async function defaultActivateChannel(config, channel, importChannelsIntelligence = () => import(CHANNELS_INTELLIGENCE_SPECIFIER), log) {
50
+ async function defaultActivateChannel(config, channel, importChannelsIntelligence = () => import(CHANNELS_INTELLIGENCE_SPECIFIER), log, services) {
49
51
  let mod;
50
52
  try {
51
53
  mod = await importChannelsIntelligence();
@@ -53,6 +55,7 @@ async function defaultActivateChannel(config, channel, importChannelsIntelligenc
53
55
  if (isModuleNotFound(err)) throw new Error("Managed Channels require '@copilotkit/channels-intelligence' to be installed. Add it to your app's dependencies.", { cause: err });
54
56
  throw err;
55
57
  }
58
+ if (!services) throw new Error("Managed Channels require the runtime AgentRunner and Intelligence client");
56
59
  return mod.startChannelsOverRealtimeGateway([channel], {
57
60
  wsUrl: config.wsUrl,
58
61
  apiKey: config.apiKey,
@@ -62,10 +65,202 @@ async function defaultActivateChannel(config, channel, importChannelsIntelligenc
62
65
  },
63
66
  runtimeInstanceId: config.runtimeInstanceId,
64
67
  adapter: config.adapter,
68
+ ...config.showToolStatus !== void 0 ? { showToolStatus: config.showToolStatus } : {},
69
+ ...config.replyContinuation !== void 0 ? { replyContinuation: config.replyContinuation } : {},
65
70
  appApiBaseUrl: config.apiUrl,
66
- ...log ? { log } : {}
71
+ ...log ? { log } : {},
72
+ runCanonical: (args) => runCanonicalChannelAgent(services.runner, services.intelligence, services.lockTtlSeconds ?? 20, services.lockHeartbeatIntervalSeconds ?? 15, args, services.lockKeyPrefix),
73
+ loadHistory: async ({ deliveryId, threadId, appUserId }) => {
74
+ const history = await services.intelligence.getThreadMessages({
75
+ threadId,
76
+ userId: appUserId,
77
+ channelDeliveryId: deliveryId
78
+ });
79
+ return Promise.all(history.messages.map((message) => toAgentMessage(message, services.intelligence)));
80
+ }
67
81
  });
68
82
  }
83
+ /** One outer agent that lets the standard runner own the whole local tool loop. */
84
+ var ChannelOuterAgent = class extends AbstractAgent {
85
+ constructor(inner, canonicalThreadId, executeLoop) {
86
+ super({
87
+ threadId: inner.threadId,
88
+ initialMessages: inner.messages,
89
+ initialState: inner.state,
90
+ ...inner.agentId ? { agentId: inner.agentId } : {}
91
+ });
92
+ this.inner = inner;
93
+ this.canonicalThreadId = canonicalThreadId;
94
+ this.executeLoop = executeLoop;
95
+ }
96
+ run() {
97
+ return EMPTY;
98
+ }
99
+ async runAgent(parameters, subscriber) {
100
+ if (!parameters?.runId) throw new Error("Canonical Channel run requires a runId");
101
+ return {
102
+ result: await this.executeLoop(subscriber ?? {}, {
103
+ threadId: this.canonicalThreadId,
104
+ runId: parameters.runId
105
+ }),
106
+ newMessages: []
107
+ };
108
+ }
109
+ abortRun() {
110
+ this.inner.abortRun();
111
+ }
112
+ };
113
+ /** Drive one public Channel run through the runtime's existing AgentRunner. */
114
+ async function runCanonicalChannelAgent(runner, intelligence, lockTtlSeconds, lockHeartbeatIntervalSeconds, args, lockKeyPrefix) {
115
+ const lock = await intelligence.ɵacquireThreadLock({
116
+ threadId: args.threadId,
117
+ runId: args.runId,
118
+ userId: args.userId,
119
+ agentId: args.agentId,
120
+ channelDeliveryId: args.deliveryId,
121
+ ttlSeconds: lockTtlSeconds,
122
+ ...lockKeyPrefix !== void 0 ? { lockKeyPrefix } : {}
123
+ });
124
+ const canonicalThreadId = lock.threadId;
125
+ const canonicalRunId = lock.runId;
126
+ let result = {
127
+ iterations: 0,
128
+ interrupted: false
129
+ };
130
+ const outer = new ChannelOuterAgent(args.agent, canonicalThreadId, async (subscriber, canonicalRun) => {
131
+ result = await args.execute(subscriber, canonicalRun);
132
+ return result;
133
+ });
134
+ let stopPromise;
135
+ let heartbeatError;
136
+ let heartbeatTimer;
137
+ const stopCanonicalRun = () => {
138
+ stopPromise ??= Promise.resolve().then(() => runner.stop({
139
+ threadId: canonicalThreadId,
140
+ runId: canonicalRunId
141
+ })).catch(() => false);
142
+ };
143
+ const abortCanonicalRun = () => {
144
+ try {
145
+ args.agent.abortRun();
146
+ } catch {}
147
+ stopCanonicalRun();
148
+ };
149
+ args.signal?.addEventListener("abort", abortCanonicalRun, { once: true });
150
+ heartbeatTimer = setInterval(() => {
151
+ intelligence.ɵrenewThreadLock({
152
+ threadId: canonicalThreadId,
153
+ runId: canonicalRunId,
154
+ ttlSeconds: lockTtlSeconds,
155
+ ...lockKeyPrefix !== void 0 ? { lockKeyPrefix } : {}
156
+ }).catch((error) => {
157
+ if (heartbeatTimer === void 0) return;
158
+ clearInterval(heartbeatTimer);
159
+ heartbeatTimer = void 0;
160
+ heartbeatError = error;
161
+ try {
162
+ args.agent.abortRun();
163
+ } catch {}
164
+ stopCanonicalRun();
165
+ });
166
+ }, lockHeartbeatIntervalSeconds * 1e3);
167
+ heartbeatTimer.unref?.();
168
+ try {
169
+ await new Promise((resolve, reject) => {
170
+ let terminalError;
171
+ runner.run({
172
+ threadId: canonicalThreadId,
173
+ agent: outer,
174
+ input: {
175
+ threadId: canonicalThreadId,
176
+ runId: canonicalRunId,
177
+ messages: args.agent.messages,
178
+ state: args.agent.state,
179
+ tools: [...args.tools],
180
+ context: [...args.context],
181
+ forwardedProps: void 0
182
+ },
183
+ persistedInputMessages: args.persistedInputMessages
184
+ }).subscribe({
185
+ next: (event) => {
186
+ if (event.type !== EventType.RUN_ERROR || terminalError) return;
187
+ const message = "message" in event && typeof event.message === "string" ? event.message : "Canonical Channel agent run failed";
188
+ terminalError = new Error(message);
189
+ terminalError.name = "ChannelCanonicalRunError";
190
+ if ("code" in event && typeof event.code === "string" && event.code.length > 0) terminalError.code = event.code;
191
+ },
192
+ error: reject,
193
+ complete: () => {
194
+ if (terminalError) reject(terminalError);
195
+ else resolve();
196
+ }
197
+ });
198
+ if (args.signal?.aborted) abortCanonicalRun();
199
+ });
200
+ } finally {
201
+ args.signal?.removeEventListener("abort", abortCanonicalRun);
202
+ if (heartbeatTimer !== void 0) {
203
+ clearInterval(heartbeatTimer);
204
+ heartbeatTimer = void 0;
205
+ }
206
+ await intelligence.ɵcleanupThreadLock({
207
+ threadId: canonicalThreadId,
208
+ runId: canonicalRunId
209
+ }).catch(() => void 0);
210
+ }
211
+ if (heartbeatError !== void 0) {
212
+ await stopPromise;
213
+ throw heartbeatError;
214
+ }
215
+ return result;
216
+ }
217
+ /** Convert canonical Intelligence history into AG-UI messages. */
218
+ async function toAgentMessage(message, intelligence) {
219
+ const content = await hydrateManagedContent(message.content, intelligence);
220
+ return {
221
+ id: message.id,
222
+ role: message.role,
223
+ content: content ?? "",
224
+ ...message.activityType ? { activityType: message.activityType } : {},
225
+ ...message.toolCalls ? { toolCalls: message.toolCalls.map((call) => ({
226
+ id: call.id,
227
+ type: "function",
228
+ function: {
229
+ name: call.name,
230
+ arguments: call.args
231
+ }
232
+ })) } : {},
233
+ ...message.toolCallId ? { toolCallId: message.toolCallId } : {}
234
+ };
235
+ }
236
+ /** Resolves managed asset references only at the authorized Runtime boundary. */
237
+ async function hydrateManagedContent(content, intelligence) {
238
+ if (Array.isArray(content)) return Promise.all(content.map(async (part) => {
239
+ if (typeof part !== "object" || part === null || !("source" in part) || typeof part.source !== "object" || part.source === null || !("value" in part.source) || typeof part.source.value !== "string" || !part.source.value.startsWith("cpki-asset://")) return part;
240
+ const assetId = part.source.value.slice(13);
241
+ const asset = await intelligence.ɵgetManagedChannelAsset(assetId);
242
+ return {
243
+ ...part,
244
+ source: {
245
+ type: "data",
246
+ value: Buffer.from(asset.bytes).toString("base64"),
247
+ mimeType: asset.mimeType ?? ("mimeType" in part.source && typeof part.source.mimeType === "string" ? part.source.mimeType : "application/octet-stream")
248
+ }
249
+ };
250
+ }));
251
+ if (typeof content === "object" && content !== null && "assetId" in content && typeof content.assetId === "string") {
252
+ const asset = await intelligence.ɵgetManagedChannelAsset(content.assetId);
253
+ return {
254
+ ...content,
255
+ source: {
256
+ type: "data",
257
+ value: Buffer.from(asset.bytes).toString("base64"),
258
+ mimeType: asset.mimeType ?? ("mimeType" in content && typeof content.mimeType === "string" ? content.mimeType : "application/octet-stream")
259
+ }
260
+ };
261
+ }
262
+ return content;
263
+ }
69
264
  /** Whether `err` signals a missing managed provider rather than a hard failure. */
70
265
  function isSetupRequired(err) {
71
266
  return err instanceof ChannelSetupRequiredError || typeof err === "object" && err !== null && err.code === "SETUP_REQUIRED";
@@ -83,6 +278,8 @@ function isModuleNotFound(err) {
83
278
  }
84
279
  /** Default deadline (ms) for a single `handle.stop()` during teardown. */
85
280
  const DEFAULT_STOP_HANDLE_TIMEOUT_MS = 5e3;
281
+ /** Default cadence (ms) for the "still down" log while a session is dropped. */
282
+ const DEFAULT_RECONNECT_LOG_INTERVAL_MS = 3e4;
86
283
  /**
87
284
  * Reject with `timeoutMessage` after `timeoutMs` if `inner` has not settled,
88
285
  * otherwise pass `inner` through. When `timeoutMs` is undefined, `inner` is
@@ -125,15 +322,12 @@ function withTimeout(inner, timeoutMs, timeoutMessage) {
125
322
  * and {@link ready} rather than thrown.
126
323
  *
127
324
  * Reconnection is NOT handled here — it is delegated to the Phoenix connection
128
- * layer that backs the launcher. When a managed socket drops, Phoenix's `Socket`
129
- * auto-reconnects and auto-rejoins, re-sending the channel's join declaration;
130
- * the Intelligence gateway's `join/3` re-runs `record_heartbeat` (re-registering
131
- * the runtime's listener) and its `terminate/2` releases the dead socket's
132
- * leases (verified against Intelligence #511 `sdk_channel.ex`). So the transport
133
- * self-heals under the persistent adapter and a re-activation here would be both
134
- * redundant AND broken: re-invoking the engine on an already-started `Channel`
135
- * throws in `channel.addAdapter` (started=true). The manager therefore never
136
- * re-activates on a drop.
325
+ * layer that backs the launcher. When a managed control socket drops, Phoenix's
326
+ * `Socket` reconnects and rejoins with the same Runtime declaration. Active
327
+ * deliveries request fresh one-use join tokens through that control link. A
328
+ * re-activation here would be both redundant AND broken: re-invoking the engine
329
+ * on an already-started `Channel` throws in `channel.addAdapter` (started=true).
330
+ * The manager therefore never re-activates on a drop.
137
331
  *
138
332
  * It DOES, however, reflect real connection health through the session's
139
333
  * `onStateChange` observer so {@link ChannelManager.status} stays honest rather
@@ -148,11 +342,22 @@ var ChannelManager = class {
148
342
  this.activated = false;
149
343
  this.stopped = false;
150
344
  this.intelligence = args.intelligence;
345
+ this.runner = args.runner;
346
+ this.lockTtlSeconds = args.lockTtlSeconds ?? 20;
347
+ this.lockHeartbeatIntervalSeconds = args.lockHeartbeatIntervalSeconds ?? 15;
348
+ this.lockKeyPrefix = args.lockKeyPrefix;
151
349
  this.channels = args.channels;
152
350
  this.log = args.log;
153
- this.activateChannel = args.activateChannel ?? ((config, channel) => defaultActivateChannel(config, channel, void 0, this.log));
351
+ this.activateChannel = args.activateChannel ?? ((config, channel) => defaultActivateChannel(config, channel, void 0, this.log, this.runner ? {
352
+ runner: this.runner,
353
+ intelligence: this.intelligence,
354
+ lockTtlSeconds: this.lockTtlSeconds,
355
+ lockHeartbeatIntervalSeconds: this.lockHeartbeatIntervalSeconds,
356
+ ...this.lockKeyPrefix !== void 0 ? { lockKeyPrefix: this.lockKeyPrefix } : {}
357
+ } : void 0));
154
358
  this.mintRuntimeInstanceId = args.mintRuntimeInstanceId ?? (() => `rti_${randomUUID().replace(/-/g, "")}`);
155
359
  this.stopHandleTimeoutMs = args.stopHandleTimeoutMs ?? DEFAULT_STOP_HANDLE_TIMEOUT_MS;
360
+ this.reconnectLogIntervalMs = args.reconnectLogIntervalMs ?? DEFAULT_RECONNECT_LOG_INTERVAL_MS;
156
361
  }
157
362
  /**
158
363
  * Start activation of every declared Channel (lazy + idempotent). Mints a
@@ -410,8 +615,7 @@ var ChannelManager = class {
410
615
  *
411
616
  * - `reconnecting` → status `reconnecting` (dropped, Phoenix retrying);
412
617
  * - `online` → status `online` (rejoined, sendable again);
413
- * - `gave_up` → status `error` (dead after the bounded reconnect window);
414
- * - `fenced` → status `error` immediately (another activation superseded it).
618
+ * - `gave_up` → status `error` (dead after the bounded reconnect window).
415
619
  *
416
620
  * Makes NO re-activation — reconnection is delegated to the Phoenix connection
417
621
  * layer (see {@link ChannelManager}), which auto-rejoins under the persistent
@@ -423,23 +627,55 @@ var ChannelManager = class {
423
627
  * @param entry - The Channel's activation entry.
424
628
  */
425
629
  registerConnectionObserver(name, entry) {
426
- entry.handle?.onStateChange?.((state) => {
630
+ entry.handle?.onStateChange?.((state, detail) => {
427
631
  if (this.stopped || entry.status === "stopped") return;
632
+ const cause = detail?.reason ?? detail?.code;
633
+ const because = cause !== void 0 ? ` — ${cause}` : "";
428
634
  if (state === "reconnecting") {
429
635
  entry.status = "reconnecting";
430
- this.log?.(`channel "${name}" managed session dropped; reconnecting (Phoenix auto-rejoin)`);
636
+ entry.downSince ??= Date.now();
637
+ this.log?.(`channel "${name}" managed session dropped; reconnecting (Phoenix auto-rejoin)${because}`);
638
+ this.startReconnectLog(name, entry);
431
639
  } else if (state === "online") {
432
640
  entry.status = "online";
641
+ this.clearReconnectLog(entry);
642
+ entry.downSince = void 0;
433
643
  this.log?.(`channel "${name}" managed session back online`);
434
644
  } else if (state === "gave_up") {
435
645
  entry.status = "error";
436
- this.log?.(`channel "${name}" managed session gave up reconnecting; marking error`);
437
- } else {
438
- entry.status = "error";
439
- this.log?.(`channel "${name}" managed session was generation-fenced by a replacement; marking error`);
646
+ this.log?.(`channel "${name}" managed session gave up reconnecting after ${this.downFor(entry)}; marking error (still retrying — a successful rejoin restores online)${because}`);
440
647
  }
441
648
  });
442
649
  }
650
+ /** Rendered downtime for this outage episode (`"45s"`), or `"unknown"`. */
651
+ downFor(entry) {
652
+ return entry.downSince === void 0 ? "unknown" : `${Math.round((Date.now() - entry.downSince) / 1e3)}s`;
653
+ }
654
+ /**
655
+ * Repeat a "still down" line for as long as this outage lasts. Runs THROUGH
656
+ * `gave_up` on purpose: that transition is where the old behavior went quiet,
657
+ * and an operator watching a silent process cannot tell a dead bot from an
658
+ * idle one.
659
+ */
660
+ startReconnectLog(name, entry) {
661
+ if (entry.reconnectLogTimer !== void 0) return;
662
+ const timer = setInterval(() => {
663
+ if (this.stopped || entry.status === "stopped") {
664
+ this.clearReconnectLog(entry);
665
+ return;
666
+ }
667
+ this.log?.(`channel "${name}" managed session still down after ${this.downFor(entry)}; Phoenix is retrying`);
668
+ }, this.reconnectLogIntervalMs);
669
+ timer.unref?.();
670
+ entry.reconnectLogTimer = timer;
671
+ }
672
+ /** Stop this entry's "still down" repeat, if one is running. */
673
+ clearReconnectLog(entry) {
674
+ if (entry.reconnectLogTimer !== void 0) {
675
+ clearInterval(entry.reconnectLogTimer);
676
+ entry.reconnectLogTimer = void 0;
677
+ }
678
+ }
443
679
  /**
444
680
  * Drive a single entry to its terminal `stopped` state, tearing down its
445
681
  * handle AT MOST ONCE. Idempotent: it always sets `status = "stopped"`, and
@@ -477,6 +713,7 @@ var ChannelManager = class {
477
713
  */
478
714
  async stopEntry(entry) {
479
715
  entry.status = "stopped";
716
+ this.clearReconnectLog(entry);
480
717
  if (entry.handle && !entry.handleStopped) {
481
718
  entry.handleStopped = true;
482
719
  const handle = entry.handle;