@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.
- package/dist/package.cjs +1 -1
- package/dist/runtime/package.mjs +1 -1
- package/dist/v2/runtime/core/channel-activation-config.cjs +5 -2
- package/dist/v2/runtime/core/channel-activation-config.cjs.map +1 -1
- package/dist/v2/runtime/core/channel-activation-config.d.cts +13 -0
- package/dist/v2/runtime/core/channel-activation-config.d.cts.map +1 -1
- package/dist/v2/runtime/core/channel-activation-config.d.mts +13 -0
- package/dist/v2/runtime/core/channel-activation-config.d.mts.map +1 -1
- package/dist/v2/runtime/core/channel-activation-config.mjs +5 -2
- package/dist/v2/runtime/core/channel-activation-config.mjs.map +1 -1
- package/dist/v2/runtime/core/channel-manager.cjs +257 -20
- package/dist/v2/runtime/core/channel-manager.cjs.map +1 -1
- package/dist/v2/runtime/core/channel-manager.d.cts +9 -5
- package/dist/v2/runtime/core/channel-manager.d.cts.map +1 -1
- package/dist/v2/runtime/core/channel-manager.d.mts +9 -5
- package/dist/v2/runtime/core/channel-manager.d.mts.map +1 -1
- package/dist/v2/runtime/core/channel-manager.mjs +257 -20
- package/dist/v2/runtime/core/channel-manager.mjs.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.cjs +4 -0
- package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.d.cts +5 -0
- package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.d.mts +5 -0
- package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.mjs +4 -0
- package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
- package/dist/v2/runtime/endpoints/auto-start-channels.cjs +47 -0
- package/dist/v2/runtime/endpoints/auto-start-channels.cjs.map +1 -0
- package/dist/v2/runtime/endpoints/auto-start-channels.mjs +46 -0
- package/dist/v2/runtime/endpoints/auto-start-channels.mjs.map +1 -0
- package/dist/v2/runtime/endpoints/express.cjs +2 -0
- package/dist/v2/runtime/endpoints/express.cjs.map +1 -1
- package/dist/v2/runtime/endpoints/express.d.cts +9 -8
- package/dist/v2/runtime/endpoints/express.d.cts.map +1 -1
- package/dist/v2/runtime/endpoints/express.d.mts +9 -8
- package/dist/v2/runtime/endpoints/express.d.mts.map +1 -1
- package/dist/v2/runtime/endpoints/express.mjs +2 -0
- package/dist/v2/runtime/endpoints/express.mjs.map +1 -1
- package/dist/v2/runtime/endpoints/hono.cjs.map +1 -1
- package/dist/v2/runtime/endpoints/hono.d.cts +14 -5
- package/dist/v2/runtime/endpoints/hono.d.cts.map +1 -1
- package/dist/v2/runtime/endpoints/hono.d.mts +14 -5
- package/dist/v2/runtime/endpoints/hono.d.mts.map +1 -1
- package/dist/v2/runtime/endpoints/hono.mjs.map +1 -1
- package/dist/v2/runtime/endpoints/node.cjs +2 -0
- package/dist/v2/runtime/endpoints/node.cjs.map +1 -1
- package/dist/v2/runtime/endpoints/node.d.cts +23 -13
- package/dist/v2/runtime/endpoints/node.d.cts.map +1 -1
- package/dist/v2/runtime/endpoints/node.d.mts +23 -13
- package/dist/v2/runtime/endpoints/node.d.mts.map +1 -1
- package/dist/v2/runtime/endpoints/node.mjs +2 -0
- package/dist/v2/runtime/endpoints/node.mjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.cjs +33 -2
- package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.cts +14 -3
- package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.mts +14 -3
- package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.mjs +33 -2
- package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
- package/dist/v2/runtime/runner/agent-runner.cjs.map +1 -1
- package/dist/v2/runtime/runner/agent-runner.d.cts +4 -0
- package/dist/v2/runtime/runner/agent-runner.d.cts.map +1 -1
- package/dist/v2/runtime/runner/agent-runner.d.mts +4 -0
- package/dist/v2/runtime/runner/agent-runner.d.mts.map +1 -1
- package/dist/v2/runtime/runner/agent-runner.mjs.map +1 -1
- package/dist/v2/runtime/runner/in-memory.cjs +3 -5
- package/dist/v2/runtime/runner/in-memory.cjs.map +1 -1
- package/dist/v2/runtime/runner/in-memory.d.cts.map +1 -1
- package/dist/v2/runtime/runner/in-memory.d.mts.map +1 -1
- package/dist/v2/runtime/runner/in-memory.mjs +3 -5
- package/dist/v2/runtime/runner/in-memory.mjs.map +1 -1
- package/dist/v2/runtime/runner/intelligence.cjs +38 -31
- package/dist/v2/runtime/runner/intelligence.cjs.map +1 -1
- package/dist/v2/runtime/runner/intelligence.d.cts.map +1 -1
- package/dist/v2/runtime/runner/intelligence.d.mts.map +1 -1
- package/dist/v2/runtime/runner/intelligence.mjs +38 -31
- package/dist/v2/runtime/runner/intelligence.mjs.map +1 -1
- 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
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
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
|
-
|
|
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;
|