fix(brain): resolve direct AX findings

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-22 13:02:37 +00:00
parent a422eb1b6b
commit ed842122a2
4 changed files with 101 additions and 9 deletions

View file

@ -7,7 +7,6 @@ import (
"context"
"encoding/json"
"net/http"
"os"
"time"
"dappco.re/go/agent/pkg/agentic"
@ -33,16 +32,15 @@ var _ coremcp.Subsystem = (*DirectSubsystem)(nil)
// sub := brain.NewDirect()
// sub.RegisterTools(server)
func NewDirect() *DirectSubsystem {
apiURL := os.Getenv("CORE_BRAIN_URL")
apiURL := core.Env("CORE_BRAIN_URL")
if apiURL == "" {
apiURL = "https://api.lthn.sh"
}
apiKey := os.Getenv("CORE_BRAIN_KEY")
apiKey := core.Env("CORE_BRAIN_KEY")
keyPath := ""
if apiKey == "" {
home, _ := os.UserHomeDir()
keyPath = brainKeyPath(home)
keyPath = brainKeyPath(brainHomeDir())
if keyPath != "" {
if r := fs.Read(keyPath); r.OK {
apiKey = core.Trim(r.Value.(string))
@ -104,6 +102,13 @@ func brainKeyPath(home string) string {
return core.JoinPath(core.TrimSuffix(home, "/"), ".claude", "brain.key")
}
func brainHomeDir() string {
if home := core.Env("CORE_HOME"); home != "" {
return home
}
return core.Env("DIR_HOME")
}
func (s *DirectSubsystem) apiCall(ctx context.Context, method, path string, body any) (map[string]any, error) {
if s.apiKey == "" {
return nil, core.E("brain.apiCall", "no API key (set CORE_BRAIN_KEY or create ~/.claude/brain.key)", nil)

View file

@ -5,12 +5,12 @@ package brain
import (
"context"
"encoding/json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// newTestDirect returns a DirectSubsystem wired to the given test server.
@ -59,7 +59,7 @@ func TestNewDirect_Good_KeyFromFile(t *testing.T) {
t.Setenv("CORE_BRAIN_KEY", "")
tmpHome := t.TempDir()
t.Setenv("HOME", tmpHome)
t.Setenv("CORE_HOME", tmpHome)
keyDir := filepath.Join(tmpHome, ".claude")
require.True(t, fs.EnsureDir(keyDir).OK)
require.True(t, fs.Write(filepath.Join(keyDir, "brain.key"), " file-key-456 \n").OK)

View file

@ -4,6 +4,7 @@ package brain
import (
"net/http"
"strconv"
"dappco.re/go/core/api"
"dappco.re/go/core/api/pkg/provider"
@ -14,6 +15,11 @@ import (
// BrainProvider wraps the brain Subsystem as a service provider with REST
// endpoints. It delegates to the same IDE bridge that the MCP tools use.
//
// Usage example:
//
// provider := brain.NewProvider(bridge, hub)
// provider.RegisterRoutes(router.Group("/api/brain"))
type BrainProvider struct {
bridge *ide.Bridge
hub *ws.Hub
@ -294,13 +300,23 @@ func (p *BrainProvider) list(c *gin.Context) {
return
}
limit := 0
if rawLimit := c.Query("limit"); rawLimit != "" {
parsedLimit, err := strconv.Atoi(rawLimit)
if err != nil {
c.JSON(http.StatusBadRequest, api.Fail("invalid_limit", "limit must be an integer"))
return
}
limit = parsedLimit
}
err := p.bridge.Send(ide.BridgeMessage{
Type: "brain_list",
Data: map[string]any{
"project": c.Query("project"),
"type": c.Query("type"),
"agent_id": c.Query("agent_id"),
"limit": c.Query("limit"),
"limit": limit,
},
})
if err != nil {

View file

@ -14,6 +14,13 @@ import (
// -- Input/Output types -------------------------------------------------------
// RememberInput is the input for brain_remember.
//
// Usage example:
//
// input := brain.RememberInput{
// Content: "Use core.Env for system paths.",
// Type: "convention",
// }
type RememberInput struct {
Content string `json:"content"`
Type string `json:"type"`
@ -25,6 +32,13 @@ type RememberInput struct {
}
// RememberOutput is the output for brain_remember.
//
// Usage example:
//
// output := brain.RememberOutput{
// Success: true,
// MemoryID: "mem_123",
// }
type RememberOutput struct {
Success bool `json:"success"`
MemoryID string `json:"memoryId,omitempty"`
@ -32,6 +46,13 @@ type RememberOutput struct {
}
// RecallInput is the input for brain_recall.
//
// Usage example:
//
// input := brain.RecallInput{
// Query: "core.Env conventions",
// TopK: 5,
// }
type RecallInput struct {
Query string `json:"query"`
TopK int `json:"top_k,omitempty"`
@ -39,6 +60,13 @@ type RecallInput struct {
}
// RecallFilter holds optional filter criteria for brain_recall.
//
// Usage example:
//
// filter := brain.RecallFilter{
// Project: "agent",
// Type: "convention",
// }
type RecallFilter struct {
Project string `json:"project,omitempty"`
Type any `json:"type,omitempty"`
@ -47,6 +75,13 @@ type RecallFilter struct {
}
// RecallOutput is the output for brain_recall.
//
// Usage example:
//
// output := brain.RecallOutput{
// Success: true,
// Count: 1,
// }
type RecallOutput struct {
Success bool `json:"success"`
Count int `json:"count"`
@ -54,6 +89,14 @@ type RecallOutput struct {
}
// Memory is a single memory entry returned by recall or list.
//
// Usage example:
//
// memory := brain.Memory{
// ID: "mem_123",
// Type: "convention",
// Content: "Use core.Env for system paths.",
// }
type Memory struct {
ID string `json:"id"`
AgentID string `json:"agent_id"`
@ -69,12 +112,26 @@ type Memory struct {
}
// ForgetInput is the input for brain_forget.
//
// Usage example:
//
// input := brain.ForgetInput{
// ID: "mem_123",
// Reason: "superseded",
// }
type ForgetInput struct {
ID string `json:"id"`
Reason string `json:"reason,omitempty"`
}
// ForgetOutput is the output for brain_forget.
//
// Usage example:
//
// output := brain.ForgetOutput{
// Success: true,
// Forgotten: "mem_123",
// }
type ForgetOutput struct {
Success bool `json:"success"`
Forgotten string `json:"forgotten"`
@ -82,6 +139,13 @@ type ForgetOutput struct {
}
// ListInput is the input for brain_list.
//
// Usage example:
//
// input := brain.ListInput{
// Project: "agent",
// Limit: 20,
// }
type ListInput struct {
Project string `json:"project,omitempty"`
Type string `json:"type,omitempty"`
@ -90,6 +154,13 @@ type ListInput struct {
}
// ListOutput is the output for brain_list.
//
// Usage example:
//
// output := brain.ListOutput{
// Success: true,
// Count: 2,
// }
type ListOutput struct {
Success bool `json:"success"`
Count int `json:"count"`