feat: wire persist.Store into engine, elf manager, and agent tools
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
mrand "math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
|
||||
"somegit.dev/Owlibou/gnoma/internal/engine"
|
||||
"encoding/json"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
|
||||
gnomacfg "somegit.dev/Owlibou/gnoma/internal/config"
|
||||
gnomactx "somegit.dev/Owlibou/gnoma/internal/context"
|
||||
"somegit.dev/Owlibou/gnoma/internal/message"
|
||||
@@ -273,6 +275,14 @@ func main() {
|
||||
}
|
||||
permChecker := permission.NewChecker(permission.Mode(*permMode), permRules, pipePromptFn)
|
||||
|
||||
// Generate session-scoped ID for /tmp artifact directory
|
||||
sessionID := fmt.Sprintf("%s-%06x",
|
||||
time.Now().Format("20060102-150405"),
|
||||
mrand.Int63()&0xffffff,
|
||||
)
|
||||
store := persist.New(sessionID)
|
||||
logger.Debug("session store initialized", "dir", store.Dir())
|
||||
|
||||
// Create elf manager and register agent tools.
|
||||
// Must be created after fw and permChecker so elfs inherit security layers.
|
||||
elfMgr := elf.NewManager(elf.ManagerConfig{
|
||||
@@ -280,13 +290,14 @@ func main() {
|
||||
Tools: reg,
|
||||
Permissions: permChecker,
|
||||
Firewall: fw,
|
||||
Store: store,
|
||||
Logger: logger,
|
||||
})
|
||||
elfProgressCh := make(chan elf.Progress, 16)
|
||||
agentTool := agent.New(elfMgr)
|
||||
agentTool := agent.New(elfMgr, store)
|
||||
agentTool.SetProgressCh(elfProgressCh)
|
||||
reg.Register(agentTool)
|
||||
batchTool := agent.NewBatch(elfMgr)
|
||||
batchTool := agent.NewBatch(elfMgr, store)
|
||||
batchTool.SetProgressCh(elfProgressCh)
|
||||
reg.Register(batchTool)
|
||||
|
||||
@@ -337,7 +348,8 @@ func main() {
|
||||
System: systemPrompt,
|
||||
Model: *model,
|
||||
MaxTurns: *maxTurns,
|
||||
Logger: logger,
|
||||
Store: store,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultMaxResultSize is the threshold for persisting tool results.
|
||||
DefaultMaxResultSize = 50_000 // chars
|
||||
// PreviewSize is the number of chars to show inline.
|
||||
PreviewSize = 2000
|
||||
// ToolResultsDir is the subdirectory for persisted results.
|
||||
ToolResultsDir = "tool-results"
|
||||
)
|
||||
|
||||
// PersistLargeResult checks if a tool result exceeds the size limit.
|
||||
// If so, writes it to disk and returns a preview + file path.
|
||||
// Otherwise returns the original content unchanged.
|
||||
func PersistLargeResult(content, toolUseID, sessionDir string) (string, bool) {
|
||||
if len(content) <= DefaultMaxResultSize {
|
||||
return content, false
|
||||
}
|
||||
|
||||
// Create directory
|
||||
dir := filepath.Join(sessionDir, ToolResultsDir)
|
||||
os.MkdirAll(dir, 0o755)
|
||||
|
||||
// Write full result to disk
|
||||
filename := toolUseID + ".txt"
|
||||
path := filepath.Join(dir, filename)
|
||||
os.WriteFile(path, []byte(content), 0o644)
|
||||
|
||||
// Build preview
|
||||
preview := content
|
||||
if len(preview) > PreviewSize {
|
||||
preview = preview[:PreviewSize]
|
||||
}
|
||||
|
||||
return fmt.Sprintf("<persisted-output>\nFull output saved to: %s\n\nPreview (first %d chars):\n%s\n</persisted-output>",
|
||||
path, PreviewSize, preview), true
|
||||
}
|
||||
|
||||
// TruncateToolResult truncates a tool result to a maximum size with an indicator.
|
||||
func TruncateToolResult(content string, maxSize int) string {
|
||||
if len(content) <= maxSize {
|
||||
return content
|
||||
}
|
||||
lines := strings.Split(content[:maxSize], "\n")
|
||||
return strings.Join(lines, "\n") + fmt.Sprintf("\n\n... (truncated, %d total chars)", len(content))
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"somegit.dev/Owlibou/gnoma/internal/router"
|
||||
"somegit.dev/Owlibou/gnoma/internal/security"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
|
||||
)
|
||||
|
||||
// elfMeta tracks routing metadata and pool reservations for quality feedback.
|
||||
@@ -30,6 +31,7 @@ type Manager struct {
|
||||
tools *tool.Registry
|
||||
permissions *permission.Checker
|
||||
firewall *security.Firewall
|
||||
store *persist.Store
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
@@ -38,6 +40,7 @@ type ManagerConfig struct {
|
||||
Tools *tool.Registry
|
||||
Permissions *permission.Checker // nil = allow all (unsafe; prefer passing parent checker)
|
||||
Firewall *security.Firewall // nil = no scanning
|
||||
Store *persist.Store // nil = no result persistence for elfs
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
@@ -53,6 +56,7 @@ func NewManager(cfg ManagerConfig) *Manager {
|
||||
tools: cfg.Tools,
|
||||
permissions: cfg.Permissions,
|
||||
firewall: cfg.Firewall,
|
||||
store: cfg.Store,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -96,6 +100,7 @@ func (m *Manager) Spawn(ctx context.Context, taskType router.TaskType, prompt, s
|
||||
System: systemPrompt,
|
||||
Model: arm.ModelName,
|
||||
MaxTurns: maxTurns,
|
||||
Store: m.store,
|
||||
Logger: m.logger,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -152,6 +157,7 @@ func (m *Manager) SpawnWithProvider(prov provider.Provider, model, prompt, syste
|
||||
System: systemPrompt,
|
||||
Model: model,
|
||||
MaxTurns: maxTurns,
|
||||
Store: m.store,
|
||||
Logger: m.logger,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"somegit.dev/Owlibou/gnoma/internal/router"
|
||||
"somegit.dev/Owlibou/gnoma/internal/security"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
|
||||
)
|
||||
|
||||
// Config holds engine configuration.
|
||||
@@ -25,6 +26,7 @@ type Config struct {
|
||||
System string // system prompt
|
||||
Model string // override model (empty = provider default)
|
||||
MaxTurns int // safety limit on tool loops (0 = unlimited)
|
||||
Store *persist.Store // nil = no result persistence
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"somegit.dev/Owlibou/gnoma/internal/router"
|
||||
"somegit.dev/Owlibou/gnoma/internal/stream"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
|
||||
)
|
||||
|
||||
// Submit sends a user message and runs the agentic loop to completion.
|
||||
@@ -394,10 +395,12 @@ func (e *Engine) executeSingleTool(ctx context.Context, call message.ToolCall, t
|
||||
output = e.cfg.Firewall.ScanToolResult(output)
|
||||
}
|
||||
|
||||
// Persist large results to disk
|
||||
if persisted, ok := gnomactx.PersistLargeResult(output, call.ID, ".gnoma/sessions"); ok {
|
||||
e.logger.Debug("tool result persisted to disk", "name", call.Name, "size", len(output))
|
||||
output = persisted
|
||||
// Persist results to /tmp for cross-tool session sharing
|
||||
if e.cfg.Store != nil {
|
||||
if path, ok := e.cfg.Store.Save(call.Name, call.ID, output); ok {
|
||||
e.logger.Debug("tool result persisted", "name", call.Name, "path", path)
|
||||
output = persist.InlineReplacement(path, output)
|
||||
}
|
||||
}
|
||||
|
||||
// Emit tool result event for the UI
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"somegit.dev/Owlibou/gnoma/internal/router"
|
||||
"somegit.dev/Owlibou/gnoma/internal/stream"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
|
||||
)
|
||||
|
||||
var paramSchema = json.RawMessage(`{
|
||||
@@ -37,10 +38,11 @@ var paramSchema = json.RawMessage(`{
|
||||
type Tool struct {
|
||||
manager *elf.Manager
|
||||
ProgressCh chan<- elf.Progress // optional: sends structured progress to TUI
|
||||
store *persist.Store
|
||||
}
|
||||
|
||||
func New(mgr *elf.Manager) *Tool {
|
||||
return &Tool{manager: mgr}
|
||||
func New(mgr *elf.Manager, store *persist.Store) *Tool {
|
||||
return &Tool{manager: mgr, store: store}
|
||||
}
|
||||
|
||||
// SetProgressCh sets the channel for forwarding elf progress to the TUI.
|
||||
@@ -80,6 +82,12 @@ func (t *Tool) Execute(ctx context.Context, args json.RawMessage) (tool.Result,
|
||||
|
||||
systemPrompt := "You are an elf — a focused sub-agent of gnoma. Complete the given task thoroughly and concisely. Use tools as needed."
|
||||
|
||||
var preSave []persist.ResultFile
|
||||
if t.store != nil {
|
||||
preSave, _ = t.store.List("")
|
||||
}
|
||||
_ = preSave // used in Task 4 for ResultFilePaths diff
|
||||
|
||||
e, err := t.manager.Spawn(ctx, taskType, a.Prompt, systemPrompt, maxTurns)
|
||||
if err != nil {
|
||||
return tool.Result{Output: fmt.Sprintf("Failed to spawn elf: %v", err)}, nil
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"somegit.dev/Owlibou/gnoma/internal/elf"
|
||||
"somegit.dev/Owlibou/gnoma/internal/stream"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool"
|
||||
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
|
||||
)
|
||||
|
||||
var batchSchema = json.RawMessage(`{
|
||||
@@ -49,10 +50,11 @@ var batchSchema = json.RawMessage(`{
|
||||
type BatchTool struct {
|
||||
manager *elf.Manager
|
||||
progressCh chan<- elf.Progress
|
||||
store *persist.Store
|
||||
}
|
||||
|
||||
func NewBatch(mgr *elf.Manager) *BatchTool {
|
||||
return &BatchTool{manager: mgr}
|
||||
func NewBatch(mgr *elf.Manager, store *persist.Store) *BatchTool {
|
||||
return &BatchTool{manager: mgr, store: store}
|
||||
}
|
||||
|
||||
func (t *BatchTool) SetProgressCh(ch chan<- elf.Progress) {
|
||||
@@ -91,6 +93,12 @@ func (t *BatchTool) Execute(ctx context.Context, args json.RawMessage) (tool.Res
|
||||
|
||||
systemPrompt := "You are an elf — a focused sub-agent of gnoma. Complete the given task thoroughly and concisely. Use tools as needed."
|
||||
|
||||
var preSave []persist.ResultFile
|
||||
if t.store != nil {
|
||||
preSave, _ = t.store.List("")
|
||||
}
|
||||
_ = preSave // used in Task 4
|
||||
|
||||
// Spawn all elfs with slight stagger to avoid rate limit bursts
|
||||
type elfEntry struct {
|
||||
elf elf.Elf
|
||||
|
||||
Reference in New Issue
Block a user