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