feat: wire ChatMode.STEERING toggle in TUI input bar

- Add ChatMode.STEERING toggle via ctrl+s keybinding
- Add KeyEvent.ToggleSteeringMode, Action.CycleChatMode
- TambouiKeyMapper: map ctrl+s to ToggleSteeringMode
- KeyResolver: route ToggleSteeringMode to CycleChatMode (global)
- InputReducer: toggle chatMode between CHAT and STEERING
- TuiState: add chatMode field (default CHAT)
- SessionsReducer: use ctx.chatMode instead of hardcoded CHAT
- RootReducer: pass afterInput.chatMode to SessionsReducerContext
- InputBar: show blue 'chat' or magenta 'steer' label in status line

Activation: ctrl+s toggles mode, 'steer' label appears, submitted
ChatInput uses ChatMode.STEERING → router emits SteeringNoteAddedEvent
→ context builder folds into next stage's context pack
This commit is contained in:
2026-05-29 02:27:23 +04:00
parent 3981d8443d
commit fb3ab9b344
10 changed files with 45 additions and 8 deletions
@@ -549,16 +549,28 @@ abstract class SessionOrchestrator(
private suspend fun buildSteeringNoteEntries(sessionId: SessionId): List<ContextEntry> {
val events = eventStore.read(sessionId)
return events.mapNotNull { event ->
(event.payload as? SteeringNoteAddedEvent)?.let { steering ->
ContextEntry(
when (val p = event.payload) {
is SteeringNoteAddedEvent -> ContextEntry(
id = ContextEntryId(UUID.randomUUID().toString()),
layer = ContextLayer.L2,
content = steering.content,
content = p.content,
sourceType = "steeringNote",
sourceId = steering.stageId?.value ?: sessionId.value,
tokenEstimate = estimateTokens(steering.content),
sourceId = p.stageId?.value ?: sessionId.value,
tokenEstimate = estimateTokens(p.content),
role = EntryRole.SYSTEM,
)
is ApprovalDecisionResolvedEvent -> p.userSteering?.let { steering ->
ContextEntry(
id = ContextEntryId(UUID.randomUUID().toString()),
layer = ContextLayer.L2,
content = steering.text,
sourceType = "steeringNote",
sourceId = steering.sessionId.value,
tokenEstimate = estimateTokens(steering.text),
role = EntryRole.USER,
)
}
else -> null
}
}
}