diff --git a/scripts/build.mjs b/scripts/build.mjs index 9c74efd..07fb46d 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -39,4 +39,3 @@ function main() { } main(); -// AUDIT_MARKER diff --git a/scripts/migrate-tests.mjs b/scripts/migrate-tests.mjs index fe8b738..416768e 100644 --- a/scripts/migrate-tests.mjs +++ b/scripts/migrate-tests.mjs @@ -61,4 +61,3 @@ function migrateDirectory(dir) { } migrateDirectory("tests"); -// AUDIT_MARKER diff --git a/scripts/migrate-v1-to-v2.ts b/scripts/migrate-v1-to-v2.ts index bc85b44..ba08c2f 100644 --- a/scripts/migrate-v1-to-v2.ts +++ b/scripts/migrate-v1-to-v2.ts @@ -405,4 +405,3 @@ migrate(storagePath) console.error("Migration failed:", error); process.exit(1); }); -// AUDIT_MARKER diff --git a/src/config.ts b/src/config.ts index 4664030..0b5a8b2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -751,4 +751,3 @@ export function isConfigured(): boolean { // The system should check specific sub-configurations when features are invoked. return true; } -// AUDIT_MARKER diff --git a/src/index.ts b/src/index.ts index 47b4307..ee63844 100644 --- a/src/index.ts +++ b/src/index.ts @@ -88,7 +88,7 @@ export const OpenCodeMemPlugin: Plugin = async (ctx: PluginInput) => { duration: 3000, }, }) - .catch(() => {}); + .catch((err) => log("Toast display failed", { error: String(err) })); } }); @@ -103,7 +103,7 @@ export const OpenCodeMemPlugin: Plugin = async (ctx: PluginInput) => { duration: 5000, }, }) - .catch(() => {}); + .catch((err) => log("Toast display failed", { error: String(err) })); } } else { if (ctx.client?.tui) { @@ -116,7 +116,7 @@ export const OpenCodeMemPlugin: Plugin = async (ctx: PluginInput) => { duration: 3000, }, }) - .catch(() => {}); + .catch((err) => log("Toast display failed", { error: String(err) })); } } }) @@ -133,7 +133,7 @@ export const OpenCodeMemPlugin: Plugin = async (ctx: PluginInput) => { duration: 5000, }, }) - .catch(() => {}); + .catch((err) => log("Toast display failed", { error: String(err) })); } }); } @@ -278,7 +278,7 @@ export const OpenCodeMemPlugin: Plugin = async (ctx: PluginInput) => { duration: 5000, }, }) - .catch(() => {}); + .catch((err) => log("Toast display failed", { error: String(err) })); } } }, @@ -587,7 +587,7 @@ export const OpenCodeMemPlugin: Plugin = async (ctx: PluginInput) => { duration: 3000, }, }) - .catch(() => {}); + .catch((err) => log("Toast display failed", { error: String(err) })); } log("Compaction memory injected", { @@ -634,4 +634,3 @@ function formatMemoriesForCompaction(memories: any[]): string { return output; } -// AUDIT_MARKER diff --git a/src/plugin.ts b/src/plugin.ts index aea905b..085ca1b 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -6,4 +6,3 @@ export const id = typeof pkg.name === "string" && pkg.name.trim() ? pkg.name.trim() : "opencode-mem0"; export { OpenCodeMemPlugin }; export default { id, server: OpenCodeMemPlugin } satisfies PluginModule; -// AUDIT_MARKER diff --git a/src/services/ai/ai-provider-factory.ts b/src/services/ai/ai-provider-factory.ts index b85bea4..9737a4f 100644 --- a/src/services/ai/ai-provider-factory.ts +++ b/src/services/ai/ai-provider-factory.ts @@ -48,4 +48,3 @@ export class AIProviderFactory { return getAISessionManager().cleanupExpiredSessions(); } } -// AUDIT_MARKER diff --git a/src/services/ai/opencode-provider.ts b/src/services/ai/opencode-provider.ts index a8f1de4..53108fd 100644 --- a/src/services/ai/opencode-provider.ts +++ b/src/services/ai/opencode-provider.ts @@ -297,4 +297,3 @@ export async function generateStructuredOutput(options: { }); return result.output as T; } -// AUDIT_MARKER diff --git a/src/services/ai/provider-config.ts b/src/services/ai/provider-config.ts index 0cd5982..9913702 100644 --- a/src/services/ai/provider-config.ts +++ b/src/services/ai/provider-config.ts @@ -33,4 +33,3 @@ export function buildMemoryProviderConfig( iterationTimeout: overrides.iterationTimeout ?? config.autoCaptureIterationTimeout, }; } -// AUDIT_MARKER diff --git a/src/services/ai/providers/anthropic-messages.ts b/src/services/ai/providers/anthropic-messages.ts index 046bb3b..d488e59 100644 --- a/src/services/ai/providers/anthropic-messages.ts +++ b/src/services/ai/providers/anthropic-messages.ts @@ -237,4 +237,3 @@ export class AnthropicMessagesProvider extends BaseAIProvider { return null; } } -// AUDIT_MARKER diff --git a/src/services/ai/providers/base-provider.ts b/src/services/ai/providers/base-provider.ts index 39701d2..2de3545 100644 --- a/src/services/ai/providers/base-provider.ts +++ b/src/services/ai/providers/base-provider.ts @@ -61,4 +61,3 @@ export abstract class BaseAIProvider { abstract supportsSession(): boolean; } -// AUDIT_MARKER diff --git a/src/services/ai/providers/google-gemini.ts b/src/services/ai/providers/google-gemini.ts index a9b43b2..188485a 100644 --- a/src/services/ai/providers/google-gemini.ts +++ b/src/services/ai/providers/google-gemini.ts @@ -271,4 +271,3 @@ export class GoogleGeminiProvider extends BaseAIProvider { return { success: false, error: `Max iterations (${maxIterations}) reached`, iterations }; } } -// AUDIT_MARKER diff --git a/src/services/ai/providers/openai-chat-completion.ts b/src/services/ai/providers/openai-chat-completion.ts index 659f795..b62c537 100644 --- a/src/services/ai/providers/openai-chat-completion.ts +++ b/src/services/ai/providers/openai-chat-completion.ts @@ -434,4 +434,3 @@ export class OpenAIChatCompletionProvider extends BaseAIProvider { }; } } -// AUDIT_MARKER diff --git a/src/services/ai/providers/openai-responses.ts b/src/services/ai/providers/openai-responses.ts index f63942f..70ce73d 100644 --- a/src/services/ai/providers/openai-responses.ts +++ b/src/services/ai/providers/openai-responses.ts @@ -233,4 +233,3 @@ export class OpenAIResponsesProvider extends BaseAIProvider { return data; } } -// AUDIT_MARKER diff --git a/src/services/ai/session/ai-session-manager.ts b/src/services/ai/session/ai-session-manager.ts index fac8825..7499f1f 100644 --- a/src/services/ai/session/ai-session-manager.ts +++ b/src/services/ai/session/ai-session-manager.ts @@ -282,4 +282,3 @@ export const aiSessionManager = { return getAISessionManager().getLastSequence.bind(getAISessionManager()); }, } as unknown as AISessionManager; -// AUDIT_MARKER diff --git a/src/services/ai/session/session-types.ts b/src/services/ai/session/session-types.ts index f908f15..64c6cd0 100644 --- a/src/services/ai/session/session-types.ts +++ b/src/services/ai/session/session-types.ts @@ -41,4 +41,3 @@ export interface SessionUpdateParams { conversationId?: string; metadata?: Record; } -// AUDIT_MARKER diff --git a/src/services/ai/tools/tool-schema.ts b/src/services/ai/tools/tool-schema.ts index 14609ef..f48970c 100644 --- a/src/services/ai/tools/tool-schema.ts +++ b/src/services/ai/tools/tool-schema.ts @@ -62,4 +62,3 @@ export class ToolSchemaConverter { }; } } -// AUDIT_MARKER diff --git a/src/services/ai/validators/user-profile-validator.ts b/src/services/ai/validators/user-profile-validator.ts index 7a6ba90..845e106 100644 --- a/src/services/ai/validators/user-profile-validator.ts +++ b/src/services/ai/validators/user-profile-validator.ts @@ -118,4 +118,3 @@ export class UserProfileValidator { return errors; } } -// AUDIT_MARKER diff --git a/src/services/api-handlers.ts b/src/services/api-handlers.ts index 8adfcb7..b079ae9 100644 --- a/src/services/api-handlers.ts +++ b/src/services/api-handlers.ts @@ -142,12 +142,15 @@ export async function handleListMemories( includePrompts: boolean = true ): Promise>> { try { + const safePage = Number.isFinite(page) && page > 0 ? Math.min(Math.floor(page), 10000) : 1; + const safePageSize = + Number.isFinite(pageSize) && pageSize > 0 && pageSize <= 100 ? Math.floor(pageSize) : 20; await embeddingService.warmup(); let allMemories: any[] = []; if (tag) { const { scope: tagScope, hash } = extractScopeFromTag(tag); const shards = shardManager.getAllShards(tagScope, hash); - const limit = page * pageSize; // Fetch enough to cover the requested page across shards + const limit = safePage * safePageSize; // Fetch enough to cover the requested page across shards for (const shard of shards) { const db = connectionManager.getConnection(shard.dbPath); const memories = vectorSearch.listMemories(db, tag, limit); @@ -233,9 +236,9 @@ export async function handleListMemories( timeline = sortedTimeline; const total = timeline.length; - const totalPages = Math.ceil(total / pageSize); - const offset = (page - 1) * pageSize; - const paginatedResults = timeline.slice(offset, offset + pageSize); + const totalPages = Math.ceil(total / safePageSize); + const offset = (safePage - 1) * safePageSize; + const paginatedResults = timeline.slice(offset, offset + safePageSize); const items = paginatedResults.map((item: any) => { if (item.type === "memory") { @@ -270,7 +273,10 @@ export async function handleListMemories( } }); - return { success: true, data: { items, total, page, pageSize, totalPages } }; + return { + success: true, + data: { items, total, page: safePage, pageSize: safePageSize, totalPages }, + }; } catch (error) { log("handleListMemories: error", { error: String(error) }); return { success: false, error: "Internal error" }; @@ -491,6 +497,9 @@ export async function handleSearch( ): Promise>> { try { if (!query) return { success: false, error: "query is required" }; + const safePage = Number.isFinite(page) && page > 0 ? Math.min(Math.floor(page), 10000) : 1; + const safePageSize = + Number.isFinite(pageSize) && pageSize > 0 && pageSize <= 100 ? Math.floor(pageSize) : 20; await embeddingService.warmup(); const queryVector = await embeddingService.embedWithTimeout(query); let memoryResults: any[] = []; @@ -500,42 +509,35 @@ export async function handleSearch( const shards = shardManager.getAllShards(scope, hash); for (const shard of shards) { try { - const results = await vectorSearch.searchInShard(shard, queryVector, tag, pageSize * 2); + const results = await vectorSearch.searchInShard( + shard, + queryVector, + tag, + safePageSize * 2 + ); memoryResults.push(...results); } catch (error) { log("Shard search error", { shardId: shard.id, error: String(error) }); } } const projectPath = getProjectPathFromTag(tag); - promptResults = userPromptManager.searchPrompts(query, projectPath, pageSize * 2); + promptResults = userPromptManager.searchPrompts(query, projectPath, safePageSize * 2); } else { + const userShards = shardManager.getAllShards("user", ""); const projectShards = shardManager.getAllShards("project", ""); - const uniqueTags = new Set(); - for (const shard of projectShards) { - const db = connectionManager.getConnection(shard.dbPath); - const tags = vectorSearch.getDistinctTags(db); - for (const t of tags) { - if (t.container_tag) uniqueTags.add(t.container_tag); - } - } - for (const containerTag of uniqueTags) { - const { scope, hash } = extractScopeFromTag(containerTag); - const shards = shardManager.getAllShards(scope, hash); - for (const shard of shards) { - try { - const results = await vectorSearch.searchInShard( - shard, - queryVector, - containerTag, - pageSize - ); - memoryResults.push(...results); - } catch (error) { - log("Shard search error", { shardId: shard.id, error: String(error) }); - } + const searchedPaths = new Set(); + for (const shard of [...userShards, ...projectShards]) { + if (searchedPaths.has(shard.dbPath)) continue; + searchedPaths.add(shard.dbPath); + try { + const perShardLimit = Math.min(safePage * safePageSize, 500); + const results = await vectorSearch.searchInShard(shard, queryVector, "", perShardLimit); + memoryResults.push(...results); + } catch (error) { + log("Shard search error", { shardId: shard.id, error: String(error) }); } } - promptResults = userPromptManager.searchPrompts(query, undefined, pageSize * 2); + promptResults = userPromptManager.searchPrompts(query, undefined, safePageSize * 2); } const formattedPrompts: FormattedPrompt[] = promptResults.map((p) => ({ @@ -575,9 +577,12 @@ export async function handleSearch( ); const total = combinedResults.length; - const totalPages = Math.ceil(total / pageSize); - const offset = (page - 1) * pageSize; - const paginatedResults: SearchResultItem[] = combinedResults.slice(offset, offset + pageSize); + const totalPages = Math.ceil(total / safePageSize); + const offset = (safePage - 1) * safePageSize; + const paginatedResults: SearchResultItem[] = combinedResults.slice( + offset, + offset + safePageSize + ); const missingPromptIds = new Set(); const missingMemoryIds = new Set(); @@ -640,7 +645,10 @@ export async function handleSearch( } } - return { success: true, data: { items: paginatedResults, total, page, pageSize, totalPages } }; + return { + success: true, + data: { items: paginatedResults, total, page: safePage, pageSize: safePageSize, totalPages }, + }; } catch (error) { log("handleSearch: error", { error: String(error) }); return { success: false, error: "Internal error" }; @@ -1179,4 +1187,3 @@ export async function handleConflictStats(): Promise< return { success: false, error: "Internal error" }; } } -// AUDIT_MARKER diff --git a/src/services/auto-capture.ts b/src/services/auto-capture.ts index 84bd6f7..63db9f2 100644 --- a/src/services/auto-capture.ts +++ b/src/services/auto-capture.ts @@ -393,4 +393,3 @@ Analyze this conversation. If it contains technical work (code, bugs, features, tags: (result.data.tags || []).map((t: string) => t.toLowerCase().trim()), }; } -// AUDIT_MARKER diff --git a/src/services/cleanup-service.ts b/src/services/cleanup-service.ts index c56fdc0..ae227f8 100644 --- a/src/services/cleanup-service.ts +++ b/src/services/cleanup-service.ts @@ -131,4 +131,3 @@ export class CleanupService { } export const cleanupService = new CleanupService(); -// AUDIT_MARKER diff --git a/src/services/client.ts b/src/services/client.ts index 82f0dce..42a8bf3 100644 --- a/src/services/client.ts +++ b/src/services/client.ts @@ -444,4 +444,3 @@ export class LocalMemoryClient { } export const memoryClient = new LocalMemoryClient(); -// AUDIT_MARKER diff --git a/src/services/context.ts b/src/services/context.ts index 63731b0..db74cbf 100644 --- a/src/services/context.ts +++ b/src/services/context.ts @@ -40,4 +40,3 @@ export function formatContextForPrompt( return parts.join("\n"); } -// AUDIT_MARKER diff --git a/src/services/deduplication-service.ts b/src/services/deduplication-service.ts index ca2fe5f..c6928ec 100644 --- a/src/services/deduplication-service.ts +++ b/src/services/deduplication-service.ts @@ -182,4 +182,3 @@ export class DeduplicationService { } export const deduplicationService = new DeduplicationService(); -// AUDIT_MARKER diff --git a/src/services/embedding.ts b/src/services/embedding.ts index 87e879a..bcb292e 100644 --- a/src/services/embedding.ts +++ b/src/services/embedding.ts @@ -144,4 +144,3 @@ export class EmbeddingService { } export const embeddingService = EmbeddingService.getInstance(); -// AUDIT_MARKER diff --git a/src/services/jsonc.ts b/src/services/jsonc.ts index f7bafca..7959907 100644 --- a/src/services/jsonc.ts +++ b/src/services/jsonc.ts @@ -83,4 +83,3 @@ export function stripJsoncComments(content: string): string { // Remove trailing commas before } or ] return result.replace(/,\s*([}\]])/g, "$1"); } -// AUDIT_MARKER diff --git a/src/services/language-detector.ts b/src/services/language-detector.ts index 215805b..091af42 100644 --- a/src/services/language-detector.ts +++ b/src/services/language-detector.ts @@ -37,4 +37,3 @@ export function getLanguageName(code: string): string { } return lang?.name || "English"; } -// AUDIT_MARKER diff --git a/src/services/memory-conflicts.ts b/src/services/memory-conflicts.ts index 926a60b..321b83a 100644 --- a/src/services/memory-conflicts.ts +++ b/src/services/memory-conflicts.ts @@ -654,4 +654,3 @@ export function getAllUnresolvedConflicts( return allConflicts.sort((a, b) => b.detectedAt - a.detectedAt).slice(0, limit); } -// AUDIT_MARKER diff --git a/src/services/memory-lifecycle.ts b/src/services/memory-lifecycle.ts index f586ebb..d21710c 100644 --- a/src/services/memory-lifecycle.ts +++ b/src/services/memory-lifecycle.ts @@ -415,4 +415,3 @@ export async function runLifecycleMaintenance(): Promise { log("Lifecycle maintenance error", { error: String(error) }); } } -// AUDIT_MARKER diff --git a/src/services/memory-scoring-service.ts b/src/services/memory-scoring-service.ts index 0407a2e..bfbbfb5 100644 --- a/src/services/memory-scoring-service.ts +++ b/src/services/memory-scoring-service.ts @@ -239,4 +239,3 @@ export async function runOneTimeScoringRecalculation(): Promise<{ }> { return recalculateAllScores(true); } -// AUDIT_MARKER diff --git a/src/services/memory-scoring.ts b/src/services/memory-scoring.ts index b91dfb0..e9e4c40 100644 --- a/src/services/memory-scoring.ts +++ b/src/services/memory-scoring.ts @@ -619,4 +619,3 @@ export function recordAccess(currentAccessCount: number): { lastAccessed: Date.now(), }; } -// AUDIT_MARKER diff --git a/src/services/migration-service.ts b/src/services/migration-service.ts index 5670d4f..465c35b 100644 --- a/src/services/migration-service.ts +++ b/src/services/migration-service.ts @@ -350,4 +350,3 @@ export class MigrationService { } export const migrationService = new MigrationService(); -// AUDIT_MARKER diff --git a/src/services/platform-server.ts b/src/services/platform-server.ts index c2cfed1..015984b 100644 --- a/src/services/platform-server.ts +++ b/src/services/platform-server.ts @@ -107,4 +107,3 @@ export function serve(options: ServeOptions): Promise { return createNodeServer(options); } -// AUDIT_MARKER diff --git a/src/services/privacy.ts b/src/services/privacy.ts index 40725ac..e98a886 100644 --- a/src/services/privacy.ts +++ b/src/services/privacy.ts @@ -6,4 +6,3 @@ export function isFullyPrivate(content: string): boolean { const stripped = stripPrivateContent(content).trim(); return stripped === "[REDACTED]" || stripped === ""; } -// AUDIT_MARKER diff --git a/src/services/retrieval-context.ts b/src/services/retrieval-context.ts index 004eff9..c498a9b 100644 --- a/src/services/retrieval-context.ts +++ b/src/services/retrieval-context.ts @@ -164,4 +164,3 @@ export function calculateDiversityPenalty( return 0; } -// AUDIT_MARKER diff --git a/src/services/secret-resolver.ts b/src/services/secret-resolver.ts index 45dc482..b39f4f3 100644 --- a/src/services/secret-resolver.ts +++ b/src/services/secret-resolver.ts @@ -67,4 +67,3 @@ export function resolveSecretValue(value: string | undefined): string | undefine return value; } -// AUDIT_MARKER diff --git a/src/services/sqlite/connection-manager.ts b/src/services/sqlite/connection-manager.ts index 9f03bf0..ba5bea9 100644 --- a/src/services/sqlite/connection-manager.ts +++ b/src/services/sqlite/connection-manager.ts @@ -83,4 +83,3 @@ export class ConnectionManager { } export const connectionManager = new ConnectionManager(); -// AUDIT_MARKER diff --git a/src/services/sqlite/shard-manager.ts b/src/services/sqlite/shard-manager.ts index 5300f72..f2638b4 100644 --- a/src/services/sqlite/shard-manager.ts +++ b/src/services/sqlite/shard-manager.ts @@ -405,4 +405,3 @@ export class ShardManager { } export const shardManager = new ShardManager(); -// AUDIT_MARKER diff --git a/src/services/sqlite/sqlite-bootstrap.ts b/src/services/sqlite/sqlite-bootstrap.ts index 83a2c4e..6c2a6c4 100644 --- a/src/services/sqlite/sqlite-bootstrap.ts +++ b/src/services/sqlite/sqlite-bootstrap.ts @@ -126,4 +126,3 @@ export function getDatabase(): new (path: string) => Database { } return DatabaseImpl; } -// AUDIT_MARKER diff --git a/src/services/sqlite/transcript-manager.ts b/src/services/sqlite/transcript-manager.ts index a1dcf3e..42dd3a0 100644 --- a/src/services/sqlite/transcript-manager.ts +++ b/src/services/sqlite/transcript-manager.ts @@ -275,4 +275,3 @@ export class TranscriptManager { } export const transcriptManager = new TranscriptManager(); -// AUDIT_MARKER diff --git a/src/services/sqlite/types.ts b/src/services/sqlite/types.ts index 402bdc5..b59dc03 100644 --- a/src/services/sqlite/types.ts +++ b/src/services/sqlite/types.ts @@ -81,4 +81,3 @@ export interface SearchResult { contextBoost?: number; finalScore?: number; } -// AUDIT_MARKER diff --git a/src/services/sqlite/vector-search.ts b/src/services/sqlite/vector-search.ts index 6993dee..d61f8ea 100644 --- a/src/services/sqlite/vector-search.ts +++ b/src/services/sqlite/vector-search.ts @@ -675,4 +675,3 @@ export class VectorSearch { } export const vectorSearch = new VectorSearch(); -// AUDIT_MARKER diff --git a/src/services/tags.ts b/src/services/tags.ts index 51b312e..ce2ddc0 100644 --- a/src/services/tags.ts +++ b/src/services/tags.ts @@ -178,4 +178,3 @@ export function getTags(directory: string): { project: getProjectTagInfo(directory), }; } -// AUDIT_MARKER diff --git a/src/services/transcript-capture.ts b/src/services/transcript-capture.ts index 19374bf..e950504 100644 --- a/src/services/transcript-capture.ts +++ b/src/services/transcript-capture.ts @@ -92,4 +92,3 @@ export async function cleanupOldTranscripts(): Promise { return transcriptManager.deleteOldTranscripts(cutoffTime); } -// AUDIT_MARKER diff --git a/src/services/user-memory-learning.ts b/src/services/user-memory-learning.ts index aafdbb4..eb5fabc 100644 --- a/src/services/user-memory-learning.ts +++ b/src/services/user-memory-learning.ts @@ -306,4 +306,3 @@ Use the update_user_profile tool to save the ${existingProfile ? "updated" : "ne return rawData as UserProfileData; } -// AUDIT_MARKER diff --git a/src/services/user-profile/profile-context.ts b/src/services/user-profile/profile-context.ts index 12f20a3..6578d14 100644 --- a/src/services/user-profile/profile-context.ts +++ b/src/services/user-profile/profile-context.ts @@ -52,4 +52,3 @@ export function getUserProfileContext(userId: string): string | null { return parts.join("\n"); } -// AUDIT_MARKER diff --git a/src/services/user-profile/profile-utils.ts b/src/services/user-profile/profile-utils.ts index 17f40ef..8e58005 100644 --- a/src/services/user-profile/profile-utils.ts +++ b/src/services/user-profile/profile-utils.ts @@ -38,4 +38,3 @@ export const safeObject = (obj: any, fallback: T): T => { } return result && typeof result === "object" && !Array.isArray(result) ? (result as T) : fallback; }; -// AUDIT_MARKER diff --git a/src/services/user-profile/types.ts b/src/services/user-profile/types.ts index 8a01780..2705f33 100644 --- a/src/services/user-profile/types.ts +++ b/src/services/user-profile/types.ts @@ -48,4 +48,3 @@ export interface UserProfileChangelog { profileDataSnapshot: string; createdAt: number; } -// AUDIT_MARKER diff --git a/src/services/user-profile/user-profile-manager.ts b/src/services/user-profile/user-profile-manager.ts index d74f71d..7c65269 100644 --- a/src/services/user-profile/user-profile-manager.ts +++ b/src/services/user-profile/user-profile-manager.ts @@ -414,4 +414,3 @@ export class UserProfileManager { } export const userProfileManager = new UserProfileManager(); -// AUDIT_MARKER diff --git a/src/services/user-prompt/user-prompt-manager.ts b/src/services/user-prompt/user-prompt-manager.ts index a128836..835cc81 100644 --- a/src/services/user-prompt/user-prompt-manager.ts +++ b/src/services/user-prompt/user-prompt-manager.ts @@ -263,4 +263,3 @@ export class UserPromptManager { } export const userPromptManager = new UserPromptManager(); -// AUDIT_MARKER diff --git a/src/services/vector-backends/backend-factory.ts b/src/services/vector-backends/backend-factory.ts index b7e0aae..9aa76ec 100644 --- a/src/services/vector-backends/backend-factory.ts +++ b/src/services/vector-backends/backend-factory.ts @@ -118,4 +118,3 @@ export async function createVectorBackend( return exactScanBackend; } } -// AUDIT_MARKER diff --git a/src/services/vector-backends/exact-scan-backend.ts b/src/services/vector-backends/exact-scan-backend.ts index 7a03df2..f10ea80 100644 --- a/src/services/vector-backends/exact-scan-backend.ts +++ b/src/services/vector-backends/exact-scan-backend.ts @@ -118,4 +118,3 @@ export class ExactScanBackend implements VectorBackend { return dot / (Math.sqrt(magA) * Math.sqrt(magB)); } } -// AUDIT_MARKER diff --git a/src/services/vector-backends/types.ts b/src/services/vector-backends/types.ts index 7657389..7f34922 100644 --- a/src/services/vector-backends/types.ts +++ b/src/services/vector-backends/types.ts @@ -44,4 +44,3 @@ export interface VectorBackendFactoryOptions { probeUSearch?: () => Promise; createUSearchBackend?: () => VectorBackend; } -// AUDIT_MARKER diff --git a/src/services/vector-backends/usearch-backend.ts b/src/services/vector-backends/usearch-backend.ts index bfc602e..aaedd98 100644 --- a/src/services/vector-backends/usearch-backend.ts +++ b/src/services/vector-backends/usearch-backend.ts @@ -249,4 +249,3 @@ export class USearchBackend implements VectorBackend { } } } -// AUDIT_MARKER diff --git a/src/services/web-server-worker.ts b/src/services/web-server-worker.ts index ba74764..f3ee106 100644 --- a/src/services/web-server-worker.ts +++ b/src/services/web-server-worker.ts @@ -2,6 +2,7 @@ import { readFileSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { serve, type PlatformServer } from "./platform-server.js"; +import { stripPrivateContent } from "./privacy.js"; import { handleListTags, handleListMemories, @@ -35,6 +36,8 @@ interface WorkerMessage { type: "start" | "stop" | "status"; port?: number; host?: string; + apiKey?: string; + enabled?: boolean; } interface WorkerResponse { @@ -45,6 +48,42 @@ interface WorkerResponse { } let server: PlatformServer | null = null; +let workerConfig: { host?: string; apiKey?: string; enabled?: boolean } = {}; + +const LOCAL_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]); + +function isLocalhost(host: string): boolean { + return LOCAL_HOSTS.has(host); +} + +function redactPII(value: unknown): unknown { + if (typeof value === "string") { + // Strip tags + let redacted = stripPrivateContent(value); + // Redact email addresses + redacted = redacted.replace( + /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, + "[REDACTED_EMAIL]" + ); + return redacted; + } + if (Array.isArray(value)) { + return value.map(redactPII); + } + if (value && typeof value === "object") { + const result: Record = {}; + for (const [k, v] of Object.entries(value)) { + // Redact known sensitive keys entirely (exact match to avoid false positives like accessCount) + if (/^(apiKey|access_token|refresh_token|secret|password|token|authorization)$/i.test(k)) { + result[k] = "[REDACTED]"; + } else { + result[k] = redactPII(v); + } + } + return result; + } + return value; +} async function handleRequest(req: Request): Promise { const url = new URL(req.url); @@ -52,6 +91,16 @@ async function handleRequest(req: Request): Promise { const method = req.method; try { + // Auth: enforce API key for non-localhost API access + const isApiPath = path.startsWith("/api/"); + const requiresAuth = + isApiPath && workerConfig.apiKey && workerConfig.host && !isLocalhost(workerConfig.host); + if (requiresAuth) { + const apiKey = req.headers.get("x-opencode-mem-key"); + if (apiKey !== workerConfig.apiKey) { + return jsonResponse({ success: false, error: "Unauthorized" }, 401); + } + } if (path === "/" || path === "/index.html") { return serveStaticFile("index.html", "text/html"); } @@ -75,8 +124,11 @@ async function handleRequest(req: Request): Promise { if (path === "/api/memories" && method === "GET") { const tag = url.searchParams.get("tag") || undefined; - const page = parseInt(url.searchParams.get("page") || "1"); - const pageSize = parseInt(url.searchParams.get("pageSize") || "20"); + const rawPage = parseInt(url.searchParams.get("page") || "1"); + const rawPageSize = parseInt(url.searchParams.get("pageSize") || "20"); + const page = Number.isFinite(rawPage) && rawPage > 0 ? Math.min(rawPage, 10000) : 1; + const pageSize = + Number.isFinite(rawPageSize) && rawPageSize > 0 && rawPageSize <= 100 ? rawPageSize : 20; const includePrompts = url.searchParams.get("includePrompts") !== "false"; const result = await handleListMemories(tag, page, pageSize, includePrompts); return jsonResponse(result); @@ -119,8 +171,11 @@ async function handleRequest(req: Request): Promise { if (path === "/api/search" && method === "GET") { const query = url.searchParams.get("q"); const tag = url.searchParams.get("tag") || undefined; - const page = parseInt(url.searchParams.get("page") || "1"); - const pageSize = parseInt(url.searchParams.get("pageSize") || "20"); + const rawPage = parseInt(url.searchParams.get("page") || "1"); + const rawPageSize = parseInt(url.searchParams.get("pageSize") || "20"); + const page = Number.isFinite(rawPage) && rawPage > 0 ? Math.min(rawPage, 10000) : 1; + const pageSize = + Number.isFinite(rawPageSize) && rawPageSize > 0 && rawPageSize <= 100 ? rawPageSize : 20; if (!query) { return jsonResponse({ success: false, error: "query parameter required" }); @@ -291,7 +346,8 @@ function serveStaticFile(filename: string, contentType: string): Response { } function jsonResponse(data: any, status: number = 200): Response { - return new Response(JSON.stringify(data), { + const redacted = redactPII(data); + return new Response(JSON.stringify(redacted), { status, headers: { "Content-Type": "application/json", @@ -315,6 +371,12 @@ self.onmessage = async (event: MessageEvent) => { return; } + workerConfig = { + host: message.host, + apiKey: message.apiKey, + enabled: message.enabled, + }; + server = await serve({ port: message.port!, hostname: message.host!, @@ -367,4 +429,3 @@ self.onmessage = async (event: MessageEvent) => { } as WorkerResponse); } }; -// AUDIT_MARKER diff --git a/src/services/web-server.ts b/src/services/web-server.ts index debc177..34e0478 100644 --- a/src/services/web-server.ts +++ b/src/services/web-server.ts @@ -270,8 +270,11 @@ export class WebServer { if (path === "/api/memories" && method === "GET") { const tag = url.searchParams.get("tag") || undefined; - const page = parseInt(url.searchParams.get("page") || "1"); - const pageSize = parseInt(url.searchParams.get("pageSize") || "20"); + const rawPage = parseInt(url.searchParams.get("page") || "1"); + const rawPageSize = parseInt(url.searchParams.get("pageSize") || "20"); + const page = Number.isFinite(rawPage) && rawPage > 0 ? Math.min(rawPage, 10000) : 1; + const pageSize = + Number.isFinite(rawPageSize) && rawPageSize > 0 && rawPageSize <= 100 ? rawPageSize : 20; const includePrompts = url.searchParams.get("includePrompts") !== "false"; const result = await handleListMemories(tag, page, pageSize, includePrompts); return this.jsonResponse(result, 200, !isLocal); @@ -314,8 +317,11 @@ export class WebServer { if (path === "/api/search" && method === "GET") { const query = url.searchParams.get("q"); const tag = url.searchParams.get("tag") || undefined; - const page = parseInt(url.searchParams.get("page") || "1"); - const pageSize = parseInt(url.searchParams.get("pageSize") || "20"); + const rawPage = parseInt(url.searchParams.get("page") || "1"); + const rawPageSize = parseInt(url.searchParams.get("pageSize") || "20"); + const page = Number.isFinite(rawPage) && rawPage > 0 ? Math.min(rawPage, 10000) : 1; + const pageSize = + Number.isFinite(rawPageSize) && rawPageSize > 0 && rawPageSize <= 100 ? rawPageSize : 20; if (!query) { return this.jsonResponse({ success: false, error: "query parameter required" }); @@ -524,4 +530,3 @@ export async function startWebServer(config: WebServerConfig): Promise ${t("badge-memory")} - ${memory.memoryType ? `${memory.memoryType}` : ""} + ${memory.memoryType ? `${escapeHtml(memory.memoryType)}` : ""} ${similarityHtml} ${isPinned ? `${t("badge-pinned")}` : ""} ${escapeHtml(memory.displayName || memory.id)} @@ -286,7 +286,7 @@ function renderMemoryCard(memory) {
- ${memory.memoryType ? `${memory.memoryType}` : ""} + ${memory.memoryType ? `${escapeHtml(memory.memoryType)}` : ""} ${isLinked ? ` ${t("badge-linked")}` : ""} ${similarityHtml} ${isPinned ? `${t("badge-pinned")}` : ""} @@ -903,7 +903,7 @@ function renderUserProfile() { container.innerHTML = `
-

${profile.message}

+

${escapeHtml(profile.message)}

`; lucide.createIcons(); @@ -948,7 +948,7 @@ function renderUserProfile() { container.innerHTML = `
-

${profile.displayName || profile.userId}

+

${escapeHtml(profile.displayName || profile.userId)}

${t("profile-version")} @@ -1392,4 +1392,3 @@ document.addEventListener("DOMContentLoaded", async () => { lucide.createIcons(); }); -// AUDIT_MARKER diff --git a/src/web/i18n.js b/src/web/i18n.js index afe8c69..e02929f 100644 --- a/src/web/i18n.js +++ b/src/web/i18n.js @@ -265,4 +265,3 @@ window.t = t; window.getLanguage = getLanguage; window.setLanguage = setLanguage; window.applyLanguage = applyLanguage; -// AUDIT_MARKER diff --git a/tests/ai-provider-config.test.ts b/tests/ai-provider-config.test.ts index bb7316d..4f90730 100644 --- a/tests/ai-provider-config.test.ts +++ b/tests/ai-provider-config.test.ts @@ -239,4 +239,3 @@ describe("AI provider config", () => { expect(capturedBody?.temperature).toBeUndefined(); }); }); -// AUDIT_MARKER diff --git a/tests/anthropic-provider.test.ts b/tests/anthropic-provider.test.ts index 3b7534e..9738756 100644 --- a/tests/anthropic-provider.test.ts +++ b/tests/anthropic-provider.test.ts @@ -105,4 +105,3 @@ describe("AnthropicMessagesProvider", () => { expect(capturedBody?.max_tokens).toBe(2048); }); }); -// AUDIT_MARKER diff --git a/tests/config-resolution.test.ts b/tests/config-resolution.test.ts index 621df5f..e5003f7 100644 --- a/tests/config-resolution.test.ts +++ b/tests/config-resolution.test.ts @@ -82,4 +82,3 @@ describe("project-scoped config resolution", () => { expect(CONFIG.opencodeProvider).toBeUndefined(); }); }); -// AUDIT_MARKER diff --git a/tests/config.test.ts b/tests/config.test.ts index 8cf9ae7..a113f53 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -108,4 +108,3 @@ describe("config", () => { }); }); }); -// AUDIT_MARKER diff --git a/tests/language-detector.test.ts b/tests/language-detector.test.ts index f8f4643..2875117 100644 --- a/tests/language-detector.test.ts +++ b/tests/language-detector.test.ts @@ -42,4 +42,3 @@ describe("getLanguageName", () => { expect(getLanguageName("xyz")).toBe("English"); }); }); -// AUDIT_MARKER diff --git a/tests/memory-engine.test.ts b/tests/memory-engine.test.ts index 51ca59e..aeb0843 100644 --- a/tests/memory-engine.test.ts +++ b/tests/memory-engine.test.ts @@ -722,4 +722,3 @@ describe("Memory Engine Integration", () => { }); }); }); -// AUDIT_MARKER diff --git a/tests/memory-scope.test.ts b/tests/memory-scope.test.ts index 040376b..5bad8b7 100644 --- a/tests/memory-scope.test.ts +++ b/tests/memory-scope.test.ts @@ -126,4 +126,3 @@ describe("memory scope", () => { expect(res.results.map((r: any) => r.id)).toEqual(["shard-a", "shard-b"]); }); }); -// AUDIT_MARKER diff --git a/tests/openai-chat-completion-provider.test.ts b/tests/openai-chat-completion-provider.test.ts index 4266fda..c9a921f 100644 --- a/tests/openai-chat-completion-provider.test.ts +++ b/tests/openai-chat-completion-provider.test.ts @@ -444,4 +444,3 @@ describe("OpenAIChatCompletionProvider", () => { expect(result.success).toBe(false); }); }); -// AUDIT_MARKER diff --git a/tests/opencode-provider.test.ts b/tests/opencode-provider.test.ts index ffb8758..c1a6766 100644 --- a/tests/opencode-provider.test.ts +++ b/tests/opencode-provider.test.ts @@ -177,4 +177,3 @@ describe("state management", () => { expect(isProviderConnected("gemini")).toBe(false); }); }); -// AUDIT_MARKER diff --git a/tests/plugin-loader-contract.test.ts b/tests/plugin-loader-contract.test.ts index 57a97dc..292a95e 100644 --- a/tests/plugin-loader-contract.test.ts +++ b/tests/plugin-loader-contract.test.ts @@ -81,4 +81,3 @@ describe("OpenCode 1.3.x plugin-loader contract", () => { } }); }); -// AUDIT_MARKER diff --git a/tests/privacy.test.ts b/tests/privacy.test.ts index 27699d1..399192d 100644 --- a/tests/privacy.test.ts +++ b/tests/privacy.test.ts @@ -72,4 +72,3 @@ describe("privacy", () => { }); }); }); -// AUDIT_MARKER diff --git a/tests/profile-tool-runtime.test.ts b/tests/profile-tool-runtime.test.ts index bcad12b..06f0fa8 100644 --- a/tests/profile-tool-runtime.test.ts +++ b/tests/profile-tool-runtime.test.ts @@ -210,4 +210,3 @@ describe("memory tool profile runtime behavior", () => { ); }); }); -// AUDIT_MARKER diff --git a/tests/profile-write.test.ts b/tests/profile-write.test.ts index b524a81..30ab96a 100644 --- a/tests/profile-write.test.ts +++ b/tests/profile-write.test.ts @@ -211,4 +211,3 @@ describe("UserProfileManager – explicit preference writes", () => { expect(last.changeType).toBe("update"); }); }); -// AUDIT_MARKER diff --git a/tests/project-scope.test.ts b/tests/project-scope.test.ts index 4f74c40..26a7d41 100644 --- a/tests/project-scope.test.ts +++ b/tests/project-scope.test.ts @@ -75,4 +75,3 @@ describe("project scope identity", () => { expect(rootTag.projectPath).toBe(nestedTag.projectPath); }); }); -// AUDIT_MARKER diff --git a/tests/tags.test.ts b/tests/tags.test.ts index 8d46a2b..dd1bd18 100644 --- a/tests/tags.test.ts +++ b/tests/tags.test.ts @@ -30,4 +30,3 @@ describe("tags", () => { }); }); }); -// AUDIT_MARKER diff --git a/tests/tool-scope.test.ts b/tests/tool-scope.test.ts index 100eb5f..b5b20b0 100644 --- a/tests/tool-scope.test.ts +++ b/tests/tool-scope.test.ts @@ -170,4 +170,3 @@ describe("tool memory scope", () => { expect(lastListScope).toBe("project"); }); }); -// AUDIT_MARKER diff --git a/tests/vector-backends/backend-factory.test.ts b/tests/vector-backends/backend-factory.test.ts index d1fb013..bd6f68b 100644 --- a/tests/vector-backends/backend-factory.test.ts +++ b/tests/vector-backends/backend-factory.test.ts @@ -117,4 +117,3 @@ describe("vector backend factory", () => { expect(backend.getBackendName()).toBe("exact-scan"); }); }); -// AUDIT_MARKER diff --git a/tests/vector-backends/exact-scan-backend.test.ts b/tests/vector-backends/exact-scan-backend.test.ts index 14885f6..fc1cbfd 100644 --- a/tests/vector-backends/exact-scan-backend.test.ts +++ b/tests/vector-backends/exact-scan-backend.test.ts @@ -110,4 +110,3 @@ describe("ExactScanBackend", () => { expect(result).toEqual([]); }); }); -// AUDIT_MARKER diff --git a/tests/vector-backends/migration-fallback.test.ts b/tests/vector-backends/migration-fallback.test.ts index 7e768d7..509bee9 100644 --- a/tests/vector-backends/migration-fallback.test.ts +++ b/tests/vector-backends/migration-fallback.test.ts @@ -88,4 +88,3 @@ describe("migration with backend abstraction", () => { expect(results.map((r) => r.id)).toEqual(["a"]); }); }); -// AUDIT_MARKER diff --git a/tests/vector-backends/usearch-backend.test.ts b/tests/vector-backends/usearch-backend.test.ts index b27111e..a4599e3 100644 --- a/tests/vector-backends/usearch-backend.test.ts +++ b/tests/vector-backends/usearch-backend.test.ts @@ -148,4 +148,3 @@ describe("USearchBackend", () => { expect(result.map((x) => x.id)).toEqual(["alpha"]); }); }); -// AUDIT_MARKER diff --git a/tests/vector-search-backend-integration.test.ts b/tests/vector-search-backend-integration.test.ts index 89bc54e..6d5683b 100644 --- a/tests/vector-search-backend-integration.test.ts +++ b/tests/vector-search-backend-integration.test.ts @@ -221,4 +221,3 @@ describe("vector search backend integration", () => { expect(typeof results[0]?.similarity).toBe("number"); }); }); -// AUDIT_MARKER diff --git a/tests/windows-path.test.ts b/tests/windows-path.test.ts index c3d6b54..18fd0a2 100644 --- a/tests/windows-path.test.ts +++ b/tests/windows-path.test.ts @@ -94,4 +94,3 @@ describe("Windows Path Handling", () => { }); }); }); -// AUDIT_MARKER