feat(narration): ground pause narration in the pending approval + roomier budget

Live QA showed two problems with paused-approval narration:

- The narrator was told to "name the stage, outcome, and reason" but the pause
  trigger only carried "APPROVAL_PENDING" with no detail — on a first-stage pause
  there's no L2 history either, so it had nothing to ground on. NarrationSubscriber
  now captures the latest ApprovalRequestedEvent per session (tool name, tier,
  preview) and folds it into the pause instruction (preview truncated to 800
  chars).
- Narration reused the 512-token chat generation config; a reasoning model spent
  the whole budget "thinking" and returned empty content (finishReason=length).
  Add a separate narrationGenerationConfig (maxTokens=1024) so thinking can
  complete and still leave room for the one or two sentences.

Adds a subscriber test asserting the pause narration names the tool and includes
the preview.
This commit is contained in:
2026-06-03 22:55:13 +04:00
parent e6084dcbda
commit e95d2633f8
4 changed files with 81 additions and 2 deletions
@@ -181,7 +181,7 @@ class DefaultRouterFacade(
sessionId = sessionId,
stageId = effectiveStageId,
contextPack = contextPack,
generationConfig = config.generationConfig,
generationConfig = config.narrationGenerationConfig,
responseFormat = ResponseFormat.Text,
)
val inferenceResponse = provider.infer(inferenceRequest)
@@ -14,4 +14,13 @@ data class RouterConfig(
topP = 0.9,
maxTokens = 512,
),
// Narration uses a larger token ceiling: reasoning models spend tokens "thinking"
// before emitting content, and a 512 cap left no room for the actual line (the
// response came back empty with finishReason=length). 1024 lets thinking complete
// and still produce the one or two sentences.
val narrationGenerationConfig: GenerationConfig = GenerationConfig(
temperature = 0.7,
topP = 0.9,
maxTokens = 1024,
),
)