Merge pull request '[agent/codex:gpt-5.4-mini] Read ~/spec/code/core/ide/RFC.md §3.3 (Marketplace Tools). ...' (#10) from agent/read---spec-code-core-ide-rfc-md--3-2--w into dev
This commit is contained in:
commit
1720f1c76c
6 changed files with 663 additions and 3 deletions
467
brain_direct.go
Normal file
467
brain_direct.go
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
coreerr "dappco.re/go/core/log"
|
||||
coremcp "forge.lthn.ai/core/mcp/pkg/mcp"
|
||||
brainpkg "forge.lthn.ai/core/mcp/pkg/mcp/brain"
|
||||
_ "github.com/marcboeker/go-duckdb"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBrainAPIURL = "https://api.lthn.sh"
|
||||
defaultBrainCacheTTL = 5 * time.Minute
|
||||
defaultBrainCacheDriver = "duckdb"
|
||||
)
|
||||
|
||||
type (
|
||||
RememberInput = brainpkg.RememberInput
|
||||
RememberOutput = brainpkg.RememberOutput
|
||||
RecallInput = brainpkg.RecallInput
|
||||
RecallFilter = brainpkg.RecallFilter
|
||||
RecallOutput = brainpkg.RecallOutput
|
||||
Memory = brainpkg.Memory
|
||||
ForgetInput = brainpkg.ForgetInput
|
||||
ForgetOutput = brainpkg.ForgetOutput
|
||||
)
|
||||
|
||||
// BrainDirectSubsystem implements the OpenBrain MCP tools over the direct HTTP API.
|
||||
// brain_recall is cached locally in DuckDB to avoid repeated semantic searches.
|
||||
type BrainDirectSubsystem struct {
|
||||
workspaceRoot string
|
||||
apiURL string
|
||||
apiKey string
|
||||
client *http.Client
|
||||
|
||||
cache *brainRecallCache
|
||||
}
|
||||
|
||||
var _ coremcp.Subsystem = (*BrainDirectSubsystem)(nil)
|
||||
var _ coremcp.SubsystemWithShutdown = (*BrainDirectSubsystem)(nil)
|
||||
|
||||
// NewCachedBrainDirect creates the direct OpenBrain subsystem with a DuckDB-backed
|
||||
// cache for brain_recall responses.
|
||||
func NewCachedBrainDirect(workspaceRoot string) (*BrainDirectSubsystem, error) {
|
||||
apiURL := strings.TrimSpace(os.Getenv("CORE_BRAIN_URL"))
|
||||
if apiURL == "" {
|
||||
apiURL = defaultBrainAPIURL
|
||||
}
|
||||
|
||||
apiKey := strings.TrimSpace(os.Getenv("CORE_BRAIN_KEY"))
|
||||
if apiKey == "" {
|
||||
if home, err := os.UserHomeDir(); err == nil {
|
||||
keyPath := filepath.Join(home, ".claude", "brain.key")
|
||||
if data, readErr := os.ReadFile(keyPath); readErr == nil {
|
||||
apiKey = strings.TrimSpace(string(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cacheTTL := defaultBrainCacheTTL
|
||||
if raw := strings.TrimSpace(os.Getenv("CORE_BRAIN_RECALL_CACHE_TTL")); raw != "" {
|
||||
if parsed, err := time.ParseDuration(raw); err == nil && parsed > 0 {
|
||||
cacheTTL = parsed
|
||||
}
|
||||
}
|
||||
|
||||
cache, err := newBrainRecallCache(workspaceRoot, apiURL, apiKey, cacheTTL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BrainDirectSubsystem{
|
||||
workspaceRoot: workspaceRoot,
|
||||
apiURL: apiURL,
|
||||
apiKey: apiKey,
|
||||
client: &http.Client{Timeout: 30 * time.Second},
|
||||
cache: cache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Name implements mcp.Subsystem.
|
||||
func (s *BrainDirectSubsystem) Name() string { return "brain" }
|
||||
|
||||
// RegisterTools implements mcp.Subsystem.
|
||||
func (s *BrainDirectSubsystem) RegisterTools(server *mcp.Server) {
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "brain_remember",
|
||||
Description: "Store a memory in OpenBrain. Types: fact, decision, observation, plan, convention, architecture, research, documentation, service, bug, pattern, context, procedure.",
|
||||
}, s.brainRemember)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "brain_recall",
|
||||
Description: "Semantic search across OpenBrain memories. Returns memories ranked by similarity. Use agent_id 'cladius' for Cladius's memories.",
|
||||
}, s.brainRecall)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "brain_forget",
|
||||
Description: "Remove a memory from OpenBrain by ID.",
|
||||
}, s.brainForget)
|
||||
}
|
||||
|
||||
// Shutdown implements mcp.SubsystemWithShutdown.
|
||||
func (s *BrainDirectSubsystem) Shutdown(ctx context.Context) error {
|
||||
if s.cache != nil {
|
||||
return s.cache.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BrainDirectSubsystem) apiCall(ctx context.Context, method, path string, body any) (map[string]any, error) {
|
||||
if s.apiKey == "" {
|
||||
return nil, coreerr.E("brain.apiCall", "no API key (set CORE_BRAIN_KEY or create ~/.claude/brain.key)", nil)
|
||||
}
|
||||
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("brain.apiCall", "marshal request", err)
|
||||
}
|
||||
reqBody = bytes.NewReader(data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, s.apiURL+path, reqBody)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("brain.apiCall", "create request", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+s.apiKey)
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("brain.apiCall", "API call failed", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("brain.apiCall", "read response", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, coreerr.E("brain.apiCall", "API returned "+string(respData), nil)
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
if err := json.Unmarshal(respData, &result); err != nil {
|
||||
return nil, coreerr.E("brain.apiCall", "parse response", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *BrainDirectSubsystem) brainRemember(ctx context.Context, _ *mcp.CallToolRequest, input RememberInput) (*mcp.CallToolResult, RememberOutput, error) {
|
||||
result, err := s.apiCall(ctx, "POST", "/v1/brain/remember", map[string]any{
|
||||
"content": input.Content,
|
||||
"type": input.Type,
|
||||
"tags": input.Tags,
|
||||
"project": input.Project,
|
||||
"agent_id": "cladius",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, RememberOutput{}, err
|
||||
}
|
||||
|
||||
if s.cache != nil {
|
||||
_ = s.cache.clear(ctx)
|
||||
}
|
||||
|
||||
id, _ := result["id"].(string)
|
||||
return nil, RememberOutput{
|
||||
Success: true,
|
||||
MemoryID: id,
|
||||
Timestamp: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *BrainDirectSubsystem) brainRecall(ctx context.Context, _ *mcp.CallToolRequest, input RecallInput) (*mcp.CallToolResult, RecallOutput, error) {
|
||||
request := s.normalisedRecallRequest(input)
|
||||
cacheKey, err := s.cacheKey(request)
|
||||
if err != nil {
|
||||
return nil, RecallOutput{}, err
|
||||
}
|
||||
|
||||
if s.cache != nil {
|
||||
if cached, ok, err := s.cache.get(ctx, cacheKey); err != nil {
|
||||
return nil, RecallOutput{}, err
|
||||
} else if ok {
|
||||
return nil, cached, nil
|
||||
}
|
||||
}
|
||||
|
||||
body := map[string]any{
|
||||
"query": request.Query,
|
||||
"top_k": request.TopK,
|
||||
"agent_id": request.AgentID,
|
||||
}
|
||||
if request.Project != "" {
|
||||
body["project"] = request.Project
|
||||
}
|
||||
if request.Type != nil {
|
||||
body["type"] = request.Type
|
||||
}
|
||||
|
||||
result, err := s.apiCall(ctx, "POST", "/v1/brain/recall", body)
|
||||
if err != nil {
|
||||
return nil, RecallOutput{}, err
|
||||
}
|
||||
|
||||
output := brainRecallResult(result)
|
||||
if s.cache != nil {
|
||||
_ = s.cache.set(ctx, cacheKey, output)
|
||||
}
|
||||
|
||||
return nil, output, nil
|
||||
}
|
||||
|
||||
func (s *BrainDirectSubsystem) brainForget(ctx context.Context, _ *mcp.CallToolRequest, input ForgetInput) (*mcp.CallToolResult, ForgetOutput, error) {
|
||||
_, err := s.apiCall(ctx, "DELETE", "/v1/brain/forget/"+input.ID, nil)
|
||||
if err != nil {
|
||||
return nil, ForgetOutput{}, err
|
||||
}
|
||||
|
||||
if s.cache != nil {
|
||||
_ = s.cache.clear(ctx)
|
||||
}
|
||||
|
||||
return nil, ForgetOutput{
|
||||
Success: true,
|
||||
Forgotten: input.ID,
|
||||
Timestamp: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type brainRecallRequest struct {
|
||||
WorkspaceRoot string `json:"workspace_root"`
|
||||
APIURL string `json:"api_url"`
|
||||
APIKeyHash string `json:"api_key_hash"`
|
||||
Query string `json:"query"`
|
||||
TopK int `json:"top_k"`
|
||||
AgentID string `json:"agent_id"`
|
||||
Project string `json:"project,omitempty"`
|
||||
Type any `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
func (s *BrainDirectSubsystem) normalisedRecallRequest(input RecallInput) brainRecallRequest {
|
||||
topK := input.TopK
|
||||
if topK <= 0 {
|
||||
topK = 10
|
||||
}
|
||||
|
||||
return brainRecallRequest{
|
||||
WorkspaceRoot: strings.TrimSpace(s.workspaceRoot),
|
||||
APIURL: strings.TrimSpace(s.apiURL),
|
||||
APIKeyHash: hashString(s.apiKey),
|
||||
Query: strings.TrimSpace(input.Query),
|
||||
TopK: topK,
|
||||
AgentID: "cladius",
|
||||
Project: strings.TrimSpace(input.Filter.Project),
|
||||
Type: input.Filter.Type,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BrainDirectSubsystem) cacheKey(request brainRecallRequest) (string, error) {
|
||||
data, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return "", coreerr.E("brain.cacheKey", "marshal recall request", err)
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(data)
|
||||
return hex.EncodeToString(sum[:]), nil
|
||||
}
|
||||
|
||||
func brainRecallResult(result map[string]any) RecallOutput {
|
||||
var memories []Memory
|
||||
if mems, ok := result["memories"].([]any); ok {
|
||||
for _, m := range mems {
|
||||
mm, ok := m.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
mem := Memory{
|
||||
Content: fmt.Sprintf("%v", mm["content"]),
|
||||
Type: fmt.Sprintf("%v", mm["type"]),
|
||||
Project: fmt.Sprintf("%v", mm["project"]),
|
||||
AgentID: fmt.Sprintf("%v", mm["agent_id"]),
|
||||
CreatedAt: fmt.Sprintf("%v", mm["created_at"]),
|
||||
}
|
||||
if id, ok := mm["id"].(string); ok {
|
||||
mem.ID = id
|
||||
}
|
||||
if score, ok := mm["score"].(float64); ok {
|
||||
mem.Confidence = score
|
||||
}
|
||||
if source, ok := mm["source"].(string); ok {
|
||||
mem.Tags = append(mem.Tags, "source:"+source)
|
||||
}
|
||||
memories = append(memories, mem)
|
||||
}
|
||||
}
|
||||
|
||||
return RecallOutput{
|
||||
Success: true,
|
||||
Count: len(memories),
|
||||
Memories: memories,
|
||||
}
|
||||
}
|
||||
|
||||
func hashString(value string) string {
|
||||
sum := sha256.Sum256([]byte(value))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
type brainRecallCache struct {
|
||||
db *sql.DB
|
||||
ttl time.Duration
|
||||
path string
|
||||
}
|
||||
|
||||
func newBrainRecallCache(workspaceRoot, apiURL, apiKey string, ttl time.Duration) (*brainRecallCache, error) {
|
||||
cachePath, err := brainRecallCachePath(workspaceRoot, apiURL, apiKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := sql.Open(defaultBrainCacheDriver, cachePath)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("brain.cache.open", "open duckdb cache", err)
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
db.SetMaxIdleConns(1)
|
||||
|
||||
schema := `
|
||||
CREATE TABLE IF NOT EXISTS brain_recall_cache (
|
||||
cache_key TEXT PRIMARY KEY,
|
||||
payload TEXT NOT NULL,
|
||||
created_at_unix_ms BIGINT NOT NULL,
|
||||
expires_at_unix_ms BIGINT NOT NULL
|
||||
);
|
||||
`
|
||||
if _, err := db.Exec(schema); err != nil {
|
||||
_ = db.Close()
|
||||
return nil, coreerr.E("brain.cache.schema", "initialise duckdb cache", err)
|
||||
}
|
||||
|
||||
return &brainRecallCache{
|
||||
db: db,
|
||||
ttl: ttl,
|
||||
path: cachePath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func brainRecallCachePath(workspaceRoot, apiURL, apiKey string) (string, error) {
|
||||
baseDir, err := os.UserCacheDir()
|
||||
if err != nil || baseDir == "" {
|
||||
baseDir = os.TempDir()
|
||||
}
|
||||
|
||||
identity := strings.Join([]string{
|
||||
strings.TrimSpace(workspaceRoot),
|
||||
strings.TrimSpace(apiURL),
|
||||
hashString(apiKey),
|
||||
}, "\x00")
|
||||
|
||||
sum := sha256.Sum256([]byte(identity))
|
||||
fileName := "brain-recall-" + hex.EncodeToString(sum[:]) + ".duckdb"
|
||||
cacheDir := filepath.Join(baseDir, "core-ide", "brain")
|
||||
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
|
||||
return "", coreerr.E("brain.cache.path", "create cache directory", err)
|
||||
}
|
||||
|
||||
return filepath.Join(cacheDir, fileName), nil
|
||||
}
|
||||
|
||||
func (c *brainRecallCache) Close() error {
|
||||
if c == nil || c.db == nil {
|
||||
return nil
|
||||
}
|
||||
return c.db.Close()
|
||||
}
|
||||
|
||||
func (c *brainRecallCache) get(ctx context.Context, key string) (RecallOutput, bool, error) {
|
||||
if c == nil || c.db == nil {
|
||||
return RecallOutput{}, false, nil
|
||||
}
|
||||
|
||||
var payload string
|
||||
var expires int64
|
||||
err := c.db.QueryRowContext(ctx, `SELECT payload, expires_at_unix_ms FROM brain_recall_cache WHERE cache_key = ?`, key).Scan(&payload, &expires)
|
||||
if err == sql.ErrNoRows {
|
||||
return RecallOutput{}, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return RecallOutput{}, false, coreerr.E("brain.cache.get", "read recall cache", err)
|
||||
}
|
||||
|
||||
if time.Now().UTC().UnixMilli() >= expires {
|
||||
_, _ = c.db.ExecContext(ctx, `DELETE FROM brain_recall_cache WHERE cache_key = ?`, key)
|
||||
return RecallOutput{}, false, nil
|
||||
}
|
||||
|
||||
var out RecallOutput
|
||||
if err := json.Unmarshal([]byte(payload), &out); err != nil {
|
||||
return RecallOutput{}, false, coreerr.E("brain.cache.get", "decode cached recall output", err)
|
||||
}
|
||||
|
||||
return out, true, nil
|
||||
}
|
||||
|
||||
func (c *brainRecallCache) set(ctx context.Context, key string, value RecallOutput) error {
|
||||
if c == nil || c.db == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return coreerr.E("brain.cache.set", "encode recall output", err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC().UnixMilli()
|
||||
expires := now + int64(c.ttl/time.Millisecond)
|
||||
if expires <= now {
|
||||
expires = now + 1
|
||||
}
|
||||
|
||||
_, err = c.db.ExecContext(ctx, `
|
||||
INSERT INTO brain_recall_cache (cache_key, payload, created_at_unix_ms, expires_at_unix_ms)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(cache_key) DO UPDATE SET
|
||||
payload = excluded.payload,
|
||||
created_at_unix_ms = excluded.created_at_unix_ms,
|
||||
expires_at_unix_ms = excluded.expires_at_unix_ms
|
||||
`, key, string(payload), now, expires)
|
||||
if err != nil {
|
||||
return coreerr.E("brain.cache.set", "store recall cache", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *brainRecallCache) clear(ctx context.Context) error {
|
||||
if c == nil || c.db == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := c.db.ExecContext(ctx, `DELETE FROM brain_recall_cache`); err != nil {
|
||||
return coreerr.E("brain.cache.clear", "clear recall cache", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
148
brain_direct_test.go
Normal file
148
brain_direct_test.go
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBrainRecallCache_Good_StoresAndExpires(t *testing.T) {
|
||||
cache, err := newBrainRecallCache(t.TempDir(), "https://example.com", "secret", 25*time.Millisecond)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, cache.Close())
|
||||
}()
|
||||
|
||||
want := RecallOutput{
|
||||
Success: true,
|
||||
Count: 1,
|
||||
Memories: []Memory{
|
||||
{ID: "m-1", Content: "remember this", CreatedAt: "2026-03-31T00:00:00Z"},
|
||||
},
|
||||
}
|
||||
|
||||
require.NoError(t, cache.set(context.Background(), "cache-key", want))
|
||||
|
||||
got, ok, err := cache.get(context.Background(), "cache-key")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, want, got)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
got, ok, err = cache.get(context.Background(), "cache-key")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, got)
|
||||
}
|
||||
|
||||
func TestBrainDirectSubsystem_RecallCachesResults(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls.Add(1)
|
||||
assert.Equal(t, http.MethodPost, r.Method)
|
||||
assert.Equal(t, "/v1/brain/recall", r.URL.Path)
|
||||
|
||||
var body map[string]any
|
||||
require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
|
||||
assert.Equal(t, "alpha", body["query"])
|
||||
assert.Equal(t, float64(5), body["top_k"])
|
||||
assert.Equal(t, "cladius", body["agent_id"])
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"memories": []map[string]any{
|
||||
{
|
||||
"id": "m-1",
|
||||
"content": "alpha memory",
|
||||
"type": "decision",
|
||||
"project": "ide",
|
||||
"agent_id": "cladius",
|
||||
"created_at": "2026-03-31T00:00:00Z",
|
||||
"score": 0.91,
|
||||
"source": "laravel",
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
cache, err := newBrainRecallCache(t.TempDir(), upstream.URL, "secret", time.Hour)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, cache.Close())
|
||||
}()
|
||||
|
||||
sub := &BrainDirectSubsystem{
|
||||
workspaceRoot: "/workspace",
|
||||
apiURL: upstream.URL,
|
||||
apiKey: "secret",
|
||||
client: upstream.Client(),
|
||||
cache: cache,
|
||||
}
|
||||
|
||||
_, first, err := sub.brainRecall(context.Background(), nil, RecallInput{Query: "alpha", TopK: 5})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, first.Success)
|
||||
require.Equal(t, int32(1), calls.Load())
|
||||
|
||||
_, second, err := sub.brainRecall(context.Background(), nil, RecallInput{Query: "alpha", TopK: 5})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, second.Success)
|
||||
require.Equal(t, int32(1), calls.Load())
|
||||
}
|
||||
|
||||
func TestBrainDirectSubsystem_ForgetClearsCache(t *testing.T) {
|
||||
var recallCalls atomic.Int32
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == http.MethodPost && r.URL.Path == "/v1/brain/recall":
|
||||
recallCalls.Add(1)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"memories": []map[string]any{
|
||||
{"id": "m-1", "content": "alpha", "type": "decision", "created_at": "2026-03-31T00:00:00Z"},
|
||||
},
|
||||
})
|
||||
case r.Method == http.MethodDelete && r.URL.Path == "/v1/brain/forget/m-1":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"deleted": true})
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
cache, err := newBrainRecallCache(t.TempDir(), upstream.URL, "secret", time.Hour)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, cache.Close())
|
||||
}()
|
||||
|
||||
sub := &BrainDirectSubsystem{
|
||||
workspaceRoot: "/workspace",
|
||||
apiURL: upstream.URL,
|
||||
apiKey: "secret",
|
||||
client: upstream.Client(),
|
||||
cache: cache,
|
||||
}
|
||||
|
||||
_, _, err = sub.brainRecall(context.Background(), nil, RecallInput{Query: "alpha", TopK: 5})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int32(1), recallCalls.Load())
|
||||
|
||||
_, _, err = sub.brainForget(context.Background(), nil, ForgetInput{ID: "m-1"})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = sub.brainRecall(context.Background(), nil, RecallInput{Query: "alpha", TopK: 5})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int32(2), recallCalls.Load())
|
||||
}
|
||||
11
go.mod
11
go.mod
|
|
@ -11,6 +11,7 @@ require (
|
|||
forge.lthn.ai/core/go-ws v0.2.3
|
||||
forge.lthn.ai/core/gui v0.1.3
|
||||
forge.lthn.ai/core/mcp v0.3.2
|
||||
github.com/marcboeker/go-duckdb v1.8.5
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.74
|
||||
)
|
||||
|
|
@ -21,6 +22,7 @@ require (
|
|||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.0 // indirect
|
||||
github.com/adrg/xdg v0.5.3 // indirect
|
||||
github.com/apache/arrow-go/v18 v18.5.2 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/coder/websocket v1.8.14 // indirect
|
||||
|
|
@ -35,14 +37,17 @@ require (
|
|||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/flatbuffers v25.12.19+incompatible // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/lmittmann/tint v1.1.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
|
|
@ -53,6 +58,10 @@ require (
|
|||
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20260312161427-1546bf4b83fe // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
|
|
@ -130,7 +139,7 @@ require (
|
|||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.2 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.1 // indirect
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.1
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ollama/ollama v0.18.1 // indirect
|
||||
|
|
|
|||
26
go.sum
26
go.sum
|
|
@ -53,6 +53,10 @@ github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kk
|
|||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/apache/arrow-go/v18 v18.5.2 h1:3uoHjoaEie5eVsxx/Bt64hKwZx4STb+beAkqKOlq/lY=
|
||||
github.com/apache/arrow-go/v18 v18.5.2/go.mod h1:yNoizNTT4peTciJ7V01d2EgOkE1d0fQ1vZcFOsVtFsw=
|
||||
github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
|
||||
github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
|
|
@ -215,6 +219,10 @@ github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
|||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
|
@ -244,6 +252,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
||||
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
|
||||
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
|
|
@ -263,6 +275,8 @@ github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I=
|
|||
github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M=
|
||||
github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/marcboeker/go-duckdb v1.8.5 h1:tkYp+TANippy0DaIOP5OEfBEwbUINqiFqgwMQ44jME0=
|
||||
github.com/marcboeker/go-duckdb v1.8.5/go.mod h1:6mK7+WQE4P4u5AFLvVBmhFxY5fvhymFptghgJX6B+/8=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
|
|
@ -270,6 +284,10 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
|||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
|
||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
|
||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
|
||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.1 h1:M4x9GyIPj+HoIlHNGpK2hq5o3BFhC+78PkEaldQRphc=
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.1/go.mod h1:Bo/mS87hPQqHSRkMv4dQq1XCu6zv4INdXnFZabkNU6s=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
|
@ -285,6 +303,8 @@ github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
|||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
|
||||
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
|
|
@ -372,6 +392,8 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i
|
|||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||
|
|
@ -443,6 +465,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20260312161427-1546bf4b83fe h1:MaXjBsxue6l0hflXDwJ/XBfUJRjiyX1PwLd7F3lYDXA=
|
||||
golang.org/x/telemetry v0.0.0-20260312161427-1546bf4b83fe/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
|
|
@ -462,6 +486,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
|
||||
|
|
|
|||
7
main.go
7
main.go
|
|
@ -63,6 +63,11 @@ func main() {
|
|||
}
|
||||
bridge := ide.NewBridge(hub, bridgeCfg)
|
||||
|
||||
brainDirect, err := NewCachedBrainDirect(cwd)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialise brain cache: %v", err)
|
||||
}
|
||||
|
||||
// ── Service Provider Registry ──────────────────────────────
|
||||
reg := provider.NewRegistry()
|
||||
reg.Add(processapi.NewProvider(process.DefaultRegistry(), hub))
|
||||
|
|
@ -100,7 +105,7 @@ func main() {
|
|||
return mcp.New(
|
||||
mcp.WithWorkspaceRoot(cwd),
|
||||
mcp.WithWSHub(hub),
|
||||
mcp.WithSubsystem(brain.NewDirect()),
|
||||
mcp.WithSubsystem(brainDirect),
|
||||
mcp.WithSubsystem(agentic.NewPrep()),
|
||||
mcp.WithSubsystem(guiMCP.New(c)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@ func main() {
|
|||
}
|
||||
bridge := ide.NewBridge(hub, bridgeCfg)
|
||||
|
||||
brainDirect, err := NewCachedBrainDirect(cwd)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialise brain cache: %v", err)
|
||||
}
|
||||
|
||||
reg := provider.NewRegistry()
|
||||
reg.Add(processapi.NewProvider(process.DefaultRegistry(), hub))
|
||||
reg.Add(brain.NewProvider(bridge, hub))
|
||||
|
|
@ -79,7 +84,7 @@ func main() {
|
|||
return mcp.New(
|
||||
mcp.WithWorkspaceRoot(cwd),
|
||||
mcp.WithWSHub(hub),
|
||||
mcp.WithSubsystem(brain.NewDirect()),
|
||||
mcp.WithSubsystem(brainDirect),
|
||||
mcp.WithSubsystem(agentic.NewPrep()),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue