@langchain/langgraph-sdk 1.9.28 → 1.9.29

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.
@@ -120,14 +120,15 @@ declare class StreamController<StateType extends object = Record<string, unknown
120
120
  * oldest and picks the first entry whose `interruptId` has not already
121
121
  * been resolved by a prior `respond()` call. That entry may be at the
122
122
  * root (`namespace: []`) or inside a subgraph (non-empty `namespace`).
123
- * This is **not** the same as {@link RootSnapshot.interrupts
124
- * `rootStore.interrupts[0]`} / framework `stream.interrupt`, which only
125
- * mirrors root-namespace interrupts for UI convenience.
123
+ * {@link RootSnapshot.interrupts `rootStore.interrupts`} /
124
+ * framework `stream.interrupts` mirrors the same pending interrupts
125
+ * (including nested namespaces) for UI rendering.
126
126
  *
127
127
  * Omitting `interruptId` is fine when exactly one interrupt is pending.
128
128
  * When several can be active (parallel subagents, fan-out, nested
129
- * graphs), pass an explicit `interruptId` (and `namespace` for subgraph
130
- * interrupts) so you resume the interrupt the user acted on.
129
+ * graphs), pass an explicit `interruptId` so you resume the interrupt
130
+ * the user acted on. `namespace` is resolved automatically from
131
+ * `thread.interrupts` / `stream.interrupts` when omitted.
131
132
  *
132
133
  * To resume several interrupts pending at the same checkpoint in one
133
134
  * command, use {@link respondAll} — sequential single `respond()` calls
@@ -135,9 +136,8 @@ declare class StreamController<StateType extends object = Record<string, unknown
135
136
  * others with no interrupted run to respond to.
136
137
  *
137
138
  * The server validates `namespace` against the pending interrupt. Root
138
- * interrupts use `namespace: []` (the default when `namespace` is
139
- * omitted). Subgraph interrupts require the exact tuple from
140
- * `getThread()?.interrupts` — see the example below.
139
+ * interrupts use `namespace: []`. Subgraph interrupts carry the exact
140
+ * tuple on each {@link Interrupt} / {@link InterruptPayload} entry.
141
141
  *
142
142
  * @param response - Payload sent back to the interrupted namespace.
143
143
  * @param options - Optional target (`interruptId` / `namespace`) and
@@ -158,27 +158,16 @@ declare class StreamController<StateType extends object = Record<string, unknown
158
158
  * );
159
159
  * ```
160
160
  *
161
- * @example Multiple root interrupts — target by id
161
+ * @example Multiple / nested interrupts — target by id
162
162
  * ```tsx
163
163
  * for (const intr of stream.interrupts) {
164
164
  * await stream.respond(decide(intr.value), { interruptId: intr.id! });
165
165
  * }
166
166
  * ```
167
167
  *
168
- * @example Subgraph interrupt — read `namespace` from the thread stream
169
- * ```tsx
170
- * const thread = stream.getThread();
171
- * for (const entry of thread?.interrupts ?? []) {
172
- * await stream.respond(buildResponse(entry.payload), {
173
- * interruptId: entry.interruptId,
174
- * namespace: entry.namespace,
175
- * });
176
- * }
177
- * ```
178
- *
179
- * Each {@link InterruptPayload} on `thread.interrupts` mirrors an
180
- * `input.requested` event: `{ interruptId, payload, namespace }`.
181
- * Nested interrupts may appear here but not on `stream.interrupts`.
168
+ * Each {@link InterruptPayload} on `thread.interrupts` and each
169
+ * {@link Interrupt} on `stream.interrupts` mirrors an
170
+ * `input.requested` event with its protocol `namespace`.
182
171
  */
183
172
  respond(response: unknown, options?: StreamRespondOptions<ConfigurableType>): Promise<void>;
184
173
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.cts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;AA6KA;;cAAa,kBAAA,WAA6B,OAAA;;AAkC1C;;;;;;;;;;;;cAAa,gBAAA,4BACgB,MAAA,8EAEO,MAAA;EAAA;WAEzB,SAAA,EAAW,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,aAAA;EAAA,SAC/C,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,mBAAA,EAAqB,WAAA,CAAY,iBAAA;EAAA,SACjC,oBAAA,EAAsB,WAAA,CAAY,kBAAA;EAAA,SAClC,UAAA,EAAY,WAAA,CAAY,uBAAA,CAAwB,SAAA;EAAA,SAChD,QAAA,EAAU,eAAA;EAsH0B;;;;;EAA7C,WAAA,CAAY,OAAA,EAAS,uBAAA,CAAwB,SAAA;EA0vBF;;;;;;;EAAA,IA/oBvC,gBAAA,CAAA,GAAoB,OAAA;EA+zBS;;;;;;;;EAtwB3B,OAAA,CAAQ,QAAA,mBAA2B,OAAA;EAm/BpB;;;;;;;;;;;;;;EAlqBf,wBAAA,CAAyB,UAAA,WAAqB,OAAA;EAhnB5B;;;;;;;;EAm3BlB,MAAA,CACJ,KAAA,WACA,OAAA,GAAU,mBAAA,CAAoB,SAAA,EAAW,gBAAA,IACxC,OAAA;EAn3B4B;;;;;;EA63BzB,IAAA,CAAK,OAAA,GAAU,iBAAA,GAAoB,OAAA;EA33BtB;;;;EA+4Bb,UAAA,CAAA,GAAc,OAAA;EA9qBhB;;;;;;;;;;;EA8uBE,YAAA,CAAa,EAAA,WAAa,OAAA;EA/FW;;;EAsGrC,UAAA,CAAA,GAAc,OAAA;EA3FC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmKf,OAAA,CACJ,QAAA,WACA,OAAA,GAAU,oBAAA,CAAqB,gBAAA,IAC9B,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4GG,UAAA,CACJ,aAAA,EAAe,MAAA,mBACf,OAAA,GAAU,uBAAA,CAAwB,gBAAA,IACjC,OAAA;;;;EA4DG,OAAA,CAAA,GAAW,OAAA;;;;;;;;;;;;;;;;EAyBjB,QAAA,CAAA;;;;;;EA4BA,SAAA,CAAA,GAAa,YAAA;;;;;;;;EAWb,eAAA,CACE,QAAA,GAAW,MAAA,EAAQ,YAAA;AAAA"}
1
+ {"version":3,"file":"controller.d.cts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;AA6KA;;cAAa,kBAAA,WAA6B,OAAA;;AAkC1C;;;;;;;;;;;;cAAa,gBAAA,4BACgB,MAAA,8EAEO,MAAA;EAAA;WAEzB,SAAA,EAAW,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,aAAA;EAAA,SAC/C,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,mBAAA,EAAqB,WAAA,CAAY,iBAAA;EAAA,SACjC,oBAAA,EAAsB,WAAA,CAAY,kBAAA;EAAA,SAClC,UAAA,EAAY,WAAA,CAAY,uBAAA,CAAwB,SAAA;EAAA,SAChD,QAAA,EAAU,eAAA;EAgI0B;;;;;EAA7C,WAAA,CAAY,OAAA,EAAS,uBAAA,CAAwB,SAAA;EAsyBF;;;;;;;EAAA,IAvrBvC,gBAAA,CAAA,GAAoB,OAAA;EA41BS;;;;;;;;EAnyB3B,OAAA,CAAQ,QAAA,mBAA2B,OAAA;EAihCpB;;;;;;;;;;;;;;EAzpBf,wBAAA,CAAyB,UAAA,WAAqB,OAAA;EArqB5B;;;;;;;;EAy6BlB,MAAA,CACJ,KAAA,WACA,OAAA,GAAU,mBAAA,CAAoB,SAAA,EAAW,gBAAA,IACxC,OAAA;EAz6B4B;;;;;;EAm7BzB,IAAA,CAAK,OAAA,GAAU,iBAAA,GAAoB,OAAA;EAj7BtB;;;;EAq8Bb,UAAA,CAAA,GAAc,OAAA;EAttBhB;;;;;;;;;;;EAsxBE,YAAA,CAAa,EAAA,WAAa,OAAA;EA/FW;;;EAsGrC,UAAA,CAAA,GAAc,OAAA;EA3FC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwJf,OAAA,CACJ,QAAA,WACA,OAAA,GAAU,oBAAA,CAAqB,gBAAA,IAC9B,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+GG,UAAA,CACJ,aAAA,EAAe,MAAA,mBACf,OAAA,GAAU,uBAAA,CAAwB,gBAAA,IACjC,OAAA;;;;EA0DG,OAAA,CAAA,GAAW,OAAA;;;;;;;;;;;;;;;;EAyBjB,QAAA,CAAA;;;;;;EA4BA,SAAA,CAAA,GAAa,YAAA;;;;;;;;EAWb,eAAA,CACE,QAAA,GAAW,MAAA,EAAQ,YAAA;AAAA"}
@@ -120,14 +120,15 @@ declare class StreamController<StateType extends object = Record<string, unknown
120
120
  * oldest and picks the first entry whose `interruptId` has not already
121
121
  * been resolved by a prior `respond()` call. That entry may be at the
122
122
  * root (`namespace: []`) or inside a subgraph (non-empty `namespace`).
123
- * This is **not** the same as {@link RootSnapshot.interrupts
124
- * `rootStore.interrupts[0]`} / framework `stream.interrupt`, which only
125
- * mirrors root-namespace interrupts for UI convenience.
123
+ * {@link RootSnapshot.interrupts `rootStore.interrupts`} /
124
+ * framework `stream.interrupts` mirrors the same pending interrupts
125
+ * (including nested namespaces) for UI rendering.
126
126
  *
127
127
  * Omitting `interruptId` is fine when exactly one interrupt is pending.
128
128
  * When several can be active (parallel subagents, fan-out, nested
129
- * graphs), pass an explicit `interruptId` (and `namespace` for subgraph
130
- * interrupts) so you resume the interrupt the user acted on.
129
+ * graphs), pass an explicit `interruptId` so you resume the interrupt
130
+ * the user acted on. `namespace` is resolved automatically from
131
+ * `thread.interrupts` / `stream.interrupts` when omitted.
131
132
  *
132
133
  * To resume several interrupts pending at the same checkpoint in one
133
134
  * command, use {@link respondAll} — sequential single `respond()` calls
@@ -135,9 +136,8 @@ declare class StreamController<StateType extends object = Record<string, unknown
135
136
  * others with no interrupted run to respond to.
136
137
  *
137
138
  * The server validates `namespace` against the pending interrupt. Root
138
- * interrupts use `namespace: []` (the default when `namespace` is
139
- * omitted). Subgraph interrupts require the exact tuple from
140
- * `getThread()?.interrupts` — see the example below.
139
+ * interrupts use `namespace: []`. Subgraph interrupts carry the exact
140
+ * tuple on each {@link Interrupt} / {@link InterruptPayload} entry.
141
141
  *
142
142
  * @param response - Payload sent back to the interrupted namespace.
143
143
  * @param options - Optional target (`interruptId` / `namespace`) and
@@ -158,27 +158,16 @@ declare class StreamController<StateType extends object = Record<string, unknown
158
158
  * );
159
159
  * ```
160
160
  *
161
- * @example Multiple root interrupts — target by id
161
+ * @example Multiple / nested interrupts — target by id
162
162
  * ```tsx
163
163
  * for (const intr of stream.interrupts) {
164
164
  * await stream.respond(decide(intr.value), { interruptId: intr.id! });
165
165
  * }
166
166
  * ```
167
167
  *
168
- * @example Subgraph interrupt — read `namespace` from the thread stream
169
- * ```tsx
170
- * const thread = stream.getThread();
171
- * for (const entry of thread?.interrupts ?? []) {
172
- * await stream.respond(buildResponse(entry.payload), {
173
- * interruptId: entry.interruptId,
174
- * namespace: entry.namespace,
175
- * });
176
- * }
177
- * ```
178
- *
179
- * Each {@link InterruptPayload} on `thread.interrupts` mirrors an
180
- * `input.requested` event: `{ interruptId, payload, namespace }`.
181
- * Nested interrupts may appear here but not on `stream.interrupts`.
168
+ * Each {@link InterruptPayload} on `thread.interrupts` and each
169
+ * {@link Interrupt} on `stream.interrupts` mirrors an
170
+ * `input.requested` event with its protocol `namespace`.
182
171
  */
183
172
  respond(response: unknown, options?: StreamRespondOptions<ConfigurableType>): Promise<void>;
184
173
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;;AA6KA;cAAa,kBAAA,WAA6B,OAAA;;;AAkC1C;;;;;;;;;;;cAAa,gBAAA,4BACgB,MAAA,8EAEO,MAAA;EAAA;WAEzB,SAAA,EAAW,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,aAAA;EAAA,SAC/C,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,mBAAA,EAAqB,WAAA,CAAY,iBAAA;EAAA,SACjC,oBAAA,EAAsB,WAAA,CAAY,kBAAA;EAAA,SAClC,UAAA,EAAY,WAAA,CAAY,uBAAA,CAAwB,SAAA;EAAA,SAChD,QAAA,EAAU,eAAA;EAAA;;;;;EAsHnB,WAAA,CAAY,OAAA,EAAS,uBAAA,CAAwB,SAAA;EA0vBb;;;;;;;EAAA,IA/oB5B,gBAAA,CAAA,GAAoB,OAAA;EAqvBJ;;;;;;;;EA5rBd,OAAA,CAAQ,QAAA,mBAA2B,OAAA;EAu+B5B;;;;;;;;;;;;;;EAtpBP,wBAAA,CAAyB,UAAA,WAAqB,OAAA;EAhnB3C;;;;;;;;EAm3BH,MAAA,CACJ,KAAA,WACA,OAAA,GAAU,mBAAA,CAAoB,SAAA,EAAW,gBAAA,IACxC,OAAA;EAn3BM;;;;;;EA63BH,IAAA,CAAK,OAAA,GAAU,iBAAA,GAAoB,OAAA;EA33BhC;;;;EA+4BH,UAAA,CAAA,GAAc,OAAA;EAzxBR;;;;;;;;;;;EAy1BN,YAAA,CAAa,EAAA,WAAa,OAAA;EA/FA;;;EAsG1B,UAAA,CAAA,GAAc,OAAA;EA3Fd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmKA,OAAA,CACJ,QAAA,WACA,OAAA,GAAU,oBAAA,CAAqB,gBAAA,IAC9B,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4GG,UAAA,CACJ,aAAA,EAAe,MAAA,mBACf,OAAA,GAAU,uBAAA,CAAwB,gBAAA,IACjC,OAAA;;;;EA4DG,OAAA,CAAA,GAAW,OAAA;;;;;;;;;;;;;;;;EAyBjB,QAAA,CAAA;;;;;;EA4BA,SAAA,CAAA,GAAa,YAAA;;;;;;;;EAWb,eAAA,CACE,QAAA,GAAW,MAAA,EAAQ,YAAA;AAAA"}
1
+ {"version":3,"file":"controller.d.ts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;;AA6KA;cAAa,kBAAA,WAA6B,OAAA;;;AAkC1C;;;;;;;;;;;cAAa,gBAAA,4BACgB,MAAA,8EAEO,MAAA;EAAA;WAEzB,SAAA,EAAW,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,aAAA;EAAA,SAC/C,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,mBAAA,EAAqB,WAAA,CAAY,iBAAA;EAAA,SACjC,oBAAA,EAAsB,WAAA,CAAY,kBAAA;EAAA,SAClC,UAAA,EAAY,WAAA,CAAY,uBAAA,CAAwB,SAAA;EAAA,SAChD,QAAA,EAAU,eAAA;EAAA;;;;;EAgInB,WAAA,CAAY,OAAA,EAAS,uBAAA,CAAwB,SAAA;EAsyBb;;;;;;;EAAA,IAvrB5B,gBAAA,CAAA,GAAoB,OAAA;EA6xBJ;;;;;;;;EApuBd,OAAA,CAAQ,QAAA,mBAA2B,OAAA;EAqgC5B;;;;;;;;;;;;;;EA7oBP,wBAAA,CAAyB,UAAA,WAAqB,OAAA;EArqB3C;;;;;;;;EAy6BH,MAAA,CACJ,KAAA,WACA,OAAA,GAAU,mBAAA,CAAoB,SAAA,EAAW,gBAAA,IACxC,OAAA;EAz6BM;;;;;;EAm7BH,IAAA,CAAK,OAAA,GAAU,iBAAA,GAAoB,OAAA;EAj7BhC;;;;EAq8BH,UAAA,CAAA,GAAc,OAAA;EAr0BR;;;;;;;;;;;EAq4BN,YAAA,CAAa,EAAA,WAAa,OAAA;EA/FA;;;EAsG1B,UAAA,CAAA,GAAc,OAAA;EA3Fd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwJA,OAAA,CACJ,QAAA,WACA,OAAA,GAAU,oBAAA,CAAqB,gBAAA,IAC9B,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+GG,UAAA,CACJ,aAAA,EAAe,MAAA,mBACf,OAAA,GAAU,uBAAA,CAAwB,gBAAA,IACjC,OAAA;;;;EA0DG,OAAA,CAAA,GAAW,OAAA;;;;;;;;;;;;;;;;EAyBjB,QAAA,CAAA;;;;;;EA4BA,SAAA,CAAA,GAAa,YAAA;;;;;;;;EAWb,eAAA,CACE,QAAA,GAAW,MAAA,EAAQ,YAAA;AAAA"}
@@ -169,13 +169,23 @@ var StreamController = class {
169
169
  */
170
170
  #submitGeneration = 0;
171
171
  /**
172
- * Thread ids we minted client-side on first `submit()`. Keeping them
173
- * here lets `hydrate()` skip the `threads.getState()` round-trip —
174
- * we know there is nothing checkpointed server-side yet (and the
175
- * request would 404 and surface a spurious error to the UI).
172
+ * Thread ids this controller minted client-side on first `submit()`.
173
+ * `hydrate()` skips `threads.getState()` for these — we know there
174
+ * is nothing checkpointed yet. Cleared once dispatch commits the
175
+ * thread server-side.
176
176
  */
177
177
  #selfCreatedThreadIds = /* @__PURE__ */ new Set();
178
178
  /**
179
+ * Caller-supplied thread ids that 404'd on hydrate. Unlike
180
+ * {@link #selfCreatedThreadIds}, these are revalidated on every
181
+ * hydrate: another client may create the same id between visits.
182
+ * While marked missing, `submit()` still defers the root SSE pump
183
+ * until `run.start` / `commands` commits the row — joining
184
+ * `/stream/events` beforehand leaves a dead subscription on
185
+ * langgraph_api's in-mem runtime.
186
+ */
187
+ #missingThreadIds = /* @__PURE__ */ new Set();
188
+ /**
179
189
  * In-flight per-subagent namespace resolutions, keyed by tool-call
180
190
  * id. De-dupes concurrent {@link resolveSubagentNamespace} calls so
181
191
  * re-renders / multiple consumers of the same subagent don't issue
@@ -270,12 +280,14 @@ var StreamController = class {
270
280
  rememberSelfCreatedThreadId: (threadId) => {
271
281
  this.#selfCreatedThreadIds.add(threadId);
272
282
  },
283
+ isSelfCreatedThreadId: (threadId) => this.#selfCreatedThreadIds.has(threadId) || this.#missingThreadIds.has(threadId),
273
284
  hydrate: (threadId) => this.hydrate(threadId),
274
285
  ensureThread: (threadId, deferRootPump) => this.#ensureThread(threadId, deferRootPump),
275
286
  startDeferredRootPump: () => this.#startDeferredRootPump(),
276
287
  abandonDeferredRootPump: () => this.#abandonDeferredRootPump(),
277
288
  forgetSelfCreatedThreadId: (threadId) => {
278
289
  this.#selfCreatedThreadIds.delete(threadId);
290
+ this.#missingThreadIds.delete(threadId);
279
291
  },
280
292
  waitForRootPumpReady: () => this.#rootPumpReady,
281
293
  awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),
@@ -426,12 +438,14 @@ var StreamController = class {
426
438
  const hydratedThreadId = this.#currentThreadId;
427
439
  let hydrationError;
428
440
  let threadExists = false;
441
+ let threadMissing = false;
429
442
  let threadActive = true;
430
443
  try {
431
444
  const state = await this.#fetchHydrationState();
432
445
  if (this.#disposed || this.#currentThreadId !== hydratedThreadId) return;
433
446
  threadExists = state != null;
434
447
  threadActive = isThreadStateActive(state);
448
+ this.#missingThreadIds.delete(hydratedThreadId);
435
449
  if (state?.values != null) {
436
450
  /**
437
451
  * `threads.getState()` returns the legacy `ThreadState` shape
@@ -516,6 +530,8 @@ var StreamController = class {
516
530
  const activeIds = /* @__PURE__ */ new Set();
517
531
  for (const task of state.tasks) {
518
532
  if (!Array.isArray(task?.interrupts)) continue;
533
+ const checkpointNs = task?.checkpoint?.checkpoint_ns;
534
+ const namespace = typeof checkpointNs === "string" && checkpointNs.length > 0 ? checkpointNs.split("|").filter((segment) => segment.length > 0) : [...ROOT_NAMESPACE];
519
535
  for (const interrupt of task.interrupts) {
520
536
  const typed = interrupt;
521
537
  const id = typed?.id;
@@ -523,7 +539,8 @@ var StreamController = class {
523
539
  activeIds.add(id);
524
540
  activeInterrupts.push(normalizeInterruptForClient({
525
541
  id,
526
- value: typed?.value
542
+ value: typed?.value,
543
+ namespace: Array.isArray(typed?.namespace) ? [...typed.namespace] : namespace
527
544
  }));
528
545
  }
529
546
  }
@@ -541,7 +558,7 @@ var StreamController = class {
541
558
  ...s,
542
559
  error
543
560
  }));
544
- }
561
+ } else threadMissing = true;
545
562
  } finally {
546
563
  this.rootStore.setState((s) => ({
547
564
  ...s,
@@ -551,6 +568,20 @@ var StreamController = class {
551
568
  else this.#resolveHydration();
552
569
  }
553
570
  /**
571
+ * Missing threads must not open `/stream/events`. The in-mem
572
+ * runtime accepts the join against a not-yet-created id but the
573
+ * subscription never attaches to the live channel — the first run
574
+ * then delivers 0 bytes until idle reconnect. Park the pump until
575
+ * `submit()`'s `run.start` creates the row (see
576
+ * {@link #startDeferredRootPump}). Tracked in `#missingThreadIds`
577
+ * (not `#selfCreatedThreadIds`) so a later hydrate revalidates —
578
+ * another client may create the same id between visits.
579
+ */
580
+ if (threadMissing) {
581
+ this.#missingThreadIds.add(hydratedThreadId);
582
+ return;
583
+ }
584
+ /**
554
585
  * Open the shared subscription on mount so in-flight server-side
555
586
  * runs are observed even when no local `submit()` is active — BUT
556
587
  * only when the thread is actually active (see
@@ -687,7 +718,7 @@ var StreamController = class {
687
718
  */
688
719
  async #trySeedProjectionFromHistory(params) {
689
720
  const threadId = this.#currentThreadId;
690
- if (this.#disposed || threadId == null || params.namespace.length === 0 || !this.#rootPumpDeferred || this.#selfCreatedThreadIds.has(threadId)) return false;
721
+ if (this.#disposed || threadId == null || params.namespace.length === 0 || !this.#rootPumpDeferred || this.#selfCreatedThreadIds.has(threadId) || this.#missingThreadIds.has(threadId)) return false;
691
722
  if (await this.#skipDefaultSubagentProjection(params.namespace, threadId)) return true;
692
723
  if (this.#disposed || this.#currentThreadId !== threadId || !this.#rootPumpDeferred) return false;
693
724
  const seed = await this.#getScopedHistorySeed(threadId, params.namespace);
@@ -895,14 +926,15 @@ var StreamController = class {
895
926
  * oldest and picks the first entry whose `interruptId` has not already
896
927
  * been resolved by a prior `respond()` call. That entry may be at the
897
928
  * root (`namespace: []`) or inside a subgraph (non-empty `namespace`).
898
- * This is **not** the same as {@link RootSnapshot.interrupts
899
- * `rootStore.interrupts[0]`} / framework `stream.interrupt`, which only
900
- * mirrors root-namespace interrupts for UI convenience.
929
+ * {@link RootSnapshot.interrupts `rootStore.interrupts`} /
930
+ * framework `stream.interrupts` mirrors the same pending interrupts
931
+ * (including nested namespaces) for UI rendering.
901
932
  *
902
933
  * Omitting `interruptId` is fine when exactly one interrupt is pending.
903
934
  * When several can be active (parallel subagents, fan-out, nested
904
- * graphs), pass an explicit `interruptId` (and `namespace` for subgraph
905
- * interrupts) so you resume the interrupt the user acted on.
935
+ * graphs), pass an explicit `interruptId` so you resume the interrupt
936
+ * the user acted on. `namespace` is resolved automatically from
937
+ * `thread.interrupts` / `stream.interrupts` when omitted.
906
938
  *
907
939
  * To resume several interrupts pending at the same checkpoint in one
908
940
  * command, use {@link respondAll} — sequential single `respond()` calls
@@ -910,9 +942,8 @@ var StreamController = class {
910
942
  * others with no interrupted run to respond to.
911
943
  *
912
944
  * The server validates `namespace` against the pending interrupt. Root
913
- * interrupts use `namespace: []` (the default when `namespace` is
914
- * omitted). Subgraph interrupts require the exact tuple from
915
- * `getThread()?.interrupts` — see the example below.
945
+ * interrupts use `namespace: []`. Subgraph interrupts carry the exact
946
+ * tuple on each {@link Interrupt} / {@link InterruptPayload} entry.
916
947
  *
917
948
  * @param response - Payload sent back to the interrupted namespace.
918
949
  * @param options - Optional target (`interruptId` / `namespace`) and
@@ -933,33 +964,22 @@ var StreamController = class {
933
964
  * );
934
965
  * ```
935
966
  *
936
- * @example Multiple root interrupts — target by id
967
+ * @example Multiple / nested interrupts — target by id
937
968
  * ```tsx
938
969
  * for (const intr of stream.interrupts) {
939
970
  * await stream.respond(decide(intr.value), { interruptId: intr.id! });
940
971
  * }
941
972
  * ```
942
973
  *
943
- * @example Subgraph interrupt — read `namespace` from the thread stream
944
- * ```tsx
945
- * const thread = stream.getThread();
946
- * for (const entry of thread?.interrupts ?? []) {
947
- * await stream.respond(buildResponse(entry.payload), {
948
- * interruptId: entry.interruptId,
949
- * namespace: entry.namespace,
950
- * });
951
- * }
952
- * ```
953
- *
954
- * Each {@link InterruptPayload} on `thread.interrupts` mirrors an
955
- * `input.requested` event: `{ interruptId, payload, namespace }`.
956
- * Nested interrupts may appear here but not on `stream.interrupts`.
974
+ * Each {@link InterruptPayload} on `thread.interrupts` and each
975
+ * {@link Interrupt} on `stream.interrupts` mirrors an
976
+ * `input.requested` event with its protocol `namespace`.
957
977
  */
958
978
  async respond(response, options) {
959
979
  if (this.#disposed || this.#thread == null) throw new Error("No active thread to respond to.");
960
980
  const resolved = options?.interruptId != null ? {
961
981
  interruptId: options.interruptId,
962
- namespace: options.namespace ?? [...ROOT_NAMESPACE]
982
+ namespace: this.#resolveNamespaceForInterruptId(options.interruptId, options.namespace)
963
983
  } : this.#resolveInterruptForResume();
964
984
  if (resolved == null) throw new Error("No pending interrupt to respond to.");
965
985
  const thread = this.#thread;
@@ -1029,11 +1049,10 @@ var StreamController = class {
1029
1049
  const entries = Object.entries(responsesById);
1030
1050
  if (entries.length === 0) throw new Error("respondAll() requires at least one response.");
1031
1051
  const thread = this.#thread;
1032
- const pending = thread.interrupts;
1033
1052
  const responses = entries.map(([interruptId, response]) => ({
1034
1053
  interrupt_id: interruptId,
1035
1054
  response: normalizeHitlResponseForServer(response),
1036
- namespace: pending.find((entry) => entry.interruptId === interruptId)?.namespace ?? [...ROOT_NAMESPACE]
1055
+ namespace: this.#resolveNamespaceForInterruptId(interruptId)
1037
1056
  }));
1038
1057
  const prepared = options?.update != null ? this.#beginOptimistic(options.update) : void 0;
1039
1058
  const dispatchUpdate = this.#resolveDispatchUpdate(options?.update, prepared);
@@ -1200,16 +1219,11 @@ var StreamController = class {
1200
1219
  * Without this, the controller would be wedged in a state where:
1201
1220
  * - `#thread` is wired but no content pump is open
1202
1221
  * - `#rootPumpDeferred` stays `true`
1203
- * - `selfCreatedThreadIds` still holds the id
1204
1222
  *
1205
- * A retry submit on the same controller would see
1206
- * `wasSelfCreated=false` (because `currentThreadId` is no longer
1207
- * null), `#ensureThread(id, false)` would early-return because
1208
- * `#thread != null`, and the pump would never start. The thread
1209
- * would have an id committed to the URL but no live subscription.
1210
- *
1211
- * Tearing down `#thread` so the next submit re-runs `#ensureThread`
1212
- * from scratch is the simplest recovery — the failed dispatch
1223
+ * A retry submit must re-run `#ensureThread` from scratch (with
1224
+ * `deferRootPump` still true — the id stays in
1225
+ * `#selfCreatedThreadIds` until a successful dispatch). Tearing
1226
+ * down `#thread` is the simplest recovery; the failed dispatch
1213
1227
  * means there was nothing to subscribe to anyway.
1214
1228
  */
1215
1229
  #abandonDeferredRootPump() {
@@ -1412,16 +1426,14 @@ var StreamController = class {
1412
1426
  this.#subgraphs.push(event);
1413
1427
  this.#lifecycleLoading.handle(event);
1414
1428
  /**
1415
- * Nested `input.requested` events (HITL inside a subagent /
1416
- * subgraph) are not observable via the narrow content pump. The
1417
- * `ThreadStream` itself already records them into
1418
- * `thread.interrupts`, which `#latestUnresolvedInterrupt()`
1419
- * consults — so HITL respond() works for any depth. Root-level
1420
- * interrupts are also mirrored into `rootStore.interrupts` here so
1421
- * UI state does not depend on the narrower content pump being the
1422
- * first consumer to see the event.
1429
+ * `input.requested` events (including HITL inside a subagent /
1430
+ * subgraph) are not always observable via the narrow content pump.
1431
+ * Mirror every namespace into `rootStore.interrupts` here so UI
1432
+ * state does not depend on the content pump being the first
1433
+ * consumer. `ThreadStream.interrupts` is updated in parallel for
1434
+ * resume targeting.
1423
1435
  */
1424
- this.#recordRootInterrupt(event);
1436
+ this.#recordInterrupt(event);
1425
1437
  }
1426
1438
  /**
1427
1439
  * Process one root-pump event and update all root projections.
@@ -1514,7 +1526,7 @@ var StreamController = class {
1514
1526
  return;
1515
1527
  }
1516
1528
  if (event.method === "input.requested") {
1517
- this.#recordRootInterrupt(event);
1529
+ this.#recordInterrupt(event);
1518
1530
  return;
1519
1531
  }
1520
1532
  if (event.method === "lifecycle") event.params.data;
@@ -1642,21 +1654,22 @@ var StreamController = class {
1642
1654
  }
1643
1655
  }
1644
1656
  /**
1645
- * Mirror root protocol interrupts into the root snapshot.
1657
+ * Mirror protocol interrupts (any namespace) into the root snapshot.
1646
1658
  *
1647
1659
  * This can be called from both the wildcard lifecycle/input watcher and the
1648
1660
  * root content pump. Store-level dedup keeps the user-facing list stable.
1649
1661
  */
1650
- #recordRootInterrupt(event) {
1662
+ #recordInterrupt(event) {
1651
1663
  if (event.method !== "input.requested") return;
1652
- if (!isRootNamespace(event.params.namespace)) return;
1653
1664
  const data = event.params.data;
1654
1665
  const interruptId = data?.interrupt_id;
1655
1666
  if (typeof interruptId !== "string" || this.#resolvedInterrupts.has(interruptId)) return;
1656
1667
  if (this.#hydratedActiveInterruptIds != null && !this.#hydratedActiveInterruptIds.has(interruptId)) return;
1668
+ const namespace = Array.isArray(event.params.namespace) ? [...event.params.namespace] : [...ROOT_NAMESPACE];
1657
1669
  const interrupt = normalizeInterruptForClient({
1658
1670
  id: interruptId,
1659
- value: data.payload
1671
+ value: data.payload,
1672
+ namespace
1660
1673
  });
1661
1674
  this.rootStore.setState((s) => {
1662
1675
  if (s.interrupts.some((entry) => entry.id === interruptId)) return s;
@@ -1669,6 +1682,21 @@ var StreamController = class {
1669
1682
  });
1670
1683
  }
1671
1684
  /**
1685
+ * Resolve the protocol namespace for a targeted resume.
1686
+ *
1687
+ * Prefer an explicit caller-supplied namespace, then the matching
1688
+ * {@link ThreadStream.interrupts} entry, then the UI mirror on
1689
+ * `rootStore.interrupts`, finally root (`[]`).
1690
+ */
1691
+ #resolveNamespaceForInterruptId(interruptId, explicitNamespace) {
1692
+ if (explicitNamespace != null) return [...explicitNamespace];
1693
+ const fromThread = this.#thread?.interrupts.find((entry) => entry.interruptId === interruptId)?.namespace;
1694
+ if (fromThread != null) return [...fromThread];
1695
+ const fromRoot = this.rootStore.getSnapshot().interrupts.find((entry) => entry.id === interruptId)?.namespace;
1696
+ if (fromRoot != null) return [...fromRoot];
1697
+ return [...ROOT_NAMESPACE];
1698
+ }
1699
+ /**
1672
1700
  * Mark an interrupt resolved for replay filtering and mirror the
1673
1701
  * removal into the root snapshot the framework hooks read.
1674
1702
  */