@threadplane/langgraph 0.0.60 → 0.0.62
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.
|
@@ -361,14 +361,28 @@ class SubagentTracker {
|
|
|
361
361
|
return establish(toolCallId);
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
|
-
// Last-resort fallback — tool children only
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
364
|
+
// Last-resort fallback — tool children only, and only when there is
|
|
365
|
+
// nothing to guess between.
|
|
366
|
+
//
|
|
367
|
+
// LangGraph's `tools:<uuid>` namespace is a checkpoint id assigned
|
|
368
|
+
// independently of the parent's `call_*` tool-call id; the two are not
|
|
369
|
+
// linked anywhere on the wire (verified against a live run). So when a
|
|
370
|
+
// delegation tool carries no matchable description, position is the only
|
|
371
|
+
// signal left.
|
|
372
|
+
//
|
|
373
|
+
// That is sound with exactly one outstanding child — the shape every graph
|
|
374
|
+
// in this repo produces, since each dispatches one tool call per assistant
|
|
375
|
+
// turn. With several outstanding at once (parallel fan-out) arrival order
|
|
376
|
+
// is NOT dispatch order, and claiming the first unmapped call cross-wires
|
|
377
|
+
// the children: one card renders another's output. Leaving the stream
|
|
378
|
+
// unattributed keeps its messages buffered instead, so an empty card is
|
|
379
|
+
// the worst case rather than a confidently wrong one. It can still resolve
|
|
380
|
+
// later: as siblings complete, the candidate set shrinks back to one.
|
|
381
|
+
const candidates = [...this.subagents].filter(([toolCallId, subagent]) => subagent.kind === 'tool' &&
|
|
382
|
+
!mapped.has(toolCallId) &&
|
|
383
|
+
(subagent.status === 'pending' || subagent.status === 'running'));
|
|
384
|
+
if (candidates.length === 1) {
|
|
385
|
+
return establish(candidates[0][0]);
|
|
372
386
|
}
|
|
373
387
|
if (description) {
|
|
374
388
|
this.pendingMatches.set(namespaceId, description);
|
|
@@ -393,6 +407,32 @@ class SubagentTracker {
|
|
|
393
407
|
});
|
|
394
408
|
this.onSubagentChange?.();
|
|
395
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Authoritative namespace→tool-call binding, from the server.
|
|
412
|
+
*
|
|
413
|
+
* `threadplane.middleware.langgraph.announce_subagent` emits a custom event
|
|
414
|
+
* pairing the child's checkpoint namespace with its tool-call id — the two
|
|
415
|
+
* halves that are never linked on the wire otherwise. Unlike the matching
|
|
416
|
+
* ladder this is not a heuristic: it overrides nothing that is already
|
|
417
|
+
* mapped, works with any number of children outstanding, and replays any
|
|
418
|
+
* chunks that streamed before the binding arrived.
|
|
419
|
+
*/
|
|
420
|
+
bindChildStream(namespaceId, toolCallId) {
|
|
421
|
+
if (this.namespaceToToolCallId.get(namespaceId) === toolCallId)
|
|
422
|
+
return;
|
|
423
|
+
this.namespaceToToolCallId.set(namespaceId, toolCallId);
|
|
424
|
+
const subagent = this.subagents.get(toolCallId);
|
|
425
|
+
if (subagent) {
|
|
426
|
+
const buffered = this.unattributedMessages.get(namespaceId);
|
|
427
|
+
this.subagents.set(toolCallId, {
|
|
428
|
+
...subagent,
|
|
429
|
+
status: subagent.status === 'complete' || subagent.status === 'error' ? subagent.status : 'running',
|
|
430
|
+
messages: buffered ? mergeMessages$1(subagent.messages, buffered) : subagent.messages,
|
|
431
|
+
});
|
|
432
|
+
this.unattributedMessages.delete(namespaceId);
|
|
433
|
+
}
|
|
434
|
+
this.onSubagentChange?.();
|
|
435
|
+
}
|
|
396
436
|
/**
|
|
397
437
|
* Attribute a `tools:` child stream to its parent tool call as soon as the
|
|
398
438
|
* child is seen, without requiring a description to match on.
|
|
@@ -1470,6 +1510,20 @@ function createStreamManagerBridge({ options, subjects, threadId$, destroy$ }) {
|
|
|
1470
1510
|
break;
|
|
1471
1511
|
case 'custom': {
|
|
1472
1512
|
const eventData = event['data'];
|
|
1513
|
+
// Server-announced subagent identity (threadplane-middleware's
|
|
1514
|
+
// `announce_subagent`). Consumed here rather than forwarded: it is
|
|
1515
|
+
// protocol chatter, not application data.
|
|
1516
|
+
if (isRecord$1(eventData)
|
|
1517
|
+
&& eventData['type'] === 'threadplane.subagent_binding'
|
|
1518
|
+
&& typeof eventData['namespace'] === 'string'
|
|
1519
|
+
&& typeof eventData['tool_call_id'] === 'string') {
|
|
1520
|
+
const bound = childStreamRefFromNamespace([eventData['namespace']]);
|
|
1521
|
+
if (bound?.kind === 'tool') {
|
|
1522
|
+
subagentManager.bindChildStream(bound.key, eventData['tool_call_id']);
|
|
1523
|
+
publishSubagents();
|
|
1524
|
+
}
|
|
1525
|
+
break;
|
|
1526
|
+
}
|
|
1473
1527
|
const name = (event['name'] ?? eventData?.['name'] ?? '');
|
|
1474
1528
|
const data = eventData?.['data'] ?? eventData;
|
|
1475
1529
|
const current = subjects.custom$.value;
|