fix: chat session reliability, cost, and hygiene (empty-response guard, tool truncation, delete, titles)
- Empty/refusal responses retried once, then surfaced as errors instead of silent blanks
- Chinese refusal boilerplate detected via denylist + non-ASCII heuristic
- Bulk-tool preference added to SOUL.md (list_lxcs over per-entity get_lxc_state)
- Tool results truncated to 4KB on persist; get_state_snapshot filters null-state entities
- Session delete (DELETE /sessions/{id} + confirm-on-second-click UI)
- Session titles auto-generated from assistant answer instead of raw user message
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
const maxToolResultSize = 4096
|
||||
|
||||
type store struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
@@ -71,10 +73,45 @@ func (s *store) saveMessage(ctx context.Context, sessionID, role string, content
|
||||
}
|
||||
_, err := s.pool.Exec(ctx,
|
||||
`INSERT INTO agent_messages (session_id, role, content) VALUES ($1, $2, $3)`,
|
||||
sessionID, role, content)
|
||||
sessionID, role, truncateToolResults(content))
|
||||
return err
|
||||
}
|
||||
|
||||
func truncateToolResults(content json.RawMessage) json.RawMessage {
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(content, &m); err != nil {
|
||||
return content
|
||||
}
|
||||
toolCalls, ok := m["tool_calls"].([]any)
|
||||
if !ok || len(toolCalls) == 0 {
|
||||
return content
|
||||
}
|
||||
changed := false
|
||||
for i, raw := range toolCalls {
|
||||
tc, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if result, ok := tc["result"]; ok {
|
||||
resultJSON, _ := json.Marshal(result)
|
||||
if len(resultJSON) > maxToolResultSize {
|
||||
tc["result"] = string(resultJSON[:maxToolResultSize]) + fmt.Sprintf("...truncated (%d bytes total)", len(resultJSON))
|
||||
toolCalls[i] = tc
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return content
|
||||
}
|
||||
m["tool_calls"] = toolCalls
|
||||
out, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return content
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *store) touchSession(ctx context.Context, id string) {
|
||||
if s != nil {
|
||||
s.pool.Exec(ctx, `UPDATE agent_sessions SET last_active_at=now() WHERE id=$1`, id)
|
||||
@@ -126,6 +163,26 @@ func (s *store) getMessages(ctx context.Context, sessionID string) ([]message, e
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *store) deleteSession(ctx context.Context, id string) error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := s.pool.Exec(ctx, `DELETE FROM agent_messages WHERE session_id = $1`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.pool.Exec(ctx, `DELETE FROM agent_sessions WHERE id = $1`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *store) updateSessionTitle(ctx context.Context, id, title string) error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := s.pool.Exec(ctx, `UPDATE agent_sessions SET title = $1 WHERE id = $2`, title, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// resolveAgentID looks up the UUID of the agent entity (e.g. "agent:nomos").
|
||||
// Returns uuid.Nil if the store is absent or the slug is unknown.
|
||||
func (s *store) resolveAgentID(ctx context.Context, slug string) uuid.UUID {
|
||||
|
||||
Reference in New Issue
Block a user