diff --git a/src/auto-reply/reply/directive-handling.impl.ts b/src/auto-reply/reply/directive-handling.impl.ts index da533526519..bfbf204ef54 100644 --- a/src/auto-reply/reply/directive-handling.impl.ts +++ b/src/auto-reply/reply/directive-handling.impl.ts @@ -99,10 +99,12 @@ export async function handleDirectiveOnly( }).sandboxed; const shouldHintDirectRuntime = directives.hasElevatedDirective && !runtimeIsSandboxed; const allowInternalExecPersistence = canPersistInternalExecDirective({ + messageProvider: params.messageProvider, surface: params.surface, gatewayClientScopes: params.gatewayClientScopes, }); const allowInternalVerbosePersistence = canPersistInternalVerboseDirective({ + messageProvider: params.messageProvider, surface: params.surface, gatewayClientScopes: params.gatewayClientScopes, }); diff --git a/src/auto-reply/reply/directive-handling.model.test.ts b/src/auto-reply/reply/directive-handling.model.test.ts index b34547eb3d3..bb64f61254e 100644 --- a/src/auto-reply/reply/directive-handling.model.test.ts +++ b/src/auto-reply/reply/directive-handling.model.test.ts @@ -717,4 +717,39 @@ describe("persistInlineDirectives internal exec scope gate", () => { expect(sessionEntry.verboseLevel).toBeUndefined(); }); + + it("treats internal provider context as authoritative over external surface metadata", async () => { + const allowedModelKeys = new Set(["anthropic/claude-opus-4-5", "openai/gpt-4o"]); + const directives = parseInlineDirectives("/verbose full"); + const sessionEntry = { + sessionId: "s1", + updatedAt: Date.now(), + } as SessionEntry; + const sessionStore = { "agent:main:main": sessionEntry }; + + await persistInlineDirectives({ + directives, + cfg: baseConfig(), + sessionEntry, + sessionStore, + sessionKey: "agent:main:main", + storePath: "/tmp/sessions.json", + elevatedEnabled: true, + elevatedAllowed: true, + defaultProvider: "anthropic", + defaultModel: "claude-opus-4-5", + aliasIndex: baseAliasIndex(), + allowedModelKeys, + provider: "anthropic", + model: "claude-opus-4-5", + initialModelLabel: "anthropic/claude-opus-4-5", + formatModelSwitchEvent: (label) => `Switched to ${label}`, + agentCfg: undefined, + messageProvider: "webchat", + surface: "telegram", + gatewayClientScopes: ["operator.write"], + }); + + expect(sessionEntry.verboseLevel).toBeUndefined(); + }); }); diff --git a/src/auto-reply/reply/directive-handling.params.ts b/src/auto-reply/reply/directive-handling.params.ts index 0b14b536581..dcbf47a9093 100644 --- a/src/auto-reply/reply/directive-handling.params.ts +++ b/src/auto-reply/reply/directive-handling.params.ts @@ -31,6 +31,7 @@ export type HandleDirectiveOnlyCoreParams = { }; export type HandleDirectiveOnlyParams = HandleDirectiveOnlyCoreParams & { + messageProvider?: string; currentThinkLevel?: ThinkLevel; currentFastMode?: boolean; currentVerboseLevel?: VerboseLevel; diff --git a/src/auto-reply/reply/directive-handling.persist.ts b/src/auto-reply/reply/directive-handling.persist.ts index 9235ed0bbea..364ec795f25 100644 --- a/src/auto-reply/reply/directive-handling.persist.ts +++ b/src/auto-reply/reply/directive-handling.persist.ts @@ -41,6 +41,7 @@ export async function persistInlineDirectives(params: { initialModelLabel: string; formatModelSwitchEvent: (label: string, alias?: string) => string; agentCfg: NonNullable["defaults"] | undefined; + messageProvider?: string; surface?: string; gatewayClientScopes?: string[]; }): Promise<{ provider: string; model: string; contextTokens: number }> { @@ -63,10 +64,12 @@ export async function persistInlineDirectives(params: { } = params; let { provider, model } = params; const allowInternalExecPersistence = canPersistInternalExecDirective({ + messageProvider: params.messageProvider, surface: params.surface, gatewayClientScopes: params.gatewayClientScopes, }); const allowInternalVerbosePersistence = canPersistInternalVerboseDirective({ + messageProvider: params.messageProvider, surface: params.surface, gatewayClientScopes: params.gatewayClientScopes, }); diff --git a/src/auto-reply/reply/directive-handling.shared.ts b/src/auto-reply/reply/directive-handling.shared.ts index c4306042e05..45520f88bb2 100644 --- a/src/auto-reply/reply/directive-handling.shared.ts +++ b/src/auto-reply/reply/directive-handling.shared.ts @@ -24,10 +24,14 @@ export const formatInternalVerboseCurrentReplyOnlyText = () => "Verbose logging set for the current reply only."; function canPersistInternalDirective(params: { + messageProvider?: string; surface?: string; gatewayClientScopes?: string[]; }): boolean { - if (!isInternalMessageChannel(params.surface)) { + const authoritativeChannel = isInternalMessageChannel(params.messageProvider) + ? params.messageProvider + : params.surface; + if (!isInternalMessageChannel(authoritativeChannel)) { return true; } const scopes = params.gatewayClientScopes ?? []; diff --git a/src/auto-reply/reply/get-reply-directives-apply.ts b/src/auto-reply/reply/get-reply-directives-apply.ts index c74d62dfc68..5c62b18260e 100644 --- a/src/auto-reply/reply/get-reply-directives-apply.ts +++ b/src/auto-reply/reply/get-reply-directives-apply.ts @@ -233,6 +233,7 @@ export async function applyInlineDirectiveOverrides(params: { currentVerboseLevel, currentReasoningLevel, currentElevatedLevel, + messageProvider: ctx.Provider, surface: ctx.Surface, gatewayClientScopes: ctx.GatewayClientScopes, }); @@ -328,6 +329,7 @@ export async function applyInlineDirectiveOverrides(params: { initialModelLabel, formatModelSwitchEvent, agentCfg, + messageProvider: ctx.Provider, surface: ctx.Surface, gatewayClientScopes: ctx.GatewayClientScopes, });