diff --git a/agent/assistant/chat.go b/agent/assistant/chat.go index df748a18..381472fe 100644 --- a/agent/assistant/chat.go +++ b/agent/assistant/chat.go @@ -252,14 +252,14 @@ func (ast *Assistant) BufferUserInput(ctx *agentcontext.Context, inputMessages [ } } -// UpdateSpaceSnapshot updates the space snapshot in the buffer -// Should be called when space data changes +// UpdateSpaceSnapshot updates the context memory snapshot in the buffer +// Only captures Context-level memory (request-scoped temporary data) for recovery func (ast *Assistant) UpdateSpaceSnapshot(ctx *agentcontext.Context) { - if ctx.Buffer == nil || ctx.Space == nil { + if ctx.Buffer == nil || ctx.Memory == nil || ctx.Memory.Context == nil { return } - snapshot := ctx.Space.Snapshot() + snapshot := ctx.Memory.Context.Snapshot() ctx.Buffer.SetSpaceSnapshot(snapshot) } diff --git a/agent/assistant/chat_test.go b/agent/assistant/chat_test.go index 3423636e..877d222b 100644 --- a/agent/assistant/chat_test.go +++ b/agent/assistant/chat_test.go @@ -10,7 +10,6 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/yaoapp/gou/plan" "github.com/yaoapp/yao/agent/assistant" agentcontext "github.com/yaoapp/yao/agent/context" storetypes "github.com/yaoapp/yao/agent/store/types" @@ -431,15 +430,16 @@ func TestBufferStepTracking(t *testing.T) { t.Run("BeginAndCompleteStep", func(t *testing.T) { ctx := agentcontext.New(context.Background(), nil, "test_chat_step_001") - ctx.Space = plan.NewMemorySharedSpace() // Enter stack and init buffer _, _, done := agentcontext.EnterStack(ctx, ast.ID, nil) defer done() ast.InitBuffer(ctx) - // Set some space data - ctx.Space.Set("test_key", "test_value") + // Set some context memory data + if ctx.Memory != nil && ctx.Memory.Context != nil { + ctx.Memory.Context.Set("test_key", "test_value", 0) + } // Begin a step step := ast.BeginStep(ctx, agentcontext.StepTypeLLM, map[string]interface{}{ @@ -464,34 +464,34 @@ func TestBufferStepTracking(t *testing.T) { t.Logf("✓ Step tracking works correctly") }) - t.Run("SpaceSnapshotCapture", func(t *testing.T) { - ctx := agentcontext.New(context.Background(), nil, "test_chat_space_001") - ctx.Space = plan.NewMemorySharedSpace() + t.Run("ContextMemorySnapshotCapture", func(t *testing.T) { + ctx := agentcontext.New(context.Background(), nil, "test_chat_memory_001") // Enter stack and init buffer _, _, done := agentcontext.EnterStack(ctx, ast.ID, nil) defer done() ast.InitBuffer(ctx) - // Set space data before step - ctx.Space.Set("key1", "value1") - ctx.Space.Set("key2", 123) + // Set context memory data before step + require.NotNil(t, ctx.Memory) + require.NotNil(t, ctx.Memory.Context) + ctx.Memory.Context.Set("key1", "value1", 0) + ctx.Memory.Context.Set("key2", 123, 0) - // Begin step (should capture space snapshot) + // Begin step (should capture context memory snapshot) ast.BeginStep(ctx, agentcontext.StepTypeHookCreate, nil) - // Verify space snapshot was captured + // Verify context memory snapshot was captured steps := ctx.Buffer.GetAllSteps() require.Len(t, steps, 1) assert.NotNil(t, steps[0].SpaceSnapshot) assert.Equal(t, "value1", steps[0].SpaceSnapshot["key1"]) assert.Equal(t, 123, steps[0].SpaceSnapshot["key2"]) - t.Logf("✓ Space snapshot captured: %v", steps[0].SpaceSnapshot) + t.Logf("✓ Context memory snapshot captured: %v", steps[0].SpaceSnapshot) }) t.Run("MultipleSteps", func(t *testing.T) { ctx := agentcontext.New(context.Background(), nil, "test_chat_multi_step") - ctx.Space = plan.NewMemorySharedSpace() // Enter stack and init buffer _, _, done := agentcontext.EnterStack(ctx, ast.ID, nil) @@ -536,7 +536,6 @@ func TestFlushBuffer(t *testing.T) { t.Run("FlushOnSuccess", func(t *testing.T) { chatID := fmt.Sprintf("test_flush_success_%s", uuid.New().String()[:8]) ctx := agentcontext.New(context.Background(), nil, chatID) - ctx.Space = plan.NewMemorySharedSpace() // Enter stack and init buffer _, _, done := agentcontext.EnterStack(ctx, ast.ID, nil) @@ -584,7 +583,6 @@ func TestFlushBuffer(t *testing.T) { t.Run("FlushOnFailure", func(t *testing.T) { chatID := fmt.Sprintf("test_flush_fail_%s", uuid.New().String()[:8]) ctx := agentcontext.New(context.Background(), nil, chatID) - ctx.Space = plan.NewMemorySharedSpace() // Enter stack and init buffer _, _, done := agentcontext.EnterStack(ctx, ast.ID, nil) @@ -633,7 +631,6 @@ func TestFlushBuffer(t *testing.T) { t.Run("FlushOnInterrupt", func(t *testing.T) { chatID := fmt.Sprintf("test_flush_interrupt_%s", uuid.New().String()[:8]) ctx := agentcontext.New(context.Background(), nil, chatID) - ctx.Space = plan.NewMemorySharedSpace() // Enter stack and init buffer _, _, done := agentcontext.EnterStack(ctx, ast.ID, nil) @@ -673,7 +670,6 @@ func TestFlushBuffer(t *testing.T) { t.Run("FlushWithModeAndConnector", func(t *testing.T) { chatID := fmt.Sprintf("test_flush_mode_%s", uuid.New().String()[:8]) ctx := agentcontext.New(context.Background(), nil, chatID) - ctx.Space = plan.NewMemorySharedSpace() // Enter stack with connector and mode options opts := &agentcontext.Options{ diff --git a/agent/assistant/search_auth_integration_test.go b/agent/assistant/search_auth_integration_test.go index aaa89662..9eb4760f 100644 --- a/agent/assistant/search_auth_integration_test.go +++ b/agent/assistant/search_auth_integration_test.go @@ -262,7 +262,7 @@ func TestDBAuthWheresFilter(t *testing.T) { }) t.Run("NilAuthorizedReturnsNil", func(t *testing.T) { - ctx := &agentContext.Context{Authorized: nil} + ctx := agentContext.New(context.Background(), nil, "test-chat") wheres := assistant.BuildDBAuthWheres(ctx) assert.Nil(t, wheres, "Nil Authorized should return nil") @@ -419,16 +419,15 @@ func TestKBSearchIntegration(t *testing.T) { // ========== Helper Functions ========== func createAuthContext(userID, teamID string, teamOnly, ownerOnly bool) *agentContext.Context { - return &agentContext.Context{ - Authorized: &oauthtypes.AuthorizedInfo{ - UserID: userID, - TeamID: teamID, - Constraints: oauthtypes.DataConstraints{ - TeamOnly: teamOnly, - OwnerOnly: ownerOnly, - }, + authorized := &oauthtypes.AuthorizedInfo{ + UserID: userID, + TeamID: teamID, + Constraints: oauthtypes.DataConstraints{ + TeamOnly: teamOnly, + OwnerOnly: ownerOnly, }, } + return agentContext.New(context.Background(), authorized, "test-chat") } func createAuthCollection(ctx context.Context, t *testing.T, id, userID, teamID string, public bool, share string) { diff --git a/agent/content/content_vision_test.go b/agent/content/content_vision_test.go index 36060d6d..02f4fb05 100644 --- a/agent/content/content_vision_test.go +++ b/agent/content/content_vision_test.go @@ -457,8 +457,8 @@ func TestVision_CachedContent(t *testing.T) { t.Logf("✓ Content successfully cached and reused: %d characters", len(cachedText)) } -// TestVision_FileMetadataInSpace tests that file metadata is correctly passed to vision agent via ctx.Space -func TestVision_FileMetadataInSpace(t *testing.T) { +// TestVision_FileMetadataInMemory tests that file metadata is correctly passed to vision agent via ctx.Memory.Context +func TestVision_FileMetadataInMemory(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) diff --git a/agent/content/image_test.go b/agent/content/image_test.go index 66d2d502..cc0c18d4 100644 --- a/agent/content/image_test.go +++ b/agent/content/image_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/yaoapp/gou/connector/openai" - "github.com/yaoapp/gou/plan" agentContext "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/openapi/oauth/types" @@ -27,31 +26,29 @@ func TestMain(m *testing.M) { // newTestContext creates a Context for testing with commonly used fields pre-populated func newTestContext(capabilities *openai.Capabilities) *agentContext.Context { - return &agentContext.Context{ - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - ChatID: "test-chat", - AssistantID: "test-assistant", - Locale: "en-us", - Theme: "light", - Client: agentContext.Client{ - Type: "web", - UserAgent: "TestAgent/1.0", - IP: "127.0.0.1", - }, - Referer: agentContext.RefererAPI, - Accept: agentContext.AcceptWebCUI, - Route: "", - Metadata: make(map[string]interface{}), - Capabilities: capabilities, - Authorized: &types.AuthorizedInfo{ - Subject: "test-user", - ClientID: "test-client-id", - UserID: "test-user-123", - TeamID: "test-team-456", - TenantID: "test-tenant-789", - }, + authorized := &types.AuthorizedInfo{ + Subject: "test-user", + ClientID: "test-client-id", + UserID: "test-user-123", + TeamID: "test-team-456", + TenantID: "test-tenant-789", } + + ctx := agentContext.New(stdContext.Background(), authorized, "test-chat") + ctx.AssistantID = "test-assistant" + ctx.Locale = "en-us" + ctx.Theme = "light" + ctx.Client = agentContext.Client{ + Type: "web", + UserAgent: "TestAgent/1.0", + IP: "127.0.0.1", + } + ctx.Referer = agentContext.RefererAPI + ctx.Accept = agentContext.AcceptWebCUI + ctx.Route = "" + ctx.Metadata = make(map[string]interface{}) + ctx.Capabilities = capabilities + return ctx } func TestImageHandler_CanHandle(t *testing.T) { diff --git a/agent/content/tools.go b/agent/content/tools.go index b4fc6468..d0de3806 100644 --- a/agent/content/tools.go +++ b/agent/content/tools.go @@ -49,15 +49,15 @@ func CallAgent(ctx *agentContext.Context, agentID string, message agentContext.M } // CallAgentWithFileInfo calls an agent to process content with file metadata -// The file metadata is passed via ctx.Space for access by hooks (especially Next hook) -// Uses Space instead of Metadata to avoid creating context copies and ensure proper cleanup +// The file metadata is passed via ctx.Memory.Context for access by hooks (especially Next hook) +// Uses Memory.Context (request-scoped) to avoid creating context copies and ensure proper cleanup // -// Space Keys (with agent ID as namespace prefix to avoid conflicts between different agents): +// Memory Keys (with agent ID as namespace prefix to avoid conflicts between different agents): // - {agentID}:files_info - List of all files being processed by this agent (array) // - {agentID}:current_file - Currently processing file (single object) func CallAgentWithFileInfo(ctx *agentContext.Context, agentID string, message agentContext.Message, info *Info) (string, error) { - // Store file information in Space if available - if info != nil && ctx.Space != nil { + // Store file information in Memory.Context if available + if info != nil && ctx.Memory != nil && ctx.Memory.Context != nil { fileInfo := map[string]interface{}{ "url": info.URL, "filename": info.Filename, @@ -74,14 +74,14 @@ func CallAgentWithFileInfo(ctx *agentContext.Context, agentID string, message ag fileInfo["file_id"] = info.FileID } - // Use agent ID as namespace prefix for Space keys + // Use agent ID as namespace prefix for Memory keys filesListKey := agentID + ":files_info" currentFileKey := agentID + ":current_file" // Thread-safe: append current file to files list fileInfoMutex.Lock() var filesList []map[string]interface{} - if existing, err := ctx.Space.Get(filesListKey); err == nil { + if existing, ok := ctx.Memory.Context.Get(filesListKey); ok { // Convert existing data to []map[string]interface{} if existingList, ok := existing.([]interface{}); ok { for _, item := range existingList { @@ -95,23 +95,23 @@ func CallAgentWithFileInfo(ctx *agentContext.Context, agentID string, message ag } // Append current file to list filesList = append(filesList, fileInfo) - ctx.Space.Set(filesListKey, filesList) + ctx.Memory.Context.Set(filesListKey, filesList, 0) fileInfoMutex.Unlock() - // Store current file in Space - if err := ctx.Space.Set(currentFileKey, fileInfo); err != nil { - log.Trace("[Content] Failed to set current file info in Space: %v", err) + // Store current file in Memory.Context + if err := ctx.Memory.Context.Set(currentFileKey, fileInfo, 0); err != nil { + log.Trace("[Content] Failed to set current file info in Memory.Context: %v", err) } // Ensure cleanup after agent call completes defer func() { // Clean up current file - if err := ctx.Space.Delete(currentFileKey); err != nil { - log.Trace("[Content] Failed to delete current file info from Space: %v", err) + if err := ctx.Memory.Context.Del(currentFileKey); err != nil { + log.Trace("[Content] Failed to delete current file info from Memory.Context: %v", err) } // Clean up files list (reset for next call) - if err := ctx.Space.Delete(filesListKey); err != nil { - log.Trace("[Content] Failed to delete files list from Space: %v", err) + if err := ctx.Memory.Context.Del(filesListKey); err != nil { + log.Trace("[Content] Failed to delete files list from Memory.Context: %v", err) } }() } diff --git a/agent/context/JSAPI.md b/agent/context/JSAPI.md index 1d007bcc..4ba9409f 100644 --- a/agent/context/JSAPI.md +++ b/agent/context/JSAPI.md @@ -35,7 +35,7 @@ interface Context { authorized: Record; // Authorization data (empty object if not set) // Objects - space: Space; // Shared data space for passing data between requests + memory: Memory; // Agent memory with four namespaces: user, team, chat, context trace: Trace; // Trace object for debugging and monitoring mcp: MCP; // MCP object for external tool/resource access } @@ -1210,7 +1210,7 @@ Releases trace resources. Trace spaces are visual containers for organizing trace nodes in the frontend UI. They help group related operations together for better presentation to users. -> **Note:** Trace spaces are purely for visual organization and presentation. They do not store data - use `ctx.space` for data storage between hooks. +> **Note:** Trace spaces are purely for visual organization and presentation. They do not store data - use `ctx.memory` for data storage between hooks. #### `ctx.trace.CreateSpace(option)` @@ -1248,138 +1248,372 @@ Retrieves a trace space by ID. const search_space = ctx.trace.GetSpace("search-space-id"); ``` -## Space API +## Memory API -The `ctx.space` object provides a shared data space for passing data between requests and agent calls. This is useful for storing temporary data that needs to be accessed across different hooks or nested agent calls. +The `ctx.memory` object provides a four-level hierarchical memory system for agent state management. Each level has different persistence and scope characteristics. -### Methods Summary +### Memory Namespaces -| Method | Description | -| ----------------- | ------------------------------------- | -| `Get(key)` | Get a value from the space | -| `Set(key, value)` | Set a value in the space | -| `Delete(key)` | Delete a key from the space | -| `GetDel(key)` | Get a value and immediately delete it | +| Namespace | Scope | Persistence | Use Case | +| -------------------- | ------------------- | ----------- | ------------------------------------------- | +| `ctx.memory.user` | Per user | Persistent | User preferences, settings, long-term state | +| `ctx.memory.team` | Per team | Persistent | Team-wide settings, shared configurations | +| `ctx.memory.chat` | Per chat session | Persistent | Chat-specific context, conversation state | +| `ctx.memory.context` | Per request context | Temporary | Request-scoped data, cleared on release | -### Methods +### Namespace Interface -#### `ctx.space.Get(key): any` +Each namespace (`user`, `team`, `chat`, `context`) provides the same interface: -Gets a value from the space. +```typescript +interface MemoryNamespace { + // Basic KV operations + Get(key: string): any; // Get a value + Set(key: string, value: any, ttl?: number): void; // Set a value with optional TTL (seconds) + Del(key: string): void; // Delete a key (supports wildcards: "prefix:*") + Has(key: string): boolean; // Check if key exists + GetDel(key: string): any; // Get and delete atomically -**Parameters:** + // Collection operations + Keys(): string[]; // Get all keys + Len(): number; // Get number of keys + Clear(): void; // Delete all keys -- `key`: String - The key to retrieve + // Atomic counter operations + Incr(key: string, delta?: number): number; // Increment (default delta=1) + Decr(key: string, delta?: number): number; // Decrement (default delta=1) -**Returns:** + // List operations + Push(key: string, values: any[]): number; // Append to list, returns new length + Pop(key: string): any; // Remove and return last element + Pull(key: string, count: number): any[]; // Remove and return last N elements + PullAll(key: string): any[]; // Remove and return all elements + AddToSet(key: string, values: any[]): number; // Add unique values to set -- `any`: The value, or `null` if not found + // Array access operations + ArrayLen(key: string): number; // Get array length + ArrayGet(key: string, index: number): any; // Get element at index + ArraySet(key: string, index: number, value: any): void; // Set element at index + ArraySlice(key: string, start: number, end: number): any[]; // Get slice + ArrayPage(key: string, page: number, size: number): any[]; // Paginated access + ArrayAll(key: string): any[]; // Get all elements -**Example:** - -```javascript -const user_data = ctx.space.Get("user_data"); -if (user_data) { - console.log("Found user:", user_data.name); + // Metadata + id: string; // Namespace ID + space: string; // Space type: "user", "team", "chat", or "context" } ``` -#### `ctx.space.Set(key, value): void` +### Basic KV Operations -Sets a value in the space. +#### `Get(key): any` -**Parameters:** - -- `key`: String - The key to set -- `value`: Any - The value to store - -**Example:** +Gets a value from the namespace. ```javascript -ctx.space.Set("user_data", { name: "John", id: 123 }); -ctx.space.Set("processing_status", "started"); +// User preferences +const theme = ctx.memory.user.Get("theme"); +if (theme) { + console.log("User prefers:", theme); +} + +// Chat context +const topic = ctx.memory.chat.Get("current_topic"); ``` -#### `ctx.space.Delete(key): void` +#### `Set(key, value, ttl?): void` -Deletes a key from the space. - -**Parameters:** - -- `key`: String - The key to delete - -**Example:** +Sets a value with optional TTL (time-to-live in seconds). ```javascript -ctx.space.Delete("temp_data"); +// Persistent user setting +ctx.memory.user.Set("language", "en"); + +// Team configuration +ctx.memory.team.Set("api_key", "sk-xxx"); + +// Chat state +ctx.memory.chat.Set("last_query", "What is AI?"); + +// Temporary context data with 5 minute TTL +ctx.memory.context.Set("temp_result", { data: "..." }, 300); ``` -#### `ctx.space.GetDel(key): any` +#### `Del(key): void` -Gets a value and immediately deletes it. Convenient for one-time use data. - -**Parameters:** - -- `key`: String - The key to retrieve and delete - -**Returns:** - -- `any`: The value, or `null` if not found - -**Example:** +Deletes a key. Supports wildcard patterns with `*`. ```javascript -// Store file metadata in parent agent -ctx.space.Set("file_metadata", { name: "report.pdf", size: 1024 }); +// Delete single key +ctx.memory.user.Del("old_setting"); -// In child agent, get and consume the data -const metadata = ctx.space.GetDel("file_metadata"); -// metadata is now deleted from space +// Delete with wildcard pattern +ctx.memory.chat.Del("cache:*"); // Deletes all keys starting with "cache:" +``` + +#### `Has(key): boolean` + +Checks if a key exists. + +```javascript +if (ctx.memory.user.Has("onboarding_complete")) { + // Skip onboarding +} +``` + +#### `GetDel(key): any` + +Atomically gets and deletes a value. Useful for one-time tokens. + +```javascript +const token = ctx.memory.context.GetDel("one_time_token"); +if (token) { + // Use token (it's now deleted) +} +``` + +### Collection Operations + +#### `Keys(): string[]` + +Returns all keys in the namespace. + +```javascript +const userKeys = ctx.memory.user.Keys(); +console.log("User has", userKeys.length, "stored values"); +``` + +#### `Len(): number` + +Returns the number of keys. + +```javascript +const count = ctx.memory.chat.Len(); +console.log("Chat has", count, "stored values"); +``` + +#### `Clear(): void` + +Deletes all keys in the namespace. + +```javascript +// Clear temporary context data +ctx.memory.context.Clear(); +``` + +### Atomic Counter Operations + +#### `Incr(key, delta?): number` + +Atomically increments a counter. Returns the new value. + +```javascript +// Simple counter +const views = ctx.memory.user.Incr("page_views"); +console.log("Total views:", views); + +// Increment by custom amount +const points = ctx.memory.user.Incr("points", 10); +``` + +#### `Decr(key, delta?): number` + +Atomically decrements a counter. Returns the new value. + +```javascript +const remaining = ctx.memory.user.Decr("credits"); +if (remaining < 0) { + throw new Error("Insufficient credits"); +} +``` + +### List Operations + +#### `Push(key, values): number` + +Appends values to a list. Returns new length. + +```javascript +const len = ctx.memory.chat.Push("history", [ + { role: "user", content: "Hello" }, + { role: "assistant", content: "Hi there!" }, +]); +``` + +#### `Pop(key): any` + +Removes and returns the last element. + +```javascript +const lastItem = ctx.memory.chat.Pop("pending_tasks"); +``` + +#### `Pull(key, count): any[]` + +Removes and returns the last N elements. + +```javascript +const recentItems = ctx.memory.chat.Pull("notifications", 5); +``` + +#### `PullAll(key): any[]` + +Removes and returns all elements. + +```javascript +const allTasks = ctx.memory.context.PullAll("batch_queue"); +// Process all tasks, queue is now empty +``` + +#### `AddToSet(key, values): number` + +Adds unique values to a set (no duplicates). Returns new size. + +```javascript +ctx.memory.user.AddToSet("visited_pages", ["/home", "/about"]); +ctx.memory.user.AddToSet("visited_pages", ["/home", "/contact"]); // "/home" not added again +``` + +### Array Access Operations + +#### `ArrayLen(key): number` + +Gets the length of an array. + +```javascript +const historyLen = ctx.memory.chat.ArrayLen("messages"); +``` + +#### `ArrayGet(key, index): any` + +Gets an element at a specific index. + +```javascript +const firstMessage = ctx.memory.chat.ArrayGet("messages", 0); +const lastMessage = ctx.memory.chat.ArrayGet("messages", -1); // Negative index +``` + +#### `ArraySet(key, index, value): void` + +Sets an element at a specific index. + +```javascript +ctx.memory.chat.ArraySet("messages", 0, { role: "system", content: "Updated" }); +``` + +#### `ArraySlice(key, start, end): any[]` + +Gets a slice of the array. + +```javascript +const recent = ctx.memory.chat.ArraySlice("messages", -10, -1); // Last 10 messages +``` + +#### `ArrayPage(key, page, size): any[]` + +Gets a page of elements (1-indexed pages). + +```javascript +const page1 = ctx.memory.chat.ArrayPage("messages", 1, 20); // First 20 messages +const page2 = ctx.memory.chat.ArrayPage("messages", 2, 20); // Next 20 messages +``` + +#### `ArrayAll(key): any[]` + +Gets all elements of the array. + +```javascript +const allMessages = ctx.memory.chat.ArrayAll("messages"); ``` ### Use Cases ```javascript -// Use case 1: Pass data between hooks +// Use case 1: User preferences (persistent across sessions) function Create(ctx, messages) { - // Store data for later use - ctx.space.Set("original_query", messages[0].content); + // Load user preferences + const locale = ctx.memory.user.Get("preferred_locale") || "en"; + const style = ctx.memory.user.Get("response_style") || "concise"; + + return { + messages, + locale: locale, + metadata: { style: style }, + }; +} + +// Use case 2: Chat context (persistent within chat session) +function Next(ctx, payload) { + // Track conversation topics + const topics = ctx.memory.chat.Get("discussed_topics") || []; + const newTopic = extractTopic(payload.completion.content); + + if (newTopic && !topics.includes(newTopic)) { + topics.push(newTopic); + ctx.memory.chat.Set("discussed_topics", topics); + } +} + +// Use case 3: Request-scoped data (cleared on context release) +function Create(ctx, messages) { + // Store temporary processing data + ctx.memory.context.Set("request_start", Date.now()); + ctx.memory.context.Set("original_query", messages[0]?.content); + return { messages }; } function Next(ctx, payload) { - // Retrieve data from Create hook - const query = ctx.space.Get("original_query"); - console.log("Original query was:", query); + // Retrieve temporary data + const startTime = ctx.memory.context.Get("request_start"); + const duration = Date.now() - startTime; + console.log("Request took", duration, "ms"); + + // context memory is automatically cleared when ctx.Release() is called } -// Use case 2: Pass data to nested agent calls +// Use case 4: Team-wide settings function Create(ctx, messages) { - // Prepare context for child agent - ctx.space.Set("parent_context", { - user_id: ctx.authorized.user_id, - session_start: Date.now(), - }); + // Check team quota + const used = ctx.memory.team.Incr("monthly_requests"); + const limit = ctx.memory.team.Get("monthly_limit") || 10000; - // Call child agent... + if (used > limit) { + throw new Error("Team quota exceeded"); + } + + return { messages }; } -// Use case 3: One-time data consumption -function Next(ctx, payload) { - // Get and delete in one operation - const temp_data = ctx.space.GetDel("temp_processing_data"); - if (temp_data) { - // Process and discard +// Use case 5: Rate limiting with counters +function Create(ctx, messages) { + const key = `rate:${new Date().toISOString().slice(0, 13)}`; // Hourly bucket + const count = ctx.memory.user.Incr(key); + + if (count > 100) { + throw new Error("Rate limit exceeded"); } + + return { messages }; } ``` +### Memory Lifecycle + +| Namespace | Created When | Cleared When | +| --------- | ---------------- | --------------- | +| `user` | First access | Manual only | +| `team` | First access | Manual only | +| `chat` | First access | Manual only | +| `context` | Context creation | `ctx.Release()` | + **Notes:** -- Space is shared across all hooks within the same request -- Space persists across nested agent calls (A2A) -- Values can be any JSON-serializable data -- Use `GetDel` for data that should only be consumed once +- `user`, `team`, `chat` namespaces are persistent (backed by database) +- `context` namespace is temporary and cleared when the request context is released +- All namespaces support TTL for automatic expiration +- Wildcard deletion (`Del("prefix:*")`) works on all namespaces +- Counter operations (`Incr`, `Decr`) are atomic ## MCP API @@ -1697,7 +1931,7 @@ interface UsesConfig { ```javascript function Create(ctx, messages) { // Store data for Next hook - ctx.space.Set("user_query", messages[0]?.content); + ctx.memory.context.Set("user_query", messages[0]?.content); // Modify messages const enhanced_messages = messages.map((msg) => ({ @@ -1873,7 +2107,7 @@ See the [Agent Execution Lifecycle](#agent-execution-lifecycle) diagram above fo - **Hooks can send messages directly** via `ctx.Send()`, `ctx.SendStream()`, etc. - **Create Hook** runs before LLM call (if any), can modify messages and configure the request - **Next Hook** runs after LLM call and tool execution (if any), can post-process or delegate -- Use `ctx.space` to pass data between Create and Next hooks +- Use `ctx.memory.context` to pass data between Create and Next hooks within a request ## Complete Example @@ -1891,9 +2125,9 @@ function Create(ctx, messages) { // Extract user query from the last message const user_query = messages[messages.length - 1]?.content || ""; - // Store data in space for use in Next hook - ctx.space.Set("original_query", user_query); - ctx.space.Set("request_time", Date.now()); + // Store data in context memory for use in Next hook + ctx.memory.context.Set("original_query", user_query); + ctx.memory.context.Set("request_time", Date.now()); // Add trace node to show processing in UI const create_node = ctx.trace.Add( @@ -1943,9 +2177,9 @@ function Create(ctx, messages) { function Next(ctx, payload) { const { messages, completion, tools, error } = payload; - // Retrieve data from Create hook via space - const original_query = ctx.space.Get("original_query"); - const request_time = ctx.space.Get("request_time"); + // Retrieve data from Create hook via context memory + const original_query = ctx.memory.context.Get("original_query"); + const request_time = ctx.memory.context.Get("request_time"); const duration = Date.now() - request_time; // Create trace node for Next hook processing @@ -2038,7 +2272,7 @@ function Next(ctx, payload) { 4. **Logging Levels**: Use appropriate log levels (Debug for development, Info for progress, Error for failures) 5. **Message IDs**: Let the system auto-generate message IDs unless you need specific tracking 6. **Parallel Operations**: Use `Trace.Parallel()` for concurrent operations to maintain trace clarity -7. **Space Usage**: Use `ctx.space` for passing data between hooks and nested agent calls +7. **Memory Usage**: Use `ctx.memory.context` for request-scoped data, `ctx.memory.chat` for chat state, `ctx.memory.user` for user preferences 8. **Streaming Messages**: Use `SendStream()` + `Append()` + `End()` for streaming output; use `Send()` for complete messages 9. **Block Grouping**: Only use Block IDs when you need to group multiple messages together (e.g., LLM output + follow-up card) diff --git a/agent/context/context.go b/agent/context/context.go index 20f77033..71062bd5 100644 --- a/agent/context/context.go +++ b/agent/context/context.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/yaoapp/gou/plan" + "github.com/yaoapp/yao/agent/memory" "github.com/yaoapp/yao/agent/output/message" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/openapi/oauth/types" @@ -27,11 +27,21 @@ func New(parent context.Context, authorized *types.AuthorizedInfo, chatID string contextID := generateContextID() + // Extract user and team IDs from authorized info + var userID, teamID string + if authorized != nil { + userID = authorized.UserID + teamID = authorized.TeamID + } + + // Create memory instance using global manager + mem, _ := memory.GetMemory(userID, teamID, chatID, contextID) + ctx := &Context{ Context: parent, ID: contextID, // Generate unique ID for the context Authorized: authorized, // Set authorized info - Space: plan.NewMemorySharedSpace(), + Memory: mem, ChatID: chatID, IDGenerator: message.NewIDGenerator(), // Initialize ID generator for this context messageMetadata: newMessageMetadataStore(), // Initialize message metadata store @@ -78,14 +88,15 @@ func (ctx *Context) Release() { ctx.trace = nil } - // Clear space - if ctx.Space != nil { + // Clear context-level memory only (request-scoped temporary data) + // User, Team, Chat level memory is persistent and should NOT be cleared + if ctx.Memory != nil && ctx.Memory.Context != nil { if ctx.Logger != nil { - ctx.Logger.Cleanup("Space") + ctx.Logger.Cleanup("Memory.Context") } - ctx.Space.Clear() - ctx.Space = nil + ctx.Memory.Context.Clear() } + ctx.Memory = nil // Clear stacks if ctx.Stacks != nil { @@ -379,9 +390,9 @@ func (ctx *Context) BeginStep(stepType string, input map[string]interface{}) *Bu return nil } - // Update space snapshot before starting step - if ctx.Space != nil { - ctx.Buffer.SetSpaceSnapshot(ctx.Space.Snapshot()) + // Update context memory snapshot before starting step (for recovery) + if ctx.Memory != nil && ctx.Memory.Context != nil { + ctx.Buffer.SetSpaceSnapshot(ctx.Memory.Context.Snapshot()) } return ctx.Buffer.BeginStep(stepType, input, ctx.Stack) diff --git a/agent/context/context_test.go b/agent/context/context_test.go index 623cfb1c..7986cbf8 100644 --- a/agent/context/context_test.go +++ b/agent/context/context_test.go @@ -211,7 +211,7 @@ func TestGetCompletionRequest(t *testing.T) { assert.Equal(t, tt.expectedReferer, ctx.Referer) assert.Equal(t, tt.expectedAccept, ctx.Accept) assert.Equal(t, tt.expectedAssistantID, ctx.AssistantID) - assert.NotNil(t, ctx.Space) + assert.NotNil(t, ctx.Memory) assert.NotNil(t, ctx.Cache) }) } @@ -227,7 +227,7 @@ func TestContextNew_WithAuthorized(t *testing.T) { assert.NotNil(t, ctx) assert.Equal(t, "test-chat-id", ctx.ChatID) - assert.NotNil(t, ctx.Space) + assert.NotNil(t, ctx.Memory) assert.NotNil(t, ctx.IDGenerator) } diff --git a/agent/context/jsapi.go b/agent/context/jsapi.go index 2daa0a66..fa7707c1 100644 --- a/agent/context/jsapi.go +++ b/agent/context/jsapi.go @@ -1,7 +1,10 @@ package context import ( + "time" + "github.com/yaoapp/gou/runtime/v8/bridge" + "github.com/yaoapp/yao/agent/memory" "github.com/yaoapp/yao/agent/output" "github.com/yaoapp/yao/agent/output/message" traceJsapi "github.com/yaoapp/yao/trace/jsapi" @@ -136,11 +139,11 @@ func (ctx *Context) NewObject(v8ctx *v8go.Context) (*v8go.Value, error) { } } - // Space object - create a JavaScript object with Get/Set/Delete methods - if ctx.Space != nil { - spaceObj := ctx.createSpaceObject(v8ctx) - obj.Set("space", spaceObj) - spaceObj.Release() + // Memory object - create a JavaScript object with User/Team/Chat/Context namespaces + if ctx.Memory != nil { + memoryObj := ctx.createMemoryObject(v8ctx) + obj.Set("memory", memoryObj) + memoryObj.Release() } return instance.Value, nil @@ -718,26 +721,57 @@ func (ctx *Context) endBlockMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { }) } -// createSpaceObject creates a Space object for JavaScript access -// Space is a shared data space for passing data between requests and calls -func (ctx *Context) createSpaceObject(v8ctx *v8go.Context) *v8go.Value { +// createMemoryObject creates a Memory object for JavaScript access +// Memory provides four namespaces: User, Team, Chat, Context +// Each namespace supports: Get, Set, Del, Has, Keys, Len, Clear, Incr, Decr +func (ctx *Context) createMemoryObject(v8ctx *v8go.Context) *v8go.Value { iso := v8ctx.Isolate() - spaceObj, _ := v8ctx.RunScript("({})", "space-init") - obj, _ := spaceObj.AsObject() + objTpl := v8go.NewObjectTemplate(iso) + obj, _ := objTpl.NewInstance(v8ctx) - // Get method: space.Get(key) + // Create namespace accessors + if ctx.Memory.User != nil { + userObj := ctx.createNamespaceObject(v8ctx, ctx.Memory.User) + obj.Set("user", userObj) + userObj.Release() + } + + if ctx.Memory.Team != nil { + teamObj := ctx.createNamespaceObject(v8ctx, ctx.Memory.Team) + obj.Set("team", teamObj) + teamObj.Release() + } + + if ctx.Memory.Chat != nil { + chatObj := ctx.createNamespaceObject(v8ctx, ctx.Memory.Chat) + obj.Set("chat", chatObj) + chatObj.Release() + } + + if ctx.Memory.Context != nil { + contextObj := ctx.createNamespaceObject(v8ctx, ctx.Memory.Context) + obj.Set("context", contextObj) + contextObj.Release() + } + + return obj.Value +} + +// createNamespaceObject creates a namespace object with KV store methods +func (ctx *Context) createNamespaceObject(v8ctx *v8go.Context, ns *memory.Namespace) *v8go.Value { + iso := v8ctx.Isolate() + objTpl := v8go.NewObjectTemplate(iso) + obj, _ := objTpl.NewInstance(v8ctx) + + // Get method: ns.Get(key) getFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { - if ctx.Space == nil { - return v8go.Null(iso) - } - if len(info.Args()) < 1 { return bridge.JsException(info.Context(), "Get requires a key argument") } key := info.Args()[0].String() - value, err := ctx.Space.Get(key) - if err != nil { + value, ok := ns.Get(key) + if !ok { return v8go.Null(iso) } @@ -751,12 +785,8 @@ func (ctx *Context) createSpaceObject(v8ctx *v8go.Context) *v8go.Value { getFuncVal := getFunc.GetFunction(v8ctx) obj.Set("Get", getFuncVal.Value) - // Set method: space.Set(key, value) + // Set method: ns.Set(key, value, ttl?) setFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { - if ctx.Space == nil { - return bridge.JsException(info.Context(), "Space is not available") - } - if len(info.Args()) < 2 { return bridge.JsException(info.Context(), "Set requires key and value arguments") } @@ -767,7 +797,14 @@ func (ctx *Context) createSpaceObject(v8ctx *v8go.Context) *v8go.Value { return bridge.JsException(info.Context(), "Failed to convert value: "+err.Error()) } - if err := ctx.Space.Set(key, value); err != nil { + // Optional TTL in milliseconds (third argument) + var ttl time.Duration + if len(info.Args()) >= 3 && info.Args()[2].IsNumber() { + ttlMs := info.Args()[2].Integer() + ttl = time.Duration(ttlMs) * time.Millisecond + } + + if err := ns.Set(key, value, ttl); err != nil { return bridge.JsException(info.Context(), "Failed to set value: "+err.Error()) } @@ -776,50 +813,128 @@ func (ctx *Context) createSpaceObject(v8ctx *v8go.Context) *v8go.Value { setFuncVal := setFunc.GetFunction(v8ctx) obj.Set("Set", setFuncVal.Value) - // Delete method: space.Delete(key) + // Del method: ns.Del(key) delFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { - if ctx.Space == nil { - return bridge.JsException(info.Context(), "Space is not available") - } - if len(info.Args()) < 1 { - return bridge.JsException(info.Context(), "Delete requires a key argument") + return bridge.JsException(info.Context(), "Del requires a key argument") } key := info.Args()[0].String() - if err := ctx.Space.Delete(key); err != nil { + if err := ns.Del(key); err != nil { return bridge.JsException(info.Context(), "Failed to delete key: "+err.Error()) } return v8go.Undefined(iso) }) delFuncVal := delFunc.GetFunction(v8ctx) - obj.Set("Delete", delFuncVal.Value) + obj.Set("Del", delFuncVal.Value) - // GetDel method: space.GetDel(key) - Get value and delete immediately - // Convenient for one-time use data (e.g., file metadata passed between agents) - getDelFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { - if ctx.Space == nil { - return v8go.Null(iso) + // Has method: ns.Has(key) + hasFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { + if len(info.Args()) < 1 { + return bridge.JsException(info.Context(), "Has requires a key argument") } + key := info.Args()[0].String() + exists := ns.Has(key) + + jsValue, _ := v8go.NewValue(iso, exists) + return jsValue + }) + hasFuncVal := hasFunc.GetFunction(v8ctx) + obj.Set("Has", hasFuncVal.Value) + + // Keys method: ns.Keys() + keysFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { + keys := ns.Keys() + jsValue, err := bridge.JsValue(info.Context(), keys) + if err != nil { + return bridge.JsException(info.Context(), "Failed to get keys: "+err.Error()) + } + return jsValue + }) + keysFuncVal := keysFunc.GetFunction(v8ctx) + obj.Set("Keys", keysFuncVal.Value) + + // Len method: ns.Len() + lenFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { + length := ns.Len() + // Use int32 for JavaScript Number (int64 becomes BigInt which is incompatible) + jsValue, _ := v8go.NewValue(iso, int32(length)) + return jsValue + }) + lenFuncVal := lenFunc.GetFunction(v8ctx) + obj.Set("Len", lenFuncVal.Value) + + // Clear method: ns.Clear() + clearFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { + ns.Clear() + return v8go.Undefined(iso) + }) + clearFuncVal := clearFunc.GetFunction(v8ctx) + obj.Set("Clear", clearFuncVal.Value) + + // Incr method: ns.Incr(key, delta?) + incrFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { + if len(info.Args()) < 1 { + return bridge.JsException(info.Context(), "Incr requires a key argument") + } + + key := info.Args()[0].String() + delta := int64(1) + if len(info.Args()) >= 2 && info.Args()[1].IsNumber() { + delta = info.Args()[1].Integer() + } + + newValue, err := ns.Incr(key, delta) + if err != nil { + return bridge.JsException(info.Context(), "Failed to increment: "+err.Error()) + } + + // Use int32 for JavaScript Number (int64 becomes BigInt which is incompatible with ===) + // For counters, int32 range (-2^31 to 2^31-1) is sufficient + jsValue, _ := v8go.NewValue(iso, int32(newValue)) + return jsValue + }) + incrFuncVal := incrFunc.GetFunction(v8ctx) + obj.Set("Incr", incrFuncVal.Value) + + // Decr method: ns.Decr(key, delta?) + decrFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { + if len(info.Args()) < 1 { + return bridge.JsException(info.Context(), "Decr requires a key argument") + } + + key := info.Args()[0].String() + delta := int64(1) + if len(info.Args()) >= 2 && info.Args()[1].IsNumber() { + delta = info.Args()[1].Integer() + } + + newValue, err := ns.Decr(key, delta) + if err != nil { + return bridge.JsException(info.Context(), "Failed to decrement: "+err.Error()) + } + + // Use int32 for JavaScript Number (int64 becomes BigInt which is incompatible with ===) + jsValue, _ := v8go.NewValue(iso, int32(newValue)) + return jsValue + }) + decrFuncVal := decrFunc.GetFunction(v8ctx) + obj.Set("Decr", decrFuncVal.Value) + + // GetDel method: ns.GetDel(key) - Get value and delete immediately + getDelFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { if len(info.Args()) < 1 { return bridge.JsException(info.Context(), "GetDel requires a key argument") } key := info.Args()[0].String() - - // Get value first - value, err := ctx.Space.Get(key) - if err != nil { + value, ok := ns.GetDel(key) + if !ok { return v8go.Null(iso) } - // Delete immediately after getting - // Ignore delete errors (key might not exist) - ctx.Space.Delete(key) - - // Convert to JavaScript value jsValue, err := bridge.JsValue(info.Context(), value) if err != nil { return v8go.Null(iso) @@ -830,7 +945,7 @@ func (ctx *Context) createSpaceObject(v8ctx *v8go.Context) *v8go.Value { getDelFuncVal := getDelFunc.GetFunction(v8ctx) obj.Set("GetDel", getDelFuncVal.Value) - return spaceObj + return obj.Value } // sendGroupMethod implements ctx.SendGroup(group) diff --git a/agent/context/jsapi_mcp_test.go b/agent/context/jsapi_mcp_test.go index de9ecbf5..49dd5972 100644 --- a/agent/context/jsapi_mcp_test.go +++ b/agent/context/jsapi_mcp_test.go @@ -11,21 +11,23 @@ import ( "github.com/yaoapp/yao/test" ) +// newMCPTestContext creates a test context for MCP testing +func newMCPTestContext() *context.Context { + ctx := context.New(stdContext.Background(), nil, "test-chat-id") + ctx.AssistantID = "test-assistant-id" + ctx.Locale = "en" + ctx.Referer = context.RefererAPI + stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) + ctx.Stack = stack + return ctx +} + // TestMCPListResources tests MCP.ListResources from JavaScript func TestMCPListResources(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - // Initialize context with trace - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -62,15 +64,7 @@ func TestMCPReadResource(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -105,15 +99,7 @@ func TestMCPListTools(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -152,15 +138,7 @@ func TestMCPCallTool(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -195,15 +173,7 @@ func TestMCPCallTools(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -243,15 +213,7 @@ func TestMCPCallToolsParallel(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -291,15 +253,7 @@ func TestMCPListPrompts(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -336,15 +290,7 @@ func TestMCPGetPrompt(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -379,15 +325,7 @@ func TestMCPListSamples(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -422,15 +360,7 @@ func TestMCPGetSample(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -467,15 +397,7 @@ func TestMCPJsApiWithTrace(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Referer: context.RefererAPI, - } - stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) - ctx.Stack = stack + ctx := newMCPTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { diff --git a/agent/context/jsapi_memory_test.go b/agent/context/jsapi_memory_test.go new file mode 100644 index 00000000..edd545d8 --- /dev/null +++ b/agent/context/jsapi_memory_test.go @@ -0,0 +1,561 @@ +package context_test + +import ( + stdContext "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v8 "github.com/yaoapp/gou/runtime/v8" + "github.com/yaoapp/yao/agent/context" + "github.com/yaoapp/yao/agent/memory" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/openapi/oauth/types" + "github.com/yaoapp/yao/test" +) + +func TestMemoryUserNamespace(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + ctx := &context.Context{ + ChatID: "test-chat-id", + AssistantID: "test-assistant-id", + Locale: "en", + Context: stdContext.Background(), + Memory: mem, + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Set values in user namespace + ctx.memory.user.Set("name", "John"); + ctx.memory.user.Set("age", 30); + ctx.memory.user.Set("active", true); + + // Get values back + const name = ctx.memory.user.Get("name"); + const age = ctx.memory.user.Get("age"); + const active = ctx.memory.user.Get("active"); + + // Verify + if (name !== "John") throw new Error("Name mismatch"); + if (age !== 30) throw new Error("Age mismatch"); + if (active !== true) throw new Error("Active mismatch"); + + return { + success: true, + name: name, + age: age, + active: active + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) + assert.Equal(t, "John", result["name"]) + assert.Equal(t, float64(30), result["age"]) + assert.Equal(t, true, result["active"]) +} + +func TestMemoryTeamNamespace(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + ctx := &context.Context{ + ChatID: "test-chat-id", + AssistantID: "test-assistant-id", + Locale: "en", + Context: stdContext.Background(), + Memory: mem, + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Set team-wide settings + ctx.memory.team.Set("settings", { theme: "dark", language: "en" }); + + // Get back + const settings = ctx.memory.team.Get("settings"); + + if (settings.theme !== "dark") throw new Error("Theme mismatch"); + if (settings.language !== "en") throw new Error("Language mismatch"); + + return { + success: true, + settings: settings + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) +} + +func TestMemoryChatNamespace(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + ctx := &context.Context{ + ChatID: "test-chat-id", + AssistantID: "test-assistant-id", + Locale: "en", + Context: stdContext.Background(), + Memory: mem, + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Set chat context + ctx.memory.chat.Set("topic", "AI Discussion"); + ctx.memory.chat.Set("participants", ["Alice", "Bob"]); + + // Get back + const topic = ctx.memory.chat.Get("topic"); + const participants = ctx.memory.chat.Get("participants"); + + if (topic !== "AI Discussion") throw new Error("Topic mismatch"); + if (participants.length !== 2) throw new Error("Participants mismatch"); + + return { + success: true, + topic: topic, + participants: participants + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) + assert.Equal(t, "AI Discussion", result["topic"]) +} + +func TestMemoryContextNamespace(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + ctx := &context.Context{ + ChatID: "test-chat-id", + AssistantID: "test-assistant-id", + Locale: "en", + Context: stdContext.Background(), + Memory: mem, + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Set temporary context data + ctx.memory.context.Set("temp_result", { step: 1, data: "processing" }); + + // Get back + const result = ctx.memory.context.Get("temp_result"); + + if (result.step !== 1) throw new Error("Step mismatch"); + if (result.data !== "processing") throw new Error("Data mismatch"); + + return { + success: true, + result: result + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) +} + +func TestMemoryHasAndDel(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + ctx := &context.Context{ + ChatID: "test-chat-id", + AssistantID: "test-assistant-id", + Locale: "en", + Context: stdContext.Background(), + Memory: mem, + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Set a value + ctx.memory.user.Set("key", "value"); + + // Check Has + const hasBefore = ctx.memory.user.Has("key"); + if (!hasBefore) throw new Error("Should have key before delete"); + + // Delete + ctx.memory.user.Del("key"); + + // Check Has again + const hasAfter = ctx.memory.user.Has("key"); + if (hasAfter) throw new Error("Should not have key after delete"); + + return { + success: true, + hasBefore: hasBefore, + hasAfter: hasAfter + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) + assert.True(t, result["hasBefore"].(bool)) + assert.False(t, result["hasAfter"].(bool)) +} + +func TestMemoryIncrDecr(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + ctx := &context.Context{ + ChatID: "test-chat-id", + AssistantID: "test-assistant-id", + Locale: "en", + Context: stdContext.Background(), + Memory: mem, + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Incr on non-existent key + const v1 = ctx.memory.user.Incr("counter"); + if (v1 !== 1) throw new Error("First incr should be 1, got " + v1); + + // Incr with delta + const v2 = ctx.memory.user.Incr("counter", 5); + if (v2 !== 6) throw new Error("Second incr should be 6, got " + v2); + + // Decr + const v3 = ctx.memory.user.Decr("counter", 2); + if (v3 !== 4) throw new Error("Decr should be 4, got " + v3); + + return { + success: true, + v1: v1, + v2: v2, + v3: v3 + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) + assert.Equal(t, float64(1), result["v1"]) + assert.Equal(t, float64(6), result["v2"]) + assert.Equal(t, float64(4), result["v3"]) +} + +func TestMemoryKeysAndLen(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + // Use unique IDs to avoid data pollution from other tests + mem, err := memory.New(nil, "user-keys-len", "team-keys-len", "chat-keys-len", "ctx-keys-len") + require.NoError(t, err) + + ctx := &context.Context{ + ChatID: "test-chat-id", + AssistantID: "test-assistant-id", + Locale: "en", + Context: stdContext.Background(), + Memory: mem, + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Set multiple values + ctx.memory.user.Set("a", 1); + ctx.memory.user.Set("b", 2); + ctx.memory.user.Set("c", 3); + + // Get keys + const keys = ctx.memory.user.Keys(); + if (keys.length !== 3) throw new Error("Should have 3 keys, got " + keys.length); + + // Get len + const len = ctx.memory.user.Len(); + if (len !== 3) throw new Error("Len should be 3, got " + len); + + return { + success: true, + keys: keys, + len: len + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + if !result["success"].(bool) { + t.Fatalf("Test failed: %v", result["error"]) + } + assert.Equal(t, float64(3), result["len"]) +} + +func TestMemoryClear(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + ctx := &context.Context{ + ChatID: "test-chat-id", + AssistantID: "test-assistant-id", + Locale: "en", + Context: stdContext.Background(), + Memory: mem, + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Set values + ctx.memory.user.Set("a", 1); + ctx.memory.user.Set("b", 2); + + // Clear + ctx.memory.user.Clear(); + + // Check len + const len = ctx.memory.user.Len(); + if (len !== 0) throw new Error("Len should be 0 after clear, got " + len); + + return { + success: true, + len: len + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) + assert.Equal(t, float64(0), result["len"]) +} + +func TestMemoryGetDel(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + ctx := &context.Context{ + ChatID: "test-chat-id", + AssistantID: "test-assistant-id", + Locale: "en", + Context: stdContext.Background(), + Memory: mem, + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Set a one-time value + ctx.memory.user.Set("token", "secret123"); + + // GetDel + const value = ctx.memory.user.GetDel("token"); + if (value !== "secret123") throw new Error("Value mismatch"); + + // Should be deleted + const after = ctx.memory.user.Get("token"); + if (after !== null) throw new Error("Should be null after GetDel"); + + return { + success: true, + value: value, + after: after + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) + assert.Equal(t, "secret123", result["value"]) + assert.Nil(t, result["after"]) +} + +func TestMemoryIsolation(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + // Create two different memory instances + mem1, err := memory.New(nil, "user1", "", "", "") + require.NoError(t, err) + + mem2, err := memory.New(nil, "user2", "", "", "") + require.NoError(t, err) + + ctx1 := &context.Context{ + ChatID: "chat1", + Context: stdContext.Background(), + Memory: mem1, + } + + ctx2 := &context.Context{ + ChatID: "chat2", + Context: stdContext.Background(), + Memory: mem2, + } + + // Set value in user1 + res1, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + ctx.memory.user.Set("key", "user1_value"); + return ctx.memory.user.Get("key"); + }`, ctx1) + require.NoError(t, err) + assert.Equal(t, "user1_value", res1) + + // Set value in user2 + res2, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + ctx.memory.user.Set("key", "user2_value"); + return ctx.memory.user.Get("key"); + }`, ctx2) + require.NoError(t, err) + assert.Equal(t, "user2_value", res2) + + // Verify user1 still has its own value + res3, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + return ctx.memory.user.Get("key"); + }`, ctx1) + require.NoError(t, err) + assert.Equal(t, "user1_value", res3) +} + +func TestMemoryNoMemory(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + ctx := &context.Context{ + ChatID: "test-chat-id", + Context: stdContext.Background(), + Memory: nil, // No memory + } + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + const hasMemory = ctx.memory !== undefined && ctx.memory !== null; + return { + success: true, + hasMemory: hasMemory + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) + assert.False(t, result["hasMemory"].(bool)) +} + +func TestMemoryWithAuthorizedInfo(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + // Use context.New to create context with authorized info + authorized := &types.AuthorizedInfo{ + UserID: "user123", + TeamID: "team456", + } + + ctx := context.New(stdContext.Background(), authorized, "chat789") + defer ctx.Release() + + // Verify memory was created with correct IDs + require.NotNil(t, ctx.Memory) + require.NotNil(t, ctx.Memory.User) + require.NotNil(t, ctx.Memory.Team) + require.NotNil(t, ctx.Memory.Chat) + require.NotNil(t, ctx.Memory.Context) + + res, err := v8.Call(v8.CallOptions{}, ` + function test(ctx) { + try { + // Set values in different namespaces + ctx.memory.user.Set("pref", "dark"); + ctx.memory.team.Set("setting", "shared"); + ctx.memory.chat.Set("topic", "test"); + ctx.memory.context.Set("temp", "data"); + + return { + success: true, + user: ctx.memory.user.Get("pref"), + team: ctx.memory.team.Get("setting"), + chat: ctx.memory.chat.Get("topic"), + context: ctx.memory.context.Get("temp") + }; + } catch (error) { + return { success: false, error: error.message }; + } + }`, ctx) + + require.NoError(t, err) + result := res.(map[string]interface{}) + assert.True(t, result["success"].(bool)) + assert.Equal(t, "dark", result["user"]) + assert.Equal(t, "shared", result["team"]) + assert.Equal(t, "test", result["chat"]) + assert.Equal(t, "data", result["context"]) +} diff --git a/agent/context/jsapi_release_test.go b/agent/context/jsapi_release_test.go index 63b0d716..333e35a1 100644 --- a/agent/context/jsapi_release_test.go +++ b/agent/context/jsapi_release_test.go @@ -7,27 +7,26 @@ import ( "github.com/stretchr/testify/assert" v8 "github.com/yaoapp/gou/runtime/v8" "github.com/yaoapp/yao/agent/context" - "github.com/yaoapp/yao/agent/output/message" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/test" ) +// newReleaseTestContext creates a test context for release testing +func newReleaseTestContext() *context.Context { + ctx := context.New(stdContext.Background(), nil, "test-chat-id") + ctx.AssistantID = "test-assistant-id" + ctx.Referer = context.RefererAPI + stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) + ctx.Stack = stack + return ctx +} + // TestContextRelease tests explicit Release() method on Context func TestContextRelease(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - // Initialize stack and trace - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newReleaseTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -71,17 +70,7 @@ func TestTraceRelease(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - // Initialize stack and trace - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newReleaseTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -134,17 +123,7 @@ func TestContextReleaseWithTrace(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - // Initialize stack and trace - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newReleaseTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -183,17 +162,7 @@ func TestTryFinallyPattern(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - // Initialize stack and trace - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newReleaseTestContext() res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -237,12 +206,8 @@ func TestNoOpTraceRelease(t *testing.T) { defer test.Clean() // Context without trace initialization - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - } + cxt := context.New(stdContext.Background(), nil, "test-chat-id") + cxt.AssistantID = "test-assistant-id" res, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -286,17 +251,7 @@ func TestTryFinallyPatternWithError(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - // Initialize stack and trace - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newReleaseTestContext() _, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { diff --git a/agent/context/jsapi_space_test.go b/agent/context/jsapi_space_test.go deleted file mode 100644 index 50148894..00000000 --- a/agent/context/jsapi_space_test.go +++ /dev/null @@ -1,817 +0,0 @@ -package context_test - -import ( - stdContext "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/yaoapp/gou/plan" - v8 "github.com/yaoapp/gou/runtime/v8" - "github.com/yaoapp/yao/agent/context" - "github.com/yaoapp/yao/config" - "github.com/yaoapp/yao/test" -) - -// TestSpaceSetAndGet tests ctx.space.Set and ctx.space.Get -func TestSpaceSetAndGet(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Set various types of values - ctx.space.Set("string_key", "hello world"); - ctx.space.Set("number_key", 42); - ctx.space.Set("boolean_key", true); - ctx.space.Set("object_key", { name: "test", value: 123 }); - ctx.space.Set("array_key", [1, 2, 3, 4, 5]); - - // Get values back - const str = ctx.space.Get("string_key"); - const num = ctx.space.Get("number_key"); - const bool = ctx.space.Get("boolean_key"); - const obj = ctx.space.Get("object_key"); - const arr = ctx.space.Get("array_key"); - - // Verify values - if (str !== "hello world") throw new Error("String mismatch"); - if (num !== 42) throw new Error("Number mismatch"); - if (bool !== true) throw new Error("Boolean mismatch"); - if (obj.name !== "test" || obj.value !== 123) throw new Error("Object mismatch"); - if (arr.length !== 5 || arr[0] !== 1) throw new Error("Array mismatch"); - - return { - success: true, - str: str, - num: num, - bool: bool, - obj: obj, - arr: arr - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - if !result["success"].(bool) { - t.Fatalf("Test failed: %v", result["error"]) - } - - assert.Equal(t, true, result["success"], "Space Set/Get should succeed") - assert.Equal(t, "hello world", result["str"], "String should match") - assert.Equal(t, float64(42), result["num"], "Number should match") - assert.Equal(t, true, result["bool"], "Boolean should match") -} - -// TestSpaceGetNonExistentKey tests getting a non-existent key -func TestSpaceGetNonExistentKey(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Get non-existent key should return null/undefined - const value = ctx.space.Get("non_existent_key"); - - return { - success: true, - value: value, - is_null: value === null, - is_undefined: value === undefined - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - assert.Equal(t, true, result["success"], "Get non-existent key should succeed") - // JavaScript null is returned as nil in Go - assert.True(t, result["is_null"].(bool) || result["is_undefined"].(bool), "Non-existent key should return null or undefined") -} - -// TestSpaceDelete tests ctx.space.Delete -func TestSpaceDelete(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Set a value - ctx.space.Set("delete_me", "temporary value"); - - // Verify it exists - const before = ctx.space.Get("delete_me"); - if (before !== "temporary value") throw new Error("Value not set correctly"); - - // Delete it - ctx.space.Delete("delete_me"); - - // Verify it's gone - const after = ctx.space.Get("delete_me"); - - return { - success: true, - before: before, - after: after, - is_deleted: after === null || after === undefined - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - if !result["success"].(bool) { - t.Fatalf("Test failed: %v", result["error"]) - } - - assert.Equal(t, true, result["success"], "Space Delete should succeed") - assert.Equal(t, "temporary value", result["before"], "Value should exist before delete") - assert.Equal(t, true, result["is_deleted"], "Value should be deleted") -} - -// TestSpaceDeleteNonExistentKey tests deleting a non-existent key -func TestSpaceDeleteNonExistentKey(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Delete non-existent key should not throw error - ctx.space.Delete("non_existent_key"); - - return { success: true }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - assert.Equal(t, true, result["success"], "Delete non-existent key should not throw error") -} - -// TestSpaceWithNamespace tests using Space with namespace prefixes (like agent IDs) -func TestSpaceWithNamespace(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Simulate namespace pattern used in voucher assistant - const agentID = "workers.voucher"; - - // Set files_info with namespace - const filesInfo = [ - { - file_id: "abc123", - filename: "test.png", - content_type: "image/png", - file_type: "image", - source: "uploader" - } - ]; - ctx.space.Set(agentID + ":files_info", filesInfo); - - // Set current_file with namespace - const currentFile = { - file_id: "abc123", - filename: "test.png", - content_type: "image/png" - }; - ctx.space.Set(agentID + ":current_file", currentFile); - - // Read back with namespace - const retrievedFiles = ctx.space.Get(agentID + ":files_info"); - const retrievedCurrent = ctx.space.Get(agentID + ":current_file"); - - // Verify - if (!Array.isArray(retrievedFiles)) throw new Error("files_info should be array"); - if (retrievedFiles.length !== 1) throw new Error("files_info length mismatch"); - if (retrievedFiles[0].file_id !== "abc123") throw new Error("file_id mismatch"); - if (retrievedCurrent.filename !== "test.png") throw new Error("filename mismatch"); - - return { - success: true, - files_count: retrievedFiles.length, - file_id: retrievedFiles[0].file_id, - current_filename: retrievedCurrent.filename - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - if !result["success"].(bool) { - t.Fatalf("Test failed: %v", result["error"]) - } - - assert.Equal(t, true, result["success"], "Namespace operations should succeed") - assert.Equal(t, float64(1), result["files_count"], "Should have 1 file") - assert.Equal(t, "abc123", result["file_id"], "File ID should match") - assert.Equal(t, "test.png", result["current_filename"], "Filename should match") -} - -// TestSpaceComplexData tests Space with complex nested data structures -func TestSpaceComplexData(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Complex nested structure - const complexData = { - metadata: { - assistant_id: "tests.vision-helper", - has_files_info: true, - files_count: 2 - }, - files_info: [ - { - file_id: "file1", - filename: "image1.png", - content_type: "image/png", - metadata: { - size: 1024, - created: Date.now() - } - }, - { - file_id: "file2", - filename: "image2.jpg", - content_type: "image/jpeg", - metadata: { - size: 2048, - created: Date.now() - } - } - ], - tags: ["vision", "test", "multi-file"] - }; - - ctx.space.Set("complex_data", complexData); - - // Retrieve and verify - const retrieved = ctx.space.Get("complex_data"); - - if (!retrieved) throw new Error("Data not retrieved"); - if (!retrieved.metadata) throw new Error("Metadata missing"); - if (retrieved.metadata.files_count !== 2) throw new Error("Files count mismatch"); - if (!Array.isArray(retrieved.files_info)) throw new Error("files_info not array"); - if (retrieved.files_info.length !== 2) throw new Error("files_info length mismatch"); - if (!Array.isArray(retrieved.tags)) throw new Error("tags not array"); - if (retrieved.tags[0] !== "vision") throw new Error("tags mismatch"); - - return { - success: true, - files_count: retrieved.files_info.length, - first_file_id: retrieved.files_info[0].file_id, - second_filename: retrieved.files_info[1].filename, - tags: retrieved.tags - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - if !result["success"].(bool) { - t.Fatalf("Test failed: %v", result["error"]) - } - - assert.Equal(t, true, result["success"], "Complex data operations should succeed") - assert.Equal(t, float64(2), result["files_count"], "Should have 2 files") - assert.Equal(t, "file1", result["first_file_id"], "First file ID should match") - assert.Equal(t, "image2.jpg", result["second_filename"], "Second filename should match") -} - -// TestSpaceOverwrite tests overwriting existing values -func TestSpaceOverwrite(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Set initial value - ctx.space.Set("counter", 1); - const first = ctx.space.Get("counter"); - - // Overwrite with new value - ctx.space.Set("counter", 2); - const second = ctx.space.Get("counter"); - - // Overwrite again - ctx.space.Set("counter", 3); - const third = ctx.space.Get("counter"); - - return { - success: true, - first: first, - second: second, - third: third - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - if !result["success"].(bool) { - t.Fatalf("Test failed: %v", result["error"]) - } - - assert.Equal(t, true, result["success"], "Overwrite operations should succeed") - assert.Equal(t, float64(1), result["first"], "First value should be 1") - assert.Equal(t, float64(2), result["second"], "Second value should be 2") - assert.Equal(t, float64(3), result["third"], "Third value should be 3") -} - -// TestSpaceNoSpace tests behavior when Space is nil -func TestSpaceNoSpace(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: nil, // No Space - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // ctx.space should be undefined when Space is nil - const hasSpace = ctx.space !== undefined && ctx.space !== null; - - return { - success: true, - has_space: hasSpace - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - assert.Equal(t, true, result["success"], "Should handle nil Space gracefully") - assert.Equal(t, false, result["has_space"], "Should not have space when Space is nil") -} - -// TestSpaceErrorHandling tests error handling in Space methods -func TestSpaceErrorHandling(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - // Test Set without key - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Set without proper arguments should throw - ctx.space.Set(); - return { success: false, error: "Should have thrown" }; - } catch (error) { - return { success: true, caught_error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - assert.Equal(t, true, result["success"], "Should catch Set error") - - // Test Get without key - res, err = v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Get without key should throw - ctx.space.Get(); - return { success: false, error: "Should have thrown" }; - } catch (error) { - return { success: true, caught_error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok = res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - assert.Equal(t, true, result["success"], "Should catch Get error") - - // Test Delete without key - res, err = v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Delete without key should throw - ctx.space.Delete(); - return { success: false, error: "Should have thrown" }; - } catch (error) { - return { success: true, caught_error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok = res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - assert.Equal(t, true, result["success"], "Should catch Delete error") -} - -// TestSpaceGetDel tests ctx.space.GetDel method -func TestSpaceGetDel(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Set a one-time use value - ctx.space.Set("one_time_token", "secret_token_12345"); - - // Verify it exists before GetDel - const before = ctx.space.Get("one_time_token"); - if (before !== "secret_token_12345") throw new Error("Value not set"); - - // Use GetDel - should get value and delete automatically - const value = ctx.space.GetDel("one_time_token"); - - // Verify value was retrieved - if (value !== "secret_token_12345") throw new Error("GetDel returned wrong value"); - - // Verify key was deleted - const after = ctx.space.Get("one_time_token"); - if (after !== null && after !== undefined) throw new Error("Key should be deleted after GetDel"); - - return { - success: true, - value: value, - is_deleted: after === null || after === undefined - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - if !result["success"].(bool) { - t.Fatalf("Test failed: %v", result["error"]) - } - - assert.Equal(t, true, result["success"], "GetDel should succeed") - assert.Equal(t, "secret_token_12345", result["value"], "GetDel should return correct value") - assert.Equal(t, true, result["is_deleted"], "Key should be deleted after GetDel") -} - -// TestSpaceGetDelNonExistentKey tests GetDel on non-existent key -func TestSpaceGetDelNonExistentKey(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // GetDel on non-existent key should return null/undefined - const value = ctx.space.GetDel("non_existent_key"); - - return { - success: true, - value: value, - is_null: value === null || value === undefined - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - assert.Equal(t, true, result["success"], "GetDel on non-existent key should not throw") - assert.Equal(t, true, result["is_null"], "GetDel on non-existent key should return null") -} - -// TestSpaceGetDelComplexData tests GetDel with complex data structures -func TestSpaceGetDelComplexData(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Set complex file info data (like voucher assistant use case) - const filesInfo = [ - { - file_id: "file123", - filename: "invoice.pdf", - content_type: "application/pdf", - file_type: "pdf", - source: "uploader", - uploader_name: "__yao.attachment" - }, - { - file_id: "file456", - filename: "receipt.png", - content_type: "image/png", - file_type: "image", - source: "uploader", - uploader_name: "__yao.attachment" - } - ]; - - ctx.space.Set("workers.voucher:files_info", filesInfo); - - // Use GetDel to retrieve and clean up - const retrieved = ctx.space.GetDel("workers.voucher:files_info"); - - // Verify data integrity - if (!Array.isArray(retrieved)) throw new Error("Should be array"); - if (retrieved.length !== 2) throw new Error("Length mismatch"); - if (retrieved[0].file_id !== "file123") throw new Error("First file_id mismatch"); - if (retrieved[1].filename !== "receipt.png") throw new Error("Second filename mismatch"); - - // Verify it's deleted - const after = ctx.space.Get("workers.voucher:files_info"); - if (after !== null && after !== undefined) throw new Error("Should be deleted"); - - return { - success: true, - files_count: retrieved.length, - first_file_id: retrieved[0].file_id, - is_deleted: after === null || after === undefined - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - if !result["success"].(bool) { - t.Fatalf("Test failed: %v", result["error"]) - } - - assert.Equal(t, true, result["success"], "GetDel with complex data should succeed") - assert.Equal(t, float64(2), result["files_count"], "Should have 2 files") - assert.Equal(t, "file123", result["first_file_id"], "File ID should match") - assert.Equal(t, true, result["is_deleted"], "Should be deleted after GetDel") -} - -// TestSpaceGetDelMultipleCalls tests that GetDel only works once -func TestSpaceGetDelMultipleCalls(t *testing.T) { - test.Prepare(t, config.Conf) - defer test.Clean() - - ctx := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "en", - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - } - - res, err := v8.Call(v8.CallOptions{}, ` - function test(ctx) { - try { - // Set a value - ctx.space.Set("single_use", "use_me_once"); - - // First GetDel should work - const first = ctx.space.GetDel("single_use"); - if (first !== "use_me_once") throw new Error("First GetDel failed"); - - // Second GetDel should return null (already deleted) - const second = ctx.space.GetDel("single_use"); - if (second !== null && second !== undefined) throw new Error("Second GetDel should return null"); - - // Third GetDel should also return null - const third = ctx.space.GetDel("single_use"); - if (third !== null && third !== undefined) throw new Error("Third GetDel should return null"); - - return { - success: true, - first: first, - second_is_null: second === null || second === undefined, - third_is_null: third === null || third === undefined - }; - } catch (error) { - return { success: false, error: error.message }; - } - }`, ctx) - - if err != nil { - t.Fatalf("Call failed: %v", err) - } - - result, ok := res.(map[string]interface{}) - if !ok { - t.Fatalf("Expected map result, got %T", res) - } - - if !result["success"].(bool) { - t.Fatalf("Test failed: %v", result["error"]) - } - - assert.Equal(t, true, result["success"], "Multiple GetDel calls should work correctly") - assert.Equal(t, "use_me_once", result["first"], "First GetDel should return value") - assert.Equal(t, true, result["second_is_null"], "Second GetDel should return null") - assert.Equal(t, true, result["third_is_null"], "Third GetDel should return null") -} diff --git a/agent/context/jsapi_stress_test.go b/agent/context/jsapi_stress_test.go index 44424fca..2d016025 100644 --- a/agent/context/jsapi_stress_test.go +++ b/agent/context/jsapi_stress_test.go @@ -12,11 +12,20 @@ import ( "github.com/stretchr/testify/assert" v8 "github.com/yaoapp/gou/runtime/v8" "github.com/yaoapp/yao/agent/context" - "github.com/yaoapp/yao/agent/output/message" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/test" ) +// newStressTestContext creates a test context for stress testing +func newStressTestContext(chatID string) *context.Context { + ctx := context.New(stdContext.Background(), nil, chatID) + ctx.AssistantID = "test-assistant" + ctx.Referer = context.RefererAPI + stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) + ctx.Stack = stack + return ctx +} + // TestStressContextCreationAndRelease tests massive context creation and cleanup func TestStressContextCreationAndRelease(t *testing.T) { if testing.Short() { @@ -30,17 +39,7 @@ func TestStressContextCreationAndRelease(t *testing.T) { startMemory := getMemStats() for i := 0; i < iterations; i++ { - cxt := &context.Context{ - ChatID: fmt.Sprintf("chat-%d", i), - AssistantID: "test-assistant", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - } - - // Initialize stack and trace - cxt.Referer = context.RefererAPI - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newStressTestContext(fmt.Sprintf("chat-%d", i)) _, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -107,17 +106,7 @@ func TestStressTraceOperations(t *testing.T) { for i := 0; i < iterations; i++ { // Create new context for each iteration to avoid context cancellation issues - cxt := &context.Context{ - ChatID: fmt.Sprintf("stress-test-chat-%d", i), - AssistantID: "test-assistant", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - // Initialize stack and trace - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newStressTestContext(fmt.Sprintf("stress-test-chat-%d", i)) _, err := v8.Call(v8.CallOptions{}, fmt.Sprintf(` function test(ctx) { const trace = ctx.trace @@ -185,16 +174,7 @@ func TestStressMCPOperations(t *testing.T) { iterations := 500 - cxt := &context.Context{ - ChatID: "mcp-stress-test", - AssistantID: "test-assistant", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newStressTestContext("mcp-stress-test") startMemory := getMemStats() @@ -269,16 +249,7 @@ func TestStressConcurrentContexts(t *testing.T) { defer wg.Done() for i := 0; i < iterationsPerGoroutine; i++ { - cxt := &context.Context{ - ChatID: fmt.Sprintf("chat-%d-%d", goroutineID, i), - AssistantID: "test-assistant", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newStressTestContext(fmt.Sprintf("chat-%d-%d", goroutineID, i)) _, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -345,12 +316,8 @@ func TestStressNoOpTracePerformance(t *testing.T) { iterations := 1000 // Context without trace initialization (no-op trace) - cxt := &context.Context{ - ChatID: "noop-stress-test", - AssistantID: "test-assistant", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - } + cxt := context.New(stdContext.Background(), nil, "noop-stress-test") + cxt.AssistantID = "test-assistant" startMemory := getMemStats() startTime := time.Now() @@ -420,16 +387,7 @@ func TestStressReleasePatterns(t *testing.T) { startMemory := getMemStats() for i := 0; i < iterations; i++ { - cxt := &context.Context{ - ChatID: fmt.Sprintf("manual-%d", i), - AssistantID: "test-assistant", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newStressTestContext(fmt.Sprintf("manual-%d", i)) _, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -459,16 +417,7 @@ func TestStressReleasePatterns(t *testing.T) { startMemory := getMemStats() for i := 0; i < iterations; i++ { - cxt := &context.Context{ - ChatID: fmt.Sprintf("gc-%d", i), - AssistantID: "test-assistant", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newStressTestContext(fmt.Sprintf("gc-%d", i)) _, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -500,16 +449,7 @@ func TestStressReleasePatterns(t *testing.T) { startMemory := getMemStats() for i := 0; i < iterations; i++ { - cxt := &context.Context{ - ChatID: fmt.Sprintf("separate-%d", i), - AssistantID: "test-assistant", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newStressTestContext(fmt.Sprintf("separate-%d", i)) _, err := v8.Call(v8.CallOptions{}, ` function test(ctx) { @@ -546,16 +486,7 @@ func TestStressLongRunningTrace(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "long-running-test", - AssistantID: "test-assistant", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Referer: context.RefererAPI, - } - - stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{}) - cxt.Stack = stack + cxt := newStressTestContext("long-running-test") startMemory := getMemStats() operations := 100 diff --git a/agent/context/jsapi_test.go b/agent/context/jsapi_test.go index 698a221c..056747c9 100644 --- a/agent/context/jsapi_test.go +++ b/agent/context/jsapi_test.go @@ -10,7 +10,6 @@ import ( v8 "github.com/yaoapp/gou/runtime/v8" "github.com/yaoapp/gou/runtime/v8/bridge" "github.com/yaoapp/yao/agent/context" - "github.com/yaoapp/yao/agent/output/message" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/openapi/oauth/types" "github.com/yaoapp/yao/test" @@ -23,12 +22,8 @@ func TestJsValue(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "ChatID-123456", - AssistantID: "AssistantID-1234", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - } + cxt := context.New(stdContext.Background(), nil, "ChatID-123456") + cxt.AssistantID = "AssistantID-1234" v8.RegisterFunction("testContextJsvalue", testContextJsvalueEmbed) res, err := v8.Call(v8.CallOptions{}, ` @@ -91,12 +86,8 @@ func TestJsValueConcurrent(t *testing.T) { chatID := fmt.Sprintf("ChatID-%d-%d", routineID, j) assistantID := fmt.Sprintf("AssistantID-%d-%d", routineID, j) - cxt := &context.Context{ - ChatID: chatID, - AssistantID: assistantID, - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - } + cxt := context.New(stdContext.Background(), nil, chatID) + cxt.AssistantID = assistantID res, err := v8.Call(v8.CallOptions{}, ` function test(cxt) { @@ -150,12 +141,8 @@ func TestJsValueRegistrationAndCleanup(t *testing.T) { // Create multiple contexts and verify registration contextCount := 5 for i := 0; i < contextCount; i++ { - cxt := &context.Context{ - ChatID: fmt.Sprintf("ChatID-%d", i), - AssistantID: fmt.Sprintf("AssistantID-%d", i), - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - } + cxt := context.New(stdContext.Background(), nil, fmt.Sprintf("ChatID-%d", i)) + cxt.AssistantID = fmt.Sprintf("AssistantID-%d", i) _, err := v8.Call(v8.CallOptions{}, ` function test(cxt) { @@ -219,43 +206,41 @@ func TestJsValueAllFields(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Locale: "zh-cn", - Theme: "dark", - Context: stdContext.Background(), - Client: context.Client{ - Type: "web", - UserAgent: "Mozilla/5.0", - IP: "127.0.0.1", - }, - Referer: "api", - Accept: "cui-web", - Route: "/dashboard/home", - Metadata: map[string]interface{}{ - "key1": "value1", - "key2": 123, - "key3": true, - }, - Authorized: &types.AuthorizedInfo{ - Subject: "test-user", - ClientID: "test-client", - UserID: "user-123", - TeamID: "team-456", - TenantID: "tenant-789", - Constraints: types.DataConstraints{ - OwnerOnly: true, - CreatorOnly: false, - TeamOnly: true, - Extra: map[string]interface{}{ - "department": "engineering", - "region": "us-west", - }, + authInfo := &types.AuthorizedInfo{ + Subject: "test-user", + ClientID: "test-client", + UserID: "user-123", + TeamID: "team-456", + TenantID: "tenant-789", + Constraints: types.DataConstraints{ + OwnerOnly: true, + CreatorOnly: false, + TeamOnly: true, + Extra: map[string]interface{}{ + "department": "engineering", + "region": "us-west", }, }, } + cxt := context.New(stdContext.Background(), authInfo, "test-chat-id") + cxt.AssistantID = "test-assistant-id" + cxt.Locale = "zh-cn" + cxt.Theme = "dark" + cxt.Client = context.Client{ + Type: "web", + UserAgent: "Mozilla/5.0", + IP: "127.0.0.1", + } + cxt.Referer = "api" + cxt.Accept = "cui-web" + cxt.Route = "/dashboard/home" + cxt.Metadata = map[string]interface{}{ + "key1": "value1", + "key2": 123, + "key3": true, + } + v8.RegisterFunction("testAllFields", testAllFieldsEmbed) res, err := v8.Call(v8.CallOptions{}, ` function test(cxt) { @@ -404,14 +389,10 @@ func TestJsValueTrace(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Stack: &context.Stack{ - TraceID: "test-trace-id", - }, - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), + cxt := context.New(stdContext.Background(), nil, "test-chat-id") + cxt.AssistantID = "test-assistant-id" + cxt.Stack = &context.Stack{ + TraceID: "test-trace-id", } res, err := v8.Call(v8.CallOptions{}, ` @@ -470,21 +451,17 @@ func TestJsValueAuthorizedAndMetadata(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Authorized: &types.AuthorizedInfo{ - UserID: "user-123", - TenantID: "tenant-456", - ClientID: "client-789", - }, - Metadata: map[string]interface{}{ - "request_id": "req-001", - "source": "api", - "version": "1.0.0", - }, + authInfo := &types.AuthorizedInfo{ + UserID: "user-123", + TenantID: "tenant-456", + ClientID: "client-789", + } + cxt := context.New(stdContext.Background(), authInfo, "test-chat-id") + cxt.AssistantID = "test-assistant-id" + cxt.Metadata = map[string]interface{}{ + "request_id": "req-001", + "source": "api", + "version": "1.0.0", } v8.RegisterFunction("testAuthorizedMetadata", testAuthorizedMetadataEmbed) @@ -573,14 +550,9 @@ func TestJsValueAuthorizedNil(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() - cxt := &context.Context{ - ChatID: "test-chat-id", - AssistantID: "test-assistant-id", - Context: stdContext.Background(), - IDGenerator: message.NewIDGenerator(), - Authorized: nil, // Explicitly nil - Metadata: nil, // Explicitly nil (should be empty object) - } + cxt := context.New(stdContext.Background(), nil, "test-chat-id") + cxt.AssistantID = "test-assistant-id" + cxt.Metadata = nil // Explicitly nil (should be empty object) res, err := v8.Call(v8.CallOptions{}, ` function test(cxt) { diff --git a/agent/context/mcp_test.go b/agent/context/mcp_test.go index 0604d8df..73201c90 100644 --- a/agent/context/mcp_test.go +++ b/agent/context/mcp_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/yaoapp/gou/mcp/types" - "github.com/yaoapp/gou/plan" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/test" @@ -13,15 +12,10 @@ import ( // newTestMCPContext creates a test context func newTestMCPContext() *context.Context { - ctx := &context.Context{ - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - ID: "test-context", - ChatID: "test-chat", - AssistantID: "test-assistant", - Locale: "en", - Referer: context.RefererAPI, - } + ctx := context.New(stdContext.Background(), nil, "test-chat") + ctx.AssistantID = "test-assistant" + ctx.Locale = "en" + ctx.Referer = context.RefererAPI // Initialize stack and trace stack, traceID, _ := context.EnterStack(ctx, "test-assistant", &context.Options{}) diff --git a/agent/context/types.go b/agent/context/types.go index 025f5eab..d567a72f 100644 --- a/agent/context/types.go +++ b/agent/context/types.go @@ -6,8 +6,8 @@ import ( "time" "github.com/yaoapp/gou/connector/openai" - "github.com/yaoapp/gou/plan" "github.com/yaoapp/gou/store" + "github.com/yaoapp/yao/agent/memory" "github.com/yaoapp/yao/agent/output" "github.com/yaoapp/yao/agent/output/message" "github.com/yaoapp/yao/openapi/oauth/types" @@ -226,7 +226,7 @@ type Context struct { // External ID string `json:"id"` // Context ID for external interrupt identification - Space plan.Space `json:"-"` // Shared data space, it will be used to share data between the request and the call + Memory *memory.Memory `json:"-"` // Agent memory with four spaces: User, Team, Chat, Context Cache store.Store `json:"-"` // Cache store, it will be used to store the message cache, default is "__yao.agent.cache" Stack *Stack `json:"-"` // Stack, current active stack of the request Stacks map[string]*Stack `json:"-"` // Stacks, all stacks in this request (for trace logging) diff --git a/agent/memory/interfaces.go b/agent/memory/interfaces.go new file mode 100644 index 00000000..e351832e --- /dev/null +++ b/agent/memory/interfaces.go @@ -0,0 +1,58 @@ +package memory + +import "github.com/yaoapp/gou/store" + +// Manager defines the interface for managing agent memory +type Manager interface { + // Memory returns the memory instance for given identifiers + Memory(userID, teamID, chatID, contextID string) (*Memory, error) + + // Close closes all stores and releases resources + Close() error +} + +// Accessor defines the interface for accessing memory from agent context +// This is the primary interface used by agent hooks and tools +type Accessor interface { + // User returns the user-level memory namespace + User() NamespaceAccessor + + // Team returns the team-level memory namespace + Team() NamespaceAccessor + + // Chat returns the chat-level memory namespace + Chat() NamespaceAccessor + + // Context returns the context-level memory namespace + Context() NamespaceAccessor + + // Space returns a memory namespace by space type + Space(space Space) NamespaceAccessor + + // Stats returns memory statistics + Stats() *Stats +} + +// NamespaceAccessor defines the interface for accessing a single memory namespace +// Embeds store.Store for all KV and list operations +type NamespaceAccessor interface { + store.Store + + // GetID returns the namespace identifier (user_id, team_id, chat_id, or context_id) + GetID() string + + // GetSpace returns the space type of this namespace + GetSpace() Space + + // Stats returns statistics for this namespace + Stats() *NamespaceStats +} + +// Factory defines the interface for creating memory instances +type Factory interface { + // Create creates a new memory instance with the given configuration + Create(config *Config) (Manager, error) + + // CreateWithDefaults creates a new memory instance with default configuration + CreateWithDefaults() (Manager, error) +} diff --git a/agent/memory/manager.go b/agent/memory/manager.go new file mode 100644 index 00000000..e68e556a --- /dev/null +++ b/agent/memory/manager.go @@ -0,0 +1,98 @@ +package memory + +import ( + "sync" +) + +// Global manager instance +var globalManager Manager + +// Init initializes the global memory manager with the given configuration +// Called by agent.Load() after loading agent DSL +func Init(config *Config) { + globalManager = NewManager(config) +} + +// GetMemory returns a memory instance for the given identifiers using the global manager +// This is the main entry point for creating Memory instances from agent/context +func GetMemory(userID, teamID, chatID, contextID string) (*Memory, error) { + if globalManager == nil { + // Initialize with defaults if not configured + globalManager = NewManagerWithDefaults() + } + return globalManager.Memory(userID, teamID, chatID, contextID) +} + +// Close closes the global manager and releases resources +func Close() error { + if globalManager != nil { + err := globalManager.Close() + globalManager = nil + return err + } + return nil +} + +// DefaultManager is the default memory manager implementation +type DefaultManager struct { + config *Config + memories sync.Map // map[string]*Memory, key is composite of userID:teamID:chatID:contextID +} + +// NewManager creates a new memory manager with the given configuration +func NewManager(config *Config) Manager { + if config == nil { + config = &Config{} + } + return &DefaultManager{ + config: config, + } +} + +// NewManagerWithDefaults creates a new memory manager with default configuration +func NewManagerWithDefaults() Manager { + return NewManager(&Config{ + User: DefaultUserStore, + Team: DefaultTeamStore, + Chat: DefaultChatStore, + Context: DefaultContextStore, + }) +} + +// memoryKey generates a unique key for the memory instance +func memoryKey(userID, teamID, chatID, contextID string) string { + return userID + ":" + teamID + ":" + chatID + ":" + contextID +} + +// Memory returns the memory instance for given identifiers +func (m *DefaultManager) Memory(userID, teamID, chatID, contextID string) (*Memory, error) { + key := memoryKey(userID, teamID, chatID, contextID) + + // Check if memory already exists + if val, ok := m.memories.Load(key); ok { + return val.(*Memory), nil + } + + // Create new memory instance + mem, err := New(m.config, userID, teamID, chatID, contextID) + if err != nil { + return nil, err + } + + // Store and return (use LoadOrStore for thread safety) + actual, _ := m.memories.LoadOrStore(key, mem) + return actual.(*Memory), nil +} + +// Close closes all stores and releases resources +func (m *DefaultManager) Close() error { + // Clear all cached memory instances + m.memories.Range(func(key, value interface{}) bool { + m.memories.Delete(key) + return true + }) + return nil +} + +// Ensure DefaultManager implements Manager +var _ Manager = (*DefaultManager)(nil) diff --git a/agent/memory/memory.go b/agent/memory/memory.go new file mode 100644 index 00000000..7a40d54a --- /dev/null +++ b/agent/memory/memory.go @@ -0,0 +1,185 @@ +package memory + +import ( + "fmt" + "time" + + "github.com/yaoapp/gou/store" +) + +// Default TTL values for each memory space +const ( + DefaultUserTTL = 0 // No expiration for user-level memory + DefaultTeamTTL = 0 // No expiration for team-level memory + DefaultChatTTL = 24 * time.Hour // 24 hours for chat-level memory + DefaultContextTTL = 30 * time.Minute // 30 minutes for context-level memory +) + +// New creates a new Memory instance with the given configuration and identifiers +func New(cfg *Config, userID, teamID, chatID, contextID string) (*Memory, error) { + if cfg == nil { + cfg = &Config{} + } + + m := &Memory{ + UserID: userID, + TeamID: teamID, + ChatID: chatID, + ContextID: contextID, + Config: cfg, + } + + // Initialize user namespace + if userID != "" { + ns, err := newNamespace(SpaceUser, userID, cfg.User, DefaultUserTTL) + if err != nil { + return nil, fmt.Errorf("failed to create user namespace: %w", err) + } + m.User = ns + } + + // Initialize team namespace + if teamID != "" { + ns, err := newNamespace(SpaceTeam, teamID, cfg.Team, DefaultTeamTTL) + if err != nil { + return nil, fmt.Errorf("failed to create team namespace: %w", err) + } + m.Team = ns + } + + // Initialize chat namespace + if chatID != "" { + ns, err := newNamespace(SpaceChat, chatID, cfg.Chat, DefaultChatTTL) + if err != nil { + return nil, fmt.Errorf("failed to create chat namespace: %w", err) + } + m.Chat = ns + } + + // Initialize context namespace + if contextID != "" { + ns, err := newNamespace(SpaceContext, contextID, cfg.Context, DefaultContextTTL) + if err != nil { + return nil, fmt.Errorf("failed to create context namespace: %w", err) + } + m.Context = ns + } + + return m, nil +} + +// newNamespace creates a new Namespace with the given parameters +func newNamespace(space Space, id, storeID string, defaultTTL time.Duration) (*Namespace, error) { + // Use default store ID if not specified + if storeID == "" { + switch space { + case SpaceUser: + storeID = DefaultUserStore + case SpaceTeam: + storeID = DefaultTeamStore + case SpaceChat: + storeID = DefaultChatStore + case SpaceContext: + storeID = DefaultContextStore + } + } + + // Get store instance + s, err := store.Get(storeID) + if err != nil { + return nil, fmt.Errorf("failed to get store %s: %w", storeID, err) + } + + return &Namespace{ + Space: space, + ID: id, + Store: s, + StoreID: storeID, + Prefix: fmt.Sprintf("%s:%s:", space, id), + Default: defaultTTL, + }, nil +} + +// GetUser returns the user-level memory namespace accessor +func (m *Memory) GetUser() NamespaceAccessor { + if m.User == nil { + return nil + } + return m.User +} + +// GetTeam returns the team-level memory namespace accessor +func (m *Memory) GetTeam() NamespaceAccessor { + if m.Team == nil { + return nil + } + return m.Team +} + +// GetChat returns the chat-level memory namespace accessor +func (m *Memory) GetChat() NamespaceAccessor { + if m.Chat == nil { + return nil + } + return m.Chat +} + +// GetContext returns the context-level memory namespace accessor +func (m *Memory) GetContext() NamespaceAccessor { + if m.Context == nil { + return nil + } + return m.Context +} + +// GetSpace returns a memory namespace by space type +func (m *Memory) GetSpace(space Space) NamespaceAccessor { + switch space { + case SpaceUser: + return m.GetUser() + case SpaceTeam: + return m.GetTeam() + case SpaceChat: + return m.GetChat() + case SpaceContext: + return m.GetContext() + default: + return nil + } +} + +// GetStats returns memory statistics for all namespaces +func (m *Memory) GetStats() *Stats { + stats := &Stats{} + + if m.User != nil { + stats.User = m.User.Stats() + } + if m.Team != nil { + stats.Team = m.Team.Stats() + } + if m.Chat != nil { + stats.Chat = m.Chat.Stats() + } + if m.Context != nil { + stats.Context = m.Context.Stats() + } + + return stats +} + +// Clear clears all memory in all namespaces for this memory instance +func (m *Memory) Clear() { + if m.User != nil { + m.User.Clear() + } + if m.Team != nil { + m.Team.Clear() + } + if m.Chat != nil { + m.Chat.Clear() + } + if m.Context != nil { + m.Context.Clear() + } +} diff --git a/agent/memory/memory_test.go b/agent/memory/memory_test.go new file mode 100644 index 00000000..cf238c5c --- /dev/null +++ b/agent/memory/memory_test.go @@ -0,0 +1,391 @@ +package memory_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yaoapp/yao/agent/memory" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/test" +) + +func TestMemoryNew(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + // Create memory with default stores + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + require.NotNil(t, mem) + + // Verify all namespaces are initialized + assert.NotNil(t, mem.User) + assert.NotNil(t, mem.Team) + assert.NotNil(t, mem.Chat) + assert.NotNil(t, mem.Context) + + // Verify IDs + assert.Equal(t, "user1", mem.UserID) + assert.Equal(t, "team1", mem.TeamID) + assert.Equal(t, "chat1", mem.ChatID) + assert.Equal(t, "ctx1", mem.ContextID) +} + +func TestMemoryPartialIDs(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + // Create memory with only user and chat + mem, err := memory.New(nil, "user1", "", "chat1", "") + require.NoError(t, err) + require.NotNil(t, mem) + + // Only user and chat namespaces should be initialized + assert.NotNil(t, mem.User) + assert.Nil(t, mem.Team) + assert.NotNil(t, mem.Chat) + assert.Nil(t, mem.Context) +} + +func TestNamespaceBasicOperations(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + // Test User namespace + t.Run("User namespace", func(t *testing.T) { + ns := mem.GetUser() + require.NotNil(t, ns) + + // Set and Get + err := ns.Set("name", "John", 0) + require.NoError(t, err) + + val, ok := ns.Get("name") + assert.True(t, ok) + assert.Equal(t, "John", val) + + // Has + assert.True(t, ns.Has("name")) + assert.False(t, ns.Has("nonexistent")) + + // Del + err = ns.Del("name") + require.NoError(t, err) + assert.False(t, ns.Has("name")) + }) + + // Test Team namespace + t.Run("Team namespace", func(t *testing.T) { + ns := mem.GetTeam() + require.NotNil(t, ns) + + err := ns.Set("setting", "value", 0) + require.NoError(t, err) + + val, ok := ns.Get("setting") + assert.True(t, ok) + assert.Equal(t, "value", val) + }) + + // Test Chat namespace + t.Run("Chat namespace", func(t *testing.T) { + ns := mem.GetChat() + require.NotNil(t, ns) + + err := ns.Set("topic", "AI", 0) + require.NoError(t, err) + + val, ok := ns.Get("topic") + assert.True(t, ok) + assert.Equal(t, "AI", val) + }) + + // Test Context namespace + t.Run("Context namespace", func(t *testing.T) { + ns := mem.GetContext() + require.NotNil(t, ns) + + err := ns.Set("temp", "data", 0) + require.NoError(t, err) + + val, ok := ns.Get("temp") + assert.True(t, ok) + assert.Equal(t, "data", val) + }) +} + +func TestNamespaceIsolation(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + // Create two memory instances with different user IDs + mem1, err := memory.New(nil, "user1", "", "", "") + require.NoError(t, err) + + mem2, err := memory.New(nil, "user2", "", "", "") + require.NoError(t, err) + + // Set value in user1's namespace + err = mem1.GetUser().Set("key", "user1_value", 0) + require.NoError(t, err) + + // Set value in user2's namespace + err = mem2.GetUser().Set("key", "user2_value", 0) + require.NoError(t, err) + + // Verify isolation + val1, ok := mem1.GetUser().Get("key") + assert.True(t, ok) + assert.Equal(t, "user1_value", val1) + + val2, ok := mem2.GetUser().Get("key") + assert.True(t, ok) + assert.Equal(t, "user2_value", val2) +} + +func TestNamespaceIncrDecr(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "", "", "") + require.NoError(t, err) + + ns := mem.GetUser() + + // Incr on non-existent key + val, err := ns.Incr("counter", 1) + require.NoError(t, err) + assert.Equal(t, int64(1), val) + + // Incr again + val, err = ns.Incr("counter", 5) + require.NoError(t, err) + assert.Equal(t, int64(6), val) + + // Decr + val, err = ns.Decr("counter", 2) + require.NoError(t, err) + assert.Equal(t, int64(4), val) +} + +func TestNamespaceListOperations(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "", "", "") + require.NoError(t, err) + + ns := mem.GetUser() + + // Push values + err = ns.Push("list", "a", "b", "c") + require.NoError(t, err) + + // ArrayLen + assert.Equal(t, 3, ns.ArrayLen("list")) + + // ArrayAll + all, err := ns.ArrayAll("list") + require.NoError(t, err) + assert.Len(t, all, 3) + + // Pop from end + val, err := ns.Pop("list", 1) + require.NoError(t, err) + assert.Equal(t, "c", val) + + // ArrayLen after pop + assert.Equal(t, 2, ns.ArrayLen("list")) +} + +func TestNamespaceSetOperations(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "", "", "") + require.NoError(t, err) + + ns := mem.GetUser() + + // AddToSet + err = ns.AddToSet("tags", "go", "rust", "go") // "go" should only appear once + require.NoError(t, err) + + all, err := ns.ArrayAll("tags") + require.NoError(t, err) + assert.Len(t, all, 2) // Only "go" and "rust" +} + +func TestNamespaceTTL(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "", "", "", "ctx1") + require.NoError(t, err) + + ns := mem.GetContext() + + // Set with short TTL + err = ns.Set("temp", "value", 100*time.Millisecond) + require.NoError(t, err) + + // Should exist immediately + val, ok := ns.Get("temp") + assert.True(t, ok) + assert.Equal(t, "value", val) + + // Wait for expiration + time.Sleep(150 * time.Millisecond) + + // Should be expired + _, ok = ns.Get("temp") + assert.False(t, ok) +} + +func TestMemoryClear(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + // Set values in all namespaces + mem.GetUser().Set("key", "user_value", 0) + mem.GetTeam().Set("key", "team_value", 0) + mem.GetChat().Set("key", "chat_value", 0) + mem.GetContext().Set("key", "ctx_value", 0) + + // Clear all + mem.Clear() + + // All should be empty + _, ok := mem.GetUser().Get("key") + assert.False(t, ok) + _, ok = mem.GetTeam().Get("key") + assert.False(t, ok) + _, ok = mem.GetChat().Get("key") + assert.False(t, ok) + _, ok = mem.GetContext().Get("key") + assert.False(t, ok) +} + +func TestMemoryStats(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + // Set some values + mem.GetUser().Set("k1", "v1", 0) + mem.GetUser().Set("k2", "v2", 0) + mem.GetTeam().Set("k1", "v1", 0) + + stats := mem.GetStats() + require.NotNil(t, stats) + + assert.Equal(t, 2, stats.User.KeyCount) + assert.Equal(t, 1, stats.Team.KeyCount) + assert.Equal(t, 0, stats.Chat.KeyCount) + assert.Equal(t, 0, stats.Context.KeyCount) +} + +func TestManager(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mgr := memory.NewManagerWithDefaults() + defer mgr.Close() + + // Get memory instance + mem1, err := mgr.Memory("user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + require.NotNil(t, mem1) + + // Set a value + err = mem1.GetUser().Set("key", "value", 0) + require.NoError(t, err) + + // Get same memory instance again + mem2, err := mgr.Memory("user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + // Should be the same instance (cached) + val, ok := mem2.GetUser().Get("key") + assert.True(t, ok) + assert.Equal(t, "value", val) +} + +func TestGetSpace(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1") + require.NoError(t, err) + + // Test GetSpace + assert.NotNil(t, mem.GetSpace(memory.SpaceUser)) + assert.NotNil(t, mem.GetSpace(memory.SpaceTeam)) + assert.NotNil(t, mem.GetSpace(memory.SpaceChat)) + assert.NotNil(t, mem.GetSpace(memory.SpaceContext)) + + // Invalid space + assert.Nil(t, mem.GetSpace(memory.Space("invalid"))) +} + +func TestNamespaceGetMultiSetMulti(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "", "", "") + require.NoError(t, err) + + ns := mem.GetUser() + + // SetMulti + ns.SetMulti(map[string]interface{}{ + "a": 1, + "b": 2, + "c": 3, + }, 0) + + // GetMulti + result := ns.GetMulti([]string{"a", "b", "c"}) + assert.Equal(t, 1, result["a"]) + assert.Equal(t, 2, result["b"]) + assert.Equal(t, 3, result["c"]) + + // DelMulti + ns.DelMulti([]string{"a", "b"}) + assert.False(t, ns.Has("a")) + assert.False(t, ns.Has("b")) + assert.True(t, ns.Has("c")) +} + +func TestNamespaceGetDel(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + mem, err := memory.New(nil, "user1", "", "", "") + require.NoError(t, err) + + ns := mem.GetUser() + + // Set a value + err = ns.Set("key", "value", 0) + require.NoError(t, err) + + // GetDel + val, ok := ns.GetDel("key") + assert.True(t, ok) + assert.Equal(t, "value", val) + + // Should be deleted + _, ok = ns.Get("key") + assert.False(t, ok) +} diff --git a/agent/memory/namespace.go b/agent/memory/namespace.go new file mode 100644 index 00000000..10cc843a --- /dev/null +++ b/agent/memory/namespace.go @@ -0,0 +1,238 @@ +package memory + +import ( + "time" +) + +// Ensure Namespace implements NamespaceAccessor +var _ NamespaceAccessor = (*Namespace)(nil) + +// GetID returns the namespace identifier +func (ns *Namespace) GetID() string { + return ns.ID +} + +// GetSpace returns the space type of this namespace +func (ns *Namespace) GetSpace() Space { + return ns.Space +} + +// prefixKey adds the namespace prefix to a key +func (ns *Namespace) prefixKey(key string) string { + return ns.Prefix + key +} + +// Get retrieves a value by key +func (ns *Namespace) Get(key string) (interface{}, bool) { + return ns.Store.Get(ns.prefixKey(key)) +} + +// Set stores a value with the default TTL for this namespace +func (ns *Namespace) Set(key string, value interface{}, ttl time.Duration) error { + if ttl == 0 { + ttl = ns.Default + } + return ns.Store.Set(ns.prefixKey(key), value, ttl) +} + +// Has checks if a key exists +func (ns *Namespace) Has(key string) bool { + return ns.Store.Has(ns.prefixKey(key)) +} + +// Del deletes a key (supports wildcards) +func (ns *Namespace) Del(key string) error { + return ns.Store.Del(ns.prefixKey(key)) +} + +// Keys returns all keys in this namespace +func (ns *Namespace) Keys() []string { + allKeys := ns.Store.Keys() + prefixLen := len(ns.Prefix) + + // Filter keys that belong to this namespace + var result []string + for _, key := range allKeys { + if len(key) >= prefixLen && key[:prefixLen] == ns.Prefix { + result = append(result, key[prefixLen:]) + } + } + return result +} + +// Len returns the number of keys in this namespace +func (ns *Namespace) Len() int { + return len(ns.Keys()) +} + +// Clear deletes all keys in this namespace +func (ns *Namespace) Clear() { + ns.Store.Del(ns.Prefix + "*") +} + +// GetSet retrieves a value and sets a new value if not exists +func (ns *Namespace) GetSet(key string, ttl time.Duration, getValue func(key string) (interface{}, error)) (interface{}, error) { + if ttl == 0 { + ttl = ns.Default + } + return ns.Store.GetSet(ns.prefixKey(key), ttl, getValue) +} + +// GetDel retrieves a value and deletes it atomically +func (ns *Namespace) GetDel(key string) (interface{}, bool) { + return ns.Store.GetDel(ns.prefixKey(key)) +} + +// GetMulti retrieves multiple values by keys +func (ns *Namespace) GetMulti(keys []string) map[string]interface{} { + prefixedKeys := make([]string, len(keys)) + for i, key := range keys { + prefixedKeys[i] = ns.prefixKey(key) + } + result := ns.Store.GetMulti(prefixedKeys) + + // Remove prefix from result keys + unprefixed := make(map[string]interface{}) + prefixLen := len(ns.Prefix) + for k, v := range result { + if len(k) > prefixLen { + unprefixed[k[prefixLen:]] = v + } else { + unprefixed[k] = v + } + } + return unprefixed +} + +// SetMulti stores multiple values +func (ns *Namespace) SetMulti(values map[string]interface{}, ttl time.Duration) { + if ttl == 0 { + ttl = ns.Default + } + prefixed := make(map[string]interface{}) + for k, v := range values { + prefixed[ns.prefixKey(k)] = v + } + ns.Store.SetMulti(prefixed, ttl) +} + +// DelMulti deletes multiple keys +func (ns *Namespace) DelMulti(keys []string) { + prefixedKeys := make([]string, len(keys)) + for i, key := range keys { + prefixedKeys[i] = ns.prefixKey(key) + } + ns.Store.DelMulti(prefixedKeys) +} + +// GetSetMulti retrieves multiple values and sets new values if not exists +func (ns *Namespace) GetSetMulti(keys []string, ttl time.Duration, getValue func(key string) (interface{}, error)) map[string]interface{} { + if ttl == 0 { + ttl = ns.Default + } + prefixedKeys := make([]string, len(keys)) + for i, key := range keys { + prefixedKeys[i] = ns.prefixKey(key) + } + result := ns.Store.GetSetMulti(prefixedKeys, ttl, getValue) + + // Remove prefix from result keys + unprefixed := make(map[string]interface{}) + prefixLen := len(ns.Prefix) + for k, v := range result { + if len(k) > prefixLen { + unprefixed[k[prefixLen:]] = v + } else { + unprefixed[k] = v + } + } + return unprefixed +} + +// Incr increments a numeric value +func (ns *Namespace) Incr(key string, delta int64) (int64, error) { + return ns.Store.Incr(ns.prefixKey(key), delta) +} + +// Decr decrements a numeric value +func (ns *Namespace) Decr(key string, delta int64) (int64, error) { + return ns.Store.Decr(ns.prefixKey(key), delta) +} + +// Push appends values to a list +func (ns *Namespace) Push(key string, values ...interface{}) error { + return ns.Store.Push(ns.prefixKey(key), values...) +} + +// Pop removes and returns an element from a list +func (ns *Namespace) Pop(key string, position int) (interface{}, error) { + return ns.Store.Pop(ns.prefixKey(key), position) +} + +// Pull removes the first occurrence of a value from a list +func (ns *Namespace) Pull(key string, value interface{}) error { + return ns.Store.Pull(ns.prefixKey(key), value) +} + +// PullAll removes all occurrences of values from a list +func (ns *Namespace) PullAll(key string, values []interface{}) error { + return ns.Store.PullAll(ns.prefixKey(key), values) +} + +// AddToSet adds values to a set (no duplicates) +func (ns *Namespace) AddToSet(key string, values ...interface{}) error { + return ns.Store.AddToSet(ns.prefixKey(key), values...) +} + +// ArrayLen returns the length of a list +func (ns *Namespace) ArrayLen(key string) int { + return ns.Store.ArrayLen(ns.prefixKey(key)) +} + +// ArrayGet retrieves an element from a list by index +func (ns *Namespace) ArrayGet(key string, index int) (interface{}, error) { + return ns.Store.ArrayGet(ns.prefixKey(key), index) +} + +// ArraySet sets an element in a list by index +func (ns *Namespace) ArraySet(key string, index int, value interface{}) error { + return ns.Store.ArraySet(ns.prefixKey(key), index, value) +} + +// ArraySlice returns a slice of a list +func (ns *Namespace) ArraySlice(key string, skip, limit int) ([]interface{}, error) { + return ns.Store.ArraySlice(ns.prefixKey(key), skip, limit) +} + +// ArrayPage returns a page of a list +func (ns *Namespace) ArrayPage(key string, page, pageSize int) ([]interface{}, error) { + return ns.Store.ArrayPage(ns.prefixKey(key), page, pageSize) +} + +// ArrayAll returns all elements of a list +func (ns *Namespace) ArrayAll(key string) ([]interface{}, error) { + return ns.Store.ArrayAll(ns.prefixKey(key)) +} + +// Stats returns statistics for this namespace +func (ns *Namespace) Stats() *NamespaceStats { + return &NamespaceStats{ + Space: ns.Space, + ID: ns.ID, + KeyCount: ns.Len(), + StoreID: ns.StoreID, + } +} + +// Snapshot returns all key-value pairs in this namespace +// Used for recovery/resume functionality +func (ns *Namespace) Snapshot() map[string]interface{} { + keys := ns.Keys() + snapshot := make(map[string]interface{}, len(keys)) + for _, key := range keys { + if value, ok := ns.Get(key); ok { + snapshot[key] = value + } + } + return snapshot +} diff --git a/agent/memory/types.go b/agent/memory/types.go new file mode 100644 index 00000000..bd957362 --- /dev/null +++ b/agent/memory/types.go @@ -0,0 +1,99 @@ +package memory + +import ( + "time" + + "github.com/yaoapp/gou/store" +) + +// Space defines the memory space type +type Space string + +const ( + // SpaceUser user-level memory, persists across all chats for a user + // Use case: user preferences, long-term knowledge, personal settings + SpaceUser Space = "user" + + // SpaceTeam team-level memory, shared across all users in a team + // Use case: team knowledge, shared settings, collaborative data + SpaceTeam Space = "team" + + // SpaceChat chat-level memory, persists within a single chat session + // Use case: conversation context, chat-specific settings, accumulated knowledge + SpaceChat Space = "chat" + + // SpaceContext context-level memory, temporary within a single request context + // Use case: intermediate results, temporary variables, request-scoped cache + SpaceContext Space = "context" +) + +// Config represents the memory configuration +// Each field is a Store ID referencing gou/store, empty string uses built-in default +// All spaces use xun-based storage by default for persistence and reliability +type Config struct { + User string `json:"user,omitempty" yaml:"user,omitempty"` // Store ID for user-level memory (default: xun-based) + Team string `json:"team,omitempty" yaml:"team,omitempty"` // Store ID for team-level memory (default: xun-based) + Chat string `json:"chat,omitempty" yaml:"chat,omitempty"` // Store ID for chat-level memory (default: xun-based) + Context string `json:"context,omitempty" yaml:"context,omitempty"` // Store ID for context-level memory (default: xun-based, shorter TTL) +} + +// DefaultStoreID constants for built-in stores +const ( + DefaultUserStore = "__yao.agent.memory.user" + DefaultTeamStore = "__yao.agent.memory.team" + DefaultChatStore = "__yao.agent.memory.chat" + DefaultContextStore = "__yao.agent.memory.context" +) + +// Entry represents a memory entry +type Entry struct { + Key string `json:"key"` + Value interface{} `json:"value"` + Space Space `json:"space"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + TTL time.Duration `json:"ttl,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` +} + +// Namespace represents a memory namespace for a specific space +type Namespace struct { + Space Space `json:"space"` + ID string `json:"id"` // UserID, TeamID, ChatID, or ContextID depending on space + Store store.Store `json:"-"` // Underlying store + StoreID string `json:"-"` // Store ID + Prefix string `json:"-"` // Computed key prefix (e.g., "user:123:", "team:456:") + Default time.Duration `json:"-"` // Default TTL for this namespace +} + +// Memory represents the complete memory system for an agent +// It manages four separate namespaces: User, Team, Chat, and Context +type Memory struct { + UserID string `json:"user_id"` + TeamID string `json:"team_id"` + ChatID string `json:"chat_id"` + ContextID string `json:"context_id"` + + User *Namespace `json:"-"` // User-level memory namespace + Team *Namespace `json:"-"` // Team-level memory namespace + Chat *Namespace `json:"-"` // Chat-level memory namespace + Context *Namespace `json:"-"` // Context-level memory namespace + Config *Config `json:"-"` // Memory configuration +} + +// Stats represents memory statistics +type Stats struct { + User *NamespaceStats `json:"user,omitempty"` + Team *NamespaceStats `json:"team,omitempty"` + Chat *NamespaceStats `json:"chat,omitempty"` + Context *NamespaceStats `json:"context,omitempty"` +} + +// NamespaceStats represents statistics for a single memory namespace +type NamespaceStats struct { + Space Space `json:"space"` + ID string `json:"id"` + KeyCount int `json:"key_count"` + StoreID string `json:"store_id"` +} diff --git a/agent/search/jsapi_test.go b/agent/search/jsapi_test.go index c7c34fa2..2cd8e868 100644 --- a/agent/search/jsapi_test.go +++ b/agent/search/jsapi_test.go @@ -312,7 +312,7 @@ func TestSetJSAPIFactory(t *testing.T) { require.NotNil(t, context.SearchAPIFactory) // Create a mock context - ctx := &context.Context{} + ctx := context.New(nil, nil, "test-chat") // Get search API searchAPI := context.SearchAPIFactory(ctx) @@ -337,7 +337,8 @@ func TestSetJSAPIFactory_WithGetter(t *testing.T) { require.NotNil(t, context.SearchAPIFactory) // Create a context with assistant ID - ctx := &context.Context{AssistantID: "test-assistant"} + ctx := context.New(nil, nil, "test-chat") + ctx.AssistantID = "test-assistant" // Get search API searchAPI := context.SearchAPIFactory(ctx) diff --git a/agent/search/nlp/querydsl/mcp_test.go b/agent/search/nlp/querydsl/mcp_test.go index be413001..0bc0a917 100644 --- a/agent/search/nlp/querydsl/mcp_test.go +++ b/agent/search/nlp/querydsl/mcp_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/yaoapp/gou/plan" agentContext "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/test" @@ -14,15 +13,10 @@ import ( // newTestContext creates a test context for MCP testing func newTestContext() *agentContext.Context { - ctx := &agentContext.Context{ - Context: stdContext.Background(), - Space: plan.NewMemorySharedSpace(), - ID: "test-querydsl", - ChatID: "test-chat", - AssistantID: "test-assistant", - Locale: "en", - Referer: agentContext.RefererAPI, - } + ctx := agentContext.New(stdContext.Background(), nil, "test-chat") + ctx.AssistantID = "test-assistant" + ctx.Locale = "en" + ctx.Referer = agentContext.RefererAPI stack, _, _ := agentContext.EnterStack(ctx, "test-assistant", &agentContext.Options{}) ctx.Stack = stack return ctx diff --git a/data/bindata.go b/data/bindata.go index 1cdb50c9..fda7e630 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -153,7 +153,10 @@ // .tmp/data/yao/models/user.mod.yao // .tmp/data/yao/release/app.yaz // .tmp/data/yao/stores/agent/cache.lru.yao -// .tmp/data/yao/stores/agent/memory.xun.yao +// .tmp/data/yao/stores/agent/memory/chat.xun.yao +// .tmp/data/yao/stores/agent/memory/context.xun.yao +// .tmp/data/yao/stores/agent/memory/team.xun.yao +// .tmp/data/yao/stores/agent/memory/user.xun.yao // .tmp/data/yao/stores/cache.lru.yao // .tmp/data/yao/stores/kb/cache.lru.yao // .tmp/data/yao/stores/kb/store.xun.yao @@ -340,7 +343,7 @@ func cuiSetupIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -360,7 +363,7 @@ func cuiV09IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -380,7 +383,7 @@ func cuiV10IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -400,7 +403,7 @@ func cuiV10Layouts__indexAsyncJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -420,7 +423,7 @@ func cuiV10UmiJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -440,7 +443,7 @@ func initEnv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -460,7 +463,7 @@ func initVscodeSettingsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -480,7 +483,7 @@ func initVscodeTypesRuntimeConsoleDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -500,7 +503,7 @@ func initVscodeTypesRuntimeExceptionDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -520,7 +523,7 @@ func initVscodeTypesRuntimeFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -540,7 +543,7 @@ func initVscodeTypesRuntimeGlobalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -560,7 +563,7 @@ func initVscodeTypesRuntimeHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -580,7 +583,7 @@ func initVscodeTypesRuntimeIoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -600,7 +603,7 @@ func initVscodeTypesRuntimeLogDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -620,7 +623,7 @@ func initVscodeTypesRuntimeNeoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -640,7 +643,7 @@ func initVscodeTypesRuntimeProcessFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -660,7 +663,7 @@ func initVscodeTypesRuntimeProcessHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -680,7 +683,7 @@ func initVscodeTypesRuntimeProcessModelDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -700,7 +703,7 @@ func initVscodeTypesRuntimeProcessDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -720,7 +723,7 @@ func initVscodeTypesRuntimeQueryDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -740,7 +743,7 @@ func initVscodeTypesRuntimeStoreDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -760,7 +763,7 @@ func initVscodeTypesRuntimeSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -780,7 +783,7 @@ func initVscodeTypesRuntimeTimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -800,7 +803,7 @@ func initVscodeTypesRuntimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -820,7 +823,7 @@ func initVscodeTypesSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -840,7 +843,7 @@ func initAppYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -860,7 +863,7 @@ func initDataReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -880,7 +883,7 @@ func initDataTemplatesDefault__assetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -900,7 +903,7 @@ func initDataTemplatesDefault__assetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -920,7 +923,7 @@ func initDataTemplatesDefault__assetsImagesLogosLogo_colorSvg() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -940,7 +943,7 @@ func initDataTemplatesDefault__assetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -960,7 +963,7 @@ func initDataTemplatesDefault__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -980,7 +983,7 @@ func initDataTemplatesDefault__documentHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1000,7 +1003,7 @@ func initDataTemplatesDefaultIndexIndexCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1020,7 +1023,7 @@ func initDataTemplatesDefaultIndexIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1040,7 +1043,7 @@ func initDataTemplatesDefaultIndexIndexJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1060,7 +1063,7 @@ func initDbReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1080,7 +1083,7 @@ func initFlowsMenuFlowYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1100,7 +1103,7 @@ func initFormsAccountFormYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1120,7 +1123,7 @@ func initIconsAppIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1140,7 +1143,7 @@ func initIconsAppIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1160,7 +1163,7 @@ func initIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1180,7 +1183,7 @@ func initLoginsAdminLoginYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1200,7 +1203,7 @@ func initLogsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1220,7 +1223,7 @@ func initModelsAdminUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1240,7 +1243,7 @@ func initModelsTestsPetModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1260,7 +1263,7 @@ func initNeoNeoYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1280,7 +1283,7 @@ func initPublicReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1300,7 +1303,7 @@ func initPublicAssetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1320,7 +1323,7 @@ func initPublicAssetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1340,7 +1343,7 @@ func initPublicAssetsImagesLogosLogo_colorSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1360,7 +1363,7 @@ func initPublicAssetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1380,7 +1383,7 @@ func initPublicAssetsLibsuiMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1400,7 +1403,7 @@ func initPublicAssetsLibsuiMinJsMap() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1420,7 +1423,7 @@ func initPublicIndexCfg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1440,7 +1443,7 @@ func initPublicIndexSui() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1460,7 +1463,7 @@ func initScriptsAccountTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1480,7 +1483,7 @@ func initScriptsAiNeoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1500,7 +1503,7 @@ func initScriptsTestsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1520,7 +1523,7 @@ func initScriptsUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1540,7 +1543,7 @@ func initSuisWebSuiYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1560,7 +1563,7 @@ func initTablesAccountTabYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1580,7 +1583,7 @@ func initTsconfigJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1600,7 +1603,7 @@ func libsuiAgentTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1620,7 +1623,7 @@ func libsuiIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1640,7 +1643,7 @@ func libsuiUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1660,7 +1663,7 @@ func libsuiYaoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1680,7 +1683,7 @@ func publicIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1700,7 +1703,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1720,7 +1723,7 @@ func yaoAssistantsEntityPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 212, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 212, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1740,7 +1743,7 @@ func yaoAssistantsEntityPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1760,7 +1763,7 @@ func yaoAssistantsKeywordPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 200, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 200, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1780,7 +1783,7 @@ func yaoAssistantsKeywordPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1800,7 +1803,7 @@ func yaoAssistantsKeywordSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1820,7 +1823,7 @@ func yaoAssistantsNeedsearchPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 207, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 207, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1840,7 +1843,7 @@ func yaoAssistantsNeedsearchPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1860,7 +1863,7 @@ func yaoAssistantsNeedsearchSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1880,7 +1883,7 @@ func yaoAssistantsPromptPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 186, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 186, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1900,7 +1903,7 @@ func yaoAssistantsPromptPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1920,7 +1923,7 @@ func yaoAssistantsQuerydslPackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 196, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 196, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1940,7 +1943,7 @@ func yaoAssistantsQuerydslPromptsAggregationYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1960,7 +1963,7 @@ func yaoAssistantsQuerydslPromptsComplexYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1980,7 +1983,7 @@ func yaoAssistantsQuerydslPromptsFilterYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2000,7 +2003,7 @@ func yaoAssistantsQuerydslPromptsJoinYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2020,7 +2023,7 @@ func yaoAssistantsQuerydslPromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2040,7 +2043,7 @@ func yaoAssistantsQuerydslSrcIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2060,7 +2063,7 @@ func yaoAssistantsTitlePackageYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 178, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 178, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2080,7 +2083,7 @@ func yaoAssistantsTitlePromptsYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2100,7 +2103,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2120,7 +2123,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2140,7 +2143,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2160,7 +2163,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2180,7 +2183,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2200,7 +2203,7 @@ func yaoDataKbProvidersChunkingSemanticEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2220,7 +2223,7 @@ func yaoDataKbProvidersChunkingSemanticZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2240,7 +2243,7 @@ func yaoDataKbProvidersChunkingStructuredEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2260,7 +2263,7 @@ func yaoDataKbProvidersChunkingStructuredZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2280,7 +2283,7 @@ func yaoDataKbProvidersConverterMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2300,7 +2303,7 @@ func yaoDataKbProvidersConverterMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2320,7 +2323,7 @@ func yaoDataKbProvidersConverterOcrEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2340,7 +2343,7 @@ func yaoDataKbProvidersConverterOcrZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2360,7 +2363,7 @@ func yaoDataKbProvidersConverterOfficeEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2380,7 +2383,7 @@ func yaoDataKbProvidersConverterOfficeZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2400,7 +2403,7 @@ func yaoDataKbProvidersConverterUtf8EnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2420,7 +2423,7 @@ func yaoDataKbProvidersConverterUtf8ZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2440,7 +2443,7 @@ func yaoDataKbProvidersConverterVideoEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2460,7 +2463,7 @@ func yaoDataKbProvidersConverterVideoZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2480,7 +2483,7 @@ func yaoDataKbProvidersConverterVisionEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2500,7 +2503,7 @@ func yaoDataKbProvidersConverterVisionZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2520,7 +2523,7 @@ func yaoDataKbProvidersConverterWhisperEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2540,7 +2543,7 @@ func yaoDataKbProvidersConverterWhisperZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2560,7 +2563,7 @@ func yaoDataKbProvidersEmbeddingFastembedEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2580,7 +2583,7 @@ func yaoDataKbProvidersEmbeddingFastembedZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2600,7 +2603,7 @@ func yaoDataKbProvidersEmbeddingOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2620,7 +2623,7 @@ func yaoDataKbProvidersEmbeddingOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2640,7 +2643,7 @@ func yaoDataKbProvidersExtractionOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2660,7 +2663,7 @@ func yaoDataKbProvidersExtractionOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2680,7 +2683,7 @@ func yaoDataKbProvidersFetcherHttpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2700,7 +2703,7 @@ func yaoDataKbProvidersFetcherHttpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2720,7 +2723,7 @@ func yaoDataKbProvidersFetcherMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2740,7 +2743,7 @@ func yaoDataKbProvidersFetcherMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2760,7 +2763,7 @@ func yaoFieldsModelTransJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2780,7 +2783,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2800,7 +2803,7 @@ func yaoLangsZhCnGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2820,7 +2823,7 @@ func yaoLangsZhCnLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2840,7 +2843,7 @@ func yaoLangsZhCnLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2860,7 +2863,7 @@ func yaoLangsZhHkGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2880,7 +2883,7 @@ func yaoLangsZhHkLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2900,7 +2903,7 @@ func yaoLangsZhHkLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2920,7 +2923,7 @@ func yaoModelsAgentAssistantModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 6945, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 6945, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2940,7 +2943,7 @@ func yaoModelsAgentChatModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2960,7 +2963,7 @@ func yaoModelsAgentMessageModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2980,7 +2983,7 @@ func yaoModelsAgentResumeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3000,7 +3003,7 @@ func yaoModelsAgentSearchModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3020,7 +3023,7 @@ func yaoModelsAttachmentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4687, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4687, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3040,7 +3043,7 @@ func yaoModelsAuditModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3060,7 +3063,7 @@ func yaoModelsConfigModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3080,7 +3083,7 @@ func yaoModelsDslModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3100,7 +3103,7 @@ func yaoModelsInvitationModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3120,7 +3123,7 @@ func yaoModelsJobCategoryModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3140,7 +3143,7 @@ func yaoModelsJobExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3160,7 +3163,7 @@ func yaoModelsJobJobModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6330, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6330, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3180,7 +3183,7 @@ func yaoModelsJobLogModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3200,7 +3203,7 @@ func yaoModelsKbCollectionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3220,7 +3223,7 @@ func yaoModelsKbDocumentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3240,7 +3243,7 @@ func yaoModelsMemberModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3260,7 +3263,7 @@ func yaoModelsRoleModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3280,7 +3283,7 @@ func yaoModelsTeamModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3300,7 +3303,7 @@ func yaoModelsUserOauth_accountModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3320,7 +3323,7 @@ func yaoModelsUserTypeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3340,7 +3343,7 @@ func yaoModelsUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3360,7 +3363,7 @@ func yaoReleaseAppYaz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3380,27 +3383,87 @@ func yaoStoresAgentCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoStoresAgentMemoryXunYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x90\x31\x6b\xc4\x30\x0c\x85\xf7\xfc\x0a\xa1\xf9\x0e\x4a\xc7\x6c\x85\xae\x9d\x3a\x96\x12\x94\x58\x09\xa6\x8e\x14\x64\x05\x1a\xca\xfd\xf7\x62\xa5\xb7\x95\x9b\x6c\x3f\x7f\xef\xf9\xc9\x3f\x1d\x00\x16\x1a\xb9\x60\x0f\xf8\xb2\xb0\x38\xbc\xf1\xaa\x76\xc0\xbb\xab\x31\x5e\x1a\x90\xb8\x4e\x96\x37\xcf\x2a\x0d\x7b\x25\xa7\x91\x2a\x5f\x47\x9a\xbe\x38\x41\x6d\x24\xcc\x6a\x40\x11\xb0\x9e\x01\x1b\x5b\xcd\xd5\x59\x26\x06\x92\x04\x45\x65\xb9\x3a\xdb\x1a\x06\x5a\xfe\xc2\x9d\x96\x8a\x3d\x7c\x60\x98\xf1\x02\x78\xfa\xdb\xee\x7b\x97\xb6\x50\xc6\xcf\x60\x8d\x29\xa9\x94\x03\x7b\x98\xa9\x54\x0e\x71\xdc\x73\xf1\xdc\x9a\xb9\xed\xa7\x54\xd5\x1c\x7b\x78\x7e\x8a\x93\xd0\xca\x8f\xc6\xf3\x63\x8b\xfb\x78\xad\x09\x7a\x1f\xb5\x7d\xcf\x3f\x40\xb4\x1e\x4b\x68\xc3\x70\x90\x0e\xd1\x7d\xb8\x17\x3f\x91\x49\x45\x78\x72\xb5\x86\x25\x9e\x69\x2f\x8e\x1d\xc0\xad\xbb\x75\xbf\x01\x00\x00\xff\xff\x26\x99\x82\xdd\x78\x01\x00\x00") +var _yaoStoresAgentMemoryChatXunYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x91\x41\x4f\xfb\x30\x0c\xc5\xef\xfd\x14\x56\xce\x9d\xb4\xff\xfe\x88\x43\x6f\x88\x33\x27\x8e\x08\x45\x5e\xea\x76\x11\xa9\x53\x25\xce\x58\x41\xfb\xee\xc8\xe9\x2a\x90\xe0\x56\xbf\xf7\x6b\xfd\x9e\xfb\xd9\x00\x98\x80\x47\x0a\xa6\x03\xf3\x30\x12\x0b\x3c\xd1\x14\xd3\x02\x3b\x78\x3c\xa1\x98\x56\x89\x9e\xb2\x4b\x7e\x16\x1f\x59\x39\x35\x76\x81\xce\x14\x60\x5a\xe1\x2c\x31\x11\x0c\x31\x81\x8b\x7c\xa6\x94\x51\x59\x1d\x84\x2e\xd2\x82\xd3\x37\xf2\x4c\xce\x0f\xde\x41\x26\x11\xcf\x63\x6e\x01\xb9\x07\x74\xae\x4c\x25\xa0\x50\x0f\x6f\x1c\xdf\x03\xf5\x23\xad\x7b\x05\xc7\x6c\x3a\x78\x31\xa8\xc9\x4c\x0b\x66\xdd\xa7\x4f\xae\xa6\x03\x73\x29\x6c\x5e\x2b\x9d\x08\xfb\xc8\x61\x31\x1d\x0c\x18\x32\x55\xf1\x58\x7c\x10\xaf\xb1\x25\x95\x55\xca\x31\x89\xe9\xe0\x70\xa8\x13\xe3\x44\xbf\xca\x6b\x43\x78\xd6\x52\xb7\x20\xcb\x5c\x21\x5d\x56\x85\xb8\x1d\x43\x2f\xf8\x07\x50\xc3\x1f\x43\xd5\xac\x5d\x30\xda\x5a\xc1\xae\xf9\xad\xdb\x4e\x0b\x60\x5c\x64\x26\x27\x31\x29\xdb\xd3\x80\x25\x7c\x7b\xe8\x4e\x64\xb3\xff\xd0\x0f\xfd\xdb\x1f\xee\xf6\x37\x63\xa6\x94\x7d\x16\xeb\x59\x28\x9d\x51\x7f\xdf\xff\xcd\x73\x81\x90\xcb\xfc\xd3\xbb\xdf\x37\x00\xd7\xe6\xda\x7c\x05\x00\x00\xff\xff\xf4\xee\x00\xf1\xf1\x01\x00\x00") -func yaoStoresAgentMemoryXunYaoBytes() ([]byte, error) { +func yaoStoresAgentMemoryChatXunYaoBytes() ([]byte, error) { return bindataRead( - _yaoStoresAgentMemoryXunYao, - "yao/stores/agent/memory.xun.yao", + _yaoStoresAgentMemoryChatXunYao, + "yao/stores/agent/memory/chat.xun.yao", ) } -func yaoStoresAgentMemoryXunYao() (*asset, error) { - bytes, err := yaoStoresAgentMemoryXunYaoBytes() +func yaoStoresAgentMemoryChatXunYao() (*asset, error) { + bytes, err := yaoStoresAgentMemoryChatXunYaoBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory.xun.yao", size: 376, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _yaoStoresAgentMemoryContextXunYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x50\x3d\x4f\xc3\x30\x10\xdd\xf3\x2b\x4e\x9e\x53\xa9\x14\x58\xb2\x21\x66\x26\x46\x84\xac\x8b\x73\x2d\x96\x1c\x3b\xdc\x9d\xab\x06\xd4\xff\x8e\xec\x24\xa2\x12\x6c\xc9\x7b\xef\xfc\x3e\xbe\x1b\x00\x13\xb0\xa7\x60\x3a\x30\x4f\x27\x8a\x0a\x2f\x34\x26\x9e\x61\x07\xcf\x29\x2a\x5d\xd4\xb4\x45\x34\x90\x38\xf6\x93\xfa\x14\x8b\x74\xe5\x76\x81\xce\x14\x60\x5c\x4e\x44\x13\x13\x1c\x13\x83\x8f\x4a\x3c\xd2\xe0\x51\x09\x98\x24\x07\x95\x16\x94\xc6\x29\x31\xf2\x0c\x67\x64\x8f\x7d\x20\x69\x01\xe3\x00\x4c\x9f\x99\x44\x77\xe2\xd2\x44\x03\x38\x74\x1f\xb4\xd8\x2a\x9e\xc4\x74\xf0\x66\xb0\x64\x33\x2d\x98\xc5\xab\x7c\xb9\x2d\x1f\x98\x4b\x8e\xe6\xbd\x1e\x30\xe1\x90\x62\x98\x4d\x07\x47\x0c\x42\x15\xec\xb3\x0f\xea\x4b\x70\xe5\xbc\x40\x92\x58\x4d\x07\x87\xfb\xfa\x17\x71\xa4\x3f\x0b\xac\x1d\xe1\xb5\xd4\x5a\xe3\xcc\x53\xd5\x15\xbf\x0a\xa4\x6d\x91\xb2\xe4\x3f\x82\x5a\xa1\x0f\x15\xb3\x76\xc6\x64\x6b\x11\xbb\xb4\xb0\xee\x66\x62\xa8\x8d\x22\x39\x4d\x5c\xe4\x03\x1d\x31\x87\x5f\xae\x8c\x62\xc5\x7f\x95\xb7\xee\xf6\x87\x87\xfd\x4a\x4c\xc4\xe2\x45\x6d\xdd\xfc\x8c\xa1\xd2\xdb\x51\x20\x8c\x79\xba\xe5\x1e\x1b\x80\x6b\x73\x6d\x7e\x02\x00\x00\xff\xff\x5e\x92\x18\x6b\xfb\x01\x00\x00") + +func yaoStoresAgentMemoryContextXunYaoBytes() ([]byte, error) { + return bindataRead( + _yaoStoresAgentMemoryContextXunYao, + "yao/stores/agent/memory/context.xun.yao", + ) +} + +func yaoStoresAgentMemoryContextXunYao() (*asset, error) { + bytes, err := yaoStoresAgentMemoryContextXunYaoBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _yaoStoresAgentMemoryTeamXunYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x91\xc1\x6e\xe3\x30\x0c\x44\xef\xfe\x0a\x42\x67\x07\x48\x82\x60\x0f\xbe\xed\x07\xec\x69\xf7\xb6\x28\x04\xda\x62\x1c\xa1\xb4\x68\x48\x74\x5a\xb7\xc8\xbf\x17\x94\x93\xa2\x40\x7b\xb3\x67\x1e\xc8\x19\xea\xbd\x01\x70\x8c\x3d\xb1\xeb\xc0\xfd\x1e\x29\x29\xfc\xa1\x49\xf2\x0a\x3b\xf8\x47\x38\xb9\xd6\x88\x40\x65\xc8\x71\xd6\x28\xc9\x38\x33\x76\x4c\x57\x62\x98\x36\xb8\xa8\x64\x82\xb3\x64\x50\xc2\x09\x9e\x93\xbc\x30\x85\x91\x5a\x28\x17\xcc\x14\xa0\x90\x6a\x4c\x63\x69\x01\x53\x80\x41\x98\xb1\x97\x8c\x1a\xaf\x04\x01\x15\xb7\x3d\x8a\x63\x71\x1d\xfc\x77\x68\x49\x5c\x0b\x6e\x9b\x6f\x5f\x5a\xd3\x80\x7b\x5d\x92\x7b\xaa\x74\x26\x0c\x92\x78\x75\x1d\x9c\x91\x0b\x55\xb1\x5f\x22\x6b\xb4\x98\x9a\x97\x4d\x2a\x92\xd5\x75\x70\x3c\xd4\xbf\x84\x13\x7d\x2b\x6b\x8d\xe0\xaf\x95\xb8\x07\x59\xe7\x0a\xd9\xb2\x2a\xc8\xa3\xbc\x5d\xec\x07\xa0\x86\xef\xb9\x6a\xde\xaf\x28\xbe\x56\xf0\x5b\x7e\xaf\x8f\x53\x02\xb8\x41\x52\xa2\x41\x25\x1b\x1b\xe8\x8c\x0b\xeb\xa7\x87\xc3\x85\x7c\x89\x6f\x36\xe8\xb0\x3f\x9e\xf6\x77\x63\xa6\x5c\x62\x51\x1f\x93\x52\xbe\xa2\x3d\xd7\xaf\x87\x37\x30\x61\x5a\xe6\xaf\xde\xe1\x74\xda\x37\x00\xb7\xe6\xd6\x7c\x04\x00\x00\xff\xff\xba\xec\x6e\xcd\xe3\x01\x00\x00") + +func yaoStoresAgentMemoryTeamXunYaoBytes() ([]byte, error) { + return bindataRead( + _yaoStoresAgentMemoryTeamXunYao, + "yao/stores/agent/memory/team.xun.yao", + ) +} + +func yaoStoresAgentMemoryTeamXunYao() (*asset, error) { + bytes, err := yaoStoresAgentMemoryTeamXunYaoBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _yaoStoresAgentMemoryUserXunYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x91\x41\x4f\x83\x40\x10\x85\xef\xfc\x8a\xc9\x9e\x69\x42\x9b\xc6\x03\x37\x7f\x80\x27\xe3\xc9\x18\xb2\x85\x01\x37\x2e\xb3\x64\x66\xb6\x8a\xa6\xff\xdd\xcc\xd2\x1a\x13\xbd\xc1\x7b\x5f\x98\xf7\x1e\x5f\x15\x80\x8b\xfe\x84\xd1\xb5\xe0\xee\x27\x24\x85\x07\x9c\x13\xaf\xb0\x83\x27\x41\x76\xb5\x11\x03\x4a\xcf\x61\xd1\x90\xc8\x38\x33\x76\x11\xcf\x18\x61\xde\x60\xd1\xc4\x08\x63\x62\xc8\x82\x0c\x0b\xe3\x88\x8c\xd4\xa3\xd4\x10\x13\x4d\x3b\x45\x9e\xe1\x8d\xd2\x7b\xc4\x61\xc2\x1a\x3c\x0d\xb0\x20\x4b\x22\x1f\x41\x50\x35\xd0\x24\xdb\x31\xf5\x93\xb8\x16\x9e\x9d\xb7\x38\xae\x06\xb7\x1d\xb1\xa7\x5c\x22\x81\xfb\xc8\xe4\x5e\x0a\xcd\xe8\x87\x44\x71\x75\x2d\x8c\x3e\x0a\x16\xf1\x94\x43\xd4\x60\x59\x95\xf3\x26\x49\x62\x75\x2d\x1c\x9a\xf2\x46\x7e\xc6\x3f\x8d\xad\x16\x3c\x5a\x93\x6b\x90\x75\x29\x90\x1d\x2b\x42\xba\x2d\x60\xb3\xfd\x03\x94\xf0\xa7\x58\xb4\xae\x5b\x7d\xea\x4a\x85\x6e\xcb\xdf\xe5\xdb\x9e\x00\xae\x4f\x44\xd8\x6b\x62\x63\x07\x1c\x7d\x8e\xfa\xe3\xf9\xfe\x15\x3b\x09\x9f\xf6\xa1\x7d\x73\x38\x36\x57\xc3\x06\x0b\xa2\x5d\x20\x45\x3e\x7b\xfb\x67\x77\x37\xaf\x8f\xe8\x29\x2f\xbf\xbd\xfd\xf1\xd8\x54\x00\x97\xea\x52\x55\xdf\x01\x00\x00\xff\xff\x7b\xc8\x10\xdf\xe9\x01\x00\x00") + +func yaoStoresAgentMemoryUserXunYaoBytes() ([]byte, error) { + return bindataRead( + _yaoStoresAgentMemoryUserXunYao, + "yao/stores/agent/memory/user.xun.yao", + ) +} + +func yaoStoresAgentMemoryUserXunYao() (*asset, error) { + bytes, err := yaoStoresAgentMemoryUserXunYaoBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3420,7 +3483,7 @@ func yaoStoresCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3440,7 +3503,7 @@ func yaoStoresKbCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3460,7 +3523,7 @@ func yaoStoresKbStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3480,7 +3543,7 @@ func yaoStoresOauthCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3500,7 +3563,7 @@ func yaoStoresOauthClientXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3520,7 +3583,7 @@ func yaoStoresOauthStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3540,7 +3603,7 @@ func yaoStoresStoreXunYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3560,7 +3623,7 @@ func yaoUploadersAttachmentLocalYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1766365615, 0)} + info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1766373341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3770,7 +3833,10 @@ var _bindata = map[string]func() (*asset, error){ "yao/models/user.mod.yao": yaoModelsUserModYao, "yao/release/app.yaz": yaoReleaseAppYaz, "yao/stores/agent/cache.lru.yao": yaoStoresAgentCacheLruYao, - "yao/stores/agent/memory.xun.yao": yaoStoresAgentMemoryXunYao, + "yao/stores/agent/memory/chat.xun.yao": yaoStoresAgentMemoryChatXunYao, + "yao/stores/agent/memory/context.xun.yao": yaoStoresAgentMemoryContextXunYao, + "yao/stores/agent/memory/team.xun.yao": yaoStoresAgentMemoryTeamXunYao, + "yao/stores/agent/memory/user.xun.yao": yaoStoresAgentMemoryUserXunYao, "yao/stores/cache.lru.yao": yaoStoresCacheLruYao, "yao/stores/kb/cache.lru.yao": yaoStoresKbCacheLruYao, "yao/stores/kb/store.xun.yao": yaoStoresKbStoreXunYao, @@ -4149,8 +4215,13 @@ var _bintree = &bintree{nil, map[string]*bintree{ }}, "stores": {nil, map[string]*bintree{ "agent": {nil, map[string]*bintree{ - "cache.lru.yao": {yaoStoresAgentCacheLruYao, map[string]*bintree{}}, - "memory.xun.yao": {yaoStoresAgentMemoryXunYao, map[string]*bintree{}}, + "cache.lru.yao": {yaoStoresAgentCacheLruYao, map[string]*bintree{}}, + "memory": {nil, map[string]*bintree{ + "chat.xun.yao": {yaoStoresAgentMemoryChatXunYao, map[string]*bintree{}}, + "context.xun.yao": {yaoStoresAgentMemoryContextXunYao, map[string]*bintree{}}, + "team.xun.yao": {yaoStoresAgentMemoryTeamXunYao, map[string]*bintree{}}, + "user.xun.yao": {yaoStoresAgentMemoryUserXunYao, map[string]*bintree{}}, + }}, }}, "cache.lru.yao": {yaoStoresCacheLruYao, map[string]*bintree{}}, "kb": {nil, map[string]*bintree{ diff --git a/store/store.go b/store/store.go index 0574e82e..f2aa5a2d 100644 --- a/store/store.go +++ b/store/store.go @@ -14,15 +14,18 @@ import ( ) var systemStores = map[string]string{ - "__yao.store": "yao/stores/store.xun.yao", // for common data store - "__yao.cache": "yao/stores/cache.lru.yao", // for common cache store - "__yao.oauth.store": "yao/stores/oauth/store.xun.yao", // for OAuth data store - "__yao.oauth.cache": "yao/stores/oauth/cache.lru.yao", // for OAuth cache store - "__yao.oauth.client": "yao/stores/oauth/client.xun.yao", // for OAuth client store - "__yao.agent.memory": "yao/stores/agent/memory.xun.yao", // for agent memory store (for agent memory) - "__yao.agent.cache": "yao/stores/agent/cache.lru.yao", // for agent cache store (for agent cache) - "__yao.kb.store": "yao/stores/kb/store.xun.yao", // for knowledge base store - "__yao.kb.cache": "yao/stores/kb/cache.lru.yao", // for knowledge base cache store + "__yao.store": "yao/stores/store.xun.yao", // for common data store + "__yao.cache": "yao/stores/cache.lru.yao", // for common cache store + "__yao.oauth.store": "yao/stores/oauth/store.xun.yao", // for OAuth data store + "__yao.oauth.cache": "yao/stores/oauth/cache.lru.yao", // for OAuth cache store + "__yao.oauth.client": "yao/stores/oauth/client.xun.yao", // for OAuth client store + "__yao.agent.memory.user": "yao/stores/agent/memory/user.xun.yao", // for agent user-level memory + "__yao.agent.memory.team": "yao/stores/agent/memory/team.xun.yao", // for agent team-level memory + "__yao.agent.memory.chat": "yao/stores/agent/memory/chat.xun.yao", // for agent chat-level memory + "__yao.agent.memory.context": "yao/stores/agent/memory/context.xun.yao", // for agent context-level memory + "__yao.agent.cache": "yao/stores/agent/cache.lru.yao", // for agent cache store + "__yao.kb.store": "yao/stores/kb/store.xun.yao", // for knowledge base store + "__yao.kb.cache": "yao/stores/kb/cache.lru.yao", // for knowledge base cache store } // replaceVars replaces template variables in the JSON string diff --git a/store/store_test.go b/store/store_test.go index fcabb5b3..2d9797fc 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -43,7 +43,10 @@ func check(t *testing.T) { assert.True(t, ids["__yao.oauth.store"]) assert.True(t, ids["__yao.oauth.client"]) assert.True(t, ids["__yao.oauth.cache"]) - assert.True(t, ids["__yao.agent.memory"]) + assert.True(t, ids["__yao.agent.memory.user"]) + assert.True(t, ids["__yao.agent.memory.team"]) + assert.True(t, ids["__yao.agent.memory.chat"]) + assert.True(t, ids["__yao.agent.memory.context"]) assert.True(t, ids["__yao.agent.cache"]) } diff --git a/test/utils.go b/test/utils.go index 9d64abe8..624bdb59 100644 --- a/test/utils.go +++ b/test/utils.go @@ -220,15 +220,18 @@ var testSystemModels = map[string]string{ } var testSystemStores = map[string]string{ - "__yao.store": "yao/stores/store.xun.yao", - "__yao.cache": "yao/stores/cache.lru.yao", - "__yao.oauth.store": "yao/stores/oauth/store.xun.yao", - "__yao.oauth.client": "yao/stores/oauth/client.xun.yao", - "__yao.oauth.cache": "yao/stores/oauth/cache.lru.yao", - "__yao.agent.memory": "yao/stores/agent/memory.xun.yao", - "__yao.agent.cache": "yao/stores/agent/cache.lru.yao", - "__yao.kb.store": "yao/stores/kb/store.xun.yao", - "__yao.kb.cache": "yao/stores/kb/cache.lru.yao", + "__yao.store": "yao/stores/store.xun.yao", + "__yao.cache": "yao/stores/cache.lru.yao", + "__yao.oauth.store": "yao/stores/oauth/store.xun.yao", + "__yao.oauth.client": "yao/stores/oauth/client.xun.yao", + "__yao.oauth.cache": "yao/stores/oauth/cache.lru.yao", + "__yao.agent.memory.user": "yao/stores/agent/memory/user.xun.yao", + "__yao.agent.memory.team": "yao/stores/agent/memory/team.xun.yao", + "__yao.agent.memory.chat": "yao/stores/agent/memory/chat.xun.yao", + "__yao.agent.memory.context": "yao/stores/agent/memory/context.xun.yao", + "__yao.agent.cache": "yao/stores/agent/cache.lru.yao", + "__yao.kb.store": "yao/stores/kb/store.xun.yao", + "__yao.kb.cache": "yao/stores/kb/cache.lru.yao", } func loadSystemStores(t *testing.T, cfg config.Config) error { diff --git a/yao/stores/agent/memory.xun.yao b/yao/stores/agent/memory.xun.yao deleted file mode 100644 index bca390a9..00000000 --- a/yao/stores/agent/memory.xun.yao +++ /dev/null @@ -1,15 +0,0 @@ -{ - "label": "Agent Memory Store", - "description": "Database-backed store for agent memory persistence and long-term storage", - "tags": ["agent", "memory", "xun", "ai"], - "readonly": false, - "builtin": true, - "sort": 20, - "name": "Agent Memory Store", - "type": "xun", - "option": { - "type": "xun", - "table": "__yao_agent_memory", - "connector": "default" - } -} diff --git a/yao/stores/agent/memory/chat.xun.yao b/yao/stores/agent/memory/chat.xun.yao new file mode 100644 index 00000000..984479ea --- /dev/null +++ b/yao/stores/agent/memory/chat.xun.yao @@ -0,0 +1,18 @@ +{ + "label": "Agent Memory - Chat", + "description": "Chat-level memory store for conversation context, chat-specific settings, and accumulated knowledge", + "tags": ["agent", "memory", "chat", "xun"], + "readonly": false, + "builtin": true, + "sort": 22, + "name": "Agent Memory Chat Store", + "type": "xun", + "option": { + "type": "xun", + "table": "__yao_agent_memory_chat", + "connector": "default", + "cache_size": 10240, + "persist_interval": 30, + "cleanup_interval": 60 + } +} diff --git a/yao/stores/agent/memory/context.xun.yao b/yao/stores/agent/memory/context.xun.yao new file mode 100644 index 00000000..88ad7738 --- /dev/null +++ b/yao/stores/agent/memory/context.xun.yao @@ -0,0 +1,18 @@ +{ + "label": "Agent Memory - Context", + "description": "Context-level memory store for intermediate results, temporary variables, and request-scoped cache", + "tags": ["agent", "memory", "context", "xun"], + "readonly": false, + "builtin": true, + "sort": 23, + "name": "Agent Memory Context Store", + "type": "xun", + "option": { + "type": "xun", + "table": "__yao_agent_memory_context", + "connector": "default", + "cache_size": 10240, + "persist_interval": 10, + "cleanup_interval": 5 + } +} diff --git a/yao/stores/agent/memory/team.xun.yao b/yao/stores/agent/memory/team.xun.yao new file mode 100644 index 00000000..f794cfc7 --- /dev/null +++ b/yao/stores/agent/memory/team.xun.yao @@ -0,0 +1,18 @@ +{ + "label": "Agent Memory - Team", + "description": "Team-level memory store for team knowledge, shared settings, and collaborative data", + "tags": ["agent", "memory", "team", "xun"], + "readonly": false, + "builtin": true, + "sort": 21, + "name": "Agent Memory Team Store", + "type": "xun", + "option": { + "type": "xun", + "table": "__yao_agent_memory_team", + "connector": "default", + "cache_size": 10240, + "persist_interval": 60, + "cleanup_interval": 1440 + } +} diff --git a/yao/stores/agent/memory/user.xun.yao b/yao/stores/agent/memory/user.xun.yao new file mode 100644 index 00000000..cb471890 --- /dev/null +++ b/yao/stores/agent/memory/user.xun.yao @@ -0,0 +1,19 @@ +{ + "label": "Agent Memory - User", + "description": "User-level memory store for user preferences, long-term knowledge, and personal settings", + "tags": ["agent", "memory", "user", "xun"], + "readonly": false, + "builtin": true, + "sort": 20, + "name": "Agent Memory User Store", + "type": "xun", + "option": { + "type": "xun", + "table": "__yao_agent_memory_user", + "connector": "default", + "cache_size": 10240, + "persist_interval": 60, + "cleanup_interval": 1440 + } +} +