@langchain/langgraph-sdk 1.9.16 → 1.9.18

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 (114) hide show
  1. package/dist/client/base.cjs +70 -4
  2. package/dist/client/base.cjs.map +1 -1
  3. package/dist/client/base.d.cts +3 -0
  4. package/dist/client/base.d.cts.map +1 -1
  5. package/dist/client/base.d.ts +3 -0
  6. package/dist/client/base.d.ts.map +1 -1
  7. package/dist/client/base.js +70 -4
  8. package/dist/client/base.js.map +1 -1
  9. package/dist/client/threads/index.cjs +4 -2
  10. package/dist/client/threads/index.cjs.map +1 -1
  11. package/dist/client/threads/index.d.cts.map +1 -1
  12. package/dist/client/threads/index.d.ts.map +1 -1
  13. package/dist/client/threads/index.js +4 -2
  14. package/dist/client/threads/index.js.map +1 -1
  15. package/dist/stream/controller.cjs +451 -32
  16. package/dist/stream/controller.cjs.map +1 -1
  17. package/dist/stream/controller.d.cts +15 -0
  18. package/dist/stream/controller.d.cts.map +1 -1
  19. package/dist/stream/controller.d.ts +15 -0
  20. package/dist/stream/controller.d.ts.map +1 -1
  21. package/dist/stream/controller.js +472 -32
  22. package/dist/stream/controller.js.map +1 -1
  23. package/dist/stream/discovery/index.cjs +2 -0
  24. package/dist/stream/discovery/index.js +3 -0
  25. package/dist/stream/discovery/namespace-from-history.cjs +207 -0
  26. package/dist/stream/discovery/namespace-from-history.cjs.map +1 -0
  27. package/dist/stream/discovery/namespace-from-history.js +204 -0
  28. package/dist/stream/discovery/namespace-from-history.js.map +1 -0
  29. package/dist/stream/discovery/subagents.cjs +56 -1
  30. package/dist/stream/discovery/subagents.cjs.map +1 -1
  31. package/dist/stream/discovery/subagents.d.cts +31 -0
  32. package/dist/stream/discovery/subagents.d.cts.map +1 -1
  33. package/dist/stream/discovery/subagents.d.ts +31 -0
  34. package/dist/stream/discovery/subagents.d.ts.map +1 -1
  35. package/dist/stream/discovery/subagents.js +56 -1
  36. package/dist/stream/discovery/subagents.js.map +1 -1
  37. package/dist/stream/discovery/subgraphs.cjs +24 -0
  38. package/dist/stream/discovery/subgraphs.cjs.map +1 -1
  39. package/dist/stream/discovery/subgraphs.d.cts +13 -0
  40. package/dist/stream/discovery/subgraphs.d.cts.map +1 -1
  41. package/dist/stream/discovery/subgraphs.d.ts +13 -0
  42. package/dist/stream/discovery/subgraphs.d.ts.map +1 -1
  43. package/dist/stream/discovery/subgraphs.js +24 -0
  44. package/dist/stream/discovery/subgraphs.js.map +1 -1
  45. package/dist/stream/index.cjs +1 -0
  46. package/dist/stream/index.js +1 -0
  47. package/dist/stream/message-coercion.cjs +101 -0
  48. package/dist/stream/message-coercion.cjs.map +1 -0
  49. package/dist/stream/message-coercion.d.ts +1 -0
  50. package/dist/stream/message-coercion.js +98 -0
  51. package/dist/stream/message-coercion.js.map +1 -0
  52. package/dist/stream/message-metadata-tracker.cjs +92 -0
  53. package/dist/stream/message-metadata-tracker.cjs.map +1 -1
  54. package/dist/stream/message-metadata-tracker.d.cts +23 -0
  55. package/dist/stream/message-metadata-tracker.d.cts.map +1 -1
  56. package/dist/stream/message-metadata-tracker.d.ts +23 -0
  57. package/dist/stream/message-metadata-tracker.d.ts.map +1 -1
  58. package/dist/stream/message-metadata-tracker.js +92 -0
  59. package/dist/stream/message-metadata-tracker.js.map +1 -1
  60. package/dist/stream/message-reconciliation.cjs +2 -2
  61. package/dist/stream/message-reconciliation.cjs.map +1 -1
  62. package/dist/stream/message-reconciliation.js +2 -2
  63. package/dist/stream/message-reconciliation.js.map +1 -1
  64. package/dist/stream/optimistic-input.cjs +86 -0
  65. package/dist/stream/optimistic-input.cjs.map +1 -0
  66. package/dist/stream/optimistic-input.d.ts +1 -0
  67. package/dist/stream/optimistic-input.js +86 -0
  68. package/dist/stream/optimistic-input.js.map +1 -0
  69. package/dist/stream/projections/channel.cjs +1 -0
  70. package/dist/stream/projections/channel.cjs.map +1 -1
  71. package/dist/stream/projections/channel.d.cts.map +1 -1
  72. package/dist/stream/projections/channel.d.ts.map +1 -1
  73. package/dist/stream/projections/channel.js +1 -0
  74. package/dist/stream/projections/channel.js.map +1 -1
  75. package/dist/stream/projections/messages.cjs +24 -14
  76. package/dist/stream/projections/messages.cjs.map +1 -1
  77. package/dist/stream/projections/messages.js +21 -11
  78. package/dist/stream/projections/messages.js.map +1 -1
  79. package/dist/stream/projections/tool-calls.cjs +22 -10
  80. package/dist/stream/projections/tool-calls.cjs.map +1 -1
  81. package/dist/stream/projections/tool-calls.js +22 -10
  82. package/dist/stream/projections/tool-calls.js.map +1 -1
  83. package/dist/stream/projections/values.cjs +2 -2
  84. package/dist/stream/projections/values.cjs.map +1 -1
  85. package/dist/stream/projections/values.js +1 -1
  86. package/dist/stream/projections/values.js.map +1 -1
  87. package/dist/stream/root-message-projection.cjs +130 -3
  88. package/dist/stream/root-message-projection.cjs.map +1 -1
  89. package/dist/stream/root-message-projection.js +130 -3
  90. package/dist/stream/root-message-projection.js.map +1 -1
  91. package/dist/stream/submit-coordinator.cjs +28 -6
  92. package/dist/stream/submit-coordinator.cjs.map +1 -1
  93. package/dist/stream/submit-coordinator.d.cts.map +1 -1
  94. package/dist/stream/submit-coordinator.d.ts +0 -1
  95. package/dist/stream/submit-coordinator.d.ts.map +1 -1
  96. package/dist/stream/submit-coordinator.js +28 -6
  97. package/dist/stream/submit-coordinator.js.map +1 -1
  98. package/dist/stream/tool-calls.cjs +32 -0
  99. package/dist/stream/tool-calls.cjs.map +1 -1
  100. package/dist/stream/tool-calls.js +32 -1
  101. package/dist/stream/tool-calls.js.map +1 -1
  102. package/dist/stream/types.d.cts +43 -0
  103. package/dist/stream/types.d.cts.map +1 -1
  104. package/dist/stream/types.d.ts +43 -0
  105. package/dist/stream/types.d.ts.map +1 -1
  106. package/dist/ui/index.d.cts +1 -1
  107. package/dist/ui/index.d.ts +1 -1
  108. package/dist/ui/messages.cjs +4 -50
  109. package/dist/ui/messages.cjs.map +1 -1
  110. package/dist/ui/messages.d.cts.map +1 -1
  111. package/dist/ui/messages.d.ts.map +1 -1
  112. package/dist/ui/messages.js +3 -48
  113. package/dist/ui/messages.js.map +1 -1
  114. package/package.json +1 -1
@@ -0,0 +1,207 @@
1
+ const require_message_coercion = require("../message-coercion.cjs");
2
+ require("../constants.cjs");
3
+ const require_namespace = require("../namespace.cjs");
4
+ //#region src/stream/discovery/namespace-from-history.ts
5
+ function getTasks(checkpoint) {
6
+ const tasks = checkpoint.tasks;
7
+ return Array.isArray(tasks) ? tasks : [];
8
+ }
9
+ /**
10
+ * Phase 1 (preferred): map a `task` tool-call id directly to its
11
+ * execution namespace via the task's result `ToolMessage`. Unambiguous
12
+ * — works even when a step mixes subagent and non-subagent tool calls.
13
+ *
14
+ * The subgraph checkpoint_ns is `task.name + ":" + task.id` (mirrors
15
+ * pregel's `taskCheckpointNamespace`), so we derive it from name+id
16
+ * rather than the always-null completed-task checkpoint.
17
+ */
18
+ function collectDirectTaskMappings(tasks, targets, out) {
19
+ for (const task of tasks) {
20
+ if (!Array.isArray(task.path) || task.path[0] !== "__pregel_push" || typeof task.id !== "string" || typeof task.name !== "string") continue;
21
+ const resultMessages = task.result?.messages;
22
+ if (!Array.isArray(resultMessages)) continue;
23
+ for (const msg of resultMessages) {
24
+ const m = msg;
25
+ const id = m.tool_call_id;
26
+ if (m.type === "tool" && typeof id === "string" && targets.has(id) && !out.has(id)) out.set(id, `${task.name}:${task.id}`);
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Phase 2 (fallback): align still-pending push tasks to the triggering
32
+ * AI message's tool calls by Send index (`path[1]`), where `path[1]`
33
+ * indexes into the *full* `tool_calls` array. Only push tasks whose
34
+ * targeted call is a subagent `task` are mapped, so a sibling
35
+ * non-subagent tool call in the same message can't capture a subagent's
36
+ * namespace. Applied only to ids Phase 1 could not resolve so a correct
37
+ * direct mapping is never overwritten by a positional guess.
38
+ */
39
+ function collectPositionalTaskMappings(checkpoint, targets, out, messagesKey) {
40
+ const pushTasks = getTasks(checkpoint).filter((t) => Array.isArray(t.path) && t.path[0] === "__pregel_push" && typeof t.path[1] === "number" && typeof t.id === "string" && typeof t.name === "string");
41
+ if (pushTasks.length === 0) return;
42
+ const msgs = checkpoint.values?.[messagesKey];
43
+ if (!Array.isArray(msgs)) return;
44
+ let toolCalls;
45
+ for (let i = msgs.length - 1; i >= 0; i -= 1) {
46
+ const m = msgs[i];
47
+ if (m.type !== "ai") continue;
48
+ const normalizedToolCalls = require_message_coercion.normalizeAIMessageToolCalls(m).tool_calls;
49
+ if (Array.isArray(normalizedToolCalls) && normalizedToolCalls.some((tc) => tc.name === "task")) {
50
+ toolCalls = normalizedToolCalls;
51
+ break;
52
+ }
53
+ }
54
+ if (toolCalls == null) return;
55
+ for (const task of pushTasks) {
56
+ const index = task.path[1];
57
+ const tc = toolCalls[index];
58
+ if (tc?.name === "task" && tc.id != null && typeof task.id === "string" && typeof task.name === "string" && targets.has(tc.id) && !out.has(tc.id)) out.set(tc.id, `${task.name}:${task.id}`);
59
+ }
60
+ }
61
+ /** Build a `getHistory` `before` cursor from a history entry. */
62
+ function beforeCursor(entry) {
63
+ const checkpointId = entry.checkpoint?.checkpoint_id;
64
+ if (typeof checkpointId !== "string") return void 0;
65
+ return { configurable: { checkpoint_id: checkpointId } };
66
+ }
67
+ function applyCollectors(history, targets, out, messagesKey) {
68
+ for (const checkpoint of history) collectDirectTaskMappings(getTasks(checkpoint), targets, out);
69
+ const stillUnmapped = () => [...targets].some((id) => !out.has(id));
70
+ if (stillUnmapped()) for (const checkpoint of history) {
71
+ if (!stillUnmapped()) break;
72
+ collectPositionalTaskMappings(checkpoint, targets, out, messagesKey);
73
+ }
74
+ }
75
+ /**
76
+ * Synchronous subagent namespace mapping over an already-fetched
77
+ * history page. Used by {@link resolveSubagentNamespaces} and by the
78
+ * controller's hydrate-time bulk seed (which shares a single
79
+ * `getHistory` page with subgraph host detection).
80
+ */
81
+ function mapSubagentNamespaces(history, toolCallIds, messagesKey = "messages") {
82
+ const out = /* @__PURE__ */ new Map();
83
+ const targets = new Set(toolCallIds);
84
+ if (targets.size === 0) return out;
85
+ applyCollectors(history, targets, out, messagesKey);
86
+ return out;
87
+ }
88
+ /**
89
+ * Resolve execution namespaces for the given subagent `task` tool-call
90
+ * ids from checkpoint history.
91
+ *
92
+ * Bounded and O(1) in calls: one `getHistory` page, plus at most one
93
+ * `before`-cursor fallback page when the first leaves ids unresolved.
94
+ * Never fans out per id.
95
+ *
96
+ * @returns Map of `toolCallId` → single execution namespace segment
97
+ * (e.g. `tools:<uuid>`). Unresolved ids are omitted.
98
+ */
99
+ async function resolveSubagentNamespaces(client, threadId, toolCallIds, opts) {
100
+ const out = /* @__PURE__ */ new Map();
101
+ const targets = new Set(toolCallIds);
102
+ if (targets.size === 0) return out;
103
+ const limit = opts?.limit ?? 20;
104
+ const messagesKey = opts?.messagesKey ?? "messages";
105
+ const signal = opts?.signal;
106
+ const page1 = await getHistoryPage(client, threadId, {
107
+ limit,
108
+ signal
109
+ });
110
+ applyCollectors(page1, targets, out, messagesKey);
111
+ if ([...targets].filter((id) => !out.has(id)).length > 0 && page1.length > 0) {
112
+ const before = beforeCursor(page1[page1.length - 1]);
113
+ if (before != null) applyCollectors(await getHistoryPage(client, threadId, {
114
+ limit,
115
+ before,
116
+ signal
117
+ }), targets, out, messagesKey);
118
+ }
119
+ return out;
120
+ }
121
+ /**
122
+ * Fetch one bounded history page typed as plain records, sidestepping
123
+ * the client's `TStateType` generic so the discovery collectors (which
124
+ * read raw `values`/`tasks`) get a stable {@link AnyCheckpoint} shape.
125
+ */
126
+ function getHistoryPage(client, threadId, options) {
127
+ return client.threads.getHistory(threadId, options);
128
+ }
129
+ function checkpointNsToSegments(checkpointNs) {
130
+ if (typeof checkpointNs !== "string" || checkpointNs.length === 0) return [];
131
+ return checkpointNs.split("|").filter((segment) => segment.length > 0);
132
+ }
133
+ function isInternalSegment(segment) {
134
+ return segment.startsWith("tools:") || segment.startsWith("task:");
135
+ }
136
+ /**
137
+ * Identify subgraph host namespaces from checkpoint history, mirroring the
138
+ * promotion rule of the live {@link ./subgraphs} `SubgraphDiscovery`: a
139
+ * namespace is a host iff it is a *strict ancestor* of a deeper namespace,
140
+ * and inner function-node leaves are never promoted on their own.
141
+ *
142
+ * Two host signals are derived from the checkpoint namespaces observed in
143
+ * history (`state` + `task` `checkpoint_ns`):
144
+ *
145
+ * - **Interior hosts** — every strict ancestor of an observed namespace.
146
+ * Each segment of a nested `checkpoint_ns` wraps a deeper one, so the
147
+ * parent is always a subgraph (this also recovers a host whose own
148
+ * checkpoint fell outside the fetched page).
149
+ * - **Top-level hosts** — every depth-1 observed namespace. A top-level
150
+ * subgraph is always a host, including the values-only shape supported
151
+ * by `SubgraphDiscovery.#onValuesEvent`, where the host (e.g.
152
+ * `research:<uuid>`) appears with no deeper `research:<uuid>|...` key.
153
+ * The old strict-prefix rule dropped those, so reconnecting waited for
154
+ * SSE replay instead of hydrating the card immediately.
155
+ *
156
+ * A *deeper* observed namespace's own deepest segment is NOT promoted
157
+ * unless it is itself an ancestor of something deeper — otherwise a plain
158
+ * inner node (e.g. `worker:<uuid>|inner:<uuid>`) would surface as a
159
+ * spurious subgraph card that was never present during live streaming.
160
+ *
161
+ * Tool/subagent namespaces (`tools:` / `task:`) are excluded — those are
162
+ * owned by {@link ./subagents}. A namespace nested under a subgraph (e.g.
163
+ * `research:<uuid>|tools:<uuid>`) still promotes its non-internal
164
+ * `research:<uuid>` ancestor.
165
+ */
166
+ function collectSubgraphHostNamespaces(history) {
167
+ const observed = /* @__PURE__ */ new Map();
168
+ const record = (segments) => {
169
+ if (segments.length === 0) return;
170
+ observed.set(require_namespace.namespaceKey(segments), segments);
171
+ };
172
+ for (const state of history) {
173
+ record(checkpointNsToSegments(state.checkpoint?.checkpoint_ns));
174
+ for (const task of getTasks(state)) record(checkpointNsToSegments(task.checkpoint?.checkpoint_ns));
175
+ }
176
+ const pending = /* @__PURE__ */ new Set();
177
+ if (history.length > 0) for (const task of getTasks(history[0])) {
178
+ const segments = checkpointNsToSegments(task.checkpoint?.checkpoint_ns);
179
+ if (segments.length > 0) pending.add(require_namespace.namespaceKey(segments));
180
+ }
181
+ const candidates = /* @__PURE__ */ new Map();
182
+ for (const segments of observed.values()) {
183
+ for (let depth = 1; depth < segments.length; depth += 1) {
184
+ const slice = segments.slice(0, depth);
185
+ candidates.set(require_namespace.namespaceKey(slice), slice);
186
+ }
187
+ if (segments.length === 1) candidates.set(require_namespace.namespaceKey(segments), segments);
188
+ }
189
+ const hosts = [];
190
+ for (const [key, segments] of candidates) {
191
+ if (segments.some(isInternalSegment)) continue;
192
+ const prefix = key + "\0";
193
+ const running = pending.has(key) || [...pending].some((p) => p.startsWith(prefix));
194
+ hosts.push({
195
+ namespace: segments,
196
+ status: running ? "running" : "complete"
197
+ });
198
+ }
199
+ return hosts;
200
+ }
201
+ //#endregion
202
+ exports.collectSubgraphHostNamespaces = collectSubgraphHostNamespaces;
203
+ exports.getHistoryPage = getHistoryPage;
204
+ exports.mapSubagentNamespaces = mapSubagentNamespaces;
205
+ exports.resolveSubagentNamespaces = resolveSubagentNamespaces;
206
+
207
+ //# sourceMappingURL=namespace-from-history.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace-from-history.cjs","names":["normalizeAIMessageToolCalls","namespaceKey"],"sources":["../../../src/stream/discovery/namespace-from-history.ts"],"sourcesContent":["/**\n * Reconstruct discovery namespaces from checkpoint history.\n *\n * On reconnect the always-on SSE replay eventually re-derives every\n * subagent execution namespace and every subgraph host, but that costs\n * a full depth-1 replay. This module derives the same information from\n * a single bounded `getHistory()` read so the controller can promote\n * namespaces immediately on `hydrate()` (subgraph hosts + subagent\n * execution namespaces) and lazily for a single opened subagent.\n *\n * The logic mirrors the live discovery state machines so a\n * history-derived namespace and an SSE-derived one cannot disagree:\n * - subagent mapping ports {@link ../../ui/manager} `fetchSubagentHistory`\n * (direct task-result mapping, then positional Send-index fallback);\n * - subgraph host detection ports the strict-prefix promotion rule in\n * {@link ./subgraphs}.\n */\nimport type { Client } from \"../../client/index.js\";\nimport type {\n Checkpoint,\n Config,\n Metadata,\n ThreadState,\n} from \"../../schema.js\";\nimport { NAMESPACE_SEPARATOR } from \"../constants.js\";\nimport { namespaceKey } from \"../namespace.js\";\nimport { normalizeAIMessageToolCalls } from \"../message-coercion.js\";\n\ntype AnyCheckpoint = ThreadState<Record<string, unknown>>;\n\ninterface HistoryTask {\n id?: unknown;\n name?: unknown;\n path?: unknown;\n result?: { messages?: unknown[] };\n checkpoint?: { checkpoint_ns?: unknown } | null;\n}\n\nfunction getTasks(checkpoint: AnyCheckpoint): HistoryTask[] {\n const tasks = (checkpoint as { tasks?: unknown }).tasks;\n return Array.isArray(tasks) ? (tasks as HistoryTask[]) : [];\n}\n\n/**\n * Phase 1 (preferred): map a `task` tool-call id directly to its\n * execution namespace via the task's result `ToolMessage`. Unambiguous\n * — works even when a step mixes subagent and non-subagent tool calls.\n *\n * The subgraph checkpoint_ns is `task.name + \":\" + task.id` (mirrors\n * pregel's `taskCheckpointNamespace`), so we derive it from name+id\n * rather than the always-null completed-task checkpoint.\n */\nexport function collectDirectTaskMappings(\n tasks: HistoryTask[],\n targets: Set<string>,\n out: Map<string, string>\n): void {\n for (const task of tasks) {\n if (\n !Array.isArray(task.path) ||\n task.path[0] !== \"__pregel_push\" ||\n typeof task.id !== \"string\" ||\n typeof task.name !== \"string\"\n ) {\n continue;\n }\n const resultMessages = task.result?.messages;\n if (!Array.isArray(resultMessages)) continue;\n for (const msg of resultMessages) {\n const m = msg as Record<string, unknown>;\n const id = m.tool_call_id;\n if (\n m.type === \"tool\" &&\n typeof id === \"string\" &&\n targets.has(id) &&\n !out.has(id)\n ) {\n out.set(id, `${task.name}:${task.id}`);\n }\n }\n }\n}\n\n/**\n * Phase 2 (fallback): align still-pending push tasks to the triggering\n * AI message's tool calls by Send index (`path[1]`), where `path[1]`\n * indexes into the *full* `tool_calls` array. Only push tasks whose\n * targeted call is a subagent `task` are mapped, so a sibling\n * non-subagent tool call in the same message can't capture a subagent's\n * namespace. Applied only to ids Phase 1 could not resolve so a correct\n * direct mapping is never overwritten by a positional guess.\n */\nexport function collectPositionalTaskMappings(\n checkpoint: AnyCheckpoint,\n targets: Set<string>,\n out: Map<string, string>,\n messagesKey: string\n): void {\n const pushTasks = getTasks(checkpoint).filter(\n (t) =>\n Array.isArray(t.path) &&\n t.path[0] === \"__pregel_push\" &&\n typeof t.path[1] === \"number\" &&\n typeof t.id === \"string\" &&\n typeof t.name === \"string\"\n );\n if (pushTasks.length === 0) return;\n\n const msgs = (checkpoint.values as Record<string, unknown> | undefined)?.[\n messagesKey\n ];\n if (!Array.isArray(msgs)) return;\n\n let toolCalls: Array<{ id?: string; name?: string }> | undefined;\n for (let i = msgs.length - 1; i >= 0; i -= 1) {\n const m = msgs[i] as Record<string, unknown>;\n if (m.type !== \"ai\") continue;\n const normalized = normalizeAIMessageToolCalls(\n m as unknown as Parameters<typeof normalizeAIMessageToolCalls>[0]\n ) as Record<string, unknown>;\n const normalizedToolCalls = normalized.tool_calls;\n if (\n Array.isArray(normalizedToolCalls) &&\n (normalizedToolCalls as Array<{ name?: string }>).some(\n (tc) => tc.name === \"task\"\n )\n ) {\n toolCalls = normalizedToolCalls as Array<{\n id?: string;\n name?: string;\n }>;\n break;\n }\n }\n if (toolCalls == null) return;\n\n // `path[1]` is the Send index into the *full* `tool_calls` array, not the\n // subagent-only subset. Resolve each push task against that array and map\n // it only when the targeted call is itself a `task`, so a sibling\n // non-subagent tool call cannot capture a subagent's namespace.\n for (const task of pushTasks) {\n const index = (task.path as unknown[])[1] as number;\n const tc = toolCalls[index];\n if (\n tc?.name === \"task\" &&\n tc.id != null &&\n typeof task.id === \"string\" &&\n typeof task.name === \"string\" &&\n targets.has(tc.id) &&\n !out.has(tc.id)\n ) {\n out.set(tc.id, `${task.name}:${task.id}`);\n }\n }\n}\n\n/** Build a `getHistory` `before` cursor from a history entry. */\nfunction beforeCursor(entry: AnyCheckpoint): Config | undefined {\n const checkpointId = entry.checkpoint?.checkpoint_id;\n if (typeof checkpointId !== \"string\") return undefined;\n return { configurable: { checkpoint_id: checkpointId } };\n}\n\nfunction applyCollectors(\n history: AnyCheckpoint[],\n targets: Set<string>,\n out: Map<string, string>,\n messagesKey: string\n): void {\n for (const checkpoint of history) {\n collectDirectTaskMappings(getTasks(checkpoint), targets, out);\n }\n const stillUnmapped = () => [...targets].some((id) => !out.has(id));\n if (stillUnmapped()) {\n for (const checkpoint of history) {\n if (!stillUnmapped()) break;\n collectPositionalTaskMappings(checkpoint, targets, out, messagesKey);\n }\n }\n}\n\n/**\n * Synchronous subagent namespace mapping over an already-fetched\n * history page. Used by {@link resolveSubagentNamespaces} and by the\n * controller's hydrate-time bulk seed (which shares a single\n * `getHistory` page with subgraph host detection).\n */\nexport function mapSubagentNamespaces(\n history: AnyCheckpoint[],\n toolCallIds: string[],\n messagesKey = \"messages\"\n): Map<string, string> {\n const out = new Map<string, string>();\n const targets = new Set(toolCallIds);\n if (targets.size === 0) return out;\n applyCollectors(history, targets, out, messagesKey);\n return out;\n}\n\n/**\n * Resolve execution namespaces for the given subagent `task` tool-call\n * ids from checkpoint history.\n *\n * Bounded and O(1) in calls: one `getHistory` page, plus at most one\n * `before`-cursor fallback page when the first leaves ids unresolved.\n * Never fans out per id.\n *\n * @returns Map of `toolCallId` → single execution namespace segment\n * (e.g. `tools:<uuid>`). Unresolved ids are omitted.\n */\nexport async function resolveSubagentNamespaces<TStateType>(\n client: Client<TStateType>,\n threadId: string,\n toolCallIds: string[],\n opts?: { limit?: number; messagesKey?: string; signal?: AbortSignal }\n): Promise<Map<string, string>> {\n const out = new Map<string, string>();\n const targets = new Set(toolCallIds);\n if (targets.size === 0) return out;\n\n const limit = opts?.limit ?? 20;\n const messagesKey = opts?.messagesKey ?? \"messages\";\n const signal = opts?.signal;\n\n const page1 = await getHistoryPage(client, threadId, { limit, signal });\n applyCollectors(page1, targets, out, messagesKey);\n\n const unresolved = [...targets].filter((id) => !out.has(id));\n if (unresolved.length > 0 && page1.length > 0) {\n const before = beforeCursor(page1[page1.length - 1]);\n if (before != null) {\n const page2 = await getHistoryPage(client, threadId, {\n limit,\n before,\n signal,\n });\n applyCollectors(page2, targets, out, messagesKey);\n }\n }\n\n return out;\n}\n\n/**\n * Fetch one bounded history page typed as plain records, sidestepping\n * the client's `TStateType` generic so the discovery collectors (which\n * read raw `values`/`tasks`) get a stable {@link AnyCheckpoint} shape.\n */\nexport function getHistoryPage<TStateType>(\n client: Client<TStateType>,\n threadId: string,\n options: {\n limit?: number;\n before?: Config;\n checkpoint?: Partial<Omit<Checkpoint, \"thread_id\">>;\n metadata?: Metadata;\n signal?: AbortSignal;\n }\n): Promise<AnyCheckpoint[]> {\n return client.threads.getHistory<Record<string, unknown>>(threadId, options);\n}\n\nexport interface SubgraphHost {\n namespace: string[];\n status: \"running\" | \"complete\" | \"error\";\n}\n\nfunction checkpointNsToSegments(checkpointNs: unknown): string[] {\n if (typeof checkpointNs !== \"string\" || checkpointNs.length === 0) return [];\n return checkpointNs.split(\"|\").filter((segment) => segment.length > 0);\n}\n\nfunction isInternalSegment(segment: string): boolean {\n return segment.startsWith(\"tools:\") || segment.startsWith(\"task:\");\n}\n\n/**\n * Identify subgraph host namespaces from checkpoint history, mirroring the\n * promotion rule of the live {@link ./subgraphs} `SubgraphDiscovery`: a\n * namespace is a host iff it is a *strict ancestor* of a deeper namespace,\n * and inner function-node leaves are never promoted on their own.\n *\n * Two host signals are derived from the checkpoint namespaces observed in\n * history (`state` + `task` `checkpoint_ns`):\n *\n * - **Interior hosts** — every strict ancestor of an observed namespace.\n * Each segment of a nested `checkpoint_ns` wraps a deeper one, so the\n * parent is always a subgraph (this also recovers a host whose own\n * checkpoint fell outside the fetched page).\n * - **Top-level hosts** — every depth-1 observed namespace. A top-level\n * subgraph is always a host, including the values-only shape supported\n * by `SubgraphDiscovery.#onValuesEvent`, where the host (e.g.\n * `research:<uuid>`) appears with no deeper `research:<uuid>|...` key.\n * The old strict-prefix rule dropped those, so reconnecting waited for\n * SSE replay instead of hydrating the card immediately.\n *\n * A *deeper* observed namespace's own deepest segment is NOT promoted\n * unless it is itself an ancestor of something deeper — otherwise a plain\n * inner node (e.g. `worker:<uuid>|inner:<uuid>`) would surface as a\n * spurious subgraph card that was never present during live streaming.\n *\n * Tool/subagent namespaces (`tools:` / `task:`) are excluded — those are\n * owned by {@link ./subagents}. A namespace nested under a subgraph (e.g.\n * `research:<uuid>|tools:<uuid>`) still promotes its non-internal\n * `research:<uuid>` ancestor.\n */\nexport function collectSubgraphHostNamespaces(\n history: AnyCheckpoint[]\n): SubgraphHost[] {\n // Directly observed namespace tuples (state + task checkpoint_ns), with\n // NO prefix synthesis — host promotion is derived below so that a deeper\n // namespace's leaf segment is not mistaken for a host.\n const observed = new Map<string, string[]>();\n const record = (segments: string[]) => {\n if (segments.length === 0) return;\n observed.set(namespaceKey(segments), segments);\n };\n for (const state of history) {\n record(checkpointNsToSegments(state.checkpoint?.checkpoint_ns));\n for (const task of getTasks(state)) {\n record(checkpointNsToSegments(task.checkpoint?.checkpoint_ns));\n }\n }\n\n // Pending namespaces of the newest checkpoint → still running.\n const pending = new Set<string>();\n if (history.length > 0) {\n for (const task of getTasks(history[0])) {\n const segments = checkpointNsToSegments(task.checkpoint?.checkpoint_ns);\n if (segments.length > 0) pending.add(namespaceKey(segments));\n }\n }\n\n // Host candidates = strict ancestors of observed (interior) + depth-1\n // observed (top-level). Insertion order: shallow ancestors first so the\n // emitted host list keeps parents ahead of children.\n const candidates = new Map<string, string[]>();\n for (const segments of observed.values()) {\n for (let depth = 1; depth < segments.length; depth += 1) {\n const slice = segments.slice(0, depth);\n candidates.set(namespaceKey(slice), slice);\n }\n if (segments.length === 1) {\n candidates.set(namespaceKey(segments), segments);\n }\n }\n\n const hosts: SubgraphHost[] = [];\n for (const [key, segments] of candidates) {\n if (segments.some(isInternalSegment)) continue;\n const prefix = key + NAMESPACE_SEPARATOR;\n const running =\n pending.has(key) || [...pending].some((p) => p.startsWith(prefix));\n hosts.push({\n namespace: segments,\n status: running ? \"running\" : \"complete\",\n });\n }\n return hosts;\n}\n"],"mappings":"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"}
@@ -0,0 +1,204 @@
1
+ import { normalizeAIMessageToolCalls } from "../message-coercion.js";
2
+ import "../constants.js";
3
+ import { namespaceKey } from "../namespace.js";
4
+ //#region src/stream/discovery/namespace-from-history.ts
5
+ function getTasks(checkpoint) {
6
+ const tasks = checkpoint.tasks;
7
+ return Array.isArray(tasks) ? tasks : [];
8
+ }
9
+ /**
10
+ * Phase 1 (preferred): map a `task` tool-call id directly to its
11
+ * execution namespace via the task's result `ToolMessage`. Unambiguous
12
+ * — works even when a step mixes subagent and non-subagent tool calls.
13
+ *
14
+ * The subgraph checkpoint_ns is `task.name + ":" + task.id` (mirrors
15
+ * pregel's `taskCheckpointNamespace`), so we derive it from name+id
16
+ * rather than the always-null completed-task checkpoint.
17
+ */
18
+ function collectDirectTaskMappings(tasks, targets, out) {
19
+ for (const task of tasks) {
20
+ if (!Array.isArray(task.path) || task.path[0] !== "__pregel_push" || typeof task.id !== "string" || typeof task.name !== "string") continue;
21
+ const resultMessages = task.result?.messages;
22
+ if (!Array.isArray(resultMessages)) continue;
23
+ for (const msg of resultMessages) {
24
+ const m = msg;
25
+ const id = m.tool_call_id;
26
+ if (m.type === "tool" && typeof id === "string" && targets.has(id) && !out.has(id)) out.set(id, `${task.name}:${task.id}`);
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Phase 2 (fallback): align still-pending push tasks to the triggering
32
+ * AI message's tool calls by Send index (`path[1]`), where `path[1]`
33
+ * indexes into the *full* `tool_calls` array. Only push tasks whose
34
+ * targeted call is a subagent `task` are mapped, so a sibling
35
+ * non-subagent tool call in the same message can't capture a subagent's
36
+ * namespace. Applied only to ids Phase 1 could not resolve so a correct
37
+ * direct mapping is never overwritten by a positional guess.
38
+ */
39
+ function collectPositionalTaskMappings(checkpoint, targets, out, messagesKey) {
40
+ const pushTasks = getTasks(checkpoint).filter((t) => Array.isArray(t.path) && t.path[0] === "__pregel_push" && typeof t.path[1] === "number" && typeof t.id === "string" && typeof t.name === "string");
41
+ if (pushTasks.length === 0) return;
42
+ const msgs = checkpoint.values?.[messagesKey];
43
+ if (!Array.isArray(msgs)) return;
44
+ let toolCalls;
45
+ for (let i = msgs.length - 1; i >= 0; i -= 1) {
46
+ const m = msgs[i];
47
+ if (m.type !== "ai") continue;
48
+ const normalizedToolCalls = normalizeAIMessageToolCalls(m).tool_calls;
49
+ if (Array.isArray(normalizedToolCalls) && normalizedToolCalls.some((tc) => tc.name === "task")) {
50
+ toolCalls = normalizedToolCalls;
51
+ break;
52
+ }
53
+ }
54
+ if (toolCalls == null) return;
55
+ for (const task of pushTasks) {
56
+ const index = task.path[1];
57
+ const tc = toolCalls[index];
58
+ if (tc?.name === "task" && tc.id != null && typeof task.id === "string" && typeof task.name === "string" && targets.has(tc.id) && !out.has(tc.id)) out.set(tc.id, `${task.name}:${task.id}`);
59
+ }
60
+ }
61
+ /** Build a `getHistory` `before` cursor from a history entry. */
62
+ function beforeCursor(entry) {
63
+ const checkpointId = entry.checkpoint?.checkpoint_id;
64
+ if (typeof checkpointId !== "string") return void 0;
65
+ return { configurable: { checkpoint_id: checkpointId } };
66
+ }
67
+ function applyCollectors(history, targets, out, messagesKey) {
68
+ for (const checkpoint of history) collectDirectTaskMappings(getTasks(checkpoint), targets, out);
69
+ const stillUnmapped = () => [...targets].some((id) => !out.has(id));
70
+ if (stillUnmapped()) for (const checkpoint of history) {
71
+ if (!stillUnmapped()) break;
72
+ collectPositionalTaskMappings(checkpoint, targets, out, messagesKey);
73
+ }
74
+ }
75
+ /**
76
+ * Synchronous subagent namespace mapping over an already-fetched
77
+ * history page. Used by {@link resolveSubagentNamespaces} and by the
78
+ * controller's hydrate-time bulk seed (which shares a single
79
+ * `getHistory` page with subgraph host detection).
80
+ */
81
+ function mapSubagentNamespaces(history, toolCallIds, messagesKey = "messages") {
82
+ const out = /* @__PURE__ */ new Map();
83
+ const targets = new Set(toolCallIds);
84
+ if (targets.size === 0) return out;
85
+ applyCollectors(history, targets, out, messagesKey);
86
+ return out;
87
+ }
88
+ /**
89
+ * Resolve execution namespaces for the given subagent `task` tool-call
90
+ * ids from checkpoint history.
91
+ *
92
+ * Bounded and O(1) in calls: one `getHistory` page, plus at most one
93
+ * `before`-cursor fallback page when the first leaves ids unresolved.
94
+ * Never fans out per id.
95
+ *
96
+ * @returns Map of `toolCallId` → single execution namespace segment
97
+ * (e.g. `tools:<uuid>`). Unresolved ids are omitted.
98
+ */
99
+ async function resolveSubagentNamespaces(client, threadId, toolCallIds, opts) {
100
+ const out = /* @__PURE__ */ new Map();
101
+ const targets = new Set(toolCallIds);
102
+ if (targets.size === 0) return out;
103
+ const limit = opts?.limit ?? 20;
104
+ const messagesKey = opts?.messagesKey ?? "messages";
105
+ const signal = opts?.signal;
106
+ const page1 = await getHistoryPage(client, threadId, {
107
+ limit,
108
+ signal
109
+ });
110
+ applyCollectors(page1, targets, out, messagesKey);
111
+ if ([...targets].filter((id) => !out.has(id)).length > 0 && page1.length > 0) {
112
+ const before = beforeCursor(page1[page1.length - 1]);
113
+ if (before != null) applyCollectors(await getHistoryPage(client, threadId, {
114
+ limit,
115
+ before,
116
+ signal
117
+ }), targets, out, messagesKey);
118
+ }
119
+ return out;
120
+ }
121
+ /**
122
+ * Fetch one bounded history page typed as plain records, sidestepping
123
+ * the client's `TStateType` generic so the discovery collectors (which
124
+ * read raw `values`/`tasks`) get a stable {@link AnyCheckpoint} shape.
125
+ */
126
+ function getHistoryPage(client, threadId, options) {
127
+ return client.threads.getHistory(threadId, options);
128
+ }
129
+ function checkpointNsToSegments(checkpointNs) {
130
+ if (typeof checkpointNs !== "string" || checkpointNs.length === 0) return [];
131
+ return checkpointNs.split("|").filter((segment) => segment.length > 0);
132
+ }
133
+ function isInternalSegment(segment) {
134
+ return segment.startsWith("tools:") || segment.startsWith("task:");
135
+ }
136
+ /**
137
+ * Identify subgraph host namespaces from checkpoint history, mirroring the
138
+ * promotion rule of the live {@link ./subgraphs} `SubgraphDiscovery`: a
139
+ * namespace is a host iff it is a *strict ancestor* of a deeper namespace,
140
+ * and inner function-node leaves are never promoted on their own.
141
+ *
142
+ * Two host signals are derived from the checkpoint namespaces observed in
143
+ * history (`state` + `task` `checkpoint_ns`):
144
+ *
145
+ * - **Interior hosts** — every strict ancestor of an observed namespace.
146
+ * Each segment of a nested `checkpoint_ns` wraps a deeper one, so the
147
+ * parent is always a subgraph (this also recovers a host whose own
148
+ * checkpoint fell outside the fetched page).
149
+ * - **Top-level hosts** — every depth-1 observed namespace. A top-level
150
+ * subgraph is always a host, including the values-only shape supported
151
+ * by `SubgraphDiscovery.#onValuesEvent`, where the host (e.g.
152
+ * `research:<uuid>`) appears with no deeper `research:<uuid>|...` key.
153
+ * The old strict-prefix rule dropped those, so reconnecting waited for
154
+ * SSE replay instead of hydrating the card immediately.
155
+ *
156
+ * A *deeper* observed namespace's own deepest segment is NOT promoted
157
+ * unless it is itself an ancestor of something deeper — otherwise a plain
158
+ * inner node (e.g. `worker:<uuid>|inner:<uuid>`) would surface as a
159
+ * spurious subgraph card that was never present during live streaming.
160
+ *
161
+ * Tool/subagent namespaces (`tools:` / `task:`) are excluded — those are
162
+ * owned by {@link ./subagents}. A namespace nested under a subgraph (e.g.
163
+ * `research:<uuid>|tools:<uuid>`) still promotes its non-internal
164
+ * `research:<uuid>` ancestor.
165
+ */
166
+ function collectSubgraphHostNamespaces(history) {
167
+ const observed = /* @__PURE__ */ new Map();
168
+ const record = (segments) => {
169
+ if (segments.length === 0) return;
170
+ observed.set(namespaceKey(segments), segments);
171
+ };
172
+ for (const state of history) {
173
+ record(checkpointNsToSegments(state.checkpoint?.checkpoint_ns));
174
+ for (const task of getTasks(state)) record(checkpointNsToSegments(task.checkpoint?.checkpoint_ns));
175
+ }
176
+ const pending = /* @__PURE__ */ new Set();
177
+ if (history.length > 0) for (const task of getTasks(history[0])) {
178
+ const segments = checkpointNsToSegments(task.checkpoint?.checkpoint_ns);
179
+ if (segments.length > 0) pending.add(namespaceKey(segments));
180
+ }
181
+ const candidates = /* @__PURE__ */ new Map();
182
+ for (const segments of observed.values()) {
183
+ for (let depth = 1; depth < segments.length; depth += 1) {
184
+ const slice = segments.slice(0, depth);
185
+ candidates.set(namespaceKey(slice), slice);
186
+ }
187
+ if (segments.length === 1) candidates.set(namespaceKey(segments), segments);
188
+ }
189
+ const hosts = [];
190
+ for (const [key, segments] of candidates) {
191
+ if (segments.some(isInternalSegment)) continue;
192
+ const prefix = key + "\0";
193
+ const running = pending.has(key) || [...pending].some((p) => p.startsWith(prefix));
194
+ hosts.push({
195
+ namespace: segments,
196
+ status: running ? "running" : "complete"
197
+ });
198
+ }
199
+ return hosts;
200
+ }
201
+ //#endregion
202
+ export { collectSubgraphHostNamespaces, getHistoryPage, mapSubagentNamespaces, resolveSubagentNamespaces };
203
+
204
+ //# sourceMappingURL=namespace-from-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace-from-history.js","names":[],"sources":["../../../src/stream/discovery/namespace-from-history.ts"],"sourcesContent":["/**\n * Reconstruct discovery namespaces from checkpoint history.\n *\n * On reconnect the always-on SSE replay eventually re-derives every\n * subagent execution namespace and every subgraph host, but that costs\n * a full depth-1 replay. This module derives the same information from\n * a single bounded `getHistory()` read so the controller can promote\n * namespaces immediately on `hydrate()` (subgraph hosts + subagent\n * execution namespaces) and lazily for a single opened subagent.\n *\n * The logic mirrors the live discovery state machines so a\n * history-derived namespace and an SSE-derived one cannot disagree:\n * - subagent mapping ports {@link ../../ui/manager} `fetchSubagentHistory`\n * (direct task-result mapping, then positional Send-index fallback);\n * - subgraph host detection ports the strict-prefix promotion rule in\n * {@link ./subgraphs}.\n */\nimport type { Client } from \"../../client/index.js\";\nimport type {\n Checkpoint,\n Config,\n Metadata,\n ThreadState,\n} from \"../../schema.js\";\nimport { NAMESPACE_SEPARATOR } from \"../constants.js\";\nimport { namespaceKey } from \"../namespace.js\";\nimport { normalizeAIMessageToolCalls } from \"../message-coercion.js\";\n\ntype AnyCheckpoint = ThreadState<Record<string, unknown>>;\n\ninterface HistoryTask {\n id?: unknown;\n name?: unknown;\n path?: unknown;\n result?: { messages?: unknown[] };\n checkpoint?: { checkpoint_ns?: unknown } | null;\n}\n\nfunction getTasks(checkpoint: AnyCheckpoint): HistoryTask[] {\n const tasks = (checkpoint as { tasks?: unknown }).tasks;\n return Array.isArray(tasks) ? (tasks as HistoryTask[]) : [];\n}\n\n/**\n * Phase 1 (preferred): map a `task` tool-call id directly to its\n * execution namespace via the task's result `ToolMessage`. Unambiguous\n * — works even when a step mixes subagent and non-subagent tool calls.\n *\n * The subgraph checkpoint_ns is `task.name + \":\" + task.id` (mirrors\n * pregel's `taskCheckpointNamespace`), so we derive it from name+id\n * rather than the always-null completed-task checkpoint.\n */\nexport function collectDirectTaskMappings(\n tasks: HistoryTask[],\n targets: Set<string>,\n out: Map<string, string>\n): void {\n for (const task of tasks) {\n if (\n !Array.isArray(task.path) ||\n task.path[0] !== \"__pregel_push\" ||\n typeof task.id !== \"string\" ||\n typeof task.name !== \"string\"\n ) {\n continue;\n }\n const resultMessages = task.result?.messages;\n if (!Array.isArray(resultMessages)) continue;\n for (const msg of resultMessages) {\n const m = msg as Record<string, unknown>;\n const id = m.tool_call_id;\n if (\n m.type === \"tool\" &&\n typeof id === \"string\" &&\n targets.has(id) &&\n !out.has(id)\n ) {\n out.set(id, `${task.name}:${task.id}`);\n }\n }\n }\n}\n\n/**\n * Phase 2 (fallback): align still-pending push tasks to the triggering\n * AI message's tool calls by Send index (`path[1]`), where `path[1]`\n * indexes into the *full* `tool_calls` array. Only push tasks whose\n * targeted call is a subagent `task` are mapped, so a sibling\n * non-subagent tool call in the same message can't capture a subagent's\n * namespace. Applied only to ids Phase 1 could not resolve so a correct\n * direct mapping is never overwritten by a positional guess.\n */\nexport function collectPositionalTaskMappings(\n checkpoint: AnyCheckpoint,\n targets: Set<string>,\n out: Map<string, string>,\n messagesKey: string\n): void {\n const pushTasks = getTasks(checkpoint).filter(\n (t) =>\n Array.isArray(t.path) &&\n t.path[0] === \"__pregel_push\" &&\n typeof t.path[1] === \"number\" &&\n typeof t.id === \"string\" &&\n typeof t.name === \"string\"\n );\n if (pushTasks.length === 0) return;\n\n const msgs = (checkpoint.values as Record<string, unknown> | undefined)?.[\n messagesKey\n ];\n if (!Array.isArray(msgs)) return;\n\n let toolCalls: Array<{ id?: string; name?: string }> | undefined;\n for (let i = msgs.length - 1; i >= 0; i -= 1) {\n const m = msgs[i] as Record<string, unknown>;\n if (m.type !== \"ai\") continue;\n const normalized = normalizeAIMessageToolCalls(\n m as unknown as Parameters<typeof normalizeAIMessageToolCalls>[0]\n ) as Record<string, unknown>;\n const normalizedToolCalls = normalized.tool_calls;\n if (\n Array.isArray(normalizedToolCalls) &&\n (normalizedToolCalls as Array<{ name?: string }>).some(\n (tc) => tc.name === \"task\"\n )\n ) {\n toolCalls = normalizedToolCalls as Array<{\n id?: string;\n name?: string;\n }>;\n break;\n }\n }\n if (toolCalls == null) return;\n\n // `path[1]` is the Send index into the *full* `tool_calls` array, not the\n // subagent-only subset. Resolve each push task against that array and map\n // it only when the targeted call is itself a `task`, so a sibling\n // non-subagent tool call cannot capture a subagent's namespace.\n for (const task of pushTasks) {\n const index = (task.path as unknown[])[1] as number;\n const tc = toolCalls[index];\n if (\n tc?.name === \"task\" &&\n tc.id != null &&\n typeof task.id === \"string\" &&\n typeof task.name === \"string\" &&\n targets.has(tc.id) &&\n !out.has(tc.id)\n ) {\n out.set(tc.id, `${task.name}:${task.id}`);\n }\n }\n}\n\n/** Build a `getHistory` `before` cursor from a history entry. */\nfunction beforeCursor(entry: AnyCheckpoint): Config | undefined {\n const checkpointId = entry.checkpoint?.checkpoint_id;\n if (typeof checkpointId !== \"string\") return undefined;\n return { configurable: { checkpoint_id: checkpointId } };\n}\n\nfunction applyCollectors(\n history: AnyCheckpoint[],\n targets: Set<string>,\n out: Map<string, string>,\n messagesKey: string\n): void {\n for (const checkpoint of history) {\n collectDirectTaskMappings(getTasks(checkpoint), targets, out);\n }\n const stillUnmapped = () => [...targets].some((id) => !out.has(id));\n if (stillUnmapped()) {\n for (const checkpoint of history) {\n if (!stillUnmapped()) break;\n collectPositionalTaskMappings(checkpoint, targets, out, messagesKey);\n }\n }\n}\n\n/**\n * Synchronous subagent namespace mapping over an already-fetched\n * history page. Used by {@link resolveSubagentNamespaces} and by the\n * controller's hydrate-time bulk seed (which shares a single\n * `getHistory` page with subgraph host detection).\n */\nexport function mapSubagentNamespaces(\n history: AnyCheckpoint[],\n toolCallIds: string[],\n messagesKey = \"messages\"\n): Map<string, string> {\n const out = new Map<string, string>();\n const targets = new Set(toolCallIds);\n if (targets.size === 0) return out;\n applyCollectors(history, targets, out, messagesKey);\n return out;\n}\n\n/**\n * Resolve execution namespaces for the given subagent `task` tool-call\n * ids from checkpoint history.\n *\n * Bounded and O(1) in calls: one `getHistory` page, plus at most one\n * `before`-cursor fallback page when the first leaves ids unresolved.\n * Never fans out per id.\n *\n * @returns Map of `toolCallId` → single execution namespace segment\n * (e.g. `tools:<uuid>`). Unresolved ids are omitted.\n */\nexport async function resolveSubagentNamespaces<TStateType>(\n client: Client<TStateType>,\n threadId: string,\n toolCallIds: string[],\n opts?: { limit?: number; messagesKey?: string; signal?: AbortSignal }\n): Promise<Map<string, string>> {\n const out = new Map<string, string>();\n const targets = new Set(toolCallIds);\n if (targets.size === 0) return out;\n\n const limit = opts?.limit ?? 20;\n const messagesKey = opts?.messagesKey ?? \"messages\";\n const signal = opts?.signal;\n\n const page1 = await getHistoryPage(client, threadId, { limit, signal });\n applyCollectors(page1, targets, out, messagesKey);\n\n const unresolved = [...targets].filter((id) => !out.has(id));\n if (unresolved.length > 0 && page1.length > 0) {\n const before = beforeCursor(page1[page1.length - 1]);\n if (before != null) {\n const page2 = await getHistoryPage(client, threadId, {\n limit,\n before,\n signal,\n });\n applyCollectors(page2, targets, out, messagesKey);\n }\n }\n\n return out;\n}\n\n/**\n * Fetch one bounded history page typed as plain records, sidestepping\n * the client's `TStateType` generic so the discovery collectors (which\n * read raw `values`/`tasks`) get a stable {@link AnyCheckpoint} shape.\n */\nexport function getHistoryPage<TStateType>(\n client: Client<TStateType>,\n threadId: string,\n options: {\n limit?: number;\n before?: Config;\n checkpoint?: Partial<Omit<Checkpoint, \"thread_id\">>;\n metadata?: Metadata;\n signal?: AbortSignal;\n }\n): Promise<AnyCheckpoint[]> {\n return client.threads.getHistory<Record<string, unknown>>(threadId, options);\n}\n\nexport interface SubgraphHost {\n namespace: string[];\n status: \"running\" | \"complete\" | \"error\";\n}\n\nfunction checkpointNsToSegments(checkpointNs: unknown): string[] {\n if (typeof checkpointNs !== \"string\" || checkpointNs.length === 0) return [];\n return checkpointNs.split(\"|\").filter((segment) => segment.length > 0);\n}\n\nfunction isInternalSegment(segment: string): boolean {\n return segment.startsWith(\"tools:\") || segment.startsWith(\"task:\");\n}\n\n/**\n * Identify subgraph host namespaces from checkpoint history, mirroring the\n * promotion rule of the live {@link ./subgraphs} `SubgraphDiscovery`: a\n * namespace is a host iff it is a *strict ancestor* of a deeper namespace,\n * and inner function-node leaves are never promoted on their own.\n *\n * Two host signals are derived from the checkpoint namespaces observed in\n * history (`state` + `task` `checkpoint_ns`):\n *\n * - **Interior hosts** — every strict ancestor of an observed namespace.\n * Each segment of a nested `checkpoint_ns` wraps a deeper one, so the\n * parent is always a subgraph (this also recovers a host whose own\n * checkpoint fell outside the fetched page).\n * - **Top-level hosts** — every depth-1 observed namespace. A top-level\n * subgraph is always a host, including the values-only shape supported\n * by `SubgraphDiscovery.#onValuesEvent`, where the host (e.g.\n * `research:<uuid>`) appears with no deeper `research:<uuid>|...` key.\n * The old strict-prefix rule dropped those, so reconnecting waited for\n * SSE replay instead of hydrating the card immediately.\n *\n * A *deeper* observed namespace's own deepest segment is NOT promoted\n * unless it is itself an ancestor of something deeper — otherwise a plain\n * inner node (e.g. `worker:<uuid>|inner:<uuid>`) would surface as a\n * spurious subgraph card that was never present during live streaming.\n *\n * Tool/subagent namespaces (`tools:` / `task:`) are excluded — those are\n * owned by {@link ./subagents}. A namespace nested under a subgraph (e.g.\n * `research:<uuid>|tools:<uuid>`) still promotes its non-internal\n * `research:<uuid>` ancestor.\n */\nexport function collectSubgraphHostNamespaces(\n history: AnyCheckpoint[]\n): SubgraphHost[] {\n // Directly observed namespace tuples (state + task checkpoint_ns), with\n // NO prefix synthesis — host promotion is derived below so that a deeper\n // namespace's leaf segment is not mistaken for a host.\n const observed = new Map<string, string[]>();\n const record = (segments: string[]) => {\n if (segments.length === 0) return;\n observed.set(namespaceKey(segments), segments);\n };\n for (const state of history) {\n record(checkpointNsToSegments(state.checkpoint?.checkpoint_ns));\n for (const task of getTasks(state)) {\n record(checkpointNsToSegments(task.checkpoint?.checkpoint_ns));\n }\n }\n\n // Pending namespaces of the newest checkpoint → still running.\n const pending = new Set<string>();\n if (history.length > 0) {\n for (const task of getTasks(history[0])) {\n const segments = checkpointNsToSegments(task.checkpoint?.checkpoint_ns);\n if (segments.length > 0) pending.add(namespaceKey(segments));\n }\n }\n\n // Host candidates = strict ancestors of observed (interior) + depth-1\n // observed (top-level). Insertion order: shallow ancestors first so the\n // emitted host list keeps parents ahead of children.\n const candidates = new Map<string, string[]>();\n for (const segments of observed.values()) {\n for (let depth = 1; depth < segments.length; depth += 1) {\n const slice = segments.slice(0, depth);\n candidates.set(namespaceKey(slice), slice);\n }\n if (segments.length === 1) {\n candidates.set(namespaceKey(segments), segments);\n }\n }\n\n const hosts: SubgraphHost[] = [];\n for (const [key, segments] of candidates) {\n if (segments.some(isInternalSegment)) continue;\n const prefix = key + NAMESPACE_SEPARATOR;\n const running =\n pending.has(key) || [...pending].some((p) => p.startsWith(prefix));\n hosts.push({\n namespace: segments,\n status: running ? \"running\" : \"complete\",\n });\n }\n return hosts;\n}\n"],"mappings":";;;;AAsCA,SAAS,SAAS,YAA0C;CAC1D,MAAM,QAAS,WAAmC;AAClD,QAAO,MAAM,QAAQ,MAAM,GAAI,QAA0B,EAAE;;;;;;;;;;;AAY7D,SAAgB,0BACd,OACA,SACA,KACM;AACN,MAAK,MAAM,QAAQ,OAAO;AACxB,MACE,CAAC,MAAM,QAAQ,KAAK,KAAK,IACzB,KAAK,KAAK,OAAO,mBACjB,OAAO,KAAK,OAAO,YACnB,OAAO,KAAK,SAAS,SAErB;EAEF,MAAM,iBAAiB,KAAK,QAAQ;AACpC,MAAI,CAAC,MAAM,QAAQ,eAAe,CAAE;AACpC,OAAK,MAAM,OAAO,gBAAgB;GAChC,MAAM,IAAI;GACV,MAAM,KAAK,EAAE;AACb,OACE,EAAE,SAAS,UACX,OAAO,OAAO,YACd,QAAQ,IAAI,GAAG,IACf,CAAC,IAAI,IAAI,GAAG,CAEZ,KAAI,IAAI,IAAI,GAAG,KAAK,KAAK,GAAG,KAAK,KAAK;;;;;;;;;;;;;AAe9C,SAAgB,8BACd,YACA,SACA,KACA,aACM;CACN,MAAM,YAAY,SAAS,WAAW,CAAC,QACpC,MACC,MAAM,QAAQ,EAAE,KAAK,IACrB,EAAE,KAAK,OAAO,mBACd,OAAO,EAAE,KAAK,OAAO,YACrB,OAAO,EAAE,OAAO,YAChB,OAAO,EAAE,SAAS,SACrB;AACD,KAAI,UAAU,WAAW,EAAG;CAE5B,MAAM,OAAQ,WAAW,SACvB;AAEF,KAAI,CAAC,MAAM,QAAQ,KAAK,CAAE;CAE1B,IAAI;AACJ,MAAK,IAAI,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;EAC5C,MAAM,IAAI,KAAK;AACf,MAAI,EAAE,SAAS,KAAM;EAIrB,MAAM,sBAHa,4BACjB,EACD,CACsC;AACvC,MACE,MAAM,QAAQ,oBAAoB,IACjC,oBAAiD,MAC/C,OAAO,GAAG,SAAS,OACrB,EACD;AACA,eAAY;AAIZ;;;AAGJ,KAAI,aAAa,KAAM;AAMvB,MAAK,MAAM,QAAQ,WAAW;EAC5B,MAAM,QAAS,KAAK,KAAmB;EACvC,MAAM,KAAK,UAAU;AACrB,MACE,IAAI,SAAS,UACb,GAAG,MAAM,QACT,OAAO,KAAK,OAAO,YACnB,OAAO,KAAK,SAAS,YACrB,QAAQ,IAAI,GAAG,GAAG,IAClB,CAAC,IAAI,IAAI,GAAG,GAAG,CAEf,KAAI,IAAI,GAAG,IAAI,GAAG,KAAK,KAAK,GAAG,KAAK,KAAK;;;;AAM/C,SAAS,aAAa,OAA0C;CAC9D,MAAM,eAAe,MAAM,YAAY;AACvC,KAAI,OAAO,iBAAiB,SAAU,QAAO,KAAA;AAC7C,QAAO,EAAE,cAAc,EAAE,eAAe,cAAc,EAAE;;AAG1D,SAAS,gBACP,SACA,SACA,KACA,aACM;AACN,MAAK,MAAM,cAAc,QACvB,2BAA0B,SAAS,WAAW,EAAE,SAAS,IAAI;CAE/D,MAAM,sBAAsB,CAAC,GAAG,QAAQ,CAAC,MAAM,OAAO,CAAC,IAAI,IAAI,GAAG,CAAC;AACnE,KAAI,eAAe,CACjB,MAAK,MAAM,cAAc,SAAS;AAChC,MAAI,CAAC,eAAe,CAAE;AACtB,gCAA8B,YAAY,SAAS,KAAK,YAAY;;;;;;;;;AAW1E,SAAgB,sBACd,SACA,aACA,cAAc,YACO;CACrB,MAAM,sBAAM,IAAI,KAAqB;CACrC,MAAM,UAAU,IAAI,IAAI,YAAY;AACpC,KAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,iBAAgB,SAAS,SAAS,KAAK,YAAY;AACnD,QAAO;;;;;;;;;;;;;AAcT,eAAsB,0BACpB,QACA,UACA,aACA,MAC8B;CAC9B,MAAM,sBAAM,IAAI,KAAqB;CACrC,MAAM,UAAU,IAAI,IAAI,YAAY;AACpC,KAAI,QAAQ,SAAS,EAAG,QAAO;CAE/B,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,cAAc,MAAM,eAAe;CACzC,MAAM,SAAS,MAAM;CAErB,MAAM,QAAQ,MAAM,eAAe,QAAQ,UAAU;EAAE;EAAO;EAAQ,CAAC;AACvE,iBAAgB,OAAO,SAAS,KAAK,YAAY;AAGjD,KADmB,CAAC,GAAG,QAAQ,CAAC,QAAQ,OAAO,CAAC,IAAI,IAAI,GAAG,CAAC,CAC7C,SAAS,KAAK,MAAM,SAAS,GAAG;EAC7C,MAAM,SAAS,aAAa,MAAM,MAAM,SAAS,GAAG;AACpD,MAAI,UAAU,KAMZ,iBALc,MAAM,eAAe,QAAQ,UAAU;GACnD;GACA;GACA;GACD,CAAC,EACqB,SAAS,KAAK,YAAY;;AAIrD,QAAO;;;;;;;AAQT,SAAgB,eACd,QACA,UACA,SAO0B;AAC1B,QAAO,OAAO,QAAQ,WAAoC,UAAU,QAAQ;;AAQ9E,SAAS,uBAAuB,cAAiC;AAC/D,KAAI,OAAO,iBAAiB,YAAY,aAAa,WAAW,EAAG,QAAO,EAAE;AAC5E,QAAO,aAAa,MAAM,IAAI,CAAC,QAAQ,YAAY,QAAQ,SAAS,EAAE;;AAGxE,SAAS,kBAAkB,SAA0B;AACnD,QAAO,QAAQ,WAAW,SAAS,IAAI,QAAQ,WAAW,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCpE,SAAgB,8BACd,SACgB;CAIhB,MAAM,2BAAW,IAAI,KAAuB;CAC5C,MAAM,UAAU,aAAuB;AACrC,MAAI,SAAS,WAAW,EAAG;AAC3B,WAAS,IAAI,aAAa,SAAS,EAAE,SAAS;;AAEhD,MAAK,MAAM,SAAS,SAAS;AAC3B,SAAO,uBAAuB,MAAM,YAAY,cAAc,CAAC;AAC/D,OAAK,MAAM,QAAQ,SAAS,MAAM,CAChC,QAAO,uBAAuB,KAAK,YAAY,cAAc,CAAC;;CAKlE,MAAM,0BAAU,IAAI,KAAa;AACjC,KAAI,QAAQ,SAAS,EACnB,MAAK,MAAM,QAAQ,SAAS,QAAQ,GAAG,EAAE;EACvC,MAAM,WAAW,uBAAuB,KAAK,YAAY,cAAc;AACvE,MAAI,SAAS,SAAS,EAAG,SAAQ,IAAI,aAAa,SAAS,CAAC;;CAOhE,MAAM,6BAAa,IAAI,KAAuB;AAC9C,MAAK,MAAM,YAAY,SAAS,QAAQ,EAAE;AACxC,OAAK,IAAI,QAAQ,GAAG,QAAQ,SAAS,QAAQ,SAAS,GAAG;GACvD,MAAM,QAAQ,SAAS,MAAM,GAAG,MAAM;AACtC,cAAW,IAAI,aAAa,MAAM,EAAE,MAAM;;AAE5C,MAAI,SAAS,WAAW,EACtB,YAAW,IAAI,aAAa,SAAS,EAAE,SAAS;;CAIpD,MAAM,QAAwB,EAAE;AAChC,MAAK,MAAM,CAAC,KAAK,aAAa,YAAY;AACxC,MAAI,SAAS,KAAK,kBAAkB,CAAE;EACtC,MAAM,SAAS,MAAA;EACf,MAAM,UACJ,QAAQ,IAAI,IAAI,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,MAAM,EAAE,WAAW,OAAO,CAAC;AACpE,QAAM,KAAK;GACT,WAAW;GACX,QAAQ,UAAU,YAAY;GAC/B,CAAC;;AAEJ,QAAO"}
@@ -1,3 +1,4 @@
1
+ const require_message_coercion = require("../message-coercion.cjs");
1
2
  const require_store = require("../store.cjs");
2
3
  const require_namespace = require("../namespace.cjs");
3
4
  //#region src/stream/discovery/subagents.ts
@@ -14,6 +15,60 @@ var SubagentDiscovery = class {
14
15
  if (event.method === "tools") this.#onToolEvent(event);
15
16
  else if (event.method === "values") this.#onValuesEvent(event);
16
17
  }
18
+ /**
19
+ * Seed discovery from a checkpoint's root `messages` array (as
20
+ * returned by `client.threads.getState().values.messages`) so deep
21
+ * agent cards render on thread refresh without waiting for SSE
22
+ * replay.
23
+ *
24
+ * Drives the existing root `values` path via a synthetic event so it
25
+ * reuses task discovery + completion marking with no new parsing
26
+ * logic. Root namespace `[]` keeps namespaces at the default
27
+ * `tools:<toolCallId>`; the always-on root pump (and {@link
28
+ * applyExecutionNamespace}) promote them to the execution namespace
29
+ * later. Idempotent by construction: re-driving root values for
30
+ * already-discovered tasks is a no-op (the FIFO `taskInput` queue is
31
+ * only populated on first discovery), so no `snapshot.size` guard is
32
+ * needed.
33
+ */
34
+ seedFromCheckpointMessages(messages) {
35
+ if (!Array.isArray(messages) || messages.length === 0) return;
36
+ this.push({
37
+ type: "event",
38
+ method: "values",
39
+ params: {
40
+ namespace: [],
41
+ timestamp: Date.now(),
42
+ data: { messages }
43
+ }
44
+ });
45
+ }
46
+ /**
47
+ * Promote a discovered subagent to its execution namespace, derived
48
+ * from checkpoint history (see `namespace-from-history`).
49
+ *
50
+ * Routes through the same guarded promotion machinery the live SSE
51
+ * replay uses ({@link #recordTaskNamespaceCandidate} + the
52
+ * `#observedOwnNamespaces` no-demote rule) so a getHistory-derived
53
+ * namespace and an SSE-derived one cannot disagree. A no-op when the
54
+ * subagent is unknown or already sits on the target namespace.
55
+ *
56
+ * @param toolCallId - Parent `task` tool-call id (the subagent's discovery key).
57
+ * @param executionSegment - Single execution namespace segment, e.g. `tools:<uuid>`.
58
+ */
59
+ applyExecutionNamespace(toolCallId, executionSegment) {
60
+ if (typeof executionSegment !== "string" || executionSegment.length === 0) return;
61
+ const entry = this.#map.get(toolCallId);
62
+ if (entry == null) return;
63
+ const namespace = [executionSegment];
64
+ const namespaceKeyed = require_namespace.namespaceKey(namespace);
65
+ const ownNamespaceKey = `tools:${toolCallId}`;
66
+ this.#recordTaskNamespaceCandidate(toolCallId, namespace);
67
+ if (namespaceKeyed !== ownNamespaceKey && this.#observedOwnNamespaces.has(toolCallId)) return;
68
+ if (require_namespace.namespaceKey(entry.namespace) === namespaceKeyed) return;
69
+ entry.namespace = namespace;
70
+ this.#commit();
71
+ }
17
72
  /** Current snapshot map. */
18
73
  get snapshot() {
19
74
  return this.store.getSnapshot();
@@ -229,7 +284,7 @@ function parseTaskInput(raw) {
229
284
  }
230
285
  function getTaskToolCalls(message) {
231
286
  if (message == null || typeof message !== "object" || Array.isArray(message)) return [];
232
- const record = message;
287
+ const record = require_message_coercion.normalizeAIMessageToolCalls(message);
233
288
  const toolCalls = record.tool_calls ?? record.kwargs?.tool_calls ?? record.lc_kwargs?.tool_calls;
234
289
  if (!Array.isArray(toolCalls)) return [];
235
290
  const result = [];