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).
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user