@burtson-labs/bandit-engine 2.0.164 → 2.0.166

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +4 -1
  2. package/dist/aiProviderStore-HY5G4QAK.mjs +8 -0
  3. package/dist/chat-RCMZYACB.mjs +15 -0
  4. package/dist/chat-provider.js +186 -111
  5. package/dist/chat-provider.js.map +1 -1
  6. package/dist/chat-provider.mjs +7 -8
  7. package/dist/{chunk-J6SWNO7Z.mjs → chunk-33OSALGS.mjs} +25 -7
  8. package/dist/{chunk-J6SWNO7Z.mjs.map → chunk-33OSALGS.mjs.map} +1 -1
  9. package/dist/{chunk-5KBTOCQZ.mjs → chunk-6KRAPGXE.mjs} +13 -85
  10. package/dist/chunk-6KRAPGXE.mjs.map +1 -0
  11. package/dist/{chunk-VBHI4TJC.mjs → chunk-75OBTJPN.mjs} +6 -6
  12. package/dist/{chunk-URZSIL7X.mjs → chunk-A6W6XIDD.mjs} +7 -7
  13. package/dist/{chunk-CQJGUYRW.mjs → chunk-GRPTCHFN.mjs} +150 -42
  14. package/dist/chunk-GRPTCHFN.mjs.map +1 -0
  15. package/dist/{chunk-OCVZVBQQ.mjs → chunk-HNXPDIWC.mjs} +12 -3
  16. package/dist/chunk-HNXPDIWC.mjs.map +1 -0
  17. package/dist/{chunk-IZX2J65Q.mjs → chunk-IB2LPSQQ.mjs} +2 -2
  18. package/dist/{chunk-PNQRS5EN.mjs → chunk-M4DWPNZZ.mjs} +11 -11
  19. package/dist/{chunk-O6P37PFR.mjs → chunk-MM3NRLVR.mjs} +4 -4
  20. package/dist/{chunk-WLNZL6SP.mjs → chunk-Q2DINRIU.mjs} +6 -6
  21. package/dist/{chunk-WLNZL6SP.mjs.map → chunk-Q2DINRIU.mjs.map} +1 -1
  22. package/dist/{chunk-JYBQAKSZ.mjs → chunk-WSZE3DDJ.mjs} +8 -8
  23. package/dist/{chunk-TY5VL3SG.mjs → chunk-YH5YWK4K.mjs} +3 -3
  24. package/dist/index.d.mts +97 -4
  25. package/dist/index.d.ts +97 -4
  26. package/dist/index.js +200 -133
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +22 -21
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/management/management.js +171 -123
  31. package/dist/management/management.js.map +1 -1
  32. package/dist/management/management.mjs +10 -11
  33. package/dist/{memoryUtils-2FVXDSS6.mjs → memoryUtils-HGCYEUCD.mjs} +1 -3
  34. package/dist/{memoryUtils-2FVXDSS6.mjs.map → memoryUtils-HGCYEUCD.mjs.map} +1 -1
  35. package/dist/modals/chat-modal/chat-modal.js +161 -33
  36. package/dist/modals/chat-modal/chat-modal.js.map +1 -1
  37. package/dist/modals/chat-modal/chat-modal.mjs +6 -7
  38. package/dist/modelStore-F6JQUXGG.mjs +8 -0
  39. package/dist/shared/custom-element.mjs +0 -1
  40. package/docs/03_provider_integration.md +5 -2
  41. package/package.json +1 -1
  42. package/dist/aiProviderStore-PEGSX4PK.mjs +0 -9
  43. package/dist/chat-OSAVW6L2.mjs +0 -16
  44. package/dist/chunk-5KBTOCQZ.mjs.map +0 -1
  45. package/dist/chunk-BJTO5JO5.mjs +0 -11
  46. package/dist/chunk-CQJGUYRW.mjs.map +0 -1
  47. package/dist/chunk-OCVZVBQQ.mjs.map +0 -1
  48. package/dist/modelStore-7XYYDCNC.mjs +0 -9
  49. package/dist/modelStore-7XYYDCNC.mjs.map +0 -1
  50. /package/dist/{aiProviderStore-PEGSX4PK.mjs.map → aiProviderStore-HY5G4QAK.mjs.map} +0 -0
  51. /package/dist/{chat-OSAVW6L2.mjs.map → chat-RCMZYACB.mjs.map} +0 -0
  52. /package/dist/{chunk-VBHI4TJC.mjs.map → chunk-75OBTJPN.mjs.map} +0 -0
  53. /package/dist/{chunk-URZSIL7X.mjs.map → chunk-A6W6XIDD.mjs.map} +0 -0
  54. /package/dist/{chunk-IZX2J65Q.mjs.map → chunk-IB2LPSQQ.mjs.map} +0 -0
  55. /package/dist/{chunk-PNQRS5EN.mjs.map → chunk-M4DWPNZZ.mjs.map} +0 -0
  56. /package/dist/{chunk-O6P37PFR.mjs.map → chunk-MM3NRLVR.mjs.map} +0 -0
  57. /package/dist/{chunk-JYBQAKSZ.mjs.map → chunk-WSZE3DDJ.mjs.map} +0 -0
  58. /package/dist/{chunk-TY5VL3SG.mjs.map → chunk-YH5YWK4K.mjs.map} +0 -0
  59. /package/dist/{chunk-BJTO5JO5.mjs.map → modelStore-F6JQUXGG.mjs.map} +0 -0
@@ -1,16 +1,15 @@
1
1
  import {
2
2
  management_default
3
- } from "../chunk-PNQRS5EN.mjs";
4
- import "../chunk-IZX2J65Q.mjs";
5
- import "../chunk-5KBTOCQZ.mjs";
6
- import "../chunk-O6P37PFR.mjs";
7
- import "../chunk-VBHI4TJC.mjs";
8
- import "../chunk-WLNZL6SP.mjs";
9
- import "../chunk-J6SWNO7Z.mjs";
10
- import "../chunk-TY5VL3SG.mjs";
11
- import "../chunk-CQJGUYRW.mjs";
12
- import "../chunk-OCVZVBQQ.mjs";
13
- import "../chunk-BJTO5JO5.mjs";
3
+ } from "../chunk-M4DWPNZZ.mjs";
4
+ import "../chunk-IB2LPSQQ.mjs";
5
+ import "../chunk-6KRAPGXE.mjs";
6
+ import "../chunk-MM3NRLVR.mjs";
7
+ import "../chunk-75OBTJPN.mjs";
8
+ import "../chunk-Q2DINRIU.mjs";
9
+ import "../chunk-33OSALGS.mjs";
10
+ import "../chunk-YH5YWK4K.mjs";
11
+ import "../chunk-GRPTCHFN.mjs";
12
+ import "../chunk-HNXPDIWC.mjs";
14
13
  export {
15
14
  management_default as default
16
15
  };
@@ -1,5 +1,3 @@
1
- import "./chunk-BJTO5JO5.mjs";
2
-
3
1
  // src/utils/memoryUtils.ts
4
2
  var isRecord = (value) => typeof value === "object" && value !== null;
5
3
  var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
@@ -220,4 +218,4 @@ export {
220
218
  generateProgressMessage,
221
219
  validateMemories
222
220
  };
223
- //# sourceMappingURL=memoryUtils-2FVXDSS6.mjs.map
221
+ //# sourceMappingURL=memoryUtils-HGCYEUCD.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/memoryUtils.ts"],"sourcesContent":["/*\n * Copyright (c) 2025 Burtson Labs LLC.\n * SPDX-License-Identifier: BUSL-1.1\n * See LICENSE for the applicable terms.\n */\n\n\nexport interface BulkMemoryResult {\n success: boolean;\n message: string;\n totalProcessed: number;\n successCount: number;\n failureCount: number;\n errors: string[];\n}\n\nexport interface MemoryImportOptions {\n mode: 'append' | 'replace' | 'smartDedupe';\n clearExisting?: boolean;\n includeMetadata?: boolean;\n batchSize?: number;\n}\n\ntype MemoryRecordCandidate = {\n content?: unknown;\n title?: unknown;\n tags?: unknown;\n timestamp?: unknown;\n};\n\ntype BatchOperationResult = {\n summary?: {\n totalProcessed?: number;\n successCount?: number;\n failureCount?: number;\n };\n totalMemories?: number;\n successCount?: number;\n failureCount?: number;\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isStringArray = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every((item) => typeof item === 'string');\n\nconst resolveTimestampLabel = (timestamp: unknown): string => {\n if (timestamp instanceof Date && !Number.isNaN(timestamp.getTime())) {\n return timestamp.toLocaleDateString();\n }\n\n if (typeof timestamp === 'number' && Number.isFinite(timestamp)) {\n const date = new Date(timestamp);\n if (!Number.isNaN(date.getTime())) {\n return date.toLocaleDateString();\n }\n }\n\n if (typeof timestamp === 'string' && timestamp.trim()) {\n const date = new Date(timestamp);\n if (!Number.isNaN(date.getTime())) {\n return date.toLocaleDateString();\n }\n }\n\n return new Date().toLocaleDateString();\n};\n\n/**\n * Memory Utilities for Bulk Operations\n * \n * Provides helper functions for batch memory operations,\n * data formatting, and migration utilities.\n */\nexport class MemoryUtils {\n /**\n * Validate memory content before bulk operations\n */\n static validateMemories(memories: Array<{ content: string; title?: string; tags?: string[] }>): {\n valid: Array<{ content: string; title?: string; tags?: string[] }>;\n invalid: Array<{ content: string; error: string; index: number }>;\n } {\n const valid: Array<{ content: string; title?: string; tags?: string[] }> = [];\n const invalid: Array<{ content: string; error: string; index: number }> = [];\n\n memories.forEach((memory, index) => {\n // Check content length\n if (!memory.content || memory.content.trim().length === 0) {\n invalid.push({\n content: memory.content || '',\n error: 'Empty content',\n index\n });\n return;\n }\n\n // Check maximum content length (e.g., 50KB)\n if (memory.content.length > 50000) {\n invalid.push({\n content: memory.content.substring(0, 100) + '...',\n error: 'Content too long (max 50KB)',\n index\n });\n return;\n }\n\n // Check tags format\n if (memory.tags && (!Array.isArray(memory.tags) || memory.tags.some(tag => typeof tag !== 'string'))) {\n invalid.push({\n content: memory.content.substring(0, 100) + '...',\n error: 'Invalid tags format (must be string array)',\n index\n });\n return;\n }\n\n valid.push(memory);\n });\n\n return { valid, invalid };\n }\n\n /**\n * Format IndexedDB memories for vector import\n */\n static formatForVectorImport(indexedDBMemories: ReadonlyArray<unknown>): Array<{ content: string; title?: string; tags?: string[] }> {\n return indexedDBMemories.reduce<Array<{ content: string; title?: string; tags?: string[] }>>((acc, entry) => {\n if (!isRecord(entry)) {\n return acc;\n }\n\n const candidate = entry as MemoryRecordCandidate;\n if (typeof candidate.content !== 'string') {\n return acc;\n }\n\n const title = typeof candidate.title === 'string' && candidate.title.trim().length > 0\n ? candidate.title\n : `Memory from ${resolveTimestampLabel(candidate.timestamp)}`;\n\n const tags = isStringArray(candidate.tags) ? candidate.tags : [];\n\n acc.push({\n content: candidate.content,\n title,\n tags\n });\n return acc;\n }, []);\n }\n\n /**\n * Chunk large batch operations for better performance\n */\n static chunkMemories<T>(\n memories: T[], \n chunkSize: number = 100\n ): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < memories.length; i += chunkSize) {\n chunks.push(memories.slice(i, i + chunkSize));\n }\n return chunks;\n }\n\n /**\n * Estimate batch operation time and resource usage\n */\n static estimateBatchOperation(memoryCount: number, averageContentLength: number): {\n estimatedTimeMinutes: number;\n estimatedTokens: number;\n recommendedChunkSize: number;\n warnings: string[];\n } {\n const warnings: string[] = [];\n \n // Rough estimates based on typical embedding performance\n const embeddingsPerMinute = 100; // Conservative estimate\n const tokensPerCharacter = 0.25; // Rough estimate for token calculation\n \n const estimatedTimeMinutes = Math.ceil(memoryCount / embeddingsPerMinute);\n const estimatedTokens = Math.ceil(memoryCount * averageContentLength * tokensPerCharacter);\n \n // Recommend smaller chunks for large operations\n let recommendedChunkSize = 50;\n if (memoryCount > 1000) {\n recommendedChunkSize = 25;\n warnings.push('Large batch operation - consider running during off-peak hours');\n }\n if (averageContentLength > 2000) {\n recommendedChunkSize = Math.max(10, recommendedChunkSize / 2);\n warnings.push('Large content size - reduced chunk size recommended');\n }\n if (estimatedTimeMinutes > 30) {\n warnings.push('Long operation expected - ensure stable internet connection');\n }\n\n return {\n estimatedTimeMinutes,\n estimatedTokens,\n recommendedChunkSize,\n warnings\n };\n }\n\n /**\n * Create summary report for batch operations\n */\n static createBatchSummary(\n result: BatchOperationResult,\n operationType: 'import' | 'create' | 'migrate',\n startTime: Date\n ): {\n summary: string;\n details: {\n operation: string;\n duration: string;\n totalMemories: number;\n successful: number;\n failed: number;\n successRate: string;\n averageTimePerMemory: string;\n };\n } {\n const endTime = new Date();\n const durationMs = endTime.getTime() - startTime.getTime();\n const durationMinutes = Math.floor(durationMs / 60000);\n const durationSeconds = Math.floor((durationMs % 60000) / 1000);\n \n const totalMemories = result.summary?.totalProcessed ?? result.totalMemories ?? 0;\n const successful = result.summary?.successCount ?? result.successCount ?? 0;\n const failed = result.summary?.failureCount ?? result.failureCount ?? 0;\n const successRate = totalMemories > 0 ? ((successful / totalMemories) * 100).toFixed(1) : '0';\n const avgTimePerMemory = totalMemories > 0 ? (durationMs / totalMemories).toFixed(0) : '0';\n\n const summary = `${operationType.toUpperCase()} completed: ${successful}/${totalMemories} memories processed successfully (${successRate}% success rate) in ${durationMinutes}m ${durationSeconds}s`;\n\n const details = {\n operation: operationType,\n duration: `${durationMinutes}m ${durationSeconds}s`,\n totalMemories,\n successful,\n failed,\n successRate: `${successRate}%`,\n averageTimePerMemory: `${avgTimePerMemory}ms`\n };\n\n return { summary, details };\n }\n\n /**\n * Generate progress messages for UI updates\n */\n static generateProgressMessage(\n current: number, \n total: number, \n operation: string = 'Processing'\n ): string {\n const percentage = Math.floor((current / total) * 100);\n const remaining = total - current;\n \n if (current === 0) {\n return `Starting ${operation.toLowerCase()}...`;\n } else if (current === total) {\n return `${operation} completed!`;\n } else if (current < total / 4) {\n return `${operation} memories... (${current}/${total})`;\n } else if (current < total / 2) {\n return `${percentage}% complete... (${remaining} remaining)`;\n } else if (current < total * 0.9) {\n return `Almost done... ${percentage}% complete`;\n } else {\n return `Finishing up... ${remaining} memories left`;\n }\n }\n\n /**\n * Compare memory content for deduplication\n */\n static calculateSimilarity(content1: string, content2: string): number {\n // Simple similarity check - could be enhanced with more sophisticated algorithms\n const words1 = content1.toLowerCase().split(/\\s+/);\n const words2 = content2.toLowerCase().split(/\\s+/);\n \n const set1 = new Set(words1);\n const set2 = new Set(words2);\n \n const intersection = new Set([...set1].filter(word => set2.has(word)));\n const union = new Set([...set1, ...set2]);\n \n return union.size > 0 ? intersection.size / union.size : 0;\n }\n\n /**\n * Find potential duplicates in memory list\n */\n static findPotentialDuplicates(\n memories: Array<{ content: string; title?: string; tags?: string[] }>,\n similarityThreshold: number = 0.8\n ): Array<{\n memory: { content: string; title?: string; tags?: string[] };\n duplicates: Array<{\n memory: { content: string; title?: string; tags?: string[] };\n similarity: number;\n index: number;\n }>;\n }> {\n const duplicates: Array<{\n memory: { content: string; title?: string; tags?: string[] };\n duplicates: Array<{\n memory: { content: string; title?: string; tags?: string[] };\n similarity: number;\n index: number;\n }>;\n }> = [];\n\n for (let i = 0; i < memories.length; i++) {\n const currentMemory = memories[i];\n const potentialDuplicates: Array<{\n memory: { content: string; title?: string; tags?: string[] };\n similarity: number;\n index: number;\n }> = [];\n\n for (let j = i + 1; j < memories.length; j++) {\n const compareMemory = memories[j];\n const similarity = this.calculateSimilarity(currentMemory.content, compareMemory.content);\n \n if (similarity >= similarityThreshold) {\n potentialDuplicates.push({\n memory: compareMemory,\n similarity,\n index: j\n });\n }\n }\n\n if (potentialDuplicates.length > 0) {\n duplicates.push({\n memory: currentMemory,\n duplicates: potentialDuplicates\n });\n }\n }\n\n return duplicates;\n }\n}\n\n// Export utility functions for direct use\nexport const validateMemories = MemoryUtils.validateMemories;\nexport const formatForVectorImport = MemoryUtils.formatForVectorImport;\nexport const chunkMemories = MemoryUtils.chunkMemories;\nexport const estimateBatchOperation = MemoryUtils.estimateBatchOperation;\nexport const createBatchSummary = MemoryUtils.createBatchSummary;\nexport const generateProgressMessage = MemoryUtils.generateProgressMessage;\nexport const findPotentialDuplicates = MemoryUtils.findPotentialDuplicates;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/utils/memoryUtils.ts"],"sourcesContent":["/*\n * Copyright (c) 2025 Burtson Labs LLC.\n * SPDX-License-Identifier: BUSL-1.1\n * See LICENSE for the applicable terms.\n */\n\n\nexport interface BulkMemoryResult {\n success: boolean;\n message: string;\n totalProcessed: number;\n successCount: number;\n failureCount: number;\n errors: string[];\n}\n\nexport interface MemoryImportOptions {\n mode: 'append' | 'replace' | 'smartDedupe';\n clearExisting?: boolean;\n includeMetadata?: boolean;\n batchSize?: number;\n}\n\ntype MemoryRecordCandidate = {\n content?: unknown;\n title?: unknown;\n tags?: unknown;\n timestamp?: unknown;\n};\n\ntype BatchOperationResult = {\n summary?: {\n totalProcessed?: number;\n successCount?: number;\n failureCount?: number;\n };\n totalMemories?: number;\n successCount?: number;\n failureCount?: number;\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isStringArray = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every((item) => typeof item === 'string');\n\nconst resolveTimestampLabel = (timestamp: unknown): string => {\n if (timestamp instanceof Date && !Number.isNaN(timestamp.getTime())) {\n return timestamp.toLocaleDateString();\n }\n\n if (typeof timestamp === 'number' && Number.isFinite(timestamp)) {\n const date = new Date(timestamp);\n if (!Number.isNaN(date.getTime())) {\n return date.toLocaleDateString();\n }\n }\n\n if (typeof timestamp === 'string' && timestamp.trim()) {\n const date = new Date(timestamp);\n if (!Number.isNaN(date.getTime())) {\n return date.toLocaleDateString();\n }\n }\n\n return new Date().toLocaleDateString();\n};\n\n/**\n * Memory Utilities for Bulk Operations\n * \n * Provides helper functions for batch memory operations,\n * data formatting, and migration utilities.\n */\nexport class MemoryUtils {\n /**\n * Validate memory content before bulk operations\n */\n static validateMemories(memories: Array<{ content: string; title?: string; tags?: string[] }>): {\n valid: Array<{ content: string; title?: string; tags?: string[] }>;\n invalid: Array<{ content: string; error: string; index: number }>;\n } {\n const valid: Array<{ content: string; title?: string; tags?: string[] }> = [];\n const invalid: Array<{ content: string; error: string; index: number }> = [];\n\n memories.forEach((memory, index) => {\n // Check content length\n if (!memory.content || memory.content.trim().length === 0) {\n invalid.push({\n content: memory.content || '',\n error: 'Empty content',\n index\n });\n return;\n }\n\n // Check maximum content length (e.g., 50KB)\n if (memory.content.length > 50000) {\n invalid.push({\n content: memory.content.substring(0, 100) + '...',\n error: 'Content too long (max 50KB)',\n index\n });\n return;\n }\n\n // Check tags format\n if (memory.tags && (!Array.isArray(memory.tags) || memory.tags.some(tag => typeof tag !== 'string'))) {\n invalid.push({\n content: memory.content.substring(0, 100) + '...',\n error: 'Invalid tags format (must be string array)',\n index\n });\n return;\n }\n\n valid.push(memory);\n });\n\n return { valid, invalid };\n }\n\n /**\n * Format IndexedDB memories for vector import\n */\n static formatForVectorImport(indexedDBMemories: ReadonlyArray<unknown>): Array<{ content: string; title?: string; tags?: string[] }> {\n return indexedDBMemories.reduce<Array<{ content: string; title?: string; tags?: string[] }>>((acc, entry) => {\n if (!isRecord(entry)) {\n return acc;\n }\n\n const candidate = entry as MemoryRecordCandidate;\n if (typeof candidate.content !== 'string') {\n return acc;\n }\n\n const title = typeof candidate.title === 'string' && candidate.title.trim().length > 0\n ? candidate.title\n : `Memory from ${resolveTimestampLabel(candidate.timestamp)}`;\n\n const tags = isStringArray(candidate.tags) ? candidate.tags : [];\n\n acc.push({\n content: candidate.content,\n title,\n tags\n });\n return acc;\n }, []);\n }\n\n /**\n * Chunk large batch operations for better performance\n */\n static chunkMemories<T>(\n memories: T[], \n chunkSize: number = 100\n ): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < memories.length; i += chunkSize) {\n chunks.push(memories.slice(i, i + chunkSize));\n }\n return chunks;\n }\n\n /**\n * Estimate batch operation time and resource usage\n */\n static estimateBatchOperation(memoryCount: number, averageContentLength: number): {\n estimatedTimeMinutes: number;\n estimatedTokens: number;\n recommendedChunkSize: number;\n warnings: string[];\n } {\n const warnings: string[] = [];\n \n // Rough estimates based on typical embedding performance\n const embeddingsPerMinute = 100; // Conservative estimate\n const tokensPerCharacter = 0.25; // Rough estimate for token calculation\n \n const estimatedTimeMinutes = Math.ceil(memoryCount / embeddingsPerMinute);\n const estimatedTokens = Math.ceil(memoryCount * averageContentLength * tokensPerCharacter);\n \n // Recommend smaller chunks for large operations\n let recommendedChunkSize = 50;\n if (memoryCount > 1000) {\n recommendedChunkSize = 25;\n warnings.push('Large batch operation - consider running during off-peak hours');\n }\n if (averageContentLength > 2000) {\n recommendedChunkSize = Math.max(10, recommendedChunkSize / 2);\n warnings.push('Large content size - reduced chunk size recommended');\n }\n if (estimatedTimeMinutes > 30) {\n warnings.push('Long operation expected - ensure stable internet connection');\n }\n\n return {\n estimatedTimeMinutes,\n estimatedTokens,\n recommendedChunkSize,\n warnings\n };\n }\n\n /**\n * Create summary report for batch operations\n */\n static createBatchSummary(\n result: BatchOperationResult,\n operationType: 'import' | 'create' | 'migrate',\n startTime: Date\n ): {\n summary: string;\n details: {\n operation: string;\n duration: string;\n totalMemories: number;\n successful: number;\n failed: number;\n successRate: string;\n averageTimePerMemory: string;\n };\n } {\n const endTime = new Date();\n const durationMs = endTime.getTime() - startTime.getTime();\n const durationMinutes = Math.floor(durationMs / 60000);\n const durationSeconds = Math.floor((durationMs % 60000) / 1000);\n \n const totalMemories = result.summary?.totalProcessed ?? result.totalMemories ?? 0;\n const successful = result.summary?.successCount ?? result.successCount ?? 0;\n const failed = result.summary?.failureCount ?? result.failureCount ?? 0;\n const successRate = totalMemories > 0 ? ((successful / totalMemories) * 100).toFixed(1) : '0';\n const avgTimePerMemory = totalMemories > 0 ? (durationMs / totalMemories).toFixed(0) : '0';\n\n const summary = `${operationType.toUpperCase()} completed: ${successful}/${totalMemories} memories processed successfully (${successRate}% success rate) in ${durationMinutes}m ${durationSeconds}s`;\n\n const details = {\n operation: operationType,\n duration: `${durationMinutes}m ${durationSeconds}s`,\n totalMemories,\n successful,\n failed,\n successRate: `${successRate}%`,\n averageTimePerMemory: `${avgTimePerMemory}ms`\n };\n\n return { summary, details };\n }\n\n /**\n * Generate progress messages for UI updates\n */\n static generateProgressMessage(\n current: number, \n total: number, \n operation: string = 'Processing'\n ): string {\n const percentage = Math.floor((current / total) * 100);\n const remaining = total - current;\n \n if (current === 0) {\n return `Starting ${operation.toLowerCase()}...`;\n } else if (current === total) {\n return `${operation} completed!`;\n } else if (current < total / 4) {\n return `${operation} memories... (${current}/${total})`;\n } else if (current < total / 2) {\n return `${percentage}% complete... (${remaining} remaining)`;\n } else if (current < total * 0.9) {\n return `Almost done... ${percentage}% complete`;\n } else {\n return `Finishing up... ${remaining} memories left`;\n }\n }\n\n /**\n * Compare memory content for deduplication\n */\n static calculateSimilarity(content1: string, content2: string): number {\n // Simple similarity check - could be enhanced with more sophisticated algorithms\n const words1 = content1.toLowerCase().split(/\\s+/);\n const words2 = content2.toLowerCase().split(/\\s+/);\n \n const set1 = new Set(words1);\n const set2 = new Set(words2);\n \n const intersection = new Set([...set1].filter(word => set2.has(word)));\n const union = new Set([...set1, ...set2]);\n \n return union.size > 0 ? intersection.size / union.size : 0;\n }\n\n /**\n * Find potential duplicates in memory list\n */\n static findPotentialDuplicates(\n memories: Array<{ content: string; title?: string; tags?: string[] }>,\n similarityThreshold: number = 0.8\n ): Array<{\n memory: { content: string; title?: string; tags?: string[] };\n duplicates: Array<{\n memory: { content: string; title?: string; tags?: string[] };\n similarity: number;\n index: number;\n }>;\n }> {\n const duplicates: Array<{\n memory: { content: string; title?: string; tags?: string[] };\n duplicates: Array<{\n memory: { content: string; title?: string; tags?: string[] };\n similarity: number;\n index: number;\n }>;\n }> = [];\n\n for (let i = 0; i < memories.length; i++) {\n const currentMemory = memories[i];\n const potentialDuplicates: Array<{\n memory: { content: string; title?: string; tags?: string[] };\n similarity: number;\n index: number;\n }> = [];\n\n for (let j = i + 1; j < memories.length; j++) {\n const compareMemory = memories[j];\n const similarity = this.calculateSimilarity(currentMemory.content, compareMemory.content);\n \n if (similarity >= similarityThreshold) {\n potentialDuplicates.push({\n memory: compareMemory,\n similarity,\n index: j\n });\n }\n }\n\n if (potentialDuplicates.length > 0) {\n duplicates.push({\n memory: currentMemory,\n duplicates: potentialDuplicates\n });\n }\n }\n\n return duplicates;\n }\n}\n\n// Export utility functions for direct use\nexport const validateMemories = MemoryUtils.validateMemories;\nexport const formatForVectorImport = MemoryUtils.formatForVectorImport;\nexport const chunkMemories = MemoryUtils.chunkMemories;\nexport const estimateBatchOperation = MemoryUtils.estimateBatchOperation;\nexport const createBatchSummary = MemoryUtils.createBatchSummary;\nexport const generateProgressMessage = MemoryUtils.generateProgressMessage;\nexport const findPotentialDuplicates = MemoryUtils.findPotentialDuplicates;\n"],"mappings":"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","names":[]}
@@ -72,11 +72,20 @@ var init_debugLogger = __esm({
72
72
  */
73
73
  debug(message, context) {
74
74
  }
75
+ verboseEnabled() {
76
+ if (this.isDevelopment) return true;
77
+ try {
78
+ return typeof localStorage !== "undefined" && localStorage.getItem("bandit:debug") === "1";
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
75
83
  /**
76
- * Info logs - shown in both development and production
77
- * Used for important user-facing information
84
+ * Info logs - shown in development, or in production when localStorage
85
+ * `bandit:debug` is "1"
78
86
  */
79
87
  info(message, context) {
88
+ if (!this.verboseEnabled()) return;
80
89
  const prefix = this.isDevelopment ? "\u2139\uFE0F [INFO]" : "[INFO]";
81
90
  if (context) {
82
91
  console.info(`${prefix} ${message}`, context);
@@ -1441,31 +1450,42 @@ var init_anthropic_provider = __esm({
1441
1450
  });
1442
1451
 
1443
1452
  // src/store/authenticationStore.ts
1444
- var import_zustand2, TOKEN_KEY, validToken, user, rawToken, useAuthenticationStore;
1453
+ var import_zustand2, TOKEN_KEY, decodeClaims, validOrNull, readLegacyStorage, validToken, user, rawToken, useAuthenticationStore;
1445
1454
  var init_authenticationStore = __esm({
1446
1455
  "src/store/authenticationStore.ts"() {
1447
1456
  "use strict";
1448
1457
  import_zustand2 = require("zustand");
1449
1458
  TOKEN_KEY = "authToken";
1450
- validToken = null;
1451
- user = null;
1452
- rawToken = localStorage.getItem(TOKEN_KEY);
1453
- if (rawToken) {
1459
+ decodeClaims = (token) => {
1454
1460
  try {
1455
- const base64Url = rawToken.split(".")[1];
1461
+ const base64Url = token.split(".")[1];
1456
1462
  const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
1457
1463
  const jsonPayload = decodeURIComponent(
1458
1464
  atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
1459
1465
  );
1460
- const decoded = JSON.parse(jsonPayload);
1461
- if (decoded.exp * 1e3 > Date.now()) {
1462
- validToken = rawToken;
1463
- user = decoded;
1464
- }
1466
+ return JSON.parse(jsonPayload);
1465
1467
  } catch {
1466
- validToken = null;
1467
- user = null;
1468
+ return null;
1469
+ }
1470
+ };
1471
+ validOrNull = (raw) => {
1472
+ if (!raw) return null;
1473
+ const decoded = decodeClaims(raw);
1474
+ return decoded && decoded.exp * 1e3 > Date.now() ? raw : null;
1475
+ };
1476
+ readLegacyStorage = () => {
1477
+ try {
1478
+ return typeof localStorage === "undefined" ? null : localStorage.getItem(TOKEN_KEY);
1479
+ } catch {
1480
+ return null;
1468
1481
  }
1482
+ };
1483
+ validToken = null;
1484
+ user = null;
1485
+ rawToken = readLegacyStorage();
1486
+ if (rawToken) {
1487
+ validToken = validOrNull(rawToken);
1488
+ user = validToken ? decodeClaims(validToken) : null;
1469
1489
  }
1470
1490
  useAuthenticationStore = (0, import_zustand2.create)((set) => ({
1471
1491
  token: validToken,
@@ -1473,16 +1493,10 @@ var init_authenticationStore = __esm({
1473
1493
  authError: null,
1474
1494
  setToken: (token) => {
1475
1495
  if (token) {
1476
- try {
1477
- const base64Url = token.split(".")[1];
1478
- const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
1479
- const jsonPayload = decodeURIComponent(
1480
- atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
1481
- );
1482
- const user2 = JSON.parse(jsonPayload);
1483
- localStorage.setItem(TOKEN_KEY, token);
1484
- set({ token, authError: null, user: user2 });
1485
- } catch {
1496
+ const decoded = decodeClaims(token);
1497
+ if (decoded) {
1498
+ set({ token, authError: null, user: decoded });
1499
+ } else {
1486
1500
  set({ token: null, authError: "Invalid token", user: null });
1487
1501
  }
1488
1502
  } else {
@@ -1490,7 +1504,6 @@ var init_authenticationStore = __esm({
1490
1504
  }
1491
1505
  },
1492
1506
  clearToken: () => {
1493
- localStorage.removeItem(TOKEN_KEY);
1494
1507
  set({ token: null, authError: null, user: null });
1495
1508
  }
1496
1509
  }));
@@ -1506,7 +1519,7 @@ function emitAuthTokenChanged(token) {
1506
1519
  detail: { token }
1507
1520
  }));
1508
1521
  }
1509
- var TOKEN_KEY2, AUTH_TOKEN_CHANGED_EVENT, AuthenticationService, authenticationService;
1522
+ var TOKEN_KEY2, AUTH_TOKEN_CHANGED_EVENT, readLocalStorage, writeLocalStorage, AuthenticationService, authenticationService;
1510
1523
  var init_authenticationService = __esm({
1511
1524
  "src/services/auth/authenticationService.ts"() {
1512
1525
  "use strict";
@@ -1514,21 +1527,103 @@ var init_authenticationService = __esm({
1514
1527
  init_debugLogger();
1515
1528
  TOKEN_KEY2 = "authToken";
1516
1529
  AUTH_TOKEN_CHANGED_EVENT = "bandit:auth-token-changed";
1530
+ readLocalStorage = () => {
1531
+ try {
1532
+ return typeof localStorage === "undefined" ? null : localStorage.getItem(TOKEN_KEY2);
1533
+ } catch {
1534
+ return null;
1535
+ }
1536
+ };
1537
+ writeLocalStorage = (token) => {
1538
+ try {
1539
+ if (typeof localStorage === "undefined") return;
1540
+ if (token) localStorage.setItem(TOKEN_KEY2, token);
1541
+ else localStorage.removeItem(TOKEN_KEY2);
1542
+ } catch {
1543
+ }
1544
+ };
1517
1545
  AuthenticationService = class {
1546
+ storageMode = "localStorage";
1547
+ persistence = null;
1548
+ handlers = {};
1549
+ /** Bumped on every setToken/clearToken; async flows compare it to detect a superseded session. */
1550
+ revision = 0;
1551
+ refreshInFlight = null;
1552
+ lastPersist = Promise.resolve();
1553
+ /**
1554
+ * Pick the storage contract for this host. Call once at boot, before the
1555
+ * first `setToken`. Switching modes never moves a token by itself: the
1556
+ * host migrates (e.g. reads the legacy localStorage value, then clears it).
1557
+ */
1558
+ configureSession(config) {
1559
+ this.storageMode = config.storage;
1560
+ this.persistence = config.persistence ?? null;
1561
+ this.handlers = config.handlers ?? {};
1562
+ }
1563
+ getStorageMode() {
1564
+ return this.storageMode;
1565
+ }
1566
+ getSessionRevision() {
1567
+ return this.revision;
1568
+ }
1518
1569
  getToken() {
1519
- const token = localStorage.getItem(TOKEN_KEY2);
1520
- return token;
1570
+ if (this.storageMode === "memory") {
1571
+ return useAuthenticationStore.getState().token;
1572
+ }
1573
+ return readLocalStorage();
1521
1574
  }
1522
1575
  setToken(token) {
1523
- localStorage.setItem(TOKEN_KEY2, token);
1576
+ this.revision += 1;
1577
+ if (this.storageMode === "localStorage") {
1578
+ writeLocalStorage(token);
1579
+ } else {
1580
+ writeLocalStorage(null);
1581
+ }
1524
1582
  useAuthenticationStore.getState().setToken(token);
1583
+ this.mirror(() => this.persistence?.write(token));
1525
1584
  emitAuthTokenChanged(token);
1526
1585
  }
1527
1586
  clearToken() {
1528
- localStorage.removeItem(TOKEN_KEY2);
1587
+ this.revision += 1;
1588
+ const current = this.getToken() ?? useAuthenticationStore.getState().token;
1589
+ try {
1590
+ this.handlers.beforeClear?.(current);
1591
+ } catch (error) {
1592
+ debugLogger.warn("Auth beforeClear handler failed", { error });
1593
+ }
1594
+ writeLocalStorage(null);
1529
1595
  useAuthenticationStore.getState().clearToken();
1596
+ this.mirror(() => this.persistence?.clear());
1530
1597
  emitAuthTokenChanged(null);
1531
1598
  }
1599
+ /** True when the host registered a silent refresh path. */
1600
+ canRefresh() {
1601
+ return typeof this.handlers.refresh === "function";
1602
+ }
1603
+ /**
1604
+ * Ask the host for a fresh token. Concurrent callers share one attempt.
1605
+ * Resolves false when no refresh path exists or it did not produce a token.
1606
+ */
1607
+ tryRefresh() {
1608
+ const refresh = this.handlers.refresh;
1609
+ if (!refresh) return Promise.resolve(false);
1610
+ if (this.refreshInFlight) return this.refreshInFlight;
1611
+ this.refreshInFlight = (async () => {
1612
+ try {
1613
+ return await refresh();
1614
+ } catch (error) {
1615
+ debugLogger.warn("Auth refresh handler failed", { error });
1616
+ return false;
1617
+ } finally {
1618
+ this.refreshInFlight = null;
1619
+ }
1620
+ })();
1621
+ return this.refreshInFlight;
1622
+ }
1623
+ /** Resolves once the most recent persistence write/clear has settled. */
1624
+ whenPersisted() {
1625
+ return this.lastPersist;
1626
+ }
1532
1627
  isAuthenticated() {
1533
1628
  const token = useAuthenticationStore.getState().token;
1534
1629
  return !!token && !this.isTokenExpired(token);
@@ -1558,6 +1653,16 @@ var init_authenticationService = __esm({
1558
1653
  return null;
1559
1654
  }
1560
1655
  }
1656
+ mirror(run) {
1657
+ const attempt = (async () => {
1658
+ try {
1659
+ await run();
1660
+ } catch (error) {
1661
+ debugLogger.warn("Auth token persistence failed", { error });
1662
+ }
1663
+ })();
1664
+ this.lastPersist = attempt;
1665
+ }
1561
1666
  };
1562
1667
  authenticationService = new AuthenticationService();
1563
1668
  }
@@ -1613,13 +1718,35 @@ var init_gateway_service = __esm({
1613
1718
  });
1614
1719
  instance.interceptors.response.use(
1615
1720
  (response) => response,
1616
- (error) => {
1721
+ async (error) => {
1722
+ const retried = await this._retryAfterRefresh(instance, error);
1723
+ if (retried) return retried;
1617
1724
  this._handleAuthFailure(error);
1618
1725
  return Promise.reject(this._normalizeAxiosError(error));
1619
1726
  }
1620
1727
  );
1621
1728
  return instance;
1622
1729
  }
1730
+ /**
1731
+ * A bare 401 on a host with a silent refresh path (HttpOnly refresh cookie,
1732
+ * native device token) usually means the access token aged out mid-session.
1733
+ * Refresh once and replay the request with the new token instead of
1734
+ * signing the user out; a failed refresh falls through to the sign-out path.
1735
+ */
1736
+ async _retryAfterRefresh(instance, error) {
1737
+ const config = error.config;
1738
+ if (!config || config._banditAuthRetried || error.response?.status !== 401) return null;
1739
+ if (!authenticationService.canRefresh()) return null;
1740
+ const data = error.response?.data;
1741
+ if (data?.code && data.code !== "TOKEN_EXPIRED") return null;
1742
+ try {
1743
+ if (!await authenticationService.tryRefresh()) return null;
1744
+ } catch {
1745
+ return null;
1746
+ }
1747
+ config._banditAuthRetried = true;
1748
+ return instance.request(config);
1749
+ }
1623
1750
  _normalizeAxiosError(error) {
1624
1751
  if (error.response) {
1625
1752
  return this._createHttpError(
@@ -7150,6 +7277,7 @@ var import_react5 = require("react");
7150
7277
  // src/contexts/FeatureFlagContext.tsx
7151
7278
  var import_react4 = require("react");
7152
7279
  init_debugLogger();
7280
+ init_authenticationService();
7153
7281
  var import_jsx_runtime4 = require("react/jsx-runtime");
7154
7282
  var FeatureFlagContext = (0, import_react4.createContext)(void 0);
7155
7283
 
@@ -11546,7 +11674,7 @@ var VoiceService = class _VoiceService {
11546
11674
  }
11547
11675
  const token = authenticationService.getToken();
11548
11676
  if (!token) {
11549
- debugLogger.warn("No JWT token available - skipping voice models fetch");
11677
+ debugLogger.info("No JWT token available - skipping voice models fetch");
11550
11678
  const emptyModels = {
11551
11679
  models: [],
11552
11680
  defaultModel: "",