UNPKG

@tencentdb-agent-memory/memory-tencentdb

Version:

Four-layer local memory system plugin for OpenClaw — auto-captures, structures, and profiles conversational knowledge using local LLM + SQLite vector search (L0→L1→L2→L3 pipeline)

113 lines (107 loc) 3.79 kB
/** * Recall error taxonomy (H-15). * * Design: keep fast-fail throw semantics at the lowest level (searchMemories etc.), * but classify errors into structured RecallError types so the top-level handler * (performAutoRecallInner) can translate them into RecallResult.error rather than * propagating as raw rejections. Gateway layer then returns HTTP 200 + envelope.code * to distinguish "no recall results" vs "recall failed". * * Numeric code space: * 1xxxx — config (non-retryable, usually requires ops action) * 2xxxx — dependency (retryable, transient) * 3xxxx — storage (retryable) * 9xxxx — internal (non-retryable, indicates a bug) * * The codes are stable wire contract; never reuse a number for a different meaning. */ export type RecallErrorCategory = "config" | "dependency" | "storage" | "internal"; export interface RecallError { /** Stable numeric business code; 0 = ok (success path never emits a RecallError). */ code: number; category: RecallErrorCategory; /** Safe message — must not contain paths, stack traces, SQL fragments, or credentials. */ message: string; /** Whether the client may retry the same request and reasonably expect a different outcome. */ retryable: boolean; } /** * Thrown at the lowest level (e.g. searchMemories) and caught at the top-level * (performAutoRecallInner). Carries a RecallError plus an optional `cause` for logging. */ export class RecallFailure extends Error { constructor(public readonly recallError: RecallError, public readonly cause?: unknown) { super(recallError.message); this.name = "RecallFailure"; } } /** * Factory functions — centralizes the numeric code → message mapping so we never * accidentally emit two different `code` values for the same error category. */ export const RecallErrors = { configMissingEmbedding(strategy: string): RecallFailure { return new RecallFailure({ code: 10001, category: "config", message: `Recall strategy "${strategy}" requires EmbeddingService but it is not available.`, retryable: false, }); }, configInvalidStrategy(strategy: string): RecallFailure { return new RecallFailure({ code: 10002, category: "config", message: `Unknown recall strategy "${strategy}".`, retryable: false, }); }, dependencyTimeout(op: string): RecallFailure { return new RecallFailure({ code: 20001, category: "dependency", message: `Recall ${op} timed out`, retryable: true, }); }, dependencyUnavailable(op: string, cause?: unknown): RecallFailure { return new RecallFailure({ code: 20002, category: "dependency", message: `Recall ${op} service unavailable`, retryable: true, }, cause); }, storageError(op: string, cause?: unknown): RecallFailure { return new RecallFailure({ code: 30001, category: "storage", message: `Recall storage operation failed: ${op}`, retryable: true, }, cause); }, internal(cause?: unknown): RecallFailure { return new RecallFailure({ code: 90001, category: "internal", message: "Internal recall error", retryable: false, }, cause); }, }; /** * Type guard / converter: turn any unknown thrown value into a RecallFailure. * * - RecallFailure → pass-through * - AbortError / TimeoutError → dependencyTimeout * - everything else → internal (wraps original as `cause`) */ export function toRecallFailure(err: unknown): RecallFailure { if (err instanceof RecallFailure) return err; if (err instanceof Error) { if (err.name === "AbortError" || err.name === "TimeoutError") { return RecallErrors.dependencyTimeout("recall"); } } return RecallErrors.internal(err); }