first commit
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
// This file implements the /memory slash command (US-011, FR-20, #484) that
|
||||
// prints a colored report describing the persistent memory store and the
|
||||
// infinite-context state: entry counts by scope, the current context window and
|
||||
// auto-compaction trigger point, current context usage, and checkpoint status.
|
||||
//
|
||||
// Unlike /status (which reads everything through cli.Host), /memory takes its
|
||||
// inputs explicitly so both the REPL and the TUI can call it with the live
|
||||
// memory store, which the Host contract does not expose.
|
||||
package memstatus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/smallnest/pigo/internal/agentcore"
|
||||
"github.com/smallnest/pigo/internal/cli/ui"
|
||||
"github.com/smallnest/pigo/internal/compaction"
|
||||
"github.com/smallnest/pigo/internal/memory"
|
||||
"github.com/smallnest/pigo/internal/runtime"
|
||||
)
|
||||
|
||||
// scopeOrder is the display order for memory scopes. Scopes not listed here are
|
||||
// appended afterwards in lexical order.
|
||||
var scopeOrder = []memory.Scope{
|
||||
memory.ScopeGlobal,
|
||||
memory.ScopeProjects,
|
||||
memory.ScopeSessions,
|
||||
memory.ScopeCC,
|
||||
}
|
||||
|
||||
// RunMemory prints a colored multi-section report about persistent memory and
|
||||
// infinite context to out. store may be nil (persistent memory disabled), in
|
||||
// which case the memory-store section reports that state and the checkpoint /
|
||||
// context sections still render from msgs and the checkpoint file.
|
||||
func RunMemory(out io.Writer, store *memory.Store, memoryRoot, sessionID string, msgs agentcore.MessageList, contextWindow int) {
|
||||
color := ui.Enabled()
|
||||
|
||||
fmt.Fprintln(out)
|
||||
printStoreSection(out, color, store, memoryRoot)
|
||||
fmt.Fprintln(out)
|
||||
printContextSection(out, color, msgs, contextWindow)
|
||||
fmt.Fprintln(out)
|
||||
printCheckpointSection(out, color, memoryRoot, sessionID)
|
||||
}
|
||||
|
||||
// printStoreSection prints the persistent memory store section: whether memory
|
||||
// is enabled, the root directory, and entry counts per scope. It reconciles the
|
||||
// on-disk files into the index first so the counts are fresh.
|
||||
func printStoreSection(out io.Writer, color bool, store *memory.Store, memoryRoot string) {
|
||||
fmt.Fprintf(out, "%s\n", ui.Colorize(color, ui.Bold, "persistent memory:"))
|
||||
if store == nil {
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "status:"),
|
||||
ui.Colorize(color, ui.Yellow, "disabled"))
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "status:"),
|
||||
ui.Colorize(color, ui.Green, "enabled"))
|
||||
if memoryRoot != "" {
|
||||
fmt.Fprintf(out, " %s %s\n", ui.Colorize(color, ui.Dim, "root:"), memoryRoot)
|
||||
}
|
||||
|
||||
if _, err := store.Reconcile(); err != nil {
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "entries:"),
|
||||
ui.Colorize(color, ui.Red, fmt.Sprintf("reconcile failed: %v", err)))
|
||||
return
|
||||
}
|
||||
counts, err := store.CountByScope()
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "entries:"),
|
||||
ui.Colorize(color, ui.Red, fmt.Sprintf("count failed: %v", err)))
|
||||
return
|
||||
}
|
||||
|
||||
total := 0
|
||||
for _, n := range counts {
|
||||
total += n
|
||||
}
|
||||
fmt.Fprintf(out, " %s %d\n", ui.Colorize(color, ui.Dim, "entries:"), total)
|
||||
for _, scope := range orderedScopes(counts) {
|
||||
fmt.Fprintf(out, " %s %d\n",
|
||||
ui.Colorize(color, ui.Dim, string(scope)+":"), counts[scope])
|
||||
}
|
||||
}
|
||||
|
||||
// orderedScopes returns the scopes present in counts, listing the well-known
|
||||
// scopes first (scopeOrder) and any others afterwards in lexical order.
|
||||
func orderedScopes(counts map[memory.Scope]int) []memory.Scope {
|
||||
seen := make(map[memory.Scope]bool, len(counts))
|
||||
var out []memory.Scope
|
||||
for _, s := range scopeOrder {
|
||||
if _, ok := counts[s]; ok {
|
||||
out = append(out, s)
|
||||
seen[s] = true
|
||||
}
|
||||
}
|
||||
var extra []memory.Scope
|
||||
for s := range counts {
|
||||
if !seen[s] {
|
||||
extra = append(extra, s)
|
||||
}
|
||||
}
|
||||
sort.Slice(extra, func(i, j int) bool { return extra[i] < extra[j] })
|
||||
return append(out, extra...)
|
||||
}
|
||||
|
||||
// printContextSection prints the current context usage and the auto-compaction
|
||||
// trigger point (window minus reserve), mirroring /status's context block so
|
||||
// /memory is self-contained for infinite-context inspection.
|
||||
func printContextSection(out io.Writer, color bool, msgs agentcore.MessageList, contextWindow int) {
|
||||
tokens := compaction.EstimateContextTokens(msgs).Tokens
|
||||
|
||||
fmt.Fprintf(out, "%s\n", ui.Colorize(color, ui.Bold, "context:"))
|
||||
if contextWindow > 0 {
|
||||
fmt.Fprintf(out, " %s %d / %d tokens\n",
|
||||
ui.Colorize(color, ui.Dim, "current:"), tokens, contextWindow)
|
||||
util := int(float64(tokens) / float64(contextWindow) * 100)
|
||||
utilColor := ui.Green
|
||||
if util >= 90 {
|
||||
utilColor = ui.Red
|
||||
} else if util >= 70 {
|
||||
utilColor = ui.Yellow
|
||||
}
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "utilization:"),
|
||||
ui.Colorize(color, utilColor, fmt.Sprintf("%d%%", util)))
|
||||
|
||||
reserve := compaction.DefaultCompactionSettings.ReserveTokens
|
||||
threshold := contextWindow - reserve
|
||||
remaining := threshold - tokens
|
||||
if remaining < 0 {
|
||||
fmt.Fprintf(out, " %s %s (window: %d, reserve: %d)\n",
|
||||
ui.Colorize(color, ui.Dim, "compaction trigger:"),
|
||||
ui.Colorize(color, ui.Red, fmt.Sprintf("%d tokens over trigger (%d)", -remaining, threshold)),
|
||||
contextWindow, reserve)
|
||||
} else {
|
||||
fmt.Fprintf(out, " %s %s (window: %d, reserve: %d)\n",
|
||||
ui.Colorize(color, ui.Dim, "compaction trigger:"),
|
||||
ui.Colorize(color, ui.Green, fmt.Sprintf("%d tokens until trigger (%d)", remaining, threshold)),
|
||||
contextWindow, reserve)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(out, " %s %d tokens\n", ui.Colorize(color, ui.Dim, "current:"), tokens)
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "compaction trigger:"),
|
||||
ui.Colorize(color, ui.Yellow, "auto-compaction disabled (unknown window)"))
|
||||
}
|
||||
}
|
||||
|
||||
// printCheckpointSection prints the infinite-context checkpoint status for the
|
||||
// session: whether a checkpoint.md exists and, if so, its watermark, covered
|
||||
// message count, and creation time.
|
||||
func printCheckpointSection(out io.Writer, color bool, memoryRoot, sessionID string) {
|
||||
fmt.Fprintf(out, "%s\n", ui.Colorize(color, ui.Bold, "checkpoint:"))
|
||||
if memoryRoot == "" || sessionID == "" {
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "status:"),
|
||||
ui.Colorize(color, ui.Yellow, "unavailable (no memory root or session id)"))
|
||||
return
|
||||
}
|
||||
cp, ok, err := runtime.LoadCheckpoint(sessionID, memoryRoot)
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "status:"),
|
||||
ui.Colorize(color, ui.Red, fmt.Sprintf("load failed: %v", err)))
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "status:"),
|
||||
ui.Colorize(color, ui.Dim, "none yet"))
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "path:"),
|
||||
runtime.CheckpointPath(sessionID, memoryRoot))
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "status:"),
|
||||
ui.Colorize(color, ui.Green, "present"))
|
||||
fmt.Fprintf(out, " %s %d\n", ui.Colorize(color, ui.Dim, "watermark:"), cp.Watermark)
|
||||
fmt.Fprintf(out, " %s %d\n", ui.Colorize(color, ui.Dim, "covered messages:"), cp.CoveredMessages)
|
||||
if !cp.CreatedAt.IsZero() {
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "created:"),
|
||||
cp.CreatedAt.Format("2006-01-02 15:04:05 MST"))
|
||||
}
|
||||
fmt.Fprintf(out, " %s %s\n",
|
||||
ui.Colorize(color, ui.Dim, "path:"),
|
||||
runtime.CheckpointPath(sessionID, memoryRoot))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user