Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/JAVA/Openclaw/extensions/memory-core/src/memory/   (Universität von Manchester ©)  Datei vom 26.3.2026 mit Größe 28 kB image not shown  

Quelle  manager.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import type { DatabaseSync } from "node:sqlite";
import { type FSWatcher } from "chokidar";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import {
  createSubsystemLogger,
  resolveAgentDir,
  resolveAgentWorkspaceDir,
  resolveMemorySearchConfig,
  type OpenClawConfig,
  type ResolvedMemorySearchConfig,
} from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
import { extractKeywords } from "openclaw/plugin-sdk/memory-core-host-engine-qmd";
import {
  readMemoryFile,
  type MemoryEmbeddingProbeResult,
  type MemoryProviderStatus,
  type MemorySearchManager,
  type MemorySearchRuntimeDebug,
  type MemorySearchResult,
  type MemorySource,
  type MemorySyncProgressUpdate,
} from "openclaw/plugin-sdk/memory-core-host-engine-storage";
import {
  createEmbeddingProvider,
  type EmbeddingProvider,
  type EmbeddingProviderId,
  type EmbeddingProviderRequest,
  type EmbeddingProviderResult,
  type EmbeddingProviderRuntime,
} from "./embeddings.js";
import { bm25RankToScore, buildFtsQuery, mergeHybridResults } from "./hybrid.js";
import { awaitPendingManagerWork, startAsyncSearchSync } from "./manager-async-state.js";
import { MEMORY_BATCH_FAILURE_LIMIT } from "./manager-batch-state.js";
import {
  closeManagedCacheEntries,
  getOrCreateManagedCacheEntry,
  resolveSingletonManagedCache,
} from "./manager-cache.js";
import { MemoryManagerEmbeddingOps } from "./manager-embedding-ops.js";
import {
  resolveMemoryPrimaryProviderRequest,
  resolveMemoryProviderState,
} from "./manager-provider-state.js";
import { resolveMemorySearchPreflight } from "./manager-search-preflight.js";
import { searchKeyword, searchVector } from "./manager-search.js";
import {
  collectMemoryStatusAggregate,
  resolveInitialMemoryDirty,
  resolveStatusProviderInfo,
} from "./manager-status-state.js";
import {
  enqueueMemoryTargetedSessionSync,
  extractMemoryErrorReason,
  isMemoryReadonlyDbError,
  runMemorySyncWithReadonlyRecovery,
  type MemoryReadonlyRecoveryState,
} from "./manager-sync-control.js";
import { applyTemporalDecayToHybridResults } from "./temporal-decay.js";
const SNIPPET_MAX_CHARS = 700;
const VECTOR_TABLE = "chunks_vec";
const FTS_TABLE = "chunks_fts";
const EMBEDDING_CACHE_TABLE = "embedding_cache";
const MEMORY_INDEX_MANAGER_CACHE_KEY = Symbol.for("openclaw.memoryIndexManagerCache");
const log = createSubsystemLogger("memory");

const { cache: INDEX_CACHE, pending: INDEX_CACHE_PENDING } =
  resolveSingletonManagedCache<MemoryIndexManager>(MEMORY_INDEX_MANAGER_CACHE_KEY);
export async function closeAllMemoryIndexManagers(): Promise<void> {
  await closeManagedCacheEntries({
    cache: INDEX_CACHE,
    pending: INDEX_CACHE_PENDING,
    onCloseError: (err) => {
      log.warn(`failed to close memory index manager: ${String(err)}`);
    },
  });
}

export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements MemorySearchManager {
  private readonly cacheKey: string;
  protected readonly cfg: OpenClawConfig;
  protected readonly agentId: string;
  protected readonly workspaceDir: string;
  protected readonly settings: ResolvedMemorySearchConfig;
  protected provider: EmbeddingProvider | null;
  private readonly requestedProvider: EmbeddingProviderRequest;
  private providerInitPromise: Promise<void> | null = null;
  private providerInitialized = false;
  protected fallbackFrom?: EmbeddingProviderId;
  protected fallbackReason?: string;
  private providerUnavailableReason?: string;
  protected providerRuntime?: EmbeddingProviderRuntime;
  protected batch: {
    enabled: boolean;
    wait: boolean;
    concurrency: number;
    pollIntervalMs: number;
    timeoutMs: number;
  };
  protected batchFailureCount = 0;
  protected batchFailureLastError?: string;
  protected batchFailureLastProvider?: string;
  protected batchFailureLock: Promise<void> = Promise.resolve();
  protected db: DatabaseSync;
  protected readonly sources: Set<MemorySource>;
  protected providerKey: string;
  protected readonly cache: { enabled: boolean; maxEntries?: number };
  protected readonly vector: {
    enabled: boolean;
    available: boolean | null;
    extensionPath?: string;
    loadError?: string;
    dims?: number;
  };
  protected readonly fts: {
    enabled: boolean;
    available: boolean;
    loadError?: string;
  };
  protected vectorReady: Promise<boolean> | null = null;
  protected watcher: FSWatcher | null = null;
  protected watchTimer: NodeJS.Timeout | null = null;
  protected sessionWatchTimer: NodeJS.Timeout | null = null;
  protected sessionUnsubscribe: (() => void) | null = null;
  protected intervalTimer: NodeJS.Timeout | null = null;
  protected closed = false;
  protected dirty = false;
  protected sessionsDirty = false;
  protected sessionsDirtyFiles = new Set<string>();
  protected sessionPendingFiles = new Set<string>();
  protected sessionDeltas = new Map<
    string,
    { lastSize: number; pendingBytes: number; pendingMessages: number }
  >();
  private sessionWarm = new Set<string>();
  private syncing: Promise<void> | null = null;
  private queuedSessionFiles = new Set<string>();
  private queuedSessionSync: Promise<void> | null = null;
  private readonlyRecoveryAttempts = 0;
  private readonlyRecoverySuccesses = 0;
  private readonlyRecoveryFailures = 0;
  private readonlyRecoveryLastError?: string;

  private static async loadProviderResult(params: {
    cfg: OpenClawConfig;
    agentId: string;
    settings: ResolvedMemorySearchConfig;
  }): Promise<EmbeddingProviderResult> {
    return await createEmbeddingProvider({
      config: params.cfg,
      agentDir: resolveAgentDir(params.cfg, params.agentId),
      ...resolveMemoryPrimaryProviderRequest({ settings: params.settings }),
    });
  }

  static async get(params: {
    cfg: OpenClawConfig;
    agentId: string;
    purpose?: "default" | "status";
  }): Promise<MemoryIndexManager | null> {
    const { cfg, agentId } = params;
    const settings = resolveMemorySearchConfig(cfg, agentId);
    if (!settings) {
      return null;
    }
    const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
    const purpose = params.purpose === "status" ? "status" : "default";
    const key = `${agentId}:${workspaceDir}:${JSON.stringify(settings)}:${purpose}`;
    const statusOnly = params.purpose === "status";
    return await getOrCreateManagedCacheEntry({
      cache: INDEX_CACHE,
      pending: INDEX_CACHE_PENDING,
      key,
      bypassCache: statusOnly,
      create: async () =>
        new MemoryIndexManager({
          cacheKey: key,
          cfg,
          agentId,
          workspaceDir,
          settings,
          purpose: params.purpose,
        }),
    });
  }

  private constructor(params: {
    cacheKey: string;
    cfg: OpenClawConfig;
    agentId: string;
    workspaceDir: string;
    settings: ResolvedMemorySearchConfig;
    providerResult?: EmbeddingProviderResult;
    purpose?: "default" | "status";
  }) {
    super();
    this.cacheKey = params.cacheKey;
    this.cfg = params.cfg;
    this.agentId = params.agentId;
    this.workspaceDir = params.workspaceDir;
    this.settings = params.settings;
    this.provider = null;
    this.requestedProvider = params.settings.provider;
    if (params.providerResult) {
      this.applyProviderResult(params.providerResult);
    }
    this.sources = new Set(params.settings.sources);
    this.db = this.openDatabase();
    this.providerKey = this.computeProviderKey();
    this.cache = {
      enabled: params.settings.cache.enabled,
      maxEntries: params.settings.cache.maxEntries,
    };
    this.fts = { enabled: params.settings.query.hybrid.enabled, available: false };
    this.ensureSchema();
    this.vector = {
      enabled: params.settings.store.vector.enabled,
      available: null,
      extensionPath: params.settings.store.vector.extensionPath,
    };
    const meta = this.readMeta();
    if (meta?.vectorDims) {
      this.vector.dims = meta.vectorDims;
    }
    const statusOnly = params.purpose === "status";
    if (!statusOnly) {
      this.ensureWatcher();
      this.ensureSessionListener();
      this.ensureIntervalSync();
    }
    this.dirty = resolveInitialMemoryDirty({
      hasMemorySource: this.sources.has("memory"),
      statusOnly,
      hasIndexedMeta: Boolean(meta),
    });
    this.batch = this.resolveBatchConfig();
  }

  private applyProviderResult(providerResult: EmbeddingProviderResult): void {
    const providerState = resolveMemoryProviderState(providerResult);
    this.provider = providerState.provider;
    this.fallbackFrom = providerState.fallbackFrom;
    this.fallbackReason = providerState.fallbackReason;
    this.providerUnavailableReason = providerState.providerUnavailableReason;
    this.providerRuntime = providerState.providerRuntime;
    this.providerInitialized = true;
  }

  private async ensureProviderInitialized(): Promise<void> {
    if (this.providerInitialized) {
      return;
    }
    if (!this.providerInitPromise) {
      this.providerInitPromise = (async () => {
        const providerResult = await MemoryIndexManager.loadProviderResult({
          cfg: this.cfg,
          agentId: this.agentId,
          settings: this.settings,
        });
        this.applyProviderResult(providerResult);
        this.providerKey = this.computeProviderKey();
        this.batch = this.resolveBatchConfig();
      })();
    }
    try {
      await this.providerInitPromise;
    } finally {
      if (this.providerInitialized) {
        this.providerInitPromise = null;
      }
    }
  }

  async warmSession(sessionKey?: string): Promise<void> {
    if (!this.settings.sync.onSessionStart) {
      return;
    }
    const key = sessionKey?.trim() || "";
    if (key && this.sessionWarm.has(key)) {
      return;
    }
    void this.sync({ reason: "session-start" }).catch((err) => {
      log.warn(`memory sync failed (session-start): ${String(err)}`);
    });
    if (key) {
      this.sessionWarm.add(key);
    }
  }

  async search(
    query: string,
    opts?: {
      maxResults?: number;
      minScore?: number;
      sessionKey?: string;
      qmdSearchModeOverride?: "query" | "search" | "vsearch";
      onDebug?: (debug: MemorySearchRuntimeDebug) => void;
      /** When set, only these chunk sources are considered (must be enabled for this manager). */
      sources?: MemorySource[];
    },
  ): Promise<MemorySearchResult[]> {
    opts?.onDebug?.({ backend: "builtin" });
    let hasIndexedContent = this.hasIndexedContent();
    if (!hasIndexedContent) {
      try {
        // A fresh process can receive its first search before background watch/session
        // syncs have built the index. Force one synchronous bootstrap so the first
        // lookup after restart does not fail closed with empty results.
        await this.sync({ reason: "search", force: true });
      } catch (err) {
        log.warn(`memory sync failed (search-bootstrap): ${String(err)}`);
      }
      hasIndexedContent = this.hasIndexedContent();
    }
    const preflight = resolveMemorySearchPreflight({
      query,
      hasIndexedContent,
    });
    if (!preflight.shouldSearch) {
      return [];
    }
    const cleaned = preflight.normalizedQuery;
    void this.warmSession(opts?.sessionKey);
    startAsyncSearchSync({
      enabled: this.settings.sync.onSearch,
      dirty: this.dirty,
      sessionsDirty: this.sessionsDirty,
      sync: async (params) => await this.sync(params),
      onError: (err) => {
        log.warn(`memory sync failed (search): ${String(err)}`);
      },
    });
    if (preflight.shouldInitializeProvider) {
      await this.ensureProviderInitialized();
    }
    const minScore = opts?.minScore ?? this.settings.query.minScore;
    const maxResults = opts?.maxResults ?? this.settings.query.maxResults;
    const searchSources =
      opts?.sources && opts.sources.length > 0
        ? [...new Set(opts.sources)].filter((s) => this.sources.has(s))
        : undefined;
    if (opts?.sources && opts.sources.length > 0 && (!searchSources || searchSources.length === 0)) {
      return [];
    }
    const sourceFilterList = searchSources ?? [...this.sources];
    const hybrid = this.settings.query.hybrid;
    const candidates = Math.min(
      200,
      Math.max(1, Math.floor(maxResults * hybrid.candidateMultiplier)),
    );

    // FTS-only mode: no embedding provider available
    if (!this.provider) {
      if (!this.fts.enabled || !this.fts.available) {
        log.warn("memory search: no provider and FTS unavailable");
        return [];
      }

      const fullQueryResults = await this.searchKeyword(
        cleaned,
        candidates,
        {
          boostFallbackRanking: true,
        },
        sourceFilterList,
      ).catch(() => []);
      const resultSets =
        fullQueryResults.length > 0
          ? [fullQueryResults]
          : await Promise.all(
              // Fallback: broaden recall for conversational queries when the
              // exact AND query is too strict to return any results.
              (() => {
                const keywords = extractKeywords(cleaned, {
                  ftsTokenizer: this.settings.store.fts.tokenizer,
                });
                const searchTerms = keywords.length > 0 ? keywords : [cleaned];
                return searchTerms.map((term) =>
                  this.searchKeyword(term, candidates, { boostFallbackRanking: true }, sourceFilterList).catch(
                    () => [],
                  ),
                );
              })(),
            );

      // Merge and deduplicate results, keeping highest score for each chunk
      const seenIds = new Map<string, (typeof resultSets)[0][0]>();
      for (const results of resultSets) {
        for (const result of results) {
          const existing = seenIds.get(result.id);
          if (!existing || result.score > existing.score) {
            seenIds.set(result.id, result);
          }
        }
      }

      const merged = [...seenIds.values()];
      const decayed = await applyTemporalDecayToHybridResults({
        results: merged,
        temporalDecay: hybrid.temporalDecay,
        workspaceDir: this.workspaceDir,
      });
      const sorted = decayed.toSorted((a, b) => b.score - a.score);
      return this.selectScoredResults(sorted, maxResults, minScore, 0);
    }

    // If FTS isn't available, hybrid mode cannot use keyword search; degrade to vector-only.
    const keywordResults =
      hybrid.enabled && this.fts.enabled && this.fts.available
        ? await this.searchKeyword(cleaned, candidates, undefined, sourceFilterList).catch(() => [])
        : [];

    const queryVec = await this.embedQueryWithTimeout(cleaned);
    const hasVector = queryVec.some((v) => v !== 0);
    const vectorResults = hasVector
      ? await this.searchVector(queryVec, candidates, sourceFilterList).catch(() => [])
      : [];

    if (!hybrid.enabled || !this.fts.enabled || !this.fts.available) {
      return vectorResults.filter((entry) => entry.score >= minScore).slice(0, maxResults);
    }

    const merged = await this.mergeHybridResults({
      vector: vectorResults,
      keyword: keywordResults,
      vectorWeight: hybrid.vectorWeight,
      textWeight: hybrid.textWeight,
      mmr: hybrid.mmr,
      temporalDecay: hybrid.temporalDecay,
    });
    const strict = merged.filter((entry) => entry.score >= minScore);
    if (strict.length > 0 || keywordResults.length === 0) {
      return strict.slice(0, maxResults);
    }

    // Hybrid defaults can produce keyword-only matches with max score equal to
    // textWeight (for example 0.3). If minScore is higher (for example 0.35),
    // these exact lexical hits get filtered out even when they are the only
    // relevant results.
    const relaxedMinScore = Math.min(minScore, hybrid.textWeight);
    const keywordKeys = new Set(
      keywordResults.map(
        (entry) => `${entry.source}:${entry.path}:${entry.startLine}:${entry.endLine}`,
      ),
    );
    return this.selectScoredResults(
      merged.filter((entry) =>
        keywordKeys.has(`${entry.source}:${entry.path}:${entry.startLine}:${entry.endLine}`),
      ),
      maxResults,
      minScore,
      relaxedMinScore,
    );
  }

  private selectScoredResults<T extends MemorySearchResult & { score: number }>(
    results: T[],
    maxResults: number,
    minScore: number,
    relaxedMinScore = minScore,
  ): T[] {
    const strict = results.filter((entry) => entry.score >= minScore);
    if (strict.length > 0) {
      return strict.slice(0, maxResults);
    }
    return results.filter((entry) => entry.score >= relaxedMinScore).slice(0, maxResults);
  }

  private hasIndexedContent(): boolean {
    const chunkRow = this.db.prepare(`SELECT 1 as found FROM chunks LIMIT 1`).get() as
      | {
          found?: number;
        }
      | undefined;
    if (chunkRow?.found === 1) {
      return true;
    }
    if (!this.fts.enabled || !this.fts.available) {
      return false;
    }
    const ftsRow = this.db.prepare(`SELECT 1 as found FROM ${FTS_TABLE} LIMIT 1`).get() as
      | {
          found?: number;
        }
      | undefined;
    return ftsRow?.found === 1;
  }

  private async searchVector(
    queryVec: number[],
    limit: number,
    sourceFilterList: MemorySource[],
  ): Promise<Array<MemorySearchResult & { id: string }>> {
    // This method should never be called without a provider
    if (!this.provider) {
      return [];
    }
    const results = await searchVector({
      db: this.db,
      vectorTable: VECTOR_TABLE,
      providerModel: this.provider.model,
      queryVec,
      limit,
      snippetMaxChars: SNIPPET_MAX_CHARS,
      ensureVectorReady: async (dimensions) => await this.ensureVectorReady(dimensions),
      sourceFilterVec: this.buildSourceFilter("c", sourceFilterList),
      sourceFilterChunks: this.buildSourceFilter(undefined, sourceFilterList),
    });
    return results.map((entry) => entry as MemorySearchResult & { id: string });
  }

  private buildFtsQuery(raw: string): string | null {
    return buildFtsQuery(raw);
  }

  private async searchKeyword(
    query: string,
    limit: number,
    options?: { boostFallbackRanking?: boolean },
    sourceFilterList?: MemorySource[],
  ): Promise<Array<MemorySearchResult & { id: string; textScore: number }>> {
    if (!this.fts.enabled || !this.fts.available) {
      return [];
    }
    const sourceFilter = this.buildSourceFilter(undefined, sourceFilterList);
    // In FTS-only mode (no provider), search all models; otherwise filter by current provider's model
    const providerModel = this.provider?.model;
    const results = await searchKeyword({
      db: this.db,
      ftsTable: FTS_TABLE,
      providerModel,
      query,
      ftsTokenizer: this.settings.store.fts.tokenizer,
      limit,
      snippetMaxChars: SNIPPET_MAX_CHARS,
      sourceFilter,
      buildFtsQuery: (raw) => this.buildFtsQuery(raw),
      bm25RankToScore,
      boostFallbackRanking: options?.boostFallbackRanking,
    });
    return results.map((entry) => entry as MemorySearchResult & { id: string; textScore: number });
  }

  private mergeHybridResults(params: {
    vector: Array<MemorySearchResult & { id: string }>;
    keyword: Array<MemorySearchResult & { id: string; textScore: number }>;
    vectorWeight: number;
    textWeight: number;
    mmr?: { enabled: boolean; lambda: number };
    temporalDecay?: { enabled: boolean; halfLifeDays: number };
  }): Promise<MemorySearchResult[]> {
    return mergeHybridResults({
      vector: params.vector.map((r) => ({
        id: r.id,
        path: r.path,
        startLine: r.startLine,
        endLine: r.endLine,
        source: r.source,
        snippet: r.snippet,
        vectorScore: r.score,
      })),
      keyword: params.keyword.map((r) => ({
        id: r.id,
        path: r.path,
        startLine: r.startLine,
        endLine: r.endLine,
        source: r.source,
        snippet: r.snippet,
        textScore: r.textScore,
      })),
      vectorWeight: params.vectorWeight,
      textWeight: params.textWeight,
      mmr: params.mmr,
      temporalDecay: params.temporalDecay,
      workspaceDir: this.workspaceDir,
    }).then((entries) => entries.map((entry) => entry as MemorySearchResult));
  }

  async sync(params?: {
    reason?: string;
    force?: boolean;
    sessionFiles?: string[];
    progress?: (update: MemorySyncProgressUpdate) => void;
  }): Promise<void> {
    if (this.closed) {
      return;
    }
    await this.ensureProviderInitialized();
    if (this.syncing) {
      if (params?.sessionFiles?.some((sessionFile) => sessionFile.trim().length > 0)) {
        return this.enqueueTargetedSessionSync(params.sessionFiles);
      }
      return this.syncing;
    }
    this.syncing = this.runSyncWithReadonlyRecovery(params).finally(() => {
      this.syncing = null;
    });
    return this.syncing ?? Promise.resolve();
  }

  private enqueueTargetedSessionSync(sessionFiles?: string[]): Promise<void> {
    return enqueueMemoryTargetedSessionSync(
      {
        isClosed: () => this.closed,
        getSyncing: () => this.syncing,
        getQueuedSessionFiles: () => this.queuedSessionFiles,
        getQueuedSessionSync: () => this.queuedSessionSync,
        setQueuedSessionSync: (value) => {
          this.queuedSessionSync = value;
        },
        sync: async (params) => await this.sync(params),
      },
      sessionFiles,
    );
  }

  private isReadonlyDbError(err: unknown): boolean {
    return isMemoryReadonlyDbError(err);
  }

  private extractErrorReason(err: unknown): string {
    return extractMemoryErrorReason(err);
  }

  private async runSyncWithReadonlyRecovery(params?: {
    reason?: string;
    force?: boolean;
    sessionFiles?: string[];
    progress?: (update: MemorySyncProgressUpdate) => void;
  }): Promise<void> {
    const getClosed = () => this.closed;
    const getDb = () => this.db;
    const setDb = (value: DatabaseSync) => {
      this.db = value;
    };
    const getReadonlyRecoveryAttempts = () => this.readonlyRecoveryAttempts;
    const setReadonlyRecoveryAttempts = (value: number) => {
      this.readonlyRecoveryAttempts = value;
    };
    const getReadonlyRecoverySuccesses = () => this.readonlyRecoverySuccesses;
    const setReadonlyRecoverySuccesses = (value: number) => {
      this.readonlyRecoverySuccesses = value;
    };
    const getReadonlyRecoveryFailures = () => this.readonlyRecoveryFailures;
    const setReadonlyRecoveryFailures = (value: number) => {
      this.readonlyRecoveryFailures = value;
    };
    const getReadonlyRecoveryLastError = () => this.readonlyRecoveryLastError;
    const setReadonlyRecoveryLastError = (value: string | undefined) => {
      this.readonlyRecoveryLastError = value;
    };
    const state: MemoryReadonlyRecoveryState = {
      get closed() {
        return getClosed();
      },
      get db() {
        return getDb();
      },
      set db(value) {
        setDb(value);
      },
      vector: this.vector,
      get readonlyRecoveryAttempts() {
        return getReadonlyRecoveryAttempts();
      },
      set readonlyRecoveryAttempts(value) {
        setReadonlyRecoveryAttempts(value);
      },
      get readonlyRecoverySuccesses() {
        return getReadonlyRecoverySuccesses();
      },
      set readonlyRecoverySuccesses(value) {
        setReadonlyRecoverySuccesses(value);
      },
      get readonlyRecoveryFailures() {
        return getReadonlyRecoveryFailures();
      },
      set readonlyRecoveryFailures(value) {
        setReadonlyRecoveryFailures(value);
      },
      get readonlyRecoveryLastError() {
        return getReadonlyRecoveryLastError();
      },
      set readonlyRecoveryLastError(value) {
        setReadonlyRecoveryLastError(value);
      },
      runSync: (nextParams) => this.runSync(nextParams),
      openDatabase: () => this.openDatabase(),
      resetVectorState: () => this.resetVectorState(),
      ensureSchema: () => this.ensureSchema(),
      readMeta: () => this.readMeta() ?? undefined,
    };
    await runMemorySyncWithReadonlyRecovery(state, params);
  }

  async readFile(params: {
    relPath: string;
    from?: number;
    lines?: number;
  }): Promise<{ text: string; path: string }> {
    return await readMemoryFile({
      workspaceDir: this.workspaceDir,
      extraPaths: this.settings.extraPaths,
      relPath: params.relPath,
      from: params.from,
      lines: params.lines,
    });
  }

  status(): MemoryProviderStatus {
    const sourceFilter = this.buildSourceFilter();
    const aggregateState = collectMemoryStatusAggregate({
      db: {
        prepare: (sql) => ({
          all: (...args) =>
            this.db.prepare(sql).all(...args) as Array<{
              kind: "files" | "chunks";
              source: MemorySource;
              c: number;
            }>,
        }),
      },
      sources: this.sources,
      sourceFilterSql: sourceFilter.sql,
      sourceFilterParams: sourceFilter.params,
    });

    const providerInfo = resolveStatusProviderInfo({
      provider: this.provider,
      providerInitialized: this.providerInitialized,
      requestedProvider: this.requestedProvider,
      configuredModel: this.settings.model || undefined,
    });

    return {
      backend: "builtin",
      files: aggregateState.files,
      chunks: aggregateState.chunks,
      dirty: this.dirty || this.sessionsDirty,
      workspaceDir: this.workspaceDir,
      dbPath: this.settings.store.path,
      provider: providerInfo.provider,
      model: providerInfo.model,
      requestedProvider: this.requestedProvider,
      sources: Array.from(this.sources),
      extraPaths: this.settings.extraPaths,
      sourceCounts: aggregateState.sourceCounts,
      cache: this.cache.enabled
        ? {
            enabled: true,
            entries:
              (
                this.db.prepare(`SELECT COUNT(*) as c FROM ${EMBEDDING_CACHE_TABLE}`).get() as
                  | { c: number }
                  | undefined
              )?.c ?? 0,
            maxEntries: this.cache.maxEntries,
          }
        : { enabled: false, maxEntries: this.cache.maxEntries },
      fts: {
        enabled: this.fts.enabled,
        available: this.fts.available,
        error: this.fts.loadError,
      },
      fallback: this.fallbackReason
        ? { from: this.fallbackFrom ?? "local", reason: this.fallbackReason }
        : undefined,
      vector: {
        enabled: this.vector.enabled,
        available: this.vector.available ?? undefined,
        extensionPath: this.vector.extensionPath,
        loadError: this.vector.loadError,
        dims: this.vector.dims,
      },
      batch: {
        enabled: this.batch.enabled,
        failures: this.batchFailureCount,
        limit: MEMORY_BATCH_FAILURE_LIMIT,
        wait: this.batch.wait,
        concurrency: this.batch.concurrency,
        pollIntervalMs: this.batch.pollIntervalMs,
        timeoutMs: this.batch.timeoutMs,
        lastError: this.batchFailureLastError,
        lastProvider: this.batchFailureLastProvider,
      },
      custom: {
        searchMode: providerInfo.searchMode,
        providerUnavailableReason: this.providerUnavailableReason,
        readonlyRecovery: {
          attempts: this.readonlyRecoveryAttempts,
          successes: this.readonlyRecoverySuccesses,
          failures: this.readonlyRecoveryFailures,
          lastError: this.readonlyRecoveryLastError,
        },
      },
    };
  }

  async probeVectorAvailability(): Promise<boolean> {
    if (!this.vector.enabled) {
      return false;
    }
    await this.ensureProviderInitialized();
    // FTS-only mode: vector search not available
    if (!this.provider) {
      return false;
    }
    return this.ensureVectorReady();
  }

  async probeEmbeddingAvailability(): Promise<MemoryEmbeddingProbeResult> {
    await this.ensureProviderInitialized();
    // FTS-only mode: embeddings not available but search still works
    if (!this.provider) {
      return {
        ok: false,
        error: this.providerUnavailableReason ?? "No embedding provider available (FTS-only mode)",
      };
    }
    try {
      await this.embedBatchWithRetry(["ping"]);
      return { ok: true };
    } catch (err) {
      const message = formatErrorMessage(err);
      return { ok: false, error: message };
    }
  }

  async close(): Promise<void> {
    if (this.closed) {
      return;
    }
    this.closed = true;
    const pendingSync = this.syncing;
    const pendingProviderInit = this.providerInitPromise;
    if (this.watchTimer) {
      clearTimeout(this.watchTimer);
      this.watchTimer = null;
    }
    if (this.sessionWatchTimer) {
      clearTimeout(this.sessionWatchTimer);
      this.sessionWatchTimer = null;
    }
    if (this.intervalTimer) {
      clearInterval(this.intervalTimer);
      this.intervalTimer = null;
    }
    if (this.watcher) {
      await this.watcher.close();
      this.watcher = null;
    }
    if (this.sessionUnsubscribe) {
      this.sessionUnsubscribe();
      this.sessionUnsubscribe = null;
    }
    await awaitPendingManagerWork({ pendingSync, pendingProviderInit });
    this.db.close();
    INDEX_CACHE.delete(this.cacheKey);
  }
}

¤ Dauer der Verarbeitung: 0.3 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.