@copilotkit/runtime 1.62.3 → 1.63.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/_virtual/_rolldown/runtime.mjs +15 -1
  2. package/dist/agent/converters/aisdk.d.cts +1 -1
  3. package/dist/agent/converters/tanstack.d.cts +1 -1
  4. package/dist/agent/index.d.cts +1 -1
  5. package/dist/channels/dist/index.d.cts +10 -0
  6. package/dist/channels/dist/index.d.cts.map +1 -0
  7. package/dist/channels/dist/index.d.mts +10 -0
  8. package/dist/channels/dist/index.d.mts.map +1 -0
  9. package/dist/graphql/inputs/action.input.d.cts +1 -1
  10. package/dist/graphql/inputs/agent-session.input.d.cts +1 -1
  11. package/dist/graphql/inputs/agent-state.input.d.cts +1 -1
  12. package/dist/graphql/inputs/extensions.input.d.cts +1 -1
  13. package/dist/graphql/inputs/forwarded-parameters.input.d.cts +1 -1
  14. package/dist/graphql/inputs/message.input.d.cts +1 -1
  15. package/dist/graphql/types/base/index.d.cts +1 -1
  16. package/dist/graphql/types/converted/index.d.cts +1 -1
  17. package/dist/graphql/types/copilot-response.type.d.cts +1 -1
  18. package/dist/graphql/types/enums.d.cts +1 -1
  19. package/dist/graphql/types/extensions-response.type.d.cts +1 -1
  20. package/dist/graphql/types/message-status.type.d.cts +1 -1
  21. package/dist/graphql/types/response-status.type.d.cts +1 -1
  22. package/dist/index.d.cts +1 -1
  23. package/dist/langgraph.d.cts +1 -1
  24. package/dist/lib/cloud/index.d.cts +1 -1
  25. package/dist/lib/index.d.cts +1 -1
  26. package/dist/lib/integrations/index.d.cts +1 -1
  27. package/dist/lib/integrations/nest/index.d.cts +1 -1
  28. package/dist/lib/integrations/nextjs/app-router.d.cts +1 -1
  29. package/dist/lib/integrations/nextjs/pages-router.d.cts +1 -1
  30. package/dist/lib/integrations/node-express/index.d.cts +1 -1
  31. package/dist/lib/integrations/node-http/index.d.cts +1 -1
  32. package/dist/lib/integrations/shared.d.cts +1 -1
  33. package/dist/lib/logger.d.cts +1 -1
  34. package/dist/lib/observability.d.cts +1 -1
  35. package/dist/lib/runtime/agent-integrations/langgraph/agent.d.cts +1 -1
  36. package/dist/lib/runtime/agent-integrations/langgraph/consts.d.cts +1 -1
  37. package/dist/lib/runtime/agent-integrations/langgraph/index.d.cts +1 -1
  38. package/dist/lib/runtime/copilot-runtime.d.cts +1 -1
  39. package/dist/lib/runtime/mcp-tools-utils.d.cts +1 -1
  40. package/dist/lib/runtime/telemetry-agent-runner.d.cts +1 -1
  41. package/dist/lib/runtime/types.d.cts +1 -1
  42. package/dist/lib/telemetry-client.mjs +1 -1
  43. package/dist/package.cjs +4 -1
  44. package/dist/{package.mjs → runtime/package.mjs} +5 -2
  45. package/dist/runtime/package.mjs.map +1 -0
  46. package/dist/service-adapters/anthropic/anthropic-adapter.d.cts +1 -1
  47. package/dist/service-adapters/bedrock/bedrock-adapter.d.cts +1 -1
  48. package/dist/service-adapters/empty/empty-adapter.d.cts +1 -1
  49. package/dist/service-adapters/events.d.cts +1 -1
  50. package/dist/service-adapters/experimental/ollama/ollama-adapter.d.cts +1 -1
  51. package/dist/service-adapters/google/google-genai-adapter.d.cts +1 -1
  52. package/dist/service-adapters/groq/groq-adapter.d.cts +1 -1
  53. package/dist/service-adapters/index.d.cts +1 -1
  54. package/dist/service-adapters/langchain/langchain-adapter.d.cts +1 -1
  55. package/dist/service-adapters/langchain/langserve.d.cts +1 -1
  56. package/dist/service-adapters/langchain/types.d.cts +1 -1
  57. package/dist/service-adapters/openai/openai-adapter.d.cts +1 -1
  58. package/dist/service-adapters/openai/openai-assistant-adapter.d.cts +1 -1
  59. package/dist/service-adapters/service-adapter.d.cts +1 -1
  60. package/dist/service-adapters/shared/error-utils.d.cts +1 -1
  61. package/dist/service-adapters/shared/sdk-client-utils.d.cts +1 -1
  62. package/dist/service-adapters/unify/unify-adapter.d.cts +1 -1
  63. package/dist/utils/failed-response-status-reasons.d.cts +1 -1
  64. package/dist/v2/express.d.cts +3 -3
  65. package/dist/v2/express.d.mts +2 -2
  66. package/dist/v2/hono.d.cts +3 -3
  67. package/dist/v2/hono.d.mts +2 -2
  68. package/dist/v2/index.d.cts +7 -6
  69. package/dist/v2/index.d.mts +6 -5
  70. package/dist/v2/node.d.cts +1 -1
  71. package/dist/v2/runtime/core/channel-activation-config.cjs +93 -0
  72. package/dist/v2/runtime/core/channel-activation-config.cjs.map +1 -0
  73. package/dist/v2/runtime/core/channel-activation-config.d.cts +34 -0
  74. package/dist/v2/runtime/core/channel-activation-config.d.cts.map +1 -0
  75. package/dist/v2/runtime/core/channel-activation-config.d.mts +34 -0
  76. package/dist/v2/runtime/core/channel-activation-config.d.mts.map +1 -0
  77. package/dist/v2/runtime/core/channel-activation-config.mjs +91 -0
  78. package/dist/v2/runtime/core/channel-activation-config.mjs.map +1 -0
  79. package/dist/v2/runtime/core/channel-manager.cjs +447 -0
  80. package/dist/v2/runtime/core/channel-manager.cjs.map +1 -0
  81. package/dist/v2/runtime/core/channel-manager.d.cts +90 -0
  82. package/dist/v2/runtime/core/channel-manager.d.cts.map +1 -0
  83. package/dist/v2/runtime/core/channel-manager.d.mts +90 -0
  84. package/dist/v2/runtime/core/channel-manager.d.mts.map +1 -0
  85. package/dist/v2/runtime/core/channel-manager.mjs +446 -0
  86. package/dist/v2/runtime/core/channel-manager.mjs.map +1 -0
  87. package/dist/v2/runtime/core/debug-event-bus.d.cts +1 -1
  88. package/dist/v2/runtime/core/fetch-cors.d.cts +1 -1
  89. package/dist/v2/runtime/core/fetch-handler.cjs +47 -1
  90. package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
  91. package/dist/v2/runtime/core/fetch-handler.d.cts +60 -4
  92. package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
  93. package/dist/v2/runtime/core/fetch-handler.d.mts +59 -3
  94. package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
  95. package/dist/v2/runtime/core/fetch-handler.mjs +47 -1
  96. package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
  97. package/dist/v2/runtime/core/hooks.d.cts +1 -1
  98. package/dist/v2/runtime/core/middleware-sse-parser.d.cts +1 -1
  99. package/dist/v2/runtime/core/middleware.d.cts +1 -1
  100. package/dist/v2/runtime/core/runtime.cjs +24 -1
  101. package/dist/v2/runtime/core/runtime.cjs.map +1 -1
  102. package/dist/v2/runtime/core/runtime.d.cts +75 -27
  103. package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
  104. package/dist/v2/runtime/core/runtime.d.mts +74 -26
  105. package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
  106. package/dist/v2/runtime/core/runtime.mjs +25 -2
  107. package/dist/v2/runtime/core/runtime.mjs.map +1 -1
  108. package/dist/v2/runtime/endpoints/express-single.d.cts +1 -1
  109. package/dist/v2/runtime/endpoints/express.cjs +10 -5
  110. package/dist/v2/runtime/endpoints/express.cjs.map +1 -1
  111. package/dist/v2/runtime/endpoints/express.d.cts +30 -4
  112. package/dist/v2/runtime/endpoints/express.d.cts.map +1 -1
  113. package/dist/v2/runtime/endpoints/express.d.mts +29 -3
  114. package/dist/v2/runtime/endpoints/express.d.mts.map +1 -1
  115. package/dist/v2/runtime/endpoints/express.mjs +10 -5
  116. package/dist/v2/runtime/endpoints/express.mjs.map +1 -1
  117. package/dist/v2/runtime/endpoints/hono-single.d.cts +1 -1
  118. package/dist/v2/runtime/endpoints/hono.cjs +7 -3
  119. package/dist/v2/runtime/endpoints/hono.cjs.map +1 -1
  120. package/dist/v2/runtime/endpoints/hono.d.cts +30 -16
  121. package/dist/v2/runtime/endpoints/hono.d.cts.map +1 -1
  122. package/dist/v2/runtime/endpoints/hono.d.mts +29 -15
  123. package/dist/v2/runtime/endpoints/hono.d.mts.map +1 -1
  124. package/dist/v2/runtime/endpoints/hono.mjs +7 -3
  125. package/dist/v2/runtime/endpoints/hono.mjs.map +1 -1
  126. package/dist/v2/runtime/endpoints/index.d.cts +3 -3
  127. package/dist/v2/runtime/endpoints/index.d.mts +2 -2
  128. package/dist/v2/runtime/endpoints/node-fetch-handler.d.cts +1 -1
  129. package/dist/v2/runtime/endpoints/node.cjs +12 -1
  130. package/dist/v2/runtime/endpoints/node.cjs.map +1 -1
  131. package/dist/v2/runtime/endpoints/node.d.cts +22 -2
  132. package/dist/v2/runtime/endpoints/node.d.cts.map +1 -1
  133. package/dist/v2/runtime/endpoints/node.d.mts +21 -1
  134. package/dist/v2/runtime/endpoints/node.d.mts.map +1 -1
  135. package/dist/v2/runtime/endpoints/node.mjs +12 -1
  136. package/dist/v2/runtime/endpoints/node.mjs.map +1 -1
  137. package/dist/v2/runtime/express.d.cts +2 -2
  138. package/dist/v2/runtime/express.d.mts +1 -1
  139. package/dist/v2/runtime/handlers/header-utils.d.cts +1 -1
  140. package/dist/v2/runtime/hono.d.cts +2 -2
  141. package/dist/v2/runtime/hono.d.mts +1 -1
  142. package/dist/v2/runtime/index.d.cts +6 -5
  143. package/dist/v2/runtime/index.d.cts.map +1 -1
  144. package/dist/v2/runtime/index.d.mts +5 -4
  145. package/dist/v2/runtime/index.d.mts.map +1 -1
  146. package/dist/v2/runtime/intelligence-platform/client.d.cts +1 -1
  147. package/dist/v2/runtime/node.d.cts +1 -1
  148. package/dist/v2/runtime/runner/agent-runner.d.cts +1 -1
  149. package/dist/v2/runtime/runner/in-memory.cjs +35 -19
  150. package/dist/v2/runtime/runner/in-memory.cjs.map +1 -1
  151. package/dist/v2/runtime/runner/in-memory.d.cts +13 -1
  152. package/dist/v2/runtime/runner/in-memory.d.cts.map +1 -1
  153. package/dist/v2/runtime/runner/in-memory.d.mts +12 -0
  154. package/dist/v2/runtime/runner/in-memory.d.mts.map +1 -1
  155. package/dist/v2/runtime/runner/in-memory.mjs +35 -19
  156. package/dist/v2/runtime/runner/in-memory.mjs.map +1 -1
  157. package/dist/v2/runtime/runner/index.d.cts +1 -1
  158. package/dist/v2/runtime/runner/intelligence.d.cts +1 -1
  159. package/dist/v2/runtime/telemetry/telemetry-client.mjs +1 -1
  160. package/dist/v2/runtime/transcription-service/transcription-service.d.cts +1 -1
  161. package/package.json +5 -2
  162. package/dist/package.mjs.map +0 -1
@@ -0,0 +1,90 @@
1
+ import "reflect-metadata";
2
+ import { index_d_exports } from "../../../channels/dist/index.mjs";
3
+ import { ChannelActivationConfig } from "./channel-activation-config.mjs";
4
+
5
+ //#region src/v2/runtime/core/channel-manager.d.ts
6
+ /**
7
+ * Lifecycle status of a single Channel activation, or of the manager overall.
8
+ *
9
+ * - `connecting`: activation in flight, not yet settled.
10
+ * - `online`: activation resolved AND the managed session can currently send.
11
+ * A drop moves the Channel to `reconnecting` (not `online`); a successful
12
+ * rejoin restores `online`.
13
+ * - `setup_required`: the Channel is declared but has no managed provider yet —
14
+ * a valid degraded state, not a failure.
15
+ * - `reconnecting`: the managed session dropped and Phoenix is retrying — not
16
+ * currently sendable. The manager does NOT re-activate (reconnection is
17
+ * delegated to the Phoenix connection layer); it only reflects the health the
18
+ * session reports via its `onStateChange` observer.
19
+ * - `stopped`: {@link ChannelManager.stop} has torn the Channel down.
20
+ * - `unmanaged`: the Channel carries a developer-supplied direct adapter, so this
21
+ * handler does NOT own its lifecycle — the developer starts it via
22
+ * `channel.start()`. The manager records the Channel with this status purely so
23
+ * its presence is observable and never misreported as `online`. It is neither
24
+ * activated, awaited, nor stopped here. Real routing of direct channels is
25
+ * deferred (tracked in OSS-486).
26
+ * - `error`: activation rejected with a non-setup error, OR a previously-online
27
+ * session gave up reconnecting after its bounded reconnect window.
28
+ */
29
+ type ChannelStatus = "connecting" | "online" | "setup_required" | "reconnecting" | "stopped" | "unmanaged" | "error";
30
+ /**
31
+ * The lifecycle control surface a Channel host uses to drive and observe
32
+ * managed Channel activation.
33
+ */
34
+ interface ChannelsControl {
35
+ /**
36
+ * Resolve once every declared Channel has settled to a terminal, non-connecting
37
+ * state (`online` or `setup_required`). Rejects if any Channel is in `error`,
38
+ * or — when `timeoutMs` is given — if the whole set has not settled in time.
39
+ */
40
+ ready(opts?: {
41
+ timeoutMs?: number;
42
+ }): Promise<void>;
43
+ /** Snapshot the overall status and the per-Channel status map. */
44
+ status(): {
45
+ overall: ChannelStatus;
46
+ channels: Record<string, ChannelStatus>;
47
+ };
48
+ /** Tear down every activated Channel. Idempotent. */
49
+ stop(): Promise<void>;
50
+ }
51
+ /**
52
+ * The activation engine: given a resolved {@link ChannelActivationConfig} and
53
+ * the declared {@link Channel}, bring the Channel online and return its handle.
54
+ * Injected in tests (a fake engine); defaults to the Realtime Gateway launcher.
55
+ */
56
+ type ActivateChannelEngine = (config: ChannelActivationConfig, channel: index_d_exports.Channel) => Promise<ChannelsHandle>;
57
+ /**
58
+ * Minimal structural view of the `@copilotkit/channels-intelligence`
59
+ * `ChannelsHandle`. Declared locally (not imported) because the runtime is a
60
+ * CJS package that must not take a static dependency on the pure-ESM
61
+ * channels-intelligence package — the default engine reaches its launcher
62
+ * through a dynamic `import()` instead. The manager only ever needs `stop()`.
63
+ */
64
+ interface ChannelsHandle {
65
+ /** Activation metadata declared to Intelligence. Unused by the manager. */
66
+ metadata: unknown;
67
+ /** Stop the underlying Channel(s) and release transports. */
68
+ stop(): Promise<void>;
69
+ /**
70
+ * Optional seam: register a callback the handle fires when its managed
71
+ * session drops. Retained as a per-episode drop breadcrumb; the manager drives
72
+ * status from {@link ChannelsHandle.onStateChange} instead. Present on the
73
+ * Realtime Gateway launcher handle; optional for non-gateway/test handles.
74
+ */
75
+ onClose?(cb: () => void): void;
76
+ /**
77
+ * Optional seam: register a connection-health observer the handle fires as its
78
+ * managed session moves between `online` (sendable), `reconnecting` (dropped,
79
+ * Phoenix retrying), and `gave_up` (dead after the bounded reconnect window).
80
+ * The manager uses this to keep {@link ChannelManager.status} honest — it does
81
+ * NOT re-activate on a drop (reconnection is delegated to the Phoenix
82
+ * connection layer; see {@link ChannelManager}). Optional so non-gateway or
83
+ * test handles that do not implement it are always invoked as
84
+ * `handle.onStateChange?.(cb)`.
85
+ */
86
+ onStateChange?(cb: (state: "online" | "reconnecting" | "gave_up") => void): void;
87
+ }
88
+ //#endregion
89
+ export { ActivateChannelEngine, ChannelStatus, ChannelsControl };
90
+ //# sourceMappingURL=channel-manager.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-manager.d.mts","names":[],"sources":["../../../../src/v2/runtime/core/channel-manager.ts"],"mappings":";;;;;;AAmCA;;;;;AAaA;;;;;;;;;;;;;;;;;KAbY,aAAA;;;;;UAaK,eAAA;EAUA;AAsBjB;;;;EA1BE,KAAA,CAAM,IAAA;IAAS,SAAA;EAAA,IAAuB,OAAA;EA6B5B;EA3BV,MAAA;IAAY,OAAA,EAAS,aAAA;IAAe,QAAA,EAAU,MAAA,SAAe,aAAA;EAAA;EA0B7D;EAxBA,IAAA,IAAQ,OAAA;AAAA;;;;;;KAsBE,qBAAA,IACV,MAAA,EAAQ,uBAAA,EACR,OAAA,EAAS,eAAA,CAAA,OAAA,KACN,OAAA,CAAQ,cAAA;;;;;;;;UASI,cAAA;;EAEf,QAAA;;EAEA,IAAA,IAAQ,OAAA;;;;;;;EAOR,OAAA,EAAS,EAAA;;;;;;;;;;;EAWT,aAAA,EACE,EAAA,GAAK,KAAA;AAAA"}
@@ -0,0 +1,446 @@
1
+ import "reflect-metadata";
2
+ import { ChannelConfigError, deriveChannelActivationConfig } from "./channel-activation-config.mjs";
3
+ import { randomUUID } from "node:crypto";
4
+
5
+ //#region src/v2/runtime/core/channel-manager.ts
6
+ /**
7
+ * Signals that a declared Channel cannot be activated because no managed
8
+ * provider exists for it yet. The engine throws this (or any error whose
9
+ * `code === "SETUP_REQUIRED"`) to move a Channel to `setup_required` rather
10
+ * than `error` — a declared-but-unprovisioned Channel is a valid degraded
11
+ * state, not a failure.
12
+ */
13
+ var ChannelSetupRequiredError = class extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "ChannelSetupRequiredError";
17
+ }
18
+ };
19
+ /**
20
+ * Runtime installs this pure-ESM package as a direct dependency, but the
21
+ * specifier must stay non-literal so it never becomes a static dependency of
22
+ * the runtime's CJS build. The packed-consumer contract is enforced by
23
+ * `scripts/release/verify-runtime-package.ts`.
24
+ */
25
+ const CHANNELS_INTELLIGENCE_SPECIFIER = "@copilotkit/channels-intelligence";
26
+ /**
27
+ * Default engine: wrap the channels-intelligence Realtime Gateway launcher.
28
+ *
29
+ * The module is reached through an injectable importer that defaults to a
30
+ * dynamic `import()` of a non-literal specifier, so the pure-ESM
31
+ * `@copilotkit/channels-intelligence` never becomes a static dependency of this
32
+ * CJS package (mirrors the runtime's other channels seams). The `import`
33
+ * seam is a parameter purely so this function's config→opts mapping and its
34
+ * module-not-found / generic-error branches are unit-testable WITHOUT the real
35
+ * package installed; production always uses the default importer.
36
+ *
37
+ * Passes NO `org`/`channelId` — the launcher's realtime scope treats them as
38
+ * optional.
39
+ *
40
+ * @param config - Resolved activation config for the Channel.
41
+ * @param channel - The Channel to activate.
42
+ * @param importChannelsIntelligence - Test seam; loads the channels-intelligence
43
+ * module. Defaults to a dynamic import of the real package.
44
+ * @param log - Optional diagnostic sink forwarded to the launcher/transport so
45
+ * transport-level drop diagnostics are not silent in the managed path.
46
+ * @returns The launcher's {@link ChannelsHandle}.
47
+ */
48
+ async function defaultActivateChannel(config, channel, importChannelsIntelligence = () => import(CHANNELS_INTELLIGENCE_SPECIFIER), log) {
49
+ let mod;
50
+ try {
51
+ mod = await importChannelsIntelligence();
52
+ } catch (err) {
53
+ if (isModuleNotFound(err)) throw new Error("Managed Channels require '@copilotkit/channels-intelligence' to be installed. Add it to your app's dependencies.", { cause: err });
54
+ throw err;
55
+ }
56
+ return mod.startChannelsOverRealtimeGateway([channel], {
57
+ wsUrl: config.wsUrl,
58
+ apiKey: config.apiKey,
59
+ scope: {
60
+ projectId: config.projectId,
61
+ channelName: config.channelName
62
+ },
63
+ runtimeInstanceId: config.runtimeInstanceId,
64
+ adapter: config.adapter,
65
+ appApiBaseUrl: config.apiUrl,
66
+ ...log ? { log } : {}
67
+ });
68
+ }
69
+ /** Whether `err` signals a missing managed provider rather than a hard failure. */
70
+ function isSetupRequired(err) {
71
+ return err instanceof ChannelSetupRequiredError || typeof err === "object" && err !== null && err.code === "SETUP_REQUIRED";
72
+ }
73
+ /**
74
+ * Whether `err` is a Node/runtime module-resolution failure — i.e. the error
75
+ * a dynamic `import()` throws when the target package is not installed.
76
+ * Exported so the friendly-error path in {@link defaultActivateChannel} can be
77
+ * unit-tested without forcing a real failing import.
78
+ */
79
+ function isModuleNotFound(err) {
80
+ if (typeof err !== "object" || err === null) return false;
81
+ const code = err.code;
82
+ return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
83
+ }
84
+ /** Default deadline (ms) for a single `handle.stop()` during teardown. */
85
+ const DEFAULT_STOP_HANDLE_TIMEOUT_MS = 5e3;
86
+ /**
87
+ * Reject with `timeoutMessage` after `timeoutMs` if `inner` has not settled,
88
+ * otherwise pass `inner` through. When `timeoutMs` is undefined, `inner` is
89
+ * returned unchanged. The timer is `unref`'d so a pending deadline never keeps
90
+ * the process alive, and `inner` always has a settle handler attached, so a
91
+ * timed-out promise that later settles never surfaces as unhandled.
92
+ */
93
+ function withTimeout(inner, timeoutMs, timeoutMessage) {
94
+ if (timeoutMs === void 0) return inner;
95
+ return new Promise((resolve, reject) => {
96
+ const timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
97
+ timer.unref?.();
98
+ inner.then((value) => {
99
+ clearTimeout(timer);
100
+ resolve(value);
101
+ }, (err) => {
102
+ clearTimeout(timer);
103
+ reject(err);
104
+ });
105
+ });
106
+ }
107
+ /**
108
+ * Drives managed Channel activation for an Intelligence runtime: lazily
109
+ * activates each declared Channel through an engine, tracks per-Channel
110
+ * lifecycle status, exposes readiness, and tears everything down.
111
+ *
112
+ * Activation is lazy and idempotent — constructing the manager does nothing;
113
+ * {@link activate} starts it and a second call is a no-op. Activation throws
114
+ * SYNCHRONOUSLY (a {@link ChannelConfigError}) only for a misconfiguration it
115
+ * can detect up front — a duplicate or missing Channel name. Every OTHER
116
+ * activation failure is recorded as the Channel's status (`error`, or
117
+ * `setup_required` for a missing provider) and surfaced through {@link status}
118
+ * and {@link ready} rather than thrown.
119
+ *
120
+ * Reconnection is NOT handled here — it is delegated to the Phoenix connection
121
+ * layer that backs the launcher. When a managed socket drops, Phoenix's `Socket`
122
+ * auto-reconnects and auto-rejoins, re-sending the channel's join declaration;
123
+ * the Intelligence gateway's `join/3` re-runs `record_heartbeat` (re-registering
124
+ * the runtime's listener) and its `terminate/2` releases the dead socket's
125
+ * leases (verified against Intelligence #511 `sdk_channel.ex`). So the transport
126
+ * self-heals under the persistent adapter and a re-activation here would be both
127
+ * redundant AND broken: re-invoking the engine on an already-started `Channel`
128
+ * throws in `channel.addAdapter` (started=true). The manager therefore never
129
+ * re-activates on a drop.
130
+ *
131
+ * It DOES, however, reflect real connection health through the session's
132
+ * `onStateChange` observer so {@link ChannelManager.status} stays honest rather
133
+ * than reporting `online` forever after a drop: a drop moves the Channel to
134
+ * `reconnecting`, a successful rejoin restores `online`, and a bounded give-up
135
+ * (Phoenix would otherwise retry forever) moves it to `error`.
136
+ */
137
+ var ChannelManager = class {
138
+ /** @param args - See {@link ChannelManagerArgs}. */
139
+ constructor(args) {
140
+ this.entries = /* @__PURE__ */ new Map();
141
+ this.activated = false;
142
+ this.stopped = false;
143
+ this.intelligence = args.intelligence;
144
+ this.channels = args.channels;
145
+ this.log = args.log;
146
+ this.activateChannel = args.activateChannel ?? ((config, channel) => defaultActivateChannel(config, channel, void 0, this.log));
147
+ this.mintRuntimeInstanceId = args.mintRuntimeInstanceId ?? (() => `rti_${randomUUID().replace(/-/g, "")}`);
148
+ this.stopHandleTimeoutMs = args.stopHandleTimeoutMs ?? DEFAULT_STOP_HANDLE_TIMEOUT_MS;
149
+ }
150
+ /**
151
+ * Start activation of every declared Channel (lazy + idempotent). Mints a
152
+ * distinct runtime instance id per Channel, derives its activation config,
153
+ * and calls the engine. Records each Channel as `connecting`, transitioning
154
+ * to `online`/`setup_required`/`error` as its activation settles.
155
+ */
156
+ activate() {
157
+ if (this.activated || this.stopped) return;
158
+ this.assertUniqueChannelNames();
159
+ this.activated = true;
160
+ for (const channel of this.channels) {
161
+ if (channel.adapters.some((a) => !a.__intelligenceChannel)) {
162
+ this.log?.(`channel "${channel.name}" carries a direct adapter — recording status "unmanaged" and skipping managed activation (this handler does not own its lifecycle; start it via channel.start(); exclusive per Channel: a Channel served by a direct adapter is not also managed, regardless of platform — managed+direct coexistence deferred (OSS-484); routing of direct channels deferred (OSS-486))`);
163
+ this.entries.set(channel.name, {
164
+ status: "unmanaged",
165
+ handle: void 0,
166
+ handleStopped: false,
167
+ settled: Promise.resolve()
168
+ });
169
+ continue;
170
+ }
171
+ const name = channel.name;
172
+ const runtimeInstanceId = this.mintRuntimeInstanceId();
173
+ let resolveSettled;
174
+ let rejectSettled;
175
+ const settled = new Promise((resolve, reject) => {
176
+ resolveSettled = resolve;
177
+ rejectSettled = reject;
178
+ });
179
+ settled.catch(() => {});
180
+ let activation;
181
+ let config;
182
+ try {
183
+ config = deriveChannelActivationConfig({
184
+ intelligence: this.intelligence,
185
+ channel,
186
+ runtimeInstanceId
187
+ });
188
+ activation = this.activateChannel(config, channel);
189
+ } catch (err) {
190
+ activation = Promise.reject(err);
191
+ }
192
+ const entry = {
193
+ status: "connecting",
194
+ handle: void 0,
195
+ handleStopped: false,
196
+ settled
197
+ };
198
+ activation.then(async (handle) => {
199
+ entry.handle = handle;
200
+ if (this.stopped) {
201
+ await this.stopEntry(entry);
202
+ resolveSettled();
203
+ return;
204
+ }
205
+ entry.status = "online";
206
+ this.registerConnectionObserver(name, entry);
207
+ resolveSettled();
208
+ }, async (err) => {
209
+ if (this.stopped) {
210
+ await this.stopEntry(entry);
211
+ resolveSettled();
212
+ return;
213
+ }
214
+ if (isSetupRequired(err)) {
215
+ entry.status = "setup_required";
216
+ this.log?.(`channel "${name}" requires setup`, err);
217
+ resolveSettled();
218
+ } else {
219
+ entry.status = "error";
220
+ this.log?.(`channel "${name}" failed to activate`, err);
221
+ rejectSettled(err);
222
+ }
223
+ }).catch(() => {});
224
+ this.entries.set(name, entry);
225
+ }
226
+ }
227
+ /**
228
+ * Throw if two declared Channels share a `name`. `entries` is keyed by name,
229
+ * so a duplicate would overwrite the first Channel's entry and leak its live
230
+ * session. Called at the very start of {@link activate}, before any engine
231
+ * call, so a misconfiguration fails loud instead of silently.
232
+ *
233
+ * @throws {ChannelConfigError} If any Channel is missing a name, or if any
234
+ * name appears more than once.
235
+ */
236
+ assertUniqueChannelNames() {
237
+ const seen = /* @__PURE__ */ new Set();
238
+ for (const channel of this.channels) {
239
+ const name = channel.name;
240
+ if (!name) throw new ChannelConfigError("A managed Channel is missing a `name` — every declared Channel must have a unique, non-empty name (pass createChannel({ name })).");
241
+ if (seen.has(name)) throw new ChannelConfigError(`Duplicate managed Channel name "${name}" — every declared Channel must have a unique name.`);
242
+ seen.add(name);
243
+ }
244
+ }
245
+ /**
246
+ * Resolve when every managed Channel has settled to `online`/`setup_required`.
247
+ *
248
+ * A direct-adapter (`unmanaged`) Channel has an already-resolved `settled` and
249
+ * so never blocks — but its resolution implies NO health: this handler does not
250
+ * own it. Truthfulness about direct Channels lives in {@link status} (they read
251
+ * `unmanaged`, never `online`), not in `ready()` resolving.
252
+ *
253
+ * Activates lazily if not already started — so a first call rejects with the
254
+ * same {@link ChannelConfigError} as the synchronous throw from
255
+ * {@link activate} for an up-front misconfiguration (duplicate/missing Channel
256
+ * names). Once activation has been kicked off, all OTHER failures are surfaced
257
+ * here instead: this rejects with an `AggregateError` if any Channel settled
258
+ * to `error` OR — when `timeoutMs` is given — did not settle in time. The
259
+ * `timeoutMs` deadline is applied PER CHANNEL, so the aggregate carries each
260
+ * failed Channel's real reason AND a named timeout for each Channel still
261
+ * hanging: a genuine activation error is never masked by a sibling that hangs
262
+ * (a pre-fix set-wide timeout discarded the real reason in that case).
263
+ *
264
+ * A STOPPED manager short-circuits and resolves: a Channel that settled to
265
+ * `error` BEFORE {@link stop} already rejected its `settled` promise, so
266
+ * awaiting it here would throw an `AggregateError` even though
267
+ * {@link status}.overall is `"stopped"` — inconsistent with the case where the
268
+ * Channel was still online at stop() (which resolves). A stopped manager has
269
+ * nothing left to be ready for, so resolve uniformly.
270
+ *
271
+ * `ready()` is ONE-SHOT: it settles on the INITIAL activation outcome. Later
272
+ * connection-health transitions (a live Channel dropping to `reconnecting`, or
273
+ * giving up to `error`) are reported through {@link status} — where `online`
274
+ * means currently-sendable — but do NOT re-arm or re-reject an already-settled
275
+ * `ready()`.
276
+ */
277
+ async ready(opts) {
278
+ if (this.stopped) return;
279
+ this.activate();
280
+ const entries = [...this.entries.entries()];
281
+ const errors = (await Promise.allSettled(entries.map(([name, e]) => withTimeout(e.settled, opts?.timeoutMs, `channel "${name}" did not settle within ${opts?.timeoutMs}ms`)))).filter((r) => r.status === "rejected").map((r) => r.reason);
282
+ if (errors.length > 0) throw new AggregateError(errors, `ChannelManager.ready: ${errors.length} channel(s) failed to activate or settle in time`);
283
+ }
284
+ /**
285
+ * Snapshot status. Every declared Channel — managed OR direct/`unmanaged` —
286
+ * appears keyed by name in `channels`; a direct-adapter Channel this handler
287
+ * does not own is always surfaced as `unmanaged`, never `online`.
288
+ *
289
+ * `overall` is folded over the MANAGED Channels only (see {@link computeOverall}),
290
+ * by precedence `error` > `reconnecting` > `setup_required` > `connecting` >
291
+ * `online`. `online` means every managed Channel can currently send.
292
+ * `reconnecting` outranks `setup_required` because a dropped-but-retrying
293
+ * Channel is an active outage, louder than a steadily-degraded unprovisioned
294
+ * one. `unmanaged` Channels are EXCLUDED from that fold — they carry no health
295
+ * this handler established — so a healthy managed Channel alongside an
296
+ * `unmanaged` one still reports `overall: "online"` while the `unmanaged` one
297
+ * stays visible per-Channel. When every declared Channel is `unmanaged`,
298
+ * `overall` is `unmanaged` (NOT `online`). With no declared Channels at all,
299
+ * `overall` is `online` (nothing is degraded); once every managed Channel has
300
+ * been stopped, `overall` is `stopped`.
301
+ */
302
+ status() {
303
+ const channels = {};
304
+ for (const [name, entry] of this.entries) channels[name] = entry.status;
305
+ if (this.stopped) return {
306
+ overall: "stopped",
307
+ channels
308
+ };
309
+ if (!this.activated && this.channels.length > 0) return {
310
+ overall: "connecting",
311
+ channels
312
+ };
313
+ return {
314
+ overall: this.computeOverall(Object.values(channels)),
315
+ channels
316
+ };
317
+ }
318
+ /**
319
+ * Fold per-Channel statuses into a single overall status (see {@link status}).
320
+ *
321
+ * `unmanaged` Channels are folded out FIRST: they carry no lifecycle this
322
+ * handler owns, so they must neither count as `online` nor mask a real managed
323
+ * outage. The remaining MANAGED statuses are ranked
324
+ * `error` > `reconnecting` > `setup_required` > `connecting` > `online`, so a
325
+ * genuine managed failure still dominates while a healthy managed Channel
326
+ * beside an `unmanaged` one reads `online`. If NO managed Channels remain (every
327
+ * declared Channel is direct/`unmanaged`) the result is `unmanaged` — never the
328
+ * false-healthy `online`. The empty-input case (no declared Channels at all)
329
+ * stays `online` (nothing is degraded).
330
+ */
331
+ computeOverall(values) {
332
+ if (values.length === 0) return "online";
333
+ const managed = values.filter((v) => v !== "unmanaged");
334
+ if (managed.length === 0) return "unmanaged";
335
+ if (managed.every((v) => v === "stopped")) return "stopped";
336
+ if (managed.includes("error")) return "error";
337
+ if (managed.includes("reconnecting")) return "reconnecting";
338
+ if (managed.includes("setup_required")) return "setup_required";
339
+ if (managed.includes("connecting")) return "connecting";
340
+ return "online";
341
+ }
342
+ /**
343
+ * Wire the Channel's connection-health observer (if the handle exposes the
344
+ * optional `onStateChange` seam) so {@link ChannelManager.status} reflects real
345
+ * health instead of reporting `online` forever after a drop:
346
+ *
347
+ * - `reconnecting` → status `reconnecting` (dropped, Phoenix retrying);
348
+ * - `online` → status `online` (rejoined, sendable again);
349
+ * - `gave_up` → status `error` (dead after the bounded reconnect window).
350
+ *
351
+ * Makes NO re-activation — reconnection is delegated to the Phoenix connection
352
+ * layer (see {@link ChannelManager}), which auto-rejoins under the persistent
353
+ * adapter. A STOPPED manager (or an already-stopped entry) ignores late
354
+ * connection events, so a drop that fires after {@link ChannelManager.stop}
355
+ * never resurrects the Channel out of `stopped`.
356
+ *
357
+ * @param name - The Channel name (map key).
358
+ * @param entry - The Channel's activation entry.
359
+ */
360
+ registerConnectionObserver(name, entry) {
361
+ entry.handle?.onStateChange?.((state) => {
362
+ if (this.stopped || entry.status === "stopped") return;
363
+ if (state === "reconnecting") {
364
+ entry.status = "reconnecting";
365
+ this.log?.(`channel "${name}" managed session dropped; reconnecting (Phoenix auto-rejoin)`);
366
+ } else if (state === "online") {
367
+ entry.status = "online";
368
+ this.log?.(`channel "${name}" managed session back online`);
369
+ } else {
370
+ entry.status = "error";
371
+ this.log?.(`channel "${name}" managed session gave up reconnecting; marking error`);
372
+ }
373
+ });
374
+ }
375
+ /**
376
+ * Drive a single entry to its terminal `stopped` state, tearing down its
377
+ * handle AT MOST ONCE. Idempotent: it always sets `status = "stopped"`, and
378
+ * only calls `handle.stop()` on the first invocation that sees a live,
379
+ * not-yet-stopped handle (gated by {@link ChannelEntry.handleStopped}).
380
+ *
381
+ * This is the ONE guarded teardown path shared by both `stop()` and the
382
+ * post-settle guard in {@link activate}. Because the guard is per-entry and
383
+ * idempotent, a handle assigned in the same tick as `stop()` is stopped
384
+ * exactly once even when both callers reach the entry, and a late settle can
385
+ * never resurrect a `stopped` entry.
386
+ *
387
+ * `handle.stop()` failures are logged (via {@link ChannelManager.log}) but NOT
388
+ * rethrown: the real launcher's `stop()` rethrows after `session.disconnect()`,
389
+ * and teardown must still complete for every other entry. The call is wrapped
390
+ * in `Promise.resolve().then(...)` so a foreign/injected handle whose `stop()`
391
+ * throws SYNCHRONOUSLY (before any promise is created) is caught by the same
392
+ * `.catch` — otherwise the sync throw would escape, skip `resolveSettled()` in
393
+ * the fulfilled-then-stopped branch of {@link activate}, and hang `settled`.
394
+ *
395
+ * An `unmanaged` entry (a direct-adapter Channel this handler never activated)
396
+ * is left untouched: the manager owns no handle and no lifecycle for it, so
397
+ * claiming to have `stopped` it would be as untruthful as calling it `online`.
398
+ * The developer's `channel.start()`/stop path is unaffected by manager
399
+ * teardown.
400
+ *
401
+ * A WEDGED `handle.stop()` (one that never settles) is bounded by
402
+ * {@link ChannelManagerArgs.stopHandleTimeoutMs}: after the deadline the call
403
+ * is logged and abandoned so it can't hang `stop()` — and thus SIGTERM
404
+ * shutdown — forever.
405
+ *
406
+ * @param entry - The Channel entry to stop.
407
+ */
408
+ async stopEntry(entry) {
409
+ if (entry.status === "unmanaged") return;
410
+ entry.status = "stopped";
411
+ if (entry.handle && !entry.handleStopped) {
412
+ entry.handleStopped = true;
413
+ const handle = entry.handle;
414
+ await withTimeout(Promise.resolve().then(() => handle.stop()), this.stopHandleTimeoutMs, `channel handle stop() timed out after ${this.stopHandleTimeoutMs}ms during teardown`).catch((err) => this.log?.("channel handle stop() failed during teardown", err));
415
+ }
416
+ }
417
+ /**
418
+ * Stop every activated Channel exactly once and mark all statuses `stopped`.
419
+ * Idempotent — a second call is a no-op.
420
+ *
421
+ * Resolves promptly: {@link stopEntry} stops only the handles that already
422
+ * exist and never blocks on activations that have not settled. A hung connect
423
+ * (which `ready({ timeoutMs })` tolerates) has no handle to stop yet, and
424
+ * awaiting it here would hang teardown — and thus SIGTERM shutdown — forever.
425
+ * Any handle that arrives after this point is torn down by the post-settle
426
+ * guard in {@link activate}, which routes through the same idempotent
427
+ * {@link stopEntry}, so nothing leaks and nothing double-stops.
428
+ *
429
+ * Teardown is resilient to a throwing `handle.stop()`: `Promise.allSettled`
430
+ * over the per-entry `stopEntry` calls guarantees one rejection can't abort
431
+ * the rest, so every entry reaches `stopped` and `stop()` always resolves.
432
+ * It is equally resilient to a WEDGED `handle.stop()` that never settles: each
433
+ * is bounded by {@link ChannelManagerArgs.stopHandleTimeoutMs} inside
434
+ * {@link stopEntry}, so a single hung handle can't hang SIGTERM shutdown.
435
+ */
436
+ async stop() {
437
+ if (this.stopped) return;
438
+ this.stopped = true;
439
+ const entries = [...this.entries.values()];
440
+ await Promise.allSettled(entries.map((entry) => this.stopEntry(entry)));
441
+ }
442
+ };
443
+
444
+ //#endregion
445
+ export { ChannelManager };
446
+ //# sourceMappingURL=channel-manager.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-manager.mjs","names":[],"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\";\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 * - `unmanaged`: the Channel carries a developer-supplied direct adapter, so this\n * handler does NOT own its lifecycle — the developer starts it via\n * `channel.start()`. The manager records the Channel with this status purely so\n * its presence is observable and never misreported as `online`. It is neither\n * activated, awaited, nor stopped here. Real routing of direct channels is\n * deferred (tracked in OSS-486).\n * - `error`: activation rejected with a non-setup error, OR a previously-online\n * session gave up reconnecting after its bounded reconnect window.\n */\nexport type ChannelStatus =\n | \"connecting\"\n | \"online\"\n | \"setup_required\"\n | \"reconnecting\"\n | \"stopped\"\n | \"unmanaged\"\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: (state: \"online\" | \"reconnecting\" | \"gave_up\") => 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 /**\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 /** 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}\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 /** 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 },\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): 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 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 // 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 });\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/**\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 managed Channel activation for an Intelligence runtime: lazily\n * activates each declared Channel through an engine, tracks per-Channel\n * lifecycle status, exposes readiness, and tears everything down.\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 socket drops, Phoenix's `Socket`\n * auto-reconnects and auto-rejoins, re-sending the channel's join declaration;\n * the Intelligence gateway's `join/3` re-runs `record_heartbeat` (re-registering\n * the runtime's listener) and its `terminate/2` releases the dead socket's\n * leases (verified against Intelligence #511 `sdk_channel.ex`). So the transport\n * self-heals under the persistent adapter and a re-activation here would be both\n * redundant AND broken: re-invoking the engine on an already-started `Channel`\n * throws in `channel.addAdapter` (started=true). The manager therefore never\n * 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 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\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.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(config, channel, undefined, this.log));\n this.mintRuntimeInstanceId =\n args.mintRuntimeInstanceId ??\n (() => `rti_${randomUUID().replace(/-/g, \"\")}`);\n this.stopHandleTimeoutMs =\n args.stopHandleTimeoutMs ?? DEFAULT_STOP_HANDLE_TIMEOUT_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 live session 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: it is started by the developer via `channel.start()`, not\n // managed-activated here. The skip is EXCLUSIVE PER CHANNEL, not per platform\n // — a Channel served by a direct adapter is not also managed: ANY direct\n // adapter makes the WHOLE Channel `unmanaged` and skips managed activation,\n // regardless of platform. Attaching the managed adapter alongside a direct\n // one would double-deliver every turn (and trip the SDK's `assertExclusive`\n // guard, moving the Channel to `error`). Per the SoT rule, never infer\n // managed intent from a local direct adapter — a managed-eligible Channel has\n // an empty `adapters` at declaration time. Managed+direct coexistence on the\n // same Channel is NOT supported today; it is deferred (OSS-484), as is real\n // routing of direct channels (OSS-486).\n for (const channel of this.channels) {\n const isDirect = channel.adapters.some((a) => !a.__intelligenceChannel);\n if (isDirect) {\n this.log?.(\n `channel \"${channel.name!}\" carries a direct adapter — recording status \"unmanaged\" and skipping managed activation (this handler does not own its lifecycle; start it via channel.start(); exclusive per Channel: a Channel served by a direct adapter is not also managed, regardless of platform — managed+direct coexistence deferred (OSS-484); routing of direct channels deferred (OSS-486))`,\n );\n // Record an EXPLICIT `unmanaged` entry rather than skipping silently.\n // A skipped Channel with no entry vanishes from status()/computeOverall,\n // so a runtime whose only Channel is direct would falsely read `online`\n // and ready() would imply a health this handler never established. The\n // entry keeps the Channel observable and truthful: it is never\n // activated, its `settled` is already resolved (nothing on the managed\n // path to wait for), and stopEntry leaves it untouched (see stopEntry).\n this.entries.set(channel.name!, {\n status: \"unmanaged\",\n handle: undefined,\n handleStopped: false,\n settled: Promise.resolve(),\n });\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 * 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 managed Channel has settled to `online`/`setup_required`.\n *\n * A direct-adapter (`unmanaged`) Channel has an already-resolved `settled` and\n * so never blocks — but its resolution implies NO health: this handler does not\n * own it. Truthfulness about direct Channels lives in {@link status} (they read\n * `unmanaged`, never `online`), not in `ready()` resolving.\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/`unmanaged` —\n * appears keyed by name in `channels`; a direct-adapter Channel this handler\n * does not own is always surfaced as `unmanaged`, never `online`.\n *\n * `overall` is folded over the MANAGED Channels only (see {@link computeOverall}),\n * by precedence `error` > `reconnecting` > `setup_required` > `connecting` >\n * `online`. `online` means every managed Channel can currently send.\n * `reconnecting` outranks `setup_required` because a dropped-but-retrying\n * Channel is an active outage, louder than a steadily-degraded unprovisioned\n * one. `unmanaged` Channels are EXCLUDED from that fold — they carry no health\n * this handler established — so a healthy managed Channel alongside an\n * `unmanaged` one still reports `overall: \"online\"` while the `unmanaged` one\n * stays visible per-Channel. When every declared Channel is `unmanaged`,\n * `overall` is `unmanaged` (NOT `online`). With no declared Channels at all,\n * `overall` is `online` (nothing is degraded); once every managed Channel has\n * 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 * `unmanaged` Channels are folded out FIRST: they carry no lifecycle this\n * handler owns, so they must neither count as `online` nor mask a real managed\n * outage. The remaining MANAGED statuses are ranked\n * `error` > `reconnecting` > `setup_required` > `connecting` > `online`, so a\n * genuine managed failure still dominates while a healthy managed Channel\n * beside an `unmanaged` one reads `online`. If NO managed Channels remain (every\n * declared Channel is direct/`unmanaged`) the result is `unmanaged` — never the\n * false-healthy `online`. The empty-input case (no declared Channels at all)\n * stays `online` (nothing is degraded).\n */\n private computeOverall(values: ChannelStatus[]): ChannelStatus {\n if (values.length === 0) {\n return \"online\";\n }\n const managed = values.filter((v) => v !== \"unmanaged\");\n if (managed.length === 0) {\n return \"unmanaged\";\n }\n if (managed.every((v) => v === \"stopped\")) {\n return \"stopped\";\n }\n if (managed.includes(\"error\")) {\n return \"error\";\n }\n if (managed.includes(\"reconnecting\")) {\n return \"reconnecting\";\n }\n if (managed.includes(\"setup_required\")) {\n return \"setup_required\";\n }\n if (managed.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) => {\n // A stopped manager (or a stopped entry) ignores late connection events.\n if (this.stopped || entry.status === \"stopped\") {\n return;\n }\n if (state === \"reconnecting\") {\n entry.status = \"reconnecting\";\n this.log?.(\n `channel \"${name}\" managed session dropped; reconnecting (Phoenix auto-rejoin)`,\n );\n } else if (state === \"online\") {\n entry.status = \"online\";\n this.log?.(`channel \"${name}\" managed session back online`);\n } else {\n entry.status = \"error\";\n this.log?.(\n `channel \"${name}\" managed session gave up reconnecting; marking error`,\n );\n }\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}. Because the guard is per-entry and\n * idempotent, a handle assigned in the same tick as `stop()` is stopped\n * exactly once even when both callers reach the entry, and a late settle can\n * never resurrect a `stopped` entry.\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 `unmanaged` entry (a direct-adapter Channel this handler never activated)\n * is left untouched: the manager owns no handle and no lifecycle for it, so\n * claiming to have `stopped` it would be as untruthful as calling it `online`.\n * The developer's `channel.start()`/stop path is unaffected by manager\n * teardown.\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 if (entry.status === \"unmanaged\") {\n return;\n }\n entry.status = \"stopped\";\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,4 +1,4 @@
1
-
1
+ require("reflect-metadata");
2
2
  import { DebugEventEnvelope } from "@copilotkit/shared";
3
3
  import { BaseEvent } from "@ag-ui/client";
4
4
 
@@ -1,4 +1,4 @@
1
-
1
+ require("reflect-metadata");
2
2
  //#region src/v2/runtime/core/fetch-cors.d.ts
3
3
  /**
4
4
  * Built-in CORS utility for framework-agnostic CopilotKit runtime handler.