📡 feat: Stream Detached Subagent Activity (#15111)

* feat: stream detached subagent activity

* fix: annotate activity stream limits

* fix: isolate subagent activity imports

* fix: harden detached subagent activity lifecycle

* test: cover synchronous activity transport failure

* test: include required subagent activity identity

* fix: identify and reconnect subagent activity events

* fix: bound subagent activity lifecycles

* fix: close subagent activity handoff races

* fix: bind and synchronize activity subscriptions

* fix: detect fresh activity attachment

* fix: complete activity synchronization handoff

* fix: bind activity sync and failure circuits

* fix: expose subscription-bound synchronization

* fix: fence activity reconnect publications

* test: make detached timeout settlement deterministic

* fix: fence Redis activity attachments

* fix: close failed activity streams

* perf: reuse fenced activity frontier

* style: sort subagent thread imports

* fix: preserve queued subagent activity

* test: type activity publication counter

* fix: disconnect subagent activity subscriber

* fix: close background activity lifecycle gaps

* fix: preserve streamed activity spacing

* fix: preserve bounded live subagent activity

* fix: merge durable subagent activity safely

* fix: model detached activity coverage

* fix: type detached activity inputs

* fix: order overlapping subagent activity

* chore: sort activity test imports

* fix: buffer subagent activity handoff gaps

* fix: flush activity after parent close

* fix: advance closed activity suffixes

* fix: preserve detached activity ordering

* fix: close detached activity delivery races

* fix: bound shared Redis subscriber readiness

* fix: expire shared Redis subscription readiness

* fix: clean up late Redis subscriptions

* fix: preserve late Redis subscription fallback
This commit is contained in:
Danny Avila 2026-08-22 09:45:27 -04:00 committed by GitHub
parent 3ebef4c84e
commit d3e70159ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 4125 additions and 110 deletions

View file

@ -7,6 +7,8 @@ const {
createSubagentThreadTaskStore,
createSubagentCompletionWakeupHandler,
RedisSubagentTaskControlTransport,
RedisEventTransport,
SubagentActivityStream,
} = require('@librechat/api');
const db = require('~/models');
const { enqueueAgentTrigger } = require('../../Agents/triggers');
@ -46,6 +48,12 @@ const subagentThreadTaskStore = createSubagentThreadTaskStore(
},
);
registerShutdownTask(
'subagent activity streams prepare',
() => subagentThreadTaskStore.prepareActivityForShutdown(),
{ phase: 'pre-drain', priority: 100 },
);
let taskRoutingConfigured = false;
/** Starts the optional Redis owner directory before HTTP admission opens. */
@ -62,14 +70,21 @@ async function configureSubagentTaskRouting() {
* steer the caller was told had failed could still reach the child. Failing fast
* turns that into the honest `unavailable` the caller already handles. */
const publisher = duplicateIoRedisClient(ioredisClient, { enableOfflineQueue: false });
const activitySubscriber = ioredisClient.duplicate();
const activityPublisher = duplicateIoRedisClient(ioredisClient, { enableOfflineQueue: false });
const transport = new RedisSubagentTaskControlTransport(publisher, subscriber, {
namespace: cacheConfig.REDIS_KEY_PREFIX,
});
try {
await subagentThreadTaskStore.configureTaskControlTransport(transport);
subagentThreadTaskStore.configureActivityStream(
new SubagentActivityStream(new RedisEventTransport(activityPublisher, activitySubscriber)),
);
} catch (error) {
subscriber.disconnect();
publisher.disconnect();
activitySubscriber.disconnect();
activityPublisher.disconnect();
throw error;
}
taskRoutingConfigured = true;
@ -77,7 +92,10 @@ async function configureSubagentTaskRouting() {
'subagent task control transport',
async () => {
await subagentThreadTaskStore.destroyTaskControlTransport();
subagentThreadTaskStore.destroyActivityStream();
publisher.disconnect();
activitySubscriber.disconnect();
activityPublisher.disconnect();
},
{ priority: 90 },
);