{"version":3,"file":"subgraphs.cjs","names":["StreamStore","#onValuesEvent","isRootNamespace","namespaceKey","#promoted","#shadow","#ensureShadow","#commit","isInternalWorkNamespace"],"sources":["../../../src/stream/discovery/subgraphs.ts"],"sourcesContent":["/**\n * Root-scoped subgraph discovery.\n *\n * Watches namespaced `lifecycle` events on the root subscription and\n * assembles two views of the subgraph set:\n *\n *  - {@link SubgraphMap}: `Map<namespaceKey, SubgraphDiscoverySnapshot>`,\n *    the canonical identity-keyed view consumed by the channel\n *    registry and selector hooks.\n *  - {@link SubgraphByNodeMap}: `Map<nodeName, readonly\n *    SubgraphDiscoverySnapshot[]>`, a convenience index so callers\n *    can look up subgraphs by the graph node that produced them\n *    (`addNode(\"visualizer_0\", …)`) without parsing namespaces.\n *    Arrays preserve insertion order, which matters for parallel\n *    fan-outs that share a node name.\n *\n * # What counts as a subgraph\n *\n * The server emits a namespaced `lifecycle` event for every node\n * invocation — a plain function node (`orchestrator`) and a subgraph\n * host (`research`) look identical on the wire. We classify a\n * namespace as a subgraph iff at least one strictly-deeper namespace\n * has been observed with it as a prefix. Concretely, given a stream\n * whose lifecycle events hit the namespaces\n *\n *   `[\"orchestrator:u1\"]`\n *   `[\"research:u2\"]`\n *   `[\"research:u2\", \"researcher:u3\"]`\n *   `[\"research:u2\", \"tools:u4\"]`\n *   `[\"writer:u5\"]`\n *\n * only `[\"research:u2\"]` is promoted — it's the only namespace that\n * hosts deeper executions. `orchestrator` and `writer` are plain\n * function-node leaves; the `researcher` / `tools` entries are the\n * subgraph's internal nodes, not subgraphs in their own right.\n *\n * Promotion is monotonic (a namespace never loses subgraph status)\n * and retroactive: a namespace whose own `started` event arrived\n * before any descendant is promoted later when the first descendant\n * event lands. Latency is bounded by the gap between a parent node\n * entering and its first inner node materializing — typically tens\n * of milliseconds.\n */\nimport type { Event, LifecycleEvent, ValuesEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"../store.js\";\nimport type { SubgraphDiscoverySnapshot } from \"../types.js\";\nimport {\n  isInternalWorkNamespace,\n  isRootNamespace,\n  namespaceKey,\n} from \"../namespace.js\";\n\nexport type SubgraphMap = ReadonlyMap<string, SubgraphDiscoverySnapshot>;\nexport type SubgraphByNodeMap = ReadonlyMap<\n  string,\n  readonly SubgraphDiscoverySnapshot[]\n>;\n\n/** Stable empty maps — reused on {@link SubgraphDiscovery.reset}. */\nconst EMPTY_SUBGRAPH_MAP: SubgraphMap = new Map();\nconst EMPTY_SUBGRAPH_BY_NODE_MAP: SubgraphByNodeMap = new Map();\n\ninterface MutableSubgraph {\n  id: string;\n  namespace: readonly string[];\n  nodeName: string;\n  status: \"running\" | \"complete\" | \"error\";\n  startedAt: Date;\n  completedAt: Date | null;\n}\n\n/**\n * LangGraph namespaces a node invocation as `<node_name>:<uuid>`\n * (parallel fan-outs share `<node_name>` as a prefix but each get a\n * fresh uuid). Extract the node-name half so callers can key\n * discovery lookups on names they wrote in `addNode(...)`.\n */\nfunction parseNodeName(segment: string): string {\n  const colon = segment.indexOf(\":\");\n  return colon === -1 ? segment : segment.slice(0, colon);\n}\n\nexport class SubgraphDiscovery {\n  readonly store = new StreamStore<SubgraphMap>(new Map());\n  readonly byNodeStore = new StreamStore<SubgraphByNodeMap>(new Map());\n\n  /**\n   * Latest known status for every namespaced lifecycle event we have\n   * ever observed. A shadow entry is NOT necessarily a subgraph —\n   * it is only projected into the committed stores once the same\n   * namespace also appears in {@link #promoted}.\n   */\n  #shadow = new Map<string, MutableSubgraph>();\n\n  /**\n   * Namespaces that have been observed as a strict prefix of a\n   * deeper namespace and are therefore confirmed subgraph hosts.\n   * Insertion order is preserved and becomes the iteration order\n   * of the committed snapshot maps.\n   */\n  #promoted = new Set<string>();\n\n  /** Feed a single root event. Non-discovery events are ignored. */\n  push(event: Event): void {\n    if (event.method === \"values\") {\n      this.#onValuesEvent(event as ValuesEvent);\n      return;\n    }\n    if (event.method !== \"lifecycle\") return;\n    const lifecycle = event as LifecycleEvent;\n    const namespace = lifecycle.params.namespace;\n    // Root lifecycle events describe the main run; subgraph discovery\n    // only cares about namespaced lifecycle events.\n    if (isRootNamespace(namespace)) return;\n    const id = namespaceKey(namespace);\n    const data = lifecycle.params.data as { event?: string };\n    const lastSegment = namespace[namespace.length - 1] ?? \"\";\n    const nodeName = parseNodeName(lastSegment);\n\n    let touched = false;\n\n    // Promote every strict ancestor the first time we see it as a\n    // prefix. The ancestor may or may not yet have a shadow entry;\n    // #commit() tolerates either case.\n    for (let depth = 1; depth < namespace.length; depth += 1) {\n      const ancestorId = namespaceKey(namespace.slice(0, depth));\n      if (!this.#promoted.has(ancestorId)) {\n        this.#promoted.add(ancestorId);\n        if (this.#shadow.has(ancestorId)) touched = true;\n      }\n    }\n\n    // Update shadow status for this namespace itself.\n    if (data.event === \"started\") {\n      if (!this.#shadow.has(id)) {\n        this.#shadow.set(id, {\n          id,\n          namespace: [...namespace],\n          nodeName,\n          status: \"running\",\n          startedAt: new Date(),\n          completedAt: null,\n        });\n        if (this.#promoted.has(id)) touched = true;\n      }\n    } else if (\n      data.event === \"completed\" ||\n      data.event === \"interrupted\" ||\n      data.event === \"failed\"\n    ) {\n      // Synthesize a shadow entry if we missed the `started` event\n      // (common when a late subscription attaches to a running run).\n      const entry = this.#ensureShadow(id, namespace, nodeName);\n      if (data.event === \"failed\") {\n        entry.status = \"error\";\n      } else {\n        entry.status = \"complete\";\n      }\n      entry.completedAt = new Date();\n      if (this.#promoted.has(id)) touched = true;\n    }\n\n    if (touched) this.#commit();\n  }\n\n  /**\n   * Promote subgraph host namespaces from namespaced `values` snapshots.\n   *\n   * Older protocol streams exposed subgraph structure primarily through\n   * nested `lifecycle` events: a host namespace such as\n   * `[\"research:<uuid>\"]` was promoted once a deeper namespace like\n   * `[\"research:<uuid>\", \"inner:<uuid>\"]` appeared. Some runtimes now\n   * emit the useful subgraph signal as `values` snapshots scoped directly\n   * to the host namespace instead, without forwarding inner lifecycle\n   * events to the client. In that shape, the namespace on the `values`\n   * event is already the selector target that `useMessages(stream,\n   * subgraph)` should subscribe to, so we create/promote the shadow\n   * subgraph entry from that namespace.\n   *\n   * Root `values` events are ignored because they represent the parent\n   * thread state, not a subgraph. Tool/subagent namespaces are also\n   * ignored because deep-agent task tools emit their own namespaced\n   * `values` snapshots under `tools:*` / `task:*`; those are discovered\n   * by `SubagentDiscovery` and must not be duplicated as subgraphs.\n   *\n   * A `values` event does not carry lifecycle terminal status, so the\n   * entry remains marked `running` until a matching lifecycle terminal\n   * event arrives. If no terminal arrives, the discovery map still\n   * contains the host namespace, which is the important invariant for\n   * scoped selectors.\n   */\n  #onValuesEvent(event: ValuesEvent): void {\n    const namespace = event.params.namespace;\n    if (isRootNamespace(namespace)) return;\n    if (isInternalWorkNamespace(namespace)) return;\n    const data = event.params.data;\n    if (data == null || typeof data !== \"object\" || Array.isArray(data)) return;\n\n    const id = namespaceKey(namespace);\n    const lastSegment = namespace[namespace.length - 1] ?? \"\";\n    const nodeName = parseNodeName(lastSegment);\n    const entry = this.#ensureShadow(id, namespace, nodeName);\n    entry.status = \"running\";\n\n    if (!this.#promoted.has(id)) {\n      this.#promoted.add(id);\n    }\n    this.#commit();\n  }\n\n  get snapshot(): SubgraphMap {\n    return this.store.getSnapshot();\n  }\n\n  get byNodeSnapshot(): SubgraphByNodeMap {\n    return this.byNodeStore.getSnapshot();\n  }\n\n  /**\n   * Drop all discovery state. Called on thread rebind / dispose so a\n   * new thread's subgraphs cannot bleed into the previous UI.\n   */\n  reset(): void {\n    this.#shadow.clear();\n    this.#promoted.clear();\n    this.store.setValue(EMPTY_SUBGRAPH_MAP);\n    this.byNodeStore.setValue(EMPTY_SUBGRAPH_BY_NODE_MAP);\n  }\n\n  #ensureShadow(\n    id: string,\n    namespace: readonly string[],\n    nodeName: string\n  ): MutableSubgraph {\n    let entry = this.#shadow.get(id);\n    if (entry == null) {\n      entry = {\n        id,\n        namespace: [...namespace],\n        nodeName,\n        status: \"running\",\n        startedAt: new Date(),\n        completedAt: null,\n      };\n      this.#shadow.set(id, entry);\n    }\n    return entry;\n  }\n\n  #commit(): void {\n    const snapshots: SubgraphDiscoverySnapshot[] = [];\n    for (const id of this.#promoted) {\n      const entry = this.#shadow.get(id);\n      // A namespace can be promoted before its own lifecycle event\n      // arrives if descendant events outpace the prefix event. Skip\n      // until the shadow entry lands; the next push() promoting or\n      // updating this namespace will re-commit.\n      if (entry == null) continue;\n      snapshots.push({ ...entry });\n    }\n\n    this.store.setValue(new Map(snapshots.map((s) => [s.id, s])));\n\n    const byNode = new Map<string, SubgraphDiscoverySnapshot[]>();\n    for (const snap of snapshots) {\n      const bucket = byNode.get(snap.nodeName);\n      if (bucket == null) byNode.set(snap.nodeName, [snap]);\n      else bucket.push(snap);\n    }\n    this.byNodeStore.setValue(byNode);\n  }\n}\n"],"mappings":"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"}