{"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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"}