@langchain/langgraph-sdk 1.9.4 → 1.9.6

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 (110) hide show
  1. package/dist/client/base.cjs.map +1 -1
  2. package/dist/client/base.d.cts +37 -0
  3. package/dist/client/base.d.cts.map +1 -1
  4. package/dist/client/base.d.ts +37 -0
  5. package/dist/client/base.d.ts.map +1 -1
  6. package/dist/client/base.js.map +1 -1
  7. package/dist/client/index.d.cts +1 -1
  8. package/dist/client/index.d.ts +1 -1
  9. package/dist/client/stream/handles/index.d.ts +1 -1
  10. package/dist/client/stream/handles/subagents.cjs +1 -1
  11. package/dist/client/stream/handles/subagents.cjs.map +1 -1
  12. package/dist/client/stream/handles/subagents.d.cts +2 -2
  13. package/dist/client/stream/handles/subagents.d.cts.map +1 -1
  14. package/dist/client/stream/handles/subagents.d.ts +2 -2
  15. package/dist/client/stream/handles/subagents.d.ts.map +1 -1
  16. package/dist/client/stream/handles/subagents.js +2 -2
  17. package/dist/client/stream/handles/subagents.js.map +1 -1
  18. package/dist/client/stream/handles/subgraphs.cjs +1 -1
  19. package/dist/client/stream/handles/subgraphs.cjs.map +1 -1
  20. package/dist/client/stream/handles/subgraphs.d.cts +2 -2
  21. package/dist/client/stream/handles/subgraphs.d.cts.map +1 -1
  22. package/dist/client/stream/handles/subgraphs.d.ts +2 -2
  23. package/dist/client/stream/handles/subgraphs.d.ts.map +1 -1
  24. package/dist/client/stream/handles/subgraphs.js +2 -2
  25. package/dist/client/stream/handles/subgraphs.js.map +1 -1
  26. package/dist/client/stream/handles/tools.cjs +124 -52
  27. package/dist/client/stream/handles/tools.cjs.map +1 -1
  28. package/dist/client/stream/handles/tools.d.cts +59 -13
  29. package/dist/client/stream/handles/tools.d.cts.map +1 -1
  30. package/dist/client/stream/handles/tools.d.ts +59 -13
  31. package/dist/client/stream/handles/tools.d.ts.map +1 -1
  32. package/dist/client/stream/handles/tools.js +122 -53
  33. package/dist/client/stream/handles/tools.js.map +1 -1
  34. package/dist/client/stream/index.cjs +13 -5
  35. package/dist/client/stream/index.cjs.map +1 -1
  36. package/dist/client/stream/index.d.cts +3 -3
  37. package/dist/client/stream/index.d.cts.map +1 -1
  38. package/dist/client/stream/index.d.ts +3 -3
  39. package/dist/client/stream/index.d.ts.map +1 -1
  40. package/dist/client/stream/index.js +14 -6
  41. package/dist/client/stream/index.js.map +1 -1
  42. package/dist/headless-tools.cjs +131 -4
  43. package/dist/headless-tools.cjs.map +1 -1
  44. package/dist/headless-tools.d.cts +9 -1
  45. package/dist/headless-tools.d.cts.map +1 -1
  46. package/dist/headless-tools.d.ts +9 -1
  47. package/dist/headless-tools.d.ts.map +1 -1
  48. package/dist/headless-tools.js +129 -5
  49. package/dist/headless-tools.js.map +1 -1
  50. package/dist/index.cjs +1 -0
  51. package/dist/index.d.cts +3 -3
  52. package/dist/index.d.ts +3 -3
  53. package/dist/index.js +2 -2
  54. package/dist/stream/controller.cjs +77 -16
  55. package/dist/stream/controller.cjs.map +1 -1
  56. package/dist/stream/controller.d.cts +3 -1
  57. package/dist/stream/controller.d.cts.map +1 -1
  58. package/dist/stream/controller.d.ts +3 -1
  59. package/dist/stream/controller.d.ts.map +1 -1
  60. package/dist/stream/controller.js +78 -17
  61. package/dist/stream/controller.js.map +1 -1
  62. package/dist/stream/discovery/subagents.cjs +13 -0
  63. package/dist/stream/discovery/subagents.cjs.map +1 -1
  64. package/dist/stream/discovery/subagents.d.cts +5 -0
  65. package/dist/stream/discovery/subagents.d.cts.map +1 -1
  66. package/dist/stream/discovery/subagents.d.ts +5 -0
  67. package/dist/stream/discovery/subagents.d.ts.map +1 -1
  68. package/dist/stream/discovery/subagents.js +13 -0
  69. package/dist/stream/discovery/subagents.js.map +1 -1
  70. package/dist/stream/discovery/subgraphs.cjs +13 -0
  71. package/dist/stream/discovery/subgraphs.cjs.map +1 -1
  72. package/dist/stream/discovery/subgraphs.d.cts +5 -0
  73. package/dist/stream/discovery/subgraphs.d.cts.map +1 -1
  74. package/dist/stream/discovery/subgraphs.d.ts +5 -0
  75. package/dist/stream/discovery/subgraphs.d.ts.map +1 -1
  76. package/dist/stream/discovery/subgraphs.js +13 -0
  77. package/dist/stream/discovery/subgraphs.js.map +1 -1
  78. package/dist/stream/index.cjs +3 -0
  79. package/dist/stream/index.d.cts +5 -5
  80. package/dist/stream/index.d.ts +5 -5
  81. package/dist/stream/index.js +2 -1
  82. package/dist/stream/lifecycle-loading-tracker.cjs +1 -1
  83. package/dist/stream/lifecycle-loading-tracker.cjs.map +1 -1
  84. package/dist/stream/lifecycle-loading-tracker.js +1 -1
  85. package/dist/stream/lifecycle-loading-tracker.js.map +1 -1
  86. package/dist/stream/projections/tool-calls.cjs.map +1 -1
  87. package/dist/stream/projections/tool-calls.js.map +1 -1
  88. package/dist/stream/submit-coordinator.cjs +47 -16
  89. package/dist/stream/submit-coordinator.cjs.map +1 -1
  90. package/dist/stream/submit-coordinator.d.cts.map +1 -1
  91. package/dist/stream/submit-coordinator.d.ts.map +1 -1
  92. package/dist/stream/submit-coordinator.js +47 -16
  93. package/dist/stream/submit-coordinator.js.map +1 -1
  94. package/dist/stream/tool-calls.cjs +39 -2
  95. package/dist/stream/tool-calls.cjs.map +1 -1
  96. package/dist/stream/tool-calls.js +38 -3
  97. package/dist/stream/tool-calls.js.map +1 -1
  98. package/dist/stream/types-inference.d.cts +65 -7
  99. package/dist/stream/types-inference.d.cts.map +1 -1
  100. package/dist/stream/types-inference.d.ts +65 -7
  101. package/dist/stream/types-inference.d.ts.map +1 -1
  102. package/dist/stream/types.d.cts +42 -23
  103. package/dist/stream/types.d.cts.map +1 -1
  104. package/dist/stream/types.d.ts +42 -23
  105. package/dist/stream/types.d.ts.map +1 -1
  106. package/dist/types.messages.d.cts +38 -1
  107. package/dist/types.messages.d.cts.map +1 -1
  108. package/dist/types.messages.d.ts +38 -1
  109. package/dist/types.messages.d.ts.map +1 -1
  110. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"subagents.cjs","names":["StreamStore","#onToolEvent","#onValuesEvent","#upsertTaskToolCall","#commit","#map","#bindNamespaceByTaskInput","#recordObservedWorkNamespace","#recordTaskNamespaceCandidate","namespaceKey","isConcreteToolNamespace","#observedOwnNamespaces","#toolCallIdByTaskInput","#taskIdByObservedNamespace","isToolNamespaceSegment","isRootNamespace"],"sources":["../../../src/stream/discovery/subagents.ts"],"sourcesContent":["/**\n * Root-scoped subagent discovery.\n *\n * Populates a `Map<callId, SubagentDiscoverySnapshot>` by watching\n * `task` tool calls on the root subscription. No content channels\n * (subagent messages, tool calls, extensions) are opened here — that\n * layer is driven by selector hooks via the\n * {@link ChannelRegistry}, keyed on `SubagentDiscoverySnapshot.namespace`.\n *\n * Discovery data this runner populates per subagent:\n * - id, name, namespace, parentId, depth\n * - status (`running` | `complete` | `error`)\n * - taskInput / output / error / startedAt / completedAt\n *\n * The runner is fed events by the {@link StreamController}'s root\n * subscription; it does not open subscriptions of its own.\n */\nimport type { Event, ToolsEvent, ValuesEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"../store.js\";\nimport type { SubagentDiscoverySnapshot } from \"../types.js\";\nimport {\n isConcreteToolNamespace,\n isRootNamespace,\n isToolNamespaceSegment,\n namespaceKey,\n} from \"../namespace.js\";\n\nexport type SubagentMap = ReadonlyMap<string, SubagentDiscoverySnapshot>;\n\ninterface MutableSubagent {\n id: string;\n name: string;\n namespace: readonly string[];\n parentId: string | null;\n depth: number;\n status: \"running\" | \"complete\" | \"error\";\n taskInput: string | undefined;\n output: unknown;\n error: string | undefined;\n startedAt: Date;\n completedAt: Date | null;\n}\n\nexport class SubagentDiscovery {\n readonly store = new StreamStore<SubagentMap>(new Map());\n #map = new Map<string, MutableSubagent>();\n #taskIdByObservedNamespace = new Map<string, string>();\n #observedOwnNamespaces = new Set<string>();\n // Index from `taskInput` (the `description` arg) to a FIFO queue of\n // pending parent `tool_call_id`s. Bridges the wire's missing link\n // between a deepagents `task` dispatch and its subagent execution\n // namespace — the server seeds the subagent's first HumanMessage\n // with `taskInput` verbatim, so an exact-equality lookup is\n // deterministic. The queue (not a single value) handles the case\n // where the coordinator dispatches N task calls with identical\n // descriptions; pregel preserves dispatch order across executions,\n // so FIFO pop attributes them correctly.\n #toolCallIdByTaskInput = new Map<string, string[]>();\n\n /** Feed a single root event. Non-discovery events are ignored. */\n push(event: Event): void {\n if (event.method === \"tools\") {\n this.#onToolEvent(event as ToolsEvent);\n } else if (event.method === \"values\") {\n this.#onValuesEvent(event as ValuesEvent);\n }\n }\n\n /** Current snapshot map. */\n get snapshot(): SubagentMap {\n return this.store.getSnapshot();\n }\n\n discoverFromMessage(message: unknown, namespace: readonly string[]): void {\n let changed = false;\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) ||\n changed;\n }\n if (changed) this.#commit();\n }\n\n #commit(): void {\n // Rebuild as a fresh Map so React / useSyncExternalStore sees a\n // new reference on every change.\n this.store.setValue(\n new Map(\n [...this.#map.values()].map((entry) => [entry.id, toSnapshot(entry)])\n )\n );\n }\n\n #onToolEvent(event: ToolsEvent): void {\n const data = event.params.data;\n const toolCallId = (data as { tool_call_id?: string }).tool_call_id;\n const toolName = (data as { tool_name?: string }).tool_name;\n\n if (data.event === \"tool-started\" && toolName === \"task\") {\n const input = parseTaskInput((data as { input?: unknown }).input);\n if (toolCallId == null) return;\n this.#upsertTaskToolCall(toolCallId, input, event.params.namespace);\n this.#commit();\n return;\n }\n\n if (toolCallId == null) return;\n const entry = this.#map.get(toolCallId);\n if (entry == null) return;\n\n if (data.event === \"tool-finished\") {\n entry.status = \"complete\";\n entry.output = (data as { output?: unknown }).output;\n entry.completedAt = new Date();\n this.#commit();\n return;\n }\n\n if (data.event === \"tool-error\") {\n entry.status = \"error\";\n entry.error = (data as { message?: string }).message ?? \"Subagent failed\";\n entry.completedAt = new Date();\n this.#commit();\n }\n }\n\n #onValuesEvent(event: ValuesEvent): void {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) return;\n const messages = (data as { messages?: unknown }).messages;\n if (!Array.isArray(messages)) return;\n\n // If a `tools:<id>` namespace's first HumanMessage matches a known\n // taskInput, that's the subagent's execution scope. Record the\n // binding so `#recordObservedWorkNamespace` can promote it.\n this.#bindNamespaceByTaskInput(event.params.namespace, messages);\n\n let changed = this.#recordObservedWorkNamespace(event.params.namespace);\n for (const message of messages) {\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(\n toolCall.id,\n toolCall.input,\n event.params.namespace\n ) || changed;\n }\n\n const toolCallId = getToolMessageCallId(message);\n if (toolCallId == null) continue;\n const existing = this.#map.get(toolCallId);\n if (existing == null) continue;\n existing.status = \"complete\";\n existing.output = message;\n existing.completedAt = new Date();\n changed = true;\n }\n if (changed) this.#commit();\n }\n\n #upsertTaskToolCall(\n toolCallId: string,\n input: { description?: string; subagent_type?: string },\n eventNamespace: readonly string[]\n ): boolean {\n const namespace = taskWorkNamespace(toolCallId, eventNamespace);\n const existing = this.#map.get(toolCallId);\n if (existing != null) {\n let changed = false;\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n const nextName = input.subagent_type ?? existing.name;\n const nextTaskInput = input.description ?? existing.taskInput;\n if (existing.name !== nextName) {\n existing.name = nextName;\n changed = true;\n }\n if (existing.taskInput !== nextTaskInput) {\n existing.taskInput = nextTaskInput;\n changed = true;\n }\n const namespaceKeyed = namespaceKey(existing.namespace);\n const ownNamespaceKey = `tools:${toolCallId}`;\n const nextNamespaceKey = namespaceKey(namespace);\n if (\n isConcreteToolNamespace(eventNamespace) ||\n namespaceKeyed === ownNamespaceKey\n ) {\n // A wrapper task tool event can arrive under an execution namespace\n // like `tools:<uuid>`, while the subagent's actual message state is\n // under `tools:<tool_call_id>`. Once discovery has observed the own\n // namespace carrying state, do not demote it back to the wrapper\n // namespace.\n if (\n namespaceKeyed === ownNamespaceKey &&\n nextNamespaceKey !== ownNamespaceKey &&\n this.#observedOwnNamespaces.has(toolCallId)\n ) {\n return changed;\n }\n if (namespaceKeyed !== nextNamespaceKey) {\n existing.namespace = namespace;\n changed = true;\n }\n }\n if (existing.status !== \"complete\" && existing.status !== \"error\") {\n if (existing.status !== \"running\") {\n existing.status = \"running\";\n changed = true;\n }\n }\n return changed;\n }\n\n // Prefer the namespace where the task is first observed. Later\n // observations may move it between wrapper execution namespaces\n // and `[\"tools:<toolCallId>\"]`, depending on where the stream proves\n // the worker's scoped message/tool state exists.\n const { parentId, depth } = lineageFromNamespace(eventNamespace);\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n if (input.description != null) {\n const queue = this.#toolCallIdByTaskInput.get(input.description) ?? [];\n queue.push(toolCallId);\n this.#toolCallIdByTaskInput.set(input.description, queue);\n }\n this.#map.set(toolCallId, {\n id: toolCallId,\n name: input.subagent_type ?? \"unknown\",\n namespace,\n parentId,\n depth,\n status: \"running\",\n taskInput: input.description,\n output: undefined,\n error: undefined,\n startedAt: new Date(),\n completedAt: null,\n });\n return true;\n }\n\n #recordObservedWorkNamespace(namespace: readonly string[]): boolean {\n if (!isConcreteToolNamespace(namespace)) return false;\n const last = namespace.at(-1);\n if (last == null) return false;\n const namespaceKeyed = namespaceKey(namespace);\n const toolCallId =\n this.#taskIdByObservedNamespace.get(namespaceKeyed) ??\n last.slice(\"tools:\".length);\n const existing = this.#map.get(toolCallId);\n if (existing == null) return false;\n\n const ownNamespaceKey = `tools:${toolCallId}`;\n if (namespaceKeyed === ownNamespaceKey) {\n this.#observedOwnNamespaces.add(toolCallId);\n } else if (\n this.#observedOwnNamespaces.has(toolCallId) ||\n (!this.#taskIdByObservedNamespace.has(namespaceKeyed) &&\n !shouldPromoteToObservedNamespace(existing))\n ) {\n return false;\n }\n\n if (namespaceKey(existing.namespace) === namespaceKeyed) return false;\n existing.namespace = [...namespace];\n return true;\n }\n\n #recordTaskNamespaceCandidate(\n toolCallId: string,\n namespace: readonly string[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n this.#taskIdByObservedNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Bind a `tools:<id>` namespace to a registered subagent by looking\n * up the first HumanMessage content in the `taskInput` index.\n */\n #bindNamespaceByTaskInput(\n namespace: readonly string[],\n messages: unknown[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n const namespaceKeyed = namespaceKey(namespace);\n if (this.#taskIdByObservedNamespace.has(namespaceKeyed)) return;\n\n const text = getFirstHumanMessageText(messages);\n if (text == null) return;\n const toolCallId = this.#toolCallIdByTaskInput.get(text)?.shift();\n if (toolCallId == null) return;\n this.#taskIdByObservedNamespace.set(namespaceKeyed, toolCallId);\n }\n}\n\nfunction getFirstHumanMessageText(messages: unknown[]): string | null {\n for (const message of messages) {\n if (message == null || typeof message !== \"object\") continue;\n const record = message as {\n type?: unknown;\n role?: unknown;\n content?: unknown;\n kwargs?: { type?: unknown; content?: unknown };\n lc_kwargs?: { type?: unknown; content?: unknown };\n };\n const type =\n record.type ??\n record.role ??\n record.kwargs?.type ??\n record.lc_kwargs?.type;\n if (type !== \"human\") continue;\n const content =\n record.content ?? record.kwargs?.content ?? record.lc_kwargs?.content;\n return typeof content === \"string\" && content.length > 0 ? content : null;\n }\n return null;\n}\n\nfunction shouldPromoteToObservedNamespace(entry: MutableSubagent): boolean {\n return (\n entry.name === \"fanout-worker\" ||\n /^Worker worker-\\d+/i.test(entry.taskInput ?? \"\")\n );\n}\n\nfunction taskWorkNamespace(\n toolCallId: string,\n eventNamespace: readonly string[]\n): readonly string[] {\n const last = eventNamespace.at(-1);\n if (last != null && isToolNamespaceSegment(last)) return [...eventNamespace];\n return [`tools:${toolCallId}`];\n}\n\nfunction toSnapshot(entry: MutableSubagent): SubagentDiscoverySnapshot {\n return {\n id: entry.id,\n name: entry.name,\n namespace: entry.namespace,\n parentId: entry.parentId,\n depth: entry.depth,\n status: entry.status,\n taskInput: entry.taskInput,\n output: entry.output,\n error: entry.error,\n startedAt: entry.startedAt,\n completedAt: entry.completedAt,\n };\n}\n\nfunction parseTaskInput(raw: unknown): {\n description?: string;\n subagent_type?: string;\n} {\n if (raw == null) return {};\n if (typeof raw === \"string\") {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n return {\n description:\n typeof parsed.description === \"string\"\n ? parsed.description\n : undefined,\n subagent_type:\n typeof parsed.subagent_type === \"string\"\n ? parsed.subagent_type\n : undefined,\n };\n } catch {\n return {};\n }\n }\n if (typeof raw === \"object\" && !Array.isArray(raw)) {\n const obj = raw as Record<string, unknown>;\n return {\n description:\n typeof obj.description === \"string\" ? obj.description : undefined,\n subagent_type:\n typeof obj.subagent_type === \"string\" ? obj.subagent_type : undefined,\n };\n }\n return {};\n}\n\nfunction getTaskToolCalls(message: unknown): Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n}> {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return [];\n }\n const record = message as {\n tool_calls?: unknown;\n kwargs?: { tool_calls?: unknown };\n lc_kwargs?: { tool_calls?: unknown };\n };\n const toolCalls =\n record.tool_calls ??\n record.kwargs?.tool_calls ??\n record.lc_kwargs?.tool_calls;\n if (!Array.isArray(toolCalls)) return [];\n\n const result: Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n }> = [];\n for (const toolCall of toolCalls) {\n if (\n toolCall == null ||\n typeof toolCall !== \"object\" ||\n Array.isArray(toolCall)\n ) {\n continue;\n }\n const record = toolCall as {\n id?: unknown;\n name?: unknown;\n args?: unknown;\n };\n if (typeof record.id !== \"string\" || record.name !== \"task\") continue;\n result.push({ id: record.id, input: parseTaskInput(record.args) });\n }\n return result;\n}\n\nfunction getToolMessageCallId(message: unknown): string | undefined {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return undefined;\n }\n const record = message as {\n tool_call_id?: unknown;\n kwargs?: { tool_call_id?: unknown };\n lc_kwargs?: { tool_call_id?: unknown };\n };\n const id =\n record.tool_call_id ??\n record.kwargs?.tool_call_id ??\n record.lc_kwargs?.tool_call_id;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n}\n\n/**\n * Derive (parentId, depth) from a namespace like\n * `[\"subagents:abc:def\"]`. Namespaces form a rooted tree; the last\n * `:` segment of the deepest namespace element is the current node's\n * call-id and the one before it is the parent.\n */\nfunction lineageFromNamespace(namespace: readonly string[]): {\n parentId: string | null;\n depth: number;\n} {\n if (isRootNamespace(namespace)) return { parentId: null, depth: 1 };\n const last = namespace[namespace.length - 1];\n if (last == null) return { parentId: null, depth: 1 };\n // Namespace segments typically look like\n // subagents:<parentCallId>:<thisCallId>\n // but the protocol doesn't mandate that shape; we best-effort.\n const parts = last.split(\":\").filter((part) => part.length > 0);\n const trimmed = parts.slice(1); // drop the leading \"subagents\" prefix\n const depth = Math.max(1, trimmed.length);\n const parentId = trimmed.length >= 2 ? trimmed[trimmed.length - 2] : null;\n return { parentId: parentId ?? null, depth };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"subagents.cjs","names":["StreamStore","#onToolEvent","#onValuesEvent","#map","#taskIdByObservedNamespace","#observedOwnNamespaces","#toolCallIdByTaskInput","#upsertTaskToolCall","#commit","#bindNamespaceByTaskInput","#recordObservedWorkNamespace","#recordTaskNamespaceCandidate","namespaceKey","isConcreteToolNamespace","isToolNamespaceSegment","isRootNamespace"],"sources":["../../../src/stream/discovery/subagents.ts"],"sourcesContent":["/**\n * Root-scoped subagent discovery.\n *\n * Populates a `Map<callId, SubagentDiscoverySnapshot>` by watching\n * `task` tool calls on the root subscription. No content channels\n * (subagent messages, tool calls, extensions) are opened here — that\n * layer is driven by selector hooks via the\n * {@link ChannelRegistry}, keyed on `SubagentDiscoverySnapshot.namespace`.\n *\n * Discovery data this runner populates per subagent:\n * - id, name, namespace, parentId, depth\n * - status (`running` | `complete` | `error`)\n * - taskInput / output / error / startedAt / completedAt\n *\n * The runner is fed events by the {@link StreamController}'s root\n * subscription; it does not open subscriptions of its own.\n */\nimport type { Event, ToolsEvent, ValuesEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"../store.js\";\nimport type { SubagentDiscoverySnapshot } from \"../types.js\";\nimport {\n isConcreteToolNamespace,\n isRootNamespace,\n isToolNamespaceSegment,\n namespaceKey,\n} from \"../namespace.js\";\n\nexport type SubagentMap = ReadonlyMap<string, SubagentDiscoverySnapshot>;\n\n/** Stable empty map — reused on {@link SubagentDiscovery.reset}. */\nconst EMPTY_SUBAGENT_MAP: SubagentMap = new Map();\n\ninterface MutableSubagent {\n id: string;\n name: string;\n namespace: readonly string[];\n parentId: string | null;\n depth: number;\n status: \"running\" | \"complete\" | \"error\";\n taskInput: string | undefined;\n output: unknown;\n error: string | undefined;\n startedAt: Date;\n completedAt: Date | null;\n}\n\nexport class SubagentDiscovery {\n readonly store = new StreamStore<SubagentMap>(new Map());\n #map = new Map<string, MutableSubagent>();\n #taskIdByObservedNamespace = new Map<string, string>();\n #observedOwnNamespaces = new Set<string>();\n // Index from `taskInput` (the `description` arg) to a FIFO queue of\n // pending parent `tool_call_id`s. Bridges the wire's missing link\n // between a deepagents `task` dispatch and its subagent execution\n // namespace — the server seeds the subagent's first HumanMessage\n // with `taskInput` verbatim, so an exact-equality lookup is\n // deterministic. The queue (not a single value) handles the case\n // where the coordinator dispatches N task calls with identical\n // descriptions; pregel preserves dispatch order across executions,\n // so FIFO pop attributes them correctly.\n #toolCallIdByTaskInput = new Map<string, string[]>();\n\n /** Feed a single root event. Non-discovery events are ignored. */\n push(event: Event): void {\n if (event.method === \"tools\") {\n this.#onToolEvent(event as ToolsEvent);\n } else if (event.method === \"values\") {\n this.#onValuesEvent(event as ValuesEvent);\n }\n }\n\n /** Current snapshot map. */\n get snapshot(): SubagentMap {\n return this.store.getSnapshot();\n }\n\n /**\n * Drop all discovery state. Called on thread rebind / dispose so a\n * new thread's subagents cannot bleed into the previous UI.\n */\n reset(): void {\n this.#map.clear();\n this.#taskIdByObservedNamespace.clear();\n this.#observedOwnNamespaces.clear();\n this.#toolCallIdByTaskInput.clear();\n this.store.setValue(EMPTY_SUBAGENT_MAP);\n }\n\n discoverFromMessage(message: unknown, namespace: readonly string[]): void {\n let changed = false;\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) ||\n changed;\n }\n if (changed) this.#commit();\n }\n\n #commit(): void {\n // Rebuild as a fresh Map so React / useSyncExternalStore sees a\n // new reference on every change.\n this.store.setValue(\n new Map(\n [...this.#map.values()].map((entry) => [entry.id, toSnapshot(entry)])\n )\n );\n }\n\n #onToolEvent(event: ToolsEvent): void {\n const data = event.params.data;\n const toolCallId = (data as { tool_call_id?: string }).tool_call_id;\n const toolName = (data as { tool_name?: string }).tool_name;\n\n if (data.event === \"tool-started\" && toolName === \"task\") {\n const input = parseTaskInput((data as { input?: unknown }).input);\n if (toolCallId == null) return;\n this.#upsertTaskToolCall(toolCallId, input, event.params.namespace);\n this.#commit();\n return;\n }\n\n if (toolCallId == null) return;\n const entry = this.#map.get(toolCallId);\n if (entry == null) return;\n\n if (data.event === \"tool-finished\") {\n entry.status = \"complete\";\n entry.output = (data as { output?: unknown }).output;\n entry.completedAt = new Date();\n this.#commit();\n return;\n }\n\n if (data.event === \"tool-error\") {\n entry.status = \"error\";\n entry.error = (data as { message?: string }).message ?? \"Subagent failed\";\n entry.completedAt = new Date();\n this.#commit();\n }\n }\n\n #onValuesEvent(event: ValuesEvent): void {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) return;\n const messages = (data as { messages?: unknown }).messages;\n if (!Array.isArray(messages)) return;\n\n // If a `tools:<id>` namespace's first HumanMessage matches a known\n // taskInput, that's the subagent's execution scope. Record the\n // binding so `#recordObservedWorkNamespace` can promote it.\n this.#bindNamespaceByTaskInput(event.params.namespace, messages);\n\n let changed = this.#recordObservedWorkNamespace(event.params.namespace);\n for (const message of messages) {\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(\n toolCall.id,\n toolCall.input,\n event.params.namespace\n ) || changed;\n }\n\n const toolCallId = getToolMessageCallId(message);\n if (toolCallId == null) continue;\n const existing = this.#map.get(toolCallId);\n if (existing == null) continue;\n existing.status = \"complete\";\n existing.output = message;\n existing.completedAt = new Date();\n changed = true;\n }\n if (changed) this.#commit();\n }\n\n #upsertTaskToolCall(\n toolCallId: string,\n input: { description?: string; subagent_type?: string },\n eventNamespace: readonly string[]\n ): boolean {\n const namespace = taskWorkNamespace(toolCallId, eventNamespace);\n const existing = this.#map.get(toolCallId);\n if (existing != null) {\n let changed = false;\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n const nextName = input.subagent_type ?? existing.name;\n const nextTaskInput = input.description ?? existing.taskInput;\n if (existing.name !== nextName) {\n existing.name = nextName;\n changed = true;\n }\n if (existing.taskInput !== nextTaskInput) {\n existing.taskInput = nextTaskInput;\n changed = true;\n }\n const namespaceKeyed = namespaceKey(existing.namespace);\n const ownNamespaceKey = `tools:${toolCallId}`;\n const nextNamespaceKey = namespaceKey(namespace);\n if (\n isConcreteToolNamespace(eventNamespace) ||\n namespaceKeyed === ownNamespaceKey\n ) {\n // A wrapper task tool event can arrive under an execution namespace\n // like `tools:<uuid>`, while the subagent's actual message state is\n // under `tools:<tool_call_id>`. Once discovery has observed the own\n // namespace carrying state, do not demote it back to the wrapper\n // namespace.\n if (\n namespaceKeyed === ownNamespaceKey &&\n nextNamespaceKey !== ownNamespaceKey &&\n this.#observedOwnNamespaces.has(toolCallId)\n ) {\n return changed;\n }\n if (namespaceKeyed !== nextNamespaceKey) {\n existing.namespace = namespace;\n changed = true;\n }\n }\n if (existing.status !== \"complete\" && existing.status !== \"error\") {\n if (existing.status !== \"running\") {\n existing.status = \"running\";\n changed = true;\n }\n }\n return changed;\n }\n\n // Prefer the namespace where the task is first observed. Later\n // observations may move it between wrapper execution namespaces\n // and `[\"tools:<toolCallId>\"]`, depending on where the stream proves\n // the worker's scoped message/tool state exists.\n const { parentId, depth } = lineageFromNamespace(eventNamespace);\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n if (input.description != null) {\n const queue = this.#toolCallIdByTaskInput.get(input.description) ?? [];\n queue.push(toolCallId);\n this.#toolCallIdByTaskInput.set(input.description, queue);\n }\n this.#map.set(toolCallId, {\n id: toolCallId,\n name: input.subagent_type ?? \"unknown\",\n namespace,\n parentId,\n depth,\n status: \"running\",\n taskInput: input.description,\n output: undefined,\n error: undefined,\n startedAt: new Date(),\n completedAt: null,\n });\n return true;\n }\n\n #recordObservedWorkNamespace(namespace: readonly string[]): boolean {\n if (!isConcreteToolNamespace(namespace)) return false;\n const last = namespace.at(-1);\n if (last == null) return false;\n const namespaceKeyed = namespaceKey(namespace);\n const toolCallId =\n this.#taskIdByObservedNamespace.get(namespaceKeyed) ??\n last.slice(\"tools:\".length);\n const existing = this.#map.get(toolCallId);\n if (existing == null) return false;\n\n const ownNamespaceKey = `tools:${toolCallId}`;\n if (namespaceKeyed === ownNamespaceKey) {\n this.#observedOwnNamespaces.add(toolCallId);\n } else if (\n this.#observedOwnNamespaces.has(toolCallId) ||\n (!this.#taskIdByObservedNamespace.has(namespaceKeyed) &&\n !shouldPromoteToObservedNamespace(existing))\n ) {\n return false;\n }\n\n if (namespaceKey(existing.namespace) === namespaceKeyed) return false;\n existing.namespace = [...namespace];\n return true;\n }\n\n #recordTaskNamespaceCandidate(\n toolCallId: string,\n namespace: readonly string[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n this.#taskIdByObservedNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Bind a `tools:<id>` namespace to a registered subagent by looking\n * up the first HumanMessage content in the `taskInput` index.\n */\n #bindNamespaceByTaskInput(\n namespace: readonly string[],\n messages: unknown[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n const namespaceKeyed = namespaceKey(namespace);\n if (this.#taskIdByObservedNamespace.has(namespaceKeyed)) return;\n\n const text = getFirstHumanMessageText(messages);\n if (text == null) return;\n const toolCallId = this.#toolCallIdByTaskInput.get(text)?.shift();\n if (toolCallId == null) return;\n this.#taskIdByObservedNamespace.set(namespaceKeyed, toolCallId);\n }\n}\n\nfunction getFirstHumanMessageText(messages: unknown[]): string | null {\n for (const message of messages) {\n if (message == null || typeof message !== \"object\") continue;\n const record = message as {\n type?: unknown;\n role?: unknown;\n content?: unknown;\n kwargs?: { type?: unknown; content?: unknown };\n lc_kwargs?: { type?: unknown; content?: unknown };\n };\n const type =\n record.type ??\n record.role ??\n record.kwargs?.type ??\n record.lc_kwargs?.type;\n if (type !== \"human\") continue;\n const content =\n record.content ?? record.kwargs?.content ?? record.lc_kwargs?.content;\n return typeof content === \"string\" && content.length > 0 ? content : null;\n }\n return null;\n}\n\nfunction shouldPromoteToObservedNamespace(entry: MutableSubagent): boolean {\n return (\n entry.name === \"fanout-worker\" ||\n /^Worker worker-\\d+/i.test(entry.taskInput ?? \"\")\n );\n}\n\nfunction taskWorkNamespace(\n toolCallId: string,\n eventNamespace: readonly string[]\n): readonly string[] {\n const last = eventNamespace.at(-1);\n if (last != null && isToolNamespaceSegment(last)) return [...eventNamespace];\n return [`tools:${toolCallId}`];\n}\n\nfunction toSnapshot(entry: MutableSubagent): SubagentDiscoverySnapshot {\n return {\n id: entry.id,\n name: entry.name,\n namespace: entry.namespace,\n parentId: entry.parentId,\n depth: entry.depth,\n status: entry.status,\n taskInput: entry.taskInput,\n output: entry.output,\n error: entry.error,\n startedAt: entry.startedAt,\n completedAt: entry.completedAt,\n };\n}\n\nfunction parseTaskInput(raw: unknown): {\n description?: string;\n subagent_type?: string;\n} {\n if (raw == null) return {};\n if (typeof raw === \"string\") {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n return {\n description:\n typeof parsed.description === \"string\"\n ? parsed.description\n : undefined,\n subagent_type:\n typeof parsed.subagent_type === \"string\"\n ? parsed.subagent_type\n : undefined,\n };\n } catch {\n return {};\n }\n }\n if (typeof raw === \"object\" && !Array.isArray(raw)) {\n const obj = raw as Record<string, unknown>;\n return {\n description:\n typeof obj.description === \"string\" ? obj.description : undefined,\n subagent_type:\n typeof obj.subagent_type === \"string\" ? obj.subagent_type : undefined,\n };\n }\n return {};\n}\n\nfunction getTaskToolCalls(message: unknown): Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n}> {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return [];\n }\n const record = message as {\n tool_calls?: unknown;\n kwargs?: { tool_calls?: unknown };\n lc_kwargs?: { tool_calls?: unknown };\n };\n const toolCalls =\n record.tool_calls ??\n record.kwargs?.tool_calls ??\n record.lc_kwargs?.tool_calls;\n if (!Array.isArray(toolCalls)) return [];\n\n const result: Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n }> = [];\n for (const toolCall of toolCalls) {\n if (\n toolCall == null ||\n typeof toolCall !== \"object\" ||\n Array.isArray(toolCall)\n ) {\n continue;\n }\n const record = toolCall as {\n id?: unknown;\n name?: unknown;\n args?: unknown;\n };\n if (typeof record.id !== \"string\" || record.name !== \"task\") continue;\n result.push({ id: record.id, input: parseTaskInput(record.args) });\n }\n return result;\n}\n\nfunction getToolMessageCallId(message: unknown): string | undefined {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return undefined;\n }\n const record = message as {\n tool_call_id?: unknown;\n kwargs?: { tool_call_id?: unknown };\n lc_kwargs?: { tool_call_id?: unknown };\n };\n const id =\n record.tool_call_id ??\n record.kwargs?.tool_call_id ??\n record.lc_kwargs?.tool_call_id;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n}\n\n/**\n * Derive (parentId, depth) from a namespace like\n * `[\"subagents:abc:def\"]`. Namespaces form a rooted tree; the last\n * `:` segment of the deepest namespace element is the current node's\n * call-id and the one before it is the parent.\n */\nfunction lineageFromNamespace(namespace: readonly string[]): {\n parentId: string | null;\n depth: number;\n} {\n if (isRootNamespace(namespace)) return { parentId: null, depth: 1 };\n const last = namespace[namespace.length - 1];\n if (last == null) return { parentId: null, depth: 1 };\n // Namespace segments typically look like\n // subagents:<parentCallId>:<thisCallId>\n // but the protocol doesn't mandate that shape; we best-effort.\n const parts = last.split(\":\").filter((part) => part.length > 0);\n const trimmed = parts.slice(1); // drop the leading \"subagents\" prefix\n const depth = Math.max(1, trimmed.length);\n const parentId = trimmed.length >= 2 ? trimmed[trimmed.length - 2] : null;\n return { parentId: parentId ?? null, depth };\n}\n"],"mappings":"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"}
@@ -11,6 +11,11 @@ declare class SubagentDiscovery {
11
11
  push(event: Event): void;
12
12
  /** Current snapshot map. */
13
13
  get snapshot(): SubagentMap;
14
+ /**
15
+ * Drop all discovery state. Called on thread rebind / dispose so a
16
+ * new thread's subagents cannot bleed into the previous UI.
17
+ */
18
+ reset(): void;
14
19
  discoverFromMessage(message: unknown, namespace: readonly string[]): void;
15
20
  }
16
21
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"subagents.d.cts","names":[],"sources":["../../../src/stream/discovery/subagents.ts"],"mappings":";;;;;KA2BY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,cAgBjC,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAgBF;EAAZ,IAAA,CAAK,KAAA,EAAO,KAAA;EASR;EAAA,IAAA,QAAA,CAAA,GAAY,WAAA;EAIhB,mBAAA,CAAoB,OAAA,WAAkB,SAAA;AAAA"}
1
+ {"version":3,"file":"subagents.d.cts","names":[],"sources":["../../../src/stream/discovery/subagents.ts"],"mappings":";;;;;KA2BY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,cAmBjC,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAgBF;EAAZ,IAAA,CAAK,KAAA,EAAO,KAAA;EASR;EAAA,IAAA,QAAA,CAAA,GAAY,WAAA;EAQhB;;;;EAAA,KAAA,CAAA;EAQA,mBAAA,CAAoB,OAAA,WAAkB,SAAA;AAAA"}
@@ -11,6 +11,11 @@ declare class SubagentDiscovery {
11
11
  push(event: Event): void;
12
12
  /** Current snapshot map. */
13
13
  get snapshot(): SubagentMap;
14
+ /**
15
+ * Drop all discovery state. Called on thread rebind / dispose so a
16
+ * new thread's subagents cannot bleed into the previous UI.
17
+ */
18
+ reset(): void;
14
19
  discoverFromMessage(message: unknown, namespace: readonly string[]): void;
15
20
  }
16
21
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"subagents.d.ts","names":[],"sources":["../../../src/stream/discovery/subagents.ts"],"mappings":";;;;;KA2BY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,cAgBjC,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAgBF;EAAZ,IAAA,CAAK,KAAA,EAAO,KAAA;EASR;EAAA,IAAA,QAAA,CAAA,GAAY,WAAA;EAIhB,mBAAA,CAAoB,OAAA,WAAkB,SAAA;AAAA"}
1
+ {"version":3,"file":"subagents.d.ts","names":[],"sources":["../../../src/stream/discovery/subagents.ts"],"mappings":";;;;;KA2BY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,cAmBjC,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAgBF;EAAZ,IAAA,CAAK,KAAA,EAAO,KAAA;EASR;EAAA,IAAA,QAAA,CAAA,GAAY,WAAA;EAQhB;;;;EAAA,KAAA,CAAA;EAQA,mBAAA,CAAoB,OAAA,WAAkB,SAAA;AAAA"}
@@ -1,6 +1,8 @@
1
1
  import { StreamStore } from "../store.js";
2
2
  import { isConcreteToolNamespace, isRootNamespace, isToolNamespaceSegment, namespaceKey } from "../namespace.js";
3
3
  //#region src/stream/discovery/subagents.ts
4
+ /** Stable empty map — reused on {@link SubagentDiscovery.reset}. */
5
+ const EMPTY_SUBAGENT_MAP = /* @__PURE__ */ new Map();
4
6
  var SubagentDiscovery = class {
5
7
  store = new StreamStore(/* @__PURE__ */ new Map());
6
8
  #map = /* @__PURE__ */ new Map();
@@ -16,6 +18,17 @@ var SubagentDiscovery = class {
16
18
  get snapshot() {
17
19
  return this.store.getSnapshot();
18
20
  }
21
+ /**
22
+ * Drop all discovery state. Called on thread rebind / dispose so a
23
+ * new thread's subagents cannot bleed into the previous UI.
24
+ */
25
+ reset() {
26
+ this.#map.clear();
27
+ this.#taskIdByObservedNamespace.clear();
28
+ this.#observedOwnNamespaces.clear();
29
+ this.#toolCallIdByTaskInput.clear();
30
+ this.store.setValue(EMPTY_SUBAGENT_MAP);
31
+ }
19
32
  discoverFromMessage(message, namespace) {
20
33
  let changed = false;
21
34
  for (const toolCall of getTaskToolCalls(message)) changed = this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) || changed;
@@ -1 +1 @@
1
- {"version":3,"file":"subagents.js","names":["#onToolEvent","#onValuesEvent","#upsertTaskToolCall","#commit","#map","#bindNamespaceByTaskInput","#recordObservedWorkNamespace","#recordTaskNamespaceCandidate","#observedOwnNamespaces","#toolCallIdByTaskInput","#taskIdByObservedNamespace"],"sources":["../../../src/stream/discovery/subagents.ts"],"sourcesContent":["/**\n * Root-scoped subagent discovery.\n *\n * Populates a `Map<callId, SubagentDiscoverySnapshot>` by watching\n * `task` tool calls on the root subscription. No content channels\n * (subagent messages, tool calls, extensions) are opened here — that\n * layer is driven by selector hooks via the\n * {@link ChannelRegistry}, keyed on `SubagentDiscoverySnapshot.namespace`.\n *\n * Discovery data this runner populates per subagent:\n * - id, name, namespace, parentId, depth\n * - status (`running` | `complete` | `error`)\n * - taskInput / output / error / startedAt / completedAt\n *\n * The runner is fed events by the {@link StreamController}'s root\n * subscription; it does not open subscriptions of its own.\n */\nimport type { Event, ToolsEvent, ValuesEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"../store.js\";\nimport type { SubagentDiscoverySnapshot } from \"../types.js\";\nimport {\n isConcreteToolNamespace,\n isRootNamespace,\n isToolNamespaceSegment,\n namespaceKey,\n} from \"../namespace.js\";\n\nexport type SubagentMap = ReadonlyMap<string, SubagentDiscoverySnapshot>;\n\ninterface MutableSubagent {\n id: string;\n name: string;\n namespace: readonly string[];\n parentId: string | null;\n depth: number;\n status: \"running\" | \"complete\" | \"error\";\n taskInput: string | undefined;\n output: unknown;\n error: string | undefined;\n startedAt: Date;\n completedAt: Date | null;\n}\n\nexport class SubagentDiscovery {\n readonly store = new StreamStore<SubagentMap>(new Map());\n #map = new Map<string, MutableSubagent>();\n #taskIdByObservedNamespace = new Map<string, string>();\n #observedOwnNamespaces = new Set<string>();\n // Index from `taskInput` (the `description` arg) to a FIFO queue of\n // pending parent `tool_call_id`s. Bridges the wire's missing link\n // between a deepagents `task` dispatch and its subagent execution\n // namespace — the server seeds the subagent's first HumanMessage\n // with `taskInput` verbatim, so an exact-equality lookup is\n // deterministic. The queue (not a single value) handles the case\n // where the coordinator dispatches N task calls with identical\n // descriptions; pregel preserves dispatch order across executions,\n // so FIFO pop attributes them correctly.\n #toolCallIdByTaskInput = new Map<string, string[]>();\n\n /** Feed a single root event. Non-discovery events are ignored. */\n push(event: Event): void {\n if (event.method === \"tools\") {\n this.#onToolEvent(event as ToolsEvent);\n } else if (event.method === \"values\") {\n this.#onValuesEvent(event as ValuesEvent);\n }\n }\n\n /** Current snapshot map. */\n get snapshot(): SubagentMap {\n return this.store.getSnapshot();\n }\n\n discoverFromMessage(message: unknown, namespace: readonly string[]): void {\n let changed = false;\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) ||\n changed;\n }\n if (changed) this.#commit();\n }\n\n #commit(): void {\n // Rebuild as a fresh Map so React / useSyncExternalStore sees a\n // new reference on every change.\n this.store.setValue(\n new Map(\n [...this.#map.values()].map((entry) => [entry.id, toSnapshot(entry)])\n )\n );\n }\n\n #onToolEvent(event: ToolsEvent): void {\n const data = event.params.data;\n const toolCallId = (data as { tool_call_id?: string }).tool_call_id;\n const toolName = (data as { tool_name?: string }).tool_name;\n\n if (data.event === \"tool-started\" && toolName === \"task\") {\n const input = parseTaskInput((data as { input?: unknown }).input);\n if (toolCallId == null) return;\n this.#upsertTaskToolCall(toolCallId, input, event.params.namespace);\n this.#commit();\n return;\n }\n\n if (toolCallId == null) return;\n const entry = this.#map.get(toolCallId);\n if (entry == null) return;\n\n if (data.event === \"tool-finished\") {\n entry.status = \"complete\";\n entry.output = (data as { output?: unknown }).output;\n entry.completedAt = new Date();\n this.#commit();\n return;\n }\n\n if (data.event === \"tool-error\") {\n entry.status = \"error\";\n entry.error = (data as { message?: string }).message ?? \"Subagent failed\";\n entry.completedAt = new Date();\n this.#commit();\n }\n }\n\n #onValuesEvent(event: ValuesEvent): void {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) return;\n const messages = (data as { messages?: unknown }).messages;\n if (!Array.isArray(messages)) return;\n\n // If a `tools:<id>` namespace's first HumanMessage matches a known\n // taskInput, that's the subagent's execution scope. Record the\n // binding so `#recordObservedWorkNamespace` can promote it.\n this.#bindNamespaceByTaskInput(event.params.namespace, messages);\n\n let changed = this.#recordObservedWorkNamespace(event.params.namespace);\n for (const message of messages) {\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(\n toolCall.id,\n toolCall.input,\n event.params.namespace\n ) || changed;\n }\n\n const toolCallId = getToolMessageCallId(message);\n if (toolCallId == null) continue;\n const existing = this.#map.get(toolCallId);\n if (existing == null) continue;\n existing.status = \"complete\";\n existing.output = message;\n existing.completedAt = new Date();\n changed = true;\n }\n if (changed) this.#commit();\n }\n\n #upsertTaskToolCall(\n toolCallId: string,\n input: { description?: string; subagent_type?: string },\n eventNamespace: readonly string[]\n ): boolean {\n const namespace = taskWorkNamespace(toolCallId, eventNamespace);\n const existing = this.#map.get(toolCallId);\n if (existing != null) {\n let changed = false;\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n const nextName = input.subagent_type ?? existing.name;\n const nextTaskInput = input.description ?? existing.taskInput;\n if (existing.name !== nextName) {\n existing.name = nextName;\n changed = true;\n }\n if (existing.taskInput !== nextTaskInput) {\n existing.taskInput = nextTaskInput;\n changed = true;\n }\n const namespaceKeyed = namespaceKey(existing.namespace);\n const ownNamespaceKey = `tools:${toolCallId}`;\n const nextNamespaceKey = namespaceKey(namespace);\n if (\n isConcreteToolNamespace(eventNamespace) ||\n namespaceKeyed === ownNamespaceKey\n ) {\n // A wrapper task tool event can arrive under an execution namespace\n // like `tools:<uuid>`, while the subagent's actual message state is\n // under `tools:<tool_call_id>`. Once discovery has observed the own\n // namespace carrying state, do not demote it back to the wrapper\n // namespace.\n if (\n namespaceKeyed === ownNamespaceKey &&\n nextNamespaceKey !== ownNamespaceKey &&\n this.#observedOwnNamespaces.has(toolCallId)\n ) {\n return changed;\n }\n if (namespaceKeyed !== nextNamespaceKey) {\n existing.namespace = namespace;\n changed = true;\n }\n }\n if (existing.status !== \"complete\" && existing.status !== \"error\") {\n if (existing.status !== \"running\") {\n existing.status = \"running\";\n changed = true;\n }\n }\n return changed;\n }\n\n // Prefer the namespace where the task is first observed. Later\n // observations may move it between wrapper execution namespaces\n // and `[\"tools:<toolCallId>\"]`, depending on where the stream proves\n // the worker's scoped message/tool state exists.\n const { parentId, depth } = lineageFromNamespace(eventNamespace);\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n if (input.description != null) {\n const queue = this.#toolCallIdByTaskInput.get(input.description) ?? [];\n queue.push(toolCallId);\n this.#toolCallIdByTaskInput.set(input.description, queue);\n }\n this.#map.set(toolCallId, {\n id: toolCallId,\n name: input.subagent_type ?? \"unknown\",\n namespace,\n parentId,\n depth,\n status: \"running\",\n taskInput: input.description,\n output: undefined,\n error: undefined,\n startedAt: new Date(),\n completedAt: null,\n });\n return true;\n }\n\n #recordObservedWorkNamespace(namespace: readonly string[]): boolean {\n if (!isConcreteToolNamespace(namespace)) return false;\n const last = namespace.at(-1);\n if (last == null) return false;\n const namespaceKeyed = namespaceKey(namespace);\n const toolCallId =\n this.#taskIdByObservedNamespace.get(namespaceKeyed) ??\n last.slice(\"tools:\".length);\n const existing = this.#map.get(toolCallId);\n if (existing == null) return false;\n\n const ownNamespaceKey = `tools:${toolCallId}`;\n if (namespaceKeyed === ownNamespaceKey) {\n this.#observedOwnNamespaces.add(toolCallId);\n } else if (\n this.#observedOwnNamespaces.has(toolCallId) ||\n (!this.#taskIdByObservedNamespace.has(namespaceKeyed) &&\n !shouldPromoteToObservedNamespace(existing))\n ) {\n return false;\n }\n\n if (namespaceKey(existing.namespace) === namespaceKeyed) return false;\n existing.namespace = [...namespace];\n return true;\n }\n\n #recordTaskNamespaceCandidate(\n toolCallId: string,\n namespace: readonly string[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n this.#taskIdByObservedNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Bind a `tools:<id>` namespace to a registered subagent by looking\n * up the first HumanMessage content in the `taskInput` index.\n */\n #bindNamespaceByTaskInput(\n namespace: readonly string[],\n messages: unknown[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n const namespaceKeyed = namespaceKey(namespace);\n if (this.#taskIdByObservedNamespace.has(namespaceKeyed)) return;\n\n const text = getFirstHumanMessageText(messages);\n if (text == null) return;\n const toolCallId = this.#toolCallIdByTaskInput.get(text)?.shift();\n if (toolCallId == null) return;\n this.#taskIdByObservedNamespace.set(namespaceKeyed, toolCallId);\n }\n}\n\nfunction getFirstHumanMessageText(messages: unknown[]): string | null {\n for (const message of messages) {\n if (message == null || typeof message !== \"object\") continue;\n const record = message as {\n type?: unknown;\n role?: unknown;\n content?: unknown;\n kwargs?: { type?: unknown; content?: unknown };\n lc_kwargs?: { type?: unknown; content?: unknown };\n };\n const type =\n record.type ??\n record.role ??\n record.kwargs?.type ??\n record.lc_kwargs?.type;\n if (type !== \"human\") continue;\n const content =\n record.content ?? record.kwargs?.content ?? record.lc_kwargs?.content;\n return typeof content === \"string\" && content.length > 0 ? content : null;\n }\n return null;\n}\n\nfunction shouldPromoteToObservedNamespace(entry: MutableSubagent): boolean {\n return (\n entry.name === \"fanout-worker\" ||\n /^Worker worker-\\d+/i.test(entry.taskInput ?? \"\")\n );\n}\n\nfunction taskWorkNamespace(\n toolCallId: string,\n eventNamespace: readonly string[]\n): readonly string[] {\n const last = eventNamespace.at(-1);\n if (last != null && isToolNamespaceSegment(last)) return [...eventNamespace];\n return [`tools:${toolCallId}`];\n}\n\nfunction toSnapshot(entry: MutableSubagent): SubagentDiscoverySnapshot {\n return {\n id: entry.id,\n name: entry.name,\n namespace: entry.namespace,\n parentId: entry.parentId,\n depth: entry.depth,\n status: entry.status,\n taskInput: entry.taskInput,\n output: entry.output,\n error: entry.error,\n startedAt: entry.startedAt,\n completedAt: entry.completedAt,\n };\n}\n\nfunction parseTaskInput(raw: unknown): {\n description?: string;\n subagent_type?: string;\n} {\n if (raw == null) return {};\n if (typeof raw === \"string\") {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n return {\n description:\n typeof parsed.description === \"string\"\n ? parsed.description\n : undefined,\n subagent_type:\n typeof parsed.subagent_type === \"string\"\n ? parsed.subagent_type\n : undefined,\n };\n } catch {\n return {};\n }\n }\n if (typeof raw === \"object\" && !Array.isArray(raw)) {\n const obj = raw as Record<string, unknown>;\n return {\n description:\n typeof obj.description === \"string\" ? obj.description : undefined,\n subagent_type:\n typeof obj.subagent_type === \"string\" ? obj.subagent_type : undefined,\n };\n }\n return {};\n}\n\nfunction getTaskToolCalls(message: unknown): Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n}> {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return [];\n }\n const record = message as {\n tool_calls?: unknown;\n kwargs?: { tool_calls?: unknown };\n lc_kwargs?: { tool_calls?: unknown };\n };\n const toolCalls =\n record.tool_calls ??\n record.kwargs?.tool_calls ??\n record.lc_kwargs?.tool_calls;\n if (!Array.isArray(toolCalls)) return [];\n\n const result: Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n }> = [];\n for (const toolCall of toolCalls) {\n if (\n toolCall == null ||\n typeof toolCall !== \"object\" ||\n Array.isArray(toolCall)\n ) {\n continue;\n }\n const record = toolCall as {\n id?: unknown;\n name?: unknown;\n args?: unknown;\n };\n if (typeof record.id !== \"string\" || record.name !== \"task\") continue;\n result.push({ id: record.id, input: parseTaskInput(record.args) });\n }\n return result;\n}\n\nfunction getToolMessageCallId(message: unknown): string | undefined {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return undefined;\n }\n const record = message as {\n tool_call_id?: unknown;\n kwargs?: { tool_call_id?: unknown };\n lc_kwargs?: { tool_call_id?: unknown };\n };\n const id =\n record.tool_call_id ??\n record.kwargs?.tool_call_id ??\n record.lc_kwargs?.tool_call_id;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n}\n\n/**\n * Derive (parentId, depth) from a namespace like\n * `[\"subagents:abc:def\"]`. Namespaces form a rooted tree; the last\n * `:` segment of the deepest namespace element is the current node's\n * call-id and the one before it is the parent.\n */\nfunction lineageFromNamespace(namespace: readonly string[]): {\n parentId: string | null;\n depth: number;\n} {\n if (isRootNamespace(namespace)) return { parentId: null, depth: 1 };\n const last = namespace[namespace.length - 1];\n if (last == null) return { parentId: null, depth: 1 };\n // Namespace segments typically look like\n // subagents:<parentCallId>:<thisCallId>\n // but the protocol doesn't mandate that shape; we best-effort.\n const parts = last.split(\":\").filter((part) => part.length > 0);\n const trimmed = parts.slice(1); // drop the leading \"subagents\" prefix\n const depth = Math.max(1, trimmed.length);\n const parentId = trimmed.length >= 2 ? trimmed[trimmed.length - 2] : null;\n return { parentId: parentId ?? null, depth };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"subagents.js","names":["#onToolEvent","#onValuesEvent","#map","#taskIdByObservedNamespace","#observedOwnNamespaces","#toolCallIdByTaskInput","#upsertTaskToolCall","#commit","#bindNamespaceByTaskInput","#recordObservedWorkNamespace","#recordTaskNamespaceCandidate"],"sources":["../../../src/stream/discovery/subagents.ts"],"sourcesContent":["/**\n * Root-scoped subagent discovery.\n *\n * Populates a `Map<callId, SubagentDiscoverySnapshot>` by watching\n * `task` tool calls on the root subscription. No content channels\n * (subagent messages, tool calls, extensions) are opened here — that\n * layer is driven by selector hooks via the\n * {@link ChannelRegistry}, keyed on `SubagentDiscoverySnapshot.namespace`.\n *\n * Discovery data this runner populates per subagent:\n * - id, name, namespace, parentId, depth\n * - status (`running` | `complete` | `error`)\n * - taskInput / output / error / startedAt / completedAt\n *\n * The runner is fed events by the {@link StreamController}'s root\n * subscription; it does not open subscriptions of its own.\n */\nimport type { Event, ToolsEvent, ValuesEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"../store.js\";\nimport type { SubagentDiscoverySnapshot } from \"../types.js\";\nimport {\n isConcreteToolNamespace,\n isRootNamespace,\n isToolNamespaceSegment,\n namespaceKey,\n} from \"../namespace.js\";\n\nexport type SubagentMap = ReadonlyMap<string, SubagentDiscoverySnapshot>;\n\n/** Stable empty map — reused on {@link SubagentDiscovery.reset}. */\nconst EMPTY_SUBAGENT_MAP: SubagentMap = new Map();\n\ninterface MutableSubagent {\n id: string;\n name: string;\n namespace: readonly string[];\n parentId: string | null;\n depth: number;\n status: \"running\" | \"complete\" | \"error\";\n taskInput: string | undefined;\n output: unknown;\n error: string | undefined;\n startedAt: Date;\n completedAt: Date | null;\n}\n\nexport class SubagentDiscovery {\n readonly store = new StreamStore<SubagentMap>(new Map());\n #map = new Map<string, MutableSubagent>();\n #taskIdByObservedNamespace = new Map<string, string>();\n #observedOwnNamespaces = new Set<string>();\n // Index from `taskInput` (the `description` arg) to a FIFO queue of\n // pending parent `tool_call_id`s. Bridges the wire's missing link\n // between a deepagents `task` dispatch and its subagent execution\n // namespace — the server seeds the subagent's first HumanMessage\n // with `taskInput` verbatim, so an exact-equality lookup is\n // deterministic. The queue (not a single value) handles the case\n // where the coordinator dispatches N task calls with identical\n // descriptions; pregel preserves dispatch order across executions,\n // so FIFO pop attributes them correctly.\n #toolCallIdByTaskInput = new Map<string, string[]>();\n\n /** Feed a single root event. Non-discovery events are ignored. */\n push(event: Event): void {\n if (event.method === \"tools\") {\n this.#onToolEvent(event as ToolsEvent);\n } else if (event.method === \"values\") {\n this.#onValuesEvent(event as ValuesEvent);\n }\n }\n\n /** Current snapshot map. */\n get snapshot(): SubagentMap {\n return this.store.getSnapshot();\n }\n\n /**\n * Drop all discovery state. Called on thread rebind / dispose so a\n * new thread's subagents cannot bleed into the previous UI.\n */\n reset(): void {\n this.#map.clear();\n this.#taskIdByObservedNamespace.clear();\n this.#observedOwnNamespaces.clear();\n this.#toolCallIdByTaskInput.clear();\n this.store.setValue(EMPTY_SUBAGENT_MAP);\n }\n\n discoverFromMessage(message: unknown, namespace: readonly string[]): void {\n let changed = false;\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) ||\n changed;\n }\n if (changed) this.#commit();\n }\n\n #commit(): void {\n // Rebuild as a fresh Map so React / useSyncExternalStore sees a\n // new reference on every change.\n this.store.setValue(\n new Map(\n [...this.#map.values()].map((entry) => [entry.id, toSnapshot(entry)])\n )\n );\n }\n\n #onToolEvent(event: ToolsEvent): void {\n const data = event.params.data;\n const toolCallId = (data as { tool_call_id?: string }).tool_call_id;\n const toolName = (data as { tool_name?: string }).tool_name;\n\n if (data.event === \"tool-started\" && toolName === \"task\") {\n const input = parseTaskInput((data as { input?: unknown }).input);\n if (toolCallId == null) return;\n this.#upsertTaskToolCall(toolCallId, input, event.params.namespace);\n this.#commit();\n return;\n }\n\n if (toolCallId == null) return;\n const entry = this.#map.get(toolCallId);\n if (entry == null) return;\n\n if (data.event === \"tool-finished\") {\n entry.status = \"complete\";\n entry.output = (data as { output?: unknown }).output;\n entry.completedAt = new Date();\n this.#commit();\n return;\n }\n\n if (data.event === \"tool-error\") {\n entry.status = \"error\";\n entry.error = (data as { message?: string }).message ?? \"Subagent failed\";\n entry.completedAt = new Date();\n this.#commit();\n }\n }\n\n #onValuesEvent(event: ValuesEvent): void {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) return;\n const messages = (data as { messages?: unknown }).messages;\n if (!Array.isArray(messages)) return;\n\n // If a `tools:<id>` namespace's first HumanMessage matches a known\n // taskInput, that's the subagent's execution scope. Record the\n // binding so `#recordObservedWorkNamespace` can promote it.\n this.#bindNamespaceByTaskInput(event.params.namespace, messages);\n\n let changed = this.#recordObservedWorkNamespace(event.params.namespace);\n for (const message of messages) {\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(\n toolCall.id,\n toolCall.input,\n event.params.namespace\n ) || changed;\n }\n\n const toolCallId = getToolMessageCallId(message);\n if (toolCallId == null) continue;\n const existing = this.#map.get(toolCallId);\n if (existing == null) continue;\n existing.status = \"complete\";\n existing.output = message;\n existing.completedAt = new Date();\n changed = true;\n }\n if (changed) this.#commit();\n }\n\n #upsertTaskToolCall(\n toolCallId: string,\n input: { description?: string; subagent_type?: string },\n eventNamespace: readonly string[]\n ): boolean {\n const namespace = taskWorkNamespace(toolCallId, eventNamespace);\n const existing = this.#map.get(toolCallId);\n if (existing != null) {\n let changed = false;\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n const nextName = input.subagent_type ?? existing.name;\n const nextTaskInput = input.description ?? existing.taskInput;\n if (existing.name !== nextName) {\n existing.name = nextName;\n changed = true;\n }\n if (existing.taskInput !== nextTaskInput) {\n existing.taskInput = nextTaskInput;\n changed = true;\n }\n const namespaceKeyed = namespaceKey(existing.namespace);\n const ownNamespaceKey = `tools:${toolCallId}`;\n const nextNamespaceKey = namespaceKey(namespace);\n if (\n isConcreteToolNamespace(eventNamespace) ||\n namespaceKeyed === ownNamespaceKey\n ) {\n // A wrapper task tool event can arrive under an execution namespace\n // like `tools:<uuid>`, while the subagent's actual message state is\n // under `tools:<tool_call_id>`. Once discovery has observed the own\n // namespace carrying state, do not demote it back to the wrapper\n // namespace.\n if (\n namespaceKeyed === ownNamespaceKey &&\n nextNamespaceKey !== ownNamespaceKey &&\n this.#observedOwnNamespaces.has(toolCallId)\n ) {\n return changed;\n }\n if (namespaceKeyed !== nextNamespaceKey) {\n existing.namespace = namespace;\n changed = true;\n }\n }\n if (existing.status !== \"complete\" && existing.status !== \"error\") {\n if (existing.status !== \"running\") {\n existing.status = \"running\";\n changed = true;\n }\n }\n return changed;\n }\n\n // Prefer the namespace where the task is first observed. Later\n // observations may move it between wrapper execution namespaces\n // and `[\"tools:<toolCallId>\"]`, depending on where the stream proves\n // the worker's scoped message/tool state exists.\n const { parentId, depth } = lineageFromNamespace(eventNamespace);\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n if (input.description != null) {\n const queue = this.#toolCallIdByTaskInput.get(input.description) ?? [];\n queue.push(toolCallId);\n this.#toolCallIdByTaskInput.set(input.description, queue);\n }\n this.#map.set(toolCallId, {\n id: toolCallId,\n name: input.subagent_type ?? \"unknown\",\n namespace,\n parentId,\n depth,\n status: \"running\",\n taskInput: input.description,\n output: undefined,\n error: undefined,\n startedAt: new Date(),\n completedAt: null,\n });\n return true;\n }\n\n #recordObservedWorkNamespace(namespace: readonly string[]): boolean {\n if (!isConcreteToolNamespace(namespace)) return false;\n const last = namespace.at(-1);\n if (last == null) return false;\n const namespaceKeyed = namespaceKey(namespace);\n const toolCallId =\n this.#taskIdByObservedNamespace.get(namespaceKeyed) ??\n last.slice(\"tools:\".length);\n const existing = this.#map.get(toolCallId);\n if (existing == null) return false;\n\n const ownNamespaceKey = `tools:${toolCallId}`;\n if (namespaceKeyed === ownNamespaceKey) {\n this.#observedOwnNamespaces.add(toolCallId);\n } else if (\n this.#observedOwnNamespaces.has(toolCallId) ||\n (!this.#taskIdByObservedNamespace.has(namespaceKeyed) &&\n !shouldPromoteToObservedNamespace(existing))\n ) {\n return false;\n }\n\n if (namespaceKey(existing.namespace) === namespaceKeyed) return false;\n existing.namespace = [...namespace];\n return true;\n }\n\n #recordTaskNamespaceCandidate(\n toolCallId: string,\n namespace: readonly string[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n this.#taskIdByObservedNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Bind a `tools:<id>` namespace to a registered subagent by looking\n * up the first HumanMessage content in the `taskInput` index.\n */\n #bindNamespaceByTaskInput(\n namespace: readonly string[],\n messages: unknown[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n const namespaceKeyed = namespaceKey(namespace);\n if (this.#taskIdByObservedNamespace.has(namespaceKeyed)) return;\n\n const text = getFirstHumanMessageText(messages);\n if (text == null) return;\n const toolCallId = this.#toolCallIdByTaskInput.get(text)?.shift();\n if (toolCallId == null) return;\n this.#taskIdByObservedNamespace.set(namespaceKeyed, toolCallId);\n }\n}\n\nfunction getFirstHumanMessageText(messages: unknown[]): string | null {\n for (const message of messages) {\n if (message == null || typeof message !== \"object\") continue;\n const record = message as {\n type?: unknown;\n role?: unknown;\n content?: unknown;\n kwargs?: { type?: unknown; content?: unknown };\n lc_kwargs?: { type?: unknown; content?: unknown };\n };\n const type =\n record.type ??\n record.role ??\n record.kwargs?.type ??\n record.lc_kwargs?.type;\n if (type !== \"human\") continue;\n const content =\n record.content ?? record.kwargs?.content ?? record.lc_kwargs?.content;\n return typeof content === \"string\" && content.length > 0 ? content : null;\n }\n return null;\n}\n\nfunction shouldPromoteToObservedNamespace(entry: MutableSubagent): boolean {\n return (\n entry.name === \"fanout-worker\" ||\n /^Worker worker-\\d+/i.test(entry.taskInput ?? \"\")\n );\n}\n\nfunction taskWorkNamespace(\n toolCallId: string,\n eventNamespace: readonly string[]\n): readonly string[] {\n const last = eventNamespace.at(-1);\n if (last != null && isToolNamespaceSegment(last)) return [...eventNamespace];\n return [`tools:${toolCallId}`];\n}\n\nfunction toSnapshot(entry: MutableSubagent): SubagentDiscoverySnapshot {\n return {\n id: entry.id,\n name: entry.name,\n namespace: entry.namespace,\n parentId: entry.parentId,\n depth: entry.depth,\n status: entry.status,\n taskInput: entry.taskInput,\n output: entry.output,\n error: entry.error,\n startedAt: entry.startedAt,\n completedAt: entry.completedAt,\n };\n}\n\nfunction parseTaskInput(raw: unknown): {\n description?: string;\n subagent_type?: string;\n} {\n if (raw == null) return {};\n if (typeof raw === \"string\") {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n return {\n description:\n typeof parsed.description === \"string\"\n ? parsed.description\n : undefined,\n subagent_type:\n typeof parsed.subagent_type === \"string\"\n ? parsed.subagent_type\n : undefined,\n };\n } catch {\n return {};\n }\n }\n if (typeof raw === \"object\" && !Array.isArray(raw)) {\n const obj = raw as Record<string, unknown>;\n return {\n description:\n typeof obj.description === \"string\" ? obj.description : undefined,\n subagent_type:\n typeof obj.subagent_type === \"string\" ? obj.subagent_type : undefined,\n };\n }\n return {};\n}\n\nfunction getTaskToolCalls(message: unknown): Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n}> {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return [];\n }\n const record = message as {\n tool_calls?: unknown;\n kwargs?: { tool_calls?: unknown };\n lc_kwargs?: { tool_calls?: unknown };\n };\n const toolCalls =\n record.tool_calls ??\n record.kwargs?.tool_calls ??\n record.lc_kwargs?.tool_calls;\n if (!Array.isArray(toolCalls)) return [];\n\n const result: Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n }> = [];\n for (const toolCall of toolCalls) {\n if (\n toolCall == null ||\n typeof toolCall !== \"object\" ||\n Array.isArray(toolCall)\n ) {\n continue;\n }\n const record = toolCall as {\n id?: unknown;\n name?: unknown;\n args?: unknown;\n };\n if (typeof record.id !== \"string\" || record.name !== \"task\") continue;\n result.push({ id: record.id, input: parseTaskInput(record.args) });\n }\n return result;\n}\n\nfunction getToolMessageCallId(message: unknown): string | undefined {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return undefined;\n }\n const record = message as {\n tool_call_id?: unknown;\n kwargs?: { tool_call_id?: unknown };\n lc_kwargs?: { tool_call_id?: unknown };\n };\n const id =\n record.tool_call_id ??\n record.kwargs?.tool_call_id ??\n record.lc_kwargs?.tool_call_id;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n}\n\n/**\n * Derive (parentId, depth) from a namespace like\n * `[\"subagents:abc:def\"]`. Namespaces form a rooted tree; the last\n * `:` segment of the deepest namespace element is the current node's\n * call-id and the one before it is the parent.\n */\nfunction lineageFromNamespace(namespace: readonly string[]): {\n parentId: string | null;\n depth: number;\n} {\n if (isRootNamespace(namespace)) return { parentId: null, depth: 1 };\n const last = namespace[namespace.length - 1];\n if (last == null) return { parentId: null, depth: 1 };\n // Namespace segments typically look like\n // subagents:<parentCallId>:<thisCallId>\n // but the protocol doesn't mandate that shape; we best-effort.\n const parts = last.split(\":\").filter((part) => part.length > 0);\n const trimmed = parts.slice(1); // drop the leading \"subagents\" prefix\n const depth = Math.max(1, trimmed.length);\n const parentId = trimmed.length >= 2 ? trimmed[trimmed.length - 2] : null;\n return { parentId: parentId ?? null, depth };\n}\n"],"mappings":"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"}
@@ -1,6 +1,9 @@
1
1
  const require_store = require("../store.cjs");
2
2
  const require_namespace = require("../namespace.cjs");
3
3
  //#region src/stream/discovery/subgraphs.ts
4
+ /** Stable empty maps — reused on {@link SubgraphDiscovery.reset}. */
5
+ const EMPTY_SUBGRAPH_MAP = /* @__PURE__ */ new Map();
6
+ const EMPTY_SUBGRAPH_BY_NODE_MAP = /* @__PURE__ */ new Map();
4
7
  /**
5
8
  * LangGraph namespaces a node invocation as `<node_name>:<uuid>`
6
9
  * (parallel fan-outs share `<node_name>` as a prefix but each get a
@@ -115,6 +118,16 @@ var SubgraphDiscovery = class {
115
118
  get byNodeSnapshot() {
116
119
  return this.byNodeStore.getSnapshot();
117
120
  }
121
+ /**
122
+ * Drop all discovery state. Called on thread rebind / dispose so a
123
+ * new thread's subgraphs cannot bleed into the previous UI.
124
+ */
125
+ reset() {
126
+ this.#shadow.clear();
127
+ this.#promoted.clear();
128
+ this.store.setValue(EMPTY_SUBGRAPH_MAP);
129
+ this.byNodeStore.setValue(EMPTY_SUBGRAPH_BY_NODE_MAP);
130
+ }
118
131
  #ensureShadow(id, namespace, nodeName) {
119
132
  let entry = this.#shadow.get(id);
120
133
  if (entry == null) {
@@ -1 +1 @@
1
- {"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\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 #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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"}
1
+ {"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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"}
@@ -13,6 +13,11 @@ declare class SubgraphDiscovery {
13
13
  push(event: Event): void;
14
14
  get snapshot(): SubgraphMap;
15
15
  get byNodeSnapshot(): SubgraphByNodeMap;
16
+ /**
17
+ * Drop all discovery state. Called on thread rebind / dispose so a
18
+ * new thread's subgraphs cannot bleed into the previous UI.
19
+ */
20
+ reset(): void;
16
21
  }
17
22
  //#endregion
18
23
  export { SubgraphByNodeMap, SubgraphDiscovery, SubgraphMap };
@@ -1 +1 @@
1
- {"version":3,"file":"subgraphs.d.cts","names":[],"sources":["../../../src/stream/discovery/subgraphs.ts"],"mappings":";;;;;KAoDY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,KAClC,iBAAA,GAAoB,WAAA,kBAErB,yBAAA;AAAA,cAuBE,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAAA,SACL,WAAA,EAAW,WAAA,CAAA,iBAAA;;EAmBpB,IAAA,CAAK,KAAA,EAAO,KAAA;EAAA,IA2GR,QAAA,CAAA,GAAY,WAAA;EAAA,IAIZ,cAAA,CAAA,GAAkB,iBAAA;AAAA"}
1
+ {"version":3,"file":"subgraphs.d.cts","names":[],"sources":["../../../src/stream/discovery/subgraphs.ts"],"mappings":";;;;;KAoDY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,KAClC,iBAAA,GAAoB,WAAA,kBAErB,yBAAA;AAAA,cA2BE,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAAA,SACL,WAAA,EAAW,WAAA,CAAA,iBAAA;;EAmBpB,IAAA,CAAK,KAAA,EAAO,KAAA;EAAA,IA2GR,QAAA,CAAA,GAAY,WAAA;EAAA,IAIZ,cAAA,CAAA,GAAkB,iBAAA;;;;;EAQtB,KAAA,CAAA;AAAA"}
@@ -13,6 +13,11 @@ declare class SubgraphDiscovery {
13
13
  push(event: Event): void;
14
14
  get snapshot(): SubgraphMap;
15
15
  get byNodeSnapshot(): SubgraphByNodeMap;
16
+ /**
17
+ * Drop all discovery state. Called on thread rebind / dispose so a
18
+ * new thread's subgraphs cannot bleed into the previous UI.
19
+ */
20
+ reset(): void;
16
21
  }
17
22
  //#endregion
18
23
  export { SubgraphByNodeMap, SubgraphDiscovery, SubgraphMap };
@@ -1 +1 @@
1
- {"version":3,"file":"subgraphs.d.ts","names":[],"sources":["../../../src/stream/discovery/subgraphs.ts"],"mappings":";;;;;KAoDY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,KAClC,iBAAA,GAAoB,WAAA,kBAErB,yBAAA;AAAA,cAuBE,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAAA,SACL,WAAA,EAAW,WAAA,CAAA,iBAAA;;EAmBpB,IAAA,CAAK,KAAA,EAAO,KAAA;EAAA,IA2GR,QAAA,CAAA,GAAY,WAAA;EAAA,IAIZ,cAAA,CAAA,GAAkB,iBAAA;AAAA"}
1
+ {"version":3,"file":"subgraphs.d.ts","names":[],"sources":["../../../src/stream/discovery/subgraphs.ts"],"mappings":";;;;;KAoDY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,KAClC,iBAAA,GAAoB,WAAA,kBAErB,yBAAA;AAAA,cA2BE,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAAA,SACL,WAAA,EAAW,WAAA,CAAA,iBAAA;;EAmBpB,IAAA,CAAK,KAAA,EAAO,KAAA;EAAA,IA2GR,QAAA,CAAA,GAAY,WAAA;EAAA,IAIZ,cAAA,CAAA,GAAkB,iBAAA;;;;;EAQtB,KAAA,CAAA;AAAA"}
@@ -1,6 +1,9 @@
1
1
  import { StreamStore } from "../store.js";
2
2
  import { isInternalWorkNamespace, isRootNamespace, namespaceKey } from "../namespace.js";
3
3
  //#region src/stream/discovery/subgraphs.ts
4
+ /** Stable empty maps — reused on {@link SubgraphDiscovery.reset}. */
5
+ const EMPTY_SUBGRAPH_MAP = /* @__PURE__ */ new Map();
6
+ const EMPTY_SUBGRAPH_BY_NODE_MAP = /* @__PURE__ */ new Map();
4
7
  /**
5
8
  * LangGraph namespaces a node invocation as `<node_name>:<uuid>`
6
9
  * (parallel fan-outs share `<node_name>` as a prefix but each get a
@@ -115,6 +118,16 @@ var SubgraphDiscovery = class {
115
118
  get byNodeSnapshot() {
116
119
  return this.byNodeStore.getSnapshot();
117
120
  }
121
+ /**
122
+ * Drop all discovery state. Called on thread rebind / dispose so a
123
+ * new thread's subgraphs cannot bleed into the previous UI.
124
+ */
125
+ reset() {
126
+ this.#shadow.clear();
127
+ this.#promoted.clear();
128
+ this.store.setValue(EMPTY_SUBGRAPH_MAP);
129
+ this.byNodeStore.setValue(EMPTY_SUBGRAPH_BY_NODE_MAP);
130
+ }
118
131
  #ensureShadow(id, namespace, nodeName) {
119
132
  let entry = this.#shadow.get(id);
120
133
  if (entry == null) {
@@ -1 +1 @@
1
- {"version":3,"file":"subgraphs.js","names":["#onValuesEvent","#promoted","#shadow","#ensureShadow","#commit"],"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\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 #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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"}
1
+ {"version":3,"file":"subgraphs.js","names":["#onValuesEvent","#promoted","#shadow","#ensureShadow","#commit"],"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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"}
@@ -1,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_tools = require("../client/stream/handles/tools.cjs");
2
3
  const require_media = require("../client/stream/media.cjs");
3
4
  const require_constants = require("./constants.cjs");
4
5
  const require_store = require("./store.cjs");
@@ -31,6 +32,8 @@ exports.extensionProjection = require_extension.extensionProjection;
31
32
  exports.filesProjection = require_media$1.filesProjection;
32
33
  exports.imagesProjection = require_media$1.imagesProjection;
33
34
  exports.messagesProjection = require_messages.messagesProjection;
35
+ exports.parseToolOutput = require_tools.parseToolOutput;
36
+ exports.parseToolPayload = require_tools.parseToolPayload;
34
37
  exports.toolCallsProjection = require_tool_calls.toolCallsProjection;
35
38
  exports.valuesProjection = require_values.valuesProjection;
36
39
  exports.videoProjection = require_media$1.videoProjection;
@@ -1,12 +1,12 @@
1
- import { DefaultToolCall, ToolCallFromTool } from "../types.messages.cjs";
2
- import { AssembledToolCall, ToolCallStatus } from "../client/stream/handles/tools.cjs";
1
+ import { DefaultToolCall, InferToolOutput, ToolCallFromTool } from "../types.messages.cjs";
2
+ import { AssembledToolCall, ToolCallStatus, parseToolOutput, parseToolPayload } from "../client/stream/handles/tools.cjs";
3
3
  import { AnyMediaHandle, AudioMedia, FileMedia, ImageMedia, MediaAssembler, MediaAssemblerCallbacks, MediaAssemblerOptions, MediaAssemblyError, MediaAssemblyErrorKind, MediaBase, MediaBlockType, VideoMedia } from "../client/stream/media.cjs";
4
4
  import { AgentServerAdapter, TransportAdapter } from "../client/stream/transport.cjs";
5
5
  import { AgentTypeConfigLike, CompiledSubAgentLike, DeepAgentTypeConfigLike, DefaultSubagentStates, ExtractAgentConfig, ExtractDeepAgentConfig, ExtractSubAgentMiddleware, InferAgentToolCalls, InferDeepAgentSubagents, InferSubagentByName, InferSubagentNames, InferSubagentState, IsAgentLike, IsDeepAgentLike, SubAgentLike, SubagentStateMap, SubagentToolCall } from "../ui/types.cjs";
6
6
  import { InferBag, InferNodeNames } from "../ui/stream/index.cjs";
7
- import { InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.cjs";
7
+ import { AssembledToolCallFromTool, InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.cjs";
8
8
  import { StoreListener, StreamStore } from "./store.cjs";
9
- import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, StateOf, StreamControllerOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.cjs";
9
+ import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, RunCompletedInfo, RunExecutionInfo, RunExecutionReason, StreamControllerOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.cjs";
10
10
  import { ChannelRegistry } from "./channel-registry.cjs";
11
11
  import { SubagentDiscovery, SubagentMap } from "./discovery/subagents.cjs";
12
12
  import { SubgraphByNodeMap, SubgraphDiscovery, SubgraphMap } from "./discovery/subgraphs.cjs";
@@ -22,4 +22,4 @@ import { MediaProjectionOptions, audioProjection, filesProjection, imagesProject
22
22
  import { AssembledToMessageInput, ExtendedMessageRole, assembledMessageToBaseMessage, assembledToBaseMessage } from "./assembled-to-message.cjs";
23
23
  import { NAMESPACE_SEPARATOR } from "./constants.cjs";
24
24
  import { Channel, Event } from "@langchain/protocol";
25
- export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type StateOf, type StoreListener, StreamController, type StreamControllerOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, toolCallsProjection, valuesProjection, videoProjection };
25
+ export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AssembledToolCallFromTool, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type InferToolOutput, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type RunCompletedInfo, type RunExecutionInfo, type RunExecutionReason, type StoreListener, StreamController, type StreamControllerOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, parseToolOutput, parseToolPayload, toolCallsProjection, valuesProjection, videoProjection };
@@ -1,12 +1,12 @@
1
- import { DefaultToolCall, ToolCallFromTool } from "../types.messages.js";
2
- import { AssembledToolCall, ToolCallStatus } from "../client/stream/handles/tools.js";
1
+ import { DefaultToolCall, InferToolOutput, ToolCallFromTool } from "../types.messages.js";
2
+ import { AssembledToolCall, ToolCallStatus, parseToolOutput, parseToolPayload } from "../client/stream/handles/tools.js";
3
3
  import { AnyMediaHandle, AudioMedia, FileMedia, ImageMedia, MediaAssembler, MediaAssemblerCallbacks, MediaAssemblerOptions, MediaAssemblyError, MediaAssemblyErrorKind, MediaBase, MediaBlockType, VideoMedia } from "../client/stream/media.js";
4
4
  import { AgentServerAdapter, TransportAdapter } from "../client/stream/transport.js";
5
5
  import { AgentTypeConfigLike, CompiledSubAgentLike, DeepAgentTypeConfigLike, DefaultSubagentStates, ExtractAgentConfig, ExtractDeepAgentConfig, ExtractSubAgentMiddleware, InferAgentToolCalls, InferDeepAgentSubagents, InferSubagentByName, InferSubagentNames, InferSubagentState, IsAgentLike, IsDeepAgentLike, SubAgentLike, SubagentStateMap, SubagentToolCall } from "../ui/types.js";
6
6
  import { InferBag, InferNodeNames } from "../ui/stream/index.js";
7
- import { InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.js";
7
+ import { AssembledToolCallFromTool, InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.js";
8
8
  import { StoreListener, StreamStore } from "./store.js";
9
- import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, StateOf, StreamControllerOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.js";
9
+ import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, RunCompletedInfo, RunExecutionInfo, RunExecutionReason, StreamControllerOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.js";
10
10
  import { ChannelRegistry } from "./channel-registry.js";
11
11
  import { SubagentDiscovery, SubagentMap } from "./discovery/subagents.js";
12
12
  import { SubgraphByNodeMap, SubgraphDiscovery, SubgraphMap } from "./discovery/subgraphs.js";
@@ -22,4 +22,4 @@ import { MediaProjectionOptions, audioProjection, filesProjection, imagesProject
22
22
  import { AssembledToMessageInput, ExtendedMessageRole, assembledMessageToBaseMessage, assembledToBaseMessage } from "./assembled-to-message.js";
23
23
  import { NAMESPACE_SEPARATOR } from "./constants.js";
24
24
  import { Channel, Event } from "@langchain/protocol";
25
- export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type StateOf, type StoreListener, StreamController, type StreamControllerOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, toolCallsProjection, valuesProjection, videoProjection };
25
+ export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AssembledToolCallFromTool, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type InferToolOutput, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type RunCompletedInfo, type RunExecutionInfo, type RunExecutionReason, type StoreListener, StreamController, type StreamControllerOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, parseToolOutput, parseToolPayload, toolCallsProjection, valuesProjection, videoProjection };