Files
kanban_cpp/backend/chat_log.go
T
Egutierrez a76ec74338 feat: initial scaffold kanban_cpp v0.1.0
C++ ImGui kanban for steering LLM agents. Six panels (Board, Calendar,
Dashboard, Agent runs, Worktrees, DoD inspector) wired to registry
functions http_request, kpi_card, sparkline, agent_runs_timeline,
dod_evidence_panel. Backend Go on :8403 (independent operations.db from
kanban_web).
2026-05-18 18:46:09 +02:00

87 lines
2.1 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"sync"
"time"
)
// ChatLogger appends one JSON line per tool invocation to a file. Thread-safe.
// Format per line: {"ts":"...","tool":"...","input":{...},"ok":bool,"error":"...","result_summary":"..."}
type ChatLogger struct {
path string
mu sync.Mutex
}
func newChatLogger(path string) *ChatLogger {
return &ChatLogger{path: path}
}
type ChatLogEntry struct {
TS string `json:"ts"`
Tool string `json:"tool"`
Input json.RawMessage `json:"input"`
OK bool `json:"ok"`
Error string `json:"error,omitempty"`
ResultSummary string `json:"result_summary,omitempty"`
}
func (l *ChatLogger) Log(tool string, input json.RawMessage, res ToolResult) {
if l == nil || l.path == "" {
return
}
entry := ChatLogEntry{
TS: time.Now().UTC().Format(time.RFC3339Nano),
Tool: tool,
Input: input,
OK: res.OK,
Error: res.Error,
}
if res.OK && res.Result != nil {
entry.ResultSummary = summarizeResult(res.Result)
}
line, err := json.Marshal(entry)
if err != nil {
return
}
l.mu.Lock()
defer l.mu.Unlock()
f, err := os.OpenFile(l.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return
}
defer f.Close()
f.Write(line)
f.Write([]byte("\n"))
}
// summarizeResult produces a short description of a tool result for the log.
// Keeps the log line compact: full payloads can be reconstructed from operations.db.
func summarizeResult(v any) string {
switch r := v.(type) {
case *Column:
return fmt.Sprintf("column %s name=%q", r.ID, r.Name)
case *Card:
return fmt.Sprintf("card %s title=%q col=%s", r.ID, r.Title, r.ColumnID)
case []Card:
return fmt.Sprintf("%d cards", len(r))
case []HistoryEntry:
return fmt.Sprintf("%d history entries", len(r))
case map[string]any:
// list_board shape
cols, _ := r["columns"].([]Column)
cards, _ := r["cards"].([]Card)
return fmt.Sprintf("board: %d cols, %d cards", len(cols), len(cards))
}
b, err := json.Marshal(v)
if err != nil || len(b) == 0 {
return ""
}
if len(b) > 200 {
return string(b[:200]) + "..."
}
return string(b)
}