@copilotkit/runtime 1.64.2-canary.1785426837 → 1.64.2-canary.1785441273
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 +1 -0
- package/dist/v2/runtime/core/channel-activation-config.cjs.map +1 -1
- package/dist/v2/runtime/core/channel-activation-config.d.cts +7 -0
- package/dist/v2/runtime/core/channel-activation-config.d.cts.map +1 -1
- package/dist/v2/runtime/core/channel-activation-config.d.mts +7 -0
- package/dist/v2/runtime/core/channel-activation-config.d.mts.map +1 -1
- package/dist/v2/runtime/core/channel-activation-config.mjs +1 -0
- package/dist/v2/runtime/core/channel-activation-config.mjs.map +1 -1
- package/dist/v2/runtime/core/channel-manager.cjs +36 -4
- package/dist/v2/runtime/core/channel-manager.cjs.map +1 -1
- package/dist/v2/runtime/core/channel-manager.mjs +36 -4
- package/dist/v2/runtime/core/channel-manager.mjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.cjs +18 -0
- package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.cts +9 -2
- package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.mts +9 -2
- package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.mjs +18 -0
- package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
- package/package.json +6 -6
package/dist/package.cjs
CHANGED
|
@@ -5,7 +5,7 @@ const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
|
5
5
|
var require_package = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => {
|
|
6
6
|
module.exports = {
|
|
7
7
|
"name": "@copilotkit/runtime",
|
|
8
|
-
"version": "1.64.2-canary.
|
|
8
|
+
"version": "1.64.2-canary.1785441273",
|
|
9
9
|
"private": false,
|
|
10
10
|
"keywords": [
|
|
11
11
|
"ai",
|
package/dist/runtime/package.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { __commonJSMin } from "../_virtual/_rolldown/runtime.mjs";
|
|
|
5
5
|
var require_package = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
6
6
|
module.exports = {
|
|
7
7
|
"name": "@copilotkit/runtime",
|
|
8
|
-
"version": "1.64.2-canary.
|
|
8
|
+
"version": "1.64.2-canary.1785441273",
|
|
9
9
|
"private": false,
|
|
10
10
|
"keywords": [
|
|
11
11
|
"ai",
|
|
@@ -85,6 +85,7 @@ function deriveChannelActivationConfig(args) {
|
|
|
85
85
|
channelName,
|
|
86
86
|
adapter: trimmedProvider ? trimmedProvider : "slack",
|
|
87
87
|
...channel.showToolStatus !== void 0 ? { showToolStatus: channel.showToolStatus } : {},
|
|
88
|
+
...channel.replyContinuation !== void 0 ? { replyContinuation: channel.replyContinuation } : {},
|
|
88
89
|
runtimeInstanceId
|
|
89
90
|
};
|
|
90
91
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-activation-config.cjs","names":[],"sources":["../../../../src/v2/runtime/core/channel-activation-config.ts"],"sourcesContent":["import type { CopilotKitIntelligence } from \"../intelligence-platform\";\n// Type-only: @copilotkit/channels is pure-ESM, so a value import would break this\n// package's CJS output (see `runtime.ts` for the same constraint).\nimport type { Channel } from \"@copilotkit/channels\";\n\n/**\n * Error thrown when a Channel activation config cannot be derived — either the\n * Intelligence API key does not carry a project id in the expected\n * `cpk-{projectId}_...` format, or the {@link Channel} is missing a `name`.\n */\nexport class ChannelConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ChannelConfigError\";\n }\n}\n\n/**\n * Resolved configuration needed to activate a single Channel against a\n * running Intelligence runtime instance.\n */\nexport interface ChannelActivationConfig {\n /** Intelligence runner websocket URL the Channel connects to. */\n wsUrl: string;\n /** Intelligence API key used to authenticate the runner connection. */\n apiKey: string;\n /** Intelligence app-api HTTP base URL (`intelligence.ɵgetApiUrl()`), forwarded\n * to the transport so the managed realtime path enables file/history parity\n * (those are HTTP-only). Without it, Channels started by the normal runtime\n * handler run with no history and no file support (OSS-476). */\n apiUrl: string;\n /** Project id parsed from {@link apiKey}. */\n projectId: number;\n /** The Channel's declared name (`createChannel({ name })`). */\n channelName: string;\n /**\n * The managed provider this Channel declares to the Intelligence gateway on\n * join, resolved from the Channel's per-Channel `provider` (e.g. `\"slack\"` or\n * `\"teams\"`), defaulting to `\"slack\"`. Named `adapter` because that is the\n * field the gateway's join payload expects; the gateway resolves the actual\n * connection for the declared provider.\n */\n adapter: string;\n /**\n * Optional per-Channel override for visible managed tool-call progress.\n * Unset Slack routes default to hidden; other providers retain their existing\n * default.\n */\n showToolStatus?: boolean;\n /** Identifier for the runtime instance activating this Channel. */\n runtimeInstanceId: string;\n}\n\n/** Matches the `cpk-{projectId}_...` Intelligence API key format. */\nconst API_KEY_PROJECT_ID_PATTERN = /^cpk-(\\d+)_/;\n\n/**\n * Parse the project id embedded in an Intelligence API key.\n *\n * Intelligence API keys are formatted `cpk-{projectId}_{rest}` — this\n * extracts and numerically parses the `{projectId}` segment.\n *\n * @param apiKey - The Intelligence API key to parse.\n * @returns The parsed, strictly-positive, safe-integer project id.\n * @throws {ChannelConfigError} If `apiKey` does not match the expected\n * `cpk-{projectId}_...` format — a wrong/missing prefix or an absent project\n * id segment all fail the same match — or if the parsed project id is not a\n * strictly-positive safe integer.\n */\nexport function parseProjectIdFromApiKey(apiKey: string): number {\n const match = API_KEY_PROJECT_ID_PATTERN.exec(apiKey);\n if (!match) {\n // The whole API key is a `cpk-…` secret — everything after the fixed `cpk-`\n // namespace is sensitive, so a fixed-width slice (e.g. `apiKey.slice(0, 8)`)\n // would echo secret bytes for a `cpk-_short_long`-shaped key. This message is\n // logged and surfaced through `ready()`'s AggregateError, so echo NONE of the\n // key value; name only the expected format to aid diagnosis.\n throw new ChannelConfigError(\n `Could not parse a project id from the Intelligence API key — expected the ` +\n `\"cpk-{projectId}_...\" format (the key value is omitted here to avoid ` +\n `leaking secret material).`,\n );\n }\n const projectId = Number(match[1]);\n // Validate the parser's OWN output: `cpk-0_...` matches the pattern but a\n // non-positive project id would otherwise fail deep inside the launcher's\n // `assertValidChannelRealtimeScope` (which requires a positive projectId).\n // A very long digit run also matches `\\d+` but loses precision (or overflows\n // to Infinity) once coerced to `Number`, so it must be rejected too even\n // though it is `> 0` — `Number.isSafeInteger` catches both. This is the\n // parser guarding its own contract, not a channel-name replica, so it\n // belongs here. Reuse the same redaction: never echo the key value.\n if (!Number.isSafeInteger(projectId) || projectId <= 0) {\n throw new ChannelConfigError(\n `Parsed an invalid project id (${projectId}) from the Intelligence API ` +\n `key — the project id in \"cpk-{projectId}_...\" must be a positive safe ` +\n `integer (the key value is omitted here to avoid leaking secret material).`,\n );\n }\n return projectId;\n}\n\n/**\n * Derive the {@link ChannelActivationConfig} needed to activate `channel`\n * against the given Intelligence runtime configuration.\n *\n * @param args.intelligence - The Intelligence runtime client to pull the\n * runner websocket URL and auth token from.\n * @param args.channel - The Channel being activated. Must have a `name`; its\n * per-Channel `provider` selects the managed adapter declared to the gateway,\n * and `showToolStatus` optionally overrides managed tool-call visibility.\n * @param args.runtimeInstanceId - Identifier for the activating runtime\n * instance, passed through unchanged.\n * @returns The resolved {@link ChannelActivationConfig}.\n * @throws {ChannelConfigError} If the Intelligence API key does not carry a\n * parseable, strictly-positive project id, or if `channel.name` is\n * missing/empty.\n *\n * The managed provider is a PER-CHANNEL choice read from `channel.provider`, so\n * one runtime can activate a Slack-backed Channel and a Teams-backed Channel\n * side by side. When `channel.provider` is unset the config adapter defaults to\n * `\"slack\"` — an explicit, documented default, not a silent global. The SDK\n * only DECLARES this provider to the Intelligence gateway on join; the gateway\n * resolves the actual connection and is the authority on which providers it\n * accepts (it accepts only `\"slack\"` today — Teams gateway support is tracked\n * in OSS-450).\n *\n * The Channel-name FORMAT rules (lowercase kebab-case, 3–64 chars) and the\n * reserved-name rule are NOT re-checked here. Their single source of truth is\n * the `@copilotkit/channels-intelligence` launcher\n * (`assertValidChannelRealtimeScope` + `assertValidChannelNames`), which\n * validates them at activation; a malformed name surfaces as a logged `error`\n * status via {@link ChannelManager.ready} rather than an up-front throw. An\n * empty/missing name is still rejected here because that is this config's own\n * precondition (it has no name to forward at all), not a downstream replica.\n */\nexport function deriveChannelActivationConfig(args: {\n intelligence: CopilotKitIntelligence;\n channel: Channel;\n runtimeInstanceId: string;\n}): ChannelActivationConfig {\n const { intelligence, channel, runtimeInstanceId } = args;\n\n if (!channel.name) {\n throw new ChannelConfigError(\n \"Channel is missing a `name` — pass createChannel({ name }) to activate it.\",\n );\n }\n\n const channelName = channel.name;\n\n const wsUrl = intelligence.ɵgetChannelsWsUrl();\n const apiUrl = intelligence.ɵgetApiUrl();\n const apiKey = intelligence.ɵgetRunnerAuthToken();\n const projectId = parseProjectIdFromApiKey(apiKey);\n\n // Resolve the managed adapter declared to the gateway from the Channel's OWN\n // `provider` — a per-Channel choice, not a manager-wide default. When\n // `provider` is unset the adapter is the documented default `\"slack\"`; set\n // `createChannel({ provider: \"teams\" })` to declare Teams instead. The value\n // is trimmed so a padded runtime value (one that bypassed the typed union)\n // resolves to its bare provider rather than being forwarded with whitespace,\n // and a blank/whitespace-only provider falls back to `\"slack\"` (`??` alone\n // would keep `\"\"`).\n const trimmedProvider = channel.provider?.trim();\n\n return {\n wsUrl,\n apiUrl,\n apiKey,\n projectId,\n channelName,\n adapter: trimmedProvider ? trimmedProvider : \"slack\",\n ...(channel.showToolStatus !== undefined\n ? { showToolStatus: channel.showToolStatus }\n : {}),\n runtimeInstanceId,\n };\n}\n"],"mappings":";;;;;;;;AAUA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;
|
|
1
|
+
{"version":3,"file":"channel-activation-config.cjs","names":[],"sources":["../../../../src/v2/runtime/core/channel-activation-config.ts"],"sourcesContent":["import type { CopilotKitIntelligence } from \"../intelligence-platform\";\n// Type-only: @copilotkit/channels is pure-ESM, so a value import would break this\n// package's CJS output (see `runtime.ts` for the same constraint).\nimport type { Channel, ReplyContinuationOptions } from \"@copilotkit/channels\";\n\n/**\n * Error thrown when a Channel activation config cannot be derived — either the\n * Intelligence API key does not carry a project id in the expected\n * `cpk-{projectId}_...` format, or the {@link Channel} is missing a `name`.\n */\nexport class ChannelConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ChannelConfigError\";\n }\n}\n\n/**\n * Resolved configuration needed to activate a single Channel against a\n * running Intelligence runtime instance.\n */\nexport interface ChannelActivationConfig {\n /** Intelligence runner websocket URL the Channel connects to. */\n wsUrl: string;\n /** Intelligence API key used to authenticate the runner connection. */\n apiKey: string;\n /** Intelligence app-api HTTP base URL (`intelligence.ɵgetApiUrl()`), forwarded\n * to the transport so the managed realtime path enables file/history parity\n * (those are HTTP-only). Without it, Channels started by the normal runtime\n * handler run with no history and no file support (OSS-476). */\n apiUrl: string;\n /** Project id parsed from {@link apiKey}. */\n projectId: number;\n /** The Channel's declared name (`createChannel({ name })`). */\n channelName: string;\n /**\n * The managed provider this Channel declares to the Intelligence gateway on\n * join, resolved from the Channel's per-Channel `provider` (e.g. `\"slack\"` or\n * `\"teams\"`), defaulting to `\"slack\"`. Named `adapter` because that is the\n * field the gateway's join payload expects; the gateway resolves the actual\n * connection for the declared provider.\n */\n adapter: string;\n /**\n * Optional per-Channel override for visible managed tool-call progress.\n * Unset Slack routes default to hidden; other providers retain their existing\n * default.\n */\n showToolStatus?: boolean;\n /**\n * Optional per-Channel tuning for splitting a long reply across continuation\n * messages. Unset leaves the provider renderer's defaults in place.\n */\n replyContinuation?: ReplyContinuationOptions;\n /** Identifier for the runtime instance activating this Channel. */\n runtimeInstanceId: string;\n}\n\n/** Matches the `cpk-{projectId}_...` Intelligence API key format. */\nconst API_KEY_PROJECT_ID_PATTERN = /^cpk-(\\d+)_/;\n\n/**\n * Parse the project id embedded in an Intelligence API key.\n *\n * Intelligence API keys are formatted `cpk-{projectId}_{rest}` — this\n * extracts and numerically parses the `{projectId}` segment.\n *\n * @param apiKey - The Intelligence API key to parse.\n * @returns The parsed, strictly-positive, safe-integer project id.\n * @throws {ChannelConfigError} If `apiKey` does not match the expected\n * `cpk-{projectId}_...` format — a wrong/missing prefix or an absent project\n * id segment all fail the same match — or if the parsed project id is not a\n * strictly-positive safe integer.\n */\nexport function parseProjectIdFromApiKey(apiKey: string): number {\n const match = API_KEY_PROJECT_ID_PATTERN.exec(apiKey);\n if (!match) {\n // The whole API key is a `cpk-…` secret — everything after the fixed `cpk-`\n // namespace is sensitive, so a fixed-width slice (e.g. `apiKey.slice(0, 8)`)\n // would echo secret bytes for a `cpk-_short_long`-shaped key. This message is\n // logged and surfaced through `ready()`'s AggregateError, so echo NONE of the\n // key value; name only the expected format to aid diagnosis.\n throw new ChannelConfigError(\n `Could not parse a project id from the Intelligence API key — expected the ` +\n `\"cpk-{projectId}_...\" format (the key value is omitted here to avoid ` +\n `leaking secret material).`,\n );\n }\n const projectId = Number(match[1]);\n // Validate the parser's OWN output: `cpk-0_...` matches the pattern but a\n // non-positive project id would otherwise fail deep inside the launcher's\n // `assertValidChannelRealtimeScope` (which requires a positive projectId).\n // A very long digit run also matches `\\d+` but loses precision (or overflows\n // to Infinity) once coerced to `Number`, so it must be rejected too even\n // though it is `> 0` — `Number.isSafeInteger` catches both. This is the\n // parser guarding its own contract, not a channel-name replica, so it\n // belongs here. Reuse the same redaction: never echo the key value.\n if (!Number.isSafeInteger(projectId) || projectId <= 0) {\n throw new ChannelConfigError(\n `Parsed an invalid project id (${projectId}) from the Intelligence API ` +\n `key — the project id in \"cpk-{projectId}_...\" must be a positive safe ` +\n `integer (the key value is omitted here to avoid leaking secret material).`,\n );\n }\n return projectId;\n}\n\n/**\n * Derive the {@link ChannelActivationConfig} needed to activate `channel`\n * against the given Intelligence runtime configuration.\n *\n * @param args.intelligence - The Intelligence runtime client to pull the\n * runner websocket URL and auth token from.\n * @param args.channel - The Channel being activated. Must have a `name`; its\n * per-Channel `provider` selects the managed adapter declared to the gateway,\n * and `showToolStatus` optionally overrides managed tool-call visibility.\n * @param args.runtimeInstanceId - Identifier for the activating runtime\n * instance, passed through unchanged.\n * @returns The resolved {@link ChannelActivationConfig}.\n * @throws {ChannelConfigError} If the Intelligence API key does not carry a\n * parseable, strictly-positive project id, or if `channel.name` is\n * missing/empty.\n *\n * The managed provider is a PER-CHANNEL choice read from `channel.provider`, so\n * one runtime can activate a Slack-backed Channel and a Teams-backed Channel\n * side by side. When `channel.provider` is unset the config adapter defaults to\n * `\"slack\"` — an explicit, documented default, not a silent global. The SDK\n * only DECLARES this provider to the Intelligence gateway on join; the gateway\n * resolves the actual connection and is the authority on which providers it\n * accepts (it accepts only `\"slack\"` today — Teams gateway support is tracked\n * in OSS-450).\n *\n * The Channel-name FORMAT rules (lowercase kebab-case, 3–64 chars) and the\n * reserved-name rule are NOT re-checked here. Their single source of truth is\n * the `@copilotkit/channels-intelligence` launcher\n * (`assertValidChannelRealtimeScope` + `assertValidChannelNames`), which\n * validates them at activation; a malformed name surfaces as a logged `error`\n * status via {@link ChannelManager.ready} rather than an up-front throw. An\n * empty/missing name is still rejected here because that is this config's own\n * precondition (it has no name to forward at all), not a downstream replica.\n */\nexport function deriveChannelActivationConfig(args: {\n intelligence: CopilotKitIntelligence;\n channel: Channel;\n runtimeInstanceId: string;\n}): ChannelActivationConfig {\n const { intelligence, channel, runtimeInstanceId } = args;\n\n if (!channel.name) {\n throw new ChannelConfigError(\n \"Channel is missing a `name` — pass createChannel({ name }) to activate it.\",\n );\n }\n\n const channelName = channel.name;\n\n const wsUrl = intelligence.ɵgetChannelsWsUrl();\n const apiUrl = intelligence.ɵgetApiUrl();\n const apiKey = intelligence.ɵgetRunnerAuthToken();\n const projectId = parseProjectIdFromApiKey(apiKey);\n\n // Resolve the managed adapter declared to the gateway from the Channel's OWN\n // `provider` — a per-Channel choice, not a manager-wide default. When\n // `provider` is unset the adapter is the documented default `\"slack\"`; set\n // `createChannel({ provider: \"teams\" })` to declare Teams instead. The value\n // is trimmed so a padded runtime value (one that bypassed the typed union)\n // resolves to its bare provider rather than being forwarded with whitespace,\n // and a blank/whitespace-only provider falls back to `\"slack\"` (`??` alone\n // would keep `\"\"`).\n const trimmedProvider = channel.provider?.trim();\n\n return {\n wsUrl,\n apiUrl,\n apiKey,\n projectId,\n channelName,\n adapter: trimmedProvider ? trimmedProvider : \"slack\",\n ...(channel.showToolStatus !== undefined\n ? { showToolStatus: channel.showToolStatus }\n : {}),\n ...(channel.replyContinuation !== undefined\n ? { replyContinuation: channel.replyContinuation }\n : {}),\n runtimeInstanceId,\n };\n}\n"],"mappings":";;;;;;;;AAUA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;AA8ChB,MAAM,6BAA6B;;;;;;;;;;;;;;AAenC,SAAgB,yBAAyB,QAAwB;CAC/D,MAAM,QAAQ,2BAA2B,KAAK,OAAO;AACrD,KAAI,CAAC,MAMH,OAAM,IAAI,mBACR,6KAGD;CAEH,MAAM,YAAY,OAAO,MAAM,GAAG;AASlC,KAAI,CAAC,OAAO,cAAc,UAAU,IAAI,aAAa,EACnD,OAAM,IAAI,mBACR,iCAAiC,UAAU,6KAG5C;AAEH,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCT,SAAgB,8BAA8B,MAIlB;CAC1B,MAAM,EAAE,cAAc,SAAS,sBAAsB;AAErD,KAAI,CAAC,QAAQ,KACX,OAAM,IAAI,mBACR,6EACD;CAGH,MAAM,cAAc,QAAQ;CAE5B,MAAM,QAAQ,aAAa,mBAAmB;CAC9C,MAAM,SAAS,aAAa,YAAY;CACxC,MAAM,SAAS,aAAa,qBAAqB;CACjD,MAAM,YAAY,yBAAyB,OAAO;CAUlD,MAAM,kBAAkB,QAAQ,UAAU,MAAM;AAEhD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA,SAAS,kBAAkB,kBAAkB;EAC7C,GAAI,QAAQ,mBAAmB,SAC3B,EAAE,gBAAgB,QAAQ,gBAAgB,GAC1C,EAAE;EACN,GAAI,QAAQ,sBAAsB,SAC9B,EAAE,mBAAmB,QAAQ,mBAAmB,GAChD,EAAE;EACN;EACD"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require("reflect-metadata");
|
|
2
|
+
import { index_d_exports } from "../../../channels/dist/index.cjs";
|
|
3
|
+
|
|
2
4
|
//#region src/v2/runtime/core/channel-activation-config.d.ts
|
|
3
5
|
/**
|
|
4
6
|
* Resolved configuration needed to activate a single Channel against a
|
|
@@ -32,6 +34,11 @@ interface ChannelActivationConfig {
|
|
|
32
34
|
* default.
|
|
33
35
|
*/
|
|
34
36
|
showToolStatus?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Optional per-Channel tuning for splitting a long reply across continuation
|
|
39
|
+
* messages. Unset leaves the provider renderer's defaults in place.
|
|
40
|
+
*/
|
|
41
|
+
replyContinuation?: index_d_exports.ReplyContinuationOptions;
|
|
35
42
|
/** Identifier for the runtime instance activating this Channel. */
|
|
36
43
|
runtimeInstanceId: string;
|
|
37
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-activation-config.d.cts","names":[],"sources":["../../../../src/v2/runtime/core/channel-activation-config.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"channel-activation-config.d.cts","names":[],"sources":["../../../../src/v2/runtime/core/channel-activation-config.ts"],"mappings":";;;;;;;;UAqBiB,uBAAA;EAgCK;EA9BpB,KAAA;EAgCiB;EA9BjB,MAAA;;;;;EAKA,MAAA;;EAEA,SAAA;;EAEA,WAAA;;;;;;;;EAQA,OAAA;;;;;;EAMA,cAAA;;;;;EAKA,iBAAA,GAAoB,eAAA,CAAA,wBAAA;;EAEpB,iBAAA;AAAA"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
+
import { index_d_exports } from "../../../channels/dist/index.mjs";
|
|
3
|
+
|
|
2
4
|
//#region src/v2/runtime/core/channel-activation-config.d.ts
|
|
3
5
|
/**
|
|
4
6
|
* Resolved configuration needed to activate a single Channel against a
|
|
@@ -32,6 +34,11 @@ interface ChannelActivationConfig {
|
|
|
32
34
|
* default.
|
|
33
35
|
*/
|
|
34
36
|
showToolStatus?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Optional per-Channel tuning for splitting a long reply across continuation
|
|
39
|
+
* messages. Unset leaves the provider renderer's defaults in place.
|
|
40
|
+
*/
|
|
41
|
+
replyContinuation?: index_d_exports.ReplyContinuationOptions;
|
|
35
42
|
/** Identifier for the runtime instance activating this Channel. */
|
|
36
43
|
runtimeInstanceId: string;
|
|
37
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-activation-config.d.mts","names":[],"sources":["../../../../src/v2/runtime/core/channel-activation-config.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"channel-activation-config.d.mts","names":[],"sources":["../../../../src/v2/runtime/core/channel-activation-config.ts"],"mappings":";;;;;;;;UAqBiB,uBAAA;EAgCK;EA9BpB,KAAA;EAgCiB;EA9BjB,MAAA;;;;;EAKA,MAAA;;EAEA,SAAA;;EAEA,WAAA;;;;;;;;EAQA,OAAA;;;;;;EAMA,cAAA;;;;;EAKA,iBAAA,GAAoB,eAAA,CAAA,wBAAA;;EAEpB,iBAAA;AAAA"}
|
|
@@ -84,6 +84,7 @@ function deriveChannelActivationConfig(args) {
|
|
|
84
84
|
channelName,
|
|
85
85
|
adapter: trimmedProvider ? trimmedProvider : "slack",
|
|
86
86
|
...channel.showToolStatus !== void 0 ? { showToolStatus: channel.showToolStatus } : {},
|
|
87
|
+
...channel.replyContinuation !== void 0 ? { replyContinuation: channel.replyContinuation } : {},
|
|
87
88
|
runtimeInstanceId
|
|
88
89
|
};
|
|
89
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-activation-config.mjs","names":[],"sources":["../../../../src/v2/runtime/core/channel-activation-config.ts"],"sourcesContent":["import type { CopilotKitIntelligence } from \"../intelligence-platform\";\n// Type-only: @copilotkit/channels is pure-ESM, so a value import would break this\n// package's CJS output (see `runtime.ts` for the same constraint).\nimport type { Channel } from \"@copilotkit/channels\";\n\n/**\n * Error thrown when a Channel activation config cannot be derived — either the\n * Intelligence API key does not carry a project id in the expected\n * `cpk-{projectId}_...` format, or the {@link Channel} is missing a `name`.\n */\nexport class ChannelConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ChannelConfigError\";\n }\n}\n\n/**\n * Resolved configuration needed to activate a single Channel against a\n * running Intelligence runtime instance.\n */\nexport interface ChannelActivationConfig {\n /** Intelligence runner websocket URL the Channel connects to. */\n wsUrl: string;\n /** Intelligence API key used to authenticate the runner connection. */\n apiKey: string;\n /** Intelligence app-api HTTP base URL (`intelligence.ɵgetApiUrl()`), forwarded\n * to the transport so the managed realtime path enables file/history parity\n * (those are HTTP-only). Without it, Channels started by the normal runtime\n * handler run with no history and no file support (OSS-476). */\n apiUrl: string;\n /** Project id parsed from {@link apiKey}. */\n projectId: number;\n /** The Channel's declared name (`createChannel({ name })`). */\n channelName: string;\n /**\n * The managed provider this Channel declares to the Intelligence gateway on\n * join, resolved from the Channel's per-Channel `provider` (e.g. `\"slack\"` or\n * `\"teams\"`), defaulting to `\"slack\"`. Named `adapter` because that is the\n * field the gateway's join payload expects; the gateway resolves the actual\n * connection for the declared provider.\n */\n adapter: string;\n /**\n * Optional per-Channel override for visible managed tool-call progress.\n * Unset Slack routes default to hidden; other providers retain their existing\n * default.\n */\n showToolStatus?: boolean;\n /** Identifier for the runtime instance activating this Channel. */\n runtimeInstanceId: string;\n}\n\n/** Matches the `cpk-{projectId}_...` Intelligence API key format. */\nconst API_KEY_PROJECT_ID_PATTERN = /^cpk-(\\d+)_/;\n\n/**\n * Parse the project id embedded in an Intelligence API key.\n *\n * Intelligence API keys are formatted `cpk-{projectId}_{rest}` — this\n * extracts and numerically parses the `{projectId}` segment.\n *\n * @param apiKey - The Intelligence API key to parse.\n * @returns The parsed, strictly-positive, safe-integer project id.\n * @throws {ChannelConfigError} If `apiKey` does not match the expected\n * `cpk-{projectId}_...` format — a wrong/missing prefix or an absent project\n * id segment all fail the same match — or if the parsed project id is not a\n * strictly-positive safe integer.\n */\nexport function parseProjectIdFromApiKey(apiKey: string): number {\n const match = API_KEY_PROJECT_ID_PATTERN.exec(apiKey);\n if (!match) {\n // The whole API key is a `cpk-…` secret — everything after the fixed `cpk-`\n // namespace is sensitive, so a fixed-width slice (e.g. `apiKey.slice(0, 8)`)\n // would echo secret bytes for a `cpk-_short_long`-shaped key. This message is\n // logged and surfaced through `ready()`'s AggregateError, so echo NONE of the\n // key value; name only the expected format to aid diagnosis.\n throw new ChannelConfigError(\n `Could not parse a project id from the Intelligence API key — expected the ` +\n `\"cpk-{projectId}_...\" format (the key value is omitted here to avoid ` +\n `leaking secret material).`,\n );\n }\n const projectId = Number(match[1]);\n // Validate the parser's OWN output: `cpk-0_...` matches the pattern but a\n // non-positive project id would otherwise fail deep inside the launcher's\n // `assertValidChannelRealtimeScope` (which requires a positive projectId).\n // A very long digit run also matches `\\d+` but loses precision (or overflows\n // to Infinity) once coerced to `Number`, so it must be rejected too even\n // though it is `> 0` — `Number.isSafeInteger` catches both. This is the\n // parser guarding its own contract, not a channel-name replica, so it\n // belongs here. Reuse the same redaction: never echo the key value.\n if (!Number.isSafeInteger(projectId) || projectId <= 0) {\n throw new ChannelConfigError(\n `Parsed an invalid project id (${projectId}) from the Intelligence API ` +\n `key — the project id in \"cpk-{projectId}_...\" must be a positive safe ` +\n `integer (the key value is omitted here to avoid leaking secret material).`,\n );\n }\n return projectId;\n}\n\n/**\n * Derive the {@link ChannelActivationConfig} needed to activate `channel`\n * against the given Intelligence runtime configuration.\n *\n * @param args.intelligence - The Intelligence runtime client to pull the\n * runner websocket URL and auth token from.\n * @param args.channel - The Channel being activated. Must have a `name`; its\n * per-Channel `provider` selects the managed adapter declared to the gateway,\n * and `showToolStatus` optionally overrides managed tool-call visibility.\n * @param args.runtimeInstanceId - Identifier for the activating runtime\n * instance, passed through unchanged.\n * @returns The resolved {@link ChannelActivationConfig}.\n * @throws {ChannelConfigError} If the Intelligence API key does not carry a\n * parseable, strictly-positive project id, or if `channel.name` is\n * missing/empty.\n *\n * The managed provider is a PER-CHANNEL choice read from `channel.provider`, so\n * one runtime can activate a Slack-backed Channel and a Teams-backed Channel\n * side by side. When `channel.provider` is unset the config adapter defaults to\n * `\"slack\"` — an explicit, documented default, not a silent global. The SDK\n * only DECLARES this provider to the Intelligence gateway on join; the gateway\n * resolves the actual connection and is the authority on which providers it\n * accepts (it accepts only `\"slack\"` today — Teams gateway support is tracked\n * in OSS-450).\n *\n * The Channel-name FORMAT rules (lowercase kebab-case, 3–64 chars) and the\n * reserved-name rule are NOT re-checked here. Their single source of truth is\n * the `@copilotkit/channels-intelligence` launcher\n * (`assertValidChannelRealtimeScope` + `assertValidChannelNames`), which\n * validates them at activation; a malformed name surfaces as a logged `error`\n * status via {@link ChannelManager.ready} rather than an up-front throw. An\n * empty/missing name is still rejected here because that is this config's own\n * precondition (it has no name to forward at all), not a downstream replica.\n */\nexport function deriveChannelActivationConfig(args: {\n intelligence: CopilotKitIntelligence;\n channel: Channel;\n runtimeInstanceId: string;\n}): ChannelActivationConfig {\n const { intelligence, channel, runtimeInstanceId } = args;\n\n if (!channel.name) {\n throw new ChannelConfigError(\n \"Channel is missing a `name` — pass createChannel({ name }) to activate it.\",\n );\n }\n\n const channelName = channel.name;\n\n const wsUrl = intelligence.ɵgetChannelsWsUrl();\n const apiUrl = intelligence.ɵgetApiUrl();\n const apiKey = intelligence.ɵgetRunnerAuthToken();\n const projectId = parseProjectIdFromApiKey(apiKey);\n\n // Resolve the managed adapter declared to the gateway from the Channel's OWN\n // `provider` — a per-Channel choice, not a manager-wide default. When\n // `provider` is unset the adapter is the documented default `\"slack\"`; set\n // `createChannel({ provider: \"teams\" })` to declare Teams instead. The value\n // is trimmed so a padded runtime value (one that bypassed the typed union)\n // resolves to its bare provider rather than being forwarded with whitespace,\n // and a blank/whitespace-only provider falls back to `\"slack\"` (`??` alone\n // would keep `\"\"`).\n const trimmedProvider = channel.provider?.trim();\n\n return {\n wsUrl,\n apiUrl,\n apiKey,\n projectId,\n channelName,\n adapter: trimmedProvider ? trimmedProvider : \"slack\",\n ...(channel.showToolStatus !== undefined\n ? { showToolStatus: channel.showToolStatus }\n : {}),\n runtimeInstanceId,\n };\n}\n"],"mappings":";;;;;;;AAUA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;
|
|
1
|
+
{"version":3,"file":"channel-activation-config.mjs","names":[],"sources":["../../../../src/v2/runtime/core/channel-activation-config.ts"],"sourcesContent":["import type { CopilotKitIntelligence } from \"../intelligence-platform\";\n// Type-only: @copilotkit/channels is pure-ESM, so a value import would break this\n// package's CJS output (see `runtime.ts` for the same constraint).\nimport type { Channel, ReplyContinuationOptions } from \"@copilotkit/channels\";\n\n/**\n * Error thrown when a Channel activation config cannot be derived — either the\n * Intelligence API key does not carry a project id in the expected\n * `cpk-{projectId}_...` format, or the {@link Channel} is missing a `name`.\n */\nexport class ChannelConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ChannelConfigError\";\n }\n}\n\n/**\n * Resolved configuration needed to activate a single Channel against a\n * running Intelligence runtime instance.\n */\nexport interface ChannelActivationConfig {\n /** Intelligence runner websocket URL the Channel connects to. */\n wsUrl: string;\n /** Intelligence API key used to authenticate the runner connection. */\n apiKey: string;\n /** Intelligence app-api HTTP base URL (`intelligence.ɵgetApiUrl()`), forwarded\n * to the transport so the managed realtime path enables file/history parity\n * (those are HTTP-only). Without it, Channels started by the normal runtime\n * handler run with no history and no file support (OSS-476). */\n apiUrl: string;\n /** Project id parsed from {@link apiKey}. */\n projectId: number;\n /** The Channel's declared name (`createChannel({ name })`). */\n channelName: string;\n /**\n * The managed provider this Channel declares to the Intelligence gateway on\n * join, resolved from the Channel's per-Channel `provider` (e.g. `\"slack\"` or\n * `\"teams\"`), defaulting to `\"slack\"`. Named `adapter` because that is the\n * field the gateway's join payload expects; the gateway resolves the actual\n * connection for the declared provider.\n */\n adapter: string;\n /**\n * Optional per-Channel override for visible managed tool-call progress.\n * Unset Slack routes default to hidden; other providers retain their existing\n * default.\n */\n showToolStatus?: boolean;\n /**\n * Optional per-Channel tuning for splitting a long reply across continuation\n * messages. Unset leaves the provider renderer's defaults in place.\n */\n replyContinuation?: ReplyContinuationOptions;\n /** Identifier for the runtime instance activating this Channel. */\n runtimeInstanceId: string;\n}\n\n/** Matches the `cpk-{projectId}_...` Intelligence API key format. */\nconst API_KEY_PROJECT_ID_PATTERN = /^cpk-(\\d+)_/;\n\n/**\n * Parse the project id embedded in an Intelligence API key.\n *\n * Intelligence API keys are formatted `cpk-{projectId}_{rest}` — this\n * extracts and numerically parses the `{projectId}` segment.\n *\n * @param apiKey - The Intelligence API key to parse.\n * @returns The parsed, strictly-positive, safe-integer project id.\n * @throws {ChannelConfigError} If `apiKey` does not match the expected\n * `cpk-{projectId}_...` format — a wrong/missing prefix or an absent project\n * id segment all fail the same match — or if the parsed project id is not a\n * strictly-positive safe integer.\n */\nexport function parseProjectIdFromApiKey(apiKey: string): number {\n const match = API_KEY_PROJECT_ID_PATTERN.exec(apiKey);\n if (!match) {\n // The whole API key is a `cpk-…` secret — everything after the fixed `cpk-`\n // namespace is sensitive, so a fixed-width slice (e.g. `apiKey.slice(0, 8)`)\n // would echo secret bytes for a `cpk-_short_long`-shaped key. This message is\n // logged and surfaced through `ready()`'s AggregateError, so echo NONE of the\n // key value; name only the expected format to aid diagnosis.\n throw new ChannelConfigError(\n `Could not parse a project id from the Intelligence API key — expected the ` +\n `\"cpk-{projectId}_...\" format (the key value is omitted here to avoid ` +\n `leaking secret material).`,\n );\n }\n const projectId = Number(match[1]);\n // Validate the parser's OWN output: `cpk-0_...` matches the pattern but a\n // non-positive project id would otherwise fail deep inside the launcher's\n // `assertValidChannelRealtimeScope` (which requires a positive projectId).\n // A very long digit run also matches `\\d+` but loses precision (or overflows\n // to Infinity) once coerced to `Number`, so it must be rejected too even\n // though it is `> 0` — `Number.isSafeInteger` catches both. This is the\n // parser guarding its own contract, not a channel-name replica, so it\n // belongs here. Reuse the same redaction: never echo the key value.\n if (!Number.isSafeInteger(projectId) || projectId <= 0) {\n throw new ChannelConfigError(\n `Parsed an invalid project id (${projectId}) from the Intelligence API ` +\n `key — the project id in \"cpk-{projectId}_...\" must be a positive safe ` +\n `integer (the key value is omitted here to avoid leaking secret material).`,\n );\n }\n return projectId;\n}\n\n/**\n * Derive the {@link ChannelActivationConfig} needed to activate `channel`\n * against the given Intelligence runtime configuration.\n *\n * @param args.intelligence - The Intelligence runtime client to pull the\n * runner websocket URL and auth token from.\n * @param args.channel - The Channel being activated. Must have a `name`; its\n * per-Channel `provider` selects the managed adapter declared to the gateway,\n * and `showToolStatus` optionally overrides managed tool-call visibility.\n * @param args.runtimeInstanceId - Identifier for the activating runtime\n * instance, passed through unchanged.\n * @returns The resolved {@link ChannelActivationConfig}.\n * @throws {ChannelConfigError} If the Intelligence API key does not carry a\n * parseable, strictly-positive project id, or if `channel.name` is\n * missing/empty.\n *\n * The managed provider is a PER-CHANNEL choice read from `channel.provider`, so\n * one runtime can activate a Slack-backed Channel and a Teams-backed Channel\n * side by side. When `channel.provider` is unset the config adapter defaults to\n * `\"slack\"` — an explicit, documented default, not a silent global. The SDK\n * only DECLARES this provider to the Intelligence gateway on join; the gateway\n * resolves the actual connection and is the authority on which providers it\n * accepts (it accepts only `\"slack\"` today — Teams gateway support is tracked\n * in OSS-450).\n *\n * The Channel-name FORMAT rules (lowercase kebab-case, 3–64 chars) and the\n * reserved-name rule are NOT re-checked here. Their single source of truth is\n * the `@copilotkit/channels-intelligence` launcher\n * (`assertValidChannelRealtimeScope` + `assertValidChannelNames`), which\n * validates them at activation; a malformed name surfaces as a logged `error`\n * status via {@link ChannelManager.ready} rather than an up-front throw. An\n * empty/missing name is still rejected here because that is this config's own\n * precondition (it has no name to forward at all), not a downstream replica.\n */\nexport function deriveChannelActivationConfig(args: {\n intelligence: CopilotKitIntelligence;\n channel: Channel;\n runtimeInstanceId: string;\n}): ChannelActivationConfig {\n const { intelligence, channel, runtimeInstanceId } = args;\n\n if (!channel.name) {\n throw new ChannelConfigError(\n \"Channel is missing a `name` — pass createChannel({ name }) to activate it.\",\n );\n }\n\n const channelName = channel.name;\n\n const wsUrl = intelligence.ɵgetChannelsWsUrl();\n const apiUrl = intelligence.ɵgetApiUrl();\n const apiKey = intelligence.ɵgetRunnerAuthToken();\n const projectId = parseProjectIdFromApiKey(apiKey);\n\n // Resolve the managed adapter declared to the gateway from the Channel's OWN\n // `provider` — a per-Channel choice, not a manager-wide default. When\n // `provider` is unset the adapter is the documented default `\"slack\"`; set\n // `createChannel({ provider: \"teams\" })` to declare Teams instead. The value\n // is trimmed so a padded runtime value (one that bypassed the typed union)\n // resolves to its bare provider rather than being forwarded with whitespace,\n // and a blank/whitespace-only provider falls back to `\"slack\"` (`??` alone\n // would keep `\"\"`).\n const trimmedProvider = channel.provider?.trim();\n\n return {\n wsUrl,\n apiUrl,\n apiKey,\n projectId,\n channelName,\n adapter: trimmedProvider ? trimmedProvider : \"slack\",\n ...(channel.showToolStatus !== undefined\n ? { showToolStatus: channel.showToolStatus }\n : {}),\n ...(channel.replyContinuation !== undefined\n ? { replyContinuation: channel.replyContinuation }\n : {}),\n runtimeInstanceId,\n };\n}\n"],"mappings":";;;;;;;AAUA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;AA8ChB,MAAM,6BAA6B;;;;;;;;;;;;;;AAenC,SAAgB,yBAAyB,QAAwB;CAC/D,MAAM,QAAQ,2BAA2B,KAAK,OAAO;AACrD,KAAI,CAAC,MAMH,OAAM,IAAI,mBACR,6KAGD;CAEH,MAAM,YAAY,OAAO,MAAM,GAAG;AASlC,KAAI,CAAC,OAAO,cAAc,UAAU,IAAI,aAAa,EACnD,OAAM,IAAI,mBACR,iCAAiC,UAAU,6KAG5C;AAEH,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCT,SAAgB,8BAA8B,MAIlB;CAC1B,MAAM,EAAE,cAAc,SAAS,sBAAsB;AAErD,KAAI,CAAC,QAAQ,KACX,OAAM,IAAI,mBACR,6EACD;CAGH,MAAM,cAAc,QAAQ;CAE5B,MAAM,QAAQ,aAAa,mBAAmB;CAC9C,MAAM,SAAS,aAAa,YAAY;CACxC,MAAM,SAAS,aAAa,qBAAqB;CACjD,MAAM,YAAY,yBAAyB,OAAO;CAUlD,MAAM,kBAAkB,QAAQ,UAAU,MAAM;AAEhD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA,SAAS,kBAAkB,kBAAkB;EAC7C,GAAI,QAAQ,mBAAmB,SAC3B,EAAE,gBAAgB,QAAQ,gBAAgB,GAC1C,EAAE;EACN,GAAI,QAAQ,sBAAsB,SAC9B,EAAE,mBAAmB,QAAQ,mBAAmB,GAChD,EAAE;EACN;EACD"}
|
|
@@ -67,14 +67,16 @@ async function defaultActivateChannel(config, channel, importChannelsIntelligenc
|
|
|
67
67
|
runtimeInstanceId: config.runtimeInstanceId,
|
|
68
68
|
adapter: config.adapter,
|
|
69
69
|
...config.showToolStatus !== void 0 ? { showToolStatus: config.showToolStatus } : {},
|
|
70
|
+
...config.replyContinuation !== void 0 ? { replyContinuation: config.replyContinuation } : {},
|
|
70
71
|
appApiBaseUrl: config.apiUrl,
|
|
71
72
|
...log ? { log } : {},
|
|
72
73
|
runCanonical: (args) => runCanonicalChannelAgent(services.runner, services.intelligence, services.lockTtlSeconds ?? 20, services.lockHeartbeatIntervalSeconds ?? 15, args, services.lockKeyPrefix),
|
|
73
74
|
loadHistory: async ({ threadId, appUserId }) => {
|
|
74
|
-
|
|
75
|
+
const history = await services.intelligence.getThreadMessages({
|
|
75
76
|
threadId,
|
|
76
77
|
userId: appUserId
|
|
77
|
-
})
|
|
78
|
+
});
|
|
79
|
+
return Promise.all(history.messages.map((message) => toAgentMessage(message, services.intelligence)));
|
|
78
80
|
}
|
|
79
81
|
});
|
|
80
82
|
}
|
|
@@ -200,11 +202,13 @@ async function runCanonicalChannelAgent(runner, intelligence, lockTtlSeconds, lo
|
|
|
200
202
|
return result;
|
|
201
203
|
}
|
|
202
204
|
/** Convert canonical Intelligence history into AG-UI messages. */
|
|
203
|
-
function toAgentMessage(message) {
|
|
205
|
+
async function toAgentMessage(message, intelligence) {
|
|
206
|
+
const content = await hydrateManagedContent(message.content, intelligence);
|
|
204
207
|
return {
|
|
205
208
|
id: message.id,
|
|
206
209
|
role: message.role,
|
|
207
|
-
content:
|
|
210
|
+
content: content ?? "",
|
|
211
|
+
...message.activityType ? { activityType: message.activityType } : {},
|
|
208
212
|
...message.toolCalls ? { toolCalls: message.toolCalls.map((call) => ({
|
|
209
213
|
id: call.id,
|
|
210
214
|
type: "function",
|
|
@@ -216,6 +220,34 @@ function toAgentMessage(message) {
|
|
|
216
220
|
...message.toolCallId ? { toolCallId: message.toolCallId } : {}
|
|
217
221
|
};
|
|
218
222
|
}
|
|
223
|
+
/** Resolves managed asset references only at the authorized Runtime boundary. */
|
|
224
|
+
async function hydrateManagedContent(content, intelligence) {
|
|
225
|
+
if (Array.isArray(content)) return Promise.all(content.map(async (part) => {
|
|
226
|
+
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;
|
|
227
|
+
const assetId = part.source.value.slice(13);
|
|
228
|
+
const asset = await intelligence.ɵgetManagedChannelAsset(assetId);
|
|
229
|
+
return {
|
|
230
|
+
...part,
|
|
231
|
+
source: {
|
|
232
|
+
type: "data",
|
|
233
|
+
value: Buffer.from(asset.bytes).toString("base64"),
|
|
234
|
+
mimeType: asset.mimeType ?? ("mimeType" in part.source && typeof part.source.mimeType === "string" ? part.source.mimeType : "application/octet-stream")
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}));
|
|
238
|
+
if (typeof content === "object" && content !== null && "assetId" in content && typeof content.assetId === "string") {
|
|
239
|
+
const asset = await intelligence.ɵgetManagedChannelAsset(content.assetId);
|
|
240
|
+
return {
|
|
241
|
+
...content,
|
|
242
|
+
source: {
|
|
243
|
+
type: "data",
|
|
244
|
+
value: Buffer.from(asset.bytes).toString("base64"),
|
|
245
|
+
mimeType: asset.mimeType ?? ("mimeType" in content && typeof content.mimeType === "string" ? content.mimeType : "application/octet-stream")
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return content;
|
|
250
|
+
}
|
|
219
251
|
/** Whether `err` signals a missing managed provider rather than a hard failure. */
|
|
220
252
|
function isSetupRequired(err) {
|
|
221
253
|
return err instanceof ChannelSetupRequiredError || typeof err === "object" && err !== null && err.code === "SETUP_REQUIRED";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-manager.cjs","names":["AbstractAgent","EMPTY","EventType","deriveChannelActivationConfig","ChannelConfigError"],"sources":["../../../../src/v2/runtime/core/channel-manager.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n ChannelConfigError,\n deriveChannelActivationConfig,\n} from \"./channel-activation-config\";\nimport type { ChannelActivationConfig } from \"./channel-activation-config\";\nimport type { CopilotKitIntelligence } from \"../intelligence-platform\";\nimport { AbstractAgent, EventType } from \"@ag-ui/client\";\nimport type {\n AgentSubscriber,\n BaseEvent,\n Message,\n RunAgentParameters,\n RunAgentResult,\n} from \"@ag-ui/client\";\nimport { EMPTY } from \"rxjs\";\nimport type { AgentRunner } from \"../runner/agent-runner\";\n// Type-only: @copilotkit/channels is pure-ESM, so a value import would break this\n// package's CJS output (see `core/runtime.ts` and `channel-activation-config.ts`\n// for the same constraint).\nimport type { Channel } from \"@copilotkit/channels\";\n\n/**\n * Lifecycle status of a single Channel activation, or of the manager overall.\n *\n * - `connecting`: activation in flight, not yet settled.\n * - `online`: activation resolved AND the managed session can currently send.\n * A drop moves the Channel to `reconnecting` (not `online`); a successful\n * rejoin restores `online`.\n * - `setup_required`: the Channel is declared but has no managed provider yet —\n * a valid degraded state, not a failure.\n * - `reconnecting`: the managed session dropped and Phoenix is retrying — not\n * currently sendable. The manager does NOT re-activate (reconnection is\n * delegated to the Phoenix connection layer); it only reflects the health the\n * session reports via its `onStateChange` observer.\n * - `stopped`: {@link ChannelManager.stop} has torn the Channel down.\n * - `error`: activation rejected with a non-setup error, or a previously-online\n * control link gave up reconnecting after its bounded reconnect window.\n *\n * Both MANAGED (Intelligence-gateway) and DIRECT (developer-supplied adapter)\n * Channels move through these states. A direct Channel is driven by the manager\n * too — it is started via {@link Channel.ɵruntime}`.start()` and reaches `online`\n * once its own transport is up — but it is NOT wired into the Intelligence\n * gateway/canonical/reliability layer: it simply runs its own adapter transport,\n * started and stopped by the manager. A direct Channel has no managed-session\n * drop signal, so it never reports `reconnecting`.\n *\n * That gap is BY DESIGN, not a deferral. Run-correctness (canonical\n * cross-surface history, one outer run and one terminal outcome, durable\n * HITL-resume-across-restart, selection pinning) and the reliability layer are\n * Intelligence-side only — see OSS-599's boundary discipline. A direct Channel's\n * ceiling is the SDK's in-process run loop. Do not \"finish\" this by pulling the\n * canonical/reliability layer down into the SDK: that is explicitly the thing\n * OSS-599 forbids.\n */\nexport type ChannelStatus =\n | \"connecting\"\n | \"online\"\n | \"setup_required\"\n | \"reconnecting\"\n | \"stopped\"\n | \"error\";\n\n/**\n * The lifecycle control surface a Channel host uses to drive and observe\n * managed Channel activation.\n */\nexport interface ChannelsControl {\n /**\n * Resolve once every declared Channel has settled to a terminal, non-connecting\n * state (`online` or `setup_required`). Rejects if any Channel is in `error`,\n * or — when `timeoutMs` is given — if the whole set has not settled in time.\n */\n ready(opts?: { timeoutMs?: number }): Promise<void>;\n /** Snapshot the overall status and the per-Channel status map. */\n status(): { overall: ChannelStatus; channels: Record<string, ChannelStatus> };\n /** Tear down every activated Channel. Idempotent. */\n stop(): Promise<void>;\n}\n\n/**\n * Signals that a declared Channel cannot be activated because no managed\n * provider exists for it yet. The engine throws this (or any error whose\n * `code === \"SETUP_REQUIRED\"`) to move a Channel to `setup_required` rather\n * than `error` — a declared-but-unprovisioned Channel is a valid degraded\n * state, not a failure.\n */\nexport class ChannelSetupRequiredError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ChannelSetupRequiredError\";\n }\n}\n\n/**\n * The activation engine: given a resolved {@link ChannelActivationConfig} and\n * the declared {@link Channel}, bring the Channel online and return its handle.\n * Injected in tests (a fake engine); defaults to the Realtime Gateway launcher.\n */\nexport type ActivateChannelEngine = (\n config: ChannelActivationConfig,\n channel: Channel,\n) => Promise<ChannelsHandle>;\n\n/**\n * Minimal structural view of the `@copilotkit/channels-intelligence`\n * `ChannelsHandle`. Declared locally (not imported) because the runtime is a\n * CJS package that must not take a static dependency on the pure-ESM\n * channels-intelligence package — the default engine reaches its launcher\n * through a dynamic `import()` instead. The manager only ever needs `stop()`.\n */\nexport interface ChannelsHandle {\n /** Activation metadata declared to Intelligence. Unused by the manager. */\n metadata: unknown;\n /** Stop the underlying Channel(s) and release transports. */\n stop(): Promise<void>;\n /**\n * Optional seam: register a callback the handle fires when its managed\n * session drops. Retained as a per-episode drop breadcrumb; the manager drives\n * status from {@link ChannelsHandle.onStateChange} instead. Present on the\n * Realtime Gateway launcher handle; optional for non-gateway/test handles.\n */\n onClose?(cb: () => void): void;\n /**\n * Optional seam: register a connection-health observer the handle fires as its\n * managed session moves between `online` (sendable), `reconnecting` (dropped,\n * Phoenix retrying), and `gave_up` (dead after the bounded reconnect window).\n * The manager uses this to keep {@link ChannelManager.status} honest — it does\n * NOT re-activate on a drop (reconnection is delegated to the Phoenix\n * connection layer; see {@link ChannelManager}). Optional so non-gateway or\n * test handles that do not implement it are always invoked as\n * `handle.onStateChange?.(cb)`.\n */\n onStateChange?(\n cb: (\n state: \"online\" | \"reconnecting\" | \"gave_up\",\n detail?: { reason?: string; code?: string },\n ) => void,\n ): void;\n}\n\n/** Constructor arguments for {@link ChannelManager}. */\nexport interface ChannelManagerArgs {\n /** The Intelligence runtime client the activation config is derived from. */\n intelligence: CopilotKitIntelligence;\n /** The declared framework Channels to activate. */\n channels: Channel[];\n /** Standard runtime AgentRunner used by managed Channel executions. */\n runner?: AgentRunner;\n /** Standard thread-lock TTL forwarded to Channel AgentRunner heartbeats. */\n lockTtlSeconds?: number;\n /** Standard thread-lock heartbeat cadence used by Channel AgentRunner calls. */\n lockHeartbeatIntervalSeconds?: number;\n /** Must match web Intelligence runs so channel + HTTP share the same lock key. */\n lockKeyPrefix?: string;\n /**\n * Activation engine. Defaults to a wrapper over the channels-intelligence\n * Realtime Gateway launcher (`startChannelsOverRealtimeGateway`), reached via\n * dynamic import so this CJS package keeps no static ESM dependency.\n */\n activateChannel?: ActivateChannelEngine;\n /** Mint a runtime instance id per Channel. Defaults to `rti_{uuid-no-dashes}`. */\n mintRuntimeInstanceId?: () => string;\n /** Diagnostic sink. Forwarded to the launcher/transport when the default\n * activation engine is used, so transport-level drops surface in the managed\n * path (not just activation-level events). */\n log?: (msg: string, meta?: unknown) => void;\n /**\n * How often (ms) to repeat a \"still down\" log while a managed session is\n * disconnected. A dropped session was previously silent for as long as the\n * outage lasted, which made a dead bot indistinguishable from an idle one\n * (OSS-670). Injectable so tests need no fake timers. Default 30000.\n */\n reconnectLogIntervalMs?: number;\n /** Per-handle deadline (ms) for `handle.stop()` during {@link ChannelManager.stop}\n * so a wedged stop can't hang SIGTERM shutdown. Default 5000. */\n stopHandleTimeoutMs?: number;\n}\n\n/** Per-Channel mutable activation entry tracked by the manager. */\ninterface ChannelEntry {\n status: ChannelStatus;\n /** Resolves on `online`/`setup_required`; rejects on `error`. Awaited by `ready`. */\n readonly settled: Promise<void>;\n handle?: ChannelsHandle;\n /**\n * Whether {@link ChannelManager.stopEntry} has already stopped `handle`. Gates\n * the single-stop guarantee: the success settle handler and `stop()` can both\n * reach the same entry in the same tick, but the handle is torn down at most\n * once.\n */\n handleStopped: boolean;\n /** Epoch ms this outage episode began; unset while the session is healthy. */\n downSince?: number;\n /** Repeating \"still down\" logger for this outage; cleared on recovery/teardown. */\n reconnectLogTimer?: ReturnType<typeof setInterval>;\n}\n\n/**\n * Runtime installs this pure-ESM package as a direct dependency, but the\n * specifier must stay non-literal so it never becomes a static dependency of\n * the runtime's CJS build. The packed-consumer contract is enforced by\n * `scripts/release/verify-runtime-package.ts`.\n */\nconst CHANNELS_INTELLIGENCE_SPECIFIER = \"@copilotkit/channels-intelligence\";\n\n/**\n * Structural view of the `@copilotkit/channels-intelligence` module surface the\n * default engine consumes. Declared locally (not imported) for the same\n * CJS/ESM-boundary reason the {@link ChannelsHandle} view is.\n */\nexport interface ChannelsIntelligenceModule {\n startChannelsOverRealtimeGateway: (\n channels: Channel[],\n opts: {\n wsUrl: string;\n apiKey: string;\n scope: { projectId: number; channelName: string };\n runtimeInstanceId: string;\n adapter?: string;\n /** Optional per-Channel override for managed tool-call visibility. */\n showToolStatus?: boolean;\n /** Intelligence app-api HTTP base URL, forwarded to the transport so the\n * managed realtime path enables file/history parity (HTTP-only) — OSS-476. */\n appApiBaseUrl?: string;\n /** Diagnostic sink forwarded to the launcher/transport so transport-level\n * drop diagnostics (e.g. a version-skew missing-leaseToken outage) are not\n * silent in the managed path. */\n log?: (msg: string, meta?: unknown) => void;\n runCanonical(args: {\n agent: AbstractAgent;\n threadId: string;\n runId: string;\n userId: string;\n agentId: string;\n tools: readonly {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n }[];\n context: readonly { description: string; value: string }[];\n persistedInputMessages: Message[];\n execute(\n subscriber: AgentSubscriber,\n canonicalRun?: { threadId: string; runId: string },\n ): Promise<{\n iterations: number;\n interrupted: boolean;\n deliveryError?: unknown;\n }>;\n }): Promise<{\n iterations: number;\n interrupted: boolean;\n deliveryError?: unknown;\n }>;\n loadHistory(args: {\n threadId: string;\n appUserId: string;\n }): Promise<Message[]>;\n },\n ) => Promise<ChannelsHandle>;\n}\n\n/**\n * Default engine: wrap the channels-intelligence Realtime Gateway launcher.\n *\n * The module is reached through an injectable importer that defaults to a\n * dynamic `import()` of a non-literal specifier, so the pure-ESM\n * `@copilotkit/channels-intelligence` never becomes a static dependency of this\n * CJS package (mirrors the runtime's other channels seams). The `import`\n * seam is a parameter purely so this function's config→opts mapping and its\n * module-not-found / generic-error branches are unit-testable WITHOUT the real\n * package installed; production always uses the default importer.\n *\n * Passes NO `org`/`channelId` — the launcher's realtime scope treats them as\n * optional.\n *\n * @param config - Resolved activation config for the Channel.\n * @param channel - The Channel to activate.\n * @param importChannelsIntelligence - Test seam; loads the channels-intelligence\n * module. Defaults to a dynamic import of the real package.\n * @param log - Optional diagnostic sink forwarded to the launcher/transport so\n * transport-level drop diagnostics are not silent in the managed path.\n * @returns The launcher's {@link ChannelsHandle}.\n */\nexport async function defaultActivateChannel(\n config: ChannelActivationConfig,\n channel: Channel,\n importChannelsIntelligence: () => Promise<ChannelsIntelligenceModule> = () =>\n import(\n CHANNELS_INTELLIGENCE_SPECIFIER\n ) as Promise<ChannelsIntelligenceModule>,\n log?: (msg: string, meta?: unknown) => void,\n services?: {\n runner: AgentRunner;\n intelligence: CopilotKitIntelligence;\n lockTtlSeconds?: number;\n lockHeartbeatIntervalSeconds?: number;\n lockKeyPrefix?: string;\n },\n): Promise<ChannelsHandle> {\n let mod: ChannelsIntelligenceModule;\n try {\n mod = await importChannelsIntelligence();\n } catch (err) {\n if (isModuleNotFound(err)) {\n throw new Error(\n \"Managed Channels require '@copilotkit/channels-intelligence' to be installed. Add it to your app's dependencies.\",\n { cause: err },\n );\n }\n throw err;\n }\n if (!services) {\n throw new Error(\n \"Managed Channels require the runtime AgentRunner and Intelligence client\",\n );\n }\n return mod.startChannelsOverRealtimeGateway([channel], {\n wsUrl: config.wsUrl,\n apiKey: config.apiKey,\n scope: { projectId: config.projectId, channelName: config.channelName },\n runtimeInstanceId: config.runtimeInstanceId,\n adapter: config.adapter,\n ...(config.showToolStatus !== undefined\n ? { showToolStatus: config.showToolStatus }\n : {}),\n // Forward the app-api HTTP base URL so the transport wires file/history\n // (HTTP-only) on the NORMAL managed path — without this, Channels started by\n // the CopilotRuntime handler run with no history/file support (OSS-476).\n appApiBaseUrl: config.apiUrl,\n // Forward the manager's diagnostic sink down to the launcher/transport so a\n // transport-level drop (e.g. a version-skew missing-leaseToken outage) is\n // observable in the managed path, not just activation-level events.\n ...(log ? { log } : {}),\n runCanonical: (args) =>\n runCanonicalChannelAgent(\n services.runner,\n services.intelligence,\n services.lockTtlSeconds ?? 20,\n services.lockHeartbeatIntervalSeconds ?? 15,\n args,\n services.lockKeyPrefix,\n ),\n loadHistory: async ({ threadId, appUserId }) => {\n const history = await services.intelligence.getThreadMessages({\n threadId,\n userId: appUserId,\n });\n return history.messages.map(toAgentMessage);\n },\n });\n}\n\ninterface CanonicalRunArgs {\n agent: AbstractAgent;\n threadId: string;\n runId: string;\n userId: string;\n agentId: string;\n tools: readonly {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n }[];\n context: readonly { description: string; value: string }[];\n persistedInputMessages: Message[];\n execute(\n subscriber: AgentSubscriber,\n canonicalRun?: { threadId: string; runId: string },\n ): Promise<{\n iterations: number;\n interrupted: boolean;\n deliveryError?: unknown;\n }>;\n}\n\n/** One outer agent that lets the standard runner own the whole local tool loop. */\nclass ChannelOuterAgent extends AbstractAgent {\n constructor(\n private readonly inner: AbstractAgent,\n private readonly canonicalThreadId: string,\n private readonly executeLoop: CanonicalRunArgs[\"execute\"],\n ) {\n super({\n threadId: inner.threadId,\n initialMessages: inner.messages,\n initialState: inner.state,\n ...(inner.agentId ? { agentId: inner.agentId } : {}),\n });\n }\n\n run(): ReturnType<AbstractAgent[\"run\"]> {\n return EMPTY;\n }\n\n override async runAgent(\n parameters?: RunAgentParameters,\n subscriber?: AgentSubscriber,\n ): Promise<RunAgentResult> {\n if (!parameters?.runId) {\n throw new Error(\"Canonical Channel run requires a runId\");\n }\n const result = await this.executeLoop(subscriber ?? {}, {\n threadId: this.canonicalThreadId,\n runId: parameters.runId,\n });\n return { result, newMessages: [] };\n }\n\n override abortRun(): void {\n this.inner.abortRun();\n }\n}\n\n/** Drive one public Channel run through the runtime's existing AgentRunner. */\nasync function runCanonicalChannelAgent(\n runner: AgentRunner,\n intelligence: CopilotKitIntelligence,\n lockTtlSeconds: number,\n lockHeartbeatIntervalSeconds: number,\n args: CanonicalRunArgs,\n lockKeyPrefix?: string,\n): Promise<{\n iterations: number;\n interrupted: boolean;\n deliveryError?: unknown;\n}> {\n const lock = await intelligence.ɵacquireThreadLock({\n threadId: args.threadId,\n runId: args.runId,\n userId: args.userId,\n agentId: args.agentId,\n ttlSeconds: lockTtlSeconds,\n ...(lockKeyPrefix !== undefined ? { lockKeyPrefix } : {}),\n });\n const canonicalThreadId = lock.threadId;\n const canonicalRunId = lock.runId;\n let result = { iterations: 0, interrupted: false };\n const outer = new ChannelOuterAgent(\n args.agent,\n canonicalThreadId,\n async (subscriber, canonicalRun) => {\n result = await args.execute(subscriber, canonicalRun);\n return result;\n },\n );\n let stopPromise: Promise<boolean | undefined> | undefined;\n let heartbeatError: unknown;\n let heartbeatTimer: ReturnType<typeof setInterval> | undefined;\n const stopCanonicalRun = (): void => {\n stopPromise ??= Promise.resolve()\n .then(() => runner.stop({ threadId: canonicalThreadId }))\n .catch(() => false);\n };\n heartbeatTimer = setInterval(() => {\n intelligence\n .ɵrenewThreadLock({\n threadId: canonicalThreadId,\n runId: canonicalRunId,\n ttlSeconds: lockTtlSeconds,\n ...(lockKeyPrefix !== undefined ? { lockKeyPrefix } : {}),\n })\n .catch((error: unknown) => {\n if (heartbeatTimer === undefined) return;\n clearInterval(heartbeatTimer);\n heartbeatTimer = undefined;\n heartbeatError = error;\n try {\n args.agent.abortRun();\n } catch {\n // The runner stop below remains the authoritative cancellation path.\n }\n stopCanonicalRun();\n });\n }, lockHeartbeatIntervalSeconds * 1_000);\n heartbeatTimer.unref?.();\n\n try {\n await new Promise<void>((resolve, reject) => {\n let terminalError: (Error & { code?: string }) | undefined;\n const stream = runner.run({\n threadId: canonicalThreadId,\n agent: outer,\n input: {\n threadId: canonicalThreadId,\n runId: canonicalRunId,\n messages: args.agent.messages,\n state: args.agent.state,\n tools: [...args.tools],\n context: [...args.context],\n forwardedProps: undefined,\n },\n persistedInputMessages: args.persistedInputMessages,\n });\n stream.subscribe({\n next: (event: BaseEvent) => {\n if (event.type !== EventType.RUN_ERROR || terminalError) return;\n const message =\n \"message\" in event && typeof event.message === \"string\"\n ? event.message\n : \"Canonical Channel agent run failed\";\n terminalError = new Error(message);\n terminalError.name = \"ChannelCanonicalRunError\";\n if (\n \"code\" in event &&\n typeof event.code === \"string\" &&\n event.code.length > 0\n ) {\n terminalError.code = event.code;\n }\n },\n error: reject,\n complete: () => {\n if (terminalError) {\n reject(terminalError);\n } else {\n resolve();\n }\n },\n });\n });\n } finally {\n if (heartbeatTimer !== undefined) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = undefined;\n }\n // Always release the product thread lock from the Runtime side. Gateway\n // may also release on terminal AG-UI ingestion; cleanup is idempotent and\n // covers runner paths that never stream terminal events (or lose them).\n await intelligence\n .ɵcleanupThreadLock({\n threadId: canonicalThreadId,\n runId: canonicalRunId,\n })\n .catch(() => undefined);\n }\n\n if (heartbeatError !== undefined) {\n await stopPromise;\n throw heartbeatError;\n }\n return result;\n}\n\n/** Convert canonical Intelligence history into AG-UI messages. */\nfunction toAgentMessage(message: {\n id: string;\n role: string;\n content?: string;\n toolCalls?: Array<{ id: string; name: string; args: string }>;\n toolCallId?: string;\n}): Message {\n return {\n id: message.id,\n role: message.role as Message[\"role\"],\n content: message.content ?? \"\",\n ...(message.toolCalls\n ? {\n toolCalls: message.toolCalls.map((call) => ({\n id: call.id,\n type: \"function\" as const,\n function: { name: call.name, arguments: call.args },\n })),\n }\n : {}),\n ...(message.toolCallId ? { toolCallId: message.toolCallId } : {}),\n } as Message;\n}\n\n/** Whether `err` signals a missing managed provider rather than a hard failure. */\nfunction isSetupRequired(err: unknown): boolean {\n return (\n err instanceof ChannelSetupRequiredError ||\n (typeof err === \"object\" &&\n err !== null &&\n (err as { code?: unknown }).code === \"SETUP_REQUIRED\")\n );\n}\n\n/**\n * Whether `err` is a Node/runtime module-resolution failure — i.e. the error\n * a dynamic `import()` throws when the target package is not installed.\n * Exported so the friendly-error path in {@link defaultActivateChannel} can be\n * unit-tested without forcing a real failing import.\n */\nexport function isModuleNotFound(err: unknown): boolean {\n if (typeof err !== \"object\" || err === null) {\n return false;\n }\n const code = (err as { code?: unknown }).code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\n/** Default deadline (ms) for a single `handle.stop()` during teardown. */\nconst DEFAULT_STOP_HANDLE_TIMEOUT_MS = 5_000;\n\n/** Default cadence (ms) for the \"still down\" log while a session is dropped. */\nconst DEFAULT_RECONNECT_LOG_INTERVAL_MS = 30_000;\n\n/**\n * Reject with `timeoutMessage` after `timeoutMs` if `inner` has not settled,\n * otherwise pass `inner` through. When `timeoutMs` is undefined, `inner` is\n * returned unchanged. The timer is `unref`'d so a pending deadline never keeps\n * the process alive, and `inner` always has a settle handler attached, so a\n * timed-out promise that later settles never surfaces as unhandled.\n */\nfunction withTimeout<T>(\n inner: Promise<T>,\n timeoutMs: number | undefined,\n timeoutMessage: string,\n): Promise<T> {\n if (timeoutMs === undefined) {\n return inner;\n }\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(\n () => reject(new Error(timeoutMessage)),\n timeoutMs,\n );\n (timer as unknown as { unref?: () => void }).unref?.();\n inner.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n },\n );\n });\n}\n\n/**\n * Drives Channel activation for an Intelligence runtime: lazily activates each\n * declared Channel, tracks per-Channel lifecycle status, exposes readiness, and\n * tears everything down. A MANAGED Channel (empty `adapters`) is activated\n * through the injected engine over the Intelligence gateway; a DIRECT Channel\n * (developer-supplied adapter) is started through its own transport seam\n * (`channel.ɵruntime.start()`) — driven by the manager, but running only its own\n * adapter transport, not the gateway path (direct Channels stay below the\n * canonical/reliability layer by design — see {@link ChannelStatus} and OSS-599).\n * Its very existence means Intelligence is configured, so there is no\n * standalone/self-started path.\n *\n * Activation is lazy and idempotent — constructing the manager does nothing;\n * {@link activate} starts it and a second call is a no-op. Activation throws\n * SYNCHRONOUSLY (a {@link ChannelConfigError}) only for a misconfiguration it\n * can detect up front — a duplicate or missing Channel name. Every OTHER\n * activation failure is recorded as the Channel's status (`error`, or\n * `setup_required` for a missing provider) and surfaced through {@link status}\n * and {@link ready} rather than thrown.\n *\n * Reconnection is NOT handled here — it is delegated to the Phoenix connection\n * layer that backs the launcher. When a managed control socket drops, Phoenix's\n * `Socket` reconnects and rejoins with the same Runtime declaration. Active\n * deliveries request fresh one-use join tokens through that control link. A\n * re-activation here would be both redundant AND broken: re-invoking the engine\n * on an already-started `Channel` throws in `channel.addAdapter` (started=true).\n * The manager therefore never re-activates on a drop.\n *\n * It DOES, however, reflect real connection health through the session's\n * `onStateChange` observer so {@link ChannelManager.status} stays honest rather\n * than reporting `online` forever after a drop: a drop moves the Channel to\n * `reconnecting`, a successful rejoin restores `online`, and a bounded give-up\n * (Phoenix would otherwise retry forever) moves it to `error`.\n */\nexport class ChannelManager implements ChannelsControl {\n private readonly intelligence: CopilotKitIntelligence;\n private readonly runner?: AgentRunner;\n private readonly lockTtlSeconds: number;\n private readonly lockHeartbeatIntervalSeconds: number;\n private readonly lockKeyPrefix?: string;\n private readonly channels: Channel[];\n private readonly activateChannel: ActivateChannelEngine;\n private readonly mintRuntimeInstanceId: () => string;\n private readonly log?: (msg: string, meta?: unknown) => void;\n private readonly stopHandleTimeoutMs: number;\n private readonly reconnectLogIntervalMs: number;\n\n private readonly entries = new Map<string, ChannelEntry>();\n private activated = false;\n private stopped = false;\n\n /** @param args - See {@link ChannelManagerArgs}. */\n constructor(args: ChannelManagerArgs) {\n this.intelligence = args.intelligence;\n this.runner = args.runner;\n this.lockTtlSeconds = args.lockTtlSeconds ?? 20;\n this.lockHeartbeatIntervalSeconds = args.lockHeartbeatIntervalSeconds ?? 15;\n this.lockKeyPrefix = args.lockKeyPrefix;\n this.channels = args.channels;\n this.log = args.log;\n // When using the default engine, forward the manager's log DOWN to the\n // launcher/transport (via defaultActivateChannel's log param) so a\n // transport-level drop is observable in the managed path. `this.log` is read\n // lazily at activation time, so this closure always sees the assigned sink.\n this.activateChannel =\n args.activateChannel ??\n ((config, channel) =>\n defaultActivateChannel(\n config,\n channel,\n undefined,\n this.log,\n this.runner\n ? {\n runner: this.runner,\n intelligence: this.intelligence,\n lockTtlSeconds: this.lockTtlSeconds,\n lockHeartbeatIntervalSeconds: this.lockHeartbeatIntervalSeconds,\n ...(this.lockKeyPrefix !== undefined\n ? { lockKeyPrefix: this.lockKeyPrefix }\n : {}),\n }\n : undefined,\n ));\n this.mintRuntimeInstanceId =\n args.mintRuntimeInstanceId ??\n (() => `rti_${randomUUID().replace(/-/g, \"\")}`);\n this.stopHandleTimeoutMs =\n args.stopHandleTimeoutMs ?? DEFAULT_STOP_HANDLE_TIMEOUT_MS;\n this.reconnectLogIntervalMs =\n args.reconnectLogIntervalMs ?? DEFAULT_RECONNECT_LOG_INTERVAL_MS;\n }\n\n /**\n * Start activation of every declared Channel (lazy + idempotent). Mints a\n * distinct runtime instance id per Channel, derives its activation config,\n * and calls the engine. Records each Channel as `connecting`, transitioning\n * to `online`/`setup_required`/`error` as its activation settles.\n */\n activate(): void {\n // Short-circuit on BOTH latches: `activated` makes activation idempotent,\n // and `stopped` prevents a post-`stop()` activate() from opening transports\n // on a dead manager. (A late activation self-heals via the post-settle guard,\n // but never starting it is cheaper and clearer.)\n if (this.activated || this.stopped) {\n return;\n }\n // Reject duplicate Channel names BEFORE kicking off any engine call. The\n // manager keys `entries` by name, so a duplicate would let the second\n // activation's entry silently overwrite the first — leaking the first\n // Channel's control link out of status()/ready()/stop(). Fail loud here so\n // nothing is ever activated in that state.\n this.assertUniqueChannelNames();\n this.activated = true;\n\n // Partition declared Channels by transport. A Channel carrying ANY adapter\n // that is NOT the Intelligence managed adapter (a developer-supplied\n // slack/discord/... adapter, which lacks `__intelligenceChannel`) is a\n // DIRECT channel. The manager still owns its lifecycle — but a direct Channel\n // runs its OWN adapter transport rather than the Intelligence gateway path:\n // it is started here via `channel.ɵruntime.start()` (not the managed engine)\n // and torn down via `channel.ɵruntime.stop()`. The distinction is EXCLUSIVE\n // PER CHANNEL, not per platform — a Channel served by a direct adapter is not\n // also managed: ANY direct adapter makes the WHOLE Channel direct, regardless\n // of platform. Attaching the managed adapter alongside a direct one would\n // double-deliver every turn (and trip the SDK's `assertExclusive` guard,\n // moving the Channel to `error`). Per the SoT rule, never infer managed intent\n // from a local direct adapter — a managed-eligible Channel has an empty\n // `adapters` at declaration time. Managed+direct coexistence on the same\n // Channel is NOT supported today; it is deferred (OSS-484). Direct Channels\n // run only their own transport here and stay below the Intelligence\n // canonical/reliability layer by design, not pending work (OSS-599).\n for (const channel of this.channels) {\n const isDirect = channel.adapters.some((a) => !a.__intelligenceChannel);\n if (isDirect) {\n this.startDirectChannel(channel);\n continue;\n }\n const name = channel.name!;\n const runtimeInstanceId = this.mintRuntimeInstanceId();\n\n let resolveSettled!: () => void;\n let rejectSettled!: (err: unknown) => void;\n const settled = new Promise<void>((resolve, reject) => {\n resolveSettled = resolve;\n rejectSettled = reject;\n });\n // ready() awaits `settled`; if nothing ever handles a rejection there,\n // Node reports an unhandled rejection. Attach a no-op catch so the\n // promise is always considered handled — ready() still sees the reason.\n settled.catch(() => {});\n\n // Invoke the engine synchronously so activation is observably started the\n // moment activate() returns (callers assert the engine was called and see\n // `connecting` before awaiting ready). A synchronous config/engine throw is\n // turned into a rejected activation so it becomes this channel's status\n // rather than throwing out of activate().\n let activation: Promise<ChannelsHandle>;\n let config: ChannelActivationConfig | undefined;\n try {\n config = deriveChannelActivationConfig({\n intelligence: this.intelligence,\n channel,\n runtimeInstanceId,\n });\n activation = this.activateChannel(config, channel);\n } catch (err) {\n activation = Promise.reject(err);\n }\n\n // The deferred `.then` callbacks capture `entry` and run only after the\n // literal has fully initialized, so referencing it here is safe.\n const entry: ChannelEntry = {\n status: \"connecting\",\n handle: undefined,\n handleStopped: false,\n settled,\n };\n\n // Anchor the settle handlers. Both branches route every teardown through\n // the idempotent `stopEntry`, so a late settle can never resurrect a\n // `stopped` entry and a handle is torn down at most once. The handlers\n // only mutate state (never throw), so the trailing no-op catch just keeps\n // the chain from surfacing as an unhandled rejection.\n activation\n .then(\n async (handle) => {\n entry.handle = handle;\n if (this.stopped) {\n // stop() ran before this activation settled, so it could not tear\n // down a handle that did not exist yet. Release it now (idempotent)\n // and keep the Channel `stopped`.\n await this.stopEntry(entry);\n resolveSettled();\n return;\n }\n entry.status = \"online\";\n this.registerConnectionObserver(name, entry);\n resolveSettled();\n },\n async (err: unknown) => {\n if (this.stopped) {\n // A rejection that arrives AFTER stop() must NOT resurrect the\n // entry into `error`/`setup_required`: the Channel is already\n // being torn down. Keep it `stopped` and resolve `settled` so a\n // subsequent ready() does not reject on a stopped Channel.\n await this.stopEntry(entry);\n resolveSettled();\n return;\n }\n if (isSetupRequired(err)) {\n entry.status = \"setup_required\";\n this.log?.(`channel \"${name}\" requires setup`, err);\n resolveSettled();\n } else {\n entry.status = \"error\";\n this.log?.(`channel \"${name}\" failed to activate`, err);\n rejectSettled(err);\n }\n },\n )\n .catch(() => {});\n\n this.entries.set(name, entry);\n }\n }\n\n /**\n * Start a DIRECT-adapter Channel through its own transport seam\n * ({@link Channel.ɵruntime}`.start()`), recording a live entry so\n * {@link ready}/{@link status}/{@link stop} all cover it. A direct Channel is\n * driven by the manager — but only because the Intelligence runtime constructed\n * this manager at all — and runs its OWN adapter transport, NOT the Intelligence\n * gateway path. It is deliberately NOT wired into the gateway/canonical/\n * reliability layer — that boundary is permanent, not a deferral (OSS-599).\n *\n * Mirrors the managed path's settle machinery so teardown resilience is shared:\n * the entry's `handle` is a synthetic {@link ChannelsHandle} whose `stop()`\n * calls `channel.ɵruntime.stop()`, so the SAME idempotent, bounded, resilient\n * {@link stopEntry} that tears down a managed handle tears down a direct Channel\n * too. The handle is assigned only AFTER `start()` resolves (exactly as the\n * managed path assigns its handle only on resolve), so a `stop()` during a\n * still-starting direct Channel returns promptly with nothing to stop and the\n * post-settle guard tears down the late transport. A direct Channel exposes no\n * managed-session drop signal, so no connection observer is wired and it never\n * reaches `reconnecting`.\n *\n * @param channel - The direct-adapter Channel to start.\n */\n private startDirectChannel(channel: Channel): void {\n const name = channel.name!;\n this.log?.(\n `channel \"${name}\" carries a direct adapter — starting its own transport via channel.ɵruntime.start() (the Intelligence runtime drives its lifecycle; it runs its own adapter transport, NOT the managed gateway path — direct Channels stay below the canonical/reliability layer by design (OSS-599); managed+direct coexistence deferred (OSS-484))`,\n );\n\n let resolveSettled!: () => void;\n let rejectSettled!: (err: unknown) => void;\n const settled = new Promise<void>((resolve, reject) => {\n resolveSettled = resolve;\n rejectSettled = reject;\n });\n // ready() awaits `settled`; attach a no-op catch so a rejection is always\n // considered handled (ready() still sees the reason).\n settled.catch(() => {});\n\n // Synthetic handle wrapping the Channel's own stop seam. Assigned to the\n // entry only on successful start (below) so the shared teardown machinery\n // (handleStopped guard, withTimeout bounding, resilient allSettled) stops a\n // direct Channel exactly as it stops a managed handle.\n const directHandle: ChannelsHandle = {\n metadata: {},\n stop: () => channel.ɵruntime.stop(),\n };\n\n // Start the direct transport synchronously so it is observably started the\n // moment activate() returns (callers see `connecting` before awaiting ready).\n // A synchronous throw becomes this Channel's status rather than throwing out\n // of activate().\n let activation: Promise<void>;\n try {\n activation = channel.ɵruntime.start();\n } catch (err) {\n activation = Promise.reject(err);\n }\n\n const entry: ChannelEntry = {\n status: \"connecting\",\n handle: undefined,\n handleStopped: false,\n settled,\n };\n\n activation\n .then(\n async () => {\n entry.handle = directHandle;\n if (this.stopped) {\n // stop() ran before start() settled, so it could not tear down a\n // transport that was not up yet. Release it now (idempotent) and keep\n // the Channel `stopped`.\n await this.stopEntry(entry);\n resolveSettled();\n return;\n }\n entry.status = \"online\";\n resolveSettled();\n },\n async (err: unknown) => {\n if (this.stopped) {\n // A start rejection that arrives AFTER stop() must NOT resurrect the\n // entry into `error`: the Channel is already being torn down. Keep it\n // `stopped` and resolve `settled` so a later ready() does not reject.\n entry.handle = directHandle;\n await this.stopEntry(entry);\n resolveSettled();\n return;\n }\n entry.status = \"error\";\n this.log?.(\n `channel \"${name}\" failed to start its direct transport`,\n err,\n );\n rejectSettled(err);\n },\n )\n .catch(() => {});\n\n this.entries.set(name, entry);\n }\n\n /**\n * Throw if two declared Channels share a `name`. `entries` is keyed by name,\n * so a duplicate would overwrite the first Channel's entry and leak its live\n * session. Called at the very start of {@link activate}, before any engine\n * call, so a misconfiguration fails loud instead of silently.\n *\n * @throws {ChannelConfigError} If any Channel is missing a name, or if any\n * name appears more than once.\n */\n private assertUniqueChannelNames(): void {\n const seen = new Set<string>();\n for (const channel of this.channels) {\n const name = channel.name;\n // Check for a missing/empty name FIRST: `channel.name!` on a nameless\n // Channel keys as the string \"undefined\", which would otherwise report a\n // spurious duplicate for two nameless Channels before the accurate\n // missing-name error. Fail with the precise error instead.\n if (!name) {\n throw new ChannelConfigError(\n \"A managed Channel is missing a `name` — every declared Channel must \" +\n \"have a unique, non-empty name (pass createChannel({ name })).\",\n );\n }\n if (seen.has(name)) {\n throw new ChannelConfigError(\n `Duplicate managed Channel name \"${name}\" — every declared Channel ` +\n `must have a unique name.`,\n );\n }\n seen.add(name);\n }\n }\n\n /**\n * Resolve when every declared Channel — managed OR direct — has settled to\n * `online`/`setup_required`.\n *\n * A direct-adapter Channel is awaited too: its `settled` resolves once its own\n * transport is up (`channel.ɵruntime.start()` settling) and rejects if that\n * start fails, exactly as a managed Channel's `settled` tracks its activation.\n *\n * Activates lazily if not already started — so a first call rejects with the\n * same {@link ChannelConfigError} as the synchronous throw from\n * {@link activate} for an up-front misconfiguration (duplicate/missing Channel\n * names). Once activation has been kicked off, all OTHER failures are surfaced\n * here instead: this rejects with an `AggregateError` if any Channel settled\n * to `error` OR — when `timeoutMs` is given — did not settle in time. The\n * `timeoutMs` deadline is applied PER CHANNEL, so the aggregate carries each\n * failed Channel's real reason AND a named timeout for each Channel still\n * hanging: a genuine activation error is never masked by a sibling that hangs\n * (a pre-fix set-wide timeout discarded the real reason in that case).\n *\n * A STOPPED manager short-circuits and resolves: a Channel that settled to\n * `error` BEFORE {@link stop} already rejected its `settled` promise, so\n * awaiting it here would throw an `AggregateError` even though\n * {@link status}.overall is `\"stopped\"` — inconsistent with the case where the\n * Channel was still online at stop() (which resolves). A stopped manager has\n * nothing left to be ready for, so resolve uniformly.\n *\n * `ready()` is ONE-SHOT: it settles on the INITIAL activation outcome. Later\n * connection-health transitions (a live Channel dropping to `reconnecting`, or\n * giving up to `error`) are reported through {@link status} — where `online`\n * means currently-sendable — but do NOT re-arm or re-reject an already-settled\n * `ready()`.\n */\n async ready(opts?: { timeoutMs?: number }): Promise<void> {\n if (this.stopped) {\n return;\n }\n this.activate();\n const entries = [...this.entries.entries()];\n // Apply `timeoutMs` PER CHANNEL rather than to the whole set. A single\n // set-wide timeout wrapping `allSettled` would, when one channel settles to\n // `error` while a sibling hangs, reject with only a generic timeout and\n // DISCARD the erroring channel's real reason. Timing out each channel's\n // `settled` independently lets `allSettled` collect BOTH a hung channel's\n // named timeout AND a failed channel's real error into one AggregateError.\n const results = await Promise.allSettled(\n entries.map(([name, e]) =>\n withTimeout(\n e.settled,\n opts?.timeoutMs,\n `channel \"${name}\" did not settle within ${opts?.timeoutMs}ms`,\n ),\n ),\n );\n const errors = results\n .filter((r): r is PromiseRejectedResult => r.status === \"rejected\")\n .map((r) => r.reason);\n if (errors.length > 0) {\n throw new AggregateError(\n errors,\n `ChannelManager.ready: ${errors.length} channel(s) failed to activate or settle in time`,\n );\n }\n }\n\n /**\n * Snapshot status. Every declared Channel — managed OR direct — appears keyed\n * by name in `channels`; a direct-adapter Channel reads `online` once its own\n * transport is up, exactly like a managed one.\n *\n * `overall` is folded over ALL declared Channels (see {@link computeOverall}),\n * by precedence `error` > `reconnecting` > `setup_required` > `connecting` >\n * `online`. `online` means every Channel can currently send. `reconnecting`\n * outranks `setup_required` because a dropped-but-retrying Channel is an active\n * outage, louder than a steadily-degraded unprovisioned one (only managed\n * Channels ever reach `reconnecting`; a direct Channel has no managed-session\n * drop signal). With no declared Channels at all, `overall` is `online` (nothing\n * is degraded); once every Channel has been stopped, `overall` is `stopped`.\n */\n status(): {\n overall: ChannelStatus;\n channels: Record<string, ChannelStatus>;\n } {\n const channels: Record<string, ChannelStatus> = {};\n for (const [name, entry] of this.entries) {\n channels[name] = entry.status;\n }\n // A stopped manager is `stopped` regardless of whether it was ever activated.\n // stop() before activate() (e.g. SIGTERM during startup) leaves `entries`\n // empty, and the empty-set fold below returns `online` — a torn-down manager\n // must never read healthy. Short-circuit before that fold. (After a normal\n // activate→stop, every entry is already `stopped` and the fold agrees, so\n // this is also consistent with the populated case.)\n if (this.stopped) {\n return { overall: \"stopped\", channels };\n }\n // Before activate() has run, `entries` is empty. Folding an empty set gives\n // `online` — correct for a manager that declares NO channels (nothing is\n // degraded), but a LIE for one that declares channels and simply has not\n // opened its socket yet: activation is lazy (deferred to the first\n // `ready()`), so a not-yet-activated manager must never read `online`.\n // Report `connecting` (\"not started\") for that case so `status()` is honest\n // before any `ready()`.\n if (!this.activated && this.channels.length > 0) {\n return { overall: \"connecting\", channels };\n }\n return { overall: this.computeOverall(Object.values(channels)), channels };\n }\n\n /**\n * Fold per-Channel statuses into a single overall status (see {@link status}).\n *\n * Every declared Channel — managed OR direct — participates: a started direct\n * Channel reads `online` and counts toward health exactly like a managed one,\n * and its `error` is a real outage that must dominate. Statuses are ranked\n * `error` > `reconnecting` > `setup_required` > `connecting` > `online`, so a\n * genuine failure still dominates a healthy sibling. (Only managed Channels ever\n * reach `reconnecting`; a direct Channel has no managed-session drop signal.)\n * The empty-input case (no declared Channels at all) stays `online` (nothing is\n * degraded).\n */\n private computeOverall(values: ChannelStatus[]): ChannelStatus {\n if (values.length === 0) {\n return \"online\";\n }\n if (values.every((v) => v === \"stopped\")) {\n return \"stopped\";\n }\n if (values.includes(\"error\")) {\n return \"error\";\n }\n if (values.includes(\"reconnecting\")) {\n return \"reconnecting\";\n }\n if (values.includes(\"setup_required\")) {\n return \"setup_required\";\n }\n if (values.includes(\"connecting\")) {\n return \"connecting\";\n }\n return \"online\";\n }\n\n /**\n * Wire the Channel's connection-health observer (if the handle exposes the\n * optional `onStateChange` seam) so {@link ChannelManager.status} reflects real\n * health instead of reporting `online` forever after a drop:\n *\n * - `reconnecting` → status `reconnecting` (dropped, Phoenix retrying);\n * - `online` → status `online` (rejoined, sendable again);\n * - `gave_up` → status `error` (dead after the bounded reconnect window).\n *\n * Makes NO re-activation — reconnection is delegated to the Phoenix connection\n * layer (see {@link ChannelManager}), which auto-rejoins under the persistent\n * adapter. A STOPPED manager (or an already-stopped entry) ignores late\n * connection events, so a drop that fires after {@link ChannelManager.stop}\n * never resurrects the Channel out of `stopped`.\n *\n * @param name - The Channel name (map key).\n * @param entry - The Channel's activation entry.\n */\n private registerConnectionObserver(name: string, entry: ChannelEntry): void {\n entry.handle?.onStateChange?.((state, detail) => {\n // A stopped manager (or a stopped entry) ignores late connection events.\n if (this.stopped || entry.status === \"stopped\") {\n return;\n }\n const cause = detail?.reason ?? detail?.code;\n const because = cause !== undefined ? ` — ${cause}` : \"\";\n if (state === \"reconnecting\") {\n entry.status = \"reconnecting\";\n entry.downSince ??= Date.now();\n this.log?.(\n `channel \"${name}\" managed session dropped; reconnecting (Phoenix auto-rejoin)${because}`,\n );\n this.startReconnectLog(name, entry);\n } else if (state === \"online\") {\n entry.status = \"online\";\n this.clearReconnectLog(entry);\n entry.downSince = undefined;\n this.log?.(`channel \"${name}\" managed session back online`);\n } else if (state === \"gave_up\") {\n // `error` here means \"not sendable\", NOT \"dead\": Phoenix keeps retrying\n // underneath and a successful rejoin restores `online`. Say so, or the\n // line reads as terminal (OSS-670). The repeat keeps running.\n entry.status = \"error\";\n this.log?.(\n `channel \"${name}\" managed session gave up reconnecting after ${this.downFor(entry)}; ` +\n `marking error (still retrying — a successful rejoin restores online)${because}`,\n );\n }\n });\n }\n\n /** Rendered downtime for this outage episode (`\"45s\"`), or `\"unknown\"`. */\n private downFor(entry: ChannelEntry): string {\n return entry.downSince === undefined\n ? \"unknown\"\n : `${Math.round((Date.now() - entry.downSince) / 1000)}s`;\n }\n\n /**\n * Repeat a \"still down\" line for as long as this outage lasts. Runs THROUGH\n * `gave_up` on purpose: that transition is where the old behavior went quiet,\n * and an operator watching a silent process cannot tell a dead bot from an\n * idle one.\n */\n private startReconnectLog(name: string, entry: ChannelEntry): void {\n if (entry.reconnectLogTimer !== undefined) return;\n const timer = setInterval(() => {\n if (this.stopped || entry.status === \"stopped\") {\n this.clearReconnectLog(entry);\n return;\n }\n this.log?.(\n `channel \"${name}\" managed session still down after ${this.downFor(entry)}; Phoenix is retrying`,\n );\n }, this.reconnectLogIntervalMs);\n (timer as unknown as { unref?: () => void }).unref?.();\n entry.reconnectLogTimer = timer;\n }\n\n /** Stop this entry's \"still down\" repeat, if one is running. */\n private clearReconnectLog(entry: ChannelEntry): void {\n if (entry.reconnectLogTimer !== undefined) {\n clearInterval(entry.reconnectLogTimer);\n entry.reconnectLogTimer = undefined;\n }\n }\n\n /**\n * Drive a single entry to its terminal `stopped` state, tearing down its\n * handle AT MOST ONCE. Idempotent: it always sets `status = \"stopped\"`, and\n * only calls `handle.stop()` on the first invocation that sees a live,\n * not-yet-stopped handle (gated by {@link ChannelEntry.handleStopped}).\n *\n * This is the ONE guarded teardown path shared by both `stop()` and the\n * post-settle guard in {@link activate}/{@link startDirectChannel}. Because the\n * guard is per-entry and idempotent, a handle assigned in the same tick as\n * `stop()` is stopped exactly once even when both callers reach the entry, and a\n * late settle can never resurrect a `stopped` entry. It is transport-agnostic: a\n * managed entry's `handle.stop()` releases the gateway session, and a direct\n * entry's synthetic handle (assigned in {@link startDirectChannel}) routes the\n * same `handle.stop()` to `channel.ɵruntime.stop()` — so direct and managed\n * Channels share one bounded, resilient teardown.\n *\n * `handle.stop()` failures are logged (via {@link ChannelManager.log}) but NOT\n * rethrown: the real launcher's `stop()` rethrows after `session.disconnect()`,\n * and teardown must still complete for every other entry. The call is wrapped\n * in `Promise.resolve().then(...)` so a foreign/injected handle whose `stop()`\n * throws SYNCHRONOUSLY (before any promise is created) is caught by the same\n * `.catch` — otherwise the sync throw would escape, skip `resolveSettled()` in\n * the fulfilled-then-stopped branch of {@link activate}, and hang `settled`.\n *\n * An entry with no handle yet (a still-`connecting` Channel whose transport has\n * not come up) is only marked `stopped`: there is nothing to tear down, and the\n * post-settle guard releases the transport if it arrives after `stop()`.\n *\n * A WEDGED `handle.stop()` (one that never settles) is bounded by\n * {@link ChannelManagerArgs.stopHandleTimeoutMs}: after the deadline the call\n * is logged and abandoned so it can't hang `stop()` — and thus SIGTERM\n * shutdown — forever.\n *\n * @param entry - The Channel entry to stop.\n */\n private async stopEntry(entry: ChannelEntry): Promise<void> {\n entry.status = \"stopped\";\n // An unref'd interval would not hold the process open, but a stopped\n // manager must not keep logging about a session it no longer owns.\n this.clearReconnectLog(entry);\n if (entry.handle && !entry.handleStopped) {\n entry.handleStopped = true;\n const handle = entry.handle;\n // Bound handle.stop(): a wedged stop() (e.g. a socket.disconnect that\n // never returns) must not hang teardown — and thus SIGTERM shutdown —\n // forever. On timeout, log and abandon it (the call keeps running with a\n // settle handler attached inside withTimeout, so it never surfaces as an\n // unhandled rejection) so every OTHER entry still reaches `stopped`. The\n // `Promise.resolve().then(...)` wrap also routes a SYNCHRONOUS throw from\n // a foreign handle through the same timeout+catch.\n await withTimeout(\n Promise.resolve().then(() => handle.stop()),\n this.stopHandleTimeoutMs,\n `channel handle stop() timed out after ${this.stopHandleTimeoutMs}ms during teardown`,\n ).catch((err: unknown) =>\n this.log?.(\"channel handle stop() failed during teardown\", err),\n );\n }\n }\n\n /**\n * Stop every activated Channel exactly once and mark all statuses `stopped`.\n * Idempotent — a second call is a no-op.\n *\n * Resolves promptly: {@link stopEntry} stops only the handles that already\n * exist and never blocks on activations that have not settled. A hung connect\n * (which `ready({ timeoutMs })` tolerates) has no handle to stop yet, and\n * awaiting it here would hang teardown — and thus SIGTERM shutdown — forever.\n * Any handle that arrives after this point is torn down by the post-settle\n * guard in {@link activate}, which routes through the same idempotent\n * {@link stopEntry}, so nothing leaks and nothing double-stops.\n *\n * Teardown is resilient to a throwing `handle.stop()`: `Promise.allSettled`\n * over the per-entry `stopEntry` calls guarantees one rejection can't abort\n * the rest, so every entry reaches `stopped` and `stop()` always resolves.\n * It is equally resilient to a WEDGED `handle.stop()` that never settles: each\n * is bounded by {@link ChannelManagerArgs.stopHandleTimeoutMs} inside\n * {@link stopEntry}, so a single hung handle can't hang SIGTERM shutdown.\n */\n async stop(): Promise<void> {\n if (this.stopped) {\n return;\n }\n this.stopped = true;\n\n const entries = [...this.entries.values()];\n await Promise.allSettled(entries.map((entry) => this.stopEntry(entry)));\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"channel-manager.cjs","names":["AbstractAgent","EMPTY","EventType","deriveChannelActivationConfig","ChannelConfigError"],"sources":["../../../../src/v2/runtime/core/channel-manager.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n ChannelConfigError,\n deriveChannelActivationConfig,\n} from \"./channel-activation-config\";\nimport type { ChannelActivationConfig } from \"./channel-activation-config\";\nimport type { CopilotKitIntelligence } from \"../intelligence-platform\";\nimport { AbstractAgent, EventType } from \"@ag-ui/client\";\nimport type {\n AgentSubscriber,\n BaseEvent,\n Message,\n RunAgentParameters,\n RunAgentResult,\n} from \"@ag-ui/client\";\nimport { EMPTY } from \"rxjs\";\nimport type { AgentRunner } from \"../runner/agent-runner\";\n// Type-only: @copilotkit/channels is pure-ESM, so a value import would break this\n// package's CJS output (see `core/runtime.ts` and `channel-activation-config.ts`\n// for the same constraint).\nimport type { Channel, ReplyContinuationOptions } from \"@copilotkit/channels\";\n\n/**\n * Lifecycle status of a single Channel activation, or of the manager overall.\n *\n * - `connecting`: activation in flight, not yet settled.\n * - `online`: activation resolved AND the managed session can currently send.\n * A drop moves the Channel to `reconnecting` (not `online`); a successful\n * rejoin restores `online`.\n * - `setup_required`: the Channel is declared but has no managed provider yet —\n * a valid degraded state, not a failure.\n * - `reconnecting`: the managed session dropped and Phoenix is retrying — not\n * currently sendable. The manager does NOT re-activate (reconnection is\n * delegated to the Phoenix connection layer); it only reflects the health the\n * session reports via its `onStateChange` observer.\n * - `stopped`: {@link ChannelManager.stop} has torn the Channel down.\n * - `error`: activation rejected with a non-setup error, or a previously-online\n * control link gave up reconnecting after its bounded reconnect window.\n *\n * Both MANAGED (Intelligence-gateway) and DIRECT (developer-supplied adapter)\n * Channels move through these states. A direct Channel is driven by the manager\n * too — it is started via {@link Channel.ɵruntime}`.start()` and reaches `online`\n * once its own transport is up — but it is NOT wired into the Intelligence\n * gateway/canonical/reliability layer: it simply runs its own adapter transport,\n * started and stopped by the manager. A direct Channel has no managed-session\n * drop signal, so it never reports `reconnecting`.\n *\n * That gap is BY DESIGN, not a deferral. Run-correctness (canonical\n * cross-surface history, one outer run and one terminal outcome, durable\n * HITL-resume-across-restart, selection pinning) and the reliability layer are\n * Intelligence-side only — see OSS-599's boundary discipline. A direct Channel's\n * ceiling is the SDK's in-process run loop. Do not \"finish\" this by pulling the\n * canonical/reliability layer down into the SDK: that is explicitly the thing\n * OSS-599 forbids.\n */\nexport type ChannelStatus =\n | \"connecting\"\n | \"online\"\n | \"setup_required\"\n | \"reconnecting\"\n | \"stopped\"\n | \"error\";\n\n/**\n * The lifecycle control surface a Channel host uses to drive and observe\n * managed Channel activation.\n */\nexport interface ChannelsControl {\n /**\n * Resolve once every declared Channel has settled to a terminal, non-connecting\n * state (`online` or `setup_required`). Rejects if any Channel is in `error`,\n * or — when `timeoutMs` is given — if the whole set has not settled in time.\n */\n ready(opts?: { timeoutMs?: number }): Promise<void>;\n /** Snapshot the overall status and the per-Channel status map. */\n status(): { overall: ChannelStatus; channels: Record<string, ChannelStatus> };\n /** Tear down every activated Channel. Idempotent. */\n stop(): Promise<void>;\n}\n\n/**\n * Signals that a declared Channel cannot be activated because no managed\n * provider exists for it yet. The engine throws this (or any error whose\n * `code === \"SETUP_REQUIRED\"`) to move a Channel to `setup_required` rather\n * than `error` — a declared-but-unprovisioned Channel is a valid degraded\n * state, not a failure.\n */\nexport class ChannelSetupRequiredError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ChannelSetupRequiredError\";\n }\n}\n\n/**\n * The activation engine: given a resolved {@link ChannelActivationConfig} and\n * the declared {@link Channel}, bring the Channel online and return its handle.\n * Injected in tests (a fake engine); defaults to the Realtime Gateway launcher.\n */\nexport type ActivateChannelEngine = (\n config: ChannelActivationConfig,\n channel: Channel,\n) => Promise<ChannelsHandle>;\n\n/**\n * Minimal structural view of the `@copilotkit/channels-intelligence`\n * `ChannelsHandle`. Declared locally (not imported) because the runtime is a\n * CJS package that must not take a static dependency on the pure-ESM\n * channels-intelligence package — the default engine reaches its launcher\n * through a dynamic `import()` instead. The manager only ever needs `stop()`.\n */\nexport interface ChannelsHandle {\n /** Activation metadata declared to Intelligence. Unused by the manager. */\n metadata: unknown;\n /** Stop the underlying Channel(s) and release transports. */\n stop(): Promise<void>;\n /**\n * Optional seam: register a callback the handle fires when its managed\n * session drops. Retained as a per-episode drop breadcrumb; the manager drives\n * status from {@link ChannelsHandle.onStateChange} instead. Present on the\n * Realtime Gateway launcher handle; optional for non-gateway/test handles.\n */\n onClose?(cb: () => void): void;\n /**\n * Optional seam: register a connection-health observer the handle fires as its\n * managed session moves between `online` (sendable), `reconnecting` (dropped,\n * Phoenix retrying), and `gave_up` (dead after the bounded reconnect window).\n * The manager uses this to keep {@link ChannelManager.status} honest — it does\n * NOT re-activate on a drop (reconnection is delegated to the Phoenix\n * connection layer; see {@link ChannelManager}). Optional so non-gateway or\n * test handles that do not implement it are always invoked as\n * `handle.onStateChange?.(cb)`.\n */\n onStateChange?(\n cb: (\n state: \"online\" | \"reconnecting\" | \"gave_up\",\n detail?: { reason?: string; code?: string },\n ) => void,\n ): void;\n}\n\n/** Constructor arguments for {@link ChannelManager}. */\nexport interface ChannelManagerArgs {\n /** The Intelligence runtime client the activation config is derived from. */\n intelligence: CopilotKitIntelligence;\n /** The declared framework Channels to activate. */\n channels: Channel[];\n /** Standard runtime AgentRunner used by managed Channel executions. */\n runner?: AgentRunner;\n /** Standard thread-lock TTL forwarded to Channel AgentRunner heartbeats. */\n lockTtlSeconds?: number;\n /** Standard thread-lock heartbeat cadence used by Channel AgentRunner calls. */\n lockHeartbeatIntervalSeconds?: number;\n /** Must match web Intelligence runs so channel + HTTP share the same lock key. */\n lockKeyPrefix?: string;\n /**\n * Activation engine. Defaults to a wrapper over the channels-intelligence\n * Realtime Gateway launcher (`startChannelsOverRealtimeGateway`), reached via\n * dynamic import so this CJS package keeps no static ESM dependency.\n */\n activateChannel?: ActivateChannelEngine;\n /** Mint a runtime instance id per Channel. Defaults to `rti_{uuid-no-dashes}`. */\n mintRuntimeInstanceId?: () => string;\n /** Diagnostic sink. Forwarded to the launcher/transport when the default\n * activation engine is used, so transport-level drops surface in the managed\n * path (not just activation-level events). */\n log?: (msg: string, meta?: unknown) => void;\n /**\n * How often (ms) to repeat a \"still down\" log while a managed session is\n * disconnected. A dropped session was previously silent for as long as the\n * outage lasted, which made a dead bot indistinguishable from an idle one\n * (OSS-670). Injectable so tests need no fake timers. Default 30000.\n */\n reconnectLogIntervalMs?: number;\n /** Per-handle deadline (ms) for `handle.stop()` during {@link ChannelManager.stop}\n * so a wedged stop can't hang SIGTERM shutdown. Default 5000. */\n stopHandleTimeoutMs?: number;\n}\n\n/** Per-Channel mutable activation entry tracked by the manager. */\ninterface ChannelEntry {\n status: ChannelStatus;\n /** Resolves on `online`/`setup_required`; rejects on `error`. Awaited by `ready`. */\n readonly settled: Promise<void>;\n handle?: ChannelsHandle;\n /**\n * Whether {@link ChannelManager.stopEntry} has already stopped `handle`. Gates\n * the single-stop guarantee: the success settle handler and `stop()` can both\n * reach the same entry in the same tick, but the handle is torn down at most\n * once.\n */\n handleStopped: boolean;\n /** Epoch ms this outage episode began; unset while the session is healthy. */\n downSince?: number;\n /** Repeating \"still down\" logger for this outage; cleared on recovery/teardown. */\n reconnectLogTimer?: ReturnType<typeof setInterval>;\n}\n\n/**\n * Runtime installs this pure-ESM package as a direct dependency, but the\n * specifier must stay non-literal so it never becomes a static dependency of\n * the runtime's CJS build. The packed-consumer contract is enforced by\n * `scripts/release/verify-runtime-package.ts`.\n */\nconst CHANNELS_INTELLIGENCE_SPECIFIER = \"@copilotkit/channels-intelligence\";\n\n/**\n * Structural view of the `@copilotkit/channels-intelligence` module surface the\n * default engine consumes. Declared locally (not imported) for the same\n * CJS/ESM-boundary reason the {@link ChannelsHandle} view is.\n */\nexport interface ChannelsIntelligenceModule {\n startChannelsOverRealtimeGateway: (\n channels: Channel[],\n opts: {\n wsUrl: string;\n apiKey: string;\n scope: { projectId: number; channelName: string };\n runtimeInstanceId: string;\n adapter?: string;\n /** Optional per-Channel override for managed tool-call visibility. */\n showToolStatus?: boolean;\n /** Optional per-Channel tuning for continuation messages on long replies. */\n replyContinuation?: ReplyContinuationOptions;\n /** Intelligence app-api HTTP base URL, forwarded to the transport so the\n * managed realtime path enables file/history parity (HTTP-only) — OSS-476. */\n appApiBaseUrl?: string;\n /** Diagnostic sink forwarded to the launcher/transport so transport-level\n * drop diagnostics (e.g. a version-skew missing-leaseToken outage) are not\n * silent in the managed path. */\n log?: (msg: string, meta?: unknown) => void;\n runCanonical(args: {\n agent: AbstractAgent;\n threadId: string;\n runId: string;\n userId: string;\n agentId: string;\n tools: readonly {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n }[];\n context: readonly { description: string; value: string }[];\n persistedInputMessages: Message[];\n execute(\n subscriber: AgentSubscriber,\n canonicalRun?: { threadId: string; runId: string },\n ): Promise<{\n iterations: number;\n interrupted: boolean;\n deliveryError?: unknown;\n }>;\n }): Promise<{\n iterations: number;\n interrupted: boolean;\n deliveryError?: unknown;\n }>;\n loadHistory(args: {\n threadId: string;\n appUserId: string;\n }): Promise<Message[]>;\n },\n ) => Promise<ChannelsHandle>;\n}\n\n/**\n * Default engine: wrap the channels-intelligence Realtime Gateway launcher.\n *\n * The module is reached through an injectable importer that defaults to a\n * dynamic `import()` of a non-literal specifier, so the pure-ESM\n * `@copilotkit/channels-intelligence` never becomes a static dependency of this\n * CJS package (mirrors the runtime's other channels seams). The `import`\n * seam is a parameter purely so this function's config→opts mapping and its\n * module-not-found / generic-error branches are unit-testable WITHOUT the real\n * package installed; production always uses the default importer.\n *\n * Passes NO `org`/`channelId` — the launcher's realtime scope treats them as\n * optional.\n *\n * @param config - Resolved activation config for the Channel.\n * @param channel - The Channel to activate.\n * @param importChannelsIntelligence - Test seam; loads the channels-intelligence\n * module. Defaults to a dynamic import of the real package.\n * @param log - Optional diagnostic sink forwarded to the launcher/transport so\n * transport-level drop diagnostics are not silent in the managed path.\n * @returns The launcher's {@link ChannelsHandle}.\n */\nexport async function defaultActivateChannel(\n config: ChannelActivationConfig,\n channel: Channel,\n importChannelsIntelligence: () => Promise<ChannelsIntelligenceModule> = () =>\n import(\n CHANNELS_INTELLIGENCE_SPECIFIER\n ) as Promise<ChannelsIntelligenceModule>,\n log?: (msg: string, meta?: unknown) => void,\n services?: {\n runner: AgentRunner;\n intelligence: CopilotKitIntelligence;\n lockTtlSeconds?: number;\n lockHeartbeatIntervalSeconds?: number;\n lockKeyPrefix?: string;\n },\n): Promise<ChannelsHandle> {\n let mod: ChannelsIntelligenceModule;\n try {\n mod = await importChannelsIntelligence();\n } catch (err) {\n if (isModuleNotFound(err)) {\n throw new Error(\n \"Managed Channels require '@copilotkit/channels-intelligence' to be installed. Add it to your app's dependencies.\",\n { cause: err },\n );\n }\n throw err;\n }\n if (!services) {\n throw new Error(\n \"Managed Channels require the runtime AgentRunner and Intelligence client\",\n );\n }\n return mod.startChannelsOverRealtimeGateway([channel], {\n wsUrl: config.wsUrl,\n apiKey: config.apiKey,\n scope: { projectId: config.projectId, channelName: config.channelName },\n runtimeInstanceId: config.runtimeInstanceId,\n adapter: config.adapter,\n ...(config.showToolStatus !== undefined\n ? { showToolStatus: config.showToolStatus }\n : {}),\n ...(config.replyContinuation !== undefined\n ? { replyContinuation: config.replyContinuation }\n : {}),\n // Forward the app-api HTTP base URL so the transport wires file/history\n // (HTTP-only) on the NORMAL managed path — without this, Channels started by\n // the CopilotRuntime handler run with no history/file support (OSS-476).\n appApiBaseUrl: config.apiUrl,\n // Forward the manager's diagnostic sink down to the launcher/transport so a\n // transport-level drop (e.g. a version-skew missing-leaseToken outage) is\n // observable in the managed path, not just activation-level events.\n ...(log ? { log } : {}),\n runCanonical: (args) =>\n runCanonicalChannelAgent(\n services.runner,\n services.intelligence,\n services.lockTtlSeconds ?? 20,\n services.lockHeartbeatIntervalSeconds ?? 15,\n args,\n services.lockKeyPrefix,\n ),\n loadHistory: async ({ threadId, appUserId }) => {\n const history = await services.intelligence.getThreadMessages({\n threadId,\n userId: appUserId,\n });\n return Promise.all(\n history.messages.map((message) =>\n toAgentMessage(message, services.intelligence),\n ),\n );\n },\n });\n}\n\ninterface CanonicalRunArgs {\n agent: AbstractAgent;\n threadId: string;\n runId: string;\n userId: string;\n agentId: string;\n tools: readonly {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n }[];\n context: readonly { description: string; value: string }[];\n persistedInputMessages: Message[];\n execute(\n subscriber: AgentSubscriber,\n canonicalRun?: { threadId: string; runId: string },\n ): Promise<{\n iterations: number;\n interrupted: boolean;\n deliveryError?: unknown;\n }>;\n}\n\n/** One outer agent that lets the standard runner own the whole local tool loop. */\nclass ChannelOuterAgent extends AbstractAgent {\n constructor(\n private readonly inner: AbstractAgent,\n private readonly canonicalThreadId: string,\n private readonly executeLoop: CanonicalRunArgs[\"execute\"],\n ) {\n super({\n threadId: inner.threadId,\n initialMessages: inner.messages,\n initialState: inner.state,\n ...(inner.agentId ? { agentId: inner.agentId } : {}),\n });\n }\n\n run(): ReturnType<AbstractAgent[\"run\"]> {\n return EMPTY;\n }\n\n override async runAgent(\n parameters?: RunAgentParameters,\n subscriber?: AgentSubscriber,\n ): Promise<RunAgentResult> {\n if (!parameters?.runId) {\n throw new Error(\"Canonical Channel run requires a runId\");\n }\n const result = await this.executeLoop(subscriber ?? {}, {\n threadId: this.canonicalThreadId,\n runId: parameters.runId,\n });\n return { result, newMessages: [] };\n }\n\n override abortRun(): void {\n this.inner.abortRun();\n }\n}\n\n/** Drive one public Channel run through the runtime's existing AgentRunner. */\nasync function runCanonicalChannelAgent(\n runner: AgentRunner,\n intelligence: CopilotKitIntelligence,\n lockTtlSeconds: number,\n lockHeartbeatIntervalSeconds: number,\n args: CanonicalRunArgs,\n lockKeyPrefix?: string,\n): Promise<{\n iterations: number;\n interrupted: boolean;\n deliveryError?: unknown;\n}> {\n const lock = await intelligence.ɵacquireThreadLock({\n threadId: args.threadId,\n runId: args.runId,\n userId: args.userId,\n agentId: args.agentId,\n ttlSeconds: lockTtlSeconds,\n ...(lockKeyPrefix !== undefined ? { lockKeyPrefix } : {}),\n });\n const canonicalThreadId = lock.threadId;\n const canonicalRunId = lock.runId;\n let result = { iterations: 0, interrupted: false };\n const outer = new ChannelOuterAgent(\n args.agent,\n canonicalThreadId,\n async (subscriber, canonicalRun) => {\n result = await args.execute(subscriber, canonicalRun);\n return result;\n },\n );\n let stopPromise: Promise<boolean | undefined> | undefined;\n let heartbeatError: unknown;\n let heartbeatTimer: ReturnType<typeof setInterval> | undefined;\n const stopCanonicalRun = (): void => {\n stopPromise ??= Promise.resolve()\n .then(() => runner.stop({ threadId: canonicalThreadId }))\n .catch(() => false);\n };\n heartbeatTimer = setInterval(() => {\n intelligence\n .ɵrenewThreadLock({\n threadId: canonicalThreadId,\n runId: canonicalRunId,\n ttlSeconds: lockTtlSeconds,\n ...(lockKeyPrefix !== undefined ? { lockKeyPrefix } : {}),\n })\n .catch((error: unknown) => {\n if (heartbeatTimer === undefined) return;\n clearInterval(heartbeatTimer);\n heartbeatTimer = undefined;\n heartbeatError = error;\n try {\n args.agent.abortRun();\n } catch {\n // The runner stop below remains the authoritative cancellation path.\n }\n stopCanonicalRun();\n });\n }, lockHeartbeatIntervalSeconds * 1_000);\n heartbeatTimer.unref?.();\n\n try {\n await new Promise<void>((resolve, reject) => {\n let terminalError: (Error & { code?: string }) | undefined;\n const stream = runner.run({\n threadId: canonicalThreadId,\n agent: outer,\n input: {\n threadId: canonicalThreadId,\n runId: canonicalRunId,\n messages: args.agent.messages,\n state: args.agent.state,\n tools: [...args.tools],\n context: [...args.context],\n forwardedProps: undefined,\n },\n persistedInputMessages: args.persistedInputMessages,\n });\n stream.subscribe({\n next: (event: BaseEvent) => {\n if (event.type !== EventType.RUN_ERROR || terminalError) return;\n const message =\n \"message\" in event && typeof event.message === \"string\"\n ? event.message\n : \"Canonical Channel agent run failed\";\n terminalError = new Error(message);\n terminalError.name = \"ChannelCanonicalRunError\";\n if (\n \"code\" in event &&\n typeof event.code === \"string\" &&\n event.code.length > 0\n ) {\n terminalError.code = event.code;\n }\n },\n error: reject,\n complete: () => {\n if (terminalError) {\n reject(terminalError);\n } else {\n resolve();\n }\n },\n });\n });\n } finally {\n if (heartbeatTimer !== undefined) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = undefined;\n }\n // Always release the product thread lock from the Runtime side. Gateway\n // may also release on terminal AG-UI ingestion; cleanup is idempotent and\n // covers runner paths that never stream terminal events (or lose them).\n await intelligence\n .ɵcleanupThreadLock({\n threadId: canonicalThreadId,\n runId: canonicalRunId,\n })\n .catch(() => undefined);\n }\n\n if (heartbeatError !== undefined) {\n await stopPromise;\n throw heartbeatError;\n }\n return result;\n}\n\n/** Convert canonical Intelligence history into AG-UI messages. */\nasync function toAgentMessage(\n message: {\n id: string;\n role: string;\n activityType?: string;\n content?: unknown;\n toolCalls?: Array<{ id: string; name: string; args: string }>;\n toolCallId?: string;\n },\n intelligence: CopilotKitIntelligence,\n): Promise<Message> {\n const content = await hydrateManagedContent(message.content, intelligence);\n return {\n id: message.id,\n role: message.role as Message[\"role\"],\n content: content ?? \"\",\n ...(message.activityType ? { activityType: message.activityType } : {}),\n ...(message.toolCalls\n ? {\n toolCalls: message.toolCalls.map((call) => ({\n id: call.id,\n type: \"function\" as const,\n function: { name: call.name, arguments: call.args },\n })),\n }\n : {}),\n ...(message.toolCallId ? { toolCallId: message.toolCallId } : {}),\n } as Message;\n}\n\n/** Resolves managed asset references only at the authorized Runtime boundary. */\nasync function hydrateManagedContent(\n content: unknown,\n intelligence: CopilotKitIntelligence,\n): Promise<unknown> {\n if (Array.isArray(content)) {\n return Promise.all(\n content.map(async (part) => {\n if (\n typeof part !== \"object\" ||\n part === null ||\n !(\"source\" in part) ||\n typeof part.source !== \"object\" ||\n part.source === null ||\n !(\"value\" in part.source) ||\n typeof part.source.value !== \"string\" ||\n !part.source.value.startsWith(\"cpki-asset://\")\n ) {\n return part;\n }\n const assetId = part.source.value.slice(\"cpki-asset://\".length);\n const asset = await intelligence.ɵgetManagedChannelAsset(assetId);\n return {\n ...part,\n source: {\n type: \"data\",\n value: Buffer.from(asset.bytes).toString(\"base64\"),\n mimeType:\n asset.mimeType ??\n (\"mimeType\" in part.source &&\n typeof part.source.mimeType === \"string\"\n ? part.source.mimeType\n : \"application/octet-stream\"),\n },\n };\n }),\n );\n }\n\n if (\n typeof content === \"object\" &&\n content !== null &&\n \"assetId\" in content &&\n typeof content.assetId === \"string\"\n ) {\n const asset = await intelligence.ɵgetManagedChannelAsset(content.assetId);\n return {\n ...content,\n source: {\n type: \"data\",\n value: Buffer.from(asset.bytes).toString(\"base64\"),\n mimeType:\n asset.mimeType ??\n (\"mimeType\" in content && typeof content.mimeType === \"string\"\n ? content.mimeType\n : \"application/octet-stream\"),\n },\n };\n }\n\n return content;\n}\n\n/** Whether `err` signals a missing managed provider rather than a hard failure. */\nfunction isSetupRequired(err: unknown): boolean {\n return (\n err instanceof ChannelSetupRequiredError ||\n (typeof err === \"object\" &&\n err !== null &&\n (err as { code?: unknown }).code === \"SETUP_REQUIRED\")\n );\n}\n\n/**\n * Whether `err` is a Node/runtime module-resolution failure — i.e. the error\n * a dynamic `import()` throws when the target package is not installed.\n * Exported so the friendly-error path in {@link defaultActivateChannel} can be\n * unit-tested without forcing a real failing import.\n */\nexport function isModuleNotFound(err: unknown): boolean {\n if (typeof err !== \"object\" || err === null) {\n return false;\n }\n const code = (err as { code?: unknown }).code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\n/** Default deadline (ms) for a single `handle.stop()` during teardown. */\nconst DEFAULT_STOP_HANDLE_TIMEOUT_MS = 5_000;\n\n/** Default cadence (ms) for the \"still down\" log while a session is dropped. */\nconst DEFAULT_RECONNECT_LOG_INTERVAL_MS = 30_000;\n\n/**\n * Reject with `timeoutMessage` after `timeoutMs` if `inner` has not settled,\n * otherwise pass `inner` through. When `timeoutMs` is undefined, `inner` is\n * returned unchanged. The timer is `unref`'d so a pending deadline never keeps\n * the process alive, and `inner` always has a settle handler attached, so a\n * timed-out promise that later settles never surfaces as unhandled.\n */\nfunction withTimeout<T>(\n inner: Promise<T>,\n timeoutMs: number | undefined,\n timeoutMessage: string,\n): Promise<T> {\n if (timeoutMs === undefined) {\n return inner;\n }\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(\n () => reject(new Error(timeoutMessage)),\n timeoutMs,\n );\n (timer as unknown as { unref?: () => void }).unref?.();\n inner.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n },\n );\n });\n}\n\n/**\n * Drives Channel activation for an Intelligence runtime: lazily activates each\n * declared Channel, tracks per-Channel lifecycle status, exposes readiness, and\n * tears everything down. A MANAGED Channel (empty `adapters`) is activated\n * through the injected engine over the Intelligence gateway; a DIRECT Channel\n * (developer-supplied adapter) is started through its own transport seam\n * (`channel.ɵruntime.start()`) — driven by the manager, but running only its own\n * adapter transport, not the gateway path (direct Channels stay below the\n * canonical/reliability layer by design — see {@link ChannelStatus} and OSS-599).\n * Its very existence means Intelligence is configured, so there is no\n * standalone/self-started path.\n *\n * Activation is lazy and idempotent — constructing the manager does nothing;\n * {@link activate} starts it and a second call is a no-op. Activation throws\n * SYNCHRONOUSLY (a {@link ChannelConfigError}) only for a misconfiguration it\n * can detect up front — a duplicate or missing Channel name. Every OTHER\n * activation failure is recorded as the Channel's status (`error`, or\n * `setup_required` for a missing provider) and surfaced through {@link status}\n * and {@link ready} rather than thrown.\n *\n * Reconnection is NOT handled here — it is delegated to the Phoenix connection\n * layer that backs the launcher. When a managed control socket drops, Phoenix's\n * `Socket` reconnects and rejoins with the same Runtime declaration. Active\n * deliveries request fresh one-use join tokens through that control link. A\n * re-activation here would be both redundant AND broken: re-invoking the engine\n * on an already-started `Channel` throws in `channel.addAdapter` (started=true).\n * The manager therefore never re-activates on a drop.\n *\n * It DOES, however, reflect real connection health through the session's\n * `onStateChange` observer so {@link ChannelManager.status} stays honest rather\n * than reporting `online` forever after a drop: a drop moves the Channel to\n * `reconnecting`, a successful rejoin restores `online`, and a bounded give-up\n * (Phoenix would otherwise retry forever) moves it to `error`.\n */\nexport class ChannelManager implements ChannelsControl {\n private readonly intelligence: CopilotKitIntelligence;\n private readonly runner?: AgentRunner;\n private readonly lockTtlSeconds: number;\n private readonly lockHeartbeatIntervalSeconds: number;\n private readonly lockKeyPrefix?: string;\n private readonly channels: Channel[];\n private readonly activateChannel: ActivateChannelEngine;\n private readonly mintRuntimeInstanceId: () => string;\n private readonly log?: (msg: string, meta?: unknown) => void;\n private readonly stopHandleTimeoutMs: number;\n private readonly reconnectLogIntervalMs: number;\n\n private readonly entries = new Map<string, ChannelEntry>();\n private activated = false;\n private stopped = false;\n\n /** @param args - See {@link ChannelManagerArgs}. */\n constructor(args: ChannelManagerArgs) {\n this.intelligence = args.intelligence;\n this.runner = args.runner;\n this.lockTtlSeconds = args.lockTtlSeconds ?? 20;\n this.lockHeartbeatIntervalSeconds = args.lockHeartbeatIntervalSeconds ?? 15;\n this.lockKeyPrefix = args.lockKeyPrefix;\n this.channels = args.channels;\n this.log = args.log;\n // When using the default engine, forward the manager's log DOWN to the\n // launcher/transport (via defaultActivateChannel's log param) so a\n // transport-level drop is observable in the managed path. `this.log` is read\n // lazily at activation time, so this closure always sees the assigned sink.\n this.activateChannel =\n args.activateChannel ??\n ((config, channel) =>\n defaultActivateChannel(\n config,\n channel,\n undefined,\n this.log,\n this.runner\n ? {\n runner: this.runner,\n intelligence: this.intelligence,\n lockTtlSeconds: this.lockTtlSeconds,\n lockHeartbeatIntervalSeconds: this.lockHeartbeatIntervalSeconds,\n ...(this.lockKeyPrefix !== undefined\n ? { lockKeyPrefix: this.lockKeyPrefix }\n : {}),\n }\n : undefined,\n ));\n this.mintRuntimeInstanceId =\n args.mintRuntimeInstanceId ??\n (() => `rti_${randomUUID().replace(/-/g, \"\")}`);\n this.stopHandleTimeoutMs =\n args.stopHandleTimeoutMs ?? DEFAULT_STOP_HANDLE_TIMEOUT_MS;\n this.reconnectLogIntervalMs =\n args.reconnectLogIntervalMs ?? DEFAULT_RECONNECT_LOG_INTERVAL_MS;\n }\n\n /**\n * Start activation of every declared Channel (lazy + idempotent). Mints a\n * distinct runtime instance id per Channel, derives its activation config,\n * and calls the engine. Records each Channel as `connecting`, transitioning\n * to `online`/`setup_required`/`error` as its activation settles.\n */\n activate(): void {\n // Short-circuit on BOTH latches: `activated` makes activation idempotent,\n // and `stopped` prevents a post-`stop()` activate() from opening transports\n // on a dead manager. (A late activation self-heals via the post-settle guard,\n // but never starting it is cheaper and clearer.)\n if (this.activated || this.stopped) {\n return;\n }\n // Reject duplicate Channel names BEFORE kicking off any engine call. The\n // manager keys `entries` by name, so a duplicate would let the second\n // activation's entry silently overwrite the first — leaking the first\n // Channel's control link out of status()/ready()/stop(). Fail loud here so\n // nothing is ever activated in that state.\n this.assertUniqueChannelNames();\n this.activated = true;\n\n // Partition declared Channels by transport. A Channel carrying ANY adapter\n // that is NOT the Intelligence managed adapter (a developer-supplied\n // slack/discord/... adapter, which lacks `__intelligenceChannel`) is a\n // DIRECT channel. The manager still owns its lifecycle — but a direct Channel\n // runs its OWN adapter transport rather than the Intelligence gateway path:\n // it is started here via `channel.ɵruntime.start()` (not the managed engine)\n // and torn down via `channel.ɵruntime.stop()`. The distinction is EXCLUSIVE\n // PER CHANNEL, not per platform — a Channel served by a direct adapter is not\n // also managed: ANY direct adapter makes the WHOLE Channel direct, regardless\n // of platform. Attaching the managed adapter alongside a direct one would\n // double-deliver every turn (and trip the SDK's `assertExclusive` guard,\n // moving the Channel to `error`). Per the SoT rule, never infer managed intent\n // from a local direct adapter — a managed-eligible Channel has an empty\n // `adapters` at declaration time. Managed+direct coexistence on the same\n // Channel is NOT supported today; it is deferred (OSS-484). Direct Channels\n // run only their own transport here and stay below the Intelligence\n // canonical/reliability layer by design, not pending work (OSS-599).\n for (const channel of this.channels) {\n const isDirect = channel.adapters.some((a) => !a.__intelligenceChannel);\n if (isDirect) {\n this.startDirectChannel(channel);\n continue;\n }\n const name = channel.name!;\n const runtimeInstanceId = this.mintRuntimeInstanceId();\n\n let resolveSettled!: () => void;\n let rejectSettled!: (err: unknown) => void;\n const settled = new Promise<void>((resolve, reject) => {\n resolveSettled = resolve;\n rejectSettled = reject;\n });\n // ready() awaits `settled`; if nothing ever handles a rejection there,\n // Node reports an unhandled rejection. Attach a no-op catch so the\n // promise is always considered handled — ready() still sees the reason.\n settled.catch(() => {});\n\n // Invoke the engine synchronously so activation is observably started the\n // moment activate() returns (callers assert the engine was called and see\n // `connecting` before awaiting ready). A synchronous config/engine throw is\n // turned into a rejected activation so it becomes this channel's status\n // rather than throwing out of activate().\n let activation: Promise<ChannelsHandle>;\n let config: ChannelActivationConfig | undefined;\n try {\n config = deriveChannelActivationConfig({\n intelligence: this.intelligence,\n channel,\n runtimeInstanceId,\n });\n activation = this.activateChannel(config, channel);\n } catch (err) {\n activation = Promise.reject(err);\n }\n\n // The deferred `.then` callbacks capture `entry` and run only after the\n // literal has fully initialized, so referencing it here is safe.\n const entry: ChannelEntry = {\n status: \"connecting\",\n handle: undefined,\n handleStopped: false,\n settled,\n };\n\n // Anchor the settle handlers. Both branches route every teardown through\n // the idempotent `stopEntry`, so a late settle can never resurrect a\n // `stopped` entry and a handle is torn down at most once. The handlers\n // only mutate state (never throw), so the trailing no-op catch just keeps\n // the chain from surfacing as an unhandled rejection.\n activation\n .then(\n async (handle) => {\n entry.handle = handle;\n if (this.stopped) {\n // stop() ran before this activation settled, so it could not tear\n // down a handle that did not exist yet. Release it now (idempotent)\n // and keep the Channel `stopped`.\n await this.stopEntry(entry);\n resolveSettled();\n return;\n }\n entry.status = \"online\";\n this.registerConnectionObserver(name, entry);\n resolveSettled();\n },\n async (err: unknown) => {\n if (this.stopped) {\n // A rejection that arrives AFTER stop() must NOT resurrect the\n // entry into `error`/`setup_required`: the Channel is already\n // being torn down. Keep it `stopped` and resolve `settled` so a\n // subsequent ready() does not reject on a stopped Channel.\n await this.stopEntry(entry);\n resolveSettled();\n return;\n }\n if (isSetupRequired(err)) {\n entry.status = \"setup_required\";\n this.log?.(`channel \"${name}\" requires setup`, err);\n resolveSettled();\n } else {\n entry.status = \"error\";\n this.log?.(`channel \"${name}\" failed to activate`, err);\n rejectSettled(err);\n }\n },\n )\n .catch(() => {});\n\n this.entries.set(name, entry);\n }\n }\n\n /**\n * Start a DIRECT-adapter Channel through its own transport seam\n * ({@link Channel.ɵruntime}`.start()`), recording a live entry so\n * {@link ready}/{@link status}/{@link stop} all cover it. A direct Channel is\n * driven by the manager — but only because the Intelligence runtime constructed\n * this manager at all — and runs its OWN adapter transport, NOT the Intelligence\n * gateway path. It is deliberately NOT wired into the gateway/canonical/\n * reliability layer — that boundary is permanent, not a deferral (OSS-599).\n *\n * Mirrors the managed path's settle machinery so teardown resilience is shared:\n * the entry's `handle` is a synthetic {@link ChannelsHandle} whose `stop()`\n * calls `channel.ɵruntime.stop()`, so the SAME idempotent, bounded, resilient\n * {@link stopEntry} that tears down a managed handle tears down a direct Channel\n * too. The handle is assigned only AFTER `start()` resolves (exactly as the\n * managed path assigns its handle only on resolve), so a `stop()` during a\n * still-starting direct Channel returns promptly with nothing to stop and the\n * post-settle guard tears down the late transport. A direct Channel exposes no\n * managed-session drop signal, so no connection observer is wired and it never\n * reaches `reconnecting`.\n *\n * @param channel - The direct-adapter Channel to start.\n */\n private startDirectChannel(channel: Channel): void {\n const name = channel.name!;\n this.log?.(\n `channel \"${name}\" carries a direct adapter — starting its own transport via channel.ɵruntime.start() (the Intelligence runtime drives its lifecycle; it runs its own adapter transport, NOT the managed gateway path — direct Channels stay below the canonical/reliability layer by design (OSS-599); managed+direct coexistence deferred (OSS-484))`,\n );\n\n let resolveSettled!: () => void;\n let rejectSettled!: (err: unknown) => void;\n const settled = new Promise<void>((resolve, reject) => {\n resolveSettled = resolve;\n rejectSettled = reject;\n });\n // ready() awaits `settled`; attach a no-op catch so a rejection is always\n // considered handled (ready() still sees the reason).\n settled.catch(() => {});\n\n // Synthetic handle wrapping the Channel's own stop seam. Assigned to the\n // entry only on successful start (below) so the shared teardown machinery\n // (handleStopped guard, withTimeout bounding, resilient allSettled) stops a\n // direct Channel exactly as it stops a managed handle.\n const directHandle: ChannelsHandle = {\n metadata: {},\n stop: () => channel.ɵruntime.stop(),\n };\n\n // Start the direct transport synchronously so it is observably started the\n // moment activate() returns (callers see `connecting` before awaiting ready).\n // A synchronous throw becomes this Channel's status rather than throwing out\n // of activate().\n let activation: Promise<void>;\n try {\n activation = channel.ɵruntime.start();\n } catch (err) {\n activation = Promise.reject(err);\n }\n\n const entry: ChannelEntry = {\n status: \"connecting\",\n handle: undefined,\n handleStopped: false,\n settled,\n };\n\n activation\n .then(\n async () => {\n entry.handle = directHandle;\n if (this.stopped) {\n // stop() ran before start() settled, so it could not tear down a\n // transport that was not up yet. Release it now (idempotent) and keep\n // the Channel `stopped`.\n await this.stopEntry(entry);\n resolveSettled();\n return;\n }\n entry.status = \"online\";\n resolveSettled();\n },\n async (err: unknown) => {\n if (this.stopped) {\n // A start rejection that arrives AFTER stop() must NOT resurrect the\n // entry into `error`: the Channel is already being torn down. Keep it\n // `stopped` and resolve `settled` so a later ready() does not reject.\n entry.handle = directHandle;\n await this.stopEntry(entry);\n resolveSettled();\n return;\n }\n entry.status = \"error\";\n this.log?.(\n `channel \"${name}\" failed to start its direct transport`,\n err,\n );\n rejectSettled(err);\n },\n )\n .catch(() => {});\n\n this.entries.set(name, entry);\n }\n\n /**\n * Throw if two declared Channels share a `name`. `entries` is keyed by name,\n * so a duplicate would overwrite the first Channel's entry and leak its live\n * session. Called at the very start of {@link activate}, before any engine\n * call, so a misconfiguration fails loud instead of silently.\n *\n * @throws {ChannelConfigError} If any Channel is missing a name, or if any\n * name appears more than once.\n */\n private assertUniqueChannelNames(): void {\n const seen = new Set<string>();\n for (const channel of this.channels) {\n const name = channel.name;\n // Check for a missing/empty name FIRST: `channel.name!` on a nameless\n // Channel keys as the string \"undefined\", which would otherwise report a\n // spurious duplicate for two nameless Channels before the accurate\n // missing-name error. Fail with the precise error instead.\n if (!name) {\n throw new ChannelConfigError(\n \"A managed Channel is missing a `name` — every declared Channel must \" +\n \"have a unique, non-empty name (pass createChannel({ name })).\",\n );\n }\n if (seen.has(name)) {\n throw new ChannelConfigError(\n `Duplicate managed Channel name \"${name}\" — every declared Channel ` +\n `must have a unique name.`,\n );\n }\n seen.add(name);\n }\n }\n\n /**\n * Resolve when every declared Channel — managed OR direct — has settled to\n * `online`/`setup_required`.\n *\n * A direct-adapter Channel is awaited too: its `settled` resolves once its own\n * transport is up (`channel.ɵruntime.start()` settling) and rejects if that\n * start fails, exactly as a managed Channel's `settled` tracks its activation.\n *\n * Activates lazily if not already started — so a first call rejects with the\n * same {@link ChannelConfigError} as the synchronous throw from\n * {@link activate} for an up-front misconfiguration (duplicate/missing Channel\n * names). Once activation has been kicked off, all OTHER failures are surfaced\n * here instead: this rejects with an `AggregateError` if any Channel settled\n * to `error` OR — when `timeoutMs` is given — did not settle in time. The\n * `timeoutMs` deadline is applied PER CHANNEL, so the aggregate carries each\n * failed Channel's real reason AND a named timeout for each Channel still\n * hanging: a genuine activation error is never masked by a sibling that hangs\n * (a pre-fix set-wide timeout discarded the real reason in that case).\n *\n * A STOPPED manager short-circuits and resolves: a Channel that settled to\n * `error` BEFORE {@link stop} already rejected its `settled` promise, so\n * awaiting it here would throw an `AggregateError` even though\n * {@link status}.overall is `\"stopped\"` — inconsistent with the case where the\n * Channel was still online at stop() (which resolves). A stopped manager has\n * nothing left to be ready for, so resolve uniformly.\n *\n * `ready()` is ONE-SHOT: it settles on the INITIAL activation outcome. Later\n * connection-health transitions (a live Channel dropping to `reconnecting`, or\n * giving up to `error`) are reported through {@link status} — where `online`\n * means currently-sendable — but do NOT re-arm or re-reject an already-settled\n * `ready()`.\n */\n async ready(opts?: { timeoutMs?: number }): Promise<void> {\n if (this.stopped) {\n return;\n }\n this.activate();\n const entries = [...this.entries.entries()];\n // Apply `timeoutMs` PER CHANNEL rather than to the whole set. A single\n // set-wide timeout wrapping `allSettled` would, when one channel settles to\n // `error` while a sibling hangs, reject with only a generic timeout and\n // DISCARD the erroring channel's real reason. Timing out each channel's\n // `settled` independently lets `allSettled` collect BOTH a hung channel's\n // named timeout AND a failed channel's real error into one AggregateError.\n const results = await Promise.allSettled(\n entries.map(([name, e]) =>\n withTimeout(\n e.settled,\n opts?.timeoutMs,\n `channel \"${name}\" did not settle within ${opts?.timeoutMs}ms`,\n ),\n ),\n );\n const errors = results\n .filter((r): r is PromiseRejectedResult => r.status === \"rejected\")\n .map((r) => r.reason);\n if (errors.length > 0) {\n throw new AggregateError(\n errors,\n `ChannelManager.ready: ${errors.length} channel(s) failed to activate or settle in time`,\n );\n }\n }\n\n /**\n * Snapshot status. Every declared Channel — managed OR direct — appears keyed\n * by name in `channels`; a direct-adapter Channel reads `online` once its own\n * transport is up, exactly like a managed one.\n *\n * `overall` is folded over ALL declared Channels (see {@link computeOverall}),\n * by precedence `error` > `reconnecting` > `setup_required` > `connecting` >\n * `online`. `online` means every Channel can currently send. `reconnecting`\n * outranks `setup_required` because a dropped-but-retrying Channel is an active\n * outage, louder than a steadily-degraded unprovisioned one (only managed\n * Channels ever reach `reconnecting`; a direct Channel has no managed-session\n * drop signal). With no declared Channels at all, `overall` is `online` (nothing\n * is degraded); once every Channel has been stopped, `overall` is `stopped`.\n */\n status(): {\n overall: ChannelStatus;\n channels: Record<string, ChannelStatus>;\n } {\n const channels: Record<string, ChannelStatus> = {};\n for (const [name, entry] of this.entries) {\n channels[name] = entry.status;\n }\n // A stopped manager is `stopped` regardless of whether it was ever activated.\n // stop() before activate() (e.g. SIGTERM during startup) leaves `entries`\n // empty, and the empty-set fold below returns `online` — a torn-down manager\n // must never read healthy. Short-circuit before that fold. (After a normal\n // activate→stop, every entry is already `stopped` and the fold agrees, so\n // this is also consistent with the populated case.)\n if (this.stopped) {\n return { overall: \"stopped\", channels };\n }\n // Before activate() has run, `entries` is empty. Folding an empty set gives\n // `online` — correct for a manager that declares NO channels (nothing is\n // degraded), but a LIE for one that declares channels and simply has not\n // opened its socket yet: activation is lazy (deferred to the first\n // `ready()`), so a not-yet-activated manager must never read `online`.\n // Report `connecting` (\"not started\") for that case so `status()` is honest\n // before any `ready()`.\n if (!this.activated && this.channels.length > 0) {\n return { overall: \"connecting\", channels };\n }\n return { overall: this.computeOverall(Object.values(channels)), channels };\n }\n\n /**\n * Fold per-Channel statuses into a single overall status (see {@link status}).\n *\n * Every declared Channel — managed OR direct — participates: a started direct\n * Channel reads `online` and counts toward health exactly like a managed one,\n * and its `error` is a real outage that must dominate. Statuses are ranked\n * `error` > `reconnecting` > `setup_required` > `connecting` > `online`, so a\n * genuine failure still dominates a healthy sibling. (Only managed Channels ever\n * reach `reconnecting`; a direct Channel has no managed-session drop signal.)\n * The empty-input case (no declared Channels at all) stays `online` (nothing is\n * degraded).\n */\n private computeOverall(values: ChannelStatus[]): ChannelStatus {\n if (values.length === 0) {\n return \"online\";\n }\n if (values.every((v) => v === \"stopped\")) {\n return \"stopped\";\n }\n if (values.includes(\"error\")) {\n return \"error\";\n }\n if (values.includes(\"reconnecting\")) {\n return \"reconnecting\";\n }\n if (values.includes(\"setup_required\")) {\n return \"setup_required\";\n }\n if (values.includes(\"connecting\")) {\n return \"connecting\";\n }\n return \"online\";\n }\n\n /**\n * Wire the Channel's connection-health observer (if the handle exposes the\n * optional `onStateChange` seam) so {@link ChannelManager.status} reflects real\n * health instead of reporting `online` forever after a drop:\n *\n * - `reconnecting` → status `reconnecting` (dropped, Phoenix retrying);\n * - `online` → status `online` (rejoined, sendable again);\n * - `gave_up` → status `error` (dead after the bounded reconnect window).\n *\n * Makes NO re-activation — reconnection is delegated to the Phoenix connection\n * layer (see {@link ChannelManager}), which auto-rejoins under the persistent\n * adapter. A STOPPED manager (or an already-stopped entry) ignores late\n * connection events, so a drop that fires after {@link ChannelManager.stop}\n * never resurrects the Channel out of `stopped`.\n *\n * @param name - The Channel name (map key).\n * @param entry - The Channel's activation entry.\n */\n private registerConnectionObserver(name: string, entry: ChannelEntry): void {\n entry.handle?.onStateChange?.((state, detail) => {\n // A stopped manager (or a stopped entry) ignores late connection events.\n if (this.stopped || entry.status === \"stopped\") {\n return;\n }\n const cause = detail?.reason ?? detail?.code;\n const because = cause !== undefined ? ` — ${cause}` : \"\";\n if (state === \"reconnecting\") {\n entry.status = \"reconnecting\";\n entry.downSince ??= Date.now();\n this.log?.(\n `channel \"${name}\" managed session dropped; reconnecting (Phoenix auto-rejoin)${because}`,\n );\n this.startReconnectLog(name, entry);\n } else if (state === \"online\") {\n entry.status = \"online\";\n this.clearReconnectLog(entry);\n entry.downSince = undefined;\n this.log?.(`channel \"${name}\" managed session back online`);\n } else if (state === \"gave_up\") {\n // `error` here means \"not sendable\", NOT \"dead\": Phoenix keeps retrying\n // underneath and a successful rejoin restores `online`. Say so, or the\n // line reads as terminal (OSS-670). The repeat keeps running.\n entry.status = \"error\";\n this.log?.(\n `channel \"${name}\" managed session gave up reconnecting after ${this.downFor(entry)}; ` +\n `marking error (still retrying — a successful rejoin restores online)${because}`,\n );\n }\n });\n }\n\n /** Rendered downtime for this outage episode (`\"45s\"`), or `\"unknown\"`. */\n private downFor(entry: ChannelEntry): string {\n return entry.downSince === undefined\n ? \"unknown\"\n : `${Math.round((Date.now() - entry.downSince) / 1000)}s`;\n }\n\n /**\n * Repeat a \"still down\" line for as long as this outage lasts. Runs THROUGH\n * `gave_up` on purpose: that transition is where the old behavior went quiet,\n * and an operator watching a silent process cannot tell a dead bot from an\n * idle one.\n */\n private startReconnectLog(name: string, entry: ChannelEntry): void {\n if (entry.reconnectLogTimer !== undefined) return;\n const timer = setInterval(() => {\n if (this.stopped || entry.status === \"stopped\") {\n this.clearReconnectLog(entry);\n return;\n }\n this.log?.(\n `channel \"${name}\" managed session still down after ${this.downFor(entry)}; Phoenix is retrying`,\n );\n }, this.reconnectLogIntervalMs);\n (timer as unknown as { unref?: () => void }).unref?.();\n entry.reconnectLogTimer = timer;\n }\n\n /** Stop this entry's \"still down\" repeat, if one is running. */\n private clearReconnectLog(entry: ChannelEntry): void {\n if (entry.reconnectLogTimer !== undefined) {\n clearInterval(entry.reconnectLogTimer);\n entry.reconnectLogTimer = undefined;\n }\n }\n\n /**\n * Drive a single entry to its terminal `stopped` state, tearing down its\n * handle AT MOST ONCE. Idempotent: it always sets `status = \"stopped\"`, and\n * only calls `handle.stop()` on the first invocation that sees a live,\n * not-yet-stopped handle (gated by {@link ChannelEntry.handleStopped}).\n *\n * This is the ONE guarded teardown path shared by both `stop()` and the\n * post-settle guard in {@link activate}/{@link startDirectChannel}. Because the\n * guard is per-entry and idempotent, a handle assigned in the same tick as\n * `stop()` is stopped exactly once even when both callers reach the entry, and a\n * late settle can never resurrect a `stopped` entry. It is transport-agnostic: a\n * managed entry's `handle.stop()` releases the gateway session, and a direct\n * entry's synthetic handle (assigned in {@link startDirectChannel}) routes the\n * same `handle.stop()` to `channel.ɵruntime.stop()` — so direct and managed\n * Channels share one bounded, resilient teardown.\n *\n * `handle.stop()` failures are logged (via {@link ChannelManager.log}) but NOT\n * rethrown: the real launcher's `stop()` rethrows after `session.disconnect()`,\n * and teardown must still complete for every other entry. The call is wrapped\n * in `Promise.resolve().then(...)` so a foreign/injected handle whose `stop()`\n * throws SYNCHRONOUSLY (before any promise is created) is caught by the same\n * `.catch` — otherwise the sync throw would escape, skip `resolveSettled()` in\n * the fulfilled-then-stopped branch of {@link activate}, and hang `settled`.\n *\n * An entry with no handle yet (a still-`connecting` Channel whose transport has\n * not come up) is only marked `stopped`: there is nothing to tear down, and the\n * post-settle guard releases the transport if it arrives after `stop()`.\n *\n * A WEDGED `handle.stop()` (one that never settles) is bounded by\n * {@link ChannelManagerArgs.stopHandleTimeoutMs}: after the deadline the call\n * is logged and abandoned so it can't hang `stop()` — and thus SIGTERM\n * shutdown — forever.\n *\n * @param entry - The Channel entry to stop.\n */\n private async stopEntry(entry: ChannelEntry): Promise<void> {\n entry.status = \"stopped\";\n // An unref'd interval would not hold the process open, but a stopped\n // manager must not keep logging about a session it no longer owns.\n this.clearReconnectLog(entry);\n if (entry.handle && !entry.handleStopped) {\n entry.handleStopped = true;\n const handle = entry.handle;\n // Bound handle.stop(): a wedged stop() (e.g. a socket.disconnect that\n // never returns) must not hang teardown — and thus SIGTERM shutdown —\n // forever. On timeout, log and abandon it (the call keeps running with a\n // settle handler attached inside withTimeout, so it never surfaces as an\n // unhandled rejection) so every OTHER entry still reaches `stopped`. The\n // `Promise.resolve().then(...)` wrap also routes a SYNCHRONOUS throw from\n // a foreign handle through the same timeout+catch.\n await withTimeout(\n Promise.resolve().then(() => handle.stop()),\n this.stopHandleTimeoutMs,\n `channel handle stop() timed out after ${this.stopHandleTimeoutMs}ms during teardown`,\n ).catch((err: unknown) =>\n this.log?.(\"channel handle stop() failed during teardown\", err),\n );\n }\n }\n\n /**\n * Stop every activated Channel exactly once and mark all statuses `stopped`.\n * Idempotent — a second call is a no-op.\n *\n * Resolves promptly: {@link stopEntry} stops only the handles that already\n * exist and never blocks on activations that have not settled. A hung connect\n * (which `ready({ timeoutMs })` tolerates) has no handle to stop yet, and\n * awaiting it here would hang teardown — and thus SIGTERM shutdown — forever.\n * Any handle that arrives after this point is torn down by the post-settle\n * guard in {@link activate}, which routes through the same idempotent\n * {@link stopEntry}, so nothing leaks and nothing double-stops.\n *\n * Teardown is resilient to a throwing `handle.stop()`: `Promise.allSettled`\n * over the per-entry `stopEntry` calls guarantees one rejection can't abort\n * the rest, so every entry reaches `stopped` and `stop()` always resolves.\n * It is equally resilient to a WEDGED `handle.stop()` that never settles: each\n * is bounded by {@link ChannelManagerArgs.stopHandleTimeoutMs} inside\n * {@link stopEntry}, so a single hung handle can't hang SIGTERM shutdown.\n */\n async stop(): Promise<void> {\n if (this.stopped) {\n return;\n }\n this.stopped = true;\n\n const entries = [...this.entries.values()];\n await Promise.allSettled(entries.map((entry) => this.stopEntry(entry)));\n }\n}\n"],"mappings":"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"}
|