From 17bd84d56b2aeec3fb76b81a9ad12e6c36935420 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Fri, 3 Apr 2026 16:42:52 +0200 Subject: [PATCH] feat: inject mode changes into engine conversation history Engine.InjectMessage() appends messages to history without triggering a turn. When permission mode or incognito changes, the notification is injected as a user+assistant pair so the model sees it as context. Fixes: model now knows permissions changed and will retry tool calls instead of remembering old denials from previous mode. --- internal/engine/engine.go | 7 +++++++ internal/tui/app.go | 31 +++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/internal/engine/engine.go b/internal/engine/engine.go index a0f0372..461a337 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -107,6 +107,13 @@ func (e *Engine) History() []message.Message { return e.history } +// InjectMessage appends a message to conversation history without triggering a turn. +// Used for system notifications (permission mode changes, incognito toggles) that +// the model should see as context in subsequent turns. +func (e *Engine) InjectMessage(msg message.Message) { + e.history = append(e.history, msg) +} + // Usage returns cumulative token usage. func (e *Engine) Usage() message.Usage { return e.usage diff --git a/internal/tui/app.go b/internal/tui/app.go index 4420b47..c44ff69 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -11,6 +11,7 @@ import ( "charm.land/bubbles/v2/textinput" "charm.land/lipgloss/v2" "somegit.dev/Owlibou/gnoma/internal/engine" + "somegit.dev/Owlibou/gnoma/internal/message" "somegit.dev/Owlibou/gnoma/internal/permission" "somegit.dev/Owlibou/gnoma/internal/security" "somegit.dev/Owlibou/gnoma/internal/session" @@ -126,13 +127,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Toggle incognito if m.config.Firewall != nil { m.incognito = m.config.Firewall.Incognito().Toggle() + var msg string if m.incognito { - m.messages = append(m.messages, chatMessage{role: "system", - content: "πŸ”’ incognito ON β€” no persistence, no learning, no logging"}) + msg = "πŸ”’ incognito ON β€” no persistence, no learning, no logging" } else { - m.messages = append(m.messages, chatMessage{role: "system", - content: "πŸ”“ incognito OFF"}) + msg = "πŸ”“ incognito OFF" } + m.messages = append(m.messages, chatMessage{role: "system", content: msg}) + m.injectSystemContext(msg) m.scrollOffset = 0 } return m, nil @@ -156,8 +158,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { next = permission.ModeBypass } m.config.Permissions.SetMode(next) - m.messages = append(m.messages, chatMessage{role: "system", - content: fmt.Sprintf("permission mode changed to: %s β€” previous tool denials no longer apply, retry if asked", next)}) + msg := fmt.Sprintf("permission mode changed to: %s β€” previous tool denials no longer apply, retry if asked", next) + m.messages = append(m.messages, chatMessage{role: "system", content: msg}) + m.injectSystemContext(msg) m.scrollOffset = 0 } return m, nil @@ -309,8 +312,9 @@ func (m Model) handleCommand(cmd string) (tea.Model, tea.Cmd) { return m, nil } m.config.Permissions.SetMode(mode) - m.messages = append(m.messages, chatMessage{role: "system", - content: fmt.Sprintf("permission mode changed to: %s β€” previous tool denials no longer apply, retry if asked", mode)}) + msg := fmt.Sprintf("permission mode changed to: %s β€” previous tool denials no longer apply, retry if asked", mode) + m.messages = append(m.messages, chatMessage{role: "system", content: msg}) + m.injectSystemContext(msg) return m, nil case "/provider": @@ -679,6 +683,17 @@ func wrapText(text string, width int) string { return result.String() } +// injectSystemContext adds a message to the engine's conversation history +// so the model sees it as context in subsequent turns. +func (m Model) injectSystemContext(text string) { + if m.config.Engine != nil { + m.config.Engine.InjectMessage(message.NewUserText("[system] " + text)) + // Immediately follow with a synthetic assistant acknowledgment + // so the conversation stays in userβ†’assistant alternation + m.config.Engine.InjectMessage(message.NewAssistantText("Understood.")) + } +} + func detectGitBranch() string { cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") out, err := cmd.Output()