remove emoji reaction feature (core, dbus, KDE GUI)

This commit is contained in:
Ollie Agent 2026-07-31 09:09:24 +02:00
parent cfdedb2e5e
commit f37e81d8cc
4 changed files with 4 additions and 198 deletions

View File

@ -9,7 +9,6 @@ import (
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/simonfxr/pubsub"
"ollie/backend"
@ -439,68 +438,6 @@ func (ag *Agent) ListModels() []string {
return ag.runtime.Backend.Models(context.Background())
}
// Reactions returns a map of response ID → emoji for all recorded reactions.
func (ag *Agent) Reactions() map[string]string {
result := make(map[string]string)
if ag.history == nil {
return result
}
for _, reaction := range ag.history.Reactions {
result[reaction.ResponseID] = reaction.Emoji
}
return result
}
// React records a reaction emoji against the most recent (or specified) response.
func (ag *Agent) React(responseID, emoji string) error {
if ag.history == nil {
return fmt.Errorf("no active session")
}
category, _, _ := classifyReaction(emoji)
if category == "unknown" {
return fmt.Errorf("unsupported reaction: %s", emoji)
}
if responseID == "" {
for i := len(ag.history.messages) - 1; i >= 0; i-- {
if ag.history.messages[i].Role == "assistant" {
responseID = ag.history.messages[i].ID
break
}
}
}
if responseID == "" {
return fmt.Errorf("no assistant response to react to")
}
found := false
for i := range ag.history.messages {
if ag.history.messages[i].Role == "assistant" && ag.history.messages[i].ID == responseID {
found = true
break
}
}
if !found {
return fmt.Errorf("assistant response not found: %s", responseID)
}
reaction := Reaction{ID: NewReactionID(), ResponseID: responseID, Emoji: emoji, Category: category, CreatedAt: time.Now()}
replaced := false
for i := range ag.history.Reactions {
if ag.history.Reactions[i].ResponseID == responseID {
if ag.history.Reactions[i].Emoji == emoji {
return nil
}
ag.history.Reactions[i] = reaction
replaced = true
break
}
}
if !replaced {
ag.history.Reactions = append(ag.history.Reactions, reaction)
}
ag.history.recomputeReactionCounts()
ag.saveSession()
return nil
}
// execServer returns the execute server, or nil if unavailable.
func (ag *Agent) execServer() toolsrv.Runner {
return ag.runtime.ToolServer

View File

@ -36,14 +36,6 @@ Be concise. Capture what another LLM needs to seamlessly continue.`
warmIndexSize = 10
)
type Reaction struct {
ID string `json:"id"`
ResponseID string `json:"responseId"`
Emoji string `json:"emoji"`
Category string `json:"category"`
CreatedAt time.Time `json:"createdAt"`
}
// PersistedAgent is the on-disk format for a saved session.
type PersistedAgent struct {
ID string `json:"id"`
@ -63,10 +55,6 @@ type PersistedAgent struct {
Estimated bool `json:"estimated,omitempty"`
LastTurnCostUSD float64 `json:"lastTurnCostUSD,omitempty"`
SessionCostUSD float64 `json:"sessionCostUSD,omitempty"`
// Reaction tracking.
PositiveReactions int `json:"positiveReactions,omitempty"`
NegativeReactions int `json:"negativeReactions,omitempty"`
Reactions []Reaction `json:"reactions,omitempty"`
}
// TaskState is a compact structured overlay that summarizes the agent's
@ -149,9 +137,6 @@ func (s *History) saveToFull(path, id, agentName, backendName, modelName, cwd, r
Estimated: s.Estimated,
LastTurnCostUSD: s.LastTurnCostUSD,
SessionCostUSD: s.SessionCostUSD,
PositiveReactions: s.PositiveReactions,
NegativeReactions: s.NegativeReactions,
Reactions: s.Reactions,
}
data, err := json.Marshal(ps)
if err != nil {
@ -191,9 +176,6 @@ func RestoreHistory(ps *PersistedAgent) *History {
Estimated: ps.Estimated,
LastTurnCostUSD: ps.LastTurnCostUSD,
SessionCostUSD: ps.SessionCostUSD,
PositiveReactions: ps.PositiveReactions,
NegativeReactions: ps.NegativeReactions,
Reactions: ps.Reactions,
}
for i := range s.messages {
if s.messages[i].Role == "assistant" && s.messages[i].ID == "" {
@ -229,10 +211,6 @@ type History struct {
turnCreationTokens int
turnOutputTokens int
turnCostUSD float64 // >0 when backend reported cost directly (e.g. OpenRouter)
// Reaction tracking.
PositiveReactions int
NegativeReactions int
Reactions []Reaction
}
// newHistory creates a new empty Session. The caller is responsible for
@ -258,18 +236,7 @@ func (s *History) Checkpoint(ts TaskState) *History {
}
func (s *History) history() []backend.Message {
if len(s.Reactions) == 0 {
return s.messages
}
// Append feedback after the conversation so assistant tool-call messages stay
// adjacent to their tool results, as required by provider APIs.
out := make([]backend.Message, 0, len(s.messages)+len(s.Reactions))
out = append(out, s.messages...)
for _, r := range s.Reactions {
_, desc, _ := classifyReaction(r.Emoji)
out = append(out, backend.Message{Role: "user", Content: "[reaction to assistant response " + r.ResponseID + ": " + r.Category + " (" + r.Emoji + ")] " + desc})
}
return out
return s.messages
}
func (s *History) taskState() *TaskState {
@ -296,18 +263,7 @@ func (s *History) addUsage(u backend.Usage, estimated bool) {
}
}
func (s *History) recomputeReactionCounts() {
s.PositiveReactions = 0
s.NegativeReactions = 0
for _, r := range s.Reactions {
switch r.Category {
case "positive", "excellent":
s.PositiveReactions++
case "negative", "terrible":
s.NegativeReactions++
}
}
}
func (s *History) recomputeReactionCounts() {}
func (s *History) resetTurnAccumulators() {
s.turnInputTokens = 0
@ -701,30 +657,5 @@ func (s *History) contextDebug() string {
func NewResponseID() string {
b := make([]byte, 3)
rand.Read(b) //nolint:errcheck
return "resp_" + strconv.FormatInt(time.Now().UnixNano(), 10) + "_" + fmt.Sprintf("%06x", b)
}
// NewReactionID generates a unique identifier for a user reaction.
func NewReactionID() string {
b := make([]byte, 3)
rand.Read(b) //nolint:errcheck
return "react_" + strconv.FormatInt(time.Now().UnixNano(), 10) + "_" + fmt.Sprintf("%06x", b)
}
// classifyReaction returns a category and description for a reaction emoji.
func classifyReaction(emoji string) (category, description string, positive bool) {
switch emoji {
case "👍", "✅":
return "positive", "The response was good. Keep doing what you're doing.", true
case "🚀", "🎉":
return "excellent", "The response was exactly what was wanted.", true
case "👎", "❌":
return "negative", "The response was wrong or unhelpful.", false
case "💩", "🤬":
return "terrible", "The response was fundamentally wrong. Stop this approach entirely and reassess from scratch.", false
case "🤔":
return "confused", "The response was unclear or confusing.", false
default:
return "unknown", "", false
}
return "resp_" + strconv.FormatInt(time.Now().UnixNano(), 10) + "_" + fmt.Sprintf("%06x", b)
}

View File

@ -297,21 +297,6 @@ func (a *DBusAdapter) Submit(sessionID, prompt string) (bool, *dbus.Error) {
return true, nil
}
func (a *DBusAdapter) React(sessionID, emoji string) (bool, *dbus.Error) {
return a.ReactTo(sessionID, "", emoji)
}
func (a *DBusAdapter) ReactTo(sessionID, responseID, emoji string) (bool, *dbus.Error) {
sess := fs.Lookup(a.tree, sessionID)
if sess == nil {
return false, nil
}
if err := sess.Core.Agent().React(responseID, emoji); err != nil {
return false, dbus.MakeFailedError(err)
}
return true, nil
}
func (a *DBusAdapter) Interrupt(sessionID string) (bool, *dbus.Error) {
sess := fs.Lookup(a.tree, sessionID)
if sess == nil {
@ -396,18 +381,6 @@ func (a *DBusAdapter) SetConfig(sessionID, key, value string) (bool, *dbus.Error
return true, nil
}
func (a *DBusAdapter) GetReactions(sessionID string) (string, *dbus.Error) {
sess := fs.Lookup(a.tree, sessionID)
if sess == nil {
return "{}", nil
}
data, err := json.Marshal(sess.Core.Agent().Reactions())
if err != nil {
return "", dbus.MakeFailedError(err)
}
return string(data), nil
}
func (a *DBusAdapter) GetContext(sessionID string) (string, *dbus.Error) {
sess := fs.Lookup(a.tree, sessionID)
if sess == nil {
@ -684,8 +657,6 @@ var introspectXML = `<node>
<method name="KillSession"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="b"/></method>
<method name="RenameSession"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="newName"/><arg direction="out" type="b"/></method>
<method name="Submit"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="prompt"/><arg direction="out" type="b"/></method>
<method name="React"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="emoji"/><arg direction="out" type="b"/></method>
<method name="ReactTo"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="responseID"/><arg direction="in" type="s" name="emoji"/><arg direction="out" type="b"/></method>
<method name="Interrupt"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="b"/></method>
<method name="GetState"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
<method name="GetChat"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="x" name="offset"/><arg direction="out" type="s"/><arg direction="out" type="x"/></method>
@ -694,7 +665,6 @@ var introspectXML = `<node>
<method name="GetConfig"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
<method name="SetConfig"><arg direction="in" type="s" name="sessionID"/><arg direction="in" type="s" name="key"/><arg direction="in" type="s" name="value"/><arg direction="out" type="b"/></method>
<method name="GetContext"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
<method name="GetReactions"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="s"/></method>
<method name="ListBackends"><arg direction="out" type="as"/></method>
<method name="ListModelsByBackend"><arg direction="in" type="s" name="backend"/><arg direction="out" type="as"/></method>
<method name="ListModels"><arg direction="in" type="s" name="sessionID"/><arg direction="out" type="as"/></method>

View File

@ -10,8 +10,8 @@ import (
"ollie/agent"
"ollie/backend"
"ollie/toolsrv"
olog "ollie/log"
"ollie/toolsrv"
)
// SessionFileList defines session-level files (shared across agents).
@ -47,8 +47,6 @@ var AgentFileList = []struct {
{"tail", false, false},
{"prompt.prev", false, false},
{"context", false, false},
{"react", false, true},
{"reactions", false, false},
{"toolsrv.loaded", false, false},
{"toolsrv.rev", false, false},
{"tools", false, false},
@ -125,14 +123,6 @@ func (h *sessionHelper) fileSpec(name string, mode os.FileMode) FileSpec {
h.sess.mu.RUnlock()
return data, nil
}
case "reactions":
fs.Read = func() ([]byte, error) {
data, err := json.Marshal(h.sess.Core.Agent().Reactions())
if err != nil {
return nil, err
}
return append(data, '\n'), nil
}
case "chat":
fs.Read = func() ([]byte, error) {
h.sess.mu.RLock()
@ -238,28 +228,6 @@ func (h *sessionHelper) fileSpec(name string, mode os.FileMode) FileSpec {
}
return h.toolRegistry.Load(h.sess.id, input)
}
case "react":
fs.Write = func(data []byte) error {
input := strings.TrimSpace(string(data))
if input == "" {
return nil
}
var req struct {
ResponseID string `json:"responseId"`
Emoji string `json:"emoji"`
}
if strings.HasPrefix(input, "{") {
if err := json.Unmarshal(data, &req); err != nil {
return fmt.Errorf("react: invalid JSON: %w", err)
}
} else {
req.Emoji = input
}
if req.Emoji == "" {
return fmt.Errorf("react: emoji is required")
}
return h.sess.Core.Agent().React(req.ResponseID, req.Emoji)
}
}
// Wait