@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
@@ -1,5 +1,7 @@
1
- const require_messages = require("../ui/messages.cjs");
1
+ require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_message_coercion = require("./message-coercion.cjs");
2
3
  const require_tools = require("../client/stream/handles/tools.cjs");
4
+ require("./constants.cjs");
3
5
  const require_headless_tools = require("../headless-tools.cjs");
4
6
  const require_store = require("./store.cjs");
5
7
  const require_channel_registry = require("./channel-registry.cjs");
@@ -8,11 +10,15 @@ const require_interrupts = require("../ui/interrupts.cjs");
8
10
  const require_namespace = require("./namespace.cjs");
9
11
  const require_subagents = require("./discovery/subagents.cjs");
10
12
  const require_subgraphs = require("./discovery/subgraphs.cjs");
13
+ require("./discovery/index.cjs");
14
+ const require_namespace_from_history = require("./discovery/namespace-from-history.cjs");
11
15
  const require_message_metadata_tracker = require("./message-metadata-tracker.cjs");
12
16
  const require_lifecycle_loading_tracker = require("./lifecycle-loading-tracker.cjs");
13
17
  const require_root_message_projection = require("./root-message-projection.cjs");
18
+ const require_optimistic_input = require("./optimistic-input.cjs");
14
19
  const require_submit_coordinator = require("./submit-coordinator.cjs");
15
20
  const require_tool_calls = require("./tool-calls.cjs");
21
+ let uuid = require("uuid");
16
22
  require("@langchain/core/messages");
17
23
  //#region src/stream/controller.ts
18
24
  /**
@@ -48,6 +54,43 @@ function lifecycleReason(event) {
48
54
  if (event === "interrupted") return "interrupt";
49
55
  return null;
50
56
  }
57
+ /**
58
+ * Decide whether a hydrated thread is *active* (a run is executing or
59
+ * paused awaiting resume) from the `getState()` snapshot alone — no
60
+ * extra request.
61
+ *
62
+ * Why this gate exists: a finished thread does not need either of the
63
+ * always-on SSE pumps. Subagent/subgraph cards are already seeded from
64
+ * the `getState()` messages and a single bounded `getHistory()` page, so
65
+ * opening the depth-1 content pump + the wildcard lifecycle watcher only
66
+ * to replay a completed run and then idle forever is pure waste. We open
67
+ * the pumps eagerly only when the thread is active; otherwise they come
68
+ * up on the first local `submit()` (the existing deferred-pump path) or
69
+ * a thread swap that lands on an active thread.
70
+ *
71
+ * The gate is deliberately conservative: we only conclude *idle* when
72
+ * the state proves it. A thread is treated as active unless `next` is a
73
+ * present, empty array AND no task carries a pending interrupt:
74
+ * - `next` missing / not an array: unknown shape (a server or custom
75
+ * client may omit it). Treat as active so an already-running
76
+ * server-side run is still observed on reconnect — never silently
77
+ * disable streaming on an unfamiliar `getState` shape.
78
+ * - `next.length > 0`: the checkpoint still has nodes to execute, i.e.
79
+ * a run is mid-flight or paused at an interrupt.
80
+ * - `next` is `[]` but a `tasks[].interrupts` is non-empty: the thread
81
+ * is interrupted and a resume (which starts a run) must be observable.
82
+ * - `next` is `[]` and no pending interrupts: a completed run → idle.
83
+ */
84
+ function isThreadStateActive(state) {
85
+ if (state == null) return true;
86
+ if (!Array.isArray(state.next)) return true;
87
+ if (state.next.length > 0) return true;
88
+ if (Array.isArray(state.tasks)) for (const task of state.tasks) {
89
+ const interrupts = task?.interrupts;
90
+ if (Array.isArray(interrupts) && interrupts.length > 0) return true;
91
+ }
92
+ return false;
93
+ }
51
94
  const ROOT_NAMESPACE = [];
52
95
  /**
53
96
  * Channel set covered by the always-on root subscription. Exported so
@@ -133,11 +176,37 @@ var StreamController = class {
133
176
  * request would 404 and surface a spurious error to the UI).
134
177
  */
135
178
  #selfCreatedThreadIds = /* @__PURE__ */ new Set();
179
+ /**
180
+ * In-flight per-subagent namespace resolutions, keyed by tool-call
181
+ * id. De-dupes concurrent {@link resolveSubagentNamespace} calls so
182
+ * re-renders / multiple consumers of the same subagent don't issue
183
+ * parallel `getHistory` walks.
184
+ */
185
+ #namespaceResolves = /* @__PURE__ */ new Map();
186
+ /**
187
+ * In-flight hydrate-time discovery seed ({@link #seedDiscoveryFromHistory}):
188
+ * a single bounded `getHistory` page that bulk-promotes every
189
+ * still-default subagent namespace and seeds subgraph hosts. Per-card
190
+ * {@link resolveSubagentNamespace} calls await this shared promise
191
+ * instead of each firing their own `getHistory` walk, so opening N
192
+ * cards right after reconnect costs one history read, not N. Re-armed
193
+ * per hydrate cycle and cleared once it settles.
194
+ */
195
+ #discoverySeedPromise;
196
+ #scopedHistorySeeds = /* @__PURE__ */ new Map();
136
197
  #rootEventListeners = /* @__PURE__ */ new Set();
137
198
  #rootBus;
138
199
  #activeRunId;
139
200
  #localRunDepth = 0;
140
201
  /**
202
+ * `true` once a root `values` event has been applied for the current
203
+ * optimistic run. Reset to `false` in {@link #beginOptimistic} and
204
+ * read in {@link #settleOptimistic}: when a run terminates without
205
+ * the server ever echoing a `values` snapshot, optimistically-merged
206
+ * non-message keys are rolled back to their pre-submit values.
207
+ */
208
+ #sawValuesForRun = false;
209
+ /**
141
210
  * Single-shot hydration promise. Exposed via `hydrationPromise`
142
211
  * so Suspense wrappers can throw it until the first hydrate
143
212
  * completes (resolve) or fails (reject). Reset whenever a new
@@ -172,7 +241,8 @@ var StreamController = class {
172
241
  return () => {
173
242
  this.#rootEventListeners.delete(listener);
174
243
  };
175
- }
244
+ },
245
+ trySeedFromHistory: (params) => this.#trySeedProjectionFromHistory(params)
176
246
  };
177
247
  this.registry = new require_channel_registry.ChannelRegistry(this.#rootBus);
178
248
  this.subagentStore = this.#subagents.store;
@@ -218,7 +288,9 @@ var StreamController = class {
218
288
  onRunStart: () => this.#markLocalRunStart(),
219
289
  onRunCreated: (runId) => this.#notifyCreated(runId),
220
290
  onRunCompleted: (reason, runId) => this.#notifyCompleted(reason, runId),
221
- onRunEnd: () => this.#markLocalRunEnd()
291
+ onRunEnd: () => this.#markLocalRunEnd(),
292
+ beginOptimistic: (input) => this.#beginOptimistic(input),
293
+ settleOptimistic: (handle, event) => this.#settleOptimistic(handle, event)
222
294
  });
223
295
  this.#hydrationPromise = this.#createHydrationPromise();
224
296
  /**
@@ -285,6 +357,8 @@ var StreamController = class {
285
357
  const target = threadId === void 0 ? this.#currentThreadId : threadId;
286
358
  const changed = target !== this.#currentThreadId;
287
359
  this.#currentThreadId = target ?? null;
360
+ this.#discoverySeedPromise = void 0;
361
+ this.#scopedHistorySeeds.clear();
288
362
  this.rootStore.setState((s) => ({
289
363
  ...s,
290
364
  threadId: this.#currentThreadId
@@ -341,9 +415,11 @@ var StreamController = class {
341
415
  }));
342
416
  let hydrationError;
343
417
  let threadExists = false;
418
+ let threadActive = true;
344
419
  try {
345
420
  const state = await this.#options.client.threads.getState(this.#currentThreadId);
346
421
  threadExists = state != null;
422
+ threadActive = isThreadStateActive(state);
347
423
  if (state?.values != null) {
348
424
  /**
349
425
  * `threads.getState()` returns the legacy `ThreadState` shape
@@ -355,11 +431,43 @@ var StreamController = class {
355
431
  */
356
432
  const checkpointId = state.checkpoint?.checkpoint_id;
357
433
  const parentCheckpointId = state.parent_checkpoint?.checkpoint_id ?? void 0;
434
+ /**
435
+ * Carry the checkpoint `step` from `getState()` metadata so the
436
+ * root message projection treats this seed as the authoritative
437
+ * latest superstep. The content pump's reconnect replay emits
438
+ * older checkpoints (lower step); marking the seed's step lets
439
+ * the projection reject those as stale instead of letting them
440
+ * remove the seeded message tail (the final assistant turn).
441
+ */
442
+ const seedStep = state.metadata?.step;
358
443
  const syntheticCheckpoint = typeof checkpointId === "string" ? {
359
444
  id: checkpointId,
360
- ...parentCheckpointId != null ? { parent_id: parentCheckpointId } : {}
445
+ ...parentCheckpointId != null ? { parent_id: parentCheckpointId } : {},
446
+ ...typeof seedStep === "number" ? { step: seedStep } : {}
361
447
  } : void 0;
362
448
  this.#applyValues(state.values, syntheticCheckpoint);
449
+ /**
450
+ * Seed subagent discovery from checkpoint messages so deep-agent
451
+ * cards render on refresh without waiting for SSE replay. Zero
452
+ * extra HTTP (reuses the `getState` payload); mirrors the
453
+ * interrupt-seeding below. `#subagents` was cleared in
454
+ * `#teardownThread`, and `seedFromCheckpointMessages` is
455
+ * idempotent, so this is safe on re-hydrate.
456
+ */
457
+ const seedMessages = state.values[this.#messagesKey];
458
+ if (Array.isArray(seedMessages)) this.#subagents.seedFromCheckpointMessages(seedMessages);
459
+ }
460
+ /**
461
+ * Converge to server truth: drop any optimistic messages the
462
+ * server state does not contain (`pending` / `failed` that were
463
+ * never persisted — e.g. a failed run's user message). Echoed
464
+ * ids were flipped to `"sent"` by `#applyValues` above and so are
465
+ * excluded from `unpersistedOptimisticIds()`.
466
+ */
467
+ const unpersisted = this.#messageMetadata.unpersistedOptimisticIds();
468
+ if (unpersisted.size > 0) {
469
+ this.#rootMessages.dropOptimisticMessages(unpersisted);
470
+ this.#messageMetadata.forget(unpersisted);
363
471
  }
364
472
  /**
365
473
  * Sync the visible interrupt list to the server's authoritative
@@ -413,27 +521,253 @@ var StreamController = class {
413
521
  else this.#resolveHydration();
414
522
  }
415
523
  /**
416
- * P0 fix: open the shared subscription on mount so in-flight
417
- * server-side runs are observed even when no local `submit()` is
418
- * active. The transport replays the run from `seq=0` on a rotating
419
- * subscribe, so late-joining is free once the subscription exists.
420
- * `isLoading` transitions are driven by the persistent root
421
- * lifecycle listener registered in `#startRootPump`.
524
+ * Open the shared subscription on mount so in-flight server-side
525
+ * runs are observed even when no local `submit()` is active — BUT
526
+ * only when the thread is actually active (see
527
+ * {@link isThreadStateActive}). A finished thread's cards are seeded
528
+ * from `getState()` + the bounded `getHistory()` below, so opening
529
+ * the depth-1 content pump just to replay a completed run and idle
530
+ * forever is pure waste. When idle we take the deferred path: the
531
+ * pump (and watcher) come up on the first local `submit()` via
532
+ * {@link #startDeferredRootPump}, exactly like a self-created thread.
533
+ * The transport replays from `seq=0` on the deferred subscribe, so
534
+ * nothing is missed.
422
535
  */
423
- const thread = this.#ensureThread(this.#currentThreadId);
536
+ const thread = this.#ensureThread(this.#currentThreadId, !threadActive);
424
537
  /**
425
- * Start the wildcard lifecycle watcher up-front for existing
426
- * threads. The root content pump runs at `depth: 1`, which covers
427
- * root-namespace and one-deep events but not arbitrarily-nested
428
- * subagent / subgraph lifecycle — the dedicated watcher handles
429
- * those.
538
+ * Start the wildcard lifecycle watcher up-front for existing,
539
+ * active threads. The root content pump runs at `depth: 1`, which
540
+ * covers root-namespace and one-deep events but not arbitrarily-
541
+ * nested subagent / subgraph lifecycle — the dedicated watcher
542
+ * handles those.
430
543
  *
431
- * For self-created (new) threads we skip — the watcher would 404
432
- * against a not-yet-existent thread. `submitRun` / `respondInput`
433
- * call `startLifecycleWatcher` on first submission to cover that
434
- * case.
544
+ * Skipped when:
545
+ * - the thread is idle/finished — there are no live events to
546
+ * watch; discovery is seeded from history below, and the watcher
547
+ * starts with the deferred pump on the first `submit()`.
548
+ * - the thread is self-created (new) — the watcher would 404
549
+ * against a not-yet-existent thread; `submitRun` / `respondInput`
550
+ * call `startLifecycleWatcher` on first submission instead.
435
551
  */
436
- if (threadExists) thread.startLifecycleWatcher();
552
+ if (threadExists && threadActive) thread.startLifecycleWatcher();
553
+ if (threadExists) {
554
+ /**
555
+ * Seed subgraph discovery and promote subagent execution
556
+ * namespaces from a single bounded `getHistory` page. Subgraph
557
+ * structure is not present in the root checkpoint messages
558
+ * (unlike subagents), so it can only be reconstructed from
559
+ * history. Fire-and-forget — not awaited into the hydration
560
+ * promise, so Suspense / first paint stay unblocked; cards fill
561
+ * in progressively when it resolves.
562
+ *
563
+ * Held in `#discoverySeedPromise` so lazy per-card
564
+ * {@link resolveSubagentNamespace} calls coalesce onto this single
565
+ * read instead of each firing their own `getHistory` walk.
566
+ */
567
+ const seed = this.#seedDiscoveryFromHistory(this.#currentThreadId).finally(() => {
568
+ if (this.#discoverySeedPromise === seed) this.#discoverySeedPromise = void 0;
569
+ });
570
+ this.#discoverySeedPromise = seed;
571
+ }
572
+ }
573
+ /**
574
+ * One bounded, non-blocking `getHistory` read at hydrate that seeds
575
+ * subgraph hosts and bulk-promotes still-default subagent execution
576
+ * namespaces. O(1) in requests regardless of subagent/subgraph count.
577
+ */
578
+ async #seedDiscoveryFromHistory(threadId) {
579
+ try {
580
+ const history = await require_namespace_from_history.getHistoryPage(this.#options.client, threadId, { limit: 20 });
581
+ if (this.#disposed || this.#currentThreadId !== threadId) return;
582
+ this.#primeScopedHistorySeedsFromHistory(threadId, history);
583
+ const hosts = require_namespace_from_history.collectSubgraphHostNamespaces(history);
584
+ this.#subgraphs.seedFromHistory(hosts);
585
+ const defaultOnlyIds = [...this.#subagents.snapshot.values()].filter(namespaceIsDefaultOnly).map((entry) => entry.id);
586
+ if (defaultOnlyIds.length > 0) {
587
+ const map = require_namespace_from_history.mapSubagentNamespaces(history, defaultOnlyIds, this.#messagesKey);
588
+ for (const [id, segment] of map) this.#subagents.applyExecutionNamespace(id, segment);
589
+ }
590
+ } catch {}
591
+ }
592
+ /**
593
+ * Lazily resolve a single subagent's execution namespace from
594
+ * checkpoint history. Intended call site: the first scoped
595
+ * `useMessages` / `useToolCalls` mount for a subagent whose namespace
596
+ * is still the default `tools:<toolCallId>`. A fallback for the
597
+ * hydrate-time bulk seed ({@link #seedDiscoveryFromHistory}) — most
598
+ * subagents are already promoted by the time a panel opens.
599
+ *
600
+ * Skips ids already promoted past default-only (SSE replay or a prior
601
+ * resolve). Concurrent calls for the same id share one `getHistory`
602
+ * walk via {@link #namespaceResolves}.
603
+ *
604
+ * @param toolCallId - Parent `task` tool-call id (the subagent's discovery key).
605
+ */
606
+ async resolveSubagentNamespace(toolCallId) {
607
+ if (this.#disposed) return;
608
+ const threadId = this.#currentThreadId;
609
+ if (threadId == null) return;
610
+ if (!namespaceIsDefaultOnly(this.#subagents.snapshot.get(toolCallId))) return;
611
+ const inflight = this.#namespaceResolves.get(toolCallId);
612
+ if (inflight != null) return inflight;
613
+ const run = (async () => {
614
+ try {
615
+ /**
616
+ * Coalesce onto the hydrate-time discovery seed. That single
617
+ * bounded `getHistory` page bulk-promotes every default-only
618
+ * subagent, so when many cards mount at once (the common
619
+ * reconnect case) they all await this one read instead of each
620
+ * firing their own walk. Re-check after it settles: usually the
621
+ * bulk seed already promoted us and no further fetch is needed.
622
+ */
623
+ const seed = this.#discoverySeedPromise;
624
+ if (seed != null) {
625
+ await seed;
626
+ if (this.#disposed || this.#currentThreadId !== threadId) return;
627
+ if (!namespaceIsDefaultOnly(this.#subagents.snapshot.get(toolCallId))) return;
628
+ }
629
+ const map = await require_namespace_from_history.resolveSubagentNamespaces(this.#options.client, threadId, [toolCallId], { messagesKey: this.#messagesKey });
630
+ if (this.#disposed || this.#currentThreadId !== threadId) return;
631
+ const segment = map.get(toolCallId);
632
+ if (segment != null) this.#subagents.applyExecutionNamespace(toolCallId, segment);
633
+ } catch {} finally {
634
+ this.#namespaceResolves.delete(toolCallId);
635
+ }
636
+ })();
637
+ this.#namespaceResolves.set(toolCallId, run);
638
+ return run;
639
+ }
640
+ /**
641
+ * Try to satisfy a scoped selector projection from checkpoint history
642
+ * instead of opening a scoped `/events` replay.
643
+ *
644
+ * This is only valid while the root pump is deferred, which means hydrate
645
+ * has classified the thread as idle/stale. Active and interrupted threads
646
+ * must keep using SSE so ongoing work and resumes are observed. For an idle
647
+ * thread, though, a late-mounted subagent card only needs the latest scoped
648
+ * checkpoint snapshot; opening `/events` just asks the server to replay work
649
+ * that already finished and can be slow for namespaces discovered from
650
+ * history.
651
+ *
652
+ * Returns `true` when the projection was handled without `/events`. That can
653
+ * mean either the store was seeded from namespace-specific history, or the
654
+ * projection targeted a default subagent namespace that should be skipped
655
+ * because hydrate promoted it to its execution namespace. Returns `false`
656
+ * when the caller should fall back to the normal subscription path.
657
+ */
658
+ async #trySeedProjectionFromHistory(params) {
659
+ const threadId = this.#currentThreadId;
660
+ if (this.#disposed || threadId == null || params.namespace.length === 0 || !this.#rootPumpDeferred || this.#selfCreatedThreadIds.has(threadId)) return false;
661
+ if (await this.#skipDefaultSubagentProjection(params.namespace, threadId)) return true;
662
+ if (this.#disposed || this.#currentThreadId !== threadId || !this.#rootPumpDeferred) return false;
663
+ const seed = await this.#getScopedHistorySeed(threadId, params.namespace);
664
+ if (seed == null || this.#disposed || this.#currentThreadId !== threadId || !this.#rootPumpDeferred) return false;
665
+ if (await this.#skipDefaultSubagentProjection(params.namespace, threadId)) return true;
666
+ if (params.kind === "messages") {
667
+ params.store.setValue(seed.messages);
668
+ return true;
669
+ }
670
+ params.store.setValue(seed.toolCalls);
671
+ return true;
672
+ }
673
+ /**
674
+ * Suppress subscriptions for placeholder subagent namespaces once hydrate has
675
+ * resolved the real execution namespace.
676
+ *
677
+ * Deep-agent discovery first creates cards at `tools:<toolCallId>`. The
678
+ * actual worker history usually lives under a different checkpoint namespace
679
+ * such as `tools:<uuid>`, and hydrate resolves that mapping from the bounded
680
+ * root history seed. React/Vue/Svelte/Angular selector effects can mount
681
+ * while that seed is still in flight, so this helper waits for it and then
682
+ * returns `true` when the original placeholder namespace is stale. Returning
683
+ * `true` tells the projection runtime not to open an `/events` subscription
684
+ * for the wrong namespace; the framework will re-render with the promoted
685
+ * card namespace and acquire the real projection.
686
+ */
687
+ async #skipDefaultSubagentProjection(namespace, threadId) {
688
+ const toolCallId = defaultSubagentToolCallId(namespace);
689
+ if (toolCallId == null) return false;
690
+ if (!namespaceIsDefaultOnly(this.#subagents.snapshot.get(toolCallId))) return false;
691
+ const seed = this.#discoverySeedPromise;
692
+ if (seed != null) await seed;
693
+ if (this.#disposed || this.#currentThreadId !== threadId) return true;
694
+ return !namespaceIsDefaultOnly(this.#subagents.snapshot.get(toolCallId));
695
+ }
696
+ /**
697
+ * Load and cache the latest checkpoint snapshot for one scoped namespace.
698
+ *
699
+ * `useMessages(stream, subagent)` and `useToolCalls(stream, subagent)` often
700
+ * mount together. Both need the same namespace-specific history page, so the
701
+ * controller keeps an in-flight promise per `threadId + checkpoint_ns`.
702
+ * The cache may already be primed by the hydrate-time discovery history page;
703
+ * otherwise this method performs a narrow `checkpoint_ns` read and derives
704
+ * both projection snapshots from that one response:
705
+ *
706
+ * - `messages` are coerced with the stream-local message coercion rules, so
707
+ * serialized `content_blocks` and tool-call metadata hydrate correctly.
708
+ * - `toolCalls` are reconstructed from AI tool calls plus matching
709
+ * ToolMessages, enough for finished/stale card panels without replaying
710
+ * the `tools` channel.
711
+ *
712
+ * Returns `null` when history does not contain usable values, or the request
713
+ * fails. Callers treat that as a signal to fall back to `/events` so custom
714
+ * servers or unusual state shapes still work.
715
+ */
716
+ #getScopedHistorySeed(threadId, namespace) {
717
+ const checkpointNs = require_namespace.namespaceKey(namespace);
718
+ const key = `${threadId}|${checkpointNs}`;
719
+ const existing = this.#scopedHistorySeeds.get(key);
720
+ if (existing != null) return existing;
721
+ const seed = (async () => {
722
+ try {
723
+ const values = (await require_namespace_from_history.getHistoryPage(this.#options.client, threadId, {
724
+ limit: 1,
725
+ checkpoint: { checkpoint_ns: checkpointNs }
726
+ }))[0]?.values;
727
+ if (values == null || typeof values !== "object") return null;
728
+ const messages = extractAndCoerceMessagesWithFallback(values, this.#messagesKey);
729
+ if (messages == null) return null;
730
+ return {
731
+ messages,
732
+ toolCalls: require_tool_calls.seedToolCallsFromMessages(namespace, messages)
733
+ };
734
+ } catch {
735
+ return null;
736
+ }
737
+ })();
738
+ this.#scopedHistorySeeds.set(key, seed);
739
+ return seed;
740
+ }
741
+ /**
742
+ * Reuse the hydrate-time discovery history page as scoped projection data
743
+ * when it already contains checkpoint values for a namespace.
744
+ *
745
+ * The discovery read is required to resolve subagent execution namespaces and
746
+ * subgraph hosts. That same page often includes the latest values for those
747
+ * namespaces, so priming `#scopedHistorySeeds` here lets later
748
+ * `useMessages(stream, subagent)` / `useToolCalls(stream, subagent)` mounts
749
+ * hydrate from memory instead of issuing an immediate second `getHistory`
750
+ * request. If a namespace is not present in the bounded page,
751
+ * `#getScopedHistorySeed` still falls back to a targeted `checkpoint_ns`
752
+ * history read.
753
+ */
754
+ #primeScopedHistorySeedsFromHistory(threadId, history) {
755
+ for (const state of history) {
756
+ const checkpointNs = state.checkpoint?.checkpoint_ns;
757
+ if (typeof checkpointNs !== "string" || checkpointNs.length === 0) continue;
758
+ const namespace = checkpointNs.split("\0").filter((segment) => segment.length > 0);
759
+ if (namespace.length === 0) continue;
760
+ const key = `${threadId}|${require_namespace.namespaceKey(namespace)}`;
761
+ if (this.#scopedHistorySeeds.has(key)) continue;
762
+ const values = state.values;
763
+ if (values == null || typeof values !== "object") continue;
764
+ const messages = extractAndCoerceMessagesWithFallback(values, this.#messagesKey);
765
+ if (messages == null) continue;
766
+ this.#scopedHistorySeeds.set(key, Promise.resolve({
767
+ messages,
768
+ toolCalls: require_tool_calls.seedToolCallsFromMessages(namespace, messages)
769
+ }));
770
+ }
437
771
  }
438
772
  /**
439
773
  * Submit input to the active thread.
@@ -872,6 +1206,7 @@ var StreamController = class {
872
1206
  this.#lifecycleLoading.reset();
873
1207
  this.#subagents.reset();
874
1208
  this.#subgraphs.reset();
1209
+ this.#scopedHistorySeeds.clear();
875
1210
  this.#activeRunId = void 0;
876
1211
  this.#localRunDepth = 0;
877
1212
  this.#messageMetadata.reset();
@@ -1148,9 +1483,67 @@ var StreamController = class {
1148
1483
  * @param raw - Raw `values` channel payload.
1149
1484
  * @param checkpoint - Optional checkpoint envelope paired with the values event.
1150
1485
  */
1486
+ /**
1487
+ * Apply a submit input optimistically to the root projection before
1488
+ * the server responds. Mints stable ids for id-less messages (so the
1489
+ * server echo reconciles by id), appends them to the projection, and
1490
+ * shallow-merges non-message input keys into `values`.
1491
+ *
1492
+ * Returns the dispatch payload (id-injected) for the coordinator to
1493
+ * send, plus an {@link OptimisticHandle} for terminal reconciliation.
1494
+ * Returns `undefined` when optimistic UI is disabled or there is
1495
+ * nothing to echo, in which case the coordinator dispatches the raw
1496
+ * input unchanged.
1497
+ *
1498
+ * @param input - Raw input passed to `submit()`.
1499
+ */
1500
+ #beginOptimistic(input) {
1501
+ if (this.#options.optimistic === false) return void 0;
1502
+ if (input == null || typeof input !== "object" || Array.isArray(input)) return;
1503
+ const prepared = require_optimistic_input.prepareOptimisticInput(input, this.#messagesKey, () => (0, uuid.v7)());
1504
+ const extraKeys = Object.keys(prepared.extraValues);
1505
+ if (prepared.echoedIds.length === 0 && extraKeys.length === 0) return;
1506
+ const currentValues = this.rootStore.getSnapshot().values;
1507
+ const restoreKeys = extraKeys.map((key) => ({
1508
+ key,
1509
+ hadKey: Object.prototype.hasOwnProperty.call(currentValues, key),
1510
+ prevValue: currentValues[key]
1511
+ }));
1512
+ this.#sawValuesForRun = false;
1513
+ this.#rootMessages.appendOptimistic(prepared.optimisticMessages, prepared.extraValues);
1514
+ if (prepared.echoedIds.length > 0) this.#messageMetadata.markPending(prepared.echoedIds);
1515
+ return {
1516
+ dispatchInput: prepared.dispatchInput,
1517
+ handle: {
1518
+ echoedIds: prepared.echoedIds,
1519
+ restoreKeys
1520
+ }
1521
+ };
1522
+ }
1523
+ /**
1524
+ * Reconcile optimistic state when a run terminates.
1525
+ *
1526
+ * - Messages: any echoed id still `"pending"` (never echoed by the
1527
+ * server) is flipped to `"sent"` on success/interrupt, or
1528
+ * `"failed"` on failure/abort. Ids the server already echoed were
1529
+ * flipped to `"sent"` in {@link #applyValues} and are untouched.
1530
+ * - Non-message keys: rolled back to their pre-submit values when no
1531
+ * server `values` event landed during the run (otherwise the
1532
+ * server snapshot already reconciled them). Skipped on abort,
1533
+ * where a superseding run (or `stop()`) owns subsequent state.
1534
+ *
1535
+ * @param handle - Handle returned by {@link #beginOptimistic}.
1536
+ * @param event - Terminal lifecycle event for the run.
1537
+ */
1538
+ #settleOptimistic(handle, event) {
1539
+ const failed = event === "failed" || event === "aborted";
1540
+ this.#messageMetadata.resolvePending(handle.echoedIds, failed ? "failed" : "sent");
1541
+ if (event !== "aborted" && !this.#sawValuesForRun) this.#rootMessages.restoreValueKeys(handle.restoreKeys);
1542
+ }
1151
1543
  #applyValues(raw, checkpoint) {
1152
1544
  if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return;
1153
1545
  const state = raw;
1546
+ this.#sawValuesForRun = true;
1154
1547
  /**
1155
1548
  * Surface parent_checkpoint per-message when the values event
1156
1549
  * carries the lightweight checkpoint envelope (populated by
@@ -1165,7 +1558,7 @@ var StreamController = class {
1165
1558
  let nextValues;
1166
1559
  let nextMessages;
1167
1560
  if (Array.isArray(maybeMessages)) {
1168
- const coerced = require_messages.ensureMessageInstances(maybeMessages);
1561
+ const coerced = require_message_coercion.ensureMessageInstances(maybeMessages);
1169
1562
  nextValues = {
1170
1563
  ...state,
1171
1564
  [this.#messagesKey]: coerced
@@ -1175,15 +1568,18 @@ var StreamController = class {
1175
1568
  nextValues = state;
1176
1569
  nextMessages = [];
1177
1570
  }
1178
- this.#rootMessages.applyValues(nextValues, nextMessages);
1179
- if (nextMessages.length > 0) this.rootStore.setState((s) => {
1180
- const toolCalls = require_tool_calls.reconcileToolCallsFromMessages(s.toolCalls, nextMessages);
1181
- if (toolCalls === s.toolCalls) return s;
1182
- return {
1183
- ...s,
1184
- toolCalls
1185
- };
1186
- });
1571
+ this.#rootMessages.applyValues(nextValues, nextMessages, { step: checkpoint?.step });
1572
+ if (nextMessages.length > 0) {
1573
+ this.#messageMetadata.resolvePending(nextMessages.map((m) => m.id).filter((id) => typeof id === "string"), "sent");
1574
+ this.rootStore.setState((s) => {
1575
+ const toolCalls = require_tool_calls.reconcileToolCallsFromMessages(s.toolCalls, nextMessages);
1576
+ if (toolCalls === s.toolCalls) return s;
1577
+ return {
1578
+ ...s,
1579
+ toolCalls
1580
+ };
1581
+ });
1582
+ }
1187
1583
  }
1188
1584
  /**
1189
1585
  * Mirror root protocol interrupts into the root snapshot.
@@ -1314,6 +1710,23 @@ var StreamController = class {
1314
1710
  }
1315
1711
  };
1316
1712
  /**
1713
+ * True when a subagent still sits on its default `tools:<toolCallId>`
1714
+ * namespace — i.e. no execution namespace has been observed (via SSE
1715
+ * replay) or resolved (via history) yet. Used to gate lazy namespace
1716
+ * resolution so already-promoted subagents aren't re-fetched.
1717
+ */
1718
+ function namespaceIsDefaultOnly(entry) {
1719
+ if (entry == null) return false;
1720
+ return entry.namespace.length === 1 && entry.namespace[0] === `tools:${entry.id}`;
1721
+ }
1722
+ function defaultSubagentToolCallId(namespace) {
1723
+ if (namespace.length !== 1) return void 0;
1724
+ const segment = namespace[0];
1725
+ if (!segment.startsWith("tools:")) return void 0;
1726
+ const id = segment.slice(6);
1727
+ return id.length > 0 ? id : void 0;
1728
+ }
1729
+ /**
1317
1730
  * Extract and coerce the configured messages key from a values object.
1318
1731
  *
1319
1732
  * @param values - State values object to read from.
@@ -1322,7 +1735,13 @@ var StreamController = class {
1322
1735
  function extractAndCoerceMessages(values, messagesKey) {
1323
1736
  const raw = values[messagesKey];
1324
1737
  if (!Array.isArray(raw)) return [];
1325
- return require_messages.ensureMessageInstances(raw);
1738
+ return require_message_coercion.ensureMessageInstances(raw);
1739
+ }
1740
+ function extractAndCoerceMessagesWithFallback(values, messagesKey) {
1741
+ let raw = values[messagesKey];
1742
+ if (!Array.isArray(raw) && messagesKey !== "messages") raw = values.messages;
1743
+ if (!Array.isArray(raw)) return null;
1744
+ return require_message_coercion.ensureMessageInstances(raw);
1326
1745
  }
1327
1746
  //#endregion
1328
1747
  exports.ROOT_PUMP_CHANNELS = ROOT_PUMP_CHANNELS;