@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.
- 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 +84 -56
- 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 +84 -56
- package/dist/stream/controller.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/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;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
|
-
*
|
|
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;;;;;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
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
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
|
-
*
|
|
899
|
-
* `
|
|
900
|
-
*
|
|
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`
|
|
905
|
-
*
|
|
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: []
|
|
914
|
-
*
|
|
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
|
|
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
|
-
* @
|
|
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`.
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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.
|
|
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.#
|
|
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.#
|
|
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
|
|
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
|
-
#
|
|
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
|
*/
|