mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-09-01 11:33:44 +00:00
* 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
106 lines
4.2 KiB
JavaScript
106 lines
4.2 KiB
JavaScript
const {
|
|
cacheConfig,
|
|
ioredisClient,
|
|
isEnabled,
|
|
registerShutdownTask,
|
|
duplicateIoRedisClient,
|
|
createSubagentThreadTaskStore,
|
|
createSubagentCompletionWakeupHandler,
|
|
RedisSubagentTaskControlTransport,
|
|
RedisEventTransport,
|
|
SubagentActivityStream,
|
|
} = require('@librechat/api');
|
|
const db = require('~/models');
|
|
const { enqueueAgentTrigger } = require('../../Agents/triggers');
|
|
|
|
/** Keep producers off for the first rollout so older trigger workers cannot
|
|
* permanently reject the new `continue` envelope. Enable only after every API
|
|
* replica runs a release that understands completion wakeups. */
|
|
const completionWakeupsEnabled = isEnabled(process.env.ENABLE_SUBAGENT_COMPLETION_WAKEUPS);
|
|
|
|
/** Durable logical threads use normal LibreChat conversations/messages. Mongo
|
|
* fences continuation; optional Redis routing reaches the live owning process. */
|
|
const subagentThreadTaskStore = createSubagentThreadTaskStore(
|
|
{
|
|
acquireSubagentThreadLease: db.acquireSubagentThreadLease,
|
|
claimSubagentTaskResult: db.claimSubagentTaskResult,
|
|
releaseSubagentTaskResultClaim: db.releaseSubagentTaskResultClaim,
|
|
countActiveSubagentThreadLeases: db.countActiveSubagentThreadLeases,
|
|
deleteConvos: db.deleteConvos,
|
|
deleteMessages: db.deleteMessages,
|
|
getConvo: db.getConvo,
|
|
getMessages: db.getMessages,
|
|
listActiveSubagentThreadLeases: db.listActiveSubagentThreadLeases,
|
|
releaseSubagentThreadLease: db.releaseSubagentThreadLease,
|
|
reserveSubagentThread: db.reserveSubagentThread,
|
|
renewSubagentThreadLease: db.renewSubagentThreadLease,
|
|
saveConvo: db.saveConvo,
|
|
saveMessage: db.saveMessage,
|
|
},
|
|
{
|
|
isOwnerActive: db.isSubagentOwnerAdmissible,
|
|
fenceOwnerAdmission: db.fenceSubagentAdmission,
|
|
renewOwnerAdmission: db.renewSubagentAdmission,
|
|
releaseOwnerAdmission: db.releaseSubagentAdmission,
|
|
...(completionWakeupsEnabled && {
|
|
onTaskPrepared: createSubagentCompletionWakeupHandler(enqueueAgentTrigger),
|
|
}),
|
|
},
|
|
);
|
|
|
|
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. */
|
|
async function configureSubagentTaskRouting() {
|
|
if (taskRoutingConfigured || !cacheConfig.USE_REDIS) {
|
|
return;
|
|
}
|
|
if (ioredisClient == null || typeof ioredisClient.duplicate !== 'function') {
|
|
throw new Error('Redis subagent task routing requires a dedicated subscriber connection.');
|
|
}
|
|
const subscriber = ioredisClient.duplicate();
|
|
/** A dedicated publisher without the offline queue: the shared client would hold a
|
|
* command issued during a disconnect and deliver it after the caller gave up, so a
|
|
* 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;
|
|
registerShutdownTask(
|
|
'subagent task control transport',
|
|
async () => {
|
|
await subagentThreadTaskStore.destroyTaskControlTransport();
|
|
subagentThreadTaskStore.destroyActivityStream();
|
|
publisher.disconnect();
|
|
activitySubscriber.disconnect();
|
|
activityPublisher.disconnect();
|
|
},
|
|
{ priority: 90 },
|
|
);
|
|
}
|
|
|
|
module.exports = subagentThreadTaskStore;
|
|
module.exports.completionWakeupsEnabled = completionWakeupsEnabled;
|
|
module.exports.configureSubagentTaskRouting = configureSubagentTaskRouting;
|