@langchain/langgraph-sdk 1.9.28 → 1.9.30
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.
- package/dist/client/stream/types.d.cts +4 -4
- package/dist/client/stream/types.d.ts +4 -4
- package/dist/schema.d.cts +7 -0
- package/dist/schema.d.cts.map +1 -1
- package/dist/schema.d.ts +7 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/stream/controller.cjs +135 -71
- package/dist/stream/controller.cjs.map +1 -1
- package/dist/stream/controller.d.cts +12 -23
- package/dist/stream/controller.d.cts.map +1 -1
- package/dist/stream/controller.d.ts +12 -23
- package/dist/stream/controller.d.ts.map +1 -1
- package/dist/stream/controller.js +135 -71
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/lifecycle-loading-tracker.cjs +12 -2
- package/dist/stream/lifecycle-loading-tracker.cjs.map +1 -1
- package/dist/stream/lifecycle-loading-tracker.js +12 -2
- package/dist/stream/lifecycle-loading-tracker.js.map +1 -1
- package/dist/stream/message-reconciliation.cjs +80 -4
- package/dist/stream/message-reconciliation.cjs.map +1 -1
- package/dist/stream/message-reconciliation.js +80 -4
- package/dist/stream/message-reconciliation.js.map +1 -1
- package/dist/stream/projections/channel.cjs +4 -6
- package/dist/stream/projections/channel.cjs.map +1 -1
- package/dist/stream/projections/channel.d.cts.map +1 -1
- package/dist/stream/projections/channel.d.ts.map +1 -1
- package/dist/stream/projections/channel.js +4 -6
- package/dist/stream/projections/channel.js.map +1 -1
- package/dist/stream/projections/messages.cjs +1 -1
- package/dist/stream/projections/messages.cjs.map +1 -1
- package/dist/stream/projections/messages.js +2 -2
- package/dist/stream/projections/messages.js.map +1 -1
- package/dist/stream/root-message-projection.cjs +12 -6
- package/dist/stream/root-message-projection.cjs.map +1 -1
- package/dist/stream/root-message-projection.js +13 -7
- package/dist/stream/root-message-projection.js.map +1 -1
- package/dist/stream/submit-coordinator.cjs +6 -5
- package/dist/stream/submit-coordinator.cjs.map +1 -1
- package/dist/stream/submit-coordinator.js +6 -5
- package/dist/stream/submit-coordinator.js.map +1 -1
- package/dist/stream/types.d.cts +3 -3
- package/dist/stream/types.d.ts +3 -3
- package/dist/utils/async_caller.cjs +1 -1
- package/dist/utils/async_caller.cjs.map +1 -1
- package/dist/utils/async_caller.d.cts.map +1 -1
- package/dist/utils/async_caller.d.ts.map +1 -1
- package/dist/utils/async_caller.js +1 -1
- package/dist/utils/async_caller.js.map +1 -1
- package/package.json +7 -7
|
@@ -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
|
-
*
|
|
124
|
-
* `
|
|
125
|
-
*
|
|
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`
|
|
130
|
-
*
|
|
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: []
|
|
139
|
-
*
|
|
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
|
|
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
|
-
* @
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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;
|
|
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;EAwI0B;;;;;EAA7C,WAAA,CAAY,OAAA,EAAS,uBAAA,CAAwB,SAAA;EA0yBF;;;;;;;EAAA,IA3rBvC,gBAAA,CAAA,GAAoB,OAAA;EAg2BS;;;;;;;;EAvyB3B,OAAA,CAAQ,QAAA,mBAA2B,OAAA;EAqhCpB;;;;;;;;;;;;;;EAzpBf,wBAAA,CAAyB,UAAA,WAAqB,OAAA;EAjrB5B;;;;;;;;EAq7BlB,MAAA,CACJ,KAAA,WACA,OAAA,GAAU,mBAAA,CAAoB,SAAA,EAAW,gBAAA,IACxC,OAAA;EAr7B4B;;;;;;EA+7BzB,IAAA,CAAK,OAAA,GAAU,iBAAA,GAAoB,OAAA;EA77BtB;;;;EAi9Bb,UAAA,CAAA,GAAc,OAAA;EA1tBhB;;;;;;;;;;;EA0xBE,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
|
-
*
|
|
124
|
-
* `
|
|
125
|
-
*
|
|
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`
|
|
130
|
-
*
|
|
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: []
|
|
139
|
-
*
|
|
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
|
|
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
|
-
* @
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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;;;;;
|
|
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;;;;;EAwInB,WAAA,CAAY,OAAA,EAAS,uBAAA,CAAwB,SAAA;EA0yBb;;;;;;;EAAA,IA3rB5B,gBAAA,CAAA,GAAoB,OAAA;EAiyBJ;;;;;;;;EAxuBd,OAAA,CAAQ,QAAA,mBAA2B,OAAA;EAygC5B;;;;;;;;;;;;;;EA7oBP,wBAAA,CAAyB,UAAA,WAAqB,OAAA;EAjrB3C;;;;;;;;EAq7BH,MAAA,CACJ,KAAA,WACA,OAAA,GAAU,mBAAA,CAAoB,SAAA,EAAW,gBAAA,IACxC,OAAA;EAr7BM;;;;;;EA+7BH,IAAA,CAAK,OAAA,GAAU,iBAAA,GAAoB,OAAA;EA77BhC;;;;EAi9BH,UAAA,CAAA,GAAc,OAAA;EAz0BR;;;;;;;;;;;EAy4BN,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,31 @@ var StreamController = class {
|
|
|
169
169
|
*/
|
|
170
170
|
#submitGeneration = 0;
|
|
171
171
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
172
|
+
* Monotonic counter bumped at the start of each `#teardownThread`.
|
|
173
|
+
* The root pump captures the generation when it starts and drops
|
|
174
|
+
* any event whose generation no longer matches — including queued
|
|
175
|
+
* events that `SubscriptionHandle.close()` still drains after the
|
|
176
|
+
* snapshot has already been cleared by `hydrate()`.
|
|
177
|
+
*/
|
|
178
|
+
#rootPumpGeneration = 0;
|
|
179
|
+
/**
|
|
180
|
+
* Thread ids this controller minted client-side on first `submit()`.
|
|
181
|
+
* `hydrate()` skips `threads.getState()` for these — we know there
|
|
182
|
+
* is nothing checkpointed yet. Cleared once dispatch commits the
|
|
183
|
+
* thread server-side.
|
|
176
184
|
*/
|
|
177
185
|
#selfCreatedThreadIds = /* @__PURE__ */ new Set();
|
|
178
186
|
/**
|
|
187
|
+
* Caller-supplied thread ids that 404'd on hydrate. Unlike
|
|
188
|
+
* {@link #selfCreatedThreadIds}, these are revalidated on every
|
|
189
|
+
* hydrate: another client may create the same id between visits.
|
|
190
|
+
* While marked missing, `submit()` still defers the root SSE pump
|
|
191
|
+
* until `run.start` / `commands` commits the row — joining
|
|
192
|
+
* `/stream/events` beforehand leaves a dead subscription on
|
|
193
|
+
* langgraph_api's in-mem runtime.
|
|
194
|
+
*/
|
|
195
|
+
#missingThreadIds = /* @__PURE__ */ new Set();
|
|
196
|
+
/**
|
|
179
197
|
* In-flight per-subagent namespace resolutions, keyed by tool-call
|
|
180
198
|
* id. De-dupes concurrent {@link resolveSubagentNamespace} calls so
|
|
181
199
|
* re-renders / multiple consumers of the same subagent don't issue
|
|
@@ -270,12 +288,14 @@ var StreamController = class {
|
|
|
270
288
|
rememberSelfCreatedThreadId: (threadId) => {
|
|
271
289
|
this.#selfCreatedThreadIds.add(threadId);
|
|
272
290
|
},
|
|
291
|
+
isSelfCreatedThreadId: (threadId) => this.#selfCreatedThreadIds.has(threadId) || this.#missingThreadIds.has(threadId),
|
|
273
292
|
hydrate: (threadId) => this.hydrate(threadId),
|
|
274
293
|
ensureThread: (threadId, deferRootPump) => this.#ensureThread(threadId, deferRootPump),
|
|
275
294
|
startDeferredRootPump: () => this.#startDeferredRootPump(),
|
|
276
295
|
abandonDeferredRootPump: () => this.#abandonDeferredRootPump(),
|
|
277
296
|
forgetSelfCreatedThreadId: (threadId) => {
|
|
278
297
|
this.#selfCreatedThreadIds.delete(threadId);
|
|
298
|
+
this.#missingThreadIds.delete(threadId);
|
|
279
299
|
},
|
|
280
300
|
waitForRootPumpReady: () => this.#rootPumpReady,
|
|
281
301
|
awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),
|
|
@@ -379,13 +399,16 @@ var StreamController = class {
|
|
|
379
399
|
* Suspense boundary remounted against the new id suspends again.
|
|
380
400
|
*/
|
|
381
401
|
this.#resetHydrationPromise();
|
|
382
|
-
await this.#teardownThread();
|
|
383
402
|
/**
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
403
|
+
* Kick teardown so the synchronous abort (unbind, drop onEvent,
|
|
404
|
+
* close the root subscription, reset assemblers) runs before we
|
|
405
|
+
* touch the snapshot. Then reset immediately so the UI does not
|
|
406
|
+
* wait on pump/close. An interrupted thread parks the root pump
|
|
407
|
+
* on `waitForResume()`; awaiting teardown *before* this reset
|
|
408
|
+
* left `threadId` null while `messages` still showed the previous
|
|
409
|
+
* conversation (`useStream` fire-and-forgets `hydrate()`).
|
|
388
410
|
*/
|
|
411
|
+
const teardown = this.#teardownThread();
|
|
389
412
|
this.rootStore.setState(() => ({
|
|
390
413
|
...this.#createInitialSnapshot(),
|
|
391
414
|
threadId: this.#currentThreadId
|
|
@@ -396,6 +419,7 @@ var StreamController = class {
|
|
|
396
419
|
* surprising. Mirrors the legacy `StreamOrchestrator` behaviour.
|
|
397
420
|
*/
|
|
398
421
|
this.queueStore.setState(() => EMPTY_QUEUE);
|
|
422
|
+
await teardown;
|
|
399
423
|
}
|
|
400
424
|
if (this.#currentThreadId == null) {
|
|
401
425
|
this.rootStore.setState((s) => ({
|
|
@@ -426,12 +450,14 @@ var StreamController = class {
|
|
|
426
450
|
const hydratedThreadId = this.#currentThreadId;
|
|
427
451
|
let hydrationError;
|
|
428
452
|
let threadExists = false;
|
|
453
|
+
let threadMissing = false;
|
|
429
454
|
let threadActive = true;
|
|
430
455
|
try {
|
|
431
456
|
const state = await this.#fetchHydrationState();
|
|
432
457
|
if (this.#disposed || this.#currentThreadId !== hydratedThreadId) return;
|
|
433
458
|
threadExists = state != null;
|
|
434
459
|
threadActive = isThreadStateActive(state);
|
|
460
|
+
this.#missingThreadIds.delete(hydratedThreadId);
|
|
435
461
|
if (state?.values != null) {
|
|
436
462
|
/**
|
|
437
463
|
* `threads.getState()` returns the legacy `ThreadState` shape
|
|
@@ -516,6 +542,8 @@ var StreamController = class {
|
|
|
516
542
|
const activeIds = /* @__PURE__ */ new Set();
|
|
517
543
|
for (const task of state.tasks) {
|
|
518
544
|
if (!Array.isArray(task?.interrupts)) continue;
|
|
545
|
+
const checkpointNs = task?.checkpoint?.checkpoint_ns;
|
|
546
|
+
const namespace = typeof checkpointNs === "string" && checkpointNs.length > 0 ? checkpointNs.split("|").filter((segment) => segment.length > 0) : [...ROOT_NAMESPACE];
|
|
519
547
|
for (const interrupt of task.interrupts) {
|
|
520
548
|
const typed = interrupt;
|
|
521
549
|
const id = typed?.id;
|
|
@@ -523,7 +551,8 @@ var StreamController = class {
|
|
|
523
551
|
activeIds.add(id);
|
|
524
552
|
activeInterrupts.push(normalizeInterruptForClient({
|
|
525
553
|
id,
|
|
526
|
-
value: typed?.value
|
|
554
|
+
value: typed?.value,
|
|
555
|
+
namespace: Array.isArray(typed?.namespace) ? [...typed.namespace] : namespace
|
|
527
556
|
}));
|
|
528
557
|
}
|
|
529
558
|
}
|
|
@@ -541,7 +570,7 @@ var StreamController = class {
|
|
|
541
570
|
...s,
|
|
542
571
|
error
|
|
543
572
|
}));
|
|
544
|
-
}
|
|
573
|
+
} else threadMissing = true;
|
|
545
574
|
} finally {
|
|
546
575
|
this.rootStore.setState((s) => ({
|
|
547
576
|
...s,
|
|
@@ -551,6 +580,20 @@ var StreamController = class {
|
|
|
551
580
|
else this.#resolveHydration();
|
|
552
581
|
}
|
|
553
582
|
/**
|
|
583
|
+
* Missing threads must not open `/stream/events`. The in-mem
|
|
584
|
+
* runtime accepts the join against a not-yet-created id but the
|
|
585
|
+
* subscription never attaches to the live channel — the first run
|
|
586
|
+
* then delivers 0 bytes until idle reconnect. Park the pump until
|
|
587
|
+
* `submit()`'s `run.start` creates the row (see
|
|
588
|
+
* {@link #startDeferredRootPump}). Tracked in `#missingThreadIds`
|
|
589
|
+
* (not `#selfCreatedThreadIds`) so a later hydrate revalidates —
|
|
590
|
+
* another client may create the same id between visits.
|
|
591
|
+
*/
|
|
592
|
+
if (threadMissing) {
|
|
593
|
+
this.#missingThreadIds.add(hydratedThreadId);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
554
597
|
* Open the shared subscription on mount so in-flight server-side
|
|
555
598
|
* runs are observed even when no local `submit()` is active — BUT
|
|
556
599
|
* only when the thread is actually active (see
|
|
@@ -687,7 +730,7 @@ var StreamController = class {
|
|
|
687
730
|
*/
|
|
688
731
|
async #trySeedProjectionFromHistory(params) {
|
|
689
732
|
const threadId = this.#currentThreadId;
|
|
690
|
-
if (this.#disposed || threadId == null || params.namespace.length === 0 || !this.#rootPumpDeferred || this.#selfCreatedThreadIds.has(threadId)) return false;
|
|
733
|
+
if (this.#disposed || threadId == null || params.namespace.length === 0 || !this.#rootPumpDeferred || this.#selfCreatedThreadIds.has(threadId) || this.#missingThreadIds.has(threadId)) return false;
|
|
691
734
|
if (await this.#skipDefaultSubagentProjection(params.namespace, threadId)) return true;
|
|
692
735
|
if (this.#disposed || this.#currentThreadId !== threadId || !this.#rootPumpDeferred) return false;
|
|
693
736
|
const seed = await this.#getScopedHistorySeed(threadId, params.namespace);
|
|
@@ -895,14 +938,15 @@ var StreamController = class {
|
|
|
895
938
|
* oldest and picks the first entry whose `interruptId` has not already
|
|
896
939
|
* been resolved by a prior `respond()` call. That entry may be at the
|
|
897
940
|
* root (`namespace: []`) or inside a subgraph (non-empty `namespace`).
|
|
898
|
-
*
|
|
899
|
-
* `
|
|
900
|
-
*
|
|
941
|
+
* {@link RootSnapshot.interrupts `rootStore.interrupts`} /
|
|
942
|
+
* framework `stream.interrupts` mirrors the same pending interrupts
|
|
943
|
+
* (including nested namespaces) for UI rendering.
|
|
901
944
|
*
|
|
902
945
|
* Omitting `interruptId` is fine when exactly one interrupt is pending.
|
|
903
946
|
* When several can be active (parallel subagents, fan-out, nested
|
|
904
|
-
* graphs), pass an explicit `interruptId`
|
|
905
|
-
*
|
|
947
|
+
* graphs), pass an explicit `interruptId` so you resume the interrupt
|
|
948
|
+
* the user acted on. `namespace` is resolved automatically from
|
|
949
|
+
* `thread.interrupts` / `stream.interrupts` when omitted.
|
|
906
950
|
*
|
|
907
951
|
* To resume several interrupts pending at the same checkpoint in one
|
|
908
952
|
* command, use {@link respondAll} — sequential single `respond()` calls
|
|
@@ -910,9 +954,8 @@ var StreamController = class {
|
|
|
910
954
|
* others with no interrupted run to respond to.
|
|
911
955
|
*
|
|
912
956
|
* The server validates `namespace` against the pending interrupt. Root
|
|
913
|
-
* interrupts use `namespace: []
|
|
914
|
-
*
|
|
915
|
-
* `getThread()?.interrupts` — see the example below.
|
|
957
|
+
* interrupts use `namespace: []`. Subgraph interrupts carry the exact
|
|
958
|
+
* tuple on each {@link Interrupt} / {@link InterruptPayload} entry.
|
|
916
959
|
*
|
|
917
960
|
* @param response - Payload sent back to the interrupted namespace.
|
|
918
961
|
* @param options - Optional target (`interruptId` / `namespace`) and
|
|
@@ -933,33 +976,22 @@ var StreamController = class {
|
|
|
933
976
|
* );
|
|
934
977
|
* ```
|
|
935
978
|
*
|
|
936
|
-
* @example Multiple
|
|
979
|
+
* @example Multiple / nested interrupts — target by id
|
|
937
980
|
* ```tsx
|
|
938
981
|
* for (const intr of stream.interrupts) {
|
|
939
982
|
* await stream.respond(decide(intr.value), { interruptId: intr.id! });
|
|
940
983
|
* }
|
|
941
984
|
* ```
|
|
942
985
|
*
|
|
943
|
-
* @
|
|
944
|
-
*
|
|
945
|
-
*
|
|
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`.
|
|
986
|
+
* Each {@link InterruptPayload} on `thread.interrupts` and each
|
|
987
|
+
* {@link Interrupt} on `stream.interrupts` mirrors an
|
|
988
|
+
* `input.requested` event with its protocol `namespace`.
|
|
957
989
|
*/
|
|
958
990
|
async respond(response, options) {
|
|
959
991
|
if (this.#disposed || this.#thread == null) throw new Error("No active thread to respond to.");
|
|
960
992
|
const resolved = options?.interruptId != null ? {
|
|
961
993
|
interruptId: options.interruptId,
|
|
962
|
-
namespace: options.namespace
|
|
994
|
+
namespace: this.#resolveNamespaceForInterruptId(options.interruptId, options.namespace)
|
|
963
995
|
} : this.#resolveInterruptForResume();
|
|
964
996
|
if (resolved == null) throw new Error("No pending interrupt to respond to.");
|
|
965
997
|
const thread = this.#thread;
|
|
@@ -1029,11 +1061,10 @@ var StreamController = class {
|
|
|
1029
1061
|
const entries = Object.entries(responsesById);
|
|
1030
1062
|
if (entries.length === 0) throw new Error("respondAll() requires at least one response.");
|
|
1031
1063
|
const thread = this.#thread;
|
|
1032
|
-
const pending = thread.interrupts;
|
|
1033
1064
|
const responses = entries.map(([interruptId, response]) => ({
|
|
1034
1065
|
interrupt_id: interruptId,
|
|
1035
1066
|
response: normalizeHitlResponseForServer(response),
|
|
1036
|
-
namespace:
|
|
1067
|
+
namespace: this.#resolveNamespaceForInterruptId(interruptId)
|
|
1037
1068
|
}));
|
|
1038
1069
|
const prepared = options?.update != null ? this.#beginOptimistic(options.update) : void 0;
|
|
1039
1070
|
const dispatchUpdate = this.#resolveDispatchUpdate(options?.update, prepared);
|
|
@@ -1200,16 +1231,11 @@ var StreamController = class {
|
|
|
1200
1231
|
* Without this, the controller would be wedged in a state where:
|
|
1201
1232
|
* - `#thread` is wired but no content pump is open
|
|
1202
1233
|
* - `#rootPumpDeferred` stays `true`
|
|
1203
|
-
* - `selfCreatedThreadIds` still holds the id
|
|
1204
1234
|
*
|
|
1205
|
-
* A retry submit
|
|
1206
|
-
* `
|
|
1207
|
-
*
|
|
1208
|
-
* `#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
|
|
1235
|
+
* A retry submit must re-run `#ensureThread` from scratch (with
|
|
1236
|
+
* `deferRootPump` still true — the id stays in
|
|
1237
|
+
* `#selfCreatedThreadIds` until a successful dispatch). Tearing
|
|
1238
|
+
* down `#thread` is the simplest recovery; the failed dispatch
|
|
1213
1239
|
* means there was nothing to subscribe to anyway.
|
|
1214
1240
|
*/
|
|
1215
1241
|
#abandonDeferredRootPump() {
|
|
@@ -1221,6 +1247,14 @@ var StreamController = class {
|
|
|
1221
1247
|
* Close the current thread stream and reset per-thread assembly state.
|
|
1222
1248
|
*/
|
|
1223
1249
|
async #teardownThread() {
|
|
1250
|
+
/**
|
|
1251
|
+
* Invalidate the in-flight root pump before `close()` unparks it.
|
|
1252
|
+
* `SubscriptionHandle.close()` leaves queued events intact and the
|
|
1253
|
+
* iterator drains them before observing `closed`, so without this
|
|
1254
|
+
* bump a parked interrupted pump would still dispatch stale
|
|
1255
|
+
* `values` / `messages` into a snapshot `hydrate()` already cleared.
|
|
1256
|
+
*/
|
|
1257
|
+
this.#rootPumpGeneration += 1;
|
|
1224
1258
|
const thread = this.#thread;
|
|
1225
1259
|
this.#thread = void 0;
|
|
1226
1260
|
this.registry.bind(void 0);
|
|
@@ -1233,16 +1267,16 @@ var StreamController = class {
|
|
|
1233
1267
|
*/
|
|
1234
1268
|
this.#rootEventListeners.delete(this.#lifecycleLoading.listener);
|
|
1235
1269
|
this.#rootEventListeners.delete(this.#runLifecycleListener);
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1270
|
+
/**
|
|
1271
|
+
* Close first so a pump parked on `waitForResume()` (interrupted
|
|
1272
|
+
* run) unparks even if the unsubscribe RPC is slow. `close()` is
|
|
1273
|
+
* idempotent; `unsubscribe()` is then a no-op once closed.
|
|
1274
|
+
*/
|
|
1275
|
+
const subscription = this.#rootSubscription;
|
|
1239
1276
|
this.#rootSubscription = void 0;
|
|
1277
|
+
subscription?.close();
|
|
1240
1278
|
this.#rootPumpReady = void 0;
|
|
1241
1279
|
this.#rootPumpDeferred = false;
|
|
1242
|
-
try {
|
|
1243
|
-
await this.#rootPump;
|
|
1244
|
-
} catch {}
|
|
1245
|
-
this.#rootPump = void 0;
|
|
1246
1280
|
this.#rootMessages.reset();
|
|
1247
1281
|
this.#rootToolAssembler = new ToolCallAssembler();
|
|
1248
1282
|
this.#lifecycleLoading.reset();
|
|
@@ -1254,6 +1288,13 @@ var StreamController = class {
|
|
|
1254
1288
|
this.#messageMetadata.reset();
|
|
1255
1289
|
this.#hydratedActiveInterruptIds = null;
|
|
1256
1290
|
this.queueStore.setState(() => EMPTY_QUEUE);
|
|
1291
|
+
try {
|
|
1292
|
+
await subscription?.unsubscribe();
|
|
1293
|
+
} catch {}
|
|
1294
|
+
try {
|
|
1295
|
+
await this.#rootPump;
|
|
1296
|
+
} catch {}
|
|
1297
|
+
this.#rootPump = void 0;
|
|
1257
1298
|
if (thread != null) {
|
|
1258
1299
|
try {
|
|
1259
1300
|
await thread.close();
|
|
@@ -1277,6 +1318,7 @@ var StreamController = class {
|
|
|
1277
1318
|
*/
|
|
1278
1319
|
#startRootPump(thread) {
|
|
1279
1320
|
if (this.#rootPump != null) return;
|
|
1321
|
+
const pumpGeneration = this.#rootPumpGeneration;
|
|
1280
1322
|
let resolveReady;
|
|
1281
1323
|
this.#rootPumpReady = new Promise((resolve) => {
|
|
1282
1324
|
resolveReady = resolve;
|
|
@@ -1335,6 +1377,14 @@ var StreamController = class {
|
|
|
1335
1377
|
resolveReady = void 0;
|
|
1336
1378
|
});
|
|
1337
1379
|
const subscription = await subscriptionPromise;
|
|
1380
|
+
if (pumpGeneration !== this.#rootPumpGeneration) {
|
|
1381
|
+
resolveReady?.();
|
|
1382
|
+
resolveReady = void 0;
|
|
1383
|
+
try {
|
|
1384
|
+
subscription.close();
|
|
1385
|
+
} catch {}
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1338
1388
|
resolveReady?.();
|
|
1339
1389
|
resolveReady = void 0;
|
|
1340
1390
|
this.#rootSubscription = subscription;
|
|
@@ -1348,9 +1398,9 @@ var StreamController = class {
|
|
|
1348
1398
|
* for every resumed iteration until the subscription is
|
|
1349
1399
|
* permanently closed or the controller is disposed.
|
|
1350
1400
|
*/
|
|
1351
|
-
while (!this.#disposed) {
|
|
1401
|
+
while (!this.#disposed && pumpGeneration === this.#rootPumpGeneration) {
|
|
1352
1402
|
for await (const event of subscription) {
|
|
1353
|
-
if (this.#disposed) break;
|
|
1403
|
+
if (this.#disposed || pumpGeneration !== this.#rootPumpGeneration) break;
|
|
1354
1404
|
/**
|
|
1355
1405
|
* Resilience: isolate per-event dispatch from the pump loop.
|
|
1356
1406
|
*
|
|
@@ -1382,7 +1432,7 @@ var StreamController = class {
|
|
|
1382
1432
|
this.#onRootEvent(event);
|
|
1383
1433
|
} catch {}
|
|
1384
1434
|
}
|
|
1385
|
-
if (this.#disposed) break;
|
|
1435
|
+
if (this.#disposed || pumpGeneration !== this.#rootPumpGeneration) break;
|
|
1386
1436
|
if (!subscription.isPaused) break;
|
|
1387
1437
|
await subscription.waitForResume();
|
|
1388
1438
|
}
|
|
@@ -1412,16 +1462,14 @@ var StreamController = class {
|
|
|
1412
1462
|
this.#subgraphs.push(event);
|
|
1413
1463
|
this.#lifecycleLoading.handle(event);
|
|
1414
1464
|
/**
|
|
1415
|
-
*
|
|
1416
|
-
* subgraph) are not observable via the narrow content pump.
|
|
1417
|
-
* `
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
1420
|
-
*
|
|
1421
|
-
* UI state does not depend on the narrower content pump being the
|
|
1422
|
-
* first consumer to see the event.
|
|
1465
|
+
* `input.requested` events (including HITL inside a subagent /
|
|
1466
|
+
* subgraph) are not always observable via the narrow content pump.
|
|
1467
|
+
* Mirror every namespace into `rootStore.interrupts` here so UI
|
|
1468
|
+
* state does not depend on the content pump being the first
|
|
1469
|
+
* consumer. `ThreadStream.interrupts` is updated in parallel for
|
|
1470
|
+
* resume targeting.
|
|
1423
1471
|
*/
|
|
1424
|
-
this.#
|
|
1472
|
+
this.#recordInterrupt(event);
|
|
1425
1473
|
}
|
|
1426
1474
|
/**
|
|
1427
1475
|
* Process one root-pump event and update all root projections.
|
|
@@ -1514,7 +1562,7 @@ var StreamController = class {
|
|
|
1514
1562
|
return;
|
|
1515
1563
|
}
|
|
1516
1564
|
if (event.method === "input.requested") {
|
|
1517
|
-
this.#
|
|
1565
|
+
this.#recordInterrupt(event);
|
|
1518
1566
|
return;
|
|
1519
1567
|
}
|
|
1520
1568
|
if (event.method === "lifecycle") event.params.data;
|
|
@@ -1642,21 +1690,22 @@ var StreamController = class {
|
|
|
1642
1690
|
}
|
|
1643
1691
|
}
|
|
1644
1692
|
/**
|
|
1645
|
-
* Mirror
|
|
1693
|
+
* Mirror protocol interrupts (any namespace) into the root snapshot.
|
|
1646
1694
|
*
|
|
1647
1695
|
* This can be called from both the wildcard lifecycle/input watcher and the
|
|
1648
1696
|
* root content pump. Store-level dedup keeps the user-facing list stable.
|
|
1649
1697
|
*/
|
|
1650
|
-
#
|
|
1698
|
+
#recordInterrupt(event) {
|
|
1651
1699
|
if (event.method !== "input.requested") return;
|
|
1652
|
-
if (!isRootNamespace(event.params.namespace)) return;
|
|
1653
1700
|
const data = event.params.data;
|
|
1654
1701
|
const interruptId = data?.interrupt_id;
|
|
1655
1702
|
if (typeof interruptId !== "string" || this.#resolvedInterrupts.has(interruptId)) return;
|
|
1656
1703
|
if (this.#hydratedActiveInterruptIds != null && !this.#hydratedActiveInterruptIds.has(interruptId)) return;
|
|
1704
|
+
const namespace = Array.isArray(event.params.namespace) ? [...event.params.namespace] : [...ROOT_NAMESPACE];
|
|
1657
1705
|
const interrupt = normalizeInterruptForClient({
|
|
1658
1706
|
id: interruptId,
|
|
1659
|
-
value: data.payload
|
|
1707
|
+
value: data.payload,
|
|
1708
|
+
namespace
|
|
1660
1709
|
});
|
|
1661
1710
|
this.rootStore.setState((s) => {
|
|
1662
1711
|
if (s.interrupts.some((entry) => entry.id === interruptId)) return s;
|
|
@@ -1669,6 +1718,21 @@ var StreamController = class {
|
|
|
1669
1718
|
});
|
|
1670
1719
|
}
|
|
1671
1720
|
/**
|
|
1721
|
+
* Resolve the protocol namespace for a targeted resume.
|
|
1722
|
+
*
|
|
1723
|
+
* Prefer an explicit caller-supplied namespace, then the matching
|
|
1724
|
+
* {@link ThreadStream.interrupts} entry, then the UI mirror on
|
|
1725
|
+
* `rootStore.interrupts`, finally root (`[]`).
|
|
1726
|
+
*/
|
|
1727
|
+
#resolveNamespaceForInterruptId(interruptId, explicitNamespace) {
|
|
1728
|
+
if (explicitNamespace != null) return [...explicitNamespace];
|
|
1729
|
+
const fromThread = this.#thread?.interrupts.find((entry) => entry.interruptId === interruptId)?.namespace;
|
|
1730
|
+
if (fromThread != null) return [...fromThread];
|
|
1731
|
+
const fromRoot = this.rootStore.getSnapshot().interrupts.find((entry) => entry.id === interruptId)?.namespace;
|
|
1732
|
+
if (fromRoot != null) return [...fromRoot];
|
|
1733
|
+
return [...ROOT_NAMESPACE];
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1672
1736
|
* Mark an interrupt resolved for replay filtering and mirror the
|
|
1673
1737
|
* removal into the root snapshot the framework hooks read.
|
|
1674
1738
|
*/
|