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.
This commit is contained in:
2026-04-03 16:42:52 +02:00
parent 9e064e52ad
commit 17bd84d56b
2 changed files with 30 additions and 8 deletions

View File

@@ -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

View File

@@ -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()