From 99fea6482316c70b30d555b6c78629df6c2f0bd5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 12 Jan 2026 21:44:09 +0000 Subject: [PATCH] fix: fast-lane directives bypass queue dedupe --- src/auto-reply/reply.ts | 78 +++++++++++++++++++ .../reply/queue.collect-routing.test.ts | 4 +- src/auto-reply/reply/queue.ts | 6 +- 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index a8fc461fa..38561bf2f 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -708,6 +708,9 @@ export async function getReplyFromConfig( const inlineStatusRequested = hasInlineStatus && allowTextCommands && command.isAuthorizedSender; + // Inline control directives should apply immediately, even when mixed with text. + let directiveAck: ReplyPayload | undefined; + if (!command.isAuthorizedSender) { directives = { ...directives, @@ -851,6 +854,77 @@ export async function getReplyFromConfig( await opts.onBlockReply(reply); }; + const hasAnyDirective = + directives.hasThinkDirective || + directives.hasVerboseDirective || + directives.hasReasoningDirective || + directives.hasElevatedDirective || + directives.hasModelDirective || + directives.hasQueueDirective || + directives.hasStatusDirective; + + if ( + hasAnyDirective && + command.isAuthorizedSender && + !isDirectiveOnly({ + directives, + cleanedBody: directives.cleaned, + ctx, + cfg, + agentId, + isGroup, + }) + ) { + const resolvedDefaultThinkLevel = + (sessionEntry?.thinkingLevel as ThinkLevel | undefined) ?? + (agentCfg?.thinkingDefault as ThinkLevel | undefined) ?? + (await modelState.resolveDefaultThinkingLevel()); + const currentThinkLevel = resolvedDefaultThinkLevel; + const currentVerboseLevel = + (sessionEntry?.verboseLevel as VerboseLevel | undefined) ?? + (agentCfg?.verboseDefault as VerboseLevel | undefined); + const currentReasoningLevel = + (sessionEntry?.reasoningLevel as ReasoningLevel | undefined) ?? "off"; + const currentElevatedLevel = + (sessionEntry?.elevatedLevel as ElevatedLevel | undefined) ?? + (agentCfg?.elevatedDefault as ElevatedLevel | undefined); + + directiveAck = await handleDirectiveOnly({ + cfg, + directives, + sessionEntry, + sessionStore, + sessionKey, + storePath, + elevatedEnabled, + elevatedAllowed, + elevatedFailures, + messageProviderKey, + defaultProvider, + defaultModel, + aliasIndex, + allowedModelKeys: modelState.allowedModelKeys, + allowedModelCatalog: modelState.allowedModelCatalog, + resetModelOverride: modelState.resetModelOverride, + provider, + model, + initialModelLabel, + formatModelSwitchEvent, + currentThinkLevel, + currentVerboseLevel, + currentReasoningLevel, + currentElevatedLevel, + }); + + // Refresh provider/model from session overrides applied by directives. + if (sessionEntry?.providerOverride) { + provider = sessionEntry.providerOverride; + } + if (sessionEntry?.modelOverride) { + model = sessionEntry.modelOverride; + } + } + const inlineCommand = allowTextCommands && command.isAuthorizedSender ? extractInlineSimpleCommand(cleanedBody) @@ -930,6 +1004,10 @@ export async function getReplyFromConfig( } } + if (directiveAck) { + await sendInlineReply(directiveAck); + } + const isEmptyConfig = Object.keys(cfg).length === 0; const skipWhenConfigEmpty = command.providerId ? Boolean( diff --git a/src/auto-reply/reply/queue.collect-routing.test.ts b/src/auto-reply/reply/queue.collect-routing.test.ts index 74295f1ae..84ad1bf19 100644 --- a/src/auto-reply/reply/queue.collect-routing.test.ts +++ b/src/auto-reply/reply/queue.collect-routing.test.ts @@ -117,7 +117,7 @@ describe("followup queue deduplication", () => { ); expect(first).toBe(true); - // Second enqueue with same prompt should be deduplicated + // Second enqueue with same prompt should be allowed (we only dedupe with message id) const second = enqueueFollowupRun( key, createRun({ @@ -127,7 +127,7 @@ describe("followup queue deduplication", () => { }), settings, ); - expect(second).toBe(false); + expect(second).toBe(true); // Third enqueue with different prompt should succeed const third = enqueueFollowupRun( diff --git a/src/auto-reply/reply/queue.ts b/src/auto-reply/reply/queue.ts index c7b144b75..49f3b0801 100644 --- a/src/auto-reply/reply/queue.ts +++ b/src/auto-reply/reply/queue.ts @@ -358,9 +358,9 @@ function isRunAlreadyQueued( (item) => item.messageId?.trim() === messageId && hasSameRouting(item), ); } - return queue.items.some( - (item) => item.prompt === run.prompt && hasSameRouting(item), - ); + // Without a provider message id, avoid prompt-based dedupe to ensure rapid + // directive messages are not dropped. + return false; } export function enqueueFollowupRun(