refactor(store): split gatewayStore into specialized domain stores
Major restructuring: - Split monolithic gatewayStore into 5 focused stores: - connectionStore: WebSocket connection and gateway lifecycle - configStore: quickConfig, workspaceInfo, MCP services - agentStore: clones, usage stats, agent management - handStore: hands, approvals, triggers, hand runs - workflowStore: workflows, workflow runs, execution - Update all components to use new stores with selector pattern - Remove
This commit is contained in:
408
docs/archive/v1-viking-dead-code/lib/viking-memory-adapter.ts
Normal file
408
docs/archive/v1-viking-dead-code/lib/viking-memory-adapter.ts
Normal file
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* VikingMemoryAdapter - Bridges VikingAdapter to MemoryManager Interface
|
||||
*
|
||||
* This adapter allows the existing MemoryPanel to use OpenViking as a backend
|
||||
* while maintaining compatibility with the existing MemoryManager interface.
|
||||
*
|
||||
* Features:
|
||||
* - Implements MemoryManager interface
|
||||
* - Falls back to local MemoryManager when OpenViking unavailable
|
||||
* - Supports both sidecar and remote modes
|
||||
*/
|
||||
|
||||
import {
|
||||
getMemoryManager,
|
||||
type MemoryEntry,
|
||||
type MemoryType,
|
||||
type MemorySource,
|
||||
type MemorySearchOptions,
|
||||
type MemoryStats,
|
||||
} from './agent-memory';
|
||||
|
||||
import {
|
||||
getVikingAdapter,
|
||||
type MemoryResult,
|
||||
type VikingMode,
|
||||
} from './viking-adapter';
|
||||
|
||||
// === Types ===
|
||||
|
||||
export interface VikingMemoryConfig {
|
||||
enabled: boolean;
|
||||
mode: VikingMode | 'auto';
|
||||
fallbackToLocal: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: VikingMemoryConfig = {
|
||||
enabled: true,
|
||||
mode: 'auto',
|
||||
fallbackToLocal: true,
|
||||
};
|
||||
|
||||
// === VikingMemoryAdapter Implementation ===
|
||||
|
||||
/**
|
||||
* VikingMemoryAdapter implements the MemoryManager interface
|
||||
* using OpenViking as the backend with optional fallback to localStorage.
|
||||
*/
|
||||
export class VikingMemoryAdapter {
|
||||
private config: VikingMemoryConfig;
|
||||
private vikingAvailable: boolean | null = null;
|
||||
private lastCheckTime: number = 0;
|
||||
private static CHECK_INTERVAL = 30000; // 30 seconds
|
||||
|
||||
constructor(config?: Partial<VikingMemoryConfig>) {
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
}
|
||||
|
||||
// === Availability Check ===
|
||||
|
||||
private async isVikingAvailable(): Promise<boolean> {
|
||||
const now = Date.now();
|
||||
if (this.vikingAvailable !== null && now - this.lastCheckTime < VikingMemoryAdapter.CHECK_INTERVAL) {
|
||||
return this.vikingAvailable;
|
||||
}
|
||||
|
||||
try {
|
||||
const viking = getVikingAdapter();
|
||||
const connected = await viking.isConnected();
|
||||
this.vikingAvailable = connected;
|
||||
this.lastCheckTime = now;
|
||||
return connected;
|
||||
} catch {
|
||||
this.vikingAvailable = false;
|
||||
this.lastCheckTime = now;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async getBackend(): Promise<'viking' | 'local'> {
|
||||
if (!this.config.enabled) {
|
||||
return 'local';
|
||||
}
|
||||
|
||||
const available = await this.isVikingAvailable();
|
||||
if (available) {
|
||||
return 'viking';
|
||||
}
|
||||
|
||||
if (this.config.fallbackToLocal) {
|
||||
console.log('[VikingMemoryAdapter] OpenViking unavailable, using local fallback');
|
||||
return 'local';
|
||||
}
|
||||
|
||||
throw new Error('OpenViking unavailable and fallback disabled');
|
||||
}
|
||||
|
||||
// === MemoryManager Interface Implementation ===
|
||||
|
||||
async save(
|
||||
entry: Omit<MemoryEntry, 'id' | 'createdAt' | 'lastAccessedAt' | 'accessCount'>
|
||||
): Promise<MemoryEntry> {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (backend === 'viking') {
|
||||
const viking = getVikingAdapter();
|
||||
const result = await this.saveToViking(viking, entry);
|
||||
return result;
|
||||
}
|
||||
|
||||
return getMemoryManager().save(entry);
|
||||
}
|
||||
|
||||
private async saveToViking(
|
||||
viking: ReturnType<typeof getVikingAdapter>,
|
||||
entry: Omit<MemoryEntry, 'id' | 'createdAt' | 'lastAccessedAt' | 'accessCount'>
|
||||
): Promise<MemoryEntry> {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
let result;
|
||||
const tags = entry.tags.join(',');
|
||||
|
||||
switch (entry.type) {
|
||||
case 'fact':
|
||||
result = await viking.saveUserFact('general', entry.content, entry.tags);
|
||||
break;
|
||||
case 'preference':
|
||||
result = await viking.saveUserPreference(tags || 'preference', entry.content);
|
||||
break;
|
||||
case 'lesson':
|
||||
result = await viking.saveAgentLesson(entry.agentId, entry.content, entry.tags);
|
||||
break;
|
||||
case 'context':
|
||||
result = await viking.saveAgentPattern(entry.agentId, `[Context] ${entry.content}`, entry.tags);
|
||||
break;
|
||||
case 'task':
|
||||
result = await viking.saveAgentPattern(entry.agentId, `[Task] ${entry.content}`, entry.tags);
|
||||
break;
|
||||
default:
|
||||
result = await viking.saveUserFact('general', entry.content, entry.tags);
|
||||
}
|
||||
|
||||
return {
|
||||
id: result.uri,
|
||||
agentId: entry.agentId,
|
||||
content: entry.content,
|
||||
type: entry.type,
|
||||
importance: entry.importance,
|
||||
source: entry.source,
|
||||
tags: entry.tags,
|
||||
createdAt: now,
|
||||
lastAccessedAt: now,
|
||||
accessCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async search(query: string, options?: MemorySearchOptions): Promise<MemoryEntry[]> {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (backend === 'viking') {
|
||||
const viking = getVikingAdapter();
|
||||
return this.searchViking(viking, query, options);
|
||||
}
|
||||
|
||||
return getMemoryManager().search(query, options);
|
||||
}
|
||||
|
||||
private async searchViking(
|
||||
viking: ReturnType<typeof getVikingAdapter>,
|
||||
query: string,
|
||||
options?: MemorySearchOptions
|
||||
): Promise<MemoryEntry[]> {
|
||||
const results: MemoryEntry[] = [];
|
||||
const agentId = options?.agentId || 'zclaw-main';
|
||||
|
||||
// Search user memories
|
||||
const userResults = await viking.searchUserMemories(query, options?.limit || 10);
|
||||
for (const r of userResults) {
|
||||
results.push(this.memoryResultToEntry(r, agentId));
|
||||
}
|
||||
|
||||
// Search agent memories
|
||||
const agentResults = await viking.searchAgentMemories(agentId, query, options?.limit || 10);
|
||||
for (const r of agentResults) {
|
||||
results.push(this.memoryResultToEntry(r, agentId));
|
||||
}
|
||||
|
||||
// Filter by type if specified
|
||||
if (options?.type) {
|
||||
return results.filter(r => r.type === options.type);
|
||||
}
|
||||
|
||||
// Sort by score (desc) and limit
|
||||
return results.slice(0, options?.limit || 10);
|
||||
}
|
||||
|
||||
private memoryResultToEntry(result: MemoryResult, agentId: string): MemoryEntry {
|
||||
const type = this.mapCategoryToType(result.category);
|
||||
return {
|
||||
id: result.uri,
|
||||
agentId,
|
||||
content: result.content,
|
||||
type,
|
||||
importance: Math.round(result.score * 10),
|
||||
source: 'auto' as MemorySource,
|
||||
tags: result.tags || [],
|
||||
createdAt: new Date().toISOString(),
|
||||
lastAccessedAt: new Date().toISOString(),
|
||||
accessCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
private mapCategoryToType(category: string): MemoryType {
|
||||
const categoryLower = category.toLowerCase();
|
||||
if (categoryLower.includes('prefer') || categoryLower.includes('偏好')) {
|
||||
return 'preference';
|
||||
}
|
||||
if (categoryLower.includes('fact') || categoryLower.includes('事实')) {
|
||||
return 'fact';
|
||||
}
|
||||
if (categoryLower.includes('lesson') || categoryLower.includes('经验')) {
|
||||
return 'lesson';
|
||||
}
|
||||
if (categoryLower.includes('context') || categoryLower.includes('上下文')) {
|
||||
return 'context';
|
||||
}
|
||||
if (categoryLower.includes('task') || categoryLower.includes('任务')) {
|
||||
return 'task';
|
||||
}
|
||||
return 'fact';
|
||||
}
|
||||
|
||||
async getAll(agentId: string, options?: { type?: MemoryType; limit?: number }): Promise<MemoryEntry[]> {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (backend === 'viking') {
|
||||
const viking = getVikingAdapter();
|
||||
const entries = await viking.browseMemories(`viking://agent/${agentId}/memories`);
|
||||
|
||||
return entries
|
||||
.filter(_e => !options?.type || true) // TODO: filter by type
|
||||
.slice(0, options?.limit || 50)
|
||||
.map(e => ({
|
||||
id: e.uri,
|
||||
agentId,
|
||||
content: e.name, // Placeholder - would need to fetch full content
|
||||
type: 'fact' as MemoryType,
|
||||
importance: 5,
|
||||
source: 'auto' as MemorySource,
|
||||
tags: [],
|
||||
createdAt: e.modifiedAt || new Date().toISOString(),
|
||||
lastAccessedAt: new Date().toISOString(),
|
||||
accessCount: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
return getMemoryManager().getAll(agentId, options);
|
||||
}
|
||||
|
||||
async get(id: string): Promise<MemoryEntry | null> {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (backend === 'viking') {
|
||||
const viking = getVikingAdapter();
|
||||
try {
|
||||
const content = await viking.getIdentityFromViking('zclaw-main', id);
|
||||
return {
|
||||
id,
|
||||
agentId: 'zclaw-main',
|
||||
content,
|
||||
type: 'fact',
|
||||
importance: 5,
|
||||
source: 'auto',
|
||||
tags: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
lastAccessedAt: new Date().toISOString(),
|
||||
accessCount: 0,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return getMemoryManager().get(id);
|
||||
}
|
||||
|
||||
async forget(id: string): Promise<void> {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (backend === 'viking') {
|
||||
const viking = getVikingAdapter();
|
||||
await viking.deleteMemory(id);
|
||||
return;
|
||||
}
|
||||
|
||||
return getMemoryManager().forget(id);
|
||||
}
|
||||
|
||||
async prune(options: {
|
||||
maxAgeDays?: number;
|
||||
minImportance?: number;
|
||||
agentId?: string;
|
||||
}): Promise<number> {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (backend === 'viking') {
|
||||
// OpenViking handles pruning internally
|
||||
// For now, return 0 (no items pruned)
|
||||
console.log('[VikingMemoryAdapter] Pruning delegated to OpenViking');
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getMemoryManager().prune(options);
|
||||
}
|
||||
|
||||
async exportToMarkdown(agentId: string): Promise<string> {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (backend === 'viking') {
|
||||
const entries = await this.getAll(agentId, { limit: 100 });
|
||||
// Generate markdown from entries
|
||||
const lines = [
|
||||
`# Agent Memory Export (OpenViking)`,
|
||||
'',
|
||||
`> Agent: ${agentId}`,
|
||||
`> Exported: ${new Date().toISOString()}`,
|
||||
`> Total entries: ${entries.length}`,
|
||||
'',
|
||||
];
|
||||
|
||||
for (const entry of entries) {
|
||||
lines.push(`- [${entry.type}] ${entry.content}`);
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
return getMemoryManager().exportToMarkdown(agentId);
|
||||
}
|
||||
|
||||
async stats(agentId?: string): Promise<MemoryStats> {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (backend === 'viking') {
|
||||
const viking = getVikingAdapter();
|
||||
try {
|
||||
const vikingStats = await viking.getMemoryStats(agentId || 'zclaw-main');
|
||||
return {
|
||||
totalEntries: vikingStats.totalEntries,
|
||||
byType: vikingStats.categories,
|
||||
byAgent: { [agentId || 'zclaw-main']: vikingStats.agentMemories },
|
||||
oldestEntry: null,
|
||||
newestEntry: null,
|
||||
};
|
||||
} catch {
|
||||
// Fall back to local stats
|
||||
return getMemoryManager().stats(agentId);
|
||||
}
|
||||
}
|
||||
|
||||
return getMemoryManager().stats(agentId);
|
||||
}
|
||||
|
||||
async updateImportance(id: string, importance: number): Promise<void> {
|
||||
const backend = await this.getBackend();
|
||||
|
||||
if (backend === 'viking') {
|
||||
// OpenViking handles importance internally via access patterns
|
||||
console.log(`[VikingMemoryAdapter] Importance update for ${id}: ${importance}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return getMemoryManager().updateImportance(id, importance);
|
||||
}
|
||||
|
||||
// === Configuration ===
|
||||
|
||||
updateConfig(config: Partial<VikingMemoryConfig>): void {
|
||||
this.config = { ...this.config, ...config };
|
||||
// Reset availability check when config changes
|
||||
this.vikingAvailable = null;
|
||||
}
|
||||
|
||||
getConfig(): Readonly<VikingMemoryConfig> {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
getMode(): 'viking' | 'local' | 'unavailable' {
|
||||
if (!this.config.enabled) return 'local';
|
||||
if (this.vikingAvailable === true) return 'viking';
|
||||
if (this.vikingAvailable === false && this.config.fallbackToLocal) return 'local';
|
||||
return 'unavailable';
|
||||
}
|
||||
}
|
||||
|
||||
// === Singleton ===
|
||||
|
||||
let _instance: VikingMemoryAdapter | null = null;
|
||||
|
||||
export function getVikingMemoryAdapter(config?: Partial<VikingMemoryConfig>): VikingMemoryAdapter {
|
||||
if (!_instance || config) {
|
||||
_instance = new VikingMemoryAdapter(config);
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
export function resetVikingMemoryAdapter(): void {
|
||||
_instance = null;
|
||||
}
|
||||
Reference in New Issue
Block a user